]> git.donarmstrong.com Git - deb_pkgs/autorandr.git/blobdiff - autorandr.py
Batch mode: Prefer processes with XAUTHORITY set as environment templates (See #81)
[deb_pkgs/autorandr.git] / autorandr.py
index 8d561fd85874d1b1d50499344744119715744f53..f8b3ae8a5a96e006052243797950c824963c2d4b 100755 (executable)
@@ -50,6 +50,7 @@ except NameError:
 virtual_profiles = [
     # (name, description, callback)
     ("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),
     ("vertical", "Stack all connected outputs vertically at their largest resolution", None),
 ]
@@ -601,6 +602,9 @@ def apply_configuration(new_configuration, current_configuration, dry_run=False)
     #   at least.)
     # - Some implementations can not handle --transform at all, so avoid it unless
     #   necessary. (See https://github.com/phillipberndt/autorandr/issues/37)
+    # - Some implementations can not handle --panning without specifying --fb
+    #   explicitly, so avoid it unless necessary.
+    #   (See https://github.com/phillipberndt/autorandr/issues/72)
 
     auxiliary_changes_pre = []
     disable_outputs = []
@@ -615,15 +619,16 @@ def apply_configuration(new_configuration, current_configuration, dry_run=False)
 
             option_vector = new_configuration[output].option_vector
             if xrandr_version() >= Version("1.3.0"):
-                if "transform" in current_configuration[output].options:
-                    auxiliary_changes_pre.append(["--output", output, "--transform", "none"])
-                else:
-                    try:
-                        transform_index = option_vector.index("--transform")
-                        if option_vector[transform_index+1] == XrandrOutput.XRANDR_DEFAULTS["transform"]:
-                            option_vector = option_vector[:transform_index] + option_vector[transform_index+2:]
-                    except ValueError:
-                        pass
+                for option in ("transform", "panning"):
+                    if option in current_configuration[output].options:
+                        auxiliary_changes_pre.append(["--output", output, "--%s" % option, "none"])
+                    else:
+                        try:
+                            option_index = option_vector.index("--%s" % option)
+                            if option_vector[option_index+1] == XrandrOutput.XRANDR_DEFAULTS[option]:
+                                option_vector = option_vector[:option_index] + option_vector[option_index+2:]
+                        except ValueError:
+                            pass
 
             enable_outputs.append(option_vector)
 
@@ -711,6 +716,21 @@ def generate_virtual_profile(configuration, modes, profile_name):
                 shift += int(mode[shift_index])
             else:
                 configuration[output].options["off"] = None
+    elif profile_name == "clone-largest":
+        biggest_resolution = sorted([output_modes[0] for output, output_modes in modes.items()], key=lambda x: int(x["width"])*int(x["height"]), reverse=True)[0]
+        for output in configuration:
+            configuration[output].options = {}
+            if output in modes and configuration[output].edid:
+                mode = sorted(modes[output], key=lambda a: int(a["width"])*int(a["height"]) + (10**6 if a["preferred"] else 0))[-1]
+                configuration[output].options["mode"] = mode["name"]
+                configuration[output].options["rate"] = mode["rate"]
+                configuration[output].options["pos"] = "0x0"
+                scale = max(float(biggest_resolution["width"]) / float(mode["width"]) ,float(biggest_resolution["height"]) / float(mode["height"]))
+                mov_x = (float(mode["width"])*scale-float(biggest_resolution["width"]))/-2
+                mov_y = (float(mode["height"])*scale-float(biggest_resolution["height"]))/-2
+                configuration[output].options["transform"] = "{},0,{},0,{},{},0,0,1".format(scale, mov_x, scale, mov_y)
+            else:
+                configuration[output].options["off"] = None
     return configuration
 
 def print_profile_differences(one, another):
@@ -734,7 +754,15 @@ def exit_help():
     "Print help and exit"
     print(help_text)
     for profile in virtual_profiles:
-        print("  %-10s %s" % profile[:2])
+        name, description = profile[:2]
+        description = [ description ]
+        max_width = 78-18
+        while len(description[0]) > max_width + 1:
+            left_over = description[0][max_width:]
+            description[0] = description[0][:max_width] + "-"
+            description.insert(1, "  %-15s %s" % ("", left_over))
+        description = "\n".join(description)
+        print("  %-15s %s" % (name, description))
     sys.exit(0)
 
 def exec_scripts(profile_path, script_name, meta_information=None):
@@ -799,11 +827,11 @@ def dispatch_call_to_sessions(argv):
     """Invoke autorandr for each open local X11 session with the given options.
 
     The function iterates over all processes not owned by root and checks
-    whether they have a DISPLAY variable set. It strips the screen from any
-    variable it finds (i.e. :0.0 becomes :0) and checks whether this display
-    has been handled already. If it has not, it forks, changes uid/gid to
-    the user owning the process, reuses the process's environment and runs
-    autorandr with the parameters from argv.
+    whether they have DISPLAY and XAUTHORITY variables set. It strips the
+    screen from any variable it finds (i.e. :0.0 becomes :0) and checks whether
+    this display has been handled already. If it has not, it forks, changes
+    uid/gid to the user owning the process, reuses the process's environment
+    and runs autorandr with the parameters from argv.
 
     This function requires root permissions. It only works for X11 servers that
     have at least one non-root process running. It is susceptible for attacks
@@ -812,9 +840,29 @@ def dispatch_call_to_sessions(argv):
     which won't work. Since no other harm than prevention of automated
     execution of autorandr can be done this way, the assumption is that in this
     situation, the local administrator will handle the situation."""
+
     X11_displays_done = set()
 
     autorandr_binary = os.path.abspath(argv[0])
+    backup_candidates = {}
+
+    def fork_child_autorandr(pwent, process_environ):
+        print("Running autorandr as %s for display %s" % (pwent.pw_name, process_environ["DISPLAY"]))
+        child_pid = os.fork()
+        if child_pid == 0:
+            # This will throw an exception if any of the privilege changes fails,
+            # so it should be safe. Also, note that since the environment
+            # is taken from a process owned by the user, reusing it should
+            # not leak any information.
+            os.setgroups([])
+            os.setresgid(pwent.pw_gid, pwent.pw_gid, pwent.pw_gid)
+            os.setresuid(pwent.pw_uid, pwent.pw_uid, pwent.pw_uid)
+            os.chdir(pwent.pw_dir)
+            os.environ.clear()
+            os.environ.update(process_environ)
+            os.execl(autorandr_binary, autorandr_binary, *argv[1:])
+            os.exit(1)
+        os.waitpid(child_pid, 0)
 
     for directory in os.listdir("/proc"):
         directory = os.path.join("/proc/", directory)
@@ -841,35 +889,44 @@ def dispatch_call_to_sessions(argv):
                 if name == "DISPLAY" and "." in value:
                     value = value[:value.find(".")]
                 process_environ[name] = value
-        display = process_environ["DISPLAY"] if "DISPLAY" in process_environ else None
+
+        if "DISPLAY" not in process_environ:
+            # Cannot work with this environment, skip.
+            continue
 
         # To allow scripts to detect batch invocation (especially useful for predetect)
         process_environ["AUTORANDR_BATCH_PID"] = str(os.getpid())
+        process_environ["UID"] = str(uid)
+
+        display = process_environ["DISPLAY"]
 
-        if display and display not in X11_displays_done:
+        if "XAUTHORITY" not in process_environ:
+            # It's very likely that we cannot work with this environment either,
+            # but keep it as a backup just in case we don't find anything else.
+            backup_candidates[display] = process_environ
+            continue
+
+        if display not in X11_displays_done:
             try:
                 pwent = pwd.getpwuid(uid)
             except KeyError:
                 # User has no pwd entry
                 continue
 
-            print("Running autorandr as %s for display %s" % (pwent.pw_name, display))
-            child_pid = os.fork()
-            if child_pid == 0:
-                # This will throw an exception if any of the privilege changes fails,
-                # so it should be safe. Also, note that since the environment
-                # is taken from a process owned by the user, reusing it should
-                # not leak any information.
-                os.setgroups([])
-                os.setresgid(pwent.pw_gid, pwent.pw_gid, pwent.pw_gid)
-                os.setresuid(pwent.pw_uid, pwent.pw_uid, pwent.pw_uid)
-                os.chdir(pwent.pw_dir)
-                os.environ.clear()
-                os.environ.update(process_environ)
-                os.execl(autorandr_binary, autorandr_binary, *argv[1:])
-                os.exit(1)
-            os.waitpid(child_pid, 0)
+            fork_child_autorandr(pwent, process_environ)
+            X11_displays_done.add(display)
+
+    # Run autorandr for any users/displays which didn't have a process with
+    # XAUTHORITY set.
+    for display, process_environ in backup_candidates.items():
+        if display not in X11_displays_done:
+            try:
+                pwent = pwd.getpwuid(int(process_environ["UID"]))
+            except KeyError:
+                # User has no pwd entry
+                continue
 
+            fork_child_autorandr(pwent, process_environ)
             X11_displays_done.add(display)
 
 def main(argv):
@@ -891,6 +948,10 @@ def main(argv):
         else:
             print("--batch mode can only be used by root and if $DISPLAY is unset")
         return
+    if "AUTORANDR_BATCH_PID" in os.environ:
+        user = pwd.getpwuid(os.getuid())
+        user = user.pw_name if user else "#%d" % os.getuid()
+        print("autorandr running as user %s (started from batch instance)" % user)
 
     profiles = {}
     profile_symlinks = {}