glfw.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414
  1. #!/usr/bin/env python
  2. # vim:fileencoding=utf-8
  3. # License: GPL v3 Copyright: 2017, Kovid Goyal <kovid at kovidgoyal.net>
  4. import json
  5. import os
  6. import re
  7. import subprocess
  8. import sys
  9. from enum import Enum
  10. from typing import Any, Callable, Dict, List, NamedTuple, Optional, Sequence, Tuple
  11. _plat = sys.platform.lower()
  12. is_linux = 'linux' in _plat
  13. is_openbsd = 'openbsd' in _plat
  14. base = os.path.dirname(os.path.abspath(__file__))
  15. def null_func() -> None:
  16. return None
  17. class CompileKey(NamedTuple):
  18. src: str
  19. dest: str
  20. class Command(NamedTuple):
  21. desc: str
  22. cmd: Sequence[str]
  23. is_newer_func: Callable[[], bool]
  24. on_success: Callable[[], None] = null_func
  25. key: Optional[CompileKey] = None
  26. keyfile: Optional[str] = None
  27. class ISA(Enum):
  28. X86 = 0x03
  29. AMD64 = 0x3e
  30. ARM64 = 0xb7
  31. Other = 0x0
  32. class BinaryArch(NamedTuple):
  33. bits: int = 64
  34. isa: ISA = ISA.AMD64
  35. class CompilerType(Enum):
  36. gcc = 'gcc'
  37. clang = 'clang'
  38. unknown = 'unknown'
  39. class Env:
  40. cc: List[str] = []
  41. cppflags: List[str] = []
  42. cflags: List[str] = []
  43. ldflags: List[str] = []
  44. library_paths: Dict[str, List[str]] = {}
  45. ldpaths: List[str] = []
  46. ccver: Tuple[int, int]
  47. vcs_rev: str = ''
  48. binary_arch: BinaryArch = BinaryArch()
  49. native_optimizations: bool = False
  50. primary_version: int = 0
  51. secondary_version: int = 0
  52. xt_version: str = ''
  53. has_copy_file_range: bool = False
  54. # glfw stuff
  55. all_headers: List[str] = []
  56. sources: List[str] = []
  57. wayland_packagedir: str = ''
  58. wayland_scanner: str = ''
  59. wayland_scanner_code: str = ''
  60. wayland_protocols: Tuple[str, ...] = ()
  61. def __init__(
  62. self, cc: List[str] = [], cppflags: List[str] = [], cflags: List[str] = [], ldflags: List[str] = [],
  63. library_paths: Dict[str, List[str]] = {}, ldpaths: Optional[List[str]] = None, ccver: Tuple[int, int] = (0, 0),
  64. vcs_rev: str = '', binary_arch: BinaryArch = BinaryArch(),
  65. native_optimizations: bool = False,
  66. ):
  67. self.cc, self.cppflags, self.cflags, self.ldflags, self.library_paths = cc, cppflags, cflags, ldflags, library_paths
  68. self.ldpaths = ldpaths or []
  69. self.ccver = ccver
  70. self.vcs_rev = vcs_rev
  71. self.binary_arch = binary_arch
  72. self.native_optimizations = native_optimizations
  73. self._cc_version_string = ''
  74. self._compiler_type: Optional[CompilerType] = None
  75. @property
  76. def cc_version_string(self) -> str:
  77. if not self._cc_version_string:
  78. self._cc_version_string = subprocess.check_output(self.cc + ['--version']).decode()
  79. return self._cc_version_string
  80. @property
  81. def compiler_type(self) -> CompilerType:
  82. if self._compiler_type is None:
  83. raw = self.cc_version_string
  84. if 'Free Software Foundation' in raw:
  85. self._compiler_type = CompilerType.gcc
  86. elif 'clang' in raw.lower().split():
  87. self._compiler_type = CompilerType.clang
  88. else:
  89. self._compiler_type = CompilerType.unknown
  90. return self._compiler_type
  91. def copy(self) -> 'Env':
  92. ans = Env(self.cc, list(self.cppflags), list(self.cflags), list(self.ldflags), dict(self.library_paths), list(self.ldpaths), self.ccver)
  93. ans.all_headers = list(self.all_headers)
  94. ans._cc_version_string = self._cc_version_string
  95. ans.sources = list(self.sources)
  96. ans.wayland_packagedir = self.wayland_packagedir
  97. ans.wayland_scanner = self.wayland_scanner
  98. ans.wayland_scanner_code = self.wayland_scanner_code
  99. ans.wayland_protocols = self.wayland_protocols
  100. ans.vcs_rev = self.vcs_rev
  101. ans.binary_arch = self.binary_arch
  102. ans.native_optimizations = self.native_optimizations
  103. ans.primary_version = self.primary_version
  104. ans.secondary_version = self.secondary_version
  105. ans.xt_version = self.xt_version
  106. ans.has_copy_file_range = self.has_copy_file_range
  107. return ans
  108. def wayland_protocol_file_name(base: str, ext: str = 'c') -> str:
  109. base = os.path.basename(base).rpartition('.')[0]
  110. return f'wayland-{base}-client-protocol.{ext}'
  111. def init_env(
  112. env: Env,
  113. pkg_config: Callable[..., List[str]],
  114. pkg_version: Callable[[str], Tuple[int, int]],
  115. at_least_version: Callable[..., None],
  116. test_compile: Callable[..., Any],
  117. module: str = 'x11'
  118. ) -> Env:
  119. ans = env.copy()
  120. ans.cflags.append('-fPIC')
  121. ans.cppflags.append(f'-D_GLFW_{module.upper()}')
  122. ans.cppflags.append('-D_GLFW_BUILD_DLL')
  123. with open(os.path.join(base, 'source-info.json')) as f:
  124. sinfo = json.load(f)
  125. module_sources = list(sinfo[module]['sources'])
  126. if module in ('x11', 'wayland'):
  127. remove = 'null_joystick.c' if is_linux else 'linux_joystick.c'
  128. module_sources.remove(remove)
  129. ans.sources = sinfo['common']['sources'] + module_sources
  130. ans.all_headers = [x for x in os.listdir(base) if x.endswith('.h')]
  131. if module in ('x11', 'wayland'):
  132. ans.cflags.append('-pthread')
  133. ans.ldpaths.extend('-pthread -lm'.split())
  134. if not is_openbsd:
  135. ans.ldpaths.extend('-lrt -ldl'.split())
  136. major, minor = pkg_version('xkbcommon')
  137. if (major, minor) < (0, 5):
  138. raise SystemExit('libxkbcommon >= 0.5 required')
  139. if major < 1:
  140. ans.cflags.append('-DXKB_HAS_NO_UTF32')
  141. if module == 'x11':
  142. for dep in 'x11 xrandr xinerama xcursor xkbcommon xkbcommon-x11 x11-xcb dbus-1'.split():
  143. ans.cflags.extend(pkg_config(dep, '--cflags-only-I'))
  144. ans.ldpaths.extend(pkg_config(dep, '--libs'))
  145. elif module == 'cocoa':
  146. ans.cppflags.append('-DGL_SILENCE_DEPRECATION')
  147. for f_ in 'Cocoa IOKit CoreFoundation CoreVideo UniformTypeIdentifiers'.split():
  148. ans.ldpaths.extend(('-framework', f_))
  149. elif module == 'wayland':
  150. at_least_version('wayland-protocols', *sinfo['wayland_protocols'])
  151. ans.wayland_packagedir = os.path.abspath(pkg_config('wayland-protocols', '--variable=pkgdatadir')[0])
  152. ans.wayland_scanner = os.path.abspath(pkg_config('wayland-scanner', '--variable=wayland_scanner')[0])
  153. scanner_version = tuple(map(int, pkg_config('wayland-scanner', '--modversion')[0].strip().split('.')))
  154. ans.wayland_scanner_code = 'private-code' if scanner_version >= (1, 14, 91) else 'code'
  155. ans.wayland_protocols = tuple(sinfo[module]['protocols'])
  156. for p in ans.wayland_protocols:
  157. ans.sources.append(wayland_protocol_file_name(p))
  158. ans.all_headers.append(wayland_protocol_file_name(p, 'h'))
  159. for dep in 'wayland-client wayland-cursor xkbcommon dbus-1'.split():
  160. ans.cflags.extend(pkg_config(dep, '--cflags-only-I'))
  161. ans.ldpaths.extend(pkg_config(dep, '--libs'))
  162. has_memfd_create = test_compile(env.cc, '-Werror', src='''#define _GNU_SOURCE
  163. #include <unistd.h>
  164. #include <sys/syscall.h>
  165. int main(void) {
  166. return syscall(__NR_memfd_create, "test", 0);
  167. }''')
  168. if has_memfd_create:
  169. ans.cppflags.append('-DHAS_MEMFD_CREATE')
  170. return ans
  171. def build_wayland_protocols(
  172. env: Env,
  173. parallel_run: Callable[[List[Command]], None],
  174. emphasis: Callable[[str], str],
  175. newer: Callable[..., bool],
  176. dest_dir: str
  177. ) -> None:
  178. items = []
  179. for protocol in env.wayland_protocols:
  180. if '/' in protocol:
  181. src = os.path.join(env.wayland_packagedir, protocol)
  182. if not os.path.exists(src):
  183. raise SystemExit(f'The wayland-protocols package on your system is missing the {protocol} protocol definition file')
  184. else:
  185. src = os.path.join(os.path.dirname(os.path.abspath(__file__)), protocol)
  186. if not os.path.exists(src):
  187. raise SystemExit(f'The local Wayland protocol {protocol} is missing from kitty sources')
  188. for ext in 'hc':
  189. dest = wayland_protocol_file_name(src, ext)
  190. dest = os.path.join(dest_dir, dest)
  191. if newer(dest, src):
  192. q = 'client-header' if ext == 'h' else env.wayland_scanner_code
  193. items.append(Command(
  194. f'Generating {emphasis(os.path.basename(dest))} ...',
  195. [env.wayland_scanner, q, src, dest], lambda: True))
  196. if items:
  197. parallel_run(items)
  198. class Arg:
  199. def __init__(self, decl: str):
  200. self.type, self.name = decl.rsplit(' ', 1)
  201. self.type = self.type.strip()
  202. self.name = self.name.strip()
  203. while self.name.startswith('*'):
  204. self.name = self.name[1:]
  205. self.type = self.type + '*'
  206. if '[' in self.name:
  207. self.type += '[' + self.name.partition('[')[-1]
  208. def __repr__(self) -> str:
  209. return f'Arg({self.type}, {self.name})'
  210. class Function:
  211. def __init__(self, declaration: str, check_fail: bool = True):
  212. self.check_fail = check_fail
  213. m = re.match(
  214. r'(.+?)\s+(glfw[A-Z][a-zA-Z0-9]+)[(](.+)[)]$', declaration
  215. )
  216. if m is None:
  217. raise SystemExit('Failed to parse ' + repr(declaration))
  218. self.restype = m.group(1).strip()
  219. self.name = m.group(2)
  220. args = m.group(3).strip().split(',')
  221. args = [x.strip() for x in args]
  222. self.args = []
  223. for a in args:
  224. if a == 'void':
  225. continue
  226. self.args.append(Arg(a))
  227. if not self.args:
  228. self.args = [Arg('void v')]
  229. def declaration(self) -> str:
  230. return 'typedef {restype} (*{name}_func)({args});\nGFW_EXTERN {name}_func {name}_impl;\n#define {name} {name}_impl'.format(
  231. restype=self.restype,
  232. name=self.name,
  233. args=', '.join(a.type for a in self.args)
  234. )
  235. def load(self) -> str:
  236. ans = f'*(void **) (&{self.name}_impl) = dlsym(handle, "{self.name}");'
  237. ans += f'\n if ({self.name}_impl == NULL) '
  238. if self.check_fail:
  239. ans += f'fail("Failed to load glfw function {self.name} with error: %s", dlerror());'
  240. else:
  241. ans += 'dlerror(); // clear error indicator'
  242. return ans
  243. def generate_wrappers(glfw_header: str) -> None:
  244. with open(glfw_header) as f:
  245. src = f.read()
  246. functions = []
  247. first = None
  248. for m in re.finditer(r'^GLFWAPI\s+(.+[)]);\s*$', src, flags=re.MULTILINE):
  249. if first is None:
  250. first = m.start()
  251. decl = m.group(1)
  252. if 'VkInstance' in decl:
  253. continue
  254. functions.append(Function(decl))
  255. for line in '''\
  256. void* glfwGetCocoaWindow(GLFWwindow* window)
  257. void* glfwGetNSGLContext(GLFWwindow *window)
  258. uint32_t glfwGetCocoaMonitor(GLFWmonitor* monitor)
  259. GLFWcocoatextinputfilterfun glfwSetCocoaTextInputFilter(GLFWwindow* window, GLFWcocoatextinputfilterfun callback)
  260. GLFWhandleurlopen glfwSetCocoaURLOpenCallback(GLFWhandleurlopen callback)
  261. GLFWcocoatogglefullscreenfun glfwSetCocoaToggleFullscreenIntercept(GLFWwindow *window, GLFWcocoatogglefullscreenfun callback)
  262. GLFWapplicationshouldhandlereopenfun glfwSetApplicationShouldHandleReopen(GLFWapplicationshouldhandlereopenfun callback)
  263. GLFWapplicationwillfinishlaunchingfun glfwSetApplicationWillFinishLaunching(GLFWapplicationwillfinishlaunchingfun callback)
  264. uint32_t glfwGetCocoaKeyEquivalent(uint32_t glfw_key, int glfw_mods, int* cocoa_mods)
  265. void glfwCocoaRequestRenderFrame(GLFWwindow *w, GLFWcocoarenderframefun callback)
  266. GLFWcocoarenderframefun glfwCocoaSetWindowResizeCallback(GLFWwindow *w, GLFWcocoarenderframefun callback)
  267. void* glfwGetX11Display(void)
  268. unsigned long glfwGetX11Window(GLFWwindow* window)
  269. void glfwSetPrimarySelectionString(GLFWwindow* window, const char* string)
  270. void glfwCocoaSetWindowChrome(GLFWwindow* window, unsigned int color, bool use_system_color, unsigned int system_color,\
  271. int background_blur, unsigned int hide_window_decorations, bool show_text_in_titlebar, int color_space, float background_opacity, bool resizable)
  272. const char* glfwGetPrimarySelectionString(GLFWwindow* window, void)
  273. int glfwGetNativeKeyForName(const char* key_name, int case_sensitive)
  274. void glfwRequestWaylandFrameEvent(GLFWwindow *handle, unsigned long long id, GLFWwaylandframecallbackfunc callback)
  275. void glfwWaylandActivateWindow(GLFWwindow *handle, const char *activation_token)
  276. const char* glfwWaylandMissingCapabilities(void)
  277. void glfwWaylandRunWithActivationToken(GLFWwindow *handle, GLFWactivationcallback cb, void *cb_data)
  278. bool glfwWaylandSetTitlebarColor(GLFWwindow *handle, uint32_t color, bool use_system_color)
  279. void glfwWaylandRedrawCSDWindowTitle(GLFWwindow *handle)
  280. void glfwWaylandSetupLayerShellForNextWindow(const GLFWLayerShellConfig *c)
  281. pid_t glfwWaylandCompositorPID(void)
  282. unsigned long long glfwDBusUserNotify(const GLFWDBUSNotificationData *n, GLFWDBusnotificationcreatedfun callback, void *data)
  283. void glfwDBusSetUserNotificationHandler(GLFWDBusnotificationactivatedfun handler)
  284. int glfwSetX11LaunchCommand(GLFWwindow *handle, char **argv, int argc)
  285. void glfwSetX11WindowAsDock(int32_t x11_window_id)
  286. void glfwSetX11WindowStrut(int32_t x11_window_id, uint32_t dimensions[12])
  287. '''.splitlines():
  288. if line:
  289. functions.append(Function(line.strip(), check_fail=False))
  290. declarations = [f.declaration() for f in functions]
  291. p = src.find(' * GLFW API tokens')
  292. p = src.find('*/', p)
  293. preamble = src[p + 2:first]
  294. header = '''\
  295. //
  296. // THIS FILE IS GENERATED BY glfw.py
  297. //
  298. // SAVE YOURSELF SOME TIME, DO NOT MANUALLY EDIT
  299. //
  300. #pragma once
  301. #include <stddef.h>
  302. #include <stdint.h>
  303. #include "monotonic.h"
  304. #ifndef GFW_EXTERN
  305. #define GFW_EXTERN extern
  306. #endif
  307. {}
  308. typedef int (* GLFWcocoatextinputfilterfun)(int,int,unsigned int,unsigned long);
  309. typedef bool (* GLFWapplicationshouldhandlereopenfun)(int);
  310. typedef bool (* GLFWhandleurlopen)(const char*);
  311. typedef void (* GLFWapplicationwillfinishlaunchingfun)(void);
  312. typedef bool (* GLFWcocoatogglefullscreenfun)(GLFWwindow*);
  313. typedef void (* GLFWcocoarenderframefun)(GLFWwindow*);
  314. typedef void (*GLFWwaylandframecallbackfunc)(unsigned long long id);
  315. typedef void (*GLFWDBusnotificationcreatedfun)(unsigned long long, uint32_t, void*);
  316. typedef void (*GLFWDBusnotificationactivatedfun)(uint32_t, int, const char*);
  317. {}
  318. const char* load_glfw(const char* path);
  319. '''.format(preamble, '\n\n'.join(declarations))
  320. with open('../kitty/glfw-wrapper.h', 'w') as f:
  321. f.write(header)
  322. code = '''
  323. // generated by glfw.py DO NOT edit
  324. #define GFW_EXTERN
  325. #include "data-types.h"
  326. #include "glfw-wrapper.h"
  327. #include <dlfcn.h>
  328. static void* handle = NULL;
  329. #define fail(msg, ...) { snprintf(buf, sizeof(buf), msg, __VA_ARGS__); return buf; }
  330. const char*
  331. load_glfw(const char* path) {
  332. static char buf[2048];
  333. handle = dlopen(path, RTLD_LAZY);
  334. if (handle == NULL) fail("Failed to dlopen %s with error: %s", path, dlerror());
  335. dlerror();
  336. LOAD
  337. return NULL;
  338. }
  339. void
  340. unload_glfw(void) {
  341. if (handle) { dlclose(handle); handle = NULL; }
  342. }
  343. '''.replace('LOAD', '\n\n '.join(f.load() for f in functions))
  344. with open('../kitty/glfw-wrapper.c', 'w') as f:
  345. f.write(code)
  346. def main() -> None:
  347. os.chdir(os.path.dirname(os.path.abspath(__file__)))
  348. generate_wrappers('glfw3.h')
  349. if __name__ == '__main__':
  350. main()