mirror of
https://github.com/nomi-san/parsec-vdd.git
synced 2026-08-07 05:20:44 +00:00
chore(docs): update readme and docs, add RE doc
This commit is contained in:
@@ -42,32 +42,30 @@ for an enhanced, flexible visual experience.
|
|||||||
|
|
||||||
## 📺 ParsecDisplay App
|
## 📺 ParsecDisplay App
|
||||||
|
|
||||||
ParsecDisplay is a comprehensive virtual display manager for Parsec VDD, built
|
ParsecDisplay is a virtual display manager for Parsec VDD, built with C# and
|
||||||
with C# and WPF. The app provides an intuitive interface to manage virtual
|
WPF. It provides a tray-based interface to add and remove virtual displays,
|
||||||
displays, showing the number of active displays and allowing users to add or
|
change their resolution, refresh rate, and orientation, capture screenshots,
|
||||||
remove specific virtual displays. It also supports features like changing
|
and more.
|
||||||
display resolution, capturing screenshots, and more, making it a versatile tool
|
|
||||||
for flexible display management.
|
|
||||||
|
|
||||||
👉 Check out [Releases](https://github.com/nomi-san/parsec-vdd/releases) to
|
👉 Check out [Releases](https://github.com/nomi-san/parsec-vdd/releases) to
|
||||||
download it.
|
download it.
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<img src="https://github.com/nomi-san/parsec-vdd/assets/38210249/71b25bc6-eee1-4d80-94e0-e39eab7f8fb9" />
|
<img src="https://github.com/user-attachments/assets/2c014dbb-2358-4906-90fb-f94a62087065" />
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
## 🚀 Using Core API
|
## 🚀 Using Core API
|
||||||
|
|
||||||
### Design notes
|
### Design notes
|
||||||
|
|
||||||
Parsec VDD is designed to work with Parsec client-connection sessions. When the
|
Parsec VDD is designed to work with Parsec client-connection sessions. When
|
||||||
user connects to the host, the app will start controlling the driver, it sends
|
the user connects to the host, the app starts controlling the driver —
|
||||||
IO control codes and gets results. When adding a virtual display, you will get
|
sending IO control codes and receiving results. Adding a virtual display
|
||||||
its index to be used for unplugging, the maximum number of displays could be
|
returns an index, used later to unplug it; up to 16 displays can be added
|
||||||
added up to 16 per adapter. You have to ping the driver periodically to keep
|
per adapter. The driver must be pinged periodically to keep added displays
|
||||||
added displays alive, otherwise all of them will be unplugged after a second.
|
alive, otherwise all of them will be unplugged after about a second. There
|
||||||
There's no direct way to manipulate added displays, you should call Win32
|
is no direct way to manipulate added displays — call the Win32 Display API
|
||||||
Display API to change their display mode (see the ParsecDisplay source).
|
to change their display mode (see the ParsecDisplay source).
|
||||||
|
|
||||||
```mermaid
|
```mermaid
|
||||||
flowchart LR
|
flowchart LR
|
||||||
@@ -88,13 +86,13 @@ flowchart LR
|
|||||||
|
|
||||||
For detailed instructions and usage examples, refer to the [VDD_LIBRARY_USAGE](./docs/VDD_LIBRARY_USAGE.md).
|
For detailed instructions and usage examples, refer to the [VDD_LIBRARY_USAGE](./docs/VDD_LIBRARY_USAGE.md).
|
||||||
|
|
||||||
- The core API is designed as single C/C++ header that can be added to any
|
- The core API is designed as a single C/C++ header that can be added to any
|
||||||
project, 👉 [core/parsec-vdd.h](./core/parsec-vdd.h)
|
project, 👉 [core/parsec-vdd.h](./core/parsec-vdd.h)
|
||||||
- There is also a simple demo program, 👉 [core/vdd-demo.cc](./core/vdd-demo.cc)
|
- There is also a simple demo program, 👉 [core/vdd-demo.cc](./core/vdd-demo.cc)
|
||||||
|
|
||||||
### Picking a driver
|
### Picking a driver
|
||||||
|
|
||||||
You have to install the driver to make them work.
|
You have to install the driver before any virtual displays can be created.
|
||||||
|
|
||||||
| Version | Minimum OS | IddCx | Notes |
|
| Version | Minimum OS | IddCx | Notes |
|
||||||
| :---------------- | :-------------- | :---: | :-------------------------------------------------------- |
|
| :---------------- | :-------------- | :---: | :-------------------------------------------------------- |
|
||||||
@@ -141,9 +139,10 @@ In addition, you can run the driver setup in silent mode to install it quickly.
|
|||||||
### 1. HDR support
|
### 1. HDR support
|
||||||
|
|
||||||
Parsec VDD does not support HDR on its displays (see the EDID below).
|
Parsec VDD does not support HDR on its displays (see the EDID below).
|
||||||
Theoretically, you can unlock support by editing the EDID, then adding HDR
|
Theoretically, you can unlock support by editing the EDID to include HDR
|
||||||
metadata and setting 10-bit+ color depth. Unfortunately, you cannot flash its
|
metadata and a 10-bit+ color depth. Unfortunately, you cannot flash its
|
||||||
firmware like a physical device, or modify the registry value.
|
firmware the way you would a physical monitor — there is no registry
|
||||||
|
setting to toggle either.
|
||||||
|
|
||||||
All IDDs have their own fixed EDID block inside the driver binary to initialize
|
All IDDs have their own fixed EDID block inside the driver binary to initialize
|
||||||
the monitor specs. So the solution is to modify this block in the driver DLL
|
the monitor specs. So the solution is to modify this block in the driver DLL
|
||||||
@@ -152,8 +151,8 @@ the monitor specs. So the solution is to modify this block in the driver DLL
|
|||||||
### 2. Custom resolutions
|
### 2. Custom resolutions
|
||||||
|
|
||||||
Before connecting, the virtual display looks in the `HKLM\SOFTWARE\Parsec\vdd`
|
Before connecting, the virtual display looks in the `HKLM\SOFTWARE\Parsec\vdd`
|
||||||
registry for additional preset resolutions. Currently this supports a maximum of
|
registry for additional preset resolutions. Currently this supports a maximum
|
||||||
5 values.
|
of 5 entries.
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
HKLM\SOFTWARE\Parsec\vdd:
|
HKLM\SOFTWARE\Parsec\vdd:
|
||||||
@@ -180,10 +179,32 @@ HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\GraphicsDrivers\Connectivity
|
|||||||
```
|
```
|
||||||
|
|
||||||
This option causes your main display to turn off when virtual displays are
|
This option causes your main display to turn off when virtual displays are
|
||||||
added, making it difficult to turn the display on and disrupting the remote
|
added, making it difficult to turn the main display back on and disrupting
|
||||||
desktop session.
|
the remote desktop session.
|
||||||
|
|
||||||
### 2. // todo
|
### 2. Windows 10 Connectivity registry quirk
|
||||||
|
|
||||||
|
Windows 10 caches display arrangements keyed by the _combination_ of attached
|
||||||
|
display IDs. When a middle display is unplugged, the remaining subset
|
||||||
|
(e.g. `DISP001_DISP003`) is a new combo Windows hasn't seen — those displays
|
||||||
|
fall back to default mode and arrangement.
|
||||||
|
|
||||||
|
The app works around this by always unplugging **right-to-left** (latest
|
||||||
|
driver index first) during sleep, exit, and `vdd remove all`. See
|
||||||
|
[issue #23](https://github.com/nomi-san/parsec-vdd/issues/23)
|
||||||
|
for the full write-up.
|
||||||
|
|
||||||
|
### 3. Headless before user login
|
||||||
|
|
||||||
|
The app is a GUI process and requires an interactive user session (Vista+
|
||||||
|
session 0 isolation). On a freshly-booted headless host with no auto-login,
|
||||||
|
nothing runs until the user signs in. Workarounds:
|
||||||
|
|
||||||
|
- Enable auto-login on the host.
|
||||||
|
- Or use a Task Scheduler entry that runs at logon with desktop interaction.
|
||||||
|
- Or use the service-based fork
|
||||||
|
[ParsecVDA-Always-Connected](https://github.com/timminator/ParsecVDA-Always-Connected)
|
||||||
|
for fully headless single-display deployments.
|
||||||
|
|
||||||
## 🤔 Comparison with other IDDs
|
## 🤔 Comparison with other IDDs
|
||||||
|
|
||||||
@@ -207,8 +228,8 @@ projects.
|
|||||||
[virtual-display-rs]: https://github.com/MolotovCherry/virtual-display-rs
|
[virtual-display-rs]: https://github.com/MolotovCherry/virtual-display-rs
|
||||||
[Virtual-Display-Driver (HDR)]: https://github.com/itsmikethetech/Virtual-Display-Driver
|
[Virtual-Display-Driver (HDR)]: https://github.com/itsmikethetech/Virtual-Display-Driver
|
||||||
|
|
||||||
**Signed** means that the driver files have a valid digital signature.
|
**Signed** means the driver files have a valid digital signature.
|
||||||
**H-Cursor** means hardware cursor support, without it, you will get a double
|
**H-Cursor** means hardware-cursor support — without it, you get a double
|
||||||
cursor on some remote desktop apps. **Tweakable** is the ability to customize
|
cursor on some remote desktop apps. **Tweakable** is the ability to customize
|
||||||
display modes. Visit
|
display modes. Visit
|
||||||
[MSDN IddCx versions](https://learn.microsoft.com/en-us/windows-hardware/drivers/display/iddcx-versions)
|
[MSDN IddCx versions](https://learn.microsoft.com/en-us/windows-hardware/drivers/display/iddcx-versions)
|
||||||
@@ -228,19 +249,17 @@ Common preset display modes:
|
|||||||
| 1600 x 900 | HD+ | 16:9 | 60/144/240 |
|
| 1600 x 900 | HD+ | 16:9 | 60/144/240 |
|
||||||
| 1280 x 720 | HD | 16:9 | 60/144/240 |
|
| 1280 x 720 | HD | 16:9 | 60/144/240 |
|
||||||
|
|
||||||
Check out [docs/PARSEC_VDD_SPECS](./docs/PARSEC_VDD_SPECS.md) to see full of
|
Check out [docs/PARSEC_VDD_SPECS](./docs/PARSEC_VDD_SPECS.md) for the full
|
||||||
preset display modes the driver specs.
|
list of preset display modes and driver specs.
|
||||||
|
|
||||||
## 🤝 Sponsors
|
|
||||||
|
|
||||||
<table>
|
|
||||||
<tr>
|
|
||||||
<td><img src="https://github.com/user-attachments/assets/58e9a6f4-6630-437d-a758-b284c0ed41e7" /></td>
|
|
||||||
<td>Free code signing on Windows provided by <a href="https://signpath.io">SignPath.io</a>, certificate by <a href="https://signpath.org">SignPath Foundation</a></td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
## 🍻 Credits
|
## 🍻 Credits
|
||||||
|
|
||||||
- Thanks to Parsec for the driver
|
- Thanks to Parsec for the driver
|
||||||
- The app's background was from old parsecgaming.com
|
- The app's background was from old parsecgaming.com
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<td><img src="https://github.com/user-attachments/assets/58e9a6f4-6630-437d-a758-b284c0ed41e7" /></td>
|
||||||
|
<td>Trusted code signing on Windows provided by <a href="https://signpath.io">SignPath.io</a>, certificate by <a href="https://signpath.org">SignPath Foundation</a></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|||||||
@@ -0,0 +1,300 @@
|
|||||||
|
# Parsec VDD — Reverse-Engineered Reference
|
||||||
|
|
||||||
|
Protocol-level documentation of the Parsec Virtual Display Driver as exercised
|
||||||
|
by Parsec's host application. This focuses on what the kernel-mode driver
|
||||||
|
expects from user-mode callers: device discovery, IOCTL formats, lifecycle,
|
||||||
|
and timing. It's intended as a reference for re-implementations (CLI tools,
|
||||||
|
managers, third-party hosts).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Architecture
|
||||||
|
|
||||||
|
Parsec VDD is an Indirect Display Driver (IDD) miniport that creates virtual
|
||||||
|
monitors on Windows 10+. Each running instance is identified by:
|
||||||
|
|
||||||
|
- **Hardware ID:** `Root\Parsec\VDA`
|
||||||
|
- **Setup class GUID:** `{4d36e968-e325-11ce-bfc1-08002be10318}` (Display)
|
||||||
|
- **Device interface GUID:** `{00b41627-04c4-429e-a26e-0265cf50c8fa}`
|
||||||
|
|
||||||
|
A host creates a software device with `SwDeviceCreate`, then communicates
|
||||||
|
through the device interface GUID via `DeviceIoControl`.
|
||||||
|
|
||||||
|
**Key constraints**
|
||||||
|
|
||||||
|
- Maximum **16 virtual monitors** per adapter (driver-side tracking is a
|
||||||
|
`DWORD[16]`, with `-1` marking empty slots).
|
||||||
|
- Maximum resolution clamped to **8192 × 4320** by the host.
|
||||||
|
- Default refresh rate is **60 Hz** when not specified.
|
||||||
|
- Custom preset resolutions live in the registry — up to 5 slots.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Device Lifecycle
|
||||||
|
|
||||||
|
```
|
||||||
|
MM_adapter_create (SwDeviceCreate)
|
||||||
|
↓
|
||||||
|
MM_open_device (SetupDiGetClassDevs + CreateFileW)
|
||||||
|
↓
|
||||||
|
MM_connect (opens handle, spawns keep-alive thread)
|
||||||
|
↓
|
||||||
|
MM_device_add (IOCTL 0x22E004) × N monitors
|
||||||
|
↓
|
||||||
|
MM_keepalive_thread (IOCTL 0x22A00C every ~200 ms — keeps displays alive)
|
||||||
|
↓
|
||||||
|
host_vdd_privacy_thread (optional — blanks physical displays)
|
||||||
|
↓ [on disconnect]
|
||||||
|
MM_disconnect (IOCTL 0x22A008 × 16, close handle)
|
||||||
|
```
|
||||||
|
|
||||||
|
The device handle is opened **asynchronously** (`FILE_FLAG_OVERLAPPED`); every
|
||||||
|
IOCTL is issued with an `OVERLAPPED` structure and awaited via
|
||||||
|
`GetOverlappedResultEx`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. IOCTL Reference
|
||||||
|
|
||||||
|
All four IOCTLs use a **32-byte (`0x20`) zero-initialized input buffer**.
|
||||||
|
|
||||||
|
| IOCTL | Hex | Function | Direction | In | Out | Timeout | Purpose |
|
||||||
|
|-------|-----|----------|-----------|----|-----|---------|---------|
|
||||||
|
| `CTL_CODE(0x22, 0x802, 0, 0)` | `0x22E004` | `MM_device_add` | In/Out | 32 B | 4 B | 5000 ms | Create a virtual monitor. Output is the assigned monitor index (0–15). |
|
||||||
|
| `CTL_CODE(0x22, 0x802, 2, 0)` | `0x22A008` | `MM_disconnect` | In | 32 B | 0 | 1000 ms | Remove a single monitor. `InBuffer[1..4]` = monitor index. |
|
||||||
|
| `CTL_CODE(0x22, 0x803, 2, 0)` | `0x22A00C` | `MM_update_keepalive` | In | 32 B | 0 | 1000 ms | Watchdog ping. Must be sent every ~200 ms or the driver removes **all** monitors after ~1 s. |
|
||||||
|
| `CTL_CODE(0x22, 0x804, 0, 0)` | `0x22E010` | `MM_query_adapter_status` | In/Out | 32 B | 4 B | 1000 ms | Query the adapter. Output is a 4-byte status word (typically the driver minor version). |
|
||||||
|
|
||||||
|
### Buffer layout
|
||||||
|
|
||||||
|
```c
|
||||||
|
struct VDD_IOCTL_Input {
|
||||||
|
uint8_t reserved; // [0]
|
||||||
|
uint32_t monitor_index; // [1..4] — for remove/create
|
||||||
|
uint8_t padding[27]; // [5..31]
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### IoControl pattern (important)
|
||||||
|
|
||||||
|
The driver can return `FALSE` from `DeviceIoControl` with a non-`ERROR_IO_PENDING`
|
||||||
|
status code **while still queuing** the operation. The robust pattern,
|
||||||
|
mirrored from the reference C library:
|
||||||
|
|
||||||
|
1. Call `DeviceIoControl` — **ignore its synchronous return**.
|
||||||
|
2. Call `GetOverlappedResultEx(handle, &ov, ..., timeout, FALSE)`.
|
||||||
|
3. If the wait fails (timeout / error), call `CancelIoEx` followed by a
|
||||||
|
**blocking** `GetOverlappedResult(..., bWait = TRUE)` before returning.
|
||||||
|
|
||||||
|
Skipping step 3 lets the kernel write into the (now-defunct) stack frame
|
||||||
|
that held the `OVERLAPPED` / output buffer, which AVs the next caller on the
|
||||||
|
same thread.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Adapter Creation — `MM_adapter_create`
|
||||||
|
|
||||||
|
Registers a software device at runtime via `SwDeviceCreate`. Waits on a
|
||||||
|
creation-complete callback (signaled by `WaitForSingleObject`). Once
|
||||||
|
registered, the device interface is enumerated with `SetupDiGetClassDevs` and
|
||||||
|
opened via `CreateFileW` using `FILE_FLAG_OVERLAPPED | FILE_FLAG_WRITE_THROUGH
|
||||||
|
| FILE_FLAG_NO_BUFFERING`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Monitor Create — `MM_device_add`
|
||||||
|
|
||||||
|
```c
|
||||||
|
// Find the first empty slot (value == -1) in the 16-entry array
|
||||||
|
for (i = 0; i < 16; i++) {
|
||||||
|
if (context->monitors[i] == -1) break;
|
||||||
|
}
|
||||||
|
// Send IOCTL 0x22E004
|
||||||
|
DeviceIoControl(hDevice, 0x22E004, in_buf, 0x20, &out_index, 4, ...);
|
||||||
|
// Wait via GetOverlappedResultEx (timeout 5000 ms)
|
||||||
|
// out_index must be < 16; stored in context->monitors[i]
|
||||||
|
```
|
||||||
|
|
||||||
|
The returned monitor index becomes the **driver index** for that virtual
|
||||||
|
display. The UID seen later in monitor device-instance paths is
|
||||||
|
`0x100 + driver_index` (used to map back to a specific virtual monitor when
|
||||||
|
walking `EnumDisplayDevices` results).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Keep-Alive Watchdog — `MM_keepalive_thread`
|
||||||
|
|
||||||
|
Spawned by `MM_connect`. Loops continuously sending `IOCTL_UPDATE` at
|
||||||
|
~200 ms cadence:
|
||||||
|
|
||||||
|
```c
|
||||||
|
while (*alive_flag) {
|
||||||
|
Sleep(100); // 100 ms granularity
|
||||||
|
QueryPerformanceCounter(&now);
|
||||||
|
QueryPerformanceFrequency(&freq);
|
||||||
|
elapsed_ms = (now - last) / (freq / 1000.0);
|
||||||
|
if (elapsed_ms > 200.0) {
|
||||||
|
MM_update_keepalive(context); // IOCTL 0x22A00C
|
||||||
|
last = now;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Critical behavior.** If pings stop, the driver removes **all** virtual
|
||||||
|
monitors after roughly 1 second. This is its built-in watchdog for cleaning
|
||||||
|
up after host processes that crashed without explicit disconnect. Any
|
||||||
|
re-implementation must keep up the cadence (a dedicated thread or 100 ms
|
||||||
|
timer is the standard pattern).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Display Enumeration — `MM_enumerate_displays`
|
||||||
|
|
||||||
|
Walks all displays using the Windows configuration-manager APIs:
|
||||||
|
|
||||||
|
1. `EnumDisplayDevicesW(NULL, i, ...)` — outer loop over adapters.
|
||||||
|
2. `EnumDisplayDevicesW(adapter, j, EDD_GET_DEVICE_INTERFACE_NAME)` — inner loop over monitors on that adapter.
|
||||||
|
3. `CM_Get_Device_Interface_PropertyW` → device node.
|
||||||
|
4. `CM_Locate_DevNodeW` + `CM_Get_DevNode_PropertyW` → hardware IDs.
|
||||||
|
|
||||||
|
Each entry is **836 bytes (209 DWORDs)**:
|
||||||
|
|
||||||
|
```
|
||||||
|
+0x004 adapter device ID (128 wchars)
|
||||||
|
+0x108 display name (32 wchars)
|
||||||
|
+0x148 monitor device ID (128 wchars)
|
||||||
|
+0x248 interface path (128 wchars)
|
||||||
|
Flags at DWORD offsets 4/5/6/7:
|
||||||
|
[4] = active, [5] = primary, [6] = removable, [7] = is-parsec
|
||||||
|
```
|
||||||
|
|
||||||
|
The `is-parsec` flag is determined by matching the hardware ID against
|
||||||
|
`Root\Parsec\VDA` (with case-insensitive substring search).
|
||||||
|
|
||||||
|
> **Note for RDP / cloud-server hosts:** `EnumDisplayDevicesW` is bound to the
|
||||||
|
> calling thread's window station — it cannot see console-session displays
|
||||||
|
> from inside an RDP session. A supplemental `SetupDi` enumeration of the
|
||||||
|
> MONITOR class (GUID `{4d36e96e-e325-11ce-bfc1-08002be10318}`) walks the
|
||||||
|
> kernel device tree directly and is session-independent.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Custom Resolutions
|
||||||
|
|
||||||
|
Up to **5 custom resolutions** can be added at slot indices 0–4. The driver
|
||||||
|
reads them at adapter init from:
|
||||||
|
|
||||||
|
```
|
||||||
|
HKLM\SOFTWARE\Parsec\vdd\<slot>
|
||||||
|
w (REG_DWORD) width
|
||||||
|
h (REG_DWORD) height
|
||||||
|
hz (REG_DWORD) refresh rate
|
||||||
|
```
|
||||||
|
|
||||||
|
Written via:
|
||||||
|
|
||||||
|
```c
|
||||||
|
RegCreateKeyExW(hkey, "SOFTWARE\\Parsec\\vdd\\<slot>", ..., KEY_READ | KEY_WRITE, ...);
|
||||||
|
RegSetValueExW(hkey, "w", REG_DWORD, &width, 4);
|
||||||
|
RegSetValueExW(hkey, "h", REG_DWORD, &height, 4);
|
||||||
|
RegSetValueExW(hkey, "hz", REG_DWORD, &refrate, 4);
|
||||||
|
```
|
||||||
|
|
||||||
|
To raise the 5-slot limit you'd have to patch the driver DLL itself
|
||||||
|
(`mm.dll`) — 5 is fixed in the driver-side parsing.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. Privacy Mode — `host_vdd_privacy_thread`
|
||||||
|
|
||||||
|
Optional dedicated thread polling every 30 ms while the host wants to hide
|
||||||
|
physical displays from a connected client:
|
||||||
|
|
||||||
|
- `WTSQuerySessionInformationW` — check active session state.
|
||||||
|
- `EnumWindows` — detect whether any windows are still on real displays.
|
||||||
|
- `SetDisplayConfig(flags=0x44)` — `SDC_TOPOLOGY_INTERNAL`: blank/disable real displays.
|
||||||
|
- `SetDisplayConfig(flags=0x84)` — `SDC_TOPOLOGY_EXTEND`: restore real displays on disconnect.
|
||||||
|
- `MM_query_adapter_status` — poll for VDD adapter health.
|
||||||
|
|
||||||
|
When combined with `IDD_DISPLAY` only mode, this gives Parsec's "Privacy
|
||||||
|
Mode" — physical screens go dark while a virtual display drives the remote
|
||||||
|
client. **Note:** this conflicts with standalone managers like ParsecDisplay;
|
||||||
|
running both will fight over the topology. The README documents the
|
||||||
|
workaround (clear `HKLM\SYSTEM\CurrentControlSet\Control\GraphicsDrivers\Connectivity`).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. Device Status Codes — `host_vdd_init`
|
||||||
|
|
||||||
|
Checks the device at `Root\Parsec\VDA` up to 4 times during startup.
|
||||||
|
Maps `CM_Get_DevNode_Status` flags to a numeric status:
|
||||||
|
|
||||||
|
| Status | Meaning |
|
||||||
|
|:------:|---------|
|
||||||
|
| 0 | UNKNOWN |
|
||||||
|
| 2 | OK |
|
||||||
|
| 3 | UNKNOWN PROBLEM |
|
||||||
|
| 4 | DISABLED |
|
||||||
|
| 5 | DRIVER ERROR |
|
||||||
|
| 6 | RESTART REQUIRED |
|
||||||
|
| 7 | DISABLED SERVICE |
|
||||||
|
| other | NOT INSTALLED |
|
||||||
|
|
||||||
|
These are surfaced to the user as informative dialogs in Parsec's host UI
|
||||||
|
("driver disabled — re-enable in Device Manager", "restart required after
|
||||||
|
install", etc.).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 11. Adapter Recovery — `host_vdd_adapter_monitor`
|
||||||
|
|
||||||
|
A separate monitoring thread that handles driver disable/re-enable cycles
|
||||||
|
(e.g. the user updates the driver in Device Manager):
|
||||||
|
|
||||||
|
- Polls every 100 ms (idle) or 1000 ms (active).
|
||||||
|
- Re-attempts `MM_adapter_create` up to **3 times** when the device disappears.
|
||||||
|
- Bails out after **5 faults within 5 seconds** to avoid CPU spin on a
|
||||||
|
permanently broken driver.
|
||||||
|
- Adapter is dynamically re-created when the driver returns to a healthy state.
|
||||||
|
|
||||||
|
This is what makes the Parsec host survive a driver upgrade without a
|
||||||
|
restart — and the same pattern is mirrored in ParsecDisplay's
|
||||||
|
`Vdd.Controller.StatusThread`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 12. Session Startup Ordering
|
||||||
|
|
||||||
|
The order in which a Parsec host initializes display-related machinery at
|
||||||
|
session start (excluding VUSB / gamepad / microphone code paths, which live
|
||||||
|
outside this driver):
|
||||||
|
|
||||||
|
1. **Custom resolutions** — `vdd_register_custom_resolution` × 3 (indices 2–4).
|
||||||
|
2. **Virtual displays** — `host_vdd_init` opens the device, spawns
|
||||||
|
keep-alive + privacy threads, adds N monitors.
|
||||||
|
3. **Resolution clamping** — capped at 8192 × 4320 (configurable via session params).
|
||||||
|
4. **Screen blanking** — `PostMessageW(HWND_BROADCAST, WM_SYSCOMMAND, SC_MONITORPOWER, -1)`.
|
||||||
|
5. **Host video thread** — creates the capture queue + encoding thread.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Function map
|
||||||
|
|
||||||
|
Behavioral labels used throughout this document. Names are conventional —
|
||||||
|
choose any that suit your re-implementation.
|
||||||
|
|
||||||
|
| Function | Purpose |
|
||||||
|
|----------|---------|
|
||||||
|
| `MM_adapter_create` | `SwDeviceCreate` registration of the IDD adapter. |
|
||||||
|
| `MM_open_device` | `SetupDiGetClassDevs` + `CreateFileW` to obtain the device handle. |
|
||||||
|
| `MM_connect` | High-level init: open device, spawn keep-alive thread. |
|
||||||
|
| `MM_disconnect` | Tear down: remove all monitors, close handle. |
|
||||||
|
| `MM_device_add` | `IOCTL 0x22E004` — create a virtual monitor. |
|
||||||
|
| `MM_query_adapter_status` | `IOCTL 0x22E010` — adapter health / version. |
|
||||||
|
| `MM_update_keepalive` | `IOCTL 0x22A00C` — watchdog ping. |
|
||||||
|
| `MM_keepalive_thread` | Loop sending keep-alive at ~200 ms cadence. |
|
||||||
|
| `MM_enumerate_displays` | Walk all displays via `EnumDisplayDevices` + CM APIs. |
|
||||||
|
| `vdd_register_custom_resolution` | Write a `(w, h, hz)` triple into the registry slot. |
|
||||||
|
| `host_vdd_init` | Driver-status check + open handle + add initial monitors. |
|
||||||
|
| `host_vdd_privacy_thread` | Optional: hide physical displays while a session is active. |
|
||||||
|
| `host_vdd_adapter_monitor` | Survive driver disable/re-enable cycles. |
|
||||||
+16
-4
@@ -42,12 +42,18 @@ Use command `-a` or `add` to add a virtual display.
|
|||||||
vdd -a
|
vdd -a
|
||||||
```
|
```
|
||||||
|
|
||||||
The exit code is the index of added display, you can reuse this index to remove
|
Output looks like:
|
||||||
the display. Less than 0 means error occurred.
|
|
||||||
|
```
|
||||||
|
Added a virtual display with index 0.
|
||||||
|
```
|
||||||
|
|
||||||
|
The exit code is the driver index of the added display (0–15); you can reuse
|
||||||
|
it to remove the display later. A negative exit code means an error occurred.
|
||||||
|
|
||||||
### Removing virtual display
|
### Removing virtual display
|
||||||
|
|
||||||
Use command `-r` or `remove` to remove the last added.
|
Use command `-r` or `remove` to remove the last-added display.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
vdd -r
|
vdd -r
|
||||||
@@ -59,12 +65,18 @@ Remove the added virtual display at index `0`.
|
|||||||
vdd -r 0
|
vdd -r 0
|
||||||
```
|
```
|
||||||
|
|
||||||
To remove all the added, replace the index with `all` or `*`.
|
To remove all added displays, replace the index with `all` or `*`.
|
||||||
|
|
||||||
```
|
```
|
||||||
vdd -r all
|
vdd -r all
|
||||||
```
|
```
|
||||||
|
|
||||||
|
`remove all` iterates in **reverse driver-index order** so the Windows 10
|
||||||
|
Connectivity registry doesn't invent a new (default-mode) configuration for
|
||||||
|
the surviving subset. Per-display failures are reported as warnings and the
|
||||||
|
loop continues; the exit code is `0` on full success and `1` if any
|
||||||
|
individual removal failed.
|
||||||
|
|
||||||
### Listing added displays
|
### Listing added displays
|
||||||
|
|
||||||
List all added virtual displays.
|
List all added virtual displays.
|
||||||
|
|||||||
+27
-14
@@ -1,6 +1,10 @@
|
|||||||
# C/C++ API Usage
|
# C/C++ API Usage
|
||||||
|
|
||||||
This document describes how to use the Parsec Virtual Display Driver (VDD) C/C++ API, as defined in `core/parsec-vdd.h`. For full project details, see the [README](../README.md) and [PARSEC_VDD_SPECS.md](./PARSEC_VDD_SPECS.md). For code example, see `core/vdd-demo.cc`.
|
This document describes the legacy single-header C/C++ API at
|
||||||
|
[`core/parsec-vdd.h`](../core/parsec-vdd.h). It's intentionally minimal — just
|
||||||
|
enough to add/remove monitors and keep them alive. For project context, see
|
||||||
|
the [README](../README.md). For supported display modes, see
|
||||||
|
[PARSEC_VDD_SPECS.md](./PARSEC_VDD_SPECS.md).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -8,7 +12,7 @@ This document describes how to use the Parsec Virtual Display Driver (VDD) C/C++
|
|||||||
|
|
||||||
Parsec VDD enables creation and management of virtual displays on Windows 10+ systems. The C/C++ API allows direct control over the driver, including querying status, adding/removing displays, and updating device state.
|
Parsec VDD enables creation and management of virtual displays on Windows 10+ systems. The C/C++ API allows direct control over the driver, including querying status, adding/removing displays, and updating device state.
|
||||||
|
|
||||||
- Up to **8 virtual displays** per adapter (default).
|
- Up to **16 virtual displays** per adapter (the legacy header caps at 8 to avoid plugging lag — adjust if needed).
|
||||||
- Supports high resolutions and refresh rates (see the [specs](./PARSEC_VDD_SPECS.md)).
|
- Supports high resolutions and refresh rates (see the [specs](./PARSEC_VDD_SPECS.md)).
|
||||||
- Can be used independently of the Parsec app.
|
- Can be used independently of the Parsec app.
|
||||||
|
|
||||||
@@ -41,6 +45,7 @@ enum DeviceStatus {
|
|||||||
```c
|
```c
|
||||||
DeviceStatus QueryDeviceStatus(const GUID *classGuid, const char *deviceId);
|
DeviceStatus QueryDeviceStatus(const GUID *classGuid, const char *deviceId);
|
||||||
```
|
```
|
||||||
|
|
||||||
- Checks the status of a device by class GUID and hardware ID.
|
- Checks the status of a device by class GUID and hardware ID.
|
||||||
- Returns a `DeviceStatus` value.
|
- Returns a `DeviceStatus` value.
|
||||||
|
|
||||||
@@ -51,6 +56,7 @@ DeviceStatus QueryDeviceStatus(const GUID *classGuid, const char *deviceId);
|
|||||||
```c
|
```c
|
||||||
HANDLE OpenDeviceHandle(const GUID *interfaceGuid);
|
HANDLE OpenDeviceHandle(const GUID *interfaceGuid);
|
||||||
```
|
```
|
||||||
|
|
||||||
- Opens a handle to the device interface.
|
- Opens a handle to the device interface.
|
||||||
- Returns `INVALID_HANDLE_VALUE` or a valid handle.
|
- Returns `INVALID_HANDLE_VALUE` or a valid handle.
|
||||||
|
|
||||||
@@ -59,20 +65,21 @@ HANDLE OpenDeviceHandle(const GUID *interfaceGuid);
|
|||||||
```c
|
```c
|
||||||
void CloseDeviceHandle(HANDLE handle);
|
void CloseDeviceHandle(HANDLE handle);
|
||||||
```
|
```
|
||||||
|
|
||||||
- Closes a previously opened device handle.
|
- Closes a previously opened device handle.
|
||||||
|
|
||||||
### VDD Core Operations
|
### VDD Core Operations
|
||||||
|
|
||||||
#### Constants
|
#### Constants
|
||||||
|
|
||||||
| Constant | Value | Description |
|
| Constant | Value | Description |
|
||||||
|---------------------|-----------------------------------------|----------------------------|
|
| ------------------ | ---------------------------------------- | ------------------------ |
|
||||||
| `VDD_DISPLAY_ID` | `"PSCCDD0"` | Display device ID |
|
| `VDD_DISPLAY_ID` | `"PSCCDD0"` | Display device ID |
|
||||||
| `VDD_DISPLAY_NAME` | `"ParsecVDA"` | Display name |
|
| `VDD_DISPLAY_NAME` | `"ParsecVDA"` | Display name |
|
||||||
| `VDD_ADAPTER_GUID` | `{00b41627-04c4-429e-a26e-0265cf50c8fa}`| Adapter GUID |
|
| `VDD_ADAPTER_GUID` | `{00b41627-04c4-429e-a26e-0265cf50c8fa}` | Adapter GUID |
|
||||||
| `VDD_CLASS_GUID` | `{4d36e968-e325-11ce-bfc1-08002be10318}`| Device class GUID |
|
| `VDD_CLASS_GUID` | `{4d36e968-e325-11ce-bfc1-08002be10318}` | Device class GUID |
|
||||||
| `VDD_HARDWARE_ID` | `"Root\\Parsec\\VDA"` | Hardware ID |
|
| `VDD_HARDWARE_ID` | `"Root\\Parsec\\VDA"` | Hardware ID |
|
||||||
| `VDD_MAX_DISPLAYS` | `8` | Maximum virtual displays |
|
| `VDD_MAX_DISPLAYS` | `8` | Maximum virtual displays |
|
||||||
|
|
||||||
#### IOCTL Codes
|
#### IOCTL Codes
|
||||||
|
|
||||||
@@ -91,6 +98,7 @@ enum VddCtlCode {
|
|||||||
```c
|
```c
|
||||||
DWORD VddIoControl(HANDLE vdd, VddCtlCode code, const void *data, size_t size);
|
DWORD VddIoControl(HANDLE vdd, VddCtlCode code, const void *data, size_t size);
|
||||||
```
|
```
|
||||||
|
|
||||||
- Sends an IOCTL to the VDD device.
|
- Sends an IOCTL to the VDD device.
|
||||||
|
|
||||||
#### Query Driver Version
|
#### Query Driver Version
|
||||||
@@ -98,6 +106,7 @@ DWORD VddIoControl(HANDLE vdd, VddCtlCode code, const void *data, size_t size);
|
|||||||
```c
|
```c
|
||||||
int VddVersion(HANDLE vdd);
|
int VddVersion(HANDLE vdd);
|
||||||
```
|
```
|
||||||
|
|
||||||
- Returns the minor version of the VDD driver.
|
- Returns the minor version of the VDD driver.
|
||||||
|
|
||||||
#### Update/Ping VDD
|
#### Update/Ping VDD
|
||||||
@@ -105,13 +114,15 @@ int VddVersion(HANDLE vdd);
|
|||||||
```c
|
```c
|
||||||
void VddUpdate(HANDLE vdd);
|
void VddUpdate(HANDLE vdd);
|
||||||
```
|
```
|
||||||
- Should be called periodically (<100ms) to keep displays alive.
|
|
||||||
|
- **Must** be called every ~100 ms (no longer than ~200 ms apart) to keep displays alive. If pings stop for ~1 second the driver **removes all virtual monitors** — this is its built-in watchdog for crashed hosts. Either run a dedicated thread or schedule a timer.
|
||||||
|
|
||||||
#### Add Virtual Display
|
#### Add Virtual Display
|
||||||
|
|
||||||
```c
|
```c
|
||||||
int VddAddDisplay(HANDLE vdd);
|
int VddAddDisplay(HANDLE vdd);
|
||||||
```
|
```
|
||||||
|
|
||||||
- Adds a new virtual display.
|
- Adds a new virtual display.
|
||||||
- Returns the index of the added display.
|
- Returns the index of the added display.
|
||||||
|
|
||||||
@@ -120,13 +131,14 @@ int VddAddDisplay(HANDLE vdd);
|
|||||||
```c
|
```c
|
||||||
void VddRemoveDisplay(HANDLE vdd, int index);
|
void VddRemoveDisplay(HANDLE vdd, int index);
|
||||||
```
|
```
|
||||||
|
|
||||||
- Removes the display at the given index.
|
- Removes the display at the given index.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Example Usage
|
## Example Usage
|
||||||
|
|
||||||
Check out [core/vdd-demo.cc](/core/vdd-demo.cc).
|
- Minimal demo using the legacy single-header API: [`core/vdd-demo.cc`](../core/vdd-demo.cc).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -136,7 +148,8 @@ See [PARSEC_VDD_SPECS.md](./PARSEC_VDD_SPECS.md) for supported resolutions and r
|
|||||||
|
|
||||||
## Further Reading
|
## Further Reading
|
||||||
|
|
||||||
- [README.md](../README.md): Project overview, app features, and installation.
|
- [README.md](../README.md): project overview, app features, lifecycle diagram.
|
||||||
- [PARSEC_VDD_SPECS.md](./PARSEC_VDD_SPECS.md): Supported display modes and technical specs.
|
- [PARSEC_VDD_SPECS.md](./PARSEC_VDD_SPECS.md): supported display modes and technical specs.
|
||||||
|
- [PARSEC_VDD_RE.md](../docs/PARSEC_VDD_RE.md): full reverse-engineered IOCTL reference — struct layouts, lifecycle, status codes.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
Reference in New Issue
Block a user