From 1db71616878c9af884b2746700c192cbbcfec827 Mon Sep 17 00:00:00 2001 From: Sebastian Hildebrandt Date: Wed, 14 Sep 2016 09:31:28 +0200 Subject: [PATCH] added graphics info (controller and display) --- README.md | 18 +++ lib/index.js | 328 ++++++++++++++++++++++++++++++++++++++++++++++++--- package.json | 6 +- 3 files changed, 333 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index 5619b46..10f16d0 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,7 @@ si.cpu() ### Latest Activity +- Version 3.5.0: added graphics info (controller and display). - Version 3.4.0: rewritten currentLoad and CPU load for processes (linux). This is now much more accurate. - Version 3.3.0: added process list. Get full process list including details like cpu and mem usage, status, command, ... - Version 3.2.0: added battery support. If a battery is installed, you get information about status and current capacity level @@ -68,6 +69,7 @@ Here all changes more detailed: New Functions +- `graphics`: returns arrays of graphics controllers and displays (new in version 3.5) - `networkInterfaceDefault`: returns default network interface (new in version 3.4) - `processes`: now returns also a process list with all process details (new in version 3.3) - `battery`: retrieves battery status and charging level (new in version 3.2) @@ -215,6 +217,21 @@ This library is splitted in several sections: | - maxcapacity | X | X | max capacity of battery | | - currentcapacity | X | X | current capacity of battery | | - percent | X | X | charging level in percent | +| si.graphics(cb) | X | X | arrays of graphics controllers and displays | +| - controllers[0].model | X | X | graphics controller model | +| - controllers[0].vendor | X | X | e.g. ATI | +| - controllers[0].bus | X | X | on which bus (e.g. PCIe) | +| - controllers[0].vram | X | X | VRAM size (in MB) | +| - controllers[0].vramDynamic | X | X | true if dynamicly allocated ram | +| - displays[0].model | X | X | Monitor/Display Model | +| - displays[0].main | X | X | true if main monitor | +| - displays[0].builtin | X | X | true if built in monitor | +| - displays[0].connection | X | X | e.g. DisplayPort or HDMI | +| - displays[0].resolutionx | X | X | pixel horizontal | +| - displays[0].resolutiony | X | X | pixel vertical | +| - displays[0].depth | X | X | color depth in bits | +| - displays[0].sizex | X | X | size in mm horizontal | +| - displays[0].sizey | X | X | size in mm vertical | | si.fsSize(cb) | X | X | returns array of mounted file systems | | - [0].fs | X | X | name of file system | | - [0].size | X | X | sizes in Bytes | @@ -377,6 +394,7 @@ I am happy to discuss any comments and suggestions. Please feel free to contact | Version | Date | Comment | | -------------- | -------------- | -------- | +| 3.5.0 | 2016-09-14 | added graphics info (controller, display) | | 3.4.4 | 2016-09-02 | tiny fixes system.model, getDefaultNetworkInterface | | 3.4.3 | 2016-09-02 | tiny bug fix fsStats, disksIO OSX | | 3.4.2 | 2016-09-01 | improved default network interface | diff --git a/lib/index.js b/lib/index.js index a4675ae..536fdfe 100644 --- a/lib/index.js +++ b/lib/index.js @@ -23,13 +23,14 @@ // 4. CPU // 5. Memory // 6. Battery -// 7. File System -// 8. Network -// 9. Processes -// 10. Users/Sessions -// 11. Internet -// 12. Docker -// 13. GetAll - get all data +// 7. Graphics +// 8. File System +// 9. Network +// 10. Processes +// 11. Users/Sessions +// 12. Internet +// 13. Docker +// 14. GetAll - get all data // // ================================================================================== // @@ -80,6 +81,7 @@ // -------------------------------- // // version date comment +// 3.5.0 2016-09-14 added graphics info (controller, display) // 3.4.4 2016-09-02 tiny fixes system.model, getDefaultNetworkInterface // 3.4.3 2016-09-02 tiny bug fix fsStats, disksIO OSX // 3.4.2 2016-09-01 improved default network interface @@ -908,7 +910,294 @@ function battery(callback) { exports.battery = battery; // ---------------------------------------------------------------------------------- -// 7. File System +// 7. Graphics (controller, display) +// ---------------------------------------------------------------------------------- + +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 is_vga = 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'); + if (vgapos != -1) { // VGA + if (Object.keys(currentController).length > 0) {// already a controller found + controllers.push(currentController); + currentController = {}; + } + is_vga = 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')) { + 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 { + is_vga = false; + } + } + if (is_vga) { // 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(() => { + if (_windows) { + let error = new Error(NOT_SUPPORTED); + if (callback) { + callback(NOT_SUPPORTED) + } + reject(error); + } + let result = {}; + 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); + }) + }) + } + }) + } + }); + }); +} + +exports.graphics = graphics; +// ---------------------------------------------------------------------------------- +// 8. File System // ---------------------------------------------------------------------------------- // -------------------------- @@ -1166,7 +1455,7 @@ function disksIO(callback) { exports.disksIO = disksIO; // ---------------------------------------------------------------------------------- -// 8. Network +// 9. Network // ---------------------------------------------------------------------------------- function getDefaultNetworkInterface() { @@ -1528,7 +1817,7 @@ function networkConnections(callback) { exports.networkConnections = networkConnections; // ---------------------------------------------------------------------------------- -// 9. Processes +// 10. Processes // ---------------------------------------------------------------------------------- // -------------------------- @@ -2057,7 +2346,7 @@ function processLoad(proc, callback) { exports.processLoad = processLoad; // ---------------------------------------------------------------------------------- -// 10. Users/Sessions +// 11. Users/Sessions // ---------------------------------------------------------------------------------- // -------------------------- @@ -2240,7 +2529,7 @@ function users(callback) { exports.users = users; // ---------------------------------------------------------------------------------- -// 11. Internet +// 12. Internet // ---------------------------------------------------------------------------------- // -------------------------- @@ -2329,7 +2618,7 @@ function inetLatency(host, callback) { exports.inetLatency = inetLatency; // ---------------------------------------------------------------------------------- -// 12. Docker +// 13. Docker // ---------------------------------------------------------------------------------- // -------------------------- @@ -2615,7 +2904,7 @@ function dockerAll(callback) { exports.dockerAll = dockerAll; // ---------------------------------------------------------------------------------- -// 13. get all +// 14. get all // ---------------------------------------------------------------------------------- // -------------------------- @@ -2641,10 +2930,13 @@ function getStaticData(callback) { data.os = res; cpu().then(res => { data.cpu = res; - networkInterfaces().then(res => { - data.net = res; - if (callback) { callback(data) } - resolve(data); + graphics().then(res => { + data.graphics = res; + networkInterfaces().then(res => { + data.net = res; + if (callback) { callback(data) } + resolve(data); + }) }) }) }) diff --git a/package.json b/package.json index 1595288..c1060ae 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,11 @@ "users", "internet", "battery", - "docker" + "docker", + "graphics", + "graphic card", + "graphic controller", + "display" ], "repository": { "type": "git",