Skip to content

Commit 2e1af84

Browse files
committed
Merge remote-tracking branch 'origin/master'
# Conflicts: # Community Scripts/silent-install.ps1 # README.md
2 parents 43c5ac0 + 22e3abf commit 2e1af84

48 files changed

Lines changed: 19692 additions & 603 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/build-and-sign-sequential.yml

Lines changed: 0 additions & 493 deletions
This file was deleted.
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
name: Build Virtual Display Driver
2+
3+
on:
4+
pull_request:
5+
branches: [ main, master ]
6+
push:
7+
branches: [ main, master ]
8+
paths-ignore:
9+
- '**.md'
10+
- 'docs/**'
11+
- 'LICENSE*'
12+
workflow_dispatch:
13+
14+
env:
15+
BUILD_CONFIGURATION: Release
16+
VDD_SOLUTION: Virtual Display Driver (HDR)/MttVDD.sln
17+
18+
permissions:
19+
contents: read
20+
21+
jobs:
22+
build:
23+
runs-on: windows-latest
24+
strategy:
25+
fail-fast: false
26+
matrix:
27+
platform: [x64, ARM64]
28+
29+
steps:
30+
- name: Checkout
31+
uses: actions/checkout@v4
32+
33+
- name: Setup MSBuild
34+
uses: microsoft/setup-msbuild@v2
35+
36+
- name: Install Windows Driver Kit (IddCx headers)
37+
shell: pwsh
38+
run: |
39+
$ErrorActionPreference = "Stop"
40+
choco install windowsdriverkit11 -y --no-progress
41+
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
42+
43+
- name: Detect Windows SDK (windows.h)
44+
shell: pwsh
45+
run: |
46+
$ErrorActionPreference = "Stop"
47+
48+
$kitsRoot = $null
49+
try {
50+
$kitsRoot = (Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows Kits\Installed Roots" -Name "KitsRoot10" -ErrorAction Stop).KitsRoot10
51+
} catch {
52+
$kitsRoot = Join-Path ${env:ProgramFiles(x86)} "Windows Kits\10\"
53+
}
54+
55+
if (-not $kitsRoot -or -not (Test-Path $kitsRoot)) {
56+
throw "Windows Kits root not found: $kitsRoot"
57+
}
58+
59+
$includeRoot = Join-Path $kitsRoot "Include"
60+
if (-not (Test-Path $includeRoot)) {
61+
throw "Windows Kits Include directory not found: $includeRoot"
62+
}
63+
64+
$best =
65+
Get-ChildItem -Path $includeRoot -Directory -ErrorAction SilentlyContinue |
66+
Where-Object { $_.Name -match '^\d+\.\d+\.\d+\.\d+$' } |
67+
Where-Object { Test-Path (Join-Path $_.FullName "um\Windows.h") } |
68+
Sort-Object -Property Name -Descending |
69+
Select-Object -First 1
70+
71+
if (-not $best) {
72+
throw "Could not find an SDK version directory containing um\\Windows.h under: $includeRoot"
73+
}
74+
75+
$sdkVersion = $best.Name
76+
Write-Output "Using WindowsSdkDir: $kitsRoot"
77+
Write-Output "Using WindowsTargetPlatformVersion: $sdkVersion"
78+
"WINDOWS_SDK_DIR=$kitsRoot" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
79+
"WINDOWS_TARGET_PLATFORM_VERSION=$sdkVersion" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
80+
81+
- name: Build VDD (${{ matrix.platform }})
82+
shell: pwsh
83+
run: |
84+
$ErrorActionPreference = "Stop"
85+
86+
$sln = "${{ env.VDD_SOLUTION }}"
87+
if (-not (Test-Path $sln)) { throw "VDD solution file not found at: $sln" }
88+
89+
# Pick an available UMDF WDF header version on the runner.
90+
# Kits can be laid out either versioned:
91+
# Include\<sdk>\wdf\umdf\2.xx
92+
# or unversioned:
93+
# Include\wdf\umdf\2.xx
94+
$umdfRoots = @(
95+
(Join-Path $env:WINDOWS_SDK_DIR ("Include\" + $env:WINDOWS_TARGET_PLATFORM_VERSION + "\wdf\umdf")),
96+
(Join-Path $env:WINDOWS_SDK_DIR "Include\wdf\umdf")
97+
) | Where-Object { Test-Path $_ }
98+
99+
if (-not $umdfRoots -or $umdfRoots.Count -eq 0) {
100+
throw "UMDF WDF include root not found under $env:WINDOWS_SDK_DIR (tried versioned + unversioned layouts)"
101+
}
102+
103+
$umdfBest =
104+
$umdfRoots |
105+
ForEach-Object { Get-ChildItem -Path $_ -Directory -ErrorAction SilentlyContinue } |
106+
Where-Object { $_.Name -match '^\d+\.\d+$' } |
107+
Sort-Object -Property Name -Descending |
108+
Select-Object -First 1
109+
110+
if (-not $umdfBest) { throw "No UMDF version directories found under: $($umdfRoots -join ', ')" }
111+
112+
$umdfMinor = ($umdfBest.Name -split '\.')[1]
113+
Write-Output "Using UMDF version: $($umdfBest.Name) (minor=$umdfMinor) from $($umdfBest.FullName)"
114+
115+
msbuild $sln `
116+
/m `
117+
/t:Build `
118+
/p:Configuration="${{ env.BUILD_CONFIGURATION }}" `
119+
/p:Platform="${{ matrix.platform }}" `
120+
/p:WindowsSdkDir="$env:WINDOWS_SDK_DIR" `
121+
/p:WindowsTargetPlatformVersion="$env:WINDOWS_TARGET_PLATFORM_VERSION" `
122+
/p:UMDF_VERSION_MINOR="$umdfMinor" `
123+
/p:EnableInfVerif=false `
124+
/p:RunApiValidator=false `
125+
/verbosity:minimal
126+
127+
- name: Collect outputs
128+
shell: pwsh
129+
run: |
130+
$ErrorActionPreference = "Stop"
131+
$outDir = "Virtual Display Driver (HDR)\${{ matrix.platform }}\${{ env.BUILD_CONFIGURATION }}\MttVDD"
132+
if (-not (Test-Path $outDir)) { throw "Build output directory not found: $outDir" }
133+
134+
$dest = "artifacts\VDD\${{ matrix.platform }}"
135+
New-Item -ItemType Directory -Path $dest -Force | Out-Null
136+
Copy-Item "$outDir\*" -Destination $dest -Recurse -Force
137+
138+
- name: Upload artifacts
139+
uses: actions/upload-artifact@v4
140+
with:
141+
name: VDD-${{ matrix.platform }}-${{ env.BUILD_CONFIGURATION }}
142+
path: artifacts/VDD/${{ matrix.platform }}/
143+
if-no-files-found: error
144+

Common/Include/AdapterOption.h

Lines changed: 104 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,26 @@
33
#include <wrl/client.h> // For ComPtr
44
#include <dxgi.h> // For IDXGIAdapter, IDXGIFactory1
55
#include <algorithm> // For sort
6+
#include <setupapi.h>
7+
#include <devguid.h>
8+
#include <devpropdef.h>
9+
#include <devpkey.h>
10+
#include <cstdint>
11+
#include <optional>
12+
#include <string>
13+
#include <vector>
14+
#include <fstream>
615

716
using namespace std;
817
using namespace Microsoft::WRL;
918

19+
// DEVPKEY_Device_Luid: {60b193cb-5276-4d0f-96fc-f173ab17af69}, 2
20+
// Define it ourselves to avoid SDK/WDK header differences where DEVPKEY_Device_Luid may not be declared.
21+
static const DEVPROPKEY DEVPKEY_Device_Luid_Custom = {
22+
{ 0x60b193cb, 0x5276, 0x4d0f, { 0x96, 0xfc, 0xf1, 0x73, 0xab, 0xad, 0x3e, 0xc6 } },
23+
2
24+
};
25+
1026
// Structure to vector gpus
1127
struct GPUInfo {
1228
wstring name; // GPU name
@@ -49,6 +65,68 @@ vector<GPUInfo> getAvailableGPUs() {
4965
return gpus;
5066
}
5167

68+
// Resolve an adapter LUID from a PCI bus number by enumerating display devices (SetupAPI).
69+
// Returns nullopt if no match is found or if the system doesn't expose the LUID property.
70+
inline std::optional<LUID> ResolveAdapterLuidFromPciBus(uint32_t targetBusIndex) {
71+
HDEVINFO devInfo = SetupDiGetClassDevsW(&GUID_DEVCLASS_DISPLAY, nullptr, nullptr, DIGCF_PRESENT);
72+
if (devInfo == INVALID_HANDLE_VALUE) {
73+
return std::nullopt;
74+
}
75+
76+
SP_DEVINFO_DATA devData = {};
77+
devData.cbSize = sizeof(devData);
78+
79+
std::optional<LUID> result = std::nullopt;
80+
81+
for (DWORD i = 0; SetupDiEnumDeviceInfo(devInfo, i, &devData); ++i) {
82+
DWORD currentBus = 0;
83+
if (!SetupDiGetDeviceRegistryPropertyW(
84+
devInfo,
85+
&devData,
86+
SPDRP_BUSNUMBER,
87+
nullptr,
88+
reinterpret_cast<PBYTE>(&currentBus),
89+
sizeof(currentBus),
90+
nullptr)) {
91+
continue;
92+
}
93+
94+
if (static_cast<uint32_t>(currentBus) != targetBusIndex) {
95+
continue;
96+
}
97+
98+
// DEVPKEY_Device_Luid is exposed as a UINT64 on Windows; convert into LUID.
99+
DEVPROPTYPE propType = 0;
100+
ULONG propSize = 0;
101+
ULONGLONG luid64 = 0;
102+
103+
if (!SetupDiGetDevicePropertyW(
104+
devInfo,
105+
&devData,
106+
&DEVPKEY_Device_Luid_Custom,
107+
&propType,
108+
reinterpret_cast<PBYTE>(&luid64),
109+
sizeof(luid64),
110+
&propSize,
111+
0)) {
112+
continue;
113+
}
114+
115+
if (propType != DEVPROP_TYPE_UINT64 || propSize != sizeof(luid64)) {
116+
continue;
117+
}
118+
119+
LUID luid{};
120+
luid.LowPart = static_cast<DWORD>(luid64 & 0xFFFFFFFFull);
121+
luid.HighPart = static_cast<LONG>((luid64 >> 32) & 0xFFFFFFFFull);
122+
result = luid;
123+
break;
124+
}
125+
126+
SetupDiDestroyDeviceInfoList(devInfo);
127+
return result;
128+
}
129+
52130
class AdapterOption {
53131
public:
54132
bool hasTargetAdapter{}; // Indicates if a target adapter is selected
@@ -110,8 +188,32 @@ class AdapterOption {
110188
}
111189

112190
private:
113-
// Find and set the adapter by its name
114-
bool findAndSetAdapter(const wstring& adapterName) {
191+
// Find and set the adapter by name, optionally using "name,bus" where bus is the PCI bus number.
192+
bool findAndSetAdapter(const wstring& adapterSpec) {
193+
// If user provides "name,bus", use bus to resolve LUID (more deterministic on multi-GPU setups).
194+
const size_t comma = adapterSpec.find(L',');
195+
if (comma != wstring::npos) {
196+
const wstring namePart = adapterSpec.substr(0, comma);
197+
wstring busPart = adapterSpec.substr(comma + 1);
198+
// Trim whitespace in bus part
199+
busPart.erase(remove_if(busPart.begin(), busPart.end(), iswspace), busPart.end());
200+
201+
wchar_t* end = nullptr;
202+
const unsigned long busUl = wcstoul(busPart.c_str(), &end, 10);
203+
const bool parsedOk = (end != nullptr) && (*end == L'\0') && (end != busPart.c_str());
204+
if (parsedOk && busUl <= 0xFFFFFFFFul) {
205+
if (auto luidOpt = ResolveAdapterLuidFromPciBus(static_cast<uint32_t>(busUl)); luidOpt.has_value()) {
206+
adapterLuid = luidOpt.value();
207+
hasTargetAdapter = true;
208+
return true;
209+
}
210+
}
211+
212+
// Fall through to name matching using the name portion.
213+
return findAndSetAdapter(namePart);
214+
}
215+
216+
const wstring& adapterName = adapterSpec;
115217
auto gpus = getAvailableGPUs();
116218

117219
// Iterate through all available GPUs

Community Scripts/README.md

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,20 @@
1-
#Scripts
1+
# Scripts
22

33
This is a collection of Powershell scripts, written during the whole driver development. PR's are welcome for improvements or other/more functions. There haven't been a focus on keeping the scripts "up to date" all the time. Which means changes that might have occurred in the modules that these scripts depend upon, haven't been corrected either. These are more ment as a framework for "tinkers" to make use of Powershell to change settings on the fly. To use these, you should be at least be comfortable with Powershell and understand Admin privilegiets, risk/reward.
44

55
Anyway here is a short description of each script:
6-
7-
silent‑install.ps1 – Silently fetches the latest signed Virtual Display Driver from GitHub and installs it via DevCon, then tidies up the temp workspace.
8-
modules_install.bat – Opens an elevated PowerShell session that installs the DisplayConfig and MonitorConfig modules so every helper script has its prerequisites.
9-
set‑dependencies.ps1 – Verifies the exact module versions required, installs or imports them on demand, and aborts downstream execution if anything is missing.
10-
get_disp_num.ps1 – Returns the numeric adapter ID of the VDD screen by scanning WMI for your custom “MTT1337” monitor identifier.
11-
changeres‑VDD.ps1 – Hot‑swaps the virtual panel’s resolution to the width and height you pass on the command line.
12-
refreshrate‑VDD.ps1 – Changes the VDD monitor’s refresh rate (30‑‑500 Hz) after validating the value against a safe list.
13-
rotate‑VDD.ps1 – Rotates the virtual display 90°, 180°, or 270° by calling DisplayConfig with the matching rotation token.
14-
scale‑VDD.ps1 – Sets or resets DPI scaling on the virtual monitor, respecting Windows’ maximum allowed scale factor.
15-
HDRswitch‑VDD.ps1 – Flips the virtual screen between SDR (8‑bit) and HDR (10‑bit) colour modes in one click. Unsure if it works anylonger, since changes in moduels)
16-
primary‑VDD.ps1 – Makes the virtual display the Windows primary so you can stream or remote‑desktop from a headless rig.
17-
toggle‑VDD.ps1 – A one‑click PowerShell switch that first elevates itself to admin, then enables or disables your Virtual Display Driver and immediately flips Windows between Extended and Cloned desktops, perfect for streamers who need to bring a virtual monitor online or offline on demand.
18-
winp‑VDD.ps1 – A lightweight companion script that leaves the driver untouched and simply yo‑yos Windows between Extend and Clone modes, giving you an instant “presentation toggle” when the virtual display should stay permanently enabled.
6+
| Script | Description |
7+
| ---------------------- | ---------------- |
8+
| [changeres‑VDD.ps1](changeres-VDD.ps1) | Hot‑swaps the virtual panel’s resolution to the width and height you pass on the command line. |
9+
| [get_disp_num.ps1](get_disp_num.ps1) | Returns the numeric adapter ID of the VDD screen by scanning WMI for your custom “MTT1337” monitor identifier. |
10+
| [HDRswitch‑VDD.ps1](HDRswitch-VDD.ps1) | Flips the virtual screen between SDR (8‑bit) and HDR (10‑bit) colour modes in one click. Unsure if it works anylonger, since changes in modules |
11+
| [modules_install.bat](modules_install.bat) | Opens an elevated PowerShell session that installs the DisplayConfig and MonitorConfig modules so every helper script has its prerequisites. |
12+
| [primary‑VDD.ps1](primary-VDD.ps1) | Makes the virtual display the Windows primary so you can stream or remote‑desktop from a headless rig. |
13+
| [refreshrate‑VDD.ps1](refreshrate.ps1) | Changes the VDD monitor’s refresh rate (30‑‑500 Hz) after validating the value against a safe list. |
14+
| [rotate‑VDD.ps1](rotate-VDD.ps1) | Rotates the virtual display 90°, 180°, or 270° by calling DisplayConfig with the matching rotation token. |
15+
| [scale‑VDD.ps1](scale-VDD.ps1) | Sets or resets DPI scaling on the virtual monitor, respecting Windows’ maximum allowed scale factor. |
16+
| [set‑dependencies.ps1](set-dependencies.ps1) | Verifies the exact module versions required, installs or imports them on demand, and aborts downstream execution if anything is missing. |
17+
| [silent‑install.ps1](silent-install.ps1) | Silently fetches the latest signed Virtual Display Driver from GitHub and installs it via Nefcon, then tidies up the temp workspace. |
18+
| [toggle‑VDD.ps1](toggle-VDD.ps1) | A one‑click PowerShell switch that first elevates itself to admin, then enables or disables your Virtual Display Driver and immediately flips Windows between Extended and Cloned desktops, perfect for streamers who need to bring a virtual monitor online or offline on demand. |
19+
| [virtual-driver-manager.ps1](virtual-driver-manager.ps1) | A comprehensive script to manage the Virtual Display Driver. It can install, uninstall, enable, disable, toggle, and check the status of the driver. |
20+
| [winp‑VDD.ps1](winp-VDD.ps1) | A lightweight companion script that leaves the driver untouched and simply yo‑yos Windows between Extend and Clone modes, giving you an instant “presentation toggle” when the virtual display should stay permanently enabled. |

0 commit comments

Comments
 (0)