From 1a9f4343354dbf0baffb90cc9fdeb2c553e29f12 Mon Sep 17 00:00:00 2001 From: Sebastian Hildebrandt Date: Tue, 23 Dec 2025 07:09:28 +0100 Subject: [PATCH] fix GPU getNvidiaSmi directory tracing --- lib/graphics.js | 597 ++++++++++++++++++++++++++++-------------------- 1 file changed, 352 insertions(+), 245 deletions(-) diff --git a/lib/graphics.js b/lib/graphics.js index 6833bdc..00a697b 100644 --- a/lib/graphics.js +++ b/lib/graphics.js @@ -21,13 +21,13 @@ const util = require('./util'); let _platform = process.platform; let _nvidiaSmiPath = ''; -const _linux = (_platform === 'linux' || _platform === 'android'); -const _darwin = (_platform === 'darwin'); -const _windows = (_platform === 'win32'); -const _freebsd = (_platform === 'freebsd'); -const _openbsd = (_platform === 'openbsd'); -const _netbsd = (_platform === 'netbsd'); -const _sunos = (_platform === 'sunos'); +const _linux = _platform === 'linux' || _platform === 'android'; +const _darwin = _platform === 'darwin'; +const _windows = _platform === 'win32'; +const _freebsd = _platform === 'freebsd'; +const _openbsd = _platform === 'openbsd'; +const _netbsd = _platform === 'netbsd'; +const _sunos = _platform === 'sunos'; let _resolutionX = 0; let _resolutionY = 0; @@ -37,22 +37,22 @@ let _refreshRate = 0; const videoTypes = { '-2': 'UNINITIALIZED', '-1': 'OTHER', - '0': 'HD15', - '1': 'SVIDEO', - '2': 'Composite video', - '3': 'Component video', - '4': 'DVI', - '5': 'HDMI', - '6': 'LVDS', - '8': 'D_JPN', - '9': 'SDI', - '10': 'DP', - '11': 'DP embedded', - '12': 'UDI', - '13': 'UDI embedded', - '14': 'SDTVDONGLE', - '15': 'MIRACAST', - '2147483648': 'INTERNAL' + 0: 'HD15', + 1: 'SVIDEO', + 2: 'Composite video', + 3: 'Component video', + 4: 'DVI', + 5: 'HDMI', + 6: 'LVDS', + 8: 'D_JPN', + 9: 'SDI', + 10: 'DP', + 11: 'DP embedded', + 12: 'UDI', + 13: 'UDI embedded', + 14: 'SDTVDONGLE', + 15: 'MIRACAST', + 2147483648: 'INTERNAL' }; function getVendorFromModel(model) { @@ -77,7 +77,7 @@ function getVendorFromModel(model) { { pattern: 'APPLE.?', manufacturer: 'Apple' }, { pattern: 'INTEL.?', manufacturer: 'Intel' }, { pattern: 'AMD.?', manufacturer: 'AMD' }, - { pattern: 'NVIDIA.?', manufacturer: 'NVDIA' }, + { pattern: 'NVIDIA.?', manufacturer: 'NVDIA' } ]; let result = ''; @@ -85,7 +85,9 @@ function getVendorFromModel(model) { model = model.toUpperCase(); manufacturers.forEach((manufacturer) => { const re = RegExp(manufacturer.pattern); - if (re.test(model)) { result = manufacturer.manufacturer; } + if (re.test(model)) { + result = manufacturer.manufacturer; + } }); } return result; @@ -93,11 +95,11 @@ function getVendorFromModel(model) { function getVendorFromId(id) { const vendors = { - '610': 'Apple', + 610: 'Apple', '1e6d': 'LG', '10ac': 'DELL', '4dd9': 'Sony', - '38a3': 'NEC', + '38a3': 'NEC' }; return vendors[id] || ''; } @@ -105,47 +107,51 @@ function getVendorFromId(id) { function vendorToId(str) { let result = ''; str = (str || '').toLowerCase(); - if (str.indexOf('apple') >= 0) { result = '0x05ac'; } - else if (str.indexOf('nvidia') >= 0) { result = '0x10de'; } - else if (str.indexOf('intel') >= 0) { result = '0x8086'; } - else if (str.indexOf('ati') >= 0 || str.indexOf('amd') >= 0) { result = '0x1002'; } + if (str.indexOf('apple') >= 0) { + result = '0x05ac'; + } else if (str.indexOf('nvidia') >= 0) { + result = '0x10de'; + } else if (str.indexOf('intel') >= 0) { + result = '0x8086'; + } else if (str.indexOf('ati') >= 0 || str.indexOf('amd') >= 0) { + result = '0x1002'; + } return result; } function getMetalVersion(id) { const families = { - 'spdisplays_mtlgpufamilymac1': 'mac1', - 'spdisplays_mtlgpufamilymac2': 'mac2', - 'spdisplays_mtlgpufamilyapple1': 'apple1', - 'spdisplays_mtlgpufamilyapple2': 'apple2', - 'spdisplays_mtlgpufamilyapple3': 'apple3', - 'spdisplays_mtlgpufamilyapple4': 'apple4', - 'spdisplays_mtlgpufamilyapple5': 'apple5', - 'spdisplays_mtlgpufamilyapple6': 'apple6', - 'spdisplays_mtlgpufamilyapple7': 'apple7', - 'spdisplays_metalfeaturesetfamily11': 'family1_v1', - 'spdisplays_metalfeaturesetfamily12': 'family1_v2', - 'spdisplays_metalfeaturesetfamily13': 'family1_v3', - 'spdisplays_metalfeaturesetfamily14': 'family1_v4', - 'spdisplays_metalfeaturesetfamily21': 'family2_v1' + spdisplays_mtlgpufamilymac1: 'mac1', + spdisplays_mtlgpufamilymac2: 'mac2', + spdisplays_mtlgpufamilyapple1: 'apple1', + spdisplays_mtlgpufamilyapple2: 'apple2', + spdisplays_mtlgpufamilyapple3: 'apple3', + spdisplays_mtlgpufamilyapple4: 'apple4', + spdisplays_mtlgpufamilyapple5: 'apple5', + spdisplays_mtlgpufamilyapple6: 'apple6', + spdisplays_mtlgpufamilyapple7: 'apple7', + spdisplays_metalfeaturesetfamily11: 'family1_v1', + spdisplays_metalfeaturesetfamily12: 'family1_v2', + spdisplays_metalfeaturesetfamily13: 'family1_v3', + spdisplays_metalfeaturesetfamily14: 'family1_v4', + spdisplays_metalfeaturesetfamily21: 'family2_v1' }; return families[id] || ''; } function graphics(callback) { - function parseLinesDarwin(graphicsArr) { const res = { controllers: [], displays: [] }; try { - graphicsArr.forEach(function (item) { + graphicsArr.forEach( (item) => { // controllers - const bus = ((item.sppci_bus || '').indexOf('builtin') > -1 ? 'Built-In' : ((item.sppci_bus || '').indexOf('pcie') > -1 ? 'PCIe' : '')); - const vram = (parseInt((item.spdisplays_vram || ''), 10) || 0) * (((item.spdisplays_vram || '').indexOf('GB') > -1) ? 1024 : 1); - const vramDyn = (parseInt((item.spdisplays_vram_shared || ''), 10) || 0) * (((item.spdisplays_vram_shared || '').indexOf('GB') > -1) ? 1024 : 1); + const bus = (item.sppci_bus || '').indexOf('builtin') > -1 ? 'Built-In' : (item.sppci_bus || '').indexOf('pcie') > -1 ? 'PCIe' : ''; + const vram = (parseInt(item.spdisplays_vram || '', 10) || 0) * ((item.spdisplays_vram || '').indexOf('GB') > -1 ? 1024 : 1); + const vramDyn = (parseInt(item.spdisplays_vram_shared || '', 10) || 0) * ((item.spdisplays_vram_shared || '').indexOf('GB') > -1 ? 1024 : 1); let metalVersion = getMetalVersion(item.spdisplays_metal || item.spdisplays_metalfamily || ''); res.controllers.push({ vendor: getVendorFromModel(item.spdisplays_vendor || '') || item.spdisplays_vendor || '', @@ -155,14 +161,14 @@ function graphics(callback) { vram: vram || vramDyn || null, deviceId: item['spdisplays_device-id'] || '', vendorId: item['spdisplays_vendor-id'] || vendorToId((item['spdisplays_vendor'] || '') + (item.sppci_model || '')), - external: (item.sppci_device_type === 'spdisplays_egpu'), + external: item.sppci_device_type === 'spdisplays_egpu', cores: item['sppci_cores'] || null, metalVersion }); // displays if (item.spdisplays_ndrvs && item.spdisplays_ndrvs.length) { - item.spdisplays_ndrvs.forEach(function (displayItem) { + item.spdisplays_ndrvs.forEach( (displayItem) => { const connectionType = displayItem['spdisplays_connection_type'] || ''; const currentResolutionParts = (displayItem['_spdisplays_resolution'] || '').split('@'); const currentResolution = currentResolutionParts[0].split('x'); @@ -178,18 +184,17 @@ function graphics(callback) { displayId: displayItem['_spdisplays_displayID'] || null, main: displayItem['spdisplays_main'] ? displayItem['spdisplays_main'] === 'spdisplays_yes' : false, builtin: (displayItem['spdisplays_display_type'] || '').indexOf('built-in') > -1, - connection: ((connectionType.indexOf('_internal') > -1) ? 'Internal' : ((connectionType.indexOf('_displayport') > -1) ? 'Display Port' : ((connectionType.indexOf('_hdmi') > -1) ? 'HDMI' : null))), + connection: connectionType.indexOf('_internal') > -1 ? 'Internal' : connectionType.indexOf('_displayport') > -1 ? 'Display Port' : connectionType.indexOf('_hdmi') > -1 ? 'HDMI' : null, sizeX: null, sizeY: null, - pixelDepth: (pixelDepthString === 'CGSThirtyBitColor' ? 30 : (pixelDepthString === 'CGSThirtytwoBitColor' ? 32 : (pixelDepthString === 'CGSTwentyfourBitColor' ? 24 : null))), + pixelDepth: pixelDepthString === 'CGSThirtyBitColor' ? 30 : pixelDepthString === 'CGSThirtytwoBitColor' ? 32 : pixelDepthString === 'CGSTwentyfourBitColor' ? 24 : null, resolutionX: pixelParts.length > 1 ? parseInt(pixelParts[0], 10) : null, resolutionY: pixelParts.length > 1 ? parseInt(pixelParts[1], 10) : null, currentResX: currentResolution.length > 1 ? parseInt(currentResolution[0], 10) : null, currentResY: currentResolution.length > 1 ? parseInt(currentResolution[1], 10) : null, positionX: 0, positionY: 0, - currentRefreshRate: currentResolutionParts.length > 1 ? parseInt(currentResolutionParts[1], 10) : null, - + currentRefreshRate: currentResolutionParts.length > 1 ? parseInt(currentResolutionParts[1], 10) : null }); }); } @@ -229,22 +234,26 @@ function graphics(callback) { let i = 1; lines.forEach((line) => { let subsystem = ''; - if (i < lines.length && lines[i]) { // get next line; + if (i < lines.length && lines[i]) { + // get next line; subsystem = lines[i]; if (subsystem.indexOf(':') > 0) { subsystem = subsystem.split(':')[1]; } } if ('' !== line.trim()) { - if (' ' !== line[0] && '\t' !== line[0]) { // first line of new entry - let isExternal = (pciIDs.indexOf(line.split(' ')[0]) >= 0); + if (' ' !== line[0] && '\t' !== line[0]) { + // first line of new entry + let isExternal = pciIDs.indexOf(line.split(' ')[0]) >= 0; let vgapos = line.toLowerCase().indexOf(' vga '); let _3dcontrollerpos = line.toLowerCase().indexOf('3d controller'); - if (vgapos !== -1 || _3dcontrollerpos !== -1) { // VGA + if (vgapos !== -1 || _3dcontrollerpos !== -1) { + // VGA if (_3dcontrollerpos !== -1 && vgapos === -1) { vgapos = _3dcontrollerpos; } - if (currentController.vendor || currentController.model || currentController.bus || currentController.vram !== null || currentController.vramDynamic) { // already a controller found + if (currentController.vendor || currentController.model || currentController.bus || currentController.vram !== null || currentController.vramDynamic) { + // already a controller found controllers.push(currentController); currentController = { vendor: '', @@ -252,7 +261,7 @@ function graphics(callback) { bus: '', busAddress: '', vram: null, - vramDynamic: false, + vramDynamic: false }; } @@ -268,28 +277,47 @@ function graphics(callback) { parts[1] = parts[1].trim(); if (parts[1].toLowerCase().indexOf('corporation') >= 0) { currentController.vendor = parts[1].substr(0, parts[1].toLowerCase().indexOf('corporation') + 11).trim(); - currentController.model = parts[1].substr(parts[1].toLowerCase().indexOf('corporation') + 11, 200).split('(')[0].trim(); - currentController.bus = (pciIDs.length > 0 && isExternal) ? 'PCIe' : 'Onboard'; + currentController.model = parts[1] + .substr(parts[1].toLowerCase().indexOf('corporation') + 11, 200) + .split('(')[0] + .trim(); + currentController.bus = pciIDs.length > 0 && isExternal ? 'PCIe' : 'Onboard'; currentController.vram = null; currentController.vramDynamic = false; } else if (parts[1].toLowerCase().indexOf(' inc.') >= 0) { if ((parts[1].match(/]/g) || []).length > 1) { currentController.vendor = parts[1].substr(0, parts[1].toLowerCase().indexOf(']') + 1).trim(); - currentController.model = parts[1].substr(parts[1].toLowerCase().indexOf(']') + 1, 200).trim().split('(')[0].trim(); + currentController.model = parts[1] + .substr(parts[1].toLowerCase().indexOf(']') + 1, 200) + .trim() + .split('(')[0] + .trim(); } else { currentController.vendor = parts[1].substr(0, parts[1].toLowerCase().indexOf(' inc.') + 5).trim(); - currentController.model = parts[1].substr(parts[1].toLowerCase().indexOf(' inc.') + 5, 200).trim().split('(')[0].trim(); + currentController.model = parts[1] + .substr(parts[1].toLowerCase().indexOf(' inc.') + 5, 200) + .trim() + .split('(')[0] + .trim(); } - currentController.bus = (pciIDs.length > 0 && isExternal) ? 'PCIe' : 'Onboard'; + currentController.bus = pciIDs.length > 0 && isExternal ? 'PCIe' : 'Onboard'; currentController.vram = null; currentController.vramDynamic = false; } else if (parts[1].toLowerCase().indexOf(' ltd.') >= 0) { if ((parts[1].match(/]/g) || []).length > 1) { currentController.vendor = parts[1].substr(0, parts[1].toLowerCase().indexOf(']') + 1).trim(); - currentController.model = parts[1].substr(parts[1].toLowerCase().indexOf(']') + 1, 200).trim().split('(')[0].trim(); + currentController.model = parts[1] + .substr(parts[1].toLowerCase().indexOf(']') + 1, 200) + .trim() + .split('(')[0] + .trim(); } else { currentController.vendor = parts[1].substr(0, parts[1].toLowerCase().indexOf(' ltd.') + 5).trim(); - currentController.model = parts[1].substr(parts[1].toLowerCase().indexOf(' ltd.') + 5, 200).trim().split('(')[0].trim(); + currentController.model = parts[1] + .substr(parts[1].toLowerCase().indexOf(' ltd.') + 5, 200) + .trim() + .split('(')[0] + .trim(); } } if (currentController.model && subsystem.indexOf(currentController.model) !== -1) { @@ -299,14 +327,16 @@ function graphics(callback) { } } } - } else { isGraphicsController = false; } } - if (isGraphicsController) { // within VGA details + if (isGraphicsController) { + // within VGA details let parts = line.split(':'); - if (parts.length > 1 && parts[0].replace(/ +/g, '').toLowerCase().indexOf('devicename') !== -1 && parts[1].toLowerCase().indexOf('onboard') !== -1) { currentController.bus = 'Onboard'; } + if (parts.length > 1 && parts[0].replace(/ +/g, '').toLowerCase().indexOf('devicename') !== -1 && parts[1].toLowerCase().indexOf('onboard') !== -1) { + currentController.bus = 'Onboard'; + } if (parts.length > 1 && parts[0].replace(/ +/g, '').toLowerCase().indexOf('region') !== -1 && parts[1].toLowerCase().indexOf('memory') !== -1) { let memparts = parts[1].split('='); if (memparts.length > 1) { @@ -318,10 +348,11 @@ function graphics(callback) { i++; }); - if (currentController.vendor || currentController.model || currentController.bus || currentController.busAddress || currentController.vram !== null || currentController.vramDynamic) { // already a controller found + if (currentController.vendor || currentController.model || currentController.bus || currentController.busAddress || currentController.vram !== null || currentController.vramDynamic) { + // already a controller found controllers.push(currentController); } - return (controllers); + return controllers; } function parseLinesLinuxClinfo(controllers, lines) { @@ -356,7 +387,7 @@ function graphics(callback) { } } if (busAddress) { - let controller = controllers.find(controller => controller.busAddress === busAddress); + let controller = controllers.find((controller) => controller.busAddress === busAddress); if (!controller) { controller = { vendor: '', @@ -391,22 +422,27 @@ function graphics(callback) { if (_windows) { try { - const basePath = util.WINDIR + '\\System32\\DriverStore\\FileRepository'; + const basePath = util.WINDIR + String.raw`\System32\DriverStore\FileRepository`; // find all directories that have an nvidia-smi.exe file - const candidateDirs = fs.readdirSync(basePath).filter(dir => { - return fs.readdirSync([basePath, dir].join('/')).includes('nvidia-smi.exe'); + + const candidateDirs = fs.readdirSync(basePath).filter((dir) => { + if (fs.statSync([basePath, dir].join('/')).isDirectory()) { + return fs.readdirSync([basePath, dir].join('/')).includes('nvidia-smi.exe'); + } else { + return false; + } }); // use the directory with the most recently created nvidia-smi.exe file const targetDir = candidateDirs.reduce((prevDir, currentDir) => { const previousNvidiaSmi = fs.statSync([basePath, prevDir, 'nvidia-smi.exe'].join('/')); const currentNvidiaSmi = fs.statSync([basePath, currentDir, 'nvidia-smi.exe'].join('/')); - return (previousNvidiaSmi.ctimeMs > currentNvidiaSmi.ctimeMs) ? prevDir : currentDir; + return previousNvidiaSmi.ctimeMs > currentNvidiaSmi.ctimeMs ? prevDir : currentDir; }); if (targetDir) { _nvidiaSmiPath = [basePath, targetDir, 'nvidia-smi.exe'].join('/'); } - } catch (e) { + } catch { util.noop(); } } else if (_linux) { @@ -419,7 +455,8 @@ function graphics(callback) { const nvidiaSmiExe = getNvidiaSmi(); options = options || util.execOptsWin; if (nvidiaSmiExe) { - const nvidiaSmiOpts = '--query-gpu=driver_version,pci.sub_device_id,name,pci.bus_id,fan.speed,memory.total,memory.used,memory.free,utilization.gpu,utilization.memory,temperature.gpu,temperature.memory,power.draw,power.limit,clocks.gr,clocks.mem --format=csv,noheader,nounits'; + const nvidiaSmiOpts = + '--query-gpu=driver_version,pci.sub_device_id,name,pci.bus_id,fan.speed,memory.total,memory.used,memory.free,utilization.gpu,utilization.memory,temperature.gpu,temperature.memory,power.draw,power.limit,clocks.gr,clocks.mem --format=csv,noheader,nounits'; const cmd = nvidiaSmiExe + ' ' + nvidiaSmiOpts + (_linux ? ' 2>/dev/null' : ''); if (_linux) { options.stdio = ['pipe', 'pipe', 'ignore']; @@ -428,7 +465,7 @@ function graphics(callback) { const sanitized = util.sanitizeShellString(cmd); const res = execSync(sanitized, options).toString(); return res; - } catch (e) { + } catch { util.noop(); } } @@ -436,7 +473,6 @@ function graphics(callback) { } function nvidiaDevices() { - function safeParseNumber(value) { if ([null, undefined].includes(value)) { return value; @@ -450,8 +486,8 @@ function graphics(callback) { } const gpus = stdout.split('\n').filter(Boolean); - let results = gpus.map(gpu => { - const splittedData = gpu.split(', ').map(value => value.includes('N/A') ? undefined : value); + let results = gpus.map((gpu) => { + const splittedData = gpu.split(', ').map((value) => (value.includes('N/A') ? undefined : value)); if (splittedData.length === 16) { return { driverVersion: splittedData[0], @@ -469,39 +505,69 @@ function graphics(callback) { powerDraw: safeParseNumber(splittedData[12]), powerLimit: safeParseNumber(splittedData[13]), clockCore: safeParseNumber(splittedData[14]), - clockMemory: safeParseNumber(splittedData[15]), + clockMemory: safeParseNumber(splittedData[15]) }; } else { return {}; } }); results = results.filter((item) => { - return ('pciBus' in item); + return 'pciBus' in item; }); return results; } function mergeControllerNvidia(controller, nvidia) { - if (nvidia.driverVersion) { controller.driverVersion = nvidia.driverVersion; } - if (nvidia.subDeviceId) { controller.subDeviceId = nvidia.subDeviceId; } - if (nvidia.name) { controller.name = nvidia.name; } - if (nvidia.pciBus) { controller.pciBus = nvidia.pciBus; } - if (nvidia.fanSpeed) { controller.fanSpeed = nvidia.fanSpeed; } + if (nvidia.driverVersion) { + controller.driverVersion = nvidia.driverVersion; + } + if (nvidia.subDeviceId) { + controller.subDeviceId = nvidia.subDeviceId; + } + if (nvidia.name) { + controller.name = nvidia.name; + } + if (nvidia.pciBus) { + controller.pciBus = nvidia.pciBus; + } + if (nvidia.fanSpeed) { + controller.fanSpeed = nvidia.fanSpeed; + } if (nvidia.memoryTotal) { controller.memoryTotal = nvidia.memoryTotal; controller.vram = nvidia.memoryTotal; controller.vramDynamic = false; } - if (nvidia.memoryUsed) { controller.memoryUsed = nvidia.memoryUsed; } - if (nvidia.memoryFree) { controller.memoryFree = nvidia.memoryFree; } - if (nvidia.utilizationGpu) { controller.utilizationGpu = nvidia.utilizationGpu; } - if (nvidia.utilizationMemory) { controller.utilizationMemory = nvidia.utilizationMemory; } - if (nvidia.temperatureGpu) { controller.temperatureGpu = nvidia.temperatureGpu; } - if (nvidia.temperatureMemory) { controller.temperatureMemory = nvidia.temperatureMemory; } - if (nvidia.powerDraw) { controller.powerDraw = nvidia.powerDraw; } - if (nvidia.powerLimit) { controller.powerLimit = nvidia.powerLimit; } - if (nvidia.clockCore) { controller.clockCore = nvidia.clockCore; } - if (nvidia.clockMemory) { controller.clockMemory = nvidia.clockMemory; } + if (nvidia.memoryUsed) { + controller.memoryUsed = nvidia.memoryUsed; + } + if (nvidia.memoryFree) { + controller.memoryFree = nvidia.memoryFree; + } + if (nvidia.utilizationGpu) { + controller.utilizationGpu = nvidia.utilizationGpu; + } + if (nvidia.utilizationMemory) { + controller.utilizationMemory = nvidia.utilizationMemory; + } + if (nvidia.temperatureGpu) { + controller.temperatureGpu = nvidia.temperatureGpu; + } + if (nvidia.temperatureMemory) { + controller.temperatureMemory = nvidia.temperatureMemory; + } + if (nvidia.powerDraw) { + controller.powerDraw = nvidia.powerDraw; + } + if (nvidia.powerLimit) { + controller.powerLimit = nvidia.powerLimit; + } + if (nvidia.clockCore) { + controller.clockCore = nvidia.clockCore; + } + if (nvidia.clockMemory) { + controller.clockMemory = nvidia.clockMemory; + } return controller; } @@ -514,7 +580,7 @@ function graphics(callback) { // --> pixeldepth (?) // --> sizex // --> sizey - let result = { + const result = { vendor: '', model: '', deviceName: '', @@ -559,11 +625,12 @@ function graphics(callback) { } try { if (model_raw.length > 2) { - result.model = model_raw.match(/.{1,2}/g).map(function (v) { - return String.fromCharCode(parseInt(v, 16)); - }).join(''); + result.model = model_raw + .match(/.{1,2}/g) + .map((v) => String.fromCharCode(parseInt(v, 16))) + .join(''); } - } catch (e) { + } catch { util.noop(); } } else { @@ -573,7 +640,7 @@ function graphics(callback) { } function parseLinesLinuxDisplays(lines, depth) { - let displays = []; + const displays = []; let currentDisplay = { vendor: '', model: '', @@ -596,10 +663,21 @@ function graphics(callback) { let is_current = false; let edid_raw = ''; let start = 0; - for (let i = 1; i < lines.length; i++) { // start with second line + for (let i = 1; i < lines.length; i++) { + // start with second line if ('' !== lines[i].trim()) { - if (' ' !== lines[i][0] && '\t' !== lines[i][0] && lines[i].toLowerCase().indexOf(' connected ') !== -1) { // first line of new entry - if (currentDisplay.model || currentDisplay.main || currentDisplay.builtin || currentDisplay.connection || currentDisplay.sizeX !== null || currentDisplay.pixelDepth !== null || currentDisplay.resolutionX !== null) { // push last display to array + if (' ' !== lines[i][0] && '\t' !== lines[i][0] && lines[i].toLowerCase().indexOf(' connected ') !== -1) { + // first line of new entry + if ( + currentDisplay.model || + currentDisplay.main || + currentDisplay.builtin || + currentDisplay.connection || + currentDisplay.sizeX !== null || + currentDisplay.pixelDepth !== null || + currentDisplay.resolutionX !== null + ) { + // push last display to array displays.push(currentDisplay); currentDisplay = { vendor: '', @@ -622,7 +700,7 @@ function graphics(callback) { let parts = lines[i].split(' '); currentDisplay.connection = parts[0]; currentDisplay.main = lines[i].toLowerCase().indexOf(' primary ') >= 0; - currentDisplay.builtin = (parts[0].toLowerCase().indexOf('edp') >= 0); + currentDisplay.builtin = parts[0].toLowerCase().indexOf('edp') >= 0; } // try to read EDID information @@ -666,7 +744,16 @@ function graphics(callback) { } // pushen displays - if (currentDisplay.model || currentDisplay.main || currentDisplay.builtin || currentDisplay.connection || currentDisplay.sizeX !== null || currentDisplay.pixelDepth !== null || currentDisplay.resolutionX !== null) { // still information there + if ( + currentDisplay.model || + currentDisplay.main || + currentDisplay.builtin || + currentDisplay.connection || + currentDisplay.sizeX !== null || + currentDisplay.pixelDepth !== null || + currentDisplay.resolutionX !== null + ) { + // still information there displays.push(currentDisplay); } return displays; @@ -680,8 +767,8 @@ function graphics(callback) { displays: [] }; if (_darwin) { - let cmd = 'system_profiler -xml -detailLevel full SPDisplaysDataType'; - exec(cmd, function (error, stdout) { + const cmd = 'system_profiler -xml -detailLevel full SPDisplaysDataType'; + exec(cmd, (error, stdout) => { if (!error) { try { const output = stdout.toString(); @@ -690,7 +777,10 @@ function graphics(callback) { util.noop(); } try { - stdout = execSync('defaults read /Library/Preferences/com.apple.windowserver.plist 2>/dev/null;defaults read /Library/Preferences/com.apple.windowserver.displays.plist 2>/dev/null; echo ""', { maxBuffer: 1024 * 20000 }); + stdout = execSync( + 'defaults read /Library/Preferences/com.apple.windowserver.plist 2>/dev/null;defaults read /Library/Preferences/com.apple.windowserver.displays.plist 2>/dev/null; echo ""', + { maxBuffer: 1024 * 20000 } + ); const output = (stdout || '').toString(); const obj = util.plistReader(output); if (obj['DisplayAnyUserSets'] && obj['DisplayAnyUserSets']['Configs'] && obj['DisplayAnyUserSets']['Configs'][0] && obj['DisplayAnyUserSets']['Configs'][0]['DisplayConfig']) { @@ -722,7 +812,7 @@ function graphics(callback) { i++; }); } - } catch (e) { + } catch { util.noop(); } } @@ -735,9 +825,9 @@ function graphics(callback) { if (_linux) { // Raspberry: https://elinux.org/RPI_vcgencmd_usage if (util.isRaspberry()) { - let cmd = 'fbset -s 2> /dev/null | grep \'mode "\' ; vcgencmd get_mem gpu 2> /dev/null; tvservice -s 2> /dev/null; tvservice -n 2> /dev/null;'; - exec(cmd, function (error, stdout) { - let lines = stdout.toString().split('\n'); + const cmd = "fbset -s 2> /dev/null | grep 'mode \"' ; vcgencmd get_mem gpu 2> /dev/null; tvservice -s 2> /dev/null; tvservice -n 2> /dev/null;"; + exec(cmd, (error, stdout) => { + const lines = stdout.toString().split('\n'); if (lines.length > 3 && lines[0].indexOf('mode "') >= -1 && lines[2].indexOf('0x12000a') > -1) { const parts = lines[0].replace('mode', '').replace(/"/g, '').trim().split('x'); if (parts.length === 2) { @@ -769,44 +859,41 @@ function graphics(callback) { vramDynamic: true }); } - // if (callback) { - // callback(result); - // } - // resolve(result); }); } // } else { - let cmd = 'lspci -vvv 2>/dev/null'; - exec(cmd, function (error, stdout) { + const cmd = 'lspci -vvv 2>/dev/null'; + exec(cmd, (error, stdout) => { if (!error) { - let lines = stdout.toString().split('\n'); + const lines = stdout.toString().split('\n'); if (result.controllers.length === 0) { result.controllers = parseLinesLinuxControllers(lines); const nvidiaData = nvidiaDevices(); // needs to be rewritten ... using no spread operators - result.controllers = result.controllers.map((controller) => { // match by busAddress + result.controllers = result.controllers.map((controller) => { + // match by busAddress return mergeControllerNvidia(controller, nvidiaData.find((contr) => contr.pciBus.toLowerCase().endsWith(controller.busAddress.toLowerCase())) || {}); }); } } - let cmd = 'clinfo --raw'; - exec(cmd, function (error, stdout) { + const cmd = 'clinfo --raw'; + exec(cmd, (error, stdout) => { if (!error) { - let lines = stdout.toString().split('\n'); + const lines = stdout.toString().split('\n'); result.controllers = parseLinesLinuxClinfo(result.controllers, lines); } - let cmd = 'xdpyinfo 2>/dev/null | grep \'depth of root window\' | awk \'{ print $5 }\''; - exec(cmd, function (error, stdout) { + const cmd = "xdpyinfo 2>/dev/null | grep 'depth of root window' | awk '{ print $5 }'"; + exec(cmd, (error, stdout) => { let depth = 0; if (!error) { - let lines = stdout.toString().split('\n'); + const lines = stdout.toString().split('\n'); depth = parseInt(lines[0]) || 0; } - let cmd = 'xrandr --verbose 2>/dev/null'; - exec(cmd, function (error, stdout) { + const cmd = 'xrandr --verbose 2>/dev/null'; + exec(cmd, (error, stdout) => { if (!error) { - let lines = stdout.toString().split('\n'); + const lines = stdout.toString().split('\n'); result.displays = parseLinesLinuxDisplays(lines, depth); } if (callback) { @@ -820,124 +907,142 @@ function graphics(callback) { // } } if (_freebsd || _openbsd || _netbsd) { - if (callback) { callback(null); } + if (callback) { + callback(null); + } resolve(null); } if (_sunos) { - if (callback) { callback(null); } + if (callback) { + callback(null); + } resolve(null); } if (_windows) { - // https://blogs.technet.microsoft.com/heyscriptingguy/2013/10/03/use-powershell-to-discover-multi-monitor-information/ // https://devblogs.microsoft.com/scripting/use-powershell-to-discover-multi-monitor-information/ try { 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( + '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 -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 -notmatch 0 | foreach {[char]$_}) -join "") + "|" + (($_.ProductCodeID -notmatch 0 | foreach {[char]$_}) -join "") + "|" + (($_.UserFriendlyName -notmatch 0 | foreach {[char]$_}) -join "") + "|" + (($_.SerialNumberID -notmatch 0 | foreach {[char]$_}) -join "") + "|" + $_.InstanceName}' + ) + ); 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/); - result.controllers = parseLinesWindowsControllers(csections, vsections); - result.controllers = result.controllers.map((controller) => { // match by subDeviceId - if (controller.vendor.toLowerCase() === 'nvidia') { - return mergeControllerNvidia(controller, nvidiaData.find(device => { - let windowsSubDeviceId = (controller.subDeviceId || '').toLowerCase(); - const nvidiaSubDeviceIdParts = device.subDeviceId.split('x'); - let nvidiaSubDeviceId = nvidiaSubDeviceIdParts.length > 1 ? nvidiaSubDeviceIdParts[1].toLowerCase() : nvidiaSubDeviceIdParts[0].toLowerCase(); - const lengthDifference = Math.abs(windowsSubDeviceId.length - nvidiaSubDeviceId.length); - if (windowsSubDeviceId.length > nvidiaSubDeviceId.length) { - for (let i = 0; i < lengthDifference; i++) { - nvidiaSubDeviceId = '0' + nvidiaSubDeviceId; - } - } else if (windowsSubDeviceId.length < nvidiaSubDeviceId.length) { - for (let i = 0; i < lengthDifference; i++) { - windowsSubDeviceId = '0' + windowsSubDeviceId; - } + Promise.all(workload) + .then((data) => { + // controller + vram + 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') { + return mergeControllerNvidia( + controller, + nvidiaData.find((device) => { + let windowsSubDeviceId = (controller.subDeviceId || '').toLowerCase(); + const nvidiaSubDeviceIdParts = device.subDeviceId.split('x'); + let nvidiaSubDeviceId = nvidiaSubDeviceIdParts.length > 1 ? nvidiaSubDeviceIdParts[1].toLowerCase() : nvidiaSubDeviceIdParts[0].toLowerCase(); + const lengthDifference = Math.abs(windowsSubDeviceId.length - nvidiaSubDeviceId.length); + if (windowsSubDeviceId.length > nvidiaSubDeviceId.length) { + for (let i = 0; i < lengthDifference; i++) { + nvidiaSubDeviceId = '0' + nvidiaSubDeviceId; + } + } else if (windowsSubDeviceId.length < nvidiaSubDeviceId.length) { + for (let i = 0; i < lengthDifference; i++) { + windowsSubDeviceId = '0' + windowsSubDeviceId; + } + } + return windowsSubDeviceId === nvidiaSubDeviceId; + }) || {} + ); + } else { + return controller; + } + }); + + // displays + const 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) + const msections = data[3].replace(/\r/g, '').split('Active '); + msections.shift(); + + // forms.screens (powershell) + const ssections = data[4].replace(/\r/g, '').split('BitsPerPixel '); + ssections.shift(); + + // connection params (powershell) - video type + const 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/); + const 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; } - return windowsSubDeviceId === nvidiaSubDeviceId; - }) || {}); - } else { - return controller; - } - }); - - // 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; + } + } + if (_pixelDepth) { + result.displays[0].pixelDepth = _pixelDepth; } } - if (_resolutionY) { - result.displays[0].resolutionY = _resolutionY; - if (result.displays[0].currentResY === 0) { - result.displays[0].currentResY = _resolutionY; + result.displays = result.displays.map((element) => { + if (_refreshRate && !element.currentRefreshRate) { + element.currentRefreshRate = _refreshRate; } - } - if (_pixelDepth) { - result.displays[0].pixelDepth = _pixelDepth; - } - } - result.displays = result.displays.map(element => { - if (_refreshRate && !element.currentRefreshRate) { - element.currentRefreshRate = _refreshRate; - } - return element; - }); + return element; + }); - if (callback) { - callback(result); - } - resolve(result); - }) + if (callback) { + callback(result); + } + resolve(result); + }) .catch(() => { if (callback) { callback(result); @@ -945,7 +1050,9 @@ function graphics(callback) { resolve(result); }); } catch (e) { - if (callback) { callback(result); } + if (callback) { + callback(result); + } resolve(result); } } @@ -976,12 +1083,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) { @@ -1031,7 +1138,7 @@ function graphics(callback) { model: util.getValue(lines, 'name', ':'), bus: util.getValue(lines, 'PNPDeviceID', ':').startsWith('PCI') ? 'PCI' : '', vram: (memorySize == null ? util.toInt(util.getValue(lines, 'AdapterRAM', ':')) : memorySize) / 1024 / 1024, - vramDynamic: (util.getValue(lines, 'VideoMemoryType', ':') === '2'), + vramDynamic: util.getValue(lines, 'VideoMemoryType', ':') === '2', subDeviceId }); _resolutionX = util.toInt(util.getValue(lines, 'CurrentHorizontalResolution', ':')) || _resolutionX; @@ -1045,14 +1152,14 @@ function graphics(callback) { } function parseLinesWindowsDisplaysPowershell(ssections, msections, dsections, tsections, isections) { - let displays = []; + const displays = []; let vendor = ''; let model = ''; let deviceID = ''; let resolutionX = 0; let resolutionY = 0; if (dsections && dsections.length) { - let linesDisplay = dsections[0].split('\n'); + const linesDisplay = dsections[0].split('\n'); vendor = util.getValue(linesDisplay, 'MonitorManufacturer', ':'); model = util.getValue(linesDisplay, 'Name', ':'); deviceID = util.getValue(linesDisplay, 'PNPDeviceID', ':').replace(/&/g, '&').toLowerCase(); @@ -1068,10 +1175,10 @@ function graphics(callback) { if (tsections.length === 0 || tsections[i] === undefined) { tsections[i] = 'Unknown'; } - let linesScreen = ssections[i].split('\n'); - let linesMonitor = msections[i].split('\n'); + const linesScreen = ssections[i].split('\n'); + const linesMonitor = msections[i].split('\n'); - let linesConnection = tsections[i].split('\n'); + const 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'); @@ -1082,7 +1189,7 @@ function graphics(callback) { const deviceName = util.getValue(linesScreen, 'DeviceName'); let displayVendor = ''; let displayModel = ''; - isections.forEach(element => { + isections.forEach((element) => { if (element.instanceId.toLowerCase().startsWith(instanceName) && vendor.startsWith('(') && model.startsWith('PnP')) { displayVendor = element.vendor; displayModel = element.model; @@ -1103,7 +1210,7 @@ function graphics(callback) { 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', ':')), + positionY: util.toInt(util.getValue(bounds, 'Y', ':')) }); } }