vdi-cloud/tools/set-vdi-display-mode.ps1

112 lines
5.6 KiB
PowerShell

param(
[ValidateSet('Query', 'List', 'Apply', 'Restore')][string]$Action = 'Query',
[ValidateRange(640, 7680)][int]$Width = 2560,
[ValidateRange(480, 4320)][int]$Height = 1440,
[ValidateRange(24, 240)][int]$RefreshRate = 60,
[string]$BackupPath = 'C:\ProgramData\VDI\mouse-optimization\display-before.json'
)
$ErrorActionPreference = 'Stop'
# Run in the logged-in desktop session. Apply is temporary (no registry update).
# Never assumes DISPLAY1, and refuses ambiguous active virtual displays.
Add-Type -TypeDefinition @'
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
public static class VdiModeProbe {
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct Device {
public int cb;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)] public string Name;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)] public string Description;
public int Flags;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)] public string Id;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)] public string Key;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct Mode {
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)] public string name;
public short spec, driver, size, extra;
public int fields, x, y, orientation, fixedOutput;
public short color, duplex, yres, tt, collate;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)] public string form;
public short logPixels;
public int bpp, width, height, flags, hz, icmMethod, icmIntent, media, dither,
reserved1, reserved2, panWidth, panHeight;
}
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
static extern bool EnumDisplayDevices(string name, uint index, ref Device dev, uint flags);
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
static extern bool EnumDisplaySettings(string name, int index, ref Mode mode);
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
static extern int ChangeDisplaySettingsEx(string name, ref Mode mode, IntPtr window, uint flags, IntPtr param);
public static string Find() {
string found = null;
for (uint i = 0; ; i++) {
var d = new Device(); d.cb = Marshal.SizeOf(typeof(Device));
if (!EnumDisplayDevices(null, i, ref d, 0)) return found;
if ((d.Flags & 1) == 0 || d.Description.IndexOf("Virtual Display Driver", StringComparison.OrdinalIgnoreCase) < 0) continue;
if (found != null) throw new InvalidOperationException("Multiple active virtual displays; select manually");
found = d.Name;
}
}
public static Mode Current(string name) {
var m = new Mode(); m.size = (short)Marshal.SizeOf(typeof(Mode));
if (!EnumDisplaySettings(name, -1, ref m)) throw new InvalidOperationException("Cannot query mode");
return m;
}
public static Mode[] List(string name) {
var modes = new List<Mode>();
for(int i=0; ; ++i) {
var m = new Mode(); m.size = (short)Marshal.SizeOf(typeof(Mode));
if (!EnumDisplaySettings(name, i, ref m)) return modes.ToArray();
modes.Add(m);
}
}
public static void Apply(string name, int w, int h, int hz, int bpp) {
foreach(var m0 in List(name)) {
var m = m0;
if(m.width != w || m.height != h || m.hz != hz || m.bpp != bpp) continue;
m.fields = 0x80000 | 0x100000 | 0x400000 | 0x40000;
int result = ChangeDisplaySettingsEx(name, ref m, IntPtr.Zero, 2, IntPtr.Zero);
if(result != 0) throw new InvalidOperationException("Mode test failed: " + result);
result = ChangeDisplaySettingsEx(name, ref m, IntPtr.Zero, 0, IntPtr.Zero);
if(result != 0) throw new InvalidOperationException("Mode apply failed: " + result);
var actual = Current(name);
if(actual.width != w || actual.height != h || actual.hz != hz || actual.bpp != bpp)
throw new InvalidOperationException("Mode read-back mismatch");
return;
}
throw new InvalidOperationException("Requested mode not advertised");
}
}
'@
$device = [VdiModeProbe]::Find()
if (-not $device) { throw 'No active Virtual Display Driver in this session' }
$current = [VdiModeProbe]::Current($device)
if ($Action -eq 'List') {
[VdiModeProbe]::List($device) | Where-Object { $_.width -eq $Width -and $_.height -eq $Height } |
Select-Object width,height,hz,bpp | ConvertTo-Json -Compress
exit 0
}
if ($Action -eq 'Apply') {
if (-not (Test-Path -LiteralPath $BackupPath)) {
New-Item -ItemType Directory -Force (Split-Path $BackupPath) | Out-Null
@{device=$device; width=$current.width; height=$current.height; hz=$current.hz; bpp=$current.bpp} |
ConvertTo-Json | Set-Content -LiteralPath $BackupPath
}
try {
[VdiModeProbe]::Apply($device,$Width,$Height,$RefreshRate,$current.bpp)
} catch {
[VdiModeProbe]::Apply($device,$current.width,$current.height,$current.hz,$current.bpp)
throw
}
}
if ($Action -eq 'Restore') {
$before = Get-Content -LiteralPath $BackupPath -Raw | ConvertFrom-Json
if ($before.device -ne $device) { throw 'Display identity changed; inspect before restoring' }
[VdiModeProbe]::Apply($device,$before.width,$before.height,$before.hz,$before.bpp)
}
$current = [VdiModeProbe]::Current($device)
@{device=$device; width=$current.width; height=$current.height; hz=$current.hz; bpp=$current.bpp; action=$Action} |
ConvertTo-Json -Compress