1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
|
From 82ff5a0da54aa6da27232b55eb93e5f4b5de22f2 Mon Sep 17 00:00:00 2001
From: Christian Seiler <christian@iwakd.de>
Date: Fri, 17 Jun 2016 17:27:27 +0200
Subject: [PATCH] Properly handle the case when both gtk2 and gtk3 are loaded
In case both gtk2 and gtk3 are loaded, but only gtk2 is used (which can
happen if a piece of software is linked against neither, but has
different plugins, some of which link against gtk2, others against
gtk3, all loaded at the same time, but only one of the variants used),
make sure that gtk3-nocsd detects that, doesn't actually inject any
code and calls all the gtk2 functions, instead of the gtk3 variants.
(Because they are incompatible and mixing calls will lead to crashes.)
Fixes Github issue #18.
---
ChangeLog | 2 ++
gtk3-nocsd.c | 74 +++++++++++++++++++++++++++++++++++++++++++++-------
2 files changed, 66 insertions(+), 10 deletions(-)
diff --git a/ChangeLog b/ChangeLog
index 53cbd33..3e93cba 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -7,6 +7,8 @@ New in version 4 (unreleased)
* Support python-gi again by not caching the result of the version
check if Gtk is not yet loaded. (python-gi loads Glib before it
loads Gtk.)
+ * Handle the case when both Gtk+3 and Gtk+2 are loaded (e.g. via
+ different plugins), but Gtk+2 is used.
New in version 3
----------------
diff --git a/gtk3-nocsd.c b/gtk3-nocsd.c
index 804fbd5..f9c0d70 100644
--- a/gtk3-nocsd.c
+++ b/gtk3-nocsd.c
@@ -22,6 +22,7 @@
#define _GNU_SOURCE
#include <dlfcn.h>
+#include <link.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
@@ -111,6 +112,12 @@ static void * volatile library_handles[NUM_LIBRARIES * 2] = {
static pthread_key_t key_tls;
static pthread_once_t key_tls_once = PTHREAD_ONCE_INIT;
+/* Marking both as volatile here saves the trouble of caring about
+ * memory barriers. */
+static volatile gboolean is_compatible_gtk_version_cached = FALSE;
+static volatile gboolean is_compatible_gtk_version_checked = FALSE;
+static volatile int gtk2_active;
+
typedef struct gtk3_nocsd_tls_data_t {
// When set to true, this override gdk_screen_is_composited() and let it
// return FALSE temporarily. Then, client-side decoration (CSD) cannot be initialized.
@@ -140,6 +147,12 @@ static void *find_orig_function(int try_gtk2, int library_id, const char *symbol
void *handle;
void *symptr;
+ /* Ok, so in case both gtk2 + gtk3 are loaded, but we are using
+ * gtk2, we don't know what RTLD_NEXT is going to choose - so we
+ * must explicitly pick up the gtk2 versions... */
+ if (try_gtk2 && gtk2_active)
+ goto try_gtk2_version;
+
/* This will work in most cases, and is completely thread-safe. */
handle = dlsym(RTLD_NEXT, symbol);
if (handle)
@@ -374,6 +387,42 @@ static void static_g_log(const gchar *log_domain, GLogLevelFlags log_level, cons
va_end (args);
}
+int check_gtk2_callback(struct dl_phdr_info *info, size_t size, void *pointer)
+{
+ ElfW(Half) n;
+
+ if (G_UNLIKELY(strstr(info->dlpi_name, GDK_LIBRARY_SONAME_V2))) {
+ for (n = 0; n < info->dlpi_phnum; n++) {
+ uintptr_t start = (uintptr_t) (info->dlpi_addr + info->dlpi_phdr[n].p_vaddr);
+ uintptr_t end = start + (uintptr_t) info->dlpi_phdr[n].p_memsz;
+ if ((uintptr_t) pointer >= start && (uintptr_t) pointer < end) {
+ gtk2_active = 1;
+ /* The gtk version check could have already been cached
+ * before we were able to determine that gtk2 is in
+ * use, so force this to FALSE. (Regardless of the
+ * _checked value.) */
+ is_compatible_gtk_version_cached = FALSE;
+ return 0;
+ }
+ }
+ }
+ return 0;
+}
+
+static void detect_gtk2(void *pointer)
+{
+ if (gtk2_active)
+ return;
+ /* There is a corner case where a program with plugins loads
+ * multiple plugins, some of which are linked against gtk2, while
+ * others are linked against gtk3. If the gtk2 plugins are used,
+ * this causes problems if we detect gtk3 just on the fact of
+ * whether gtk3 is loaded. Hence we iterate over all loaded
+ * libraries and if the pointer passed to us is within the memory
+ * region of gtk2, we set a global flag. */
+ dl_iterate_phdr(check_gtk2_callback, pointer);
+}
+
static gboolean is_gtk_version_larger_or_equal2(guint major, guint minor, guint micro, int* gtk_loaded) {
static gtk_check_version_t orig_func = NULL;
if(!orig_func)
@@ -414,18 +463,16 @@ static gboolean are_csd_disabled() {
}
static gboolean is_compatible_gtk_version() {
- /* Marking both as volatile here saves the trouble of caring about
- * memory barriers. */
- static volatile gboolean checked = FALSE;
- static volatile gboolean compatible = FALSE;
int gtk_loaded = FALSE;
- if(G_UNLIKELY(!checked)) {
- if (!is_gtk_version_larger_or_equal2(3, 10, 0, >k_loaded)) {
+ if(G_UNLIKELY(!is_compatible_gtk_version_checked)) {
+ if (gtk2_active) {
+ is_compatible_gtk_version_cached = FALSE;
+ } else if (!is_gtk_version_larger_or_equal2(3, 10, 0, >k_loaded)) {
/* CSD was introduced there */
- compatible = FALSE;
+ is_compatible_gtk_version_cached = FALSE;
} else {
- compatible = TRUE;
+ is_compatible_gtk_version_cached = TRUE;
}
/* If in a dynamical program (e.g. using python-gi) Glib is loaded before
* Gtk, then the Gtk version check is executed before Gtk is even loaded,
@@ -433,10 +480,10 @@ static gboolean is_compatible_gtk_version() {
* loaded later. To circumvent this, cache the value only if we know that
* Gtk is loaded. */
if (gtk_loaded)
- checked = TRUE;
+ is_compatible_gtk_version_checked = TRUE;
}
- return compatible;
+ return is_compatible_gtk_version_cached;
}
static void set_has_custom_title(GtkWindow* window, gboolean set) {
@@ -1023,6 +1070,7 @@ GType g_type_register_static_simple (GType parent_type, const gchar *type_name,
if(type_name && G_UNLIKELY(strcmp(type_name, "GtkWindow") == 0)) {
// override GtkWindowClass
orig_gtk_window_class_init = class_init;
+ detect_gtk2((void *) class_init);
if(is_compatible_gtk_version() && are_csd_disabled()) {
class_init = (GClassInitFunc)fake_gtk_window_class_init;
save_type = >k_window_type;
@@ -1035,6 +1083,7 @@ GType g_type_register_static_simple (GType parent_type, const gchar *type_name,
if(type_name && G_UNLIKELY(strcmp(type_name, "GtkDialog") == 0)) {
// override GtkDialogClass
orig_gtk_dialog_class_init = class_init;
+ detect_gtk2((void *) class_init);
if(is_compatible_gtk_version() && are_csd_disabled()) {
class_init = (GClassInitFunc)fake_gtk_dialog_class_init;
save_type = >k_dialog_type;
@@ -1047,6 +1096,7 @@ GType g_type_register_static_simple (GType parent_type, const gchar *type_name,
if(type_name && G_UNLIKELY(strcmp(type_name, "GtkHeaderBar") == 0)) {
// override GtkHeaderBarClass
orig_gtk_header_bar_class_init = class_init;
+ detect_gtk2((void *) class_init);
if(is_compatible_gtk_version() && are_csd_disabled()) {
class_init = (GClassInitFunc)fake_gtk_header_bar_class_init;
save_type = >k_header_bar_type;
@@ -1059,6 +1109,7 @@ GType g_type_register_static_simple (GType parent_type, const gchar *type_name,
if(type_name && G_UNLIKELY(strcmp(type_name, "GtkShortcutsWindow") == 0)) {
// override GtkShortcutsWindowClass
orig_gtk_shortcuts_window_init = instance_init;
+ detect_gtk2((void *) instance_init);
if(is_compatible_gtk_version() && are_csd_disabled()) {
instance_init = (GInstanceInitFunc) fake_gtk_shortcuts_window_init;
goto out;
@@ -1112,6 +1163,9 @@ static void fake_gtk_dialog_buildable_interface_init (GtkBuildableIface *iface,
}
void g_type_add_interface_static (GType instance_type, GType interface_type, const GInterfaceInfo *info) {
+ if (info && info->interface_init)
+ detect_gtk2((void *) info->interface_init);
+
if(is_compatible_gtk_version() && are_csd_disabled() && (instance_type == gtk_window_type || instance_type == gtk_dialog_type)) {
if(interface_type == GTK_TYPE_BUILDABLE) {
// register GtkBuildable interface for GtkWindow/GtkDialog class
|