diff options
author | Minteck <contact@minteck.org> | 2022-07-05 13:40:01 +0200 |
---|---|---|
committer | Minteck <contact@minteck.org> | 2022-07-05 13:40:01 +0200 |
commit | b74e9708f0adcc5c80095e432e8051b01b6255d9 (patch) | |
tree | e4d2fec7ac3718f97966b10f7dc12a2b4d36d115 /node_modules/systeminformation/lib/graphics.js | |
download | strawberry-boot-mane.tar.gz strawberry-boot-mane.tar.bz2 strawberry-boot-mane.zip |
Diffstat (limited to 'node_modules/systeminformation/lib/graphics.js')
-rw-r--r-- | node_modules/systeminformation/lib/graphics.js | 1062 |
1 files changed, 1062 insertions, 0 deletions
diff --git a/node_modules/systeminformation/lib/graphics.js b/node_modules/systeminformation/lib/graphics.js new file mode 100644 index 0000000..c904e86 --- /dev/null +++ b/node_modules/systeminformation/lib/graphics.js @@ -0,0 +1,1062 @@ +'use strict'; +// @ts-check +// ================================================================================== +// graphics.js +// ---------------------------------------------------------------------------------- +// Description: System Information - library +// for Node.js +// Copyright: (c) 2014 - 2022 +// Author: Sebastian Hildebrandt +// ---------------------------------------------------------------------------------- +// License: MIT +// ================================================================================== +// 7. Graphics (controller, display) +// ---------------------------------------------------------------------------------- + +const fs = require('fs'); +const exec = require('child_process').exec; +const execSync = require('child_process').execSync; +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'); + +let _resolutionX = 0; +let _resolutionY = 0; +let _pixelDepth = 0; +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' +}; + +function getVendorFromModel(model) { + const manufacturers = [ + { pattern: '^LG.+', manufacturer: 'LG' }, + { pattern: '^BENQ.+', manufacturer: 'BenQ' }, + { pattern: '^ASUS.+', manufacturer: 'Asus' }, + { pattern: '^DELL.+', manufacturer: 'Dell' }, + { pattern: '^SAMSUNG.+', manufacturer: 'Samsung' }, + { pattern: '^VIEWSON.+', manufacturer: 'ViewSonic' }, + { pattern: '^SONY.+', manufacturer: 'Sony' }, + { pattern: '^ACER.+', manufacturer: 'Acer' }, + { pattern: '^AOC.+', manufacturer: 'AOC Monitors' }, + { pattern: '^HP.+', manufacturer: 'HP' }, + { pattern: '^EIZO.?', manufacturer: 'Eizo' }, + { pattern: '^PHILIPS.?', manufacturer: 'Philips' }, + { pattern: '^IIYAMA.?', manufacturer: 'Iiyama' }, + { pattern: '^SHARP.?', manufacturer: 'Sharp' }, + { pattern: '^NEC.?', manufacturer: 'NEC' }, + { pattern: '^LENOVO.?', manufacturer: 'Lenovo' }, + { pattern: 'COMPAQ.?', manufacturer: 'Compaq' }, + { pattern: 'APPLE.?', manufacturer: 'Apple' }, + { pattern: 'INTEL.?', manufacturer: 'Intel' }, + { pattern: 'AMD.?', manufacturer: 'AMD' }, + { pattern: 'NVIDIA.?', manufacturer: 'NVDIA' }, + ]; + + let result = ''; + if (model) { + model = model.toUpperCase(); + manufacturers.forEach((manufacturer) => { + const re = RegExp(manufacturer.pattern); + if (re.test(model)) { result = manufacturer.manufacturer; } + }); + } + return result; +} + +function getVendorFromId(id) { + const vendors = { + '610': 'Apple', + '1e6d': 'LG', + '10ac': 'DELL', + '4dd9': 'Sony', + '38a3': 'NEC', + }; + return vendors[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'; } + + 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' + }; + return families[id] || ''; +} + +function graphics(callback) { + + function parseLinesDarwin(graphicsArr) { + const res = { + controllers: [], + displays: [] + }; + try { + graphicsArr.forEach(function (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); + let metalVersion = getMetalVersion(item.spdisplays_metal || item.spdisplays_metalfamily || ''); + res.controllers.push({ + vendor: getVendorFromModel(item.spdisplays_vendor || '') || item.spdisplays_vendor || '', + model: item.sppci_model || '', + bus, + vramDynamic: bus === 'Built-In', + 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'), + cores: item['sppci_cores'] || null, + metalVersion + }); + + // displays + if (item.spdisplays_ndrvs && item.spdisplays_ndrvs.length) { + item.spdisplays_ndrvs.forEach(function (displayItem) { + const connectionType = displayItem['spdisplays_connection_type'] || ''; + const currentResolutionParts = (displayItem['_spdisplays_resolution'] || '').split('@'); + const currentResolution = currentResolutionParts[0].split('x'); + const pixelParts = (displayItem['_spdisplays_pixels'] || '').split('x'); + const pixelDepthString = displayItem['spdisplays_depth'] || ''; + const serial = displayItem['_spdisplays_display-serial-number'] || displayItem['_spdisplays_display-serial-number2'] || null; + res.displays.push({ + vendor: getVendorFromId(displayItem['_spdisplays_display-vendor-id'] || '') || getVendorFromModel(displayItem['_name'] || ''), + vendorId: displayItem['_spdisplays_display-vendor-id'] || '', + model: displayItem['_name'] || '', + productionYear: displayItem['_spdisplays_display-year'] || null, + serial: serial !== '0' ? serial : null, + 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))), + sizeX: null, + sizeY: 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, + + }); + }); + } + }); + return res; + } catch (e) { + return res; + } + } + + function parseLinesLinuxControllers(lines) { + let controllers = []; + let currentController = { + vendor: '', + model: '', + bus: '', + busAddress: '', + vram: null, + vramDynamic: false, + pciID: '' + }; + let isGraphicsController = false; + // PCI bus IDs + let pciIDs = []; + try { + pciIDs = execSync('export LC_ALL=C; dmidecode -t 9 2>/dev/null; unset LC_ALL | grep "Bus Address: "').toString().split('\n'); + for (let i = 0; i < pciIDs.length; i++) { + pciIDs[i] = pciIDs[i].replace('Bus Address:', '').replace('0000:', '').trim(); + } + pciIDs = pciIDs.filter(function (el) { + return el != null && el; + }); + } catch (e) { + util.noop(); + } + for (let i = 0; i < lines.length; i++) { + if ('' !== lines[i].trim()) { + if (' ' !== lines[i][0] && '\t' !== lines[i][0]) { // first line of new entry + let isExternal = (pciIDs.indexOf(lines[i].split(' ')[0]) >= 0); + let vgapos = lines[i].toLowerCase().indexOf(' vga '); + let _3dcontrollerpos = lines[i].toLowerCase().indexOf('3d controller'); + 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 + controllers.push(currentController); + currentController = { + vendor: '', + model: '', + bus: '', + busAddress: '', + vram: null, + vramDynamic: false, + }; + } + + const pciIDCandidate = lines[i].split(' ')[0]; + if (/[\da-fA-F]{2}:[\da-fA-F]{2}\.[\da-fA-F]/.test(pciIDCandidate)) { + currentController.busAddress = pciIDCandidate; + } + isGraphicsController = true; + let endpos = lines[i].search(/\[[0-9a-f]{4}:[0-9a-f]{4}]|$/); + let parts = lines[i].substr(vgapos, endpos - vgapos).split(':'); + currentController.busAddress = lines[i].substr(0, vgapos).trim(); + if (parts.length > 1) { + 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).trim().split('(')[0]; + 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(new RegExp(']', '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(); + } 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.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(new RegExp(']', '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(); + } 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(); + } + } + } + + } else { + isGraphicsController = false; + } + } + if (isGraphicsController) { // within VGA details + let parts = lines[i].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('region') !== -1 && parts[1].toLowerCase().indexOf('memory') !== -1) { + let memparts = parts[1].split('='); + if (memparts.length > 1) { + currentController.vram = parseInt(memparts[1]); + } + } + } + } + } + if (currentController.vendor || currentController.model || currentController.bus || currentController.busAddress || currentController.vram !== null || currentController.vramDynamic) { // already a controller found + controllers.push(currentController); + } + return (controllers); + } + + function parseLinesLinuxClinfo(controllers, lines) { + const fieldPattern = /\[([^\]]+)\]\s+(\w+)\s+(.*)/; + const devices = lines.reduce((devices, line) => { + const field = fieldPattern.exec(line.trim()); + if (field) { + if (!devices[field[1]]) { + devices[field[1]] = {}; + } + devices[field[1]][field[2]] = field[3]; + } + return devices; + }, {}); + for (let deviceId in devices) { + const device = devices[deviceId]; + if (device['CL_DEVICE_TYPE'] === 'CL_DEVICE_TYPE_GPU') { + let busAddress; + if (device['CL_DEVICE_TOPOLOGY_AMD']) { + const bdf = device['CL_DEVICE_TOPOLOGY_AMD'].match(/[a-zA-Z0-9]+:\d+\.\d+/); + if (bdf) { + busAddress = bdf[0]; + } + } else if (device['CL_DEVICE_PCI_BUS_ID_NV'] && device['CL_DEVICE_PCI_SLOT_ID_NV']) { + const bus = parseInt(device['CL_DEVICE_PCI_BUS_ID_NV']); + const slot = parseInt(device['CL_DEVICE_PCI_SLOT_ID_NV']); + if (!isNaN(bus) && !isNaN(slot)) { + const b = bus & 0xff; + const d = (slot >> 3) & 0xff; + const f = slot & 0x07; + busAddress = `${b.toString().padStart(2, '0')}:${d.toString().padStart(2, '0')}.${f}`; + } + } + if (busAddress) { + let controller = controllers.find(controller => controller.busAddress === busAddress); + if (!controller) { + controller = { + vendor: '', + model: '', + bus: '', + busAddress, + vram: null, + vramDynamic: false + }; + controllers.push(controller); + } + controller.vendor = device['CL_DEVICE_VENDOR']; + if (device['CL_DEVICE_BOARD_NAME_AMD']) { + controller.model = device['CL_DEVICE_BOARD_NAME_AMD']; + } else { + controller.model = device['CL_DEVICE_NAME']; + } + const memory = parseInt(device['CL_DEVICE_GLOBAL_MEM_SIZE']); + if (!isNaN(memory)) { + controller.vram = Math.round(memory / 1024 / 1024); + } + } + } + } + return controllers; + } + + function getNvidiaSmi() { + if (_nvidiaSmiPath) { + return _nvidiaSmiPath; + } + + if (_windows) { + try { + const basePath = util.WINDIR + '\\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'); + }); + // 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; + }); + + if (targetDir) { + _nvidiaSmiPath = [basePath, targetDir, 'nvidia-smi.exe'].join('/'); + } + } catch (e) { + util.noop(); + } + } else if (_linux) { + _nvidiaSmiPath = 'nvidia-smi'; + } + return _nvidiaSmiPath; + } + + function nvidiaSmi(options) { + 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 cmd = nvidiaSmiExe + ' ' + nvidiaSmiOpts + (_linux ? ' 2>/dev/null' : ''); + try { + const res = execSync(cmd, options).toString(); + return res; + } catch (e) { + util.noop(); + } + } + return ''; + } + + function nvidiaDevices() { + + function safeParseNumber(value) { + if ([null, undefined].includes(value)) { + return value; + } + return parseFloat(value); + } + + const stdout = nvidiaSmi(); + if (!stdout) { + return []; + } + + const gpus = stdout.split('\n').filter(Boolean); + const results = gpus.map(gpu => { + const splittedData = gpu.split(', ').map(value => value.includes('N/A') ? undefined : value); + if (splittedData.length === 16) { + return { + driverVersion: splittedData[0], + subDeviceId: splittedData[1], + name: splittedData[2], + pciBus: splittedData[3], + fanSpeed: safeParseNumber(splittedData[4]), + memoryTotal: safeParseNumber(splittedData[5]), + memoryUsed: safeParseNumber(splittedData[6]), + memoryFree: safeParseNumber(splittedData[7]), + utilizationGpu: safeParseNumber(splittedData[8]), + utilizationMemory: safeParseNumber(splittedData[9]), + temperatureGpu: safeParseNumber(splittedData[10]), + temperatureMemory: safeParseNumber(splittedData[11]), + powerDraw: safeParseNumber(splittedData[12]), + powerLimit: safeParseNumber(splittedData[13]), + clockCore: safeParseNumber(splittedData[14]), + clockMemory: safeParseNumber(splittedData[15]), + }; + } + }); + + 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.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; } + return controller; + } + + function parseLinesLinuxEdid(edid) { + // parsen EDID + // --> model + // --> resolutionx + // --> resolutiony + // --> builtin = false + // --> pixeldepth (?) + // --> sizex + // --> sizey + let result = { + vendor: '', + model: '', + deviceName: '', + main: false, + builtin: false, + connection: '', + sizeX: null, + sizeY: null, + pixelDepth: null, + resolutionX: null, + resolutionY: null, + currentResX: null, + currentResY: null, + positionX: 0, + positionY: 0, + currentRefreshRate: null + }; + // find first "Detailed Timing Description" + let start = 108; + if (edid.substr(start, 6) === '000000') { + start += 36; + } + if (edid.substr(start, 6) === '000000') { + start += 36; + } + if (edid.substr(start, 6) === '000000') { + start += 36; + } + if (edid.substr(start, 6) === '000000') { + start += 36; + } + result.resolutionX = parseInt('0x0' + edid.substr(start + 8, 1) + edid.substr(start + 4, 2)); + result.resolutionY = parseInt('0x0' + edid.substr(start + 14, 1) + edid.substr(start + 10, 2)); + result.sizeX = parseInt('0x0' + edid.substr(start + 28, 1) + edid.substr(start + 24, 2)); + result.sizeY = parseInt('0x0' + edid.substr(start + 29, 1) + edid.substr(start + 26, 2)); + // monitor name + start = edid.indexOf('000000fc00'); // find first "Monitor Description Data" + if (start >= 0) { + let model_raw = edid.substr(start + 10, 26); + if (model_raw.indexOf('0a') !== -1) { + model_raw = model_raw.substr(0, model_raw.indexOf('0a')); + } + try { + if (model_raw.length > 2) { + result.model = model_raw.match(/.{1,2}/g).map(function (v) { + return String.fromCharCode(parseInt(v, 16)); + }).join(''); + } + } catch (e) { + util.noop(); + } + } else { + result.model = ''; + } + return result; + } + + function parseLinesLinuxDisplays(lines, depth) { + let displays = []; + let currentDisplay = { + vendor: '', + model: '', + deviceName: '', + main: false, + builtin: false, + connection: '', + sizeX: null, + sizeY: null, + pixelDepth: null, + resolutionX: null, + resolutionY: null, + currentResX: null, + currentResY: null, + positionX: 0, + positionY: 0, + currentRefreshRate: null + }; + let is_edid = false; + let is_current = false; + let edid_raw = ''; + let start = 0; + 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 + displays.push(currentDisplay); + currentDisplay = { + vendor: '', + model: '', + main: false, + builtin: false, + connection: '', + sizeX: null, + sizeY: null, + pixelDepth: null, + resolutionX: null, + resolutionY: null, + currentResX: null, + currentResY: null, + positionX: 0, + positionY: 0, + currentRefreshRate: null + }; + } + 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); + } + + // try to read EDID information + if (is_edid) { + if (lines[i].search(/\S|$/) > start) { + edid_raw += lines[i].toLowerCase().trim(); + } else { + // parsen EDID + let edid_decoded = parseLinesLinuxEdid(edid_raw); + currentDisplay.vendor = edid_decoded.vendor; + currentDisplay.model = edid_decoded.model; + currentDisplay.resolutionX = edid_decoded.resolutionX; + currentDisplay.resolutionY = edid_decoded.resolutionY; + currentDisplay.sizeX = edid_decoded.sizeX; + currentDisplay.sizeY = edid_decoded.sizeY; + currentDisplay.pixelDepth = depth; + is_edid = false; + } + } + if (lines[i].toLowerCase().indexOf('edid:') >= 0) { + is_edid = true; + start = lines[i].search(/\S|$/); + } + if (lines[i].toLowerCase().indexOf('*current') >= 0) { + const parts1 = lines[i].split('('); + if (parts1 && parts1.length > 1 && parts1[0].indexOf('x') >= 0) { + const resParts = parts1[0].trim().split('x'); + currentDisplay.currentResX = util.toInt(resParts[0]); + currentDisplay.currentResY = util.toInt(resParts[1]); + } + is_current = true; + } + if (is_current && lines[i].toLowerCase().indexOf('clock') >= 0 && lines[i].toLowerCase().indexOf('hz') >= 0 && lines[i].toLowerCase().indexOf('v: height') >= 0) { + const parts1 = lines[i].split('clock'); + if (parts1 && parts1.length > 1 && parts1[1].toLowerCase().indexOf('hz') >= 0) { + currentDisplay.currentRefreshRate = util.toInt(parts1[1]); + } + is_current = false; + } + } + } + + // pushen displays + 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; + } + + // function starts here + return new Promise((resolve) => { + process.nextTick(() => { + let result = { + controllers: [], + displays: [] + }; + if (_darwin) { + let cmd = 'system_profiler -xml -detailLevel full SPDisplaysDataType'; + exec(cmd, function (error, stdout) { + if (!error) { + try { + let output = stdout.toString(); + result = parseLinesDarwin(util.plistParser(output)[0]._items); + } catch (e) { + util.noop(); + } + } + if (callback) { + callback(result); + } + resolve(result); + }); + } + if (_linux) { + // Raspberry: https://elinux.org/RPI_vcgencmd_usage + if (util.isRaspberry() && util.isRaspbian()) { + let cmd = 'fbset -s | grep \'mode "\'; vcgencmd get_mem gpu; tvservice -s; tvservice -n;'; + exec(cmd, function (error, stdout) { + let 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) { + result.displays.push({ + vendor: '', + model: util.getValue(lines, 'device_name', '='), + main: true, + builtin: false, + connection: 'HDMI', + sizeX: null, + sizeY: null, + pixelDepth: null, + resolutionX: parseInt(parts[0], 10), + resolutionY: parseInt(parts[1], 10), + currentResX: null, + currentResY: null, + positionX: 0, + positionY: 0, + currentRefreshRate: null + }); + } + } + if (lines.length > 1 && stdout.toString().indexOf('gpu=') >= -1) { + result.controllers.push({ + vendor: 'Broadcom', + model: 'VideoCore IV', + bus: '', + vram: util.getValue(lines, 'gpu', '=').replace('M', ''), + vramDynamic: true + }); + } + if (callback) { + callback(result); + } + resolve(result); + }); + } else { + let cmd = 'lspci -vvv 2>/dev/null'; + exec(cmd, function (error, stdout) { + if (!error) { + let lines = stdout.toString().split('\n'); + result.controllers = parseLinesLinuxControllers(lines); + const nvidiaData = nvidiaDevices(); + // needs to be rewritten ... using no spread operators + 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) { + if (!error) { + let 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) { + let depth = 0; + if (!error) { + let lines = stdout.toString().split('\n'); + depth = parseInt(lines[0]) || 0; + } + let cmd = 'xrandr --verbose 2>/dev/null'; + exec(cmd, function (error, stdout) { + if (!error) { + let lines = stdout.toString().split('\n'); + result.displays = parseLinesLinuxDisplays(lines, depth); + } + if (callback) { + callback(result); + } + resolve(result); + }); + }); + }); + }); + } + } + if (_freebsd || _openbsd || _netbsd) { + if (callback) { callback(null); } + resolve(null); + } + if (_sunos) { + 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-WmiObject 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-WmiObject 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}')); + + 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; + } + } + 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 (_refreshRate && !result.displays[0].currentRefreshRate) { + result.displays[0].currentRefreshRate = _refreshRate; + } + } + + if (callback) { + callback(result); + } + resolve(result); + }) + .catch(() => { + if (callback) { + callback(result); + } + resolve(result); + }); + } catch (e) { + if (callback) { callback(result); } + resolve(result); + } + } + }); + }); + + function parseLinesWindowsControllers(sections, vections) { + const memorySizes = {}; + for (const i in vections) { + if ({}.hasOwnProperty.call(vections, i)) { + if (vections[i].trim() !== '') { + const lines = vections[i].trim().split('\n'); + const matchingDeviceId = util.getValue(lines, 'MatchingDeviceId').match(/PCI\\(VEN_[0-9A-F]{4})&(DEV_[0-9A-F]{4})(?:&(SUBSYS_[0-9A-F]{8}))?(?:&(REV_[0-9A-F]{2}))?/i); + if (matchingDeviceId) { + const quadWordmemorySize = parseInt(util.getValue(lines, 'HardwareInformation.qwMemorySize')); + if (!isNaN(quadWordmemorySize)) { + let deviceId = matchingDeviceId[1].toUpperCase() + '&' + matchingDeviceId[2].toUpperCase(); + if (matchingDeviceId[3]) { + deviceId += '&' + matchingDeviceId[3].toUpperCase(); + } + if (matchingDeviceId[4]) { + deviceId += '&' + matchingDeviceId[4].toUpperCase(); + } + memorySizes[deviceId] = quadWordmemorySize; + } + } + } + } + } + + let controllers = []; + for (let 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); + let subDeviceId = null; + let memorySize = null; + if (pnpDeviceId) { + subDeviceId = pnpDeviceId[3] || ''; + if (subDeviceId) { + subDeviceId = subDeviceId.split('_')[1]; + } + + // Match PCI device identifier (there's an order of increasing generality): + // https://docs.microsoft.com/en-us/windows-hardware/drivers/install/identifiers-for-pci-devices + + // PCI\VEN_v(4)&DEV_d(4)&SUBSYS_s(4)n(4)&REV_r(2) + if (memorySize == null && pnpDeviceId[3] && pnpDeviceId[4]) { + const deviceId = pnpDeviceId[1].toUpperCase() + '&' + pnpDeviceId[2].toUpperCase() + '&' + pnpDeviceId[3].toUpperCase() + '&' + pnpDeviceId[4].toUpperCase(); + if ({}.hasOwnProperty.call(memorySizes, deviceId)) { + memorySize = memorySizes[deviceId]; + } + } + + // PCI\VEN_v(4)&DEV_d(4)&SUBSYS_s(4)n(4) + if (memorySize == null && pnpDeviceId[3]) { + const deviceId = pnpDeviceId[1].toUpperCase() + '&' + pnpDeviceId[2].toUpperCase() + '&' + pnpDeviceId[3].toUpperCase(); + if ({}.hasOwnProperty.call(memorySizes, deviceId)) { + memorySize = memorySizes[deviceId]; + } + } + + // PCI\VEN_v(4)&DEV_d(4)&REV_r(2) + if (memorySize == null && pnpDeviceId[4]) { + const deviceId = pnpDeviceId[1].toUpperCase() + '&' + pnpDeviceId[2].toUpperCase() + '&' + pnpDeviceId[4].toUpperCase(); + if ({}.hasOwnProperty.call(memorySizes, deviceId)) { + memorySize = memorySizes[deviceId]; + } + } + + // PCI\VEN_v(4)&DEV_d(4) + if (memorySize == null) { + const deviceId = pnpDeviceId[1].toUpperCase() + '&' + pnpDeviceId[2].toUpperCase(); + if ({}.hasOwnProperty.call(memorySizes, deviceId)) { + memorySize = memorySizes[deviceId]; + } + } + } + + controllers.push({ + vendor: util.getValue(lines, 'AdapterCompatibility', ':'), + 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'), + subDeviceId + }); + _resolutionX = util.toInt(util.getValue(lines, 'CurrentHorizontalResolution', ':')) || _resolutionX; + _resolutionY = util.toInt(util.getValue(lines, 'CurrentVerticalResolution', ':')) || _resolutionY; + _refreshRate = util.toInt(util.getValue(lines, 'CurrentRefreshRate', ':')) || _refreshRate; + _pixelDepth = util.toInt(util.getValue(lines, 'CurrentBitsPerPixel', ':')) || _pixelDepth; + } + } + } + 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; |