Repositorio del curso CCOM4030 el semestre B91 del proyecto Artesanías con el Instituto de Cultura

graphics.js 31KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790
  1. 'use strict';
  2. // @ts-check
  3. // ==================================================================================
  4. // graphics.js
  5. // ----------------------------------------------------------------------------------
  6. // Description: System Information - library
  7. // for Node.js
  8. // Copyright: (c) 2014 - 2020
  9. // Author: Sebastian Hildebrandt
  10. // ----------------------------------------------------------------------------------
  11. // License: MIT
  12. // ==================================================================================
  13. // 7. Graphics (controller, display)
  14. // ----------------------------------------------------------------------------------
  15. const os = require('os');
  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. const _linux = (_platform === 'linux');
  21. const _darwin = (_platform === 'darwin');
  22. const _windows = (_platform === 'win32');
  23. const _freebsd = (_platform === 'freebsd');
  24. const _openbsd = (_platform === 'openbsd');
  25. const _netbsd = (_platform === 'netbsd');
  26. const _sunos = (_platform === 'sunos');
  27. let _resolutionx = 0;
  28. let _resolutiony = 0;
  29. let _pixeldepth = 0;
  30. let _refreshrate = 0;
  31. const videoTypes = {
  32. '-2': 'UNINITIALIZED',
  33. '-1': 'OTHER',
  34. '0': 'HD15',
  35. '1': 'SVIDEO',
  36. '2': 'Composite video',
  37. '3': 'Component video',
  38. '4': 'DVI',
  39. '5': 'HDMI',
  40. '6': 'LVDS',
  41. '8': 'D_JPN',
  42. '9': 'SDI',
  43. '10': 'DP',
  44. '11': 'DP embedded',
  45. '12': 'UDI',
  46. '13': 'UDI embedded',
  47. '14': 'SDTVDONGLE',
  48. '15': 'MIRACAST',
  49. '2147483648': 'INTERNAL'
  50. };
  51. function graphics(callback) {
  52. function parseLinesDarwin(lines) {
  53. let starts = [];
  54. let level = -1;
  55. let lastlevel = -1;
  56. let controllers = [];
  57. let displays = [];
  58. let currentController = {
  59. vendor: '',
  60. model: '',
  61. bus: '',
  62. vram: -1,
  63. vramDynamic: false
  64. };
  65. let currentDisplay = {
  66. vendor: '',
  67. model: '',
  68. deviceName: '',
  69. main: false,
  70. builtin: false,
  71. connection: '',
  72. sizex: -1,
  73. sizey: -1,
  74. pixeldepth: -1,
  75. resolutionx: -1,
  76. resolutiony: -1,
  77. currentResX: -1,
  78. currentResY: -1,
  79. positionX: 0,
  80. positionY: 0,
  81. currentRefreshRate: -1
  82. };
  83. for (let i = 0; i < lines.length; i++) {
  84. if ('' !== lines[i].trim()) {
  85. let start = lines[i].search(/\S|$/);
  86. if (-1 === starts.indexOf(start)) {
  87. starts.push(start);
  88. }
  89. level = starts.indexOf(start);
  90. if (level < lastlevel) {
  91. if (Object.keys(currentController).length > 0) {// just changed to Displays
  92. controllers.push(currentController);
  93. currentController = {
  94. vendor: '',
  95. model: '',
  96. bus: '',
  97. vram: -1,
  98. vramDynamic: false
  99. };
  100. }
  101. if (Object.keys(currentDisplay).length > 0) {// just changed to Displays
  102. displays.push(currentDisplay);
  103. currentDisplay = {
  104. vendor: '',
  105. model: '',
  106. deviceName: '',
  107. main: false,
  108. builtin: false,
  109. connection: '',
  110. sizex: -1,
  111. sizey: -1,
  112. pixeldepth: -1,
  113. resolutionx: -1,
  114. resolutiony: -1,
  115. currentResX: -1,
  116. currentResY: -1,
  117. positionX: 0,
  118. positionY: 0,
  119. currentRefreshRate: -1
  120. };
  121. }
  122. }
  123. lastlevel = level;
  124. let parts = lines[i].split(':');
  125. if (2 === level) { // grafics controller level
  126. if (parts.length > 1 && parts[0].replace(/ +/g, '').toLowerCase().indexOf('chipsetmodel') !== -1) currentController.model = parts[1].trim();
  127. if (parts.length > 1 && parts[0].replace(/ +/g, '').toLowerCase().indexOf('bus') !== -1) currentController.bus = parts[1].trim();
  128. if (parts.length > 1 && parts[0].replace(/ +/g, '').toLowerCase().indexOf('vendor') !== -1) currentController.vendor = parts[1].split('(')[0].trim();
  129. if (parts.length > 1 && parts[0].replace(/ +/g, '').toLowerCase().indexOf('vram(total)') !== -1) {
  130. currentController.vram = parseInt(parts[1]); // in MB
  131. if (parts[1].toLowerCase().indexOf('gb') !== -1) {
  132. currentController.vram = currentController.vram * 1024;
  133. }
  134. currentController.vramDynamic = false;
  135. }
  136. if (parts.length > 1 && parts[0].replace(/ +/g, '').toLowerCase().indexOf('vram(dynamic,max)') !== -1) {
  137. currentController.vram = parseInt(parts[1]); // in MB
  138. if (parts[1].toLowerCase().indexOf('gb') !== -1) {
  139. currentController.vram = currentController.vram * 1024;
  140. }
  141. currentController.vramDynamic = true;
  142. }
  143. }
  144. if (3 === level) { // display controller level
  145. if (parts.length > 1 && '' === parts[1]) {
  146. currentDisplay.vendor = '';
  147. currentDisplay.model = parts[0].trim();
  148. currentDisplay.main = false;
  149. currentDisplay.builtin = false;
  150. currentDisplay.connection = '';
  151. currentDisplay.sizex = -1;
  152. currentDisplay.sizey = -1;
  153. currentDisplay.positionX = 0;
  154. currentDisplay.positionY = 0;
  155. currentDisplay.pixeldepth = -1;
  156. }
  157. }
  158. if (4 === level) { // display controller details level
  159. if (parts.length > 1 && parts[0].replace(/ +/g, '').toLowerCase().indexOf('resolution') !== -1) {
  160. let resolution = parts[1].split('x');
  161. currentDisplay.resolutionx = (resolution.length > 1 ? parseInt(resolution[0]) : 0);
  162. currentDisplay.resolutiony = (resolution.length > 1 ? parseInt(resolution[1]) : 0);
  163. currentDisplay.currentResX = currentDisplay.resolutionx;
  164. currentDisplay.currentResY = currentDisplay.resolutiony;
  165. }
  166. if (parts.length > 1 && parts[0].replace(/ +/g, '').toLowerCase().indexOf('pixeldepth') !== -1) currentDisplay.pixeldepth = parseInt(parts[1]); // in BIT
  167. if (parts.length > 1 && parts[0].replace(/ +/g, '').toLowerCase().indexOf('framebufferdepth') !== -1) currentDisplay.pixeldepth = parseInt(parts[1]); // in BIT
  168. if (parts.length > 1 && parts[0].replace(/ +/g, '').toLowerCase().indexOf('maindisplay') !== -1 && parts[1].replace(/ +/g, '').toLowerCase() === 'yes') currentDisplay.main = true;
  169. if (parts.length > 1 && parts[0].replace(/ +/g, '').toLowerCase().indexOf('built-in') !== -1 && parts[1].replace(/ +/g, '').toLowerCase() === 'yes') {
  170. currentDisplay.builtin = true;
  171. currentDisplay.connection = '';
  172. }
  173. if (parts.length > 1 && parts[0].replace(/ +/g, '').toLowerCase().indexOf('connectiontype') !== -1) {
  174. currentDisplay.builtin = false;
  175. currentDisplay.connection = parts[1].trim();
  176. }
  177. }
  178. }
  179. }
  180. if (Object.keys(currentController).length > 0) {// just changed to Displays
  181. controllers.push(currentController);
  182. }
  183. if (Object.keys(currentDisplay).length > 0) {// just changed to Displays
  184. displays.push(currentDisplay);
  185. }
  186. return ({
  187. controllers: controllers,
  188. displays: displays
  189. });
  190. }
  191. function parseLinesLinuxControllers(lines) {
  192. let controllers = [];
  193. let currentController = {
  194. vendor: '',
  195. model: '',
  196. bus: '',
  197. vram: -1,
  198. vramDynamic: false
  199. };
  200. let isGraphicsController = false;
  201. // PCI bus IDs
  202. let pciIDs = [];
  203. try {
  204. pciIDs = execSync('export LC_ALL=C; dmidecode -t 9 2>/dev/null; unset LC_ALL | grep "Bus Address: "').toString().split('\n');
  205. for (let i = 0; i < pciIDs.length; i++) {
  206. pciIDs[i] = pciIDs[i].replace('Bus Address:', '').replace('0000:', '').trim();
  207. }
  208. pciIDs = pciIDs.filter(function (el) {
  209. return el != null && el;
  210. });
  211. } catch (e) {
  212. util.noop();
  213. }
  214. for (let i = 0; i < lines.length; i++) {
  215. if ('' !== lines[i].trim()) {
  216. if (' ' !== lines[i][0] && '\t' !== lines[i][0]) { // first line of new entry
  217. let isExternal = (pciIDs.indexOf(lines[i].split(' ')[0]) >= 0);
  218. let vgapos = lines[i].toLowerCase().indexOf(' vga ');
  219. let _3dcontrollerpos = lines[i].toLowerCase().indexOf('3d controller');
  220. if (vgapos !== -1 || _3dcontrollerpos !== -1) { // VGA
  221. if (_3dcontrollerpos !== -1 && vgapos === -1) {
  222. vgapos = _3dcontrollerpos;
  223. }
  224. if (currentController.vendor || currentController.model || currentController.bus || currentController.vram !== -1 || currentController.vramDynamic) { // already a controller found
  225. controllers.push(currentController);
  226. currentController = {
  227. vendor: '',
  228. model: '',
  229. bus: '',
  230. vram: -1,
  231. vramDynamic: false
  232. };
  233. }
  234. isGraphicsController = true;
  235. let endpos = lines[i].search(/\[[0-9a-f]{4}:[0-9a-f]{4}]|$/);
  236. let parts = lines[i].substr(vgapos, endpos - vgapos).split(':');
  237. if (parts.length > 1) {
  238. parts[1] = parts[1].trim();
  239. if (parts[1].toLowerCase().indexOf('corporation') >= 0) {
  240. currentController.vendor = parts[1].substr(0, parts[1].toLowerCase().indexOf('corporation') + 11).trim();
  241. currentController.model = parts[1].substr(parts[1].toLowerCase().indexOf('corporation') + 11, 200).trim().split('(')[0];
  242. currentController.bus = (pciIDs.length > 0 && isExternal) ? 'PCIe' : 'Onboard';
  243. currentController.vram = -1;
  244. currentController.vramDynamic = false;
  245. } else if (parts[1].toLowerCase().indexOf(' inc.') >= 0) {
  246. if ((parts[1].match(new RegExp(']', 'g')) || []).length > 1) {
  247. currentController.vendor = parts[1].substr(0, parts[1].toLowerCase().indexOf(']') + 1).trim();
  248. currentController.model = parts[1].substr(parts[1].toLowerCase().indexOf(']') + 1, 200).trim().split('(')[0].trim();
  249. } else {
  250. currentController.vendor = parts[1].substr(0, parts[1].toLowerCase().indexOf(' inc.') + 5).trim();
  251. currentController.model = parts[1].substr(parts[1].toLowerCase().indexOf(' inc.') + 5, 200).trim().split('(')[0].trim();
  252. }
  253. currentController.bus = (pciIDs.length > 0 && isExternal) ? 'PCIe' : 'Onboard';
  254. currentController.vram = -1;
  255. currentController.vramDynamic = false;
  256. } else if (parts[1].toLowerCase().indexOf(' ltd.') >= 0) {
  257. if ((parts[1].match(new RegExp(']', 'g')) || []).length > 1) {
  258. currentController.vendor = parts[1].substr(0, parts[1].toLowerCase().indexOf(']') + 1).trim();
  259. currentController.model = parts[1].substr(parts[1].toLowerCase().indexOf(']') + 1, 200).trim().split('(')[0].trim();
  260. } else {
  261. currentController.vendor = parts[1].substr(0, parts[1].toLowerCase().indexOf(' ltd.') + 5).trim();
  262. currentController.model = parts[1].substr(parts[1].toLowerCase().indexOf(' ltd.') + 5, 200).trim().split('(')[0].trim();
  263. }
  264. }
  265. }
  266. } else {
  267. isGraphicsController = false;
  268. }
  269. }
  270. if (isGraphicsController) { // within VGA details
  271. let parts = lines[i].split(':');
  272. if (parts.length > 1 && parts[0].replace(/ +/g, '').toLowerCase().indexOf('devicename') !== -1 && parts[1].toLowerCase().indexOf('onboard') !== -1) currentController.bus = 'Onboard';
  273. if (parts.length > 1 && parts[0].replace(/ +/g, '').toLowerCase().indexOf('region') !== -1 && parts[1].toLowerCase().indexOf('memory') !== -1) {
  274. let memparts = parts[1].split('=');
  275. if (memparts.length > 1) {
  276. currentController.vram = parseInt(memparts[1]);
  277. }
  278. }
  279. }
  280. }
  281. }
  282. if (currentController.vendor || currentController.model || currentController.bus || currentController.vram !== -1 || currentController.vramDynamic) { // already a controller found
  283. controllers.push(currentController);
  284. }
  285. return (controllers);
  286. }
  287. function parseLinesLinuxEdid(edid) {
  288. // parsen EDID
  289. // --> model
  290. // --> resolutionx
  291. // --> resolutiony
  292. // --> builtin = false
  293. // --> pixeldepth (?)
  294. // --> sizex
  295. // --> sizey
  296. let result = {
  297. vendor: '',
  298. model: '',
  299. deviceName: '',
  300. main: false,
  301. builtin: false,
  302. connection: '',
  303. sizex: -1,
  304. sizey: -1,
  305. pixeldepth: -1,
  306. resolutionx: -1,
  307. resolutiony: -1,
  308. currentResX: -1,
  309. currentResY: -1,
  310. positionX: 0,
  311. positionY: 0,
  312. currentRefreshRate: -1
  313. };
  314. // find first "Detailed Timing Description"
  315. let start = 108;
  316. if (edid.substr(start, 6) === '000000') {
  317. start += 36;
  318. }
  319. if (edid.substr(start, 6) === '000000') {
  320. start += 36;
  321. }
  322. if (edid.substr(start, 6) === '000000') {
  323. start += 36;
  324. }
  325. if (edid.substr(start, 6) === '000000') {
  326. start += 36;
  327. }
  328. result.resolutionx = parseInt('0x0' + edid.substr(start + 8, 1) + edid.substr(start + 4, 2));
  329. result.resolutiony = parseInt('0x0' + edid.substr(start + 14, 1) + edid.substr(start + 10, 2));
  330. result.sizex = parseInt('0x0' + edid.substr(start + 28, 1) + edid.substr(start + 24, 2));
  331. result.sizey = parseInt('0x0' + edid.substr(start + 29, 1) + edid.substr(start + 26, 2));
  332. // monitor name
  333. start = edid.indexOf('000000fc00'); // find first "Monitor Description Data"
  334. if (start >= 0) {
  335. let model_raw = edid.substr(start + 10, 26);
  336. if (model_raw.indexOf('0a') !== -1) {
  337. model_raw = model_raw.substr(0, model_raw.indexOf('0a'));
  338. }
  339. try {
  340. if (model_raw.length > 2) {
  341. result.model = model_raw.match(/.{1,2}/g).map(function (v) {
  342. return String.fromCharCode(parseInt(v, 16));
  343. }).join('');
  344. }
  345. } catch (e) {
  346. util.noop();
  347. }
  348. } else {
  349. result.model = '';
  350. }
  351. return result;
  352. }
  353. function parseLinesLinuxDisplays(lines, depth) {
  354. let displays = [];
  355. let currentDisplay = {
  356. vendor: '',
  357. model: '',
  358. deviceName: '',
  359. main: false,
  360. builtin: false,
  361. connection: '',
  362. sizex: -1,
  363. sizey: -1,
  364. pixeldepth: -1,
  365. resolutionx: -1,
  366. resolutiony: -1,
  367. currentResX: -1,
  368. currentResY: -1,
  369. positionX: 0,
  370. positionY: 0,
  371. currentRefreshRate: -1
  372. };
  373. let is_edid = false;
  374. let is_current = false;
  375. let edid_raw = '';
  376. let start = 0;
  377. for (let i = 1; i < lines.length; i++) { // start with second line
  378. if ('' !== lines[i].trim()) {
  379. if (' ' !== lines[i][0] && '\t' !== lines[i][0] && lines[i].toLowerCase().indexOf(' connected ') !== -1) { // first line of new entry
  380. if (currentDisplay.model || currentDisplay.main || currentDisplay.builtin || currentDisplay.connection || currentDisplay.sizex !== -1 || currentDisplay.pixeldepth !== -1 || currentDisplay.resolutionx !== -1) { // push last display to array
  381. displays.push(currentDisplay);
  382. currentDisplay = {
  383. vendor: '',
  384. model: '',
  385. main: false,
  386. builtin: false,
  387. connection: '',
  388. sizex: -1,
  389. sizey: -1,
  390. pixeldepth: -1,
  391. resolutionx: -1,
  392. resolutiony: -1,
  393. currentResX: -1,
  394. currentResY: -1,
  395. positionX: 0,
  396. positionY: 0,
  397. currentRefreshRate: -1
  398. };
  399. }
  400. let parts = lines[i].split(' ');
  401. currentDisplay.connection = parts[0];
  402. currentDisplay.main = lines[i].toLowerCase().indexOf(' primary ') >= 0;
  403. currentDisplay.builtin = (parts[0].toLowerCase().indexOf('edp') >= 0);
  404. }
  405. // try to read EDID information
  406. if (is_edid) {
  407. if (lines[i].search(/\S|$/) > start) {
  408. edid_raw += lines[i].toLowerCase().trim();
  409. } else {
  410. // parsen EDID
  411. let edid_decoded = parseLinesLinuxEdid(edid_raw);
  412. currentDisplay.vendor = edid_decoded.vendor;
  413. currentDisplay.model = edid_decoded.model;
  414. currentDisplay.resolutionx = edid_decoded.resolutionx;
  415. currentDisplay.resolutiony = edid_decoded.resolutiony;
  416. currentDisplay.sizex = edid_decoded.sizex;
  417. currentDisplay.sizey = edid_decoded.sizey;
  418. currentDisplay.pixeldepth = depth;
  419. is_edid = false;
  420. }
  421. }
  422. if (lines[i].toLowerCase().indexOf('edid:') >= 0) {
  423. is_edid = true;
  424. start = lines[i].search(/\S|$/);
  425. }
  426. if (lines[i].toLowerCase().indexOf('*current') >= 0) {
  427. const parts1 = lines[i].split('(');
  428. if (parts1 && parts1.length > 1 && parts1[0].indexOf('x') >= 0) {
  429. const resParts = parts1[0].trim().split('x');
  430. currentDisplay.currentResX = util.toInt(resParts[0]);
  431. currentDisplay.currentResY = util.toInt(resParts[1]);
  432. }
  433. is_current = true;
  434. }
  435. if (is_current && lines[i].toLowerCase().indexOf('clock') >= 0 && lines[i].toLowerCase().indexOf('hz') >= 0 && lines[i].toLowerCase().indexOf('v: height') >= 0) {
  436. const parts1 = lines[i].split('clock');
  437. if (parts1 && parts1.length > 1 && parts1[1].toLowerCase().indexOf('hz') >= 0) {
  438. currentDisplay.currentRefreshRate = util.toInt(parts1[1]);
  439. }
  440. is_current = false;
  441. }
  442. }
  443. }
  444. // pushen displays
  445. if (currentDisplay.model || currentDisplay.main || currentDisplay.builtin || currentDisplay.connection || currentDisplay.sizex !== -1 || currentDisplay.pixeldepth !== -1 || currentDisplay.resolutionx !== -1) { // still information there
  446. displays.push(currentDisplay);
  447. }
  448. return displays;
  449. }
  450. // function starts here
  451. return new Promise((resolve) => {
  452. process.nextTick(() => {
  453. let result = {
  454. controllers: [],
  455. displays: []
  456. };
  457. if (_darwin) {
  458. let cmd = 'system_profiler SPDisplaysDataType';
  459. exec(cmd, function (error, stdout) {
  460. if (!error) {
  461. let lines = stdout.toString().split('\n');
  462. result = parseLinesDarwin(lines);
  463. }
  464. if (callback) {
  465. callback(result);
  466. }
  467. resolve(result);
  468. });
  469. }
  470. if (_linux) {
  471. // Raspberry: https://elinux.org/RPI_vcgencmd_usage
  472. if (util.isRaspberry() && util.isRaspbian()) {
  473. let cmd = 'fbset -s | grep \'mode "\'; vcgencmd get_mem gpu; tvservice -s; tvservice -n;';
  474. exec(cmd, function (error, stdout) {
  475. let lines = stdout.toString().split('\n');
  476. if (lines.length > 3 && lines[0].indexOf('mode "') >= -1 && lines[2].indexOf('0x12000a') > -1) {
  477. const parts = lines[0].replace('mode', '').replace(/"/g, '').trim().split('x');
  478. if (parts.length === 2) {
  479. result.displays.push({
  480. vendor: '',
  481. model: util.getValue(lines, 'device_name', '='),
  482. main: true,
  483. builtin: false,
  484. connection: 'HDMI',
  485. sizex: -1,
  486. sizey: -1,
  487. pixeldepth: -1,
  488. resolutionx: parseInt(parts[0], 10),
  489. resolutiony: parseInt(parts[1], 10),
  490. currentResX: -1,
  491. currentResY: -1,
  492. positionX: 0,
  493. positionY: 0,
  494. currentRefreshRate: -1
  495. });
  496. }
  497. }
  498. if (lines.length > 1 && lines[1].indexOf('gpu=') >= -1) {
  499. result.controllers.push({
  500. vendor: 'Broadcom',
  501. model: 'VideoCore IV',
  502. bus: '',
  503. vram: lines[1].replace('gpu=', ''),
  504. vramDynamic: true
  505. });
  506. }
  507. if (callback) {
  508. callback(result);
  509. }
  510. resolve(result);
  511. });
  512. } else {
  513. let cmd = 'lspci -vvv 2>/dev/null';
  514. exec(cmd, function (error, stdout) {
  515. if (!error) {
  516. let lines = stdout.toString().split('\n');
  517. result.controllers = parseLinesLinuxControllers(lines);
  518. }
  519. let cmd = 'xdpyinfo 2>/dev/null | grep \'depth of root window\' | awk \'{ print $5 }\'';
  520. exec(cmd, function (error, stdout) {
  521. let depth = 0;
  522. if (!error) {
  523. let lines = stdout.toString().split('\n');
  524. depth = parseInt(lines[0]) || 0;
  525. }
  526. let cmd = 'xrandr --verbose 2>/dev/null';
  527. exec(cmd, function (error, stdout) {
  528. if (!error) {
  529. let lines = stdout.toString().split('\n');
  530. result.displays = parseLinesLinuxDisplays(lines, depth);
  531. }
  532. if (callback) {
  533. callback(result);
  534. }
  535. resolve(result);
  536. });
  537. });
  538. });
  539. }
  540. }
  541. if (_freebsd || _openbsd || _netbsd) {
  542. if (callback) { callback(result); }
  543. resolve(result);
  544. }
  545. if (_sunos) {
  546. if (callback) { callback(result); }
  547. resolve(result);
  548. }
  549. if (_windows) {
  550. // https://blogs.technet.microsoft.com/heyscriptingguy/2013/10/03/use-powershell-to-discover-multi-monitor-information/
  551. // https://devblogs.microsoft.com/scripting/use-powershell-to-discover-multi-monitor-information/
  552. try {
  553. const workload = [];
  554. workload.push(util.wmic('path win32_VideoController get /value'));
  555. workload.push(util.wmic('path win32_desktopmonitor get /value'));
  556. workload.push(util.powerShell('Get-CimInstance -Namespace root\\wmi -ClassName WmiMonitorBasicDisplayParams | fl'));
  557. workload.push(util.powerShell('Add-Type -AssemblyName System.Windows.Forms; [System.Windows.Forms.Screen]::AllScreens'));
  558. workload.push(util.powerShell('Get-CimInstance -Namespace root\\wmi -ClassName WmiMonitorConnectionParams | fl'));
  559. 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}'));
  560. Promise.all(
  561. workload
  562. ).then(data => {
  563. // controller
  564. let csections = data[0].split(/\n\s*\n/);
  565. result.controllers = parseLinesWindowsControllers(csections);
  566. // displays
  567. let dsections = data[1].split(/\n\s*\n/);
  568. // result.displays = parseLinesWindowsDisplays(dsections);
  569. dsections.shift();
  570. dsections.pop();
  571. // monitor (powershell)
  572. let msections = data[2].split('Active ');
  573. msections.shift();
  574. // forms.screens (powershell)
  575. let ssections = data[3].split('BitsPerPixel ');
  576. ssections.shift();
  577. // connection params (powershell) - video type
  578. let tsections = data[4].split(/\n\s*\n/);
  579. tsections.shift();
  580. // monitor ID (powershell) - model / vendor
  581. const res = data[5].split(/\r\n/);
  582. let isections = [];
  583. res.forEach(element => {
  584. const parts = element.split('|');
  585. if (parts.length === 5) {
  586. isections.push({
  587. vendor: parts[0],
  588. code: parts[1],
  589. model: parts[2],
  590. serial: parts[3],
  591. instanceId: parts[4]
  592. });
  593. }
  594. });
  595. result.displays = parseLinesWindowsDisplaysPowershell(ssections, msections, dsections, tsections, isections);
  596. if (result.displays.length === 1) {
  597. if (_resolutionx) {
  598. result.displays[0].resolutionx = _resolutionx;
  599. if (!result.displays[0].currentResX) {
  600. result.displays[0].currentResX = _resolutionx;
  601. }
  602. }
  603. if (_resolutiony) {
  604. result.displays[0].resolutiony = _resolutiony;
  605. if (result.displays[0].currentResY === 0) {
  606. result.displays[0].currentResY = _resolutiony;
  607. }
  608. }
  609. if (_pixeldepth) {
  610. result.displays[0].pixeldepth = _pixeldepth;
  611. }
  612. if (_refreshrate && !result.displays[0].refreshrate) {
  613. result.displays[0].currentRefreshRate = _refreshrate;
  614. }
  615. }
  616. if (callback) {
  617. callback(result);
  618. }
  619. resolve(result);
  620. })
  621. .catch(() => {
  622. if (callback) {
  623. callback(result);
  624. }
  625. resolve(result);
  626. });
  627. } catch (e) {
  628. if (callback) { callback(result); }
  629. resolve(result);
  630. }
  631. }
  632. });
  633. });
  634. function parseLinesWindowsControllers(sections) {
  635. let controllers = [];
  636. for (let i in sections) {
  637. if ({}.hasOwnProperty.call(sections, i)) {
  638. if (sections[i].trim() !== '') {
  639. let lines = sections[i].trim().split('\r\n');
  640. controllers.push({
  641. vendor: util.getValue(lines, 'AdapterCompatibility', '='),
  642. model: util.getValue(lines, 'name', '='),
  643. bus: util.getValue(lines, 'PNPDeviceID', '=').startsWith('PCI') ? 'PCI' : '',
  644. vram: parseInt(util.getValue(lines, 'AdapterRAM', '='), 10) / 1024 / 1024,
  645. vramDynamic: (util.getValue(lines, 'VideoMemoryType', '=') === '2')
  646. });
  647. _resolutionx = util.toInt(util.getValue(lines, 'CurrentHorizontalResolution', '=')) || _resolutionx;
  648. _resolutiony = util.toInt(util.getValue(lines, 'CurrentVerticalResolution', '=')) || _resolutiony;
  649. _refreshrate = util.toInt(util.getValue(lines, 'CurrentRefreshRate', '=')) || _refreshrate;
  650. _pixeldepth = util.toInt(util.getValue(lines, 'CurrentBitsPerPixel', '=')) || _pixeldepth;
  651. }
  652. }
  653. }
  654. return controllers;
  655. }
  656. // function parseLinesWindowsDisplays(sections) {
  657. // let displays = [];
  658. // for (let i in sections) {
  659. // if (sections.hasOwnProperty(i)) {
  660. // if (sections[i].trim() !== '') {
  661. // let lines = sections[i].trim().split('\r\n');
  662. // displays.push({
  663. // vendor: util.getValue(lines, 'MonitorManufacturer', '='),
  664. // model: util.getValue(lines, 'Name', '='),
  665. // main: false,
  666. // builtin: false,
  667. // connection: '',
  668. // sizex: -1,
  669. // sizey: -1,
  670. // pixeldepth: -1,
  671. // resolutionx: util.toInt(util.getValue(lines, 'ScreenWidth', '=')),
  672. // resolutiony: util.toInt(util.getValue(lines, 'ScreenHeight', '=')),
  673. // });
  674. // }
  675. // }
  676. // }
  677. // return displays;
  678. // }
  679. function parseLinesWindowsDisplaysPowershell(ssections, msections, dsections, tsections, isections) {
  680. let displays = [];
  681. let vendor = '';
  682. let model = '';
  683. let deviceID = '';
  684. let resolutionx = 0;
  685. let resolutiony = 0;
  686. if (dsections && dsections.length) {
  687. let linesDisplay = dsections[0].split(os.EOL);
  688. vendor = util.getValue(linesDisplay, 'MonitorManufacturer', '=');
  689. model = util.getValue(linesDisplay, 'Name', '=');
  690. deviceID = util.getValue(linesDisplay, 'PNPDeviceID', '=').replace(/&amp;/g, '&').toLowerCase();
  691. resolutionx = util.toInt(util.getValue(linesDisplay, 'ScreenWidth', '='));
  692. resolutiony = util.toInt(util.getValue(linesDisplay, 'ScreenHeight', '='));
  693. }
  694. for (let i = 0; i < ssections.length; i++) {
  695. if (ssections[i].trim() !== '') {
  696. ssections[i] = 'BitsPerPixel ' + ssections[i];
  697. msections[i] = 'Active ' + msections[i];
  698. // tsections can be empty on earlier versions of powershell (<=2.0). Tag connection type as UNKNOWN
  699. // if this information is missing
  700. if (tsections.length === 0) {
  701. tsections[i] = 'Unknown';
  702. }
  703. let linesScreen = ssections[i].split(os.EOL);
  704. let linesMonitor = msections[i].split(os.EOL);
  705. let linesConnection = tsections[i].split(os.EOL);
  706. const bitsPerPixel = util.getValue(linesScreen, 'BitsPerPixel');
  707. const bounds = util.getValue(linesScreen, 'Bounds').replace('{', '').replace('}', '').split(',');
  708. const primary = util.getValue(linesScreen, 'Primary');
  709. const sizex = util.getValue(linesMonitor, 'MaxHorizontalImageSize');
  710. const sizey = util.getValue(linesMonitor, 'MaxVerticalImageSize');
  711. const instanceName = util.getValue(linesMonitor, 'InstanceName').toLowerCase();
  712. const videoOutputTechnology = util.getValue(linesConnection, 'VideoOutputTechnology');
  713. const deviceName = util.getValue(linesScreen, 'DeviceName');
  714. let displayVendor = '';
  715. let displayModel = '';
  716. isections.forEach(element => {
  717. if (element.instanceId.toLowerCase().startsWith(instanceName) && vendor.startsWith('(') && model.startsWith('PnP')) {
  718. displayVendor = element.vendor;
  719. displayModel = element.model;
  720. }
  721. });
  722. displays.push({
  723. vendor: instanceName.startsWith(deviceID) && displayVendor === '' ? vendor : displayVendor,
  724. model: instanceName.startsWith(deviceID) && displayModel === '' ? model : displayModel,
  725. deviceName,
  726. main: primary.toLowerCase() === 'true',
  727. builtin: videoOutputTechnology === '2147483648',
  728. connection: videoOutputTechnology && videoTypes[videoOutputTechnology] ? videoTypes[videoOutputTechnology] : '',
  729. resolutionx: util.toInt(util.getValue(bounds, 'Width', '=')),
  730. resolutiony: util.toInt(util.getValue(bounds, 'Height', '=')),
  731. sizex: sizex ? parseInt(sizex, 10) : -1,
  732. sizey: sizey ? parseInt(sizey, 10) : -1,
  733. pixeldepth: bitsPerPixel,
  734. currentResX: util.toInt(util.getValue(bounds, 'Width', '=')),
  735. currentResY: util.toInt(util.getValue(bounds, 'Height', '=')),
  736. positionX: util.toInt(util.getValue(bounds, 'X', '=')),
  737. positionY: util.toInt(util.getValue(bounds, 'Y', '=')),
  738. });
  739. }
  740. }
  741. if (ssections.length === 0) {
  742. displays.push({
  743. vendor,
  744. model,
  745. main: true,
  746. resolutionx,
  747. resolutiony,
  748. sizex: -1,
  749. sizey: -1,
  750. pixeldepth: -1,
  751. currentResX: resolutionx,
  752. currentResY: resolutiony,
  753. positionX: 0,
  754. positionY: 0
  755. });
  756. }
  757. return displays;
  758. }
  759. }
  760. exports.graphics = graphics;