import hashlib
import os
import posix
+import pwd
import re
import subprocess
import sys
--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 call "block" in its
directory. The script is evaluated before the screen setup is inspected, and
return all_ok
+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.
+
+ 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])
+
+ 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
+ if uid == 0:
+ continue
+
+ process_environ = {}
+ for environ_entry in open(environ_file).read().split("\0"):
+ if "=" in environ_entry:
+ name, value = environ_entry.split("=", 1)
+ 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 and 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)
+
+ X11_displays_done.add(display)
+
def main(argv):
try:
- 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])
+ options = dict(getopt.getopt(argv[1:], "s:r:l:d:cfh", [ "batch", "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)),
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:
+ dispatch_call_to_sessions([ x for x in argv if x != "--batch" ])
+ else:
+ print("--batch mode can only be used by root and if $DISPLAY is unset")
+ return
+
profiles = {}
try:
# Load profiles from each XDG config directory
# 40autorandr: Change autorandr profile on thaw/resume
exec > /var/log/autorandr.log 2>&1
-AUTORANDR="autorandr -c --default default"
-
-# Work around #44: Long user names in w
-export PROCPS_USERLEN=32
-
-find_user() {
- # Determine user owning the display session from $1
- D="$1"
-
- # Prefer loginctl over all others, see bug #39
- if [ -x "`which loginctl`" ]; then
- # Based on http://unix.stackexchange.com/questions/203844/how-to-find-out-the-current-active-xserver-display-number/204498
- # by SO user intelfx
- user="$(
- loginctl list-sessions --no-legend | while read id uid user seat; do
- session=$(loginctl show-session -p Display -p Active "$id")
- first=$(echo $session | cut -d" " -f1)
- second=$(echo $session | cut -d" " -f2)
-
- if [ -n $(echo "$first" | grep "Display") ]; then
- display=$first
- active=$second
- else
- display=$second
- active=$first
- fi
-
- active_value=$(echo "$active" | cut -d"=" -f2)
- display_value=$(echo "$display" | cut -d"=" -f2)
-
- if [ "$active_value" != "yes" ]; then
- continue
- fi
-
- if [ -n $display_value -a "$display_value" = "$D" ]; then
- echo $user
- fi
- done
- )"
- if [ -n "$user" ]; then
- echo $user
- return 0
- fi
- fi
-
- # Prefer w to who, see bug #39
- if [ -x "`which w`" ]; then
- user="`w -h | awk -vD="$D" '$2 ~ ":"D"(.[0-9])?$" || $3 ~ ":"D"(.[0-9])?$" {print $1}' | head -n1`"
-
- if [ -z "$user" ]; then
- # This fallback checks if there is exactly one user (except
- # root) logged in an interactive session and assumes the
- # session belongs to him. See bug #39.
- user="`w -hu | awk '/^\w+/ && $1 !~ "root" { users[$1]=$1; } ENDFILE { if(asort(users) == 1) for(u in users) print users[u]; }'`"
- fi
- else
- user="`who --all | awk -vD="$D" '$3 ~ ":"D"(.[0-9])?$" {print $1}' | head -1`"
-
- if [ -z "$user" ]; then
- # Same fallback as above; see bug #39.
- user="`who -u | awk '/^\w+/ && $1 !~ "root" { users[$1]=$1; } ENDFILE { if(asort(users) == 1) for(u in users) print users[u]; }'`"
- fi
- fi
-
- if [ -n "$user" ]; then
- echo $user
- return 0
- fi
-
- # If none of the above worked, check if there is only one user owning
- # processes in $DISPLAY except root
- #
- # This code should be optimized somehow, but keep in mind that not all
- # systems symlink sh -> bash!
- OUTPUT="$(
- for p in /proc/*; do
- [ -d $p ] || continue
- d="$(awk -v RS='\0' -F= '$1=="DISPLAY" {print $2}' $p/environ 2>/dev/null)"
- if [ "$d" = "$D" ]; then
- stat -c %U $p
- fi
- done | sort | uniq | grep -v root | nl | head -n1
- )"
- count=$(echo $OUTPUT | awk '{print $1}')
- user=$(echo $OUTPUT | awk '{print $2}')
-
- if [ "$count" -eq 1 ]; then
- echo $user
- return 0
- fi
-
- return 1
-}
-
-detect_display()
-{
- for X in /tmp/.X11-unix/X*; do
- D="${X##/tmp/.X11-unix/X}"
-
- user="$(find_user ":$D")"
-
- if [ x"$user" != x"" ]; then
- logger "autorandr: Changing display configuration for display :$D, user '$user'"
- export DISPLAY=":$D"
- /bin/su -c "${AUTORANDR}" "$user"
- fi
- done
-}
-
-if grep -q systemd /proc/1/comm && [ "$2" = "udev" ]; then
- exec /bin/systemctl start autorandr-resume.service
-fi
-
case "$1" in
thaw|resume)
- detect_display
+ autorandr --batch -c --default default
;;
esac