Try add sudovda driver support
This commit is contained in:
@@ -39,6 +39,7 @@
|
||||
#include "utility.h"
|
||||
#include "uuid.h"
|
||||
#include "version.h"
|
||||
#include "process.h"
|
||||
|
||||
using namespace std::literals;
|
||||
|
||||
@@ -545,6 +546,9 @@ namespace confighttp {
|
||||
outputTree.put("status", "true");
|
||||
outputTree.put("platform", SUNSHINE_PLATFORM);
|
||||
outputTree.put("version", PROJECT_VER);
|
||||
#ifdef _WIN32
|
||||
outputTree.put("vdisplaySupported", proc::vdisplayDriverInitialized ? "true" : "false");
|
||||
#endif
|
||||
|
||||
auto vars = config::parse_config(file_handler::read_file(config::sunshine.config_file.c_str()));
|
||||
|
||||
|
||||
32
src/misc.h
32
src/misc.h
@@ -1,32 +0,0 @@
|
||||
#pragma once
|
||||
#include <string>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
|
||||
std::string convertUtf8ToCurrentCodepage(const std::string& utf8Str) {
|
||||
if (GetACP() == CP_UTF8) {
|
||||
return std::string(utf8Str);
|
||||
}
|
||||
// Step 1: Convert UTF-8 to UTF-16
|
||||
int utf16Len = MultiByteToWideChar(CP_UTF8, 0, utf8Str.c_str(), -1, NULL, 0);
|
||||
if (utf16Len == 0) {
|
||||
throw std::runtime_error("Failed to convert UTF-8 to UTF-16");
|
||||
}
|
||||
|
||||
std::wstring utf16Str(utf16Len, L'\0');
|
||||
MultiByteToWideChar(CP_UTF8, 0, utf8Str.c_str(), -1, &utf16Str[0], utf16Len);
|
||||
|
||||
// Step 2: Convert UTF-16 to the current Windows codepage
|
||||
int codepageLen = WideCharToMultiByte(GetACP(), 0, utf16Str.c_str(), -1, NULL, 0, NULL, NULL);
|
||||
if (codepageLen == 0) {
|
||||
throw std::runtime_error("Failed to convert UTF-16 to current Windows codepage");
|
||||
}
|
||||
|
||||
std::string codepageStr(codepageLen, '\0');
|
||||
WideCharToMultiByte(GetACP(), 0, utf16Str.c_str(), -1, &codepageStr[0], codepageLen, NULL, NULL);
|
||||
|
||||
return codepageStr;
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -334,6 +334,7 @@ namespace nvhttp {
|
||||
launch_session->surround_params = (get_arg(args, "surroundParams", ""));
|
||||
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->virtual_display = util::from_view(get_arg(args, "virtualDisplay", "0"));
|
||||
|
||||
// Encrypted RTSP is enabled with client reported corever >= 1
|
||||
auto corever = util::from_view(get_arg(args, "corever", "0"));
|
||||
@@ -702,6 +703,11 @@ namespace nvhttp {
|
||||
tree.put("root.ExternalPort", net::map_port(PORT_HTTP));
|
||||
tree.put("root.MaxLumaPixelsHEVC", video::active_hevc_mode > 1 ? "1869449984" : "0");
|
||||
|
||||
#ifdef _WIN32
|
||||
tree.put("root.VirtualDisplayCapable", true);
|
||||
tree.put("root.VirtualDisplayDriverReady", proc::vdisplayDriverInitialized);
|
||||
#endif
|
||||
|
||||
// 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<SimpleWeb::HTTPS, T>) {
|
||||
|
||||
29
src/platform/windows/utils.h
Normal file
29
src/platform/windows/utils.h
Normal file
@@ -0,0 +1,29 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <windows.h>
|
||||
|
||||
std::string convertUtf8ToCurrentCodepage(const std::string& utf8Str) {
|
||||
if (GetACP() == CP_UTF8) {
|
||||
return std::string(utf8Str);
|
||||
}
|
||||
// Step 1: Convert UTF-8 to UTF-16
|
||||
int utf16Len = MultiByteToWideChar(CP_UTF8, 0, utf8Str.c_str(), -1, NULL, 0);
|
||||
if (utf16Len == 0) {
|
||||
return std::string(utf8Str);
|
||||
}
|
||||
|
||||
std::wstring utf16Str(utf16Len, L'\0');
|
||||
MultiByteToWideChar(CP_UTF8, 0, utf8Str.c_str(), -1, &utf16Str[0], utf16Len);
|
||||
|
||||
// Step 2: Convert UTF-16 to the current Windows codepage
|
||||
int codepageLen = WideCharToMultiByte(GetACP(), 0, utf16Str.c_str(), -1, NULL, 0, NULL, NULL);
|
||||
if (codepageLen == 0) {
|
||||
return std::string(utf8Str);
|
||||
}
|
||||
|
||||
std::string codepageStr(codepageLen, '\0');
|
||||
WideCharToMultiByte(GetACP(), 0, utf16Str.c_str(), -1, &codepageStr[0], codepageLen, NULL, NULL);
|
||||
|
||||
return codepageStr;
|
||||
}
|
||||
199
src/platform/windows/virtual_display.cpp
Normal file
199
src/platform/windows/virtual_display.cpp
Normal file
@@ -0,0 +1,199 @@
|
||||
#include <windows.h>
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
#include <setupapi.h>
|
||||
#include <initguid.h>
|
||||
#include <combaseapi.h>
|
||||
#include <thread>
|
||||
#include "virtual_display.h"
|
||||
|
||||
using namespace SUDOVDA;
|
||||
|
||||
namespace VDISPLAY {
|
||||
// {dff7fd29-5b75-41d1-9731-b32a17a17104}
|
||||
static const GUID DEFAULT_DISPLAY_GUID = { 0xdff7fd29, 0x5b75, 0x41d1, { 0x97, 0x31, 0xb3, 0x2a, 0x17, 0xa1, 0x71, 0x04 } };
|
||||
|
||||
HANDLE SUDOVDA_DRIVER_HANDLE = INVALID_HANDLE_VALUE;
|
||||
|
||||
LONG getDeviceSettings(const wchar_t* deviceName, DEVMODEW& devMode) {
|
||||
devMode.dmSize = sizeof(DEVMODEW);
|
||||
return EnumDisplaySettingsW(deviceName, ENUM_CURRENT_SETTINGS, &devMode);
|
||||
}
|
||||
|
||||
LONG changeDisplaySettings(const wchar_t* deviceName, int width, int height, int refresh_rate) {
|
||||
DEVMODEW devMode = {0};
|
||||
devMode.dmSize = sizeof(devMode);
|
||||
|
||||
if (EnumDisplaySettingsW(deviceName, ENUM_CURRENT_SETTINGS, &devMode)) {
|
||||
devMode.dmPelsWidth = width;
|
||||
devMode.dmPelsHeight = height;
|
||||
devMode.dmDisplayFrequency = refresh_rate;
|
||||
devMode.dmFields = DM_PELSWIDTH | DM_PELSHEIGHT | DM_DISPLAYFREQUENCY;
|
||||
|
||||
return ChangeDisplaySettingsExW(deviceName, &devMode, NULL, CDS_UPDATEREGISTRY, NULL);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool setPrimaryDisplay(const wchar_t* primaryDeviceName) {
|
||||
DEVMODEW primaryDevMode{};
|
||||
if (!getDeviceSettings(primaryDeviceName, primaryDevMode)) {
|
||||
return false;
|
||||
};
|
||||
|
||||
int offset_x = primaryDevMode.dmPosition.x;
|
||||
int offset_y = primaryDevMode.dmPosition.y;
|
||||
|
||||
LONG result;
|
||||
|
||||
DISPLAY_DEVICEW displayDevice;
|
||||
displayDevice.cb = sizeof(DISPLAY_DEVICEA);
|
||||
int device_index = 0;
|
||||
|
||||
while (EnumDisplayDevicesW(NULL, device_index, &displayDevice, 0)) {
|
||||
device_index++;
|
||||
if (!(displayDevice.StateFlags & DISPLAY_DEVICE_ACTIVE)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
DEVMODEW devMode{};
|
||||
if (getDeviceSettings(displayDevice.DeviceName, devMode)) {
|
||||
devMode.dmPosition.x -= offset_x;
|
||||
devMode.dmPosition.y -= offset_y;
|
||||
devMode.dmFields = DM_POSITION;
|
||||
|
||||
result = ChangeDisplaySettingsExW(displayDevice.DeviceName, &devMode, NULL, CDS_UPDATEREGISTRY | CDS_NORESET, NULL);
|
||||
if (result != DISP_CHANGE_SUCCESSFUL) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update primary device's config to ensure it's primary
|
||||
primaryDevMode.dmPosition.x = 0;
|
||||
primaryDevMode.dmPosition.y = 0;
|
||||
primaryDevMode.dmFields = DM_POSITION;
|
||||
ChangeDisplaySettingsExW(primaryDeviceName, &primaryDevMode, NULL, CDS_UPDATEREGISTRY | CDS_NORESET | CDS_SET_PRIMARY, NULL);
|
||||
|
||||
result = ChangeDisplaySettingsExW(NULL, NULL, NULL, 0, NULL);
|
||||
if (result != DISP_CHANGE_SUCCESSFUL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool openVDisplayDevice() {
|
||||
SUDOVDA_DRIVER_HANDLE = OpenDevice(&SUVDA_INTERFACE_GUID);
|
||||
if (SUDOVDA_DRIVER_HANDLE == INVALID_HANDLE_VALUE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!CheckProtocolCompatible(SUDOVDA_DRIVER_HANDLE)) {
|
||||
printf("[SUDOVDA] SUDOVDA protocol not compatible with driver!\n");
|
||||
CloseHandle(SUDOVDA_DRIVER_HANDLE);
|
||||
SUDOVDA_DRIVER_HANDLE = INVALID_HANDLE_VALUE;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool startPingThread() {
|
||||
if (SUDOVDA_DRIVER_HANDLE == INVALID_HANDLE_VALUE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
VIRTUAL_DISPLAY_GET_WATCHDOG_OUT watchdogOut;
|
||||
if (GetWatchdogTimeout(SUDOVDA_DRIVER_HANDLE, watchdogOut)) {
|
||||
printf("[SUDOVDA] Watchdog: Timeout %d, Countdown %d\n", watchdogOut.Timeout, watchdogOut.Countdown);
|
||||
} else {
|
||||
printf("[SUDOVDA] Watchdog fetch failed!\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (watchdogOut.Timeout) {
|
||||
auto sleepInterval = watchdogOut.Timeout * 1000 / 2;
|
||||
std::thread ping_thread([sleepInterval]{
|
||||
for (;;) {
|
||||
if (!sleepInterval) return;
|
||||
if (!PingDriver(SUDOVDA_DRIVER_HANDLE)) return;
|
||||
Sleep(sleepInterval);
|
||||
}
|
||||
});
|
||||
|
||||
ping_thread.detach();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::wstring createVirtualDisplay(
|
||||
const char* s_client_uid,
|
||||
const char* s_client_name,
|
||||
const char* s_app_name,
|
||||
uint32_t width,
|
||||
uint32_t height,
|
||||
uint32_t fps,
|
||||
GUID& guid
|
||||
) {
|
||||
if (SUDOVDA_DRIVER_HANDLE == INVALID_HANDLE_VALUE) {
|
||||
return std::wstring();
|
||||
}
|
||||
|
||||
if (!s_app_name || !strlen(s_app_name) || !strcmp(s_app_name, "unknown")) {
|
||||
s_app_name = "ApolloVDisp";
|
||||
}
|
||||
|
||||
if (!s_client_name || !strlen(s_client_name) || !strcmp(s_client_name, "unknown")) {
|
||||
s_client_name = s_app_name;
|
||||
}
|
||||
|
||||
if (s_client_uid && strcmp(s_client_uid, "unknown")) {
|
||||
size_t len = strlen(s_client_uid);
|
||||
if (len > sizeof(GUID)) {
|
||||
len = sizeof(GUID);
|
||||
}
|
||||
memcpy((void*)&guid, (void*)s_client_uid, len);
|
||||
} else {
|
||||
guid = DEFAULT_DISPLAY_GUID;
|
||||
s_client_uid = "unknown";
|
||||
}
|
||||
|
||||
VIRTUAL_DISPLAY_ADD_OUT output;
|
||||
if (!AddVirtualDisplay(SUDOVDA_DRIVER_HANDLE, width, height, fps, guid, s_client_name, s_client_uid, output)) {
|
||||
printf("[SUDOVDA] Failed to add virtual display.\n");
|
||||
return std::wstring();
|
||||
}
|
||||
|
||||
uint32_t retryInterval = 20;
|
||||
wchar_t deviceName[CCHDEVICENAME]{};
|
||||
while (!GetAddedDisplayName(output, deviceName)) {
|
||||
Sleep(retryInterval);
|
||||
if (retryInterval > 160) {
|
||||
printf("[SUDOVDA] Cannot get name for newly added virtual display!\n");
|
||||
return std::wstring();
|
||||
}
|
||||
retryInterval *= 2;
|
||||
}
|
||||
|
||||
wprintf(L"[SUDOVDA] Virtual display added successfully: %ws\n", deviceName);
|
||||
printf("[SUDOVDA] Configuration: W: %d, H: %d, FPS: %d\n", width, height, fps);
|
||||
|
||||
return std::wstring(deviceName);
|
||||
}
|
||||
|
||||
bool removeVirtualDisplay(const GUID& guid) {
|
||||
if (SUDOVDA_DRIVER_HANDLE == INVALID_HANDLE_VALUE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (RemoveVirtualDisplay(SUDOVDA_DRIVER_HANDLE, guid)) {
|
||||
printf("[SUDOVDA] Virtual display removed successfully.\n");
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
30
src/platform/windows/virtual_display.h
Normal file
30
src/platform/windows/virtual_display.h
Normal file
@@ -0,0 +1,30 @@
|
||||
#pragma once
|
||||
|
||||
#ifndef FILE_DEVICE_UNKNOWN
|
||||
#define FILE_DEVICE_UNKNOWN 0x00000022
|
||||
#endif
|
||||
|
||||
#include <ddk/d4iface.h>
|
||||
#include <ddk/d4drvif.h>
|
||||
#include <sudovda/sudovda.h>
|
||||
|
||||
namespace VDISPLAY {
|
||||
extern HANDLE SUDOVDA_DRIVER_HANDLE;
|
||||
|
||||
LONG getDeviceSettings(const wchar_t* deviceName, DEVMODEW& devMode);
|
||||
LONG changeDisplaySettings(const wchar_t* deviceName, int width, int height, int refresh_rate);
|
||||
bool setPrimaryDisplay(const wchar_t* primaryDeviceName);
|
||||
|
||||
bool openVDisplayDevice();
|
||||
bool startPingThread();
|
||||
std::wstring createVirtualDisplay(
|
||||
const char* s_client_uid,
|
||||
const char* s_client_name,
|
||||
const char* s_app_name,
|
||||
uint32_t width,
|
||||
uint32_t height,
|
||||
uint32_t fps,
|
||||
GUID& guid
|
||||
);
|
||||
bool removeVirtualDisplay(const GUID& guid);
|
||||
}
|
||||
@@ -31,6 +31,7 @@
|
||||
#ifdef _WIN32
|
||||
// from_utf8() string conversion function
|
||||
#include "platform/windows/misc.h"
|
||||
#include "platform/windows/virtual_display.h"
|
||||
|
||||
// _SH constants for _wfsopen()
|
||||
#include <share.h>
|
||||
@@ -44,6 +45,10 @@ namespace proc {
|
||||
|
||||
proc_t proc;
|
||||
|
||||
#ifdef _WIN32
|
||||
bool vdisplayDriverInitialized = false;
|
||||
#endif
|
||||
|
||||
class deinit_t: public platf::deinit_t {
|
||||
public:
|
||||
~deinit_t() {
|
||||
@@ -53,6 +58,9 @@ namespace proc {
|
||||
|
||||
std::unique_ptr<platf::deinit_t>
|
||||
init() {
|
||||
#ifdef _WIN32
|
||||
vdisplayDriverInitialized = VDISPLAY::openVDisplayDevice();
|
||||
#endif
|
||||
return std::make_unique<deinit_t>();
|
||||
}
|
||||
|
||||
@@ -199,6 +207,32 @@ namespace proc {
|
||||
terminate();
|
||||
});
|
||||
|
||||
_launch_session = launch_session;
|
||||
|
||||
#ifdef _WIN32
|
||||
if (launch_session->virtual_display) {
|
||||
if (!vdisplayDriverInitialized) {
|
||||
// Try init driver again
|
||||
vdisplayDriverInitialized = VDISPLAY::openVDisplayDevice();
|
||||
}
|
||||
|
||||
if (vdisplayDriverInitialized) {
|
||||
std::wstring vdisplay_name = VDISPLAY::createVirtualDisplay(
|
||||
launch_session->unique_id.c_str(),
|
||||
launch_session->device_name.c_str(),
|
||||
_app.name.c_str(),
|
||||
launch_session->width,
|
||||
launch_session->height,
|
||||
launch_session->fps,
|
||||
launch_session->display_guid
|
||||
);
|
||||
|
||||
VDISPLAY::changeDisplaySettings(vdisplay_name.c_str(), launch_session->width, launch_session->height, launch_session->fps);
|
||||
VDISPLAY::setPrimaryDisplay(vdisplay_name.c_str());
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
for (; _app_prep_it != std::end(_app.prep_cmds); ++_app_prep_it) {
|
||||
auto &cmd = *_app_prep_it;
|
||||
|
||||
@@ -332,6 +366,13 @@ namespace proc {
|
||||
}
|
||||
|
||||
_pipe.reset();
|
||||
|
||||
#ifdef _WIN32
|
||||
if (vdisplayDriverInitialized && _launch_session && _launch_session->virtual_display) {
|
||||
VDISPLAY::removeVirtualDisplay(_launch_session->display_guid);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined SUNSHINE_TRAY && SUNSHINE_TRAY >= 1
|
||||
bool has_run = _app_id > 0;
|
||||
|
||||
@@ -344,6 +385,7 @@ namespace proc {
|
||||
|
||||
_app_id = -1;
|
||||
display_name.clear();
|
||||
_launch_session.reset();
|
||||
}
|
||||
|
||||
const std::vector<ctx_t> &
|
||||
|
||||
@@ -21,6 +21,10 @@
|
||||
namespace proc {
|
||||
using file_t = util::safe_ptr_v2<FILE, int, fclose>;
|
||||
|
||||
#ifdef _WIN32
|
||||
extern bool vdisplayDriverInitialized;
|
||||
#endif
|
||||
|
||||
typedef config::prep_cmd_t cmd_t;
|
||||
/**
|
||||
* pre_cmds -- guaranteed to be executed unless any of the commands fail.
|
||||
@@ -101,6 +105,8 @@ namespace proc {
|
||||
private:
|
||||
int _app_id;
|
||||
|
||||
std::shared_ptr<rtsp_stream::launch_session_t> _launch_session;
|
||||
|
||||
boost::process::environment _env;
|
||||
std::vector<ctx_t> _apps;
|
||||
ctx_t _app;
|
||||
|
||||
@@ -33,10 +33,15 @@ namespace rtsp_stream {
|
||||
std::string surround_params;
|
||||
bool enable_hdr;
|
||||
bool enable_sops;
|
||||
bool virtual_display;
|
||||
|
||||
std::optional<crypto::cipher::gcm_t> rtsp_cipher;
|
||||
std::string rtsp_url_scheme;
|
||||
uint32_t rtsp_iv_counter;
|
||||
|
||||
#ifdef _WIN32
|
||||
GUID display_guid{};
|
||||
#endif
|
||||
};
|
||||
|
||||
void
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <accctrl.h>
|
||||
#include <aclapi.h>
|
||||
#include "platform/windows/utils.h"
|
||||
#define TRAY_ICON WEB_DIR "images/apollo.ico"
|
||||
#define TRAY_ICON_PLAYING WEB_DIR "images/apollo-playing.ico"
|
||||
#define TRAY_ICON_PAUSING WEB_DIR "images/apollo-pausing.ico"
|
||||
@@ -46,7 +47,6 @@
|
||||
#include "network.h"
|
||||
#include "src/entry_handler.h"
|
||||
#include "version.h"
|
||||
#include "misc.h"
|
||||
|
||||
using namespace std::literals;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user