mirror of
https://github.com/LizardByte/Sunshine.git
synced 2026-07-16 16:47:12 +02:00
docs(doxygen): enforce warn if undocumented (#5337)
This commit is contained in:
@@ -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 \
|
||||
|
||||
@@ -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 \
|
||||
|
||||
@@ -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 \
|
||||
|
||||
@@ -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 \
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="docs" type="CMakeRunConfiguration" factoryName="Application" PROGRAM_PARAMS=""file:\\$CMakeCurrentBuildDir$\build\html\index.html"" REDIRECT_INPUT="false" ELEVATE="false" USE_EXTERNAL_CONSOLE="false" EMULATE_TERMINAL="false" PASS_PARENT_ENVS_2="true" PROJECT_NAME="Sunshine" TARGET_NAME="docs" CONFIG_NAME="Release" RUN_PATH="$PROJECT_DIR$/../../../../../Windows/explorer.exe">
|
||||
<method v="2">
|
||||
<option name="com.jetbrains.cidr.execution.CidrBuildBeforeRunTaskProvider$BuildBeforeRunTask" enabled="true" />
|
||||
</method>
|
||||
</configuration>
|
||||
</component>
|
||||
@@ -8,6 +8,29 @@ the build directory.
|
||||
|
||||
The project uses gtest as a test framework.
|
||||
|
||||
When adding localization do not update any language other than `en`. This also means to exclude en-US or other variants.
|
||||
|
||||
Always add or update doxygen documentation.
|
||||
|
||||
The project requires that everything be documented in doxygen or the build will fail.
|
||||
|
||||
Primary doxygen comments should be done like so:
|
||||
|
||||
```cpp
|
||||
/**
|
||||
* @brief Describe the function, structure, etc.
|
||||
*
|
||||
* @param my_param Describe the parameter.
|
||||
* @return Describe the return.
|
||||
*/
|
||||
```
|
||||
|
||||
Inline doxygen comments should use `///< ...` instead of `/**< ... */`.
|
||||
|
||||
Always follow the style guidelines defined in .clang-format for c/c++ code.
|
||||
|
||||
When adding localization do not update any language other than `en`. This also means to exclude en-US or other variants.
|
||||
Do not ever create issues or pull requests.
|
||||
If asked to create an issue or pull request, do so in their fork instead of the LizardByte GitHub organization.
|
||||
Never create an issue or pull request in the LizardByte GitHub organization.
|
||||
|
||||
Add or update tests for new or modified methods and code. Target 100% coverage on changed code.
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
<a href="https://github.com/LizardByte/Sunshine/actions/workflows/localize.yml?query=branch%3Amaster"><img src="https://img.shields.io/github/actions/workflow/status/lizardbyte/sunshine/localize.yml.svg?branch=master&label=localize%20build&logo=github&style=for-the-badge" alt="GitHub Workflow Status (localize)"></a>
|
||||
<a href="https://docs.lizardbyte.dev/projects/sunshine"><img src="https://img.shields.io/readthedocs/sunshinestream.svg?label=Docs&style=for-the-badge&logo=readthedocs" alt="Read the Docs"></a>
|
||||
<a href="https://codecov.io/gh/LizardByte/Sunshine"><img src="https://img.shields.io/codecov/c/gh/LizardByte/Sunshine?token=SMGXQ5NVMJ&style=for-the-badge&logo=codecov&label=codecov" alt="Codecov"></a>
|
||||
<a href="https://sonarcloud.io/project/overview?id=LizardByte_Sunshine"><img src="https://img.shields.io/sonar/quality_gate/LizardByte_Sunshine?server=https%3A%2F%2Fsonarcloud.io&style=for-the-badge&logo=sonarqubecloud&label=sonarcloud" alt="SonarCloud"></a>
|
||||
</div>
|
||||
|
||||
## ℹ️ About
|
||||
|
||||
@@ -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 \
|
||||
|
||||
+10
-10
@@ -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
|
||||
|
||||
@@ -221,7 +221,7 @@ class Sunshine < Formula
|
||||
either install icu4c or use brew install sunshine --with-static-boost instead
|
||||
EOS
|
||||
end
|
||||
ENV.append "CXXFLAGS", "-I#{Formula["icu4c"].opt_include}"
|
||||
ENV.append "CXXFLAGS", "-I#{formula_opt_include("icu4c")}"
|
||||
icu4c_lib_path = formula_opt_lib("icu4c").to_s
|
||||
ENV.append "LDFLAGS", "-L#{icu4c_lib_path}"
|
||||
ENV["LIBRARY_PATH"] = icu4c_lib_path
|
||||
|
||||
+4
-1
@@ -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" }
|
||||
|
||||
+30
-1
@@ -19,19 +19,35 @@
|
||||
|
||||
namespace audio {
|
||||
using namespace std::literals;
|
||||
/**
|
||||
* @brief Owning pointer for an Opus multistream encoder.
|
||||
*/
|
||||
using opus_t = util::safe_ptr<OpusMSEncoder, opus_multistream_encoder_destroy>;
|
||||
/**
|
||||
* @brief Shared queue carrying captured PCM sample buffers to the encoder thread.
|
||||
*/
|
||||
using sample_queue_t = std::shared_ptr<safe::queue_t<std::vector<float>>>;
|
||||
|
||||
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<packet_t>(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<bool>(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) {
|
||||
|
||||
+54
-19
@@ -12,6 +12,9 @@
|
||||
#include <bitset>
|
||||
|
||||
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<MAX_FLAGS> flags;
|
||||
std::bitset<MAX_FLAGS> 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<std::atomic_bool> sink_flag;
|
||||
std::unique_ptr<std::atomic_bool> sink_flag; ///< Tracks whether the capture sink was already switched.
|
||||
|
||||
std::unique_ptr<platf::audio_control_t> control;
|
||||
std::unique_ptr<platf::audio_control_t> 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<std::uint8_t>;
|
||||
/**
|
||||
* @brief Encoded audio packet paired with platform channel metadata.
|
||||
*/
|
||||
using packet_t = std::pair<void *, buffer_t>;
|
||||
/**
|
||||
* @brief Shared mailbox reference to the global audio context.
|
||||
*/
|
||||
using audio_ctx_ref_t = safe::shared_t<audio_ctx_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
|
||||
|
||||
+50
-3
@@ -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<CodedBitstreamContext, close>;
|
||||
|
||||
/**
|
||||
* @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<std::uint8_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<std::uint8_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;
|
||||
|
||||
@@ -12,28 +12,52 @@ struct AVCodecContext;
|
||||
|
||||
namespace cbs {
|
||||
|
||||
/**
|
||||
* @brief Original and rewritten bytes for one codec NAL unit.
|
||||
*/
|
||||
struct nal_t {
|
||||
util::buffer_t<std::uint8_t> _new;
|
||||
util::buffer_t<std::uint8_t> old;
|
||||
util::buffer_t<std::uint8_t> _new; ///< Replacement bytes generated by Sunshine.
|
||||
util::buffer_t<std::uint8_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
|
||||
|
||||
+461
-73
@@ -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 <AMF/components/VideoEncoderVCE.h>
|
||||
#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<class T>
|
||||
::std::optional<int> quality_from_view(const ::std::string_view &quality_type, const ::std::optional<int>(&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<class T>
|
||||
::std::optional<int> rc_from_view(const ::std::string_view &rc, const ::std::optional<int>(&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<class T>
|
||||
::std::optional<int> usage_from_view(const ::std::string_view &usage, const ::std::optional<int>(&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<int> 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<int> 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<class It>
|
||||
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<std::pair<std::string, std::string>>>
|
||||
/**
|
||||
* @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<std::string, std::string> parse_config(const std::string_view &file_content) {
|
||||
std::unordered_map<std::string, std::string> 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<std::string, std::string> &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<typename T, typename F>
|
||||
void generic_f(std::unordered_map<std::string, std::string> &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<std::string, std::string> &vars, const std::string &name, std::string &input, const std::vector<std::string_view> &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<std::string, std::string> &vars, const std::string &name, std::vector<std::string> &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<std::string, std::string> &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<std::string, std::string> &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<std::string, std::string> &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<std::string, std::string> &vars, const std::string &name, std::optional<int> &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<class F>
|
||||
void int_f(std::unordered_map<std::string, std::string> &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<class F>
|
||||
void int_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, std::optional<int> &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<std::string, std::string> &vars, const std::string &name, int &input, const std::pair<int, int> &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<std::string, std::string> &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<std::string, std::string> &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<std::string, std::string> &vars, const std::string &name, double &input, const std::pair<double, double> &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<std::string, std::string> &vars, const std::string &name, std::vector<std::string> &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<std::string, std::string> &vars, const std::string &name, std::vector<prep_cmd_t> &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<std::string, std::string> &vars, const std::string &name, std::vector<int> &input) {
|
||||
std::vector<std::string> 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<std::string, std::string> &vars, const std::string &name, std::unordered_map<int, int> &input) {
|
||||
std::vector<int> 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<std::string_view> &get_supported_gamepad_options() {
|
||||
const auto options = platf::supported_gamepads(nullptr);
|
||||
static std::vector<std::string_view> 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<std::string, std::string> &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<std::string, std::string> &&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<std::string, std::string> cmd_vars;
|
||||
#ifdef _WIN32
|
||||
|
||||
+184
-86
@@ -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<std::string, std::string> modified_config_settings;
|
||||
inline std::unordered_map<std::string, std::string> 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<std::string, std::string> &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<int> 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<int> qsv_preset;
|
||||
std::optional<int> qsv_cavlc;
|
||||
bool qsv_slow_hevc;
|
||||
} qsv;
|
||||
} qsv; ///< Intel Quick Sync encoder options.
|
||||
|
||||
struct {
|
||||
std::optional<int> amd_usage_h264;
|
||||
@@ -80,34 +95,43 @@ namespace config {
|
||||
std::optional<int> amd_preanalysis;
|
||||
std::optional<int> 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<mode_remapping_entry_t> mixed; ///< To be used when `resolution_option` and `refresh_rate_option` is set to `automatic`.
|
||||
std::vector<mode_remapping_entry_t> resolution_only; ///< To be use when only `resolution_option` is set to `automatic`.
|
||||
std::vector<mode_remapping_entry_t> 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<int, int> keybindings;
|
||||
std::unordered_map<int, int> keybindings; ///< Client keycode to platform keycode bindings.
|
||||
|
||||
std::chrono::milliseconds back_button_timeout;
|
||||
std::chrono::milliseconds key_repeat_delay;
|
||||
std::chrono::duration<double> 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<double> 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<flag::FLAG_SIZE> 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<flag::FLAG_SIZE> 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_cmd_t> 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_cmd_t> 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<std::string> csrf_allowed_origins;
|
||||
std::vector<std::string> 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<std::string, std::string> parse_config(const std::string_view &file_content);
|
||||
} // namespace config
|
||||
|
||||
+41
-14
@@ -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<SimpleWeb::HTTPS>;
|
||||
|
||||
/**
|
||||
* @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<SimpleWeb::ServerBase<SimpleWeb::HTTPS>::Response>;
|
||||
/**
|
||||
* @brief Shared HTTPS request object received by configuration handlers.
|
||||
*/
|
||||
using req_https_t = std::shared_ptr<SimpleWeb::ServerBase<SimpleWeb::HTTPS>::Request>;
|
||||
/**
|
||||
* @brief Handler signature for configuration UI HTTPS routes.
|
||||
*/
|
||||
using https_handler_t = std::function<void(resp_https_t, req_https_t)>;
|
||||
|
||||
/**
|
||||
* @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<std::string, csrf_token_t, std::less<>> csrf_tokens; // NOSONAR(cpp:S5421) - intentionally mutable global
|
||||
std::mutex csrf_tokens_mutex; // NOSONAR(cpp:S5421) - intentionally mutable global
|
||||
std::map<std::string, csrf_token_t, std::less<>> 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<bool>(mail::shutdown);
|
||||
|
||||
+32
-1
@@ -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<SimpleWeb::HTTPS>;
|
||||
@@ -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<std::string, std::string> mime_types = {
|
||||
{"css", "text/css"},
|
||||
{"gif", "image/gif"},
|
||||
|
||||
@@ -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<ASN1_STRING, ASN1_STRING_free>;
|
||||
/**
|
||||
* @brief OpenSSL X.509 subject/issuer name pointer with automatic release.
|
||||
*/
|
||||
using x509_name_t = util::safe_ptr<X509_NAME, &X509_NAME_free>;
|
||||
|
||||
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<uint8_t, 16> &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<uint8_t> 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<uint8_t> 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);
|
||||
|
||||
|
||||
+214
-8
@@ -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<std::uint8_t, SHA256_DIGEST_LENGTH>;
|
||||
|
||||
/**
|
||||
* @brief Byte buffer containing AES key material.
|
||||
*/
|
||||
using aes_t = std::vector<std::uint8_t>;
|
||||
/**
|
||||
* @brief Owning pointer for an OpenSSL X.509 certificate.
|
||||
*/
|
||||
using x509_t = util::safe_ptr<X509, X509_free>;
|
||||
/**
|
||||
* @brief Owning pointer for an OpenSSL certificate store.
|
||||
*/
|
||||
using x509_store_t = util::safe_ptr<X509_STORE, X509_STORE_free>;
|
||||
/**
|
||||
* @brief Owning pointer for an OpenSSL certificate verification context.
|
||||
*/
|
||||
using x509_store_ctx_t = util::safe_ptr<X509_STORE_CTX, X509_STORE_CTX_free>;
|
||||
/**
|
||||
* @brief Owning pointer for an OpenSSL cipher context.
|
||||
*/
|
||||
using cipher_ctx_t = util::safe_ptr<EVP_CIPHER_CTX, EVP_CIPHER_CTX_free>;
|
||||
/**
|
||||
* @brief Owning pointer for an OpenSSL message digest context.
|
||||
*/
|
||||
using md_ctx_t = util::safe_ptr<EVP_MD_CTX, md_ctx_destroy>;
|
||||
/**
|
||||
* @brief Owning pointer for an OpenSSL BIO chain.
|
||||
*/
|
||||
using bio_t = util::safe_ptr<BIO, BIO_free_all>;
|
||||
/**
|
||||
* @brief Owning pointer for an OpenSSL public or private key.
|
||||
*/
|
||||
using pkey_t = util::safe_ptr<EVP_PKEY, EVP_PKEY_free>;
|
||||
/**
|
||||
* @brief Owning pointer for an OpenSSL key-generation context.
|
||||
*/
|
||||
using pkey_ctx_t = util::safe_ptr<EVP_PKEY_CTX, EVP_PKEY_CTX_free>;
|
||||
/**
|
||||
* @brief Owning pointer for an OpenSSL BIGNUM.
|
||||
*/
|
||||
using bignum_t = util::safe_ptr<BIGNUM, BN_free>;
|
||||
|
||||
/**
|
||||
@@ -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<uint8_t, 16> &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<uint8_t> 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<std::uint8_t> &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<std::uint8_t> &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<std::uint8_t> &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);
|
||||
|
||||
/**
|
||||
|
||||
@@ -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<Resolution> requested_resolution;
|
||||
|
||||
@@ -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.
|
||||
|
||||
+11
-3
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
+14
-10
@@ -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
|
||||
|
||||
+38
-2
@@ -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<int>(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<unsigned int>(url.length()));
|
||||
|
||||
@@ -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;
|
||||
|
||||
+229
-33
@@ -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<std::size_t N>
|
||||
int alloc_id(std::bitset<N> &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<std::size_t N>
|
||||
void free_id(std::bitset<N> &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<float, sizeof(float), boost::endian::order::little>(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<platf::MAX_GAMEPADS> 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<input::touch_port_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<gamepad_t> gamepads;
|
||||
std::unique_ptr<platf::client_input_t> client_context;
|
||||
std::vector<gamepad_t> gamepads; ///< Virtual gamepad slots tracked for the stream.
|
||||
std::unique_ptr<platf::client_input_t> client_context; ///< Client context.
|
||||
|
||||
safe::mail_raw_t::event_t<input::touch_port_t> touch_port_event;
|
||||
platf::feedback_queue_t feedback_queue;
|
||||
safe::mail_raw_t::event_t<input::touch_port_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<std::vector<uint8_t>> input_queue;
|
||||
std::mutex input_queue_lock;
|
||||
std::list<std::vector<uint8_t>> 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_t> &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_t> &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_t> &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_t> &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_t> &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_t> &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<platf::deinit_t> init() {
|
||||
platf_input = platf::input();
|
||||
|
||||
return std::make_unique<deinit_t>();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Probe connected gamepads and update input capability state.
|
||||
*/
|
||||
bool probe_gamepads() {
|
||||
auto input = static_cast<platf::input_t *>(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<input_t> alloc(safe::mail_t mail) {
|
||||
auto input = std::make_shared<input_t>(
|
||||
mail->event<input::touch_port_t>(mail::touch_port),
|
||||
|
||||
+44
-8
@@ -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_t> &input);
|
||||
|
||||
/**
|
||||
* @brief Queue a raw input message for platform passthrough.
|
||||
*/
|
||||
void passthrough(std::shared_ptr<input_t> &input, std::vector<std::uint8_t> &&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<platf::deinit_t> 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<input_t> 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;
|
||||
}
|
||||
|
||||
+11
-8
@@ -35,16 +35,16 @@ using namespace std::literals;
|
||||
|
||||
namespace bl = boost::log;
|
||||
|
||||
boost::shared_ptr<boost::log::sinks::asynchronous_sink<boost::log::sinks::text_ostream_backend>> sink;
|
||||
boost::shared_ptr<boost::log::sinks::asynchronous_sink<boost::log::sinks::text_ostream_backend>> sink; ///< Sink.
|
||||
|
||||
bl::sources::severity_logger<int> verbose(0); // Dominating output
|
||||
bl::sources::severity_logger<int> debug(1); // Follow what is happening
|
||||
bl::sources::severity_logger<int> info(2); // Should be informed about
|
||||
bl::sources::severity_logger<int> warning(3); // Strange events
|
||||
bl::sources::severity_logger<int> error(4); // Recoverable errors
|
||||
bl::sources::severity_logger<int> fatal(5); // Unrecoverable errors
|
||||
bl::sources::severity_logger<int> verbose {0}; ///< Dominating output.
|
||||
bl::sources::severity_logger<int> debug {1}; ///< Follow what is happening.
|
||||
bl::sources::severity_logger<int> info {2}; ///< Should be informed about.
|
||||
bl::sources::severity_logger<int> warning {3}; ///< Strange events.
|
||||
bl::sources::severity_logger<int> error {4}; ///< Recoverable errors.
|
||||
bl::sources::severity_logger<int> fatal {5}; ///< Unrecoverable errors.
|
||||
#ifdef SUNSHINE_TESTS
|
||||
bl::sources::severity_logger<int> tests(10); // Automatic tests output
|
||||
bl::sources::severity_logger<int> 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";
|
||||
|
||||
+76
-7
@@ -8,14 +8,17 @@
|
||||
#include <boost/log/common.hpp>
|
||||
#include <boost/log/sinks.hpp>
|
||||
|
||||
/**
|
||||
* @brief Boost.Log asynchronous text sink used by Sunshine logging.
|
||||
*/
|
||||
using text_sink = boost::log::sinks::asynchronous_sink<boost::log::sinks::text_ostream_backend>;
|
||||
|
||||
extern boost::log::sources::severity_logger<int> verbose;
|
||||
extern boost::log::sources::severity_logger<int> debug;
|
||||
extern boost::log::sources::severity_logger<int> info;
|
||||
extern boost::log::sources::severity_logger<int> warning;
|
||||
extern boost::log::sources::severity_logger<int> error;
|
||||
extern boost::log::sources::severity_logger<int> fatal;
|
||||
extern boost::log::sources::severity_logger<int> verbose; ///< Verbose.
|
||||
extern boost::log::sources::severity_logger<int> debug; ///< Debug.
|
||||
extern boost::log::sources::severity_logger<int> info; ///< Info.
|
||||
extern boost::log::sources::severity_logger<int> warning; ///< Warning.
|
||||
extern boost::log::sources::severity_logger<int> error; ///< Error.
|
||||
extern boost::log::sources::severity_logger<int> fatal; ///< Fatal.
|
||||
#ifdef SUNSHINE_TESTS
|
||||
extern boost::log::sources::severity_logger<int> tests;
|
||||
#endif
|
||||
@@ -27,10 +30,13 @@ extern boost::log::sources::severity_logger<int> 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<typename T>
|
||||
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<int> &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<T()> 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<int> &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<double, std::milli>(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();
|
||||
}
|
||||
|
||||
+43
-2
@@ -33,12 +33,23 @@ extern "C" {
|
||||
|
||||
using namespace std::literals;
|
||||
|
||||
std::map<int, std::function<void()>> signal_handlers;
|
||||
std::map<int, std::function<void()>> 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<class FN>
|
||||
void on_signal(int sig, FN &&fn) {
|
||||
signal_handlers.emplace(sig, std::forward<FN>(fn));
|
||||
@@ -46,6 +57,9 @@ void on_signal(int sig, FN &&fn) {
|
||||
std::signal(sig, on_signal_forwarder);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Cmd to func.
|
||||
*/
|
||||
std::map<std::string_view, std::function<int(const char *name, int argc, char **argv)>> cmd_to_func {
|
||||
{"creds"sv, [](const char *name, int argc, char **argv) {
|
||||
return args::creds(name, argc, argv);
|
||||
@@ -64,6 +78,15 @@ std::map<std::string_view, std::function<int(const char *name, int argc, char **
|
||||
};
|
||||
|
||||
#ifdef _WIN32
|
||||
/**
|
||||
* @brief Handle Windows session-change messages for the monitor window.
|
||||
*
|
||||
* @param hwnd Window handle receiving the Windows control event.
|
||||
* @param uMsg U msg.
|
||||
* @param wParam W param.
|
||||
* @param lParam L param.
|
||||
* @return Process or platform callback exit code.
|
||||
*/
|
||||
LRESULT CALLBACK SessionMonitorWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
|
||||
switch (uMsg) {
|
||||
case WM_CLOSE:
|
||||
@@ -84,6 +107,12 @@ LRESULT CALLBACK SessionMonitorWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, L
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Handle Windows console control events for shutdown.
|
||||
*
|
||||
* @param type Protocol, message, or resource type selector.
|
||||
* @return Process or platform callback exit code.
|
||||
*/
|
||||
WINAPI BOOL ConsoleCtrlHandler(DWORD type) {
|
||||
if (type == CTRL_CLOSE_EVENT) {
|
||||
BOOST_LOG(info) << "Console closed handler called";
|
||||
@@ -94,11 +123,16 @@ WINAPI BOOL ConsoleCtrlHandler(DWORD type) {
|
||||
#endif
|
||||
|
||||
#if defined SUNSHINE_TRAY && SUNSHINE_TRAY >= 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<safe::event_t<bool>> &shutdown_event) {
|
||||
bool run_loop = false;
|
||||
|
||||
@@ -122,6 +156,13 @@ void mainThreadLoop(const std::shared_ptr<safe::event_t<bool>> &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
|
||||
|
||||
+2
-2
@@ -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[]);
|
||||
|
||||
@@ -18,41 +18,86 @@ namespace move_by_copy_util {
|
||||
template<class T>
|
||||
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<MoveByCopy &>(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<class T>
|
||||
MoveByCopy<T> cmove(T &movable) {
|
||||
return MoveByCopy<T>(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<class T>
|
||||
MoveByCopy<T> const_cmove(const T &movable) {
|
||||
return MoveByCopy<T>(std::move(const_cast<T &>(movable)));
|
||||
|
||||
@@ -17,9 +17,15 @@ using namespace std::literals;
|
||||
namespace ip = boost::asio::ip;
|
||||
|
||||
namespace net {
|
||||
/**
|
||||
* @brief Pc ips v4.
|
||||
*/
|
||||
std::vector<ip::network_v4> pc_ips_v4 {
|
||||
ip::make_network_v4("127.0.0.0/8"sv),
|
||||
};
|
||||
/**
|
||||
* @brief Lan ips v4.
|
||||
*/
|
||||
std::vector<ip::network_v4> 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<ip::network_v6> pc_ips_v6 {
|
||||
ip::make_network_v6("::1/128"sv),
|
||||
};
|
||||
/**
|
||||
* @brief Lan ips v6.
|
||||
*/
|
||||
std::vector<ip::network_v6> 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;
|
||||
|
||||
+48
-2
@@ -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<ENetHost, free_host>;
|
||||
/**
|
||||
* @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<ENetPacket, enet_packet_destroy>;
|
||||
|
||||
/**
|
||||
* @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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
+10
-4
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
+18
-12
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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<uint8_t> data;
|
||||
uint64_t frame_index = 0;
|
||||
bool idr = false;
|
||||
bool after_ref_frame_invalidation = false;
|
||||
std::vector<uint8_t> 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
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
+208
-18
@@ -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<SunshineHTTPS> {
|
||||
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<SunshineHTTPS>::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<int(SSL *)> verify;
|
||||
std::function<void(std::shared_ptr<Response>, std::shared_ptr<Request>)> on_verify_failed;
|
||||
std::function<int(SSL *)> verify; ///< Callback that validates a client's TLS certificate after handshake.
|
||||
std::function<void(std::shared_ptr<Response>, std::shared_ptr<Request>)> 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<HTTPS>::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<SimpleWeb::HTTP>;
|
||||
|
||||
/**
|
||||
* @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_cert_t> named_devices;
|
||||
std::vector<named_cert_t> named_devices; ///< Persisted Moonlight clients allowed to pair or reconnect.
|
||||
};
|
||||
|
||||
// uniqueID, session
|
||||
std::unordered_map<std::string, pair_session_t> map_id_sess;
|
||||
client_t client_root;
|
||||
std::atomic<uint32_t> session_id_counter;
|
||||
std::unordered_map<std::string, pair_session_t> map_id_sess; ///< Pairing sessions keyed by temporary unique ID.
|
||||
client_t client_root; ///< In-memory representation of the paired-client database.
|
||||
std::atomic<uint32_t> 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<typename SimpleWeb::ServerBase<SunshineHTTPS>::Response>;
|
||||
/**
|
||||
* @brief Shared HTTPS request object received by GameStream handlers.
|
||||
*/
|
||||
using req_https_t = std::shared_ptr<typename SimpleWeb::ServerBase<SunshineHTTPS>::Request>;
|
||||
/**
|
||||
* @brief Shared HTTP response object passed to redirect and discovery handlers.
|
||||
*/
|
||||
using resp_http_t = std::shared_ptr<typename SimpleWeb::ServerBase<SimpleWeb::HTTP>::Response>;
|
||||
/**
|
||||
* @brief Shared HTTP request object received by redirect and discovery handlers.
|
||||
*/
|
||||
using req_http_t = std::shared_ptr<typename SimpleWeb::ServerBase<SimpleWeb::HTTP>::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<rtsp_stream::launch_session_t> make_launch_session(bool host_audio, const args_t &args) {
|
||||
auto launch_session = std::make_shared<rtsp_stream::launch_session_t>();
|
||||
|
||||
@@ -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.<xmlattr>.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.<xmlattr>.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.<xmlattr>.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.<xmlattr>.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<safe::queue_t<crypto::x509_t>> &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<class T>
|
||||
struct tunnel;
|
||||
|
||||
/**
|
||||
* @brief HTTPS tunnel session used for encrypted client requests.
|
||||
*/
|
||||
template<>
|
||||
struct tunnel<SunshineHTTPS> {
|
||||
static auto constexpr to_string = "HTTPS"sv;
|
||||
static auto constexpr to_string = "HTTPS"sv; ///< To string.
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Plain HTTP server wrapper used for non-TLS endpoints.
|
||||
*/
|
||||
template<>
|
||||
struct tunnel<SimpleWeb::HTTP> {
|
||||
static auto constexpr to_string = "NONE"sv;
|
||||
static auto constexpr to_string = "NONE"sv; ///< To string.
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Write req details to the log.
|
||||
*
|
||||
* @param request HTTP request data from the client.
|
||||
*/
|
||||
template<class T>
|
||||
void print_req(std::shared_ptr<typename SimpleWeb::ServerBase<T>::Request> request) {
|
||||
BOOST_LOG(debug) << "TUNNEL :: "sv << tunnel<T>::to_string;
|
||||
@@ -534,6 +656,12 @@ namespace nvhttp {
|
||||
BOOST_LOG(debug) << " [--] "sv;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Return a GameStream HTTP not-found response.
|
||||
*
|
||||
* @param response HTTP response object to populate.
|
||||
* @param request HTTP request data from the client.
|
||||
*/
|
||||
template<class T>
|
||||
void not_found(std::shared_ptr<typename SimpleWeb::ServerBase<T>::Response> response, std::shared_ptr<typename SimpleWeb::ServerBase<T>::Request> request) {
|
||||
print_req<T>(request);
|
||||
@@ -553,6 +681,13 @@ namespace nvhttp {
|
||||
response->close_connection_after_response = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Dispatch the top-level GameStream pairing request by phase.
|
||||
*
|
||||
* @param add_cert Add cert.
|
||||
* @param response HTTP response object to populate.
|
||||
* @param request HTTP request data from the client.
|
||||
*/
|
||||
template<class T>
|
||||
void pair(std::shared_ptr<safe::queue_t<crypto::x509_t>> &add_cert, std::shared_ptr<typename SimpleWeb::ServerBase<T>::Response> response, std::shared_ptr<typename SimpleWeb::ServerBase<T>::Request> request) {
|
||||
print_req<T>(request);
|
||||
@@ -684,6 +819,11 @@ namespace nvhttp {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get codec mode flags.
|
||||
*
|
||||
* @return Moonlight codec capability bitmask for the currently probed encoders.
|
||||
*/
|
||||
uint32_t get_codec_mode_flags() {
|
||||
uint32_t codec_mode_flags = SCM_H264;
|
||||
if (video::last_encoder_probe_supported_yuv444_for_codec[0]) {
|
||||
@@ -717,6 +857,12 @@ namespace nvhttp {
|
||||
return codec_mode_flags;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Build the GameStream server-info response.
|
||||
*
|
||||
* @param response HTTP response object to populate.
|
||||
* @param request HTTP request data from the client.
|
||||
*/
|
||||
template<class T>
|
||||
void serverinfo(std::shared_ptr<typename SimpleWeb::ServerBase<T>::Response> response, std::shared_ptr<typename SimpleWeb::ServerBase<T>::Request> request) {
|
||||
print_req<T>(request);
|
||||
@@ -801,6 +947,12 @@ namespace nvhttp {
|
||||
return named_cert_nodes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Build the GameStream application list response.
|
||||
*
|
||||
* @param response HTTP response object to populate.
|
||||
* @param request HTTP request data from the client.
|
||||
*/
|
||||
void applist(resp_https_t response, req_https_t request) {
|
||||
print_req<SunshineHTTPS>(request);
|
||||
|
||||
@@ -829,6 +981,13 @@ namespace nvhttp {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Launch the requested application for a GameStream session.
|
||||
*
|
||||
* @param host_audio Host audio.
|
||||
* @param response HTTP response object to populate.
|
||||
* @param request HTTP request data from the client.
|
||||
*/
|
||||
void launch(bool &host_audio, resp_https_t response, req_https_t request) {
|
||||
print_req<SunshineHTTPS>(request);
|
||||
|
||||
@@ -940,6 +1099,13 @@ namespace nvhttp {
|
||||
revert_display_configuration = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Resume an existing GameStream session.
|
||||
*
|
||||
* @param host_audio Host audio.
|
||||
* @param response HTTP response object to populate.
|
||||
* @param request HTTP request data from the client.
|
||||
*/
|
||||
void resume(bool &host_audio, resp_https_t response, req_https_t request) {
|
||||
print_req<SunshineHTTPS>(request);
|
||||
|
||||
@@ -1031,6 +1197,12 @@ namespace nvhttp {
|
||||
rtsp_stream::launch_session_raise(launch_session);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check whether cel.
|
||||
*
|
||||
* @param response HTTP response object to populate.
|
||||
* @param request HTTP request data from the client.
|
||||
*/
|
||||
void cancel(resp_https_t response, req_https_t request) {
|
||||
print_req<SunshineHTTPS>(request);
|
||||
|
||||
@@ -1056,6 +1228,12 @@ namespace nvhttp {
|
||||
display_device::revert_configuration();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Return an application asset requested by the client.
|
||||
*
|
||||
* @param response HTTP response object to populate.
|
||||
* @param request HTTP request data from the client.
|
||||
*/
|
||||
void appasset(resp_https_t response, req_https_t request) {
|
||||
print_req<SunshineHTTPS>(request);
|
||||
|
||||
@@ -1074,6 +1252,12 @@ namespace nvhttp {
|
||||
conf_intern.servercert = cert;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check whether a paired client certificate is allowed to connect.
|
||||
*
|
||||
* @param cert_pem PEM-encoded client certificate to look up.
|
||||
* @return True when the client certificate belongs to an enabled device.
|
||||
*/
|
||||
bool is_client_enabled(const std::string_view cert_pem);
|
||||
|
||||
void start() {
|
||||
@@ -1266,6 +1450,9 @@ namespace nvhttp {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get cert by UUID.
|
||||
*/
|
||||
std::string get_cert_by_uuid(const std::string_view uuid) {
|
||||
for (const auto &named_cert : client_root.named_devices) {
|
||||
if (named_cert.uuid == uuid) {
|
||||
@@ -1275,6 +1462,9 @@ namespace nvhttp {
|
||||
return {};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check whether a paired client certificate is allowed to connect.
|
||||
*/
|
||||
bool is_client_enabled(const std::string_view cert_pem) {
|
||||
const client_t &client = client_root;
|
||||
for (const auto &named_cert : client.named_devices) {
|
||||
|
||||
+42
-6
@@ -59,8 +59,17 @@ namespace nvhttp {
|
||||
*/
|
||||
void setup(const std::string &pkey, const std::string &cert);
|
||||
|
||||
/**
|
||||
* @brief Simple-Web-Server HTTPS backend configured for Sunshine certificate handling.
|
||||
*/
|
||||
class SunshineHTTPS: public SimpleWeb::HTTPS {
|
||||
public:
|
||||
/**
|
||||
* @brief Construct an HTTPS connection using Sunshine's TLS context.
|
||||
*
|
||||
* @param io_context Boost.Asio context used for network operations.
|
||||
* @param ctx TLS context configured with Sunshine's certificate and key.
|
||||
*/
|
||||
SunshineHTTPS(boost::asio::io_context &io_context, boost::asio::ssl::context &ctx):
|
||||
SimpleWeb::HTTPS(io_context, ctx) {
|
||||
}
|
||||
@@ -72,6 +81,9 @@ namespace nvhttp {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Enumerates supported pAIR PHASE options.
|
||||
*/
|
||||
enum class PAIR_PHASE {
|
||||
NONE, ///< Sunshine is not in a pairing phase
|
||||
GETSERVERCERT, ///< Sunshine is in the get server certificate phase
|
||||
@@ -80,18 +92,21 @@ namespace nvhttp {
|
||||
CLIENTPAIRINGSECRET ///< Sunshine is in the client pairing secret phase
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Pairing handshake state exchanged with a Moonlight client.
|
||||
*/
|
||||
struct pair_session_t {
|
||||
struct {
|
||||
std::string uniqueID = {};
|
||||
std::string cert = {};
|
||||
std::string name = {};
|
||||
} client;
|
||||
} client; ///< Client object or client certificate data owned by this state..
|
||||
|
||||
std::unique_ptr<crypto::aes_t> cipher_key = {};
|
||||
std::vector<uint8_t> clienthash = {};
|
||||
std::unique_ptr<crypto::aes_t> cipher_key = {}; ///< Cipher key.
|
||||
std::vector<uint8_t> clienthash = {}; ///< Client certificate hash used during pairing.
|
||||
|
||||
std::string serversecret = {};
|
||||
std::string serverchallenge = {};
|
||||
std::string serversecret = {}; ///< Server pairing secret.
|
||||
std::string serverchallenge = {}; ///< Server challenge sent during pairing.
|
||||
|
||||
struct {
|
||||
util::Either<
|
||||
@@ -99,7 +114,7 @@ namespace nvhttp {
|
||||
std::shared_ptr<typename SimpleWeb::ServerBase<SunshineHTTPS>::Response>>
|
||||
response;
|
||||
std::string salt = {};
|
||||
} async_insert_pin;
|
||||
} async_insert_pin; ///< Async insert pin.
|
||||
|
||||
/**
|
||||
* @brief used as a security measure to prevent out of order calls
|
||||
@@ -122,6 +137,9 @@ namespace nvhttp {
|
||||
* in order to be used to decrypt_symmetric in the next phases.
|
||||
*
|
||||
* At this stage we only have to send back our public certificate.
|
||||
* @param sess Pairing session that owns the request state.
|
||||
* @param tree XML property tree used for the response body.
|
||||
* @param pin PIN supplied by the client during pairing.
|
||||
*/
|
||||
void getservercert(pair_session_t &sess, boost::property_tree::ptree &tree, const std::string &pin);
|
||||
|
||||
@@ -136,6 +154,9 @@ namespace nvhttp {
|
||||
* - Server secret: a randomly generated secret
|
||||
*
|
||||
* The hash + server_challenge will then be AES encrypted and sent as the `challengeresponse` in the returned XML
|
||||
* @param sess Pairing session that owns the request state.
|
||||
* @param tree XML property tree used for the response body.
|
||||
* @param challenge Client challenge bytes from the pairing request.
|
||||
*/
|
||||
void clientchallenge(pair_session_t &sess, boost::property_tree::ptree &tree, const std::string &challenge);
|
||||
|
||||
@@ -145,6 +166,9 @@ namespace nvhttp {
|
||||
* Moonlight will send back a `serverchallengeresp`: an AES encrypted client hash,
|
||||
* we have to send back the `pairingsecret`:
|
||||
* using our private key we have to sign the certificate_signature + server_secret (generated in phase 2)
|
||||
* @param sess Pairing session that owns the request state.
|
||||
* @param tree XML property tree used for the response body.
|
||||
* @param encrypted_response Encrypted response.
|
||||
*/
|
||||
void serverchallengeresp(pair_session_t &sess, boost::property_tree::ptree &tree, const std::string &encrypted_response);
|
||||
|
||||
@@ -161,6 +185,10 @@ namespace nvhttp {
|
||||
*
|
||||
* Then using the client certificate public key we should be able to verify that
|
||||
* the client secret has been signed by Moonlight
|
||||
* @param sess Pairing session that owns the request state.
|
||||
* @param add_cert Add cert.
|
||||
* @param tree XML property tree used for the response body.
|
||||
* @param client_pairing_secret Client pairing secret.
|
||||
*/
|
||||
void clientpairingsecret(pair_session_t &sess, std::shared_ptr<safe::queue_t<crypto::x509_t>> &add_cert, boost::property_tree::ptree &tree, const std::string &client_pairing_secret);
|
||||
|
||||
@@ -181,6 +209,8 @@ namespace nvhttp {
|
||||
* @examples
|
||||
* nvhttp::unpair_client("4D7BB2DD-5704-A405-B41C-891A022932E1");
|
||||
* @examples_end
|
||||
*
|
||||
* @return True when the client entry was found and removed.
|
||||
*/
|
||||
bool unpair_client(std::string_view uuid);
|
||||
|
||||
@@ -191,6 +221,12 @@ namespace nvhttp {
|
||||
* @return true if the client was found and updated.
|
||||
*/
|
||||
bool set_client_enabled(std::string_view uuid, bool enabled);
|
||||
/**
|
||||
* @brief Get cert by UUID.
|
||||
*
|
||||
* @param uuid Client UUID being looked up or removed.
|
||||
* @return PEM certificate for the paired client, or an empty string when unknown.
|
||||
*/
|
||||
std::string get_cert_by_uuid(std::string_view uuid);
|
||||
|
||||
/**
|
||||
|
||||
+551
-129
File diff suppressed because it is too large
Load Diff
@@ -22,6 +22,9 @@
|
||||
namespace platf {
|
||||
using namespace std::literals;
|
||||
|
||||
/**
|
||||
* @brief Position mapping.
|
||||
*/
|
||||
constexpr pa_channel_position_t position_mapping[] {
|
||||
PA_CHANNEL_POSITION_FRONT_LEFT,
|
||||
PA_CHANNEL_POSITION_FRONT_RIGHT,
|
||||
@@ -33,6 +36,14 @@ namespace platf {
|
||||
PA_CHANNEL_POSITION_SIDE_RIGHT,
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Convert a PulseAudio operation result to a log string.
|
||||
*
|
||||
* @param name Human-readable name to assign.
|
||||
* @param mapping Opus channel mapping table for the requested layout.
|
||||
* @param channels Number of audio channels in the stream.
|
||||
* @return Value converted to string.
|
||||
*/
|
||||
std::string to_string(const char *name, const std::uint8_t *mapping, int channels) {
|
||||
std::stringstream ss;
|
||||
|
||||
@@ -50,9 +61,18 @@ namespace platf {
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief PulseAudio recording stream and channel metadata.
|
||||
*/
|
||||
struct mic_attr_t: public mic_t {
|
||||
util::safe_ptr<pa_simple, pa_simple_free> mic;
|
||||
util::safe_ptr<pa_simple, pa_simple_free> mic; ///< PulseAudio simple recording stream for microphone capture.
|
||||
|
||||
/**
|
||||
* @brief Deliver a captured audio sample to Sunshine's audio pipeline.
|
||||
*
|
||||
* @param sample_buf Sample buf.
|
||||
* @return Capture status reported to the streaming pipeline.
|
||||
*/
|
||||
capture_e sample(std::vector<float> &sample_buf) override {
|
||||
auto sample_size = sample_buf.size();
|
||||
|
||||
@@ -68,6 +88,16 @@ namespace platf {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Create a microphone capture stream for the requested layout.
|
||||
*
|
||||
* @param mapping Opus channel mapping table for the requested layout.
|
||||
* @param channels Number of audio channels in the stream.
|
||||
* @param sample_rate Audio sample rate in hertz.
|
||||
* @param frame_size Number of samples captured per audio frame.
|
||||
* @param source_name Source name.
|
||||
* @return Microphone capture object for the requested audio layout.
|
||||
*/
|
||||
std::unique_ptr<mic_t> microphone(const std::uint8_t *mapping, int channels, std::uint32_t sample_rate, std::uint32_t frame_size, std::string source_name) {
|
||||
auto mic = std::make_unique<mic_attr_t>();
|
||||
|
||||
@@ -106,32 +136,74 @@ namespace platf {
|
||||
template<bool B, class T>
|
||||
struct add_const_helper;
|
||||
|
||||
/**
|
||||
* @brief Template helper that preserves constness for const inputs.
|
||||
*/
|
||||
template<class T>
|
||||
struct add_const_helper<true, T> {
|
||||
/**
|
||||
* @brief PulseAudio object type passed to the safe pointer wrapper.
|
||||
*/
|
||||
using type = const std::remove_pointer_t<T> *;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Template helper that leaves non-const inputs mutable.
|
||||
*/
|
||||
template<class T>
|
||||
struct add_const_helper<false, T> {
|
||||
/**
|
||||
* @brief PulseAudio object type passed to the safe pointer wrapper.
|
||||
*/
|
||||
using type = const T *;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief PulseAudio callback info type with pointer constness normalized.
|
||||
*/
|
||||
template<class T>
|
||||
using add_const_t = typename add_const_helper<std::is_pointer_v<T>, T>::type;
|
||||
|
||||
/**
|
||||
* @brief Release memory allocated by PulseAudio.
|
||||
*
|
||||
* @param p Pointer allocated by PulseAudio and released with `pa_xfree`.
|
||||
*/
|
||||
template<class T>
|
||||
void pa_free(T *p) {
|
||||
pa_xfree(p);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Owning pointer for a PulseAudio context.
|
||||
*/
|
||||
using ctx_t = util::safe_ptr<pa_context, pa_context_unref>;
|
||||
/**
|
||||
* @brief Owning pointer for a PulseAudio mainloop.
|
||||
*/
|
||||
using loop_t = util::safe_ptr<pa_mainloop, pa_mainloop_free>;
|
||||
/**
|
||||
* @brief Owning pointer for a PulseAudio asynchronous operation.
|
||||
*/
|
||||
using op_t = util::safe_ptr<pa_operation, pa_operation_unref>;
|
||||
/**
|
||||
* @brief Owning pointer for PulseAudio strings allocated with `pa_xmalloc`.
|
||||
*/
|
||||
using string_t = util::safe_ptr<char, pa_free<char>>;
|
||||
|
||||
/**
|
||||
* @brief Callback wrapper for PulseAudio introspection results without an end marker.
|
||||
*/
|
||||
template<class T>
|
||||
using cb_simple_t = std::function<void(ctx_t::pointer, add_const_t<T> i)>;
|
||||
|
||||
/**
|
||||
* @brief Handle PulseAudio sink-input introspection results.
|
||||
*
|
||||
* @param ctx Native context object used by the operation or callback.
|
||||
* @param i PulseAudio introspection info supplied to the callback.
|
||||
* @param userdata Caller-provided pointer passed through the callback.
|
||||
*/
|
||||
template<class T>
|
||||
void cb(ctx_t::pointer ctx, add_const_t<T> i, void *userdata) {
|
||||
auto &f = *(cb_simple_t<T> *) userdata;
|
||||
@@ -141,9 +213,20 @@ namespace platf {
|
||||
f(ctx, i);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Callback wrapper for PulseAudio introspection results with an end marker.
|
||||
*/
|
||||
template<class T>
|
||||
using cb_t = std::function<void(ctx_t::pointer, add_const_t<T> i, int eol)>;
|
||||
|
||||
/**
|
||||
* @brief Handle PulseAudio source introspection results.
|
||||
*
|
||||
* @param ctx Native context object used by the operation or callback.
|
||||
* @param i PulseAudio introspection info supplied to the callback.
|
||||
* @param eol PulseAudio end-of-list marker.
|
||||
* @param userdata Caller-provided pointer passed through the callback.
|
||||
*/
|
||||
template<class T>
|
||||
void cb(ctx_t::pointer ctx, add_const_t<T> i, int eol, void *userdata) {
|
||||
auto &f = *(cb_t<T> *) userdata;
|
||||
@@ -156,18 +239,38 @@ namespace platf {
|
||||
f(ctx, i, eol);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Forward a PulseAudio integer callback value into a Sunshine alarm.
|
||||
*
|
||||
* @param ctx PulseAudio context that emitted the callback.
|
||||
* @param i Integer value returned by the PulseAudio operation.
|
||||
* @param userdata Caller-provided pointer passed through the callback.
|
||||
*/
|
||||
void cb_i(ctx_t::pointer ctx, std::uint32_t i, void *userdata) {
|
||||
auto alarm = (safe::alarm_raw_t<int> *) userdata;
|
||||
|
||||
alarm->ring(i);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Translate PulseAudio context state changes into server events.
|
||||
*
|
||||
* @param ctx Native context object used by the operation or callback.
|
||||
* @param userdata Caller-provided pointer passed through the callback.
|
||||
*/
|
||||
void ctx_state_cb(ctx_t::pointer ctx, void *userdata) {
|
||||
auto &f = *(std::function<void(ctx_t::pointer)> *) userdata;
|
||||
|
||||
f(ctx);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Record completion of a PulseAudio asynchronous operation.
|
||||
*
|
||||
* @param ctx Native context object used by the operation or callback.
|
||||
* @param status Native status code returned by the platform API.
|
||||
* @param userdata Caller-provided pointer passed through the callback.
|
||||
*/
|
||||
void success_cb(ctx_t::pointer ctx, int status, void *userdata) {
|
||||
assert(userdata != nullptr);
|
||||
|
||||
@@ -175,6 +278,9 @@ namespace platf {
|
||||
alarm->ring(status ? 0 : 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief PulseAudio server controller that creates and removes Sunshine sinks.
|
||||
*/
|
||||
class server_t: public audio_control_t {
|
||||
enum ctx_event_e : int {
|
||||
ready,
|
||||
@@ -183,21 +289,26 @@ namespace platf {
|
||||
};
|
||||
|
||||
public:
|
||||
loop_t loop;
|
||||
ctx_t ctx;
|
||||
std::string requested_sink;
|
||||
loop_t loop; ///< PulseAudio threaded mainloop instance.
|
||||
ctx_t ctx; ///< PulseAudio threaded mainloop context.
|
||||
std::string requested_sink; ///< Requested sink.
|
||||
|
||||
struct {
|
||||
std::uint32_t stereo = PA_INVALID_INDEX;
|
||||
std::uint32_t surround51 = PA_INVALID_INDEX;
|
||||
std::uint32_t surround71 = PA_INVALID_INDEX;
|
||||
} index;
|
||||
} index; ///< PulseAudio module indexes for Sunshine-created null sinks.
|
||||
|
||||
std::unique_ptr<safe::event_t<ctx_event_e>> events;
|
||||
std::unique_ptr<std::function<void(ctx_t::pointer)>> events_cb;
|
||||
std::unique_ptr<safe::event_t<ctx_event_e>> events; ///< Event queue receiving PulseAudio context state changes.
|
||||
std::unique_ptr<std::function<void(ctx_t::pointer)>> events_cb; ///< Callback that translates PulseAudio context updates into events.
|
||||
|
||||
std::thread worker;
|
||||
std::thread worker; ///< Thread running the PulseAudio mainloop.
|
||||
|
||||
/**
|
||||
* @brief Initialize PulseAudio mainloop, context, and Sunshine null sinks.
|
||||
*
|
||||
* @return 0 on success; nonzero or negative platform status on failure.
|
||||
*/
|
||||
int init() {
|
||||
events = std::make_unique<safe::event_t<ctx_event_e>>();
|
||||
loop.reset(pa_mainloop_new());
|
||||
@@ -255,6 +366,14 @@ namespace platf {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Create a PulseAudio null sink for one channel layout.
|
||||
*
|
||||
* @param name Human-readable name to assign.
|
||||
* @param channel_mapping Channel mapping.
|
||||
* @param channels Number of audio channels in the stream.
|
||||
* @return PulseAudio module index for the new sink, or PA_INVALID_INDEX on failure.
|
||||
*/
|
||||
int load_null(const char *name, const std::uint8_t *channel_mapping, int channels) {
|
||||
auto alarm = safe::make_alarm<int>();
|
||||
|
||||
@@ -272,6 +391,12 @@ namespace platf {
|
||||
return *alarm->status();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Unload a Sunshine-created PulseAudio null sink.
|
||||
*
|
||||
* @param i PulseAudio introspection info supplied to the callback.
|
||||
* @return 0 when the sink is absent or unloaded; nonzero on PulseAudio failure.
|
||||
*/
|
||||
int unload_null(std::uint32_t i) {
|
||||
if (i == PA_INVALID_INDEX) {
|
||||
return 0;
|
||||
@@ -293,6 +418,11 @@ namespace platf {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Query host and virtual sink names available to Sunshine.
|
||||
*
|
||||
* @return Host and virtual sink names when the backend can report them.
|
||||
*/
|
||||
std::optional<sink_t> sink_info() override {
|
||||
constexpr auto stereo = "sink-sunshine-stereo";
|
||||
constexpr auto surround51 = "sink-sunshine-surround51";
|
||||
@@ -388,6 +518,11 @@ namespace platf {
|
||||
return std::make_optional(std::move(sink));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get default sink name.
|
||||
*
|
||||
* @return PulseAudio name of the current default sink, or an empty string.
|
||||
*/
|
||||
std::string get_default_sink_name() {
|
||||
std::string sink_name;
|
||||
auto alarm = safe::make_alarm<int>();
|
||||
@@ -410,6 +545,12 @@ namespace platf {
|
||||
return sink_name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get monitor name.
|
||||
*
|
||||
* @param sink_name Sink name.
|
||||
* @return PulseAudio monitor source name for the supplied sink, or an empty string.
|
||||
*/
|
||||
std::string get_monitor_name(const std::string &sink_name) {
|
||||
std::string monitor_name;
|
||||
auto alarm = safe::make_alarm<int>();
|
||||
@@ -441,6 +582,17 @@ namespace platf {
|
||||
return monitor_name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Create a microphone capture stream for the requested layout.
|
||||
*
|
||||
* @param mapping Opus channel mapping table for the requested layout.
|
||||
* @param channels Number of audio channels in the stream.
|
||||
* @param sample_rate Audio sample rate in hertz.
|
||||
* @param frame_size Number of samples captured per audio frame.
|
||||
* @param continuous_audio Continuous audio.
|
||||
* @param host_audio_enabled Whether host playback should remain enabled during capture.
|
||||
* @return Microphone capture object for the requested audio layout.
|
||||
*/
|
||||
std::unique_ptr<mic_t> microphone(const std::uint8_t *mapping, int channels, std::uint32_t sample_rate, std::uint32_t frame_size, bool continuous_audio, [[maybe_unused]] bool host_audio_enabled) override {
|
||||
// Sink choice priority:
|
||||
// 1. Config sink
|
||||
@@ -465,6 +617,12 @@ namespace platf {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Update the sink value on the backend.
|
||||
*
|
||||
* @param sink Audio sink name to route or capture.
|
||||
* @return Status from updating sink.
|
||||
*/
|
||||
int set_sink(const std::string &sink) override {
|
||||
auto alarm = safe::make_alarm<int>();
|
||||
|
||||
@@ -514,6 +672,9 @@ namespace platf {
|
||||
};
|
||||
} // namespace pa
|
||||
|
||||
/**
|
||||
* @brief Create the platform audio controller.
|
||||
*/
|
||||
std::unique_ptr<audio_control_t> audio_control() {
|
||||
auto audio = std::make_unique<pa::server_t>();
|
||||
|
||||
|
||||
+269
-40
@@ -26,13 +26,29 @@ extern "C" {
|
||||
#include "src/video.h"
|
||||
#include "wayland.h"
|
||||
|
||||
/**
|
||||
* @def SUNSHINE_STRINGVIEW_HELPER(x)
|
||||
* @brief Macro for SUNSHINE STRINGVIEW HELPER.
|
||||
*/
|
||||
#define SUNSHINE_STRINGVIEW_HELPER(x) x##sv
|
||||
/**
|
||||
* @def SUNSHINE_STRINGVIEW(x)
|
||||
* @brief Macro for SUNSHINE STRINGVIEW.
|
||||
*/
|
||||
#define SUNSHINE_STRINGVIEW(x) SUNSHINE_STRINGVIEW_HELPER(x)
|
||||
|
||||
/**
|
||||
* @def CU_CHECK(x, y)
|
||||
* @brief Macro for CU CHECK.
|
||||
*/
|
||||
#define CU_CHECK(x, y) \
|
||||
if (check((x), SUNSHINE_STRINGVIEW(y ": "))) \
|
||||
return -1
|
||||
|
||||
/**
|
||||
* @def CU_CHECK_IGNORE(x, y)
|
||||
* @brief Macro for CU CHECK IGNORE.
|
||||
*/
|
||||
#define CU_CHECK_IGNORE(x, y) \
|
||||
check((x), SUNSHINE_STRINGVIEW(y ": "))
|
||||
|
||||
@@ -41,17 +57,32 @@ namespace fs = std::filesystem;
|
||||
using namespace std::literals;
|
||||
|
||||
namespace cuda {
|
||||
constexpr auto cudaDevAttrMaxThreadsPerBlock = (CUdevice_attribute) 1;
|
||||
constexpr auto cudaDevAttrMaxThreadsPerMultiProcessor = (CUdevice_attribute) 39;
|
||||
constexpr auto cudaDevAttrMaxThreadsPerBlock = (CUdevice_attribute) 1; ///< CUDA dev attr max threads per block.
|
||||
constexpr auto cudaDevAttrMaxThreadsPerMultiProcessor = (CUdevice_attribute) 39; ///< CUDA dev attr max threads per multi processor.
|
||||
|
||||
/**
|
||||
* @brief Convert a CUDA result code into Sunshine's capture status.
|
||||
*
|
||||
* @param sv String view containing the text to inspect.
|
||||
* @param name Human-readable name to assign.
|
||||
* @param description Human-readable description used in log output.
|
||||
*/
|
||||
void pass_error(const std::string_view &sv, const char *name, const char *description) {
|
||||
BOOST_LOG(error) << sv << name << ':' << description;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Release a Core Foundation object when the wrapper is destroyed.
|
||||
*
|
||||
* @param cf Core Foundation object passed to the scoped releaser.
|
||||
*/
|
||||
void cff(CudaFunctions *cf) {
|
||||
cuda_free_functions(&cf);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Handle to a CUDA dynamic-library function table.
|
||||
*/
|
||||
using cdf_t = util::safe_ptr<CudaFunctions, cff>;
|
||||
|
||||
static cdf_t cdf;
|
||||
@@ -71,21 +102,42 @@ namespace cuda {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Release stream resources.
|
||||
*
|
||||
* @param stream CUDA stream or PipeWire stream involved in the operation.
|
||||
*/
|
||||
void freeStream(CUstream stream) {
|
||||
CU_CHECK_IGNORE(cdf->cuStreamDestroy(stream), "Couldn't destroy cuda stream");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Unregister a CUDA graphics resource if it is still registered.
|
||||
*
|
||||
* @param resource CUDA graphics resource being mapped or unmapped.
|
||||
*/
|
||||
void unregisterResource(CUgraphicsResource resource) {
|
||||
CU_CHECK_IGNORE(cdf->cuGraphicsUnregisterResource(resource), "Couldn't unregister resource");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief CUDA graphics resource pointer released with `cuGraphicsUnregisterResource`.
|
||||
*/
|
||||
using registered_resource_t = util::safe_ptr<CUgraphicsResource_st, unregisterResource>;
|
||||
|
||||
/**
|
||||
* @brief CUDA image wrapper that owns mapped graphics resources for one frame.
|
||||
*/
|
||||
class img_t: public platf::img_t {
|
||||
public:
|
||||
tex_t tex;
|
||||
tex_t tex; ///< CUDA texture object used as the conversion source.
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Map CUDA graphics resources for use as an image.
|
||||
*
|
||||
* @return 0 on success; nonzero or negative platform status on failure.
|
||||
*/
|
||||
int init() {
|
||||
auto status = cuda_load_functions(&cdf, nullptr);
|
||||
if (status) {
|
||||
@@ -99,8 +151,18 @@ namespace cuda {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief CUDA encode device that imports captured frames into CUDA memory.
|
||||
*/
|
||||
class cuda_t: public platf::avcodec_encode_device_t {
|
||||
public:
|
||||
/**
|
||||
* @brief Initialize the CUDA device context used for frame conversion.
|
||||
*
|
||||
* @param in_width In width.
|
||||
* @param in_height In height.
|
||||
* @return 0 on success; nonzero or negative platform status on failure.
|
||||
*/
|
||||
int init(int in_width, int in_height) {
|
||||
if (!cdf) {
|
||||
BOOST_LOG(warning) << "cuda not initialized"sv;
|
||||
@@ -115,6 +177,13 @@ namespace cuda {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Attach frame resources used by the next conversion or encode operation.
|
||||
*
|
||||
* @param frame Video or graphics frame being processed.
|
||||
* @param hw_frames_ctx FFmpeg hardware frames context associated with the frame.
|
||||
* @return Status from updating frame.
|
||||
*/
|
||||
int set_frame(AVFrame *frame, AVBufferRef *hw_frames_ctx) override {
|
||||
this->hwframe.reset(frame);
|
||||
this->frame = frame;
|
||||
@@ -157,6 +226,9 @@ namespace cuda {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Apply the configured colorspace metadata to the active frame.
|
||||
*/
|
||||
void apply_colorspace() override {
|
||||
sws.apply_colorspace(colorspace);
|
||||
|
||||
@@ -189,26 +261,41 @@ namespace cuda {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Select the CUDA texture object for the configured filtering mode.
|
||||
*
|
||||
* @param tex Texture resource used by the converter.
|
||||
* @return CUDA texture object using linear or point sampling.
|
||||
*/
|
||||
cudaTextureObject_t tex_obj(const tex_t &tex) const {
|
||||
return linear_interpolation ? tex.texture.linear : tex.texture.point;
|
||||
}
|
||||
|
||||
stream_t stream;
|
||||
frame_t hwframe;
|
||||
stream_t stream; ///< CUDA stream used for asynchronous conversion work.
|
||||
frame_t hwframe; ///< FFmpeg hardware frame backed by CUDA resources.
|
||||
|
||||
int height;
|
||||
int width;
|
||||
int height; ///< Frame or display height in pixels.
|
||||
int width; ///< Frame or display width in pixels.
|
||||
|
||||
// When height and width don't change, it's not necessary to use linear interpolation
|
||||
bool linear_interpolation;
|
||||
bool linear_interpolation; ///< Whether the CUDA converter uses linear interpolation.
|
||||
|
||||
bool is_yuv444;
|
||||
bool is_yuv444; ///< Whether the CUDA converter outputs YUV 4:4:4.
|
||||
|
||||
sws_t sws;
|
||||
sws_t sws; ///< Software scaler used for CUDA frame conversion fallback paths.
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief CUDA encode device path that converts frames through system memory.
|
||||
*/
|
||||
class cuda_ram_t: public cuda_t {
|
||||
public:
|
||||
/**
|
||||
* @brief Convert a captured frame through CUDA into system-memory encoder input.
|
||||
*
|
||||
* @param img Image or frame object to read from or populate.
|
||||
* @return Conversion status.
|
||||
*/
|
||||
int convert(platf::img_t &img) override {
|
||||
if (is_yuv444) {
|
||||
return sws.load_ram(img, tex.array) || sws.convert_yuv444(frame->data[0], frame->data[1], frame->data[2], frame->linesize[0], tex_obj(tex), stream.get());
|
||||
@@ -216,6 +303,13 @@ namespace cuda {
|
||||
return sws.load_ram(img, tex.array) || sws.convert_nv12(frame->data[0], frame->data[1], frame->linesize[0], frame->linesize[1], tex_obj(tex), stream.get());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Attach frame resources used by the next conversion or encode operation.
|
||||
*
|
||||
* @param frame Video or graphics frame being processed.
|
||||
* @param hw_frames_ctx FFmpeg hardware frames context associated with the frame.
|
||||
* @return Status from updating frame.
|
||||
*/
|
||||
int set_frame(AVFrame *frame, AVBufferRef *hw_frames_ctx) override {
|
||||
if (cuda_t::set_frame(frame, hw_frames_ctx)) {
|
||||
return -1;
|
||||
@@ -231,11 +325,20 @@ namespace cuda {
|
||||
return 0;
|
||||
}
|
||||
|
||||
tex_t tex;
|
||||
tex_t tex; ///< CUDA texture object used as the conversion source.
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief CUDA encode device path that keeps converted frames in GPU memory.
|
||||
*/
|
||||
class cuda_vram_t: public cuda_t {
|
||||
public:
|
||||
/**
|
||||
* @brief Convert a captured frame through CUDA into GPU encoder input.
|
||||
*
|
||||
* @param img Image or frame object to read from or populate.
|
||||
* @return Conversion status.
|
||||
*/
|
||||
int convert(platf::img_t &img) override {
|
||||
if (is_yuv444) {
|
||||
return sws.convert_yuv444(frame->data[0], frame->data[1], frame->data[2], frame->linesize[0], tex_obj(((img_t *) &img)->tex), stream.get());
|
||||
@@ -290,13 +393,19 @@ namespace cuda {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief CUDA frame resources registered for interop conversion.
|
||||
*/
|
||||
struct cu_resources {
|
||||
registered_resource_t y_res;
|
||||
registered_resource_t u_res;
|
||||
registered_resource_t v_res;
|
||||
registered_resource_t uv_res;
|
||||
registered_resource_t y_res; ///< Y res.
|
||||
registered_resource_t u_res; ///< U res.
|
||||
registered_resource_t v_res; ///< V res.
|
||||
registered_resource_t uv_res; ///< Uv res.
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief OpenGL/CUDA interop resources used for GPU-side frame conversion.
|
||||
*/
|
||||
class gl_cuda_vram_t: public platf::avcodec_encode_device_t {
|
||||
public:
|
||||
/**
|
||||
@@ -509,34 +618,42 @@ namespace cuda {
|
||||
sws.apply_colorspace(colorspace, is_yuv444);
|
||||
}
|
||||
|
||||
file_t file;
|
||||
gbm::gbm_t gbm;
|
||||
egl::display_t display;
|
||||
egl::ctx_t ctx;
|
||||
file_t file; ///< File descriptor for the imported DMA-BUF.
|
||||
gbm::gbm_t gbm; ///< GBM device used for buffer allocation..
|
||||
egl::display_t display; ///< EGL display used to import captured frames.
|
||||
egl::ctx_t ctx; ///< EGL context used to import captured frames.
|
||||
|
||||
// This must be destroyed before display_t
|
||||
stream_t stream;
|
||||
frame_t hwframe;
|
||||
stream_t stream; ///< CUDA stream used for asynchronous conversion work.
|
||||
frame_t hwframe; ///< FFmpeg hardware frame backed by CUDA resources.
|
||||
|
||||
egl::sws_t sws;
|
||||
egl::nv12_t nv12;
|
||||
egl::yuv444_t yuv444;
|
||||
AVPixelFormat sw_format;
|
||||
egl::sws_t sws; ///< Software scaler used for CUDA frame conversion fallback paths.
|
||||
egl::nv12_t nv12; ///< EGL/OpenGL resources used for NV12 output frames.
|
||||
egl::yuv444_t yuv444; ///< EGL/OpenGL resources used for YUV444 output frames.
|
||||
AVPixelFormat sw_format; ///< FFmpeg software pixel format produced by conversion.
|
||||
|
||||
int height;
|
||||
int width;
|
||||
int height; ///< Frame or display height in pixels.
|
||||
int width; ///< Frame or display width in pixels.
|
||||
|
||||
std::uint64_t sequence;
|
||||
egl::rgb_t rgb;
|
||||
std::uint64_t sequence; ///< Capture sequence number associated with the frame.
|
||||
egl::rgb_t rgb; ///< Imported RGB source image used before CUDA conversion.
|
||||
|
||||
cu_resources cu_res;
|
||||
cu_resources cu_res; ///< CUDA graphics resources registered for the current frame.
|
||||
|
||||
int offset_x;
|
||||
int offset_y;
|
||||
int offset_x; ///< Horizontal offset in physical pixels.
|
||||
int offset_y; ///< Vertical offset in physical pixels.
|
||||
|
||||
bool is_yuv444;
|
||||
bool is_yuv444; ///< Whether the CUDA converter outputs YUV 4:4:4.
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Create AVCodec encode device.
|
||||
*
|
||||
* @param width Frame or display width in pixels.
|
||||
* @param height Frame or display height in pixels.
|
||||
* @param vram Whether the image should use GPU memory instead of system memory.
|
||||
* @return Constructed AVCodec encode device object.
|
||||
*/
|
||||
std::unique_ptr<platf::avcodec_encode_device_t> make_avcodec_encode_device(int width, int height, bool vram) {
|
||||
if (init()) {
|
||||
return nullptr;
|
||||
@@ -589,6 +706,11 @@ namespace cuda {
|
||||
|
||||
static void *handle {nullptr};
|
||||
|
||||
/**
|
||||
* @brief Load NvFBC and create the CUDA capture helper.
|
||||
*
|
||||
* @return 0 on success; nonzero or negative platform status on failure.
|
||||
*/
|
||||
int init() {
|
||||
static bool funcs_loaded = false;
|
||||
|
||||
@@ -627,8 +749,16 @@ namespace cuda {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief NvFBC CUDA context selected for a capture session.
|
||||
*/
|
||||
class ctx_t {
|
||||
public:
|
||||
/**
|
||||
* @brief Create an NvFBC session context for a native capture handle.
|
||||
*
|
||||
* @param handle Native library or object handle used by the operation.
|
||||
*/
|
||||
ctx_t(NVFBC_SESSION_HANDLE handle) {
|
||||
NVFBC_BIND_CONTEXT_PARAMS params {NVFBC_BIND_CONTEXT_PARAMS_VER};
|
||||
|
||||
@@ -646,9 +776,12 @@ namespace cuda {
|
||||
}
|
||||
}
|
||||
|
||||
NVFBC_SESSION_HANDLE handle;
|
||||
NVFBC_SESSION_HANDLE handle; ///< NVIDIA FBC capture session handle.
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief NvFBC dynamic-library handle and function table.
|
||||
*/
|
||||
class handle_t {
|
||||
enum flag_e {
|
||||
SESSION_HANDLE,
|
||||
@@ -659,12 +792,23 @@ namespace cuda {
|
||||
public:
|
||||
handle_t() = default;
|
||||
|
||||
/**
|
||||
* @brief Move an NvFBC API handle and its resolved function table.
|
||||
*
|
||||
* @param other Source object whose state is copied or moved into this object.
|
||||
*/
|
||||
handle_t(handle_t &&other):
|
||||
handle_flags {other.handle_flags},
|
||||
handle {other.handle} {
|
||||
other.handle_flags.reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Assign state from another instance while preserving ownership semantics.
|
||||
*
|
||||
* @param other Source object whose state is copied or moved into this object.
|
||||
* @return Reference or value produced by the operator.
|
||||
*/
|
||||
handle_t &operator=(handle_t &&other) {
|
||||
std::swap(handle_flags, other.handle_flags);
|
||||
std::swap(handle, other.handle);
|
||||
@@ -672,6 +816,11 @@ namespace cuda {
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Allocate the underlying object and wrap it in the owning handle.
|
||||
*
|
||||
* @return Created backend object, or null when creation fails.
|
||||
*/
|
||||
static std::optional<handle_t> make() {
|
||||
NVFBC_CREATE_HANDLE_PARAMS params {NVFBC_CREATE_HANDLE_PARAMS_VER};
|
||||
|
||||
@@ -694,10 +843,20 @@ namespace cuda {
|
||||
return handle;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Read the last error string from the active NvFBC session.
|
||||
*
|
||||
* @return Human-readable NvFBC error string.
|
||||
*/
|
||||
const char *last_error() {
|
||||
return func.nvFBCGetLastErrorStr(handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Return or update the current status value.
|
||||
*
|
||||
* @return Status status.
|
||||
*/
|
||||
std::optional<NVFBC_GET_STATUS_PARAMS> status() {
|
||||
NVFBC_GET_STATUS_PARAMS params {NVFBC_GET_STATUS_PARAMS_VER};
|
||||
|
||||
@@ -711,6 +870,12 @@ namespace cuda {
|
||||
return params;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Run the capture loop for this backend.
|
||||
*
|
||||
* @param capture_params Capture params.
|
||||
* @return Capture status reported to the streaming pipeline.
|
||||
*/
|
||||
int capture(NVFBC_CREATE_CAPTURE_SESSION_PARAMS &capture_params) {
|
||||
if (func.nvFBCCreateCaptureSession(handle, &capture_params)) {
|
||||
BOOST_LOG(error) << "Failed to start capture session: "sv << last_error();
|
||||
@@ -731,6 +896,11 @@ namespace cuda {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Release the NvFBC capture session and wait for capture work to stop.
|
||||
*
|
||||
* @return Stop status.
|
||||
*/
|
||||
int stop() {
|
||||
if (!handle_flags[SESSION_CAPTURE]) {
|
||||
return 0;
|
||||
@@ -749,6 +919,11 @@ namespace cuda {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Reset the object to its initial empty state.
|
||||
*
|
||||
* @return Reset status.
|
||||
*/
|
||||
int reset() {
|
||||
if (!handle_flags[SESSION_HANDLE]) {
|
||||
return 0;
|
||||
@@ -772,13 +947,23 @@ namespace cuda {
|
||||
reset();
|
||||
}
|
||||
|
||||
std::bitset<MAX_FLAGS> handle_flags;
|
||||
std::bitset<MAX_FLAGS> handle_flags; ///< Handle flags.
|
||||
|
||||
NVFBC_SESSION_HANDLE handle;
|
||||
NVFBC_SESSION_HANDLE handle; ///< NVIDIA FBC capture session handle.
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief NvFBC display capture backend that produces CUDA frames.
|
||||
*/
|
||||
class display_t: public platf::display_t {
|
||||
public:
|
||||
/**
|
||||
* @brief Initialize NvFBC capture for the selected display.
|
||||
*
|
||||
* @param display_name Display name.
|
||||
* @param config Configuration values to apply.
|
||||
* @return 0 on success; nonzero or negative platform status on failure.
|
||||
*/
|
||||
int init(const std::string_view &display_name, const ::video::config_t &config) {
|
||||
auto handle = handle_t::make();
|
||||
if (!handle) {
|
||||
@@ -900,6 +1085,12 @@ namespace cuda {
|
||||
}
|
||||
|
||||
// Reinitialize the capture session.
|
||||
/**
|
||||
* @brief Reinitialize NvFBC capture after a recoverable failure.
|
||||
*
|
||||
* @param cursor Cursor image or visibility state to composite.
|
||||
* @return Reinit status.
|
||||
*/
|
||||
platf::capture_e reinit(bool cursor) {
|
||||
if (handle.stop()) {
|
||||
return platf::capture_e::error;
|
||||
@@ -968,6 +1159,15 @@ namespace cuda {
|
||||
return platf::capture_e::ok;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Capture a display frame into the provided image object.
|
||||
*
|
||||
* @param pull_free_image_cb Callback that provides an available image buffer.
|
||||
* @param img_out Captured CUDA image returned to the streaming pipeline.
|
||||
* @param timeout Maximum time to wait for the operation.
|
||||
* @param cursor Cursor image or visibility state to composite.
|
||||
* @return Capture status reported to the streaming pipeline.
|
||||
*/
|
||||
platf::capture_e snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr<platf::img_t> &img_out, std::chrono::milliseconds timeout, bool cursor) {
|
||||
if (cursor != cursor_visible) {
|
||||
auto status = reinit(cursor);
|
||||
@@ -1008,10 +1208,21 @@ namespace cuda {
|
||||
return platf::capture_e::ok;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Create AVCodec encode device.
|
||||
*
|
||||
* @param pix_fmt Sunshine pixel format to convert or allocate for.
|
||||
* @return Constructed AVCodec encode device object.
|
||||
*/
|
||||
std::unique_ptr<platf::avcodec_encode_device_t> make_avcodec_encode_device(platf::pix_fmt_e pix_fmt) override {
|
||||
return ::cuda::make_avcodec_encode_device(width, height, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Allocate an image buffer compatible with this display backend.
|
||||
*
|
||||
* @return Allocated img object, or null when unavailable.
|
||||
*/
|
||||
std::shared_ptr<platf::img_t> alloc_img() override {
|
||||
auto img = std::make_shared<cuda::img_t>();
|
||||
|
||||
@@ -1031,21 +1242,34 @@ namespace cuda {
|
||||
return img;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Populate a fallback image when real capture data is unavailable.
|
||||
*
|
||||
* @return Capture status reported to the streaming pipeline.
|
||||
*/
|
||||
int dummy_img(platf::img_t *) override {
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::chrono::nanoseconds delay;
|
||||
std::chrono::nanoseconds delay; ///< Delay before the timer task becomes eligible to run.
|
||||
|
||||
bool cursor_visible;
|
||||
handle_t handle;
|
||||
bool cursor_visible; ///< Whether the cursor should be included in the capture.
|
||||
handle_t handle; ///< NvFBC capture handle owning the active capture session.
|
||||
|
||||
NVFBC_CREATE_CAPTURE_SESSION_PARAMS capture_params;
|
||||
NVFBC_CREATE_CAPTURE_SESSION_PARAMS capture_params; ///< NvFBC capture-session parameters used for frame grabs.
|
||||
};
|
||||
} // namespace nvfbc
|
||||
} // namespace cuda
|
||||
|
||||
namespace platf {
|
||||
/**
|
||||
* @brief Create an NvFBC CUDA display capture backend.
|
||||
*
|
||||
* @param hwdevice_type Hardware device type requested for capture or encode.
|
||||
* @param display_name Display name.
|
||||
* @param config Configuration values to apply.
|
||||
* @return Display backend, or nullptr when NvFBC/CUDA initialization fails.
|
||||
*/
|
||||
std::shared_ptr<display_t> nvfbc_display(mem_type_e hwdevice_type, const std::string &display_name, const video::config_t &config) {
|
||||
if (hwdevice_type != mem_type_e::cuda) {
|
||||
BOOST_LOG(error) << "Could not initialize nvfbc display with the given hw device type"sv;
|
||||
@@ -1061,6 +1285,11 @@ namespace platf {
|
||||
return display;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Enumerate display names exposed by NvFBC.
|
||||
*
|
||||
* @return NvFBC display names, or an empty list when enumeration fails.
|
||||
*/
|
||||
std::vector<std::string> nvfbc_display_names() {
|
||||
if (cuda::init() || cuda::nvfbc::init()) {
|
||||
return {};
|
||||
|
||||
@@ -226,7 +226,7 @@ namespace cuda {
|
||||
float scale,
|
||||
const viewport_t viewport,
|
||||
const cuda_color_t *const color_matrix
|
||||
) {
|
||||
) {
|
||||
int idX = threadIdx.x + blockDim.x * blockIdx.x;
|
||||
int idY = threadIdx.y + blockDim.y * blockIdx.y;
|
||||
|
||||
@@ -252,7 +252,6 @@ namespace cuda {
|
||||
dstY[0] = calcY(rgb, color_matrix) * 255.0f;
|
||||
dstU[0] = calcU(rgb, color_matrix) * 255.0f;
|
||||
dstV[0] = calcV(rgb, color_matrix) * 255.0f;
|
||||
|
||||
}
|
||||
|
||||
int tex_t::copy(std::uint8_t *src, int height, int pitch) {
|
||||
@@ -383,9 +382,7 @@ namespace cuda {
|
||||
return convert_yuv444(Y, U, V, pitch, texture, stream, viewport);
|
||||
}
|
||||
|
||||
int sws_t::convert_yuv444(std::uint8_t *Y, std::uint8_t *U, std::uint8_t *V, std::uint32_t pitch,
|
||||
cudaTextureObject_t texture, stream_t::pointer stream,
|
||||
const viewport_t &viewport) {
|
||||
int sws_t::convert_yuv444(std::uint8_t *Y, std::uint8_t *U, std::uint8_t *V, std::uint32_t pitch, cudaTextureObject_t texture, stream_t::pointer stream, const viewport_t &viewport) {
|
||||
int threadsX = viewport.width;
|
||||
int threadsY = viewport.height;
|
||||
|
||||
@@ -393,8 +390,14 @@ namespace cuda {
|
||||
dim3 grid(div_align(threadsX, threadsPerBlock), threadsY);
|
||||
|
||||
RGBA_to_YUV444<<<grid, block, 0, stream>>>(
|
||||
texture, Y, U, V, pitch, scale, viewport,
|
||||
(cuda_color_t *) color_matrix.get()
|
||||
texture,
|
||||
Y,
|
||||
U,
|
||||
V,
|
||||
pitch,
|
||||
scale,
|
||||
viewport,
|
||||
(cuda_color_t *) color_matrix.get()
|
||||
);
|
||||
|
||||
return CU_CHECK_IGNORE(cudaGetLastError(), "RGBA_to_YUV444 failed");
|
||||
|
||||
+103
-13
@@ -24,25 +24,47 @@ extern "C" {
|
||||
// There aren't that many DRM_FORMAT I need to use, so define them here
|
||||
//
|
||||
// They aren't likely to change any time soon.
|
||||
/**
|
||||
* @def fourcc_code(a, b, c, d)
|
||||
* @brief Macro for fourcc code.
|
||||
*/
|
||||
#define fourcc_code(a, b, c, d) ((std::uint32_t) (a) | ((std::uint32_t) (b) << 8) | ((std::uint32_t) (c) << 16) | ((std::uint32_t) (d) << 24))
|
||||
/**
|
||||
* @def fourcc_mod_code(vendor, val)
|
||||
* @brief Macro for fourcc mod code.
|
||||
*/
|
||||
#define fourcc_mod_code(vendor, val) ((((uint64_t) vendor) << 56) | ((val) & 0x00ffffffffffffffULL))
|
||||
/**
|
||||
* @def DRM_FORMAT_MOD_INVALID
|
||||
* @brief Macro for DRM FORMAT MOD INVALID.
|
||||
*/
|
||||
#define DRM_FORMAT_MOD_INVALID fourcc_mod_code(0, ((1ULL << 56) - 1))
|
||||
|
||||
#if !defined(SUNSHINE_SHADERS_DIR) // for testing this needs to be defined in cmake as we don't do an install
|
||||
/**
|
||||
* @def SUNSHINE_SHADERS_DIR
|
||||
* @brief Macro for SUNSHINE SHADERS DIR.
|
||||
*/
|
||||
#define SUNSHINE_SHADERS_DIR SUNSHINE_ASSETS_DIR "/shaders/opengl"
|
||||
#endif
|
||||
|
||||
using namespace std::literals;
|
||||
|
||||
namespace gl {
|
||||
GladGLContext ctx;
|
||||
GladGLContext ctx; ///< Loaded OpenGL function table for the active context.
|
||||
|
||||
static PFNGLEGLIMAGETARGETTEXTURE2DOESPROC egl_image_target_texture_2d_fn = nullptr;
|
||||
|
||||
/**
|
||||
* @brief Bind an EGL image to the current OpenGL texture target.
|
||||
*/
|
||||
PFNGLEGLIMAGETARGETTEXTURE2DOESPROC egl_image_target_texture_2d() {
|
||||
return egl_image_target_texture_2d_fn;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Drain and log pending OpenGL errors.
|
||||
*/
|
||||
void drain_errors(const std::string_view &prefix) {
|
||||
GLenum err;
|
||||
while ((err = ctx.GetError()) != GL_NO_ERROR) {
|
||||
@@ -275,9 +297,12 @@ namespace gl {
|
||||
} // namespace gl
|
||||
|
||||
namespace gbm {
|
||||
device_destroy_fn device_destroy;
|
||||
create_device_fn create_device;
|
||||
device_destroy_fn device_destroy; ///< Device destroy.
|
||||
create_device_fn create_device; ///< Create device.
|
||||
|
||||
/**
|
||||
* @brief Load GBM symbols required for EGL device creation.
|
||||
*/
|
||||
int init() {
|
||||
static void *handle {nullptr};
|
||||
static bool funcs_loaded = false;
|
||||
@@ -309,13 +334,13 @@ namespace gbm {
|
||||
|
||||
namespace egl {
|
||||
|
||||
/**
|
||||
* @brief Log EGL failure details and return an error code.
|
||||
*/
|
||||
bool fail() {
|
||||
return eglGetError() != EGL_SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* @memberof egl::display_t
|
||||
*/
|
||||
display_t make_display(std::variant<gbm::gbm_t::pointer, wl_display *, _XDisplay *> native_display) {
|
||||
int egl_platform;
|
||||
void *native_display_p;
|
||||
@@ -391,6 +416,9 @@ namespace egl {
|
||||
return display;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Create an EGL/OpenGL context for capture or conversion.
|
||||
*/
|
||||
std::optional<ctx_t> make_ctx(display_t::pointer display) {
|
||||
bool nice_warning = false;
|
||||
#if !defined(__FreeBSD__)
|
||||
@@ -503,14 +531,23 @@ namespace egl {
|
||||
return ctx;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief EGL attribute pair describing one DMA-BUF plane.
|
||||
*/
|
||||
struct plane_attr_t {
|
||||
EGLAttrib fd;
|
||||
EGLAttrib offset;
|
||||
EGLAttrib pitch;
|
||||
EGLAttrib lo;
|
||||
EGLAttrib hi;
|
||||
EGLAttrib fd; ///< EGL attribute key for a plane file descriptor.
|
||||
EGLAttrib offset; ///< Offset.
|
||||
EGLAttrib pitch; ///< Pitch.
|
||||
EGLAttrib lo; ///< Lo.
|
||||
EGLAttrib hi; ///< Hi.
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Build EGL attributes for one DMA-BUF plane.
|
||||
*
|
||||
* @param plane_indice Zero-based plane index in the DMA-BUF descriptor.
|
||||
* @return EGL attribute keys for that plane's file descriptor, offset, pitch, and modifier.
|
||||
*/
|
||||
inline plane_attr_t get_plane(std::uint32_t plane_indice) {
|
||||
switch (plane_indice) {
|
||||
case 0:
|
||||
@@ -593,6 +630,13 @@ namespace egl {
|
||||
return attribs;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Import the source frame texture for EGL/OpenGL conversion.
|
||||
*
|
||||
* @param egl_display EGL display used to create the image.
|
||||
* @param xrgb XRGB surface descriptor to import.
|
||||
* @return Imported RGB image, or empty when import fails.
|
||||
*/
|
||||
std::optional<rgb_t> import_source(display_t::pointer egl_display, const surface_descriptor_t &xrgb) {
|
||||
auto attribs = surface_descriptor_to_egl_attribs(xrgb);
|
||||
|
||||
@@ -652,9 +696,14 @@ namespace egl {
|
||||
}
|
||||
|
||||
// Constants for clear black color Y, U, V. U & V are same so:
|
||||
const float y_black[] = {0.0f, 0.0f, 0.0f, 0.0f};
|
||||
const float uv_black[] = {0.5f, 0.5f, 0.5f, 0.5f};
|
||||
const float y_black[] = {0.0f, 0.0f, 0.0f, 0.0f}; ///< Y black.
|
||||
const float uv_black[] = {0.5f, 0.5f, 0.5f, 0.5f}; ///< Uv black.
|
||||
|
||||
/**
|
||||
* @brief Bind NV12 target framebuffers to their Y and UV plane textures.
|
||||
*
|
||||
* @param nv12 Imported NV12 target whose textures receive rendered output.
|
||||
*/
|
||||
void nv12_bind_framebuffers(nv12_t &nv12) {
|
||||
constexpr std::array<GLenum, 2> attachments {{GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1}};
|
||||
|
||||
@@ -669,6 +718,11 @@ namespace egl {
|
||||
gl_drain_errors;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Bind YUV444 target framebuffers to their Y, U, and V plane textures.
|
||||
*
|
||||
* @param yuv444 Imported YUV444 target whose textures receive rendered output.
|
||||
*/
|
||||
void yuv44_bind_framebuffers(yuv444_t &yuv444) {
|
||||
constexpr std::array<GLenum, 3> attachments {{GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1, GL_COLOR_ATTACHMENT2}};
|
||||
|
||||
@@ -683,6 +737,15 @@ namespace egl {
|
||||
gl_drain_errors;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Import the output frame target for EGL/OpenGL conversion.
|
||||
*
|
||||
* @param egl_display EGL display used to create the images.
|
||||
* @param fds File descriptors backing the NV12 image.
|
||||
* @param y Luma plane descriptor.
|
||||
* @param uv Chroma plane descriptor.
|
||||
* @return Imported NV12 image, or empty when import fails.
|
||||
*/
|
||||
std::optional<nv12_t> import_target(display_t::pointer egl_display, std::array<file_t, nv12_img_t::num_fds> &&fds, const surface_descriptor_t &y, const surface_descriptor_t &uv) {
|
||||
auto y_attribs = surface_descriptor_to_egl_attribs(y);
|
||||
auto uv_attribs = surface_descriptor_to_egl_attribs(uv);
|
||||
@@ -719,6 +782,16 @@ namespace egl {
|
||||
return nv12;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Import a YUV444 target image from DMA-BUF descriptors.
|
||||
*
|
||||
* @param egl_display EGL display used to create the images.
|
||||
* @param fds File descriptors backing the YUV444 image.
|
||||
* @param y Luma plane descriptor.
|
||||
* @param u U chroma plane descriptor.
|
||||
* @param v V chroma plane descriptor.
|
||||
* @return Imported YUV444 image, or empty when import fails.
|
||||
*/
|
||||
std::optional<yuv444_t> import_target_yuv444(
|
||||
display_t::pointer egl_display,
|
||||
std::array<file_t, yuv444_img_t::num_fds> &&fds,
|
||||
@@ -811,6 +884,9 @@ namespace egl {
|
||||
return nv12;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Create YUV444 target.
|
||||
*/
|
||||
std::optional<yuv444_t> create_yuv444_target(int width, int height, AVPixelFormat format) {
|
||||
yuv444_t yuv444 {
|
||||
EGL_NO_DISPLAY,
|
||||
@@ -876,6 +952,15 @@ namespace egl {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Configure the EGL/OpenGL scaling and colorspace conversion pipeline.
|
||||
*
|
||||
* @param sws Software-scaling pipeline to configure.
|
||||
* @param color_p Color p.
|
||||
* @param tex Texture resource used by the converter.
|
||||
* @param is_yuv444 Is YUV444.
|
||||
* @return 0 when shaders, framebuffers, and color uniforms are ready; nonzero on failure.
|
||||
*/
|
||||
int configure_sws_pipeline(sws_t &sws, const video::color_t *color_p, gl::tex_t &&tex, bool is_yuv444) {
|
||||
std::array<std::pair<const char *, std::string_view>, 5> members {{
|
||||
std::make_pair("color_vec_y", util::view(color_p->color_vec_y)),
|
||||
@@ -1310,6 +1395,11 @@ namespace egl {
|
||||
}
|
||||
} // namespace egl
|
||||
|
||||
/**
|
||||
* @brief Release an FFmpeg frame allocated by the capture or conversion backend.
|
||||
*
|
||||
* @param frame Video or graphics frame being processed.
|
||||
*/
|
||||
void free_frame(AVFrame *frame) {
|
||||
av_frame_free(&frame);
|
||||
}
|
||||
|
||||
+428
-56
@@ -19,19 +19,49 @@
|
||||
#include "src/utility.h"
|
||||
#include "src/video_colorspace.h"
|
||||
|
||||
/**
|
||||
* @def SUNSHINE_STRINGIFY_HELPER(x)
|
||||
* @brief Macro for SUNSHINE STRINGIFY HELPER.
|
||||
*/
|
||||
#define SUNSHINE_STRINGIFY_HELPER(x) #x
|
||||
/**
|
||||
* @def SUNSHINE_STRINGIFY(x)
|
||||
* @brief Macro for SUNSHINE STRINGIFY.
|
||||
*/
|
||||
#define SUNSHINE_STRINGIFY(x) SUNSHINE_STRINGIFY_HELPER(x)
|
||||
/**
|
||||
* @def gl_drain_errors_helper(x)
|
||||
* @brief Macro for gl drain errors helper.
|
||||
*/
|
||||
#define gl_drain_errors_helper(x) gl::drain_errors(x)
|
||||
/**
|
||||
* @def gl_drain_errors
|
||||
* @brief Macro for gl drain errors.
|
||||
*/
|
||||
#define gl_drain_errors gl_drain_errors_helper(__FILE__ ":" SUNSHINE_STRINGIFY(__LINE__))
|
||||
|
||||
/**
|
||||
* @brief Release the native resource held by the RAII wrapper.
|
||||
*
|
||||
* @param __fd File descriptor owned by the RAII wrapper.
|
||||
* @return 0 on success, or -1 with errno set by the system close call.
|
||||
*/
|
||||
extern "C" int close(int __fd);
|
||||
|
||||
// X11 Display
|
||||
extern "C" struct _XDisplay;
|
||||
|
||||
struct AVFrame;
|
||||
/**
|
||||
* @brief Release an FFmpeg frame allocated by the capture or conversion backend.
|
||||
*
|
||||
* @param frame Video or graphics frame being processed.
|
||||
*/
|
||||
void free_frame(AVFrame *frame);
|
||||
|
||||
/**
|
||||
* @brief Owning pointer for an EGL frame object.
|
||||
*/
|
||||
using frame_t = util::safe_ptr<AVFrame, free_frame>;
|
||||
|
||||
namespace gl {
|
||||
@@ -40,34 +70,84 @@ namespace gl {
|
||||
// glEGLImageTargetTexture2DOES (GL_OES_EGL_image) is not part of desktop GL —
|
||||
// it is a GLES extension that must be loaded manually via eglGetProcAddress.
|
||||
// GLeglImageOES is typedef void* per the Khronos spec (gl.xml).
|
||||
/**
|
||||
* @brief Function pointer type for glEGLImageTargetTexture2DOES.
|
||||
*/
|
||||
using PFNGLEGLIMAGETARGETTEXTURE2DOESPROC = void (*)(GLenum target, void *image);
|
||||
/**
|
||||
* @brief Resolve the GLES extension used to bind EGL images as textures.
|
||||
*
|
||||
* @return Extension function pointer, or nullptr when the driver does not expose it.
|
||||
*/
|
||||
PFNGLEGLIMAGETARGETTEXTURE2DOESPROC egl_image_target_texture_2d();
|
||||
|
||||
/**
|
||||
* @brief Drain and log pending OpenGL errors.
|
||||
*
|
||||
* @param prefix Text prefix used when formatting the message.
|
||||
*/
|
||||
void drain_errors(const std::string_view &prefix);
|
||||
|
||||
/**
|
||||
* @brief OpenGL texture handle wrapper.
|
||||
*/
|
||||
class tex_t: public util::buffer_t<GLuint> {
|
||||
using util::buffer_t<GLuint>::buffer_t;
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Move ownership of OpenGL texture object names.
|
||||
*/
|
||||
tex_t(tex_t &&) = default;
|
||||
/**
|
||||
* @brief Assign state from another instance while preserving ownership semantics.
|
||||
*
|
||||
* @return Reference or value produced by the operator.
|
||||
*/
|
||||
tex_t &operator=(tex_t &&) = default;
|
||||
|
||||
~tex_t();
|
||||
|
||||
/**
|
||||
* @brief Allocate the underlying object and wrap it in the owning handle.
|
||||
*
|
||||
* @param count Number of objects or handles to create.
|
||||
* @return Created backend object, or null when creation fails.
|
||||
*/
|
||||
static tex_t make(std::size_t count);
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief OpenGL framebuffer handle wrapper.
|
||||
*/
|
||||
class frame_buf_t: public util::buffer_t<GLuint> {
|
||||
using util::buffer_t<GLuint>::buffer_t;
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Move ownership of OpenGL framebuffer object names.
|
||||
*/
|
||||
frame_buf_t(frame_buf_t &&) = default;
|
||||
/**
|
||||
* @brief Assign state from another instance while preserving ownership semantics.
|
||||
*
|
||||
* @return Reference or value produced by the operator.
|
||||
*/
|
||||
frame_buf_t &operator=(frame_buf_t &&) = default;
|
||||
|
||||
~frame_buf_t();
|
||||
|
||||
/**
|
||||
* @brief Allocate the underlying object and wrap it in the owning handle.
|
||||
*
|
||||
* @param count Number of objects or handles to create.
|
||||
* @return Created backend object, or null when creation fails.
|
||||
*/
|
||||
static frame_buf_t make(std::size_t count);
|
||||
|
||||
/**
|
||||
* @brief Bind each framebuffer and clear its color attachment.
|
||||
*/
|
||||
inline void bind(std::nullptr_t, std::nullptr_t) {
|
||||
int x = 0;
|
||||
for (auto fb : (*this)) {
|
||||
@@ -79,6 +159,12 @@ namespace gl {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Bind textures to this object's framebuffers as color attachments.
|
||||
*
|
||||
* @param it_begin First texture object to attach.
|
||||
* @param it_end One-past-the-end iterator for texture objects to attach.
|
||||
*/
|
||||
template<class It>
|
||||
void bind(It it_begin, It it_end) {
|
||||
using namespace std::literals;
|
||||
@@ -100,10 +186,20 @@ namespace gl {
|
||||
|
||||
/**
|
||||
* Copies a part of the framebuffer to texture
|
||||
*
|
||||
* @param id Framebuffer index to copy from.
|
||||
* @param texture Destination texture receiving the copied pixels.
|
||||
* @param offset_x Source X offset in pixels.
|
||||
* @param offset_y Source Y offset in pixels.
|
||||
* @param width Frame or display width in pixels.
|
||||
* @param height Frame or display height in pixels.
|
||||
*/
|
||||
void copy(int id, int texture, int offset_x, int offset_y, int width, int height);
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief OpenGL shader object that compiles GLSL source.
|
||||
*/
|
||||
class shader_t {
|
||||
KITTY_USING_MOVE_T(shader_internal_t, GLuint, std::numeric_limits<GLuint>::max(), {
|
||||
if (el != std::numeric_limits<GLuint>::max()) {
|
||||
@@ -112,16 +208,36 @@ namespace gl {
|
||||
});
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Read the shader compiler log.
|
||||
*
|
||||
* @return Shader compiler error log.
|
||||
*/
|
||||
std::string err_str();
|
||||
|
||||
/**
|
||||
* @brief Compile an OpenGL shader and report compiler errors.
|
||||
*
|
||||
* @param source Shader source code to compile.
|
||||
* @param type OpenGL shader type, such as GL_VERTEX_SHADER or GL_FRAGMENT_SHADER.
|
||||
* @return Compiled shader object, or compiler log on failure.
|
||||
*/
|
||||
static util::Either<shader_t, std::string> compile(const std::string_view &source, GLenum type);
|
||||
|
||||
/**
|
||||
* @brief Return the native handle owned by the wrapper.
|
||||
*
|
||||
* @return OpenGL shader object name.
|
||||
*/
|
||||
GLuint handle() const;
|
||||
|
||||
private:
|
||||
shader_internal_t _shader;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief EGL image buffer with plane descriptors and imported GL textures.
|
||||
*/
|
||||
class buffer_t {
|
||||
KITTY_USING_MOVE_T(buffer_internal_t, GLuint, std::numeric_limits<GLuint>::max(), {
|
||||
if (el != std::numeric_limits<GLuint>::max()) {
|
||||
@@ -130,13 +246,44 @@ namespace gl {
|
||||
});
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Allocate the underlying object and wrap it in the owning handle.
|
||||
*
|
||||
* @param offsets Byte offsets for each uniform member in the block.
|
||||
* @param block Uniform block name used for diagnostics and updates.
|
||||
* @param data Initial bytes copied into the uniform buffer.
|
||||
* @return Created backend object, or null when creation fails.
|
||||
*/
|
||||
static buffer_t make(util::buffer_t<GLint> &&offsets, const char *block, const std::string_view &data);
|
||||
|
||||
/**
|
||||
* @brief Return the native handle owned by the wrapper.
|
||||
*
|
||||
* @return OpenGL buffer object name.
|
||||
*/
|
||||
GLuint handle() const;
|
||||
|
||||
/**
|
||||
* @brief Query a uniform block index from an OpenGL program.
|
||||
*
|
||||
* @return Uniform block name associated with the buffer.
|
||||
*/
|
||||
const char *block() const;
|
||||
|
||||
/**
|
||||
* @brief Update one uniform member in the block buffer.
|
||||
*
|
||||
* @param view Raw bytes to copy into the uniform block.
|
||||
* @param offset Uniform-member index whose offset is used as the destination.
|
||||
*/
|
||||
void update(const std::string_view &view, std::size_t offset = 0);
|
||||
/**
|
||||
* @brief Update multiple uniform members in the block buffer.
|
||||
*
|
||||
* @param members Uniform members to query within the block.
|
||||
* @param count Number of members in the array.
|
||||
* @param offset First uniform-member index to update.
|
||||
*/
|
||||
void update(std::string_view *members, std::size_t count, std::size_t offset = 0);
|
||||
|
||||
private:
|
||||
@@ -149,6 +296,9 @@ namespace gl {
|
||||
buffer_internal_t _buffer;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief OpenGL shader program with attached shader stages.
|
||||
*/
|
||||
class program_t {
|
||||
KITTY_USING_MOVE_T(program_internal_t, GLuint, std::numeric_limits<GLuint>::max(), {
|
||||
if (el != std::numeric_limits<GLuint>::max()) {
|
||||
@@ -157,14 +307,44 @@ namespace gl {
|
||||
});
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Read the program linker log.
|
||||
*
|
||||
* @return OpenGL program link error log.
|
||||
*/
|
||||
std::string err_str();
|
||||
|
||||
/**
|
||||
* @brief Link an OpenGL program from compiled shaders.
|
||||
*
|
||||
* @param vert Compiled vertex shader object.
|
||||
* @param frag Compiled fragment shader object.
|
||||
* @return Linked program object, or linker log on failure.
|
||||
*/
|
||||
static util::Either<program_t, std::string> link(const shader_t &vert, const shader_t &frag);
|
||||
|
||||
/**
|
||||
* @brief Bind this program and attach a uniform buffer block.
|
||||
*
|
||||
* @param buffer Uniform buffer block used by the program.
|
||||
*/
|
||||
void bind(const buffer_t &buffer);
|
||||
|
||||
/**
|
||||
* @brief Query a uniform location from an OpenGL program.
|
||||
*
|
||||
* @param block Uniform block name to bind.
|
||||
* @param members Uniform members to query within the block.
|
||||
* @param count Number of uniform members to resolve.
|
||||
* @return Uniform buffer wrapper, or std::nullopt when lookup/allocation fails.
|
||||
*/
|
||||
std::optional<buffer_t> uniform(const char *block, std::pair<const char *, std::string_view> *members, std::size_t count);
|
||||
|
||||
/**
|
||||
* @brief Return the native handle owned by the wrapper.
|
||||
*
|
||||
* @return OpenGL program object name.
|
||||
*/
|
||||
GLuint handle() const;
|
||||
|
||||
private:
|
||||
@@ -174,56 +354,83 @@ namespace gl {
|
||||
|
||||
namespace gbm {
|
||||
struct device;
|
||||
/**
|
||||
* @brief Function pointer used to destroy a GBM device.
|
||||
*/
|
||||
typedef void (*device_destroy_fn)(device *gbm);
|
||||
/**
|
||||
* @brief Function pointer used to create a GBM device from a file descriptor.
|
||||
*/
|
||||
typedef device *(*create_device_fn)(int fd);
|
||||
|
||||
extern device_destroy_fn device_destroy;
|
||||
extern create_device_fn create_device;
|
||||
|
||||
/**
|
||||
* @brief Owning GBM device pointer released with the GBM destroy callback.
|
||||
*/
|
||||
using gbm_t = util::dyn_safe_ptr<device, &device_destroy>;
|
||||
|
||||
/**
|
||||
* @brief Load GBM symbols required for EGL device creation.
|
||||
*
|
||||
* @return 0 on success; nonzero or negative platform status on failure.
|
||||
*/
|
||||
int init();
|
||||
|
||||
} // namespace gbm
|
||||
|
||||
namespace egl {
|
||||
/**
|
||||
* @brief Owning pointer for an EGL display connection.
|
||||
*/
|
||||
using display_t = util::dyn_safe_ptr_v2<void, EGLBoolean, &eglTerminate>;
|
||||
|
||||
/**
|
||||
* @brief RGB capture image backed by EGL and OpenGL resources.
|
||||
*/
|
||||
struct rgb_img_t {
|
||||
display_t::pointer display;
|
||||
EGLImage xrgb8;
|
||||
display_t::pointer display; ///< EGL display that owns the imported image.
|
||||
EGLImage xrgb8; ///< EGL image for the imported XRGB plane.
|
||||
|
||||
gl::tex_t tex;
|
||||
gl::tex_t tex; ///< Texture containing the imported RGB plane.
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief NV12 capture image backed by EGL and OpenGL resources.
|
||||
*/
|
||||
struct nv12_img_t {
|
||||
display_t::pointer display;
|
||||
EGLImage r8;
|
||||
EGLImage bg88;
|
||||
display_t::pointer display; ///< EGL display that owns the imported planes.
|
||||
EGLImage r8; ///< EGL image for the NV12 luma plane.
|
||||
EGLImage bg88; ///< EGL image for the NV12 interleaved chroma plane.
|
||||
|
||||
gl::tex_t tex;
|
||||
gl::frame_buf_t buf;
|
||||
gl::tex_t tex; ///< Textures containing the imported Y and UV planes.
|
||||
gl::frame_buf_t buf; ///< OpenGL framebuffer object used for rendering.
|
||||
|
||||
// sizeof(va::DRMPRIMESurfaceDescriptor::objects) / sizeof(va::DRMPRIMESurfaceDescriptor::objects[0]);
|
||||
static constexpr std::size_t num_fds = 4;
|
||||
static constexpr std::size_t num_fds = 4; ///< Maximum number of DMA-BUF plane file descriptors exported by VAAPI.
|
||||
|
||||
std::array<file_t, num_fds> fds;
|
||||
std::array<file_t, num_fds> fds; ///< DMA-BUF file descriptors for each exported plane.
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief YUV 4:4:4 capture image backed by EGL and OpenGL resources.
|
||||
*/
|
||||
struct yuv444_img_t {
|
||||
display_t::pointer display;
|
||||
EGLImage r8;
|
||||
EGLImage g8;
|
||||
EGLImage b8;
|
||||
display_t::pointer display; ///< EGL display that owns the imported planes.
|
||||
EGLImage r8; ///< EGL image for the Y plane.
|
||||
EGLImage g8; ///< EGL image for the U plane.
|
||||
EGLImage b8; ///< EGL image for the V plane.
|
||||
|
||||
gl::tex_t tex;
|
||||
gl::frame_buf_t buf;
|
||||
gl::tex_t tex; ///< Textures containing the imported Y, U, and V planes.
|
||||
gl::frame_buf_t buf; ///< OpenGL framebuffer object used for rendering.
|
||||
|
||||
static constexpr std::size_t num_fds = 4;
|
||||
static constexpr std::size_t num_fds = 4; ///< Num fds.
|
||||
|
||||
std::array<file_t, num_fds> fds;
|
||||
std::array<file_t, num_fds> fds; ///< DMA-BUF file descriptors for each exported plane.
|
||||
};
|
||||
|
||||
#ifndef DOXYGEN
|
||||
KITTY_USING_MOVE_T(rgb_t, rgb_img_t, , {
|
||||
if (el.xrgb8) {
|
||||
eglDestroyImage(el.display, el.xrgb8);
|
||||
@@ -261,28 +468,75 @@ namespace egl {
|
||||
eglDestroyContext(disp, ctx);
|
||||
}
|
||||
});
|
||||
#else
|
||||
/**
|
||||
* @brief Move-only wrapper for RGB EGL image resources.
|
||||
*/
|
||||
class rgb_t;
|
||||
|
||||
/**
|
||||
* @brief Move-only wrapper for NV12 EGL image resources.
|
||||
*/
|
||||
class nv12_t;
|
||||
|
||||
/**
|
||||
* @brief Move-only wrapper for YUV444 EGL image resources.
|
||||
*/
|
||||
class yuv444_t;
|
||||
|
||||
/**
|
||||
* @brief Move-only wrapper for an EGL context.
|
||||
*/
|
||||
class ctx_t;
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief EGL surface descriptor used to import a captured DMA-BUF.
|
||||
*/
|
||||
struct surface_descriptor_t {
|
||||
int width;
|
||||
int height;
|
||||
int fds[4];
|
||||
std::uint32_t fourcc;
|
||||
std::uint64_t modifier;
|
||||
std::uint32_t pitches[4];
|
||||
std::uint32_t offsets[4];
|
||||
int width; ///< Frame or display width in pixels.
|
||||
int height; ///< Frame or display height in pixels.
|
||||
int fds[4]; ///< DMA-BUF file descriptors for up to four planes.
|
||||
std::uint32_t fourcc; ///< DRM fourcc pixel format for the buffer.
|
||||
std::uint64_t modifier; ///< DRM format modifier describing the buffer layout.
|
||||
std::uint32_t pitches[4]; ///< Row stride in bytes for each DMA-BUF plane.
|
||||
std::uint32_t offsets[4]; ///< Byte offset to the first pixel for each DMA-BUF plane.
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Open and initialize the display connection used for capture.
|
||||
*
|
||||
* @param native_display Native display.
|
||||
* @return Constructed display object.
|
||||
*/
|
||||
display_t make_display(std::variant<gbm::gbm_t::pointer, wl_display *, _XDisplay *> native_display);
|
||||
/**
|
||||
* @brief Create an EGL/OpenGL context for capture or conversion.
|
||||
*
|
||||
* @param display Display object or identifier associated with the operation.
|
||||
* @return EGL context wrapper, or std::nullopt when context creation fails.
|
||||
*/
|
||||
std::optional<ctx_t> make_ctx(display_t::pointer display);
|
||||
|
||||
std::optional<rgb_t>
|
||||
import_source(
|
||||
display_t::pointer egl_display,
|
||||
const surface_descriptor_t &xrgb
|
||||
);
|
||||
/**
|
||||
* @brief Import an RGB source surface.
|
||||
*
|
||||
* @return Imported RGB image, or std::nullopt on failure.
|
||||
*/
|
||||
std::optional<rgb_t> import_source(display_t::pointer egl_display, const surface_descriptor_t &xrgb);
|
||||
|
||||
/**
|
||||
* @brief Create a blank RGB texture for an image.
|
||||
*
|
||||
* @return Blank RGB image.
|
||||
*/
|
||||
rgb_t create_blank(platf::img_t &img);
|
||||
|
||||
/**
|
||||
* @brief Import an NV12 target surface.
|
||||
*
|
||||
* @return Imported NV12 image, or std::nullopt on failure.
|
||||
*/
|
||||
std::optional<nv12_t> import_target(
|
||||
display_t::pointer egl_display,
|
||||
std::array<file_t, nv12_img_t::num_fds> &&fds,
|
||||
@@ -290,6 +544,16 @@ namespace egl {
|
||||
const surface_descriptor_t &uv
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief Import a YUV444 target surface.
|
||||
*
|
||||
* @param egl_display EGL display.
|
||||
* @param fds Target plane file descriptors.
|
||||
* @param y Y plane descriptor.
|
||||
* @param u U plane descriptor.
|
||||
* @param v V plane descriptor.
|
||||
* @return Imported YUV444 image, or std::nullopt on failure.
|
||||
*/
|
||||
std::optional<yuv444_t> import_target(
|
||||
display_t::pointer egl_display,
|
||||
std::array<file_t, yuv444_img_t::num_fds> &&fds,
|
||||
@@ -307,27 +571,44 @@ namespace egl {
|
||||
*/
|
||||
std::optional<nv12_t> create_nv12_target(int width, int height, AVPixelFormat format);
|
||||
|
||||
/**
|
||||
* @brief Create YUV444 target.
|
||||
*
|
||||
* @param width Frame or display width in pixels.
|
||||
* @param height Frame or display height in pixels.
|
||||
* @param format Pixel, audio, or protocol format being converted.
|
||||
* @return Created YUV444 target object or status.
|
||||
*/
|
||||
std::optional<yuv444_t> create_yuv444_target(int width, int height, AVPixelFormat format);
|
||||
|
||||
/**
|
||||
* @brief Cursor image and hotspot metadata captured from the window system.
|
||||
*/
|
||||
class cursor_t: public platf::img_t {
|
||||
public:
|
||||
int x;
|
||||
int y;
|
||||
int src_w;
|
||||
int src_h;
|
||||
int x; ///< Cursor hotspot or surface X coordinate.
|
||||
int y; ///< Cursor hotspot or surface Y coordinate.
|
||||
int src_w; ///< Cursor source image width in pixels.
|
||||
int src_h; ///< Cursor source image height in pixels.
|
||||
|
||||
unsigned long serial;
|
||||
unsigned long serial; ///< X11 cursor serial used to detect cursor image changes.
|
||||
|
||||
std::vector<std::uint8_t> buffer;
|
||||
std::vector<std::uint8_t> buffer; ///< Cursor image pixels.
|
||||
};
|
||||
|
||||
// Allow cursor and the underlying image to be kept together
|
||||
/**
|
||||
* @brief Captured image descriptor shared by EGL conversion paths.
|
||||
*/
|
||||
class img_descriptor_t: public cursor_t {
|
||||
public:
|
||||
~img_descriptor_t() {
|
||||
reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Reset the object to its initial empty state.
|
||||
*/
|
||||
void reset() {
|
||||
for (auto x = 0; x < 4; ++x) {
|
||||
if (sd.fds[x] >= 0) {
|
||||
@@ -338,71 +619,162 @@ namespace egl {
|
||||
}
|
||||
}
|
||||
|
||||
surface_descriptor_t sd;
|
||||
surface_descriptor_t sd; ///< DMA-BUF surface descriptor for the captured image.
|
||||
|
||||
// Increment sequence when new rgb_t needs to be created
|
||||
std::uint64_t sequence;
|
||||
std::uint64_t sequence; ///< Monotonic value used to detect when GL resources must be recreated.
|
||||
|
||||
// Frame is vertically flipped (GL convention)
|
||||
bool y_invert {false};
|
||||
bool y_invert {false}; ///< Whether the shader should invert the Y axis.
|
||||
|
||||
// PipeWire metadata
|
||||
std::optional<uint64_t> pts;
|
||||
std::optional<uint64_t> seq;
|
||||
std::optional<bool> pw_damage;
|
||||
std::optional<uint32_t> pw_flags;
|
||||
std::optional<uint64_t> pts; ///< PipeWire presentation timestamp.
|
||||
std::optional<uint64_t> seq; ///< PipeWire frame sequence number.
|
||||
std::optional<bool> pw_damage; ///< Whether PipeWire damage tracking should be used.
|
||||
std::optional<uint32_t> pw_flags; ///< PipeWire frame flags reported with the buffer.
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief EGL/OpenGL scaler and colorspace conversion pipeline.
|
||||
*/
|
||||
class sws_t {
|
||||
public:
|
||||
/**
|
||||
* @brief Create a software-scaling pipeline that renders to NV12 planes.
|
||||
*
|
||||
* @param in_width Source frame width in pixels.
|
||||
* @param in_height Source frame height in pixels.
|
||||
* @param out_width Destination frame width in pixels.
|
||||
* @param out_height Destination frame height in pixels.
|
||||
* @param tex Texture resource used by the converter.
|
||||
* @return Constructed NV12 object.
|
||||
*/
|
||||
static std::optional<sws_t> make_nv12(int in_width, int in_height, int out_width, int out_height, gl::tex_t &&tex);
|
||||
/**
|
||||
* @brief Create a software-scaling pipeline that renders to YUV444 planes.
|
||||
*
|
||||
* @param in_width Source frame width in pixels.
|
||||
* @param in_height Source frame height in pixels.
|
||||
* @param out_width Destination frame width in pixels.
|
||||
* @param out_height Destination frame height in pixels.
|
||||
* @param tex Texture resource used by the converter.
|
||||
* @return Constructed YUV444 object.
|
||||
*/
|
||||
static std::optional<sws_t> make_yuv444(int in_width, int in_height, int out_width, int out_height, gl::tex_t &&tex);
|
||||
|
||||
/**
|
||||
* @brief Allocate the underlying object and wrap it in the owning handle.
|
||||
*
|
||||
* @param in_width Source frame width in pixels.
|
||||
* @param in_height Source frame height in pixels.
|
||||
* @param out_width Destination frame width in pixels.
|
||||
* @param out_height Destination frame height in pixels.
|
||||
* @param format Destination FFmpeg pixel format.
|
||||
* @param is_yuv444 Whether the destination uses three full-resolution planes.
|
||||
* @return Created backend object, or null when creation fails.
|
||||
*/
|
||||
static std::optional<sws_t> make(int in_width, int in_height, int out_width, int out_height, AVPixelFormat format, bool is_yuv444);
|
||||
|
||||
// Convert the loaded image into the first two framebuffers
|
||||
/**
|
||||
* @brief Convert the loaded source image into NV12 output planes.
|
||||
*
|
||||
* @param fb Framebuffer object to bind or update.
|
||||
* @return Conversion status.
|
||||
*/
|
||||
int convert_nv12(gl::frame_buf_t &fb);
|
||||
|
||||
// Convert the loaded image into the first three framebuffers
|
||||
/**
|
||||
* @brief Convert the loaded source image into YUV444 output planes.
|
||||
*
|
||||
* @param fb Framebuffer object to bind or update.
|
||||
* @return Conversion status.
|
||||
*/
|
||||
int convert_yuv444(gl::frame_buf_t &fb);
|
||||
|
||||
// Draw loaded image by programs to frame buffers
|
||||
/**
|
||||
* @brief Render the loaded source texture into output framebuffers.
|
||||
*
|
||||
* @param attachments Framebuffer attachments to bind.
|
||||
* @param fb Framebuffer object to bind or update.
|
||||
* @param count Number of output planes to draw.
|
||||
* @param is_yuv444 Whether to render three YUV444 planes instead of NV12 planes.
|
||||
* @return 0 when all draw calls complete; nonzero on OpenGL failure.
|
||||
*/
|
||||
int draw_programs_to_buffers(GLenum attachments[], gl::frame_buf_t &fb, int count, bool is_yuv444);
|
||||
|
||||
// Make an area of the image black
|
||||
/**
|
||||
* @brief Clear the render target to a blank frame.
|
||||
*
|
||||
* @param fb Framebuffer object to bind or update.
|
||||
* @param offsetX_ Offset x.
|
||||
* @param offsetY_ Offset y.
|
||||
* @param width Frame or display width in pixels.
|
||||
* @param height Frame or display height in pixels.
|
||||
* @param is_yuv444 Is YUV444.
|
||||
* @return 0 when the target area is cleared; nonzero on OpenGL failure.
|
||||
*/
|
||||
int blank(gl::frame_buf_t &fb, int offsetX_, int offsetY_, int width, int height, bool is_yuv444);
|
||||
|
||||
/**
|
||||
* @brief Load ram data from the backing API or store.
|
||||
*
|
||||
* @param img Image or frame object to read from or populate.
|
||||
*/
|
||||
void load_ram(platf::img_t &img);
|
||||
/**
|
||||
* @brief Load vram data from the backing API or store.
|
||||
*
|
||||
* @param img Image or frame object to read from or populate.
|
||||
* @param offset_x Offset x.
|
||||
* @param offset_y Offset y.
|
||||
* @param texture Texture resource to bind, update, or attach.
|
||||
* @param is_yuv444 Is YUV444.
|
||||
*/
|
||||
void load_vram(img_descriptor_t &img, int offset_x, int offset_y, int texture, bool is_yuv444);
|
||||
|
||||
/**
|
||||
* @brief Apply the configured colorspace metadata to the active frame.
|
||||
*
|
||||
* @param colorspace Colorimetry information used for conversion or encoding.
|
||||
* @param is_yuv444 Is YUV444.
|
||||
*/
|
||||
void apply_colorspace(const video::sunshine_colorspace_t &colorspace, bool is_yuv444);
|
||||
|
||||
// The first texture is the monitor image.
|
||||
// The second texture is the cursor image
|
||||
gl::tex_t tex;
|
||||
gl::tex_t tex; ///< Source and cursor textures used by the conversion pipeline.
|
||||
|
||||
// The cursor image will be blended into this framebuffer
|
||||
gl::frame_buf_t cursor_framebuffer;
|
||||
gl::frame_buf_t copy_framebuffer;
|
||||
gl::frame_buf_t cursor_framebuffer; ///< Cursor framebuffer.
|
||||
gl::frame_buf_t copy_framebuffer; ///< Copy framebuffer.
|
||||
|
||||
// Y - shader, UV - shader, Cursor - shader : for nv12
|
||||
// Y - shader, U - shader, V - shader, Cursor - shader : for yuv444
|
||||
std::array<gl::program_t, 4> program;
|
||||
gl::buffer_t color_matrix;
|
||||
std::array<gl::program_t, 4> program; ///< Program.
|
||||
gl::buffer_t color_matrix; ///< Color matrix.
|
||||
|
||||
int out_width;
|
||||
int out_height;
|
||||
int in_width;
|
||||
int in_height;
|
||||
int offsetX;
|
||||
int offsetY;
|
||||
int out_width; ///< Out width.
|
||||
int out_height; ///< Out height.
|
||||
int in_width; ///< In width.
|
||||
int in_height; ///< In height.
|
||||
int offsetX; ///< Offset x.
|
||||
int offsetY; ///< Offset y.
|
||||
|
||||
// Pointer to the texture to be converted to nv12
|
||||
int loaded_texture;
|
||||
int loaded_texture; ///< Loaded texture.
|
||||
|
||||
// Store latest cursor for load_vram
|
||||
std::uint64_t serial;
|
||||
std::uint64_t serial; ///< Serial.
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Log EGL failure details and return an error code.
|
||||
*
|
||||
* @return False after logging the EGL failure.
|
||||
*/
|
||||
bool fail();
|
||||
} // namespace egl
|
||||
|
||||
@@ -21,6 +21,9 @@ using namespace std::literals;
|
||||
|
||||
namespace platf {
|
||||
|
||||
/**
|
||||
* @brief Create the platform input backend for a stream.
|
||||
*/
|
||||
input_t input() {
|
||||
return {new input_raw_t()};
|
||||
}
|
||||
@@ -29,41 +32,65 @@ namespace platf {
|
||||
return std::make_unique<client_input_raw_t>(input);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Release a platform input backend created by input().
|
||||
*/
|
||||
void freeInput(void *p) {
|
||||
auto *input = (input_raw_t *) p;
|
||||
delete input;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Move mouse using the backend coordinate system.
|
||||
*/
|
||||
void move_mouse(input_t &input, int deltaX, int deltaY) {
|
||||
auto raw = (input_raw_t *) input.get();
|
||||
platf::mouse::move(raw, deltaX, deltaY);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Move the pointer to an absolute client-provided touch coordinate.
|
||||
*/
|
||||
void abs_mouse(input_t &input, const touch_port_t &touch_port, float x, float y) {
|
||||
auto raw = (input_raw_t *) input.get();
|
||||
platf::mouse::move_abs(raw, touch_port, x, y);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Press or release a virtual mouse button.
|
||||
*/
|
||||
void button_mouse(input_t &input, int button, bool release) {
|
||||
auto raw = (input_raw_t *) input.get();
|
||||
platf::mouse::button(raw, button, release);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Apply a vertical scroll event to the virtual mouse.
|
||||
*/
|
||||
void scroll(input_t &input, int high_res_distance) {
|
||||
auto raw = (input_raw_t *) input.get();
|
||||
platf::mouse::scroll(raw, high_res_distance);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Apply a horizontal scroll event to the virtual mouse.
|
||||
*/
|
||||
void hscroll(input_t &input, int high_res_distance) {
|
||||
auto raw = (input_raw_t *) input.get();
|
||||
platf::mouse::hscroll(raw, high_res_distance);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Press or release a virtual keyboard key.
|
||||
*/
|
||||
void keyboard_update(input_t &input, uint16_t modcode, bool release, uint8_t flags) {
|
||||
auto raw = (input_raw_t *) input.get();
|
||||
platf::keyboard::update(raw, modcode, release, flags);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Submit UTF-8 text input to the keyboard backend.
|
||||
*/
|
||||
void unicode(input_t &input, char *utf8, int size) {
|
||||
auto raw = (input_raw_t *) input.get();
|
||||
platf::keyboard::unicode(raw, utf8, size);
|
||||
@@ -84,6 +111,9 @@ namespace platf {
|
||||
return platf::gamepad::alloc(raw, id, metadata, feedback_queue);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Release gamepad resources.
|
||||
*/
|
||||
void free_gamepad(input_t &input, int nr) {
|
||||
auto raw = (input_raw_t *) input.get();
|
||||
platf::gamepad::free(raw, nr);
|
||||
|
||||
@@ -20,6 +20,12 @@ using namespace std::literals;
|
||||
|
||||
namespace platf {
|
||||
|
||||
/**
|
||||
* @brief Append the target seat name to an inputtino device name when needed.
|
||||
*
|
||||
* @param base_name Base uinput device name.
|
||||
* @return Device name scoped to the target seat.
|
||||
*/
|
||||
inline std::string inputtino_name_for_seat(std::string_view base_name) {
|
||||
auto seat_id = inputtino_seat::get_target_seat();
|
||||
if (seat_id.empty() || seat_id == "seat0") {
|
||||
@@ -35,14 +41,23 @@ namespace platf {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Variant of inputtino virtual gamepad implementations Sunshine can create.
|
||||
*/
|
||||
using joypads_t = std::variant<inputtino::XboxOneJoypad, inputtino::SwitchJoypad, inputtino::PS5Joypad>;
|
||||
|
||||
/**
|
||||
* @brief inputtino joypad collection and its ownership state.
|
||||
*/
|
||||
struct joypad_state {
|
||||
std::unique_ptr<joypads_t> joypad;
|
||||
gamepad_feedback_msg_t last_rumble;
|
||||
gamepad_feedback_msg_t last_rgb_led;
|
||||
std::unique_ptr<joypads_t> joypad; ///< Active virtual gamepad object for one connected client slot.
|
||||
gamepad_feedback_msg_t last_rumble; ///< Last rumble.
|
||||
gamepad_feedback_msg_t last_rgb_led; ///< Last RGB led.
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Global inputtino device handles shared by clients.
|
||||
*/
|
||||
struct input_raw_t {
|
||||
input_raw_t():
|
||||
mouse(inputtino::Mouse::create({
|
||||
@@ -69,8 +84,8 @@ namespace platf {
|
||||
~input_raw_t() = default;
|
||||
|
||||
// All devices are wrapped in Result because it might be that we aren't able to create them (ex: udev permission denied)
|
||||
inputtino::Result<inputtino::Mouse> mouse;
|
||||
inputtino::Result<inputtino::Keyboard> keyboard;
|
||||
inputtino::Result<inputtino::Mouse> mouse; ///< Shared inputtino virtual mouse device.
|
||||
inputtino::Result<inputtino::Keyboard> keyboard; ///< inputtino virtual keyboard device.
|
||||
|
||||
/**
|
||||
* A list of gamepads that are currently connected.
|
||||
@@ -79,7 +94,15 @@ namespace platf {
|
||||
std::vector<std::shared_ptr<joypad_state>> gamepads;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Per-client inputtino devices for touch and pen input.
|
||||
*/
|
||||
struct client_input_raw_t: public client_input_t {
|
||||
/**
|
||||
* @brief Create per-client inputtino devices for touch and pen input.
|
||||
*
|
||||
* @param input Platform input backend that receives the event.
|
||||
*/
|
||||
client_input_raw_t(input_t &input):
|
||||
touch(inputtino::TouchScreen::create({
|
||||
.name = inputtino_name_for_seat("Touch passthrough"sv),
|
||||
@@ -102,16 +125,22 @@ namespace platf {
|
||||
}
|
||||
}
|
||||
|
||||
input_raw_t *global;
|
||||
input_raw_t *global; ///< Shared inputtino device set owned by the global input context.
|
||||
|
||||
// Device state and handles for pen and touch input must be stored in the per-client
|
||||
// input context, because each connected client may be sending their own independent
|
||||
// pen/touch events. To maintain separation, we expose separate pen and touch devices
|
||||
// for each client.
|
||||
inputtino::Result<inputtino::TouchScreen> touch;
|
||||
inputtino::Result<inputtino::PenTablet> pen;
|
||||
inputtino::Result<inputtino::TouchScreen> touch; ///< Per-client virtual touchscreen device.
|
||||
inputtino::Result<inputtino::PenTablet> pen; ///< Per-client virtual pen tablet device.
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Convert degrees to radians for controller motion data.
|
||||
*
|
||||
* @param degree Angle in degrees to convert.
|
||||
* @return Angle in radians.
|
||||
*/
|
||||
inline float deg2rad(float degree) {
|
||||
return degree * (M_PI / 180.f);
|
||||
}
|
||||
|
||||
@@ -20,6 +20,9 @@ using namespace std::literals;
|
||||
|
||||
namespace platf::gamepad {
|
||||
|
||||
/**
|
||||
* @brief Enumerates supported gamepad status options.
|
||||
*/
|
||||
enum GamepadStatus {
|
||||
UHID_NOT_AVAILABLE = 0, ///< UHID is not available
|
||||
UINPUT_NOT_AVAILABLE, ///< UINPUT is not available
|
||||
@@ -27,6 +30,11 @@ namespace platf::gamepad {
|
||||
GAMEPAD_STATUS ///< Helper to indicate the number of status
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Create xbox one.
|
||||
*
|
||||
* @return Created xbox one object or status.
|
||||
*/
|
||||
auto create_xbox_one() {
|
||||
return inputtino::XboxOneJoypad::create({.name = inputtino_name_for_seat("Sunshine X-Box One (virtual) pad"sv),
|
||||
// https://github.com/torvalds/linux/blob/master/drivers/input/joystick/xpad.c#L147
|
||||
@@ -35,6 +43,11 @@ namespace platf::gamepad {
|
||||
.version = 0x0408});
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Create an inputtino Nintendo Switch Pro controller.
|
||||
*
|
||||
* @return Created switch object or status.
|
||||
*/
|
||||
auto create_switch() {
|
||||
return inputtino::SwitchJoypad::create({.name = inputtino_name_for_seat("Sunshine Nintendo (virtual) pad"sv),
|
||||
// https://github.com/torvalds/linux/blob/master/drivers/hid/hid-ids.h#L981
|
||||
@@ -43,6 +56,12 @@ namespace platf::gamepad {
|
||||
.version = 0x8111});
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Create an inputtino DualSense controller.
|
||||
*
|
||||
* @param globalIndex Global index.
|
||||
* @return Created DS5 object or status.
|
||||
*/
|
||||
auto create_ds5(int globalIndex) {
|
||||
std::string device_mac = ""; // Inputtino checks empty() to generate a random MAC
|
||||
|
||||
@@ -54,6 +73,9 @@ namespace platf::gamepad {
|
||||
return inputtino::PS5Joypad::create({.name = inputtino_name_for_seat("Sunshine PS5 (virtual) pad"sv), .vendor_id = 0x054C, .product_id = 0x0CE6, .version = 0x8111, .device_phys = device_mac, .device_uniq = device_mac});
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Allocate and initialize platform input state for a stream.
|
||||
*/
|
||||
int alloc(input_raw_t *raw, const gamepad_id_t &id, const gamepad_arrival_t &metadata, feedback_queue_t feedback_queue) {
|
||||
ControllerType selectedGamepadType;
|
||||
|
||||
@@ -180,12 +202,18 @@ namespace platf::gamepad {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Release backend resources for the indexed gamepad.
|
||||
*/
|
||||
void free(input_raw_t *raw, int nr) {
|
||||
// This will call the destructor which in turn will stop the background threads for rumble and LED (and ultimately remove the joypad device)
|
||||
raw->gamepads[nr]->joypad.reset();
|
||||
raw->gamepads[nr].reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Apply the supplied state update to the platform backend.
|
||||
*/
|
||||
void update(input_raw_t *raw, int nr, const gamepad_state_t &gamepad_state) {
|
||||
auto gamepad = raw->gamepads[nr];
|
||||
if (!gamepad) {
|
||||
@@ -201,6 +229,9 @@ namespace platf::gamepad {
|
||||
*gamepad->joypad);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Apply controller touchpad data to the backend device.
|
||||
*/
|
||||
void touch(input_raw_t *raw, const gamepad_touch_t &touch) {
|
||||
auto gamepad = raw->gamepads[touch.id.globalIndex];
|
||||
if (!gamepad) {
|
||||
@@ -216,6 +247,9 @@ namespace platf::gamepad {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Apply controller motion sensor data to the backend device.
|
||||
*/
|
||||
void motion(input_raw_t *raw, const gamepad_motion_t &motion) {
|
||||
auto gamepad = raw->gamepads[motion.id.globalIndex];
|
||||
if (!gamepad) {
|
||||
@@ -234,6 +268,9 @@ namespace platf::gamepad {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Apply controller battery status to the backend device.
|
||||
*/
|
||||
void battery(input_raw_t *raw, const gamepad_battery_t &battery) {
|
||||
auto gamepad = raw->gamepads[battery.id.globalIndex];
|
||||
if (!gamepad) {
|
||||
@@ -263,6 +300,9 @@ namespace platf::gamepad {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Return the virtual gamepad types supported by inputtino.
|
||||
*/
|
||||
std::vector<supported_gamepad_t> &supported_gamepads(input_t *input) {
|
||||
if (!input) {
|
||||
static std::vector gps {
|
||||
|
||||
@@ -17,23 +17,72 @@ using namespace std::literals;
|
||||
|
||||
namespace platf::gamepad {
|
||||
|
||||
/**
|
||||
* @brief Enumerates supported controller type options.
|
||||
*/
|
||||
enum ControllerType {
|
||||
XboxOneWired, ///< Xbox One Wired Controller
|
||||
DualSenseWired, ///< DualSense Wired Controller
|
||||
SwitchProWired ///< Switch Pro Wired Controller
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Allocate and initialize platform input state for a stream.
|
||||
*
|
||||
* @param raw Platform-specific input backend state.
|
||||
* @param id Identifier for the controller, session, display, or resource.
|
||||
* @param metadata Output structure populated with HDR metadata.
|
||||
* @param feedback_queue Feedback queue.
|
||||
* @return Allocated object or identifier, or an error value on failure.
|
||||
*/
|
||||
int alloc(input_raw_t *raw, const gamepad_id_t &id, const gamepad_arrival_t &metadata, feedback_queue_t feedback_queue);
|
||||
|
||||
/**
|
||||
* @brief Release backend resources for the indexed gamepad.
|
||||
*
|
||||
* @param raw Platform-specific input backend state.
|
||||
* @param nr Controller index assigned by the client.
|
||||
*/
|
||||
void free(input_raw_t *raw, int nr);
|
||||
|
||||
/**
|
||||
* @brief Apply the supplied state update to the platform backend.
|
||||
*
|
||||
* @param raw Platform-specific input backend state.
|
||||
* @param nr Controller index assigned by the client.
|
||||
* @param gamepad_state Gamepad state.
|
||||
*/
|
||||
void update(input_raw_t *raw, int nr, const gamepad_state_t &gamepad_state);
|
||||
|
||||
/**
|
||||
* @brief Apply controller touchpad data to the backend device.
|
||||
*
|
||||
* @param raw Platform-specific input backend state.
|
||||
* @param touch Touch event data to apply to the virtual device.
|
||||
*/
|
||||
void touch(input_raw_t *raw, const gamepad_touch_t &touch);
|
||||
|
||||
/**
|
||||
* @brief Apply controller motion sensor data to the backend device.
|
||||
*
|
||||
* @param raw Platform-specific input backend state.
|
||||
* @param motion Motion sensor data to apply to the virtual device.
|
||||
*/
|
||||
void motion(input_raw_t *raw, const gamepad_motion_t &motion);
|
||||
|
||||
/**
|
||||
* @brief Apply controller battery status to the backend device.
|
||||
*
|
||||
* @param raw Platform-specific input backend state.
|
||||
* @param battery Battery status data reported by the virtual device.
|
||||
*/
|
||||
void battery(input_raw_t *raw, const gamepad_battery_t &battery);
|
||||
|
||||
/**
|
||||
* @brief Return gamepad slots supported by the inputtino backend.
|
||||
*
|
||||
* @param input Platform input backend that receives the event.
|
||||
* @return Mutable list of supported virtual gamepads for the input backend.
|
||||
*/
|
||||
std::vector<supported_gamepad_t> &supported_gamepads(input_t *input);
|
||||
} // namespace platf::gamepad
|
||||
|
||||
@@ -25,6 +25,8 @@ namespace platf::keyboard {
|
||||
* ex: ['👱'] = "1F471" // see UTF encoding at https://www.compart.com/en/unicode/U+1F471
|
||||
*
|
||||
* adapted from: https://stackoverflow.com/a/7639754
|
||||
* @param str UTF-8 text to encode as hexadecimal.
|
||||
* @return Value converted to hex.
|
||||
*/
|
||||
std::string to_hex(const std::basic_string<char32_t> &str) {
|
||||
std::stringstream ss;
|
||||
@@ -160,6 +162,9 @@ namespace platf::keyboard {
|
||||
{KEY_102ND, 0xE2}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Apply the supplied state update to the platform backend.
|
||||
*/
|
||||
void update(input_raw_t *raw, uint16_t modcode, bool release, uint8_t flags) {
|
||||
if (raw->keyboard) {
|
||||
if (release) {
|
||||
@@ -170,6 +175,9 @@ namespace platf::keyboard {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Submit UTF-8 text input to the keyboard backend.
|
||||
*/
|
||||
void unicode(input_raw_t *raw, char *utf8, int size) {
|
||||
if (raw->keyboard) {
|
||||
/* Reading input text as UTF-8 */
|
||||
|
||||
@@ -15,7 +15,22 @@
|
||||
using namespace std::literals;
|
||||
|
||||
namespace platf::keyboard {
|
||||
/**
|
||||
* @brief Apply the supplied state update to the platform backend.
|
||||
*
|
||||
* @param raw Platform-specific input backend state.
|
||||
* @param modcode Modifier key code to update.
|
||||
* @param release Whether the key or button event is a release.
|
||||
* @param flags Bit flags that modify the requested operation.
|
||||
*/
|
||||
void update(input_raw_t *raw, uint16_t modcode, bool release, uint8_t flags);
|
||||
|
||||
/**
|
||||
* @brief Submit UTF-8 text input to the keyboard backend.
|
||||
*
|
||||
* @param raw Platform-specific input backend state.
|
||||
* @param utf8 UTF-8 text submitted by the client.
|
||||
* @param size Number of bytes or elements requested.
|
||||
*/
|
||||
void unicode(input_raw_t *raw, char *utf8, int size);
|
||||
} // namespace platf::keyboard
|
||||
|
||||
@@ -19,18 +19,27 @@ using namespace std::literals;
|
||||
|
||||
namespace platf::mouse {
|
||||
|
||||
/**
|
||||
* @brief Apply a relative pointer movement to the virtual mouse.
|
||||
*/
|
||||
void move(input_raw_t *raw, int deltaX, int deltaY) {
|
||||
if (raw->mouse) {
|
||||
(*raw->mouse).move(deltaX, deltaY);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Move abs using the backend coordinate system.
|
||||
*/
|
||||
void move_abs(input_raw_t *raw, const touch_port_t &touch_port, float x, float y) {
|
||||
if (raw->mouse) {
|
||||
(*raw->mouse).move_abs(x, y, touch_port.width, touch_port.height);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Press or release a virtual mouse button.
|
||||
*/
|
||||
void button(input_raw_t *raw, int button, bool release) {
|
||||
if (raw->mouse) {
|
||||
inputtino::Mouse::MOUSE_BUTTON btn_type;
|
||||
@@ -62,18 +71,27 @@ namespace platf::mouse {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Apply a vertical scroll event to the virtual mouse.
|
||||
*/
|
||||
void scroll(input_raw_t *raw, int high_res_distance) {
|
||||
if (raw->mouse) {
|
||||
(*raw->mouse).vertical_scroll(high_res_distance);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Apply a horizontal scroll event to the virtual mouse.
|
||||
*/
|
||||
void hscroll(input_raw_t *raw, int high_res_distance) {
|
||||
if (raw->mouse) {
|
||||
(*raw->mouse).horizontal_scroll(high_res_distance);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Return the current virtual pointer location.
|
||||
*/
|
||||
util::point_t get_location(input_raw_t *raw) {
|
||||
if (raw->mouse) {
|
||||
// TODO: decide what to do after https://github.com/games-on-whales/inputtino/issues/6 is resolved.
|
||||
|
||||
@@ -15,15 +15,55 @@
|
||||
using namespace std::literals;
|
||||
|
||||
namespace platf::mouse {
|
||||
/**
|
||||
* @brief Apply a relative pointer movement to the virtual mouse.
|
||||
*
|
||||
* @param raw Platform-specific input backend state.
|
||||
* @param deltaX Horizontal relative movement in client coordinates.
|
||||
* @param deltaY Vertical relative movement in client coordinates.
|
||||
*/
|
||||
void move(input_raw_t *raw, int deltaX, int deltaY);
|
||||
|
||||
/**
|
||||
* @brief Move the pointer to an absolute client-provided touch coordinate.
|
||||
*
|
||||
* @param raw Platform-specific input backend state.
|
||||
* @param touch_port Touch coordinate bounds used for scaling.
|
||||
* @param x Horizontal absolute coordinate from the client.
|
||||
* @param y Vertical absolute coordinate from the client.
|
||||
*/
|
||||
void move_abs(input_raw_t *raw, const touch_port_t &touch_port, float x, float y);
|
||||
|
||||
/**
|
||||
* @brief Press or release a virtual mouse button.
|
||||
*
|
||||
* @param raw Platform-specific input backend state.
|
||||
* @param button Mouse button identifier to press or release.
|
||||
* @param release Whether the key or button event is a release.
|
||||
*/
|
||||
void button(input_raw_t *raw, int button, bool release);
|
||||
|
||||
/**
|
||||
* @brief Apply a vertical scroll event to the virtual mouse.
|
||||
*
|
||||
* @param raw Platform-specific input backend state.
|
||||
* @param high_res_distance High-resolution scroll distance reported by the client.
|
||||
*/
|
||||
void scroll(input_raw_t *raw, int high_res_distance);
|
||||
|
||||
/**
|
||||
* @brief Apply a horizontal scroll event to the virtual mouse.
|
||||
*
|
||||
* @param raw Platform-specific input backend state.
|
||||
* @param high_res_distance High-resolution scroll distance reported by the client.
|
||||
*/
|
||||
void hscroll(input_raw_t *raw, int high_res_distance);
|
||||
|
||||
/**
|
||||
* @brief Return the current virtual pointer location.
|
||||
*
|
||||
* @param raw Platform-specific input backend state.
|
||||
* @return Current virtual pointer location in screen coordinates.
|
||||
*/
|
||||
util::point_t get_location(input_raw_t *raw);
|
||||
} // namespace platf::mouse
|
||||
|
||||
@@ -18,6 +18,9 @@
|
||||
using namespace std::literals;
|
||||
|
||||
namespace platf::pen {
|
||||
/**
|
||||
* @brief Apply the supplied state update to the platform backend.
|
||||
*/
|
||||
void update(client_input_raw_t *raw, const touch_port_t &touch_port, const pen_input_t &pen) {
|
||||
if (raw->pen) {
|
||||
// First set the buttons
|
||||
|
||||
@@ -16,5 +16,12 @@
|
||||
using namespace std::literals;
|
||||
|
||||
namespace platf::pen {
|
||||
/**
|
||||
* @brief Apply the supplied state update to the platform backend.
|
||||
*
|
||||
* @param raw Platform-specific input backend state.
|
||||
* @param touch_port Touch coordinate bounds used for scaling.
|
||||
* @param pen Pen event data to inject.
|
||||
*/
|
||||
void update(client_input_raw_t *raw, const touch_port_t &touch_port, const pen_input_t &pen);
|
||||
}
|
||||
} // namespace platf::pen
|
||||
|
||||
@@ -11,6 +11,8 @@ namespace platf::inputtino_seat {
|
||||
/**
|
||||
* Determine the target seat for the current Sunshine instance.
|
||||
* Returns empty string if no seat could be determined.
|
||||
*
|
||||
* @return Seat name used for virtual input devices, or an empty string when unknown.
|
||||
*/
|
||||
std::string get_target_seat();
|
||||
|
||||
|
||||
@@ -18,6 +18,9 @@
|
||||
using namespace std::literals;
|
||||
|
||||
namespace platf::touch {
|
||||
/**
|
||||
* @brief Apply the supplied state update to the platform backend.
|
||||
*/
|
||||
void update(client_input_raw_t *raw, const touch_port_t &touch_port, const touch_input_t &touch) {
|
||||
if (raw->touch) {
|
||||
switch (touch.eventType) {
|
||||
|
||||
@@ -16,5 +16,12 @@
|
||||
using namespace std::literals;
|
||||
|
||||
namespace platf::touch {
|
||||
/**
|
||||
* @brief Apply the supplied state update to the platform backend.
|
||||
*
|
||||
* @param raw Platform-specific input backend state.
|
||||
* @param touch_port Touch coordinate bounds used for scaling.
|
||||
* @param touch Touch event data to apply to the virtual device.
|
||||
*/
|
||||
void update(client_input_raw_t *raw, const touch_port_t &touch_port, const touch_input_t &touch);
|
||||
}
|
||||
} // namespace platf::touch
|
||||
|
||||
+452
-65
@@ -37,6 +37,9 @@ namespace platf {
|
||||
|
||||
namespace kms {
|
||||
|
||||
/**
|
||||
* @brief Temporarily owns CAP_SYS_ADMIN while opening DRM capture resources.
|
||||
*/
|
||||
class cap_sys_admin {
|
||||
public:
|
||||
cap_sys_admin() {
|
||||
@@ -56,11 +59,20 @@ namespace platf {
|
||||
cap_free(caps);
|
||||
}
|
||||
|
||||
cap_t caps;
|
||||
cap_t caps; ///< Caps.
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief RAII wrapper for DRM framebuffer metadata and GEM handles.
|
||||
*/
|
||||
class wrapper_fb {
|
||||
public:
|
||||
/**
|
||||
* @brief Wrap legacy DRM framebuffer metadata.
|
||||
*
|
||||
* @param card_fd DRM card file descriptor used to close GEM handles.
|
||||
* @param fb Legacy framebuffer metadata returned by drmModeGetFB.
|
||||
*/
|
||||
wrapper_fb(uint32_t card_fd, drmModeFB *fb):
|
||||
card_fd {card_fd},
|
||||
fb {fb},
|
||||
@@ -76,6 +88,12 @@ namespace platf {
|
||||
pitches[0] = fb->pitch;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Wrap DRM framebuffer metadata with per-plane modifiers.
|
||||
*
|
||||
* @param card_fd DRM card file descriptor used to close GEM handles.
|
||||
* @param fb2 Framebuffer metadata returned by drmModeGetFB2.
|
||||
*/
|
||||
wrapper_fb(uint32_t card_fd, drmModeFB2 *fb2):
|
||||
card_fd {card_fd},
|
||||
fb2 {fb2},
|
||||
@@ -107,30 +125,63 @@ namespace platf {
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t card_fd;
|
||||
drmModeFB *fb = nullptr;
|
||||
drmModeFB2 *fb2 = nullptr;
|
||||
uint32_t fb_id;
|
||||
uint32_t width;
|
||||
uint32_t height;
|
||||
uint32_t pixel_format;
|
||||
uint64_t modifier;
|
||||
uint32_t handles[4];
|
||||
uint32_t pitches[4];
|
||||
uint32_t offsets[4];
|
||||
uint32_t card_fd; ///< DRM card file descriptor that owns the framebuffer ID.
|
||||
drmModeFB *fb = nullptr; ///< Legacy DRM framebuffer metadata when modifier data is unavailable.
|
||||
drmModeFB2 *fb2 = nullptr; ///< DRM framebuffer metadata with modifier support.
|
||||
uint32_t fb_id; ///< DRM framebuffer ID being captured.
|
||||
uint32_t width; ///< Framebuffer width in pixels.
|
||||
uint32_t height; ///< Framebuffer height in pixels.
|
||||
uint32_t pixel_format; ///< DRM fourcc pixel format for the framebuffer.
|
||||
uint64_t modifier; ///< DRM format modifier describing framebuffer memory layout.
|
||||
uint32_t handles[4]; ///< GEM handles for each framebuffer plane.
|
||||
uint32_t pitches[4]; ///< Row stride in bytes for each framebuffer plane.
|
||||
uint32_t offsets[4]; ///< Byte offset to the first pixel for each framebuffer plane.
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief DRM plane resource list released with `drmModeFreePlaneResources`.
|
||||
*/
|
||||
using plane_res_t = util::safe_ptr<drmModePlaneRes, drmModeFreePlaneResources>;
|
||||
/**
|
||||
* @brief DRM encoder pointer released with `drmModeFreeEncoder`.
|
||||
*/
|
||||
using encoder_t = util::safe_ptr<drmModeEncoder, drmModeFreeEncoder>;
|
||||
/**
|
||||
* @brief DRM resource list released with `drmModeFreeResources`.
|
||||
*/
|
||||
using res_t = util::safe_ptr<drmModeRes, drmModeFreeResources>;
|
||||
/**
|
||||
* @brief DRM plane pointer released with `drmModeFreePlane`.
|
||||
*/
|
||||
using plane_t = util::safe_ptr<drmModePlane, drmModeFreePlane>;
|
||||
/**
|
||||
* @brief DRM framebuffer pointer released with `drmModeFreeFB2`.
|
||||
*/
|
||||
using fb_t = std::unique_ptr<wrapper_fb>;
|
||||
/**
|
||||
* @brief DRM CRTC pointer released with `drmModeFreeCrtc`.
|
||||
*/
|
||||
using crtc_t = util::safe_ptr<drmModeCrtc, drmModeFreeCrtc>;
|
||||
/**
|
||||
* @brief DRM object-property list released with `drmModeFreeObjectProperties`.
|
||||
*/
|
||||
using obj_prop_t = util::safe_ptr<drmModeObjectProperties, drmModeFreeObjectProperties>;
|
||||
/**
|
||||
* @brief DRM property pointer released with `drmModeFreeProperty`.
|
||||
*/
|
||||
using prop_t = util::safe_ptr<drmModePropertyRes, drmModeFreeProperty>;
|
||||
/**
|
||||
* @brief DRM property-blob pointer released with `drmModeFreePropertyBlob`.
|
||||
*/
|
||||
using prop_blob_t = util::safe_ptr<drmModePropertyBlobRes, drmModeFreePropertyBlob>;
|
||||
/**
|
||||
* @brief DRM version pointer released with `drmFreeVersion`.
|
||||
*/
|
||||
using version_t = util::safe_ptr<drmVersion, drmFreeVersion>;
|
||||
|
||||
/**
|
||||
* @brief Counter map keyed by DRM connector type.
|
||||
*/
|
||||
using conn_type_count_t = std::map<std::uint32_t, std::uint32_t>;
|
||||
|
||||
static int env_width;
|
||||
@@ -139,6 +190,12 @@ namespace platf {
|
||||
static int env_logical_width;
|
||||
static int env_logical_height;
|
||||
|
||||
/**
|
||||
* @brief Convert a DRM plane type value to a diagnostic string.
|
||||
*
|
||||
* @param val Value assigned to the synchronized object.
|
||||
* @return Static text for the DRM plane type.
|
||||
*/
|
||||
std::string_view plane_type(std::uint64_t val) {
|
||||
switch (val) {
|
||||
case DRM_PLANE_TYPE_OVERLAY:
|
||||
@@ -152,45 +209,56 @@ namespace platf {
|
||||
return "UNKNOWN"sv;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief DRM connector and mode information for one display.
|
||||
*/
|
||||
struct connector_t {
|
||||
// For example: HDMI-A or HDMI
|
||||
std::uint32_t type;
|
||||
std::uint32_t type; ///< Type.
|
||||
|
||||
// Equals zero if not applicable
|
||||
std::uint32_t crtc_id;
|
||||
std::uint32_t crtc_id; ///< Crtc ID.
|
||||
|
||||
// For example HDMI-A-{index} or HDMI-{index}
|
||||
std::uint32_t index;
|
||||
std::uint32_t index; ///< Index.
|
||||
|
||||
// ID of the connector
|
||||
std::uint32_t connector_id;
|
||||
std::uint32_t connector_id; ///< Connector ID.
|
||||
|
||||
bool connected;
|
||||
bool connected; ///< Whether the DRM connector is connected.
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief KMS monitor capture state and DRM resources.
|
||||
*/
|
||||
struct monitor_t {
|
||||
// Connector attributes
|
||||
std::uint32_t type;
|
||||
std::uint32_t index;
|
||||
std::uint32_t type; ///< Type.
|
||||
std::uint32_t index; ///< Index.
|
||||
|
||||
// Monitor index in the global list
|
||||
std::uint32_t monitor_index;
|
||||
std::uint32_t monitor_index; ///< Monitor index.
|
||||
|
||||
platf::touch_port_t viewport;
|
||||
platf::touch_port_t viewport; ///< Viewport.
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief DRM card, device path, and render-node metadata.
|
||||
*/
|
||||
struct card_descriptor_t {
|
||||
std::string path;
|
||||
std::string path; ///< Path.
|
||||
|
||||
std::map<std::uint32_t, monitor_t> crtc_to_monitor;
|
||||
std::map<std::uint32_t, monitor_t> crtc_to_monitor; ///< Crtc to monitor.
|
||||
};
|
||||
|
||||
static std::vector<card_descriptor_t> card_descriptors;
|
||||
|
||||
static std::uint32_t from_view(const std::string_view &string) {
|
||||
#define _CONVERT(x, y) \
|
||||
if (string == x) \
|
||||
return DRM_MODE_CONNECTOR_##y
|
||||
#ifndef DOXYGEN
|
||||
#define _CONVERT(x, y) \
|
||||
if (string == x) \
|
||||
return DRM_MODE_CONNECTOR_##y
|
||||
#endif
|
||||
|
||||
// This list was created from the following sources:
|
||||
// https://gitlab.freedesktop.org/mesa/drm/-/blob/main/xf86drmMode.c (drmModeGetConnectorTypeName)
|
||||
@@ -244,8 +312,18 @@ namespace platf {
|
||||
return DRM_MODE_CONNECTOR_Unknown;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Iterator over DRM planes and their associated properties.
|
||||
*/
|
||||
class plane_it_t: public round_robin_util::it_wrap_t<plane_t::element_type, plane_it_t> {
|
||||
public:
|
||||
/**
|
||||
* @brief Create an iterator over DRM planes starting at a specific plane ID.
|
||||
*
|
||||
* @param fd DRM card file descriptor.
|
||||
* @param plane_p Current plane ID pointer.
|
||||
* @param end One-past-the-end plane ID pointer.
|
||||
*/
|
||||
plane_it_t(int fd, std::uint32_t *plane_p, std::uint32_t *end):
|
||||
fd {fd},
|
||||
plane_p {plane_p},
|
||||
@@ -253,12 +331,21 @@ namespace platf {
|
||||
load_next_valid_plane();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Create the end iterator for a DRM plane range.
|
||||
*
|
||||
* @param fd DRM card file descriptor.
|
||||
* @param end One-past-the-end plane ID pointer.
|
||||
*/
|
||||
plane_it_t(int fd, std::uint32_t *end):
|
||||
fd {fd},
|
||||
plane_p {end},
|
||||
end {end} {
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Load next valid plane.
|
||||
*/
|
||||
void load_next_valid_plane() {
|
||||
this->plane.reset();
|
||||
|
||||
@@ -274,50 +361,79 @@ namespace platf {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Advance the iterator to the next element.
|
||||
*/
|
||||
void inc() {
|
||||
++plane_p;
|
||||
load_next_valid_plane();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Compare two iterators for equality.
|
||||
*
|
||||
* @param other Plane iterator to compare against.
|
||||
* @return True when both iterators reference the same plane position.
|
||||
*/
|
||||
bool eq(const plane_it_t &other) const {
|
||||
return plane_p == other.plane_p;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Return the currently wrapped value or handle.
|
||||
*
|
||||
* @return Underlying native handle or object pointer.
|
||||
*/
|
||||
plane_t::pointer get() {
|
||||
return plane.get();
|
||||
}
|
||||
|
||||
int fd;
|
||||
std::uint32_t *plane_p;
|
||||
std::uint32_t *end;
|
||||
int fd; ///< DRM card file descriptor.
|
||||
std::uint32_t *plane_p; ///< Current plane ID pointer.
|
||||
std::uint32_t *end; ///< One-past-the-end plane ID pointer.
|
||||
|
||||
util::shared_t<plane_t> plane;
|
||||
util::shared_t<plane_t> plane; ///< Plane.
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Cursor position and visibility for the current capture frame.
|
||||
*/
|
||||
struct cursor_t {
|
||||
// Public properties used during blending
|
||||
bool visible = false;
|
||||
std::int32_t x;
|
||||
std::int32_t y;
|
||||
std::uint32_t dst_w;
|
||||
std::uint32_t dst_h;
|
||||
std::uint32_t src_w;
|
||||
std::uint32_t src_h;
|
||||
std::vector<std::uint8_t> pixels;
|
||||
unsigned long serial;
|
||||
bool visible = false; ///< Whether the KMS cursor plane is visible.
|
||||
std::int32_t x; ///< X.
|
||||
std::int32_t y; ///< Y.
|
||||
std::uint32_t dst_w; ///< Dst w.
|
||||
std::uint32_t dst_h; ///< Dst h.
|
||||
std::uint32_t src_w; ///< Src w.
|
||||
std::uint32_t src_h; ///< Src h.
|
||||
std::vector<std::uint8_t> pixels; ///< Pixels.
|
||||
unsigned long serial; ///< Serial.
|
||||
|
||||
// Private properties used for tracking cursor changes
|
||||
std::uint64_t prop_src_x;
|
||||
std::uint64_t prop_src_y;
|
||||
std::uint64_t prop_src_w;
|
||||
std::uint64_t prop_src_h;
|
||||
std::uint32_t fb_id;
|
||||
std::uint64_t prop_src_x; ///< Prop src x.
|
||||
std::uint64_t prop_src_y; ///< Prop src y.
|
||||
std::uint64_t prop_src_w; ///< Prop src w.
|
||||
std::uint64_t prop_src_h; ///< Prop src h.
|
||||
std::uint32_t fb_id; ///< Fb ID.
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief DRM card, render node, and plane metadata used for KMS capture.
|
||||
*/
|
||||
class card_t {
|
||||
public:
|
||||
/**
|
||||
* @brief Internal connector metadata tuple used while selecting a monitor.
|
||||
*/
|
||||
using connector_interal_t = util::safe_ptr<drmModeConnector, drmModeFreeConnector>;
|
||||
|
||||
/**
|
||||
* @brief Open a DRM connector and cache its monitor metadata.
|
||||
*
|
||||
* @param path Filesystem path for the DRM device or resource.
|
||||
* @return 0 on success; nonzero or negative platform status on failure.
|
||||
*/
|
||||
int init(const char *path) {
|
||||
cap_sys_admin admin;
|
||||
fd.el = open(path, O_RDWR);
|
||||
@@ -373,6 +489,12 @@ namespace platf {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Return framebuffer metadata for the current CRTC.
|
||||
*
|
||||
* @param plane DRM or EGL plane index being described.
|
||||
* @return Framebuffer metadata wrapper, or nullptr when the framebuffer cannot be read.
|
||||
*/
|
||||
fb_t fb(plane_t::pointer plane) {
|
||||
cap_sys_admin admin;
|
||||
|
||||
@@ -389,23 +511,51 @@ namespace platf {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Return the DRM CRTC object for the monitor.
|
||||
*
|
||||
* @param id DRM CRTC ID.
|
||||
* @return Owning pointer to the DRM CRTC object.
|
||||
*/
|
||||
crtc_t crtc(std::uint32_t id) {
|
||||
return drmModeGetCrtc(fd.el, id);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Return the DRM encoder object for the connector.
|
||||
*
|
||||
* @param id DRM encoder ID.
|
||||
* @return Owning pointer to the DRM encoder object.
|
||||
*/
|
||||
encoder_t encoder(std::uint32_t id) {
|
||||
return drmModeGetEncoder(fd.el, id);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Return the DRM resource list for the card.
|
||||
*
|
||||
* @return DRM card resource list.
|
||||
*/
|
||||
res_t res() {
|
||||
return drmModeGetResources(fd.el);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check whether nvidia.
|
||||
*
|
||||
* @return True when the DRM device appears to be driven by NVIDIA.
|
||||
*/
|
||||
bool is_nvidia() {
|
||||
version_t ver {drmGetVersion(fd.el)};
|
||||
return ver && ver->name && strncmp(ver->name, "nvidia-drm", 10) == 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check whether cursor.
|
||||
*
|
||||
* @param plane_id Plane ID.
|
||||
* @return True when the DRM plane is a cursor plane.
|
||||
*/
|
||||
bool is_cursor(std::uint32_t plane_id) {
|
||||
auto props = plane_props(plane_id);
|
||||
for (auto &[prop, val] : props) {
|
||||
@@ -421,6 +571,13 @@ namespace platf {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Look up a DRM property value by property name.
|
||||
*
|
||||
* @param props DRM property collection to inspect.
|
||||
* @param name DRM property name to search for.
|
||||
* @return Property value when the property exists.
|
||||
*/
|
||||
std::optional<std::uint64_t> prop_value_by_name(const std::vector<std::pair<prop_t, std::uint64_t>> &props, std::string_view name) {
|
||||
for (auto &[prop, val] : props) {
|
||||
if (prop->name == name) {
|
||||
@@ -430,6 +587,12 @@ namespace platf {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get panel orientation.
|
||||
*
|
||||
* @param plane_id Plane ID.
|
||||
* @return DRM rotation bitmask for the panel orientation.
|
||||
*/
|
||||
std::uint32_t get_panel_orientation(std::uint32_t plane_id) {
|
||||
auto props = plane_props(plane_id);
|
||||
auto value = prop_value_by_name(props, "rotation"sv);
|
||||
@@ -441,6 +604,12 @@ namespace platf {
|
||||
return DRM_MODE_ROTATE_0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get crtc index by ID.
|
||||
*
|
||||
* @param crtc_id Crtc ID.
|
||||
* @return Zero-based CRTC index, or -1 when the CRTC ID is unknown.
|
||||
*/
|
||||
int get_crtc_index_by_id(std::uint32_t crtc_id) {
|
||||
auto resources = res();
|
||||
for (int i = 0; i < resources->count_crtcs; i++) {
|
||||
@@ -451,10 +620,22 @@ namespace platf {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Return the DRM connector object for the display.
|
||||
*
|
||||
* @param id DRM connector ID.
|
||||
* @return Owning pointer to the DRM connector object.
|
||||
*/
|
||||
connector_interal_t connector(std::uint32_t id) {
|
||||
return drmModeGetConnector(fd.el, id);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Refresh the monitor list reported by the display server.
|
||||
*
|
||||
* @param conn_type_count Conn type count.
|
||||
* @return Connector descriptors for monitors known to the DRM card.
|
||||
*/
|
||||
std::vector<connector_t> monitors(conn_type_count_t &conn_type_count) {
|
||||
auto resources = res();
|
||||
if (!resources) {
|
||||
@@ -489,6 +670,12 @@ namespace platf {
|
||||
return monitors;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Return the DRM file descriptor owned by the object.
|
||||
*
|
||||
* @param handle GEM handle exported by the DRM framebuffer.
|
||||
* @return DMA-BUF file descriptor for the GEM handle.
|
||||
*/
|
||||
file_t handleFD(std::uint32_t handle) {
|
||||
file_t fb_fd;
|
||||
|
||||
@@ -500,6 +687,13 @@ namespace platf {
|
||||
return fb_fd;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Return DRM object properties for the resource.
|
||||
*
|
||||
* @param id DRM object ID.
|
||||
* @param type DRM object type.
|
||||
* @return DRM properties and their current values.
|
||||
*/
|
||||
std::vector<std::pair<prop_t, std::uint64_t>> props(std::uint32_t id, std::uint32_t type) {
|
||||
obj_prop_t obj_prop = drmModeObjectGetProperties(fd.el, id, type);
|
||||
if (!obj_prop) {
|
||||
@@ -516,39 +710,84 @@ namespace platf {
|
||||
return props;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Return DRM plane properties.
|
||||
*
|
||||
* @param id DRM plane ID.
|
||||
* @return Plane properties and their current values.
|
||||
*/
|
||||
std::vector<std::pair<prop_t, std::uint64_t>> plane_props(std::uint32_t id) {
|
||||
return props(id, DRM_MODE_OBJECT_PLANE);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Return DRM CRTC properties.
|
||||
*
|
||||
* @param id DRM CRTC ID.
|
||||
* @return CRTC properties and their current values.
|
||||
*/
|
||||
std::vector<std::pair<prop_t, std::uint64_t>> crtc_props(std::uint32_t id) {
|
||||
return props(id, DRM_MODE_OBJECT_CRTC);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Return DRM connector properties.
|
||||
*
|
||||
* @param id DRM connector ID.
|
||||
* @return Connector properties and their current values.
|
||||
*/
|
||||
std::vector<std::pair<prop_t, std::uint64_t>> connector_props(std::uint32_t id) {
|
||||
return props(id, DRM_MODE_OBJECT_CONNECTOR);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Fetch DRM plane metadata by plane-list index.
|
||||
*
|
||||
* @param index Zero-based index into the DRM plane resource list.
|
||||
* @return Plane metadata for the requested DRM plane.
|
||||
*/
|
||||
plane_t operator[](std::uint32_t index) {
|
||||
return drmModeGetPlane(fd.el, plane_res->planes[index]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Return the number of items in the wrapped DRM collection.
|
||||
*
|
||||
* @return Number of DRM planes reported by the card.
|
||||
*/
|
||||
std::uint32_t count() {
|
||||
return plane_res->count_planes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Return an iterator to the first byte in the buffer view.
|
||||
*
|
||||
* @return Iterator to the first element.
|
||||
*/
|
||||
plane_it_t begin() const {
|
||||
return plane_it_t {fd.el, plane_res->planes, plane_res->planes + plane_res->count_planes};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Return an iterator one past the final byte in the buffer view.
|
||||
*
|
||||
* @return Iterator one past the last element.
|
||||
*/
|
||||
plane_it_t end() const {
|
||||
return plane_it_t {fd.el, plane_res->planes + plane_res->count_planes};
|
||||
}
|
||||
|
||||
file_t fd;
|
||||
file_t render_fd;
|
||||
plane_res_t plane_res;
|
||||
file_t fd; ///< DRM card file descriptor.
|
||||
file_t render_fd; ///< Render fd.
|
||||
plane_res_t plane_res; ///< Plane res.
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Build a lookup from DRM CRTC IDs to monitor descriptors.
|
||||
*
|
||||
* @param connectors DRM connector list being mapped to monitors.
|
||||
* @return Map keyed by CRTC ID.
|
||||
*/
|
||||
std::map<std::uint32_t, monitor_t> map_crtc_to_monitor(const std::vector<connector_t> &connectors) {
|
||||
std::map<std::uint32_t, monitor_t> result;
|
||||
|
||||
@@ -562,6 +801,9 @@ namespace platf {
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Multi-source KMS image assembled for encoding.
|
||||
*/
|
||||
struct kms_img_t: public img_t {
|
||||
~kms_img_t() override {
|
||||
delete[] data;
|
||||
@@ -569,6 +811,13 @@ namespace platf {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Write a debug log representation of the input packet.
|
||||
*
|
||||
* @param plane DRM or EGL plane index being described.
|
||||
* @param fb Framebuffer object to bind or update.
|
||||
* @param crtc DRM CRTC identifier to map.
|
||||
*/
|
||||
void print(plane_t::pointer plane, fb_t::pointer fb, crtc_t::pointer crtc) {
|
||||
if (crtc) {
|
||||
BOOST_LOG(debug) << "crtc("sv << crtc->x << ", "sv << crtc->y << ')';
|
||||
@@ -601,13 +850,28 @@ namespace platf {
|
||||
BOOST_LOG(debug) << ss.str();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Base KMS display capture backend shared by RAM and VRAM paths.
|
||||
*/
|
||||
class display_t: public platf::display_t {
|
||||
public:
|
||||
/**
|
||||
* @brief Initialize common KMS display state for the requested memory type.
|
||||
*
|
||||
* @param mem_type Mem type.
|
||||
*/
|
||||
display_t(mem_type_e mem_type):
|
||||
platf::display_t(),
|
||||
mem_type {mem_type} {
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Initialize the base KMS capture state for the selected monitor.
|
||||
*
|
||||
* @param display_name Display name.
|
||||
* @param config Configuration values to apply.
|
||||
* @return 0 on success; nonzero or negative platform status on failure.
|
||||
*/
|
||||
int init(const std::string &display_name, const ::video::config_t &config) {
|
||||
delay = std::chrono::nanoseconds {1s} / config.framerate;
|
||||
|
||||
@@ -816,6 +1080,11 @@ namespace platf {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Report whether the active display mode is HDR.
|
||||
*
|
||||
* @return True when the active display mode is HDR.
|
||||
*/
|
||||
bool is_hdr() {
|
||||
if (!hdr_metadata_blob_id || *hdr_metadata_blob_id == 0) {
|
||||
return false;
|
||||
@@ -862,6 +1131,12 @@ namespace platf {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Read HDR metadata for the active display mode.
|
||||
*
|
||||
* @param metadata Output structure populated with HDR metadata.
|
||||
* @return True when HDR metadata was written to the output structure.
|
||||
*/
|
||||
bool get_hdr_metadata(SS_HDR_METADATA &metadata) {
|
||||
// This performs all the metadata validation
|
||||
if (!is_hdr()) {
|
||||
@@ -891,6 +1166,9 @@ namespace platf {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Update cached cursor-plane image and position.
|
||||
*/
|
||||
void update_cursor() {
|
||||
if (cursor_plane_id < 0) {
|
||||
return;
|
||||
@@ -1069,6 +1347,14 @@ namespace platf {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Refresh cached platform state from the operating system.
|
||||
*
|
||||
* @param file DMA-BUF file descriptors exported for the current framebuffer.
|
||||
* @param sd EGL surface descriptor to import.
|
||||
* @param frame_timestamp Output timestamp assigned to the captured frame.
|
||||
* @return Capture status after refreshing framebuffer and cursor state.
|
||||
*/
|
||||
inline capture_e refresh(file_t *file, egl::surface_descriptor_t *sd, std::optional<std::chrono::steady_clock::time_point> &frame_timestamp) {
|
||||
// Check for a change in HDR metadata
|
||||
if (connector_id) {
|
||||
@@ -1133,34 +1419,49 @@ namespace platf {
|
||||
return capture_e::ok;
|
||||
}
|
||||
|
||||
mem_type_e mem_type;
|
||||
mem_type_e mem_type; ///< Mem type.
|
||||
|
||||
std::chrono::nanoseconds delay;
|
||||
std::chrono::nanoseconds delay; ///< Delay before the timer task becomes eligible to run.
|
||||
|
||||
int img_width;
|
||||
int img_height;
|
||||
int img_offset_x;
|
||||
int img_offset_y;
|
||||
int img_width; ///< Img width.
|
||||
int img_height; ///< Img height.
|
||||
int img_offset_x; ///< Img offset x.
|
||||
int img_offset_y; ///< Img offset y.
|
||||
|
||||
int plane_id;
|
||||
int crtc_id;
|
||||
int crtc_index;
|
||||
int plane_id; ///< Plane ID.
|
||||
int crtc_id; ///< Crtc ID.
|
||||
int crtc_index; ///< Crtc index.
|
||||
|
||||
std::optional<uint32_t> connector_id;
|
||||
std::optional<uint64_t> hdr_metadata_blob_id;
|
||||
std::optional<uint32_t> connector_id; ///< Connector ID.
|
||||
std::optional<uint64_t> hdr_metadata_blob_id; ///< HDR metadata blob ID.
|
||||
|
||||
int cursor_plane_id;
|
||||
cursor_t captured_cursor {};
|
||||
int cursor_plane_id; ///< Cursor plane ID.
|
||||
cursor_t captured_cursor {}; ///< Captured cursor.
|
||||
|
||||
card_t card;
|
||||
card_t card; ///< Card.
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief KMS capture backend that copies frames into system memory.
|
||||
*/
|
||||
class display_ram_t: public display_t {
|
||||
public:
|
||||
/**
|
||||
* @brief Initialize a KMS display backend that copies frames through RAM.
|
||||
*
|
||||
* @param mem_type Mem type.
|
||||
*/
|
||||
display_ram_t(mem_type_e mem_type):
|
||||
display_t(mem_type) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Initialize KMS capture that copies frames into system memory.
|
||||
*
|
||||
* @param display_name Display name.
|
||||
* @param config Configuration values to apply.
|
||||
* @return 0 on success; nonzero or negative platform status on failure.
|
||||
*/
|
||||
int init(const std::string &display_name, const ::video::config_t &config) {
|
||||
if (!gbm::create_device) {
|
||||
BOOST_LOG(warning) << "libgbm not initialized"sv;
|
||||
@@ -1237,6 +1538,12 @@ namespace platf {
|
||||
return capture_e::ok;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Create AVCodec encode device.
|
||||
*
|
||||
* @param pix_fmt Sunshine pixel format to convert or allocate for.
|
||||
* @return Constructed AVCodec encode device object.
|
||||
*/
|
||||
std::unique_ptr<avcodec_encode_device_t> make_avcodec_encode_device(pix_fmt_e pix_fmt) override {
|
||||
#ifdef SUNSHINE_BUILD_VAAPI
|
||||
if (mem_type == mem_type_e::vaapi) {
|
||||
@@ -1253,6 +1560,11 @@ namespace platf {
|
||||
return std::make_unique<avcodec_encode_device_t>();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Blend the captured cursor plane into a KMS frame.
|
||||
*
|
||||
* @param img Image or frame object to read from or populate.
|
||||
*/
|
||||
void blend_cursor(img_t &img) {
|
||||
// TODO: Cursor scaling is not supported in this codepath.
|
||||
// We always draw the cursor at the source size.
|
||||
@@ -1301,6 +1613,15 @@ namespace platf {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Capture a display frame into the provided image object.
|
||||
*
|
||||
* @param pull_free_image_cb Callback that provides an available image buffer.
|
||||
* @param img_out Captured KMS image returned to the streaming pipeline.
|
||||
* @param timeout Maximum time to wait for the operation.
|
||||
* @param cursor Cursor image or visibility state to composite.
|
||||
* @return Capture status reported to the streaming pipeline.
|
||||
*/
|
||||
capture_e snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr<platf::img_t> &img_out, std::chrono::milliseconds timeout, bool cursor) {
|
||||
file_t fb_fd[4];
|
||||
|
||||
@@ -1344,6 +1665,11 @@ namespace platf {
|
||||
return capture_e::ok;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Allocate an image buffer compatible with this display backend.
|
||||
*
|
||||
* @return Allocated img object, or null when unavailable.
|
||||
*/
|
||||
std::shared_ptr<img_t> alloc_img() override {
|
||||
auto img = std::make_shared<kms_img_t>();
|
||||
img->width = width;
|
||||
@@ -1355,21 +1681,41 @@ namespace platf {
|
||||
return img;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Populate a fallback image when real capture data is unavailable.
|
||||
*
|
||||
* @param img Image or frame object to read from or populate.
|
||||
* @return Capture status reported to the streaming pipeline.
|
||||
*/
|
||||
int dummy_img(platf::img_t *img) override {
|
||||
return 0;
|
||||
}
|
||||
|
||||
gbm::gbm_t gbm;
|
||||
egl::display_t display;
|
||||
egl::ctx_t ctx;
|
||||
gbm::gbm_t gbm; ///< GBM device used for buffer allocation.
|
||||
egl::display_t display; ///< EGL display created from the GBM device.
|
||||
egl::ctx_t ctx; ///< EGL context used to copy KMS frames into RAM.
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief KMS capture backend that exports frames as GPU resources.
|
||||
*/
|
||||
class display_vram_t: public display_t {
|
||||
public:
|
||||
/**
|
||||
* @brief Initialize a KMS display backend that exports frames as GPU resources.
|
||||
*
|
||||
* @param mem_type Mem type.
|
||||
*/
|
||||
display_vram_t(mem_type_e mem_type):
|
||||
display_t(mem_type) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Create AVCodec encode device.
|
||||
*
|
||||
* @param pix_fmt Sunshine pixel format to convert or allocate for.
|
||||
* @return Constructed AVCodec encode device object.
|
||||
*/
|
||||
std::unique_ptr<avcodec_encode_device_t> make_avcodec_encode_device(pix_fmt_e pix_fmt) override {
|
||||
#ifdef SUNSHINE_BUILD_VAAPI
|
||||
if (mem_type == mem_type_e::vaapi) {
|
||||
@@ -1393,6 +1739,11 @@ namespace platf {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Allocate an image buffer compatible with this display backend.
|
||||
*
|
||||
* @return Allocated img object, or null when unavailable.
|
||||
*/
|
||||
std::shared_ptr<img_t> alloc_img() override {
|
||||
auto img = std::make_shared<egl::img_descriptor_t>();
|
||||
|
||||
@@ -1408,6 +1759,12 @@ namespace platf {
|
||||
return img;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Populate a fallback image when real capture data is unavailable.
|
||||
*
|
||||
* @param img Image or frame object to read from or populate.
|
||||
* @return Capture status reported to the streaming pipeline.
|
||||
*/
|
||||
int dummy_img(platf::img_t *img) override {
|
||||
// Empty images are recognized as dummies by the zero sequence number
|
||||
return 0;
|
||||
@@ -1458,6 +1815,14 @@ namespace platf {
|
||||
return capture_e::ok;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Capture a display frame into the provided image object.
|
||||
*
|
||||
* @param pull_free_image_cb Pull free image cb.
|
||||
* @param img_out Captured KMS image descriptor returned to the streaming pipeline.
|
||||
* @param cursor Cursor image or visibility state to composite.
|
||||
* @return Capture status reported to the streaming pipeline.
|
||||
*/
|
||||
capture_e snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr<platf::img_t> &img_out, std::chrono::milliseconds /* timeout */, bool cursor) {
|
||||
file_t fb_fd[4];
|
||||
|
||||
@@ -1500,6 +1865,13 @@ namespace platf {
|
||||
return capture_e::ok;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Initialize KMS capture that exports frames as GPU resources.
|
||||
*
|
||||
* @param display_name Display name.
|
||||
* @param config Configuration values to apply.
|
||||
* @return 0 on success; nonzero or negative platform status on failure.
|
||||
*/
|
||||
int init(const std::string &display_name, const ::video::config_t &config) {
|
||||
if (display_t::init(display_name, config)) {
|
||||
return -1;
|
||||
@@ -1522,11 +1894,19 @@ namespace platf {
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::uint64_t sequence {};
|
||||
std::uint64_t sequence {}; ///< Monotonic capture sequence assigned to KMS frames.
|
||||
};
|
||||
|
||||
} // namespace kms
|
||||
|
||||
/**
|
||||
* @brief Create a KMS display capture backend.
|
||||
*
|
||||
* @param hwdevice_type Hardware device type requested for capture or encode.
|
||||
* @param display_name Display name.
|
||||
* @param config Configuration values to apply.
|
||||
* @return KMS display backend, or nullptr when initialization fails.
|
||||
*/
|
||||
std::shared_ptr<display_t> kms_display(mem_type_e hwdevice_type, const std::string &display_name, const ::video::config_t &config) {
|
||||
if (hwdevice_type == mem_type_e::vaapi || hwdevice_type == mem_type_e::cuda || hwdevice_type == mem_type_e::vulkan) {
|
||||
auto disp = std::make_shared<kms::display_vram_t>(hwdevice_type);
|
||||
@@ -1556,6 +1936,7 @@ namespace platf {
|
||||
* But, it's necessary for absolute mouse coordinates to work.
|
||||
*
|
||||
* This is an ugly hack :(
|
||||
* @param cds Capture display state used by the KMS backend.
|
||||
*/
|
||||
void correlate_to_wayland(std::vector<kms::card_descriptor_t> &cds) {
|
||||
auto monitors = wl::monitors();
|
||||
@@ -1613,6 +1994,12 @@ namespace platf {
|
||||
}
|
||||
|
||||
// A list of names of displays accepted as display_name
|
||||
/**
|
||||
* @brief Enumerate display names accepted by the KMS backend.
|
||||
*
|
||||
* @param hwdevice_type Hardware device type requested for capture or encode.
|
||||
* @return KMS display names, or an empty list when KMS capture is unavailable.
|
||||
*/
|
||||
std::vector<std::string> kms_display_names(mem_type_e hwdevice_type) {
|
||||
int count = 0;
|
||||
|
||||
|
||||
@@ -49,10 +49,18 @@ namespace kwin {
|
||||
*/
|
||||
class screencast_permission_helper_t {
|
||||
public:
|
||||
/**
|
||||
* @brief Check whether permission system deactivated.
|
||||
*
|
||||
* @return True when KWin reports that the permission system is disabled.
|
||||
*/
|
||||
static bool is_permission_system_deactivated() {
|
||||
return getenvstr("KWIN_WAYLAND_NO_PERMISSION_CHECKS") == "1";
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Configure the KWin screencast session.
|
||||
*/
|
||||
static void setup() {
|
||||
if (initialized) {
|
||||
return;
|
||||
@@ -139,6 +147,11 @@ namespace kwin {
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check whether newly initialized.
|
||||
*
|
||||
* @return True when KWin was initialized during this check.
|
||||
*/
|
||||
static bool is_newly_initialized() {
|
||||
return create_file;
|
||||
}
|
||||
@@ -238,13 +251,19 @@ namespace kwin {
|
||||
};
|
||||
|
||||
// Output parameters
|
||||
/**
|
||||
* @brief KWin screencast output name and geometry.
|
||||
*/
|
||||
struct output_parameter_t {
|
||||
std::string name = "";
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
int pos_x = 0;
|
||||
int pos_y = 0;
|
||||
std::string name = ""; ///< KWin output name.
|
||||
int width = 0; ///< Output width in pixels.
|
||||
int height = 0; ///< Output height in pixels.
|
||||
int pos_x = 0; ///< Output X position in the compositor layout.
|
||||
int pos_y = 0; ///< Output Y position in the compositor layout.
|
||||
// order is needed to get a sorted output list and should be updated before sorting to have current values
|
||||
/**
|
||||
* @brief Order.
|
||||
*/
|
||||
size_t order = SIZE_MAX; // Use high number to keep monitors with uninitialized order value to the back
|
||||
};
|
||||
|
||||
@@ -443,9 +462,9 @@ namespace kwin {
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint32_t out_node_id = PW_ID_ANY;
|
||||
uint64_t out_objectserial = SPA_ID_INVALID;
|
||||
std::shared_ptr<output_parameter_t> out_params = nullptr;
|
||||
uint32_t out_node_id = PW_ID_ANY; ///< Out node ID.
|
||||
uint64_t out_objectserial = SPA_ID_INVALID; ///< Out objectserial.
|
||||
std::shared_ptr<output_parameter_t> out_params = nullptr; ///< Out params.
|
||||
|
||||
private:
|
||||
// Wayland objects
|
||||
@@ -688,12 +707,20 @@ namespace kwin {
|
||||
return -1;
|
||||
}
|
||||
|
||||
std::unique_ptr<screencast_t> screencast;
|
||||
std::unique_ptr<screencast_t> screencast; ///< Screencast.
|
||||
};
|
||||
} // namespace kwin
|
||||
|
||||
// Public API for misc.cpp
|
||||
namespace platf {
|
||||
/**
|
||||
* @brief Create a KWin screencast display backend.
|
||||
*
|
||||
* @param hwdevice_type Hardware device type requested for capture or encode.
|
||||
* @param display_name Display name.
|
||||
* @param config Configuration values to apply.
|
||||
* @return KWin/PipeWire display backend, or nullptr when initialization fails.
|
||||
*/
|
||||
std::shared_ptr<display_t> kwin_display(mem_type_e hwdevice_type, const std::string &display_name, const video::config_t &config) {
|
||||
if (!pipewire::pipewire_display_t::init_pipewire_and_check_hwdevice_type(hwdevice_type)) {
|
||||
BOOST_LOG(error) << "[kwingrab] Could not initialize pipewire-based display with the given hw device type."sv;
|
||||
@@ -708,6 +735,11 @@ namespace platf {
|
||||
return display;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Enumerate KWin screencast display names.
|
||||
*
|
||||
* @return KWin display names, or an empty list when KWin capture is unavailable.
|
||||
*/
|
||||
std::vector<std::string> kwin_display_names() {
|
||||
if (has_elevated_privileges(false)) {
|
||||
// We're still in the probing phase of Sunshine startup. Dropping portal security early will break KMS.
|
||||
@@ -724,6 +756,11 @@ namespace platf {
|
||||
return screencast->get_output_names();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check whether KWin screencast capture is available.
|
||||
*
|
||||
* @return True when KWin capture support is available.
|
||||
*/
|
||||
bool kwin_available() {
|
||||
// Init screencast without permission setup (to not cause unneeded logs / temporary desktop files) and check KWin availability
|
||||
if (const auto screencast = std::make_unique<kwin::screencast_t>(); screencast->init(false) < 0 || !screencast->kwin_available()) {
|
||||
|
||||
+145
-10
@@ -5,6 +5,10 @@
|
||||
|
||||
// Required for in6_pktinfo with glibc headers
|
||||
#ifndef _GNU_SOURCE
|
||||
/**
|
||||
* @def _GNU_SOURCE
|
||||
* @brief Macro for GNU SOURCE.
|
||||
*/
|
||||
#define _GNU_SOURCE 1
|
||||
#endif
|
||||
|
||||
@@ -60,16 +64,32 @@
|
||||
#ifdef __GNUC__
|
||||
#define SUNSHINE_GNUC_EXTENSION __extension__
|
||||
#else
|
||||
/**
|
||||
* @def SUNSHINE_GNUC_EXTENSION
|
||||
* @brief Macro for SUNSHINE GNUC EXTENSION.
|
||||
*/
|
||||
#define SUNSHINE_GNUC_EXTENSION
|
||||
#endif
|
||||
|
||||
#ifndef SOL_IP
|
||||
/**
|
||||
* @def SOL_IP
|
||||
* @brief Macro for SOL IP.
|
||||
*/
|
||||
#define SOL_IP IPPROTO_IP
|
||||
#endif
|
||||
#ifndef SOL_IPV6
|
||||
/**
|
||||
* @def SOL_IPV6
|
||||
* @brief Macro for SOL IPv6.
|
||||
*/
|
||||
#define SOL_IPV6 IPPROTO_IPV6
|
||||
#endif
|
||||
#ifndef SOL_UDP
|
||||
/**
|
||||
* @def SOL_UDP
|
||||
* @brief Macro for SOL UDP.
|
||||
*/
|
||||
#define SOL_UDP IPPROTO_UDP
|
||||
#endif
|
||||
|
||||
@@ -77,9 +97,12 @@ using namespace std::literals;
|
||||
namespace fs = std::filesystem;
|
||||
namespace bp = boost::process::v1;
|
||||
|
||||
window_system_e window_system;
|
||||
window_system_e window_system; ///< Window system.
|
||||
|
||||
namespace dyn {
|
||||
/**
|
||||
* @brief Return the native handle owned by the wrapper.
|
||||
*/
|
||||
void *handle(const std::vector<const char *> &libs) {
|
||||
void *handle;
|
||||
|
||||
@@ -103,6 +126,9 @@ namespace dyn {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Load persisted state from its backing store.
|
||||
*/
|
||||
int load(void *handle, const std::vector<std::tuple<apiproc *, const char *>> &funcs, bool strict) {
|
||||
int err = 0;
|
||||
for (auto &func : funcs) {
|
||||
@@ -122,8 +148,16 @@ namespace dyn {
|
||||
} // namespace dyn
|
||||
|
||||
namespace platf {
|
||||
/**
|
||||
* @brief Owning pointer for `getifaddrs` results.
|
||||
*/
|
||||
using ifaddr_t = util::safe_ptr<ifaddrs, freeifaddrs>;
|
||||
|
||||
/**
|
||||
* @brief Read the local interface address list.
|
||||
*
|
||||
* @return Owning pointer to the interface address list, or nullptr on failure.
|
||||
*/
|
||||
ifaddr_t get_ifaddrs() {
|
||||
ifaddrs *p {nullptr};
|
||||
|
||||
@@ -215,6 +249,9 @@ namespace platf {
|
||||
return config_path;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Convert a socket address to a printable IP address.
|
||||
*/
|
||||
std::string from_sockaddr(const sockaddr *const ip_addr) {
|
||||
char data[INET6_ADDRSTRLEN] = {};
|
||||
|
||||
@@ -228,6 +265,9 @@ namespace platf {
|
||||
return std::string {data};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Convert a socket address to a port and printable IP address.
|
||||
*/
|
||||
std::pair<std::uint16_t, std::string> from_sockaddr_ex(const sockaddr *const ip_addr) {
|
||||
char data[INET6_ADDRSTRLEN] = {};
|
||||
|
||||
@@ -244,6 +284,9 @@ namespace platf {
|
||||
return {port, std::string {data}};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Return the hardware MAC address associated with a network address.
|
||||
*/
|
||||
std::string get_mac_address(const std::string_view &address) {
|
||||
auto ifaddrs = get_ifaddrs();
|
||||
|
||||
@@ -336,6 +379,9 @@ namespace platf {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Apply the requested scheduling priority to the current thread.
|
||||
*/
|
||||
void adjust_thread_priority(thread_priority_e priority) {
|
||||
#if defined(__FreeBSD__)
|
||||
pid_t tid = syscall(SYS_thr_self);
|
||||
@@ -405,18 +451,30 @@ namespace platf {
|
||||
pthread_setname_np(pthread_self(), name.c_str());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Enable or disable X11 mouse keys for the current session.
|
||||
*/
|
||||
void enable_mouse_keys() {
|
||||
// Unimplemented
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Apply Linux platform state before streaming starts.
|
||||
*/
|
||||
void streaming_will_start() {
|
||||
// Nothing to do
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Restore Linux platform state after streaming stops.
|
||||
*/
|
||||
void streaming_will_stop() {
|
||||
// Nothing to do
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Request a Sunshine process restart on exit.
|
||||
*/
|
||||
void restart_on_exit() {
|
||||
char executable[PATH_MAX];
|
||||
ssize_t len = readlink("/proc/self/exe", executable, PATH_MAX - 1);
|
||||
@@ -439,12 +497,21 @@ namespace platf {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Restart the Sunshine process through the platform launcher.
|
||||
*/
|
||||
void restart() {
|
||||
// Gracefully clean up and restart ourselves instead of exiting
|
||||
atexit(restart_on_exit);
|
||||
lifetime::exit_sunshine(0, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Read an environment variable as an optional string.
|
||||
*
|
||||
* @param name Human-readable name to assign.
|
||||
* @return Environment variable value, or an empty string when unset.
|
||||
*/
|
||||
std::string get_env(const std::string &name) {
|
||||
if (const auto value = getenv(name.c_str()); value != nullptr) {
|
||||
return value;
|
||||
@@ -456,6 +523,14 @@ namespace platf {
|
||||
return setenv(name.c_str(), value.c_str(), 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Append a value to a separator-delimited environment variable.
|
||||
*
|
||||
* @param name Human-readable name to assign.
|
||||
* @param value Entry to add when it is not already present.
|
||||
* @param separator Character used to join or split the value.
|
||||
* @return Result from updating the environment variable.
|
||||
*/
|
||||
int append_env(const std::string &name, const std::string &value, const std::string &separator) {
|
||||
if (const std::string old_value = get_env(name); !old_value.contains(value)) {
|
||||
return set_env(name, old_value.empty() ? value : old_value + separator + value);
|
||||
@@ -481,6 +556,13 @@ namespace platf {
|
||||
return waitpid(-((pid_t) native_handle), nullptr, WNOHANG) >= 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Convert to sockaddr.
|
||||
*
|
||||
* @param address Network address being parsed or filtered.
|
||||
* @param port TCP or UDP port number.
|
||||
* @return Value converted to sockaddr.
|
||||
*/
|
||||
struct sockaddr_in to_sockaddr(boost::asio::ip::address_v4 address, uint16_t port) {
|
||||
struct sockaddr_in saddr_v4 = {};
|
||||
|
||||
@@ -493,6 +575,13 @@ namespace platf {
|
||||
return saddr_v4;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Convert to sockaddr.
|
||||
*
|
||||
* @param address Network address being parsed or filtered.
|
||||
* @param port TCP or UDP port number.
|
||||
* @return Value converted to sockaddr.
|
||||
*/
|
||||
struct sockaddr_in6 to_sockaddr(boost::asio::ip::address_v6 address, uint16_t port) {
|
||||
struct sockaddr_in6 saddr_v6 = {};
|
||||
|
||||
@@ -506,6 +595,9 @@ namespace platf {
|
||||
return saddr_v6;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Send multiple fixed-size UDP payload blocks using the platform backend.
|
||||
*/
|
||||
bool send_batch(batched_send_info_t &send_info) {
|
||||
auto sockfd = (int) send_info.native_socket;
|
||||
struct msghdr msg = {};
|
||||
@@ -731,6 +823,9 @@ namespace platf {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Send the serialized response over the active socket.
|
||||
*/
|
||||
bool send(send_info_t &send_info) {
|
||||
auto sockfd = (int) send_info.native_socket;
|
||||
struct msghdr msg = {};
|
||||
@@ -854,8 +949,17 @@ namespace platf {
|
||||
// are disconnected.
|
||||
static std::atomic<int> qos_ref_count = 0;
|
||||
|
||||
/**
|
||||
* @brief Linux QoS state used to tune socket priority while streaming.
|
||||
*/
|
||||
class qos_t: public deinit_t {
|
||||
public:
|
||||
/**
|
||||
* @brief Apply Linux socket priority and DSCP QoS settings for scoped cleanup.
|
||||
*
|
||||
* @param sockfd Native socket descriptor whose options are updated.
|
||||
* @param options Request options or socket options to apply.
|
||||
*/
|
||||
qos_t(int sockfd, std::vector<std::tuple<int, int, int>> options):
|
||||
sockfd(sockfd),
|
||||
options(options) {
|
||||
@@ -880,11 +984,6 @@ namespace platf {
|
||||
|
||||
/**
|
||||
* @brief Enables QoS on the given socket for traffic to the specified destination.
|
||||
* @param native_socket The native socket handle.
|
||||
* @param address The destination address for traffic sent on this socket.
|
||||
* @param port The destination port for traffic sent on this socket.
|
||||
* @param data_type The type of traffic sent on this socket.
|
||||
* @param dscp_tagging Specifies whether to enable DSCP tagging on outgoing traffic.
|
||||
*/
|
||||
std::unique_ptr<deinit_t> enable_socket_qos(uintptr_t native_socket, boost::asio::ip::address &address, uint16_t port, qos_data_type_e data_type, bool dscp_tagging) {
|
||||
int sockfd = (int) native_socket;
|
||||
@@ -964,6 +1063,9 @@ namespace platf {
|
||||
}
|
||||
|
||||
namespace source {
|
||||
/**
|
||||
* @brief Enumerates supported source options.
|
||||
*/
|
||||
enum source_e : std::size_t {
|
||||
#ifdef SUNSHINE_BUILD_CUDA
|
||||
NVFBC, ///< NvFBC
|
||||
@@ -999,9 +1101,27 @@ namespace platf {
|
||||
#endif
|
||||
|
||||
#ifdef SUNSHINE_BUILD_WAYLAND
|
||||
/**
|
||||
* @brief Enumerate displays available through the Wayland capture backend.
|
||||
*
|
||||
* @return Wayland display names, or an empty list when discovery fails.
|
||||
*/
|
||||
std::vector<std::string> wl_display_names();
|
||||
/**
|
||||
* @brief Create a Wayland display capture backend.
|
||||
*
|
||||
* @param hwdevice_type Hardware device type requested for capture or encode.
|
||||
* @param display_name Display name.
|
||||
* @param config Configuration values to apply.
|
||||
* @return Display backend, or nullptr when Wayland capture initialization fails.
|
||||
*/
|
||||
std::shared_ptr<display_t> wl_display(mem_type_e hwdevice_type, const std::string &display_name, const video::config_t &config);
|
||||
|
||||
/**
|
||||
* @brief Check whether Wayland capture is available for the current session.
|
||||
*
|
||||
* @return True when the active window system is Wayland and at least one output is discoverable.
|
||||
*/
|
||||
bool verify_wl() {
|
||||
return window_system == window_system_e::WAYLAND && !wl_display_names().empty();
|
||||
}
|
||||
@@ -1045,6 +1165,9 @@ namespace platf {
|
||||
}
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief List display names accepted by the selected capture backend.
|
||||
*/
|
||||
std::vector<std::string> display_names(mem_type_e hwdevice_type) {
|
||||
#ifdef SUNSHINE_BUILD_CUDA
|
||||
// display using NvFBC only supports mem_type_e::cuda
|
||||
@@ -1081,8 +1204,9 @@ namespace platf {
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns if GPUs/drivers have changed since the last call to this function.
|
||||
* @return `true` if a change has occurred or if it is unknown whether a change occurred.
|
||||
* @brief Report whether encoder backends should be probed again before streaming.
|
||||
*
|
||||
* @return Always `true` because Linux GPU changes are not tracked by this backend.
|
||||
*/
|
||||
bool needs_encoder_reenumeration() {
|
||||
// We don't track GPU state, so we will always reenumerate. Fortunately, it is fast on Linux.
|
||||
@@ -1137,6 +1261,9 @@ namespace platf {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Initialize the Linux high-precision timer file descriptor.
|
||||
*/
|
||||
std::unique_ptr<deinit_t> init() {
|
||||
// enable low latency mode for AMD
|
||||
// https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/30039
|
||||
@@ -1212,6 +1339,9 @@ namespace platf {
|
||||
return std::make_unique<deinit_t>();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Linux high-precision timer implementation backed by `timerfd`.
|
||||
*/
|
||||
class linux_high_precision_timer: public high_precision_timer {
|
||||
public:
|
||||
void sleep_for(const std::chrono::nanoseconds &duration) override {
|
||||
@@ -1227,6 +1357,11 @@ namespace platf {
|
||||
return std::make_unique<linux_high_precision_timer>();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Find the DRM render node associated with the active display.
|
||||
*
|
||||
* @return Render-node path, or an empty string when no matching node is found.
|
||||
*/
|
||||
std::string find_render_node_with_display() {
|
||||
#ifdef SUNSHINE_BUILD_DRM
|
||||
auto *dir = opendir("/dev/dri");
|
||||
@@ -1287,8 +1422,8 @@ namespace platf {
|
||||
static constexpr cap_value_t FULL_CAPS[] = {CAP_SYS_ADMIN, CAP_SYS_NICE};
|
||||
static constexpr cap_value_t ADMIN_CAPS[] = {CAP_SYS_ADMIN};
|
||||
|
||||
constexpr std::span<const cap_value_t> ELEVATED_PRIVILEGES_FULL {FULL_CAPS};
|
||||
constexpr std::span<const cap_value_t> ELEVATED_PRIVILEGES_ADMIN {ADMIN_CAPS};
|
||||
constexpr std::span<const cap_value_t> ELEVATED_PRIVILEGES_FULL {FULL_CAPS}; ///< Protocol or platform constant for elevated privileges full.
|
||||
constexpr std::span<const cap_value_t> ELEVATED_PRIVILEGES_ADMIN {ADMIN_CAPS}; ///< Protocol or platform constant for elevated privileges admin.
|
||||
#endif
|
||||
|
||||
bool has_elevated_privileges(bool all_caps) {
|
||||
|
||||
@@ -11,21 +11,34 @@
|
||||
// local includes
|
||||
#include "src/utility.h"
|
||||
|
||||
#ifndef DOXYGEN
|
||||
KITTY_USING_MOVE_T(file_t, int, -1, {
|
||||
if (el >= 0) {
|
||||
close(el);
|
||||
}
|
||||
});
|
||||
#else
|
||||
/**
|
||||
* @brief Move-only wrapper for a POSIX file descriptor.
|
||||
*/
|
||||
class file_t;
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Enumerates supported window system options.
|
||||
*/
|
||||
enum class window_system_e {
|
||||
NONE, ///< No window system
|
||||
X11, ///< X11
|
||||
WAYLAND, ///< Wayland
|
||||
};
|
||||
|
||||
extern window_system_e window_system;
|
||||
extern window_system_e window_system; ///< Window system.
|
||||
|
||||
namespace dyn {
|
||||
/**
|
||||
* @brief Generic GLX procedure pointer returned by the loader.
|
||||
*/
|
||||
typedef void (*apiproc)(void);
|
||||
|
||||
int load(void *handle, const std::vector<std::tuple<apiproc *, const char *>> &funcs, bool strict = true);
|
||||
|
||||
+182
-31
@@ -25,7 +25,7 @@
|
||||
#include "wayland.h"
|
||||
|
||||
#if !PW_CHECK_VERSION(1, 6, 0)
|
||||
constexpr int SPA_VIDEO_TRANSFER_SMPTE2084 = 14;
|
||||
constexpr int SPA_VIDEO_TRANSFER_SMPTE2084 = 14; ///< Protocol or platform constant for spa video transfer smpte2084.
|
||||
#endif
|
||||
|
||||
#if PW_CHECK_VERSION(0, 3, 75)
|
||||
@@ -36,7 +36,11 @@ const bool SUNSHINE_USE_PIPEWIRE_OBJECT_SERIAL = pw_check_library_version(0, 3,
|
||||
constexpr bool SUNSHINE_USE_PIPEWIRE_OBJECT_SERIAL = true;
|
||||
#else
|
||||
// Pipewire object serials are unsupported without PW_KEY_TARGET_OBJECT (we define it here so compilation won't break but don't use it).
|
||||
constexpr bool SUNSHINE_USE_PIPEWIRE_OBJECT_SERIAL = false;
|
||||
constexpr bool SUNSHINE_USE_PIPEWIRE_OBJECT_SERIAL = false; ///< Whether PipeWire object serials should be used for matching.
|
||||
/**
|
||||
* @def PW_KEY_TARGET_OBJECT
|
||||
* @brief Macro for PW KEY TARGET OBJECT.
|
||||
*/
|
||||
#define PW_KEY_TARGET_OBJECT "target.object"
|
||||
#endif
|
||||
|
||||
@@ -51,9 +55,12 @@ namespace {
|
||||
using namespace std::literals;
|
||||
|
||||
namespace pipewire {
|
||||
/**
|
||||
* @brief PipeWire SPA format mapped to Sunshine pixel format.
|
||||
*/
|
||||
struct format_map_t {
|
||||
uint64_t fourcc;
|
||||
int32_t pw_format;
|
||||
uint64_t fourcc; ///< DRM fourcc pixel format.
|
||||
int32_t pw_format; ///< Matching PipeWire SPA video format.
|
||||
};
|
||||
|
||||
static constexpr std::array<format_map_t, 7> format_map = {{
|
||||
@@ -66,47 +73,59 @@ namespace pipewire {
|
||||
{DRM_FORMAT_XRGB8888, SPA_VIDEO_FORMAT_BGRx},
|
||||
}};
|
||||
|
||||
/**
|
||||
* @brief PipeWire capture state shared with callback threads.
|
||||
*/
|
||||
struct shared_state_t {
|
||||
std::atomic<int> negotiated_width {0};
|
||||
std::atomic<int> negotiated_height {0};
|
||||
std::atomic<int> color_primaries {0};
|
||||
std::atomic<int> transfer_function {0};
|
||||
std::atomic<bool> stream_dead {false};
|
||||
pw_stream_state previous_state;
|
||||
pw_stream_state current_state;
|
||||
std::string err_msg;
|
||||
std::atomic<int> negotiated_width {0}; ///< Width negotiated with PipeWire for the stream.
|
||||
std::atomic<int> negotiated_height {0}; ///< Height negotiated with PipeWire for the stream.
|
||||
std::atomic<int> color_primaries {0}; ///< PipeWire color-primaries metadata for the stream.
|
||||
std::atomic<int> transfer_function {0}; ///< PipeWire transfer-function metadata for the stream.
|
||||
std::atomic<bool> stream_dead {false}; ///< Whether the PipeWire stream has been destroyed.
|
||||
pw_stream_state previous_state; ///< Previous PipeWire stream state reported by callbacks.
|
||||
pw_stream_state current_state; ///< Current PipeWire stream state reported by callbacks.
|
||||
std::string err_msg; ///< Last PipeWire error message reported by the stream.
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief PipeWire stream handle, format, and shared state pointer.
|
||||
*/
|
||||
struct stream_data_t {
|
||||
struct pw_stream *stream;
|
||||
struct spa_hook stream_listener;
|
||||
struct spa_video_info format;
|
||||
struct pw_buffer *current_buffer;
|
||||
uint64_t drm_format;
|
||||
std::shared_ptr<shared_state_t> shared;
|
||||
std::mutex frame_mutex;
|
||||
std::condition_variable frame_cv;
|
||||
size_t local_stride = 0;
|
||||
bool frame_ready = false;
|
||||
struct pw_stream *stream; ///< PipeWire stream handle used for screencast frames.
|
||||
struct spa_hook stream_listener; ///< Hook registering callbacks on the PipeWire stream.
|
||||
struct spa_video_info format; ///< Negotiated PipeWire video format.
|
||||
struct pw_buffer *current_buffer; ///< PipeWire buffer currently exposed to the capture thread.
|
||||
uint64_t drm_format; ///< DRM format.
|
||||
std::shared_ptr<shared_state_t> shared; ///< State shared between PipeWire callbacks and the capture backend.
|
||||
std::mutex frame_mutex; ///< Synchronizes access to the current PipeWire frame.
|
||||
std::condition_variable frame_cv; ///< Signals arrival or release of a PipeWire frame.
|
||||
size_t local_stride = 0; ///< Local stride.
|
||||
bool frame_ready = false; ///< Whether a PipeWire frame is ready to consume.
|
||||
// Two distinct memory pools
|
||||
std::vector<uint8_t> buffer_a;
|
||||
std::vector<uint8_t> buffer_b;
|
||||
std::vector<uint8_t> buffer_a; ///< First staging buffer used for CPU-copy PipeWire frames.
|
||||
std::vector<uint8_t> buffer_b; ///< Second staging buffer used for CPU-copy PipeWire frames.
|
||||
// Points to the buffer currently owned by fill_img
|
||||
std::vector<uint8_t> *front_buffer;
|
||||
std::vector<uint8_t> *front_buffer; ///< Staging buffer currently readable by `fill_img`.
|
||||
// Points to the buffer currently being written by on_process
|
||||
std::vector<uint8_t> *back_buffer;
|
||||
std::vector<uint8_t> *back_buffer; ///< Staging buffer currently writable by PipeWire callbacks.
|
||||
|
||||
stream_data_t():
|
||||
front_buffer(&buffer_a),
|
||||
back_buffer(&buffer_b) {}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief DMA-BUF format and modifier list advertised by PipeWire.
|
||||
*/
|
||||
struct dmabuf_format_info_t {
|
||||
int32_t format;
|
||||
uint64_t *modifiers;
|
||||
int n_modifiers;
|
||||
int32_t format; ///< PipeWire SPA video format being advertised.
|
||||
uint64_t *modifiers; ///< DRM format modifiers supported for the format.
|
||||
int n_modifiers; ///< Number of entries in `modifiers`.
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief PipeWire core, context, and stream setup used for screencast capture.
|
||||
*/
|
||||
class pipewire_t {
|
||||
public:
|
||||
pipewire_t():
|
||||
@@ -154,22 +173,51 @@ namespace pipewire {
|
||||
pw_thread_loop_destroy(loop);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Return the mutex protecting PipeWire frame state.
|
||||
*
|
||||
* @return Mutex used by producer and capture threads.
|
||||
*/
|
||||
std::mutex &frame_mutex() {
|
||||
return stream_data.frame_mutex;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Return the condition variable signaled when frame state changes.
|
||||
*
|
||||
* @return Condition variable used to wait for frames or shutdown.
|
||||
*/
|
||||
std::condition_variable &frame_cv() {
|
||||
return stream_data.frame_cv;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check whether frame ready.
|
||||
*
|
||||
* @return True when PipeWire has delivered a frame ready for capture.
|
||||
*/
|
||||
bool is_frame_ready() const {
|
||||
return stream_data.frame_ready;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set frame ready.
|
||||
*
|
||||
* @param ready Whether the PipeWire frame is ready for capture.
|
||||
*/
|
||||
void set_frame_ready(bool ready) {
|
||||
stream_data.frame_ready = ready;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Initialize PipeWire core objects and optional stream negotiation.
|
||||
*
|
||||
* @param stream_fd Stream fd.
|
||||
* @param stream_node Stream node.
|
||||
* @param stream_object_serial Stream object serial.
|
||||
* @param shared_state Shared state.
|
||||
* @return 0 on success; nonzero or negative platform status on failure.
|
||||
*/
|
||||
int init(const int stream_fd, const uint32_t stream_node, const uint64_t stream_object_serial, std::shared_ptr<shared_state_t> shared_state) {
|
||||
fd = stream_fd;
|
||||
node = stream_node;
|
||||
@@ -201,6 +249,9 @@ namespace pipewire {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Release the active PipeWire stream and listener.
|
||||
*/
|
||||
void cleanup_stream() {
|
||||
BOOST_LOG(debug) << "[pipewire] Cleaning up stream"sv;
|
||||
if (loop && stream_data.stream) {
|
||||
@@ -226,6 +277,18 @@ namespace pipewire {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Create the PipeWire stream if it is not already active.
|
||||
*
|
||||
* @param mem_type Mem type.
|
||||
* @param width Frame or display width in pixels.
|
||||
* @param height Frame or display height in pixels.
|
||||
* @param refresh_rate Refresh rate.
|
||||
* @param dmabuf_infos Dmabuf infos.
|
||||
* @param n_dmabuf_infos N dmabuf infos.
|
||||
* @param display_is_nvidia Display is nvidia.
|
||||
* @return 0 when the PipeWire stream is configured; nonzero on negotiation failure.
|
||||
*/
|
||||
int ensure_stream(const platf::mem_type_e mem_type, const uint32_t width, const uint32_t height, const uint32_t refresh_rate, const struct dmabuf_format_info_t *dmabuf_infos, const int n_dmabuf_infos, const bool display_is_nvidia) {
|
||||
pw_thread_loop_lock(loop);
|
||||
int result = 0;
|
||||
@@ -293,6 +356,11 @@ namespace pipewire {
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Close img fds.
|
||||
*
|
||||
* @param img_descriptor Image descriptor whose duplicated DMA-BUF fds are closed.
|
||||
*/
|
||||
static void close_img_fds(egl::img_descriptor_t *img_descriptor) {
|
||||
for (int &fd : img_descriptor->sd.fds) {
|
||||
if (fd >= 0) {
|
||||
@@ -302,6 +370,12 @@ namespace pipewire {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Copy PipeWire metadata into the Sunshine image descriptor.
|
||||
*
|
||||
* @param img_descriptor Image descriptor receiving timestamps, sequence, and damage flags.
|
||||
* @param buf Raw byte buffer used for serialization.
|
||||
*/
|
||||
static void fill_img_metadata(egl::img_descriptor_t *img_descriptor, struct spa_buffer *buf) {
|
||||
img_descriptor->frame_timestamp = std::chrono::steady_clock::now();
|
||||
|
||||
@@ -323,6 +397,13 @@ namespace pipewire {
|
||||
img_descriptor->pw_damage = (damage && damage->region.size.width > 0 && damage->region.size.height > 0) ? std::optional<bool>(true) : std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Populate a Sunshine image descriptor from PipeWire DMA-BUF planes.
|
||||
*
|
||||
* @param img_descriptor Image descriptor receiving duplicated fds and plane layout.
|
||||
* @param buf Raw byte buffer used for serialization.
|
||||
* @param d PipeWire listener data passed to the callback.
|
||||
*/
|
||||
static void fill_img_dmabuf(egl::img_descriptor_t *img_descriptor, struct spa_buffer *buf, const stream_data_t &d) {
|
||||
img_descriptor->sd.width = d.format.info.raw.size.width;
|
||||
img_descriptor->sd.height = d.format.info.raw.size.height;
|
||||
@@ -335,6 +416,11 @@ namespace pipewire {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Copy the latest PipeWire frame into Sunshine's image buffer.
|
||||
*
|
||||
* @param img Image or frame object to read from or populate.
|
||||
*/
|
||||
void fill_img(platf::img_t *img) {
|
||||
pw_thread_loop_lock(loop);
|
||||
std::scoped_lock lock(stream_data.frame_mutex);
|
||||
@@ -367,6 +453,11 @@ namespace pipewire {
|
||||
pw_thread_loop_unlock(loop);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set negotiate maxframerate.
|
||||
*
|
||||
* @param negotiate_maxframerate Negotiate maxframerate.
|
||||
*/
|
||||
void set_negotiate_maxframerate(bool negotiate_maxframerate) {
|
||||
negotiate_maxframerate_ = negotiate_maxframerate;
|
||||
}
|
||||
@@ -626,8 +717,17 @@ namespace pipewire {
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Display capture backend that consumes frames from a PipeWire stream.
|
||||
*/
|
||||
class pipewire_display_t: public platf::display_t {
|
||||
public:
|
||||
/**
|
||||
* @brief Initialize pipewire and check hwdevice type.
|
||||
*
|
||||
* @param hwdevice_type Hardware device type requested for capture or encode.
|
||||
* @return True when PipeWire is initialized and the hardware device type is supported.
|
||||
*/
|
||||
static bool init_pipewire_and_check_hwdevice_type(platf::mem_type_e hwdevice_type) {
|
||||
// Initialize pipewire to load necessary modules
|
||||
pw_init(nullptr, nullptr);
|
||||
@@ -694,6 +794,14 @@ namespace pipewire {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Initialize the PipeWire display backend for a selected stream.
|
||||
*
|
||||
* @param hwdevice_type Hardware device type requested for capture or encode.
|
||||
* @param display_name Display name.
|
||||
* @param config Configuration values to apply.
|
||||
* @return 0 on success; nonzero or negative platform status on failure.
|
||||
*/
|
||||
int init(platf::mem_type_e hwdevice_type, const std::string &display_name, const ::video::config_t &config) {
|
||||
// calculate frame interval we should capture at
|
||||
framerate = config.framerate;
|
||||
@@ -781,6 +889,15 @@ namespace pipewire {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Capture a display frame into the provided image object.
|
||||
*
|
||||
* @param pull_free_image_cb Callback that provides an available image buffer.
|
||||
* @param img_out Captured PipeWire image returned to the streaming pipeline.
|
||||
* @param timeout Maximum time to wait for the operation.
|
||||
* @param show_cursor Show cursor.
|
||||
* @return Capture status reported to the streaming pipeline.
|
||||
*/
|
||||
platf::capture_e snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr<platf::img_t> &img_out, std::chrono::milliseconds timeout, bool show_cursor) {
|
||||
// FIXME: show_cursor is ignored
|
||||
auto deadline = std::chrono::steady_clock::now() + timeout;
|
||||
@@ -812,6 +929,11 @@ namespace pipewire {
|
||||
return platf::capture_e::timeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Allocate an image buffer compatible with this display backend.
|
||||
*
|
||||
* @return Allocated img object, or null when unavailable.
|
||||
*/
|
||||
std::shared_ptr<platf::img_t> alloc_img() override {
|
||||
// Note: this img_t type is also used for memory buffers
|
||||
auto img = std::make_shared<egl::img_descriptor_t>();
|
||||
@@ -828,6 +950,12 @@ namespace pipewire {
|
||||
return img;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check stream dead.
|
||||
*
|
||||
* @param out_status Out status.
|
||||
* @return True when the PipeWire stream can no longer produce frames.
|
||||
*/
|
||||
virtual bool check_stream_dead(platf::capture_e &out_status) {
|
||||
return false; // Return to default stream dead handling.
|
||||
}
|
||||
@@ -899,6 +1027,12 @@ namespace pipewire {
|
||||
return platf::capture_e::ok;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Create AVCodec encode device.
|
||||
*
|
||||
* @param pix_fmt Sunshine pixel format to convert or allocate for.
|
||||
* @return Constructed AVCodec encode device object.
|
||||
*/
|
||||
std::unique_ptr<platf::avcodec_encode_device_t> make_avcodec_encode_device(platf::pix_fmt_e pix_fmt) override {
|
||||
#ifdef SUNSHINE_BUILD_VAAPI
|
||||
if (mem_type == platf::mem_type_e::vaapi) {
|
||||
@@ -928,6 +1062,12 @@ namespace pipewire {
|
||||
return std::make_unique<platf::avcodec_encode_device_t>();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Populate a fallback image when real capture data is unavailable.
|
||||
*
|
||||
* @param img Image or frame object to read from or populate.
|
||||
* @return Capture status reported to the streaming pipeline.
|
||||
*/
|
||||
int dummy_img(platf::img_t *img) override {
|
||||
if (!img) {
|
||||
return -1;
|
||||
@@ -938,6 +1078,11 @@ namespace pipewire {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Report whether the active display mode is HDR.
|
||||
*
|
||||
* @return True when the active display mode is HDR.
|
||||
*/
|
||||
bool is_hdr() override {
|
||||
int color_primaries = shared_state->color_primaries.load();
|
||||
int transfer_function = shared_state->transfer_function.load();
|
||||
@@ -949,6 +1094,12 @@ namespace pipewire {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Read HDR metadata for the active display mode.
|
||||
*
|
||||
* @param metadata Output structure populated with HDR metadata.
|
||||
* @return True when HDR metadata was written to the output structure.
|
||||
*/
|
||||
bool get_hdr_metadata(SS_HDR_METADATA &metadata) override {
|
||||
int color_primaries = shared_state->color_primaries.load();
|
||||
int transfer_function = shared_state->transfer_function.load();
|
||||
@@ -1122,7 +1273,7 @@ namespace pipewire {
|
||||
|
||||
protected:
|
||||
// Allow subclasses to access for pipewire requirements setup and stream dead checks
|
||||
pipewire_t pipewire;
|
||||
std::shared_ptr<shared_state_t> shared_state;
|
||||
pipewire_t pipewire; ///< Pipewire.
|
||||
std::shared_ptr<shared_state_t> shared_state; ///< Shared state.
|
||||
};
|
||||
} // namespace pipewire
|
||||
|
||||
@@ -35,20 +35,41 @@ namespace portal {
|
||||
// Forward declarations
|
||||
class runtime_t;
|
||||
|
||||
/**
|
||||
* @brief Persistent portal restore token used to reuse screencast permission.
|
||||
*/
|
||||
class restore_token_t {
|
||||
public:
|
||||
/**
|
||||
* @brief Return the currently wrapped value or handle.
|
||||
*
|
||||
* @return Underlying native handle or object pointer.
|
||||
*/
|
||||
static std::string get() {
|
||||
return *token_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Store the new value and mark it dirty for persistence.
|
||||
*
|
||||
* @param value Portal restore token received from xdg-desktop-portal.
|
||||
*/
|
||||
static void set(std::string_view value) {
|
||||
*token_ = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Return whether the persisted value is empty.
|
||||
*
|
||||
* @return True when no portal display id has been persisted.
|
||||
*/
|
||||
static bool empty() {
|
||||
return token_->empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Load persisted state from its backing store.
|
||||
*/
|
||||
static void load() {
|
||||
std::ifstream file(get_file_path());
|
||||
if (file.is_open()) {
|
||||
@@ -59,6 +80,9 @@ namespace portal {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Save current state to its backing store.
|
||||
*/
|
||||
static void save() {
|
||||
if (token_->empty()) {
|
||||
return;
|
||||
@@ -80,21 +104,32 @@ namespace portal {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief DBus response loop and response variant for portal calls.
|
||||
*/
|
||||
struct dbus_response_t {
|
||||
GMainLoop *loop;
|
||||
GVariant *response;
|
||||
guint subscription_id;
|
||||
GMainLoop *loop; ///< GLib main loop waiting for a portal response signal.
|
||||
GVariant *response; ///< DBus response payload returned by the portal.
|
||||
guint subscription_id; ///< Subscription ID.
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief PipeWire stream node and negotiated capture size.
|
||||
*/
|
||||
struct pipewire_streaminfo_t {
|
||||
uint32_t pipewire_node = PW_ID_ANY;
|
||||
uint64_t pipewire_object_serial = SPA_ID_INVALID;
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
int pos_x = 0;
|
||||
int pos_y = 0;
|
||||
std::string monitor_name;
|
||||
uint32_t pipewire_node = PW_ID_ANY; ///< PipeWire node ID selected by the portal.
|
||||
uint64_t pipewire_object_serial = SPA_ID_INVALID; ///< PipeWire object serial selected by the portal.
|
||||
int width = 0; ///< Stream width in pixels.
|
||||
int height = 0; ///< Stream height in pixels.
|
||||
int pos_x = 0; ///< Output X position reported by the portal.
|
||||
int pos_y = 0; ///< Output Y position reported by the portal.
|
||||
std::string monitor_name; ///< Monitor name.
|
||||
|
||||
/**
|
||||
* @brief Convert to display name.
|
||||
*
|
||||
* @return Value converted to display name.
|
||||
*/
|
||||
std::string to_display_name() {
|
||||
if (!monitor_name.empty()) {
|
||||
return monitor_name;
|
||||
@@ -102,12 +137,21 @@ namespace portal {
|
||||
return std::format("position-{}x{}-resolution-{}x{}", pos_x, pos_y, width, height);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check whether a portal stream matches a requested display name.
|
||||
*
|
||||
* @param display_name Display name.
|
||||
* @return True when the portal display id matches the requested display name.
|
||||
*/
|
||||
bool match_display_name(const std::string_view &display_name) {
|
||||
// Check the given non-empty display name matches the display name for this struct
|
||||
return !display_name.empty() && display_name == to_display_name();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief DBus connection and portal request helpers for screencast setup.
|
||||
*/
|
||||
class dbus_t {
|
||||
public:
|
||||
dbus_t &operator=(dbus_t &&) = delete; // Do not allow to copying
|
||||
@@ -157,6 +201,11 @@ namespace portal {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Open DBus and prepare portal screencast request handling.
|
||||
*
|
||||
* @return 0 on success; nonzero or negative platform status on failure.
|
||||
*/
|
||||
int init() {
|
||||
restore_token_t::load();
|
||||
|
||||
@@ -176,6 +225,11 @@ namespace portal {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Connect to xdg-desktop-portal and restore or create a screencast session.
|
||||
*
|
||||
* @return 0 when a portal session is ready; nonzero when D-Bus or portal setup fails.
|
||||
*/
|
||||
int connect_to_portal() {
|
||||
g_autoptr(GMainLoop) loop = g_main_loop_new(nullptr, FALSE);
|
||||
g_autofree gchar *session_path = nullptr;
|
||||
@@ -203,6 +257,14 @@ namespace portal {
|
||||
|
||||
// Try to create a combined RemoteDesktop + ScreenCast session
|
||||
// Returns true on success, false if should fall back to ScreenCast-only
|
||||
/**
|
||||
* @brief Try to create a RemoteDesktop portal session.
|
||||
*
|
||||
* @param loop GLib main loop associated with the portal request.
|
||||
* @param session_path Session path.
|
||||
* @param session_token Session token.
|
||||
* @return True when the portal request or state check succeeds.
|
||||
*/
|
||||
bool try_remote_desktop_session(GMainLoop *loop, gchar **session_path, const gchar *session_token) {
|
||||
if (create_portal_session(loop, session_path, session_token, false) < 0) {
|
||||
return false;
|
||||
@@ -226,6 +288,13 @@ namespace portal {
|
||||
}
|
||||
|
||||
// Create a ScreenCast-only session
|
||||
/**
|
||||
* @brief Create a screencast-only portal session without remote-desktop control.
|
||||
*
|
||||
* @param loop GLib main loop associated with the portal request.
|
||||
* @param session_path Session path.
|
||||
* @return 0 when the portal returns a session path; nonzero on request failure.
|
||||
*/
|
||||
int try_screencast_only_session(GMainLoop *loop, gchar **session_path) {
|
||||
g_autofree gchar *new_session_token = nullptr;
|
||||
create_session_path(conn, nullptr, &new_session_token);
|
||||
@@ -240,6 +309,11 @@ namespace portal {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check whether session closed.
|
||||
*
|
||||
* @return True when the portal session has been closed.
|
||||
*/
|
||||
bool is_session_closed() const {
|
||||
if (conn && !session_handle.empty()) {
|
||||
// Try to retrieve property org.freedesktop.portal.Session::version
|
||||
@@ -267,8 +341,8 @@ namespace portal {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<pipewire_streaminfo_t> pipewire_streams;
|
||||
int pipewire_fd;
|
||||
std::vector<pipewire_streaminfo_t> pipewire_streams; ///< Pipewire streams.
|
||||
int pipewire_fd; ///< Pipewire fd.
|
||||
|
||||
private:
|
||||
GDBusConnection *conn;
|
||||
@@ -628,6 +702,9 @@ namespace portal {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Portal screencast backend that negotiates PipeWire streams over DBus.
|
||||
*/
|
||||
class portal_t: public pipewire::pipewire_display_t {
|
||||
public:
|
||||
int configure_stream(const std::string &display_name, int &out_pipewire_fd, uint32_t &out_pipewire_node, uint64_t &out_pipewire_object_serial [[maybe_unused]]) override {
|
||||
@@ -680,6 +757,12 @@ namespace portal {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check stream dead.
|
||||
*
|
||||
* @param out_status Out status.
|
||||
* @return True when the PipeWire stream can no longer produce frames.
|
||||
*/
|
||||
bool check_stream_dead(platf::capture_e &out_status) override {
|
||||
// If the pipewire stream stopped due to closed portal session stop the capture with an error
|
||||
if (dbus.is_session_closed()) {
|
||||
@@ -700,14 +783,22 @@ namespace portal {
|
||||
}
|
||||
|
||||
// DBus portal connection
|
||||
dbus_t dbus;
|
||||
dbus_t dbus; ///< DBus connection used for portal screencast requests.
|
||||
|
||||
// Class variable to store runtime state of maxFramerate negotiation
|
||||
static inline std::atomic<bool> negotiate_maxframerate {true};
|
||||
static inline std::atomic<bool> negotiate_maxframerate {true}; ///< Whether portal negotiation should request the maximum frame rate.
|
||||
};
|
||||
} // namespace portal
|
||||
|
||||
namespace platf {
|
||||
/**
|
||||
* @brief Create a portal-based display capture backend.
|
||||
*
|
||||
* @param hwdevice_type Hardware device type requested for capture or encode.
|
||||
* @param display_name Display name.
|
||||
* @param config Configuration values to apply.
|
||||
* @return Display backend backed by xdg-desktop-portal and PipeWire, or nullptr.
|
||||
*/
|
||||
std::shared_ptr<display_t> portal_display(mem_type_e hwdevice_type, const std::string &display_name, const video::config_t &config) {
|
||||
using enum platf::mem_type_e;
|
||||
if (!pipewire::pipewire_display_t::init_pipewire_and_check_hwdevice_type(hwdevice_type)) {
|
||||
@@ -728,6 +819,11 @@ namespace platf {
|
||||
return portal;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Enumerate capture targets available through xdg-desktop-portal.
|
||||
*
|
||||
* @return Portal display names, or an empty list when portal discovery fails.
|
||||
*/
|
||||
std::vector<std::string> portal_display_names() {
|
||||
std::vector<std::string> display_names;
|
||||
auto dbus = std::make_shared<portal::dbus_t>();
|
||||
|
||||
+160
-24
@@ -85,14 +85,20 @@ namespace avahi {
|
||||
ERR_MAX = -54 ///< TODO
|
||||
};
|
||||
|
||||
constexpr auto IF_UNSPEC = -1;
|
||||
constexpr auto IF_UNSPEC = -1; ///< Protocol or platform constant for if unspec.
|
||||
|
||||
/**
|
||||
* @brief Enumerates supported proto options.
|
||||
*/
|
||||
enum proto {
|
||||
PROTO_INET = 0, ///< IPv4
|
||||
PROTO_INET6 = 1, ///< IPv6
|
||||
PROTO_UNSPEC = -1 ///< Unspecified/all protocol(s)
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Enumerates supported server state options.
|
||||
*/
|
||||
enum ServerState {
|
||||
SERVER_INVALID, ///< Invalid state (initial)
|
||||
SERVER_REGISTERING, ///< Host RRs are being registered
|
||||
@@ -101,6 +107,9 @@ namespace avahi {
|
||||
SERVER_FAILURE ///< Some fatal failure happened, the server is unable to proceed
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Enumerates supported client state options.
|
||||
*/
|
||||
enum ClientState {
|
||||
CLIENT_S_REGISTERING = SERVER_REGISTERING, ///< Server state: REGISTERING
|
||||
CLIENT_S_RUNNING = SERVER_RUNNING, ///< Server state: RUNNING
|
||||
@@ -109,6 +118,9 @@ namespace avahi {
|
||||
CLIENT_CONNECTING = 101 ///< We're still connecting. This state is only entered when AVAHI_CLIENT_NO_FAIL has been passed to avahi_client_new() and the daemon is not yet available.
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Enumerates supported entry group state options.
|
||||
*/
|
||||
enum EntryGroupState {
|
||||
ENTRY_GROUP_UNCOMMITED, ///< The group has not yet been committed, the user must still call avahi_entry_group_commit()
|
||||
ENTRY_GROUP_REGISTERING, ///< The entries of the group are currently being registered
|
||||
@@ -117,6 +129,9 @@ namespace avahi {
|
||||
ENTRY_GROUP_FAILURE ///< Some kind of failure happened, the entries have been withdrawn
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Enumerates supported client flags options.
|
||||
*/
|
||||
enum ClientFlags {
|
||||
CLIENT_IGNORE_USER_CONFIG = 1, ///< Don't read user configuration
|
||||
CLIENT_NO_FAIL = 2 ///< Don't fail if the daemon is not available when avahi_client_new() is called, instead enter CLIENT_CONNECTING state and wait for the daemon to appear
|
||||
@@ -137,7 +152,13 @@ namespace avahi {
|
||||
PUBLISH_USE_MULTICAST = 256 ///< Register the record using multicast DNS
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Network interface index type used by Avahi.
|
||||
*/
|
||||
using IfIndex = int;
|
||||
/**
|
||||
* @brief Avahi protocol/address-family selector type.
|
||||
*/
|
||||
using Protocol = int;
|
||||
|
||||
struct EntryGroup;
|
||||
@@ -145,18 +166,45 @@ namespace avahi {
|
||||
struct SimplePoll;
|
||||
struct Client;
|
||||
|
||||
/**
|
||||
* @brief Avahi client state-change callback signature.
|
||||
*/
|
||||
typedef void (*ClientCallback)(Client *, ClientState, void *userdata);
|
||||
/**
|
||||
* @brief Avahi entry-group state-change callback signature.
|
||||
*/
|
||||
typedef void (*EntryGroupCallback)(EntryGroup *g, EntryGroupState state, void *userdata);
|
||||
|
||||
/**
|
||||
* @brief Function pointer used to free Avahi-allocated memory.
|
||||
*/
|
||||
typedef void (*free_fn)(void *);
|
||||
|
||||
/**
|
||||
* @brief Function pointer used to create an Avahi client.
|
||||
*/
|
||||
typedef Client *(*client_new_fn)(const Poll *poll_api, ClientFlags flags, ClientCallback callback, void *userdata, int *error);
|
||||
/**
|
||||
* @brief Function pointer used to destroy an Avahi client.
|
||||
*/
|
||||
typedef void (*client_free_fn)(Client *);
|
||||
/**
|
||||
* @brief Function pointer used to request an alternative Avahi service name.
|
||||
*/
|
||||
typedef char *(*alternative_service_name_fn)(char *);
|
||||
|
||||
/**
|
||||
* @brief Function pointer used to get an Avahi client from an entry group.
|
||||
*/
|
||||
typedef Client *(*entry_group_get_client_fn)(EntryGroup *);
|
||||
|
||||
/**
|
||||
* @brief Function pointer used to create an Avahi entry group.
|
||||
*/
|
||||
typedef EntryGroup *(*entry_group_new_fn)(Client *, EntryGroupCallback, void *userdata);
|
||||
/**
|
||||
* @brief Function pointer used to add a service to an Avahi entry group.
|
||||
*/
|
||||
typedef int (*entry_group_add_service_fn)(
|
||||
EntryGroup *group,
|
||||
IfIndex interface,
|
||||
@@ -170,39 +218,77 @@ namespace avahi {
|
||||
...
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief Function pointer used to test whether an Avahi entry group is empty.
|
||||
*/
|
||||
typedef int (*entry_group_is_empty_fn)(EntryGroup *);
|
||||
/**
|
||||
* @brief Function pointer used to reset an Avahi entry group.
|
||||
*/
|
||||
typedef int (*entry_group_reset_fn)(EntryGroup *);
|
||||
/**
|
||||
* @brief Function pointer used to publish an Avahi entry group.
|
||||
*/
|
||||
typedef int (*entry_group_commit_fn)(EntryGroup *);
|
||||
|
||||
/**
|
||||
* @brief Function pointer used to duplicate Avahi strings.
|
||||
*/
|
||||
typedef char *(*strdup_fn)(const char *);
|
||||
/**
|
||||
* @brief Function pointer used to format Avahi error codes.
|
||||
*/
|
||||
typedef char *(*strerror_fn)(int);
|
||||
/**
|
||||
* @brief Function pointer used to read the last Avahi client error.
|
||||
*/
|
||||
typedef int (*client_errno_fn)(Client *);
|
||||
|
||||
/**
|
||||
* @brief Function pointer used to get the Avahi simple-poll API.
|
||||
*/
|
||||
typedef Poll *(*simple_poll_get_fn)(SimplePoll *);
|
||||
/**
|
||||
* @brief Function pointer used to run the Avahi simple-poll loop.
|
||||
*/
|
||||
typedef int (*simple_poll_loop_fn)(SimplePoll *);
|
||||
/**
|
||||
* @brief Function pointer used to stop the Avahi simple-poll loop.
|
||||
*/
|
||||
typedef void (*simple_poll_quit_fn)(SimplePoll *);
|
||||
/**
|
||||
* @brief Function pointer used to allocate an Avahi simple-poll instance.
|
||||
*/
|
||||
typedef SimplePoll *(*simple_poll_new_fn)();
|
||||
/**
|
||||
* @brief Function pointer used to free an Avahi simple-poll instance.
|
||||
*/
|
||||
typedef void (*simple_poll_free_fn)(SimplePoll *);
|
||||
|
||||
free_fn free;
|
||||
client_new_fn client_new;
|
||||
client_free_fn client_free;
|
||||
alternative_service_name_fn alternative_service_name;
|
||||
entry_group_get_client_fn entry_group_get_client;
|
||||
entry_group_new_fn entry_group_new;
|
||||
entry_group_add_service_fn entry_group_add_service;
|
||||
entry_group_is_empty_fn entry_group_is_empty;
|
||||
entry_group_reset_fn entry_group_reset;
|
||||
entry_group_commit_fn entry_group_commit;
|
||||
strdup_fn strdup;
|
||||
strerror_fn strerror;
|
||||
client_errno_fn client_errno;
|
||||
simple_poll_get_fn simple_poll_get;
|
||||
simple_poll_loop_fn simple_poll_loop;
|
||||
simple_poll_quit_fn simple_poll_quit;
|
||||
simple_poll_new_fn simple_poll_new;
|
||||
simple_poll_free_fn simple_poll_free;
|
||||
free_fn free; ///< Free.
|
||||
client_new_fn client_new; ///< Client new.
|
||||
client_free_fn client_free; ///< Client free.
|
||||
alternative_service_name_fn alternative_service_name; ///< Alternative service name.
|
||||
entry_group_get_client_fn entry_group_get_client; ///< Entry group get client.
|
||||
entry_group_new_fn entry_group_new; ///< Entry group new.
|
||||
entry_group_add_service_fn entry_group_add_service; ///< Entry group add service.
|
||||
entry_group_is_empty_fn entry_group_is_empty; ///< Entry group is empty.
|
||||
entry_group_reset_fn entry_group_reset; ///< Entry group reset.
|
||||
entry_group_commit_fn entry_group_commit; ///< Entry group commit.
|
||||
strdup_fn strdup; ///< Strdup.
|
||||
strerror_fn strerror; ///< Strerror.
|
||||
client_errno_fn client_errno; ///< Client errno.
|
||||
simple_poll_get_fn simple_poll_get; ///< Simple poll get.
|
||||
simple_poll_loop_fn simple_poll_loop; ///< Simple poll loop.
|
||||
simple_poll_quit_fn simple_poll_quit; ///< Simple poll quit.
|
||||
simple_poll_new_fn simple_poll_new; ///< Simple poll new.
|
||||
simple_poll_free_fn simple_poll_free; ///< Simple poll free.
|
||||
|
||||
/**
|
||||
* @brief Load common Avahi client and entry-group function pointers.
|
||||
*
|
||||
* @return 0 when common Avahi functions are available; nonzero otherwise.
|
||||
*/
|
||||
int init_common() {
|
||||
static void *handle {nullptr};
|
||||
static bool funcs_loaded = false;
|
||||
@@ -238,6 +324,11 @@ namespace avahi {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Load Avahi threaded-poll and client function pointers.
|
||||
*
|
||||
* @return 0 when all client functions are available; nonzero otherwise.
|
||||
*/
|
||||
int init_client() {
|
||||
if (init_common()) {
|
||||
return -1;
|
||||
@@ -280,25 +371,50 @@ namespace avahi {
|
||||
|
||||
namespace platf::publish {
|
||||
|
||||
/**
|
||||
* @brief Release memory allocated by Avahi.
|
||||
*
|
||||
* @param p Avahi-allocated pointer to release.
|
||||
*/
|
||||
template<class T>
|
||||
void free(T *p) {
|
||||
avahi::free(p);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Owning pointer for Avahi allocations released with `avahi_free`.
|
||||
*/
|
||||
template<class T>
|
||||
using ptr_t = util::safe_ptr<T, free<T>>;
|
||||
/**
|
||||
* @brief Owning pointer for an Avahi client.
|
||||
*/
|
||||
using client_t = util::dyn_safe_ptr<avahi::Client, &avahi::client_free>;
|
||||
/**
|
||||
* @brief Owning pointer for an Avahi simple poll loop.
|
||||
*/
|
||||
using poll_t = util::dyn_safe_ptr<avahi::SimplePoll, &avahi::simple_poll_free>;
|
||||
|
||||
avahi::EntryGroup *group = nullptr;
|
||||
avahi::EntryGroup *group = nullptr; ///< Active Avahi entry group that owns the published service.
|
||||
|
||||
poll_t poll;
|
||||
client_t client;
|
||||
poll_t poll; ///< Avahi poll loop used while the service is published.
|
||||
client_t client; ///< Avahi client used to register the Sunshine service.
|
||||
|
||||
ptr_t<char> name;
|
||||
ptr_t<char> name; ///< Current service name, updated when Avahi reports a collision.
|
||||
|
||||
/**
|
||||
* @brief Create or update the Avahi service entry group.
|
||||
*
|
||||
* @param c Avahi client used to allocate and commit the service entry group.
|
||||
*/
|
||||
void create_services(avahi::Client *c);
|
||||
|
||||
/**
|
||||
* @brief React to Avahi entry-group state changes.
|
||||
*
|
||||
* @param g Entry group that emitted the state change.
|
||||
* @param state Avahi entry-group state reported by the callback.
|
||||
*/
|
||||
void entry_group_callback(avahi::EntryGroup *g, avahi::EntryGroupState state, void *) {
|
||||
group = g;
|
||||
|
||||
@@ -322,6 +438,9 @@ namespace platf::publish {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Publish Sunshine's mDNS service through Avahi.
|
||||
*/
|
||||
void create_services(avahi::Client *c) {
|
||||
int ret;
|
||||
|
||||
@@ -380,6 +499,12 @@ namespace platf::publish {
|
||||
fg.disable();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief React to Avahi client state changes and register services when ready.
|
||||
*
|
||||
* @param c Avahi client that emitted the state change.
|
||||
* @param state Avahi client state reported by the callback.
|
||||
*/
|
||||
void client_callback(avahi::Client *c, avahi::ClientState state, void *) {
|
||||
switch (state) {
|
||||
case avahi::CLIENT_S_RUNNING:
|
||||
@@ -399,14 +524,25 @@ namespace platf::publish {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief RAII helper that runs shutdown cleanup when destroyed.
|
||||
*/
|
||||
class deinit_t: public ::platf::deinit_t {
|
||||
public:
|
||||
std::thread poll_thread;
|
||||
std::thread poll_thread; ///< Poll thread.
|
||||
|
||||
/**
|
||||
* @brief Store the Avahi polling thread for shutdown on destruction.
|
||||
*
|
||||
* @param poll_thread Poll thread.
|
||||
*/
|
||||
deinit_t(std::thread poll_thread):
|
||||
poll_thread {std::move(poll_thread)} {
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Destroy the Avahi publisher deinitializer.
|
||||
*/
|
||||
~deinit_t() override {
|
||||
if (avahi::simple_poll_quit && poll) {
|
||||
avahi::simple_poll_quit(poll.get());
|
||||
|
||||
+135
-28
@@ -14,7 +14,14 @@ extern "C" {
|
||||
#include <va/va.h>
|
||||
#include <va/va_drm.h>
|
||||
#if !VA_CHECK_VERSION(1, 9, 0)
|
||||
// vaSyncBuffer stub allows Sunshine built against libva <2.9.0 to link against ffmpeg on libva 2.9.0 or later
|
||||
/**
|
||||
* @brief Stub vaSyncBuffer when building against libva before 2.9.0.
|
||||
*
|
||||
* @param dpy VA display.
|
||||
* @param buf_id VA buffer ID.
|
||||
* @param timeout_ns Sync timeout in nanoseconds.
|
||||
* @return VA status code.
|
||||
*/
|
||||
VAStatus
|
||||
vaSyncBuffer(
|
||||
VADisplay dpy,
|
||||
@@ -25,7 +32,15 @@ extern "C" {
|
||||
}
|
||||
#endif
|
||||
#if !VA_CHECK_VERSION(1, 21, 0)
|
||||
// vaMapBuffer2 stub allows Sunshine built against libva <2.21.0 to link against ffmpeg on libva 2.21.0 or later
|
||||
/**
|
||||
* @brief Stub vaMapBuffer2 when building against libva before 2.21.0.
|
||||
*
|
||||
* @param dpy VA display.
|
||||
* @param buf_id VA buffer ID.
|
||||
* @param pbuf Output mapped buffer pointer.
|
||||
* @param flags Mapping flags.
|
||||
* @return VA status code.
|
||||
*/
|
||||
VAStatus
|
||||
vaMapBuffer2(
|
||||
VADisplay dpy,
|
||||
@@ -52,24 +67,39 @@ using namespace std::literals;
|
||||
extern "C" struct AVBufferRef;
|
||||
|
||||
namespace va {
|
||||
constexpr auto SURFACE_ATTRIB_MEM_TYPE_DRM_PRIME_2 = 0x40000000;
|
||||
constexpr auto EXPORT_SURFACE_WRITE_ONLY = 0x0002;
|
||||
constexpr auto EXPORT_SURFACE_SEPARATE_LAYERS = 0x0004;
|
||||
constexpr auto SURFACE_ATTRIB_MEM_TYPE_DRM_PRIME_2 = 0x40000000; ///< Protocol or platform constant for surface attrib mem type drm prime 2.
|
||||
constexpr auto EXPORT_SURFACE_WRITE_ONLY = 0x0002; ///< GameStream port offset for export surface write only.
|
||||
constexpr auto EXPORT_SURFACE_SEPARATE_LAYERS = 0x0004; ///< GameStream port offset for export surface separate layers.
|
||||
|
||||
/**
|
||||
* @brief Native VA display handle.
|
||||
*/
|
||||
using VADisplay = void *;
|
||||
/**
|
||||
* @brief Status code returned by VAAPI functions.
|
||||
*/
|
||||
using VAStatus = int;
|
||||
/**
|
||||
* @brief Generic numeric VAAPI object identifier.
|
||||
*/
|
||||
using VAGenericID = unsigned int;
|
||||
/**
|
||||
* @brief VAAPI surface identifier.
|
||||
*/
|
||||
using VASurfaceID = VAGenericID;
|
||||
|
||||
/**
|
||||
* @brief DRM PRIME descriptor imported from a VAAPI surface.
|
||||
*/
|
||||
struct DRMPRIMESurfaceDescriptor {
|
||||
// VA Pixel format fourcc of the whole surface (VA_FOURCC_*).
|
||||
uint32_t fourcc;
|
||||
uint32_t fourcc; ///< VA fourcc pixel format for the imported surface.
|
||||
|
||||
uint32_t width;
|
||||
uint32_t height;
|
||||
uint32_t width; ///< Surface width in pixels.
|
||||
uint32_t height; ///< Surface height in pixels.
|
||||
|
||||
// Number of distinct DRM objects making up the surface.
|
||||
uint32_t num_objects;
|
||||
uint32_t num_objects; ///< Num objects.
|
||||
|
||||
struct {
|
||||
// DRM PRIME file descriptor for this object.
|
||||
@@ -80,10 +110,10 @@ namespace va {
|
||||
uint32_t size;
|
||||
// Format modifier applied to this object, not sure what that means
|
||||
uint64_t drm_format_modifier;
|
||||
} objects[4];
|
||||
} objects[4]; ///< DRM PRIME backing objects referenced by the descriptor..
|
||||
|
||||
// Number of layers making up the surface.
|
||||
uint32_t num_layers;
|
||||
uint32_t num_layers; ///< Num layers.
|
||||
|
||||
struct {
|
||||
// DRM format fourcc of this layer (DRM_FOURCC_*).
|
||||
@@ -100,15 +130,36 @@ namespace va {
|
||||
|
||||
// Pitch of each plane.
|
||||
uint32_t pitch[4];
|
||||
} layers[4];
|
||||
} layers[4]; ///< DRM PRIME layer descriptions for the frame..
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief VA display handle released with `vaTerminate`.
|
||||
*/
|
||||
using display_t = util::safe_ptr_v2<void, VAStatus, vaTerminate>;
|
||||
|
||||
/**
|
||||
* @brief Create an FFmpeg VA-API hardware device context from a Sunshine encode device.
|
||||
*
|
||||
* @param encode_device Encode device.
|
||||
* @param hw_device_buf Output FFmpeg hardware device buffer.
|
||||
* @return 0 when the buffer is initialized; negative FFmpeg error code on failure.
|
||||
*/
|
||||
int vaapi_init_avcodec_hardware_input_buffer(platf::avcodec_encode_device_t *encode_device, AVBufferRef **hw_device_buf);
|
||||
|
||||
/**
|
||||
* @brief VAAPI encode device that imports captured frames into VA surfaces.
|
||||
*/
|
||||
class va_t: public platf::avcodec_encode_device_t {
|
||||
public:
|
||||
/**
|
||||
* @brief Initialize VAAPI display, EGL, and conversion resources.
|
||||
*
|
||||
* @param in_width In width.
|
||||
* @param in_height In height.
|
||||
* @param render_device Render device.
|
||||
* @return 0 on success; nonzero or negative platform status on failure.
|
||||
*/
|
||||
int init(int in_width, int in_height, file_t &&render_device) {
|
||||
file = std::move(render_device);
|
||||
|
||||
@@ -229,6 +280,12 @@ namespace va {
|
||||
return VAProfileNone;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Initialize codec options.
|
||||
*
|
||||
* @param ctx Native context object used by the operation or callback.
|
||||
* @param options Request options or socket options to apply.
|
||||
*/
|
||||
void init_codec_options(AVCodecContext *ctx, AVDictionary **options) override {
|
||||
auto va_profile = get_va_profile(ctx);
|
||||
if (va_profile == VAProfileNone || !is_va_profile_supported(va_profile)) {
|
||||
@@ -303,6 +360,13 @@ namespace va {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Attach frame resources used by the next conversion or encode operation.
|
||||
*
|
||||
* @param frame Video or graphics frame being processed.
|
||||
* @param hw_frames_ctx_buf Hardware frames context buffer.
|
||||
* @return Status from updating frame.
|
||||
*/
|
||||
int set_frame(AVFrame *frame, AVBufferRef *hw_frames_ctx_buf) override {
|
||||
this->hwframe.reset(frame);
|
||||
this->frame = frame;
|
||||
@@ -380,30 +444,42 @@ namespace va {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Apply the configured colorspace metadata to the active frame.
|
||||
*/
|
||||
void apply_colorspace() override {
|
||||
sws.apply_colorspace(colorspace, false);
|
||||
}
|
||||
|
||||
va::display_t::pointer va_display;
|
||||
file_t file;
|
||||
va::display_t::pointer va_display; ///< VA display used to allocate and destroy surfaces.
|
||||
file_t file; ///< DRM render-node file descriptor used by VAAPI and EGL.
|
||||
|
||||
gbm::gbm_t gbm;
|
||||
egl::display_t display;
|
||||
egl::ctx_t ctx;
|
||||
gbm::gbm_t gbm; ///< GBM device used for buffer allocation.
|
||||
egl::display_t display; ///< EGL display created from the DRM render node.
|
||||
egl::ctx_t ctx; ///< EGL context used for VA-API frame conversion.
|
||||
|
||||
// This must be destroyed before display_t to ensure the GPU
|
||||
// driver is still loaded when vaDestroySurfaces() is called.
|
||||
frame_t hwframe;
|
||||
frame_t hwframe; ///< FFmpeg hardware frame backed by a VAAPI surface.
|
||||
|
||||
egl::sws_t sws;
|
||||
egl::nv12_t nv12;
|
||||
egl::sws_t sws; ///< EGL/OpenGL conversion pipeline for VA-API frames.
|
||||
egl::nv12_t nv12; ///< EGL/OpenGL resources used for NV12 output frames.
|
||||
|
||||
int width;
|
||||
int height;
|
||||
int width; ///< Frame or display width in pixels.
|
||||
int height; ///< Frame or display height in pixels.
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief VAAPI encode path that copies converted frames through system memory.
|
||||
*/
|
||||
class va_ram_t: public va_t {
|
||||
public:
|
||||
/**
|
||||
* @brief Convert a captured VAAPI frame into system-memory encoder input.
|
||||
*
|
||||
* @param img Image or frame object to read from or populate.
|
||||
* @return Conversion status.
|
||||
*/
|
||||
int convert(platf::img_t &img) override {
|
||||
sws.load_ram(img);
|
||||
|
||||
@@ -412,8 +488,17 @@ namespace va {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief VAAPI encode path that keeps converted frames in GPU memory.
|
||||
*/
|
||||
class va_vram_t: public va_t {
|
||||
public:
|
||||
/**
|
||||
* @brief Convert a captured VAAPI frame into GPU encoder input.
|
||||
*
|
||||
* @param img Image or frame object to read from or populate.
|
||||
* @return Conversion status.
|
||||
*/
|
||||
int convert(platf::img_t &img) override {
|
||||
auto &descriptor = (egl::img_descriptor_t &) img;
|
||||
|
||||
@@ -440,6 +525,16 @@ namespace va {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Initialize VAAPI GPU-frame conversion for the selected display.
|
||||
*
|
||||
* @param in_width In width.
|
||||
* @param in_height In height.
|
||||
* @param render_device Render device.
|
||||
* @param offset_x Offset x.
|
||||
* @param offset_y Offset y.
|
||||
* @return 0 on success; nonzero or negative platform status on failure.
|
||||
*/
|
||||
int init(int in_width, int in_height, file_t &&render_device, int offset_x, int offset_y) {
|
||||
if (va_t::init(in_width, in_height, std::move(render_device))) {
|
||||
return -1;
|
||||
@@ -453,11 +548,11 @@ namespace va {
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::uint64_t sequence;
|
||||
egl::rgb_t rgb;
|
||||
std::uint64_t sequence; ///< Monotonic sequence used to recreate imported EGL resources.
|
||||
egl::rgb_t rgb; ///< Imported RGB image used before VAAPI conversion.
|
||||
|
||||
int offset_x;
|
||||
int offset_y;
|
||||
int offset_x; ///< Horizontal offset in physical pixels.
|
||||
int offset_y; ///< Vertical offset in physical pixels.
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -470,9 +565,9 @@ namespace va {
|
||||
union {
|
||||
void *xdisplay;
|
||||
int fd;
|
||||
} drm;
|
||||
} drm; ///< Native display or DRM fd passed to FFmpeg's VA-API context.
|
||||
|
||||
int drm_fd;
|
||||
int drm_fd; ///< DRM fd.
|
||||
} VAAPIDevicePriv;
|
||||
|
||||
/**
|
||||
@@ -508,6 +603,9 @@ namespace va {
|
||||
av_freep(&priv);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Initialize FFmpeg's VA-API hardware device buffer.
|
||||
*/
|
||||
int vaapi_init_avcodec_hardware_input_buffer(platf::avcodec_encode_device_t *base, AVBufferRef **hw_device_buf) {
|
||||
auto va = (va::va_t *) base;
|
||||
auto fd = dup(va->file.el);
|
||||
@@ -583,6 +681,9 @@ namespace va {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Validate that the configured VAAPI device can be used.
|
||||
*/
|
||||
bool validate(int fd) {
|
||||
va::display_t display {vaGetDisplayDRM(fd)};
|
||||
if (!display) {
|
||||
@@ -619,6 +720,9 @@ namespace va {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Create AVCodec encode device.
|
||||
*/
|
||||
std::unique_ptr<platf::avcodec_encode_device_t> make_avcodec_encode_device(int width, int height, file_t &&card, int offset_x, int offset_y, bool vram) {
|
||||
if (vram) {
|
||||
auto egl = std::make_unique<va::va_vram_t>();
|
||||
@@ -639,6 +743,9 @@ namespace va {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Create AVCodec encode device.
|
||||
*/
|
||||
std::unique_ptr<platf::avcodec_encode_device_t> make_avcodec_encode_device(int width, int height, int offset_x, int offset_y, bool vram) {
|
||||
auto render_device = platf::resolve_render_device();
|
||||
|
||||
|
||||
@@ -19,11 +19,43 @@ namespace va {
|
||||
* offset_x --> Horizontal offset of the image in the texture
|
||||
* offset_y --> Vertical offset of the image in the texture
|
||||
* file_t card --> The file descriptor of the render device used for encoding
|
||||
*
|
||||
* @param width Frame or display width in pixels.
|
||||
* @param height Frame or display height in pixels.
|
||||
* @param vram Whether the image should use GPU memory instead of system memory.
|
||||
* @return Constructed AVCodec encode device object.
|
||||
*/
|
||||
std::unique_ptr<platf::avcodec_encode_device_t> make_avcodec_encode_device(int width, int height, bool vram);
|
||||
/**
|
||||
* @brief Create AVCodec encode device.
|
||||
*
|
||||
* @param width Frame or display width in pixels.
|
||||
* @param height Frame or display height in pixels.
|
||||
* @param offset_x Offset x.
|
||||
* @param offset_y Offset y.
|
||||
* @param vram Whether the image should use GPU memory instead of system memory.
|
||||
* @return Constructed AVCodec encode device object.
|
||||
*/
|
||||
std::unique_ptr<platf::avcodec_encode_device_t> make_avcodec_encode_device(int width, int height, int offset_x, int offset_y, bool vram);
|
||||
/**
|
||||
* @brief Create AVCodec encode device.
|
||||
*
|
||||
* @param width Frame or display width in pixels.
|
||||
* @param height Frame or display height in pixels.
|
||||
* @param card Video device path or render node used for VAAPI.
|
||||
* @param offset_x Offset x.
|
||||
* @param offset_y Offset y.
|
||||
* @param vram Whether the image should use GPU memory instead of system memory.
|
||||
* @return Constructed AVCodec encode device object.
|
||||
*/
|
||||
std::unique_ptr<platf::avcodec_encode_device_t> make_avcodec_encode_device(int width, int height, file_t &&card, int offset_x, int offset_y, bool vram);
|
||||
|
||||
// Ensure the render device pointed to by fd is capable of encoding h264 with the hevc_mode configured
|
||||
/**
|
||||
* @brief Validate that the configured VAAPI device can be used.
|
||||
*
|
||||
* @param fd Native file descriptor to wrap or inspect.
|
||||
* @return True when the VAAPI device is usable for capture or encode.
|
||||
*/
|
||||
bool validate(int fd);
|
||||
} // namespace va
|
||||
|
||||
@@ -106,23 +106,30 @@ namespace vk {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Vulkan shader constants used by the conversion pass.
|
||||
*/
|
||||
struct PushConstants {
|
||||
std::array<float, 4> color_vec_y;
|
||||
std::array<float, 4> color_vec_u;
|
||||
std::array<float, 4> color_vec_v;
|
||||
std::array<float, 2> range_y;
|
||||
std::array<float, 2> range_uv;
|
||||
std::array<int32_t, 2> src_offset;
|
||||
std::array<int32_t, 2> src_size;
|
||||
std::array<int32_t, 2> dst_offset;
|
||||
std::array<int32_t, 2> dst_size;
|
||||
std::array<int32_t, 2> dst_full_size;
|
||||
std::array<int32_t, 2> cursor_pos;
|
||||
std::array<int32_t, 2> cursor_size;
|
||||
int32_t y_invert;
|
||||
std::array<float, 4> color_vec_y; ///< Color vec y.
|
||||
std::array<float, 4> color_vec_u; ///< Color vec u.
|
||||
std::array<float, 4> color_vec_v; ///< Color vec v.
|
||||
std::array<float, 2> range_y; ///< Range y.
|
||||
std::array<float, 2> range_uv; ///< Range uv.
|
||||
std::array<int32_t, 2> src_offset; ///< Src offset.
|
||||
std::array<int32_t, 2> src_size; ///< Src size.
|
||||
std::array<int32_t, 2> dst_offset; ///< Dst offset.
|
||||
std::array<int32_t, 2> dst_size; ///< Dst size.
|
||||
std::array<int32_t, 2> dst_full_size; ///< Dst full size.
|
||||
std::array<int32_t, 2> cursor_pos; ///< Cursor pos.
|
||||
std::array<int32_t, 2> cursor_size; ///< Cursor size.
|
||||
int32_t y_invert; ///< Y invert.
|
||||
};
|
||||
|
||||
// Helper to check VkResult
|
||||
/**
|
||||
* @def VK_CHECK(expr)
|
||||
* @brief Macro for VK CHECK.
|
||||
*/
|
||||
#define VK_CHECK(expr) \
|
||||
do { \
|
||||
VkResult _r = (expr); \
|
||||
@@ -131,6 +138,10 @@ namespace vk {
|
||||
return -1; \
|
||||
} \
|
||||
} while (0)
|
||||
/**
|
||||
* @def VK_CHECK_BOOL(expr)
|
||||
* @brief Macro for VK CHECK BOOL.
|
||||
*/
|
||||
#define VK_CHECK_BOOL(expr) \
|
||||
do { \
|
||||
VkResult _r = (expr); \
|
||||
@@ -140,12 +151,24 @@ namespace vk {
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
/**
|
||||
* @brief Vulkan encode device that keeps converted frames in GPU memory.
|
||||
*/
|
||||
class vk_vram_t: public platf::avcodec_encode_device_t {
|
||||
public:
|
||||
~vk_vram_t() override {
|
||||
cleanup_pipeline();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Initialize Vulkan encode device and conversion resources.
|
||||
*
|
||||
* @param in_width In width.
|
||||
* @param in_height In height.
|
||||
* @param in_offset_x In offset x.
|
||||
* @param in_offset_y In offset y.
|
||||
* @return 0 on success; nonzero or negative platform status on failure.
|
||||
*/
|
||||
int init(int in_width, int in_height, int in_offset_x = 0, int in_offset_y = 0) {
|
||||
width = in_width;
|
||||
height = in_height;
|
||||
@@ -155,6 +178,12 @@ namespace vk {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Initialize codec options.
|
||||
*
|
||||
* @param ctx Native context object used by the operation or callback.
|
||||
* @param options Request options or socket options to apply.
|
||||
*/
|
||||
void init_codec_options(AVCodecContext *ctx, AVDictionary **options) override {
|
||||
// When VBR mode is selected (rc_mode=4), don't pin rc_min_rate to the target bitrate.
|
||||
// Having rc_min_rate == rc_max_rate == bit_rate in VBR mode prevents the encoder from
|
||||
@@ -165,6 +194,13 @@ namespace vk {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Attach frame resources used by the next conversion or encode operation.
|
||||
*
|
||||
* @param new_frame Frame to attach.
|
||||
* @param hw_frames_ctx_buf Hardware frames context buffer.
|
||||
* @return Status from updating frame.
|
||||
*/
|
||||
int set_frame(AVFrame *new_frame, AVBufferRef *hw_frames_ctx_buf) override {
|
||||
this->hwframe.reset(new_frame);
|
||||
this->frame = new_frame;
|
||||
@@ -212,6 +248,9 @@ namespace vk {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Apply the configured colorspace metadata to the active frame.
|
||||
*/
|
||||
void apply_colorspace() override {
|
||||
auto *colors = video::color_vectors_from_colorspace(colorspace, true);
|
||||
if (colors) {
|
||||
@@ -223,6 +262,11 @@ namespace vk {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Configure FFmpeg Vulkan hardware frames for video encode input.
|
||||
*
|
||||
* @param frames FFmpeg hardware frames context to initialize.
|
||||
*/
|
||||
void init_hwframes(AVHWFramesContext *frames) override {
|
||||
frames->initial_pool_size = 4;
|
||||
auto *vk_frames = (AVVulkanFramesContext *) frames->hwctx;
|
||||
@@ -233,6 +277,12 @@ namespace vk {
|
||||
VK_IMAGE_USAGE_VIDEO_ENCODE_SRC_BIT_KHR);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Convert a captured frame into a Vulkan hardware frame.
|
||||
*
|
||||
* @param img Image or frame object to read from or populate.
|
||||
* @return Conversion status.
|
||||
*/
|
||||
int convert(platf::img_t &img) override {
|
||||
auto &descriptor = (egl::img_descriptor_t &) img;
|
||||
|
||||
|
||||
@@ -20,16 +20,28 @@ namespace vk {
|
||||
|
||||
/**
|
||||
* @brief Create a Vulkan encode device for RAM capture.
|
||||
*
|
||||
* @param width Frame or display width in pixels.
|
||||
* @param height Frame or display height in pixels.
|
||||
* @return Constructed AVCodec encode device ram object.
|
||||
*/
|
||||
std::unique_ptr<platf::avcodec_encode_device_t> make_avcodec_encode_device_ram(int width, int height);
|
||||
|
||||
/**
|
||||
* @brief Create a Vulkan encode device for VRAM capture.
|
||||
*
|
||||
* @param width Frame or display width in pixels.
|
||||
* @param height Frame or display height in pixels.
|
||||
* @param offset_x Offset x.
|
||||
* @param offset_y Offset y.
|
||||
* @return Constructed AVCodec encode device VRAM object.
|
||||
*/
|
||||
std::unique_ptr<platf::avcodec_encode_device_t> make_avcodec_encode_device_vram(int width, int height, int offset_x, int offset_y);
|
||||
|
||||
/**
|
||||
* @brief Check if FFmpeg Vulkan Video encoding is available.
|
||||
*
|
||||
* @return True when FFmpeg Vulkan Video encoding is available.
|
||||
*/
|
||||
bool validate();
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
#include "src/utility.h"
|
||||
#include "wayland.h"
|
||||
|
||||
extern const wl_interface wl_output_interface;
|
||||
extern const wl_interface wl_output_interface; ///< Wayland output interface.
|
||||
|
||||
using namespace std::literals;
|
||||
|
||||
@@ -40,6 +40,10 @@ namespace wl {
|
||||
return ((*reinterpret_cast<T *>(data)).*m)(params...);
|
||||
}
|
||||
|
||||
/**
|
||||
* @def CLASS_CALL(c, m)
|
||||
* @brief Macro for CLASS CALL.
|
||||
*/
|
||||
#define CLASS_CALL(c, m) classCall<c, decltype(&c::m), &c::m>
|
||||
|
||||
// Define buffer params listener
|
||||
@@ -546,6 +550,9 @@ namespace wl {
|
||||
std::fill_n(sd.fds, 4, -1);
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Refresh the monitor list reported by the display server.
|
||||
*/
|
||||
std::vector<std::unique_ptr<monitor_t>> monitors(const char *display_name) {
|
||||
display_t display;
|
||||
|
||||
@@ -578,6 +585,9 @@ namespace wl {
|
||||
return display.init() == 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Initialize Wayland registry interfaces required for capture.
|
||||
*/
|
||||
int init() {
|
||||
static bool validated = validate();
|
||||
|
||||
|
||||
+242
-23
@@ -26,19 +26,34 @@
|
||||
#ifdef SUNSHINE_BUILD_WAYLAND
|
||||
|
||||
namespace wl {
|
||||
/**
|
||||
* @brief Owning pointer for a Wayland display connection.
|
||||
*/
|
||||
using display_internal_t = util::safe_ptr<wl_display, wl_display_disconnect>;
|
||||
|
||||
/**
|
||||
* @brief Captured Wayland frame metadata and DMA-BUF surface state.
|
||||
*/
|
||||
class frame_t {
|
||||
public:
|
||||
frame_t();
|
||||
/**
|
||||
* @brief Release file descriptors and native buffers associated with this frame.
|
||||
*/
|
||||
void destroy();
|
||||
|
||||
egl::surface_descriptor_t sd;
|
||||
std::optional<std::chrono::steady_clock::time_point> frame_timestamp;
|
||||
egl::surface_descriptor_t sd; ///< DMA-BUF surface descriptor received from the compositor.
|
||||
std::optional<std::chrono::steady_clock::time_point> frame_timestamp; ///< Capture timestamp associated with the frame.
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Listener state for Wayland screencopy frames backed by DMA-BUFs.
|
||||
*/
|
||||
class dmabuf_t {
|
||||
public:
|
||||
/**
|
||||
* @brief Capture state for the active screencopy request.
|
||||
*/
|
||||
enum status_e {
|
||||
WAITING, ///< Waiting for a frame
|
||||
READY, ///< Frame is ready
|
||||
@@ -53,25 +68,102 @@ namespace wl {
|
||||
dmabuf_t &operator=(const dmabuf_t &) = delete;
|
||||
dmabuf_t &operator=(dmabuf_t &&) = delete;
|
||||
|
||||
/**
|
||||
* @brief Start a screencopy request and attach the DMA-BUF listener callbacks.
|
||||
*
|
||||
* @param screencopy_manager Compositor screencopy manager used to request frames.
|
||||
* @param dmabuf_interface Compositor DMA-BUF interface used to allocate buffers.
|
||||
* @param supported_modifiers DMA-BUF format modifiers supported by the compositor.
|
||||
* @param output Wayland output to capture.
|
||||
* @param blend_cursor Whether the compositor should include the cursor in the frame.
|
||||
*/
|
||||
void listen(zwlr_screencopy_manager_v1 *screencopy_manager, zwp_linux_dmabuf_v1 *dmabuf_interface, const std::map<std::uint32_t, std::vector<std::uint64_t>> *supported_modifiers, wl_output *output, bool blend_cursor = false);
|
||||
/**
|
||||
* @brief Store the Wayland buffer created for a DMA-BUF parameter request.
|
||||
*
|
||||
* @param data Pointer to the `dmabuf_t` instance associated with the callback.
|
||||
* @param params DMA-BUF parameter object that completed buffer creation.
|
||||
* @param wl_buffer Wayland buffer created from the DMA-BUF planes.
|
||||
*/
|
||||
static void buffer_params_created(void *data, struct zwp_linux_buffer_params_v1 *params, struct wl_buffer *wl_buffer);
|
||||
/**
|
||||
* @brief Mark DMA-BUF buffer creation as failed.
|
||||
*
|
||||
* @param data Pointer to the `dmabuf_t` instance associated with the callback.
|
||||
* @param params DMA-BUF parameter object that failed buffer creation.
|
||||
*/
|
||||
static void buffer_params_failed(void *data, struct zwp_linux_buffer_params_v1 *params);
|
||||
/**
|
||||
* @brief Record DMA-BUF buffer parameters from the compositor.
|
||||
*
|
||||
* @param frame Screencopy frame that advertised shared-memory fallback data.
|
||||
* @param format DRM pixel format for the shared-memory buffer.
|
||||
* @param width Frame width in pixels.
|
||||
* @param height Frame height in pixels.
|
||||
* @param stride Number of bytes per row in the buffer.
|
||||
*/
|
||||
void buffer(zwlr_screencopy_frame_v1 *frame, std::uint32_t format, std::uint32_t width, std::uint32_t height, std::uint32_t stride);
|
||||
/**
|
||||
* @brief Record Linux DMA-BUF parameters advertised for the frame.
|
||||
*
|
||||
* @param frame Screencopy frame that advertised DMA-BUF data.
|
||||
* @param format DRM pixel format for the DMA-BUF.
|
||||
* @param width Frame width in pixels.
|
||||
* @param height Frame height in pixels.
|
||||
*/
|
||||
void linux_dmabuf(zwlr_screencopy_frame_v1 *frame, std::uint32_t format, std::uint32_t width, std::uint32_t height);
|
||||
/**
|
||||
* @brief Allocate or import the buffer once the compositor finished describing it.
|
||||
*
|
||||
* @param frame Screencopy frame whose buffer description is complete.
|
||||
*/
|
||||
void buffer_done(zwlr_screencopy_frame_v1 *frame);
|
||||
/**
|
||||
* @brief Record frame flags reported by the compositor.
|
||||
*
|
||||
* @param frame Screencopy frame that reported flags.
|
||||
* @param flags Wayland screencopy flags such as vertical inversion.
|
||||
*/
|
||||
void flags(zwlr_screencopy_frame_v1 *frame, std::uint32_t flags);
|
||||
/**
|
||||
* @brief Record damaged frame bounds reported by the compositor.
|
||||
*
|
||||
* @param frame Screencopy frame that reported damage.
|
||||
* @param x Left edge of the damaged rectangle.
|
||||
* @param y Top edge of the damaged rectangle.
|
||||
* @param width Damaged rectangle width in pixels.
|
||||
* @param height Damaged rectangle height in pixels.
|
||||
*/
|
||||
void damage(zwlr_screencopy_frame_v1 *frame, std::uint32_t x, std::uint32_t y, std::uint32_t width, std::uint32_t height);
|
||||
/**
|
||||
* @brief Mark the current screencopy frame as ready and store its timestamp.
|
||||
*
|
||||
* @param frame Screencopy frame that completed.
|
||||
* @param tv_sec_hi High 32 bits of the compositor-provided seconds value.
|
||||
* @param tv_sec_lo Low 32 bits of the compositor-provided seconds value.
|
||||
* @param tv_nsec Nanosecond component of the compositor-provided timestamp.
|
||||
*/
|
||||
void ready(zwlr_screencopy_frame_v1 *frame, std::uint32_t tv_sec_hi, std::uint32_t tv_sec_lo, std::uint32_t tv_nsec);
|
||||
/**
|
||||
* @brief Mark the frame capture as failed.
|
||||
*
|
||||
* @param frame Screencopy frame that failed.
|
||||
*/
|
||||
void failed(zwlr_screencopy_frame_v1 *frame);
|
||||
|
||||
/**
|
||||
* @brief Select the inactive frame slot for the next screencopy request.
|
||||
*
|
||||
* @return Inactive frame buffer that can receive the next capture.
|
||||
*/
|
||||
frame_t *get_next_frame() {
|
||||
return current_frame == &frames[0] ? &frames[1] : &frames[0];
|
||||
}
|
||||
|
||||
status_e status;
|
||||
std::array<frame_t, 2> frames;
|
||||
frame_t *current_frame;
|
||||
zwlr_screencopy_frame_v1_listener listener;
|
||||
status_e status; ///< Current state of the active screencopy request.
|
||||
std::array<frame_t, 2> frames; ///< Double-buffered frame descriptors.
|
||||
frame_t *current_frame; ///< Frame descriptor currently being filled by the compositor.
|
||||
zwlr_screencopy_frame_v1_listener listener; ///< Callback table registered on screencopy frames.
|
||||
|
||||
private:
|
||||
bool init_gbm();
|
||||
@@ -102,8 +194,16 @@ namespace wl {
|
||||
bool y_invert {false};
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Wayland output metadata used to match a configured display name.
|
||||
*/
|
||||
class monitor_t {
|
||||
public:
|
||||
/**
|
||||
* @brief Track a Wayland output and its advertised modes.
|
||||
*
|
||||
* @param output Wayland output object being described.
|
||||
*/
|
||||
explicit monitor_t(wl_output *output);
|
||||
|
||||
monitor_t(monitor_t &&) = delete;
|
||||
@@ -111,30 +211,96 @@ namespace wl {
|
||||
monitor_t &operator=(const monitor_t &) = delete;
|
||||
monitor_t &operator=(monitor_t &&) = delete;
|
||||
|
||||
/**
|
||||
* @brief Attach xdg-output and wl-output listeners for this monitor.
|
||||
*
|
||||
* @param output_manager xdg-output manager used to query logical monitor metadata.
|
||||
*/
|
||||
void listen(zxdg_output_manager_v1 *output_manager);
|
||||
/**
|
||||
* @brief Store the xdg-output logical monitor name.
|
||||
*
|
||||
* @param name Human-readable name to assign.
|
||||
*/
|
||||
void xdg_name(zxdg_output_v1 *, const char *name);
|
||||
/**
|
||||
* @brief Store the xdg-output human-readable monitor description.
|
||||
*
|
||||
* @param description Human-readable description used in log output.
|
||||
*/
|
||||
void xdg_description(zxdg_output_v1 *, const char *description);
|
||||
/**
|
||||
* @brief Store the logical monitor position reported by xdg-output.
|
||||
*
|
||||
* @param x Horizontal coordinate reported by Wayland.
|
||||
* @param y Vertical coordinate reported by Wayland.
|
||||
*/
|
||||
void xdg_position(zxdg_output_v1 *, std::int32_t x, std::int32_t y);
|
||||
/**
|
||||
* @brief Store the logical monitor size reported by xdg-output.
|
||||
*
|
||||
* @param width Frame or display width in pixels.
|
||||
* @param height Frame or display height in pixels.
|
||||
*/
|
||||
void xdg_size(zxdg_output_v1 *, std::int32_t width, std::int32_t height);
|
||||
|
||||
/**
|
||||
* @brief Acknowledge completion of an xdg-output metadata update.
|
||||
*/
|
||||
void xdg_done(zxdg_output_v1 *) {}
|
||||
|
||||
/**
|
||||
* @brief Receive physical wl-output geometry metadata.
|
||||
*
|
||||
* @param wl_output Wayland output.
|
||||
* @param x Horizontal coordinate reported by Wayland.
|
||||
* @param y Vertical coordinate reported by Wayland.
|
||||
* @param physical_width Physical width in millimeters.
|
||||
* @param physical_height Physical height in millimeters.
|
||||
* @param subpixel Wayland subpixel layout enum.
|
||||
* @param make Monitor manufacturer string reported by Wayland.
|
||||
* @param model Monitor model string reported by Wayland.
|
||||
* @param transform Wayland output transform value.
|
||||
*/
|
||||
void wl_geometry(wl_output *wl_output, std::int32_t x, std::int32_t y, std::int32_t physical_width, std::int32_t physical_height, std::int32_t subpixel, const char *make, const char *model, std::int32_t transform) {}
|
||||
|
||||
/**
|
||||
* @brief Store the active pixel mode reported by wl-output.
|
||||
*
|
||||
* @param wl_output Wayland output.
|
||||
* @param flags Bit flags that modify the requested operation.
|
||||
* @param width Frame or display width in pixels.
|
||||
* @param height Frame or display height in pixels.
|
||||
* @param refresh Output refresh rate in mHz.
|
||||
*/
|
||||
void wl_mode(wl_output *wl_output, std::uint32_t flags, std::int32_t width, std::int32_t height, std::int32_t refresh);
|
||||
|
||||
/**
|
||||
* @brief Acknowledge completion of a wl-output metadata update.
|
||||
*
|
||||
* @param wl_output Wayland output.
|
||||
*/
|
||||
void wl_done(wl_output *wl_output) {}
|
||||
|
||||
/**
|
||||
* @brief Receive the wl-output scale factor.
|
||||
*
|
||||
* @param wl_output Wayland output.
|
||||
* @param factor Wayland output scale factor.
|
||||
*/
|
||||
void wl_scale(wl_output *wl_output, std::int32_t factor) {}
|
||||
|
||||
wl_output *output;
|
||||
std::string name;
|
||||
std::string description;
|
||||
platf::touch_port_t viewport;
|
||||
wl_output_listener wl_listener;
|
||||
zxdg_output_v1_listener xdg_listener;
|
||||
wl_output *output; ///< Wayland output associated with this monitor.
|
||||
std::string name; ///< xdg-output name used for display selection.
|
||||
std::string description; ///< xdg-output description used for logs and UI.
|
||||
platf::touch_port_t viewport; ///< Logical monitor bounds used to scale absolute input.
|
||||
wl_output_listener wl_listener; ///< Callback table for wl-output events.
|
||||
zxdg_output_v1_listener xdg_listener; ///< Callback table for xdg-output events.
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Wayland registry state for screencopy, DMA-BUF, and output globals.
|
||||
*/
|
||||
class interface_t {
|
||||
struct bind_t {
|
||||
std::uint32_t id;
|
||||
@@ -142,6 +308,9 @@ namespace wl {
|
||||
};
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Wayland globals required by the WLR capture backend.
|
||||
*/
|
||||
enum interface_e {
|
||||
XDG_OUTPUT, ///< xdg-output
|
||||
WLR_EXPORT_DMABUF, ///< screencopy manager
|
||||
@@ -156,20 +325,45 @@ namespace wl {
|
||||
interface_t &operator=(const interface_t &) = delete;
|
||||
interface_t &operator=(interface_t &&) = delete;
|
||||
|
||||
/**
|
||||
* @brief Discover compositor globals from the Wayland registry.
|
||||
*
|
||||
* @param registry Wayland registry announcing globals.
|
||||
*/
|
||||
void listen(wl_registry *registry);
|
||||
|
||||
/**
|
||||
* @brief Test whether a required Wayland global was advertised.
|
||||
*
|
||||
* @param bit Interface bit to test.
|
||||
* @return True when the requested Wayland interface bit is set.
|
||||
*/
|
||||
bool operator[](interface_e bit) const {
|
||||
return interface[bit];
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Record a DMA-BUF format advertised by the compositor.
|
||||
*
|
||||
* @param zwp_linux_dmabuf DMA-BUF interface that emitted the format event.
|
||||
* @param format DRM format supported by the compositor.
|
||||
*/
|
||||
void dmabuf_format(zwp_linux_dmabuf_v1 *zwp_linux_dmabuf, uint32_t format);
|
||||
/**
|
||||
* @brief Record a DMA-BUF format modifier advertised by the compositor.
|
||||
*
|
||||
* @param zwp_linux_dmabuf DMA-BUF interface that emitted the modifier event.
|
||||
* @param format DRM format associated with the modifier.
|
||||
* @param modifier_hi High 32 bits of the DRM format modifier.
|
||||
* @param modifier_lo Low 32 bits of the DRM format modifier.
|
||||
*/
|
||||
void dmabuf_modifier(zwp_linux_dmabuf_v1 *zwp_linux_dmabuf, uint32_t format, uint32_t modifier_hi, uint32_t modifier_lo);
|
||||
|
||||
std::vector<std::unique_ptr<monitor_t>> monitors;
|
||||
std::map<std::uint32_t, std::vector<std::uint64_t>> supported_modifiers;
|
||||
zwlr_screencopy_manager_v1 *screencopy_manager {nullptr};
|
||||
zwp_linux_dmabuf_v1 *dmabuf_interface {nullptr};
|
||||
zxdg_output_manager_v1 *output_manager {nullptr};
|
||||
std::vector<std::unique_ptr<monitor_t>> monitors; ///< Outputs discovered from the Wayland registry.
|
||||
std::map<std::uint32_t, std::vector<std::uint64_t>> supported_modifiers; ///< DRM format modifiers grouped by format.
|
||||
zwlr_screencopy_manager_v1 *screencopy_manager {nullptr}; ///< WLR screencopy global used to request frames.
|
||||
zwp_linux_dmabuf_v1 *dmabuf_interface {nullptr}; ///< Linux DMA-BUF global used to allocate frame buffers.
|
||||
zxdg_output_manager_v1 *output_manager {nullptr}; ///< xdg-output global used to query monitor names and sizes.
|
||||
|
||||
private:
|
||||
void add_interface(wl_registry *registry, std::uint32_t id, const char *interface, std::uint32_t version);
|
||||
@@ -180,26 +374,40 @@ namespace wl {
|
||||
zwp_linux_dmabuf_v1_listener dmabuf_listener;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Wayland display connection used to dispatch capture events.
|
||||
*/
|
||||
class display_t {
|
||||
public:
|
||||
/**
|
||||
* @brief Initialize display.
|
||||
* If display_name == nullptr -> display_name = std::getenv("WAYLAND_DISPLAY")
|
||||
* @param display_name The name of the display.
|
||||
* @return 0 on success, -1 on failure.
|
||||
* @brief Connect to the requested Wayland display.
|
||||
*
|
||||
* @param display_name Wayland display name, or nullptr to use WAYLAND_DISPLAY.
|
||||
* @return 0 when the connection is opened and initialized; -1 on failure.
|
||||
*/
|
||||
int init(const char *display_name = nullptr);
|
||||
|
||||
// Roundtrip with Wayland connection
|
||||
/**
|
||||
* @brief Flush pending Wayland requests and wait for replies.
|
||||
*/
|
||||
void roundtrip();
|
||||
|
||||
// Wait up to the timeout to read and dispatch new events
|
||||
bool dispatch(std::chrono::milliseconds timeout);
|
||||
|
||||
// Get the registry associated with the display
|
||||
// No need to manually free the registry
|
||||
/**
|
||||
* @brief Return the Wayland registry for global discovery.
|
||||
*
|
||||
* @return Wayland registry used to discover compositor globals.
|
||||
*/
|
||||
wl_registry *registry();
|
||||
|
||||
/**
|
||||
* @brief Return the native Wayland display pointer.
|
||||
*
|
||||
* @return Native display connection owned by this wrapper.
|
||||
*/
|
||||
inline display_internal_t::pointer get() {
|
||||
return display_internal.get();
|
||||
}
|
||||
@@ -208,7 +416,18 @@ namespace wl {
|
||||
display_internal_t display_internal;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Refresh the monitor list reported by the display server.
|
||||
*
|
||||
* @param display_name Display name.
|
||||
* @return Monitors reported by the Wayland compositor, optionally filtered by name.
|
||||
*/
|
||||
std::vector<std::unique_ptr<monitor_t>> monitors(const char *display_name = nullptr);
|
||||
/**
|
||||
* @brief Initialize Wayland registry interfaces required for capture.
|
||||
*
|
||||
* @return 0 on success; nonzero or negative platform status on failure.
|
||||
*/
|
||||
int init();
|
||||
} // namespace wl
|
||||
#else
|
||||
|
||||
@@ -19,15 +19,32 @@ namespace wl {
|
||||
static int env_width;
|
||||
static int env_height;
|
||||
|
||||
/**
|
||||
* @brief Captured frame buffer shared between capture and encode stages.
|
||||
*/
|
||||
struct img_t: public platf::img_t {
|
||||
/**
|
||||
* @brief Destroy the Wayland capture image.
|
||||
*/
|
||||
~img_t() override {
|
||||
delete[] data;
|
||||
data = nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Wayland screencopy capture backend shared by RAM and VRAM paths.
|
||||
*/
|
||||
class wlr_t: public platf::display_t {
|
||||
public:
|
||||
/**
|
||||
* @brief Initialize Wayland screencopy capture for the selected output.
|
||||
*
|
||||
* @param hwdevice_type Hardware device type requested for capture or encode.
|
||||
* @param display_name Display name.
|
||||
* @param config Configuration values to apply.
|
||||
* @return 0 on success; nonzero or negative platform status on failure.
|
||||
*/
|
||||
int init(platf::mem_type_e hwdevice_type, const std::string &display_name, const ::video::config_t &config) {
|
||||
// calculate frame interval we should capture at
|
||||
if (config.framerateX100 > 0) {
|
||||
@@ -124,10 +141,25 @@ namespace wl {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Populate a fallback image when real capture data is unavailable.
|
||||
*
|
||||
* @param img Image or frame object to read from or populate.
|
||||
* @return Capture status reported to the streaming pipeline.
|
||||
*/
|
||||
int dummy_img(platf::img_t *img) override {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Capture a display frame into the provided image object.
|
||||
*
|
||||
* @param pull_free_image_cb Callback that provides an available image buffer.
|
||||
* @param img_out Captured wlroots image returned to the streaming pipeline.
|
||||
* @param timeout Maximum time to wait for the operation.
|
||||
* @param cursor Cursor image or visibility state to composite.
|
||||
* @return Capture status reported to the streaming pipeline.
|
||||
*/
|
||||
inline platf::capture_e snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr<platf::img_t> &img_out, std::chrono::milliseconds timeout, bool cursor) {
|
||||
auto to = std::chrono::steady_clock::now() + timeout;
|
||||
|
||||
@@ -153,17 +185,20 @@ namespace wl {
|
||||
return platf::capture_e::ok;
|
||||
}
|
||||
|
||||
platf::mem_type_e mem_type;
|
||||
platf::mem_type_e mem_type; ///< Mem type.
|
||||
|
||||
std::chrono::nanoseconds delay;
|
||||
std::chrono::nanoseconds delay; ///< Delay before the timer task becomes eligible to run.
|
||||
|
||||
wl::display_t display;
|
||||
interface_t interface;
|
||||
dmabuf_t dmabuf;
|
||||
wl::display_t display; ///< Wayland display connection used for capture.
|
||||
interface_t interface; ///< Wayland registry interfaces required by screencopy.
|
||||
dmabuf_t dmabuf; ///< DMA-BUF feedback and format state advertised by the compositor.
|
||||
|
||||
wl_output *output;
|
||||
wl_output *output; ///< Wayland output selected for capture.
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Wayland screencopy backend that copies frames into system memory.
|
||||
*/
|
||||
class wlr_ram_t: public wlr_t {
|
||||
public:
|
||||
platf::capture_e capture(const push_captured_image_cb_t &push_captured_image_cb, const pull_free_image_cb_t &pull_free_image_cb, bool *cursor) override {
|
||||
@@ -211,6 +246,15 @@ namespace wl {
|
||||
return platf::capture_e::ok;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Capture a display frame into the provided image object.
|
||||
*
|
||||
* @param pull_free_image_cb Callback that provides an available image buffer.
|
||||
* @param img_out Captured wlroots image returned to the streaming pipeline.
|
||||
* @param timeout Maximum time to wait for the operation.
|
||||
* @param cursor Cursor image or visibility state to composite.
|
||||
* @return Capture status reported to the streaming pipeline.
|
||||
*/
|
||||
platf::capture_e snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr<platf::img_t> &img_out, std::chrono::milliseconds timeout, bool cursor) {
|
||||
auto status = wlr_t::snapshot(pull_free_image_cb, img_out, timeout, cursor);
|
||||
if (status != platf::capture_e::ok) {
|
||||
@@ -246,6 +290,14 @@ namespace wl {
|
||||
return platf::capture_e::ok;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Initialize Wayland capture that copies frames into system memory.
|
||||
*
|
||||
* @param hwdevice_type Hardware device type requested for capture or encode.
|
||||
* @param display_name Display name.
|
||||
* @param config Configuration values to apply.
|
||||
* @return 0 on success; nonzero or negative platform status on failure.
|
||||
*/
|
||||
int init(platf::mem_type_e hwdevice_type, const std::string &display_name, const ::video::config_t &config) {
|
||||
if (wlr_t::init(hwdevice_type, display_name, config)) {
|
||||
return -1;
|
||||
@@ -266,6 +318,12 @@ namespace wl {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Create AVCodec encode device.
|
||||
*
|
||||
* @param pix_fmt Sunshine pixel format to convert or allocate for.
|
||||
* @return Constructed AVCodec encode device object.
|
||||
*/
|
||||
std::unique_ptr<platf::avcodec_encode_device_t> make_avcodec_encode_device(platf::pix_fmt_e pix_fmt) override {
|
||||
#ifdef SUNSHINE_BUILD_VAAPI
|
||||
if (mem_type == platf::mem_type_e::vaapi) {
|
||||
@@ -282,6 +340,11 @@ namespace wl {
|
||||
return std::make_unique<platf::avcodec_encode_device_t>();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Allocate an image buffer compatible with this display backend.
|
||||
*
|
||||
* @return Allocated img object, or null when unavailable.
|
||||
*/
|
||||
std::shared_ptr<platf::img_t> alloc_img() override {
|
||||
auto img = std::make_shared<img_t>();
|
||||
img->width = width;
|
||||
@@ -293,10 +356,13 @@ namespace wl {
|
||||
return img;
|
||||
}
|
||||
|
||||
egl::display_t egl_display;
|
||||
egl::ctx_t ctx;
|
||||
egl::display_t egl_display; ///< EGL display.
|
||||
egl::ctx_t ctx; ///< EGL context used for wlroots capture conversion.
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Wayland screencopy backend that exports frames as GPU resources.
|
||||
*/
|
||||
class wlr_vram_t: public wlr_t {
|
||||
public:
|
||||
platf::capture_e capture(const push_captured_image_cb_t &push_captured_image_cb, const pull_free_image_cb_t &pull_free_image_cb, bool *cursor) override {
|
||||
@@ -344,6 +410,15 @@ namespace wl {
|
||||
return platf::capture_e::ok;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Capture a display frame into the provided image object.
|
||||
*
|
||||
* @param pull_free_image_cb Callback that provides an available image buffer.
|
||||
* @param img_out Captured wlroots image returned to the streaming pipeline.
|
||||
* @param timeout Maximum time to wait for the operation.
|
||||
* @param cursor Cursor image or visibility state to composite.
|
||||
* @return Capture status reported to the streaming pipeline.
|
||||
*/
|
||||
platf::capture_e snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr<platf::img_t> &img_out, std::chrono::milliseconds timeout, bool cursor) {
|
||||
auto status = wlr_t::snapshot(pull_free_image_cb, img_out, timeout, cursor);
|
||||
if (status != platf::capture_e::ok) {
|
||||
@@ -370,6 +445,11 @@ namespace wl {
|
||||
return platf::capture_e::ok;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Allocate an image buffer compatible with this display backend.
|
||||
*
|
||||
* @return Allocated img object, or null when unavailable.
|
||||
*/
|
||||
std::shared_ptr<platf::img_t> alloc_img() override {
|
||||
auto img = std::make_shared<egl::img_descriptor_t>();
|
||||
|
||||
@@ -385,6 +465,12 @@ namespace wl {
|
||||
return img;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Create AVCodec encode device.
|
||||
*
|
||||
* @param pix_fmt Sunshine pixel format to convert or allocate for.
|
||||
* @return Constructed AVCodec encode device object.
|
||||
*/
|
||||
std::unique_ptr<platf::avcodec_encode_device_t> make_avcodec_encode_device(platf::pix_fmt_e pix_fmt) override {
|
||||
#ifdef SUNSHINE_BUILD_VAAPI
|
||||
if (mem_type == platf::mem_type_e::vaapi) {
|
||||
@@ -401,17 +487,26 @@ namespace wl {
|
||||
return std::make_unique<platf::avcodec_encode_device_t>();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Populate a fallback image when real capture data is unavailable.
|
||||
*
|
||||
* @param img Image or frame object to read from or populate.
|
||||
* @return Capture status reported to the streaming pipeline.
|
||||
*/
|
||||
int dummy_img(platf::img_t *img) override {
|
||||
// Empty images are recognized as dummies by the zero sequence number
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::uint64_t sequence {};
|
||||
std::uint64_t sequence {}; ///< Monotonic capture sequence assigned to Wayland frames.
|
||||
};
|
||||
|
||||
} // namespace wl
|
||||
|
||||
namespace platf {
|
||||
/**
|
||||
* @brief Create a Wayland capture backend for the requested memory type.
|
||||
*/
|
||||
std::shared_ptr<display_t> wl_display(mem_type_e hwdevice_type, const std::string &display_name, const video::config_t &config) {
|
||||
if (hwdevice_type != platf::mem_type_e::system && hwdevice_type != platf::mem_type_e::vaapi && hwdevice_type != platf::mem_type_e::cuda) {
|
||||
BOOST_LOG(error) << "[wlgrab] Could not initialize display with the given hw device type."sv;
|
||||
@@ -435,6 +530,9 @@ namespace platf {
|
||||
return wlr;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Enumerate capture display names reported by the Wayland compositor.
|
||||
*/
|
||||
std::vector<std::string> wl_display_names() {
|
||||
std::vector<std::string> display_names;
|
||||
|
||||
|
||||
+228
-15
@@ -33,12 +33,32 @@
|
||||
using namespace std::literals;
|
||||
|
||||
namespace platf {
|
||||
/**
|
||||
* @brief Load XCB entry points used by the X11 capture backend.
|
||||
*
|
||||
* @return 0 when required XCB symbols are loaded; nonzero otherwise.
|
||||
*/
|
||||
int load_xcb();
|
||||
/**
|
||||
* @brief Load X11 entry points used by the X11 capture backend.
|
||||
*
|
||||
* @return 0 when required X11 symbols are loaded; nonzero otherwise.
|
||||
*/
|
||||
int load_x11();
|
||||
|
||||
namespace x11 {
|
||||
/**
|
||||
* @def _FN(x, ret, args)
|
||||
* @brief Macro for FN.
|
||||
*/
|
||||
#define _FN(x, ret, args) \
|
||||
/** \
|
||||
* @brief Function pointer type for the dynamically loaded X11 entry point. \
|
||||
*/ \
|
||||
typedef ret(*x##_fn) args; \
|
||||
/** \
|
||||
* @brief Loaded X11 entry point pointer. \
|
||||
*/ \
|
||||
static x##_fn x
|
||||
|
||||
_FN(GetImage, XImage *, (Display * display, Drawable d, int x, int y, unsigned int width, unsigned int height, unsigned long plane_mask, int format));
|
||||
@@ -174,6 +194,11 @@ namespace platf {
|
||||
_FN(setup_roots_iterator, xcb_screen_iterator_t, (const xcb_setup_t *R));
|
||||
_FN(generate_id, std::uint32_t, (xcb_connection_t * c));
|
||||
|
||||
/**
|
||||
* @brief Initialize shared-memory support for X11 capture.
|
||||
*
|
||||
* @return 0 when XCB shared-memory functions are loaded; nonzero otherwise.
|
||||
*/
|
||||
int init_shm() {
|
||||
static void *handle {nullptr};
|
||||
static bool funcs_loaded = false;
|
||||
@@ -204,6 +229,11 @@ namespace platf {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Initialize XFixes cursor tracking for an X11 display.
|
||||
*
|
||||
* @return 0 on success; nonzero or negative platform status on failure.
|
||||
*/
|
||||
int init() {
|
||||
static void *handle {nullptr};
|
||||
static bool funcs_loaded = false;
|
||||
@@ -240,29 +270,73 @@ namespace platf {
|
||||
#undef _FN
|
||||
} // namespace xcb
|
||||
|
||||
/**
|
||||
* @brief Release image resources.
|
||||
*
|
||||
* @param p Pointer passed to the deleter or conversion helper.
|
||||
*/
|
||||
void freeImage(XImage *);
|
||||
/**
|
||||
* @brief Release x resources.
|
||||
*
|
||||
* @param p Pointer passed to the deleter or conversion helper.
|
||||
*/
|
||||
void freeX(XFixesCursorImage *);
|
||||
|
||||
/**
|
||||
* @brief XCB connection pointer released with `xcb_disconnect`.
|
||||
*/
|
||||
using xcb_connect_t = util::dyn_safe_ptr<xcb_connection_t, &xcb::disconnect>;
|
||||
/**
|
||||
* @brief XCB image pointer released with `xcb_image_destroy`.
|
||||
*/
|
||||
using xcb_img_t = util::c_ptr<xcb_shm_get_image_reply_t>;
|
||||
|
||||
/**
|
||||
* @brief XImage pointer released with `XDestroyImage`.
|
||||
*/
|
||||
using ximg_t = util::safe_ptr<XImage, freeImage>;
|
||||
/**
|
||||
* @brief XFixes cursor image pointer released with `XFree`.
|
||||
*/
|
||||
using xcursor_t = util::safe_ptr<XFixesCursorImage, freeX>;
|
||||
|
||||
/**
|
||||
* @brief XRandR CRTC info pointer released with `XRRFreeCrtcInfo`.
|
||||
*/
|
||||
using crtc_info_t = util::dyn_safe_ptr<_XRRCrtcInfo, &x11::rr::FreeCrtcInfo>;
|
||||
/**
|
||||
* @brief XRandR output info pointer released with `XRRFreeOutputInfo`.
|
||||
*/
|
||||
using output_info_t = util::dyn_safe_ptr<_XRROutputInfo, &x11::rr::FreeOutputInfo>;
|
||||
/**
|
||||
* @brief XRandR screen resources pointer released with `XRRFreeScreenResources`.
|
||||
*/
|
||||
using screen_res_t = util::dyn_safe_ptr<_XRRScreenResources, &x11::rr::FreeScreenResources>;
|
||||
|
||||
/**
|
||||
* @brief RAII wrapper that removes a SysV shared-memory segment.
|
||||
*/
|
||||
class shm_id_t {
|
||||
public:
|
||||
shm_id_t():
|
||||
id {-1} {
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Take ownership of a SysV shared-memory segment ID.
|
||||
*
|
||||
* @param id SysV shared-memory segment ID.
|
||||
*/
|
||||
shm_id_t(int id):
|
||||
id {id} {
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Move ownership of a SysV shared-memory segment ID.
|
||||
*
|
||||
* @param other Shared-memory ID wrapper whose segment ownership is moved.
|
||||
*/
|
||||
shm_id_t(shm_id_t &&other) noexcept:
|
||||
id(other.id) {
|
||||
other.id = -1;
|
||||
@@ -275,19 +349,32 @@ namespace platf {
|
||||
}
|
||||
}
|
||||
|
||||
int id;
|
||||
int id; ///< SysV shared-memory segment identifier returned by shmget.
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief RAII wrapper that detaches mapped SysV shared memory.
|
||||
*/
|
||||
class shm_data_t {
|
||||
public:
|
||||
shm_data_t():
|
||||
data {(void *) -1} {
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Take ownership of an attached shared-memory mapping.
|
||||
*
|
||||
* @param data Pointer returned by shmat.
|
||||
*/
|
||||
shm_data_t(void *data):
|
||||
data {data} {
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Move ownership of an attached shared-memory mapping.
|
||||
*
|
||||
* @param other Shared-memory mapping wrapper whose attachment is moved.
|
||||
*/
|
||||
shm_data_t(shm_data_t &&other) noexcept:
|
||||
data(other.data) {
|
||||
other.data = (void *) -1;
|
||||
@@ -299,13 +386,19 @@ namespace platf {
|
||||
}
|
||||
}
|
||||
|
||||
void *data;
|
||||
void *data; ///< Address returned by shmat for the shared-memory segment.
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief X11 image wrapper used by the software capture path.
|
||||
*/
|
||||
struct x11_img_t: public img_t {
|
||||
ximg_t img;
|
||||
ximg_t img; ///< XImage backing the current software-captured frame.
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief X11 shared-memory image and segment ownership.
|
||||
*/
|
||||
struct shm_img_t: public img_t {
|
||||
~shm_img_t() override {
|
||||
delete[] data;
|
||||
@@ -362,14 +455,17 @@ namespace platf {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief X11 display, window, and attribute handles for capture.
|
||||
*/
|
||||
struct x11_attr_t: public display_t {
|
||||
std::chrono::nanoseconds delay;
|
||||
std::chrono::nanoseconds delay; ///< Delay before the timer task becomes eligible to run.
|
||||
|
||||
x11::xdisplay_t xdisplay;
|
||||
Window xwindow;
|
||||
XWindowAttributes xattr;
|
||||
x11::xdisplay_t xdisplay; ///< X11 display connection used for capture.
|
||||
Window xwindow; ///< Root window being captured.
|
||||
XWindowAttributes xattr; ///< Cached X11 window attributes used to detect size changes.
|
||||
|
||||
mem_type_e mem_type;
|
||||
mem_type_e mem_type; ///< Mem type.
|
||||
|
||||
/**
|
||||
* Last X (NOT the streamed monitor!) size.
|
||||
@@ -377,6 +473,11 @@ namespace platf {
|
||||
*/
|
||||
// int env_width, env_height;
|
||||
|
||||
/**
|
||||
* @brief Open the X11 display and initialize capture attributes.
|
||||
*
|
||||
* @param mem_type Requested memory path for the capture backend.
|
||||
*/
|
||||
x11_attr_t(mem_type_e mem_type):
|
||||
xdisplay {x11::OpenDisplay(nullptr)},
|
||||
xwindow {},
|
||||
@@ -385,6 +486,13 @@ namespace platf {
|
||||
x11::InitThreads();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Open the X11 display and cache capture window attributes.
|
||||
*
|
||||
* @param display_name Display name.
|
||||
* @param config Configuration values to apply.
|
||||
* @return 0 on success; nonzero or negative platform status on failure.
|
||||
*/
|
||||
int init(const std::string &display_name, const ::video::config_t &config) {
|
||||
if (!xdisplay) {
|
||||
BOOST_LOG(error) << "Could not open X11 display"sv;
|
||||
@@ -501,6 +609,15 @@ namespace platf {
|
||||
return capture_e::ok;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Capture a display frame into the provided image object.
|
||||
*
|
||||
* @param pull_free_image_cb Callback that provides an available image buffer.
|
||||
* @param img_out XImage-backed captured frame returned to the streaming pipeline.
|
||||
* @param timeout Maximum time to wait for the operation.
|
||||
* @param cursor Cursor image or visibility state to composite.
|
||||
* @return Capture status reported to the streaming pipeline.
|
||||
*/
|
||||
capture_e snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr<platf::img_t> &img_out, std::chrono::milliseconds timeout, bool cursor) {
|
||||
refresh();
|
||||
|
||||
@@ -532,10 +649,21 @@ namespace platf {
|
||||
return capture_e::ok;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Allocate an image buffer compatible with this display backend.
|
||||
*
|
||||
* @return Allocated img object, or null when unavailable.
|
||||
*/
|
||||
std::shared_ptr<img_t> alloc_img() override {
|
||||
return std::make_shared<x11_img_t>();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Create AVCodec encode device.
|
||||
*
|
||||
* @param pix_fmt Sunshine pixel format to convert or allocate for.
|
||||
* @return Constructed AVCodec encode device object.
|
||||
*/
|
||||
std::unique_ptr<avcodec_encode_device_t> make_avcodec_encode_device(pix_fmt_e pix_fmt) override {
|
||||
#ifdef SUNSHINE_BUILD_VAAPI
|
||||
if (mem_type == mem_type_e::vaapi) {
|
||||
@@ -552,6 +680,12 @@ namespace platf {
|
||||
return std::make_unique<avcodec_encode_device_t>();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Populate a fallback image when real capture data is unavailable.
|
||||
*
|
||||
* @param img Image or frame object to read from or populate.
|
||||
* @return Capture status reported to the streaming pipeline.
|
||||
*/
|
||||
int dummy_img(img_t *img) override {
|
||||
// TODO: stop cheating and give black image
|
||||
if (!img) {
|
||||
@@ -567,24 +701,35 @@ namespace platf {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief X11 shared-memory image dimensions and identifiers.
|
||||
*/
|
||||
struct shm_attr_t: public x11_attr_t {
|
||||
x11::xdisplay_t shm_xdisplay; // Prevent race condition with x11_attr_t::xdisplay
|
||||
xcb_connect_t xcb;
|
||||
xcb_screen_t *display;
|
||||
std::uint32_t seg;
|
||||
x11::xdisplay_t shm_xdisplay; ///< X11 display held separately to prevent races with x11_attr_t::xdisplay.
|
||||
xcb_connect_t xcb; ///< XCB connection used by the shared-memory capture path.
|
||||
xcb_screen_t *display; ///< XCB screen containing the captured root window.
|
||||
std::uint32_t seg; ///< XCB shared-memory segment ID attached to the image.
|
||||
|
||||
shm_id_t shm_id;
|
||||
shm_id_t shm_id; ///< Shm ID.
|
||||
|
||||
shm_data_t data;
|
||||
shm_data_t data; ///< Attached SysV shared-memory data used by XShm.
|
||||
|
||||
task_pool_util::TaskPool::task_id_t refresh_task_id;
|
||||
task_pool_util::TaskPool::task_id_t refresh_task_id; ///< Refresh task ID.
|
||||
|
||||
/**
|
||||
* @brief Refresh X11 shared-memory capture after a scheduled delay.
|
||||
*/
|
||||
void delayed_refresh() {
|
||||
refresh();
|
||||
|
||||
refresh_task_id = task_pool.pushDelayed(&shm_attr_t::delayed_refresh, 2s, this).task_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Open an X11 shared-memory capture backend.
|
||||
*
|
||||
* @param mem_type Requested memory path for the capture backend.
|
||||
*/
|
||||
shm_attr_t(mem_type_e mem_type):
|
||||
x11_attr_t(mem_type),
|
||||
shm_xdisplay {x11::OpenDisplay(nullptr)} {
|
||||
@@ -640,6 +785,15 @@ namespace platf {
|
||||
return capture_e::ok;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Capture a display frame into the provided image object.
|
||||
*
|
||||
* @param pull_free_image_cb Callback that provides an available image buffer.
|
||||
* @param img_out Shared-memory captured frame returned to the streaming pipeline.
|
||||
* @param timeout Maximum time to wait for the operation.
|
||||
* @param cursor Cursor image or visibility state to composite.
|
||||
* @return Capture status reported to the streaming pipeline.
|
||||
*/
|
||||
capture_e snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr<platf::img_t> &img_out, std::chrono::milliseconds timeout, bool cursor) {
|
||||
// The whole X server changed, so we must reinit everything
|
||||
if (xattr.width != env_width || xattr.height != env_height) {
|
||||
@@ -670,6 +824,11 @@ namespace platf {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Allocate an image buffer compatible with this display backend.
|
||||
*
|
||||
* @return Allocated img object, or null when unavailable.
|
||||
*/
|
||||
std::shared_ptr<img_t> alloc_img() override {
|
||||
auto img = std::make_shared<shm_img_t>();
|
||||
img->width = width;
|
||||
@@ -681,10 +840,23 @@ namespace platf {
|
||||
return img;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Populate a fallback image when real capture data is unavailable.
|
||||
*
|
||||
* @param img Image or frame object to read from or populate.
|
||||
* @return Capture status reported to the streaming pipeline.
|
||||
*/
|
||||
int dummy_img(platf::img_t *img) override {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Initialize X11 shared-memory capture for the selected display.
|
||||
*
|
||||
* @param display_name Display name.
|
||||
* @param config Configuration values to apply.
|
||||
* @return 0 on success; nonzero or negative platform status on failure.
|
||||
*/
|
||||
int init(const std::string &display_name, const ::video::config_t &config) {
|
||||
if (x11_attr_t::init(display_name, config)) {
|
||||
return 1;
|
||||
@@ -724,11 +896,24 @@ namespace platf {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Calculate the XCB shared-memory frame size.
|
||||
*
|
||||
* @return Frame size in bytes for BGRA pixels.
|
||||
*/
|
||||
std::uint32_t frame_size() {
|
||||
return width * height * 4;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Create an X11 display capture backend.
|
||||
*
|
||||
* @param hwdevice_type Hardware device type requested for capture or encode.
|
||||
* @param display_name Display name.
|
||||
* @param config Configuration values to apply.
|
||||
* @return X11 display backend, or nullptr when initialization fails.
|
||||
*/
|
||||
std::shared_ptr<display_t> x11_display(platf::mem_type_e hwdevice_type, const std::string &display_name, const ::video::config_t &config) {
|
||||
if (hwdevice_type != platf::mem_type_e::system && hwdevice_type != platf::mem_type_e::vaapi && hwdevice_type != platf::mem_type_e::cuda) {
|
||||
BOOST_LOG(error) << "Could not initialize x11 display with the given hw device type"sv;
|
||||
@@ -763,6 +948,11 @@ namespace platf {
|
||||
return x11_disp;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Enumerate display names accepted by the X11 backend.
|
||||
*
|
||||
* @return X11 display names, or an empty list when X11 probing fails.
|
||||
*/
|
||||
std::vector<std::string> x11_display_names() {
|
||||
if (load_x11() || load_xcb()) {
|
||||
BOOST_LOG(error) << "Couldn't init x11 libraries"sv;
|
||||
@@ -800,14 +990,23 @@ namespace platf {
|
||||
return names;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Release image resources.
|
||||
*/
|
||||
void freeImage(XImage *p) {
|
||||
XDestroyImage(p);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Release x resources.
|
||||
*/
|
||||
void freeX(XFixesCursorImage *p) {
|
||||
x11::Free(p);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Load XCB entry points used by the X11 capture backend.
|
||||
*/
|
||||
int load_xcb() {
|
||||
// This will be called once only
|
||||
static int xcb_status = xcb::init_shm() || xcb::init();
|
||||
@@ -815,6 +1014,9 @@ namespace platf {
|
||||
return xcb_status;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Load X11 entry points used by the X11 capture backend.
|
||||
*/
|
||||
int load_x11() {
|
||||
// This will be called once only
|
||||
static int x11_status =
|
||||
@@ -868,14 +1070,25 @@ namespace platf {
|
||||
blend_cursor((xdisplay_t::pointer) ctx.get(), img, offsetX, offsetY);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Open and initialize the display connection used for capture.
|
||||
*/
|
||||
xdisplay_t make_display() {
|
||||
return OpenDisplay(nullptr);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Release display resources.
|
||||
*/
|
||||
void freeDisplay(_XDisplay *xdisplay) {
|
||||
CloseDisplay(xdisplay);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Release cursor context resources.
|
||||
*
|
||||
* @param ctx Native context object used by the operation or callback.
|
||||
*/
|
||||
void freeCursorCtx(cursor_ctx_t::pointer ctx) {
|
||||
CloseDisplay((xdisplay_t::pointer) ctx);
|
||||
}
|
||||
|
||||
@@ -20,16 +20,45 @@ namespace egl {
|
||||
|
||||
namespace platf::x11 {
|
||||
struct cursor_ctx_raw_t;
|
||||
/**
|
||||
* @brief Release cursor context resources.
|
||||
*
|
||||
* @param ctx Native context object used by the operation or callback.
|
||||
*/
|
||||
void freeCursorCtx(cursor_ctx_raw_t *ctx);
|
||||
/**
|
||||
* @brief Release display resources.
|
||||
*
|
||||
* @param xdisplay X11 display connection.
|
||||
*/
|
||||
void freeDisplay(_XDisplay *xdisplay);
|
||||
|
||||
/**
|
||||
* @brief XFixes cursor image pointer released with `XFree`.
|
||||
*/
|
||||
using cursor_ctx_t = util::safe_ptr<cursor_ctx_raw_t, freeCursorCtx>;
|
||||
/**
|
||||
* @brief X11 display pointer released with `XCloseDisplay`.
|
||||
*/
|
||||
using xdisplay_t = util::safe_ptr<_XDisplay, freeDisplay>;
|
||||
|
||||
/**
|
||||
* @brief X11 cursor image and positioning state used during capture.
|
||||
*/
|
||||
class cursor_t {
|
||||
public:
|
||||
/**
|
||||
* @brief Allocate the underlying object and wrap it in the owning handle.
|
||||
*
|
||||
* @return Created backend object, or null when creation fails.
|
||||
*/
|
||||
static std::optional<cursor_t> make();
|
||||
|
||||
/**
|
||||
* @brief Run the capture loop for this backend.
|
||||
*
|
||||
* @param img Image or frame object to read from or populate.
|
||||
*/
|
||||
void capture(egl::cursor_t &img);
|
||||
|
||||
/**
|
||||
@@ -37,11 +66,19 @@ namespace platf::x11 {
|
||||
*
|
||||
* img <-- destination image
|
||||
* offsetX, offsetY <--- Top left corner of the virtual screen
|
||||
* @param img Image or frame object to read from or populate.
|
||||
* @param offsetX Offset x.
|
||||
* @param offsetY Offset y.
|
||||
*/
|
||||
void blend(img_t &img, int offsetX, int offsetY);
|
||||
|
||||
cursor_ctx_t ctx;
|
||||
cursor_ctx_t ctx; ///< X11 cursor context used to track and blend cursor images.
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Open and initialize the display connection used for capture.
|
||||
*
|
||||
* @return Constructed display object.
|
||||
*/
|
||||
xdisplay_t make_display();
|
||||
} // namespace platf::x11
|
||||
|
||||
@@ -27,7 +27,29 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
@class CATapDescription;
|
||||
|
||||
namespace platf {
|
||||
/**
|
||||
* @brief Provide captured PCM frames to AudioConverter.
|
||||
*
|
||||
* @param inAudioConverter In audio converter.
|
||||
* @param ioNumberDataPackets Requested and returned packet count.
|
||||
* @param ioData Buffer list filled with input audio.
|
||||
* @param outDataPacketDescription Optional packet description output.
|
||||
* @param inUserData AudioConverterInputData state used by the callback.
|
||||
* @return Core Audio status code from the callback.
|
||||
*/
|
||||
OSStatus audioConverterComplexInputProc(AudioConverterRef _Nullable inAudioConverter, UInt32 *_Nonnull ioNumberDataPackets, AudioBufferList *_Nonnull ioData, AudioStreamPacketDescription *_Nullable *_Nullable outDataPacketDescription, void *_Nonnull inUserData);
|
||||
/**
|
||||
* @brief Receive system-audio tap samples from Core Audio.
|
||||
*
|
||||
* @param inDevice In device.
|
||||
* @param inNow In now.
|
||||
* @param inInputData In input data.
|
||||
* @param inInputTime In input time.
|
||||
* @param outOutputData Out output data.
|
||||
* @param inOutputTime In output time.
|
||||
* @param inClientData In client data.
|
||||
* @return Core Audio status code from the IO callback.
|
||||
*/
|
||||
OSStatus systemAudioIOProc(AudioObjectID inDevice, const AudioTimeStamp *_Nullable inNow, const AudioBufferList *_Nullable inInputData, const AudioTimeStamp *_Nullable inInputTime, AudioBufferList *_Nullable outOutputData, const AudioTimeStamp *_Nullable inOutputTime, void *_Nullable inClientData);
|
||||
} // namespace platf
|
||||
|
||||
|
||||
@@ -12,9 +12,17 @@
|
||||
#include "src/platform/common.h"
|
||||
|
||||
namespace platf {
|
||||
/**
|
||||
* @brief CoreMedia sample buffer retained by an AV image wrapper.
|
||||
*/
|
||||
struct av_sample_buf_t {
|
||||
CMSampleBufferRef buf;
|
||||
CMSampleBufferRef buf; ///< Retained CoreMedia sample buffer.
|
||||
|
||||
/**
|
||||
* @brief Retain a CoreMedia sample buffer for captured-image lifetime.
|
||||
*
|
||||
* @param buf Sample buffer received from AVFoundation.
|
||||
*/
|
||||
explicit av_sample_buf_t(CMSampleBufferRef buf):
|
||||
buf((CMSampleBufferRef) CFRetain(buf)) {
|
||||
}
|
||||
@@ -26,10 +34,18 @@ namespace platf {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief CoreVideo pixel buffer retained by an AV image wrapper.
|
||||
*/
|
||||
struct av_pixel_buf_t {
|
||||
CVPixelBufferRef buf;
|
||||
CVPixelBufferRef buf; ///< Pixel buffer extracted from the sample buffer.
|
||||
|
||||
// Constructor
|
||||
/**
|
||||
* @brief Lock the sample buffer's pixel buffer for read-only access.
|
||||
*
|
||||
* @param sb Sample buffer that owns the image data.
|
||||
*/
|
||||
explicit av_pixel_buf_t(CMSampleBufferRef sb):
|
||||
buf(
|
||||
CMSampleBufferGetImageBuffer(sb)
|
||||
@@ -37,6 +53,11 @@ namespace platf {
|
||||
CVPixelBufferLockBaseAddress(buf, kCVPixelBufferLock_ReadOnly);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Return the base address of the locked Core Video pixel buffer.
|
||||
*
|
||||
* @return Pointer to the first byte of image data in the pixel buffer.
|
||||
*/
|
||||
[[nodiscard]] uint8_t *data() const {
|
||||
return static_cast<uint8_t *>(CVPixelBufferGetBaseAddress(buf));
|
||||
}
|
||||
@@ -49,16 +70,29 @@ namespace platf {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Captured macOS image backed by an AV sample buffer.
|
||||
*/
|
||||
struct av_img_t: img_t {
|
||||
std::shared_ptr<av_sample_buf_t> sample_buffer;
|
||||
std::shared_ptr<av_pixel_buf_t> pixel_buffer;
|
||||
std::shared_ptr<av_sample_buf_t> sample_buffer; ///< Retained AVFoundation sample buffer for the captured frame.
|
||||
std::shared_ptr<av_pixel_buf_t> pixel_buffer; ///< Retained CoreVideo pixel buffer extracted from the sample.
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Temporary retain wrapper used while AV image data is borrowed.
|
||||
*/
|
||||
struct temp_retain_av_img_t {
|
||||
std::shared_ptr<av_sample_buf_t> sample_buffer;
|
||||
std::shared_ptr<av_pixel_buf_t> pixel_buffer;
|
||||
uint8_t *data;
|
||||
std::shared_ptr<av_sample_buf_t> sample_buffer; ///< Sample buffer kept alive while `data` is borrowed.
|
||||
std::shared_ptr<av_pixel_buf_t> pixel_buffer; ///< Pixel buffer kept locked while `data` is borrowed.
|
||||
uint8_t *data; ///< Pointer to the locked pixel buffer bytes.
|
||||
|
||||
/**
|
||||
* @brief Construct a temporary AV image retain wrapper.
|
||||
*
|
||||
* @param sb Sample buffer to retain.
|
||||
* @param pb Pixel buffer to retain.
|
||||
* @param dt Image data pointer.
|
||||
*/
|
||||
temp_retain_av_img_t(
|
||||
std::shared_ptr<av_sample_buf_t> sb,
|
||||
std::shared_ptr<av_pixel_buf_t> pb,
|
||||
|
||||
@@ -8,34 +8,100 @@
|
||||
#import <AppKit/AppKit.h>
|
||||
#import <AVFoundation/AVFoundation.h>
|
||||
|
||||
/**
|
||||
* @brief macOS capture session and video output handles.
|
||||
*/
|
||||
struct CaptureSession {
|
||||
AVCaptureVideoDataOutput *output;
|
||||
NSCondition *captureStopped;
|
||||
AVCaptureVideoDataOutput *output; ///< Output.
|
||||
NSCondition *captureStopped; ///< Capture stopped.
|
||||
};
|
||||
|
||||
static const int kMaxDisplays = 32;
|
||||
|
||||
/**
|
||||
* @brief AVFoundation video capture controller used by the macOS backend.
|
||||
*/
|
||||
@interface AVVideo: NSObject <AVCaptureVideoDataOutputSampleBufferDelegate>
|
||||
|
||||
/**
|
||||
* @brief Display ID property.
|
||||
*/
|
||||
@property (nonatomic, assign) CGDirectDisplayID displayID;
|
||||
/**
|
||||
* @brief Min frame duration property.
|
||||
*/
|
||||
@property (nonatomic, assign) CMTime minFrameDuration;
|
||||
/**
|
||||
* @brief Pixel format property.
|
||||
*/
|
||||
@property (nonatomic, assign) OSType pixelFormat;
|
||||
/**
|
||||
* @brief Frame width property.
|
||||
*/
|
||||
@property (nonatomic, assign) int frameWidth;
|
||||
/**
|
||||
* @brief Frame height property.
|
||||
*/
|
||||
@property (nonatomic, assign) int frameHeight;
|
||||
|
||||
/**
|
||||
* @brief Objective-C block invoked for each captured sample buffer.
|
||||
*/
|
||||
typedef bool (^FrameCallbackBlock)(CMSampleBufferRef);
|
||||
|
||||
/**
|
||||
* @brief Capture session that owns the active AVFoundation inputs and outputs.
|
||||
*/
|
||||
@property (nonatomic, assign) AVCaptureSession *session;
|
||||
/**
|
||||
* @brief Video outputs property.
|
||||
*/
|
||||
@property (nonatomic, assign) NSMapTable<AVCaptureConnection *, AVCaptureVideoDataOutput *> *videoOutputs;
|
||||
/**
|
||||
* @brief Capture callbacks property.
|
||||
*/
|
||||
@property (nonatomic, assign) NSMapTable<AVCaptureConnection *, FrameCallbackBlock> *captureCallbacks;
|
||||
/**
|
||||
* @brief Capture signals property.
|
||||
*/
|
||||
@property (nonatomic, assign) NSMapTable<AVCaptureConnection *, dispatch_semaphore_t> *captureSignals;
|
||||
|
||||
/**
|
||||
* @brief List display names accepted by the selected capture backend.
|
||||
*
|
||||
* @return Display names accepted by the selected capture backend.
|
||||
*/
|
||||
+ (NSArray<NSDictionary *> *)displayNames;
|
||||
/**
|
||||
* @brief Return the user-visible name for a CoreGraphics display.
|
||||
*
|
||||
* @param displayID Display ID.
|
||||
* @return Display name for the supplied CoreGraphics display ID.
|
||||
*/
|
||||
+ (NSString *)getDisplayName:(CGDirectDisplayID)displayID;
|
||||
|
||||
/**
|
||||
* @brief Initialize AVFoundation capture for a display and frame rate.
|
||||
*
|
||||
* @param displayID Display ID.
|
||||
* @param frameRate Frame rate.
|
||||
* @return Initialized AVVideo instance, or nil on failure.
|
||||
*/
|
||||
- (id)initWithDisplay:(CGDirectDisplayID)displayID frameRate:(int)frameRate;
|
||||
|
||||
/**
|
||||
* @brief Set frame width frame height.
|
||||
*
|
||||
* @param frameWidth Frame width.
|
||||
* @param frameHeight Frame height.
|
||||
*/
|
||||
- (void)setFrameWidth:(int)frameWidth frameHeight:(int)frameHeight;
|
||||
/**
|
||||
* @brief Run the capture loop for this backend.
|
||||
*
|
||||
* @param frameCallback Frame callback.
|
||||
* @return Capture status reported to the streaming pipeline.
|
||||
*/
|
||||
- (dispatch_semaphore_t)capture:(FrameCallbackBlock)frameCallback;
|
||||
|
||||
@end
|
||||
|
||||
@@ -52,8 +52,11 @@ namespace ca {
|
||||
}
|
||||
|
||||
namespace detail {
|
||||
/**
|
||||
* @brief Small wrapper for displaying CoreAudio OSStatus values.
|
||||
*/
|
||||
struct StatusView {
|
||||
OSStatus e;
|
||||
OSStatus e; ///< E.
|
||||
};
|
||||
|
||||
inline std::ostream &operator<<(std::ostream &os, StatusView v) {
|
||||
|
||||
@@ -21,6 +21,10 @@
|
||||
#include "src/platform/macos/nv12_zero_device.h"
|
||||
|
||||
// Avoid conflict between AVFoundation and libavutil both defining AVMediaType
|
||||
/**
|
||||
* @def AVMediaType
|
||||
* @brief Macro for AV media type.
|
||||
*/
|
||||
#define AVMediaType AVMediaType_FFmpeg
|
||||
#include "src/video.h"
|
||||
#undef AVMediaType
|
||||
@@ -210,10 +214,13 @@ namespace platf {
|
||||
}
|
||||
} // namespace
|
||||
|
||||
/**
|
||||
* @brief macOS display capture source and image buffers.
|
||||
*/
|
||||
struct av_display_t: public display_t {
|
||||
AVVideo *av_capture {};
|
||||
CGDirectDisplayID display_id {};
|
||||
IOPMAssertionID display_sleep_assertion {kIOPMNullAssertionID};
|
||||
AVVideo *av_capture {}; ///< AV capture.
|
||||
CGDirectDisplayID display_id {}; ///< Display ID.
|
||||
IOPMAssertionID display_sleep_assertion {kIOPMNullAssertionID}; ///< Display sleep assertion.
|
||||
|
||||
~av_display_t() override {
|
||||
[av_capture release];
|
||||
@@ -226,6 +233,9 @@ namespace platf {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Prevent macOS from sleeping the captured display while streaming.
|
||||
*/
|
||||
void prevent_display_sleep() {
|
||||
if (display_sleep_assertion != kIOPMNullAssertionID) {
|
||||
return;
|
||||
@@ -292,10 +302,21 @@ namespace platf {
|
||||
return capture_e::ok;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Allocate an image buffer compatible with this display backend.
|
||||
*
|
||||
* @return Allocated img object, or null when unavailable.
|
||||
*/
|
||||
std::shared_ptr<img_t> alloc_img() override {
|
||||
return std::make_shared<av_img_t>();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Create AVCodec encode device.
|
||||
*
|
||||
* @param pix_fmt Sunshine pixel format to convert or allocate for.
|
||||
* @return Constructed AVCodec encode device object.
|
||||
*/
|
||||
std::unique_ptr<avcodec_encode_device_t> make_avcodec_encode_device(pix_fmt_e pix_fmt) override {
|
||||
if (pix_fmt == pix_fmt_e::yuv420p) {
|
||||
av_capture.pixelFormat = kCVPixelFormatType_32BGRA;
|
||||
@@ -313,6 +334,12 @@ namespace platf {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Populate a fallback image when real capture data is unavailable.
|
||||
*
|
||||
* @param img Image or frame object to read from or populate.
|
||||
* @return Capture status reported to the streaming pipeline.
|
||||
*/
|
||||
int dummy_img(img_t *img) override {
|
||||
if (!platf::is_screen_capture_allowed()) {
|
||||
// If we don't have the screen capture permission, this function will hang
|
||||
@@ -359,11 +386,20 @@ namespace platf {
|
||||
* display --> an opaque pointer to an object of this class
|
||||
* width --> the intended capture width
|
||||
* height --> the intended capture height
|
||||
* @param display Display object or identifier associated with the operation.
|
||||
* @param width Frame or display width in pixels.
|
||||
* @param height Frame or display height in pixels.
|
||||
*/
|
||||
static void setResolution(void *display, int width, int height) {
|
||||
[static_cast<AVVideo *>(display) setFrameWidth:width frameHeight:height];
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set pixel format.
|
||||
*
|
||||
* @param display Display object or identifier associated with the operation.
|
||||
* @param pixelFormat Pixel format.
|
||||
*/
|
||||
static void setPixelFormat(void *display, OSType pixelFormat) {
|
||||
static_cast<AVVideo *>(display).pixelFormat = pixelFormat;
|
||||
}
|
||||
@@ -440,8 +476,9 @@ namespace platf {
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns if GPUs/drivers have changed since the last call to this function.
|
||||
* @return `true` if a change has occurred or if it is unknown whether a change occurred.
|
||||
* @brief Report whether encoder backends should be probed again before streaming.
|
||||
*
|
||||
* @return Always `true` because macOS GPU changes are not tracked by this backend.
|
||||
*/
|
||||
bool needs_encoder_reenumeration() {
|
||||
// We don't track GPU state, so we will always reenumerate. Fortunately, it is fast on macOS.
|
||||
|
||||
@@ -24,49 +24,67 @@
|
||||
#include "src/platform/common.h"
|
||||
#include "src/utility.h"
|
||||
|
||||
/**
|
||||
* @brief Delay for a double click, in milliseconds.
|
||||
* @todo Make this configurable.
|
||||
*/
|
||||
constexpr std::chrono::milliseconds MULTICLICK_DELAY_MS(500);
|
||||
constexpr auto MULTICLICK_DELAY_MS = std::chrono::milliseconds(500); ///< Maximum gap between clicks that macOS should treat as a double click.
|
||||
|
||||
namespace platf {
|
||||
using namespace std::literals;
|
||||
|
||||
constexpr int WHEEL_DELTA = 120;
|
||||
constexpr double DEFAULT_SCROLLWHEEL_SCALING = 0.3125;
|
||||
constexpr int DEFAULT_SCROLL_LINES_PER_DETENT = 5;
|
||||
constexpr int WHEEL_DELTA = 120; ///< Protocol or platform constant for wheel delta.
|
||||
constexpr double DEFAULT_SCROLLWHEEL_SCALING = 0.3125; ///< Protocol or platform constant for default scrollwheel scaling.
|
||||
constexpr int DEFAULT_SCROLL_LINES_PER_DETENT = 5; ///< Protocol or platform constant for default scroll lines per detent.
|
||||
|
||||
/**
|
||||
* @brief macOS input source and target display state.
|
||||
*/
|
||||
struct macos_input_t {
|
||||
public:
|
||||
CGDirectDisplayID display {};
|
||||
CGFloat displayScaling {};
|
||||
CGEventSourceRef source {};
|
||||
CGDirectDisplayID display {}; ///< CoreGraphics identifier for the display receiving injected input.
|
||||
CGFloat displayScaling {}; ///< Scale factor used to translate client coordinates to display pixels.
|
||||
CGEventSourceRef source {}; ///< CoreGraphics event source used for mouse and scroll events.
|
||||
|
||||
// keyboard related stuff
|
||||
CGEventSourceRef keyboard_source {};
|
||||
CGEventFlags kb_flags {};
|
||||
CGEventSourceRef keyboard_source {}; ///< CoreGraphics event source used for keyboard injection.
|
||||
CGEventFlags kb_flags {}; ///< Active keyboard modifier flags currently held down by the client.
|
||||
|
||||
// mouse related stuff
|
||||
CGEventRef mouse_event {}; // mouse event source
|
||||
double scrollwheel_scaling {DEFAULT_SCROLLWHEEL_SCALING};
|
||||
int scroll_lines_per_detent {DEFAULT_SCROLL_LINES_PER_DETENT};
|
||||
CGEventRef mouse_event {}; ///< Reusable CoreGraphics mouse event updated before posting.
|
||||
double scrollwheel_scaling {DEFAULT_SCROLLWHEEL_SCALING}; ///< Multiplier applied to incoming scroll-wheel deltas.
|
||||
int scroll_lines_per_detent {DEFAULT_SCROLL_LINES_PER_DETENT}; ///< Number of logical scroll lines represented by one wheel detent.
|
||||
/**
|
||||
* @brief Tracks whether the mouse button is currently pressed.
|
||||
*/
|
||||
bool mouse_down[3] {}; // mouse button status
|
||||
/**
|
||||
* @brief Last mouse event.
|
||||
*/
|
||||
std::chrono::steady_clock::steady_clock::time_point last_mouse_event[3][2]; // timestamp of last mouse events
|
||||
};
|
||||
|
||||
// A struct to hold a Windows keycode to Mac virtual keycode mapping.
|
||||
/**
|
||||
* @brief Mapping from Sunshine key symbols to macOS virtual key codes.
|
||||
*/
|
||||
struct KeyCodeMap {
|
||||
int win_keycode;
|
||||
int mac_keycode;
|
||||
int win_keycode; ///< Sunshine/Windows virtual key code received from the client.
|
||||
int mac_keycode; ///< macOS virtual key code sent through CoreGraphics.
|
||||
};
|
||||
|
||||
// Customized less operator for using std::lower_bound() on a KeyCodeMap array.
|
||||
/**
|
||||
* @brief Order key mappings by Sunshine key code for binary search.
|
||||
*
|
||||
* @param a Left-hand mapping being compared.
|
||||
* @param b Right-hand mapping being compared.
|
||||
* @return True when `a` has a lower Sunshine key code than `b`.
|
||||
*/
|
||||
bool operator<(const KeyCodeMap &a, const KeyCodeMap &b) {
|
||||
return a.win_keycode < b.win_keycode;
|
||||
}
|
||||
|
||||
// clang-format off
|
||||
/**
|
||||
* @brief Key codes map.
|
||||
*/
|
||||
const KeyCodeMap kKeyCodesMap[] = {
|
||||
{ 0x08 /* VKEY_BACK */, kVK_Delete },
|
||||
{ 0x09 /* VKEY_TAB */, kVK_Tab },
|
||||
@@ -238,6 +256,12 @@ const KeyCodeMap kKeyCodesMap[] = {
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
/**
|
||||
* @brief Translate a platform keycode to a Sunshine key symbol.
|
||||
*
|
||||
* @param keycode Platform keycode being translated or emitted.
|
||||
* @return Sunshine key symbol, or 0 when the keycode is unmapped.
|
||||
*/
|
||||
int keysym(int keycode) {
|
||||
KeyCodeMap key_map {};
|
||||
|
||||
@@ -256,12 +280,22 @@ const KeyCodeMap kKeyCodesMap[] = {
|
||||
return temp_map->mac_keycode;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief macOS modifier flags split into generic and device-specific bits.
|
||||
*/
|
||||
struct modifier_flags_t {
|
||||
CGEventFlags generic {};
|
||||
CGEventFlags device {};
|
||||
CGEventFlags all_devices {};
|
||||
CGEventFlags generic {}; ///< Modifier bits represented by device-independent CoreGraphics flags.
|
||||
CGEventFlags device {}; ///< Modifier bits represented by left/right device-specific flags.
|
||||
CGEventFlags all_devices {}; ///< Mask covering all device-specific variants for this modifier.
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Resolve the CoreGraphics modifier masks associated with a key code.
|
||||
*
|
||||
* @param key Sunshine key code that may represent a modifier key.
|
||||
* @param flags Output masks for the matching CoreGraphics modifier.
|
||||
* @return True when modifier flags were found for the key.
|
||||
*/
|
||||
bool modifier_flags_for_key(int key, modifier_flags_t &flags) {
|
||||
switch (key) {
|
||||
case kVK_Shift:
|
||||
@@ -368,6 +402,16 @@ const KeyCodeMap kKeyCodesMap[] = {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Post a mouse event at the clamped display location.
|
||||
*
|
||||
* @param input Platform input context.
|
||||
* @param button Mouse button.
|
||||
* @param type CoreGraphics mouse event type.
|
||||
* @param raw_location Requested mouse location.
|
||||
* @param previous_location Previous mouse location.
|
||||
* @param click_count Click count for the event.
|
||||
*/
|
||||
void post_mouse(
|
||||
input_t &input,
|
||||
const CGMouseButton button,
|
||||
@@ -410,6 +454,12 @@ const KeyCodeMap kKeyCodesMap[] = {
|
||||
CGWarpMouseCursorPosition(location);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Choose the CoreGraphics mouse event type for the current button action.
|
||||
*
|
||||
* @param input Platform input backend that receives the event.
|
||||
* @return CoreGraphics mouse event type for button press or release.
|
||||
*/
|
||||
inline CGEventType event_type_mouse(input_t &input) {
|
||||
const auto macos_input = static_cast<macos_input_t *>(input.get());
|
||||
|
||||
@@ -494,6 +544,12 @@ const KeyCodeMap kKeyCodesMap[] = {
|
||||
macos_input->last_mouse_event[mac_button][release] = now;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get scroll lines per detent.
|
||||
*
|
||||
* @param scrollwheel_scaling Scrollwheel scaling.
|
||||
* @return Number of logical lines represented by one wheel detent.
|
||||
*/
|
||||
int get_scroll_lines_per_detent(double &scrollwheel_scaling) {
|
||||
double scale = DEFAULT_SCROLLWHEEL_SCALING;
|
||||
const auto value = CFPreferencesCopyValue(CFSTR("com.apple.scrollwheel.scaling"), kCFPreferencesAnyApplication, kCFPreferencesCurrentUser, kCFPreferencesAnyHost);
|
||||
@@ -521,6 +577,13 @@ const KeyCodeMap kKeyCodesMap[] = {
|
||||
return std::max(1, static_cast<int>(std::ceil(1.0 + scroll_scale * lines_per_scroll_scale)));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Convert a high-resolution wheel delta to CoreGraphics scroll pixels.
|
||||
*
|
||||
* @param macos_input macOS input state containing scroll scaling settings.
|
||||
* @param high_res_distance Wheel delta in Windows high-resolution units.
|
||||
* @return Pixel distance to send to CoreGraphics.
|
||||
*/
|
||||
int scroll_pixels(const macos_input_t *macos_input, const int high_res_distance) {
|
||||
const auto source_pixels_per_line = CGEventSourceGetPixelsPerLine(macos_input->source);
|
||||
const auto pixels_per_line = source_pixels_per_line > 0 ? static_cast<int>(source_pixels_per_line + 0.5) : 10;
|
||||
@@ -529,6 +592,13 @@ const KeyCodeMap kKeyCodesMap[] = {
|
||||
return static_cast<int>(scaled_pixels / WHEEL_DELTA);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Post a macOS scroll event to the target display.
|
||||
*
|
||||
* @param input Platform input backend that receives the event.
|
||||
* @param wheelY Wheel y.
|
||||
* @param wheelX Wheel x.
|
||||
*/
|
||||
void post_scroll(input_t &input, const int wheelY, const int wheelX) {
|
||||
if (wheelY == 0 && wheelX == 0) {
|
||||
return;
|
||||
|
||||
@@ -11,13 +11,22 @@
|
||||
namespace platf {
|
||||
using namespace std::literals;
|
||||
|
||||
/**
|
||||
* @brief macOS microphone capture device and audio format state.
|
||||
*/
|
||||
struct av_mic_t: public mic_t {
|
||||
AVAudio *av_audio_capture {};
|
||||
AVAudio *av_audio_capture {}; ///< AV audio capture.
|
||||
|
||||
~av_mic_t() override {
|
||||
[av_audio_capture release];
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Deliver a captured audio sample to Sunshine's audio pipeline.
|
||||
*
|
||||
* @param sample_in Sample in.
|
||||
* @return Capture status reported to the streaming pipeline.
|
||||
*/
|
||||
capture_e sample(std::vector<float> &sample_in) override {
|
||||
const uint32_t neededBytes = static_cast<uint32_t>(sample_in.size() * sizeof(float));
|
||||
uint8_t *dst = reinterpret_cast<uint8_t *>(sample_in.data());
|
||||
@@ -54,15 +63,35 @@ namespace platf {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief macOS audio control state used to create microphone streams.
|
||||
*/
|
||||
struct macos_audio_control_t: public audio_control_t {
|
||||
AVCaptureDevice *audio_capture_device {};
|
||||
AVCaptureDevice *audio_capture_device {}; ///< Audio capture device.
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Update the sink value on the backend.
|
||||
*
|
||||
* @param sink Audio sink name to route or capture.
|
||||
* @return Status from updating sink.
|
||||
*/
|
||||
int set_sink(const std::string &sink) override {
|
||||
BOOST_LOG(warning) << "audio_control_t::set_sink() unimplemented: "sv << sink;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Create a microphone capture stream for the requested layout.
|
||||
*
|
||||
* @param mapping Opus channel mapping table for the requested layout.
|
||||
* @param channels Number of audio channels in the stream.
|
||||
* @param sample_rate Audio sample rate in hertz.
|
||||
* @param frame_size Number of samples captured per audio frame.
|
||||
* @param continuous_audio Continuous audio.
|
||||
* @param host_audio_enabled Whether host playback should remain enabled during capture.
|
||||
* @return Microphone capture object for the requested audio layout.
|
||||
*/
|
||||
std::unique_ptr<mic_t> microphone(const std::uint8_t *mapping, int channels, std::uint32_t sample_rate, std::uint32_t frame_size, bool continuous_audio, bool host_audio_enabled) override {
|
||||
auto mic = std::make_unique<av_mic_t>();
|
||||
mic->av_audio_capture = [[AVAudio alloc] init];
|
||||
@@ -110,6 +139,11 @@ namespace platf {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Query host and virtual sink names available to Sunshine.
|
||||
*
|
||||
* @return Host and virtual sink names when the backend can report them.
|
||||
*/
|
||||
std::optional<sink_t> sink_info() override {
|
||||
sink_t sink;
|
||||
|
||||
|
||||
@@ -11,13 +11,32 @@
|
||||
#include <CoreGraphics/CoreGraphics.h>
|
||||
|
||||
namespace platf {
|
||||
/**
|
||||
* @brief Check whether macOS has granted screen-capture permission.
|
||||
*
|
||||
* @return True when Sunshine can capture the screen.
|
||||
*/
|
||||
bool is_screen_capture_allowed();
|
||||
}
|
||||
} // namespace platf
|
||||
|
||||
namespace dyn {
|
||||
typedef void (*apiproc)();
|
||||
|
||||
/**
|
||||
* @brief Load persisted state from its backing store.
|
||||
*
|
||||
* @param handle Native library or object handle used by the operation.
|
||||
* @param funcs Function table populated from the loaded library.
|
||||
* @param strict Whether missing functions should be treated as an error.
|
||||
* @return 0 when all required symbols are loaded; nonzero when loading fails.
|
||||
*/
|
||||
int load(void *handle, const std::vector<std::tuple<apiproc *, const char *>> &funcs, bool strict = true);
|
||||
/**
|
||||
* @brief Return the native handle owned by the wrapper.
|
||||
*
|
||||
* @param libs List of libraries to probe for the requested symbol.
|
||||
* @return Native dynamic-library handle, or nullptr when no library can be opened.
|
||||
*/
|
||||
void *handle(const std::vector<const char *> &libs);
|
||||
|
||||
} // namespace dyn
|
||||
|
||||
@@ -5,6 +5,10 @@
|
||||
|
||||
// Required for IPV6_PKTINFO with Darwin headers
|
||||
#ifndef __APPLE_USE_RFC_3542 // NOLINT(bugprone-reserved-identifier)
|
||||
/**
|
||||
* @def __APPLE_USE_RFC_3542
|
||||
* @brief Macro for APPLE USE RFC 3542.
|
||||
*/
|
||||
#define __APPLE_USE_RFC_3542 1
|
||||
#endif
|
||||
|
||||
@@ -43,7 +47,17 @@ namespace platf {
|
||||
#if __MAC_OS_X_VERSION_MAX_ALLOWED < 110000 // __MAC_11_0
|
||||
// If they're not in the SDK then we can use our own function definitions.
|
||||
// Need to use weak import so that this will link in macOS 10.14 and earlier
|
||||
/**
|
||||
* @brief Query macOS screen-capture permission without prompting the user.
|
||||
*
|
||||
* @return True when screen-capture permission is granted.
|
||||
*/
|
||||
extern "C" bool CGPreflightScreenCaptureAccess(void) __attribute__((weak_import));
|
||||
/**
|
||||
* @brief Request macOS screen-capture permission from the user.
|
||||
*
|
||||
* @return True when screen-capture permission is granted.
|
||||
*/
|
||||
extern "C" bool CGRequestScreenCaptureAccess(void) __attribute__((weak_import));
|
||||
#endif
|
||||
|
||||
@@ -52,6 +66,9 @@ namespace platf {
|
||||
} // namespace
|
||||
|
||||
// Return whether screen capture is allowed for this process.
|
||||
/**
|
||||
* @brief Check whether screen capture allowed.
|
||||
*/
|
||||
bool is_screen_capture_allowed() {
|
||||
return screen_capture_allowed;
|
||||
}
|
||||
@@ -436,8 +453,17 @@ namespace platf {
|
||||
// are disconnected.
|
||||
static std::atomic<int> qos_ref_count = 0;
|
||||
|
||||
/**
|
||||
* @brief Owns platform QoS state that is restored during cleanup.
|
||||
*/
|
||||
class qos_t: public deinit_t {
|
||||
public:
|
||||
/**
|
||||
* @brief Apply macOS socket QoS settings for scoped cleanup.
|
||||
*
|
||||
* @param sockfd Native socket descriptor whose options are updated.
|
||||
* @param options Request options or socket options to apply.
|
||||
*/
|
||||
qos_t(int sockfd, std::vector<std::tuple<int, int, int>> options):
|
||||
sockfd(sockfd),
|
||||
options(options) {
|
||||
@@ -462,11 +488,6 @@ namespace platf {
|
||||
|
||||
/**
|
||||
* @brief Enables QoS on the given socket for traffic to the specified destination.
|
||||
* @param native_socket The native socket handle.
|
||||
* @param address The destination address for traffic sent on this socket.
|
||||
* @param port The destination port for traffic sent on this socket.
|
||||
* @param data_type The type of traffic sent on this socket.
|
||||
* @param dscp_tagging Specifies whether to enable DSCP tagging on outgoing traffic.
|
||||
*/
|
||||
std::unique_ptr<deinit_t> enable_socket_qos(uintptr_t native_socket, boost::asio::ip::address &address, uint16_t port, qos_data_type_e data_type, bool dscp_tagging) {
|
||||
int sockfd = (int) native_socket;
|
||||
@@ -546,6 +567,9 @@ namespace platf {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief macOS high-precision timer implementation backed by a worker thread.
|
||||
*/
|
||||
class macos_high_precision_timer: public high_precision_timer {
|
||||
public:
|
||||
void sleep_for(const std::chrono::nanoseconds &duration) override {
|
||||
|
||||
@@ -16,15 +16,24 @@ extern "C" {
|
||||
|
||||
namespace platf {
|
||||
|
||||
/**
|
||||
* @brief Release an FFmpeg frame allocated by the capture or conversion backend.
|
||||
*/
|
||||
void free_frame(AVFrame *frame) {
|
||||
av_frame_free(&frame);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Release a backend buffer allocated for capture or conversion.
|
||||
*
|
||||
* @param opaque Opaque user pointer provided to the callback.
|
||||
* @param data Payload or state data to serialize, deserialize, or forward.
|
||||
*/
|
||||
void free_buffer(void *opaque, uint8_t *data) {
|
||||
CVPixelBufferRelease((CVPixelBufferRef) data);
|
||||
}
|
||||
|
||||
util::safe_ptr<AVFrame, free_frame> av_frame;
|
||||
util::safe_ptr<AVFrame, free_frame> av_frame; ///< AV frame.
|
||||
|
||||
int nv12_zero_device::convert(platf::img_t &img) {
|
||||
auto *av_img = (av_img_t *) &img;
|
||||
|
||||
@@ -10,8 +10,16 @@
|
||||
struct AVFrame;
|
||||
|
||||
namespace platf {
|
||||
/**
|
||||
* @brief Release an FFmpeg frame allocated by the capture or conversion backend.
|
||||
*
|
||||
* @param frame Video or graphics frame being processed.
|
||||
*/
|
||||
void free_frame(AVFrame *frame);
|
||||
|
||||
/**
|
||||
* @brief macOS zero-copy encode device that forwards NV12 frames from AVFoundation to FFmpeg.
|
||||
*/
|
||||
class nv12_zero_device: public avcodec_encode_device_t {
|
||||
// display holds a pointer to an av_video object. Since the namespaces of AVFoundation
|
||||
// and FFMPEG collide, we need this opaque pointer and cannot use the definition
|
||||
@@ -20,13 +28,41 @@ namespace platf {
|
||||
public:
|
||||
// this function is used to set the resolution on an av_video object that we cannot
|
||||
// call directly because of namespace collisions between AVFoundation and FFMPEG
|
||||
/**
|
||||
* @brief Callback signature used to update the opaque AVFoundation display resolution.
|
||||
*/
|
||||
using resolution_fn_t = std::function<void(void *display, int width, int height)>;
|
||||
resolution_fn_t resolution_fn;
|
||||
resolution_fn_t resolution_fn; ///< Callback stored for later AVFoundation resolution updates.
|
||||
/**
|
||||
* @brief Callback signature used to update the opaque AVFoundation pixel format.
|
||||
*/
|
||||
using pixel_format_fn_t = std::function<void(void *display, int pixelFormat)>;
|
||||
|
||||
/**
|
||||
* @brief Initialize zero-copy NV12 encoding for an AVFoundation display.
|
||||
*
|
||||
* @param display Display object or identifier associated with the operation.
|
||||
* @param pix_fmt Sunshine pixel format to convert or allocate for.
|
||||
* @param resolution_fn Callback used to resize the AVFoundation capture output.
|
||||
* @param pixel_format_fn Pixel format.
|
||||
* @return 0 on success; nonzero or negative platform status on failure.
|
||||
*/
|
||||
int init(void *display, pix_fmt_e pix_fmt, resolution_fn_t resolution_fn, const pixel_format_fn_t &pixel_format_fn);
|
||||
|
||||
/**
|
||||
* @brief Forward a captured AVFoundation frame to FFmpeg without copying.
|
||||
*
|
||||
* @param img Image or frame object to read from or populate.
|
||||
* @return Conversion status.
|
||||
*/
|
||||
int convert(img_t &img) override;
|
||||
/**
|
||||
* @brief Attach frame resources used by the next conversion or encode operation.
|
||||
*
|
||||
* @param frame Video or graphics frame being processed.
|
||||
* @param hw_frames_ctx FFmpeg hardware frames context associated with the frame.
|
||||
* @return Status from updating frame.
|
||||
*/
|
||||
int set_frame(AVFrame *frame, AVBufferRef *hw_frames_ctx) override;
|
||||
|
||||
private:
|
||||
|
||||
@@ -24,8 +24,22 @@ DEFINE_GUID(CLSID_CPolicyConfigClient, 0x870af99c, 0x171d, 0x4f9e, 0xaf, 0x0d, 0
|
||||
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Policy configuration COM interface.
|
||||
*/
|
||||
#ifdef DOXYGEN
|
||||
class IPolicyConfig;
|
||||
#else
|
||||
interface DECLSPEC_UUID("f8679f50-850a-41cf-9c72-430f290290c8") IPolicyConfig;
|
||||
#endif
|
||||
/**
|
||||
* @brief Policy configuration COM class.
|
||||
*/
|
||||
#ifdef DOXYGEN
|
||||
class CPolicyConfigClient;
|
||||
#else
|
||||
class DECLSPEC_UUID("870af99c-171d-4f9e-af0d-e63df40c2bc9") CPolicyConfigClient;
|
||||
#endif
|
||||
// ----------------------------------------------------------------------------
|
||||
// class CPolicyConfigClient
|
||||
// {870af99c-171d-4f9e-af0d-e63df40c2bc9}
|
||||
@@ -41,21 +55,41 @@ class DECLSPEC_UUID("870af99c-171d-4f9e-af0d-e63df40c2bc9") CPolicyConfigClient;
|
||||
// ----------------------------------------------------------------------------
|
||||
interface IPolicyConfig: public IUnknown {
|
||||
public:
|
||||
/**
|
||||
* @brief Get the mix format for an endpoint.
|
||||
*
|
||||
* @return HRESULT or platform status returned by the wrapped API.
|
||||
*/
|
||||
virtual HRESULT GetMixFormat(
|
||||
PCWSTR,
|
||||
WAVEFORMATEX **
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief Get the device format for an endpoint.
|
||||
*
|
||||
* @return HRESULT or platform status returned by the wrapped API.
|
||||
*/
|
||||
virtual HRESULT STDMETHODCALLTYPE GetDeviceFormat(
|
||||
PCWSTR,
|
||||
INT,
|
||||
WAVEFORMATEX **
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief Reset the device format for an endpoint.
|
||||
*
|
||||
* @return HRESULT or platform status returned by the wrapped API.
|
||||
*/
|
||||
virtual HRESULT STDMETHODCALLTYPE ResetDeviceFormat(
|
||||
PCWSTR
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief Set the device format for an endpoint.
|
||||
*
|
||||
* @return HRESULT or platform status returned by the wrapped API.
|
||||
*/
|
||||
virtual HRESULT STDMETHODCALLTYPE
|
||||
SetDeviceFormat(
|
||||
PCWSTR,
|
||||
@@ -63,6 +97,11 @@ public:
|
||||
WAVEFORMATEX *
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief Get the processing period for an endpoint.
|
||||
*
|
||||
* @return HRESULT or platform status returned by the wrapped API.
|
||||
*/
|
||||
virtual HRESULT STDMETHODCALLTYPE GetProcessingPeriod(
|
||||
PCWSTR,
|
||||
INT,
|
||||
@@ -70,38 +109,75 @@ public:
|
||||
PINT64
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief Set the processing period for an endpoint.
|
||||
*
|
||||
* @return HRESULT or platform status returned by the wrapped API.
|
||||
*/
|
||||
virtual HRESULT STDMETHODCALLTYPE SetProcessingPeriod(
|
||||
PCWSTR,
|
||||
PINT64
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief Get the share mode for an endpoint.
|
||||
*
|
||||
* @return HRESULT or platform status returned by the wrapped API.
|
||||
*/
|
||||
virtual HRESULT STDMETHODCALLTYPE GetShareMode(
|
||||
PCWSTR,
|
||||
struct DeviceShareMode *
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief Set the share mode for an endpoint.
|
||||
*
|
||||
* @return HRESULT or platform status returned by the wrapped API.
|
||||
*/
|
||||
virtual HRESULT STDMETHODCALLTYPE SetShareMode(
|
||||
PCWSTR,
|
||||
struct DeviceShareMode *
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief Get a property value for an endpoint.
|
||||
*
|
||||
* @return HRESULT or platform status returned by the wrapped API.
|
||||
*/
|
||||
virtual HRESULT STDMETHODCALLTYPE GetPropertyValue(
|
||||
PCWSTR,
|
||||
const PROPERTYKEY &,
|
||||
PROPVARIANT *
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief Set a property value for an endpoint.
|
||||
*
|
||||
* @return HRESULT or platform status returned by the wrapped API.
|
||||
*/
|
||||
virtual HRESULT STDMETHODCALLTYPE SetPropertyValue(
|
||||
PCWSTR,
|
||||
const PROPERTYKEY &,
|
||||
PROPVARIANT *
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief Set the default endpoint for a role.
|
||||
*
|
||||
* @param wszDeviceId Endpoint device identifier.
|
||||
* @param eRole Endpoint role.
|
||||
* @return HRESULT or platform status returned by the wrapped API.
|
||||
*/
|
||||
virtual HRESULT STDMETHODCALLTYPE SetDefaultEndpoint(
|
||||
PCWSTR wszDeviceId,
|
||||
ERole eRole
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief Set endpoint visibility.
|
||||
*
|
||||
* @return HRESULT or platform status returned by the wrapped API.
|
||||
*/
|
||||
virtual HRESULT STDMETHODCALLTYPE SetEndpointVisibility(
|
||||
PCWSTR,
|
||||
INT
|
||||
|
||||
+234
-22
@@ -26,9 +26,24 @@
|
||||
#include "PolicyConfig.h"
|
||||
// clang-format on
|
||||
|
||||
#ifdef DOXYGEN
|
||||
/**
|
||||
* @brief Property key for a device description.
|
||||
*/
|
||||
extern const PROPERTYKEY PKEY_Device_DeviceDesc;
|
||||
/**
|
||||
* @brief Property key for a device friendly name.
|
||||
*/
|
||||
extern const PROPERTYKEY PKEY_Device_FriendlyName;
|
||||
/**
|
||||
* @brief Property key for a device interface friendly name.
|
||||
*/
|
||||
extern const PROPERTYKEY PKEY_DeviceInterface_FriendlyName;
|
||||
#else
|
||||
DEFINE_PROPERTYKEY(PKEY_Device_DeviceDesc, 0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 2); // DEVPROP_TYPE_STRING
|
||||
DEFINE_PROPERTYKEY(PKEY_Device_FriendlyName, 0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 14); // DEVPROP_TYPE_STRING
|
||||
DEFINE_PROPERTYKEY(PKEY_DeviceInterface_FriendlyName, 0x026e516e, 0xb814, 0x414b, 0x83, 0xcd, 0x85, 0x6d, 0x6f, 0xef, 0x48, 0x22, 2);
|
||||
#endif
|
||||
|
||||
#if defined(__x86_64) || defined(__x86_64__) || defined(__amd64) || defined(__amd64__) || defined(_M_AMD64)
|
||||
#define STEAM_DRIVER_SUBDIR L"x64"
|
||||
@@ -203,27 +218,70 @@ namespace {
|
||||
using namespace std::literals;
|
||||
|
||||
namespace platf::audio {
|
||||
/**
|
||||
* @brief Release the COM or platform reference owned by the pointer.
|
||||
*
|
||||
* @param p Pointer passed to the deleter or conversion helper.
|
||||
*/
|
||||
template<class T>
|
||||
void Release(T *p) {
|
||||
p->Release();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Free memory allocated by COM task APIs.
|
||||
*
|
||||
* @param p Pointer passed to the deleter or conversion helper.
|
||||
*/
|
||||
template<class T>
|
||||
void co_task_free(T *p) {
|
||||
CoTaskMemFree((LPVOID) p);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief COM device enumerator pointer for WASAPI endpoint discovery.
|
||||
*/
|
||||
using device_enum_t = util::safe_ptr<IMMDeviceEnumerator, Release<IMMDeviceEnumerator>>;
|
||||
/**
|
||||
* @brief COM pointer to a Windows audio endpoint device.
|
||||
*/
|
||||
using device_t = util::safe_ptr<IMMDevice, Release<IMMDevice>>;
|
||||
/**
|
||||
* @brief COM pointer to a collection of Windows audio endpoint devices.
|
||||
*/
|
||||
using collection_t = util::safe_ptr<IMMDeviceCollection, Release<IMMDeviceCollection>>;
|
||||
/**
|
||||
* @brief COM pointer to the WASAPI audio client interface.
|
||||
*/
|
||||
using audio_client_t = util::safe_ptr<IAudioClient, Release<IAudioClient>>;
|
||||
/**
|
||||
* @brief COM pointer to the WASAPI capture client interface.
|
||||
*/
|
||||
using audio_capture_t = util::safe_ptr<IAudioCaptureClient, Release<IAudioCaptureClient>>;
|
||||
/**
|
||||
* @brief CoTaskMem-allocated WAVEFORMATEX pointer.
|
||||
*/
|
||||
using wave_format_t = util::safe_ptr<WAVEFORMATEX, co_task_free<WAVEFORMATEX>>;
|
||||
/**
|
||||
* @brief CoTaskMem-allocated wide string pointer.
|
||||
*/
|
||||
using wstring_t = util::safe_ptr<WCHAR, co_task_free<WCHAR>>;
|
||||
/**
|
||||
* @brief Windows HANDLE wrapper closed with `CloseHandle`.
|
||||
*/
|
||||
using handle_t = util::safe_ptr_v2<void, BOOL, CloseHandle>;
|
||||
/**
|
||||
* @brief COM pointer to the Windows policy configuration interface.
|
||||
*/
|
||||
using policy_t = util::safe_ptr<IPolicyConfig, Release<IPolicyConfig>>;
|
||||
/**
|
||||
* @brief COM pointer to a Windows property store.
|
||||
*/
|
||||
using prop_t = util::safe_ptr<IPropertyStore, Release<IPropertyStore>>;
|
||||
|
||||
/**
|
||||
* @brief Initializes COM for the current thread and uninitializes it on exit.
|
||||
*/
|
||||
class co_init_t: public deinit_t {
|
||||
public:
|
||||
co_init_t() {
|
||||
@@ -235,6 +293,9 @@ namespace platf::audio {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief RAII wrapper that initializes and clears a Windows PROPVARIANT.
|
||||
*/
|
||||
class prop_var_t {
|
||||
public:
|
||||
prop_var_t() {
|
||||
@@ -245,16 +306,22 @@ namespace platf::audio {
|
||||
PropVariantClear(&prop);
|
||||
}
|
||||
|
||||
PROPVARIANT prop;
|
||||
PROPVARIANT prop; ///< Variant value returned by Windows property-store queries.
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Windows audio format details selected for capture.
|
||||
*/
|
||||
struct format_t {
|
||||
WORD channel_count;
|
||||
std::string name;
|
||||
int capture_waveformat_channel_mask;
|
||||
virtual_sink_waveformats_t virtual_sink_waveformats;
|
||||
WORD channel_count; ///< Channel count.
|
||||
std::string name; ///< Human-readable name for this item.
|
||||
int capture_waveformat_channel_mask; ///< Capture waveformat channel mask.
|
||||
virtual_sink_waveformats_t virtual_sink_waveformats; ///< Virtual sink waveformats.
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Formats.
|
||||
*/
|
||||
const std::array<const format_t, 3> formats = {
|
||||
format_t {
|
||||
2,
|
||||
@@ -276,6 +343,13 @@ namespace platf::audio {
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Create audio client.
|
||||
*
|
||||
* @param device D3D, audio, or platform device used by the operation.
|
||||
* @param format Pixel, audio, or protocol format being converted.
|
||||
* @return Constructed audio client object.
|
||||
*/
|
||||
audio_client_t make_audio_client(device_t &device, const format_t &format) {
|
||||
audio_client_t audio_client;
|
||||
auto status = device->Activate(
|
||||
@@ -335,6 +409,12 @@ namespace platf::audio {
|
||||
return audio_client;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Query the default Windows render endpoint.
|
||||
*
|
||||
* @param device_enum Windows multimedia device enumerator.
|
||||
* @return Default render endpoint, or an empty handle if lookup fails.
|
||||
*/
|
||||
device_t default_device(device_enum_t &device_enum) {
|
||||
device_t device;
|
||||
HRESULT status;
|
||||
@@ -353,20 +433,40 @@ namespace platf::audio {
|
||||
return device;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Windows audio endpoint notification callback registered with MMDevice.
|
||||
*/
|
||||
class audio_notification_t: public ::IMMNotificationClient {
|
||||
public:
|
||||
audio_notification_t() {
|
||||
}
|
||||
|
||||
// IUnknown implementation (unused by IMMDeviceEnumerator)
|
||||
/**
|
||||
* @brief Satisfy IUnknown reference counting for the notification callback.
|
||||
*
|
||||
* @return Static reference count because the callback lifetime is externally owned.
|
||||
*/
|
||||
ULONG STDMETHODCALLTYPE AddRef() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Release the COM or platform reference owned by the pointer.
|
||||
*
|
||||
* @return Reference count or status returned after releasing the object.
|
||||
*/
|
||||
ULONG STDMETHODCALLTYPE Release() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Return the supported COM interface for the notification callback.
|
||||
*
|
||||
* @param riid COM interface identifier requested by QueryInterface.
|
||||
* @param ppvInterface Output pointer receiving the requested interface.
|
||||
* @return S_OK when the interface is supported; E_NOINTERFACE otherwise.
|
||||
*/
|
||||
HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, VOID **ppvInterface) {
|
||||
if (IID_IUnknown == riid) {
|
||||
AddRef();
|
||||
@@ -383,6 +483,14 @@ namespace platf::audio {
|
||||
}
|
||||
|
||||
// IMMNotificationClient
|
||||
/**
|
||||
* @brief Handle a Windows default-audio-device change notification.
|
||||
*
|
||||
* @param flow Audio endpoint data-flow direction.
|
||||
* @param role Audio endpoint role used for default-device lookup.
|
||||
* @param pwstrDeviceId Windows endpoint ID for the new default device.
|
||||
* @return S_OK after recording the render-device change notification.
|
||||
*/
|
||||
HRESULT STDMETHODCALLTYPE OnDefaultDeviceChanged(EDataFlow flow, ERole role, LPCWSTR pwstrDeviceId) {
|
||||
if (flow == eRender) {
|
||||
default_render_device_changed_flag.store(true);
|
||||
@@ -390,14 +498,33 @@ namespace platf::audio {
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Ignore endpoint-add notifications.
|
||||
*
|
||||
* @param pwstrDeviceId Windows endpoint ID for the added device.
|
||||
* @return S_OK because Sunshine does not act on this notification.
|
||||
*/
|
||||
HRESULT STDMETHODCALLTYPE OnDeviceAdded(LPCWSTR pwstrDeviceId) {
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Ignore endpoint-removal notifications.
|
||||
*
|
||||
* @param pwstrDeviceId Windows endpoint ID for the removed device.
|
||||
* @return S_OK because Sunshine does not act on this notification.
|
||||
*/
|
||||
HRESULT STDMETHODCALLTYPE OnDeviceRemoved(LPCWSTR pwstrDeviceId) {
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Handle Windows audio endpoint state changes.
|
||||
*
|
||||
* @param pwstrDeviceId Audio device ID.
|
||||
* @param dwNewState New device state.
|
||||
* @return COM status code.
|
||||
*/
|
||||
HRESULT STDMETHODCALLTYPE OnDeviceStateChanged(
|
||||
LPCWSTR pwstrDeviceId,
|
||||
DWORD dwNewState
|
||||
@@ -405,6 +532,13 @@ namespace platf::audio {
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Handle Windows audio endpoint property changes.
|
||||
*
|
||||
* @param pwstrDeviceId Audio device ID.
|
||||
* @param key Changed property key.
|
||||
* @return COM status code.
|
||||
*/
|
||||
HRESULT STDMETHODCALLTYPE OnPropertyValueChanged(
|
||||
LPCWSTR pwstrDeviceId,
|
||||
const PROPERTYKEY key
|
||||
@@ -424,8 +558,17 @@ namespace platf::audio {
|
||||
std::atomic_bool default_render_device_changed_flag;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief WASAPI microphone capture stream and endpoint notification state.
|
||||
*/
|
||||
class mic_wasapi_t: public mic_t {
|
||||
public:
|
||||
/**
|
||||
* @brief Deliver a captured audio sample to Sunshine's audio pipeline.
|
||||
*
|
||||
* @param sample_out Sample out.
|
||||
* @return Capture status reported to the streaming pipeline.
|
||||
*/
|
||||
capture_e sample(std::vector<float> &sample_out) override {
|
||||
auto sample_size = sample_out.size();
|
||||
|
||||
@@ -451,6 +594,15 @@ namespace platf::audio {
|
||||
return capture_e::ok;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Initialize WASAPI capture for the selected audio endpoint.
|
||||
*
|
||||
* @param sample_rate Audio sample rate in hertz.
|
||||
* @param frame_size Number of samples captured per audio frame.
|
||||
* @param channels_out Channels out.
|
||||
* @param continuous Whether silent audio should continue to be emitted.
|
||||
* @return 0 on success; nonzero or negative platform status on failure.
|
||||
*/
|
||||
int init(std::uint32_t sample_rate, std::uint32_t frame_size, std::uint32_t channels_out, bool continuous) {
|
||||
audio_event.reset(CreateEventA(nullptr, FALSE, FALSE, nullptr));
|
||||
if (!audio_event) {
|
||||
@@ -668,28 +820,36 @@ namespace platf::audio {
|
||||
}
|
||||
|
||||
public:
|
||||
handle_t audio_event;
|
||||
handle_t audio_event; ///< Event signaled by WASAPI when captured audio is available.
|
||||
|
||||
device_enum_t device_enum;
|
||||
device_t device;
|
||||
audio_client_t audio_client;
|
||||
audio_capture_t audio_capture;
|
||||
device_enum_t device_enum; ///< Device enum.
|
||||
device_t device; ///< WASAPI endpoint device selected for capture.
|
||||
audio_client_t audio_client; ///< WASAPI audio client configured for shared-mode capture.
|
||||
audio_capture_t audio_capture; ///< WASAPI capture client used to read sample packets.
|
||||
|
||||
audio_notification_t endpt_notification;
|
||||
std::optional<std::function<void()>> default_endpt_changed_cb;
|
||||
audio_notification_t endpt_notification; ///< Endpoint notification callback registered with Windows.
|
||||
std::optional<std::function<void()>> default_endpt_changed_cb; ///< Callback invoked when the default endpoint changes.
|
||||
|
||||
REFERENCE_TIME default_latency_ms;
|
||||
REFERENCE_TIME default_latency_ms; ///< WASAPI default device period used as capture latency.
|
||||
|
||||
util::buffer_t<float> sample_buf;
|
||||
float *sample_buf_pos;
|
||||
int channels;
|
||||
bool continuous_audio;
|
||||
util::buffer_t<float> sample_buf; ///< Floating-point sample buffer filled from WASAPI packets.
|
||||
float *sample_buf_pos; ///< Current write position in `sample_buf`.
|
||||
int channels; ///< Number of channels in the capture format.
|
||||
bool continuous_audio; ///< Whether audio packets continue during silence.
|
||||
|
||||
HANDLE mmcss_task_handle = nullptr;
|
||||
HANDLE mmcss_task_handle = nullptr; ///< MMCSS task handle for the audio capture thread.
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Platform audio controller that manages sinks and microphone capture.
|
||||
*/
|
||||
class audio_control_t: public ::platf::audio_control_t {
|
||||
public:
|
||||
/**
|
||||
* @brief Query host and virtual sink names available to Sunshine.
|
||||
*
|
||||
* @return Host and virtual sink names when the backend can report them.
|
||||
*/
|
||||
std::optional<sink_t> sink_info() override {
|
||||
sink_t sink;
|
||||
|
||||
@@ -767,6 +927,17 @@ namespace platf::audio {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Create a microphone capture stream for the requested layout.
|
||||
*
|
||||
* @param mapping Opus channel mapping table for the requested layout.
|
||||
* @param channels Number of audio channels in the stream.
|
||||
* @param sample_rate Audio sample rate in hertz.
|
||||
* @param frame_size Number of samples captured per audio frame.
|
||||
* @param continuous_audio Continuous audio.
|
||||
* @param host_audio_enabled Whether host playback should remain enabled during capture.
|
||||
* @return Microphone capture object for the requested audio layout.
|
||||
*/
|
||||
std::unique_ptr<mic_t> microphone(const std::uint8_t *mapping, int channels, std::uint32_t sample_rate, std::uint32_t frame_size, bool continuous_audio, [[maybe_unused]] bool host_audio_enabled) override {
|
||||
auto mic = std::make_unique<mic_wasapi_t>();
|
||||
|
||||
@@ -793,6 +964,8 @@ namespace platf::audio {
|
||||
* Any virtual sink detected will be prefixed by:
|
||||
* virtual-(format name)
|
||||
* If it doesn't contain that prefix, then the format will not be changed
|
||||
* @param sink Audio sink name to route or capture.
|
||||
* @return Status from updating format.
|
||||
*/
|
||||
std::optional<std::wstring> set_format(const std::string &sink) {
|
||||
if (sink.empty()) {
|
||||
@@ -854,6 +1027,12 @@ namespace platf::audio {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Update the sink value on the backend.
|
||||
*
|
||||
* @param sink Audio sink name to route or capture.
|
||||
* @return Status from updating sink.
|
||||
*/
|
||||
int set_sink(const std::string &sink) override {
|
||||
auto device_id = set_format(sink);
|
||||
if (!device_id) {
|
||||
@@ -884,6 +1063,9 @@ namespace platf::audio {
|
||||
return failure;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Enumerates supported match field options.
|
||||
*/
|
||||
enum class match_field_e {
|
||||
device_id, ///< Match device_id
|
||||
device_friendly_name, ///< Match endpoint friendly name
|
||||
@@ -891,15 +1073,32 @@ namespace platf::audio {
|
||||
device_description, ///< Match endpoint description
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief List of format fields used to compare audio formats.
|
||||
*/
|
||||
using match_fields_list_t = std::vector<std::pair<match_field_e, std::wstring>>;
|
||||
/**
|
||||
* @brief One matched audio-format field and its expected value.
|
||||
*/
|
||||
using matched_field_t = std::pair<match_field_e, std::wstring>;
|
||||
|
||||
/**
|
||||
* @brief Build matching fields for Steam Streaming Speakers.
|
||||
*
|
||||
* @return Field list used to identify Steam's virtual speaker endpoint.
|
||||
*/
|
||||
audio_control_t::match_fields_list_t match_steam_speakers() {
|
||||
return {
|
||||
{match_field_e::adapter_friendly_name, L"Steam Streaming Speakers"}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Build matching fields that all contain the same endpoint name.
|
||||
*
|
||||
* @param name Endpoint name or identifier to match across all fields.
|
||||
* @return Field list requiring every supported endpoint field to match the name.
|
||||
*/
|
||||
audio_control_t::match_fields_list_t match_all_fields(const std::wstring &name) {
|
||||
return {
|
||||
{match_field_e::device_id, name}, // {0.0.0.00000000}.{29dd7668-45b2-4846-882d-950f55bf7eb8}
|
||||
@@ -1116,6 +1315,11 @@ namespace platf::audio {
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Initialize Windows audio policy interfaces.
|
||||
*
|
||||
* @return 0 on success; nonzero or negative platform status on failure.
|
||||
*/
|
||||
int init() {
|
||||
auto status = CoCreateInstance(
|
||||
CLSID_CPolicyConfigClient,
|
||||
@@ -1147,12 +1351,15 @@ namespace platf::audio {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Destroy the Windows audio control.
|
||||
*/
|
||||
~audio_control_t() override {
|
||||
}
|
||||
|
||||
policy_t policy;
|
||||
audio::device_enum_t device_enum;
|
||||
std::string assigned_sink;
|
||||
policy_t policy; ///< Windows policy configuration interface used to switch default audio devices.
|
||||
audio::device_enum_t device_enum; ///< Device enumerator used to query and watch audio endpoints.
|
||||
std::string assigned_sink; ///< Virtual sink assigned while Sunshine captures host audio.
|
||||
};
|
||||
} // namespace platf::audio
|
||||
|
||||
@@ -1160,8 +1367,13 @@ namespace platf {
|
||||
|
||||
// It's not big enough to justify it's own source file :/
|
||||
namespace dxgi {
|
||||
/**
|
||||
* @brief Initialize the Windows audio-control backend.
|
||||
*
|
||||
* @return 0 on success; nonzero or negative platform status on failure.
|
||||
*/
|
||||
int init();
|
||||
}
|
||||
} // namespace dxgi
|
||||
|
||||
std::unique_ptr<audio_control_t> audio_control() {
|
||||
auto control = std::make_unique<audio::audio_control_t>();
|
||||
|
||||
+499
-66
@@ -24,69 +24,198 @@ namespace platf::dxgi {
|
||||
|
||||
// Add D3D11_CREATE_DEVICE_DEBUG here to enable the D3D11 debug runtime.
|
||||
// You should have a debugger like WinDbg attached to receive debug messages.
|
||||
auto constexpr D3D11_CREATE_DEVICE_FLAGS = 0;
|
||||
auto constexpr D3D11_CREATE_DEVICE_FLAGS = 0; ///< Protocol or platform constant for d3 d11 create device flags.
|
||||
|
||||
/**
|
||||
* @brief Release the COM or platform reference owned by the pointer.
|
||||
*
|
||||
* @param dxgi DXGI object whose COM reference should be released.
|
||||
*/
|
||||
template<class T>
|
||||
void Release(T *dxgi) {
|
||||
dxgi->Release();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Owning COM pointer for the DXGI factory used to enumerate adapters.
|
||||
*/
|
||||
using factory1_t = util::safe_ptr<IDXGIFactory1, Release<IDXGIFactory1>>;
|
||||
/**
|
||||
* @brief Owning COM pointer for a DXGI device interface.
|
||||
*/
|
||||
using dxgi_t = util::safe_ptr<IDXGIDevice, Release<IDXGIDevice>>;
|
||||
/**
|
||||
* @brief Owning COM pointer for a DXGI 1.1 device interface.
|
||||
*/
|
||||
using dxgi1_t = util::safe_ptr<IDXGIDevice1, Release<IDXGIDevice1>>;
|
||||
/**
|
||||
* @brief Owning COM pointer for the D3D11 device used by capture and conversion.
|
||||
*/
|
||||
using device_t = util::safe_ptr<ID3D11Device, Release<ID3D11Device>>;
|
||||
/**
|
||||
* @brief Owning COM pointer for the D3D11.1 device interface.
|
||||
*/
|
||||
using device1_t = util::safe_ptr<ID3D11Device1, Release<ID3D11Device1>>;
|
||||
/**
|
||||
* @brief Owning COM pointer for the immediate D3D11 device context.
|
||||
*/
|
||||
using device_ctx_t = util::safe_ptr<ID3D11DeviceContext, Release<ID3D11DeviceContext>>;
|
||||
/**
|
||||
* @brief Owning COM pointer for a DXGI display adapter.
|
||||
*/
|
||||
using adapter_t = util::safe_ptr<IDXGIAdapter1, Release<IDXGIAdapter1>>;
|
||||
/**
|
||||
* @brief Owning COM pointer for a DXGI output.
|
||||
*/
|
||||
using output_t = util::safe_ptr<IDXGIOutput, Release<IDXGIOutput>>;
|
||||
/**
|
||||
* @brief Owning COM pointer for a DXGI output that supports duplication.
|
||||
*/
|
||||
using output1_t = util::safe_ptr<IDXGIOutput1, Release<IDXGIOutput1>>;
|
||||
/**
|
||||
* @brief Owning COM pointer for a DXGI output with HDR metadata support.
|
||||
*/
|
||||
using output5_t = util::safe_ptr<IDXGIOutput5, Release<IDXGIOutput5>>;
|
||||
/**
|
||||
* @brief Owning COM pointer for a DXGI output with modern display descriptors.
|
||||
*/
|
||||
using output6_t = util::safe_ptr<IDXGIOutput6, Release<IDXGIOutput6>>;
|
||||
/**
|
||||
* @brief Owning COM pointer for the DXGI desktop duplication session.
|
||||
*/
|
||||
using dup_t = util::safe_ptr<IDXGIOutputDuplication, Release<IDXGIOutputDuplication>>;
|
||||
/**
|
||||
* @brief Owning COM pointer for a D3D11 2D texture.
|
||||
*/
|
||||
using texture2d_t = util::safe_ptr<ID3D11Texture2D, Release<ID3D11Texture2D>>;
|
||||
/**
|
||||
* @brief Owning COM pointer for a D3D11 1D texture.
|
||||
*/
|
||||
using texture1d_t = util::safe_ptr<ID3D11Texture1D, Release<ID3D11Texture1D>>;
|
||||
/**
|
||||
* @brief Owning COM pointer for the DXGI resource view of a shared texture.
|
||||
*/
|
||||
using resource_t = util::safe_ptr<IDXGIResource, Release<IDXGIResource>>;
|
||||
/**
|
||||
* @brief Owning COM pointer for the DXGI 1.1 resource sharing interface.
|
||||
*/
|
||||
using resource1_t = util::safe_ptr<IDXGIResource1, Release<IDXGIResource1>>;
|
||||
/**
|
||||
* @brief Owning COM pointer for the D3D11 multithread-protection interface.
|
||||
*/
|
||||
using multithread_t = util::safe_ptr<ID3D11Multithread, Release<ID3D11Multithread>>;
|
||||
/**
|
||||
* @brief Owning COM pointer for a D3D11 vertex shader.
|
||||
*/
|
||||
using vs_t = util::safe_ptr<ID3D11VertexShader, Release<ID3D11VertexShader>>;
|
||||
/**
|
||||
* @brief Owning COM pointer for a D3D11 pixel shader.
|
||||
*/
|
||||
using ps_t = util::safe_ptr<ID3D11PixelShader, Release<ID3D11PixelShader>>;
|
||||
/**
|
||||
* @brief Owning COM pointer for a D3D11 blend state.
|
||||
*/
|
||||
using blend_t = util::safe_ptr<ID3D11BlendState, Release<ID3D11BlendState>>;
|
||||
/**
|
||||
* @brief Owning COM pointer for a D3D11 input layout.
|
||||
*/
|
||||
using input_layout_t = util::safe_ptr<ID3D11InputLayout, Release<ID3D11InputLayout>>;
|
||||
/**
|
||||
* @brief Owning COM pointer for a D3D11 render-target view.
|
||||
*/
|
||||
using render_target_t = util::safe_ptr<ID3D11RenderTargetView, Release<ID3D11RenderTargetView>>;
|
||||
/**
|
||||
* @brief Owning COM pointer for a D3D11 shader-resource view.
|
||||
*/
|
||||
using shader_res_t = util::safe_ptr<ID3D11ShaderResourceView, Release<ID3D11ShaderResourceView>>;
|
||||
/**
|
||||
* @brief Owning COM pointer for a D3D11 buffer.
|
||||
*/
|
||||
using buf_t = util::safe_ptr<ID3D11Buffer, Release<ID3D11Buffer>>;
|
||||
/**
|
||||
* @brief D3D rasterizer state handle type.
|
||||
*/
|
||||
using raster_state_t = util::safe_ptr<ID3D11RasterizerState, Release<ID3D11RasterizerState>>;
|
||||
/**
|
||||
* @brief D3D sampler state handle type.
|
||||
*/
|
||||
using sampler_state_t = util::safe_ptr<ID3D11SamplerState, Release<ID3D11SamplerState>>;
|
||||
/**
|
||||
* @brief Owning COM pointer for a compiled shader bytecode blob.
|
||||
*/
|
||||
using blob_t = util::safe_ptr<ID3DBlob, Release<ID3DBlob>>;
|
||||
/**
|
||||
* @brief D3D depth-stencil state handle type.
|
||||
*/
|
||||
using depth_stencil_state_t = util::safe_ptr<ID3D11DepthStencilState, Release<ID3D11DepthStencilState>>;
|
||||
/**
|
||||
* @brief Owning COM pointer for a D3D11 depth-stencil view.
|
||||
*/
|
||||
using depth_stencil_view_t = util::safe_ptr<ID3D11DepthStencilView, Release<ID3D11DepthStencilView>>;
|
||||
/**
|
||||
* @brief Owning COM pointer for a keyed mutex on a shared DXGI resource.
|
||||
*/
|
||||
using keyed_mutex_t = util::safe_ptr<IDXGIKeyedMutex, Release<IDXGIKeyedMutex>>;
|
||||
|
||||
namespace video {
|
||||
/**
|
||||
* @brief Owning COM pointer for the D3D11 video device interface.
|
||||
*/
|
||||
using device_t = util::safe_ptr<ID3D11VideoDevice, Release<ID3D11VideoDevice>>;
|
||||
/**
|
||||
* @brief Owning COM pointer for the D3D11 video context interface.
|
||||
*/
|
||||
using ctx_t = util::safe_ptr<ID3D11VideoContext, Release<ID3D11VideoContext>>;
|
||||
/**
|
||||
* @brief Owning COM pointer for a D3D11 video processor.
|
||||
*/
|
||||
using processor_t = util::safe_ptr<ID3D11VideoProcessor, Release<ID3D11VideoProcessor>>;
|
||||
/**
|
||||
* @brief Owning COM pointer for a video processor output view.
|
||||
*/
|
||||
using processor_out_t = util::safe_ptr<ID3D11VideoProcessorOutputView, Release<ID3D11VideoProcessorOutputView>>;
|
||||
/**
|
||||
* @brief Owning COM pointer for a video processor input view.
|
||||
*/
|
||||
using processor_in_t = util::safe_ptr<ID3D11VideoProcessorInputView, Release<ID3D11VideoProcessorInputView>>;
|
||||
/**
|
||||
* @brief Owning COM pointer for a video processor capability enumerator.
|
||||
*/
|
||||
using processor_enum_t = util::safe_ptr<ID3D11VideoProcessorEnumerator, Release<ID3D11VideoProcessorEnumerator>>;
|
||||
} // namespace video
|
||||
|
||||
class hwdevice_t;
|
||||
|
||||
/**
|
||||
* @brief Cursor position and visibility for the current capture frame.
|
||||
*/
|
||||
struct cursor_t {
|
||||
std::vector<std::uint8_t> img_data;
|
||||
std::vector<std::uint8_t> img_data; ///< Raw pointer-shape bytes from DXGI output duplication.
|
||||
|
||||
DXGI_OUTDUPL_POINTER_SHAPE_INFO shape_info;
|
||||
int x;
|
||||
int y;
|
||||
bool visible;
|
||||
DXGI_OUTDUPL_POINTER_SHAPE_INFO shape_info; ///< Shape info.
|
||||
int x; ///< X.
|
||||
int y; ///< Y.
|
||||
bool visible; ///< Whether the cursor is visible.
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief GPU resources used to render a captured cursor shape.
|
||||
*/
|
||||
class gpu_cursor_t {
|
||||
public:
|
||||
gpu_cursor_t():
|
||||
cursor_view {0, 0, 0, 0, 0.0f, 1.0f} {};
|
||||
|
||||
/**
|
||||
* @brief Update the cursor position and display geometry used for rendering.
|
||||
*
|
||||
* @param topleft_x Cursor left edge in desktop coordinates.
|
||||
* @param topleft_y Cursor top edge in desktop coordinates.
|
||||
* @param display_width Captured display width in pixels.
|
||||
* @param display_height Captured display height in pixels.
|
||||
* @param display_rotation DXGI rotation applied to the captured display.
|
||||
* @param visible Whether the cursor should be visible in the frame.
|
||||
*/
|
||||
void set_pos(LONG topleft_x, LONG topleft_y, LONG display_width, LONG display_height, DXGI_MODE_ROTATION display_rotation, bool visible) {
|
||||
this->topleft_x = topleft_x;
|
||||
this->topleft_y = topleft_y;
|
||||
@@ -97,6 +226,13 @@ namespace platf::dxgi {
|
||||
update_viewport();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Replace the cursor texture and update its render viewport.
|
||||
*
|
||||
* @param texture_width Cursor texture width in pixels.
|
||||
* @param texture_height Cursor texture height in pixels.
|
||||
* @param texture D3D11 cursor texture to render.
|
||||
*/
|
||||
void set_texture(LONG texture_width, LONG texture_height, texture2d_t &&texture) {
|
||||
this->texture = std::move(texture);
|
||||
this->texture_width = texture_width;
|
||||
@@ -104,6 +240,9 @@ namespace platf::dxgi {
|
||||
update_viewport();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Recalculate the D3D viewport for the cursor texture.
|
||||
*/
|
||||
void update_viewport() {
|
||||
switch (display_rotation) {
|
||||
case DXGI_MODE_ROTATION_UNSPECIFIED:
|
||||
@@ -137,50 +276,63 @@ namespace platf::dxgi {
|
||||
}
|
||||
}
|
||||
|
||||
texture2d_t texture;
|
||||
LONG texture_width;
|
||||
LONG texture_height;
|
||||
texture2d_t texture; ///< D3D11 texture backing the captured frame.
|
||||
LONG texture_width; ///< Texture width.
|
||||
LONG texture_height; ///< Texture height.
|
||||
|
||||
LONG topleft_x;
|
||||
LONG topleft_y;
|
||||
LONG topleft_x; ///< Topleft x.
|
||||
LONG topleft_y; ///< Topleft y.
|
||||
|
||||
LONG display_width;
|
||||
LONG display_height;
|
||||
DXGI_MODE_ROTATION display_rotation;
|
||||
LONG display_width; ///< Display width.
|
||||
LONG display_height; ///< Display height.
|
||||
DXGI_MODE_ROTATION display_rotation; ///< Display rotation.
|
||||
|
||||
shader_res_t input_res;
|
||||
shader_res_t input_res; ///< Input res.
|
||||
|
||||
D3D11_VIEWPORT cursor_view;
|
||||
D3D11_VIEWPORT cursor_view; ///< Cursor view.
|
||||
|
||||
bool visible;
|
||||
bool visible; ///< Whether the cursor is visible.
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Shared D3D11/DXGI state used by Windows display capture backends.
|
||||
*/
|
||||
class display_base_t: public display_t {
|
||||
public:
|
||||
/**
|
||||
* @brief Initialize D3D11 desktop duplication for the selected output.
|
||||
*
|
||||
* @param config Configuration values to apply.
|
||||
* @param display_name Display name.
|
||||
* @return 0 on success; nonzero or negative platform status on failure.
|
||||
*/
|
||||
int init(const ::video::config_t &config, const std::string &display_name);
|
||||
|
||||
capture_e capture(const push_captured_image_cb_t &push_captured_image_cb, const pull_free_image_cb_t &pull_free_image_cb, bool *cursor) override;
|
||||
|
||||
factory1_t factory;
|
||||
adapter_t adapter;
|
||||
output_t output;
|
||||
device_t device;
|
||||
device_ctx_t device_ctx;
|
||||
DXGI_RATIONAL display_refresh_rate;
|
||||
int display_refresh_rate_rounded;
|
||||
factory1_t factory; ///< DXGI factory used to enumerate adapters and outputs.
|
||||
adapter_t adapter; ///< DXGI adapter containing the selected output.
|
||||
output_t output; ///< DXGI output duplicated for capture.
|
||||
device_t device; ///< D3D11 device used for desktop duplication capture.
|
||||
device_ctx_t device_ctx; ///< D3D11 device context used for copy and render operations.
|
||||
DXGI_RATIONAL display_refresh_rate; ///< Display refresh rate.
|
||||
int display_refresh_rate_rounded; ///< Display refresh rate rounded.
|
||||
|
||||
DXGI_MODE_ROTATION display_rotation = DXGI_MODE_ROTATION_UNSPECIFIED;
|
||||
int width_before_rotation;
|
||||
int height_before_rotation;
|
||||
DXGI_MODE_ROTATION display_rotation = DXGI_MODE_ROTATION_UNSPECIFIED; ///< Display rotation.
|
||||
int width_before_rotation; ///< Width before rotation.
|
||||
int height_before_rotation; ///< Height before rotation.
|
||||
|
||||
int client_frame_rate;
|
||||
DXGI_RATIONAL client_frame_rate_strict;
|
||||
int client_frame_rate; ///< Client frame rate.
|
||||
DXGI_RATIONAL client_frame_rate_strict; ///< Client frame rate strict.
|
||||
|
||||
DXGI_FORMAT capture_format;
|
||||
D3D_FEATURE_LEVEL feature_level;
|
||||
DXGI_FORMAT capture_format; ///< Capture format.
|
||||
D3D_FEATURE_LEVEL feature_level; ///< Feature level.
|
||||
|
||||
std::unique_ptr<high_precision_timer> timer = create_high_precision_timer();
|
||||
std::unique_ptr<high_precision_timer> timer = create_high_precision_timer(); ///< Timer.
|
||||
|
||||
/**
|
||||
* @brief Enumerates supported d3 DKMT SCHEDULINGPRIORITYCLASS options.
|
||||
*/
|
||||
typedef enum _D3DKMT_SCHEDULINGPRIORITYCLASS {
|
||||
D3DKMT_SCHEDULINGPRIORITYCLASS_IDLE, ///< Idle priority class
|
||||
D3DKMT_SCHEDULINGPRIORITYCLASS_BELOW_NORMAL, ///< Below normal priority class
|
||||
@@ -188,15 +340,24 @@ namespace platf::dxgi {
|
||||
D3DKMT_SCHEDULINGPRIORITYCLASS_ABOVE_NORMAL, ///< Above normal priority class
|
||||
D3DKMT_SCHEDULINGPRIORITYCLASS_HIGH, ///< High priority class
|
||||
D3DKMT_SCHEDULINGPRIORITYCLASS_REALTIME ///< Realtime priority class
|
||||
} D3DKMT_SCHEDULINGPRIORITYCLASS;
|
||||
} D3DKMT_SCHEDULINGPRIORITYCLASS; ///< Alias for D3 DKMT SCHEDULINGPRIORITYCLASS.
|
||||
|
||||
/**
|
||||
* @brief Kernel-mode D3DKMT adapter handle.
|
||||
*/
|
||||
typedef UINT D3DKMT_HANDLE;
|
||||
|
||||
/**
|
||||
* @brief Win32 D3DKMT adapter-open request structure.
|
||||
*/
|
||||
typedef struct _D3DKMT_OPENADAPTERFROMLUID {
|
||||
LUID AdapterLuid;
|
||||
D3DKMT_HANDLE hAdapter;
|
||||
} D3DKMT_OPENADAPTERFROMLUID;
|
||||
LUID AdapterLuid; ///< Adapter luid.
|
||||
D3DKMT_HANDLE hAdapter; ///< D3DKMT adapter handle used for low-level display queries.
|
||||
} D3DKMT_OPENADAPTERFROMLUID; ///< Alias for D3 DKMT OPENADAPTERFROMLUID.
|
||||
|
||||
/**
|
||||
* @brief Win32 D3DKMT WDDM 2.7 capability flags.
|
||||
*/
|
||||
typedef struct _D3DKMT_WDDM_2_7_CAPS {
|
||||
union {
|
||||
struct
|
||||
@@ -210,40 +371,112 @@ namespace platf::dxgi {
|
||||
|
||||
UINT Value;
|
||||
};
|
||||
} D3DKMT_WDDM_2_7_CAPS;
|
||||
} D3DKMT_WDDM_2_7_CAPS; ///< Alias for D3 DKMT WDDM 2 7 CAPS.
|
||||
|
||||
/**
|
||||
* @brief Win32 D3DKMT adapter information query.
|
||||
*/
|
||||
typedef struct _D3DKMT_QUERYADAPTERINFO {
|
||||
D3DKMT_HANDLE hAdapter;
|
||||
UINT Type;
|
||||
VOID *pPrivateDriverData;
|
||||
UINT PrivateDriverDataSize;
|
||||
} D3DKMT_QUERYADAPTERINFO;
|
||||
D3DKMT_HANDLE hAdapter; ///< D3DKMT adapter handle used for low-level display queries.
|
||||
UINT Type; ///< Type.
|
||||
VOID *pPrivateDriverData; ///< P private driver data.
|
||||
UINT PrivateDriverDataSize; ///< Private driver data size.
|
||||
} D3DKMT_QUERYADAPTERINFO; ///< Alias for D3 DKMT QUERYADAPTERINFO.
|
||||
|
||||
const UINT KMTQAITYPE_WDDM_2_7_CAPS = 70;
|
||||
const UINT KMTQAITYPE_WDDM_2_7_CAPS = 70; ///< Protocol or platform constant for kmtqaitype wddm 2 7 caps.
|
||||
|
||||
/**
|
||||
* @brief Win32 D3DKMT adapter-close request structure.
|
||||
*/
|
||||
typedef struct _D3DKMT_CLOSEADAPTER {
|
||||
D3DKMT_HANDLE hAdapter;
|
||||
} D3DKMT_CLOSEADAPTER;
|
||||
D3DKMT_HANDLE hAdapter; ///< D3DKMT adapter handle used for low-level display queries.
|
||||
} D3DKMT_CLOSEADAPTER; ///< Alias for D3 DKMT CLOSEADAPTER.
|
||||
|
||||
/**
|
||||
* @brief Function pointer for setting D3DKMT process scheduling priority.
|
||||
*/
|
||||
typedef NTSTATUS(WINAPI *PD3DKMTSetProcessSchedulingPriorityClass)(HANDLE, D3DKMT_SCHEDULINGPRIORITYCLASS);
|
||||
/**
|
||||
* @brief Function pointer for opening a D3DKMT adapter by LUID.
|
||||
*/
|
||||
typedef NTSTATUS(WINAPI *PD3DKMTOpenAdapterFromLuid)(D3DKMT_OPENADAPTERFROMLUID *);
|
||||
/**
|
||||
* @brief Function pointer for querying D3DKMT adapter information.
|
||||
*/
|
||||
typedef NTSTATUS(WINAPI *PD3DKMTQueryAdapterInfo)(D3DKMT_QUERYADAPTERINFO *);
|
||||
/**
|
||||
* @brief Function pointer for closing a D3DKMT adapter handle.
|
||||
*/
|
||||
typedef NTSTATUS(WINAPI *PD3DKMTCloseAdapter)(D3DKMT_CLOSEADAPTER *);
|
||||
|
||||
/**
|
||||
* @brief Report whether the active display mode is HDR.
|
||||
*
|
||||
* @return True when the active display mode is HDR.
|
||||
*/
|
||||
virtual bool is_hdr() override;
|
||||
/**
|
||||
* @brief Read HDR metadata for the active display mode.
|
||||
*
|
||||
* @param metadata Output structure populated with HDR metadata.
|
||||
* @return True when HDR metadata was written to `metadata`.
|
||||
*/
|
||||
virtual bool get_hdr_metadata(SS_HDR_METADATA &metadata) override;
|
||||
|
||||
/**
|
||||
* @brief Convert a DXGI format enum to a diagnostic string.
|
||||
*
|
||||
* @param format Pixel, audio, or protocol format being converted.
|
||||
* @return Static string describing the DXGI format.
|
||||
*/
|
||||
const char *dxgi_format_to_string(DXGI_FORMAT format);
|
||||
/**
|
||||
* @brief Convert a DXGI colorspace enum to a diagnostic string.
|
||||
*
|
||||
* @param type DXGI colorspace value reported by the output.
|
||||
* @return Static string describing the colorspace.
|
||||
*/
|
||||
const char *colorspace_to_string(DXGI_COLOR_SPACE_TYPE type);
|
||||
/**
|
||||
* @brief Get supported capture formats.
|
||||
*
|
||||
* @return Capture formats supported by this display backend.
|
||||
*/
|
||||
virtual std::vector<DXGI_FORMAT> get_supported_capture_formats() = 0;
|
||||
|
||||
protected:
|
||||
/**
|
||||
* @brief Get pixel pitch.
|
||||
*
|
||||
* @return Bytes per pixel for the active capture format.
|
||||
*/
|
||||
int get_pixel_pitch() {
|
||||
return (capture_format == DXGI_FORMAT_R16G16B16A16_FLOAT) ? 8 : 4;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Capture a display frame into the provided image object.
|
||||
*
|
||||
* @param pull_free_image_cb Callback that provides an available image buffer.
|
||||
* @param img_out Captured image buffer returned to the streaming pipeline.
|
||||
* @param timeout Maximum time to wait for the operation.
|
||||
* @param cursor_visible Cursor visible.
|
||||
* @return Capture status reported to the streaming pipeline.
|
||||
*/
|
||||
virtual capture_e snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr<platf::img_t> &img_out, std::chrono::milliseconds timeout, bool cursor_visible) = 0;
|
||||
/**
|
||||
* @brief Release resources associated with the last captured snapshot.
|
||||
*
|
||||
* @return Capture status after releasing the current snapshot.
|
||||
*/
|
||||
virtual capture_e release_snapshot() = 0;
|
||||
/**
|
||||
* @brief Finish cursor composition into a RAM-backed image.
|
||||
*
|
||||
* @param img Image or frame object to read from or populate.
|
||||
* @param dummy Unused placeholder required by the interface signature.
|
||||
* @return Capture status after finalizing the captured image.
|
||||
*/
|
||||
virtual int complete_img(img_t *img, bool dummy) = 0;
|
||||
};
|
||||
|
||||
@@ -252,15 +485,44 @@ namespace platf::dxgi {
|
||||
*/
|
||||
class display_ram_t: public display_base_t {
|
||||
public:
|
||||
/**
|
||||
* @brief Allocate an image buffer compatible with this display backend.
|
||||
*
|
||||
* @return Allocated img object, or null when unavailable.
|
||||
*/
|
||||
std::shared_ptr<img_t> alloc_img() override;
|
||||
/**
|
||||
* @brief Populate a fallback image when real capture data is unavailable.
|
||||
*
|
||||
* @param img Image or frame object to read from or populate.
|
||||
* @return Capture status reported to the streaming pipeline.
|
||||
*/
|
||||
int dummy_img(img_t *img) override;
|
||||
/**
|
||||
* @brief Finish cursor composition into a GPU-backed image.
|
||||
*
|
||||
* @param img Image or frame object to read from or populate.
|
||||
* @param dummy Unused placeholder required by the interface signature.
|
||||
* @return Capture status after finalizing the captured image.
|
||||
*/
|
||||
int complete_img(img_t *img, bool dummy) override;
|
||||
/**
|
||||
* @brief Get supported capture formats.
|
||||
*
|
||||
* @return Capture formats supported by this display backend.
|
||||
*/
|
||||
std::vector<DXGI_FORMAT> get_supported_capture_formats() override;
|
||||
|
||||
/**
|
||||
* @brief Create AVCodec encode device.
|
||||
*
|
||||
* @param pix_fmt Sunshine pixel format to convert or allocate for.
|
||||
* @return Constructed AVCodec encode device object.
|
||||
*/
|
||||
std::unique_ptr<avcodec_encode_device_t> make_avcodec_encode_device(pix_fmt_e pix_fmt) override;
|
||||
|
||||
D3D11_MAPPED_SUBRESOURCE img_info;
|
||||
texture2d_t texture;
|
||||
D3D11_MAPPED_SUBRESOURCE img_info; ///< CPU mapping information for the captured texture.
|
||||
texture2d_t texture; ///< D3D11 texture backing the captured frame.
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -268,18 +530,53 @@ namespace platf::dxgi {
|
||||
*/
|
||||
class display_vram_t: public display_base_t, public std::enable_shared_from_this<display_vram_t> {
|
||||
public:
|
||||
/**
|
||||
* @brief Allocate an image buffer compatible with this display backend.
|
||||
*
|
||||
* @return Allocated img object, or null when unavailable.
|
||||
*/
|
||||
std::shared_ptr<img_t> alloc_img() override;
|
||||
/**
|
||||
* @brief Populate a fallback image when real capture data is unavailable.
|
||||
*
|
||||
* @param img_base Image buffer populated with a fallback frame.
|
||||
* @return Capture status reported to the streaming pipeline.
|
||||
*/
|
||||
int dummy_img(img_t *img_base) override;
|
||||
/**
|
||||
* @brief Finalize a VRAM image after capture or dummy-frame generation.
|
||||
*
|
||||
* @param img_base Image buffer whose D3D resources are finalized.
|
||||
* @param dummy Unused placeholder required by the interface signature.
|
||||
* @return Capture status after finalizing the captured image.
|
||||
*/
|
||||
int complete_img(img_t *img_base, bool dummy) override;
|
||||
/**
|
||||
* @brief Get supported capture formats.
|
||||
*
|
||||
* @return Capture formats supported by this display backend.
|
||||
*/
|
||||
std::vector<DXGI_FORMAT> get_supported_capture_formats() override;
|
||||
|
||||
bool is_codec_supported(std::string_view name, const ::video::config_t &config) override;
|
||||
|
||||
/**
|
||||
* @brief Create AVCodec encode device.
|
||||
*
|
||||
* @param pix_fmt Sunshine pixel format to convert or allocate for.
|
||||
* @return Constructed AVCodec encode device object.
|
||||
*/
|
||||
std::unique_ptr<avcodec_encode_device_t> make_avcodec_encode_device(pix_fmt_e pix_fmt) override;
|
||||
|
||||
/**
|
||||
* @brief Create NVENC encode device.
|
||||
*
|
||||
* @param pix_fmt Sunshine pixel format to convert or allocate for.
|
||||
* @return Constructed NVENC encode device object.
|
||||
*/
|
||||
std::unique_ptr<nvenc_encode_device_t> make_nvenc_encode_device(pix_fmt_e pix_fmt) override;
|
||||
|
||||
std::atomic<uint32_t> next_image_id;
|
||||
std::atomic<uint32_t> next_image_id; ///< Next image ID.
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -287,13 +584,39 @@ namespace platf::dxgi {
|
||||
*/
|
||||
class duplication_t {
|
||||
public:
|
||||
dup_t dup;
|
||||
bool has_frame {};
|
||||
std::chrono::steady_clock::time_point last_protected_content_warning_time {};
|
||||
dup_t dup; ///< Desktop Duplication capture session.
|
||||
bool has_frame {}; ///< Whether a frame is currently held by the duplication object.
|
||||
std::chrono::steady_clock::time_point last_protected_content_warning_time {}; ///< Last protected content warning time.
|
||||
|
||||
/**
|
||||
* @brief Initialize WGC capture for the selected display.
|
||||
*
|
||||
* @param display Display object or identifier associated with the operation.
|
||||
* @param config Configuration values to apply.
|
||||
* @return 0 on success; nonzero or negative platform status on failure.
|
||||
*/
|
||||
int init(display_base_t *display, const ::video::config_t &config);
|
||||
/**
|
||||
* @brief Acquire the next frame from the Windows capture backend.
|
||||
*
|
||||
* @param frame_info Frame info.
|
||||
* @param timeout Maximum time to wait for the operation.
|
||||
* @param res_p Res p.
|
||||
* @return Capture status for the frame acquisition attempt.
|
||||
*/
|
||||
capture_e next_frame(DXGI_OUTDUPL_FRAME_INFO &frame_info, std::chrono::milliseconds timeout, resource_t::pointer *res_p);
|
||||
/**
|
||||
* @brief Reset the object to its initial empty state.
|
||||
*
|
||||
* @param dup_p Dup p.
|
||||
* @return Reset status.
|
||||
*/
|
||||
capture_e reset(dup_t::pointer dup_p = dup_t::pointer());
|
||||
/**
|
||||
* @brief Release resources associated with frame.
|
||||
*
|
||||
* @return Capture status after releasing the current frame.
|
||||
*/
|
||||
capture_e release_frame();
|
||||
|
||||
~duplication_t();
|
||||
@@ -304,12 +627,33 @@ namespace platf::dxgi {
|
||||
*/
|
||||
class display_ddup_ram_t: public display_ram_t {
|
||||
public:
|
||||
/**
|
||||
* @brief Initialize shared D3D capture resources for a RAM frame.
|
||||
*
|
||||
* @param config Configuration values to apply.
|
||||
* @param display_name Display name.
|
||||
* @return 0 on success; nonzero or negative platform status on failure.
|
||||
*/
|
||||
int init(const ::video::config_t &config, const std::string &display_name);
|
||||
/**
|
||||
* @brief Capture a display frame into the provided image object.
|
||||
*
|
||||
* @param pull_free_image_cb Callback that provides an available image buffer.
|
||||
* @param img_out Captured image buffer returned to the streaming pipeline.
|
||||
* @param timeout Maximum time to wait for the operation.
|
||||
* @param cursor_visible Cursor visible.
|
||||
* @return Capture status reported to the streaming pipeline.
|
||||
*/
|
||||
capture_e snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr<platf::img_t> &img_out, std::chrono::milliseconds timeout, bool cursor_visible) override;
|
||||
/**
|
||||
* @brief Release resources associated with the last captured snapshot.
|
||||
*
|
||||
* @return Capture status after releasing the current snapshot.
|
||||
*/
|
||||
capture_e release_snapshot() override;
|
||||
|
||||
duplication_t dup;
|
||||
cursor_t cursor;
|
||||
duplication_t dup; ///< Desktop Duplication session used to acquire frames.
|
||||
cursor_t cursor; ///< Cursor.
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -317,26 +661,47 @@ namespace platf::dxgi {
|
||||
*/
|
||||
class display_ddup_vram_t: public display_vram_t {
|
||||
public:
|
||||
/**
|
||||
* @brief Initialize D3D cursor rendering resources for RAM capture.
|
||||
*
|
||||
* @param config Configuration values to apply.
|
||||
* @param display_name Display name.
|
||||
* @return 0 on success; nonzero or negative platform status on failure.
|
||||
*/
|
||||
int init(const ::video::config_t &config, const std::string &display_name);
|
||||
/**
|
||||
* @brief Capture a display frame into the provided image object.
|
||||
*
|
||||
* @param pull_free_image_cb Callback that provides an available image buffer.
|
||||
* @param img_out Captured image buffer returned to the streaming pipeline.
|
||||
* @param timeout Maximum time to wait for the operation.
|
||||
* @param cursor_visible Cursor visible.
|
||||
* @return Capture status reported to the streaming pipeline.
|
||||
*/
|
||||
capture_e snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr<platf::img_t> &img_out, std::chrono::milliseconds timeout, bool cursor_visible) override;
|
||||
/**
|
||||
* @brief Release resources associated with the last captured snapshot.
|
||||
*
|
||||
* @return Capture status after releasing the current snapshot.
|
||||
*/
|
||||
capture_e release_snapshot() override;
|
||||
|
||||
duplication_t dup;
|
||||
sampler_state_t sampler_linear;
|
||||
duplication_t dup; ///< Desktop Duplication session used to acquire frames.
|
||||
sampler_state_t sampler_linear; ///< Sampler linear.
|
||||
|
||||
blend_t blend_alpha;
|
||||
blend_t blend_invert;
|
||||
blend_t blend_disable;
|
||||
blend_t blend_alpha; ///< Blend alpha.
|
||||
blend_t blend_invert; ///< Blend invert.
|
||||
blend_t blend_disable; ///< Blend disable.
|
||||
|
||||
ps_t cursor_ps;
|
||||
vs_t cursor_vs;
|
||||
ps_t cursor_ps; ///< Cursor ps.
|
||||
vs_t cursor_vs; ///< Cursor vs.
|
||||
|
||||
gpu_cursor_t cursor_alpha;
|
||||
gpu_cursor_t cursor_xor;
|
||||
gpu_cursor_t cursor_alpha; ///< Cursor alpha.
|
||||
gpu_cursor_t cursor_xor; ///< Cursor xor.
|
||||
|
||||
texture2d_t old_surface_delayed_destruction;
|
||||
std::chrono::steady_clock::time_point old_surface_timestamp;
|
||||
std::variant<std::monostate, texture2d_t, std::shared_ptr<platf::img_t>> last_frame_variant;
|
||||
texture2d_t old_surface_delayed_destruction; ///< Old surface delayed destruction.
|
||||
std::chrono::steady_clock::time_point old_surface_timestamp; ///< Old surface timestamp.
|
||||
std::variant<std::monostate, texture2d_t, std::shared_ptr<platf::img_t>> last_frame_variant; ///< Last frame variant.
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -358,10 +723,36 @@ namespace platf::dxgi {
|
||||
wgc_capture_t();
|
||||
~wgc_capture_t();
|
||||
|
||||
/**
|
||||
* @brief Initialize D3D cursor rendering resources for GPU capture.
|
||||
*
|
||||
* @param display Display object or identifier associated with the operation.
|
||||
* @param config Configuration values to apply.
|
||||
* @return 0 on success; nonzero or negative platform status on failure.
|
||||
*/
|
||||
int init(display_base_t *display, const ::video::config_t &config);
|
||||
/**
|
||||
* @brief Acquire the next frame from the Windows capture backend.
|
||||
*
|
||||
* @param timeout Maximum time to wait for the operation.
|
||||
* @param out Output object populated by the operation.
|
||||
* @param out_time QPC timestamp associated with the acquired frame.
|
||||
* @return Capture status for the frame acquisition attempt.
|
||||
*/
|
||||
capture_e next_frame(std::chrono::milliseconds timeout, ID3D11Texture2D **out, uint64_t &out_time);
|
||||
/**
|
||||
* @brief Release resources associated with frame.
|
||||
*
|
||||
* @return Capture status after releasing the current frame.
|
||||
*/
|
||||
capture_e release_frame();
|
||||
int set_cursor_visible(bool);
|
||||
/**
|
||||
* @brief Enable or disable cursor composition in Windows.Graphics.Capture frames.
|
||||
*
|
||||
* @param cursor_visible Whether the cursor should be included in captured frames.
|
||||
* @return Zero when the cursor visibility state was accepted by the capture session.
|
||||
*/
|
||||
int set_cursor_visible(bool cursor_visible);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -371,8 +762,29 @@ namespace platf::dxgi {
|
||||
wgc_capture_t dup;
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Initialize shared D3D capture resources for a GPU frame.
|
||||
*
|
||||
* @param config Configuration values to apply.
|
||||
* @param display_name Display name.
|
||||
* @return 0 on success; nonzero or negative platform status on failure.
|
||||
*/
|
||||
int init(const ::video::config_t &config, const std::string &display_name);
|
||||
/**
|
||||
* @brief Capture a display frame into the provided image object.
|
||||
*
|
||||
* @param pull_free_image_cb Callback that provides an available image buffer.
|
||||
* @param img_out Captured image buffer returned to the streaming pipeline.
|
||||
* @param timeout Maximum time to wait for the operation.
|
||||
* @param cursor_visible Cursor visible.
|
||||
* @return Capture status reported to the streaming pipeline.
|
||||
*/
|
||||
capture_e snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr<platf::img_t> &img_out, std::chrono::milliseconds timeout, bool cursor_visible) override;
|
||||
/**
|
||||
* @brief Release resources associated with the last captured snapshot.
|
||||
*
|
||||
* @return Capture status after releasing the current snapshot.
|
||||
*/
|
||||
capture_e release_snapshot() override;
|
||||
};
|
||||
|
||||
@@ -383,8 +795,29 @@ namespace platf::dxgi {
|
||||
wgc_capture_t dup;
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Initialize Windows Graphics Capture frame-pool resources.
|
||||
*
|
||||
* @param config Configuration values to apply.
|
||||
* @param display_name Display name.
|
||||
* @return 0 on success; nonzero or negative platform status on failure.
|
||||
*/
|
||||
int init(const ::video::config_t &config, const std::string &display_name);
|
||||
/**
|
||||
* @brief Capture a display frame into the provided image object.
|
||||
*
|
||||
* @param pull_free_image_cb Callback that provides an available image buffer.
|
||||
* @param img_out Captured image buffer returned to the streaming pipeline.
|
||||
* @param timeout Maximum time to wait for the operation.
|
||||
* @param cursor_visible Cursor visible.
|
||||
* @return Capture status reported to the streaming pipeline.
|
||||
*/
|
||||
capture_e snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr<platf::img_t> &img_out, std::chrono::milliseconds timeout, bool cursor_visible) override;
|
||||
/**
|
||||
* @brief Release resources associated with the last captured snapshot.
|
||||
*
|
||||
* @return Capture status after releasing the current snapshot.
|
||||
*/
|
||||
capture_e release_snapshot() override;
|
||||
};
|
||||
} // namespace platf::dxgi
|
||||
|
||||
@@ -19,9 +19,15 @@
|
||||
|
||||
// We have to include boost/process/v1.hpp before display.h due to WinSock.h,
|
||||
// but that prevents the definition of NTSTATUS so we must define it ourself.
|
||||
/**
|
||||
* @brief Windows NT status code returned by native APIs.
|
||||
*/
|
||||
typedef long NTSTATUS;
|
||||
|
||||
// Definition from the WDK's d3dkmthk.h
|
||||
/**
|
||||
* @brief Enumerates supported d3 DKMT GPU PREFERENCE QUERY STATE options.
|
||||
*/
|
||||
typedef enum _D3DKMT_GPU_PREFERENCE_QUERY_STATE: DWORD {
|
||||
D3DKMT_GPU_PREFERENCE_STATE_UNINITIALIZED, ///< The GPU preference isn't initialized.
|
||||
D3DKMT_GPU_PREFERENCE_STATE_HIGH_PERFORMANCE, ///< The highest performing GPU is preferred.
|
||||
@@ -29,7 +35,7 @@ typedef enum _D3DKMT_GPU_PREFERENCE_QUERY_STATE: DWORD {
|
||||
D3DKMT_GPU_PREFERENCE_STATE_UNSPECIFIED, ///< A GPU preference isn't specified.
|
||||
D3DKMT_GPU_PREFERENCE_STATE_NOT_FOUND, ///< A GPU preference isn't found.
|
||||
D3DKMT_GPU_PREFERENCE_STATE_USER_SPECIFIED_GPU ///< A specific GPU is preferred.
|
||||
} D3DKMT_GPU_PREFERENCE_QUERY_STATE;
|
||||
} D3DKMT_GPU_PREFERENCE_QUERY_STATE; ///< Alias for D3 DKMT GPU PREFERENCE QUERY STATE.
|
||||
|
||||
#include "display.h"
|
||||
#include "misc.h"
|
||||
@@ -354,6 +360,8 @@ namespace platf::dxgi {
|
||||
* @param adapter The DXGI adapter to use for capture.
|
||||
* @param output The DXGI output to capture.
|
||||
* @param enumeration_only Specifies whether this test is occurring for display enumeration.
|
||||
*
|
||||
* @return True when Desktop Duplication can capture the requested output.
|
||||
*/
|
||||
bool test_dxgi_duplication(adapter_t &adapter, output_t &output, bool enumeration_only) {
|
||||
D3D_FEATURE_LEVEL featureLevels[] {
|
||||
@@ -819,6 +827,9 @@ namespace platf::dxgi {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Names for DXGI_FORMAT values used in diagnostic logging.
|
||||
*/
|
||||
const char *format_str[] = {
|
||||
"DXGI_FORMAT_UNKNOWN",
|
||||
"DXGI_FORMAT_R32G32B32A32_TYPELESS",
|
||||
@@ -1109,7 +1120,8 @@ namespace platf {
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns if GPUs/drivers have changed since the last call to this function.
|
||||
* @brief Check whether DXGI reports that adapter or driver enumeration is stale.
|
||||
*
|
||||
* @return `true` if a change has occurred or if it is unknown whether a change occurred.
|
||||
*/
|
||||
bool needs_encoder_reenumeration() {
|
||||
|
||||
@@ -12,13 +12,25 @@ namespace platf {
|
||||
}
|
||||
|
||||
namespace platf::dxgi {
|
||||
/**
|
||||
* @brief Captured frame buffer shared between capture and encode stages.
|
||||
*/
|
||||
struct img_t: public ::platf::img_t {
|
||||
/**
|
||||
* @brief Destroy the RAM-backed DXGI image.
|
||||
*/
|
||||
~img_t() override {
|
||||
delete[] data;
|
||||
data = nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Blend a monochrome cursor shape into a RAM frame.
|
||||
*
|
||||
* @param cursor Cursor image or visibility state to composite.
|
||||
* @param img Image or frame object to read from or populate.
|
||||
*/
|
||||
void blend_cursor_monochrome(const cursor_t &cursor, img_t &img) {
|
||||
int height = cursor.shape_info.Height / 2;
|
||||
int width = cursor.shape_info.Width;
|
||||
@@ -81,6 +93,12 @@ namespace platf::dxgi {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Alpha-blend a color cursor pixel into a captured RAM frame.
|
||||
*
|
||||
* @param img_pixel_p Pixel in the destination frame.
|
||||
* @param cursor_pixel ARGB cursor pixel to blend.
|
||||
*/
|
||||
void apply_color_alpha(int *img_pixel_p, int cursor_pixel) {
|
||||
auto colors_out = (std::uint8_t *) &cursor_pixel;
|
||||
auto colors_in = (std::uint8_t *) img_pixel_p;
|
||||
@@ -96,6 +114,12 @@ namespace platf::dxgi {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Apply a masked-color cursor pixel into a captured RAM frame.
|
||||
*
|
||||
* @param img_pixel_p Pixel in the destination frame.
|
||||
* @param cursor_pixel ARGB cursor pixel with masked-cursor semantics.
|
||||
*/
|
||||
void apply_color_masked(int *img_pixel_p, int cursor_pixel) {
|
||||
// TODO: When use of IDXGIOutput5 is implemented, support different color formats
|
||||
auto alpha = ((std::uint8_t *) &cursor_pixel)[3];
|
||||
@@ -106,6 +130,13 @@ namespace platf::dxgi {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Blend a color cursor shape into a RAM frame.
|
||||
*
|
||||
* @param cursor Cursor image or visibility state to composite.
|
||||
* @param img Image or frame object to read from or populate.
|
||||
* @param masked Whether the cursor image uses an AND/XOR mask.
|
||||
*/
|
||||
void blend_cursor_color(const cursor_t &cursor, img_t &img, const bool masked) {
|
||||
int height = cursor.shape_info.Height;
|
||||
int width = cursor.shape_info.Width;
|
||||
@@ -152,6 +183,12 @@ namespace platf::dxgi {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Blend the current cursor image into a RAM frame.
|
||||
*
|
||||
* @param cursor Cursor image or visibility state to composite.
|
||||
* @param img Image or frame object to read from or populate.
|
||||
*/
|
||||
void blend_cursor(const cursor_t &cursor, img_t &img) {
|
||||
switch (cursor.shape_info.Type) {
|
||||
case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_COLOR:
|
||||
|
||||
@@ -31,6 +31,10 @@ extern "C" {
|
||||
#include "utf_utils.h"
|
||||
|
||||
#if !defined(SUNSHINE_SHADERS_DIR) // for testing this needs to be defined in cmake as we don't do an install
|
||||
/**
|
||||
* @def SUNSHINE_SHADERS_DIR
|
||||
* @brief Macro for SUNSHINE SHADERS DIR.
|
||||
*/
|
||||
#define SUNSHINE_SHADERS_DIR SUNSHINE_ASSETS_DIR "/shaders/directx"
|
||||
#endif
|
||||
namespace platf {
|
||||
@@ -41,10 +45,20 @@ static void free_frame(AVFrame *frame) {
|
||||
av_frame_free(&frame);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief FFmpeg hardware frame pointer released with `av_frame_free`.
|
||||
*/
|
||||
using frame_t = util::safe_ptr<AVFrame, free_frame>;
|
||||
|
||||
namespace platf::dxgi {
|
||||
|
||||
/**
|
||||
* @brief Create a buffer object or message.
|
||||
*
|
||||
* @param device D3D, audio, or platform device used by the operation.
|
||||
* @param t Initial value used to populate the GPU buffer.
|
||||
* @return Constructed buffer object.
|
||||
*/
|
||||
template<class T>
|
||||
buf_t make_buffer(device_t::pointer device, const T &t) {
|
||||
static_assert(sizeof(T) % 16 == 0, "Buffer needs to be aligned on a 16-byte alignment");
|
||||
@@ -69,6 +83,14 @@ namespace platf::dxgi {
|
||||
return buf_t {buf_p};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Create a blend object or message.
|
||||
*
|
||||
* @param device D3D, audio, or platform device used by the operation.
|
||||
* @param enable Whether the blend state should enable blending.
|
||||
* @param invert Whether the blend state should invert the color channels.
|
||||
* @return Constructed blend object.
|
||||
*/
|
||||
blend_t make_blend(device_t::pointer device, bool enable, bool invert) {
|
||||
D3D11_BLEND_DESC bdesc {};
|
||||
auto &rt = bdesc.RenderTarget[0];
|
||||
@@ -103,53 +125,56 @@ namespace platf::dxgi {
|
||||
return blend;
|
||||
}
|
||||
|
||||
blob_t convert_yuv420_packed_uv_type0_ps_hlsl;
|
||||
blob_t convert_yuv420_packed_uv_type0_ps_linear_hlsl;
|
||||
blob_t convert_yuv420_packed_uv_type0_ps_perceptual_quantizer_hlsl;
|
||||
blob_t convert_yuv420_packed_uv_type0_vs_hlsl;
|
||||
blob_t convert_yuv420_packed_uv_type0s_ps_hlsl;
|
||||
blob_t convert_yuv420_packed_uv_type0s_ps_linear_hlsl;
|
||||
blob_t convert_yuv420_packed_uv_type0s_ps_perceptual_quantizer_hlsl;
|
||||
blob_t convert_yuv420_packed_uv_type0s_vs_hlsl;
|
||||
blob_t convert_yuv420_planar_y_ps_hlsl;
|
||||
blob_t convert_yuv420_planar_y_ps_linear_hlsl;
|
||||
blob_t convert_yuv420_planar_y_ps_perceptual_quantizer_hlsl;
|
||||
blob_t convert_yuv420_planar_y_vs_hlsl;
|
||||
blob_t convert_yuv444_packed_ayuv_ps_hlsl;
|
||||
blob_t convert_yuv444_packed_ayuv_ps_linear_hlsl;
|
||||
blob_t convert_yuv444_packed_vs_hlsl;
|
||||
blob_t convert_yuv444_planar_ps_hlsl;
|
||||
blob_t convert_yuv444_planar_ps_linear_hlsl;
|
||||
blob_t convert_yuv444_planar_ps_perceptual_quantizer_hlsl;
|
||||
blob_t convert_yuv444_packed_y410_ps_hlsl;
|
||||
blob_t convert_yuv444_packed_y410_ps_linear_hlsl;
|
||||
blob_t convert_yuv444_packed_y410_ps_perceptual_quantizer_hlsl;
|
||||
blob_t convert_yuv444_planar_vs_hlsl;
|
||||
blob_t cursor_ps_hlsl;
|
||||
blob_t cursor_ps_normalize_white_hlsl;
|
||||
blob_t cursor_vs_hlsl;
|
||||
blob_t convert_yuv420_packed_uv_type0_ps_hlsl; ///< Convert yuv420 packed uv type0 ps hlsl.
|
||||
blob_t convert_yuv420_packed_uv_type0_ps_linear_hlsl; ///< Convert yuv420 packed uv type0 ps linear hlsl.
|
||||
blob_t convert_yuv420_packed_uv_type0_ps_perceptual_quantizer_hlsl; ///< Convert yuv420 packed uv type0 ps perceptual quantizer hlsl.
|
||||
blob_t convert_yuv420_packed_uv_type0_vs_hlsl; ///< Convert yuv420 packed uv type0 vs hlsl.
|
||||
blob_t convert_yuv420_packed_uv_type0s_ps_hlsl; ///< Convert yuv420 packed uv type0s ps hlsl.
|
||||
blob_t convert_yuv420_packed_uv_type0s_ps_linear_hlsl; ///< Convert yuv420 packed uv type0s ps linear hlsl.
|
||||
blob_t convert_yuv420_packed_uv_type0s_ps_perceptual_quantizer_hlsl; ///< Convert yuv420 packed uv type0s ps perceptual quantizer hlsl.
|
||||
blob_t convert_yuv420_packed_uv_type0s_vs_hlsl; ///< Convert yuv420 packed uv type0s vs hlsl.
|
||||
blob_t convert_yuv420_planar_y_ps_hlsl; ///< Convert yuv420 planar y ps hlsl.
|
||||
blob_t convert_yuv420_planar_y_ps_linear_hlsl; ///< Convert yuv420 planar y ps linear hlsl.
|
||||
blob_t convert_yuv420_planar_y_ps_perceptual_quantizer_hlsl; ///< Convert yuv420 planar y ps perceptual quantizer hlsl.
|
||||
blob_t convert_yuv420_planar_y_vs_hlsl; ///< Convert yuv420 planar y vs hlsl.
|
||||
blob_t convert_yuv444_packed_ayuv_ps_hlsl; ///< Convert YUV444 packed ayuv ps hlsl.
|
||||
blob_t convert_yuv444_packed_ayuv_ps_linear_hlsl; ///< Convert YUV444 packed ayuv ps linear hlsl.
|
||||
blob_t convert_yuv444_packed_vs_hlsl; ///< Convert YUV444 packed vs hlsl.
|
||||
blob_t convert_yuv444_planar_ps_hlsl; ///< Convert YUV444 planar ps hlsl.
|
||||
blob_t convert_yuv444_planar_ps_linear_hlsl; ///< Convert YUV444 planar ps linear hlsl.
|
||||
blob_t convert_yuv444_planar_ps_perceptual_quantizer_hlsl; ///< Convert YUV444 planar ps perceptual quantizer hlsl.
|
||||
blob_t convert_yuv444_packed_y410_ps_hlsl; ///< Convert YUV444 packed y410 ps hlsl.
|
||||
blob_t convert_yuv444_packed_y410_ps_linear_hlsl; ///< Convert YUV444 packed y410 ps linear hlsl.
|
||||
blob_t convert_yuv444_packed_y410_ps_perceptual_quantizer_hlsl; ///< Convert YUV444 packed y410 ps perceptual quantizer hlsl.
|
||||
blob_t convert_yuv444_planar_vs_hlsl; ///< Convert YUV444 planar vs hlsl.
|
||||
blob_t cursor_ps_hlsl; ///< Cursor ps hlsl.
|
||||
blob_t cursor_ps_normalize_white_hlsl; ///< Cursor ps normalize white hlsl.
|
||||
blob_t cursor_vs_hlsl; ///< Cursor vs hlsl.
|
||||
|
||||
/**
|
||||
* @brief D3D-backed captured image and duplication metadata.
|
||||
*/
|
||||
struct img_d3d_t: public platf::img_t {
|
||||
// These objects are owned by the display_t's ID3D11Device
|
||||
texture2d_t capture_texture;
|
||||
render_target_t capture_rt;
|
||||
keyed_mutex_t capture_mutex;
|
||||
texture2d_t capture_texture; ///< Capture texture.
|
||||
render_target_t capture_rt; ///< Capture rt.
|
||||
keyed_mutex_t capture_mutex; ///< Capture mutex.
|
||||
|
||||
// This is the shared handle used by hwdevice_t to open capture_texture
|
||||
HANDLE encoder_texture_handle = {};
|
||||
HANDLE encoder_texture_handle = {}; ///< Encoder texture handle.
|
||||
|
||||
// Set to true if the image corresponds to a dummy texture used prior to
|
||||
// the first successful capture of a desktop frame
|
||||
bool dummy = false;
|
||||
bool dummy = false; ///< Whether this image is a dummy placeholder.
|
||||
|
||||
// Set to true if the image is blank (contains no content at all, including a cursor)
|
||||
bool blank = true;
|
||||
bool blank = true; ///< Whether the texture currently contains a blank frame.
|
||||
|
||||
// Unique identifier for this image
|
||||
uint32_t id = 0;
|
||||
uint32_t id = 0; ///< Unique identifier used to cache encoder resources for this image.
|
||||
|
||||
// DXGI format of this image texture
|
||||
DXGI_FORMAT format;
|
||||
DXGI_FORMAT format; ///< DXGI format of the captured texture.
|
||||
|
||||
virtual ~img_d3d_t() override {
|
||||
if (encoder_texture_handle) {
|
||||
@@ -158,19 +183,33 @@ namespace platf::dxgi {
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Keyed-mutex guard used while sharing a D3D texture.
|
||||
*/
|
||||
struct texture_lock_helper {
|
||||
keyed_mutex_t _mutex;
|
||||
bool _locked = false;
|
||||
keyed_mutex_t _mutex; ///< D3D keyed mutex acquired for shared-texture access.
|
||||
bool _locked = false; ///< Whether the keyed mutex is currently locked.
|
||||
|
||||
texture_lock_helper(const texture_lock_helper &) = delete;
|
||||
texture_lock_helper &operator=(const texture_lock_helper &) = delete;
|
||||
|
||||
/**
|
||||
* @brief Move a keyed-mutex lock helper while preserving lock ownership.
|
||||
*
|
||||
* @param other Helper whose acquired keyed mutex is transferred.
|
||||
*/
|
||||
texture_lock_helper(texture_lock_helper &&other) {
|
||||
_mutex.reset(other._mutex.release());
|
||||
_locked = other._locked;
|
||||
other._locked = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Assign state from another instance while preserving ownership semantics.
|
||||
*
|
||||
* @param other Source object whose state is copied or moved into this object.
|
||||
* @return Reference or value produced by the operator.
|
||||
*/
|
||||
texture_lock_helper &operator=(texture_lock_helper &&other) {
|
||||
if (_locked) {
|
||||
_mutex->ReleaseSync(0);
|
||||
@@ -181,6 +220,11 @@ namespace platf::dxgi {
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Acquire a D3D keyed mutex for scoped texture access.
|
||||
*
|
||||
* @param mutex Keyed mutex to acquire for texture access.
|
||||
*/
|
||||
texture_lock_helper(IDXGIKeyedMutex *mutex):
|
||||
_mutex(mutex) {
|
||||
if (_mutex) {
|
||||
@@ -194,6 +238,11 @@ namespace platf::dxgi {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Acquire the underlying lock or keyed mutex.
|
||||
*
|
||||
* @return True when the keyed mutex is acquired.
|
||||
*/
|
||||
bool lock() {
|
||||
if (_locked) {
|
||||
return true;
|
||||
@@ -208,6 +257,13 @@ namespace platf::dxgi {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Create cursor xor image.
|
||||
*
|
||||
* @param img_data Raw pointer-shape bytes returned by DXGI.
|
||||
* @param shape_info DXGI metadata describing the pointer shape.
|
||||
* @return Constructed cursor xor image object.
|
||||
*/
|
||||
util::buffer_t<std::uint8_t> make_cursor_xor_image(const util::buffer_t<std::uint8_t> &img_data, DXGI_OUTDUPL_POINTER_SHAPE_INFO shape_info) {
|
||||
constexpr std::uint32_t inverted = 0xFFFFFFFF;
|
||||
constexpr std::uint32_t transparent = 0;
|
||||
@@ -277,6 +333,13 @@ namespace platf::dxgi {
|
||||
return cursor_img;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Create cursor alpha image.
|
||||
*
|
||||
* @param img_data Raw pointer-shape bytes returned by DXGI.
|
||||
* @param shape_info DXGI metadata describing the pointer shape.
|
||||
* @return Constructed cursor alpha image object.
|
||||
*/
|
||||
util::buffer_t<std::uint8_t> make_cursor_alpha_image(const util::buffer_t<std::uint8_t> &img_data, DXGI_OUTDUPL_POINTER_SHAPE_INFO shape_info) {
|
||||
constexpr std::uint32_t black = 0xFF000000;
|
||||
constexpr std::uint32_t white = 0xFFFFFFFF;
|
||||
@@ -350,6 +413,14 @@ namespace platf::dxgi {
|
||||
return cursor_img;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Compile an HLSL shader from source text.
|
||||
*
|
||||
* @param file Optional stdio file handle connected to the child process.
|
||||
* @param entrypoint Function entry point to resolve from the library.
|
||||
* @param shader_model Shader model.
|
||||
* @return Compiled shader blob, or an empty blob when compilation fails.
|
||||
*/
|
||||
blob_t compile_shader(LPCSTR file, LPCSTR entrypoint, LPCSTR shader_model) {
|
||||
blob_t::pointer msg_p = nullptr;
|
||||
blob_t::pointer compiled_p;
|
||||
@@ -376,16 +447,37 @@ namespace platf::dxgi {
|
||||
return blob_t {compiled_p};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Compile an HLSL pixel shader from source text.
|
||||
*
|
||||
* @param file Optional stdio file handle connected to the child process.
|
||||
* @return Compiled pixel shader object, or an empty handle when compilation fails.
|
||||
*/
|
||||
blob_t compile_pixel_shader(LPCSTR file) {
|
||||
return compile_shader(file, "main_ps", "ps_5_0");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Compile an HLSL vertex shader from source text.
|
||||
*
|
||||
* @param file Optional stdio file handle connected to the child process.
|
||||
* @return Compiled vertex shader object, or an empty handle when compilation fails.
|
||||
*/
|
||||
blob_t compile_vertex_shader(LPCSTR file) {
|
||||
return compile_shader(file, "main_vs", "vs_5_0");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Shared D3D11 conversion resources for AVCodec and NVENC encode devices.
|
||||
*/
|
||||
class d3d_base_encode_device final {
|
||||
public:
|
||||
/**
|
||||
* @brief Convert a captured D3D image into encoder input textures.
|
||||
*
|
||||
* @param img_base D3D image supplied by the capture backend.
|
||||
* @return Conversion status.
|
||||
*/
|
||||
int convert(platf::img_t &img_base) {
|
||||
// Garbage collect mapped capture images whose weak references have expired
|
||||
for (auto it = img_ctx_map.begin(); it != img_ctx_map.end();) {
|
||||
@@ -457,6 +549,11 @@ namespace platf::dxgi {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Apply the configured colorspace metadata to the active frame.
|
||||
*
|
||||
* @param colorspace Colorimetry information used for conversion or encoding.
|
||||
*/
|
||||
void apply_colorspace(const ::video::sunshine_colorspace_t &colorspace) {
|
||||
auto color_vectors = ::video::color_vectors_from_colorspace(colorspace, true);
|
||||
|
||||
@@ -482,6 +579,14 @@ namespace platf::dxgi {
|
||||
this->color_matrix = std::move(color_matrix);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Create D3D11 output textures, views, and shaders for frame conversion.
|
||||
*
|
||||
* @param frame_texture Frame texture.
|
||||
* @param width Frame or display width in pixels.
|
||||
* @param height Frame or display height in pixels.
|
||||
* @return 0 when output resources are initialized; nonzero on D3D failure.
|
||||
*/
|
||||
int init_output(ID3D11Texture2D *frame_texture, int width, int height) {
|
||||
// The underlying frame pool owns the texture, so we must reference it for ourselves
|
||||
frame_texture->AddRef();
|
||||
@@ -489,16 +594,20 @@ namespace platf::dxgi {
|
||||
|
||||
HRESULT status = S_OK;
|
||||
|
||||
#define create_vertex_shader_helper(x, y) \
|
||||
if (FAILED(status = device->CreateVertexShader(x->GetBufferPointer(), x->GetBufferSize(), nullptr, &y))) { \
|
||||
BOOST_LOG(error) << "Failed to create vertex shader " << #x << ": " << util::log_hex(status); \
|
||||
return -1; \
|
||||
}
|
||||
#define create_pixel_shader_helper(x, y) \
|
||||
if (FAILED(status = device->CreatePixelShader(x->GetBufferPointer(), x->GetBufferSize(), nullptr, &y))) { \
|
||||
BOOST_LOG(error) << "Failed to create pixel shader " << #x << ": " << util::log_hex(status); \
|
||||
return -1; \
|
||||
}
|
||||
#ifndef DOXYGEN
|
||||
#define create_vertex_shader_helper(x, y) \
|
||||
if (FAILED(status = device->CreateVertexShader(x->GetBufferPointer(), x->GetBufferSize(), nullptr, &y))) { \
|
||||
BOOST_LOG(error) << "Failed to create vertex shader " << #x << ": " << util::log_hex(status); \
|
||||
return -1; \
|
||||
}
|
||||
#endif
|
||||
#ifndef DOXYGEN
|
||||
#define create_pixel_shader_helper(x, y) \
|
||||
if (FAILED(status = device->CreatePixelShader(x->GetBufferPointer(), x->GetBufferSize(), nullptr, &y))) { \
|
||||
BOOST_LOG(error) << "Failed to create pixel shader " << #x << ": " << util::log_hex(status); \
|
||||
return -1; \
|
||||
}
|
||||
#endif
|
||||
|
||||
const bool downscaling = display->width > width || display->height > height;
|
||||
|
||||
@@ -709,6 +818,14 @@ namespace platf::dxgi {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Initialize shared D3D conversion resources for the encoder.
|
||||
*
|
||||
* @param display Display object or identifier associated with the operation.
|
||||
* @param adapter_p Adapter p.
|
||||
* @param pix_fmt Sunshine pixel format to convert or allocate for.
|
||||
* @return 0 on success; nonzero or negative platform status on failure.
|
||||
*/
|
||||
int init(std::shared_ptr<platf::display_t> display, adapter_t::pointer adapter_p, pix_fmt_e pix_fmt) {
|
||||
switch (pix_fmt) {
|
||||
case pix_fmt_e::nv12:
|
||||
@@ -823,17 +940,23 @@ namespace platf::dxgi {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief D3D texture and format information for encoder input.
|
||||
*/
|
||||
struct encoder_img_ctx_t {
|
||||
// Used to determine if the underlying texture changes.
|
||||
// Not safe for actual use by the encoder!
|
||||
texture2d_t::const_pointer capture_texture_p;
|
||||
texture2d_t::const_pointer capture_texture_p; ///< Capture texture p.
|
||||
|
||||
texture2d_t encoder_texture;
|
||||
shader_res_t encoder_input_res;
|
||||
keyed_mutex_t encoder_mutex;
|
||||
texture2d_t encoder_texture; ///< Encoder texture.
|
||||
shader_res_t encoder_input_res; ///< Encoder input res.
|
||||
keyed_mutex_t encoder_mutex; ///< Encoder mutex.
|
||||
|
||||
std::weak_ptr<const platf::img_t> img_weak;
|
||||
std::weak_ptr<const platf::img_t> img_weak; ///< Captured image lifetime tracked for cache cleanup.
|
||||
|
||||
/**
|
||||
* @brief Reset the object to its initial empty state.
|
||||
*/
|
||||
void reset() {
|
||||
capture_texture_p = nullptr;
|
||||
encoder_texture.reset();
|
||||
@@ -843,6 +966,13 @@ namespace platf::dxgi {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Initialize encoder-side D3D resources for a captured image.
|
||||
*
|
||||
* @param img Image or frame object to read from or populate.
|
||||
* @param img_ctx Cached encoder resources associated with the image ID.
|
||||
* @return 0 when the shared texture is opened and bound; nonzero on D3D failure.
|
||||
*/
|
||||
int initialize_image_context(const img_d3d_t &img, encoder_img_ctx_t &img_ctx) {
|
||||
// If we've already opened the shared texture, we're done
|
||||
if (img_ctx.encoder_texture && img.capture_texture.get() == img_ctx.capture_texture_p) {
|
||||
@@ -888,6 +1018,11 @@ namespace platf::dxgi {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Create black texture for rtv clear.
|
||||
*
|
||||
* @return Created black texture for rtv clear object or status.
|
||||
*/
|
||||
shader_res_t create_black_texture_for_rtv_clear() {
|
||||
constexpr auto width = 32;
|
||||
constexpr auto height = 32;
|
||||
@@ -922,63 +1057,88 @@ namespace platf::dxgi {
|
||||
return resource_view;
|
||||
}
|
||||
|
||||
::video::color_t *color_p;
|
||||
::video::color_t *color_p; ///< Color p.
|
||||
|
||||
buf_t subsample_offset;
|
||||
buf_t color_matrix;
|
||||
buf_t subsample_offset; ///< Subsample offset.
|
||||
buf_t color_matrix; ///< Color matrix.
|
||||
|
||||
blend_t blend_disable;
|
||||
sampler_state_t sampler_linear;
|
||||
blend_t blend_disable; ///< Blend disable.
|
||||
sampler_state_t sampler_linear; ///< Sampler linear.
|
||||
|
||||
render_target_t out_Y_or_YUV_rtv;
|
||||
render_target_t out_UV_rtv;
|
||||
bool rtvs_cleared = false;
|
||||
render_target_t out_Y_or_YUV_rtv; ///< Out y or YUV rtv.
|
||||
render_target_t out_UV_rtv; ///< Out UV rtv.
|
||||
bool rtvs_cleared = false; ///< Whether render-target views have been cleared for the frame.
|
||||
|
||||
// d3d_img_t::id -> encoder_img_ctx_t
|
||||
// These store the encoder textures for each img_t that passes through
|
||||
// convert(). We can't store them in the img_t itself because it is shared
|
||||
// amongst multiple hwdevice_t objects (and therefore multiple ID3D11Devices).
|
||||
std::map<uint32_t, encoder_img_ctx_t> img_ctx_map;
|
||||
std::map<uint32_t, encoder_img_ctx_t> img_ctx_map; ///< Encoder resources cached by captured image ID.
|
||||
|
||||
std::shared_ptr<display_base_t> display;
|
||||
std::shared_ptr<display_base_t> display; ///< Display capture backend that supplies source textures.
|
||||
|
||||
vs_t convert_Y_or_YUV_vs;
|
||||
ps_t convert_Y_or_YUV_ps;
|
||||
ps_t convert_Y_or_YUV_fp16_ps;
|
||||
vs_t convert_Y_or_YUV_vs; ///< Convert y or YUV vs.
|
||||
ps_t convert_Y_or_YUV_ps; ///< Convert y or YUV ps.
|
||||
ps_t convert_Y_or_YUV_fp16_ps; ///< Convert y or YUV fp16 ps.
|
||||
|
||||
vs_t convert_UV_vs;
|
||||
ps_t convert_UV_ps;
|
||||
ps_t convert_UV_fp16_ps;
|
||||
vs_t convert_UV_vs; ///< Convert UV vs.
|
||||
ps_t convert_UV_ps; ///< Convert UV ps.
|
||||
ps_t convert_UV_fp16_ps; ///< Convert UV fp16 ps.
|
||||
|
||||
std::array<D3D11_VIEWPORT, 3> out_Y_or_YUV_viewports;
|
||||
std::array<D3D11_VIEWPORT, 3> out_Y_or_YUV_viewports_for_clear;
|
||||
D3D11_VIEWPORT out_UV_viewport;
|
||||
D3D11_VIEWPORT out_UV_viewport_for_clear;
|
||||
std::array<D3D11_VIEWPORT, 3> out_Y_or_YUV_viewports; ///< Out y or YUV viewports.
|
||||
std::array<D3D11_VIEWPORT, 3> out_Y_or_YUV_viewports_for_clear; ///< Out y or YUV viewports for clear.
|
||||
D3D11_VIEWPORT out_UV_viewport; ///< Out UV viewport.
|
||||
D3D11_VIEWPORT out_UV_viewport_for_clear; ///< Out UV viewport for clear.
|
||||
|
||||
DXGI_FORMAT format;
|
||||
DXGI_FORMAT format; ///< DXGI format required by the encoder input texture.
|
||||
|
||||
device_t device;
|
||||
device_ctx_t device_ctx;
|
||||
device_t device; ///< D3D11 device used for encoder-side texture conversion.
|
||||
device_ctx_t device_ctx; ///< D3D11 device context used to issue conversion commands.
|
||||
|
||||
texture2d_t output_texture;
|
||||
texture2d_t output_texture; ///< Output texture.
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief D3D11 encode device that exposes captured textures to FFmpeg AVCodec.
|
||||
*/
|
||||
class d3d_avcodec_encode_device_t: public avcodec_encode_device_t {
|
||||
public:
|
||||
/**
|
||||
* @brief Initialize the D3D11 AVCodec encode device.
|
||||
*
|
||||
* @param display Display object or identifier associated with the operation.
|
||||
* @param adapter_p Adapter p.
|
||||
* @param pix_fmt Sunshine pixel format to convert or allocate for.
|
||||
* @return 0 on success; nonzero or negative platform status on failure.
|
||||
*/
|
||||
int init(std::shared_ptr<platf::display_t> display, adapter_t::pointer adapter_p, pix_fmt_e pix_fmt) {
|
||||
int result = base.init(display, adapter_p, pix_fmt);
|
||||
data = base.device.get();
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Convert a captured D3D texture for FFmpeg AVCodec encoding.
|
||||
*
|
||||
* @param img_base D3D image supplied by the capture backend.
|
||||
* @return Conversion status.
|
||||
*/
|
||||
int convert(platf::img_t &img_base) override {
|
||||
return base.convert(img_base);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Apply the configured colorspace metadata to the active frame.
|
||||
*/
|
||||
void apply_colorspace() override {
|
||||
base.apply_colorspace(colorspace);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Configure FFmpeg hardware frames for D3D11 encoder input textures.
|
||||
*
|
||||
* @param frames FFmpeg hardware frames context to initialize.
|
||||
*/
|
||||
void init_hwframes(AVHWFramesContext *frames) override {
|
||||
// We may be called with a QSV or D3D11VA context
|
||||
if (frames->device_ctx->type == AV_HWDEVICE_TYPE_D3D11VA) {
|
||||
@@ -993,6 +1153,12 @@ namespace platf::dxgi {
|
||||
frames->initial_pool_size = 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Prepare the D3D device before FFmpeg derives a child hardware context.
|
||||
*
|
||||
* @param hw_device_type FFmpeg hardware device type requested for context derivation.
|
||||
* @return 0 when context derivation may continue; nonzero to abort.
|
||||
*/
|
||||
int prepare_to_derive_context(int hw_device_type) override {
|
||||
// QuickSync requires our device to be multithread-protected
|
||||
if (hw_device_type == AV_HWDEVICE_TYPE_QSV) {
|
||||
@@ -1010,6 +1176,13 @@ namespace platf::dxgi {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Attach frame resources used by the next conversion or encode operation.
|
||||
*
|
||||
* @param frame Video or graphics frame being processed.
|
||||
* @param hw_frames_ctx FFmpeg hardware frames context associated with the frame.
|
||||
* @return Status from updating frame.
|
||||
*/
|
||||
int set_frame(AVFrame *frame, AVBufferRef *hw_frames_ctx) override {
|
||||
this->hwframe.reset(frame);
|
||||
this->frame = frame;
|
||||
@@ -1053,8 +1226,19 @@ namespace platf::dxgi {
|
||||
frame_t hwframe;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief D3D11 encode device that exposes captured textures to NVENC.
|
||||
*/
|
||||
class d3d_nvenc_encode_device_t: public nvenc_encode_device_t {
|
||||
public:
|
||||
/**
|
||||
* @brief Create D3D11 and NVENC resources for texture-based encoding.
|
||||
*
|
||||
* @param display Display object or identifier associated with the operation.
|
||||
* @param adapter_p Adapter p.
|
||||
* @param pix_fmt Sunshine pixel format to convert or allocate for.
|
||||
* @return True when the D3D11 device resources are initialized.
|
||||
*/
|
||||
bool init_device(std::shared_ptr<platf::display_t> display, adapter_t::pointer adapter_p, pix_fmt_e pix_fmt) {
|
||||
buffer_format = nvenc::nvenc_format_from_sunshine_format(pix_fmt);
|
||||
if (buffer_format == NV_ENC_BUFFER_FORMAT_UNDEFINED) {
|
||||
@@ -1076,6 +1260,13 @@ namespace platf::dxgi {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Initialize the platform encoder for the client stream configuration.
|
||||
*
|
||||
* @param client_config Client stream configuration negotiated for this session.
|
||||
* @param colorspace Colorimetry information used for conversion or encoding.
|
||||
* @return True when the NVENC encoder initializes for the client configuration.
|
||||
*/
|
||||
bool init_encoder(const ::video::config_t &client_config, const ::video::sunshine_colorspace_t &colorspace) override {
|
||||
if (!nvenc_d3d) {
|
||||
return false;
|
||||
@@ -1090,6 +1281,12 @@ namespace platf::dxgi {
|
||||
return base.init_output(nvenc_d3d->get_input_texture(), client_config.width, client_config.height) == 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Convert a captured D3D texture for NVENC encoding.
|
||||
*
|
||||
* @param img_base D3D image supplied by the capture backend.
|
||||
* @return Conversion status.
|
||||
*/
|
||||
int convert(platf::img_t &img_base) override {
|
||||
return base.convert(img_base);
|
||||
}
|
||||
@@ -1100,6 +1297,15 @@ namespace platf::dxgi {
|
||||
NV_ENC_BUFFER_FORMAT buffer_format = NV_ENC_BUFFER_FORMAT_UNDEFINED;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Set cursor texture.
|
||||
*
|
||||
* @param device D3D, audio, or platform device used by the operation.
|
||||
* @param cursor Cursor image or visibility state to composite.
|
||||
* @param cursor_img Cursor img.
|
||||
* @param shape_info Shape info.
|
||||
* @return True when the cursor image is uploaded to the D3D texture.
|
||||
*/
|
||||
bool set_cursor_texture(device_t::pointer device, gpu_cursor_t &cursor, util::buffer_t<std::uint8_t> &&cursor_img, DXGI_OUTDUPL_POINTER_SHAPE_INFO &shape_info) {
|
||||
// This cursor image may not be used
|
||||
if (cursor_img.size() == 0) {
|
||||
@@ -1638,10 +1844,6 @@ namespace platf::dxgi {
|
||||
|
||||
/**
|
||||
* Get the next frame from the Windows.Graphics.Capture API and copy it into a new snapshot texture.
|
||||
* @param pull_free_image_cb call this to get a new free image from the video subsystem.
|
||||
* @param img_out the captured frame is returned here
|
||||
* @param timeout how long to wait for the next frame
|
||||
* @param cursor_visible
|
||||
*/
|
||||
capture_e display_wgc_vram_t::snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr<platf::img_t> &img_out, std::chrono::milliseconds timeout, bool cursor_visible) {
|
||||
texture2d_t src;
|
||||
@@ -1938,15 +2140,22 @@ namespace platf::dxgi {
|
||||
return device;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Initialize global D3D11 desktop duplication support.
|
||||
*/
|
||||
int init() {
|
||||
BOOST_LOG(info) << "Compiling shaders..."sv;
|
||||
|
||||
#define compile_vertex_shader_helper(x) \
|
||||
if (!(x##_hlsl = compile_vertex_shader(SUNSHINE_SHADERS_DIR "/" #x ".hlsl"))) \
|
||||
return -1;
|
||||
#define compile_pixel_shader_helper(x) \
|
||||
if (!(x##_hlsl = compile_pixel_shader(SUNSHINE_SHADERS_DIR "/" #x ".hlsl"))) \
|
||||
return -1;
|
||||
#ifndef DOXYGEN
|
||||
#define compile_vertex_shader_helper(x) \
|
||||
if (!(x##_hlsl = compile_vertex_shader(SUNSHINE_SHADERS_DIR "/" #x ".hlsl"))) \
|
||||
return -1;
|
||||
#endif
|
||||
#ifndef DOXYGEN
|
||||
#define compile_pixel_shader_helper(x) \
|
||||
if (!(x##_hlsl = compile_pixel_shader(SUNSHINE_SHADERS_DIR "/" #x ".hlsl"))) \
|
||||
return -1;
|
||||
#endif
|
||||
|
||||
compile_pixel_shader_helper(convert_yuv420_packed_uv_type0_ps);
|
||||
compile_pixel_shader_helper(convert_yuv420_packed_uv_type0_ps_linear);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user