From 9f4d7c43887df54c7ed7aefc47d8a1e392e5e4a8 Mon Sep 17 00:00:00 2001 From: Nguyen Duy Date: Sat, 30 Dec 2023 16:33:02 +0700 Subject: [PATCH] add core api --- demo.cc | 214 --------------------------- parsec-vdd-demo.cc | 91 ++++++++++++ parsec-vdd.h | 350 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 441 insertions(+), 214 deletions(-) delete mode 100644 demo.cc create mode 100644 parsec-vdd-demo.cc create mode 100644 parsec-vdd.h diff --git a/demo.cc b/demo.cc deleted file mode 100644 index bffe7ec..0000000 --- a/demo.cc +++ /dev/null @@ -1,214 +0,0 @@ -#include -#include -#include -#include -#include -#include - -#pragma comment(lib, "Setupapi.lib") - -BOOLEAN GetDevicePath2( - _In_ LPCGUID InterfaceGuid, - _Out_writes_(BufLen) PTCHAR DevicePath, - _In_ size_t BufLen -) -{ - HANDLE hDevice = INVALID_HANDLE_VALUE; - PSP_DEVICE_INTERFACE_DETAIL_DATA deviceInterfaceDetailData = NULL; - ULONG predictedLength = 0; - ULONG requiredLength = 0; - HDEVINFO hardwareDeviceInfo; - SP_DEVICE_INTERFACE_DATA deviceInterfaceData; - BOOLEAN status = FALSE; - HRESULT hr; - - hardwareDeviceInfo = SetupDiGetClassDevs( - InterfaceGuid, - NULL, // Define no enumerator (global) - NULL, // Define no - (DIGCF_PRESENT | // Only Devices present - DIGCF_DEVICEINTERFACE)); // Function class devices. - if (INVALID_HANDLE_VALUE == hardwareDeviceInfo) - { - printf("Idd device: SetupDiGetClassDevs failed, last error 0x%x\n", GetLastError()); - return FALSE; - } - - deviceInterfaceData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA); - - if (!SetupDiEnumDeviceInterfaces(hardwareDeviceInfo, - 0, // No care about specific PDOs - InterfaceGuid, - 0, // - &deviceInterfaceData)) - { - printf("Idd device: SetupDiEnumDeviceInterfaces failed, last error 0x%x\n", GetLastError()); - goto Clean0; - } - - // - // Allocate a function class device data structure to receive the - // information about this particular device. - // - SetupDiGetDeviceInterfaceDetail( - hardwareDeviceInfo, - &deviceInterfaceData, - NULL, // probing so no output buffer yet - 0, // probing so output buffer length of zero - &requiredLength, - NULL);//not interested in the specific dev-node - - if (ERROR_INSUFFICIENT_BUFFER != GetLastError()) - { - printf("Idd device: SetupDiGetDeviceInterfaceDetail failed, last error 0x%x\n", GetLastError()); - goto Clean0; - } - - predictedLength = requiredLength; - deviceInterfaceDetailData = (PSP_DEVICE_INTERFACE_DETAIL_DATA)HeapAlloc( - GetProcessHeap(), - HEAP_ZERO_MEMORY, - predictedLength - ); - - if (deviceInterfaceDetailData) - { - deviceInterfaceDetailData->cbSize = - sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA); - } - else - { - printf("Idd device: HeapAlloc failed, last error 0x%x\n", GetLastError()); - goto Clean0; - } - - if (!SetupDiGetDeviceInterfaceDetail( - hardwareDeviceInfo, - &deviceInterfaceData, - deviceInterfaceDetailData, - predictedLength, - &requiredLength, - NULL)) - { - printf("Idd device: SetupDiGetDeviceInterfaceDetail failed, last error 0x%x\n", GetLastError()); - goto Clean1; - } - - hr = StringCchCopy(DevicePath, BufLen, deviceInterfaceDetailData->DevicePath); - if (FAILED(hr)) - { - printf("Error: StringCchCopy failed with HRESULT 0x%x", hr); - status = FALSE; - goto Clean1; - } - else - { - status = TRUE; - } - -Clean1: - (VOID)HeapFree(GetProcessHeap(), 0, deviceInterfaceDetailData); -Clean0: - (VOID)SetupDiDestroyDeviceInfoList(hardwareDeviceInfo); - return status; -} - -HANDLE DeviceOpenHandle(const GUID &devGuid) -{ - // const int maxDevPathLen = 256; - TCHAR devicePath[256] = { 0 }; - HANDLE hDevice = INVALID_HANDLE_VALUE; - do - { - if (FALSE == GetDevicePath2( - &devGuid, - devicePath, - sizeof(devicePath) / sizeof(devicePath[0]))) - { - break; - } - if (_tcslen(devicePath) == 0) - { - printf("GetDevicePath got empty device path\n"); - break; - } - - _tprintf(_T("Idd device: try open %s\n"), devicePath); - hDevice = CreateFile( - devicePath, - GENERIC_READ | GENERIC_WRITE, - // FILE_SHARE_READ | FILE_SHARE_WRITE, - 0, - NULL, // no SECURITY_ATTRIBUTES structure - OPEN_EXISTING, // No special create flags - 0, // No special attributes - NULL - ); - if (hDevice == INVALID_HANDLE_VALUE || hDevice == NULL) - { - DWORD error = GetLastError(); - printf("CreateFile failed 0x%lx\n", error); - } - } while (0); - - return hDevice; -} - -enum VddCtlCode -{ - IOCTL_VDD_CONNECT = 0x22A008, - IOCTL_VDD_ADD = 0x22E004, - IOCTL_VDD_UPDATE = 0x22A00C, -}; - -void VddIoCtl(HANDLE vdd, VddCtlCode code) -{ - BYTE InBuffer[32]{}; - int OutBuffer = 0; - OVERLAPPED Overlapped{}; - DWORD NumberOfBytesTransferred; - - Overlapped.hEvent = CreateEventW(NULL, NULL, NULL, NULL); - DeviceIoControl(vdd, code, InBuffer, _countof(InBuffer), &OutBuffer, sizeof(OutBuffer), NULL, &Overlapped); - GetOverlappedResult(vdd, &Overlapped, &NumberOfBytesTransferred, TRUE); - - if (Overlapped.hEvent && Overlapped.hEvent != INVALID_HANDLE_VALUE) - CloseHandle(Overlapped.hEvent); -} - -int main() -{ - const GUID PARSEC_VDD_DEVINTERFACE = \ - { 0x00b41627, 0x04c4, 0x429e, { 0xa2, 0x6e, 0x02, 0x65, 0xcf, 0x50, 0xc8, 0xfa } }; - - // try to get device handle with GUID - HANDLE vdd = DeviceOpenHandle(PARSEC_VDD_DEVINTERFACE); - if (!vdd || vdd == INVALID_HANDLE_VALUE) - { - printf("failed to get ParsecVDD device handle.\n"); - return 1; - } - - // connect & plug in - VddIoCtl(vdd, IOCTL_VDD_CONNECT); - VddIoCtl(vdd, IOCTL_VDD_UPDATE); - VddIoCtl(vdd, IOCTL_VDD_ADD); - VddIoCtl(vdd, IOCTL_VDD_UPDATE); - - // work for 5s - const int kDuration = 5000; - auto startTime = std::chrono::high_resolution_clock::now(); - - while (std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - startTime).count() < kDuration) - { - // update each 100ms - std::this_thread::sleep_for(std::chrono::milliseconds(100)); - VddIoCtl(vdd, IOCTL_VDD_UPDATE); - } - - // disconnect - VddIoCtl(vdd, IOCTL_VDD_CONNECT); - CloseHandle(vdd); - - return 0; -} diff --git a/parsec-vdd-demo.cc b/parsec-vdd-demo.cc new file mode 100644 index 0000000..0629601 --- /dev/null +++ b/parsec-vdd-demo.cc @@ -0,0 +1,91 @@ +#include +#include +#include +#include +#include +#include "parsec-vdd.h" + +using namespace std::chrono_literals; +using namespace parsec_vdd; + +int main() +{ + // Check driver status. + DeviceStatus status = QueryDeviceStatus(&VDD_CLASS_GUID, VDD_HARDWARE_ID); + if (status != DEVICE_OK) + { + printf("Parsec VDD device is not OK, got status %d.\n", status); + return 1; + } + + // Obtain device handle. + HANDLE vdd = OpenDeviceHandle(&VDD_ADAPTER_GUID); + if (vdd == NULL || vdd == INVALID_HANDLE_VALUE) { + printf("Failed to obtain the device handle.\n"); + return 1; + } + + bool running = true; + std::vector displays; + + // Side thread for updating vdd. + std::thread updater([&running, vdd] { + while (running) { + VddUpdate(vdd); + std::this_thread::sleep_for(100ms); + } + }); + + updater.detach(); + + // Print out guide. + printf("Press A to add a virtual display.\n"); + printf("Press R to remove the last added.\n"); + printf("Press Q to quit (then unplug all).\n\n"); + + while (running) { + switch (_getch()) { + // quit + case 'q': + running = false; + break; + // add display + case 'a': + if (displays.size() < VDD_MAX_DISPLAYS) { + int index = VddAddDisplay(vdd); + displays.push_back(index); + printf("Added a new virtual display, index: %d.\n", index); + } + else { + printf("Limit exceeded (%d), could not add more virtual displays.\n", VDD_MAX_DISPLAYS); + } + break; + // remove display + case 'r': + if (displays.size() > 0) { + int index = displays.back(); + VddRemoveDisplay(vdd, index); + displays.pop_back(); + printf("Removed the last virtual display, index: %d.\n", index); + } + else { + printf("No added virtual displays.\n"); + } + break; + } + } + + // Remove all before exiting. + for (int index : displays) { + VddRemoveDisplay(vdd, index); + } + + if (updater.joinable()) { + updater.join(); + } + + // Close the device handle. + CloseDeviceHandle(vdd); + + return 0; +} \ No newline at end of file diff --git a/parsec-vdd.h b/parsec-vdd.h new file mode 100644 index 0000000..5168ecf --- /dev/null +++ b/parsec-vdd.h @@ -0,0 +1,350 @@ +/* + * Copyright (c) 2023, Nguyen Duy All rights reserved. + * GitHub repo: https://github.com/nomi-san/parsec-vdd/ + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef __PARSEC_VDD_H +#define __PARSEC_VDD_H + +#include +#include +#include + +#ifdef _MSC_VER +#pragma comment(lib, "cfgmgr32.lib") +#pragma comment(lib, "setupapi.lib") +#endif + +#ifdef __cplusplus +namespace parsec_vdd +{ +#endif + +// Device helper. +////////////////////////////////////////////////// + +typedef enum { + DEVICE_OK = 0, // Ready to use + DEVICE_INACCESSIBLE, // Inaccessible + DEVICE_UNKNOW, // Unknow status + DEVICE_UNKNOW_PROBLEM, // Unknow problem + DEVICE_DISABLED, // Device is disabled + DEVICE_DRIVER_ERROR, // Device encountered error + DEVICE_RESTART_REQUIRED, // Must restart PC to use (could ignore but would have issue) + DEVICE_DISABLED_SERVICE, // Service is disabled + DEVICE_NOT_INSTALLED // Driver is not installed +} DeviceStatus; + +/** +* Query the driver status. +* +* @param classGuid The GUID of the class. +* @param deviceId The device/hardware ID of the driver. +* @return DeviceStatus +*/ +static DeviceStatus QueryDeviceStatus(const GUID *classGuid, const char *deviceId) +{ + DeviceStatus status = DEVICE_INACCESSIBLE; + + SP_DEVINFO_DATA devInfoData; + ZeroMemory(&devInfoData, sizeof(SP_DEVINFO_DATA)); + devInfoData.cbSize = sizeof(SP_DEVINFO_DATA); + + HDEVINFO devInfo = SetupDiGetClassDevsA(classGuid, NULL, NULL, DIGCF_PRESENT); + + if (devInfo != INVALID_HANDLE_VALUE) + { + BOOL foundProp = FALSE; + UINT deviceIndex = 0; + + do + { + if (!SetupDiEnumDeviceInfo(devInfo, deviceIndex, &devInfoData)) + break; + + DWORD requiredSize = 0; + SetupDiGetDeviceRegistryPropertyA(devInfo, &devInfoData, + SPDRP_HARDWAREID, NULL, NULL, 0, &requiredSize); + + if (requiredSize > 0) + { + DWORD regDataType = 0; + LPBYTE propBuffer = (LPBYTE)calloc(1, requiredSize); + + if (SetupDiGetDeviceRegistryPropertyA( + devInfo, + &devInfoData, + SPDRP_HARDWAREID, + ®DataType, + propBuffer, + requiredSize, + &requiredSize)) + { + if (regDataType == REG_SZ || regDataType == REG_MULTI_SZ) + { + for (LPCSTR cp = (LPCSTR)propBuffer; ; cp += lstrlenA(cp) + 1) + { + if (!cp || *cp == 0 || cp >= (LPCSTR)(propBuffer + requiredSize)) + { + status = DEVICE_NOT_INSTALLED; + goto except; + } + + if (lstrcmpA(deviceId, cp) == 0) + break; + } + + foundProp = TRUE; + ULONG devStatus, devProblemNum; + + if (CM_Get_DevNode_Status(&devStatus, &devProblemNum, devInfoData.DevInst, 0) != CR_SUCCESS) + { + status = DEVICE_NOT_INSTALLED; + goto except; + } + + if ((devStatus & (DN_DRIVER_LOADED | DN_STARTED)) != 0) + { + status = DEVICE_OK; + } + else if ((devStatus & DN_HAS_PROBLEM) != 0) + { + switch (devProblemNum) + { + case CM_PROB_NEED_RESTART: + status = DEVICE_RESTART_REQUIRED; + break; + case CM_PROB_DISABLED: + case CM_PROB_HARDWARE_DISABLED: + status = DEVICE_DISABLED; + break; + case CM_PROB_DISABLED_SERVICE: + status = DEVICE_DISABLED_SERVICE; + break; + default: + if (devProblemNum == CM_PROB_FAILED_POST_START) + status = DEVICE_DRIVER_ERROR; + else + status = DEVICE_UNKNOW_PROBLEM; + break; + } + } + else + { + status = DEVICE_UNKNOW; + } + } + } + + except: + free(propBuffer); + } + + ++deviceIndex; + } while (!foundProp); + + if (!foundProp && GetLastError() != 0) + status = DEVICE_NOT_INSTALLED; + + SetupDiDestroyDeviceInfoList(devInfo); + } + + return status; +} + +/** +* Obtain the device handle. +* Returns NULL or INVALID_HANDLE_VALUE if fails, otherwise a valid handle. +* Should call CloseDeviceHandle to close this handle after use. +* +* @param interfaceGuid The adapter/interface GUID of the target device. +* @return HANDLE +*/ +static HANDLE OpenDeviceHandle(const GUID *interfaceGuid) +{ + HANDLE handle = INVALID_HANDLE_VALUE; + HDEVINFO devInfo = SetupDiGetClassDevsA(interfaceGuid, + NULL, NULL, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE); + + if (devInfo != INVALID_HANDLE_VALUE) + { + SP_DEVICE_INTERFACE_DATA devInterface; + ZeroMemory(&devInterface, sizeof(SP_DEVICE_INTERFACE_DATA)); + devInterface.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA); + + for (DWORD i = 0; SetupDiEnumDeviceInterfaces(devInfo, NULL, interfaceGuid, i, &devInterface); ++i) + { + DWORD detailSize = 0; + SetupDiGetDeviceInterfaceDetailA(devInfo, &devInterface, 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(devInfo, &devInterface, 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(devInfo); + } + + return handle; +} + +/* Release the device handle */ +static void CloseDeviceHandle(HANDLE handle) +{ + if (handle != NULL && handle != INVALID_HANDLE_VALUE) + CloseHandle(handle); +} + +// Parsec VDD core. +////////////////////////////////////////////////// + +// Display name info. +static const char *VDD_DISPLAY_ID = "PSCCDD0"; // You will see it in registry (HKLM\SYSTEM\CurrentControlSet\Enum\DISPLAY) +static const char *VDD_DISPLAY_NAME = "ParsecVDA"; // You will see it in the [Advanced display settings] tab. + +// Apdater GUID to obtain the device handle. +// {00b41627-04c4-429e-a26e-0265cf50c8fa} +static const GUID VDD_ADAPTER_GUID = { 0x00b41627, 0x04c4, 0x429e, { 0xa2, 0x6e, 0x02, 0x65, 0xcf, 0x50, 0xc8, 0xfa } }; +static const char *VDD_ADAPTER_NAME = "Parsec Virtual Display Adapter"; + +// Class and hwid to query device status. +// {4d36e968-e325-11ce-bfc1-08002be10318} +static const GUID VDD_CLASS_GUID = { 0x4d36e968, 0xe325, 0x11ce, { 0xbf, 0xc1, 0x08, 0x00, 0x2b, 0xe1, 0x03, 0x18 } }; +static const char *VDD_HARDWARE_ID = "Root\\Parsec\\VDA"; + +// Actually up to 16 devices could be created per adapter +// so just use a half to avoid plugging lag. +static const int VDD_MAX_DISPLAYS = 8; + +// Core IoControl codes, see usage below. +typedef enum { + VDD_IOCTL_ADD = 0x0022e004, + VDD_IOCTL_REMOVE = 0x0022a008, + VDD_IOCTL_UPDATE = 0x0022a00c, + VDD_IOCTL_VERSION = 0x0022e010, +} VddCtlCode; + +// Generic DeviceIoControl for all IoControl codes. +static DWORD VddIoControl(HANDLE vdd, VddCtlCode code, const void *data, size_t size) +{ + if (vdd == NULL || vdd == INVALID_HANDLE_VALUE) + return 0; + + BYTE InBuffer[32]; + ZeroMemory(InBuffer, sizeof(InBuffer)); + + OVERLAPPED Overlapped; + ZeroMemory(&Overlapped, sizeof(OVERLAPPED)); + + DWORD OutBuffer = 0; + DWORD NumberOfBytesTransferred; + + if (data != NULL && size > 0) + memcpy(InBuffer, data, (size < sizeof(InBuffer)) ? size : sizeof(InBuffer)); + + Overlapped.hEvent = CreateEventA(NULL, FALSE, FALSE, NULL); + DeviceIoControl(vdd, (DWORD)code, InBuffer, sizeof(InBuffer), &OutBuffer, sizeof(DWORD), NULL, &Overlapped); + + GetOverlappedResult(vdd, &Overlapped, &NumberOfBytesTransferred, TRUE); + + if (Overlapped.hEvent != NULL) + CloseHandle(Overlapped.hEvent); + + return OutBuffer; +} + +/** +* Query VDD minor version. +* +* @param vdd The device handle of VDD. +* @return The number of minor version. +*/ +static int VddVersion(HANDLE vdd) +{ + int minor = VddIoControl(vdd, VDD_IOCTL_VERSION, NULL, 0); + return minor; +} + +/** +* Update/ping to VDD. +* Should call this function in a side thread for each +* less than 100ms to keep all added virtual displays alive. +* +* @param vdd The device handle of VDD. +*/ +static void VddUpdate(HANDLE vdd) +{ + VddIoControl(vdd, VDD_IOCTL_UPDATE, NULL, 0); +} + +/** +* Add/plug a virtual display. +* +* @param vdd The device handle of VDD. +* @return The index of the added display. +*/ +static int VddAddDisplay(HANDLE vdd) +{ + int idx = VddIoControl(vdd, VDD_IOCTL_ADD, NULL, 0); + VddUpdate(vdd); + + return idx; +} + +/** +* Remove/unplug a virtual display. +* +* @param vdd The device handle of VDD. +* @param index The index of the display will be removed. +*/ +static void VddRemoveDisplay(HANDLE vdd, int index) +{ + // 16-bit BE index + UINT16 indexData = ((index & 0xFF) << 8) | ((index >> 8) & 0xFF); + + VddIoControl(vdd, VDD_IOCTL_REMOVE, &indexData, sizeof(indexData)); + VddUpdate(vdd); +} + +#ifdef __cplusplus +} +#endif + +#endif \ No newline at end of file