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