Skip to content

Commit

Permalink
Add more data for Studio Display
Browse files Browse the repository at this point in the history
  • Loading branch information
tuxudo committed Aug 16, 2023
1 parent 1d98d79 commit 4ec35cf
Show file tree
Hide file tree
Showing 9 changed files with 130 additions and 31 deletions.
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,11 @@ This is the table of values for 'displays':
* 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
* hardware_model (string) Studio Display hardware model
* region_info (string) Studio Display region
* os_version (string) Studio Display OS version
* model_identifier (string) Studio Display model ID
* model_number (string) Studio Display model number

Remarks
---
Expand All @@ -64,7 +69,7 @@ The default configuration of the module is to delete any previous display inform

Nonetheless you can configure it to keep old data by adding this to your config.php `$conf['keep_previous_displays'] = TRUE;`. Some examples of what to expect in each case:

### Example of default behaviour:
### Example of default behavior:

* Scenario 1:
* Laptop1 reports in the morning about the built-in display and an external display --> mr-php stores and displays both
Expand Down
2 changes: 1 addition & 1 deletion displays_info_controller.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public function get_data($serial = '')

$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
$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, model_number, model_identifier, os_version, region_info, hardware_model
FROM displays
WHERE serial_number = '$serial'";

Expand Down
34 changes: 21 additions & 13 deletions displays_info_model.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,11 @@ public function __construct($serial = '')
$this->rs['virtual_device'] = 0; // Boolean
$this->rs['dynamic_range'] = '';
$this->rs['dp_adapter_firmware_version'] = '';
$this->rs['hardware_model'] = '';
$this->rs['region_info'] = '';
$this->rs['os_version'] = '';
$this->rs['model_identifier'] = '';
$this->rs['model_number'] = '';

// Add local config
configAppendFile(__DIR__ . '/config.php');
Expand All @@ -57,7 +62,7 @@ public function __construct($serial = '')

$this->serial = $serial;
} // End construct

