@@ -12,7 +12,7 @@ const path = require('node:path')
1212const crypto = require ( 'node:crypto' )
1313const child_process = require ( 'node:child_process' )
1414
15- const SPEC_VERSION = 2
15+ const SPEC_VERSION = 3
1616
1717// PowerShell on Windows has a meaningful cold-start cost (CLR init + WMI
1818// service spin-up can easily blow past 5s on the first call after boot),
@@ -139,8 +139,101 @@ try {
139139 }
140140} catch { }
141141
142+ # Real VRAM via the display-adapter class key. Win32_VideoController.AdapterRAM
143+ # is a DWORD and is capped at 4GB (and often outright wrong on modern cards);
144+ # the kernel writes the 64-bit truth into HardwareInformation.qwMemorySize.
145+ # We collect every adapter subkey and let the JS side correlate by PNPDeviceID
146+ # (MatchingDeviceId) so we can attach VRAM to the right Win32_VideoController row.
147+ $adapters = @()
148+ try {
149+ $classRoot = 'HKLM:\SYSTEM\CurrentControlSet\Control\Class\{4d36e968-e325-11ce-bfc1-08002be10318}'
150+ if (Test-Path $classRoot) {
151+ $adapters = Get-ChildItem $classRoot -ErrorAction SilentlyContinue | Where-Object { $_.PSChildName -match '^\d{4}$' } | ForEach-Object {
152+ $vals = $null
153+ try { $vals = Get-ItemProperty -Path $_.PSPath -ErrorAction Stop } catch { }
154+ if ($vals) {
155+ @{
156+ subkey = $_.PSChildName
157+ driverDesc = "$($vals.DriverDesc)"
158+ matchingDevId = "$($vals.MatchingDeviceId)"
159+ # qwMemorySize is the 64-bit value; HardwareInformation.MemorySize is the
160+ # legacy DWORD. Prefer the QWORD when present.
161+ qwMemorySize = if ($vals.'HardwareInformation.qwMemorySize') { [int64]$vals.'HardwareInformation.qwMemorySize' } else { $null }
162+ dwMemorySize = if ($vals.'HardwareInformation.MemorySize') { [int64]$vals.'HardwareInformation.MemorySize' } else { $null }
163+ }
164+ }
165+ }
166+ }
167+ } catch { }
168+
169+ # Physical monitors (panels actually attached) via the WMI monitor namespace.
170+ # Win32_VideoController only knows about adapters; a single GPU can drive 1..N
171+ # monitors. We pull EDID-derived manufacturer/product strings + the largest
172+ # supported source mode (proxy for the panel's native resolution) so the
173+ # scanner reports every attached monitor, not just "the GPU's primary".
174+ $monitors = @()
175+ try {
176+ $ids = Get-CimInstance -Namespace root/wmi -ClassName WmiMonitorID -ErrorAction Stop
177+ $modes = @()
178+ try { $modes = Get-CimInstance -Namespace root/wmi -ClassName WmiMonitorListedSupportedSourceModes -ErrorAction Stop } catch { }
179+ $modesByInstance = @{}
180+ foreach ($m in $modes) {
181+ $modesByInstance[$m.InstanceName] = $m
182+ }
183+ foreach ($id in $ids) {
184+ $name = ''
185+ if ($id.UserFriendlyName) {
186+ $name = -join ($id.UserFriendlyName | Where-Object { $_ -gt 0 } | ForEach-Object { [char]$_ })
187+ }
188+ $manuf = ''
189+ if ($id.ManufacturerName) {
190+ $manuf = -join ($id.ManufacturerName | Where-Object { $_ -gt 0 } | ForEach-Object { [char]$_ })
191+ }
192+ $product = ''
193+ if ($id.ProductCodeID) {
194+ $product = -join ($id.ProductCodeID | Where-Object { $_ -gt 0 } | ForEach-Object { [char]$_ })
195+ }
196+ $serial = ''
197+ if ($id.SerialNumberID) {
198+ $serial = -join ($id.SerialNumberID | Where-Object { $_ -gt 0 } | ForEach-Object { [char]$_ })
199+ }
200+ # Native mode = largest preferred mode. The list is unordered; pick the one
201+ # with the highest horizontal active pixels and matching vertical.
202+ $w = $null; $h = $null; $hz = $null
203+ $sm = $modesByInstance[$id.InstanceName]
204+ if ($sm -and $sm.MonitorSourceModes) {
205+ $best = $sm.MonitorSourceModes | Sort-Object -Property HorizontalActivePixels -Descending | Select-Object -First 1
206+ if ($best) {
207+ $w = [int]$best.HorizontalActivePixels
208+ $h = [int]$best.VerticalActivePixels
209+ if ($best.VerticalRefreshRateNumerator -and $best.VerticalRefreshRateDenominator) {
210+ $hz = [int][math]::Round($best.VerticalRefreshRateNumerator / $best.VerticalRefreshRateDenominator)
211+ }
212+ }
213+ }
214+ $monitors += @{
215+ instance = $id.InstanceName
216+ name = $name
217+ manuf = $manuf
218+ product = $product
219+ serial = $serial
220+ yearOfManufacture = $id.YearOfManufacture
221+ active = [bool]$id.Active
222+ width = $w
223+ height = $h
224+ refreshHz = $hz
225+ }
226+ }
227+ } catch { }
228+
229+ # DPI / current-mode pass: Win32_DesktopMonitor has the *current* (not native)
230+ # resolution for whichever monitor each Win32_VideoController is currently
231+ # driving. Useful as a fallback when WmiMonitor* isn't accessible (some
232+ # locked-down corporate images).
233+ $desktopMonitors = Try-CIM 'Win32_DesktopMonitor' $null | Select-Object Name,ScreenWidth,ScreenHeight,DeviceID,PNPDeviceID,MonitorManufacturer,MonitorType
234+
142235$out = @{
143- cs=$cs; os=$os_; cpu=$cpu; gpu=$gpu; mem=$mem; disk=$disk; vol=$vol; volMedia=$volMedia; pdisks=$pdisks
236+ cs=$cs; os=$os_; cpu=$cpu; gpu=$gpu; mem=$mem; disk=$disk; vol=$vol; volMedia=$volMedia; pdisks=$pdisks; adapters=$adapters; monitors=$monitors; desktopMonitors=$desktopMonitors
144237}
145238$out | ConvertTo-Json -Depth 8 -Compress
146239`
@@ -172,14 +265,39 @@ async function scanWindows() {
172265 const cpu = cpus [ 0 ] || { }
173266 const archMap = { 0 : 'x86' , 5 : 'arm' , 6 : 'ia64' , 9 : 'x64' , 12 : 'arm64' }
174267
268+ // Build a lookup of "real" VRAM from the registry. AdapterRAM (DWORD) is
269+ // capped at 4GB and outright wrong on most modern cards. The display class
270+ // key stores the 64-bit HardwareInformation.qwMemorySize that we trust over
271+ // the WMI value. Correlate by PNPDeviceID prefix.
272+ const vramByPnpPrefix = new Map ( )
273+ for ( const a of toArray ( parsed . adapters ) ) {
274+ const matching = String ( a ?. matchingDevId || '' ) . toUpperCase ( )
275+ if ( ! matching ) continue
276+ const bytes = Number ( a ?. qwMemorySize ) || Number ( a ?. dwMemorySize ) || 0
277+ if ( ! bytes ) continue
278+ // Strip the subsys/rev suffix so we match "PCI\VEN_10DE&DEV_2786" against
279+ // both shortened PnP IDs and full ones.
280+ const key = matching . split ( '&' ) . slice ( 0 , 2 ) . join ( '&' )
281+ vramByPnpPrefix . set ( key , bytes )
282+ vramByPnpPrefix . set ( matching , bytes )
283+ }
284+ function lookupRealVram ( pnp ) {
285+ if ( ! pnp ) return null
286+ const up = String ( pnp ) . toUpperCase ( )
287+ if ( vramByPnpPrefix . has ( up ) ) return vramByPnpPrefix . get ( up )
288+ const short = up . split ( '&' ) . slice ( 0 , 2 ) . join ( '&' )
289+ if ( vramByPnpPrefix . has ( short ) ) return vramByPnpPrefix . get ( short )
290+ return null
291+ }
292+
175293 // Rank GPUs so virtual / paravirtual adapters (Meta Oculus Virtual,
176294 // Parsec, Microsoft Basic Display, IddSampleDriver, RDP, Hyper-V) lose
177295 // to real silicon when the renderer reads `gpus[0]`.
178296 const rawGpus = toArray ( parsed . gpu )
179297 . filter ( ( g ) => g && ( g . Name || g . PNPDeviceID ) )
180298 . map ( ( g ) => ( {
181299 name : g ?. Name || null ,
182- vramBytes : Number ( g ?. AdapterRAM ) || null ,
300+ vramBytes : lookupRealVram ( g ?. PNPDeviceID ) || Number ( g ?. AdapterRAM ) || null ,
183301 vendor : detectGpuVendor ( g ?. Name || '' ) ,
184302 driverVersion : g ?. DriverVersion || null ,
185303 driverDate : g ?. DriverDate || null ,
@@ -258,23 +376,77 @@ async function scanWindows() {
258376 }
259377 } )
260378
261- // Re-derive displays from the same Win32_VideoController rows (they
262- // carry CurrentHorizontalResolution/Refresh). Filter to active adapters
263- // so a disabled Oculus virtual display doesn't show up as your monitor.
264- const displays = toArray ( parsed . gpu )
265- . filter ( ( d ) => d ?. CurrentHorizontalResolution && d ?. CurrentVerticalResolution )
266- . map ( ( d ) => ( {
267- label : d ?. Name || null ,
268- width : Number ( d . CurrentHorizontalResolution ) || null ,
269- height : Number ( d . CurrentVerticalResolution ) || null ,
270- refreshHz : Number ( d . CurrentRefreshRate ) || null ,
271- } ) )
272- // Same virtual-adapter pushdown as GPUs.
379+ // Enumerate every *physical* monitor attached to the machine. The previous
380+ // implementation derived displays from Win32_VideoController, which is
381+ // per-adapter and only ever reported one entry (the GPU's primary surface)
382+ // — multi-monitor setups appeared as a single 1080p panel. Now we use
383+ // root/wmi → WmiMonitorID for the panel list and WmiMonitorListedSupported-
384+ // SourceModes for the native resolution, falling back to Win32_DesktopMonitor
385+ // and finally Win32_VideoController for locked-down systems where the WMI
386+ // monitor namespace is blocked.
387+ const monitorRows = toArray ( parsed . monitors ) . filter ( ( m ) => m && ( m . name || m . product || m . manuf ) )
388+ let displays = monitorRows . map ( ( m ) => ( {
389+ label : ( m . name && m . name . trim ( ) ) || [ m . manuf , m . product ] . filter ( Boolean ) . join ( ' ' ) . trim ( ) || null ,
390+ width : Number ( m . width ) || null ,
391+ height : Number ( m . height ) || null ,
392+ refreshHz : Number ( m . refreshHz ) || null ,
393+ manufacturer : m . manuf || null ,
394+ product : m . product || null ,
395+ serial : m . serial || null ,
396+ active : m . active !== false ,
397+ } ) )
398+
399+ if ( displays . length === 0 ) {
400+ // Win32_DesktopMonitor fallback. Multiple rows per machine on multi-head
401+ // rigs; sometimes one ghost row with zero dimensions — filter those.
402+ displays = toArray ( parsed . desktopMonitors )
403+ . filter ( ( d ) => Number ( d ?. ScreenWidth ) && Number ( d ?. ScreenHeight ) )
404+ . map ( ( d ) => ( {
405+ label : d ?. Name || d ?. MonitorType || null ,
406+ width : Number ( d . ScreenWidth ) || null ,
407+ height : Number ( d . ScreenHeight ) || null ,
408+ refreshHz : null ,
409+ manufacturer : d ?. MonitorManufacturer || null ,
410+ product : null ,
411+ serial : null ,
412+ active : true ,
413+ } ) )
414+ }
415+
416+ if ( displays . length === 0 ) {
417+ // Last-resort fallback: the old per-adapter view. Only fires when both
418+ // monitor namespaces are unavailable (rare).
419+ displays = toArray ( parsed . gpu )
420+ . filter ( ( d ) => d ?. CurrentHorizontalResolution && d ?. CurrentVerticalResolution )
421+ . map ( ( d ) => ( {
422+ label : d ?. Name || null ,
423+ width : Number ( d . CurrentHorizontalResolution ) || null ,
424+ height : Number ( d . CurrentVerticalResolution ) || null ,
425+ refreshHz : Number ( d . CurrentRefreshRate ) || null ,
426+ manufacturer : null ,
427+ product : null ,
428+ serial : null ,
429+ active : true ,
430+ } ) )
431+ }
432+
433+ // Push virtual / inactive monitors to the back, and dedupe entries that
434+ // share width+height+label (Win32_DesktopMonitor sometimes lists the same
435+ // monitor twice once via DDC and once via EDID).
436+ const seen = new Set ( )
437+ displays = displays
273438 . sort ( ( a , b ) => {
439+ if ( a . active !== b . active ) return a . active ? - 1 : 1
274440 const av = isVirtualGpu ( a . label || '' , '' )
275441 const bv = isVirtualGpu ( b . label || '' , '' )
276442 if ( av !== bv ) return av ? 1 : - 1
277- return 0
443+ return ( b . width || 0 ) - ( a . width || 0 )
444+ } )
445+ . filter ( ( d ) => {
446+ const k = `${ d . label } |${ d . width } |${ d . height } |${ d . serial || '' } `
447+ if ( seen . has ( k ) ) return false
448+ seen . add ( k )
449+ return true
278450 } )
279451
280452 return {
0 commit comments