Try add sudovda driver support

This commit is contained in:
Yukino Song
2024-08-17 13:19:07 +08:00
parent 25919a9a89
commit 6fa277ec0b
13 changed files with 641 additions and 33 deletions

View File

@@ -52,6 +52,10 @@ set(PLATFORM_TARGET_FILES
"${CMAKE_SOURCE_DIR}/src/platform/windows/display_ram.cpp"
"${CMAKE_SOURCE_DIR}/src/platform/windows/display_wgc.cpp"
"${CMAKE_SOURCE_DIR}/src/platform/windows/audio.cpp"
"${CMAKE_SOURCE_DIR}/src/platform/windows/virtual_display.h"
"${CMAKE_SOURCE_DIR}/src/platform/windows/virtual_display.cpp"
"${CMAKE_SOURCE_DIR}/third-party/sudovda/sudovda-ioctl.h"
"${CMAKE_SOURCE_DIR}/third-party/sudovda/sudovda.h"
"${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"

View File

@@ -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()));

View File

@@ -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

View File

@@ -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>) {

View 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;
}

View 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;
}
}
}

View 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);
}

View File

@@ -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> &

View File

@@ -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;

View File

@@ -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

View File

@@ -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;

68
third-party/sudovda/sudovda-ioctl.h vendored Normal file
View File

@@ -0,0 +1,68 @@
#pragma once
#include <windows.h>
#ifdef __cplusplus
namespace SUDOVDA
{
#endif
#define IOCTL_ADD_VIRTUAL_DISPLAY CTL_CODE(FILE_DEVICE_UNKNOWN, 0x800, METHOD_BUFFERED, FILE_ANY_ACCESS)
#define IOCTL_REMOVE_VIRTUAL_DISPLAY CTL_CODE(FILE_DEVICE_UNKNOWN, 0x801, METHOD_BUFFERED, FILE_ANY_ACCESS)
#define IOCTL_SET_RENDER_ADAPTER CTL_CODE(FILE_DEVICE_UNKNOWN, 0x802, METHOD_BUFFERED, FILE_ANY_ACCESS)
#define IOCTL_GET_WATCHDOG CTL_CODE(FILE_DEVICE_UNKNOWN, 0x803, METHOD_BUFFERED, FILE_ANY_ACCESS)
#define IOCTL_DRIVER_PING CTL_CODE(FILE_DEVICE_UNKNOWN, 0x888, METHOD_BUFFERED, FILE_ANY_ACCESS)
#define IOCTL_GET_PROTOCOL_VERSION CTL_CODE(FILE_DEVICE_UNKNOWN, 0x8FF, METHOD_BUFFERED, FILE_ANY_ACCESS)
typedef struct _SUVDA_PROTOCAL_VERSION {
uint8_t Major;
uint8_t Minor;
uint8_t Incremental;
bool TestBuild;
} SUVDA_PROTOCAL_VERSION, * PSUVDA_PROTOCAL_VERSION;
// Please update the version after ioctl changed
static const SUVDA_PROTOCAL_VERSION VDAProtocolVersion = { 0, 2, 0, true };
static const char* SUVDA_HARDWARE_ID = "root\\sudomaker\\sudovda";
// DO NOT CHANGE
// {4d36e968-e325-11ce-bfc1-08002be10318}
static const GUID SUVDA_CLASS_GUID = { 0x4d36e968, 0xe325, 0x11ce, { 0xbf, 0xc1, 0x08, 0x00, 0x2b, 0xe1, 0x03, 0x18 } };
// {e5bcc234-1e0c-418a-a0d4-ef8b7501414d}
static const GUID SUVDA_INTERFACE_GUID = { 0xe5bcc234, 0x1e0c, 0x418a, { 0xa0, 0xd4, 0xef, 0x8b, 0x75, 0x01, 0x41, 0x4d } };
typedef struct _VIRTUAL_DISPLAY_ADD_PARAMS {
UINT Width;
UINT Height;
UINT RefreshRate;
GUID MonitorGuid;
CHAR DeviceName[14];
CHAR SerialNumber[14];
} VIRTUAL_DISPLAY_ADD_PARAMS, * PVIRTUAL_DISPLAY_ADD_PARAMS;
typedef struct _VIRTUAL_DISPLAY_REMOVE_PARAMS {
GUID MonitorGuid;
} VIRTUAL_DISPLAY_REMOVE_PARAMS, * PVIRTUAL_DISPLAY_REMOVE_PARAMS;
typedef struct _VIRTUAL_DISPLAY_ADD_OUT {
LUID AdapterLuid;
UINT TargetId;
} VIRTUAL_DISPLAY_ADD_OUT, * PVIRTUAL_DISPLAY_ADD_OUT;
typedef struct _VIRTUAL_DISPLAY_SET_RENDER_ADAPTER_PARAMS {
LUID AdapterLuid;
} VIRTUAL_DISPLAY_SET_RENDER_ADAPTER_PARAMS, * PVIRTUAL_DISPLAY_SET_RENDER_ADAPTER_PARAMS;
typedef struct _VIRTUAL_DISPLAY_GET_WATCHDOG_OUT {
UINT Timeout;
UINT Countdown;
} VIRTUAL_DISPLAY_GET_WATCHDOG_OUT, * PVIRTUAL_DISPLAY_GET_WATCHDOG_OUT;
typedef struct _VIRTUAL_DISPLAY_GET_PROTOCOL_VERSION_OUT {
SUVDA_PROTOCAL_VERSION Version;
} VIRTUAL_DISPLAY_GET_PROTOCOL_VERSION_OUT, * PVIRTUAL_DISPLAY_GET_PROTOCOL_VERSION_OUT;
#ifdef __cplusplus
} // namespace SUDOVDA
#endif

