summaryrefslogtreecommitdiff
path: root/bin/read_edid.py
blob: 5b204d7d2bd9e34f4a5e1d4e97dcea2f5b4c2bc0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
#!/usr/bin/env python3

# https://en.wikipedia.org/wiki/Extended_Display_Identification_Data

# TODO: This is a real mess, it's a transition from the setup-desktop.sh shell
#       script to something a little more flexible. But it mostly doesn't work
#       yet and is just subbing in for parse-edid.

# TODO: List screens if none are known

# TODO: Zooming
# xrandr --output eDP-1 --mode 2880x1620 --panning 2880x1620
#bigmodes=( 3840x2160 3200x1800 )
#modes=( 2560x1440 2048x1152 1920x1080 1600x900 1368x768
#        1360x768 1280x720 1024x576 960x540 864x486
#        720x405 640x360 )

import re
import os
import sys
import struct
from collections import namedtuple
from subprocess import check_output, call

BUILTIN_DISPLAY_RESOLUTIONS = [
    "1600x900", "1920x1080", "2048x1152", "2880x1620"]
BEST_RESOLUTION = BUILTIN_DISPLAY_RESOLUTIONS[2]

DRM_PATH = "/sys/class/drm"
EDID_MAGIC = b'\x00\xff\xff\xff\xff\xff\xff\x00'
EDID_14 = b'\x01\x04'
UNSPEC_TEXT = 0xFE
SERIAL = 0xFF
NAME = 0xFC


MonitorData = namedtuple(
    "MonitorData", ("card", "port", "name", "serial", "text"))


def parse_xrandr_query():
    monitors = {}
    monitor = None

    for line in check_output(("xrandr", "-q")).split(b"\n"):
        line = line.decode("us-ascii")

        if " connected " in line:
            monitor = line.split(" ")[0]
            monitors[monitor] = []
        elif monitor and line.startswith("  "):
            monitors[monitor].append(line.strip().split(" ")[0])

    return monitors


def parse_card_port(p):
    card, port = p.split("/")[-1].split("-", 1)
    return int(card[len("card"):]), port


def parse_descriptor(card, port, md):
    name, serial, text = None, None, None

    for i in range(54, 108, 18):
        d = md[i:i+18]
        dtype, data = d[3], d[5:]

        prep = lambda dd: dd.decode("us-ascii", errors="ignore").strip()

        if dtype == UNSPEC_TEXT:
            text = prep(data)
        elif dtype == SERIAL:
            serial = prep(data)
        elif dtype == NAME:
            name = prep(data)

    return MonitorData(card, port, name, serial, text)


def parse_edid(data, path):
    if data[0:8] != EDID_MAGIC:
        raise Exception("Not EDID")

    if data[18:20] != EDID_14:
        raise Exception("Not EDID 1.4")

    card, port = parse_card_port(path)
    return parse_descriptor(card, port, data)


def get_monitor_edid(path):
    edid = os.path.join(path, "edid")
    if not os.path.exists(edid):
        raise Exception("No card with that name {!r}".format(path))

    with open(edid, "rb") as fp:
        data = fp.read()

    return parse_edid(data, path)


def enumerate_monitors():
    monitors = []

    for path in os.listdir(DRM_PATH):
        if not re.match("^card\d+-", path):
            continue

        drm_path = os.path.join(DRM_PATH, path)
        monitors.append(get_monitor_edid(drm_path))

    return monitors


def script_main(args):
    all_resolutions = parse_xrandr_query() # Do first to refresh /sys
    all_monitors = enumerate_monitors()

    for monitor in all_monitors:
        resolutions = all_resolutions[monitor.port]

        # 27" Dell monitors
        if monitor == "DELL U2715H":
            call(["xrandr",
                  "--output", "DP-1","--auto",
                  "--output", "DP-2", "--auto", "--right-of", "DP-1",
                  "--output", "eDP-1", "--off"])
            break
        elif monitor == "AMX_HDMI1_A2":
            call(["xrandr",
                  "--output", "eDP-1", "--mode", BEST_RESOLUTION,
                  "--output", monitor.card, "--auto", "--right-of", "eDP-1"])
            break
        elif monitor == "DELL U3415W":
            call(["xrandr",
                  "--output", "eDP-1", "--mode", BEST_RESOLUTION,
                  "--output", monitor.card, "--mode", "3440x1440",
                  "--right-of", "eDP-1"])
            break
        elif monitor == "CS-CODECPLUS":
            call(["xrandr",
                  "--output", "eDP-1", "--mode", BEST_RESOLUTION,
                  "--output", monitor.card, "--auto", "--right-of", "eDP-1"])
            break
        else:
            call(["xrandr",
                  "--output", "eDP-1", "--mode", BEST_RESOLUTION,
                  "--output", "DP-1", "--off",
                  "--output", "DP-2", "--off"])
            break


def main(args):
    print(get_monitor_edid(args[1]).name)

if __name__ == "__main__":
    #sys.exit(main(sys.argv))
    sys.stdin.mode = "rb"
    print(parse_edid(sys.stdin.buffer.read(), "/card1-eDP-1").text)