"origin_web_ui_allowed": "lan",
"qsv_coder": "auto",
"qsv_preset": "medium",
- "resolutions": "[352x240,480x360,858x480,1280x720,1920x1080,2560x1080,3440x1440,1920x1200,3860x2160,3840x1600]",
+ "resolutions": "[352x240,480x360,858x480,1280x720,1920x1080,2560x1080,3440x1440,1920x1200,3840x2160,3840x1600]",
"sw_preset": "superfast",
"sw_tune": "zerolatency",
"upnp": "disabled",
From 290c9b3e20025c47f86312da49f3138749bb0948 Mon Sep 17 00:00:00 2001
From: ns6089 <61738816+ns6089@users.noreply.github.com>
Date: Thu, 6 Apr 2023 04:54:49 +0300
Subject: [PATCH 004/144] Fix compilation on macOS (#1132)
---
src/thread_safe.h | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/thread_safe.h b/src/thread_safe.h
index 1f29761f1e2..6db1e9ecd6c 100644
--- a/src/thread_safe.h
+++ b/src/thread_safe.h
@@ -3,6 +3,7 @@
#ifndef SUNSHINE_THREAD_SAFE_H
#define SUNSHINE_THREAD_SAFE_H
+#include
#include
#include
#include
From f04d5e12de1d4aa702ecf0d2bf20502ab58ad154 Mon Sep 17 00:00:00 2001
From: Cameron Gutman
Date: Tue, 4 Apr 2023 22:19:02 -0500
Subject: [PATCH 005/144] Combine the separate HDR and SDR capture formats
Since we support multi-encoding from a single display context, we have
to ensure our capture format allows for HDR color even if the initial
encoding session is SDR.
---
src/platform/windows/display.h | 12 +++---------
src/platform/windows/display_base.cpp | 2 +-
src/platform/windows/display_ram.cpp | 8 +-------
src/platform/windows/display_vram.cpp | 7 +------
4 files changed, 6 insertions(+), 23 deletions(-)
diff --git a/src/platform/windows/display.h b/src/platform/windows/display.h
index ba6aac38432..17e79e26b70 100644
--- a/src/platform/windows/display.h
+++ b/src/platform/windows/display.h
@@ -172,9 +172,7 @@ namespace platf::dxgi {
virtual int
complete_img(img_t *img, bool dummy) = 0;
virtual std::vector
- get_supported_sdr_capture_formats() = 0;
- virtual std::vector
- get_supported_hdr_capture_formats() = 0;
+ get_supported_capture_formats() = 0;
};
class display_ram_t: public display_base_t {
@@ -189,9 +187,7 @@ namespace platf::dxgi {
int
complete_img(img_t *img, bool dummy) override;
std::vector
- get_supported_sdr_capture_formats() override;
- std::vector
- get_supported_hdr_capture_formats() override;
+ get_supported_capture_formats() override;
int
init(const ::video::config_t &config, const std::string &display_name);
@@ -213,9 +209,7 @@ namespace platf::dxgi {
int
complete_img(img_t *img_base, bool dummy) override;
std::vector
- get_supported_sdr_capture_formats() override;
- std::vector
- get_supported_hdr_capture_formats() override;
+ get_supported_capture_formats() override;
int
init(const ::video::config_t &config, const std::string &display_name);
diff --git a/src/platform/windows/display_base.cpp b/src/platform/windows/display_base.cpp
index f6c3bfb351f..b92ac7b7c2e 100644
--- a/src/platform/windows/display_base.cpp
+++ b/src/platform/windows/display_base.cpp
@@ -517,7 +517,7 @@ namespace platf::dxgi {
status = output->QueryInterface(IID_IDXGIOutput5, (void **) &output5);
if (SUCCEEDED(status)) {
// Ask the display implementation which formats it supports
- auto supported_formats = config.dynamicRange ? get_supported_hdr_capture_formats() : get_supported_sdr_capture_formats();
+ auto supported_formats = get_supported_capture_formats();
if (supported_formats.empty()) {
BOOST_LOG(warning) << "No compatible capture formats for this encoder"sv;
return -1;
diff --git a/src/platform/windows/display_ram.cpp b/src/platform/windows/display_ram.cpp
index 581d925c51b..8d24d07a2bd 100644
--- a/src/platform/windows/display_ram.cpp
+++ b/src/platform/windows/display_ram.cpp
@@ -358,16 +358,10 @@ namespace platf::dxgi {
}
std::vector
- display_ram_t::get_supported_sdr_capture_formats() {
+ display_ram_t::get_supported_capture_formats() {
return { DXGI_FORMAT_B8G8R8A8_UNORM };
}
- std::vector
- display_ram_t::get_supported_hdr_capture_formats() {
- // HDR is unsupported
- return {};
- }
-
int
display_ram_t::init(const ::video::config_t &config, const std::string &display_name) {
if (display_base_t::init(config, display_name)) {
diff --git a/src/platform/windows/display_vram.cpp b/src/platform/windows/display_vram.cpp
index b6d51335e1e..d35285ab589 100644
--- a/src/platform/windows/display_vram.cpp
+++ b/src/platform/windows/display_vram.cpp
@@ -1159,12 +1159,7 @@ namespace platf::dxgi {
}
std::vector
- display_vram_t::get_supported_sdr_capture_formats() {
- return { DXGI_FORMAT_B8G8R8A8_UNORM, DXGI_FORMAT_R8G8B8A8_UNORM };
- }
-
- std::vector
- display_vram_t::get_supported_hdr_capture_formats() {
+ display_vram_t::get_supported_capture_formats() {
return {
// scRGB FP16 is the desired format for HDR content. This will also handle
// 10-bit SDR displays with the increased precision of FP16 vs 8-bit UNORMs.
From 65268212ee1f2f52759c81401851b959c5b50ce3 Mon Sep 17 00:00:00 2001
From: Cameron Gutman
Date: Tue, 4 Apr 2023 22:22:48 -0500
Subject: [PATCH 006/144] Saturate RGB values in non-PQ shaders to improve SDR
streams of HDR displays
This avoids color shift caused by RGB values > 1.0f.
---
src_assets/windows/assets/shaders/directx/ConvertUVPS.hlsl | 4 ++--
src_assets/windows/assets/shaders/directx/ConvertYPS.hlsl | 2 +-
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/src_assets/windows/assets/shaders/directx/ConvertUVPS.hlsl b/src_assets/windows/assets/shaders/directx/ConvertUVPS.hlsl
index 93d45762dbf..2c4431889b9 100644
--- a/src_assets/windows/assets/shaders/directx/ConvertUVPS.hlsl
+++ b/src_assets/windows/assets/shaders/directx/ConvertUVPS.hlsl
@@ -19,8 +19,8 @@ cbuffer ColorMatrix : register(b0) {
//--------------------------------------------------------------------------------------
float2 main_ps(FragTexWide input) : SV_Target
{
- float3 rgb_left = image.Sample(def_sampler, input.uuv.xz).rgb;
- float3 rgb_right = image.Sample(def_sampler, input.uuv.yz).rgb;
+ float3 rgb_left = saturate(image.Sample(def_sampler, input.uuv.xz)).rgb;
+ float3 rgb_right = saturate(image.Sample(def_sampler, input.uuv.yz)).rgb;
float3 rgb = (rgb_left + rgb_right) * 0.5;
float u = dot(color_vec_u.xyz, rgb) + color_vec_u.w;
diff --git a/src_assets/windows/assets/shaders/directx/ConvertYPS.hlsl b/src_assets/windows/assets/shaders/directx/ConvertYPS.hlsl
index 0abb89a86eb..aad74f318b9 100644
--- a/src_assets/windows/assets/shaders/directx/ConvertYPS.hlsl
+++ b/src_assets/windows/assets/shaders/directx/ConvertYPS.hlsl
@@ -18,7 +18,7 @@ struct PS_INPUT
float main_ps(PS_INPUT frag_in) : SV_Target
{
- float3 rgb = image.Sample(def_sampler, frag_in.tex, 0).rgb;
+ float3 rgb = saturate(image.Sample(def_sampler, frag_in.tex, 0)).rgb;
float y = dot(color_vec_y.xyz, rgb) + color_vec_y.w;
return y * range_y.x + range_y.y;
From bd443395c63176e685be204becd2ddbcd6a1d78e Mon Sep 17 00:00:00 2001
From: Cameron Gutman
Date: Tue, 4 Apr 2023 22:32:28 -0500
Subject: [PATCH 007/144] Don't test HDR encoding with H.264
It doesn't work on any GPU, and even if it did, Moonlight doesn't support it.
---
src/video.cpp | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/src/video.cpp b/src/video.cpp
index 0ee04ff39aa..16e2a001f52 100644
--- a/src/video.cpp
+++ b/src/video.cpp
@@ -1866,7 +1866,9 @@ namespace video {
h264.videoFormat = 0;
hevc.videoFormat = 1;
- encoder.h264[flag] = validate_config(disp, encoder, h264) >= 0;
+ // HDR is not supported with H.264. Don't bother even trying it.
+ encoder.h264[flag] = flag != encoder_t::DYNAMIC_RANGE && validate_config(disp, encoder, h264) >= 0;
+
if (encoder.hevc[encoder_t::PASSED]) {
encoder.hevc[flag] = validate_config(disp, encoder, hevc) >= 0;
}
From 036aa2e470cf72e63f036b6b4ec23446a4fdf140 Mon Sep 17 00:00:00 2001
From: Cameron Gutman
Date: Tue, 4 Apr 2023 21:51:24 -0500
Subject: [PATCH 008/144] Free dummy images when they are no longer needed
---
src/video.cpp | 26 +++++++++++++++-----------
1 file changed, 15 insertions(+), 11 deletions(-)
diff --git a/src/video.cpp b/src/video.cpp
index 16e2a001f52..6066266e21f 100644
--- a/src/video.cpp
+++ b/src/video.cpp
@@ -1317,11 +1317,15 @@ namespace video {
auto packets = mail::man->queue(mail::video_packets);
auto idr_events = mail->event(mail::idr);
- // Load a dummy image into the AVFrame to ensure we have something to encode
- // even if we timeout waiting on the first frame.
- auto dummy_img = disp->alloc_img();
- if (!dummy_img || disp->dummy_img(dummy_img.get()) || session->device->convert(*dummy_img)) {
- return;
+ {
+ // Load a dummy image into the AVFrame to ensure we have something to encode
+ // even if we timeout waiting on the first frame. This is a relatively large
+ // allocation which can be freed immediately after convert(), so we do this
+ // in a separate scope.
+ auto dummy_img = disp->alloc_img();
+ if (!dummy_img || disp->dummy_img(dummy_img.get()) || session->device->convert(*dummy_img)) {
+ return;
+ }
}
while (true) {
@@ -1728,12 +1732,12 @@ namespace video {
return -1;
}
- auto img = disp->alloc_img();
- if (!img || disp->dummy_img(img.get())) {
- return -1;
- }
- if (session->device->convert(*img)) {
- return -1;
+ {
+ // Image buffers are large, so we use a separate scope to free it immediately after convert()
+ auto img = disp->alloc_img();
+ if (!img || disp->dummy_img(img.get()) || session->device->convert(*img)) {
+ return -1;
+ }
}
auto frame = session->device->frame;
From 0e92bdd29c71eaf0015e1e04699cff1e50598020 Mon Sep 17 00:00:00 2001
From: aseligmann
Date: Fri, 7 Apr 2023 19:10:25 +0200
Subject: [PATCH 009/144] Use DS4_BUTTON_SHARE as BACK action
---
src/platform/windows/input.cpp | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/platform/windows/input.cpp b/src/platform/windows/input.cpp
index 0e53766527d..69570f148b2 100644
--- a/src/platform/windows/input.cpp
+++ b/src/platform/windows/input.cpp
@@ -494,6 +494,7 @@ namespace platf {
if(flags & LEFT_BUTTON) buttons |= DS4_BUTTON_SHOULDER_LEFT;
if(flags & RIGHT_BUTTON) buttons |= DS4_BUTTON_SHOULDER_RIGHT;
if(flags & START) buttons |= DS4_BUTTON_OPTIONS;
+ if(flags & BACK) buttons |= DS4_BUTTON_SHARE;
if(flags & A) buttons |= DS4_BUTTON_CROSS;
if(flags & B) buttons |= DS4_BUTTON_CIRCLE;
if(flags & X) buttons |= DS4_BUTTON_SQUARE;
@@ -510,7 +511,6 @@ namespace platf {
ds4_special_buttons(const gamepad_state_t &gamepad_state) {
int buttons {};
- if (gamepad_state.buttonFlags & BACK) buttons |= DS4_SPECIAL_BUTTON_TOUCHPAD;
if (gamepad_state.buttonFlags & HOME) buttons |= DS4_SPECIAL_BUTTON_PS;
return (DS4_SPECIAL_BUTTONS) buttons;
From abf4ab42a0b03b7ce77aedd5eff73b02b694752c Mon Sep 17 00:00:00 2001
From: ns6089 <61738816+ns6089@users.noreply.github.com>
Date: Fri, 7 Apr 2023 23:12:47 +0300
Subject: [PATCH 010/144] Don't zero-initialize non-dummy textures
---
src/platform/windows/display_vram.cpp | 23 ++++++++++++++---------
1 file changed, 14 insertions(+), 9 deletions(-)
diff --git a/src/platform/windows/display_vram.cpp b/src/platform/windows/display_vram.cpp
index d35285ab589..5fc2050ee5b 100644
--- a/src/platform/windows/display_vram.cpp
+++ b/src/platform/windows/display_vram.cpp
@@ -1106,15 +1106,20 @@ namespace platf::dxgi {
t.BindFlags = D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_RENDER_TARGET;
t.MiscFlags = D3D11_RESOURCE_MISC_SHARED_NTHANDLE | D3D11_RESOURCE_MISC_SHARED_KEYEDMUTEX;
- auto dummy_data = std::make_unique(img->row_pitch * img->height);
- std::fill_n(dummy_data.get(), img->row_pitch * img->height, 0);
- D3D11_SUBRESOURCE_DATA initial_data {
- dummy_data.get(),
- (UINT) img->row_pitch,
- 0
- };
-
- auto status = device->CreateTexture2D(&t, &initial_data, &img->capture_texture);
+ HRESULT status;
+ if (dummy) {
+ auto dummy_data = std::make_unique(img->row_pitch * img->height);
+ std::fill_n(dummy_data.get(), img->row_pitch * img->height, 0);
+ D3D11_SUBRESOURCE_DATA initial_data {
+ dummy_data.get(),
+ (UINT) img->row_pitch,
+ 0
+ };
+ status = device->CreateTexture2D(&t, &initial_data, &img->capture_texture);
+ }
+ else {
+ status = device->CreateTexture2D(&t, nullptr, &img->capture_texture);
+ }
if (FAILED(status)) {
BOOST_LOG(error) << "Failed to create img buf texture [0x"sv << util::hex(status).to_string_view() << ']';
return -1;
From a9b7fd7fa30c9e5faf78fbd982bd5642e7dfc4b1 Mon Sep 17 00:00:00 2001
From: Cameron Gutman
Date: Sat, 8 Apr 2023 15:42:27 -0500
Subject: [PATCH 011/144] Remove useless Flush() call
---
src/platform/windows/display_vram.cpp | 4 ----
1 file changed, 4 deletions(-)
diff --git a/src/platform/windows/display_vram.cpp b/src/platform/windows/display_vram.cpp
index 5fc2050ee5b..45428c02492 100644
--- a/src/platform/windows/display_vram.cpp
+++ b/src/platform/windows/display_vram.cpp
@@ -333,10 +333,6 @@ namespace platf::dxgi {
device_ctx->PSSetShaderResources(0, 1, &img_ctx.encoder_input_res);
device_ctx->Draw(3, 0);
- // Artifacts start appearing on the rendered image if Sunshine doesn't flush
- // before rendering on the UV part of the image.
- device_ctx->Flush();
-
device_ctx->OMSetRenderTargets(1, &nv12_UV_rt, nullptr);
device_ctx->VSSetShader(convert_UV_vs.get(), nullptr, 0);
device_ctx->PSSetShader(convert_UV_ps.get(), nullptr, 0);
From 191b929ab0f4de5c67a07896c88a9d4ec5de422a Mon Sep 17 00:00:00 2001
From: Chase Payne
Date: Fri, 7 Apr 2023 14:54:28 -0500
Subject: [PATCH 012/144] Make installer DPI aware so it doesn't appear blurry
to 4k screens
---
CMakeLists.txt | 1 +
1 file changed, 1 insertion(+)
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 641b2712b6f..f7f80a6ba94 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -789,6 +789,7 @@ if(WIN32) # see options at: https://cmake.org/cmake/help/latest/cpack_gen/nsis.h
"https://sunshinestream.readthedocs.io" "Sunshine documentation"
"https://app.lizardbyte.dev" "LizardByte Web Site"
"https://app.lizardbyte.dev/support" "LizardByte Support")
+ set(CPACK_NSIS_MANIFEST_DPI_AWARE true)
# Setting components groups and dependencies
# sunshine binary
From eed27d3c0ad7390191f7daaba3d58708ea72c799 Mon Sep 17 00:00:00 2001
From: ns6089 <61738816+ns6089@users.noreply.github.com>
Date: Wed, 29 Mar 2023 18:07:20 +0300
Subject: [PATCH 013/144] Decrease normal capture buffer to single image
---
src/platform/common.h | 30 ++++++---
src/platform/linux/cuda.cpp | 32 +++++++--
src/platform/linux/cuda.cu | 4 +-
src/platform/linux/kmsgrab.cpp | 62 ++++++++++++-----
src/platform/linux/wlgrab.cpp | 64 +++++++++++++-----
src/platform/linux/x11grab.cpp | 78 ++++++++++++++-------
src/platform/macos/display.mm | 45 ++++++++-----
src/platform/windows/display.h | 9 +--
src/platform/windows/display_base.cpp | 16 +++--
src/platform/windows/display_ram.cpp | 9 ++-
src/platform/windows/display_vram.cpp | 34 +++++++++-
src/video.cpp | 97 +++++++++++++++++----------
12 files changed, 334 insertions(+), 146 deletions(-)
diff --git a/src/platform/common.h b/src/platform/common.h
index 1175f889934..e59d0a39b38 100644
--- a/src/platform/common.h
+++ b/src/platform/common.h
@@ -169,7 +169,7 @@ namespace platf {
virtual ~deinit_t() = default;
};
- struct img_t {
+ struct img_t: std::enable_shared_from_this {
public:
img_t() = default;
@@ -245,6 +245,7 @@ namespace platf {
ok,
reinit,
timeout,
+ interrupted,
error
};
@@ -255,20 +256,33 @@ namespace platf {
* If a frame was captured, frame_captured will be true. If a timeout occurred, it will be false.
*
* On Break Request -->
- * Returns nullptr
+ * Returns false
*
* On Success -->
- * Returns the image object that should be filled next.
- * This may or may not be the image send with the callback
+ * Returns true
*/
- using snapshot_cb_t = std::function(std::shared_ptr &img, bool frame_captured)>;
+ using push_captured_image_cb_t = std::function &&img, bool frame_captured)>;
+
+ /**
+ * Use to get free image from the pool. Calls must be synchronized.
+ * Blocks until there is free image in the pool or capture is interrupted.
+ *
+ * Returns:
+ * 'true' on success, img_out contains free image
+ * 'false' when capture has been interrupted, img_out contains nullptr
+ */
+ using pull_free_image_cb_t = std::function &img_out)>;
display_t() noexcept:
offset_x { 0 }, offset_y { 0 } {}
/**
- * snapshot_cb --> the callback
- * std::shared_ptr img --> The first image to use
+ * push_captured_image_cb --> The callback that is called with captured image,
+ * must be called from the same thread as capture()
+ * pull_free_image_cb --> Capture backends call this callback to get empty image
+ * from the pool. If backend uses multiple threads, calls to this
+ * callback must be synchronized. Calls to this callback and
+ * push_captured_image_cb must be synchronized as well.
* bool *cursor --> A pointer to the flag that indicates wether the cursor should be captured as well
*
* Returns either:
@@ -277,7 +291,7 @@ namespace platf {
* capture_e::reinit when need of reinitialization
*/
virtual capture_e
- capture(snapshot_cb_t &&snapshot_cb, std::shared_ptr img, bool *cursor) = 0;
+ capture(const push_captured_image_cb_t &push_captured_image_cb, const pull_free_image_cb_t &pull_free_image_cb, bool *cursor) = 0;
virtual std::shared_ptr
alloc_img() = 0;
diff --git a/src/platform/linux/cuda.cpp b/src/platform/linux/cuda.cpp
index d1f8c1485d9..55a4a47f858 100644
--- a/src/platform/linux/cuda.cpp
+++ b/src/platform/linux/cuda.cpp
@@ -504,9 +504,16 @@ namespace cuda {
}
platf::capture_e
- capture(snapshot_cb_t &&snapshot_cb, std::shared_ptr img, bool *cursor) override {
+ capture(const push_captured_image_cb_t &push_captured_image_cb, const pull_free_image_cb_t &pull_free_image_cb, bool *cursor) override {
auto next_frame = std::chrono::steady_clock::now();
+ {
+ // We must create at least one texture on this thread before calling NvFBCToCudaSetUp()
+ // Otherwise it fails with "Unable to register an OpenGL buffer to a CUDA resource (result: 201)" message
+ std::shared_ptr img_dummy;
+ pull_free_image_cb(img_dummy);
+ }
+
// Force display_t::capture to initialize handle_t::capture
cursor_visible = !*cursor;
@@ -515,7 +522,7 @@ namespace cuda {
handle.reset();
});
- while (img) {
+ while (true) {
auto now = std::chrono::steady_clock::now();
if (next_frame > now) {
std::this_thread::sleep_for((next_frame - now) / 3 * 2);
@@ -526,16 +533,22 @@ namespace cuda {
}
next_frame = now + delay;
- auto status = snapshot(img.get(), 150ms, *cursor);
+ std::shared_ptr img_out;
+ auto status = snapshot(pull_free_image_cb, img_out, 150ms, *cursor);
switch (status) {
case platf::capture_e::reinit:
case platf::capture_e::error:
+ case platf::capture_e::interrupted:
return status;
case platf::capture_e::timeout:
- img = snapshot_cb(img, false);
+ if (!push_captured_image_cb(std::move(img_out), false)) {
+ return platf::capture_e::ok;
+ }
break;
case platf::capture_e::ok:
- img = snapshot_cb(img, true);
+ if (!push_captured_image_cb(std::move(img_out), true)) {
+ return platf::capture_e::ok;
+ }
break;
default:
BOOST_LOG(error) << "Unrecognized capture status ["sv << (int) status << ']';
@@ -618,7 +631,7 @@ namespace cuda {
}
platf::capture_e
- snapshot(platf::img_t *img, std::chrono::milliseconds timeout, bool cursor) {
+ snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr &img_out, std::chrono::milliseconds timeout, bool cursor) {
if (cursor != cursor_visible) {
auto status = reinit(cursor);
if (status != platf::capture_e::ok) {
@@ -646,7 +659,12 @@ namespace cuda {
return platf::capture_e::error;
}
- if (((img_t *) img)->tex.copy((std::uint8_t *) device_ptr, img->height, img->row_pitch)) {
+ if (!pull_free_image_cb(img_out)) {
+ return platf::capture_e::interrupted;
+ }
+ auto img = (img_t *) img_out.get();
+
+ if (img->tex.copy((std::uint8_t *) device_ptr, img->height, img->row_pitch)) {
return platf::capture_e::error;
}
diff --git a/src/platform/linux/cuda.cu b/src/platform/linux/cuda.cu
index b81cd40d85a..ba630c03fa7 100644
--- a/src/platform/linux/cuda.cu
+++ b/src/platform/linux/cuda.cu
@@ -35,7 +35,7 @@ using namespace std::literals;
* Therefore, some declarations need to be added explicitely
*/
namespace platf {
-struct img_t {
+struct img_t: std::enable_shared_from_this {
public:
std::uint8_t *data {};
std::int32_t width {};
@@ -70,7 +70,7 @@ struct alignas(16) color_extern_t {
static_assert(sizeof(video::color_t) == sizeof(video::color_extern_t), "color matrix struct mismatch");
-extern color_t colors[4];
+extern color_t colors[6];
} // namespace video
//////////////////// End special declarations
diff --git a/src/platform/linux/kmsgrab.cpp b/src/platform/linux/kmsgrab.cpp
index 392bb65db98..cffa98c0f35 100644
--- a/src/platform/linux/kmsgrab.cpp
+++ b/src/platform/linux/kmsgrab.cpp
@@ -723,10 +723,10 @@ namespace platf {
}
capture_e
- capture(snapshot_cb_t &&snapshot_cb, std::shared_ptr img, bool *cursor) override {
+ capture(const push_captured_image_cb_t &push_captured_image_cb, const pull_free_image_cb_t &pull_free_image_cb, bool *cursor) override {
auto next_frame = std::chrono::steady_clock::now();
- while (img) {
+ while (true) {
auto now = std::chrono::steady_clock::now();
if (next_frame > now) {
@@ -738,16 +738,22 @@ namespace platf {
}
next_frame = now + delay;
- auto status = snapshot(img.get(), 1000ms, *cursor);
+ std::shared_ptr img_out;
+ auto status = snapshot(pull_free_image_cb, img_out, 1000ms, *cursor);
switch (status) {
case platf::capture_e::reinit:
case platf::capture_e::error:
+ case platf::capture_e::interrupted:
return status;
case platf::capture_e::timeout:
- img = snapshot_cb(img, false);
+ if (!push_captured_image_cb(std::move(img_out), false)) {
+ return platf::capture_e::ok;
+ }
break;
case platf::capture_e::ok:
- img = snapshot_cb(img, true);
+ if (!push_captured_image_cb(std::move(img_out), true)) {
+ return platf::capture_e::ok;
+ }
break;
default:
BOOST_LOG(error) << "Unrecognized capture status ["sv << (int) status << ']';
@@ -768,7 +774,7 @@ namespace platf {
}
capture_e
- snapshot(img_t *img_out_base, std::chrono::milliseconds timeout, bool cursor) {
+ snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr &img_out, std::chrono::milliseconds timeout, bool cursor) {
file_t fb_fd[4];
egl::surface_descriptor_t sd;
@@ -793,10 +799,14 @@ namespace platf {
gl::ctx.GetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &h);
BOOST_LOG(debug) << "width and height: w "sv << w << " h "sv << h;
- gl::ctx.GetTextureSubImage(rgb->tex[0], 0, img_offset_x, img_offset_y, 0, width, height, 1, GL_BGRA, GL_UNSIGNED_BYTE, img_out_base->height * img_out_base->row_pitch, img_out_base->data);
+ if (!pull_free_image_cb(img_out)) {
+ return platf::capture_e::interrupted;
+ }
+
+ gl::ctx.GetTextureSubImage(rgb->tex[0], 0, img_offset_x, img_offset_y, 0, width, height, 1, GL_BGRA, GL_UNSIGNED_BYTE, img_out->height * img_out->row_pitch, img_out->data);
if (cursor_opt && cursor) {
- cursor_opt->blend(*img_out_base, img_offset_x, img_offset_y);
+ cursor_opt->blend(*img_out, img_offset_x, img_offset_y);
}
return capture_e::ok;
@@ -855,14 +865,23 @@ namespace platf {
int
dummy_img(platf::img_t *img) override {
- return snapshot(img, 1s, false) != capture_e::ok;
+ // TODO: stop cheating and give black image
+ if (!img) {
+ return -1;
+ };
+ auto pull_dummy_img_callback = [&img](std::shared_ptr &img_out) -> bool {
+ img_out = img->shared_from_this();
+ return true;
+ };
+ std::shared_ptr img_out;
+ return snapshot(pull_dummy_img_callback, img_out, 1s, false) != platf::capture_e::ok;
}
capture_e
- capture(snapshot_cb_t &&snapshot_cb, std::shared_ptr img, bool *cursor) {
+ capture(const push_captured_image_cb_t &push_captured_image_cb, const pull_free_image_cb_t &pull_free_image_cb, bool *cursor) {
auto next_frame = std::chrono::steady_clock::now();
- while (img) {
+ while (true) {
auto now = std::chrono::steady_clock::now();
if (next_frame > now) {
@@ -874,16 +893,22 @@ namespace platf {
}
next_frame = now + delay;
- auto status = snapshot(img.get(), 1000ms, *cursor);
+ std::shared_ptr img_out;
+ auto status = snapshot(pull_free_image_cb, img_out, 1000ms, *cursor);
switch (status) {
case platf::capture_e::reinit:
case platf::capture_e::error:
+ case platf::capture_e::interrupted:
return status;
case platf::capture_e::timeout:
- img = snapshot_cb(img, false);
+ if (!push_captured_image_cb(std::move(img_out), false)) {
+ return platf::capture_e::ok;
+ }
break;
case platf::capture_e::ok:
- img = snapshot_cb(img, true);
+ if (!push_captured_image_cb(std::move(img_out), true)) {
+ return platf::capture_e::ok;
+ }
break;
default:
BOOST_LOG(error) << "Unrecognized capture status ["sv << (int) status << ']';
@@ -895,10 +920,13 @@ namespace platf {
}
capture_e
- snapshot(img_t *img_out_base, std::chrono::milliseconds /* timeout */, bool cursor) {
+ snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr &img_out, std::chrono::milliseconds /* timeout */, bool cursor) {
file_t fb_fd[4];
- auto img = (egl::img_descriptor_t *) img_out_base;
+ if (!pull_free_image_cb(img_out)) {
+ return platf::capture_e::interrupted;
+ }
+ auto img = (egl::img_descriptor_t *) img_out.get();
img->reset();
auto status = refresh(fb_fd, &img->sd);
@@ -909,7 +937,7 @@ namespace platf {
img->sequence = ++sequence;
if (!cursor || !cursor_opt) {
- img_out_base->data = nullptr;
+ img->data = nullptr;
for (auto x = 0; x < 4; ++x) {
fb_fd[x].release();
diff --git a/src/platform/linux/wlgrab.cpp b/src/platform/linux/wlgrab.cpp
index db4d24a5093..5511ea3d600 100644
--- a/src/platform/linux/wlgrab.cpp
+++ b/src/platform/linux/wlgrab.cpp
@@ -80,7 +80,7 @@ namespace wl {
}
inline platf::capture_e
- snapshot(platf::img_t *img_out_base, std::chrono::milliseconds timeout, bool cursor) {
+ snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr &img_out, std::chrono::milliseconds timeout, bool cursor) {
auto to = std::chrono::steady_clock::now() + timeout;
dmabuf.listen(interface.dmabuf_manager, output, cursor);
@@ -118,10 +118,10 @@ namespace wl {
class wlr_ram_t: public wlr_t {
public:
platf::capture_e
- capture(snapshot_cb_t &&snapshot_cb, std::shared_ptr img, bool *cursor) override {
+ capture(const push_captured_image_cb_t &push_captured_image_cb, const pull_free_image_cb_t &pull_free_image_cb, bool *cursor) override {
auto next_frame = std::chrono::steady_clock::now();
- while (img) {
+ while (true) {
auto now = std::chrono::steady_clock::now();
if (next_frame > now) {
@@ -132,16 +132,22 @@ namespace wl {
}
next_frame = now + delay;
- auto status = snapshot(img.get(), 1000ms, *cursor);
+ std::shared_ptr img_out;
+ auto status = snapshot(pull_free_image_cb, img_out, 1000ms, *cursor);
switch (status) {
case platf::capture_e::reinit:
case platf::capture_e::error:
+ case platf::capture_e::interrupted:
return status;
case platf::capture_e::timeout:
- img = snapshot_cb(img, false);
+ if (!push_captured_image_cb(std::move(img_out), false)) {
+ return platf::capture_e::ok;
+ }
break;
case platf::capture_e::ok:
- img = snapshot_cb(img, true);
+ if (!push_captured_image_cb(std::move(img_out), true)) {
+ return platf::capture_e::ok;
+ }
break;
default:
BOOST_LOG(error) << "Unrecognized capture status ["sv << (int) status << ']';
@@ -153,8 +159,8 @@ namespace wl {
}
platf::capture_e
- snapshot(platf::img_t *img_out_base, std::chrono::milliseconds timeout, bool cursor) {
- auto status = wlr_t::snapshot(img_out_base, timeout, cursor);
+ snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr &img_out, std::chrono::milliseconds timeout, bool cursor) {
+ auto status = wlr_t::snapshot(pull_free_image_cb, img_out, timeout, cursor);
if (status != platf::capture_e::ok) {
return status;
}
@@ -167,6 +173,10 @@ namespace wl {
return platf::capture_e::reinit;
}
+ if (!pull_free_image_cb(img_out)) {
+ return platf::capture_e::interrupted;
+ }
+
gl::ctx.BindTexture(GL_TEXTURE_2D, (*rgb_opt)->tex[0]);
int w, h;
@@ -174,7 +184,7 @@ namespace wl {
gl::ctx.GetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &h);
BOOST_LOG(debug) << "width and height: w "sv << w << " h "sv << h;
- gl::ctx.GetTextureSubImage((*rgb_opt)->tex[0], 0, 0, 0, 0, width, height, 1, GL_BGRA, GL_UNSIGNED_BYTE, img_out_base->height * img_out_base->row_pitch, img_out_base->data);
+ gl::ctx.GetTextureSubImage((*rgb_opt)->tex[0], 0, 0, 0, 0, width, height, 1, GL_BGRA, GL_UNSIGNED_BYTE, img_out->height * img_out->row_pitch, img_out->data);
gl::ctx.BindTexture(GL_TEXTURE_2D, 0);
return platf::capture_e::ok;
@@ -229,10 +239,10 @@ namespace wl {
class wlr_vram_t: public wlr_t {
public:
platf::capture_e
- capture(snapshot_cb_t &&snapshot_cb, std::shared_ptr img, bool *cursor) override {
+ capture(const push_captured_image_cb_t &push_captured_image_cb, const pull_free_image_cb_t &pull_free_image_cb, bool *cursor) override {
auto next_frame = std::chrono::steady_clock::now();
- while (img) {
+ while (true) {
auto now = std::chrono::steady_clock::now();
if (next_frame > now) {
@@ -243,16 +253,22 @@ namespace wl {
}
next_frame = now + delay;
- auto status = snapshot(img.get(), 1000ms, *cursor);
+ std::shared_ptr img_out;
+ auto status = snapshot(pull_free_image_cb, img_out, 1000ms, *cursor);
switch (status) {
case platf::capture_e::reinit:
case platf::capture_e::error:
+ case platf::capture_e::interrupted:
return status;
case platf::capture_e::timeout:
- img = snapshot_cb(img, false);
+ if (!push_captured_image_cb(std::move(img_out), false)) {
+ return platf::capture_e::ok;
+ }
break;
case platf::capture_e::ok:
- img = snapshot_cb(img, true);
+ if (!push_captured_image_cb(std::move(img_out), true)) {
+ return platf::capture_e::ok;
+ }
break;
default:
BOOST_LOG(error) << "Unrecognized capture status ["sv << (int) status << ']';
@@ -264,13 +280,16 @@ namespace wl {
}
platf::capture_e
- snapshot(platf::img_t *img_out_base, std::chrono::milliseconds timeout, bool cursor) {
- auto status = wlr_t::snapshot(img_out_base, timeout, cursor);
+ snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr &img_out, std::chrono::milliseconds timeout, bool cursor) {
+ auto status = wlr_t::snapshot(pull_free_image_cb, img_out, timeout, cursor);
if (status != platf::capture_e::ok) {
return status;
}
- auto img = (egl::img_descriptor_t *) img_out_base;
+ if (!pull_free_image_cb(img_out)) {
+ return platf::capture_e::interrupted;
+ }
+ auto img = (egl::img_descriptor_t *) img_out.get();
img->reset();
auto current_frame = dmabuf.current_frame;
@@ -311,7 +330,16 @@ namespace wl {
int
dummy_img(platf::img_t *img) override {
- return snapshot(img, 1000ms, false) != platf::capture_e::ok;
+ // TODO: stop cheating and give black image
+ if (!img) {
+ return -1;
+ };
+ auto pull_dummy_img_callback = [&img](std::shared_ptr &img_out) -> bool {
+ img_out = img->shared_from_this();
+ return true;
+ };
+ std::shared_ptr img_out;
+ return snapshot(pull_dummy_img_callback, img_out, 1000ms, false) != platf::capture_e::ok;
}
std::uint64_t sequence {};
diff --git a/src/platform/linux/x11grab.cpp b/src/platform/linux/x11grab.cpp
index ae294cf1a78..9a46afffec1 100644
--- a/src/platform/linux/x11grab.cpp
+++ b/src/platform/linux/x11grab.cpp
@@ -476,10 +476,10 @@ namespace platf {
}
capture_e
- capture(snapshot_cb_t &&snapshot_cb, std::shared_ptr img, bool *cursor) override {
+ capture(const push_captured_image_cb_t &push_captured_image_cb, const pull_free_image_cb_t &pull_free_image_cb, bool *cursor) override {
auto next_frame = std::chrono::steady_clock::now();
- while (img) {
+ while (true) {
auto now = std::chrono::steady_clock::now();
if (next_frame > now) {
@@ -491,16 +491,22 @@ namespace platf {
}
next_frame = now + delay;
- auto status = snapshot(img.get(), 1000ms, *cursor);
+ std::shared_ptr img_out;
+ auto status = snapshot(pull_free_image_cb, img_out, 1000ms, *cursor);
switch (status) {
case platf::capture_e::reinit:
case platf::capture_e::error:
+ case platf::capture_e::interrupted:
return status;
case platf::capture_e::timeout:
- img = snapshot_cb(img, false);
+ if (!push_captured_image_cb(std::move(img_out), false)) {
+ return platf::capture_e::ok;
+ }
break;
case platf::capture_e::ok:
- img = snapshot_cb(img, true);
+ if (!push_captured_image_cb(std::move(img_out), true)) {
+ return platf::capture_e::ok;
+ }
break;
default:
BOOST_LOG(error) << "Unrecognized capture status ["sv << (int) status << ']';
@@ -512,7 +518,7 @@ namespace platf {
}
capture_e
- snapshot(img_t *img_out_base, std::chrono::milliseconds timeout, bool cursor) {
+ snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr &img_out, std::chrono::milliseconds timeout, bool cursor) {
refresh();
//The whole X server changed, so we must reinit everything
@@ -520,18 +526,23 @@ namespace platf {
BOOST_LOG(warning) << "X dimensions changed in non-SHM mode, request reinit"sv;
return capture_e::reinit;
}
- XImage *img { x11::GetImage(xdisplay.get(), xwindow, offset_x, offset_y, width, height, AllPlanes, ZPixmap) };
- auto img_out = (x11_img_t *) img_out_base;
- img_out->width = img->width;
- img_out->height = img->height;
- img_out->data = (uint8_t *) img->data;
- img_out->row_pitch = img->bytes_per_line;
- img_out->pixel_pitch = img->bits_per_pixel / 8;
- img_out->img.reset(img);
+ if (!pull_free_image_cb(img_out)) {
+ return platf::capture_e::interrupted;
+ }
+ auto img = (x11_img_t *) img_out.get();
+
+ XImage *x_img { x11::GetImage(xdisplay.get(), xwindow, offset_x, offset_y, width, height, AllPlanes, ZPixmap) };
+
+ img->width = x_img->width;
+ img->height = x_img->height;
+ img->data = (uint8_t *) x_img->data;
+ img->row_pitch = x_img->bytes_per_line;
+ img->pixel_pitch = x_img->bits_per_pixel / 8;
+ img->img.reset(x_img);
if (cursor) {
- blend_cursor(xdisplay.get(), *img_out_base, offset_x, offset_y);
+ blend_cursor(xdisplay.get(), *img, offset_x, offset_y);
}
return capture_e::ok;
@@ -559,7 +570,16 @@ namespace platf {
int
dummy_img(img_t *img) override {
- snapshot(img, 0s, true);
+ // TODO: stop cheating and give black image
+ if (!img) {
+ return -1;
+ };
+ auto pull_dummy_img_callback = [&img](std::shared_ptr &img_out) -> bool {
+ img_out = img->shared_from_this();
+ return true;
+ };
+ std::shared_ptr img_out;
+ snapshot(pull_dummy_img_callback, img_out, 0s, true);
return 0;
}
};
@@ -594,10 +614,10 @@ namespace platf {
}
capture_e
- capture(snapshot_cb_t &&snapshot_cb, std::shared_ptr img, bool *cursor) override {
+ capture(const push_captured_image_cb_t &push_captured_image_cb, const pull_free_image_cb_t &pull_free_image_cb, bool *cursor) override {
auto next_frame = std::chrono::steady_clock::now();
- while (img) {
+ while (true) {
auto now = std::chrono::steady_clock::now();
if (next_frame > now) {
@@ -609,16 +629,22 @@ namespace platf {
}
next_frame = now + delay;
- auto status = snapshot(img.get(), 1000ms, *cursor);
+ std::shared_ptr img_out;
+ auto status = snapshot(pull_free_image_cb, img_out, 1000ms, *cursor);
switch (status) {
case platf::capture_e::reinit:
case platf::capture_e::error:
+ case platf::capture_e::interrupted:
return status;
case platf::capture_e::timeout:
- img = snapshot_cb(img, false);
+ if (!push_captured_image_cb(std::move(img_out), false)) {
+ return platf::capture_e::ok;
+ }
break;
case platf::capture_e::ok:
- img = snapshot_cb(img, true);
+ if (!push_captured_image_cb(std::move(img_out), true)) {
+ return platf::capture_e::ok;
+ }
break;
default:
BOOST_LOG(error) << "Unrecognized capture status ["sv << (int) status << ']';
@@ -630,7 +656,7 @@ namespace platf {
}
capture_e
- snapshot(img_t *img, std::chrono::milliseconds timeout, bool cursor) {
+ snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr &img_out, std::chrono::milliseconds timeout, bool cursor) {
//The whole X server changed, so we must reinit everything
if (xattr.width != env_width || xattr.height != env_height) {
BOOST_LOG(warning) << "X dimensions changed in SHM mode, request reinit"sv;
@@ -645,10 +671,14 @@ namespace platf {
return capture_e::reinit;
}
- std::copy_n((std::uint8_t *) data.data, frame_size(), img->data);
+ if (!pull_free_image_cb(img_out)) {
+ return platf::capture_e::interrupted;
+ }
+
+ std::copy_n((std::uint8_t *) data.data, frame_size(), img_out->data);
if (cursor) {
- blend_cursor(shm_xdisplay.get(), *img, offset_x, offset_y);
+ blend_cursor(shm_xdisplay.get(), *img_out, offset_x, offset_y);
}
return capture_e::ok;
diff --git a/src/platform/macos/display.mm b/src/platform/macos/display.mm
index f053ea03911..a09dad19351 100644
--- a/src/platform/macos/display.mm
+++ b/src/platform/macos/display.mm
@@ -37,38 +37,46 @@
}
capture_e
- capture(snapshot_cb_t &&snapshot_cb, std::shared_ptr img, bool *cursor) override {
- __block auto img_next = std::move(img);
-
+ capture(const push_captured_image_cb_t &push_captured_image_cb, const pull_free_image_cb_t &pull_free_image_cb, bool *cursor) override {
auto signal = [av_capture capture:^(CMSampleBufferRef sampleBuffer) {
- auto av_img_next = std::static_pointer_cast(img_next);
-
CFRetain(sampleBuffer);
CVPixelBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
CVPixelBufferLockBaseAddress(pixelBuffer, kCVPixelBufferLock_ReadOnly);
- if (av_img_next->pixel_buffer != nullptr)
- CVPixelBufferUnlockBaseAddress(av_img_next->pixel_buffer, 0);
+ std::shared_ptr img_out;
+ if (!pull_free_image_cb(img_out)) {
+ // got interrupt signal
+ // returning false here stops capture backend
+ return false;
+ }
+ auto av_img = std::static_pointer_cast(img_out);
+
+ if (av_img->pixel_buffer != nullptr)
+ CVPixelBufferUnlockBaseAddress(av_img->pixel_buffer, 0);
- if (av_img_next->sample_buffer != nullptr)
- CFRelease(av_img_next->sample_buffer);
+ if (av_img->sample_buffer != nullptr)
+ CFRelease(av_img->sample_buffer);
- av_img_next->sample_buffer = sampleBuffer;
- av_img_next->pixel_buffer = pixelBuffer;
- img_next->data = (uint8_t *) CVPixelBufferGetBaseAddress(pixelBuffer);
+ av_img->sample_buffer = sampleBuffer;
+ av_img->pixel_buffer = pixelBuffer;
+ img_out->data = (uint8_t *) CVPixelBufferGetBaseAddress(pixelBuffer);
size_t extraPixels[4];
CVPixelBufferGetExtendedPixels(pixelBuffer, &extraPixels[0], &extraPixels[1], &extraPixels[2], &extraPixels[3]);
- img_next->width = CVPixelBufferGetWidth(pixelBuffer) + extraPixels[0] + extraPixels[1];
- img_next->height = CVPixelBufferGetHeight(pixelBuffer) + extraPixels[2] + extraPixels[3];
- img_next->row_pitch = CVPixelBufferGetBytesPerRow(pixelBuffer);
- img_next->pixel_pitch = img_next->row_pitch / img_next->width;
+ img_out->width = CVPixelBufferGetWidth(pixelBuffer) + extraPixels[0] + extraPixels[1];
+ img_out->height = CVPixelBufferGetHeight(pixelBuffer) + extraPixels[2] + extraPixels[3];
+ img_out->row_pitch = CVPixelBufferGetBytesPerRow(pixelBuffer);
+ img_out->pixel_pitch = img_out->row_pitch / img_out->width;
- img_next = snapshot_cb(img_next, true);
+ if (!push_captured_image_cb(std::move(img_out), false)) {
+ // got interrupt signal
+ // returning false here stops capture backend
+ return false;
+ }
- return img_next != nullptr;
+ return true;
}];
// FIXME: We should time out if an image isn't returned for a while
@@ -132,6 +140,7 @@
img->row_pitch = CVPixelBufferGetBytesPerRow(pixelBuffer);
img->pixel_pitch = img->row_pitch / img->width;
+ // returning false here stops capture backend
return false;
}];
diff --git a/src/platform/windows/display.h b/src/platform/windows/display.h
index 17e79e26b70..349d4c4c561 100644
--- a/src/platform/windows/display.h
+++ b/src/platform/windows/display.h
@@ -125,8 +125,9 @@ namespace platf::dxgi {
public:
int
init(const ::video::config_t &config, const std::string &display_name);
+
capture_e
- capture(snapshot_cb_t &&snapshot_cb, std::shared_ptr img, bool *cursor) override;
+ capture(const push_captured_image_cb_t &push_captured_image_cb, const pull_free_image_cb_t &pull_free_image_cb, bool *cursor) override;
std::chrono::nanoseconds delay;
@@ -168,7 +169,7 @@ namespace platf::dxgi {
colorspace_to_string(DXGI_COLOR_SPACE_TYPE type);
virtual capture_e
- snapshot(img_t *img, std::chrono::milliseconds timeout, bool cursor_visible) = 0;
+ snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr &img_out, std::chrono::milliseconds timeout, bool cursor_visible) = 0;
virtual int
complete_img(img_t *img, bool dummy) = 0;
virtual std::vector
@@ -178,7 +179,7 @@ namespace platf::dxgi {
class display_ram_t: public display_base_t {
public:
virtual capture_e
- snapshot(img_t *img, std::chrono::milliseconds timeout, bool cursor_visible) override;
+ snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr &img_out, std::chrono::milliseconds timeout, bool cursor_visible) override;
std::shared_ptr
alloc_img() override;
@@ -200,7 +201,7 @@ namespace platf::dxgi {
class display_vram_t: public display_base_t, public std::enable_shared_from_this {
public:
virtual capture_e
- snapshot(img_t *img, std::chrono::milliseconds timeout, bool cursor_visible) override;
+ snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr &img_out, std::chrono::milliseconds timeout, bool cursor_visible) override;
std::shared_ptr
alloc_img() override;
diff --git a/src/platform/windows/display_base.cpp b/src/platform/windows/display_base.cpp
index b92ac7b7c2e..02b3fca136c 100644
--- a/src/platform/windows/display_base.cpp
+++ b/src/platform/windows/display_base.cpp
@@ -92,7 +92,7 @@ namespace platf::dxgi {
}
capture_e
- display_base_t::capture(snapshot_cb_t &&snapshot_cb, std::shared_ptr<::platf::img_t> img, bool *cursor) {
+ display_base_t::capture(const push_captured_image_cb_t &push_captured_image_cb, const pull_free_image_cb_t &pull_free_image_cb, bool *cursor) {
auto next_frame = std::chrono::steady_clock::now();
// Use CREATE_WAITABLE_TIMER_HIGH_RESOLUTION if supported (Windows 10 1809+)
@@ -110,7 +110,7 @@ namespace platf::dxgi {
CloseHandle(timer);
});
- while (img) {
+ while (true) {
// This will return false if the HDR state changes or for any number of other
// display or GPU changes. We should reinit to examine the updated state of
// the display subsystem. It is recommended to call this once per frame.
@@ -135,16 +135,22 @@ namespace platf::dxgi {
next_frame = std::chrono::steady_clock::now() + delay;
}
- auto status = snapshot(img.get(), 1000ms, *cursor);
+ std::shared_ptr img_out;
+ auto status = snapshot(pull_free_image_cb, img_out, 1000ms, *cursor);
switch (status) {
case platf::capture_e::reinit:
case platf::capture_e::error:
+ case platf::capture_e::interrupted:
return status;
case platf::capture_e::timeout:
- img = snapshot_cb(img, false);
+ if (!push_captured_image_cb(std::move(img_out), false)) {
+ return capture_e::ok;
+ }
break;
case platf::capture_e::ok:
- img = snapshot_cb(img, true);
+ if (!push_captured_image_cb(std::move(img_out), true)) {
+ return capture_e::ok;
+ }
break;
default:
BOOST_LOG(error) << "Unrecognized capture status ["sv << (int) status << ']';
diff --git a/src/platform/windows/display_ram.cpp b/src/platform/windows/display_ram.cpp
index 8d24d07a2bd..0254668187a 100644
--- a/src/platform/windows/display_ram.cpp
+++ b/src/platform/windows/display_ram.cpp
@@ -171,9 +171,7 @@ namespace platf::dxgi {
}
capture_e
- display_ram_t::snapshot(::platf::img_t *img_base, std::chrono::milliseconds timeout, bool cursor_visible) {
- auto img = (img_t *) img_base;
-
+ display_ram_t::snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr &img_out, std::chrono::milliseconds timeout, bool cursor_visible) {
HRESULT status;
DXGI_OUTDUPL_FRAME_INFO frame_info;
@@ -269,6 +267,11 @@ namespace platf::dxgi {
}
}
+ if (!pull_free_image_cb(img_out)) {
+ return capture_e::interrupted;
+ }
+ auto img = (img_t *) img_out.get();
+
// If we don't know the final capture format yet, encode a dummy image
if (capture_format == DXGI_FORMAT_UNKNOWN) {
BOOST_LOG(debug) << "Capture format is still unknown. Encoding a blank image"sv;
diff --git a/src/platform/windows/display_vram.cpp b/src/platform/windows/display_vram.cpp
index 45428c02492..fa3ecf4df4c 100644
--- a/src/platform/windows/display_vram.cpp
+++ b/src/platform/windows/display_vram.cpp
@@ -311,6 +311,16 @@ namespace platf::dxgi {
public:
int
convert(platf::img_t &img_base) override {
+ // Garbage collect mapped capture images whose weak references have expired
+ for (auto it = img_ctx_map.begin(); it != img_ctx_map.end();) {
+ if (it->second.img_weak.expired()) {
+ it = img_ctx_map.erase(it);
+ }
+ else {
+ it++;
+ }
+ }
+
auto &img = (img_d3d_t &) img_base;
auto &img_ctx = img_ctx_map[img.id];
@@ -342,6 +352,9 @@ namespace platf::dxgi {
// Release encoder mutex to allow capture code to reuse this image
img_ctx.encoder_mutex->ReleaseSync(0);
+ ID3D11ShaderResourceView *emptyShaderResourceView = nullptr;
+ device_ctx->PSSetShaderResources(0, 1, &emptyShaderResourceView);
+
return 0;
}
@@ -650,12 +663,15 @@ namespace platf::dxgi {
shader_res_t encoder_input_res;
keyed_mutex_t encoder_mutex;
+ std::weak_ptr img_weak;
+
void
reset() {
capture_texture_p = nullptr;
encoder_texture.reset();
encoder_input_res.reset();
encoder_mutex.reset();
+ img_weak.reset();
}
};
@@ -699,6 +715,9 @@ namespace platf::dxgi {
}
img_ctx.capture_texture_p = img.capture_texture.get();
+
+ img_ctx.img_weak = img.weak_from_this();
+
return 0;
}
@@ -789,9 +808,7 @@ namespace platf::dxgi {
}
capture_e
- display_vram_t::snapshot(platf::img_t *img_base, std::chrono::milliseconds timeout, bool cursor_visible) {
- auto img = (img_d3d_t *) img_base;
-
+ display_vram_t::snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr &img_out, std::chrono::milliseconds timeout, bool cursor_visible) {
HRESULT status;
DXGI_OUTDUPL_FRAME_INFO frame_info;
@@ -839,6 +856,11 @@ namespace platf::dxgi {
cursor_xor.set_pos(frame_info.PointerPosition.Position.x, frame_info.PointerPosition.Position.y, frame_info.PointerPosition.Visible);
}
+ if (!pull_free_image_cb(img_out)) {
+ return capture_e::interrupted;
+ }
+ auto img = (img_d3d_t *) img_out.get();
+
if (frame_update_flag) {
texture2d_t src {};
@@ -969,6 +991,12 @@ namespace platf::dxgi {
}
device_ctx->OMSetBlendState(blend_disable.get(), nullptr, 0xFFFFFFFFu);
+
+ ID3D11RenderTargetView *emptyRenderTarget = nullptr;
+ device_ctx->OMSetRenderTargets(1, &emptyRenderTarget, nullptr);
+ device_ctx->RSSetViewports(0, nullptr);
+ ID3D11ShaderResourceView *emptyShaderResourceView = nullptr;
+ device_ctx->PSSetShaderResources(0, 1, &emptyShaderResourceView);
}
// Release the mutex to allow encoding of this frame
diff --git a/src/video.cpp b/src/video.cpp
index 6066266e21f..302c0c8fbdb 100644
--- a/src/video.cpp
+++ b/src/video.cpp
@@ -2,6 +2,7 @@
#include
#include
+#include
#include
extern "C" {
@@ -14,7 +15,6 @@ extern "C" {
#include "input.h"
#include "main.h"
#include "platform/common.h"
-#include "round_robin.h"
#include "sync.h"
#include "video.h"
@@ -791,16 +791,45 @@ namespace video {
}
display_wp = disp;
- std::vector> imgs(12);
- auto round_robin = round_robin_util::make_round_robin>(std::begin(imgs), std::end(imgs));
-
- for (auto &img : imgs) {
- img = disp->alloc_img();
- if (!img) {
- BOOST_LOG(error) << "Couldn't initialize an image"sv;
- return;
+ constexpr auto capture_buffer_normal_size = 1;
+ constexpr auto capture_buffer_size = 12;
+ std::list> imgs(capture_buffer_size);
+
+ auto pull_free_image_callback = [&](std::shared_ptr &img_out) -> bool {
+ img_out.reset();
+ while (capture_ctx_queue->running()) {
+ for (auto it = imgs.begin(); it != imgs.end(); it++) {
+ // pick first unallocated or unused
+ if (!*it || it->use_count() == 1) {
+ if (!*it) {
+ // allocate if unallocated
+ *it = disp->alloc_img();
+ }
+ img_out = *it;
+ if (it != imgs.begin()) {
+ // move freshly allocated or unused img to the front of the list to prioritize its reusal
+ imgs.erase(it);
+ imgs.push_front(img_out);
+ }
+ break;
+ }
+ }
+ if (img_out) {
+ // unallocate unused above normal buffer size
+ size_t index = 0;
+ for (auto &img : imgs) {
+ if (index >= capture_buffer_normal_size && img && img.use_count() == 1) img.reset();
+ index++;
+ }
+ return true;
+ }
+ else {
+ // sleep and retry if image pool is full
+ std::this_thread::sleep_for(1ms);
+ }
}
- }
+ return false;
+ };
// Capture takes place on this thread
platf::adjust_thread_priority(platf::thread_priority_e::critical);
@@ -808,7 +837,7 @@ namespace video {
while (capture_ctx_queue->running()) {
bool artificial_reinit = false;
- auto status = disp->capture([&](std::shared_ptr &img, bool frame_captured) -> std::shared_ptr {
+ auto push_captured_image_callback = [&](std::shared_ptr &&img, bool frame_captured) -> bool {
KITTY_WHILE_LOOP(auto capture_ctx = std::begin(capture_ctxs), capture_ctx != std::end(capture_ctxs), {
if (!capture_ctx->images->running()) {
capture_ctx = capture_ctxs.erase(capture_ctx);
@@ -824,8 +853,9 @@ namespace video {
})
if (!capture_ctx_queue->running()) {
- return nullptr;
+ return false;
}
+
while (capture_ctx_queue->peek()) {
capture_ctxs.emplace_back(std::move(*capture_ctx_queue->pop()));
}
@@ -834,18 +864,13 @@ namespace video {
artificial_reinit = true;
display_p = std::clamp(*switch_display_event->pop(), 0, (int) display_names.size() - 1);
- return nullptr;
+ return false;
}
- auto &next_img = *round_robin++;
- while (next_img.use_count() > 1) {
- // Sleep a bit to avoid starving the encoder threads
- std::this_thread::sleep_for(2ms);
- }
+ return true;
+ };
- return next_img;
- },
- *round_robin++, &display_cursor);
+ auto status = disp->capture(push_captured_image_callback, pull_free_image_callback, &display_cursor);
if (artificial_reinit && status != platf::capture_e::error) {
status = platf::capture_e::reinit;
@@ -899,21 +924,13 @@ namespace video {
display_wp = disp;
- // Re-allocate images
- for (auto &img : imgs) {
- img = disp->alloc_img();
- if (!img) {
- BOOST_LOG(error) << "Couldn't initialize an image"sv;
- return;
- }
- }
-
reinit_event.reset();
continue;
}
case platf::capture_e::error:
case platf::capture_e::ok:
case platf::capture_e::timeout:
+ case platf::capture_e::interrupted:
return;
default:
BOOST_LOG(error) << "Unrecognized capture status ["sv << (int) status << ']';
@@ -1487,11 +1504,11 @@ namespace video {
auto ec = platf::capture_e::ok;
while (encode_session_ctx_queue.running()) {
- auto snapshot_cb = [&](std::shared_ptr &img, bool frame_captured) -> std::shared_ptr {
+ auto push_captured_image_callback = [&](std::shared_ptr &&img, bool frame_captured) -> bool {
while (encode_session_ctx_queue.peek()) {
auto encode_session_ctx = encode_session_ctx_queue.pop();
if (!encode_session_ctx) {
- return nullptr;
+ return false;
}
synced_session_ctxs.emplace_back(std::make_unique(std::move(*encode_session_ctx)));
@@ -1499,7 +1516,7 @@ namespace video {
auto encode_session = make_synced_session(disp.get(), encoder, *img, *synced_session_ctxs.back());
if (!encode_session) {
ec = platf::capture_e::error;
- return nullptr;
+ return false;
}
synced_sessions.emplace_back(std::move(*encode_session));
@@ -1518,7 +1535,7 @@ namespace video {
}));
if (synced_sessions.empty()) {
- return nullptr;
+ return false;
}
continue;
@@ -1555,18 +1572,24 @@ namespace video {
ec = platf::capture_e::reinit;
display_p = std::clamp(*switch_display_event->pop(), 0, (int) display_names.size() - 1);
- return nullptr;
+ return false;
}
- return img;
+ return true;
+ };
+
+ auto pull_free_image_callback = [&img](std::shared_ptr &img_out) -> bool {
+ img_out = img;
+ return true;
};
- auto status = disp->capture(std::move(snapshot_cb), img, &display_cursor);
+ auto status = disp->capture(push_captured_image_callback, pull_free_image_callback, &display_cursor);
switch (status) {
case platf::capture_e::reinit:
case platf::capture_e::error:
case platf::capture_e::ok:
case platf::capture_e::timeout:
+ case platf::capture_e::interrupted:
return ec != platf::capture_e::ok ? ec : status;
}
}
From ae7ae8a8707804e672ebd3957e80d3dfd1bd7db8 Mon Sep 17 00:00:00 2001
From: ns6089 <61738816+ns6089@users.noreply.github.com>
Date: Sat, 8 Apr 2023 12:23:43 +0300
Subject: [PATCH 014/144] Raise capture buffer normal size to 2
Temporal measure until dynamic resize is merged.
---
src/video.cpp | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/video.cpp b/src/video.cpp
index 302c0c8fbdb..0eb0e6a2836 100644
--- a/src/video.cpp
+++ b/src/video.cpp
@@ -791,7 +791,7 @@ namespace video {
}
display_wp = disp;
- constexpr auto capture_buffer_normal_size = 1;
+ constexpr auto capture_buffer_normal_size = 2;
constexpr auto capture_buffer_size = 12;
std::list> imgs(capture_buffer_size);
From 4e04604696a93020226182cba17562def1890467 Mon Sep 17 00:00:00 2001
From: Cameron Gutman
Date: Sun, 9 Apr 2023 11:55:03 -0500
Subject: [PATCH 015/144] Add support for keyboard input that is not normalized
to US English layout
This is used by the soft keyboards on Android and iOS
---
src/input.cpp | 32 +++++++++++++++++++++++---------
src/platform/common.h | 2 +-
src/platform/linux/input.cpp | 12 +++++++-----
src/platform/macos/input.cpp | 2 +-
src/platform/windows/input.cpp | 9 +++++----
5 files changed, 37 insertions(+), 20 deletions(-)
diff --git a/src/input.cpp b/src/input.cpp
index b0fc2791fa9..381d3097f1f 100644
--- a/src/input.cpp
+++ b/src/input.cpp
@@ -59,8 +59,22 @@ namespace input {
gamepad_mask[id] = false;
}
+ typedef uint32_t key_press_id_t;
+ key_press_id_t
+ make_kpid(uint16_t vk, uint8_t flags) {
+ return (key_press_id_t) vk << 8 | flags;
+ }
+ uint16_t
+ vk_from_kpid(key_press_id_t kpid) {
+ return kpid >> 8;
+ }
+ uint8_t
+ flags_from_kpid(key_press_id_t kpid) {
+ return kpid & 0xFF;
+ }
+
static task_pool_util::TaskPool::task_id_t key_press_repeat_id {};
- static std::unordered_map key_press {};
+ static std::unordered_map key_press {};
static std::array mouse_press {};
static platf::input_t platf_input;
@@ -449,16 +463,16 @@ namespace input {
}
void
- repeat_key(short key_code) {
+ repeat_key(uint16_t key_code, uint8_t flags) {
// If key no longer pressed, stop repeating
- if (!key_press[key_code]) {
+ if (!key_press[make_kpid(key_code, flags)]) {
key_press_repeat_id = nullptr;
return;
}
- platf::keyboard(platf_input, map_keycode(key_code), false);
+ platf::keyboard(platf_input, map_keycode(key_code), false, flags);
- key_press_repeat_id = task_pool.pushDelayed(repeat_key, config::input.key_repeat_period, key_code).task_id;
+ key_press_repeat_id = task_pool.pushDelayed(repeat_key, config::input.key_repeat_period, key_code, flags).task_id;
}
void
@@ -470,7 +484,7 @@ namespace input {
auto release = util::endian::little(packet->header.magic) == KEY_UP_EVENT_MAGIC;
auto keyCode = packet->keyCode & 0x00FF;
- auto &pressed = key_press[keyCode];
+ auto &pressed = key_press[make_kpid(keyCode, packet->flags)];
if (!pressed) {
if (!release) {
// A new key has been pressed down, we need to check for key combo's
@@ -484,7 +498,7 @@ namespace input {
}
if (config::input.key_repeat_delay.count() > 0) {
- key_press_repeat_id = task_pool.pushDelayed(repeat_key, config::input.key_repeat_delay, keyCode).task_id;
+ key_press_repeat_id = task_pool.pushDelayed(repeat_key, config::input.key_repeat_delay, keyCode, packet->flags).task_id;
}
}
else {
@@ -500,7 +514,7 @@ namespace input {
pressed = !release;
update_shortcutFlags(&input->shortcutFlags, map_keycode(keyCode), release);
- platf::keyboard(platf_input, map_keycode(keyCode), release);
+ platf::keyboard(platf_input, map_keycode(keyCode), release, packet->flags);
}
void
@@ -731,7 +745,7 @@ namespace input {
}
for (auto &kp : key_press) {
- platf::keyboard(platf_input, kp.first & 0x00FF, true);
+ platf::keyboard(platf_input, vk_from_kpid(kp.first) & 0x00FF, true, flags_from_kpid(kp.first));
key_press[kp.first] = false;
}
});
diff --git a/src/platform/common.h b/src/platform/common.h
index e59d0a39b38..fa8104dcf64 100644
--- a/src/platform/common.h
+++ b/src/platform/common.h
@@ -436,7 +436,7 @@ namespace platf {
void
hscroll(input_t &input, int distance);
void
- keyboard(input_t &input, uint16_t modcode, bool release);
+ keyboard(input_t &input, uint16_t modcode, bool release, uint8_t flags);
void
gamepad(input_t &input, int nr, const gamepad_state_t &gamepad_state);
void
diff --git a/src/platform/linux/input.cpp b/src/platform/linux/input.cpp
index 5749b30773d..9f277987814 100644
--- a/src/platform/linux/input.cpp
+++ b/src/platform/linux/input.cpp
@@ -1337,14 +1337,15 @@ namespace platf {
* @param input The input_t instance to use.
* @param modcode The moonlight key code.
* @param release Whether the event was a press (false) or a release (true)
+ * @param flags SS_KBE_FLAG_* values
*
* EXAMPLES:
* ```cpp
- * x_keyboard(input, 0x5A, false); // Press Z
+ * x_keyboard(input, 0x5A, false, 0); // Press Z
* ```
*/
static void
- x_keyboard(input_t &input, uint16_t modcode, bool release) {
+ x_keyboard(input_t &input, uint16_t modcode, bool release, uint8_t flags) {
#ifdef SUNSHINE_BUILD_X11
auto keycode = keysym(modcode);
if (keycode.keysym == UNKNOWN) {
@@ -1371,17 +1372,18 @@ namespace platf {
* @param input The input_t instance to use.
* @param modcode The moonlight key code.
* @param release Whether the event was a press (false) or a release (true)
+ * @param flags SS_KBE_FLAG_* values
*
* EXAMPLES:
* ```cpp
- * keyboard(input, 0x5A, false); // Press Z
+ * keyboard(input, 0x5A, false, 0); // Press Z
* ```
*/
void
- keyboard(input_t &input, uint16_t modcode, bool release) {
+ keyboard(input_t &input, uint16_t modcode, bool release, uint8_t flags) {
auto keyboard = ((input_raw_t *) input.get())->keyboard_input.get();
if (!keyboard) {
- x_keyboard(input, modcode, release);
+ x_keyboard(input, modcode, release, flags);
return;
}
diff --git a/src/platform/macos/input.cpp b/src/platform/macos/input.cpp
index 19948a957dd..7565351d3df 100644
--- a/src/platform/macos/input.cpp
+++ b/src/platform/macos/input.cpp
@@ -230,7 +230,7 @@ const KeyCodeMap kKeyCodesMap[] = {
}
void
- keyboard(input_t &input, uint16_t modcode, bool release) {
+ keyboard(input_t &input, uint16_t modcode, bool release, uint8_t flags) {
auto key = keysym(modcode);
BOOST_LOG(debug) << "got keycode: 0x"sv << std::hex << modcode << ", translated to: 0x" << std::hex << key << ", release:" << release;
diff --git a/src/platform/windows/input.cpp b/src/platform/windows/input.cpp
index 69570f148b2..9c0372fe9f8 100644
--- a/src/platform/windows/input.cpp
+++ b/src/platform/windows/input.cpp
@@ -338,15 +338,16 @@ namespace platf {
}
void
- keyboard(input_t &input, uint16_t modcode, bool release) {
+ keyboard(input_t &input, uint16_t modcode, bool release, uint8_t flags) {
auto raw = (input_raw_t *) input.get();
INPUT i {};
i.type = INPUT_KEYBOARD;
auto &ki = i.ki;
- // For some reason, MapVirtualKey(VK_LWIN, MAPVK_VK_TO_VSC) doesn't seem to work :/
- if (modcode != VK_LWIN && modcode != VK_RWIN && modcode != VK_PAUSE && raw->keyboard_layout != NULL) {
+ // If the client did not normalize this VK code to a US English layout, we can't accurately convert it to a scancode.
+ if (!(flags & SS_KBE_FLAG_NON_NORMALIZED) && modcode != VK_LWIN && modcode != VK_RWIN && modcode != VK_PAUSE && raw->keyboard_layout != NULL) {
+ // For some reason, MapVirtualKey(VK_LWIN, MAPVK_VK_TO_VSC) doesn't seem to work :/
ki.wScan = MapVirtualKeyEx(modcode, MAPVK_VK_TO_VSC, raw->keyboard_layout);
}
@@ -355,7 +356,7 @@ namespace platf {
ki.dwFlags = KEYEVENTF_SCANCODE;
}
else {
- // If there is no scancode mapping, send it as a regular VK event.
+ // If there is no scancode mapping or it's non-normalized, send it as a regular VK event.
ki.wVk = modcode;
}
From 44f89de33b2263d456f40fbd483fbe659fa4023b Mon Sep 17 00:00:00 2001
From: Cameron Gutman
Date: Sun, 9 Apr 2023 12:22:30 -0500
Subject: [PATCH 016/144] Respect modifier flags in keyboard events
Fixes capital letters from Android soft keyboard
---
src/input.cpp | 75 +++++++++++++++++++++++++++++++++++++++++++++++----
1 file changed, 70 insertions(+), 5 deletions(-)
diff --git a/src/input.cpp b/src/input.cpp
index 381d3097f1f..a957b89b94b 100644
--- a/src/input.cpp
+++ b/src/input.cpp
@@ -462,17 +462,66 @@ namespace input {
}
}
+ bool
+ is_modifier(uint16_t keyCode) {
+ switch (keyCode) {
+ case VKEY_SHIFT:
+ case VKEY_LSHIFT:
+ case VKEY_RSHIFT:
+ case VKEY_CONTROL:
+ case VKEY_LCONTROL:
+ case VKEY_RCONTROL:
+ case VKEY_MENU:
+ case VKEY_LMENU:
+ case VKEY_RMENU:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ 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
+ if (synthetic_modifiers & MODIFIER_SHIFT) {
+ platf::keyboard(platf_input, VKEY_SHIFT, false, flags);
+ }
+ if (synthetic_modifiers & MODIFIER_CTRL) {
+ platf::keyboard(platf_input, VKEY_CONTROL, false, flags);
+ }
+ if (synthetic_modifiers & MODIFIER_ALT) {
+ platf::keyboard(platf_input, VKEY_MENU, false, flags);
+ }
+ }
+
+ platf::keyboard(platf_input, map_keycode(key_code), release, flags);
+
+ if (!release) {
+ // Raise any synthetic modifier keys we pressed
+ if (synthetic_modifiers & MODIFIER_SHIFT) {
+ platf::keyboard(platf_input, VKEY_SHIFT, true, flags);
+ }
+ if (synthetic_modifiers & MODIFIER_CTRL) {
+ platf::keyboard(platf_input, VKEY_CONTROL, true, flags);
+ }
+ if (synthetic_modifiers & MODIFIER_ALT) {
+ platf::keyboard(platf_input, VKEY_MENU, true, flags);
+ }
+ }
+ }
+
void
- repeat_key(uint16_t key_code, uint8_t flags) {
+ 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)]) {
key_press_repeat_id = nullptr;
return;
}
- platf::keyboard(platf_input, map_keycode(key_code), false, flags);
+ send_key_and_modifiers(key_code, false, flags, synthetic_modifiers);
- key_press_repeat_id = task_pool.pushDelayed(repeat_key, config::input.key_repeat_period, key_code, flags).task_id;
+ key_press_repeat_id = task_pool.pushDelayed(repeat_key, config::input.key_repeat_period, key_code, flags, synthetic_modifiers).task_id;
}
void
@@ -484,6 +533,21 @@ namespace input {
auto release = util::endian::little(packet->header.magic) == KEY_UP_EVENT_MAGIC;
auto keyCode = packet->keyCode & 0x00FF;
+ // Set synthetic modifier flags if the keyboard packet is requesting modifier
+ // keys that are not current pressed.
+ uint8_t synthetic_modifiers = 0;
+ if (!release && !is_modifier(keyCode)) {
+ if (!(input->shortcutFlags & input_t::SHIFT) && (packet->modifiers & MODIFIER_SHIFT)) {
+ synthetic_modifiers |= MODIFIER_SHIFT;
+ }
+ if (!(input->shortcutFlags & input_t::CTRL) && (packet->modifiers & MODIFIER_CTRL)) {
+ synthetic_modifiers |= MODIFIER_CTRL;
+ }
+ if (!(input->shortcutFlags & input_t::ALT) && (packet->modifiers & MODIFIER_ALT)) {
+ synthetic_modifiers |= MODIFIER_ALT;
+ }
+ }
+
auto &pressed = key_press[make_kpid(keyCode, packet->flags)];
if (!pressed) {
if (!release) {
@@ -498,7 +562,7 @@ namespace input {
}
if (config::input.key_repeat_delay.count() > 0) {
- key_press_repeat_id = task_pool.pushDelayed(repeat_key, config::input.key_repeat_delay, keyCode, packet->flags).task_id;
+ key_press_repeat_id = task_pool.pushDelayed(repeat_key, config::input.key_repeat_delay, keyCode, packet->flags, synthetic_modifiers).task_id;
}
}
else {
@@ -513,8 +577,9 @@ namespace input {
pressed = !release;
+ send_key_and_modifiers(keyCode, release, packet->flags, synthetic_modifiers);
+
update_shortcutFlags(&input->shortcutFlags, map_keycode(keyCode), release);
- platf::keyboard(platf_input, map_keycode(keyCode), release, packet->flags);
}
void
From 6467e10def5198876441400ca190897b6d5ef7c7 Mon Sep 17 00:00:00 2001
From: Cameron Gutman
Date: Sun, 9 Apr 2023 16:34:07 -0500
Subject: [PATCH 017/144] Reprobe encoders each time streaming begins
Available encoders can change due to driver updates, GPU hotplugging, primary monitor changes, etc.
---
src/main.cpp | 4 +--
src/nvhttp.cpp | 27 ++++++++++++++++
src/video.cpp | 87 ++++++++++++++++++++++++++++----------------------
src/video.h | 2 +-
4 files changed, 79 insertions(+), 41 deletions(-)
diff --git a/src/main.cpp b/src/main.cpp
index 941dcfc194c..4c1ae2abec8 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -356,8 +356,8 @@ main(int argc, char *argv[]) {
reed_solomon_init();
auto input_deinit_guard = input::init();
- if (video::init()) {
- BOOST_LOG(error) << "Video failed to initialize"sv;
+ if (video::probe_encoders()) {
+ BOOST_LOG(error) << "Video failed to find working encoder"sv;
}
if (http::init()) {
diff --git a/src/nvhttp.cpp b/src/nvhttp.cpp
index 15fc1f349a6..d2cdd6a8af7 100644
--- a/src/nvhttp.cpp
+++ b/src/nvhttp.cpp
@@ -29,6 +29,7 @@
#include "rtsp.h"
#include "utility.h"
#include "uuid.h"
+#include "video.h"
using namespace std::literals;
namespace nvhttp {
@@ -762,6 +763,19 @@ namespace nvhttp {
return;
}
+ // Probe encoders again before streaming to ensure our chosen
+ // encoder matches the active GPU (which could have changed
+ // due to hotplugging, driver crash, primary monitor change,
+ // or any number of other factors).
+ if (rtsp_stream::session_count() == 0) {
+ if (video::probe_encoders()) {
+ tree.put("root..status_code", 503);
+ tree.put("root.gamesession", 0);
+
+ return;
+ }
+ }
+
if (appid > 0) {
auto err = proc::proc.execute(appid);
if (err) {
@@ -820,6 +834,19 @@ namespace nvhttp {
return;
}
+ // Probe encoders again before streaming to ensure our chosen
+ // encoder matches the active GPU (which could have changed
+ // due to hotplugging, driver crash, primary monitor change,
+ // or any number of other factors).
+ if (rtsp_stream::session_count() == 0) {
+ if (video::probe_encoders()) {
+ tree.put("root.resume", 0);
+ tree.put("root..status_code", 503);
+
+ return;
+ }
+ }
+
rtsp_stream::launch_session_raise(make_launch_session(host_audio, args));
tree.put("root..status_code", 200);
diff --git a/src/video.cpp b/src/video.cpp
index 0eb0e6a2836..1b8b682fb3f 100644
--- a/src/video.cpp
+++ b/src/video.cpp
@@ -710,23 +710,25 @@ namespace video {
};
#endif
- static std::vector encoders {
+ static const std::vector encoders {
#ifndef __APPLE__
- nvenc,
+ &nvenc,
#endif
#ifdef _WIN32
- quicksync,
- amdvce,
+ &quicksync,
+ &amdvce,
#endif
#ifdef __linux__
- vaapi,
+ &vaapi,
#endif
#ifdef __APPLE__
- videotoolbox,
+ &videotoolbox,
#endif
- software
+ &software
};
+ static encoder_t *chosen_encoder;
+
void
reset_display(std::shared_ptr &disp, AVHWDeviceType type, const std::string &display_name, const config_t &config) {
// We try this twice, in case we still get an error on reinitialization
@@ -1446,7 +1448,7 @@ namespace video {
encode_run_sync(
std::vector> &synced_session_ctxs,
encode_session_ctx_queue_t &encode_session_ctx_queue) {
- const auto &encoder = encoders.front();
+ const auto &encoder = *chosen_encoder;
auto display_names = platf::display_names(map_base_dev_type(encoder.base_dev_type));
int display_p = 0;
@@ -1673,7 +1675,7 @@ namespace video {
display = ref->display_wp->lock();
}
- auto &encoder = encoders.front();
+ auto &encoder = *chosen_encoder;
auto pix_fmt = config.dynamicRange == 0 ? map_pix_fmt(encoder.static_pix_fmt) : map_pix_fmt(encoder.dynamic_pix_fmt);
auto hwdevice = display->make_hwdevice(pix_fmt);
if (!hwdevice) {
@@ -1709,7 +1711,7 @@ namespace video {
auto idr_events = mail->event(mail::idr);
idr_events->raise(true);
- if (encoders.front().flags & PARALLEL_ENCODING) {
+ if (chosen_encoder->flags & PARALLEL_ENCODING) {
capture_async(std::move(mail), config, channel_data);
}
else {
@@ -1927,86 +1929,95 @@ namespace video {
return true;
}
+ /*
+ This is called once at startup and each time a stream is launched to
+ ensure the best encoder is selected. Encoder availablility can change
+ at runtime due to all sorts of things from driver updates to eGPUs.
+
+ This is only safe to call when there is no client actively streaming.
+ */
int
- init() {
- bool encoder_found = false;
+ probe_encoders() {
+ auto encoder_list = encoders;
+
+ // Reset encoder selection
+ chosen_encoder = nullptr;
+
if (!config::video.encoder.empty()) {
// If there is a specific encoder specified, use it if it passes validation
- KITTY_WHILE_LOOP(auto pos = std::begin(encoders), pos != std::end(encoders), {
+ KITTY_WHILE_LOOP(auto pos = std::begin(encoder_list), pos != std::end(encoder_list), {
auto encoder = *pos;
- if (encoder.name == config::video.encoder) {
+ if (encoder->name == config::video.encoder) {
// Remove the encoder from the list entirely if it fails validation
- if (!validate_encoder(encoder)) {
- pos = encoders.erase(pos);
+ if (!validate_encoder(*encoder)) {
+ pos = encoder_list.erase(pos);
break;
}
// If we can't satisfy both the encoder and HDR requirement, prefer the encoder over HDR support
- if (config::video.hevc_mode == 3 && !encoder.hevc[encoder_t::DYNAMIC_RANGE]) {
+ if (config::video.hevc_mode == 3 && !encoder->hevc[encoder_t::DYNAMIC_RANGE]) {
BOOST_LOG(warning) << "Encoder ["sv << config::video.encoder << "] does not support HDR on this system"sv;
config::video.hevc_mode = 0;
}
- encoders.clear();
- encoders.emplace_back(encoder);
- encoder_found = true;
+ chosen_encoder = encoder;
break;
}
pos++;
});
- if (!encoder_found) {
+ if (chosen_encoder == nullptr) {
BOOST_LOG(error) << "Couldn't find any working encoder matching ["sv << config::video.encoder << ']';
- config::video.encoder.clear();
}
}
BOOST_LOG(info) << "// Testing for available encoders, this may generate errors. You can safely ignore those errors. //"sv;
// If we haven't found an encoder yet, but we want one with HDR support, search for that now.
- if (!encoder_found && config::video.hevc_mode == 3) {
- KITTY_WHILE_LOOP(auto pos = std::begin(encoders), pos != std::end(encoders), {
+ if (chosen_encoder == nullptr && config::video.hevc_mode == 3) {
+ KITTY_WHILE_LOOP(auto pos = std::begin(encoder_list), pos != std::end(encoder_list), {
auto encoder = *pos;
// Remove the encoder from the list entirely if it fails validation
- if (!validate_encoder(encoder)) {
- pos = encoders.erase(pos);
+ if (!validate_encoder(*encoder)) {
+ pos = encoder_list.erase(pos);
continue;
}
// Skip it if it doesn't support HDR
- if (!encoder.hevc[encoder_t::DYNAMIC_RANGE]) {
+ if (!encoder->hevc[encoder_t::DYNAMIC_RANGE]) {
pos++;
continue;
}
- encoders.clear();
- encoders.emplace_back(encoder);
- encoder_found = true;
+ chosen_encoder = encoder;
break;
});
- if (!encoder_found) {
+ if (chosen_encoder == nullptr) {
BOOST_LOG(error) << "Couldn't find any working HDR-capable encoder"sv;
}
}
// If no encoder was specified or the specified encoder was unusable, keep trying
// the remaining encoders until we find one that passes validation.
- if (!encoder_found) {
- KITTY_WHILE_LOOP(auto pos = std::begin(encoders), pos != std::end(encoders), {
- if (!validate_encoder(*pos)) {
- pos = encoders.erase(pos);
+ if (chosen_encoder == nullptr) {
+ KITTY_WHILE_LOOP(auto pos = std::begin(encoder_list), pos != std::end(encoder_list), {
+ auto encoder = *pos;
+
+ if (!validate_encoder(*encoder)) {
+ pos = encoder_list.erase(pos);
continue;
}
+ chosen_encoder = encoder;
break;
});
}
- if (encoders.empty()) {
+ if (chosen_encoder == nullptr) {
BOOST_LOG(fatal) << "Couldn't find any working encoder"sv;
return -1;
}
@@ -2015,7 +2026,7 @@ namespace video {
BOOST_LOG(info) << "// Ignore any errors mentioned above, they are not relevant. //"sv;
BOOST_LOG(info);
- auto &encoder = encoders.front();
+ auto &encoder = *chosen_encoder;
BOOST_LOG(debug) << "------ h264 ------"sv;
for (int x = 0; x < encoder_t::MAX_FLAGS; ++x) {
@@ -2147,7 +2158,7 @@ namespace video {
int
start_capture_async(capture_thread_async_ctx_t &capture_thread_ctx) {
- capture_thread_ctx.encoder_p = &encoders.front();
+ capture_thread_ctx.encoder_p = chosen_encoder;
capture_thread_ctx.reinit_event.reset();
capture_thread_ctx.capture_ctx_queue = std::make_shared>(30);
diff --git a/src/video.h b/src/video.h
index 03bf218b038..c02c317e5bc 100644
--- a/src/video.h
+++ b/src/video.h
@@ -97,7 +97,7 @@ namespace video {
void *channel_data);
int
- init();
+ probe_encoders();
} // namespace video
#endif // SUNSHINE_VIDEO_H
From 7038c1aeb0f4217cd1f34e5de487227eea1a8d20 Mon Sep 17 00:00:00 2001
From: ns6089 <61738816+ns6089@users.noreply.github.com>
Date: Wed, 5 Apr 2023 18:03:03 +0300
Subject: [PATCH 018/144] Create config directories recursively
---
src/config.cpp | 2 +-
src/confighttp.cpp | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/config.cpp b/src/config.cpp
index 07d4d71c7d8..1e4276dfe30 100644
--- a/src/config.cpp
+++ b/src/config.cpp
@@ -1138,7 +1138,7 @@ namespace config {
// create appdata folder if it does not exist
if (!boost::filesystem::exists(platf::appdata().string())) {
- boost::filesystem::create_directory(platf::appdata().string());
+ boost::filesystem::create_directories(platf::appdata().string());
}
// create config file if it does not exist
diff --git a/src/confighttp.cpp b/src/confighttp.cpp
index c7a2a8b3480..908ef721c22 100644
--- a/src/confighttp.cpp
+++ b/src/confighttp.cpp
@@ -488,7 +488,7 @@ namespace confighttp {
const std::string coverdir = platf::appdata().string() + "/covers/";
if (!boost::filesystem::exists(coverdir)) {
- boost::filesystem::create_directory(coverdir);
+ boost::filesystem::create_directories(coverdir);
}
std::basic_string path = coverdir + http::url_escape(key) + ".png";
From 010440dbd3e0e89e2dd891f0179d126745fa3460 Mon Sep 17 00:00:00 2001
From: Morgan Helton
Date: Mon, 10 Apr 2023 12:05:33 -0500
Subject: [PATCH 019/144] add X11 to PLATFORM_LIBARIES when found
---
CMakeLists.txt | 1 +
1 file changed, 1 insertion(+)
diff --git a/CMakeLists.txt b/CMakeLists.txt
index f7f80a6ba94..9e197fe34f2 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -354,6 +354,7 @@ else()
if(X11_FOUND)
add_compile_definitions(SUNSHINE_BUILD_X11)
include_directories(${X11_INCLUDE_DIR})
+ list(APPEND PLATFORM_LIBRARIES ${X11_LIBRARIES})
list(APPEND PLATFORM_TARGET_FILES src/platform/linux/x11grab.cpp)
endif()
From b5c8bbaa24bfcab32ef7e08e51eba6a08fd661ec Mon Sep 17 00:00:00 2001
From: Cameron Gutman
Date: Wed, 12 Apr 2023 23:43:29 -0500
Subject: [PATCH 020/144] Fix color conversion for SDR FP16 capture formats
---
src/platform/windows/display_vram.cpp | 57 +++++++++++++++----
.../shaders/directx/ConvertUVPS_Linear.hlsl | 36 ++++++++++++
.../shaders/directx/ConvertYPS_Linear.hlsl | 31 ++++++++++
3 files changed, 112 insertions(+), 12 deletions(-)
create mode 100644 src_assets/windows/assets/shaders/directx/ConvertUVPS_Linear.hlsl
create mode 100644 src_assets/windows/assets/shaders/directx/ConvertYPS_Linear.hlsl
diff --git a/src/platform/windows/display_vram.cpp b/src/platform/windows/display_vram.cpp
index fa3ecf4df4c..157f884c8f5 100644
--- a/src/platform/windows/display_vram.cpp
+++ b/src/platform/windows/display_vram.cpp
@@ -91,9 +91,11 @@ namespace platf::dxgi {
blob_t convert_UV_vs_hlsl;
blob_t convert_UV_ps_hlsl;
+ blob_t convert_UV_linear_ps_hlsl;
blob_t convert_UV_PQ_ps_hlsl;
blob_t scene_vs_hlsl;
blob_t convert_Y_ps_hlsl;
+ blob_t convert_Y_linear_ps_hlsl;
blob_t convert_Y_PQ_ps_hlsl;
blob_t scene_ps_hlsl;
blob_t scene_NW_ps_hlsl;
@@ -116,6 +118,9 @@ namespace platf::dxgi {
// Unique identifier for this image
uint32_t id = 0;
+ // DXGI format of this image texture
+ DXGI_FORMAT format;
+
virtual ~img_d3d_t() override {
if (encoder_texture_handle) {
CloseHandle(encoder_texture_handle);
@@ -338,14 +343,14 @@ namespace platf::dxgi {
device_ctx->OMSetRenderTargets(1, &nv12_Y_rt, nullptr);
device_ctx->VSSetShader(scene_vs.get(), nullptr, 0);
- device_ctx->PSSetShader(convert_Y_ps.get(), nullptr, 0);
+ device_ctx->PSSetShader(img.format == DXGI_FORMAT_R16G16B16A16_FLOAT ? convert_Y_fp16_ps.get() : convert_Y_ps.get(), nullptr, 0);
device_ctx->RSSetViewports(1, &outY_view);
device_ctx->PSSetShaderResources(0, 1, &img_ctx.encoder_input_res);
device_ctx->Draw(3, 0);
device_ctx->OMSetRenderTargets(1, &nv12_UV_rt, nullptr);
device_ctx->VSSetShader(convert_UV_vs.get(), nullptr, 0);
- device_ctx->PSSetShader(convert_UV_ps.get(), nullptr, 0);
+ device_ctx->PSSetShader(img.format == DXGI_FORMAT_R16G16B16A16_FLOAT ? convert_UV_fp16_ps.get() : convert_UV_ps.get(), nullptr, 0);
device_ctx->RSSetViewports(1, &outUV_view);
device_ctx->Draw(3, 0);
@@ -577,34 +582,48 @@ namespace platf::dxgi {
}
// If the display is in HDR and we're streaming HDR, we'll be converting scRGB to SMPTE 2084 PQ.
- // NB: We can consume scRGB in SDR with our regular shaders because it behaves like UNORM input.
if (format == DXGI_FORMAT_P010 && display->is_hdr()) {
- status = device->CreatePixelShader(convert_Y_PQ_ps_hlsl->GetBufferPointer(), convert_Y_PQ_ps_hlsl->GetBufferSize(), nullptr, &convert_Y_ps);
+ status = device->CreatePixelShader(convert_Y_PQ_ps_hlsl->GetBufferPointer(), convert_Y_PQ_ps_hlsl->GetBufferSize(), nullptr, &convert_Y_fp16_ps);
if (status) {
BOOST_LOG(error) << "Failed to create convertY pixel shader [0x"sv << util::hex(status).to_string_view() << ']';
return -1;
}
- status = device->CreatePixelShader(convert_UV_PQ_ps_hlsl->GetBufferPointer(), convert_UV_PQ_ps_hlsl->GetBufferSize(), nullptr, &convert_UV_ps);
+ status = device->CreatePixelShader(convert_UV_PQ_ps_hlsl->GetBufferPointer(), convert_UV_PQ_ps_hlsl->GetBufferSize(), nullptr, &convert_UV_fp16_ps);
if (status) {
BOOST_LOG(error) << "Failed to create convertUV pixel shader [0x"sv << util::hex(status).to_string_view() << ']';
return -1;
}
}
else {
- status = device->CreatePixelShader(convert_Y_ps_hlsl->GetBufferPointer(), convert_Y_ps_hlsl->GetBufferSize(), nullptr, &convert_Y_ps);
+ // If the display is in Advanced Color mode, the desktop format will be scRGB FP16.
+ // scRGB uses linear gamma, so we must use our linear to sRGB conversion shaders.
+ status = device->CreatePixelShader(convert_Y_linear_ps_hlsl->GetBufferPointer(), convert_Y_linear_ps_hlsl->GetBufferSize(), nullptr, &convert_Y_fp16_ps);
if (status) {
BOOST_LOG(error) << "Failed to create convertY pixel shader [0x"sv << util::hex(status).to_string_view() << ']';
return -1;
}
- status = device->CreatePixelShader(convert_UV_ps_hlsl->GetBufferPointer(), convert_UV_ps_hlsl->GetBufferSize(), nullptr, &convert_UV_ps);
+ status = device->CreatePixelShader(convert_UV_linear_ps_hlsl->GetBufferPointer(), convert_UV_linear_ps_hlsl->GetBufferSize(), nullptr, &convert_UV_fp16_ps);
if (status) {
BOOST_LOG(error) << "Failed to create convertUV pixel shader [0x"sv << util::hex(status).to_string_view() << ']';
return -1;
}
}
+ // These shaders consume standard 8-bit sRGB input
+ status = device->CreatePixelShader(convert_Y_ps_hlsl->GetBufferPointer(), convert_Y_ps_hlsl->GetBufferSize(), nullptr, &convert_Y_ps);
+ if (status) {
+ BOOST_LOG(error) << "Failed to create convertY pixel shader [0x"sv << util::hex(status).to_string_view() << ']';
+ return -1;
+ }
+
+ status = device->CreatePixelShader(convert_UV_ps_hlsl->GetBufferPointer(), convert_UV_ps_hlsl->GetBufferSize(), nullptr, &convert_UV_ps);
+ if (status) {
+ BOOST_LOG(error) << "Failed to create convertUV pixel shader [0x"sv << util::hex(status).to_string_view() << ']';
+ return -1;
+ }
+
color_matrix = make_buffer(device.get(), ::video::colors[0]);
if (!color_matrix) {
BOOST_LOG(error) << "Failed to create color matrix buffer"sv;
@@ -750,7 +769,9 @@ namespace platf::dxgi {
vs_t convert_UV_vs;
ps_t convert_UV_ps;
+ ps_t convert_UV_fp16_ps;
ps_t convert_Y_ps;
+ ps_t convert_Y_fp16_ps;
vs_t scene_vs;
D3D11_VIEWPORT outY_view;
@@ -1118,6 +1139,7 @@ namespace platf::dxgi {
img->pixel_pitch = get_pixel_pitch();
img->row_pitch = img->pixel_pitch * img->width;
img->dummy = dummy;
+ img->format = (capture_format == DXGI_FORMAT_UNKNOWN) ? DXGI_FORMAT_B8G8R8A8_UNORM : capture_format;
D3D11_TEXTURE2D_DESC t {};
t.Width = img->width;
@@ -1126,7 +1148,7 @@ namespace platf::dxgi {
t.ArraySize = 1;
t.SampleDesc.Count = 1;
t.Usage = D3D11_USAGE_DEFAULT;
- t.Format = (capture_format == DXGI_FORMAT_UNKNOWN) ? DXGI_FORMAT_B8G8R8A8_UNORM : capture_format;
+ t.Format = img->format;
t.BindFlags = D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_RENDER_TARGET;
t.MiscFlags = D3D11_RESOURCE_MISC_SHARED_NTHANDLE | D3D11_RESOURCE_MISC_SHARED_KEYEDMUTEX;
@@ -1190,8 +1212,9 @@ namespace platf::dxgi {
std::vector
display_vram_t::get_supported_capture_formats() {
return {
- // scRGB FP16 is the desired format for HDR content. This will also handle
- // 10-bit SDR displays with the increased precision of FP16 vs 8-bit UNORMs.
+ // scRGB FP16 is the ideal format for Wide Color Gamut and Advanced Color
+ // displays (both SDR and HDR). This format uses linear gamma, so we will
+ // use a linear->PQ shader for HDR and a linear->sRGB shader for SDR.
DXGI_FORMAT_R16G16B16A16_FLOAT,
// DXGI_FORMAT_R10G10B10A2_UNORM seems like it might give us frames already
@@ -1203,8 +1226,8 @@ namespace platf::dxgi {
// but we avoid it for now.
// We include the 8-bit modes too for when the display is in SDR mode,
- // while the client stream is HDR-capable. These UNORM formats behave
- // like a degenerate case of scRGB FP16 with values between 0.0f-1.0f.
+ // while the client stream is HDR-capable. These UNORM formats can
+ // use our normal pixel shaders that expect sRGB input.
DXGI_FORMAT_B8G8R8A8_UNORM,
DXGI_FORMAT_R8G8B8A8_UNORM,
};
@@ -1250,6 +1273,11 @@ namespace platf::dxgi {
return -1;
}
+ convert_Y_linear_ps_hlsl = compile_pixel_shader(SUNSHINE_SHADERS_DIR "/ConvertYPS_Linear.hlsl");
+ if (!convert_Y_linear_ps_hlsl) {
+ return -1;
+ }
+
convert_UV_ps_hlsl = compile_pixel_shader(SUNSHINE_SHADERS_DIR "/ConvertUVPS.hlsl");
if (!convert_UV_ps_hlsl) {
return -1;
@@ -1260,6 +1288,11 @@ namespace platf::dxgi {
return -1;
}
+ convert_UV_linear_ps_hlsl = compile_pixel_shader(SUNSHINE_SHADERS_DIR "/ConvertUVPS_Linear.hlsl");
+ if (!convert_UV_linear_ps_hlsl) {
+ return -1;
+ }
+
convert_UV_vs_hlsl = compile_vertex_shader(SUNSHINE_SHADERS_DIR "/ConvertUVVS.hlsl");
if (!convert_UV_vs_hlsl) {
return -1;
diff --git a/src_assets/windows/assets/shaders/directx/ConvertUVPS_Linear.hlsl b/src_assets/windows/assets/shaders/directx/ConvertUVPS_Linear.hlsl
new file mode 100644
index 00000000000..209f3bf713c
--- /dev/null
+++ b/src_assets/windows/assets/shaders/directx/ConvertUVPS_Linear.hlsl
@@ -0,0 +1,36 @@
+Texture2D image : register(t0);
+
+SamplerState def_sampler : register(s0);
+
+struct FragTexWide {
+ float3 uuv : TEXCOORD0;
+};
+
+cbuffer ColorMatrix : register(b0) {
+ float4 color_vec_y;
+ float4 color_vec_u;
+ float4 color_vec_v;
+ float2 range_y;
+ float2 range_uv;
+};
+
+// This is a fast sRGB approximation from Microsoft's ColorSpaceUtility.hlsli
+float3 ApplySRGBCurve(float3 x)
+{
+ return x < 0.0031308 ? 12.92 * x : 1.13005 * sqrt(x - 0.00228) - 0.13448 * x + 0.005719;
+}
+
+float2 main_ps(FragTexWide input) : SV_Target
+{
+ float3 rgb_left = ApplySRGBCurve(saturate(image.Sample(def_sampler, input.uuv.xz)).rgb);
+ float3 rgb_right = ApplySRGBCurve(saturate(image.Sample(def_sampler, input.uuv.yz)).rgb);
+ float3 rgb = (rgb_left + rgb_right) * 0.5;
+
+ float u = dot(color_vec_u.xyz, rgb) + color_vec_u.w;
+ float v = dot(color_vec_v.xyz, rgb) + color_vec_v.w;
+
+ u = u * range_uv.x + range_uv.y;
+ v = v * range_uv.x + range_uv.y;
+
+ return float2(u, v);
+}
\ No newline at end of file
diff --git a/src_assets/windows/assets/shaders/directx/ConvertYPS_Linear.hlsl b/src_assets/windows/assets/shaders/directx/ConvertYPS_Linear.hlsl
new file mode 100644
index 00000000000..a7e91dc16dd
--- /dev/null
+++ b/src_assets/windows/assets/shaders/directx/ConvertYPS_Linear.hlsl
@@ -0,0 +1,31 @@
+Texture2D image : register(t0);
+
+SamplerState def_sampler : register(s0);
+
+cbuffer ColorMatrix : register(b0) {
+ float4 color_vec_y;
+ float4 color_vec_u;
+ float4 color_vec_v;
+ float2 range_y;
+ float2 range_uv;
+};
+
+struct PS_INPUT
+{
+ float4 pos : SV_POSITION;
+ float2 tex : TEXCOORD;
+};
+
+// This is a fast sRGB approximation from Microsoft's ColorSpaceUtility.hlsli
+float3 ApplySRGBCurve(float3 x)
+{
+ return x < 0.0031308 ? 12.92 * x : 1.13005 * sqrt(x - 0.00228) - 0.13448 * x + 0.005719;
+}
+
+float main_ps(PS_INPUT frag_in) : SV_Target
+{
+ float3 rgb = ApplySRGBCurve(saturate(image.Sample(def_sampler, frag_in.tex, 0)).rgb);
+ float y = dot(color_vec_y.xyz, rgb) + color_vec_y.w;
+
+ return y * range_y.x + range_y.y;
+}
\ No newline at end of file
From 242a1467126a0487b5f72af2a119f8a41fa793c1 Mon Sep 17 00:00:00 2001
From: Cameron Gutman
Date: Thu, 13 Apr 2023 21:10:32 -0500
Subject: [PATCH 021/144] Add DXGI_FORMAT_B8G8R8X8_UNORM as a supported capture
format
---
src/platform/windows/display_ram.cpp | 2 +-
src/platform/windows/display_vram.cpp | 1 +
2 files changed, 2 insertions(+), 1 deletion(-)
diff --git a/src/platform/windows/display_ram.cpp b/src/platform/windows/display_ram.cpp
index 0254668187a..04ea07536fd 100644
--- a/src/platform/windows/display_ram.cpp
+++ b/src/platform/windows/display_ram.cpp
@@ -362,7 +362,7 @@ namespace platf::dxgi {
std::vector
display_ram_t::get_supported_capture_formats() {
- return { DXGI_FORMAT_B8G8R8A8_UNORM };
+ return { DXGI_FORMAT_B8G8R8A8_UNORM, DXGI_FORMAT_B8G8R8X8_UNORM };
}
int
diff --git a/src/platform/windows/display_vram.cpp b/src/platform/windows/display_vram.cpp
index 157f884c8f5..8b8f0038238 100644
--- a/src/platform/windows/display_vram.cpp
+++ b/src/platform/windows/display_vram.cpp
@@ -1229,6 +1229,7 @@ namespace platf::dxgi {
// while the client stream is HDR-capable. These UNORM formats can
// use our normal pixel shaders that expect sRGB input.
DXGI_FORMAT_B8G8R8A8_UNORM,
+ DXGI_FORMAT_B8G8R8X8_UNORM,
DXGI_FORMAT_R8G8B8A8_UNORM,
};
}
From 7b11b48317ef411533d5ed8c519e3f195a818d8f Mon Sep 17 00:00:00 2001
From: ns6089 <61738816+ns6089@users.noreply.github.com>
Date: Thu, 6 Apr 2023 21:29:21 +0300
Subject: [PATCH 022/144] Delayed capture buffer trim
---
src/video.cpp | 83 ++++++++++++++++++++++++++++++++++++++++++---------
1 file changed, 69 insertions(+), 14 deletions(-)
diff --git a/src/video.cpp b/src/video.cpp
index 1b8b682fb3f..86de57deab4 100644
--- a/src/video.cpp
+++ b/src/video.cpp
@@ -793,36 +793,91 @@ namespace video {
}
display_wp = disp;
- constexpr auto capture_buffer_normal_size = 2;
constexpr auto capture_buffer_size = 12;
std::list> imgs(capture_buffer_size);
+ std::vector> imgs_used_timestamps;
+ const std::chrono::seconds trim_timeot = 3s;
+ auto trim_imgs = [&]() {
+ // count allocated and used within current pool
+ size_t allocated_count = 0;
+ size_t used_count = 0;
+ for (const auto &img : imgs) {
+ if (img) {
+ allocated_count += 1;
+ if (img.use_count() > 1) {
+ used_count += 1;
+ }
+ }
+ }
+
+ // remember the timestamp of currently used count
+ const auto now = std::chrono::steady_clock::now();
+ if (imgs_used_timestamps.size() <= used_count) {
+ imgs_used_timestamps.resize(used_count + 1);
+ }
+ imgs_used_timestamps[used_count] = now;
+
+ // decide whether to trim allocated unused above the currently used count
+ // based on last used timestamp and universal timeout
+ size_t trim_target = used_count;
+ for (size_t i = used_count; i < imgs_used_timestamps.size(); i++) {
+ if (imgs_used_timestamps[i] && now - *imgs_used_timestamps[i] < trim_timeot) {
+ trim_target = i;
+ }
+ }
+
+ // trim allocated unused above the newly decided trim target
+ if (allocated_count > trim_target) {
+ size_t to_trim = allocated_count - trim_target;
+ // prioritize trimming least recently used
+ for (auto it = imgs.rbegin(); it != imgs.rend(); it++) {
+ auto &img = *it;
+ if (img && img.use_count() == 1) {
+ img.reset();
+ to_trim -= 1;
+ if (to_trim == 0) break;
+ }
+ }
+ // forget timestamps that no longer relevant
+ imgs_used_timestamps.resize(trim_target + 1);
+ }
+ };
+
auto pull_free_image_callback = [&](std::shared_ptr &img_out) -> bool {
img_out.reset();
while (capture_ctx_queue->running()) {
+ // pick first allocated but unused
for (auto it = imgs.begin(); it != imgs.end(); it++) {
- // pick first unallocated or unused
- if (!*it || it->use_count() == 1) {
- if (!*it) {
- // allocate if unallocated
- *it = disp->alloc_img();
- }
+ if (*it && it->use_count() == 1) {
img_out = *it;
if (it != imgs.begin()) {
- // move freshly allocated or unused img to the front of the list to prioritize its reusal
+ // move image to the front of the list to prioritize its reusal
imgs.erase(it);
imgs.push_front(img_out);
}
break;
}
}
- if (img_out) {
- // unallocate unused above normal buffer size
- size_t index = 0;
- for (auto &img : imgs) {
- if (index >= capture_buffer_normal_size && img && img.use_count() == 1) img.reset();
- index++;
+ // otherwise pick first unallocated
+ if (!img_out) {
+ for (auto it = imgs.begin(); it != imgs.end(); it++) {
+ if (!*it) {
+ // allocate image
+ *it = disp->alloc_img();
+ img_out = *it;
+ if (it != imgs.begin()) {
+ // move image to the front of the list to prioritize its reusal
+ imgs.erase(it);
+ imgs.push_front(img_out);
+ }
+ break;
+ }
}
+ }
+ if (img_out) {
+ // trim allocated but unused portion of the pool based on timeouts
+ trim_imgs();
return true;
}
else {
From 045d4b81a461b16eaea30cbba35320c976a28349 Mon Sep 17 00:00:00 2001
From: ns6089 <61738816+ns6089@users.noreply.github.com>
Date: Thu, 6 Apr 2023 23:39:44 +0300
Subject: [PATCH 023/144] Switch between img and surface in last frame
---
src/platform/windows/display.h | 4 +-
src/platform/windows/display_vram.cpp | 354 +++++++++++++++++++++-----
2 files changed, 289 insertions(+), 69 deletions(-)
diff --git a/src/platform/windows/display.h b/src/platform/windows/display.h
index 349d4c4c561..66b23d74d15 100644
--- a/src/platform/windows/display.h
+++ b/src/platform/windows/display.h
@@ -230,7 +230,9 @@ namespace platf::dxgi {
gpu_cursor_t cursor_alpha;
gpu_cursor_t cursor_xor;
- texture2d_t last_frame_copy;
+ texture2d_t old_surface_delayed_destruction;
+ std::chrono::steady_clock::time_point old_surface_timestamp;
+ std::variant> last_frame_variant;
std::atomic next_image_id;
};
diff --git a/src/platform/windows/display_vram.cpp b/src/platform/windows/display_vram.cpp
index 8b8f0038238..f2818602d9b 100644
--- a/src/platform/windows/display_vram.cpp
+++ b/src/platform/windows/display_vram.cpp
@@ -128,6 +128,52 @@ namespace platf::dxgi {
};
};
+ struct texture_lock_helper {
+ keyed_mutex_t _mutex;
+ bool _locked = false;
+
+ texture_lock_helper(const texture_lock_helper &) = delete;
+ texture_lock_helper &
+ operator=(const texture_lock_helper &) = delete;
+
+ texture_lock_helper(texture_lock_helper &&other) {
+ _mutex.reset(other._mutex.release());
+ _locked = other._locked;
+ other._locked = false;
+ }
+
+ texture_lock_helper &
+ operator=(texture_lock_helper &&other) {
+ if (_locked) _mutex->ReleaseSync(0);
+ _mutex.reset(other._mutex.release());
+ _locked = other._locked;
+ other._locked = false;
+ return *this;
+ }
+
+ texture_lock_helper(IDXGIKeyedMutex *mutex):
+ _mutex(mutex) {
+ if (_mutex) _mutex->AddRef();
+ }
+
+ ~texture_lock_helper() {
+ if (_locked) _mutex->ReleaseSync(0);
+ }
+
+ bool
+ lock() {
+ if (_locked) return true;
+ HRESULT status = _mutex->AcquireSync(0, INFINITE);
+ if (status == S_OK) {
+ _locked = true;
+ }
+ else {
+ BOOST_LOG(error) << "Failed to acquire texture mutex [0x"sv << util::hex(status).to_string_view() << ']';
+ }
+ return _locked;
+ }
+ };
+
util::buffer_t
make_cursor_xor_image(const util::buffer_t &img_data, DXGI_OUTDUPL_POINTER_SHAPE_INFO shape_info) {
constexpr std::uint32_t inverted = 0xFFFFFFFF;
@@ -877,14 +923,10 @@ namespace platf::dxgi {
cursor_xor.set_pos(frame_info.PointerPosition.Position.x, frame_info.PointerPosition.Position.y, frame_info.PointerPosition.Visible);
}
- if (!pull_free_image_cb(img_out)) {
- return capture_e::interrupted;
- }
- auto img = (img_d3d_t *) img_out.get();
+ const bool blend_mouse_cursor_flag = (cursor_alpha.visible || cursor_xor.visible) && cursor_visible;
+ texture2d_t src {};
if (frame_update_flag) {
- texture2d_t src {};
-
// Get the texture object from this frame
status = res->QueryInterface(IID_ID3D11Texture2D, (void **) &src);
if (FAILED(status)) {
@@ -906,23 +948,6 @@ namespace platf::dxgi {
if (capture_format == DXGI_FORMAT_UNKNOWN) {
capture_format = desc.Format;
BOOST_LOG(info) << "Capture format ["sv << dxgi_format_to_string(capture_format) << ']';
-
- D3D11_TEXTURE2D_DESC t {};
- t.Width = width;
- t.Height = height;
- t.MipLevels = 1;
- t.ArraySize = 1;
- t.SampleDesc.Count = 1;
- t.Usage = D3D11_USAGE_DEFAULT;
- t.Format = capture_format;
- t.BindFlags = 0;
-
- // Create a texture to store the most recent copy of the desktop
- status = device->CreateTexture2D(&t, nullptr, &last_frame_copy);
- if (FAILED(status)) {
- BOOST_LOG(error) << "Failed to create frame copy texture [0x"sv << util::hex(status).to_string_view() << ']';
- return capture_e::error;
- }
}
// It's also possible for the capture format to change on the fly. If that happens,
@@ -931,67 +956,201 @@ namespace platf::dxgi {
BOOST_LOG(info) << "Capture format changed ["sv << dxgi_format_to_string(capture_format) << " -> "sv << dxgi_format_to_string(desc.Format) << ']';
return capture_e::reinit;
}
+ }
- // Now that we know the capture format, we can finish creating the image
- if (complete_img(img, false)) {
- return capture_e::error;
- }
+ enum class lfa {
+ nothing,
+ replace_surface_with_img,
+ replace_img_with_surface,
+ copy_src_to_img,
+ copy_src_to_surface,
+ };
- // Copy the texture to use for cursor-only updates
- device_ctx->CopyResource(last_frame_copy.get(), src.get());
+ enum class ofa {
+ forward_last_img,
+ copy_last_surface_and_blend_cursor,
+ dummy_fallback,
+ };
- // Copy into the capture texture on the image with the mutex held
- status = img->capture_mutex->AcquireSync(0, INFINITE);
- if (status != S_OK) {
- BOOST_LOG(error) << "Failed to acquire capture mutex [0x"sv << util::hex(status).to_string_view() << ']';
- return capture_e::error;
+ auto last_frame_action = lfa::nothing;
+ auto out_frame_action = ofa::dummy_fallback;
+
+ if (capture_format == DXGI_FORMAT_UNKNOWN) {
+ // We don't know the final capture format yet, so we will encode a black dummy image
+ last_frame_action = lfa::nothing;
+ out_frame_action = ofa::dummy_fallback;
+ }
+ else {
+ if (src) {
+ // We got a new frame from DesktopDuplication...
+ if (blend_mouse_cursor_flag) {
+ // ...and we need to blend the mouse cursor onto it.
+ // Copy the frame to intermediate surface so we can blend this and future mouse cursor updates
+ // without new frames from DesktopDuplication. We use direct3d surface directly here and not
+ // an image from pull_free_image_cb mainly because it's lighter (surface sharing between
+ // direct3d devices produce significant memory overhead).
+ last_frame_action = lfa::copy_src_to_surface;
+ // Copy the intermediate surface to a new image from pull_free_image_cb and blend the mouse cursor onto it.
+ out_frame_action = ofa::copy_last_surface_and_blend_cursor;
+ }
+ else {
+ // ...and we don't need to blend the mouse cursor.
+ // Copy the frame to a new image from pull_free_image_cb and save the shared pointer to the image
+ // in case the mouse cursor appears without a new frame from DesktopDuplication.
+ last_frame_action = lfa::copy_src_to_img;
+ // Use saved last image shared pointer as output image evading copy.
+ out_frame_action = ofa::forward_last_img;
+ }
+ }
+ else if (!std::holds_alternative(last_frame_variant)) {
+ // We didn't get a new frame from DesktopDuplication...
+ if (blend_mouse_cursor_flag) {
+ // ...but we need to blend the mouse cursor.
+ if (std::holds_alternative>(last_frame_variant)) {
+ // We have the shared pointer of the last image, replace it with intermediate surface
+ // while copying contents so we can blend this and future mouse cursor updates.
+ last_frame_action = lfa::replace_img_with_surface;
+ }
+ // Copy the intermediate surface which contains last DesktopDuplication frame
+ // to a new image from pull_free_image_cb and blend the mouse cursor onto it.
+ out_frame_action = ofa::copy_last_surface_and_blend_cursor;
+ }
+ else {
+ // ...and we don't need to blend the mouse cursor.
+ // This happens when the mouse cursor disappears from screen,
+ // or there's mouse cursor on screen, but its drawing is disabled in sunshine.
+ if (std::holds_alternative(last_frame_variant)) {
+ // We have the intermediate surface that was used as the mouse cursor blending base.
+ // Replace it with an image from pull_free_image_cb copying contents and freeing up the surface memory.
+ // Save the shared pointer to the image in case the mouse cursor reappears.
+ last_frame_action = lfa::replace_surface_with_img;
+ }
+ // Use saved last image shared pointer as output image evading copy.
+ out_frame_action = ofa::forward_last_img;
+ }
}
- device_ctx->CopyResource(img->capture_texture.get(), src.get());
}
- else if (capture_format == DXGI_FORMAT_UNKNOWN) {
- // We don't know the final capture format yet, so we will encode a dummy image
- BOOST_LOG(debug) << "Capture format is still unknown. Encoding a blank image"sv;
- // Finish creating the image as a dummy (if it hasn't happened already)
- if (complete_img(img, true)) {
- return capture_e::error;
+ auto create_surface = [&](texture2d_t &surface) -> bool {
+ // Try to reuse the old surface if it hasn't been destroyed yet.
+ if (old_surface_delayed_destruction) {
+ surface.reset(old_surface_delayed_destruction.release());
+ return true;
}
- auto dummy_data = std::make_unique(img->row_pitch * img->height);
- std::fill_n(dummy_data.get(), img->row_pitch * img->height, 0);
+ // Otherwise create a new surface.
+ D3D11_TEXTURE2D_DESC t {};
+ t.Width = width;
+ t.Height = height;
+ t.MipLevels = 1;
+ t.ArraySize = 1;
+ t.SampleDesc.Count = 1;
+ t.Usage = D3D11_USAGE_DEFAULT;
+ t.Format = capture_format;
+ t.BindFlags = 0;
+ status = device->CreateTexture2D(&t, nullptr, &surface);
+ if (FAILED(status)) {
+ BOOST_LOG(error) << "Failed to create frame copy texture [0x"sv << util::hex(status).to_string_view() << ']';
+ return false;
+ }
- status = img->capture_mutex->AcquireSync(0, INFINITE);
- if (status != S_OK) {
- BOOST_LOG(error) << "Failed to acquire capture mutex [0x"sv << util::hex(status).to_string_view() << ']';
- return capture_e::error;
+ return true;
+ };
+
+ auto get_locked_d3d_img = [&](std::shared_ptr &img, bool dummy = false) -> std::tuple, texture_lock_helper> {
+ auto d3d_img = std::static_pointer_cast(img);
+
+ // Finish creating the image (if it hasn't happened already),
+ // also creates synchonization primitives for shared access from multiple direct3d devices.
+ if (complete_img(d3d_img.get(), dummy)) return { nullptr, nullptr };
+
+ // This image is shared between capture direct3d device and encoders direct3d devices,
+ // we must acquire lock before doing anything to it.
+ texture_lock_helper lock_helper(d3d_img->capture_mutex.get());
+ if (!lock_helper.lock()) {
+ BOOST_LOG(error) << "Failed to lock capture texture";
+ return { nullptr, nullptr };
}
- // Populate the image with dummy data. This is required because these images could be reused
- // after rendering (in which case they would have a cursor already rendered into them).
- device_ctx->UpdateSubresource(img->capture_texture.get(), 0, nullptr, dummy_data.get(), img->row_pitch, 0);
- }
- else {
- // We must know the capture format in this path or we would have hit the above unknown format case
- if (complete_img(img, false)) {
- return capture_e::error;
+ return { std::move(d3d_img), std::move(lock_helper) };
+ };
+
+ switch (last_frame_action) {
+ case lfa::nothing: {
+ break;
}
- // We have a previously captured frame to reuse. We can't just grab the src texture from
- // the call to AcquireNextFrame() because that won't be valid. It seems to return a texture
- // in the unmodified desktop format (rather than the formats we passed to DuplicateOutput1())
- // if called in that case.
- status = img->capture_mutex->AcquireSync(0, INFINITE);
- if (status != S_OK) {
- BOOST_LOG(error) << "Failed to acquire capture mutex [0x"sv << util::hex(status).to_string_view() << ']';
- return capture_e::error;
+ case lfa::replace_surface_with_img: {
+ auto p_surface = std::get_if(&last_frame_variant);
+ if (!p_surface) {
+ BOOST_LOG(error) << "Logical error at " << __FILE__ << ":" << __LINE__;
+ return capture_e::error;
+ }
+
+ std::shared_ptr img;
+ if (!pull_free_image_cb(img)) return capture_e::interrupted;
+
+ auto [d3d_img, lock] = get_locked_d3d_img(img);
+ if (!d3d_img) return capture_e::error;
+
+ device_ctx->CopyResource(d3d_img->capture_texture.get(), p_surface->get());
+
+ // We delay the destruction of intermediate surface in case the mouse cursor reappears shortly.
+ old_surface_delayed_destruction.reset(p_surface->release());
+ old_surface_timestamp = std::chrono::steady_clock::now();
+
+ last_frame_variant = img;
+ break;
+ }
+
+ case lfa::replace_img_with_surface: {
+ auto p_img = std::get_if>(&last_frame_variant);
+ if (!p_img) {
+ BOOST_LOG(error) << "Logical error at " << __FILE__ << ":" << __LINE__;
+ return capture_e::error;
+ }
+ auto [d3d_img, lock] = get_locked_d3d_img(*p_img);
+ if (!d3d_img) return capture_e::error;
+
+ p_img = nullptr;
+ last_frame_variant = texture2d_t {};
+ auto &surface = std::get(last_frame_variant);
+ if (!create_surface(surface)) return capture_e::error;
+
+ device_ctx->CopyResource(surface.get(), d3d_img->capture_texture.get());
+ break;
+ }
+
+ case lfa::copy_src_to_img: {
+ last_frame_variant = {};
+
+ std::shared_ptr img;
+ if (!pull_free_image_cb(img)) return capture_e::interrupted;
+
+ auto [d3d_img, lock] = get_locked_d3d_img(img);
+ if (!d3d_img) return capture_e::error;
+
+ device_ctx->CopyResource(d3d_img->capture_texture.get(), src.get());
+ last_frame_variant = img;
+ break;
+ }
+
+ case lfa::copy_src_to_surface: {
+ auto p_surface = std::get_if(&last_frame_variant);
+ if (!p_surface) {
+ last_frame_variant = texture2d_t {};
+ p_surface = std::get_if(&last_frame_variant);
+ if (!create_surface(*p_surface)) return capture_e::error;
+ }
+ device_ctx->CopyResource(p_surface->get(), src.get());
+ break;
}
- device_ctx->CopyResource(img->capture_texture.get(), last_frame_copy.get());
}
- if ((cursor_alpha.visible || cursor_xor.visible) && cursor_visible) {
+ auto blend_cursor = [&](img_d3d_t &d3d_img) {
device_ctx->VSSetShader(scene_vs.get(), nullptr, 0);
device_ctx->PSSetShader(scene_ps.get(), nullptr, 0);
- device_ctx->OMSetRenderTargets(1, &img->capture_rt, nullptr);
+ device_ctx->OMSetRenderTargets(1, &d3d_img.capture_rt, nullptr);
if (cursor_alpha.texture.get()) {
// Perform an alpha blending operation
@@ -1018,10 +1177,69 @@ namespace platf::dxgi {
device_ctx->RSSetViewports(0, nullptr);
ID3D11ShaderResourceView *emptyShaderResourceView = nullptr;
device_ctx->PSSetShaderResources(0, 1, &emptyShaderResourceView);
+ };
+
+ switch (out_frame_action) {
+ case ofa::forward_last_img: {
+ auto p_img = std::get_if>(&last_frame_variant);
+ if (!p_img) {
+ BOOST_LOG(error) << "Logical error at " << __FILE__ << ":" << __LINE__;
+ return capture_e::error;
+ }
+ img_out = *p_img;
+ break;
+ }
+
+ case ofa::copy_last_surface_and_blend_cursor: {
+ auto p_surface = std::get_if(&last_frame_variant);
+ if (!p_surface) {
+ BOOST_LOG(error) << "Logical error at " << __FILE__ << ":" << __LINE__;
+ return capture_e::error;
+ }
+ if (!blend_mouse_cursor_flag) {
+ BOOST_LOG(error) << "Logical error at " << __FILE__ << ":" << __LINE__;
+ return capture_e::error;
+ }
+
+ if (!pull_free_image_cb(img_out)) return capture_e::interrupted;
+
+ auto [d3d_img, lock] = get_locked_d3d_img(img_out);
+ if (!d3d_img) return capture_e::error;
+
+ device_ctx->CopyResource(d3d_img->capture_texture.get(), p_surface->get());
+ blend_cursor(*d3d_img);
+ break;
+ }
+
+ case ofa::dummy_fallback: {
+ if (!pull_free_image_cb(img_out)) return capture_e::interrupted;
+
+ // Clear the image if it has been used as a dummy.
+ // It can have the mouse cursor blended onto it.
+ auto old_d3d_img = (img_d3d_t *) img_out.get();
+ bool reclear_dummy = old_d3d_img->dummy && old_d3d_img->capture_texture;
+
+ auto [d3d_img, lock] = get_locked_d3d_img(img_out, true);
+ if (!d3d_img) return capture_e::error;
+
+ if (reclear_dummy) {
+ auto dummy_data = std::make_unique(d3d_img->row_pitch * d3d_img->height);
+ std::fill_n(dummy_data.get(), d3d_img->row_pitch * d3d_img->height, 0);
+ device_ctx->UpdateSubresource(d3d_img->capture_texture.get(), 0, nullptr, dummy_data.get(), d3d_img->row_pitch, 0);
+ }
+
+ if (blend_mouse_cursor_flag) {
+ blend_cursor(*d3d_img);
+ }
+
+ break;
+ }
}
- // Release the mutex to allow encoding of this frame
- img->capture_mutex->ReleaseSync(0);
+ // Perform delayed destruction of the unused surface if the time is due.
+ if (old_surface_delayed_destruction && old_surface_timestamp + 10s < std::chrono::steady_clock::now()) {
+ old_surface_delayed_destruction.reset();
+ }
return capture_e::ok;
}
From 7da33f79511a5228c1f515383a10f63642450276 Mon Sep 17 00:00:00 2001
From: ns6089 <61738816+ns6089@users.noreply.github.com>
Date: Sat, 15 Apr 2023 21:21:02 +0300
Subject: [PATCH 024/144] Remove unnecessary reference
---
src/platform/windows/display_vram.cpp | 3 ---
1 file changed, 3 deletions(-)
diff --git a/src/platform/windows/display_vram.cpp b/src/platform/windows/display_vram.cpp
index f2818602d9b..93cef64e9aa 100644
--- a/src/platform/windows/display_vram.cpp
+++ b/src/platform/windows/display_vram.cpp
@@ -101,8 +101,6 @@ namespace platf::dxgi {
blob_t scene_NW_ps_hlsl;
struct img_d3d_t: public platf::img_t {
- std::shared_ptr display;
-
// These objects are owned by the display_t's ID3D11Device
texture2d_t capture_texture;
render_target_t capture_rt;
@@ -1321,7 +1319,6 @@ namespace platf::dxgi {
// Initialize format-independent fields
img->width = width;
img->height = height;
- img->display = shared_from_this();
img->id = next_image_id++;
return img;
From 808187397d91626fa77b030759066d675713ccbd Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Sun, 16 Apr 2023 12:02:54 -0400
Subject: [PATCH 025/144] build(deps): bump sphinx-copybutton from 0.5.1 to
0.5.2 (#1182)
Signed-off-by: dependabot[bot]
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
docs/requirements.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/requirements.txt b/docs/requirements.txt
index 6160650a168..89923ce41e3 100644
--- a/docs/requirements.txt
+++ b/docs/requirements.txt
@@ -2,4 +2,4 @@ breathe==4.35.0
furo==2023.3.27
m2r2==0.3.3.post2
Sphinx==6.1.3
-sphinx-copybutton==0.5.1
+sphinx-copybutton==0.5.2
From 8f74c3b48230055b64561c3e2ccdaca461ba822d Mon Sep 17 00:00:00 2001
From: Cameron Gutman
Date: Fri, 14 Apr 2023 18:08:53 -0500
Subject: [PATCH 026/144] Leave reference frames unspecified if
REF_FRAMES_RESTRICT is not supported
FFmpeg codecs specify their own defaults that we shouldn't override.
---
src/video.cpp | 14 ++++++++------
1 file changed, 8 insertions(+), 6 deletions(-)
diff --git a/src/video.cpp b/src/video.cpp
index 86de57deab4..776dd50c13a 100644
--- a/src/video.cpp
+++ b/src/video.cpp
@@ -1106,12 +1106,14 @@ namespace video {
ctx->keyint_min = std::numeric_limits::max();
- if (config.numRefFrames == 0) {
- ctx->refs = video_format[encoder_t::REF_FRAMES_AUTOSELECT] ? 0 : 16;
- }
- else {
- // Some client decoders have limits on the number of reference frames
- ctx->refs = video_format[encoder_t::REF_FRAMES_RESTRICT] ? config.numRefFrames : 0;
+ // Some client decoders have limits on the number of reference frames
+ if (config.numRefFrames) {
+ if (video_format[encoder_t::REF_FRAMES_RESTRICT]) {
+ ctx->refs = config.numRefFrames;
+ }
+ else {
+ BOOST_LOG(warning) << "Client requested reference frame limit, but encoder doesn't support it!"sv;
+ }
}
ctx->flags |= (AV_CODEC_FLAG_CLOSED_GOP | AV_CODEC_FLAG_LOW_DELAY);
From d973e5bbc7d63d44f3795f7ef45b9f8e263226f8 Mon Sep 17 00:00:00 2001
From: Cameron Gutman
Date: Fri, 14 Apr 2023 18:19:58 -0500
Subject: [PATCH 027/144] Avoid unnecessary encoder probing during startup and
launch
---
src/video.cpp | 34 ++++++----------------------------
1 file changed, 6 insertions(+), 28 deletions(-)
diff --git a/src/video.cpp b/src/video.cpp
index 776dd50c13a..eef3a028c03 100644
--- a/src/video.cpp
+++ b/src/video.cpp
@@ -277,8 +277,6 @@ namespace video {
enum flag_e {
PASSED, // Is supported
REF_FRAMES_RESTRICT, // Set maximum reference frames
- REF_FRAMES_AUTOSELECT, // Allow encoder to select maximum reference frames (If !REF_FRAMES_RESTRICT --> REF_FRAMES_AUTOSELECT)
- SLICE, // Allow frame to be partitioned into multiple slices
CBR, // Some encoders don't support CBR, if not supported --> attempt constant quantatication parameter instead
DYNAMIC_RANGE, // hdr
VUI_PARAMETERS, // AMD encoder with VAAPI doesn't add VUI parameters to SPS
@@ -294,8 +292,6 @@ namespace video {
switch (flag) {
_CONVERT(PASSED);
_CONVERT(REF_FRAMES_RESTRICT);
- _CONVERT(REF_FRAMES_AUTOSELECT);
- _CONVERT(SLICE);
_CONVERT(CBR);
_CONVERT(DYNAMIC_RANGE);
_CONVERT(VUI_PARAMETERS);
@@ -1222,7 +1218,7 @@ namespace video {
ctx->slices = std::max(config.slicesPerFrame, config::video.min_threads);
}
- if (!video_format[encoder_t::SLICE]) {
+ if (encoder.flags & SINGLE_SLICE_ONLY) {
ctx->slices = 1;
}
@@ -1870,17 +1866,14 @@ namespace video {
encoder.h264.capabilities.set();
encoder.hevc.capabilities.set();
- encoder.hevc[encoder_t::PASSED] = test_hevc;
-
// First, test encoder viability
config_t config_max_ref_frames { 1920, 1080, 60, 1000, 1, 1, 1, 0, 0 };
config_t config_autoselect { 1920, 1080, 60, 1000, 1, 0, 1, 0, 0 };
retry:
auto max_ref_frames_h264 = validate_config(disp, encoder, config_max_ref_frames);
- auto autoselect_h264 = validate_config(disp, encoder, config_autoselect);
-
- if (max_ref_frames_h264 < 0 && autoselect_h264 < 0) {
+ auto autoselect_h264 = max_ref_frames_h264 >= 0 ? max_ref_frames_h264 : validate_config(disp, encoder, config_autoselect);
+ if (autoselect_h264 < 0) {
if (encoder.h264.qp && encoder.h264[encoder_t::CBR]) {
// It's possible the encoder isn't accepting Constant Bit Rate. Turn off CBR and make another attempt
encoder.h264.capabilities.set();
@@ -1900,20 +1893,16 @@ namespace video {
}
encoder.h264[encoder_t::REF_FRAMES_RESTRICT] = max_ref_frames_h264 >= 0;
- encoder.h264[encoder_t::REF_FRAMES_AUTOSELECT] = autoselect_h264 >= 0;
encoder.h264[encoder_t::PASSED] = true;
- encoder.h264[encoder_t::SLICE] = validate_config(disp, encoder, config_max_ref_frames);
if (test_hevc) {
config_max_ref_frames.videoFormat = 1;
config_autoselect.videoFormat = 1;
retry_hevc:
auto max_ref_frames_hevc = validate_config(disp, encoder, config_max_ref_frames);
- auto autoselect_hevc = validate_config(disp, encoder, config_autoselect);
-
- // If HEVC must be supported, but it is not supported
- if (max_ref_frames_hevc < 0 && autoselect_hevc < 0) {
+ auto autoselect_hevc = max_ref_frames_hevc >= 0 ? max_ref_frames_hevc : validate_config(disp, encoder, config_autoselect);
+ if (autoselect_hevc < 0) {
if (encoder.hevc.qp && encoder.hevc[encoder_t::CBR]) {
// It's possible the encoder isn't accepting Constant Bit Rate. Turn off CBR and make another attempt
encoder.hevc.capabilities.set();
@@ -1921,6 +1910,7 @@ namespace video {
goto retry_hevc;
}
+ // If HEVC must be supported, but it is not supported
if (force_hevc) {
return false;
}
@@ -1931,8 +1921,6 @@ namespace video {
}
encoder.hevc[encoder_t::REF_FRAMES_RESTRICT] = max_ref_frames_hevc >= 0;
- encoder.hevc[encoder_t::REF_FRAMES_AUTOSELECT] = autoselect_hevc >= 0;
-
encoder.hevc[encoder_t::PASSED] = max_ref_frames_hevc >= 0 || autoselect_hevc >= 0;
}
@@ -1940,11 +1928,6 @@ namespace video {
{ encoder_t::DYNAMIC_RANGE, { 1920, 1080, 60, 1000, 1, 0, 3, 1, 1 } },
};
- if (!(encoder.flags & SINGLE_SLICE_ONLY)) {
- configs.emplace_back(
- std::pair { encoder_t::SLICE, { 1920, 1080, 60, 1000, 2, 1, 1, 0, 0 } });
- }
-
for (auto &[flag, config] : configs) {
auto h264 = config;
auto hevc = config;
@@ -1960,11 +1943,6 @@ namespace video {
}
}
- if (encoder.flags & SINGLE_SLICE_ONLY) {
- encoder.h264.capabilities[encoder_t::SLICE] = false;
- encoder.hevc.capabilities[encoder_t::SLICE] = false;
- }
-
encoder.h264[encoder_t::VUI_PARAMETERS] = encoder.h264[encoder_t::VUI_PARAMETERS] && !config::sunshine.flags[config::flag::FORCE_VIDEO_HEADER_REPLACE];
encoder.hevc[encoder_t::VUI_PARAMETERS] = encoder.hevc[encoder_t::VUI_PARAMETERS] && !config::sunshine.flags[config::flag::FORCE_VIDEO_HEADER_REPLACE];
From 006a6984c36e49e20cc00759db08dc803f339ebf Mon Sep 17 00:00:00 2001
From: Cameron Gutman
Date: Fri, 14 Apr 2023 19:29:47 -0500
Subject: [PATCH 028/144] Use a faster probe for launch-time encoders that
weren't available before
---
src/video.cpp | 22 ++++++++++++++++------
1 file changed, 16 insertions(+), 6 deletions(-)
diff --git a/src/video.cpp b/src/video.cpp
index eef3a028c03..1304d77670f 100644
--- a/src/video.cpp
+++ b/src/video.cpp
@@ -1852,7 +1852,7 @@ namespace video {
}
bool
- validate_encoder(encoder_t &encoder) {
+ validate_encoder(encoder_t &encoder, bool expect_failure) {
std::shared_ptr disp;
BOOST_LOG(info) << "Trying encoder ["sv << encoder.name << ']';
@@ -1871,7 +1871,9 @@ namespace video {
config_t config_autoselect { 1920, 1080, 60, 1000, 1, 0, 1, 0, 0 };
retry:
- auto max_ref_frames_h264 = validate_config(disp, encoder, config_max_ref_frames);
+ // If we're expecting failure, use the autoselect ref config first since that will always succeed
+ // if the encoder is available.
+ auto max_ref_frames_h264 = expect_failure ? -1 : validate_config(disp, encoder, config_max_ref_frames);
auto autoselect_h264 = max_ref_frames_h264 >= 0 ? max_ref_frames_h264 : validate_config(disp, encoder, config_autoselect);
if (autoselect_h264 < 0) {
if (encoder.h264.qp && encoder.h264[encoder_t::CBR]) {
@@ -1882,6 +1884,10 @@ namespace video {
}
return false;
}
+ else if (expect_failure) {
+ // We expected failure, but actually succeeded. Do the max_ref_frames probe we skipped.
+ max_ref_frames_h264 = validate_config(disp, encoder, config_max_ref_frames);
+ }
std::vector> packet_deficiencies {
{ VUI_PARAMS, encoder_t::VUI_PARAMETERS },
@@ -1975,7 +1981,8 @@ namespace video {
probe_encoders() {
auto encoder_list = encoders;
- // Reset encoder selection
+ // Restart encoder selection
+ auto previous_encoder = chosen_encoder;
chosen_encoder = nullptr;
if (!config::video.encoder.empty()) {
@@ -1985,7 +1992,7 @@ namespace video {
if (encoder->name == config::video.encoder) {
// Remove the encoder from the list entirely if it fails validation
- if (!validate_encoder(*encoder)) {
+ if (!validate_encoder(*encoder, previous_encoder && previous_encoder != encoder)) {
pos = encoder_list.erase(pos);
break;
}
@@ -2016,7 +2023,7 @@ namespace video {
auto encoder = *pos;
// Remove the encoder from the list entirely if it fails validation
- if (!validate_encoder(*encoder)) {
+ if (!validate_encoder(*encoder, previous_encoder && previous_encoder != encoder)) {
pos = encoder_list.erase(pos);
continue;
}
@@ -2042,7 +2049,10 @@ namespace video {
KITTY_WHILE_LOOP(auto pos = std::begin(encoder_list), pos != std::end(encoder_list), {
auto encoder = *pos;
- if (!validate_encoder(*encoder)) {
+ // If we've used a previous encoder and it's not this one, we expect this encoder to
+ // fail to validate. It will use a slightly different order of checks to more quickly
+ // eliminate failing encoders.
+ if (!validate_encoder(*encoder, previous_encoder && previous_encoder != encoder)) {
pos = encoder_list.erase(pos);
continue;
}
From d33bd00bb460c442a652b91efd8e3530941fd736 Mon Sep 17 00:00:00 2001
From: Cameron Gutman
Date: Sun, 16 Apr 2023 15:15:19 -0500
Subject: [PATCH 029/144] Fix HEVC mode if the encoder changes
---
src/nvhttp.cpp | 8 ++++----
src/platform/linux/vaapi.cpp | 5 +++--
src/rtsp.cpp | 5 +++--
src/video.cpp | 16 +++++++++-------
src/video.h | 2 ++
5 files changed, 21 insertions(+), 15 deletions(-)
diff --git a/src/nvhttp.cpp b/src/nvhttp.cpp
index d2cdd6a8af7..f2e65f20483 100644
--- a/src/nvhttp.cpp
+++ b/src/nvhttp.cpp
@@ -622,13 +622,13 @@ namespace nvhttp {
tree.put("root.HttpsPort", map_port(PORT_HTTPS));
tree.put("root.ExternalPort", map_port(PORT_HTTP));
tree.put("root.mac", platf::get_mac_address(local_endpoint.address().to_string()));
- tree.put("root.MaxLumaPixelsHEVC", config::video.hevc_mode > 1 ? "1869449984" : "0");
+ tree.put("root.MaxLumaPixelsHEVC", video::active_hevc_mode > 1 ? "1869449984" : "0");
tree.put("root.LocalIP", local_endpoint.address().to_string());
- if (config::video.hevc_mode == 3) {
+ if (video::active_hevc_mode == 3) {
tree.put("root.ServerCodecModeSupport", "3843");
}
- else if (config::video.hevc_mode == 2) {
+ else if (video::active_hevc_mode == 2) {
tree.put("root.ServerCodecModeSupport", "259");
}
else {
@@ -713,7 +713,7 @@ namespace nvhttp {
for (auto &proc : proc::proc.get_apps()) {
pt::ptree app;
- app.put("IsHdrSupported"s, config::video.hevc_mode == 3 ? 1 : 0);
+ app.put("IsHdrSupported"s, video::active_hevc_mode == 3 ? 1 : 0);
app.put("AppTitle"s, proc.name);
app.put("ID", proc.id);
diff --git a/src/platform/linux/vaapi.cpp b/src/platform/linux/vaapi.cpp
index 12281ac4292..8f3c3705d2e 100644
--- a/src/platform/linux/vaapi.cpp
+++ b/src/platform/linux/vaapi.cpp
@@ -25,6 +25,7 @@ vaSyncBuffer(
#include "src/main.h"
#include "src/platform/common.h"
#include "src/utility.h"
+#include "src/video.h"
using namespace std::literals;
@@ -626,11 +627,11 @@ namespace va {
return false;
}
- if (config::video.hevc_mode > 1 && !query(display.get(), profile_e::HEVCMain)) {
+ if (video::active_hevc_mode > 1 && !query(display.get(), profile_e::HEVCMain)) {
return false;
}
- if (config::video.hevc_mode > 2 && !query(display.get(), profile_e::HEVCMain10)) {
+ if (video::active_hevc_mode > 2 && !query(display.get(), profile_e::HEVCMain10)) {
return false;
}
diff --git a/src/rtsp.cpp b/src/rtsp.cpp
index 98606be93d6..653afde55aa 100644
--- a/src/rtsp.cpp
+++ b/src/rtsp.cpp
@@ -19,6 +19,7 @@ extern "C" {
#include "rtsp.h"
#include "stream.h"
#include "sync.h"
+#include "video.h"
#include
@@ -489,7 +490,7 @@ namespace rtsp_stream {
option.content = const_cast(seqn_str.c_str());
std::stringstream ss;
- if (config::video.hevc_mode != 1) {
+ if (video::active_hevc_mode != 1) {
ss << "sprop-parameter-sets=AAAAAU"sv << std::endl;
}
@@ -690,7 +691,7 @@ namespace rtsp_stream {
}
}
- if (config.monitor.videoFormat != 0 && config::video.hevc_mode == 1) {
+ if (config.monitor.videoFormat != 0 && video::active_hevc_mode == 1) {
BOOST_LOG(warning) << "HEVC is disabled, yet the client requested HEVC"sv;
respond(sock, &option, 400, "BAD REQUEST", req->sequenceNumber, {});
diff --git a/src/video.cpp b/src/video.cpp
index 1304d77670f..41ddab7e11a 100644
--- a/src/video.cpp
+++ b/src/video.cpp
@@ -724,6 +724,7 @@ namespace video {
};
static encoder_t *chosen_encoder;
+ int active_hevc_mode;
void
reset_display(std::shared_ptr &disp, AVHWDeviceType type, const std::string &display_name, const config_t &config) {
@@ -1860,8 +1861,8 @@ namespace video {
BOOST_LOG(info) << "Encoder ["sv << encoder.name << "] failed"sv;
});
- auto force_hevc = config::video.hevc_mode >= 2;
- auto test_hevc = force_hevc || (config::video.hevc_mode == 0 && !(encoder.flags & H264_ONLY));
+ auto force_hevc = active_hevc_mode >= 2;
+ auto test_hevc = force_hevc || (active_hevc_mode == 0 && !(encoder.flags & H264_ONLY));
encoder.h264.capabilities.set();
encoder.hevc.capabilities.set();
@@ -1984,6 +1985,7 @@ namespace video {
// Restart encoder selection
auto previous_encoder = chosen_encoder;
chosen_encoder = nullptr;
+ active_hevc_mode = config::video.hevc_mode;
if (!config::video.encoder.empty()) {
// If there is a specific encoder specified, use it if it passes validation
@@ -1998,9 +2000,9 @@ namespace video {
}
// If we can't satisfy both the encoder and HDR requirement, prefer the encoder over HDR support
- if (config::video.hevc_mode == 3 && !encoder->hevc[encoder_t::DYNAMIC_RANGE]) {
+ if (active_hevc_mode == 3 && !encoder->hevc[encoder_t::DYNAMIC_RANGE]) {
BOOST_LOG(warning) << "Encoder ["sv << config::video.encoder << "] does not support HDR on this system"sv;
- config::video.hevc_mode = 0;
+ active_hevc_mode = 0;
}
chosen_encoder = encoder;
@@ -2018,7 +2020,7 @@ namespace video {
BOOST_LOG(info) << "// Testing for available encoders, this may generate errors. You can safely ignore those errors. //"sv;
// If we haven't found an encoder yet, but we want one with HDR support, search for that now.
- if (chosen_encoder == nullptr && config::video.hevc_mode == 3) {
+ if (chosen_encoder == nullptr && active_hevc_mode == 3) {
KITTY_WHILE_LOOP(auto pos = std::begin(encoder_list), pos != std::end(encoder_list), {
auto encoder = *pos;
@@ -2094,8 +2096,8 @@ namespace video {
BOOST_LOG(info) << "Found encoder "sv << encoder.name << ": ["sv << encoder.h264.name << ']';
}
- if (config::video.hevc_mode == 0) {
- config::video.hevc_mode = encoder.hevc[encoder_t::PASSED] ? (encoder.hevc[encoder_t::DYNAMIC_RANGE] ? 3 : 2) : 1;
+ if (active_hevc_mode == 0) {
+ active_hevc_mode = encoder.hevc[encoder_t::PASSED] ? (encoder.hevc[encoder_t::DYNAMIC_RANGE] ? 3 : 2) : 1;
}
return 0;
diff --git a/src/video.h b/src/video.h
index c02c317e5bc..d3eb1a955ac 100644
--- a/src/video.h
+++ b/src/video.h
@@ -90,6 +90,8 @@ namespace video {
extern color_t colors[6];
+ extern int active_hevc_mode;
+
void
capture(
safe::mail_t mail,
From dd6fcbd7b8c1719566f8a29ccd4580110101bf5a Mon Sep 17 00:00:00 2001
From: Cameron Gutman
Date: Sat, 15 Apr 2023 17:41:02 -0500
Subject: [PATCH 030/144] Grant everyone SYNCHRONIZE access to our systray
thread
Explorer requires this access right to remove our tray icon if we terminate unexpectedly
---
src/system_tray.cpp | 78 +++++++++++++++++++++++++++++++++++++++++++--
1 file changed, 75 insertions(+), 3 deletions(-)
diff --git a/src/system_tray.cpp b/src/system_tray.cpp
index 4b10acffcaf..b42acc191e2 100644
--- a/src/system_tray.cpp
+++ b/src/system_tray.cpp
@@ -4,7 +4,10 @@
// macros
#if defined SUNSHINE_TRAY && SUNSHINE_TRAY >= 1
- #if defined(_WIN32) || defined(_WIN64)
+ #if defined(_WIN32)
+ #define WIN32_LEAN_AND_MEAN
+ #include
+ #include
#define TRAY_ICON WEB_DIR "images/favicon.ico"
#elif defined(__linux__) || defined(linux) || defined(__linux)
#define TRAY_ICON "sunshine"
@@ -42,7 +45,7 @@ namespace system_tray {
boost::filesystem::path working_dir;
// if windows
- #if defined(_WIN32) || defined(_WIN64)
+ #if defined(_WIN32)
// set working dir to Windows system directory
working_dir = boost::filesystem::path(std::getenv("SystemRoot"));
@@ -134,7 +137,7 @@ namespace system_tray {
// Tray menu
static struct tray tray = {
.icon = TRAY_ICON,
- #if defined(_WIN32) || defined(_WIN64)
+ #if defined(_WIN32)
.tooltip = const_cast("Sunshine"), // cast the string literal to a non-const char* pointer
#endif
.menu =
@@ -162,6 +165,75 @@ namespace system_tray {
*/
int
system_tray() {
+ #ifdef _WIN32
+ // If we're running as SYSTEM, Explorer.exe will not have permission to open our thread handle
+ // to monitor for thread termination. If Explorer fails to open our thread, our tray icon
+ // will persist forever if we terminate unexpectedly. To avoid this, we will modify our thread
+ // DACL to add an ACE that allows SYNCHRONIZE access to Everyone.
+ {
+ PACL old_dacl;
+ PSECURITY_DESCRIPTOR sd;
+ auto error = GetSecurityInfo(GetCurrentThread(),
+ SE_KERNEL_OBJECT,
+ DACL_SECURITY_INFORMATION,
+ nullptr,
+ nullptr,
+ &old_dacl,
+ nullptr,
+ &sd);
+ if (error != ERROR_SUCCESS) {
+ BOOST_LOG(warning) << "GetSecurityInfo() failed: "sv << error;
+ return 1;
+ }
+
+ auto free_sd = util::fail_guard([sd]() {
+ LocalFree(sd);
+ });
+
+ SID_IDENTIFIER_AUTHORITY sid_authority = SECURITY_WORLD_SID_AUTHORITY;
+ PSID world_sid;
+ if (!AllocateAndInitializeSid(&sid_authority, 1, SECURITY_WORLD_RID, 0, 0, 0, 0, 0, 0, 0, &world_sid)) {
+ error = GetLastError();
+ BOOST_LOG(warning) << "AllocateAndInitializeSid() failed: "sv << error;
+ return 1;
+ }
+
+ auto free_sid = util::fail_guard([world_sid]() {
+ FreeSid(world_sid);
+ });
+
+ EXPLICIT_ACCESS ea {};
+ ea.grfAccessPermissions = SYNCHRONIZE;
+ ea.grfAccessMode = GRANT_ACCESS;
+ ea.grfInheritance = NO_INHERITANCE;
+ ea.Trustee.TrusteeForm = TRUSTEE_IS_SID;
+ ea.Trustee.ptstrName = (LPSTR) world_sid;
+
+ PACL new_dacl;
+ error = SetEntriesInAcl(1, &ea, old_dacl, &new_dacl);
+ if (error != ERROR_SUCCESS) {
+ BOOST_LOG(warning) << "SetEntriesInAcl() failed: "sv << error;
+ return 1;
+ }
+
+ auto free_new_dacl = util::fail_guard([new_dacl]() {
+ LocalFree(new_dacl);
+ });
+
+ error = SetSecurityInfo(GetCurrentThread(),
+ SE_KERNEL_OBJECT,
+ DACL_SECURITY_INFORMATION,
+ nullptr,
+ nullptr,
+ new_dacl,
+ nullptr);
+ if (error != ERROR_SUCCESS) {
+ BOOST_LOG(warning) << "SetSecurityInfo() failed: "sv << error;
+ return 1;
+ }
+ }
+ #endif
+
if (tray_init(&tray) < 0) {
BOOST_LOG(warning) << "Failed to create system tray"sv;
return 1;
From f51876893e731422b70e75751ea9dc5749075670 Mon Sep 17 00:00:00 2001
From: Cameron Gutman
Date: Sat, 15 Apr 2023 17:51:32 -0500
Subject: [PATCH 031/144] Stop the service if the user quits via the tray icon
---
src/main.cpp | 37 +++++++++++++++++++++++++++--------
src/main.h | 6 ++++++
src/platform/windows/misc.cpp | 7 ++++---
src/system_tray.cpp | 10 +++++++++-
tools/sunshinesvc.cpp | 9 ++++++++-
5 files changed, 56 insertions(+), 13 deletions(-)
diff --git a/src/main.cpp b/src/main.cpp
index 4c1ae2abec8..0733778f2c5 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -107,6 +107,31 @@ namespace version {
}
} // namespace version
+namespace lifetime {
+ static std::atomic_int desired_exit_code;
+
+ /**
+ * @brief Terminates Sunshine gracefully with the provided exit code
+ * @param exit_code The exit code to return from main()
+ * @param async Specifies whether our termination will be non-blocking
+ */
+ void
+ exit_sunshine(int exit_code, bool async) {
+ // Store the exit code of the first exit_sunshine() call
+ int zero = 0;
+ desired_exit_code.compare_exchange_strong(zero, exit_code);
+
+ // Raise SIGINT to start termination
+ std::raise(SIGINT);
+
+ // Termination will happen asynchronously, but the caller may
+ // have wanted synchronous behavior.
+ while (!async) {
+ std::this_thread::sleep_for(1s);
+ }
+ }
+} // namespace lifetime
+
/**
* @brief Flush the log.
*
@@ -159,13 +184,9 @@ LRESULT CALLBACK
SessionMonitorWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
switch (uMsg) {
case WM_ENDSESSION: {
- // Raise a SIGINT to trigger our cleanup logic and terminate ourselves
+ // Terminate ourselves with a blocking exit call
std::cout << "Received WM_ENDSESSION"sv << std::endl;
- std::raise(SIGINT);
-
- // The signal handling is asynchronous, so we will wait here to be terminated.
- // If for some reason we don't terminate in a few seconds, Windows will kill us.
- SuspendThread(GetCurrentThread());
+ lifetime::exit_sunshine(0, false);
return 0;
}
default:
@@ -376,7 +397,7 @@ main(int argc, char *argv[]) {
// FIXME: Temporary workaround: Simple-Web_server needs to be updated or replaced
if (shutdown_event->peek()) {
- return 0;
+ return lifetime::desired_exit_code;
}
std::thread httpThread { nvhttp::start };
@@ -395,7 +416,7 @@ main(int argc, char *argv[]) {
system_tray::end_tray();
#endif
- return 0;
+ return lifetime::desired_exit_code;
}
/**
diff --git a/src/main.h b/src/main.h
index aff1cfdd6bb..05c0f15fb24 100644
--- a/src/main.h
+++ b/src/main.h
@@ -79,4 +79,10 @@ namespace mail {
#undef MAIL
} // namespace mail
+
+namespace lifetime {
+ void
+ exit_sunshine(int exit_code, bool async);
+} // namespace lifetime
+
#endif // SUNSHINE_MAIN_H
diff --git a/src/platform/windows/misc.cpp b/src/platform/windows/misc.cpp
index 45b25e3ddef..5335cae2edd 100644
--- a/src/platform/windows/misc.cpp
+++ b/src/platform/windows/misc.cpp
@@ -727,9 +727,10 @@ namespace platf {
bool
restart() {
- // Raise SIGINT to trigger the graceful exit logic. The service will
- // restart us in a few seconds.
- std::raise(SIGINT);
+ // Gracefully exit. The service will restart us in a few seconds.
+ // We use an async exit call here because we can't block the
+ // HTTP thread or we'll hang shutdown.
+ lifetime::exit_sunshine(0, true);
return true;
}
diff --git a/src/system_tray.cpp b/src/system_tray.cpp
index b42acc191e2..b25d6d38714 100644
--- a/src/system_tray.cpp
+++ b/src/system_tray.cpp
@@ -131,7 +131,15 @@ namespace system_tray {
tray_quit_cb(struct tray_menu *item) {
BOOST_LOG(info) << "Quiting from system tray"sv;
- std::raise(SIGINT);
+ #ifdef _WIN32
+ // If we're running in a service, return a special status to
+ // tell it to terminate too, otherwise it will just respawn us.
+ if (GetConsoleWindow() == NULL) {
+ lifetime::exit_sunshine(ERROR_SHUTDOWN_IN_PROGRESS, false);
+ }
+ #endif
+
+ lifetime::exit_sunshine(0, false);
}
// Tray menu
diff --git a/tools/sunshinesvc.cpp b/tools/sunshinesvc.cpp
index 19a7b9dcccd..e02fc8b9a75 100644
--- a/tools/sunshinesvc.cpp
+++ b/tools/sunshinesvc.cpp
@@ -301,9 +301,16 @@ ServiceMain(DWORD dwArgc, LPTSTR *lpszArgv) {
}
break;
- case WAIT_OBJECT_0 + 1:
+ case WAIT_OBJECT_0 + 1: {
// Sunshine terminated itself.
+
+ DWORD exit_code;
+ if (GetExitCodeProcess(process_info.hProcess, &exit_code) && exit_code == ERROR_SHUTDOWN_IN_PROGRESS) {
+ // Sunshine is asking for us to shut down, so gracefully stop ourselves.
+ SetEvent(stop_event);
+ }
break;
+ }
}
CloseHandle(process_info.hThread);
From 820a20dc709caaf65d1c94c01dc8cd7d5ec6d284 Mon Sep 17 00:00:00 2001
From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com>
Date: Mon, 17 Apr 2023 10:56:51 -0400
Subject: [PATCH 032/144] build(docker): update dependencies (#1193)
---
docker/fedora-36.dockerfile | 18 +++++++++---------
docker/fedora-37.dockerfile | 16 ++++++++--------
docker/ubuntu-20.04.dockerfile | 2 +-
3 files changed, 18 insertions(+), 18 deletions(-)
diff --git a/docker/fedora-36.dockerfile b/docker/fedora-36.dockerfile
index 33adf2250b4..e1e4ee6ba5e 100644
--- a/docker/fedora-36.dockerfile
+++ b/docker/fedora-36.dockerfile
@@ -31,15 +31,15 @@ dnf -y update
dnf -y group install "Development Tools"
dnf -y install \
boost-devel-1.76.0* \
- cmake-3.22.2* \
- gcc-12.0.1* \
- gcc-c++-12.0.1* \
+ cmake-3.25.2* \
+ gcc-12.2.1* \
+ gcc-c++-12.2.1* \
git-2.39.2* \
libappindicator-gtk3-devel-12.10.0* \
libcap-devel-2.48* \
libcurl-devel-7.82.0* \
- libdrm-devel-2.4.110* \
- libevdev-devel-1.12.0* \
+ libdrm-devel-2.4.114* \
+ libevdev-devel-1.13.0* \
libva-devel-2.14.0* \
libvdpau-devel-1.5* \
libX11-devel-1.7.3* \
@@ -50,13 +50,13 @@ dnf -y install \
libXinerama-devel-1.1.4* \
libXrandr-devel-1.5.2* \
libXtst-devel-1.2.3* \
- mesa-libGL-devel-22.0.1* \
- npm-8.3.1* \
+ mesa-libGL-devel-22.1.7* \
+ npm-8.19.2* \
numactl-devel-2.0.14* \
- openssl-devel-3.0.2* \
+ openssl-devel-3.0.8* \
opus-devel-1.3.1* \
pulseaudio-libs-devel-15.0* \
- rpm-build-4.17.0* \
+ rpm-build-4.17.1* \
wget-1.21.3* \
which-2.21*
if [[ "${TARGETPLATFORM}" == 'linux/amd64' ]]; then
diff --git a/docker/fedora-37.dockerfile b/docker/fedora-37.dockerfile
index 2e1be80c368..a497f57b0f3 100644
--- a/docker/fedora-37.dockerfile
+++ b/docker/fedora-37.dockerfile
@@ -31,18 +31,18 @@ dnf -y update
dnf -y group install "Development Tools"
dnf -y install \
boost-devel-1.78.0* \
- cmake-3.24.1* \
+ cmake-3.26.3* \
gcc-12.2.1* \
gcc-c++-12.2.1* \
git-2.39.2* \
libappindicator-gtk3-devel-12.10.1* \
libcap-devel-2.48* \
libcurl-devel-7.85.0* \
- libdrm-devel-2.4.112* \
+ libdrm-devel-2.4.114* \
libevdev-devel-1.13.0* \
- libva-devel-2.15.0* \
+ libva-devel-2.16.0* \
libvdpau-devel-1.5* \
- libX11-devel-1.8.1* \
+ libX11-devel-1.8.4* \
libxcb-devel-1.13.1* \
libXcursor-devel-1.2.1* \
libXfixes-devel-6.0.0* \
@@ -50,17 +50,17 @@ dnf -y install \
libXinerama-devel-1.1.4* \
libXrandr-devel-1.5.2* \
libXtst-devel-1.2.3* \
- mesa-libGL-devel-22.2.2* \
- npm-8.15.0* \
+ mesa-libGL-devel-22.3.7* \
+ nodejs-npm-9.5.0* \
numactl-devel-2.0.14* \
- openssl-devel-3.0.5* \
+ openssl-devel-3.0.8* \
opus-devel-1.3.1* \
pulseaudio-libs-devel-16.1* \
rpm-build-4.18.0* \
wget-1.21.3* \
which-2.21*
if [[ "${TARGETPLATFORM}" == 'linux/amd64' ]]; then
- dnf -y install intel-mediasdk-devel-22.4.4*
+ dnf -y install intel-mediasdk-devel-22.5.4*
fi
dnf clean all
rm -rf /var/cache/yum
diff --git a/docker/ubuntu-20.04.dockerfile b/docker/ubuntu-20.04.dockerfile
index 4de2c02ca2d..f9c8ab46a4d 100644
--- a/docker/ubuntu-20.04.dockerfile
+++ b/docker/ubuntu-20.04.dockerfile
@@ -48,7 +48,7 @@ apt-get install -y --no-install-recommends \
libnuma-dev=2.0.12* \
libopus-dev=1.3.1* \
libpulse-dev=1:13.99.1* \
- libssl-dev=1.1.1* \
+ libssl-dev=1.1.1f* \
libva-dev=2.7.0* \
libvdpau-dev=1.3* \
libwayland-dev=1.18.0* \
From 91afe331be2c1a0060531cef55ff114f91706f9d Mon Sep 17 00:00:00 2001
From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com>
Date: Tue, 18 Apr 2023 17:15:13 -0400
Subject: [PATCH 033/144] build(deps): bump git from 2.39.2 to 2.40.0 for
fedora 37 (#1196)
---
docker/fedora-37.dockerfile | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docker/fedora-37.dockerfile b/docker/fedora-37.dockerfile
index a497f57b0f3..0df4511e161 100644
--- a/docker/fedora-37.dockerfile
+++ b/docker/fedora-37.dockerfile
@@ -34,7 +34,7 @@ dnf -y install \
cmake-3.26.3* \
gcc-12.2.1* \
gcc-c++-12.2.1* \
- git-2.39.2* \
+ git-2.40.0* \
libappindicator-gtk3-devel-12.10.1* \
libcap-devel-2.48* \
libcurl-devel-7.85.0* \
From 2215ab98b401907401676dfd3b8a126a4107ed2e Mon Sep 17 00:00:00 2001
From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com>
Date: Tue, 18 Apr 2023 17:41:45 -0400
Subject: [PATCH 034/144] fix(web ui): add response headers (#1190)
---
src/confighttp.cpp | 38 +++++++++++++++++++++++++++-----------
1 file changed, 27 insertions(+), 11 deletions(-)
diff --git a/src/confighttp.cpp b/src/confighttp.cpp
index 908ef721c22..cd1d7ee8623 100644
--- a/src/confighttp.cpp
+++ b/src/confighttp.cpp
@@ -159,7 +159,9 @@ namespace confighttp {
std::string header = read_file(WEB_DIR "header.html");
std::string content = read_file(WEB_DIR "index.html");
- response->write(header + content);
+ SimpleWeb::CaseInsensitiveMultimap headers;
+ headers.emplace("Content-Type", "text/html; charset=utf-8");
+ response->write(header + content, headers);
}
void
@@ -170,7 +172,9 @@ namespace confighttp {
std::string header = read_file(WEB_DIR "header.html");
std::string content = read_file(WEB_DIR "pin.html");
- response->write(header + content);
+ SimpleWeb::CaseInsensitiveMultimap headers;
+ headers.emplace("Content-Type", "text/html; charset=utf-8");
+ response->write(header + content, headers);
}
void
@@ -179,11 +183,11 @@ namespace confighttp {
print_req(request);
- SimpleWeb::CaseInsensitiveMultimap headers;
- headers.emplace("Access-Control-Allow-Origin", "https://images.igdb.com/");
-
std::string header = read_file(WEB_DIR "header.html");
std::string content = read_file(WEB_DIR "apps.html");
+ SimpleWeb::CaseInsensitiveMultimap headers;
+ headers.emplace("Content-Type", "text/html; charset=utf-8");
+ headers.emplace("Access-Control-Allow-Origin", "https://images.igdb.com/");
response->write(header + content, headers);
}
@@ -195,7 +199,9 @@ namespace confighttp {
std::string header = read_file(WEB_DIR "header.html");
std::string content = read_file(WEB_DIR "clients.html");
- response->write(header + content);
+ SimpleWeb::CaseInsensitiveMultimap headers;
+ headers.emplace("Content-Type", "text/html; charset=utf-8");
+ response->write(header + content, headers);
}
void
@@ -206,7 +212,9 @@ namespace confighttp {
std::string header = read_file(WEB_DIR "header.html");
std::string content = read_file(WEB_DIR "config.html");
- response->write(header + content);
+ SimpleWeb::CaseInsensitiveMultimap headers;
+ headers.emplace("Content-Type", "text/html; charset=utf-8");
+ response->write(header + content, headers);
}
void
@@ -217,7 +225,9 @@ namespace confighttp {
std::string header = read_file(WEB_DIR "header.html");
std::string content = read_file(WEB_DIR "password.html");
- response->write(header + content);
+ SimpleWeb::CaseInsensitiveMultimap headers;
+ headers.emplace("Content-Type", "text/html; charset=utf-8");
+ response->write(header + content, headers);
}
void
@@ -229,7 +239,9 @@ namespace confighttp {
}
std::string header = read_file(WEB_DIR "header-no-nav.html");
std::string content = read_file(WEB_DIR "welcome.html");
- response->write(header + content);
+ SimpleWeb::CaseInsensitiveMultimap headers;
+ headers.emplace("Content-Type", "text/html; charset=utf-8");
+ response->write(header + content, headers);
}
void
@@ -240,7 +252,9 @@ namespace confighttp {
std::string header = read_file(WEB_DIR "header.html");
std::string content = read_file(WEB_DIR "troubleshooting.html");
- response->write(header + content);
+ SimpleWeb::CaseInsensitiveMultimap headers;
+ headers.emplace("Content-Type", "text/html; charset=utf-8");
+ response->write(header + content, headers);
}
void
@@ -314,7 +328,9 @@ namespace confighttp {
print_req(request);
std::string content = read_file(config::stream.file_apps.c_str());
- response->write(content);
+ SimpleWeb::CaseInsensitiveMultimap headers;
+ headers.emplace("Content-Type", "application/json");
+ response->write(content, headers);
}
void
From 25d0f434c56108729701f23041b3f1652dbf6710 Mon Sep 17 00:00:00 2001
From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com>
Date: Sat, 22 Apr 2023 17:43:01 -0400
Subject: [PATCH 035/144] build(deps): update fedora dependencies (#1206)
---
docker/fedora-36.dockerfile | 4 ++--
docker/fedora-37.dockerfile | 2 +-
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/docker/fedora-36.dockerfile b/docker/fedora-36.dockerfile
index e1e4ee6ba5e..ce69023add6 100644
--- a/docker/fedora-36.dockerfile
+++ b/docker/fedora-36.dockerfile
@@ -31,10 +31,10 @@ dnf -y update
dnf -y group install "Development Tools"
dnf -y install \
boost-devel-1.76.0* \
- cmake-3.25.2* \
+ cmake-3.26.3* \
gcc-12.2.1* \
gcc-c++-12.2.1* \
- git-2.39.2* \
+ git-2.40.0* \
libappindicator-gtk3-devel-12.10.0* \
libcap-devel-2.48* \
libcurl-devel-7.82.0* \
diff --git a/docker/fedora-37.dockerfile b/docker/fedora-37.dockerfile
index 0df4511e161..c48b1223cce 100644
--- a/docker/fedora-37.dockerfile
+++ b/docker/fedora-37.dockerfile
@@ -50,7 +50,7 @@ dnf -y install \
libXinerama-devel-1.1.4* \
libXrandr-devel-1.5.2* \
libXtst-devel-1.2.3* \
- mesa-libGL-devel-22.3.7* \
+ mesa-libGL-devel-23.0.2* \
nodejs-npm-9.5.0* \
numactl-devel-2.0.14* \
openssl-devel-3.0.8* \
From 2138d0c9bc7766a08265d9f2cc8632237b6bc88c Mon Sep 17 00:00:00 2001
From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com>
Date: Sat, 22 Apr 2023 20:41:32 -0400
Subject: [PATCH 036/144] docs(advanced_usage): fix nvlax url (#1205)
---
docs/source/about/advanced_usage.rst | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/source/about/advanced_usage.rst b/docs/source/about/advanced_usage.rst
index cf4a348c184..1353f4a1d62 100644
--- a/docs/source/about/advanced_usage.rst
+++ b/docs/source/about/advanced_usage.rst
@@ -790,7 +790,7 @@ capture
nvfbc Use NVIDIA Frame Buffer Capture to capture direct to GPU memory. This is usually the fastest method for
NVIDIA cards. For GeForce cards it will only work with drivers patched with
`nvidia-patch `_
- or `nvlax `_.
+ or `nvlax `_.
wlr Capture for wlroots based Wayland compositors via DMA-BUF.
kms DRM/KMS screen capture from the kernel. This requires that sunshine has cap_sys_admin capability.
See :ref:`Linux Setup `.
From 5a8b6c3ed0aa2277e50c55a07d6f5ce4522bbf92 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Mon, 24 Apr 2023 10:16:38 -0400
Subject: [PATCH 037/144] build(deps): bump sphinx from 6.1.3 to 6.2.0 (#1212)
Signed-off-by: dependabot[bot]
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
docs/requirements.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/requirements.txt b/docs/requirements.txt
index 89923ce41e3..f4f56be39b4 100644
--- a/docs/requirements.txt
+++ b/docs/requirements.txt
@@ -1,5 +1,5 @@
breathe==4.35.0
furo==2023.3.27
m2r2==0.3.3.post2
-Sphinx==6.1.3
+Sphinx==6.2.0
sphinx-copybutton==0.5.2
From 5ad5699456be78ff45865a16d0ae1961498baa8f Mon Sep 17 00:00:00 2001
From: istori1 <107304850+istori1@users.noreply.github.com>
Date: Tue, 25 Apr 2023 21:45:32 -0400
Subject: [PATCH 038/144] build(linux): honor install prefix for tray icon
(#1208)
---
CMakeLists.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 9e197fe34f2..53d809c598e 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -944,7 +944,7 @@ elseif(UNIX)
if(${SUNSHINE_TRAY} STREQUAL 1)
install(FILES "${CMAKE_SOURCE_DIR}/sunshine.svg"
- DESTINATION "/usr/share/icons")
+ DESTINATION "${CMAKE_INSTALL_PREFIX}/share/icons")
set(CPACK_DEBIAN_PACKAGE_DEPENDS "\
${CPACK_DEBIAN_PACKAGE_DEPENDS}, \
From db5c03282ca216fe318a9303f46aedf619f74d7c Mon Sep 17 00:00:00 2001
From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com>
Date: Wed, 26 Apr 2023 09:27:08 -0400
Subject: [PATCH 039/144] refactor(tray icon): update header definitions
(#1221)
---
src/main.h | 14 --------------
src/system_tray.h | 7 ++-----
2 files changed, 2 insertions(+), 19 deletions(-)
diff --git a/src/main.h b/src/main.h
index 05c0f15fb24..1a892386ff9 100644
--- a/src/main.h
+++ b/src/main.h
@@ -33,20 +33,6 @@ main(int argc, char *argv[]);
void
log_flush();
void
-open_url(const std::string &url);
-void
-tray_open_ui_cb(struct tray_menu *item);
-void
-tray_donate_github_cb(struct tray_menu *item);
-void
-tray_donate_mee6_cb(struct tray_menu *item);
-void
-tray_donate_patreon_cb(struct tray_menu *item);
-void
-tray_donate_paypal_cb(struct tray_menu *item);
-void
-tray_quit_cb(struct tray_menu *item);
-void
print_help(const char *name);
std::string
read_file(const char *path);
diff --git a/src/system_tray.h b/src/system_tray.h
index 46849883381..a1e86c0bd73 100644
--- a/src/system_tray.h
+++ b/src/system_tray.h
@@ -1,8 +1,6 @@
/**
-* @file system_tray.h
-*/
-// macros
-#if defined SUNSHINE_TRAY && SUNSHINE_TRAY >= 1
+ * @file system_tray.h
+ */
// system_tray namespace
namespace system_tray {
@@ -30,4 +28,3 @@ namespace system_tray {
end_tray();
} // namespace system_tray
-#endif
From 4459e54583ca60a7876d3deaa09c4af20648e6fd Mon Sep 17 00:00:00 2001
From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com>
Date: Thu, 27 Apr 2023 09:50:22 -0400
Subject: [PATCH 040/144] docs(doxygen): enable macro expansion (#1226)
---
docs/Doxyfile | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/Doxyfile b/docs/Doxyfile
index cac6c8f8e9b..9fe84048d7d 100644
--- a/docs/Doxyfile
+++ b/docs/Doxyfile
@@ -2331,7 +2331,7 @@ ENABLE_PREPROCESSING = YES
# The default value is: NO.
# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
-MACRO_EXPANSION = NO
+MACRO_EXPANSION = YES
# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES then
# the macro expansion is limited to the macros specified with the PREDEFINED and
From eed4df1eeefcd5a091bb19ba35b6487cb610139a Mon Sep 17 00:00:00 2001
From: Zach Sirotto
Date: Thu, 27 Apr 2023 10:19:33 -0400
Subject: [PATCH 041/144] fix: header guards on system tray (#1222)
Co-authored-by: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com>
---
src/system_tray.h | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/src/system_tray.h b/src/system_tray.h
index a1e86c0bd73..81e62afef7c 100644
--- a/src/system_tray.h
+++ b/src/system_tray.h
@@ -2,6 +2,9 @@
* @file system_tray.h
*/
+#ifndef SUNSHINE_SYSTEM_TRAY_H
+#define SUNSHINE_SYSTEM_TRAY_H
+
// system_tray namespace
namespace system_tray {
@@ -28,3 +31,4 @@ namespace system_tray {
end_tray();
} // namespace system_tray
+#endif // SUNSHINE_SYSTEM_TRAY_H
From 82d6bc28c3e0d3718691e7f332ace713849a1964 Mon Sep 17 00:00:00 2001
From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com>
Date: Thu, 27 Apr 2023 18:28:16 -0400
Subject: [PATCH 042/144] docs(diagrams): add diagrams using graphviz (#1228)
---
.readthedocs.yaml | 14 ++------------
docs/Doxyfile | 6 +++---
docs/source/source/src/audio.rst | 1 +
docs/source/source/src/cbs.rst | 1 +
docs/source/source/src/config.rst | 1 +
docs/source/source/src/confighttp.rst | 1 +
docs/source/source/src/crypto.rst | 1 +
docs/source/source/src/httpcommon.rst | 1 +
docs/source/source/src/input.rst | 1 +
docs/source/source/src/main.rst | 1 +
docs/source/source/src/move_by_copy.rst | 1 +
docs/source/source/src/network.rst | 1 +
docs/source/source/src/nvhttp.rst | 1 +
docs/source/source/src/platform/linux/cuda.rst | 1 +
docs/source/source/src/platform/linux/vaapi.rst | 1 +
docs/source/source/src/platform/linux/wayland.rst | 1 +
docs/source/source/src/platform/macos/misc.rst | 1 +
docs/source/source/src/platform/windows/misc.rst | 1 +
docs/source/source/src/process.rst | 1 +
docs/source/source/src/round_robin.rst | 1 +
docs/source/source/src/rtsp.rst | 1 +
docs/source/source/src/stream.rst | 1 +
docs/source/source/src/sync.rst | 1 +
docs/source/source/src/system_tray.rst | 1 +
docs/source/source/src/task_pool.rst | 1 +
docs/source/source/src/thread_pool.rst | 1 +
docs/source/source/src/thread_safe.rst | 1 +
docs/source/source/src/upnp.rst | 1 +
docs/source/source/src/uuid.rst | 1 +
docs/source/source/src/video.rst | 1 +
30 files changed, 33 insertions(+), 15 deletions(-)
diff --git a/.readthedocs.yaml b/.readthedocs.yaml
index 75ec50a72e3..3316c40ee40 100644
--- a/.readthedocs.yaml
+++ b/.readthedocs.yaml
@@ -11,18 +11,8 @@ build:
os: ubuntu-20.04
tools:
python: "3.10"
-
-## apt packages required packages to run cmake on sunshine, note that additional packages are required
-# apt_packages:
-# - cmake
-# - libboost-filesystem-dev
-# - libboost-log-dev
-# - libboost-thread-dev
-
-## run cmake
-# jobs:
-# pre_build:
-# - cmake .
+ apt_packages:
+ - graphviz
## Include the submodules, required for cmake
# submodules:
diff --git a/docs/Doxyfile b/docs/Doxyfile
index 9fe84048d7d..728c246fcbf 100644
--- a/docs/Doxyfile
+++ b/docs/Doxyfile
@@ -184,7 +184,7 @@ FULL_PATH_NAMES = YES
# will be relative from the directory where doxygen is started.
# This tag requires that the tag FULL_PATH_NAMES is set to YES.
-STRIP_FROM_PATH =
+STRIP_FROM_PATH = ../
# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the
# path mentioned in the documentation of a class, which tells the reader which
@@ -193,7 +193,7 @@ STRIP_FROM_PATH =
# specify the list of include paths that are normally passed to the compiler
# using the -I flag.
-STRIP_FROM_INC_PATH =
+STRIP_FROM_INC_PATH = ../
# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but
# less readable) file names. This can be useful is your file systems doesn't
@@ -2463,7 +2463,7 @@ HIDE_UNDOC_RELATIONS = YES
# set to NO
# The default value is: NO.
-HAVE_DOT = NO
+HAVE_DOT = YES
# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is allowed
# to run in parallel. When set to 0 doxygen will base this on the number of
diff --git a/docs/source/source/src/audio.rst b/docs/source/source/src/audio.rst
index a8fb3c9ca45..08665fe6780 100644
--- a/docs/source/source/src/audio.rst
+++ b/docs/source/source/src/audio.rst
@@ -2,3 +2,4 @@ audio
=====
.. doxygenfile:: audio.h
+ :allow-dot-graphs:
diff --git a/docs/source/source/src/cbs.rst b/docs/source/source/src/cbs.rst
index 2abe9915772..6547c495821 100644
--- a/docs/source/source/src/cbs.rst
+++ b/docs/source/source/src/cbs.rst
@@ -2,3 +2,4 @@ cbs
===
.. doxygenfile:: cbs.h
+ :allow-dot-graphs:
diff --git a/docs/source/source/src/config.rst b/docs/source/source/src/config.rst
index 44e10565493..34bccff3ce2 100644
--- a/docs/source/source/src/config.rst
+++ b/docs/source/source/src/config.rst
@@ -2,3 +2,4 @@ config
======
.. doxygenfile:: config.h
+ :allow-dot-graphs:
diff --git a/docs/source/source/src/confighttp.rst b/docs/source/source/src/confighttp.rst
index 3b46ba5edf7..348f6521ba6 100644
--- a/docs/source/source/src/confighttp.rst
+++ b/docs/source/source/src/confighttp.rst
@@ -2,3 +2,4 @@ confighttp
==========
.. doxygenfile:: confighttp.h
+ :allow-dot-graphs:
diff --git a/docs/source/source/src/crypto.rst b/docs/source/source/src/crypto.rst
index 5abf24693db..aced9d35c68 100644
--- a/docs/source/source/src/crypto.rst
+++ b/docs/source/source/src/crypto.rst
@@ -2,3 +2,4 @@ crypto
======
.. doxygenfile:: crypto.h
+ :allow-dot-graphs:
diff --git a/docs/source/source/src/httpcommon.rst b/docs/source/source/src/httpcommon.rst
index 8afbdeb215f..67557f86860 100644
--- a/docs/source/source/src/httpcommon.rst
+++ b/docs/source/source/src/httpcommon.rst
@@ -2,3 +2,4 @@ httpcommon
==========
.. doxygenfile:: httpcommon.h
+ :allow-dot-graphs:
diff --git a/docs/source/source/src/input.rst b/docs/source/source/src/input.rst
index e1988b2406f..4c8db082dc6 100644
--- a/docs/source/source/src/input.rst
+++ b/docs/source/source/src/input.rst
@@ -2,3 +2,4 @@ input
=====
.. doxygenfile:: input.h
+ :allow-dot-graphs:
diff --git a/docs/source/source/src/main.rst b/docs/source/source/src/main.rst
index cd6716faa5c..5102a3e9448 100644
--- a/docs/source/source/src/main.rst
+++ b/docs/source/source/src/main.rst
@@ -2,3 +2,4 @@ main
====
.. doxygenfile:: main.h
+ :allow-dot-graphs:
diff --git a/docs/source/source/src/move_by_copy.rst b/docs/source/source/src/move_by_copy.rst
index 034c3aa240a..5ec37716a9a 100644
--- a/docs/source/source/src/move_by_copy.rst
+++ b/docs/source/source/src/move_by_copy.rst
@@ -2,3 +2,4 @@ move_by_copy
============
.. doxygenfile:: move_by_copy.h
+ :allow-dot-graphs:
diff --git a/docs/source/source/src/network.rst b/docs/source/source/src/network.rst
index 00df0e16a5f..a9121e16008 100644
--- a/docs/source/source/src/network.rst
+++ b/docs/source/source/src/network.rst
@@ -2,3 +2,4 @@ network
=======
.. doxygenfile:: network.h
+ :allow-dot-graphs:
diff --git a/docs/source/source/src/nvhttp.rst b/docs/source/source/src/nvhttp.rst
index ea4daa01507..8a3bbd008a5 100644
--- a/docs/source/source/src/nvhttp.rst
+++ b/docs/source/source/src/nvhttp.rst
@@ -2,3 +2,4 @@ nvhttp
======
.. doxygenfile:: nvhttp.h
+ :allow-dot-graphs:
diff --git a/docs/source/source/src/platform/linux/cuda.rst b/docs/source/source/src/platform/linux/cuda.rst
index cb975aa918d..5b6dffe770b 100644
--- a/docs/source/source/src/platform/linux/cuda.rst
+++ b/docs/source/source/src/platform/linux/cuda.rst
@@ -2,3 +2,4 @@ cuda
====
.. doxygenfile:: platform/linux/cuda.h
+ :allow-dot-graphs:
diff --git a/docs/source/source/src/platform/linux/vaapi.rst b/docs/source/source/src/platform/linux/vaapi.rst
index 8880e079eeb..973e78523b4 100644
--- a/docs/source/source/src/platform/linux/vaapi.rst
+++ b/docs/source/source/src/platform/linux/vaapi.rst
@@ -2,3 +2,4 @@ vaapi
=====
.. doxygenfile:: platform/linux/vaapi.h
+ :allow-dot-graphs:
diff --git a/docs/source/source/src/platform/linux/wayland.rst b/docs/source/source/src/platform/linux/wayland.rst
index 72f74fe2b83..670e4340ab0 100644
--- a/docs/source/source/src/platform/linux/wayland.rst
+++ b/docs/source/source/src/platform/linux/wayland.rst
@@ -2,3 +2,4 @@ wayland
=======
.. doxygenfile:: platform/linux/wayland.h
+ :allow-dot-graphs:
diff --git a/docs/source/source/src/platform/macos/misc.rst b/docs/source/source/src/platform/macos/misc.rst
index f000da0813f..26cbc1874c6 100644
--- a/docs/source/source/src/platform/macos/misc.rst
+++ b/docs/source/source/src/platform/macos/misc.rst
@@ -2,3 +2,4 @@ misc
====
.. doxygenfile:: platform/macos/misc.h
+ :allow-dot-graphs:
diff --git a/docs/source/source/src/platform/windows/misc.rst b/docs/source/source/src/platform/windows/misc.rst
index cca4b82fdd4..88c1620002a 100644
--- a/docs/source/source/src/platform/windows/misc.rst
+++ b/docs/source/source/src/platform/windows/misc.rst
@@ -2,3 +2,4 @@ misc
====
.. doxygenfile:: platform/windows/misc.h
+ :allow-dot-graphs:
diff --git a/docs/source/source/src/process.rst b/docs/source/source/src/process.rst
index fd4110a09a0..ad8f9764114 100644
--- a/docs/source/source/src/process.rst
+++ b/docs/source/source/src/process.rst
@@ -2,3 +2,4 @@ process
=======
.. doxygenfile:: process.h
+ :allow-dot-graphs:
diff --git a/docs/source/source/src/round_robin.rst b/docs/source/source/src/round_robin.rst
index 2a838b88f50..89aea6f5904 100644
--- a/docs/source/source/src/round_robin.rst
+++ b/docs/source/source/src/round_robin.rst
@@ -2,3 +2,4 @@ round_robin
===========
.. doxygenfile:: round_robin.h
+ :allow-dot-graphs:
diff --git a/docs/source/source/src/rtsp.rst b/docs/source/source/src/rtsp.rst
index 419f7d7fe2c..7aee0baf056 100644
--- a/docs/source/source/src/rtsp.rst
+++ b/docs/source/source/src/rtsp.rst
@@ -2,3 +2,4 @@ rtsp
====
.. doxygenfile:: rtsp.h
+ :allow-dot-graphs:
diff --git a/docs/source/source/src/stream.rst b/docs/source/source/src/stream.rst
index b9bcc4b4675..c771414016a 100644
--- a/docs/source/source/src/stream.rst
+++ b/docs/source/source/src/stream.rst
@@ -2,3 +2,4 @@ stream
======
.. doxygenfile:: stream.h
+ :allow-dot-graphs:
diff --git a/docs/source/source/src/sync.rst b/docs/source/source/src/sync.rst
index 43cea778eca..6f3d95929e2 100644
--- a/docs/source/source/src/sync.rst
+++ b/docs/source/source/src/sync.rst
@@ -2,3 +2,4 @@ sync
====
.. doxygenfile:: sync.h
+ :allow-dot-graphs:
diff --git a/docs/source/source/src/system_tray.rst b/docs/source/source/src/system_tray.rst
index 3b69c246977..7c1009e1c8c 100644
--- a/docs/source/source/src/system_tray.rst
+++ b/docs/source/source/src/system_tray.rst
@@ -2,3 +2,4 @@ system_tray
===========
.. doxygenfile:: system_tray.h
+ :allow-dot-graphs:
diff --git a/docs/source/source/src/task_pool.rst b/docs/source/source/src/task_pool.rst
index 8c37231452d..cad02fa7265 100644
--- a/docs/source/source/src/task_pool.rst
+++ b/docs/source/source/src/task_pool.rst
@@ -2,3 +2,4 @@ tasl_pool
=========
.. doxygenfile:: task_pool.h
+ :allow-dot-graphs:
diff --git a/docs/source/source/src/thread_pool.rst b/docs/source/source/src/thread_pool.rst
index 3d563bd3486..32787512811 100644
--- a/docs/source/source/src/thread_pool.rst
+++ b/docs/source/source/src/thread_pool.rst
@@ -2,3 +2,4 @@ thread_pool
===========
.. doxygenfile:: thread_pool.h
+ :allow-dot-graphs:
diff --git a/docs/source/source/src/thread_safe.rst b/docs/source/source/src/thread_safe.rst
index 00f394c4df3..d7ecdda3f43 100644
--- a/docs/source/source/src/thread_safe.rst
+++ b/docs/source/source/src/thread_safe.rst
@@ -2,3 +2,4 @@ thread_safe
===========
.. doxygenfile:: thread_safe.h
+ :allow-dot-graphs:
diff --git a/docs/source/source/src/upnp.rst b/docs/source/source/src/upnp.rst
index b38e8d63dc0..987692353b7 100644
--- a/docs/source/source/src/upnp.rst
+++ b/docs/source/source/src/upnp.rst
@@ -2,3 +2,4 @@ upnp
====
.. doxygenfile:: upnp.h
+ :allow-dot-graphs:
diff --git a/docs/source/source/src/uuid.rst b/docs/source/source/src/uuid.rst
index 803f0e1c904..d21e0a6ceaf 100644
--- a/docs/source/source/src/uuid.rst
+++ b/docs/source/source/src/uuid.rst
@@ -2,3 +2,4 @@ uuid
====
.. doxygenfile:: uuid.h
+ :allow-dot-graphs:
diff --git a/docs/source/source/src/video.rst b/docs/source/source/src/video.rst
index 17bca35e8ba..2e45883f293 100644
--- a/docs/source/source/src/video.rst
+++ b/docs/source/source/src/video.rst
@@ -2,3 +2,4 @@ video
=====
.. doxygenfile:: video.h
+ :allow-dot-graphs:
From 18ab7dcf6cde47120804181682a1f617199317c9 Mon Sep 17 00:00:00 2001
From: Cameron Gutman
Date: Thu, 27 Apr 2023 19:20:10 -0500
Subject: [PATCH 043/144] Remove 3-byte NALU prefix fixup
The bug in Moonlight that required it has been fixed since the middle of last year.
---
src/video.cpp | 26 --------------------------
1 file changed, 26 deletions(-)
diff --git a/src/video.cpp b/src/video.cpp
index 41ddab7e11a..93c5633d1b1 100644
--- a/src/video.cpp
+++ b/src/video.cpp
@@ -27,9 +27,6 @@ extern "C" {
using namespace std::literals;
namespace video {
- constexpr auto hevc_nalu = "\000\000\000\001("sv;
- constexpr auto h264_nalu = "\000\000\000\001e"sv;
-
void
free_ctx(AVCodecContext *ctx) {
avcodec_free_context(&ctx);
@@ -280,7 +277,6 @@ namespace video {
CBR, // Some encoders don't support CBR, if not supported --> attempt constant quantatication parameter instead
DYNAMIC_RANGE, // hdr
VUI_PARAMETERS, // AMD encoder with VAAPI doesn't add VUI parameters to SPS
- NALU_PREFIX_5b, // libx264/libx265 have a 3-byte nalu prefix instead of 4-byte nalu prefix
MAX_FLAGS
};
@@ -295,7 +291,6 @@ namespace video {
_CONVERT(CBR);
_CONVERT(DYNAMIC_RANGE);
_CONVERT(VUI_PARAMETERS);
- _CONVERT(NALU_PREFIX_5b);
_CONVERT(MAX_FLAGS);
}
#undef _CONVERT
@@ -1359,12 +1354,6 @@ namespace video {
(1 - (int) video_format[encoder_t::VUI_PARAMETERS]) * (1 + config.videoFormat),
};
- if (!video_format[encoder_t::NALU_PREFIX_5b]) {
- auto nalu_prefix = config.videoFormat ? hevc_nalu : h264_nalu;
-
- session.replacements.emplace_back(nalu_prefix.substr(1), nalu_prefix);
- }
-
return std::make_optional(std::move(session));
}
@@ -1790,7 +1779,6 @@ namespace video {
enum validate_flag_e {
VUI_PARAMS = 0x01,
- NALU_PREFIX_5b = 0x02,
};
int
@@ -1843,12 +1831,6 @@ namespace video {
flag |= VUI_PARAMS;
}
- auto nalu_prefix = config.videoFormat ? hevc_nalu : h264_nalu;
- std::string_view payload { (char *) av_packet->data, (std::size_t) av_packet->size };
- if (std::search(std::begin(payload), std::end(payload), std::begin(nalu_prefix), std::end(nalu_prefix)) != std::end(payload)) {
- flag |= NALU_PREFIX_5b;
- }
-
return flag;
}
@@ -1892,7 +1874,6 @@ namespace video {
std::vector> packet_deficiencies {
{ VUI_PARAMS, encoder_t::VUI_PARAMETERS },
- { NALU_PREFIX_5b, encoder_t::NALU_PREFIX_5b },
};
for (auto [validate_flag, encoder_flag] : packet_deficiencies) {
@@ -1960,13 +1941,6 @@ namespace video {
BOOST_LOG(warning) << encoder.name << ": hevc missing sps->vui parameters"sv;
}
- if (!encoder.h264[encoder_t::NALU_PREFIX_5b]) {
- BOOST_LOG(warning) << encoder.name << ": h264: replacing nalu prefix data"sv;
- }
- if (encoder.hevc[encoder_t::PASSED] && !encoder.hevc[encoder_t::NALU_PREFIX_5b]) {
- BOOST_LOG(warning) << encoder.name << ": hevc: replacing nalu prefix data"sv;
- }
-
fg.disable();
return true;
}
From 430a43969892098f40e18d378c9e9a30fed11e0f Mon Sep 17 00:00:00 2001
From: Chase Payne
Date: Sat, 29 Apr 2023 00:22:01 -0500
Subject: [PATCH 044/144] Elevated Commands Redesign (#1123)
---
CMakeLists.txt | 40 +-
docs/source/about/app_examples.rst | 29 ++
src/config.cpp | 10 +-
src/config.h | 10 +-
src/httpcommon.cpp | 2 +-
src/platform/common.h | 2 +-
src/platform/linux/misc.cpp | 3 +-
src/platform/macos/misc.mm | 3 +-
src/platform/windows/misc.cpp | 455 ++++++++++++++---------
src/platform/windows/misc.h | 1 -
src/process.cpp | 52 +--
src/process.h | 1 +
src/system_tray.cpp | 2 +-
src_assets/common/assets/web/apps.html | 202 +++++++---
src_assets/common/assets/web/config.html | 73 ++--
tools/CMakeLists.txt | 7 -
tools/elevator.cpp | 71 ----
17 files changed, 553 insertions(+), 410 deletions(-)
delete mode 100644 tools/elevator.cpp
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 53d809c598e..30ec64c8ff4 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -639,6 +639,8 @@ if(WIN32)
set_target_properties(sunshine PROPERTIES LINK_SEARCH_START_STATIC 1)
set(CMAKE_FIND_LIBRARY_SUFFIXES ".dll")
find_library(ZLIB ZLIB1)
+ list(APPEND SUNSHINE_EXTERNAL_LIBRARIES
+ Wtsapi32.lib)
endif()
target_link_libraries(sunshine ${SUNSHINE_EXTERNAL_LIBRARIES} ${EXTRA_LIBS})
@@ -694,7 +696,6 @@ if(WIN32) # see options at: https://cmake.org/cmake/help/latest/cpack_gen/nsis.h
install(TARGETS dxgi-info RUNTIME DESTINATION "tools" COMPONENT dxgi)
install(TARGETS audio-info RUNTIME DESTINATION "tools" COMPONENT audio)
install(TARGETS sunshinesvc RUNTIME DESTINATION "tools" COMPONENT sunshinesvc)
- install(TARGETS elevator RUNTIME DESTINATION "tools" COMPONENT elevator)
# Mandatory tools
install(TARGETS ddprobe RUNTIME DESTINATION "tools" COMPONENT application)
@@ -730,15 +731,16 @@ if(WIN32) # see options at: https://cmake.org/cmake/help/latest/cpack_gen/nsis.h
# Extra install commands
# Restores permissions on the install directory
# Migrates config files from the root into the new config folder
- # Sets permissions on the config folder so that we can write in it
# Install service
SET(CPACK_NSIS_EXTRA_INSTALL_COMMANDS
"${CPACK_NSIS_EXTRA_INSTALL_COMMANDS}
IfSilent +2 0
ExecShell 'open' 'https://sunshinestream.readthedocs.io/'
- nsExec::ExecToLog 'icacls \\\"$INSTDIR\\\" /reset'
+ nsExec::ExecToLog 'icacls \\\"$INSTDIR\\\" /reset /T'
+ nsExec::ExecToLog 'icacls \\\"$INSTDIR\\\\config\\\\credentials\\\" /inheritance:r'
+ nsExec::ExecToLog 'icacls \\\"$INSTDIR\\\\config\\\\credentials\\\" \
+ /grant:r Administrators:\\\(OI\\\)\\\(CI\\\)\\\(F\\\)'
nsExec::ExecToLog '\\\"$INSTDIR\\\\scripts\\\\migrate-config.bat\\\"'
- nsExec::ExecToLog 'icacls \\\"$INSTDIR\\\\config\\\" /grant:r Users:\\\(OI\\\)\\\(CI\\\)\\\(F\\\)'
nsExec::ExecToLog '\\\"$INSTDIR\\\\scripts\\\\add-firewall-rule.bat\\\"'
nsExec::ExecToLog '\\\"$INSTDIR\\\\scripts\\\\install-service.bat\\\"'
nsExec::ExecToLog '\\\"$INSTDIR\\\\scripts\\\\install-vigembus.bat\\\"'
@@ -763,21 +765,6 @@ if(WIN32) # see options at: https://cmake.org/cmake/help/latest/cpack_gen/nsis.h
NoDelete:
")
- # Adding an option for the start menu and PATH
- # TODO: it asks to add it to the PATH but is not working https://gitlab.kitware.com/cmake/cmake/-/issues/15635
- set(CPACK_NSIS_MODIFY_PATH "OFF")
- set(CPACK_NSIS_EXECUTABLES_DIRECTORY ".")
- # This will be shown on the installed apps Windows settings
- set(CPACK_NSIS_INSTALLED_ICON_NAME "${CMAKE_PROJECT_NAME}.exe")
- set(CPACK_NSIS_CREATE_ICONS_EXTRA
- "${CPACK_NSIS_CREATE_ICONS_EXTRA}
- CreateShortCut '\$SMPROGRAMS\\\\$STARTMENU_FOLDER\\\\${CMAKE_PROJECT_NAME} (Foreground Mode).lnk' \
- '\$INSTDIR\\\\${CMAKE_PROJECT_NAME}.exe'
- ")
- set(CPACK_NSIS_DELETE_ICONS_EXTRA
- "${CPACK_NSIS_DELETE_ICONS_EXTRA}
- Delete '\$SMPROGRAMS\\\\$MUI_TEMP\\\\${CMAKE_PROJECT_NAME} (Foreground Mode).lnk'
- ")
# Checking for previous installed versions
set(CPACK_NSIS_ENABLE_UNINSTALL_BEFORE_INSTALL "ON")
@@ -811,12 +798,6 @@ if(WIN32) # see options at: https://cmake.org/cmake/help/latest/cpack_gen/nsis.h
set(CPACK_COMPONENT_AUDIO_DESCRIPTION "CLI tool providing information about sound devices.")
set(CPACK_COMPONENT_AUDIO_GROUP "tools")
- # elevation tool
- set(CPACK_COMPONENT_ELEVATOR_DISPLAY_NAME "elevator")
- set(CPACK_COMPONENT_ELEVATOR_DESCRIPTION "CLI tool that assists with elevating \
- commands when permissions have been denied.")
- set(CPACK_COMPONENT_ELEVATOR_GROUP "tools")
-
# display tool
set(CPACK_COMPONENT_DXGI_DISPLAY_NAME "dxgi-info")
set(CPACK_COMPONENT_DXGI_DESCRIPTION "CLI tool providing information about graphics cards and displays.")
@@ -824,14 +805,19 @@ if(WIN32) # see options at: https://cmake.org/cmake/help/latest/cpack_gen/nsis.h
# service
set(CPACK_COMPONENT_SUNSHINESVC_DISPLAY_NAME "sunshinesvc")
- set(CPACK_COMPONENT_SUNSHINESVC_DESCRIPTION "CLI tool providing ability to enable/disable the Sunshine service.")
+ set(CPACK_COMPONENT_SUNSHINESVC_DESCRIPTION "Installs sunshine as a service as SYSTEM, \
+ so that it can allow usage before logon and other benefits.")
set(CPACK_COMPONENT_SUNSHINESVC_GROUP "tools")
+ # This is required because we've changed the ACL for config to require admin to protect against EoP exploits.
+ set(CPACK_COMPONENT_SUNSHINESVC_REQUIRED true)
# service scripts
set(CPACK_COMPONENT_SERVICE_DISPLAY_NAME "service-scripts")
- set(CPACK_COMPONENT_SERVICE_DESCRIPTION "Scripts to enable/disable the service.")
+ set(CPACK_COMPONENT_SERVICE_DESCRIPTION "Scripts for installing and enabling the service.")
set(CPACK_COMPONENT_SERVICE_GROUP "scripts")
set(CPACK_COMPONENT_SERVICE_DEPENDS sunshinesvc)
+ # This is required because we've changed the ACL for config to require admin to protect against EoP exploits.
+ set(CPACK_COMPONENT_SERVICE_REQUIRED true)
# firewall scripts
set(CPACK_COMPONENT_FIREWALL_DISPLAY_NAME "firewall-scripts")
diff --git a/docs/source/about/app_examples.rst b/docs/source/about/app_examples.rst
index dbeac1ac91d..0e39029797b 100644
--- a/docs/source/about/app_examples.rst
+++ b/docs/source/about/app_examples.rst
@@ -187,3 +187,32 @@ Changing Resolution and Refresh Rate (Windows)
.. Tip:: You can change your host resolution to match the client resolution automatically using the
`Nonary/ResolutionAutomation `_ project.
+
+
+Elevating Commands (Windows)
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+If you've installed Sunshine as a service (default), you can now specify if a command should be elevated with adminsitrative privileges.
+Simply enable the elevated option in the WEB UI, or add it to the JSON configuration.
+This is an option for both prep-cmd and regular commands and will launch the process with the current user without a UAC prompt.
+
+.. Note:: It's important to write the values "true" and "false" as string values, not as the typical true/false values in most JSON.
+
+**Example**
+ .. code-block:: json
+
+ {
+ "name": "Game With AntiCheat that Requires Admin",
+ "output": "",
+ "cmd": "ping 127.0.0.1",
+ "exclude-global-prep-cmd": "false",
+ "elevated": "true",
+ "prep-cmd": [
+ {
+ "do": "powershell.exe -command \"Start-Streaming\"",
+ "undo": "powershell.exe -command \"Stop-Streaming\"",
+ "elevated": "false"
+ }
+ ],
+ "image-path": ""
+ }
diff --git a/src/config.cpp b/src/config.cpp
index 1e4276dfe30..60961cd6e45 100644
--- a/src/config.cpp
+++ b/src/config.cpp
@@ -816,12 +816,14 @@ namespace config {
boost::property_tree::read_json(jsonStream, jsonTree);
for (auto &[_, prep_cmd] : jsonTree.get_child("prep_cmd"s)) {
- auto do_cmd = prep_cmd.get("do"s);
- auto undo_cmd = prep_cmd.get("undo"s);
+ auto do_cmd = prep_cmd.get_optional("do"s);
+ auto undo_cmd = prep_cmd.get_optional("undo"s);
+ auto elevated = prep_cmd.get_optional("elevated"s);
input.emplace_back(
- std::move(do_cmd),
- std::move(undo_cmd));
+ std::move(do_cmd.value_or("")),
+ std::move(undo_cmd.value_or("")),
+ std::move(elevated.value_or(false)));
}
}
diff --git a/src/config.h b/src/config.h
index fc77748bdde..90b3fbee2ca 100644
--- a/src/config.h
+++ b/src/config.h
@@ -119,14 +119,14 @@ namespace config {
}
struct prep_cmd_t {
- prep_cmd_t(std::string &&do_cmd, std::string &&undo_cmd):
- do_cmd(std::move(do_cmd)), undo_cmd(std::move(undo_cmd)) {}
- explicit prep_cmd_t(std::string &&do_cmd):
- do_cmd(std::move(do_cmd)) {}
+ 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)) {}
+ 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;
};
-
struct sunshine_t {
int min_log_level;
std::bitset flags;
diff --git a/src/httpcommon.cpp b/src/httpcommon.cpp
index 24810f51663..31054a05f08 100644
--- a/src/httpcommon.cpp
+++ b/src/httpcommon.cpp
@@ -89,7 +89,7 @@ namespace http {
pt::write_json(file, outputTree);
}
catch (std::exception &e) {
- BOOST_LOG(error) << "generating user credentials: "sv << e.what();
+ BOOST_LOG(error) << "error writing to the credentials file, perhaps try this again as an administrator? Details: "sv << e.what();
return -1;
}
diff --git a/src/platform/common.h b/src/platform/common.h
index fa8104dcf64..6b6f3f1e9c9 100644
--- a/src/platform/common.h
+++ b/src/platform/common.h
@@ -382,7 +382,7 @@ namespace platf {
display_names(mem_type_e hwdevice_type);
boost::process::child
- run_unprivileged(const std::string &cmd, boost::filesystem::path &working_dir, boost::process::environment &env, FILE *file, std::error_code &ec, boost::process::group *group);
+ run_command(bool elevated, const std::string &cmd, boost::filesystem::path &working_dir, boost::process::environment &env, FILE *file, std::error_code &ec, boost::process::group *group);
enum class thread_priority_e : int {
low,
diff --git a/src/platform/linux/misc.cpp b/src/platform/linux/misc.cpp
index ba41e625d44..631792777f2 100644
--- a/src/platform/linux/misc.cpp
+++ b/src/platform/linux/misc.cpp
@@ -159,8 +159,7 @@ namespace platf {
}
bp::child
- run_unprivileged(const std::string &cmd, boost::filesystem::path &working_dir, bp::environment &env, FILE *file, std::error_code &ec, bp::group *group) {
- BOOST_LOG(warning) << "run_unprivileged() is not yet implemented for this platform. The new process will run with Sunshine's permissions."sv;
+ run_command(bool elevated, const std::string &cmd, boost::filesystem::path &working_dir, bp::environment &env, FILE *file, std::error_code &ec, bp::group *group) {
if (!group) {
if (!file) {
return bp::child(cmd, env, bp::start_dir(working_dir), bp::std_out > bp::null, bp::std_err > bp::null, ec);
diff --git a/src/platform/macos/misc.mm b/src/platform/macos/misc.mm
index 3d8637297c5..2f16f4babbd 100644
--- a/src/platform/macos/misc.mm
+++ b/src/platform/macos/misc.mm
@@ -158,8 +158,7 @@
}
bp::child
- run_unprivileged(const std::string &cmd, boost::filesystem::path &working_dir, bp::environment &env, FILE *file, std::error_code &ec, bp::group *group) {
- BOOST_LOG(warning) << "run_unprivileged() is not yet implemented for this platform. The new process will run with Sunshine's permissions."sv;
+ run_command(bool elevated, const std::string &cmd, boost::filesystem::path &working_dir, bp::environment &env, FILE *file, std::error_code &ec, bp::group *group) {
if (!group) {
if (!file) {
return bp::child(cmd, env, bp::start_dir(working_dir), bp::std_out > bp::null, bp::std_err > bp::null, ec);
diff --git a/src/platform/windows/misc.cpp b/src/platform/windows/misc.cpp
index 5335cae2edd..af6b4a26486 100644
--- a/src/platform/windows/misc.cpp
+++ b/src/platform/windows/misc.cpp
@@ -19,6 +19,8 @@
#include
#include
#include
+#include
+#include
// clang-format on
#include "src/main.h"
@@ -200,101 +202,97 @@ namespace platf {
return std::string(buffer, bytes);
}
- HANDLE
- duplicate_shell_token() {
- // Get the shell window (will usually be owned by explorer.exe)
- HWND shell_window = GetShellWindow();
- if (!shell_window) {
- BOOST_LOG(error) << "No shell window found. Is explorer.exe running?"sv;
- return NULL;
- }
-
- // Open a handle to the explorer.exe process
- DWORD shell_pid;
- GetWindowThreadProcessId(shell_window, &shell_pid);
- HANDLE shell_process = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, shell_pid);
- if (!shell_process) {
- BOOST_LOG(error) << "Failed to open shell process: "sv << GetLastError();
- return NULL;
- }
-
- // Open explorer's token to clone for process creation
- HANDLE shell_token;
- BOOL ret = OpenProcessToken(shell_process, TOKEN_DUPLICATE, &shell_token);
- CloseHandle(shell_process);
- if (!ret) {
- BOOST_LOG(error) << "Failed to open shell process token: "sv << GetLastError();
- return NULL;
+ bool
+ IsUserAdmin(HANDLE user_token) {
+ WINBOOL ret;
+ SID_IDENTIFIER_AUTHORITY NtAuthority = SECURITY_NT_AUTHORITY;
+ PSID AdministratorsGroup;
+ ret = AllocateAndInitializeSid(
+ &NtAuthority,
+ 2,
+ SECURITY_BUILTIN_DOMAIN_RID,
+ DOMAIN_ALIAS_RID_ADMINS,
+ 0, 0, 0, 0, 0, 0,
+ &AdministratorsGroup);
+ if (ret) {
+ if (!CheckTokenMembership(user_token, AdministratorsGroup, &ret)) {
+ ret = false;
+ BOOST_LOG(error) << "Failed to verify token membership for administrative access: " << GetLastError();
+ }
+ FreeSid(AdministratorsGroup);
}
-
- // Duplicate the token to make it usable for process creation
- HANDLE new_token;
- ret = DuplicateTokenEx(shell_token, TOKEN_ALL_ACCESS, NULL, SecurityImpersonation, TokenPrimary, &new_token);
- CloseHandle(shell_token);
- if (!ret) {
- BOOST_LOG(error) << "Failed to duplicate shell process token: "sv << GetLastError();
- return NULL;
+ else {
+ BOOST_LOG(error) << "Unable to allocate SID to check administrative access: " << GetLastError();
}
- return new_token;
+ return ret;
}
- PTOKEN_USER
- get_token_user(HANDLE token) {
- DWORD return_length;
- if (GetTokenInformation(token, TokenUser, NULL, 0, &return_length) || GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
- auto winerr = GetLastError();
- BOOST_LOG(error) << "Failed to get token information size: "sv << winerr;
+ /**
+ * @brief A function to obtain the current sessions user's primary token with elevated privileges
+ *
+ * @return The users token, if user has admin capability it will be elevated. If not, it will return back a limited token. On error, nullptrs
+ */
+ HANDLE
+ retrieve_users_token(bool elevated) {
+ DWORD consoleSessionId;
+ HANDLE userToken;
+ TOKEN_ELEVATION_TYPE elevationType;
+ DWORD dwSize;
+
+ // Get the session ID of the active console session
+ consoleSessionId = WTSGetActiveConsoleSessionId();
+ if (0xFFFFFFFF == consoleSessionId) {
+ // If there is no active console session, log a warning and return null
+ BOOST_LOG(warning) << "There isn't an active user session, therefore it is not possible to execute commands under the users profile.";
return nullptr;
}
- auto user = (PTOKEN_USER) HeapAlloc(GetProcessHeap(), 0, return_length);
- if (!user) {
+ // Get the user token for the active console session
+ if (!WTSQueryUserToken(consoleSessionId, &userToken)) {
+ BOOST_LOG(debug) << "QueryUserToken failed, this would prevent commands from launching under the users profile.";
return nullptr;
}
- if (!GetTokenInformation(token, TokenUser, user, return_length, &return_length)) {
- auto winerr = GetLastError();
- BOOST_LOG(error) << "Failed to get token information: "sv << winerr;
- HeapFree(GetProcessHeap(), 0, user);
+ // We need to know if this is an elevated token or not.
+ // Get the elevation type of the user token
+ // Elevation - Default: User is not an admin, UAC enabled/disabled does not matter.
+ // Elevation - Limited: User is an admin, has UAC enabled.
+ // Elevation - Full: User is an admin, has UAC disabled.
+ if (!GetTokenInformation(userToken, TokenElevationType, &elevationType, sizeof(TOKEN_ELEVATION_TYPE), &dwSize)) {
+ BOOST_LOG(debug) << "Retrieving token information failed: " << GetLastError();
+ CloseHandle(userToken);
return nullptr;
}
- return user;
- }
-
- void
- free_token_user(PTOKEN_USER user) {
- HeapFree(GetProcessHeap(), 0, user);
- }
-
- bool
- is_token_same_user_as_process(HANDLE other_token) {
- HANDLE process_token;
- if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &process_token)) {
- auto winerr = GetLastError();
- BOOST_LOG(error) << "Failed to open process token: "sv << winerr;
- return false;
+ // User is currently not an administrator
+ // The documentation for this scenario is conflicting, so we'll double check to see if user is actually an admin.
+ if (elevated && (elevationType == TokenElevationTypeDefault && !IsUserAdmin(userToken))) {
+ // We don't have to strip the token or do anything here, but let's give the user a warning so they're aware what is happening.
+ BOOST_LOG(warning) << "This command requires elevation and the current user account logged in does not have administrator rights. "
+ << "For security reasons Sunshine will retain the same access level as the current user and will not elevate it.";
}
- auto process_user = get_token_user(process_token);
- CloseHandle(process_token);
- if (!process_user) {
- return false;
- }
-
- auto token_user = get_token_user(other_token);
- if (!token_user) {
- free_token_user(process_user);
- return false;
- }
+ // User has a limited token, this means they have UAC enabled and is an Administrator
+ if (elevated && elevationType == TokenElevationTypeLimited) {
+ TOKEN_LINKED_TOKEN linkedToken;
+ // Retrieve the administrator token that is linked to the limited token
+ if (!GetTokenInformation(userToken, TokenLinkedToken, reinterpret_cast(&linkedToken), sizeof(TOKEN_LINKED_TOKEN), &dwSize)) {
+ // If the retrieval failed, log an error message and return null
+ BOOST_LOG(error) << "Retrieving linked token information failed: " << GetLastError();
+ CloseHandle(userToken);
- bool ret = EqualSid(process_user->User.Sid, token_user->User.Sid);
+ // There is no scenario where this should be hit, except for an actual error.
+ return nullptr;
+ }
- free_token_user(process_user);
- free_token_user(token_user);
+ // Since we need the elevated token, we'll replace it with their administrative token.
+ CloseHandle(userToken);
+ userToken = linkedToken.LinkedToken;
+ }
- return ret;
+ // We don't need to do anything for TokenElevationTypeFull users here, because they're already elevated.
+ return userToken;
}
bool
@@ -334,6 +332,42 @@ namespace platf {
return true;
}
+ /**
+
+@brief Check if the current process is running with system-level privileges.
+@return true if the current process has system-level privileges, false otherwise.
+*/
+ bool
+ is_running_as_system() {
+ BOOL ret;
+ PSID SystemSid;
+ DWORD dwSize = SECURITY_MAX_SID_SIZE;
+
+ // Allocate memory for the SID structure
+ SystemSid = LocalAlloc(LMEM_FIXED, dwSize);
+ if (SystemSid == nullptr) {
+ BOOST_LOG(error) << "Failed to allocate memory for the SID structure: " << GetLastError();
+ return false;
+ }
+
+ // Create a SID for the local system account
+ ret = CreateWellKnownSid(WinLocalSystemSid, nullptr, SystemSid, &dwSize);
+ if (ret) {
+ // Check if the current process token contains this SID
+ if (!CheckTokenMembership(nullptr, SystemSid, &ret)) {
+ BOOST_LOG(error) << "Failed to check token membership: " << GetLastError();
+ ret = false;
+ }
+ }
+ else {
+ BOOST_LOG(error) << "Failed to create a SID for the local system account. This may happen if the system is out of memory or if the SID buffer is too small: " << GetLastError();
+ }
+
+ // Free the memory allocated for the SID structure
+ LocalFree(SystemSid);
+ return ret;
+ }
+
// Note: This does NOT append a null terminator
void
append_string_to_environment_block(wchar_t *env_block, int &offset, const std::wstring &wstr) {
@@ -395,46 +429,114 @@ namespace platf {
HeapFree(GetProcessHeap(), 0, list);
}
+ /**
+ * @brief Creates a bp::child object from the results of launching a process
+ *
+ * @param process_launched A boolean indicating whether the launch was successful or not
+ * @param cmd The command that was used to launch the process
+ * @param ec A reference to an std::error_code object that will store any error that occurred during the launch
+ * @param process_info A reference to a PROCESS_INFORMATION structure that contains information about the new process
+ * @param group A pointer to a bp::group object that will add the new process to its group, if not null
+ * @return A bp::child object representing the new process, or an empty bp::child object if the launch failed or an error occurred
+ */
bp::child
- run_unprivileged(const std::string &cmd, boost::filesystem::path &working_dir, bp::environment &env, FILE *file, std::error_code &ec, bp::group *group) {
- HANDLE shell_token = duplicate_shell_token();
- if (!shell_token) {
- // This can happen if the shell has crashed. Fail the launch rather than risking launching with
- // Sunshine's permissions unmodified.
- ec = std::make_error_code(std::errc::no_such_process);
+ create_boost_child_from_results(bool process_launched, const std::string &cmd, std::error_code &ec, PROCESS_INFORMATION &process_info, bp::group *group) {
+ // Use RAII to ensure the process is closed when we're done with it, even if there was an error.
+ auto close_process_handles = util::fail_guard([process_launched, process_info]() {
+ if (process_launched) {
+ CloseHandle(process_info.hThread);
+ CloseHandle(process_info.hProcess);
+ }
+ });
+
+ if (ec) {
+ // If there was an error, return an empty bp::child object
return bp::child();
}
- auto token_close = util::fail_guard([shell_token]() {
- CloseHandle(shell_token);
- });
+ if (process_launched) {
+ // If the launch was successful, create a new bp::child object representing the new process
+ auto child = bp::child((bp::pid_t) process_info.dwProcessId);
+ if (group) {
+ // If a group was provided, add the new process to the group
+ group->add(child);
+ }
- // Populate env with user-specific environment variables
- if (!merge_user_environment_block(env, shell_token)) {
- ec = std::make_error_code(std::errc::not_enough_memory);
+ BOOST_LOG(info) << cmd << " running with PID "sv << child.id();
+ return child;
+ }
+ else {
+ auto winerror = GetLastError();
+ BOOST_LOG(error) << "Failed to launch process: "sv << winerror;
+ ec = std::make_error_code(std::errc::invalid_argument);
+ // We must NOT attach the failed process here, since this case can potentially be induced by ACL
+ // manipulation (denying yourself execute permission) to cause an escalation of privilege.
+ // So to protect ourselves against that, we'll return an empty child process instead.
return bp::child();
}
+ }
- // Most Win32 APIs can't consume UTF-8 strings directly, so we must convert them into UTF-16
- std::wstring wcmd = utf8_to_wide_string(cmd);
- std::wstring env_block = create_environment_block(env);
- std::wstring start_dir = utf8_to_wide_string(working_dir.string());
+ /**
+ * @brief Impersonate the current user, invoke the callback function, then returns back to system context.
+ *
+ * @param user_token A handle to the user's token that was obtained from the shell
+ * @param callback A function that will be executed while impersonating the user
+ * @return An std::error_code object that will store any error that occurred during the impersonation
+ */
+ std::error_code
+ impersonate_current_user(HANDLE user_token, std::function callback) {
+ std::error_code ec;
+ // Impersonate the user when launching the process. This will ensure that appropriate access
+ // checks are done against the user token, not our SYSTEM token. It will also allow network
+ // shares and mapped network drives to be used as launch targets, since those credentials
+ // are stored per-user.
+ if (!ImpersonateLoggedOnUser(user_token)) {
+ auto winerror = GetLastError();
+ // Log the failure of impersonating the user and its error code
+ BOOST_LOG(error) << "Failed to impersonate user: "sv << winerror;
+ ec = std::make_error_code(std::errc::permission_denied);
+ return ec;
+ }
+ // Execute the callback function while impersonating the user
+ callback();
+
+ // End impersonation of the logged on user. If this fails (which is extremely unlikely),
+ // we will be running with an unknown user token. The only safe thing to do in that case
+ // is terminate ourselves.
+ if (!RevertToSelf()) {
+ auto winerror = GetLastError();
+ // Log the failure of reverting to self and its error code
+ BOOST_LOG(fatal) << "Failed to revert to self after impersonation: "sv << winerror;
+ std::abort();
+ }
+
+ return ec;
+ }
+
+ /**
+ * @brief A function to create a STARTUPINFOEXW structure for launching a process
+ *
+ * @param file A pointer to a FILE object that will be used as the standard output and error for the new process, or null if not needed
+ * @param ec A reference to an std::error_code object that will store any error that occurred during the creation of the structure
+ * @return A STARTUPINFOEXW structure that contains information about how to launch the new process
+ */
+ STARTUPINFOEXW
+ create_startup_info(FILE *file, std::error_code &ec) {
+ // Initialize a zeroed-out STARTUPINFOEXW structure and set its size
STARTUPINFOEXW startup_info = {};
startup_info.StartupInfo.cb = sizeof(startup_info);
// Allocate a process attribute list with space for 1 element
startup_info.lpAttributeList = allocate_proc_thread_attr_list(1);
if (startup_info.lpAttributeList == NULL) {
+ // If the allocation failed, set ec to an appropriate error code and return the structure
ec = std::make_error_code(std::errc::not_enough_memory);
- return bp::child();
+ return startup_info;
}
- auto attr_list_free = util::fail_guard([list = startup_info.lpAttributeList]() {
- free_proc_thread_attr_list(list);
- });
-
if (file) {
+ // If a file was provided, get its handle and use it as the standard output and error for the new process
HANDLE log_file_handle = (HANDLE) _get_osfhandle(_fileno(file));
// Populate std handles if the caller gave us a log file to use
@@ -454,74 +556,84 @@ namespace platf {
NULL);
}
- // If we're running with the same user account as the shell, just use CreateProcess().
- // This will launch the child process elevated if Sunshine is elevated.
- PROCESS_INFORMATION process_info;
+ return startup_info;
+ }
+
+ /**
+ * @brief Runs a command on the users profile
+ *
+ * This function launches a child process as the user, using the current user's environment
+ * and a specific working directory. If the launch is successful, a `bp::child` object representing the new
+ * process is returned. Otherwise, an error code is returned.
+ *
+ * @param elevated Specify to elevate the process or not
+ * @param cmd The command to run
+ * @param working_dir The working directory for the new process
+ * @param env The environment variables to use for the new process
+ * @param file A file object to redirect the child process's output to (may be nullptr)
+ * @param ec An error code, set to indicate any errors that occur during the launch process
+ * @param group A pointer to a `bp::group` object to which the new process should belong (may be nullptr)
+ *
+ * @return A `bp::child` object representing the new process, or an empty `bp::child` object if the launch fails
+ */
+ bp::child
+ run_command(bool elevated, const std::string &cmd, boost::filesystem::path &working_dir, bp::environment &env, FILE *file, std::error_code &ec, bp::group *group) {
BOOL ret;
- if (!is_token_same_user_as_process(shell_token)) {
- // Impersonate the user when launching the process. This will ensure that appropriate access
- // checks are done against the user token, not our SYSTEM token. It will also allow network
- // shares and mapped network drives to be used as launch targets, since those credentials
- // are stored per-user.
- if (!ImpersonateLoggedOnUser(shell_token)) {
- auto winerror = GetLastError();
- BOOST_LOG(error) << "Failed to impersonate user: "sv << winerror;
- ec = std::make_error_code(std::errc::permission_denied);
+ // Convert cmd, env, and working_dir to the appropriate character sets for Win32 APIs
+ std::wstring wcmd = utf8_to_wide_string(cmd);
+ std::wstring env_block = create_environment_block(env);
+ std::wstring start_dir = utf8_to_wide_string(working_dir.string());
+
+ STARTUPINFOEXW startup_info = create_startup_info(file, ec);
+ PROCESS_INFORMATION process_info;
+
+ if (ec) {
+ // In the event that startup_info failed, return a blank child process.
+ return bp::child();
+ }
+
+ // Use RAII to ensure the attribute list is freed when we're done with it
+ auto attr_list_free = util::fail_guard([list = startup_info.lpAttributeList]() {
+ free_proc_thread_attr_list(list);
+ });
+
+ if (is_running_as_system()) {
+ // Duplicate the current user's token
+ HANDLE user_token = retrieve_users_token(elevated);
+ if (!user_token) {
+ // Fail the launch rather than risking launching with Sunshine's permissions unmodified.
+ ec = std::make_error_code(std::errc::no_such_process);
return bp::child();
}
- // Launch the process with the duplicated shell token.
- // Set CREATE_BREAKAWAY_FROM_JOB to avoid the child being killed if SunshineSvc.exe is terminated.
- // Set CREATE_NEW_CONSOLE to avoid writing stdout to Sunshine's log if 'file' is not specified.
- ret = CreateProcessAsUserW(shell_token,
- NULL,
- (LPWSTR) wcmd.c_str(),
- NULL,
- NULL,
- !!(startup_info.StartupInfo.dwFlags & STARTF_USESTDHANDLES),
- EXTENDED_STARTUPINFO_PRESENT | CREATE_UNICODE_ENVIRONMENT | CREATE_NEW_CONSOLE | CREATE_BREAKAWAY_FROM_JOB,
- env_block.data(),
- start_dir.empty() ? NULL : start_dir.c_str(),
- (LPSTARTUPINFOW) &startup_info,
- &process_info);
+ // Use RAII to ensure the shell token is closed when we're done with it
+ auto token_close = util::fail_guard([user_token]() {
+ CloseHandle(user_token);
+ });
- if (!ret) {
- auto error = GetLastError();
-
- if (error == 740) {
- BOOST_LOG(info) << "Could not execute previous command because it required elevation. Running the command again with elevation, for security reasons this will prompt user interaction."sv;
- startup_info.StartupInfo.wShowWindow = SW_HIDE;
- startup_info.StartupInfo.dwFlags = startup_info.StartupInfo.dwFlags | STARTF_USESHOWWINDOW;
- std::wstring elevated_command = L"tools\\elevator.exe ";
- elevated_command += wcmd;
-
- // For security reasons, Windows enforces that an application can have only one "interactive thread" responsible for processing user input and managing the user interface (UI).
- // Since UAC prompts are interactive, we cannot have a UAC prompt while Sunshine is already running because it would block the thread.
- // To work around this issue, we will launch a separate process that will elevate the command, which will prompt the user to confirm the elevation.
- // This is our intended behavior: to require interaction before elevating the command.
- ret = CreateProcessAsUserW(shell_token,
- nullptr,
- (LPWSTR) elevated_command.c_str(),
- nullptr,
- nullptr,
- !!(startup_info.StartupInfo.dwFlags & STARTF_USESTDHANDLES),
- EXTENDED_STARTUPINFO_PRESENT | CREATE_UNICODE_ENVIRONMENT | CREATE_NEW_CONSOLE | CREATE_BREAKAWAY_FROM_JOB,
- env_block.data(),
- start_dir.empty() ? nullptr : start_dir.c_str(),
- (LPSTARTUPINFOW) &startup_info,
- &process_info);
- }
+ // Populate env with user-specific environment variables
+ if (!merge_user_environment_block(env, user_token)) {
+ ec = std::make_error_code(std::errc::not_enough_memory);
+ return bp::child();
}
- // End impersonation of the logged on user. If this fails (which is extremely unlikely),
- // we will be running with an unknown user token. The only safe thing to do in that case
- // is terminate ourselves.
- if (!RevertToSelf()) {
- auto winerror = GetLastError();
- BOOST_LOG(fatal) << "Failed to revert to self after impersonation: "sv << winerror;
- std::abort();
- }
- }
+ // Open the process as the current user account, elevation is handled in the token itself.
+ ec = impersonate_current_user(user_token, [&]() {
+ ret = CreateProcessAsUserW(user_token,
+ NULL,
+ (LPWSTR) wcmd.c_str(),
+ NULL,
+ NULL,
+ !!(startup_info.StartupInfo.dwFlags & STARTF_USESTDHANDLES),
+ EXTENDED_STARTUPINFO_PRESENT | CREATE_UNICODE_ENVIRONMENT | CREATE_NEW_CONSOLE | CREATE_BREAKAWAY_FROM_JOB,
+ env_block.data(),
+ start_dir.empty() ? NULL : start_dir.c_str(),
+ (LPSTARTUPINFOW) &startup_info,
+ &process_info);
+ });
+ }
+ // Otherwise, launch the process using CreateProcessW()
+ // This will inherit the elevation of whatever the user launched Sunshine with.
else {
ret = CreateProcessW(NULL,
(LPWSTR) wcmd.c_str(),
@@ -535,30 +647,8 @@ namespace platf {
&process_info);
}
- if (ret) {
- // Since we are always spawning a process with a less privileged token than ourselves,
- // bp::child() should have no problem opening it with any access rights it wants.
- auto child = bp::child((bp::pid_t) process_info.dwProcessId);
- if (group) {
- group->add(child);
- }
-
- // Only close handles after bp::child() has opened the process. If the process terminates
- // quickly, the PID could be reused if we close the process handle.
- CloseHandle(process_info.hThread);
- CloseHandle(process_info.hProcess);
-
- BOOST_LOG(info) << cmd << " running with PID "sv << child.id();
- return child;
- }
- else {
- // We must NOT try bp::child() here, since this case can potentially be induced by ACL
- // manipulation (denying yourself execute permission) to cause an escalation of privilege.
- auto winerror = GetLastError();
- BOOST_LOG(error) << "Failed to launch process: "sv << winerror;
- ec = std::make_error_code(std::errc::invalid_argument);
- return bp::child();
- }
+ // Use the results of the launch to create a bp::child object
+ return create_boost_child_from_results(ret, cmd, ec, process_info, group);
}
void
@@ -900,5 +990,4 @@ namespace platf {
return std::make_unique(flow_id);
}
-
} // namespace platf
\ No newline at end of file
diff --git a/src/platform/windows/misc.h b/src/platform/windows/misc.h
index 4bcd31fd397..6fe68ac2a53 100644
--- a/src/platform/windows/misc.h
+++ b/src/platform/windows/misc.h
@@ -1,6 +1,5 @@
#ifndef SUNSHINE_WINDOWS_MISC_H
#define SUNSHINE_WINDOWS_MISC_H
-
#include
#include
#include
diff --git a/src/process.cpp b/src/process.cpp
index 6053e73bbcf..f887545213d 100644
--- a/src/process.cpp
+++ b/src/process.cpp
@@ -125,16 +125,21 @@ namespace proc {
});
for (; _app_prep_it != std::end(_app.prep_cmds); ++_app_prep_it) {
- auto &cmd = _app_prep_it->do_cmd;
+ auto &cmd = *_app_prep_it;
+
+ // Skip empty commands
+ if (cmd.do_cmd.empty()) {
+ continue;
+ }
boost::filesystem::path working_dir = _app.working_dir.empty() ?
- find_working_directory(cmd, _env) :
+ find_working_directory(cmd.do_cmd, _env) :
boost::filesystem::path(_app.working_dir);
- BOOST_LOG(info) << "Executing Do Cmd: ["sv << cmd << ']';
- auto child = platf::run_unprivileged(cmd, working_dir, _env, _pipe.get(), ec, nullptr);
+ BOOST_LOG(info) << "Executing Do Cmd: ["sv << cmd.do_cmd << ']';
+ auto child = platf::run_command(cmd.elevated, cmd.do_cmd, working_dir, _env, _pipe.get(), ec, nullptr);
if (ec) {
- BOOST_LOG(error) << "Couldn't run ["sv << cmd << "]: System: "sv << ec.message();
+ BOOST_LOG(error) << "Couldn't run ["sv << cmd.do_cmd << "]: System: "sv << ec.message();
return -1;
}
@@ -142,7 +147,7 @@ namespace proc {
auto ret = child.exit_code();
if (ret != 0) {
- BOOST_LOG(error) << '[' << cmd << "] failed with code ["sv << ret << ']';
+ BOOST_LOG(error) << '[' << cmd.do_cmd << "] failed with code ["sv << ret << ']';
return -1;
}
}
@@ -152,7 +157,7 @@ namespace proc {
find_working_directory(cmd, _env) :
boost::filesystem::path(_app.working_dir);
BOOST_LOG(info) << "Spawning ["sv << cmd << "] in ["sv << working_dir << ']';
- auto child = platf::run_unprivileged(cmd, working_dir, _env, _pipe.get(), ec, nullptr);
+ auto child = platf::run_command(_app.elevated, cmd, working_dir, _env, _pipe.get(), ec, nullptr);
if (ec) {
BOOST_LOG(warning) << "Couldn't spawn ["sv << cmd << "]: System: "sv << ec.message();
}
@@ -170,7 +175,7 @@ namespace proc {
find_working_directory(_app.cmd, _env) :
boost::filesystem::path(_app.working_dir);
BOOST_LOG(info) << "Executing: ["sv << _app.cmd << "] in ["sv << working_dir << ']';
- _process = platf::run_unprivileged(_app.cmd, working_dir, _env, _pipe.get(), ec, &_process_handle);
+ _process = platf::run_command(_app.elevated, _app.cmd, working_dir, _env, _pipe.get(), ec, &_process_handle);
if (ec) {
BOOST_LOG(warning) << "Couldn't run ["sv << _app.cmd << "]: System: "sv << ec.message();
return -1;
@@ -208,17 +213,17 @@ namespace proc {
_app_id = -1;
for (; _app_prep_it != _app_prep_begin; --_app_prep_it) {
- auto &cmd = (_app_prep_it - 1)->undo_cmd;
+ auto &cmd = *(_app_prep_it - 1);
- if (cmd.empty()) {
+ if (cmd.undo_cmd.empty()) {
continue;
}
boost::filesystem::path working_dir = _app.working_dir.empty() ?
- find_working_directory(cmd, _env) :
+ find_working_directory(cmd.undo_cmd, _env) :
boost::filesystem::path(_app.working_dir);
- BOOST_LOG(info) << "Executing Undo Cmd: ["sv << cmd << ']';
- auto child = platf::run_unprivileged(cmd, working_dir, _env, _pipe.get(), ec, nullptr);
+ BOOST_LOG(info) << "Executing Undo Cmd: ["sv << cmd.undo_cmd << ']';
+ auto child = platf::run_command(cmd.elevated, cmd.undo_cmd, working_dir, _env, _pipe.get(), ec, nullptr);
if (ec) {
BOOST_LOG(warning) << "System: "sv << ec.message();
@@ -481,6 +486,7 @@ namespace proc {
auto cmd = app_node.get_optional("cmd"s);
auto image_path = app_node.get_optional("image-path"s);
auto working_dir = app_node.get_optional("working-dir"s);
+ auto elevated = app_node.get_optional("elevated"s);
std::vector prep_cmds;
if (!exclude_global_prep.value_or(false)) {
@@ -489,7 +495,10 @@ namespace proc {
auto do_cmd = parse_env_val(this_env, prep_cmd.do_cmd);
auto undo_cmd = parse_env_val(this_env, prep_cmd.undo_cmd);
- prep_cmds.emplace_back(std::move(do_cmd), std::move(undo_cmd));
+ prep_cmds.emplace_back(
+ std::move(do_cmd),
+ std::move(undo_cmd),
+ std::move(prep_cmd.elevated));
}
}
@@ -498,15 +507,14 @@ namespace proc {
prep_cmds.reserve(prep_cmds.size() + prep_nodes.size());
for (auto &[_, prep_node] : prep_nodes) {
- auto do_cmd = parse_env_val(this_env, prep_node.get("do"s));
+ auto do_cmd = prep_node.get_optional("do"s);
auto undo_cmd = prep_node.get_optional("undo"s);
+ auto elevated = prep_node.get_optional("elevated");
- if (undo_cmd) {
- prep_cmds.emplace_back(std::move(do_cmd), parse_env_val(this_env, *undo_cmd));
- }
- else {
- prep_cmds.emplace_back(std::move(do_cmd));
- }
+ prep_cmds.emplace_back(
+ parse_env_val(this_env, do_cmd.value_or("")),
+ parse_env_val(this_env, undo_cmd.value_or("")),
+ std::move(elevated.value_or(false)));
}
}
@@ -536,6 +544,8 @@ namespace proc {
ctx.image_path = parse_env_val(this_env, *image_path);
}
+ ctx.elevated = elevated.value_or(false);
+
auto possible_ids = calculate_app_id(name, ctx.image_path, i++);
if (ids.count(std::get<0>(possible_ids)) == 0) {
// Avoid using index to generate id if possible
diff --git a/src/process.h b/src/process.h
index 12f4b5d9077..418ed3a79c9 100644
--- a/src/process.h
+++ b/src/process.h
@@ -48,6 +48,7 @@ namespace proc {
std::string output;
std::string image_path;
std::string id;
+ bool elevated;
};
class proc_t {
diff --git a/src/system_tray.cpp b/src/system_tray.cpp
index b25d6d38714..8de4baf5572 100644
--- a/src/system_tray.cpp
+++ b/src/system_tray.cpp
@@ -62,7 +62,7 @@ namespace system_tray {
boost::process::environment _env = boost::this_process::environment();
std::error_code ec;
- auto child = platf::run_unprivileged(cmd, working_dir, _env, nullptr, ec, nullptr);
+ auto child = platf::run_command(false, cmd, working_dir, _env, nullptr, ec, nullptr);
if (ec) {
BOOST_LOG(warning) << "Couldn't open url ["sv << url << "]: System: "sv << ec.message();
}
diff --git a/src_assets/common/assets/web/apps.html b/src_assets/common/assets/web/apps.html
index 8908c50c509..f3243036f96 100644
--- a/src_assets/common/assets/web/apps.html
+++ b/src_assets/common/assets/web/apps.html
@@ -15,9 +15,11 @@
Applications
{{app.name}}
-
+
@@ -56,29 +58,51 @@
Applications
-
-
-
-
-
- Enable/Disable the execution of Global Prep Commands for this application.
-
+
+
+
+
+ Enable/Disable the execution of Global Prep Commands for this
+ application.
+
+
- A list of commands to be run before/after this application.
- If any of the prep-commands fail, starting the application is aborted
+ A list of commands to be run before/after this application.
+ If any of the prep-commands fail, starting the application is aborted.
-
+
+
+
+
-
Do
-
Undo
-
+
+
Do Command
+
Undo Command
+
+ Run as Admin
+
+
+
-
+
Applications
v-model="c.undo"
/>
+
+
+
+
+
+
+
-
@@ -170,9 +205,28 @@
Applications
v-model="editForm['working-dir']"
/>
- The working directory that should be passed to the process.
- For example, some applications use the working directory to search for configuration files.
- If not set, Sunshine will default to the parent directory of the command
+ The working directory that should be passed to the process. For
+ example, some applications use the working directory to search for
+ configuration files. If not set, Sunshine will default to the parent
+ directory of the command
+
+
+
+
+
+
+
+ This can be necessary for some applications that require administrator
+ permissions to run properly.