247
third-party/sudovda/sudovda.h vendored Normal file
View File

@@ -0,0 +1,247 @@
#pragma once
#include <algorithm>
#include <iostream>
#include <setupapi.h>
#include <cfgmgr32.h>
#include <stdio.h>
#include <string.h>
#include "sudovda-ioctl.h"
#ifdef _MSC_VER
#pragma comment(lib, "cfgmgr32.lib")
#pragma comment(lib, "setupapi.lib")
#endif
#ifdef __cplusplus
namespace SUDOVDA
{
#endif
static const HANDLE OpenDevice(const GUID* interfaceGuid) {
// Get the device information set for the specified interface GUID
HDEVINFO deviceInfoSet = SetupDiGetClassDevs(interfaceGuid, nullptr, nullptr, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
if (deviceInfoSet == INVALID_HANDLE_VALUE) {
return INVALID_HANDLE_VALUE;
}
HANDLE handle = INVALID_HANDLE_VALUE;
SP_DEVICE_INTERFACE_DATA deviceInterfaceData;
ZeroMemory(&deviceInterfaceData, sizeof(SP_DEVICE_INTERFACE_DATA));
deviceInterfaceData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);
for (DWORD i = 0; SetupDiEnumDeviceInterfaces(deviceInfoSet, nullptr, interfaceGuid, i, &deviceInterfaceData); ++i)
{
DWORD detailSize = 0;
SetupDiGetDeviceInterfaceDetailA(deviceInfoSet, &deviceInterfaceData, NULL, 0, &detailSize, NULL);
SP_DEVICE_INTERFACE_DETAIL_DATA_A *detail = (SP_DEVICE_INTERFACE_DETAIL_DATA_A *)calloc(1, detailSize);
detail->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA_A);
if (SetupDiGetDeviceInterfaceDetailA(deviceInfoSet, &deviceInterfaceData, detail, detailSize, &detailSize, NULL))
{
handle = CreateFileA(detail->DevicePath,
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_NO_BUFFERING | FILE_FLAG_OVERLAPPED | FILE_FLAG_WRITE_THROUGH,
NULL);
if (handle != NULL && handle != INVALID_HANDLE_VALUE) {
break;
}
}
free(detail);
}
SetupDiDestroyDeviceInfoList(deviceInfoSet);
return handle;
}
static const bool AddVirtualDisplay(HANDLE hDevice, UINT Width, UINT Height, UINT RefreshRate, const GUID& MonitorGuid, const CHAR* DeviceName, const CHAR* SerialNumber, VIRTUAL_DISPLAY_ADD_OUT& output) {
VIRTUAL_DISPLAY_ADD_PARAMS params{Width, Height, RefreshRate, MonitorGuid, {}, {}};
strncpy(params.DeviceName, DeviceName, 13);
strncpy(params.SerialNumber, SerialNumber, 13);
DWORD bytesReturned;
BOOL success = DeviceIoControl(
hDevice,
IOCTL_ADD_VIRTUAL_DISPLAY,
(LPVOID)&params,
sizeof(params),
(LPVOID)&output,
sizeof(output),
&bytesReturned,
nullptr
);
if (!success) {
std::cerr << "AddVirtualDisplay failed: " << GetLastError() << std::endl;
}
return success;
}
static const bool RemoveVirtualDisplay(HANDLE hDevice, const GUID& MonitorGuid) {
VIRTUAL_DISPLAY_REMOVE_PARAMS params{MonitorGuid};
DWORD bytesReturned;
BOOL success = DeviceIoControl(
hDevice,
IOCTL_REMOVE_VIRTUAL_DISPLAY,
(LPVOID)&params,
sizeof(params),
nullptr,
0,
&bytesReturned,
nullptr
);
if (!success) {
std::cerr << "RemoveVirtualDisplay failed: " << GetLastError() << std::endl;
}
return success;
}
static const bool SetRenderAdapter(HANDLE hDevice, const LUID& AdapterLuid) {
VIRTUAL_DISPLAY_SET_RENDER_ADAPTER_PARAMS params{AdapterLuid};
DWORD bytesReturned;
BOOL success = DeviceIoControl(
hDevice,
IOCTL_SET_RENDER_ADAPTER,
(LPVOID)&params,
sizeof(params),
nullptr,
0,
&bytesReturned,
nullptr
);
if (!success) {
std::cerr << "SetRenderAdapter failed: " << GetLastError() << std::endl;
}
return success;
}
static const bool GetWatchdogTimeout(HANDLE hDevice, VIRTUAL_DISPLAY_GET_WATCHDOG_OUT& output) {
DWORD bytesReturned;
BOOL success = DeviceIoControl(
hDevice,
IOCTL_GET_WATCHDOG,
nullptr,
0,
(LPVOID)&output,
sizeof(output),
&bytesReturned,
nullptr
);
if (!success) {
std::cerr << "DeviceIoControl failed: " << GetLastError() << std::endl;
}
return success;
}
static const bool GetProtocolVersion(HANDLE hDevice, VIRTUAL_DISPLAY_GET_PROTOCOL_VERSION_OUT& output) {
DWORD bytesReturned;
BOOL success = DeviceIoControl(
hDevice,
IOCTL_GET_PROTOCOL_VERSION,
nullptr,
0,
(LPVOID)&output,
sizeof(output),
&bytesReturned,
nullptr
);
if (!success) {
std::cerr << "DeviceIoControl failed: " << GetLastError() << std::endl;
}
return success;
}
static const bool isProtocolCompatible(const SUVDA_PROTOCAL_VERSION& otherVersion) {
// Changes to existing ioctl must be marked as major
if (VDAProtocolVersion.Major != otherVersion.Major) {
return false;
}
// We shouldn't break compatibility with minor/incremental changes
// e.g. add new ioctl in the driver
// But if our minor version is newer than the driver version, break
if (VDAProtocolVersion.Minor > otherVersion.Minor) {
return false;
}
return true;
};
static const bool CheckProtocolCompatible(HANDLE hDevice) {
VIRTUAL_DISPLAY_GET_PROTOCOL_VERSION_OUT protocolVersion;
if (GetProtocolVersion(hDevice, protocolVersion)) {
return isProtocolCompatible(protocolVersion.Version);
}
return false;
}
static const bool PingDriver(HANDLE hDevice) {
DWORD bytesReturned;
BOOL success = DeviceIoControl(
hDevice,
IOCTL_DRIVER_PING,
nullptr,
0,
nullptr,
0,
&bytesReturned,
nullptr
);
if (!success) {
std::cerr << "DeviceIoControl failed: " << GetLastError() << std::endl;
}
return success;
}
static const bool GetAddedDisplayName(const VIRTUAL_DISPLAY_ADD_OUT& addedDisplay, wchar_t* deviceName) {
// get all paths
UINT pathCount;
UINT modeCount;
if (GetDisplayConfigBufferSizes(QDC_ONLY_ACTIVE_PATHS, &pathCount, &modeCount))
return 0;
std::vector<DISPLAYCONFIG_PATH_INFO> paths(pathCount);
std::vector<DISPLAYCONFIG_MODE_INFO> modes(modeCount);
if (QueryDisplayConfig(QDC_ONLY_ACTIVE_PATHS, &pathCount, paths.data(), &modeCount, modes.data(), nullptr))
return 0;
auto path = std::find_if(paths.begin(), paths.end(), [&addedDisplay](DISPLAYCONFIG_PATH_INFO _path) {
return _path.targetInfo.id == addedDisplay.TargetId;
});
DISPLAYCONFIG_SOURCE_DEVICE_NAME sourceName = {};
sourceName.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME;
sourceName.header.size = sizeof(DISPLAYCONFIG_SOURCE_DEVICE_NAME);
sourceName.header.adapterId = addedDisplay.AdapterLuid;
sourceName.header.id = path->sourceInfo.id;
if (DisplayConfigGetDeviceInfo((DISPLAYCONFIG_DEVICE_INFO_HEADER*)&sourceName)) {
return false;
}
wcscpy_s(deviceName, CCHDEVICENAME, sourceName.viewGdiDeviceName);
return true;
}
#ifdef __cplusplus
} // namespace SUDOVDA
#endif