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 is a comprehensive virtual display manager for Parsec VDD, built
|
||||
with C# and WPF. The app provides an intuitive interface to manage virtual
|
||||
displays, showing the number of active displays and allowing users to add or
|
||||
remove specific virtual displays. It also supports features like changing
|
||||
display resolution, capturing screenshots, and more, making it a versatile tool
|
||||
for flexible display management.
|
||||
ParsecDisplay is a virtual display manager for Parsec VDD, built with C# and
|
||||
WPF. It provides a tray-based interface to add and remove virtual displays,
|
||||
change their resolution, refresh rate, and orientation, capture screenshots,
|
||||
and more.
|
||||
|
||||
👉 Check out [Releases](https://github.com/nomi-san/parsec-vdd/releases) to
|
||||
download it.
|
||||
|
||||
<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>
|
||||
|
||||
## 🚀 Using Core API
|
||||
|
||||
### Design notes
|
||||
|
||||
Parsec VDD is designed to work with Parsec client-connection sessions. When the
|
||||
user connects to the host, the app will start controlling the driver, it sends
|
||||
IO control codes and gets results. When adding a virtual display, you will get
|
||||
its index to be used for unplugging, the maximum number of displays could be
|
||||
added up to 16 per adapter. You have to ping the driver periodically to keep
|
||||
added displays alive, otherwise all of them will be unplugged after a second.
|
||||
There's no direct way to manipulate added displays, you should call Win32
|
||||
Display API to change their display mode (see the ParsecDisplay source).
|
||||
Parsec VDD is designed to work with Parsec client-connection sessions. When
|
||||
the user connects to the host, the app starts controlling the driver —
|
||||
sending IO control codes and receiving results. Adding a virtual display
|
||||
returns an index, used later to unplug it; up to 16 displays can be added
|
||||
per adapter. The driver must be pinged periodically to keep added displays
|
||||
alive, otherwise all of them will be unplugged after about a second. There
|
||||
is no direct way to manipulate added displays — call the Win32 Display API
|
||||
to change their display mode (see the ParsecDisplay source).
|
||||
|
||||
```mermaid
|
||||
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).
|
||||
|
||||
- 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)
|
||||
- There is also a simple demo program, 👉 [core/vdd-demo.cc](./core/vdd-demo.cc)
|
||||
|
||||
### 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 |
|
||||
| :---------------- | :-------------- | :---: | :-------------------------------------------------------- |
|
||||
@@ -141,9 +139,10 @@ In addition, you can run the driver setup in silent mode to install it quickly.
|
||||
### 1. HDR support
|
||||
|
||||
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
|
||||
metadata and setting 10-bit+ color depth. Unfortunately, you cannot flash its
|
||||
firmware like a physical device, or modify the registry value.
|
||||
Theoretically, you can unlock support by editing the EDID to include HDR
|
||||
metadata and a 10-bit+ color depth. Unfortunately, you cannot flash its
|
||||
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
|
||||
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
|
||||
|
||||
Before connecting, the virtual display looks in the `HKLM\SOFTWARE\Parsec\vdd`
|
||||
registry for additional preset resolutions. Currently this supports a maximum of
|
||||
5 values.
|
||||
registry for additional preset resolutions. Currently this supports a maximum
|
||||
of 5 entries.
|
||||
|
||||
```yaml
|
||||
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
|
||||
added, making it difficult to turn the display on and disrupting the remote
|
||||
desktop session.
|
||||
added, making it difficult to turn the main display back on and disrupting
|
||||
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
|
||||
|
||||
@@ -207,8 +228,8 @@ projects.
|
||||
[virtual-display-rs]: https://github.com/MolotovCherry/virtual-display-rs
|
||||
[Virtual-Display-Driver (HDR)]: https://github.com/itsmikethetech/Virtual-Display-Driver
|
||||
|
||||
**Signed** means that the driver files have a valid digital signature.
|
||||
**H-Cursor** means hardware cursor support, without it, you will get a double
|
||||
**Signed** means the driver files have a valid digital signature.
|
||||
**H-Cursor** means hardware-cursor support — without it, you get a double
|
||||
cursor on some remote desktop apps. **Tweakable** is the ability to customize
|
||||
display modes. Visit
|
||||
[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 |
|
||||
| 1280 x 720 | HD | 16:9 | 60/144/240 |
|
||||
|
||||
Check out [docs/PARSEC_VDD_SPECS](./docs/PARSEC_VDD_SPECS.md) to see full of
|
||||
preset display modes the 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>
|
||||
Check out [docs/PARSEC_VDD_SPECS](./docs/PARSEC_VDD_SPECS.md) for the full
|
||||
list of preset display modes and driver specs.
|
||||
|
||||
## 🍻 Credits
|
||||
|
||||
- Thanks to Parsec for the driver
|
||||
- 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
|
||||
```
|
||||
|
||||
The exit code is the index of added display, you can reuse this index to remove
|
||||
the display. Less than 0 means error occurred.
|
||||
Output looks like:
|
||||
|
||||
```
|
||||
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
|
||||
|
||||
Use command `-r` or `remove` to remove the last added.
|
||||
Use command `-r` or `remove` to remove the last-added display.
|
||||
|
||||
```sh
|
||||
vdd -r
|
||||
@@ -59,12 +65,18 @@ Remove the added virtual display at index `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
|
||||
```
|
||||
|
||||
`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
|
||||
|
||||
List all added virtual displays.
|
||||
|
||||
+27
-14
@@ -1,6 +1,10 @@
|
||||
# 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.
|
||||
|
||||
- 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)).
|
||||
- Can be used independently of the Parsec app.
|
||||
|
||||
@@ -41,6 +45,7 @@ enum DeviceStatus {
|
||||
```c
|
||||
DeviceStatus QueryDeviceStatus(const GUID *classGuid, const char *deviceId);
|
||||
```
|
||||
|
||||
- Checks the status of a device by class GUID and hardware ID.
|
||||
- Returns a `DeviceStatus` value.
|
||||
|
||||
@@ -51,6 +56,7 @@ DeviceStatus QueryDeviceStatus(const GUID *classGuid, const char *deviceId);
|
||||
```c
|
||||
HANDLE OpenDeviceHandle(const GUID *interfaceGuid);
|
||||
```
|
||||
|
||||
- Opens a handle to the device interface.
|
||||
- Returns `INVALID_HANDLE_VALUE` or a valid handle.
|
||||
|
||||
@@ -59,20 +65,21 @@ HANDLE OpenDeviceHandle(const GUID *interfaceGuid);
|
||||
```c
|
||||
void CloseDeviceHandle(HANDLE handle);
|
||||
```
|
||||
|
||||
- Closes a previously opened device handle.
|
||||
|
||||
### VDD Core Operations
|
||||
|
||||
#### Constants
|
||||
|
||||
| Constant | Value | Description |
|
||||
|---------------------|-----------------------------------------|----------------------------|
|
||||
| `VDD_DISPLAY_ID` | `"PSCCDD0"` | Display device ID |
|
||||
| `VDD_DISPLAY_NAME` | `"ParsecVDA"` | Display name |
|
||||
| `VDD_ADAPTER_GUID` | `{00b41627-04c4-429e-a26e-0265cf50c8fa}`| Adapter GUID |
|
||||
| `VDD_CLASS_GUID` | `{4d36e968-e325-11ce-bfc1-08002be10318}`| Device class GUID |
|
||||
| `VDD_HARDWARE_ID` | `"Root\\Parsec\\VDA"` | Hardware ID |
|
||||
| `VDD_MAX_DISPLAYS` | `8` | Maximum virtual displays |
|
||||
| Constant | Value | Description |
|
||||
| ------------------ | ---------------------------------------- | ------------------------ |
|
||||
| `VDD_DISPLAY_ID` | `"PSCCDD0"` | Display device ID |
|
||||
| `VDD_DISPLAY_NAME` | `"ParsecVDA"` | Display name |
|
||||
| `VDD_ADAPTER_GUID` | `{00b41627-04c4-429e-a26e-0265cf50c8fa}` | Adapter GUID |
|
||||
| `VDD_CLASS_GUID` | `{4d36e968-e325-11ce-bfc1-08002be10318}` | Device class GUID |
|
||||
| `VDD_HARDWARE_ID` | `"Root\\Parsec\\VDA"` | Hardware ID |
|
||||
| `VDD_MAX_DISPLAYS` | `8` | Maximum virtual displays |
|
||||
|
||||
#### IOCTL Codes
|
||||
|
||||
@@ -91,6 +98,7 @@ enum VddCtlCode {
|
||||
```c
|
||||
DWORD VddIoControl(HANDLE vdd, VddCtlCode code, const void *data, size_t size);
|
||||
```
|
||||
|
||||
- Sends an IOCTL to the VDD device.
|
||||
|
||||
#### Query Driver Version
|
||||
@@ -98,6 +106,7 @@ DWORD VddIoControl(HANDLE vdd, VddCtlCode code, const void *data, size_t size);
|
||||
```c
|
||||
int VddVersion(HANDLE vdd);
|
||||
```
|
||||
|
||||
- Returns the minor version of the VDD driver.
|
||||
|
||||
#### Update/Ping VDD
|
||||
@@ -105,13 +114,15 @@ int VddVersion(HANDLE vdd);
|
||||
```c
|
||||
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
|
||||
|
||||
```c
|
||||
int VddAddDisplay(HANDLE vdd);
|
||||
```
|
||||
|
||||
- Adds a new virtual display.
|
||||
- Returns the index of the added display.
|
||||
|
||||
@@ -120,13 +131,14 @@ int VddAddDisplay(HANDLE vdd);
|
||||
```c
|
||||
void VddRemoveDisplay(HANDLE vdd, int index);
|
||||
```
|
||||
|
||||
- Removes the display at the given index.
|
||||
|
||||
---
|
||||
|
||||
## 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
|
||||
|
||||
- [README.md](../README.md): Project overview, app features, and installation.
|
||||
- [PARSEC_VDD_SPECS.md](./PARSEC_VDD_SPECS.md): Supported display modes and technical specs.
|
||||
- [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_RE.md](../docs/PARSEC_VDD_RE.md): full reverse-engineered IOCTL reference — struct layouts, lifecycle, status codes.
|
||||
|
||||
---
|
||||
|
||||
Reference in New Issue
Block a user