1// Copyright (C) 2022 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 "qgenericunixtheme_p.h"
5#include "qgnometheme_p.h"
6
7#include <QPalette>
8#include <QFont>
9#include <QGuiApplication>
10#include <QDir>
11#include <QFileInfo>
12#include <QFile>
13#include <QDebug>
14#include <QHash>
15#include <QLoggingCategory>
16#include <QVariant>
17#include <QStandardPaths>
18#include <QStringList>
19#if QT_CONFIG(mimetype)
20#include <QMimeDatabase>
21#endif
22#if QT_CONFIG(settings)
23#include <QSettings>
24#if QT_CONFIG(dbus)
25#include "qkdetheme_p.h"
26#endif
27#endif
28
29#include <qpa/qplatformfontdatabase.h> // lcQpaFonts
30#include <qpa/qplatformintegration.h>
31#include <qpa/qplatformservices.h>
32#include <qpa/qplatformdialoghelper.h>
33#include <qpa/qplatformtheme_p.h>
34
35#if QT_CONFIG(dbus)
36#include <QJsonDocument>
37#include <QJsonArray>
38#include <QJsonObject>
39#include <QJsonValue>
40#include <QJsonParseError>
41#ifndef QT_NO_SYSTEMTRAYICON
42#include <private/qdbustrayicon_p.h>
43#include <private/qdbusmenubar_p.h>
44#endif
45#endif
46
47#include <private/qguiapplication_p.h>
48#include <qpa/qplatformintegration.h>
49#include <QtCore/QStandardPaths>
50#if QT_CONFIG(dbus)
51#include <QtDBus/QDBusConnectionInterface>
52#include <private/qdbustrayicon_p.h>
53#endif
54#if QT_CONFIG(mimetype)
55#include <QtCore/QMimeDatabase>
56#include <QtCore/QMimeData>
57#endif
58
59
60QT_BEGIN_NAMESPACE
61
62Q_DECLARE_LOGGING_CATEGORY(qLcTray)
63using namespace Qt::StringLiterals;
64
65const char *QGenericUnixTheme::name = "generic";
66
67QGenericUnixThemePrivate::QGenericUnixThemePrivate()
68 : QPlatformThemePrivate()
69 , systemFont(QLatin1StringView(QGenericUnixTheme::defaultSystemFontNameC),
70 QGenericUnixTheme::defaultSystemFontSize)
71 , fixedFont(QLatin1StringView(QGenericUnixTheme::defaultFixedFontNameC),
72 systemFont.pointSize())
73{
74 fixedFont.setStyleHint(QFont::TypeWriter);
75 qCDebug(lcQpaFonts) << "default fonts: system" << systemFont << "fixed" << fixedFont;
76}
77
78QGenericUnixTheme::QGenericUnixTheme(QGenericUnixThemePrivate *p)
79 : QPlatformTheme(p)
80{}
81
82QGenericUnixTheme::QGenericUnixTheme()
83 : QPlatformTheme(new QGenericUnixThemePrivate())
84{}
85
86const QFont *QGenericUnixTheme::font(Font type) const
87{
88 Q_D(const QGenericUnixTheme);
89 switch (type) {
90 case QPlatformTheme::SystemFont:
91 return &d->systemFont;
92 case QPlatformTheme::FixedFont:
93 return &d->fixedFont;
94 default:
95 return nullptr;
96 }
97}
98
99#if QT_CONFIG(dbus)
100QPlatformMenuBar *QGenericUnixTheme::createPlatformMenuBar() const
101{
102 if (isDBusGlobalMenuAvailable())
103 return new QDBusMenuBar();
104 return nullptr;
105}
106#endif
107
108#if QT_CONFIG(dbus) && QT_CONFIG(systemtrayicon)
109QPlatformSystemTrayIcon *QGenericUnixTheme::createPlatformSystemTrayIcon() const
110{
111 if (shouldUseDBusTray())
112 return new QDBusTrayIcon();
113 return nullptr;
114}
115#endif
116
117QVariant QGenericUnixTheme::themeHint(ThemeHint hint) const
118{
119 switch (hint) {
120 case QPlatformTheme::SystemIconFallbackThemeName:
121 return QVariant(QString(QStringLiteral("hicolor")));
122 case QPlatformTheme::IconThemeSearchPaths:
123 return xdgIconThemePaths();
124 case QPlatformTheme::IconFallbackSearchPaths:
125 return iconFallbackPaths();
126 case QPlatformTheme::DialogButtonBoxButtonsHaveIcons:
127 return QVariant(true);
128 case QPlatformTheme::StyleNames: {
129 QStringList styleNames;
130 styleNames << QStringLiteral("Fusion") << QStringLiteral("Windows");
131 return QVariant(styleNames);
132 }
133 case QPlatformTheme::KeyboardScheme:
134 return QVariant(int(X11KeyboardScheme));
135 case QPlatformTheme::UiEffects:
136 return QVariant(int(HoverEffect));
137 case QPlatformTheme::MouseCursorTheme:
138 return QVariant(mouseCursorTheme());
139 case QPlatformTheme::MouseCursorSize:
140 return QVariant(mouseCursorSize());
141 case QPlatformTheme::PreferFileIconFromTheme:
142 return true;
143 default:
144 break;
145 }
146 return QPlatformTheme::themeHint(hint);
147}
148
149QStringList QGenericUnixTheme::themeNames()
150{
151 QStringList result;
152 if (QGuiApplication::desktopSettingsAware()) {
153 const QByteArray desktopEnvironment = QGuiApplicationPrivate::platformIntegration()->services()->desktopEnvironment();
154 QList<QByteArray> gtkBasedEnvironments;
155 gtkBasedEnvironments << "GNOME"
156 << "X-CINNAMON"
157 << "PANTHEON"
158 << "UNITY"
159 << "MATE"
160 << "XFCE"
161 << "LXDE";
162 const QList<QByteArray> desktopNames = desktopEnvironment.split(sep: ':');
163 for (const QByteArray &desktopName : desktopNames) {
164#if QT_CONFIG(dbus) && QT_CONFIG(settings) && (QT_CONFIG(xcb) || QT_CONFIG(wayland))
165 if (desktopEnvironment == "KDE") {
166 result.push_back(t: QLatin1StringView(QKdeTheme::name));
167 } else
168#endif
169 if (gtkBasedEnvironments.contains(t: desktopName)) {
170 // prefer the GTK3 theme implementation with native dialogs etc.
171 result.push_back(QStringLiteral("gtk3"));
172 // fallback to the generic Gnome theme if loading the GTK3 theme fails
173 result.push_back(t: QLatin1StringView(QGnomeTheme::name));
174 } else {
175 // unknown, but lowercase the name (our standard practice) and
176 // remove any "x-" prefix
177 QString s = QString::fromLatin1(ba: desktopName.toLower());
178 result.push_back(t: s.startsWith(s: "x-"_L1) ? s.mid(position: 2) : s);
179 }
180 }
181 } // desktopSettingsAware
182 result.append(t: QLatin1StringView(QGenericUnixTheme::name));
183 return result;
184}
185
186/*!
187 \internal
188 \brief Creates a UNIX theme according to the given theme \a name
189*/
190QPlatformTheme *QGenericUnixTheme::createUnixTheme(const QString &name)
191{
192 if (name == QLatin1StringView(QGenericUnixTheme::name))
193 return new QGenericUnixTheme;
194#if QT_CONFIG(dbus) && QT_CONFIG(settings) && (QT_CONFIG(xcb) || QT_CONFIG(wayland))
195 if (name == QLatin1StringView(QKdeTheme::name))
196 return QKdeTheme::createKdeTheme();
197#endif
198 if (name == QLatin1StringView(QGnomeTheme::name))
199 return new QGnomeTheme;
200 return nullptr;
201}
202
203// Helper to return the icon theme paths from XDG.
204QStringList QGenericUnixTheme::xdgIconThemePaths()
205{
206 QStringList paths;
207 // Add home directory first in search path
208 const QFileInfo homeIconDir(QDir::homePath() + "/.icons"_L1);
209 if (homeIconDir.isDir())
210 paths.prepend(t: homeIconDir.absoluteFilePath());
211
212 paths.append(other: QStandardPaths::locateAll(type: QStandardPaths::GenericDataLocation,
213 QStringLiteral("icons"),
214 options: QStandardPaths::LocateDirectory));
215
216 return paths;
217}
218
219QStringList QGenericUnixTheme::iconFallbackPaths()
220{
221 QStringList paths;
222 const QFileInfo pixmapsIconsDir(QStringLiteral("/usr/share/pixmaps"));
223 if (pixmapsIconsDir.isDir())
224 paths.append(t: pixmapsIconsDir.absoluteFilePath());
225
226 return paths;
227}
228
229QString QGenericUnixTheme::mouseCursorTheme()
230{
231 static QString themeName = qEnvironmentVariable(varName: "XCURSOR_THEME");
232 return themeName;
233}
234
235QSize QGenericUnixTheme::mouseCursorSize()
236{
237 constexpr int defaultCursorSize = 24;
238 static const int xCursorSize = qEnvironmentVariableIntValue(varName: "XCURSOR_SIZE");
239 static const int s = xCursorSize > 0 ? xCursorSize : defaultCursorSize;
240 return QSize(s, s);
241}
242
243#if QT_CONFIG(dbus)
244static bool checkDBusGlobalMenuAvailable()
245{
246 const QDBusConnection connection = QDBusConnection::sessionBus();
247 static const QString registrarService = QStringLiteral("com.canonical.AppMenu.Registrar");
248 if (const auto iface = connection.interface())
249 return iface->isServiceRegistered(serviceName: registrarService);
250 return false;
251}
252
253bool QGenericUnixTheme::isDBusGlobalMenuAvailable()
254{
255 static bool dbusGlobalMenuAvailable = checkDBusGlobalMenuAvailable();
256 return dbusGlobalMenuAvailable;
257}
258#endif
259
260#if QT_CONFIG(mimetype)
261QIcon QGenericUnixTheme::xdgFileIcon(const QFileInfo &fileInfo)
262{
263 QMimeDatabase mimeDatabase;
264 QMimeType mimeType = mimeDatabase.mimeTypeForFile(fileInfo);
265 if (!mimeType.isValid())
266 return QIcon();
267 const QString &iconName = mimeType.iconName();
268 if (!iconName.isEmpty()) {
269 QIcon icon = QIcon::fromTheme(name: iconName);
270 if (!icon.isNull())
271 return icon;
272 }
273 const QString &genericIconName = mimeType.genericIconName();
274 return genericIconName.isEmpty() ? QIcon() : QIcon::fromTheme(name: genericIconName);
275}
276#endif
277
278
279#if QT_CONFIG(dbus) && QT_CONFIG(systemtrayicon)
280bool QGenericUnixTheme::shouldUseDBusTray()
281{
282 // There's no other tray implementation to fallback to on non-X11
283 // and QDBusTrayIcon can register the icon on the fly after creation
284 if (QGuiApplication::platformName() != "xcb"_L1)
285 return true;
286 const bool result = QDBusMenuConnection().isWatcherRegistered();
287 qCDebug(qLcTray) << "D-Bus tray available:" << result;
288 return result;
289}
290#endif
291
292// Helper functions for implementing QPlatformTheme::fileIcon() for XDG icon themes.
293QList<QSize> QGenericUnixTheme::availableXdgFileIconSizes()
294{
295 return QIcon::fromTheme(QStringLiteral("inode-directory")).availableSizes();
296}
297
298
299QT_END_NAMESPACE
300

source code of qtbase/src/gui/platform/unix/qgenericunixtheme.cpp