blob: d05e866401f075669ee2424628ec10e5b892368f [file] [log] [blame]
// 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 <dlfcn.h>
#include "base/check.h"
#include "base/compiler_specific.h"
#include "base/debug/leak_annotations.h"
#include "base/no_destructor.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_ICALL.
namespace {
struct Gdk3Rgba {
gdouble r;
gdouble g;
gdouble b;
gdouble a;
};
struct Gdk4Rgba {
float r;
float g;
float b;
float a;
};
template <typename T>
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 <typename T>
auto DlCast(void* symbol) {
return reinterpret_cast<T*>(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() {
static void* libgtk4 = DlOpen("libgtk-4.so.1");
return libgtk4;
}
void* GetLibGtk() {
if (GtkCheckVersion(4))
return GetLibGtk4();
return GetLibGtk3();
}
bool LoadGtkImpl(int gtk_version) {
// Prefer GTK3 for now as the GTK4 ecosystem is still immature.
if (GetLibGtk3(false)) {
ui_gtk::InitializeGdk_pixbuf(GetLibGdkPixbuf());
ui_gtk::InitializeGdk(GetLibGdk3());
ui_gtk::InitializeGtk(GetLibGtk3());
} else {
// 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;
}
gfx::Insets InsetsFromGtkBorder(const GtkBorder& border) {
return gfx::Insets(border.top, border.left, border.bottom, border.right);
}
} // namespace
bool LoadGtk() {
static bool loaded = LoadGtkImpl(GTK_MAJOR_VERSION);
return loaded;
}
const base::Version& GtkVersion() {
static base::NoDestructor<base::Version> gtk_version(
std::vector<uint32_t>{gtk_get_major_version(), gtk_get_minor_version(),
gtk_get_micro_version()});
return *gtk_version;
}
bool GtkCheckVersion(int major, int minor, int micro) {
return GtkVersion() >= base::Version({major, minor, micro});
}
DISABLE_CFI_ICALL
void GtkInit(const std::vector<std::string>& args) {
static void* gtk_init = DlSym(GetLibGtk(), "gtk_init");
if (GtkCheckVersion(4)) {
DlCast<void()>(gtk_init)();
} else {
// gtk_init() modifies argv, so make a copy first.
size_t args_chars = 0;
for (const auto& arg : args)
args_chars += arg.size() + 1;
std::vector<char> args_copy(args_chars);
std::vector<char*> argv;
char* dst = args_copy.data();
for (const auto& arg : args) {
argv.push_back(strcpy(dst, arg.c_str()));
dst += arg.size() + 1;
}
int gtk_argc = argv.size();
char** gtk_argv = argv.data();
{
// http://crbug.com/423873
ANNOTATE_SCOPED_MEMORY_LEAK;
DlCast<void(int*, char***)>(gtk_init)(&gtk_argc, &gtk_argv);
}
}
}
DISABLE_CFI_ICALL
gfx::Insets GtkStyleContextGetPadding(GtkStyleContext* context) {
static void* get_padding =
DlSym(GetLibGtk(), "gtk_style_context_get_padding");
GtkBorder padding;
if (GtkCheckVersion(4)) {
DlCast<void(GtkStyleContext*, GtkBorder*)>(get_padding)(context, &padding);
} else {
DlCast<void(GtkStyleContext*, GtkStateFlags, GtkBorder*)>(get_padding)(
context, gtk_style_context_get_state(context), &padding);
}
return InsetsFromGtkBorder(padding);
}
DISABLE_CFI_ICALL
gfx::Insets GtkStyleContextGetBorder(GtkStyleContext* context) {
static void* get_border = DlSym(GetLibGtk(), "gtk_style_context_get_border");
GtkBorder border;
if (GtkCheckVersion(4)) {
DlCast<void(GtkStyleContext*, GtkBorder*)>(get_border)(context, &border);
} else {
DlCast<void(GtkStyleContext*, GtkStateFlags, GtkBorder*)>(get_border)(
context, gtk_style_context_get_state(context), &border);
}
return InsetsFromGtkBorder(border);
}
DISABLE_CFI_ICALL
gfx::Insets GtkStyleContextGetMargin(GtkStyleContext* context) {
static void* get_margin = DlSym(GetLibGtk(), "gtk_style_context_get_margin");
GtkBorder margin;
if (GtkCheckVersion(4)) {
DlCast<void(GtkStyleContext*, GtkBorder*)>(get_margin)(context, &margin);
} else {
DlCast<void(GtkStyleContext*, GtkStateFlags, GtkBorder*)>(get_margin)(
context, gtk_style_context_get_state(context), &margin);
}
return InsetsFromGtkBorder(margin);
}
DISABLE_CFI_ICALL
SkColor GtkStyleContextGetColor(GtkStyleContext* context) {
static void* get_color = DlSym(GetLibGtk(), "gtk_style_context_get_color");
if (GtkCheckVersion(4)) {
Gdk4Rgba color;
DlCast<void(GtkStyleContext*, Gdk4Rgba*)>(get_color)(context, &color);
return GdkRgbaToSkColor(color);
}
Gdk3Rgba color;
DlCast<void(GtkStyleContext*, GtkStateFlags, Gdk3Rgba*)>(get_color)(
context, gtk_style_context_get_state(context), &color);
return GdkRgbaToSkColor(color);
}
DISABLE_CFI_ICALL
SkColor GtkStyleContextGetBackgroundColor(GtkStyleContext* context) {
DCHECK(!GtkCheckVersion(4));
static void* get_bg_color =
DlSym(GetLibGtk(), "gtk_style_context_get_background_color");
Gdk3Rgba color;
DlCast<void(GtkStyleContext*, GtkStateFlags, Gdk3Rgba*)>(get_bg_color)(
context, gtk_style_context_get_state(context), &color);
return GdkRgbaToSkColor(color);
}
DISABLE_CFI_ICALL
SkColor 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<gboolean(GtkStyleContext*, const gchar*, Gdk3Rgba*)>(lookup_color)(
context, color_name, &color)) {
return GdkRgbaToSkColor(color);
}
return gfx::kPlaceholderColor;
}
DISABLE_CFI_ICALL
bool GtkImContextFilterKeypress(GtkIMContext* context, GdkEventKey* event) {
static void* filter = DlSym(GetLibGtk(), "gtk_im_context_filter_keypress");
if (GtkCheckVersion(4)) {
return DlCast<bool(GtkIMContext*, GdkEvent*)>(filter)(
context, reinterpret_cast<GdkEvent*>(event));
}
return DlCast<bool(GtkIMContext*, GdkEventKey*)>(filter)(context, event);
}
DISABLE_CFI_ICALL
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<bool(GtkFileChooser*, GFile*, GError**)>(set)(dialog, file,
nullptr);
}
return DlCast<bool(GtkFileChooser*, const gchar*)>(set)(dialog,
path.value().c_str());
}
DISABLE_CFI_ICALL
void GtkRenderIcon(GtkStyleContext* context,
cairo_t* cr,
GdkPixbuf* pixbuf,
GdkTexture* texture,
double x,
double y) {
static void* render = DlSym(GetLibGtk(), "gtk_render_icon");
if (GtkCheckVersion(4)) {
DCHECK(texture);
DlCast<void(GtkStyleContext*, cairo_t*, GdkTexture*, double, double)>(
render)(context, cr, texture, x, y);
} else {
DCHECK(pixbuf);
DlCast<void(GtkStyleContext*, cairo_t*, GdkPixbuf*, double, double)>(
render)(context, cr, pixbuf, x, y);
}
}
DISABLE_CFI_ICALL
GtkWidget* GtkToplevelWindowNew() {
static void* window_new = DlSym(GetLibGtk(), "gtk_window_new");
if (GtkCheckVersion(4))
return DlCast<GtkWidget*()>(window_new)();
return DlCast<GtkWidget*(GtkWindowType)>(window_new)(GTK_WINDOW_TOPLEVEL);
}
DISABLE_CFI_ICALL
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<void(GtkCssProvider*, const char*, gssize)>(load)(css_provider, data,
length);
} else {
DlCast<gboolean(GtkCssProvider*, const char*, gssize, GError**)>(load)(
css_provider, data, length, nullptr);
}
}
ScopedGObject<GListModel> Gtk4FileChooserGetFiles(GtkFileChooser* dialog) {
DCHECK(GtkCheckVersion(4));
static void* get = DlSym(GetLibGtk(), "gtk_file_chooser_get_files");
return TakeGObject(DlCast<GListModel*(GtkFileChooser*)>(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_ICALL
ScopedGObject<GtkIconInfo> Gtk3IconThemeLookupByGicon(
GtkIconTheme* theme,
GIcon* icon,
int size,
GtkIconLookupFlags flags) {
DCHECK(!GtkCheckVersion(4));
static void* lookup = DlSym(GetLibGtk(), "gtk_icon_theme_lookup_by_gicon");
return TakeGObject(
DlCast<GtkIconInfo*(GtkIconTheme*, GIcon*, int, GtkIconLookupFlags)>(
lookup)(theme, icon, size, flags));
}
DISABLE_CFI_ICALL
ScopedGObject<GtkIconPaintable> 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<GtkIconPaintable*(GtkIconTheme*, const char*, const char*[], int,
int, GtkTextDirection, GtkIconLookupFlags)>(
lookup)(theme, icon_name, fallbacks, size, scale, direction, flags));
}
DISABLE_CFI_ICALL
ScopedGObject<GtkIconPaintable> 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<GtkIconPaintable*(GtkIconTheme*, GIcon*, int, int,
GtkTextDirection, GtkIconLookupFlags)>(lookup)(
theme, icon, size, scale, direction, flags));
}
DISABLE_CFI_ICALL
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<GtkWidget*(const gchar*, GtkWindow*, GtkFileChooserAction,
const gchar*, ...)>(create)(
title, parent, action, first_button_text, first_response,
second_button_text, second_response, nullptr);
}
DISABLE_CFI_ICALL
GtkTreeStore* GtkTreeStoreNew(GType type) {
static void* create = DlSym(GetLibGtk(), "gtk_tree_store_new");
return DlCast<GtkTreeStore*(gint, ...)>(create)(1, type);
}
DISABLE_CFI_ICALL
GdkEventType GdkEventGetEventType(GdkEvent* event) {
static void* get = DlSym(GetLibGtk(), "gdk_event_get_event_type");
return DlCast<GdkEventType(GdkEvent*)>(get)(event);
}
DISABLE_CFI_ICALL
guint32 GdkEventGetTime(GdkEvent* event) {
static void* get = DlSym(GetLibGtk(), "gdk_event_get_time");
return DlCast<guint32(GdkEvent*)>(get)(event);
}
GdkEventType GdkKeyPress() {
return static_cast<GdkEventType>(GtkCheckVersion(4) ? 4 : 8);
}
GdkEventType GdkKeyRelease() {
return static_cast<GdkEventType>(GtkCheckVersion(4) ? 5 : 9);
}
} // namespace gtk