File indexing completed on 2024-12-08 04:20:25

0001 /*
0002  * SPDX-License-Identifier: LGPL-2.0-or-later
0003  * SPDX-FileCopyrightText: 2016-2022 Red Hat Inc
0004  * SPDX-FileContributor: Jan Grulich <jgrulich@redhat.com>
0005  * SPDX-FileCopyrightText: 2022 Harald Sitter <sitter@kde.org>
0006  */
0007 
0008 #include "xdgportaltest.h"
0009 
0010 #include <QBuffer>
0011 #include <QDBusArgument>
0012 #include <QDBusConnection>
0013 #include <QDBusConnectionInterface>
0014 #include <QDBusMetaType>
0015 #include <QDBusUnixFileDescriptor>
0016 #include <QDesktopServices>
0017 #include <QFile>
0018 #include <QFileDialog>
0019 #include <QMenu>
0020 #include <QMenuBar>
0021 #include <QPainter>
0022 #include <QPdfWriter>
0023 #include <QStandardPaths>
0024 #include <QSystemTrayIcon>
0025 #include <QTemporaryFile>
0026 #include <QWindow>
0027 
0028 #include <KIO/OpenUrlJob>
0029 #include <KNotification>
0030 #include <KWindowSystem>
0031 
0032 #include <gst/gst.h>
0033 #include <optional>
0034 
0035 #include "dropsite/dropsitewindow.h"
0036 #include <globalshortcuts_portal_interface.h>
0037 #include <portalsrequest_interface.h>
0038 
0039 #include "xdgexporterv2.h"
0040 
0041 Q_LOGGING_CATEGORY(XdgPortalTestKde, "xdg-portal-test-kde")
0042 
0043 Q_DECLARE_METATYPE(XdgPortalTest::Stream);
0044 Q_DECLARE_METATYPE(XdgPortalTest::Streams);
0045 
0046 struct PortalIcon {
0047     QString str;
0048     QDBusVariant data;
0049 
0050     static void registerDBusType()
0051     {
0052         qDBusRegisterMetaType<PortalIcon>();
0053     }
0054 };
0055 Q_DECLARE_METATYPE(PortalIcon);
0056 
0057 QDBusArgument &operator<<(QDBusArgument &argument, const PortalIcon &icon)
0058 {
0059     argument.beginStructure();
0060     argument << icon.str << icon.data;
0061     argument.endStructure();
0062     return argument;
0063 }
0064 
0065 const QDBusArgument &operator>>(const QDBusArgument &argument, PortalIcon &icon)
0066 {
0067     argument.beginStructure();
0068     argument >> icon.str >> icon.data;
0069     argument.endStructure();
0070     return argument;
0071 }
0072 
0073 /// a(sa{sv})
0074 using Shortcuts = QList<QPair<QString, QVariantMap>>;
0075 
0076 const QDBusArgument &operator >> (const QDBusArgument &arg, XdgPortalTest::Stream &stream)
0077 {
0078     arg.beginStructure();
0079     arg >> stream.node_id;
0080 
0081     arg.beginMap();
0082     while (!arg.atEnd()) {
0083         QString key;
0084         QVariant map;
0085         arg.beginMapEntry();
0086         arg >> key >> map;
0087         arg.endMapEntry();
0088         stream.map.insert(key, map);
0089     }
0090     arg.endMap();
0091     arg.endStructure();
0092 
0093     return arg;
0094 }
0095 
0096 static QString desktopPortalService()
0097 {
0098     return QStringLiteral("org.freedesktop.portal.Desktop");
0099 }
0100 
0101 static QString desktopPortalPath()
0102 {
0103     return QStringLiteral("/org/freedesktop/portal/desktop");
0104 }
0105 
0106 static QString portalRequestInterface()
0107 {
0108     return QStringLiteral("org.freedesktop.portal.Request");
0109 }
0110 
0111 static QString portalRequestResponse()
0112 {
0113     return QStringLiteral("Response");
0114 }
0115 
0116 QString XdgPortalTest::parentWindowId() const
0117 {
0118     switch (KWindowSystem::platform()) {
0119     case KWindowSystem::Platform::X11:
0120         return QLatin1String("x11:") + QString::number(winId());
0121     case KWindowSystem::Platform::Wayland:
0122         if (!m_xdgExported) {
0123             qDebug() << "nope!";
0124             return {};
0125         }
0126         return QLatin1String("wayland:") + *m_xdgExported->handle();
0127     case KWindowSystem::Platform::Unknown:
0128         break;
0129     }
0130     return {};
0131 }
0132 
0133 XdgPortalTest::XdgPortalTest(QWidget *parent, Qt::WindowFlags f)
0134     : QMainWindow(parent, f)
0135     , m_mainWindow(std::make_unique<Ui::XdgPortalTest>())
0136     , m_sessionTokenCounter(0)
0137     , m_requestTokenCounter(0)
0138 {
0139     qDBusRegisterMetaType<Shortcuts>();
0140     qDBusRegisterMetaType<QPair<QString,QVariantMap>>();
0141 
0142     QLoggingCategory::setFilterRules(QStringLiteral("xdg-portal-test-kde.debug = true"));
0143     PortalIcon::registerDBusType();
0144 
0145     m_mainWindow->setupUi(this);
0146 
0147     auto dropSiteLayout = new QVBoxLayout(m_mainWindow->dropSite);
0148     auto dropSite = new DropSiteWindow(m_mainWindow->dropSite);
0149     dropSiteLayout->addWidget(dropSite);
0150 
0151     m_mainWindow->sandboxLabel->setText(isRunningSandbox() ? QLatin1String("yes") : QLatin1String("no"));
0152     m_mainWindow->printWarning->setText(QLatin1String("Select an image in JPG format using FileChooser part!!"));
0153 
0154     auto menubar = new QMenuBar(this);
0155     setMenuBar(menubar);
0156 
0157     auto menu = new QMenu(QLatin1String("File"), menubar);
0158     menu->addAction(QIcon::fromTheme(QLatin1String("application-exit")), QLatin1String("Quit"), qApp, &QApplication::quit);
0159     menubar->insertMenu(nullptr, menu);
0160 
0161     auto trayIcon = new QSystemTrayIcon(QIcon::fromTheme(QLatin1String("kde")), this);
0162     trayIcon->setContextMenu(menu);
0163     trayIcon->show();
0164 
0165     connect(trayIcon, &QSystemTrayIcon::activated, this, [this] (QSystemTrayIcon::ActivationReason reason) {
0166         switch (reason) {
0167             case QSystemTrayIcon::Unknown:
0168                 m_mainWindow->systrayLabel->setText(QLatin1String("Unknown reason"));
0169                 break;
0170             case QSystemTrayIcon::Context:
0171                 m_mainWindow->systrayLabel->setText(QLatin1String("The context menu for the system tray entry was requested"));
0172                 break;
0173             case QSystemTrayIcon::DoubleClick:
0174                 m_mainWindow->systrayLabel->setText(QLatin1String("The system tray entry was double clicked"));
0175                 break;
0176             case QSystemTrayIcon::Trigger:
0177                 m_mainWindow->systrayLabel->setText(QLatin1String("The system tray entry was clicked"));
0178                 show();
0179                 break;
0180             case QSystemTrayIcon::MiddleClick:
0181                 m_mainWindow->systrayLabel->setText(QLatin1String("The system tray entry was clicked with the middle mouse button"));
0182                 break;
0183         }
0184     });
0185 
0186     connect(m_mainWindow->krun, &QPushButton::clicked, this, [this] {
0187         auto job = new KIO::OpenUrlJob(m_mainWindow->kurlrequester->url());
0188         job->start();
0189     });
0190     connect(m_mainWindow->openurl, &QPushButton::clicked, this, [this] {
0191         QDesktopServices::openUrl(m_mainWindow->kurlrequester->url());
0192     });
0193     connect(m_mainWindow->inhibit, &QPushButton::clicked, this, &XdgPortalTest::inhibitRequested);
0194     connect(m_mainWindow->uninhibit, &QPushButton::clicked, this, &XdgPortalTest::uninhibitRequested);
0195     connect(m_mainWindow->openFile, &QPushButton::clicked, this, &XdgPortalTest::openFileRequested);
0196     connect(m_mainWindow->openFileModal, &QPushButton::clicked, this, &XdgPortalTest::openFileModalRequested);
0197     connect(m_mainWindow->saveFile, &QPushButton::clicked, this, &XdgPortalTest::saveFileRequested);
0198     connect(m_mainWindow->openDir, &QPushButton::clicked, this, &XdgPortalTest::openDirRequested);
0199     connect(m_mainWindow->openDirModal, &QPushButton::clicked, this, &XdgPortalTest::openDirModalRequested);
0200     connect(m_mainWindow->notifyButton, &QPushButton::clicked, this, &XdgPortalTest::sendNotification);
0201     connect(m_mainWindow->notifyPixmapButton, &QPushButton::clicked, this, &XdgPortalTest::sendNotificationPixmap);
0202     connect(m_mainWindow->notifyWithDefault, &QPushButton::clicked, this, &XdgPortalTest::sendNotificationDefault);
0203     connect(m_mainWindow->printButton, &QPushButton::clicked, this, &XdgPortalTest::printDocument);
0204     connect(m_mainWindow->requestDeviceAccess, &QPushButton::clicked, this, &XdgPortalTest::requestDeviceAccess);
0205     connect(m_mainWindow->screenShareButton, &QPushButton::clicked, this, &XdgPortalTest::requestScreenSharing);
0206     connect(m_mainWindow->screenshotButton, &QPushButton::clicked, this, &XdgPortalTest::requestScreenshot);
0207     connect(m_mainWindow->accountButton, &QPushButton::clicked, this, &XdgPortalTest::requestAccount);
0208     connect(m_mainWindow->appChooserButton, &QPushButton::clicked, this, &XdgPortalTest::chooseApplication);
0209     connect(m_mainWindow->webAppButton, &QPushButton::clicked, this, &XdgPortalTest::addLauncher);
0210     connect(m_mainWindow->removeWebAppButton, &QPushButton::clicked, this, &XdgPortalTest::removeLauncher);
0211 
0212     // launcher buttons only work correctly inside sandboxes
0213     m_mainWindow->webAppButton->setEnabled(isRunningSandbox());
0214     m_mainWindow->removeWebAppButton->setEnabled(isRunningSandbox());
0215     connect(m_mainWindow->configureShortcuts, &QPushButton::clicked, this, &XdgPortalTest::configureShortcuts);
0216 
0217     connect(m_mainWindow->openFileButton, &QPushButton::clicked, this, [this] () {
0218         QDesktopServices::openUrl(QUrl::fromLocalFile(m_mainWindow->selectedFiles->text().split(",").first()));
0219     });
0220 
0221     m_shortcuts = new OrgFreedesktopPortalGlobalShortcutsInterface(QLatin1String("org.freedesktop.portal.Desktop"),
0222                                                                       QLatin1String("/org/freedesktop/portal/desktop"),
0223                                                                       QDBusConnection::sessionBus(), this);
0224 
0225     connect(m_shortcuts, &OrgFreedesktopPortalGlobalShortcutsInterface::Activated, this, [this] (const QDBusObjectPath &session_handle, const QString &shortcut_id, qulonglong timestamp, const QVariantMap &options) {
0226         qDebug() << "activated" << session_handle.path() << shortcut_id << timestamp << options;
0227         m_mainWindow->shortcutState->setText(QStringLiteral("Active!"));
0228     });
0229     connect(m_shortcuts, &OrgFreedesktopPortalGlobalShortcutsInterface::Deactivated, this, [this] {
0230         m_mainWindow->shortcutState->setText(QStringLiteral("Deactivated!"));
0231     });
0232 
0233     Shortcuts initialShortcuts = {
0234         { QStringLiteral("AwesomeTrigger"), { { QStringLiteral("description"), QStringLiteral("Awesome Description") } } }
0235     };
0236     QDBusArgument arg;
0237     arg << initialShortcuts;
0238     auto reply = m_shortcuts->CreateSession({
0239         { QLatin1String("session_handle_token"), "XdpPortalTest" },
0240         { QLatin1String("handle_token"), getRequestToken() },
0241         { QLatin1String("shortcuts"), QVariant::fromValue(arg) },
0242     });
0243     reply.waitForFinished();
0244     if (reply.isError()) {
0245         qWarning() << "Couldn't get reply";
0246         qWarning() << "Error:" << reply.error().message();
0247         m_mainWindow->shortcutsDescriptions->setText(reply.error().message());
0248     } else {
0249         QDBusConnection::sessionBus().connect(QString(),
0250                                             reply.value().path(),
0251                                             QLatin1String("org.freedesktop.portal.Request"),
0252                                             QLatin1String("Response"),
0253                                             this,
0254                                             SLOT(gotGlobalShortcutsCreateSessionResponse(uint,QVariantMap)));
0255     }
0256 
0257     gst_init(nullptr, nullptr);
0258 
0259     m_xdgExporter.reset(new XdgExporterV2);
0260     m_xdgExported = m_xdgExporter->exportWidget(this);
0261 }
0262 
0263 XdgPortalTest::~XdgPortalTest()
0264 {
0265 }
0266 
0267 void XdgPortalTest::notificationActivated(const QString &action)
0268 {
0269     m_mainWindow->notificationResponse->setText(QString("%1 activated").arg(action));
0270 }
0271 
0272 void XdgPortalTest::openFileRequested()
0273 {
0274     auto fileDialog = new QFileDialog(this);
0275     fileDialog->setFileMode(QFileDialog::ExistingFiles);
0276     fileDialog->setLabelText(QFileDialog::Accept, QLatin1String("Open (portal)"));
0277     fileDialog->setModal(false);
0278     fileDialog->setWindowTitle(QLatin1String("Flatpak test - open dialog"));
0279     fileDialog->setMimeTypeFilters(QStringList { QLatin1String("text/plain"), QLatin1String("image/jpeg") } );
0280     connect(fileDialog, &QFileDialog::accepted, this, [this, fileDialog] () {
0281         if (!fileDialog->selectedFiles().isEmpty()) {
0282             m_mainWindow->selectedFiles->setText(fileDialog->selectedFiles().join(QLatin1String(", ")));
0283             if (fileDialog->selectedFiles().first().endsWith(QLatin1String(".jpg"))) {
0284                 m_mainWindow->printButton->setEnabled(true);
0285                 m_mainWindow->printWarning->setVisible(false);
0286             } else {
0287                 m_mainWindow->printButton->setEnabled(false);
0288                 m_mainWindow->printWarning->setVisible(true);
0289             }
0290         }
0291         m_mainWindow->openFileButton->setEnabled(true);
0292         fileDialog->deleteLater();
0293     });
0294     fileDialog->show();
0295 }
0296 
0297 void XdgPortalTest::openFileModalRequested()
0298 {
0299     auto fileDialog = new QFileDialog(this);
0300     fileDialog->setFileMode(QFileDialog::ExistingFiles);
0301     fileDialog->setNameFilter(QLatin1String("*.txt"));
0302     fileDialog->setLabelText(QFileDialog::Accept, QLatin1String("Open (portal)"));
0303     fileDialog->setModal(false);
0304     fileDialog->setWindowTitle(QLatin1String("Flatpak test - open dialog"));
0305 
0306     if (fileDialog->exec() == QDialog::Accepted) {
0307         if (!fileDialog->selectedFiles().isEmpty()) {
0308             m_mainWindow->selectedFiles->setText(fileDialog->selectedFiles().join(QLatin1String(", ")));
0309             if (fileDialog->selectedFiles().first().endsWith(QLatin1String(".jpg"))) {
0310                 m_mainWindow->printButton->setEnabled(true);
0311                 m_mainWindow->printWarning->setVisible(false);
0312             } else {
0313                 m_mainWindow->printButton->setEnabled(false);
0314                 m_mainWindow->printWarning->setVisible(true);
0315             }
0316         }
0317         m_mainWindow->openFileButton->setEnabled(true);
0318         fileDialog->deleteLater();
0319     }
0320 }
0321 
0322 void XdgPortalTest::openDirRequested()
0323 {
0324     auto fileDialog = new QFileDialog(this);
0325     fileDialog->setFileMode(QFileDialog::Directory);
0326     fileDialog->setLabelText(QFileDialog::Accept, QLatin1String("Open (portal)"));
0327     fileDialog->setModal(false);
0328     fileDialog->setWindowTitle(QLatin1String("Flatpak test - open directory dialog"));
0329 
0330     connect(fileDialog, &QFileDialog::accepted, this, [this, fileDialog] () {
0331         m_mainWindow->selectedDir->setText(fileDialog->selectedFiles().join(QLatin1String(", ")));
0332         fileDialog->deleteLater();
0333     });
0334     fileDialog->show();
0335 }
0336 
0337 void XdgPortalTest::openDirModalRequested()
0338 {
0339     auto fileDialog = new QFileDialog(this);
0340     fileDialog->setFileMode(QFileDialog::Directory);
0341     fileDialog->setLabelText(QFileDialog::Accept, QLatin1String("Open (portal)"));
0342     fileDialog->setModal(false);
0343     fileDialog->setWindowTitle(QLatin1String("Flatpak test - open directory dialog"));
0344 
0345     if (fileDialog->exec() == QDialog::Accepted) {
0346         m_mainWindow->selectedDir->setText(fileDialog->selectedFiles().join(QLatin1String(", ")));
0347         fileDialog->deleteLater();
0348     }
0349 }
0350 
0351 void XdgPortalTest::gotPrintResponse(uint response, const QVariantMap &results)
0352 {
0353     // TODO do cleaning
0354     qWarning() << response << results;
0355 }
0356 
0357 void XdgPortalTest::gotPreparePrintResponse(uint response, const QVariantMap &results)
0358 {
0359     if (!response) {
0360         QVariantMap settings;
0361         QVariantMap pageSetup;
0362 
0363         QDBusArgument dbusArgument = results.value(QLatin1String("settings")).value<QDBusArgument>();
0364         dbusArgument >> settings;
0365 
0366         QDBusArgument dbusArgument1 = results.value(QLatin1String("page-setup")).value<QDBusArgument>();
0367         dbusArgument1 >> pageSetup;
0368 
0369         QTemporaryFile tempFile;
0370         tempFile.setAutoRemove(false);
0371         if (!tempFile.open()) {
0372             qWarning() << "Couldn't generate pdf file";
0373             return;
0374         }
0375 
0376         QPdfWriter writer(tempFile.fileName());
0377         QPainter painter(&writer);
0378 
0379         if (pageSetup.contains(QLatin1String("Orientation"))) {
0380             const QString orientation = pageSetup.value(QLatin1String("Orientation")).toString();
0381             if (orientation == QLatin1String("portrait") || orientation == QLatin1String("revers-portrait")) {
0382                 writer.setPageOrientation(QPageLayout::Portrait);
0383             } else if (orientation == QLatin1String("landscape") || orientation == QLatin1String("reverse-landscape")) {
0384                 writer.setPageOrientation(QPageLayout::Landscape);
0385             }
0386         }
0387 
0388         if (pageSetup.contains(QLatin1String("MarginTop")) &&
0389             pageSetup.contains(QLatin1String("MarginBottom")) &&
0390             pageSetup.contains(QLatin1String("MarginLeft")) &&
0391             pageSetup.contains(QLatin1String("MarginRight"))) {
0392             const int marginTop = pageSetup.value(QLatin1String("MarginTop")).toInt();
0393             const int marginBottom = pageSetup.value(QLatin1String("MarginBottom")).toInt();
0394             const int marginLeft = pageSetup.value(QLatin1String("MarginLeft")).toInt();
0395             const int marginRight = pageSetup.value(QLatin1String("MarginRight")).toInt();
0396             writer.setPageMargins(QMarginsF(marginLeft, marginTop, marginRight, marginBottom), QPageLayout::Millimeter);
0397         }
0398 
0399         // TODO num-copies, pages
0400 
0401         writer.setPageSize(QPageSize(QPageSize::A4));
0402 
0403         painter.drawPixmap(QPoint(0,0), QPixmap(m_mainWindow->selectedFiles->text()));
0404         painter.end();
0405 
0406         // Send it back for printing
0407         QDBusUnixFileDescriptor descriptor(tempFile.handle());
0408 
0409         QDBusMessage message = QDBusMessage::createMethodCall(desktopPortalService(),
0410                                                             desktopPortalPath(),
0411                                                             QLatin1String("org.freedesktop.portal.Print"),
0412                                                             QLatin1String("Print"));
0413 
0414         message << parentWindowId() << QLatin1String("Print dialog") << QVariant::fromValue<QDBusUnixFileDescriptor>(descriptor) << QVariantMap{{QLatin1String("token"), results.value(QLatin1String("token")).toUInt()}, { QLatin1String("handle_token"), getRequestToken() }};
0415 
0416         QDBusPendingCall pendingCall = QDBusConnection::sessionBus().asyncCall(message);
0417         auto watcher = new QDBusPendingCallWatcher(pendingCall);
0418         connect(watcher, &QDBusPendingCallWatcher::finished, this, [this] (QDBusPendingCallWatcher *watcher) {
0419             QDBusPendingReply<QDBusObjectPath> reply = *watcher;
0420             if (reply.isError()) {
0421                 qWarning() << "Couldn't get reply";
0422                 qWarning() << "Error: " << reply.error().message();
0423             } else {
0424                 QDBusConnection::sessionBus().connect(desktopPortalService(),
0425                                                     reply.value().path(),
0426                                                     portalRequestInterface(),
0427                                                     portalRequestResponse(),
0428                                                     this,
0429                                                     SLOT(gotPrintResponse(uint,QVariantMap)));
0430             }
0431         });
0432     } else {
0433         qWarning() << "Failed to print selected document";
0434     }
0435 }
0436 
0437 void XdgPortalTest::inhibitRequested()
0438 {
0439     QDBusMessage message = QDBusMessage::createMethodCall(desktopPortalService(),
0440                                                           desktopPortalPath(),
0441                                                           QLatin1String("org.freedesktop.portal.Inhibit"),
0442                                                           QLatin1String("Inhibit"));
0443     // flags: 1 (logout) & 2 (user switch) & 4 (suspend) & 8 (idle)
0444     message << parentWindowId() << 8U << QVariantMap({{QLatin1String("reason"), QLatin1String("Testing inhibition")}});
0445 
0446     QDBusPendingCall pendingCall = QDBusConnection::sessionBus().asyncCall(message);
0447     auto watcher = new QDBusPendingCallWatcher(pendingCall);
0448     connect(watcher, &QDBusPendingCallWatcher::finished, this, [this] (QDBusPendingCallWatcher *watcher) {
0449         QDBusPendingReply<QDBusObjectPath> reply = *watcher;
0450         if (reply.isError()) {
0451             qWarning() << "Couldn't get reply";
0452             qWarning() << "Error: " << reply.error().message();
0453         } else {
0454             qWarning() << reply.value().path();
0455             m_mainWindow->inhibitLabel->setText(QLatin1String("Inhibited"));
0456             m_mainWindow->inhibit->setEnabled(false);
0457             m_mainWindow->uninhibit->setEnabled(true);
0458             m_inhibitionRequest = reply.value();
0459         }
0460     });
0461 }
0462 
0463 void XdgPortalTest::uninhibitRequested()
0464 {
0465     QDBusMessage message = QDBusMessage::createMethodCall(desktopPortalService(),
0466                                                           m_inhibitionRequest.path(),
0467                                                           portalRequestInterface(),
0468                                                           QLatin1String("Close"));
0469     QDBusPendingCall pendingCall = QDBusConnection::sessionBus().asyncCall(message);
0470     m_mainWindow->inhibitLabel->setText(QLatin1String("Not inhibited"));
0471     m_mainWindow->inhibit->setEnabled(true);
0472     m_mainWindow->uninhibit->setEnabled(false);
0473     m_inhibitionRequest = QDBusObjectPath();
0474 }
0475 
0476 void XdgPortalTest::printDocument()
0477 {
0478     QDBusMessage message = QDBusMessage::createMethodCall(desktopPortalService(),
0479                                                           desktopPortalPath(),
0480                                                           QLatin1String("org.freedesktop.portal.Print"),
0481                                                           QLatin1String("PreparePrint"));
0482     // TODO add some default configuration to verify it's read/parsed properly
0483     message << parentWindowId() << QLatin1String("Prepare print") << QVariantMap() << QVariantMap() << QVariantMap{ {QLatin1String("handle_token"), getRequestToken()} };
0484 
0485     QDBusPendingCall pendingCall = QDBusConnection::sessionBus().asyncCall(message);
0486     auto watcher = new QDBusPendingCallWatcher(pendingCall);
0487     connect(watcher, &QDBusPendingCallWatcher::finished, this, [this] (QDBusPendingCallWatcher *watcher) {
0488         QDBusPendingReply<QDBusObjectPath> reply = *watcher;
0489         if (reply.isError()) {
0490             qWarning() << "Couldn't get reply";
0491             qWarning() << "Error: " << reply.error().message();
0492         } else {
0493             QDBusConnection::sessionBus().connect(desktopPortalService(),
0494                                                   reply.value().path(),
0495                                                   portalRequestInterface(),
0496                                                   portalRequestResponse(),
0497                                                   this,
0498                                                   SLOT(gotPreparePrintResponse(uint,QVariantMap)));
0499         }
0500     });
0501 }
0502 
0503 void XdgPortalTest::requestDeviceAccess()
0504 {
0505     qWarning() << "Request device access";
0506     const QString device = m_mainWindow->deviceCombobox->currentIndex() == 0 ? QLatin1String("microphone") :
0507                                                                                m_mainWindow->deviceCombobox->currentIndex() == 1 ? QLatin1String("speakers") : QLatin1String("camera");
0508 
0509 
0510     QDBusMessage message = QDBusMessage::createMethodCall(desktopPortalService(),
0511                                                           desktopPortalPath(),
0512                                                           QLatin1String("org.freedesktop.portal.Device"),
0513                                                           QLatin1String("AccessDevice"));
0514     message << (uint)QApplication::applicationPid() << QStringList {device} << QVariantMap();
0515 
0516     QDBusPendingCall pendingCall = QDBusConnection::sessionBus().asyncCall(message);
0517     auto watcher = new QDBusPendingCallWatcher(pendingCall);
0518     connect(watcher, &QDBusPendingCallWatcher::finished, this, [] (QDBusPendingCallWatcher *watcher) {
0519         watcher->deleteLater();
0520         QDBusPendingReply<QDBusObjectPath> reply = *watcher;
0521         if (reply.isError()) {
0522             qWarning() << "Couldn't get reply";
0523             qWarning() << "Error: " << reply.error().message();
0524         } else {
0525             qWarning() << reply.value().path();
0526         }
0527     });
0528 }
0529 
0530 void XdgPortalTest::saveFileRequested()
0531 {
0532     auto fileDialog = new QFileDialog(this);
0533     fileDialog->setAcceptMode(QFileDialog::AcceptSave);
0534     fileDialog->setLabelText(QFileDialog::Accept, QLatin1String("Save (portal)"));
0535     fileDialog->setNameFilters(QStringList { QLatin1String("Fooo (*.txt *.patch)"), QLatin1String("Text (*.doc *.docx)"), QLatin1String("Any file (*)") });
0536     fileDialog->setModal(true);
0537     fileDialog->setDirectory(QStandardPaths::standardLocations(QStandardPaths::ConfigLocation).last());
0538     fileDialog->selectFile(QLatin1String("test.txt"));
0539     fileDialog->setWindowTitle(QLatin1String("Flatpak test - save dialog"));
0540 
0541     if (fileDialog->exec() == QDialog::Accepted) {
0542         if (!fileDialog->selectedFiles().isEmpty()) {
0543             m_mainWindow->selectedFiles->setText(fileDialog->selectedFiles().join(QLatin1String(", ")));
0544         }
0545         fileDialog->deleteLater();
0546     }
0547 }
0548 
0549 void XdgPortalTest::sendNotification()
0550 {
0551     auto notify = new KNotification(QLatin1String("notification"));
0552     connect(m_mainWindow->notifyCloseButton, &QPushButton::clicked, notify, &KNotification::close);
0553     connect(notify, &KNotification::closed, this, [this] () {
0554         m_mainWindow->notifyCloseButton->setDisabled(true);
0555     });
0556 
0557     notify->setFlags(KNotification::DefaultEvent);
0558     notify->setTitle(QLatin1String("Notification test"));
0559     notify->setText(QLatin1String("<html><b>Hello world!!<b><html>"));
0560     KNotificationAction *action1 = notify->addAction(QStringLiteral("Action 1"));
0561     KNotificationAction *action2 = notify->addAction(QStringLiteral("Action 2"));
0562     connect(action1, &KNotificationAction::activated, this, [this, action1] () {
0563         this->notificationActivated(action1->label());
0564     });
0565     connect(action2, &KNotificationAction::activated, this, [this, action2] () {
0566         this->notificationActivated(action2->label());
0567     });
0568     notify->setIconName(QLatin1String("applications-development"));
0569 
0570     m_mainWindow->notifyCloseButton->setEnabled(true);
0571     notify->sendEvent();
0572 }
0573 
0574 void XdgPortalTest::sendNotificationPixmap()
0575 {
0576     auto notify = new KNotification(QLatin1String("notification"));
0577     connect(m_mainWindow->notifyCloseButton, &QPushButton::clicked, notify, &KNotification::close);
0578     connect(notify, &KNotification::closed, this, [this] () {
0579         m_mainWindow->notifyCloseButton->setDisabled(true);
0580     });
0581 
0582     notify->setFlags(KNotification::DefaultEvent);
0583     notify->setTitle(QLatin1String("Notification test"));
0584     notify->setText(QLatin1String("<html><b>Hello world!!<b><html>"));
0585     KNotificationAction *action1 = notify->addAction(QStringLiteral("Action 1"));
0586     KNotificationAction *action2 = notify->addAction(QStringLiteral("Action 2"));
0587     connect(action1, &KNotificationAction::activated, this, [this, action1] () {
0588         this->notificationActivated(action1->label());
0589     });
0590     connect(action2, &KNotificationAction::activated, this, [this, action2] () {
0591         this->notificationActivated(action2->label());
0592     });
0593 
0594     QPixmap pixmap(64, 64);
0595     pixmap.fill(Qt::red);
0596 
0597     notify->setPixmap(pixmap);
0598 
0599     m_mainWindow->notifyCloseButton->setEnabled(true);
0600     notify->sendEvent();
0601 }
0602 
0603 void XdgPortalTest::sendNotificationDefault()
0604 {
0605     auto notify = new KNotification(QLatin1String("notification"));
0606     connect(m_mainWindow->notifyCloseButton, &QPushButton::clicked, notify, &KNotification::close);
0607     connect(notify, &KNotification::closed, this, [this] () {
0608         m_mainWindow->notifyCloseButton->setDisabled(true);
0609     });
0610 
0611     notify->setFlags(KNotification::DefaultEvent);
0612     notify->setTitle(QLatin1String("Notification test"));
0613     notify->setText(QLatin1String("<html><b>Hello world!!<b><html>"));
0614     KNotificationAction *action1 = notify->addAction(QStringLiteral("Action 1"));
0615     KNotificationAction *action2 = notify->addAction(QStringLiteral("Action 2"));
0616     KNotificationAction *actionDefault = notify->addDefaultAction(QStringLiteral("Default action"));
0617     connect(action1, &KNotificationAction::activated, this, [this, action1] () {
0618         this->notificationActivated(action1->label());
0619     });
0620     connect(action2, &KNotificationAction::activated, this, [this, action2] () {
0621         this->notificationActivated(action2->label());
0622     });
0623     connect(actionDefault, &KNotificationAction::activated, this, [this, actionDefault] () {
0624         this->notificationActivated(actionDefault->label());
0625     });
0626 
0627     m_mainWindow->notifyCloseButton->setEnabled(true);
0628     notify->sendEvent();
0629 }
0630 
0631 void XdgPortalTest::requestScreenSharing()
0632 {
0633     QDBusMessage message = QDBusMessage::createMethodCall(desktopPortalService(),
0634                                                           desktopPortalPath(),
0635                                                           QLatin1String("org.freedesktop.portal.ScreenCast"),
0636                                                           QLatin1String("CreateSession"));
0637 
0638     message << QVariantMap { { QLatin1String("session_handle_token"), getSessionToken() }, { QLatin1String("handle_token"), getRequestToken() } };
0639 
0640     QDBusPendingCall pendingCall = QDBusConnection::sessionBus().asyncCall(message);
0641     auto watcher = new QDBusPendingCallWatcher(pendingCall);
0642     connect(watcher, &QDBusPendingCallWatcher::finished, this, [this] (QDBusPendingCallWatcher *watcher) {
0643         QDBusPendingReply<QDBusObjectPath> reply = *watcher;
0644         if (reply.isError()) {
0645             qWarning() << "Couldn't get reply";
0646             qWarning() << "Error: " << reply.error().message();
0647         } else {
0648             QDBusConnection::sessionBus().connect(desktopPortalService(),
0649                                                 reply.value().path(),
0650                                                 portalRequestInterface(),
0651                                                 portalRequestResponse(),
0652                                                 this,
0653                                                 SLOT(gotCreateSessionResponse(uint,QVariantMap)));
0654         }
0655     });
0656 }
0657 
0658 void XdgPortalTest::requestScreenshot()
0659 {
0660     QDBusMessage message = QDBusMessage::createMethodCall(desktopPortalService(),
0661                                                           desktopPortalPath(),
0662                                                           QLatin1String("org.freedesktop.portal.Screenshot"),
0663                                                           QLatin1String("Screenshot"));
0664     // TODO add some default configuration to verify it's read/parsed properly
0665     message << parentWindowId() << QVariantMap{{QLatin1String("interactive"), true}, {QLatin1String("handle_token"), getRequestToken()}};
0666 
0667     QDBusPendingCall pendingCall = QDBusConnection::sessionBus().asyncCall(message);
0668     auto watcher = new QDBusPendingCallWatcher(pendingCall);
0669     connect(watcher, &QDBusPendingCallWatcher::finished, this, [this] (QDBusPendingCallWatcher *watcher) {
0670         QDBusPendingReply<QDBusObjectPath> reply = *watcher;
0671         if (reply.isError()) {
0672             qWarning() << "Couldn't get reply";
0673             qWarning() << "Error: " << reply.error().message();
0674         } else {
0675             QDBusConnection::sessionBus().connect(desktopPortalService(),
0676                                                   reply.value().path(),
0677                                                   portalRequestInterface(),
0678                                                   portalRequestResponse(),
0679                                                   this,
0680                                                   SLOT(gotScreenshotResponse(uint,QVariantMap)));
0681         }
0682     });
0683 }
0684 
0685 void XdgPortalTest::requestAccount()
0686 {
0687     QDBusMessage message = QDBusMessage::createMethodCall(desktopPortalService(),
0688                                                           desktopPortalPath(),
0689                                                           QLatin1String("org.freedesktop.portal.Account"),
0690                                                           QLatin1String("GetUserInformation"));
0691     // TODO add some default configuration to verify it's read/parsed properly
0692     message << parentWindowId() << QVariantMap{{QLatin1String("interactive"), true}, {QLatin1String("handle_token"), getRequestToken()}};
0693 
0694     QDBusPendingCall pendingCall = QDBusConnection::sessionBus().asyncCall(message);
0695     auto watcher = new QDBusPendingCallWatcher(pendingCall);
0696     connect(watcher, &QDBusPendingCallWatcher::finished, this, [this] (QDBusPendingCallWatcher *watcher) {
0697         QDBusPendingReply<QDBusObjectPath> reply = *watcher;
0698         if (reply.isError()) {
0699             qWarning() << "Couldn't get reply";
0700             qWarning() << "Error: " << reply.error().message();
0701         } else {
0702             QDBusConnection::sessionBus().connect(desktopPortalService(),
0703                                                   reply.value().path(),
0704                                                   portalRequestInterface(),
0705                                                   portalRequestResponse(),
0706                                                   this,
0707                                                   SLOT(gotAccountResponse(uint,QVariantMap)));
0708         }
0709     });
0710 }
0711 
0712 void XdgPortalTest::gotCreateSessionResponse(uint response, const QVariantMap &results)
0713 {
0714     if (response != 0) {
0715         qWarning() << "Failed to create session: " << response;
0716         return;
0717     }
0718 
0719     QDBusMessage message = QDBusMessage::createMethodCall(desktopPortalService(),
0720                                                           desktopPortalPath(),
0721                                                           QLatin1String("org.freedesktop.portal.ScreenCast"),
0722                                                           QLatin1String("SelectSources"));
0723 
0724     m_session = results.value(QLatin1String("session_handle")).toString();
0725 
0726     message << QVariant::fromValue(QDBusObjectPath(m_session))
0727             << QVariantMap { { QLatin1String("multiple"), false},
0728                              { QLatin1String("types"), (uint)m_mainWindow->screenShareCombobox->currentIndex() + 1},
0729                              { QLatin1String("handle_token"), getRequestToken() } };
0730 
0731     QDBusPendingCall pendingCall = QDBusConnection::sessionBus().asyncCall(message);
0732     auto watcher = new QDBusPendingCallWatcher(pendingCall);
0733     connect(watcher, &QDBusPendingCallWatcher::finished, this, [this] (QDBusPendingCallWatcher *watcher) {
0734         QDBusPendingReply<QDBusObjectPath> reply = *watcher;
0735         if (reply.isError()) {
0736             qWarning() << "Couldn't get reply";
0737             qWarning() << "Error: " << reply.error().message();
0738         } else {
0739             QDBusConnection::sessionBus().connect(desktopPortalService(),
0740                                                 reply.value().path(),
0741                                                 portalRequestInterface(),
0742                                                 portalRequestResponse(),
0743                                                 this,
0744                                                 SLOT(gotSelectSourcesResponse(uint,QVariantMap)));
0745         }
0746     });
0747 }
0748 
0749 void XdgPortalTest::gotSelectSourcesResponse(uint response, const QVariantMap &results)
0750 {
0751     if (response != 0) {
0752         qWarning() << "Failed to select sources: " << response;
0753         return;
0754     }
0755 
0756     QDBusMessage message = QDBusMessage::createMethodCall(desktopPortalService(),
0757                                                           desktopPortalPath(),
0758                                                           QLatin1String("org.freedesktop.portal.ScreenCast"),
0759                                                           QLatin1String("Start"));
0760 
0761     message << QVariant::fromValue(QDBusObjectPath(m_session))
0762             << parentWindowId()
0763             << QVariantMap { { QLatin1String("handle_token"), getRequestToken() } };
0764 
0765     QDBusPendingCall pendingCall = QDBusConnection::sessionBus().asyncCall(message);
0766     auto watcher = new QDBusPendingCallWatcher(pendingCall);
0767     connect(watcher, &QDBusPendingCallWatcher::finished, this, [this] (QDBusPendingCallWatcher *watcher) {
0768         QDBusPendingReply<QDBusObjectPath> reply = *watcher;
0769         if (reply.isError()) {
0770             qWarning() << "Couldn't get reply";
0771             qWarning() << "Error: " << reply.error().message();
0772         } else {
0773             QDBusConnection::sessionBus().connect(desktopPortalService(),
0774                                                 reply.value().path(),
0775                                                 portalRequestInterface(),
0776                                                 portalRequestResponse(),
0777                                                 this,
0778                                                 SLOT(gotStartResponse(uint,QVariantMap)));
0779         }
0780     });
0781 }
0782 
0783 void XdgPortalTest::gotStartResponse(uint response, const QVariantMap &results)
0784 {
0785     if (response != 0) {
0786         qWarning() << "Failed to start: " << response;
0787     }
0788 
0789     Streams streams = qdbus_cast<Streams>(results.value(QLatin1String("streams")));
0790     for (const auto &stream : streams) {
0791         QDBusMessage message = QDBusMessage::createMethodCall(desktopPortalService(),
0792                                                               desktopPortalPath(),
0793                                                               QLatin1String("org.freedesktop.portal.ScreenCast"),
0794                                                               QLatin1String("OpenPipeWireRemote"));
0795 
0796         message << QVariant::fromValue(QDBusObjectPath(m_session)) << QVariantMap();
0797 
0798         QDBusPendingCall pendingCall = QDBusConnection::sessionBus().asyncCall(message);
0799         pendingCall.waitForFinished();
0800         QDBusPendingReply<QDBusUnixFileDescriptor> reply = pendingCall.reply();
0801         if (reply.isError()) {
0802             qWarning() << "Failed to get fd for node_id " << stream.node_id;
0803         }
0804 
0805         QString gstLaunch = QString("pipewiresrc fd=%1 path=%2 ! videoconvert ! xvimagesink").arg(reply.value().fileDescriptor()).arg(stream.node_id);
0806         GstElement *element = gst_parse_launch(gstLaunch.toUtf8(), nullptr);
0807         gst_element_set_state(element, GST_STATE_PLAYING);
0808     }
0809 }
0810 
0811 void XdgPortalTest::gotScreenshotResponse(uint response, const QVariantMap& results)
0812 {
0813     qWarning() << "Screenshot response: " << response << results;
0814     if (!response) {
0815         if (results.contains(QLatin1String("uri"))) {
0816             QDesktopServices::openUrl(QUrl::fromLocalFile(results.value(QLatin1String("uri")).toString()));
0817         }
0818     } else {
0819         qWarning() << "Failed to take screenshot";
0820     }
0821 }
0822 
0823 void XdgPortalTest::gotAccountResponse(uint response, const QVariantMap& results)
0824 {
0825     qWarning() << "Account response: " << response << results;
0826     if (!response) {
0827         QString resultsString = QStringLiteral("Response is:\n");
0828         const auto resultKeys = results.keys();
0829         for (const auto &key : resultKeys) {
0830             resultsString += "    " + key + ": " + results.value(key).toString() + "\n";
0831         }
0832         m_mainWindow->accountResultsLabel->setText(resultsString);
0833     } else {
0834         qWarning() << "Failed to get account information";
0835     }
0836 }
0837 
0838 bool XdgPortalTest::isRunningSandbox()
0839 {
0840     QString runtimeDir = qgetenv("XDG_RUNTIME_DIR");
0841 
0842     if (runtimeDir.isEmpty()) {
0843         return false;
0844     }
0845 
0846     QFile file(runtimeDir + QLatin1String("/flatpak-info"));
0847 
0848     return file.exists();
0849 }
0850 
0851 QString XdgPortalTest::getSessionToken()
0852 {
0853     m_sessionTokenCounter += 1;
0854     return QString("u%1").arg(m_sessionTokenCounter);
0855 }
0856 
0857 QString XdgPortalTest::getRequestToken()
0858 {
0859     m_requestTokenCounter += 1;
0860     return QString("u%1").arg(m_requestTokenCounter);
0861 }
0862 
0863 void XdgPortalTest::chooseApplication()
0864 {
0865     QDBusMessage message = QDBusMessage::createMethodCall(desktopPortalService(),
0866                                                           desktopPortalPath(),
0867                                                           QStringLiteral("org.freedesktop.portal.OpenURI"),
0868                                                           QStringLiteral("OpenURI"));
0869 
0870     message << parentWindowId() << QStringLiteral("https://kde.org") << QVariantMap{{QStringLiteral("ask"), true}};
0871 
0872     QDBusPendingCall pendingCall = QDBusConnection::sessionBus().asyncCall(message);
0873     auto watcher = new QDBusPendingCallWatcher(pendingCall, this);
0874     connect(watcher, &QDBusPendingCallWatcher::finished, this, [this](QDBusPendingCallWatcher *watcher) {
0875         watcher->deleteLater();
0876         QDBusPendingReply<QDBusObjectPath> reply = *watcher;
0877         if (reply.isError()) {
0878             qWarning() << "Couldn't get reply";
0879             qWarning() << "Error: " << reply.error().message();
0880         } else {
0881             QDBusConnection::sessionBus().connect(desktopPortalService(),
0882                                                   reply.value().path(),
0883                                                   portalRequestInterface(),
0884                                                   portalRequestResponse(),
0885                                                   this,
0886                                                   SLOT(gotApplicationChoice(uint,QVariantMap)));
0887         }
0888     });
0889 }
0890 
0891 void XdgPortalTest::gotApplicationChoice(uint response, const QVariantMap &results)
0892 {
0893     qDebug() << response << results;
0894 }
0895 
0896 void XdgPortalTest::addLauncher()
0897 {
0898     qDebug() << getSessionToken() << getRequestToken();
0899     QDBusMessage message = QDBusMessage::createMethodCall(desktopPortalService(),
0900                                                           desktopPortalPath(),
0901                                                           QLatin1String("org.freedesktop.portal.DynamicLauncher"),
0902                                                           QLatin1String("PrepareInstall"));
0903 
0904     QBuffer buffer;
0905     static constexpr auto maxSize = 512;
0906     QIcon::fromTheme("utilities-terminal").pixmap(maxSize, maxSize).save(&buffer,"PNG");
0907     PortalIcon icon {QStringLiteral("bytes"), QDBusVariant(buffer.buffer())};
0908 
0909     message << parentWindowId() << QStringLiteral("Patschen")
0910             << QVariant::fromValue(QDBusVariant(QVariant::fromValue(icon)))
0911             << QVariantMap {{QStringLiteral("launcher_type"), 2U},
0912                             {QStringLiteral("target"), QStringLiteral("https://kde.org")},
0913                             {QStringLiteral("editable_icon"), true}};
0914 
0915     QDBusPendingCall pendingCall = QDBusConnection::sessionBus().asyncCall(message);
0916     auto watcher = new QDBusPendingCallWatcher(pendingCall, this);
0917     connect(watcher, &QDBusPendingCallWatcher::finished, this, [this] (QDBusPendingCallWatcher *watcher) {
0918         watcher->deleteLater();
0919         QDBusPendingReply<QDBusObjectPath> reply = *watcher;
0920         if (reply.isError()) {
0921             qWarning() << "Couldn't get reply";
0922             qWarning() << "Error: " << reply.error().message();
0923         } else {
0924             QDBusConnection::sessionBus().connect(desktopPortalService(),
0925                                                   reply.value().path(),
0926                                                   portalRequestInterface(),
0927                                                   portalRequestResponse(),
0928                                                   this,
0929                                                   SLOT(gotLauncher(uint,QVariantMap)));
0930         }
0931     });
0932 }
0933 
0934 void XdgPortalTest::gotLauncher(uint response, const QVariantMap &results)
0935 {
0936     qDebug() << response << results;
0937 
0938     QDBusMessage message = QDBusMessage::createMethodCall(desktopPortalService(),
0939                                                           desktopPortalPath(),
0940                                                           QLatin1String("org.freedesktop.portal.DynamicLauncher"),
0941                                                           QLatin1String("Install"));
0942 
0943     QFile desktopFile(":/data/patschen.desktop");
0944     Q_ASSERT(desktopFile.open(QFile::ReadOnly));
0945     auto data = desktopFile.readAll();
0946 
0947     message << results.value(QStringLiteral("token")) << QStringLiteral("org.kde.xdg-portal-test-kde.patschen.desktop")
0948             << QString::fromUtf8(data) << QVariantMap {};
0949 
0950     QDBusPendingCall pendingCall = QDBusConnection::sessionBus().asyncCall(message);
0951     auto watcher = new QDBusPendingCallWatcher(pendingCall, this);
0952     connect(watcher, &QDBusPendingCallWatcher::finished, this, [](QDBusPendingCallWatcher *watcher) {
0953         watcher->deleteLater();
0954         QDBusPendingReply<QDBusObjectPath> reply = *watcher;
0955         if (reply.isError()) {
0956             qWarning() << "Couldn't get reply";
0957             qWarning() << "Error: " << reply.error().message();
0958         }
0959     });
0960 }
0961 
0962 void XdgPortalTest::removeLauncher()
0963 {
0964     QDBusMessage message = QDBusMessage::createMethodCall(desktopPortalService(),
0965                                                           desktopPortalPath(),
0966                                                           QLatin1String("org.freedesktop.portal.DynamicLauncher"),
0967                                                           QLatin1String("Uninstall"));
0968 
0969 
0970     message << QStringLiteral("org.kde.xdg-portal-test-kde.patschen.desktop") << QVariantMap {};
0971 
0972     QDBusPendingCall pendingCall = QDBusConnection::sessionBus().asyncCall(message);
0973     auto watcher = new QDBusPendingCallWatcher(pendingCall, this);
0974     connect(watcher, &QDBusPendingCallWatcher::finished, this, [](QDBusPendingCallWatcher *watcher) {
0975         watcher->deleteLater();
0976         QDBusPendingReply<QDBusObjectPath> reply = *watcher;
0977         if (reply.isError()) {
0978             qWarning() << "Couldn't get reply";
0979             qWarning() << "Error: " << reply.error().message();
0980         }
0981     });
0982 }
0983 
0984 void XdgPortalTest::gotGlobalShortcutsCreateSessionResponse(uint res, const QVariantMap& results)
0985 {
0986     if (res != 0) {
0987         qWarning() << "failed to create a global shortcuts session" << res << results;
0988         return;
0989     }
0990 
0991     m_globalShortcutsSession = QDBusObjectPath(results["session_handle"].toString());
0992 
0993     auto reply = m_shortcuts->ListShortcuts(m_globalShortcutsSession, {});
0994     reply.waitForFinished();
0995     if (reply.isError()) {
0996         qWarning() << "failed to call ListShortcuts" << reply.error();
0997         return;
0998     }
0999 
1000     auto req = new OrgFreedesktopPortalRequestInterface(QLatin1String("org.freedesktop.portal.Desktop"),
1001                                                         reply.value().path(), QDBusConnection::sessionBus(), this);
1002 
1003     // BindShortcuts and ListShortcuts answer the same
1004     connect(req, &OrgFreedesktopPortalRequestInterface::Response, this, &XdgPortalTest::gotListShortcutsResponse);
1005     connect(req, &OrgFreedesktopPortalRequestInterface::Response, req, &QObject::deleteLater);
1006 }
1007 
1008 void XdgPortalTest::gotListShortcutsResponse(uint code, const QVariantMap& results)
1009 {
1010     if (code != 0) {
1011         qDebug() << "failed to get the list of shortcuts" << code << results;
1012         return;
1013     }
1014 
1015     if (!results.contains("shortcuts")) {
1016         qWarning() << "no shortcuts reply" << results;
1017         return;
1018     }
1019 
1020     Shortcuts s;
1021     const auto arg = results["shortcuts"].value<QDBusArgument>();
1022     arg >> s;
1023     QString desc;
1024     for (auto it = s.cbegin(), itEnd = s.cend(); it != itEnd; ++it) {
1025         desc += i18n("%1: %2 %3", it->first, it->second["description"].toString(), it->second["trigger_description"].toString());
1026     }
1027     m_mainWindow->shortcutsDescriptions->setText(desc);
1028 }
1029 
1030 void XdgPortalTest::configureShortcuts()
1031 {
1032     auto reply = m_shortcuts->BindShortcuts(m_globalShortcutsSession, {},  parentWindowId(), { { "handle_token", getRequestToken() } });
1033     reply.waitForFinished();
1034     if (reply.isError()) {
1035         qWarning() << "failed to call BindShortcuts" << reply.error();
1036         return;
1037     }
1038 
1039     auto req = new OrgFreedesktopPortalRequestInterface(QLatin1String("org.freedesktop.portal.Desktop"),
1040                                                         reply.value().path(), QDBusConnection::sessionBus(), this);
1041 
1042     // BindShortcuts and ListShortcuts answer the same
1043     connect(req, &OrgFreedesktopPortalRequestInterface::Response, this, &XdgPortalTest::gotListShortcutsResponse);
1044 }