From deab1c069128f9e839f8b8dcb233147efef1cdef Mon Sep 17 00:00:00 2001 From: Erkan Ozgur Yilmaz Date: Wed, 16 Oct 2024 17:13:25 +0100 Subject: [PATCH 01/34] [#16] Add a new `DisplayCAL.argyll` module to collect all the Argyll related utility classes and functions. --- DisplayCAL/argyll.py | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 DisplayCAL/argyll.py diff --git a/DisplayCAL/argyll.py b/DisplayCAL/argyll.py new file mode 100644 index 00000000..5b498619 --- /dev/null +++ b/DisplayCAL/argyll.py @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- +"""Argyll utilities situated here. + +The utilities that were previously spread around are gathered here. +""" From 22fa7a6b760af6c2f33df2950a128614285d3d5a Mon Sep 17 00:00:00 2001 From: Erkan Ozgur Yilmaz Date: Wed, 16 Oct 2024 18:12:07 +0100 Subject: [PATCH 02/34] [#16] Added a new test module for `DisplayCAL.argyll`. --- tests/test_argyll.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 tests/test_argyll.py diff --git a/tests/test_argyll.py b/tests/test_argyll.py new file mode 100644 index 00000000..e69de29b From 9067de7cb2b208b331d39888fd81e8ca5252739f Mon Sep 17 00:00:00 2001 From: Erkan Ozgur Yilmaz Date: Wed, 16 Oct 2024 22:29:38 +0100 Subject: [PATCH 03/34] * [#16] Moved ArgyllCMS related utility functions to their own module at `DisplayCAL.argyll`. --- DisplayCAL/argyll.py | 212 +++++++++++++++++++++++++++++++++++++ DisplayCAL/argyll_cgats.py | 10 +- DisplayCAL/display_cal.py | 2 +- DisplayCAL/worker.py | 64 +++++------ DisplayCAL/worker_base.py | 198 +++------------------------------- tests/conftest.py | 2 +- tests/test_argyll.py | 37 +++++++ tests/test_worker.py | 4 +- tests/test_worker_base.py | 33 ------ 9 files changed, 299 insertions(+), 263 deletions(-) diff --git a/DisplayCAL/argyll.py b/DisplayCAL/argyll.py index 5b498619..78bae191 100644 --- a/DisplayCAL/argyll.py +++ b/DisplayCAL/argyll.py @@ -3,3 +3,215 @@ The utilities that were previously spread around are gathered here. """ +# Standard Library Imports +import os +import re +import subprocess as sp +import sys +import urllib.error +import urllib.request + +# Local Imports +from DisplayCAL.argyll_names import ( + names as argyll_names, + altnames as argyll_altnames, + optional as argyll_optional, +) +from DisplayCAL import config +from DisplayCAL.config import exe_ext, fs_enc, get_data_path, getcfg +from DisplayCAL.options import debug, verbose +from DisplayCAL.util_os import getenvu, which + +argyll_utils = {} + + +def check_argyll_bin(paths=None): + """Check if the Argyll binaries can be found.""" + prev_dir = None + cur_dir = os.curdir + for name in argyll_names: + exe = get_argyll_util(name, paths) + if not exe: + if name in argyll_optional: + continue + return False + cur_dir = os.path.dirname(exe) + if prev_dir: + if cur_dir != prev_dir: + if name in argyll_optional: + if verbose: + print( + "Warning: Optional Argyll executable %s is not in the same " + "directory as the main executables (%s)." % (exe, prev_dir) + ) + else: + if verbose: + print( + "Error: Main Argyll executable %s is not in the same " + "directory as the other executables (%s)." % (exe, prev_dir) + ) + return False + else: + prev_dir = cur_dir + if verbose >= 3: + print("Argyll binary directory:", cur_dir) + if debug: + print("[D] check_argyll_bin OK") + if debug >= 2: + if not paths: + paths = getenvu("PATH", os.defpath).split(os.pathsep) + argyll_dir = (getcfg("argyll.dir") or "").rstrip(os.path.sep) + if argyll_dir: + if argyll_dir in paths: + paths.remove(argyll_dir) + paths = [argyll_dir] + paths + print("[D] Searchpath:\n ", "\n ".join(paths)) + # Fedora doesn't ship Rec709.icm + config.defaults["3dlut.input.profile"] = ( + get_data_path(os.path.join("ref", "Rec709.icm")) + or get_data_path(os.path.join("ref", "sRGB.icm")) + or "" + ) + config.defaults["testchart.reference"] = ( + get_data_path(os.path.join("ref", "ColorChecker.cie")) or "" + ) + config.defaults["gamap_profile"] = ( + get_data_path(os.path.join("ref", "sRGB.icm")) or "" + ) + return True + + +def get_argyll_util(name, paths=None): + """Find a single Argyll utility. Return the full path. + + Args: + name (str): The name of the utility. + paths (Union[None, List[str]]): The paths to look for. + + Returns: + Union[None, str]: None if not found or the path of the utility. + """ + cfg_argyll_dir = getcfg("argyll.dir") + if not paths: + paths = getenvu("PATH", os.defpath).split(os.pathsep) + argyll_dir = (cfg_argyll_dir or "").rstrip(os.path.sep) + if argyll_dir: + if argyll_dir in paths: + paths.remove(argyll_dir) + paths = [argyll_dir] + paths + cache_key = os.pathsep.join(paths) + exe = argyll_utils.get(cache_key, {}).get(name, None) + if exe: + return exe + elif verbose >= 4: + print("Info: Searching for", name, "in", os.pathsep.join(paths)) + for path in paths: + for altname in argyll_altnames.get(name, []): + exe = which(altname + exe_ext, [path]) + if exe: + break + if exe: + break + if verbose >= 4: + if exe: + print("Info:", name, "=", exe) + else: + print( + "Info:", + "|".join(argyll_altnames[name]), + "not found in", + os.pathsep.join(paths), + ) + if exe: + if cache_key not in argyll_utils: + argyll_utils[cache_key] = {} + argyll_utils[cache_key][name] = exe + return exe + + +def get_argyll_utilname(name, paths=None): + """Find a single Argyll utility. + + Return the basename without extension. + """ + exe = get_argyll_util(name, paths) + if exe: + exe = os.path.basename(os.path.splitext(exe)[0]) + return exe + + +def get_argyll_version(name, paths=None): + """Determine version of a certain Argyll utility.""" + argyll_version_string = get_argyll_version_string(name, paths) + return parse_argyll_version_string(argyll_version_string) + + +def get_argyll_version_string(name, paths=None): + """Return the version of the requested Argyll utility. + + Args: + name (str): The name of the Argyll utility. + paths (Union[list, None]): Paths to look for Argyll executables. + + Returns: + str: The Argyll utility version. + """ + argyll_version_string = b"0.0.0" + cmd = get_argyll_util(name, paths) + if sys.platform == "win32": + startupinfo = sp.STARTUPINFO() + startupinfo.dwFlags |= sp.STARTF_USESHOWWINDOW + startupinfo.wShowWindow = sp.SW_HIDE + else: + startupinfo = None + try: + p = sp.Popen( + [cmd.encode(fs_enc), "-?"], + stdin=sp.PIPE, + stdout=sp.PIPE, + stderr=sp.STDOUT, + startupinfo=startupinfo, + ) + except Exception as exception: + print(exception) + return argyll_version_string.decode("utf-8") + for line in (p.communicate(timeout=30)[0] or "").splitlines(): + if isinstance(line, bytes): + line = line.strip() + if b"version" in line.lower(): + argyll_version_string = line[line.lower().find(b"version") + 8:] + break + return argyll_version_string.decode("utf-8") + + +def parse_argyll_version_string(argyll_version_string): + if isinstance(argyll_version_string, bytes): + argyll_version_string = argyll_version_string.decode() + argyll_version = re.findall(r"(\d+|[^.\d]+)", argyll_version_string) + for i, v in enumerate(argyll_version): + try: + argyll_version[i] = int(v) + except ValueError: + pass + return argyll_version + + +def get_argyll_latest_version(): + """Return the latest ArgyllCMS version from argyllcms.com. + + Returns: + str: The latest version number. Returns + """ + argyll_domain = config.defaults.get("argyll.domain", "") + try: + changelog = re.search( + r"(?<=Version ).{5}", + urllib.request.urlopen(f"{argyll_domain}/log.txt") + .read(150) + .decode("utf-8"), + ) + except urllib.error.URLError as e: + # no internet connection + # return the default version + return config.defaults.get("argyll.version") + return changelog.group() diff --git a/DisplayCAL/argyll_cgats.py b/DisplayCAL/argyll_cgats.py index 238e84cd..bd27e0e3 100644 --- a/DisplayCAL/argyll_cgats.py +++ b/DisplayCAL/argyll_cgats.py @@ -71,7 +71,6 @@ def cal_to_fake_profile(cal): cal must refer to a valid Argyll CAL file and can be a CGATS instance or a filename. - """ vcgt, cal = cal_to_vcgt(cal, True) if not vcgt: @@ -95,7 +94,6 @@ def cal_to_vcgt(cal, return_cgats=False): cal must refer to a valid Argyll CAL file and can be a CGATS instance or a filename. - """ if not isinstance(cal, CGATS.CGATS): try: @@ -180,7 +178,6 @@ def extract_cal_from_profile( profile, out_cal_path=None, raise_on_missing_cal=True, prefer_cal=False ): """Extract calibration from 'targ' tag in profile or vcgt as fallback""" - white = False # Check if calibration is included in TI3 @@ -284,7 +281,6 @@ def extract_cal_from_ti3(ti3): """Extract and return the CAL section of a TI3. ti3 can be a file object or a string holding the data. - """ if isinstance(ti3, CGATS.CGATS): ti3 = bytes(ti3) @@ -315,7 +311,6 @@ def extract_fix_copy_cal(source_filename, target_filename=None): Try to 'fix it' (add information needed to make the resulting .cal file 'updateable') and optionally copy it to target_filename. - """ from DisplayCAL.worker import get_options_from_profile @@ -433,7 +428,6 @@ def extract_device_gray_primaries( """Extract gray or primaries into new TI3 Return extracted ti3, extracted RGB to XYZ mapping and remaining RGB to XYZ - """ filename = ti3.filename ti3 = ti3.queryi1("DATA") @@ -536,7 +530,6 @@ def ti3_to_ti1(ti3_data): """Create and return TI1 data converted from TI3. ti3_data can be a file object, a list of strings or a string holding the data. - """ ti3 = CGATS.CGATS(ti3_data) if not ti3: @@ -558,7 +551,7 @@ def ti3_to_ti1(ti3_data): def vcgt_to_cal(profile): - """Return a CAL (CGATS instance) from vcgt""" + """Return a CAL (CGATS instance) from vcgt.""" cgats = CGATS.CGATS(file_identifier=b"CAL") context = cgats.add_data({"DESCRIPTOR": b"Argyll Device Calibration State"}) context.add_data({"ORIGINATOR": b"vcgt"}) @@ -630,6 +623,5 @@ def verify_ti1_rgb_xyz(cgats): Verify if a CGATS instance has a TI1 section with all required fields for RGB devices. Return the TI1 section as CGATS instance on success, None on failure. - """ return verify_cgats(cgats, ("RGB_R", "RGB_B", "RGB_G", "XYZ_X", "XYZ_Y", "XYZ_Z")) diff --git a/DisplayCAL/display_cal.py b/DisplayCAL/display_cal.py index dfa525bd..9181bcf9 100644 --- a/DisplayCAL/display_cal.py +++ b/DisplayCAL/display_cal.py @@ -209,7 +209,6 @@ check_ti3_criteria1, check_ti3_criteria2, get_arg, - get_argyll_latest_version, get_argyll_util, get_argyll_version, get_cfg_option_from_args, @@ -225,6 +224,7 @@ FilteredStream, _applycal_bug_workaround, ) +from DisplayCAL.argyll import get_argyll_latest_version from DisplayCAL.wxLUT3DFrame import LUT3DFrame, LUT3DMixin try: diff --git a/DisplayCAL/worker.py b/DisplayCAL/worker.py index 12d1f10d..7a962c0d 100644 --- a/DisplayCAL/worker.py +++ b/DisplayCAL/worker.py @@ -46,7 +46,6 @@ from _thread import start_new_thread elif sys.platform == "win32": from ctypes import windll - import winreg else: import grp @@ -70,6 +69,13 @@ from DisplayCAL import imfile from DisplayCAL import localization as lang from DisplayCAL import wexpect +from DisplayCAL.argyll import ( + check_argyll_bin, + get_argyll_util, + get_argyll_utilname, + get_argyll_version_string as base_get_argyll_version_string, + parse_argyll_version_string, +) from DisplayCAL.argyll_cgats import ( add_dispcal_options_to_cal, add_options_to_ti3, @@ -112,9 +118,7 @@ get_total_patches, get_verified_path, isapp, - isexe, is_ccxx_testchart, - logdir, profile_ext, pydir, setcfg, @@ -183,7 +187,6 @@ mac_terminal_do_script, mac_terminal_set_colors, osascript, - get_machine_attributes, get_model_id, ) elif sys.platform == "win32": @@ -213,8 +216,6 @@ from DisplayCAL import colord from DisplayCAL.util_os import ( dlopen, - expanduseru, - fname_ext, getenvu, is_superuser, launch_file, @@ -245,11 +246,6 @@ Xicclu, _mp_generate_B2A_clut, _mp_xicclu, - check_argyll_bin, - get_argyll_util, - get_argyll_utilname, - get_argyll_version_string as base_get_argyll_version_string, - parse_argyll_version_string, printcmdline, ) from DisplayCAL.wxaddons import BetterCallLater, BetterWindowDisabler, wx @@ -1097,34 +1093,32 @@ def _applycal_bug_workaround(profile): trc_tag[:] = [interp(i / 255.0) for i in range(256)] -def get_argyll_latest_version(): - """Return the latest ArgyllCMS version from argyllcms.com. +def get_argyll_version(name, silent=False, paths=None): + """Determine version of a certain Argyll utility. + + Args: + name (str): The name of the Argyll utility. + silent (bool): Silently check Argyll version. Default is False. + paths (Union[list, None]): Paths to look for Argyll executables. Returns: - str: The latest version number. Returns + str: The Argyll utility version. """ - argyll_domain = config.defaults.get("argyll.domain", "") - try: - changelog = re.search( - r"(?<=Version ).{5}", - urllib.request.urlopen(f"{argyll_domain}/log.txt") - .read(150) - .decode("utf-8"), - ) - except urllib.error.URLError as e: - # no internet connection - # return the default version - return config.defaults.get("argyll.version") - return changelog.group() - - -def get_argyll_version(name, silent=False, paths=None): - """Determine version of a certain Argyll utility.""" argyll_version_string = get_argyll_version_string(name, silent, paths) return parse_argyll_version_string(argyll_version_string) def get_argyll_version_string(name, silent=False, paths=None): + """Return the version of the requested Argyll utility. + + Args: + name (str): The name of the Argyll utility. + silent (bool): Silently check Argyll version. Default is False. + paths (Union[list, None]): Paths to look for Argyll executables. + + Returns: + str: The Argyll utility version. + """ argyll_version_string = "0.0.0" if (silent and check_argyll_bin(paths)) or ( not silent and check_set_argyll_bin(paths) @@ -1581,7 +1575,7 @@ def make_argyll_compatible_path(path): def set_argyll_bin(parent=None, silent=False, callafter=None, callafter_args=()): - """Set the directory containing the Argyll CMS binary executables""" + """Set the directory containing the Argyll CMS binary executables.""" if parent and not parent.IsShownOnScreen(): parent = None # do not center on parent if not visible # Check if Argyll version on PATH is newer than configured Argyll version @@ -1713,7 +1707,7 @@ def set_argyll_bin(parent=None, silent=False, callafter=None, callafter_args=()) class EvalFalse(object): - """Evaluate to False in boolean comparisons""" + """Evaluate to False in boolean comparisons.""" def __init__(self, wrapped_object): self._wrapped_object = wrapped_object @@ -1893,7 +1887,7 @@ def __call__(self, *args, **kwargs): class StringWithLengthOverride(UserString): - """Allow defined behavior in comparisons and when evaluating length""" + """Allow defined behavior in comparisons and when evaluating length.""" def __init__(self, seq, length=None): UserString.__init__(self, seq) @@ -1966,7 +1960,7 @@ def _expect_timeout(self, patterns, timeout=-1, child_timeout=1): return result def _terminate(self): - """Terminate running sudo subprocess""" + """Terminate running sudo subprocess.""" self.subprocess.sendcontrol("C") self._expect_timeout([wexpect.EOF], 10) if self.subprocess.after is wexpect.TIMEOUT: diff --git a/DisplayCAL/worker_base.py b/DisplayCAL/worker_base.py index b8e8493d..58ded2d2 100644 --- a/DisplayCAL/worker_base.py +++ b/DisplayCAL/worker_base.py @@ -6,7 +6,6 @@ import math import os import shlex -import re import shutil import struct import subprocess as sp @@ -15,40 +14,40 @@ import textwrap import traceback + if sys.platform == "win32": import win32api -from DisplayCAL.argyll_names import ( - names as argyll_names, - altnames as argyll_altnames, - optional as argyll_optional, -) + +from DisplayCAL import CGATS +from DisplayCAL import config +from DisplayCAL import colormath +from DisplayCAL import ICCProfile as ICCP +from DisplayCAL import localization as lang +from DisplayCAL.argyll import get_argyll_util, get_argyll_version from DisplayCAL.colormath import ( VidRGB_to_eeColor, VidRGB_to_cLUT65, - cLUT65_to_VidRGB, eeColor_to_VidRGB, ) -from DisplayCAL.config import exe_ext, fs_enc, get_data_path, getcfg, profile_ext +from DisplayCAL.config import ( + # exe_ext, fs_enc, get_data_path, getcfg, + profile_ext +) from DisplayCAL.debughelpers import ( Error, Info, - UnloggedError, - UnloggedInfo, - UnloggedWarning, - Warn, + # UnloggedError, + # UnloggedInfo, + # UnloggedWarning, + # Warn, ) from DisplayCAL.log import LogFile from DisplayCAL.meta import name as appname from DisplayCAL.multiprocess import mp, pool_slice from DisplayCAL.options import debug, verbose -from DisplayCAL.util_os import getenvu, quote_args, which +from DisplayCAL.util_os import quote_args from DisplayCAL.util_str import make_filename_safe, safe_basestring, safe_str -from DisplayCAL import CGATS -from DisplayCAL import colormath -from DisplayCAL import config -from DisplayCAL import ICCProfile as ICCP -from DisplayCAL import localization as lang def _mp_xicclu( @@ -241,169 +240,6 @@ def _mp_generate_B2A_clut( return idata, data1, data2 -def check_argyll_bin(paths=None): - """Check if the Argyll binaries can be found.""" - prev_dir = None - cur_dir = os.curdir - for name in argyll_names: - exe = get_argyll_util(name, paths) - if not exe: - if name in argyll_optional: - continue - return False - cur_dir = os.path.dirname(exe) - if prev_dir: - if cur_dir != prev_dir: - if name in argyll_optional: - if verbose: - print( - "Warning: Optional Argyll executable %s is not in the same " - "directory as the main executables (%s)." % (exe, prev_dir) - ) - else: - if verbose: - print( - "Error: Main Argyll executable %s is not in the same " - "directory as the other executables (%s)." % (exe, prev_dir) - ) - return False - else: - prev_dir = cur_dir - if verbose >= 3: - print("Argyll binary directory:", cur_dir) - if debug: - print("[D] check_argyll_bin OK") - if debug >= 2: - if not paths: - paths = getenvu("PATH", os.defpath).split(os.pathsep) - argyll_dir = (getcfg("argyll.dir") or "").rstrip(os.path.sep) - if argyll_dir: - if argyll_dir in paths: - paths.remove(argyll_dir) - paths = [argyll_dir] + paths - print("[D] Searchpath:\n ", "\n ".join(paths)) - # Fedora doesn't ship Rec709.icm - config.defaults["3dlut.input.profile"] = ( - get_data_path(os.path.join("ref", "Rec709.icm")) - or get_data_path(os.path.join("ref", "sRGB.icm")) - or "" - ) - config.defaults["testchart.reference"] = ( - get_data_path(os.path.join("ref", "ColorChecker.cie")) or "" - ) - config.defaults["gamap_profile"] = ( - get_data_path(os.path.join("ref", "sRGB.icm")) or "" - ) - return True - - -argyll_utils = {} - - -def get_argyll_util(name, paths=None): - """Find a single Argyll utility. Return the full path.""" - cfg_argyll_dir = getcfg("argyll.dir") - if not paths: - paths = getenvu("PATH", os.defpath).split(os.pathsep) - argyll_dir = (cfg_argyll_dir or "").rstrip(os.path.sep) - if argyll_dir: - if argyll_dir in paths: - paths.remove(argyll_dir) - paths = [argyll_dir] + paths - cache_key = os.pathsep.join(paths) - exe = argyll_utils.get(cache_key, {}).get(name, None) - if exe: - return exe - elif verbose >= 4: - print("Info: Searching for", name, "in", os.pathsep.join(paths)) - for path in paths: - for altname in argyll_altnames.get(name, []): - exe = which(altname + exe_ext, [path]) - if exe: - break - if exe: - break - if verbose >= 4: - if exe: - print("Info:", name, "=", exe) - else: - print( - "Info:", - "|".join(argyll_altnames[name]), - "not found in", - os.pathsep.join(paths), - ) - if exe: - if cache_key not in argyll_utils: - argyll_utils[cache_key] = {} - argyll_utils[cache_key][name] = exe - return exe - - -def get_argyll_utilname(name, paths=None): - """Find a single Argyll utility. Return the basename without extension.""" - exe = get_argyll_util(name, paths) - if exe: - exe = os.path.basename(os.path.splitext(exe)[0]) - return exe - - -def get_argyll_version(name, paths=None): - """Determine version of a certain Argyll utility.""" - argyll_version_string = get_argyll_version_string(name, paths) - return parse_argyll_version_string(argyll_version_string) - - -def get_argyll_version_string(name, paths=None): - """Return the version of the requested Argyll utility. - - Args: - name (str): The name of the Argyll utility. - paths (Union[list, None]): Paths to look for Argyll executables. - - Returns: - str: The Argyll utility version. - """ - argyll_version_string = b"0.0.0" - cmd = get_argyll_util(name, paths) - if sys.platform == "win32": - startupinfo = sp.STARTUPINFO() - startupinfo.dwFlags |= sp.STARTF_USESHOWWINDOW - startupinfo.wShowWindow = sp.SW_HIDE - else: - startupinfo = None - try: - p = sp.Popen( - [cmd.encode(fs_enc), "-?"], - stdin=sp.PIPE, - stdout=sp.PIPE, - stderr=sp.STDOUT, - startupinfo=startupinfo, - ) - except Exception as exception: - print(exception) - return argyll_version_string - for line in (p.communicate(timeout=30)[0] or "").splitlines(): - if isinstance(line, bytes): - line = line.strip() - if b"version" in line.lower(): - argyll_version_string = line[line.lower().find(b"version") + 8 :] - break - return argyll_version_string.decode("utf-8") - - -def parse_argyll_version_string(argyll_version_string): - if isinstance(argyll_version_string, bytes): - argyll_version_string = argyll_version_string.decode() - argyll_version = re.findall(r"(\d+|[^.\d]+)", argyll_version_string) - for i, v in enumerate(argyll_version): - try: - argyll_version[i] = int(v) - except ValueError: - pass - return argyll_version - - def printcmdline(cmd, args=None, fn=None, cwd=None): """Pretty-print a command line.""" if fn is None: diff --git a/tests/conftest.py b/tests/conftest.py index 2c648979..48cbb633 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -11,7 +11,7 @@ import DisplayCAL from DisplayCAL.config import setcfg -from DisplayCAL.worker import get_argyll_latest_version +from DisplayCAL.argyll import get_argyll_latest_version @pytest.fixture(scope="module") diff --git a/tests/test_argyll.py b/tests/test_argyll.py index e69de29b..ef18d10d 100644 --- a/tests/test_argyll.py +++ b/tests/test_argyll.py @@ -0,0 +1,37 @@ +# -*- coding: utf-8 -*- +import os +from subprocess import Popen + +from DisplayCAL.argyll import get_argyll_util, get_argyll_version_string +from DisplayCAL import config +from DisplayCAL.dev.mocks import check_call + +from tests.data.argyll_sp_data import SUBPROCESS_COM + + +# todo: deactivated test temporarily +# def test_xicclu_is_working_properly(data_files): +# """testing if ``DisplayCAL.worker_base.Xicclu`` is working properly""" +# from DisplayCAL import ICCProfile +# from DisplayCAL.worker_base import Xicclu +# +# profile = ICCProfile.ICCProfile(profile=data_files["default.icc"].absolute()) +# xicclu = Xicclu(profile, "r", "a", pcs="X", scale=100) +# assert xicclu() is not None + + +def test_get_argyll_util(argyll): + """Test worker_base.get_argyll_util() function.""" + config.initcfg() + result = get_argyll_util("ccxxmake") + expected_result = os.path.join(config.getcfg("argyll.dir"), "ccxxmake") + assert result == expected_result + + +def test_get_argyll_version_string_1(argyll): + """Test worker_base.get_argyll_version_string() function.""" + config.initcfg() + with check_call(Popen, "communicate", SUBPROCESS_COM): + result = get_argyll_version_string("ccxxmake") + expected_result = "2.3.0" + assert result == expected_result diff --git a/tests/test_worker.py b/tests/test_worker.py index 6e499800..c646f756 100644 --- a/tests/test_worker.py +++ b/tests/test_worker.py @@ -25,10 +25,8 @@ check_profile_isfile, check_file_isfile, check_ti3_criteria1, - get_argyll_latest_version, ) - -from DisplayCAL.worker_base import get_argyll_util +from DisplayCAL.argyll import get_argyll_latest_version, get_argyll_util from tests.data.display_data import DisplayData diff --git a/tests/test_worker_base.py b/tests/test_worker_base.py index be05f651..85362222 100644 --- a/tests/test_worker_base.py +++ b/tests/test_worker_base.py @@ -1,39 +1,6 @@ # -*- coding: utf-8 -*- -import os -from subprocess import Popen from DisplayCAL import worker_base -from DisplayCAL import config -from DisplayCAL.dev.mocks import check_call - -from tests.data.argyll_sp_data import SUBPROCESS_COM - -# todo: deactivated test temporarily -# def test_xicclu_is_working_properly(data_files): -# """testing if ``DisplayCAL.worker_base.Xicclu`` is working properly""" -# from DisplayCAL import ICCProfile -# from DisplayCAL.worker_base import Xicclu -# -# profile = ICCProfile.ICCProfile(profile=data_files["default.icc"].absolute()) -# xicclu = Xicclu(profile, "r", "a", pcs="X", scale=100) -# assert xicclu() is not None - - -def test_get_argyll_util(argyll): - """Test worker_base.get_argyll_util() function.""" - config.initcfg() - result = worker_base.get_argyll_util("ccxxmake") - expected_result = os.path.join(config.getcfg("argyll.dir"), "ccxxmake") - assert result == expected_result - - -def test_get_argyll_version_string_1(argyll): - """Test worker_base.get_argyll_version_string() function.""" - config.initcfg() - with check_call(Popen, "communicate", SUBPROCESS_COM): - result = worker_base.get_argyll_version_string("ccxxmake") - expected_result = "2.3.0" - assert result == expected_result def test_printcmdline_1(): From 8c260de4134fad799c9b6a6175e630ca730c69f9 Mon Sep 17 00:00:00 2001 From: Erkan Ozgur Yilmaz Date: Thu, 17 Oct 2024 14:34:40 +0100 Subject: [PATCH 04/34] * [#16] Initial prototype of C-Extension independent version of `RealDisplaySizeMM` module. This needs a redesign and better implementation. But is a good proof of concept. --- DisplayCAL/RealDisplaySizeMM.py | 186 ++++++++++++++++++++++++----- tests/data/display_data.py | 75 ++++++++++++ tests/test_real_display_size_mm.py | 112 +++++++++++++++++ 3 files changed, 346 insertions(+), 27 deletions(-) diff --git a/DisplayCAL/RealDisplaySizeMM.py b/DisplayCAL/RealDisplaySizeMM.py index 884063fc..24ce0140 100644 --- a/DisplayCAL/RealDisplaySizeMM.py +++ b/DisplayCAL/RealDisplaySizeMM.py @@ -1,36 +1,38 @@ # -*- coding: utf-8 -*- import os -import platform import re +import subprocess import sys +from typing import List +from DisplayCAL import argyll from DisplayCAL.util_dbus import DBusObject, DBusException, BUSTYPE_SESSION from DisplayCAL.util_x import get_display as _get_x_display -if sys.platform == "darwin": - # Mac OS X has universal binaries in three flavors: - # - i386 & PPC - # - i386 & x86_64 - # - i386 & ARM - if platform.architecture()[0].startswith("64"): - # TODO: Intel vs ARM (Apple Silicon) distinction - from DisplayCAL.lib64.RealDisplaySizeMM import * -else: - # elif sys.platform == "win32": - # Windows have separate files - if sys.version_info[:2] == (3, 8): - from DisplayCAL.lib64.python38.RealDisplaySizeMM import * - elif sys.version_info[:2] == (3, 9): - from DisplayCAL.lib64.python39.RealDisplaySizeMM import * - elif sys.version_info[:2] == (3, 10): - from DisplayCAL.lib64.python310.RealDisplaySizeMM import * - elif sys.version_info[:2] == (3, 11): - from DisplayCAL.lib64.python311.RealDisplaySizeMM import * - elif sys.version_info[:2] == (3, 12): - from DisplayCAL.lib64.python312.RealDisplaySizeMM import * - elif sys.version_info[:2] == (3, 13): - from DisplayCAL.lib64.python313.RealDisplaySizeMM import * +# if sys.platform == "darwin": +# # Mac OS X has universal binaries in three flavors: +# # - i386 & PPC +# # - i386 & x86_64 +# # - i386 & ARM +# if platform.architecture()[0].startswith("64"): +# # TODO: Intel vs ARM (Apple Silicon) distinction +# from DisplayCAL.lib64.RealDisplaySizeMM import * +# else: +# # elif sys.platform == "win32": +# # Windows have separate files +# if sys.version_info[:2] == (3, 8): +# from DisplayCAL.lib64.python38.RealDisplaySizeMM import * +# elif sys.version_info[:2] == (3, 9): +# from DisplayCAL.lib64.python39.RealDisplaySizeMM import * +# elif sys.version_info[:2] == (3, 10): +# from DisplayCAL.lib64.python310.RealDisplaySizeMM import * +# elif sys.version_info[:2] == (3, 11): +# from DisplayCAL.lib64.python311.RealDisplaySizeMM import * +# elif sys.version_info[:2] == (3, 12): +# from DisplayCAL.lib64.python312.RealDisplaySizeMM import * +# elif sys.version_info[:2] == (3, 13): +# from DisplayCAL.lib64.python313.RealDisplaySizeMM import * # TODO: For Linux use the ``xrandr`` command output which supplies everything. # @@ -46,9 +48,9 @@ _displays = None -_GetXRandROutputXID = GetXRandROutputXID -_RealDisplaySizeMM = RealDisplaySizeMM -_enumerate_displays = enumerate_displays +# _GetXRandROutputXID = GetXRandROutputXID +# _RealDisplaySizeMM = RealDisplaySizeMM +# _enumerate_displays = enumerate_displays def GetXRandROutputXID(display_no=0): @@ -80,6 +82,136 @@ def RealDisplaySizeMM(display_no=0): return 0, 0 +class Display(object): + """Store information about display.""" + + def __init__(self): + self.name = None + """Display name.""" + self.description = None # USED + """Description of display or URL.""" + + self.xrandr_name = None # Generated from self.description + + self.pos = (0, 0) # USED + """Displays offset in pixel.""" + # self.sx = None + # """Displays offset in pixels (X).""" + # self.sy = None + # """Displays offset in pixels (Y).""" + + self.size = (0, 0) # USED + """Displays width and height in pixels.""" + + # WINDOWS / NT + self.monid = None + """Monitor ID.""" + self.prim = None + """ NZ if primary display monitor.""" + + # APPLE + self.ddid = None + + # UNIX + self.screen = None + """X11 (possibly virtual) Screen.""" + self.uscreen = None + """Underlying Xinerama/XRandr screen.""" + self.rscreen = None + """Underlying RAMDAC screen (user override).""" + self.icc_atom = None + """ICC profile root/output atom for this display.""" + self.edid = None + """128, 256 or 384 bytes of monitor EDID, NULL if none.""" + self.edid_len = None + """128, 256 or 384.""" + + # Xrandr stuff - output is connected 1:1 to a display + self.crtc = None + """Associated crtc.""" + self.output = None + """Associated output.""" + self.icc_out_atom = None + """ICC profile atom for this output.""" + + def from_dispwin_data(self, display_info_line): + """Parse from dispwin display list data. + + Args: + display_info_line (str): The dispwin data line. + """ + display_id, description = list(map(str.strip, display_info_line.split("="))) + description = description[1:-1] + self.monid = int(display_id) + parts = description.split(",") + display_name = parts[0].replace("'", "") + x = int(parts[1].strip().split(" ")[-1]) + y = int(parts[2]) + self.pos = (x, y) + width = int(parts[3].strip()[len("width "):]) + height = int(parts[4].strip().replace("'", "")[len("height "):].split(" ")[0]) + self.name = display_name + self.description = description + self.size = (width, height) + + def to_dict(self): + """Return a dictionary. + + Returns: + dict: The display data as dictionary, matching the previous implementation. + """ + display_dict = {} + if self.monid is not None: + display_dict["monid"] = self.monid + if self.description is not None: + display_dict["description"] = self.description + if self.name is not None: + display_dict["name"] = self.name + if self.pos is not None: + display_dict["pos"] = self.pos + if self.size is not None: + display_dict["size"] = self.size + + return display_dict + + +def _enumerate_displays() -> List[dict]: + """Generate display information data from ArgyllCMS's dispwin. + + Returns: + List[dict]: A list of dictionary containing display data. + """ + displays = [] + dispwin_path = argyll.get_argyll_util("dispwin") + if dispwin_path is None: + return [] + p = subprocess.Popen( + [dispwin_path, "-v", "-d0"], + stdout=subprocess.PIPE + ) + output, error = p.communicate() + + # now parse output + output = output.decode("utf-8") + # find the display list section + display_list_start = -1 + display_list_end = -1 + lines = output.split("\n") + for i, line in enumerate(lines): + if "-d n" in line: + display_list_start = i + 1 + if "-dweb[:port]" in line: + display_list_end = i + + for i in range(display_list_start, display_list_end): + display = Display() + display_info_line = lines[i].strip() + display.from_dispwin_data(display_info_line) + displays.append(display.to_dict()) + + return displays + + def enumerate_displays(): """Enumerate and return a list of displays.""" global _displays diff --git a/tests/data/display_data.py b/tests/data/display_data.py index addb35fe..0b4b1996 100644 --- a/tests/data/display_data.py +++ b/tests/data/display_data.py @@ -74,6 +74,71 @@ class DisplayData: "year_of_manufacture": 2017, } + DISPWIN_OUTPUT_1 = b"""Test display patch window, Set Video LUTs, Install profiles, Version 3.3.0 +Author: Graeme W. Gill, licensed under the AGPL Version 3 +Diagnostic: -d parameter '0' is out of range +usage: dispwin [options] [calfile] + -v Verbose mode + -d n Choose the display from the following list (default 1) + 1 = 'Built-in Retina Display, at 0, 0, width 1728, height 1117 (Primary Display)' + -dweb[:port] Display via web server at port (default 8080) + -dcc[:n] Display via n'th ChromeCast (default 1, ? for list) + -d dummy Display via dummy (non-existant, invisible) display + -P ho,vo,ss[,vs] Position test window and scale it + -F Fill whole screen with black background + -E Video encode output as (16-235)/255 "TV" levels + -i Run forever with random values + -G filename Display RGB colors from CGATS (ie .ti1) file + -C r.rr,g.gg,b.bb Add this RGB color to list to be displayed + -m Manually cycle through values + -Y msec patch delay in msec (default 2000) + -f Test grey ramp fade + -r Test just Video LUT loading & Beeps + -n Test native output (rather than through Video LUT and C.M.) + -s filename Save the currently loaded Video LUT to 'filename' + -c Load a linear display calibration + -V Verify that calfile/profile cal. is currently loaded in LUT + -I Install profile for display and use its calibration + -U Un-install profile for display + -S d Specify the install/uninstall scope for OS X [nlu] or X11/Vista [lu] + d is one of: n = network, l = local system, u = user (default) + -L Load installed profile & calibration + -D [level] Print debug diagnostics to stderr + calfile Load calibration (.cal or .icc) into Video LUT""" + + DISPWIN_OUTPUT_2 = b"""Test display patch window, Set Video LUTs, Install profiles, Version 3.3.0 +Author: Graeme W. Gill, licensed under the AGPL Version 3 +Diagnostic: -d parameter '0' is out of range +usage: dispwin [options] [calfile] + -v Verbose mode + -d n Choose the display from the following list (default 1) + 1 = 'Built-in Retina Display, at 0, 0, width 1728, height 1117 (Primary Display)' + 2 = 'DELL U2720Q, at 1728, -575, width 3008, height 1692' + -dweb[:port] Display via web server at port (default 8080) + -dcc[:n] Display via n'th ChromeCast (default 1, ? for list) + -d dummy Display via dummy (non-existant, invisible) display + -P ho,vo,ss[,vs] Position test window and scale it + -F Fill whole screen with black background + -E Video encode output as (16-235)/255 "TV" levels + -i Run forever with random values + -G filename Display RGB colors from CGATS (ie .ti1) file + -C r.rr,g.gg,b.bb Add this RGB color to list to be displayed + -m Manually cycle through values + -Y msec patch delay in msec (default 2000) + -f Test grey ramp fade + -r Test just Video LUT loading & Beeps + -n Test native output (rather than through Video LUT and C.M.) + -s filename Save the currently loaded Video LUT to 'filename' + -c Load a linear display calibration + -V Verify that calfile/profile cal. is currently loaded in LUT + -I Install profile for display and use its calibration + -U Un-install profile for display + -S d Specify the install/uninstall scope for OS X [nlu] or X11/Vista [lu] + d is one of: n = network, l = local system, u = user (default) + -L Load installed profile & calibration + -D [level] Print debug diagnostics to stderr + calfile Load calibration (.cal or .icc) into Video LUT""" + @classmethod def CFG_DATA(self, name, fallback=True, raw=False, cfg=None): return { @@ -313,3 +378,13 @@ def Geometry(self): def enumerate_displays() -> List[Dict]: """Return the display data itself.""" return [DisplayData.DISPLAY_DATA_1] + + @staticmethod + def dispwin_output_1(*args, **kwargs) -> bytes: + """Return dispwin output.""" + return DisplayData.DISPWIN_OUTPUT_1 + + @staticmethod + def dispwin_output_2(*args, **kwargs) -> bytes: + """Return dispwin output.""" + return DisplayData.DISPWIN_OUTPUT_2 diff --git a/tests/test_real_display_size_mm.py b/tests/test_real_display_size_mm.py index 0bce0277..4a778318 100644 --- a/tests/test_real_display_size_mm.py +++ b/tests/test_real_display_size_mm.py @@ -17,6 +17,62 @@ pass +@pytest.fixture(scope="function") +def patch_subprocess(monkeypatch): + """Patch subprocess. + + Yields: + Any: The patched subprocess class. + """ + class Process: + def __init__(self, output=None): + self.output = output + + def communicate(self): + return self.output, None + + class PatchedSubprocess: + passed_args = [] + passed_kwargs = {} + PIPE = None + output = None + + @classmethod + def Popen(cls, *args, **kwargs): + cls.passed_args += args + cls.passed_kwargs.update(kwargs) + process = Process(output=cls.output) + return process + + monkeypatch.setattr( + "DisplayCAL.RealDisplaySizeMM.subprocess", PatchedSubprocess + ) + yield PatchedSubprocess + + +@pytest.fixture(scope="function") +def patch_argyll_util(monkeypatch): + """Patch argyll. + + Yields: + Any: The patched argyll class. + """ + + class PatchedArgyll: + passed_util_name = [] + + @classmethod + def get_argyll_util(cls, util_name): + cls.passed_util_name.append(util_name) + return "/some/path/to/argyll_v3.3.0/bin/dispwin" + + monkeypatch.setattr( + "DisplayCAL.RealDisplaySizeMM.argyll", PatchedArgyll + ) + + yield PatchedArgyll + + def test_real_display_size_mm(): """Test DisplayCAL.RealDisplaySizeMM.RealDisplaySizeMM() function.""" RealDisplaySizeMM._displays = None @@ -71,6 +127,62 @@ def test_enumerate_displays(): assert RealDisplaySizeMM._displays is not None +def test__enumerate_displays_dispwin_path_is_none(monkeypatch): + """_enumerate_displays() dispwin path is None returns empty list.""" + monkeypatch.setattr( + "DisplayCAL.RealDisplaySizeMM.argyll.get_argyll_util", lambda x: None + ) + result = RealDisplaySizeMM._enumerate_displays() + assert result == [] + + +def test__enumerate_displays_uses_argyll_dispwin(patch_subprocess, patch_argyll_util): + """DisplayCAL.RealDisplaySizeMM._enumerate_displays() uses dispwin.""" + PatchedSubprocess = patch_subprocess + PatchedSubprocess.output = DisplayData.DISPWIN_OUTPUT_1 + PatchedArgyll = patch_argyll_util + assert PatchedSubprocess.passed_args == [] + assert PatchedSubprocess.passed_kwargs == {} + result = RealDisplaySizeMM._enumerate_displays() + # assert result == DisplayData.DISPWIN_OUTPUT_1 + assert PatchedSubprocess.passed_args != [] + assert "dispwin" in PatchedSubprocess.passed_args[0][0] + assert PatchedSubprocess.passed_args[0][1] == "-v" + assert PatchedSubprocess.passed_args[0][2] == "-d0" + + +def test__enumerate_displays_uses_argyll_dispwin_output_1(patch_subprocess, patch_argyll_util): + """DisplayCAL.RealDisplaySizeMM._enumerate_displays() uses dispwin.""" + PatchedSubprocess = patch_subprocess + PatchedSubprocess.output = DisplayData.DISPWIN_OUTPUT_1 + PatchedArgyll = patch_argyll_util + result = RealDisplaySizeMM._enumerate_displays() + assert isinstance(result, list) + assert len(result) == 1 + assert result[0]["description"] == "Built-in Retina Display, at 0, 0, width 1728, height 1117 (Primary Display)" + assert result[0]["name"] == "Built-in Retina Display" + assert result[0]["size"] == (1728, 1117) + assert result[0]["pos"] == (0, 0) + + +def test__enumerate_displays_uses_argyll_dispwin_output_2(patch_subprocess, patch_argyll_util): + """DisplayCAL.RealDisplaySizeMM._enumerate_displays() uses dispwin.""" + PatchedSubprocess = patch_subprocess + PatchedSubprocess.output = DisplayData.DISPWIN_OUTPUT_2 + PatchedArgyll = patch_argyll_util + result = RealDisplaySizeMM._enumerate_displays() + assert isinstance(result, list) + assert len(result) == 2 + assert result[0]["description"] == "Built-in Retina Display, at 0, 0, width 1728, height 1117 (Primary Display)" + assert result[0]["name"] == "Built-in Retina Display" + assert result[0]["size"] == (1728, 1117) + assert result[0]["pos"] == (0, 0) + assert result[1]["description"] == "DELL U2720Q, at 1728, -575, width 3008, height 1692" + assert result[1]["name"] == "DELL U2720Q" + assert result[1]["size"] == (3008, 1692) + assert result[1]["pos"] == (1728, -575) + + def test_get_display(): """Test DisplayCAL.RealDisplaySizeMM.get_display() function.""" RealDisplaySizeMM._displays = None From 17c5ad529e5e6fd9ac14332531bdbf8c9eeb3e8f Mon Sep 17 00:00:00 2001 From: Erkan Ozgur Yilmaz Date: Fri, 18 Oct 2024 09:37:48 +0100 Subject: [PATCH 05/34] * [#16] `DisplayCAL.RealDisplaySizeMM.Display.from_dispwin_data()` now uses regular expressions to extract the data. --- DisplayCAL/RealDisplaySizeMM.py | 124 ++++++++++++----------------- DisplayCAL/edid.py | 11 ++- DisplayCAL/lang/en.yaml | 82 +++++++++---------- tests/data/display_data.py | 96 ++++++++++++++++++++++ tests/test_real_display_size_mm.py | 57 ++++++++++--- 5 files changed, 242 insertions(+), 128 deletions(-) diff --git a/DisplayCAL/RealDisplaySizeMM.py b/DisplayCAL/RealDisplaySizeMM.py index 24ce0140..8ea118c9 100644 --- a/DisplayCAL/RealDisplaySizeMM.py +++ b/DisplayCAL/RealDisplaySizeMM.py @@ -7,51 +7,13 @@ from typing import List from DisplayCAL import argyll +from DisplayCAL import localization as lang from DisplayCAL.util_dbus import DBusObject, DBusException, BUSTYPE_SESSION from DisplayCAL.util_x import get_display as _get_x_display -# if sys.platform == "darwin": -# # Mac OS X has universal binaries in three flavors: -# # - i386 & PPC -# # - i386 & x86_64 -# # - i386 & ARM -# if platform.architecture()[0].startswith("64"): -# # TODO: Intel vs ARM (Apple Silicon) distinction -# from DisplayCAL.lib64.RealDisplaySizeMM import * -# else: -# # elif sys.platform == "win32": -# # Windows have separate files -# if sys.version_info[:2] == (3, 8): -# from DisplayCAL.lib64.python38.RealDisplaySizeMM import * -# elif sys.version_info[:2] == (3, 9): -# from DisplayCAL.lib64.python39.RealDisplaySizeMM import * -# elif sys.version_info[:2] == (3, 10): -# from DisplayCAL.lib64.python310.RealDisplaySizeMM import * -# elif sys.version_info[:2] == (3, 11): -# from DisplayCAL.lib64.python311.RealDisplaySizeMM import * -# elif sys.version_info[:2] == (3, 12): -# from DisplayCAL.lib64.python312.RealDisplaySizeMM import * -# elif sys.version_info[:2] == (3, 13): -# from DisplayCAL.lib64.python313.RealDisplaySizeMM import * - -# TODO: For Linux use the ``xrandr`` command output which supplies everything. -# -# ``xrandr --verbose`` gives all the info we need, including EDID which needs to -# be decoded: -# -# ```python -# import codecs -# edid = codecs.decode(xrandr_edid_data, "hex") -# ``` -# - _displays = None -# _GetXRandROutputXID = GetXRandROutputXID -# _RealDisplaySizeMM = RealDisplaySizeMM -# _enumerate_displays = enumerate_displays - def GetXRandROutputXID(display_no=0): """Return the XRandR output X11 ID of a given display. @@ -140,18 +102,33 @@ def from_dispwin_data(self, display_info_line): Args: display_info_line (str): The dispwin data line. """ - display_id, description = list(map(str.strip, display_info_line.split("="))) - description = description[1:-1] - self.monid = int(display_id) - parts = description.split(",") - display_name = parts[0].replace("'", "") - x = int(parts[1].strip().split(" ")[-1]) - y = int(parts[2]) + display_info_line = display_info_line.strip() + description_data = re.findall( + r"[\s\d]+= '(?P.*)'", + display_info_line + ) + dispwin_error_message = lang.getstr( + "error.generic", ( + -1, "dispwin returns no usable data while enumerating displays.") + ) + if not description_data: + raise ValueError(dispwin_error_message) + self.description = description_data[0] + match = re.match( + r"[\s]*(?P\d) = '(?P.*), at (?P\d+), (?P[-\d]+), " + r"width (?P\d+), height (?P\d+).*'", + display_info_line + ) + if not match: + raise ValueError(dispwin_error_message) + groups_dict = match.groupdict() + self.monid = int(groups_dict["id"]) + self.name = groups_dict["name"] + x = int(groups_dict["x"]) + y = int(groups_dict["y"]) self.pos = (x, y) - width = int(parts[3].strip()[len("width "):]) - height = int(parts[4].strip().replace("'", "")[len("height "):].split(" ")[0]) - self.name = display_name - self.description = description + width = int(groups_dict["width"]) + height = int(groups_dict["height"]) self.size = (width, height) def to_dict(self): @@ -175,39 +152,40 @@ def to_dict(self): return display_dict -def _enumerate_displays() -> List[dict]: - """Generate display information data from ArgyllCMS's dispwin. +def get_dispwin_output(): + """Return Argyll dispwin output. Returns: - List[dict]: A list of dictionary containing display data. + str: The dispwin output. """ - displays = [] dispwin_path = argyll.get_argyll_util("dispwin") if dispwin_path is None: - return [] + return "" p = subprocess.Popen( [dispwin_path, "-v", "-d0"], stdout=subprocess.PIPE ) - output, error = p.communicate() - - # now parse output - output = output.decode("utf-8") - # find the display list section - display_list_start = -1 - display_list_end = -1 - lines = output.split("\n") - for i, line in enumerate(lines): - if "-d n" in line: - display_list_start = i + 1 - if "-dweb[:port]" in line: - display_list_end = i + output, _ = p.communicate() + return output.decode("utf-8") - for i in range(display_list_start, display_list_end): - display = Display() - display_info_line = lines[i].strip() - display.from_dispwin_data(display_info_line) - displays.append(display.to_dict()) + +def _enumerate_displays() -> List[dict]: + """Generate display information data from ArgyllCMS's dispwin. + + Returns: + List[dict]: A list of dictionary containing display data. + """ + displays = [] + has_display = False + for line in get_dispwin_output().split("\n"): + if "-dweb[:port]" in line: + break + if has_display: + display = Display() + display.from_dispwin_data(line) + displays.append(display.to_dict()) + if "-d n" in line: + has_display = True return displays diff --git a/DisplayCAL/edid.py b/DisplayCAL/edid.py index a656bdc2..cb33c58b 100644 --- a/DisplayCAL/edid.py +++ b/DisplayCAL/edid.py @@ -32,8 +32,6 @@ import subprocess as sp from DisplayCAL import config -from DisplayCAL.config import enc -from DisplayCAL.log import log from DisplayCAL.util_str import make_ascii_printable, safe_str, strtr if sys.platform == "win32": @@ -216,6 +214,15 @@ def get_edid(display_no=0, display_name=None, device=None): return parsed_edid return {} elif RDSMM: + # TODO: For Linux use the ``xrandr`` command output which supplies everything. + # + # ``xrandr --verbose`` gives all the info we need, including EDID which needs to + # be decoded: + # + # ```python + # import codecs + # edid = codecs.decode(xrandr_edid_data, "hex") + # ``` display = RDSMM.get_display(display_no) if display: edid = display.get("edid") diff --git a/DisplayCAL/lang/en.yaml b/DisplayCAL/lang/en.yaml index ea9b76bc..d0d909b0 100644 --- a/DisplayCAL/lang/en.yaml +++ b/DisplayCAL/lang/en.yaml @@ -243,7 +243,7 @@ Install ArgyllCMS instrument drivers... "argyll.instrument.drivers.install.confirm": |- You only need to install the ArgyllCMS specific drivers if you have a measurement instrument that is not a ColorMunki Display, i1 Display Pro, Huey, ColorHug, specbos, spectraval or K-10. - + Do you want to proceed? "argyll.instrument.drivers.install.failure": |- Installation of ArgyllCMS instrument drivers failed. @@ -253,7 +253,7 @@ Uninstall ArgyllCMS instrument drivers... "argyll.instrument.drivers.uninstall.confirm": |- Note: Uninstallation doesn't affect instrument drivers that are currently in use, but just removes the driver from Windows' Driver Store. If you want to switch back to an installed vendor-supplied driver for your instrument, you'll have to use Windows' Device Manager to manually switch to the vendor driver. To do that, right click on the instrument and select “Update Driver Software...”, then choose “Browse my computer for driver software”, “Let me pick from a list of device drivers on my computer” and finally select the vendor driver for your instrument from the list. - + Do you want to proceed? "argyll.instrument.drivers.uninstall.failure": |- Uninstallation of ArgyllCMS instrument drivers failed. @@ -664,7 +664,7 @@ Not all colorimeter corrections could be imported from %s. %i of %i entries had to be skipped. "colorimeter_correction.import.success": |- Colorimeter corrections and/or measurement modes have been successfully imported from the following softwares: - + %s "colorimeter_correction.info": |- Colorimeter correction information @@ -888,9 +888,9 @@ Connection "display.levels_issue_detected": |- The measured luminance difference between RGB level 0 and 16 is below 0.02 cd/m², indicating clipping. Likely either the display device's input- and/or black level setting is incorrect, and/or the output levels configuration of the graphics driver is wrong. - + If your display device has a setting for input levels, please set it to full range RGB (0-255). If your display device doesn't have such a setting, set your graphics driver to output limited range (16-235) instead. Select “Retry” after making either change. - + If neither your display nor graphics driver has a range setting, you can also fix the output levels using the video card gamma table by clicking “Continue”, but note that this is the least preferable solution. "display.manufacturer": |- Display device manufacturer @@ -1007,7 +1007,7 @@ "donation_message": |- If you would like to support the development of, technical assistance with, and continued availability of DisplayCAL and ArgyllCMS, please consider a financial contribution. As DisplayCAL wouldn't be useful without ArgyllCMS, all contributions received for DisplayCAL will be split between both projects. For light personal non-commercial use, a one-time contribution may be appropriate. If you're using DisplayCAL professionally, an annual or monthly contribution would make a great deal of difference in ensuring that both projects continue to be available. - + Thanks to all contributors! "download": |- Download @@ -1102,8 +1102,8 @@ "error.file_type_unsupported": |- Unsupported filetype. "error.generic": |- - An internal error occured. - + An internal error occurred. + Error code: %s Error message: %s "error.luminance.invalid": |- @@ -1174,7 +1174,7 @@ The integrity of %s cannot be verified because it doesn't have an entry in the checksum file. "file.hash.verification.fail": |- Checksum verification failed for %s: - + Expected %s Actual %s "file.invalid": |- @@ -1404,49 +1404,49 @@ In "info.3dlut_settings": |- A 3D LUT (LUT = Look Up Table) or ICC device link profile can be used by 3D LUT or ICC device link capable applications for full display color correction. - + Creating several (additional) 3D LUTs from an existing profile With the desired profile selected under “Settings”, uncheck the “Create 3D LUT after profiling” checkbox. - + Choosing the right source colorspace and tone response curve For 3D LUTs and ICC device link profiles, the source colorspace and tone response curve need to be set in advance and become a fixed part of the overall color transformation (unlike ICC device profiles which are linked dynamically on-the-fly). As an example, HD video material is usually mastered according to the Rec. 709 standard with either a pure power gamma of around 2.2-2.4 (relative with black output offset of 100%) or Rec. 1886 tone response curve (absolute with black output offset 0%). A split between Rec. 1886-like and pure power is also possible by setting black output offset to a value between 0% and 100%. - + “Absolute” vs. “relative” gamma To accomodate a non-zero black level of a real display, the tone response curve needs to be offset and scaled accordingly. “Absolute” gamma results in an actual output at 50% input which doesn't match that of an idealized power curve (unless the black level is zero). “Relative” gamma results in an actual output at 50% input which matches that of an idealized power curve. - + Rendering intent If you have calibrated to a different whitepoint than the one mandated by the source colorspace, and want to use that for the 3D LUT instead, select “Relative colorimetric”. Otherwise, select “Absolute colorimetric with white point scaling” (white point scaling will prevent clipping in absolute colorimetric mode that could happen if the source colorspace whitepoint is outside of the display gamut). You also have the option to use a non-colorimetric rendering intent that may compress or expand the source colorspace to fit within the display gamut (not recommended when colorimetric accuracy is required). "info.calibration_settings": |- Calibration is done by interactively adjusting the display to meet the chosen whitepoint and/or luminance as well as optionally creating 1D LUT calibration curves (LUT = Look Up Table) to meet the chosen tone response curve and ensure gray balance. Note that if during calibration you only want to adjust the display and skip the generation of 1D LUT curves, you need to set the tone response curve to “As measured”. 1D LUT calibration can not be used for full display color correction, you need to create a ICC device profile or 3D LUT and use them with applications that support them for that purpose. 1D LUT calibration complements profiling and 3D LUT calibration though. "info.display_instrument": |- Disable any and all dynamic picture settings of your display if applicable. This can include functions such as dynamic contrast, dimming, automatic brightness and similar features. - + Make sure light does not shine directly onto the screen of your display. - + If your display is an OLED or Plasma TV, or other type with variable light output depending on picture content, enable white level drift compensation. - + If your instrument is a spectrometer and you use it in contact mode on a display with stable black level, you may want to enable instrument black level drift compensation. - + If your instrument is a colorimeter, you should use a measurement mode or correction suitable for your display or display technology type. Note that some instruments (e.g. K-10, Spyder4/5/X) may offer a selection of measurement modes already tuned for specific display types. "info.display_instrument.warmup": |- You should let the display warm up for at least 30 minutes before commencing measurements. If you use your instrument in contact mode, it is a good idea to leave it on the display during that time as well. "info.display_tech": |- LCD White LED (WLED with normal color gamut and sRGB coverage up to 100%) is the most common LCD backlight technology. Displays in this category include most laptops, notebooks, allround/office and non-HDR gaming monitors from 2010 and newer as well as Apple iMac/MacBook from 2009 to mid 2015. - + LCD PFS Phosphor WLED has a wide color gamut in two common variants with P3 or Adobe RGB coverage around 90-99% (according to manufacturer data). Common displays in the P3 gamut category include Apple iMac 4K/5K Retina (late 2015 and newer), MacBook Pro Retina (late 2016 and newer) as well as some HDR-capable desktop monitors. The Adobe RGB gamut category includes professional displays like EIZO CG2730, HP DreamColor Z24x G2 or NEC PA271Q. - + LCD Quantum Dot LED has a wide color gamut with near-full P3 coverage, commonly used in Samsung QLED TVs. - + WOLED has a wide color gamut with high P3 coverage, commonly used in LG OLED TVs. - + RGB OLED/AMOLED has a wide color gamut with near-full Adobe RGB and/or P3 coverage, used in some professional monitors and laptops as well as high quality smartphones. - + LCD GB-r-LED (also known as GB-LED) has a wide color gamut with Adobe RGB coverage around 99% (according to manufacturer data). Common displays in this category include Dell U2413, EIZO CG247/CG277 and NEC PA242W/PA272W. - + LCD RGB LED has a wide color gamut with near-full Adobe RGB coverage. Common displays in this category include Samsung XL20/XL24/XL30 and HP DreamColor LP2480zx. - + LCD CCFL and LCD CCFL Wide Gamut are older backlight technologies commonly used in displays from 2009 and earlier. "info.display_tech.linklabel.displayspecifications.com": |- Find your monitor/TV and corresponding technology at displayspecifications.com @@ -1456,16 +1456,16 @@ Show information about common display technologies "info.mr_settings": |- You can verify the accuracy of an ICC profile or 3D LUT via a measurement report that contains detailed statistics about the color errors of the measured patches. - + When verifying a 3D LUT, make sure to use the same settings as the 3D LUT was created with. - + Tip: To create a self check report instead of a measurement report, hold the ALT key on your keyboard. "info.profile_settings": |- Profiling is the process of characterizing the display and recording its response in a ICC device profile. The ICC device profile can be used by applications that support ICC color management for full display color correction, and/or it can be used to create a 3D LUT (LUT = Look Up Table) which serves the same purpose for applications that support 3D LUTs. - + The amount of patches measured influences the attainable accuracy of the resulting profile. For a reasonable quality 3x3 matrix and curves based profile, a few dozen patches can be enough if the display has good additive color mixing properties and linearity. Some professional displays fall into that category. If the highest possible accuracy is desired, a LUT-based profile from around several hundred up to several thousand patches is recommended. The “Auto-optimized” testchart setting automatically takes into account the non-linearities and actual response of the display, and should be used for best quality results. With this, you can adjust a slider to choose a compromise between resulting profile accuracy and measurement as well as computation time. "infoframe.default_text": |- - + "infoframe.title": |- Log "infoframe.toggle": |- @@ -1553,12 +1553,12 @@ "macos.bugs.cal.warning": |- Applications using the Apple CMM (color management module), like macOS Preview and QuickLook, do not support calibration black point hue correction. Applications that use their own color management are not affected (like Adobe Creative Suite using Adobe ACE, or open source graphics applications using littleCMS like GIMP or Krita, or applications employing 3D LUTs for color management). - + Do you want to disable calibration black point hue correction? "macos.bugs.profile.warning": |- Applications using the Apple CMM (color management module), like macOS Preview and QuickLook, do not support display profile types other than single curve + matrix with black point compensation. Applications that use their own color management are not affected (like Adobe Creative Suite using Adobe ACE, or open source graphics applications using littleCMS like GIMP or Krita, or applications employing 3D LUTs for color management). - + Do you want to set the profile type to single curve + matrix and enable black point compensation? "madhcnet.outdated": |- The installed version of %s found at %s is outdated. Please update to the latest madVR version (at least %i.%i.%i.%i). @@ -1771,7 +1771,7 @@ Shaw & Fairchild 1997 2° "oem.import.auto": |- If your colorimeter came with a software CD for Windows, please insert it now (abort any possibly automatic software installation). - + If you don't have a CD and the required files cannot be found otherwise, they will be downloaded from the Web. "oem.import.auto.download_selection": |- A preselection was made based on the detected instrument(s). Please select the file(s) to download. @@ -1919,7 +1919,7 @@ Profile name "profile.name.placeholders": |- You may use the following placeholders in the profile name: - + %a Abbreviated weekday name %A Full weekday name %b Abbreviated month name @@ -1973,7 +1973,7 @@ The resolution of the profile's PCS to device tables is too low. "profile.share.enter_info": |- You may share your profile with other users via the OpenSUSE “ICC Profile Taxi” service. - + Please provide a meaningful description and enter display device properties below. Read display device settings from your display device's OSD and only enter settings applicable to your display device which have changed from defaults. Example: For most display devices, these are the name of the chosen preset, brightness, contrast, color temperature and/or whitepoint RGB gains, as well as gamma. On laptops and notebooks, usually only the brightness. If you have changed additional settings while calibrating your display device, enter them too if possible. "profile.share.meta_missing": |- @@ -2050,9 +2050,9 @@ Automatically fix profile associations "profile_loader.fix_profile_associations_warning": |- As long as the profile loader is running, it can take care of profile associations when you change the display configuration. - + Before you proceed, please make sure the profile associations shown above are correct. If they aren't, follow these steps: - + 1. Open Windows display settings. 2. Activate all connected displays (extended desktop). 3. Open profile associations. @@ -2564,22 +2564,22 @@ Warning: Values below the display profile blackpoint will be clipped! "warning.suspicious_delta_e": |- Warning: Suspicious measurements have been found. Please note this check assumes an sRGB-like display device. If your display device differs from this assumption (e.g. if it has a very wide gamut or a tone response too far from sRGB), then errors found are probably not meaningful. - + One or all of the following criteria matched: - + • Consecutive patches with different RGB numbers, but suspiciously low ΔE*00 of the measurements against each other. • RGB gray with increasing RGB numbers towards the previous patch, but declining lightness (L*). • RGB gray with decreasing RGB numbers towards the previous patch, but rising lightness (L*). • Unusually high ΔE*00 and ΔL*00 or ΔH*00 or ΔC*00 of measurements to the sRGB equivalent of the RGB numbers. - + This could hint at measurement errors. Values that are considered problematic (which does not have to be true!) are highlighted in red. Check the measurements carefully and mark the ones you want to accept. You can also edit the RGB and XYZ values. "warning.suspicious_delta_e.info": |- Explanation of the Δ columns: - + ΔE*00 XYZ A/B: ΔE*00 of measured values towards measured values of the previous patch. If this value is missing, it wasn't a factor while checking. If it is present, then it is assumed to be too low. - + 0.5 ΔE*00 RGB A/B: ΔE*00 of the sRGB equivalent of the RGB numbers towards the sRGB equivalent of the RGB numbers of the previous patch (with a factor of 0.5). If this value is present, it represents the comparison (minimal) value during checking for assumed too low ΔE*00 XYZ A/B. It serves as a very rough orientation. - + ΔE*00 RGB-XYZ: ΔE*00 of measured values towards the sRGB equivalent of the RGB numbers. ΔL*00 RGB-XYZ: ΔL*00 of measured values towards the sRGB equivalent of the RGB numbers. ΔC*00 RGB-XYZ: ΔC*00 of measured values towards the sRGB equivalent of the RGB numbers. diff --git a/tests/data/display_data.py b/tests/data/display_data.py index 0b4b1996..1b2d2378 100644 --- a/tests/data/display_data.py +++ b/tests/data/display_data.py @@ -139,6 +139,102 @@ class DisplayData: -D [level] Print debug diagnostics to stderr calfile Load calibration (.cal or .icc) into Video LUT""" + DISPWIN_OUTPUT_3 = b"""Test display patch window, Set Video LUTs, Install profiles, Version 3.3.0 + Author: Graeme W. Gill, licensed under the AGPL Version 3 + Diagnostic: -d parameter '0' is out of range + usage: dispwin [options] [calfile] + -v Verbose mode + -d n Choose the display from the following list (default 1) + -dweb[:port] Display via web server at port (default 8080) + -dcc[:n] Display via n'th ChromeCast (default 1, ? for list) + -d dummy Display via dummy (non-existant, invisible) display + -P ho,vo,ss[,vs] Position test window and scale it + -F Fill whole screen with black background + -E Video encode output as (16-235)/255 "TV" levels + -i Run forever with random values + -G filename Display RGB colors from CGATS (ie .ti1) file + -C r.rr,g.gg,b.bb Add this RGB color to list to be displayed + -m Manually cycle through values + -Y msec patch delay in msec (default 2000) + -f Test grey ramp fade + -r Test just Video LUT loading & Beeps + -n Test native output (rather than through Video LUT and C.M.) + -s filename Save the currently loaded Video LUT to 'filename' + -c Load a linear display calibration + -V Verify that calfile/profile cal. is currently loaded in LUT + -I Install profile for display and use its calibration + -U Un-install profile for display + -S d Specify the install/uninstall scope for OS X [nlu] or X11/Vista [lu] + d is one of: n = network, l = local system, u = user (default) + -L Load installed profile & calibration + -D [level] Print debug diagnostics to stderr + calfile Load calibration (.cal or .icc) into Video LUT""" + + DISPWIN_OUTPUT_4 = b"""Test display patch window, Set Video LUTs, Install profiles, Version 3.3.0 + Author: Graeme W. Gill, licensed under the AGPL Version 3 + Diagnostic: -d parameter '0' is out of range + usage: dispwin [options] [calfile] + -v Verbose mode + -d n Choose the display from the following list (default 1) + let's say for some reason these lines + don't match the requested format + -dweb[:port] Display via web server at port (default 8080) + -dcc[:n] Display via n'th ChromeCast (default 1, ? for list) + -d dummy Display via dummy (non-existant, invisible) display + -P ho,vo,ss[,vs] Position test window and scale it + -F Fill whole screen with black background + -E Video encode output as (16-235)/255 "TV" levels + -i Run forever with random values + -G filename Display RGB colors from CGATS (ie .ti1) file + -C r.rr,g.gg,b.bb Add this RGB color to list to be displayed + -m Manually cycle through values + -Y msec patch delay in msec (default 2000) + -f Test grey ramp fade + -r Test just Video LUT loading & Beeps + -n Test native output (rather than through Video LUT and C.M.) + -s filename Save the currently loaded Video LUT to 'filename' + -c Load a linear display calibration + -V Verify that calfile/profile cal. is currently loaded in LUT + -I Install profile for display and use its calibration + -U Un-install profile for display + -S d Specify the install/uninstall scope for OS X [nlu] or X11/Vista [lu] + d is one of: n = network, l = local system, u = user (default) + -L Load installed profile & calibration + -D [level] Print debug diagnostics to stderr + calfile Load calibration (.cal or .icc) into Video LUT""" + + DISPWIN_OUTPUT_5 = b"""Test display patch window, Set Video LUTs, Install profiles, Version 3.3.0 + Author: Graeme W. Gill, licensed under the AGPL Version 3 + Diagnostic: -d parameter '0' is out of range + usage: dispwin [options] [calfile] + -v Verbose mode + -d n Choose the display from the following list (default 1) + 1 = 'Built-in Retina Display, at 0, 0, width 1728, so we have a partial match' + -dweb[:port] Display via web server at port (default 8080) + -dcc[:n] Display via n'th ChromeCast (default 1, ? for list) + -d dummy Display via dummy (non-existant, invisible) display + -P ho,vo,ss[,vs] Position test window and scale it + -F Fill whole screen with black background + -E Video encode output as (16-235)/255 "TV" levels + -i Run forever with random values + -G filename Display RGB colors from CGATS (ie .ti1) file + -C r.rr,g.gg,b.bb Add this RGB color to list to be displayed + -m Manually cycle through values + -Y msec patch delay in msec (default 2000) + -f Test grey ramp fade + -r Test just Video LUT loading & Beeps + -n Test native output (rather than through Video LUT and C.M.) + -s filename Save the currently loaded Video LUT to 'filename' + -c Load a linear display calibration + -V Verify that calfile/profile cal. is currently loaded in LUT + -I Install profile for display and use its calibration + -U Un-install profile for display + -S d Specify the install/uninstall scope for OS X [nlu] or X11/Vista [lu] + d is one of: n = network, l = local system, u = user (default) + -L Load installed profile & calibration + -D [level] Print debug diagnostics to stderr + calfile Load calibration (.cal or .icc) into Video LUT""" + @classmethod def CFG_DATA(self, name, fallback=True, raw=False, cfg=None): return { diff --git a/tests/test_real_display_size_mm.py b/tests/test_real_display_size_mm.py index 4a778318..9120878a 100644 --- a/tests/test_real_display_size_mm.py +++ b/tests/test_real_display_size_mm.py @@ -74,7 +74,7 @@ def get_argyll_util(cls, util_name): def test_real_display_size_mm(): - """Test DisplayCAL.RealDisplaySizeMM.RealDisplaySizeMM() function.""" + """Test RealDisplaySizeMM() function.""" RealDisplaySizeMM._displays = None assert RealDisplaySizeMM._displays is None with check_call( @@ -88,7 +88,7 @@ def test_real_display_size_mm(): def test_xrandr_output_x_id_1(): - """Test DisplayCAL.RealDisplaySizeMM.GetXRandROutputXID() function.""" + """Test GetXRandROutputXID() function.""" RealDisplaySizeMM._displays = None assert RealDisplaySizeMM._displays is None with check_call( @@ -100,7 +100,7 @@ def test_xrandr_output_x_id_1(): def test_enumerate_displays(): - """Test DisplayCAL.RealDisplaySizeMM.enumerate_displays() function.""" + """Test enumerate_displays() function.""" RealDisplaySizeMM._displays = None assert RealDisplaySizeMM._displays is None with check_call( @@ -137,7 +137,7 @@ def test__enumerate_displays_dispwin_path_is_none(monkeypatch): def test__enumerate_displays_uses_argyll_dispwin(patch_subprocess, patch_argyll_util): - """DisplayCAL.RealDisplaySizeMM._enumerate_displays() uses dispwin.""" + """_enumerate_displays() uses dispwin.""" PatchedSubprocess = patch_subprocess PatchedSubprocess.output = DisplayData.DISPWIN_OUTPUT_1 PatchedArgyll = patch_argyll_util @@ -152,10 +152,8 @@ def test__enumerate_displays_uses_argyll_dispwin(patch_subprocess, patch_argyll_ def test__enumerate_displays_uses_argyll_dispwin_output_1(patch_subprocess, patch_argyll_util): - """DisplayCAL.RealDisplaySizeMM._enumerate_displays() uses dispwin.""" - PatchedSubprocess = patch_subprocess - PatchedSubprocess.output = DisplayData.DISPWIN_OUTPUT_1 - PatchedArgyll = patch_argyll_util + """_enumerate_displays() uses dispwin.""" + patch_subprocess.output = DisplayData.DISPWIN_OUTPUT_1 result = RealDisplaySizeMM._enumerate_displays() assert isinstance(result, list) assert len(result) == 1 @@ -166,10 +164,8 @@ def test__enumerate_displays_uses_argyll_dispwin_output_1(patch_subprocess, patc def test__enumerate_displays_uses_argyll_dispwin_output_2(patch_subprocess, patch_argyll_util): - """DisplayCAL.RealDisplaySizeMM._enumerate_displays() uses dispwin.""" - PatchedSubprocess = patch_subprocess - PatchedSubprocess.output = DisplayData.DISPWIN_OUTPUT_2 - PatchedArgyll = patch_argyll_util + """_enumerate_displays() uses dispwin.""" + patch_subprocess.output = DisplayData.DISPWIN_OUTPUT_2 result = RealDisplaySizeMM._enumerate_displays() assert isinstance(result, list) assert len(result) == 2 @@ -183,6 +179,43 @@ def test__enumerate_displays_uses_argyll_dispwin_output_2(patch_subprocess, patc assert result[1]["pos"] == (1728, -575) +def test__enumerate_displays_without_a_proper_dispwin_output_missing_lines(patch_subprocess, patch_argyll_util): + """_enumerate_displays() return empty list when dispwin returns no usable data.""" + patch_subprocess.output = DisplayData.DISPWIN_OUTPUT_3 + result = RealDisplaySizeMM._enumerate_displays() + assert isinstance(result, list) + assert len(result) == 0 + + +def test__enumerate_displays_without_a_proper_dispwin_output_with_wrong_formatted_data(patch_subprocess, patch_argyll_util): + """_enumerate_displays() return empty list when dispwin returns no usable data.""" + from DisplayCAL import localization as lang + lang.init() + patch_subprocess.output = DisplayData.DISPWIN_OUTPUT_4 + with pytest.raises(ValueError) as cm: + result = RealDisplaySizeMM._enumerate_displays() + assert str(cm.value) == ( + 'An internal error occurred.\n' + 'Error code: -1\n' + 'Error message: dispwin returns no usable data while enumerating displays.' + ) + + +def test__enumerate_displays_without_a_proper_dispwin_output_with_partial_match(patch_subprocess, patch_argyll_util): + """_enumerate_displays() return empty list when dispwin returns no usable data.""" + from DisplayCAL import localization as lang + lang.init() + patch_subprocess.output = DisplayData.DISPWIN_OUTPUT_5 + with pytest.raises(ValueError) as cm: + result = RealDisplaySizeMM._enumerate_displays() + assert str(cm.value) == ( + 'An internal error occurred.\n' + 'Error code: -1\n' + 'Error message: dispwin returns no usable data while enumerating displays.' + ) + + + def test_get_display(): """Test DisplayCAL.RealDisplaySizeMM.get_display() function.""" RealDisplaySizeMM._displays = None From cb1ff43cf2e809121444b32ff23c2ff549e57304 Mon Sep 17 00:00:00 2001 From: Erkan Ozgur Yilmaz Date: Fri, 18 Oct 2024 09:40:01 +0100 Subject: [PATCH 06/34] * [#16] Removed the extension build part from setup scripts. --- DisplayCAL/lib64/__init__.py | 0 DisplayCAL/lib64/python310/__init__.py | 0 DisplayCAL/lib64/python311/__init__.py | 0 DisplayCAL/lib64/python312/__init__.py | 0 DisplayCAL/lib64/python313/__init__.py | 0 DisplayCAL/lib64/python38/__init__.py | 0 DisplayCAL/lib64/python39/__init__.py | 0 DisplayCAL/setup.py | 65 +------------------------- pyproject.toml | 6 +-- setup.cfg | 3 +- setup.py | 5 +- 11 files changed, 7 insertions(+), 72 deletions(-) delete mode 100644 DisplayCAL/lib64/__init__.py delete mode 100644 DisplayCAL/lib64/python310/__init__.py delete mode 100644 DisplayCAL/lib64/python311/__init__.py delete mode 100644 DisplayCAL/lib64/python312/__init__.py delete mode 100644 DisplayCAL/lib64/python313/__init__.py delete mode 100644 DisplayCAL/lib64/python38/__init__.py delete mode 100644 DisplayCAL/lib64/python39/__init__.py diff --git a/DisplayCAL/lib64/__init__.py b/DisplayCAL/lib64/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/DisplayCAL/lib64/python310/__init__.py b/DisplayCAL/lib64/python310/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/DisplayCAL/lib64/python311/__init__.py b/DisplayCAL/lib64/python311/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/DisplayCAL/lib64/python312/__init__.py b/DisplayCAL/lib64/python312/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/DisplayCAL/lib64/python313/__init__.py b/DisplayCAL/lib64/python313/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/DisplayCAL/lib64/python38/__init__.py b/DisplayCAL/lib64/python38/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/DisplayCAL/lib64/python39/__init__.py b/DisplayCAL/lib64/python39/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/DisplayCAL/setup.py b/DisplayCAL/setup.py index b221a4ad..986130c7 100644 --- a/DisplayCAL/setup.py +++ b/DisplayCAL/setup.py @@ -200,11 +200,6 @@ def add_lib_excludes(key, excludebits): for exclude in ("32", "64"): for pycompat in ("38", "39", "310", "311", "312", "313"): - if key == "win32" and ( - pycompat == str(sys.version_info[0]) + str(sys.version_info[1]) - or exclude == excludebits[0] - ): - continue config["excludes"][key].extend( [ f"{name}.lib{exclude}.python{pycompat}", @@ -925,38 +920,7 @@ def findall( "-framework Python", "-framework IOKit", ] - if not help and ( - "build" in sys.argv[1:] - or "build_ext" in sys.argv[1:] - or ( - ("install" in sys.argv[1:] or "install_lib" in sys.argv[1:]) - and "--skip-build" not in sys.argv[1:] - ) - ): - p = sp.Popen( - [ - sys.executable, - "-c", - f"""import os -from distutils.core import setup, Extension - -setup(ext_modules=[Extension("{name}.lib{bits}.RealDisplaySizeMM", sources={sources}, define_macros={macros}, extra_link_args={link_args})])""", - ] - + sys.argv[1:], - stdout=sp.PIPE, - stderr=sp.STDOUT, - ) - lines = [] - while True: - o = p.stdout.readline() - if o == "" and p.poll() is not None: - break - if o[0:4] == "gcc ": - lines.append(o) - print(o.rstrip()) - if len(lines): - os.environ["MACOSX_DEPLOYMENT_TARGET"] = "10.5" - sp.call(lines[-1], shell=True) # fix the library + else: macros = [("UNIX", None)] libraries = ["X11", "Xinerama", "Xrandr", "Xxf86vm"] @@ -966,14 +930,7 @@ def findall( else: extname = f"{name}.lib{bits}.python{sys.version_info[0]}{sys.version_info[1]}.RealDisplaySizeMM" - RealDisplaySizeMM = Extension( - extname, - sources=sources, - define_macros=macros, - libraries=libraries, - extra_link_args=link_args, - ) - ext_modules = [RealDisplaySizeMM] + ext_modules = [] requires = [] if not setuptools or sys.platform != "win32": @@ -986,24 +943,6 @@ def findall( requires.append("pywin32 (>= 213.0)") packages = [name, f"{name}.lib", f"{name}.lib.agw"] - if sdist: - # For source distributions we want all libraries - for pycompat in ("38", "39", "310", "311", "312", "313"): - packages.extend([f"{name}.lib{bits}", f"{name}.lib{bits}.python{pycompat}"]) - elif sys.platform == "darwin": - # On Mac OS X we only want the universal binaries - packages.append(f"{name}.lib{bits}") - elif sys.platform == "win32": - # On Windows we want separate libraries - packages.extend( - [ - f"{name}.lib{bits}", - f"{name}.lib{bits}.python{sys.version_info[0]}{sys.version_info[1]}", - ] - ) - else: - # On Linux the libraries will be built on build step - pass attrs = { "author": author_ascii, diff --git a/pyproject.toml b/pyproject.toml index 303f6602..bc7d7618 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,9 +1,9 @@ [build-system] requires = ["setuptools", - 'pywin32; platform_system=="Windows"'] + 'pywin32; platform_system=="Windows"'] build-backend = "setuptools.build_meta" [tool.pytest.ini_options] pythonpath = [ - "." -] \ No newline at end of file + "." +] diff --git a/setup.cfg b/setup.cfg index 3863c24a..544c865f 100644 --- a/setup.cfg +++ b/setup.cfg @@ -16,7 +16,6 @@ install_requires = PyChromecast pywin32; sys_platform=='win32' Send2Trash - #wexpect; sys_platform=='win32' wxPython zeroconf @@ -40,4 +39,4 @@ install_script = util/DisplayCAL_postinstall.py record = INSTALLED_FILES [black] -line-length = 88 \ No newline at end of file +line-length = 88 diff --git a/setup.py b/setup.py index 9bc0c0b5..800119f5 100755 --- a/setup.py +++ b/setup.py @@ -662,7 +662,7 @@ def setup(): from DisplayCAL import localization as lang scripts = get_scripts() - provides = [f"{name}"] + provides = [f"{name}"] for script, desc in scripts: provides.append(f"{script}") @@ -728,9 +728,6 @@ def setup(): i = sys.argv.index("bdist_pyi") sys.argv = sys.argv[:i] + sys.argv[i + 1 :] - if "build_ext" not in sys.argv[1:i]: - sys.argv.insert(i, "build_ext") - if "-F" in sys.argv[1:]: sys.argv.remove("-F") From df66b026c195ba8404897fc513d588fe8d91026c Mon Sep 17 00:00:00 2001 From: Erkan Ozgur Yilmaz Date: Mon, 21 Oct 2024 15:15:52 +0100 Subject: [PATCH 07/34] - [#16] `DisplayCAL.edid.get_edid()` now uses `xrandr` commad output to extract EDID data. - [#16] Fixed `DisplayCAL.RealDisplaySizeMM.Display.from_dispwin_data()` to expect and use `bytes` data instead of `str`. - [#16] Updated `DisplayCAL.RealDisplaySizeMM.get_dispwin_output()` to return `bytes` instead of `str` to match the rest of the application. --- DisplayCAL/RealDisplaySizeMM.py | 42 ++- DisplayCAL/argyll.py | 11 +- DisplayCAL/edid.py | 59 ++-- tests/conftest.py | 53 ++++ tests/data/dispwin_output_1.txt | 31 ++ tests/data/dispwin_output_2.txt | 32 ++ tests/data/dispwin_output_3.txt | 31 ++ tests/data/xrandr_output_1.txt | 388 +++++++++++++++++++++++ tests/data/xrandr_output_2.txt | 478 +++++++++++++++++++++++++++++ tests/data/xrandr_output_3.txt | 336 ++++++++++++++++++++ tests/test_edid.py | 192 +++++++++++- tests/test_real_display_size_mm.py | 184 ++++++----- 12 files changed, 1698 insertions(+), 139 deletions(-) create mode 100644 tests/data/dispwin_output_1.txt create mode 100644 tests/data/dispwin_output_2.txt create mode 100644 tests/data/dispwin_output_3.txt create mode 100644 tests/data/xrandr_output_1.txt create mode 100644 tests/data/xrandr_output_2.txt create mode 100644 tests/data/xrandr_output_3.txt diff --git a/DisplayCAL/RealDisplaySizeMM.py b/DisplayCAL/RealDisplaySizeMM.py index 8ea118c9..392452c3 100644 --- a/DisplayCAL/RealDisplaySizeMM.py +++ b/DisplayCAL/RealDisplaySizeMM.py @@ -104,20 +104,19 @@ def from_dispwin_data(self, display_info_line): """ display_info_line = display_info_line.strip() description_data = re.findall( - r"[\s\d]+= '(?P.*)'", - display_info_line + rb"[\s\d]+= '(?P.*)'", display_info_line ) dispwin_error_message = lang.getstr( - "error.generic", ( - -1, "dispwin returns no usable data while enumerating displays.") + "error.generic", + (-1, "dispwin returns no usable data while enumerating displays."), ) if not description_data: raise ValueError(dispwin_error_message) self.description = description_data[0] match = re.match( - r"[\s]*(?P\d) = '(?P.*), at (?P\d+), (?P[-\d]+), " - r"width (?P\d+), height (?P\d+).*'", - display_info_line + rb"[\s]*(?P\d) = '(?P.*), at (?P\d+), (?P[-\d]+), " + rb"width (?P\d+), height (?P\d+).*'", + display_info_line, ) if not match: raise ValueError(dispwin_error_message) @@ -152,21 +151,32 @@ def to_dict(self): return display_dict -def get_dispwin_output(): +def get_dispwin_output() -> bytes: """Return Argyll dispwin output. Returns: - str: The dispwin output. + bytes: The dispwin output. """ dispwin_path = argyll.get_argyll_util("dispwin") if dispwin_path is None: - return "" + return b"" + + if sys.platform == "win32": + startupinfo = sp.STARTUPINFO() + startupinfo.dwFlags |= sp.STARTF_USESHOWWINDOW + startupinfo.wShowWindow = sp.SW_HIDE + else: + startupinfo = None + p = subprocess.Popen( [dispwin_path, "-v", "-d0"], - stdout=subprocess.PIPE + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + startupinfo=startupinfo, ) output, _ = p.communicate() - return output.decode("utf-8") + return output def _enumerate_displays() -> List[dict]: @@ -177,14 +187,15 @@ def _enumerate_displays() -> List[dict]: """ displays = [] has_display = False - for line in get_dispwin_output().split("\n"): - if "-dweb[:port]" in line: + dispwin_output = get_dispwin_output() + for line in dispwin_output.split(b"\n"): + if b"-dweb[:port]" in line: break if has_display: display = Display() display.from_dispwin_data(line) displays.append(display.to_dict()) - if "-d n" in line: + if b"-d n" in line: has_display = True return displays @@ -229,6 +240,7 @@ def get_display(display_no=0): if is_virtual_display(display_no): return + try: argyll_display = getcfg("displays")[display_no] except IndexError: diff --git a/DisplayCAL/argyll.py b/DisplayCAL/argyll.py index 78bae191..8a575266 100644 --- a/DisplayCAL/argyll.py +++ b/DisplayCAL/argyll.py @@ -175,12 +175,11 @@ def get_argyll_version_string(name, paths=None): except Exception as exception: print(exception) return argyll_version_string.decode("utf-8") - for line in (p.communicate(timeout=30)[0] or "").splitlines(): - if isinstance(line, bytes): - line = line.strip() - if b"version" in line.lower(): - argyll_version_string = line[line.lower().find(b"version") + 8:] - break + for line in (p.communicate(timeout=30)[0] or b"").splitlines(): + line = line.strip() + if b"version" in line.lower(): + argyll_version_string = line[line.lower().find(b"version") + 8:] + break return argyll_version_string.decode("utf-8") diff --git a/DisplayCAL/edid.py b/DisplayCAL/edid.py index cb33c58b..53408551 100644 --- a/DisplayCAL/edid.py +++ b/DisplayCAL/edid.py @@ -7,8 +7,9 @@ import re import string import struct +import subprocess import sys -import warnings + if sys.platform == "win32": from threading import _MainThread, currentThread @@ -29,19 +30,13 @@ import win32api elif sys.platform == "darwin": import binascii - import subprocess as sp from DisplayCAL import config +from DisplayCAL import RealDisplaySizeMM as RDSMM from DisplayCAL.util_str import make_ascii_printable, safe_str, strtr if sys.platform == "win32": from DisplayCAL import util_win -elif sys.platform != "darwin": - try: - from DisplayCAL import RealDisplaySizeMM as RDSMM - except ImportError as exception: - warnings.warn(str(exception), Warning) - RDSMM = None HEADER = (0, 8) MANUFACTURER_ID = (8, 10) @@ -90,7 +85,6 @@ def get_edid(display_no=0, display_name=None, device=None): On Mac OS X, you need to specify a display name. On all other platforms, you need to specify a display number (zero-based). - """ edid = None if sys.platform == "win32": @@ -192,7 +186,9 @@ def get_edid(display_no=0, display_name=None, device=None): raise WMIError("No WMI connection") elif sys.platform == "darwin": # Get EDID via ioreg - p = sp.Popen(["ioreg", "-c", "IODisplay", "-S", "-w0"], stdout=sp.PIPE) + p = subprocess.Popen( + ["ioreg", "-c", "IODisplay", "-S", "-w0"], stdout=subprocess.PIPE + ) stdout, stderr = p.communicate() if not stdout: return {} @@ -213,19 +209,38 @@ def get_edid(display_no=0, display_name=None, device=None): # because the order is unknown return parsed_edid return {} - elif RDSMM: - # TODO: For Linux use the ``xrandr`` command output which supplies everything. - # - # ``xrandr --verbose`` gives all the info we need, including EDID which needs to - # be decoded: - # - # ```python - # import codecs - # edid = codecs.decode(xrandr_edid_data, "hex") - # ``` + else: display = RDSMM.get_display(display_no) - if display: - edid = display.get("edid") + if not display: + return {} + + p = subprocess.Popen(["xrandr", "--verbose"], stdout=subprocess.PIPE) + stdout, stderr = p.communicate() + + if not stdout: + return {} + + found_display = False + found_edid = False + edid_data = [] + for line in stdout.splitlines(): + if found_edid: + if line.startswith(b"\t\t"): + # extract the edid data + edid_data.append(line.strip()) + else: + # read all data, exit + break + if found_display: + # try to find EDID + if b"EDID" in line: + found_edid = True + # try to find the display + if (display["name"] + b" connected") in line: + found_display = True + + edid = b"".join(edid_data) + if edid and len(edid) >= 128: return parse_edid(edid) return {} diff --git a/tests/conftest.py b/tests/conftest.py index 48cbb633..c083acf2 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -139,3 +139,56 @@ def random_icc_profile(): # clean the file os.remove(icc_profile_path) + + +@pytest.fixture(scope="function") +def patch_subprocess(monkeypatch): + """Patch subprocess. + + Yields: + Any: The patched subprocess class. + """ + + class Process: + def __init__(self, output=None): + self.output = output + + def communicate(self): + return self.output, None + + class PatchedSubprocess: + passed_args = [] + passed_kwargs = {} + STDOUT = None + PIPE = None + output = {} + + @classmethod + def Popen(cls, *args, **kwargs): + cls.passed_args += args + cls.passed_kwargs.update(kwargs) + process = Process(output=cls.output.get("".join(*args))) + return process + + yield PatchedSubprocess + + +@pytest.fixture(scope="function") +def patch_argyll_util(monkeypatch): + """Patch argyll. + + Yields: + Any: The patched argyll class. + """ + + class PatchedArgyll: + passed_util_name = [] + + @classmethod + def get_argyll_util(cls, util_name): + cls.passed_util_name.append(util_name) + return "dispwin" + + monkeypatch.setattr("DisplayCAL.RealDisplaySizeMM.argyll", PatchedArgyll) + + yield PatchedArgyll diff --git a/tests/data/dispwin_output_1.txt b/tests/data/dispwin_output_1.txt new file mode 100644 index 00000000..ae3514e9 --- /dev/null +++ b/tests/data/dispwin_output_1.txt @@ -0,0 +1,31 @@ +Test display patch window, Set Video LUTs, Install profiles, Version 3.3.0 +Author: Graeme W. Gill, licensed under the AGPL Version 3 +Diagnostic: -d parameter '0' is out of range +usage: dispwin [options] [calfile] + -v Verbose mode + -d n Choose the display from the following list (default 1) + 1 = 'DP-4, at 0, 0, width 2560, height 1440 (Primary Display)' + -dweb[:port] Display via web server at port (default 8080) + -dcc[:n] Display via n'th ChromeCast (default 1, ? for list) + -d dummy Display via dummy (non-existant, invisible) display + -P ho,vo,ss[,vs] Position test window and scale it + -F Fill whole screen with black background + -E Video encode output as (16-235)/255 "TV" levels + -i Run forever with random values + -G filename Display RGB colors from CGATS (ie .ti1) file + -C r.rr,g.gg,b.bb Add this RGB color to list to be displayed + -m Manually cycle through values + -Y msec patch delay in msec (default 2000) + -f Test grey ramp fade + -r Test just Video LUT loading & Beeps + -n Test native output (rather than through Video LUT and C.M.) + -s filename Save the currently loaded Video LUT to 'filename' + -c Load a linear display calibration + -V Verify that calfile/profile cal. is currently loaded in LUT + -I Install profile for display and use its calibration + -U Un-install profile for display + -S d Specify the install/uninstall scope for OS X [nlu] or X11/Vista [lu] + d is one of: n = network, l = local system, u = user (default) + -L Load installed profile & calibration + -D [level] Print debug diagnostics to stderr + calfile Load calibration (.cal or .icc) into Video LUT diff --git a/tests/data/dispwin_output_2.txt b/tests/data/dispwin_output_2.txt new file mode 100644 index 00000000..2d2bc981 --- /dev/null +++ b/tests/data/dispwin_output_2.txt @@ -0,0 +1,32 @@ +Test display patch window, Set Video LUTs, Install profiles, Version 3.3.0 +Author: Graeme W. Gill, licensed under the AGPL Version 3 +Diagnostic: -d parameter '0' is out of range +usage: dispwin [options] [calfile] + -v Verbose mode + -d n Choose the display from the following list (default 1) + 1 = 'DP-4, at 0, 0, width 2560, height 1440 (Primary Display)' + 2 = 'DP-2, at 2160, 0, width 3840, height 2160' + -dweb[:port] Display via web server at port (default 8080) + -dcc[:n] Display via n'th ChromeCast (default 1, ? for list) + -d dummy Display via dummy (non-existant, invisible) display + -P ho,vo,ss[,vs] Position test window and scale it + -F Fill whole screen with black background + -E Video encode output as (16-235)/255 "TV" levels + -i Run forever with random values + -G filename Display RGB colors from CGATS (ie .ti1) file + -C r.rr,g.gg,b.bb Add this RGB color to list to be displayed + -m Manually cycle through values + -Y msec patch delay in msec (default 2000) + -f Test grey ramp fade + -r Test just Video LUT loading & Beeps + -n Test native output (rather than through Video LUT and C.M.) + -s filename Save the currently loaded Video LUT to 'filename' + -c Load a linear display calibration + -V Verify that calfile/profile cal. is currently loaded in LUT + -I Install profile for display and use its calibration + -U Un-install profile for display + -S d Specify the install/uninstall scope for OS X [nlu] or X11/Vista [lu] + d is one of: n = network, l = local system, u = user (default) + -L Load installed profile & calibration + -D [level] Print debug diagnostics to stderr + calfile Load calibration (.cal or .icc) into Video LUT diff --git a/tests/data/dispwin_output_3.txt b/tests/data/dispwin_output_3.txt new file mode 100644 index 00000000..24cfcc62 --- /dev/null +++ b/tests/data/dispwin_output_3.txt @@ -0,0 +1,31 @@ +Test display patch window, Set Video LUTs, Install profiles, Version 3.3.0 +Author: Graeme W. Gill, licensed under the AGPL Version 3 +Diagnostic: -d parameter '0' is out of range +usage: dispwin [options] [calfile] + -v Verbose mode + -d n Choose the display from the following list (default 1) + 1 = 'HDMI-A-0, at 0, 0, width 3840, height 2160 (Primary Display)' + -dweb[:port] Display via web server at port (default 8080) + -dcc[:n] Display via n'th ChromeCast (default 1, ? for list) + -d dummy Display via dummy (non-existant, invisible) display + -P ho,vo,ss[,vs] Position test window and scale it + -F Fill whole screen with black background + -E Video encode output as (16-235)/255 "TV" levels + -i Run forever with random values + -G filename Display RGB colors from CGATS (ie .ti1) file + -C r.rr,g.gg,b.bb Add this RGB color to list to be displayed + -m Manually cycle through values + -Y msec patch delay in msec (default 2000) + -f Test grey ramp fade + -r Test just Video LUT loading & Beeps + -n Test native output (rather than through Video LUT and C.M.) + -s filename Save the currently loaded Video LUT to 'filename' + -c Load a linear display calibration + -V Verify that calfile/profile cal. is currently loaded in LUT + -I Install profile for display and use its calibration + -U Un-install profile for display + -S d Specify the install/uninstall scope for OS X [nlu] or X11/Vista [lu] + d is one of: n = network, l = local system, u = user (default) + -L Load installed profile & calibration + -D [level] Print debug diagnostics to stderr + calfile Load calibration (.cal or .icc) into Video LUT diff --git a/tests/data/xrandr_output_1.txt b/tests/data/xrandr_output_1.txt new file mode 100644 index 00000000..f9b8198f --- /dev/null +++ b/tests/data/xrandr_output_1.txt @@ -0,0 +1,388 @@ +Screen 0: minimum 8 x 8, current 2560 x 1440, maximum 32767 x 32767 +HDMI-0 disconnected (normal left inverted right x axis y axis) + Identifier: 0x1bc + Timestamp: 21510 + Subpixel: unknown + Clones: + CRTCs: 0 1 2 3 + Transform: 1.000000 0.000000 0.000000 + 0.000000 1.000000 0.000000 + 0.000000 0.000000 1.000000 + filter: + 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 + BorderDimensions: 4 + supported: 4 + Border: 0 0 0 0 + range: (0, 65535) + SignalFormat: TMDS + supported: TMDS + ConnectorType: HDMI + ConnectorNumber: 3 + _ConnectorLocation: 3 + non-desktop: 0 + supported: 0, 1 +DP-0 disconnected (normal left inverted right x axis y axis) + Identifier: 0x1bd + Timestamp: 21510 + Subpixel: unknown + Clones: + CRTCs: 0 1 2 3 + Transform: 1.000000 0.000000 0.000000 + 0.000000 1.000000 0.000000 + 0.000000 0.000000 1.000000 + filter: + 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 + BorderDimensions: 4 + supported: 4 + Border: 0 0 0 0 + range: (0, 65535) + SignalFormat: DisplayPort + supported: DisplayPort + ConnectorType: DisplayPort + ConnectorNumber: 2 + _ConnectorLocation: 2 + non-desktop: 0 + supported: 0, 1 +DP-1 disconnected (normal left inverted right x axis y axis) + Identifier: 0x1be + Timestamp: 21510 + Subpixel: unknown + Clones: + CRTCs: 0 1 2 3 + Transform: 1.000000 0.000000 0.000000 + 0.000000 1.000000 0.000000 + 0.000000 0.000000 1.000000 + filter: + 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 + BorderDimensions: 4 + supported: 4 + Border: 0 0 0 0 + range: (0, 65535) + SignalFormat: TMDS + supported: TMDS + ConnectorType: DisplayPort + ConnectorNumber: 2 + _ConnectorLocation: 2 + non-desktop: 0 + supported: 0, 1 +HDMI-1 disconnected (normal left inverted right x axis y axis) + Identifier: 0x1bf + Timestamp: 21510 + Subpixel: unknown + Clones: + CRTCs: 0 1 2 3 + Transform: 1.000000 0.000000 0.000000 + 0.000000 1.000000 0.000000 + 0.000000 0.000000 1.000000 + filter: + 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 + BorderDimensions: 4 + supported: 4 + Border: 0 0 0 0 + range: (0, 65535) + SignalFormat: TMDS + supported: TMDS + ConnectorType: HDMI + ConnectorNumber: 1 + _ConnectorLocation: 1 + non-desktop: 0 + supported: 0, 1 +DP-2 disconnected (normal left inverted right x axis y axis) + Identifier: 0x1c0 + Timestamp: 21510 + Subpixel: unknown + Clones: + CRTCs: 0 1 2 3 + Transform: 1.000000 0.000000 0.000000 + 0.000000 1.000000 0.000000 + 0.000000 0.000000 1.000000 + filter: + 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 + BorderDimensions: 4 + supported: 4 + Border: 0 0 0 0 + range: (0, 65535) + SignalFormat: DisplayPort + supported: DisplayPort + ConnectorType: DisplayPort + ConnectorNumber: 0 + _ConnectorLocation: 0 + non-desktop: 0 + supported: 0, 1 +DP-3 disconnected (normal left inverted right x axis y axis) + Identifier: 0x1c1 + Timestamp: 21510 + Subpixel: unknown + Clones: + CRTCs: 0 1 2 3 + Transform: 1.000000 0.000000 0.000000 + 0.000000 1.000000 0.000000 + 0.000000 0.000000 1.000000 + filter: + 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 + BorderDimensions: 4 + supported: 4 + Border: 0 0 0 0 + range: (0, 65535) + SignalFormat: TMDS + supported: TMDS + ConnectorType: DisplayPort + ConnectorNumber: 0 + _ConnectorLocation: 0 + non-desktop: 0 + supported: 0, 1 +DP-4 connected primary 2560x1440+0+0 (0x1c3) normal (normal left inverted right x axis y axis) 597mm x 336mm + Identifier: 0x1c2 + Timestamp: 21510 + Subpixel: unknown + Gamma: 1.0:1.0:1.0 + Brightness: 1.0 + Clones: + CRTC: 0 + CRTCs: 0 1 2 3 + Transform: 1.000000 0.000000 0.000000 + 0.000000 1.000000 0.000000 + 0.000000 0.000000 1.000000 + filter: + _MUTTER_PRESENTATION_OUTPUT: 0 + 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: + 00ffffffffffff005a633a7a0f010101 + 311e0104b53c22783bb091ab524ea026 + 0f5054bfef80e1c0d100d1c0b300a940 + 8180810081c0565e00a0a0a029503020 + 350055502100001a000000ff00573855 + 3230343930303130340a000000fd0018 + 4b0f5a1e000a202020202020000000fc + 00565032373638610a2020202020017b + 020322f155901f05145a5904131e1d0f + 0e07061211161503020123097f078301 + 0000023a801871382d40582c45005550 + 2100001e011d8018711c1620582c2500 + 55502100009e023a80d072382d40102c + 458055502100001e011d007251d01e20 + 6e28550055502100001e584d00b8a138 + 1440f82c4b0055502100001e000000d2 + BorderDimensions: 4 + supported: 4 + Border: 0 0 0 0 + range: (0, 65535) + SignalFormat: DisplayPort + supported: DisplayPort + ConnectorType: DisplayPort + ConnectorNumber: 4 + _ConnectorLocation: 4 + non-desktop: 0 + supported: 0, 1 + 2560x1440 (0x1c3) 241.500MHz +HSync -VSync *current +preferred + h: width 2560 start 2608 end 2640 total 2720 skew 0 clock 88.79KHz + v: height 1440 start 1443 end 1448 total 1481 clock 59.95Hz + 2560x1080 (0x1c4) 198.000MHz +HSync +VSync + h: width 2560 start 2808 end 2852 total 3000 skew 0 clock 66.00KHz + v: height 1080 start 1084 end 1095 total 1100 clock 60.00Hz + 2560x1080 (0x1c5) 197.800MHz +HSync +VSync + h: width 2560 start 2808 end 2852 total 3000 skew 0 clock 65.93KHz + v: height 1080 start 1084 end 1089 total 1100 clock 59.94Hz + 2560x1080 (0x1c6) 185.630MHz +HSync +VSync + h: width 2560 start 3108 end 3152 total 3300 skew 0 clock 56.25KHz + v: height 1080 start 1084 end 1089 total 1125 clock 50.00Hz + 2048x1152 (0x1c7) 162.000MHz +HSync +VSync + h: width 2048 start 2074 end 2154 total 2250 skew 0 clock 72.00KHz + v: height 1152 start 1153 end 1156 total 1200 clock 60.00Hz + 1920x1200 (0x1c8) 193.250MHz -HSync +VSync + h: width 1920 start 2056 end 2256 total 2592 skew 0 clock 74.56KHz + v: height 1200 start 1203 end 1209 total 1245 clock 59.88Hz + 1920x1080 (0x1c9) 148.500MHz +HSync +VSync + h: width 1920 start 2008 end 2052 total 2200 skew 0 clock 67.50KHz + v: height 1080 start 1084 end 1089 total 1125 clock 60.00Hz + 1920x1080 (0x1ca) 148.350MHz +HSync +VSync + h: width 1920 start 2008 end 2052 total 2200 skew 0 clock 67.43KHz + v: height 1080 start 1084 end 1089 total 1125 clock 59.94Hz + 1920x1080 (0x1cb) 148.500MHz +HSync +VSync + h: width 1920 start 2448 end 2492 total 2640 skew 0 clock 56.25KHz + v: height 1080 start 1084 end 1089 total 1125 clock 50.00Hz + 1680x1050 (0x1cc) 146.250MHz -HSync +VSync + h: width 1680 start 1784 end 1960 total 2240 skew 0 clock 65.29KHz + v: height 1050 start 1053 end 1059 total 1089 clock 59.95Hz + 1600x1200 (0x1cd) 162.000MHz +HSync +VSync + h: width 1600 start 1664 end 1856 total 2160 skew 0 clock 75.00KHz + v: height 1200 start 1201 end 1204 total 1250 clock 60.00Hz + 1440x576 (0x1ce) 54.000MHz -HSync +VSync + h: width 1440 start 1464 end 1592 total 1728 skew 0 clock 31.25KHz + v: height 576 start 581 end 586 total 625 clock 50.00Hz + 1440x480 (0x1cf) 54.000MHz -HSync -VSync + h: width 1440 start 1472 end 1596 total 1716 skew 0 clock 31.47KHz + v: height 480 start 489 end 495 total 525 clock 59.94Hz + 1280x1024 (0x1d0) 135.000MHz +HSync +VSync + h: width 1280 start 1296 end 1440 total 1688 skew 0 clock 79.98KHz + v: height 1024 start 1025 end 1028 total 1066 clock 75.02Hz + 1280x1024 (0x1d1) 108.000MHz +HSync +VSync + h: width 1280 start 1328 end 1440 total 1688 skew 0 clock 63.98KHz + v: height 1024 start 1025 end 1028 total 1066 clock 60.02Hz + 1280x800 (0x1d2) 83.500MHz -HSync +VSync + h: width 1280 start 1352 end 1480 total 1680 skew 0 clock 49.70KHz + v: height 800 start 803 end 809 total 831 clock 59.81Hz + 1280x720 (0x1d3) 74.250MHz +HSync +VSync + h: width 1280 start 1390 end 1430 total 1650 skew 0 clock 45.00KHz + v: height 720 start 725 end 730 total 750 clock 60.00Hz + 1280x720 (0x1d4) 74.180MHz +HSync +VSync + h: width 1280 start 1390 end 1430 total 1650 skew 0 clock 44.96KHz + v: height 720 start 725 end 730 total 750 clock 59.94Hz + 1280x720 (0x1d5) 74.250MHz +HSync +VSync + h: width 1280 start 1720 end 1760 total 1980 skew 0 clock 37.50KHz + v: height 720 start 725 end 730 total 750 clock 50.00Hz + 1024x768 (0x1d6) 78.750MHz +HSync +VSync + h: width 1024 start 1040 end 1136 total 1312 skew 0 clock 60.02KHz + v: height 768 start 769 end 772 total 800 clock 75.03Hz + 1024x768 (0x1d7) 75.000MHz -HSync -VSync + h: width 1024 start 1048 end 1184 total 1328 skew 0 clock 56.48KHz + v: height 768 start 771 end 777 total 806 clock 70.07Hz + 1024x768 (0x1d8) 65.000MHz -HSync -VSync + h: width 1024 start 1048 end 1184 total 1344 skew 0 clock 48.36KHz + v: height 768 start 771 end 777 total 806 clock 60.00Hz + 800x600 (0x1d9) 49.500MHz +HSync +VSync + h: width 800 start 816 end 896 total 1056 skew 0 clock 46.88KHz + v: height 600 start 601 end 604 total 625 clock 75.00Hz + 800x600 (0x1da) 50.000MHz +HSync +VSync + h: width 800 start 856 end 976 total 1040 skew 0 clock 48.08KHz + v: height 600 start 637 end 643 total 666 clock 72.19Hz + 800x600 (0x1db) 40.000MHz +HSync +VSync + h: width 800 start 840 end 968 total 1056 skew 0 clock 37.88KHz + v: height 600 start 601 end 605 total 628 clock 60.32Hz + 800x600 (0x1dc) 36.000MHz +HSync +VSync + h: width 800 start 824 end 896 total 1024 skew 0 clock 35.16KHz + v: height 600 start 601 end 603 total 625 clock 56.25Hz + 720x576 (0x1dd) 27.000MHz -HSync -VSync + h: width 720 start 732 end 796 total 864 skew 0 clock 31.25KHz + v: height 576 start 581 end 586 total 625 clock 50.00Hz + 720x480 (0x1de) 27.000MHz -HSync -VSync + h: width 720 start 736 end 798 total 858 skew 0 clock 31.47KHz + v: height 480 start 489 end 495 total 525 clock 59.94Hz + 640x480 (0x1df) 31.500MHz -HSync -VSync + h: width 640 start 656 end 720 total 840 skew 0 clock 37.50KHz + v: height 480 start 481 end 484 total 500 clock 75.00Hz + 640x480 (0x1e0) 31.500MHz -HSync -VSync + h: width 640 start 656 end 696 total 832 skew 0 clock 37.86KHz + v: height 480 start 481 end 484 total 520 clock 72.81Hz + 640x480 (0x1e1) 25.175MHz -HSync -VSync + h: width 640 start 656 end 752 total 800 skew 0 clock 31.47KHz + v: height 480 start 490 end 492 total 525 clock 59.94Hz + 640x480 (0x1e2) 25.170MHz -HSync -VSync + h: width 640 start 656 end 752 total 800 skew 0 clock 31.46KHz + v: height 480 start 490 end 492 total 525 clock 59.93Hz +DP-5 disconnected (normal left inverted right x axis y axis) + Identifier: 0x1e3 + Timestamp: 21510 + Subpixel: unknown + Clones: + CRTCs: 0 1 2 3 + Transform: 1.000000 0.000000 0.000000 + 0.000000 1.000000 0.000000 + 0.000000 0.000000 1.000000 + filter: + 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 + BorderDimensions: 4 + supported: 4 + Border: 0 0 0 0 + range: (0, 65535) + SignalFormat: TMDS + supported: TMDS + ConnectorType: DisplayPort + ConnectorNumber: 4 + _ConnectorLocation: 4 + non-desktop: 0 + supported: 0, 1 +HDMI-1-1 disconnected (normal left inverted right x axis y axis) + Identifier: 0x206 + Timestamp: 20565 + Subpixel: unknown + Clones: + CRTCs: 4 5 6 7 + Transform: 1.000000 0.000000 0.000000 + 0.000000 1.000000 0.000000 + 0.000000 0.000000 1.000000 + filter: + PRIME Synchronization: 1 + supported: 0, 1 + HDCP Content Type: HDCP Type0 + supported: HDCP Type0, HDCP Type1 + Content Protection: Undesired + supported: Undesired, Desired, Enabled + vrr_capable: 0 + range: (0, 1) + Colorspace: Default + supported: Default, BT709_YCC, opRGB, BT2020_RGB, BT2020_YCC + max bpc: 16 + range: (8, 16) + underscan vborder: 0 + range: (0, 128) + underscan hborder: 0 + range: (0, 128) + underscan: off + supported: off, on, auto + scaling mode: None + supported: None, Full, Center, Full aspect + 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: 93 + supported: 93 + non-desktop: 0 + range: (0, 1) +DP-1-1 disconnected (normal left inverted right x axis y axis) + Identifier: 0x207 + Timestamp: 20565 + Subpixel: unknown + Clones: + CRTCs: 4 5 6 7 + Transform: 1.000000 0.000000 0.000000 + 0.000000 1.000000 0.000000 + 0.000000 0.000000 1.000000 + filter: + PRIME Synchronization: 1 + supported: 0, 1 + subconnector: Unknown + supported: Unknown, VGA, DVI-D, HDMI, DP, Wireless, Native + HDCP Content Type: HDCP Type0 + supported: HDCP Type0, HDCP Type1 + Content Protection: Undesired + supported: Undesired, Desired, Enabled + vrr_capable: 0 + range: (0, 1) + Colorspace: Default + supported: Default, BT709_YCC, opRGB, BT2020_RGB, BT2020_YCC + max bpc: 16 + range: (8, 16) + underscan vborder: 0 + range: (0, 128) + underscan hborder: 0 + range: (0, 128) + underscan: off + supported: off, on, auto + scaling mode: None + supported: None, Full, Center, Full aspect + 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: 100 + supported: 100 + non-desktop: 0 + range: (0, 1) diff --git a/tests/data/xrandr_output_2.txt b/tests/data/xrandr_output_2.txt new file mode 100644 index 00000000..016838d1 --- /dev/null +++ b/tests/data/xrandr_output_2.txt @@ -0,0 +1,478 @@ +Screen 0: minimum 8 x 8, current 6400 x 2160, maximum 32767 x 32767 +HDMI-0 disconnected (normal left inverted right x axis y axis) + Identifier: 0x1bc + Timestamp: 744876 + Subpixel: unknown + Clones: + CRTCs: 0 1 2 3 + Transform: 1.000000 0.000000 0.000000 + 0.000000 1.000000 0.000000 + 0.000000 0.000000 1.000000 + filter: + 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 + BorderDimensions: 4 + supported: 4 + Border: 0 0 0 0 + range: (0, 65535) + SignalFormat: TMDS + supported: TMDS + ConnectorType: HDMI + ConnectorNumber: 3 + _ConnectorLocation: 3 + non-desktop: 0 + supported: 0, 1 +DP-0 disconnected (normal left inverted right x axis y axis) + Identifier: 0x1bd + Timestamp: 744876 + Subpixel: unknown + Clones: + CRTCs: 0 1 2 3 + Transform: 1.000000 0.000000 0.000000 + 0.000000 1.000000 0.000000 + 0.000000 0.000000 1.000000 + filter: + 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 + BorderDimensions: 4 + supported: 4 + Border: 0 0 0 0 + range: (0, 65535) + SignalFormat: DisplayPort + supported: DisplayPort + ConnectorType: DisplayPort + ConnectorNumber: 2 + _ConnectorLocation: 2 + non-desktop: 0 + supported: 0, 1 +DP-1 disconnected (normal left inverted right x axis y axis) + Identifier: 0x1be + Timestamp: 744876 + Subpixel: unknown + Clones: + CRTCs: 0 1 2 3 + Transform: 1.000000 0.000000 0.000000 + 0.000000 1.000000 0.000000 + 0.000000 0.000000 1.000000 + filter: + 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 + BorderDimensions: 4 + supported: 4 + Border: 0 0 0 0 + range: (0, 65535) + SignalFormat: TMDS + supported: TMDS + ConnectorType: DisplayPort + ConnectorNumber: 2 + _ConnectorLocation: 2 + non-desktop: 0 + supported: 0, 1 +HDMI-1 disconnected (normal left inverted right x axis y axis) + Identifier: 0x1bf + Timestamp: 744876 + Subpixel: unknown + Clones: + CRTCs: 0 1 2 3 + Transform: 1.000000 0.000000 0.000000 + 0.000000 1.000000 0.000000 + 0.000000 0.000000 1.000000 + filter: + 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 + BorderDimensions: 4 + supported: 4 + Border: 0 0 0 0 + range: (0, 65535) + SignalFormat: TMDS + supported: TMDS + ConnectorType: HDMI + ConnectorNumber: 1 + _ConnectorLocation: 1 + non-desktop: 0 + supported: 0, 1 +DP-2 connected 3840x2160+2560+0 (0x238) normal (normal left inverted right x axis y axis) 607mm x 345mm + Identifier: 0x1c0 + Timestamp: 744876 + Subpixel: unknown + Gamma: 1.0:1.0:1.0 + Brightness: 1.0 + Clones: + CRTC: 1 + CRTCs: 0 1 2 3 + Transform: 1.000000 0.000000 0.000000 + 0.000000 1.000000 0.000000 + 0.000000 0.000000 1.000000 + filter: + _MUTTER_PRESENTATION_OUTPUT: 0 + EDID: + 00ffffffffffff004c2d4d0c46584d30 + 231a0104b53d23783a5fb1a2574fa228 + 0f5054bfef80714f810081c08180a9c0 + b300950001014dd000a0f0703e803020 + 35005f592100001a000000fd00384b1e + 873c000a202020202020000000fc0055 + 3238453539300a2020202020000000ff + 00485450483930303130330a20200166 + 02030ef041102309070783010000023a + 801871382d40582c45005f592100001e + 565e00a0a0a02950302035005f592100 + 001a04740030f2705a80b0588a005f59 + 2100001e000000000000000000000000 + 00000000000000000000000000000000 + 00000000000000000000000000000000 + 00000000000000000000000000000052 + 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 + BorderDimensions: 4 + supported: 4 + Border: 0 0 0 0 + range: (0, 65535) + SignalFormat: DisplayPort + supported: DisplayPort + ConnectorType: DisplayPort + ConnectorNumber: 0 + _ConnectorLocation: 0 + non-desktop: 0 + supported: 0, 1 + 3840x2160 (0x238) 533.250MHz +HSync -VSync *current +preferred + h: width 3840 start 3888 end 3920 total 4000 skew 0 clock 133.31KHz + v: height 2160 start 2163 end 2168 total 2222 clock 60.00Hz + 3840x2160 (0x239) 297.000MHz +HSync +VSync + h: width 3840 start 4016 end 4104 total 4400 skew 0 clock 67.50KHz + v: height 2160 start 2168 end 2178 total 2250 clock 30.00Hz + 2560x1440 (0x1c3) 241.500MHz +HSync -VSync + h: width 2560 start 2608 end 2640 total 2720 skew 0 clock 88.79KHz + v: height 1440 start 1443 end 1448 total 1481 clock 59.95Hz + 1920x1080 (0x1c9) 148.500MHz +HSync +VSync + h: width 1920 start 2008 end 2052 total 2200 skew 0 clock 67.50KHz + v: height 1080 start 1084 end 1089 total 1125 clock 60.00Hz + 1920x1080 (0x1ca) 148.350MHz +HSync +VSync + h: width 1920 start 2008 end 2052 total 2200 skew 0 clock 67.43KHz + v: height 1080 start 1084 end 1089 total 1125 clock 59.94Hz + 1680x1050 (0x1cc) 146.250MHz -HSync +VSync + h: width 1680 start 1784 end 1960 total 2240 skew 0 clock 65.29KHz + v: height 1050 start 1053 end 1059 total 1089 clock 59.95Hz + 1600x900 (0x23a) 108.000MHz +HSync +VSync + h: width 1600 start 1624 end 1704 total 1800 skew 0 clock 60.00KHz + v: height 900 start 901 end 904 total 1000 clock 60.00Hz + 1440x900 (0x23b) 106.500MHz -HSync +VSync + h: width 1440 start 1520 end 1672 total 1904 skew 0 clock 55.93KHz + v: height 900 start 903 end 909 total 934 clock 59.89Hz + 1280x1024 (0x1d0) 135.000MHz +HSync +VSync + h: width 1280 start 1296 end 1440 total 1688 skew 0 clock 79.98KHz + v: height 1024 start 1025 end 1028 total 1066 clock 75.02Hz + 1280x1024 (0x1d1) 108.000MHz +HSync +VSync + h: width 1280 start 1328 end 1440 total 1688 skew 0 clock 63.98KHz + v: height 1024 start 1025 end 1028 total 1066 clock 60.02Hz + 1280x800 (0x1d2) 83.500MHz -HSync +VSync + h: width 1280 start 1352 end 1480 total 1680 skew 0 clock 49.70KHz + v: height 800 start 803 end 809 total 831 clock 59.81Hz + 1280x720 (0x1d3) 74.250MHz +HSync +VSync + h: width 1280 start 1390 end 1430 total 1650 skew 0 clock 45.00KHz + v: height 720 start 725 end 730 total 750 clock 60.00Hz + 1152x864 (0x23c) 108.000MHz +HSync +VSync + h: width 1152 start 1216 end 1344 total 1600 skew 0 clock 67.50KHz + v: height 864 start 865 end 868 total 900 clock 75.00Hz + 1024x768 (0x1d6) 78.750MHz +HSync +VSync + h: width 1024 start 1040 end 1136 total 1312 skew 0 clock 60.02KHz + v: height 768 start 769 end 772 total 800 clock 75.03Hz + 1024x768 (0x1d7) 75.000MHz -HSync -VSync + h: width 1024 start 1048 end 1184 total 1328 skew 0 clock 56.48KHz + v: height 768 start 771 end 777 total 806 clock 70.07Hz + 1024x768 (0x1d8) 65.000MHz -HSync -VSync + h: width 1024 start 1048 end 1184 total 1344 skew 0 clock 48.36KHz + v: height 768 start 771 end 777 total 806 clock 60.00Hz + 800x600 (0x1d9) 49.500MHz +HSync +VSync + h: width 800 start 816 end 896 total 1056 skew 0 clock 46.88KHz + v: height 600 start 601 end 604 total 625 clock 75.00Hz + 800x600 (0x1da) 50.000MHz +HSync +VSync + h: width 800 start 856 end 976 total 1040 skew 0 clock 48.08KHz + v: height 600 start 637 end 643 total 666 clock 72.19Hz + 800x600 (0x1db) 40.000MHz +HSync +VSync + h: width 800 start 840 end 968 total 1056 skew 0 clock 37.88KHz + v: height 600 start 601 end 605 total 628 clock 60.32Hz + 800x600 (0x1dc) 36.000MHz +HSync +VSync + h: width 800 start 824 end 896 total 1024 skew 0 clock 35.16KHz + v: height 600 start 601 end 603 total 625 clock 56.25Hz + 640x480 (0x1df) 31.500MHz -HSync -VSync + h: width 640 start 656 end 720 total 840 skew 0 clock 37.50KHz + v: height 480 start 481 end 484 total 500 clock 75.00Hz + 640x480 (0x1e0) 31.500MHz -HSync -VSync + h: width 640 start 656 end 696 total 832 skew 0 clock 37.86KHz + v: height 480 start 481 end 484 total 520 clock 72.81Hz + 640x480 (0x1e1) 25.175MHz -HSync -VSync + h: width 640 start 656 end 752 total 800 skew 0 clock 31.47KHz + v: height 480 start 490 end 492 total 525 clock 59.94Hz +DP-3 disconnected (normal left inverted right x axis y axis) + Identifier: 0x1c1 + Timestamp: 744876 + Subpixel: unknown + Clones: + CRTCs: 0 1 2 3 + Transform: 1.000000 0.000000 0.000000 + 0.000000 1.000000 0.000000 + 0.000000 0.000000 1.000000 + filter: + 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 + BorderDimensions: 4 + supported: 4 + Border: 0 0 0 0 + range: (0, 65535) + SignalFormat: TMDS + supported: TMDS + ConnectorType: DisplayPort + ConnectorNumber: 0 + _ConnectorLocation: 0 + non-desktop: 0 + supported: 0, 1 +DP-4 connected primary 2560x1440+0+0 (0x1c3) normal (normal left inverted right x axis y axis) 597mm x 336mm + Identifier: 0x1c2 + Timestamp: 744876 + Subpixel: unknown + Gamma: 1.0:1.0:1.0 + Brightness: 1.0 + Clones: + CRTC: 0 + CRTCs: 0 1 2 3 + Transform: 1.000000 0.000000 0.000000 + 0.000000 1.000000 0.000000 + 0.000000 0.000000 1.000000 + filter: + _MUTTER_PRESENTATION_OUTPUT: 0 + 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: + 00ffffffffffff005a633a7a0f010101 + 311e0104b53c22783bb091ab524ea026 + 0f5054bfef80e1c0d100d1c0b300a940 + 8180810081c0565e00a0a0a029503020 + 350055502100001a000000ff00573855 + 3230343930303130340a000000fd0018 + 4b0f5a1e000a202020202020000000fc + 00565032373638610a2020202020017b + 020322f155901f05145a5904131e1d0f + 0e07061211161503020123097f078301 + 0000023a801871382d40582c45005550 + 2100001e011d8018711c1620582c2500 + 55502100009e023a80d072382d40102c + 458055502100001e011d007251d01e20 + 6e28550055502100001e584d00b8a138 + 1440f82c4b0055502100001e000000d2 + BorderDimensions: 4 + supported: 4 + Border: 0 0 0 0 + range: (0, 65535) + SignalFormat: DisplayPort + supported: DisplayPort + ConnectorType: DisplayPort + ConnectorNumber: 4 + _ConnectorLocation: 4 + non-desktop: 0 + supported: 0, 1 + 2560x1440 (0x1c3) 241.500MHz +HSync -VSync *current +preferred + h: width 2560 start 2608 end 2640 total 2720 skew 0 clock 88.79KHz + v: height 1440 start 1443 end 1448 total 1481 clock 59.95Hz + 2560x1080 (0x1c4) 198.000MHz +HSync +VSync + h: width 2560 start 2808 end 2852 total 3000 skew 0 clock 66.00KHz + v: height 1080 start 1084 end 1095 total 1100 clock 60.00Hz + 2560x1080 (0x1c5) 197.800MHz +HSync +VSync + h: width 2560 start 2808 end 2852 total 3000 skew 0 clock 65.93KHz + v: height 1080 start 1084 end 1089 total 1100 clock 59.94Hz + 2560x1080 (0x1c6) 185.630MHz +HSync +VSync + h: width 2560 start 3108 end 3152 total 3300 skew 0 clock 56.25KHz + v: height 1080 start 1084 end 1089 total 1125 clock 50.00Hz + 2048x1152 (0x1c7) 162.000MHz +HSync +VSync + h: width 2048 start 2074 end 2154 total 2250 skew 0 clock 72.00KHz + v: height 1152 start 1153 end 1156 total 1200 clock 60.00Hz + 1920x1200 (0x1c8) 193.250MHz -HSync +VSync + h: width 1920 start 2056 end 2256 total 2592 skew 0 clock 74.56KHz + v: height 1200 start 1203 end 1209 total 1245 clock 59.88Hz + 1920x1080 (0x1c9) 148.500MHz +HSync +VSync + h: width 1920 start 2008 end 2052 total 2200 skew 0 clock 67.50KHz + v: height 1080 start 1084 end 1089 total 1125 clock 60.00Hz + 1920x1080 (0x1ca) 148.350MHz +HSync +VSync + h: width 1920 start 2008 end 2052 total 2200 skew 0 clock 67.43KHz + v: height 1080 start 1084 end 1089 total 1125 clock 59.94Hz + 1920x1080 (0x1cb) 148.500MHz +HSync +VSync + h: width 1920 start 2448 end 2492 total 2640 skew 0 clock 56.25KHz + v: height 1080 start 1084 end 1089 total 1125 clock 50.00Hz + 1680x1050 (0x1cc) 146.250MHz -HSync +VSync + h: width 1680 start 1784 end 1960 total 2240 skew 0 clock 65.29KHz + v: height 1050 start 1053 end 1059 total 1089 clock 59.95Hz + 1600x1200 (0x1cd) 162.000MHz +HSync +VSync + h: width 1600 start 1664 end 1856 total 2160 skew 0 clock 75.00KHz + v: height 1200 start 1201 end 1204 total 1250 clock 60.00Hz + 1440x576 (0x1ce) 54.000MHz -HSync +VSync + h: width 1440 start 1464 end 1592 total 1728 skew 0 clock 31.25KHz + v: height 576 start 581 end 586 total 625 clock 50.00Hz + 1440x480 (0x1cf) 54.000MHz -HSync -VSync + h: width 1440 start 1472 end 1596 total 1716 skew 0 clock 31.47KHz + v: height 480 start 489 end 495 total 525 clock 59.94Hz + 1280x1024 (0x1d0) 135.000MHz +HSync +VSync + h: width 1280 start 1296 end 1440 total 1688 skew 0 clock 79.98KHz + v: height 1024 start 1025 end 1028 total 1066 clock 75.02Hz + 1280x1024 (0x1d1) 108.000MHz +HSync +VSync + h: width 1280 start 1328 end 1440 total 1688 skew 0 clock 63.98KHz + v: height 1024 start 1025 end 1028 total 1066 clock 60.02Hz + 1280x800 (0x1d2) 83.500MHz -HSync +VSync + h: width 1280 start 1352 end 1480 total 1680 skew 0 clock 49.70KHz + v: height 800 start 803 end 809 total 831 clock 59.81Hz + 1280x720 (0x1d3) 74.250MHz +HSync +VSync + h: width 1280 start 1390 end 1430 total 1650 skew 0 clock 45.00KHz + v: height 720 start 725 end 730 total 750 clock 60.00Hz + 1280x720 (0x1d4) 74.180MHz +HSync +VSync + h: width 1280 start 1390 end 1430 total 1650 skew 0 clock 44.96KHz + v: height 720 start 725 end 730 total 750 clock 59.94Hz + 1280x720 (0x1d5) 74.250MHz +HSync +VSync + h: width 1280 start 1720 end 1760 total 1980 skew 0 clock 37.50KHz + v: height 720 start 725 end 730 total 750 clock 50.00Hz + 1024x768 (0x1d6) 78.750MHz +HSync +VSync + h: width 1024 start 1040 end 1136 total 1312 skew 0 clock 60.02KHz + v: height 768 start 769 end 772 total 800 clock 75.03Hz + 1024x768 (0x1d7) 75.000MHz -HSync -VSync + h: width 1024 start 1048 end 1184 total 1328 skew 0 clock 56.48KHz + v: height 768 start 771 end 777 total 806 clock 70.07Hz + 1024x768 (0x1d8) 65.000MHz -HSync -VSync + h: width 1024 start 1048 end 1184 total 1344 skew 0 clock 48.36KHz + v: height 768 start 771 end 777 total 806 clock 60.00Hz + 800x600 (0x1d9) 49.500MHz +HSync +VSync + h: width 800 start 816 end 896 total 1056 skew 0 clock 46.88KHz + v: height 600 start 601 end 604 total 625 clock 75.00Hz + 800x600 (0x1da) 50.000MHz +HSync +VSync + h: width 800 start 856 end 976 total 1040 skew 0 clock 48.08KHz + v: height 600 start 637 end 643 total 666 clock 72.19Hz + 800x600 (0x1db) 40.000MHz +HSync +VSync + h: width 800 start 840 end 968 total 1056 skew 0 clock 37.88KHz + v: height 600 start 601 end 605 total 628 clock 60.32Hz + 800x600 (0x1dc) 36.000MHz +HSync +VSync + h: width 800 start 824 end 896 total 1024 skew 0 clock 35.16KHz + v: height 600 start 601 end 603 total 625 clock 56.25Hz + 720x576 (0x1dd) 27.000MHz -HSync -VSync + h: width 720 start 732 end 796 total 864 skew 0 clock 31.25KHz + v: height 576 start 581 end 586 total 625 clock 50.00Hz + 720x480 (0x1de) 27.000MHz -HSync -VSync + h: width 720 start 736 end 798 total 858 skew 0 clock 31.47KHz + v: height 480 start 489 end 495 total 525 clock 59.94Hz + 640x480 (0x1df) 31.500MHz -HSync -VSync + h: width 640 start 656 end 720 total 840 skew 0 clock 37.50KHz + v: height 480 start 481 end 484 total 500 clock 75.00Hz + 640x480 (0x1e0) 31.500MHz -HSync -VSync + h: width 640 start 656 end 696 total 832 skew 0 clock 37.86KHz + v: height 480 start 481 end 484 total 520 clock 72.81Hz + 640x480 (0x1e1) 25.175MHz -HSync -VSync + h: width 640 start 656 end 752 total 800 skew 0 clock 31.47KHz + v: height 480 start 490 end 492 total 525 clock 59.94Hz + 640x480 (0x1e2) 25.170MHz -HSync -VSync + h: width 640 start 656 end 752 total 800 skew 0 clock 31.46KHz + v: height 480 start 490 end 492 total 525 clock 59.93Hz +DP-5 disconnected (normal left inverted right x axis y axis) + Identifier: 0x1e3 + Timestamp: 744876 + Subpixel: unknown + Clones: + CRTCs: 0 1 2 3 + Transform: 1.000000 0.000000 0.000000 + 0.000000 1.000000 0.000000 + 0.000000 0.000000 1.000000 + filter: + 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 + BorderDimensions: 4 + supported: 4 + Border: 0 0 0 0 + range: (0, 65535) + SignalFormat: TMDS + supported: TMDS + ConnectorType: DisplayPort + ConnectorNumber: 4 + _ConnectorLocation: 4 + non-desktop: 0 + supported: 0, 1 +HDMI-1-1 disconnected (normal left inverted right x axis y axis) + Identifier: 0x206 + Timestamp: 20565 + Subpixel: unknown + Clones: + CRTCs: 4 5 6 7 + Transform: 1.000000 0.000000 0.000000 + 0.000000 1.000000 0.000000 + 0.000000 0.000000 1.000000 + filter: + PRIME Synchronization: 1 + supported: 0, 1 + HDCP Content Type: HDCP Type0 + supported: HDCP Type0, HDCP Type1 + Content Protection: Undesired + supported: Undesired, Desired, Enabled + vrr_capable: 0 + range: (0, 1) + Colorspace: Default + supported: Default, BT709_YCC, opRGB, BT2020_RGB, BT2020_YCC + max bpc: 16 + range: (8, 16) + underscan vborder: 0 + range: (0, 128) + underscan hborder: 0 + range: (0, 128) + underscan: off + supported: off, on, auto + scaling mode: None + supported: None, Full, Center, Full aspect + 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: 93 + supported: 93 + non-desktop: 0 + range: (0, 1) +DP-1-1 disconnected (normal left inverted right x axis y axis) + Identifier: 0x207 + Timestamp: 20565 + Subpixel: unknown + Clones: + CRTCs: 4 5 6 7 + Transform: 1.000000 0.000000 0.000000 + 0.000000 1.000000 0.000000 + 0.000000 0.000000 1.000000 + filter: + PRIME Synchronization: 1 + supported: 0, 1 + subconnector: Unknown + supported: Unknown, VGA, DVI-D, HDMI, DP, Wireless, Native + HDCP Content Type: HDCP Type0 + supported: HDCP Type0, HDCP Type1 + Content Protection: Undesired + supported: Undesired, Desired, Enabled + vrr_capable: 0 + range: (0, 1) + Colorspace: Default + supported: Default, BT709_YCC, opRGB, BT2020_RGB, BT2020_YCC + max bpc: 16 + range: (8, 16) + underscan vborder: 0 + range: (0, 128) + underscan hborder: 0 + range: (0, 128) + underscan: off + supported: off, on, auto + scaling mode: None + supported: None, Full, Center, Full aspect + 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: 100 + supported: 100 + non-desktop: 0 + range: (0, 1) diff --git a/tests/data/xrandr_output_3.txt b/tests/data/xrandr_output_3.txt new file mode 100644 index 00000000..821117bb --- /dev/null +++ b/tests/data/xrandr_output_3.txt @@ -0,0 +1,336 @@ +Screen 0: minimum 320 x 200, current 3840 x 2160, maximum 16384 x 16384 +DisplayPort-0 disconnected (normal left inverted right x axis y axis) + Identifier: 0x54 + Timestamp: 7125 + Subpixel: unknown + Clones: + CRTCs: 0 1 2 3 4 5 + Transform: 1.000000 0.000000 0.000000 + 0.000000 1.000000 0.000000 + 0.000000 0.000000 1.000000 + filter: + GAMMA_LUT_SIZE: 4096 + range: (0, -1) + DEGAMMA_LUT_SIZE: 4096 + range: (0, -1) + GAMMA_LUT: 0 + range: (0, 65535) + CTM: 0 + DEGAMMA_LUT: 0 + range: (0, 65535) + TearFree: auto + supported: off, on, auto + subconnector: Unknown + supported: Unknown, VGA, DVI-D, HDMI, DP, Wireless, Native + vrr_capable: 0 + range: (0, 1) + max bpc: 8 + range: (8, 16) + underscan vborder: 0 + range: (0, 128) + underscan hborder: 0 + range: (0, 128) + underscan: off + supported: off, on, auto + scaling mode: None + supported: None, Full, Center, Full aspect + link-status: Good + supported: Good, Bad + CONNECTOR_ID: 65 + supported: 65 + non-desktop: 0 + range: (0, 1) +DisplayPort-1 disconnected (normal left inverted right x axis y axis) + Identifier: 0x55 + Timestamp: 7125 + Subpixel: unknown + Clones: + CRTCs: 0 1 2 3 4 5 + Transform: 1.000000 0.000000 0.000000 + 0.000000 1.000000 0.000000 + 0.000000 0.000000 1.000000 + filter: + GAMMA_LUT_SIZE: 4096 + range: (0, -1) + DEGAMMA_LUT_SIZE: 4096 + range: (0, -1) + GAMMA_LUT: 0 + range: (0, 65535) + CTM: 0 + DEGAMMA_LUT: 0 + range: (0, 65535) + TearFree: auto + supported: off, on, auto + subconnector: Unknown + supported: Unknown, VGA, DVI-D, HDMI, DP, Wireless, Native + vrr_capable: 0 + range: (0, 1) + max bpc: 8 + range: (8, 16) + underscan vborder: 0 + range: (0, 128) + underscan hborder: 0 + range: (0, 128) + underscan: off + supported: off, on, auto + scaling mode: None + supported: None, Full, Center, Full aspect + link-status: Good + supported: Good, Bad + CONNECTOR_ID: 70 + supported: 70 + non-desktop: 0 + range: (0, 1) +HDMI-A-0 connected primary 3840x2160+0+0 (0x5a) normal (normal left inverted right x axis y axis) 941mm x 529mm + Identifier: 0x56 + Timestamp: 7125 + Subpixel: unknown + Gamma: 1.0:1.0:1.0 + Brightness: 1.0 + Clones: + CRTC: 0 + CRTCs: 0 1 2 3 4 5 + Transform: 1.000000 0.000000 0.000000 + 0.000000 1.000000 0.000000 + 0.000000 0.000000 1.000000 + filter: + EDID: + 00ffffffffffff0004725805436e6072 + 1a1b0103805e35782aa191a9544d9c26 + 0f5054bfef80714f8140818081c08100 + 9500b300d1c04dd000a0f0703e803020 + 3500ad113200001a565e00a0a0a02950 + 2f203500ad113200001a000000fd0032 + 3c1e8c3c000a202020202020000000fc + 00416365722045543433304b0a200129 + 020341f1506101600304121305141f10 + 0706026b5f23090707830100006b030c + 002000383c2000200167d85dc4017880 + 00e305e001e40f050000e60607016060 + 45023a801871382d40582c4500ad1132 + 00001e011d007251d01e206e285500ad + 113200001e8c0ad08a20e02d10103e96 + 00ad1132000018000000000000000088 + GAMMA_LUT_SIZE: 4096 + range: (0, -1) + DEGAMMA_LUT_SIZE: 4096 + range: (0, -1) + GAMMA_LUT: 0 + range: (0, 65535) + CTM: 0 1 0 0 0 0 0 0 0 1 0 0 0 0 0 0 + 0 1 + DEGAMMA_LUT: 0 + range: (0, 65535) + TearFree: auto + supported: off, on, auto + vrr_capable: 0 + range: (0, 1) + max bpc: 8 + range: (8, 16) + underscan vborder: 0 + range: (0, 128) + underscan hborder: 0 + range: (0, 128) + underscan: off + supported: off, on, auto + scaling mode: None + supported: None, Full, Center, Full aspect + link-status: Good + supported: Good, Bad + CONNECTOR_ID: 74 + supported: 74 + non-desktop: 0 + range: (0, 1) + 3840x2160 (0x5a) 533.250MHz +HSync -VSync *current +preferred + h: width 3840 start 3888 end 3920 total 4000 skew 0 clock 133.31KHz + v: height 2160 start 2163 end 2168 total 2222 clock 60.00Hz + 3840x2160 (0x5b) 594.000MHz +HSync +VSync + h: width 3840 start 4016 end 4104 total 4400 skew 0 clock 135.00KHz + v: height 2160 start 2168 end 2178 total 2250 clock 60.00Hz + 3840x2160 (0x5c) 594.000MHz +HSync +VSync + h: width 3840 start 4896 end 4984 total 5280 skew 0 clock 112.50KHz + v: height 2160 start 2168 end 2178 total 2250 clock 50.00Hz + 3840x2160 (0x5d) 593.407MHz +HSync +VSync + h: width 3840 start 4016 end 4104 total 4400 skew 0 clock 134.87KHz + v: height 2160 start 2168 end 2178 total 2250 clock 59.94Hz + 3840x2160 (0x5e) 297.000MHz +HSync +VSync + h: width 3840 start 4016 end 4104 total 4400 skew 0 clock 67.50KHz + v: height 2160 start 2168 end 2178 total 2250 clock 30.00Hz + 3840x2160 (0x5f) 296.703MHz +HSync +VSync + h: width 3840 start 4016 end 4104 total 4400 skew 0 clock 67.43KHz + v: height 2160 start 2168 end 2178 total 2250 clock 29.97Hz + 2560x1440 (0x60) 241.500MHz +HSync -VSync + h: width 2560 start 2607 end 2639 total 2720 skew 0 clock 88.79KHz + v: height 1440 start 1443 end 1448 total 1481 clock 59.95Hz + 1920x1200 (0x61) 533.250MHz +HSync -VSync + h: width 1920 start 3888 end 3920 total 4000 skew 0 clock 133.31KHz + v: height 1200 start 2163 end 2168 total 2222 clock 60.00Hz + 1920x1080 (0x62) 148.500MHz +HSync +VSync + h: width 1920 start 2008 end 2052 total 2200 skew 0 clock 67.50KHz + v: height 1080 start 1084 end 1089 total 1125 clock 60.00Hz + 1920x1080 (0x63) 148.500MHz +HSync +VSync + h: width 1920 start 2448 end 2492 total 2640 skew 0 clock 56.25KHz + v: height 1080 start 1084 end 1089 total 1125 clock 50.00Hz + 1920x1080 (0x64) 148.352MHz +HSync +VSync + h: width 1920 start 2008 end 2052 total 2200 skew 0 clock 67.43KHz + v: height 1080 start 1084 end 1089 total 1125 clock 59.94Hz + 1600x1200 (0x65) 533.250MHz +HSync -VSync + h: width 1600 start 3888 end 3920 total 4000 skew 0 clock 133.31KHz + v: height 1200 start 2163 end 2168 total 2222 clock 60.00Hz + 1680x1050 (0x66) 119.000MHz +HSync -VSync + h: width 1680 start 1728 end 1760 total 1840 skew 0 clock 64.67KHz + v: height 1050 start 1053 end 1059 total 1080 clock 59.88Hz + 1280x1024 (0x67) 135.000MHz +HSync +VSync + h: width 1280 start 1296 end 1440 total 1688 skew 0 clock 79.98KHz + v: height 1024 start 1025 end 1028 total 1066 clock 75.02Hz + 1280x1024 (0x68) 108.000MHz +HSync +VSync + h: width 1280 start 1328 end 1440 total 1688 skew 0 clock 63.98KHz + v: height 1024 start 1025 end 1028 total 1066 clock 60.02Hz + 1440x900 (0x69) 88.750MHz +HSync -VSync + h: width 1440 start 1488 end 1520 total 1600 skew 0 clock 55.47KHz + v: height 900 start 903 end 909 total 926 clock 59.90Hz + 1280x960 (0x6a) 108.000MHz +HSync +VSync + h: width 1280 start 1376 end 1488 total 1800 skew 0 clock 60.00KHz + v: height 960 start 961 end 964 total 1000 clock 60.00Hz + 1280x800 (0x6b) 71.000MHz +HSync -VSync + h: width 1280 start 1328 end 1360 total 1440 skew 0 clock 49.31KHz + v: height 800 start 803 end 809 total 823 clock 59.91Hz + 1152x864 (0x6c) 108.000MHz +HSync +VSync + h: width 1152 start 1216 end 1344 total 1600 skew 0 clock 67.50KHz + v: height 864 start 865 end 868 total 900 clock 75.00Hz + 1280x720 (0x6d) 74.250MHz +HSync +VSync + h: width 1280 start 1390 end 1430 total 1650 skew 0 clock 45.00KHz + v: height 720 start 725 end 730 total 750 clock 60.00Hz + 1280x720 (0x6e) 74.250MHz +HSync +VSync + h: width 1280 start 1720 end 1760 total 1980 skew 0 clock 37.50KHz + v: height 720 start 725 end 730 total 750 clock 50.00Hz + 1280x720 (0x6f) 74.176MHz +HSync +VSync + h: width 1280 start 1390 end 1430 total 1650 skew 0 clock 44.96KHz + v: height 720 start 725 end 730 total 750 clock 59.94Hz + 1024x768 (0x70) 78.750MHz +HSync +VSync + h: width 1024 start 1040 end 1136 total 1312 skew 0 clock 60.02KHz + v: height 768 start 769 end 772 total 800 clock 75.03Hz + 1024x768 (0x71) 75.000MHz -HSync -VSync + h: width 1024 start 1048 end 1184 total 1328 skew 0 clock 56.48KHz + v: height 768 start 771 end 777 total 806 clock 70.07Hz + 1024x768 (0x72) 65.000MHz -HSync -VSync + h: width 1024 start 1048 end 1184 total 1344 skew 0 clock 48.36KHz + v: height 768 start 771 end 777 total 806 clock 60.00Hz + 832x624 (0x73) 57.284MHz -HSync -VSync + h: width 832 start 864 end 928 total 1152 skew 0 clock 49.73KHz + v: height 624 start 625 end 628 total 667 clock 74.55Hz + 800x600 (0x74) 50.000MHz +HSync +VSync + h: width 800 start 856 end 976 total 1040 skew 0 clock 48.08KHz + v: height 600 start 637 end 643 total 666 clock 72.19Hz + 800x600 (0x75) 49.500MHz +HSync +VSync + h: width 800 start 816 end 896 total 1056 skew 0 clock 46.88KHz + v: height 600 start 601 end 604 total 625 clock 75.00Hz + 800x600 (0x76) 40.000MHz +HSync +VSync + h: width 800 start 840 end 968 total 1056 skew 0 clock 37.88KHz + v: height 600 start 601 end 605 total 628 clock 60.32Hz + 800x600 (0x77) 36.000MHz +HSync +VSync + h: width 800 start 824 end 896 total 1024 skew 0 clock 35.16KHz + v: height 600 start 601 end 603 total 625 clock 56.25Hz + 720x576 (0x78) 27.000MHz -HSync -VSync + h: width 720 start 732 end 796 total 864 skew 0 clock 31.25KHz + v: height 576 start 581 end 586 total 625 clock 50.00Hz + 720x480 (0x79) 27.027MHz -HSync -VSync + h: width 720 start 736 end 798 total 858 skew 0 clock 31.50KHz + v: height 480 start 489 end 495 total 525 clock 60.00Hz + 720x480 (0x7a) 27.000MHz -HSync -VSync + h: width 720 start 736 end 798 total 858 skew 0 clock 31.47KHz + v: height 480 start 489 end 495 total 525 clock 59.94Hz + 640x480 (0x7b) 31.500MHz -HSync -VSync + h: width 640 start 656 end 720 total 840 skew 0 clock 37.50KHz + v: height 480 start 481 end 484 total 500 clock 75.00Hz + 640x480 (0x7c) 31.500MHz -HSync -VSync + h: width 640 start 664 end 704 total 832 skew 0 clock 37.86KHz + v: height 480 start 489 end 492 total 520 clock 72.81Hz + 640x480 (0x7d) 30.240MHz -HSync -VSync + h: width 640 start 704 end 768 total 864 skew 0 clock 35.00KHz + v: height 480 start 483 end 486 total 525 clock 66.67Hz + 640x480 (0x7e) 25.200MHz -HSync -VSync + h: width 640 start 656 end 752 total 800 skew 0 clock 31.50KHz + v: height 480 start 490 end 492 total 525 clock 60.00Hz + 640x480 (0x7f) 25.175MHz -HSync -VSync + h: width 640 start 656 end 752 total 800 skew 0 clock 31.47KHz + v: height 480 start 490 end 492 total 525 clock 59.94Hz + 720x400 (0x80) 28.320MHz -HSync +VSync + h: width 720 start 738 end 846 total 900 skew 0 clock 31.47KHz + v: height 400 start 412 end 414 total 449 clock 70.08Hz +HDMI-A-1 disconnected (normal left inverted right x axis y axis) + Identifier: 0x57 + Timestamp: 7125 + Subpixel: unknown + Clones: + CRTCs: 0 1 2 3 4 5 + Transform: 1.000000 0.000000 0.000000 + 0.000000 1.000000 0.000000 + 0.000000 0.000000 1.000000 + filter: + GAMMA_LUT_SIZE: 4096 + range: (0, -1) + DEGAMMA_LUT_SIZE: 4096 + range: (0, -1) + GAMMA_LUT: 0 + range: (0, 65535) + CTM: 0 + DEGAMMA_LUT: 0 + range: (0, 65535) + TearFree: auto + supported: off, on, auto + vrr_capable: 0 + range: (0, 1) + max bpc: 8 + range: (8, 16) + underscan vborder: 0 + range: (0, 128) + underscan hborder: 0 + range: (0, 128) + underscan: off + supported: off, on, auto + scaling mode: None + supported: None, Full, Center, Full aspect + link-status: Good + supported: Good, Bad + CONNECTOR_ID: 79 + supported: 79 + non-desktop: 0 + range: (0, 1) +DVI-D-0 disconnected (normal left inverted right x axis y axis) + Identifier: 0x58 + Timestamp: 7125 + Subpixel: unknown + Clones: + CRTCs: 0 1 2 3 4 5 + Transform: 1.000000 0.000000 0.000000 + 0.000000 1.000000 0.000000 + 0.000000 0.000000 1.000000 + filter: + GAMMA_LUT_SIZE: 4096 + range: (0, -1) + DEGAMMA_LUT_SIZE: 4096 + range: (0, -1) + GAMMA_LUT: 0 + range: (0, 65535) + CTM: 0 + DEGAMMA_LUT: 0 + range: (0, 65535) + TearFree: auto + supported: off, on, auto + max bpc: 8 + range: (8, 16) + underscan vborder: 0 + range: (0, 128) + underscan hborder: 0 + range: (0, 128) + underscan: off + supported: off, on, auto + scaling mode: None + supported: None, Full, Center, Full aspect + link-status: Good + supported: Good, Bad + CONNECTOR_ID: 83 + supported: 83 + non-desktop: 0 + range: (0, 1) diff --git a/tests/test_edid.py b/tests/test_edid.py index a6bcf197..723f9f6c 100644 --- a/tests/test_edid.py +++ b/tests/test_edid.py @@ -7,7 +7,7 @@ from DisplayCAL import config, RealDisplaySizeMM from DisplayCAL.dev.mocks import check_call from tests.data.display_data import DisplayData -from DisplayCAL.edid import parse_edid, parse_manufacturer_id +from DisplayCAL.edid import get_edid, parse_edid, parse_manufacturer_id @pytest.mark.skipif(sys.platform == "darwin", reason="Not working as expected on MacOS") @@ -121,6 +121,196 @@ def test_get_edid_2(): # ) +@pytest.mark.parametrize( + "xrandr_data_file_name,dispwin_data_file_name,getcfg_displays_output,display_no,expected_result", + [ + [ + "xrandr_output_1.txt", + "dispwin_output_1.txt", + ["DP-4 @ 0, 0, 2560x1440 [PRIMARY]"], + 0, + { + "edid": b"00ffffffffffff005a633a7a0f010101311e0104b53c22783bb091ab524ea0260f5054bfef80e1c0d100d1c0b300a9408180810081c0565e00a0a0a029503020350055502100001a000000ff005738553230343930303130340a000000fd00184b0f5a1e000a202020202020000000fc00565032373638610a2020202020017b020322f155901f05145a5904131e1d0f0e07061211161503020123097f0783010000023a801871382d40582c450055502100001e011d8018711c1620582c250055502100009e023a80d072382d40102c458055502100001e011d007251d01e206e28550055502100001e584d00b8a1381440f82c4b0055502100001e000000d2", + "hash": "aee2b726b409d9d54ed5924ad309781d", + "header": b"00ffffff", + "manufacturer_id": "YSF", + "product_id": 26214, + "serial_32": 808478310, + "week_of_manufacture": 53, + "year_of_manufacture": 2087, + "edid_version": 54, + "edid_revision": 51, + "max_h_size_cm": 97, + "max_v_size_cm": 55, + "gamma": 1.97, + "features": 48, + "red_x": 0.1923828125, + "red_y": 0.189453125, + "green_x": 0.1923828125, + "green_y": 0.189453125, + "blue_x": 0.19140625, + "blue_y": 0.2021484375, + "white_x": 0.19140625, + "white_y": 0.19140625, + "ext_flag": 50, + "checksum": 48, + "checksum_valid": False, + }, + ], + [ + "xrandr_output_2.txt", + "dispwin_output_2.txt", + ["DP-4 @ 0, 0, 2560x1440 [PRIMARY]", "DP-2 @ 2160, 0, 3840x2160"], + 0, + { + "edid": b"00ffffffffffff005a633a7a0f010101311e0104b53c22783bb091ab524ea0260f5054bfef80e1c0d100d1c0b300a9408180810081c0565e00a0a0a029503020350055502100001a000000ff005738553230343930303130340a000000fd00184b0f5a1e000a202020202020000000fc00565032373638610a2020202020017b020322f155901f05145a5904131e1d0f0e07061211161503020123097f0783010000023a801871382d40582c450055502100001e011d8018711c1620582c250055502100009e023a80d072382d40102c458055502100001e011d007251d01e206e28550055502100001e584d00b8a1381440f82c4b0055502100001e000000d2", + "hash": "aee2b726b409d9d54ed5924ad309781d", + "header": b"00ffffff", + "manufacturer_id": "YSF", + "product_id": 26214, + "serial_32": 808478310, + "week_of_manufacture": 53, + "year_of_manufacture": 2087, + "edid_version": 54, + "edid_revision": 51, + "max_h_size_cm": 97, + "max_v_size_cm": 55, + "gamma": 1.97, + "features": 48, + "red_x": 0.1923828125, + "red_y": 0.189453125, + "green_x": 0.1923828125, + "green_y": 0.189453125, + "blue_x": 0.19140625, + "blue_y": 0.2021484375, + "white_x": 0.19140625, + "white_y": 0.19140625, + "ext_flag": 50, + "checksum": 48, + "checksum_valid": False, + }, + ], + [ + "xrandr_output_2.txt", + "dispwin_output_2.txt", + ["DP-4 @ 0, 0, 2560x1440 [PRIMARY]", "DP-2 @ 2160, 0, 3840x2160"], + 1, + { + "edid": b"00ffffffffffff004c2d4d0c46584d30231a0104b53d23783a5fb1a2574fa2280f5054bfef80714f810081c08180a9c0b300950001014dd000a0f0703e80302035005f592100001a000000fd00384b1e873c000a202020202020000000fc00553238453539300a2020202020000000ff00485450483930303130330a2020016602030ef041102309070783010000023a801871382d40582c45005f592100001e565e00a0a0a02950302035005f592100001a04740030f2705a80b0588a005f592100001e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000052", + "hash": "ce204468c25bc6df152fba3b1237c286", + "header": b"00ffffff", + "manufacturer_id": "YSF", + "product_id": 26214, + "serial_32": 808478310, + "week_of_manufacture": 52, + "year_of_manufacture": 2089, + "edid_version": 50, + "edid_revision": 100, + "max_h_size_cm": 100, + "max_v_size_cm": 48, + "gamma": 1.99, + "features": 52, + "red_x": 0.21875, + "red_y": 0.2060546875, + "green_x": 0.3916015625, + "green_y": 0.201171875, + "blue_x": 0.1875, + "blue_y": 0.1982421875, + "white_x": 0.2001953125, + "white_y": 0.1923828125, + "ext_flag": 50, + "checksum": 48, + "checksum_valid": False, + }, + ], + [ + "xrandr_output_3.txt", + "dispwin_output_3.txt", + ["HDMI-A-0 @ 0, 0, 3840x2160 [PRIMARY]"], + 0, + { + "edid": b"00ffffffffffff0004725805436e60721a1b0103805e35782aa191a9544d9c260f5054bfef80714f8140818081c081009500b300d1c04dd000a0f0703e8030203500ad113200001a565e00a0a0a029502f203500ad113200001a000000fd00323c1e8c3c000a202020202020000000fc00416365722045543433304b0a200129020341f1506101600304121305141f100706026b5f23090707830100006b030c002000383c2000200167d85dc401788000e305e001e40f050000e6060701606045023a801871382d40582c4500ad113200001e011d007251d01e206e285500ad113200001e8c0ad08a20e02d10103e9600ad1132000018000000000000000088", + "hash": "2f78783c69d1a435d655b34dc64c2b51", + "header": b"00ffffff", + "manufacturer_id": "YSF", + "product_id": 26214, + "serial_32": 808478310, + "week_of_manufacture": 48, + "year_of_manufacture": 2042, + "edid_version": 55, + "edid_revision": 50, + "max_h_size_cm": 56, + "max_v_size_cm": 48, + "gamma": 1.53, + "features": 52, + "red_x": 0.39453125, + "red_y": 0.2138671875, + "green_x": 0.1875, + "green_y": 0.2177734375, + "blue_x": 0.1953125, + "blue_y": 0.1943359375, + "white_x": 0.3798828125, + "white_y": 0.193359375, + "ext_flag": 50, + "checksum": 48, + "checksum_valid": False, + }, + ], + ], +) +def test_get_edid_4( + monkeypatch, + patch_subprocess, + patch_argyll_util, + data_files, + xrandr_data_file_name, + dispwin_data_file_name, + getcfg_displays_output, + display_no, + expected_result, +): + """DisplayCAL.edid.get_edid() gets the EDID data from xrandr --verbose command.""" + monkeypatch.setattr("DisplayCAL.edid.subprocess", patch_subprocess) + monkeypatch.setattr("DisplayCAL.RealDisplaySizeMM.subprocess", patch_subprocess) + monkeypatch.setattr("DisplayCAL.edid.sys.platform", "linux") + from DisplayCAL import RealDisplaySizeMM + + # reset displays + RealDisplaySizeMM._displays = None + + # patch xrandr + with open(data_files[xrandr_data_file_name], "rb") as xrandr_data_file: + xrandr_data = xrandr_data_file.read() + patch_subprocess.output["xrandr--verbose"] = xrandr_data + + # patch dispwin + with open(data_files[dispwin_data_file_name], "rb") as dispwin_data_file: + dispwin_data = dispwin_data_file.read() + patch_subprocess.output["dispwin-v-d0"] = dispwin_data + + # patch RealDisplaySizeMM.getcfg("displays") + from DisplayCAL.config import getcfg + + orig_getcfg = getcfg + + def patched_getcfg(config_value): + if config_value == "displays": + return getcfg_displays_output + [ + "Web @ localhost", + "madVR", + "Prisma", + "Resolve", + "Untethered", + ] + else: + return orig_getcfg(config_value) + + monkeypatch.setattr("DisplayCAL.config.getcfg", patched_getcfg) + + result = get_edid(display_no=display_no) + assert result == expected_result + + def test_parse_edid_1(): """Testing DisplayCAL.edid.parse_edid() function.""" raw_edid = ( diff --git a/tests/test_real_display_size_mm.py b/tests/test_real_display_size_mm.py index 9120878a..f300e658 100644 --- a/tests/test_real_display_size_mm.py +++ b/tests/test_real_display_size_mm.py @@ -18,65 +18,21 @@ @pytest.fixture(scope="function") -def patch_subprocess(monkeypatch): - """Patch subprocess. - - Yields: - Any: The patched subprocess class. - """ - class Process: - def __init__(self, output=None): - self.output = output - - def communicate(self): - return self.output, None - - class PatchedSubprocess: - passed_args = [] - passed_kwargs = {} - PIPE = None - output = None - - @classmethod - def Popen(cls, *args, **kwargs): - cls.passed_args += args - cls.passed_kwargs.update(kwargs) - process = Process(output=cls.output) - return process - - monkeypatch.setattr( - "DisplayCAL.RealDisplaySizeMM.subprocess", PatchedSubprocess - ) - yield PatchedSubprocess +def clear_displays(): + """Clear RealDisplaySizeMM._displays.""" + RealDisplaySizeMM._displays = None + assert RealDisplaySizeMM._displays is None @pytest.fixture(scope="function") -def patch_argyll_util(monkeypatch): - """Patch argyll. - - Yields: - Any: The patched argyll class. - """ - - class PatchedArgyll: - passed_util_name = [] - - @classmethod - def get_argyll_util(cls, util_name): - cls.passed_util_name.append(util_name) - return "/some/path/to/argyll_v3.3.0/bin/dispwin" - - monkeypatch.setattr( - "DisplayCAL.RealDisplaySizeMM.argyll", PatchedArgyll - ) - - yield PatchedArgyll +def patch_subprocess_on_rdsmm(monkeypatch, patch_subprocess): + """Patch DisplayCAL.RealDisplaySizeMM.subprocess to return whatever we want.""" + monkeypatch.setattr("DisplayCAL.RealDisplaySizeMM.subprocess", patch_subprocess) + yield patch_subprocess -def test_real_display_size_mm(): +def test_real_display_size_mm(clear_displays): """Test RealDisplaySizeMM() function.""" - RealDisplaySizeMM._displays = None - assert RealDisplaySizeMM._displays is None with check_call( RealDisplaySizeMM, "_enumerate_displays", DisplayData.enumerate_displays() ): @@ -87,10 +43,8 @@ def test_real_display_size_mm(): assert display_size[1] > 1 -def test_xrandr_output_x_id_1(): +def test_xrandr_output_x_id_1(clear_displays): """Test GetXRandROutputXID() function.""" - RealDisplaySizeMM._displays = None - assert RealDisplaySizeMM._displays is None with check_call( RealDisplaySizeMM, "_enumerate_displays", DisplayData.enumerate_displays() ): @@ -99,10 +53,8 @@ def test_xrandr_output_x_id_1(): assert result != 0 -def test_enumerate_displays(): +def test_enumerate_displays(clear_displays): """Test enumerate_displays() function.""" - RealDisplaySizeMM._displays = None - assert RealDisplaySizeMM._displays is None with check_call( RealDisplaySizeMM, "_enumerate_displays", DisplayData.enumerate_displays() ): @@ -127,7 +79,7 @@ def test_enumerate_displays(): assert RealDisplaySizeMM._displays is not None -def test__enumerate_displays_dispwin_path_is_none(monkeypatch): +def test__enumerate_displays_dispwin_path_is_none(monkeypatch, clear_displays): """_enumerate_displays() dispwin path is None returns empty list.""" monkeypatch.setattr( "DisplayCAL.RealDisplaySizeMM.argyll.get_argyll_util", lambda x: None @@ -136,10 +88,12 @@ def test__enumerate_displays_dispwin_path_is_none(monkeypatch): assert result == [] -def test__enumerate_displays_uses_argyll_dispwin(patch_subprocess, patch_argyll_util): +def test__enumerate_displays_uses_argyll_dispwin( + patch_subprocess_on_rdsmm, patch_argyll_util +): """_enumerate_displays() uses dispwin.""" - PatchedSubprocess = patch_subprocess - PatchedSubprocess.output = DisplayData.DISPWIN_OUTPUT_1 + PatchedSubprocess = patch_subprocess_on_rdsmm + PatchedSubprocess.output["dispwin-v-d0"] = DisplayData.DISPWIN_OUTPUT_1 PatchedArgyll = patch_argyll_util assert PatchedSubprocess.passed_args == [] assert PatchedSubprocess.passed_kwargs == {} @@ -151,75 +105,93 @@ def test__enumerate_displays_uses_argyll_dispwin(patch_subprocess, patch_argyll_ assert PatchedSubprocess.passed_args[0][2] == "-d0" -def test__enumerate_displays_uses_argyll_dispwin_output_1(patch_subprocess, patch_argyll_util): +def test__enumerate_displays_uses_argyll_dispwin_output_1( + clear_displays, patch_subprocess_on_rdsmm, patch_argyll_util +): """_enumerate_displays() uses dispwin.""" - patch_subprocess.output = DisplayData.DISPWIN_OUTPUT_1 + patch_subprocess_on_rdsmm.output["dispwin-v-d0"] = DisplayData.DISPWIN_OUTPUT_1 result = RealDisplaySizeMM._enumerate_displays() assert isinstance(result, list) assert len(result) == 1 - assert result[0]["description"] == "Built-in Retina Display, at 0, 0, width 1728, height 1117 (Primary Display)" - assert result[0]["name"] == "Built-in Retina Display" + assert ( + result[0]["description"] + == b"Built-in Retina Display, at 0, 0, width 1728, height 1117 (Primary Display)" + ) + assert result[0]["name"] == b"Built-in Retina Display" assert result[0]["size"] == (1728, 1117) assert result[0]["pos"] == (0, 0) -def test__enumerate_displays_uses_argyll_dispwin_output_2(patch_subprocess, patch_argyll_util): +def test__enumerate_displays_uses_argyll_dispwin_output_2( + patch_subprocess_on_rdsmm, patch_argyll_util +): """_enumerate_displays() uses dispwin.""" - patch_subprocess.output = DisplayData.DISPWIN_OUTPUT_2 + patch_subprocess_on_rdsmm.output["dispwin-v-d0"] = DisplayData.DISPWIN_OUTPUT_2 result = RealDisplaySizeMM._enumerate_displays() assert isinstance(result, list) assert len(result) == 2 - assert result[0]["description"] == "Built-in Retina Display, at 0, 0, width 1728, height 1117 (Primary Display)" - assert result[0]["name"] == "Built-in Retina Display" + assert ( + result[0]["description"] + == b"Built-in Retina Display, at 0, 0, width 1728, height 1117 (Primary Display)" + ) + assert result[0]["name"] == b"Built-in Retina Display" assert result[0]["size"] == (1728, 1117) assert result[0]["pos"] == (0, 0) - assert result[1]["description"] == "DELL U2720Q, at 1728, -575, width 3008, height 1692" - assert result[1]["name"] == "DELL U2720Q" + assert ( + result[1]["description"] + == b"DELL U2720Q, at 1728, -575, width 3008, height 1692" + ) + assert result[1]["name"] == b"DELL U2720Q" assert result[1]["size"] == (3008, 1692) assert result[1]["pos"] == (1728, -575) -def test__enumerate_displays_without_a_proper_dispwin_output_missing_lines(patch_subprocess, patch_argyll_util): +def test__enumerate_displays_without_a_proper_dispwin_output_missing_lines( + patch_subprocess_on_rdsmm, patch_argyll_util +): """_enumerate_displays() return empty list when dispwin returns no usable data.""" - patch_subprocess.output = DisplayData.DISPWIN_OUTPUT_3 + patch_subprocess_on_rdsmm.output["dispwin-v-d0"] = DisplayData.DISPWIN_OUTPUT_3 result = RealDisplaySizeMM._enumerate_displays() assert isinstance(result, list) assert len(result) == 0 -def test__enumerate_displays_without_a_proper_dispwin_output_with_wrong_formatted_data(patch_subprocess, patch_argyll_util): +def test__enumerate_displays_without_a_proper_dispwin_output_with_wrong_formatted_data( + patch_subprocess_on_rdsmm, patch_argyll_util +): """_enumerate_displays() return empty list when dispwin returns no usable data.""" from DisplayCAL import localization as lang + lang.init() - patch_subprocess.output = DisplayData.DISPWIN_OUTPUT_4 + patch_subprocess_on_rdsmm.output["dispwin-v-d0"] = DisplayData.DISPWIN_OUTPUT_4 with pytest.raises(ValueError) as cm: result = RealDisplaySizeMM._enumerate_displays() assert str(cm.value) == ( - 'An internal error occurred.\n' - 'Error code: -1\n' - 'Error message: dispwin returns no usable data while enumerating displays.' + "An internal error occurred.\n" + "Error code: -1\n" + "Error message: dispwin returns no usable data while enumerating displays." ) -def test__enumerate_displays_without_a_proper_dispwin_output_with_partial_match(patch_subprocess, patch_argyll_util): +def test__enumerate_displays_without_a_proper_dispwin_output_with_partial_match( + patch_subprocess_on_rdsmm, patch_argyll_util +): """_enumerate_displays() return empty list when dispwin returns no usable data.""" from DisplayCAL import localization as lang + lang.init() - patch_subprocess.output = DisplayData.DISPWIN_OUTPUT_5 + patch_subprocess_on_rdsmm.output["dispwin-v-d0"] = DisplayData.DISPWIN_OUTPUT_5 with pytest.raises(ValueError) as cm: result = RealDisplaySizeMM._enumerate_displays() assert str(cm.value) == ( - 'An internal error occurred.\n' - 'Error code: -1\n' - 'Error message: dispwin returns no usable data while enumerating displays.' + "An internal error occurred.\n" + "Error code: -1\n" + "Error message: dispwin returns no usable data while enumerating displays." ) - -def test_get_display(): +def test_get_display(clear_displays): """Test DisplayCAL.RealDisplaySizeMM.get_display() function.""" - RealDisplaySizeMM._displays = None - assert RealDisplaySizeMM._displays is None with check_call( RealDisplaySizeMM, "_enumerate_displays", DisplayData.enumerate_displays() ): @@ -229,10 +201,8 @@ def test_get_display(): assert isinstance(display, dict) -def test_get_x_display(): +def test_get_x_display(clear_displays): """Test DisplayCAL.RealDisplaySizeMM.get_x_display() function.""" - RealDisplaySizeMM._displays = None - assert RealDisplaySizeMM._displays is None with check_call( RealDisplaySizeMM, "_enumerate_displays", DisplayData.enumerate_displays() ): @@ -249,10 +219,8 @@ def test_get_x_display(): RealDisplaySizeMM.get_x_icc_profile_output_atom_id, ), ) -def test_get_x_icc_profile_atom_id(function) -> None: +def test_get_x_icc_profile_atom_id(clear_displays, function) -> None: """Test DisplayCAL.RealDisplaySizeMM.get_x_icc_profile_atom_id() function.""" - RealDisplaySizeMM._displays = None - assert RealDisplaySizeMM._displays is None with check_call( RealDisplaySizeMM, "_enumerate_displays", DisplayData.enumerate_displays() ): @@ -269,3 +237,29 @@ def test_get_wayland_display(monkeypatch: MonkeyPatch) -> None: display = RealDisplaySizeMM.get_wayland_display(0, 0, 0, 0) assert display["xrandr_name"] == "DP-2" assert display["size_mm"] == (597, 336) + + +def test_get_dispwin_output_dispwin_path_is_none_returns_empty_bytes( + clear_displays, monkeypatch +): + """get_dispwin_output() argyll.get_argyll_util("dispwin") returns None.""" + + def patched_get_argyll_util(*args): + return None + + monkeypatch.setattr( + "DisplayCAL.RealDisplaySizeMM.argyll.get_argyll_util", patched_get_argyll_util + ) + assert RealDisplaySizeMM.get_dispwin_output() is b"" + + +def test_get_dispwin_output_returns_dispwin_output_as_bytes( + clear_displays, data_files, patch_subprocess +): + """get_dispwin_output() returns bytes.""" + # patch dispwin + dispwin_data_file_name = "dispwin_output_1.txt" + with open(data_files[dispwin_data_file_name], "rb") as dispwin_data_file: + dispwin_data = dispwin_data_file.read() + patch_subprocess.output["dispwin-v-d0"] = dispwin_data + assert isinstance(RealDisplaySizeMM.get_dispwin_output(), bytes) From dc8472045a6cdccb9f29257889d3cdd0d12dfac1 Mon Sep 17 00:00:00 2001 From: Erkan Ozgur Yilmaz Date: Mon, 21 Oct 2024 15:32:15 +0100 Subject: [PATCH 08/34] [#16] Removed compiling of the C-Extension from the `pytest` GitHub action. --- .github/workflows/pytest.yml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index 71825f78..e1676e6b 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -95,14 +95,12 @@ jobs: pip install -r requirements-tests.txt pip install -r requirements-dev.txt - - name: Compile C-Extensions + - name: Build DisplayCAL run: | sudo chmod a+rw /etc/udev/rules.d python3 -m build pip install dist/DisplayCAL-*.whl - export PYPATH=`which python` - export PYDIR=`dirname $PYPATH` - cp $PYDIR/../lib/python${{ matrix.python-version }}/site-packages/DisplayCAL/lib64/python${{ env.py_version }}/RealDisplaySizeMM.*.so ./DisplayCAL/lib64/python${{ env.py_version }}/ + - name: Test with pytest run: | python -m pytest --verbose --cov=. --cov-report html From 4ea66d9f07efd5901c9bb4d61f94cd1859415094 Mon Sep 17 00:00:00 2001 From: Erkan Ozgur Yilmaz Date: Mon, 21 Oct 2024 16:31:20 +0100 Subject: [PATCH 09/34] [#16] Fix `tests.test_colord.test_device_id_from_edid_1()` to use predefined test data. --- tests/test_colord.py | 43 ++++++++++++++++++++++++++++++++++++------- 1 file changed, 36 insertions(+), 7 deletions(-) diff --git a/tests/test_colord.py b/tests/test_colord.py index c7eb5047..6a6fdec0 100644 --- a/tests/test_colord.py +++ b/tests/test_colord.py @@ -7,16 +7,45 @@ import pytest -@pytest.mark.skipif(sys.platform == "darwin", reason="not working properly under MacOS") def test_device_id_from_edid_1(): """Testing DisplayCAL.colord.device_id_from_edid() function.""" from DisplayCAL.colord import device_id_from_edid - with check_call(config, "getcfg", DisplayData.CFG_DATA, call_count=2): - with check_call( - RealDisplaySizeMM, "_enumerate_displays", DisplayData.enumerate_displays() - ): - edid = get_edid(0) + edid = { + "edid": b"00ffffffffffff005a633a7a0f010101311e0104b53c22783bb091ab524ea0260f505" + b"4bfef80e1c0d100d1c0b300a9408180810081c0565e00a0a0a02950302035005550210000" + b"1a000000ff005738553230343930303130340a000000fd00184b0f5a1e000a20202020202" + b"0000000fc00565032373638610a2020202020017b020322f155901f05145a5904131e1d0f" + b"0e07061211161503020123097f0783010000023a801871382d40582c450055502100001e0" + b"11d8018711c1620582c250055502100009e023a80d072382d40102c458055502100001e01" + b"1d007251d01e206e28550055502100001e584d00b8a1381440f82c4b0055502100" + b"001e000000d2", + "hash": "aee2b726b409d9d54ed5924ad309781d", + "header": b"00ffffff", + "manufacturer_id": "YSF", + "product_id": 26214, + "serial_32": 808478310, + "week_of_manufacture": 53, + "year_of_manufacture": 2087, + "edid_version": 54, + "edid_revision": 51, + "max_h_size_cm": 97, + "max_v_size_cm": 55, + "gamma": 1.97, + "features": 48, + "red_x": 0.1923828125, + "red_y": 0.189453125, + "green_x": 0.1923828125, + "green_y": 0.189453125, + "blue_x": 0.19140625, + "blue_y": 0.2021484375, + "white_x": 0.19140625, + "white_y": 0.19140625, + "ext_flag": 50, + "checksum": 48, + "checksum_valid": False, + } + device_id = device_id_from_edid(edid) assert isinstance(device_id, str) - assert device_id != "" + assert device_id == "xrandr-808478310" From e5b8cd1fcae239a89ada7cf633129da1ed77fa5e Mon Sep 17 00:00:00 2001 From: Erkan Ozgur Yilmaz Date: Mon, 21 Oct 2024 16:32:27 +0100 Subject: [PATCH 10/34] [#16] Update import statements in `tests.test_colord`. --- tests/test_colord.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_colord.py b/tests/test_colord.py index 6a6fdec0..6862a028 100644 --- a/tests/test_colord.py +++ b/tests/test_colord.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- import sys from DisplayCAL import RealDisplaySizeMM, config +from DisplayCAL.colord import device_id_from_edid from DisplayCAL.dev.mocks import check_call from DisplayCAL.edid import get_edid from tests.data.display_data import DisplayData @@ -9,7 +10,6 @@ def test_device_id_from_edid_1(): """Testing DisplayCAL.colord.device_id_from_edid() function.""" - from DisplayCAL.colord import device_id_from_edid edid = { "edid": b"00ffffffffffff005a633a7a0f010101311e0104b53c22783bb091ab524ea0260f505" From a61a2e1ca2fcd49eae1e06e108a364cfa852c392 Mon Sep 17 00:00:00 2001 From: Erkan Ozgur Yilmaz Date: Tue, 22 Oct 2024 11:41:58 +0100 Subject: [PATCH 11/34] [#16] Dropped a comment about why we are using `dispwin` command with `-d0` argument in `DisplayCAL.RealDisplaySizeMM`. --- DisplayCAL/RealDisplaySizeMM.py | 3 +++ tests/conftest.py | 8 +++++++ tests/test_edid.py | 34 ++++++++++-------------------- tests/test_real_display_size_mm.py | 7 ------ 4 files changed, 22 insertions(+), 30 deletions(-) diff --git a/DisplayCAL/RealDisplaySizeMM.py b/DisplayCAL/RealDisplaySizeMM.py index 392452c3..4d3abdb0 100644 --- a/DisplayCAL/RealDisplaySizeMM.py +++ b/DisplayCAL/RealDisplaySizeMM.py @@ -168,6 +168,9 @@ def get_dispwin_output() -> bytes: else: startupinfo = None + # the invalid value of "-d0" is intentional, + # we just want to get the information we want and close the dispwin, + # if we don't supply "-d0" it will start showing color patches. p = subprocess.Popen( [dispwin_path, "-v", "-d0"], stdin=subprocess.PIPE, diff --git a/tests/conftest.py b/tests/conftest.py index c083acf2..b9067c7c 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -10,6 +10,7 @@ import tempfile import DisplayCAL +from DisplayCAL import RealDisplaySizeMM from DisplayCAL.config import setcfg from DisplayCAL.argyll import get_argyll_latest_version @@ -192,3 +193,10 @@ def get_argyll_util(cls, util_name): monkeypatch.setattr("DisplayCAL.RealDisplaySizeMM.argyll", PatchedArgyll) yield PatchedArgyll + + +@pytest.fixture(scope="function") +def clear_displays(): + """Clear RealDisplaySizeMM._displays.""" + RealDisplaySizeMM._displays = None + assert RealDisplaySizeMM._displays is None \ No newline at end of file diff --git a/tests/test_edid.py b/tests/test_edid.py index 723f9f6c..24d7b38f 100644 --- a/tests/test_edid.py +++ b/tests/test_edid.py @@ -5,18 +5,15 @@ import pytest from DisplayCAL import config, RealDisplaySizeMM +from DisplayCAL.config import getcfg from DisplayCAL.dev.mocks import check_call from tests.data.display_data import DisplayData from DisplayCAL.edid import get_edid, parse_edid, parse_manufacturer_id @pytest.mark.skipif(sys.platform == "darwin", reason="Not working as expected on MacOS") -def test_get_edid_1(): +def test_get_edid_1(clear_displays): """Testing DisplayCAL.colord.device_id_from_edid() function.""" - from DisplayCAL.edid import get_edid - - RealDisplaySizeMM._displays = None - assert RealDisplaySizeMM._displays is None with check_call( config, "getcfg", @@ -85,21 +82,10 @@ def test_get_edid_1(): assert isinstance(result["year_of_manufacture"], int) -def test_get_edid_2(): - """Testing DisplayCAL.colord.device_id_from_edid() function.""" - from DisplayCAL.edid import parse_edid - - edid = DisplayData.DISPLAY_DATA_2["edid"] - result = parse_edid(edid) - - assert result == DisplayData.DISPLAY_DATA_2 -# def test_get_edid_3(): +# def test_get_edid_3(clear_displays): # """Testing DisplayCAL.colord.device_id_from_edid() function.""" -# from DisplayCAL import RealDisplaySizeMM as RDSMM -# from DisplayCAL import config -# # config.initcfg() # display = RDSMM.get_display(0) # edid = display.get("edid") @@ -262,6 +248,7 @@ def test_get_edid_4( monkeypatch, patch_subprocess, patch_argyll_util, + clear_displays, data_files, xrandr_data_file_name, dispwin_data_file_name, @@ -273,10 +260,6 @@ def test_get_edid_4( monkeypatch.setattr("DisplayCAL.edid.subprocess", patch_subprocess) monkeypatch.setattr("DisplayCAL.RealDisplaySizeMM.subprocess", patch_subprocess) monkeypatch.setattr("DisplayCAL.edid.sys.platform", "linux") - from DisplayCAL import RealDisplaySizeMM - - # reset displays - RealDisplaySizeMM._displays = None # patch xrandr with open(data_files[xrandr_data_file_name], "rb") as xrandr_data_file: @@ -289,8 +272,6 @@ def test_get_edid_4( patch_subprocess.output["dispwin-v-d0"] = dispwin_data # patch RealDisplaySizeMM.getcfg("displays") - from DisplayCAL.config import getcfg - orig_getcfg = getcfg def patched_getcfg(config_value): @@ -645,6 +626,13 @@ def test_parse_edid_5(): assert result == expected_result +def test_parse_edid_6(): + """parse_edid() with test data.""" + edid = DisplayData.DISPLAY_DATA_2["edid"] + result = parse_edid(edid) + assert result == DisplayData.DISPLAY_DATA_2 + + def test_parse_manufacturer_id_1(): """Test parse_manufacturer_id.""" manufacturer_id_raw = b"\x10\xac" diff --git a/tests/test_real_display_size_mm.py b/tests/test_real_display_size_mm.py index f300e658..bc054aae 100644 --- a/tests/test_real_display_size_mm.py +++ b/tests/test_real_display_size_mm.py @@ -17,13 +17,6 @@ pass -@pytest.fixture(scope="function") -def clear_displays(): - """Clear RealDisplaySizeMM._displays.""" - RealDisplaySizeMM._displays = None - assert RealDisplaySizeMM._displays is None - - @pytest.fixture(scope="function") def patch_subprocess_on_rdsmm(monkeypatch, patch_subprocess): """Patch DisplayCAL.RealDisplaySizeMM.subprocess to return whatever we want.""" From 31a9ebdb6133ae934201f74c9f7ae5e5b7a49505 Mon Sep 17 00:00:00 2001 From: Erkan Ozgur Yilmaz Date: Tue, 22 Oct 2024 12:15:43 +0100 Subject: [PATCH 12/34] [#16] Updated `pytest.yml` to output colors and run in parallel. --- .github/workflows/pytest.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index e1676e6b..80ed1b35 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -103,7 +103,7 @@ jobs: - name: Test with pytest run: | - python -m pytest --verbose --cov=. --cov-report html + python -m pytest --verbose -n auto -W ignore --color=yes --cov=. --cov-report html - name: Archive code coverage results uses: actions/upload-artifact@v4 From 1cfec787f3dca08fa03f5bb85afd344944c3b23a Mon Sep 17 00:00:00 2001 From: Erkan Ozgur Yilmaz Date: Tue, 22 Oct 2024 12:47:18 +0100 Subject: [PATCH 13/34] [#16] Updated `DisplayCAL.edid.get_edid()` to use the `which` utility to get the `xrandr` executable path under Linux. --- DisplayCAL/edid.py | 3 ++- tests/test_edid.py | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/DisplayCAL/edid.py b/DisplayCAL/edid.py index 53408551..8997cc44 100644 --- a/DisplayCAL/edid.py +++ b/DisplayCAL/edid.py @@ -33,6 +33,7 @@ from DisplayCAL import config from DisplayCAL import RealDisplaySizeMM as RDSMM +from DisplayCAL.util_os import which from DisplayCAL.util_str import make_ascii_printable, safe_str, strtr if sys.platform == "win32": @@ -214,7 +215,7 @@ def get_edid(display_no=0, display_name=None, device=None): if not display: return {} - p = subprocess.Popen(["xrandr", "--verbose"], stdout=subprocess.PIPE) + p = subprocess.Popen([which("xrandr"), "--verbose"], stdout=subprocess.PIPE) stdout, stderr = p.communicate() if not stdout: diff --git a/tests/test_edid.py b/tests/test_edid.py index 24d7b38f..0ae706ab 100644 --- a/tests/test_edid.py +++ b/tests/test_edid.py @@ -260,6 +260,7 @@ def test_get_edid_4( monkeypatch.setattr("DisplayCAL.edid.subprocess", patch_subprocess) monkeypatch.setattr("DisplayCAL.RealDisplaySizeMM.subprocess", patch_subprocess) monkeypatch.setattr("DisplayCAL.edid.sys.platform", "linux") + monkeypatch.setattr("DisplayCAL.edid.which", lambda x: "xrandr") # patch xrandr with open(data_files[xrandr_data_file_name], "rb") as xrandr_data_file: From 6b4dcbe52ede6d572af181fbaf2eb25f09f00dfa Mon Sep 17 00:00:00 2001 From: Erkan Ozgur Yilmaz Date: Tue, 22 Oct 2024 13:21:39 +0100 Subject: [PATCH 14/34] [#16] Added some debug prints for GitHub tests. --- .github/workflows/pytest.yml | 7 ++++--- DisplayCAL/RealDisplaySizeMM.py | 4 ++++ requirements-dev.txt | 2 +- requirements-tests.txt | 6 +++--- requirements.txt | 2 +- 5 files changed, 13 insertions(+), 8 deletions(-) diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index 80ed1b35..2c365fbc 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -86,9 +86,10 @@ jobs: python3 -m pip install --upgrade pip pip install wheel - - name: Install wxPython ${{ matrix.wx-version }} - run: | - pip install wxPython==${{ matrix.wx-version }} + # Disabling temporarily + # - name: Install wxPython ${{ matrix.wx-version }} + # run: | + # pip install wxPython==${{ matrix.wx-version }} - name: Install Python dependencies run: | diff --git a/DisplayCAL/RealDisplaySizeMM.py b/DisplayCAL/RealDisplaySizeMM.py index 4d3abdb0..639939fa 100644 --- a/DisplayCAL/RealDisplaySizeMM.py +++ b/DisplayCAL/RealDisplaySizeMM.py @@ -242,10 +242,14 @@ def get_display(display_no=0): from DisplayCAL.config import getcfg, is_virtual_display if is_virtual_display(display_no): + print("This is a virtual display!") return try: + print(f'getcfg("displays"): {getcfg("displays")}') + print(f"display_no : {display_no}") argyll_display = getcfg("displays")[display_no] + print(f"argyll_display : {argyll_display}") except IndexError: return else: diff --git a/requirements-dev.txt b/requirements-dev.txt index 971af080..7aedd3e5 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -19,4 +19,4 @@ pytest-github-actions-annotate-failures pytest-xdist snowballstemmer twine -wheel +wheel \ No newline at end of file diff --git a/requirements-tests.txt b/requirements-tests.txt index f9b66864..fa0676b5 100644 --- a/requirements-tests.txt +++ b/requirements-tests.txt @@ -6,6 +6,6 @@ numpy Pillow PyChromecast Send2Trash -wxPython>=4.2.2;python_version>="3.12" -wxPython>=4.1.1;python_version<"3.12" -zeroconf +# wxPython>=4.2.2;python_version>="3.12" +# wxPython>=4.1.1;python_version<"3.12" +zeroconf \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 2c832dc7..2526d37c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,5 +8,5 @@ Pillow PyChromecast pywin32; sys_platform=='win32' Send2Trash -wxPython>=4.2.2 +# wxPython>=4.2.2 zeroconf From e2d697493668b367b39774bb5c667db6170a21e9 Mon Sep 17 00:00:00 2001 From: Erkan Ozgur Yilmaz Date: Tue, 22 Oct 2024 13:27:58 +0100 Subject: [PATCH 15/34] [#16] Removed `wxPython` from `setup.py` for debugging purposes of GitHub actions. --- DisplayCAL/setup.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/DisplayCAL/setup.py b/DisplayCAL/setup.py index 986130c7..424d1112 100644 --- a/DisplayCAL/setup.py +++ b/DisplayCAL/setup.py @@ -936,9 +936,9 @@ def findall( if not setuptools or sys.platform != "win32": # wxPython windows installer doesn't add egg-info entry, so # a dependency check from pkg_resources would always fail - requires.append( - "wxPython (>= {})".format(".".join(str(n) for n in wx_minversion)) - ) + # requires.append( + # "wxPython (>= {})".format(".".join(str(n) for n in wx_minversion)) + # ) if sys.platform == "win32": requires.append("pywin32 (>= 213.0)") From 9b66ae97ccec2d4bf56e5b1e8662acb825732113 Mon Sep 17 00:00:00 2001 From: Erkan Ozgur Yilmaz Date: Tue, 22 Oct 2024 13:31:52 +0100 Subject: [PATCH 16/34] [#16] Fix typo in `setup.py` after fix for debugging purposes of GitHub actions. --- DisplayCAL/setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/DisplayCAL/setup.py b/DisplayCAL/setup.py index 424d1112..e3f121e4 100644 --- a/DisplayCAL/setup.py +++ b/DisplayCAL/setup.py @@ -939,6 +939,7 @@ def findall( # requires.append( # "wxPython (>= {})".format(".".join(str(n) for n in wx_minversion)) # ) + pass if sys.platform == "win32": requires.append("pywin32 (>= 213.0)") From 9f45f47b1b27709143dfc93f385f91e1562008cd Mon Sep 17 00:00:00 2001 From: Erkan Ozgur Yilmaz Date: Tue, 22 Oct 2024 13:35:45 +0100 Subject: [PATCH 17/34] [#16] Removed `wxPython` from `setup.cfg` for debugging purposes of GitHub actions. --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 544c865f..8ed9c496 100644 --- a/setup.cfg +++ b/setup.cfg @@ -16,7 +16,7 @@ install_requires = PyChromecast pywin32; sys_platform=='win32' Send2Trash - wxPython + # wxPython zeroconf [bdist_wheel] From bcdb5ac68a9f516ae251f23d8d6a554e072d5846 Mon Sep 17 00:00:00 2001 From: Erkan Ozgur Yilmaz Date: Tue, 22 Oct 2024 13:39:22 +0100 Subject: [PATCH 18/34] [#16] Added some debug prints for GitHub tests (2). --- DisplayCAL/RealDisplaySizeMM.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/DisplayCAL/RealDisplaySizeMM.py b/DisplayCAL/RealDisplaySizeMM.py index 639939fa..18a1725c 100644 --- a/DisplayCAL/RealDisplaySizeMM.py +++ b/DisplayCAL/RealDisplaySizeMM.py @@ -235,8 +235,10 @@ def enumerate_displays(): def get_display(display_no=0): + print(f"_displays (1): {_displays}") if _displays is None: enumerate_displays() + print(f"_displays (2): {_displays}") # Translate from Argyll display index to enumerated display index # using the coordinates and dimensions from DisplayCAL.config import getcfg, is_virtual_display @@ -247,7 +249,7 @@ def get_display(display_no=0): try: print(f'getcfg("displays"): {getcfg("displays")}') - print(f"display_no : {display_no}") + print(f"display_no : {display_no}") argyll_display = getcfg("displays")[display_no] print(f"argyll_display : {argyll_display}") except IndexError: From 7438b345e4438f44dd34e6b08ca9e907dd5160ea Mon Sep 17 00:00:00 2001 From: Erkan Ozgur Yilmaz Date: Tue, 22 Oct 2024 13:41:58 +0100 Subject: [PATCH 19/34] [#16] Fix typo in `RealDisplaySizeMM.py` after fix for debugging purposes of GitHub actions. --- DisplayCAL/RealDisplaySizeMM.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DisplayCAL/RealDisplaySizeMM.py b/DisplayCAL/RealDisplaySizeMM.py index 18a1725c..f28a46b4 100644 --- a/DisplayCAL/RealDisplaySizeMM.py +++ b/DisplayCAL/RealDisplaySizeMM.py @@ -249,7 +249,7 @@ def get_display(display_no=0): try: print(f'getcfg("displays"): {getcfg("displays")}') - print(f"display_no : {display_no}") + print(f"display_no : {display_no}") argyll_display = getcfg("displays")[display_no] print(f"argyll_display : {argyll_display}") except IndexError: From 936a62a309fcb2f7e96d94a85f6331502dc1c075 Mon Sep 17 00:00:00 2001 From: Erkan Ozgur Yilmaz Date: Tue, 22 Oct 2024 13:52:54 +0100 Subject: [PATCH 20/34] [#16] Added some debug prints for GitHub tests (3). --- DisplayCAL/RealDisplaySizeMM.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/DisplayCAL/RealDisplaySizeMM.py b/DisplayCAL/RealDisplaySizeMM.py index f28a46b4..784eeb1a 100644 --- a/DisplayCAL/RealDisplaySizeMM.py +++ b/DisplayCAL/RealDisplaySizeMM.py @@ -251,15 +251,20 @@ def get_display(display_no=0): print(f'getcfg("displays"): {getcfg("displays")}') print(f"display_no : {display_no}") argyll_display = getcfg("displays")[display_no] - print(f"argyll_display : {argyll_display}") + print(f"argyll_display (1): {argyll_display}") except IndexError: return else: if argyll_display.endswith(" [PRIMARY]"): argyll_display = " ".join(argyll_display.split(" ")[:-1]) + print(f"argyll_display (2): {argyll_display}") for display in _displays: - if desc := display["description"]: + desc = display["description"] + print(f"desc : {desc}") + if desc: geometry = b"".join(desc.split(b"@ ")[-1:]) + print(f"geometry : {geometry}") + print(f'argyll_display.endswith((b"@ " + geometry).decode("utf-8")): {argyll_display.endswith((b"@ " + geometry).decode("utf-8"))}') if argyll_display.endswith((b"@ " + geometry).decode("utf-8")): return display From 4f856849c9401d3a94f2946acac9edae29ea8a0e Mon Sep 17 00:00:00 2001 From: Erkan Ozgur Yilmaz Date: Tue, 22 Oct 2024 13:53:44 +0100 Subject: [PATCH 21/34] [#16] Added some debug prints for GitHub tests (4). --- DisplayCAL/edid.py | 1 + 1 file changed, 1 insertion(+) diff --git a/DisplayCAL/edid.py b/DisplayCAL/edid.py index 8997cc44..01bab50c 100644 --- a/DisplayCAL/edid.py +++ b/DisplayCAL/edid.py @@ -212,6 +212,7 @@ def get_edid(display_no=0, display_name=None, device=None): return {} else: display = RDSMM.get_display(display_no) + print(f"display (3) : {display}") if not display: return {} From 3aa2dd44a331cce00c7d3f77226677daff52b5da Mon Sep 17 00:00:00 2001 From: Erkan Ozgur Yilmaz Date: Tue, 22 Oct 2024 14:14:38 +0100 Subject: [PATCH 22/34] [#16] Added some debug prints for GitHub tests (5). --- DisplayCAL/RealDisplaySizeMM.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/DisplayCAL/RealDisplaySizeMM.py b/DisplayCAL/RealDisplaySizeMM.py index 784eeb1a..714a98cd 100644 --- a/DisplayCAL/RealDisplaySizeMM.py +++ b/DisplayCAL/RealDisplaySizeMM.py @@ -158,6 +158,7 @@ def get_dispwin_output() -> bytes: bytes: The dispwin output. """ dispwin_path = argyll.get_argyll_util("dispwin") + print(f"dispwin_path: {dispwin_path}") if dispwin_path is None: return b"" @@ -248,9 +249,10 @@ def get_display(display_no=0): return try: - print(f'getcfg("displays"): {getcfg("displays")}') + getcfg_displays = getcfg("displays") + print(f'getcfg("displays"): {getcfg_displays}') print(f"display_no : {display_no}") - argyll_display = getcfg("displays")[display_no] + argyll_display = getcfg_displays[display_no] print(f"argyll_display (1): {argyll_display}") except IndexError: return From 34e5618f8adb344604bd3980121f60309b8d9386 Mon Sep 17 00:00:00 2001 From: Erkan Ozgur Yilmaz Date: Tue, 22 Oct 2024 14:31:00 +0100 Subject: [PATCH 23/34] [#16] Added some debug prints for GitHub tests (6). --- DisplayCAL/RealDisplaySizeMM.py | 49 +++++++++++++++++++-------------- 1 file changed, 28 insertions(+), 21 deletions(-) diff --git a/DisplayCAL/RealDisplaySizeMM.py b/DisplayCAL/RealDisplaySizeMM.py index 714a98cd..2110c94c 100644 --- a/DisplayCAL/RealDisplaySizeMM.py +++ b/DisplayCAL/RealDisplaySizeMM.py @@ -192,6 +192,7 @@ def _enumerate_displays() -> List[dict]: displays = [] has_display = False dispwin_output = get_dispwin_output() + print(f"dispwin_output: {dispwin_output}") for line in dispwin_output.split(b"\n"): if b"-dweb[:port]" in line: break @@ -211,27 +212,33 @@ def enumerate_displays(): _displays = _enumerate_displays() for display in _displays: desc = display["description"] - if desc: - match = re.findall( - rb"(.+?),? at (-?\d+), (-?\d+), width (\d+), height (\d+)", desc - ) - if len(match): - if sys.platform not in ("darwin", "win32"): - if ( - os.getenv("XDG_SESSION_TYPE") == "wayland" - and "pos" in display - and "size" in display - ): - x, y, w, h = display["pos"] + display["size"] - wayland_display = get_wayland_display(x, y, w, h) - if wayland_display: - display.update(wayland_display) - else: - xrandr_name = re.search(rb", Output (.+)", match[0][0]) - if xrandr_name: - display["xrandr_name"] = xrandr_name.group(1) - desc = b"%s @ %s, %s, %sx%s" % match[0] - display["description"] = desc + print(f"desc (1): {desc}") + if not desc: + continue + match = re.findall( + rb"(.+?),? at (-?\d+), (-?\d+), width (\d+), height (\d+)", desc + ) + if not len(match): + continue + + # update xrandr_name from description + if sys.platform not in ("darwin", "win32"): + if ( + os.getenv("XDG_SESSION_TYPE") == "wayland" + and "pos" in display + and "size" in display + ): + x, y, w, h = display["pos"] + display["size"] + wayland_display = get_wayland_display(x, y, w, h) + if wayland_display: + display.update(wayland_display) + else: + xrandr_name = re.search(rb", Output (.+)", match[0][0]) + if xrandr_name: + display["xrandr_name"] = xrandr_name.group(1) + desc = b"%s @ %s, %s, %sx%s" % match[0] + print(f"desc (2): {desc}") + display["description"] = desc return _displays From 4f21015e3d3f7fe441ecb11d8a4880ff458e6eaa Mon Sep 17 00:00:00 2001 From: Erkan Ozgur Yilmaz Date: Tue, 22 Oct 2024 14:43:05 +0100 Subject: [PATCH 24/34] [#16] Added some debug prints for GitHub tests (7). --- DisplayCAL/RealDisplaySizeMM.py | 3 +++ tests/test_edid.py | 1 + 2 files changed, 4 insertions(+) diff --git a/DisplayCAL/RealDisplaySizeMM.py b/DisplayCAL/RealDisplaySizeMM.py index 2110c94c..08d948e0 100644 --- a/DisplayCAL/RealDisplaySizeMM.py +++ b/DisplayCAL/RealDisplaySizeMM.py @@ -209,7 +209,9 @@ def _enumerate_displays() -> List[dict]: def enumerate_displays(): """Enumerate and return a list of displays.""" global _displays + print("calling _enumerate_displays (before)") _displays = _enumerate_displays() + print("calling _enumerate_displays (after)") for display in _displays: desc = display["description"] print(f"desc (1): {desc}") @@ -244,6 +246,7 @@ def enumerate_displays(): def get_display(display_no=0): print(f"_displays (1): {_displays}") + print(f"_displays is None: {_displays is None}") if _displays is None: enumerate_displays() print(f"_displays (2): {_displays}") diff --git a/tests/test_edid.py b/tests/test_edid.py index 0ae706ab..a92ba07d 100644 --- a/tests/test_edid.py +++ b/tests/test_edid.py @@ -259,6 +259,7 @@ def test_get_edid_4( """DisplayCAL.edid.get_edid() gets the EDID data from xrandr --verbose command.""" monkeypatch.setattr("DisplayCAL.edid.subprocess", patch_subprocess) monkeypatch.setattr("DisplayCAL.RealDisplaySizeMM.subprocess", patch_subprocess) + monkeypatch.setattr("DisplayCAL.RealDisplaySizeMM.sys.platform", "linux") monkeypatch.setattr("DisplayCAL.edid.sys.platform", "linux") monkeypatch.setattr("DisplayCAL.edid.which", lambda x: "xrandr") From e8ee1095de3115aecda14a617196ff97e8bdc471 Mon Sep 17 00:00:00 2001 From: Erkan Ozgur Yilmaz Date: Tue, 22 Oct 2024 14:51:57 +0100 Subject: [PATCH 25/34] [#16] Added some debug prints for GitHub tests (8). --- DisplayCAL/RealDisplaySizeMM.py | 1 + tests/data/display_data.py | 1 + 2 files changed, 2 insertions(+) diff --git a/DisplayCAL/RealDisplaySizeMM.py b/DisplayCAL/RealDisplaySizeMM.py index 08d948e0..d0360142 100644 --- a/DisplayCAL/RealDisplaySizeMM.py +++ b/DisplayCAL/RealDisplaySizeMM.py @@ -210,6 +210,7 @@ def enumerate_displays(): """Enumerate and return a list of displays.""" global _displays print("calling _enumerate_displays (before)") + print(f"_enumerate_displays: {_enumerate_displays}") _displays = _enumerate_displays() print("calling _enumerate_displays (after)") for display in _displays: diff --git a/tests/data/display_data.py b/tests/data/display_data.py index 1b2d2378..637c37b6 100644 --- a/tests/data/display_data.py +++ b/tests/data/display_data.py @@ -473,6 +473,7 @@ def Geometry(self): @staticmethod def enumerate_displays() -> List[Dict]: """Return the display data itself.""" + print("DsiplayData.enumerate_displays is getting called!") return [DisplayData.DISPLAY_DATA_1] @staticmethod From 386e24cd90c36ed07fc426f5c913784629fd3341 Mon Sep 17 00:00:00 2001 From: Erkan Ozgur Yilmaz Date: Tue, 22 Oct 2024 15:01:17 +0100 Subject: [PATCH 26/34] [#16] Fixing `check_call()` and `check_call_str()` in `DisplayCAL.dev.mocks` for a possible bug. --- DisplayCAL/dev/mocks.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/DisplayCAL/dev/mocks.py b/DisplayCAL/dev/mocks.py index bf646442..1a6f9a2a 100644 --- a/DisplayCAL/dev/mocks.py +++ b/DisplayCAL/dev/mocks.py @@ -112,10 +112,12 @@ def check_call( # pylint: disable=too-many-arguments ) monkeypatch = MonkeyPatch() calls = _mp_call(monkeypatch, mock_class, method, return_value, as_property) - yield calls - m_name = f"{mock_class.__name__}.{method}" - assert_calls(call_count, call_args_list, call_kwargs_list, calls, m_name) - monkeypatch.undo() + try: + yield calls + finally: + m_name = f"{mock_class.__name__}.{method}" + monkeypatch.undo() + assert_calls(call_count, call_args_list, call_kwargs_list, calls, m_name) # Duplicate the code because overloading is a mess due to this bug: @@ -143,8 +145,8 @@ def check_call_str( # pylint: disable=too-many-arguments calls = _mp_call(monkeypatch, mock_class, return_value, as_property) yield calls m_name = mock_class - assert_calls(call_count, call_args_list, call_kwargs_list, calls, m_name) monkeypatch.undo() + assert_calls(call_count, call_args_list, call_kwargs_list, calls, m_name) def assert_calls( From a36be9ba8ecfe3e4636e4ff9857fd2486e6471e7 Mon Sep 17 00:00:00 2001 From: Erkan Ozgur Yilmaz Date: Tue, 22 Oct 2024 15:56:39 +0100 Subject: [PATCH 27/34] [#16] Fixed `tests.test_edid.test_get_edid_1()` to run properly with the mocked `xrandr` output. --- DisplayCAL/RealDisplaySizeMM.py | 1 + DisplayCAL/edid.py | 8 +- tests/data/display_data.py | 3 +- tests/data/xrandr_output_4.txt | 336 ++++++++++++++++++++++++++++++++ tests/test_edid.py | 24 ++- 5 files changed, 359 insertions(+), 13 deletions(-) create mode 100644 tests/data/xrandr_output_4.txt diff --git a/DisplayCAL/RealDisplaySizeMM.py b/DisplayCAL/RealDisplaySizeMM.py index d0360142..3a3febb6 100644 --- a/DisplayCAL/RealDisplaySizeMM.py +++ b/DisplayCAL/RealDisplaySizeMM.py @@ -212,6 +212,7 @@ def enumerate_displays(): print("calling _enumerate_displays (before)") print(f"_enumerate_displays: {_enumerate_displays}") _displays = _enumerate_displays() + print(f"_displays: {_displays}") print("calling _enumerate_displays (after)") for display in _displays: desc = display["description"] diff --git a/DisplayCAL/edid.py b/DisplayCAL/edid.py index 01bab50c..65d0dc7a 100644 --- a/DisplayCAL/edid.py +++ b/DisplayCAL/edid.py @@ -286,10 +286,10 @@ def get_manufacturer_name(manufacturer_id): "/usr/share/misc/pnp.ids", # pnputils, e.g. Debian "/usr/share/libgnome-desktop/pnp.ids", ] # fallback gnome-desktop - if sys.platform in ("darwin", "win32"): - paths.append(os.path.join(config.pydir, "pnp.ids")) # fallback - # fallback for tests - paths.append(os.path.join(config.pydir, "DisplayCAL", "pnp.ids")) + # if sys.platform in ("darwin", "win32"): + paths.append(os.path.join(config.pydir, "pnp.ids")) # fallback + # fallback for tests + paths.append(os.path.join(config.pydir, "DisplayCAL", "pnp.ids")) for path in paths: if os.path.isfile(path): try: diff --git a/tests/data/display_data.py b/tests/data/display_data.py index 637c37b6..c4c5c8af 100644 --- a/tests/data/display_data.py +++ b/tests/data/display_data.py @@ -8,7 +8,7 @@ class DisplayData: """Sample Display.""" DISPLAY_DATA_1 = { - "name": b":1.0", + "name": b"Monitor 1, Output DP-2", "description": b"Monitor 1, Output DP-2 at 0, 0, width 1280, height 1024", "pos": (0, 0), "size": (1280, 1024), @@ -473,7 +473,6 @@ def Geometry(self): @staticmethod def enumerate_displays() -> List[Dict]: """Return the display data itself.""" - print("DsiplayData.enumerate_displays is getting called!") return [DisplayData.DISPLAY_DATA_1] @staticmethod diff --git a/tests/data/xrandr_output_4.txt b/tests/data/xrandr_output_4.txt new file mode 100644 index 00000000..2f6496b3 --- /dev/null +++ b/tests/data/xrandr_output_4.txt @@ -0,0 +1,336 @@ +Screen 0: minimum 320 x 200, current 3840 x 2160, maximum 16384 x 16384 +DisplayPort-0 disconnected (normal left inverted right x axis y axis) + Identifier: 0x54 + Timestamp: 7125 + Subpixel: unknown + Clones: + CRTCs: 0 1 2 3 4 5 + Transform: 1.000000 0.000000 0.000000 + 0.000000 1.000000 0.000000 + 0.000000 0.000000 1.000000 + filter: + GAMMA_LUT_SIZE: 4096 + range: (0, -1) + DEGAMMA_LUT_SIZE: 4096 + range: (0, -1) + GAMMA_LUT: 0 + range: (0, 65535) + CTM: 0 + DEGAMMA_LUT: 0 + range: (0, 65535) + TearFree: auto + supported: off, on, auto + subconnector: Unknown + supported: Unknown, VGA, DVI-D, HDMI, DP, Wireless, Native + vrr_capable: 0 + range: (0, 1) + max bpc: 8 + range: (8, 16) + underscan vborder: 0 + range: (0, 128) + underscan hborder: 0 + range: (0, 128) + underscan: off + supported: off, on, auto + scaling mode: None + supported: None, Full, Center, Full aspect + link-status: Good + supported: Good, Bad + CONNECTOR_ID: 65 + supported: 65 + non-desktop: 0 + range: (0, 1) +DisplayPort-1 disconnected (normal left inverted right x axis y axis) + Identifier: 0x55 + Timestamp: 7125 + Subpixel: unknown + Clones: + CRTCs: 0 1 2 3 4 5 + Transform: 1.000000 0.000000 0.000000 + 0.000000 1.000000 0.000000 + 0.000000 0.000000 1.000000 + filter: + GAMMA_LUT_SIZE: 4096 + range: (0, -1) + DEGAMMA_LUT_SIZE: 4096 + range: (0, -1) + GAMMA_LUT: 0 + range: (0, 65535) + CTM: 0 + DEGAMMA_LUT: 0 + range: (0, 65535) + TearFree: auto + supported: off, on, auto + subconnector: Unknown + supported: Unknown, VGA, DVI-D, HDMI, DP, Wireless, Native + vrr_capable: 0 + range: (0, 1) + max bpc: 8 + range: (8, 16) + underscan vborder: 0 + range: (0, 128) + underscan hborder: 0 + range: (0, 128) + underscan: off + supported: off, on, auto + scaling mode: None + supported: None, Full, Center, Full aspect + link-status: Good + supported: Good, Bad + CONNECTOR_ID: 70 + supported: 70 + non-desktop: 0 + range: (0, 1) +Monitor 1, Output DP-2 connected primary 1280x1024+0+0 (0x5a) normal (normal left inverted right x axis y axis) 270mm x 338mm + Identifier: 0x56 + Timestamp: 7125 + Subpixel: unknown + Gamma: 1.0:1.0:1.0 + Brightness: 1.0 + Clones: + CRTC: 0 + CRTCs: 0 1 2 3 4 5 + Transform: 1.000000 0.000000 0.000000 + 0.000000 1.000000 0.000000 + 0.000000 0.000000 1.000000 + filter: + EDID: + 00ffffffffffff005a633a7a0f010101 + 311e0104b53c22783bb091ab524ea026 + 0f5054bfef80e1c0d100d1c0b300a940 + 8180810081c0565e00a0a0a029503020 + 350055502100001a000000ff00573855 + 3230343930303130340a000000fd0018 + 4b0f5a1e000a202020202020000000fc + 00565032373638610a2020202020017b + 020322f155901f05145a5904131e1d0f + 0e07061211161503020123097f078301 + 0000023a801871382d40582c45005550 + 2100001e011d8018711c1620582c2500 + 55502100009e023a80d072382d40102c + 458055502100001e011d007251d01e20 + 6e28550055502100001e584d00b8a138 + 1440f82c4b0055502100001e000000d2 + GAMMA_LUT_SIZE: 4096 + range: (0, -1) + DEGAMMA_LUT_SIZE: 4096 + range: (0, -1) + GAMMA_LUT: 0 + range: (0, 65535) + CTM: 0 1 0 0 0 0 0 0 0 1 0 0 0 0 0 0 + 0 1 + DEGAMMA_LUT: 0 + range: (0, 65535) + TearFree: auto + supported: off, on, auto + vrr_capable: 0 + range: (0, 1) + max bpc: 8 + range: (8, 16) + underscan vborder: 0 + range: (0, 128) + underscan hborder: 0 + range: (0, 128) + underscan: off + supported: off, on, auto + scaling mode: None + supported: None, Full, Center, Full aspect + link-status: Good + supported: Good, Bad + CONNECTOR_ID: 74 + supported: 74 + non-desktop: 0 + range: (0, 1) + 3840x2160 (0x5a) 533.250MHz +HSync -VSync *current +preferred + h: width 3840 start 3888 end 3920 total 4000 skew 0 clock 133.31KHz + v: height 2160 start 2163 end 2168 total 2222 clock 60.00Hz + 3840x2160 (0x5b) 594.000MHz +HSync +VSync + h: width 3840 start 4016 end 4104 total 4400 skew 0 clock 135.00KHz + v: height 2160 start 2168 end 2178 total 2250 clock 60.00Hz + 3840x2160 (0x5c) 594.000MHz +HSync +VSync + h: width 3840 start 4896 end 4984 total 5280 skew 0 clock 112.50KHz + v: height 2160 start 2168 end 2178 total 2250 clock 50.00Hz + 3840x2160 (0x5d) 593.407MHz +HSync +VSync + h: width 3840 start 4016 end 4104 total 4400 skew 0 clock 134.87KHz + v: height 2160 start 2168 end 2178 total 2250 clock 59.94Hz + 3840x2160 (0x5e) 297.000MHz +HSync +VSync + h: width 3840 start 4016 end 4104 total 4400 skew 0 clock 67.50KHz + v: height 2160 start 2168 end 2178 total 2250 clock 30.00Hz + 3840x2160 (0x5f) 296.703MHz +HSync +VSync + h: width 3840 start 4016 end 4104 total 4400 skew 0 clock 67.43KHz + v: height 2160 start 2168 end 2178 total 2250 clock 29.97Hz + 2560x1440 (0x60) 241.500MHz +HSync -VSync + h: width 2560 start 2607 end 2639 total 2720 skew 0 clock 88.79KHz + v: height 1440 start 1443 end 1448 total 1481 clock 59.95Hz + 1920x1200 (0x61) 533.250MHz +HSync -VSync + h: width 1920 start 3888 end 3920 total 4000 skew 0 clock 133.31KHz + v: height 1200 start 2163 end 2168 total 2222 clock 60.00Hz + 1920x1080 (0x62) 148.500MHz +HSync +VSync + h: width 1920 start 2008 end 2052 total 2200 skew 0 clock 67.50KHz + v: height 1080 start 1084 end 1089 total 1125 clock 60.00Hz + 1920x1080 (0x63) 148.500MHz +HSync +VSync + h: width 1920 start 2448 end 2492 total 2640 skew 0 clock 56.25KHz + v: height 1080 start 1084 end 1089 total 1125 clock 50.00Hz + 1920x1080 (0x64) 148.352MHz +HSync +VSync + h: width 1920 start 2008 end 2052 total 2200 skew 0 clock 67.43KHz + v: height 1080 start 1084 end 1089 total 1125 clock 59.94Hz + 1600x1200 (0x65) 533.250MHz +HSync -VSync + h: width 1600 start 3888 end 3920 total 4000 skew 0 clock 133.31KHz + v: height 1200 start 2163 end 2168 total 2222 clock 60.00Hz + 1680x1050 (0x66) 119.000MHz +HSync -VSync + h: width 1680 start 1728 end 1760 total 1840 skew 0 clock 64.67KHz + v: height 1050 start 1053 end 1059 total 1080 clock 59.88Hz + 1280x1024 (0x67) 135.000MHz +HSync +VSync + h: width 1280 start 1296 end 1440 total 1688 skew 0 clock 79.98KHz + v: height 1024 start 1025 end 1028 total 1066 clock 75.02Hz + 1280x1024 (0x68) 108.000MHz +HSync +VSync + h: width 1280 start 1328 end 1440 total 1688 skew 0 clock 63.98KHz + v: height 1024 start 1025 end 1028 total 1066 clock 60.02Hz + 1440x900 (0x69) 88.750MHz +HSync -VSync + h: width 1440 start 1488 end 1520 total 1600 skew 0 clock 55.47KHz + v: height 900 start 903 end 909 total 926 clock 59.90Hz + 1280x960 (0x6a) 108.000MHz +HSync +VSync + h: width 1280 start 1376 end 1488 total 1800 skew 0 clock 60.00KHz + v: height 960 start 961 end 964 total 1000 clock 60.00Hz + 1280x800 (0x6b) 71.000MHz +HSync -VSync + h: width 1280 start 1328 end 1360 total 1440 skew 0 clock 49.31KHz + v: height 800 start 803 end 809 total 823 clock 59.91Hz + 1152x864 (0x6c) 108.000MHz +HSync +VSync + h: width 1152 start 1216 end 1344 total 1600 skew 0 clock 67.50KHz + v: height 864 start 865 end 868 total 900 clock 75.00Hz + 1280x720 (0x6d) 74.250MHz +HSync +VSync + h: width 1280 start 1390 end 1430 total 1650 skew 0 clock 45.00KHz + v: height 720 start 725 end 730 total 750 clock 60.00Hz + 1280x720 (0x6e) 74.250MHz +HSync +VSync + h: width 1280 start 1720 end 1760 total 1980 skew 0 clock 37.50KHz + v: height 720 start 725 end 730 total 750 clock 50.00Hz + 1280x720 (0x6f) 74.176MHz +HSync +VSync + h: width 1280 start 1390 end 1430 total 1650 skew 0 clock 44.96KHz + v: height 720 start 725 end 730 total 750 clock 59.94Hz + 1024x768 (0x70) 78.750MHz +HSync +VSync + h: width 1024 start 1040 end 1136 total 1312 skew 0 clock 60.02KHz + v: height 768 start 769 end 772 total 800 clock 75.03Hz + 1024x768 (0x71) 75.000MHz -HSync -VSync + h: width 1024 start 1048 end 1184 total 1328 skew 0 clock 56.48KHz + v: height 768 start 771 end 777 total 806 clock 70.07Hz + 1024x768 (0x72) 65.000MHz -HSync -VSync + h: width 1024 start 1048 end 1184 total 1344 skew 0 clock 48.36KHz + v: height 768 start 771 end 777 total 806 clock 60.00Hz + 832x624 (0x73) 57.284MHz -HSync -VSync + h: width 832 start 864 end 928 total 1152 skew 0 clock 49.73KHz + v: height 624 start 625 end 628 total 667 clock 74.55Hz + 800x600 (0x74) 50.000MHz +HSync +VSync + h: width 800 start 856 end 976 total 1040 skew 0 clock 48.08KHz + v: height 600 start 637 end 643 total 666 clock 72.19Hz + 800x600 (0x75) 49.500MHz +HSync +VSync + h: width 800 start 816 end 896 total 1056 skew 0 clock 46.88KHz + v: height 600 start 601 end 604 total 625 clock 75.00Hz + 800x600 (0x76) 40.000MHz +HSync +VSync + h: width 800 start 840 end 968 total 1056 skew 0 clock 37.88KHz + v: height 600 start 601 end 605 total 628 clock 60.32Hz + 800x600 (0x77) 36.000MHz +HSync +VSync + h: width 800 start 824 end 896 total 1024 skew 0 clock 35.16KHz + v: height 600 start 601 end 603 total 625 clock 56.25Hz + 720x576 (0x78) 27.000MHz -HSync -VSync + h: width 720 start 732 end 796 total 864 skew 0 clock 31.25KHz + v: height 576 start 581 end 586 total 625 clock 50.00Hz + 720x480 (0x79) 27.027MHz -HSync -VSync + h: width 720 start 736 end 798 total 858 skew 0 clock 31.50KHz + v: height 480 start 489 end 495 total 525 clock 60.00Hz + 720x480 (0x7a) 27.000MHz -HSync -VSync + h: width 720 start 736 end 798 total 858 skew 0 clock 31.47KHz + v: height 480 start 489 end 495 total 525 clock 59.94Hz + 640x480 (0x7b) 31.500MHz -HSync -VSync + h: width 640 start 656 end 720 total 840 skew 0 clock 37.50KHz + v: height 480 start 481 end 484 total 500 clock 75.00Hz + 640x480 (0x7c) 31.500MHz -HSync -VSync + h: width 640 start 664 end 704 total 832 skew 0 clock 37.86KHz + v: height 480 start 489 end 492 total 520 clock 72.81Hz + 640x480 (0x7d) 30.240MHz -HSync -VSync + h: width 640 start 704 end 768 total 864 skew 0 clock 35.00KHz + v: height 480 start 483 end 486 total 525 clock 66.67Hz + 640x480 (0x7e) 25.200MHz -HSync -VSync + h: width 640 start 656 end 752 total 800 skew 0 clock 31.50KHz + v: height 480 start 490 end 492 total 525 clock 60.00Hz + 640x480 (0x7f) 25.175MHz -HSync -VSync + h: width 640 start 656 end 752 total 800 skew 0 clock 31.47KHz + v: height 480 start 490 end 492 total 525 clock 59.94Hz + 720x400 (0x80) 28.320MHz -HSync +VSync + h: width 720 start 738 end 846 total 900 skew 0 clock 31.47KHz + v: height 400 start 412 end 414 total 449 clock 70.08Hz +HDMI-A-1 disconnected (normal left inverted right x axis y axis) + Identifier: 0x57 + Timestamp: 7125 + Subpixel: unknown + Clones: + CRTCs: 0 1 2 3 4 5 + Transform: 1.000000 0.000000 0.000000 + 0.000000 1.000000 0.000000 + 0.000000 0.000000 1.000000 + filter: + GAMMA_LUT_SIZE: 4096 + range: (0, -1) + DEGAMMA_LUT_SIZE: 4096 + range: (0, -1) + GAMMA_LUT: 0 + range: (0, 65535) + CTM: 0 + DEGAMMA_LUT: 0 + range: (0, 65535) + TearFree: auto + supported: off, on, auto + vrr_capable: 0 + range: (0, 1) + max bpc: 8 + range: (8, 16) + underscan vborder: 0 + range: (0, 128) + underscan hborder: 0 + range: (0, 128) + underscan: off + supported: off, on, auto + scaling mode: None + supported: None, Full, Center, Full aspect + link-status: Good + supported: Good, Bad + CONNECTOR_ID: 79 + supported: 79 + non-desktop: 0 + range: (0, 1) +DVI-D-0 disconnected (normal left inverted right x axis y axis) + Identifier: 0x58 + Timestamp: 7125 + Subpixel: unknown + Clones: + CRTCs: 0 1 2 3 4 5 + Transform: 1.000000 0.000000 0.000000 + 0.000000 1.000000 0.000000 + 0.000000 0.000000 1.000000 + filter: + GAMMA_LUT_SIZE: 4096 + range: (0, -1) + DEGAMMA_LUT_SIZE: 4096 + range: (0, -1) + GAMMA_LUT: 0 + range: (0, 65535) + CTM: 0 + DEGAMMA_LUT: 0 + range: (0, 65535) + TearFree: auto + supported: off, on, auto + max bpc: 8 + range: (8, 16) + underscan vborder: 0 + range: (0, 128) + underscan hborder: 0 + range: (0, 128) + underscan: off + supported: off, on, auto + scaling mode: None + supported: None, Full, Center, Full aspect + link-status: Good + supported: Good, Bad + CONNECTOR_ID: 83 + supported: 83 + non-desktop: 0 + range: (0, 1) diff --git a/tests/test_edid.py b/tests/test_edid.py index a92ba07d..1daf2f14 100644 --- a/tests/test_edid.py +++ b/tests/test_edid.py @@ -11,22 +11,32 @@ from DisplayCAL.edid import get_edid, parse_edid, parse_manufacturer_id -@pytest.mark.skipif(sys.platform == "darwin", reason="Not working as expected on MacOS") -def test_get_edid_1(clear_displays): +# @pytest.mark.skipif(sys.platform == "darwin", reason="Not working as expected on MacOS") +def test_get_edid_1(clear_displays, monkeypatch, patch_subprocess, data_files): """Testing DisplayCAL.colord.device_id_from_edid() function.""" + # patch xrandr + monkeypatch.setattr("DisplayCAL.edid.subprocess", patch_subprocess) + monkeypatch.setattr("DisplayCAL.edid.sys.platform", "linux") + monkeypatch.setattr("DisplayCAL.edid.which", lambda x: "xrandr") + xrandr_data_file_name = "xrandr_output_4.txt" + with open(data_files[xrandr_data_file_name], "rb") as xrandr_data_file: + xrandr_data = xrandr_data_file.read() + patch_subprocess.output["xrandr--verbose"] = xrandr_data + with check_call( config, "getcfg", DisplayData.CFG_DATA, - call_count=0 if sys.platform == "darwin" else 2, + call_count=-1, ): with check_call( RealDisplaySizeMM, "_enumerate_displays", DisplayData.enumerate_displays(), - call_count=0 if sys.platform == "darwin" else 1, + call_count=-1, ): result = get_edid(0) + assert isinstance(result, dict) assert "blue_x" in result assert isinstance(result["blue_y"], float) @@ -35,7 +45,7 @@ def test_get_edid_1(clear_displays): assert "checksum" in result assert result["checksum"] > 0 assert "checksum_valid" in result - assert result["checksum_valid"] is True + assert result["checksum_valid"] is False assert "edid" in result assert isinstance(result["edid"], bytes) assert "edid_revision" in result @@ -56,8 +66,8 @@ def test_get_edid_1(clear_displays): assert isinstance(result["hash"], str) assert "header" in result assert isinstance(result["header"], bytes) - assert "manufacturer" in result - assert isinstance(result["manufacturer"], str) + assert "manufacturer" not in result + # assert isinstance(result["manufacturer"], str) assert "manufacturer_id" in result assert isinstance(result["manufacturer_id"], str) assert "max_h_size_cm" in result From fbb0f340c9d392baffd7e251a5f78b7a0037cf47 Mon Sep 17 00:00:00 2001 From: Erkan Ozgur Yilmaz Date: Tue, 22 Oct 2024 16:05:13 +0100 Subject: [PATCH 28/34] [#16] Re-enabled building `wxPython` for tests. --- .github/workflows/pytest.yml | 7 +++---- DisplayCAL/setup.py | 7 +++---- requirements-tests.txt | 4 ++-- requirements.txt | 2 +- setup.cfg | 2 +- tests/test_real_display_size_mm.py | 2 +- 6 files changed, 11 insertions(+), 13 deletions(-) diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index 2c365fbc..80ed1b35 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -86,10 +86,9 @@ jobs: python3 -m pip install --upgrade pip pip install wheel - # Disabling temporarily - # - name: Install wxPython ${{ matrix.wx-version }} - # run: | - # pip install wxPython==${{ matrix.wx-version }} + - name: Install wxPython ${{ matrix.wx-version }} + run: | + pip install wxPython==${{ matrix.wx-version }} - name: Install Python dependencies run: | diff --git a/DisplayCAL/setup.py b/DisplayCAL/setup.py index e3f121e4..986130c7 100644 --- a/DisplayCAL/setup.py +++ b/DisplayCAL/setup.py @@ -936,10 +936,9 @@ def findall( if not setuptools or sys.platform != "win32": # wxPython windows installer doesn't add egg-info entry, so # a dependency check from pkg_resources would always fail - # requires.append( - # "wxPython (>= {})".format(".".join(str(n) for n in wx_minversion)) - # ) - pass + requires.append( + "wxPython (>= {})".format(".".join(str(n) for n in wx_minversion)) + ) if sys.platform == "win32": requires.append("pywin32 (>= 213.0)") diff --git a/requirements-tests.txt b/requirements-tests.txt index fa0676b5..9d1149ef 100644 --- a/requirements-tests.txt +++ b/requirements-tests.txt @@ -6,6 +6,6 @@ numpy Pillow PyChromecast Send2Trash -# wxPython>=4.2.2;python_version>="3.12" -# wxPython>=4.1.1;python_version<"3.12" +wxPython>=4.2.2;python_version>="3.12" +wxPython>=4.1.1;python_version<"3.12" zeroconf \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 2526d37c..2c832dc7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,5 +8,5 @@ Pillow PyChromecast pywin32; sys_platform=='win32' Send2Trash -# wxPython>=4.2.2 +wxPython>=4.2.2 zeroconf diff --git a/setup.cfg b/setup.cfg index 8ed9c496..544c865f 100644 --- a/setup.cfg +++ b/setup.cfg @@ -16,7 +16,7 @@ install_requires = PyChromecast pywin32; sys_platform=='win32' Send2Trash - # wxPython + wxPython zeroconf [bdist_wheel] diff --git a/tests/test_real_display_size_mm.py b/tests/test_real_display_size_mm.py index bc054aae..a9e7d70c 100644 --- a/tests/test_real_display_size_mm.py +++ b/tests/test_real_display_size_mm.py @@ -243,7 +243,7 @@ def patched_get_argyll_util(*args): monkeypatch.setattr( "DisplayCAL.RealDisplaySizeMM.argyll.get_argyll_util", patched_get_argyll_util ) - assert RealDisplaySizeMM.get_dispwin_output() is b"" + assert RealDisplaySizeMM.get_dispwin_output() == b"" def test_get_dispwin_output_returns_dispwin_output_as_bytes( From 59106f2ded091268a1a433357551c9caf8b30c78 Mon Sep 17 00:00:00 2001 From: Erkan Ozgur Yilmaz Date: Tue, 22 Oct 2024 16:32:30 +0100 Subject: [PATCH 29/34] [#16] Removed temporary debug messages. --- DisplayCAL/RealDisplaySizeMM.py | 19 ------------------- DisplayCAL/edid.py | 1 - 2 files changed, 20 deletions(-) diff --git a/DisplayCAL/RealDisplaySizeMM.py b/DisplayCAL/RealDisplaySizeMM.py index 3a3febb6..cd7582f4 100644 --- a/DisplayCAL/RealDisplaySizeMM.py +++ b/DisplayCAL/RealDisplaySizeMM.py @@ -158,7 +158,6 @@ def get_dispwin_output() -> bytes: bytes: The dispwin output. """ dispwin_path = argyll.get_argyll_util("dispwin") - print(f"dispwin_path: {dispwin_path}") if dispwin_path is None: return b"" @@ -192,7 +191,6 @@ def _enumerate_displays() -> List[dict]: displays = [] has_display = False dispwin_output = get_dispwin_output() - print(f"dispwin_output: {dispwin_output}") for line in dispwin_output.split(b"\n"): if b"-dweb[:port]" in line: break @@ -209,14 +207,9 @@ def _enumerate_displays() -> List[dict]: def enumerate_displays(): """Enumerate and return a list of displays.""" global _displays - print("calling _enumerate_displays (before)") - print(f"_enumerate_displays: {_enumerate_displays}") _displays = _enumerate_displays() - print(f"_displays: {_displays}") - print("calling _enumerate_displays (after)") for display in _displays: desc = display["description"] - print(f"desc (1): {desc}") if not desc: continue match = re.findall( @@ -241,44 +234,32 @@ def enumerate_displays(): if xrandr_name: display["xrandr_name"] = xrandr_name.group(1) desc = b"%s @ %s, %s, %sx%s" % match[0] - print(f"desc (2): {desc}") display["description"] = desc return _displays def get_display(display_no=0): - print(f"_displays (1): {_displays}") - print(f"_displays is None: {_displays is None}") if _displays is None: enumerate_displays() - print(f"_displays (2): {_displays}") # Translate from Argyll display index to enumerated display index # using the coordinates and dimensions from DisplayCAL.config import getcfg, is_virtual_display if is_virtual_display(display_no): - print("This is a virtual display!") return try: getcfg_displays = getcfg("displays") - print(f'getcfg("displays"): {getcfg_displays}') - print(f"display_no : {display_no}") argyll_display = getcfg_displays[display_no] - print(f"argyll_display (1): {argyll_display}") except IndexError: return else: if argyll_display.endswith(" [PRIMARY]"): argyll_display = " ".join(argyll_display.split(" ")[:-1]) - print(f"argyll_display (2): {argyll_display}") for display in _displays: desc = display["description"] - print(f"desc : {desc}") if desc: geometry = b"".join(desc.split(b"@ ")[-1:]) - print(f"geometry : {geometry}") - print(f'argyll_display.endswith((b"@ " + geometry).decode("utf-8")): {argyll_display.endswith((b"@ " + geometry).decode("utf-8"))}') if argyll_display.endswith((b"@ " + geometry).decode("utf-8")): return display diff --git a/DisplayCAL/edid.py b/DisplayCAL/edid.py index 65d0dc7a..d46bc068 100644 --- a/DisplayCAL/edid.py +++ b/DisplayCAL/edid.py @@ -212,7 +212,6 @@ def get_edid(display_no=0, display_name=None, device=None): return {} else: display = RDSMM.get_display(display_no) - print(f"display (3) : {display}") if not display: return {} From 70e447409884bf5eeffca3327d727d53d1121ed9 Mon Sep 17 00:00:00 2001 From: Erkan Ozgur Yilmaz Date: Tue, 22 Oct 2024 17:34:13 +0100 Subject: [PATCH 30/34] [#16] Fixed `DisplayCAL.RealDisplaySizeMM.Display.from_dispwin_data()` and `DisplayCAL.RealDisplaySizeMM._enumerate_displays()` for ArgyllCMS<3.3.0 style output. --- DisplayCAL/RealDisplaySizeMM.py | 11 ++++++---- tests/conftest.py | 2 +- tests/data/display_data.py | 32 ++++++++++++++++++++++++++++++ tests/test_edid.py | 4 +--- tests/test_real_display_size_mm.py | 19 +++++++++--------- 5 files changed, 51 insertions(+), 17 deletions(-) diff --git a/DisplayCAL/RealDisplaySizeMM.py b/DisplayCAL/RealDisplaySizeMM.py index cd7582f4..6866283d 100644 --- a/DisplayCAL/RealDisplaySizeMM.py +++ b/DisplayCAL/RealDisplaySizeMM.py @@ -114,7 +114,7 @@ def from_dispwin_data(self, display_info_line): raise ValueError(dispwin_error_message) self.description = description_data[0] match = re.match( - rb"[\s]*(?P\d) = '(?P.*), at (?P\d+), (?P[-\d]+), " + rb"[\s]*(?P\d) = '(?P.*) at (?P\d+), (?P[-\d]+), " rb"width (?P\d+), height (?P\d+).*'", display_info_line, ) @@ -123,6 +123,9 @@ def from_dispwin_data(self, display_info_line): groups_dict = match.groupdict() self.monid = int(groups_dict["id"]) self.name = groups_dict["name"] + # fix the name ending with "," for ArgyllCMS<3.3.0 + if self.name.endswith(b","): + self.name = self.name[:-1] x = int(groups_dict["x"]) y = int(groups_dict["y"]) self.pos = (x, y) @@ -192,13 +195,13 @@ def _enumerate_displays() -> List[dict]: has_display = False dispwin_output = get_dispwin_output() for line in dispwin_output.split(b"\n"): - if b"-dweb[:port]" in line: + if has_display and b"-dweb[:port]" in line: break - if has_display: + if has_display and b"=" in line: display = Display() display.from_dispwin_data(line) displays.append(display.to_dict()) - if b"-d n" in line: + if not has_display and b"-d n" in line: has_display = True return displays diff --git a/tests/conftest.py b/tests/conftest.py index b9067c7c..121233b0 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -199,4 +199,4 @@ def get_argyll_util(cls, util_name): def clear_displays(): """Clear RealDisplaySizeMM._displays.""" RealDisplaySizeMM._displays = None - assert RealDisplaySizeMM._displays is None \ No newline at end of file + assert RealDisplaySizeMM._displays is None diff --git a/tests/data/display_data.py b/tests/data/display_data.py index c4c5c8af..6e2cd6b0 100644 --- a/tests/data/display_data.py +++ b/tests/data/display_data.py @@ -235,6 +235,38 @@ class DisplayData: -D [level] Print debug diagnostics to stderr calfile Load calibration (.cal or .icc) into Video LUT""" + DISPWIN_OUTPUT_6 = b"""Test display patch window, Set Video LUTs, Install profiles, Version 3.3.0 + Author: Graeme W. Gill, licensed under the AGPL Version 3 + Diagnostic: -d parameter '0' is out of range + usage: dispwin [options] [calfile] + -v Verbose mode + -d n Choose the display from the following list (default 1) + 1 = 'Monitor 1, Output Virtual-1 at 0, 0, width 1920, height 1080' + -dweb[:port] Display via web server at port (default 8080) + -dcc[:n] Display via n'th ChromeCast (default 1, ? for list) + -d dummy Display via dummy (non-existant, invisible) display + -P ho,vo,ss[,vs] Position test window and scale it + -F Fill whole screen with black background + -E Video encode output as (16-235)/255 "TV" levels + -i Run forever with random values + -G filename Display RGB colors from CGATS (ie .ti1) file + -C r.rr,g.gg,b.bb Add this RGB color to list to be displayed + -m Manually cycle through values + -Y msec patch delay in msec (default 2000) + -f Test grey ramp fade + -r Test just Video LUT loading & Beeps + -n Test native output (rather than through Video LUT and C.M.) + -s filename Save the currently loaded Video LUT to 'filename' + -c Load a linear display calibration + -V Verify that calfile/profile cal. is currently loaded in LUT + -I Install profile for display and use its calibration + -U Un-install profile for display + -S d Specify the install/uninstall scope for OS X [nlu] or X11/Vista [lu] + d is one of: n = network, l = local system, u = user (default) + -L Load installed profile & calibration + -D [level] Print debug diagnostics to stderr + calfile Load calibration (.cal or .icc) into Video LUT""" + @classmethod def CFG_DATA(self, name, fallback=True, raw=False, cfg=None): return { diff --git a/tests/test_edid.py b/tests/test_edid.py index 1daf2f14..e1caa226 100644 --- a/tests/test_edid.py +++ b/tests/test_edid.py @@ -45,7 +45,7 @@ def test_get_edid_1(clear_displays, monkeypatch, patch_subprocess, data_files): assert "checksum" in result assert result["checksum"] > 0 assert "checksum_valid" in result - assert result["checksum_valid"] is False + assert result["checksum_valid"] is False assert "edid" in result assert isinstance(result["edid"], bytes) assert "edid_revision" in result @@ -92,8 +92,6 @@ def test_get_edid_1(clear_displays, monkeypatch, patch_subprocess, data_files): assert isinstance(result["year_of_manufacture"], int) - - # def test_get_edid_3(clear_displays): # """Testing DisplayCAL.colord.device_id_from_edid() function.""" # config.initcfg() diff --git a/tests/test_real_display_size_mm.py b/tests/test_real_display_size_mm.py index a9e7d70c..fd4b0ddc 100644 --- a/tests/test_real_display_size_mm.py +++ b/tests/test_real_display_size_mm.py @@ -149,21 +149,22 @@ def test__enumerate_displays_without_a_proper_dispwin_output_missing_lines( assert len(result) == 0 -def test__enumerate_displays_without_a_proper_dispwin_output_with_wrong_formatted_data( +def test__enumerate_displays_with_argyll_3_1_style_output( patch_subprocess_on_rdsmm, patch_argyll_util ): - """_enumerate_displays() return empty list when dispwin returns no usable data.""" + """_enumerate_displays() can enumerate ArgyllCMS 3.1.0 dispwin output format.""" from DisplayCAL import localization as lang lang.init() - patch_subprocess_on_rdsmm.output["dispwin-v-d0"] = DisplayData.DISPWIN_OUTPUT_4 - with pytest.raises(ValueError) as cm: - result = RealDisplaySizeMM._enumerate_displays() - assert str(cm.value) == ( - "An internal error occurred.\n" - "Error code: -1\n" - "Error message: dispwin returns no usable data while enumerating displays." + patch_subprocess_on_rdsmm.output["dispwin-v-d0"] = DisplayData.DISPWIN_OUTPUT_6 + result = RealDisplaySizeMM._enumerate_displays() + assert result[0]["name"] == b"Monitor 1, Output Virtual-1" + assert ( + result[0]["description"] + == b"Monitor 1, Output Virtual-1 at 0, 0, width 1920, height 1080" ) + assert result[0]["size"] == (1920, 1080) + assert result[0]["pos"] == (0, 0) def test__enumerate_displays_without_a_proper_dispwin_output_with_partial_match( From 8b0654a83c72af792edd710becbf89cd3d5bd6b6 Mon Sep 17 00:00:00 2001 From: Erkan Ozgur Yilmaz Date: Tue, 22 Oct 2024 22:36:48 +0100 Subject: [PATCH 31/34] [#16] Updated `setup.cfg` to use `wxPython>=4.2.2`. --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 544c865f..cf2d984c 100644 --- a/setup.cfg +++ b/setup.cfg @@ -16,7 +16,7 @@ install_requires = PyChromecast pywin32; sys_platform=='win32' Send2Trash - wxPython + wxPython>=4.2.2 zeroconf [bdist_wheel] From 22e98a84c47a38fb775175850273ae0fa70fe7d2 Mon Sep 17 00:00:00 2001 From: Erkan Ozgur Yilmaz Date: Tue, 22 Oct 2024 22:38:05 +0100 Subject: [PATCH 32/34] [#16] Updated `DisplayCAL.setup` and removed any mentions of an C-Extension. --- DisplayCAL/setup.py | 25 ++----------------------- 1 file changed, 2 insertions(+), 23 deletions(-) diff --git a/DisplayCAL/setup.py b/DisplayCAL/setup.py index 986130c7..f943080c 100644 --- a/DisplayCAL/setup.py +++ b/DisplayCAL/setup.py @@ -194,23 +194,6 @@ def findall(dir=os.curdir): } -def add_lib_excludes(key, excludebits): - for exclude in excludebits: - config["excludes"][key].extend([f"{name}.lib{exclude}", f"lib{exclude}"]) - - for exclude in ("32", "64"): - for pycompat in ("38", "39", "310", "311", "312", "313"): - config["excludes"][key].extend( - [ - f"{name}.lib{exclude}.python{pycompat}", - f"{name}.lib{exclude}.python{pycompat}.RealDisplaySizeMM", - ] - ) - - -add_lib_excludes("darwin", ["64" if bits == "32" else "32"]) -add_lib_excludes("win32", ["64" if bits == "32" else "32"]) - msiversion = ".".join( ( str(version_tuple[0]), @@ -901,7 +884,7 @@ def findall( ) ) - sources = [os.path.join(name, "RealDisplaySizeMM.c")] + sources = [] if sys.platform == "win32": macros = [("NT", None)] libraries = ["user32", "gdi32"] @@ -925,10 +908,6 @@ def findall( macros = [("UNIX", None)] libraries = ["X11", "Xinerama", "Xrandr", "Xxf86vm"] link_args = None - if sys.platform == "darwin": - extname = f"{name}.lib{bits}.RealDisplaySizeMM" - else: - extname = f"{name}.lib{bits}.python{sys.version_info[0]}{sys.version_info[1]}.RealDisplaySizeMM" ext_modules = [] @@ -1005,7 +984,7 @@ def findall( for script, desc in scripts ] } - attrs["exclude_package_data"] = {name: ["RealDisplaySizeMM.c"]} + attrs["exclude_package_data"] = {name: []} attrs["include_package_data"] = ( sys.platform in ("darwin", "win32") and not do_py2app ) From 4e4761436826607f138fcf8ca56a9262cfbf6978 Mon Sep 17 00:00:00 2001 From: Erkan Ozgur Yilmaz Date: Tue, 22 Oct 2024 22:38:47 +0100 Subject: [PATCH 33/34] [#16] Fixed `DisplayCAL.RealDisplaySizeMM.Display.get_dispwin_output()` for Windows. --- DisplayCAL/RealDisplaySizeMM.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/DisplayCAL/RealDisplaySizeMM.py b/DisplayCAL/RealDisplaySizeMM.py index 6866283d..70c913b6 100644 --- a/DisplayCAL/RealDisplaySizeMM.py +++ b/DisplayCAL/RealDisplaySizeMM.py @@ -165,9 +165,9 @@ def get_dispwin_output() -> bytes: return b"" if sys.platform == "win32": - startupinfo = sp.STARTUPINFO() - startupinfo.dwFlags |= sp.STARTF_USESHOWWINDOW - startupinfo.wShowWindow = sp.SW_HIDE + startupinfo = subprocess.STARTUPINFO() + startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW + startupinfo.wShowWindow = subprocess.SW_HIDE else: startupinfo = None From 4dfe15c5e46cdc658fd620e287e499101b432cba Mon Sep 17 00:00:00 2001 From: Erkan Ozgur Yilmaz Date: Tue, 22 Oct 2024 22:39:51 +0100 Subject: [PATCH 34/34] [#16] Removed the now retired `RealDisplaySizeMM.c` with pleasure. --- DisplayCAL/RealDisplaySizeMM.c | 1442 -------------------------------- 1 file changed, 1442 deletions(-) delete mode 100644 DisplayCAL/RealDisplaySizeMM.c diff --git a/DisplayCAL/RealDisplaySizeMM.c b/DisplayCAL/RealDisplaySizeMM.c deleted file mode 100644 index ee5706c8..00000000 --- a/DisplayCAL/RealDisplaySizeMM.c +++ /dev/null @@ -1,1442 +0,0 @@ -#include "Python.h" -#include "bytesobject.h" - -#include -#include -#include -#include -#include -#include -#include -#ifdef NT -# include -# include -#else -# include -# include -# include -#endif - -#ifdef __APPLE__ /* Assume OSX Carbon */ -# include -# include -# include -#endif /* __APPLE__ */ - -#if defined(UNIX) && !defined(__APPLE__) -# include -# include -# include -# include -# include -# include -# include -# include -# include -#endif /* UNIX */ - -#if defined(_MSC_VER) -# define DLL extern "C" __declspec(dllexport) -#else -# define DLL -#endif - -#define errout stderr -#ifdef DEBUG -# define debug(xx) fprintf(errout, xx ) -# define debug2(xx) fprintf xx -# define debugr(xx) fprintf(errout, xx ) -# define debugr2(xx) fprintf xx -# define debugrr(xx) fprintf(errout, xx ) -# define debugrr2(xx) fprintf xx -#else -# define debug(xx) -# define debug2(xx) -# define debugr(xx) -# define debugr2(xx) -# define debugrr(xx) -# define debugrr2(xx) -#endif - - -#if PY_MAJOR_VERSION >= 3 - #define IS_PY3K - #define PyBaseString_Type PyBytes_Type - #define PyStringObject PyBytesObject - #define PyString_Type PyBytes_Type - #define PyString_Check PyBytes_Check - #define PyString_CheckExact PyBytes_CheckExact - #define PyString_FromString PyBytes_FromString - #define PyString_FromStringAndSize PyBytes_FromStringAndSize -#endif - - -// START disppath - -/* Structure to store information about possible displays */ -typedef struct { - char *name; /* Display name */ - char *description; /* Description of display or URL */ - int sx,sy; /* Displays offset in pixels */ - int sw,sh; /* Displays width and height in pixels*/ -#ifdef NT - char monid[128]; /* Monitor ID */ - int prim; /* NZ if primary display monitor */ -#endif /* NT */ -#ifdef __APPLE__ - CGDirectDisplayID ddid; -#endif /* __APPLE__ */ -#if defined(UNIX) && !defined(__APPLE__) - int screen; /* X11 (possibly virtual) Screen */ - int uscreen; /* Underlying Xinerma/XRandr screen */ - int rscreen; /* Underlying RAMDAC screen (user override) */ - Atom icc_atom; /* ICC profile root/output atom for this display */ - unsigned char *edid; /* 128, 256, or 384 bytes of monitor EDID, NULL if none */ - int edid_len; /* 128, 256, or 384 */ - -#if RANDR_MAJOR == 1 && RANDR_MINOR >= 2 - /* Xrandr stuff - output is connected 1:1 to a display */ - RRCrtc crtc; /* Associated crtc */ - RROutput output; /* Associated output */ - Atom icc_out_atom; /* ICC profile atom for this output */ -#endif /* randr >= V 1.2 */ -#endif /* UNIX */ -} disppath; - -// END disppath - -void free_a_disppath(disppath *path); -void free_disppaths(disppath **paths); - -/* ===================================================================== */ -/* Display enumeration code */ -/* ===================================================================== */ - -int callback_ddebug = 0; /* Diagnostic global for get_displays() and get_a_display() */ - /* and events */ - -#ifdef NT - -#define sleep(secs) Sleep((secs) * 1000) - -static BOOL CALLBACK MonitorEnumProc( - HMONITOR hMonitor, /* handle to display monitor */ - HDC hdcMonitor, /* NULL, because EnumDisplayMonitors hdc is NULL */ - LPRECT lprcMonitor, /* Virtual screen coordinates of this monitor */ - LPARAM dwData /* Context data */ -) { - disppath ***pdisps = (disppath ***)dwData; - disppath **disps = *pdisps; - MONITORINFOEX pmi; - int ndisps = 0; - - debugrr2((errout, "MonitorEnumProc() called with hMonitor = 0x%x\n",hMonitor)); - - /* Get some more information */ - pmi.cbSize = sizeof(MONITORINFOEX); - if (GetMonitorInfo(hMonitor, (MONITORINFO *)&pmi) == 0) { - debugrr("get_displays failed GetMonitorInfo - ignoring display\n"); - return TRUE; - } - - /* See if it seems to be a pseudo-display */ - if (strncmp(pmi.szDevice, "\\\\.\\DISPLAYV", 12) == 0) { - debugrr("Seems to be invisible pseudo-display - ignoring it\n"); - return TRUE; - } - - /* Add the display to the list */ - if (disps == NULL) { - if ((disps = (disppath **)calloc(sizeof(disppath *), 1 + 1)) == NULL) { - debugrr("get_displays failed on malloc\n"); - return FALSE; - } - } else { - /* Count current number on list */ - for (ndisps = 0; disps[ndisps] != NULL; ndisps++) - ; - if ((disps = (disppath **)realloc(disps, - sizeof(disppath *) * (ndisps + 2))) == NULL) { - debugrr("get_displays failed on malloc\n"); - return FALSE; - } - disps[ndisps+1] = NULL; /* End marker */ - } - - if ((disps[ndisps] = calloc(sizeof(disppath),1)) == NULL) { - debugrr("get_displays failed on malloc\n"); - return FALSE; - } - - if ((disps[ndisps]->name = strdup(pmi.szDevice)) == NULL) { - debugrr("malloc failed\n"); - return FALSE; - } - disps[ndisps]->prim = (pmi.dwFlags & MONITORINFOF_PRIMARY) ? 1 : 0; - - disps[ndisps]->sx = lprcMonitor->left; - disps[ndisps]->sy = lprcMonitor->top; - disps[ndisps]->sw = lprcMonitor->right - lprcMonitor->left; - disps[ndisps]->sh = lprcMonitor->bottom - lprcMonitor->top; - - disps[ndisps]->description = NULL; - - debugrr2((errout, "MonitorEnumProc() set initial monitor info: %d,%d %d,%d name '%s'\n",disps[ndisps]->sx,disps[ndisps]->sy,disps[ndisps]->sw,disps[ndisps]->sh, disps[ndisps]->name)); - - *pdisps = disps; - return TRUE; -} - -/* Dynamically linked function support */ - -BOOL (WINAPI* pEnumDisplayDevices)(PVOID,DWORD,PVOID,DWORD) = NULL; - -/* See if we can get the wanted function calls */ -/* return nz if OK */ -static int setup_dyn_calls() { - static int dyn_inited = 0; - - if (dyn_inited == 0) { - dyn_inited = 1; - - /* EnumDisplayDevicesA was left out of lib32.lib on earlier SDK's ... */ - pEnumDisplayDevices = (BOOL (WINAPI*)(PVOID,DWORD,PVOID,DWORD)) GetProcAddress(LoadLibrary("USER32"), "EnumDisplayDevicesA"); - if (pEnumDisplayDevices == NULL) - dyn_inited = 0; - } - - return dyn_inited; -} - -/* Simple up conversion from char string to wchar string */ -/* Return NULL if malloc fails */ -/* ~~~ Note, should probably replace this with mbstowcs() ???? */ -static unsigned short *char2wchar(char *s) { - unsigned char *cp; - unsigned short *w, *wp; - - if ((w = malloc(sizeof(unsigned short) * (strlen(s) + 1))) == NULL) - return w; - - for (cp = (unsigned char *)s, wp = w; ; cp++, wp++) { - *wp = *cp; /* Zero extend */ - if (*cp == 0) - break; - } - - return w; -} - -#endif /* NT */ - -#if defined(UNIX) && !defined(__APPLE__) -/* Hack to notice if the error handler has been triggered */ -/* when a function doesn't return a value. */ - -int g_error_handler_triggered = 0; - -/* A noop X11 error handler */ -int null_error_handler(Display *disp, XErrorEvent *ev) { - g_error_handler_triggered = 1; - return 0; -} -#endif /* X11 */ - -/* Return pointer to list of disppath. Last will be NULL. */ -/* Return NULL on failure. Call free_disppaths() to free up allocation */ -disppath **get_displays() { - disppath **disps = NULL; - -#ifdef NT - DISPLAY_DEVICE dd; - char buf[200]; - int i, j; - - if (setup_dyn_calls() == 0) { - debugrr("Dynamic linking to EnumDisplayDevices or Vista AssociateColorProfile failed\n"); - free_disppaths(disps); - return NULL; - } - - /* Create an initial list of monitors */ - /* (It might be better to call pEnumDisplayDevices(NULL, i ..) instead ??, */ - /* then we can use the StateFlags to distingish monitors not attached to the desktop etc.) */ - if (EnumDisplayMonitors(NULL, NULL, MonitorEnumProc, (LPARAM)&disps) == 0) { - debugrr("EnumDisplayMonitors failed\n"); - free_disppaths(disps); - return NULL; - } - - /* Now locate detailed information about displays */ - for (i = 0; ; i++) { - if (disps == NULL || disps[i] == NULL) - break; - - dd.cb = sizeof(dd); - - debugrr2((errout, "get_displays about to get monitor information for %d\n",i)); - /* Get monitor information */ - for (j = 0; ;j++) { - if ((*pEnumDisplayDevices)(disps[i]->name, j, &dd, 0) == 0) { - debugrr2((errout,"EnumDisplayDevices failed on '%s' Mon = %d\n",disps[i]->name,j)); - if (j == 0) { - strcpy(disps[i]->monid, ""); /* We won't be able to set a profile */ - } - break; - } - if (callback_ddebug) { - fprintf(errout,"Mon %d, name '%s'\n",j,dd.DeviceName); - fprintf(errout,"Mon %d, string '%s'\n",j,dd.DeviceString); - fprintf(errout,"Mon %d, flags 0x%x\n",j,dd.StateFlags); - fprintf(errout,"Mon %d, id '%s'\n",j,dd.DeviceID); - fprintf(errout,"Mon %d, key '%s'\n",j,dd.DeviceKey); - } - if (j == 0) { - strcpy(disps[i]->monid, dd.DeviceID); - } - } - - sprintf(buf,"%s, at %d, %d, width %d, height %d%s",disps[i]->name+4, - disps[i]->sx, disps[i]->sy, disps[i]->sw, disps[i]->sh, - disps[i]->prim ? " (Primary Display)" : ""); - - if ((disps[i]->description = strdup(buf)) == NULL) { - debugrr("get_displays failed on malloc\n"); - free_disppaths(disps); - return NULL; - } - - debugrr2((errout, "get_displays added description '%s' to display %d\n",disps[i]->description,i)); - - /* Note that calling EnumDisplayDevices(NULL, j, ..) for the adapter can return other */ - /* information, such as the graphics card name, and additional state flags. */ - /* EnumDisplaySettings() can also be called to get information such as display depth etc. */ - } - -#ifdef NEVER - /* Explore adapter information */ - for (j = 0; ; j++) { - /* Get adapater information */ - if ((*pEnumDisplayDevices)(NULL, j, &dd, 0) == 0) - break; - printf("Adapt %d, name '%s'\n",j,dd.DeviceName); - printf("Adapt %d, string '%s'\n",j,dd.DeviceString); - printf("Adapt %d, flags 0x%x\n",j,dd.StateFlags); - printf("Adapt %d, id '%s'\n",j,dd.DeviceID); - printf("Adapt %d, key '%s'\n",j,dd.DeviceKey); - } -#endif /* NEVER */ - -#endif /* NT */ - -#ifdef __APPLE__ - /* Note :- some recent releases of OS X have a feature which */ - /* automatically adjusts the screen brightness with ambient level. */ - /* We may have to find a way of disabling this during calibration and profiling. */ - /* See the "pset -g" command. */ - - /* - We could possibly use NSScreen instead of CG here, - If we're using libui, then we have an NSApp, so - this would be possible. - */ - - int i; - CGDisplayErr dstat; - CGDisplayCount dcount; /* Number of display IDs */ - CGDirectDisplayID *dids; /* Array of display IDs */ - - if ((dstat = CGGetActiveDisplayList(0, NULL, &dcount)) != kCGErrorSuccess || dcount < 1) { - debugrr("CGGetActiveDisplayList #1 returned error\n"); - return NULL; - } - if ((dids = (CGDirectDisplayID *)malloc(dcount * sizeof(CGDirectDisplayID))) == NULL) { - debugrr("malloc of CGDirectDisplayID's failed\n"); - return NULL; - } - if ((dstat = CGGetActiveDisplayList(dcount, dids, &dcount)) != kCGErrorSuccess) { - debugrr("CGGetActiveDisplayList #2 returned error\n"); - free(dids); - return NULL; - } - - /* Found dcount displays */ - debugrr2((errout,"Found %d screens\n",dcount)); - - /* Allocate our list */ - if ((disps = (disppath **)calloc(sizeof(disppath *), dcount + 1)) == NULL) { - debugrr("get_displays failed on malloc\n"); - free(dids); - return NULL; - } - for (i = 0; i < dcount; i++) { - if ((disps[i] = calloc(sizeof(disppath), 1)) == NULL) { - debugrr("get_displays failed on malloc\n"); - free_disppaths(disps); - free(dids); - return NULL; - } - disps[i]->ddid = dids[i]; - } - - /* Got displays, now figure out a description for each one */ - for (i = 0; i < dcount; i++) { - CGRect dbound; /* Bounding rectangle of chosen display */ - io_service_t dport; - CFDictionaryRef ddr, pndr; - CFIndex dcount; - char *dp = NULL, desc[50]; - char buf[200]; - - dbound = CGDisplayBounds(dids[i]); - disps[i]->sx = dbound.origin.x; - disps[i]->sy = dbound.origin.y; - disps[i]->sw = dbound.size.width; - disps[i]->sh = dbound.size.height; - - /* Try and get some information about the display */ - if ((dport = CGDisplayIOServicePort(dids[i])) == MACH_PORT_NULL) { - debugrr("CGDisplayIOServicePort returned error\n"); - free_disppaths(disps); - free(dids); - return NULL; - } - -#ifdef NEVER - { - io_name_t name; - if (IORegistryEntryGetName(dport, name) != KERN_SUCCESS) { - debugrr("IORegistryEntryGetName returned error\n"); - free_disppaths(disps); - free(dids); - return NULL; - } - printf("Driver %d name = '%s'\n",i,name); - } -#endif - if ((ddr = IODisplayCreateInfoDictionary(dport, 0)) == NULL) { - debugrr("IODisplayCreateInfoDictionary returned NULL\n"); - free_disppaths(disps); - free(dids); - return NULL; - } - if ((pndr = CFDictionaryGetValue(ddr, CFSTR(kDisplayProductName))) == NULL) { - debugrr("CFDictionaryGetValue returned NULL\n"); - CFRelease(ddr); - free_disppaths(disps); - free(dids); - return NULL; - } - if ((dcount = CFDictionaryGetCount(pndr)) > 0) { - const void **keys; - const void **values; - int j; - - keys = (const void **)calloc(sizeof(void *), dcount); - values = (const void **)calloc(sizeof(void *), dcount); - if (keys == NULL || values == NULL) { - if (keys != NULL) - free(keys); - if (values != NULL) - free(values); - debugrr("malloc failed\n"); - CFRelease(ddr); - free_disppaths(disps); - free(dids); - return NULL; - } - CFDictionaryGetKeysAndValues(pndr, keys, values); - for (j = 0; j < dcount; j++) { - const char *k, *v; - char kbuf[50], vbuf[50]; - k = CFStringGetCStringPtr(keys[j], kCFStringEncodingMacRoman); - if (k == NULL) { - if (CFStringGetCString(keys[j], kbuf, 50, kCFStringEncodingMacRoman)) - k = kbuf; - } - v = CFStringGetCStringPtr(values[j], kCFStringEncodingMacRoman); - if (v == NULL) { - if (CFStringGetCString(values[j], vbuf, 50, kCFStringEncodingMacRoman)) - v = vbuf; - } - /* We're only grabbing the english description... */ - if (k != NULL && v != NULL && strcmp(k, "en_US") == 0) { - strncpy(desc, v, 49); - desc[49] = '\000'; - dp = desc; - } - } - free(keys); - free(values); - } - CFRelease(ddr); - - if (dp == NULL) { - strcpy(desc, "(unknown)"); - dp = desc; - } - sprintf(buf,"%s, at %d, %d, width %d, height %d%s",dp, - disps[i]->sx, disps[i]->sy, disps[i]->sw, disps[i]->sh, - CGDisplayIsMain(dids[i]) ? " (Primary Display)" : ""); - - if ((disps[i]->name = strdup(dp)) == NULL - || (disps[i]->description = strdup(buf)) == NULL) { - debugrr("get_displays failed on malloc\n"); - free_disppaths(disps); - free(dids); - return NULL; - } - } - - free(dids); -#endif /* __APPLE__ */ - -#if defined(UNIX) && !defined(__APPLE__) - int i, j, k; - int defsix = 0; /* default screen index */ - int dcount; /* Number of screens */ - char *dname; - char dnbuf[100]; - int evb = 0, erb = 0; - int majv, minv; /* Version */ - Display *mydisplay; - int ndisps = 0; - XineramaScreenInfo *xai = NULL; - char desc1[100], desc2[200]; - - /* There seems to be no way of getting the available displays */ - /* on an X11 system. Attempting to open them in sequence */ - /* takes too long. We just rely on the user supplying the */ - /* right display. We can enumerate screens though. */ - - /* Open the base display, and then enumerate all the screens */ - if ((dname = getenv("DISPLAY")) != NULL) { - char *pp; - strncpy(dnbuf,dname,99); dnbuf[99] = '\000'; - if ((pp = strrchr(dnbuf, ':')) != NULL) { - if ((pp = strchr(pp, '.')) == NULL) - strcat(dnbuf,".0"); - else { - if (pp[1] == '\000') - strcat(dnbuf,"0"); - else { - pp[1] = '0'; - pp[2] = '\000'; - } - } - } - } else - strcpy(dnbuf,":0.0"); - - if ((mydisplay = XOpenDisplay(dnbuf)) == NULL) { - debugrr2((errout, "failed to open display '%s'\n",dnbuf)); - return NULL; - } - -#if RANDR_MAJOR == 1 && RANDR_MINOR >= 2 && !defined(DISABLE_RANDR) - /* Use Xrandr 1.2 if it's available, and if it's not disabled. */ - if (getenv("ARGYLL_IGNORE_XRANDR1_2") == NULL - && XRRQueryExtension(mydisplay, &evb, &erb) != 0 - && XRRQueryVersion(mydisplay, &majv, &minv) - && majv == 1 && minv >= 2) { - static void *xrr_found = NULL; /* .so handle */ - static XRRScreenResources *(*_XRRGetScreenResourcesCurrent) - (Display *dpy, Window window) = NULL; - static RROutput (*_XRRGetOutputPrimary)(Display *dpy, Window window) = NULL; -// int defsix; /* Default Screen index */ - - if (XSetErrorHandler(null_error_handler) == 0) { - debugrr("get_displays failed on XSetErrorHandler\n"); - XCloseDisplay(mydisplay); - free_disppaths(disps); - return NULL; - } - - /* Get functions available in Xrandr V1.3 */ - if (minv >= 3 && xrr_found == NULL) { - if ((xrr_found = dlopen("libXrandr.so", RTLD_LAZY)) != NULL) { - _XRRGetScreenResourcesCurrent = dlsym(xrr_found, "XRRGetScreenResourcesCurrent"); - _XRRGetOutputPrimary = dlsym(xrr_found, "XRRGetOutputPrimary"); - } - } - - /* Hmm. Do Xrandr systems alway have only one Screen, */ - /* just like Xinerama ? */ - dcount = ScreenCount(mydisplay); - - debugrr2((errout,"get_displays using %d XRandR Screens\n",dcount)); - - /* Not sure what to do with this. */ - /* Should we go through X11 screens with this first ? */ - /* (How does Xrandr translate Screen 1..n to Xinerama ?????) */ - defsix = DefaultScreen(mydisplay); - - /* In order to be in sync with an application using Xinerama, */ - /* we need to generate our screen indexes in the same */ - /* order as Xinerama. */ - - /* Go through all the X11 screens */ - for (i = 0; i < dcount; i++) { - XRRScreenResources *scrnres; - int has_primary = 0; - int pix = -1; /* CRTC index containing primary */ - int pop = -1; /* Output index containing primary */ - int jj; /* Xinerama screen ix */ - int xj; /* working crtc index */ - int xk; /* working output index */ - - if (minv >= 3 && _XRRGetScreenResourcesCurrent != NULL) { - scrnres = _XRRGetScreenResourcesCurrent(mydisplay, RootWindow(mydisplay,i)); - - } else { - scrnres = XRRGetScreenResources(mydisplay, RootWindow(mydisplay,i)); - } - if (scrnres == NULL) { - debugrr("XRRGetScreenResources failed\n"); - XCloseDisplay(mydisplay); - free_disppaths(disps); - return NULL; - } - /* We have to scan through CRTC's & outputs in the same order */ - /* as the XRANDR XInerama implementation in the X server. */ - /* This is a little tricky, as we need to do the primary output, */ - /* first, while keeping the rest in order. */ - - /* Locate the crtc index that contains the primary (if any) */ - if (minv >= 3 && _XRRGetOutputPrimary != NULL) { - XID primary; /* Primary output ID */ - - primary = _XRRGetOutputPrimary(mydisplay, RootWindow(mydisplay,i)); - debugrr2((errout,"XRRGetOutputPrimary returned XID %x\n",primary)); - - if (primary != None) { - for (j = 0; j < scrnres->ncrtc; j++) { - XRRCrtcInfo *crtci = NULL; - - if ((crtci = XRRGetCrtcInfo(mydisplay, scrnres, scrnres->crtcs[j])) == NULL) - continue; - - if (crtci->mode == None || crtci->noutput == 0) { - XRRFreeCrtcInfo(crtci); - continue; - } - - for (k = 0; k < crtci->noutput; k++) { - if (crtci->outputs[k] == primary) { - pix = j; - pop = k; - } - } - XRRFreeCrtcInfo(crtci); - } - if (pix < 0) { /* Didn't locate primary */ - debugrr2((errout,"Couldn't locate primary CRTC!\n")); - } else { - debugrr2((errout,"Primary is at CRTC %d Output %d\n",pix,pop)); - has_primary = 1; - } - } - } - - /* Look through all the Screens CRTC's */ - for (jj = xj = j = 0; j < scrnres->ncrtc; j++, xj++) { - char *pp; - XRRCrtcInfo *crtci = NULL; - XRROutputInfo *outi0 = NULL; - - if (has_primary) { - if (j == 0) - xj = pix; /* Start with crtc containing primary */ - - else if (xj == pix) /* We've up to primary that we've alread done */ - xj++; /* Skip it */ - } - - if ((crtci = XRRGetCrtcInfo(mydisplay, scrnres, scrnres->crtcs[xj])) == NULL) { - debugrr2((errout,"XRRGetCrtcInfo of Screen %d CRTC %d failed\n",i,xj)); - if (has_primary && j == 0) - xj = -1; /* Start at beginning */ - continue; - } - - debugrr2((errout,"XRRGetCrtcInfo of Screen %d CRTC %d has %d Outputs %s Mode\n",i,xj,crtci->noutput,crtci->mode == None ? "No" : "Valid")); - - if (crtci->mode == None || crtci->noutput == 0) { - debugrr2((errout,"CRTC skipped as it has no mode or no outputs\n",i,xj,crtci->noutput)); - XRRFreeCrtcInfo(crtci); - if (has_primary && j == 0) - xj = -1; /* Start at beginning */ - continue; - } - - /* This CRTC will now be counted as an Xinerama screen */ - /* For each output of Crtc */ - for (xk = k = 0; k < crtci->noutput; k++, xk++) { - XRROutputInfo *outi = NULL; - - if (has_primary && xj == pix) { - if (k == 0) - xk = pop; /* Start with primary output */ - else if (xk == pop) /* We've up to primary that we've alread done */ - xk++; /* Skip it */ - } - - if ((outi = XRRGetOutputInfo(mydisplay, scrnres, crtci->outputs[xk])) == NULL) { - debugrr2((errout,"XRRGetOutputInfo failed for Screen %d CRTC %d Output %d\n",i,xj,xk)); - goto next_output; - } - if (k == 0) /* Save this so we can label any clones */ - outi0 = outi; - - if (outi->connection == RR_Disconnected) { - debugrr2((errout,"Screen %d CRTC %d Output %d is disconnected\n",i,xj,xk)); - goto next_output; - } - - /* Check that the VideoLUT's are accessible */ - { - XRRCrtcGamma *crtcgam = NULL; - - debugrr("Checking XRandR 1.2 VideoLUT access\n"); - if ((crtcgam = XRRGetCrtcGamma(mydisplay, scrnres->crtcs[xj])) == NULL - || crtcgam->size == 0) { - fprintf(stderr,"XRRGetCrtcGamma failed - falling back to older extensions\n"); - if (crtcgam != NULL) - XRRFreeGamma(crtcgam); - if (outi != NULL && outi != outi0) - XRRFreeOutputInfo(outi); - if (outi0 != NULL) - XRRFreeOutputInfo(outi0); - XRRFreeCrtcInfo(crtci); - XRRFreeScreenResources(scrnres); - free_disppaths(disps); - disps = NULL; - goto done_xrandr; - } - if (crtcgam != NULL) - XRRFreeGamma(crtcgam); - } -#ifdef NEVER - { - Atom *oprops; - int noprop; - - /* Get a list of the properties of the output */ - oprops = XRRListOutputProperties(mydisplay, crtci->outputs[xk], &noprop); - - printf("num props = %d\n", noprop); - for (k = 0; k < noprop; k++) { - printf("%d: atom 0x%x, name = '%s'\n", k, oprops[k], XGetAtomName(mydisplay, oprops[k])); - } - } -#endif /* NEVER */ - - /* Add the output to the list */ - debugrr2((errout,"Adding Screen %d CRTC %d Output %d\n",i,xj,xk)); - if (disps == NULL) { - if ((disps = (disppath **)calloc(sizeof(disppath *), 1 + 1)) == NULL) { - debugrr("get_displays failed on malloc\n"); - XRRFreeCrtcInfo(crtci); - if (outi != NULL && outi != outi0) - XRRFreeOutputInfo(outi); - if (outi0 != NULL) - XRRFreeOutputInfo(outi0); - XRRFreeScreenResources(scrnres); - XCloseDisplay(mydisplay); - return NULL; - } - } else { - if ((disps = (disppath **)realloc(disps, - sizeof(disppath *) * (ndisps + 2))) == NULL) { - debugrr("get_displays failed on malloc\n"); - XRRFreeCrtcInfo(crtci); - if (outi != NULL && outi != outi0) - XRRFreeOutputInfo(outi); - if (outi0 != NULL) - XRRFreeOutputInfo(outi0); - XRRFreeScreenResources(scrnres); - XCloseDisplay(mydisplay); - return NULL; - } - disps[ndisps+1] = NULL; /* End marker */ - } - /* ndisps is current display we're filling in */ - if ((disps[ndisps] = calloc(sizeof(disppath),1)) == NULL) { - debugrr("get_displays failed on malloc\n"); - XRRFreeCrtcInfo(crtci); - if (outi != NULL && outi != outi0) - XRRFreeOutputInfo(outi); - if (outi0 != NULL) - XRRFreeOutputInfo(outi0); - XRRFreeScreenResources(scrnres); - XCloseDisplay(mydisplay); - free_disppaths(disps); - return NULL; - } - - disps[ndisps]->screen = i; /* X11 (virtual) Screen */ - disps[ndisps]->uscreen = jj; /* Xinerama/Xrandr screen */ - disps[ndisps]->rscreen = jj; - disps[ndisps]->sx = crtci->x; - disps[ndisps]->sy = crtci->y; - disps[ndisps]->sw = crtci->width; - disps[ndisps]->sh = crtci->height; - disps[ndisps]->crtc = scrnres->crtcs[xj]; /* XID of CRTC */ - disps[ndisps]->output = crtci->outputs[xk]; /* XID of output */ - - sprintf(desc1,"Monitor %d, Output %s",ndisps+1,outi->name); - sprintf(desc2,"%s at %d, %d, width %d, height %d",desc1, - disps[ndisps]->sx, disps[ndisps]->sy, disps[ndisps]->sw, disps[ndisps]->sh); - - /* If it is a clone */ - if ( (k > 0) & (outi0 != NULL) ) { - sprintf(desc1, "[ Clone of %s ]",outi0->name); - strcat(desc2, desc1); - } - - if ((disps[ndisps]->description = strdup(desc2)) == NULL) { - debugrr("get_displays failed on malloc\n"); - XRRFreeCrtcInfo(crtci); - if (outi != NULL && outi != outi0) - XRRFreeOutputInfo(outi); - if (outi0 != NULL) - XRRFreeOutputInfo(outi0); - XRRFreeScreenResources(scrnres); - XCloseDisplay(mydisplay); - free_disppaths(disps); - return NULL; - } - - /* Form the display name */ - if ((pp = strrchr(dnbuf, ':')) != NULL) { - if ((pp = strchr(pp, '.')) != NULL) { - sprintf(pp,".%d",i); - } - } - if ((disps[ndisps]->name = strdup(dnbuf)) == NULL) { - debugrr("get_displays failed on malloc\n"); - XRRFreeCrtcInfo(crtci); - if (outi != NULL && outi != outi0) - XRRFreeOutputInfo(outi); - if (outi0 != NULL) - XRRFreeOutputInfo(outi0); - XRRFreeScreenResources(scrnres); - XCloseDisplay(mydisplay); - free_disppaths(disps); - return NULL; - } - debugrr2((errout, "Display %d name = '%s'\n",ndisps,disps[ndisps]->name)); - - /* Create the X11 root atom of the default screen */ - /* that may contain the associated ICC profile. */ - if (jj == 0) - strcpy(desc1, "_ICC_PROFILE"); - else - sprintf(desc1, "_ICC_PROFILE_%d",disps[ndisps]->uscreen); - - if ((disps[ndisps]->icc_atom = XInternAtom(mydisplay, desc1, False)) == None) - fprintf(stderr, "Unable to intern atom '%s'",desc1); - - debugrr2((errout,"Root atom '%s'\n",desc1)); - - /* Create the atom of the output that may contain the associated ICC profile */ - if ((disps[ndisps]->icc_out_atom = XInternAtom(mydisplay, "_ICC_PROFILE", False)) == None) - fprintf(stderr, "Unable to intern atom '%s'","_ICC_PROFILE"); - - /* Grab the EDID from the output */ - { - Atom edid_atom, ret_type; - int ret_format; - long ret_len = 0; - unsigned long ret_togo; - unsigned char *atomv = NULL; - int ii; - char *keys[] = { /* Possible keys that may be used */ - "EDID_DATA", - "EDID", - "" - }; - - /* Try each key in turn */ - for (ii = 0; keys[ii][0] != '\000'; ii++) { - /* Get the atom for the EDID data */ - if ((edid_atom = XInternAtom(mydisplay, keys[ii], True)) == None) { - // debugrr2((errout, "Unable to intern atom '%s'\n",keys[ii])); - /* Try the next key */ - - /* Get the EDID_DATA */ - } else { - if (XRRGetOutputProperty(mydisplay, crtci->outputs[xk], edid_atom, - 0, 0x7ffffff, False, False, XA_INTEGER, - &ret_type, &ret_format, &ret_len, &ret_togo, &atomv) == Success - && (ret_len == 128 || ret_len == 256 || ret_len == 384)) { - if ((disps[ndisps]->edid = malloc(sizeof(unsigned char) * ret_len)) == NULL) { - debugrr("get_displays failed on malloc\n"); - XRRFreeCrtcInfo(crtci); - if (outi != NULL && outi != outi0) - XRRFreeOutputInfo(outi); - if (outi0 != NULL) - XRRFreeOutputInfo(outi0); - XRRFreeScreenResources(scrnres); - XCloseDisplay(mydisplay); - free_disppaths(disps); - return NULL; - } - memmove(disps[ndisps]->edid, atomv, ret_len); - disps[ndisps]->edid_len = ret_len; - XFree(atomv); - debugrr2((errout, "Got EDID for display\n")); - break; - } - /* Try the next key */ - } - } - if (keys[ii][0] == '\000') - debugrr2((errout, "Failed to get EDID for display\n")); - } - ndisps++; /* Now it's number of displays */ - - next_output:; - if (outi != NULL && outi != outi0) - XRRFreeOutputInfo(outi); - if (has_primary && xj == pix && k == 0) - xk = -1; /* Go to first output */ - } -// next_screen:; - if (outi0 != NULL) - XRRFreeOutputInfo(outi0); - XRRFreeCrtcInfo(crtci); - jj++; /* Next Xinerama screen index */ - if (has_primary && j == 0) - xj = -1; /* Go to first screen */ - } - XRRFreeScreenResources(scrnres); - } - done_xrandr:; - XSetErrorHandler(NULL); - } -#endif /* randr >= V 1.2 */ - - if (disps == NULL) { /* Use Older style identification */ - - if (XSetErrorHandler(null_error_handler) == 0) { - debugrr("get_displays failed on XSetErrorHandler\n"); - XCloseDisplay(mydisplay); - return NULL; - } - - if (getenv("ARGYLL_IGNORE_XINERAMA") == NULL - && XineramaQueryExtension(mydisplay, &evb, &erb) != 0 - && XineramaIsActive(mydisplay)) { - - xai = XineramaQueryScreens(mydisplay, &dcount); - - if (xai == NULL || dcount == 0) { - debugrr("XineramaQueryScreens failed\n"); - XCloseDisplay(mydisplay); - return NULL; - } - debugrr2((errout,"get_displays using %d Xinerama Screens\n",dcount)); - } else { - dcount = ScreenCount(mydisplay); - debugrr2((errout,"get_displays using %d X11 Screens\n",dcount)); - } - - /* Allocate our list */ - if ((disps = (disppath **)calloc(sizeof(disppath *), dcount + 1)) == NULL) { - debugrr("get_displays failed on malloc\n"); - XCloseDisplay(mydisplay); - return NULL; - } - for (i = 0; i < dcount; i++) { - if ((disps[i] = calloc(sizeof(disppath), 1)) == NULL) { - debugrr("get_displays failed on malloc\n"); - free_disppaths(disps); - XCloseDisplay(mydisplay); - return NULL; - } - } - - /* Create a description for each screen */ - for (i = 0; i < dcount; i++) { - XF86VidModeMonitor monitor; - int evb = 0, erb = 0; - char *pp; - - /* Form the display name */ - if ((pp = strrchr(dnbuf, ':')) != NULL) { - if ((pp = strchr(pp, '.')) != NULL) { - if (xai != NULL) /* Xinerama */ - sprintf(pp,".%d",0); - else - sprintf(pp,".%d",i); - } - } - if ((disps[i]->name = strdup(dnbuf)) == NULL) { - debugrr("get_displays failed on malloc\n"); - free_disppaths(disps); - XCloseDisplay(mydisplay); - return NULL; - } - - debugrr2((errout, "Display %d name = '%s'\n",i,disps[i]->name)); - if (xai != NULL) { /* Xinerama */ - /* xai[i].screen_number should be == i */ - disps[i]->screen = 0; /* Assume Xinerame creates a single virtual X11 screen */ - disps[i]->uscreen = i; /* Underlying Xinerma screen */ - disps[i]->rscreen = i; - disps[i]->sx = xai[i].x_org; - disps[i]->sy = xai[i].y_org; - disps[i]->sw = xai[i].width; - disps[i]->sh = xai[i].height; - } else { /* Plain X11 Screens */ - disps[i]->screen = i; - disps[i]->uscreen = i; - disps[i]->rscreen = i; - disps[i]->sx = 0; /* Must be 0 */ - disps[i]->sy = 0; - disps[i]->sw = DisplayWidth(mydisplay, disps[i]->screen); - disps[i]->sh = DisplayHeight(mydisplay, disps[i]->screen); - } - - /* Create the X11 root atom of the default screen */ - /* that may contain the associated ICC profile */ - if (disps[i]->uscreen == 0) - strcpy(desc1, "_ICC_PROFILE"); - else - sprintf(desc1, "_ICC_PROFILE_%d",disps[i]->uscreen); - - if ((disps[i]->icc_atom = XInternAtom(mydisplay, desc1, False)) == None) - fprintf(stderr, "Unable to intern atom '%s'",desc1); - - /* See if we can locate the EDID of the monitor for this screen */ - for (j = 0; j < 2; j++) { - char edid_name[50]; - Atom edid_atom, ret_type; - int ret_format = 8; - long ret_len, ret_togo; - unsigned char *atomv = NULL; - - if (disps[i]->uscreen == 0) { - if (j == 0) - strcpy(edid_name,"XFree86_DDC_EDID1_RAWDATA"); - else - strcpy(edid_name,"XFree86_DDC_EDID2_RAWDATA"); - } else { - if (j == 0) - sprintf(edid_name,"XFree86_DDC_EDID1_RAWDATA_%d",disps[i]->uscreen); - else - sprintf(edid_name,"XFree86_DDC_EDID2_RAWDATA_%d",disps[i]->uscreen); - } - - if ((edid_atom = XInternAtom(mydisplay, edid_name, True)) == None) - continue; - if (XGetWindowProperty(mydisplay, RootWindow(mydisplay, disps[i]->uscreen), edid_atom, - 0, 0x7ffffff, False, XA_INTEGER, - &ret_type, &ret_format, &ret_len, &ret_togo, &atomv) == Success - && (ret_len == 128 || ret_len == 256 || ret_len == 384)) { - if ((disps[i]->edid = malloc(sizeof(unsigned char) * ret_len)) == NULL) { - debugrr("get_displays failed on malloc\n"); - free_disppaths(disps); - XCloseDisplay(mydisplay); - return NULL; - } - memmove(disps[i]->edid, atomv, ret_len); - disps[i]->edid_len = ret_len; - XFree(atomv); - debugrr2((errout, "Got EDID for display\n")); - break; - } else { - debugrr2((errout, "Failed to get EDID for display\n")); - } - } - - if (XF86VidModeQueryExtension(mydisplay, &evb, &erb) != 0) { - /* Some propietary multi-screen drivers (ie. TwinView & MergeFB) */ - /* don't implement the XVidMode extension properly. */ - monitor.model = NULL; - if (XF86VidModeGetMonitor(mydisplay, disps[i]->uscreen, &monitor) != 0 - && monitor.model != NULL && monitor.model[0] != '\000') - sprintf(desc1, "%s",monitor.model); - else - sprintf(desc1,"Monitor %d",i+1); - } else - sprintf(desc1,"Monitor %d",i+1); - - sprintf(desc2,"%s at %d, %d, width %d, height %d",desc1, - disps[i]->sx, disps[i]->sy, disps[i]->sw, disps[i]->sh); - if ((disps[i]->description = strdup(desc2)) == NULL) { - debugrr("get_displays failed on malloc\n"); - free_disppaths(disps); - XCloseDisplay(mydisplay); - return NULL; - } - } - XSetErrorHandler(NULL); - - /* Put the default Screen the top of the list */ - if (xai == NULL) { -// int defsix = DefaultScreen(mydisplay); - defsix = DefaultScreen(mydisplay); - disppath *tdispp; - tdispp = disps[defsix]; - disps[defsix] = disps[0]; - disps[0] = tdispp; - } - } - - if (xai != NULL) - XFree(xai); - - XCloseDisplay(mydisplay); - -#endif /* UNIX X11 */ - return disps; -} - -// END get_displays - -/* Free a whole list of display paths */ -void free_disppaths(disppath **disps) { - if (disps != NULL) { - int i; - for (i = 0; ; i++) { - if (disps[i] == NULL) - break; - - if (disps[i]->name != NULL) - free(disps[i]->name); - if (disps[i]->description != NULL) - free(disps[i]->description); -#if defined(UNIX) && !defined(__APPLE__) - if (disps[i]->edid != NULL) - free(disps[i]->edid); -#endif - free(disps[i]); - } - free(disps); - } -} - -// START get_a_display - -/* ----------------------------------------------- */ -/* Deal with selecting a display */ - -/* Return the given display given its index 0..n-1 */ -disppath *get_a_display(int ix) { - disppath **paths, *rv = NULL; - int i; - - debugrr2((errout, "get_a_display called with ix %d\n",ix)); - - if ((paths = get_displays()) == NULL) - return NULL; - - for (i = 0; ;i++) { - if (paths[i] == NULL) { - free_disppaths(paths); - return NULL; - } - if (i == ix) { - break; - } - } - if ((rv = malloc(sizeof(disppath))) == NULL) { - debugrr("get_a_display failed malloc\n"); - free_disppaths(paths); - return NULL; - } - *rv = *paths[i]; /* Structure copy */ - if ((rv->name = strdup(paths[i]->name)) == NULL) { - debugrr("get_displays failed on malloc\n"); - free(rv->description); - free(rv); - free_disppaths(paths); - return NULL; - } - if ((rv->description = strdup(paths[i]->description)) == NULL) { - debugrr("get_displays failed on malloc\n"); - free(rv); - free_disppaths(paths); - return NULL; - } -#if defined(UNIX) && !defined(__APPLE__) - if (paths[i]->edid != NULL) { - if ((rv->edid = malloc(sizeof(unsigned char) * paths[i]->edid_len)) == NULL) { - debugrr("get_displays failed on malloc\n"); - free(rv); - free_disppaths(paths); - return NULL; - } - rv->edid_len = paths[i]->edid_len; - memmove(rv->edid, paths[i]->edid, rv->edid_len ); - } -#endif - debugrr2((errout, " Selected ix %d '%s' %s'\n",i,rv->name,rv->description)); - - free_disppaths(paths); - return rv; -} -// END get_a_display - -void free_a_disppath(disppath *path) { - if (path != NULL) { - if (path->name != NULL) - free(path->name); - if (path->description != NULL) - free(path->description); -#if defined(UNIX) && !defined(__APPLE__) - if (path->edid != NULL) - free(path->edid); -#endif - free(path); - } -} - -// MAIN - -typedef struct { - int width_mm, height_mm; -} size_mm; - -static size_mm get_real_screen_size_mm_disp(disppath *disp) { - size_mm size; -#ifdef NT - HDC hdc = NULL; -#endif -#ifdef __APPLE__ - CGSize sz; /* Display size in mm */ -#endif -#if defined(UNIX) && !defined(__APPLE__) - char *pp, *bname; /* base display name */ - Display *mydisplay; - int myscreen; /* Usual or virtual screen with Xinerama */ -#endif - - size.width_mm = 0; - size.height_mm = 0; - - if (disp == NULL) return size; - -#ifdef NT - hdc = CreateDC(disp->name, NULL, NULL, NULL); - if (hdc == NULL) { - return size; - } - size.width_mm = GetDeviceCaps(hdc, HORZSIZE); - size.height_mm = GetDeviceCaps(hdc, VERTSIZE); - DeleteDC(hdc); -#endif -#ifdef __APPLE__ - sz = CGDisplayScreenSize(disp->ddid); - size.width_mm = sz.width; - size.height_mm = sz.height; -#endif -#if defined(UNIX) && !defined(__APPLE__) - /* Create the base display name (in case of Xinerama, XRandR) */ - if ((bname = strdup(disp->name)) == NULL) { - return size; - } - if ((pp = strrchr(bname, ':')) != NULL) { - if ((pp = strchr(pp, '.')) != NULL) { - sprintf(pp,".%d",disp->screen); - } - } - - /* open the display */ - mydisplay = XOpenDisplay(bname); - if(!mydisplay) { - debugr2((errout,"Unable to open display '%s'\n",bname)); - free(bname); - return size; - } - free(bname); - debugr("Opened display OK\n"); - - myscreen = disp->screen; - - size.width_mm = DisplayWidthMM(mydisplay, myscreen); - size.height_mm = DisplayHeightMM(mydisplay, myscreen); - - XCloseDisplay(mydisplay); -#endif - - return size; -} - -static size_mm get_real_screen_size_mm(int ix) { - size_mm size; - disppath *disp = NULL; - - disp = get_a_display(ix); - - size = get_real_screen_size_mm_disp(disp); - - free_a_disppath(disp); - return size; -} - -static int get_xrandr_output_xid(int ix) { - int xid = 0; -#if defined(UNIX) && !defined(__APPLE__) -#if RANDR_MAJOR == 1 && RANDR_MINOR >= 2 - disppath *disp = NULL; - - disp = get_a_display(ix); - - if (disp == NULL) return 0; - - xid = disp->output; - free_a_disppath(disp); -#endif -#endif - - return xid; -} - -static PyObject * enumerate_displays(PyObject *self, PyObject *args) -{ - PyObject *l = PyList_New(0); - disppath **dp; - - dp = get_displays(); - - if (dp != NULL && dp[0] != NULL) { - PyObject* value; - PyObject *d; - size_mm size; - int i; - for (i = 0; ; i++) { - if (dp[i] == NULL) - break; - - d = PyDict_New(); - if (dp[i]->name != NULL && (value = PyString_FromString(dp[i]->name)) != NULL) { - PyDict_SetItemString(d, "name", value); - } - - if (dp[i]->description != NULL && - (value = PyString_FromString(dp[i]->description)) != NULL) { - PyDict_SetItemString(d, "description", value); - } - - value = Py_BuildValue("(i,i)", dp[i]->sx, dp[i]->sy); - PyDict_SetItemString(d, "pos", value); - - value = Py_BuildValue("(i,i)", dp[i]->sw, dp[i]->sh); - PyDict_SetItemString(d, "size", value); - - size = get_real_screen_size_mm_disp(dp[i]); - // TODO: The ``value`` doesn't match realworld!!! - value = Py_BuildValue("(i,i)", size.width_mm, size.height_mm); - PyDict_SetItemString(d, "size_mm", value); - -#ifdef NT - if (dp[i]->monid != NULL && - (value = PyString_FromString(dp[i]->monid)) != NULL) { - PyDict_SetItemString(d, "DeviceID", value); - } - - value = PyBool_FromLong(dp[i]->prim); - PyDict_SetItemString(d, "is_primary", value); -#endif /* NT */ - -#ifdef __APPLE__ - //value = PyLong_FromLong(dp[i]->ddid); - //PyDict_SetItemString(d, "CGDirectDisplayID", value); -#endif /* __APPLE__ */ - -#if defined(UNIX) && !defined(__APPLE__) - value = PyLong_FromLong(dp[i]->screen); - PyDict_SetItemString(d, "x11_screen", value); - - value = PyLong_FromLong(dp[i]->uscreen); - PyDict_SetItemString(d, "screen", value); - - value = PyLong_FromLong(dp[i]->rscreen); - PyDict_SetItemString(d, "ramdac_screen", value); - - value = PyLong_FromLong(dp[i]->icc_atom); - PyDict_SetItemString(d, "icc_profile_atom_id", value); - - if (dp[i]->edid_len > 0 && dp[i]->edid != NULL && - (value = PyString_FromStringAndSize(dp[i]->edid, dp[i]->edid_len)) != NULL) { - PyDict_SetItemString(d, "edid", value); - } -#if RANDR_MAJOR == 1 && RANDR_MINOR >= 2 - //value = PyLong_FromLong(dp[i]->crtc); - //PyDict_SetItemString(d, "crtc", value); - - value = PyLong_FromLong(dp[i]->output); - PyDict_SetItemString(d, "output", value); - - value = PyLong_FromLong(dp[i]->icc_out_atom); - PyDict_SetItemString(d, "icc_profile_output_atom_id", value); -#endif /* randr >= V 1.2 */ -#endif /* UNIX */ - - PyList_Append(l, d); - } - } - free_disppaths(dp); - return l; -} - -static PyObject * -RealDisplaySizeMM(PyObject *self, PyObject *args) -{ - int ix; - size_mm size; - - if (!PyArg_ParseTuple(args, "i", &ix)) return NULL; - - size = get_real_screen_size_mm(ix); - - return Py_BuildValue("(i,i)", size.width_mm, size.height_mm); -} - -static PyObject * -GetXRandROutputXID(PyObject *self, PyObject *args) -{ - int ix; - int xid; - - if (!PyArg_ParseTuple(args, "i", &ix)) return NULL; - - xid = get_xrandr_output_xid(ix); - - return Py_BuildValue("i", xid); -} - -static PyMethodDef RealDisplaySizeMM_methods[] = { - {"enumerate_displays", enumerate_displays, METH_NOARGS, "Enumerate and return a list of displays."}, - {"RealDisplaySizeMM", RealDisplaySizeMM, METH_VARARGS, "RealDisplaySizeMM(int displayNum)\nReturn the size (in mm) of a given display."}, - {"GetXRandROutputXID", GetXRandROutputXID, METH_VARARGS, "GetXRandROutputXID(int displayNum)\nReturn the XRandR output X11 ID of a given display."}, - {NULL, NULL, 0, NULL} /* Sentinel - marks the end of this structure */ -}; - - -#ifdef IS_PY3K - - static struct PyModuleDef RealDisplaySizeMM_ModuleDef = - { - PyModuleDef_HEAD_INIT, - "RealDisplaySizeMM", /* name of module */ - "", /* module documentation, may be NULL */ - -1, /* size of per-interpreter state of the module, or -1 if the module keeps state in global variables. */ - RealDisplaySizeMM_methods - }; - - PyMODINIT_FUNC PyInit_RealDisplaySizeMM(void) - { - return PyModule_Create(&RealDisplaySizeMM_ModuleDef); - } - -#else - - PyMODINIT_FUNC initRealDisplaySizeMM(void) - { - Py_InitModule("RealDisplaySizeMM", RealDisplaySizeMM_methods); - } -#endif