graphics.js 44 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062
  1. 'use strict';
  2. // @ts-check
  3. // ==================================================================================
  4. // graphics.js
  5. // ----------------------------------------------------------------------------------
  6. // Description: System Information - library
  7. // for Node.js
  8. // Copyright: (c) 2014 - 2022
  9. // Author: Sebastian Hildebrandt
  10. // ----------------------------------------------------------------------------------
  11. // License: MIT
  12. // ==================================================================================
  13. // 7. Graphics (controller, display)
  14. // ----------------------------------------------------------------------------------
  15. const fs = require('fs');
  16. const exec = require('child_process').exec;
  17. const execSync = require('child_process').execSync;
  18. const util = require('./util');
  19. let _platform = process.platform;
  20. let _nvidiaSmiPath = '';
  21. const _linux = (_platform === 'linux' || _platform === 'android');
  22. const _darwin = (_platform === 'darwin');
  23. const _windows = (_platform === 'win32');
  24. const _freebsd = (_platform === 'freebsd');
  25. const _openbsd = (_platform === 'openbsd');
  26. const _netbsd = (_platform === 'netbsd');
  27. const _sunos = (_platform === 'sunos');
  28. let _resolutionX = 0;
  29. let _resolutionY = 0;
  30. let _pixelDepth = 0;
  31. let _refreshRate = 0;
  32. const videoTypes = {
  33. '-2': 'UNINITIALIZED',
  34. '-1': 'OTHER',
  35. '0': 'HD15',
  36. '1': 'SVIDEO',
  37. '2': 'Composite video',
  38. '3': 'Component video',
  39. '4': 'DVI',
  40. '5': 'HDMI',
  41. '6': 'LVDS',
  42. '8': 'D_JPN',
  43. '9': 'SDI',
  44. '10': 'DP',
  45. '11': 'DP embedded',
  46. '12': 'UDI',
  47. '13': 'UDI embedded',
  48. '14': 'SDTVDONGLE',
  49. '15': 'MIRACAST',
  50. '2147483648': 'INTERNAL'
  51. };
  52. function getVendorFromModel(model) {
  53. const manufacturers = [
  54. { pattern: '^LG.+', manufacturer: 'LG' },
  55. { pattern: '^BENQ.+', manufacturer: 'BenQ' },
  56. { pattern: '^ASUS.+', manufacturer: 'Asus' },
  57. { pattern: '^DELL.+', manufacturer: 'Dell' },
  58. { pattern: '^SAMSUNG.+', manufacturer: 'Samsung' },
  59. { pattern: '^VIEWSON.+', manufacturer: 'ViewSonic' },
  60. { pattern: '^SONY.+', manufacturer: 'Sony' },
  61. { pattern: '^ACER.+', manufacturer: 'Acer' },
  62. { pattern: '^AOC.+', manufacturer: 'AOC Monitors' },
  63. { pattern: '^HP.+', manufacturer: 'HP' },
  64. { pattern: '^EIZO.?', manufacturer: 'Eizo' },
  65. { pattern: '^PHILIPS.?', manufacturer: 'Philips' },
  66. { pattern: '^IIYAMA.?', manufacturer: 'Iiyama' },
  67. { pattern: '^SHARP.?', manufacturer: 'Sharp' },
  68. { pattern: '^NEC.?', manufacturer: 'NEC' },
  69. { pattern: '^LENOVO.?', manufacturer: 'Lenovo' },
  70. { pattern: 'COMPAQ.?', manufacturer: 'Compaq' },
  71. { pattern: 'APPLE.?', manufacturer: 'Apple' },
  72. { pattern: 'INTEL.?', manufacturer: 'Intel' },
  73. { pattern: 'AMD.?', manufacturer: 'AMD' },
  74. { pattern: 'NVIDIA.?', manufacturer: 'NVDIA' },
  75. ];
  76. let result = '';
  77. if (model) {
  78. model = model.toUpperCase();
  79. manufacturers.forEach((manufacturer) => {
  80. const re = RegExp(manufacturer.pattern);
  81. if (re.test(model)) { result = manufacturer.manufacturer; }
  82. });
  83. }
  84. return result;
  85. }
  86. function getVendorFromId(id) {
  87. const vendors = {
  88. '610': 'Apple',
  89. '1e6d': 'LG',
  90. '10ac': 'DELL',
  91. '4dd9': 'Sony',
  92. '38a3': 'NEC',
  93. };
  94. return vendors[id] || '';
  95. }
  96. function vendorToId(str) {
  97. let result = '';
  98. str = (str || '').toLowerCase();
  99. if (str.indexOf('apple') >= 0) { result = '0x05ac'; }
  100. else if (str.indexOf('nvidia') >= 0) { result = '0x10de'; }
  101. else if (str.indexOf('intel') >= 0) { result = '0x8086'; }
  102. else if (str.indexOf('ati') >= 0 || str.indexOf('amd') >= 0) { result = '0x1002'; }
  103. return result;
  104. }
  105. function getMetalVersion(id) {
  106. const families = {
  107. 'spdisplays_mtlgpufamilymac1': 'mac1',
  108. 'spdisplays_mtlgpufamilymac2': 'mac2',
  109. 'spdisplays_mtlgpufamilyapple1': 'apple1',
  110. 'spdisplays_mtlgpufamilyapple2': 'apple2',
  111. 'spdisplays_mtlgpufamilyapple3': 'apple3',
  112. 'spdisplays_mtlgpufamilyapple4': 'apple4',
  113. 'spdisplays_mtlgpufamilyapple5': 'apple5',
  114. 'spdisplays_mtlgpufamilyapple6': 'apple6',
  115. 'spdisplays_mtlgpufamilyapple7': 'apple7',
  116. 'spdisplays_metalfeaturesetfamily11': 'family1_v1',
  117. 'spdisplays_metalfeaturesetfamily12': 'family1_v2',
  118. 'spdisplays_metalfeaturesetfamily13': 'family1_v3',
  119. 'spdisplays_metalfeaturesetfamily14': 'family1_v4',
  120. 'spdisplays_metalfeaturesetfamily21': 'family2_v1'
  121. };
  122. return families[id] || '';
  123. }
  124. function graphics(callback) {
  125. function parseLinesDarwin(graphicsArr) {
  126. const res = {
  127. controllers: [],
  128. displays: []
  129. };
  130. try {
  131. graphicsArr.forEach(function (item) {
  132. // controllers
  133. const bus = ((item.sppci_bus || '').indexOf('builtin') > -1 ? 'Built-In' : ((item.sppci_bus || '').indexOf('pcie') > -1 ? 'PCIe' : ''));
  134. const vram = (parseInt((item.spdisplays_vram || ''), 10) || 0) * (((item.spdisplays_vram || '').indexOf('GB') > -1) ? 1024 : 1);
  135. const vramDyn = (parseInt((item.spdisplays_vram_shared || ''), 10) || 0) * (((item.spdisplays_vram_shared || '').indexOf('GB') > -1) ? 1024 : 1);
  136. let metalVersion = getMetalVersion(item.spdisplays_metal || item.spdisplays_metalfamily || '');
  137. res.controllers.push({
  138. vendor: getVendorFromModel(item.spdisplays_vendor || '') || item.spdisplays_vendor || '',
  139. model: item.sppci_model || '',
  140. bus,
  141. vramDynamic: bus === 'Built-In',
  142. vram: vram || vramDyn || null,
  143. deviceId: item['spdisplays_device-id'] || '',
  144. vendorId: item['spdisplays_vendor-id'] || vendorToId((item['spdisplays_vendor'] || '') + (item.sppci_model || '')),
  145. external: (item.sppci_device_type === 'spdisplays_egpu'),
  146. cores: item['sppci_cores'] || null,
  147. metalVersion
  148. });
  149. // displays
  150. if (item.spdisplays_ndrvs && item.spdisplays_ndrvs.length) {
  151. item.spdisplays_ndrvs.forEach(function (displayItem) {
  152. const connectionType = displayItem['spdisplays_connection_type'] || '';
  153. const currentResolutionParts = (displayItem['_spdisplays_resolution'] || '').split('@');
  154. const currentResolution = currentResolutionParts[0].split('x');
  155. const pixelParts = (displayItem['_spdisplays_pixels'] || '').split('x');
  156. const pixelDepthString = displayItem['spdisplays_depth'] || '';
  157. const serial = displayItem['_spdisplays_display-serial-number'] || displayItem['_spdisplays_display-serial-number2'] || null;
  158. res.displays.push({
  159. vendor: getVendorFromId(displayItem['_spdisplays_display-vendor-id'] || '') || getVendorFromModel(displayItem['_name'] || ''),
  160. vendorId: displayItem['_spdisplays_display-vendor-id'] || '',
  161. model: displayItem['_name'] || '',
  162. productionYear: displayItem['_spdisplays_display-year'] || null,
  163. serial: serial !== '0' ? serial : null,
  164. displayId: displayItem['_spdisplays_displayID'] || null,
  165. main: displayItem['spdisplays_main'] ? displayItem['spdisplays_main'] === 'spdisplays_yes' : false,
  166. builtin: (displayItem['spdisplays_display_type'] || '').indexOf('built-in') > -1,
  167. connection: ((connectionType.indexOf('_internal') > -1) ? 'Internal' : ((connectionType.indexOf('_displayport') > -1) ? 'Display Port' : ((connectionType.indexOf('_hdmi') > -1) ? 'HDMI' : null))),
  168. sizeX: null,
  169. sizeY: null,
  170. pixelDepth: (pixelDepthString === 'CGSThirtyBitColor' ? 30 : (pixelDepthString === 'CGSThirtytwoBitColor' ? 32 : (pixelDepthString === 'CGSTwentyfourBitColor' ? 24 : null))),
  171. resolutionX: pixelParts.length > 1 ? parseInt(pixelParts[0], 10) : null,
  172. resolutionY: pixelParts.length > 1 ? parseInt(pixelParts[1], 10) : null,
  173. currentResX: currentResolution.length > 1 ? parseInt(currentResolution[0], 10) : null,
  174. currentResY: currentResolution.length > 1 ? parseInt(currentResolution[1], 10) : null,
  175. positionX: 0,
  176. positionY: 0,
  177. currentRefreshRate: currentResolutionParts.length > 1 ? parseInt(currentResolutionParts[1], 10) : null,
  178. });
  179. });
  180. }
  181. });
  182. return res;
  183. } catch (e) {
  184. return res;
  185. }
  186. }
  187. function parseLinesLinuxControllers(lines) {
  188. let controllers = [];
  189. let currentController = {
  190. vendor: '',
  191. model: '',
  192. bus: '',
  193. busAddress: '',
  194. vram: null,
  195. vramDynamic: false,
  196. pciID: ''
  197. };
  198. let isGraphicsController = false;
  199. // PCI bus IDs
  200. let pciIDs = [];
  201. try {
  202. pciIDs = execSync('export LC_ALL=C; dmidecode -t 9 2>/dev/null; unset LC_ALL | grep "Bus Address: "').toString().split('\n');
  203. for (let i = 0; i < pciIDs.length; i++) {
  204. pciIDs[i] = pciIDs[i].replace('Bus Address:', '').replace('0000:', '').trim();
  205. }
  206. pciIDs = pciIDs.filter(function (el) {
  207. return el != null && el;
  208. });
  209. } catch (e) {
  210. util.noop();
  211. }
  212. for (let i = 0; i < lines.length; i++) {
  213. if ('' !== lines[i].trim()) {
  214. if (' ' !== lines[i][0] && '\t' !== lines[i][0]) { // first line of new entry
  215. let isExternal = (pciIDs.indexOf(lines[i].split(' ')[0]) >= 0);
  216. let vgapos = lines[i].toLowerCase().indexOf(' vga ');
  217. let _3dcontrollerpos = lines[i].toLowerCase().indexOf('3d controller');
  218. if (vgapos !== -1 || _3dcontrollerpos !== -1) { // VGA
  219. if (_3dcontrollerpos !== -1 && vgapos === -1) {
  220. vgapos = _3dcontrollerpos;
  221. }
  222. if (currentController.vendor || currentController.model || currentController.bus || currentController.vram !== null || currentController.vramDynamic) { // already a controller found
  223. controllers.push(currentController);
  224. currentController = {
  225. vendor: '',
  226. model: '',
  227. bus: '',
  228. busAddress: '',
  229. vram: null,
  230. vramDynamic: false,
  231. };
  232. }
  233. const pciIDCandidate = lines[i].split(' ')[0];
  234. if (/[\da-fA-F]{2}:[\da-fA-F]{2}\.[\da-fA-F]/.test(pciIDCandidate)) {
  235. currentController.busAddress = pciIDCandidate;
  236. }
  237. isGraphicsController = true;
  238. let endpos = lines[i].search(/\[[0-9a-f]{4}:[0-9a-f]{4}]|$/);
  239. let parts = lines[i].substr(vgapos, endpos - vgapos).split(':');
  240. currentController.busAddress = lines[i].substr(0, vgapos).trim();
  241. if (parts.length > 1) {
  242. parts[1] = parts[1].trim();
  243. if (parts[1].toLowerCase().indexOf('corporation') >= 0) {
  244. currentController.vendor = parts[1].substr(0, parts[1].toLowerCase().indexOf('corporation') + 11).trim();
  245. currentController.model = parts[1].substr(parts[1].toLowerCase().indexOf('corporation') + 11, 200).trim().split('(')[0];
  246. currentController.bus = (pciIDs.length > 0 && isExternal) ? 'PCIe' : 'Onboard';
  247. currentController.vram = null;
  248. currentController.vramDynamic = false;
  249. } else if (parts[1].toLowerCase().indexOf(' inc.') >= 0) {
  250. if ((parts[1].match(new RegExp(']', 'g')) || []).length > 1) {
  251. currentController.vendor = parts[1].substr(0, parts[1].toLowerCase().indexOf(']') + 1).trim();
  252. currentController.model = parts[1].substr(parts[1].toLowerCase().indexOf(']') + 1, 200).trim().split('(')[0].trim();
  253. } else {
  254. currentController.vendor = parts[1].substr(0, parts[1].toLowerCase().indexOf(' inc.') + 5).trim();
  255. currentController.model = parts[1].substr(parts[1].toLowerCase().indexOf(' inc.') + 5, 200).trim().split('(')[0].trim();
  256. }
  257. currentController.bus = (pciIDs.length > 0 && isExternal) ? 'PCIe' : 'Onboard';
  258. currentController.vram = null;
  259. currentController.vramDynamic = false;
  260. } else if (parts[1].toLowerCase().indexOf(' ltd.') >= 0) {
  261. if ((parts[1].match(new RegExp(']', 'g')) || []).length > 1) {
  262. currentController.vendor = parts[1].substr(0, parts[1].toLowerCase().indexOf(']') + 1).trim();
  263. currentController.model = parts[1].substr(parts[1].toLowerCase().indexOf(']') + 1, 200).trim().split('(')[0].trim();
  264. } else {
  265. currentController.vendor = parts[1].substr(0, parts[1].toLowerCase().indexOf(' ltd.') + 5).trim();
  266. currentController.model = parts[1].substr(parts[1].toLowerCase().indexOf(' ltd.') + 5, 200).trim().split('(')[0].trim();
  267. }
  268. }
  269. }
  270. } else {
  271. isGraphicsController = false;
  272. }
  273. }
  274. if (isGraphicsController) { // within VGA details
  275. let parts = lines[i].split(':');
  276. if (parts.length > 1 && parts[0].replace(/ +/g, '').toLowerCase().indexOf('devicename') !== -1 && parts[1].toLowerCase().indexOf('onboard') !== -1) { currentController.bus = 'Onboard'; }
  277. if (parts.length > 1 && parts[0].replace(/ +/g, '').toLowerCase().indexOf('region') !== -1 && parts[1].toLowerCase().indexOf('memory') !== -1) {
  278. let memparts = parts[1].split('=');
  279. if (memparts.length > 1) {
  280. currentController.vram = parseInt(memparts[1]);
  281. }
  282. }
  283. }
  284. }
  285. }
  286. if (currentController.vendor || currentController.model || currentController.bus || currentController.busAddress || currentController.vram !== null || currentController.vramDynamic) { // already a controller found
  287. controllers.push(currentController);
  288. }
  289. return (controllers);
  290. }
  291. function parseLinesLinuxClinfo(controllers, lines) {
  292. const fieldPattern = /\[([^\]]+)\]\s+(\w+)\s+(.*)/;
  293. const devices = lines.reduce((devices, line) => {
  294. const field = fieldPattern.exec(line.trim());
  295. if (field) {
  296. if (!devices[field[1]]) {
  297. devices[field[1]] = {};
  298. }
  299. devices[field[1]][field[2]] = field[3];
  300. }
  301. return devices;
  302. }, {});
  303. for (let deviceId in devices) {
  304. const device = devices[deviceId];
  305. if (device['CL_DEVICE_TYPE'] === 'CL_DEVICE_TYPE_GPU') {
  306. let busAddress;
  307. if (device['CL_DEVICE_TOPOLOGY_AMD']) {
  308. const bdf = device['CL_DEVICE_TOPOLOGY_AMD'].match(/[a-zA-Z0-9]+:\d+\.\d+/);
  309. if (bdf) {
  310. busAddress = bdf[0];
  311. }
  312. } else if (device['CL_DEVICE_PCI_BUS_ID_NV'] && device['CL_DEVICE_PCI_SLOT_ID_NV']) {
  313. const bus = parseInt(device['CL_DEVICE_PCI_BUS_ID_NV']);
  314. const slot = parseInt(device['CL_DEVICE_PCI_SLOT_ID_NV']);
  315. if (!isNaN(bus) && !isNaN(slot)) {
  316. const b = bus & 0xff;
  317. const d = (slot >> 3) & 0xff;
  318. const f = slot & 0x07;
  319. busAddress = `${b.toString().padStart(2, '0')}:${d.toString().padStart(2, '0')}.${f}`;
  320. }
  321. }
  322. if (busAddress) {
  323. let controller = controllers.find(controller => controller.busAddress === busAddress);
  324. if (!controller) {
  325. controller = {
  326. vendor: '',
  327. model: '',
  328. bus: '',
  329. busAddress,
  330. vram: null,
  331. vramDynamic: false
  332. };
  333. controllers.push(controller);
  334. }
  335. controller.vendor = device['CL_DEVICE_VENDOR'];
  336. if (device['CL_DEVICE_BOARD_NAME_AMD']) {
  337. controller.model = device['CL_DEVICE_BOARD_NAME_AMD'];
  338. } else {
  339. controller.model = device['CL_DEVICE_NAME'];
  340. }
  341. const memory = parseInt(device['CL_DEVICE_GLOBAL_MEM_SIZE']);
  342. if (!isNaN(memory)) {
  343. controller.vram = Math.round(memory / 1024 / 1024);
  344. }
  345. }
  346. }
  347. }
  348. return controllers;
  349. }
  350. function getNvidiaSmi() {
  351. if (_nvidiaSmiPath) {
  352. return _nvidiaSmiPath;
  353. }
  354. if (_windows) {
  355. try {
  356. const basePath = util.WINDIR + '\\System32\\DriverStore\\FileRepository';
  357. // find all directories that have an nvidia-smi.exe file
  358. const candidateDirs = fs.readdirSync(basePath).filter(dir => {
  359. return fs.readdirSync([basePath, dir].join('/')).includes('nvidia-smi.exe');
  360. });
  361. // use the directory with the most recently created nvidia-smi.exe file
  362. const targetDir = candidateDirs.reduce((prevDir, currentDir) => {
  363. const previousNvidiaSmi = fs.statSync([basePath, prevDir, 'nvidia-smi.exe'].join('/'));
  364. const currentNvidiaSmi = fs.statSync([basePath, currentDir, 'nvidia-smi.exe'].join('/'));
  365. return (previousNvidiaSmi.ctimeMs > currentNvidiaSmi.ctimeMs) ? prevDir : currentDir;
  366. });
  367. if (targetDir) {
  368. _nvidiaSmiPath = [basePath, targetDir, 'nvidia-smi.exe'].join('/');
  369. }
  370. } catch (e) {
  371. util.noop();
  372. }
  373. } else if (_linux) {
  374. _nvidiaSmiPath = 'nvidia-smi';
  375. }
  376. return _nvidiaSmiPath;
  377. }
  378. function nvidiaSmi(options) {
  379. const nvidiaSmiExe = getNvidiaSmi();
  380. options = options || util.execOptsWin;
  381. if (nvidiaSmiExe) {
  382. 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';
  383. const cmd = nvidiaSmiExe + ' ' + nvidiaSmiOpts + (_linux ? ' 2>/dev/null' : '');
  384. try {
  385. const res = execSync(cmd, options).toString();
  386. return res;
  387. } catch (e) {
  388. util.noop();
  389. }
  390. }
  391. return '';
  392. }
  393. function nvidiaDevices() {
  394. function safeParseNumber(value) {
  395. if ([null, undefined].includes(value)) {
  396. return value;
  397. }
  398. return parseFloat(value);
  399. }
  400. const stdout = nvidiaSmi();
  401. if (!stdout) {
  402. return [];
  403. }
  404. const gpus = stdout.split('\n').filter(Boolean);
  405. const results = gpus.map(gpu => {
  406. const splittedData = gpu.split(', ').map(value => value.includes('N/A') ? undefined : value);
  407. if (splittedData.length === 16) {
  408. return {
  409. driverVersion: splittedData[0],
  410. subDeviceId: splittedData[1],
  411. name: splittedData[2],
  412. pciBus: splittedData[3],
  413. fanSpeed: safeParseNumber(splittedData[4]),
  414. memoryTotal: safeParseNumber(splittedData[5]),
  415. memoryUsed: safeParseNumber(splittedData[6]),
  416. memoryFree: safeParseNumber(splittedData[7]),
  417. utilizationGpu: safeParseNumber(splittedData[8]),
  418. utilizationMemory: safeParseNumber(splittedData[9]),
  419. temperatureGpu: safeParseNumber(splittedData[10]),
  420. temperatureMemory: safeParseNumber(splittedData[11]),
  421. powerDraw: safeParseNumber(splittedData[12]),
  422. powerLimit: safeParseNumber(splittedData[13]),
  423. clockCore: safeParseNumber(splittedData[14]),
  424. clockMemory: safeParseNumber(splittedData[15]),
  425. };
  426. }
  427. });
  428. return results;
  429. }
  430. function mergeControllerNvidia(controller, nvidia) {
  431. if (nvidia.driverVersion) { controller.driverVersion = nvidia.driverVersion; }
  432. if (nvidia.subDeviceId) { controller.subDeviceId = nvidia.subDeviceId; }
  433. if (nvidia.name) { controller.name = nvidia.name; }
  434. if (nvidia.pciBus) { controller.pciBus = nvidia.pciBus; }
  435. if (nvidia.fanSpeed) { controller.fanSpeed = nvidia.fanSpeed; }
  436. if (nvidia.memoryTotal) {
  437. controller.memoryTotal = nvidia.memoryTotal;
  438. controller.vram = nvidia.memoryTotal;
  439. controller.vramDynamic = false;
  440. }
  441. if (nvidia.memoryUsed) { controller.memoryUsed = nvidia.memoryUsed; }
  442. if (nvidia.memoryFree) { controller.memoryFree = nvidia.memoryFree; }
  443. if (nvidia.utilizationGpu) { controller.utilizationGpu = nvidia.utilizationGpu; }
  444. if (nvidia.utilizationMemory) { controller.utilizationMemory = nvidia.utilizationMemory; }
  445. if (nvidia.temperatureGpu) { controller.temperatureGpu = nvidia.temperatureGpu; }
  446. if (nvidia.temperatureMemory) { controller.temperatureMemory = nvidia.temperatureMemory; }
  447. if (nvidia.powerDraw) { controller.powerDraw = nvidia.powerDraw; }
  448. if (nvidia.powerLimit) { controller.powerLimit = nvidia.powerLimit; }
  449. if (nvidia.clockCore) { controller.clockCore = nvidia.clockCore; }
  450. if (nvidia.clockMemory) { controller.clockMemory = nvidia.clockMemory; }
  451. return controller;
  452. }
  453. function parseLinesLinuxEdid(edid) {
  454. // parsen EDID
  455. // --> model
  456. // --> resolutionx
  457. // --> resolutiony
  458. // --> builtin = false
  459. // --> pixeldepth (?)
  460. // --> sizex
  461. // --> sizey
  462. let result = {
  463. vendor: '',
  464. model: '',
  465. deviceName: '',
  466. main: false,
  467. builtin: false,
  468. connection: '',
  469. sizeX: null,
  470. sizeY: null,
  471. pixelDepth: null,
  472. resolutionX: null,
  473. resolutionY: null,
  474. currentResX: null,
  475. currentResY: null,
  476. positionX: 0,
  477. positionY: 0,
  478. currentRefreshRate: null
  479. };
  480. // find first "Detailed Timing Description"
  481. let start = 108;
  482. if (edid.substr(start, 6) === '000000') {
  483. start += 36;
  484. }
  485. if (edid.substr(start, 6) === '000000') {
  486. start += 36;
  487. }
  488. if (edid.substr(start, 6) === '000000') {
  489. start += 36;
  490. }
  491. if (edid.substr(start, 6) === '000000') {
  492. start += 36;
  493. }
  494. result.resolutionX = parseInt('0x0' + edid.substr(start + 8, 1) + edid.substr(start + 4, 2));
  495. result.resolutionY = parseInt('0x0' + edid.substr(start + 14, 1) + edid.substr(start + 10, 2));
  496. result.sizeX = parseInt('0x0' + edid.substr(start + 28, 1) + edid.substr(start + 24, 2));
  497. result.sizeY = parseInt('0x0' + edid.substr(start + 29, 1) + edid.substr(start + 26, 2));
  498. // monitor name
  499. start = edid.indexOf('000000fc00'); // find first "Monitor Description Data"
  500. if (start >= 0) {
  501. let model_raw = edid.substr(start + 10, 26);
  502. if (model_raw.indexOf('0a') !== -1) {
  503. model_raw = model_raw.substr(0, model_raw.indexOf('0a'));
  504. }
  505. try {
  506. if (model_raw.length > 2) {
  507. result.model = model_raw.match(/.{1,2}/g).map(function (v) {
  508. return String.fromCharCode(parseInt(v, 16));
  509. }).join('');
  510. }
  511. } catch (e) {
  512. util.noop();
  513. }
  514. } else {
  515. result.model = '';
  516. }
  517. return result;
  518. }
  519. function parseLinesLinuxDisplays(lines, depth) {
  520. let displays = [];
  521. let currentDisplay = {
  522. vendor: '',
  523. model: '',
  524. deviceName: '',
  525. main: false,
  526. builtin: false,
  527. connection: '',
  528. sizeX: null,
  529. sizeY: null,
  530. pixelDepth: null,
  531. resolutionX: null,
  532. resolutionY: null,
  533. currentResX: null,
  534. currentResY: null,
  535. positionX: 0,
  536. positionY: 0,
  537. currentRefreshRate: null
  538. };
  539. let is_edid = false;
  540. let is_current = false;
  541. let edid_raw = '';
  542. let start = 0;
  543. for (let i = 1; i < lines.length; i++) { // start with second line
  544. if ('' !== lines[i].trim()) {
  545. if (' ' !== lines[i][0] && '\t' !== lines[i][0] && lines[i].toLowerCase().indexOf(' connected ') !== -1) { // first line of new entry
  546. if (currentDisplay.model || currentDisplay.main || currentDisplay.builtin || currentDisplay.connection || currentDisplay.sizeX !== null || currentDisplay.pixelDepth !== null || currentDisplay.resolutionX !== null) { // push last display to array
  547. displays.push(currentDisplay);
  548. currentDisplay = {
  549. vendor: '',
  550. model: '',
  551. main: false,
  552. builtin: false,
  553. connection: '',
  554. sizeX: null,
  555. sizeY: null,
  556. pixelDepth: null,
  557. resolutionX: null,
  558. resolutionY: null,
  559. currentResX: null,
  560. currentResY: null,
  561. positionX: 0,
  562. positionY: 0,
  563. currentRefreshRate: null
  564. };
  565. }
  566. let parts = lines[i].split(' ');
  567. currentDisplay.connection = parts[0];
  568. currentDisplay.main = lines[i].toLowerCase().indexOf(' primary ') >= 0;
  569. currentDisplay.builtin = (parts[0].toLowerCase().indexOf('edp') >= 0);
  570. }
  571. // try to read EDID information
  572. if (is_edid) {
  573. if (lines[i].search(/\S|$/) > start) {
  574. edid_raw += lines[i].toLowerCase().trim();
  575. } else {
  576. // parsen EDID
  577. let edid_decoded = parseLinesLinuxEdid(edid_raw);
  578. currentDisplay.vendor = edid_decoded.vendor;
  579. currentDisplay.model = edid_decoded.model;
  580. currentDisplay.resolutionX = edid_decoded.resolutionX;
  581. currentDisplay.resolutionY = edid_decoded.resolutionY;
  582. currentDisplay.sizeX = edid_decoded.sizeX;
  583. currentDisplay.sizeY = edid_decoded.sizeY;
  584. currentDisplay.pixelDepth = depth;
  585. is_edid = false;
  586. }
  587. }
  588. if (lines[i].toLowerCase().indexOf('edid:') >= 0) {
  589. is_edid = true;
  590. start = lines[i].search(/\S|$/);
  591. }
  592. if (lines[i].toLowerCase().indexOf('*current') >= 0) {
  593. const parts1 = lines[i].split('(');
  594. if (parts1 && parts1.length > 1 && parts1[0].indexOf('x') >= 0) {
  595. const resParts = parts1[0].trim().split('x');
  596. currentDisplay.currentResX = util.toInt(resParts[0]);
  597. currentDisplay.currentResY = util.toInt(resParts[1]);
  598. }
  599. is_current = true;
  600. }
  601. if (is_current && lines[i].toLowerCase().indexOf('clock') >= 0 && lines[i].toLowerCase().indexOf('hz') >= 0 && lines[i].toLowerCase().indexOf('v: height') >= 0) {
  602. const parts1 = lines[i].split('clock');
  603. if (parts1 && parts1.length > 1 && parts1[1].toLowerCase().indexOf('hz') >= 0) {
  604. currentDisplay.currentRefreshRate = util.toInt(parts1[1]);
  605. }
  606. is_current = false;
  607. }
  608. }
  609. }
  610. // pushen displays
  611. if (currentDisplay.model || currentDisplay.main || currentDisplay.builtin || currentDisplay.connection || currentDisplay.sizeX !== null || currentDisplay.pixelDepth !== null || currentDisplay.resolutionX !== null) { // still information there
  612. displays.push(currentDisplay);
  613. }
  614. return displays;
  615. }
  616. // function starts here
  617. return new Promise((resolve) => {
  618. process.nextTick(() => {
  619. let result = {
  620. controllers: [],
  621. displays: []
  622. };
  623. if (_darwin) {
  624. let cmd = 'system_profiler -xml -detailLevel full SPDisplaysDataType';
  625. exec(cmd, function (error, stdout) {
  626. if (!error) {
  627. try {
  628. let output = stdout.toString();
  629. result = parseLinesDarwin(util.plistParser(output)[0]._items);
  630. } catch (e) {
  631. util.noop();
  632. }
  633. }
  634. if (callback) {
  635. callback(result);
  636. }
  637. resolve(result);
  638. });
  639. }
  640. if (_linux) {
  641. // Raspberry: https://elinux.org/RPI_vcgencmd_usage
  642. if (util.isRaspberry() && util.isRaspbian()) {
  643. let cmd = 'fbset -s | grep \'mode "\'; vcgencmd get_mem gpu; tvservice -s; tvservice -n;';
  644. exec(cmd, function (error, stdout) {
  645. let lines = stdout.toString().split('\n');
  646. if (lines.length > 3 && lines[0].indexOf('mode "') >= -1 && lines[2].indexOf('0x12000a') > -1) {
  647. const parts = lines[0].replace('mode', '').replace(/"/g, '').trim().split('x');
  648. if (parts.length === 2) {
  649. result.displays.push({
  650. vendor: '',
  651. model: util.getValue(lines, 'device_name', '='),
  652. main: true,
  653. builtin: false,
  654. connection: 'HDMI',
  655. sizeX: null,
  656. sizeY: null,
  657. pixelDepth: null,
  658. resolutionX: parseInt(parts[0], 10),
  659. resolutionY: parseInt(parts[1], 10),
  660. currentResX: null,
  661. currentResY: null,
  662. positionX: 0,
  663. positionY: 0,
  664. currentRefreshRate: null
  665. });
  666. }
  667. }
  668. if (lines.length > 1 && stdout.toString().indexOf('gpu=') >= -1) {
  669. result.controllers.push({
  670. vendor: 'Broadcom',
  671. model: 'VideoCore IV',
  672. bus: '',
  673. vram: util.getValue(lines, 'gpu', '=').replace('M', ''),
  674. vramDynamic: true
  675. });
  676. }
  677. if (callback) {
  678. callback(result);
  679. }
  680. resolve(result);
  681. });
  682. } else {
  683. let cmd = 'lspci -vvv 2>/dev/null';
  684. exec(cmd, function (error, stdout) {
  685. if (!error) {
  686. let lines = stdout.toString().split('\n');
  687. result.controllers = parseLinesLinuxControllers(lines);
  688. const nvidiaData = nvidiaDevices();
  689. // needs to be rewritten ... using no spread operators
  690. result.controllers = result.controllers.map((controller) => { // match by busAddress
  691. return mergeControllerNvidia(controller, nvidiaData.find((contr) => contr.pciBus.toLowerCase().endsWith(controller.busAddress.toLowerCase())) || {});
  692. });
  693. }
  694. let cmd = 'clinfo --raw';
  695. exec(cmd, function (error, stdout) {
  696. if (!error) {
  697. let lines = stdout.toString().split('\n');
  698. result.controllers = parseLinesLinuxClinfo(result.controllers, lines);
  699. }
  700. let cmd = 'xdpyinfo 2>/dev/null | grep \'depth of root window\' | awk \'{ print $5 }\'';
  701. exec(cmd, function (error, stdout) {
  702. let depth = 0;
  703. if (!error) {
  704. let lines = stdout.toString().split('\n');
  705. depth = parseInt(lines[0]) || 0;
  706. }
  707. let cmd = 'xrandr --verbose 2>/dev/null';
  708. exec(cmd, function (error, stdout) {
  709. if (!error) {
  710. let lines = stdout.toString().split('\n');
  711. result.displays = parseLinesLinuxDisplays(lines, depth);
  712. }
  713. if (callback) {
  714. callback(result);
  715. }
  716. resolve(result);
  717. });
  718. });
  719. });
  720. });
  721. }
  722. }
  723. if (_freebsd || _openbsd || _netbsd) {
  724. if (callback) { callback(null); }
  725. resolve(null);
  726. }
  727. if (_sunos) {
  728. if (callback) { callback(null); }
  729. resolve(null);
  730. }
  731. if (_windows) {
  732. // https://blogs.technet.microsoft.com/heyscriptingguy/2013/10/03/use-powershell-to-discover-multi-monitor-information/
  733. // https://devblogs.microsoft.com/scripting/use-powershell-to-discover-multi-monitor-information/
  734. try {
  735. const workload = [];
  736. workload.push(util.powerShell('Get-WmiObject win32_VideoController | fl *'));
  737. 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'));
  738. workload.push(util.powerShell('Get-WmiObject win32_desktopmonitor | fl *'));
  739. workload.push(util.powerShell('Get-CimInstance -Namespace root\\wmi -ClassName WmiMonitorBasicDisplayParams | fl'));
  740. workload.push(util.powerShell('Add-Type -AssemblyName System.Windows.Forms; [System.Windows.Forms.Screen]::AllScreens'));
  741. workload.push(util.powerShell('Get-CimInstance -Namespace root\\wmi -ClassName WmiMonitorConnectionParams | fl'));
  742. 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}'));
  743. const nvidiaData = nvidiaDevices();
  744. Promise.all(
  745. workload
  746. ).then(data => {
  747. // controller + vram
  748. let csections = data[0].replace(/\r/g, '').split(/\n\s*\n/);
  749. let vsections = data[1].replace(/\r/g, '').split(/\n\s*\n/);
  750. result.controllers = parseLinesWindowsControllers(csections, vsections);
  751. result.controllers = result.controllers.map((controller) => { // match by subDeviceId
  752. if (controller.vendor.toLowerCase() === 'nvidia') {
  753. return mergeControllerNvidia(controller, nvidiaData.find(device => {
  754. let windowsSubDeviceId = (controller.subDeviceId || '').toLowerCase();
  755. const nvidiaSubDeviceIdParts = device.subDeviceId.split('x');
  756. let nvidiaSubDeviceId = nvidiaSubDeviceIdParts.length > 1 ? nvidiaSubDeviceIdParts[1].toLowerCase() : nvidiaSubDeviceIdParts[0].toLowerCase();
  757. const lengthDifference = Math.abs(windowsSubDeviceId.length - nvidiaSubDeviceId.length);
  758. if (windowsSubDeviceId.length > nvidiaSubDeviceId.length) {
  759. for (let i = 0; i < lengthDifference; i++) {
  760. nvidiaSubDeviceId = '0' + nvidiaSubDeviceId;
  761. }
  762. } else if (windowsSubDeviceId.length < nvidiaSubDeviceId.length) {
  763. for (let i = 0; i < lengthDifference; i++) {
  764. windowsSubDeviceId = '0' + windowsSubDeviceId;
  765. }
  766. }
  767. return windowsSubDeviceId === nvidiaSubDeviceId;
  768. }) || {});
  769. } else {
  770. return controller;
  771. }
  772. });
  773. // displays
  774. let dsections = data[2].replace(/\r/g, '').split(/\n\s*\n/);
  775. // result.displays = parseLinesWindowsDisplays(dsections);
  776. if (dsections[0].trim() === '') { dsections.shift(); }
  777. if (dsections.length && dsections[dsections.length - 1].trim() === '') { dsections.pop(); }
  778. // monitor (powershell)
  779. let msections = data[3].replace(/\r/g, '').split('Active ');
  780. msections.shift();
  781. // forms.screens (powershell)
  782. let ssections = data[4].replace(/\r/g, '').split('BitsPerPixel ');
  783. ssections.shift();
  784. // connection params (powershell) - video type
  785. let tsections = data[5].replace(/\r/g, '').split(/\n\s*\n/);
  786. tsections.shift();
  787. // monitor ID (powershell) - model / vendor
  788. const res = data[6].replace(/\r/g, '').split(/\n/);
  789. let isections = [];
  790. res.forEach(element => {
  791. const parts = element.split('|');
  792. if (parts.length === 5) {
  793. isections.push({
  794. vendor: parts[0],
  795. code: parts[1],
  796. model: parts[2],
  797. serial: parts[3],
  798. instanceId: parts[4]
  799. });
  800. }
  801. });
  802. result.displays = parseLinesWindowsDisplaysPowershell(ssections, msections, dsections, tsections, isections);
  803. if (result.displays.length === 1) {
  804. if (_resolutionX) {
  805. result.displays[0].resolutionX = _resolutionX;
  806. if (!result.displays[0].currentResX) {
  807. result.displays[0].currentResX = _resolutionX;
  808. }
  809. }
  810. if (_resolutionY) {
  811. result.displays[0].resolutionY = _resolutionY;
  812. if (result.displays[0].currentResY === 0) {
  813. result.displays[0].currentResY = _resolutionY;
  814. }
  815. }
  816. if (_pixelDepth) {
  817. result.displays[0].pixelDepth = _pixelDepth;
  818. }
  819. if (_refreshRate && !result.displays[0].currentRefreshRate) {
  820. result.displays[0].currentRefreshRate = _refreshRate;
  821. }
  822. }
  823. if (callback) {
  824. callback(result);
  825. }
  826. resolve(result);
  827. })
  828. .catch(() => {
  829. if (callback) {
  830. callback(result);
  831. }
  832. resolve(result);
  833. });
  834. } catch (e) {
  835. if (callback) { callback(result); }
  836. resolve(result);
  837. }
  838. }
  839. });
  840. });
  841. function parseLinesWindowsControllers(sections, vections) {
  842. const memorySizes = {};
  843. for (const i in vections) {
  844. if ({}.hasOwnProperty.call(vections, i)) {
  845. if (vections[i].trim() !== '') {
  846. const lines = vections[i].trim().split('\n');
  847. 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);
  848. if (matchingDeviceId) {
  849. const quadWordmemorySize = parseInt(util.getValue(lines, 'HardwareInformation.qwMemorySize'));
  850. if (!isNaN(quadWordmemorySize)) {
  851. let deviceId = matchingDeviceId[1].toUpperCase() + '&' + matchingDeviceId[2].toUpperCase();
  852. if (matchingDeviceId[3]) {
  853. deviceId += '&' + matchingDeviceId[3].toUpperCase();
  854. }
  855. if (matchingDeviceId[4]) {
  856. deviceId += '&' + matchingDeviceId[4].toUpperCase();
  857. }
  858. memorySizes[deviceId] = quadWordmemorySize;
  859. }
  860. }
  861. }
  862. }
  863. }
  864. let controllers = [];
  865. for (let i in sections) {
  866. if ({}.hasOwnProperty.call(sections, i)) {
  867. if (sections[i].trim() !== '') {
  868. let lines = sections[i].trim().split('\n');
  869. 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);
  870. let subDeviceId = null;
  871. let memorySize = null;
  872. if (pnpDeviceId) {
  873. subDeviceId = pnpDeviceId[3] || '';
  874. if (subDeviceId) {
  875. subDeviceId = subDeviceId.split('_')[1];
  876. }
  877. // Match PCI device identifier (there's an order of increasing generality):
  878. // https://docs.microsoft.com/en-us/windows-hardware/drivers/install/identifiers-for-pci-devices
  879. // PCI\VEN_v(4)&DEV_d(4)&SUBSYS_s(4)n(4)&REV_r(2)
  880. if (memorySize == null && pnpDeviceId[3] && pnpDeviceId[4]) {
  881. const deviceId = pnpDeviceId[1].toUpperCase() + '&' + pnpDeviceId[2].toUpperCase() + '&' + pnpDeviceId[3].toUpperCase() + '&' + pnpDeviceId[4].toUpperCase();
  882. if ({}.hasOwnProperty.call(memorySizes, deviceId)) {
  883. memorySize = memorySizes[deviceId];
  884. }
  885. }
  886. // PCI\VEN_v(4)&DEV_d(4)&SUBSYS_s(4)n(4)
  887. if (memorySize == null && pnpDeviceId[3]) {
  888. const deviceId = pnpDeviceId[1].toUpperCase() + '&' + pnpDeviceId[2].toUpperCase() + '&' + pnpDeviceId[3].toUpperCase();
  889. if ({}.hasOwnProperty.call(memorySizes, deviceId)) {
  890. memorySize = memorySizes[deviceId];
  891. }
  892. }
  893. // PCI\VEN_v(4)&DEV_d(4)&REV_r(2)
  894. if (memorySize == null && pnpDeviceId[4]) {
  895. const deviceId = pnpDeviceId[1].toUpperCase() + '&' + pnpDeviceId[2].toUpperCase() + '&' + pnpDeviceId[4].toUpperCase();
  896. if ({}.hasOwnProperty.call(memorySizes, deviceId)) {
  897. memorySize = memorySizes[deviceId];
  898. }
  899. }
  900. // PCI\VEN_v(4)&DEV_d(4)
  901. if (memorySize == null) {
  902. const deviceId = pnpDeviceId[1].toUpperCase() + '&' + pnpDeviceId[2].toUpperCase();
  903. if ({}.hasOwnProperty.call(memorySizes, deviceId)) {
  904. memorySize = memorySizes[deviceId];
  905. }
  906. }
  907. }
  908. controllers.push({
  909. vendor: util.getValue(lines, 'AdapterCompatibility', ':'),
  910. model: util.getValue(lines, 'name', ':'),
  911. bus: util.getValue(lines, 'PNPDeviceID', ':').startsWith('PCI') ? 'PCI' : '',
  912. vram: (memorySize == null ? util.toInt(util.getValue(lines, 'AdapterRAM', ':')) : memorySize) / 1024 / 1024,
  913. vramDynamic: (util.getValue(lines, 'VideoMemoryType', ':') === '2'),
  914. subDeviceId
  915. });
  916. _resolutionX = util.toInt(util.getValue(lines, 'CurrentHorizontalResolution', ':')) || _resolutionX;
  917. _resolutionY = util.toInt(util.getValue(lines, 'CurrentVerticalResolution', ':')) || _resolutionY;
  918. _refreshRate = util.toInt(util.getValue(lines, 'CurrentRefreshRate', ':')) || _refreshRate;
  919. _pixelDepth = util.toInt(util.getValue(lines, 'CurrentBitsPerPixel', ':')) || _pixelDepth;
  920. }
  921. }
  922. }
  923. return controllers;
  924. }
  925. function parseLinesWindowsDisplaysPowershell(ssections, msections, dsections, tsections, isections) {
  926. let displays = [];
  927. let vendor = '';
  928. let model = '';
  929. let deviceID = '';
  930. let resolutionX = 0;
  931. let resolutionY = 0;
  932. if (dsections && dsections.length) {
  933. let linesDisplay = dsections[0].split('\n');
  934. vendor = util.getValue(linesDisplay, 'MonitorManufacturer', ':');
  935. model = util.getValue(linesDisplay, 'Name', ':');
  936. deviceID = util.getValue(linesDisplay, 'PNPDeviceID', ':').replace(/&amp;/g, '&').toLowerCase();
  937. resolutionX = util.toInt(util.getValue(linesDisplay, 'ScreenWidth', ':'));
  938. resolutionY = util.toInt(util.getValue(linesDisplay, 'ScreenHeight', ':'));
  939. }
  940. for (let i = 0; i < ssections.length; i++) {
  941. if (ssections[i].trim() !== '') {
  942. ssections[i] = 'BitsPerPixel ' + ssections[i];
  943. msections[i] = 'Active ' + msections[i];
  944. // tsections can be empty OR undefined on earlier versions of powershell (<=2.0)
  945. // Tag connection type as UNKNOWN by default if this information is missing
  946. if (tsections.length === 0 || tsections[i] === undefined) {
  947. tsections[i] = 'Unknown';
  948. }
  949. let linesScreen = ssections[i].split('\n');
  950. let linesMonitor = msections[i].split('\n');
  951. let linesConnection = tsections[i].split('\n');
  952. const bitsPerPixel = util.getValue(linesScreen, 'BitsPerPixel');
  953. const bounds = util.getValue(linesScreen, 'Bounds').replace('{', '').replace('}', '').replace(/=/g, ':').split(',');
  954. const primary = util.getValue(linesScreen, 'Primary');
  955. const sizeX = util.getValue(linesMonitor, 'MaxHorizontalImageSize');
  956. const sizeY = util.getValue(linesMonitor, 'MaxVerticalImageSize');
  957. const instanceName = util.getValue(linesMonitor, 'InstanceName').toLowerCase();
  958. const videoOutputTechnology = util.getValue(linesConnection, 'VideoOutputTechnology');
  959. const deviceName = util.getValue(linesScreen, 'DeviceName');
  960. let displayVendor = '';
  961. let displayModel = '';
  962. isections.forEach(element => {
  963. if (element.instanceId.toLowerCase().startsWith(instanceName) && vendor.startsWith('(') && model.startsWith('PnP')) {
  964. displayVendor = element.vendor;
  965. displayModel = element.model;
  966. }
  967. });
  968. displays.push({
  969. vendor: instanceName.startsWith(deviceID) && displayVendor === '' ? vendor : displayVendor,
  970. model: instanceName.startsWith(deviceID) && displayModel === '' ? model : displayModel,
  971. deviceName,
  972. main: primary.toLowerCase() === 'true',
  973. builtin: videoOutputTechnology === '2147483648',
  974. connection: videoOutputTechnology && videoTypes[videoOutputTechnology] ? videoTypes[videoOutputTechnology] : '',
  975. resolutionX: util.toInt(util.getValue(bounds, 'Width', ':')),
  976. resolutionY: util.toInt(util.getValue(bounds, 'Height', ':')),
  977. sizeX: sizeX ? parseInt(sizeX, 10) : null,
  978. sizeY: sizeY ? parseInt(sizeY, 10) : null,
  979. pixelDepth: bitsPerPixel,
  980. currentResX: util.toInt(util.getValue(bounds, 'Width', ':')),
  981. currentResY: util.toInt(util.getValue(bounds, 'Height', ':')),
  982. positionX: util.toInt(util.getValue(bounds, 'X', ':')),
  983. positionY: util.toInt(util.getValue(bounds, 'Y', ':')),
  984. });
  985. }
  986. }
  987. if (ssections.length === 0) {
  988. displays.push({
  989. vendor,
  990. model,
  991. main: true,
  992. sizeX: null,
  993. sizeY: null,
  994. resolutionX,
  995. resolutionY,
  996. pixelDepth: null,
  997. currentResX: resolutionX,
  998. currentResY: resolutionY,
  999. positionX: 0,
  1000. positionY: 0
  1001. });
  1002. }
  1003. return displays;
  1004. }
  1005. }
  1006. exports.graphics = graphics;