generate-shm-formats.py 5.2 KB

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