-
-
Save courtarro/3adec649c086eea1bb18919d6269d544 to your computer and use it in GitHub Desktop.
#!/usr/bin/env python3 | |
from collections import namedtuple | |
import struct | |
import Xlib.display | |
class Edid: | |
# Modified from the original source: https://github.com/jojonas/pyedid/blob/master/edid.py | |
_STRUCT_FORMAT = ( ">" # big-endian | |
"8s" # constant header (8 bytes) | |
"H" # manufacturer id (2 bytes) | |
"H" # product id (2 bytes) | |
"I" # serial number (4 bytes) | |
"B" # manufactoring week (1 byte) | |
"B" # manufactoring year (1 byte) | |
"B" # edid version (1 byte) | |
"B" # edid revision (1 byte) | |
"B" # video input type (1 byte) | |
"B" # horizontal size in cm (1 byte) | |
"B" # vertical size in cm (1 byte) | |
"B" # display gamma (1 byte) | |
"B" # supported features (1 byte) | |
"10s" # color characteristics (10 bytes) | |
"H" # supported timings (2 bytes) | |
"B" # reserved timing (1 byte) | |
"16s" # EDID supported timings (16 bytes) | |
"18s" # detailed timing block 1 (18 bytes) | |
"18s" # detailed timing block 2 (18 bytes) | |
"18s" # detailed timing block 3 (18 bytes) | |
"18s" # detailed timing block 4 (18 bytes) | |
"B" # extension flag (1 byte) | |
"B" ) # checksum (1 byte) | |
_TIMINGS = { | |
0: (1280, 1024, 75.), | |
1: (1024, 768, 75.), | |
2: (1024, 768, 72.), | |
3: (1024, 768, 60.), | |
4: (1024, 768, 87.), | |
5: ( 832, 624, 75.), | |
6: ( 800, 600, 75.), | |
7: ( 800, 600, 70.), | |
8: ( 800, 600, 60.), | |
9: ( 800, 600, 56.), | |
10:( 640, 480, 75.), | |
11:( 640, 480, 72.), | |
12:( 640, 480, 67.), | |
13:( 640, 480, 60.), | |
14:( 720, 400, 88.), | |
15:( 720, 400, 70.), | |
} | |
_ASPECT_RATIOS = { | |
0b00: (16, 10), | |
0b01: ( 4, 3), | |
0b10: ( 5, 4), | |
0b11: (16, 9), | |
} | |
_RawEdid = namedtuple("RawEdid", ("header", "manu_id", "prod_id", "serial_no", "manu_week", | |
"manu_year", "edid_version", "edid_revision", "input_type", | |
"width", "height", "gamma", "features", "color", | |
"timings_supported", "timings_reserved", "timings_edid", | |
"timing_1", "timing_2", "timing_3", "timing_4", "extension", | |
"checksum")) | |
def __init__(self, bytes=None): | |
if bytes is not None: | |
self._parse_edid(bytes) | |
def _parse_edid(self, bytes): | |
if struct.calcsize(self._STRUCT_FORMAT) != 128: | |
raise ValueError("Wrong edid size.") | |
if sum(map(int, bytes)) % 256 != 0: | |
raise ValueError("Checksum mismatch.") | |
tuple = struct.unpack(self._STRUCT_FORMAT, bytes) | |
raw_edid = self._RawEdid(*tuple) | |
if raw_edid.header != b'\x00\xff\xff\xff\xff\xff\xff\x00': | |
raise ValueError("Invalid header.") | |
self.raw = bytes | |
self.manufacturer = raw_edid.manu_id | |
self.product = raw_edid.prod_id | |
self.year = raw_edid.manu_year + 1990 | |
self.edid_version = "%d.%d" % (raw_edid.edid_version, raw_edid.edid_revision) | |
self.type = "digital" if (raw_edid.input_type & 0xFF) else "analog" | |
self.width = float(raw_edid.width) | |
self.height = float(raw_edid.height) | |
self.gamma = (raw_edid.gamma+100)/100 | |
self.dpms_standby = bool( raw_edid.features & 0xFF ) | |
self.dpms_suspend = bool( raw_edid.features & 0x7F ) | |
self.dpms_activeoff = bool( raw_edid.features & 0x3F ) | |
self.resolutions = [] | |
for i in range(16): | |
bit = raw_edid.timings_supported & i | |
if bit: | |
self.resolutions.append(self._TIMINGS[16-i]) | |
for i in range(8): | |
bytes = raw_edid.timings_edid[2*i:2*i+2] | |
if bytes == b'\x01\x01': | |
continue | |
byte1, byte2 = bytes | |
x_res = 8*(int(byte1)+31) | |
aspect_ratio = self._ASPECT_RATIOS[ (byte2>>6) & 0b11 ] | |
y_res = int(x_res * aspect_ratio[1]/aspect_ratio[0]) | |
rate = (int(byte2) & 0b00111111) + 60.0 | |
self.resolutions.append((x_res, y_res, rate)) | |
self.name = None | |
self.serial = None | |
for bytes in (raw_edid.timing_1, raw_edid.timing_2, raw_edid.timing_3, raw_edid.timing_4): | |
if bytes[0:2] == b'\x00\x00': # "other" descriptor | |
type = bytes[3] | |
if type in (0xFF, 0xFE, 0xFC): | |
buffer = bytes[5:] | |
buffer = buffer.partition(b"\x0a")[0] | |
text = buffer.decode("cp437") | |
if type == 0xFF: | |
self.serial = text | |
elif type == 0xFC: | |
self.name = text | |
if not self.serial: | |
self.serial = raw_edid.serial_no | |
def __repr__(self): | |
clsname = self.__class__.__name__ | |
attributes = [] | |
for name in dir(self): | |
if not name.startswith("_"): | |
value = getattr(self, name) | |
attributes.append("\t%s=%r" % (name, value)) | |
return "%s(\n%s\n)" % (clsname, ", \n".join(attributes)) | |
def get_x_displays(): | |
# Xlib resources | |
d = Xlib.display.Display() | |
root = d.screen().root | |
resources = root.xrandr_get_screen_resources()._data | |
outputs = {} | |
for output in resources['outputs']: | |
output_info = d.xrandr_get_output_info(output, resources['config_timestamp'])._data | |
output_name = output_info['name'] | |
props = d.xrandr_list_output_properties(output) | |
edid_data = None | |
# Look through the atoms (properties) of each output to see if there's one named 'EDID' | |
for atom in props._data['atoms']: | |
atom_name = d.get_atom_name(atom) | |
if atom_name == 'EDID': | |
edid_raw = d.xrandr_get_output_property(output, atom, 0, 0, 1000)._data['value'] | |
edid_data = Edid(bytes(edid_raw)[:128]) | |
break | |
outputs[output_name] = edid_data | |
return outputs | |
if __name__ == '__main__': | |
displays = get_x_displays() | |
for connection, monitor in sorted(displays.items(), key=lambda kv: kv[0]): | |
print(connection) | |
print(' ' + ("No connection or empty EDID" if monitor is None else | |
"{} ({})".format(monitor.name, monitor.serial))) |
Thanks for posting this :) I used this in an Xfce4 panel plugin I made for myself, which allows for easy switching between Xfce4 display profiles right from the panel: https://gist.github.com/theY4Kman/35acc5815740eb9df2809a1dbc8c8625#file-xfce4_dswitch-py-L493-L515
Cool, glad you found it useful! I also incorporated it into a script for switching display profiles but I'm not quite happy with it enough to publish.
I know the feeling :P That one I posted is my umpteenth version. You can tell I started to get real frustrated writing UI code, so I moved to glade; then I got real frustrated restarting xfce4-panel to see my changes, so I added auto-reloading on UI/CSS changes. (And then I got rid of my third monitor/TV, so I haven't used it in a long time.)
Thanks for posting this :) I used this in an Xfce4 panel plugin I made for myself, which allows for easy switching between Xfce4 display profiles right from the panel: https://gist.github.com/theY4Kman/35acc5815740eb9df2809a1dbc8c8625#file-xfce4_dswitch-py-L493-L515