1/*
2 * SPDX-FileCopyrightText: 2018 Marco Martin <mart@kde.org>
3 * SPDX-FileCopyrightText: 2023 Harald Sitter <sitter@kde.org>
4 *
5 * SPDX-License-Identifier: LGPL-2.0-or-later
6 */
7
8#include "tabletmodewatcher.h"
9#include <QCoreApplication>
10
11#if defined(KIRIGAMI_ENABLE_DBUS)
12#include "settings_interface.h"
13#include <QDBusConnection>
14#endif
15
16using namespace Qt::Literals::StringLiterals;
17
18// TODO: All the dbus stuff should be conditional, optional win32 support
19
20namespace Kirigami
21{
22namespace Platform
23{
24
25class TabletModeWatcherSingleton
26{
27public:
28 TabletModeWatcher self;
29};
30
31Q_GLOBAL_STATIC(TabletModeWatcherSingleton, privateTabletModeWatcherSelf)
32
33class TabletModeWatcherPrivate
34{
35 static constexpr auto PORTAL_GROUP = "org.kde.TabletMode"_L1;
36 static constexpr auto KEY_AVAILABLE = "available"_L1;
37 static constexpr auto KEY_ENABLED = "enabled"_L1;
38
39public:
40 TabletModeWatcherPrivate(TabletModeWatcher *watcher)
41 : q(watcher)
42 {
43 // Called here to avoid collisions with application event types so we should use
44 // registerEventType for generating the event types.
45 TabletModeChangedEvent::type = QEvent::Type(QEvent::registerEventType());
46#if !defined(KIRIGAMI_ENABLE_DBUS) && (defined(Q_OS_ANDROID) || defined(Q_OS_IOS))
47 isTabletModeAvailable = true;
48 isTabletMode = true;
49#elif defined(KIRIGAMI_ENABLE_DBUS)
50 // Mostly for debug purposes and for platforms which are always mobile,
51 // such as Plasma Mobile
52 if (qEnvironmentVariableIsSet(varName: "QT_QUICK_CONTROLS_MOBILE") || qEnvironmentVariableIsSet(varName: "KDE_KIRIGAMI_TABLET_MODE")) {
53 /* clang-format off */
54 isTabletMode =
55 (QString::fromLatin1(ba: qgetenv(varName: "QT_QUICK_CONTROLS_MOBILE")) == QStringLiteral("1")
56 || QString::fromLatin1(ba: qgetenv(varName: "QT_QUICK_CONTROLS_MOBILE")) == QStringLiteral("true"))
57 || (QString::fromLatin1(ba: qgetenv(varName: "KDE_KIRIGAMI_TABLET_MODE")) == QStringLiteral("1")
58 || QString::fromLatin1(ba: qgetenv(varName: "KDE_KIRIGAMI_TABLET_MODE")) == QStringLiteral("true"));
59 /* clang-format on */
60 isTabletModeAvailable = isTabletMode;
61 } else if (qEnvironmentVariableIsSet(varName: "QT_NO_XDG_DESKTOP_PORTAL")) {
62 isTabletMode = false;
63 } else {
64 qDBusRegisterMetaType<VariantMapMap>();
65 auto portal = new OrgFreedesktopPortalSettingsInterface(u"org.freedesktop.portal.Desktop"_s,
66 u"/org/freedesktop/portal/desktop"_s,
67 QDBusConnection::sessionBus(),
68 q);
69
70 QObject::connect(sender: portal,
71 signal: &OrgFreedesktopPortalSettingsInterface::SettingChanged,
72 context: q,
73 slot: [this](const QString &group, const QString &key, const QDBusVariant &value) {
74 if (group != PORTAL_GROUP) {
75 return;
76 }
77 if (key == KEY_AVAILABLE) {
78 Q_EMIT q->tabletModeAvailableChanged(tabletModeAvailable: value.variant().toBool());
79 } else if (key == KEY_ENABLED) {
80 setIsTablet(value.variant().toBool());
81 }
82 });
83
84 const auto reply = portal->ReadAll(groups: {PORTAL_GROUP});
85 auto watcher = new QDBusPendingCallWatcher(reply, q);
86 QObject::connect(sender: watcher, signal: &QDBusPendingCallWatcher::finished, context: q, slot: [this, watcher]() {
87 watcher->deleteLater();
88 QDBusPendingReply<VariantMapMap> reply = *watcher;
89 const auto properties = reply.value().value(key: PORTAL_GROUP);
90 Q_EMIT q->tabletModeAvailableChanged(tabletModeAvailable: properties[KEY_AVAILABLE].toBool());
91 setIsTablet(properties[KEY_ENABLED].toBool());
92 });
93 }
94// TODO: case for Windows
95#endif
96 }
97 ~TabletModeWatcherPrivate(){};
98 void setIsTablet(bool tablet);
99
100 TabletModeWatcher *q;
101 QList<QObject *> watchers;
102 bool isTabletModeAvailable = false;
103 bool isTabletMode = false;
104};
105
106void TabletModeWatcherPrivate::setIsTablet(bool tablet)
107{
108 if (isTabletMode == tablet) {
109 return;
110 }
111
112 isTabletMode = tablet;
113 TabletModeChangedEvent event{tablet};
114 Q_EMIT q->tabletModeChanged(tabletMode: tablet);
115 for (auto *w : watchers) {
116 QCoreApplication::sendEvent(receiver: w, event: &event);
117 }
118}
119
120TabletModeWatcher::TabletModeWatcher(QObject *parent)
121 : QObject(parent)
122 , d(new TabletModeWatcherPrivate(this))
123{
124}
125
126TabletModeWatcher::~TabletModeWatcher()
127{
128 delete d;
129}
130
131TabletModeWatcher *TabletModeWatcher::self()
132{
133 return &privateTabletModeWatcherSelf()->self;
134}
135
136bool TabletModeWatcher::isTabletModeAvailable() const
137{
138 return d->isTabletModeAvailable;
139}
140
141bool TabletModeWatcher::isTabletMode() const
142{
143 return d->isTabletMode;
144}
145
146void TabletModeWatcher::addWatcher(QObject *watcher)
147{
148 d->watchers.append(t: watcher);
149}
150
151void TabletModeWatcher::removeWatcher(QObject *watcher)
152{
153 d->watchers.removeAll(t: watcher);
154}
155
156}
157}
158
159#include "moc_tabletmodewatcher.cpp"
160

source code of kirigami/src/platform/tabletmodewatcher.cpp