virtual_profiles = [
# (name, description, callback)
("common", "Clone all connected outputs at the largest common resolution", None),
+ ("clone", "Clone all connected outputs with the largest resolution and scaled down in the others", None),
("horizontal", "Stack all connected outputs horizontally at their largest resolution", None),
("vertical", "Stack all connected outputs vertically at their largest resolution", None),
]
for line in configuration.split("\n"):
if line:
line = line.split(None, 1)
+ if line and line[0].startswith("#"):
+ continue
options[line[0]] = line[1] if len(line) > 1 else None
edid = None
if not os.path.isfile(config_name) or not os.path.isfile(setup_name):
continue
- edids = dict([ x.strip().split() for x in open(setup_name).readlines() if x.strip() ])
+ edids = dict([ x.split() for x in (y.strip() for y in open(setup_name).readlines()) if x and x[0] != "#" ])
config = {}
buffer = []
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")
+ if "dry_run" in kwargs:
+ dry_run = kwargs["dry_run"]
+ del kwargs["dry_run"]
else:
- kwargs_redirected["stdout"] = open(os.devnull, "w")
- kwargs_redirected["stderr"] = kwargs_redirected["stdout"]
+ dry_run = False
+ kwargs_redirected = dict(kwargs)
+ if not dry_run:
+ 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)
# 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 = []
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)
# Perform pe-change auxiliary changes
if auxiliary_changes_pre:
argv = base_argv + list(chain.from_iterable(auxiliary_changes_pre))
- if call_and_retry(argv) != 0:
+ if call_and_retry(argv, dry_run=dry_run) != 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:
- if call_and_retry(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)), dry_run=dry_run) != 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.
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 call_and_retry(argv) != 0:
+ if call_and_retry(argv, dry_run=dry_run) != 0:
raise AutorandrException("Command failed: %s" % " ".join(argv))
def is_equal_configuration(source_configuration, target_configuration):
shift += int(mode[shift_index])
else:
configuration[output].options["off"] = None
+ elif profile_name == "clone":
+ biggest_width_resolution = sorted([output_modes[0] for output, output_modes in modes.items()], key=lambda x: x["width"], 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"
+ x_scale = float(biggest_width_resolution["width"]) / float(mode["width"])
+ y_scale = float(biggest_width_resolution["height"]) / float(mode["height"])
+ configuration[output].options["scale"] = "{}x{}".format(x_scale, y_scale)
+ else:
+ configuration[output].options["off"] = None
return configuration
def print_profile_differences(one, another):
and system-wide configuration folders, named script_name or residing in
subdirectories named script_name.d.
+ If profile_path is None, only global scripts will be invoked.
+
meta_information is expected to be an dictionary. It will be passed to the block scripts
in the environment, as variables called AUTORANDR_<CAPITALIZED_KEY_HERE>.
if not os.path.isdir(user_profile_path):
user_profile_path = os.path.join(os.environ.get("XDG_CONFIG_HOME", os.path.expanduser("~/.config")), "autorandr")
- for folder in chain((profile_path, os.path.dirname(profile_path), user_profile_path),
- (os.path.join(x, "autorandr") for x in os.environ.get("XDG_CONFIG_DIRS", "/etc/xdg").split(":"))):
+ candidate_directories = chain((user_profile_path,), (os.path.join(x, "autorandr") for x in os.environ.get("XDG_CONFIG_DIRS", "/etc/xdg").split(":")))
+ if profile_path:
+ candidate_directories = chain((profile_path,), candidate_directories)
+
+ 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):
- all_ok &= subprocess.call(script, env=env) != 0
+ try:
+ all_ok &= subprocess.call(script, env=env) != 0
+ except:
+ raise AutorandrException("Failed to execute user command: %s" % (script,))
ran_scripts.add(script_name)
script_folder = os.path.join(folder, "%s.d" % script_name)
if check_name not in ran_scripts:
script = os.path.join(script_folder, file_name)
if os.access(script, os.X_OK | os.F_OK):
- all_ok &= subprocess.call(script, env=env) != 0
+ try:
+ all_ok &= subprocess.call(script, env=env) != 0
+ except:
+ raise AutorandrException("Failed to execute user command: %s" % (script,))
ran_scripts.add(check_name)
return all_ok
process_environ[name] = value
display = process_environ["DISPLAY"] if "DISPLAY" in process_environ else None
+ # To allow scripts to detect batch invocation (especially useful for predetect)
+ process_environ["AUTORANDR_BATCH_PID"] = str(os.getpid())
+
if display and display not in X11_displays_done:
try:
pwent = pwd.getpwuid(uid)
file=sys.stderr)
sys.exit(posix.EX_USAGE)
+ if "-h" in options or "--help" in options:
+ exit_help()
+
# Batch mode
if "--batch" in options:
if ("DISPLAY" not in os.environ or not os.environ["DISPLAY"]) and os.getuid() == 0:
profile_symlinks = { k: v for k, v in profile_symlinks.items() if v in (x[0] for x in virtual_profiles) or v in profiles }
+ exec_scripts(None, "predetect")
config, modes = parse_xrandr_output()
if "--fingerprint" in options:
raise AutorandrException("Failed to remove profile '%s'" % (options["--remove"],), e)
sys.exit(0)
- if "-h" in options or "--help" in options:
- exit_help()
-
detected_profiles = find_profiles(config, profiles)
load_profile = False
apply_configuration(load_config, config, True)
apply_configuration(load_config, config, False)
exec_scripts(scripts_path, "postswitch", script_metadata)
+ except AutorandrException as e:
+ raise AutorandrException("Failed to apply profile '%s'" % load_profile, e, e.report_bug)
except Exception as e:
raise AutorandrException("Failed to apply profile '%s'" % load_profile, e, True)