]> git.donarmstrong.com Git - deb_pkgs/autorandr.git/blobdiff - autorandr.py
Version bump: autorandr 1.5
[deb_pkgs/autorandr.git] / autorandr.py
index fbc1445bf09e015e2214a7a4c4d53f438fa54a16..5a0a6860fea0c82bf72e1521781cbd06513f71a4 100755 (executable)
@@ -26,6 +26,7 @@ from __future__ import print_function
 
 import binascii
 import copy
+import fnmatch
 import getopt
 import hashlib
 import os
@@ -42,6 +43,13 @@ from distutils.version import LooseVersion as Version
 from functools import reduce
 from itertools import chain
 
+if sys.version_info.major == 2:
+    import ConfigParser as configparser
+else:
+    import configparser
+
+__version__ = "1.5"
+
 try:
     input = raw_input
 except NameError:
@@ -49,6 +57,7 @@ except NameError:
 
 virtual_profiles = [
     # (name, description, callback)
+    ("off", "Disable all outputs", None),
     ("common", "Clone all connected outputs at the largest common resolution", None),
     ("clone-largest", "Clone all connected outputs with the largest resolution (scaled down if necessary)", None),
     ("horizontal", "Stack all connected outputs horizontally at their largest resolution", None),
@@ -60,32 +69,28 @@ Usage: autorandr [options]
 
 -h, --help              get this small help
 -c, --change            reload current setup
+-d, --default <profile> make profile <profile> the default profile
+-l, --load <profile>    load profile <profile>
 -s, --save <profile>    save your current setup to profile <profile>
 -r, --remove <profile>  remove profile <profile>
--l, --load <profile>    load profile <profile>
--d, --default <profile> make profile <profile> the default profile
---skip-options <option> comma separated list of xrandr arguments (e.g. "gamma")
-                        to skip both in detecting changes and applying a profile
---force                 force (re)loading of a profile
---fingerprint           fingerprint your current hardware setup
+--batch                 run autorandr for all users with active X11 sessions
+--current               only list current (active) configuration(s)
 --config                dump your current xrandr setup
---dry-run               don't change anything, only print the xrandr commands
 --debug                 enable verbose output
---batch                 run autorandr for all users with active X11 sessions
-
- To prevent a profile from being loaded, place a script called "block" in its
- directory. The script is evaluated before the screen setup is inspected, and
- in case of it returning a value of 0 the profile is skipped. This can be used
- to query the status of a docking station you are about to leave.
+--detected              only list detected (available) configuration(s)
+--dry-run               don't change anything, only print the xrandr commands
+--fingerprint           fingerprint your current hardware setup
+--force                 force (re)loading of a profile
+--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
 
  If no suitable profile can be identified, the current configuration is kept.
  To change this behaviour and switch to a fallback configuration, specify
  --default <profile>.
 
- Another script called "postswitch" can be placed in the directory
- ~/.config/autorandr (or ~/.autorandr if you have an old installation) as well
- as in any profile directories: The scripts are executed after a mode switch
- has taken place and can notify window managers.
+ autorandr supports a set of per-profile and global hooks. See the documentation
+ for details.
 
  The following virtual configurations are available:
 """.strip()
@@ -398,6 +403,10 @@ class XrandrOutput(object):
                 return hashlib.md5(binascii.unhexlify(other.edid)).hexdigest() == self.edid
             if len(self.edid) != 32 and len(other.edid) == 32 and not self.edid.startswith(XrandrOutput.EDID_UNAVAILABLE):
                 return hashlib.md5(binascii.unhexlify(self.edid)).hexdigest() == other.edid
+            if "*" in self.edid:
+                return fnmatch.fnmatch(other.edid, self.edid)
+            elif "*" in other.edid:
+                return fnmatch.fnmatch(self.edid, other.edid)
         return self.edid == other.edid
 
     def __ne__(self, other):
@@ -663,9 +672,9 @@ def apply_configuration(new_configuration, current_configuration, dry_run=False)
 
             option_vector = new_configuration[output].option_vector
             if xrandr_version() >= Version("1.3.0"):
-                for option in ("transform", "panning"):
+                for option, off_value in (("transform", "none"), ("panning", "0x0")):
                     if option in current_configuration[output].options:
-                        auxiliary_changes_pre.append(["--output", output, "--%s" % option, "none"])
+                        auxiliary_changes_pre.append(["--output", output, "--%s" % option, off_value])
                     else:
                         try:
                             option_index = option_vector.index("--%s" % option)
@@ -769,13 +778,13 @@ def generate_virtual_profile(configuration, modes, profile_name):
         for output in configuration:
             configuration[output].options = {}
             if output in modes and configuration[output].edid:
-                def key(a, b):
+                def key(a):
                     score = int(a["width"]) * int(a["height"])
                     if a["preferred"]:
                         score += 10**6
                     return score
-                modes = sorted(modes[output], key=key)
-                mode = modes[-1]
+                output_modes = sorted(modes[output], key=key)
+                mode = output_modes[-1]
                 configuration[output].options["mode"] = mode["name"]
                 configuration[output].options["rate"] = mode["rate"]
                 configuration[output].options["pos"] = pos_specifier % shift
@@ -789,13 +798,13 @@ def generate_virtual_profile(configuration, modes, profile_name):
         for output in configuration:
             configuration[output].options = {}
             if output in modes and configuration[output].edid:
-                def key(a, b):
+                def key(a):
                     score = int(a["width"]) * int(a["height"])
                     if a["preferred"]:
                         score += 10**6
                     return score
-                modes = sorted(modes[output], key=key)
-                mode = modes[-1]
+                output_modes = sorted(modes[output], key=key)
+                mode = output_modes[-1]
                 configuration[output].options["mode"] = mode["name"]
                 configuration[output].options["rate"] = mode["rate"]
                 configuration[output].options["pos"] = "0x0"
@@ -806,6 +815,11 @@ def generate_virtual_profile(configuration, modes, profile_name):
                 configuration[output].options["transform"] = "{},0,{},0,{},{},0,0,1".format(scale, mov_x, scale, mov_y)
             else:
                 configuration[output].options["off"] = None
+    elif profile_name == "off":
+        for output in configuration:
+            for key in list(configuration[output].options.keys()):
+                del configuration[output].options[key]
+            configuration[output].options["off"] = None
     return configuration
 
 
@@ -813,18 +827,18 @@ def print_profile_differences(one, another):
     "Print the differences between two profiles for debugging"
     if one == another:
         return
-    print("| Differences between the two profiles:", file=sys.stderr)
+    print("| Differences between the two profiles:")
     for output in set(chain.from_iterable((one.keys(), another.keys()))):
         if output not in one:
             if "off" not in another[output].options:
-                print("| Output `%s' is missing from the active configuration" % output, file=sys.stderr)
+                print("| Output `%s' is missing from the active configuration" % output)
         elif output not in another:
             if "off" not in one[output].options:
-                print("| Output `%s' is missing from the new configuration" % output, file=sys.stderr)
+                print("| Output `%s' is missing from the new configuration" % output)
         else:
             for line in one[output].verbose_diff(another[output]):
-                print("| [Output %s] %s" % (output, line), file=sys.stderr)
-    print("\\-", file=sys.stderr)
+                print("| [Output %s] %s" % (output, line))
+    print("\\-")
 
 
 def exit_help():
@@ -872,12 +886,11 @@ def exec_scripts(profile_path, script_name, meta_information=None):
 
     candidate_directories = [user_profile_path]
     for config_dir in os.environ.get("XDG_CONFIG_DIRS", "/etc/xdg").split(":"):
-        candidate_directories += os.path.join(config_dir, "autorandr")
+        candidate_directories.append(os.path.join(config_dir, "autorandr"))
     if profile_path:
-        candidate_directories += profile_path
+        candidate_directories.append(profile_path)
 
     for folder in candidate_directories:
-
         if script_name not in ran_scripts:
             script = os.path.join(folder, script_name)
             if os.access(script, os.X_OK | os.F_OK):
@@ -964,8 +977,8 @@ def dispatch_call_to_sessions(argv):
 
         process_environ = {}
         for environ_entry in open(environ_file).read().split("\0"):
-            if "=" in environ_entry:
-                name, value = environ_entry.split("=", 1)
+            name, sep, value = environ_entry.partition("=")
+            if name and sep:
                 if name == "DISPLAY" and "." in value:
                     value = value[:value.find(".")]
                 process_environ[name] = value
@@ -1010,11 +1023,21 @@ def dispatch_call_to_sessions(argv):
             X11_displays_done.add(display)
 
 
+def read_config(options, directory):
+    """Parse a configuration config.ini from directory and merge it into
+    the options dictionary"""
+    config = configparser.ConfigParser()
+    config.read(os.path.join(directory, "settings.ini"))
+    if config.has_section("config"):
+        for key, value in config.items("config"):
+            options.setdefault("--%s" % key, value)
+
 def main(argv):
     try:
         opts, args = getopt.getopt(argv[1:], "s:r:l:d:cfh",
                                    ["batch", "dry-run", "change", "default=", "save=", "remove=", "load=",
-                                    "force", "fingerprint", "config", "debug", "skip-options=", "help"])
+                                    "force", "fingerprint", "config", "debug", "skip-options=", "help",
+                                    "current", "detected", "version"])
     except getopt.GetoptError as e:
         print("Failed to parse options: {0}.\n"
               "Use --help to get usage information.".format(str(e)),
@@ -1026,6 +1049,14 @@ def main(argv):
     if "-h" in options or "--help" in options:
         exit_help()
 
+    if "--version" in options:
+        print("autorandr " + __version__)
+        sys.exit(0)
+
+    if "--current" in options and "--detected" in options:
+        print("--current and --detected are mutually exclusive.", file=sys.stderr)
+        sys.exit(posix.EX_USAGE)
+
     # Batch mode
     if "--batch" in options:
         if ("DISPLAY" not in os.environ or not os.environ["DISPLAY"]) and os.getuid() == 0:
@@ -1048,6 +1079,7 @@ def main(argv):
             if os.path.isdir(system_profile_path):
                 profiles.update(load_profiles(system_profile_path))
                 profile_symlinks.update(get_symlinks(system_profile_path))
+                read_config(options, system_profile_path)
         # For the user's profiles, prefer the legacy ~/.autorandr if it already exists
         # profile_path is also used later on to store configurations
         profile_path = os.path.expanduser("~/.autorandr")
@@ -1057,6 +1089,7 @@ def main(argv):
         if os.path.isdir(profile_path):
             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:
@@ -1151,16 +1184,24 @@ def main(argv):
 
         for profile_name in profiles.keys():
             if profile_blocked(os.path.join(profile_path, profile_name), block_script_metadata):
-                print("%s (blocked)" % profile_name, file=sys.stderr)
+                if "--current" not in options and "--detected" not in options:
+                    print("%s (blocked)" % profile_name)
                 continue
             props = []
             if profile_name in detected_profiles:
                 props.append("(detected)")
                 if ("-c" in options or "--change" in options) and not load_profile:
                     load_profile = profile_name
+            elif "--detected" in options:
+                continue
             if profile_name in current_profiles:
                 props.append("(current)")
-            print("%s%s%s" % (profile_name, " " if props else "", " ".join(props)), file=sys.stderr)
+            elif "--current" in options:
+                continue
+            if "--current" in options or "--detected" in options:
+                print("%s" % (profile_name, ))
+            else:
+                print("%s%s%s" % (profile_name, " " if props else "", " ".join(props)))
             if not configs_are_equal and "--debug" in options and profile_name in detected_profiles:
                 print_profile_differences(config, profiles[profile_name]["config"])