112 lines
5.6 KiB
PowerShell
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
|