Skip to content

Commit 07d5192

Browse files
author
Ackerley Tng
committed
Add feature to match display's native resolution
Accepts new setup definitions like DP-1 native_resolution=3840x2160 eDP-1 00ffffff... To match any monitors attached as DP-1 with native resolution of 3840x2160
1 parent 06b341a commit 07d5192

File tree

1 file changed

+190
-2
lines changed

1 file changed

+190
-2
lines changed

autorandr.py

Lines changed: 190 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,174 @@ def is_closed_lid(output):
159159
return False
160160

161161

162+
def _read_resolution_from_18_byte_data_block(edid, offset):
163+
"""Read resolution from edid bytearray beginning at offset. Return a tuple
164+
of (horizontal, vertical) pixels if a valid resolution is defined, else
165+
return None"""
166+
167+
horz = edid[offset + 2] + ((edid[offset + 4] & 0xf0) << 4)
168+
if horz == 0:
169+
return None
170+
171+
vert = edid[offset + 5] + ((edid[offset + 7] & 0xf0) << 4)
172+
173+
return (horz, vert)
174+
175+
176+
def _read_resolutions_from_base_block(edid, offset):
177+
resolutions = []
178+
179+
detail = 0x36
180+
for _ in range(4):
181+
res = _read_resolution_from_18_byte_data_block(edid, offset + detail)
182+
if res:
183+
resolutions.append(res)
184+
185+
detail += 18
186+
187+
return resolutions
188+
189+
190+
def _read_resolutions_from_cta_block(edid, offset):
191+
version = edid[offset + 1]
192+
if version < 1:
193+
return []
194+
195+
detail = edid[offset + 2]
196+
if detail < 4:
197+
return []
198+
199+
resolutions = []
200+
while detail < 127:
201+
res = _read_resolution_from_18_byte_data_block(edid, offset + detail)
202+
if res:
203+
resolutions.append(res)
204+
205+
detail += 18
206+
207+
return resolutions
208+
209+
210+
def _read_resolution_from_displayid_block_0x01(edid, offset):
211+
payload_length = edid[offset + 2]
212+
213+
if payload_length != 12:
214+
return None
215+
216+
horz = (edid[offset + 8] << 8) + edid[offset + 7]
217+
if horz == 0:
218+
return None
219+
220+
vert = (edid[offset + 10] << 8) + edid[offset + 9]
221+
222+
return (horz, vert)
223+
224+
225+
def _read_resolution_from_displayid_block_0x0c(edid, offset):
226+
payload_length = edid[offset + 2]
227+
228+
if payload_length != 13:
229+
return None
230+
231+
horz = (edid[offset + 6] << 8) + edid[offset + 5] + 1
232+
if horz == 0:
233+
return None
234+
235+
vert = (edid[offset + 8] << 8) + edid[offset + 7] + 1
236+
237+
return (horz, vert)
238+
239+
240+
def _read_resolution_from_displayid_block_0x21(edid, offset):
241+
payload_length = edid[offset + 2]
242+
243+
if payload_length != 29:
244+
return None
245+
246+
horz = (edid[offset + 8] << 8) + edid[offset + 7]
247+
if horz == 0:
248+
return None
249+
250+
vert = (edid[offset + 10] << 8) + edid[offset + 9]
251+
252+
return (horz, vert)
253+
254+
255+
def _read_resolution_from_displayid_block(edid, offset):
256+
tag = edid[offset]
257+
payload_length = edid[offset + 2]
258+
259+
if tag == 0x01:
260+
res = _read_resolution_from_displayid_block_0x01(edid, offset)
261+
elif tag == 0x0c:
262+
res = _read_resolution_from_displayid_block_0x0c(edid, offset)
263+
elif tag == 0x21:
264+
res = _read_resolution_from_displayid_block_0x21(edid, offset)
265+
else:
266+
res = None
267+
268+
# Add header length of 3
269+
return payload_length + 3, res
270+
271+
272+
def _read_resolutions_from_displayid_block(edid, offset):
273+
# DisplayID length has a maximum of 121
274+
total_length = min(edid[offset + 2], 121)
275+
base = offset + 5
276+
277+
resolutions = []
278+
detail = 0
279+
while detail < total_length:
280+
block_length, resolution = \
281+
_read_resolution_from_displayid_block(edid, base + detail)
282+
if resolution:
283+
resolutions.append(resolution)
284+
285+
detail += block_length
286+
287+
return resolutions
288+
289+
290+
def _read_resolutions_from_block(edid, offset):
291+
block_type = edid[offset]
292+
293+
if block_type == 0x00:
294+
return _read_resolutions_from_base_block(edid, offset)
295+
if block_type == 0x02:
296+
return _read_resolutions_from_cta_block(edid, offset)
297+
if block_type == 0x70:
298+
return _read_resolutions_from_displayid_block(edid, offset)
299+
300+
return []
301+
302+
303+
def read_native_resolutions(edid):
304+
"""Given an edid hex string, return a set of all the native resolutions
305+
(horizontal, vertical) defined in the EDID"""
306+
307+
edid_page_size = 128
308+
309+
edid_bytearray = bytearray.fromhex(edid)
310+
311+
native_resolutions = set()
312+
for offset in range(0, len(edid_bytearray), edid_page_size):
313+
native_resolutions.update(_read_resolutions_from_block(edid_bytearray, offset))
314+
315+
return native_resolutions
316+
317+
318+
def _parse_edid_pattern(pattern):
319+
"""Parse (horizontal, vertical) pixels from a pattern defined like """
320+
321+
# Only supports patterns like "native_resolution=3840x2160" for now
322+
if "native_resolution=" not in pattern:
323+
return None
324+
325+
m = re.match(r"native_resolution=(\d+)x(\d+)", pattern)
326+
if m:
327+
return (int(m.group(1)), int(m.group(2)))
328+
329+
162330
class AutorandrException(Exception):
163331
def __init__(self, message, original_exception=None, report_bug=False):
164332
self.message = message
@@ -361,6 +529,8 @@ def parse_serial_from_edid(self):
361529
return
362530
if "*" in self.edid:
363531
return
532+
if _parse_edid_pattern(self.edid):
533+
return
364534
# Thx to pyedid project, the following code was
365535
# copied (and modified) from pyedid/__init__py:21 [parse_edid()]
366536
raw = bytes.fromhex(self.edid)
@@ -551,6 +721,10 @@ def edid_equals(self, other):
551721
return match_asterisk(self.edid, other.edid) > 0
552722
elif "*" in other.edid:
553723
return match_asterisk(other.edid, self.edid) > 0
724+
if _parse_edid_pattern(self.edid):
725+
return match_native_resolution(self.edid, other.edid)
726+
elif _parse_edid_pattern(other.edid):
727+
return match_native_resolution(other.edid, self.edid)
554728
return self.edid == other.edid
555729

