mirror of
https://github.com/kellyjonbrazil/jc.git
synced 2025-06-17 00:07:37 +02:00
[xrandr] Allow props command (#540)
* [xrandr] Allow props command Responding to issue #525 Somewhat substantial rewriting here to make the parser more resilient - Change parser to not mutate the incoming data list, instead index - Create `Line` class and `categorize` classmethod - Every line is categorized and regexed, so it gets dispatched to the right level of responsibility * Bump version --------- Co-authored-by: Kelly Brazil <kellyjonbrazil@gmail.com>
This commit is contained in:
@ -8,6 +8,16 @@ from typing import ByteString
|
||||
|
||||
__all__ = ["Edid"]
|
||||
|
||||
# EDID:
|
||||
# 00ffffffffffff004ca3523100000000
|
||||
# 0014010380221378eac8959e57549226
|
||||
# 0f505400000001010101010101010101
|
||||
# 010101010101381d56d4500016303020
|
||||
# 250058c2100000190000000f00000000
|
||||
# 000000000025d9066a00000000fe0053
|
||||
# 414d53554e470a204ca34154000000fe
|
||||
# 004c544e313536415432343430310018
|
||||
|
||||
|
||||
class Edid:
|
||||
"""Edid class
|
||||
@ -69,8 +79,10 @@ class Edid:
|
||||
0b11: (16, 9),
|
||||
}
|
||||
|
||||
_RawEdid = namedtuple("RawEdid",
|
||||
("header",
|
||||
_RawEdid = namedtuple(
|
||||
"RawEdid",
|
||||
(
|
||||
"header",
|
||||
"manu_id",
|
||||
"prod_id",
|
||||
"serial_no",
|
||||
@ -92,7 +104,8 @@ class Edid:
|
||||
"timing_3",
|
||||
"timing_4",
|
||||
"extension",
|
||||
"checksum")
|
||||
"checksum",
|
||||
),
|
||||
)
|
||||
|
||||
def __init__(self, edid: ByteString):
|
||||
@ -109,14 +122,16 @@ class Edid:
|
||||
unpacked = struct.unpack(self._STRUCT_FORMAT, edid)
|
||||
raw_edid = self._RawEdid(*unpacked)
|
||||
|
||||
if raw_edid.header != b'\x00\xff\xff\xff\xff\xff\xff\x00':
|
||||
if raw_edid.header != b"\x00\xff\xff\xff\xff\xff\xff\x00":
|
||||
raise ValueError("Invalid header.")
|
||||
|
||||
self.raw = edid
|
||||
self.manufacturer_id = raw_edid.manu_id
|
||||
self.product = raw_edid.prod_id
|
||||
self.year = raw_edid.manu_year + 1990
|
||||
self.edid_version = "{:d}.{:d}".format(raw_edid.edid_version, raw_edid.edid_revision)
|
||||
self.edid_version = "{:d}.{:d}".format(
|
||||
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)
|
||||
@ -133,7 +148,7 @@ class Edid:
|
||||
|
||||
for i in range(8):
|
||||
bytes_data = raw_edid.timings_edid[2 * i : 2 * i + 2]
|
||||
if bytes_data == b'\x01\x01':
|
||||
if bytes_data == b"\x01\x01":
|
||||
continue
|
||||
byte1, byte2 = bytes_data
|
||||
x_res = 8 * (int(byte1) + 31)
|
||||
@ -145,9 +160,14 @@ class Edid:
|
||||
self.name = None
|
||||
self.serial = None
|
||||
|
||||
for timing_bytes in (raw_edid.timing_1, raw_edid.timing_2, raw_edid.timing_3, raw_edid.timing_4):
|
||||
for timing_bytes in (
|
||||
raw_edid.timing_1,
|
||||
raw_edid.timing_2,
|
||||
raw_edid.timing_3,
|
||||
raw_edid.timing_4,
|
||||
):
|
||||
# "other" descriptor
|
||||
if timing_bytes[0:2] == b'\x00\x00':
|
||||
if timing_bytes[0:2] == b"\x00\x00":
|
||||
timing_type = timing_bytes[3]
|
||||
if timing_type in (0xFF, 0xFE, 0xFC):
|
||||
buffer = timing_bytes[5:]
|
||||
|
@ -28,7 +28,7 @@ Schema:
|
||||
"maximum_height": integer,
|
||||
"devices": [
|
||||
{
|
||||
"modes": [
|
||||
"resolution_modes": [
|
||||
{
|
||||
"resolution_width": integer,
|
||||
"resolution_height": integer,
|
||||
@ -77,7 +77,7 @@ Examples:
|
||||
"maximum_height": 32767,
|
||||
"devices": [
|
||||
{
|
||||
"modes": [
|
||||
"resolution_modes": [
|
||||
{
|
||||
"resolution_width": 1920,
|
||||
"resolution_height": 1080,
|
||||
@ -138,7 +138,7 @@ Examples:
|
||||
"maximum_height": 32767,
|
||||
"devices": [
|
||||
{
|
||||
"modes": [
|
||||
"resolution_modes": [
|
||||
{
|
||||
"resolution_width": 1920,
|
||||
"resolution_height": 1080,
|
||||
@ -189,16 +189,27 @@ Examples:
|
||||
]
|
||||
}
|
||||
"""
|
||||
from collections import defaultdict
|
||||
from enum import Enum
|
||||
import re
|
||||
from typing import Dict, List, Optional, Union
|
||||
from typing import Dict, List, Tuple, Union
|
||||
|
||||
import jc.utils
|
||||
from jc.parsers.pyedid.edid import Edid
|
||||
from jc.parsers.pyedid.helpers.edid_helper import EdidHelper
|
||||
|
||||
Match = None
|
||||
try:
|
||||
# Added Python 3.7
|
||||
Match = re.Match
|
||||
except AttributeError:
|
||||
Match = type(re.match("", ""))
|
||||
|
||||
|
||||
class info:
|
||||
"""Provides parser metadata (version, author, etc.)"""
|
||||
version = "1.4"
|
||||
|
||||
version = "2.0"
|
||||
description = "`xrandr` command parser"
|
||||
author = "Kevin Lyter"
|
||||
author_email = "code (at) lyterk.com"
|
||||
@ -210,36 +221,10 @@ class info:
|
||||
|
||||
__version__ = info.version
|
||||
|
||||
# keep parsing state so we know which parsers have already tried the line
|
||||
# Structure is:
|
||||
# {
|
||||
# <line_string>: [
|
||||
# <parser_string>
|
||||
# ]
|
||||
# }
|
||||
#
|
||||
# Where <line_string> is the xrandr output line to be checked and <parser_string>
|
||||
# can contain "screen", "device", or "model"
|
||||
parse_state: Dict[str, List] = {}
|
||||
|
||||
|
||||
def _was_parsed(line: str, parser: str) -> bool:
|
||||
"""
|
||||
Check if entered parser has already parsed. If so return True.
|
||||
If not, return false and add the parser to the list for the line entry.
|
||||
"""
|
||||
if line in parse_state:
|
||||
if parser in parse_state[line]:
|
||||
return True
|
||||
|
||||
parse_state[line].append(parser)
|
||||
return False
|
||||
|
||||
parse_state[line] = [parser]
|
||||
return False
|
||||
|
||||
|
||||
# NOTE: When developing, comment out the try statement and catch block to get
|
||||
# TypedDict type hints and valid type errors.
|
||||
try:
|
||||
# Added in Python 3.8
|
||||
from typing import TypedDict
|
||||
|
||||
Frequency = TypedDict(
|
||||
@ -250,8 +235,8 @@ try:
|
||||
"is_preferred": bool,
|
||||
},
|
||||
)
|
||||
Mode = TypedDict(
|
||||
"Mode",
|
||||
ResolutionMode = TypedDict(
|
||||
"ResolutionMode",
|
||||
{
|
||||
"resolution_width": int,
|
||||
"resolution_height": int,
|
||||
@ -259,14 +244,15 @@ try:
|
||||
"frequencies": List[Frequency],
|
||||
},
|
||||
)
|
||||
Model = TypedDict(
|
||||
"Model",
|
||||
EdidModel = TypedDict(
|
||||
"EdidModel",
|
||||
{
|
||||
"name": str,
|
||||
"product_id": str,
|
||||
"serial_number": str,
|
||||
},
|
||||
)
|
||||
Props = Dict[str, Union[List[str], EdidModel]]
|
||||
Device = TypedDict(
|
||||
"Device",
|
||||
{
|
||||
@ -282,7 +268,8 @@ try:
|
||||
"offset_height": int,
|
||||
"dimension_width": int,
|
||||
"dimension_height": int,
|
||||
"modes": List[Mode],
|
||||
"props": Props,
|
||||
"resolution_modes": List[ResolutionMode],
|
||||
"rotation": str,
|
||||
"reflection": str,
|
||||
},
|
||||
@ -307,12 +294,13 @@ try:
|
||||
},
|
||||
)
|
||||
except ImportError:
|
||||
Screen = Dict[str, Union[int, str]]
|
||||
Device = Dict[str, Union[str, int, bool]]
|
||||
EdidModel = Dict[str, str]
|
||||
Props = Dict[str, Union[List[str], EdidModel]]
|
||||
Frequency = Dict[str, Union[float, bool]]
|
||||
Mode = Dict[str, Union[int, bool, List[Frequency]]]
|
||||
Model = Dict[str, str]
|
||||
Response = Dict[str, Union[Device, Mode, Screen]]
|
||||
ResolutionMode = Dict[str, Union[int, bool, List[Frequency]]]
|
||||
Device = Dict[str, Union[str, int, bool, List[ResolutionMode]]]
|
||||
Screen = Dict[str, Union[int, List[Device]]]
|
||||
Response = Dict[str, Screen]
|
||||
|
||||
|
||||
_screen_pattern = (
|
||||
@ -323,33 +311,6 @@ _screen_pattern = (
|
||||
)
|
||||
|
||||
|
||||
def _parse_screen(next_lines: List[str]) -> Optional[Screen]:
|
||||
next_line = next_lines.pop()
|
||||
|
||||
if _was_parsed(next_line, 'screen'):
|
||||
return None
|
||||
|
||||
result = re.match(_screen_pattern, next_line)
|
||||
if not result:
|
||||
next_lines.append(next_line)
|
||||
return None
|
||||
|
||||
raw_matches = result.groupdict()
|
||||
|
||||
screen: Screen = {"devices": []}
|
||||
for k, v in raw_matches.items():
|
||||
screen[k] = int(v)
|
||||
|
||||
while next_lines:
|
||||
device: Optional[Device] = _parse_device(next_lines)
|
||||
if not device:
|
||||
break
|
||||
else:
|
||||
screen["devices"].append(device)
|
||||
|
||||
return screen
|
||||
|
||||
|
||||
# eDP1 connected primary 1920x1080+0+0 (normal left inverted right x axis y axis)
|
||||
# 310mm x 170mm
|
||||
# regex101 demo link
|
||||
@ -365,25 +326,106 @@ _device_pattern = (
|
||||
+ r"( ?((?P<dimension_width>\d+)mm x (?P<dimension_height>\d+)mm)?)?"
|
||||
)
|
||||
|
||||
# 1920x1080i 60.03*+ 59.93
|
||||
# 1920x1080 60.00 + 50.00 59.94
|
||||
_resolution_mode_pattern = r"\s*(?P<resolution_width>\d+)x(?P<resolution_height>\d+)(?P<is_high_resolution>i)?\s+(?P<rest>.*)"
|
||||
_frequencies_pattern = r"(((?P<frequency>\d+\.\d+)(?P<star>\*| |)(?P<plus>\+?)?)+)"
|
||||
|
||||
def _parse_device(next_lines: List[str], quiet: bool = False) -> Optional[Device]:
|
||||
if not next_lines:
|
||||
return None
|
||||
|
||||
next_line = next_lines.pop()
|
||||
# Values sometimes appear on the same lines as the keys (CscMatrix), sometimes on the line
|
||||
# below (as with EDIDs), and sometimes both (CTM).
|
||||
# Capture the key line that way.
|
||||
#
|
||||
# CTM: 0 1 0 0 0 0 0 0 0 1 0 0 0 0 0 0
|
||||
# 0 1
|
||||
# CscMatrix: 65536 0 0 0 0 65536 0 0 0 0 65536 0
|
||||
# EDID:
|
||||
# 00ffffffffffff0010ac33424c303541
|
||||
# 0f210104b53c22783eee95a3544c9926
|
||||
_prop_key_pattern = r"\s+(?P<key>[\w| |\-|_]+):\s?(?P<maybe_value>.*)"
|
||||
|
||||
if _was_parsed(next_line, 'device'):
|
||||
return None
|
||||
|
||||
result = re.match(_device_pattern, next_line)
|
||||
if not result:
|
||||
next_lines.append(next_line)
|
||||
return None
|
||||
class LineType(Enum):
|
||||
Screen = 1
|
||||
Device = 2
|
||||
ResolutionMode = 3
|
||||
PropKey = 4
|
||||
PropValue = 5
|
||||
Invalid = 6
|
||||
|
||||
matches = result.groupdict()
|
||||
|
||||
class Line:
|
||||
"""Provide metadata about line to make handling it more simple across fn boundaries"""
|
||||
|
||||
def __init__(self, s: str, t: LineType, m: Match):
|
||||
self.s = s
|
||||
self.t = t
|
||||
self.m = m
|
||||
|
||||
@classmethod
|
||||
def categorize(cls, line: str) -> "Line":
|
||||
"""Iterate through line char by char to see what type of line it is. Apply regexes for more distinctness. Save the regexes and return them for later processing."""
|
||||
i = 0
|
||||
tab_count = 0
|
||||
while True:
|
||||
try:
|
||||
c = line[i]
|
||||
except:
|
||||
# Really shouldn't be getting to the end of the line
|
||||
raise Exception(f"Reached end of line unexpectedly: '{line}'")
|
||||
|
||||
if not c.isspace():
|
||||
if tab_count == 0:
|
||||
screen_match = re.match(_screen_pattern, line)
|
||||
if screen_match:
|
||||
return cls(line, LineType.Screen, screen_match)
|
||||
|
||||
device_match = re.match(_device_pattern, line)
|
||||
if device_match:
|
||||
return cls(line, LineType.Device, device_match)
|
||||
else:
|
||||
break
|
||||
elif tab_count == 1:
|
||||
match = re.match(_prop_key_pattern, line)
|
||||
if match:
|
||||
return cls(line, LineType.PropKey, match)
|
||||
else:
|
||||
break
|
||||
else:
|
||||
match = re.match(r"\s+(.*)\s+", line)
|
||||
if match:
|
||||
return cls(line, LineType.PropValue, match)
|
||||
else:
|
||||
break
|
||||
else:
|
||||
if c == " ":
|
||||
match = re.match(_resolution_mode_pattern, line)
|
||||
if match:
|
||||
return cls(line, LineType.ResolutionMode, match)
|
||||
else:
|
||||
break
|
||||
elif c == "\t":
|
||||
tab_count += 1
|
||||
i += 1
|
||||
raise Exception(f"Line could not be categorized: '{line}'")
|
||||
|
||||
|
||||
def _parse_screen(line: Line) -> Screen:
|
||||
d = line.m.groupdict()
|
||||
|
||||
screen: Screen = {"devices": []} # type: ignore # Will be populated, but not immediately.
|
||||
for k, v in d.items():
|
||||
screen[k] = int(v)
|
||||
|
||||
return screen
|
||||
|
||||
|
||||
def _parse_device(line: Line) -> Device:
|
||||
matches = line.m.groupdict()
|
||||
|
||||
device: Device = {
|
||||
"modes": [],
|
||||
"props": defaultdict(list),
|
||||
"resolution_modes": [],
|
||||
"is_connected": matches["is_connected"] == "connected",
|
||||
"is_primary": matches["is_primary"] is not None
|
||||
and len(matches["is_primary"]) > 0,
|
||||
@ -403,97 +445,20 @@ def _parse_device(next_lines: List[str], quiet: bool = False) -> Optional[Device
|
||||
if v:
|
||||
device[k] = int(v)
|
||||
except ValueError:
|
||||
if not quiet:
|
||||
jc.utils.warning_message(
|
||||
[f"{next_line} : {k} - {v} is not int-able"]
|
||||
)
|
||||
raise Exception([f"{line.s} : {k} - {v} is not int-able"])
|
||||
|
||||
model: Optional[Model] = _parse_model(next_lines, quiet)
|
||||
if model:
|
||||
device["model_name"] = model["name"]
|
||||
device["product_id"] = model["product_id"]
|
||||
device["serial_number"] = model["serial_number"]
|
||||
|
||||
while next_lines:
|
||||
next_line = next_lines.pop()
|
||||
next_mode: Optional[Mode] = _parse_mode(next_line)
|
||||
if next_mode:
|
||||
device["modes"].append(next_mode)
|
||||
else:
|
||||
if re.match(_device_pattern, next_line):
|
||||
next_lines.append(next_line)
|
||||
break
|
||||
return device
|
||||
|
||||
|
||||
# EDID:
|
||||
# 00ffffffffffff004ca3523100000000
|
||||
# 0014010380221378eac8959e57549226
|
||||
# 0f505400000001010101010101010101
|
||||
# 010101010101381d56d4500016303020
|
||||
# 250058c2100000190000000f00000000
|
||||
# 000000000025d9066a00000000fe0053
|
||||
# 414d53554e470a204ca34154000000fe
|
||||
# 004c544e313536415432343430310018
|
||||
_edid_head_pattern = r"\s*EDID:\s*"
|
||||
_edid_line_pattern = r"\s*(?P<edid_line>[0-9a-fA-F]{32})\s*"
|
||||
|
||||
|
||||
def _parse_model(next_lines: List[str], quiet: bool = False) -> Optional[Model]:
|
||||
if not next_lines:
|
||||
return None
|
||||
|
||||
next_line = next_lines.pop()
|
||||
|
||||
if _was_parsed(next_line, 'model'):
|
||||
return None
|
||||
|
||||
if not re.match(_edid_head_pattern, next_line):
|
||||
next_lines.append(next_line)
|
||||
return None
|
||||
|
||||
edid_hex_value = ""
|
||||
|
||||
while next_lines:
|
||||
next_line = next_lines.pop()
|
||||
result = re.match(_edid_line_pattern, next_line)
|
||||
|
||||
if not result:
|
||||
next_lines.append(next_line)
|
||||
break
|
||||
|
||||
matches = result.groupdict()
|
||||
edid_hex_value += matches["edid_line"]
|
||||
|
||||
edid = Edid(EdidHelper.hex2bytes(edid_hex_value))
|
||||
|
||||
model: Model = {
|
||||
"name": edid.name or "Generic",
|
||||
"product_id": str(edid.product),
|
||||
"serial_number": str(edid.serial),
|
||||
}
|
||||
return model
|
||||
|
||||
|
||||
# 1920x1080i 60.03*+ 59.93
|
||||
# 1920x1080 60.00 + 50.00 59.94
|
||||
_mode_pattern = r"\s*(?P<resolution_width>\d+)x(?P<resolution_height>\d+)(?P<is_high_resolution>i)?\s+(?P<rest>.*)"
|
||||
_frequencies_pattern = r"(((?P<frequency>\d+\.\d+)(?P<star>\*| |)(?P<plus>\+?)?)+)"
|
||||
|
||||
|
||||
def _parse_mode(line: str) -> Optional[Mode]:
|
||||
result = re.match(_mode_pattern, line)
|
||||
def _parse_resolution_mode(line: Line) -> ResolutionMode:
|
||||
frequencies: List[Frequency] = []
|
||||
|
||||
if not result:
|
||||
return None
|
||||
|
||||
d = result.groupdict()
|
||||
d = line.m.groupdict()
|
||||
resolution_width = int(d["resolution_width"])
|
||||
resolution_height = int(d["resolution_height"])
|
||||
is_high_resolution = d["is_high_resolution"] is not None
|
||||
|
||||
mode: Mode = {
|
||||
mode: ResolutionMode = {
|
||||
"resolution_width": resolution_width,
|
||||
"resolution_height": resolution_height,
|
||||
"is_high_resolution": is_high_resolution,
|
||||
@ -518,7 +483,45 @@ def _parse_mode(line: str) -> Optional[Mode]:
|
||||
return mode
|
||||
|
||||
|
||||
def parse(data: str, raw: bool = False, quiet: bool = False) -> Dict:
|
||||
def _parse_props(index: int, line: Line, lines: List[str]) -> Tuple[int, Props]:
|
||||
tmp_props: Dict[str, List[str]] = {}
|
||||
key = ""
|
||||
while index <= len(lines):
|
||||
if line.t == LineType.PropKey:
|
||||
d = line.m.groupdict()
|
||||
# See _prop_key_pattern
|
||||
key = d["key"]
|
||||
maybe_value = d["maybe_value"]
|
||||
if not maybe_value:
|
||||
tmp_props[key] = []
|
||||
else:
|
||||
tmp_props[key] = [maybe_value]
|
||||
elif line.t == LineType.PropValue:
|
||||
tmp_props[key].append(line.s.strip())
|
||||
else:
|
||||
# We've gone past our props and need to ascend
|
||||
index = index - 1
|
||||
break
|
||||
index += 1
|
||||
try:
|
||||
line = Line.categorize(lines[index])
|
||||
except:
|
||||
pass
|
||||
|
||||
props: Props = {}
|
||||
if "EDID" in tmp_props:
|
||||
edid = Edid(EdidHelper.hex2bytes("".join(tmp_props["EDID"])))
|
||||
model: EdidModel = {
|
||||
"name": edid.name or "Generic",
|
||||
"product_id": str(edid.product),
|
||||
"serial_number": str(edid.serial),
|
||||
}
|
||||
props["EdidModel"] = model
|
||||
|
||||
return index, {**tmp_props, **props}
|
||||
|
||||
|
||||
def parse(data: str, raw: bool = False, quiet: bool = False) -> Response:
|
||||
"""
|
||||
Main text parsing function
|
||||
|
||||
@ -535,15 +538,34 @@ def parse(data: str, raw: bool = False, quiet: bool = False) -> Dict:
|
||||
jc.utils.compatibility(__name__, info.compatible, quiet)
|
||||
jc.utils.input_type_check(data)
|
||||
|
||||
linedata = data.splitlines()
|
||||
linedata.reverse() # For popping
|
||||
result: Dict = {}
|
||||
index = 0
|
||||
lines = data.splitlines()
|
||||
screen, device = None, None
|
||||
|
||||
result: Response = {"screens": []}
|
||||
if jc.utils.has_data(data):
|
||||
result = {"screens": []}
|
||||
while linedata:
|
||||
screen = _parse_screen(linedata)
|
||||
if screen:
|
||||
while index < len(lines):
|
||||
line = Line.categorize(lines[index])
|
||||
if line.t == LineType.Screen:
|
||||
screen = _parse_screen(line)
|
||||
result["screens"].append(screen)
|
||||
elif line.t == LineType.Device:
|
||||
device = _parse_device(line)
|
||||
if not screen:
|
||||
raise Exception("There should be an identifiable screen")
|
||||
screen["devices"].append(device)
|
||||
elif line.t == LineType.ResolutionMode:
|
||||
resolution_mode = _parse_resolution_mode(line)
|
||||
if not device:
|
||||
raise Exception("Undefined device")
|
||||
device["resolution_modes"].append(resolution_mode)
|
||||
elif line.t == LineType.PropKey:
|
||||
# Props needs to be state aware, it owns the index.
|
||||
ix, props = _parse_props(index, line, lines)
|
||||
index = ix
|
||||
if not device:
|
||||
raise Exception("Undefined device")
|
||||
device["props"] = props
|
||||
index += 1
|
||||
|
||||
return result
|
||||
|
138
tests/fixtures/generic/xrandr_issue_525.out
vendored
Normal file
138
tests/fixtures/generic/xrandr_issue_525.out
vendored
Normal file
@ -0,0 +1,138 @@
|
||||
Screen 0: minimum 320 x 200, current 1920 x 1080, maximum 16384 x 16384
|
||||
eDP-1 connected primary 1920x1080+0+0 (normal left inverted right x axis y axis) 309mm x 174mm
|
||||
EDID:
|
||||
00ffffffffffff0006af3d5700000000
|
||||
001c0104a51f1178022285a5544d9a27
|
||||
0e505400000001010101010101010101
|
||||
010101010101b43780a070383e401010
|
||||
350035ae100000180000000f00000000
|
||||
00000000000000000020000000fe0041
|
||||
554f0a202020202020202020000000fe
|
||||
004231343048414e30352e37200a0070
|
||||
scaling mode: Full aspect
|
||||
supported: Full, Center, Full aspect
|
||||
Colorspace: Default
|
||||
supported: Default, RGB_Wide_Gamut_Fixed_Point, RGB_Wide_Gamut_Floating_Point, opRGB, DCI-P3_RGB_D65, BT2020_RGB, BT601_YCC, BT709_YCC, XVYCC_601, XVYCC_709, SYCC_601, opYCC_601, BT2020_CYCC, BT2020_YCC
|
||||
max bpc: 12
|
||||
range: (6, 12)
|
||||
Broadcast RGB: Automatic
|
||||
supported: Automatic, Full, Limited 16:235
|
||||
panel orientation: Normal
|
||||
supported: Normal, Upside Down, Left Side Up, Right Side Up
|
||||
link-status: Good
|
||||
supported: Good, Bad
|
||||
CTM: 0 1 0 0 0 0 0 0 0 1 0 0 0 0 0 0
|
||||
0 1
|
||||
CONNECTOR_ID: 95
|
||||
supported: 95
|
||||
non-desktop: 0
|
||||
range: (0, 1)
|
||||
1920x1080 60.03*+ 60.01 59.97 59.96 59.93
|
||||
1680x1050 59.95 59.88
|
||||
1400x1050 59.98
|
||||
1600x900 59.99 59.94 59.95 59.82
|
||||
1280x1024 60.02
|
||||
1400x900 59.96 59.88
|
||||
1280x960 60.00
|
||||
1440x810 60.00 59.97
|
||||
1368x768 59.88 59.85
|
||||
1280x800 59.99 59.97 59.81 59.91
|
||||
1280x720 60.00 59.99 59.86 59.74
|
||||
1024x768 60.04 60.00
|
||||
960x720 60.00
|
||||
928x696 60.05
|
||||
896x672 60.01
|
||||
1024x576 59.95 59.96 59.90 59.82
|
||||
960x600 59.93 60.00
|
||||
960x540 59.96 59.99 59.63 59.82
|
||||
800x600 60.00 60.32 56.25
|
||||
840x525 60.01 59.88
|
||||
864x486 59.92 59.57
|
||||
700x525 59.98
|
||||
800x450 59.95 59.82
|
||||
640x512 60.02
|
||||
700x450 59.96 59.88
|
||||
640x480 60.00 59.94
|
||||
720x405 59.51 58.99
|
||||
684x384 59.88 59.85
|
||||
640x400 59.88 59.98
|
||||
640x360 59.86 59.83 59.84 59.32
|
||||
512x384 60.00
|
||||
512x288 60.00 59.92
|
||||
480x270 59.63 59.82
|
||||
400x300 60.32 56.34
|
||||
432x243 59.92 59.57
|
||||
320x240 60.05
|
||||
360x202 59.51 59.13
|
||||
320x180 59.84 59.32
|
||||
DP-1 disconnected (normal left inverted right x axis y axis)
|
||||
HDCP Content Type: HDCP Type0
|
||||
supported: HDCP Type0, HDCP Type1
|
||||
Content Protection: Undesired
|
||||
supported: Undesired, Desired, Enabled
|
||||
Colorspace: Default
|
||||
supported: Default, RGB_Wide_Gamut_Fixed_Point, RGB_Wide_Gamut_Floating_Point, opRGB, DCI-P3_RGB_D65, BT2020_RGB, BT601_YCC, BT709_YCC, XVYCC_601, XVYCC_709, SYCC_601, opYCC_601, BT2020_CYCC, BT2020_YCC
|
||||
max bpc: 12
|
||||
range: (6, 12)
|
||||
Broadcast RGB: Automatic
|
||||
supported: Automatic, Full, Limited 16:235
|
||||
audio: auto
|
||||
supported: force-dvi, off, auto, on
|
||||
subconnector: Unknown
|
||||
supported: Unknown, VGA, DVI-D, HDMI, DP, Wireless, Native
|
||||
link-status: Good
|
||||
supported: Good, Bad
|
||||
CTM: 0 1 0 0 0 0 0 0 0 1 0 0 0 0 0 0
|
||||
0 1
|
||||
CONNECTOR_ID: 103
|
||||
supported: 103
|
||||
non-desktop: 0
|
||||
range: (0, 1)
|
||||
HDMI-1 disconnected (normal left inverted right x axis y axis)
|
||||
HDCP Content Type: HDCP Type0
|
||||
supported: HDCP Type0, HDCP Type1
|
||||
Content Protection: Undesired
|
||||
supported: Undesired, Desired, Enabled
|
||||
max bpc: 12
|
||||
range: (8, 12)
|
||||
content type: No Data
|
||||
supported: No Data, Graphics, Photo, Cinema, Game
|
||||
Colorspace: Default
|
||||
supported: Default, SMPTE_170M_YCC, BT709_YCC, XVYCC_601, XVYCC_709, SYCC_601, opYCC_601, opRGB, BT2020_CYCC, BT2020_RGB, BT2020_YCC, DCI-P3_RGB_D65, DCI-P3_RGB_Theater
|
||||
aspect ratio: Automatic
|
||||
supported: Automatic, 4:3, 16:9
|
||||
Broadcast RGB: Automatic
|
||||
supported: Automatic, Full, Limited 16:235
|
||||
audio: auto
|
||||
supported: force-dvi, off, auto, on
|
||||
link-status: Good
|
||||
supported: Good, Bad
|
||||
CTM: 0 1 0 0 0 0 0 0 0 1 0 0 0 0 0 0
|
||||
0 1
|
||||
CONNECTOR_ID: 113
|
||||
supported: 113
|
||||
non-desktop: 0
|
||||
range: (0, 1)
|
||||
DP-2 disconnected (normal left inverted right x axis y axis)
|
||||
HDCP Content Type: HDCP Type0
|
||||
supported: HDCP Type0, HDCP Type1
|
||||
Content Protection: Undesired
|
||||
supported: Undesired, Desired, Enabled
|
||||
Colorspace: Default
|
||||
supported: Default, RGB_Wide_Gamut_Fixed_Point, RGB_Wide_Gamut_Floating_Point, opRGB, DCI-P3_RGB_D65, BT2020_RGB, BT601_YCC, BT709_YCC, XVYCC_601, XVYCC_709, SYCC_601, opYCC_601, BT2020_CYCC, BT2020_YCC
|
||||
max bpc: 12
|
||||
range: (6, 12)
|
||||
Broadcast RGB: Automatic
|
||||
supported: Automatic, Full, Limited 16:235
|
||||
audio: auto
|
||||
supported: force-dvi, off, auto, on
|
||||
subconnector: Unknown
|
||||
supported: Unknown, VGA, DVI-D, HDMI, DP, Wireless, Native
|
||||
link-status: Good
|
||||
supported: Good, Bad
|
||||
CTM: 0 1 0 0 0 0 0 0 0 1 0 0 0 0 0 0
|
||||
0 1
|
||||
CONNECTOR_ID: 119
|
||||
supported: 119
|
||||
non-desktop: 0
|
||||
range: (0, 1)
|
138
tests/fixtures/generic/xrandr_properties_1.out
vendored
Normal file
138
tests/fixtures/generic/xrandr_properties_1.out
vendored
Normal file
@ -0,0 +1,138 @@
|
||||
Screen 0: minimum 320 x 200, current 1920 x 1080, maximum 16384 x 16384
|
||||
eDP-1 connected primary 1920x1080+0+0 (normal left inverted right x axis y axis) 309mm x 174mm
|
||||
EDID:
|
||||
00ffffffffffff0006af3d5700000000
|
||||
001c0104a51f1178022285a5544d9a27
|
||||
0e505400000001010101010101010101
|
||||
010101010101b43780a070383e401010
|
||||
350035ae100000180000000f00000000
|
||||
00000000000000000020000000fe0041
|
||||
554f0a202020202020202020000000fe
|
||||
004231343048414e30352e37200a0070
|
||||
scaling mode: Full aspect
|
||||
supported: Full, Center, Full aspect
|
||||
Colorspace: Default
|
||||
supported: Default, RGB_Wide_Gamut_Fixed_Point, RGB_Wide_Gamut_Floating_Point, opRGB, DCI-P3_RGB_D65, BT2020_RGB, BT601_YCC, BT709_YCC, XVYCC_601, XVYCC_709, SYCC_601, opYCC_601, BT2020_CYCC, BT2020_YCC
|
||||
max bpc: 12
|
||||
range: (6, 12)
|
||||
Broadcast RGB: Automatic
|
||||
supported: Automatic, Full, Limited 16:235
|
||||
panel orientation: Normal
|
||||
supported: Normal, Upside Down, Left Side Up, Right Side Up
|
||||
link-status: Good
|
||||
supported: Good, Bad
|
||||
CTM: 0 1 0 0 0 0 0 0 0 1 0 0 0 0 0 0
|
||||
0 1
|
||||
CONNECTOR_ID: 95
|
||||
supported: 95
|
||||
non-desktop: 0
|
||||
range: (0, 1)
|
||||
1920x1080 60.03*+ 60.01 59.97 59.96 59.93
|
||||
1680x1050 59.95 59.88
|
||||
1400x1050 59.98
|
||||
1600x900 59.99 59.94 59.95 59.82
|
||||
1280x1024 60.02
|
||||
1400x900 59.96 59.88
|
||||
1280x960 60.00
|
||||
1440x810 60.00 59.97
|
||||
1368x768 59.88 59.85
|
||||
1280x800 59.99 59.97 59.81 59.91
|
||||
1280x720 60.00 59.99 59.86 59.74
|
||||
1024x768 60.04 60.00
|
||||
960x720 60.00
|
||||
928x696 60.05
|
||||
896x672 60.01
|
||||
1024x576 59.95 59.96 59.90 59.82
|
||||
960x600 59.93 60.00
|
||||
960x540 59.96 59.99 59.63 59.82
|
||||
800x600 60.00 60.32 56.25
|
||||
840x525 60.01 59.88
|
||||
864x486 59.92 59.57
|
||||
700x525 59.98
|
||||
800x450 59.95 59.82
|
||||
640x512 60.02
|
||||
700x450 59.96 59.88
|
||||
640x480 60.00 59.94
|
||||
720x405 59.51 58.99
|
||||
684x384 59.88 59.85
|
||||
640x400 59.88 59.98
|
||||
640x360 59.86 59.83 59.84 59.32
|
||||
512x384 60.00
|
||||
512x288 60.00 59.92
|
||||
480x270 59.63 59.82
|
||||
400x300 60.32 56.34
|
||||
432x243 59.92 59.57
|
||||
320x240 60.05
|
||||
360x202 59.51 59.13
|
||||
320x180 59.84 59.32
|
||||
DP-1 disconnected (normal left inverted right x axis y axis)
|
||||
HDCP Content Type: HDCP Type0
|
||||
supported: HDCP Type0, HDCP Type1
|
||||
Content Protection: Undesired
|
||||
supported: Undesired, Desired, Enabled
|
||||
Colorspace: Default
|
||||
supported: Default, RGB_Wide_Gamut_Fixed_Point, RGB_Wide_Gamut_Floating_Point, opRGB, DCI-P3_RGB_D65, BT2020_RGB, BT601_YCC, BT709_YCC, XVYCC_601, XVYCC_709, SYCC_601, opYCC_601, BT2020_CYCC, BT2020_YCC
|
||||
max bpc: 12
|
||||
range: (6, 12)
|
||||
Broadcast RGB: Automatic
|
||||
supported: Automatic, Full, Limited 16:235
|
||||
audio: auto
|
||||
supported: force-dvi, off, auto, on
|
||||
subconnector: Unknown
|
||||
supported: Unknown, VGA, DVI-D, HDMI, DP, Wireless, Native
|
||||
link-status: Good
|
||||
supported: Good, Bad
|
||||
CTM: 0 1 0 0 0 0 0 0 0 1 0 0 0 0 0 0
|
||||
0 1
|
||||
CONNECTOR_ID: 103
|
||||
supported: 103
|
||||
non-desktop: 0
|
||||
range: (0, 1)
|
||||
HDMI-1 disconnected (normal left inverted right x axis y axis)
|
||||
HDCP Content Type: HDCP Type0
|
||||
supported: HDCP Type0, HDCP Type1
|
||||
Content Protection: Undesired
|
||||
supported: Undesired, Desired, Enabled
|
||||
max bpc: 12
|
||||
range: (8, 12)
|
||||
content type: No Data
|
||||
supported: No Data, Graphics, Photo, Cinema, Game
|
||||
Colorspace: Default
|
||||
supported: Default, SMPTE_170M_YCC, BT709_YCC, XVYCC_601, XVYCC_709, SYCC_601, opYCC_601, opRGB, BT2020_CYCC, BT2020_RGB, BT2020_YCC, DCI-P3_RGB_D65, DCI-P3_RGB_Theater
|
||||
aspect ratio: Automatic
|
||||
supported: Automatic, 4:3, 16:9
|
||||
Broadcast RGB: Automatic
|
||||
supported: Automatic, Full, Limited 16:235
|
||||
audio: auto
|
||||
supported: force-dvi, off, auto, on
|
||||
link-status: Good
|
||||
supported: Good, Bad
|
||||
CTM: 0 1 0 0 0 0 0 0 0 1 0 0 0 0 0 0
|
||||
0 1
|
||||
CONNECTOR_ID: 113
|
||||
supported: 113
|
||||
non-desktop: 0
|
||||
range: (0, 1)
|
||||
DP-2 disconnected (normal left inverted right x axis y axis)
|
||||
HDCP Content Type: HDCP Type0
|
||||
supported: HDCP Type0, HDCP Type1
|
||||
Content Protection: Undesired
|
||||
supported: Undesired, Desired, Enabled
|
||||
Colorspace: Default
|
||||
supported: Default, RGB_Wide_Gamut_Fixed_Point, RGB_Wide_Gamut_Floating_Point, opRGB, DCI-P3_RGB_D65, BT2020_RGB, BT601_YCC, BT709_YCC, XVYCC_601, XVYCC_709, SYCC_601, opYCC_601, BT2020_CYCC, BT2020_YCC
|
||||
max bpc: 12
|
||||
range: (6, 12)
|
||||
Broadcast RGB: Automatic
|
||||
supported: Automatic, Full, Limited 16:235
|
||||
audio: auto
|
||||
supported: force-dvi, off, auto, on
|
||||
subconnector: Unknown
|
||||
supported: Unknown, VGA, DVI-D, HDMI, DP, Wireless, Native
|
||||
link-status: Good
|
||||
supported: Good, Bad
|
||||
CTM: 0 1 0 0 0 0 0 0 0 1 0 0 0 0 0 0
|
||||
0 1
|
||||
CONNECTOR_ID: 119
|
||||
supported: 119
|
||||
non-desktop: 0
|
||||
range: (0, 1)
|
@ -1,36 +1,33 @@
|
||||
import pprint
|
||||
import re
|
||||
import unittest
|
||||
from typing import Optional
|
||||
|
||||
from jc.parsers.xrandr import (
|
||||
_parse_screen,
|
||||
_parse_device,
|
||||
_parse_mode,
|
||||
_parse_model,
|
||||
_device_pattern,
|
||||
_screen_pattern,
|
||||
_mode_pattern,
|
||||
_frequencies_pattern,
|
||||
_edid_head_pattern,
|
||||
_edid_line_pattern,
|
||||
parse,
|
||||
Mode,
|
||||
Model,
|
||||
Device,
|
||||
Screen
|
||||
Edid,
|
||||
Line,
|
||||
LineType,
|
||||
ResolutionMode,
|
||||
Response,
|
||||
Screen,
|
||||
_device_pattern,
|
||||
_frequencies_pattern,
|
||||
_parse_device,
|
||||
_parse_resolution_mode,
|
||||
_parse_screen,
|
||||
_resolution_mode_pattern,
|
||||
_screen_pattern,
|
||||
parse,
|
||||
)
|
||||
import jc.parsers.xrandr
|
||||
|
||||
|
||||
class XrandrTests(unittest.TestCase):
|
||||
def setUp(self):
|
||||
jc.parsers.xrandr.parse_state = {}
|
||||
|
||||
def test_xrandr_nodata(self):
|
||||
"""
|
||||
Test 'xrandr' with no data
|
||||
"""
|
||||
self.assertEqual(parse("", quiet=True), {})
|
||||
self.assertEqual(parse("", quiet=True), {"screens": []})
|
||||
|
||||
def test_regexes(self):
|
||||
devices = [
|
||||
@ -61,37 +58,30 @@ class XrandrTests(unittest.TestCase):
|
||||
"1400x900 59.96 59.88",
|
||||
]
|
||||
for mode in modes:
|
||||
match = re.match(_mode_pattern, mode)
|
||||
match = re.match(_resolution_mode_pattern, mode)
|
||||
self.assertIsNotNone(match)
|
||||
if match:
|
||||
rest = match.groupdict()["rest"]
|
||||
self.assertIsNotNone(re.match(_frequencies_pattern, rest))
|
||||
|
||||
edid_lines = [
|
||||
" EDID: ",
|
||||
" 00ffffffffffff000469d41901010101 ",
|
||||
" 2011010308291a78ea8585a6574a9c26 ",
|
||||
" 125054bfef80714f8100810f81408180 ",
|
||||
" 9500950f01019a29a0d0518422305098 ",
|
||||
" 360098ff1000001c000000fd00374b1e ",
|
||||
" 530f000a202020202020000000fc0041 ",
|
||||
" 535553205657313933530a20000000ff ",
|
||||
" 0037384c383032313130370a20200077 ",
|
||||
]
|
||||
def test_line_categorize(self):
|
||||
base = "eDP-1 connected primary 1920x1080+0+0 (normal left inverted right x axis y axis) 309mm x 174mm"
|
||||
resolution_mode = " 320x240 60.05"
|
||||
prop_key = " EDID:"
|
||||
prop_value = " 00ffffffffffff0006af3d5700000000"
|
||||
invalid = ""
|
||||
|
||||
for i in range(len(edid_lines)):
|
||||
line = edid_lines[i]
|
||||
if i == 0:
|
||||
match = re.match(_edid_head_pattern, line)
|
||||
else:
|
||||
match = re.match(_edid_line_pattern, line)
|
||||
|
||||
self.assertIsNotNone(match)
|
||||
self.assertEqual(LineType.Device, Line.categorize(base).t)
|
||||
self.assertEqual(LineType.ResolutionMode, Line.categorize(resolution_mode).t)
|
||||
self.assertEqual(LineType.PropKey, Line.categorize(prop_key).t)
|
||||
self.assertEqual(LineType.PropValue, Line.categorize(prop_value).t)
|
||||
with self.assertRaises(Exception):
|
||||
Line.categorize(invalid)
|
||||
|
||||
def test_screens(self):
|
||||
sample = "Screen 0: minimum 8 x 8, current 1920 x 1080, maximum 32767 x 32767"
|
||||
|
||||
actual: Optional[Screen] = _parse_screen([sample])
|
||||
line = Line.categorize(sample)
|
||||
actual: Optional[Screen] = _parse_screen(line)
|
||||
self.assertIsNotNone(actual)
|
||||
|
||||
expected = {
|
||||
@ -110,7 +100,8 @@ class XrandrTests(unittest.TestCase):
|
||||
sample = (
|
||||
"Screen 0: minimum 320 x 200, current 1920 x 1080, maximum 16384 x 16384"
|
||||
)
|
||||
actual = _parse_screen([sample])
|
||||
line = Line.categorize(sample)
|
||||
actual = _parse_screen(line)
|
||||
if actual:
|
||||
self.assertEqual(320, actual["minimum_width"])
|
||||
else:
|
||||
@ -119,7 +110,8 @@ class XrandrTests(unittest.TestCase):
|
||||
def test_device(self):
|
||||
# regex101 sample link for tests/edits https://regex101.com/r/3cHMv3/1
|
||||
sample = "eDP1 connected primary 1920x1080+0+0 left (normal left inverted right x axis y axis) 310mm x 170mm"
|
||||
actual: Optional[Device] = _parse_device([sample])
|
||||
line = Line.categorize(sample)
|
||||
actual: Optional[Device] = _parse_device(line)
|
||||
|
||||
expected = {
|
||||
"device_name": "eDP1",
|
||||
@ -140,17 +132,19 @@ class XrandrTests(unittest.TestCase):
|
||||
for k, v in expected.items():
|
||||
self.assertEqual(v, actual[k], f"Devices regex failed on {k}")
|
||||
|
||||
with open("tests/fixtures/generic/xrandr_device.out", "r") as f:
|
||||
extended_sample = f.read().splitlines()
|
||||
extended_sample.reverse()
|
||||
# with open("tests/fixtures/generic/xrandr_device.out", "r") as f:
|
||||
# extended_sample = f.read().splitlines()
|
||||
|
||||
device = _parse_device(extended_sample)
|
||||
if device:
|
||||
self.assertEqual(59.94, device["modes"][12]["frequencies"][4]["frequency"])
|
||||
# device = _parse_device(extended_sample)
|
||||
# if device:
|
||||
# self.assertEqual(
|
||||
# 59.94, device["resolution_modes"][12]["frequencies"][4]["frequency"]
|
||||
# )
|
||||
|
||||
def test_device_with_reflect(self):
|
||||
sample = "VGA-1 connected primary 1920x1080+0+0 left X and Y axis (normal left inverted right x axis y axis) 310mm x 170mm"
|
||||
actual: Optional[Device] = _parse_device([sample])
|
||||
line = Line.categorize(sample)
|
||||
actual: Optional[Device] = _parse_device(line)
|
||||
|
||||
expected = {
|
||||
"device_name": "VGA-1",
|
||||
@ -183,7 +177,8 @@ class XrandrTests(unittest.TestCase):
|
||||
"resolution_height": 1080,
|
||||
"is_high_resolution": False,
|
||||
}
|
||||
actual: Optional[Mode] = _parse_mode(sample_1)
|
||||
line = Line.categorize(sample_1)
|
||||
actual: Optional[ResolutionMode] = _parse_resolution_mode(line)
|
||||
|
||||
self.assertIsNotNone(actual)
|
||||
|
||||
@ -192,7 +187,8 @@ class XrandrTests(unittest.TestCase):
|
||||
self.assertEqual(v, actual[k], f"mode regex failed on {k}")
|
||||
|
||||
sample_2 = " 1920x1080i 60.00 50.00 59.94"
|
||||
actual: Optional[Mode] = _parse_mode(sample_2)
|
||||
line = Line.categorize(sample_2)
|
||||
actual: Optional[ResolutionMode] = _parse_resolution_mode(line)
|
||||
self.assertIsNotNone(actual)
|
||||
if actual:
|
||||
self.assertEqual(True, actual["is_high_resolution"])
|
||||
@ -205,7 +201,9 @@ class XrandrTests(unittest.TestCase):
|
||||
actual = parse(txt, quiet=True)
|
||||
|
||||
self.assertEqual(1, len(actual["screens"]))
|
||||
self.assertEqual(18, len(actual["screens"][0]["devices"][0]["modes"]))
|
||||
self.assertEqual(
|
||||
18, len(actual["screens"][0]["devices"][0]["resolution_modes"])
|
||||
)
|
||||
|
||||
def test_complete_2(self):
|
||||
with open("tests/fixtures/generic/xrandr_2.out", "r") as f:
|
||||
@ -213,7 +211,9 @@ class XrandrTests(unittest.TestCase):
|
||||
actual = parse(txt, quiet=True)
|
||||
|
||||
self.assertEqual(1, len(actual["screens"]))
|
||||
self.assertEqual(38, len(actual["screens"][0]["devices"][0]["modes"]))
|
||||
self.assertEqual(
|
||||
38, len(actual["screens"][0]["devices"][0]["resolution_modes"])
|
||||
)
|
||||
|
||||
def test_complete_3(self):
|
||||
with open("tests/fixtures/generic/xrandr_3.out", "r") as f:
|
||||
@ -232,84 +232,119 @@ class XrandrTests(unittest.TestCase):
|
||||
actual = parse(txt, quiet=True)
|
||||
|
||||
self.assertEqual(1, len(actual["screens"]))
|
||||
self.assertEqual(2, len(actual["screens"][0]["devices"][0]["modes"]))
|
||||
self.assertEqual(2, len(actual["screens"][0]["devices"][0]["resolution_modes"]))
|
||||
|
||||
def test_complete_5(self):
|
||||
with open("tests/fixtures/generic/xrandr_properties.out", "r") as f:
|
||||
with open("tests/fixtures/generic/xrandr_properties_1.out", "r") as f:
|
||||
txt = f.read()
|
||||
actual = parse(txt, quiet=True)
|
||||
|
||||
self.assertEqual(1, len(actual["screens"]))
|
||||
self.assertEqual(29, len(actual["screens"][0]["devices"][0]["modes"]))
|
||||
self.assertEqual(
|
||||
38, len(actual["screens"][0]["devices"][0]["resolution_modes"])
|
||||
)
|
||||
|
||||
def test_model(self):
|
||||
asus_edid = [
|
||||
" EDID: ",
|
||||
" 00ffffffffffff000469d41901010101",
|
||||
" 2011010308291a78ea8585a6574a9c26",
|
||||
" 125054bfef80714f8100810f81408180",
|
||||
" 9500950f01019a29a0d0518422305098",
|
||||
" 360098ff1000001c000000fd00374b1e",
|
||||
" 530f000a202020202020000000fc0041",
|
||||
" 535553205657313933530a20000000ff",
|
||||
" 0037384c383032313130370a20200077",
|
||||
]
|
||||
asus_edid.reverse()
|
||||
# def test_model(self):
|
||||
# asus_edid = [
|
||||
# " EDID: ",
|
||||
# " 00ffffffffffff000469d41901010101",
|
||||
# " 2011010308291a78ea8585a6574a9c26",
|
||||
# " 125054bfef80714f8100810f81408180",
|
||||
# " 9500950f01019a29a0d0518422305098",
|
||||
# " 360098ff1000001c000000fd00374b1e",
|
||||
# " 530f000a202020202020000000fc0041",
|
||||
# " 535553205657313933530a20000000ff",
|
||||
# " 0037384c383032313130370a20200077",
|
||||
# ]
|
||||
# asus_edid.reverse()
|
||||
|
||||
expected = {
|
||||
"name": "ASUS VW193S",
|
||||
"product_id": "6612",
|
||||
"serial_number": "78L8021107",
|
||||
}
|
||||
# expected = {
|
||||
# "name": "ASUS VW193S",
|
||||
# "product_id": "6612",
|
||||
# "serial_number": "78L8021107",
|
||||
# }
|
||||
|
||||
actual: Optional[Model] = _parse_model(asus_edid)
|
||||
self.assertIsNotNone(actual)
|
||||
# actual: Optional[EdidModel] = _parse_model(asus_edid)
|
||||
# self.assertIsNotNone(actual)
|
||||
|
||||
if actual:
|
||||
for k, v in expected.items():
|
||||
self.assertEqual(v, actual[k], f"mode regex failed on {k}")
|
||||
# if actual:
|
||||
# for k, v in expected.items():
|
||||
# self.assertEqual(v, actual[k], f"mode regex failed on {k}")
|
||||
|
||||
generic_edid = [
|
||||
" EDID: ",
|
||||
" 00ffffffffffff004ca3523100000000",
|
||||
" 0014010380221378eac8959e57549226",
|
||||
" 0f505400000001010101010101010101",
|
||||
" 010101010101381d56d4500016303020",
|
||||
" 250058c2100000190000000f00000000",
|
||||
" 000000000025d9066a00000000fe0053",
|
||||
" 414d53554e470a204ca34154000000fe",
|
||||
" 004c544e313536415432343430310018",
|
||||
]
|
||||
generic_edid.reverse()
|
||||
# generic_edid = [
|
||||
# " EDID: ",
|
||||
# " 00ffffffffffff004ca3523100000000",
|
||||
# " 0014010380221378eac8959e57549226",
|
||||
# " 0f505400000001010101010101010101",
|
||||
# " 010101010101381d56d4500016303020",
|
||||
# " 250058c2100000190000000f00000000",
|
||||
# " 000000000025d9066a00000000fe0053",
|
||||
# " 414d53554e470a204ca34154000000fe",
|
||||
# " 004c544e313536415432343430310018",
|
||||
# ]
|
||||
# generic_edid.reverse()
|
||||
|
||||
expected = {
|
||||
"name": "Generic",
|
||||
"product_id": "12626",
|
||||
"serial_number": "0",
|
||||
}
|
||||
# expected = {
|
||||
# "name": "Generic",
|
||||
# "product_id": "12626",
|
||||
# "serial_number": "0",
|
||||
# }
|
||||
|
||||
jc.parsers.xrandr.parse_state = {}
|
||||
actual: Optional[Model] = _parse_model(generic_edid)
|
||||
self.assertIsNotNone(actual)
|
||||
# jc.parsers.xrandr.parse_state = {}
|
||||
# actual: Optional[EdidModel] = _parse_model(generic_edid)
|
||||
# self.assertIsNotNone(actual)
|
||||
|
||||
if actual:
|
||||
for k, v in expected.items():
|
||||
self.assertEqual(v, actual[k], f"mode regex failed on {k}")
|
||||
|
||||
empty_edid = [""]
|
||||
actual: Optional[Model] = _parse_model(empty_edid)
|
||||
self.assertIsNone(actual)
|
||||
# if actual:
|
||||
# for k, v in expected.items():
|
||||
# self.assertEqual(v, actual[k], f"mode regex failed on {k}")
|
||||
|
||||
# empty_edid = [""]
|
||||
# actual: Optional[EdidModel] = _parse_model(empty_edid)
|
||||
# self.assertIsNone(actual)
|
||||
|
||||
def test_issue_490(self):
|
||||
"""test for issue 490: https://github.com/kellyjonbrazil/jc/issues/490"""
|
||||
data_in = '''\
|
||||
data_in = """\
|
||||
Screen 0: minimum 1024 x 600, current 1024 x 600, maximum 1024 x 600
|
||||
default connected 1024x600+0+0 0mm x 0mm
|
||||
1024x600 0.00*
|
||||
'''
|
||||
expected = {"screens":[{"devices":[{"modes":[{"resolution_width":1024,"resolution_height":600,"is_high_resolution":False,"frequencies":[{"frequency":0.0,"is_current":True,"is_preferred":False}]}],"is_connected":True,"is_primary":False,"device_name":"default","rotation":"normal","reflection":"normal","resolution_width":1024,"resolution_height":600,"offset_width":0,"offset_height":0,"dimension_width":0,"dimension_height":0}],"screen_number":0,"minimum_width":1024,"minimum_height":600,"current_width":1024,"current_height":600,"maximum_width":1024,"maximum_height":600}]}
|
||||
self.assertEqual(jc.parsers.xrandr.parse(data_in), expected)
|
||||
"""
|
||||
actual: Response = parse(data_in)
|
||||
self.maxDiff = None
|
||||
self.assertEqual(1024, actual["screens"][0]["devices"][0]["resolution_width"])
|
||||
|
||||
def test_issue_525(self):
|
||||
self.maxDiff = None
|
||||
with open("tests/fixtures/generic/xrandr_issue_525.out", "r") as f:
|
||||
txt = f.read()
|
||||
actual = parse(txt, quiet=True)
|
||||
dp4 = actual["screens"][0]["devices"][0]["props"]["Broadcast RGB"][1] # type: ignore
|
||||
# pprint.pprint(actual)
|
||||
self.assertEqual("supported: Automatic, Full, Limited 16:235", dp4)
|
||||
edp1_expected_keys = {
|
||||
"EDID",
|
||||
"EdidModel",
|
||||
"scaling mode",
|
||||
"Colorspace",
|
||||
"max bpc",
|
||||
"Broadcast RGB",
|
||||
"panel orientation",
|
||||
"link-status",
|
||||
"CTM",
|
||||
"CONNECTOR_ID",
|
||||
"non-desktop",
|
||||
}
|
||||
actual_keys = set(actual["screens"][0]["devices"][0]["props"].keys())
|
||||
self.assertSetEqual(edp1_expected_keys, actual_keys)
|
||||
expected_edid_model = {
|
||||
"name": "Generic",
|
||||
"product_id": "22333",
|
||||
"serial_number": "0",
|
||||
}
|
||||
self.assertDictEqual(
|
||||
expected_edid_model,
|
||||
actual["screens"][0]["devices"][0]["props"]["EdidModel"], # type: ignore
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
Reference in New Issue
Block a user