Last active
January 14, 2018 14:50
-
-
Save AnyTimeTraveler/6706f3d5c2673c8131465db59b4c478e to your computer and use it in GitHub Desktop.
A script that organizes your dual screen setup automatically
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/python3 | |
import re | |
import subprocess | |
import sys | |
############################################## | |
# Multi Monitor setup script by Simon Struck # | |
############################################## | |
# Developed in a rush of productivity between 1 AM and 4 AM, | |
# this program provides automatic aligning of one or two monitors in a user-specified direction. | |
# All four directions are supplied to have the ability to make either monitor the primary monitor. | |
# It should not require you to install any extra dependencies apart from the interactive selectors (and for i3-users, dmenu is already pre-installed) | |
# (Shout-out to all users of i3wm, you are using an awesome piece of software! o/ ) | |
# Program arguments will always override the settings made in this configuration-section. | |
# Program will show a manual selector if this is set to true and no arguments are supplied. | |
# If an argument are supplied and matches an available setting, it will be used regardless. | |
interactive = True | |
# If no arguments are supplied and interactive is set to False, this will be used. | |
# Valid settings are: 'builtin', 'external', 'above', 'below', 'left', 'right' | |
direction = '' | |
# The program to make the interactive choice of direction with. | |
# Both programs should be available in most major repositories. | |
# Officially supported choices: 'rofi', 'dmenu' | |
command = 'dmenu' | |
############################################## | |
# LICENSE # | |
############################################## | |
# MIT License | |
# | |
# Copyright (c) 2018 Simon Struck | |
# | |
# Permission is hereby granted, free of charge, to any person obtaining a copy | |
# of this software and associated documentation files (the "Software"), to deal | |
# in the Software without restriction, including without limitation the rights | |
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
# copies of the Software, and to permit persons to whom the Software is | |
# furnished to do so, subject to the following conditions: | |
# | |
# The above copyright notice and this permission notice shall be included in all | |
# copies or substantial portions of the Software. | |
# | |
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |
# SOFTWARE. | |
############################################## | |
# SCRIPT # | |
############################################## | |
# The first argument is always the program name, so you have to check for one argument, if you want to check for zero actual arguments supplied. | |
interactive = len(sys.argv) == 1 and interactive | |
if not interactive and len(sys.argv) == 2: | |
direction = sys.argv[1] | |
if not interactive and direction not in ['builtin', 'external', 'above', 'below', 'left', 'right']: | |
print("ERROR: interactive set to false and no valid direction set.\n", | |
"\n", | |
"Usage:\n", | |
sys.argv[0] + " [direction]\n", | |
"\n", | |
"[direction] does not need to be set when interactive input is wanted or a preset has been set in the configuratio-section of this file.") | |
exit(-1) | |
# Grab screen info from xrandr | |
p = subprocess.run("xrandr", stdout=subprocess.PIPE) | |
screen_resolution = re.compile('\W+(\d+)x(\d+).+') | |
xrandr_output = str(p.stdout.decode('utf-8')).splitlines() | |
# Parse output from xrandr | |
i = 0 | |
connected = [] | |
disconnected = [] | |
while i < len(xrandr_output): | |
line = xrandr_output[i] | |
if line[0] != ' ' and not line.startswith('Screen'): | |
if 'disconnected' in line: | |
disconnected.append(line.split(' ')[0]) | |
else: | |
match = screen_resolution.match(xrandr_output[i + 1]) | |
connected.append([line.split(' ')[0], int(match.group(1)), int(match.group(2))]) | |
i += 1 | |
# If only one screen is connected, chances are that you want the output on that screen | |
if len(connected) == 1: | |
cmd = 'xrandr --output ' + connected[0][0] + ' --primary --mode ' + connected[0][1] + 'x' + connected[0][2] + ' --pos 0x0 --rotate normal' | |
for screen in disconnected: | |
cmd += ' --output ' + screen + ' --off' | |
subprocess.run(cmd.split(' ')) | |
exit(0) | |
# More than one screen is connected (this script assumes it is two) | |
# If interactive mode is enabled, | |
# Show the user a selector | |
if interactive: | |
dmenu = [command, '-p', 'Action:'] | |
if command == 'rofi': | |
dmenu.append('-dmenu') | |
p = subprocess.run(dmenu, | |
input="Abort\nBuiltin\nExternal\nAbove\nBelow\nLeft\nRight", | |
encoding='UTF-8', | |
stdout=subprocess.PIPE) | |
direction = str(p.stdout).lower() | |
# Determine the order of screens | |
screenA = None | |
screenB = None | |
if direction.startswith('abort'): | |
exit(0) | |
elif direction.startswith('builtin'): | |
screenA = connected[0] | |
disconnected.append(connected[1][0]) | |
elif direction.startswith('external'): | |
screenA = connected[1] | |
disconnected.append(connected[0][0]) | |
elif direction.startswith('above') or direction.startswith('left'): | |
screenA = connected[1] | |
screenB = connected[0] | |
elif direction.startswith('below') or direction.startswith('right'): | |
screenA = connected[0] | |
screenB = connected[1] | |
else: | |
print("This should never happen") | |
# Determine the position of screens | |
posA = '0x0' | |
posB = '0x0' | |
if str(direction).lower().startswith('left') or direction.startswith('right'): | |
posB = str(screenA[1]) + 'x' | |
if screenA[2] == screenB[2]: | |
posB += '0' | |
elif screenA[2] > screenB[2]: | |
posB += str(int((screenA[2] - screenB[2]) / 2)) | |
else: | |
posB += '0' | |
posA = '0x' + str(int((screenB[2] - screenA[2]) / 2)) | |
elif direction.startswith('above') or direction.startswith('below'): | |
if screenA[1] == screenB[1]: | |
posB = '0' | |
elif screenA[1] > screenB[1]: | |
posB = str(int((screenA[1] - screenB[1]) / 2)) | |
else: | |
posB = '0' | |
posA = str(int((screenB[1] - screenA[1]) / 2)) + 'x0' | |
posB += 'x' + str(screenA[2]) | |
# Put together the final xrandr command that organizes the screens | |
cmd = 'xrandr' | |
if screenA is not None: | |
cmd += ' --outp ut ' + screenA[0] + ' --primary --mode ' + str(screenA[1]) + 'x' + str(screenA[2]) + ' --pos ' + posA + ' --rotate normal' | |
if screenB is not None: | |
cmd += ' --out put ' + screenB[0] + ' --primary --mode ' + str(screenB[1]) + 'x' + str(screenB[2]) + ' --pos ' + posB + ' --rotate normal' | |
for screen in disconnected: | |
cmd += ' --out put ' + screen + ' --off' | |
# Execture the command | |
p = subprocess.run(cmd.split(' '), stderr=subprocess.PIPE) | |
# Create error report if needed | |
error = p.stderr.decode('utf-8') | |
if error: | |
print("======================================================") | |
print("======================================================") | |
print() | |
print("Some error occured!") | |
print("Please include the following data in the error report:") | |
print() | |
info = [sys.argv, [interactive], [command], xrandr_output, connected, disconnected, [direction], screenA, screenB, [posA], [posB], [error]] | |
for line in info: | |
print(line) | |
print() | |
print("======================================================") | |
print("======================================================") |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment