KillWindow support for Wayland windows

Summary:
AbstractClient gains a new pure virtual killWindow method and this gets
implemented in ShellClient.

ShellClient performs the killing by sending a term signal to the
process. This can only work if the client connected through the socket
and didn't get a socketpair fd passed. In that case the pid is KWin's
and KWin doesn't want to terminate. Thus this is special handled to
destroy the connection instead.

In case terminating the process has no effect, the connection gets
destroyed after five seconds.

The KillWindow is adjusted to operate on AbstractClient instead of
Client.

This implements T4463.

Test Plan: Killed windows and auto test

Reviewers: #kwin, #plasma_on_wayland

Subscribers: plasma-devel, kwin

Tags: #plasma_on_wayland, #kwin

Differential Revision: https://phabricator.kde.org/D3370
icc-effect-5.14.5
Martin Gräßlin 2016-11-15 16:48:20 +01:00
parent af56c6b050
commit 6bee7f4aac
8 changed files with 128 additions and 3 deletions

View File

@ -614,6 +614,13 @@ public:
return m_desktopFileName;
}
/**
* Tries to terminate the process of this AbstractClient.
*
* Implementing subclasses can perform a windowing system solution for terminating.
**/
virtual void killWindow() = 0;
// TODO: remove boolean trap
static bool belongToSameApplication(const AbstractClient* c1, const AbstractClient* c2, bool active_hack = false);

View File

@ -5,3 +5,7 @@ ecm_mark_as_test(copy)
add_executable(paste paste.cpp)
target_link_libraries(paste Qt5::Gui)
ecm_mark_as_test(paste)
######################
add_executable(kill kill.cpp)
target_link_libraries(kill Qt5::Widgets)
ecm_mark_as_test(kill)

View File

@ -0,0 +1,32 @@
/********************************************************************
KWin - the KDE window manager
This file is part of the KDE project.
Copyright (C) 2016 Martin Gräßlin <mgraesslin@kde.org>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*********************************************************************/
#include <QApplication>
#include <QWidget>
int main(int argc, char *argv[])
{
qputenv("QT_QPA_PLATFORM", QByteArrayLiteral("wayland"));
QApplication app(argc, argv);
QWidget w;
w.setGeometry(QRect(0, 0, 100, 200));
w.show();
return app.exec();
}

View File

@ -33,8 +33,15 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include <KWayland/Client/surface.h>
#include <KWayland/Client/xdgshell.h>
#include <KWayland/Server/clientconnection.h>
#include <KWayland/Server/display.h>
#include <KWayland/Server/shell_interface.h>
// system
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
using namespace KWin;
using namespace KWayland::Client;
@ -64,6 +71,8 @@ private Q_SLOTS:
void testHidden();
void testDesktopFileName();
void testCaptionSimplified();
void testKillWindow_data();
void testKillWindow();
};
void TestShellClient::initTestCase()
@ -633,5 +642,53 @@ void TestShellClient::testCaptionSimplified()
QCOMPARE(c->caption(), origTitle.simplified());
}
void TestShellClient::testKillWindow_data()
{
QTest::addColumn<bool>("socketMode");
QTest::newRow("display") << false;
QTest::newRow("socket") << true;
}
void TestShellClient::testKillWindow()
{
// this test verifies that killWindow properly terminates a process
// for this an external binary is launched
const QString kill = QFINDTESTDATA(QStringLiteral("helper/kill"));
QVERIFY(!kill.isEmpty());
QSignalSpy shellClientAddedSpy(waylandServer(), &WaylandServer::shellClientAdded);
QVERIFY(shellClientAddedSpy.isValid());
QScopedPointer<QProcess> process(new QProcess);
QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
QFETCH(bool, socketMode);
if (socketMode) {
int sx[2];
QVERIFY(socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, sx) >= 0);
waylandServer()->display()->createClient(sx[0]);
int socket = dup(sx[1]);
QVERIFY(socket != -1);
env.insert(QStringLiteral("WAYLAND_SOCKET"), QByteArray::number(socket));
env.remove("WAYLAND_DISPLAY");
} else {
env.insert("WAYLAND_DISPLAY", s_socketName);
}
process->setProcessEnvironment(env);
process->setProcessChannelMode(QProcess::ForwardedChannels);
process->setProgram(kill);
process->start();
QVERIFY(process->waitForStarted());
AbstractClient *killClient = nullptr;
QVERIFY(shellClientAddedSpy.wait());
killClient = shellClientAddedSpy.first().first().value<AbstractClient*>();
QVERIFY(killClient);
QSignalSpy finishedSpy(process.data(), static_cast<void(QProcess::*)(int,QProcess::ExitStatus)>(&QProcess::finished));
QVERIFY(finishedSpy.isValid());
killClient->killWindow();
QVERIFY(finishedSpy.wait());
QVERIFY(!finishedSpy.isEmpty());
}
WAYLANDTEST_MAIN(TestShellClient)
#include "shell_client_test.moc"

View File

@ -249,7 +249,7 @@ public:
static bool belongToSameApplication(const Client* c1, const Client* c2, bool active_hack = false);
static bool sameAppWindowRoleMatch(const Client* c1, const Client* c2, bool active_hack);
void killWindow();
void killWindow() override;
void toggleShade();
void showContextHelp() override;
void cancelShadeHoverTimer();

View File

@ -20,7 +20,7 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*********************************************************************/
#include "killwindow.h"
#include "client.h"
#include "abstract_client.h"
#include "main.h"
#include "platform.h"
#include "unmanaged.h"
@ -43,7 +43,7 @@ void KillWindow::start()
if (!t) {
return;
}
if (Client *c = qobject_cast<Client*>(t)) {
if (AbstractClient *c = qobject_cast<AbstractClient*>(t)) {
c->killWindow();
} else if (Unmanaged *u = qobject_cast<Unmanaged*>(t)) {
xcb_kill_client(connection(), u->window());

View File

@ -34,6 +34,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include <KDecoration2/DecoratedClient>
#include <KWayland/Client/surface.h>
#include <KWayland/Server/clientconnection.h>
#include <KWayland/Server/seat_interface.h>
#include <KWayland/Server/shell_interface.h>
#include <KWayland/Server/surface_interface.h>
@ -48,6 +49,10 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include <QOpenGLFramebufferObject>
#include <QWindow>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>
using namespace KWayland::Server;
static const QByteArray s_schemePropertyName = QByteArrayLiteral("KDE_COLOR_SCHEME_PATH");
@ -1380,4 +1385,22 @@ bool ShellClient::dockWantsInput() const
return false;
}
void ShellClient::killWindow()
{
if (isInternal()) {
return;
}
if (!surface()) {
return;
}
auto c = surface()->client();
if (c->processId() == getpid()) {
c->destroy();
return;
}
::kill(c->processId(), SIGTERM);
// give it time to terminate and only if terminate fails, try destroy Wayland connection
QTimer::singleShot(5000, c, &ClientConnection::destroy);
}
}

View File

@ -132,6 +132,8 @@ public:
void showOnScreenEdge() override;
void killWindow() override;
// TODO: const-ref
void placeIn(QRect &area);