+
+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 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
+ where one user runs a process with another user's DISPLAY variable - in
+ this case, it might happen that autorandr is invoked for the other user,
+ 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)
+ if not os.path.isdir(directory):
+ continue
+ environ_file = os.path.join(directory, "environ")
+ if not os.path.isfile(environ_file):
+ continue
+ uid = os.stat(environ_file).st_uid
+
+ # The following line assumes that user accounts start at 1000 and that
+ # no one works using the root or another system account. This is rather
+ # restrictive, but de facto default. Alternatives would be to use the
+ # UID_MIN from /etc/login.defs or FIRST_UID from /etc/adduser.conf;
+ # but effectively, both values aren't binding in any way.
+ # If this breaks your use case, please file a bug on Github.
+ if uid < 1000:
+ continue
+
+ process_environ = {}
+ for environ_entry in open(environ_file).read().split("\0"):
+ name, sep, value = environ_entry.partition("=")
+ if name and sep:
+ if name == "DISPLAY" and "." in value:
+ value = value[:value.find(".")]
+ process_environ[name] = value
+
+ 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 "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
+
+ 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 enabled_monitors(config):
+ monitors = []
+ for monitor in config:
+ if "--off" in config[monitor].option_vector:
+ continue
+ monitors.append(monitor)
+ return monitors
+
+
+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)
+