556730
def __ne__(self, other):
@@ -721,6 +895,14 @@ def match_asterisk(pattern, data):
721895
return matched * 1. / total
722896

723897

898+
def match_native_resolution(edid_pattern, edid_string):
899+
horz_x_vert = _parse_edid_pattern(edid_pattern)
900+
if not horz_x_vert:
901+
return False
902+
903+
return horz_x_vert in read_native_resolutions(edid_string)
904+
905+
724906
def update_profiles_edid(profiles, config):
725907
fp_map = {}
726908
for c in config:
@@ -766,8 +948,14 @@ def find_profiles(current_config, profiles):
766948
if not matches or any((name not in config.keys() for name in current_config.keys() if current_config[name].fingerprint)):
767949
continue
768950
if matches:
769-
closeness = max(match_asterisk(output.edid, current_config[name].edid), match_asterisk(
770-
current_config[name].edid, output.edid))
951+
config_edid = current_config[name].edid
952+
parsed = _parse_edid_pattern(output.edid) or _parse_edid_pattern(config_edid)
953+
if parsed:
954+
closeness = int(match_native_resolution(config_edid, output.edid) or
955+
match_native_resolution(output.edid, config_edid))
956+
else:
957+
closeness = max(match_asterisk(output.edid, config_edid),
958+
match_asterisk(config_edid, output.edid))
771959
detected_profiles.append((closeness, profile_name))
772960
detected_profiles = [o[1] for o in sorted(detected_profiles, key=lambda x: -x[0])]
773961
return detected_profiles

0 commit comments

Comments
 (0)