From bed58cf8b91bbff6c0151ef31e108b59aee145c9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 20 Jan 2024 11:07:42 -0500 Subject: [PATCH 001/182] build(deps): bump vite from 4.4.9 to 4.5.2 (#2048) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b9a70637337..4a709402bb3 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "@popperjs/core": "2.11.8", "@vitejs/plugin-vue": "4.6.2", "bootstrap": "5.3.2", - "vite": "4.4.9", + "vite": "4.5.2", "vite-plugin-ejs": "1.6.4", "vue": "3.4.5" } From bf1b9a20ec1a8e4d39b997a983a14cb78ceb66e8 Mon Sep 17 00:00:00 2001 From: Lukas Senionis Date: Sun, 21 Jan 2024 00:05:35 +0200 Subject: [PATCH 002/182] Add a dedicated library for working with JSON (#2047) --- .gitmodules | 4 + cmake/compile_definitions/common.cmake | 3 +- cmake/dependencies/common.cmake | 3 + cmake/dependencies/windows.cmake | 4 +- .../windows/nvprefs/driver_settings.cpp | 15 +- .../windows/nvprefs/driver_settings.h | 9 ++ .../nvprefs/nvapi_opensource_wrapper.cpp | 6 + .../windows/nvprefs/nvprefs_common.cpp | 2 + src/platform/windows/nvprefs/nvprefs_common.h | 26 +--- .../windows/nvprefs/nvprefs_interface.cpp | 6 +- .../windows/nvprefs/nvprefs_interface.h | 3 + src/platform/windows/nvprefs/undo_data.cpp | 131 ++++++++++++------ src/platform/windows/nvprefs/undo_data.h | 25 ++-- src/platform/windows/nvprefs/undo_file.cpp | 3 +- src/platform/windows/nvprefs/undo_file.h | 5 + third-party/nlohmann_json | 1 + 16 files changed, 154 insertions(+), 92 deletions(-) create mode 160000 third-party/nlohmann_json diff --git a/.gitmodules b/.gitmodules index faccb0d5b59..798235eb864 100644 --- a/.gitmodules +++ b/.gitmodules @@ -42,3 +42,7 @@ path = third-party/build-deps url = https://github.com/LizardByte/build-deps.git branch = dist +[submodule "third-party/nlohmann_json"] + path = third-party/nlohmann_json + url = https://github.com/nlohmann/json + branch = master diff --git a/cmake/compile_definitions/common.cmake b/cmake/compile_definitions/common.cmake index c096920a06b..a2ae3948ec1 100644 --- a/cmake/compile_definitions/common.cmake +++ b/cmake/compile_definitions/common.cmake @@ -126,4 +126,5 @@ list(APPEND SUNSHINE_EXTERNAL_LIBRARIES ${Boost_LIBRARIES} ${OPENSSL_LIBRARIES} ${CURL_LIBRARIES} - ${PLATFORM_LIBRARIES}) + ${PLATFORM_LIBRARIES} + nlohmann_json::nlohmann_json) diff --git a/cmake/dependencies/common.cmake b/cmake/dependencies/common.cmake index 8aeb92a8d87..699028a2b47 100644 --- a/cmake/dependencies/common.cmake +++ b/cmake/dependencies/common.cmake @@ -19,6 +19,9 @@ pkg_check_modules(CURL REQUIRED libcurl) pkg_check_modules(MINIUPNP miniupnpc REQUIRED) include_directories(SYSTEM ${MINIUPNP_INCLUDE_DIRS}) +# nlohmann_json +add_subdirectory(third-party/nlohmann_json) + # ffmpeg pre-compiled binaries if(WIN32) if(NOT CMAKE_SYSTEM_PROCESSOR STREQUAL "AMD64") diff --git a/cmake/dependencies/windows.cmake b/cmake/dependencies/windows.cmake index 3ce9a9da234..376c44da65a 100644 --- a/cmake/dependencies/windows.cmake +++ b/cmake/dependencies/windows.cmake @@ -1,6 +1,4 @@ # windows specific dependencies set(Boost_USE_STATIC_LIBS ON) # cmake-lint: disable=C0103 -# Boost >= 1.82.0 is required for boost::json::value::set_at_pointer() support -# todo - are we actually using json? I think this was attempted to be used in a PR, but we ended up not using json -find_package(Boost 1.82.0 COMPONENTS locale log filesystem program_options json REQUIRED) +find_package(Boost 1.71.0 COMPONENTS locale log filesystem program_options REQUIRED) diff --git a/src/platform/windows/nvprefs/driver_settings.cpp b/src/platform/windows/nvprefs/driver_settings.cpp index 54529fd5ddd..3b66e7a5054 100644 --- a/src/platform/windows/nvprefs/driver_settings.cpp +++ b/src/platform/windows/nvprefs/driver_settings.cpp @@ -1,6 +1,6 @@ -#include "nvprefs_common.h" - +// local includes #include "driver_settings.h" +#include "nvprefs_common.h" namespace { @@ -94,9 +94,8 @@ namespace nvprefs { driver_settings_t::restore_global_profile_to_undo(const undo_data_t &undo_data) { if (!session_handle) return false; - auto [opengl_swapchain_saved, opengl_swapchain_our_value, opengl_swapchain_undo_value] = undo_data.get_opengl_swapchain(); - - if (opengl_swapchain_saved) { + const auto &swapchain_data = undo_data.get_opengl_swapchain(); + if (swapchain_data) { NvAPI_Status status; NvDRSProfileHandle profile_handle = 0; @@ -111,14 +110,14 @@ namespace nvprefs { setting.version = NVDRS_SETTING_VER; status = NvAPI_DRS_GetSetting(session_handle, profile_handle, OGL_CPL_PREFER_DXPRESENT_ID, &setting); - if (status == NVAPI_OK && setting.settingLocation == NVDRS_CURRENT_PROFILE_LOCATION && setting.u32CurrentValue == opengl_swapchain_our_value) { - if (opengl_swapchain_undo_value) { + if (status == NVAPI_OK && setting.settingLocation == NVDRS_CURRENT_PROFILE_LOCATION && setting.u32CurrentValue == swapchain_data->our_value) { + if (swapchain_data->undo_value) { setting = {}; setting.version = NVDRS_SETTING_VER1; setting.settingId = OGL_CPL_PREFER_DXPRESENT_ID; setting.settingType = NVDRS_DWORD_TYPE; setting.settingLocation = NVDRS_CURRENT_PROFILE_LOCATION; - setting.u32CurrentValue = *opengl_swapchain_undo_value; + setting.u32CurrentValue = *swapchain_data->undo_value; status = NvAPI_DRS_SetSetting(session_handle, profile_handle, &setting); diff --git a/src/platform/windows/nvprefs/driver_settings.h b/src/platform/windows/nvprefs/driver_settings.h index fbbc9aa1f57..8e10098bae1 100644 --- a/src/platform/windows/nvprefs/driver_settings.h +++ b/src/platform/windows/nvprefs/driver_settings.h @@ -1,5 +1,14 @@ #pragma once +// nvapi headers +// disable clang-format header reordering +// as needs types from +// clang-format off +#include +#include +// clang-format on + +// local includes #include "undo_data.h" namespace nvprefs { diff --git a/src/platform/windows/nvprefs/nvapi_opensource_wrapper.cpp b/src/platform/windows/nvprefs/nvapi_opensource_wrapper.cpp index e1010737d55..3465754839c 100644 --- a/src/platform/windows/nvprefs/nvapi_opensource_wrapper.cpp +++ b/src/platform/windows/nvprefs/nvapi_opensource_wrapper.cpp @@ -1,5 +1,11 @@ +// standard library headers +#include + +// local includes +#include "driver_settings.h" #include "nvprefs_common.h" +// special nvapi header that should be the last include #include namespace { diff --git a/src/platform/windows/nvprefs/nvprefs_common.cpp b/src/platform/windows/nvprefs/nvprefs_common.cpp index ba15dfe3084..cda867dfdbc 100644 --- a/src/platform/windows/nvprefs/nvprefs_common.cpp +++ b/src/platform/windows/nvprefs/nvprefs_common.cpp @@ -1,4 +1,6 @@ +// local includes #include "nvprefs_common.h" +#include "src/main.h" // sunshine boost::log severity levels namespace nvprefs { diff --git a/src/platform/windows/nvprefs/nvprefs_common.h b/src/platform/windows/nvprefs/nvprefs_common.h index 7d4a661924e..5dc8ba4092d 100644 --- a/src/platform/windows/nvprefs/nvprefs_common.h +++ b/src/platform/windows/nvprefs/nvprefs_common.h @@ -3,19 +3,6 @@ // sunshine utility header for generic smart pointers #include "src/utility.h" -// sunshine boost::log severity levels -#include "src/main.h" - -// standard library headers -#include -#include -#include -#include -#include -#include -#include -#include - // winapi headers // disable clang-format header reordering // clang-format off @@ -23,21 +10,12 @@ #include // clang-format on -// nvapi headers -// disable clang-format header reordering -// clang-format off -#include -#include -// clang-format on - -// boost headers -#include - namespace nvprefs { struct safe_handle: public util::safe_ptr_v2 { using util::safe_ptr_v2::safe_ptr_v2; - explicit operator bool() const { + explicit + operator bool() const { auto handle = get(); return handle != NULL && handle != INVALID_HANDLE_VALUE; } diff --git a/src/platform/windows/nvprefs/nvprefs_interface.cpp b/src/platform/windows/nvprefs/nvprefs_interface.cpp index 961788aed2c..ea8fa6c5cd4 100644 --- a/src/platform/windows/nvprefs/nvprefs_interface.cpp +++ b/src/platform/windows/nvprefs/nvprefs_interface.cpp @@ -1,9 +1,7 @@ -#include "nvprefs_common.h" - +// local includes #include "nvprefs_interface.h" - #include "driver_settings.h" -#include "undo_data.h" +#include "src/main.h" // main include for assert #include "undo_file.h" namespace { diff --git a/src/platform/windows/nvprefs/nvprefs_interface.h b/src/platform/windows/nvprefs/nvprefs_interface.h index 43d588c37a1..583e72c540d 100644 --- a/src/platform/windows/nvprefs/nvprefs_interface.h +++ b/src/platform/windows/nvprefs/nvprefs_interface.h @@ -1,5 +1,8 @@ #pragma once +// standard library headers +#include + namespace nvprefs { class nvprefs_interface { diff --git a/src/platform/windows/nvprefs/undo_data.cpp b/src/platform/windows/nvprefs/undo_data.cpp index 7abd81f7279..388b02cface 100644 --- a/src/platform/windows/nvprefs/undo_data.cpp +++ b/src/platform/windows/nvprefs/undo_data.cpp @@ -1,70 +1,117 @@ -#include "nvprefs_common.h" +// external includes +#include +// local includes +#include "nvprefs_common.h" #include "undo_data.h" -namespace { - - const auto opengl_swapchain_our_value_key = "/opengl_swapchain/our_value"; - const auto opengl_swapchain_undo_value_key = "/opengl_swapchain/undo_value"; - -} // namespace +using json = nlohmann::json; -namespace nvprefs { +// Separate namespace for ADL, otherwise we need to define json +// functions in the same namespace as our types +namespace nlohmann { + using data_t = nvprefs::undo_data_t::data_t; + using opengl_swapchain_t = data_t::opengl_swapchain_t; - void - undo_data_t::set_opengl_swapchain(uint32_t our_value, std::optional undo_value) { - data.set_at_pointer(opengl_swapchain_our_value_key, our_value); - if (undo_value) { - data.set_at_pointer(opengl_swapchain_undo_value_key, *undo_value); - } - else { - data.set_at_pointer(opengl_swapchain_undo_value_key, nullptr); + template + struct adl_serializer> { + static void + to_json(json &j, const std::optional &opt) { + if (opt == std::nullopt) { + j = nullptr; + } + else { + j = *opt; + } } - } - std::tuple> - undo_data_t::get_opengl_swapchain() const { - auto get_value = [this](const auto &key) -> std::tuple> { - try { - auto value = data.at_pointer(key); - if (value.is_null()) { - return { true, std::nullopt }; - } - else if (value.is_number()) { - return { true, value.template to_number() }; - } + static void + from_json(const json &j, std::optional &opt) { + if (j.is_null()) { + opt = std::nullopt; } - catch (...) { + else { + opt = j.template get(); } - error_message(std::string("Couldn't find ") + key + " element"); - return { false, std::nullopt }; - }; + } + }; - auto [our_value_present, our_value] = get_value(opengl_swapchain_our_value_key); - auto [undo_value_present, undo_value] = get_value(opengl_swapchain_undo_value_key); + template <> + struct adl_serializer { + static void + to_json(json &j, const data_t &data) { + j = json { { "opengl_swapchain", data.opengl_swapchain } }; + } - if (!our_value_present || !undo_value_present || !our_value) { - return { false, 0, std::nullopt }; + static void + from_json(const json &j, data_t &data) { + j.at("opengl_swapchain").get_to(data.opengl_swapchain); } + }; - return { true, *our_value, undo_value }; + template <> + struct adl_serializer { + static void + to_json(json &j, const opengl_swapchain_t &opengl_swapchain) { + j = json { + { "our_value", opengl_swapchain.our_value }, + { "undo_value", opengl_swapchain.undo_value } + }; + } + + static void + from_json(const json &j, opengl_swapchain_t &opengl_swapchain) { + j.at("our_value").get_to(opengl_swapchain.our_value); + j.at("undo_value").get_to(opengl_swapchain.undo_value); + } + }; +} // namespace nlohmann + +namespace nvprefs { + + void + undo_data_t::set_opengl_swapchain(uint32_t our_value, std::optional undo_value) { + data.opengl_swapchain = data_t::opengl_swapchain_t { + our_value, + undo_value + }; + } + + std::optional + undo_data_t::get_opengl_swapchain() const { + return data.opengl_swapchain; } std::string undo_data_t::write() const { - return boost::json::serialize(data); + try { + // Keep this assignment otherwise data will be treated as an array due to + // initializer list shenanigangs. + const json json_data = data; + return json_data.dump(); + } + catch (const std::exception &err) { + error_message(std::string { "failed to serialize json data" }); + return {}; + } } void undo_data_t::read(const std::vector &buffer) { - data = boost::json::parse(std::string_view(buffer.data(), buffer.size())); + try { + data = json::parse(std::begin(buffer), std::end(buffer)); + } + catch (const std::exception &err) { + error_message(std::string { "failed to parse json data: " } + err.what()); + data = {}; + } } void undo_data_t::merge(const undo_data_t &newer_data) { - auto [opengl_swapchain_saved, opengl_swapchain_our_value, opengl_swapchain_undo_value] = newer_data.get_opengl_swapchain(); - if (opengl_swapchain_saved) { - set_opengl_swapchain(opengl_swapchain_our_value, opengl_swapchain_undo_value); + const auto &swapchain_data = newer_data.get_opengl_swapchain(); + if (swapchain_data) { + set_opengl_swapchain(swapchain_data->our_value, swapchain_data->undo_value); } } diff --git a/src/platform/windows/nvprefs/undo_data.h b/src/platform/windows/nvprefs/undo_data.h index 999483e190f..d5f30251348 100644 --- a/src/platform/windows/nvprefs/undo_data.h +++ b/src/platform/windows/nvprefs/undo_data.h @@ -1,24 +1,33 @@ #pragma once +// standard library headers +#include +#include +#include +#include + namespace nvprefs { class undo_data_t { public: + struct data_t { + struct opengl_swapchain_t { + uint32_t our_value; + std::optional undo_value; + }; + + std::optional opengl_swapchain; + }; + void set_opengl_swapchain(uint32_t our_value, std::optional undo_value); - std::tuple> + std::optional get_opengl_swapchain() const; - void - write(std::ostream &stream) const; - std::string write() const; - void - read(std::istream &stream); - void read(const std::vector &buffer); @@ -26,7 +35,7 @@ namespace nvprefs { merge(const undo_data_t &newer_data); private: - boost::json::value data; + data_t data; }; } // namespace nvprefs diff --git a/src/platform/windows/nvprefs/undo_file.cpp b/src/platform/windows/nvprefs/undo_file.cpp index 8a45955005d..9f2648ab997 100644 --- a/src/platform/windows/nvprefs/undo_file.cpp +++ b/src/platform/windows/nvprefs/undo_file.cpp @@ -1,5 +1,4 @@ -#include "nvprefs_common.h" - +// local includes #include "undo_file.h" namespace { diff --git a/src/platform/windows/nvprefs/undo_file.h b/src/platform/windows/nvprefs/undo_file.h index 46dcba61226..acbfbae2757 100644 --- a/src/platform/windows/nvprefs/undo_file.h +++ b/src/platform/windows/nvprefs/undo_file.h @@ -1,5 +1,10 @@ #pragma once +// standard library headers +#include + +// local includes +#include "nvprefs_common.h" #include "undo_data.h" namespace nvprefs { diff --git a/third-party/nlohmann_json b/third-party/nlohmann_json new file mode 160000 index 00000000000..9cca280a4d0 --- /dev/null +++ b/third-party/nlohmann_json @@ -0,0 +1 @@ +Subproject commit 9cca280a4d0ccf0c08f47a99aa71d1b0e52f8d03 From 270716b862e1566793c4eed9fb61535b6ad92903 Mon Sep 17 00:00:00 2001 From: ns6089 <61738816+ns6089@users.noreply.github.com> Date: Fri, 20 Oct 2023 13:35:33 +0300 Subject: [PATCH 003/182] Use safe string composition in tray tooltip --- src/system_tray.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/system_tray.cpp b/src/system_tray.cpp index 95a492dc297..189e58d293d 100644 --- a/src/system_tray.cpp +++ b/src/system_tray.cpp @@ -291,7 +291,7 @@ namespace system_tray { tray.icon = TRAY_ICON_PLAYING; tray.notification_title = "Stream Started"; char msg[256]; - sprintf(msg, "Streaming started for %s", app_name.c_str()); + snprintf(msg, std::size(msg), "Streaming started for %s", app_name.c_str()); tray.notification_text = msg; tray.tooltip = msg; tray.notification_icon = TRAY_ICON_PLAYING; @@ -311,7 +311,7 @@ namespace system_tray { tray.icon = TRAY_ICON_PAUSING; tray_update(&tray); char msg[256]; - sprintf(msg, "Streaming paused for %s", app_name.c_str()); + snprintf(msg, std::size(msg), "Streaming paused for %s", app_name.c_str()); tray.icon = TRAY_ICON_PAUSING; tray.notification_title = "Stream Paused"; tray.notification_text = msg; @@ -333,7 +333,7 @@ namespace system_tray { tray.icon = TRAY_ICON; tray_update(&tray); char msg[256]; - sprintf(msg, "Application %s successfully stopped", app_name.c_str()); + snprintf(msg, std::size(msg), "Application %s successfully stopped", app_name.c_str()); tray.icon = TRAY_ICON; tray.notification_icon = TRAY_ICON; tray.notification_title = "Application Stopped"; From 66150872b056b6238854de38e081dbb1f9f3ba06 Mon Sep 17 00:00:00 2001 From: ns6089 <61738816+ns6089@users.noreply.github.com> Date: Sat, 28 Oct 2023 12:02:25 +0300 Subject: [PATCH 004/182] Add nvprefs user override options --- src/config.cpp | 4 +++ src/config.h | 2 ++ .../windows/nvprefs/driver_settings.cpp | 28 +++++++++++++++++-- .../windows/nvprefs/nvprefs_common.cpp | 11 ++++++++ src/platform/windows/nvprefs/nvprefs_common.h | 8 ++++++ 5 files changed, 50 insertions(+), 3 deletions(-) diff --git a/src/config.cpp b/src/config.cpp index eb25f58a7da..1489786b3b9 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -329,6 +329,8 @@ namespace config { {}, // nv true, // nv_realtime_hags + true, // nv_opengl_vulkan_on_dxgi + true, // nv_sunshine_high_power_mode {}, // nv_legacy { @@ -940,6 +942,8 @@ namespace config { generic_f(vars, "nvenc_twopass", video.nv.two_pass, nv::twopass_from_view); bool_f(vars, "nvenc_h264_cavlc", video.nv.h264_cavlc); bool_f(vars, "nvenc_realtime_hags", video.nv_realtime_hags); + bool_f(vars, "nvenc_opengl_vulkan_on_dxgi", video.nv_opengl_vulkan_on_dxgi); + bool_f(vars, "nvenc_latency_over_power", video.nv_sunshine_high_power_mode); #ifndef __APPLE__ video.nv_legacy.preset = video.nv.quality_preset + 11; diff --git a/src/config.h b/src/config.h index ba0ee8a37fd..3ea736444c8 100644 --- a/src/config.h +++ b/src/config.h @@ -30,6 +30,8 @@ namespace config { nvenc::nvenc_config nv; bool nv_realtime_hags; + bool nv_opengl_vulkan_on_dxgi; + bool nv_sunshine_high_power_mode; struct { int preset; diff --git a/src/platform/windows/nvprefs/driver_settings.cpp b/src/platform/windows/nvprefs/driver_settings.cpp index 3b66e7a5054..2fbda6dc698 100644 --- a/src/platform/windows/nvprefs/driver_settings.cpp +++ b/src/platform/windows/nvprefs/driver_settings.cpp @@ -158,6 +158,11 @@ namespace nvprefs { undo_data.reset(); NvAPI_Status status; + if (!get_nvprefs_options().opengl_vulkan_on_dxgi) { + // User requested to leave OpenGL/Vulkan DXGI swapchain setting alone + return true; + } + NvDRSProfileHandle profile_handle = 0; status = NvAPI_DRS_GetBaseProfile(session_handle, &profile_handle); if (status != NVAPI_OK) { @@ -260,9 +265,26 @@ namespace nvprefs { setting.version = NVDRS_SETTING_VER1; status = NvAPI_DRS_GetSetting(session_handle, profile_handle, PREFERRED_PSTATE_ID, &setting); - if (status != NVAPI_OK || - setting.settingLocation != NVDRS_CURRENT_PROFILE_LOCATION || - setting.u32CurrentValue != PREFERRED_PSTATE_PREFER_MAX) { + if (!get_nvprefs_options().sunshine_high_power_mode) { + if (status == NVAPI_OK && + setting.settingLocation == NVDRS_CURRENT_PROFILE_LOCATION) { + // User requested to not use high power mode for sunshine.exe, + // remove the setting from application profile if it's been set previously + + status = NvAPI_DRS_DeleteProfileSetting(session_handle, profile_handle, PREFERRED_PSTATE_ID); + if (status != NVAPI_OK && status != NVAPI_SETTING_NOT_FOUND) { + nvapi_error_message(status); + error_message("NvAPI_DRS_DeleteProfileSetting() PREFERRED_PSTATE failed"); + return false; + } + modified = true; + + info_message(std::wstring(L"Removed PREFERRED_PSTATE for ") + sunshine_application_path); + } + } + else if (status != NVAPI_OK || + setting.settingLocation != NVDRS_CURRENT_PROFILE_LOCATION || + setting.u32CurrentValue != PREFERRED_PSTATE_PREFER_MAX) { // Set power setting if needed setting = {}; setting.version = NVDRS_SETTING_VER1; diff --git a/src/platform/windows/nvprefs/nvprefs_common.cpp b/src/platform/windows/nvprefs/nvprefs_common.cpp index cda867dfdbc..f6acb6c548a 100644 --- a/src/platform/windows/nvprefs/nvprefs_common.cpp +++ b/src/platform/windows/nvprefs/nvprefs_common.cpp @@ -2,6 +2,9 @@ #include "nvprefs_common.h" #include "src/main.h" // sunshine boost::log severity levels +// read user override preferences from global sunshine config +#include "src/config.h" + namespace nvprefs { void @@ -24,4 +27,12 @@ namespace nvprefs { BOOST_LOG(error) << "nvprefs: " << message; } + nvprefs_options + get_nvprefs_options() { + nvprefs_options options; + options.opengl_vulkan_on_dxgi = config::video.nv_opengl_vulkan_on_dxgi; + options.sunshine_high_power_mode = config::video.nv_sunshine_high_power_mode; + return options; + } + } // namespace nvprefs diff --git a/src/platform/windows/nvprefs/nvprefs_common.h b/src/platform/windows/nvprefs/nvprefs_common.h index 5dc8ba4092d..2b286d9e8ad 100644 --- a/src/platform/windows/nvprefs/nvprefs_common.h +++ b/src/platform/windows/nvprefs/nvprefs_common.h @@ -45,4 +45,12 @@ namespace nvprefs { void error_message(const std::string &message); + struct nvprefs_options { + bool opengl_vulkan_on_dxgi = true; + bool sunshine_high_power_mode = true; + }; + + nvprefs_options + get_nvprefs_options(); + } // namespace nvprefs From ede59e17d8f85d35840505d9345668b345a53369 Mon Sep 17 00:00:00 2001 From: ns6089 <61738816+ns6089@users.noreply.github.com> Date: Sat, 28 Oct 2023 12:26:32 +0300 Subject: [PATCH 005/182] Expose more nvenc options 1. Spatial AQ, for some reason NVENC have problems recovering details on flat regions of static images over multiple frames, official docs recommend to enable it for "game-streaming" 2. Percentage increase of default single-frame VBV/HRD, can act as low latency variable bitrate substitute --- src/config.cpp | 2 ++ src/nvenc/nvenc_base.cpp | 6 +++++- src/nvenc/nvenc_config.h | 3 +++ 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/config.cpp b/src/config.cpp index 1489786b3b9..b19486382ce 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -939,6 +939,8 @@ namespace config { string_f(vars, "sw_tune", video.sw.sw_tune); int_between_f(vars, "nvenc_preset", video.nv.quality_preset, { 1, 7 }); + int_between_f(vars, "nvenc_vbv_increase", video.nv.vbv_percentage_increase, { 0, 400 }); + bool_f(vars, "nvenc_spatial_aq", video.nv.adaptive_quantization); generic_f(vars, "nvenc_twopass", video.nv.two_pass, nv::twopass_from_view); bool_f(vars, "nvenc_h264_cavlc", video.nv.h264_cavlc); bool_f(vars, "nvenc_realtime_hags", video.nv_realtime_hags); diff --git a/src/nvenc/nvenc_base.cpp b/src/nvenc/nvenc_base.cpp index f305f7682ad..205a163dd26 100644 --- a/src/nvenc/nvenc_base.cpp +++ b/src/nvenc/nvenc_base.cpp @@ -222,6 +222,9 @@ namespace nvenc { if (get_encoder_cap(NV_ENC_CAPS_SUPPORT_CUSTOM_VBV_BUF_SIZE)) { enc_config.rcParams.vbvBufferSize = client_config.bitrate * 1000 / client_config.framerate; + if (config.vbv_percentage_increase > 0) { + enc_config.rcParams.vbvBufferSize += enc_config.rcParams.vbvBufferSize * config.vbv_percentage_increase / 100; + } } auto set_h264_hevc_common_format_config = [&](auto &format_config) { @@ -369,9 +372,10 @@ namespace nvenc { if (init_params.enableEncodeAsync) extra += " async"; if (buffer_is_10bit()) extra += " 10-bit"; if (enc_config.rcParams.multiPass != NV_ENC_MULTI_PASS_DISABLED) extra += " two-pass"; + if (config.vbv_percentage_increase > 0 && get_encoder_cap(NV_ENC_CAPS_SUPPORT_CUSTOM_VBV_BUF_SIZE)) extra += " vbv+" + std::to_string(config.vbv_percentage_increase); if (encoder_params.rfi) extra += " rfi"; if (init_params.enableWeightedPrediction) extra += " weighted-prediction"; - if (enc_config.rcParams.enableAQ) extra += " adaptive-quantization"; + if (enc_config.rcParams.enableAQ) extra += " spatial-aq"; if (enc_config.rcParams.enableMinQP) extra += " qpmin=" + std::to_string(enc_config.rcParams.minQP.qpInterP); if (config.insert_filler_data) extra += " filler-data"; BOOST_LOG(info) << "NvEnc: created encoder " << quality_preset_string_from_guid(init_params.presetGUID) << extra; diff --git a/src/nvenc/nvenc_config.h b/src/nvenc/nvenc_config.h index 632146b7db0..c4aae12a86e 100644 --- a/src/nvenc/nvenc_config.h +++ b/src/nvenc/nvenc_config.h @@ -20,6 +20,9 @@ namespace nvenc { // Use optional preliminary pass for better motion vectors, bitrate distribution and stricter VBV(HRD), uses CUDA cores nvenc_two_pass two_pass = nvenc_two_pass::quarter_resolution; + // Percentage increase of VBV/HRD from the default single frame, allows low-latency variable bitrate + int vbv_percentage_increase = 0; + // Improves fades compression, uses CUDA cores bool weighted_prediction = false; From 6ded2a9577bad6f7adb1970c2adffaf40e761f4a Mon Sep 17 00:00:00 2001 From: ns6089 <61738816+ns6089@users.noreply.github.com> Date: Sun, 29 Oct 2023 10:00:44 +0300 Subject: [PATCH 006/182] Update config page for new nvenc/nvprefs options --- src_assets/common/assets/web/config.html | 63 ++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/src_assets/common/assets/web/config.html b/src_assets/common/assets/web/config.html index 71e01ae2e0c..a6a26670cd7 100644 --- a/src_assets/common/assets/web/config.html +++ b/src_assets/common/assets/web/config.html @@ -889,6 +889,35 @@

+ +
+ + +
+ Assign higher QP values to flat regions of the video.
+ Recommended to enable when streaming at lower bitrates. +
+
+ + +
+ + +
+ By default sunshine uses single-frame + VBV/HRD, + which means any encoded video frame size is not expected to exceed requested bitrate divided by requested frame + rate.
+ Relaxing this restriction can be beneficial and act as low-latency variable bitrate, + but may also lead to packet loss if the network doesn't have buffer headroom to handle bitrate spikes.
+ Maximum accepted value is 400, which corresponds to 5x increased encoded video frame upper size limit. +
+
+
@@ -918,6 +947,36 @@

+ +
+ + +
+ Adaptive P-State algorithm which NVIDIA drivers employ doesn't work well with low latency streaming, so + sunshine requests high power mode explicitly.
+ Disabling it is not recommended since this can lead to + significantly increased encoding latency. +
+
+ + +
+ + +
+ Sunshine can't capture fullscreen OpenGL and Vulkan programs at full frame rate unless they present on top + of DXGI.
+ This is system-wide setting that is reverted on sunshine program exit. +
+
+
@@ -1220,7 +1279,11 @@

options: { "nvenc_preset": 1, "nvenc_twopass": "quarter_res", + "nvenc_spatial_aq": "disabled", + "nvenc_vbv_increase": 0, "nvenc_realtime_hags": "enabled", + "nvenc_latency_over_power": "enabled", + "nvenc_opengl_vulkan_on_dxgi": "enabled", "nvenc_h264_cavlc": "disabled", }, }, From fe886632dce19c19d69b4aec25f7a97c06c05bc9 Mon Sep 17 00:00:00 2001 From: ns6089 <61738816+ns6089@users.noreply.github.com> Date: Sun, 29 Oct 2023 10:22:09 +0300 Subject: [PATCH 007/182] Add new nvenc options to legacy ffmpeg path --- src/config.cpp | 2 ++ src/config.h | 2 ++ src/video.cpp | 9 +++++++++ 3 files changed, 13 insertions(+) diff --git a/src/config.cpp b/src/config.cpp index b19486382ce..7418fc0c112 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -953,6 +953,8 @@ namespace config { video.nv.two_pass == nvenc::nvenc_two_pass::full_resolution ? NV_ENC_TWO_PASS_FULL_RESOLUTION : NV_ENC_MULTI_PASS_DISABLED; video.nv_legacy.h264_coder = video.nv.h264_cavlc ? NV_ENC_H264_ENTROPY_CODING_MODE_CAVLC : NV_ENC_H264_ENTROPY_CODING_MODE_CABAC; + video.nv_legacy.aq = video.nv.adaptive_quantization; + video.nv_legacy.vbv_percentage_increase = video.nv.vbv_percentage_increase; #endif int_f(vars, "qsv_preset", video.qsv.qsv_preset, qsv::preset_from_view); diff --git a/src/config.h b/src/config.h index 3ea736444c8..e08a87f3c4d 100644 --- a/src/config.h +++ b/src/config.h @@ -37,6 +37,8 @@ namespace config { int preset; int multipass; int h264_coder; + int aq; + int vbv_percentage_increase; } nv_legacy; struct { diff --git a/src/video.cpp b/src/video.cpp index 68402d29d81..206e7feeb62 100644 --- a/src/video.cpp +++ b/src/video.cpp @@ -638,6 +638,7 @@ namespace video { { "tune"s, NV_ENC_TUNING_INFO_ULTRA_LOW_LATENCY }, { "rc"s, NV_ENC_PARAMS_RC_CBR }, { "multipass"s, &config::video.nv_legacy.multipass }, + { "aq"s, &config::video.nv_legacy.aq }, }, // SDR-specific options {}, @@ -658,6 +659,7 @@ namespace video { { "tune"s, NV_ENC_TUNING_INFO_ULTRA_LOW_LATENCY }, { "rc"s, NV_ENC_PARAMS_RC_CBR }, { "multipass"s, &config::video.nv_legacy.multipass }, + { "aq"s, &config::video.nv_legacy.aq }, }, // SDR-specific options { @@ -681,6 +683,7 @@ namespace video { { "rc"s, NV_ENC_PARAMS_RC_CBR }, { "coder"s, &config::video.nv_legacy.h264_coder }, { "multipass"s, &config::video.nv_legacy.multipass }, + { "aq"s, &config::video.nv_legacy.aq }, }, // SDR-specific options { @@ -1698,6 +1701,12 @@ namespace video { } else { ctx->rc_buffer_size = bitrate / config.framerate; + +#ifndef __APPLE__ + if (encoder.name == "nvenc" && config::video.nv_legacy.vbv_percentage_increase > 0) { + ctx->rc_buffer_size += ctx->rc_buffer_size * config::video.nv_legacy.vbv_percentage_increase / 100; + } +#endif } } } From 52511f3ccbc912ba0941b258532710cf00525171 Mon Sep 17 00:00:00 2001 From: ns6089 <61738816+ns6089@users.noreply.github.com> Date: Sun, 29 Oct 2023 13:39:43 +0300 Subject: [PATCH 008/182] Update docs for new nvenc options --- docs/source/about/advanced_usage.rst | 116 +++++++++++++++++++++++++++ 1 file changed, 116 insertions(+) diff --git a/docs/source/about/advanced_usage.rst b/docs/source/about/advanced_usage.rst index 08fb5834ace..975e88076c4 100644 --- a/docs/source/about/advanced_usage.rst +++ b/docs/source/about/advanced_usage.rst @@ -1182,6 +1182,59 @@ keybindings nvenc_twopass = quarter_res +`nvenc_spatial_aq `__ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +**Description** + Assign higher QP values to flat regions of the video. + Recommended to enable when streaming at lower bitrates. + + .. Note:: This option only applies when using NVENC `encoder`_. + +**Choices** + +.. table:: + :widths: auto + + ========== =========== + Value Description + ========== =========== + disabled Don't enable Spatial AQ (faster) + enabled Enable Spatial AQ (slower) + ========== =========== + +**Default** + ``disabled`` + +**Example** + .. code-block:: text + + nvenc_spatial_aq = disabled + +`nvenc_vbv_increase `__ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +**Description** + Single-frame VBV/HRD percentage increase. + By default sunshine uses single-frame VBV/HRD, which means any encoded video frame size is not expected to exceed requested bitrate divided by requested frame rate. + Relaxing this restriction can be beneficial and act as low-latency variable bitrate, but may also lead to packet loss if the network doesn't have buffer headroom to handle bitrate spikes. + Maximum accepted value is 400, which corresponds to 5x increased encoded video frame upper size limit. + + .. Note:: This option only applies when using NVENC `encoder`_. + + .. Warning:: Can lead to network packet loss. + +**Default** + ``0`` + +**Range** + ``0-400`` + +**Example** + .. code-block:: text + + nvenc_vbv_increase = 0 + `nvenc_realtime_hags `__ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -1214,6 +1267,69 @@ keybindings nvenc_realtime_hags = enabled +`nvenc_latency_over_power `__ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +**Description** + Adaptive P-State algorithm which NVIDIA drivers employ doesn't work well with low latency streaming, so sunshine requests high power mode explicitly. + + .. Note:: This option only applies when using NVENC `encoder`_. + + .. Warning:: Disabling it is not recommended since this can lead to significantly increased encoding latency. + + .. Caution:: Applies to Windows only. + +**Choices** + +.. table:: + :widths: auto + + ========== =========== + Value Description + ========== =========== + disabled Sunshine doesn't change GPU power preferences (not recommended) + enabled Sunshine requests high power mode explicitly + ========== =========== + +**Default** + ``enabled`` + +**Example** + .. code-block:: text + + nvenc_latency_over_power = enabled + +`nvenc_opengl_vulkan_on_dxgi `__ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +**Description** + Sunshine can't capture fullscreen OpenGL and Vulkan programs at full frame rate unless they present on top of DXGI. + This is system-wide setting that is reverted on sunshine program exit. + + .. Note:: This option only applies when using NVENC `encoder`_. + + .. Caution:: Applies to Windows only. + +**Choices** + +.. table:: + :widths: auto + + ========== =========== + Value Description + ========== =========== + disabled Sunshine leaves global Vulkan/OpenGL present method unchanged + enabled Sunshine changes global Vulkan/OpenGL present method to "Prefer layered on DXGI Swapchain" + ========== =========== + +**Default** + ``enabled`` + +**Example** + .. code-block:: text + + nvenc_opengl_vulkan_on_dxgi = enabled + `nvenc_h264_cavlc `__ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ From 0d4dfcd708c0027b7d8827a03163858800fa79fa Mon Sep 17 00:00:00 2001 From: PVermeer Date: Sun, 21 Jan 2024 22:53:30 +0100 Subject: [PATCH 009/182] Find the correct install paths for systemd units and udev rules (#2046) --- cmake/FindSystemd.cmake | 34 +++++++++++++++++++++++++++++++ cmake/FindUdev.cmake | 28 +++++++++++++++++++++++++ cmake/packaging/linux.cmake | 7 +++++-- docker/debian-bookworm.dockerfile | 1 + docker/debian-bullseye.dockerfile | 1 + docker/ubuntu-20.04.dockerfile | 1 + docker/ubuntu-22.04.dockerfile | 1 + 7 files changed, 71 insertions(+), 2 deletions(-) create mode 100644 cmake/FindSystemd.cmake create mode 100644 cmake/FindUdev.cmake diff --git a/cmake/FindSystemd.cmake b/cmake/FindSystemd.cmake new file mode 100644 index 00000000000..c41ca64d248 --- /dev/null +++ b/cmake/FindSystemd.cmake @@ -0,0 +1,34 @@ +# - Try to find Systemd +# Once done this will define +# +# SYSTEMD_FOUND - system has systemd +# SYSTEMD_USER_UNIT_INSTALL_DIR - the systemd system unit install directory +# SYSTEMD_SYSTEM_UNIT_INSTALL_DIR - the systemd user unit install directory + +IF (NOT WIN32) + + find_package(PkgConfig QUIET) + if(PKG_CONFIG_FOUND) + pkg_check_modules(SYSTEMD "systemd") + endif() + + if (SYSTEMD_FOUND) + execute_process(COMMAND ${PKG_CONFIG_EXECUTABLE} + --variable=systemduserunitdir systemd + OUTPUT_VARIABLE SYSTEMD_USER_UNIT_INSTALL_DIR) + + string(REGEX REPLACE "[ \t\n]+" "" SYSTEMD_USER_UNIT_INSTALL_DIR + "${SYSTEMD_USER_UNIT_INSTALL_DIR}") + + execute_process(COMMAND ${PKG_CONFIG_EXECUTABLE} + --variable=systemdsystemunitdir systemd + OUTPUT_VARIABLE SYSTEMD_SYSTEM_UNIT_INSTALL_DIR) + + string(REGEX REPLACE "[ \t\n]+" "" SYSTEMD_SYSTEM_UNIT_INSTALL_DIR + "${SYSTEMD_SYSTEM_UNIT_INSTALL_DIR}") + + mark_as_advanced(SYSTEMD_USER_UNIT_INSTALL_DIR SYSTEMD_SYSTEM_UNIT_INSTALL_DIR) + + endif () + +ENDIF () diff --git a/cmake/FindUdev.cmake b/cmake/FindUdev.cmake new file mode 100644 index 00000000000..8343f791d35 --- /dev/null +++ b/cmake/FindUdev.cmake @@ -0,0 +1,28 @@ +# - Try to find Udev +# Once done this will define +# +# UDEV_FOUND - system has udev +# UDEV_RULES_INSTALL_DIR - the udev rules install directory + +IF (NOT WIN32) + + find_package(PkgConfig QUIET) + if(PKG_CONFIG_FOUND) + pkg_check_modules(UDEV "udev") + endif() + + if (UDEV_FOUND) + execute_process(COMMAND ${PKG_CONFIG_EXECUTABLE} + --variable=udevdir udev + OUTPUT_VARIABLE UDEV_RULES_INSTALL_DIR) + + string(REGEX REPLACE "[ \t\n]+" "" UDEV_RULES_INSTALL_DIR + "${UDEV_RULES_INSTALL_DIR}") + + set(UDEV_RULES_INSTALL_DIR "${UDEV_RULES_INSTALL_DIR}/rules.d") + + mark_as_advanced(UDEV_RULES_INSTALL_DIR) + + endif () + +ENDIF () diff --git a/cmake/packaging/linux.cmake b/cmake/packaging/linux.cmake index 517224d3cb9..8563414a40e 100644 --- a/cmake/packaging/linux.cmake +++ b/cmake/packaging/linux.cmake @@ -8,10 +8,13 @@ if(${SUNSHINE_BUILD_APPIMAGE} OR ${SUNSHINE_BUILD_FLATPAK}) install(FILES "${CMAKE_CURRENT_BINARY_DIR}/sunshine.service" DESTINATION "${SUNSHINE_ASSETS_DIR}/systemd/user") else() + find_package(Systemd) + find_package(Udev) + install(FILES "${SUNSHINE_SOURCE_ASSETS_DIR}/linux/misc/85-sunshine.rules" - DESTINATION "${CMAKE_INSTALL_LIBDIR}/udev/rules.d") + DESTINATION "${UDEV_RULES_INSTALL_DIR}") install(FILES "${CMAKE_CURRENT_BINARY_DIR}/sunshine.service" - DESTINATION "${CMAKE_INSTALL_LIBDIR}/systemd/user") + DESTINATION "${SYSTEMD_USER_UNIT_INSTALL_DIR}") endif() # Post install diff --git a/docker/debian-bookworm.dockerfile b/docker/debian-bookworm.dockerfile index 6eb1108bf11..a62e092eab4 100644 --- a/docker/debian-bookworm.dockerfile +++ b/docker/debian-bookworm.dockerfile @@ -61,6 +61,7 @@ apt-get install -y --no-install-recommends \ libxtst-dev \ nodejs \ npm \ + udev \ wget if [[ "${TARGETPLATFORM}" == 'linux/amd64' ]]; then apt-get install -y --no-install-recommends \ diff --git a/docker/debian-bullseye.dockerfile b/docker/debian-bullseye.dockerfile index d9862432cec..f355307631d 100644 --- a/docker/debian-bullseye.dockerfile +++ b/docker/debian-bullseye.dockerfile @@ -60,6 +60,7 @@ apt-get install -y --no-install-recommends \ libxfixes-dev \ libxrandr-dev \ libxtst-dev \ + udev \ wget if [[ "${TARGETPLATFORM}" == 'linux/amd64' ]]; then apt-get install -y --no-install-recommends \ diff --git a/docker/ubuntu-20.04.dockerfile b/docker/ubuntu-20.04.dockerfile index 4715475ac6e..d677830db5c 100644 --- a/docker/ubuntu-20.04.dockerfile +++ b/docker/ubuntu-20.04.dockerfile @@ -61,6 +61,7 @@ apt-get install -y --no-install-recommends \ libxfixes-dev \ libxrandr-dev \ libxtst-dev \ + udev \ wget if [[ "${TARGETPLATFORM}" == 'linux/amd64' ]]; then apt-get install -y --no-install-recommends \ diff --git a/docker/ubuntu-22.04.dockerfile b/docker/ubuntu-22.04.dockerfile index 0e975df0558..ab6ec096a3b 100644 --- a/docker/ubuntu-22.04.dockerfile +++ b/docker/ubuntu-22.04.dockerfile @@ -60,6 +60,7 @@ apt-get install -y --no-install-recommends \ libxfixes-dev \ libxrandr-dev \ libxtst-dev \ + udev \ wget if [[ "${TARGETPLATFORM}" == 'linux/amd64' ]]; then apt-get install -y --no-install-recommends \ From 75b6e1f1d8484a02ed0043973a7f6cd32847c097 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Thu, 25 Jan 2024 20:21:05 -0500 Subject: [PATCH 010/182] docs(setup): correct instruction for portfile install (#2058) --- docs/source/about/setup.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/about/setup.rst b/docs/source/about/setup.rst index b2d2421ba56..8a280fcf644 100644 --- a/docs/source/about/setup.rst +++ b/docs/source/about/setup.rst @@ -310,7 +310,7 @@ Install ``Ctrl+x``, then ``Y`` to exit and save changes. - #. Download the ``Portfile`` to ``~/Downloads`` and run the following code. + #. Download and install by running the following code. .. code-block:: bash From 3f31400e7fe6e5fcbda4d271312582048fab8cbf Mon Sep 17 00:00:00 2001 From: Kelvie Wong Date: Wed, 24 Jan 2024 15:13:49 -0800 Subject: [PATCH 011/182] Revert "Remove useless texture size queries for every frame in display_ram_t" This reverts commit ebe01ce20b06c6f10392f945c1c7083ed3aa733f. Looks like this commit undos the fix for #453, see also PRs #649 and #884. This also adds a comment so hopefully it doesn't get removed again in the future (or a proper fix is found). --- src/platform/linux/kmsgrab.cpp | 9 ++++++++- src/platform/linux/wlgrab.cpp | 7 +++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/platform/linux/kmsgrab.cpp b/src/platform/linux/kmsgrab.cpp index faa6dfb4ed9..eb85a257ec4 100644 --- a/src/platform/linux/kmsgrab.cpp +++ b/src/platform/linux/kmsgrab.cpp @@ -1259,11 +1259,18 @@ namespace platf { auto &rgb = *rgb_opt; + gl::ctx.BindTexture(GL_TEXTURE_2D, rgb->tex[0]); + + // Don't remove these lines, see https://github.com/LizardByte/Sunshine/issues/453 + int w, h; + gl::ctx.GetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &w); + gl::ctx.GetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &h); + BOOST_LOG(debug) << "width and height: w "sv << w << " h "sv << h; + if (!pull_free_image_cb(img_out)) { return platf::capture_e::interrupted; } - gl::ctx.BindTexture(GL_TEXTURE_2D, rgb->tex[0]); 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 && captured_cursor.visible) { diff --git a/src/platform/linux/wlgrab.cpp b/src/platform/linux/wlgrab.cpp index 0e22cbe2248..24791d0d6b2 100644 --- a/src/platform/linux/wlgrab.cpp +++ b/src/platform/linux/wlgrab.cpp @@ -182,6 +182,13 @@ namespace wl { } gl::ctx.BindTexture(GL_TEXTURE_2D, (*rgb_opt)->tex[0]); + + // Don't remove these lines, see https://github.com/LizardByte/Sunshine/issues/453 + int w, h; + gl::ctx.GetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &w); + 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->height * img_out->row_pitch, img_out->data); gl::ctx.BindTexture(GL_TEXTURE_2D, 0); From 65851407b16f396c6e538a1aaa826a3181494cf7 Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Wed, 24 Jan 2024 19:03:57 -0600 Subject: [PATCH 012/182] Avoid unnecessary reallocation of the IV for every packet --- src/stream.cpp | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/src/stream.cpp b/src/stream.cpp index 2e0c0f521a2..544d0dce010 100644 --- a/src/stream.cpp +++ b/src/stream.cpp @@ -246,16 +246,13 @@ namespace stream { // return bytes written on success // return -1 on error static inline int - encode_audio(bool encrypted, const audio::buffer_t &plaintext, audio_packet_t &destination, std::uint32_t avRiKeyIv, crypto::cipher::cbc_t &cbc) { + encode_audio(bool encrypted, const audio::buffer_t &plaintext, audio_packet_t &destination, crypto::aes_t &iv, crypto::cipher::cbc_t &cbc) { // If encryption isn't enabled if (!encrypted) { std::copy(std::begin(plaintext), std::end(plaintext), destination->payload()); return plaintext.size(); } - crypto::aes_t iv(16); - *(std::uint32_t *) iv.data() = util::endian::big(avRiKeyIv + destination->rtp.sequenceNumber); - return cbc.encrypt(std::string_view { (char *) std::begin(plaintext), plaintext.size() }, destination->payload(), &iv); } @@ -401,6 +398,8 @@ namespace stream { struct { crypto::cipher::gcm_t cipher; crypto::aes_t legacy_input_enc_iv; // Only used when the client doesn't support full control stream encryption + crypto::aes_t incoming_iv; + crypto::aes_t outgoing_iv; std::uint32_t connect_data; // Used for new clients with ML_FF_SESSION_ID_V1 std::string expected_peer_address; // Only used for legacy clients without ML_FF_SESSION_ID_V1 @@ -437,7 +436,7 @@ namespace stream { auto seq = session->control.seq++; - crypto::aes_t iv; + auto &iv = session->control.outgoing_iv; if (session->config.encryptionFlagsEnabled & SS_ENC_CONTROL_V2) { // We use the deterministic IV construction algorithm specified in NIST SP 800-38D // Section 8.2.1. The sequence number is our "invocation" field and the 'CH' in the @@ -977,7 +976,7 @@ namespace stream { std::string_view tagged_cipher { (char *) header->payload(), (size_t) tagged_cipher_length }; auto &cipher = session->control.cipher; - crypto::aes_t iv; + auto &iv = session->control.incoming_iv; if (session->config.encryptionFlagsEnabled & SS_ENC_CONTROL_V2) { // We use the deterministic IV construction algorithm specified in NIST SP 800-38D // Section 8.2.1. The sequence number is our "invocation" field and the 'CC' in the @@ -1241,6 +1240,7 @@ namespace stream { platf::adjust_thread_priority(platf::thread_priority_e::high); stat_trackers::min_max_avg_tracker frame_processing_latency_tracker; + crypto::aes_t iv(12); while (auto packet = packets->pop()) { if (shutdown_event->peek()) { @@ -1413,7 +1413,6 @@ namespace stream { // // The IV counter is 64 bits long which allows for 2^64 encrypted video packets // to be sent to each client before the IV repeats. - crypto::aes_t iv(12); std::copy_n((uint8_t *) &session->video.gcm_iv_counter, sizeof(session->video.gcm_iv_counter), std::begin(iv)); iv[11] = 'V'; // Video stream session->video.gcm_iv_counter++; @@ -1486,6 +1485,7 @@ namespace stream { audio_packet_t audio_packet { (audio_packet_raw_t *) malloc(sizeof(audio_packet_raw_t) + max_block_size) }; fec::rs_t rs { reed_solomon_new(RTPA_DATA_SHARDS, RTPA_FEC_SHARDS) }; + crypto::aes_t iv(16); // For unknown reasons, the RS parity matrix computed by our RS implementation // doesn't match the one Nvidia uses for audio data. I'm not exactly sure why, @@ -1513,11 +1513,9 @@ namespace stream { auto sequenceNumber = session->audio.sequenceNumber; auto timestamp = session->audio.timestamp; - // This will be mapped to big-endianness later - // For now, encode_audio needs it to be the proper sequenceNumber - audio_packet->rtp.sequenceNumber = sequenceNumber; + *(std::uint32_t *) iv.data() = util::endian::big(session->audio.avRiKeyId + sequenceNumber); - auto bytes = encode_audio(session->config.encryptionFlagsEnabled & SS_ENC_AUDIO, packet_data, audio_packet, session->audio.avRiKeyId, session->audio.cipher); + auto bytes = encode_audio(session->config.encryptionFlagsEnabled & SS_ENC_AUDIO, packet_data, audio_packet, iv, session->audio.cipher); if (bytes < 0) { BOOST_LOG(error) << "Couldn't encode audio packet"sv; break; From 9dfe97d405e219a1420aa64f18a775138f2a47ee Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Wed, 24 Jan 2024 19:04:24 -0600 Subject: [PATCH 013/182] Send the frame number in the encrypted video header --- src/stream.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/stream.cpp b/src/stream.cpp index 544d0dce010..eefccdc740f 100644 --- a/src/stream.cpp +++ b/src/stream.cpp @@ -128,7 +128,7 @@ namespace stream { } std::uint8_t iv[12]; // 12-byte IV is ideal for AES-GCM - std::uint32_t unused; + std::uint32_t frameNumber; std::uint8_t tag[16]; }; @@ -1419,7 +1419,7 @@ namespace stream { // Encrypt the target buffer in place auto *prefix = (video_packet_enc_prefix_t *) shards.prefix(x); - prefix->unused = 0; + prefix->frameNumber = packet->frame_index(); std::copy(std::begin(iv), std::end(iv), prefix->iv); session->video.cipher->encrypt(std::string_view { (char *) inspect, (size_t) blocksize }, prefix->tag, &iv); } From a15bd6539153dcf690f724a946afb671a0ff121e Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Sat, 27 Jan 2024 11:34:04 -0600 Subject: [PATCH 014/182] Adjust default CPU thread count to 2 It's a tiny reduction of encoding quality for a major increase in performance --- docs/source/about/advanced_usage.rst | 6 +++--- src/config.cpp | 2 +- src_assets/common/assets/web/config.html | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/source/about/advanced_usage.rst b/docs/source/about/advanced_usage.rst index 975e88076c4..fe603e38174 100644 --- a/docs/source/about/advanced_usage.rst +++ b/docs/source/about/advanced_usage.rst @@ -975,19 +975,19 @@ keybindings ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ **Description** - Minimum number of threads used for software encoding. + Minimum number of CPU threads used for encoding. .. note:: Increasing the value slightly reduces encoding efficiency, but the tradeoff is usually worth it to gain the use of more CPU cores for encoding. The ideal value is the lowest value that can reliably encode at your desired streaming settings on your hardware. **Default** - ``1`` + ``2`` **Example** .. code-block:: text - min_threads = 1 + min_threads = 2 `hevc_mode `__ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/src/config.cpp b/src/config.cpp index 7418fc0c112..2bdc0e4e91d 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -320,7 +320,7 @@ namespace config { 0, // hevc_mode 0, // av1_mode - 1, // min_threads + 2, // min_threads { "superfast"s, // preset "zerolatency"s, // tune diff --git a/src_assets/common/assets/web/config.html b/src_assets/common/assets/web/config.html index a6a26670cd7..e9d866dde51 100644 --- a/src_assets/common/assets/web/config.html +++ b/src_assets/common/assets/web/config.html @@ -761,8 +761,8 @@

- - + +
Increasing the value slightly reduces encoding efficiency, but the tradeoff is usually
worth it to gain the use of more CPU cores for encoding. The ideal value is the lowest
@@ -1266,7 +1266,7 @@

"channels": 1, "fec_percentage": 20, "qp": 28, - "min_threads": 1, + "min_threads": 2, "hevc_mode": 0, "av1_mode": 0, "capture": "", From 3b11bc86b0eae1402b4f6de68be10c03fac69194 Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Sat, 27 Jan 2024 11:34:54 -0600 Subject: [PATCH 015/182] Rewrite software scaling to use new multi-threaded swscale API --- src/video.cpp | 157 +++++++++++++++++++++++++++----------------------- 1 file changed, 85 insertions(+), 72 deletions(-) diff --git a/src/video.cpp b/src/video.cpp index 206e7feeb62..d0d2e4b93c0 100644 --- a/src/video.cpp +++ b/src/video.cpp @@ -10,7 +10,10 @@ #include extern "C" { +#include #include +#include +#include #include } @@ -101,30 +104,37 @@ namespace video { public: int convert(platf::img_t &img) override { - av_frame_make_writable(sw_frame.get()); - - const int linesizes[2] { - img.row_pitch, 0 - }; - - std::uint8_t *data[4]; - - data[0] = sw_frame->data[0] + offsetY; - if (sw_frame->format == AV_PIX_FMT_NV12) { - data[1] = sw_frame->data[1] + offsetUV * 2; - data[2] = nullptr; - } - else { - data[1] = sw_frame->data[1] + offsetUV; - data[2] = sw_frame->data[2] + offsetUV; - data[3] = nullptr; + // If we need to add aspect ratio padding, we need to scale into an intermediate output buffer + bool requires_padding = (sw_frame->width != sws_output_frame->width || sw_frame->height != sws_output_frame->height); + + // Setup the input frame using the caller's img_t + sws_input_frame->data[0] = img.data; + sws_input_frame->linesize[0] = img.row_pitch; + + // Perform color conversion and scaling to the final size + auto status = sws_scale_frame(sws.get(), requires_padding ? sws_output_frame.get() : sw_frame.get(), sws_input_frame.get()); + if (status < 0) { + char string[AV_ERROR_MAX_STRING_SIZE]; + BOOST_LOG(error) << "Couldn't scale frame: "sv << av_make_error_string(string, AV_ERROR_MAX_STRING_SIZE, status); + return -1; } - int ret = sws_scale(sws.get(), (std::uint8_t *const *) &img.data, linesizes, 0, img.height, data, sw_frame->linesize); - if (ret <= 0) { - BOOST_LOG(error) << "Couldn't convert image to required format and/or size"sv; - - return -1; + // If we require aspect ratio padding, copy the output frame into the final padded frame + if (requires_padding) { + auto fmt_desc = av_pix_fmt_desc_get((AVPixelFormat) sws_output_frame->format); + auto planes = av_pix_fmt_count_planes((AVPixelFormat) sws_output_frame->format); + for (int plane = 0; plane < planes; plane++) { + auto shift_h = plane == 0 ? 0 : fmt_desc->log2_chroma_h; + auto shift_w = plane == 0 ? 0 : fmt_desc->log2_chroma_w; + auto offset = ((offsetW >> shift_w) * fmt_desc->comp[plane].step) + (offsetH >> shift_h) * sw_frame->linesize[plane]; + + // Copy line-by-line to preserve leading padding for each row + for (int line = 0; line < sws_output_frame->height >> shift_h; line++) { + memcpy(sw_frame->data[plane] + offset + (line * sw_frame->linesize[plane]), + sws_output_frame->data[plane] + (line * sws_output_frame->linesize[plane]), + (size_t) (sws_output_frame->width >> shift_w) * fmt_desc->comp[plane].step); + } + } } // If frame is not a software frame, it means we still need to transfer from main memory @@ -170,43 +180,13 @@ namespace video { /** * When preserving aspect ratio, ensure that padding is black */ - int + void prefill() { auto frame = sw_frame ? sw_frame.get() : this->frame; - auto width = frame->width; - auto height = frame->height; - av_frame_get_buffer(frame, 0); - sws_t sws { - sws_getContext( - width, height, AV_PIX_FMT_BGR0, - width, height, (AVPixelFormat) frame->format, - SWS_LANCZOS | SWS_ACCURATE_RND, - nullptr, nullptr, nullptr) - }; - - if (!sws) { - return -1; - } - - util::buffer_t img { (std::size_t)(width * height) }; - std::fill(std::begin(img), std::end(img), 0); - - const int linesizes[2] { - width, 0 - }; - av_frame_make_writable(frame); - - auto data = img.begin(); - int ret = sws_scale(sws.get(), (std::uint8_t *const *) &data, linesizes, 0, height, frame->data, frame->linesize); - if (ret <= 0) { - BOOST_LOG(error) << "Couldn't convert image to required format and/or size"sv; - - return -1; - } - - return 0; + ptrdiff_t linesize[4] = { frame->linesize[0], frame->linesize[1], frame->linesize[2], frame->linesize[3] }; + av_image_fill_black(frame->data, linesize, (AVPixelFormat) frame->format, frame->color_range, frame->width, frame->height); } int @@ -223,9 +203,8 @@ namespace video { this->frame = frame; } - if (prefill()) { - return -1; - } + // Fill aspect ratio padding in the destination frame + prefill(); auto out_width = frame->width; auto out_height = frame->height; @@ -235,30 +214,64 @@ namespace video { out_width = in_width * scalar; out_height = in_height * scalar; - // result is always positive - auto offsetW = (frame->width - out_width) / 2; - auto offsetH = (frame->height - out_height) / 2; - offsetUV = (offsetW + offsetH * frame->width / 2) / 2; - offsetY = offsetW + offsetH * frame->width; + sws_input_frame.reset(av_frame_alloc()); + sws_input_frame->width = in_width; + sws_input_frame->height = in_height; + sws_input_frame->format = AV_PIX_FMT_BGR0; + + sws_output_frame.reset(av_frame_alloc()); + sws_output_frame->width = out_width; + sws_output_frame->height = out_height; + sws_output_frame->format = format; + + // Result is always positive + offsetW = (frame->width - out_width) / 2; + offsetH = (frame->height - out_height) / 2; - sws.reset(sws_getContext( - in_width, in_height, AV_PIX_FMT_BGR0, - out_width, out_height, format, - SWS_LANCZOS | SWS_ACCURATE_RND, - nullptr, nullptr, nullptr)); + sws.reset(sws_alloc_context()); + if (!sws) { + return -1; + } - return sws ? 0 : -1; + AVDictionary *options { nullptr }; + av_dict_set_int(&options, "srcw", sws_input_frame->width, 0); + av_dict_set_int(&options, "srch", sws_input_frame->height, 0); + av_dict_set_int(&options, "src_format", sws_input_frame->format, 0); + av_dict_set_int(&options, "dstw", sws_output_frame->width, 0); + av_dict_set_int(&options, "dsth", sws_output_frame->height, 0); + av_dict_set_int(&options, "dst_format", sws_output_frame->format, 0); + av_dict_set_int(&options, "sws_flags", SWS_LANCZOS | SWS_ACCURATE_RND, 0); + av_dict_set_int(&options, "threads", config::video.min_threads, 0); + + auto status = av_opt_set_dict(sws.get(), &options); + av_dict_free(&options); + if (status < 0) { + char string[AV_ERROR_MAX_STRING_SIZE]; + BOOST_LOG(error) << "Failed to set SWS options: "sv << av_make_error_string(string, AV_ERROR_MAX_STRING_SIZE, status); + return -1; + } + + status = sws_init_context(sws.get(), nullptr, nullptr); + if (status < 0) { + char string[AV_ERROR_MAX_STRING_SIZE]; + BOOST_LOG(error) << "Failed to initialize SWS: "sv << av_make_error_string(string, AV_ERROR_MAX_STRING_SIZE, status); + return -1; + } + + return 0; } // Store ownership when frame is hw_frame avcodec_frame_t hw_frame; avcodec_frame_t sw_frame; + avcodec_frame_t sws_input_frame; + avcodec_frame_t sws_output_frame; sws_t sws; - // offset of input image to output frame in pixels - int offsetUV; - int offsetY; + // Offset of input image to output frame in pixels + int offsetW; + int offsetH; }; enum flag_e : uint32_t { From 9a3553db04e20436855567ba8023c060024c7557 Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Sat, 27 Jan 2024 17:11:32 -0600 Subject: [PATCH 016/182] Refactor to use pixel format helpers in VAAPI --- src/platform/linux/graphics.cpp | 34 +++++++++++++++++++++++++++++++-- src/platform/linux/graphics.h | 2 +- src/platform/linux/vaapi.cpp | 20 +------------------ 3 files changed, 34 insertions(+), 22 deletions(-) diff --git a/src/platform/linux/graphics.cpp b/src/platform/linux/graphics.cpp index 6fe51fc8c8c..a91ee8e449b 100644 --- a/src/platform/linux/graphics.cpp +++ b/src/platform/linux/graphics.cpp @@ -7,6 +7,10 @@ #include +extern "C" { +#include +} + // I want to have as little build dependencies as possible // There aren't that many DRM_FORMAT I need to use, so define them here // @@ -806,10 +810,36 @@ namespace egl { } std::optional - sws_t::make(int in_width, int in_height, int out_width, int out_height, GLint gl_tex_internal_fmt) { + sws_t::make(int in_width, int in_height, int out_width, int out_height, AVPixelFormat format) { + GLint gl_format; + + // Decide the bit depth format of the backing texture based the target frame format + auto fmt_desc = av_pix_fmt_desc_get(format); + switch (fmt_desc->comp[0].depth) { + case 8: + gl_format = GL_RGBA8; + break; + + case 10: + gl_format = GL_RGB10_A2; + break; + + case 12: + gl_format = GL_RGBA12; + break; + + case 16: + gl_format = GL_RGBA16; + break; + + default: + BOOST_LOG(error) << "Unsupported pixel format for EGL frame: "sv << (int) format; + return std::nullopt; + } + auto tex = gl::tex_t::make(2); gl::ctx.BindTexture(GL_TEXTURE_2D, tex[0]); - gl::ctx.TexStorage2D(GL_TEXTURE_2D, 1, gl_tex_internal_fmt, in_width, in_height); + gl::ctx.TexStorage2D(GL_TEXTURE_2D, 1, gl_format, in_width, in_height); return make(in_width, in_height, out_width, out_height, std::move(tex)); } diff --git a/src/platform/linux/graphics.h b/src/platform/linux/graphics.h index 56995ca064f..9c0c3fb0b86 100644 --- a/src/platform/linux/graphics.h +++ b/src/platform/linux/graphics.h @@ -316,7 +316,7 @@ namespace egl { static std::optional make(int in_width, int in_height, int out_width, int out_height, gl::tex_t &&tex); static std::optional - make(int in_width, int in_height, int out_width, int out_height, GLint gl_tex_internal_fmt); + make(int in_width, int in_height, int out_width, int out_height, AVPixelFormat format); // Convert the loaded image into the first two framebuffers int diff --git a/src/platform/linux/vaapi.cpp b/src/platform/linux/vaapi.cpp index 118e1bd8da0..9cb7806b59b 100644 --- a/src/platform/linux/vaapi.cpp +++ b/src/platform/linux/vaapi.cpp @@ -195,25 +195,7 @@ namespace va { return -1; } - // Decide the bit depth format of the backing texture based the target frame format - GLint gl_format; - switch (hw_frames_ctx->sw_format) { - case AV_PIX_FMT_YUV420P: - case AV_PIX_FMT_NV12: - gl_format = GL_RGBA8; - break; - - case AV_PIX_FMT_YUV420P10: - case AV_PIX_FMT_P010: - gl_format = GL_RGB10_A2; - break; - - default: - BOOST_LOG(error) << "Unsupported pixel format for VA frame: "sv << hw_frames_ctx->sw_format; - return -1; - } - - auto sws_opt = egl::sws_t::make(width, height, frame->width, frame->height, gl_format); + auto sws_opt = egl::sws_t::make(width, height, frame->width, frame->height, hw_frames_ctx->sw_format); if (!sws_opt) { return -1; } From 8182f592e8386b714c35772ca3651547d5001e5a Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Sat, 27 Jan 2024 17:16:17 -0600 Subject: [PATCH 017/182] Implement NVENC support for KMS and wlgrab capture methods --- src/platform/linux/cuda.cpp | 266 ++++++++++++++++++++++++++++++++ src/platform/linux/cuda.h | 12 ++ src/platform/linux/graphics.cpp | 65 ++++++++ src/platform/linux/graphics.h | 10 ++ src/platform/linux/kmsgrab.cpp | 28 +++- src/platform/linux/wlgrab.cpp | 16 +- 6 files changed, 391 insertions(+), 6 deletions(-) diff --git a/src/platform/linux/cuda.cpp b/src/platform/linux/cuda.cpp index 0a04893682f..0f1d64132d5 100644 --- a/src/platform/linux/cuda.cpp +++ b/src/platform/linux/cuda.cpp @@ -4,6 +4,10 @@ */ #include +#include + +#include + #include #include @@ -29,6 +33,8 @@ extern "C" { #define CU_CHECK_IGNORE(x, y) \ check((x), SUNSHINE_STRINGVIEW(y ": ")) +namespace fs = std::filesystem; + using namespace std::literals; namespace cuda { constexpr auto cudaDevAttrMaxThreadsPerBlock = (CUdevice_attribute) 1; @@ -69,6 +75,13 @@ namespace cuda { CU_CHECK_IGNORE(cdf->cuStreamDestroy(stream), "Couldn't destroy cuda stream"); } + void + unregisterResource(CUgraphicsResource resource) { + CU_CHECK_IGNORE(cdf->cuGraphicsUnregisterResource(resource), "Couldn't unregister resource"); + } + + using registered_resource_t = util::safe_ptr; + class img_t: public platf::img_t { public: tex_t tex; @@ -223,6 +236,236 @@ namespace cuda { } }; + /** + * @brief Opens the DRM device associated with the CUDA device index. + * @param index CUDA device index to open. + * @return File descriptor or -1 on failure. + */ + file_t + open_drm_fd_for_cuda_device(int index) { + CUdevice device; + CU_CHECK(cdf->cuDeviceGet(&device, index), "Couldn't get CUDA device"); + + // There's no way to directly go from CUDA to a DRM device, so we'll + // use sysfs to look up the DRM device name from the PCI ID. + char pci_bus_id[13]; + CU_CHECK(cdf->cuDeviceGetPCIBusId(pci_bus_id, sizeof(pci_bus_id), device), "Couldn't get CUDA device PCI bus ID"); + BOOST_LOG(debug) << "Found CUDA device with PCI bus ID: "sv << pci_bus_id; + + // Look for the name of the primary node in sysfs + char sysfs_path[PATH_MAX]; + std::snprintf(sysfs_path, sizeof(sysfs_path), "/sys/bus/pci/devices/%s/drm", pci_bus_id); + fs::path sysfs_dir { sysfs_path }; + for (auto &entry : fs::directory_iterator { sysfs_dir }) { + auto file = entry.path().filename(); + auto filestring = file.generic_u8string(); + if (std::string_view { filestring }.substr(0, 4) != "card"sv) { + continue; + } + + BOOST_LOG(debug) << "Found DRM primary node: "sv << filestring; + + fs::path dri_path { "/dev/dri"sv }; + auto device_path = dri_path / file; + return open(device_path.c_str(), O_RDWR); + } + + BOOST_LOG(error) << "Unable to find DRM device with PCI bus ID: "sv << pci_bus_id; + return -1; + } + + class gl_cuda_vram_t: public platf::avcodec_encode_device_t { + public: + /** + * @brief Initialize the GL->CUDA encoding device. + * @param in_width Width of captured frames. + * @param in_height Height of captured frames. + * @param offset_x Offset of content in captured frame. + * @param offset_y Offset of content in captured frame. + * @return 0 on success or -1 on failure. + */ + int + init(int in_width, int in_height, int offset_x, int offset_y) { + // This must be non-zero to tell the video core that it's a hardware encoding device. + data = (void *) 0x1; + + // TODO: Support more than one CUDA device + file = std::move(open_drm_fd_for_cuda_device(0)); + if (file.el < 0) { + char string[1024]; + BOOST_LOG(error) << "Couldn't open DRM FD for CUDA device: "sv << strerror_r(errno, string, sizeof(string)); + return -1; + } + + gbm.reset(gbm::create_device(file.el)); + if (!gbm) { + BOOST_LOG(error) << "Couldn't create GBM device: ["sv << util::hex(eglGetError()).to_string_view() << ']'; + return -1; + } + + display = egl::make_display(gbm.get()); + if (!display) { + return -1; + } + + auto ctx_opt = egl::make_ctx(display.get()); + if (!ctx_opt) { + return -1; + } + + ctx = std::move(*ctx_opt); + + width = in_width; + height = in_height; + + sequence = 0; + + this->offset_x = offset_x; + this->offset_y = offset_y; + + return 0; + } + + /** + * @brief Initialize color conversion into target CUDA frame. + * @param frame Destination CUDA frame to write into. + * @param hw_frames_ctx_buf FFmpeg hardware frame context. + * @return 0 on success or -1 on failure. + */ + int + set_frame(AVFrame *frame, AVBufferRef *hw_frames_ctx_buf) override { + this->hwframe.reset(frame); + this->frame = frame; + + if (!frame->buf[0]) { + if (av_hwframe_get_buffer(hw_frames_ctx_buf, frame, 0)) { + BOOST_LOG(error) << "Couldn't get hwframe for VAAPI"sv; + return -1; + } + } + + auto hw_frames_ctx = (AVHWFramesContext *) hw_frames_ctx_buf->data; + sw_format = hw_frames_ctx->sw_format; + + auto nv12_opt = egl::create_target(frame->width, frame->height, sw_format); + if (!nv12_opt) { + return -1; + } + + auto sws_opt = egl::sws_t::make(width, height, frame->width, frame->height, sw_format); + if (!sws_opt) { + return -1; + } + + this->sws = std::move(*sws_opt); + this->nv12 = std::move(*nv12_opt); + + auto cuda_ctx = (AVCUDADeviceContext *) hw_frames_ctx->device_ctx->hwctx; + + stream = make_stream(); + if (!stream) { + return -1; + } + + cuda_ctx->stream = stream.get(); + + CU_CHECK(cdf->cuGraphicsGLRegisterImage(&y_res, nv12->tex[0], GL_TEXTURE_2D, CU_GRAPHICS_REGISTER_FLAGS_READ_ONLY), + "Couldn't register Y plane texture"); + CU_CHECK(cdf->cuGraphicsGLRegisterImage(&uv_res, nv12->tex[1], GL_TEXTURE_2D, CU_GRAPHICS_REGISTER_FLAGS_READ_ONLY), + "Couldn't register UV plane texture"); + + return 0; + } + + /** + * @brief Convert the captured image into the target CUDA frame. + * @param img Captured screen image. + * @return 0 on success or -1 on failure. + */ + int + convert(platf::img_t &img) override { + auto &descriptor = (egl::img_descriptor_t &) img; + + if (descriptor.sequence == 0) { + // For dummy images, use a blank RGB texture instead of importing a DMA-BUF + rgb = egl::create_blank(img); + } + else if (descriptor.sequence > sequence) { + sequence = descriptor.sequence; + + rgb = egl::rgb_t {}; + + auto rgb_opt = egl::import_source(display.get(), descriptor.sd); + + if (!rgb_opt) { + return -1; + } + + rgb = std::move(*rgb_opt); + } + + // Perform the color conversion and scaling in GL + sws.load_vram(descriptor, offset_x, offset_y, rgb->tex[0]); + sws.convert(nv12->buf); + + auto fmt_desc = av_pix_fmt_desc_get(sw_format); + + // Map the GL textures to read for CUDA + CUgraphicsResource resources[2] = { y_res.get(), uv_res.get() }; + CU_CHECK(cdf->cuGraphicsMapResources(2, resources, stream.get()), "Couldn't map GL textures in CUDA"); + + // Copy from the GL textures to the target CUDA frame + for (int i = 0; i < 2; i++) { + CUDA_MEMCPY2D cpy = {}; + cpy.srcMemoryType = CU_MEMORYTYPE_ARRAY; + CU_CHECK(cdf->cuGraphicsSubResourceGetMappedArray(&cpy.srcArray, resources[i], 0, 0), "Couldn't get mapped plane array"); + + cpy.dstMemoryType = CU_MEMORYTYPE_DEVICE; + cpy.dstDevice = (CUdeviceptr) frame->data[i]; + cpy.dstPitch = frame->linesize[i]; + cpy.WidthInBytes = (frame->width * fmt_desc->comp[i].step) >> (i ? fmt_desc->log2_chroma_w : 0); + cpy.Height = frame->height >> (i ? fmt_desc->log2_chroma_h : 0); + + CU_CHECK_IGNORE(cdf->cuMemcpy2DAsync(&cpy, stream.get()), "Couldn't copy texture to CUDA frame"); + } + + // Unmap the textures to allow modification from GL again + CU_CHECK(cdf->cuGraphicsUnmapResources(2, resources, stream.get()), "Couldn't unmap GL textures from CUDA"); + return 0; + } + + /** + * @brief Configures shader parameters for the specified colorspace. + */ + void + apply_colorspace() override { + sws.apply_colorspace(colorspace); + } + + file_t file; + gbm::gbm_t gbm; + egl::display_t display; + egl::ctx_t ctx; + + // This must be destroyed before display_t + stream_t stream; + frame_t hwframe; + + egl::sws_t sws; + egl::nv12_t nv12; + AVPixelFormat sw_format; + + int width, height; + + std::uint64_t sequence; + egl::rgb_t rgb; + + registered_resource_t y_res; + registered_resource_t uv_res; + + int offset_x, offset_y; + }; + std::unique_ptr make_avcodec_encode_device(int width, int height, bool vram) { if (init()) { @@ -245,6 +488,29 @@ namespace cuda { return cuda; } + /** + * @brief Create a GL->CUDA encoding device for consuming captured dmabufs. + * @param in_width Width of captured frames. + * @param in_height Height of captured frames. + * @param offset_x Offset of content in captured frame. + * @param offset_y Offset of content in captured frame. + * @return FFmpeg encoding device context. + */ + std::unique_ptr + make_avcodec_gl_encode_device(int width, int height, int offset_x, int offset_y) { + if (init()) { + return nullptr; + } + + auto cuda = std::make_unique(); + + if (cuda->init(width, height, offset_x, offset_y)) { + return nullptr; + } + + return cuda; + } + namespace nvfbc { static PNVFBCCREATEINSTANCE createInstance {}; static NVFBC_API_FUNCTION_LIST func { NVFBC_VERSION }; diff --git a/src/platform/linux/cuda.h b/src/platform/linux/cuda.h index d5b97d65051..91564174745 100644 --- a/src/platform/linux/cuda.h +++ b/src/platform/linux/cuda.h @@ -27,6 +27,18 @@ namespace cuda { } std::unique_ptr make_avcodec_encode_device(int width, int height, bool vram); + + /** + * @brief Create a GL->CUDA encoding device for consuming captured dmabufs. + * @param in_width Width of captured frames. + * @param in_height Height of captured frames. + * @param offset_x Offset of content in captured frame. + * @param offset_y Offset of content in captured frame. + * @return FFmpeg encoding device context. + */ + std::unique_ptr + make_avcodec_gl_encode_device(int width, int height, int offset_x, int offset_y); + int init(); } // namespace cuda diff --git a/src/platform/linux/graphics.cpp b/src/platform/linux/graphics.cpp index a91ee8e449b..e53483a9a75 100644 --- a/src/platform/linux/graphics.cpp +++ b/src/platform/linux/graphics.cpp @@ -647,6 +647,71 @@ namespace egl { return nv12; } + /** + * @brief Creates biplanar YUV textures to render into. + * @param width Width of the target frame. + * @param height Height of the target frame. + * @param format Format of the target frame. + * @return The new RGB texture. + */ + std::optional + create_target(int width, int height, AVPixelFormat format) { + nv12_t nv12 { + EGL_NO_DISPLAY, + EGL_NO_IMAGE, + EGL_NO_IMAGE, + gl::tex_t::make(2), + gl::frame_buf_t::make(2), + }; + + GLint y_format; + GLint uv_format; + + // Determine the size of each plane element + auto fmt_desc = av_pix_fmt_desc_get(format); + if (fmt_desc->comp[0].depth <= 8) { + y_format = GL_R8; + uv_format = GL_RG8; + } + else if (fmt_desc->comp[0].depth <= 16) { + y_format = GL_R16; + uv_format = GL_RG16; + } + else { + BOOST_LOG(error) << "Unsupported target pixel format: "sv << format; + return std::nullopt; + } + + gl::ctx.BindTexture(GL_TEXTURE_2D, nv12->tex[0]); + gl::ctx.TexStorage2D(GL_TEXTURE_2D, 1, y_format, width, height); + + gl::ctx.BindTexture(GL_TEXTURE_2D, nv12->tex[1]); + gl::ctx.TexStorage2D(GL_TEXTURE_2D, 1, uv_format, + width >> fmt_desc->log2_chroma_w, height >> fmt_desc->log2_chroma_h); + + nv12->buf.bind(std::begin(nv12->tex), std::end(nv12->tex)); + + GLenum attachments[] { + GL_COLOR_ATTACHMENT0, + GL_COLOR_ATTACHMENT1 + }; + + for (int x = 0; x < sizeof(attachments) / sizeof(decltype(attachments[0])); ++x) { + gl::ctx.BindFramebuffer(GL_FRAMEBUFFER, nv12->buf[x]); + gl::ctx.DrawBuffers(1, &attachments[x]); + + const float y_black[] = { 0.0f, 0.0f, 0.0f, 0.0f }; + const float uv_black[] = { 0.5f, 0.5f, 0.5f, 0.5f }; + gl::ctx.ClearBufferfv(GL_COLOR, 0, x == 0 ? y_black : uv_black); + } + + gl::ctx.BindFramebuffer(GL_FRAMEBUFFER, 0); + + gl_drain_errors; + + return nv12; + } + void sws_t::apply_colorspace(const video::sunshine_colorspace_t &colorspace) { auto color_p = video::color_vectors_from_colorspace(colorspace); diff --git a/src/platform/linux/graphics.h b/src/platform/linux/graphics.h index 9c0c3fb0b86..d2759f7d98b 100644 --- a/src/platform/linux/graphics.h +++ b/src/platform/linux/graphics.h @@ -277,6 +277,16 @@ namespace egl { std::array &&fds, const surface_descriptor_t &y, const surface_descriptor_t &uv); + /** + * @brief Creates biplanar YUV textures to render into. + * @param width Width of the target frame. + * @param height Height of the target frame. + * @param format Format of the target frame. + * @return The new RGB texture. + */ + std::optional + create_target(int width, int height, AVPixelFormat format); + class cursor_t: public platf::img_t { public: int x, y; diff --git a/src/platform/linux/kmsgrab.cpp b/src/platform/linux/kmsgrab.cpp index eb85a257ec4..069900f10de 100644 --- a/src/platform/linux/kmsgrab.cpp +++ b/src/platform/linux/kmsgrab.cpp @@ -20,6 +20,7 @@ #include "src/utility.h" #include "src/video.h" +#include "cuda.h" #include "graphics.h" #include "vaapi.h" #include "wayland.h" @@ -1192,6 +1193,12 @@ namespace platf { } #endif +#ifdef SUNSHINE_BUILD_CUDA + if (mem_type == mem_type_e::cuda) { + return cuda::make_avcodec_encode_device(width, height, false); + } +#endif + return std::make_unique(); } @@ -1315,6 +1322,12 @@ namespace platf { } #endif +#ifdef SUNSHINE_BUILD_CUDA + if (mem_type == mem_type_e::cuda) { + return cuda::make_avcodec_gl_encode_device(width, height, img_offset_x, img_offset_y); + } +#endif + BOOST_LOG(error) << "Unsupported pixel format for egl::display_vram_t: "sv << platf::from_pix_fmt(pix_fmt); return nullptr; } @@ -1434,13 +1447,18 @@ namespace platf { } #ifdef SUNSHINE_BUILD_VAAPI - if (!va::validate(card.render_fd.el)) { -#else - if (true) { -#endif + if (mem_type == mem_type_e::vaapi && !va::validate(card.render_fd.el)) { BOOST_LOG(warning) << "Monitor "sv << display_name << " doesn't support hardware encoding. Reverting back to GPU -> RAM -> GPU"sv; return -1; } +#endif + +#ifndef SUNSHINE_BUILD_CUDA + if (mem_type == mem_type_e::cuda) { + BOOST_LOG(warning) << "Attempting to use NVENC without CUDA support. Reverting back to GPU -> RAM -> GPU"sv; + return -1; + } +#endif return 0; } @@ -1452,7 +1470,7 @@ namespace platf { std::shared_ptr kms_display(mem_type_e hwdevice_type, const std::string &display_name, const ::video::config_t &config) { - if (hwdevice_type == mem_type_e::vaapi) { + if (hwdevice_type == mem_type_e::vaapi || hwdevice_type == mem_type_e::cuda) { auto disp = std::make_shared(hwdevice_type); if (!disp->init(display_name, config)) { diff --git a/src/platform/linux/wlgrab.cpp b/src/platform/linux/wlgrab.cpp index 24791d0d6b2..230667734dd 100644 --- a/src/platform/linux/wlgrab.cpp +++ b/src/platform/linux/wlgrab.cpp @@ -6,6 +6,8 @@ #include "src/main.h" #include "src/video.h" + +#include "cuda.h" #include "vaapi.h" #include "wayland.h" @@ -224,6 +226,12 @@ namespace wl { } #endif +#ifdef SUNSHINE_BUILD_CUDA + if (mem_type == platf::mem_type_e::cuda) { + return cuda::make_avcodec_encode_device(width, height, false); + } +#endif + return std::make_unique(); } @@ -336,6 +344,12 @@ namespace wl { } #endif +#ifdef SUNSHINE_BUILD_CUDA + if (mem_type == platf::mem_type_e::cuda) { + return cuda::make_avcodec_gl_encode_device(width, height, 0, 0); + } +#endif + return std::make_unique(); } @@ -358,7 +372,7 @@ namespace platf { return nullptr; } - if (hwdevice_type == platf::mem_type_e::vaapi) { + if (hwdevice_type == platf::mem_type_e::vaapi || hwdevice_type == platf::mem_type_e::cuda) { auto wlr = std::make_shared(); if (wlr->init(hwdevice_type, display_name, config)) { return nullptr; From cf416f55e10e6b13a4d333fc48f72ffd4780bfcf Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Sat, 27 Jan 2024 23:43:19 -0600 Subject: [PATCH 018/182] Fix KMS cursor capture on Nvidia GPUs --- src/platform/linux/kmsgrab.cpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/platform/linux/kmsgrab.cpp b/src/platform/linux/kmsgrab.cpp index 069900f10de..430644e453c 100644 --- a/src/platform/linux/kmsgrab.cpp +++ b/src/platform/linux/kmsgrab.cpp @@ -979,6 +979,20 @@ namespace platf { // We will map the entire region, but only copy what the source rectangle specifies size_t mapped_size = ((size_t) fb->pitches[0]) * fb->height; void *mapped_data = mmap(nullptr, mapped_size, PROT_READ, MAP_SHARED, plane_fd.el, fb->offsets[0]); + + // If we got ENOSYS back, let's try to map it as a dumb buffer instead (required for Nvidia GPUs) + if (mapped_data == MAP_FAILED && errno == ENOSYS) { + drm_mode_map_dumb map = {}; + map.handle = fb->handles[0]; + if (drmIoctl(card.fd.el, DRM_IOCTL_MODE_MAP_DUMB, &map) < 0) { + BOOST_LOG(error) << "Failed to map cursor FB as dumb buffer: "sv << strerror(errno); + captured_cursor.visible = false; + return; + } + + mapped_data = mmap(nullptr, mapped_size, PROT_READ, MAP_SHARED, card.fd.el, map.offset); + } + if (mapped_data == MAP_FAILED) { BOOST_LOG(error) << "Failed to mmap cursor FB: "sv << strerror(errno); captured_cursor.visible = false; From f70c3f1c0e24d05d0d5f0e6f0a40d48413074499 Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Sat, 20 Jan 2024 20:44:34 -0600 Subject: [PATCH 019/182] Fix crash on monitor index not found in KMS --- src/platform/linux/kmsgrab.cpp | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/platform/linux/kmsgrab.cpp b/src/platform/linux/kmsgrab.cpp index 430644e453c..c146f866529 100644 --- a/src/platform/linux/kmsgrab.cpp +++ b/src/platform/linux/kmsgrab.cpp @@ -727,13 +727,11 @@ namespace platf { } } + BOOST_LOG(error) << "Couldn't find monitor ["sv << monitor_index << ']'; + return -1; + // Neatly break from nested for loop break_loop: - if (monitor != monitor_index) { - BOOST_LOG(error) << "Couldn't find monitor ["sv << monitor_index << ']'; - - return -1; - } // Look for the cursor plane for this CRTC cursor_plane_id = -1; From f82c3cbc9980d9c5b3fd58ed964cce7f34d2d0a6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 30 Jan 2024 08:31:14 -0500 Subject: [PATCH 020/182] build(deps): bump furo from 2023.9.10 to 2024.1.29 (#2077) 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 ecdb40c475f..688a0a896ce 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,5 +1,5 @@ breathe==4.35.0 -furo==2023.9.10 +furo==2024.1.29 m2r2==0.3.3.post2 rstcheck[sphinx]==6.2.0 rstfmt==0.0.14 From 5e948dc835cff50262994de43f2ae3c46df8ef17 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Wed, 31 Jan 2024 19:59:11 -0500 Subject: [PATCH 021/182] docs(build): add missing libcurl build dep for ubuntu (#2081) --- docs/source/building/linux.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/source/building/linux.rst b/docs/source/building/linux.rst index fdd8be7f155..789409e4174 100644 --- a/docs/source/building/linux.rst +++ b/docs/source/building/linux.rst @@ -106,6 +106,7 @@ Install Requirements libboost-log-dev \ libboost-program-options-dev \ libcap-dev \ # KMS + libcurl4-openssl-dev \ libdrm-dev \ # KMS libevdev-dev \ libminiupnpc-dev \ @@ -156,6 +157,7 @@ Install Requirements libboost-log-dev \ libboost-program-options-dev \ libcap-dev \ # KMS + libcurl4-openssl-dev \ libdrm-dev \ # KMS libevdev-dev \ libminiupnpc-dev \ From 190ea41b2ea04ff1ddfbe44ea4459424a87c7d39 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 1 Feb 2024 07:31:09 +0000 Subject: [PATCH 022/182] build(deps): bump third-party/moonlight-common-c Bumps [third-party/moonlight-common-c](https://github.com/moonlight-stream/moonlight-common-c) from `298f356` to `3acba57`. - [Commits](https://github.com/moonlight-stream/moonlight-common-c/compare/298f356acbb57f56863680d41c0d307a2fd5cb91...3acba578b19c14a23f58a5f2488c23e5c19ac637) --- updated-dependencies: - dependency-name: third-party/moonlight-common-c dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- third-party/moonlight-common-c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/third-party/moonlight-common-c b/third-party/moonlight-common-c index 298f356acbb..3acba578b19 160000 --- a/third-party/moonlight-common-c +++ b/third-party/moonlight-common-c @@ -1 +1 @@ -Subproject commit 298f356acbb57f56863680d41c0d307a2fd5cb91 +Subproject commit 3acba578b19c14a23f58a5f2488c23e5c19ac637 From fe8b0bad926f126e68e040deaf98ba6a0d9972c8 Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Thu, 1 Feb 2024 18:04:55 -0600 Subject: [PATCH 023/182] Implement workaround for qWAVE issues handling IPv4-mapped IPv6 addresses --- src/platform/windows/misc.cpp | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/src/platform/windows/misc.cpp b/src/platform/windows/misc.cpp index 71455837656..888b9e40f8d 100644 --- a/src/platform/windows/misc.cpp +++ b/src/platform/windows/misc.cpp @@ -1214,6 +1214,7 @@ namespace platf { SOCKADDR_IN saddr_v4; SOCKADDR_IN6 saddr_v6; PSOCKADDR dest_addr; + bool using_connect_hack = false; static std::once_flag load_qwave_once_flag; std::call_once(load_qwave_once_flag, []() { @@ -1252,9 +1253,40 @@ namespace platf { return nullptr; } + auto disconnect_fg = util::fail_guard([&]() { + if (using_connect_hack) { + SOCKADDR_IN6 empty = {}; + empty.sin6_family = AF_INET6; + if (connect((SOCKET) native_socket, (PSOCKADDR) &empty, sizeof(empty)) < 0) { + auto wsaerr = WSAGetLastError(); + BOOST_LOG(error) << "qWAVE dual-stack workaround failed: "sv << wsaerr; + } + } + }); + if (address.is_v6()) { - saddr_v6 = to_sockaddr(address.to_v6(), port); + auto address_v6 = address.to_v6(); + + saddr_v6 = to_sockaddr(address_v6, port); dest_addr = (PSOCKADDR) &saddr_v6; + + // qWAVE doesn't properly support IPv4-mapped IPv6 addresses, nor does it + // correctly support IPv4 addresses on a dual-stack socket (despite MSDN's + // claims to the contrary). To get proper QoS tagging when hosting in dual + // stack mode, we will temporarily connect() the socket to allow qWAVE to + // successfully initialize a flow, then disconnect it again so WSASendMsg() + // works later on. + if (address_v6.is_v4_mapped()) { + if (connect((SOCKET) native_socket, (PSOCKADDR) &saddr_v6, sizeof(saddr_v6)) < 0) { + auto wsaerr = WSAGetLastError(); + BOOST_LOG(error) << "qWAVE dual-stack workaround failed: "sv << wsaerr; + } + else { + BOOST_LOG(debug) << "Using qWAVE connect() workaround for QoS tagging"sv; + using_connect_hack = true; + dest_addr = nullptr; + } + } } else { saddr_v4 = to_sockaddr(address.to_v4(), port); From c7700f96fc3119b1e3cf81582419493455191899 Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Thu, 1 Feb 2024 18:05:36 -0600 Subject: [PATCH 024/182] Enable opportunistic QoS tagging for control messages --- src/network.cpp | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/network.cpp b/src/network.cpp index 5778c8db9ad..2a3d47fc1e1 100644 --- a/src/network.cpp +++ b/src/network.cpp @@ -176,11 +176,21 @@ namespace net { host_t host_create(af_e af, ENetAddress &addr, std::size_t peers, std::uint16_t port) { + static std::once_flag enet_init_flag; + std::call_once(enet_init_flag, []() { + enet_initialize(); + }); + auto any_addr = net::af_to_any_address_string(af); enet_address_set_host(&addr, any_addr.data()); enet_address_set_port(&addr, port); - return host_t { enet_host_create(af == IPV4 ? AF_INET : AF_INET6, &addr, peers, 0, 0, 0) }; + auto host = host_t { enet_host_create(af == IPV4 ? AF_INET : AF_INET6, &addr, peers, 0, 0, 0) }; + + // Enable opportunistic QoS tagging (automatically disables if the network appears to drop tagged packets) + enet_socket_set_option(host->socket, ENET_SOCKOPT_QOS, 1); + + return host; } void From 5c9533f6d71c44f9be734a309f1d553e5b317e01 Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Thu, 1 Feb 2024 18:12:05 -0600 Subject: [PATCH 025/182] Allow DSCP tagging and local traffic prioritization to be enabled separately on Mac and Linux --- src/platform/common.h | 11 +++- src/platform/linux/misc.cpp | 105 ++++++++++++++++++++++------------ src/platform/macos/misc.mm | 101 ++++++++++++++++++++++++++++++-- src/platform/windows/misc.cpp | 15 ++++- src/stream.cpp | 20 +++---- 5 files changed, 197 insertions(+), 55 deletions(-) diff --git a/src/platform/common.h b/src/platform/common.h index ab0d085fc9a..3c21e181e51 100644 --- a/src/platform/common.h +++ b/src/platform/common.h @@ -608,8 +608,17 @@ namespace platf { audio, video }; + + /** + * @brief Enables QoS on the given socket for traffic to the specified destination. + * @param native_socket The native socket handle. + * @param address The destination address for traffic sent on this socket. + * @param port The destination port for traffic sent on this socket. + * @param data_type The type of traffic sent on this socket. + * @param dscp_tagging Specifies whether to enable DSCP tagging on outgoing traffic. + */ std::unique_ptr - enable_socket_qos(uintptr_t native_socket, boost::asio::ip::address &address, uint16_t port, qos_data_type_e data_type); + enable_socket_qos(uintptr_t native_socket, boost::asio::ip::address &address, uint16_t port, qos_data_type_e data_type, bool dscp_tagging); /** * @brief Open a url in the default web browser. diff --git a/src/platform/linux/misc.cpp b/src/platform/linux/misc.cpp index 27697c2dc9b..2d82d387d94 100644 --- a/src/platform/linux/misc.cpp +++ b/src/platform/linux/misc.cpp @@ -586,59 +586,92 @@ namespace platf { class qos_t: public deinit_t { public: - qos_t(int sockfd, int level, int option): - sockfd(sockfd), level(level), option(option) {} + qos_t(int sockfd, std::vector> options): + sockfd(sockfd), options(options) {} virtual ~qos_t() { - int reset_val = -1; - if (setsockopt(sockfd, level, option, &reset_val, sizeof(reset_val)) < 0) { - BOOST_LOG(warning) << "Failed to reset IP TOS: "sv << errno; + for (const auto &tuple : options) { + auto reset_val = std::get<2>(tuple); + if (setsockopt(sockfd, std::get<0>(tuple), std::get<1>(tuple), &reset_val, sizeof(reset_val)) < 0) { + BOOST_LOG(warning) << "Failed to reset option: "sv << errno; + } } } private: int sockfd; - int level; - int option; + std::vector> options; }; + /** + * @brief Enables QoS on the given socket for traffic to the specified destination. + * @param native_socket The native socket handle. + * @param address The destination address for traffic sent on this socket. + * @param port The destination port for traffic sent on this socket. + * @param data_type The type of traffic sent on this socket. + * @param dscp_tagging Specifies whether to enable DSCP tagging on outgoing traffic. + */ std::unique_ptr - enable_socket_qos(uintptr_t native_socket, boost::asio::ip::address &address, uint16_t port, qos_data_type_e data_type) { + enable_socket_qos(uintptr_t native_socket, boost::asio::ip::address &address, uint16_t port, qos_data_type_e data_type, bool dscp_tagging) { int sockfd = (int) native_socket; + std::vector> reset_options; + + if (dscp_tagging) { + int level; + int option; + if (address.is_v6()) { + level = SOL_IPV6; + option = IPV6_TCLASS; + } + else { + level = SOL_IP; + option = IP_TOS; + } - int level; - int option; - if (address.is_v6()) { - level = SOL_IPV6; - option = IPV6_TCLASS; - } - else { - level = SOL_IP; - option = IP_TOS; - } + // The specific DSCP values here are chosen to be consistent with Windows + int dscp = 0; + switch (data_type) { + case qos_data_type_e::video: + dscp = 40; + break; + case qos_data_type_e::audio: + dscp = 56; + break; + default: + BOOST_LOG(error) << "Unknown traffic type: "sv << (int) data_type; + break; + } - // The specific DSCP values here are chosen to be consistent with Windows - int dscp; - switch (data_type) { - case qos_data_type_e::video: - dscp = 40; - break; - case qos_data_type_e::audio: - dscp = 56; - break; - default: - BOOST_LOG(error) << "Unknown traffic type: "sv << (int) data_type; - return nullptr; - } + if (dscp) { + // Shift to put the DSCP value in the correct position in the TOS field + dscp <<= 2; - // Shift to put the DSCP value in the correct position in the TOS field - dscp <<= 2; + if (setsockopt(sockfd, level, option, &dscp, sizeof(dscp)) == 0) { + // Reset TOS to -1 when QoS is disabled + reset_options.emplace_back(std::make_tuple(level, option, -1)); + } + else { + BOOST_LOG(error) << "Failed to set TOS/TCLASS: "sv << errno; + } + } + } - if (setsockopt(sockfd, level, option, &dscp, sizeof(dscp)) < 0) { - return nullptr; + // We can use SO_PRIORITY to set outgoing traffic priority without DSCP tagging. + // + // NB: We set this after IP_TOS/IPV6_TCLASS since setting TOS value seems to + // reset SO_PRIORITY back to 0. + // + // 6 is the highest priority that can be used without SYS_CAP_ADMIN. + int priority = data_type == qos_data_type_e::audio ? 6 : 5; + if (setsockopt(sockfd, SOL_SOCKET, SO_PRIORITY, &priority, sizeof(priority)) == 0) { + // Reset SO_PRIORITY to 0 when QoS is disabled + reset_options.emplace_back(std::make_tuple(SOL_SOCKET, SO_PRIORITY, 0)); + } + else { + BOOST_LOG(error) << "Failed to set SO_PRIORITY: "sv << errno; } - return std::make_unique(sockfd, level, option); + return std::make_unique(sockfd, reset_options); } namespace source { diff --git a/src/platform/macos/misc.mm b/src/platform/macos/misc.mm index 436763a00d3..5c7fca982b4 100644 --- a/src/platform/macos/misc.mm +++ b/src/platform/macos/misc.mm @@ -407,12 +407,103 @@ return true; } + class qos_t: public deinit_t { + public: + qos_t(int sockfd, std::vector> options): + sockfd(sockfd), options(options) {} + + virtual ~qos_t() { + for (const auto &tuple : options) { + auto reset_val = std::get<2>(tuple); + if (setsockopt(sockfd, std::get<0>(tuple), std::get<1>(tuple), &reset_val, sizeof(reset_val)) < 0) { + BOOST_LOG(warning) << "Failed to reset option: "sv << errno; + } + } + } + + private: + int sockfd; + std::vector> options; + }; + + /** + * @brief Enables QoS on the given socket for traffic to the specified destination. + * @param native_socket The native socket handle. + * @param address The destination address for traffic sent on this socket. + * @param port The destination port for traffic sent on this socket. + * @param data_type The type of traffic sent on this socket. + * @param dscp_tagging Specifies whether to enable DSCP tagging on outgoing traffic. + */ std::unique_ptr - enable_socket_qos(uintptr_t native_socket, boost::asio::ip::address &address, uint16_t port, qos_data_type_e data_type) { - // Unimplemented - // - // NB: When implementing, remember to consider that some routes can drop DSCP-tagged packets completely! - return nullptr; + enable_socket_qos(uintptr_t native_socket, boost::asio::ip::address &address, uint16_t port, qos_data_type_e data_type, bool dscp_tagging) { + int sockfd = (int) native_socket; + std::vector> reset_options; + + // We can use SO_NET_SERVICE_TYPE to set link-layer prioritization without DSCP tagging + int service_type = 0; + switch (data_type) { + case qos_data_type_e::video: + service_type = NET_SERVICE_TYPE_VI; + break; + case qos_data_type_e::audio: + service_type = NET_SERVICE_TYPE_VO; + break; + default: + BOOST_LOG(error) << "Unknown traffic type: "sv << (int) data_type; + break; + } + + if (service_type) { + if (setsockopt(sockfd, SOL_SOCKET, SO_NET_SERVICE_TYPE, &service_type, sizeof(service_type)) == 0) { + // Reset SO_NET_SERVICE_TYPE to best-effort when QoS is disabled + reset_options.emplace_back(std::make_tuple(SOL_SOCKET, SO_NET_SERVICE_TYPE, NET_SERVICE_TYPE_BE)); + } + else { + BOOST_LOG(error) << "Failed to set SO_NET_SERVICE_TYPE: "sv << errno; + } + } + + if (dscp_tagging) { + int level; + int option; + if (address.is_v6()) { + level = IPPROTO_IPV6; + option = IPV6_TCLASS; + } + else { + level = IPPROTO_IP; + option = IP_TOS; + } + + // The specific DSCP values here are chosen to be consistent with Windows + int dscp = 0; + switch (data_type) { + case qos_data_type_e::video: + dscp = 40; + break; + case qos_data_type_e::audio: + dscp = 56; + break; + default: + BOOST_LOG(error) << "Unknown traffic type: "sv << (int) data_type; + break; + } + + if (dscp) { + // Shift to put the DSCP value in the correct position in the TOS field + dscp <<= 2; + + if (setsockopt(sockfd, level, option, &dscp, sizeof(dscp)) == 0) { + // Reset TOS to -1 when QoS is disabled + reset_options.emplace_back(std::make_tuple(level, option, -1)); + } + else { + BOOST_LOG(error) << "Failed to set TOS/TCLASS: "sv << errno; + } + } + } + + return std::make_unique(sockfd, reset_options); } } // namespace platf diff --git a/src/platform/windows/misc.cpp b/src/platform/windows/misc.cpp index 888b9e40f8d..ec57355470d 100644 --- a/src/platform/windows/misc.cpp +++ b/src/platform/windows/misc.cpp @@ -1209,13 +1209,26 @@ namespace platf { QOS_FLOWID flow_id; }; + /** + * @brief Enables QoS on the given socket for traffic to the specified destination. + * @param native_socket The native socket handle. + * @param address The destination address for traffic sent on this socket. + * @param port The destination port for traffic sent on this socket. + * @param data_type The type of traffic sent on this socket. + * @param dscp_tagging Specifies whether to enable DSCP tagging on outgoing traffic. + */ std::unique_ptr - enable_socket_qos(uintptr_t native_socket, boost::asio::ip::address &address, uint16_t port, qos_data_type_e data_type) { + enable_socket_qos(uintptr_t native_socket, boost::asio::ip::address &address, uint16_t port, qos_data_type_e data_type, bool dscp_tagging) { SOCKADDR_IN saddr_v4; SOCKADDR_IN6 saddr_v6; PSOCKADDR dest_addr; bool using_connect_hack = false; + // Windows doesn't support any concept of traffic priority without DSCP tagging + if (!dscp_tagging) { + return nullptr; + } + static std::once_flag load_qwave_once_flag; std::call_once(load_qwave_once_flag, []() { // qWAVE is not installed by default on Windows Server, so we load it dynamically diff --git a/src/stream.cpp b/src/stream.cpp index eefccdc740f..41f80e2e331 100644 --- a/src/stream.cpp +++ b/src/stream.cpp @@ -1740,12 +1740,10 @@ namespace stream { return; } - // Enable QoS tagging on video traffic if requested by the client - if (session->config.videoQosType) { - auto address = session->video.peer.address(); - session->video.qos = platf::enable_socket_qos(ref->video_sock.native_handle(), address, - session->video.peer.port(), platf::qos_data_type_e::video); - } + // Enable local prioritization and QoS tagging on video traffic if requested by the client + auto address = session->video.peer.address(); + session->video.qos = platf::enable_socket_qos(ref->video_sock.native_handle(), address, + session->video.peer.port(), platf::qos_data_type_e::video, session->config.videoQosType != 0); BOOST_LOG(debug) << "Start capturing Video"sv; video::capture(session->mail, session->config.monitor, session); @@ -1765,12 +1763,10 @@ namespace stream { return; } - // Enable QoS tagging on audio traffic if requested by the client - if (session->config.audioQosType) { - auto address = session->audio.peer.address(); - session->audio.qos = platf::enable_socket_qos(ref->audio_sock.native_handle(), address, - session->audio.peer.port(), platf::qos_data_type_e::audio); - } + // Enable local prioritization and QoS tagging on audio traffic if requested by the client + auto address = session->audio.peer.address(); + session->audio.qos = platf::enable_socket_qos(ref->audio_sock.native_handle(), address, + session->audio.peer.port(), platf::qos_data_type_e::audio, session->config.audioQosType != 0); BOOST_LOG(debug) << "Start capturing Audio"sv; audio::capture(session->mail, session->config.audio, session); From bb3b7984f32f7206a63ba54ff5c3b01729ac6ea4 Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Thu, 1 Feb 2024 18:17:12 -0600 Subject: [PATCH 026/182] Add refcounting to Mac and Linux QoS state to ensure it works properly with multiple clients This means we can't control DSCP tagging per-client, but it shouldn't pose a big problem as routers that blackhole DSCP tagged traffic are pretty rare. --- src/platform/linux/misc.cpp | 19 ++++++++++++++----- src/platform/macos/misc.mm | 19 ++++++++++++++----- 2 files changed, 28 insertions(+), 10 deletions(-) diff --git a/src/platform/linux/misc.cpp b/src/platform/linux/misc.cpp index 2d82d387d94..bf2f616977c 100644 --- a/src/platform/linux/misc.cpp +++ b/src/platform/linux/misc.cpp @@ -584,16 +584,25 @@ namespace platf { return true; } + // We can't track QoS state separately for each destination on this OS, + // so we keep a ref count to only disable QoS options when all clients + // are disconnected. + static std::atomic qos_ref_count = 0; + class qos_t: public deinit_t { public: qos_t(int sockfd, std::vector> options): - sockfd(sockfd), options(options) {} + sockfd(sockfd), options(options) { + qos_ref_count++; + } virtual ~qos_t() { - for (const auto &tuple : options) { - auto reset_val = std::get<2>(tuple); - if (setsockopt(sockfd, std::get<0>(tuple), std::get<1>(tuple), &reset_val, sizeof(reset_val)) < 0) { - BOOST_LOG(warning) << "Failed to reset option: "sv << errno; + if (--qos_ref_count == 0) { + for (const auto &tuple : options) { + auto reset_val = std::get<2>(tuple); + if (setsockopt(sockfd, std::get<0>(tuple), std::get<1>(tuple), &reset_val, sizeof(reset_val)) < 0) { + BOOST_LOG(warning) << "Failed to reset option: "sv << errno; + } } } } diff --git a/src/platform/macos/misc.mm b/src/platform/macos/misc.mm index 5c7fca982b4..0a36e33f131 100644 --- a/src/platform/macos/misc.mm +++ b/src/platform/macos/misc.mm @@ -407,16 +407,25 @@ return true; } + // We can't track QoS state separately for each destination on this OS, + // so we keep a ref count to only disable QoS options when all clients + // are disconnected. + static std::atomic qos_ref_count = 0; + class qos_t: public deinit_t { public: qos_t(int sockfd, std::vector> options): - sockfd(sockfd), options(options) {} + sockfd(sockfd), options(options) { + qos_ref_count++; + } virtual ~qos_t() { - for (const auto &tuple : options) { - auto reset_val = std::get<2>(tuple); - if (setsockopt(sockfd, std::get<0>(tuple), std::get<1>(tuple), &reset_val, sizeof(reset_val)) < 0) { - BOOST_LOG(warning) << "Failed to reset option: "sv << errno; + if (--qos_ref_count == 0) { + for (const auto &tuple : options) { + auto reset_val = std::get<2>(tuple); + if (setsockopt(sockfd, std::get<0>(tuple), std::get<1>(tuple), &reset_val, sizeof(reset_val)) < 0) { + BOOST_LOG(warning) << "Failed to reset option: "sv << errno; + } } } } From 2008bc0eaaea5636f7b1a53587af16fe9f1c17b7 Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Thu, 1 Feb 2024 21:07:15 -0600 Subject: [PATCH 027/182] Use CS6 instead of CS7 for audio traffic CS7 is reserved for network control traffic. --- src/platform/linux/misc.cpp | 5 +++-- src/platform/macos/misc.mm | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/platform/linux/misc.cpp b/src/platform/linux/misc.cpp index bf2f616977c..7bd38d6d960 100644 --- a/src/platform/linux/misc.cpp +++ b/src/platform/linux/misc.cpp @@ -637,14 +637,15 @@ namespace platf { option = IP_TOS; } - // The specific DSCP values here are chosen to be consistent with Windows + // The specific DSCP values here are chosen to be consistent with Windows, + // except that we use CS6 instead of CS7 for audio traffic. int dscp = 0; switch (data_type) { case qos_data_type_e::video: dscp = 40; break; case qos_data_type_e::audio: - dscp = 56; + dscp = 48; break; default: BOOST_LOG(error) << "Unknown traffic type: "sv << (int) data_type; diff --git a/src/platform/macos/misc.mm b/src/platform/macos/misc.mm index 0a36e33f131..2cfa2970163 100644 --- a/src/platform/macos/misc.mm +++ b/src/platform/macos/misc.mm @@ -484,14 +484,15 @@ option = IP_TOS; } - // The specific DSCP values here are chosen to be consistent with Windows + // The specific DSCP values here are chosen to be consistent with Windows, + // except that we use CS6 instead of CS7 for audio traffic. int dscp = 0; switch (data_type) { case qos_data_type_e::video: dscp = 40; break; case qos_data_type_e::audio: - dscp = 56; + dscp = 48; break; default: BOOST_LOG(error) << "Unknown traffic type: "sv << (int) data_type; From 328a30055006f55b0b63166f770b56717eb35aa4 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sat, 3 Feb 2024 15:29:09 -0500 Subject: [PATCH 028/182] build(cmake): use CMAKE_SOURCE_DIR property (#2091) --- CMakeLists.txt | 2 +- cmake/compile_definitions/common.cmake | 124 ++++++++++++------------ cmake/compile_definitions/linux.cmake | 58 +++++------ cmake/compile_definitions/macos.cmake | 34 +++---- cmake/compile_definitions/windows.cmake | 48 ++++----- cmake/dependencies/common.cmake | 46 ++++----- cmake/packaging/windows.cmake | 2 +- cmake/prep/build_version.cmake | 10 +- cmake/prep/constants.cmake | 2 +- cmake/targets/common.cmake | 2 +- tools/CMakeLists.txt | 3 +- 11 files changed, 165 insertions(+), 166 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 9c21b380ab9..53d8bde2881 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,7 +20,7 @@ if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) endif() # set the module path, used for includes -set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake) +set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake") # set version info for this build include(${CMAKE_MODULE_PATH}/prep/build_version.cmake) diff --git a/cmake/compile_definitions/common.cmake b/cmake/compile_definitions/common.cmake index a2ae3948ec1..b523911821f 100644 --- a/cmake/compile_definitions/common.cmake +++ b/cmake/compile_definitions/common.cmake @@ -23,70 +23,70 @@ elseif(UNIX) endif() endif() -include_directories(SYSTEM third-party/nv-codec-headers/include) +include_directories(SYSTEM "${CMAKE_SOURCE_DIR}/third-party/nv-codec-headers/include") file(GLOB NVENC_SOURCES CONFIGURE_DEPENDS "src/nvenc/*.cpp" "src/nvenc/*.h") list(APPEND PLATFORM_TARGET_FILES ${NVENC_SOURCES}) -configure_file(src/version.h.in version.h @ONLY) -include_directories(${CMAKE_CURRENT_BINARY_DIR}) +configure_file("${CMAKE_SOURCE_DIR}/src/version.h.in" version.h @ONLY) +include_directories("${CMAKE_CURRENT_BINARY_DIR}") set(SUNSHINE_TARGET_FILES - third-party/nanors/rs.c - third-party/nanors/rs.h - third-party/moonlight-common-c/src/Input.h - third-party/moonlight-common-c/src/Rtsp.h - third-party/moonlight-common-c/src/RtspParser.c - third-party/moonlight-common-c/src/Video.h - third-party/tray/tray.h - src/upnp.cpp - src/upnp.h - src/cbs.cpp - src/utility.h - src/uuid.h - src/config.h - src/config.cpp - src/main.cpp - src/main.h - src/crypto.cpp - src/crypto.h - src/nvhttp.cpp - src/nvhttp.h - src/httpcommon.cpp - src/httpcommon.h - src/confighttp.cpp - src/confighttp.h - src/rtsp.cpp - src/rtsp.h - src/stream.cpp - src/stream.h - src/video.cpp - src/video.h - src/video_colorspace.cpp - src/video_colorspace.h - src/input.cpp - src/input.h - src/audio.cpp - src/audio.h - src/platform/common.h - src/process.cpp - src/process.h - src/network.cpp - src/network.h - src/move_by_copy.h - src/system_tray.cpp - src/system_tray.h - src/task_pool.h - src/thread_pool.h - src/thread_safe.h - src/sync.h - src/round_robin.h - src/stat_trackers.h - src/stat_trackers.cpp + "${CMAKE_SOURCE_DIR}/third-party/nanors/rs.c" + "${CMAKE_SOURCE_DIR}/third-party/nanors/rs.h" + "${CMAKE_SOURCE_DIR}/third-party/moonlight-common-c/src/Input.h" + "${CMAKE_SOURCE_DIR}/third-party/moonlight-common-c/src/Rtsp.h" + "${CMAKE_SOURCE_DIR}/third-party/moonlight-common-c/src/RtspParser.c" + "${CMAKE_SOURCE_DIR}/third-party/moonlight-common-c/src/Video.h" + "${CMAKE_SOURCE_DIR}/third-party/tray/tray.h" + "${CMAKE_SOURCE_DIR}/src/upnp.cpp" + "${CMAKE_SOURCE_DIR}/src/upnp.h" + "${CMAKE_SOURCE_DIR}/src/cbs.cpp" + "${CMAKE_SOURCE_DIR}/src/utility.h" + "${CMAKE_SOURCE_DIR}/src/uuid.h" + "${CMAKE_SOURCE_DIR}/src/config.h" + "${CMAKE_SOURCE_DIR}/src/config.cpp" + "${CMAKE_SOURCE_DIR}/src/main.cpp" + "${CMAKE_SOURCE_DIR}/src/main.h" + "${CMAKE_SOURCE_DIR}/src/crypto.cpp" + "${CMAKE_SOURCE_DIR}/src/crypto.h" + "${CMAKE_SOURCE_DIR}/src/nvhttp.cpp" + "${CMAKE_SOURCE_DIR}/src/nvhttp.h" + "${CMAKE_SOURCE_DIR}/src/httpcommon.cpp" + "${CMAKE_SOURCE_DIR}/src/httpcommon.h" + "${CMAKE_SOURCE_DIR}/src/confighttp.cpp" + "${CMAKE_SOURCE_DIR}/src/confighttp.h" + "${CMAKE_SOURCE_DIR}/src/rtsp.cpp" + "${CMAKE_SOURCE_DIR}/src/rtsp.h" + "${CMAKE_SOURCE_DIR}/src/stream.cpp" + "${CMAKE_SOURCE_DIR}/src/stream.h" + "${CMAKE_SOURCE_DIR}/src/video.cpp" + "${CMAKE_SOURCE_DIR}/src/video.h" + "${CMAKE_SOURCE_DIR}/src/video_colorspace.cpp" + "${CMAKE_SOURCE_DIR}/src/video_colorspace.h" + "${CMAKE_SOURCE_DIR}/src/input.cpp" + "${CMAKE_SOURCE_DIR}/src/input.h" + "${CMAKE_SOURCE_DIR}/src/audio.cpp" + "${CMAKE_SOURCE_DIR}/src/audio.h" + "${CMAKE_SOURCE_DIR}/src/platform/common.h" + "${CMAKE_SOURCE_DIR}/src/process.cpp" + "${CMAKE_SOURCE_DIR}/src/process.h" + "${CMAKE_SOURCE_DIR}/src/network.cpp" + "${CMAKE_SOURCE_DIR}/src/network.h" + "${CMAKE_SOURCE_DIR}/src/move_by_copy.h" + "${CMAKE_SOURCE_DIR}/src/system_tray.cpp" + "${CMAKE_SOURCE_DIR}/src/system_tray.h" + "${CMAKE_SOURCE_DIR}/src/task_pool.h" + "${CMAKE_SOURCE_DIR}/src/thread_pool.h" + "${CMAKE_SOURCE_DIR}/src/thread_safe.h" + "${CMAKE_SOURCE_DIR}/src/sync.h" + "${CMAKE_SOURCE_DIR}/src/round_robin.h" + "${CMAKE_SOURCE_DIR}/src/stat_trackers.h" + "${CMAKE_SOURCE_DIR}/src/stat_trackers.cpp" ${PLATFORM_TARGET_FILES}) -set_source_files_properties(src/upnp.cpp PROPERTIES COMPILE_FLAGS -Wno-pedantic) +set_source_files_properties("${CMAKE_SOURCE_DIR}/src/upnp.cpp" PROPERTIES COMPILE_FLAGS -Wno-pedantic) -set_source_files_properties(third-party/nanors/rs.c +set_source_files_properties("${CMAKE_SOURCE_DIR}/third-party/nanors/rs.c" PROPERTIES COMPILE_FLAGS "-include deps/obl/autoshim.h -ftree-vectorize") if(NOT SUNSHINE_ASSETS_DIR_DEF) @@ -96,14 +96,14 @@ list(APPEND SUNSHINE_DEFINITIONS SUNSHINE_ASSETS_DIR="${SUNSHINE_ASSETS_DIR_DEF} list(APPEND SUNSHINE_DEFINITIONS SUNSHINE_TRAY=${SUNSHINE_TRAY}) -include_directories(${CMAKE_CURRENT_SOURCE_DIR}) +include_directories("${CMAKE_SOURCE_DIR}") include_directories( SYSTEM - ${CMAKE_CURRENT_SOURCE_DIR}/third-party - ${CMAKE_CURRENT_SOURCE_DIR}/third-party/moonlight-common-c/enet/include - ${CMAKE_CURRENT_SOURCE_DIR}/third-party/nanors - ${CMAKE_CURRENT_SOURCE_DIR}/third-party/nanors/deps/obl + "${CMAKE_SOURCE_DIR}/third-party" + "${CMAKE_SOURCE_DIR}/third-party/moonlight-common-c/enet/include" + "${CMAKE_SOURCE_DIR}/third-party/nanors" + "${CMAKE_SOURCE_DIR}/third-party/nanors/deps/obl" ${FFMPEG_INCLUDE_DIRS} ${PLATFORM_INCLUDE_DIRS} ) @@ -111,7 +111,7 @@ include_directories( string(TOUPPER "x${CMAKE_BUILD_TYPE}" BUILD_TYPE) if("${BUILD_TYPE}" STREQUAL "XDEBUG") if(WIN32) - set_source_files_properties(src/nvhttp.cpp PROPERTIES COMPILE_FLAGS -O2) + set_source_files_properties("${CMAKE_SOURCE_DIR}/src/nvhttp.cpp" PROPERTIES COMPILE_FLAGS -O2) endif() else() add_definitions(-DNDEBUG) diff --git a/cmake/compile_definitions/linux.cmake b/cmake/compile_definitions/linux.cmake index d4ebb597312..613a090947d 100644 --- a/cmake/compile_definitions/linux.cmake +++ b/cmake/compile_definitions/linux.cmake @@ -89,12 +89,12 @@ if(${SUNSHINE_ENABLE_CUDA}) endif() endif() if(CUDA_FOUND) - include_directories(SYSTEM third-party/nvfbc) + include_directories(SYSTEM "${CMAKE_SOURCE_DIR}/third-party/nvfbc") list(APPEND PLATFORM_TARGET_FILES - src/platform/linux/cuda.h - src/platform/linux/cuda.cu - src/platform/linux/cuda.cpp - third-party/nvfbc/NvFBC.h) + "${CMAKE_SOURCE_DIR}/src/platform/linux/cuda.h" + "${CMAKE_SOURCE_DIR}/src/platform/linux/cuda.cu" + "${CMAKE_SOURCE_DIR}/src/platform/linux/cuda.cpp" + "${CMAKE_SOURCE_DIR}/third-party/nvfbc/NvFBC.h") add_compile_definitions(SUNSHINE_BUILD_CUDA) endif() @@ -112,7 +112,7 @@ if(LIBDRM_FOUND AND LIBCAP_FOUND) include_directories(SYSTEM ${LIBDRM_INCLUDE_DIRS} ${LIBCAP_INCLUDE_DIRS}) list(APPEND PLATFORM_LIBRARIES ${LIBDRM_LIBRARIES} ${LIBCAP_LIBRARIES}) list(APPEND PLATFORM_TARGET_FILES - src/platform/linux/kmsgrab.cpp) + "${CMAKE_SOURCE_DIR}/src/platform/linux/kmsgrab.cpp") list(APPEND SUNSHINE_DEFINITIONS EGL_NO_X11=1) elseif(NOT LIBDRM_FOUND) message(WARNING "Missing libdrm") @@ -131,8 +131,8 @@ if(LIBVA_FOUND) include_directories(SYSTEM ${LIBVA_INCLUDE_DIR}) list(APPEND PLATFORM_LIBRARIES ${LIBVA_LIBRARIES} ${LIBVA_DRM_LIBRARIES}) list(APPEND PLATFORM_TARGET_FILES - src/platform/linux/vaapi.h - src/platform/linux/vaapi.cpp) + "${CMAKE_SOURCE_DIR}/src/platform/linux/vaapi.h" + "${CMAKE_SOURCE_DIR}/src/platform/linux/vaapi.cpp") endif() # wayland @@ -162,9 +162,9 @@ if(WAYLAND_FOUND) list(APPEND PLATFORM_LIBRARIES ${WAYLAND_LIBRARIES}) list(APPEND PLATFORM_TARGET_FILES - src/platform/linux/wlgrab.cpp - src/platform/linux/wayland.h - src/platform/linux/wayland.cpp) + "${CMAKE_SOURCE_DIR}/src/platform/linux/wlgrab.cpp" + "${CMAKE_SOURCE_DIR}/src/platform/linux/wayland.h" + "${CMAKE_SOURCE_DIR}/src/platform/linux/wayland.cpp") endif() # x11 @@ -178,8 +178,8 @@ if(X11_FOUND) include_directories(SYSTEM ${X11_INCLUDE_DIR}) list(APPEND PLATFORM_LIBRARIES ${X11_LIBRARIES}) list(APPEND PLATFORM_TARGET_FILES - src/platform/linux/x11grab.h - src/platform/linux/x11grab.cpp) + "${CMAKE_SOURCE_DIR}/src/platform/linux/x11grab.h" + "${CMAKE_SOURCE_DIR}/src/platform/linux/x11grab.cpp") endif() if(NOT ${CUDA_FOUND} @@ -211,7 +211,7 @@ if(${SUNSHINE_ENABLE_TRAY}) include_directories(SYSTEM ${APPINDICATOR_INCLUDE_DIRS} ${LIBNOTIFY_INCLUDE_DIRS}) link_directories(${APPINDICATOR_LIBRARY_DIRS} ${LIBNOTIFY_LIBRARY_DIRS}) - list(APPEND PLATFORM_TARGET_FILES third-party/tray/tray_linux.c) + list(APPEND PLATFORM_TARGET_FILES "${CMAKE_SOURCE_DIR}/third-party/tray/tray_linux.c") list(APPEND SUNSHINE_EXTERNAL_LIBRARIES ${APPINDICATOR_LIBRARIES} ${LIBNOTIFY_LIBRARIES}) endif() else() @@ -224,19 +224,19 @@ if (${SUNSHINE_TRAY} EQUAL 0 AND SUNSHINE_REQUIRE_TRAY) endif() list(APPEND PLATFORM_TARGET_FILES - src/platform/linux/publish.cpp - src/platform/linux/graphics.h - src/platform/linux/graphics.cpp - src/platform/linux/misc.h - src/platform/linux/misc.cpp - src/platform/linux/audio.cpp - src/platform/linux/input.cpp - third-party/glad/src/egl.c - third-party/glad/src/gl.c - third-party/glad/include/EGL/eglplatform.h - third-party/glad/include/KHR/khrplatform.h - third-party/glad/include/glad/gl.h - third-party/glad/include/glad/egl.h) + "${CMAKE_SOURCE_DIR}/src/platform/linux/publish.cpp" + "${CMAKE_SOURCE_DIR}/src/platform/linux/graphics.h" + "${CMAKE_SOURCE_DIR}/src/platform/linux/graphics.cpp" + "${CMAKE_SOURCE_DIR}/src/platform/linux/misc.h" + "${CMAKE_SOURCE_DIR}/src/platform/linux/misc.cpp" + "${CMAKE_SOURCE_DIR}/src/platform/linux/audio.cpp" + "${CMAKE_SOURCE_DIR}/src/platform/linux/input.cpp" + "${CMAKE_SOURCE_DIR}/third-party/glad/src/egl.c" + "${CMAKE_SOURCE_DIR}/third-party/glad/src/gl.c" + "${CMAKE_SOURCE_DIR}/third-party/glad/include/EGL/eglplatform.h" + "${CMAKE_SOURCE_DIR}/third-party/glad/include/KHR/khrplatform.h" + "${CMAKE_SOURCE_DIR}/third-party/glad/include/glad/gl.h" + "${CMAKE_SOURCE_DIR}/third-party/glad/include/glad/egl.h") list(APPEND PLATFORM_LIBRARIES Boost::dynamic_linking @@ -249,5 +249,5 @@ list(APPEND PLATFORM_LIBRARIES include_directories( SYSTEM /usr/include/libevdev-1.0 - third-party/nv-codec-headers/include - third-party/glad/include) + "${CMAKE_SOURCE_DIR}/third-party/nv-codec-headers/include" + "${CMAKE_SOURCE_DIR}/third-party/glad/include") diff --git a/cmake/compile_definitions/macos.cmake b/cmake/compile_definitions/macos.cmake index 3bcf9528361..fff301b856a 100644 --- a/cmake/compile_definitions/macos.cmake +++ b/cmake/compile_definitions/macos.cmake @@ -18,32 +18,32 @@ list(APPEND SUNSHINE_EXTERNAL_LIBRARIES set(PLATFORM_INCLUDE_DIRS ${Boost_INCLUDE_DIR}) -set(APPLE_PLIST_FILE ${SUNSHINE_SOURCE_ASSETS_DIR}/macos/assets/Info.plist) +set(APPLE_PLIST_FILE "${SUNSHINE_SOURCE_ASSETS_DIR}/macos/assets/Info.plist") # todo - tray is not working on macos set(SUNSHINE_TRAY 0) set(PLATFORM_TARGET_FILES - src/platform/macos/av_audio.h - src/platform/macos/av_audio.m - src/platform/macos/av_img_t.h - src/platform/macos/av_video.h - src/platform/macos/av_video.m - src/platform/macos/display.mm - src/platform/macos/input.cpp - src/platform/macos/microphone.mm - src/platform/macos/misc.mm - src/platform/macos/misc.h - src/platform/macos/nv12_zero_device.cpp - src/platform/macos/nv12_zero_device.h - src/platform/macos/publish.cpp - third-party/TPCircularBuffer/TPCircularBuffer.c - third-party/TPCircularBuffer/TPCircularBuffer.h + "${CMAKE_SOURCE_DIR}/src/platform/macos/av_audio.h" + "${CMAKE_SOURCE_DIR}/src/platform/macos/av_audio.m" + "${CMAKE_SOURCE_DIR}/src/platform/macos/av_img_t.h" + "${CMAKE_SOURCE_DIR}/src/platform/macos/av_video.h" + "${CMAKE_SOURCE_DIR}/src/platform/macos/av_video.m" + "${CMAKE_SOURCE_DIR}/src/platform/macos/display.mm" + "${CMAKE_SOURCE_DIR}/src/platform/macos/input.cpp" + "${CMAKE_SOURCE_DIR}/src/platform/macos/microphone.mm" + "${CMAKE_SOURCE_DIR}/src/platform/macos/misc.mm" + "${CMAKE_SOURCE_DIR}/src/platform/macos/misc.h" + "${CMAKE_SOURCE_DIR}/src/platform/macos/nv12_zero_device.cpp" + "${CMAKE_SOURCE_DIR}/src/platform/macos/nv12_zero_device.h" + "${CMAKE_SOURCE_DIR}/src/platform/macos/publish.cpp" + "${CMAKE_SOURCE_DIR}/third-party/TPCircularBuffer/TPCircularBuffer.c" + "${CMAKE_SOURCE_DIR}/third-party/TPCircularBuffer/TPCircularBuffer.h" ${APPLE_PLIST_FILE}) if(SUNSHINE_ENABLE_TRAY) list(APPEND SUNSHINE_EXTERNAL_LIBRARIES ${COCOA}) list(APPEND PLATFORM_TARGET_FILES - third-party/tray/tray_darwin.m) + "${CMAKE_SOURCE_DIR}/third-party/tray/tray_darwin.m") endif() diff --git a/cmake/compile_definitions/windows.cmake b/cmake/compile_definitions/windows.cmake index 703106f189a..530e983a44e 100644 --- a/cmake/compile_definitions/windows.cmake +++ b/cmake/compile_definitions/windows.cmake @@ -18,42 +18,42 @@ add_definitions(-DMINIUPNP_STATICLIB) add_subdirectory(tools) # todo - this is temporary, only tools for Windows are needed, for now # nvidia -include_directories(SYSTEM third-party/nvapi-open-source-sdk) +include_directories(SYSTEM "${CMAKE_SOURCE_DIR}/third-party/nvapi-open-source-sdk") file(GLOB NVPREFS_FILES CONFIGURE_DEPENDS - "third-party/nvapi-open-source-sdk/*.h" - "src/platform/windows/nvprefs/*.cpp" - "src/platform/windows/nvprefs/*.h") + "${CMAKE_SOURCE_DIR}/third-party/nvapi-open-source-sdk/*.h" + "${CMAKE_SOURCE_DIR}/src/platform/windows/nvprefs/*.cpp" + "${CMAKE_SOURCE_DIR}/src/platform/windows/nvprefs/*.h") # vigem -include_directories(SYSTEM third-party/ViGEmClient/include) -set_source_files_properties(third-party/ViGEmClient/src/ViGEmClient.cpp +include_directories(SYSTEM "${CMAKE_SOURCE_DIR}/third-party/ViGEmClient/include") +set_source_files_properties("${CMAKE_SOURCE_DIR}/third-party/ViGEmClient/src/ViGEmClient.cpp" PROPERTIES COMPILE_DEFINITIONS "UNICODE=1;ERROR_INVALID_DEVICE_OBJECT_PARAMETER=650") -set_source_files_properties(third-party/ViGEmClient/src/ViGEmClient.cpp +set_source_files_properties("${CMAKE_SOURCE_DIR}/third-party/ViGEmClient/src/ViGEmClient.cpp" PROPERTIES COMPILE_FLAGS "-Wno-unknown-pragmas -Wno-misleading-indentation -Wno-class-memaccess") # sunshine icon if(NOT DEFINED SUNSHINE_ICON_PATH) - set(SUNSHINE_ICON_PATH "${CMAKE_CURRENT_SOURCE_DIR}/sunshine.ico") + set(SUNSHINE_ICON_PATH "${CMAKE_SOURCE_DIR}/sunshine.ico") endif() -configure_file(src/platform/windows/windows.rs.in windows.rc @ONLY) +configure_file("${CMAKE_SOURCE_DIR}/src/platform/windows/windows.rs.in" windows.rc @ONLY) set(PLATFORM_TARGET_FILES "${CMAKE_CURRENT_BINARY_DIR}/windows.rc" - src/platform/windows/publish.cpp - src/platform/windows/misc.h - src/platform/windows/misc.cpp - src/platform/windows/input.cpp - src/platform/windows/display.h - src/platform/windows/display_base.cpp - src/platform/windows/display_vram.cpp - src/platform/windows/display_ram.cpp - src/platform/windows/audio.cpp - third-party/ViGEmClient/src/ViGEmClient.cpp - third-party/ViGEmClient/include/ViGEm/Client.h - third-party/ViGEmClient/include/ViGEm/Common.h - third-party/ViGEmClient/include/ViGEm/Util.h - third-party/ViGEmClient/include/ViGEm/km/BusShared.h + "${CMAKE_SOURCE_DIR}/src/platform/windows/publish.cpp" + "${CMAKE_SOURCE_DIR}/src/platform/windows/misc.h" + "${CMAKE_SOURCE_DIR}/src/platform/windows/misc.cpp" + "${CMAKE_SOURCE_DIR}/src/platform/windows/input.cpp" + "${CMAKE_SOURCE_DIR}/src/platform/windows/display.h" + "${CMAKE_SOURCE_DIR}/src/platform/windows/display_base.cpp" + "${CMAKE_SOURCE_DIR}/src/platform/windows/display_vram.cpp" + "${CMAKE_SOURCE_DIR}/src/platform/windows/display_ram.cpp" + "${CMAKE_SOURCE_DIR}/src/platform/windows/audio.cpp" + "${CMAKE_SOURCE_DIR}/third-party/ViGEmClient/src/ViGEmClient.cpp" + "${CMAKE_SOURCE_DIR}/third-party/ViGEmClient/include/ViGEm/Client.h" + "${CMAKE_SOURCE_DIR}/third-party/ViGEmClient/include/ViGEm/Common.h" + "${CMAKE_SOURCE_DIR}/third-party/ViGEmClient/include/ViGEm/Util.h" + "${CMAKE_SOURCE_DIR}/third-party/ViGEmClient/include/ViGEm/km/BusShared.h" ${NVPREFS_FILES}) set(OPENSSL_LIBRARIES @@ -78,5 +78,5 @@ list(PREPEND PLATFORM_LIBRARIES if(SUNSHINE_ENABLE_TRAY) list(APPEND PLATFORM_TARGET_FILES - third-party/tray/tray_windows.c) + "${CMAKE_SOURCE_DIR}/third-party/tray/tray_windows.c") endif() diff --git a/cmake/dependencies/common.cmake b/cmake/dependencies/common.cmake index 699028a2b47..a1f35128005 100644 --- a/cmake/dependencies/common.cmake +++ b/cmake/dependencies/common.cmake @@ -4,10 +4,10 @@ # submodules # moonlight common library set(ENET_NO_INSTALL ON CACHE BOOL "Don't install any libraries build for enet") -add_subdirectory(third-party/moonlight-common-c/enet) +add_subdirectory("${CMAKE_SOURCE_DIR}/third-party/moonlight-common-c/enet") # web server -add_subdirectory(third-party/Simple-Web-Server) +add_subdirectory("${CMAKE_SOURCE_DIR}/third-party/Simple-Web-Server") # common dependencies find_package(OpenSSL REQUIRED) @@ -20,7 +20,7 @@ pkg_check_modules(MINIUPNP miniupnpc REQUIRED) include_directories(SYSTEM ${MINIUPNP_INCLUDE_DIRS}) # nlohmann_json -add_subdirectory(third-party/nlohmann_json) +add_subdirectory("${CMAKE_SOURCE_DIR}/third-party/nlohmann_json") # ffmpeg pre-compiled binaries if(WIN32) @@ -28,12 +28,12 @@ if(WIN32) message(FATAL_ERROR "Unsupported system processor:" ${CMAKE_SYSTEM_PROCESSOR}) endif() set(FFMPEG_PLATFORM_LIBRARIES mfplat ole32 strmiids mfuuid vpl) - set(FFMPEG_PREPARED_BINARIES "${CMAKE_CURRENT_SOURCE_DIR}/third-party/build-deps/ffmpeg/windows-x86_64") + set(FFMPEG_PREPARED_BINARIES "${CMAKE_SOURCE_DIR}/third-party/build-deps/ffmpeg/windows-x86_64") elseif(APPLE) if(CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64") - set(FFMPEG_PREPARED_BINARIES "${CMAKE_CURRENT_SOURCE_DIR}/third-party/build-deps/ffmpeg/macos-x86_64") + set(FFMPEG_PREPARED_BINARIES "${CMAKE_SOURCE_DIR}/third-party/build-deps/ffmpeg/macos-x86_64") elseif(CMAKE_SYSTEM_PROCESSOR STREQUAL "arm64") - set(FFMPEG_PREPARED_BINARIES "${CMAKE_CURRENT_SOURCE_DIR}/third-party/build-deps/ffmpeg/macos-aarch64") + set(FFMPEG_PREPARED_BINARIES "${CMAKE_SOURCE_DIR}/third-party/build-deps/ffmpeg/macos-aarch64") elseif(CMAKE_SYSTEM_PROCESSOR STREQUAL "powerpc") message(FATAL_ERROR "PowerPC is not supported on macOS") else() @@ -43,43 +43,43 @@ elseif(UNIX) set(FFMPEG_PLATFORM_LIBRARIES va va-drm va-x11 vdpau X11) if(CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64") list(APPEND FFMPEG_PLATFORM_LIBRARIES mfx) - set(FFMPEG_PREPARED_BINARIES "${CMAKE_CURRENT_SOURCE_DIR}/third-party/build-deps/ffmpeg/linux-x86_64") + set(FFMPEG_PREPARED_BINARIES "${CMAKE_SOURCE_DIR}/third-party/build-deps/ffmpeg/linux-x86_64") set(CPACK_DEB_PLATFORM_PACKAGE_DEPENDS "libmfx1,") set(CPACK_RPM_PLATFORM_PACKAGE_REQUIRES "intel-mediasdk >= 22.3.0,") elseif(CMAKE_SYSTEM_PROCESSOR STREQUAL "aarch64") - set(FFMPEG_PREPARED_BINARIES "${CMAKE_CURRENT_SOURCE_DIR}/third-party/build-deps/ffmpeg/linux-aarch64") + set(FFMPEG_PREPARED_BINARIES "${CMAKE_SOURCE_DIR}/third-party/build-deps/ffmpeg/linux-aarch64") elseif(CMAKE_SYSTEM_PROCESSOR STREQUAL "ppc64le" OR CMAKE_SYSTEM_PROCESSOR STREQUAL "ppc64") - set(FFMPEG_PREPARED_BINARIES "${CMAKE_CURRENT_SOURCE_DIR}/third-party/build-deps/ffmpeg/linux-powerpc64le") + set(FFMPEG_PREPARED_BINARIES "${CMAKE_SOURCE_DIR}/third-party/build-deps/ffmpeg/linux-powerpc64le") else() message(FATAL_ERROR "Unsupported system processor:" ${CMAKE_SYSTEM_PROCESSOR}) endif() endif() set(FFMPEG_INCLUDE_DIRS - ${FFMPEG_PREPARED_BINARIES}/include) -if(EXISTS ${FFMPEG_PREPARED_BINARIES}/lib/libhdr10plus.a) + "${FFMPEG_PREPARED_BINARIES}/include") +if(EXISTS "${FFMPEG_PREPARED_BINARIES}/lib/libhdr10plus.a") set(HDR10_PLUS_LIBRARY - ${FFMPEG_PREPARED_BINARIES}/lib/libhdr10plus.a) + "${FFMPEG_PREPARED_BINARIES}/lib/libhdr10plus.a") endif() set(FFMPEG_LIBRARIES - ${FFMPEG_PREPARED_BINARIES}/lib/libavcodec.a - ${FFMPEG_PREPARED_BINARIES}/lib/libavutil.a - ${FFMPEG_PREPARED_BINARIES}/lib/libcbs.a - ${FFMPEG_PREPARED_BINARIES}/lib/libSvtAv1Enc.a - ${FFMPEG_PREPARED_BINARIES}/lib/libswscale.a - ${FFMPEG_PREPARED_BINARIES}/lib/libx264.a - ${FFMPEG_PREPARED_BINARIES}/lib/libx265.a + "${FFMPEG_PREPARED_BINARIES}/lib/libavcodec.a" + "${FFMPEG_PREPARED_BINARIES}/lib/libavutil.a" + "${FFMPEG_PREPARED_BINARIES}/lib/libcbs.a" + "${FFMPEG_PREPARED_BINARIES}/lib/libSvtAv1Enc.a" + "${FFMPEG_PREPARED_BINARIES}/lib/libswscale.a" + "${FFMPEG_PREPARED_BINARIES}/lib/libx264.a" + "${FFMPEG_PREPARED_BINARIES}/lib/libx265.a" ${HDR10_PLUS_LIBRARY} ${FFMPEG_PLATFORM_LIBRARIES}) # platform specific dependencies if(WIN32) - include(${CMAKE_MODULE_PATH}/dependencies/windows.cmake) + include("${CMAKE_MODULE_PATH}/dependencies/windows.cmake") elseif(UNIX) - include(${CMAKE_MODULE_PATH}/dependencies/unix.cmake) + include("${CMAKE_MODULE_PATH}/dependencies/unix.cmake") if(APPLE) - include(${CMAKE_MODULE_PATH}/dependencies/macos.cmake) + include("${CMAKE_MODULE_PATH}/dependencies/macos.cmake") else() - include(${CMAKE_MODULE_PATH}/dependencies/linux.cmake) + include("${CMAKE_MODULE_PATH}/dependencies/linux.cmake") endif() endif() diff --git a/cmake/packaging/windows.cmake b/cmake/packaging/windows.cmake index dc2add19854..1ea1afe191d 100644 --- a/cmake/packaging/windows.cmake +++ b/cmake/packaging/windows.cmake @@ -41,7 +41,7 @@ install(DIRECTORY "${SUNSHINE_SOURCE_ASSETS_DIR}/windows/assets/" COMPONENT assets) # set(CPACK_NSIS_MUI_HEADERIMAGE "") # TODO: image should be 150x57 bmp -set(CPACK_PACKAGE_ICON "${CMAKE_CURRENT_SOURCE_DIR}\\\\sunshine.ico") +set(CPACK_PACKAGE_ICON "${CMAKE_SOURCE_DIR}\\\\sunshine.ico") set(CPACK_NSIS_INSTALLED_ICON_NAME "${PROJECT__DIR}\\\\${PROJECT_EXE}") # The name of the directory that will be created in C:/Program files/ set(CPACK_PACKAGE_INSTALL_DIRECTORY "${CPACK_PACKAGE_NAME}") diff --git a/cmake/prep/build_version.cmake b/cmake/prep/build_version.cmake index 49f85f9b585..b8cff63e14f 100644 --- a/cmake/prep/build_version.cmake +++ b/cmake/prep/build_version.cmake @@ -14,12 +14,12 @@ if((DEFINED ENV{BRANCH}) AND (DEFINED ENV{BUILD_VERSION}) AND (DEFINED ENV{COMMI else() find_package(Git) if(GIT_EXECUTABLE) - MESSAGE("${CMAKE_CURRENT_SOURCE_DIR}") - get_filename_component(SRC_DIR ${CMAKE_CURRENT_SOURCE_DIR} DIRECTORY) + MESSAGE("${CMAKE_SOURCE_DIR}") + get_filename_component(SRC_DIR "${CMAKE_SOURCE_DIR}" DIRECTORY) #Get current Branch execute_process( COMMAND ${GIT_EXECUTABLE} rev-parse --abbrev-ref HEAD - #WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + #WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" OUTPUT_VARIABLE GIT_DESCRIBE_BRANCH RESULT_VARIABLE GIT_DESCRIBE_ERROR_CODE OUTPUT_STRIP_TRAILING_WHITESPACE @@ -27,7 +27,7 @@ else() # Gather current commit execute_process( COMMAND ${GIT_EXECUTABLE} rev-parse --short HEAD - #WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + #WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" OUTPUT_VARIABLE GIT_DESCRIBE_VERSION RESULT_VARIABLE GIT_DESCRIBE_ERROR_CODE OUTPUT_STRIP_TRAILING_WHITESPACE @@ -35,7 +35,7 @@ else() # Check if Dirty execute_process( COMMAND ${GIT_EXECUTABLE} diff --quiet --exit-code - #WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + #WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" RESULT_VARIABLE GIT_IS_DIRTY OUTPUT_STRIP_TRAILING_WHITESPACE ) diff --git a/cmake/prep/constants.cmake b/cmake/prep/constants.cmake index 4f7a9e48aeb..b80be1e3480 100644 --- a/cmake/prep/constants.cmake +++ b/cmake/prep/constants.cmake @@ -1,5 +1,5 @@ # source assets will be installed from this directory -set(SUNSHINE_SOURCE_ASSETS_DIR "${CMAKE_CURRENT_SOURCE_DIR}/src_assets") +set(SUNSHINE_SOURCE_ASSETS_DIR "${CMAKE_SOURCE_DIR}/src_assets") # enable system tray, we will disable this later if we cannot find the required package config on linux set(SUNSHINE_TRAY 1) diff --git a/cmake/targets/common.cmake b/cmake/targets/common.cmake index 7c446e466df..cb5fe4e67d4 100644 --- a/cmake/targets/common.cmake +++ b/cmake/targets/common.cmake @@ -36,6 +36,6 @@ target_compile_options(sunshine PRIVATE $<$:${SUNSHINE_COM #WebUI build add_custom_target(web-ui ALL - WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" COMMENT "Installing NPM Dependencies and Building the Web UI" COMMAND bash -c \"npm install && SUNSHINE_SOURCE_ASSETS_DIR=${SUNSHINE_SOURCE_ASSETS_DIR} SUNSHINE_ASSETS_DIR=${CMAKE_BINARY_DIR} npm run build\") # cmake-lint: disable=C0301 diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt index e24be6e102c..b7767bc6637 100644 --- a/tools/CMakeLists.txt +++ b/tools/CMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.0) project(sunshine_tools) -include_directories(${CMAKE_SOURCE_DIR}) +include_directories("${CMAKE_SOURCE_DIR}") add_executable(dxgi-info dxgi.cpp) set_target_properties(dxgi-info PROPERTIES CXX_STANDARD 17) @@ -36,4 +36,3 @@ target_link_libraries(ddprobe d3d11 ${PLATFORM_LIBRARIES}) target_compile_options(ddprobe PRIVATE ${SUNSHINE_COMPILE_OPTIONS}) - From e62d6915dba4b2f26ea1c8052c58254e7d602523 Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Fri, 2 Feb 2024 19:28:55 -0600 Subject: [PATCH 029/182] Fix -Wreturn-local-addr warning in event_t::view() --- src/thread_safe.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/thread_safe.h b/src/thread_safe.h index f4713bcf014..1135c441d74 100644 --- a/src/thread_safe.h +++ b/src/thread_safe.h @@ -82,7 +82,7 @@ namespace safe { } // pop and view should not be used interchangeably - const status_t & + status_t view() { std::unique_lock ul { _lock }; From ca29eac53a69f7c2eb648b64e7b54180c3f2b946 Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Fri, 2 Feb 2024 20:05:56 -0600 Subject: [PATCH 030/182] Refactor RTSP handling to be session-based rather than socket-based This is required to support per-session attributes like encryption keys during RTSP message processing. --- src/nvhttp.cpp | 37 ++++++----- src/process.cpp | 18 +++--- src/process.h | 2 +- src/rtsp.cpp | 163 ++++++++++++++++++++++++++---------------------- src/rtsp.h | 8 ++- src/stream.cpp | 6 ++ 6 files changed, 130 insertions(+), 104 deletions(-) diff --git a/src/nvhttp.cpp b/src/nvhttp.cpp index 037503c9a54..285df69dfdc 100644 --- a/src/nvhttp.cpp +++ b/src/nvhttp.cpp @@ -141,6 +141,7 @@ namespace nvhttp { // uniqueID, session std::unordered_map map_id_sess; std::unordered_map map_id_client; + std::atomic session_id_counter; using args_t = SimpleWeb::CaseInsensitiveMultimap; using resp_https_t = std::shared_ptr::Response>; @@ -267,41 +268,43 @@ namespace nvhttp { } } - rtsp_stream::launch_session_t + std::shared_ptr make_launch_session(bool host_audio, const args_t &args) { - rtsp_stream::launch_session_t launch_session; + auto launch_session = std::make_shared(); + + launch_session->id = ++session_id_counter; auto rikey = util::from_hex_vec(get_arg(args, "rikey"), true); - std::copy(rikey.cbegin(), rikey.cend(), std::back_inserter(launch_session.gcm_key)); + std::copy(rikey.cbegin(), rikey.cend(), std::back_inserter(launch_session->gcm_key)); - launch_session.host_audio = host_audio; + launch_session->host_audio = host_audio; std::stringstream mode = std::stringstream(get_arg(args, "mode", "0x0x0")); // Split mode by the char "x", to populate width/height/fps int x = 0; std::string segment; while (std::getline(mode, segment, 'x')) { - if (x == 0) launch_session.width = atoi(segment.c_str()); - if (x == 1) launch_session.height = atoi(segment.c_str()); - if (x == 2) launch_session.fps = atoi(segment.c_str()); + if (x == 0) launch_session->width = atoi(segment.c_str()); + if (x == 1) launch_session->height = atoi(segment.c_str()); + if (x == 2) launch_session->fps = atoi(segment.c_str()); x++; } - launch_session.unique_id = (get_arg(args, "uniqueid", "unknown")); - launch_session.appid = util::from_view(get_arg(args, "appid", "unknown")); - launch_session.enable_sops = util::from_view(get_arg(args, "sops", "0")); - launch_session.surround_info = util::from_view(get_arg(args, "surroundAudioInfo", "196610")); - launch_session.gcmap = util::from_view(get_arg(args, "gcmap", "0")); - launch_session.enable_hdr = util::from_view(get_arg(args, "hdrMode", "0")); + launch_session->unique_id = (get_arg(args, "uniqueid", "unknown")); + launch_session->appid = util::from_view(get_arg(args, "appid", "unknown")); + launch_session->enable_sops = util::from_view(get_arg(args, "sops", "0")); + launch_session->surround_info = util::from_view(get_arg(args, "surroundAudioInfo", "196610")); + launch_session->gcmap = util::from_view(get_arg(args, "gcmap", "0")); + launch_session->enable_hdr = util::from_view(get_arg(args, "hdrMode", "0")); // Generate the unique identifiers for this connection that we will send later during RTSP handshake unsigned char raw_payload[8]; RAND_bytes(raw_payload, sizeof(raw_payload)); - launch_session.av_ping_payload = util::hex_vec(raw_payload); - RAND_bytes((unsigned char *) &launch_session.control_connect_data, sizeof(launch_session.control_connect_data)); + launch_session->av_ping_payload = util::hex_vec(raw_payload); + RAND_bytes((unsigned char *) &launch_session->control_connect_data, sizeof(launch_session->control_connect_data)); - launch_session.iv.resize(16); + launch_session->iv.resize(16); uint32_t prepend_iv = util::endian::big(util::from_view(get_arg(args, "rikeyid"))); auto prepend_iv_p = (uint8_t *) &prepend_iv; - std::copy(prepend_iv_p, prepend_iv_p + sizeof(prepend_iv), std::begin(launch_session.iv)); + std::copy(prepend_iv_p, prepend_iv_p + sizeof(prepend_iv), std::begin(launch_session->iv)); return launch_session; } diff --git a/src/process.cpp b/src/process.cpp index 7042dd00c71..050c58ee169 100644 --- a/src/process.cpp +++ b/src/process.cpp @@ -136,7 +136,7 @@ namespace proc { } int - proc_t::execute(int app_id, rtsp_stream::launch_session_t launch_session) { + proc_t::execute(int app_id, std::shared_ptr launch_session) { // Ensure starting from a clean slate terminate(); @@ -157,14 +157,14 @@ namespace proc { // Add Stream-specific environment variables _env["SUNSHINE_APP_ID"] = std::to_string(_app_id); _env["SUNSHINE_APP_NAME"] = _app.name; - _env["SUNSHINE_CLIENT_WIDTH"] = std::to_string(launch_session.width); - _env["SUNSHINE_CLIENT_HEIGHT"] = std::to_string(launch_session.height); - _env["SUNSHINE_CLIENT_FPS"] = std::to_string(launch_session.fps); - _env["SUNSHINE_CLIENT_HDR"] = launch_session.enable_hdr ? "true" : "false"; - _env["SUNSHINE_CLIENT_GCMAP"] = std::to_string(launch_session.gcmap); - _env["SUNSHINE_CLIENT_HOST_AUDIO"] = launch_session.host_audio ? "true" : "false"; - _env["SUNSHINE_CLIENT_ENABLE_SOPS"] = launch_session.enable_sops ? "true" : "false"; - int channelCount = launch_session.surround_info & (65535); + _env["SUNSHINE_CLIENT_WIDTH"] = std::to_string(launch_session->width); + _env["SUNSHINE_CLIENT_HEIGHT"] = std::to_string(launch_session->height); + _env["SUNSHINE_CLIENT_FPS"] = std::to_string(launch_session->fps); + _env["SUNSHINE_CLIENT_HDR"] = launch_session->enable_hdr ? "true" : "false"; + _env["SUNSHINE_CLIENT_GCMAP"] = std::to_string(launch_session->gcmap); + _env["SUNSHINE_CLIENT_HOST_AUDIO"] = launch_session->host_audio ? "true" : "false"; + _env["SUNSHINE_CLIENT_ENABLE_SOPS"] = launch_session->enable_sops ? "true" : "false"; + int channelCount = launch_session->surround_info & (65535); switch (channelCount) { case 2: _env["SUNSHINE_CLIENT_AUDIO_CONFIGURATION"] = "2.0"; diff --git a/src/process.h b/src/process.h index 433c7669902..c8754992652 100644 --- a/src/process.h +++ b/src/process.h @@ -75,7 +75,7 @@ namespace proc { _apps(std::move(apps)) {} int - execute(int app_id, rtsp_stream::launch_session_t launch_session); + execute(int app_id, std::shared_ptr launch_session); /** * @return _app_id if a process is running, otherwise returns 0 diff --git a/src/rtsp.cpp b/src/rtsp.cpp index e92e177ac2c..5fa0e024b2f 100644 --- a/src/rtsp.cpp +++ b/src/rtsp.cpp @@ -44,18 +44,18 @@ namespace rtsp_stream { class rtsp_server_t; using msg_t = util::safe_ptr; - using cmd_func_t = std::function; + using cmd_func_t = std::function; void print_msg(PRTSP_MESSAGE msg); void - cmd_not_found(tcp::socket &sock, msg_t &&req); + cmd_not_found(tcp::socket &sock, launch_session_t &, msg_t &&req); void - respond(tcp::socket &sock, POPTION_ITEM options, int statuscode, const char *status_msg, int seqn, const std::string_view &payload); + respond(tcp::socket &sock, launch_session_t &session, POPTION_ITEM options, int statuscode, const char *status_msg, int seqn, const std::string_view &payload); class socket_t: public std::enable_shared_from_this { public: - socket_t(boost::asio::io_service &ios, std::function &&handle_data_fn): + socket_t(boost::asio::io_service &ios, std::function &&handle_data_fn): handle_data_fn { std::move(handle_data_fn) }, sock { ios } {} void @@ -63,7 +63,7 @@ namespace rtsp_stream { if (begin == std::end(msg_buf)) { BOOST_LOG(error) << "RTSP: read(): Exceeded maximum rtsp packet size: "sv << msg_buf.size(); - respond(sock, nullptr, 400, "BAD REQUEST", 0, {}); + respond(sock, *session, nullptr, 400, "BAD REQUEST", 0, {}); sock.close(); @@ -83,7 +83,7 @@ namespace rtsp_stream { if (begin == std::end(msg_buf)) { BOOST_LOG(error) << "RTSP: read_payload(): Exceeded maximum rtsp packet size: "sv << msg_buf.size(); - respond(sock, nullptr, 400, "BAD REQUEST", 0, {}); + respond(sock, *session, nullptr, 400, "BAD REQUEST", 0, {}); sock.close(); @@ -122,7 +122,7 @@ namespace rtsp_stream { if (auto status = parseRtspMessage(req.get(), socket->msg_buf.data(), (std::size_t)(end - socket->msg_buf.data()))) { BOOST_LOG(error) << "Malformed RTSP message: ["sv << status << ']'; - respond(socket->sock, nullptr, 400, "BAD REQUEST", req->sequenceNumber, {}); + respond(socket->sock, *socket->session, nullptr, 400, "BAD REQUEST", req->sequenceNumber, {}); return; } @@ -206,10 +206,10 @@ namespace rtsp_stream { void handle_data(msg_t &&req) { - handle_data_fn(sock, std::move(req)); + handle_data_fn(sock, *session, std::move(req)); } - std::function handle_data_fn; + std::function handle_data_fn; tcp::socket sock; @@ -217,6 +217,8 @@ namespace rtsp_stream { char *crlf; char *begin = msg_buf.data(); + + std::shared_ptr session; }; class rtsp_server_t { @@ -251,8 +253,8 @@ namespace rtsp_stream { return -1; } - next_socket = std::make_shared(ios, [this](tcp::socket &sock, msg_t &&msg) { - handle_msg(sock, std::move(msg)); + next_socket = std::make_shared(ios, [this](tcp::socket &sock, launch_session_t &session, msg_t &&msg) { + handle_msg(sock, session, std::move(msg)); }); acceptor.async_accept(next_socket->sock, [this](const auto &ec) { @@ -269,13 +271,13 @@ namespace rtsp_stream { } void - handle_msg(tcp::socket &sock, msg_t &&req) { + handle_msg(tcp::socket &sock, launch_session_t &session, msg_t &&req) { auto func = _map_cmd_cb.find(req->message.request.command); if (func != std::end(_map_cmd_cb)) { - func->second(this, sock, std::move(req)); + func->second(this, sock, session, std::move(req)); } else { - cmd_not_found(sock, std::move(req)); + cmd_not_found(sock, session, std::move(req)); } sock.shutdown(boost::asio::socket_base::shutdown_type::shutdown_both); @@ -291,12 +293,17 @@ namespace rtsp_stream { return; } - auto socket = std::move(next_socket); - socket->read(); + auto launch_session { launch_event.view() }; + if (launch_session) { + // Associate the current RTSP session with this socket and start reading + auto socket = std::move(next_socket); + socket->session = launch_session; + socket->read(); - next_socket = std::make_shared(ios, [this](tcp::socket &sock, msg_t &&msg) { - handle_msg(sock, std::move(msg)); - }); + next_socket = std::make_shared(ios, [this](tcp::socket &sock, launch_session_t &session, msg_t &&msg) { + handle_msg(sock, session, std::move(msg)); + }); + } acceptor.async_accept(next_socket->sock, [this](const auto &ec) { handle_accept(ec); @@ -313,16 +320,9 @@ namespace rtsp_stream { * @note If the client does not begin streaming within the ping_timeout, * the session will be discarded. * @param launch_session Streaming session information. - * - * EXAMPLES: - * ```cpp - * launch_session_t launch_session; - * rtsp_server_t server {}; - * server.session_raise(launch_session); - * ``` */ void - session_raise(rtsp_stream::launch_session_t launch_session) { + session_raise(std::shared_ptr launch_session) { auto now = std::chrono::steady_clock::now(); // If a launch event is still pending, don't overwrite it. @@ -332,7 +332,26 @@ namespace rtsp_stream { raised_timeout = now + config::stream.ping_timeout; --_slot_count; - launch_event.raise(launch_session); + launch_event.raise(std::move(launch_session)); + } + + /** + * @brief Clear state for the oldest launch session. + * @param launch_session_id The ID of the session to clear. + */ + void + session_clear(uint32_t launch_session_id) { + // We currently only support a single pending RTSP session, + // so the ID should always match the one for that session. + auto launch_session = launch_event.view(); + if (launch_session) { + if (launch_session->id != launch_session_id) { + BOOST_LOG(error) << "Attempted to clear unexpected session: "sv << launch_session_id << " vs "sv << launch_session->id; + } + else { + launch_event.pop(); + } + } } int @@ -340,7 +359,7 @@ namespace rtsp_stream { return config::stream.channels - _slot_count; } - safe::event_t launch_event; + safe::event_t> launch_event; /** * @brief Clear launch sessions. @@ -420,8 +439,17 @@ namespace rtsp_stream { rtsp_server_t server {}; void - launch_session_raise(rtsp_stream::launch_session_t launch_session) { - server.session_raise(launch_session); + launch_session_raise(std::shared_ptr launch_session) { + server.session_raise(std::move(launch_session)); + } + + /** + * @brief Clear state for the specified launch session. + * @param launch_session_id The ID of the session to clear. + */ + void + launch_session_clear(uint32_t launch_session_id) { + server.session_clear(launch_session_id); } int @@ -450,7 +478,7 @@ namespace rtsp_stream { } void - respond(tcp::socket &sock, msg_t &resp) { + respond(tcp::socket &sock, launch_session_t &session, msg_t &resp) { auto payload = std::make_pair(resp->payload, resp->payloadLength); // Restore response message for proper destruction @@ -480,20 +508,20 @@ namespace rtsp_stream { } void - respond(tcp::socket &sock, POPTION_ITEM options, int statuscode, const char *status_msg, int seqn, const std::string_view &payload) { + respond(tcp::socket &sock, launch_session_t &session, POPTION_ITEM options, int statuscode, const char *status_msg, int seqn, const std::string_view &payload) { msg_t resp { new msg_t::element_type }; createRtspResponse(resp.get(), nullptr, 0, const_cast("RTSP/1.0"), statuscode, const_cast(status_msg), seqn, options, const_cast(payload.data()), (int) payload.size()); - respond(sock, resp); + respond(sock, session, resp); } void - cmd_not_found(tcp::socket &sock, msg_t &&req) { - respond(sock, nullptr, 404, "NOT FOUND", req->sequenceNumber, {}); + cmd_not_found(tcp::socket &sock, launch_session_t &session, msg_t &&req) { + respond(sock, session, nullptr, 404, "NOT FOUND", req->sequenceNumber, {}); } void - cmd_option(rtsp_server_t *server, tcp::socket &sock, msg_t &&req) { + cmd_option(rtsp_server_t *server, tcp::socket &sock, launch_session_t &session, msg_t &&req) { OPTION_ITEM option {}; // I know these string literals will not be modified @@ -502,11 +530,11 @@ namespace rtsp_stream { auto seqn_str = std::to_string(req->sequenceNumber); option.content = const_cast(seqn_str.c_str()); - respond(sock, &option, 200, "OK", req->sequenceNumber, {}); + respond(sock, session, &option, 200, "OK", req->sequenceNumber, {}); } void - cmd_describe(rtsp_server_t *server, tcp::socket &sock, msg_t &&req) { + cmd_describe(rtsp_server_t *server, tcp::socket &sock, launch_session_t &session, msg_t &&req) { OPTION_ITEM option {}; // I know these string literals will not be modified @@ -587,11 +615,11 @@ namespace rtsp_stream { ss << std::endl; } - respond(sock, &option, 200, "OK", req->sequenceNumber, ss.str()); + respond(sock, session, &option, 200, "OK", req->sequenceNumber, ss.str()); } void - cmd_setup(rtsp_server_t *server, tcp::socket &sock, msg_t &&req) { + cmd_setup(rtsp_server_t *server, tcp::socket &sock, launch_session_t &session, msg_t &&req) { OPTION_ITEM options[4] {}; auto &seqn = options[0]; @@ -604,14 +632,6 @@ namespace rtsp_stream { auto seqn_str = std::to_string(req->sequenceNumber); seqn.content = const_cast(seqn_str.c_str()); - if (!server->launch_event.peek()) { - // /launch has not been used - - respond(sock, &seqn, 503, "Service Unavailable", req->sequenceNumber, {}); - return; - } - auto launch_session { server->launch_event.view() }; - std::string_view target { req->message.request.target }; auto begin = std::find(std::begin(target), std::end(target), '=') + 1; auto end = std::find(begin, std::end(target), '/'); @@ -628,7 +648,7 @@ namespace rtsp_stream { port = map_port(stream::CONTROL_PORT); } else { - cmd_not_found(sock, std::move(req)); + cmd_not_found(sock, session, std::move(req)); return; } @@ -647,23 +667,23 @@ namespace rtsp_stream { port_option.content = port_value.data(); // Send identifiers that will be echoed in the other connections - auto connect_data = std::to_string(launch_session->control_connect_data); + auto connect_data = std::to_string(session.control_connect_data); if (type == "control"sv) { payload_option.option = const_cast("X-SS-Connect-Data"); payload_option.content = connect_data.data(); } else { payload_option.option = const_cast("X-SS-Ping-Payload"); - payload_option.content = launch_session->av_ping_payload.data(); + payload_option.content = session.av_ping_payload.data(); } port_option.next = &payload_option; - respond(sock, &seqn, 200, "OK", req->sequenceNumber, {}); + respond(sock, session, &seqn, 200, "OK", req->sequenceNumber, {}); } void - cmd_announce(rtsp_server_t *server, tcp::socket &sock, msg_t &&req) { + cmd_announce(rtsp_server_t *server, tcp::socket &sock, launch_session_t &session, msg_t &&req) { OPTION_ITEM option {}; // I know these string literals will not be modified @@ -672,14 +692,6 @@ namespace rtsp_stream { auto seqn_str = std::to_string(req->sequenceNumber); option.content = const_cast(seqn_str.c_str()); - if (!server->launch_event.peek()) { - // /launch has not been used - - respond(sock, &option, 503, "Service Unavailable", req->sequenceNumber, {}); - return; - } - auto launch_session { server->launch_event.pop() }; - std::string_view payload { req->payload, (size_t) req->payloadLength }; std::vector lines; @@ -739,7 +751,7 @@ namespace rtsp_stream { stream::config_t config; std::int64_t configuredBitrateKbps; - config.audio.flags[audio::config_t::HOST_AUDIO] = launch_session->host_audio; + config.audio.flags[audio::config_t::HOST_AUDIO] = session.host_audio; try { config.audio.channels = util::from_view(args.at("x-nv-audio.surround.numChannels"sv)); config.audio.mask = util::from_view(args.at("x-nv-audio.surround.channelMask"sv)); @@ -774,7 +786,7 @@ namespace rtsp_stream { configuredBitrateKbps = util::from_view(args.at("x-ml-video.configuredBitrateKbps"sv)); } catch (std::out_of_range &) { - respond(sock, &option, 400, "BAD REQUEST", req->sequenceNumber, {}); + respond(sock, session, &option, 400, "BAD REQUEST", req->sequenceNumber, {}); return; } @@ -820,14 +832,14 @@ namespace rtsp_stream { if (config.monitor.videoFormat == 1 && 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, {}); + respond(sock, session, &option, 400, "BAD REQUEST", req->sequenceNumber, {}); return; } if (config.monitor.videoFormat == 2 && video::active_av1_mode == 1) { BOOST_LOG(warning) << "AV1 is disabled, yet the client requested AV1"sv; - respond(sock, &option, 400, "BAD REQUEST", req->sequenceNumber, {}); + respond(sock, session, &option, 400, "BAD REQUEST", req->sequenceNumber, {}); return; } @@ -844,33 +856,33 @@ namespace rtsp_stream { (config.encryptionFlagsEnabled & (SS_ENC_VIDEO | SS_ENC_AUDIO)) != (SS_ENC_VIDEO | SS_ENC_AUDIO)) { BOOST_LOG(error) << "Rejecting client that cannot comply with mandatory encryption requirement"sv; - respond(sock, &option, 403, "Forbidden", req->sequenceNumber, {}); + respond(sock, session, &option, 403, "Forbidden", req->sequenceNumber, {}); return; } - auto session = stream::session::alloc(config, *launch_session); + auto stream_session = stream::session::alloc(config, session); - auto slot = server->accept(session); + auto slot = server->accept(stream_session); if (!slot) { BOOST_LOG(info) << "Ran out of slots for client from ["sv << ']'; - respond(sock, &option, 503, "Service Unavailable", req->sequenceNumber, {}); + respond(sock, session, &option, 503, "Service Unavailable", req->sequenceNumber, {}); return; } - if (stream::session::start(*session, sock.remote_endpoint().address().to_string())) { + if (stream::session::start(*stream_session, sock.remote_endpoint().address().to_string())) { BOOST_LOG(error) << "Failed to start a streaming session"sv; server->clear(slot); - respond(sock, &option, 500, "Internal Server Error", req->sequenceNumber, {}); + respond(sock, session, &option, 500, "Internal Server Error", req->sequenceNumber, {}); return; } - respond(sock, &option, 200, "OK", req->sequenceNumber, {}); + respond(sock, session, &option, 200, "OK", req->sequenceNumber, {}); } void - cmd_play(rtsp_server_t *server, tcp::socket &sock, msg_t &&req) { + cmd_play(rtsp_server_t *server, tcp::socket &sock, launch_session_t &session, msg_t &&req) { OPTION_ITEM option {}; // I know these string literals will not be modified @@ -879,7 +891,7 @@ namespace rtsp_stream { auto seqn_str = std::to_string(req->sequenceNumber); option.content = const_cast(seqn_str.c_str()); - respond(sock, &option, 200, "OK", req->sequenceNumber, {}); + respond(sock, session, &option, 200, "OK", req->sequenceNumber, {}); } void @@ -891,7 +903,6 @@ namespace rtsp_stream { server.map("DESCRIBE"sv, &cmd_describe); server.map("SETUP"sv, &cmd_setup); server.map("ANNOUNCE"sv, &cmd_announce); - server.map("PLAY"sv, &cmd_play); boost::system::error_code ec; diff --git a/src/rtsp.h b/src/rtsp.h index 3cdd5c7c5ea..0167405964c 100644 --- a/src/rtsp.h +++ b/src/rtsp.h @@ -13,6 +13,8 @@ namespace rtsp_stream { constexpr auto RTSP_SETUP_PORT = 21; struct launch_session_t { + uint32_t id; + crypto::aes_t gcm_key; crypto::aes_t iv; @@ -32,7 +34,11 @@ namespace rtsp_stream { }; void - launch_session_raise(launch_session_t launch_session); + launch_session_raise(std::shared_ptr launch_session); + + void + launch_session_clear(uint32_t launch_session_id); + int session_count(); diff --git a/src/stream.cpp b/src/stream.cpp index 41f80e2e331..f99e68c12cc 100644 --- a/src/stream.cpp +++ b/src/stream.cpp @@ -411,6 +411,8 @@ namespace stream { safe::mail_raw_t::event_t hdr_queue; } control; + std::uint32_t launch_session_id; + safe::mail_raw_t::event_t shutdown_event; safe::signal_t controlEnd; @@ -523,6 +525,9 @@ namespace stream { } } + // Once the control stream connection is established, RTSP session state can be torn down + rtsp_stream::launch_session_clear(session_p->launch_session_id); + session_p->control.peer = peer; // Use the local address from the control connection as the source address @@ -1881,6 +1886,7 @@ namespace stream { auto mail = std::make_shared(); session->shutdown_event = mail->event(mail::shutdown); + session->launch_session_id = launch_session.id; session->config = config; From f80b23750bea597a8bfc310c64a1a4699719ad6c Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Fri, 2 Feb 2024 23:01:33 -0600 Subject: [PATCH 031/182] Implement RTSP encryption support RTSP encryption is mandatory for client that report core version 1 or later. --- src/nvhttp.cpp | 26 ++++- src/rtsp.cpp | 300 +++++++++++++++++++++++++++++++++++++++++++------ src/rtsp.h | 4 + 3 files changed, 293 insertions(+), 37 deletions(-) diff --git a/src/nvhttp.cpp b/src/nvhttp.cpp index 285df69dfdc..ba7d068cd56 100644 --- a/src/nvhttp.cpp +++ b/src/nvhttp.cpp @@ -295,6 +295,16 @@ namespace nvhttp { launch_session->gcmap = util::from_view(get_arg(args, "gcmap", "0")); launch_session->enable_hdr = util::from_view(get_arg(args, "hdrMode", "0")); + // Encrypted RTSP is enabled with client reported corever >= 1 + auto corever = util::from_view(get_arg(args, "corever", "0")); + if (corever >= 1) { + launch_session->rtsp_cipher = crypto::cipher::gcm_t { + launch_session->gcm_key, false + }; + launch_session->rtsp_iv_counter = 0; + } + launch_session->rtsp_url_scheme = launch_session->rtsp_cipher ? "rtspenc://"s : "rtsp://"s; + // Generate the unique identifiers for this connection that we will send later during RTSP handshake unsigned char raw_payload[8]; RAND_bytes(raw_payload, sizeof(raw_payload)); @@ -821,11 +831,13 @@ namespace nvhttp { } } - rtsp_stream::launch_session_raise(launch_session); - tree.put("root..status_code", 200); - tree.put("root.sessionUrl0", "rtsp://"s + net::addr_to_url_escaped_string(request->local_endpoint().address()) + ':' + std::to_string(map_port(rtsp_stream::RTSP_SETUP_PORT))); + tree.put("root.sessionUrl0", launch_session->rtsp_url_scheme + + net::addr_to_url_escaped_string(request->local_endpoint().address()) + ':' + + std::to_string(map_port(rtsp_stream::RTSP_SETUP_PORT))); tree.put("root.gamesession", 1); + + rtsp_stream::launch_session_raise(launch_session); } void @@ -892,11 +904,15 @@ namespace nvhttp { } } - rtsp_stream::launch_session_raise(make_launch_session(host_audio, args)); + auto launch_session = make_launch_session(host_audio, args); tree.put("root..status_code", 200); - tree.put("root.sessionUrl0", "rtsp://"s + net::addr_to_url_escaped_string(request->local_endpoint().address()) + ':' + std::to_string(map_port(rtsp_stream::RTSP_SETUP_PORT))); + tree.put("root.sessionUrl0", launch_session->rtsp_url_scheme + + net::addr_to_url_escaped_string(request->local_endpoint().address()) + ':' + + std::to_string(map_port(rtsp_stream::RTSP_SETUP_PORT))); tree.put("root.resume", 1); + + rtsp_stream::launch_session_raise(launch_session); } void diff --git a/src/rtsp.cpp b/src/rtsp.cpp index 5fa0e024b2f..ba3842c50ce 100644 --- a/src/rtsp.cpp +++ b/src/rtsp.cpp @@ -41,6 +41,40 @@ namespace rtsp_stream { delete msg; } +#pragma pack(push, 1) + + struct encrypted_rtsp_header_t { + // We set the MSB in encrypted RTSP messages to allow format-agnostic + // parsing code to be able to tell encrypted from plaintext messages. + static constexpr std::uint32_t ENCRYPTED_MESSAGE_TYPE_BIT = 0x80000000; + + uint8_t * + payload() { + return (uint8_t *) (this + 1); + } + + std::uint32_t + payload_length() { + return util::endian::big(typeAndLength) & ~ENCRYPTED_MESSAGE_TYPE_BIT; + } + + bool + is_encrypted() { + return !!(util::endian::big(typeAndLength) & ENCRYPTED_MESSAGE_TYPE_BIT); + } + + // This field is the length of the payload + ENCRYPTED_MESSAGE_TYPE_BIT in big-endian + std::uint32_t typeAndLength; + + // This field is the number used to initialize the bottom 4 bytes of the AES IV in big-endian + std::uint32_t sequenceNumber; + + // This field is the AES GCM authentication tag + std::uint8_t tag[16]; + }; + +#pragma pack(pop) + class rtsp_server_t; using msg_t = util::safe_ptr; @@ -58,34 +92,174 @@ namespace rtsp_stream { socket_t(boost::asio::io_service &ios, std::function &&handle_data_fn): handle_data_fn { std::move(handle_data_fn) }, sock { ios } {} + /** + * @brief Queues an asynchronous read to begin the next message. + */ void read() { - if (begin == std::end(msg_buf)) { + if (begin == std::end(msg_buf) || (session->rtsp_cipher && begin + sizeof(encrypted_rtsp_header_t) >= std::end(msg_buf))) { BOOST_LOG(error) << "RTSP: read(): Exceeded maximum rtsp packet size: "sv << msg_buf.size(); respond(sock, *session, nullptr, 400, "BAD REQUEST", 0, {}); - sock.close(); + boost::system::error_code ec; + sock.close(ec); return; } - sock.async_read_some( - boost::asio::buffer(begin, (std::size_t)(std::end(msg_buf) - begin)), + if (session->rtsp_cipher) { + // For encrypted RTSP, we will read the the entire header first + boost::asio::async_read(sock, + boost::asio::buffer(begin, sizeof(encrypted_rtsp_header_t)), + boost::bind( + &socket_t::handle_read_encrypted_header, shared_from_this(), + boost::asio::placeholders::error, + boost::asio::placeholders::bytes_transferred)); + } + else { + sock.async_read_some( + boost::asio::buffer(begin, (std::size_t)(std::end(msg_buf) - begin)), + boost::bind( + &socket_t::handle_read_plaintext, shared_from_this(), + boost::asio::placeholders::error, + boost::asio::placeholders::bytes_transferred)); + } + } + + /** + * @brief Handles the initial read of the header of an encrypted message. + * @param socket The socket the message was received on. + * @param ec The error code of the read operation. + * @param bytes The number of bytes read. + */ + static void + handle_read_encrypted_header(std::shared_ptr &socket, const boost::system::error_code &ec, std::size_t bytes) { + BOOST_LOG(debug) << "handle_read_encrypted_header(): Handle read of size: "sv << bytes << " bytes"sv; + + auto sock_close = util::fail_guard([&socket]() { + boost::system::error_code ec; + socket->sock.close(ec); + + if (ec) { + BOOST_LOG(error) << "RTSP: handle_read_encrypted_header(): Couldn't close tcp socket: "sv << ec.message(); + } + }); + + if (ec || bytes < sizeof(encrypted_rtsp_header_t)) { + BOOST_LOG(error) << "RTSP: handle_read_encrypted_header(): Couldn't read from tcp socket: "sv << ec.message(); + + respond(socket->sock, *socket->session, nullptr, 400, "BAD REQUEST", 0, {}); + return; + } + + auto header = (encrypted_rtsp_header_t *) socket->begin; + if (!header->is_encrypted()) { + BOOST_LOG(error) << "RTSP: handle_read_encrypted_header(): Rejecting unencrypted RTSP message"sv; + + respond(socket->sock, *socket->session, nullptr, 400, "BAD REQUEST", 0, {}); + return; + } + + auto payload_length = header->payload_length(); + + // Check if we have enough space to read this message + if (socket->begin + sizeof(*header) + payload_length >= std::end(socket->msg_buf)) { + BOOST_LOG(error) << "RTSP: handle_read_encrypted_header(): Exceeded maximum rtsp packet size: "sv << socket->msg_buf.size(); + + respond(socket->sock, *socket->session, nullptr, 400, "BAD REQUEST", 0, {}); + return; + } + + sock_close.disable(); + + // Read the remainder of the header and full encrypted payload + boost::asio::async_read(socket->sock, + boost::asio::buffer(socket->begin + bytes, payload_length), boost::bind( - &socket_t::handle_read, shared_from_this(), + &socket_t::handle_read_encrypted_message, socket->shared_from_this(), boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred)); } + /** + * @brief Handles the final read of the content of an encrypted message. + * @param socket The socket the message was received on. + * @param ec The error code of the read operation. + * @param bytes The number of bytes read. + */ + static void + handle_read_encrypted_message(std::shared_ptr &socket, const boost::system::error_code &ec, std::size_t bytes) { + BOOST_LOG(debug) << "handle_read_encrypted(): Handle read of size: "sv << bytes << " bytes"sv; + + auto sock_close = util::fail_guard([&socket]() { + boost::system::error_code ec; + socket->sock.close(ec); + + if (ec) { + BOOST_LOG(error) << "RTSP: handle_read_encrypted_message(): Couldn't close tcp socket: "sv << ec.message(); + } + }); + + auto header = (encrypted_rtsp_header_t *) socket->begin; + auto payload_length = header->payload_length(); + auto seq = util::endian::big(header->sequenceNumber); + + if (ec || bytes < payload_length) { + BOOST_LOG(error) << "RTSP: handle_read_encrypted(): Couldn't read from tcp socket: "sv << ec.message(); + + respond(socket->sock, *socket->session, nullptr, 400, "BAD REQUEST", 0, {}); + return; + } + + // We use the deterministic IV construction algorithm specified in NIST SP 800-38D + // Section 8.2.1. The sequence number is our "invocation" field and the 'RC' in the + // high bytes is the "fixed" field. Because each client provides their own unique + // key, our values in the fixed field need only uniquely identify each independent + // use of the client's key with AES-GCM in our code. + // + // The sequence number is 32 bits long which allows for 2^32 RTSP messages to be + // received from each client before the IV repeats. + crypto::aes_t iv(12); + std::copy_n((uint8_t *) &seq, sizeof(seq), std::begin(iv)); + iv[10] = 'C'; // Client originated + iv[11] = 'R'; // RTSP + + std::vector plaintext; + if (socket->session->rtsp_cipher->decrypt(std::string_view { (const char *) header->tag, sizeof(header->tag) + bytes }, plaintext, &iv)) { + BOOST_LOG(error) << "Failed to verify RTSP message tag"sv; + + respond(socket->sock, *socket->session, nullptr, 400, "BAD REQUEST", 0, {}); + return; + } + + msg_t req { new msg_t::element_type {} }; + if (auto status = parseRtspMessage(req.get(), (char *) plaintext.data(), plaintext.size())) { + BOOST_LOG(error) << "Malformed RTSP message: ["sv << status << ']'; + + respond(socket->sock, *socket->session, nullptr, 400, "BAD REQUEST", 0, {}); + return; + } + + sock_close.disable(); + + print_msg(req.get()); + + socket->handle_data(std::move(req)); + } + + /** + * @brief Queues an asynchronous read of the payload portion of a plaintext message. + */ void - read_payload() { + read_plaintext_payload() { if (begin == std::end(msg_buf)) { - BOOST_LOG(error) << "RTSP: read_payload(): Exceeded maximum rtsp packet size: "sv << msg_buf.size(); + BOOST_LOG(error) << "RTSP: read_plaintext_payload(): Exceeded maximum rtsp packet size: "sv << msg_buf.size(); respond(sock, *session, nullptr, 400, "BAD REQUEST", 0, {}); - sock.close(); + boost::system::error_code ec; + sock.close(ec); return; } @@ -93,26 +267,32 @@ namespace rtsp_stream { sock.async_read_some( boost::asio::buffer(begin, (std::size_t)(std::end(msg_buf) - begin)), boost::bind( - &socket_t::handle_payload, shared_from_this(), + &socket_t::handle_plaintext_payload, shared_from_this(), boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred)); } + /** + * @brief Handles the read of the payload portion of a plaintext message. + * @param socket The socket the message was received on. + * @param ec The error code of the read operation. + * @param bytes The number of bytes read. + */ static void - handle_payload(std::shared_ptr &socket, const boost::system::error_code &ec, std::size_t bytes) { - BOOST_LOG(debug) << "handle_payload(): Handle read of size: "sv << bytes << " bytes"sv; + handle_plaintext_payload(std::shared_ptr &socket, const boost::system::error_code &ec, std::size_t bytes) { + BOOST_LOG(debug) << "handle_plaintext_payload(): Handle read of size: "sv << bytes << " bytes"sv; auto sock_close = util::fail_guard([&socket]() { boost::system::error_code ec; socket->sock.close(ec); if (ec) { - BOOST_LOG(error) << "RTSP: handle_payload(): Couldn't close tcp socket: "sv << ec.message(); + BOOST_LOG(error) << "RTSP: handle_plaintext_payload(): Couldn't close tcp socket: "sv << ec.message(); } }); if (ec) { - BOOST_LOG(error) << "RTSP: handle_payload(): Couldn't read from tcp socket: "sv << ec.message(); + BOOST_LOG(error) << "RTSP: handle_plaintext_payload(): Couldn't read from tcp socket: "sv << ec.message(); return; } @@ -122,14 +302,14 @@ namespace rtsp_stream { if (auto status = parseRtspMessage(req.get(), socket->msg_buf.data(), (std::size_t)(end - socket->msg_buf.data()))) { BOOST_LOG(error) << "Malformed RTSP message: ["sv << status << ']'; - respond(socket->sock, *socket->session, nullptr, 400, "BAD REQUEST", req->sequenceNumber, {}); + respond(socket->sock, *socket->session, nullptr, 400, "BAD REQUEST", 0, {}); return; } sock_close.disable(); auto fg = util::fail_guard([&socket]() { - socket->read_payload(); + socket->read_plaintext_payload(); }); auto content_length = 0; @@ -161,18 +341,24 @@ namespace rtsp_stream { socket->begin = end; } + /** + * @brief Handles the read of the header portion of a plaintext message. + * @param socket The socket the message was received on. + * @param ec The error code of the read operation. + * @param bytes The number of bytes read. + */ static void - handle_read(std::shared_ptr &socket, const boost::system::error_code &ec, std::size_t bytes) { - BOOST_LOG(debug) << "handle_read(): Handle read of size: "sv << bytes << " bytes"sv; + handle_read_plaintext(std::shared_ptr &socket, const boost::system::error_code &ec, std::size_t bytes) { + BOOST_LOG(debug) << "handle_read_plaintext(): Handle read of size: "sv << bytes << " bytes"sv; if (ec) { - BOOST_LOG(error) << "RTSP: handle_read(): Couldn't read from tcp socket: "sv << ec.message(); + BOOST_LOG(error) << "RTSP: handle_read_plaintext(): Couldn't read from tcp socket: "sv << ec.message(); boost::system::error_code ec; socket->sock.close(ec); if (ec) { - BOOST_LOG(error) << "RTSP: handle_read(): Couldn't close tcp socket: "sv << ec.message(); + BOOST_LOG(error) << "RTSP: handle_read_plaintext(): Couldn't close tcp socket: "sv << ec.message(); } return; @@ -201,7 +387,7 @@ namespace rtsp_stream { buf_size = end - socket->begin; fg.disable(); - handle_payload(socket, ec, buf_size); + handle_plaintext_payload(socket, ec, buf_size); } void @@ -280,7 +466,8 @@ namespace rtsp_stream { cmd_not_found(sock, session, std::move(req)); } - sock.shutdown(boost::asio::socket_base::shutdown_type::shutdown_both); + boost::system::error_code ec; + sock.shutdown(boost::asio::socket_base::shutdown_type::shutdown_both, ec); } void @@ -293,18 +480,27 @@ namespace rtsp_stream { return; } - auto launch_session { launch_event.view() }; + auto socket = std::move(next_socket); + + auto launch_session { launch_event.view(0s) }; if (launch_session) { // Associate the current RTSP session with this socket and start reading - auto socket = std::move(next_socket); socket->session = launch_session; socket->read(); + } + else { + // This can happen due to normal things like port scanning, so let's not make these visible by default + BOOST_LOG(debug) << "No pending session for incoming RTSP connection"sv; - next_socket = std::make_shared(ios, [this](tcp::socket &sock, launch_session_t &session, msg_t &&msg) { - handle_msg(sock, session, std::move(msg)); - }); + // If there is no session pending, close the connection immediately + boost::system::error_code ec; + socket->sock.close(ec); } + // Queue another asynchronous accept for the next incoming connection + next_socket = std::make_shared(ios, [this](tcp::socket &sock, launch_session_t &session, msg_t &&msg) { + handle_msg(sock, session, std::move(msg)); + }); acceptor.async_accept(next_socket->sock, [this](const auto &ec) { handle_accept(ec); }); @@ -343,7 +539,7 @@ namespace rtsp_stream { session_clear(uint32_t launch_session_id) { // We currently only support a single pending RTSP session, // so the ID should always match the one for that session. - auto launch_session = launch_event.view(); + auto launch_session = launch_event.view(0s); if (launch_session) { if (launch_session->id != launch_session_id) { BOOST_LOG(error) << "Attempted to clear unexpected session: "sv << launch_session_id << " vs "sv << launch_session->id; @@ -498,13 +694,53 @@ namespace rtsp_stream { << std::string_view { payload.first, (std::size_t) payload.second } << std::endl << "---End Response---"sv << std::endl; - std::string_view tmp_resp { raw_resp.get(), (size_t) serialized_len }; - - if (send(sock, tmp_resp)) { - return; + // Encrypt the RTSP message if encryption is enabled + if (session.rtsp_cipher) { + // We use the deterministic IV construction algorithm specified in NIST SP 800-38D + // Section 8.2.1. The sequence number is our "invocation" field and the 'RH' in the + // high bytes is the "fixed" field. Because each client provides their own unique + // key, our values in the fixed field need only uniquely identify each independent + // use of the client's key with AES-GCM in our code. + // + // The sequence number is 32 bits long which allows for 2^32 RTSP messages to be + // sent to each client before the IV repeats. + crypto::aes_t iv(12); + session.rtsp_iv_counter++; + std::copy_n((uint8_t *) &session.rtsp_iv_counter, sizeof(session.rtsp_iv_counter), std::begin(iv)); + iv[10] = 'H'; // Host originated + iv[11] = 'R'; // RTSP + + // Allocate the message with an empty header and reserved space for the payload + auto payload_length = serialized_len + payload.second; + std::vector message(sizeof(encrypted_rtsp_header_t)); + message.reserve(message.size() + payload_length); + + // Copy the complete plaintext into the message + std::copy_n(raw_resp.get(), serialized_len, std::back_inserter(message)); + std::copy_n(payload.first, payload.second, std::back_inserter(message)); + + // Initialize the message header + auto header = (encrypted_rtsp_header_t *) message.data(); + header->typeAndLength = util::endian::big(encrypted_rtsp_header_t::ENCRYPTED_MESSAGE_TYPE_BIT + payload_length); + header->sequenceNumber = util::endian::big(session.rtsp_iv_counter); + + // Encrypt the RTSP message in place + session.rtsp_cipher->encrypt(std::string_view { (const char *) header->payload(), (std::size_t) payload_length }, header->tag, &iv); + + // Send the full encrypted message + send(sock, std::string_view { (char *) message.data(), message.size() }); } + else { + std::string_view tmp_resp { raw_resp.get(), (size_t) serialized_len }; + + // Send the plaintext RTSP message header + if (send(sock, tmp_resp)) { + return; + } - send(sock, std::string_view { payload.first, (std::size_t) payload.second }); + // Send the plaintext RTSP message payload (if present) + send(sock, std::string_view { payload.first, (std::size_t) payload.second }); + } } void diff --git a/src/rtsp.h b/src/rtsp.h index 0167405964c..20bb8453998 100644 --- a/src/rtsp.h +++ b/src/rtsp.h @@ -31,6 +31,10 @@ namespace rtsp_stream { int surround_info; bool enable_hdr; bool enable_sops; + + std::optional rtsp_cipher; + std::string rtsp_url_scheme; + uint32_t rtsp_iv_counter; }; void From 3578727ff9516722a4f0b026cb2a935cd4a11fff Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Sat, 3 Feb 2024 13:57:43 -0600 Subject: [PATCH 032/182] Require RTSP encryption when encryption mode is set to mandatory This also lets us provide a friendly error to the client when it is rejected. --- src/network.cpp | 17 +++++++++++++++++ src/network.h | 8 ++++++++ src/nvhttp.cpp | 22 ++++++++++++++++++++++ src/rtsp.cpp | 18 ++---------------- 4 files changed, 49 insertions(+), 16 deletions(-) diff --git a/src/network.cpp b/src/network.cpp index 2a3d47fc1e1..6980af2a908 100644 --- a/src/network.cpp +++ b/src/network.cpp @@ -3,6 +3,7 @@ * @brief todo */ #include "network.h" +#include "config.h" #include "utility.h" #include @@ -174,6 +175,22 @@ namespace net { } } + /** + * @brief Returns the encryption mode for the given remote endpoint address. + * @param address The address used to look up the desired encryption mode. + * @return The WAN or LAN encryption mode, based on the provided address. + */ + int + encryption_mode_for_address(boost::asio::ip::address address) { + auto nettype = net::from_address(address.to_string()); + if (nettype == net::net_e::PC || nettype == net::net_e::LAN) { + return config::stream.lan_encryption_mode; + } + else { + return config::stream.wan_encryption_mode; + } + } + host_t host_create(af_e af, ENetAddress &addr, std::size_t peers, std::uint16_t port) { static std::once_flag enet_init_flag; diff --git a/src/network.h b/src/network.h index 57bc65e8fc3..0363f62ddf8 100644 --- a/src/network.h +++ b/src/network.h @@ -84,4 +84,12 @@ namespace net { */ std::string addr_to_url_escaped_string(boost::asio::ip::address address); + + /** + * @brief Returns the encryption mode for the given remote endpoint address. + * @param address The address used to look up the desired encryption mode. + * @return The WAN or LAN encryption mode, based on the provided address. + */ + int + encryption_mode_for_address(boost::asio::ip::address address); } // namespace net diff --git a/src/nvhttp.cpp b/src/nvhttp.cpp index ba7d068cd56..66de7a71b46 100644 --- a/src/nvhttp.cpp +++ b/src/nvhttp.cpp @@ -820,6 +820,17 @@ namespace nvhttp { host_audio = util::from_view(get_arg(args, "localAudioPlayMode")); auto launch_session = make_launch_session(host_audio, args); + auto encryption_mode = net::encryption_mode_for_address(request->remote_endpoint().address()); + if (!launch_session->rtsp_cipher && encryption_mode == config::ENCRYPTION_MODE_MANDATORY) { + BOOST_LOG(error) << "Rejecting client that cannot comply with mandatory encryption requirement"sv; + + tree.put("root..status_code", 403); + tree.put("root..status_message", "Encryption is mandatory for this host but unsupported by the client"); + tree.put("root.gamesession", 0); + + return; + } + if (appid > 0) { auto err = proc::proc.execute(appid, launch_session); if (err) { @@ -906,6 +917,17 @@ namespace nvhttp { auto launch_session = make_launch_session(host_audio, args); + auto encryption_mode = net::encryption_mode_for_address(request->remote_endpoint().address()); + if (!launch_session->rtsp_cipher && encryption_mode == config::ENCRYPTION_MODE_MANDATORY) { + BOOST_LOG(error) << "Rejecting client that cannot comply with mandatory encryption requirement"sv; + + tree.put("root..status_code", 403); + tree.put("root..status_message", "Encryption is mandatory for this host but unsupported by the client"); + tree.put("root.gamesession", 0); + + return; + } + tree.put("root..status_code", 200); tree.put("root.sessionUrl0", launch_session->rtsp_url_scheme + net::addr_to_url_escaped_string(request->local_endpoint().address()) + ':' + diff --git a/src/rtsp.cpp b/src/rtsp.cpp index ba3842c50ce..5b545428aab 100644 --- a/src/rtsp.cpp +++ b/src/rtsp.cpp @@ -789,14 +789,7 @@ namespace rtsp_stream { uint32_t encryption_flags_requested = SS_ENC_CONTROL_V2; // Determine the encryption desired for this remote endpoint - auto nettype = net::from_address(sock.remote_endpoint().address().to_string()); - int encryption_mode; - if (nettype == net::net_e::PC || nettype == net::net_e::LAN) { - encryption_mode = config::stream.lan_encryption_mode; - } - else { - encryption_mode = config::stream.wan_encryption_mode; - } + auto encryption_mode = net::encryption_mode_for_address(sock.remote_endpoint().address()); if (encryption_mode != config::ENCRYPTION_MODE_NEVER) { // Advertise support for video encryption if it's not disabled encryption_flags_supported |= SS_ENC_VIDEO; @@ -1080,14 +1073,7 @@ namespace rtsp_stream { } // Check that any required encryption is enabled - auto nettype = net::from_address(sock.remote_endpoint().address().to_string()); - int encryption_mode; - if (nettype == net::net_e::PC || nettype == net::net_e::LAN) { - encryption_mode = config::stream.lan_encryption_mode; - } - else { - encryption_mode = config::stream.wan_encryption_mode; - } + auto encryption_mode = net::encryption_mode_for_address(sock.remote_endpoint().address()); if (encryption_mode == config::ENCRYPTION_MODE_MANDATORY && (config.encryptionFlagsEnabled & (SS_ENC_VIDEO | SS_ENC_AUDIO)) != (SS_ENC_VIDEO | SS_ENC_AUDIO)) { BOOST_LOG(error) << "Rejecting client that cannot comply with mandatory encryption requirement"sv; From 71d88ef9c9ee2bb3fe0c02cebfb9e45551184e3e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 3 Feb 2024 21:56:18 +0000 Subject: [PATCH 033/182] build(deps): bump third-party/moonlight-common-c Bumps [third-party/moonlight-common-c](https://github.com/moonlight-stream/moonlight-common-c) from `3acba57` to `cbd0ec1`. - [Commits](https://github.com/moonlight-stream/moonlight-common-c/compare/3acba578b19c14a23f58a5f2488c23e5c19ac637...cbd0ec1b25edfb8ee8645fffa49ff95b6e04c70e) --- updated-dependencies: - dependency-name: third-party/moonlight-common-c dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- third-party/moonlight-common-c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/third-party/moonlight-common-c b/third-party/moonlight-common-c index 3acba578b19..cbd0ec1b25e 160000 --- a/third-party/moonlight-common-c +++ b/third-party/moonlight-common-c @@ -1 +1 @@ -Subproject commit 3acba578b19c14a23f58a5f2488c23e5c19ac637 +Subproject commit cbd0ec1b25edfb8ee8645fffa49ff95b6e04c70e From b7a39858071ca692e0d16534f2dbc9629962ba09 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sat, 3 Feb 2024 20:57:14 -0500 Subject: [PATCH 034/182] build(docker): add docker toolchain for clion (#2098) --- docker/clion-toolchain.dockerfile | 100 ++++++++++++++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 docker/clion-toolchain.dockerfile diff --git a/docker/clion-toolchain.dockerfile b/docker/clion-toolchain.dockerfile new file mode 100644 index 00000000000..bb4604f54f5 --- /dev/null +++ b/docker/clion-toolchain.dockerfile @@ -0,0 +1,100 @@ +# syntax=docker/dockerfile:1.4 +# artifacts: false +# platforms: linux/amd64 +# platforms_pr: linux/amd64 +# no-cache-filters: toolchain-base,toolchain +ARG BASE=ubuntu +ARG TAG=22.04 +FROM ${BASE}:${TAG} AS toolchain-base + +ENV DEBIAN_FRONTEND=noninteractive + +FROM toolchain-base as toolchain + +ARG TARGETPLATFORM +RUN echo "target_platform: ${TARGETPLATFORM}" + +SHELL ["/bin/bash", "-o", "pipefail", "-c"] +# install dependencies +RUN <<_DEPS +#!/bin/bash +set -e +apt-get update -y +apt-get install -y --no-install-recommends \ + build-essential \ + cmake=3.22.* \ + ca-certificates \ + gcc=4:11.2.* \ + g++=4:11.2.* \ + gdb \ + git \ + libayatana-appindicator3-dev \ + libavdevice-dev \ + libboost-filesystem-dev=1.74.* \ + libboost-locale-dev=1.74.* \ + libboost-log-dev=1.74.* \ + libboost-program-options-dev=1.74.* \ + libcap-dev \ + libcurl4-openssl-dev \ + libdrm-dev \ + libevdev-dev \ + libminiupnpc-dev \ + libnotify-dev \ + libnuma-dev \ + libopus-dev \ + libpulse-dev \ + libssl-dev \ + libva-dev \ + libvdpau-dev \ + libwayland-dev \ + libx11-dev \ + libxcb-shm0-dev \ + libxcb-xfixes0-dev \ + libxcb1-dev \ + libxfixes-dev \ + libxrandr-dev \ + libxtst-dev \ + udev \ + wget +if [[ "${TARGETPLATFORM}" == 'linux/amd64' ]]; then + apt-get install -y --no-install-recommends \ + libmfx-dev +fi +apt-get clean +rm -rf /var/lib/apt/lists/* +_DEPS + +#Install Node +# hadolint ignore=SC1091 +RUN <<_INSTALL_NODE +#!/bin/bash +set -e +node_version="20.9.0" +wget -qO- https://raw.githubusercontent.com/nvm-sh/nvm/master/install.sh | bash +source "$HOME/.nvm/nvm.sh" +nvm install "$node_version" +nvm use "$node_version" +nvm alias default "$node_version" +_INSTALL_NODE + +# install cuda +WORKDIR /build/cuda +# versions: https://developer.nvidia.com/cuda-toolkit-archive +ENV CUDA_VERSION="11.8.0" +ENV CUDA_BUILD="520.61.05" +# hadolint ignore=SC3010 +RUN <<_INSTALL_CUDA +#!/bin/bash +set -e +cuda_prefix="https://developer.download.nvidia.com/compute/cuda/" +cuda_suffix="" +if [[ "${TARGETPLATFORM}" == 'linux/arm64' ]]; then + cuda_suffix="_sbsa" +fi +url="${cuda_prefix}${CUDA_VERSION}/local_installers/cuda_${CUDA_VERSION}_${CUDA_BUILD}_linux${cuda_suffix}.run" +echo "cuda url: ${url}" +wget "$url" --progress=bar:force:noscroll -q --show-progress -O ./cuda.run +chmod a+x ./cuda.run +./cuda.run --silent --toolkit --toolkitpath=/usr/local --no-opengl-libs --no-man-page --no-drm +rm ./cuda.run +_INSTALL_CUDA From be6f856840979cbeb43fb1f3364e5131aae395c1 Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Sat, 3 Feb 2024 16:06:31 -0600 Subject: [PATCH 035/182] Fix DSCP tagging for dual-stack sockets on Linux --- src/platform/linux/misc.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/platform/linux/misc.cpp b/src/platform/linux/misc.cpp index 7bd38d6d960..6cfa9e49492 100644 --- a/src/platform/linux/misc.cpp +++ b/src/platform/linux/misc.cpp @@ -628,7 +628,10 @@ namespace platf { if (dscp_tagging) { int level; int option; - if (address.is_v6()) { + + // With dual-stack sockets, Linux uses IPV6_TCLASS for IPv6 traffic + // and IP_TOS for IPv4 traffic. + if (address.is_v6() && !address.to_v6().is_v4_mapped()) { level = SOL_IPV6; option = IPV6_TCLASS; } From dea1155983e4bd0a2c400b6f9caf7d39ce8d95bc Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Sat, 3 Feb 2024 16:52:10 -0600 Subject: [PATCH 036/182] Inform clients of graceful termination when Sunshine exits --- src/stream.cpp | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/stream.cpp b/src/stream.cpp index f99e68c12cc..b00de23e0c8 100644 --- a/src/stream.cpp +++ b/src/stream.cpp @@ -1035,8 +1035,12 @@ namespace stream { // This thread handles latency-sensitive control messages platf::adjust_thread_priority(platf::thread_priority_e::critical); - auto shutdown_event = mail::man->event(mail::broadcast_shutdown); - while (!shutdown_event->peek()) { + // Check for both the full shutdown event and the shutdown event for this + // broadcast to ensure we can inform connected clients of our graceful + // termination when we shut down. + auto shutdown_event = mail::man->event(mail::shutdown); + auto broadcast_shutdown_event = mail::man->event(mail::broadcast_shutdown); + while (!shutdown_event->peek() && !broadcast_shutdown_event->peek()) { bool has_session_awaiting_peer = false; { @@ -1045,6 +1049,11 @@ namespace stream { auto now = std::chrono::steady_clock::now(); KITTY_WHILE_LOOP(auto pos = std::begin(*server->_sessions), pos != std::end(*server->_sessions), { + // Don't perform additional session processing if we're shutting down + if (shutdown_event->peek() || broadcast_shutdown_event->peek()) { + break; + } + auto session = *pos; if (now > session->pingTimeout) { From 76e160bb0abf1b0b564c9ef512d43fc6d93253ed Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sun, 4 Feb 2024 18:37:44 -0500 Subject: [PATCH 037/182] style(macos): various code style fixes (#2086) --- src/platform/macos/av_audio.m | 4 ++-- src/platform/macos/av_img_t.h | 8 ++++---- src/platform/macos/av_video.m | 4 ++-- src/platform/macos/display.mm | 21 ++++++++++---------- src/platform/macos/input.cpp | 26 ++++++++++++------------- src/platform/macos/microphone.mm | 7 +++---- src/platform/macos/misc.h | 2 +- src/platform/macos/misc.mm | 14 ++++++------- src/platform/macos/nv12_zero_device.cpp | 12 +++++++----- src/platform/macos/nv12_zero_device.h | 6 +++--- src/platform/macos/publish.cpp | 2 +- 11 files changed, 53 insertions(+), 53 deletions(-) diff --git a/src/platform/macos/av_audio.m b/src/platform/macos/av_audio.m index af695179c7b..cb0c83c2e97 100644 --- a/src/platform/macos/av_audio.m +++ b/src/platform/macos/av_audio.m @@ -130,9 +130,9 @@ - (void)captureOutput:(AVCaptureOutput *)output CMSampleBufferGetAudioBufferListWithRetainedBlockBuffer(sampleBuffer, NULL, &audioBufferList, sizeof(audioBufferList), NULL, NULL, 0, &blockBuffer); - // NSAssert(audioBufferList.mNumberBuffers == 1, @"Expected interlveaved PCM format but buffer contained %u streams", audioBufferList.mNumberBuffers); + // NSAssert(audioBufferList.mNumberBuffers == 1, @"Expected interleaved PCM format but buffer contained %u streams", audioBufferList.mNumberBuffers); - // this is safe, because an interleaved PCM stream has exactly one buffer + // this is safe, because an interleaved PCM stream has exactly one buffer, // and we don't want to do sanity checks in a performance critical exec path AudioBuffer audioBuffer = audioBufferList.mBuffers[0]; diff --git a/src/platform/macos/av_img_t.h b/src/platform/macos/av_img_t.h index 6002bdcb221..fb0d7ecd0f5 100644 --- a/src/platform/macos/av_img_t.h +++ b/src/platform/macos/av_img_t.h @@ -11,7 +11,7 @@ namespace platf { struct av_sample_buf_t { - av_sample_buf_t(CMSampleBufferRef buf): + explicit av_sample_buf_t(CMSampleBufferRef buf): buf((CMSampleBufferRef) CFRetain(buf)) {} ~av_sample_buf_t() { @@ -22,12 +22,12 @@ namespace platf { }; struct av_pixel_buf_t { - av_pixel_buf_t(CVPixelBufferRef buf): + explicit av_pixel_buf_t(CVPixelBufferRef buf): buf((CVPixelBufferRef) CFRetain(buf)), locked(false) {} - uint8_t * - lock() { + [[nodiscard]] uint8_t * + lock() const { if (!locked) { CVPixelBufferLockBaseAddress(buf, kCVPixelBufferLock_ReadOnly); } diff --git a/src/platform/macos/av_video.m b/src/platform/macos/av_video.m index c64597656cc..5cdf5a9898e 100644 --- a/src/platform/macos/av_video.m +++ b/src/platform/macos/av_video.m @@ -37,8 +37,8 @@ - (id)initWithDisplay:(CGDirectDisplayID)displayID frameRate:(int)frameRate { self.displayID = displayID; self.pixelFormat = kCVPixelFormatType_32BGRA; - self.frameWidth = CGDisplayModeGetPixelWidth(mode); - self.frameHeight = CGDisplayModeGetPixelHeight(mode); + self.frameWidth = (int) CGDisplayModeGetPixelWidth(mode); + self.frameHeight = (int) CGDisplayModeGetPixelHeight(mode); self.minFrameDuration = CMTimeMake(1, frameRate); self.session = [[AVCaptureSession alloc] init]; self.videoOutputs = [[NSMapTable alloc] init]; diff --git a/src/platform/macos/display.mm b/src/platform/macos/display.mm index f424cf4ece2..5c3d37477f8 100644 --- a/src/platform/macos/display.mm +++ b/src/platform/macos/display.mm @@ -8,7 +8,6 @@ #include "src/platform/macos/nv12_zero_device.h" #include "src/config.h" -#include "src/main.h" // Avoid conflict between AVFoundation and libavutil both defining AVMediaType #define AVMediaType AVMediaType_FFmpeg @@ -21,10 +20,10 @@ using namespace std::literals; struct av_display_t: public display_t { - AVVideo *av_capture; - CGDirectDisplayID display_id; + AVVideo *av_capture {}; + CGDirectDisplayID display_id {}; - ~av_display_t() { + ~av_display_t() override { [av_capture release]; } @@ -45,9 +44,9 @@ av_img->pixel_buffer = std::make_shared(pixelBuffer); img_out->data = av_img->pixel_buffer->lock(); - img_out->width = CVPixelBufferGetWidth(pixelBuffer); - img_out->height = CVPixelBufferGetHeight(pixelBuffer); - img_out->row_pitch = CVPixelBufferGetBytesPerRow(pixelBuffer); + img_out->width = (int) CVPixelBufferGetWidth(pixelBuffer); + img_out->height = (int) CVPixelBufferGetHeight(pixelBuffer); + img_out->row_pitch = (int) CVPixelBufferGetBytesPerRow(pixelBuffer); img_out->pixel_pitch = img_out->row_pitch / img_out->width; if (!push_captured_image_cb(std::move(img_out), true)) { @@ -101,9 +100,9 @@ av_img->pixel_buffer = std::make_shared(pixelBuffer); img->data = av_img->pixel_buffer->lock(); - img->width = CVPixelBufferGetWidth(pixelBuffer); - img->height = CVPixelBufferGetHeight(pixelBuffer); - img->row_pitch = CVPixelBufferGetBytesPerRow(pixelBuffer); + img->width = (int) CVPixelBufferGetWidth(pixelBuffer); + img->height = (int) CVPixelBufferGetHeight(pixelBuffer); + img->row_pitch = (int) CVPixelBufferGetBytesPerRow(pixelBuffer); img->pixel_pitch = img->row_pitch / img->width; // returning false here stops capture backend @@ -177,7 +176,7 @@ display_names.reserve([display_array count]); [display_array enumerateObjectsUsingBlock:^(NSDictionary *_Nonnull obj, NSUInteger idx, BOOL *_Nonnull stop) { NSString *name = obj[@"name"]; - display_names.push_back(name.UTF8String); + display_names.emplace_back(name.UTF8String); }]; return display_names; diff --git a/src/platform/macos/input.cpp b/src/platform/macos/input.cpp index 66d0d130861..36484054a7a 100644 --- a/src/platform/macos/input.cpp +++ b/src/platform/macos/input.cpp @@ -21,17 +21,17 @@ namespace platf { struct macos_input_t { public: - CGDirectDisplayID display; - CGFloat displayScaling; - CGEventSourceRef source; + CGDirectDisplayID display {}; + CGFloat displayScaling {}; + CGEventSourceRef source {}; // keyboard related stuff - CGEventRef kb_event; - CGEventFlags kb_flags; + CGEventRef kb_event {}; + CGEventFlags kb_flags {}; // mouse related stuff - CGEventRef mouse_event; // mouse event source - bool mouse_down[3]; // mouse button status + CGEventRef mouse_event {}; // mouse event source + bool mouse_down[3] {}; // mouse button status std::chrono::steady_clock::steady_clock::time_point last_mouse_event[3][2]; // timestamp of last mouse events }; @@ -221,7 +221,7 @@ const KeyCodeMap kKeyCodesMap[] = { int keysym(int keycode) { - KeyCodeMap key_map; + KeyCodeMap key_map {}; key_map.win_keycode = keycode; const KeyCodeMap *temp_map = std::lower_bound( @@ -330,13 +330,13 @@ const KeyCodeMap kKeyCodesMap[] = { if (location.x < 0) location.x = 0; - if (location.x >= CGDisplayPixelsWide(display)) - location.x = CGDisplayPixelsWide(display) - 1; + if (location.x >= (double) CGDisplayPixelsWide(display)) + location.x = (double) CGDisplayPixelsWide(display) - 1; if (location.y < 0) location.y = 0; - if (location.y >= CGDisplayPixelsHigh(display)) - location.y = CGDisplayPixelsHigh(display) - 1; + if (location.y >= (double) CGDisplayPixelsHigh(display)) + location.y = (double) CGDisplayPixelsHigh(display) - 1; CGEventSetType(event, type); CGEventSetLocation(event, location); @@ -428,7 +428,7 @@ const KeyCodeMap kKeyCodesMap[] = { void scroll(input_t &input, int high_res_distance) { CGEventRef upEvent = CGEventCreateScrollWheelEvent( - NULL, + nullptr, kCGScrollEventUnitLine, 2, high_res_distance > 0 ? 1 : -1, high_res_distance); CGEventPost(kCGHIDEventTap, upEvent); diff --git a/src/platform/macos/microphone.mm b/src/platform/macos/microphone.mm index 854ca6faffe..1baf52606ab 100644 --- a/src/platform/macos/microphone.mm +++ b/src/platform/macos/microphone.mm @@ -6,15 +6,14 @@ #include "src/platform/macos/av_audio.h" #include "src/config.h" -#include "src/main.h" namespace platf { using namespace std::literals; struct av_mic_t: public mic_t { - AVAudio *av_audio_capture; + AVAudio *av_audio_capture {}; - ~av_mic_t() { + ~av_mic_t() override { [av_audio_capture release]; } @@ -42,7 +41,7 @@ }; struct macos_audio_control_t: public audio_control_t { - AVCaptureDevice *audio_capture_device; + AVCaptureDevice *audio_capture_device {}; public: int diff --git a/src/platform/macos/misc.h b/src/platform/macos/misc.h index a6fb1df3244..ca74f0ea478 100644 --- a/src/platform/macos/misc.h +++ b/src/platform/macos/misc.h @@ -9,7 +9,7 @@ #include namespace dyn { - typedef void (*apiproc)(void); + typedef void (*apiproc)(); int load(void *handle, const std::vector> &funcs, bool strict = true); diff --git a/src/platform/macos/misc.mm b/src/platform/macos/misc.mm index 2cfa2970163..76e50349f6c 100644 --- a/src/platform/macos/misc.mm +++ b/src/platform/macos/misc.mm @@ -4,7 +4,7 @@ */ // Required for IPV6_PKTINFO with Darwin headers -#ifndef __APPLE_USE_RFC_3542 +#ifndef __APPLE_USE_RFC_3542 // NOLINT(bugprone-reserved-identifier) #define __APPLE_USE_RFC_3542 1 #endif @@ -53,7 +53,7 @@ // Xcode 12.2 and later, these functions are not weakly linked and will never // be null, and therefore generate this warning. Since we are weakly linking // when compiling with earlier Xcode versions, the check for null is - // necessary and so we ignore the warning. + // necessary, and so we ignore the warning. #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunguarded-availability-new" #pragma clang diagnostic ignored "-Wtautological-pointer-compare" @@ -141,7 +141,7 @@ std::string mac_address; if (getifaddrs(&ifap) == 0) { - for (ifaptr = ifap; ifaptr != NULL; ifaptr = (ifaptr)->ifa_next) { + for (ifaptr = ifap; ifaptr != nullptr; ifaptr = (ifaptr)->ifa_next) { if (!strcmp((ifaptr)->ifa_name, pos->ifa_name) && (((ifaptr)->ifa_addr)->sa_family == AF_LINK)) { ptr = (unsigned char *) LLADDR((struct sockaddr_dl *) (ifaptr)->ifa_addr); char buff[100]; @@ -155,7 +155,7 @@ freeifaddrs(ifap); - if (ifaptr != NULL) { + if (ifaptr != nullptr) { BOOST_LOG(verbose) << "Found MAC of "sv << pos->ifa_name << ": "sv << mac_address; return mac_address; } @@ -336,7 +336,7 @@ union { char buf[std::max(CMSG_SPACE(sizeof(struct in_pktinfo)), CMSG_SPACE(sizeof(struct in6_pktinfo)))]; struct cmsghdr alignment; - } cmbuf; + } cmbuf {}; socklen_t cmbuflen = 0; msg.msg_control = cmbuf.buf; @@ -344,7 +344,7 @@ auto pktinfo_cm = CMSG_FIRSTHDR(&msg); if (send_info.source_address.is_v6()) { - struct in6_pktinfo pktInfo; + struct in6_pktinfo pktInfo {}; struct sockaddr_in6 saddr_v6 = to_sockaddr(send_info.source_address.to_v6(), 0); pktInfo.ipi6_addr = saddr_v6.sin6_addr; @@ -358,7 +358,7 @@ memcpy(CMSG_DATA(pktinfo_cm), &pktInfo, sizeof(pktInfo)); } else { - struct in_pktinfo pktInfo; + struct in_pktinfo pktInfo {}; struct sockaddr_in saddr_v4 = to_sockaddr(send_info.source_address.to_v4(), 0); pktInfo.ipi_spec_dst = saddr_v4.sin_addr; diff --git a/src/platform/macos/nv12_zero_device.cpp b/src/platform/macos/nv12_zero_device.cpp index f376bdcd413..c217c637661 100644 --- a/src/platform/macos/nv12_zero_device.cpp +++ b/src/platform/macos/nv12_zero_device.cpp @@ -2,6 +2,8 @@ * @file src/platform/macos/nv12_zero_device.cpp * @brief todo */ +#include + #include "src/platform/macos/nv12_zero_device.h" #include "src/video.h" @@ -24,7 +26,7 @@ namespace platf { int nv12_zero_device::convert(platf::img_t &img) { - av_img_t *av_img = (av_img_t *) &img; + auto *av_img = (av_img_t *) &img; // Release any existing CVPixelBuffer previously retained for encoding av_buffer_unref(&av_frame->buf[0]); @@ -34,7 +36,7 @@ namespace platf { // // The presence of the AVBufferRef allows FFmpeg to simply add a reference to the buffer // rather than having to perform a deep copy of the data buffers in avcodec_send_frame(). - av_frame->buf[0] = av_buffer_create((uint8_t *) CFRetain(av_img->pixel_buffer->buf), 0, free_buffer, NULL, 0); + av_frame->buf[0] = av_buffer_create((uint8_t *) CFRetain(av_img->pixel_buffer->buf), 0, free_buffer, nullptr, 0); // Place a CVPixelBufferRef at data[3] as required by AV_PIX_FMT_VIDEOTOOLBOX av_frame->data[3] = (uint8_t *) av_img->pixel_buffer->buf; @@ -54,15 +56,15 @@ namespace platf { } int - nv12_zero_device::init(void *display, pix_fmt_e pix_fmt, resolution_fn_t resolution_fn, pixel_format_fn_t pixel_format_fn) { + nv12_zero_device::init(void *display, pix_fmt_e pix_fmt, resolution_fn_t resolution_fn, const pixel_format_fn_t &pixel_format_fn) { pixel_format_fn(display, pix_fmt == pix_fmt_e::nv12 ? kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange : kCVPixelFormatType_420YpCbCr10BiPlanarVideoRange); this->display = display; - this->resolution_fn = resolution_fn; + this->resolution_fn = std::move(resolution_fn); - // we never use this pointer but it's existence is checked/used + // we never use this pointer, but its existence is checked/used // by the platform independent code data = this; diff --git a/src/platform/macos/nv12_zero_device.h b/src/platform/macos/nv12_zero_device.h index f1ee2702aad..b83210cb94f 100644 --- a/src/platform/macos/nv12_zero_device.h +++ b/src/platform/macos/nv12_zero_device.h @@ -26,12 +26,12 @@ namespace platf { using pixel_format_fn_t = std::function; int - init(void *display, pix_fmt_e pix_fmt, resolution_fn_t resolution_fn, pixel_format_fn_t pixel_format_fn); + init(void *display, pix_fmt_e pix_fmt, resolution_fn_t resolution_fn, const pixel_format_fn_t &pixel_format_fn); int - convert(img_t &img); + convert(img_t &img) override; int - set_frame(AVFrame *frame, AVBufferRef *hw_frames_ctx); + set_frame(AVFrame *frame, AVBufferRef *hw_frames_ctx) override; private: util::safe_ptr av_frame; diff --git a/src/platform/macos/publish.cpp b/src/platform/macos/publish.cpp index 8fca07b848a..55dec40a8e7 100644 --- a/src/platform/macos/publish.cpp +++ b/src/platform/macos/publish.cpp @@ -402,7 +402,7 @@ namespace platf::publish { public: std::thread poll_thread; - deinit_t(std::thread poll_thread): + explicit deinit_t(std::thread poll_thread): poll_thread { std::move(poll_thread) } {} ~deinit_t() override { From 040c3a69161a1505b4f936a41bfcc4d3516777a2 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sun, 4 Feb 2024 19:25:58 -0500 Subject: [PATCH 038/182] style(submodules): alphabetize gitmodules (#2104) --- .gitmodules | 44 ++++++++++++++++++++++---------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/.gitmodules b/.gitmodules index 798235eb864..67522f450ed 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,35 +1,43 @@ +[submodule "third-party/build-deps"] + path = third-party/build-deps + url = https://github.com/LizardByte/build-deps.git + branch = dist [submodule "third-party/moonlight-common-c"] path = third-party/moonlight-common-c url = https://github.com/moonlight-stream/moonlight-common-c.git branch = master -[submodule "third-party/Simple-Web-Server"] - path = third-party/Simple-Web-Server - url = https://gitlab.com/eidheim/Simple-Web-Server.git +[submodule "third-party/nanors"] + path = third-party/nanors + url = https://github.com/sleepybishop/nanors.git branch = master -[submodule "third-party/ViGEmClient"] - path = third-party/ViGEmClient - url = https://github.com/LizardByte/Virtual-Gamepad-Emulation-Client.git +[submodule "third-party/nlohmann_json"] + path = third-party/nlohmann_json + url = https://github.com/nlohmann/json branch = master [submodule "third-party/nv-codec-headers"] path = third-party/nv-codec-headers url = https://github.com/FFmpeg/nv-codec-headers branch = sdk/12.0 +[submodule "third-party/nvapi-open-source-sdk"] + path = third-party/nvapi-open-source-sdk + url = https://github.com/LizardByte/nvapi-open-source-sdk + branch = sdk +[submodule "third-party/Simple-Web-Server"] + path = third-party/Simple-Web-Server + url = https://gitlab.com/eidheim/Simple-Web-Server.git + branch = master [submodule "third-party/TPCircularBuffer"] path = third-party/TPCircularBuffer url = https://github.com/michaeltyson/TPCircularBuffer branch = master -[submodule "third-party/nanors"] - path = third-party/nanors - url = https://github.com/sleepybishop/nanors.git - branch = master [submodule "third-party/tray"] path = third-party/tray url = https://github.com/LizardByte/tray branch = master -[submodule "third-party/nvapi-open-source-sdk"] - path = third-party/nvapi-open-source-sdk - url = https://github.com/LizardByte/nvapi-open-source-sdk - branch = sdk +[submodule "third-party/ViGEmClient"] + path = third-party/ViGEmClient + url = https://github.com/LizardByte/Virtual-Gamepad-Emulation-Client.git + branch = master [submodule "third-party/wayland-protocols"] path = third-party/wayland-protocols url = https://gitlab.freedesktop.org/wayland/wayland-protocols @@ -38,11 +46,3 @@ path = third-party/wlr-protocols url = https://gitlab.freedesktop.org/wlroots/wlr-protocols branch = master -[submodule "third-party/build-deps"] - path = third-party/build-deps - url = https://github.com/LizardByte/build-deps.git - branch = dist -[submodule "third-party/nlohmann_json"] - path = third-party/nlohmann_json - url = https://github.com/nlohmann/json - branch = master From ff8c8ce3ab749d694705da3522b29ff3da0ac70b Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Mon, 5 Feb 2024 20:17:42 -0600 Subject: [PATCH 039/182] Skip encoder reprobing if no GPU changes have occurred --- src/platform/common.h | 7 ++++++ src/platform/linux/misc.cpp | 10 +++++++++ src/platform/macos/display.mm | 10 +++++++++ src/platform/windows/display_base.cpp | 31 +++++++++++++++++++++++++++ src/video.cpp | 10 +++++++-- 5 files changed, 66 insertions(+), 2 deletions(-) diff --git a/src/platform/common.h b/src/platform/common.h index 3c21e181e51..5056db8a62a 100644 --- a/src/platform/common.h +++ b/src/platform/common.h @@ -558,6 +558,13 @@ namespace platf { std::vector display_names(mem_type_e hwdevice_type); + /** + * @brief Returns if GPUs/drivers have changed since the last call to this function. + * @return `true` if a change has occurred or if it is unknown whether a change occurred. + */ + bool + needs_encoder_reenumeration(); + boost::process::child run_command(bool elevated, bool interactive, const std::string &cmd, boost::filesystem::path &working_dir, const boost::process::environment &env, FILE *file, std::error_code &ec, boost::process::group *group); diff --git a/src/platform/linux/misc.cpp b/src/platform/linux/misc.cpp index 6cfa9e49492..2c6ba16368a 100644 --- a/src/platform/linux/misc.cpp +++ b/src/platform/linux/misc.cpp @@ -773,6 +773,16 @@ namespace platf { return {}; } + /** + * @brief Returns if GPUs/drivers have changed since the last call to this function. + * @return `true` if a change has occurred or if it is unknown whether a change occurred. + */ + bool + needs_encoder_reenumeration() { + // We don't track GPU state, so we will always reenumerate. Fortunately, it is fast on Linux. + return true; + } + std::shared_ptr display(mem_type_e hwdevice_type, const std::string &display_name, const video::config_t &config) { #ifdef SUNSHINE_BUILD_CUDA diff --git a/src/platform/macos/display.mm b/src/platform/macos/display.mm index 5c3d37477f8..a9f6bf40dd4 100644 --- a/src/platform/macos/display.mm +++ b/src/platform/macos/display.mm @@ -181,4 +181,14 @@ return display_names; } + + /** + * @brief Returns if GPUs/drivers have changed since the last call to this function. + * @return `true` if a change has occurred or if it is unknown whether a change occurred. + */ + bool + needs_encoder_reenumeration() { + // We don't track GPU state, so we will always reenumerate. Fortunately, it is fast on macOS. + return true; + } } // namespace platf diff --git a/src/platform/windows/display_base.cpp b/src/platform/windows/display_base.cpp index 64954bbf283..95e58b592a6 100644 --- a/src/platform/windows/display_base.cpp +++ b/src/platform/windows/display_base.cpp @@ -1124,4 +1124,35 @@ namespace platf { return display_names; } + /** + * @brief Returns if GPUs/drivers have changed since the last call to this function. + * @return `true` if a change has occurred or if it is unknown whether a change occurred. + */ + bool + needs_encoder_reenumeration() { + // Serialize access to the static DXGI factory + static std::mutex reenumeration_state_lock; + auto lg = std::lock_guard(reenumeration_state_lock); + + // Keep a reference to the DXGI factory, which will keep track of changes internally. + static dxgi::factory1_t factory; + if (!factory || !factory->IsCurrent()) { + factory.reset(); + + auto status = CreateDXGIFactory1(IID_IDXGIFactory1, (void **) &factory); + if (FAILED(status)) { + BOOST_LOG(error) << "Failed to create DXGIFactory1 [0x"sv << util::hex(status).to_string_view() << ']'; + factory.release(); + } + + // Always request reenumeration on the first streaming session just to ensure we + // can deal with any initialization races that may occur when the system is booting. + BOOST_LOG(info) << "Encoder reenumeration is required"sv; + return true; + } + else { + // The DXGI factory from last time is still current, so no encoder changes have occurred. + return false; + } + } } // namespace platf diff --git a/src/video.cpp b/src/video.cpp index d0d2e4b93c0..6706f0e4130 100644 --- a/src/video.cpp +++ b/src/video.cpp @@ -276,7 +276,7 @@ namespace video { enum flag_e : uint32_t { DEFAULT = 0, - PARALLEL_ENCODING = 1 << 1, + PARALLEL_ENCODING = 1 << 1, // Capture and encoding can run concurrently on separate threads H264_ONLY = 1 << 2, // When HEVC is too heavy LIMITED_GOP_SIZE = 1 << 3, // Some encoders don't like it when you have an infinite GOP_SIZE. *cough* VAAPI *cough* SINGLE_SLICE_ONLY = 1 << 4, // Never use multiple slices <-- Older intel iGPU's ruin it for everyone else :P @@ -284,6 +284,7 @@ namespace video { RELAXED_COMPLIANCE = 1 << 6, // Use FF_COMPLIANCE_UNOFFICIAL compliance mode NO_RC_BUF_LIMIT = 1 << 7, // Don't set rc_buffer_size REF_FRAMES_INVALIDATION = 1 << 8, // Support reference frames invalidation + ALWAYS_REPROBE = 1 << 9, // This is an encoder of last resort and we want to aggressively probe for a better one }; struct encoder_platform_formats_t { @@ -922,7 +923,7 @@ namespace video { std::make_optional("qp"s, &config::video.qp), "libx264"s, }, - H264_ONLY | PARALLEL_ENCODING + H264_ONLY | PARALLEL_ENCODING | ALWAYS_REPROBE }; #ifdef __linux__ @@ -2579,6 +2580,11 @@ namespace video { probe_encoders() { auto encoder_list = encoders; + // If we already have a good encoder, check to see if another probe is required + if (chosen_encoder && !(chosen_encoder->flags & ALWAYS_REPROBE) && !platf::needs_encoder_reenumeration()) { + return 0; + } + // Restart encoder selection auto previous_encoder = chosen_encoder; chosen_encoder = nullptr; From 38d45b39aea01eb9cba8e66d6b9f2c127c2558b8 Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Wed, 24 Jan 2024 21:46:28 -0600 Subject: [PATCH 040/182] Don't populate QP rate control fallbacks for encoders guaranteed to support CBR/VBR It just needlessly slows down encoder probing when the encoder or codec is not supported. --- src/video.cpp | 36 +++++++++++++++++++++--------------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/src/video.cpp b/src/video.cpp index 6706f0e4130..b12e87670bf 100644 --- a/src/video.cpp +++ b/src/video.cpp @@ -379,6 +379,10 @@ namespace video { std::vector sdr_options; std::vector hdr_options; std::vector fallback_options; + + // QP option to set in the case that CBR/VBR is not supported + // by the encoder. If CBR/VBR is guaranteed to be supported, + // don't specify this option to avoid wasteful encoder probing. std::optional qp; std::string name; @@ -595,7 +599,7 @@ namespace video { {}, // Fallback options {}, - std::nullopt, // QP + std::nullopt, // QP rate control fallback "av1_nvenc"s, }, { @@ -607,7 +611,7 @@ namespace video { {}, // Fallback options {}, - std::nullopt, // QP + std::nullopt, // QP rate control fallback "hevc_nvenc"s, }, { @@ -619,7 +623,7 @@ namespace video { {}, // Fallback options {}, - std::nullopt, // QP + std::nullopt, // QP rate control fallback "h264_nvenc"s, }, PARALLEL_ENCODING | REF_FRAMES_INVALIDATION // flags @@ -660,7 +664,7 @@ namespace video { {}, // Fallback options {}, - std::nullopt, + std::nullopt, // QP rate control fallback "av1_nvenc"s, }, { @@ -684,7 +688,7 @@ namespace video { { "profile"s, (int) nv::profile_hevc_e::main_10 }, }, {}, // Fallback options - std::nullopt, + std::nullopt, // QP rate control fallback "hevc_nvenc"s, }, { @@ -705,7 +709,7 @@ namespace video { }, {}, // HDR-specific options {}, // Fallback options - std::make_optional({ "qp"s, &config::video.qp }), + std::nullopt, // QP rate control fallback "h264_nvenc"s, }, PARALLEL_ENCODING @@ -735,7 +739,7 @@ namespace video { {}, // Fallback options {}, - std::make_optional({ "qp"s, &config::video.qp }), + std::nullopt, // QP rate control fallback "av1_qsv"s, }, { @@ -759,7 +763,7 @@ namespace video { }, // Fallback options {}, - std::make_optional({ "qp"s, &config::video.qp }), + std::nullopt, // QP rate control fallback "hevc_qsv"s, }, { @@ -786,7 +790,7 @@ namespace video { { { "low_power"s, 0 }, // Some old/low-end Intel GPUs don't support low power encoding }, - std::make_optional({ "qp"s, &config::video.qp }), + std::nullopt, // QP rate control fallback "h264_qsv"s, }, PARALLEL_ENCODING | CBR_WITH_VBR | RELAXED_COMPLIANCE | NO_RC_BUF_LIMIT @@ -812,7 +816,7 @@ namespace video { {}, // SDR-specific options {}, // HDR-specific options {}, // Fallback options - std::make_optional({ "qp_p"s, &config::video.qp }), + std::nullopt, // QP rate control fallback "av1_amf"s, }, { @@ -833,7 +837,7 @@ namespace video { {}, // SDR-specific options {}, // HDR-specific options {}, // Fallback options - std::make_optional({ "qp_p"s, &config::video.qp }), + std::nullopt, // QP rate control fallback "hevc_amf"s, }, { @@ -857,7 +861,7 @@ namespace video { { { "usage"s, 2 /* AMF_VIDEO_ENCODER_USAGE_LOW_LATENCY */ }, // Workaround for https://github.com/GPUOpen-LibrariesAndSDKs/AMF/issues/410 }, - std::make_optional({ "qp_p"s, &config::video.qp }), + std::nullopt, // QP rate control fallback "h264_amf"s, }, PARALLEL_ENCODING @@ -883,7 +887,9 @@ namespace video { {}, // SDR-specific options {}, // HDR-specific options {}, // Fallback options - std::make_optional("qp"s, &config::video.qp), + + // QP rate control fallback + std::nullopt, #ifdef ENABLE_BROKEN_AV1_ENCODER // Due to bugs preventing on-demand IDR frames from working and very poor @@ -908,7 +914,7 @@ namespace video { {}, // SDR-specific options {}, // HDR-specific options {}, // Fallback options - std::make_optional("qp"s, &config::video.qp), + std::nullopt, // QP rate control fallback "libx265"s, }, { @@ -920,7 +926,7 @@ namespace video { {}, // SDR-specific options {}, // HDR-specific options {}, // Fallback options - std::make_optional("qp"s, &config::video.qp), + std::nullopt, // QP rate control fallback "libx264"s, }, H264_ONLY | PARALLEL_ENCODING | ALWAYS_REPROBE From aee88f336a84b700da66af000976d6d49508522b Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Wed, 24 Jan 2024 21:49:25 -0600 Subject: [PATCH 041/182] Don't fall back to undefined ref frame count on HEVC/AV1 if H.264 succeded with ref frame count specified I don't think there are any encoders out there that support this for some codecs and not others. --- src/video.cpp | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/video.cpp b/src/video.cpp index b12e87670bf..53ef6ecb255 100644 --- a/src/video.cpp +++ b/src/video.cpp @@ -2479,7 +2479,13 @@ namespace video { if (disp->is_codec_supported(encoder.hevc.name, config_autoselect)) { retry_hevc: auto max_ref_frames_hevc = validate_config(disp, encoder, config_max_ref_frames); - auto autoselect_hevc = max_ref_frames_hevc >= 0 ? max_ref_frames_hevc : validate_config(disp, encoder, config_autoselect); + + // If H.264 succeeded with max ref frames specified, assume that we can count on + // HEVC to also succeed with max ref frames specified if HEVC is supported. + auto autoselect_hevc = (max_ref_frames_hevc >= 0 || max_ref_frames_h264 >= 0) ? + max_ref_frames_hevc : + validate_config(disp, encoder, config_autoselect); + if (autoselect_hevc < 0 && 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(); @@ -2511,7 +2517,13 @@ namespace video { if (disp->is_codec_supported(encoder.av1.name, config_autoselect)) { retry_av1: auto max_ref_frames_av1 = validate_config(disp, encoder, config_max_ref_frames); - auto autoselect_av1 = max_ref_frames_av1 >= 0 ? max_ref_frames_av1 : validate_config(disp, encoder, config_autoselect); + + // If H.264 succeeded with max ref frames specified, assume that we can count on + // AV1 to also succeed with max ref frames specified if AV1 is supported. + auto autoselect_av1 = (max_ref_frames_av1 >= 0 || max_ref_frames_h264 >= 0) ? + max_ref_frames_av1 : + validate_config(disp, encoder, config_autoselect); + if (autoselect_av1 < 0 && encoder.av1.qp && encoder.av1[encoder_t::CBR]) { // It's possible the encoder isn't accepting Constant Bit Rate. Turn off CBR and make another attempt encoder.av1.capabilities.set(); From 8373a8b94788f943f25d6d1d7d5b3720d3591e1d Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Tue, 6 Feb 2024 19:30:22 -0600 Subject: [PATCH 042/182] Avoid recreating the display when no display parameters have changed --- src/video.cpp | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/video.cpp b/src/video.cpp index 53ef6ecb255..1914faaa181 100644 --- a/src/video.cpp +++ b/src/video.cpp @@ -2353,12 +2353,7 @@ namespace video { }; int - validate_config(std::shared_ptr &disp, const encoder_t &encoder, const config_t &config) { - reset_display(disp, encoder.platform_formats->dev_type, config::video.output_name, config); - if (!disp) { - return -1; - } - + validate_config(std::shared_ptr disp, const encoder_t &encoder, const config_t &config) { auto encode_device = make_encode_device(*disp, encoder, config); if (!encode_device) { return -1; @@ -2561,6 +2556,12 @@ namespace video { hevc.videoFormat = 1; av1.videoFormat = 2; + // Reset the display since we're switching from SDR to HDR + reset_display(disp, encoder.platform_formats->dev_type, config::video.output_name, config); + if (!disp) { + return false; + } + // 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; From 0aa4f06c39beee7a1a0b984c510f04a0fc1b2189 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Wed, 7 Feb 2024 09:59:24 -0500 Subject: [PATCH 043/182] refactor(logging): separate logging from main (#2110) --- cmake/compile_definitions/common.cmake | 2 + docs/source/source_code/src/logging.rst | 5 ++ src/audio.cpp | 1 + src/cbs.cpp | 2 +- src/config.cpp | 1 + src/confighttp.cpp | 1 + src/httpcommon.cpp | 1 + src/input.cpp | 1 + src/logging.cpp | 73 +++++++++++++++++++ src/logging.h | 27 +++++++ src/main.cpp | 57 +-------------- src/main.h | 14 ---- src/nvenc/nvenc_base.cpp | 1 + src/nvenc/nvenc_d3d11.cpp | 2 + src/nvenc/nvenc_utils.cpp | 2 + src/nvhttp.cpp | 1 + src/platform/common.h | 2 +- src/platform/linux/audio.cpp | 1 + src/platform/linux/cuda.cpp | 1 + src/platform/linux/graphics.cpp | 2 + src/platform/linux/graphics.h | 2 +- src/platform/linux/input.cpp | 1 + src/platform/linux/kmsgrab.cpp | 1 + src/platform/linux/misc.cpp | 1 + src/platform/linux/publish.cpp | 1 + src/platform/linux/vaapi.cpp | 2 +- src/platform/linux/wayland.cpp | 2 +- src/platform/linux/wlgrab.cpp | 1 + src/platform/linux/x11grab.cpp | 1 + src/platform/macos/display.mm | 1 + src/platform/macos/input.cpp | 2 +- src/platform/macos/microphone.mm | 1 + src/platform/macos/misc.mm | 1 + src/platform/macos/publish.cpp | 1 + src/platform/windows/audio.cpp | 2 +- src/platform/windows/display_base.cpp | 1 + src/platform/windows/display_ram.cpp | 2 +- src/platform/windows/display_vram.cpp | 2 +- src/platform/windows/input.cpp | 1 + src/platform/windows/misc.cpp | 1 + .../windows/nvprefs/nvprefs_common.cpp | 2 +- .../windows/nvprefs/nvprefs_interface.cpp | 6 +- src/platform/windows/publish.cpp | 1 + src/process.cpp | 1 + src/rtsp.cpp | 1 + src/stream.cpp | 1 + src/system_tray.cpp | 1 + src/upnp.cpp | 1 + src/video.cpp | 1 + src/video_colorspace.cpp | 2 +- 50 files changed, 161 insertions(+), 80 deletions(-) create mode 100644 docs/source/source_code/src/logging.rst create mode 100644 src/logging.cpp create mode 100644 src/logging.h diff --git a/cmake/compile_definitions/common.cmake b/cmake/compile_definitions/common.cmake index b523911821f..b3e84b3ef7a 100644 --- a/cmake/compile_definitions/common.cmake +++ b/cmake/compile_definitions/common.cmake @@ -45,6 +45,8 @@ set(SUNSHINE_TARGET_FILES "${CMAKE_SOURCE_DIR}/src/uuid.h" "${CMAKE_SOURCE_DIR}/src/config.h" "${CMAKE_SOURCE_DIR}/src/config.cpp" + "${CMAKE_SOURCE_DIR}/src/logging.cpp" + "${CMAKE_SOURCE_DIR}/src/logging.h" "${CMAKE_SOURCE_DIR}/src/main.cpp" "${CMAKE_SOURCE_DIR}/src/main.h" "${CMAKE_SOURCE_DIR}/src/crypto.cpp" diff --git a/docs/source/source_code/src/logging.rst b/docs/source/source_code/src/logging.rst new file mode 100644 index 00000000000..6b037c20e46 --- /dev/null +++ b/docs/source/source_code/src/logging.rst @@ -0,0 +1,5 @@ +logging +======= + +.. doxygenfile:: logging.h + :allow-dot-graphs: diff --git a/src/audio.cpp b/src/audio.cpp index 44791122b72..a3555eaa080 100644 --- a/src/audio.cpp +++ b/src/audio.cpp @@ -10,6 +10,7 @@ #include "audio.h" #include "config.h" +#include "logging.h" #include "main.h" #include "thread_safe.h" #include "utility.h" diff --git a/src/cbs.cpp b/src/cbs.cpp index c06b7c4a8b5..a2ba6f2517e 100644 --- a/src/cbs.cpp +++ b/src/cbs.cpp @@ -11,7 +11,7 @@ extern "C" { } #include "cbs.h" -#include "main.h" +#include "logging.h" #include "utility.h" using namespace std::literals; diff --git a/src/config.cpp b/src/config.cpp index 2bdc0e4e91d..9560c7cec35 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -15,6 +15,7 @@ #include #include "config.h" +#include "logging.h" #include "main.h" #include "nvhttp.h" #include "rtsp.h" diff --git a/src/confighttp.cpp b/src/confighttp.cpp index 2c7d3b21a39..45062254421 100644 --- a/src/confighttp.cpp +++ b/src/confighttp.cpp @@ -30,6 +30,7 @@ #include "confighttp.h" #include "crypto.h" #include "httpcommon.h" +#include "logging.h" #include "main.h" #include "network.h" #include "nvhttp.h" diff --git a/src/httpcommon.cpp b/src/httpcommon.cpp index b6ea0958105..849fd81af79 100644 --- a/src/httpcommon.cpp +++ b/src/httpcommon.cpp @@ -22,6 +22,7 @@ #include "config.h" #include "crypto.h" #include "httpcommon.h" +#include "logging.h" #include "main.h" #include "network.h" #include "nvhttp.h" diff --git a/src/input.cpp b/src/input.cpp index 63c10d8be77..b7416fffac2 100644 --- a/src/input.cpp +++ b/src/input.cpp @@ -18,6 +18,7 @@ extern "C" { #include "config.h" #include "input.h" +#include "logging.h" #include "main.h" #include "platform/common.h" #include "thread_pool.h" diff --git a/src/logging.cpp b/src/logging.cpp new file mode 100644 index 00000000000..70a2ae82a00 --- /dev/null +++ b/src/logging.cpp @@ -0,0 +1,73 @@ +/** + * @file src/logging.cpp + * @brief Logging implementation file for the Sunshine application. + */ + +// standard includes +#include + +// lib includes +#include +#include +#include +#include +#include + +// local includes +#include "logging.h" + +using namespace std::literals; + +namespace bl = boost::log; + +boost::shared_ptr> sink; + +bl::sources::severity_logger verbose(0); // Dominating output +bl::sources::severity_logger debug(1); // Follow what is happening +bl::sources::severity_logger info(2); // Should be informed about +bl::sources::severity_logger warning(3); // Strange events +bl::sources::severity_logger error(4); // Recoverable errors +bl::sources::severity_logger fatal(5); // Unrecoverable errors + +/** + * @brief Flush the log. + * + * EXAMPLES: + * ```cpp + * log_flush(); + * ``` + */ +void +log_flush() { + sink->flush(); +} + +/** + * @brief Print help to stdout. + * @param name The name of the program. + * + * EXAMPLES: + * ```cpp + * print_help("sunshine"); + * ``` + */ +void +print_help(const char *name) { + std::cout + << "Usage: "sv << name << " [options] [/path/to/configuration_file] [--cmd]"sv << std::endl + << " Any configurable option can be overwritten with: \"name=value\""sv << std::endl + << std::endl + << " Note: The configuration will be created if it doesn't exist."sv << std::endl + << std::endl + << " --help | print help"sv << std::endl + << " --creds username password | set user credentials for the Web manager"sv << std::endl + << " --version | print the version of sunshine"sv << std::endl + << std::endl + << " flags"sv << std::endl + << " -0 | Read PIN from stdin"sv << std::endl + << " -1 | Do not load previously saved state and do retain any state after shutdown"sv << std::endl + << " | Effectively starting as if for the first time without overwriting any pairings with your devices"sv << std::endl + << " -2 | Force replacement of headers in video stream"sv << std::endl + << " -p | Enable/Disable UPnP"sv << std::endl + << std::endl; +} diff --git a/src/logging.h b/src/logging.h new file mode 100644 index 00000000000..47a08555a0b --- /dev/null +++ b/src/logging.h @@ -0,0 +1,27 @@ +/** + * @file src/logging.h + * @brief Logging header file for the Sunshine application. + */ + +// macros +#pragma once + +// lib includes +#include +#include + +extern boost::shared_ptr> sink; +using text_sink = boost::log::sinks::asynchronous_sink; + +extern boost::log::sources::severity_logger verbose; +extern boost::log::sources::severity_logger debug; +extern boost::log::sources::severity_logger info; +extern boost::log::sources::severity_logger warning; +extern boost::log::sources::severity_logger error; +extern boost::log::sources::severity_logger fatal; + +// functions +void +log_flush(); +void +print_help(const char *name); diff --git a/src/main.cpp b/src/main.cpp index 45febf7025b..e1a0073161a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -21,6 +21,7 @@ #include "config.h" #include "confighttp.h" #include "httpcommon.h" +#include "logging.h" #include "main.h" #include "nvhttp.h" #include "platform/common.h" @@ -52,18 +53,9 @@ nvprefs::nvprefs_interface nvprefs_instance; #endif thread_pool_util::ThreadPool task_pool; -bl::sources::severity_logger verbose(0); // Dominating output -bl::sources::severity_logger debug(1); // Follow what is happening -bl::sources::severity_logger info(2); // Should be informed about -bl::sources::severity_logger warning(3); // Strange events -bl::sources::severity_logger error(4); // Recoverable errors -bl::sources::severity_logger fatal(5); // Unrecoverable errors bool display_cursor = true; -using text_sink = bl::sinks::asynchronous_sink; -boost::shared_ptr sink; - struct NoDelete { void operator()(void *) {} @@ -71,36 +63,6 @@ struct NoDelete { BOOST_LOG_ATTRIBUTE_KEYWORD(severity, "Severity", int) -/** - * @brief Print help to stdout. - * @param name The name of the program. - * - * EXAMPLES: - * ```cpp - * print_help("sunshine"); - * ``` - */ -void -print_help(const char *name) { - std::cout - << "Usage: "sv << name << " [options] [/path/to/configuration_file] [--cmd]"sv << std::endl - << " Any configurable option can be overwritten with: \"name=value\""sv << std::endl - << std::endl - << " Note: The configuration will be created if it doesn't exist."sv << std::endl - << std::endl - << " --help | print help"sv << std::endl - << " --creds username password | set user credentials for the Web manager"sv << std::endl - << " --version | print the version of sunshine"sv << std::endl - << std::endl - << " flags"sv << std::endl - << " -0 | Read PIN from stdin"sv << std::endl - << " -1 | Do not load previously saved state and do retain any state after shutdown"sv << std::endl - << " | Effectively starting as if for the first time without overwriting any pairings with your devices"sv << std::endl - << " -2 | Force replacement of headers in video stream"sv << std::endl - << " -p | Enable/Disable UPnP"sv << std::endl - << std::endl; -} - namespace help { int entry(const char *name, int argc, char *argv[]) { @@ -404,19 +366,6 @@ launch_ui_with_path(std::string path) { platf::open_url(url); } -/** - * @brief Flush the log. - * - * EXAMPLES: - * ```cpp - * log_flush(); - * ``` - */ -void -log_flush() { - sink->flush(); -} - std::map> signal_handlers; void on_signal_forwarder(int sig) { @@ -488,6 +437,9 @@ SessionMonitorWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { */ int main(int argc, char *argv[]) { + // the version should be printed to the log before anything else + BOOST_LOG(info) << PROJECT_NAME << " version: " << PROJECT_VER; + lifetime::argv = argv; task_pool_util::TaskPool::task_id_t force_shutdown = nullptr; @@ -689,7 +641,6 @@ main(int argc, char *argv[]) { #endif - BOOST_LOG(info) << PROJECT_NAME << " version: " << PROJECT_VER << std::endl; task_pool.start(1); #if defined SUNSHINE_TRAY && SUNSHINE_TRAY >= 1 diff --git a/src/main.h b/src/main.h index 8eb3e7ee277..0114586dde3 100644 --- a/src/main.h +++ b/src/main.h @@ -10,9 +10,6 @@ #include #include -// lib includes -#include - // local includes #include "thread_pool.h" #include "thread_safe.h" @@ -26,20 +23,9 @@ extern nvprefs::nvprefs_interface nvprefs_instance; extern thread_pool_util::ThreadPool task_pool; extern bool display_cursor; -extern boost::log::sources::severity_logger verbose; -extern boost::log::sources::severity_logger debug; -extern boost::log::sources::severity_logger info; -extern boost::log::sources::severity_logger warning; -extern boost::log::sources::severity_logger error; -extern boost::log::sources::severity_logger fatal; - // functions int main(int argc, char *argv[]); -void -log_flush(); -void -print_help(const char *name); std::string read_file(const char *path); int diff --git a/src/nvenc/nvenc_base.cpp b/src/nvenc/nvenc_base.cpp index 205a163dd26..63107324f37 100644 --- a/src/nvenc/nvenc_base.cpp +++ b/src/nvenc/nvenc_base.cpp @@ -1,6 +1,7 @@ #include "nvenc_base.h" #include "src/config.h" +#include "src/logging.h" #include "src/utility.h" namespace { diff --git a/src/nvenc/nvenc_d3d11.cpp b/src/nvenc/nvenc_d3d11.cpp index e86d2268634..7db6fdd2fa4 100644 --- a/src/nvenc/nvenc_d3d11.cpp +++ b/src/nvenc/nvenc_d3d11.cpp @@ -1,3 +1,5 @@ +#include "src/logging.h" + #ifdef _WIN32 #include "nvenc_d3d11.h" diff --git a/src/nvenc/nvenc_utils.cpp b/src/nvenc/nvenc_utils.cpp index 261e90969a2..1b8b7ec9f10 100644 --- a/src/nvenc/nvenc_utils.cpp +++ b/src/nvenc/nvenc_utils.cpp @@ -1,3 +1,5 @@ +#include + #include "nvenc_utils.h" namespace nvenc { diff --git a/src/nvhttp.cpp b/src/nvhttp.cpp index 66de7a71b46..04cfd5b38d5 100644 --- a/src/nvhttp.cpp +++ b/src/nvhttp.cpp @@ -23,6 +23,7 @@ #include "config.h" #include "crypto.h" #include "httpcommon.h" +#include "logging.h" #include "main.h" #include "network.h" #include "nvhttp.h" diff --git a/src/platform/common.h b/src/platform/common.h index 5056db8a62a..7a4102ad5ba 100644 --- a/src/platform/common.h +++ b/src/platform/common.h @@ -10,7 +10,7 @@ #include #include -#include "src/main.h" +#include "src/logging.h" #include "src/thread_safe.h" #include "src/utility.h" #include "src/video_colorspace.h" diff --git a/src/platform/linux/audio.cpp b/src/platform/linux/audio.cpp index e31f539f615..577287b77ef 100644 --- a/src/platform/linux/audio.cpp +++ b/src/platform/linux/audio.cpp @@ -14,6 +14,7 @@ #include "src/platform/common.h" #include "src/config.h" +#include "src/logging.h" #include "src/main.h" #include "src/thread_safe.h" diff --git a/src/platform/linux/cuda.cpp b/src/platform/linux/cuda.cpp index 0f1d64132d5..856fecc657a 100644 --- a/src/platform/linux/cuda.cpp +++ b/src/platform/linux/cuda.cpp @@ -19,6 +19,7 @@ extern "C" { #include "cuda.h" #include "graphics.h" +#include "src/logging.h" #include "src/main.h" #include "src/utility.h" #include "src/video.h" diff --git a/src/platform/linux/graphics.cpp b/src/platform/linux/graphics.cpp index e53483a9a75..ce0359a98fd 100644 --- a/src/platform/linux/graphics.cpp +++ b/src/platform/linux/graphics.cpp @@ -3,6 +3,8 @@ * @brief todo */ #include "graphics.h" +#include "src/logging.h" +#include "src/main.h" #include "src/video.h" #include diff --git a/src/platform/linux/graphics.h b/src/platform/linux/graphics.h index d2759f7d98b..a45b26fd173 100644 --- a/src/platform/linux/graphics.h +++ b/src/platform/linux/graphics.h @@ -11,7 +11,7 @@ #include #include "misc.h" -#include "src/main.h" +#include "src/logging.h" #include "src/platform/common.h" #include "src/utility.h" #include "src/video_colorspace.h" diff --git a/src/platform/linux/input.cpp b/src/platform/linux/input.cpp index c62608ce228..43433e58a92 100644 --- a/src/platform/linux/input.cpp +++ b/src/platform/linux/input.cpp @@ -23,6 +23,7 @@ #include "src/config.h" #include "src/input.h" +#include "src/logging.h" #include "src/main.h" #include "src/platform/common.h" #include "src/utility.h" diff --git a/src/platform/linux/kmsgrab.cpp b/src/platform/linux/kmsgrab.cpp index c146f866529..a18fc31a823 100644 --- a/src/platform/linux/kmsgrab.cpp +++ b/src/platform/linux/kmsgrab.cpp @@ -14,6 +14,7 @@ #include +#include "src/logging.h" #include "src/main.h" #include "src/platform/common.h" #include "src/round_robin.h" diff --git a/src/platform/linux/misc.cpp b/src/platform/linux/misc.cpp index 2c6ba16368a..8ead76b0715 100644 --- a/src/platform/linux/misc.cpp +++ b/src/platform/linux/misc.cpp @@ -26,6 +26,7 @@ #include "graphics.h" #include "misc.h" #include "src/config.h" +#include "src/logging.h" #include "src/main.h" #include "src/platform/common.h" #include "vaapi.h" diff --git a/src/platform/linux/publish.cpp b/src/platform/linux/publish.cpp index e83bfb7009e..f78b7d9dd37 100644 --- a/src/platform/linux/publish.cpp +++ b/src/platform/linux/publish.cpp @@ -7,6 +7,7 @@ #include #include "misc.h" +#include "src/logging.h" #include "src/main.h" #include "src/nvhttp.h" #include "src/platform/common.h" diff --git a/src/platform/linux/vaapi.cpp b/src/platform/linux/vaapi.cpp index 9cb7806b59b..9f1d3f325b2 100644 --- a/src/platform/linux/vaapi.cpp +++ b/src/platform/linux/vaapi.cpp @@ -26,7 +26,7 @@ vaSyncBuffer( #include "graphics.h" #include "misc.h" #include "src/config.h" -#include "src/main.h" +#include "src/logging.h" #include "src/platform/common.h" #include "src/utility.h" #include "src/video.h" diff --git a/src/platform/linux/wayland.cpp b/src/platform/linux/wayland.cpp index b762492b6ae..8dcd22c3639 100644 --- a/src/platform/linux/wayland.cpp +++ b/src/platform/linux/wayland.cpp @@ -9,7 +9,7 @@ #include #include "graphics.h" -#include "src/main.h" +#include "src/logging.h" #include "src/platform/common.h" #include "src/round_robin.h" #include "src/utility.h" diff --git a/src/platform/linux/wlgrab.cpp b/src/platform/linux/wlgrab.cpp index 230667734dd..6acde691479 100644 --- a/src/platform/linux/wlgrab.cpp +++ b/src/platform/linux/wlgrab.cpp @@ -4,6 +4,7 @@ */ #include "src/platform/common.h" +#include "src/logging.h" #include "src/main.h" #include "src/video.h" diff --git a/src/platform/linux/x11grab.cpp b/src/platform/linux/x11grab.cpp index 5cdf2d63d39..0c1583002b9 100644 --- a/src/platform/linux/x11grab.cpp +++ b/src/platform/linux/x11grab.cpp @@ -17,6 +17,7 @@ #include #include "src/config.h" +#include "src/logging.h" #include "src/main.h" #include "src/task_pool.h" #include "src/video.h" diff --git a/src/platform/macos/display.mm b/src/platform/macos/display.mm index a9f6bf40dd4..6d757176006 100644 --- a/src/platform/macos/display.mm +++ b/src/platform/macos/display.mm @@ -8,6 +8,7 @@ #include "src/platform/macos/nv12_zero_device.h" #include "src/config.h" +#include "src/logging.h" // Avoid conflict between AVFoundation and libavutil both defining AVMediaType #define AVMediaType AVMediaType_FFmpeg diff --git a/src/platform/macos/input.cpp b/src/platform/macos/input.cpp index 36484054a7a..2aa6012ef51 100644 --- a/src/platform/macos/input.cpp +++ b/src/platform/macos/input.cpp @@ -6,7 +6,7 @@ #include #include -#include "src/main.h" +#include "src/logging.h" #include "src/platform/common.h" #include "src/utility.h" diff --git a/src/platform/macos/microphone.mm b/src/platform/macos/microphone.mm index 1baf52606ab..836f134a482 100644 --- a/src/platform/macos/microphone.mm +++ b/src/platform/macos/microphone.mm @@ -6,6 +6,7 @@ #include "src/platform/macos/av_audio.h" #include "src/config.h" +#include "src/logging.h" namespace platf { using namespace std::literals; diff --git a/src/platform/macos/misc.mm b/src/platform/macos/misc.mm index 76e50349f6c..0a8bf1b78ae 100644 --- a/src/platform/macos/misc.mm +++ b/src/platform/macos/misc.mm @@ -18,6 +18,7 @@ #include #include "misc.h" +#include "src/logging.h" #include "src/main.h" #include "src/platform/common.h" diff --git a/src/platform/macos/publish.cpp b/src/platform/macos/publish.cpp index 55dec40a8e7..51dd0cd2b2d 100644 --- a/src/platform/macos/publish.cpp +++ b/src/platform/macos/publish.cpp @@ -7,6 +7,7 @@ #include #include "misc.h" +#include "src/logging.h" #include "src/main.h" #include "src/nvhttp.h" #include "src/platform/common.h" diff --git a/src/platform/windows/audio.cpp b/src/platform/windows/audio.cpp index 1311a694fd1..296112e1ad3 100644 --- a/src/platform/windows/audio.cpp +++ b/src/platform/windows/audio.cpp @@ -16,7 +16,7 @@ #include #include "src/config.h" -#include "src/main.h" +#include "src/logging.h" #include "src/platform/common.h" // Must be the last included file diff --git a/src/platform/windows/display_base.cpp b/src/platform/windows/display_base.cpp index 95e58b592a6..3ae6e337d26 100644 --- a/src/platform/windows/display_base.cpp +++ b/src/platform/windows/display_base.cpp @@ -15,6 +15,7 @@ typedef long NTSTATUS; #include "display.h" #include "misc.h" #include "src/config.h" +#include "src/logging.h" #include "src/main.h" #include "src/platform/common.h" #include "src/stat_trackers.h" diff --git a/src/platform/windows/display_ram.cpp b/src/platform/windows/display_ram.cpp index ad078d12704..cbe37edfe6b 100644 --- a/src/platform/windows/display_ram.cpp +++ b/src/platform/windows/display_ram.cpp @@ -5,7 +5,7 @@ #include "display.h" #include "misc.h" -#include "src/main.h" +#include "src/logging.h" namespace platf { using namespace std::literals; diff --git a/src/platform/windows/display_vram.cpp b/src/platform/windows/display_vram.cpp index 71310c90c16..74b0bab7fbf 100644 --- a/src/platform/windows/display_vram.cpp +++ b/src/platform/windows/display_vram.cpp @@ -17,7 +17,7 @@ extern "C" { #include "display.h" #include "misc.h" #include "src/config.h" -#include "src/main.h" +#include "src/logging.h" #include "src/nvenc/nvenc_config.h" #include "src/nvenc/nvenc_d3d11.h" #include "src/nvenc/nvenc_utils.h" diff --git a/src/platform/windows/input.cpp b/src/platform/windows/input.cpp index 65d89c0eb9c..144dac1999a 100644 --- a/src/platform/windows/input.cpp +++ b/src/platform/windows/input.cpp @@ -12,6 +12,7 @@ #include "keylayout.h" #include "misc.h" #include "src/config.h" +#include "src/logging.h" #include "src/main.h" #include "src/platform/common.h" diff --git a/src/platform/windows/misc.cpp b/src/platform/windows/misc.cpp index ec57355470d..4decb8e41b1 100644 --- a/src/platform/windows/misc.cpp +++ b/src/platform/windows/misc.cpp @@ -29,6 +29,7 @@ #include // clang-format on +#include "src/logging.h" #include "src/main.h" #include "src/platform/common.h" #include "src/utility.h" diff --git a/src/platform/windows/nvprefs/nvprefs_common.cpp b/src/platform/windows/nvprefs/nvprefs_common.cpp index f6acb6c548a..367cfc896d9 100644 --- a/src/platform/windows/nvprefs/nvprefs_common.cpp +++ b/src/platform/windows/nvprefs/nvprefs_common.cpp @@ -1,6 +1,6 @@ // local includes #include "nvprefs_common.h" -#include "src/main.h" // sunshine boost::log severity levels +#include "src/logging.h" // read user override preferences from global sunshine config #include "src/config.h" diff --git a/src/platform/windows/nvprefs/nvprefs_interface.cpp b/src/platform/windows/nvprefs/nvprefs_interface.cpp index ea8fa6c5cd4..628248ad619 100644 --- a/src/platform/windows/nvprefs/nvprefs_interface.cpp +++ b/src/platform/windows/nvprefs/nvprefs_interface.cpp @@ -1,7 +1,9 @@ +// standard includes +#include + // local includes -#include "nvprefs_interface.h" #include "driver_settings.h" -#include "src/main.h" // main include for assert +#include "nvprefs_interface.h" #include "undo_file.h" namespace { diff --git a/src/platform/windows/publish.cpp b/src/platform/windows/publish.cpp index 30aa30e24cb..4ba17ab902a 100644 --- a/src/platform/windows/publish.cpp +++ b/src/platform/windows/publish.cpp @@ -13,6 +13,7 @@ #include "misc.h" #include "src/config.h" +#include "src/logging.h" #include "src/main.h" #include "src/network.h" #include "src/nvhttp.h" diff --git a/src/process.cpp b/src/process.cpp index 050c58ee169..c01c60d1492 100644 --- a/src/process.cpp +++ b/src/process.cpp @@ -22,6 +22,7 @@ #include "config.h" #include "crypto.h" +#include "logging.h" #include "main.h" #include "platform/common.h" #include "system_tray.h" diff --git a/src/rtsp.cpp b/src/rtsp.cpp index 5b545428aab..6276753ae87 100644 --- a/src/rtsp.cpp +++ b/src/rtsp.cpp @@ -17,6 +17,7 @@ extern "C" { #include "config.h" #include "input.h" +#include "logging.h" #include "main.h" #include "network.h" #include "rtsp.h" diff --git a/src/stream.cpp b/src/stream.cpp index b00de23e0c8..8db8374b474 100644 --- a/src/stream.cpp +++ b/src/stream.cpp @@ -19,6 +19,7 @@ extern "C" { #include "config.h" #include "input.h" +#include "logging.h" #include "main.h" #include "network.h" #include "stat_trackers.h" diff --git a/src/system_tray.cpp b/src/system_tray.cpp index 189e58d293d..621ace0cfb9 100644 --- a/src/system_tray.cpp +++ b/src/system_tray.cpp @@ -37,6 +37,7 @@ // local includes #include "confighttp.h" + #include "logging.h" #include "main.h" #include "platform/common.h" #include "process.h" diff --git a/src/upnp.cpp b/src/upnp.cpp index 7a296a8b358..91a5ad85967 100644 --- a/src/upnp.cpp +++ b/src/upnp.cpp @@ -7,6 +7,7 @@ #include "config.h" #include "confighttp.h" +#include "logging.h" #include "main.h" #include "network.h" #include "nvhttp.h" diff --git a/src/video.cpp b/src/video.cpp index 1914faaa181..e013249880b 100644 --- a/src/video.cpp +++ b/src/video.cpp @@ -20,6 +20,7 @@ extern "C" { #include "cbs.h" #include "config.h" #include "input.h" +#include "logging.h" #include "main.h" #include "nvenc/nvenc_base.h" #include "platform/common.h" diff --git a/src/video_colorspace.cpp b/src/video_colorspace.cpp index ca5e489bb4b..4f5955eed7e 100644 --- a/src/video_colorspace.cpp +++ b/src/video_colorspace.cpp @@ -1,6 +1,6 @@ #include "video_colorspace.h" -#include "main.h" +#include "logging.h" #include "video.h" extern "C" { From ee93890d86ea2d4d0bf6cc31e9fdb5e68a52143c Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Sun, 4 Feb 2024 20:58:38 -0600 Subject: [PATCH 044/182] Don't attempt to get the working directory of a URL --- src/process.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/process.cpp b/src/process.cpp index c01c60d1492..fef585cdae6 100644 --- a/src/process.cpp +++ b/src/process.cpp @@ -118,7 +118,12 @@ namespace proc { return boost::filesystem::path(); } - BOOST_LOG(debug) << "Parsed executable ["sv << parts.at(0) << "] from command ["sv << cmd << ']'; + BOOST_LOG(debug) << "Parsed target ["sv << parts.at(0) << "] from command ["sv << cmd << ']'; + + // If the target is a URL, don't parse any further here + if (parts.at(0).find("://") != std::string::npos) { + return boost::filesystem::path(); + } // If the cmd path is not an absolute path, resolve it using our PATH variable boost::filesystem::path cmd_path(parts.at(0)); @@ -130,7 +135,7 @@ namespace proc { } } - BOOST_LOG(debug) << "Resolved executable ["sv << parts.at(0) << "] to path ["sv << cmd_path << ']'; + BOOST_LOG(debug) << "Resolved target ["sv << parts.at(0) << "] to path ["sv << cmd_path << ']'; // Now that we have a complete path, we can just use parent_path() return cmd_path.parent_path(); From aa76b2398b070ebe5f1d4a5ee2be937360a66b42 Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Sun, 4 Feb 2024 21:02:34 -0600 Subject: [PATCH 045/182] Add support for starting URLs and regular files that aren't executable This provides some limited ShellExecute-like behavior. --- cmake/compile_definitions/windows.cmake | 1 + src/platform/windows/misc.cpp | 267 +++++++++++++++++++++++- 2 files changed, 258 insertions(+), 10 deletions(-) diff --git a/cmake/compile_definitions/windows.cmake b/cmake/compile_definitions/windows.cmake index 530e983a44e..e3729927fc1 100644 --- a/cmake/compile_definitions/windows.cmake +++ b/cmake/compile_definitions/windows.cmake @@ -74,6 +74,7 @@ list(PREPEND PLATFORM_LIBRARIES synchronization.lib avrt iphlpapi + shlwapi ${CURL_STATIC_LIBRARIES}) if(SUNSHINE_ENABLE_TRAY) diff --git a/src/platform/windows/misc.cpp b/src/platform/windows/misc.cpp index 4decb8e41b1..192fce8e0ca 100644 --- a/src/platform/windows/misc.cpp +++ b/src/platform/windows/misc.cpp @@ -12,6 +12,7 @@ #include #include #include +#include // prevent clang format from "optimizing" the header include order // clang-format off @@ -29,6 +30,11 @@ #include // clang-format on +// Boost overrides NTDDI_VERSION, so we re-override it here +#undef NTDDI_VERSION +#define NTDDI_VERSION NTDDI_WIN10 +#include + #include "src/logging.h" #include "src/main.h" #include "src/platform/common.h" @@ -551,6 +557,252 @@ namespace platf { return startup_info; } + /** + * @brief This function overrides HKEY_CURRENT_USER and HKEY_CLASSES_ROOT using the provided token. + * @param token The primary token identifying the user to use, or `NULL` to restore original keys. + * @return `true` if the override or restore operation was successful. + */ + bool + override_per_user_predefined_keys(HANDLE token) { + HKEY user_classes_root = NULL; + if (token) { + auto err = RegOpenUserClassesRoot(token, 0, GENERIC_ALL, &user_classes_root); + if (err != ERROR_SUCCESS) { + BOOST_LOG(error) << "Failed to open classes root for target user: "sv << err; + return false; + } + } + auto close_classes_root = util::fail_guard([user_classes_root]() { + if (user_classes_root) { + RegCloseKey(user_classes_root); + } + }); + + HKEY user_key = NULL; + if (token) { + impersonate_current_user(token, [&]() { + // RegOpenCurrentUser() doesn't take a token. It assumes we're impersonating the desired user. + auto err = RegOpenCurrentUser(GENERIC_ALL, &user_key); + if (err != ERROR_SUCCESS) { + BOOST_LOG(error) << "Failed to open user key for target user: "sv << err; + user_key = NULL; + } + }); + if (!user_key) { + return false; + } + } + auto close_user = util::fail_guard([user_key]() { + if (user_key) { + RegCloseKey(user_key); + } + }); + + auto err = RegOverridePredefKey(HKEY_CLASSES_ROOT, user_classes_root); + if (err != ERROR_SUCCESS) { + BOOST_LOG(error) << "Failed to override HKEY_CLASSES_ROOT: "sv << err; + return false; + } + + err = RegOverridePredefKey(HKEY_CURRENT_USER, user_key); + if (err != ERROR_SUCCESS) { + BOOST_LOG(error) << "Failed to override HKEY_CURRENT_USER: "sv << err; + RegOverridePredefKey(HKEY_CLASSES_ROOT, NULL); + return false; + } + + return true; + } + + /** + * @brief This function resolves the given raw command into a proper command string for CreateProcess(). + * @details This converts URLs and non-executable file paths into a runnable command like ShellExecute(). + * @param raw_cmd The raw command provided by the user. + * @param working_dir The working directory for the new process. + * @param token The user token currently being impersonated or `NULL` if running as ourselves. + * @return A command string suitable for use by CreateProcess(). + */ + std::wstring + resolve_command_string(const std::string &raw_cmd, const std::wstring &working_dir, HANDLE token) { + std::wstring raw_cmd_w = converter.from_bytes(raw_cmd); + + // First, convert the given command into parts so we can get the executable/file/URL without parameters + auto raw_cmd_parts = boost::program_options::split_winmain(raw_cmd_w); + if (raw_cmd_parts.empty()) { + // This is highly unexpected, but we'll just return the raw string and hope for the best. + BOOST_LOG(warning) << "Failed to split command string: "sv << raw_cmd; + return converter.from_bytes(raw_cmd); + } + + auto raw_target = raw_cmd_parts.at(0); + std::wstring lookup_string; + HRESULT res; + + if (PathIsURLW(raw_target.c_str())) { + std::array scheme; + + DWORD out_len = scheme.size(); + res = UrlGetPartW(raw_target.c_str(), scheme.data(), &out_len, URL_PART_SCHEME, 0); + if (res != S_OK) { + BOOST_LOG(warning) << "Failed to extract URL scheme from URL: "sv << raw_target << " ["sv << util::hex(res).to_string_view() << ']'; + return converter.from_bytes(raw_cmd); + } + + // If the target is a URL, the class is found using the URL scheme (prior to and not including the ':') + lookup_string = scheme.data(); + } + else { + // If the target is not a URL, assume it's a regular file path + auto extension = PathFindExtensionW(raw_target.c_str()); + if (extension == nullptr || *extension == 0) { + // If the file has no extension, assume it's a command and allow CreateProcess() + // to try to find it via PATH + return converter.from_bytes(raw_cmd); + } + + // For regular files, the class is found using the file extension (including the dot) + lookup_string = extension; + } + + std::array shell_command_string; + { + // Overriding these predefined keys affects process-wide state, so serialize all calls + // to ensure the handle state is consistent while we perform the command query. + static std::mutex per_user_key_mutex; + auto lg = std::lock_guard(per_user_key_mutex); + + // Override HKEY_CLASSES_ROOT and HKEY_CURRENT_USER to ensure we query the correct class info + if (!override_per_user_predefined_keys(token)) { + return converter.from_bytes(raw_cmd); + } + + // Find the command string for the specified class + DWORD out_len = shell_command_string.size(); + res = AssocQueryStringW(ASSOCF_NOTRUNCATE, ASSOCSTR_COMMAND, lookup_string.c_str(), L"open", shell_command_string.data(), &out_len); + + // In some cases (UWP apps), we might not have a command for this target. If that happens, + // we'll have to launch via cmd.exe. This prevents proper job tracking, but that was already + // broken for UWP apps anyway due to how they are started by Windows. Even 'start /wait' + // doesn't work properly for UWP, so really no termination tracking seems to work at all. + // + // FIXME: Maybe we can improve this in the future. + if (res == HRESULT_FROM_WIN32(ERROR_NO_ASSOCIATION)) { + BOOST_LOG(warning) << "Using trampoline to handle target: "sv << raw_cmd; + std::wcscpy(shell_command_string.data(), L"cmd.exe /c start \"\" \"%1\" %*"); + res = S_OK; + } + + // Reset per-user keys back to the original value + override_per_user_predefined_keys(NULL); + } + + if (res != S_OK) { + BOOST_LOG(warning) << "Failed to query command string for raw command: "sv << raw_cmd << " ["sv << util::hex(res).to_string_view() << ']'; + return converter.from_bytes(raw_cmd); + } + + // Finally, construct the real command string that will be passed into CreateProcess(). + // We support common substitutions (%*, %1, %2, %L, %W, %V, etc), but there are other + // uncommon ones that are unsupported here. + // + // https://web.archive.org/web/20111002101214/http://msdn.microsoft.com/en-us/library/windows/desktop/cc144101(v=vs.85).aspx + std::wstring cmd_string { shell_command_string.data() }; + size_t match_pos = 0; + while ((match_pos = cmd_string.find_first_of(L'%', match_pos)) != std::wstring::npos) { + std::wstring match_replacement; + + // Shell command replacements are strictly '%' followed by a single non-'%' character + auto next_char = std::tolower(match_pos + 1 < cmd_string.size() ? cmd_string.at(match_pos + 1) : 0); + switch (next_char) { + // No next character + case 0: + break; + + // Escape character + case L'%': + // Skip this character and the next one + match_pos += 2; + continue; + + // Argument replacements + case L'0': + case L'1': + case L'2': + case L'3': + case L'4': + case L'5': + case L'6': + case L'7': + case L'8': + case L'9': { + // Arguments numbers are 1-based, except for %0 which is equivalent to %1 + int index = next_char - L'0'; + if (next_char != L'0') { + index--; + } + + // Replace with the matching argument, or nothing if the index is invalid + if (index < raw_cmd_parts.size()) { + match_replacement = raw_cmd_parts.at(index); + } + break; + } + + // All arguments following the target + case L'*': + for (int i = 1; i < raw_cmd_parts.size(); i++) { + match_replacement += raw_cmd_parts.at(i); + } + break; + + // Long file path of target + case L'l': + case L'd': + case L'v': { + std::array path; + std::array other_dirs { working_dir.c_str(), nullptr }; + + // PathFindOnPath() is a little gross because it uses the same + // buffer for input and output, so we need to copy our input + // into the path array. + std::wcsncpy(path.data(), raw_target.c_str(), path.size()); + if (path[path.size() - 1] != 0) { + // The path was so long it was truncated by this copy. We'll + // assume it was an absolute path (likely) and use it unmodified. + match_replacement = raw_target; + } + // See if we can find the path on our search path or working directory + else if (PathFindOnPathW(path.data(), other_dirs.data())) { + match_replacement = std::wstring { path.data() }; + } + else { + // We couldn't find the target, so we'll just hope for the best + match_replacement = raw_target; + } + break; + } + + // Working directory + case L'w': + match_replacement = working_dir; + break; + + default: + BOOST_LOG(warning) << "Unsupported argument replacement: %%" << next_char; + break; + } + + // Replace the % and following character with the match replacement + cmd_string.replace(match_pos, 2, match_replacement); + + // Skip beyond the match replacement itself to prevent recursive replacement + match_pos += match_replacement.size(); + } + + BOOST_LOG(info) << "Resolved user-provided command '"sv << raw_cmd << "' to '"sv << cmd_string << '\''; + return cmd_string; + } + /** * @brief Run a command on the users profile. * @@ -568,11 +820,7 @@ namespace platf { */ bp::child run_command(bool elevated, bool interactive, const std::string &cmd, boost::filesystem::path &working_dir, const bp::environment &env, FILE *file, std::error_code &ec, bp::group *group) { - BOOL ret; - // Convert cmd, env, and working_dir to the appropriate character sets for Win32 APIs - std::wstring wcmd = converter.from_bytes(cmd); std::wstring start_dir = converter.from_bytes(working_dir.string()); - HANDLE job = group ? group->native_handle() : nullptr; STARTUPINFOEXW startup_info = create_startup_info(file, job ? &job : nullptr, ec); PROCESS_INFORMATION process_info; @@ -597,6 +845,7 @@ namespace platf { // Create a new console for interactive processes and use no console for non-interactive processes creation_flags |= interactive ? CREATE_NEW_CONSOLE : CREATE_NO_WINDOW; + BOOL ret; if (is_running_as_system()) { // Duplicate the current user's token HANDLE user_token = retrieve_users_token(elevated); @@ -620,6 +869,7 @@ namespace platf { // Open the process as the current user account, elevation is handled in the token itself. ec = impersonate_current_user(user_token, [&]() { std::wstring env_block = create_environment_block(cloned_env); + std::wstring wcmd = resolve_command_string(cmd, start_dir, user_token); ret = CreateProcessAsUserW(user_token, NULL, (LPWSTR) wcmd.c_str(), @@ -653,6 +903,7 @@ namespace platf { } std::wstring env_block = create_environment_block(cloned_env); + std::wstring wcmd = resolve_command_string(cmd, start_dir, NULL); ret = CreateProcessW(NULL, (LPWSTR) wcmd.c_str(), NULL, @@ -675,15 +926,11 @@ namespace platf { */ void open_url(const std::string &url) { - // set working dir to Windows system directory - auto working_dir = boost::filesystem::path(std::getenv("SystemRoot")); - boost::process::environment _env = boost::this_process::environment(); + auto working_dir = boost::filesystem::path(); std::error_code ec; - // Launch this as a non-interactive non-elevated command to avoid an extra console window - std::string cmd = R"(cmd /C "start )" + url + R"(")"; - auto child = run_command(false, false, cmd, working_dir, _env, nullptr, ec, nullptr); + auto child = run_command(false, false, url, working_dir, _env, nullptr, ec, nullptr); if (ec) { BOOST_LOG(warning) << "Couldn't open url ["sv << url << "]: System: "sv << ec.message(); } From 358aac9277dce563817c0d3cf7a020478275ecf9 Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Sun, 4 Feb 2024 21:05:35 -0600 Subject: [PATCH 046/182] Modernize the default Steam shortcut With auto-detach and properly job tracking, we don't need to run Steam detached anymore. We can just use the plain URL now too. --- src_assets/windows/assets/apps.json | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src_assets/windows/assets/apps.json b/src_assets/windows/assets/apps.json index 72a56a7e01c..30b6ada06dd 100644 --- a/src_assets/windows/assets/apps.json +++ b/src_assets/windows/assets/apps.json @@ -1,7 +1,5 @@ { - "env": { - "PATH": "$(PATH);$(ProgramFiles(x86))\\Steam" - }, + "env": {}, "apps": [ { "name": "Desktop", @@ -9,9 +7,9 @@ }, { "name": "Steam Big Picture", - "detached": [ - "steam steam://open/bigpicture" - ], + "cmd": "steam://open/bigpicture", + "auto-detach": "true", + "wait-all": "true", "image-path": "steam.png" } ] From d91e2c9ecbb3cbbac4a19a0de573eb5ccad85a57 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Thu, 8 Feb 2024 10:18:10 -0500 Subject: [PATCH 047/182] docs(examples): update uri examples (#2118) --- docs/source/about/guides/app_examples.rst | 44 +++++++++++------------ 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/docs/source/about/guides/app_examples.rst b/docs/source/about/guides/app_examples.rst index 514bb5c710d..ca834e4a4f2 100644 --- a/docs/source/about/guides/app_examples.rst +++ b/docs/source/about/guides/app_examples.rst @@ -48,13 +48,13 @@ Steam Big Picture .. tab:: Windows - +----------------------+-----------------------------------+ - | Application Name | ``Steam Big Picture`` | - +----------------------+-----------------------------------+ - | Detached Commands | ``steam steam://open/bigpicture`` | - +----------------------+-----------------------------------+ - | Image | ``steam.png`` | - +----------------------+-----------------------------------+ + +----------------------+-----------------------------+ + | Application Name | ``Steam Big Picture`` | + +----------------------+-----------------------------+ + | Detached Commands | ``steam://open/bigpicture`` | + +----------------------+-----------------------------+ + | Image | ``steam.png`` | + +----------------------+-----------------------------+ Epic Game Store game ^^^^^^^^^^^^^^^^^^^^ @@ -67,11 +67,11 @@ URI (Epic) .. tab:: Windows - +----------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------+ - | Application Name | ``Surviving Mars`` | - +----------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------+ - | Detached Commands | ``cmd /C "start com.epicgames.launcher://apps/d759128018124dcabb1fbee9bb28e178%3A20729b9176c241f0b617c5723e70ec2d%3AOvenbird?action=launch&silent=true"`` | - +----------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------+ + +----------------------+--------------------------------------------------------------------------------------------------------------------------------------------+ + | Application Name | ``Surviving Mars`` | + +----------------------+--------------------------------------------------------------------------------------------------------------------------------------------+ + | Detached Commands | ``com.epicgames.launcher://apps/d759128018124dcabb1fbee9bb28e178%3A20729b9176c241f0b617c5723e70ec2d%3AOvenbird?action=launch&silent=true`` | + +----------------------+--------------------------------------------------------------------------------------------------------------------------------------------+ Binary (Epic w/ working directory) """""""""""""""""""""""""""""""""" @@ -124,11 +124,11 @@ URI (Steam) .. tab:: Windows - +----------------------+---------------------------------------------+ - | Application Name | ``Surviving Mars`` | - +----------------------+---------------------------------------------+ - | Detached Commands | ``cmd /C "start steam://rungameid/464920"`` | - +----------------------+---------------------------------------------+ + +----------------------+------------------------------+ + | Application Name | ``Surviving Mars`` | + +----------------------+------------------------------+ + | Detached Commands | ``steam://rungameid/464920`` | + +----------------------+------------------------------+ Binary (Steam w/ working directory) """"""""""""""""""""""""""""""""""" @@ -258,11 +258,11 @@ Changing Resolution and Refresh Rate .. tab:: KDE Plasma (Wayland, X11) - +----------------------+----------------------------------------------------------------------------------------------------------------------------------+ - | Command Preparations | Do: ``sh -c "kscreen-doctor output.HDMI-A-1.mode.${SUNSHINE_CLIENT_WIDTH}x${SUNSHINE_CLIENT_HEIGHT}@${SUNSHINE_CLIENT_FPS}"`` | - | +----------------------------------------------------------------------------------------------------------------------------------+ - | | Undo: ``kscreen-doctor output.HDMI-A-1.mode.3840x2160@120`` | - +----------------------+----------------------------------------------------------------------------------------------------------------------------------+ + +----------------------+-------------------------------------------------------------------------------------------------------------------------------+ + | Command Preparations | Do: ``sh -c "kscreen-doctor output.HDMI-A-1.mode.${SUNSHINE_CLIENT_WIDTH}x${SUNSHINE_CLIENT_HEIGHT}@${SUNSHINE_CLIENT_FPS}"`` | + | +-------------------------------------------------------------------------------------------------------------------------------+ + | | Undo: ``kscreen-doctor output.HDMI-A-1.mode.3840x2160@120`` | + +----------------------+-------------------------------------------------------------------------------------------------------------------------------+ .. tab:: NVIDIA From 1c50bc502bdee9764a836fa1699a6af33538e35b Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Fri, 9 Feb 2024 09:15:47 -0500 Subject: [PATCH 048/182] refactor(main): move map_port to network (#2115) --- src/confighttp.cpp | 2 +- src/main.cpp | 32 ++++---------------------------- src/main.h | 2 -- src/network.cpp | 26 ++++++++++++++++++++++++++ src/network.h | 3 +++ src/nvhttp.cpp | 12 ++++++------ src/platform/linux/publish.cpp | 4 ++-- src/platform/macos/publish.cpp | 4 ++-- src/platform/windows/publish.cpp | 2 +- src/rtsp.cpp | 10 +++++----- src/stream.cpp | 6 +++--- src/upnp.cpp | 22 +++++++++++----------- 12 files changed, 64 insertions(+), 61 deletions(-) diff --git a/src/confighttp.cpp b/src/confighttp.cpp index 45062254421..37233d6e600 100644 --- a/src/confighttp.cpp +++ b/src/confighttp.cpp @@ -723,7 +723,7 @@ namespace confighttp { start() { auto shutdown_event = mail::man->event(mail::shutdown); - auto port_https = map_port(PORT_HTTPS); + auto port_https = net::map_port(PORT_HTTPS); auto address_family = net::af_from_enum_string(config::sunshine.address_family); https_server_t server { config::nvhttp.cert, config::nvhttp.pkey }; diff --git a/src/main.cpp b/src/main.cpp index e1a0073161a..e317e4d6451 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -23,6 +23,7 @@ #include "httpcommon.h" #include "logging.h" #include "main.h" +#include "network.h" #include "nvhttp.h" #include "platform/common.h" #include "process.h" @@ -297,7 +298,7 @@ namespace service_ctrl { return false; } - uint16_t port_nbo = htons(map_port(confighttp::PORT_HTTPS)); + uint16_t port_nbo = htons(net::map_port(confighttp::PORT_HTTPS)); for (DWORD i = 0; i < tcp_table->dwNumEntries; i++) { auto &entry = tcp_table->table[i]; @@ -348,7 +349,7 @@ is_gamestream_enabled() { */ void launch_ui() { - std::string url = "https://localhost:" + std::to_string(map_port(confighttp::PORT_HTTPS)); + std::string url = "https://localhost:" + std::to_string(net::map_port(confighttp::PORT_HTTPS)); platf::open_url(url); } @@ -362,7 +363,7 @@ launch_ui() { */ void launch_ui_with_path(std::string path) { - std::string url = "https://localhost:" + std::to_string(map_port(confighttp::PORT_HTTPS)) + path; + std::string url = "https://localhost:" + std::to_string(net::map_port(confighttp::PORT_HTTPS)) + path; platf::open_url(url); } @@ -811,28 +812,3 @@ write_file(const char *path, const std::string_view &contents) { return 0; } - -/** - * @brief Map a specified port based on the base port. - * @param port The port to map as a difference from the base port. - * @return `std:uint16_t` : The mapped port number. - * - * EXAMPLES: - * ```cpp - * std::uint16_t mapped_port = map_port(1); - * ``` - */ -std::uint16_t -map_port(int port) { - // calculate the port from the config port - auto mapped_port = (std::uint16_t)((int) config::sunshine.port + port); - - // Ensure port is in the range of 1024-65535 - if (mapped_port < 1024 || mapped_port > 65535) { - BOOST_LOG(warning) << "Port out of range: "sv << mapped_port; - } - - // TODO: Ensure port is not already in use by another application - - return mapped_port; -} diff --git a/src/main.h b/src/main.h index 0114586dde3..ba726539145 100644 --- a/src/main.h +++ b/src/main.h @@ -30,8 +30,6 @@ std::string read_file(const char *path); int write_file(const char *path, const std::string_view &contents); -std::uint16_t -map_port(int port); void launch_ui(); void diff --git a/src/network.cpp b/src/network.cpp index 6980af2a908..5c158ee6c52 100644 --- a/src/network.cpp +++ b/src/network.cpp @@ -4,6 +4,7 @@ */ #include "network.h" #include "config.h" +#include "logging.h" #include "utility.h" #include @@ -222,4 +223,29 @@ namespace net { enet_host_destroy(host); } + + /** + * @brief Map a specified port based on the base port. + * @param port The port to map as a difference from the base port. + * @return `std:uint16_t` : The mapped port number. + * + * EXAMPLES: + * ```cpp + * std::uint16_t mapped_port = net::map_port(1); + * ``` + */ + std::uint16_t + map_port(int port) { + // calculate the port from the config port + auto mapped_port = (std::uint16_t)((int) config::sunshine.port + port); + + // Ensure port is in the range of 1024-65535 + if (mapped_port < 1024 || mapped_port > 65535) { + BOOST_LOG(warning) << "Port out of range: "sv << mapped_port; + } + + // TODO: Ensure port is not already in use by another application + + return mapped_port; + } } // namespace net diff --git a/src/network.h b/src/network.h index 0363f62ddf8..a4f74fb404d 100644 --- a/src/network.h +++ b/src/network.h @@ -16,6 +16,9 @@ namespace net { void free_host(ENetHost *host); + std::uint16_t + map_port(int port); + using host_t = util::safe_ptr; using peer_t = ENetPeer *; using packet_t = util::safe_ptr; diff --git a/src/nvhttp.cpp b/src/nvhttp.cpp index 04cfd5b38d5..6fb7ab363aa 100644 --- a/src/nvhttp.cpp +++ b/src/nvhttp.cpp @@ -657,8 +657,8 @@ namespace nvhttp { tree.put("root.appversion", VERSION); tree.put("root.GfeVersion", GFE_VERSION); tree.put("root.uniqueid", http::unique_id); - tree.put("root.HttpsPort", map_port(PORT_HTTPS)); - tree.put("root.ExternalPort", map_port(PORT_HTTP)); + tree.put("root.HttpsPort", net::map_port(PORT_HTTPS)); + tree.put("root.ExternalPort", net::map_port(PORT_HTTP)); tree.put("root.mac", platf::get_mac_address(net::addr_to_normalized_string(local_endpoint.address()))); tree.put("root.MaxLumaPixelsHEVC", video::active_hevc_mode > 1 ? "1869449984" : "0"); @@ -846,7 +846,7 @@ namespace nvhttp { tree.put("root..status_code", 200); tree.put("root.sessionUrl0", launch_session->rtsp_url_scheme + net::addr_to_url_escaped_string(request->local_endpoint().address()) + ':' + - std::to_string(map_port(rtsp_stream::RTSP_SETUP_PORT))); + std::to_string(net::map_port(rtsp_stream::RTSP_SETUP_PORT))); tree.put("root.gamesession", 1); rtsp_stream::launch_session_raise(launch_session); @@ -932,7 +932,7 @@ namespace nvhttp { tree.put("root..status_code", 200); tree.put("root.sessionUrl0", launch_session->rtsp_url_scheme + net::addr_to_url_escaped_string(request->local_endpoint().address()) + ':' + - std::to_string(map_port(rtsp_stream::RTSP_SETUP_PORT))); + std::to_string(net::map_port(rtsp_stream::RTSP_SETUP_PORT))); tree.put("root.resume", 1); rtsp_stream::launch_session_raise(launch_session); @@ -995,8 +995,8 @@ namespace nvhttp { start() { auto shutdown_event = mail::man->event(mail::shutdown); - auto port_http = map_port(PORT_HTTP); - auto port_https = map_port(PORT_HTTPS); + auto port_http = net::map_port(PORT_HTTP); + auto port_https = net::map_port(PORT_HTTPS); auto address_family = net::af_from_enum_string(config::sunshine.address_family); bool clean_slate = config::sunshine.flags[config::flag::FRESH_STATE]; diff --git a/src/platform/linux/publish.cpp b/src/platform/linux/publish.cpp index f78b7d9dd37..bc876e7728b 100644 --- a/src/platform/linux/publish.cpp +++ b/src/platform/linux/publish.cpp @@ -8,7 +8,7 @@ #include "misc.h" #include "src/logging.h" -#include "src/main.h" +#include "src/network.h" #include "src/nvhttp.h" #include "src/platform/common.h" #include "src/utility.h" @@ -349,7 +349,7 @@ namespace platf::publish { name.get(), SERVICE_TYPE, nullptr, nullptr, - map_port(nvhttp::PORT_HTTP), + net::map_port(nvhttp::PORT_HTTP), nullptr); if (ret < 0) { diff --git a/src/platform/macos/publish.cpp b/src/platform/macos/publish.cpp index 51dd0cd2b2d..eb8823e5020 100644 --- a/src/platform/macos/publish.cpp +++ b/src/platform/macos/publish.cpp @@ -8,7 +8,7 @@ #include "misc.h" #include "src/logging.h" -#include "src/main.h" +#include "src/network.h" #include "src/nvhttp.h" #include "src/platform/common.h" #include "src/utility.h" @@ -349,7 +349,7 @@ namespace platf::publish { name.get(), SERVICE_TYPE, nullptr, nullptr, - map_port(nvhttp::PORT_HTTP), + net::map_port(nvhttp::PORT_HTTP), nullptr); if (ret < 0) { diff --git a/src/platform/windows/publish.cpp b/src/platform/windows/publish.cpp index 4ba17ab902a..47c16721e20 100644 --- a/src/platform/windows/publish.cpp +++ b/src/platform/windows/publish.cpp @@ -117,7 +117,7 @@ namespace platf::publish { DNS_SERVICE_INSTANCE instance {}; instance.pszInstanceName = name.data(); - instance.wPort = map_port(nvhttp::PORT_HTTP); + instance.wPort = net::map_port(nvhttp::PORT_HTTP); instance.pszHostName = host.data(); // Setting these values ensures Windows mDNS answers comply with RFC 1035. diff --git a/src/rtsp.cpp b/src/rtsp.cpp index 6276753ae87..73f224b8ef1 100644 --- a/src/rtsp.cpp +++ b/src/rtsp.cpp @@ -869,13 +869,13 @@ namespace rtsp_stream { std::uint16_t port; if (type == "audio"sv) { - port = map_port(stream::AUDIO_STREAM_PORT); + port = net::map_port(stream::AUDIO_STREAM_PORT); } else if (type == "video"sv) { - port = map_port(stream::VIDEO_STREAM_PORT); + port = net::map_port(stream::VIDEO_STREAM_PORT); } else if (type == "control"sv) { - port = map_port(stream::CONTROL_PORT); + port = net::map_port(stream::CONTROL_PORT); } else { cmd_not_found(sock, session, std::move(req)); @@ -1129,8 +1129,8 @@ namespace rtsp_stream { server.map("PLAY"sv, &cmd_play); boost::system::error_code ec; - if (server.bind(net::af_from_enum_string(config::sunshine.address_family), map_port(rtsp_stream::RTSP_SETUP_PORT), ec)) { - BOOST_LOG(fatal) << "Couldn't bind RTSP server to port ["sv << map_port(rtsp_stream::RTSP_SETUP_PORT) << "], " << ec.message(); + if (server.bind(net::af_from_enum_string(config::sunshine.address_family), net::map_port(rtsp_stream::RTSP_SETUP_PORT), ec)) { + BOOST_LOG(fatal) << "Couldn't bind RTSP server to port ["sv << net::map_port(rtsp_stream::RTSP_SETUP_PORT) << "], " << ec.message(); shutdown_event->raise(true); return; diff --git a/src/stream.cpp b/src/stream.cpp index 8db8374b474..12fb8663fc8 100644 --- a/src/stream.cpp +++ b/src/stream.cpp @@ -1600,9 +1600,9 @@ namespace stream { start_broadcast(broadcast_ctx_t &ctx) { auto address_family = net::af_from_enum_string(config::sunshine.address_family); auto protocol = address_family == net::IPV4 ? udp::v4() : udp::v6(); - auto control_port = map_port(CONTROL_PORT); - auto video_port = map_port(VIDEO_STREAM_PORT); - auto audio_port = map_port(AUDIO_STREAM_PORT); + auto control_port = net::map_port(CONTROL_PORT); + auto video_port = net::map_port(VIDEO_STREAM_PORT); + auto audio_port = net::map_port(AUDIO_STREAM_PORT); if (ctx.control_server.bind(address_family, control_port)) { BOOST_LOG(error) << "Couldn't bind Control server to port ["sv << control_port << "], likely another process already bound to the port"sv; diff --git a/src/upnp.cpp b/src/upnp.cpp index 91a5ad85967..55c49aaf67c 100644 --- a/src/upnp.cpp +++ b/src/upnp.cpp @@ -62,13 +62,13 @@ namespace upnp { class deinit_t: public platf::deinit_t { public: deinit_t() { - auto rtsp = std::to_string(::map_port(rtsp_stream::RTSP_SETUP_PORT)); - auto video = std::to_string(::map_port(stream::VIDEO_STREAM_PORT)); - auto audio = std::to_string(::map_port(stream::AUDIO_STREAM_PORT)); - auto control = std::to_string(::map_port(stream::CONTROL_PORT)); - auto gs_http = std::to_string(::map_port(nvhttp::PORT_HTTP)); - auto gs_https = std::to_string(::map_port(nvhttp::PORT_HTTPS)); - auto wm_http = std::to_string(::map_port(confighttp::PORT_HTTPS)); + auto rtsp = std::to_string(net::map_port(rtsp_stream::RTSP_SETUP_PORT)); + auto video = std::to_string(net::map_port(stream::VIDEO_STREAM_PORT)); + auto audio = std::to_string(net::map_port(stream::AUDIO_STREAM_PORT)); + auto control = std::to_string(net::map_port(stream::CONTROL_PORT)); + auto gs_http = std::to_string(net::map_port(nvhttp::PORT_HTTP)); + auto gs_https = std::to_string(net::map_port(nvhttp::PORT_HTTPS)); + auto wm_http = std::to_string(net::map_port(confighttp::PORT_HTTPS)); mappings.assign({ { { rtsp, rtsp, "TCP"s }, "Sunshine - RTSP"s }, @@ -179,7 +179,7 @@ namespace upnp { * @return `true` on success. */ bool - map_port(const IGDdatas &data, const urls_t &urls, const std::string &lan_addr, const mapping_t &mapping) { + map_upnp_port(const IGDdatas &data, const urls_t &urls, const std::string &lan_addr, const mapping_t &mapping) { char intClient[16]; char intPort[6]; char desc[80]; @@ -284,7 +284,7 @@ namespace upnp { * @param data urls_t from UPNP_GetValidIGD() */ void - unmap_all_ports(const urls_t &urls, const IGDdatas &data) { + unmap_all_upnp_ports(const urls_t &urls, const IGDdatas &data) { for (auto it = std::begin(mappings); it != std::end(mappings); ++it) { auto status = UPNP_DeletePortMapping( urls->controlURL, @@ -343,7 +343,7 @@ namespace upnp { BOOST_LOG(debug) << "Found valid IGD device: "sv << urls->rootdescURL; for (auto it = std::begin(mappings); it != std::end(mappings) && !shutdown_event->peek(); ++it) { - map_port(data, urls, lan_addr_str, *it); + map_upnp_port(data, urls, lan_addr_str, *it); } if (!mapped) { @@ -365,7 +365,7 @@ namespace upnp { if (mapped) { // Unmap ports upon termination BOOST_LOG(info) << "Unmapping UPNP ports..."sv; - unmap_all_ports(mapped_urls, data); + unmap_all_upnp_ports(mapped_urls, data); } } From cd2153f3408e7f23e323483aee315a21c5fd2438 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sat, 10 Feb 2024 07:37:27 -0500 Subject: [PATCH 049/182] refactor(main): move file operation functions to new source (#2124) --- cmake/compile_definitions/common.cmake | 2 + docs/source/source_code/src/file_handler.rst | 5 ++ src/config.cpp | 3 +- src/confighttp.cpp | 25 +++---- src/file_handler.cpp | 69 ++++++++++++++++++++ src/file_handler.h | 14 ++++ src/httpcommon.cpp | 6 +- src/main.cpp | 54 --------------- src/main.h | 4 -- src/nvhttp.cpp | 5 +- src/platform/linux/graphics.cpp | 4 +- 11 files changed, 113 insertions(+), 78 deletions(-) create mode 100644 docs/source/source_code/src/file_handler.rst create mode 100644 src/file_handler.cpp create mode 100644 src/file_handler.h diff --git a/cmake/compile_definitions/common.cmake b/cmake/compile_definitions/common.cmake index b3e84b3ef7a..e51dbf56526 100644 --- a/cmake/compile_definitions/common.cmake +++ b/cmake/compile_definitions/common.cmake @@ -45,6 +45,8 @@ set(SUNSHINE_TARGET_FILES "${CMAKE_SOURCE_DIR}/src/uuid.h" "${CMAKE_SOURCE_DIR}/src/config.h" "${CMAKE_SOURCE_DIR}/src/config.cpp" + "${CMAKE_SOURCE_DIR}/src/file_handler.cpp" + "${CMAKE_SOURCE_DIR}/src/file_handler.h" "${CMAKE_SOURCE_DIR}/src/logging.cpp" "${CMAKE_SOURCE_DIR}/src/logging.h" "${CMAKE_SOURCE_DIR}/src/main.cpp" diff --git a/docs/source/source_code/src/file_handler.rst b/docs/source/source_code/src/file_handler.rst new file mode 100644 index 00000000000..221b8cbde03 --- /dev/null +++ b/docs/source/source_code/src/file_handler.rst @@ -0,0 +1,5 @@ +file_handler +============ + +.. doxygenfile:: file_handler.h + :allow-dot-graphs: diff --git a/src/config.cpp b/src/config.cpp index 9560c7cec35..5cd08cee94e 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -15,6 +15,7 @@ #include #include "config.h" +#include "file_handler.h" #include "logging.h" #include "main.h" #include "nvhttp.h" @@ -1215,7 +1216,7 @@ namespace config { } // Read config file - auto vars = parse_config(read_file(sunshine.config_file.c_str())); + auto vars = parse_config(file_handler::read_file(sunshine.config_file.c_str())); for (auto &[name, value] : cmd_vars) { vars.insert_or_assign(std::move(name), std::move(value)); diff --git a/src/confighttp.cpp b/src/confighttp.cpp index 37233d6e600..e3a5f898e39 100644 --- a/src/confighttp.cpp +++ b/src/confighttp.cpp @@ -29,6 +29,7 @@ #include "config.h" #include "confighttp.h" #include "crypto.h" +#include "file_handler.h" #include "httpcommon.h" #include "logging.h" #include "main.h" @@ -162,7 +163,7 @@ namespace confighttp { print_req(request); - std::string content = read_file(WEB_DIR "index.html"); + std::string content = file_handler::read_file(WEB_DIR "index.html"); SimpleWeb::CaseInsensitiveMultimap headers; headers.emplace("Content-Type", "text/html; charset=utf-8"); response->write(content, headers); @@ -174,7 +175,7 @@ namespace confighttp { print_req(request); - std::string content = read_file(WEB_DIR "pin.html"); + std::string content = file_handler::read_file(WEB_DIR "pin.html"); SimpleWeb::CaseInsensitiveMultimap headers; headers.emplace("Content-Type", "text/html; charset=utf-8"); response->write(content, headers); @@ -186,7 +187,7 @@ namespace confighttp { print_req(request); - std::string content = read_file(WEB_DIR "apps.html"); + std::string content = file_handler::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/"); @@ -199,7 +200,7 @@ namespace confighttp { print_req(request); - std::string content = read_file(WEB_DIR "clients.html"); + std::string content = file_handler::read_file(WEB_DIR "clients.html"); SimpleWeb::CaseInsensitiveMultimap headers; headers.emplace("Content-Type", "text/html; charset=utf-8"); response->write(content, headers); @@ -211,7 +212,7 @@ namespace confighttp { print_req(request); - std::string content = read_file(WEB_DIR "config.html"); + std::string content = file_handler::read_file(WEB_DIR "config.html"); SimpleWeb::CaseInsensitiveMultimap headers; headers.emplace("Content-Type", "text/html; charset=utf-8"); response->write(content, headers); @@ -223,7 +224,7 @@ namespace confighttp { print_req(request); - std::string content = read_file(WEB_DIR "password.html"); + std::string content = file_handler::read_file(WEB_DIR "password.html"); SimpleWeb::CaseInsensitiveMultimap headers; headers.emplace("Content-Type", "text/html; charset=utf-8"); response->write(content, headers); @@ -236,7 +237,7 @@ namespace confighttp { send_redirect(response, request, "/"); return; } - std::string content = read_file(WEB_DIR "welcome.html"); + std::string content = file_handler::read_file(WEB_DIR "welcome.html"); SimpleWeb::CaseInsensitiveMultimap headers; headers.emplace("Content-Type", "text/html; charset=utf-8"); response->write(content, headers); @@ -248,7 +249,7 @@ namespace confighttp { print_req(request); - std::string content = read_file(WEB_DIR "troubleshooting.html"); + std::string content = file_handler::read_file(WEB_DIR "troubleshooting.html"); SimpleWeb::CaseInsensitiveMultimap headers; headers.emplace("Content-Type", "text/html; charset=utf-8"); response->write(content, headers); @@ -324,7 +325,7 @@ namespace confighttp { print_req(request); - std::string content = read_file(config::stream.file_apps.c_str()); + std::string content = file_handler::read_file(config::stream.file_apps.c_str()); SimpleWeb::CaseInsensitiveMultimap headers; headers.emplace("Content-Type", "application/json"); response->write(content, headers); @@ -336,7 +337,7 @@ namespace confighttp { print_req(request); - std::string content = read_file(config::sunshine.log_file.c_str()); + std::string content = file_handler::read_file(config::sunshine.log_file.c_str()); SimpleWeb::CaseInsensitiveMultimap headers; headers.emplace("Content-Type", "text/plain"); response->write(SimpleWeb::StatusCode::success_ok, content, headers); @@ -542,7 +543,7 @@ namespace confighttp { outputTree.put("platform", SUNSHINE_PLATFORM); outputTree.put("version", PROJECT_VER); - auto vars = config::parse_config(read_file(config::sunshine.config_file.c_str())); + auto vars = config::parse_config(file_handler::read_file(config::sunshine.config_file.c_str())); for (auto &[name, value] : vars) { outputTree.put(std::move(name), std::move(value)); @@ -575,7 +576,7 @@ namespace confighttp { configStream << kv.first << " = " << value << std::endl; } - write_file(config::sunshine.config_file.c_str(), configStream.str()); + file_handler::write_file(config::sunshine.config_file.c_str(), configStream.str()); } catch (std::exception &e) { BOOST_LOG(warning) << "SaveConfig: "sv << e.what(); diff --git a/src/file_handler.cpp b/src/file_handler.cpp new file mode 100644 index 00000000000..d0783431808 --- /dev/null +++ b/src/file_handler.cpp @@ -0,0 +1,69 @@ +/** + * @file file_handler.cpp + * @brief File handling functions. + */ + +// standard includes +#include +#include + +// local includes +#include "file_handler.h" +#include "logging.h" + +namespace file_handler { + + /** + * @brief Read a file to string. + * @param path The path of the file. + * @return `std::string` : The contents of the file. + * + * EXAMPLES: + * ```cpp + * std::string contents = read_file("path/to/file"); + * ``` + */ + std::string + read_file(const char *path) { + if (!std::filesystem::exists(path)) { + BOOST_LOG(debug) << "Missing file: " << path; + return {}; + } + + std::ifstream in(path); + + std::string input; + std::string base64_cert; + + while (!in.eof()) { + std::getline(in, input); + base64_cert += input + '\n'; + } + + return base64_cert; + } + + /** + * @brief Writes a file. + * @param path The path of the file. + * @param contents The contents to write. + * @return `int` : `0` on success, `-1` on failure. + * + * EXAMPLES: + * ```cpp + * int write_status = write_file("path/to/file", "file contents"); + * ``` + */ + int + write_file(const char *path, const std::string_view &contents) { + std::ofstream out(path); + + if (!out.is_open()) { + return -1; + } + + out << contents; + + return 0; + } +} // namespace file_handler diff --git a/src/file_handler.h b/src/file_handler.h new file mode 100644 index 00000000000..aa2387f8469 --- /dev/null +++ b/src/file_handler.h @@ -0,0 +1,14 @@ +/** + * @file file_handler.h + * @brief Header file for file handling functions. + */ +#pragma once + +#include + +namespace file_handler { + std::string + read_file(const char *path); + int + write_file(const char *path, const std::string_view &contents); +} // namespace file_handler diff --git a/src/httpcommon.cpp b/src/httpcommon.cpp index 849fd81af79..aa92b3bd3d8 100644 --- a/src/httpcommon.cpp +++ b/src/httpcommon.cpp @@ -21,9 +21,9 @@ #include "config.h" #include "crypto.h" +#include "file_handler.h" #include "httpcommon.h" #include "logging.h" -#include "main.h" #include "network.h" #include "nvhttp.h" #include "platform/common.h" @@ -161,12 +161,12 @@ namespace http { return -1; } - if (write_file(pkey.c_str(), creds.pkey)) { + if (file_handler::write_file(pkey.c_str(), creds.pkey)) { BOOST_LOG(error) << "Couldn't open ["sv << config::nvhttp.pkey << ']'; return -1; } - if (write_file(cert.c_str(), creds.x509)) { + if (file_handler::write_file(cert.c_str(), creds.x509)) { BOOST_LOG(error) << "Couldn't open ["sv << config::nvhttp.cert << ']'; return -1; } diff --git a/src/main.cpp b/src/main.cpp index e317e4d6451..1d1bb305398 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -758,57 +758,3 @@ main(int argc, char *argv[]) { return lifetime::desired_exit_code; } - -/** - * @brief Read a file to string. - * @param path The path of the file. - * @return `std::string` : The contents of the file. - * - * EXAMPLES: - * ```cpp - * std::string contents = read_file("path/to/file"); - * ``` - */ -std::string -read_file(const char *path) { - if (!std::filesystem::exists(path)) { - BOOST_LOG(debug) << "Missing file: " << path; - return {}; - } - - std::ifstream in(path); - - std::string input; - std::string base64_cert; - - while (!in.eof()) { - std::getline(in, input); - base64_cert += input + '\n'; - } - - return base64_cert; -} - -/** - * @brief Writes a file. - * @param path The path of the file. - * @param contents The contents to write. - * @return `int` : `0` on success, `-1` on failure. - * - * EXAMPLES: - * ```cpp - * int write_status = write_file("path/to/file", "file contents"); - * ``` - */ -int -write_file(const char *path, const std::string_view &contents) { - std::ofstream out(path); - - if (!out.is_open()) { - return -1; - } - - out << contents; - - return 0; -} diff --git a/src/main.h b/src/main.h index ba726539145..02a21fd321e 100644 --- a/src/main.h +++ b/src/main.h @@ -26,10 +26,6 @@ extern bool display_cursor; // functions int main(int argc, char *argv[]); -std::string -read_file(const char *path); -int -write_file(const char *path, const std::string_view &contents); void launch_ui(); void diff --git a/src/nvhttp.cpp b/src/nvhttp.cpp index 6fb7ab363aa..312c68fcb7a 100644 --- a/src/nvhttp.cpp +++ b/src/nvhttp.cpp @@ -22,6 +22,7 @@ // local includes #include "config.h" #include "crypto.h" +#include "file_handler.h" #include "httpcommon.h" #include "logging.h" #include "main.h" @@ -1005,8 +1006,8 @@ namespace nvhttp { load_state(); } - conf_intern.pkey = read_file(config::nvhttp.pkey.c_str()); - conf_intern.servercert = read_file(config::nvhttp.cert.c_str()); + conf_intern.pkey = file_handler::read_file(config::nvhttp.pkey.c_str()); + conf_intern.servercert = file_handler::read_file(config::nvhttp.cert.c_str()); crypto::cert_chain_t cert_chain; for (auto &[_, client] : map_id_client) { diff --git a/src/platform/linux/graphics.cpp b/src/platform/linux/graphics.cpp index ce0359a98fd..b4129889562 100644 --- a/src/platform/linux/graphics.cpp +++ b/src/platform/linux/graphics.cpp @@ -3,8 +3,8 @@ * @brief todo */ #include "graphics.h" +#include "src/file_handler.h" #include "src/logging.h" -#include "src/main.h" #include "src/video.h" #include @@ -780,7 +780,7 @@ namespace egl { for (int x = 0; x < count; ++x) { auto &compiled_source = compiled_sources[x]; - compiled_source = gl::shader_t::compile(read_file(sources[x]), shader_type[x % 2]); + compiled_source = gl::shader_t::compile(file_handler::read_file(sources[x]), shader_type[x % 2]); gl_drain_errors; if (compiled_source.has_right()) { From 11c8cf176c7b001674e8e7a09f7c3340b828f203 Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Sat, 10 Feb 2024 00:04:54 -0600 Subject: [PATCH 050/182] Simplify and fix handling of incomplete substitution strings --- src/platform/windows/misc.cpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/platform/windows/misc.cpp b/src/platform/windows/misc.cpp index 192fce8e0ca..b8bf770c7e8 100644 --- a/src/platform/windows/misc.cpp +++ b/src/platform/windows/misc.cpp @@ -711,13 +711,15 @@ namespace platf { while ((match_pos = cmd_string.find_first_of(L'%', match_pos)) != std::wstring::npos) { std::wstring match_replacement; + // If no additional character exists after the match, the dangling '%' is stripped + if (match_pos + 1 == cmd_string.size()) { + cmd_string.erase(match_pos, 1); + break; + } + // Shell command replacements are strictly '%' followed by a single non-'%' character - auto next_char = std::tolower(match_pos + 1 < cmd_string.size() ? cmd_string.at(match_pos + 1) : 0); + auto next_char = std::tolower(cmd_string.at(match_pos + 1)); switch (next_char) { - // No next character - case 0: - break; - // Escape character case L'%': // Skip this character and the next one From c0ad9639c4d7329ea2a681b7bbce09181f789347 Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Sat, 10 Feb 2024 00:06:44 -0600 Subject: [PATCH 051/182] Fix spacing of multi-argument %* substitutions --- src/platform/windows/misc.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/platform/windows/misc.cpp b/src/platform/windows/misc.cpp index b8bf770c7e8..cd16e1780fb 100644 --- a/src/platform/windows/misc.cpp +++ b/src/platform/windows/misc.cpp @@ -753,6 +753,9 @@ namespace platf { // All arguments following the target case L'*': for (int i = 1; i < raw_cmd_parts.size(); i++) { + if (i > 1) { + match_replacement += L' '; + } match_replacement += raw_cmd_parts.at(i); } break; From 78ed91af5cf56bd0e0195c66048064876e285761 Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Sat, 10 Feb 2024 00:15:30 -0600 Subject: [PATCH 052/182] Treat escape sequences like other replacements --- src/platform/windows/misc.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/platform/windows/misc.cpp b/src/platform/windows/misc.cpp index cd16e1780fb..1bbfe113a38 100644 --- a/src/platform/windows/misc.cpp +++ b/src/platform/windows/misc.cpp @@ -722,9 +722,8 @@ namespace platf { switch (next_char) { // Escape character case L'%': - // Skip this character and the next one - match_pos += 2; - continue; + match_replacement = L'%'; + break; // Argument replacements case L'0': From 65493d09e89e73e32ea348bbfb9b4e90e23794ca Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Sat, 10 Feb 2024 00:23:15 -0600 Subject: [PATCH 053/182] Cloak MAC address from unpaired clients --- src/nvhttp.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/nvhttp.cpp b/src/nvhttp.cpp index 312c68fcb7a..af211c579b7 100644 --- a/src/nvhttp.cpp +++ b/src/nvhttp.cpp @@ -660,9 +660,17 @@ namespace nvhttp { tree.put("root.uniqueid", http::unique_id); tree.put("root.HttpsPort", net::map_port(PORT_HTTPS)); tree.put("root.ExternalPort", net::map_port(PORT_HTTP)); - tree.put("root.mac", platf::get_mac_address(net::addr_to_normalized_string(local_endpoint.address()))); tree.put("root.MaxLumaPixelsHEVC", video::active_hevc_mode > 1 ? "1869449984" : "0"); + // Only include the MAC address for requests sent from paired clients over HTTPS. + // For HTTP requests, use a placeholder MAC address that Moonlight knows to ignore. + if constexpr (std::is_same_v) { + tree.put("root.mac", platf::get_mac_address(net::addr_to_normalized_string(local_endpoint.address()))); + } + else { + tree.put("root.mac", "00:00:00:00:00:00"); + } + // Moonlight clients track LAN IPv6 addresses separately from LocalIP which is expected to // always be an IPv4 address. If we return that same IPv6 address here, it will clobber the // stored LAN IPv4 address. To avoid this, we need to return an IPv4 address in this field From a420760d36b5406d8c49a8ad435bfdbe8f8f80d6 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sun, 11 Feb 2024 09:01:14 -0500 Subject: [PATCH 054/182] ci(linux): increase root reserve for AppImage build (#2130) --- .github/workflows/CI.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index fb74710eda1..4b53e3204e9 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -286,7 +286,7 @@ jobs: - name: Maximize build space uses: easimon/maximize-build-space@v8 with: - root-reserve-mb: 20480 + root-reserve-mb: 30720 remove-dotnet: 'true' remove-android: 'true' remove-haskell: 'true' From 8689469ea8387bff6a7483c7c8bb565551bd8f41 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sun, 11 Feb 2024 14:15:45 -0500 Subject: [PATCH 055/182] refactor(main): move remaining entry related code (#2127) --- cmake/compile_definitions/common.cmake | 4 + docs/source/source_code/src/entry_handler.rst | 5 + docs/source/source_code/src/globals.rst | 5 + src/audio.cpp | 2 +- src/config.cpp | 3 +- src/confighttp.cpp | 2 +- src/entry_handler.cpp | 384 ++++++++++++++++++ src/entry_handler.h | 60 +++ src/globals.cpp | 27 ++ src/globals.h | 42 ++ src/input.cpp | 2 +- src/main.cpp | 353 +--------------- src/main.h | 67 --- src/nvhttp.cpp | 2 +- src/platform/linux/audio.cpp | 2 +- src/platform/linux/cuda.cpp | 4 +- src/platform/linux/input.cpp | 2 +- src/platform/linux/kmsgrab.cpp | 2 +- src/platform/linux/misc.cpp | 2 +- src/platform/linux/wlgrab.cpp | 3 +- src/platform/linux/x11grab.cpp | 3 +- src/platform/macos/misc.mm | 2 +- src/platform/windows/display_base.cpp | 2 +- src/platform/windows/input.cpp | 3 +- src/platform/windows/misc.cpp | 3 +- src/platform/windows/publish.cpp | 1 - src/process.cpp | 2 +- src/rtsp.cpp | 2 +- src/stream.cpp | 2 +- src/system_tray.cpp | 2 +- src/upnp.cpp | 2 +- src/video.cpp | 2 +- 32 files changed, 560 insertions(+), 439 deletions(-) create mode 100644 docs/source/source_code/src/entry_handler.rst create mode 100644 docs/source/source_code/src/globals.rst create mode 100644 src/entry_handler.cpp create mode 100644 src/entry_handler.h create mode 100644 src/globals.cpp create mode 100644 src/globals.h diff --git a/cmake/compile_definitions/common.cmake b/cmake/compile_definitions/common.cmake index e51dbf56526..94f1ac598cc 100644 --- a/cmake/compile_definitions/common.cmake +++ b/cmake/compile_definitions/common.cmake @@ -45,8 +45,12 @@ set(SUNSHINE_TARGET_FILES "${CMAKE_SOURCE_DIR}/src/uuid.h" "${CMAKE_SOURCE_DIR}/src/config.h" "${CMAKE_SOURCE_DIR}/src/config.cpp" + "${CMAKE_SOURCE_DIR}/src/entry_handler.cpp" + "${CMAKE_SOURCE_DIR}/src/entry_handler.h" "${CMAKE_SOURCE_DIR}/src/file_handler.cpp" "${CMAKE_SOURCE_DIR}/src/file_handler.h" + "${CMAKE_SOURCE_DIR}/src/globals.cpp" + "${CMAKE_SOURCE_DIR}/src/globals.h" "${CMAKE_SOURCE_DIR}/src/logging.cpp" "${CMAKE_SOURCE_DIR}/src/logging.h" "${CMAKE_SOURCE_DIR}/src/main.cpp" diff --git a/docs/source/source_code/src/entry_handler.rst b/docs/source/source_code/src/entry_handler.rst new file mode 100644 index 00000000000..c522b065652 --- /dev/null +++ b/docs/source/source_code/src/entry_handler.rst @@ -0,0 +1,5 @@ +entry_handler +============= + +.. doxygenfile:: entry_handler.h + :allow-dot-graphs: diff --git a/docs/source/source_code/src/globals.rst b/docs/source/source_code/src/globals.rst new file mode 100644 index 00000000000..ed70cecf692 --- /dev/null +++ b/docs/source/source_code/src/globals.rst @@ -0,0 +1,5 @@ +globals +======= + +.. doxygenfile:: globals.h + :allow-dot-graphs: diff --git a/src/audio.cpp b/src/audio.cpp index a3555eaa080..1995e380ea7 100644 --- a/src/audio.cpp +++ b/src/audio.cpp @@ -10,8 +10,8 @@ #include "audio.h" #include "config.h" +#include "globals.h" #include "logging.h" -#include "main.h" #include "thread_safe.h" #include "utility.h" diff --git a/src/config.cpp b/src/config.cpp index 5cd08cee94e..d9aaab3e31c 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include @@ -15,9 +16,9 @@ #include #include "config.h" +#include "entry_handler.h" #include "file_handler.h" #include "logging.h" -#include "main.h" #include "nvhttp.h" #include "rtsp.h" #include "utility.h" diff --git a/src/confighttp.cpp b/src/confighttp.cpp index e3a5f898e39..0657902dd16 100644 --- a/src/confighttp.cpp +++ b/src/confighttp.cpp @@ -30,9 +30,9 @@ #include "confighttp.h" #include "crypto.h" #include "file_handler.h" +#include "globals.h" #include "httpcommon.h" #include "logging.h" -#include "main.h" #include "network.h" #include "nvhttp.h" #include "platform/common.h" diff --git a/src/entry_handler.cpp b/src/entry_handler.cpp new file mode 100644 index 00000000000..c7719eb0ed1 --- /dev/null +++ b/src/entry_handler.cpp @@ -0,0 +1,384 @@ +/** + * @file entry_handler.cpp + * @brief Entry point related functions. + */ + +// standard includes +#include +#include +#include + +// local includes +#include "config.h" +#include "confighttp.h" +#include "entry_handler.h" +#include "globals.h" +#include "httpcommon.h" +#include "logging.h" +#include "network.h" +#include "platform/common.h" +#include "version.h" + +extern "C" { +#ifdef _WIN32 + #include +#endif +} + +using namespace std::literals; + +/** + * @brief Launch the Web UI. + * + * EXAMPLES: + * ```cpp + * launch_ui(); + * ``` + */ +void +launch_ui() { + std::string url = "https://localhost:" + std::to_string(net::map_port(confighttp::PORT_HTTPS)); + platf::open_url(url); +} + +/** + * @brief Launch the Web UI at a specific endpoint. + * + * EXAMPLES: + * ```cpp + * launch_ui_with_path("/pin"); + * ``` + */ +void +launch_ui_with_path(std::string path) { + std::string url = "https://localhost:" + std::to_string(net::map_port(confighttp::PORT_HTTPS)) + path; + platf::open_url(url); +} + +namespace args { + /** + * @brief Reset the user credentials. + * + * @param name The name of the program. + * @param argc The number of arguments. + * @param argv The arguments. + * + * EXAMPLES: + * ```cpp + * creds("sunshine", 2, {"new_username", "new_password"}); + * ``` + */ + int + creds(const char *name, int argc, char *argv[]) { + if (argc < 2 || argv[0] == "help"sv || argv[1] == "help"sv) { + help(name, argc, argv); + } + + http::save_user_creds(config::sunshine.credentials_file, argv[0], argv[1]); + + return 0; + } + + /** + * @brief Print help to stdout, then exit. + * @param name The name of the program. + * @param argc The number of arguments. (Unused) + * @param argv The arguments. (Unused) + * + * EXAMPLES: + * ```cpp + * print_help("sunshine", 0, nullptr); + * ``` + */ + int + help(const char *name, int argc, char *argv[]) { + print_help(name); + return 0; + } + + /** + * @brief Print the version to stdout, then exit. + * @param name The name of the program. (Unused) + * @param argc The number of arguments. (Unused) + * @param argv The arguments. (Unused) + * + * EXAMPLES: + * ```cpp + * version("sunshine", 0, nullptr); + * ``` + */ + int + version(const char *name, int argc, char *argv[]) { + std::cout << PROJECT_NAME << " version: v" << PROJECT_VER << std::endl; + return 0; + } + +#ifdef _WIN32 + /** + * @brief Restore global NVIDIA control panel settings. + * + * If Sunshine was improperly terminated, this function restores + * the global NVIDIA control panel settings to the undo file left + * by Sunshine. This function is typically called by the uninstaller. + * + * @param name The name of the program. (Unused) + * @param argc The number of arguments. (Unused) + * @param argv The arguments. (Unused) + * + * EXAMPLES: + * ```cpp + * restore_nvprefs_undo("sunshine", 0, nullptr); + * ``` + */ + int + restore_nvprefs_undo(const char *name, int argc, char *argv[]) { + if (nvprefs_instance.load()) { + nvprefs_instance.restore_from_and_delete_undo_file_if_exists(); + nvprefs_instance.unload(); + } + return 0; + } +#endif +} // namespace args + +namespace lifetime { + char **argv; + 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); + } + } + + /** + * @brief Gets the argv array passed to main(). + */ + char ** + get_argv() { + return argv; + } +} // namespace lifetime + +#ifdef _WIN32 +/** + * @brief Check if NVIDIA's GameStream software is running. + * @return `true` if GameStream is enabled, `false` otherwise. + */ +bool +is_gamestream_enabled() { + DWORD enabled; + DWORD size = sizeof(enabled); + return RegGetValueW( + HKEY_LOCAL_MACHINE, + L"SOFTWARE\\NVIDIA Corporation\\NvStream", + L"EnableStreaming", + RRF_RT_REG_DWORD, + nullptr, + &enabled, + &size) == ERROR_SUCCESS && + enabled != 0; +} + +namespace service_ctrl { + class service_controller { + public: + /** + * @brief Constructor for service_controller class. + * @param service_desired_access SERVICE_* desired access flags. + */ + service_controller(DWORD service_desired_access) { + scm_handle = OpenSCManagerA(nullptr, nullptr, SC_MANAGER_CONNECT); + if (!scm_handle) { + auto winerr = GetLastError(); + BOOST_LOG(error) << "OpenSCManager() failed: "sv << winerr; + return; + } + + service_handle = OpenServiceA(scm_handle, "SunshineService", service_desired_access); + if (!service_handle) { + auto winerr = GetLastError(); + BOOST_LOG(error) << "OpenService() failed: "sv << winerr; + return; + } + } + + ~service_controller() { + if (service_handle) { + CloseServiceHandle(service_handle); + } + + if (scm_handle) { + CloseServiceHandle(scm_handle); + } + } + + /** + * @brief Asynchronously starts the Sunshine service. + */ + bool + start_service() { + if (!service_handle) { + return false; + } + + if (!StartServiceA(service_handle, 0, nullptr)) { + auto winerr = GetLastError(); + if (winerr != ERROR_SERVICE_ALREADY_RUNNING) { + BOOST_LOG(error) << "StartService() failed: "sv << winerr; + return false; + } + } + + return true; + } + + /** + * @brief Query the service status. + * @param status The SERVICE_STATUS struct to populate. + */ + bool + query_service_status(SERVICE_STATUS &status) { + if (!service_handle) { + return false; + } + + if (!QueryServiceStatus(service_handle, &status)) { + auto winerr = GetLastError(); + BOOST_LOG(error) << "QueryServiceStatus() failed: "sv << winerr; + return false; + } + + return true; + } + + private: + SC_HANDLE scm_handle = NULL; + SC_HANDLE service_handle = NULL; + }; + + /** + * @brief Check if the service is running. + * + * EXAMPLES: + * ```cpp + * is_service_running(); + * ``` + */ + bool + is_service_running() { + service_controller sc { SERVICE_QUERY_STATUS }; + + SERVICE_STATUS status; + if (!sc.query_service_status(status)) { + return false; + } + + return status.dwCurrentState == SERVICE_RUNNING; + } + + /** + * @brief Start the service and wait for startup to complete. + * + * EXAMPLES: + * ```cpp + * start_service(); + * ``` + */ + bool + start_service() { + service_controller sc { SERVICE_QUERY_STATUS | SERVICE_START }; + + std::cout << "Starting Sunshine..."sv; + + // This operation is asynchronous, so we must wait for it to complete + if (!sc.start_service()) { + return false; + } + + SERVICE_STATUS status; + do { + Sleep(1000); + std::cout << '.'; + } while (sc.query_service_status(status) && status.dwCurrentState == SERVICE_START_PENDING); + + if (status.dwCurrentState != SERVICE_RUNNING) { + BOOST_LOG(error) << SERVICE_NAME " failed to start: "sv << status.dwWin32ExitCode; + return false; + } + + std::cout << std::endl; + return true; + } + + /** + * @brief Wait for the UI to be ready after Sunshine startup. + * + * EXAMPLES: + * ```cpp + * wait_for_ui_ready(); + * ``` + */ + bool + wait_for_ui_ready() { + std::cout << "Waiting for Web UI to be ready..."; + + // Wait up to 30 seconds for the web UI to start + for (int i = 0; i < 30; i++) { + PMIB_TCPTABLE tcp_table = nullptr; + ULONG table_size = 0; + ULONG err; + + auto fg = util::fail_guard([&tcp_table]() { + free(tcp_table); + }); + + do { + // Query all open TCP sockets to look for our web UI port + err = GetTcpTable(tcp_table, &table_size, false); + if (err == ERROR_INSUFFICIENT_BUFFER) { + free(tcp_table); + tcp_table = (PMIB_TCPTABLE) malloc(table_size); + } + } while (err == ERROR_INSUFFICIENT_BUFFER); + + if (err != NO_ERROR) { + BOOST_LOG(error) << "Failed to query TCP table: "sv << err; + return false; + } + + uint16_t port_nbo = htons(net::map_port(confighttp::PORT_HTTPS)); + for (DWORD i = 0; i < tcp_table->dwNumEntries; i++) { + auto &entry = tcp_table->table[i]; + + // Look for our port in the listening state + if (entry.dwLocalPort == port_nbo && entry.dwState == MIB_TCP_STATE_LISTEN) { + std::cout << std::endl; + return true; + } + } + + Sleep(1000); + std::cout << '.'; + } + + std::cout << "timed out"sv << std::endl; + return false; + } +} // namespace service_ctrl +#endif diff --git a/src/entry_handler.h b/src/entry_handler.h new file mode 100644 index 00000000000..c58d0325d70 --- /dev/null +++ b/src/entry_handler.h @@ -0,0 +1,60 @@ +/** + * @file entry_handler.h + * @brief Header file for entry point functions. + */ +#pragma once + +// standard includes +#include +#include + +// local includes +#include "thread_pool.h" +#include "thread_safe.h" + +// functions +void +launch_ui(); +void +launch_ui_with_path(std::string path); + +#ifdef _WIN32 +// windows only functions +bool +is_gamestream_enabled(); +#endif + +namespace args { + int + creds(const char *name, int argc, char *argv[]); + int + help(const char *name, int argc, char *argv[]); + int + version(const char *name, int argc, char *argv[]); +#ifdef _WIN32 + int + restore_nvprefs_undo(const char *name, int argc, char *argv[]); +#endif +} // namespace args + +namespace lifetime { + extern char **argv; + extern std::atomic_int desired_exit_code; + void + exit_sunshine(int exit_code, bool async); + char ** + get_argv(); +} // namespace lifetime + +#ifdef _WIN32 +namespace service_ctrl { + bool + is_service_running(); + + bool + start_service(); + + bool + wait_for_ui_ready(); +} // namespace service_ctrl +#endif diff --git a/src/globals.cpp b/src/globals.cpp new file mode 100644 index 00000000000..ae6c7544360 --- /dev/null +++ b/src/globals.cpp @@ -0,0 +1,27 @@ +/** + * @file globals.cpp + * @brief Implementation for globally accessible variables and functions. + */ +#include "globals.h" + +/** + * @brief A process-wide communication mechanism. + */ +safe::mail_t mail::man; + +/** + * @brief A thread pool for processing tasks. + */ +thread_pool_util::ThreadPool task_pool; + +/** + * @brief A boolean flag to indicate whether the cursor should be displayed. + */ +bool display_cursor = true; + +#ifdef _WIN32 +/** + * @brief A global singleton used for NVIDIA control panel modifications. + */ +nvprefs::nvprefs_interface nvprefs_instance; +#endif diff --git a/src/globals.h b/src/globals.h new file mode 100644 index 00000000000..a137bc9c4d5 --- /dev/null +++ b/src/globals.h @@ -0,0 +1,42 @@ +/** + * @file globals.h + * @brief Header for globally accessible variables and functions. + */ +#pragma once + +#include "entry_handler.h" +#include "thread_pool.h" + +extern thread_pool_util::ThreadPool task_pool; +extern bool display_cursor; + +#ifdef _WIN32 + // Declare global singleton used for NVIDIA control panel modifications + #include "platform/windows/nvprefs/nvprefs_interface.h" +extern nvprefs::nvprefs_interface nvprefs_instance; +#endif + +namespace mail { +#define MAIL(x) \ + constexpr auto x = std::string_view { \ + #x \ + } + + extern safe::mail_t man; + + // Global mail + MAIL(shutdown); + MAIL(broadcast_shutdown); + MAIL(video_packets); + MAIL(audio_packets); + MAIL(switch_display); + + // Local mail + MAIL(touch_port); + MAIL(idr); + MAIL(invalidate_ref_frames); + MAIL(gamepad_feedback); + MAIL(hdr); +#undef MAIL + +} // namespace mail diff --git a/src/input.cpp b/src/input.cpp index b7416fffac2..89f7291f11a 100644 --- a/src/input.cpp +++ b/src/input.cpp @@ -17,9 +17,9 @@ extern "C" { #include #include "config.h" +#include "globals.h" #include "input.h" #include "logging.h" -#include "main.h" #include "platform/common.h" #include "thread_pool.h" #include "utility.h" diff --git a/src/main.cpp b/src/main.cpp index 1d1bb305398..cbe481ee2e9 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -5,31 +5,22 @@ // standard includes #include -#include #include #include -#include // lib includes -#include -#include #include -#include -#include // local includes -#include "config.h" #include "confighttp.h" +#include "entry_handler.h" +#include "globals.h" #include "httpcommon.h" #include "logging.h" #include "main.h" -#include "network.h" #include "nvhttp.h" -#include "platform/common.h" #include "process.h" -#include "rtsp.h" #include "system_tray.h" -#include "thread_pool.h" #include "upnp.h" #include "version.h" #include "video.h" @@ -37,26 +28,11 @@ extern "C" { #include #include - -#ifdef _WIN32 - #include -#endif } -safe::mail_t mail::man; - using namespace std::literals; namespace bl = boost::log; -#ifdef _WIN32 -// Define global singleton used for NVIDIA control panel modifications -nvprefs::nvprefs_interface nvprefs_instance; -#endif - -thread_pool_util::ThreadPool task_pool; - -bool display_cursor = true; - struct NoDelete { void operator()(void *) {} @@ -64,309 +40,6 @@ struct NoDelete { BOOST_LOG_ATTRIBUTE_KEYWORD(severity, "Severity", int) -namespace help { - int - entry(const char *name, int argc, char *argv[]) { - print_help(name); - return 0; - } -} // namespace help - -namespace version { - int - entry(const char *name, int argc, char *argv[]) { - std::cout << PROJECT_NAME << " version: v" << PROJECT_VER << std::endl; - return 0; - } -} // namespace version - -#ifdef _WIN32 -namespace restore_nvprefs_undo { - int - entry(const char *name, int argc, char *argv[]) { - // Restore global NVIDIA control panel settings to the undo file - // left by improper termination of sunshine.exe, if it exists. - // This entry point is typically called by the uninstaller. - if (nvprefs_instance.load()) { - nvprefs_instance.restore_from_and_delete_undo_file_if_exists(); - nvprefs_instance.unload(); - } - return 0; - } -} // namespace restore_nvprefs_undo -#endif - -namespace lifetime { - static char **argv; - 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); - } - } - - /** - * @brief Gets the argv array passed to main(). - */ - char ** - get_argv() { - return argv; - } -} // namespace lifetime - -#ifdef _WIN32 -namespace service_ctrl { - class service_controller { - public: - /** - * @brief Constructor for service_controller class. - * @param service_desired_access SERVICE_* desired access flags. - */ - service_controller(DWORD service_desired_access) { - scm_handle = OpenSCManagerA(nullptr, nullptr, SC_MANAGER_CONNECT); - if (!scm_handle) { - auto winerr = GetLastError(); - BOOST_LOG(error) << "OpenSCManager() failed: "sv << winerr; - return; - } - - service_handle = OpenServiceA(scm_handle, "SunshineService", service_desired_access); - if (!service_handle) { - auto winerr = GetLastError(); - BOOST_LOG(error) << "OpenService() failed: "sv << winerr; - return; - } - } - - ~service_controller() { - if (service_handle) { - CloseServiceHandle(service_handle); - } - - if (scm_handle) { - CloseServiceHandle(scm_handle); - } - } - - /** - * @brief Asynchronously starts the Sunshine service. - */ - bool - start_service() { - if (!service_handle) { - return false; - } - - if (!StartServiceA(service_handle, 0, nullptr)) { - auto winerr = GetLastError(); - if (winerr != ERROR_SERVICE_ALREADY_RUNNING) { - BOOST_LOG(error) << "StartService() failed: "sv << winerr; - return false; - } - } - - return true; - } - - /** - * @brief Query the service status. - * @param status The SERVICE_STATUS struct to populate. - */ - bool - query_service_status(SERVICE_STATUS &status) { - if (!service_handle) { - return false; - } - - if (!QueryServiceStatus(service_handle, &status)) { - auto winerr = GetLastError(); - BOOST_LOG(error) << "QueryServiceStatus() failed: "sv << winerr; - return false; - } - - return true; - } - - private: - SC_HANDLE scm_handle = NULL; - SC_HANDLE service_handle = NULL; - }; - - /** - * @brief Check if the service is running. - * - * EXAMPLES: - * ```cpp - * is_service_running(); - * ``` - */ - bool - is_service_running() { - service_controller sc { SERVICE_QUERY_STATUS }; - - SERVICE_STATUS status; - if (!sc.query_service_status(status)) { - return false; - } - - return status.dwCurrentState == SERVICE_RUNNING; - } - - /** - * @brief Start the service and wait for startup to complete. - * - * EXAMPLES: - * ```cpp - * start_service(); - * ``` - */ - bool - start_service() { - service_controller sc { SERVICE_QUERY_STATUS | SERVICE_START }; - - std::cout << "Starting Sunshine..."sv; - - // This operation is asynchronous, so we must wait for it to complete - if (!sc.start_service()) { - return false; - } - - SERVICE_STATUS status; - do { - Sleep(1000); - std::cout << '.'; - } while (sc.query_service_status(status) && status.dwCurrentState == SERVICE_START_PENDING); - - if (status.dwCurrentState != SERVICE_RUNNING) { - BOOST_LOG(error) << SERVICE_NAME " failed to start: "sv << status.dwWin32ExitCode; - return false; - } - - std::cout << std::endl; - return true; - } - - /** - * @brief Wait for the UI to be ready after Sunshine startup. - * - * EXAMPLES: - * ```cpp - * wait_for_ui_ready(); - * ``` - */ - bool - wait_for_ui_ready() { - std::cout << "Waiting for Web UI to be ready..."; - - // Wait up to 30 seconds for the web UI to start - for (int i = 0; i < 30; i++) { - PMIB_TCPTABLE tcp_table = nullptr; - ULONG table_size = 0; - ULONG err; - - auto fg = util::fail_guard([&tcp_table]() { - free(tcp_table); - }); - - do { - // Query all open TCP sockets to look for our web UI port - err = GetTcpTable(tcp_table, &table_size, false); - if (err == ERROR_INSUFFICIENT_BUFFER) { - free(tcp_table); - tcp_table = (PMIB_TCPTABLE) malloc(table_size); - } - } while (err == ERROR_INSUFFICIENT_BUFFER); - - if (err != NO_ERROR) { - BOOST_LOG(error) << "Failed to query TCP table: "sv << err; - return false; - } - - uint16_t port_nbo = htons(net::map_port(confighttp::PORT_HTTPS)); - for (DWORD i = 0; i < tcp_table->dwNumEntries; i++) { - auto &entry = tcp_table->table[i]; - - // Look for our port in the listening state - if (entry.dwLocalPort == port_nbo && entry.dwState == MIB_TCP_STATE_LISTEN) { - std::cout << std::endl; - return true; - } - } - - Sleep(1000); - std::cout << '.'; - } - - std::cout << "timed out"sv << std::endl; - return false; - } -} // namespace service_ctrl - -/** - * @brief Checks if NVIDIA's GameStream software is running. - * @return `true` if GameStream is enabled. - */ -bool -is_gamestream_enabled() { - DWORD enabled; - DWORD size = sizeof(enabled); - return RegGetValueW( - HKEY_LOCAL_MACHINE, - L"SOFTWARE\\NVIDIA Corporation\\NvStream", - L"EnableStreaming", - RRF_RT_REG_DWORD, - nullptr, - &enabled, - &size) == ERROR_SUCCESS && - enabled != 0; -} - -#endif - -/** - * @brief Launch the Web UI. - * - * EXAMPLES: - * ```cpp - * launch_ui(); - * ``` - */ -void -launch_ui() { - std::string url = "https://localhost:" + std::to_string(net::map_port(confighttp::PORT_HTTPS)); - platf::open_url(url); -} - -/** - * @brief Launch the Web UI at a specific endpoint. - * - * EXAMPLES: - * ```cpp - * launch_ui_with_path("/pin"); - * ``` - */ -void -launch_ui_with_path(std::string path) { - std::string url = "https://localhost:" + std::to_string(net::map_port(confighttp::PORT_HTTPS)) + path; - platf::open_url(url); -} - std::map> signal_handlers; void on_signal_forwarder(int sig) { @@ -381,26 +54,12 @@ on_signal(int sig, FN &&fn) { std::signal(sig, on_signal_forwarder); } -namespace gen_creds { - int - entry(const char *name, int argc, char *argv[]) { - if (argc < 2 || argv[0] == "help"sv || argv[1] == "help"sv) { - print_help(name); - return 0; - } - - http::save_user_creds(config::sunshine.credentials_file, argv[0], argv[1]); - - return 0; - } -} // namespace gen_creds - std::map> cmd_to_func { - { "creds"sv, gen_creds::entry }, - { "help"sv, help::entry }, - { "version"sv, version::entry }, + { "creds"sv, args::creds }, + { "help"sv, args::help }, + { "version"sv, args::version }, #ifdef _WIN32 - { "restore-nvprefs-undo"sv, restore_nvprefs_undo::entry }, + { "restore-nvprefs-undo"sv, args::restore_nvprefs_undo }, #endif }; diff --git a/src/main.h b/src/main.h index 02a21fd321e..f34ca6cda66 100644 --- a/src/main.h +++ b/src/main.h @@ -6,73 +6,6 @@ // macros #pragma once -// standard includes -#include -#include - -// local includes -#include "thread_pool.h" -#include "thread_safe.h" - -#ifdef _WIN32 - // Declare global singleton used for NVIDIA control panel modifications - #include "platform/windows/nvprefs/nvprefs_interface.h" -extern nvprefs::nvprefs_interface nvprefs_instance; -#endif - -extern thread_pool_util::ThreadPool task_pool; -extern bool display_cursor; - // functions int main(int argc, char *argv[]); -void -launch_ui(); -void -launch_ui_with_path(std::string path); - -// namespaces -namespace mail { -#define MAIL(x) \ - constexpr auto x = std::string_view { \ - #x \ - } - - extern safe::mail_t man; - - // Global mail - MAIL(shutdown); - MAIL(broadcast_shutdown); - MAIL(video_packets); - MAIL(audio_packets); - MAIL(switch_display); - - // Local mail - MAIL(touch_port); - MAIL(idr); - MAIL(invalidate_ref_frames); - MAIL(gamepad_feedback); - MAIL(hdr); -#undef MAIL - -} // namespace mail - -namespace lifetime { - void - exit_sunshine(int exit_code, bool async); - char ** - get_argv(); -} // namespace lifetime - -#ifdef _WIN32 -namespace service_ctrl { - bool - is_service_running(); - - bool - start_service(); - - bool - wait_for_ui_ready(); -} // namespace service_ctrl -#endif diff --git a/src/nvhttp.cpp b/src/nvhttp.cpp index af211c579b7..b8bddb44bb7 100644 --- a/src/nvhttp.cpp +++ b/src/nvhttp.cpp @@ -23,9 +23,9 @@ #include "config.h" #include "crypto.h" #include "file_handler.h" +#include "globals.h" #include "httpcommon.h" #include "logging.h" -#include "main.h" #include "network.h" #include "nvhttp.h" #include "platform/common.h" diff --git a/src/platform/linux/audio.cpp b/src/platform/linux/audio.cpp index 577287b77ef..e663c811ba2 100644 --- a/src/platform/linux/audio.cpp +++ b/src/platform/linux/audio.cpp @@ -4,6 +4,7 @@ */ #include #include +#include #include @@ -15,7 +16,6 @@ #include "src/config.h" #include "src/logging.h" -#include "src/main.h" #include "src/thread_safe.h" namespace platf { diff --git a/src/platform/linux/cuda.cpp b/src/platform/linux/cuda.cpp index 856fecc657a..5b121c70691 100644 --- a/src/platform/linux/cuda.cpp +++ b/src/platform/linux/cuda.cpp @@ -3,10 +3,9 @@ * @brief todo */ #include - #include - #include +#include #include #include @@ -20,7 +19,6 @@ extern "C" { #include "cuda.h" #include "graphics.h" #include "src/logging.h" -#include "src/main.h" #include "src/utility.h" #include "src/video.h" #include "wayland.h" diff --git a/src/platform/linux/input.cpp b/src/platform/linux/input.cpp index 43433e58a92..85818110730 100644 --- a/src/platform/linux/input.cpp +++ b/src/platform/linux/input.cpp @@ -20,11 +20,11 @@ #include #include #include +#include #include "src/config.h" #include "src/input.h" #include "src/logging.h" -#include "src/main.h" #include "src/platform/common.h" #include "src/utility.h" diff --git a/src/platform/linux/kmsgrab.cpp b/src/platform/linux/kmsgrab.cpp index a18fc31a823..192482deaa1 100644 --- a/src/platform/linux/kmsgrab.cpp +++ b/src/platform/linux/kmsgrab.cpp @@ -13,9 +13,9 @@ #include #include +#include #include "src/logging.h" -#include "src/main.h" #include "src/platform/common.h" #include "src/round_robin.h" #include "src/utility.h" diff --git a/src/platform/linux/misc.cpp b/src/platform/linux/misc.cpp index 8ead76b0715..ecc5887e0f1 100644 --- a/src/platform/linux/misc.cpp +++ b/src/platform/linux/misc.cpp @@ -26,8 +26,8 @@ #include "graphics.h" #include "misc.h" #include "src/config.h" +#include "src/entry_handler.h" #include "src/logging.h" -#include "src/main.h" #include "src/platform/common.h" #include "vaapi.h" diff --git a/src/platform/linux/wlgrab.cpp b/src/platform/linux/wlgrab.cpp index 6acde691479..84b69bd0be0 100644 --- a/src/platform/linux/wlgrab.cpp +++ b/src/platform/linux/wlgrab.cpp @@ -2,10 +2,11 @@ * @file src/platform/linux/wlgrab.cpp * @brief todo */ +#include + #include "src/platform/common.h" #include "src/logging.h" -#include "src/main.h" #include "src/video.h" #include "cuda.h" diff --git a/src/platform/linux/x11grab.cpp b/src/platform/linux/x11grab.cpp index 0c1583002b9..1167d3f5809 100644 --- a/src/platform/linux/x11grab.cpp +++ b/src/platform/linux/x11grab.cpp @@ -5,6 +5,7 @@ #include "src/platform/common.h" #include +#include #include #include @@ -17,8 +18,8 @@ #include #include "src/config.h" +#include "src/globals.h" #include "src/logging.h" -#include "src/main.h" #include "src/task_pool.h" #include "src/video.h" diff --git a/src/platform/macos/misc.mm b/src/platform/macos/misc.mm index 0a8bf1b78ae..20c2247e049 100644 --- a/src/platform/macos/misc.mm +++ b/src/platform/macos/misc.mm @@ -18,8 +18,8 @@ #include #include "misc.h" +#include "src/entry_handler.h" #include "src/logging.h" -#include "src/main.h" #include "src/platform/common.h" #include diff --git a/src/platform/windows/display_base.cpp b/src/platform/windows/display_base.cpp index 3ae6e337d26..78c927e7782 100644 --- a/src/platform/windows/display_base.cpp +++ b/src/platform/windows/display_base.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include @@ -16,7 +17,6 @@ typedef long NTSTATUS; #include "misc.h" #include "src/config.h" #include "src/logging.h" -#include "src/main.h" #include "src/platform/common.h" #include "src/stat_trackers.h" #include "src/video.h" diff --git a/src/platform/windows/input.cpp b/src/platform/windows/input.cpp index 144dac1999a..5056c2c7246 100644 --- a/src/platform/windows/input.cpp +++ b/src/platform/windows/input.cpp @@ -6,14 +6,15 @@ #include #include +#include #include #include "keylayout.h" #include "misc.h" #include "src/config.h" +#include "src/globals.h" #include "src/logging.h" -#include "src/main.h" #include "src/platform/common.h" #ifdef __MINGW32__ diff --git a/src/platform/windows/misc.cpp b/src/platform/windows/misc.cpp index 1bbfe113a38..708fd267484 100644 --- a/src/platform/windows/misc.cpp +++ b/src/platform/windows/misc.cpp @@ -35,8 +35,9 @@ #define NTDDI_VERSION NTDDI_WIN10 #include +#include "src/entry_handler.h" +#include "src/globals.h" #include "src/logging.h" -#include "src/main.h" #include "src/platform/common.h" #include "src/utility.h" #include diff --git a/src/platform/windows/publish.cpp b/src/platform/windows/publish.cpp index 47c16721e20..6eb4d8948e1 100644 --- a/src/platform/windows/publish.cpp +++ b/src/platform/windows/publish.cpp @@ -14,7 +14,6 @@ #include "misc.h" #include "src/config.h" #include "src/logging.h" -#include "src/main.h" #include "src/network.h" #include "src/nvhttp.h" #include "src/platform/common.h" diff --git a/src/process.cpp b/src/process.cpp index fef585cdae6..e660e8162a1 100644 --- a/src/process.cpp +++ b/src/process.cpp @@ -8,6 +8,7 @@ #include #include +#include #include #include @@ -23,7 +24,6 @@ #include "config.h" #include "crypto.h" #include "logging.h" -#include "main.h" #include "platform/common.h" #include "system_tray.h" #include "utility.h" diff --git a/src/rtsp.cpp b/src/rtsp.cpp index 73f224b8ef1..0180fbee37a 100644 --- a/src/rtsp.cpp +++ b/src/rtsp.cpp @@ -16,9 +16,9 @@ extern "C" { #include #include "config.h" +#include "globals.h" #include "input.h" #include "logging.h" -#include "main.h" #include "network.h" #include "rtsp.h" #include "stream.h" diff --git a/src/stream.cpp b/src/stream.cpp index 12fb8663fc8..b9238057b1c 100644 --- a/src/stream.cpp +++ b/src/stream.cpp @@ -18,9 +18,9 @@ extern "C" { } #include "config.h" +#include "globals.h" #include "input.h" #include "logging.h" -#include "main.h" #include "network.h" #include "stat_trackers.h" #include "stream.h" diff --git a/src/system_tray.cpp b/src/system_tray.cpp index 621ace0cfb9..39131ba30fe 100644 --- a/src/system_tray.cpp +++ b/src/system_tray.cpp @@ -38,9 +38,9 @@ // local includes #include "confighttp.h" #include "logging.h" - #include "main.h" #include "platform/common.h" #include "process.h" + #include "src/entry_handler.h" #include "version.h" using namespace std::literals; diff --git a/src/upnp.cpp b/src/upnp.cpp index 55c49aaf67c..f65bcb87cc4 100644 --- a/src/upnp.cpp +++ b/src/upnp.cpp @@ -7,8 +7,8 @@ #include "config.h" #include "confighttp.h" +#include "globals.h" #include "logging.h" -#include "main.h" #include "network.h" #include "nvhttp.h" #include "rtsp.h" diff --git a/src/video.cpp b/src/video.cpp index e013249880b..636bcf60138 100644 --- a/src/video.cpp +++ b/src/video.cpp @@ -19,9 +19,9 @@ extern "C" { #include "cbs.h" #include "config.h" +#include "globals.h" #include "input.h" #include "logging.h" -#include "main.h" #include "nvenc/nvenc_base.h" #include "platform/common.h" #include "sync.h" From 69a3edd9b01c76aa44fd5c2a29de1c3b3722cb41 Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Sun, 11 Feb 2024 15:16:41 -0600 Subject: [PATCH 056/182] Use Win32 APIs for UTF-16<->UTF-8 conversion std::codecvt is deprecated since C++17 and broken for some characters/locales --- src/platform/windows/audio.cpp | 15 ++--- src/platform/windows/display_base.cpp | 15 ++--- src/platform/windows/display_vram.cpp | 5 +- src/platform/windows/misc.cpp | 93 +++++++++++++++++++++++---- src/platform/windows/misc.h | 16 +++++ src/platform/windows/publish.cpp | 4 +- src/process.cpp | 6 +- 7 files changed, 113 insertions(+), 41 deletions(-) diff --git a/src/platform/windows/audio.cpp b/src/platform/windows/audio.cpp index 296112e1ad3..3e9267b32aa 100644 --- a/src/platform/windows/audio.cpp +++ b/src/platform/windows/audio.cpp @@ -7,8 +7,6 @@ #include #include -#include - #include #include @@ -19,6 +17,8 @@ #include "src/logging.h" #include "src/platform/common.h" +#include "misc.h" + // Must be the last included file // clang-format off #include "PolicyConfig.h" @@ -89,7 +89,6 @@ namespace platf::audio { PROPVARIANT prop; }; - static std::wstring_convert, wchar_t> converter; struct format_t { enum type_e : int { none, @@ -613,7 +612,7 @@ namespace platf::audio { audio::wstring_t wstring; device->GetId(&wstring); - sink.host = converter.to_bytes(wstring.get()); + sink.host = to_utf8(wstring.get()); collection_t collection; auto status = device_enum->EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE, &collection); @@ -627,7 +626,7 @@ namespace platf::audio { collection->GetCount(&count); // If the sink isn't a device name, we'll assume it's a device ID - auto virtual_device_id = find_device_id_by_name(config::audio.virtual_sink).value_or(converter.from_bytes(config::audio.virtual_sink)); + auto virtual_device_id = find_device_id_by_name(config::audio.virtual_sink).value_or(from_utf8(config::audio.virtual_sink)); auto virtual_device_found = false; for (auto x = 0; x < count; ++x) { @@ -674,7 +673,7 @@ namespace platf::audio { } if (virtual_device_found) { - auto name_suffix = converter.to_bytes(virtual_device_id); + auto name_suffix = to_utf8(virtual_device_id); sink.null = std::make_optional(sink_t::null_t { "virtual-"s.append(formats[format_t::stereo - 1].name) + name_suffix, "virtual-"s.append(formats[format_t::surr51 - 1].name) + name_suffix, @@ -749,7 +748,7 @@ namespace platf::audio { auto sink_info = get_sink_info(sink); // If the sink isn't a device name, we'll assume it's a device ID - auto wstring_device_id = find_device_id_by_name(sink).value_or(converter.from_bytes(sink_info.second.data())); + auto wstring_device_id = find_device_id_by_name(sink).value_or(from_utf8(sink_info.second.data())); if (sink_info.first == format_t::none) { // wstring_device_id does not contain virtual-(format name) @@ -839,7 +838,7 @@ namespace platf::audio { UINT count; collection->GetCount(&count); - auto wstring_name = converter.from_bytes(name.data()); + auto wstring_name = from_utf8(name.data()); for (auto x = 0; x < count; ++x) { audio::device_t device; diff --git a/src/platform/windows/display_base.cpp b/src/platform/windows/display_base.cpp index 78c927e7782..a5f8cc5d31a 100644 --- a/src/platform/windows/display_base.cpp +++ b/src/platform/windows/display_base.cpp @@ -3,7 +3,6 @@ * @brief todo */ #include -#include #include #include @@ -447,10 +446,8 @@ namespace platf::dxgi { return -1; } - std::wstring_convert, wchar_t> converter; - - auto adapter_name = converter.from_bytes(config::video.adapter_name); - auto output_name = converter.from_bytes(display_name); + auto adapter_name = from_utf8(config::video.adapter_name); + auto output_name = from_utf8(display_name); adapter_t::pointer adapter_p; for (int tries = 0; tries < 2; ++tries) { @@ -561,7 +558,7 @@ namespace platf::dxgi { DXGI_ADAPTER_DESC adapter_desc; adapter->GetDesc(&adapter_desc); - auto description = converter.to_bytes(adapter_desc.Description); + auto description = to_utf8(adapter_desc.Description); BOOST_LOG(info) << std::endl << "Device Description : " << description << std::endl @@ -1066,8 +1063,6 @@ namespace platf { BOOST_LOG(debug) << "Detecting monitors..."sv; - std::wstring_convert, wchar_t> converter; - // We must set the GPU preference before calling any DXGI APIs! if (!dxgi::probe_for_gpu_preference(config::video.output_name)) { BOOST_LOG(warning) << "Failed to set GPU preference. Capture may not work!"sv; @@ -1088,7 +1083,7 @@ namespace platf { BOOST_LOG(debug) << std::endl << "====== ADAPTER ====="sv << std::endl - << "Device Name : "sv << converter.to_bytes(adapter_desc.Description) << std::endl + << "Device Name : "sv << to_utf8(adapter_desc.Description) << std::endl << "Device Vendor ID : 0x"sv << util::hex(adapter_desc.VendorId).to_string_view() << std::endl << "Device Device ID : 0x"sv << util::hex(adapter_desc.DeviceId).to_string_view() << std::endl << "Device Video Mem : "sv << adapter_desc.DedicatedVideoMemory / 1048576 << " MiB"sv << std::endl @@ -1104,7 +1099,7 @@ namespace platf { DXGI_OUTPUT_DESC desc; output->GetDesc(&desc); - auto device_name = converter.to_bytes(desc.DeviceName); + auto device_name = to_utf8(desc.DeviceName); auto width = desc.DesktopCoordinates.right - desc.DesktopCoordinates.left; auto height = desc.DesktopCoordinates.bottom - desc.DesktopCoordinates.top; diff --git a/src/platform/windows/display_vram.cpp b/src/platform/windows/display_vram.cpp index 74b0bab7fbf..e9f8140d525 100644 --- a/src/platform/windows/display_vram.cpp +++ b/src/platform/windows/display_vram.cpp @@ -4,8 +4,6 @@ */ #include -#include - #include #include @@ -342,9 +340,8 @@ namespace platf::dxgi { #ifndef NDEBUG flags |= D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION; #endif - std::wstring_convert, wchar_t> converter; - auto wFile = converter.from_bytes(file); + auto wFile = from_utf8(file); auto status = D3DCompileFromFile(wFile.c_str(), nullptr, D3D_COMPILE_STANDARD_FILE_INCLUDE, entrypoint, shader_model, flags, 0, &compiled_p, &msg_p); if (msg_p) { diff --git a/src/platform/windows/misc.cpp b/src/platform/windows/misc.cpp index 708fd267484..2014d9435b8 100644 --- a/src/platform/windows/misc.cpp +++ b/src/platform/windows/misc.cpp @@ -2,7 +2,6 @@ * @file src/platform/windows/misc.cpp * @brief todo */ -#include #include #include #include @@ -35,6 +34,8 @@ #define NTDDI_VERSION NTDDI_WIN10 #include +#include "misc.h" + #include "src/entry_handler.h" #include "src/globals.h" #include "src/logging.h" @@ -66,8 +67,6 @@ using namespace std::literals; namespace platf { using adapteraddrs_t = util::c_ptr; - static std::wstring_convert, wchar_t> converter; - bool enabled_mouse_keys = false; MOUSEKEYS previous_mouse_keys_state; @@ -297,7 +296,7 @@ namespace platf { // Parse the environment block and populate env for (auto c = (PWCHAR) env_block; *c != UNICODE_NULL; c += wcslen(c) + 1) { // Environment variable entries end with a null-terminator, so std::wstring() will get an entire entry. - std::string env_tuple = converter.to_bytes(std::wstring { c }); + std::string env_tuple = to_utf8(std::wstring { c }); std::string env_name = env_tuple.substr(0, env_tuple.find('=')); std::string env_val = env_tuple.substr(env_tuple.find('=') + 1); @@ -371,7 +370,7 @@ namespace platf { for (const auto &entry : env) { auto name = entry.get_name(); auto value = entry.to_string(); - size += converter.from_bytes(name).length() + 1 /* L'=' */ + converter.from_bytes(value).length() + 1 /* L'\0' */; + size += from_utf8(name).length() + 1 /* L'=' */ + from_utf8(value).length() + 1 /* L'\0' */; } size += 1 /* L'\0' */; @@ -383,9 +382,9 @@ namespace platf { auto value = entry.to_string(); // Construct the NAME=VAL\0 string - append_string_to_environment_block(env_block, offset, converter.from_bytes(name)); + append_string_to_environment_block(env_block, offset, from_utf8(name)); env_block[offset++] = L'='; - append_string_to_environment_block(env_block, offset, converter.from_bytes(value)); + append_string_to_environment_block(env_block, offset, from_utf8(value)); env_block[offset++] = L'\0'; } @@ -625,14 +624,14 @@ namespace platf { */ std::wstring resolve_command_string(const std::string &raw_cmd, const std::wstring &working_dir, HANDLE token) { - std::wstring raw_cmd_w = converter.from_bytes(raw_cmd); + std::wstring raw_cmd_w = from_utf8(raw_cmd); // First, convert the given command into parts so we can get the executable/file/URL without parameters auto raw_cmd_parts = boost::program_options::split_winmain(raw_cmd_w); if (raw_cmd_parts.empty()) { // This is highly unexpected, but we'll just return the raw string and hope for the best. BOOST_LOG(warning) << "Failed to split command string: "sv << raw_cmd; - return converter.from_bytes(raw_cmd); + return from_utf8(raw_cmd); } auto raw_target = raw_cmd_parts.at(0); @@ -646,7 +645,7 @@ namespace platf { res = UrlGetPartW(raw_target.c_str(), scheme.data(), &out_len, URL_PART_SCHEME, 0); if (res != S_OK) { BOOST_LOG(warning) << "Failed to extract URL scheme from URL: "sv << raw_target << " ["sv << util::hex(res).to_string_view() << ']'; - return converter.from_bytes(raw_cmd); + return from_utf8(raw_cmd); } // If the target is a URL, the class is found using the URL scheme (prior to and not including the ':') @@ -658,7 +657,7 @@ namespace platf { if (extension == nullptr || *extension == 0) { // If the file has no extension, assume it's a command and allow CreateProcess() // to try to find it via PATH - return converter.from_bytes(raw_cmd); + return from_utf8(raw_cmd); } // For regular files, the class is found using the file extension (including the dot) @@ -674,7 +673,7 @@ namespace platf { // Override HKEY_CLASSES_ROOT and HKEY_CURRENT_USER to ensure we query the correct class info if (!override_per_user_predefined_keys(token)) { - return converter.from_bytes(raw_cmd); + return from_utf8(raw_cmd); } // Find the command string for the specified class @@ -699,7 +698,7 @@ namespace platf { if (res != S_OK) { BOOST_LOG(warning) << "Failed to query command string for raw command: "sv << raw_cmd << " ["sv << util::hex(res).to_string_view() << ']'; - return converter.from_bytes(raw_cmd); + return from_utf8(raw_cmd); } // Finally, construct the real command string that will be passed into CreateProcess(). @@ -825,7 +824,7 @@ namespace platf { */ bp::child run_command(bool elevated, bool interactive, const std::string &cmd, boost::filesystem::path &working_dir, const bp::environment &env, FILE *file, std::error_code &ec, bp::group *group) { - std::wstring start_dir = converter.from_bytes(working_dir.string()); + std::wstring start_dir = from_utf8(working_dir.string()); HANDLE job = group ? group->native_handle() : nullptr; STARTUPINFOEXW startup_info = create_startup_info(file, job ? &job : nullptr, ec); PROCESS_INFORMATION process_info; @@ -1602,4 +1601,70 @@ namespace platf { } return {}; } + + /** + * @brief Converts a UTF-8 string into a UTF-16 wide string. + * @param string The UTF-8 string. + * @return The converted UTF-16 wide string. + */ + std::wstring + from_utf8(const std::string &string) { + // No conversion needed if the string is empty + if (string.empty()) { + return {}; + } + + // Get the output size required to store the string + auto output_size = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, string.data(), string.size(), nullptr, 0); + if (output_size == 0) { + auto winerr = GetLastError(); + BOOST_LOG(error) << "Failed to get UTF-16 buffer size: "sv << winerr; + return {}; + } + + // Perform the conversion + std::wstring output(output_size, L'\0'); + output_size = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, string.data(), string.size(), output.data(), output.size()); + if (output_size == 0) { + auto winerr = GetLastError(); + BOOST_LOG(error) << "Failed to convert string to UTF-16: "sv << winerr; + return {}; + } + + return output; + } + + /** + * @brief Converts a UTF-16 wide string into a UTF-8 string. + * @param string The UTF-16 wide string. + * @return The converted UTF-8 string. + */ + std::string + to_utf8(const std::wstring &string) { + // No conversion needed if the string is empty + if (string.empty()) { + return {}; + } + + // Get the output size required to store the string + auto output_size = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, string.data(), string.size(), + nullptr, 0, nullptr, nullptr); + if (output_size == 0) { + auto winerr = GetLastError(); + BOOST_LOG(error) << "Failed to get UTF-8 buffer size: "sv << winerr; + return {}; + } + + // Perform the conversion + std::string output(output_size, '\0'); + output_size = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, string.data(), string.size(), + output.data(), output.size(), nullptr, nullptr); + if (output_size == 0) { + auto winerr = GetLastError(); + BOOST_LOG(error) << "Failed to convert string to UTF-8: "sv << winerr; + return {}; + } + + return output; + } } // namespace platf diff --git a/src/platform/windows/misc.h b/src/platform/windows/misc.h index 9228ce59fe7..5a3e29b0257 100644 --- a/src/platform/windows/misc.h +++ b/src/platform/windows/misc.h @@ -20,4 +20,20 @@ namespace platf { std::chrono::nanoseconds qpc_time_difference(int64_t performance_counter1, int64_t performance_counter2); + + /** + * @brief Converts a UTF-8 string into a UTF-16 wide string. + * @param string The UTF-8 string. + * @return The converted UTF-16 wide string. + */ + std::wstring + from_utf8(const std::string &string); + + /** + * @brief Converts a UTF-16 wide string into a UTF-8 string. + * @param string The UTF-16 wide string. + * @return The converted UTF-8 string. + */ + std::string + to_utf8(const std::wstring &string); } // namespace platf diff --git a/src/platform/windows/publish.cpp b/src/platform/windows/publish.cpp index 6eb4d8948e1..131bb5ac11f 100644 --- a/src/platform/windows/publish.cpp +++ b/src/platform/windows/publish.cpp @@ -107,12 +107,10 @@ namespace platf::publish { service(bool enable, PDNS_SERVICE_INSTANCE &existing_instance) { auto alarm = safe::make_alarm(); - std::wstring_convert, wchar_t> converter; - std::wstring name { SERVICE_INSTANCE_NAME.data(), SERVICE_INSTANCE_NAME.size() }; std::wstring domain { SERVICE_TYPE_DOMAIN.data(), SERVICE_TYPE_DOMAIN.size() }; - auto host = converter.from_bytes(boost::asio::ip::host_name() + ".local"); + auto host = from_utf8(boost::asio::ip::host_name() + ".local"); DNS_SERVICE_INSTANCE instance {}; instance.pszInstanceName = name.data(); diff --git a/src/process.cpp b/src/process.cpp index e660e8162a1..804291577c2 100644 --- a/src/process.cpp +++ b/src/process.cpp @@ -29,6 +29,9 @@ #include "utility.h" #ifdef _WIN32 + // from_utf8() string conversion function + #include "platform/windows/misc.h" + // _SH constants for _wfsopen() #include #endif @@ -187,8 +190,7 @@ namespace proc { #ifdef _WIN32 // fopen() interprets the filename as an ANSI string on Windows, so we must convert it // to UTF-16 and use the wchar_t variants for proper Unicode log file path support. - std::wstring_convert, wchar_t> converter; - auto woutput = converter.from_bytes(_app.output); + auto woutput = platf::from_utf8(_app.output); // Use _SH_DENYNO to allow us to open this log file again for writing even if it is // still open from a previous execution. This is required to handle the case of a From 6ddc4b7ba31af4fc34e5c766ccee4edb81194f1b Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Sat, 10 Feb 2024 15:33:56 -0600 Subject: [PATCH 057/182] Properly re-escape arguments when processing %* --- src/platform/windows/misc.cpp | 73 ++++++++++++++++++++++++++++++++++- 1 file changed, 72 insertions(+), 1 deletion(-) diff --git a/src/platform/windows/misc.cpp b/src/platform/windows/misc.cpp index 2014d9435b8..d3e98950abb 100644 --- a/src/platform/windows/misc.cpp +++ b/src/platform/windows/misc.cpp @@ -614,6 +614,67 @@ namespace platf { return true; } + /** + * @brief This function quotes/escapes an argument according to the Windows parsing convention. + * @param argument The raw argument to process. + * @return An argument string suitable for use by CreateProcess(). + */ + std::wstring + escape_argument(const std::wstring &argument) { + // If there are no characters requiring quoting/escaping, we're done + if (argument.find_first_of(L" \t\n\v\"") == argument.npos) { + return argument; + } + + // The algorithm implemented here comes from a MSDN blog post: + // https://web.archive.org/web/20120201194949/http://blogs.msdn.com/b/twistylittlepassagesallalike/archive/2011/04/23/everyone-quotes-arguments-the-wrong-way.aspx + std::wstring escaped_arg; + escaped_arg.push_back(L'"'); + for (auto it = argument.begin();; it++) { + auto backslash_count = 0U; + while (it != argument.end() && *it == L'\\') { + it++; + backslash_count++; + } + + if (it == argument.end()) { + escaped_arg.append(backslash_count * 2, L'\\'); + break; + } + else if (*it == L'"') { + escaped_arg.append(backslash_count * 2 + 1, L'\\'); + } + else { + escaped_arg.append(backslash_count, L'\\'); + } + + escaped_arg.push_back(*it); + } + escaped_arg.push_back(L'"'); + return escaped_arg; + } + + /** + * @brief This function escapes an argument according to cmd's parsing convention. + * @param argument An argument already escaped by `escape_argument()`. + * @return An argument string suitable for use by cmd.exe. + */ + std::wstring + escape_argument_for_cmd(const std::wstring &argument) { + // Start with the original string and modify from there + std::wstring escaped_arg = argument; + + // Look for the next cmd metacharacter + size_t match_pos = 0; + while ((match_pos = escaped_arg.find_first_of(L"()%!^\"<>&|", match_pos)) != std::wstring::npos) { + // Insert an escape character and skip past the match + escaped_arg.insert(match_pos, 1, L'^'); + match_pos += 2; + } + + return escaped_arg; + } + /** * @brief This function resolves the given raw command into a proper command string for CreateProcess(). * @details This converts URLs and non-executable file paths into a runnable command like ShellExecute(). @@ -665,6 +726,7 @@ namespace platf { } std::array shell_command_string; + bool needs_cmd_escaping = false; { // Overriding these predefined keys affects process-wide state, so serialize all calls // to ensure the handle state is consistent while we perform the command query. @@ -689,6 +751,7 @@ namespace platf { if (res == HRESULT_FROM_WIN32(ERROR_NO_ASSOCIATION)) { BOOST_LOG(warning) << "Using trampoline to handle target: "sv << raw_cmd; std::wcscpy(shell_command_string.data(), L"cmd.exe /c start \"\" \"%1\" %*"); + needs_cmd_escaping = true; res = S_OK; } @@ -752,10 +815,18 @@ namespace platf { // All arguments following the target case L'*': for (int i = 1; i < raw_cmd_parts.size(); i++) { + // Insert a space before arguments after the first one if (i > 1) { match_replacement += L' '; } - match_replacement += raw_cmd_parts.at(i); + + // Argument escaping applies only to %*, not the single substitutions like %2 + auto escaped_argument = escape_argument(raw_cmd_parts.at(i)); + if (needs_cmd_escaping) { + // If we're using the cmd.exe trampoline, we'll need to add additional escaping + escaped_argument = escape_argument_for_cmd(escaped_argument); + } + match_replacement += escaped_argument; } break; From 56da68c8632c3395c49fdecd5731ccd4421e760b Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Wed, 14 Feb 2024 21:39:14 -0600 Subject: [PATCH 058/182] Preserve backwards-compatible argument escaping behavior for executables --- src/platform/windows/misc.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/platform/windows/misc.cpp b/src/platform/windows/misc.cpp index d3e98950abb..b53bfea5a6e 100644 --- a/src/platform/windows/misc.cpp +++ b/src/platform/windows/misc.cpp @@ -720,6 +720,13 @@ namespace platf { // to try to find it via PATH return from_utf8(raw_cmd); } + else if (boost::iequals(extension, L".exe")) { + // If the file has an .exe extension, we will bypass the resolution here and + // directly pass the unmodified command string to CreateProcess(). The argument + // escaping rules are subtly different between CreateProcess() and ShellExecute(), + // and we want to preserve backwards compatibility with older configs. + return from_utf8(raw_cmd); + } // For regular files, the class is found using the file extension (including the dot) lookup_string = extension; From d1a635809ac8cf7b16913479648d067159119e97 Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Wed, 14 Feb 2024 23:28:32 -0600 Subject: [PATCH 059/182] Implement backwards compatibility for NVENC APIs back to Video Codec SDK v11.0 This allows NVENC on drivers 456.71 (October 2020) and later. --- src/nvenc/nvenc_base.cpp | 63 +++++++++++++++++++++++++++++++-------- src/nvenc/nvenc_base.h | 12 ++++++++ src/nvenc/nvenc_d3d11.cpp | 4 +-- 3 files changed, 65 insertions(+), 14 deletions(-) diff --git a/src/nvenc/nvenc_base.cpp b/src/nvenc/nvenc_base.cpp index 63107324f37..b9eba5a04df 100644 --- a/src/nvenc/nvenc_base.cpp +++ b/src/nvenc/nvenc_base.cpp @@ -4,6 +4,17 @@ #include "src/logging.h" #include "src/utility.h" +#define MAKE_NVENC_VER(major, minor) ((major) | ((minor) << 24)) + +// Make sure we check backwards compatibility when bumping the Video Codec SDK version +// Things to look out for: +// - NV_ENC_*_VER definitions where the value inside NVENCAPI_STRUCT_VERSION() was increased +// - Incompatible struct changes in nvEncodeAPI.h (fields removed, semantics changed, etc.) +// - Test both old and new drivers with all supported codecs +#if NVENCAPI_VERSION != MAKE_NVENC_VER(12U, 0U) + #error Check and update NVENC code for backwards compatibility! +#endif + namespace { GUID @@ -81,6 +92,10 @@ namespace nvenc { bool nvenc_base::create_encoder(const nvenc_config &config, const video::config_t &client_config, const nvenc_colorspace_t &colorspace, NV_ENC_BUFFER_FORMAT buffer_format) { + // Pick the minimum NvEncode API version required to support the specified codec + // to maximize driver compatibility. AV1 was introduced in SDK v12.0. + minimum_api_version = (client_config.videoFormat <= 1) ? MAKE_NVENC_VER(11U, 0U) : MAKE_NVENC_VER(12U, 0U); + if (!nvenc && !init_library()) return false; if (encoder) destroy_encoder(); @@ -91,12 +106,12 @@ namespace nvenc { encoder_params.buffer_format = buffer_format; encoder_params.rfi = true; - NV_ENC_OPEN_ENCODE_SESSION_EX_PARAMS session_params = { NV_ENC_OPEN_ENCODE_SESSION_EX_PARAMS_VER }; + NV_ENC_OPEN_ENCODE_SESSION_EX_PARAMS session_params = { min_struct_version(NV_ENC_OPEN_ENCODE_SESSION_EX_PARAMS_VER) }; session_params.device = device; session_params.deviceType = device_type; - session_params.apiVersion = NVENCAPI_VERSION; + session_params.apiVersion = minimum_api_version; if (nvenc_failed(nvenc->nvEncOpenEncodeSessionEx(&session_params, &encoder))) { - BOOST_LOG(error) << "NvEncOpenEncodeSessionEx failed"; + BOOST_LOG(error) << "NvEncOpenEncodeSessionEx failed: " << last_error_string; return false; } @@ -112,7 +127,7 @@ namespace nvenc { return false; } - NV_ENC_INITIALIZE_PARAMS init_params = { NV_ENC_INITIALIZE_PARAMS_VER }; + NV_ENC_INITIALIZE_PARAMS init_params = { min_struct_version(NV_ENC_INITIALIZE_PARAMS_VER) }; switch (client_config.videoFormat) { case 0: @@ -146,7 +161,7 @@ namespace nvenc { } auto get_encoder_cap = [&](NV_ENC_CAPS cap) { - NV_ENC_CAPS_PARAM param = { NV_ENC_CAPS_PARAM_VER, cap }; + NV_ENC_CAPS_PARAM param = { min_struct_version(NV_ENC_CAPS_PARAM_VER), cap }; int value = 0; nvenc->nvEncGetEncodeCaps(encoder, init_params.encodeGUID, ¶m, &value); return value; @@ -199,7 +214,7 @@ namespace nvenc { init_params.frameRateNum = client_config.framerate; init_params.frameRateDen = 1; - NV_ENC_PRESET_CONFIG preset_config = { NV_ENC_PRESET_CONFIG_VER, { NV_ENC_CONFIG_VER } }; + NV_ENC_PRESET_CONFIG preset_config = { min_struct_version(NV_ENC_PRESET_CONFIG_VER), { min_struct_version(NV_ENC_CONFIG_VER, 7, 8) } }; if (nvenc_failed(nvenc->nvEncGetEncodePresetConfigEx(encoder, init_params.encodeGUID, init_params.presetGUID, init_params.tuningInfo, &preset_config))) { BOOST_LOG(error) << "NvEncGetEncodePresetConfigEx failed: " << last_error_string; return false; @@ -344,7 +359,7 @@ namespace nvenc { } if (async_event_handle) { - NV_ENC_EVENT_PARAMS event_params = { NV_ENC_EVENT_PARAMS_VER }; + NV_ENC_EVENT_PARAMS event_params = { min_struct_version(NV_ENC_EVENT_PARAMS_VER) }; event_params.completionEvent = async_event_handle; if (nvenc_failed(nvenc->nvEncRegisterAsyncEvent(encoder, &event_params))) { BOOST_LOG(error) << "NvEncRegisterAsyncEvent failed: " << last_error_string; @@ -352,7 +367,7 @@ namespace nvenc { } } - NV_ENC_CREATE_BITSTREAM_BUFFER create_bitstream_buffer = { NV_ENC_CREATE_BITSTREAM_BUFFER_VER }; + NV_ENC_CREATE_BITSTREAM_BUFFER create_bitstream_buffer = { min_struct_version(NV_ENC_CREATE_BITSTREAM_BUFFER_VER) }; if (nvenc_failed(nvenc->nvEncCreateBitstreamBuffer(encoder, &create_bitstream_buffer))) { BOOST_LOG(error) << "NvEncCreateBitstreamBuffer failed: " << last_error_string; return false; @@ -394,7 +409,7 @@ namespace nvenc { output_bitstream = nullptr; } if (encoder && async_event_handle) { - NV_ENC_EVENT_PARAMS event_params = { NV_ENC_EVENT_PARAMS_VER }; + NV_ENC_EVENT_PARAMS event_params = { min_struct_version(NV_ENC_EVENT_PARAMS_VER) }; event_params.completionEvent = async_event_handle; nvenc->nvEncUnregisterAsyncEvent(encoder, &event_params); } @@ -420,7 +435,7 @@ namespace nvenc { assert(registered_input_buffer); assert(output_bitstream); - NV_ENC_MAP_INPUT_RESOURCE mapped_input_buffer = { NV_ENC_MAP_INPUT_RESOURCE_VER }; + NV_ENC_MAP_INPUT_RESOURCE mapped_input_buffer = { min_struct_version(NV_ENC_MAP_INPUT_RESOURCE_VER) }; mapped_input_buffer.registeredResource = registered_input_buffer; if (nvenc_failed(nvenc->nvEncMapInputResource(encoder, &mapped_input_buffer))) { @@ -429,7 +444,7 @@ namespace nvenc { } auto unmap_guard = util::fail_guard([&] { nvenc->nvEncUnmapInputResource(encoder, &mapped_input_buffer); }); - NV_ENC_PIC_PARAMS pic_params = { NV_ENC_PIC_PARAMS_VER }; + NV_ENC_PIC_PARAMS pic_params = { min_struct_version(NV_ENC_PIC_PARAMS_VER, 4, 6) }; pic_params.inputWidth = encoder_params.width; pic_params.inputHeight = encoder_params.height; pic_params.encodePicFlags = force_idr ? NV_ENC_PIC_FLAG_FORCEIDR : 0; @@ -445,7 +460,7 @@ namespace nvenc { return {}; } - NV_ENC_LOCK_BITSTREAM lock_bitstream = { NV_ENC_LOCK_BITSTREAM_VER }; + NV_ENC_LOCK_BITSTREAM lock_bitstream = { min_struct_version(NV_ENC_LOCK_BITSTREAM_VER, 1, 2) }; lock_bitstream.outputBitstream = output_bitstream; lock_bitstream.doNotWait = 0; @@ -585,4 +600,28 @@ namespace nvenc { return false; } + /** + * @brief This function returns the corresponding struct version for the minimum API required by the codec. + * @details Reducing the struct versions maximizes driver compatibility by avoiding needless API breaks. + * @param version The raw structure version from `NVENCAPI_STRUCT_VERSION()`. + * @param v11_struct_version Optionally specifies the struct version to use with v11 SDK major versions. + * @param v12_struct_version Optionally specifies the struct version to use with v12 SDK major versions. + * @return A suitable struct version for the active codec. + */ + uint32_t + nvenc_base::min_struct_version(uint32_t version, uint32_t v11_struct_version, uint32_t v12_struct_version) { + assert(minimum_api_version); + + // Mask off and replace the original NVENCAPI_VERSION + version &= ~NVENCAPI_VERSION; + version |= minimum_api_version; + + // If there's a struct version override, apply that too + if (v11_struct_version || v12_struct_version) { + version &= ~(0xFFu << 16); + version |= (((minimum_api_version & 0xFF) >= 12) ? v12_struct_version : v11_struct_version) << 16; + } + + return version; + } } // namespace nvenc diff --git a/src/nvenc/nvenc_base.h b/src/nvenc/nvenc_base.h index aa02fbef620..2d012ef8da8 100644 --- a/src/nvenc/nvenc_base.h +++ b/src/nvenc/nvenc_base.h @@ -45,6 +45,17 @@ namespace nvenc { bool nvenc_failed(NVENCSTATUS status); + /** + * @brief This function returns the corresponding struct version for the minimum API required by the codec. + * @details Reducing the struct versions maximizes driver compatibility by avoiding needless API breaks. + * @param version The raw structure version from `NVENCAPI_STRUCT_VERSION()`. + * @param v11_struct_version Optionally specifies the struct version to use with v11 SDK major versions. + * @param v12_struct_version Optionally specifies the struct version to use with v12 SDK major versions. + * @return A suitable struct version for the active codec. + */ + uint32_t + min_struct_version(uint32_t version, uint32_t v11_struct_version = 0, uint32_t v12_struct_version = 0); + const NV_ENC_DEVICE_TYPE device_type; void *const device; @@ -68,6 +79,7 @@ namespace nvenc { private: NV_ENC_OUTPUT_PTR output_bitstream = nullptr; + uint32_t minimum_api_version = 0; struct { uint64_t last_encoded_frame_index = 0; diff --git a/src/nvenc/nvenc_d3d11.cpp b/src/nvenc/nvenc_d3d11.cpp index 7db6fdd2fa4..cb33a1801af 100644 --- a/src/nvenc/nvenc_d3d11.cpp +++ b/src/nvenc/nvenc_d3d11.cpp @@ -39,7 +39,7 @@ namespace nvenc { if ((dll = LoadLibraryEx(dll_name, NULL, LOAD_LIBRARY_SEARCH_SYSTEM32))) { if (auto create_instance = (decltype(NvEncodeAPICreateInstance) *) GetProcAddress(dll, "NvEncodeAPICreateInstance")) { auto new_nvenc = std::make_unique(); - new_nvenc->version = NV_ENCODE_API_FUNCTION_LIST_VER; + new_nvenc->version = min_struct_version(NV_ENCODE_API_FUNCTION_LIST_VER); if (nvenc_failed(create_instance(new_nvenc.get()))) { BOOST_LOG(error) << "NvEncodeAPICreateInstance failed: " << last_error_string; } @@ -83,7 +83,7 @@ namespace nvenc { } if (!registered_input_buffer) { - NV_ENC_REGISTER_RESOURCE register_resource = { NV_ENC_REGISTER_RESOURCE_VER }; + NV_ENC_REGISTER_RESOURCE register_resource = { min_struct_version(NV_ENC_REGISTER_RESOURCE_VER, 3, 4) }; register_resource.resourceType = NV_ENC_INPUT_RESOURCE_TYPE_DIRECTX; register_resource.width = encoder_params.width; register_resource.height = encoder_params.height; From 8074bf8c8d0914bed80682f18be01826b18f794c Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Fri, 23 Feb 2024 20:00:53 -0500 Subject: [PATCH 060/182] fix(main): fix version printing (#2167) --- src/main.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index cbe481ee2e9..b8983e3d221 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -97,9 +97,6 @@ SessionMonitorWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { */ int main(int argc, char *argv[]) { - // the version should be printed to the log before anything else - BOOST_LOG(info) << PROJECT_NAME << " version: " << PROJECT_VER; - lifetime::argv = argv; task_pool_util::TaskPool::task_id_t force_shutdown = nullptr; @@ -197,6 +194,11 @@ main(int argc, char *argv[]) { bl::core::get()->add_sink(sink); auto fg = util::fail_guard(log_flush); + // logging can begin at this point + // if anything is logged prior to this point, it will appear in stdout, but not in the log viewer in the UI + // the version should be printed to the log before anything else + BOOST_LOG(info) << PROJECT_NAME << " version: " << PROJECT_VER; + if (!config::sunshine.cmd.name.empty()) { auto fn = cmd_to_func.find(config::sunshine.cmd.name); if (fn == std::end(cmd_to_func)) { From 341fdaad77d65f0c4ca18db5c489520032f4effd Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Fri, 23 Feb 2024 20:54:10 -0500 Subject: [PATCH 061/182] build(cmake): add option to skip cuda inheriting compile options (#2164) --- cmake/prep/options.cmake | 4 ++++ cmake/targets/common.cmake | 9 ++++++--- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/cmake/prep/options.cmake b/cmake/prep/options.cmake index 7a8d728ba47..90f748eb8a8 100644 --- a/cmake/prep/options.cmake +++ b/cmake/prep/options.cmake @@ -6,6 +6,10 @@ option(SUNSHINE_REQUIRE_TRAY "Require system tray icon. Fail the build if tray r option(SUNSHINE_SYSTEM_WAYLAND_PROTOCOLS "Use system installation of wayland-protocols rather than the submodule." OFF) +option(CUDA_INHERIT_COMPILE_OPTIONS + "When building CUDA code, inherit compile options from the the main project. You may want to disable this if + your IDE throws errors about unknown flags after running cmake." ON) + if(APPLE) option(SUNSHINE_CONFIGURE_PORTFILE "Configure macOS Portfile. Recommended to use with SUNSHINE_CONFIGURE_ONLY" OFF) diff --git a/cmake/targets/common.cmake b/cmake/targets/common.cmake index cb5fe4e67d4..3dd629e0cab 100644 --- a/cmake/targets/common.cmake +++ b/cmake/targets/common.cmake @@ -28,9 +28,12 @@ set_target_properties(sunshine PROPERTIES CXX_STANDARD 17 VERSION ${PROJECT_VERSION} SOVERSION ${PROJECT_VERSION_MAJOR}) -foreach(flag IN LISTS SUNSHINE_COMPILE_OPTIONS) - list(APPEND SUNSHINE_COMPILE_OPTIONS_CUDA "$<$:--compiler-options=${flag}>") -endforeach() +# CLion complains about unknown flags after running cmake, and cannot add symbols to the index for cuda files +if(CUDA_INHERIT_COMPILE_OPTIONS) + foreach(flag IN LISTS SUNSHINE_COMPILE_OPTIONS) + list(APPEND SUNSHINE_COMPILE_OPTIONS_CUDA "$<$:--compiler-options=${flag}>") + endforeach() +endif() target_compile_options(sunshine PRIVATE $<$:${SUNSHINE_COMPILE_OPTIONS}>;$<$:${SUNSHINE_COMPILE_OPTIONS_CUDA};-std=c++17>) # cmake-lint: disable=C0301 From dde804f14b1e5acf8e07182ff9e3833521d33b83 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 23 Feb 2024 21:48:41 -0500 Subject: [PATCH 062/182] build(deps): bump third-party/ViGEmClient from `1920260` to `8d71f67` (#2168) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- third-party/ViGEmClient | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/third-party/ViGEmClient b/third-party/ViGEmClient index 19202603d40..8d71f6740ff 160000 --- a/third-party/ViGEmClient +++ b/third-party/ViGEmClient @@ -1 +1 @@ -Subproject commit 19202603d40202e34e06bf3a1a42c1eec0b1ec02 +Subproject commit 8d71f6740ffff4671cdadbca255ce528e3cd3fef From c6f94e93e0a8bb02c2f617843ce1ce55ce7352a9 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sat, 24 Feb 2024 22:34:53 -0500 Subject: [PATCH 063/182] build(cmake): error build on warning (#2165) --- .github/workflows/CI.yml | 12 ++++++++--- cmake/compile_definitions/common.cmake | 20 ++++++++++++++++++- cmake/compile_definitions/windows.cmake | 11 +++++++++- cmake/prep/options.cmake | 2 ++ docker/debian-bookworm.dockerfile | 1 + docker/debian-bullseye.dockerfile | 1 + docker/fedora-38.dockerfile | 1 + docker/fedora-39.dockerfile | 1 + docker/ubuntu-20.04.dockerfile | 1 + docker/ubuntu-22.04.dockerfile | 1 + packaging/linux/Arch/PKGBUILD | 1 + .../linux/flatpak/dev.lizardbyte.sunshine.yml | 1 + packaging/macos/Portfile | 3 ++- 13 files changed, 50 insertions(+), 6 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 4b53e3204e9..f1005458c93 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -400,7 +400,9 @@ jobs: mkdir -p artifacts cd build - cmake -DCMAKE_BUILD_TYPE=Release \ + cmake \ + -DBUILD_WERROR=ON \ + -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_INSTALL_PREFIX=/usr \ -DSUNSHINE_ASSETS_DIR=share/sunshine \ -DSUNSHINE_EXECUTABLE_PATH=/usr/bin/sunshine \ @@ -576,7 +578,9 @@ jobs: run: | mkdir build cd build - cmake -DCMAKE_BUILD_TYPE=Release \ + cmake \ + -DBUILD_WERROR=ON \ + -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_INSTALL_PREFIX=/usr \ -DSUNSHINE_ASSETS_DIR=local/sunshine/assets \ -DSUNSHINE_EXECUTABLE_PATH=/usr/bin/sunshine \ @@ -784,7 +788,9 @@ jobs: run: | mkdir build cd build - cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo \ + cmake \ + -DBUILD_WERROR=ON \ + -DCMAKE_BUILD_TYPE=RelWithDebInfo \ -DSUNSHINE_ASSETS_DIR=assets \ -G "MinGW Makefiles" \ .. diff --git a/cmake/compile_definitions/common.cmake b/cmake/compile_definitions/common.cmake index 94f1ac598cc..ca6ddcbb0f3 100644 --- a/cmake/compile_definitions/common.cmake +++ b/cmake/compile_definitions/common.cmake @@ -3,7 +3,25 @@ list(APPEND SUNSHINE_COMPILE_OPTIONS -Wall -Wno-sign-compare) # Wall - enable all warnings +# Werror - treat warnings as errors +# Wno-maybe-uninitialized/Wno-uninitialized - disable warnings for maybe uninitialized variables # Wno-sign-compare - disable warnings for signed/unsigned comparisons +if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + # GCC specific compile options + + # GCC 12 and higher will complain about maybe-uninitialized + if(CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 12) + list(APPEND SUNSHINE_COMPILE_OPTIONS -Wno-maybe-uninitialized) + endif() +elseif(CMAKE_CXX_COMPILER_ID STREQUAL "Clang") + # Clang specific compile options + + # Clang doesn't actually complain about this this, so disabling for now + # list(APPEND SUNSHINE_COMPILE_OPTIONS -Wno-uninitialized) +endif() +if(BUILD_WERROR) + list(APPEND SUNSHINE_COMPILE_OPTIONS -Werror) +endif() # setup assets directory if(NOT SUNSHINE_ASSETS_DIR) @@ -28,7 +46,7 @@ file(GLOB NVENC_SOURCES CONFIGURE_DEPENDS "src/nvenc/*.cpp" "src/nvenc/*.h") list(APPEND PLATFORM_TARGET_FILES ${NVENC_SOURCES}) configure_file("${CMAKE_SOURCE_DIR}/src/version.h.in" version.h @ONLY) -include_directories("${CMAKE_CURRENT_BINARY_DIR}") +include_directories("${CMAKE_CURRENT_BINARY_DIR}") # required for importing version.h set(SUNSHINE_TARGET_FILES "${CMAKE_SOURCE_DIR}/third-party/nanors/rs.c" diff --git a/cmake/compile_definitions/windows.cmake b/cmake/compile_definitions/windows.cmake index e3729927fc1..2095cc5baa4 100644 --- a/cmake/compile_definitions/windows.cmake +++ b/cmake/compile_definitions/windows.cmake @@ -6,6 +6,9 @@ enable_language(RC) set(CMAKE_RC_COMPILER windres) set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -static") +# gcc complains about misleading indentation in some mingw includes +list(APPEND SUNSHINE_COMPILE_OPTIONS -Wno-misleading-indentation) + # curl add_definitions(-DCURL_STATICLIB) include_directories(SYSTEM ${CURL_STATIC_INCLUDE_DIRS}) @@ -28,8 +31,14 @@ file(GLOB NVPREFS_FILES CONFIGURE_DEPENDS include_directories(SYSTEM "${CMAKE_SOURCE_DIR}/third-party/ViGEmClient/include") set_source_files_properties("${CMAKE_SOURCE_DIR}/third-party/ViGEmClient/src/ViGEmClient.cpp" PROPERTIES COMPILE_DEFINITIONS "UNICODE=1;ERROR_INVALID_DEVICE_OBJECT_PARAMETER=650") +set(VIGEM_COMPILE_FLAGS "") +string(APPEND VIGEM_COMPILE_FLAGS "-Wno-unknown-pragmas ") +string(APPEND VIGEM_COMPILE_FLAGS "-Wno-misleading-indentation ") +string(APPEND VIGEM_COMPILE_FLAGS "-Wno-class-memaccess ") +string(APPEND VIGEM_COMPILE_FLAGS "-Wno-unused-function ") +string(APPEND VIGEM_COMPILE_FLAGS "-Wno-unused-variable ") set_source_files_properties("${CMAKE_SOURCE_DIR}/third-party/ViGEmClient/src/ViGEmClient.cpp" - PROPERTIES COMPILE_FLAGS "-Wno-unknown-pragmas -Wno-misleading-indentation -Wno-class-memaccess") + PROPERTIES COMPILE_FLAGS ${VIGEM_COMPILE_FLAGS}) # sunshine icon if(NOT DEFINED SUNSHINE_ICON_PATH) diff --git a/cmake/prep/options.cmake b/cmake/prep/options.cmake index 90f748eb8a8..9104320d31d 100644 --- a/cmake/prep/options.cmake +++ b/cmake/prep/options.cmake @@ -1,3 +1,5 @@ +option(BUILD_WERROR "Enable -Werror flag." OFF) + # if this option is set, the build will exit after configuring special package configuration files option(SUNSHINE_CONFIGURE_ONLY "Configure special files only, then exit." OFF) diff --git a/docker/debian-bookworm.dockerfile b/docker/debian-bookworm.dockerfile index a62e092eab4..d664ff8c8ee 100644 --- a/docker/debian-bookworm.dockerfile +++ b/docker/debian-bookworm.dockerfile @@ -105,6 +105,7 @@ RUN <<_MAKE #!/bin/bash set -e cmake \ + -DBUILD_WERROR=ON \ -DCMAKE_CUDA_COMPILER:PATH=/build/cuda/bin/nvcc \ -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_INSTALL_PREFIX=/usr \ diff --git a/docker/debian-bullseye.dockerfile b/docker/debian-bullseye.dockerfile index f355307631d..5f607c2481d 100644 --- a/docker/debian-bullseye.dockerfile +++ b/docker/debian-bullseye.dockerfile @@ -119,6 +119,7 @@ set -e source "$HOME/.nvm/nvm.sh" nvm use 20.9.0 cmake \ + -DBUILD_WERROR=ON \ -DCMAKE_CUDA_COMPILER:PATH=/build/cuda/bin/nvcc \ -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_INSTALL_PREFIX=/usr \ diff --git a/docker/fedora-38.dockerfile b/docker/fedora-38.dockerfile index 5408782d074..9ba496c5b97 100644 --- a/docker/fedora-38.dockerfile +++ b/docker/fedora-38.dockerfile @@ -105,6 +105,7 @@ RUN <<_MAKE #!/bin/bash set -e cmake \ + -DBUILD_WERROR=ON \ -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_INSTALL_PREFIX=/usr \ -DSUNSHINE_ASSETS_DIR=share/sunshine \ diff --git a/docker/fedora-39.dockerfile b/docker/fedora-39.dockerfile index cc781a6730f..e942a8aa916 100644 --- a/docker/fedora-39.dockerfile +++ b/docker/fedora-39.dockerfile @@ -105,6 +105,7 @@ RUN <<_MAKE #!/bin/bash set -e cmake \ + -DBUILD_WERROR=ON \ -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_INSTALL_PREFIX=/usr \ -DSUNSHINE_ASSETS_DIR=share/sunshine \ diff --git a/docker/ubuntu-20.04.dockerfile b/docker/ubuntu-20.04.dockerfile index d677830db5c..4a1dcf4867c 100644 --- a/docker/ubuntu-20.04.dockerfile +++ b/docker/ubuntu-20.04.dockerfile @@ -155,6 +155,7 @@ set -e source "$HOME/.nvm/nvm.sh" nvm use 20.9.0 cmake \ + -DBUILD_WERROR=ON \ -DCMAKE_CUDA_COMPILER:PATH=/build/cuda/bin/nvcc \ -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_INSTALL_PREFIX=/usr \ diff --git a/docker/ubuntu-22.04.dockerfile b/docker/ubuntu-22.04.dockerfile index ab6ec096a3b..fa2d5e19d85 100644 --- a/docker/ubuntu-22.04.dockerfile +++ b/docker/ubuntu-22.04.dockerfile @@ -120,6 +120,7 @@ source "$HOME/.nvm/nvm.sh" nvm use 20.9.0 #Actually build cmake \ + -DBUILD_WERROR=ON \ -DCMAKE_CUDA_COMPILER:PATH=/build/cuda/bin/nvcc \ -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_INSTALL_PREFIX=/usr \ diff --git a/packaging/linux/Arch/PKGBUILD b/packaging/linux/Arch/PKGBUILD index a0ceb6b5684..4422698263e 100644 --- a/packaging/linux/Arch/PKGBUILD +++ b/packaging/linux/Arch/PKGBUILD @@ -61,6 +61,7 @@ build() { -S "$pkgname" \ -B build \ -Wno-dev \ + -D BUILD_WERROR=ON \ -D CMAKE_INSTALL_PREFIX=/usr \ -D SUNSHINE_EXECUTABLE_PATH=/usr/bin/sunshine \ -D SUNSHINE_ASSETS_DIR="share/sunshine" diff --git a/packaging/linux/flatpak/dev.lizardbyte.sunshine.yml b/packaging/linux/flatpak/dev.lizardbyte.sunshine.yml index b62f0e1d667..d9fe390045a 100644 --- a/packaging/linux/flatpak/dev.lizardbyte.sunshine.yml +++ b/packaging/linux/flatpak/dev.lizardbyte.sunshine.yml @@ -330,6 +330,7 @@ modules: npm_config_nodedir: /usr/lib/sdk/node18 NPM_CONFIG_LOGLEVEL: info config-opts: + - -DBUILD_WERROR=ON - -DCMAKE_BUILD_TYPE=Release - -DCMAKE_INSTALL_PREFIX=/app - -DCMAKE_CUDA_COMPILER=/app/cuda/bin/nvcc diff --git a/packaging/macos/Portfile b/packaging/macos/Portfile index 19aeccf2dbd..2ef8098f155 100644 --- a/packaging/macos/Portfile +++ b/packaging/macos/Portfile @@ -40,7 +40,8 @@ depends_lib port:avahi \ boost.version 1.81 -configure.args -DCMAKE_INSTALL_PREFIX=${prefix} \ +configure.args -DBUILD_WERROR=ON \ + -DCMAKE_INSTALL_PREFIX=${prefix} \ -DSUNSHINE_ASSETS_DIR=etc/sunshine/assets startupitem.create yes From 8a7a6c48f8048e701571ba88727345c9f6f9df48 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Mon, 26 Feb 2024 12:55:34 -0500 Subject: [PATCH 064/182] build(cmake) properly find evdev (#2176) --- cmake/compile_definitions/linux.cmake | 13 +++++++++++-- packaging/linux/flatpak/dev.lizardbyte.sunshine.yml | 1 - src/platform/linux/input.cpp | 2 ++ 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/cmake/compile_definitions/linux.cmake b/cmake/compile_definitions/linux.cmake index 613a090947d..b6d1990a24e 100644 --- a/cmake/compile_definitions/linux.cmake +++ b/cmake/compile_definitions/linux.cmake @@ -120,6 +120,17 @@ elseif(NOT LIBDRM_FOUND) message(WARNING "Missing libcap") endif() +# evdev +pkg_check_modules(PC_EVDEV libevdev REQUIRED) +find_path(EVDEV_INCLUDE_DIR libevdev/libevdev.h + HINTS ${PC_EVDEV_INCLUDE_DIRS} ${PC_EVDEV_INCLUDEDIR}) +find_library(EVDEV_LIBRARY + NAMES evdev libevdev) +if(EVDEV_INCLUDE_DIR AND EVDEV_LIBRARY) + include_directories(SYSTEM ${EVDEV_INCLUDE_DIR}) + list(APPEND PLATFORM_LIBRARIES ${EVDEV_LIBRARY}) +endif() + # vaapi if(${SUNSHINE_ENABLE_VAAPI}) find_package(Libva) @@ -241,13 +252,11 @@ list(APPEND PLATFORM_TARGET_FILES list(APPEND PLATFORM_LIBRARIES Boost::dynamic_linking dl - evdev numa pulse pulse-simple) include_directories( SYSTEM - /usr/include/libevdev-1.0 "${CMAKE_SOURCE_DIR}/third-party/nv-codec-headers/include" "${CMAKE_SOURCE_DIR}/third-party/glad/include") diff --git a/packaging/linux/flatpak/dev.lizardbyte.sunshine.yml b/packaging/linux/flatpak/dev.lizardbyte.sunshine.yml index d9fe390045a..a4176d4ea15 100644 --- a/packaging/linux/flatpak/dev.lizardbyte.sunshine.yml +++ b/packaging/linux/flatpak/dev.lizardbyte.sunshine.yml @@ -325,7 +325,6 @@ modules: append-path: /usr/lib/sdk/node18/bin build-args: - --share=network - cxxflags: -I${C_INCLUDE_PATH}/libevdev-1.0 env: npm_config_nodedir: /usr/lib/sdk/node18 NPM_CONFIG_LOGLEVEL: info diff --git a/src/platform/linux/input.cpp b/src/platform/linux/input.cpp index 85818110730..42e239980d8 100644 --- a/src/platform/linux/input.cpp +++ b/src/platform/linux/input.cpp @@ -6,8 +6,10 @@ #include #include +extern "C" { #include #include +} #ifdef SUNSHINE_BUILD_X11 #include From 11c5b64d397cb51d6de755320e7134f7b01ec304 Mon Sep 17 00:00:00 2001 From: James Le Cuirot Date: Mon, 26 Feb 2024 23:53:56 +0000 Subject: [PATCH 065/182] Use nlohmann_json package instead of submodule (#2161) --- .github/workflows/CI.yml | 1 + .gitmodules | 4 ---- cmake/compile_definitions/common.cmake | 3 +-- cmake/compile_definitions/windows.cmake | 1 + cmake/dependencies/common.cmake | 3 --- cmake/dependencies/windows.cmake | 3 +++ docs/source/building/windows.rst | 1 + packaging/macos/Portfile | 7 ++++--- third-party/nlohmann_json | 1 - 9 files changed, 11 insertions(+), 13 deletions(-) delete mode 160000 third-party/nlohmann_json diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index f1005458c93..60f555c2166 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -769,6 +769,7 @@ jobs: mingw-w64-x86_64-cmake mingw-w64-x86_64-curl mingw-w64-x86_64-miniupnpc + mingw-w64-x86_64-nlohmann-json mingw-w64-x86_64-nodejs mingw-w64-x86_64-nsis mingw-w64-x86_64-onevpl diff --git a/.gitmodules b/.gitmodules index 67522f450ed..a4231d16f72 100644 --- a/.gitmodules +++ b/.gitmodules @@ -10,10 +10,6 @@ path = third-party/nanors url = https://github.com/sleepybishop/nanors.git branch = master -[submodule "third-party/nlohmann_json"] - path = third-party/nlohmann_json - url = https://github.com/nlohmann/json - branch = master [submodule "third-party/nv-codec-headers"] path = third-party/nv-codec-headers url = https://github.com/FFmpeg/nv-codec-headers diff --git a/cmake/compile_definitions/common.cmake b/cmake/compile_definitions/common.cmake index ca6ddcbb0f3..646e1298e43 100644 --- a/cmake/compile_definitions/common.cmake +++ b/cmake/compile_definitions/common.cmake @@ -152,5 +152,4 @@ list(APPEND SUNSHINE_EXTERNAL_LIBRARIES ${Boost_LIBRARIES} ${OPENSSL_LIBRARIES} ${CURL_LIBRARIES} - ${PLATFORM_LIBRARIES} - nlohmann_json::nlohmann_json) + ${PLATFORM_LIBRARIES}) diff --git a/cmake/compile_definitions/windows.cmake b/cmake/compile_definitions/windows.cmake index 2095cc5baa4..699a84c184d 100644 --- a/cmake/compile_definitions/windows.cmake +++ b/cmake/compile_definitions/windows.cmake @@ -84,6 +84,7 @@ list(PREPEND PLATFORM_LIBRARIES avrt iphlpapi shlwapi + PkgConfig::NLOHMANN_JSON ${CURL_STATIC_LIBRARIES}) if(SUNSHINE_ENABLE_TRAY) diff --git a/cmake/dependencies/common.cmake b/cmake/dependencies/common.cmake index a1f35128005..29bed10e5cd 100644 --- a/cmake/dependencies/common.cmake +++ b/cmake/dependencies/common.cmake @@ -19,9 +19,6 @@ pkg_check_modules(CURL REQUIRED libcurl) pkg_check_modules(MINIUPNP miniupnpc REQUIRED) include_directories(SYSTEM ${MINIUPNP_INCLUDE_DIRS}) -# nlohmann_json -add_subdirectory("${CMAKE_SOURCE_DIR}/third-party/nlohmann_json") - # ffmpeg pre-compiled binaries if(WIN32) if(NOT CMAKE_SYSTEM_PROCESSOR STREQUAL "AMD64") diff --git a/cmake/dependencies/windows.cmake b/cmake/dependencies/windows.cmake index 376c44da65a..a7ecce3963d 100644 --- a/cmake/dependencies/windows.cmake +++ b/cmake/dependencies/windows.cmake @@ -2,3 +2,6 @@ set(Boost_USE_STATIC_LIBS ON) # cmake-lint: disable=C0103 find_package(Boost 1.71.0 COMPONENTS locale log filesystem program_options REQUIRED) + +# nlohmann_json +pkg_check_modules(NLOHMANN_JSON nlohmann_json REQUIRED IMPORTED_TARGET) diff --git a/docs/source/building/windows.rst b/docs/source/building/windows.rst index 8e83c58e99d..f73346ffebf 100644 --- a/docs/source/building/windows.rst +++ b/docs/source/building/windows.rst @@ -26,6 +26,7 @@ Install dependencies: mingw-w64-x86_64-cmake \ mingw-w64-x86_64-curl \ mingw-w64-x86_64-miniupnpc \ + mingw-w64-x86_64-nlohmann-json \ mingw-w64-x86_64-nodejs \ mingw-w64-x86_64-onevpl \ mingw-w64-x86_64-openssl \ diff --git a/packaging/macos/Portfile b/packaging/macos/Portfile index 2ef8098f155..ba4815e25dc 100644 --- a/packaging/macos/Portfile +++ b/packaging/macos/Portfile @@ -31,12 +31,13 @@ post-fetch { system -W ${worksrcpath} "${git.cmd} submodule update --init --recursive" } +depends_build-append port:npm9 \ + port:pkgconfig + depends_lib port:avahi \ port:curl \ port:libopus \ - port:miniupnpc \ - port:npm9 \ - port:pkgconfig + port:miniupnpc boost.version 1.81 diff --git a/third-party/nlohmann_json b/third-party/nlohmann_json deleted file mode 160000 index 9cca280a4d0..00000000000 --- a/third-party/nlohmann_json +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 9cca280a4d0ccf0c08f47a99aa71d1b0e52f8d03 From c605a4da2b8d19c079ce047abb22981a2fac5b05 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 Feb 2024 21:36:59 -0500 Subject: [PATCH 066/182] build(deps): bump peter-evans/create-pull-request from 5 to 6 (#2083) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/localize.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/localize.yml b/.github/workflows/localize.yml index f9cd64859bb..815b67585ce 100644 --- a/.github/workflows/localize.yml +++ b/.github/workflows/localize.yml @@ -77,7 +77,7 @@ jobs: run: echo "date=$(date +'%Y-%m-%d')" >> $GITHUB_OUTPUT - name: Create/Update Pull Request - uses: peter-evans/create-pull-request@v5 + uses: peter-evans/create-pull-request@v6 with: add-paths: | locale/*.po From 53b2217a34ef04a25554651a3270962cdfcf8dee Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 Feb 2024 22:33:13 -0500 Subject: [PATCH 067/182] build(deps): bump bootstrap from 5.3.2 to 5.3.3 (#2154) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 4a709402bb3..b685e0966a4 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "@fortawesome/fontawesome-free": "6.5.1", "@popperjs/core": "2.11.8", "@vitejs/plugin-vue": "4.6.2", - "bootstrap": "5.3.2", + "bootstrap": "5.3.3", "vite": "4.5.2", "vite-plugin-ejs": "1.6.4", "vue": "3.4.5" From 83e3ea5aa7cc87b17653838b8da04528d3763bf6 Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Sun, 25 Feb 2024 19:23:58 -0600 Subject: [PATCH 068/182] Use a common function to abort for debugging purposes --- src/entry_handler.cpp | 12 ++++++++++++ src/entry_handler.h | 2 ++ src/main.cpp | 4 ++-- src/platform/windows/misc.cpp | 2 +- src/stream.cpp | 2 +- 5 files changed, 18 insertions(+), 4 deletions(-) diff --git a/src/entry_handler.cpp b/src/entry_handler.cpp index c7719eb0ed1..146a4dfb07b 100644 --- a/src/entry_handler.cpp +++ b/src/entry_handler.cpp @@ -166,6 +166,18 @@ namespace lifetime { } } + /** + * @brief Breaks into the debugger or terminates Sunshine if no debugger is attached. + */ + void + debug_trap() { +#ifdef _WIN32 + DebugBreak(); +#else + std::raise(SIGTRAP); +#endif + } + /** * @brief Gets the argv array passed to main(). */ diff --git a/src/entry_handler.h b/src/entry_handler.h index c58d0325d70..bdab361cf0c 100644 --- a/src/entry_handler.h +++ b/src/entry_handler.h @@ -42,6 +42,8 @@ namespace lifetime { extern std::atomic_int desired_exit_code; void exit_sunshine(int exit_code, bool async); + void + debug_trap(); char ** get_argv(); } // namespace lifetime diff --git a/src/main.cpp b/src/main.cpp index b8983e3d221..5cc28f9f2a2 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -318,7 +318,7 @@ main(int argc, char *argv[]) { auto task = []() { BOOST_LOG(fatal) << "10 seconds passed, yet Sunshine's still running: Forcing shutdown"sv; log_flush(); - std::abort(); + lifetime::debug_trap(); }; force_shutdown = task_pool.pushDelayed(task, 10s).task_id; @@ -331,7 +331,7 @@ main(int argc, char *argv[]) { auto task = []() { BOOST_LOG(fatal) << "10 seconds passed, yet Sunshine's still running: Forcing shutdown"sv; log_flush(); - std::abort(); + lifetime::debug_trap(); }; force_shutdown = task_pool.pushDelayed(task, 10s).task_id; diff --git a/src/platform/windows/misc.cpp b/src/platform/windows/misc.cpp index b53bfea5a6e..77de69f4cd3 100644 --- a/src/platform/windows/misc.cpp +++ b/src/platform/windows/misc.cpp @@ -489,7 +489,7 @@ namespace platf { 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(); + DebugBreak(); } return ec; diff --git a/src/stream.cpp b/src/stream.cpp index b9238057b1c..9c146804523 100644 --- a/src/stream.cpp +++ b/src/stream.cpp @@ -1815,7 +1815,7 @@ namespace stream { auto task = []() { BOOST_LOG(fatal) << "Hang detected! Session failed to terminate in 10 seconds."sv; log_flush(); - std::abort(); + lifetime::debug_trap(); }; auto force_kill = task_pool.pushDelayed(task, 10s).task_id; auto fg = util::fail_guard([&force_kill]() { From 1020d0c133c027eb542161dc620b83d88f6f87ad Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Tue, 27 Feb 2024 00:25:22 -0600 Subject: [PATCH 069/182] Install ViGEmBus before starting Sunshine --- cmake/packaging/windows.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/packaging/windows.cmake b/cmake/packaging/windows.cmake index 1ea1afe191d..2b512ed6999 100644 --- a/cmake/packaging/windows.cmake +++ b/cmake/packaging/windows.cmake @@ -57,8 +57,8 @@ SET(CPACK_NSIS_EXTRA_INSTALL_COMMANDS nsExec::ExecToLog 'icacls \\\"$INSTDIR\\\" /reset' nsExec::ExecToLog '\\\"$INSTDIR\\\\scripts\\\\migrate-config.bat\\\"' nsExec::ExecToLog '\\\"$INSTDIR\\\\scripts\\\\add-firewall-rule.bat\\\"' - nsExec::ExecToLog '\\\"$INSTDIR\\\\scripts\\\\install-service.bat\\\"' nsExec::ExecToLog '\\\"$INSTDIR\\\\scripts\\\\install-gamepad.bat\\\"' + nsExec::ExecToLog '\\\"$INSTDIR\\\\scripts\\\\install-service.bat\\\"' nsExec::ExecToLog '\\\"$INSTDIR\\\\scripts\\\\autostart-service.bat\\\"' NoController: ") From a0d5973799b455203770c876f0ec0469abab84ee Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Tue, 27 Feb 2024 00:27:09 -0600 Subject: [PATCH 070/182] Avoid display switching unexpectedly when the UAC secure desktop appears --- src/platform/windows/display_base.cpp | 38 ++++++++++++++++++++++----- 1 file changed, 32 insertions(+), 6 deletions(-) diff --git a/src/platform/windows/display_base.cpp b/src/platform/windows/display_base.cpp index a5f8cc5d31a..b258690e10e 100644 --- a/src/platform/windows/display_base.cpp +++ b/src/platform/windows/display_base.cpp @@ -358,8 +358,15 @@ namespace platf::dxgi { return false; } + /** + * @brief Tests to determine if the Desktop Duplication API can capture the given output. + * @details When testing for enumeration only, we avoid resyncing the thread desktop. + * @param adapter The DXGI adapter to use for capture. + * @param output The DXGI output to capture. + * @param enumeration_only Specifies whether this test is occurring for display enumeration. + */ bool - test_dxgi_duplication(adapter_t &adapter, output_t &output) { + test_dxgi_duplication(adapter_t &adapter, output_t &output, bool enumeration_only) { D3D_FEATURE_LEVEL featureLevels[] { D3D_FEATURE_LEVEL_11_1, D3D_FEATURE_LEVEL_11_0, @@ -397,14 +404,26 @@ namespace platf::dxgi { for (int x = 0; x < 2; ++x) { dup_t dup; - // Ensure we can duplicate the current display - syncThreadDesktop(); + // Only resynchronize the thread desktop when not enumerating displays. + // During enumeration, the caller will do this only once to ensure + // a consistent view of available outputs. + if (!enumeration_only) { + syncThreadDesktop(); + } status = output1->DuplicateOutput((IUnknown *) device.get(), &dup); if (SUCCEEDED(status)) { return true; } - std::this_thread::sleep_for(200ms); + + // If we're not resyncing the thread desktop and we don't have permission to + // capture the current desktop, just bail immediately. Retrying won't help. + if (enumeration_only && status == E_ACCESSDENIED) { + break; + } + else { + std::this_thread::sleep_for(200ms); + } } BOOST_LOG(error) << "DuplicateOutput() test failed [0x"sv << util::hex(status).to_string_view() << ']'; @@ -472,7 +491,7 @@ namespace platf::dxgi { continue; } - if (desc.AttachedToDesktop && test_dxgi_duplication(adapter_tmp, output_tmp)) { + if (desc.AttachedToDesktop && test_dxgi_duplication(adapter_tmp, output_tmp, false)) { output = std::move(output_tmp); offset_x = desc.DesktopCoordinates.left; @@ -1068,6 +1087,13 @@ namespace platf { BOOST_LOG(warning) << "Failed to set GPU preference. Capture may not work!"sv; } + // We sync the thread desktop once before we start the enumeration process + // to ensure test_dxgi_duplication() returns consistent results for all GPUs + // even if the current desktop changes during our enumeration process. + // It is critical that we either fully succeed in enumeration or fully fail, + // otherwise it can lead to the capture code switching monitors unexpectedly. + syncThreadDesktop(); + dxgi::factory1_t factory; status = CreateDXGIFactory1(IID_IDXGIFactory1, (void **) &factory); if (FAILED(status)) { @@ -1111,7 +1137,7 @@ namespace platf { << std::endl; // Don't include the display in the list if we can't actually capture it - if (desc.AttachedToDesktop && dxgi::test_dxgi_duplication(adapter, output)) { + if (desc.AttachedToDesktop && dxgi::test_dxgi_duplication(adapter, output, true)) { display_names.emplace_back(std::move(device_name)); } } From 15272fb47ebb594d2d089e5f04fc24cf0d5f4af7 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Thu, 29 Feb 2024 10:33:19 -0500 Subject: [PATCH 071/182] fix(config): properly save global_prep_cmd and fps (#2192) --- src_assets/common/assets/web/config.html | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src_assets/common/assets/web/config.html b/src_assets/common/assets/web/config.html index e9d866dde51..b90ab7ed1ac 100644 --- a/src_assets/common/assets/web/config.html +++ b/src_assets/common/assets/web/config.html @@ -1408,12 +1408,20 @@

this.tabs.forEach(tab => { Object.keys(tab.options).forEach(optionKey => { let delete_value = false - if (optionKey === "global_prep_cmd" || optionKey === "resolutions" || optionKey === "fps") { - let regex = /([\d]+x[\d]+)/g // this regex is only needed for resolutions - // Use a regular expression to find each value and replace it with a quoted version - let config_value = JSON.parse(config[optionKey].replace(regex, '"$1"')).toString() - let default_value = JSON.parse(tab.options[optionKey].replace(regex, '"$1"')).toString() + if (["resolutions", "fps", "global_prep_cmd"].includes(optionKey)) { + let config_value, default_value + + if (optionKey === "resolutions") { + let regex = /([\d]+x[\d]+)/g + + // Use a regular expression to find each value and replace it with a quoted version + config_value = JSON.parse(config[optionKey].replace(regex, '"$1"')).toString() + default_value = JSON.parse(tab.options[optionKey].replace(regex, '"$1"')).toString() + } else { + config_value = JSON.parse(config[optionKey]) + default_value = JSON.parse(tab.options[optionKey]) + } if (config_value === default_value) { delete_value = true From 4252f5df7c95e492adee29976e6581bb4597ad16 Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Mon, 26 Feb 2024 21:42:52 -0600 Subject: [PATCH 072/182] Add option to allow HEVC usage on older Intel GPUs without low-power encoding --- docs/source/about/advanced_usage.rst | 16 ++++++++++++++++ src/config.cpp | 2 ++ src/config.h | 1 + src/video.cpp | 4 +++- src_assets/common/assets/web/config.html | 13 +++++++++++++ 5 files changed, 35 insertions(+), 1 deletion(-) diff --git a/docs/source/about/advanced_usage.rst b/docs/source/about/advanced_usage.rst index fe603e38174..aae82ae7bd1 100644 --- a/docs/source/about/advanced_usage.rst +++ b/docs/source/about/advanced_usage.rst @@ -1424,6 +1424,22 @@ keybindings qsv_coder = auto +`qsv_slow_hevc `__ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +**Description** + This options enables use of HEVC on older Intel GPUs that only support low power encoding for H.264. + + .. Caution:: Streaming performance may be significantly reduced when this option is enabled. + +**Default** + ``disabled`` + +**Example** + .. code-block:: text + + qsv_slow_hevc = disabled + `AMD AMF Encoder `__ --------------------------------------------------------------------- diff --git a/src/config.cpp b/src/config.cpp index d9aaab3e31c..ba2718aca7f 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -339,6 +339,7 @@ namespace config { { qsv::medium, // preset qsv::_auto, // cavlc + false, // slow_hevc }, // qsv { @@ -962,6 +963,7 @@ namespace config { int_f(vars, "qsv_preset", video.qsv.qsv_preset, qsv::preset_from_view); int_f(vars, "qsv_coder", video.qsv.qsv_cavlc, qsv::coder_from_view); + bool_f(vars, "qsv_slow_hevc", video.qsv.qsv_slow_hevc); std::string quality; string_f(vars, "amd_quality", quality); diff --git a/src/config.h b/src/config.h index e08a87f3c4d..6c48f466b8e 100644 --- a/src/config.h +++ b/src/config.h @@ -44,6 +44,7 @@ namespace config { struct { std::optional qsv_preset; std::optional qsv_cavlc; + bool qsv_slow_hevc; } qsv; struct { diff --git a/src/video.cpp b/src/video.cpp index 636bcf60138..b73c0a4dd4b 100644 --- a/src/video.cpp +++ b/src/video.cpp @@ -763,7 +763,9 @@ namespace video { { "profile"s, (int) qsv::profile_hevc_e::main_10 }, }, // Fallback options - {}, + { + { "low_power"s, []() { return config::video.qsv.qsv_slow_hevc ? 0 : 1; } }, + }, std::nullopt, // QP rate control fallback "hevc_qsv"s, }, diff --git a/src_assets/common/assets/web/config.html b/src_assets/common/assets/web/config.html index b90ab7ed1ac..85fecacd0d9 100644 --- a/src_assets/common/assets/web/config.html +++ b/src_assets/common/assets/web/config.html @@ -1021,6 +1021,18 @@

+ +
+ + +
+ This can enable HEVC encoding on older Intel GPUs, at the cost of higher GPU usage and worse performance. +
+
+
@@ -1293,6 +1305,7 @@

options: { "qsv_preset": "medium", "qsv_coder": "auto", + "qsv_slow_hevc": "disabled", }, }, { From e9bb5697b05e8fc834ae15c78bc715819574e817 Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Mon, 26 Feb 2024 21:44:14 -0600 Subject: [PATCH 073/182] Move UPnP option to the top of the Network tab --- docs/source/about/advanced_usage.rst | 52 ++++++++++++------------ src_assets/common/assets/web/config.html | 22 +++++----- 2 files changed, 37 insertions(+), 37 deletions(-) diff --git a/docs/source/about/advanced_usage.rst b/docs/source/about/advanced_usage.rst index aae82ae7bd1..6d87eb0c028 100644 --- a/docs/source/about/advanced_usage.rst +++ b/docs/source/about/advanced_usage.rst @@ -712,6 +712,32 @@ keybindings `Network `__ ----------------------------------------------------- +`upnp `__ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +**Description** + Sunshine will attempt to open ports for streaming over the internet. + +**Choices** + +.. table:: + :widths: auto + + ===== =========== + Value Description + ===== =========== + on enable UPnP + off disable UPnP + ===== =========== + +**Default** + ``disabled`` + +**Example** + .. code-block:: text + + upnp = on + `address_family `__ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -798,32 +824,6 @@ keybindings origin_web_ui_allowed = lan -`upnp `__ -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -**Description** - Sunshine will attempt to open ports for streaming over the internet. - -**Choices** - -.. table:: - :widths: auto - - ===== =========== - Value Description - ===== =========== - on enable UPnP - off disable UPnP - ===== =========== - -**Default** - ``disabled`` - -**Example** - .. code-block:: text - - upnp = on - `external_ip `__ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/src_assets/common/assets/web/config.html b/src_assets/common/assets/web/config.html index 85fecacd0d9..315ad75f370 100644 --- a/src_assets/common/assets/web/config.html +++ b/src_assets/common/assets/web/config.html @@ -553,6 +553,16 @@

+ +
+ + +
Automatically configure port forwarding for streaming over the Internet
+
+
@@ -649,16 +659,6 @@

- -
- - -
Automatically configure port forwarding
-
-
@@ -1261,10 +1261,10 @@

id: "network", name: "Network", options: { + "upnp": "disabled", "address_family": "ipv4", "port": 47989, "origin_web_ui_allowed": "lan", - "upnp": "disabled", "external_ip": "", "lan_encryption_mode": 0, "wan_encryption_mode": 1, From dfb212cc3ce885580ff09c0d9a667fb3963c47d6 Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Mon, 26 Feb 2024 21:45:01 -0600 Subject: [PATCH 074/182] Don't display automatic gamepad options on unsupported platforms --- src_assets/common/assets/web/config.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src_assets/common/assets/web/config.html b/src_assets/common/assets/web/config.html index 315ad75f370..c1305adddd7 100644 --- a/src_assets/common/assets/web/config.html +++ b/src_assets/common/assets/web/config.html @@ -221,7 +221,7 @@

-
+

- +
- +
- Adaptive P-State algorithm which NVIDIA drivers employ doesn't work well with low latency streaming, so - sunshine requests high power mode explicitly.
+ Sunshine requests maximum GPU clock speed while streaming to reduce encoding latency.
Disabling it is not recommended since this can lead to significantly increased encoding latency.
From 2e97c55005d09e1fea6636bc071919d481988fca Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Mon, 26 Feb 2024 22:00:46 -0600 Subject: [PATCH 076/182] Move and rename Channels option to feature more prominently in the UI --- docs/source/about/advanced_usage.rst | 41 +++++++++++------------- src_assets/common/assets/web/config.html | 25 ++++++--------- 2 files changed, 28 insertions(+), 38 deletions(-) diff --git a/docs/source/about/advanced_usage.rst b/docs/source/about/advanced_usage.rst index 6d87eb0c028..f376f65887d 100644 --- a/docs/source/about/advanced_usage.rst +++ b/docs/source/about/advanced_usage.rst @@ -92,6 +92,24 @@ editing the `conf` file in a text editor. Use the examples as reference. min_log_level = info +`channels `__ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +**Description** + Sunshine can support multiple clients streaming simultaneously, at the cost of higher CPU and GPU usage. + + .. note:: All connected clients share control of the same streaming session. + + .. warning:: Some hardware encoders may have limitations that reduce performance with multiple streams. + +**Default** + ``1`` + +**Example** + .. code-block:: text + + channels = 1 + `global_prep_cmd `__ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -913,29 +931,6 @@ keybindings `Advanced `__ ------------------------------------------------------- -`channels `__ -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -**Description** - This will generate distinct video streams, unlike simply broadcasting to multiple Clients. - - When multicasting, it could be useful to have different configurations for each connected Client. - - For instance: - - - Clients connected through WAN and LAN have different bitrate constraints. - - Decoders may require different settings for color. - - .. warning:: CPU usage increases for each distinct video stream generated. - -**Default** - ``1`` - -**Example** - .. code-block:: text - - channels = 1 - `fec_percentage `__ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/src_assets/common/assets/web/config.html b/src_assets/common/assets/web/config.html index 17aea3d1e7e..3921f966811 100644 --- a/src_assets/common/assets/web/config.html +++ b/src_assets/common/assets/web/config.html @@ -64,6 +64,16 @@

Configuration

+ +
+ + +
+ Sunshine can allow a single streaming session to be shared with multiple clients simultaneously.
+ Note: Some hardware encoders may have limitations that reduce performance with multiple streams. +
+
+
@@ -721,21 +731,6 @@

- -
- - -
- When multicasting, it could be useful to have different configurations for each connected Client. For example: -
    -
  • Clients connected through WAN and LAN have different bitrate constraints.
  • -
  • Decoders may require different settings for color
  • -
- Unlike simply broadcasting to multiple Client, this will generate distinct video streams.
- Note, CPU usage increases for each distinct video stream generated -
-
-
From 8081f4167e379d2a93c6161ba9ca7bb1ece4d4bc Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Mon, 26 Feb 2024 22:09:04 -0600 Subject: [PATCH 077/182] Add note to enclose paths with spaces in quotes --- src_assets/common/assets/web/apps.html | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src_assets/common/assets/web/apps.html b/src_assets/common/assets/web/apps.html index 36ddc5a9e6d..8156d0a6f48 100644 --- a/src_assets/common/assets/web/apps.html +++ b/src_assets/common/assets/web/apps.html @@ -197,7 +197,8 @@

Applications

- A list of commands to be run and forgotten about + A list of commands to be run in the background.
+ Note: If the path to the command executable contains spaces, you must enclose it in quotes.
@@ -206,8 +207,8 @@

Applications

- The main application, if it is not specified, a process is started - that sleeps indefinitely + The main application to start. If blank, no application will be started.
+ Note: If the path to the command executable contains spaces, you must enclose it in quotes.
From cb57322190fad8ac3f98e9d8346b5504669f4d1e Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Mon, 26 Feb 2024 22:13:32 -0600 Subject: [PATCH 078/182] Move and rename Files tab to be less prominent --- docs/source/about/advanced_usage.rst | 185 +++++++++++------------ src_assets/common/assets/web/config.html | 142 ++++++++--------- 2 files changed, 163 insertions(+), 164 deletions(-) diff --git a/docs/source/about/advanced_usage.rst b/docs/source/about/advanced_usage.rst index f376f65887d..f29e6d05f8d 100644 --- a/docs/source/about/advanced_usage.rst +++ b/docs/source/about/advanced_usage.rst @@ -124,99 +124,6 @@ editing the `conf` file in a text editor. Use the examples as reference. global_prep_cmd = [{"do":"nircmd.exe setdisplay 1280 720 32 144","undo":"nircmd.exe setdisplay 2560 1440 32 144"}] -`Files `__ -------------------------------------------------- - -`file_apps `__ -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -**Description** - The application configuration file path. The file contains a json formatted list of applications that can be started - by Moonlight. - -**Default** - OS and package dependent - -**Example** - .. code-block:: text - - file_apps = apps.json - -`credentials_file `__ -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -**Description** - The file where user credentials for the UI are stored. - -**Default** - ``sunshine_state.json`` - -**Example** - .. code-block:: text - - credentials_file = sunshine_state.json - -`log_path `__ -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -**Description** - The path where the sunshine log is stored. - -**Default** - ``sunshine.log`` - -**Example** - .. code-block:: text - - log_path = sunshine.log - -`pkey `__ -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -**Description** - The private key used for the web UI and Moonlight client pairing. For best compatibility, this should be an RSA-2048 private key. - - .. warning:: Not all Moonlight clients support ECDSA keys or RSA key lengths other than 2048 bits. - -**Default** - ``credentials/cakey.pem`` - -**Example** - .. code-block:: text - - pkey = /dir/pkey.pem - -`cert `__ -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -**Description** - The certificate used for the web UI and Moonlight client pairing. For best compatibility, this should have an RSA-2048 public key. - - .. warning:: Not all Moonlight clients support ECDSA keys or RSA key lengths other than 2048 bits. - -**Default** - ``credentials/cacert.pem`` - -**Example** - .. code-block:: text - - cert = /dir/cert.pem - -`file_state `__ -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -**Description** - The file where current state of Sunshine is stored. - -**Default** - ``sunshine_state.json`` - -**Example** - .. code-block:: text - - file_state = sunshine_state.json - - `Input `__ ------------------------------------------------- @@ -928,6 +835,98 @@ keybindings ping_timeout = 10000 +`Config Files `__ +-------------------------------------------------------- + +`file_apps `__ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +**Description** + The application configuration file path. The file contains a json formatted list of applications that can be started + by Moonlight. + +**Default** + OS and package dependent + +**Example** + .. code-block:: text + + file_apps = apps.json + +`credentials_file `__ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +**Description** + The file where user credentials for the UI are stored. + +**Default** + ``sunshine_state.json`` + +**Example** + .. code-block:: text + + credentials_file = sunshine_state.json + +`log_path `__ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +**Description** + The path where the sunshine log is stored. + +**Default** + ``sunshine.log`` + +**Example** + .. code-block:: text + + log_path = sunshine.log + +`pkey `__ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +**Description** + The private key used for the web UI and Moonlight client pairing. For best compatibility, this should be an RSA-2048 private key. + + .. warning:: Not all Moonlight clients support ECDSA keys or RSA key lengths other than 2048 bits. + +**Default** + ``credentials/cakey.pem`` + +**Example** + .. code-block:: text + + pkey = /dir/pkey.pem + +`cert `__ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +**Description** + The certificate used for the web UI and Moonlight client pairing. For best compatibility, this should have an RSA-2048 public key. + + .. warning:: Not all Moonlight clients support ECDSA keys or RSA key lengths other than 2048 bits. + +**Default** + ``credentials/cacert.pem`` + +**Example** + .. code-block:: text + + cert = /dir/cert.pem + +`file_state `__ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +**Description** + The file where current state of Sunshine is stored. + +**Default** + ``sunshine_state.json`` + +**Example** + .. code-block:: text + + file_state = sunshine_state.json + `Advanced `__ ------------------------------------------------------- diff --git a/src_assets/common/assets/web/config.html b/src_assets/common/assets/web/config.html index 3921f966811..a072e101ba0 100644 --- a/src_assets/common/assets/web/config.html +++ b/src_assets/common/assets/web/config.html @@ -124,65 +124,6 @@

Configuration

- -
- -
- - -
- The file where current apps of Sunshine are stored -
-
- - -
- - -
- Store Username/Password separately from Sunshine's state file. -
-
- - -
- - -
- The file where the current logs of Sunshine are stored. -
-
- - -
- - -
The private key used for the web UI and Moonlight client pairing. For best - compatibility, this should be an RSA-2048 private key.
-
- - -
- - -
- The certificate used for the web UI and Moonlight client pairing. For best compatibility, this should have - an RSA-2048 public key. -
-
- - -
- - -
- The file where current state of Sunshine is stored -
-
- -
-
@@ -729,6 +670,65 @@

+ +
+ +
+ + +
+ The file where current apps of Sunshine are stored +
+
+ + +
+ + +
+ Store Username/Password separately from Sunshine's state file. +
+
+ + +
+ + +
+ The file where the current logs of Sunshine are stored. +
+
+ + +
+ + +
The private key used for the web UI and Moonlight client pairing. For best + compatibility, this should be an RSA-2048 private key.
+
+ + +
+ + +
+ The certificate used for the web UI and Moonlight client pairing. For best compatibility, this should have + an RSA-2048 public key. +
+
+ + +
+ + +
+ The file where current state of Sunshine is stored +
+
+ +
+
@@ -1204,18 +1204,6 @@

"global_prep_cmd": "[]", }, }, - { - id: "files", - name: "Files", - options: { - "file_apps": "", - "credentials_file": "", - "log_path": "", - "pkey": "", - "cert": "", - "file_state": "", - }, - }, { id: "input", name: "Input", @@ -1264,6 +1252,18 @@

"ping_timeout": 10000, }, }, + { + id: "files", + name: "Config Files", + options: { + "file_apps": "", + "credentials_file": "", + "log_path": "", + "pkey": "", + "cert": "", + "file_state": "", + }, + }, { id: "advanced", name: "Advanced", From e430f51e2fd981ed91aed79adbbacfcaa427c2a6 Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Mon, 26 Feb 2024 22:43:34 -0600 Subject: [PATCH 079/182] Add friendly message when encoder detection fails --- src/video.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/video.cpp b/src/video.cpp index b73c0a4dd4b..f786aeb59f0 100644 --- a/src/video.cpp +++ b/src/video.cpp @@ -2721,7 +2721,13 @@ namespace video { } if (chosen_encoder == nullptr) { - BOOST_LOG(fatal) << "Couldn't find any working encoder"sv; + BOOST_LOG(fatal) << "Unable to find display or encoder during startup."sv; + if (!config::video.adapter_name.empty() || !config::video.output_name.empty()) { + BOOST_LOG(fatal) << "Please ensure your manually chosen GPU and monitor are connected and powered on."sv; + } + else { + BOOST_LOG(fatal) << "Please check that a display is connected and powered on."sv; + } return -1; } From 75a97883e71d1c17eb2784c110469042f520f81f Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Sat, 24 Feb 2024 14:18:15 -0600 Subject: [PATCH 080/182] Rework dummy image handling to avoid RTX HDR driver bug As a side effect, it avoids useless allocations and uploads of a zeroed memory buffer to clear the dummy image textures. --- src/platform/windows/display_vram.cpp | 83 +++++++++++++-------------- 1 file changed, 39 insertions(+), 44 deletions(-) diff --git a/src/platform/windows/display_vram.cpp b/src/platform/windows/display_vram.cpp index e9f8140d525..a56869eab40 100644 --- a/src/platform/windows/display_vram.cpp +++ b/src/platform/windows/display_vram.cpp @@ -125,6 +125,9 @@ namespace platf::dxgi { // the first successful capture of a desktop frame bool dummy = false; + // Set to true if the image is blank (contains no content at all, including a cursor) + bool blank = true; + // Unique identifier for this image uint32_t id = 0; @@ -382,38 +385,40 @@ namespace platf::dxgi { } auto &img = (img_d3d_t &) img_base; - auto &img_ctx = img_ctx_map[img.id]; + if (!img.blank) { + auto &img_ctx = img_ctx_map[img.id]; - // Open the shared capture texture with our ID3D11Device - if (initialize_image_context(img, img_ctx)) { - return -1; - } + // Open the shared capture texture with our ID3D11Device + if (initialize_image_context(img, img_ctx)) { + return -1; + } - // Acquire encoder mutex to synchronize with capture code - auto status = img_ctx.encoder_mutex->AcquireSync(0, INFINITE); - if (status != S_OK) { - BOOST_LOG(error) << "Failed to acquire encoder mutex [0x"sv << util::hex(status).to_string_view() << ']'; - return -1; - } + // Acquire encoder mutex to synchronize with capture code + auto status = img_ctx.encoder_mutex->AcquireSync(0, INFINITE); + if (status != S_OK) { + BOOST_LOG(error) << "Failed to acquire encoder mutex [0x"sv << util::hex(status).to_string_view() << ']'; + return -1; + } - device_ctx->OMSetRenderTargets(1, &nv12_Y_rt, nullptr); - device_ctx->VSSetShader(scene_vs.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_Y_rt, nullptr); + device_ctx->VSSetShader(scene_vs.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(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); + device_ctx->OMSetRenderTargets(1, &nv12_UV_rt, nullptr); + device_ctx->VSSetShader(convert_UV_vs.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); - // Release encoder mutex to allow capture code to reuse this image - img_ctx.encoder_mutex->ReleaseSync(0); + // 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); + ID3D11ShaderResourceView *emptyShaderResourceView = nullptr; + device_ctx->PSSetShaderResources(0, 1, &emptyShaderResourceView); + } return 0; } @@ -1144,6 +1149,9 @@ namespace platf::dxgi { return { nullptr, nullptr }; } + // Clear the blank flag now that we're ready to capture into the image + d3d_img->blank = false; + return { std::move(d3d_img), std::move(lock_helper) }; }; @@ -1289,15 +1297,14 @@ namespace platf::dxgi { // 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; + bool reclear_dummy = !old_d3d_img->blank && 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); + const float rgb_black[] = { 0.0f, 0.0f, 0.0f, 0.0f }; + device_ctx->ClearRenderTargetView(d3d_img->capture_rt.get(), rgb_black); } if (blend_mouse_cursor_flag) { @@ -1409,6 +1416,7 @@ namespace platf::dxgi { img->width = width_before_rotation; img->height = height_before_rotation; img->id = next_image_id++; + img->blank = true; return img; } @@ -1456,20 +1464,7 @@ 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; - 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); - } + auto 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 1ab30aa70bc017ec3aa53a42f559f603d47db97c Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Fri, 1 Mar 2024 20:51:50 -0600 Subject: [PATCH 081/182] Add log messages to indicate display numbers for KMS and Wlgrab --- src/platform/linux/kmsgrab.cpp | 13 ++++++++++--- src/platform/linux/wlgrab.cpp | 6 ++++++ 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/src/platform/linux/kmsgrab.cpp b/src/platform/linux/kmsgrab.cpp index 192482deaa1..cd622fa775c 100644 --- a/src/platform/linux/kmsgrab.cpp +++ b/src/platform/linux/kmsgrab.cpp @@ -145,10 +145,13 @@ namespace platf { }; struct monitor_t { + // Connector attributes std::uint32_t type; - std::uint32_t index; + // Monitor index in the global list + std::uint32_t monitor_index; + platf::touch_port_t viewport; }; @@ -1516,11 +1519,11 @@ namespace platf { correlate_to_wayland(std::vector &cds) { auto monitors = wl::monitors(); + BOOST_LOG(info) << "-------- Start of KMS monitor list --------"sv; + for (auto &monitor : monitors) { std::string_view name = monitor->name; - BOOST_LOG(info) << name << ": "sv << monitor->description; - // Try to convert names in the format: // {type}-{index} // {index} is n'th occurrence of {type} @@ -1553,6 +1556,7 @@ namespace platf { << monitor->viewport.width << 'x' << monitor->viewport.height; } + BOOST_LOG(info) << "Monitor " << monitor_descriptor.monitor_index << " is "sv << name << ": "sv << monitor->description; goto break_for_loop; } } @@ -1561,6 +1565,8 @@ namespace platf { BOOST_LOG(verbose) << "Reduced to name: "sv << name << ": "sv << index; } + + BOOST_LOG(info) << "--------- End of KMS monitor list ---------"sv; } // A list of names of displays accepted as display_name @@ -1637,6 +1643,7 @@ namespace platf { (int) crtc->width, (int) crtc->height, }; + it->second.monitor_index = count; } kms::env_width = std::max(kms::env_width, (int) (crtc->x + crtc->width)); diff --git a/src/platform/linux/wlgrab.cpp b/src/platform/linux/wlgrab.cpp index 84b69bd0be0..2ea15359fc3 100644 --- a/src/platform/linux/wlgrab.cpp +++ b/src/platform/linux/wlgrab.cpp @@ -424,15 +424,21 @@ namespace platf { display.roundtrip(); + BOOST_LOG(info) << "-------- Start of Wayland monitor list --------"sv; + for (int x = 0; x < interface.monitors.size(); ++x) { auto monitor = interface.monitors[x].get(); wl::env_width = std::max(wl::env_width, (int) (monitor->viewport.offset_x + monitor->viewport.width)); wl::env_height = std::max(wl::env_height, (int) (monitor->viewport.offset_y + monitor->viewport.height)); + BOOST_LOG(info) << "Monitor " << x << " is "sv << monitor->name << ": "sv << monitor->description; + display_names.emplace_back(std::to_string(x)); } + BOOST_LOG(info) << "--------- End of Wayland monitor list ---------"sv; + return display_names; } From 5606840c8983b714a0e442c42d887a49807715e1 Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Fri, 1 Mar 2024 20:52:17 -0600 Subject: [PATCH 082/182] Stop enumeration after finding a working capture backend --- src/platform/linux/misc.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/platform/linux/misc.cpp b/src/platform/linux/misc.cpp index ecc5887e0f1..0075d4502f8 100644 --- a/src/platform/linux/misc.cpp +++ b/src/platform/linux/misc.cpp @@ -836,27 +836,29 @@ namespace platf { #endif #ifdef SUNSHINE_BUILD_CUDA - if (config::video.capture.empty() || config::video.capture == "nvfbc") { + if ((config::video.capture.empty() && sources.none()) || config::video.capture == "nvfbc") { if (verify_nvfbc()) { sources[source::NVFBC] = true; } } #endif #ifdef SUNSHINE_BUILD_WAYLAND - if (config::video.capture.empty() || config::video.capture == "wlr") { + if ((config::video.capture.empty() && sources.none()) || config::video.capture == "wlr") { if (verify_wl()) { sources[source::WAYLAND] = true; } } #endif #ifdef SUNSHINE_BUILD_DRM - if (config::video.capture.empty() || config::video.capture == "kms") { + if ((config::video.capture.empty() && sources.none()) || config::video.capture == "kms") { if (verify_kms()) { sources[source::KMS] = true; } } #endif #ifdef SUNSHINE_BUILD_X11 + // We enumerate this capture backend regardless of other suitable sources, + // since it may be needed as a NvFBC fallback for software encoding on X11. if (config::video.capture.empty() || config::video.capture == "x11") { if (verify_x11()) { sources[source::X11] = true; From 8d5a9054ec3b60349162033db28ec34c5c40a809 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sun, 3 Mar 2024 16:50:20 -0500 Subject: [PATCH 083/182] chore: bump version to v0.22.0 (#2201) Co-authored-by: Cameron Gutman --- CHANGELOG.md | 127 +++++++++++++++++++++++++++++++++++++++++++++++++ CMakeLists.txt | 2 +- 2 files changed, 128 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bbeb4028382..037395640fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,131 @@ # Changelog +## [0.22.0] - 2024-03-03 +**Breaking** +- (Network) Clients must now be paired with the host before they can use Wake-on-LAN +- (Build/Linux) Drop Fedora 37 support + +**Added** +- (Input/Linux) Add native/pen touch support for Linux +- (Capture/Linux) Add HDR streaming support for Linux using KMS capture backend +- (Capture/Linux) Add KMS capture support for Nvidia GPUs running Wayland +- (Network) Add support for full E2E stream encryption, configurable for LAN and WAN independently +- (Process) Add process group tracking to automatically handle launchers that spawn other child processes +- (Capture/Windows) Add setting for controlling GPU power saving and encoding latency tradeoff for NVENC +- (Capture/Windows) Add additional encoding settings for NVENC +- (Process/Windows) Add experimental support for launching URLs and other non-exe files +- (Capture/Windows) Add setting to allow use of slower HEVC encoding on older Intel GPUs +- (Input/Windows) Add settings to control automatic gamepad type selection heuristics +- (Input/Windows) Add setting to allow DS4 back/select button to trigger touchpad click +- (Input) Add setting to disable high resolution scrolling and native pen/touch support +- (Network) Add support for certificates types other than RSA-2048 +- (Build/Linux) Add Fedora 39 docker image and rpm package +- (Capture/Linux) Display monitor indexes in logs for wlroots and KMS capture backends +- (UI) Add link to logs inside fatal error container +- (UI) Add hash handler and ids for all configuration categories and settings + +**Changed** +- (UI) Several configuration options have been moved to more suitable locations +- (Network) Client-selected bitrate is now adjusted for FEC percentage and other stream overhead +- (Capture/Linux) Improve VAAPI encoding performance on Intel GPUs +- (Capture) Connection establishment delay is reduced by eliminating many encoder probing operations +- (Process) Graceful termination of running processes is attempted first when stopping apps +- (Capture) Improve software encoding performance by enabling multi-threaded color conversion +- (Capture) Adjust default CPU thread count for software encoding from 1 to 2 for improved performance +- (Steam/Windows) Modernized the default Steam app shortcut to avoid depending on Steam's install location and support app termination +- (Linux) Updated desktop files +- (Config) Add 2560x1440 to default resolutions +- (Network) Use the configured ping timeout for the initial launch event timeout +- (UI) Migrate UI to Vite and Vue3, and various UX improvements +- (Logging) Adjust wording and severity of some log messages +- (Build) Use a single submodule for ffmpeg +- (Install/Windows) Skip ViGEmBus installation if a supported version is already installed +- (Build/Linux) Optionally, allow using the system installation of wayland-protocols +- (Build/Linux) Make vaapi optional +- (Windows) Replace boost::json with nlohmann/json + +**Fixed** +- (Network/Windows) Fix auto-discovery of hosts by iOS/tvOS clients +- (Network) Fix immediate connection termination when streaming over some Internet connections +- (Capture/Linux) Fix missing mouse cursor when using KMS capture on a GPU with hardware cursor support +- (Capture/Windows) Add workaround for Nvidia driver bug causing Sunshine to crash when RTX HDR is globally enabled +- (Capture/Windows) Add workaround for AMD driver bug on pre-RDNA GPUs causing hardware encoding failure +- (Capture/Windows) Reintroduce support for encoding on older Nvidia GPU drivers (v456.71-v522.25) +- (Capture/Windows) Fix encoding on old Intel GPUs that don't support low-power H.264 encoding +- (Capture/Linux) Fix GL errors or corrupt video output on GPUs that use aux planes such as Intel Arc +- (Capture/Linux) Fix GL errors or corrupt video output on GPUs that use DRM modifiers on YUV buffers +- (Input/Windows) Fix non-functional duplicate controllers appearing in rare cases +- (Input/Windows) Avoid triggering crash in ViGEmBus when the system goes to sleep +- (Input/Linux) Fix scrolling in applications that don't support high-resolution scrolling +- (Input/Linux) Fix absolute mouse input being interpreted as touch input +- (Capture/Linux) Fix wlroots capture causing GL errors and crashes +- (Capture/Linux) Fix wlroots capture failing when the display scale factor was not 1 +- (Capture/Linux) Fix excessive CPU usage when using wlroots capture backend +- (Capture/Linux) Fix capture of virtual displays created by the amdgpu kernel driver +- (Audio/Windows) Fix audio capture failures on Insider Preview versions of Windows 11 +- (Capture/Windows) Fix incorrect portrait mode rotation +- (Capture/Windows) Fix capture recovery when a driver update/crash occurs while streaming +- (Capture/Windows) Fix delay displaying UAC dialogs when the mouse cursor is not moving +- (Capture/Linux) Fix corrupt video output or stream disconnections if the display resolution changes while streaming +- (Capture/Linux) Fix color of aspect ratio padding in the capture image with VAAPI +- (Tray/Linux) Fix random crash when the tray icon is updating +- (Network) Fix QoS tagging when running in IPv4+IPv6 mode +- (Process) Fix termination of child processes upon app quit when the parent has already terminated +- (Process) Fix notification of graceful termination to connected clients when Sunshine quits +- (Capture) Fix corrupt output or green aspect-ratio padding when using software encoding with some video resolutions +- (Windows) Fix crashes when processing file paths or other strings with certain non-ASCII characters +- (Capture) Ensure user supplied framerates are used exclusively in place of pre-defined framerates +- (CMake/Linux) Skip including unnecessary headers +- (Capture/Linux) Replace vaTerminate method with dl handle +- (Capture/Linux) Fix capture when DRM is enabled and x11 is disabled +- (Tray) Use PROJECT_NAME definition for tooltip +- (CMake) Use GNUInstallDirs to install data and lib directories +- (macOS) Replace deprecated code +- (API) Allow trailing slashes in on API endpoints +- (API) Add additional pin validation +- (Linux) Use XDG spec for fetching config directory +- (CMake) Properly find evdev +- (Config) Properly save global_prep_cmd and fps settings + +**Dependencies** +- Bump third-party/wayland-protocols from 681c33c to 46f201b +- Bump third-party/nv-codec-headers from 9402b5a to 22441b5 +- Bump third-party/nanors from 395e5ad to e9e242e +- Bump third-party/Simple-Web-Server from 2f29926 to 27b41f5 +- Bump ffmpeg +- Bump third-party/tray from 2664388 to 2bf1c61 +- Bump actions/setup-python from 4 to 5 +- Bump actions/upload-artifact from 3 to 4 +- Bump @fortawesome/fontawesome-free from 6.4.2 to 6.5.1 +- Bump babel from 2.13.0 to 2.14.0 +- Move miniupnpc from submodule to system installed package +- Bump furo from 2023.9.10 to 2024.1.29 +- Bump third-party/moonlight-common-c from f78f213 to cbd0ec1 +- Bump third-party/ViGEmClient from 1920260 to 8d71f67 +- Bump peter-evans/create-pull-request from 5 to 6 +- Bump bootstrap from 5.3.2 to 5.3.3 + +**Misc** +- (Build) Update global workflows +- (Docs/Linux) Add example for setting custom resolution with NVIDIA +- (Docs) Fix broken links +- (Docs/Windows) Add information about disk permissions +- (Docs) Fix failing images +- (Docs) Use glob pattern to match source code docs +- (CI/macOS) Install boost from source +- (Docs) Add reset credentials examples for unique packages +- (Docs) Refactor and general cleanup +- (Docs) Cross-reference config settings to the UI +- (Docs/Docker) Add podman notes +- (Build) Use CMAKE_SOURCE_DIR property everywhere +- (Build/Docker) Add docker toolchain file for CLion +- (macOS) Various code style fixes +- (Deps) Alphabetize git submodules +- (Docs/Examples) Update URI examples +- (Refactor) Refactored some code in preparation for unit testing implementation +- (CMake) Add option to skip cuda inheriting compile options +- (CMake) Add option to error build on warnings + ## [0.21.0] - 2023-10-15 **Added** - (Input) Add support for automatically selecting the emulated controller type based on the physical controller connected to the client @@ -592,3 +718,4 @@ settings. In v0.17.0, games now run under your user account without elevated pri [0.19.1]: https://github.com/LizardByte/Sunshine/releases/tag/v0.19.1 [0.20.0]: https://github.com/LizardByte/Sunshine/releases/tag/v0.20.0 [0.21.0]: https://github.com/LizardByte/Sunshine/releases/tag/v0.21.0 +[0.22.0]: https://github.com/LizardByte/Sunshine/releases/tag/v0.22.0 diff --git a/CMakeLists.txt b/CMakeLists.txt index 53d8bde2881..d672c9b8ae8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,7 +3,7 @@ cmake_minimum_required(VERSION 3.18) # todo - set this conditionally # todo - set version to 0.0.0 once confident in automated versioning -project(Sunshine VERSION 0.21.0 +project(Sunshine VERSION 0.22.0 DESCRIPTION "Sunshine is a self-hosted game stream host for Moonlight." HOMEPAGE_URL "https://app.lizardbyte.dev/Sunshine") From 529f1b84f8a2c665a4442f72ec16aa86fbd8cc77 Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Sun, 3 Mar 2024 18:37:06 -0600 Subject: [PATCH 084/182] Fix CUDA context leak causing encoder init failures using X11 capture with NVENC --- CHANGELOG.md | 3 ++- src/platform/linux/cuda.cu | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 037395640fa..5f8290545da 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -50,7 +50,7 @@ - (Capture/Linux) Fix missing mouse cursor when using KMS capture on a GPU with hardware cursor support - (Capture/Windows) Add workaround for Nvidia driver bug causing Sunshine to crash when RTX HDR is globally enabled - (Capture/Windows) Add workaround for AMD driver bug on pre-RDNA GPUs causing hardware encoding failure -- (Capture/Windows) Reintroduce support for encoding on older Nvidia GPU drivers (v456.71-v522.25) +- (Capture/Windows) Reintroduce support for NVENC on older Nvidia GPU drivers (v456.71-v522.25) - (Capture/Windows) Fix encoding on old Intel GPUs that don't support low-power H.264 encoding - (Capture/Linux) Fix GL errors or corrupt video output on GPUs that use aux planes such as Intel Arc - (Capture/Linux) Fix GL errors or corrupt video output on GPUs that use DRM modifiers on YUV buffers @@ -68,6 +68,7 @@ - (Capture/Windows) Fix delay displaying UAC dialogs when the mouse cursor is not moving - (Capture/Linux) Fix corrupt video output or stream disconnections if the display resolution changes while streaming - (Capture/Linux) Fix color of aspect ratio padding in the capture image with VAAPI +- (Capture/Linux) Fix NVENC initialization error when using X11 capture with some GPUs - (Tray/Linux) Fix random crash when the tray icon is updating - (Network) Fix QoS tagging when running in IPv4+IPv6 mode - (Process) Fix termination of child processes upon app quit when the parent has already terminated diff --git a/src/platform/linux/cuda.cu b/src/platform/linux/cuda.cu index 863e3f944fe..8fb1a5ee2d6 100644 --- a/src/platform/linux/cuda.cu +++ b/src/platform/linux/cuda.cu @@ -222,7 +222,7 @@ std::optional tex_t::make(int height, int pitch) { return tex; } -tex_t::tex_t() : array {}, texture { INVALID_TEXTURE } {} +tex_t::tex_t() : array {}, texture { INVALID_TEXTURE, INVALID_TEXTURE } {} tex_t::tex_t(tex_t &&other) : array { other.array }, texture { other.texture } { other.array = 0; other.texture.point = INVALID_TEXTURE; From cacadc4df426e42273bcf4ad37758220faf2201f Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Mon, 4 Mar 2024 22:15:37 -0500 Subject: [PATCH 085/182] build(linux): ensure pre-compiled arch pkg is not debug build (#2214) --- docker/archlinux.dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/docker/archlinux.dockerfile b/docker/archlinux.dockerfile index 15cb5d23685..bd853ad395d 100644 --- a/docker/archlinux.dockerfile +++ b/docker/archlinux.dockerfile @@ -87,6 +87,7 @@ RUN <<_PKGBUILD set -e namcap -i PKGBUILD makepkg -si --noconfirm +rm -f /build/sunshine/pkg/sunshine-debug*.pkg.tar.zst ls -a _PKGBUILD From 9f94eebd32f148120389ebd3246829ee7e7bd66e Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Mon, 4 Mar 2024 18:43:16 -0600 Subject: [PATCH 086/182] Fix mismatched case and unhandled exception in open_drm_fd_for_cuda_device() --- src/platform/linux/cuda.cpp | 43 ++++++++++++++++++++++--------------- 1 file changed, 26 insertions(+), 17 deletions(-) diff --git a/src/platform/linux/cuda.cpp b/src/platform/linux/cuda.cpp index 5b121c70691..33c939243f2 100644 --- a/src/platform/linux/cuda.cpp +++ b/src/platform/linux/cuda.cpp @@ -247,29 +247,38 @@ namespace cuda { // There's no way to directly go from CUDA to a DRM device, so we'll // use sysfs to look up the DRM device name from the PCI ID. - char pci_bus_id[13]; - CU_CHECK(cdf->cuDeviceGetPCIBusId(pci_bus_id, sizeof(pci_bus_id), device), "Couldn't get CUDA device PCI bus ID"); - BOOST_LOG(debug) << "Found CUDA device with PCI bus ID: "sv << pci_bus_id; + std::array pci_bus_id; + CU_CHECK(cdf->cuDeviceGetPCIBusId(pci_bus_id.data(), pci_bus_id.size(), device), "Couldn't get CUDA device PCI bus ID"); + BOOST_LOG(debug) << "Found CUDA device with PCI bus ID: "sv << pci_bus_id.data(); + + // Linux uses lowercase hexadecimal while CUDA uses uppercase + std::transform(pci_bus_id.begin(), pci_bus_id.end(), pci_bus_id.begin(), + [](char c) { return std::tolower(c); }); // Look for the name of the primary node in sysfs - char sysfs_path[PATH_MAX]; - std::snprintf(sysfs_path, sizeof(sysfs_path), "/sys/bus/pci/devices/%s/drm", pci_bus_id); - fs::path sysfs_dir { sysfs_path }; - for (auto &entry : fs::directory_iterator { sysfs_dir }) { - auto file = entry.path().filename(); - auto filestring = file.generic_u8string(); - if (std::string_view { filestring }.substr(0, 4) != "card"sv) { - continue; - } + try { + char sysfs_path[PATH_MAX]; + std::snprintf(sysfs_path, sizeof(sysfs_path), "/sys/bus/pci/devices/%s/drm", pci_bus_id.data()); + fs::path sysfs_dir { sysfs_path }; + for (auto &entry : fs::directory_iterator { sysfs_dir }) { + auto file = entry.path().filename(); + auto filestring = file.generic_u8string(); + if (std::string_view { filestring }.substr(0, 4) != "card"sv) { + continue; + } - BOOST_LOG(debug) << "Found DRM primary node: "sv << filestring; + BOOST_LOG(debug) << "Found DRM primary node: "sv << filestring; - fs::path dri_path { "/dev/dri"sv }; - auto device_path = dri_path / file; - return open(device_path.c_str(), O_RDWR); + fs::path dri_path { "/dev/dri"sv }; + auto device_path = dri_path / file; + return open(device_path.c_str(), O_RDWR); + } + } + catch (const std::filesystem::filesystem_error &err) { + BOOST_LOG(error) << "Failed to read sysfs: "sv << err.what(); } - BOOST_LOG(error) << "Unable to find DRM device with PCI bus ID: "sv << pci_bus_id; + BOOST_LOG(error) << "Unable to find DRM device with PCI bus ID: "sv << pci_bus_id.data(); return -1; } From 4ebc7b5cef0884cae5076a8f79d96a3859bea9e8 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Tue, 5 Mar 2024 08:56:09 -0500 Subject: [PATCH 087/182] build(macos): add build strategy matrix (#2211) --- .github/workflows/CI.yml | 69 ++++++++++++++++++++++++---------- README.rst | 4 +- docs/source/about/setup.rst | 4 +- docs/source/building/macos.rst | 20 ++++++++-- src/main.cpp | 3 ++ 5 files changed, 73 insertions(+), 27 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 60f555c2166..06d5a5e5d02 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -506,11 +506,23 @@ jobs: prerelease: ${{ needs.setup_release.outputs.pre_release }} build_mac: - name: MacOS - runs-on: macos-11 needs: [check_changelog, setup_release] env: BOOST_VERSION: 1.83.0 + strategy: + fail-fast: false # false to test all, true to fail entire job if any fail + matrix: + include: + # https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners/about-github-hosted-runners#standard-github-hosted-runners-for-public-repositories + # while GitHub has larger macOS runners, they are not available for our repos :( + - os_version: "12" + arch: "x86_64" + - os_version: "13" + arch: "x86_64" + - os_version: "14" + arch: "arm64" + name: macOS-${{ matrix.os_version }} ${{ matrix.arch }} + runs-on: macos-${{ matrix.os_version }} steps: - name: Checkout @@ -520,24 +532,32 @@ jobs: - name: Setup Dependencies MacOS run: | + if [[ ${{ matrix.arch }} == "arm64" ]]; then + brew_prefix="/opt/homebrew" + else + brew_prefix="/usr/local" + fi + # install dependencies using homebrew brew install cmake curl miniupnpc node openssl opus pkg-config # fix openssl header not found - # ln -sf /usr/local/opt/openssl/include/openssl /usr/local/include/openssl - - # by installing boost from source, several headers cannot be found... - # the above commented out link only works if boost is installed from homebrew... does not make sense - ln -sf $(find /usr/local/Cellar -type d -name "openssl" -path "*/openssl@3/*/include" | head -n 1) \ - /usr/local/include/openssl + openssl_path=$(find ${brew_prefix}/Cellar -type d -name "openssl" -path "*/openssl@3/*/include" | head -n 1) + echo "OpenSSL path: $openssl_path" + ln -sf $openssl_path ${brew_prefix}/include/openssl + ls -l ${brew_prefix}/include/openssl # fix opus header not found - ln -sf $(find /usr/local/Cellar -type d -name "opus" -path "*/opus/*/include" | head -n 1) \ - /usr/local/include/opus + opus_path=$(find ${brew_prefix}/Cellar -type d -name "opus" -path "*/opus/*/include" | head -n 1) + echo "Opus path: $opus_path" + ln -sf $opus_path ${brew_prefix}/include/opus + ls -l ${brew_prefix}/include/opus # fix miniupnpc header not found - ln -sf $(find /usr/local/Cellar -type d -name "miniupnpc" -path "*/miniupnpc/*/include" | head -n 1) \ - /usr/local/include/miniupnpc + upnp_path=$(find ${brew_prefix}/Cellar -type d -name "miniupnpc" -path "*/miniupnpc/*/include" | head -n 1) + echo "Miniupnpc path: $upnp_path" + ln -sf $upnp_path ${brew_prefix}/include/miniupnpc + ls -l ${brew_prefix}/include/miniupnpc - name: Install Boost # installing boost from homebrew takes 30 minutes in a GitHub runner @@ -594,15 +614,13 @@ jobs: # package cpack -G DragNDrop - mv ./cpack_artifacts/Sunshine.dmg ../artifacts/sunshine.dmg - - # cpack -G Bundle - # mv ./cpack_artifacts/Sunshine.dmg ../artifacts/sunshine-bundle.dmg + mv ./cpack_artifacts/Sunshine.dmg \ + ../artifacts/sunshine-macos-${{ matrix.os_version }}-${{ matrix.arch }}.dmg - name: Upload Artifacts uses: actions/upload-artifact@v4 with: - name: sunshine-macos + name: sunshine-macos-${{ matrix.os_version }}-${{ matrix.arch }} path: artifacts/ - name: Create/Update GitHub Release @@ -620,9 +638,19 @@ jobs: prerelease: ${{ needs.setup_release.outputs.pre_release }} build_mac_port: - name: Macports needs: [check_changelog, setup_release] - runs-on: macos-11 + strategy: + fail-fast: false # false to test all, true to fail entire job if any fail + matrix: + include: + # https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners/about-github-hosted-runners#standard-github-hosted-runners-for-public-repositories + # while GitHub has larger macOS runners, they are not available for our repos :( + - os_version: "12" + release: true + - os_version: "13" + - os_version: "14" + name: Macports (macOS-${{ matrix.os_version }}) + runs-on: macos-${{ matrix.os_version }} steps: - name: Checkout @@ -725,13 +753,14 @@ jobs: echo "::endgroup::" - name: Upload Artifacts + if: ${{ matrix.release == 'true' }} uses: actions/upload-artifact@v4 with: name: sunshine-macports path: artifacts/ - name: Create/Update GitHub Release - if: ${{ needs.setup_release.outputs.create_release == 'true' }} + if: ${{ needs.setup_release.outputs.create_release == 'true' && matrix.release == 'true' }} uses: ncipollo/release-action@v1 with: name: ${{ needs.setup_release.outputs.release_name }} diff --git a/README.rst b/README.rst index fbc78658668..11508d976e3 100644 --- a/README.rst +++ b/README.rst @@ -32,11 +32,11 @@ System Requirements +------------+------------------------------------------------------------+ | OS | Windows: 10+ (Windows Server not supported) | | +------------------------------------------------------------+ -| | macOS: 11.7+ | +| | macOS: 12+ | | +------------------------------------------------------------+ | | Linux/Debian: 11 (bullseye) | | +------------------------------------------------------------+ -| | Linux/Fedora: 37+ | +| | Linux/Fedora: 38+ | | +------------------------------------------------------------+ | | Linux/Ubuntu: 20.04+ (focal) | +------------+------------------------------------------------------------+ diff --git a/docs/source/about/setup.rst b/docs/source/about/setup.rst index 8a280fcf644..2457ccc59e9 100644 --- a/docs/source/about/setup.rst +++ b/docs/source/about/setup.rst @@ -281,14 +281,14 @@ Install .. tab:: macOS - .. important:: Sunshine on macOS is experimental. Gamepads do not work. Other features may not work as expected. + .. important:: Sunshine on macOS is experimental. Gamepads do not work. .. tab:: dmg .. warning:: The `dmg` does not include runtime dependencies. This package is not recommended for most users. No support will be provided! - #. Download the ``sunshine.dmg`` file and install it. + #. Download the ``sunshine--.dmg`` file and install it. Uninstall: .. code-block:: bash diff --git a/docs/source/building/macos.rst b/docs/source/building/macos.rst index c14751c28f8..bf96fb394f9 100644 --- a/docs/source/building/macos.rst +++ b/docs/source/building/macos.rst @@ -20,9 +20,23 @@ Install Requirements .. code-block:: bash brew install boost cmake miniupnpc node opus pkg-config - # if there are issues with an SSL header that is not found: - cd /usr/local/include - ln -s ../opt/openssl/include/openssl . + +If there are issues with an SSL header that is not found: + .. tab:: Intel + + .. code-block:: bash + + pushd /usr/local/include + ln -s ../opt/openssl/include/openssl . + popd + + .. tab:: Apple Silicon + + .. code-block:: bash + + pushd /opt/homebrew/include + ln -s ../opt/openssl/include/openssl . + popd Build ----- diff --git a/src/main.cpp b/src/main.cpp index 5cc28f9f2a2..a3901448680 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -106,8 +106,11 @@ main(int argc, char *argv[]) { setlocale(LC_ALL, ".UTF-8"); #endif +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" // Use UTF-8 conversion for the default C++ locale (used by boost::log) std::locale::global(std::locale(std::locale(), new std::codecvt_utf8)); +#pragma GCC diagnostic pop mail::man = std::make_shared(); From b99a9e92becb5e2b56e0555093d81b8aa8712f6e Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Tue, 5 Mar 2024 18:18:17 -0500 Subject: [PATCH 088/182] build(macos): fix publishing of portfile (#2220) --- .github/workflows/CI.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 06d5a5e5d02..c2d97440e22 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -753,14 +753,14 @@ jobs: echo "::endgroup::" - name: Upload Artifacts - if: ${{ matrix.release == 'true' }} + if: ${{ matrix.release }} uses: actions/upload-artifact@v4 with: name: sunshine-macports path: artifacts/ - name: Create/Update GitHub Release - if: ${{ needs.setup_release.outputs.create_release == 'true' && matrix.release == 'true' }} + if: ${{ needs.setup_release.outputs.create_release == 'true' && matrix.release }} uses: ncipollo/release-action@v1 with: name: ${{ needs.setup_release.outputs.release_name }} From 3f215968ad61864063b1a84025e7ce74f6c1e553 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Tue, 5 Mar 2024 23:00:51 -0500 Subject: [PATCH 089/182] fix(config): add missing resolution to default config ui (#2224) --- src_assets/common/assets/web/config.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src_assets/common/assets/web/config.html b/src_assets/common/assets/web/config.html index a072e101ba0..e7d47e4d852 100644 --- a/src_assets/common/assets/web/config.html +++ b/src_assets/common/assets/web/config.html @@ -1234,7 +1234,7 @@

"install_steam_audio_drivers": "enabled", "adapter_name": "", "output_name": "", - "resolutions": "[352x240,480x360,858x480,1280x720,1920x1080,2560x1080,3440x1440,1920x1200,3840x2160,3840x1600]", + "resolutions": "[352x240,480x360,858x480,1280x720,1920x1080,2560x1080,2560x1440,3440x1440,1920x1200,3840x2160,3840x1600]", "fps": "[10,30,60,90,120]", }, }, From 9e299c295dc94ca6f1d813f1b974e40de901f369 Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Tue, 5 Mar 2024 22:32:06 -0600 Subject: [PATCH 090/182] Fix predefined FPS values not taking effect --- src/config.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/config.cpp b/src/config.cpp index ba2718aca7f..21562f83487 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -849,6 +849,11 @@ namespace config { std::vector list; list_string_f(vars, name, list); + // check if list is empty, i.e. when the value doesn't exist in the config file + if (list.empty()) { + return; + } + // The framerate list must be cleared before adding values from the file configuration. // If the list is not cleared, then the specified parameters do not affect the behavior of the sunshine server. // That is, if you set only 30 fps in the configuration file, it will not work because by default, during initialization the list includes 10, 30, 60, 90 and 120 fps. From c86a4e112be4d1a396e561619ea78f1d01a407b0 Mon Sep 17 00:00:00 2001 From: detiam <44510779+detiam@users.noreply.github.com> Date: Wed, 6 Mar 2024 23:23:32 +0800 Subject: [PATCH 091/182] Fix wrong path in desktop file (#2223) Co-authored-by: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> --- cmake/packaging/unix.cmake | 2 -- cmake/prep/special_package_configuration.cmake | 2 ++ packaging/linux/flatpak/sunshine.desktop | 4 ++-- packaging/linux/sunshine.desktop | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/cmake/packaging/unix.cmake b/cmake/packaging/unix.cmake index d6a4ae93575..bacbfc910de 100644 --- a/cmake/packaging/unix.cmake +++ b/cmake/packaging/unix.cmake @@ -1,8 +1,6 @@ # unix specific packaging # put anything here that applies to both linux and macos -include(GNUInstallDirs) - # return here if building a macos package if(SUNSHINE_PACKAGE_MACOS) return() diff --git a/cmake/prep/special_package_configuration.cmake b/cmake/prep/special_package_configuration.cmake index 3094c2399a5..a5a780f5563 100644 --- a/cmake/prep/special_package_configuration.cmake +++ b/cmake/prep/special_package_configuration.cmake @@ -3,6 +3,8 @@ if (APPLE) configure_file(packaging/macos/Portfile Portfile @ONLY) endif() elseif (UNIX) + include(GNUInstallDirs) # this needs to be included prior to configuring the desktop files + # configure the .desktop file if(${SUNSHINE_BUILD_APPIMAGE}) configure_file(packaging/linux/AppImage/sunshine.desktop sunshine.desktop @ONLY) diff --git a/packaging/linux/flatpak/sunshine.desktop b/packaging/linux/flatpak/sunshine.desktop index be702701e08..1c5fe13a409 100644 --- a/packaging/linux/flatpak/sunshine.desktop +++ b/packaging/linux/flatpak/sunshine.desktop @@ -12,9 +12,9 @@ Actions=RunInTerminal;KMS; [Desktop Action RunInTerminal] Name=Run in Terminal Icon=application-x-executable -Exec=gio launch @CMAKE_INSTALL_DATAROOTDIR@/applications/sunshine_terminal.desktop +Exec=gio launch @CMAKE_INSTALL_FULL_DATAROOTDIR@/applications/sunshine_terminal.desktop [Desktop Action KMS] Name=Run in Terminal (KMS) Icon=application-x-executable -Exec=gio launch @CMAKE_INSTALL_DATAROOTDIR@/applications/sunshine_kms.desktop +Exec=gio launch @CMAKE_INSTALL_FULL_DATAROOTDIR@/applications/sunshine_kms.desktop diff --git a/packaging/linux/sunshine.desktop b/packaging/linux/sunshine.desktop index b0f2ce327ec..719555301d5 100644 --- a/packaging/linux/sunshine.desktop +++ b/packaging/linux/sunshine.desktop @@ -12,4 +12,4 @@ Actions=RunInTerminal; [Desktop Action RunInTerminal] Name=Run in Terminal Icon=application-x-executable -Exec=gio launch @CMAKE_INSTALL_DATAROOTDIR@/applications/sunshine_terminal.desktop +Exec=gio launch @CMAKE_INSTALL_FULL_DATAROOTDIR@/applications/sunshine_terminal.desktop From 3b3e6818f31858eaa6413c8e832dc803eeb48157 Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Tue, 5 Mar 2024 22:57:28 -0600 Subject: [PATCH 092/182] Make debuginfo artifacts harder to confuse with the Windows portable build --- .github/workflows/CI.yml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index c2d97440e22..6b1eb296b47 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -843,11 +843,15 @@ jobs: - name: Package Windows Debug Info working-directory: build run: | - # save the original binaries with debug info + # use .dbg file extension for binaries to avoid confusion with real packages + Get-ChildItem -File -Recurse | ` + % { Rename-Item -Path $_.PSPath -NewName $_.Name.Replace(".exe",".dbg") } + + # save the binaries with debug info 7z -r ` "-xr!CMakeFiles" ` "-xr!cpack_artifacts" ` - a "../artifacts/sunshine-debuginfo-win32.zip" "*.exe" + a "../artifacts/sunshine-win32-debuginfo.7z" "*.dbg" - name: Upload Artifacts uses: actions/upload-artifact@v4 From 6aeaaf5ec9df4c278c1a8126a2dfbc748d000d76 Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Wed, 6 Mar 2024 19:18:12 -0600 Subject: [PATCH 093/182] Fix process tree tracking when the cmd.exe trampoline is used --- src/platform/windows/misc.cpp | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/platform/windows/misc.cpp b/src/platform/windows/misc.cpp index 77de69f4cd3..98065e3201a 100644 --- a/src/platform/windows/misc.cpp +++ b/src/platform/windows/misc.cpp @@ -681,10 +681,11 @@ namespace platf { * @param raw_cmd The raw command provided by the user. * @param working_dir The working directory for the new process. * @param token The user token currently being impersonated or `NULL` if running as ourselves. + * @param creation_flags The creation flags for CreateProcess(), which may be modified by this function. * @return A command string suitable for use by CreateProcess(). */ std::wstring - resolve_command_string(const std::string &raw_cmd, const std::wstring &working_dir, HANDLE token) { + resolve_command_string(const std::string &raw_cmd, const std::wstring &working_dir, HANDLE token, DWORD &creation_flags) { std::wstring raw_cmd_w = from_utf8(raw_cmd); // First, convert the given command into parts so we can get the executable/file/URL without parameters @@ -757,8 +758,13 @@ namespace platf { // FIXME: Maybe we can improve this in the future. if (res == HRESULT_FROM_WIN32(ERROR_NO_ASSOCIATION)) { BOOST_LOG(warning) << "Using trampoline to handle target: "sv << raw_cmd; - std::wcscpy(shell_command_string.data(), L"cmd.exe /c start \"\" \"%1\" %*"); + std::wcscpy(shell_command_string.data(), L"cmd.exe /c start \"\" /wait \"%1\" %*"); needs_cmd_escaping = true; + + // We must suppress the console window that would otherwise appear when starting cmd.exe. + creation_flags &= ~CREATE_NEW_CONSOLE; + creation_flags |= CREATE_NO_WINDOW; + res = S_OK; } @@ -951,7 +957,7 @@ namespace platf { // Open the process as the current user account, elevation is handled in the token itself. ec = impersonate_current_user(user_token, [&]() { std::wstring env_block = create_environment_block(cloned_env); - std::wstring wcmd = resolve_command_string(cmd, start_dir, user_token); + std::wstring wcmd = resolve_command_string(cmd, start_dir, user_token, creation_flags); ret = CreateProcessAsUserW(user_token, NULL, (LPWSTR) wcmd.c_str(), @@ -985,7 +991,7 @@ namespace platf { } std::wstring env_block = create_environment_block(cloned_env); - std::wstring wcmd = resolve_command_string(cmd, start_dir, NULL); + std::wstring wcmd = resolve_command_string(cmd, start_dir, NULL, creation_flags); ret = CreateProcessW(NULL, (LPWSTR) wcmd.c_str(), NULL, From 972e5d2b145d2e927f4f94c3dc36b777f9d1b650 Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Wed, 6 Mar 2024 19:45:49 -0600 Subject: [PATCH 094/182] Strip quotes out of the working directory path --- src/process.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/process.cpp b/src/process.cpp index 804291577c2..89dc4dc5ae5 100644 --- a/src/process.cpp +++ b/src/process.cpp @@ -664,6 +664,12 @@ namespace proc { if (working_dir) { ctx.working_dir = parse_env_val(this_env, *working_dir); +#ifdef _WIN32 + // The working directory, unlike the command itself, should not be quoted + // when it contains spaces. Unlike POSIX, Windows forbids quotes in paths, + // so we can safely strip them all out here to avoid confusing the user. + boost::erase_all(ctx.working_dir, "\""); +#endif } if (image_path) { From 06c0ed1d1cb5a80c6f0a6fe8d7a3fd8a77580adc Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Wed, 6 Mar 2024 20:54:27 -0600 Subject: [PATCH 095/182] Temporarily add the working directory to our path when starting an app CreateProcess() doesn't search in the child's specified working directory by default. --- src/platform/windows/misc.cpp | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/platform/windows/misc.cpp b/src/platform/windows/misc.cpp index 98065e3201a..e7bb64e52b8 100644 --- a/src/platform/windows/misc.cpp +++ b/src/platform/windows/misc.cpp @@ -933,6 +933,31 @@ namespace platf { // Create a new console for interactive processes and use no console for non-interactive processes creation_flags |= interactive ? CREATE_NEW_CONSOLE : CREATE_NO_WINDOW; + // Find the PATH variable in our environment block using a case-insensitive search + auto sunshine_wenv = boost::this_process::wenvironment(); + std::wstring path_var_name { L"PATH" }; + std::wstring old_path_val; + auto itr = std::find_if(sunshine_wenv.cbegin(), sunshine_wenv.cend(), [&](const auto &e) { return boost::iequals(e.get_name(), path_var_name); }); + if (itr != sunshine_wenv.cend()) { + // Use the existing variable if it exists, since Boost treats these as case-sensitive. + path_var_name = itr->get_name(); + old_path_val = sunshine_wenv[path_var_name].to_string(); + } + + // Temporarily prepend the specified working directory to PATH to ensure CreateProcess() + // will (preferentially) find binaries that reside in the working directory. + sunshine_wenv[path_var_name].assign(start_dir + L";" + old_path_val); + + // Restore the old PATH value for our process when we're done here + auto restore_path = util::fail_guard([&]() { + if (old_path_val.empty()) { + sunshine_wenv[path_var_name].clear(); + } + else { + sunshine_wenv[path_var_name].assign(old_path_val); + } + }); + BOOL ret; if (is_running_as_system()) { // Duplicate the current user's token From f5dd0d4eafbd34a540c77c73c9cab1d05b99b0f8 Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Wed, 6 Mar 2024 20:55:00 -0600 Subject: [PATCH 096/182] Update app examples to clarify new command syntax for Windows --- docs/source/about/guides/app_examples.rst | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/source/about/guides/app_examples.rst b/docs/source/about/guides/app_examples.rst index ca834e4a4f2..8bca2bd89f0 100644 --- a/docs/source/about/guides/app_examples.rst +++ b/docs/source/about/guides/app_examples.rst @@ -6,6 +6,8 @@ and applications to Sunshine. .. attention:: Throughout these examples, any fields not shown are left blank. You can enhance your experience by adding an image or a log file (via the ``Output`` field). +.. note:: When a working directory is not specified, it defaults to the folder where the target application resides. + Common Examples --------------- @@ -24,7 +26,7 @@ Steam Big Picture ^^^^^^^^^^^^^^^^^ .. note:: Steam is launched as a detached command because Steam starts with a process that self updates itself and the original - process is killed. Since the original process ends it will not work as a regular command. + process is killed. .. tab:: Linux @@ -51,7 +53,7 @@ Steam Big Picture +----------------------+-----------------------------+ | Application Name | ``Steam Big Picture`` | +----------------------+-----------------------------+ - | Detached Commands | ``steam://open/bigpicture`` | + | Command | ``steam://open/bigpicture`` | +----------------------+-----------------------------+ | Image | ``steam.png`` | +----------------------+-----------------------------+ @@ -59,8 +61,7 @@ Steam Big Picture Epic Game Store game ^^^^^^^^^^^^^^^^^^^^ -.. note:: Using URI method will be the most consistent between various games, but does not allow a game to be launched - using the "Command" and therefore the stream will not end when the game ends. +.. note:: Using URI method will be the most consistent between various games. URI (Epic) """""""""" @@ -70,7 +71,7 @@ URI (Epic) +----------------------+--------------------------------------------------------------------------------------------------------------------------------------------+ | Application Name | ``Surviving Mars`` | +----------------------+--------------------------------------------------------------------------------------------------------------------------------------------+ - | Detached Commands | ``com.epicgames.launcher://apps/d759128018124dcabb1fbee9bb28e178%3A20729b9176c241f0b617c5723e70ec2d%3AOvenbird?action=launch&silent=true`` | + | Command | ``com.epicgames.launcher://apps/d759128018124dcabb1fbee9bb28e178%3A20729b9176c241f0b617c5723e70ec2d%3AOvenbird?action=launch&silent=true`` | +----------------------+--------------------------------------------------------------------------------------------------------------------------------------------+ Binary (Epic w/ working directory) @@ -81,7 +82,7 @@ Binary (Epic w/ working directory) +----------------------+-----------------------------------------------+ | Application Name | ``Surviving Mars`` | +----------------------+-----------------------------------------------+ - | Command | ``cmd /c "MarsEpic.exe"`` | + | Command | ``MarsEpic.exe`` | +----------------------+-----------------------------------------------+ | Working Directory | ``C:\Program Files\Epic Games\SurvivingMars`` | +----------------------+-----------------------------------------------+ @@ -100,8 +101,7 @@ Binary (Epic w/o working directory) Steam game ^^^^^^^^^^ -.. note:: Using URI method will be the most consistent between various games, but does not allow a game to be launched - using the "Command" and therefore the stream will not end when the game ends. +.. note:: Using URI method will be the most consistent between various games. URI (Steam) """"""""""" @@ -127,7 +127,7 @@ URI (Steam) +----------------------+------------------------------+ | Application Name | ``Surviving Mars`` | +----------------------+------------------------------+ - | Detached Commands | ``steam://rungameid/464920`` | + | Command | ``steam://rungameid/464920`` | +----------------------+------------------------------+ Binary (Steam w/ working directory) From 7cdd156bcecda18d83237b7b8ff537ac10a8c0ba Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Thu, 7 Mar 2024 00:59:40 -0600 Subject: [PATCH 097/182] Fix heap corruption with cursor pixel counts that aren't divisible by 8 --- src/platform/windows/display_vram.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/platform/windows/display_vram.cpp b/src/platform/windows/display_vram.cpp index a56869eab40..1baa1282bb9 100644 --- a/src/platform/windows/display_vram.cpp +++ b/src/platform/windows/display_vram.cpp @@ -234,7 +234,7 @@ namespace platf::dxgi { auto xor_mask = std::begin(img_data) + bytes; for (auto x = 0; x < bytes; ++x) { - for (auto c = 7; c >= 0; --c) { + for (auto c = 7; c >= 0 && ((std::uint8_t *) pixel_data) != std::end(cursor_img); --c) { auto bit = 1 << c; auto color_type = ((*and_mask & bit) ? 1 : 0) + ((*xor_mask & bit) ? 2 : 0); @@ -307,7 +307,7 @@ namespace platf::dxgi { auto xor_mask = std::begin(img_data) + bytes; for (auto x = 0; x < bytes; ++x) { - for (auto c = 7; c >= 0; --c) { + for (auto c = 7; c >= 0 && ((std::uint8_t *) pixel_data) != std::end(cursor_img); --c) { auto bit = 1 << c; auto color_type = ((*and_mask & bit) ? 1 : 0) + ((*xor_mask & bit) ? 2 : 0); From ce3b62598317caf605385e1a9061b95edf889196 Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Fri, 8 Mar 2024 17:26:43 -0600 Subject: [PATCH 098/182] Fix undefined behavior when computing cursor end pointer --- src/platform/linux/kmsgrab.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/platform/linux/kmsgrab.cpp b/src/platform/linux/kmsgrab.cpp index cd622fa775c..a7a3256d433 100644 --- a/src/platform/linux/kmsgrab.cpp +++ b/src/platform/linux/kmsgrab.cpp @@ -1240,8 +1240,13 @@ namespace platf { auto delta_width = std::min(captured_cursor.src_w, std::max(0, screen_width - cursor_x)) - cursor_delta_x; for (auto y = 0; y < delta_height; ++y) { // Offset into the cursor image to skip drawing the parts of the cursor image that are off screen - auto cursor_begin = (uint32_t *) &captured_cursor.pixels[((y + cursor_delta_y) * captured_cursor.src_w + cursor_delta_x) * 4]; - auto cursor_end = (uint32_t *) &captured_cursor.pixels[((y + cursor_delta_y) * captured_cursor.src_w + delta_width + cursor_delta_x) * 4]; + // + // NB: We must access the elements via the data() function because cursor_end may point to the + // the first element beyond the valid range of the vector. Using vector's [] operator in that + // manner is undefined behavior (and triggers errors when using debug libc++), while doing the + // same with an array is fine. + auto cursor_begin = (uint32_t *) &captured_cursor.pixels.data()[((y + cursor_delta_y) * captured_cursor.src_w + cursor_delta_x) * 4]; + auto cursor_end = (uint32_t *) &captured_cursor.pixels.data()[((y + cursor_delta_y) * captured_cursor.src_w + delta_width + cursor_delta_x) * 4]; auto pixels_begin = &pixels[(y + cursor_y) * (img.row_pitch / img.pixel_pitch) + cursor_x]; From 33e99e1feb02299ac907c12fedf9d8d36cae775c Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sat, 9 Mar 2024 10:47:55 -0500 Subject: [PATCH 099/182] build(macos)!: add homebrew formula and drop dmg (#2222) --- .github/workflows/CI.yml | 155 +++++++----------- CMakeLists.txt | 2 +- cmake/prep/options.cmake | 8 + .../prep/special_package_configuration.cmake | 3 + cmake/targets/common.cmake | 13 +- docs/source/about/setup.rst | 13 +- packaging/macos/sunshine.rb | 62 +++++++ src_assets/macos/misc/uninstall_pkg.sh | 3 + vite.config.js | 17 +- 9 files changed, 162 insertions(+), 114 deletions(-) create mode 100644 packaging/macos/sunshine.rb diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 6b1eb296b47..f2529e7390e 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -505,10 +505,8 @@ jobs: discussionCategory: announcements prerelease: ${{ needs.setup_release.outputs.pre_release }} - build_mac: + build_mac_brew: needs: [check_changelog, setup_release] - env: - BOOST_VERSION: 1.83.0 strategy: fail-fast: false # false to test all, true to fail entire job if any fail matrix: @@ -516,126 +514,87 @@ jobs: # https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners/about-github-hosted-runners#standard-github-hosted-runners-for-public-repositories # while GitHub has larger macOS runners, they are not available for our repos :( - os_version: "12" - arch: "x86_64" + release: true - os_version: "13" - arch: "x86_64" - os_version: "14" - arch: "arm64" - name: macOS-${{ matrix.os_version }} ${{ matrix.arch }} + name: Homebrew (macOS-${{ matrix.os_version }}) runs-on: macos-${{ matrix.os_version }} steps: - name: Checkout uses: actions/checkout@v4 - with: - submodules: recursive - - name: Setup Dependencies MacOS + - name: Setup Dependencies Homebrew + run: | + # install dependencies using homebrew + brew install cmake + + - name: Configure formula run: | - if [[ ${{ matrix.arch }} == "arm64" ]]; then - brew_prefix="/opt/homebrew" + # variables for formula + branch=${GITHUB_HEAD_REF} + + # check the branch variable + if [ -z "$branch" ] + then + echo "This is a PUSH event" + clone_url=${{ github.event.repository.clone_url }} + branch="${{ github.ref_name }}" else - brew_prefix="/usr/local" + echo "This is a PR event" + clone_url=${{ github.event.pull_request.head.repo.clone_url }} + branch="${{ github.event.pull_request.head.ref }}" fi + echo "Branch: ${branch}" + echo "Clone URL: ${clone_url}" - # install dependencies using homebrew - brew install cmake curl miniupnpc node openssl opus pkg-config - - # fix openssl header not found - openssl_path=$(find ${brew_prefix}/Cellar -type d -name "openssl" -path "*/openssl@3/*/include" | head -n 1) - echo "OpenSSL path: $openssl_path" - ln -sf $openssl_path ${brew_prefix}/include/openssl - ls -l ${brew_prefix}/include/openssl - - # fix opus header not found - opus_path=$(find ${brew_prefix}/Cellar -type d -name "opus" -path "*/opus/*/include" | head -n 1) - echo "Opus path: $opus_path" - ln -sf $opus_path ${brew_prefix}/include/opus - ls -l ${brew_prefix}/include/opus - - # fix miniupnpc header not found - upnp_path=$(find ${brew_prefix}/Cellar -type d -name "miniupnpc" -path "*/miniupnpc/*/include" | head -n 1) - echo "Miniupnpc path: $upnp_path" - ln -sf $upnp_path ${brew_prefix}/include/miniupnpc - ls -l ${brew_prefix}/include/miniupnpc - - - name: Install Boost - # installing boost from homebrew takes 30 minutes in a GitHub runner - run: | - export BOOST_ROOT=${HOME}/boost-${BOOST_VERSION} - - # install boost - wget \ - https://github.com/boostorg/boost/releases/download/boost-${BOOST_VERSION}/boost-${BOOST_VERSION}.tar.gz \ - --progress=bar:force:noscroll -q --show-progress - tar xf boost-${BOOST_VERSION}.tar.gz - cd boost-${BOOST_VERSION} - - # libdir should be set by --prefix but isn't - ./bootstrap.sh \ - --prefix=${BOOST_ROOT} \ - --libdir=${BOOST_ROOT}/lib \ - --with-libraries=locale,log,program_options,system,thread - ./b2 headers - ./b2 install \ - --prefix=${BOOST_ROOT} \ - --libdir=${BOOST_ROOT}/lib \ - -j$(sysctl -n hw.ncpu) \ - link=shared,static \ - variant=release \ - cxxflags=-std=c++14 \ - cxxflags=-stdlib=libc++ \ - linkflags=-stdlib=libc++ - - # put boost in cmake prefix path - echo "BOOST_ROOT=${BOOST_ROOT}" >> ${GITHUB_ENV} - - - name: Build MacOS - env: - BRANCH: ${{ github.head_ref || github.ref_name }} - BUILD_VERSION: ${{ needs.check_changelog.outputs.next_version_bare }} - COMMIT: ${{ github.event.pull_request.head.sha || github.sha }} - run: | mkdir build cd build cmake \ - -DBUILD_WERROR=ON \ - -DCMAKE_BUILD_TYPE=Release \ - -DCMAKE_INSTALL_PREFIX=/usr \ - -DSUNSHINE_ASSETS_DIR=local/sunshine/assets \ - -DSUNSHINE_EXECUTABLE_PATH=/usr/bin/sunshine \ + -DGITHUB_BRANCH="${branch}" \ + -DGITHUB_CLONE_URL="${clone_url}" \ + -DSUNSHINE_CONFIGURE_HOMEBREW=ON \ + -DSUNSHINE_CONFIGURE_ONLY=ON \ .. - make -j $(sysctl -n hw.ncpu) + cd .. - - name: Package MacOS - run: | - mkdir -p artifacts - cd build + # copy formula to artifacts + mkdir -p homebrew + cp -f ./build/sunshine.rb ./homebrew/sunshine.rb - # package - cpack -G DragNDrop - mv ./cpack_artifacts/Sunshine.dmg \ - ../artifacts/sunshine-macos-${{ matrix.os_version }}-${{ matrix.arch }}.dmg + # testing + cat ./homebrew/sunshine.rb - name: Upload Artifacts + if: ${{ matrix.release }} uses: actions/upload-artifact@v4 with: - name: sunshine-macos-${{ matrix.os_version }}-${{ matrix.arch }} - path: artifacts/ + name: sunshine-homebrew + path: homebrew/ - - name: Create/Update GitHub Release - if: ${{ needs.setup_release.outputs.create_release == 'true' }} - uses: ncipollo/release-action@v1 + - name: Should Publish Homebrew Formula + id: homebrew_publish + run: | + PUBLISH=false + if [[ \ + "${{ matrix.release }}" == "true" && \ + "${{ github.repository_owner }}" == "LizardByte" && \ + "${{ needs.setup_release.outputs.create_release }}" == "true" && \ + "${{ github.ref }}" == "refs/heads/master" \ + ]]; then + PUBLISH=true + fi + + echo "publish=${PUBLISH}" >> $GITHUB_OUTPUT + + - name: Validate and Publish Homebrew Formula + uses: LizardByte/homebrew-release-action@v2024.309.150158 with: - name: ${{ needs.setup_release.outputs.release_name }} - tag: ${{ needs.setup_release.outputs.release_tag }} - commit: ${{ needs.setup_release.outputs.release_commit }} - artifacts: "*artifacts/*" + formula_file: ${{ github.workspace }}/homebrew/sunshine.rb + git_email: ${{ secrets.GH_BOT_EMAIL }} + git_username: ${{ secrets.GH_BOT_NAME }} + publish: ${{ steps.homebrew_publish.outputs.publish }} token: ${{ secrets.GH_BOT_TOKEN }} - allowUpdates: true - body: ${{ needs.setup_release.outputs.release_body }} - discussionCategory: announcements - prerelease: ${{ needs.setup_release.outputs.pre_release }} build_mac_port: needs: [check_changelog, setup_release] diff --git a/CMakeLists.txt b/CMakeLists.txt index d672c9b8ae8..593d86b82dd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,7 +4,7 @@ cmake_minimum_required(VERSION 3.18) # todo - set version to 0.0.0 once confident in automated versioning project(Sunshine VERSION 0.22.0 - DESCRIPTION "Sunshine is a self-hosted game stream host for Moonlight." + DESCRIPTION "Self-hosted game stream host for Moonlight" HOMEPAGE_URL "https://app.lizardbyte.dev/Sunshine") set(PROJECT_LICENSE "GPL-3.0") diff --git a/cmake/prep/options.cmake b/cmake/prep/options.cmake index 9104320d31d..9a7fca8e5e5 100644 --- a/cmake/prep/options.cmake +++ b/cmake/prep/options.cmake @@ -12,7 +12,15 @@ option(CUDA_INHERIT_COMPILE_OPTIONS "When building CUDA code, inherit compile options from the the main project. You may want to disable this if your IDE throws errors about unknown flags after running cmake." ON) +if(UNIX) + # technically, the homebrew build could be on linux as well... no idea if it would actually work + option(SUNSHINE_BUILD_HOMEBREW + "Enable a Homebrew build." OFF) +endif () + if(APPLE) + option(SUNSHINE_CONFIGURE_HOMEBREW + "Configure macOS Homebrew formula. Recommended to use with SUNSHINE_CONFIGURE_ONLY" OFF) option(SUNSHINE_CONFIGURE_PORTFILE "Configure macOS Portfile. Recommended to use with SUNSHINE_CONFIGURE_ONLY" OFF) option(SUNSHINE_PACKAGE_MACOS diff --git a/cmake/prep/special_package_configuration.cmake b/cmake/prep/special_package_configuration.cmake index a5a780f5563..695b6e443c2 100644 --- a/cmake/prep/special_package_configuration.cmake +++ b/cmake/prep/special_package_configuration.cmake @@ -2,6 +2,9 @@ if (APPLE) if(${SUNSHINE_CONFIGURE_PORTFILE}) configure_file(packaging/macos/Portfile Portfile @ONLY) endif() + if(${SUNSHINE_CONFIGURE_HOMEBREW}) + configure_file(packaging/macos/sunshine.rb sunshine.rb @ONLY) + endif() elseif (UNIX) include(GNUInstallDirs) # this needs to be included prior to configuring the desktop files diff --git a/cmake/targets/common.cmake b/cmake/targets/common.cmake index 3dd629e0cab..9f2ce08240e 100644 --- a/cmake/targets/common.cmake +++ b/cmake/targets/common.cmake @@ -37,8 +37,19 @@ endif() target_compile_options(sunshine PRIVATE $<$:${SUNSHINE_COMPILE_OPTIONS}>;$<$:${SUNSHINE_COMPILE_OPTIONS_CUDA};-std=c++17>) # cmake-lint: disable=C0301 +# Homebrew build fails the vite build if we set these environment variables +if(${SUNSHINE_BUILD_HOMEBREW}) + set(NPM_SOURCE_ASSETS_DIR "") + set(NPM_ASSETS_DIR "") + set(NPM_BUILD_HOMEBREW "true") +else() + set(NPM_SOURCE_ASSETS_DIR ${SUNSHINE_SOURCE_ASSETS_DIR}) + set(NPM_ASSETS_DIR ${CMAKE_BINARY_DIR}) + set(NPM_BUILD_HOMEBREW "") +endif() + #WebUI build add_custom_target(web-ui ALL WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" COMMENT "Installing NPM Dependencies and Building the Web UI" - COMMAND bash -c \"npm install && SUNSHINE_SOURCE_ASSETS_DIR=${SUNSHINE_SOURCE_ASSETS_DIR} SUNSHINE_ASSETS_DIR=${CMAKE_BINARY_DIR} npm run build\") # cmake-lint: disable=C0301 + COMMAND bash -c \"npm install && SUNSHINE_BUILD_HOMEBREW=${NPM_BUILD_HOMEBREW} SUNSHINE_SOURCE_ASSETS_DIR=${NPM_SOURCE_ASSETS_DIR} SUNSHINE_ASSETS_DIR=${NPM_ASSETS_DIR} npm run build\") # cmake-lint: disable=C0301 diff --git a/docs/source/about/setup.rst b/docs/source/about/setup.rst index 2457ccc59e9..53da139875b 100644 --- a/docs/source/about/setup.rst +++ b/docs/source/about/setup.rst @@ -283,18 +283,15 @@ Install .. important:: Sunshine on macOS is experimental. Gamepads do not work. - .. tab:: dmg + .. tab:: Homebrew - .. warning:: The `dmg` does not include runtime dependencies. This package is not recommended for most users. - No support will be provided! + #. Install `Homebrew `__ + #. Update the Homebrew sources and install Sunshine. - #. Download the ``sunshine--.dmg`` file and install it. - - Uninstall: .. code-block:: bash - cd /etc/sunshine/assets - uninstall_pkg.sh + brew tap LizardByte/homebrew + brew install sunshine .. tab:: Portfile diff --git a/packaging/macos/sunshine.rb b/packaging/macos/sunshine.rb new file mode 100644 index 00000000000..e312c99d4d6 --- /dev/null +++ b/packaging/macos/sunshine.rb @@ -0,0 +1,62 @@ +require "language/node" + +class @PROJECT_NAME@ < Formula + desc "@PROJECT_DESCRIPTION@" + homepage "@PROJECT_HOMEPAGE_URL@" + url "@GITHUB_CLONE_URL@", + tag: "@GITHUB_BRANCH@" + version "@PROJECT_VERSION@" + license all_of: ["GPL-3.0-only"] + head "@GITHUB_CLONE_URL@", branch: "nightly" + + depends_on "boost" => :build + depends_on "cmake" => :build + depends_on "pkg-config" => :build + depends_on "curl" + depends_on "miniupnpc" + depends_on "node" + depends_on "openssl" + depends_on "opus" + + def install + args = %W[ + -DBUIld_WERROR=ON + -DCMAKE_INSTALL_PREFIX=#{prefix} + -DOPENSSL_ROOT_DIR=#{Formula["openssl"].opt_prefix} + -DSUNSHINE_ASSETS_DIR=sunshine/assets + -DSUNSHINE_BUILD_HOMEBREW=ON + ] + system "cmake", "-S", ".", "-B", "build", *std_cmake_args, *args + + cd "build" do + system "make", "-j" + system "make", "install" + end + end + + service do + run [opt_bin/"sunshine", "~/.config/sunshine/sunshine.conf"] + end + + def caveats + <<~EOS + Thanks for installing @PROJECT_NAME@! + + To get started, review the documentation at: + https://docs.lizardbyte.dev/projects/sunshine/en/latest/ + + Sunshine can only access microphones on macOS due to system limitations. + To stream system audio use "Soundflower" or "BlackHole". + + Gamepads are not currently supported on macOS. + EOS + end + + test do + # test that the binary runs at all + output = shell_output("#{bin}/sunshine --version").strip + puts output + + # TODO: add unit tests + end +end diff --git a/src_assets/macos/misc/uninstall_pkg.sh b/src_assets/macos/misc/uninstall_pkg.sh index 869f33d2fe6..89e7bbfb278 100644 --- a/src_assets/macos/misc/uninstall_pkg.sh +++ b/src_assets/macos/misc/uninstall_pkg.sh @@ -1,4 +1,7 @@ #!/bin/bash -e + +# note: this file was used to remove files when using the pkg/dmg, it is no longer used, but left for reference + set -e package_name=org.macports.Sunshine diff --git a/vite.config.js b/vite.config.js index a41470e4bf5..8732f1a08c7 100644 --- a/vite.config.js +++ b/vite.config.js @@ -16,13 +16,18 @@ import process from 'process' let assetsSrcPath = 'src_assets/common/assets/web'; let assetsDstPath = 'build/assets/web'; -if (process.env.SUNSHINE_SOURCE_ASSETS_DIR) { - console.log("Using srcdir from Cmake: " + resolve(process.env.SUNSHINE_SOURCE_ASSETS_DIR,"common/assets/web")); - assetsSrcPath = resolve(process.env.SUNSHINE_SOURCE_ASSETS_DIR,"common/assets/web") +if (process.env.SUNSHINE_BUILD_HOMEBREW) { + console.log("Building for homebrew, using default paths") } -if (process.env.SUNSHINE_ASSETS_DIR) { - console.log("Using destdir from Cmake: " + resolve(process.env.SUNSHINE_ASSETS_DIR,"assets/web")); - assetsDstPath = resolve(process.env.SUNSHINE_ASSETS_DIR,"assets/web") +else { + if (process.env.SUNSHINE_SOURCE_ASSETS_DIR) { + console.log("Using srcdir from Cmake: " + resolve(process.env.SUNSHINE_SOURCE_ASSETS_DIR,"common/assets/web")); + assetsSrcPath = resolve(process.env.SUNSHINE_SOURCE_ASSETS_DIR,"common/assets/web") + } + if (process.env.SUNSHINE_ASSETS_DIR) { + console.log("Using destdir from Cmake: " + resolve(process.env.SUNSHINE_ASSETS_DIR,"assets/web")); + assetsDstPath = resolve(process.env.SUNSHINE_ASSETS_DIR,"assets/web") + } } let header = fs.readFileSync(resolve(assetsSrcPath, "template_header.html")) From 9d5b01727efdc03a80f31c46a4c6ca529db5eee7 Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Fri, 8 Mar 2024 23:13:27 -0600 Subject: [PATCH 100/182] Replace WMIC-based check for ViGEmBus with a Powershell check This version is simpler and much faster on machines with many installed apps. --- .../windows/misc/gamepad/install-gamepad.bat | 25 ++++++------------- 1 file changed, 7 insertions(+), 18 deletions(-) diff --git a/src_assets/windows/misc/gamepad/install-gamepad.bat b/src_assets/windows/misc/gamepad/install-gamepad.bat index cf164cb5109..a31babb93f6 100644 --- a/src_assets/windows/misc/gamepad/install-gamepad.bat +++ b/src_assets/windows/misc/gamepad/install-gamepad.bat @@ -2,28 +2,17 @@ setlocal enabledelayedexpansion rem Check if a compatible version of ViGEmBus is already installed (1.17 or later) -set Version= -for /f "usebackq delims=" %%a in (`wmic product where "name='ViGEm Bus Driver' or name='Nefarius Virtual Gamepad Emulation Bus Driver'" get Version /format:Textvaluelist`) do ( - for /f "delims=" %%# in ("%%a") do set "%%#" -) - -rem Extract Major and Minor versions -for /f "tokens=1,2 delims=." %%a in ("%Version%") do ( - set "MajorVersion=%%a" - set "MinorVersion=%%b" -) - -rem Compare the version to 1.17 -if /i !MajorVersion! gtr 1 goto skip -if /i !MajorVersion! equ 1 ( - if /i !MinorVersion! geq 17 ( - goto skip - ) +rem +rem Note: We use exit code 2 to indicate success because either 0 or 1 may be returned +rem based on the PowerShell version if an exception occurs. +powershell -c Exit $(if ((Get-Item "$env:SystemRoot\System32\drivers\ViGEmBus.sys").VersionInfo.FileVersion -ge [System.Version]"1.17") { 2 } Else { 1 }) +if %ERRORLEVEL% EQU 2 ( + goto skip ) goto continue :skip -echo "The installed version is %Version%, no update needed. Exiting." +echo "The installed version is 1.17 or later, no update needed. Exiting." exit /b 0 :continue From 278567f72d02b528c45c749e8cecfa54d310d55e Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Sat, 9 Mar 2024 11:18:39 -0600 Subject: [PATCH 101/182] Move kmsgrab dependencies from optdepends to depends kmsgrab is the most fully featured capture backend for current versions of Sunshine, so it should be built by default. In addition to zero-copy capture and HDR support, it is the *only* capture backend that can handle non-wlroots Wayland capture. --- docker/archlinux.dockerfile | 4 +--- packaging/linux/Arch/PKGBUILD | 6 +++--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/docker/archlinux.dockerfile b/docker/archlinux.dockerfile index bd853ad395d..ddb3c28bafb 100644 --- a/docker/archlinux.dockerfile +++ b/docker/archlinux.dockerfile @@ -34,7 +34,7 @@ ENV COMMIT=${COMMIT} SHELL ["/bin/bash", "-o", "pipefail", "-c"] # install dependencies -# cuda, libcap, and libdrm are optional dependencies for PKGBUILD +# cuda is an optional build-time dependency for PKGBUILD RUN <<_DEPS #!/bin/bash set -e @@ -43,8 +43,6 @@ pacman -Syu --disable-download-timeout --needed --noconfirm \ cmake \ cuda \ git \ - libcap \ - libdrm \ namcap _DEPS diff --git a/packaging/linux/Arch/PKGBUILD b/packaging/linux/Arch/PKGBUILD index 4422698263e..dd478080049 100644 --- a/packaging/linux/Arch/PKGBUILD +++ b/packaging/linux/Arch/PKGBUILD @@ -13,6 +13,8 @@ depends=('avahi' 'boost-libs' 'curl' 'libayatana-appindicator' + 'libcap' + 'libdrm' 'libevdev' 'libmfx' 'libnotify' @@ -35,9 +37,7 @@ makedepends=('boost' 'make' 'nodejs' 'npm') -optdepends=('cuda: NvFBC capture support' - 'libcap' - 'libdrm') +optdepends=('cuda: NvFBC capture support') provides=('sunshine') From 74ce047a4b372cd7aa39abdae2f8b12629c3f57d Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Sat, 9 Mar 2024 11:24:55 -0600 Subject: [PATCH 102/182] Add optdepends for Intel and AMD hardware encoding --- packaging/linux/Arch/PKGBUILD | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packaging/linux/Arch/PKGBUILD b/packaging/linux/Arch/PKGBUILD index dd478080049..4bdef18d99f 100644 --- a/packaging/linux/Arch/PKGBUILD +++ b/packaging/linux/Arch/PKGBUILD @@ -37,7 +37,9 @@ makedepends=('boost' 'make' 'nodejs' 'npm') -optdepends=('cuda: NvFBC capture support') +optdepends=('cuda: Nvidia GPU encoding support' + 'libva-mesa-driver: AMD GPU encoding support' + 'intel-media-driver: Intel GPU encoding support') provides=('sunshine') From cb4bfaa2f407ac3bdd6a08d4b47bbaf40d8f2f0e Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Sat, 9 Mar 2024 11:55:22 -0600 Subject: [PATCH 103/182] Add the .INSTALL script needed for kmsgrab to work This also removes the standalone PKGBUILD artifact because our PKGBUILD has external dependencies now. --- .../prep/special_package_configuration.cmake | 1 + docker/archlinux.dockerfile | 2 +- docs/source/about/setup.rst | 19 ++----------------- packaging/linux/Arch/PKGBUILD | 1 + packaging/linux/Arch/sunshine.install | 12 ++++++++++++ 5 files changed, 17 insertions(+), 18 deletions(-) create mode 100644 packaging/linux/Arch/sunshine.install diff --git a/cmake/prep/special_package_configuration.cmake b/cmake/prep/special_package_configuration.cmake index 695b6e443c2..d04066cdc8a 100644 --- a/cmake/prep/special_package_configuration.cmake +++ b/cmake/prep/special_package_configuration.cmake @@ -29,6 +29,7 @@ elseif (UNIX) # configure the arch linux pkgbuild if(${SUNSHINE_CONFIGURE_PKGBUILD}) configure_file(packaging/linux/Arch/PKGBUILD PKGBUILD @ONLY) + configure_file(packaging/linux/Arch/sunshine.install sunshine.install @ONLY) endif() # configure the flatpak manifest diff --git a/docker/archlinux.dockerfile b/docker/archlinux.dockerfile index ddb3c28bafb..e8cfd93998e 100644 --- a/docker/archlinux.dockerfile +++ b/docker/archlinux.dockerfile @@ -78,6 +78,7 @@ _MAKE WORKDIR /build/sunshine/pkg RUN mv /build/sunshine/build/PKGBUILD . +RUN mv /build/sunshine/build/sunshine.install . # namcap and build PKGBUILD file RUN <<_PKGBUILD @@ -91,7 +92,6 @@ _PKGBUILD FROM scratch as artifacts -COPY --link --from=sunshine-build /build/sunshine/pkg/PKGBUILD /PKGBUILD COPY --link --from=sunshine-build /build/sunshine/pkg/sunshine*.pkg.tar.zst /sunshine.pkg.tar.zst FROM sunshine-base as sunshine diff --git a/docs/source/about/setup.rst b/docs/source/about/setup.rst index 53da139875b..0a68bad8f14 100644 --- a/docs/source/about/setup.rst +++ b/docs/source/about/setup.rst @@ -38,7 +38,6 @@ Install =========================================== ============== ============== ================================ Package CUDA Version Min Driver CUDA Compute Capabilities =========================================== ============== ============== ================================ - PKGBUILD User dependent User dependent User dependent sunshine.AppImage 11.8.0 450.80.02 35;50;52;60;61;62;70;75;80;86;90 sunshine.pkg.tar.zst 11.8.0 450.80.02 35;50;52;60;61;62;70;75;80;86;90 sunshine_{arch}.flatpak 12.0.0 525.60.13 50;52;60;61;62;70;75;80;86;90 @@ -90,21 +89,7 @@ Install ./sunshine.AppImage --remove - .. tab:: Archlinux PKGBUILD - - #. Open terminal and run the following code. - - .. code-block:: bash - - wget https://github.com/LizardByte/Sunshine/releases/latest/download/PKGBUILD - makepkg -fi - - Uninstall: - .. code-block:: bash - - pacman -R sunshine - - .. tab:: Archlinux pkg + .. tab:: Arch Linux Package #. Open terminal and run the following code. @@ -205,7 +190,7 @@ Install sudo dnf remove sunshine - The `deb`, `rpm`, `Flatpak` and `AppImage` packages should handle these steps automatically. + The `deb`, `rpm`, `zst`, `Flatpak` and `AppImage` packages should handle these steps automatically. Third party packages may not. Sunshine needs access to `uinput` to create mouse and gamepad events. diff --git a/packaging/linux/Arch/PKGBUILD b/packaging/linux/Arch/PKGBUILD index 4bdef18d99f..44a6beb2b2f 100644 --- a/packaging/linux/Arch/PKGBUILD +++ b/packaging/linux/Arch/PKGBUILD @@ -8,6 +8,7 @@ pkgdesc="@PROJECT_DESCRIPTION@" arch=('x86_64' 'aarch64') url=@PROJECT_HOMEPAGE_URL@ license=('GPL3') +install=sunshine.install depends=('avahi' 'boost-libs' diff --git a/packaging/linux/Arch/sunshine.install b/packaging/linux/Arch/sunshine.install new file mode 100644 index 00000000000..4d4a4a45493 --- /dev/null +++ b/packaging/linux/Arch/sunshine.install @@ -0,0 +1,12 @@ +do_setcap() { + setcap cap_sys_admin+p $(readlink -f $(which sunshine)) +} + +post_install() { + do_setcap +} + +post_upgrade() { + do_setcap +} + From bc0a4786f4c403a696482933f9bd9a760cf4f344 Mon Sep 17 00:00:00 2001 From: brycerocky <56776312+brycerocky@users.noreply.github.com> Date: Sun, 10 Mar 2024 15:35:48 -0700 Subject: [PATCH 104/182] Use icon caching for system tray. (#2238) --- src/system_tray.cpp | 2 ++ third-party/tray | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/system_tray.cpp b/src/system_tray.cpp index 39131ba30fe..eb5948a41c1 100644 --- a/src/system_tray.cpp +++ b/src/system_tray.cpp @@ -145,6 +145,8 @@ namespace system_tray { { .text = "Restart", .cb = tray_restart_cb }, { .text = "Quit", .cb = tray_quit_cb }, { .text = nullptr } }, + .iconPathCount = 4, + .allIconPaths = { TRAY_ICON, TRAY_ICON_LOCKED, TRAY_ICON_PLAYING, TRAY_ICON_PAUSING }, }; /** diff --git a/third-party/tray b/third-party/tray index 2bf1c610300..a08c1025c3f 160000 --- a/third-party/tray +++ b/third-party/tray @@ -1 +1 @@ -Subproject commit 2bf1c610300b27f8d8ce87e2f13223fc83efeb42 +Subproject commit a08c1025c3f158d6b6c4b9bcf0ab770291d26896 From a2785baf0aa7202bceecd975b1aa2d8798d2378a Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sun, 10 Mar 2024 22:03:20 -0400 Subject: [PATCH 105/182] fix(linux): automatically migrate config directory (#2240) --- .../linux/flatpak/dev.lizardbyte.sunshine.yml | 1 + src/platform/linux/misc.cpp | 48 +++++++++++++++---- 2 files changed, 41 insertions(+), 8 deletions(-) diff --git a/packaging/linux/flatpak/dev.lizardbyte.sunshine.yml b/packaging/linux/flatpak/dev.lizardbyte.sunshine.yml index a4176d4ea15..1da03310f7a 100644 --- a/packaging/linux/flatpak/dev.lizardbyte.sunshine.yml +++ b/packaging/linux/flatpak/dev.lizardbyte.sunshine.yml @@ -11,6 +11,7 @@ separate-locales: false finish-args: - --device=all # access all devices - --env=PULSE_PROP_media.category=Manager # allow sunshine to manage audio sinks + - --env=SUNSHINE_MIGRATE_CONFIG=1 # migrate config files to the new location - --filesystem=home # need to save files in user's home directory - --share=ipc # required for X11 shared memory extension - --share=network # access network diff --git a/src/platform/linux/misc.cpp b/src/platform/linux/misc.cpp index 0075d4502f8..27b281ac3e0 100644 --- a/src/platform/linux/misc.cpp +++ b/src/platform/linux/misc.cpp @@ -100,22 +100,54 @@ namespace platf { fs::path appdata() { + bool found = false; + bool migrate_config = true; const char *dir; + const char *homedir; + fs::path config_path; + + // Get the home directory + if ((homedir = getenv("HOME")) == nullptr || strlen(homedir) == 0) { + // If HOME is empty or not set, use the current user's home directory + homedir = getpwuid(geteuid())->pw_dir; + } // May be set if running under a systemd service with the ConfigurationDirectory= option set. - if ((dir = getenv("CONFIGURATION_DIRECTORY")) != nullptr) { - return fs::path { dir } / "sunshine"sv; + if ((dir = getenv("CONFIGURATION_DIRECTORY")) != nullptr && strlen(dir) > 0) { + found = true; + config_path = fs::path(dir) / "sunshine"sv; } // Otherwise, follow the XDG base directory specification: // https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html - if ((dir = getenv("XDG_CONFIG_HOME")) != nullptr) { - return fs::path { dir } / "sunshine"sv; - } - if ((dir = getenv("HOME")) == nullptr) { - dir = getpwuid(geteuid())->pw_dir; + if (!found && (dir = getenv("XDG_CONFIG_HOME")) != nullptr && strlen(dir) > 0) { + found = true; + config_path = fs::path(dir) / "sunshine"sv; + } + // As a last resort, use the home directory + if (!found) { + migrate_config = false; + config_path = fs::path(homedir) / ".config/sunshine"sv; + } + + // migrate from the old config location if necessary + if (migrate_config && found && getenv("SUNSHINE_MIGRATE_CONFIG") == "1"sv) { + fs::path old_config_path = fs::path(homedir) / ".config/sunshine"sv; + if (old_config_path != config_path && fs::exists(old_config_path)) { + if (!fs::exists(config_path)) { + BOOST_LOG(info) << "Migrating config from "sv << old_config_path << " to "sv << config_path; + std::error_code ec; + fs::rename(old_config_path, config_path, ec); + if (ec) { + return old_config_path; + } + } + else { + BOOST_LOG(warning) << "Config exists in both "sv << old_config_path << " and "sv << config_path << ", using "sv << config_path << "... it is recommended to remove "sv << old_config_path; + } + } } - return fs::path { dir } / ".config/sunshine"sv; + return config_path; } std::string From 91744960c138772928b602eb0091a082d7f7a508 Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Mon, 11 Mar 2024 02:42:25 -0500 Subject: [PATCH 106/182] Avoid broken fallback to cross-adapter NVENC encoding with KMS --- src/platform/linux/kmsgrab.cpp | 21 ++++++++++++++++++++- src/platform/linux/misc.cpp | 6 +++--- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/src/platform/linux/kmsgrab.cpp b/src/platform/linux/kmsgrab.cpp index a7a3256d433..a567d70314a 100644 --- a/src/platform/linux/kmsgrab.cpp +++ b/src/platform/linux/kmsgrab.cpp @@ -108,6 +108,7 @@ namespace platf { using obj_prop_t = util::safe_ptr; using prop_t = util::safe_ptr; using prop_blob_t = util::safe_ptr; + using version_t = util::safe_ptr; using conn_type_count_t = std::map; @@ -364,6 +365,12 @@ namespace platf { return drmModeGetResources(fd.el); } + bool + is_nvidia() { + version_t ver { drmGetVersion(fd.el) }; + return ver && ver->name && strncmp(ver->name, "nvidia-drm", 10) == 0; + } + bool is_cursor(std::uint32_t plane_id) { auto props = plane_props(plane_id); @@ -604,6 +611,12 @@ namespace platf { continue; } + // Skip non-Nvidia cards if we're looking for CUDA devices + if (mem_type == mem_type_e::cuda && !card.is_nvidia()) { + BOOST_LOG(debug) << file << " is not a CUDA device"sv; + continue; + } + auto end = std::end(card); for (auto plane = std::begin(card); plane != end; ++plane) { // Skip unused planes @@ -1576,7 +1589,7 @@ namespace platf { // A list of names of displays accepted as display_name std::vector - kms_display_names() { + kms_display_names(mem_type_e hwdevice_type) { int count = 0; if (!fs::exists("/dev/dri")) { @@ -1608,6 +1621,12 @@ namespace platf { continue; } + // Skip non-Nvidia cards if we're looking for CUDA devices + if (hwdevice_type == mem_type_e::cuda && !card.is_nvidia()) { + BOOST_LOG(debug) << file << " is not a CUDA device"sv; + continue; + } + auto crtc_to_monitor = kms::map_crtc_to_monitor(card.monitors(conn_type_count)); auto end = std::end(card); diff --git a/src/platform/linux/misc.cpp b/src/platform/linux/misc.cpp index 27b281ac3e0..884c0e9047a 100644 --- a/src/platform/linux/misc.cpp +++ b/src/platform/linux/misc.cpp @@ -766,13 +766,13 @@ namespace platf { #ifdef SUNSHINE_BUILD_DRM std::vector - kms_display_names(); + kms_display_names(mem_type_e hwdevice_type); std::shared_ptr kms_display(mem_type_e hwdevice_type, const std::string &display_name, const video::config_t &config); bool verify_kms() { - return !kms_display_names().empty(); + return !kms_display_names(mem_type_e::unknown).empty(); } #endif @@ -798,7 +798,7 @@ namespace platf { if (sources[source::WAYLAND]) return wl_display_names(); #endif #ifdef SUNSHINE_BUILD_DRM - if (sources[source::KMS]) return kms_display_names(); + if (sources[source::KMS]) return kms_display_names(hwdevice_type); #endif #ifdef SUNSHINE_BUILD_X11 if (sources[source::X11]) return x11_display_names(); From 3117fa57ec53709c665833eabb316441e02207fc Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Sun, 10 Mar 2024 19:35:51 -0500 Subject: [PATCH 107/182] Rename 85-sunshine.rules to 60-sunshine.rules This ensures the rules are evaluated before 73-seat-late.rules which enables uaccess tag application for existing logged on users. --- cmake/packaging/linux.cmake | 4 ++-- docs/source/about/setup.rst | 2 +- packaging/linux/AppImage/AppRun | 4 ++-- packaging/linux/flatpak/scripts/additional-install.sh | 4 ++-- packaging/linux/flatpak/scripts/remove-additional-install.sh | 2 +- .../linux/misc/{85-sunshine.rules => 60-sunshine.rules} | 0 6 files changed, 8 insertions(+), 8 deletions(-) rename src_assets/linux/misc/{85-sunshine.rules => 60-sunshine.rules} (100%) diff --git a/cmake/packaging/linux.cmake b/cmake/packaging/linux.cmake index 8563414a40e..499f058c8bd 100644 --- a/cmake/packaging/linux.cmake +++ b/cmake/packaging/linux.cmake @@ -3,7 +3,7 @@ install(DIRECTORY "${SUNSHINE_SOURCE_ASSETS_DIR}/linux/assets/" DESTINATION "${SUNSHINE_ASSETS_DIR}") if(${SUNSHINE_BUILD_APPIMAGE} OR ${SUNSHINE_BUILD_FLATPAK}) - install(FILES "${SUNSHINE_SOURCE_ASSETS_DIR}/linux/misc/85-sunshine.rules" + install(FILES "${SUNSHINE_SOURCE_ASSETS_DIR}/linux/misc/60-sunshine.rules" DESTINATION "${SUNSHINE_ASSETS_DIR}/udev/rules.d") install(FILES "${CMAKE_CURRENT_BINARY_DIR}/sunshine.service" DESTINATION "${SUNSHINE_ASSETS_DIR}/systemd/user") @@ -11,7 +11,7 @@ else() find_package(Systemd) find_package(Udev) - install(FILES "${SUNSHINE_SOURCE_ASSETS_DIR}/linux/misc/85-sunshine.rules" + install(FILES "${SUNSHINE_SOURCE_ASSETS_DIR}/linux/misc/60-sunshine.rules" DESTINATION "${UDEV_RULES_INSTALL_DIR}") install(FILES "${CMAKE_CURRENT_BINARY_DIR}/sunshine.service" DESTINATION "${SYSTEMD_USER_UNIT_INSTALL_DIR}") diff --git a/docs/source/about/setup.rst b/docs/source/about/setup.rst index 0a68bad8f14..89b5830247f 100644 --- a/docs/source/about/setup.rst +++ b/docs/source/about/setup.rst @@ -199,7 +199,7 @@ Install .. code-block:: bash echo 'KERNEL=="uinput", SUBSYSTEM=="misc", OPTIONS+="static_node=uinput", TAG+="uaccess"' | \ - sudo tee /etc/udev/rules.d/85-sunshine.rules + sudo tee /etc/udev/rules.d/60-sunshine.rules #. Optionally, configure autostart service diff --git a/packaging/linux/AppImage/AppRun b/packaging/linux/AppImage/AppRun index ddc5fd38455..4eeaeada97f 100644 --- a/packaging/linux/AppImage/AppRun +++ b/packaging/linux/AppImage/AppRun @@ -46,7 +46,7 @@ echo " function install() { # user input rules # shellcheck disable=SC2002 - cat "$SUNSHINE_SHARE_HERE/udev/rules.d/85-sunshine.rules" | sudo tee /etc/udev/rules.d/85-sunshine.rules + cat "$SUNSHINE_SHARE_HERE/udev/rules.d/60-sunshine.rules" | sudo tee /etc/udev/rules.d/60-sunshine.rules # sunshine service mkdir -p ~/.config/systemd/user @@ -79,7 +79,7 @@ function install() { function remove() { # remove input rules - sudo rm -f /etc/udev/rules.d/85-sunshine.rules + sudo rm -f /etc/udev/rules.d/60-sunshine.rules # remove service sudo rm -f ~/.config/systemd/user/sunshine.service diff --git a/packaging/linux/flatpak/scripts/additional-install.sh b/packaging/linux/flatpak/scripts/additional-install.sh index 8a905b53810..a27db4e09ba 100644 --- a/packaging/linux/flatpak/scripts/additional-install.sh +++ b/packaging/linux/flatpak/scripts/additional-install.sh @@ -7,7 +7,7 @@ echo Sunshine User Service has been installed. echo Use [systemctl --user enable sunshine] once to autostart Sunshine on login. # Udev rule -UDEV=$(cat /app/share/sunshine/udev/rules.d/85-sunshine.rules) +UDEV=$(cat /app/share/sunshine/udev/rules.d/60-sunshine.rules) echo Configuring mouse permission. -flatpak-spawn --host pkexec sh -c "echo '$UDEV' > /etc/udev/rules.d/85-sunshine.rules" +flatpak-spawn --host pkexec sh -c "echo '$UDEV' > /etc/udev/rules.d/60-sunshine.rules" echo Restart computer for mouse permission to take effect. diff --git a/packaging/linux/flatpak/scripts/remove-additional-install.sh b/packaging/linux/flatpak/scripts/remove-additional-install.sh index 6148f62ea1e..0d13baeb62c 100644 --- a/packaging/linux/flatpak/scripts/remove-additional-install.sh +++ b/packaging/linux/flatpak/scripts/remove-additional-install.sh @@ -7,5 +7,5 @@ systemctl --user daemon-reload echo Sunshine User Service has been removed. # Udev rule -flatpak-spawn --host pkexec sh -c "rm /etc/udev/rules.d/85-sunshine.rules" +flatpak-spawn --host pkexec sh -c "rm /etc/udev/rules.d/60-sunshine.rules" echo Mouse permission removed. Restart computer to take effect. diff --git a/src_assets/linux/misc/85-sunshine.rules b/src_assets/linux/misc/60-sunshine.rules similarity index 100% rename from src_assets/linux/misc/85-sunshine.rules rename to src_assets/linux/misc/60-sunshine.rules From 3181d91edf6e1ad958f19626381b5980fc2afabb Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Sun, 10 Mar 2024 20:06:59 -0500 Subject: [PATCH 108/182] Apply udev rules to /dev/uinput immediately after installation --- docs/source/about/setup.rst | 5 ++++- packaging/linux/AppImage/AppRun | 21 ++------------------- packaging/linux/Arch/sunshine.install | 8 ++++++++ src_assets/linux/misc/postinst | 7 +++++++ 4 files changed, 21 insertions(+), 20 deletions(-) diff --git a/docs/source/about/setup.rst b/docs/source/about/setup.rst index 89b5830247f..8b803a78215 100644 --- a/docs/source/about/setup.rst +++ b/docs/source/about/setup.rst @@ -195,11 +195,14 @@ Install Sunshine needs access to `uinput` to create mouse and gamepad events. - #. Create `udev` rules. + #. Create and reload `udev` rules for uinput. .. code-block:: bash echo 'KERNEL=="uinput", SUBSYSTEM=="misc", OPTIONS+="static_node=uinput", TAG+="uaccess"' | \ sudo tee /etc/udev/rules.d/60-sunshine.rules + sudo udevadm control --reload-rules + sudo udevadm trigger + sudo modprobe uinput #. Optionally, configure autostart service diff --git a/packaging/linux/AppImage/AppRun b/packaging/linux/AppImage/AppRun index 4eeaeada97f..404704c34d3 100644 --- a/packaging/linux/AppImage/AppRun +++ b/packaging/linux/AppImage/AppRun @@ -47,6 +47,8 @@ function install() { # user input rules # shellcheck disable=SC2002 cat "$SUNSHINE_SHARE_HERE/udev/rules.d/60-sunshine.rules" | sudo tee /etc/udev/rules.d/60-sunshine.rules + sudo udevadm control --reload-rules + sudo udevadm trigger --property-match=DEVNAME=/dev/uinput # sunshine service mkdir -p ~/.config/systemd/user @@ -56,25 +58,6 @@ function install() { # setcap sudo setcap cap_sys_admin+p "$(readlink -f "$SUNSHINE_BIN_HERE")" - - while true - do - read -r -p "This installation requires a reboot. Do you want to reboot NOW? [y/n] " input - - case $input in - [yY][eE][sS]|[yY]) - echo "Yes" - sudo reboot now - ;; - [nN][oO]|[nN]) - echo "No" - break - ;; - *) - echo "Invalid input..." - ;; - esac - done } function remove() { diff --git a/packaging/linux/Arch/sunshine.install b/packaging/linux/Arch/sunshine.install index 4d4a4a45493..a8a700f1f1c 100644 --- a/packaging/linux/Arch/sunshine.install +++ b/packaging/linux/Arch/sunshine.install @@ -2,11 +2,19 @@ do_setcap() { setcap cap_sys_admin+p $(readlink -f $(which sunshine)) } +do_udev_reload() { + udevadm control --reload-rules + udevadm trigger --property-match=DEVNAME=/dev/uinput + modprobe uinput || true +} + post_install() { do_setcap + do_udev_reload } post_upgrade() { do_setcap + do_udev_reload } diff --git a/src_assets/linux/misc/postinst b/src_assets/linux/misc/postinst index 63f0523d867..aab899f94ec 100644 --- a/src_assets/linux/misc/postinst +++ b/src_assets/linux/misc/postinst @@ -6,3 +6,10 @@ if [ -x "$path_to_setcap" ] ; then echo "$path_to_setcap cap_sys_admin+p /usr/bin/sunshine" $path_to_setcap cap_sys_admin+p $(readlink -f /usr/bin/sunshine) fi + +# Trigger udev rule reload for /dev/uinput +path_to_udevadm=$(which udevadm) +if [ -x "$path_to_udevadm" ] ; then + $path_to_udevadm control --reload-rules + $path_to_udevadm trigger --property-match=DEVNAME=/dev/uinput +fi From 97467ea355ea3f39ad1ea568847d85757aa1052b Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Sun, 10 Mar 2024 22:06:11 -0500 Subject: [PATCH 109/182] Reorder and reword the KMS setup step --- docs/source/about/setup.rst | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/docs/source/about/setup.rst b/docs/source/about/setup.rst index 8b803a78215..5b1f32937b9 100644 --- a/docs/source/about/setup.rst +++ b/docs/source/about/setup.rst @@ -204,6 +204,22 @@ Install sudo udevadm trigger sudo modprobe uinput + #. Enable permissions for KMS capture. + .. warning:: Capture of most Wayland-based desktop environments will fail unless this step is performed. + + .. note:: ``cap_sys_admin`` may as well be root, except you don't need to be root to run it. It is necessary to + allow Sunshine to use KMS capture. + + **Enable** + .. code-block:: bash + + sudo setcap cap_sys_admin+p $(readlink -f $(which sunshine)) + + **Disable (for Xorg/X11 only)** + .. code-block:: bash + + sudo setcap -r $(readlink -f $(which sunshine)) + #. Optionally, configure autostart service - filename: ``~/.config/systemd/user/sunshine.service`` @@ -248,20 +264,6 @@ Install systemctl --user enable sunshine - #. Additional Setup for KMS - .. note:: ``cap_sys_admin`` may as well be root, except you don't need to be root to run it. It is necessary to - allow Sunshine to use KMS. - - **Enable** - .. code-block:: bash - - sudo setcap cap_sys_admin+p $(readlink -f $(which sunshine)) - - **Disable (for Xorg/X11)** - .. code-block:: bash - - sudo setcap -r $(readlink -f $(which sunshine)) - #. Reboot .. code-block:: bash From e383ab99563e82e54a15e2d4b394e7bc3f0c8bb9 Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Sun, 10 Mar 2024 22:20:52 -0500 Subject: [PATCH 110/182] Add note to prefer distro packages over Flatpak/AppImage --- docs/source/about/setup.rst | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/source/about/setup.rst b/docs/source/about/setup.rst index 5b1f32937b9..70ed96f57f7 100644 --- a/docs/source/about/setup.rst +++ b/docs/source/about/setup.rst @@ -51,6 +51,8 @@ Install .. tab:: AppImage + .. caution:: Use distro-specific packages instead of the AppImage if they are available. + According to AppImageLint the supported distro matrix of the AppImage is below. - ✔ Debian bullseye @@ -103,7 +105,7 @@ Install pacman -R sunshine - .. tab:: Debian Package + .. tab:: Debian/Ubuntu Package #. Download ``sunshine-{distro}-{distro-version}-{arch}.deb`` and run the following code. @@ -123,6 +125,8 @@ Install .. tab:: Flatpak Package + .. caution:: Use distro-specific packages instead of the Flatpak if they are available. + .. important:: The instructions provided here are for the version supplied in the `latest release`_, which does not necessarily match the version in the Flathub repository! From d8877982ffe4a4fc22dbae3e2776145d6651d92a Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Tue, 12 Mar 2024 18:08:14 -0500 Subject: [PATCH 111/182] Improve KMS debuggability and avoid known broken cases --- src/platform/linux/kmsgrab.cpp | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/src/platform/linux/kmsgrab.cpp b/src/platform/linux/kmsgrab.cpp index a567d70314a..748fc93a967 100644 --- a/src/platform/linux/kmsgrab.cpp +++ b/src/platform/linux/kmsgrab.cpp @@ -15,6 +15,7 @@ #include #include +#include "src/config.h" #include "src/logging.h" #include "src/platform/common.h" #include "src/round_robin.h" @@ -298,6 +299,9 @@ namespace platf { return -1; } + version_t ver { drmGetVersion(fd.el) }; + BOOST_LOG(info) << path << " -> "sv << ((ver && ver->name) ? ver->name : "UNKNOWN"); + // Open the render node for this card to share with libva. // If it fails, we'll just share the primary node instead. char *rendernode_path = drmGetRenderDeviceNameFromFd(fd.el); @@ -316,12 +320,21 @@ namespace platf { } if (drmSetClientCap(fd.el, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1)) { - BOOST_LOG(error) << "Couldn't expose some/all drm planes for card: "sv << path; + BOOST_LOG(error) << "GPU driver doesn't support universal planes: "sv << path; return -1; } if (drmSetClientCap(fd.el, DRM_CLIENT_CAP_ATOMIC, 1)) { - BOOST_LOG(warning) << "Couldn't expose some properties for card: "sv << path; + BOOST_LOG(warning) << "GPU driver doesn't support atomic mode-setting: "sv << path; +#if defined(SUNSHINE_BUILD_X11) + // We won't be able to capture the mouse cursor with KMS on non-atomic drivers, + // so fall back to X11 if it's available and the user didn't explicitly force KMS. + if (window_system == window_system_e::X11 && config::video.capture != "kms") { + BOOST_LOG(info) << "Avoiding KMS capture under X11 due to lack of atomic mode-setting"sv; + return -1; + } +#endif + BOOST_LOG(warning) << "Cursor capture may fail without atomic mode-setting support!"sv; } plane_res.reset(drmModeGetPlaneResources(fd.el)); @@ -640,8 +653,7 @@ namespace platf { } if (!fb->handles[0]) { - BOOST_LOG(error) - << "Couldn't get handle for DRM Framebuffer ["sv << plane->fb_id << "]: Possibly not permitted: do [sudo setcap cap_sys_admin+p sunshine]"sv; + BOOST_LOG(error) << "Couldn't get handle for DRM Framebuffer ["sv << plane->fb_id << "]: Probably not permitted"sv; return -1; } @@ -909,12 +921,14 @@ namespace platf { if (!prop_crtc_w || !prop_crtc_h || !prop_crtc_x || !prop_crtc_y) { BOOST_LOG(error) << "Cursor plane is missing required plane CRTC properties!"sv; + BOOST_LOG(error) << "Atomic mode-setting must be enabled to capture the cursor!"sv; cursor_plane_id = -1; captured_cursor.visible = false; return; } if (!prop_src_x || !prop_src_y || !prop_src_w || !prop_src_h) { BOOST_LOG(error) << "Cursor plane is missing required plane SRC properties!"sv; + BOOST_LOG(error) << "Atomic mode-setting must be enabled to capture the cursor!"sv; cursor_plane_id = -1; captured_cursor.visible = false; return; @@ -1072,8 +1086,7 @@ namespace platf { } if (!fb->handles[0]) { - BOOST_LOG(error) - << "Couldn't get handle for DRM Framebuffer ["sv << plane->fb_id << "]: Possibly not permitted: do [sudo setcap cap_sys_admin+p sunshine]"sv; + BOOST_LOG(error) << "Couldn't get handle for DRM Framebuffer ["sv << plane->fb_id << "]: Probably not permitted"sv; return capture_e::error; } @@ -1647,8 +1660,9 @@ namespace platf { } if (!fb->handles[0]) { - BOOST_LOG(error) - << "Couldn't get handle for DRM Framebuffer ["sv << plane->fb_id << "]: Possibly not permitted: do [sudo setcap cap_sys_admin+p sunshine]"sv; + BOOST_LOG(error) << "Couldn't get handle for DRM Framebuffer ["sv << plane->fb_id << "]: Probably not permitted"sv; + BOOST_LOG((window_system != window_system_e::X11 || config::video.capture == "kms") ? fatal : error) + << "You must run [sudo setcap cap_sys_admin+p $(readlink -f sunshine)] for KMS display capture to work!"sv; break; } From c13a30db7876ad18fecd222f4433ccd11f24d6c5 Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Tue, 12 Mar 2024 21:01:49 -0500 Subject: [PATCH 112/182] Allow NVENC to be forced to try capturing non-Nvidia GPUs --- src/platform/linux/kmsgrab.cpp | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/platform/linux/kmsgrab.cpp b/src/platform/linux/kmsgrab.cpp index 748fc93a967..d4feb3557d8 100644 --- a/src/platform/linux/kmsgrab.cpp +++ b/src/platform/linux/kmsgrab.cpp @@ -625,9 +625,12 @@ namespace platf { } // Skip non-Nvidia cards if we're looking for CUDA devices + // unless NVENC is selected manually by the user if (mem_type == mem_type_e::cuda && !card.is_nvidia()) { BOOST_LOG(debug) << file << " is not a CUDA device"sv; - continue; + if (config::video.encoder != "nvenc") { + continue; + } } auto end = std::end(card); @@ -1635,9 +1638,15 @@ namespace platf { } // Skip non-Nvidia cards if we're looking for CUDA devices + // unless NVENC is selected manually by the user if (hwdevice_type == mem_type_e::cuda && !card.is_nvidia()) { BOOST_LOG(debug) << file << " is not a CUDA device"sv; - continue; + if (config::video.encoder == "nvenc") { + BOOST_LOG(warning) << "Using NVENC with your display connected to a different GPU may not work properly!"sv; + } + else { + continue; + } } auto crtc_to_monitor = kms::map_crtc_to_monitor(card.monitors(conn_type_count)); From 1859e23cd5be366a38a106b5e9e42149b68f8961 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 13 Mar 2024 09:04:41 -0400 Subject: [PATCH 113/182] build(deps): bump LizardByte/homebrew-release-action from 2024.309.150158 to 2024.311.172824 (#2245) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/CI.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index f2529e7390e..80c309ca052 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -588,7 +588,7 @@ jobs: echo "publish=${PUBLISH}" >> $GITHUB_OUTPUT - name: Validate and Publish Homebrew Formula - uses: LizardByte/homebrew-release-action@v2024.309.150158 + uses: LizardByte/homebrew-release-action@v2024.311.172824 with: formula_file: ${{ github.workspace }}/homebrew/sunshine.rb git_email: ${{ secrets.GH_BOT_EMAIL }} From 0bfad20d4c3d7ffe27b25a9617e601998911e5c4 Mon Sep 17 00:00:00 2001 From: Crashdummy Date: Wed, 13 Mar 2024 14:48:13 +0100 Subject: [PATCH 114/182] fix(Linux/Fedora): re-enable CUDA and bump to 12.4.0 (#2247) --- docker/fedora-38.dockerfile | 42 ++++++++++++++++---------------- docker/fedora-39.dockerfile | 48 ++++++++++++++++++------------------- docs/source/about/setup.rst | 4 ++-- 3 files changed, 45 insertions(+), 49 deletions(-) diff --git a/docker/fedora-38.dockerfile b/docker/fedora-38.dockerfile index 9ba496c5b97..55622ab7070 100644 --- a/docker/fedora-38.dockerfile +++ b/docker/fedora-38.dockerfile @@ -68,28 +68,27 @@ dnf clean all rm -rf /var/cache/yum _DEPS -# todo - enable cuda once it's supported for gcc 13 and fedora 38 ## install cuda -#WORKDIR /build/cuda +WORKDIR /build/cuda ## versions: https://developer.nvidia.com/cuda-toolkit-archive -#ENV CUDA_VERSION="12.0.0" -#ENV CUDA_BUILD="525.60.13" +ENV CUDA_VERSION="12.4.0" +ENV CUDA_BUILD="550.54.14" ## hadolint ignore=SC3010 -#RUN <<_INSTALL_CUDA -##!/bin/bash -#set -e -#cuda_prefix="https://developer.download.nvidia.com/compute/cuda/" -#cuda_suffix="" -#if [[ "${TARGETPLATFORM}" == 'linux/arm64' ]]; then -# cuda_suffix="_sbsa" -#fi -#url="${cuda_prefix}${CUDA_VERSION}/local_installers/cuda_${CUDA_VERSION}_${CUDA_BUILD}_linux${cuda_suffix}.run" -#echo "cuda url: ${url}" -#wget "$url" --progress=bar:force:noscroll -q --show-progress -O ./cuda.run -#chmod a+x ./cuda.run -#./cuda.run --silent --toolkit --toolkitpath=/build/cuda --no-opengl-libs --no-man-page --no-drm -#rm ./cuda.run -#_INSTALL_CUDA +RUN <<_INSTALL_CUDA +#!/bin/bash +set -e +cuda_prefix="https://developer.download.nvidia.com/compute/cuda/" +cuda_suffix="" +if [[ "${TARGETPLATFORM}" == 'linux/arm64' ]]; then + cuda_suffix="_sbsa" +fi +url="${cuda_prefix}${CUDA_VERSION}/local_installers/cuda_${CUDA_VERSION}_${CUDA_BUILD}_linux${cuda_suffix}.run" +echo "cuda url: ${url}" +wget "$url" --progress=bar:force:noscroll -q --show-progress -O ./cuda.run +chmod a+x ./cuda.run +./cuda.run --silent --toolkit --toolkitpath=/build/cuda --no-opengl-libs --no-man-page --no-drm +rm ./cuda.run +_INSTALL_CUDA # copy repository WORKDIR /build/sunshine/ @@ -99,12 +98,11 @@ COPY --link .. . WORKDIR /build/sunshine/build # cmake and cpack -# todo - add cmake argument back in for cuda support "-DCMAKE_CUDA_COMPILER:PATH=/build/cuda/bin/nvcc \" -# todo - re-enable "DSUNSHINE_ENABLE_CUDA" RUN <<_MAKE #!/bin/bash set -e cmake \ + -DCMAKE_CUDA_COMPILER:PATH=/build/cuda/bin/nvcc \ -DBUILD_WERROR=ON \ -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_INSTALL_PREFIX=/usr \ @@ -113,7 +111,7 @@ cmake \ -DSUNSHINE_ENABLE_WAYLAND=ON \ -DSUNSHINE_ENABLE_X11=ON \ -DSUNSHINE_ENABLE_DRM=ON \ - -DSUNSHINE_ENABLE_CUDA=OFF \ + -DSUNSHINE_ENABLE_CUDA=ON \ /build/sunshine make -j "$(nproc)" cpack -G RPM diff --git a/docker/fedora-39.dockerfile b/docker/fedora-39.dockerfile index e942a8aa916..7bdb4adc216 100644 --- a/docker/fedora-39.dockerfile +++ b/docker/fedora-39.dockerfile @@ -68,28 +68,27 @@ dnf clean all rm -rf /var/cache/yum _DEPS -# todo - enable cuda once it's supported for gcc 13 and fedora 39 -## install cuda -#WORKDIR /build/cuda -## versions: https://developer.nvidia.com/cuda-toolkit-archive -#ENV CUDA_VERSION="12.0.0" -#ENV CUDA_BUILD="525.60.13" -## hadolint ignore=SC3010 -#RUN <<_INSTALL_CUDA -##!/bin/bash -#set -e -#cuda_prefix="https://developer.download.nvidia.com/compute/cuda/" -#cuda_suffix="" -#if [[ "${TARGETPLATFORM}" == 'linux/arm64' ]]; then -# cuda_suffix="_sbsa" -#fi -#url="${cuda_prefix}${CUDA_VERSION}/local_installers/cuda_${CUDA_VERSION}_${CUDA_BUILD}_linux${cuda_suffix}.run" -#echo "cuda url: ${url}" -#wget "$url" --progress=bar:force:noscroll -q --show-progress -O ./cuda.run -#chmod a+x ./cuda.run -#./cuda.run --silent --toolkit --toolkitpath=/build/cuda --no-opengl-libs --no-man-page --no-drm -#rm ./cuda.run -#_INSTALL_CUDA +# install cuda +WORKDIR /build/cuda +# versions: https://developer.nvidia.com/cuda-toolkit-archive +ENV CUDA_VERSION="12.4.0" +ENV CUDA_BUILD="550.54.14" +# hadolint ignore=SC3010 +RUN <<_INSTALL_CUDA +#!/bin/bash +set -e +cuda_prefix="https://developer.download.nvidia.com/compute/cuda/" +cuda_suffix="" +if [[ "${TARGETPLATFORM}" == 'linux/arm64' ]]; then + cuda_suffix="_sbsa" +fi +url="${cuda_prefix}${CUDA_VERSION}/local_installers/cuda_${CUDA_VERSION}_${CUDA_BUILD}_linux${cuda_suffix}.run" +echo "cuda url: ${url}" +wget "$url" --progress=bar:force:noscroll -q --show-progress -O ./cuda.run +chmod a+x ./cuda.run +./cuda.run --silent --toolkit --toolkitpath=/build/cuda --no-opengl-libs --no-man-page --no-drm +rm ./cuda.run +_INSTALL_CUDA # copy repository WORKDIR /build/sunshine/ @@ -99,12 +98,11 @@ COPY --link .. . WORKDIR /build/sunshine/build # cmake and cpack -# todo - add cmake argument back in for cuda support "-DCMAKE_CUDA_COMPILER:PATH=/build/cuda/bin/nvcc \" -# todo - re-enable "DSUNSHINE_ENABLE_CUDA" RUN <<_MAKE #!/bin/bash set -e cmake \ + -DCMAKE_CUDA_COMPILER:PATH=/build/cuda/bin/nvcc \ -DBUILD_WERROR=ON \ -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_INSTALL_PREFIX=/usr \ @@ -113,7 +111,7 @@ cmake \ -DSUNSHINE_ENABLE_WAYLAND=ON \ -DSUNSHINE_ENABLE_X11=ON \ -DSUNSHINE_ENABLE_DRM=ON \ - -DSUNSHINE_ENABLE_CUDA=OFF \ + -DSUNSHINE_ENABLE_CUDA=ON \ /build/sunshine make -j "$(nproc)" cpack -G RPM diff --git a/docs/source/about/setup.rst b/docs/source/about/setup.rst index 70ed96f57f7..2a2b015ce9c 100644 --- a/docs/source/about/setup.rst +++ b/docs/source/about/setup.rst @@ -43,8 +43,8 @@ Install sunshine_{arch}.flatpak 12.0.0 525.60.13 50;52;60;61;62;70;75;80;86;90 sunshine-debian-bookworm-{arch}.deb 12.0.0 525.60.13 50;52;60;61;62;70;75;80;86;90 sunshine-debian-bullseye-{arch}.deb 11.8.0 450.80.02 35;50;52;60;61;62;70;75;80;86;90 - sunshine-fedora-38-{arch}.rpm unavailable unavailable none - sunshine-fedora-39-{arch}.rpm unavailable unavailable none + sunshine-fedora-38-{arch}.rpm 12.4.0 525.60.13 50;52;60;61;62;70;75;80;86;90 + sunshine-fedora-39-{arch}.rpm 12.4.0 525.60.13 50;52;60;61;62;70;75;80;86;90 sunshine-ubuntu-20.04-{arch}.deb 11.8.0 450.80.02 35;50;52;60;61;62;70;75;80;86;90 sunshine-ubuntu-22.04-{arch}.deb 11.8.0 450.80.02 35;50;52;60;61;62;70;75;80;86;90 =========================================== ============== ============== ================================ From 3e49e25c63eed5cbc3d9596515ce359603060bdd Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Wed, 13 Mar 2024 10:44:31 -0400 Subject: [PATCH 115/182] chore: bump version to v0.22.1 (#2221) Co-authored-by: Cameron Gutman --- CHANGELOG.md | 38 ++++++++++++++++++++++++++++++++++++++ CMakeLists.txt | 2 +- 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5f8290545da..6ab14e01b8e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,42 @@ # Changelog +## [0.22.1] - 2024-03-13 +**Breaking** +- (ArchLinux) Drop support for standalone PKGBUILD files. Use the binary Arch package or install via AUR instead. +- (macOS) Drop support for experimental dmg package. Use Homebrew or MacPorts instead. + +**Added** +- (macOS) Added Homebrew support + +**Changed** +- (Process/Windows) The working directory is now searched first when the command contains a relative path +- (ArchLinux) The kmsgrab capture backend is now compiled by default to support Wayland capture on non-wlroots-based compositors +- (Capture/Linux) X11 capture is now preferred over kmsgrab for cards that lack atomic modesetting support to ensure cursor capture works +- (Capture/Linux) Kmsgrab will only choose NVENC by default if the display is connected to the Nvidia GPU to avoid possible EGL import failures + +**Fixed** +- (Config) Fix unsupported resolution error with some Moonlight clients +- (Capture/Windows) Fix crash when streaming Ryujinx, Red Alert 2, and other apps that use unusually sized monochrome cursors +- (Capture/Linux) Fix crash in KMS cursor capture when running on Arch-based distros +- (Capture/Linux) Fix crash if CUDA GPU has a PCI ID with hexadecimal digits greater than 9 +- (Process/Windows) Fix starting apps when the working directory is enclosed in quotes +- (Process/Windows) Fix process tree tracking when the app is launched via a cmd.exe trampoline +- (Installer/Windows) Fix slow operation during ViGEmBus installation that may cause the installer to appear stuck +- (Build/macOS) Fix issues building on macOS 13 and 14 +- (Build/Linux) Fix missing install script in the Arch binary package +- (Build/Linux) Fix missing optional dependencies in the Arch binary package +- (Build/Linux) Ensure correct Arch pkg is published to GitHub releases +- (Capture/Linux) Fix mismatched case and unhandled exception in CUDA device lookup +- (Config) Add missing resolution to default config ui +- (Linux) Fix udev rules for uinput access not working until after reboot +- (Linux) Fix wrong path in desktop files +- (Tray) Cache icons to avoid possible DRM issues +- (Linux) Migrate old config files to new location if env SUNSHINE_MIGRATE_CONFIG=1 is set (automatically set for Flatpak) +- (Linux/Fedora) Re-enable CUDA support and bump to 12.4.0 + +**Misc** +- (Build/Windows) Adjust Windows debuginfo artifact to reduce confusion with real release binaries + ## [0.22.0] - 2024-03-03 **Breaking** - (Network) Clients must now be paired with the host before they can use Wake-on-LAN @@ -720,3 +757,4 @@ settings. In v0.17.0, games now run under your user account without elevated pri [0.20.0]: https://github.com/LizardByte/Sunshine/releases/tag/v0.20.0 [0.21.0]: https://github.com/LizardByte/Sunshine/releases/tag/v0.21.0 [0.22.0]: https://github.com/LizardByte/Sunshine/releases/tag/v0.22.0 +[0.22.1]: https://github.com/LizardByte/Sunshine/releases/tag/v0.22.1 diff --git a/CMakeLists.txt b/CMakeLists.txt index 593d86b82dd..fce20cd2bb7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,7 +3,7 @@ cmake_minimum_required(VERSION 3.18) # todo - set this conditionally # todo - set version to 0.0.0 once confident in automated versioning -project(Sunshine VERSION 0.22.0 +project(Sunshine VERSION 0.22.1 DESCRIPTION "Self-hosted game stream host for Moonlight" HOMEPAGE_URL "https://app.lizardbyte.dev/Sunshine") From 22736c4ce92d3a359a96db1cd9107adea5db484f Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Wed, 13 Mar 2024 18:05:45 -0400 Subject: [PATCH 116/182] Fix(linux/fedora39) patch system headers so build succeeds with cuda (#2253) Co-authored-by: Cameron Gutman <2695644+cgutman@users.noreply.github.com> --- .gitattributes | 3 +++ docker/fedora-39.dockerfile | 7 +++++++ 2 files changed, 10 insertions(+) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000000..1ccee08a107 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,3 @@ +# ensure dockerfiles are checked out with LF line endings +Dockerfile text eol=lf +*.dockerfile text eol=lf diff --git a/docker/fedora-39.dockerfile b/docker/fedora-39.dockerfile index 7bdb4adc216..20dae39a797 100644 --- a/docker/fedora-39.dockerfile +++ b/docker/fedora-39.dockerfile @@ -81,6 +81,13 @@ cuda_prefix="https://developer.download.nvidia.com/compute/cuda/" cuda_suffix="" if [[ "${TARGETPLATFORM}" == 'linux/arm64' ]]; then cuda_suffix="_sbsa" + + # patch headers https://bugs.launchpad.net/ubuntu/+source/mumax3/+bug/2032624 + sed -i 's/__Float32x4_t/int/g' /usr/include/bits/math-vector.h + sed -i 's/__Float64x2_t/int/g' /usr/include/bits/math-vector.h + sed -i 's/__SVFloat32_t/float/g' /usr/include/bits/math-vector.h + sed -i 's/__SVFloat64_t/float/g' /usr/include/bits/math-vector.h + sed -i 's/__SVBool_t/int/g' /usr/include/bits/math-vector.h fi url="${cuda_prefix}${CUDA_VERSION}/local_installers/cuda_${CUDA_VERSION}_${CUDA_BUILD}_linux${cuda_suffix}.run" echo "cuda url: ${url}" From c43dd2489f217e7ab04a16fc1bf7534cf6266e5f Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Wed, 13 Mar 2024 17:32:04 -0500 Subject: [PATCH 117/182] Don't update tray icon after tray_exit() was called --- CHANGELOG.md | 1 + src/system_tray.cpp | 20 ++++++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6ab14e01b8e..d1c67c68c3f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,6 +31,7 @@ - (Linux) Fix udev rules for uinput access not working until after reboot - (Linux) Fix wrong path in desktop files - (Tray) Cache icons to avoid possible DRM issues +- (Tray) Fix attempt to update tray icon after it was destroyed - (Linux) Migrate old config files to new location if env SUNSHINE_MIGRATE_CONFIG=1 is set (automatically set for Flatpak) - (Linux/Fedora) Re-enable CUDA support and bump to 12.4.0 diff --git a/src/system_tray.cpp b/src/system_tray.cpp index eb5948a41c1..c34c3d75a98 100644 --- a/src/system_tray.cpp +++ b/src/system_tray.cpp @@ -47,6 +47,8 @@ using namespace std::literals; // system_tray namespace namespace system_tray { + static std::atomic tray_initialized = false; + /** * @brief Callback for opening the UI from the system tray. * @param item The tray menu item. @@ -239,6 +241,7 @@ namespace system_tray { BOOST_LOG(info) << "System tray created"sv; } + tray_initialized = true; while (tray_loop(1) == 0) { BOOST_LOG(debug) << "System tray loop"sv; } @@ -275,6 +278,7 @@ namespace system_tray { */ int end_tray() { + tray_initialized = false; tray_exit(); return 0; } @@ -285,6 +289,10 @@ namespace system_tray { */ void update_tray_playing(std::string app_name) { + if (!tray_initialized) { + return; + } + tray.notification_title = NULL; tray.notification_text = NULL; tray.notification_cb = NULL; @@ -307,6 +315,10 @@ namespace system_tray { */ void update_tray_pausing(std::string app_name) { + if (!tray_initialized) { + return; + } + tray.notification_title = NULL; tray.notification_text = NULL; tray.notification_cb = NULL; @@ -329,6 +341,10 @@ namespace system_tray { */ void update_tray_stopped(std::string app_name) { + if (!tray_initialized) { + return; + } + tray.notification_title = NULL; tray.notification_text = NULL; tray.notification_cb = NULL; @@ -350,6 +366,10 @@ namespace system_tray { */ void update_tray_require_pin() { + if (!tray_initialized) { + return; + } + tray.notification_title = NULL; tray.notification_text = NULL; tray.notification_cb = NULL; From 476141d740bc5ae622967db6c57ccf156b2a6483 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 14 Mar 2024 21:13:55 -0400 Subject: [PATCH 118/182] build(deps): bump LizardByte/homebrew-release-action from 2024.311.172824 to 2024.314.134529 (#2264) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/CI.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 80c309ca052..09a0aaeae66 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -588,7 +588,7 @@ jobs: echo "publish=${PUBLISH}" >> $GITHUB_OUTPUT - name: Validate and Publish Homebrew Formula - uses: LizardByte/homebrew-release-action@v2024.311.172824 + uses: LizardByte/homebrew-release-action@v2024.314.134529 with: formula_file: ${{ github.workspace }}/homebrew/sunshine.rb git_email: ${{ secrets.GH_BOT_EMAIL }} From b523945f48ba5542dc5a3fd1d3897ba726092723 Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Thu, 14 Mar 2024 18:11:27 -0500 Subject: [PATCH 119/182] Update tray submodule to fix broken tray icon on some systems --- third-party/tray | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/third-party/tray b/third-party/tray index a08c1025c3f..4d8b798cafd 160000 --- a/third-party/tray +++ b/third-party/tray @@ -1 +1 @@ -Subproject commit a08c1025c3f158d6b6c4b9bcf0ab770291d26896 +Subproject commit 4d8b798cafdd11285af9409c16b5f792968e0045 From f66a7d5da65a00d5b8cb8aed79169b67be9c1076 Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Thu, 14 Mar 2024 18:37:00 -0500 Subject: [PATCH 120/182] Fix dereferencing a null pointer if SUNSHINE_MIGRATE_CONFIG doesn't exist --- src/platform/linux/misc.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/platform/linux/misc.cpp b/src/platform/linux/misc.cpp index 884c0e9047a..b4de6005d13 100644 --- a/src/platform/linux/misc.cpp +++ b/src/platform/linux/misc.cpp @@ -104,6 +104,7 @@ namespace platf { bool migrate_config = true; const char *dir; const char *homedir; + const char *migrate_envvar; fs::path config_path; // Get the home directory @@ -130,7 +131,8 @@ namespace platf { } // migrate from the old config location if necessary - if (migrate_config && found && getenv("SUNSHINE_MIGRATE_CONFIG") == "1"sv) { + migrate_envvar = getenv("SUNSHINE_MIGRATE_CONFIG"); + if (migrate_config && found && migrate_envvar && strcmp(migrate_envvar, "1") == 0) { fs::path old_config_path = fs::path(homedir) / ".config/sunshine"sv; if (old_config_path != config_path && fs::exists(old_config_path)) { if (!fs::exists(config_path)) { From aa1985dec84fcf26b941a2bde4998351bbb2f2a9 Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Fri, 15 Mar 2024 00:11:02 -0500 Subject: [PATCH 121/182] Avoid calling Boost logging functions in appdata() The app data directory is needed prior to logging initialization. --- src/platform/linux/misc.cpp | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/platform/linux/misc.cpp b/src/platform/linux/misc.cpp index b4de6005d13..b4e3d1aec9b 100644 --- a/src/platform/linux/misc.cpp +++ b/src/platform/linux/misc.cpp @@ -10,6 +10,7 @@ // standard includes #include +#include // lib includes #include @@ -98,6 +99,11 @@ namespace platf { return ifaddr_t { p }; } + /** + * @brief Performs migration if necessary, then returns the appdata directory. + * @details This is used for the log directory, so it cannot invoke Boost logging! + * @return The path of the appdata directory that should be used. + */ fs::path appdata() { bool found = false; @@ -136,15 +142,18 @@ namespace platf { fs::path old_config_path = fs::path(homedir) / ".config/sunshine"sv; if (old_config_path != config_path && fs::exists(old_config_path)) { if (!fs::exists(config_path)) { - BOOST_LOG(info) << "Migrating config from "sv << old_config_path << " to "sv << config_path; + std::cout << "Migrating config from "sv << old_config_path << " to "sv << config_path << std::endl; std::error_code ec; fs::rename(old_config_path, config_path, ec); if (ec) { + std::cerr << "Migration failed: " << ec.message() << std::endl; return old_config_path; } } else { - BOOST_LOG(warning) << "Config exists in both "sv << old_config_path << " and "sv << config_path << ", using "sv << config_path << "... it is recommended to remove "sv << old_config_path; + // We cannot use Boost logging because it hasn't been initialized yet! + std::cerr << "Config exists in both "sv << old_config_path << " and "sv << config_path << ". Using "sv << config_path << " for config" << std::endl; + std::cerr << "It is recommended to remove "sv << old_config_path << std::endl; } } } From 8c9e14e3356ee05b1f48f777e44cbfde9e74a083 Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Fri, 15 Mar 2024 00:15:55 -0500 Subject: [PATCH 122/182] Only attempt a config migration once per launch --- src/platform/linux/misc.cpp | 101 +++++++++++++++++++----------------- 1 file changed, 53 insertions(+), 48 deletions(-) diff --git a/src/platform/linux/misc.cpp b/src/platform/linux/misc.cpp index b4e3d1aec9b..092c5607983 100644 --- a/src/platform/linux/misc.cpp +++ b/src/platform/linux/misc.cpp @@ -106,57 +106,62 @@ namespace platf { */ fs::path appdata() { - bool found = false; - bool migrate_config = true; - const char *dir; - const char *homedir; - const char *migrate_envvar; - fs::path config_path; - - // Get the home directory - if ((homedir = getenv("HOME")) == nullptr || strlen(homedir) == 0) { - // If HOME is empty or not set, use the current user's home directory - homedir = getpwuid(geteuid())->pw_dir; - } - - // May be set if running under a systemd service with the ConfigurationDirectory= option set. - if ((dir = getenv("CONFIGURATION_DIRECTORY")) != nullptr && strlen(dir) > 0) { - found = true; - config_path = fs::path(dir) / "sunshine"sv; - } - // Otherwise, follow the XDG base directory specification: - // https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html - if (!found && (dir = getenv("XDG_CONFIG_HOME")) != nullptr && strlen(dir) > 0) { - found = true; - config_path = fs::path(dir) / "sunshine"sv; - } - // As a last resort, use the home directory - if (!found) { - migrate_config = false; - config_path = fs::path(homedir) / ".config/sunshine"sv; - } - - // migrate from the old config location if necessary - migrate_envvar = getenv("SUNSHINE_MIGRATE_CONFIG"); - if (migrate_config && found && migrate_envvar && strcmp(migrate_envvar, "1") == 0) { - fs::path old_config_path = fs::path(homedir) / ".config/sunshine"sv; - if (old_config_path != config_path && fs::exists(old_config_path)) { - if (!fs::exists(config_path)) { - std::cout << "Migrating config from "sv << old_config_path << " to "sv << config_path << std::endl; - std::error_code ec; - fs::rename(old_config_path, config_path, ec); - if (ec) { - std::cerr << "Migration failed: " << ec.message() << std::endl; - return old_config_path; + static std::once_flag migration_flag; + static fs::path config_path; + + // Ensure migration is only attempted once + std::call_once(migration_flag, []() { + bool found = false; + bool migrate_config = true; + const char *dir; + const char *homedir; + const char *migrate_envvar; + + // Get the home directory + if ((homedir = getenv("HOME")) == nullptr || strlen(homedir) == 0) { + // If HOME is empty or not set, use the current user's home directory + homedir = getpwuid(geteuid())->pw_dir; + } + + // May be set if running under a systemd service with the ConfigurationDirectory= option set. + if ((dir = getenv("CONFIGURATION_DIRECTORY")) != nullptr && strlen(dir) > 0) { + found = true; + config_path = fs::path(dir) / "sunshine"sv; + } + // Otherwise, follow the XDG base directory specification: + // https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html + if (!found && (dir = getenv("XDG_CONFIG_HOME")) != nullptr && strlen(dir) > 0) { + found = true; + config_path = fs::path(dir) / "sunshine"sv; + } + // As a last resort, use the home directory + if (!found) { + migrate_config = false; + config_path = fs::path(homedir) / ".config/sunshine"sv; + } + + // migrate from the old config location if necessary + migrate_envvar = getenv("SUNSHINE_MIGRATE_CONFIG"); + if (migrate_config && found && migrate_envvar && strcmp(migrate_envvar, "1") == 0) { + fs::path old_config_path = fs::path(homedir) / ".config/sunshine"sv; + if (old_config_path != config_path && fs::exists(old_config_path)) { + if (!fs::exists(config_path)) { + std::cout << "Migrating config from "sv << old_config_path << " to "sv << config_path << std::endl; + std::error_code ec; + fs::rename(old_config_path, config_path, ec); + if (ec) { + std::cerr << "Migration failed: " << ec.message() << std::endl; + config_path = old_config_path; + } + } + else { + // We cannot use Boost logging because it hasn't been initialized yet! + std::cerr << "Config exists in both "sv << old_config_path << " and "sv << config_path << ". Using "sv << config_path << " for config" << std::endl; + std::cerr << "It is recommended to remove "sv << old_config_path << std::endl; } - } - else { - // We cannot use Boost logging because it hasn't been initialized yet! - std::cerr << "Config exists in both "sv << old_config_path << " and "sv << config_path << ". Using "sv << config_path << " for config" << std::endl; - std::cerr << "It is recommended to remove "sv << old_config_path << std::endl; } } - } + }); return config_path; } From 15c5e76ed4d994439322812df36e25f7b26c1814 Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Fri, 15 Mar 2024 00:44:53 -0500 Subject: [PATCH 123/182] Use a copy+delete instead of a move operation for config migration This can handle migration across filesystems. --- src/platform/linux/misc.cpp | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/src/platform/linux/misc.cpp b/src/platform/linux/misc.cpp index 092c5607983..980c0804858 100644 --- a/src/platform/linux/misc.cpp +++ b/src/platform/linux/misc.cpp @@ -143,12 +143,30 @@ namespace platf { // migrate from the old config location if necessary migrate_envvar = getenv("SUNSHINE_MIGRATE_CONFIG"); if (migrate_config && found && migrate_envvar && strcmp(migrate_envvar, "1") == 0) { + std::error_code ec; fs::path old_config_path = fs::path(homedir) / ".config/sunshine"sv; - if (old_config_path != config_path && fs::exists(old_config_path)) { - if (!fs::exists(config_path)) { + if (old_config_path != config_path && fs::exists(old_config_path, ec)) { + if (!fs::exists(config_path, ec)) { std::cout << "Migrating config from "sv << old_config_path << " to "sv << config_path << std::endl; - std::error_code ec; - fs::rename(old_config_path, config_path, ec); + if (!ec) { + // Create the new directory tree if it doesn't already exist + fs::create_directories(config_path, ec); + } + if (!ec) { + // Copy the old directory into the new location + // NB: We use a copy instead of a move so that cross-volume migrations work + fs::copy(old_config_path, config_path, fs::copy_options::recursive | fs::copy_options::copy_symlinks, ec); + } + if (!ec) { + // If the copy was successful, delete the original directory + fs::remove_all(old_config_path, ec); + if (ec) { + std::cerr << "Failed to clean up old config directory: " << ec.message() << std::endl; + + // This is not fatal. Next time we start, we'll warn the user to delete the old one. + ec.clear(); + } + } if (ec) { std::cerr << "Migration failed: " << ec.message() << std::endl; config_path = old_config_path; From bd5c50041cc020f648a45f8130ed421f2c8fc901 Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Thu, 14 Mar 2024 18:37:27 -0500 Subject: [PATCH 124/182] Update changelog and bump version to v0.22.2 --- CHANGELOG.md | 7 +++++++ CMakeLists.txt | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d1c67c68c3f..d4fbff3a4ff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## [0.22.2] - 2024-03-15 +**Fixed** +- (Tray/Windows) Fix broken system tray icon on some systems +- (Linux) Fix crash when XDG_CONFIG_HOME or CONFIGURATION_DIRECTORY are set +- (Linux) Fix config migration across filesystems and with non-existent parent directories + ## [0.22.1] - 2024-03-13 **Breaking** - (ArchLinux) Drop support for standalone PKGBUILD files. Use the binary Arch package or install via AUR instead. @@ -759,3 +765,4 @@ settings. In v0.17.0, games now run under your user account without elevated pri [0.21.0]: https://github.com/LizardByte/Sunshine/releases/tag/v0.21.0 [0.22.0]: https://github.com/LizardByte/Sunshine/releases/tag/v0.22.0 [0.22.1]: https://github.com/LizardByte/Sunshine/releases/tag/v0.22.1 +[0.22.2]: https://github.com/LizardByte/Sunshine/releases/tag/v0.22.2 diff --git a/CMakeLists.txt b/CMakeLists.txt index fce20cd2bb7..ebff395abbc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,7 +3,7 @@ cmake_minimum_required(VERSION 3.18) # todo - set this conditionally # todo - set version to 0.0.0 once confident in automated versioning -project(Sunshine VERSION 0.22.1 +project(Sunshine VERSION 0.22.2 DESCRIPTION "Self-hosted game stream host for Moonlight" HOMEPAGE_URL "https://app.lizardbyte.dev/Sunshine") From 7534fa10230483ae47e94c4e0a89e85f13d19dc5 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sat, 16 Mar 2024 09:04:29 -0400 Subject: [PATCH 125/182] refactor(video): move encoder declarations to header (#2185) --- src/video.cpp | 157 ++------------------------------------------- src/video.h | 172 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 179 insertions(+), 150 deletions(-) diff --git a/src/video.cpp b/src/video.cpp index f786aeb59f0..920ce1a4857 100644 --- a/src/video.cpp +++ b/src/video.cpp @@ -14,7 +14,6 @@ extern "C" { #include #include #include -#include } #include "cbs.h" @@ -51,12 +50,6 @@ namespace video { av_buffer_unref(&ref); } - using avcodec_ctx_t = util::safe_ptr; - using avcodec_frame_t = util::safe_ptr; - using avcodec_buffer_t = util::safe_ptr; - using sws_t = util::safe_ptr; - using img_event_t = std::shared_ptr>>; - namespace nv { enum class profile_h264_e : int { @@ -87,11 +80,6 @@ namespace video { }; } // namespace qsv - platf::mem_type_e - map_base_dev_type(AVHWDeviceType type); - platf::pix_fmt_e - map_pix_fmt(AVPixelFormat fmt); - util::Either dxgi_init_avcodec_hardware_input_buffer(platf::avcodec_encode_device_t *); util::Either @@ -288,137 +276,6 @@ namespace video { ALWAYS_REPROBE = 1 << 9, // This is an encoder of last resort and we want to aggressively probe for a better one }; - struct encoder_platform_formats_t { - virtual ~encoder_platform_formats_t() = default; - platf::mem_type_e dev_type; - platf::pix_fmt_e pix_fmt_8bit, pix_fmt_10bit; - }; - - struct encoder_platform_formats_avcodec: encoder_platform_formats_t { - using init_buffer_function_t = std::function(platf::avcodec_encode_device_t *)>; - - encoder_platform_formats_avcodec( - const AVHWDeviceType &avcodec_base_dev_type, - const AVHWDeviceType &avcodec_derived_dev_type, - const AVPixelFormat &avcodec_dev_pix_fmt, - const AVPixelFormat &avcodec_pix_fmt_8bit, - const AVPixelFormat &avcodec_pix_fmt_10bit, - const init_buffer_function_t &init_avcodec_hardware_input_buffer_function): - avcodec_base_dev_type { avcodec_base_dev_type }, - avcodec_derived_dev_type { avcodec_derived_dev_type }, - avcodec_dev_pix_fmt { avcodec_dev_pix_fmt }, - avcodec_pix_fmt_8bit { avcodec_pix_fmt_8bit }, - avcodec_pix_fmt_10bit { avcodec_pix_fmt_10bit }, - init_avcodec_hardware_input_buffer { init_avcodec_hardware_input_buffer_function } { - dev_type = map_base_dev_type(avcodec_base_dev_type); - pix_fmt_8bit = map_pix_fmt(avcodec_pix_fmt_8bit); - pix_fmt_10bit = map_pix_fmt(avcodec_pix_fmt_10bit); - } - - AVHWDeviceType avcodec_base_dev_type, avcodec_derived_dev_type; - AVPixelFormat avcodec_dev_pix_fmt; - AVPixelFormat avcodec_pix_fmt_8bit, avcodec_pix_fmt_10bit; - - init_buffer_function_t init_avcodec_hardware_input_buffer; - }; - - struct encoder_platform_formats_nvenc: encoder_platform_formats_t { - encoder_platform_formats_nvenc( - const platf::mem_type_e &dev_type, - const platf::pix_fmt_e &pix_fmt_8bit, - const platf::pix_fmt_e &pix_fmt_10bit) { - encoder_platform_formats_t::dev_type = dev_type; - encoder_platform_formats_t::pix_fmt_8bit = pix_fmt_8bit; - encoder_platform_formats_t::pix_fmt_10bit = pix_fmt_10bit; - } - }; - - struct encoder_t { - std::string_view name; - enum flag_e { - PASSED, // Is supported - REF_FRAMES_RESTRICT, // Set maximum reference frames - 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 - MAX_FLAGS - }; - - static std::string_view - from_flag(flag_e flag) { -#define _CONVERT(x) \ - case flag_e::x: \ - return #x##sv - switch (flag) { - _CONVERT(PASSED); - _CONVERT(REF_FRAMES_RESTRICT); - _CONVERT(CBR); - _CONVERT(DYNAMIC_RANGE); - _CONVERT(VUI_PARAMETERS); - _CONVERT(MAX_FLAGS); - } -#undef _CONVERT - - return "unknown"sv; - } - - struct option_t { - KITTY_DEFAULT_CONSTR_MOVE(option_t) - option_t(const option_t &) = default; - - std::string name; - std::variant *, std::function, std::string, std::string *> value; - - option_t(std::string &&name, decltype(value) &&value): - name { std::move(name) }, value { std::move(value) } {} - }; - - const std::unique_ptr platform_formats; - - struct { - std::vector common_options; - std::vector sdr_options; - std::vector hdr_options; - std::vector fallback_options; - - // QP option to set in the case that CBR/VBR is not supported - // by the encoder. If CBR/VBR is guaranteed to be supported, - // don't specify this option to avoid wasteful encoder probing. - std::optional qp; - - std::string name; - std::bitset capabilities; - - bool - operator[](flag_e flag) const { - return capabilities[(std::size_t) flag]; - } - - std::bitset::reference - operator[](flag_e flag) { - return capabilities[(std::size_t) flag]; - } - } av1, hevc, h264; - - uint32_t flags; - }; - - struct encode_session_t { - virtual ~encode_session_t() = default; - - virtual int - convert(platf::img_t &img) = 0; - - virtual void - request_idr_frame() = 0; - - virtual void - request_normal_frame() = 0; - - virtual void - invalidate_ref_frames(int64_t first_frame, int64_t last_frame) = 0; - }; - class avcodec_encode_session_t: public encode_session_t { public: avcodec_encode_session_t() = default; @@ -586,7 +443,7 @@ namespace video { auto capture_thread_sync = safe::make_shared(start_capture_sync, end_capture_sync); #ifdef _WIN32 - static encoder_t nvenc { + encoder_t nvenc { "nvenc"sv, std::make_unique( platf::mem_type_e::dxgi, @@ -630,7 +487,7 @@ namespace video { PARALLEL_ENCODING | REF_FRAMES_INVALIDATION // flags }; #elif !defined(__APPLE__) - static encoder_t nvenc { + encoder_t nvenc { "nvenc"sv, std::make_unique( #ifdef _WIN32 @@ -718,7 +575,7 @@ namespace video { #endif #ifdef _WIN32 - static encoder_t quicksync { + encoder_t quicksync { "quicksync"sv, std::make_unique( AV_HWDEVICE_TYPE_D3D11VA, AV_HWDEVICE_TYPE_QSV, @@ -799,7 +656,7 @@ namespace video { PARALLEL_ENCODING | CBR_WITH_VBR | RELAXED_COMPLIANCE | NO_RC_BUF_LIMIT }; - static encoder_t amdvce { + encoder_t amdvce { "amdvce"sv, std::make_unique( AV_HWDEVICE_TYPE_D3D11VA, AV_HWDEVICE_TYPE_NONE, @@ -871,7 +728,7 @@ namespace video { }; #endif - static encoder_t software { + encoder_t software { "software"sv, std::make_unique( AV_HWDEVICE_TYPE_NONE, AV_HWDEVICE_TYPE_NONE, @@ -936,7 +793,7 @@ namespace video { }; #ifdef __linux__ - static encoder_t vaapi { + encoder_t vaapi { "vaapi"sv, std::make_unique( AV_HWDEVICE_TYPE_VAAPI, AV_HWDEVICE_TYPE_NONE, @@ -1004,7 +861,7 @@ namespace video { #endif #ifdef __APPLE__ - static encoder_t videotoolbox { + encoder_t videotoolbox { "videotoolbox"sv, std::make_unique( AV_HWDEVICE_TYPE_VIDEOTOOLBOX, AV_HWDEVICE_TYPE_NONE, diff --git a/src/video.h b/src/video.h index fec5c38b343..eb8eabc358d 100644 --- a/src/video.h +++ b/src/video.h @@ -11,11 +11,181 @@ extern "C" { #include +#include } struct AVPacket; namespace video { + platf::mem_type_e + map_base_dev_type(AVHWDeviceType type); + platf::pix_fmt_e + map_pix_fmt(AVPixelFormat fmt); + + void + free_ctx(AVCodecContext *ctx); + void + free_frame(AVFrame *frame); + void + free_buffer(AVBufferRef *ref); + + using avcodec_ctx_t = util::safe_ptr; + using avcodec_frame_t = util::safe_ptr; + using avcodec_buffer_t = util::safe_ptr; + using sws_t = util::safe_ptr; + using img_event_t = std::shared_ptr>>; + + struct encoder_platform_formats_t { + virtual ~encoder_platform_formats_t() = default; + platf::mem_type_e dev_type; + platf::pix_fmt_e pix_fmt_8bit, pix_fmt_10bit; + }; + + struct encoder_platform_formats_avcodec: encoder_platform_formats_t { + using init_buffer_function_t = std::function(platf::avcodec_encode_device_t *)>; + + encoder_platform_formats_avcodec( + const AVHWDeviceType &avcodec_base_dev_type, + const AVHWDeviceType &avcodec_derived_dev_type, + const AVPixelFormat &avcodec_dev_pix_fmt, + const AVPixelFormat &avcodec_pix_fmt_8bit, + const AVPixelFormat &avcodec_pix_fmt_10bit, + const init_buffer_function_t &init_avcodec_hardware_input_buffer_function): + avcodec_base_dev_type { avcodec_base_dev_type }, + avcodec_derived_dev_type { avcodec_derived_dev_type }, + avcodec_dev_pix_fmt { avcodec_dev_pix_fmt }, + avcodec_pix_fmt_8bit { avcodec_pix_fmt_8bit }, + avcodec_pix_fmt_10bit { avcodec_pix_fmt_10bit }, + init_avcodec_hardware_input_buffer { init_avcodec_hardware_input_buffer_function } { + dev_type = map_base_dev_type(avcodec_base_dev_type); + pix_fmt_8bit = map_pix_fmt(avcodec_pix_fmt_8bit); + pix_fmt_10bit = map_pix_fmt(avcodec_pix_fmt_10bit); + } + + AVHWDeviceType avcodec_base_dev_type, avcodec_derived_dev_type; + AVPixelFormat avcodec_dev_pix_fmt; + AVPixelFormat avcodec_pix_fmt_8bit, avcodec_pix_fmt_10bit; + + init_buffer_function_t init_avcodec_hardware_input_buffer; + }; + + struct encoder_platform_formats_nvenc: encoder_platform_formats_t { + encoder_platform_formats_nvenc( + const platf::mem_type_e &dev_type, + const platf::pix_fmt_e &pix_fmt_8bit, + const platf::pix_fmt_e &pix_fmt_10bit) { + encoder_platform_formats_t::dev_type = dev_type; + encoder_platform_formats_t::pix_fmt_8bit = pix_fmt_8bit; + encoder_platform_formats_t::pix_fmt_10bit = pix_fmt_10bit; + } + }; + + struct encoder_t { + std::string_view name; + enum flag_e { + PASSED, // Is supported + REF_FRAMES_RESTRICT, // Set maximum reference frames + 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 + MAX_FLAGS + }; + + static std::string_view + from_flag(flag_e flag) { +#define _CONVERT(x) \ + case flag_e::x: \ + std::string_view(#x) + switch (flag) { + _CONVERT(PASSED); + _CONVERT(REF_FRAMES_RESTRICT); + _CONVERT(CBR); + _CONVERT(DYNAMIC_RANGE); + _CONVERT(VUI_PARAMETERS); + _CONVERT(MAX_FLAGS); + } +#undef _CONVERT + + return { "unknown" }; + } + + struct option_t { + KITTY_DEFAULT_CONSTR_MOVE(option_t) + option_t(const option_t &) = default; + + std::string name; + std::variant *, std::function, std::string, std::string *> value; + + option_t(std::string &&name, decltype(value) &&value): + name { std::move(name) }, value { std::move(value) } {} + }; + + const std::unique_ptr platform_formats; + + struct codec_t { + std::vector common_options; + std::vector sdr_options; + std::vector hdr_options; + std::vector fallback_options; + + // QP option to set in the case that CBR/VBR is not supported + // by the encoder. If CBR/VBR is guaranteed to be supported, + // don't specify this option to avoid wasteful encoder probing. + std::optional qp; + + std::string name; + std::bitset capabilities; + + bool + operator[](flag_e flag) const { + return capabilities[(std::size_t) flag]; + } + + std::bitset::reference + operator[](flag_e flag) { + return capabilities[(std::size_t) flag]; + } + } av1, hevc, h264; + + uint32_t flags; + }; + + struct encode_session_t { + virtual ~encode_session_t() = default; + + virtual int + convert(platf::img_t &img) = 0; + + virtual void + request_idr_frame() = 0; + + virtual void + request_normal_frame() = 0; + + virtual void + invalidate_ref_frames(int64_t first_frame, int64_t last_frame) = 0; + }; + + // encoders + extern encoder_t software; + +#if !defined(__APPLE__) + extern encoder_t nvenc; // available for windows and linux +#endif + +#ifdef _WIN32 + extern encoder_t amdvce; + extern encoder_t quicksync; +#endif + +#ifdef __linux__ + extern encoder_t vaapi; +#endif + +#ifdef __APPLE__ + extern encoder_t videotoolbox; +#endif + struct packet_raw_t { virtual ~packet_raw_t() = default; @@ -154,6 +324,8 @@ namespace video { config_t config, void *channel_data); + bool + validate_encoder(encoder_t &encoder, bool expect_failure); int probe_encoders(); } // namespace video From 8316f44e10f3f6ef27fc27b3ef6e69f359b1b667 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sun, 17 Mar 2024 00:07:18 -0400 Subject: [PATCH 126/182] ci(linux): refactor linux build (#2275) --- .github/workflows/CI.yml | 108 +++++++++++++++------------------------ 1 file changed, 40 insertions(+), 68 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 09a0aaeae66..a35f2affbcc 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -165,7 +165,7 @@ jobs: remove-android: 'true' remove-haskell: 'true' remove-codeql: 'true' - remove-docker-images: 'false' + remove-docker-images: 'true' - name: Checkout uses: actions/checkout@v4 @@ -291,61 +291,48 @@ jobs: remove-android: 'true' remove-haskell: 'true' remove-codeql: 'true' - remove-docker-images: 'false' + remove-docker-images: 'true' - name: Checkout uses: actions/checkout@v4 with: submodules: recursive + - name: Install wget + run: | + sudo apt-get update -y + sudo apt-get install -y \ + wget + + - name: Install CUDA + env: + CUDA_VERSION: 11.8.0 + CUDA_BUILD: 520.61.05 + timeout-minutes: 4 + run: | + url_base="https://developer.download.nvidia.com/compute/cuda/${CUDA_VERSION}/local_installers" + url="${url_base}/cuda_${CUDA_VERSION}_${CUDA_BUILD}_linux.run" + sudo wget -q -O /root/cuda.run ${url} + sudo chmod a+x /root/cuda.run + sudo /root/cuda.run --silent --toolkit --toolkitpath=/usr/local/cuda --no-opengl-libs --no-man-page --no-drm + sudo rm /root/cuda.run + - name: Setup Dependencies Linux run: | + # allow newer gcc sudo add-apt-repository ppa:ubuntu-toolchain-r/test -y - if [[ ${{ matrix.dist }} == "18.04" ]]; then - # Ubuntu 18.04 packages - sudo add-apt-repository ppa:savoury1/boost-defaults-1.71 -y - - sudo apt-get update -y - sudo apt-get install -y \ - libboost-filesystem1.71-dev \ - libboost-locale1.71-dev \ - libboost-log1.71-dev \ - libboost-regex1.71-dev \ - libboost-program-options1.71-dev - - # Install cmake - wget https://cmake.org/files/v3.22/cmake-3.22.2-linux-x86_64.sh - chmod +x cmake-3.22.2-linux-x86_64.sh - mkdir /opt/cmake - ./cmake-3.22.2-linux-x86_64.sh --prefix=/opt/cmake --skip-license - ln --force --symbolic /opt/cmake/bin/cmake /usr/local/bin/cmake - cmake --version - - # install newer tar from focal... appimagelint fails on 18.04 without this - echo "original tar version" - tar --version - wget -O tar.deb http://security.ubuntu.com/ubuntu/pool/main/t/tar/tar_1.30+dfsg-7ubuntu0.20.04.3_amd64.deb - sudo apt-get -y install -f ./tar.deb - echo "new tar version" - tar --version - else - # Ubuntu 20.04+ packages - sudo apt-get update -y - sudo apt-get install -y \ - cmake \ - libboost-filesystem-dev \ - libboost-locale-dev \ - libboost-log-dev \ - libboost-program-options-dev - fi - sudo apt-get install -y \ build-essential \ + cmake \ gcc-10 \ g++-10 \ libayatana-appindicator3-dev \ libavdevice-dev \ + libboost-filesystem-dev \ + libboost-locale-dev \ + libboost-log-dev \ + libboost-program-options-dev \ libcap-dev \ libcurl4-openssl-dev \ libdrm-dev \ @@ -366,8 +353,7 @@ jobs: libxcb1-dev \ libxfixes-dev \ libxrandr-dev \ - libxtst-dev \ - wget + libxtst-dev # clean apt cache sudo apt-get clean @@ -382,20 +368,15 @@ jobs: --slave /usr/bin/gcc-ar gcc-ar /usr/bin/gcc-ar-10 \ --slave /usr/bin/gcc-ranlib gcc-ranlib /usr/bin/gcc-ranlib-10 - # Install CUDA - sudo wget \ - https://developer.download.nvidia.com/compute/cuda/11.8.0/local_installers/cuda_11.8.0_520.61.05_linux.run \ - --progress=bar:force:noscroll -q --show-progress -O /root/cuda.run - sudo chmod a+x /root/cuda.run - sudo /root/cuda.run --silent --toolkit --toolkitpath=/usr --no-opengl-libs --no-man-page --no-drm - sudo rm /root/cuda.run - - name: Build Linux env: BRANCH: ${{ github.head_ref || github.ref_name }} BUILD_VERSION: ${{ needs.check_changelog.outputs.next_version_bare }} COMMIT: ${{ github.event.pull_request.head.sha || github.sha }} + timeout-minutes: 5 run: | + echo "nproc: $(nproc)" + mkdir -p build mkdir -p artifacts @@ -403,6 +384,7 @@ jobs: cmake \ -DBUILD_WERROR=ON \ -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_CUDA_COMPILER:PATH=/usr/local/cuda/bin/nvcc \ -DCMAKE_INSTALL_PREFIX=/usr \ -DSUNSHINE_ASSETS_DIR=share/sunshine \ -DSUNSHINE_EXECUTABLE_PATH=/usr/bin/sunshine \ @@ -412,20 +394,7 @@ jobs: -DSUNSHINE_ENABLE_CUDA=ON \ ${{ matrix.EXTRA_ARGS }} \ .. - make -j ${nproc} - - - name: Package Linux - CPACK - # todo - this is no longer used - if: ${{ matrix.type == 'cpack' }} - working-directory: build - run: | - cpack -G DEB - mv ./cpack_artifacts/Sunshine.deb ../artifacts/sunshine-${{ matrix.dist }}.deb - - if [[ ${{ matrix.dist }} == "20.04" ]]; then - cpack -G RPM - mv ./cpack_artifacts/Sunshine.rpm ../artifacts/sunshine.rpm - fi + make -j $(expr $(nproc) - 1) # use all but one core - name: Set AppImage Version if: | @@ -452,12 +421,12 @@ jobs: # AppImage # https://docs.appimage.org/packaging-guide/index.html - wget https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage + wget -q https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage chmod +x linuxdeploy-x86_64.AppImage # https://github.com/linuxdeploy/linuxdeploy-plugin-gtk sudo apt-get install libgtk-3-dev librsvg2-dev -y - wget https://raw.githubusercontent.com/linuxdeploy/linuxdeploy-plugin-gtk/master/linuxdeploy-plugin-gtk.sh + wget -q https://raw.githubusercontent.com/linuxdeploy/linuxdeploy-plugin-gtk/master/linuxdeploy-plugin-gtk.sh chmod +x linuxdeploy-plugin-gtk.sh export DEPLOY_GTK_VERSION=3 @@ -475,14 +444,17 @@ jobs: # permissions chmod +x ../artifacts/sunshine.AppImage + - name: Delete cuda + # free up space on the runner + run: | + sudo rm -rf /usr/local/cuda + - name: Verify AppImage if: ${{ matrix.type == 'AppImage' }} run: | wget https://github.com/TheAssassin/appimagelint/releases/download/continuous/appimagelint-x86_64.AppImage chmod +x appimagelint-x86_64.AppImage - # rm -rf ~/.cache/appimagelint/ - ./appimagelint-x86_64.AppImage ./artifacts/sunshine.AppImage - name: Upload Artifacts From 87774333f38a354a03aa9dceeba74593beec2e2f Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Fri, 22 Mar 2024 19:54:12 -0400 Subject: [PATCH 127/182] feat(i18n): add ui localization (#2279) Co-authored-by: TheElixZammuto <6505622+TheElixZammuto@users.noreply.github.com> --- crowdin.yml | 12 +- docs/source/about/advanced_usage.rst | 34 + docs/source/contributing/localization.rst | 84 +- package.json | 3 +- src/config.cpp | 14 + src/config.h | 1 + src/confighttp.cpp | 19 + src_assets/common/assets/web/Navbar.vue | 12 +- src_assets/common/assets/web/ResourceCard.vue | 21 +- src_assets/common/assets/web/apps.html | 178 ++-- src_assets/common/assets/web/config.html | 818 +++++++----------- src_assets/common/assets/web/index.html | 35 +- src_assets/common/assets/web/locale.js | 27 + src_assets/common/assets/web/password.html | 34 +- src_assets/common/assets/web/pin.html | 23 +- .../assets/web/public/assets/css/sunshine.css | 4 + .../assets/web/public/assets/locale/de.json | 376 ++++++++ .../web/public/assets/locale/en-GB.json | 376 ++++++++ .../web/public/assets/locale/en-US.json | 376 ++++++++ .../assets/web/public/assets/locale/en.json | 376 ++++++++ .../assets/web/public/assets/locale/es.json | 376 ++++++++ .../assets/web/public/assets/locale/fr.json | 376 ++++++++ .../assets/web/public/assets/locale/it.json | 376 ++++++++ .../assets/web/public/assets/locale/ru.json | 376 ++++++++ .../assets/web/public/assets/locale/sv.json | 376 ++++++++ .../assets/web/public/assets/locale/zh.json | 376 ++++++++ .../common/assets/web/template_header.html | 1 + .../common/assets/web/troubleshooting.html | 52 +- src_assets/common/assets/web/welcome.html | 33 +- 29 files changed, 4446 insertions(+), 719 deletions(-) create mode 100644 src_assets/common/assets/web/locale.js create mode 100644 src_assets/common/assets/web/public/assets/css/sunshine.css create mode 100644 src_assets/common/assets/web/public/assets/locale/de.json create mode 100644 src_assets/common/assets/web/public/assets/locale/en-GB.json create mode 100644 src_assets/common/assets/web/public/assets/locale/en-US.json create mode 100644 src_assets/common/assets/web/public/assets/locale/en.json create mode 100644 src_assets/common/assets/web/public/assets/locale/es.json create mode 100644 src_assets/common/assets/web/public/assets/locale/fr.json create mode 100644 src_assets/common/assets/web/public/assets/locale/it.json create mode 100644 src_assets/common/assets/web/public/assets/locale/ru.json create mode 100644 src_assets/common/assets/web/public/assets/locale/sv.json create mode 100644 src_assets/common/assets/web/public/assets/locale/zh.json diff --git a/crowdin.yml b/crowdin.yml index 0be504ba7d8..3dd19366ef0 100644 --- a/crowdin.yml +++ b/crowdin.yml @@ -1,7 +1,7 @@ --- "base_path": "." "base_url": "https://api.crowdin.com" # optional (for Crowdin Enterprise only) -"preserve_hierarchy": false # flatten tree on crowdin +"preserve_hierarchy": true # false will flatten tree on crowdin, but doesn't work with dest option "pull_request_labels": [ "crowdin", "l10n" @@ -10,6 +10,7 @@ "files": [ { "source": "/locale/*.po", + "dest": "/%original_file_name%", "translation": "/locale/%two_letters_code%/LC_MESSAGES/%original_file_name%", "languages_mapping": { "two_letters_code": { @@ -17,6 +18,13 @@ "en-GB": "en_GB", "en-US": "en_US" } - } + }, + "update_option": "update_as_unapproved" + }, + { + "source": "/src_assets/common/assets/web/public/assets/locale/en.json", + "dest": "/sunshine.json", + "translation": "/src_assets/common/assets/web/public/assets/locale/%two_letters_code%.%file_extension%", + "update_option": "update_as_unapproved" } ] diff --git a/docs/source/about/advanced_usage.rst b/docs/source/about/advanced_usage.rst index f29e6d05f8d..d23b96cc582 100644 --- a/docs/source/about/advanced_usage.rst +++ b/docs/source/about/advanced_usage.rst @@ -47,6 +47,40 @@ editing the `conf` file in a text editor. Use the examples as reference. `General `__ ----------------------------------------------------- +`locale `__ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +**Description** + The locale used for Sunshine's user interface. + +**Choices** + +.. table:: + :widths: auto + + ======= =========== + Value Description + ======= =========== + de German + en English + en-GB English (UK) + en-US English (United States) + es Spanish + fr French + it Italian + ru Russian + sv Swedish + zh Chinese (Simplified) + ======= =========== + +**Default** + ``en`` + +**Example** + .. code-block:: text + + locale = en + `sunshine_name `__ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/docs/source/contributing/localization.rst b/docs/source/contributing/localization.rst index 2ca912805e1..0148b8170e4 100644 --- a/docs/source/contributing/localization.rst +++ b/docs/source/contributing/localization.rst @@ -30,42 +30,74 @@ localization there. Extraction ---------- -There should be minimal cases where strings need to be extracted from source code; however it may be necessary in some -situations. For example if a system tray icon is added it should be localized as it is user interfacing. -- Wrap the string to be extracted in a function as shown. - .. code-block:: cpp +.. tab:: UI - #include - #include + Sunshine uses `Vue I18n `__ for localizing the UI. + The following is a simple example of how to use it. - std::string msg = boost::locale::translate("Hello world!"); + - Add the string to `src_assets/common/assets/web/public/assets/locale/en.json`, in English. + .. code-block:: json -.. tip:: More examples can be found in the documentation for - `boost locale `__. + { + "index": { + "welcome": "Hello, Sunshine!" + } + } -.. warning:: This is for information only. Contributors should never include manually updated template files, or - manually compiled language files in Pull Requests. + .. note:: The json keys should be sorted alphabetically. You can use `jsonabc `__ + to sort the keys. -Strings are automatically extracted from the code to the `locale/sunshine.po` template file. The generated file is -used by CrowdIn to generate language specific template files. The file is generated using the -`.github/workflows/localize.yml` workflow and is run on any push event into the `nightly` branch. Jobs are only run if -any of the following paths are modified. + - Use the string in a Vue component. + .. code-block:: html -.. code-block:: yaml + - - 'src/**' + .. tip:: More formatting examples can be found in the + `Vue I18n guide `__. -When testing locally it may be desirable to manually extract, initialize, update, and compile strings. Python is -required for this, along with the python dependencies in the `./scripts/requirements.txt` file. Additionally, -`xgettext `__ must be installed. +.. tab:: C++ -**Extract, initialize, and update** - .. code-block:: bash + There should be minimal cases where strings need to be extracted from C++ source code; however it may be necessary in + some situations. For example the system tray icon could be localized as it is user interfacing. - python ./scripts/_locale.py --extract --init --update + - Wrap the string to be extracted in a function as shown. + .. code-block:: cpp -**Compile** - .. code-block:: bash + #include + #include - python ./scripts/_locale.py --compile + std::string msg = boost::locale::translate("Hello world!"); + + .. tip:: More examples can be found in the documentation for + `boost locale `__. + + .. warning:: This is for information only. Contributors should never include manually updated template files, or + manually compiled language files in Pull Requests. + + Strings are automatically extracted from the code to the `locale/sunshine.po` template file. The generated file is + used by CrowdIn to generate language specific template files. The file is generated using the + `.github/workflows/localize.yml` workflow and is run on any push event into the `nightly` branch. Jobs are only run if + any of the following paths are modified. + + .. code-block:: yaml + + - 'src/**' + + When testing locally it may be desirable to manually extract, initialize, update, and compile strings. Python is + required for this, along with the python dependencies in the `./scripts/requirements.txt` file. Additionally, + `xgettext `__ must be installed. + + **Extract, initialize, and update** + .. code-block:: bash + + python ./scripts/_locale.py --extract --init --update + + **Compile** + .. code-block:: bash + + python ./scripts/_locale.py --compile diff --git a/package.json b/package.json index b685e0966a4..706c23384ce 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "bootstrap": "5.3.3", "vite": "4.5.2", "vite-plugin-ejs": "1.6.4", - "vue": "3.4.5" + "vue": "3.4.5", + "vue-i18n": "9.10.2" } } diff --git a/src/config.cpp b/src/config.cpp index 21562f83487..b2660f7d2c1 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -441,6 +441,7 @@ namespace config { }; sunshine_t sunshine { + "en", // locale 2, // min_log_level 0, // flags {}, // User file @@ -1101,6 +1102,19 @@ namespace config { config::sunshine.flags[config::flag::UPNP].flip(); } + string_restricted_f(vars, "locale", config::sunshine.locale, { + "de"sv, // German + "en"sv, // English + "en-GB"sv, // English (UK) + "en-US"sv, // English (US) + "es"sv, // Spanish + "fr"sv, // French + "it"sv, // Italian + "ru"sv, // Russian + "sv"sv, // Swedish + "zh"sv, // Chinese + }); + std::string log_level_string; string_f(vars, "min_log_level", log_level_string); diff --git a/src/config.h b/src/config.h index 6c48f466b8e..f30e7e6a05a 100644 --- a/src/config.h +++ b/src/config.h @@ -160,6 +160,7 @@ namespace config { bool elevated; }; struct sunshine_t { + std::string locale; int min_log_level; std::bitset flags; std::string credentials_file; diff --git a/src/confighttp.cpp b/src/confighttp.cpp index 0657902dd16..de25bf0e7cf 100644 --- a/src/confighttp.cpp +++ b/src/confighttp.cpp @@ -550,6 +550,24 @@ namespace confighttp { } } + void + getLocale(resp_https_t response, req_https_t request) { + // we need to return the locale whether authenticated or not + + print_req(request); + + pt::ptree outputTree; + auto g = util::fail_guard([&]() { + std::ostringstream data; + + pt::write_json(data, outputTree); + response->write(data.str()); + }); + + outputTree.put("status", "true"); + outputTree.put("locale", config::sunshine.locale); + } + void saveConfig(resp_https_t response, req_https_t request) { if (!authenticate(response, request)) return; @@ -743,6 +761,7 @@ namespace confighttp { server.resource["^/api/apps$"]["POST"] = saveApp; server.resource["^/api/config$"]["GET"] = getConfig; server.resource["^/api/config$"]["POST"] = saveConfig; + server.resource["^/api/configLocale$"]["GET"] = getLocale; server.resource["^/api/restart$"]["POST"] = restart; server.resource["^/api/password$"]["POST"] = savePassword; server.resource["^/api/apps/([0-9]+)$"]["DELETE"] = deleteApp; diff --git a/src_assets/common/assets/web/Navbar.vue b/src_assets/common/assets/web/Navbar.vue index 948fb4d7244..9e4e1be64f5 100644 --- a/src_assets/common/assets/web/Navbar.vue +++ b/src_assets/common/assets/web/Navbar.vue @@ -11,22 +11,22 @@ diff --git a/src_assets/common/assets/web/ResourceCard.vue b/src_assets/common/assets/web/ResourceCard.vue index e2481ca475a..aee837689cf 100644 --- a/src_assets/common/assets/web/ResourceCard.vue +++ b/src_assets/common/assets/web/ResourceCard.vue @@ -1,35 +1,32 @@