Created
May 21, 2025 17:37
-
-
Save tych0/2a6e7d9d9e06054fedd5f8214adf8da5 to your computer and use it in GitHub Desktop.
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
#!/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