]> git.donarmstrong.com Git - deb_pkgs/autorandr.git/blobdiff - autorandr.py
Retry failed xrandr calls after waiting for one second to mitigate #47
[deb_pkgs/autorandr.git] / autorandr.py
index 596d3ffb504d20f04d3cbad2dba2956efe660ddf..812329ddf72e78d8dd01bc158fa37cc74ed1cdc1 100755 (executable)
@@ -23,6 +23,7 @@
 #
 
 from __future__ import print_function
 #
 
 from __future__ import print_function
+import time
 
 import binascii
 import copy
 
 import binascii
 import copy
@@ -33,12 +34,17 @@ import posix
 import re
 import subprocess
 import sys
 import re
 import subprocess
 import sys
+import shutil
 
 from collections import OrderedDict
 from distutils.version import LooseVersion as Version
 from functools import reduce
 from itertools import chain
 
 
 from collections import OrderedDict
 from distutils.version import LooseVersion as Version
 from functools import reduce
 from itertools import chain
 
+try:
+    input = raw_input
+except NameError:
+    pass
 
 virtual_profiles = [
     # (name, description, callback)
 
 virtual_profiles = [
     # (name, description, callback)
@@ -53,6 +59,7 @@ Usage: autorandr [options]
 -h, --help              get this small help
 -c, --change            reload current setup
 -s, --save <profile>    save your current setup to profile <profile>
 -h, --help              get this small help
 -c, --change            reload current setup
 -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")
 -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")
@@ -522,6 +529,25 @@ def update_mtime(filename):
     except:
         return False
 
     except:
         return False
 
+def call_and_retry(*args, **kwargs):
+    """Wrapper around subprocess.call that retries failed calls.
+
+    This function calls subprocess.call and on non-zero exit states,
+    waits a second and then retries once. This mitigates #47,
+    a timing issue with some drivers.
+    """
+    kwargs_redirected = dict(kwargs)
+    if hasattr(subprocess, "DEVNULL"):
+        kwargs_redirected["stdout"] = getattr(subprocess, "DEVNULL")
+    else:
+        kwargs_redirected["stdout"] = open(os.devnull, "w")
+    kwargs_redirected["stderr"] = kwargs_redirected["stdout"]
+    retval = subprocess.call(*args, **kwargs_redirected)
+    if retval != 0:
+        time.sleep(1)
+        retval = subprocess.call(*args, **kwargs)
+    return retval
+
 def apply_configuration(new_configuration, current_configuration, dry_run=False):
     "Apply a configuration"
     outputs = sorted(new_configuration.keys(), key=lambda x: new_configuration[x].sort_key)
 def apply_configuration(new_configuration, current_configuration, dry_run=False):
     "Apply a configuration"
     outputs = sorted(new_configuration.keys(), key=lambda x: new_configuration[x].sort_key)
@@ -575,13 +601,13 @@ def apply_configuration(new_configuration, current_configuration, dry_run=False)
     # Perform pe-change auxiliary changes
     if auxiliary_changes_pre:
         argv = base_argv + list(chain.from_iterable(auxiliary_changes_pre))
     # Perform pe-change auxiliary changes
     if auxiliary_changes_pre:
         argv = base_argv + list(chain.from_iterable(auxiliary_changes_pre))
-        if subprocess.call(argv) != 0:
+        if call_and_retry(argv) != 0:
             raise AutorandrException("Command failed: %s" % " ".join(argv))
 
     # Disable unused outputs, but make sure that there always is at least one active screen
     disable_keep = 0 if remain_active_count else 1
     if len(disable_outputs) > disable_keep:
             raise AutorandrException("Command failed: %s" % " ".join(argv))
 
     # Disable unused outputs, but make sure that there always is at least one active screen
     disable_keep = 0 if remain_active_count else 1
     if len(disable_outputs) > disable_keep:
-        if subprocess.call(base_argv + list(chain.from_iterable(disable_outputs[:-1] if disable_keep else disable_outputs))) != 0:
+        if call_and_retry(base_argv + list(chain.from_iterable(disable_outputs[:-1] if disable_keep else disable_outputs))) != 0:
             # Disabling the outputs failed. Retry with the next command:
             # Sometimes disabling of outputs fails due to an invalid RRSetScreenSize.
             # This does not occur if simultaneously the primary screen is reset.
             # Disabling the outputs failed. Retry with the next command:
             # Sometimes disabling of outputs fails due to an invalid RRSetScreenSize.
             # This does not occur if simultaneously the primary screen is reset.
@@ -600,7 +626,7 @@ def apply_configuration(new_configuration, current_configuration, dry_run=False)
     operations = disable_outputs + enable_outputs
     for index in range(0, len(operations), 2):
         argv = base_argv + list(chain.from_iterable(operations[index:index+2]))
     operations = disable_outputs + enable_outputs
     for index in range(0, len(operations), 2):
         argv = base_argv + list(chain.from_iterable(operations[index:index+2]))
-        if subprocess.call(argv) != 0:
+        if call_and_retry(argv) != 0:
             raise AutorandrException("Command failed: %s" % " ".join(argv))
 
 def is_equal_configuration(source_configuration, target_configuration):
             raise AutorandrException("Command failed: %s" % " ".join(argv))
 
 def is_equal_configuration(source_configuration, target_configuration):
@@ -731,7 +757,7 @@ def exec_scripts(profile_path, script_name, meta_information=None):
 
 def main(argv):
     try:
 
 def main(argv):
     try:
-       options = dict(getopt.getopt(argv[1:], "s:l:d:cfh", [ "dry-run", "change", "default=", "save=", "load=", "force", "fingerprint", "config", "debug", "skip-options=", "help" ])[0])
+        options = dict(getopt.getopt(argv[1:], "s:r:l:d:cfh", [ "dry-run", "change", "default=", "save=", "remove=", "load=", "force", "fingerprint", "config", "debug", "skip-options=", "help" ])[0])
     except getopt.GetoptError as e:
         print("Failed to parse options: {0}.\n"
               "Use --help to get usage information.".format(str(e)),
     except getopt.GetoptError as e:
         print("Failed to parse options: {0}.\n"
               "Use --help to get usage information.".format(str(e)),
@@ -791,6 +817,33 @@ def main(argv):
         print("Saved current configuration as profile '%s'" % options["--save"])
         sys.exit(0)
 
         print("Saved current configuration as profile '%s'" % options["--save"])
         sys.exit(0)
 
+    if "-r" in options:
+        options["--remove"] = options["-r"]
+    if "--remove" in options:
+        if options["--remove"] in ( x[0] for x in virtual_profiles ):
+            raise AutorandrException("Cannot remove profile '%s':\nThis configuration name is a reserved virtual configuration." % options["--remove"])
+        if options["--remove"] not in profiles.keys():
+            raise AutorandrException("Cannot remove profile '%s':\nThis profile does not exist." % options["--remove"])
+        try:
+            remove = True
+            profile_folder = os.path.join(profile_path, options["--remove"])
+            profile_dirlist = os.listdir(profile_folder)
+            profile_dirlist.remove("config")
+            profile_dirlist.remove("setup")
+            if profile_dirlist:
+                print("Profile folder '%s' contains the following additional files:\n---\n%s\n---" % (options["--remove"], "\n".join(profile_dirlist)))
+                response = input("Do you really want to remove profile '%s'? If so, type 'yes': " % options["--remove"]).strip()
+                if response != "yes":
+                    remove = False
+            if remove is True:
+                shutil.rmtree(profile_folder)
+                print("Removed profile '%s'" % options["--remove"])
+            else:
+                print("Profile '%s' was not removed" % options["--remove"])
+        except Exception as e:
+            raise AutorandrException("Failed to remove profile '%s'" % (options["--remove"],), e)
+        sys.exit(0)
+
     if "-h" in options or "--help" in options:
         exit_help()
 
     if "-h" in options or "--help" in options:
         exit_help()
 
@@ -865,6 +918,9 @@ def main(argv):
                     "PROFILE_FOLDER": scripts_path,
                 }
                 exec_scripts(scripts_path, "preswitch", script_metadata)
                     "PROFILE_FOLDER": scripts_path,
                 }
                 exec_scripts(scripts_path, "preswitch", script_metadata)
+                if "--debug" in options:
+                    print("Going to run:")
+                    apply_configuration(load_config, config, True)
                 apply_configuration(load_config, config, False)
                 exec_scripts(scripts_path, "postswitch", script_metadata)
         except Exception as e:
                 apply_configuration(load_config, config, False)
                 exec_scripts(scripts_path, "postswitch", script_metadata)
         except Exception as e: