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
18QT_BEGIN_NAMESPACE
19
20using namespace Qt::StringLiterals;
21
22const char *QGtk3Theme::name = "gtk3";
23
24template <typename T>
25static 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
33static 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
41void 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
55QGtk3Theme::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
110static 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
119QVariant 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
155QString 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
163Qt::ColorScheme QGtk3Theme::colorScheme() const
164{
165 Q_ASSERT(m_storage);
166 return m_storage->colorScheme();
167}
168
169bool 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
183QPlatformDialogHelper *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
199QPlatformMenu* QGtk3Theme::createPlatformMenu() const
200{
201 return new QGtk3Menu;
202}
203
204QPlatformMenuItem* QGtk3Theme::createPlatformMenuItem() const
205{
206 return new QGtk3MenuItem;
207}
208
209bool 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
222const QPalette *QGtk3Theme::palette(Palette type) const
223{
224 Q_ASSERT(m_storage);
225 return m_storage->palette(type);
226}
227
228QPixmap QGtk3Theme::standardPixmap(StandardPixmap sp, const QSizeF &size) const
229{
230 Q_ASSERT(m_storage);
231 return m_storage->standardPixmap(standardPixmap: sp, size);
232}
233
234const QFont *QGtk3Theme::font(Font type) const
235{
236 Q_ASSERT(m_storage);
237 return m_storage->font(type);
238}
239
240QIcon 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
248QT_END_NAMESPACE
249

source code of qtbase/src/plugins/platformthemes/gtk3/qgtk3theme.cpp