399 lines
16 KiB
JavaScript
399 lines
16 KiB
JavaScript
'use strict';
|
|
// ==================================================================================
|
|
// graphics.js
|
|
// ----------------------------------------------------------------------------------
|
|
// Description: System Information - library
|
|
// for Node.js
|
|
// Copyright: (c) 2014 - 2017
|
|
// Author: Sebastian Hildebrandt
|
|
// ----------------------------------------------------------------------------------
|
|
// License: MIT
|
|
// ==================================================================================
|
|
// 7. Graphics (controller, display)
|
|
// ----------------------------------------------------------------------------------
|
|
|
|
const os = require('os');
|
|
const exec = require('child_process').exec;
|
|
const util = require('./util');
|
|
|
|
let _platform = os.type();
|
|
|
|
const _linux = (_platform === 'Linux');
|
|
const _darwin = (_platform === 'Darwin');
|
|
const _windows = (_platform === 'Windows_NT');
|
|
let _resolutionx = 0;
|
|
let _resolutiony = 0;
|
|
let _pixeldepth = 0;
|
|
|
|
function toInt(value) {
|
|
let result = parseInt(value, 10);
|
|
if (isNaN(result)) {
|
|
result = 0;
|
|
}
|
|
return result
|
|
}
|
|
|
|
function graphics(callback) {
|
|
|
|
function parseLinesDarwin(lines) {
|
|
let starts = [];
|
|
let level = -1;
|
|
let lastlevel = -1;
|
|
let controllers = [];
|
|
let displays = [];
|
|
let currentController = {};
|
|
let currentDisplay = {};
|
|
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) {// just changed to Displays
|
|
controllers.push(currentController);
|
|
currentController = {};
|
|
}
|
|
if (Object.keys(currentDisplay).length > 0) {// just changed to Displays
|
|
displays.push(currentDisplay);
|
|
currentDisplay = {};
|
|
}
|
|
}
|
|
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('bus') !== -1) currentController.bus = 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
|
|
currentController.vramDynamic = false;
|
|
}
|
|
if (parts.length > 1 && parts[0].replace(/ +/g, "").toLowerCase().indexOf('vram(dynamic,max)') !== -1) {
|
|
currentController.vram = parseInt(parts[1]); // in MB
|
|
currentController.vramDynamic = true;
|
|
}
|
|
}
|
|
if (3 === level) { // display controller level
|
|
if (parts.length > 1 && '' === parts[1]) {
|
|
currentDisplay.model = parts[0].trim();
|
|
currentDisplay.main = false;
|
|
currentDisplay.builtin = false;
|
|
currentDisplay.connection = '';
|
|
currentDisplay.sizex = -1;
|
|
currentDisplay.sizey = -1;
|
|
}
|
|
}
|
|
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');
|
|
currentDisplay.resolutionx = (resolution.length > 1 ? parseInt(resolution[0]) : 0);
|
|
currentDisplay.resolutiony = (resolution.length > 1 ? parseInt(resolution[1]) : 0);
|
|
}
|
|
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('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.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 (Object.keys(currentController).length > 0) {// just changed to Displays
|
|
controllers.push(currentController);
|
|
}
|
|
if (Object.keys(currentDisplay).length > 0) {// just changed to Displays
|
|
displays.push(currentDisplay);
|
|
}
|
|
return ({
|
|
controllers: controllers,
|
|
displays: displays
|
|
})
|
|
}
|
|
|
|
function parseLinesLinuxControllers(lines) {
|
|
let controllers = [];
|
|
let currentController = {};
|
|
let isGraphicsController = false;
|
|
for (let i = 0; i < lines.length; i++) {
|
|
if ('' !== lines[i].trim()) {
|
|
if (' ' !== lines[i][0] && '\t' !== lines[i][0]) { // first line of new entry
|
|
let vgapos = lines[i].toLowerCase().indexOf('vga');
|
|
let _3dcontrollerpos = lines[i].toLowerCase().indexOf('3d controller');
|
|
if (vgapos !== -1 || _3dcontrollerpos !== -1) { // VGA
|
|
if (_3dcontrollerpos !== -1 && vgapos === -1) {
|
|
vgapos = _3dcontrollerpos;
|
|
}
|
|
if (Object.keys(currentController).length > 0) {// already a controller found
|
|
controllers.push(currentController);
|
|
currentController = {};
|
|
}
|
|
isGraphicsController = true;
|
|
let endpos = lines[i].search(/\[[0-9a-f]{4}:[0-9a-f]{4}]|$/);
|
|
let parts = lines[i].substr(vgapos, endpos - vgapos).split(':');
|
|
if (parts.length > 1) {
|
|
parts[1] = parts[1].trim();
|
|
if (parts[1].toLowerCase().indexOf('corporation') >= 0) {
|
|
currentController.vendor = parts[1].substr(0, parts[1].toLowerCase().indexOf('corporation') + 11).trim();
|
|
currentController.model = parts[1].substr(parts[1].toLowerCase().indexOf('corporation') + 11, 200).trim().split('(')[0];
|
|
currentController.bus = '';
|
|
currentController.vram = -1;
|
|
currentController.vramDynamic = false;
|
|
} else if (parts[1].toLowerCase().indexOf(' inc.') >= 0) {
|
|
if ((parts[1].match(new RegExp("]", "g")) || []).length > 1) {
|
|
currentController.vendor = parts[1].substr(0, parts[1].toLowerCase().indexOf(']') + 1).trim();
|
|
currentController.model = parts[1].substr(parts[1].toLowerCase().indexOf(']')+1, 200).trim().split('(')[0];
|
|
} else {
|
|
currentController.vendor = parts[1].substr(0, parts[1].toLowerCase().indexOf(' inc.') + 5).trim();
|
|
currentController.model = parts[1].substr(parts[1].toLowerCase().indexOf(' inc.') + 5, 200).trim().split('(')[0];
|
|
}
|
|
currentController.bus = '';
|
|
currentController.vram = -1;
|
|
currentController.vramDynamic = false;
|
|
}
|
|
}
|
|
|
|
} else {
|
|
isGraphicsController = false;
|
|
}
|
|
}
|
|
if (isGraphicsController) { // within VGA details
|
|
let parts = lines[i].split(':');
|
|
if (parts.length > 1 && parts[0].replace(/ +/g, "").toLowerCase().indexOf('devicename') !== -1 && parts[0].toLowerCase().indexOf('onboard') !== -1) currentController.bus = 'Onboard';
|
|
if (parts.length > 1 && parts[0].replace(/ +/g, "").toLowerCase().indexOf('region') !== -1 && parts[1].toLowerCase().indexOf('memory') !== -1) {
|
|
let memparts = parts[1].split("=");
|
|
if (memparts.length > 1) {
|
|
currentController.vram = parseInt(memparts[1]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (Object.keys(currentController).length > 0) {// still controller information available
|
|
controllers.push(currentController);
|
|
}
|
|
return (controllers)
|
|
}
|
|
|
|
function parseLinesLinuxEdid(edid) {
|
|
// parsen EDID
|
|
// --> model
|
|
// --> resolutionx
|
|
// --> resolutiony
|
|
// --> builtin = false
|
|
// --> pixeldepth (?)
|
|
// --> sizex
|
|
// --> sizey
|
|
let result = {};
|
|
// find first "Detailed Timing Description"
|
|
let start = 108;
|
|
if (edid.substr(start, 6) === '000000') {
|
|
start += 36;
|
|
}
|
|
if (edid.substr(start, 6) === '000000') {
|
|
start += 36;
|
|
}
|
|
if (edid.substr(start, 6) === '000000') {
|
|
start += 36;
|
|
}
|
|
if (edid.substr(start, 6) === '000000') {
|
|
start += 36;
|
|
}
|
|
result.resolutionx = parseInt('0x0' + edid.substr(start + 8, 1) + edid.substr(start + 4, 2));
|
|
result.resolutiony = parseInt('0x0' + edid.substr(start + 14, 1) + edid.substr(start + 10, 2));
|
|
result.sizex = parseInt('0x0' + edid.substr(start + 28, 1) + edid.substr(start + 24, 2));
|
|
result.sizey = parseInt('0x0' + edid.substr(start + 29, 1) + edid.substr(start + 26, 2));
|
|
// monitor name
|
|
start = edid.indexOf('000000fc00'); // find first "Monitor Description Data"
|
|
if (start >= 0) {
|
|
let model_raw = edid.substr(start + 10, 26);
|
|
if (model_raw.indexOf('0a') !== -1) {
|
|
model_raw = model_raw.substr(0, model_raw.indexOf('0a'))
|
|
}
|
|
result.model = model_raw.match(/.{1,2}/g).map(function (v) {
|
|
return String.fromCharCode(parseInt(v, 16));
|
|
}).join('');
|
|
} else {
|
|
result.model = '';
|
|
}
|
|
return result;
|
|
}
|
|
|
|
function parseLinesLinuxDisplays(lines, depth) {
|
|
let displays = [];
|
|
let currentDisplay = {};
|
|
let is_edid = false;
|
|
let edid_raw = '';
|
|
let start = 0;
|
|
for (let i = 1; i < lines.length; i++) { // start with second line
|
|
if ('' !== lines[i].trim()) {
|
|
if (' ' !== lines[i][0] && '\t' !== lines[i][0] && lines[i].toLowerCase().indexOf(' connected ') !== -1) { // first line of new entry
|
|
if (Object.keys(currentDisplay).length > 0) { // push last display to array
|
|
displays.push(currentDisplay);
|
|
currentDisplay = {};
|
|
}
|
|
let parts = lines[i].split(' ');
|
|
currentDisplay.connection = parts[0];
|
|
currentDisplay.main = (parts[2] === 'primary');
|
|
currentDisplay.builtin = (parts[0].toLowerCase().indexOf('edp') >= 0)
|
|
}
|
|
|
|
// try to read EDID information
|
|
if (is_edid) {
|
|
if (lines[i].search(/\S|$/) > start) {
|
|
edid_raw += lines[i].toLowerCase().trim();
|
|
} else {
|
|
// parsen EDID
|
|
let edid_decoded = parseLinesLinuxEdid(edid_raw);
|
|
currentDisplay.model = edid_decoded.model;
|
|
currentDisplay.resolutionx = edid_decoded.resolutionx;
|
|
currentDisplay.resolutiony = edid_decoded.resolutiony;
|
|
currentDisplay.sizex = edid_decoded.sizex;
|
|
currentDisplay.sizey = edid_decoded.sizey;
|
|
currentDisplay.pixeldepth = depth;
|
|
is_edid = false;
|
|
}
|
|
}
|
|
if (lines[i].toLowerCase().indexOf('edid:') !== -1) {
|
|
is_edid = true;
|
|
start = lines[i].search(/\S|$/);
|
|
}
|
|
}
|
|
}
|
|
|
|
// pushen displays
|
|
if (Object.keys(currentDisplay).length > 0) { // still information there
|
|
displays.push(currentDisplay);
|
|
}
|
|
return displays
|
|
}
|
|
|
|
// function starts here
|
|
return new Promise((resolve, reject) => {
|
|
process.nextTick(() => {
|
|
let result = {
|
|
controllers: [],
|
|
displays: []
|
|
};
|
|
if (_darwin) {
|
|
let cmd = 'system_profiler SPDisplaysDataType';
|
|
exec(cmd, function (error, stdout) {
|
|
if (!error) {
|
|
let lines = stdout.toString().split('\n');
|
|
result = parseLinesDarwin(lines);
|
|
}
|
|
if (callback) {
|
|
callback(result)
|
|
}
|
|
resolve(result);
|
|
})
|
|
}
|
|
if (_linux) {
|
|
let cmd = 'lspci -vvv 2>/dev/null';
|
|
exec(cmd, function (error, stdout) {
|
|
if (!error) {
|
|
let lines = stdout.toString().split('\n');
|
|
result.controllers = parseLinesLinuxControllers(lines);
|
|
}
|
|
let cmd = "xdpyinfo 2>/dev/null | grep 'depth of root window' | awk '{ print $5 }'";
|
|
exec(cmd, function (error, stdout) {
|
|
let depth = 0;
|
|
if (!error) {
|
|
let lines = stdout.toString().split('\n');
|
|
depth = parseInt(lines[0]) || 0;
|
|
}
|
|
let cmd = 'xrandr --verbose 2>/dev/null';
|
|
exec(cmd, function (error, stdout) {
|
|
if (!error) {
|
|
let lines = stdout.toString().split('\n');
|
|
result.displays = parseLinesLinuxDisplays(lines, depth);
|
|
}
|
|
if (callback) {
|
|
callback(result)
|
|
}
|
|
resolve(result);
|
|
})
|
|
})
|
|
})
|
|
}
|
|
if (_windows) {
|
|
// https://blogs.technet.microsoft.com/heyscriptingguy/2013/10/03/use-powershell-to-discover-multi-monitor-information/
|
|
exec("wmic path win32_VideoController get AdapterCompatibility, AdapterDACType, name, PNPDeviceID, CurrentVerticalResolution, CurrentHorizontalResolution, CurrentNumberOfColors, AdapterRAM, CurrentBitsPerPixel, CurrentRefreshRate, MinRefreshRate, MaxRefreshRate, VideoMemoryType /value", function (error, stdout) {
|
|
if (!error) {
|
|
let csections = stdout.split(/\n\s*\n/);
|
|
result.controllers = parseLinesWindowsControllers(csections);
|
|
exec("wmic path win32_desktopmonitor get MonitorManufacturer, ScreenWidth, ScreenHeight /value", function (error, stdout) {
|
|
let dsections = stdout.split(/\n\s*\n/);
|
|
if (!error) {
|
|
result.displays = parseLinesWindowsDisplays(dsections);
|
|
if (result.controllers.length === 1 && result.displays.length === 1) {
|
|
if (_resolutionx && !result.displays[0].resolutionx) {
|
|
result.displays[0].resolutionx = _resolutionx
|
|
}
|
|
if (_resolutiony && !result.displays[0].resolutiony) {
|
|
result.displays[0].resolutiony = _resolutiony
|
|
}
|
|
if (_pixeldepth) {
|
|
result.displays[0].pixeldepth = _pixeldepth
|
|
}
|
|
}
|
|
}
|
|
if (callback) {
|
|
callback(result)
|
|
}
|
|
resolve(result);
|
|
})
|
|
}
|
|
})
|
|
}
|
|
});
|
|
});
|
|
|
|
function parseLinesWindowsControllers(sections) {
|
|
let controllers = [];
|
|
for (let i in sections) {
|
|
if (sections.hasOwnProperty(i)) {
|
|
if (sections[i].trim() !== "") {
|
|
|
|
let lines = sections[i].trim().split('\r\n');
|
|
controllers.push({
|
|
model: util.getValue(lines, 'name', '='),
|
|
vendor: util.getValue(lines, 'AdapterCompatibility', '='),
|
|
bus: util.getValue(lines, 'PNPDeviceID', '=').startsWith('PCI') ? 'PCI' : '',
|
|
vram: util.getValue(lines, 'AdapterRAM', '='),
|
|
vramDynamic: (util.getValue(lines, 'VideoMemoryType', '=') === '2')
|
|
});
|
|
_resolutionx = toInt(util.getValue(lines, 'CurrentHorizontalResolution', '='));
|
|
_resolutiony = toInt(util.getValue(lines, 'CurrentVerticalResolution', '='));
|
|
_pixeldepth = toInt(util.getValue(lines, 'CurrentBitsPerPixel', '='));
|
|
}
|
|
}
|
|
}
|
|
return controllers;
|
|
}
|
|
|
|
function parseLinesWindowsDisplays(sections) {
|
|
let displays = [];
|
|
for (let i in sections) {
|
|
if (sections.hasOwnProperty(i)) {
|
|
if (sections[i].trim() !== "") {
|
|
let lines = sections[i].trim().split('\r\n');
|
|
displays.push({
|
|
model: util.getValue(lines, 'MonitorManufacturer', '='),
|
|
resolutionx: toInt(util.getValue(lines, 'ScreenWidth', '=')),
|
|
resolutiony: toInt(util.getValue(lines, 'ScreenHeight', '=')),
|
|
});
|
|
}
|
|
}
|
|
}
|
|
return displays;
|
|
}
|
|
}
|
|
|
|
exports.graphics = graphics;
|