Skip to content

Instantly share code, notes, and snippets.

@AnyTimeTraveler
Last active January 14, 2018 14:50
Show Gist options
  • Save AnyTimeTraveler/6706f3d5c2673c8131465db59b4c478e to your computer and use it in GitHub Desktop.
Save AnyTimeTraveler/6706f3d5c2673c8131465db59b4c478e to your computer and use it in GitHub Desktop.
A script that organizes your dual screen setup automatically
#!/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