Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Windows graphics changes #855

Open
wants to merge 12 commits into
base: master
Choose a base branch
from
271 changes: 119 additions & 152 deletions lib/graphics.js
Expand Up @@ -18,7 +18,7 @@ const exec = require('child_process').exec;
const execSync = require('child_process').execSync;
const util = require('./util');

let _platform = process.platform;
const _platform = process.platform;
let _nvidiaSmiPath = '';

const _linux = (_platform === 'linux' || _platform === 'android');
Expand Down Expand Up @@ -52,7 +52,8 @@ const videoTypes = {
'13': 'UDI embedded',
'14': 'SDTVDONGLE',
'15': 'MIRACAST',
'2147483648': 'INTERNAL'
'2147483648': 'INTERNAL',
'4294967295': 'RDP'
};

function getVendorFromModel(model) {
Expand Down Expand Up @@ -827,20 +828,20 @@ function graphics(callback) {
const workload = [];
workload.push(util.powerShell('Get-CimInstance win32_VideoController | fl *'));
workload.push(util.powerShell('gp "HKLM:\\SYSTEM\\ControlSet001\\Control\\Class\\{4d36e968-e325-11ce-bfc1-08002be10318}\\*" -ErrorAction SilentlyContinue | where MatchingDeviceId $null -NE | select MatchingDeviceId,HardwareInformation.qwMemorySize | fl'));
workload.push(util.powerShell('Get-CimInstance win32_desktopmonitor | fl *'));
workload.push(util.powerShell('Get-CimInstance -Namespace root\\wmi -ClassName WmiMonitorBasicDisplayParams | fl'));
workload.push(util.powerShell('Add-Type -AssemblyName System.Windows.Forms; [System.Windows.Forms.Screen]::AllScreens'));
workload.push(util.powerShell('Get-CimInstance win32_desktopmonitor | select * | fl *'));
workload.push(util.powerShell('Get-CimInstance -Namespace root\\wmi -ClassName WmiMonitorBasicDisplayParams | fl *'));
workload.push(util.powerShell('Get-CimInstance -Namespace root\\wmi -ClassName WmiMonitorConnectionParams | fl'));
workload.push(util.powerShell('gwmi WmiMonitorID -Namespace root\\wmi | ForEach-Object {(($_.ManufacturerName -notmatch 0 | foreach {[char]$_}) -join "") + "|" + (($_.ProductCodeID -notmatch 0 | foreach {[char]$_}) -join "") + "|" + (($_.UserFriendlyName -notmatch 0 | foreach {[char]$_}) -join "") + "|" + (($_.SerialNumberID -notmatch 0 | foreach {[char]$_}) -join "") + "|" + $_.InstanceName}'));
workload.push(util.powerShell('gwmi WmiMonitorID -Namespace root\\wmi | ForEach-Object {("ManufacturerName:" +( $_.ManufacturerName -notmatch 0 | foreach {[char]$_}) -join "") + "|ProductCodeID:" + (($_.ProductCodeID -notmatch 0 | foreach {[char]$_}) -join "") + "|UserFriendlyName:" + (($_.UserFriendlyName -notmatch 0 | foreach {[char]$_}) -join "") + "|SerialNumberID:" + (($_.SerialNumberID -notmatch 0 | foreach {[char]$_}) -join "") + "|InstanceName:" + $_.InstanceName + "|YearOfManufacture:" + $_.YearOfManufacture}'))
workload.push(util.powerShell('Add-Type -AssemblyName System.Windows.Forms; [System.Windows.Forms.Screen]::AllScreens'));

const nvidiaData = nvidiaDevices();

Promise.all(
workload
).then((data) => {
// controller + vram
let csections = data[0].replace(/\r/g, '').split(/\n\s*\n/);
let vsections = data[1].replace(/\r/g, '').split(/\n\s*\n/);
const csections = data[0].replace(/\r/g, '').split(/\n\s*\n/);
const vsections = data[1].replace(/\r/g, '').split(/\n\s*\n/);
result.controllers = parseLinesWindowsControllers(csections, vsections);
result.controllers = result.controllers.map((controller) => { // match by subDeviceId
if (controller.vendor.toLowerCase() === 'nvidia') {
Expand All @@ -866,65 +867,23 @@ function graphics(callback) {
});

// displays
let dsections = data[2].replace(/\r/g, '').split(/\n\s*\n/);
// result.displays = parseLinesWindowsDisplays(dsections);
if (dsections[0].trim() === '') { dsections.shift(); }
if (dsections.length && dsections[dsections.length - 1].trim() === '') { dsections.pop(); }

// monitor (powershell)
let msections = data[3].replace(/\r/g, '').split('Active ');
msections.shift();

// forms.screens (powershell)
let ssections = data[4].replace(/\r/g, '').split('BitsPerPixel ');
ssections.shift();

// connection params (powershell) - video type
let tsections = data[5].replace(/\r/g, '').split(/\n\s*\n/);
tsections.shift();

// monitor ID (powershell) - model / vendor
const res = data[6].replace(/\r/g, '').split(/\n/);
let isections = [];
res.forEach(element => {
const parts = element.split('|');
if (parts.length === 5) {
isections.push({
vendor: parts[0],
code: parts[1],
model: parts[2],
serial: parts[3],
instanceId: parts[4]
});
}
});

result.displays = parseLinesWindowsDisplaysPowershell(ssections, msections, dsections, tsections, isections);

if (result.displays.length === 1) {
if (_resolutionX) {
result.displays[0].resolutionX = _resolutionX;
if (!result.displays[0].currentResX) {
result.displays[0].currentResX = _resolutionX;
}
}
if (_resolutionY) {
result.displays[0].resolutionY = _resolutionY;
if (result.displays[0].currentResY === 0) {
result.displays[0].currentResY = _resolutionY;
const dsections = giveMeJson(data[2])
const msections = giveMeJson(data[3])
const tsections = giveMeJson(data[4])
// const ssections = giveMeJson(data[6]) I cannot find a reliable method to correlate this with the displays from the other output
const isections = []
data[5].split('\r\n').forEach((display) => {
const newDisplay = {}
display.split('|').forEach((chunk) => {
const entries = chunk.split(':')
if (entries.length === 2) {
newDisplay[entries[0].trim()] = entries[1].trim()
}
}
}
if (_pixelDepth) {
result.displays[0].pixelDepth = _pixelDepth;
}
}
result.displays = result.displays.map(element => {
if (_refreshRate && !element.currentRefreshRate) {
element.currentRefreshRate = _refreshRate;
}
return element;
});

)
isections.push(newDisplay)
})
result.displays = parseWindowsDisplays(dsections, msections, tsections, isections)
if (callback) {
callback(result);
}
Expand All @@ -944,6 +903,96 @@ function graphics(callback) {
});
});

function giveMeJson(windowsOutput) {
const newResult = []
windowsOutput.trim()
.replace(/\r/g, '')
.split(/\n\s*\n/)
.map((display, i) => {
const array = display.split('\n')
array.map(line => {
const entries = line.split(':')
if (entries.length === 2) {
if (!newResult[i]) {
newResult[i] = {};
}
newResult[i][entries[0].trim()] = entries[1].trim()
return entries
}
})
})
return newResult;
}


function parseWindowsDisplays(dsections, msections, tsections, isections) {
/**
* To tie everything together tsections.InstanceName is what is being used as the key.
* TSections is the object that will list every monitor when a RDP connection is being used.
*/
const displays = []
tsections.forEach((tsection) => {
const display = {
vendor: '',
deviceName: '',
model: '',
serial: '',
displayId: tsection.InstanceName,
main: false,
builtin: ['2147483648', '4294967295'].includes(tsection.VideoOutputTechnology),
connection: videoTypes[tsection.VideoOutputTechnology] ? videoTypes[tsection.VideoOutputTechnology] : 'Unknown',
productionYear: 0,
sizeX: 0,
sizeY: 0,
pixelDepth: 0,
resolutionX: 0,
resolutionY: 0,
currentResX: 0,
currentResY: 0,
positionX: 0,
positionY: 0,
currentRefreshRate: 0,
}
const dsection = dsections.find((dsection) => {
return `${dsection.PNPDeviceID}_0` === tsection.InstanceName.toUpperCase()
})
const msection = msections.find((msection) => {
return msection.InstanceName === tsection.InstanceName
})
const isection = isections.find((isection) => {
return `${isection.InstanceName}` === tsection.InstanceName
})
if (dsection) {
display.vendor = dsection.MonitorManufacturer
display.deviceName = dsection.Name
display.displayId = tsection.InstanceName
display.resolutionX = util.toInt(dsection.ScreenWidth)
display.resolutionY = util.toInt(dsection.ScreenHeight)
display.model = dsection.Name.split('(', 1)[0].trim()
display.serial = dsection.SerialNumberID
}
if (msection) {
display.sizeX = util.toInt(msection.MaxHorizontalImageSize);
display.sizeY = util.toInt(msection.MaxVerticalImageSize);
}
if (isection) {
display.productionYear = util.toInt(isection.YearOfManufacture)
if (display.vendor === '') {
display.vendor = isection.ManufacturerName
}
display.serial = isection.SerialNumberID
if (display.deviceName === '') {
display.deviceName = isection.UserFriendlyName
display.model = isection.UserFriendlyName
}
}
display.currentResX = display.resolutionX;
display.currentResY = display.resolutionY;
displays.push(display)
})
return displays
}

function parseLinesWindowsControllers(sections, vections) {
const memorySizes = {};
for (const i in vections) {
Expand All @@ -968,12 +1017,12 @@ function graphics(callback) {
}
}

let controllers = [];
for (let i in sections) {
const controllers = [];
for (const i in sections) {
if ({}.hasOwnProperty.call(sections, i)) {
if (sections[i].trim() !== '') {
let lines = sections[i].trim().split('\n');
let pnpDeviceId = util.getValue(lines, 'PNPDeviceID', ':').match(/PCI\\(VEN_[0-9A-F]{4})&(DEV_[0-9A-F]{4})(?:&(SUBSYS_[0-9A-F]{8}))?(?:&(REV_[0-9A-F]{2}))?/i);
const lines = sections[i].trim().split('\n');
const pnpDeviceId = util.getValue(lines, 'PNPDeviceID', ':').match(/PCI\\(VEN_[0-9A-F]{4})&(DEV_[0-9A-F]{4})(?:&(SUBSYS_[0-9A-F]{8}))?(?:&(REV_[0-9A-F]{2}))?/i);
let subDeviceId = null;
let memorySize = null;
if (pnpDeviceId) {
Expand Down Expand Up @@ -1035,88 +1084,6 @@ function graphics(callback) {
}
return controllers;
}

function parseLinesWindowsDisplaysPowershell(ssections, msections, dsections, tsections, isections) {
let displays = [];
let vendor = '';
let model = '';
let deviceID = '';
let resolutionX = 0;
let resolutionY = 0;
if (dsections && dsections.length) {
let linesDisplay = dsections[0].split('\n');
vendor = util.getValue(linesDisplay, 'MonitorManufacturer', ':');
model = util.getValue(linesDisplay, 'Name', ':');
deviceID = util.getValue(linesDisplay, 'PNPDeviceID', ':').replace(/&/g, '&').toLowerCase();
resolutionX = util.toInt(util.getValue(linesDisplay, 'ScreenWidth', ':'));
resolutionY = util.toInt(util.getValue(linesDisplay, 'ScreenHeight', ':'));
}
for (let i = 0; i < ssections.length; i++) {
if (ssections[i].trim() !== '') {
ssections[i] = 'BitsPerPixel ' + ssections[i];
msections[i] = 'Active ' + msections[i];
// tsections can be empty OR undefined on earlier versions of powershell (<=2.0)
// Tag connection type as UNKNOWN by default if this information is missing
if (tsections.length === 0 || tsections[i] === undefined) {
tsections[i] = 'Unknown';
}
let linesScreen = ssections[i].split('\n');
let linesMonitor = msections[i].split('\n');

let linesConnection = tsections[i].split('\n');
const bitsPerPixel = util.getValue(linesScreen, 'BitsPerPixel');
const bounds = util.getValue(linesScreen, 'Bounds').replace('{', '').replace('}', '').replace(/=/g, ':').split(',');
const primary = util.getValue(linesScreen, 'Primary');
const sizeX = util.getValue(linesMonitor, 'MaxHorizontalImageSize');
const sizeY = util.getValue(linesMonitor, 'MaxVerticalImageSize');
const instanceName = util.getValue(linesMonitor, 'InstanceName').toLowerCase();
const videoOutputTechnology = util.getValue(linesConnection, 'VideoOutputTechnology');
const deviceName = util.getValue(linesScreen, 'DeviceName');
let displayVendor = '';
let displayModel = '';
isections.forEach(element => {
if (element.instanceId.toLowerCase().startsWith(instanceName) && vendor.startsWith('(') && model.startsWith('PnP')) {
displayVendor = element.vendor;
displayModel = element.model;
}
});
displays.push({
vendor: instanceName.startsWith(deviceID) && displayVendor === '' ? vendor : displayVendor,
model: instanceName.startsWith(deviceID) && displayModel === '' ? model : displayModel,
deviceName,
main: primary.toLowerCase() === 'true',
builtin: videoOutputTechnology === '2147483648',
connection: videoOutputTechnology && videoTypes[videoOutputTechnology] ? videoTypes[videoOutputTechnology] : '',
resolutionX: util.toInt(util.getValue(bounds, 'Width', ':')),
resolutionY: util.toInt(util.getValue(bounds, 'Height', ':')),
sizeX: sizeX ? parseInt(sizeX, 10) : null,
sizeY: sizeY ? parseInt(sizeY, 10) : null,
pixelDepth: bitsPerPixel,
currentResX: util.toInt(util.getValue(bounds, 'Width', ':')),
currentResY: util.toInt(util.getValue(bounds, 'Height', ':')),
positionX: util.toInt(util.getValue(bounds, 'X', ':')),
positionY: util.toInt(util.getValue(bounds, 'Y', ':')),
});
}
}
if (ssections.length === 0) {
displays.push({
vendor,
model,
main: true,
sizeX: null,
sizeY: null,
resolutionX,
resolutionY,
pixelDepth: null,
currentResX: resolutionX,
currentResY: resolutionY,
positionX: 0,
positionY: 0
});
}
return displays;
}
}

exports.graphics = graphics;
2 changes: 2 additions & 0 deletions lib/system.js
Expand Up @@ -564,6 +564,8 @@ function baseboard(callback) {
const maxCapacityAttribute = win10plus ? 'MaxCapacityEx' : 'MaxCapacity';
workload.push(util.powerShell('Get-CimInstance Win32_baseboard | select Model,Manufacturer,Product,Version,SerialNumber,PartNumber,SKU | fl'));
workload.push(util.powerShell(`Get-CimInstance Win32_physicalmemoryarray | select ${maxCapacityAttribute}, MemoryDevices | fl`));
workload.push(util.powerShell('Get-CimInstance Win32_SystemEnclosure | select SMBIOSAssetTag | fl'));

util.promiseAll(
workload
).then((data) => {
Expand Down