mirror of
https://github.com/kovidgoyal/kitty
synced 2026-06-06 01:05:48 +02:00
Use an explicit runtime check to apply the newer corner styling on macOS 26 while leaving behavior unchanged on older macOS versions for compatibility. Also add the required QuartzCore framework for linking the corner curve API.
418 lines
15 KiB
Python
Executable File
418 lines
15 KiB
Python
Executable File
#!/usr/bin/env python
|
|
# vim:fileencoding=utf-8
|
|
# License: GPL v3 Copyright: 2017, Kovid Goyal <kovid at kovidgoyal.net>
|
|
|
|
import json
|
|
import os
|
|
import re
|
|
import subprocess
|
|
import sys
|
|
from enum import Enum
|
|
from typing import Any, Callable, Dict, List, NamedTuple, Optional, Sequence, Tuple
|
|
|
|
_plat = sys.platform.lower()
|
|
is_linux = 'linux' in _plat
|
|
is_openbsd = 'openbsd' in _plat
|
|
base = os.path.dirname(os.path.abspath(__file__))
|
|
|
|
|
|
def null_func() -> None:
|
|
return None
|
|
|
|
|
|
class CompileKey(NamedTuple):
|
|
src: str
|
|
dest: str
|
|
|
|
|
|
class Command(NamedTuple):
|
|
desc: str
|
|
cmd: Sequence[str]
|
|
is_newer_func: Callable[[], bool]
|
|
on_success: Callable[[], None] = null_func
|
|
key: Optional[CompileKey] = None
|
|
keyfile: Optional[str] = None
|
|
|
|
|
|
class ISA(Enum):
|
|
X86 = 0x03
|
|
AMD64 = 0x3e
|
|
ARM64 = 0xb7
|
|
Other = 0x0
|
|
|
|
|
|
class BinaryArch(NamedTuple):
|
|
bits: int = 64
|
|
isa: ISA = ISA.AMD64
|
|
|
|
|
|
class CompilerType(Enum):
|
|
gcc = 'gcc'
|
|
clang = 'clang'
|
|
unknown = 'unknown'
|
|
|
|
|
|
class Env:
|
|
|
|
cc: List[str] = []
|
|
cppflags: List[str] = []
|
|
cflags: List[str] = []
|
|
ldflags: List[str] = []
|
|
library_paths: Dict[str, List[str]] = {}
|
|
ldpaths: List[str] = []
|
|
ccver: Tuple[int, int]
|
|
vcs_rev: str = ''
|
|
binary_arch: BinaryArch = BinaryArch()
|
|
native_optimizations: bool = False
|
|
primary_version: int = 0
|
|
secondary_version: int = 0
|
|
xt_version: str = ''
|
|
has_copy_file_range: bool = False
|
|
|
|
# glfw stuff
|
|
all_headers: List[str] = []
|
|
sources: List[str] = []
|
|
wayland_packagedir: str = ''
|
|
wayland_scanner: str = ''
|
|
wayland_scanner_code: str = ''
|
|
wayland_protocols: Tuple[str, ...] = ()
|
|
|
|
def __init__(
|
|
self, cc: List[str] = [], cppflags: List[str] = [], cflags: List[str] = [], ldflags: List[str] = [],
|
|
library_paths: Dict[str, List[str]] = {}, ldpaths: Optional[List[str]] = None, ccver: Tuple[int, int] = (0, 0),
|
|
vcs_rev: str = '', binary_arch: BinaryArch = BinaryArch(),
|
|
native_optimizations: bool = False,
|
|
):
|
|
self.cc, self.cppflags, self.cflags, self.ldflags, self.library_paths = cc, cppflags, cflags, ldflags, library_paths
|
|
self.ldpaths = ldpaths or []
|
|
self.ccver = ccver
|
|
self.vcs_rev = vcs_rev
|
|
self.binary_arch = binary_arch
|
|
self.native_optimizations = native_optimizations
|
|
self._cc_version_string = ''
|
|
self._compiler_type: Optional[CompilerType] = None
|
|
|
|
@property
|
|
def cc_version_string(self) -> str:
|
|
if not self._cc_version_string:
|
|
self._cc_version_string = subprocess.check_output(self.cc + ['--version']).decode()
|
|
return self._cc_version_string
|
|
|
|
@property
|
|
def compiler_type(self) -> CompilerType:
|
|
if self._compiler_type is None:
|
|
raw = self.cc_version_string
|
|
if 'Free Software Foundation' in raw:
|
|
self._compiler_type = CompilerType.gcc
|
|
elif 'clang' in raw.lower().split():
|
|
self._compiler_type = CompilerType.clang
|
|
else:
|
|
self._compiler_type = CompilerType.unknown
|
|
return self._compiler_type
|
|
|
|
def copy(self) -> 'Env':
|
|
ans = Env(self.cc, list(self.cppflags), list(self.cflags), list(self.ldflags), dict(self.library_paths), list(self.ldpaths), self.ccver)
|
|
ans.all_headers = list(self.all_headers)
|
|
ans._cc_version_string = self._cc_version_string
|
|
ans.sources = list(self.sources)
|
|
ans.wayland_packagedir = self.wayland_packagedir
|
|
ans.wayland_scanner = self.wayland_scanner
|
|
ans.wayland_scanner_code = self.wayland_scanner_code
|
|
ans.wayland_protocols = self.wayland_protocols
|
|
ans.vcs_rev = self.vcs_rev
|
|
ans.binary_arch = self.binary_arch
|
|
ans.native_optimizations = self.native_optimizations
|
|
ans.primary_version = self.primary_version
|
|
ans.secondary_version = self.secondary_version
|
|
ans.xt_version = self.xt_version
|
|
ans.has_copy_file_range = self.has_copy_file_range
|
|
return ans
|
|
|
|
|
|
def wayland_protocol_file_name(base: str, ext: str = 'c') -> str:
|
|
base = os.path.basename(base).rpartition('.')[0]
|
|
return f'wayland-{base}-client-protocol.{ext}'
|
|
|
|
|
|
def init_env(
|
|
env: Env,
|
|
pkg_config: Callable[..., List[str]],
|
|
pkg_version: Callable[[str], Tuple[int, int]],
|
|
at_least_version: Callable[..., None],
|
|
test_compile: Callable[..., Any],
|
|
module: str = 'x11'
|
|
) -> Env:
|
|
ans = env.copy()
|
|
ans.cflags.append('-fPIC')
|
|
ans.cppflags.append(f'-D_GLFW_{module.upper()}')
|
|
ans.cppflags.append('-D_GLFW_BUILD_DLL')
|
|
|
|
with open(os.path.join(base, 'source-info.json')) as f:
|
|
sinfo = json.load(f)
|
|
module_sources = list(sinfo[module]['sources'])
|
|
if module in ('x11', 'wayland'):
|
|
remove = 'null_joystick.c' if is_linux else 'linux_joystick.c'
|
|
module_sources.remove(remove)
|
|
|
|
ans.sources = sinfo['common']['sources'] + module_sources
|
|
ans.all_headers = [x for x in os.listdir(base) if x.endswith('.h')]
|
|
|
|
if module in ('x11', 'wayland'):
|
|
ans.cflags.append('-pthread')
|
|
ans.ldpaths.extend('-pthread -lm'.split())
|
|
if not is_openbsd:
|
|
ans.ldpaths.extend('-lrt -ldl'.split())
|
|
major, minor = pkg_version('xkbcommon')
|
|
if (major, minor) < (0, 5):
|
|
raise SystemExit('libxkbcommon >= 0.5 required')
|
|
if major < 1:
|
|
ans.cflags.append('-DXKB_HAS_NO_UTF32')
|
|
|
|
if module == 'x11':
|
|
for dep in 'x11 xrandr xinerama xcursor xkbcommon xkbcommon-x11 x11-xcb dbus-1'.split():
|
|
ans.cflags.extend(pkg_config(dep, '--cflags-only-I'))
|
|
ans.ldpaths.extend(pkg_config(dep, '--libs'))
|
|
|
|
elif module == 'cocoa':
|
|
ans.cppflags.append('-DGL_SILENCE_DEPRECATION')
|
|
for f_ in 'Cocoa IOKit CoreFoundation CoreVideo QuartzCore UniformTypeIdentifiers'.split():
|
|
ans.ldpaths.extend(('-framework', f_))
|
|
|
|
elif module == 'wayland':
|
|
at_least_version('wayland-protocols', *sinfo['wayland_protocols'])
|
|
ans.wayland_packagedir = os.path.abspath(pkg_config('wayland-protocols', '--variable=pkgdatadir')[0])
|
|
ans.wayland_scanner = os.path.abspath(pkg_config('wayland-scanner', '--variable=wayland_scanner')[0])
|
|
scanner_version = tuple(map(int, pkg_config('wayland-scanner', '--modversion')[0].strip().split('.')))
|
|
ans.wayland_scanner_code = 'private-code' if scanner_version >= (1, 14, 91) else 'code'
|
|
ans.wayland_protocols = tuple(sinfo[module]['protocols'])
|
|
for p in ans.wayland_protocols:
|
|
ans.sources.append(wayland_protocol_file_name(p))
|
|
ans.all_headers.append(wayland_protocol_file_name(p, 'h'))
|
|
for dep in 'wayland-client wayland-cursor xkbcommon dbus-1'.split():
|
|
ans.cflags.extend(pkg_config(dep, '--cflags-only-I'))
|
|
ans.ldpaths.extend(pkg_config(dep, '--libs'))
|
|
has_memfd_create = test_compile(env.cc, '-Werror', src='''#define _GNU_SOURCE
|
|
#include <unistd.h>
|
|
#include <sys/syscall.h>
|
|
int main(void) {
|
|
return syscall(__NR_memfd_create, "test", 0);
|
|
}''')
|
|
if has_memfd_create:
|
|
ans.cppflags.append('-DHAS_MEMFD_CREATE')
|
|
|
|
return ans
|
|
|
|
|
|
def build_wayland_protocols(
|
|
env: Env,
|
|
parallel_run: Callable[[List[Command]], None],
|
|
emphasis: Callable[[str], str],
|
|
newer: Callable[..., bool],
|
|
dest_dir: str
|
|
) -> None:
|
|
items = []
|
|
for protocol in env.wayland_protocols:
|
|
if '/' in protocol:
|
|
src = os.path.join(env.wayland_packagedir, protocol)
|
|
if not os.path.exists(src):
|
|
raise SystemExit(f'The wayland-protocols package on your system is missing the {protocol} protocol definition file')
|
|
else:
|
|
src = os.path.join(os.path.dirname(os.path.abspath(__file__)), protocol)
|
|
if not os.path.exists(src):
|
|
raise SystemExit(f'The local Wayland protocol {protocol} is missing from kitty sources')
|
|
for ext in 'hc':
|
|
dest = wayland_protocol_file_name(src, ext)
|
|
dest = os.path.join(dest_dir, dest)
|
|
if newer(dest, src):
|
|
q = 'client-header' if ext == 'h' else env.wayland_scanner_code
|
|
items.append(Command(
|
|
f'Generating {emphasis(os.path.basename(dest))} ...',
|
|
[env.wayland_scanner, q, src, dest], lambda: True))
|
|
if items:
|
|
parallel_run(items)
|
|
|
|
|
|
class Arg:
|
|
|
|
def __init__(self, decl: str):
|
|
self.type, self.name = decl.rsplit(' ', 1)
|
|
self.type = self.type.strip()
|
|
self.name = self.name.strip()
|
|
while self.name.startswith('*'):
|
|
self.name = self.name[1:]
|
|
self.type = self.type + '*'
|
|
if '[' in self.name:
|
|
self.type += '[' + self.name.partition('[')[-1]
|
|
|
|
def __repr__(self) -> str:
|
|
return f'Arg({self.type}, {self.name})'
|
|
|
|
|
|
class Function:
|
|
|
|
def __init__(self, declaration: str, check_fail: bool = True):
|
|
self.check_fail = check_fail
|
|
m = re.match(
|
|
r'(.+?)\s+(glfw[A-Z][a-zA-Z0-9]+)[(](.+)[)]$', declaration
|
|
)
|
|
if m is None:
|
|
raise SystemExit('Failed to parse ' + repr(declaration))
|
|
self.restype = m.group(1).strip()
|
|
self.name = m.group(2)
|
|
args = m.group(3).strip().split(',')
|
|
args = [x.strip() for x in args]
|
|
self.args = []
|
|
for a in args:
|
|
if a == 'void':
|
|
continue
|
|
self.args.append(Arg(a))
|
|
if not self.args:
|
|
self.args = [Arg('void v')]
|
|
|
|
def declaration(self) -> str:
|
|
return 'typedef {restype} (*{name}_func)({args});\nGFW_EXTERN {name}_func {name}_impl;\n#define {name} {name}_impl'.format(
|
|
restype=self.restype,
|
|
name=self.name,
|
|
args=', '.join(a.type for a in self.args)
|
|
)
|
|
|
|
def load(self) -> str:
|
|
ans = f'*(void **) (&{self.name}_impl) = dlsym(handle, "{self.name}");'
|
|
ans += f'\n if ({self.name}_impl == NULL) '
|
|
if self.check_fail:
|
|
ans += f'fail("Failed to load glfw function {self.name} with error: %s", dlerror());'
|
|
else:
|
|
ans += 'dlerror(); // clear error indicator'
|
|
return ans
|
|
|
|
|
|
def generate_wrappers(glfw_header: str) -> None:
|
|
with open(glfw_header) as f:
|
|
src = f.read()
|
|
functions = []
|
|
first = None
|
|
for m in re.finditer(r'^GLFWAPI\s+(.+[)]);\s*$', src, flags=re.MULTILINE):
|
|
if first is None:
|
|
first = m.start()
|
|
decl = m.group(1)
|
|
if 'VkInstance' in decl:
|
|
continue
|
|
functions.append(Function(decl))
|
|
for line in '''\
|
|
void* glfwGetCocoaWindow(GLFWwindow* window)
|
|
void* glfwGetNSGLContext(GLFWwindow *window)
|
|
uint32_t glfwGetCocoaMonitor(GLFWmonitor* monitor)
|
|
GLFWcocoatextinputfilterfun glfwSetCocoaTextInputFilter(GLFWwindow* window, GLFWcocoatextinputfilterfun callback)
|
|
GLFWhandleurlopen glfwSetCocoaURLOpenCallback(GLFWhandleurlopen callback)
|
|
GLFWcocoatogglefullscreenfun glfwSetCocoaToggleFullscreenIntercept(GLFWwindow *window, GLFWcocoatogglefullscreenfun callback)
|
|
GLFWapplicationshouldhandlereopenfun glfwSetApplicationShouldHandleReopen(GLFWapplicationshouldhandlereopenfun callback)
|
|
GLFWapplicationwillfinishlaunchingfun glfwSetApplicationWillFinishLaunching(GLFWapplicationwillfinishlaunchingfun callback)
|
|
uint32_t glfwGetCocoaKeyEquivalent(uint32_t glfw_key, int glfw_mods, int* cocoa_mods)
|
|
void glfwCocoaRequestRenderFrame(GLFWwindow *w, GLFWcocoarenderframefun callback)
|
|
bool glfwCocoaRecreateGLDrawable(GLFWwindow *w)
|
|
GLFWcocoarenderframefun glfwCocoaSetWindowResizeCallback(GLFWwindow *w, GLFWcocoarenderframefun callback)
|
|
void* glfwGetX11Display(void)
|
|
unsigned long glfwGetX11Window(GLFWwindow* window)
|
|
void glfwSetPrimarySelectionString(GLFWwindow* window, const char* string)
|
|
void glfwCocoaCycleThroughOSWindows(bool backwards)
|
|
void glfwCocoaSetWindowChrome(GLFWwindow* window, unsigned int color, bool use_system_color, unsigned int system_color,\
|
|
int background_blur, unsigned int hide_window_decorations, bool show_text_in_titlebar, int color_space, float background_opacity, bool resizable)
|
|
void glfwCocoaRegisterMIMETypes(GLFWwindow *window, const char **mimes, size_t count)
|
|
const char* glfwGetPrimarySelectionString(GLFWwindow* window, void)
|
|
int glfwGetNativeKeyForName(const char* key_name, int case_sensitive)
|
|
void glfwRequestWaylandFrameEvent(GLFWwindow *handle, unsigned long long id, GLFWwaylandframecallbackfunc callback)
|
|
void glfwWaylandActivateWindow(GLFWwindow *handle, const char *activation_token)
|
|
const char* glfwWaylandMissingCapabilities(void)
|
|
void glfwWaylandRunWithActivationToken(GLFWwindow *handle, GLFWactivationcallback cb, void *cb_data)
|
|
bool glfwWaylandSetTitlebarColor(GLFWwindow *handle, uint32_t color, bool use_system_color)
|
|
void glfwWaylandSetTitlebarHidden(GLFWwindow *handle, bool hidden)
|
|
void glfwWaylandRedrawCSDWindowTitle(GLFWwindow *handle)
|
|
bool glfwWaylandIsWindowFullyCreated(GLFWwindow *handle)
|
|
bool glfwWaylandBeep(GLFWwindow *handle)
|
|
pid_t glfwWaylandCompositorPID(void)
|
|
void glfwConfigureMomentumScroller(double friction, double min_velocity, double max_velocity, unsigned timer_interval)
|
|
unsigned long long glfwDBusUserNotify(const GLFWDBUSNotificationData *n, GLFWDBusnotificationcreatedfun callback, void *data)
|
|
void glfwDBusSetUserNotificationHandler(GLFWDBusnotificationactivatedfun handler)
|
|
int glfwSetX11LaunchCommand(GLFWwindow *handle, char **argv, int argc)
|
|
'''.splitlines():
|
|
if line:
|
|
functions.append(Function(line.strip(), check_fail=False))
|
|
|
|
declarations = [f.declaration() for f in functions]
|
|
p = src.find(' * GLFW API tokens')
|
|
p = src.find('*/', p)
|
|
preamble = src[p + 2:first]
|
|
header = '''\
|
|
//
|
|
// THIS FILE IS GENERATED BY glfw.py
|
|
//
|
|
// SAVE YOURSELF SOME TIME, DO NOT MANUALLY EDIT
|
|
//
|
|
|
|
#pragma once
|
|
#include <stddef.h>
|
|
#include <stdint.h>
|
|
#include "monotonic.h"
|
|
|
|
#ifndef GFW_EXTERN
|
|
#define GFW_EXTERN extern
|
|
#endif
|
|
{}
|
|
|
|
typedef int (* GLFWcocoatextinputfilterfun)(int,int,unsigned int,unsigned long);
|
|
typedef bool (* GLFWapplicationshouldhandlereopenfun)(int);
|
|
typedef bool (* GLFWhandleurlopen)(const char*);
|
|
typedef void (* GLFWapplicationwillfinishlaunchingfun)(bool);
|
|
typedef bool (* GLFWcocoatogglefullscreenfun)(GLFWwindow*);
|
|
typedef void (* GLFWcocoarenderframefun)(GLFWwindow*);
|
|
typedef void (*GLFWwaylandframecallbackfunc)(unsigned long long id);
|
|
typedef void (*GLFWDBusnotificationcreatedfun)(unsigned long long, uint32_t, void*);
|
|
typedef void (*GLFWDBusnotificationactivatedfun)(uint32_t, int, const char*);
|
|
{}
|
|
|
|
const char* load_glfw(const char* path);
|
|
'''.format(preamble, '\n\n'.join(declarations))
|
|
with open('../kitty/glfw-wrapper.h', 'w') as f:
|
|
f.write(header)
|
|
|
|
code = '''
|
|
// generated by glfw.py DO NOT edit
|
|
|
|
#define GFW_EXTERN
|
|
#include "data-types.h"
|
|
#include "glfw-wrapper.h"
|
|
#include <dlfcn.h>
|
|
|
|
static void* handle = NULL;
|
|
|
|
#define fail(msg, ...) { snprintf(buf, sizeof(buf), msg, __VA_ARGS__); return buf; }
|
|
|
|
const char*
|
|
load_glfw(const char* path) {
|
|
static char buf[2048];
|
|
handle = dlopen(path, RTLD_LAZY);
|
|
if (handle == NULL) fail("Failed to dlopen %s with error: %s", path, dlerror());
|
|
dlerror();
|
|
|
|
LOAD
|
|
|
|
return NULL;
|
|
}
|
|
|
|
void
|
|
unload_glfw(void) {
|
|
if (handle) { dlclose(handle); handle = NULL; }
|
|
}
|
|
'''.replace('LOAD', '\n\n '.join(f.load() for f in functions))
|
|
with open('../kitty/glfw-wrapper.c', 'w') as f:
|
|
f.write(code)
|
|
|
|
|
|
def main() -> None:
|
|
os.chdir(os.path.dirname(os.path.abspath(__file__)))
|
|
generate_wrappers('glfw3.h')
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|