]> git.donarmstrong.com Git - deb_pkgs/autorandr.git/commitdiff
Merge broadcast_rgb feature (#204)
authorPhillip Berndt <phillip.berndt@googlemail.com>
Thu, 16 Dec 2021 09:59:16 +0000 (10:59 +0100)
committerPhillip Berndt <phillip.berndt@googlemail.com>
Thu, 16 Dec 2021 09:59:16 +0000 (10:59 +0100)
1  2 
autorandr.py

diff --combined autorandr.py
index 24d8b3a9df0674291c91ac88756975fc08223315,f47a994ca2209040569ab76283ea8b2ef45f92f8..ec159bf6a0b5fc4a85b855f58cfba04b4a371f38
@@@ -1,4 -1,4 +1,4 @@@
 -#!/usr/bin/env python
 +#!/usr/bin/env python3
  # encoding: utf-8
  #
  # autorandr.py
@@@ -39,14 -39,10 +39,14 @@@ import tim
  import glob
  
  from collections import OrderedDict
 -from distutils.version import LooseVersion as Version
  from functools import reduce
  from itertools import chain
  
 +try:
 +    from packaging.version import Version
 +except ModuleNotFoundError:
 +    from distutils.version import LooseVersion as Version
 +
  if sys.version_info.major == 2:
      import ConfigParser as configparser
  else:
@@@ -68,6 -64,20 +68,20 @@@ virtual_profiles = 
      ("vertical", "Stack all connected outputs vertically at their largest resolution", None),
  ]
  
+ properties = [
+     "Colorspace",
+     "max bpc",
+     "aspect ratio",
+     "Broadcast RGB",
+     "audio",
+     "non-desktop",
+     "TearFree",
+     "underscan vborder",
+     "underscan hborder",
+     "underscan",
+     "scaling mode",
+ ]
  help_text = """
  Usage: autorandr [options]
  
  --batch                 run autorandr for all users with active X11 sessions
  --current               only list current (active) configuration(s)
  --config                dump your current xrandr setup
 +--cycle                 automatically load the next detected profile
  --debug                 enable verbose output
  --detected              only list detected (available) configuration(s)
  --dry-run               don't change anything, only print the xrandr commands
  --fingerprint           fingerprint your current hardware setup
 +--match-edid            match diplays based on edid instead of name
  --force                 force (re)loading of a profile / overwrite exiting files
 +--list                  list configurations
  --skip-options <option> comma separated list of xrandr arguments (e.g. "gamma")
                          to skip both in detecting changes and applying a profile
  --version               show version information and exit
@@@ -157,6 -164,11 +171,11 @@@ class AutorandrException(Exception)
  class XrandrOutput(object):
      "Represents an XRandR output"
  
+     XRANDR_PROPERTIES_REGEXP = "|".join(
+         [r"{}:\s*(?P<{}>[\S ]*\S+)"
+          .format(re.sub(r"\s", r"\\\g<0>", p), re.sub(r"\W+", "_", p.lower()))
+             for p in properties])
      # This regular expression is used to parse an output in `xrandr --verbose'
      XRANDR_OUTPUT_REGEXP = """(?x)
          ^\s*(?P<output>\S[^ ]*)\s+                                                      # Line starts with output name
              CRTC:\s*(?P<crtc>[0-9]) |                                                   # CRTC value
              Transform: (?P<transform>(?:[\-0-9\. ]+\s+){3}) |                           # Transformation matrix
              EDID: (?P<edid>\s*?(?:\\n\\t\\t[0-9a-f]+)+) |                               # EDID of the output
+             """ + XRANDR_PROPERTIES_REGEXP + """ |                                      # Properties to include in the profile
              (?![0-9])[^:\s][^:\n]+:.*(?:\s\\t[\\t ].+)*                                 # Other properties
          ))+
          \s*
          "Return the command line parameters for XRandR for this instance"
          args = ["--output", self.output]
          for option, arg in sorted(self.options_with_defaults.items()):
-             args.append("--%s" % option)
+             if option[:5] == "prop-":
+                 prop_found = False
+                 for prop, xrandr_prop in [(re.sub(r"\W+", "_", p.lower()), p) for p in properties]:
+                     if prop == option[5:]:
+                         args.append("--set")
+                         args.append(xrandr_prop)
+                         prop_found = True
+                         break
+                 if not prop_found:
+                     print("Warning: Unknown property `%s' in config file. Skipping." % option[5:], file=sys.stderr)
+                     continue
+             else:
+                 args.append("--%s" % option)
              if arg:
                  args.append(arg)
          return args
                  options["crtc"] = match["crtc"]
              if match["rate"]:
                  options["rate"] = match["rate"]
+             for prop in [re.sub(r"\W+", "_", p.lower()) for p in properties]:
+                 if match[prop]:
+                     options["prop-" + prop] = match[prop]
  
          return XrandrOutput(match["output"], edid, options), modes
  
