Merge remote-tracking branch 'sunshine/master'

This commit is contained in:
Yukino Song
2025-09-27 00:37:24 +08:00
104 changed files with 3939 additions and 898 deletions

View File

@@ -566,6 +566,7 @@ namespace config {
true, // back as touchpad click enabled (manual DS4 only)
true, // client gamepads with motion events are emulated as DS4
true, // client gamepads with touchpads are emulated as DS4
true, // ds5_inputtino_randomize_mac
true, // keyboard enabled
true, // mouse enabled
@@ -596,6 +597,7 @@ namespace config {
platf::appdata().string() + "/sunshine.log", // log file
false, // notify_pre_releases
false, // legacy_ordering
true, // system_tray
{}, // prep commands
{}, // state commands
{}, // server commands
@@ -1279,6 +1281,7 @@ namespace config {
bool_f(vars, "ds4_back_as_touchpad_click", input.ds4_back_as_touchpad_click);
bool_f(vars, "motion_as_ds4", input.motion_as_ds4);
bool_f(vars, "touchpad_as_ds4", input.touchpad_as_ds4);
bool_f(vars, "ds5_inputtino_randomize_mac", input.ds5_inputtino_randomize_mac);
bool_f(vars, "mouse", input.mouse);
bool_f(vars, "keyboard", input.keyboard);
@@ -1290,6 +1293,7 @@ namespace config {
bool_f(vars, "native_pen_touch", input.native_pen_touch);
bool_f(vars, "enable_input_only_mode", input.enable_input_only_mode);
bool_f(vars, "system_tray", sunshine.system_tray);
bool_f(vars, "hide_tray_controls", sunshine.hide_tray_controls);
bool_f(vars, "enable_pairing", sunshine.enable_pairing);
bool_f(vars, "enable_discovery", sunshine.enable_discovery);
@@ -1320,6 +1324,7 @@ namespace config {
"en_US"sv, // English (US)
"es"sv, // Spanish
"fr"sv, // French
"hu"sv, // Hungarian
"it"sv, // Italian
"ja"sv, // Japanese
"ko"sv, // Korean
@@ -1330,6 +1335,7 @@ namespace config {
"sv"sv, // Swedish
"tr"sv, // Turkish
"uk"sv, // Ukrainian
"vi"sv, // Vietnamese
"zh"sv, // Chinese
"zh_TW"sv, // Chinese (Traditional)
});
@@ -1497,7 +1503,7 @@ namespace config {
if (!service_ctrl::is_service_running()) {
// If the service isn't running, relaunch ourselves as admin to start it
WCHAR executable[MAX_PATH];
GetModuleFileNameW(NULL, executable, ARRAYSIZE(executable));
GetModuleFileNameW(nullptr, executable, ARRAYSIZE(executable));
SHELLEXECUTEINFOW shell_exec_info {};
shell_exec_info.cbSize = sizeof(shell_exec_info);

View File

@@ -202,6 +202,7 @@ namespace config {
bool ds4_back_as_touchpad_click;
bool motion_as_ds4;
bool touchpad_as_ds4;
bool ds5_inputtino_randomize_mac;
bool keyboard;
bool mouse;
@@ -283,6 +284,7 @@ namespace config {
std::string log_file;
bool notify_pre_releases;
bool legacy_ordering;
bool system_tray;
std::vector<prep_cmd_t> prep_cmds;
std::vector<prep_cmd_t> state_cmds;
std::vector<server_cmd_t> server_cmds;

View File

@@ -8,6 +8,7 @@
// standard includes
#include <filesystem>
#include <format>
#include <fstream>
#include <set>
#include <sstream>

View File

@@ -4,6 +4,7 @@
*/
// standard includes
#include <csignal>
#include <format>
#include <iostream>
#include <thread>
@@ -25,13 +26,11 @@ extern "C" {
using namespace std::literals;
void launch_ui() {
std::string url = "https://localhost:" + std::to_string(net::map_port(confighttp::PORT_HTTPS));
platf::open_url(url);
}
void launch_ui_with_path(std::string path) {
std::string url = "https://localhost:" + std::to_string(net::map_port(confighttp::PORT_HTTPS)) + path;
void launch_ui(const std::optional<std::string> &path) {
std::string url = std::format("https://localhost:{}", static_cast<int>(net::map_port(confighttp::PORT_HTTPS)));
if (path) {
url += *path;
}
platf::open_url(url);
}
@@ -194,8 +193,8 @@ namespace service_ctrl {
}
private:
SC_HANDLE scm_handle = NULL;
SC_HANDLE service_handle = NULL;
SC_HANDLE scm_handle = nullptr;
SC_HANDLE service_handle = nullptr;
};
bool is_service_running() {

View File

@@ -14,19 +14,13 @@
/**
* @brief Launch the Web UI.
* @param path Optional path to append to the base URL.
* @examples
* launch_ui();
* launch_ui("/pin");
* @examples_end
*/
void launch_ui();
/**
* @brief Launch the Web UI at a specific endpoint.
* @examples
* launch_ui_with_path("/pin");
* @examples_end
*/
void launch_ui_with_path(std::string path);
void launch_ui(const std::optional<std::string> &path = std::nullopt);
/**
* @brief Functions for handling command line arguments.

View File

@@ -96,6 +96,45 @@ WINAPI BOOL ConsoleCtrlHandler(DWORD type) {
}
#endif
#if defined SUNSHINE_TRAY && SUNSHINE_TRAY >= 1
constexpr bool tray_is_enabled = true;
#else
constexpr bool tray_is_enabled = false;
#endif
void mainThreadLoop(const std::shared_ptr<safe::event_t<bool>> &shutdown_event) {
bool run_loop = false;
// Conditions that would require the main thread event loop
#ifndef _WIN32
run_loop = tray_is_enabled; // On Windows, tray runs in separate thread, so no main loop needed for tray
#endif
if (!run_loop) {
BOOST_LOG(info) << "No main thread features enabled, skipping event loop"sv;
return;
}
// Main thread event loop
BOOST_LOG(info) << "Starting main loop"sv;
while (true) {
if (shutdown_event->peek()) {
BOOST_LOG(info) << "Shutdown event detected, breaking main loop"sv;
if (tray_is_enabled && config::sunshine.system_tray) {
system_tray::end_tray();
}
break;
}
if (tray_is_enabled) {
system_tray::process_tray_events();
}
// Sleep to avoid busy waiting
std::this_thread::sleep_for(std::chrono::milliseconds(50));
}
}
int main(int argc, char *argv[]) {
lifetime::argv = argv;
@@ -167,7 +206,7 @@ int main(int argc, char *argv[]) {
BOOST_LOG(error) << "Display device session failed to initialize"sv;
}
#ifdef WIN32
#ifdef _WIN32
// Modify relevant NVIDIA control panel settings if the system has corresponding gpu
if (nvprefs_instance.load()) {
// Restore global settings to the undo file left by improper termination of sunshine.exe
@@ -196,7 +235,7 @@ int main(int argc, char *argv[]) {
wnd_class.lpszClassName = "SunshineSessionMonitorClass";
wnd_class.lpfnWndProc = SessionMonitorWindowProc;
if (!RegisterClassA(&wnd_class)) {
session_monitor_hwnd_promise.set_value(NULL);
session_monitor_hwnd_promise.set_value(nullptr);
BOOST_LOG(error) << "Failed to register session monitor window class"sv << std::endl;
return;
}
@@ -256,11 +295,6 @@ int main(int argc, char *argv[]) {
task_pool.start(1);
#if defined SUNSHINE_TRAY && SUNSHINE_TRAY >= 1
// create tray thread and detach it
system_tray::run_tray();
#endif
// Create signal handler after logging has been initialized
auto shutdown_event = mail::man->event<bool>(mail::shutdown);
on_signal(SIGINT, [&force_shutdown, &display_device_deinit_guard, shutdown_event]() {
@@ -405,7 +439,23 @@ int main(int argc, char *argv[]) {
}
#endif
// Wait for shutdown
if (tray_is_enabled && config::sunshine.system_tray) {
BOOST_LOG(info) << "Starting system tray"sv;
#ifdef _WIN32
// TODO: Windows has a weird bug where when running as a service and on the first Windows boot,
// he tray icon would not appear even though Sunshine is running correctly otherwise.
// Restarting the service would allow the icon to appear normally.
// For now we will keep the Windows tray icon on a separate thread.
// Ideally, we would run the system tray on the main thread for all platforms.
system_tray::init_tray_threaded();
#else
system_tray::init_tray();
#endif
}
mainThreadLoop(shutdown_event);
// Wait for shutdown, this is not necessary when we're using the main event loop
shutdown_event->view();
httpThread.join();
@@ -415,17 +465,17 @@ int main(int argc, char *argv[]) {
task_pool.stop();
task_pool.join();
// stop system tray
#if defined SUNSHINE_TRAY && SUNSHINE_TRAY >= 1
system_tray::end_tray();
#endif
#ifdef WIN32
#ifdef _WIN32
// Restore global NVIDIA control panel settings
if (nvprefs_instance.owning_undo_file() && nvprefs_instance.load()) {
nvprefs_instance.restore_global_profile();
nvprefs_instance.unload();
}
// Stop the threaded tray if it was started
if (tray_is_enabled && config::sunshine.system_tray) {
system_tray::end_tray_threaded();
}
#endif
return lifetime::desired_exit_code;

View File

@@ -5,6 +5,9 @@
// this include
#include "nvenc_base.h"
// standard includes
#include <format>
// local includes
#include "src/config.h"
#include "src/logging.h"
@@ -427,7 +430,7 @@ namespace nvenc {
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);
extra += std::format(" vbv+{}", config.vbv_percentage_increase);
}
if (encoder_params.rfi) {
extra += " rfi";
@@ -439,7 +442,7 @@ namespace nvenc {
extra += " spatial-aq";
}
if (enc_config.rcParams.enableMinQP) {
extra += " qpmin=" + std::to_string(enc_config.rcParams.minQP.qpInterP);
extra += std::format(" qpmin={}", enc_config.rcParams.minQP.qpInterP);
}
if (config.insert_filler_data) {
extra += " filler-data";

View File

@@ -12,13 +12,13 @@ namespace nvenc {
nvenc_d3d11::nvenc_d3d11(NV_ENC_DEVICE_TYPE device_type):
nvenc_base(device_type) {
async_event_handle = CreateEvent(NULL, FALSE, FALSE, NULL);
async_event_handle = CreateEvent(nullptr, FALSE, FALSE, nullptr);
}
nvenc_d3d11::~nvenc_d3d11() {
if (dll) {
FreeLibrary(dll);
dll = NULL;
dll = nullptr;
}
if (async_event_handle) {
CloseHandle(async_event_handle);
@@ -36,7 +36,7 @@ namespace nvenc {
constexpr auto dll_name = "nvEncodeAPI.dll";
#endif
if ((dll = LoadLibraryEx(dll_name, NULL, LOAD_LIBRARY_SEARCH_SYSTEM32))) {
if ((dll = LoadLibraryEx(dll_name, nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32))) {
if (auto create_instance = (decltype(NvEncodeAPICreateInstance) *) GetProcAddress(dll, "NvEncodeAPICreateInstance")) {
auto new_nvenc = std::make_unique<NV_ENCODE_API_FUNCTION_LIST>();
new_nvenc->version = min_struct_version(NV_ENCODE_API_FUNCTION_LIST_VER);
@@ -55,7 +55,7 @@ namespace nvenc {
if (dll) {
FreeLibrary(dll);
dll = NULL;
dll = nullptr;
}
return false;

View File

@@ -39,7 +39,7 @@ namespace nvenc {
bool wait_for_async_event(uint32_t timeout_ms) override;
private:
HMODULE dll = NULL;
HMODULE dll = nullptr;
};
} // namespace nvenc

View File

@@ -63,7 +63,7 @@ namespace nvenc {
constexpr auto dll_name = "nvcuda.dll";
if ((cuda_functions.dll = LoadLibraryEx(dll_name, NULL, LOAD_LIBRARY_SEARCH_SYSTEM32))) {
if ((cuda_functions.dll = LoadLibraryEx(dll_name, nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32))) {
auto load_function = [&]<typename T>(T &location, auto symbol) -> bool {
location = (T) GetProcAddress(cuda_functions.dll, symbol);
return location != nullptr;

View File

@@ -56,7 +56,7 @@ namespace nvenc {
autopop_context push_context();
HMODULE dll = NULL;
HMODULE dll = nullptr;
const ID3D11DevicePtr d3d_device;
ID3D11Texture2DPtr d3d_input_texture;

View File

@@ -7,6 +7,7 @@
// standard includes
#include <filesystem>
#include <format>
#include <string>
#include <utility>
#include <string>
@@ -163,7 +164,7 @@ namespace nvhttp {
std::string get_arg(const args_t &args, const char *name, const char *default_value) {
auto it = args.find(name);
if (it == std::end(args)) {
if (default_value != NULL) {
if (default_value != nullptr) {
return std::string(default_value);
}
@@ -839,7 +840,7 @@ namespace nvhttp {
tree.put("root.<xmlattr>.status_code", 400);
tree.put(
"root.<xmlattr>.status_message",
"Pin must be 4 digits, " + std::to_string(pin.size()) + " provided"
std::format("Pin must be 4 digits, {} provided", pin.size())
);
return false;
}
@@ -1330,7 +1331,15 @@ namespace nvhttp {
}
tree.put("root.<xmlattr>.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(net::map_port(rtsp_stream::RTSP_SETUP_PORT)));
tree.put(
"root.sessionUrl0",
std::format(
"{}{}:{}",
launch_session->rtsp_url_scheme,
net::addr_to_url_escaped_string(request->local_endpoint().address()),
static_cast<int>(net::map_port(rtsp_stream::RTSP_SETUP_PORT))
)
);
tree.put("root.gamesession", 1);
rtsp_stream::launch_session_raise(launch_session);
@@ -1429,7 +1438,15 @@ namespace nvhttp {
}
tree.put("root.<xmlattr>.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(net::map_port(rtsp_stream::RTSP_SETUP_PORT)));
tree.put(
"root.sessionUrl0",
std::format(
"{}{}:{}",
launch_session->rtsp_url_scheme,
net::addr_to_url_escaped_string(request->local_endpoint().address()),
static_cast<int>(net::map_port(rtsp_stream::RTSP_SETUP_PORT))
)
);
tree.put("root.resume", 1);
rtsp_stream::launch_session_raise(launch_session);

View File

@@ -202,7 +202,7 @@ namespace cuda {
return sws.load_ram(img, tex.array) || sws.convert(frame->data[0], frame->data[1], frame->linesize[0], frame->linesize[1], tex_obj(tex), stream.get());
}
int set_frame(AVFrame *frame, AVBufferRef *hw_frames_ctx) {
int set_frame(AVFrame *frame, AVBufferRef *hw_frames_ctx) override {
if (cuda_t::set_frame(frame, hw_frames_ctx)) {
return -1;
}
@@ -929,7 +929,7 @@ namespace cuda {
return platf::capture_e::ok;
}
std::unique_ptr<platf::avcodec_encode_device_t> make_avcodec_encode_device(platf::pix_fmt_e pix_fmt) {
std::unique_ptr<platf::avcodec_encode_device_t> make_avcodec_encode_device(platf::pix_fmt_e pix_fmt) override {
return ::cuda::make_avcodec_encode_device(width, height, true);
}

View File

@@ -16,8 +16,8 @@
#include "src/video_colorspace.h"
namespace platf {
class avcodec_encode_device_t;
class img_t;
struct avcodec_encode_device_t;
struct img_t;
} // namespace platf
namespace cuda {

View File

@@ -42,8 +42,15 @@ namespace platf::gamepad {
.version = 0x8111});
}
auto create_ds5() {
return inputtino::PS5Joypad::create({.name = "Sunshine PS5 (virtual) pad", .vendor_id = 0x054C, .product_id = 0x0CE6, .version = 0x8111});
auto create_ds5(int globalIndex) {
std::string device_mac = ""; // Inputtino checks empty() to generate a random MAC
if (!config::input.ds5_inputtino_randomize_mac && globalIndex >= 0 && globalIndex <= 255) {
// Generate private virtual device MAC based on gamepad globalIndex between 0 (00) and 255 (ff)
device_mac = std::format("02:00:00:00:00:{:02x}", globalIndex);
}
return inputtino::PS5Joypad::create({.name = "Sunshine PS5 (virtual) pad", .vendor_id = 0x054C, .product_id = 0x0CE6, .version = 0x8111, .device_phys = device_mac, .device_uniq = device_mac});
}
int alloc(input_raw_t *raw, const gamepad_id_t &id, const gamepad_arrival_t &metadata, feedback_queue_t feedback_queue) {
@@ -138,7 +145,7 @@ namespace platf::gamepad {
}
case DualSenseWired:
{
auto ds5 = create_ds5();
auto ds5 = create_ds5(id.globalIndex);
if (ds5) {
(*ds5).set_on_rumble(on_rumble_fn);
(*ds5).set_on_led([feedback_queue, idx = id.clientRelativeIndex, gamepad](int r, int g, int b) {
@@ -267,7 +274,7 @@ namespace platf::gamepad {
return gps;
}
auto ds5 = create_ds5();
auto ds5 = create_ds5(-1); // Index -1 will result in a random MAC virtual device, which is fine for probing
auto switchPro = create_switch();
auto xOne = create_xbox_one();

View File

@@ -4,6 +4,7 @@
*/
// standard includes
#include <fcntl.h>
#include <format>
#include <sstream>
#include <string>
@@ -190,7 +191,7 @@ namespace va {
return VAProfileH264High;
} else if (ctx->codec_id == AV_CODEC_ID_HEVC) {
switch (ctx->profile) {
case FF_PROFILE_HEVC_REXT:
case AV_PROFILE_HEVC_REXT:
switch (av_pix_fmt_desc_get(ctx->sw_pix_fmt)->comp[0].depth) {
case 10:
return VAProfileHEVCMain444_10;
@@ -198,16 +199,16 @@ namespace va {
return VAProfileHEVCMain444;
}
break;
case FF_PROFILE_HEVC_MAIN_10:
case AV_PROFILE_HEVC_MAIN_10:
return VAProfileHEVCMain10;
case FF_PROFILE_HEVC_MAIN:
case AV_PROFILE_HEVC_MAIN:
return VAProfileHEVCMain;
}
} else if (ctx->codec_id == AV_CODEC_ID_AV1) {
switch (ctx->profile) {
case FF_PROFILE_AV1_HIGH:
case AV_PROFILE_AV1_HIGH:
return VAProfileAV1Profile1;
case FF_PROFILE_AV1_MAIN:
case AV_PROFILE_AV1_MAIN:
return VAProfileAV1Profile0;
}
}
@@ -574,7 +575,7 @@ namespace va {
if (!display) {
char string[1024];
auto bytes = readlink(("/proc/self/fd/" + std::to_string(fd)).c_str(), string, sizeof(string));
auto bytes = readlink(std::format("/proc/self/fd/{}", fd).c_str(), string, sizeof(string));
std::string_view render_device {string, (std::size_t) bytes};

View File

@@ -4,8 +4,11 @@
*/
#define INITGUID
// standard includes
#include <format>
// platform includes
#include <audioclient.h>
#include <Audioclient.h>
#include <avrt.h>
#include <mmdeviceapi.h>
#include <newdev.h>
@@ -168,28 +171,27 @@ namespace {
waveformat.SubFormat == KSDATAFORMAT_SUBTYPE_PCM ? "S" :
"UNKNOWN";
result += std::to_string(waveformat.Samples.wValidBitsPerSample) + " " +
std::to_string(waveformat.Format.nSamplesPerSec) + " ";
result += std::format("{} {} ", static_cast<int>(waveformat.Samples.wValidBitsPerSample), static_cast<int>(waveformat.Format.nSamplesPerSec));
switch (waveformat.dwChannelMask) {
case (waveformat_mask_stereo):
case waveformat_mask_stereo:
result += "2.0";
break;
case (waveformat_mask_surround51_with_backspeakers):
case waveformat_mask_surround51_with_backspeakers:
result += "5.1";
break;
case (waveformat_mask_surround51_with_sidespeakers):
case waveformat_mask_surround51_with_sidespeakers:
result += "5.1 (sidespeakers)";
break;
case (waveformat_mask_surround71):
case waveformat_mask_surround71:
result += "7.1";
break;
default:
result += std::to_string(waveformat.Format.nChannels) + " channels (unrecognized)";
result += std::format("{} channels (unrecognized)", static_cast<int>(waveformat.Format.nChannels));
break;
}
@@ -375,7 +377,7 @@ namespace platf::audio {
*ppvInterface = (IMMNotificationClient *) this;
return S_OK;
} else {
*ppvInterface = NULL;
*ppvInterface = nullptr;
return E_NOINTERFACE;
}
}
@@ -677,7 +679,7 @@ namespace platf::audio {
float *sample_buf_pos;
int channels;
HANDLE mmcss_task_handle = NULL;
HANDLE mmcss_task_handle = nullptr;
};
class audio_control_t: public ::platf::audio_control_t {

View File

@@ -12,7 +12,7 @@
#include <dxgi.h>
#include <dxgi1_6.h>
#include <Unknwn.h>
#include <winrt/Windows.Graphics.Capture.h>
#include <winrt/windows.graphics.capture.h>
// local includes
#include "src/platform/common.h"

View File

@@ -603,12 +603,12 @@ namespace platf::dxgi {
LUID val;
if (OpenProcessToken(GetCurrentProcess(), flags, &token) &&
!!LookupPrivilegeValue(NULL, SE_INC_BASE_PRIORITY_NAME, &val)) {
!!LookupPrivilegeValue(nullptr, SE_INC_BASE_PRIORITY_NAME, &val)) {
tp.PrivilegeCount = 1;
tp.Privileges[0].Luid = val;
tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
if (!AdjustTokenPrivileges(token, false, &tp, sizeof(tp), NULL, NULL)) {
if (!AdjustTokenPrivileges(token, false, &tp, sizeof(tp), nullptr, nullptr)) {
BOOST_LOG(warning) << "Could not set privilege to increase GPU priority";
}
}
@@ -926,20 +926,20 @@ namespace platf::dxgi {
"DXGI_FORMAT_A8P8",
"DXGI_FORMAT_B4G4R4A4_UNORM",
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
nullptr,
nullptr,
nullptr,
nullptr,
nullptr,
nullptr,
nullptr,
nullptr,
nullptr,
nullptr,
nullptr,
nullptr,
nullptr,
nullptr,
"DXGI_FORMAT_P208",
"DXGI_FORMAT_V208",

View File

@@ -7,7 +7,7 @@
// platform includes
#include <d3dcompiler.h>
#include <directxmath.h>
#include <DirectXMath.h>
extern "C" {
#include <libavcodec/avcodec.h>
@@ -1745,7 +1745,7 @@ namespace platf::dxgi {
img->data = nullptr;
if (img->encoder_texture_handle) {
CloseHandle(img->encoder_texture_handle);
img->encoder_texture_handle = NULL;
img->encoder_texture_handle = nullptr;
}
// Initialize format-dependent fields

View File

@@ -8,7 +8,7 @@
// Gross hack to work around MINGW-packages#22160
#define ____FIReference_1_boolean_INTERFACE_DEFINED__
#include <windows.graphics.capture.interop.h>
#include <Windows.Graphics.Capture.Interop.h>
#include <winrt/windows.foundation.h>
#include <winrt/windows.foundation.metadata.h>
#include <winrt/windows.graphics.directx.direct3d11.h>

View File

@@ -5,7 +5,7 @@
#define WINVER 0x0A00
// platform includes
#include <windows.h>
#include <Windows.h>
// standard includes
#include <cmath>
@@ -293,7 +293,7 @@ namespace platf {
if (gamepad.repeat_task) {
task_pool.cancel(gamepad.repeat_task);
gamepad.repeat_task = 0;
gamepad.repeat_task = nullptr;
}
if (gamepad.gp && vigem_target_is_attached(gamepad.gp.get())) {
@@ -1457,7 +1457,7 @@ namespace platf {
// Cancel any pending updates. We will requeue one here when we're finished.
if (gamepad.repeat_task) {
task_pool.cancel(gamepad.repeat_task);
gamepad.repeat_task = 0;
gamepad.repeat_task = nullptr;
}
if (gamepad.gp && vigem_target_is_attached(gamepad.gp.get())) {
@@ -1603,8 +1603,8 @@ namespace platf {
uint16_t y = touch.y * 943;
uint8_t touchData[] = {
(uint8_t) (x & 0xFF), // Low 8 bits of X
(uint8_t) (((x >> 8) & 0x0F) | ((y & 0x0F) << 4)), // High 4 bits of X and low 4 bits of Y
(uint8_t) (((y >> 4) & 0xFF)) // High 8 bits of Y
(uint8_t) ((x >> 8 & 0x0F) | (y & 0x0F) << 4), // High 4 bits of X and low 4 bits of Y
(uint8_t) (y >> 4 & 0xFF) // High 8 bits of Y
};
report.sCurrentTouch.bPacketCounter++;

View File

@@ -28,13 +28,13 @@
#include <iphlpapi.h>
#include <iterator>
#include <timeapi.h>
#include <userenv.h>
#include <winsock2.h>
#include <windows.h>
#include <winuser.h>
#include <UserEnv.h>
#include <WinSock2.h>
#include <Windows.h>
#include <WinUser.h>
#include <wlanapi.h>
#include <ws2tcpip.h>
#include <wtsapi32.h>
#include <WS2tcpip.h>
#include <WtsApi32.h>
#include <sddl.h>
// clang-format on
@@ -128,7 +128,7 @@ namespace platf {
std::filesystem::path appdata() {
WCHAR sunshine_path[MAX_PATH];
GetModuleFileNameW(NULL, sunshine_path, _countof(sunshine_path));
GetModuleFileNameW(nullptr, sunshine_path, _countof(sunshine_path));
return std::filesystem::path {sunshine_path}.remove_filename() / L"config"sv;
}
@@ -476,16 +476,16 @@ namespace platf {
LPPROC_THREAD_ATTRIBUTE_LIST allocate_proc_thread_attr_list(DWORD attribute_count) {
SIZE_T size;
InitializeProcThreadAttributeList(NULL, attribute_count, 0, &size);
InitializeProcThreadAttributeList(nullptr, attribute_count, 0, &size);
auto list = (LPPROC_THREAD_ATTRIBUTE_LIST) HeapAlloc(GetProcessHeap(), 0, size);
if (list == NULL) {
return NULL;
if (list == nullptr) {
return nullptr;
}
if (!InitializeProcThreadAttributeList(list, attribute_count, 0, &size)) {
HeapFree(GetProcessHeap(), 0, list);
return NULL;
return nullptr;
}
return list;
@@ -584,7 +584,7 @@ namespace platf {
// Allocate a process attribute list with space for 2 elements
startup_info.lpAttributeList = allocate_proc_thread_attr_list(2);
if (startup_info.lpAttributeList == NULL) {
if (startup_info.lpAttributeList == nullptr) {
// If the allocation failed, set ec to an appropriate error code and return the structure
ec = std::make_error_code(std::errc::not_enough_memory);
return startup_info;
@@ -596,7 +596,7 @@ namespace platf {
// Populate std handles if the caller gave us a log file to use
startup_info.StartupInfo.dwFlags |= STARTF_USESTDHANDLES;
startup_info.StartupInfo.hStdInput = NULL;
startup_info.StartupInfo.hStdInput = nullptr;
startup_info.StartupInfo.hStdOutput = log_file_handle;
startup_info.StartupInfo.hStdError = log_file_handle;
@@ -605,7 +605,7 @@ namespace platf {
//
// Note: The value we point to here must be valid for the lifetime of the attribute list,
// so we need to point into the STARTUPINFO instead of our log_file_variable on the stack.
UpdateProcThreadAttribute(startup_info.lpAttributeList, 0, PROC_THREAD_ATTRIBUTE_HANDLE_LIST, &startup_info.StartupInfo.hStdOutput, sizeof(startup_info.StartupInfo.hStdOutput), NULL, NULL);
UpdateProcThreadAttribute(startup_info.lpAttributeList, 0, PROC_THREAD_ATTRIBUTE_HANDLE_LIST, &startup_info.StartupInfo.hStdOutput, sizeof(startup_info.StartupInfo.hStdOutput), nullptr, nullptr);
}
if (job) {
@@ -613,7 +613,7 @@ namespace platf {
//
// Note: The value we point to here must be valid for the lifetime of the attribute list,
// so we take a HANDLE* instead of just a HANDLE to use the caller's stack storage.
UpdateProcThreadAttribute(startup_info.lpAttributeList, 0, PROC_THREAD_ATTRIBUTE_JOB_LIST, job, sizeof(*job), NULL, NULL);
UpdateProcThreadAttribute(startup_info.lpAttributeList, 0, PROC_THREAD_ATTRIBUTE_JOB_LIST, job, sizeof(*job), nullptr, nullptr);
}
return startup_info;
@@ -621,11 +621,11 @@ namespace platf {
/**
* @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.
* @param token The primary token identifying the user to use, or `nullptr` 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;
HKEY user_classes_root = nullptr;
if (token) {
auto err = RegOpenUserClassesRoot(token, 0, GENERIC_ALL, &user_classes_root);
if (err != ERROR_SUCCESS) {
@@ -639,14 +639,14 @@ namespace platf {
}
});
HKEY user_key = NULL;
HKEY user_key = nullptr;
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;
user_key = nullptr;
}
});
if (!user_key) {
@@ -668,7 +668,7 @@ namespace platf {
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);
RegOverridePredefKey(HKEY_CLASSES_ROOT, nullptr);
return false;
}
@@ -737,7 +737,7 @@ namespace platf {
* @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.
* @param token The user token currently being impersonated or `nullptr` 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().
*/
@@ -814,7 +814,7 @@ namespace platf {
}
// Reset per-user keys back to the original value
override_per_user_predefined_keys(NULL);
override_per_user_predefined_keys(nullptr);
}
if (res != S_OK) {
@@ -1029,7 +1029,7 @@ namespace platf {
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, creation_flags);
ret = CreateProcessAsUserW(user_token, NULL, (LPWSTR) wcmd.c_str(), NULL, NULL, !!(startup_info.StartupInfo.dwFlags & STARTF_USESTDHANDLES), creation_flags, env_block.data(), start_dir.empty() ? NULL : start_dir.c_str(), (LPSTARTUPINFOW) &startup_info, &process_info);
ret = CreateProcessAsUserW(user_token, nullptr, (LPWSTR) wcmd.c_str(), nullptr, nullptr, !!(startup_info.StartupInfo.dwFlags & STARTF_USESTDHANDLES), creation_flags, env_block.data(), start_dir.empty() ? nullptr : start_dir.c_str(), (LPSTARTUPINFOW) &startup_info, &process_info);
});
}
// Otherwise, launch the process using CreateProcessW()
@@ -1052,8 +1052,8 @@ namespace platf {
}
std::wstring env_block = create_environment_block(cloned_env);
std::wstring wcmd = resolve_command_string(cmd, start_dir, NULL, creation_flags);
ret = CreateProcessW(NULL, (LPWSTR) wcmd.c_str(), NULL, NULL, !!(startup_info.StartupInfo.dwFlags & STARTF_USESTDHANDLES), creation_flags, env_block.data(), start_dir.empty() ? NULL : start_dir.c_str(), (LPSTARTUPINFOW) &startup_info, &process_info);
std::wstring wcmd = resolve_command_string(cmd, start_dir, nullptr, creation_flags);
ret = CreateProcessW(nullptr, (LPWSTR) wcmd.c_str(), nullptr, nullptr, !!(startup_info.StartupInfo.dwFlags & STARTF_USESTDHANDLES), creation_flags, env_block.data(), start_dir.empty() ? nullptr : start_dir.c_str(), (LPSTARTUPINFOW) &startup_info, &process_info);
}
// Use the results of the launch to create a bp::child object
@@ -1109,7 +1109,7 @@ namespace platf {
static std::once_flag load_wlanapi_once_flag;
std::call_once(load_wlanapi_once_flag, []() {
// wlanapi.dll is not installed by default on Windows Server, so we load it dynamically
HMODULE wlanapi = LoadLibraryExA("wlanapi.dll", NULL, LOAD_LIBRARY_SEARCH_SYSTEM32);
HMODULE wlanapi = LoadLibraryExA("wlanapi.dll", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32);
if (!wlanapi) {
BOOST_LOG(debug) << "wlanapi.dll is not available on this OS"sv;
return;
@@ -1186,7 +1186,7 @@ namespace platf {
fn_WlanFreeMemory(wlan_interface_list);
} else {
fn_WlanCloseHandle(wlan_handle, nullptr);
wlan_handle = NULL;
wlan_handle = nullptr;
}
}
}
@@ -1257,7 +1257,7 @@ namespace platf {
startup_info.StartupInfo.cb = sizeof(startup_info);
WCHAR executable[MAX_PATH];
if (GetModuleFileNameW(NULL, executable, ARRAYSIZE(executable)) == 0) {
if (GetModuleFileNameW(nullptr, executable, ARRAYSIZE(executable)) == 0) {
auto winerr = GetLastError();
BOOST_LOG(fatal) << "Failed to get Sunshine path: "sv << winerr;
return;
@@ -1277,7 +1277,7 @@ namespace platf {
void restart() {
// If we're running standalone, we have to respawn ourselves via CreateProcess().
// If we're running from the service, we should just exit and let it respawn us.
if (GetConsoleWindow() != NULL) {
if (GetConsoleWindow() != nullptr) {
// Avoid racing with the new process by waiting until we're exiting to start it.
atexit(restart_on_exit);
}
@@ -1595,7 +1595,7 @@ namespace platf {
}
virtual ~qos_t() {
if (!fn_QOSRemoveSocketFromFlow(qos_handle, (SOCKET) NULL, flow_id, 0)) {
if (!fn_QOSRemoveSocketFromFlow(qos_handle, (SOCKET) nullptr, flow_id, 0)) {
auto winerr = GetLastError();
BOOST_LOG(warning) << "QOSRemoveSocketFromFlow() failed: "sv << winerr;
}
@@ -1627,7 +1627,7 @@ namespace platf {
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
HMODULE qwave = LoadLibraryExA("qwave.dll", NULL, LOAD_LIBRARY_SEARCH_SYSTEM32);
HMODULE qwave = LoadLibraryExA("qwave.dll", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32);
if (!qwave) {
BOOST_LOG(debug) << "qwave.dll is not available on this OS"sv;
return;
@@ -1845,11 +1845,11 @@ namespace platf {
}
operator bool() override {
return timer != NULL;
return timer != nullptr;
}
private:
HANDLE timer = NULL;
HANDLE timer = nullptr;
};
std::unique_ptr<high_precision_timer> create_high_precision_timer() {

View File

@@ -9,7 +9,7 @@
#include <string_view>
// platform includes
#include <windows.h>
#include <Windows.h>
#include <winnt.h>
namespace platf {

View File

@@ -60,7 +60,7 @@ namespace nvprefs {
void driver_settings_t::destroy() {
if (session_handle) {
NvAPI_DRS_DestroySession(session_handle);
session_handle = 0;
session_handle = nullptr;
}
NvAPI_Unload();
}
@@ -105,7 +105,7 @@ namespace nvprefs {
if (swapchain_data) {
NvAPI_Status status;
NvDRSProfileHandle profile_handle = 0;
NvDRSProfileHandle profile_handle = nullptr;
status = NvAPI_DRS_GetBaseProfile(session_handle, &profile_handle);
if (status != NVAPI_OK) {
nvapi_error_message(status);
@@ -168,7 +168,7 @@ namespace nvprefs {
return true;
}
NvDRSProfileHandle profile_handle = 0;
NvDRSProfileHandle profile_handle = nullptr;
status = NvAPI_DRS_GetBaseProfile(session_handle, &profile_handle);
if (status != NVAPI_OK) {
nvapi_error_message(status);
@@ -224,7 +224,7 @@ namespace nvprefs {
NvAPI_UnicodeString profile_name = {};
fill_nvapi_string(profile_name, sunshine_application_profile_name);
NvDRSProfileHandle profile_handle = 0;
NvDRSProfileHandle profile_handle = nullptr;
status = NvAPI_DRS_FindProfileByName(session_handle, profile_name, &profile_handle);
if (status != NVAPI_OK) {

View File

@@ -36,7 +36,7 @@ namespace nvprefs {
bool check_and_modify_application_profile(bool &modified);
private:
NvDRSSessionHandle session_handle = 0;
NvDRSSessionHandle session_handle = nullptr;
};
} // namespace nvprefs

View File

@@ -15,7 +15,7 @@
namespace {
std::map<const char *, void *> interfaces;
HMODULE dll = NULL;
HMODULE dll = nullptr;
template<typename Func, typename... Args>
NvAPI_Status call_interface(const char *name, Args... args) {
@@ -47,7 +47,7 @@ NvAPI_Initialize() {
auto dll_name = "nvapi.dll";
#endif
if ((dll = LoadLibraryEx(dll_name, NULL, LOAD_LIBRARY_SEARCH_SYSTEM32))) {
if ((dll = LoadLibraryEx(dll_name, nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32))) {
if (auto query_interface = (decltype(nvapi_QueryInterface) *) GetProcAddress(dll, "nvapi_QueryInterface")) {
for (const auto &item : nvapi_interface_table) {
interfaces[item.func] = query_interface(item.id);
@@ -64,7 +64,7 @@ NVAPI_INTERFACE NvAPI_Unload() {
if (dll) {
interfaces.clear();
FreeLibrary(dll);
dll = NULL;
dll = nullptr;
}
return NVAPI_OK;
}

View File

@@ -7,8 +7,8 @@
// platform includes
// disable clang-format header reordering
// clang-format off
#include <windows.h>
#include <aclapi.h>
#include <Windows.h>
#include <AclAPI.h>
// clang-format on
// local includes
@@ -21,7 +21,7 @@ namespace nvprefs {
explicit operator bool() const {
auto handle = get();
return handle != NULL && handle != INVALID_HANDLE_VALUE;
return handle != nullptr && handle != INVALID_HANDLE_VALUE;
}
};

View File

@@ -51,7 +51,7 @@ namespace nvprefs {
std::optional<undo_file_t> undo_file_t::open_existing_file(std::filesystem::path file_path, bool &access_denied) {
undo_file_t file;
file.file_handle.reset(CreateFileW(file_path.c_str(), GENERIC_READ | DELETE, 0, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL));
file.file_handle.reset(CreateFileW(file_path.c_str(), GENERIC_READ | DELETE, 0, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr));
if (file.file_handle) {
access_denied = false;
return file;
@@ -64,7 +64,7 @@ namespace nvprefs {
std::optional<undo_file_t> undo_file_t::create_new_file(std::filesystem::path file_path) {
undo_file_t file;
file.file_handle.reset(CreateFileW(file_path.c_str(), GENERIC_WRITE | STANDARD_RIGHTS_ALL, 0, nullptr, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL));
file.file_handle.reset(CreateFileW(file_path.c_str(), GENERIC_WRITE | STANDARD_RIGHTS_ALL, 0, nullptr, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, nullptr));
if (file.file_handle) {
// give GENERIC_READ, GENERIC_WRITE and DELETE permissions to Users group

View File

@@ -3,12 +3,12 @@
* @brief Definitions for Windows mDNS service registration.
*/
// platform includes
// winsock2.h must be included before windows.h
// WinSock2.h must be included before Windows.h
// clang-format off
#include <winsock2.h>
#include <windows.h>
#include <WinSock2.h>
#include <Windows.h>
// clang-format on
#include <windns.h>
#include <WinDNS.h>
#include <winerror.h>
// local includes

View File

@@ -394,7 +394,7 @@ namespace proc {
_env["APOLLO_CLIENT_HOST_AUDIO"] = launch_session->host_audio ? "true" : "false";
_env["APOLLO_CLIENT_ENABLE_SOPS"] = launch_session->enable_sops ? "true" : "false";
int channelCount = launch_session->surround_info & (65535);
int channelCount = launch_session->surround_info & 65535;
switch (channelCount) {
case 2:
_env["SUNSHINE_CLIENT_AUDIO_CONFIGURATION"] = "2.0";

View File

@@ -12,6 +12,7 @@ extern "C" {
// standard includes
#include <array>
#include <cctype>
#include <format>
#include <set>
#include <unordered_map>
#include <utility>
@@ -897,7 +898,7 @@ namespace rtsp_stream {
session_option.next = &port_option;
// Moonlight merely requires 'server_port=<port>'
auto port_value = "server_port=" + std::to_string(port);
auto port_value = std::format("server_port={}", static_cast<int>(port));
port_option.option = const_cast<char *>("Transport");
port_option.content = port_value.data();

View File

@@ -34,8 +34,12 @@
#endif
// standard includes
#include <atomic>
#include <chrono>
#include <csignal>
#include <format>
#include <string>
#include <thread>
// lib includes
#include <boost/filesystem.hpp>
@@ -56,9 +60,14 @@ using namespace std::literals;
// system_tray namespace
namespace system_tray {
static std::atomic<bool> tray_initialized = false;
static std::atomic tray_initialized = false;
void tray_open_ui_cb(struct tray_menu *item) {
// Threading variables for all platforms
static std::thread tray_thread;
static std::atomic tray_thread_running = false;
static std::atomic tray_thread_should_exit = false;
void tray_open_ui_cb([[maybe_unused]] struct tray_menu *item) {
BOOST_LOG(info) << "Opening UI from system tray"sv;
launch_ui();
}
@@ -69,20 +78,20 @@ namespace system_tray {
proc::proc.terminate();
}
void tray_reset_display_device_config_cb(struct tray_menu *item) {
void tray_reset_display_device_config_cb([[maybe_unused]] struct tray_menu *item) {
BOOST_LOG(info) << "Resetting display device config from system tray"sv;
std::ignore = display_device::reset_persistence();
}
void tray_restart_cb(struct tray_menu *item) {
void tray_restart_cb([[maybe_unused]] struct tray_menu *item) {
BOOST_LOG(info) << "Restarting from system tray"sv;
proc::proc.terminate();
platf::restart();
}
void tray_quit_cb(struct tray_menu *item) {
void tray_quit_cb([[maybe_unused]] struct tray_menu *item) {
BOOST_LOG(info) << "Quitting from system tray"sv;
proc::proc.terminate();
@@ -90,7 +99,7 @@ namespace system_tray {
#ifdef _WIN32
// If we're running in a service, return a special status to
// tell it to terminate too, otherwise it will just respawn us.
if (GetConsoleWindow() == NULL) {
if (GetConsoleWindow() == nullptr) {
lifetime::exit_sunshine(ERROR_SHUTDOWN_IN_PROGRESS, true);
return;
}
@@ -131,7 +140,7 @@ namespace system_tray {
.allIconPaths = {TRAY_ICON, TRAY_ICON_LOCKED, TRAY_ICON_PLAYING, TRAY_ICON_PAUSING},
};
int system_tray() {
int init_tray() {
#ifdef _WIN32
// If we're running as SYSTEM, Explorer.exe will not have permission to open our thread handle
// to monitor for thread termination. If Explorer fails to open our thread, our tray icon
@@ -197,52 +206,32 @@ namespace system_tray {
if (tray_init(&tray) < 0) {
BOOST_LOG(warning) << "Failed to create system tray"sv;
return 1;
} else {
BOOST_LOG(info) << "System tray created"sv;
}
BOOST_LOG(info) << "System tray created"sv;
tray_initialized = true;
while (tray_loop(1) == 0) {
BOOST_LOG(debug) << "System tray loop"sv;
return 0;
}
int process_tray_events() {
if (!tray_initialized) {
return 1;
}
// Process one iteration of the tray loop with non-blocking mode (0)
if (const int result = tray_loop(0); result != 0) {
BOOST_LOG(warning) << "System tray loop failed"sv;
return result;
}
return 0;
}
void run_tray() {
// create the system tray
#if defined(__APPLE__) || defined(__MACH__)
// macOS requires that UI elements be created on the main thread
// creating tray using dispatch queue does not work, although the code doesn't actually throw any (visible) errors
// dispatch_async(dispatch_get_main_queue(), ^{
// system_tray();
// });
BOOST_LOG(info) << "system_tray() is not yet implemented for this platform."sv;
#else // Windows, Linux
// create tray in separate thread
#ifdef _WIN32
std::string tmp_str = "Open Apollo (" + config::nvhttp.sunshine_name + ":" + std::to_string(net::map_port(confighttp::PORT_HTTPS)) + ")";
static const std::string title_str = utf8ToAcp(tmp_str);
#else
static const std::string title_str = "Open Apollo (" + config::nvhttp.sunshine_name + ":" + std::to_string(net::map_port(confighttp::PORT_HTTPS)) + ")";
#endif
tray.menu[0].text = title_str.c_str();
if (config::sunshine.hide_tray_controls) {
tray.menu[1].text = nullptr;
}
std::thread tray_thread(system_tray);
tray_thread.detach();
#endif
}
int end_tray() {
tray_initialized = false;
tray_exit();
if (tray_initialized) {
tray_initialized = false;
tray_exit();
}
return 0;
}
@@ -251,10 +240,10 @@ namespace system_tray {
return;
}
tray.notification_title = NULL;
tray.notification_text = NULL;
tray.notification_cb = NULL;
tray.notification_icon = NULL;
tray.notification_title = nullptr;
tray.notification_text = nullptr;
tray.notification_cb = nullptr;
tray.notification_icon = nullptr;
tray.icon = TRAY_ICON_PLAYING;
tray_update(&tray);
@@ -280,10 +269,10 @@ namespace system_tray {
return;
}
tray.notification_title = NULL;
tray.notification_text = NULL;
tray.notification_cb = NULL;
tray.notification_icon = NULL;
tray.notification_title = nullptr;
tray.notification_text = nullptr;
tray.notification_cb = nullptr;
tray.notification_icon = nullptr;
tray.icon = TRAY_ICON_PAUSING;
tray_update(&tray);
char msg[256];
@@ -304,10 +293,10 @@ namespace system_tray {
return;
}
tray.notification_title = NULL;
tray.notification_text = NULL;
tray.notification_cb = NULL;
tray.notification_icon = NULL;
tray.notification_title = nullptr;
tray.notification_text = nullptr;
tray.notification_cb = nullptr;
tray.notification_icon = nullptr;
tray.icon = TRAY_ICON;
tray_update(&tray);
char msg[256];
@@ -358,10 +347,10 @@ namespace system_tray {
return;
}
tray.notification_title = NULL;
tray.notification_text = NULL;
tray.notification_cb = NULL;
tray.notification_icon = NULL;
tray.notification_title = nullptr;
tray.notification_text = nullptr;
tray.notification_cb = nullptr;
tray.notification_icon = nullptr;
tray.icon = TRAY_ICON;
tray_update(&tray);
tray.icon = TRAY_ICON;
@@ -370,7 +359,7 @@ namespace system_tray {
tray.notification_icon = TRAY_ICON_LOCKED;
tray.tooltip = PROJECT_NAME;
tray.notification_cb = []() {
launch_ui_with_path("/pin#PIN");
launch_ui("/pin#PIN");
};
tray_update(&tray);
}
@@ -422,6 +411,138 @@ namespace system_tray {
tray_update(&tray);
}
// Threading functions available on all platforms
static void tray_thread_worker() {
BOOST_LOG(info) << "System tray thread started"sv;
// Initialize the tray in this thread
if (init_tray() != 0) {
BOOST_LOG(error) << "Failed to initialize tray in thread"sv;
tray_thread_running = false;
return;
}
tray_thread_running = true;
// Main tray event loop
while (!tray_thread_should_exit) {
if (process_tray_events() != 0) {
BOOST_LOG(warning) << "Tray event processing failed in thread"sv;
break;
}
// Sleep to avoid busy waiting
std::this_thread::sleep_for(std::chrono::milliseconds(50));
}
// Clean up the tray
end_tray();
tray_thread_running = false;
BOOST_LOG(info) << "System tray thread ended"sv;
}
// void run_tray() {
// // create the system tray
// #if defined(__APPLE__) || defined(__MACH__)
// // macOS requires that UI elements be created on the main thread
// // creating tray using dispatch queue does not work, although the code doesn't actually throw any (visible) errors
// // dispatch_async(dispatch_get_main_queue(), ^{
// // system_tray();
// // });
// BOOST_LOG(info) << "system_tray() is not yet implemented for this platform."sv;
// #else // Windows, Linux
// // create tray in separate thread
// #ifdef _WIN32
// std::string tmp_str = "Open Apollo (" + config::nvhttp.sunshine_name + ":" + std::to_string(net::map_port(confighttp::PORT_HTTPS)) + ")";
// static const std::string title_str = utf8ToAcp(tmp_str);
// #else
// static const std::string title_str = "Open Apollo (" + config::nvhttp.sunshine_name + ":" + std::to_string(net::map_port(confighttp::PORT_HTTPS)) + ")";
// #endif
// tray.menu[0].text = title_str.c_str();
// if (config::sunshine.hide_tray_controls) {
// tray.menu[1].text = nullptr;
// }
// std::thread tray_thread(system_tray);
// tray_thread.detach();
// #endif
// }
int init_tray_threaded() {
if (tray_thread_running) {
BOOST_LOG(warning) << "Tray thread is already running"sv;
return 1;
}
#ifdef _WIN32
std::string tmp_str = "Open Apollo (" + config::nvhttp.sunshine_name + ":" + std::to_string(net::map_port(confighttp::PORT_HTTPS)) + ")";
static const std::string title_str = utf8ToAcp(tmp_str);
#else
static const std::string title_str = "Open Apollo (" + config::nvhttp.sunshine_name + ":" + std::to_string(net::map_port(confighttp::PORT_HTTPS)) + ")";
#endif
tray.menu[0].text = title_str.c_str();
if (config::sunshine.hide_tray_controls) {
tray.menu[1].text = nullptr;
}
tray_thread_should_exit = false;
try {
tray_thread = std::thread(tray_thread_worker);
// Wait for the thread to start and initialize
const auto start_time = std::chrono::steady_clock::now();
while (!tray_thread_running && !tray_thread_should_exit) {
std::this_thread::sleep_for(std::chrono::milliseconds(10));
// Timeout after 10 seconds
if (std::chrono::steady_clock::now() - start_time > std::chrono::seconds(10)) {
BOOST_LOG(error) << "Tray thread initialization timeout"sv;
tray_thread_should_exit = true;
if (tray_thread.joinable()) {
tray_thread.join();
}
return 1;
}
}
if (!tray_thread_running) {
BOOST_LOG(error) << "Tray thread failed to start"sv;
if (tray_thread.joinable()) {
tray_thread.join();
}
return 1;
}
BOOST_LOG(info) << "System tray thread initialized successfully"sv;
return 0;
} catch (const std::exception &e) {
BOOST_LOG(error) << "Failed to create tray thread: " << e.what();
return 1;
}
}
int end_tray_threaded() {
if (!tray_thread_running) {
return 0;
}
BOOST_LOG(info) << "Stopping system tray thread"sv;
tray_thread_should_exit = true;
if (tray_thread.joinable()) {
tray_thread.join();
}
BOOST_LOG(info) << "System tray thread stopped"sv;
return 0;
}
} // namespace system_tray
#ifdef BOOST_PROCESS_VERSION

View File

@@ -12,7 +12,7 @@ namespace system_tray {
* @brief Callback for opening the UI from the system tray.
* @param item The tray menu item.
*/
void tray_open_ui_cb(struct tray_menu *item);
void tray_open_ui_cb([[maybe_unused]] struct tray_menu *item);
void tray_force_stop_cb(struct tray_menu *item);
@@ -21,32 +21,31 @@ namespace system_tray {
* @brief Callback for resetting display device configuration.
* @param item The tray menu item.
*/
void tray_reset_display_device_config_cb(struct tray_menu *item);
void tray_reset_display_device_config_cb([[maybe_unused]] struct tray_menu *item);
/**
* @brief Callback for restarting Sunshine from the system tray.
* @param item The tray menu item.
*/
void tray_restart_cb(struct tray_menu *item);
void tray_restart_cb([[maybe_unused]] struct tray_menu *item);
/**
* @brief Callback for exiting Sunshine from the system tray.
* @param item The tray menu item.
*/
void tray_quit_cb(struct tray_menu *item);
void tray_quit_cb([[maybe_unused]] struct tray_menu *item);
/**
* @brief Create the system tray.
* @details This function has an endless loop, so it should be run in a separate thread.
* @return 1 if the system tray failed to create, otherwise 0 once the tray has been terminated.
* @brief Initializes the system tray without starting a loop.
* @return 0 if initialization was successful, non-zero otherwise.
*/
int system_tray();
int init_tray();
/**
* @brief Run the system tray with platform specific options.
* @todo macOS requires that UI elements be created on the main thread, so the system tray is not currently implemented for macOS.
* @brief Processes a single tray event iteration.
* @return 0 if processing was successful, non-zero otherwise.
*/
int run_tray();
int process_tray_events();
/**
* @brief Exit the system tray.
@@ -83,4 +82,15 @@ namespace system_tray {
void update_tray_paired(std::string device_name);
void update_tray_client_connected(std::string client_name);
/**
* @brief Initializes and runs the system tray in a separate thread.
* @return 0 if initialization was successful, non-zero otherwise.
*/
int init_tray_threaded();
/**
* @brief Stops the threaded system tray and waits for the thread to finish.
* @return 0 after stopping the threaded tray.
*/
int end_tray_threaded();
} // namespace system_tray

View File

@@ -325,6 +325,12 @@ namespace video {
avcodec_encode_session_t(avcodec_encode_session_t &&other) noexcept = default;
~avcodec_encode_session_t() {
// Flush any remaining frames in the encoder
if (avcodec_send_frame(avcodec_ctx.get(), nullptr) == 0) {
packet_raw_avcodec pkt;
while (avcodec_receive_packet(avcodec_ctx.get(), pkt.av_packet) == 0);
}
// Order matters here because the context relies on the hwdevice still being valid
avcodec_ctx.reset();
device.reset();
@@ -546,7 +552,7 @@ namespace video {
{"forced-idr"s, 1},
{"zerolatency"s, 1},
{"surfaces"s, 1},
{"filler_data"s, false},
{"cbr_padding"s, false},
{"preset"s, &config::video.nv_legacy.preset},
{"tune"s, NV_ENC_TUNING_INFO_ULTRA_LOW_LATENCY},
{"rc"s, NV_ENC_PARAMS_RC_CBR},
@@ -567,6 +573,7 @@ namespace video {
{"forced-idr"s, 1},
{"zerolatency"s, 1},
{"surfaces"s, 1},
{"cbr_padding"s, false},
{"preset"s, &config::video.nv_legacy.preset},
{"tune"s, NV_ENC_TUNING_INFO_ULTRA_LOW_LATENCY},
{"rc"s, NV_ENC_PARAMS_RC_CBR},
@@ -592,6 +599,7 @@ namespace video {
{"forced-idr"s, 1},
{"zerolatency"s, 1},
{"surfaces"s, 1},
{"cbr_padding"s, false},
{"preset"s, &config::video.nv_legacy.preset},
{"tune"s, NV_ENC_TUNING_INFO_ULTRA_LOW_LATENCY},
{"rc"s, NV_ENC_PARAMS_RC_CBR},
@@ -740,6 +748,7 @@ namespace video {
{"filler_data"s, false},
{"forced_idr"s, 1},
{"latency"s, "lowest_latency"s},
{"async_depth"s, 1},
{"skip_frame"s, 0},
{"log_to_dbg"s, []() {
return config::sunshine.min_log_level < 2 ? 1 : 0;
@@ -763,6 +772,7 @@ namespace video {
{"filler_data"s, false},
{"forced_idr"s, 1},
{"latency"s, 1},
{"async_depth"s, 1},
{"skip_frame"s, 0},
{"log_to_dbg"s, []() {
return config::sunshine.min_log_level < 2 ? 1 : 0;
@@ -801,6 +811,7 @@ namespace video {
{"filler_data"s, false},
{"forced_idr"s, 1},
{"latency"s, 1},
{"async_depth"s, 1},
{"frame_skipping"s, 0},
{"log_to_dbg"s, []() {
return config::sunshine.min_log_level < 2 ? 1 : 0;
@@ -1559,22 +1570,22 @@ namespace video {
case 0:
// 10-bit h264 encoding is not supported by our streaming protocol
assert(!config.dynamicRange);
ctx->profile = (config.chromaSamplingType == 1) ? FF_PROFILE_H264_HIGH_444_PREDICTIVE : FF_PROFILE_H264_HIGH;
ctx->profile = (config.chromaSamplingType == 1) ? AV_PROFILE_H264_HIGH_444_PREDICTIVE : AV_PROFILE_H264_HIGH;
break;
case 1:
if (config.chromaSamplingType == 1) {
// HEVC uses the same RExt profile for both 8 and 10 bit YUV 4:4:4 encoding
ctx->profile = FF_PROFILE_HEVC_REXT;
ctx->profile = AV_PROFILE_HEVC_REXT;
} else {
ctx->profile = config.dynamicRange ? FF_PROFILE_HEVC_MAIN_10 : FF_PROFILE_HEVC_MAIN;
ctx->profile = config.dynamicRange ? AV_PROFILE_HEVC_MAIN_10 : AV_PROFILE_HEVC_MAIN;
}
break;
case 2:
// AV1 supports both 8 and 10 bit encoding with the same Main profile
// but YUV 4:4:4 sampling requires High profile
ctx->profile = (config.chromaSamplingType == 1) ? FF_PROFILE_AV1_HIGH : FF_PROFILE_AV1_MAIN;
ctx->profile = (config.chromaSamplingType == 1) ? AV_PROFILE_AV1_HIGH : AV_PROFILE_AV1_MAIN;
break;
}