diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..c88bb12 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 munkireport + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md old mode 100644 new mode 100755 index ddabf96..5a0b426 --- a/README.md +++ b/README.md @@ -3,6 +3,19 @@ Displays Information module Collects some relevant information from the output of `system_profiler -xml SPDisplaysDataType` +Configuration +------ + +Displays infomation module has two settings that can be managed by adding them to the server environment variables or the `.env` file. + +``` +keep_previous_displays=TRUE +show_virtual_displays=TRUE +``` + +Table Schema +-------- + This is the table of values for 'displays': * type (bool) Wether the display is internal (built-in) or external @@ -10,9 +23,39 @@ This is the table of values for 'displays': * serial_number (string) Serial number of the computer it's connected to * vendor (string) Public name translated by the model from hex value * model (string) Model reported -* manufactured (string) Aproximate date when it was manufactured +* manufactured (string) Approximate date when it was manufactured * native (string) Native resolution * timestamp (int) UNIX timestamp +* ui_resolution (string) Resolution of the user interface +* current_resolution (string) Current resolution +* color_depth (string) Color depth in use by the framebuffer +* display_type (string) Type of display; LCD/CRT/Projector +* main_display (boolean) Is main display +* mirror (boolean) Is Mirrored +* mirror_status (string) Mirroring status +* online (boolean) Display online +* interlaced (boolean) Interlacing in use +* rotation_supported (boolean) Supports rotation +* television (boolean) Is a television +* display_asleep (boolean) Is display asleep +* ambient_brightness (boolean) Ambient brightness set +* automatic_graphics_switching (boolean) Automatic graphics switching in use +* retina (boolean) Is Retina display +* edr_enabled (boolean) EDR enabled +* edr_limit (float) EDR limit +* edr_supported (boolean) Supports EDR +* connection_type (string) Type of display connection in use +* dp_dpcd_version (string) Version of DisplayPort +* dp_current_bandwidth (string) Current bandwidth of DisplayPort +* dp_current_lanes (int) Current number of lanes in use by DisplayPort +* dp_current_spread (string) Current DisplayPort spread +* dp_hdcp_capability (boolean) Supports HDCP +* dp_max_bandwidth (string) Maximum DisplayPort bandwidth +* dp_max_lanes (int) Maximum DisplayPort lanes +* dp_max_spread (string) Maximum DisplayPort spread +* virtual_device (boolean) Is virtual display device +* dynamic_range (string) Dynamic range currently in use +* dp_adapter_firmware_version (string) Firmware version of DisplayPort adapter Remarks --- @@ -52,17 +95,3 @@ Nonetheless you can configure it to keep old data by adding this to your config. * Laptop1 reports in the morning about the built-in display and an external display --> mr-php stores and displays both * The display is broken/stolen/etc. * The display remains associated with Laptop1 until you remove Laptop1 from MR - -Configuration -------------- -Displays module history option - -By default this module overrides the information of a client computer -on each client's report submission. -If you would like to keep displays information until the display is seen again -on a different computer use: -`DISPLAYS_INFO_KEEP_PREVIOUS=TRUE` -When not configured, or if set to FALSE, the default behaviour applies. -``` -DISPLAYS_INFO_KEEP_PREVIOUS=FALSE -``` diff --git a/composer.json b/composer.json index f246fc7..9de17ad 100644 --- a/composer.json +++ b/composer.json @@ -1,5 +1,8 @@ { "name": "munkireport/displays_info", - "description": "Module for munkireport.", - "license": "MIT" + "description": "Displays_info module for munkireport.", + "license": "MIT", + "require": { + "php":"^7.0" + } } diff --git a/config.php b/config.php old mode 100644 new mode 100755 index 7dc20f4..35b10d0 --- a/config.php +++ b/config.php @@ -1,5 +1,23 @@ env('DISPLAYS_INFO_KEEP_PREVIOUS', true), -]; + /* + |============================================================ + | Displays module history and virtual displays option + |============================================================ + | + | By default this module overrides the information of a client computer + | on each client's report submission. + | + | If you would like to keep displays information until the display is seen again + | on a different computer use: + | $conf['keep_previous_displays'] = TRUE; + | + | When not configured, or if set to FALSE, the default behaviour applies. + | + | By default the displays_info module saves virtual displays. To disable them + | set show_virtual_displays to FALSE. + | + */ + $conf['keep_previous_displays'] = env('DISPLAYS_INFO_KEEP_PREVIOUS', true), + $conf['show_virtual_displays'] = env('SHOW_VIRTUAL_DISPLAYS', true), +]; \ No newline at end of file diff --git a/displays_info_controller.php b/displays_info_controller.php old mode 100644 new mode 100755 index a137f09..1e3c4d7 --- a/displays_info_controller.php +++ b/displays_info_controller.php @@ -23,7 +23,7 @@ public function __construct() **/ public function index() { - echo "You've loaded the displays module!"; + echo "You've loaded the displays_info module!"; } /** @@ -33,23 +33,25 @@ public function index() **/ public function get_data($serial = '') { - $out = array(); + $obj = new View(); + if (! $this->authorized()) { - $out['error'] = 'Not authorized'; - } else { - $prm = new Displays_info_model; - foreach ($prm->retrieve_records($serial) as $display) { - $out[] = $display->rs; - } + $obj->view('json', array('msg' => 'Not authorized')); + return; } - $obj = new View(); - $obj->view('json', array('msg' => $out)); + $queryobj = new Displays_info_model(); + + $sql = "SELECT vendor, type, display_type, display_serial, manufactured, native, ui_resolution, current_resolution, color_depth, connection_type, online, main_display, display_asleep, retina, mirror, mirror_status, interlaced, rotation_supported, television, ambient_brightness, automatic_graphics_switching, virtual_device, edr_supported, edr_enabled, edr_limit, dp_dpcd_version, dp_current_bandwidth, dp_current_lanes, dp_current_spread, dp_hdcp_capability, dp_max_bandwidth, dp_max_lanes, dp_max_spread, dynamic_range, dp_adapter_firmware_version, timestamp, model + FROM displays + WHERE serial_number = '$serial'"; + + $displays_tab = $queryobj->query($sql); + $obj->view('json', array('msg' => current(array('msg' => $displays_tab)))); } /** - * Get count of displays - * + * Get count of displays * * @param int $type type 1 is external, type 0 is internal **/ @@ -65,4 +67,4 @@ public function get_count($type = 1) $dim = new Displays_info_model(); $obj->view('json', array('msg' => $dim->get_count($type))); } -} // END class default_module +} // END class displays_info diff --git a/displays_info_model.php b/displays_info_model.php old mode 100644 new mode 100755 index f719c8c..dc195ba --- a/displays_info_model.php +++ b/displays_info_model.php @@ -1,40 +1,72 @@ rs['id'] = ''; - $this->rs['type'] = 0; // Built-in = 0 ; External =1 - $this->rs['display_serial'] = ''; // Serial num of the display, if any - $this->rs['serial_number'] = $serial; // Serial num of the computer + $this->rs['serial_number'] = $serial; // Serial number of the computer + $this->rs['type'] = 0; // Built-in = 0 ; External = 1 + $this->rs['display_serial'] = ''; // Serial number of the display, if any $this->rs['vendor'] = ''; // Vendor for the display $this->rs['model'] = ''; // Model of the display $this->rs['manufactured'] = ''; // Aprox. date when it was built $this->rs['native'] = ''; // Native resolution $this->rs['timestamp'] = 0; // Unix time when the report was uploaded + $this->rs['ui_resolution'] = ''; + $this->rs['current_resolution'] = ''; + $this->rs['color_depth'] = ''; + $this->rs['display_type'] = ''; + $this->rs['main_display'] = 0; // Boolean + $this->rs['mirror'] = 0; // Boolean + $this->rs['mirror_status'] = ''; + $this->rs['online'] = 0; // Boolean + $this->rs['interlaced'] = 0; // Boolean + $this->rs['rotation_supported'] = 0; // Boolean + $this->rs['television'] = 0; // Boolean + $this->rs['display_asleep'] = 0; // Boolean + $this->rs['ambient_brightness'] = 0; // Boolean + $this->rs['automatic_graphics_switching'] = 0; // Boolean + $this->rs['retina'] = 0; // Boolean + $this->rs['edr_enabled'] = 0; // Boolean + $this->rs['edr_limit'] = 0; + $this->rs['edr_supported'] = 0; // Boolean + $this->rs['connection_type'] = ''; + $this->rs['dp_dpcd_version'] = ''; + $this->rs['dp_current_bandwidth'] = ''; + $this->rs['dp_current_lanes'] = 0; + $this->rs['dp_current_spread'] = ''; + $this->rs['dp_hdcp_capability'] = 0; // Boolean + $this->rs['dp_max_bandwidth'] = ''; + $this->rs['dp_max_lanes'] = 0; + $this->rs['dp_max_spread'] = ''; + $this->rs['virtual_device'] = 0; // Boolean + $this->rs['dynamic_range'] = ''; + $this->rs['dp_adapter_firmware_version'] = ''; + + // Add local config + configAppendFile(__DIR__ . '/config.php'); if ($serial) { $this->retrieve_record($serial); } $this->serial = $serial; - - // Add local config - configAppendFile(__DIR__ . '/config.php'); - } //end construct + } // End construct /** - * Get count of displays + * Get count of displays * * * @param int $type type 1 is external, type 0 is internal **/ public function get_count($type) { - $sql = "SELECT COUNT(CASE WHEN type=? THEN 1 END) AS total + $sql = "SELECT COUNT(CASE WHEN type=? AND AND (virtual_device=0 OR virtual_device IS NULL) THEN 1 END) AS total FROM displays LEFT JOIN reportdata USING (serial_number) ".get_machine_group_filter(); @@ -48,59 +80,125 @@ public function get_count($type) * Process data sent by postflight * * @param string data - * @author Noel B.A. + * @author Noel B.A., reworked by tuxudo **/ public function process($data) { + // If data is empty, throw error + if (! $data) { + print_r("Error Processing Displays Info Module Request: No data found"); + } else if (substr( $data, 0, 30 ) != ' 'type', + 'Serial = ' => 'display_serial', + 'Vendor = ' => 'vendor', + 'Model = ' => 'model', + 'Manufactured = ' => 'manufactured', + 'Native = ' => 'native'); - // translate array used to match data to db fields - $translate = array('Type = ' => 'type', - 'Serial = ' => 'display_serial', - 'Vendor = ' => 'vendor', - 'Model = ' => 'model', - 'Manufactured = ' => 'manufactured', - 'Native = ' => 'native'); - - // if we didn't specify in the config that we like history then - // we nuke any data we had with this computer's s/n - if (! conf('keep_previous_displays')) { - $this->deleteWhere('serial_number=?', $this->serial_number); - $this->display_serial = null; //get rid of any s/n that was left in memory - } - // Parse data - foreach (explode("\n", $data) as $line) { - // Translate standard entries - foreach ($translate as $search => $field) { - //the separator is what triggers the save for each display - //making sure we have a valid s/n. - if ((strpos($line, '----------') === 0) && ($this->display_serial)) { - //if we have not nuked the records, do a selective delete - if (conf('keep_previous_displays')) { - $this->deleteWhere('serial_number=? AND display_serial=?', array($this->serial_number, $this->display_serial)); - } - //get a new id - $this->id = 0; - $this->save(); //the actual save - $this->display_serial = null; //unset the display s/n to avoid writing twice if multiple separators are passed - break; - } elseif (strpos($line, $search) === 0) { //else if not separator and matches - $value = substr($line, strlen($search)); //get the current value - // use bool for Type - if (strpos($value, 'Internal') === 0) { - $this->$field = 0; + // If we didn't specify in the config that we like history then + // We nuke any data we had with this computer's serial number + if (! conf('keep_previous_displays')) { + $this->deleteWhere('serial_number=?', $this->serial_number); + $this->display_serial = null; // Get rid of any serial number that was left in memory + } + + // Parse data + foreach (explode("\n", $data) as $line) { + // Translate standard entries + foreach ($translate as $search => $field) { + // The separator is what triggers the save for each display + // Making sure we have a valid serial number. + if ((strpos($line, '----------') === 0) && ($this->display_serial)) { + // If we have not nuked the records, do a selective delete + if (conf('keep_previous_displays')) { + $this->deleteWhere('serial_number=? AND display_serial=?', array($this->serial_number, $this->display_serial)); + } + // Get a new id + $this->id = 0; + $this->save(); // The actual save + $this->display_serial = null; // Unset the display serial number to avoid writing twice if multiple separators are passed break; - } elseif (strpos($value, 'External') === 0) { - $this->$field = 1; + } else if (strpos($line, $search) === 0) { // Else if not separator and matches + $value = substr($line, strlen($search)); // Get the current value + // Use bool for Type + if (strpos($value, 'Internal') === 0) { + $this->$field = 0; + break; + } else if (strpos($value, 'External') === 0) { + $this->$field = 1; + break; + } + + $this->$field = $value; break; } + } // End foreach translate - $this->$field = $value; - break; + // Timestamp added by the server + $this->timestamp = time(); + } // End foreach explode lines + + } else { // Else process with new XML handler + + // If we didn't specify in the config that we like history then + // We nuke any data we had with this computer's serial number + if (! conf('keep_previous_displays')) { + $this->deleteWhere('serial_number=?', $this->serial_number); + $this->display_serial = null; // Get rid of any serial number that was left in memory + } + + // Process incoming displays.plist + $parser = new CFPropertyList(); + $parser->parse($data, CFPropertyList::FORMAT_XML); + $plist = $parser->toArray(); + + foreach ($plist as $display) { + + // Process each display + foreach (array('type','display_serial','vendor','model','manufactured','native','ui_resolution','current_resolution','color_depth','display_type','main_display','mirror','mirror_status','online','interlaced','rotation_supported','television','display_asleep','ambient_brightness','automatic_graphics_switching','retina','edr_enabled','edr_limit','edr_supported','connection_type','dp_dpcd_version','dp_current_bandwidth','dp_current_lanes','dp_current_spread','dp_hdcp_capability','dp_max_bandwidth','dp_max_lanes','dp_max_spread','virtual_device','dynamic_range','dp_adapter_firmware_version') as $item) { + // If key does not exist in $display, null it + if ( ! array_key_exists($item, $display) && $item == 'display_serial') { + // For legacy purposes, display serial number cannot be null + $this->$item = "n/a"; + } else if ( ! array_key_exists($item, $display)) { + $this->$item = null; + // Set the db fields to be the same as those for the display + } else { + $this->$item = $display[$item]; + } } - } //end foreach translate - - //timestamp added by the server - $this->timestamp = time(); - } //end foreach explode lines - } //process function end + + // If we have not nuked the records, do a selective delete + if (conf('keep_previous_displays')) { + // Selectively delete display by matching display serial number, vendor, and native resolution + $this->deleteWhere('serial_number=? AND display_serial=? AND native=? AND vendor=?', array($this->serial_number, $this->display_serial, $this->native, $this->vendor)); + } + + // Timestamp added by the server + $this->timestamp = time(); + + // Check if we are to save virtual displays, default is yes + if (conf('show_virtual_displays')) { + // Delete previous virtual displays if we are to keep them, because they can easily change and duplicate and we only want the latest ones + $this->deleteWhere('serial_number=? AND virtual_device=?', array($this->serial_number, 1)); + + // Only save the display it has a valid native resolution + if (array_key_exists('native', $display)){ + // Save the data + $this->id = ''; + $this->save(); + } + } else { + // Only save the display it has a valid native resolution and is not a virtual display + if (array_key_exists('native', $display) && $display['virtual_device'] != 1){ + // Save the data + $this->id = ''; + $this->save(); + } + } + } + } + } // Process function end } // Displays_info_model end diff --git a/locales/en.json b/locales/en.json old mode 100644 new mode 100755 index 1dbce14..cccf973 --- a/locales/en.json +++ b/locales/en.json @@ -1,12 +1,47 @@ { "reporttitle": "Displays Report", "detected": "Detected", + "not_supported": "Not Supported", + "supported": "Supported", "machineserial": "Machine Serial", + "ui_resolution": "UI Resolution", + "current_resolution": "Current Resolution", + "color_depth": "Framebuffer Depth", + "display_type": "Display Type", + "main_display": "Main Display", + "mirror": "Mirroring", + "mirror_status": "Mirror Status", + "online": "Online", + "interlaced": "Interlaced", + "rotation_supported": "Rotation Supported", + "television": "Television", + "display_asleep": "Display Asleep", + "display_asleep_short": "Asleep", + "ambient_brightness": "Ambient Brightness Enabled", + "automatic_graphics_switching": "Automatic Graphics Switching Supported", + "retina": "Retina Display", + "retina_short": "Retina", + "edr_enabled": "EDR Enabled", + "edr_limit": "EDR Limit", + "edr_supported": "EDR Supported", + "connection_type": "Connection Type", + "dp_dpcd_version": "DisplayPort Version", + "dp_current_bandwidth": "DisplayPort Current Bandwidth", + "dp_current_lanes": "DisplayPort Current Lanes", + "dp_current_spread": "DisplayPort Current Spread", + "dp_hdcp_capability": "HDCP Capable", + "dp_max_bandwidth": "DisplayPort Maximum Bandwidth", + "dp_max_lanes": "DisplayPort Maximum Lanes", + "dp_max_spread": "DisplayPort Maximum Spread", + "virtual_device": "Virtual Display", + "dynamic_range": "Dynamic Range", + "dp_adapter_firmware_version": "DisplayPort Adapter Firmware Version", "model": "Model", + "no_displays": "No Displays", "nativeresolution": "Native Resolution", "internal": "Built-In", "displays": "Displays", - "display_serial": "Serial", + "display_serial": "Serial Number", "external": "External", "vendor": "Vendor", "model": "Model", diff --git a/migrations/2018_04_12_000001_displays_info_add_extended_columns.php b/migrations/2018_04_12_000001_displays_info_add_extended_columns.php new file mode 100755 index 0000000..83b4eb3 --- /dev/null +++ b/migrations/2018_04_12_000001_displays_info_add_extended_columns.php @@ -0,0 +1,113 @@ +table($this->tableName, function (Blueprint $table) { + $table->string('ui_resolution')->nullable(); + $table->string('current_resolution')->nullable(); + $table->string('color_depth')->nullable(); + $table->string('display_type')->nullable(); + $table->boolean('main_display')->nullable(); + $table->boolean('mirror')->nullable(); + $table->string('mirror_status')->nullable(); + $table->boolean('online')->nullable(); + $table->boolean('interlaced')->nullable(); + $table->boolean('rotation_supported')->nullable(); + $table->boolean('television')->nullable(); + $table->boolean('display_asleep')->nullable(); + $table->boolean('ambient_brightness')->nullable(); + $table->boolean('automatic_graphics_switching')->nullable(); + $table->boolean('retina')->nullable(); + $table->boolean('edr_enabled')->nullable(); + $table->float('edr_limit')->nullable(); + $table->boolean('edr_supported')->nullable(); + $table->string('connection_type')->nullable(); + $table->string('dp_dpcd_version')->nullable(); + $table->string('dp_current_bandwidth')->nullable(); + $table->integer('dp_current_lanes')->nullable(); + $table->string('dp_current_spread')->nullable(); + $table->boolean('dp_hdcp_capability')->nullable(); + $table->string('dp_max_bandwidth')->nullable(); + $table->integer('dp_max_lanes')->nullable(); + $table->string('dp_max_spread')->nullable(); + $table->boolean('virtual_device')->nullable(); + $table->string('dynamic_range')->nullable(); + $table->string('dp_adapter_firmware_version')->nullable(); + + $table->index('ui_resolution'); + $table->index('current_resolution'); + $table->index('color_depth'); + $table->index('display_type'); + $table->index('main_display'); + $table->index('mirror'); + $table->index('mirror_status'); + $table->index('online'); + $table->index('interlaced'); + $table->index('rotation_supported'); + $table->index('television'); + $table->index('display_asleep'); + $table->index('ambient_brightness'); + $table->index('automatic_graphics_switching'); + $table->index('retina'); + $table->index('edr_enabled'); + $table->index('edr_limit'); + $table->index('edr_supported'); + $table->index('connection_type'); + $table->index('dp_dpcd_version'); + $table->index('dp_current_bandwidth'); + $table->index('dp_current_lanes'); + $table->index('dp_current_spread'); + $table->index('dp_hdcp_capability'); + $table->index('dp_max_bandwidth'); + $table->index('dp_max_lanes'); + $table->index('dp_max_spread'); + $table->index('virtual_device'); + $table->index('dynamic_range'); + $table->index('dp_adapter_firmware_version'); + }); + } + + public function down() + { + $capsule = new Capsule(); + $capsule::schema()->table($this->tableName, function (Blueprint $table) { + $table->dropColumn('ui_resolution'); + $table->dropColumn('color_depth'); + $table->dropColumn('display_type'); + $table->dropColumn('main_display'); + $table->dropColumn('mirror'); + $table->dropColumn('mirror_status'); + $table->dropColumn('online'); + $table->dropColumn('interlaced'); + $table->dropColumn('rotation_supported'); + $table->dropColumn('television'); + $table->dropColumn('display_asleep'); + $table->dropColumn('ambient_brightness'); + $table->dropColumn('automatic_graphics_switching'); + $table->dropColumn('retina'); + $table->dropColumn('edr_enabled'); + $table->dropColumn('edr_limit'); + $table->dropColumn('edr_supported'); + $table->dropColumn('connection_type'); + $table->dropColumn('dp_dpcd_version'); + $table->dropColumn('dp_current_bandwidth'); + $table->dropColumn('dp_current_lanes'); + $table->dropColumn('dp_current_spread'); + $table->dropColumn('dp_hdcp_capability'); + $table->dropColumn('dp_max_bandwidth'); + $table->dropColumn('dp_max_lanes'); + $table->dropColumn('dp_max_spread'); + $table->dropColumn('virtual_device'); + $table->dropColumn('dynamic_range'); + $table->dropColumn('dp_adapter_firmware_version'); + }); + } +} diff --git a/scripts/displays.py b/scripts/displays.py index 490a580..f77f0cc 100755 --- a/scripts/displays.py +++ b/scripts/displays.py @@ -1,88 +1,238 @@ #!/usr/bin/python + """ -extracts information about the external displays from system profiler +Extracts information about the external displays from System Profiler """ import sys import os import subprocess import plistlib -import datetime - -# Skip manual check -if len(sys.argv) > 1: - if sys.argv[1] == 'manualcheck': - print 'Manual check: skipping' - exit(0) - -# Create cache dir if it does not exist -cachedir = '%s/cache' % os.path.dirname(os.path.realpath(__file__)) -if not os.path.exists(cachedir): - os.makedirs(cachedir) - -sp = subprocess.Popen(['system_profiler', '-xml', 'SPDisplaysDataType'], stdout=subprocess.PIPE) -out, err = sp.communicate() - -plist = plistlib.readPlistFromString(out) -result = '' - -#loop inside each graphic card -for vga in plist[0]['_items']: - - #this filters out iMacs with no external display - if vga.get('spdisplays_ndrvs', None): - - #loop within each display - for display in vga['spdisplays_ndrvs']: - - #Type section - try: - if display.get('spdisplays_display-serial-number', None): - result += 'Type = External' - elif display['_spdisplays_display-vendor-id'] == "610": - result += 'Type = Internal' +sys.path.insert(0, '/usr/local/munki') + +from munkilib import FoundationPlist + +def get_displays_info(): + '''Uses system profiler to get display info for this machine.''' + cmd = ['/usr/sbin/system_profiler', 'SPDisplaysDataType', '-xml'] + proc = subprocess.Popen(cmd, shell=False, bufsize=-1, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + (output, unused_error) = proc.communicate() + try: + plist = plistlib.readPlistFromString(output) + # system_profiler xml is an array + sp_dict = plist[0] + items = sp_dict['_items'] + return items + except Exception: + return {} + +def flatten_displays_info(array, localization): + '''Un-nest displays, return array with objects with relevant keys''' + out = [] + for obj in array: + display = {} + for item in obj: + if item == '_items': + out = out + flatten_displays_info(obj['_items'], localization) + elif item == 'spdisplays_ndrvs': + out = out + flatten_displays_info(obj['spdisplays_ndrvs'], localization) + elif item == '_spdisplays_displayport_device': + out = out + flatten_displays_info(obj['_spdisplays_displayport_device'], localization) + + elif item == 'spdisplays_display-serial-number': + display['display_serial'] = obj[item] + elif item == '_spdisplays_display-vendor-id': + # 756e6b6e is used to show display is virtual display + if obj[item] == "756e6b6e": + display['virtual_device'] = 1 + display['vendor'] = "Virtual Display" else: - result += 'Type = External' - except KeyError, error: #this catches the error for 10.6 where there is no vendor for built-in displays - result += 'Type = Internal' - - #Serial section - if display.get('spdisplays_display-serial-number', None): - result += '\nSerial = ' + str(display['spdisplays_display-serial-number']) - else: - result += '\nSerial = n/a' - - try: - #Vendor section - result += '\nVendor = ' + str(display['_spdisplays_display-vendor-id']) - - #Model section - result += '\nModel = ' + str(display['_name']) - - #Manufactured section - # from http://en.wikipedia.org/wiki/Extended_display_identification_data#EDID_1.3_data_format - # If week=255, year is the model year. - if int(display['_spdisplays_display-week']) == 255: - result += '\nManufactured = ' + str(display['_spdisplays_display-year']) + ' Model' + try: + display['virtual_device'] = to_bool(obj['_spdisplays_virtualdevice']) + display['vendor'] = obj[item].strip() + except KeyError, error: + display['vendor'] = obj[item].strip() + + # Get Internal/External + try: + display['type'] = type_get(obj['spdisplays_builtin']) + except KeyError, error: + try: + if obj.get('spdisplays_display-serial-number', None): + display['type'] = 1 #External + elif obj['_spdisplays_display-vendor-id'] == "610": + display['type'] = 0 #Internal + else: + display['type'] = 1 #External + except KeyError, error: # This catches the error for 10.6 where there is no vendor for built-in displays + display['type'] = 0 #Internal + + elif item == '_name' and obj[item] is not "spdisplays_displayport_info" and obj[item] is not "spdisplays_display_connector" and not obj[item].startswith('kHW_') and not obj[item].startswith('NVIDIA') and not obj[item].startswith('AMD') and not obj[item].startswith('ATI') and not obj[item].startswith('Intel'): + if obj[item] == "spdisplays_display": + display['model'] = "Virtual Display" else: - weektomonth = datetime.datetime.strptime(display['_spdisplays_display-year'] + display['_spdisplays_display-week'], '%Y%W') - result += '\nManufactured = ' + str(weektomonth.strftime('%Y-%m')) - - #Native resolution section - result += '\nNative = ' + str(display['_spdisplays_pixels']) - - #Save section - result += '\n----------\n' - - except KeyError, error: - result += '\nAn error ocurred while reading this display\n' - -############## - -# Write to disk -txtfile = open("%s/displays.txt" % cachedir, "w") -txtfile.write(result) -txtfile.close() - -exit(0) + display['model'] = obj[item].strip() + + try: + display['display_asleep'] = to_bool(obj['spdisplays_asleep']) + except KeyError, error: + display['display_asleep'] = 0 + + # Set inital Retina + display['retina'] = 0 + + elif item == '_spdisplays_pixels': + display['native'] = obj[item].strip() + + elif item == 'spdisplays_pixelresolution': + # Set Retina + if "etina" in obj[item]: + display['retina'] = 1 + + elif item == 'spdisplays_resolution': + try: + try: + display['current_resolution'] = localization[obj[item]].strip() + except KeyError, error: + display['current_resolution'] = obj[item].strip() + except KeyError, error: + display['current_resolution'] = obj[item].strip() + + elif item == '_spdisplays_resolution': + try: + try: + display['ui_resolution'] = localization[obj[item]].strip() + except KeyError, error: + display['ui_resolution'] = obj[item].strip() + except KeyError, error: + display['ui_resolution'] = obj[item].strip() + + elif item == 'spdisplays_depth': + try: + display['color_depth'] = localization[obj[item]].strip() + except KeyError, error: + display['color_depth'] = obj[item].strip() + elif item == 'spdisplays_display_type': + try: + display['display_type'] = localization[obj[item]].strip() + except KeyError, error: + display['display_type'] = obj[item].strip() + # Set Retina + if "etina" in obj[item]: + display['retina'] = 1 + elif item == 'spdisplays_main': + display['main_display'] = to_bool(obj[item]) + elif item == 'spdisplays_mirror': + display['mirror'] = to_bool(obj[item]) + elif item == 'spdisplays_mirror_status': + try: + display['mirror_status'] = localization[obj[item]].strip() + except KeyError, error: + display['mirror_status'] = obj[item].strip() + elif item == 'spdisplays_online': + display['online'] = to_bool(obj[item]) + elif item == 'spdisplays_interlaced': + display['interlaced'] = to_bool(obj[item]) + elif item == 'spdisplays_rotation': + display['rotation_supported'] = to_bool(obj[item]) + elif item == 'spdisplays_television': + display['television'] = to_bool(obj[item]) + elif item == 'spdisplays_ambient_brightness': + display['ambient_brightness'] = to_bool(obj[item]) + elif item == 'spdisplays_automatic_graphics_switching': + display['automatic_graphics_switching'] = to_bool(obj[item]) + elif item == '_spdisplays_EDR_Enabled': + display['edr_enabled'] = to_bool(obj[item]) + elif item == '_spdisplays_EDR_Limit': + display['edr_limit'] = float(obj[item]) + elif item == '_spdisplays_EDR_Supported': + display['edr_supported'] = to_bool(obj[item]) + elif item == 'spdisplays_connection_type': + try: + display['connection_type'] = localization[obj[item]].strip() + except KeyError, error: + display['connection_type'] = obj[item].strip() + elif item == 'spdisplays_displayport_DPCD_version': + display['dp_dpcd_version'] = obj[item].strip() + elif item == 'spdisplays_displayport_current_bandwidth': + display['dp_current_bandwidth'] = obj[item].strip() + elif item == 'spdisplays_displayport_current_lanes': + display['dp_current_lanes'] = int(obj[item]) + elif item == 'spdisplays_displayport_current_spread': + display['dp_current_spread'] = obj[item].strip() + elif item == 'spdisplays_displayport_hdcp_capability': + display['dp_hdcp_capability'] = to_bool(obj[item]) + elif item == 'spdisplays_displayport_max_bandwidth': + display['dp_max_bandwidth'] = obj[item].strip() + elif item == 'spdisplays_displayport_max_lanes': + display['dp_max_lanes'] = int(obj[item]) + elif item == 'spdisplays_displayport_max_spread': + display['dp_max_spread'] = obj[item].strip() + elif item == 'spdisplays_dynamic_range': + try: + display['dynamic_range'] = localization[obj[item]].strip() + except KeyError, error: + display['dynamic_range'] = obj[item] .strip() + elif item == 'spdisplays_displayport_adapter_firmware_version': + display['dp_adapter_firmware_version'] = obj[item].strip() + + elif item == '_spdisplays_display-week': + # Manufactured section + # from https://en.wikipedia.org/wiki/Extended_Display_Identification_Data#EDID_1.4_data_format + # If week is 0 or 255, year is the model year. + if int(obj['_spdisplays_display-week']) == 255 or int(obj['_spdisplays_display-week']) == 0: + display['manufactured'] = str(obj['_spdisplays_display-year']) + ' Model' + else: + display['manufactured'] = obj['_spdisplays_display-year'] + '-' + obj['_spdisplays_display-week'] + + if display: + out.append(display) + + return out + + +def to_bool(s): + if s == True or s == "spdisplays_yes" or s == "spdisplays_on" or s == 'spdisplays_supported' or s == 'spdisplays_supported' or s == 'spdisplays_displayport_hdcp_capable': + return 1 + else: + return 0 + +def type_get(s): + if s == "spdisplays_yes": + return 0 + else: + return 1 + +def main(): + """Main""" + # Create cache dir if it does not exist + cachedir = '%s/cache' % os.path.dirname(os.path.realpath(__file__)) + if not os.path.exists(cachedir): + os.makedirs(cachedir) + + # Skip manual check + if len(sys.argv) > 1: + if sys.argv[1] == 'manualcheck': + print 'Manual check: skipping' + exit(0) + + # Get results + result = dict() + info = get_displays_info() + + # Read in English localizations from SystemProfiler + localization = FoundationPlist.readPlist('/System/Library/SystemProfiler/SPDisplaysReporter.spreporter/Contents/Resources/English.lproj/Localizable.strings') + + result = flatten_displays_info(info, localization) + + # Write displays results to cache + output_plist = os.path.join(cachedir, 'displays.plist') + plistlib.writePlist(result, output_plist) + #print plistlib.writePlistToString(result) + + +if __name__ == "__main__": + main() diff --git a/scripts/install.sh b/scripts/install.sh old mode 100644 new mode 100755 index ada59f0..dfec7f6 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -1,6 +1,6 @@ #!/bin/bash -# directory service controller +# Displays_info controller CTL="${BASEURL}index.php?/module/displays_info/" # Get the scripts in the proper directories @@ -11,8 +11,13 @@ if [ $? = 0 ]; then # Make executable chmod a+x "${MUNKIPATH}preflight.d/displays.py" + # Delete the older style cached file + if [[ -f "${MUNKIPATH}preflight.d/cache/displays.txt" ]] ; then + rm -f "${MUNKIPATH}preflight.d/cache/displays.txt" + fi + # Set preference to include this file in the preflight check - setreportpref "displays_info" "${CACHEPATH}displays.txt" + setreportpref "displays_info" "${CACHEPATH}displays.plist" else echo "Failed to download all required components!" diff --git a/scripts/uninstall.sh b/scripts/uninstall.sh old mode 100644 new mode 100755 index d23a4a5..3b054de --- a/scripts/uninstall.sh +++ b/scripts/uninstall.sh @@ -1,7 +1,7 @@ #!/bin/bash -# Remove directoryservice script +# Remove displays script rm -f "${MUNKIPATH}preflight.d/displays.py" -# Remove directoryservice.txt -rm -f "${CACHEPATH}displays.txt" +# Remove displays.plist +rm -f "${CACHEPATH}displays.plist" diff --git a/views/displays_listing.php b/views/displays_listing.php old mode 100644 new mode 100755 index 141cd1d..b4b2b6e --- a/views/displays_listing.php +++ b/views/displays_listing.php @@ -25,13 +25,18 @@