Skip to content

Instantly share code, notes, and snippets.

@tych0
Created May 21, 2025 17:37
Show Gist options
  • Save tych0/2a6e7d9d9e06054fedd5f8214adf8da5 to your computer and use it in GitHub Desktop.
Save tych0/2a6e7d9d9e06054fedd5f8214adf8da5 to your computer and use it in GitHub Desktop.
#!/usr/bin/env -S uv run --with xcffib --with pyedid --script
import logging as log
import math
import os
import struct
import xcffib
import xcffib.xproto
import xcffib.randr
import pyedid
from dataclasses import dataclass
DPI_TRANSFORMATION = True
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
print(f"processing {info.name.to_string()}")
if 'eDP-1' == output.name.to_string() or 'eDP1' == output.name.to_string():
print("turning off eDP-1")
# 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
# make 4k monitor primary
if output.serial == "1B8W0P3":
# 000:<:0034: 12: RANDR-Request(140,30): SetOutputPrimary window=0x00000542 output=0x0000004a
randr.SetOutputPrimary(rootid, output.id).check()
# 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 DPI_TRANSFORMATION and 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
# it is not clear why we need to hard code this, but on the newer X server
# with fedora we do. even though we have disabled the laptop monitor and
# set something else as primary, the x server still refuses this even
# though the monitor is off. we could change the math above to include it,
# but i've just hardcoded it here for now.
#
# the mouse doesn't go off the right when you pan that way, so i wonder if
# this maybe doesn't even matter, and we should just set at least the pixel
# size to the maximum allowed, rather than any particular calculated value.
total_width_px = 10600
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]
my_offset = offset_x
offset_x = offset_x + resolution.x
mode = get_preferred_mode(output)
if not output.crtc:
continue
if DPI_TRANSFORMATION:
# 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()
orientation = xcffib.randr.Rotation.Rotate_0
if output == outputs[0] and False:
orientation = xcffib.randr.Rotation.Rotate_90
config_reply = randr.SetCrtcConfig(output.crtc, xcffib.CurrentTime, xcffib.CurrentTime, my_offset, 0, mode.id, orientation, 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