@@@ -600,36 -628,6 +635,36 @@@ def match_asterisk(pattern, data)
      return matched * 1. / total
  
  
 +def update_profiles_edid(profiles, config):
 +    edid_map = {}
 +    for c in config:
 +        if config[c].edid is not None:
 +            edid_map[config[c].edid] = c
 +
 +    for p in profiles:
 +        profile_config = profiles[p]["config"]
 +
 +        for edid in edid_map:
 +            for c in profile_config.keys():
 +                if profile_config[c].edid != edid or c == edid_map[edid]:
 +                    continue
 +
 +                print("%s: renaming display %s to %s" % (p, c, edid_map[edid]))
 +
 +                tmp_disp = profile_config[c]
 +
 +                if edid_map[edid] in profile_config:
 +                    # Swap the two entries
 +                    profile_config[c] = profile_config[edid_map[edid]]
 +                    profile_config[c].output = c
 +                else:
 +                    # Object is reassigned to another key, drop this one
 +                    del profile_config[c]
 +
 +                profile_config[edid_map[edid]] = tmp_disp
 +                profile_config[edid_map[edid]].output = edid_map[edid]
 +
 +
  def find_profiles(current_config, profiles):
      "Find profiles matching the currently connected outputs, sorting asterisk matches to the back"
      detected_profiles = []
          if not matches or any((name not in config.keys() for name in current_config.keys() if current_config[name].edid)):
              continue
          if matches:
-             closeness = max(match_asterisk(output.edid, current_config[name].edid), match_asterisk(current_config[name].edid, output.edid))
+             closeness = max(match_asterisk(output.edid, current_config[name].edid), match_asterisk(
+                 current_config[name].edid, output.edid))
              detected_profiles.append((closeness, profile_name))
      detected_profiles = [o[1] for o in sorted(detected_profiles, key=lambda x: -x[0])]
      return detected_profiles
@@@ -1246,9 -1245,9 +1282,9 @@@ def read_config(options, directory)
  def main(argv):
      try:
          opts, args = getopt.getopt(argv[1:], "s:r:l:d:cfh",
 -                                   ["batch", "dry-run", "change", "default=", "save=", "remove=", "load=",
 +                                   ["batch", "dry-run", "change", "cycle", "default=", "save=", "remove=", "load=",
                                      "force", "fingerprint", "config", "debug", "skip-options=", "help",
 -                                    "current", "detected", "version"])
 +                                    "list", "current", "detected", "version", "match-edid"])
      except getopt.GetoptError as e:
          print("Failed to parse options: {0}.\n"
                "Use --help to get usage information.".format(str(e)),
              profiles.update(load_profiles(profile_path))
              profile_symlinks.update(get_symlinks(profile_path))
              read_config(options, profile_path)
 -        # Sort by descending mtime
 -        profiles = OrderedDict(sorted(profiles.items(), key=lambda x: -x[1]["config-mtime"]))
      except Exception as e:
          raise AutorandrException("Failed to load profiles", e)
  
 -    profile_symlinks = {k: v for k, v in profile_symlinks.items() if v in (x[0] for x in virtual_profiles) or v in profiles}
 -
      exec_scripts(None, "predetect")
      config, modes = parse_xrandr_output()
  
 +    if "--match-edid" in options:
 +        update_profiles_edid(profiles, config)
 +
 +    # Sort by mtime
 +    sort_direction = -1
 +    if "--cycle" in options:
 +        # When cycling through profiles, put the profile least recently used to the top of the list
 +        sort_direction = 1
 +    profiles = OrderedDict(sorted(profiles.items(), key=lambda x: sort_direction * x[1]["config-mtime"]))
 +    profile_symlinks = {k: v for k, v in profile_symlinks.items() if v in (x[0] for x in virtual_profiles) or v in profiles}
 +
      if "--fingerprint" in options:
          output_setup(config, sys.stdout)
          sys.exit(0)
          best_index = 9999
          for profile_name in profiles.keys():
              if profile_blocked(os.path.join(profile_path, profile_name), block_script_metadata):
 -                if "--current" not in options and "--detected" not in options:
 +                if not any(opt in options for opt in ("--current", "--detected", "--list")):
                      print("%s (blocked)" % profile_name)
                  continue
              props = []
 +            is_current_profile = profile_name in current_profiles
              if profile_name in detected_profiles:
                  if len(detected_profiles) == 1:
                      index = 1
                  else:
                      index = detected_profiles.index(profile_name) + 1
                      props.append("(detected) (%d%s match)" % (index, ["st", "nd", "rd"][index - 1] if index < 4 else "th"))
 -                if ("-c" in options or "--change" in options) and index < best_index:
 -                    load_profile = profile_name
 -                    best_index = index
 +                if index < best_index:
 +                    if "-c" in options or "--change" in options or ("--cycle" in options and not is_current_profile):
 +                        load_profile = profile_name
 +                        best_index = index
              elif "--detected" in options:
                  continue
 -            if profile_name in current_profiles:
 +            if is_current_profile:
                  props.append("(current)")
              elif "--current" in options:
                  continue
 -            if "--current" in options or "--detected" in options:
 +            if any(opt in options for opt in ("--current", "--detected", "--list")):
                  print("%s" % (profile_name, ))
              else:
                  print("%s%s%s" % (profile_name, " " if props else "", " ".join(props)))
  
      if "-d" in options:
          options["--default"] = options["-d"]
 -    if not load_profile and "--default" in options and ("-c" in options or "--change" in options):
 +    if not load_profile and "--default" in options and ("-c" in options or "--change" in options or "--cycle" in options):
          load_profile = options["--default"]
  
      if load_profile:
                  scripts_path = profile["path"]
              except KeyError:
                  raise AutorandrException("Failed to load profile '%s': Profile not found" % load_profile)
 -            if load_profile in detected_profiles and detected_profiles[0] != load_profile:
 +            if "--dry-run" not in options:
                  update_mtime(os.path.join(scripts_path, "config"))
          add_unused_outputs(config, load_config)
          if load_config == dict(config) and "-f" not in options and "--force" not in options: