gtk_compat.cc 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418
  1. // Copyright 2021 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4. #include "ui/gtk/gtk_compat.h"
  5. #include <dlfcn.h>
  6. #include "base/check.h"
  7. #include "base/command_line.h"
  8. #include "base/compiler_specific.h"
  9. #include "base/debug/leak_annotations.h"
  10. #include "base/no_destructor.h"
  11. #include "base/strings/string_number_conversions.h"
  12. #include "ui/gfx/color_palette.h"
  13. #include "ui/gtk/gtk_stubs.h"
  14. namespace gtk {
  15. // IMPORTANT: All functions in this file that call dlsym()'ed
  16. // functions should be annotated with DISABLE_CFI_DLSYM.
  17. namespace {
  18. const char kGtkVersionFlag[] = "gtk-version";
  19. struct Gdk3Rgba {
  20. gdouble r;
  21. gdouble g;
  22. gdouble b;
  23. gdouble a;
  24. };
  25. struct Gdk4Rgba {
  26. float r;
  27. float g;
  28. float b;
  29. float a;
  30. };
  31. template <typename T>
  32. SkColor GdkRgbaToSkColor(const T& color) {
  33. return SkColorSetARGB(color.a * 255, color.r * 255, color.g * 255,
  34. color.b * 255);
  35. }
  36. void* DlOpen(const char* library_name, bool check = true) {
  37. void* library = dlopen(library_name, RTLD_LAZY | RTLD_GLOBAL);
  38. CHECK(!check || library);
  39. return library;
  40. }
  41. void* DlSym(void* library, const char* name) {
  42. void* symbol = dlsym(library, name);
  43. CHECK(symbol);
  44. return symbol;
  45. }
  46. template <typename T>
  47. auto DlCast(void* symbol) {
  48. return reinterpret_cast<T*>(symbol);
  49. }
  50. void* GetLibGio() {
  51. static void* libgio = DlOpen("libgio-2.0.so.0");
  52. return libgio;
  53. }
  54. void* GetLibGdkPixbuf() {
  55. static void* libgdk_pixbuf = DlOpen("libgdk_pixbuf-2.0.so.0");
  56. return libgdk_pixbuf;
  57. }
  58. void* GetLibGdk3() {
  59. static void* libgdk3 = DlOpen("libgdk-3.so.0");
  60. return libgdk3;
  61. }
  62. void* GetLibGtk3(bool check = true) {
  63. static void* libgtk3 = DlOpen("libgtk-3.so.0", check);
  64. return libgtk3;
  65. }
  66. void* GetLibGtk4(bool check = true) {
  67. static void* libgtk4 = DlOpen("libgtk-4.so.1", check);
  68. return libgtk4;
  69. }
  70. void* GetLibGtk() {
  71. if (GtkCheckVersion(4))
  72. return GetLibGtk4();
  73. return GetLibGtk3();
  74. }
  75. bool LoadGtk3() {
  76. if (!GetLibGtk3(false))
  77. return false;
  78. ui_gtk::InitializeGdk_pixbuf(GetLibGdkPixbuf());
  79. ui_gtk::InitializeGdk(GetLibGdk3());
  80. ui_gtk::InitializeGtk(GetLibGtk3());
  81. return true;
  82. }
  83. bool LoadGtk4() {
  84. if (!GetLibGtk4(false))
  85. return false;
  86. // In GTK4 mode, we require some newer gio symbols that aren't available
  87. // in Ubuntu Xenial or Debian Stretch. Fortunately, GTK4 itself depends
  88. // on a newer version of glib (which provides gio), so if we're using
  89. // GTK4, we can safely assume the system has the required gio symbols.
  90. ui_gtk::InitializeGio(GetLibGio());
  91. // In GTK4, libgtk provides all gdk_*, gsk_*, and gtk_* symbols.
  92. ui_gtk::InitializeGdk(GetLibGtk4());
  93. ui_gtk::InitializeGsk(GetLibGtk4());
  94. ui_gtk::InitializeGtk(GetLibGtk4());
  95. return true;
  96. }
  97. bool LoadGtkImpl() {
  98. auto* cmd = base::CommandLine::ForCurrentProcess();
  99. unsigned int gtk_version;
  100. if (!base::StringToUint(cmd->GetSwitchValueASCII(kGtkVersionFlag),
  101. &gtk_version)) {
  102. gtk_version = 0;
  103. }
  104. // Prefer GTK3 for now as the GTK4 ecosystem is still immature.
  105. return gtk_version == 4 ? LoadGtk4() || LoadGtk3() : LoadGtk3() || LoadGtk4();
  106. }
  107. gfx::Insets InsetsFromGtkBorder(const GtkBorder& border) {
  108. return gfx::Insets::TLBR(border.top, border.left, border.bottom,
  109. border.right);
  110. }
  111. } // namespace
  112. bool LoadGtk() {
  113. static bool loaded = LoadGtkImpl();
  114. return loaded;
  115. }
  116. const base::Version& GtkVersion() {
  117. static base::NoDestructor<base::Version> gtk_version(
  118. std::vector<uint32_t>{gtk_get_major_version(), gtk_get_minor_version(),
  119. gtk_get_micro_version()});
  120. return *gtk_version;
  121. }
  122. bool GtkCheckVersion(uint32_t major, uint32_t minor, uint32_t micro) {
  123. return GtkVersion() >= base::Version({major, minor, micro});
  124. }
  125. DISABLE_CFI_DLSYM
  126. bool GtkInitCheck(int* argc, char** argv) {
  127. static void* gtk_init_check = DlSym(GetLibGtk(), "gtk_init_check");
  128. if (GtkCheckVersion(4))
  129. return DlCast<gboolean()>(gtk_init_check)();
  130. {
  131. // http://crbug.com/423873
  132. ANNOTATE_SCOPED_MEMORY_LEAK;
  133. return DlCast<gboolean(int*, char***)>(gtk_init_check)(argc, &argv);
  134. }
  135. }
  136. DISABLE_CFI_DLSYM
  137. gfx::Insets GtkStyleContextGetPadding(GtkStyleContext* context) {
  138. static void* get_padding =
  139. DlSym(GetLibGtk(), "gtk_style_context_get_padding");
  140. GtkBorder padding;
  141. if (GtkCheckVersion(4)) {
  142. DlCast<void(GtkStyleContext*, GtkBorder*)>(get_padding)(context, &padding);
  143. } else {
  144. DlCast<void(GtkStyleContext*, GtkStateFlags, GtkBorder*)>(get_padding)(
  145. context, gtk_style_context_get_state(context), &padding);
  146. }
  147. return InsetsFromGtkBorder(padding);
  148. }
  149. DISABLE_CFI_DLSYM
  150. gfx::Insets GtkStyleContextGetBorder(GtkStyleContext* context) {
  151. static void* get_border = DlSym(GetLibGtk(), "gtk_style_context_get_border");
  152. GtkBorder border;
  153. if (GtkCheckVersion(4)) {
  154. DlCast<void(GtkStyleContext*, GtkBorder*)>(get_border)(context, &border);
  155. } else {
  156. DlCast<void(GtkStyleContext*, GtkStateFlags, GtkBorder*)>(get_border)(
  157. context, gtk_style_context_get_state(context), &border);
  158. }
  159. return InsetsFromGtkBorder(border);
  160. }
  161. DISABLE_CFI_DLSYM
  162. gfx::Insets GtkStyleContextGetMargin(GtkStyleContext* context) {
  163. static void* get_margin = DlSym(GetLibGtk(), "gtk_style_context_get_margin");
  164. GtkBorder margin;
  165. if (GtkCheckVersion(4)) {
  166. DlCast<void(GtkStyleContext*, GtkBorder*)>(get_margin)(context, &margin);
  167. } else {
  168. DlCast<void(GtkStyleContext*, GtkStateFlags, GtkBorder*)>(get_margin)(
  169. context, gtk_style_context_get_state(context), &margin);
  170. }
  171. return InsetsFromGtkBorder(margin);
  172. }
  173. DISABLE_CFI_DLSYM
  174. SkColor GtkStyleContextGetColor(GtkStyleContext* context) {
  175. static void* get_color = DlSym(GetLibGtk(), "gtk_style_context_get_color");
  176. if (GtkCheckVersion(4)) {
  177. Gdk4Rgba color;
  178. DlCast<void(GtkStyleContext*, Gdk4Rgba*)>(get_color)(context, &color);
  179. return GdkRgbaToSkColor(color);
  180. }
  181. Gdk3Rgba color;
  182. DlCast<void(GtkStyleContext*, GtkStateFlags, Gdk3Rgba*)>(get_color)(
  183. context, gtk_style_context_get_state(context), &color);
  184. return GdkRgbaToSkColor(color);
  185. }
  186. DISABLE_CFI_DLSYM
  187. SkColor GtkStyleContextGetBackgroundColor(GtkStyleContext* context) {
  188. DCHECK(!GtkCheckVersion(4));
  189. static void* get_bg_color =
  190. DlSym(GetLibGtk(), "gtk_style_context_get_background_color");
  191. Gdk3Rgba color;
  192. DlCast<void(GtkStyleContext*, GtkStateFlags, Gdk3Rgba*)>(get_bg_color)(
  193. context, gtk_style_context_get_state(context), &color);
  194. return GdkRgbaToSkColor(color);
  195. }
  196. DISABLE_CFI_DLSYM
  197. absl::optional<SkColor> GtkStyleContextLookupColor(GtkStyleContext* context,
  198. const gchar* color_name) {
  199. DCHECK(!GtkCheckVersion(4));
  200. static void* lookup_color =
  201. DlSym(GetLibGtk(), "gtk_style_context_lookup_color");
  202. Gdk3Rgba color;
  203. if (DlCast<gboolean(GtkStyleContext*, const gchar*, Gdk3Rgba*)>(lookup_color)(
  204. context, color_name, &color)) {
  205. return GdkRgbaToSkColor(color);
  206. }
  207. return absl::nullopt;
  208. }
  209. DISABLE_CFI_DLSYM
  210. bool GtkImContextFilterKeypress(GtkIMContext* context, GdkEventKey* event) {
  211. static void* filter = DlSym(GetLibGtk(), "gtk_im_context_filter_keypress");
  212. if (GtkCheckVersion(4)) {
  213. return DlCast<bool(GtkIMContext*, GdkEvent*)>(filter)(
  214. context, reinterpret_cast<GdkEvent*>(event));
  215. }
  216. return DlCast<bool(GtkIMContext*, GdkEventKey*)>(filter)(context, event);
  217. }
  218. DISABLE_CFI_DLSYM
  219. bool GtkFileChooserSetCurrentFolder(GtkFileChooser* dialog,
  220. const base::FilePath& path) {
  221. static void* set = DlSym(GetLibGtk(), "gtk_file_chooser_set_current_folder");
  222. if (GtkCheckVersion(4)) {
  223. auto file = TakeGObject(g_file_new_for_path(path.value().c_str()));
  224. return DlCast<bool(GtkFileChooser*, GFile*, GError**)>(set)(dialog, file,
  225. nullptr);
  226. }
  227. return DlCast<bool(GtkFileChooser*, const gchar*)>(set)(dialog,
  228. path.value().c_str());
  229. }
  230. DISABLE_CFI_DLSYM
  231. void GtkRenderIcon(GtkStyleContext* context,
  232. cairo_t* cr,
  233. GdkPixbuf* pixbuf,
  234. GdkTexture* texture,
  235. double x,
  236. double y) {
  237. static void* render = DlSym(GetLibGtk(), "gtk_render_icon");
  238. if (texture) {
  239. DCHECK(GtkCheckVersion(4));
  240. DlCast<void(GtkStyleContext*, cairo_t*, GdkTexture*, double, double)>(
  241. render)(context, cr, texture, x, y);
  242. } else if (pixbuf) {
  243. DCHECK(!GtkCheckVersion(4));
  244. DlCast<void(GtkStyleContext*, cairo_t*, GdkPixbuf*, double, double)>(
  245. render)(context, cr, pixbuf, x, y);
  246. }
  247. }
  248. DISABLE_CFI_DLSYM
  249. GtkWidget* GtkToplevelWindowNew() {
  250. static void* window_new = DlSym(GetLibGtk(), "gtk_window_new");
  251. if (GtkCheckVersion(4))
  252. return DlCast<GtkWidget*()>(window_new)();
  253. return DlCast<GtkWidget*(GtkWindowType)>(window_new)(GTK_WINDOW_TOPLEVEL);
  254. }
  255. DISABLE_CFI_DLSYM
  256. void GtkCssProviderLoadFromData(GtkCssProvider* css_provider,
  257. const char* data,
  258. gssize length) {
  259. static void* load = DlSym(GetLibGtk(), "gtk_css_provider_load_from_data");
  260. if (GtkCheckVersion(4)) {
  261. DlCast<void(GtkCssProvider*, const char*, gssize)>(load)(css_provider, data,
  262. length);
  263. } else {
  264. DlCast<gboolean(GtkCssProvider*, const char*, gssize, GError**)>(load)(
  265. css_provider, data, length, nullptr);
  266. }
  267. }
  268. ScopedGObject<GListModel> Gtk4FileChooserGetFiles(GtkFileChooser* dialog) {
  269. DCHECK(GtkCheckVersion(4));
  270. static void* get = DlSym(GetLibGtk(), "gtk_file_chooser_get_files");
  271. return TakeGObject(DlCast<GListModel*(GtkFileChooser*)>(get)(dialog));
  272. }
  273. void GtkStyleContextGet(GtkStyleContext* context, ...) {
  274. va_list args;
  275. va_start(args, context);
  276. gtk_style_context_get_valist(context, gtk_style_context_get_state(context),
  277. args);
  278. va_end(args);
  279. }
  280. void GtkStyleContextGetStyle(GtkStyleContext* context, ...) {
  281. va_list args;
  282. va_start(args, context);
  283. gtk_style_context_get_style_valist(context, args);
  284. va_end(args);
  285. }
  286. DISABLE_CFI_DLSYM
  287. ScopedGObject<GtkIconInfo> Gtk3IconThemeLookupByGiconForScale(
  288. GtkIconTheme* theme,
  289. GIcon* icon,
  290. int size,
  291. int scale,
  292. GtkIconLookupFlags flags) {
  293. DCHECK(!GtkCheckVersion(4));
  294. static void* lookup =
  295. DlSym(GetLibGtk(), "gtk_icon_theme_lookup_by_gicon_for_scale");
  296. return TakeGObject(
  297. DlCast<GtkIconInfo*(GtkIconTheme*, GIcon*, int, int, GtkIconLookupFlags)>(
  298. lookup)(theme, icon, size, scale, flags));
  299. }
  300. DISABLE_CFI_DLSYM
  301. ScopedGObject<GtkIconPaintable> Gtk4IconThemeLookupIcon(
  302. GtkIconTheme* theme,
  303. const char* icon_name,
  304. const char* fallbacks[],
  305. int size,
  306. int scale,
  307. GtkTextDirection direction,
  308. GtkIconLookupFlags flags) {
  309. static void* lookup = DlSym(GetLibGtk(), "gtk_icon_theme_lookup_icon");
  310. return TakeGObject(
  311. DlCast<GtkIconPaintable*(GtkIconTheme*, const char*, const char*[], int,
  312. int, GtkTextDirection, GtkIconLookupFlags)>(
  313. lookup)(theme, icon_name, fallbacks, size, scale, direction, flags));
  314. }
  315. DISABLE_CFI_DLSYM
  316. ScopedGObject<GtkIconPaintable> Gtk4IconThemeLookupByGicon(
  317. GtkIconTheme* theme,
  318. GIcon* icon,
  319. int size,
  320. int scale,
  321. GtkTextDirection direction,
  322. GtkIconLookupFlags flags) {
  323. static void* lookup = DlSym(GetLibGtk(), "gtk_icon_theme_lookup_by_gicon");
  324. DCHECK(GtkCheckVersion(4));
  325. return TakeGObject(
  326. DlCast<GtkIconPaintable*(GtkIconTheme*, GIcon*, int, int,
  327. GtkTextDirection, GtkIconLookupFlags)>(lookup)(
  328. theme, icon, size, scale, direction, flags));
  329. }
  330. DISABLE_CFI_DLSYM
  331. GtkWidget* GtkFileChooserDialogNew(const gchar* title,
  332. GtkWindow* parent,
  333. GtkFileChooserAction action,
  334. const gchar* first_button_text,
  335. GtkResponseType first_response,
  336. const gchar* second_button_text,
  337. GtkResponseType second_response) {
  338. static void* create = DlSym(GetLibGtk(), "gtk_file_chooser_dialog_new");
  339. return DlCast<GtkWidget*(const gchar*, GtkWindow*, GtkFileChooserAction,
  340. const gchar*, ...)>(create)(
  341. title, parent, action, first_button_text, first_response,
  342. second_button_text, second_response, nullptr);
  343. }
  344. DISABLE_CFI_DLSYM
  345. GtkTreeStore* GtkTreeStoreNew(GType type) {
  346. static void* create = DlSym(GetLibGtk(), "gtk_tree_store_new");
  347. return DlCast<GtkTreeStore*(gint, ...)>(create)(1, type);
  348. }
  349. DISABLE_CFI_DLSYM
  350. GdkEventType GdkEventGetEventType(GdkEvent* event) {
  351. static void* get = DlSym(GetLibGtk(), "gdk_event_get_event_type");
  352. return DlCast<GdkEventType(GdkEvent*)>(get)(event);
  353. }
  354. DISABLE_CFI_DLSYM
  355. guint32 GdkEventGetTime(GdkEvent* event) {
  356. static void* get = DlSym(GetLibGtk(), "gdk_event_get_time");
  357. return DlCast<guint32(GdkEvent*)>(get)(event);
  358. }
  359. GdkEventType GdkKeyPress() {
  360. return static_cast<GdkEventType>(GtkCheckVersion(4) ? 4 : 8);
  361. }
  362. GdkEventType GdkKeyRelease() {
  363. return static_cast<GdkEventType>(GtkCheckVersion(4) ? 5 : 9);
  364. }
  365. } // namespace gtk