1 | // Copyright (C) 2016 The Qt Company Ltd. |
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only |
3 | |
4 | #include "qgtk3theme.h" |
5 | #include "qgtk3dialoghelpers.h" |
6 | #include <QVariant> |
7 | #include <QGuiApplication> |
8 | #include <qpa/qwindowsysteminterface.h> |
9 | |
10 | #undef signals |
11 | #include <gtk/gtk.h> |
12 | |
13 | #if QT_CONFIG(xcb_xlib) |
14 | #include <X11/Xlib.h> |
15 | #endif |
16 | |
17 | QT_BEGIN_NAMESPACE |
18 | |
19 | using namespace Qt::StringLiterals; |
20 | |
21 | const char *QGtk3Theme::name = "gtk3" ; |
22 | |
23 | template <typename T> |
24 | static T gtkSetting(const gchar *propertyName) |
25 | { |
26 | GtkSettings *settings = gtk_settings_get_default(); |
27 | T value; |
28 | g_object_get(settings, propertyName, &value, NULL); |
29 | return value; |
30 | } |
31 | |
32 | static QString gtkSetting(const gchar *propertyName) |
33 | { |
34 | gchararray value = gtkSetting<gchararray>(propertyName); |
35 | QString str = QString::fromUtf8(utf8: value); |
36 | g_free(mem: value); |
37 | return str; |
38 | } |
39 | |
40 | void gtkMessageHandler(const gchar *log_domain, |
41 | GLogLevelFlags log_level, |
42 | const gchar *message, |
43 | gpointer unused_data) { |
44 | /* Silence false-positive Gtk warnings (we are using Xlib to set |
45 | * the WM_TRANSIENT_FOR hint). |
46 | */ |
47 | if (g_strcmp0(str1: message, str2: "GtkDialog mapped without a transient parent. " |
48 | "This is discouraged." ) != 0) { |
49 | /* For other messages, call the default handler. */ |
50 | g_log_default_handler(log_domain, log_level, message, unused_data); |
51 | } |
52 | } |
53 | |
54 | QGtk3Theme::QGtk3Theme() |
55 | { |
56 | // Ensure gtk uses the same windowing system, but let it |
57 | // fallback in case GDK_BACKEND environment variable |
58 | // filters the preferred one out |
59 | if (QGuiApplication::platformName().startsWith(s: "wayland"_L1 )) |
60 | gdk_set_allowed_backends(backends: "wayland,x11" ); |
61 | else if (QGuiApplication::platformName() == "xcb"_L1 ) |
62 | gdk_set_allowed_backends(backends: "x11,wayland" ); |
63 | |
64 | #if QT_CONFIG(xcb_xlib) |
65 | // gtk_init will reset the Xlib error handler, and that causes |
66 | // Qt applications to quit on X errors. Therefore, we need to manually restore it. |
67 | int (*oldErrorHandler)(Display *, XErrorEvent *) = XSetErrorHandler(nullptr); |
68 | #endif |
69 | |
70 | gtk_init(argc: nullptr, argv: nullptr); |
71 | |
72 | #if QT_CONFIG(xcb_xlib) |
73 | XSetErrorHandler(oldErrorHandler); |
74 | #endif |
75 | |
76 | /* Initialize some types here so that Gtk+ does not crash when reading |
77 | * the treemodel for GtkFontChooser. |
78 | */ |
79 | g_type_ensure(PANGO_TYPE_FONT_FAMILY); |
80 | g_type_ensure(PANGO_TYPE_FONT_FACE); |
81 | |
82 | /* Use our custom log handler. */ |
83 | g_log_set_handler(log_domain: "Gtk" , log_levels: G_LOG_LEVEL_MESSAGE, log_func: gtkMessageHandler, user_data: nullptr); |
84 | |
85 | #define SETTING_CONNECT(setting) g_signal_connect(settings, "notify::" setting, G_CALLBACK(notifyThemeChanged), nullptr) |
86 | auto notifyThemeChanged = [] { |
87 | QWindowSystemInterface::handleThemeChange(); |
88 | }; |
89 | |
90 | GtkSettings *settings = gtk_settings_get_default(); |
91 | SETTING_CONNECT("gtk-cursor-blink-time" ); |
92 | SETTING_CONNECT("gtk-double-click-distance" ); |
93 | SETTING_CONNECT("gtk-double-click-time" ); |
94 | SETTING_CONNECT("gtk-long-press-time" ); |
95 | SETTING_CONNECT("gtk-entry-password-hint-timeout" ); |
96 | SETTING_CONNECT("gtk-dnd-drag-threshold" ); |
97 | SETTING_CONNECT("gtk-icon-theme-name" ); |
98 | SETTING_CONNECT("gtk-fallback-icon-theme" ); |
99 | SETTING_CONNECT("gtk-font-name" ); |
100 | SETTING_CONNECT("gtk-application-prefer-dark-theme" ); |
101 | SETTING_CONNECT("gtk-theme-name" ); |
102 | SETTING_CONNECT("gtk-cursor-theme-name" ); |
103 | SETTING_CONNECT("gtk-cursor-theme-size" ); |
104 | #undef SETTING_CONNECT |
105 | |
106 | m_storage.reset(p: new QGtk3Storage); |
107 | } |
108 | |
109 | static inline QVariant gtkGetLongPressTime() |
110 | { |
111 | const char *gtk_long_press_time = "gtk-long-press-time" ; |
112 | static bool found = g_object_class_find_property(G_OBJECT_GET_CLASS(gtk_settings_get_default()), property_name: gtk_long_press_time); |
113 | if (!found) |
114 | return QVariant(); |
115 | return QVariant(gtkSetting<guint>(propertyName: gtk_long_press_time)); // Since 3.14, apparently we support >= 3.6 |
116 | } |
117 | |
118 | QVariant QGtk3Theme::themeHint(QPlatformTheme::ThemeHint hint) const |
119 | { |
120 | switch (hint) { |
121 | case QPlatformTheme::CursorFlashTime: |
122 | return QVariant(gtkSetting<gint>(propertyName: "gtk-cursor-blink-time" )); |
123 | case QPlatformTheme::MouseDoubleClickDistance: |
124 | return QVariant(gtkSetting<gint>(propertyName: "gtk-double-click-distance" )); |
125 | case QPlatformTheme::MouseDoubleClickInterval: |
126 | return QVariant(gtkSetting<gint>(propertyName: "gtk-double-click-time" )); |
127 | case QPlatformTheme::MousePressAndHoldInterval: { |
128 | QVariant v = gtkGetLongPressTime(); |
129 | if (!v.isValid()) |
130 | v = QGnomeTheme::themeHint(hint); |
131 | return v; |
132 | } |
133 | case QPlatformTheme::PasswordMaskDelay: |
134 | return QVariant(gtkSetting<guint>(propertyName: "gtk-entry-password-hint-timeout" )); |
135 | case QPlatformTheme::StartDragDistance: |
136 | return QVariant(gtkSetting<gint>(propertyName: "gtk-dnd-drag-threshold" )); |
137 | case QPlatformTheme::SystemIconThemeName: |
138 | return QVariant(gtkSetting(propertyName: "gtk-icon-theme-name" )); |
139 | case QPlatformTheme::SystemIconFallbackThemeName: |
140 | return QVariant(gtkSetting(propertyName: "gtk-fallback-icon-theme" )); |
141 | case QPlatformTheme::MouseCursorTheme: |
142 | return QVariant(gtkSetting(propertyName: "gtk-cursor-theme-name" )); |
143 | case QPlatformTheme::MouseCursorSize: { |
144 | int s = gtkSetting<gint>(propertyName: "gtk-cursor-theme-size" ); |
145 | if (s > 0) |
146 | return QVariant(QSize(s, s)); |
147 | return QGnomeTheme::themeHint(hint); |
148 | } |
149 | default: |
150 | return QGnomeTheme::themeHint(hint); |
151 | } |
152 | } |
153 | |
154 | QString QGtk3Theme::gtkFontName() const |
155 | { |
156 | QString cfgFontName = gtkSetting(propertyName: "gtk-font-name" ); |
157 | if (!cfgFontName.isEmpty()) |
158 | return cfgFontName; |
159 | return QGnomeTheme::gtkFontName(); |
160 | } |
161 | |
162 | Qt::ColorScheme QGtk3Theme::colorScheme() const |
163 | { |
164 | Q_ASSERT(m_storage); |
165 | return m_storage->colorScheme(); |
166 | } |
167 | |
168 | bool QGtk3Theme::usePlatformNativeDialog(DialogType type) const |
169 | { |
170 | switch (type) { |
171 | case ColorDialog: |
172 | return true; |
173 | case FileDialog: |
174 | return useNativeFileDialog(); |
175 | case FontDialog: |
176 | return true; |
177 | default: |
178 | return false; |
179 | } |
180 | } |
181 | |
182 | QPlatformDialogHelper *QGtk3Theme::createPlatformDialogHelper(DialogType type) const |
183 | { |
184 | switch (type) { |
185 | case ColorDialog: |
186 | return new QGtk3ColorDialogHelper; |
187 | case FileDialog: |
188 | if (!useNativeFileDialog()) |
189 | return nullptr; |
190 | return new QGtk3FileDialogHelper; |
191 | case FontDialog: |
192 | return new QGtk3FontDialogHelper; |
193 | default: |
194 | return nullptr; |
195 | } |
196 | } |
197 | |
198 | bool QGtk3Theme::useNativeFileDialog() |
199 | { |
200 | /* Require GTK3 >= 3.15.5 to avoid running into this bug: |
201 | * https://bugzilla.gnome.org/show_bug.cgi?id=725164 |
202 | * |
203 | * While this bug only occurs when using widget-based file dialogs |
204 | * (native GTK3 dialogs are fine) we have to disable platform file |
205 | * dialogs entirely since we can't avoid creation of a platform |
206 | * dialog helper. |
207 | */ |
208 | return gtk_check_version(required_major: 3, required_minor: 15, required_micro: 5) == nullptr; |
209 | } |
210 | |
211 | const QPalette *QGtk3Theme::palette(Palette type) const |
212 | { |
213 | Q_ASSERT(m_storage); |
214 | return m_storage->palette(type); |
215 | } |
216 | |
217 | QPixmap QGtk3Theme::standardPixmap(StandardPixmap sp, const QSizeF &size) const |
218 | { |
219 | Q_ASSERT(m_storage); |
220 | return m_storage->standardPixmap(standardPixmap: sp, size); |
221 | } |
222 | |
223 | const QFont *QGtk3Theme::font(Font type) const |
224 | { |
225 | Q_ASSERT(m_storage); |
226 | return m_storage->font(type); |
227 | } |
228 | |
229 | QIcon QGtk3Theme::fileIcon(const QFileInfo &fileInfo, |
230 | QPlatformTheme::IconOptions iconOptions) const |
231 | { |
232 | Q_UNUSED(iconOptions); |
233 | Q_ASSERT(m_storage); |
234 | return m_storage->fileIcon(fileInfo); |
235 | } |
236 | |
237 | QT_END_NAMESPACE |
238 | |