diff --git a/cmake/compile_definitions/windows.cmake b/cmake/compile_definitions/windows.cmake index 6571eae5..b3ba5bbb 100644 --- a/cmake/compile_definitions/windows.cmake +++ b/cmake/compile_definitions/windows.cmake @@ -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" diff --git a/src/confighttp.cpp b/src/confighttp.cpp index a83ae764..15c13f66 100644 --- a/src/confighttp.cpp +++ b/src/confighttp.cpp @@ -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())); diff --git a/src/misc.h b/src/misc.h deleted file mode 100644 index c858fa62..00000000 --- a/src/misc.h +++ /dev/null @@ -1,32 +0,0 @@ -#pragma once -#include - -#ifdef _WIN32 -#include - -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 \ No newline at end of file diff --git a/src/nvhttp.cpp b/src/nvhttp.cpp index d4569a1d..b9e4fe9e 100644 --- a/src/nvhttp.cpp +++ b/src/nvhttp.cpp @@ -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) { diff --git a/src/platform/windows/utils.h b/src/platform/windows/utils.h new file mode 100644 index 00000000..eeb5ff27 --- /dev/null +++ b/src/platform/windows/utils.h @@ -0,0 +1,29 @@ +#pragma once + +#include +#include + +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; +} \ No newline at end of file diff --git a/src/platform/windows/virtual_display.cpp b/src/platform/windows/virtual_display.cpp new file mode 100644 index 00000000..033e5cd6 --- /dev/null +++ b/src/platform/windows/virtual_display.cpp @@ -0,0 +1,199 @@ +#include +#include +#include +#include +#include +#include +#include +#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; + } +} +} \ No newline at end of file diff --git a/src/platform/windows/virtual_display.h b/src/platform/windows/virtual_display.h new file mode 100644 index 00000000..ba0cec84 --- /dev/null +++ b/src/platform/windows/virtual_display.h @@ -0,0 +1,30 @@ +#pragma once + +#ifndef FILE_DEVICE_UNKNOWN +#define FILE_DEVICE_UNKNOWN 0x00000022 +#endif + +#include +#include +#include + +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); +} diff --git a/src/process.cpp b/src/process.cpp index 879fd158..b0cd448a 100644 --- a/src/process.cpp +++ b/src/process.cpp @@ -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 @@ -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 init() { + #ifdef _WIN32 + vdisplayDriverInitialized = VDISPLAY::openVDisplayDevice(); + #endif return std::make_unique(); } @@ -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 & diff --git a/src/process.h b/src/process.h index 85bbfa3e..fa0d8462 100644 --- a/src/process.h +++ b/src/process.h @@ -21,6 +21,10 @@ namespace proc { using file_t = util::safe_ptr_v2; +#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 _launch_session; + boost::process::environment _env; std::vector _apps; ctx_t _app; diff --git a/src/rtsp.h b/src/rtsp.h index 9b43f7d7..deeee5e1 100644 --- a/src/rtsp.h +++ b/src/rtsp.h @@ -33,10 +33,15 @@ namespace rtsp_stream { std::string surround_params; bool enable_hdr; bool enable_sops; + bool virtual_display; std::optional rtsp_cipher; std::string rtsp_url_scheme; uint32_t rtsp_iv_counter; + + #ifdef _WIN32 + GUID display_guid{}; + #endif }; void diff --git a/src/system_tray.cpp b/src/system_tray.cpp index 2845baba..29f2c66e 100644 --- a/src/system_tray.cpp +++ b/src/system_tray.cpp @@ -9,6 +9,7 @@ #define WIN32_LEAN_AND_MEAN #include #include + #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; diff --git a/third-party/sudovda/sudovda-ioctl.h b/third-party/sudovda/sudovda-ioctl.h new file mode 100644 index 00000000..4c69d01f --- /dev/null +++ b/third-party/sudovda/sudovda-ioctl.h @@ -0,0 +1,68 @@ +#pragma once + +#include + +#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 \ No newline at end of file diff --git a/third-party/sudovda/sudovda.h b/third-party/sudovda/sudovda.h new file mode 100644 index 00000000..db1e2266 --- /dev/null +++ b/third-party/sudovda/sudovda.h @@ -0,0 +1,247 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#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)¶ms, + 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)¶ms, + 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)¶ms, + 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 paths(pathCount); + std::vector 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 \ No newline at end of file