Skip to content

Instantly share code, notes, and snippets.

@elfsternberg
Created November 21, 2018 01:16
Show Gist options
  • Save elfsternberg/aedb823462514353c89d08220c6bef86 to your computer and use it in GitHub Desktop.
Save elfsternberg/aedb823462514353c89d08220c6bef86 to your computer and use it in GitHub Desktop.
Dual-Monitor Setup For A Modern Laptop and An Old Desktop Monitor
#!/usr/bin/env python
# This is the current script I use to configure my laptop and my old
# desktop monitor. My old monitor is an Acer H243, released in 2009,
# maximum resolution 1920x1200. The laptop is crazy modern huge,
# 3840x2160. This script picks out the identities of the monitors in
# xrandr, and their maximal resolutions, and then rescales the content
# so that the two monitors are displaying their best output.
# This code makes a lot of assumptions. It assumes that your external
# monitor is OLD; it's maximal resolution is less than the laptop
# monitor, but that it's more than half the maximal resolution of the
# laptop monitor. It also assumes you want the external monitor
# *above* the laptop monitor. The assumptions hold true for my
# set-up.
import os
import re
import sys
import subprocess
from collections import namedtuple
Monitor = namedtuple('Monitor', ['name', 'width', 'height'])
Details = namedtuple('Details', ['buffer_width', 'buffer_height', 'monitors'])
# This is very fragile, and probably not very smart. When it
# encounters a connected monitor, it then scans for the current
# resolution and keeps it.
# TODO: Use the maximal resolution, not the current.
def get_details():
lines = subprocess.check_output(['xrandr', '--current']).splitlines()
res = []
mon = None
mat = None
for line in lines:
pmon = re.search(r'maximum (\d+) x (\d+)', line)
if pmon and not mon:
mon = (int(pmon.group(1), 10), int(pmon.group(2), 10))
continue
if line.find(r' connected') != -1:
mat = line.split(' ')[0]
continue
if mat and re.search(r'\d+\.\d+\*', line):
g = re.search(r'(\d+)+x(\d+)', line)
if g:
res.append(Monitor(mat, int(g.group(1), 10), int(g.group(2), 10)))
mat = None
return Details(mon[0], mon[1], res)
details = get_details()
if len(details.monitors) > 2:
print("Can't handle more than two monitors")
# A fallback, in case things go sideways.
if len(details.monitors) == 1:
os.system('xrandr -s 0')
sys.exit()
smaller = sorted(details.monitors, key=lambda x: x.height)[0]
larger = sorted(details.monitors, key=lambda x: x.height)[1]
# This is also fragile. The assumption is that the laptop monitor's
# width is smaller than twice the larger monitor's. If this
# assumption is false, I have no idea what will happen.
# If case this is true, though, the frame buffer has to be twice the smaller
# monitor plus the larger.
fb_height = smaller.height * 2 + larger.height
if fb_height > details.buffer_height:
print("The requested frame buffer size is greater than the available memory.")
sys.exit()
# For the old monitor with the smaller resolution, we're doubling the
# scaling (which actually *halves* the scaling), setting the
# resolution to the maximal relationship we can establish, and build
# the new framebuffer to the new height for *both* monitors, one above
# the other, and the maximal width which, again, we're assuming is
# twice that of the lower resolution monitors.
os.system('xrandr --output {} --scale 2x2 --mode {}x{} --fb {}x{} --pos 0x0'
.format(smaller.name, smaller.width, smaller.height,
smaller.width * 2, fb_height))
# For the laptop monitor, the scale is defaulted. The position's
# height is double the lower resolution monitor so it's *just below*
# that monitor; the left position of the laptop monitor is centered in
# its relationship with the external monitor.
os.system('xrandr --output {} --scale 1x1 --pos {}x{}'
.format(larger.name,
int(((smaller.width * 2) - larger.width) / 2),
smaller.height * 2))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment