File indexing completed on 2024-11-10 04:56:46

0001 /*
0002     SPDX-FileCopyrightText: 2003 Lubos Lunak <l.lunak@kde.org>
0003     SPDX-FileCopyrightText: 2023 Kai Uwe Broulik <kde@broulik.de>
0004 
0005     SPDX-License-Identifier: MIT
0006 
0007 */
0008 
0009 #include <KAuth/Action>
0010 #include <KIconUtils>
0011 #include <KLocalizedString>
0012 #include <KMessageBox>
0013 #include <KMessageDialog>
0014 #include <KService>
0015 
0016 #include <QApplication>
0017 #include <QCommandLineParser>
0018 #include <QDebug>
0019 #include <QProcess>
0020 #include <QWaylandClientExtensionTemplate>
0021 #include <QWindow>
0022 
0023 #include <qpa/qplatformwindow_p.h>
0024 
0025 #include <private/qtx11extras_p.h>
0026 #include <xcb/xcb.h>
0027 
0028 #include <cerrno>
0029 #include <csignal>
0030 #include <memory>
0031 
0032 #include "qwayland-xdg-foreign-unstable-v2.h"
0033 
0034 class XdgImported : public QtWayland::zxdg_imported_v2
0035 {
0036 public:
0037     XdgImported(::zxdg_imported_v2 *object)
0038         : QtWayland::zxdg_imported_v2(object)
0039     {
0040     }
0041     ~XdgImported() override
0042     {
0043         destroy();
0044     }
0045 };
0046 
0047 class XdgImporter : public QWaylandClientExtensionTemplate<XdgImporter>, public QtWayland::zxdg_importer_v2
0048 {
0049 public:
0050     XdgImporter()
0051         : QWaylandClientExtensionTemplate(1)
0052     {
0053         initialize();
0054     }
0055     ~XdgImporter() override
0056     {
0057         if (isActive()) {
0058             destroy();
0059         }
0060     }
0061     XdgImported *import(const QString &handle)
0062     {
0063         return new XdgImported(import_toplevel(handle));
0064     }
0065 };
0066 
0067 int main(int argc, char *argv[])
0068 {
0069     KLocalizedString::setApplicationDomain(QByteArrayLiteral("kwin"));
0070     QApplication app(argc, argv);
0071     QApplication::setWindowIcon(QIcon::fromTheme(QStringLiteral("tools-report-bug")));
0072     QCoreApplication::setApplicationName(QStringLiteral("kwin_killer_helper"));
0073     QCoreApplication::setOrganizationDomain(QStringLiteral("kde.org"));
0074     QApplication::setApplicationDisplayName(i18n("Window Manager"));
0075     QCoreApplication::setApplicationVersion(QStringLiteral("1.0"));
0076     QApplication::setDesktopFileName(QStringLiteral("org.kde.kwin.killer"));
0077 
0078     QCommandLineOption pidOption(QStringLiteral("pid"),
0079                                  i18n("PID of the application to terminate"), i18n("pid"));
0080     QCommandLineOption hostNameOption(QStringLiteral("hostname"),
0081                                       i18n("Hostname on which the application is running"), i18n("hostname"));
0082     QCommandLineOption windowNameOption(QStringLiteral("windowname"),
0083                                         i18n("Caption of the window to be terminated"), i18n("caption"));
0084     QCommandLineOption applicationNameOption(QStringLiteral("applicationname"),
0085                                              i18n("Name of the application to be terminated"), i18n("name"));
0086     QCommandLineOption widOption(QStringLiteral("wid"),
0087                                  i18n("ID of resource belonging to the application"), i18n("id"));
0088     QCommandLineOption timestampOption(QStringLiteral("timestamp"),
0089                                        i18n("Time of user action causing termination"), i18n("time"));
0090     QCommandLineParser parser;
0091     parser.setApplicationDescription(i18n("KWin helper utility"));
0092     parser.addHelpOption();
0093     parser.addVersionOption();
0094 
0095     parser.addOption(pidOption);
0096     parser.addOption(hostNameOption);
0097     parser.addOption(windowNameOption);
0098     parser.addOption(applicationNameOption);
0099     parser.addOption(widOption);
0100     parser.addOption(timestampOption);
0101 
0102     parser.process(app);
0103 
0104     const bool isX11 = app.platformName() == QLatin1String("xcb");
0105 
0106     QString hostname = parser.value(hostNameOption);
0107     bool pid_ok = false;
0108     pid_t pid = parser.value(pidOption).toULong(&pid_ok);
0109     QString caption = parser.value(windowNameOption);
0110     QString appname = parser.value(applicationNameOption);
0111     bool id_ok = false;
0112     xcb_window_t wid = XCB_WINDOW_NONE;
0113     QString windowHandle;
0114     if (isX11) {
0115         wid = parser.value(widOption).toULong(&id_ok);
0116     } else {
0117         windowHandle = parser.value(widOption);
0118     }
0119 
0120     // on Wayland XDG_ACTIVATION_TOKEN is set in the environment.
0121     bool time_ok = false;
0122     xcb_timestamp_t timestamp = parser.value(timestampOption).toULong(&time_ok);
0123 
0124     if (!pid_ok || pid == 0 || ((!id_ok || wid == XCB_WINDOW_NONE) && windowHandle.isEmpty())
0125         || (isX11 && (!time_ok || timestamp == XCB_CURRENT_TIME))
0126         || hostname.isEmpty() || caption.isEmpty() || appname.isEmpty()) {
0127         fprintf(stdout, "%s\n", qPrintable(i18n("This helper utility is not supposed to be called directly.")));
0128         parser.showHelp(1);
0129     }
0130     bool isLocal = hostname == QStringLiteral("localhost");
0131 
0132     const auto service = KService::serviceByDesktopName(appname);
0133     if (service) {
0134         appname = service->name();
0135         QApplication::setApplicationDisplayName(appname);
0136     }
0137 
0138     // Drop redundant application name, cf. QXcbWindow::setWindowTitle.
0139     const QString titleSeparator = QString::fromUtf8(" \xe2\x80\x94 "); // // U+2014, EM DASH
0140     caption.remove(titleSeparator + appname);
0141     caption.remove(QStringLiteral(" – ") + appname); // EN dash (Firefox)
0142     caption.remove(QStringLiteral(" - ") + appname); // classic minus :-)
0143 
0144     caption = caption.toHtmlEscaped();
0145     appname = appname.toHtmlEscaped();
0146     hostname = hostname.toHtmlEscaped();
0147     QString pidString = QString::number(pid); // format pid ourself as it does not make sense to format an ID according to locale settings
0148 
0149     QString question = (caption == appname) ? xi18nc("@info", "<para><application>%1</application> is not responding. Do you want to terminate this application?</para>",
0150                                                      appname)
0151                                             : xi18nc("@info \"window title\" of application name is not responding.", "<para>\"%1\" of <application>%2</application> is not responding. Do you want to terminate this application?</para>",
0152                                                      caption, appname);
0153     question += xi18nc("@info",
0154                        "<para><emphasis strong='true'>Terminating this application will close all of its windows. Any unsaved data will be lost.</emphasis></para>");
0155 
0156     KGuiItem continueButton = KGuiItem(i18nc("@action:button Terminate app", "&Terminate %1", appname), QStringLiteral("application-exit"));
0157     KGuiItem cancelButton = KGuiItem(i18nc("@action:button Wait for frozen app to maybe respond again", "&Wait Longer"), QStringLiteral("chronometer"));
0158 
0159     if (isX11) {
0160         QX11Info::setAppUserTime(timestamp);
0161     }
0162 
0163     auto *dialog = new KMessageDialog(KMessageDialog::WarningContinueCancel, question);
0164     dialog->setAttribute(Qt::WA_DeleteOnClose);
0165     dialog->setCaption(i18nc("@title:window", "Not Responding"));
0166 
0167     QIcon icon;
0168     if (service) {
0169         const QIcon appIcon = QIcon::fromTheme(service->icon());
0170         if (!appIcon.isNull()) {
0171             // emblem-warning is non-standard, fall back to emblem-important if necessary.
0172             const QIcon warningBadge = QIcon::fromTheme(QStringLiteral("emblem-warning"), QIcon::fromTheme(QStringLiteral("emblem-important")));
0173 
0174             icon = KIconUtils::addOverlay(appIcon, warningBadge, qApp->isRightToLeft() ? Qt::BottomLeftCorner : Qt::BottomRightCorner);
0175         }
0176     }
0177     dialog->setIcon(icon); // null icon will result in default warning icon.
0178     dialog->setButtons(continueButton, KGuiItem(), cancelButton);
0179 
0180     QStringList details{
0181         i18nc("@info", "Process ID: %1", pidString)};
0182     if (!isLocal) {
0183         details << i18nc("@info", "Host name: %1", hostname);
0184     }
0185     dialog->setDetails(details.join(QLatin1Char('\n')));
0186     dialog->winId();
0187 
0188     std::unique_ptr<XdgImporter> xdgImporter;
0189     std::unique_ptr<XdgImported> importedParent;
0190 
0191     if (isX11) {
0192         if (QWindow *foreignParent = QWindow::fromWinId(wid)) {
0193             dialog->windowHandle()->setTransientParent(foreignParent);
0194         }
0195     } else {
0196         xdgImporter = std::make_unique<XdgImporter>();
0197     }
0198 
0199     QObject::connect(dialog, &QDialog::finished, &app, [pid, hostname, isLocal](int result) {
0200         if (result == KMessageBox::PrimaryAction) {
0201             if (!isLocal) {
0202                 QStringList lst;
0203                 lst << hostname << QStringLiteral("kill") << QString::number(pid);
0204                 QProcess::startDetached(QStringLiteral("xon"), lst);
0205             } else {
0206                 if (::kill(pid, SIGKILL) && errno == EPERM) {
0207                     KAuth::Action killer(QStringLiteral("org.kde.ksysguard.processlisthelper.sendsignal"));
0208                     killer.setHelperId(QStringLiteral("org.kde.ksysguard.processlisthelper"));
0209                     killer.addArgument(QStringLiteral("pid0"), pid);
0210                     killer.addArgument(QStringLiteral("pidcount"), 1);
0211                     killer.addArgument(QStringLiteral("signal"), SIGKILL);
0212                     if (killer.isValid()) {
0213                         qDebug() << "Using KAuth to kill pid: " << pid;
0214                         killer.execute();
0215                     } else {
0216                         qDebug() << "KWin process killer action not valid";
0217                     }
0218                 }
0219             }
0220         }
0221 
0222         qApp->quit();
0223     });
0224 
0225     dialog->show();
0226 
0227     if (xdgImporter) {
0228         if (auto *waylandWindow = dialog->windowHandle()->nativeInterface<QNativeInterface::Private::QWaylandWindow>()) {
0229             importedParent.reset(xdgImporter->import(windowHandle));
0230             if (auto *surface = waylandWindow->surface()) {
0231                 importedParent->set_parent_of(surface);
0232             }
0233         }
0234     }
0235 
0236     dialog->windowHandle()->requestActivate();
0237 
0238     return app.exec();
0239 }