Skip to content

Instantly share code, notes, and snippets.

@tych0
Last active February 27, 2024 20:15
Show Gist options
  • Save tych0/a0e2d802b4bde6476997051c576d0ea6 to your computer and use it in GitHub Desktop.
Save tych0/a0e2d802b4bde6476997051c576d0ea6 to your computer and use it in GitHub Desktop.
a script to set up monitors

Here's a script to do xrandr style manipulation via xcffib. It does:

  1. runs everything in its preferred mode
  2. automatic DPI scaling to match the highest DPI monitor if the monitors do not have the same DPI
  3. EDID resolution so that people can reason about devices with stable names

It's not really intended to be consumable by anyone besides someone who might want to design a real abstraction on top and needs to know what the API calls for scaling/disabling monitors/rotating things would look like in python.

#!/usr/bin/env python3
import logging as log
import math
import os
import struct
import xcffib
import xcffib.xproto
import xcffib.randr
import pyedid
from dataclasses import dataclass
log.basicConfig(level=log.DEBUG)
conn = xcffib.connect(os.environ["DISPLAY"])
randr = conn(xcffib.randr.key)
roots = conn.get_setup().roots
if len(roots) > 1:
log.warning("more than one root window, assuming first is root")
rootid = roots[0].root
EDID = conn.core.InternAtom(False, len("EDID"), "EDID").reply().atom
# initialize from xrandr -q --verbse
ssr = randr.GetScreenSizeRange(rootid).reply()
log.debug(f"max width: {ssr.max_width} max height: {ssr.max_height}")
MAX_WIDTH = ssr.max_width
MAX_HEIGHT = ssr.max_height
sr = randr.GetScreenResources(rootid).reply()
modes = dict()
for mode in sr.modes:
modes[mode.id] = mode
log.debug(f"modes: {modes}")
def get_preferred_mode(output):
return modes[output.modes[output.num_preferred-1]]
def dpi_of_mode(output, mode):
diagonal_in = math.sqrt(output.mm_width**2 + output.mm_height**2) / 25.4
diagonal_px = math.sqrt(mode.width**2 + mode.height**2)
log.debug(f"{diagonal_in} inches {diagonal_px} px")
return int(diagonal_px / diagonal_in)
def compute_dpi(output):
log.debug(f"{output.name.to_string()}: {output.num_preferred} {output.modes}")
mode = get_preferred_mode(output)
return dpi_of_mode(output, mode)
outputs = []
for output in sr.outputs:
info = randr.GetOutputInfo(output, xcffib.CurrentTime).reply()
if info.connection != xcffib.randr.Connection.Connected:
continue
log.debug(f"{output}: {info.name.to_string()} preferred: {info.num_preferred}, dpi: {compute_dpi(info)}")
edid_raw = randr.GetOutputProperty(output, EDID, 0, 0, 256, False, False).reply().data
edid = pyedid.parse_edid(bytes(edid_raw))
info.id = output
info.serial = edid.serial
outputs.append(info)
# if we're just on a laptop, no need to do anything
if len(outputs) == 1:
os._exit(0)
# more than one output -> disable the laptop
for output in outputs:
# disable the laptop output
if 'eDP-1' == output.name.to_string() or 'eDP1' == output.name.to_string():
# turn it off if it was on
if output.crtc:
randr.SetCrtcConfig(output.crtc, xcffib.CurrentTime, xcffib.CurrentTime, 0, 0, 0, xcffib.randr.Rotation.Rotate_0, 0, []).reply()
outputs.remove(output)
break
# monitors should be in the order of their serial
serials_list = ["M2GCR1AM28PL", "1B8W0P3", "M2GCR1AS21NL"]
outputs.sort(key=lambda info: serials_list.index(info.serial))
# find highest dpi
max_dpi = max(map(compute_dpi, outputs))
total_width_mm = 0
total_height_mm = 0
total_width_px = 0
total_height_px = 0
resolutions = dict()
def double_to_xfixed(d):
return struct.unpack("i", struct.pack("i", int(d * 65536)))[0]
@dataclass
class XY:
x: int
y: int
for output in outputs:
# here, everything is in one big line on the x axis, and nothing is rotated
total_width_mm = total_width_mm + output.mm_width
total_height_mm = max(total_height_mm, output.mm_height)
this_dpi = compute_dpi(output)
mode = get_preferred_mode(output)
resolution = XY(mode.width, mode.height)
if this_dpi != max_dpi:
# choose the mode closest to the preferred mode scaled by
# max_dpi / this_dpi
resolution = XY(int((max_dpi / this_dpi) * mode.width), int((max_dpi / this_dpi) * mode.height))
resolutions[output] = resolution
total_width_px = total_width_px + resolution.x
total_height_px = max(total_height_px, resolution.y)
for output, r in resolutions.items():
log.debug(f"output {output.id} getting mode {r.x}x{r.y}")
try:
conn.core.GrabServer(is_checked=True).check()
# configure screen size
log.debug(f"setting screen size to {total_width_px}x{total_height_px}px {total_width_mm}x{total_height_mm}mm")
randr.SetScreenSizeChecked(rootid, total_width_px, total_height_px, total_width_mm, total_height_mm).check()
# set config for outputs
offset_x = 0
for output in outputs:
resolution = resolutions[output]
mode = get_preferred_mode(output)
my_offset = offset_x
offset_x = offset_x + resolution.x
if not output.crtc:
continue
# FIXED in render is 16 bits int, 16 bits
width_scale = double_to_xfixed(resolution.x / mode.width)
height_scale = double_to_xfixed(resolution.y / mode.height)
xform = xcffib.render.TRANSFORM.synthetic(width_scale, 0, 0, 0, height_scale, 0, 0, 0, double_to_xfixed(1))
filter_name = "bilinear"
randr.SetCrtcTransformChecked(output.crtc, xform, len(filter_name), filter_name, 0, []).check()
config_reply = randr.SetCrtcConfig(output.crtc, xcffib.CurrentTime, xcffib.CurrentTime, my_offset, 0, mode.id, xcffib.randr.Rotation.Rotate_0, 1, [output.id]).reply()
if config_reply.status != xcffib.randr.SetConfig.Success:
raise Exception(f"SetCrtcConfig failed: {config_reply.status}")
finally:
conn.core.UngrabServer()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment