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