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 }