/**
* Get count of displays
*
Expand Down Expand Up @@ -88,7 +93,7 @@ public function process($data)
if (! $data) {
print_r("Error Processing Displays Info Module Request: No data found");
} else if (substr( $data, 0, 30 ) != '<?xml version="1.0" encoding="' ) { // Else if old style txt format, process with old txt based handler

// Translate array used to match data to db fields
$translate = array('Type = ' => 'type',
'Serial = ' => 'display_serial',
Expand All @@ -103,7 +108,7 @@ public function process($data)
$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
Expand Down Expand Up @@ -139,25 +144,28 @@ public function process($data)
// 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();


// Delete internal displays, they'll regenerate like hydra heads
$this->deleteWhere('serial_number=? AND type=0', array($this->serial_number));

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) {
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','hardware_model','region_info','os_version','model_identifier','model_number') 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
Expand All @@ -169,21 +177,21 @@ public function process($data)
$this->$item = $display[$item];
}
}

// 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
Expand Down
7 changes: 6 additions & 1 deletion js/format_displays.js
Original file line number Diff line number Diff line change
Expand Up @@ -107,5 +107,10 @@ var display_vendors = {
"34a4": "Medion",
"1ab3": "Fujitsu",
"2db2": "Kramer Electronics",
"6161706c": "AirPlay"
"6161706c": "AirPlay",
"ca9": "Crestron",
"6474": "InnoView",
"220e": "HP",
"25d8": "KanexPro",
"859": "Insignia"
}
5 changes: 5 additions & 0 deletions locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"edr_limit": "EDR Limit",
"edr_supported": "EDR Supported",
"external": "External",
"hardware_model": "Hardware Model",
"interlaced": "Interlaced",
"internal": "Built-In",
"machineserial": "Machine Serial",
Expand All @@ -32,12 +33,16 @@
"mirror": "Mirroring",
"mirror_status": "Mirror Status",
"model": "Model",
"model_identifier": "Model ID",
"model_number": "Model Number",
"native": "Native Resolution",
"nativeresolution": "Native Resolution",
"no_displays": "No Displays",
"not_supported": "Not Supported",
"online": "Online",
"os_version": "OS Version",
"reporttitle": "Displays Report",
"region_info": "Region",
"retina": "Retina Display",
"retina_short": "Retina",
"rotation_supported": "Rotation Supported",
Expand Down
33 changes: 33 additions & 0 deletions migrations/2023_08_06_000001_displays_info_studio_display.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Capsule\Manager as Capsule;

class DisplaysInfoStudioDisplay extends Migration
{
private $tableName = 'displays';

public function up()
{
$capsule = new Capsule();
$capsule::schema()->table($this->tableName, function (Blueprint $table) {
$table->string('hardware_model')->nullable();
$table->string('region_info')->nullable();
$table->string('os_version')->nullable();
$table->string('model_identifier')->nullable();
$table->string('model_number')->nullable();
});
}

public function down()
{
$capsule = new Capsule();
$capsule::schema()->table($this->tableName, function (Blueprint $table) {
$table->dropColumn('hardware_model');
$table->dropColumn('region_info');
$table->dropColumn('os_version');
$table->dropColumn('model_identifier');
$table->dropColumn('model_number');
});
}
}
63 changes: 49 additions & 14 deletions scripts/displays.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ def flatten_displays_info(array, localization):
display['vendor'] = obj[item].strip()
except KeyError as error:
display['vendor'] = obj[item].strip()

# Get Internal/External
try:
display['type'] = type_get(obj['spdisplays_builtin'])
Expand All @@ -84,23 +84,23 @@ def flatten_displays_info(array, localization):
display['model'] = "Virtual Display"
else:
display['model'] = obj[item].strip()

try:
display['display_asleep'] = to_bool(obj['spdisplays_asleep'])
except KeyError as 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:
Expand All @@ -109,13 +109,13 @@ def flatten_displays_info(array, localization):
display['current_resolution'] = obj[item].strip()
except KeyError as error:
display['current_resolution'] = obj[item].strip()

elif item == '_spdisplays_resolution':
try:
try:
display['ui_resolution'] = localization[obj[item]].strip()
except KeyError as error:
display['ui_resolution'] = obj[item].strip()
display['ui_resolution'] = obj[item].strip()
except KeyError as error:
display['ui_resolution'] = obj[item].strip()

Expand All @@ -140,7 +140,7 @@ def flatten_displays_info(array, localization):
try:
display['mirror_status'] = localization[obj[item]].strip()
except KeyError as error:
display['mirror_status'] = obj[item].strip()
display['mirror_status'] = obj[item].strip()
elif item == 'spdisplays_online':
display['online'] = to_bool(obj[item])
elif item == 'spdisplays_interlaced':
Expand Down Expand Up @@ -187,21 +187,56 @@ def flatten_displays_info(array, localization):
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'
if str(obj['_spdisplays_display-year']) != "0":
display['manufactured'] = str(obj['_spdisplays_display-year']) + ' Model'
else:
display['manufactured'] = obj['_spdisplays_display-year'] + '-' + obj['_spdisplays_display-week']

# Check for Apple Studio Display
if 'model' in display and display['model'] == "Studio Display" and 'vendor' in display and display['vendor'] == "610":
try:
display.update(get_Studio_Display_iBridge_data(display['display_serial']))
except Exception:
pass

if display:
out.append(display)


return out

def get_Studio_Display_iBridge_data(display_serial_number):
cmd = ['/usr/libexec/remotectl', 'dumpstate']
proc = subprocess.Popen(cmd, shell=False, bufsize=-1,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
(output, unused_error) = proc.communicate()

out = {}

for ibridge in output.decode().split("UUID: "):

# We need to get only the Studio Display's iBridge
if "DeviceClass => AppleDisplay" in ibridge and display_serial_number in ibridge:

for item in ibridge.split('\n'):
if 'HWModel => ' in item:
out['hardware_model'] = item.replace('HWModel => ', "").strip()
elif 'RegionInfo => ' in item:
out['region_info'] = item.replace('RegionInfo => ', "").strip()
elif 'OSVersion => ' in item:
out['os_version'] = item.replace('OSVersion => ', "").strip()
elif 'Product Type: ' in item:
out['model_identifier'] = item.replace('Product Type: ', "").strip()
elif 'ModelNumber => ' in item:
out['model_number'] = item.replace('ModelNumber => ', "").strip()

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':
Expand All @@ -221,7 +256,7 @@ def main():
# Get results
result = dict()
info = get_displays_info()

# Read in English localizations from SystemProfiler
if os.path.isfile('/System/Library/SystemProfiler/SPDisplaysReporter.spreporter/Contents/Resources/en.lproj/Localizable.strings'):
localization = FoundationPlist.readPlist('/System/Library/SystemProfiler/SPDisplaysReporter.spreporter/Contents/Resources/en.lproj/Localizable.strings')
Expand All @@ -239,7 +274,7 @@ def main():
localization = {}

result = flatten_displays_info(info, localization)

# Write displays results to cache
cachedir = '%s/cache' % os.path.dirname(os.path.realpath(__file__))
output_plist = os.path.join(cachedir, 'displays.plist')
Expand Down
8 changes: 8 additions & 0 deletions views/displays_listing.php
Original file line number Diff line number Diff line change
Expand Up @@ -58,5 +58,13 @@
"sort" => "desc",
"formatter" => "timestampToMoment",
],
[
"column" => "displays.model_identifier",
"i18n_header" => "displays_info.model_identifier",
],
[
"column" => "displays.os_version",
"i18n_header" => "displays_info.os_version",
],
]
]);
2 changes: 1 addition & 1 deletion views/displays_tab.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
for (var prop in d){
// Skip skipThese
if(skipThese.indexOf(prop) == -1){
if (d[prop] == null || d[prop] == "" || d[prop] == "n/a"){
if ((d[prop] == null || d[prop] == "" || d[prop] == "n/a") && d[prop] !== 0){
// Do nothing for the nulls to blank them
}
else if(prop == 'type' && d[prop] == 1){
Expand Down

0 comments on commit 4ec35cf

Please sign in to comment.