From 09775cd5bdaa08bf8d8a6a537b43ace58a9f38b6 Mon Sep 17 00:00:00 2001 From: Sebastian Hildebrandt Date: Tue, 14 Sep 2021 17:50:05 +0200 Subject: [PATCH] graphics() macOS new XML parser --- lib/filesystem.js | 6 +- lib/graphics.js | 250 +++++++++++++++++----------------------------- lib/util.js | 89 +++++++++++++++++ 3 files changed, 186 insertions(+), 159 deletions(-) diff --git a/lib/filesystem.js b/lib/filesystem.js index c4fd6b4..276ae77 100755 --- a/lib/filesystem.js +++ b/lib/filesystem.js @@ -989,7 +989,7 @@ function diskLayout(callback) { if (sizeStr) { let sizeValue = 0; if (sizeStr.indexOf('(') >= 0) { - sizeValue = parseInt(sizeStr.match(/\(([^)]+)\)/)[1].replace(/\./g, '').replace(/,/g, '')); + sizeValue = parseInt(sizeStr.match(/\(([^)]+)\)/)[1].replace(/\./g, '').replace(/,/g, '').replace(/ /g, '')); } if (!sizeValue) { sizeValue = parseInt(sizeStr); @@ -1037,7 +1037,7 @@ function diskLayout(callback) { if (sizeStr) { let sizeValue = 0; if (sizeStr.indexOf('(') >= 0) { - sizeValue = parseInt(sizeStr.match(/\(([^)]+)\)/)[1].replace(/\./g, '').replace(/,/g, '')); + sizeValue = parseInt(sizeStr.match(/\(([^)]+)\)/)[1].replace(/\./g, '').replace(/,/g, '').replace(/ /g, '')); } if (!sizeValue) { sizeValue = parseInt(sizeStr); @@ -1082,7 +1082,7 @@ function diskLayout(callback) { if (sizeStr) { let sizeValue = 0; if (sizeStr.indexOf('(') >= 0) { - sizeValue = parseInt(sizeStr.match(/\(([^)]+)\)/)[1].replace(/\./g, '').replace(/,/g, '')); + sizeValue = parseInt(sizeStr.match(/\(([^)]+)\)/)[1].replace(/\./g, '').replace(/,/g, '').replace(/ /g, '')); } if (!sizeValue) { sizeValue = parseInt(sizeStr); diff --git a/lib/graphics.js b/lib/graphics.js index 92e3f76..61c5a92 100644 --- a/lib/graphics.js +++ b/lib/graphics.js @@ -57,7 +57,7 @@ const videoTypes = { }; function getVendorFromModel(model) { - const diskManufacturers = [ + const manufacturers = [ { pattern: '^LG.+', manufacturer: 'LG' }, { pattern: '^BENQ.+', manufacturer: 'BenQ' }, { pattern: '^ASUS.+', manufacturer: 'Asus' }, @@ -76,12 +76,15 @@ function getVendorFromModel(model) { { 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(); - diskManufacturers.forEach((manufacturer) => { + manufacturers.forEach((manufacturer) => { const re = RegExp(manufacturer.pattern); if (re.test(model)) { result = manufacturer.manufacturer; } }); @@ -89,166 +92,97 @@ function getVendorFromModel(model) { return result; } +function getVendorFromId(id) { + const vendors = { + '610': 'Apple', + '1e6d': 'LG', + '10ac': 'DELL', + '4dd9': 'Sony', + '38a3': 'NEC', + }; + return vendors[id] || ''; +} + +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', + }; + return families[id] || ''; +} + function graphics(callback) { - function parseLinesDarwin(lines) { - let starts = []; - let level = -1; - let lastlevel = -1; - let controllers = []; - let displays = []; - let currentController = { - vendor: '', - model: '', - bus: '', - vram: null, - vramDynamic: false + function parseLinesDarwin(graphicsArr) { + const res = { + controllers: [], + 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 - }; - for (let i = 0; i < lines.length; i++) { - if ('' !== lines[i].trim()) { - let start = lines[i].search(/\S|$/); - if (-1 === starts.indexOf(start)) { - starts.push(start); - } - level = starts.indexOf(start); - if (level < lastlevel) { - if (Object.keys(currentController).length > 0 && (currentController.vendor || currentController.model)) {// just changed to Displays - controllers.push(currentController); - currentController = { - vendor: '', - model: '', - bus: '', - vram: null, - vramDynamic: false - }; - } - if (Object.keys(currentDisplay).length > 0) {// just changed to Displays - if (currentDisplay.resolutionX && currentDisplay.resolutionY) { displays.push(currentDisplay); } - currentDisplay = { - vendor: '', - model: '', - deviceName: '', - main: false, - builtin: false, - connection: '', + try { + graphicsArr.forEach(function (item) { + // controllers + const bus = ((item.sppci_bus || '').indexOf('builtin') ? 'Built-In' : ((item.sppci_bus || '').indexOf('pcie') ? 'PCIE' : '')); + const vram = (parseInt((item._spdisplays_vram || ''), 10) || 0) * (((item._spdisplays_vram || '').indexOf('GB') > -1) ? 1024 : 1); + const vramDyn = (parseInt((item.spdisplays_vram_shares || ''), 10) || 0) * (((item.spdisplays_vram_shares || '').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 || 0, + deviceId: item['spdisplays_device-id'] || '', + vendorId: item['spdisplays_vendor-id'] || '', + 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' : true, + builtin: (displayItem['spdisplays_display_type'] || '').indexOf('built-in') >= 0, + connection: ((connectionType.indexOf('_internal')) ? 'Internal' : ((connectionType.indexOf('_displayport')) ? 'Display Port' : ((connectionType.indexOf('_hdmi')) ? 'HDMI' : ''))), sizeX: null, sizeY: null, - pixelDepth: null, - resolutionX: null, - resolutionY: null, - currentResX: null, - currentResY: null, + pixelDepth: (pixelDepthString === 'CGSThirtyBitColor' ? 30 : (pixelDepthString === 'CGSThirtytwoBitColor' ? 32 : (pixelDepthString === 'CGSTwentyfourBitColor' ? 24 : ''))), + 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: null - }; - } + currentRefreshRate: currentResolutionParts.length > 1 ? parseInt(currentResolutionParts[1], 10) : null, + + }); + }); } - lastlevel = level; - let parts = lines[i].split(':'); - if (2 === level) { // grafics controller level - if (parts.length > 1 && parts[0].replace(/ +/g, '').toLowerCase().indexOf('chipsetmodel') !== -1) { currentController.model = parts[1].trim(); } - if (parts.length > 1 && parts[0].replace(/ +/g, '').toLowerCase().indexOf('vendor') !== -1) { currentController.vendor = parts[1].split('(')[0].trim(); } - if (parts.length > 1 && parts[0].replace(/ +/g, '').toLowerCase().indexOf('vram(total)') !== -1) { - currentController.vram = parseInt(parts[1]); // in MB - if (parts[1].toLowerCase().indexOf('gb') !== -1) { - currentController.vram = currentController.vram * 1024; - } - currentController.vramDynamic = false; - } - if (parts.length > 1 && parts[0].replace(/ +/g, '').toLowerCase().indexOf('vram(dynamic,max)') !== -1) { - currentController.vram = parseInt(parts[1]); // in MB - if (parts[1].toLowerCase().indexOf('gb') !== -1) { - currentController.vram = currentController.vram * 1024; - } - currentController.vramDynamic = true; - } - if (parts.length > 1 && parts[0].replace(/ +/g, '').toLowerCase().indexOf('bus') !== -1) { - currentController.bus = parts[1].trim(); - if (currentController.bus.toLowerCase() === 'built-in') { - currentController.vramDynamic = true; - } - } - } - if (3 === level) { // display controller level - if (parts.length > 1 && '' === parts[1]) { - currentDisplay.vendor = getVendorFromModel(parts[0].trim()); - currentDisplay.model = parts[0].trim(); - currentDisplay.main = false; - currentDisplay.builtin = false; - currentDisplay.connection = ''; - currentDisplay.sizeX = null; - currentDisplay.sizeY = null; - currentDisplay.positionX = 0; - currentDisplay.positionY = 0; - currentDisplay.pixelDepth = null; - } - } - if (4 === level) { // display controller details level - if (parts.length > 1 && parts[0].replace(/ +/g, '').toLowerCase().indexOf('resolution') !== -1) { - let resolution = parts[1].split('x'); - if (resolution.length > 1) { - let xpart = resolution[0]; - if (xpart.indexOf('(') !== -1) { - xpart = xpart.split('(').slice(-1)[0]; - } - let ypart = resolution[1]; - if (ypart.indexOf(')') !== -1) { - ypart = ypart.split(')')[0]; - } - currentDisplay.resolutionX = parseInt(xpart) || 0; - currentDisplay.resolutionY = parseInt(ypart) || 0; - currentDisplay.currentResX = currentDisplay.resolutionX; - currentDisplay.currentResY = currentDisplay.resolutionY; - } - } - if (parts.length > 1 && parts[0].replace(/ +/g, '').toLowerCase().indexOf('pixeldepth') !== -1) { currentDisplay.pixelDepth = parseInt(parts[1]); } // in BIT - if (parts.length > 1 && parts[0].replace(/ +/g, '').toLowerCase().indexOf('framebufferdepth') !== -1) { currentDisplay.pixelDepth = parseInt(parts[1]); } // in BIT - if (parts.length > 1 && parts[0].replace(/ +/g, '').toLowerCase().indexOf('maindisplay') !== -1 && parts[1].replace(/ +/g, '').toLowerCase() === 'yes') { currentDisplay.main = true; } - if (parts.length > 1 && parts[0].replace(/ +/g, '').toLowerCase().indexOf('built-in') !== -1 && parts[1].replace(/ +/g, '').toLowerCase() === 'yes') { - currentDisplay.vendor = 'Apple'; - currentDisplay.builtin = true; - currentDisplay.connection = ''; - } - if (parts.length > 1 && parts[0].replace(/ +/g, '').toLowerCase().indexOf('connectiontype') !== -1) { - currentDisplay.builtin = false; - currentDisplay.connection = parts[1].trim(); - if (currentDisplay.connection === 'Internal') { - currentDisplay.vendor = 'Apple'; - currentDisplay.builtin = true; - } - } - } - } + }); + return res; + } catch (e) { + return res; } - if (Object.keys(currentController).length > 0 && (currentController.vendor || currentController.model)) {// just changed to Displays - controllers.push(currentController); - } - if (Object.keys(currentDisplay).length > 0) {// just changed to Displays - if (currentDisplay.resolutionX && currentDisplay.resolutionY) { displays.push(currentDisplay); } - } - return ({ - controllers: controllers, - displays: displays - }); } function parseLinesLinuxControllers(lines) { @@ -706,11 +640,15 @@ function graphics(callback) { displays: [] }; if (_darwin) { - let cmd = 'system_profiler SPDisplaysDataType'; + let cmd = 'system_profiler -xml -detailLevel full SPDisplaysDataType'; exec(cmd, function (error, stdout) { if (!error) { - let lines = stdout.toString().split('\n'); - result = parseLinesDarwin(lines); + try { + let output = stdout.toString(); + result = parseLinesDarwin(util.plistParser(output)[0]._items); + } catch (e) { + util.noop(); + } } if (callback) { callback(result); diff --git a/lib/util.js b/lib/util.js index 3c48ef7..33aa174 100644 --- a/lib/util.js +++ b/lib/util.js @@ -985,6 +985,94 @@ function linuxVersion() { return result; } +function plistParser(xmlStr) { + const tags = ['array', 'dict', 'key', 'string', 'integer', 'date', 'real', 'data']; + + function getNextTagPos() { + let result = { + pos: 999999, + tag: '' + }; + tags.forEach((tag) => { + const ii = xmlStr.indexOf(`<${tag}>`); + if (ii !== -1 && ii < result.pos) { + result = { + pos: ii, + tag + }; + } + }); + return result; + } + + function getNextClosingTagPos(tag) { + return xmlStr.indexOf(``); + } + + function parseXmlTree(isArray, closingTag) { + // start parsing + let obj = {}; + let arr = []; + let cpos = getNextTagPos(); + let key = ''; + let valueStr = null; + + while (cpos.pos >= 0 && cpos.tag) { + let nextTagPos = getNextTagPos(); + // let nextClosePos = getNextClosingTagPos(cpos.tag); + let nextClosePosBlock = closingTag ? getNextClosingTagPos(closingTag) : 999999; + if (nextClosePosBlock <= nextTagPos.pos) { + return (isArray ? arr : obj); + } + xmlStr = xmlStr.substring((cpos.pos + cpos.tag.length + 2)); + if (cpos.tag === 'array') { + const res = parseXmlTree(true, cpos.tag); + if (key) { + obj[key] = res; + } else { + obj = res; + } + } else if (cpos.tag === 'dict') { + const res = parseXmlTree(false, cpos.tag); + if (!isArray) { + obj[key] = res; + } else { + arr.push(res); + } + xmlStr = xmlStr.substring((cpos.pos + cpos.tag.length + 3)); + } else { + let nextTagPos = getNextTagPos(); + let nextClosePos = getNextClosingTagPos(cpos.tag); + nextClosePosBlock = closingTag ? getNextClosingTagPos(closingTag) : 999999; + if (nextClosePos < nextTagPos.pos) { + if (cpos.tag === 'key') { + key = xmlStr.substring(0, nextClosePos); + xmlStr = xmlStr.substring(key.length + cpos.tag.length + 3); // key done + } else { + valueStr = xmlStr.substring(0, nextClosePos).replace(/\t/g, ''); + if (cpos.tag === 'string') { if (!isArray) { obj[key] = valueStr; } else { arr.push(valueStr); } } + if (cpos.tag === 'integer') { if (!isArray) { obj[key] = parseInt(valueStr); } else { arr.push(parseInt(valueStr)); } } + if (cpos.tag === 'date') { if (!isArray) { obj[key] = valueStr; } else { arr.push(valueStr); } } + if (cpos.tag === 'data') { if (!isArray) { obj[key] = valueStr; } else { arr.push(valueStr); } } + if (cpos.tag === 'real') { if (!isArray) { obj[key] = parseFloat(valueStr); } else { arr.push(parseFloat(valueStr)); } } + + key = ''; + xmlStr = xmlStr.substring(valueStr.length + cpos.tag.length + 3); // property done + } + } + } + cpos = getNextTagPos(); + } + return (isArray ? arr : obj); + } + try { + const result = parseXmlTree(false, ''); + return result; + } catch (e) { + return {}; + } +} + function noop() { } exports.toInt = toInt; @@ -1020,6 +1108,7 @@ exports.promisify = promisify; exports.promisifySave = promisifySave; exports.smartMonToolsInstalled = smartMonToolsInstalled; exports.linuxVersion = linuxVersion; +exports.plistParser = plistParser; exports.stringReplace = stringReplace; exports.stringToLower = stringToLower; exports.stringToString = stringToString;