From d3656bf7e9e870d3fa157bfa78367aaca48ce580 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 13 May 2022 19:56:19 +0530 Subject: [PATCH] Linux: Load libfontconfig at runtime to allow the binaries to work for running kittens on servers without FontConfig --- docs/changelog.rst | 3 ++ kitty/fontconfig.c | 104 +++++++++++++++++++++++++++++++++++++++++++++ setup.py | 17 +++++++- 3 files changed, 122 insertions(+), 2 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 0546e4dd2..9e466a517 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -61,6 +61,9 @@ Detailed list of changes - Fix deleting images by row not calculating image bounds correctly (:iss:`5081`) +- Linux: Load libfontconfig at runtime to allow the binaries to work for + running kittens on servers without FontConfig + 0.25.0 [2022-04-11] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/kitty/fontconfig.c b/kitty/fontconfig.c index 4858b32ad..9710f2109 100644 --- a/kitty/fontconfig.c +++ b/kitty/fontconfig.c @@ -10,6 +10,7 @@ #include "lineops.h" #include "fonts.h" #include +#include #include "emoji.h" #include "freetype_render_ui_text.h" #ifndef FC_COLOR @@ -18,10 +19,111 @@ static bool initialized = false; +static void* libfontconfig_handle = NULL; + +#define FcInit dynamically_loaded_fc_symbol.Init +#define FcFini dynamically_loaded_fc_symbol.Fini +#define FcCharSetAddChar dynamically_loaded_fc_symbol.CharSetAddChar +#define FcPatternDestroy dynamically_loaded_fc_symbol.PatternDestroy +#define FcObjectSetDestroy dynamically_loaded_fc_symbol.ObjectSetDestroy +#define FcPatternAddDouble dynamically_loaded_fc_symbol.PatternAddDouble +#define FcPatternAddString dynamically_loaded_fc_symbol.PatternAddString +#define FcFontMatch dynamically_loaded_fc_symbol.FontMatch +#define FcCharSetCreate dynamically_loaded_fc_symbol.CharSetCreate +#define FcPatternGetString dynamically_loaded_fc_symbol.PatternGetString +#define FcFontSetDestroy dynamically_loaded_fc_symbol.FontSetDestroy +#define FcPatternGetInteger dynamically_loaded_fc_symbol.PatternGetInteger +#define FcPatternAddBool dynamically_loaded_fc_symbol.PatternAddBool +#define FcFontList dynamically_loaded_fc_symbol.FontList +#define FcObjectSetBuild dynamically_loaded_fc_symbol.ObjectSetBuild +#define FcCharSetDestroy dynamically_loaded_fc_symbol.CharSetDestroy +#define FcConfigSubstitute dynamically_loaded_fc_symbol.ConfigSubstitute +#define FcDefaultSubstitute dynamically_loaded_fc_symbol.DefaultSubstitute +#define FcPatternAddInteger dynamically_loaded_fc_symbol.PatternAddInteger +#define FcPatternCreate dynamically_loaded_fc_symbol.PatternCreate +#define FcPatternGetBool dynamically_loaded_fc_symbol.PatternGetBool +#define FcPatternAddCharSet dynamically_loaded_fc_symbol.PatternAddCharSet + +static struct { + FcBool(*Init)(void); + void(*Fini)(void); + FcBool (*CharSetAddChar) (FcCharSet *fcs, FcChar32 ucs4); + void (*PatternDestroy) (FcPattern *p); + void (*ObjectSetDestroy) (FcObjectSet *os); + FcBool (*PatternAddDouble) (FcPattern *p, const char *object, double d); + FcBool (*PatternAddString) (FcPattern *p, const char *object, const FcChar8 *s); + FcPattern * (*FontMatch) (FcConfig *config, FcPattern *p, FcResult *result); + FcCharSet* (*CharSetCreate) (void); + FcResult (*PatternGetString) (const FcPattern *p, const char *object, int n, FcChar8 ** s); + void (*FontSetDestroy) (FcFontSet *s); + FcResult (*PatternGetInteger) (const FcPattern *p, const char *object, int n, int *i); + FcBool (*PatternAddBool) (FcPattern *p, const char *object, FcBool b); + FcFontSet * (*FontList) (FcConfig *config, FcPattern *p, FcObjectSet *os); + FcObjectSet * (*ObjectSetBuild) (const char *first, ...); + void (*CharSetDestroy) (FcCharSet *fcs); + FcBool (*ConfigSubstitute) (FcConfig *config, FcPattern *p, FcMatchKind kind); + void (*DefaultSubstitute) (FcPattern *pattern); + FcBool (*PatternAddInteger) (FcPattern *p, const char *object, int i); + FcPattern * (*PatternCreate) (void); + FcResult (*PatternGetBool) (const FcPattern *p, const char *object, int n, FcBool *b); + FcBool (*PatternAddCharSet) (FcPattern *p, const char *object, const FcCharSet *c); +} dynamically_loaded_fc_symbol = {0}; +#define LOAD_FUNC(name) {\ + *(void **) (&dynamically_loaded_fc_symbol.name) = dlsym(libfontconfig_handle, "Fc" #name); \ + if (!dynamically_loaded_fc_symbol.name) { \ + const char* error = dlerror(); \ + fatal("Failed to load the function Fc" #name " with error: %s", error ? error : ""); \ + } \ +} + + +static void +load_fontconfig_lib(void) { + const char* libnames[] = { +#if defined(_KITTY_FONTCONFIG_LIBRARY) + _KITTY_FONTCONFIG_LIBRARY, +#else + "libfontconfig.so", + // some installs are missing the .so symlink, so try the full name + "libfontconfig.so.1", +#endif + NULL + }; + for (int i = 0; libnames[i]; i++) { + libfontconfig_handle = dlopen(libnames[i], RTLD_LAZY); + if (libfontconfig_handle) break; + } + if (libfontconfig_handle == NULL) { fatal("Failed to find and load fontconfig"); } + dlerror(); /* Clear any existing error */ + LOAD_FUNC(Init); + LOAD_FUNC(Fini); + LOAD_FUNC(CharSetAddChar); + LOAD_FUNC(PatternDestroy); + LOAD_FUNC(ObjectSetDestroy); + LOAD_FUNC(PatternAddDouble); + LOAD_FUNC(PatternAddString); + LOAD_FUNC(FontMatch); + LOAD_FUNC(CharSetCreate); + LOAD_FUNC(PatternGetString); + LOAD_FUNC(FontSetDestroy); + LOAD_FUNC(PatternGetInteger); + LOAD_FUNC(PatternAddBool); + LOAD_FUNC(FontList); + LOAD_FUNC(ObjectSetBuild); + LOAD_FUNC(CharSetDestroy); + LOAD_FUNC(ConfigSubstitute); + LOAD_FUNC(DefaultSubstitute); + LOAD_FUNC(PatternAddInteger); + LOAD_FUNC(PatternCreate); + LOAD_FUNC(PatternGetBool); + LOAD_FUNC(PatternAddCharSet); +} +#undef LOAD_FUNC static void ensure_initialized(void) { if (!initialized) { + load_fontconfig_lib(); if (!FcInit()) fatal("Failed to initialize fontconfig library"); initialized = true; } @@ -31,6 +133,8 @@ static void finalize(void) { if (initialized) { FcFini(); + dlclose(libfontconfig_handle); + libfontconfig_handle = NULL; initialized = false; } } diff --git a/setup.py b/setup.py index 4a05ec33a..ac39eba97 100755 --- a/setup.py +++ b/setup.py @@ -77,6 +77,7 @@ class Options(argparse.Namespace): egl_library: Optional[str] = os.getenv('KITTY_EGL_LIBRARY') startup_notification_library: Optional[str] = os.getenv('KITTY_STARTUP_NOTIFICATION_LIBRARY') canberra_library: Optional[str] = os.getenv('KITTY_CANBERRA_LIBRARY') + fontconfig_library: Optional[str] = os.getenv('KITTY_FONTCONFIG_LIBRARY') def emphasis(text: str) -> str: @@ -296,6 +297,7 @@ def init_env( egl_library: Optional[str] = None, startup_notification_library: Optional[str] = None, canberra_library: Optional[str] = None, + fontconfig_library: Optional[str] = None, extra_logging: Iterable[str] = (), extra_include_dirs: Iterable[str] = (), ignore_compiler_warnings: bool = False, @@ -381,6 +383,10 @@ def init_env( assert('"' not in canberra_library) desktop_libs += [f'_KITTY_CANBERRA_LIBRARY="{canberra_library}"'] + if fontconfig_library is not None: + assert('"' not in fontconfig_library) + desktop_libs += [f'_KITTY_FONTCONFIG_LIBRARY="{fontconfig_library}"'] + if desktop_libs != []: library_paths['kitty/desktop.c'] = desktop_libs @@ -432,7 +438,7 @@ def kitty_env() -> Env: cppflags.append('-DGL_SILENCE_DEPRECATION') else: cflags.extend(pkg_config('fontconfig', '--cflags-only-I')) - platform_libs = pkg_config('fontconfig', '--libs') + platform_libs = [] cflags.extend(pkg_config('harfbuzz', '--cflags-only-I')) platform_libs.extend(pkg_config('harfbuzz', '--libs')) pylib = get_python_flags(cflags) @@ -810,7 +816,7 @@ def init_env_from_args(args: Options, native_optimizations: bool = False) -> Non global env env = init_env( args.debug, args.sanitize, native_optimizations, args.link_time_optimization, args.profile, - args.egl_library, args.startup_notification_library, args.canberra_library, + args.egl_library, args.startup_notification_library, args.canberra_library, args.fontconfig_library, args.extra_logging, args.extra_include_dirs, args.ignore_compiler_warnings, args.build_universal_binary, args.extra_library_dirs ) @@ -1470,6 +1476,13 @@ def option_parser() -> argparse.ArgumentParser: # {{{ help='The filename argument passed to dlopen for libcanberra.' ' This can be used to change the name of the loaded library or specify an absolute path.' ) + p.add_argument( + '--fontconfig-library', + type=str, + default=Options.fontconfig_library, + help='The filename argument passed to dlopen for libfontconfig.' + ' This can be used to change the name of the loaded library or specify an absolute path.' + ) p.add_argument( '--disable-link-time-optimization', dest='link_time_optimization',