KWin
Loading...
Searching...
No Matches
killer.cpp
Go to the documentation of this file.
1/*
2 SPDX-FileCopyrightText: 2003 Lubos Lunak <l.lunak@kde.org>
3 SPDX-FileCopyrightText: 2023 Kai Uwe Broulik <kde@broulik.de>
4
5 SPDX-License-Identifier: MIT
6
7*/
8
9#include <KAuth/Action>
10#include <KIconUtils>
11#include <KLocalizedString>
12#include <KMessageBox>
13#include <KMessageDialog>
14#include <KService>
15
16#include <QApplication>
17#include <QCommandLineParser>
18#include <QDebug>
19#include <QProcess>
20#include <QWaylandClientExtensionTemplate>
21#include <QWindow>
22
23#include <qpa/qplatformwindow_p.h>
24
25#include <private/qtx11extras_p.h>
26#include <xcb/xcb.h>
27
28#include <cerrno>
29#include <csignal>
30#include <memory>
31
32#include "qwayland-xdg-foreign-unstable-v2.h"
33
34class XdgImported : public QtWayland::zxdg_imported_v2
35{
36public:
37 XdgImported(::zxdg_imported_v2 *object)
38 : QtWayland::zxdg_imported_v2(object)
39 {
40 }
41 ~XdgImported() override
42 {
43 destroy();
44 }
45};
46
47class XdgImporter : public QWaylandClientExtensionTemplate<XdgImporter>, public QtWayland::zxdg_importer_v2
48{
49public:
51 : QWaylandClientExtensionTemplate(1)
52 {
53 initialize();
54 }
55 ~XdgImporter() override
56 {
57 if (isActive()) {
58 destroy();
59 }
60 }
61 XdgImported *import(const QString &handle)
62 {
63 return new XdgImported(import_toplevel(handle));
64 }
65};
66
67int main(int argc, char *argv[])
68{
69 KLocalizedString::setApplicationDomain(QByteArrayLiteral("kwin"));
70 QApplication app(argc, argv);
71 QApplication::setWindowIcon(QIcon::fromTheme(QStringLiteral("tools-report-bug")));
72 QCoreApplication::setApplicationName(QStringLiteral("kwin_killer_helper"));
73 QCoreApplication::setOrganizationDomain(QStringLiteral("kde.org"));
74 QApplication::setApplicationDisplayName(i18n("Window Manager"));
75 QCoreApplication::setApplicationVersion(QStringLiteral("1.0"));
76 QApplication::setDesktopFileName(QStringLiteral("org.kde.kwin.killer"));
77
78 QCommandLineOption pidOption(QStringLiteral("pid"),
79 i18n("PID of the application to terminate"), i18n("pid"));
80 QCommandLineOption hostNameOption(QStringLiteral("hostname"),
81 i18n("Hostname on which the application is running"), i18n("hostname"));
82 QCommandLineOption windowNameOption(QStringLiteral("windowname"),
83 i18n("Caption of the window to be terminated"), i18n("caption"));
84 QCommandLineOption applicationNameOption(QStringLiteral("applicationname"),
85 i18n("Name of the application to be terminated"), i18n("name"));
86 QCommandLineOption widOption(QStringLiteral("wid"),
87 i18n("ID of resource belonging to the application"), i18n("id"));
88 QCommandLineOption timestampOption(QStringLiteral("timestamp"),
89 i18n("Time of user action causing termination"), i18n("time"));
90 QCommandLineParser parser;
91 parser.setApplicationDescription(i18n("KWin helper utility"));
92 parser.addHelpOption();
93 parser.addVersionOption();
94
95 parser.addOption(pidOption);
96 parser.addOption(hostNameOption);
97 parser.addOption(windowNameOption);
98 parser.addOption(applicationNameOption);
99 parser.addOption(widOption);
100 parser.addOption(timestampOption);
101
102 parser.process(app);
103
104 const bool isX11 = app.platformName() == QLatin1String("xcb");
105
106 QString hostname = parser.value(hostNameOption);
107 bool pid_ok = false;
108 pid_t pid = parser.value(pidOption).toULong(&pid_ok);
109 QString caption = parser.value(windowNameOption);
110 QString appname = parser.value(applicationNameOption);
111 bool id_ok = false;
112 xcb_window_t wid = XCB_WINDOW_NONE;
113 QString windowHandle;
114 if (isX11) {
115 wid = parser.value(widOption).toULong(&id_ok);
116 } else {
117 windowHandle = parser.value(widOption);
118 }
119
120 // on Wayland XDG_ACTIVATION_TOKEN is set in the environment.
121 bool time_ok = false;
122 xcb_timestamp_t timestamp = parser.value(timestampOption).toULong(&time_ok);
123
124 if (!pid_ok || pid == 0 || ((!id_ok || wid == XCB_WINDOW_NONE) && windowHandle.isEmpty())
125 || (isX11 && (!time_ok || timestamp == XCB_CURRENT_TIME))
126 || hostname.isEmpty() || caption.isEmpty() || appname.isEmpty()) {
127 fprintf(stdout, "%s\n", qPrintable(i18n("This helper utility is not supposed to be called directly.")));
128 parser.showHelp(1);
129 }
130 bool isLocal = hostname == QStringLiteral("localhost");
131
132 const auto service = KService::serviceByDesktopName(appname);
133 if (service) {
134 appname = service->name();
135 QApplication::setApplicationDisplayName(appname);
136 }
137
138 // Drop redundant application name, cf. QXcbWindow::setWindowTitle.
139 const QString titleSeparator = QString::fromUtf8(" \xe2\x80\x94 "); // // U+2014, EM DASH
140 caption.remove(titleSeparator + appname);
141 caption.remove(QStringLiteral(" – ") + appname); // EN dash (Firefox)
142 caption.remove(QStringLiteral(" - ") + appname); // classic minus :-)
143
144 caption = caption.toHtmlEscaped();
145 appname = appname.toHtmlEscaped();
146 hostname = hostname.toHtmlEscaped();
147 QString pidString = QString::number(pid); // format pid ourself as it does not make sense to format an ID according to locale settings
148
149 QString question = (caption == appname) ? xi18nc("@info", "<para><application>%1</application> is not responding. Do you want to terminate this application?</para>",
150 appname)
151 : 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>",
152 caption, appname);
153 question += xi18nc("@info",
154 "<para><emphasis strong='true'>Terminating this application will close all of its windows. Any unsaved data will be lost.</emphasis></para>");
155
156 KGuiItem continueButton = KGuiItem(i18nc("@action:button Terminate app", "&Terminate %1", appname), QStringLiteral("application-exit"));
157 KGuiItem cancelButton = KGuiItem(i18nc("@action:button Wait for frozen app to maybe respond again", "&Wait Longer"), QStringLiteral("chronometer"));
158
159 if (isX11) {
160 QX11Info::setAppUserTime(timestamp);
161 }
162
163 auto *dialog = new KMessageDialog(KMessageDialog::WarningContinueCancel, question);
164 dialog->setAttribute(Qt::WA_DeleteOnClose);
165 dialog->setCaption(i18nc("@title:window", "Not Responding"));
166
167 QIcon icon;
168 if (service) {
169 const QIcon appIcon = QIcon::fromTheme(service->icon());
170 if (!appIcon.isNull()) {
171 // emblem-warning is non-standard, fall back to emblem-important if necessary.
172 const QIcon warningBadge = QIcon::fromTheme(QStringLiteral("emblem-warning"), QIcon::fromTheme(QStringLiteral("emblem-important")));
173
174 icon = KIconUtils::addOverlay(appIcon, warningBadge, qApp->isRightToLeft() ? Qt::BottomLeftCorner : Qt::BottomRightCorner);
175 }
176 }
177 dialog->setIcon(icon); // null icon will result in default warning icon.
178 dialog->setButtons(continueButton, KGuiItem(), cancelButton);
179
180 QStringList details{
181 i18nc("@info", "Process ID: %1", pidString)};
182 if (!isLocal) {
183 details << i18nc("@info", "Host name: %1", hostname);
184 }
185 dialog->setDetails(details.join(QLatin1Char('\n')));
186 dialog->winId();
187
188 std::unique_ptr<XdgImporter> xdgImporter;
189 std::unique_ptr<XdgImported> importedParent;
190
191 if (isX11) {
192 if (QWindow *foreignParent = QWindow::fromWinId(wid)) {
193 dialog->windowHandle()->setTransientParent(foreignParent);
194 }
195 } else {
196 xdgImporter = std::make_unique<XdgImporter>();
197 }
198
199 QObject::connect(dialog, &QDialog::finished, &app, [pid, hostname, isLocal](int result) {
200 if (result == KMessageBox::PrimaryAction) {
201 if (!isLocal) {
202 QStringList lst;
203 lst << hostname << QStringLiteral("kill") << QString::number(pid);
204 QProcess::startDetached(QStringLiteral("xon"), lst);
205 } else {
206 if (::kill(pid, SIGKILL) && errno == EPERM) {
207 KAuth::Action killer(QStringLiteral("org.kde.ksysguard.processlisthelper.sendsignal"));
208 killer.setHelperId(QStringLiteral("org.kde.ksysguard.processlisthelper"));
209 killer.addArgument(QStringLiteral("pid0"), pid);
210 killer.addArgument(QStringLiteral("pidcount"), 1);
211 killer.addArgument(QStringLiteral("signal"), SIGKILL);
212 if (killer.isValid()) {
213 qDebug() << "Using KAuth to kill pid: " << pid;
214 killer.execute();
215 } else {
216 qDebug() << "KWin process killer action not valid";
217 }
218 }
219 }
220 }
221
222 qApp->quit();
223 });
224
225 dialog->show();
226
227 if (xdgImporter) {
228 if (auto *waylandWindow = dialog->windowHandle()->nativeInterface<QNativeInterface::Private::QWaylandWindow>()) {
229 importedParent.reset(xdgImporter->import(windowHandle));
230 if (auto *surface = waylandWindow->surface()) {
231 importedParent->set_parent_of(surface);
232 }
233 }
234 }
235
236 dialog->windowHandle()->requestActivate();
237
238 return app.exec();
239}
~XdgImported() override
Definition killer.cpp:41
XdgImported(::zxdg_imported_v2 *object)
Definition killer.cpp:37
~XdgImporter() override
Definition killer.cpp:55