1 | // Copyright (C) 2017 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 "qxdgdesktopportaltheme.h" |
5 | #include "qxdgdesktopportalfiledialog_p.h" |
6 | |
7 | #include <private/qguiapplication_p.h> |
8 | #include <qpa/qplatformtheme_p.h> |
9 | #include <qpa/qplatformthemefactory_p.h> |
10 | #include <qpa/qplatformintegration.h> |
11 | |
12 | #include <QDBusConnection> |
13 | #include <QDBusMessage> |
14 | #include <QDBusPendingCall> |
15 | #include <QDBusPendingCallWatcher> |
16 | #include <QDBusPendingReply> |
17 | #include <QDBusReply> |
18 | |
19 | QT_BEGIN_NAMESPACE |
20 | |
21 | using namespace Qt::StringLiterals; |
22 | |
23 | class QXdgDesktopPortalThemePrivate : public QPlatformThemePrivate |
24 | { |
25 | public: |
26 | enum XdgColorschemePref { |
27 | None, |
28 | PreferDark, |
29 | PreferLight |
30 | }; |
31 | |
32 | QXdgDesktopPortalThemePrivate() |
33 | : QPlatformThemePrivate() |
34 | { } |
35 | |
36 | ~QXdgDesktopPortalThemePrivate() |
37 | { |
38 | delete baseTheme; |
39 | } |
40 | |
41 | /*! \internal |
42 | |
43 | Converts the given Freedesktop color scheme setting \a colorschemePref to a Qt::ColorScheme value. |
44 | Specification: https://github.com/flatpak/xdg-desktop-portal/blob/d7a304a00697d7d608821253cd013f3b97ac0fb6/data/org.freedesktop.impl.portal.Settings.xml#L33-L45 |
45 | |
46 | Unfortunately the enum numerical values are not defined identically, so we have to convert them. |
47 | |
48 | The mapping is as follows: |
49 | |
50 | Enum Index: Freedesktop definition | Qt definition |
51 | ----------------------------------- | ------------- |
52 | 0: No preference | 0: Unknown |
53 | 1: Prefer dark appearance | 2: Dark |
54 | 2: Prefer light appearance | 1: Light |
55 | */ |
56 | static Qt::ColorScheme colorSchemeFromXdgPref(const XdgColorschemePref colorschemePref) |
57 | { |
58 | switch (colorschemePref) { |
59 | case PreferDark: return Qt::ColorScheme::Dark; |
60 | case PreferLight: return Qt::ColorScheme::Light; |
61 | default: return Qt::ColorScheme::Unknown; |
62 | } |
63 | } |
64 | |
65 | QPlatformTheme *baseTheme = nullptr; |
66 | uint fileChooserPortalVersion = 0; |
67 | Qt::ColorScheme colorScheme = Qt::ColorScheme::Unknown; |
68 | }; |
69 | |
70 | QXdgDesktopPortalTheme::QXdgDesktopPortalTheme() |
71 | : d_ptr(new QXdgDesktopPortalThemePrivate) |
72 | { |
73 | Q_D(QXdgDesktopPortalTheme); |
74 | |
75 | QStringList themeNames; |
76 | themeNames += QGuiApplicationPrivate::platform_integration->themeNames(); |
77 | // 1) Look for a theme plugin. |
78 | for (const QString &themeName : std::as_const(t&: themeNames)) { |
79 | d->baseTheme = QPlatformThemeFactory::create(key: themeName, platformPluginPath: nullptr); |
80 | if (d->baseTheme) |
81 | break; |
82 | } |
83 | |
84 | // 2) If no theme plugin was found ask the platform integration to |
85 | // create a theme |
86 | if (!d->baseTheme) { |
87 | for (const QString &themeName : std::as_const(t&: themeNames)) { |
88 | d->baseTheme = QGuiApplicationPrivate::platform_integration->createPlatformTheme(name: themeName); |
89 | if (d->baseTheme) |
90 | break; |
91 | } |
92 | // No error message; not having a theme plugin is allowed. |
93 | } |
94 | |
95 | // 3) Fall back on the built-in "null" platform theme. |
96 | if (!d->baseTheme) |
97 | d->baseTheme = new QPlatformTheme; |
98 | |
99 | // Get information about portal version |
100 | QDBusMessage message = QDBusMessage::createMethodCall(destination: "org.freedesktop.portal.Desktop"_L1 , |
101 | path: "/org/freedesktop/portal/desktop"_L1 , |
102 | interface: "org.freedesktop.DBus.Properties"_L1 , |
103 | method: "Get"_L1 ); |
104 | message << "org.freedesktop.portal.FileChooser"_L1 << "version"_L1 ; |
105 | QDBusPendingCall pendingCall = QDBusConnection::sessionBus().asyncCall(message); |
106 | QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pendingCall); |
107 | QObject::connect(sender: watcher, signal: &QDBusPendingCallWatcher::finished, slot: [d] (QDBusPendingCallWatcher *watcher) { |
108 | QDBusPendingReply<QVariant> reply = *watcher; |
109 | if (reply.isValid()) { |
110 | d->fileChooserPortalVersion = reply.value().toUInt(); |
111 | } |
112 | watcher->deleteLater(); |
113 | }); |
114 | |
115 | // Get information about system theme preference |
116 | message = QDBusMessage::createMethodCall(destination: "org.freedesktop.portal.Desktop"_L1 , |
117 | path: "/org/freedesktop/portal/desktop"_L1 , |
118 | interface: "org.freedesktop.portal.Settings"_L1 , |
119 | method: "Read"_L1 ); |
120 | message << "org.freedesktop.appearance"_L1 << "color-scheme"_L1 ; |
121 | |
122 | // this must not be asyncCall() because we have to set appearance now |
123 | QDBusReply<QVariant> reply = QDBusConnection::sessionBus().call(message); |
124 | if (reply.isValid()) { |
125 | const QDBusVariant dbusVariant = qvariant_cast<QDBusVariant>(v: reply.value()); |
126 | const QXdgDesktopPortalThemePrivate::XdgColorschemePref xdgPref = static_cast<QXdgDesktopPortalThemePrivate::XdgColorschemePref>(dbusVariant.variant().toUInt()); |
127 | d->colorScheme = QXdgDesktopPortalThemePrivate::colorSchemeFromXdgPref(colorschemePref: xdgPref); |
128 | } |
129 | } |
130 | |
131 | QPlatformMenuItem* QXdgDesktopPortalTheme::() const |
132 | { |
133 | Q_D(const QXdgDesktopPortalTheme); |
134 | return d->baseTheme->createPlatformMenuItem(); |
135 | } |
136 | |
137 | QPlatformMenu* QXdgDesktopPortalTheme::() const |
138 | { |
139 | Q_D(const QXdgDesktopPortalTheme); |
140 | return d->baseTheme->createPlatformMenu(); |
141 | } |
142 | |
143 | QPlatformMenuBar* QXdgDesktopPortalTheme::() const |
144 | { |
145 | Q_D(const QXdgDesktopPortalTheme); |
146 | return d->baseTheme->createPlatformMenuBar(); |
147 | } |
148 | |
149 | void QXdgDesktopPortalTheme::() |
150 | { |
151 | Q_D(const QXdgDesktopPortalTheme); |
152 | return d->baseTheme->showPlatformMenuBar(); |
153 | } |
154 | |
155 | bool QXdgDesktopPortalTheme::usePlatformNativeDialog(DialogType type) const |
156 | { |
157 | Q_D(const QXdgDesktopPortalTheme); |
158 | |
159 | if (type == FileDialog) |
160 | return true; |
161 | |
162 | return d->baseTheme->usePlatformNativeDialog(type); |
163 | } |
164 | |
165 | QPlatformDialogHelper* QXdgDesktopPortalTheme::createPlatformDialogHelper(DialogType type) const |
166 | { |
167 | Q_D(const QXdgDesktopPortalTheme); |
168 | |
169 | if (type == FileDialog && d->fileChooserPortalVersion) { |
170 | // Older versions of FileChooser portal don't support opening directories, therefore we fallback |
171 | // to native file dialog opened inside the sandbox to open a directory. |
172 | if (d->baseTheme->usePlatformNativeDialog(type)) |
173 | return new QXdgDesktopPortalFileDialog(static_cast<QPlatformFileDialogHelper*>(d->baseTheme->createPlatformDialogHelper(type)), |
174 | d->fileChooserPortalVersion); |
175 | |
176 | return new QXdgDesktopPortalFileDialog; |
177 | } |
178 | |
179 | return d->baseTheme->createPlatformDialogHelper(type); |
180 | } |
181 | |
182 | #ifndef QT_NO_SYSTEMTRAYICON |
183 | QPlatformSystemTrayIcon* QXdgDesktopPortalTheme::createPlatformSystemTrayIcon() const |
184 | { |
185 | Q_D(const QXdgDesktopPortalTheme); |
186 | return d->baseTheme->createPlatformSystemTrayIcon(); |
187 | } |
188 | #endif |
189 | |
190 | const QPalette *QXdgDesktopPortalTheme::palette(Palette type) const |
191 | { |
192 | Q_D(const QXdgDesktopPortalTheme); |
193 | return d->baseTheme->palette(type); |
194 | } |
195 | |
196 | const QFont* QXdgDesktopPortalTheme::font(Font type) const |
197 | { |
198 | Q_D(const QXdgDesktopPortalTheme); |
199 | return d->baseTheme->font(type); |
200 | } |
201 | |
202 | QVariant QXdgDesktopPortalTheme::themeHint(ThemeHint hint) const |
203 | { |
204 | Q_D(const QXdgDesktopPortalTheme); |
205 | return d->baseTheme->themeHint(hint); |
206 | } |
207 | |
208 | Qt::ColorScheme QXdgDesktopPortalTheme::colorScheme() const |
209 | { |
210 | Q_D(const QXdgDesktopPortalTheme); |
211 | if (d->colorScheme == Qt::ColorScheme::Unknown) |
212 | return d->baseTheme->colorScheme(); |
213 | return d->colorScheme; |
214 | } |
215 | |
216 | QPixmap QXdgDesktopPortalTheme::standardPixmap(StandardPixmap sp, const QSizeF &size) const |
217 | { |
218 | Q_D(const QXdgDesktopPortalTheme); |
219 | return d->baseTheme->standardPixmap(sp, size); |
220 | } |
221 | |
222 | QIcon QXdgDesktopPortalTheme::fileIcon(const QFileInfo &fileInfo, |
223 | QPlatformTheme::IconOptions iconOptions) const |
224 | { |
225 | Q_D(const QXdgDesktopPortalTheme); |
226 | return d->baseTheme->fileIcon(fileInfo, iconOptions); |
227 | } |
228 | |
229 | QIconEngine * QXdgDesktopPortalTheme::createIconEngine(const QString &iconName) const |
230 | { |
231 | Q_D(const QXdgDesktopPortalTheme); |
232 | return d->baseTheme->createIconEngine(iconName); |
233 | } |
234 | |
235 | #if QT_CONFIG(shortcut) |
236 | QList<QKeySequence> QXdgDesktopPortalTheme::keyBindings(QKeySequence::StandardKey key) const |
237 | { |
238 | Q_D(const QXdgDesktopPortalTheme); |
239 | return d->baseTheme->keyBindings(key); |
240 | } |
241 | #endif |
242 | |
243 | QString QXdgDesktopPortalTheme::standardButtonText(int button) const |
244 | { |
245 | Q_D(const QXdgDesktopPortalTheme); |
246 | return d->baseTheme->standardButtonText(button); |
247 | } |
248 | |
249 | QT_END_NAMESPACE |
250 | |