1#!/usr/bin/env python3
2
3# This script synchronizes wayland.xml's wl_shm.format enum with drm_fourcc.h.
4# Invoke it to update wayland.xml, then manually check the changes applied.
5#
6# Requires Python 3, python-lxml, a C compiler and pkg-config.
7
8import os
9import subprocess
10import sys
11import tempfile
12# We need lxml instead of the standard library because we want
13# Element.sourceline
14from lxml import etree as ElementTree
15
16proto_dir = os.path.dirname(os.path.realpath(__file__))
17wayland_proto = proto_dir + "/wayland.xml"
18
19cc = os.getenv("CC", "cc")
20pkg_config = os.getenv("PKG_CONFIG", "pkg-config")
21
22# Find drm_fourcc.h
23version = subprocess.check_output([pkg_config, "libdrm",
24    "--modversion"]).decode().strip()
25cflags = subprocess.check_output([pkg_config, "libdrm",
26    "--cflags-only-I"]).decode().strip().split()
27libdrm_include = None
28for include_flag in cflags:
29    if not include_flag.startswith("-I"):
30        raise Exception("Expected one include dir for libdrm")
31    include_dir = include_flag[2:]
32    if include_dir.endswith("/libdrm"):
33        libdrm_include = include_dir
34        fourcc_include = libdrm_include + "/drm_fourcc.h"
35if libdrm_include == None:
36    raise Exception("Failed to find libdrm include dir")
37
38print("Using libdrm " + version, file=sys.stderr)
39
40def drm_format_to_wl(ident):
41    return ident.replace("DRM_FORMAT_", "").lower()
42
43# Collect DRM format constant names
44ident_list = []
45descriptions = {}
46prev_comment = None
47with open(fourcc_include) as input_file:
48    for l in input_file.readlines():
49        l = l.strip()
50
51        # Collect comments right before format definitions
52        if l.startswith("/*") and l.endswith("*/"):
53            prev_comment = l[2:-2]
54            continue
55        desc = prev_comment
56        prev_comment = None
57
58        # Recognize format definitions
59        parts = l.split()
60        if len(parts) < 3 or parts[0] != "#define":
61            continue
62        ident = parts[1]
63        if not ident.startswith("DRM_FORMAT_") or ident.startswith(
64                "DRM_FORMAT_MOD_"):
65            continue
66
67        ident_list.append(ident)
68
69        # Prefer in-line comments
70        if l.endswith("*/"):
71            desc = l[l.rfind("/*") + 2:-2]
72        if desc != None:
73            descriptions[drm_format_to_wl(ident)] = desc.strip()
74
75# Collect DRM format values
76idents = {}
77with tempfile.TemporaryDirectory() as work_dir:
78    c_file_name = work_dir + "/print-formats.c"
79    exe_file_name = work_dir + "/print-formats"
80
81    with open(c_file_name, "w+") as c_file:
82        c_file.write('#include <inttypes.h>\n')
83        c_file.write('#include <stdint.h>\n')
84        c_file.write('#include <stdio.h>\n')
85        c_file.write('#include <drm_fourcc.h>\n')
86        c_file.write('\n')
87        c_file.write('int main(void) {\n')
88        for ident in ident_list:
89            c_file.write('printf("0x%" PRIX64 "\\n", (uint64_t)' + ident + ');\n')
90        c_file.write('}\n')
91
92    subprocess.check_call([cc, "-Wall", "-Wextra", "-o", exe_file_name,
93        c_file_name] + cflags)
94    output = subprocess.check_output([exe_file_name]).decode().strip()
95    for i, val in enumerate(output.splitlines()):
96        idents[ident_list[i]] = val
97
98# We don't need those
99del idents["DRM_FORMAT_BIG_ENDIAN"]
100del idents["DRM_FORMAT_INVALID"]
101del idents["DRM_FORMAT_RESERVED"]
102
103# Convert from DRM constants to Wayland wl_shm.format entries
104formats = {}
105for ident, val in idents.items():
106    formats[drm_format_to_wl(ident)] = val.lower()
107# Special case for ARGB8888 and XRGB8888
108formats["argb8888"] = "0"
109formats["xrgb8888"] = "1"
110
111print("Loaded {} formats from drm_fourcc.h".format(len(formats)), file=sys.stderr)
112
113tree = ElementTree.parse("wayland.xml")
114root = tree.getroot()
115wl_shm_format = root.find("./interface[@name='wl_shm']/enum[@name='format']")
116if wl_shm_format == None:
117    raise Exception("wl_shm.format not found in wayland.xml")
118
119# Remove formats we already know about
120last_line = None
121for node in wl_shm_format:
122    if node.tag != "entry":
123        continue
124    fmt = node.attrib["name"]
125    val = node.attrib["value"]
126    if fmt not in formats:
127        raise Exception("Format present in wl_shm.formats but not in "
128            "drm_fourcc.h: " + fmt)
129    if val != formats[fmt]:
130        raise Exception("Format value in wl_shm.formats ({}) differs "
131            "from value in drm_fourcc.h ({}) for format {}"
132            .format(val, formats[fmt], fmt))
133    del formats[fmt]
134    last_line = node.sourceline
135if last_line == None:
136    raise Exception("Expected at least one existing wl_shm.format entry")
137
138print("Adding {} formats to wayland.xml...".format(len(formats)), file=sys.stderr)
139
140# Append new formats
141new_wayland_proto = wayland_proto + ".new"
142with open(new_wayland_proto, "w+") as output_file, \
143        open(wayland_proto) as input_file:
144    for i, l in enumerate(input_file.readlines()):
145        output_file.write(l)
146        if i + 1 == last_line:
147            for fmt, val in formats.items():
148                output_file.write('      <entry name="{}" value="{}"'
149                    .format(fmt, val))
150                if fmt in descriptions:
151                    output_file.write(' summary="{}"'.format(descriptions[fmt]))
152                output_file.write('/>\n')
153os.rename(new_wayland_proto, wayland_proto)
154