123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062 |
- '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;
|