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 }