// Copyright 2021 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "ui/gtk/gtk_compat.h" #include #include "base/check.h" #include "base/command_line.h" #include "base/compiler_specific.h" #include "base/debug/leak_annotations.h" #include "base/no_destructor.h" #include "base/strings/string_number_conversions.h" #include "ui/gfx/color_palette.h" #include "ui/gtk/gtk_stubs.h" namespace gtk { // IMPORTANT: All functions in this file that call dlsym()'ed // functions should be annotated with DISABLE_CFI_DLSYM. namespace { const char kGtkVersionFlag[] = "gtk-version"; struct Gdk3Rgba { gdouble r; gdouble g; gdouble b; gdouble a; }; struct Gdk4Rgba { float r; float g; float b; float a; }; template SkColor GdkRgbaToSkColor(const T& color) { return SkColorSetARGB(color.a * 255, color.r * 255, color.g * 255, color.b * 255); } void* DlOpen(const char* library_name, bool check = true) { void* library = dlopen(library_name, RTLD_LAZY | RTLD_GLOBAL); CHECK(!check || library); return library; } void* DlSym(void* library, const char* name) { void* symbol = dlsym(library, name); CHECK(symbol); return symbol; } template auto DlCast(void* symbol) { return reinterpret_cast(symbol); } void* GetLibGio() { static void* libgio = DlOpen("libgio-2.0.so.0"); return libgio; } void* GetLibGdkPixbuf() { static void* libgdk_pixbuf = DlOpen("libgdk_pixbuf-2.0.so.0"); return libgdk_pixbuf; } void* GetLibGdk3() { static void* libgdk3 = DlOpen("libgdk-3.so.0"); return libgdk3; } void* GetLibGtk3(bool check = true) { static void* libgtk3 = DlOpen("libgtk-3.so.0", check); return libgtk3; } void* GetLibGtk4(bool check = true) { static void* libgtk4 = DlOpen("libgtk-4.so.1", check); return libgtk4; } void* GetLibGtk() { if (GtkCheckVersion(4)) return GetLibGtk4(); return GetLibGtk3(); } bool LoadGtk3() { if (!GetLibGtk3(false)) return false; ui_gtk::InitializeGdk_pixbuf(GetLibGdkPixbuf()); ui_gtk::InitializeGdk(GetLibGdk3()); ui_gtk::InitializeGtk(GetLibGtk3()); return true; } bool LoadGtk4() { if (!GetLibGtk4(false)) return false; // In GTK4 mode, we require some newer gio symbols that aren't available // in Ubuntu Xenial or Debian Stretch. Fortunately, GTK4 itself depends // on a newer version of glib (which provides gio), so if we're using // GTK4, we can safely assume the system has the required gio symbols. ui_gtk::InitializeGio(GetLibGio()); // In GTK4, libgtk provides all gdk_*, gsk_*, and gtk_* symbols. ui_gtk::InitializeGdk(GetLibGtk4()); ui_gtk::InitializeGsk(GetLibGtk4()); ui_gtk::InitializeGtk(GetLibGtk4()); return true; } bool LoadGtkImpl() { auto* cmd = base::CommandLine::ForCurrentProcess(); unsigned int gtk_version; if (!base::StringToUint(cmd->GetSwitchValueASCII(kGtkVersionFlag), >k_version)) { gtk_version = 0; } // Prefer GTK3 for now as the GTK4 ecosystem is still immature. return gtk_version == 4 ? LoadGtk4() || LoadGtk3() : LoadGtk3() || LoadGtk4(); } gfx::Insets InsetsFromGtkBorder(const GtkBorder& border) { return gfx::Insets::TLBR(border.top, border.left, border.bottom, border.right); } } // namespace bool LoadGtk() { static bool loaded = LoadGtkImpl(); return loaded; } const base::Version& GtkVersion() { static base::NoDestructor gtk_version( std::vector{gtk_get_major_version(), gtk_get_minor_version(), gtk_get_micro_version()}); return *gtk_version; } bool GtkCheckVersion(uint32_t major, uint32_t minor, uint32_t micro) { return GtkVersion() >= base::Version({major, minor, micro}); } DISABLE_CFI_DLSYM bool GtkInitCheck(int* argc, char** argv) { static void* gtk_init_check = DlSym(GetLibGtk(), "gtk_init_check"); if (GtkCheckVersion(4)) return DlCast(gtk_init_check)(); { // http://crbug.com/423873 ANNOTATE_SCOPED_MEMORY_LEAK; return DlCast(gtk_init_check)(argc, &argv); } } DISABLE_CFI_DLSYM gfx::Insets GtkStyleContextGetPadding(GtkStyleContext* context) { static void* get_padding = DlSym(GetLibGtk(), "gtk_style_context_get_padding"); GtkBorder padding; if (GtkCheckVersion(4)) { DlCast(get_padding)(context, &padding); } else { DlCast(get_padding)( context, gtk_style_context_get_state(context), &padding); } return InsetsFromGtkBorder(padding); } DISABLE_CFI_DLSYM gfx::Insets GtkStyleContextGetBorder(GtkStyleContext* context) { static void* get_border = DlSym(GetLibGtk(), "gtk_style_context_get_border"); GtkBorder border; if (GtkCheckVersion(4)) { DlCast(get_border)(context, &border); } else { DlCast(get_border)( context, gtk_style_context_get_state(context), &border); } return InsetsFromGtkBorder(border); } DISABLE_CFI_DLSYM gfx::Insets GtkStyleContextGetMargin(GtkStyleContext* context) { static void* get_margin = DlSym(GetLibGtk(), "gtk_style_context_get_margin"); GtkBorder margin; if (GtkCheckVersion(4)) { DlCast(get_margin)(context, &margin); } else { DlCast(get_margin)( context, gtk_style_context_get_state(context), &margin); } return InsetsFromGtkBorder(margin); } DISABLE_CFI_DLSYM SkColor GtkStyleContextGetColor(GtkStyleContext* context) { static void* get_color = DlSym(GetLibGtk(), "gtk_style_context_get_color"); if (GtkCheckVersion(4)) { Gdk4Rgba color; DlCast(get_color)(context, &color); return GdkRgbaToSkColor(color); } Gdk3Rgba color; DlCast(get_color)( context, gtk_style_context_get_state(context), &color); return GdkRgbaToSkColor(color); } DISABLE_CFI_DLSYM SkColor GtkStyleContextGetBackgroundColor(GtkStyleContext* context) { DCHECK(!GtkCheckVersion(4)); static void* get_bg_color = DlSym(GetLibGtk(), "gtk_style_context_get_background_color"); Gdk3Rgba color; DlCast(get_bg_color)( context, gtk_style_context_get_state(context), &color); return GdkRgbaToSkColor(color); } DISABLE_CFI_DLSYM absl::optional GtkStyleContextLookupColor(GtkStyleContext* context, const gchar* color_name) { DCHECK(!GtkCheckVersion(4)); static void* lookup_color = DlSym(GetLibGtk(), "gtk_style_context_lookup_color"); Gdk3Rgba color; if (DlCast(lookup_color)( context, color_name, &color)) { return GdkRgbaToSkColor(color); } return absl::nullopt; } DISABLE_CFI_DLSYM bool GtkImContextFilterKeypress(GtkIMContext* context, GdkEventKey* event) { static void* filter = DlSym(GetLibGtk(), "gtk_im_context_filter_keypress"); if (GtkCheckVersion(4)) { return DlCast(filter)( context, reinterpret_cast(event)); } return DlCast(filter)(context, event); } DISABLE_CFI_DLSYM bool GtkFileChooserSetCurrentFolder(GtkFileChooser* dialog, const base::FilePath& path) { static void* set = DlSym(GetLibGtk(), "gtk_file_chooser_set_current_folder"); if (GtkCheckVersion(4)) { auto file = TakeGObject(g_file_new_for_path(path.value().c_str())); return DlCast(set)(dialog, file, nullptr); } return DlCast(set)(dialog, path.value().c_str()); } DISABLE_CFI_DLSYM void GtkRenderIcon(GtkStyleContext* context, cairo_t* cr, GdkPixbuf* pixbuf, GdkTexture* texture, double x, double y) { static void* render = DlSym(GetLibGtk(), "gtk_render_icon"); if (texture) { DCHECK(GtkCheckVersion(4)); DlCast( render)(context, cr, texture, x, y); } else if (pixbuf) { DCHECK(!GtkCheckVersion(4)); DlCast( render)(context, cr, pixbuf, x, y); } } DISABLE_CFI_DLSYM GtkWidget* GtkToplevelWindowNew() { static void* window_new = DlSym(GetLibGtk(), "gtk_window_new"); if (GtkCheckVersion(4)) return DlCast(window_new)(); return DlCast(window_new)(GTK_WINDOW_TOPLEVEL); } DISABLE_CFI_DLSYM void GtkCssProviderLoadFromData(GtkCssProvider* css_provider, const char* data, gssize length) { static void* load = DlSym(GetLibGtk(), "gtk_css_provider_load_from_data"); if (GtkCheckVersion(4)) { DlCast(load)(css_provider, data, length); } else { DlCast(load)( css_provider, data, length, nullptr); } } ScopedGObject Gtk4FileChooserGetFiles(GtkFileChooser* dialog) { DCHECK(GtkCheckVersion(4)); static void* get = DlSym(GetLibGtk(), "gtk_file_chooser_get_files"); return TakeGObject(DlCast(get)(dialog)); } void GtkStyleContextGet(GtkStyleContext* context, ...) { va_list args; va_start(args, context); gtk_style_context_get_valist(context, gtk_style_context_get_state(context), args); va_end(args); } void GtkStyleContextGetStyle(GtkStyleContext* context, ...) { va_list args; va_start(args, context); gtk_style_context_get_style_valist(context, args); va_end(args); } DISABLE_CFI_DLSYM ScopedGObject Gtk3IconThemeLookupByGiconForScale( GtkIconTheme* theme, GIcon* icon, int size, int scale, GtkIconLookupFlags flags) { DCHECK(!GtkCheckVersion(4)); static void* lookup = DlSym(GetLibGtk(), "gtk_icon_theme_lookup_by_gicon_for_scale"); return TakeGObject( DlCast( lookup)(theme, icon, size, scale, flags)); } DISABLE_CFI_DLSYM ScopedGObject Gtk4IconThemeLookupIcon( GtkIconTheme* theme, const char* icon_name, const char* fallbacks[], int size, int scale, GtkTextDirection direction, GtkIconLookupFlags flags) { static void* lookup = DlSym(GetLibGtk(), "gtk_icon_theme_lookup_icon"); return TakeGObject( DlCast( lookup)(theme, icon_name, fallbacks, size, scale, direction, flags)); } DISABLE_CFI_DLSYM ScopedGObject Gtk4IconThemeLookupByGicon( GtkIconTheme* theme, GIcon* icon, int size, int scale, GtkTextDirection direction, GtkIconLookupFlags flags) { static void* lookup = DlSym(GetLibGtk(), "gtk_icon_theme_lookup_by_gicon"); DCHECK(GtkCheckVersion(4)); return TakeGObject( DlCast(lookup)( theme, icon, size, scale, direction, flags)); } DISABLE_CFI_DLSYM GtkWidget* GtkFileChooserDialogNew(const gchar* title, GtkWindow* parent, GtkFileChooserAction action, const gchar* first_button_text, GtkResponseType first_response, const gchar* second_button_text, GtkResponseType second_response) { static void* create = DlSym(GetLibGtk(), "gtk_file_chooser_dialog_new"); return DlCast(create)( title, parent, action, first_button_text, first_response, second_button_text, second_response, nullptr); } DISABLE_CFI_DLSYM GtkTreeStore* GtkTreeStoreNew(GType type) { static void* create = DlSym(GetLibGtk(), "gtk_tree_store_new"); return DlCast(create)(1, type); } DISABLE_CFI_DLSYM GdkEventType GdkEventGetEventType(GdkEvent* event) { static void* get = DlSym(GetLibGtk(), "gdk_event_get_event_type"); return DlCast(get)(event); } DISABLE_CFI_DLSYM guint32 GdkEventGetTime(GdkEvent* event) { static void* get = DlSym(GetLibGtk(), "gdk_event_get_time"); return DlCast(get)(event); } GdkEventType GdkKeyPress() { return static_cast(GtkCheckVersion(4) ? 4 : 8); } GdkEventType GdkKeyRelease() { return static_cast(GtkCheckVersion(4) ? 5 : 9); } } // namespace gtk