File indexing completed on 2024-07-21 05:08:49

0001 // SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0002 // SPDX-FileCopyrightText: 2022-2023 Harald Sitter <sitter@kde.org>
0003 
0004 #include <chrono>
0005 
0006 #include <QDebug>
0007 #include <QGuiApplication>
0008 #include <QJsonDocument>
0009 #include <QJsonObject>
0010 #include <QTimer>
0011 
0012 #include <KWayland/Client/connection_thread.h>
0013 #include <KWayland/Client/plasmawindowmanagement.h>
0014 #include <KWayland/Client/registry.h>
0015 
0016 #include <KWindowInfo>
0017 #include <KWindowSystem>
0018 #include <KX11Extras>
0019 
0020 using namespace std::chrono_literals;
0021 
0022 class WaylandLister : public QObject
0023 {
0024     Q_OBJECT
0025 public:
0026     explicit WaylandLister(QObject *parent = nullptr)
0027         : QObject(parent)
0028     {
0029         m_connection.reset(KWayland::Client::ConnectionThread::fromApplication());
0030         if (!m_connection) {
0031             qWarning() << "no connection";
0032             return;
0033         }
0034 
0035         m_registry.create(m_connection.get());
0036 
0037         QObject::connect(&m_registry,
0038                          &KWayland::Client::Registry::plasmaWindowManagementAnnounced,
0039                          this,
0040                          [this](quint32 name, quint32 version) {
0041                              m_windowManagement.reset(m_registry.createPlasmaWindowManagement(name, version));
0042                          });
0043 
0044         m_registry.setup();
0045 
0046         // We'll need 3 because getting the registry is async, getting the window management interface is another,
0047         // then we'll have requested information about every window. By the 3rd sync it should have sent everything.
0048         static constexpr auto syncTimes = 3;
0049         for (auto i = 0; i < syncTimes; i++) {
0050             QCoreApplication::processEvents();
0051             m_connection->roundtrip();
0052             QCoreApplication::processEvents();
0053         }
0054         QCoreApplication::processEvents();
0055         Q_ASSERT(m_windowManagement);
0056         const auto windows = m_windowManagement->windows();
0057         for (const auto &window : windows) {
0058             insert(window);
0059         }
0060     }
0061 
0062     void insert(KWayland::Client::PlasmaWindow *window)
0063     {
0064         m_pidsToAppIds.insert(QString::number(window->pid()), window->appId());
0065     }
0066 
0067     QVariantHash data() const
0068     {
0069         return m_pidsToAppIds;
0070     }
0071 
0072 private:
0073     QVariantHash m_pidsToAppIds;
0074     std::unique_ptr<KWayland::Client::ConnectionThread> m_connection;
0075     KWayland::Client::Registry m_registry;
0076     std::unique_ptr<KWayland::Client::PlasmaWindowManagement> m_windowManagement;
0077 };
0078 
0079 QVariantHash waylandPidsToAppIds()
0080 {
0081     WaylandLister lister;
0082     return lister.data();
0083 }
0084 
0085 QVariantHash x11PidsToAppIds()
0086 {
0087     QVariantHash pidsToAppIds;
0088     const auto wids = KX11Extras::windows();
0089     for (const auto &wid : wids) {
0090         const KWindowInfo info(wid, NET::WMPid, NET::WM2DesktopFileName | NET::WM2GTKApplicationId);
0091         if (!info.desktopFileName().isEmpty()) {
0092             pidsToAppIds.insert(QString::number(info.pid()), info.desktopFileName());
0093         }
0094         if (!info.gtkApplicationId().isEmpty()) {
0095             pidsToAppIds.insert(QString::number(info.pid()), info.gtkApplicationId());
0096         }
0097     }
0098     return pidsToAppIds;
0099 }
0100 
0101 int main(int argc, char **argv)
0102 {
0103     const QGuiApplication app(argc, argv);
0104 
0105     QVariantHash pidsToAppIds;
0106 
0107     if (KWindowSystem::isPlatformX11()) {
0108         pidsToAppIds.insert(x11PidsToAppIds());
0109     } else if (KWindowSystem::isPlatformWayland()) {
0110         pidsToAppIds.insert(waylandPidsToAppIds());
0111     } else {
0112         qFatal("unsupported platform");
0113         return 1;
0114     }
0115 
0116     // Always append .desktop for convenience. Means we can do straight forward string matching in the python side.
0117     for (auto it = pidsToAppIds.begin(); it != pidsToAppIds.end(); ++it) {
0118         static const QLatin1String suffix(".desktop");
0119         const QString value = it.value().toString();
0120         if (!value.endsWith(suffix)) {
0121             it->setValue(QString(value + suffix));
0122         }
0123     }
0124 
0125     const QJsonDocument doc(QJsonObject::fromVariantHash(pidsToAppIds));
0126     printf("%s\n", doc.toJson().constData());
0127     return 0;
0128 }
0129 
0130 #include "main.moc"