Skip to content

Instantly share code, notes, and snippets.

@ivucica
Last active October 27, 2022 18:19
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ivucica/cc39bab278fda619220802a2af704d72 to your computer and use it in GitHub Desktop.
Save ivucica/cc39bab278fda619220802a2af704d72 to your computer and use it in GitHub Desktop.
tool to print out JSON-formatted mapping between monitor serial number, its xrandr output ID, and some other EDID-decoded information
#!/usr/bin/env python3
"""
Print out the JSON-formatted mapping between serial number, xrandr display ID and decoded edid attributes.
Useful in scenarios where you need to set your center monitor as the primary, and the other monitors to
its left and right, but the xrandr extension changes the output names arbitrarily:
xrandr --output DP-1 --auto --primary --output DP-6 --left-of DP-1 --output DP-3 --right-of DP-1
As long as the same monitor is plugged in, it should be doable to determine the ID based on the serial
using jq.
Requires pyedid==1.0.1 and xlib.
Sadly kernel's DRM does not have the same IDs:
for i in $(ls -1 /sys/class/drm/*/edid) ; do echo $i ; parse-edid < $i ; done
and
for i in $(ls -1 /sys/class/drm/*/edid) ; do echo $i ; edid-decode < $i ; done
decode the wrong thing.
pyedid suggests invoking xrandr --verbose, but then you still can't know which edid came from which output.
(Yes, this is a hack with a bunch of ugly commented-out code. Treat this as notes.)
Sample output on stdout with dummy serial numbers:
{
"AAA088AAAA": {
"output": "DP-1",
"edid": {
"manufacturer_id": 2513,
"manufacturer": "Unknown",
"product_id": 12927,
"year": 2016,
"week": 29,
"edid_version": "1.3",
"type": "digital",
"width": 53,
"height": 30,
"gamma": 2.2,
"dpms_standby": true,
"dpms_suspend": true,
"dpms_activeoff": true,
"resolutions": [
[
720,
400,
70
],
[
720,
400,
88
]
],
"name": "ZOWIE XL LCD",
"serial": "AAA088AAAA"
}
},
"BBBBBB09BBBB": {
"output": "DP-3",
"edid": {
"manufacturer_id": 1129,
"manufacturer": "Ancor Communications Inc",
"product_id": 53795,
"year": 2014,
"week": 25,
"edid_version": "1.3",
"type": "digital",
"width": 51,
"height": 29,
"gamma": 2.2,
"dpms_standby": true,
"dpms_suspend": true,
"dpms_activeoff": true,
"resolutions": [
[
720,
400,
70
],
[
720,
400,
88
],
[
1280,
800,
60
]
],
"name": "ASUS VS239",
"serial": "BBBBBB09BBBB"
}
}
}
Use with jq to get just the output (-r removes the quotemarks):
python3 edid.py | jq -r '."YOURSERIAL".output'
Or let's turn that dict into a sequence of dicts (not really JSON list from jq's or anyone else's perspective):
python3 edid.py | jq -r '.[] | select(.edid.serial == "YOURSERIAL").output'
"""
import pyedid
import subprocess
import sys
from Xlib import X, display, Xutil
from Xlib.ext import randr
import json
# pip3 install --user pyedid
# version 1.0.1
# https://github.com/dd4e/pyedid
#edid_hex = """
# 00ffffffffffff000469d223aa7c0100
# 1918010380331d782ad945a2554da027
# 125054b7ef00d1c0814081809500b300
# 714f81c08100023a801871382d40582c
# 4500fd1e1100001e000000ff0045364c
# 4d54463039373435300a000000fd0032
# 4b185311000a202020202020000000fc
# 00415355532056533233390a2020009e
#"""
#edid = pyedid.parse_edid(edid_hex)
#print(str(edid))
#r_web = pyedid.Registry.from_web()
#pyedid.DEFAULT_REGISTRY.to_csv('/tmp/edid_registry.default.csv')
#r_web.to_csv('/tmp/edid_registry.csv')
# (likely needs to be assigned to value in pyedid.DEFAULT_REGISTRY)
d = display.Display()
if not d.has_extension('RANDR'):
sys.stderr.write('{}: server does not have the RANDR extension\n'.format(sys.argv[0]))
ext = d.query_extension('RANDR')
sys.stderr.write("\n".join(self.d.list_extensions()))
if ext is None:
sys.exit(1)
r = d.xrandr_query_version()
sys.stderr.write('RANDR version %d.%d\n' % (r.major_version, r.minor_version))
s = d.screen()
# dummy window just so randr can work
window = s.root.create_window(0, 0, 1, 1, 1, s.root_depth)
res = randr.get_screen_resources(window)
edids = {}
def find_mode(id, modes):
for mode in modes:
if id == mode.id:
return f'{mode.width}x{mode.height}'
for output in res.outputs:
params = d.xrandr_get_output_info(output, res.config_timestamp)
if not params.crtc:
continue
crtc = d.xrandr_get_crtc_info(params.crtc, res.config_timestamp)
modes = set()
for mode in params.modes:
modes.add(find_mode(mode, res.modes))
#print(params.name)
#print(crtc.width, crtc.height)
#print(list(modes))
props = d.xrandr_list_output_properties(output)._data
for atom in props['atoms']:
atom_name = d.get_atom_name(atom)
if atom_name == randr.PROPERTY_RANDR_EDID:
raw_edid = d.xrandr_get_output_property(output, atom, 0, 0, 1000)._data['value']
edids[params.name] = bytes(raw_edid)
break
#randrout = subprocess.check_output(['xrandr', '--verbose'])
#edids = pyedid.get_edid_from_xrandr_verbose(randrout)
#for edid in edids:
# print(str(pyedid.parse_edid(edid)))
out = {}
for disp, edid_raw in edids.items():
edid = pyedid.parse_edid(edid_raw)
# #print(disp)
# #print(edid.manufacturer, edid.name, edid.serial)
# out[disp] = edid
out[edid.serial or disp] = {
'output': disp,
'edid': json.loads(str(edid)), # just 'edid' gives us a list; this gives us a nicer dict. could it be nicer? i don't know, didn't read pyedid source code.
}
print(json.dumps(out))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment