31ea780d79
Summary: This change splits the XdgShellClient class to better match existing abstractions in the xdg-shell protocol and fix a few issues related to sending configure events. In the new client classes, configure events are handled differently. Instead of blocking configure events, we try to send them as late as possible. Delaying configure events will let us merge changeMaximize() for X11 clients and Wayland clients and it also fixes the bug where we don't send the final configure event when user has finished resizing a window. Given that configure events are not sent immediately, XdgSurfaceClient keeps the last requested frame geometry and the last requested client geometry. This patch doesn't intend to fix all issues in kwin's implementation of the xdg-shell protocol. For example, we still handle surface unmapping very poorly. Test Plan: Tests pass. Reviewers: #kwin Subscribers: kwin Tags: #kwin Differential Revision: https://phabricator.kde.org/D27861
287 lines
7.3 KiB
C++
287 lines
7.3 KiB
C++
/*
|
|
* Copyright (C) 2015 Martin Flöser <mgraesslin@kde.org>
|
|
* Copyright (C) 2018 David Edmundson <davidedmundson@kde.org>
|
|
* Copyright (C) 2020 Vlad Zahorodnii <vlad.zahorodnii@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 "waylandclient.h"
|
|
#include "screens.h"
|
|
#include "wayland_server.h"
|
|
#include "workspace.h"
|
|
|
|
#include <KWaylandServer/display.h>
|
|
#include <KWaylandServer/clientconnection.h>
|
|
#include <KWaylandServer/surface_interface.h>
|
|
|
|
#include <QFileInfo>
|
|
|
|
#include <csignal>
|
|
|
|
#include <sys/types.h>
|
|
#include <unistd.h>
|
|
|
|
using namespace KWaylandServer;
|
|
|
|
namespace KWin
|
|
{
|
|
|
|
WaylandClient::WaylandClient(SurfaceInterface *surface)
|
|
{
|
|
// Note that we cannot setup compositing here because we may need to call visibleRect(),
|
|
// which in its turn will call bufferGeometry(), which is a pure virtual method.
|
|
|
|
setSurface(surface);
|
|
|
|
m_windowId = waylandServer()->createWindowId(surface);
|
|
|
|
connect(this, &WaylandClient::frameGeometryChanged,
|
|
this, &WaylandClient::updateClientOutputs);
|
|
connect(this, &WaylandClient::frameGeometryChanged,
|
|
this, &WaylandClient::updateClientArea);
|
|
connect(this, &WaylandClient::desktopFileNameChanged,
|
|
this, &WaylandClient::updateIcon);
|
|
connect(screens(), &Screens::changed, this,
|
|
&WaylandClient::updateClientOutputs);
|
|
|
|
updateResourceName();
|
|
updateIcon();
|
|
}
|
|
|
|
QString WaylandClient::captionNormal() const
|
|
{
|
|
return m_captionNormal;
|
|
}
|
|
|
|
QString WaylandClient::captionSuffix() const
|
|
{
|
|
return m_captionSuffix;
|
|
}
|
|
|
|
QStringList WaylandClient::activities() const
|
|
{
|
|
return QStringList();
|
|
}
|
|
|
|
void WaylandClient::setOnAllActivities(bool set)
|
|
{
|
|
Q_UNUSED(set)
|
|
}
|
|
|
|
void WaylandClient::blockActivityUpdates(bool b)
|
|
{
|
|
Q_UNUSED(b)
|
|
}
|
|
|
|
QPoint WaylandClient::clientContentPos() const
|
|
{
|
|
return -clientPos();
|
|
}
|
|
|
|
QRect WaylandClient::transparentRect() const
|
|
{
|
|
return QRect();
|
|
}
|
|
|
|
quint32 WaylandClient::windowId() const
|
|
{
|
|
return m_windowId;
|
|
}
|
|
|
|
pid_t WaylandClient::pid() const
|
|
{
|
|
return surface()->client()->processId();
|
|
}
|
|
|
|
bool WaylandClient::isLockScreen() const
|
|
{
|
|
return surface()->client() == waylandServer()->screenLockerClientConnection();
|
|
}
|
|
|
|
bool WaylandClient::isInputMethod() const
|
|
{
|
|
return surface()->client() == waylandServer()->inputMethodConnection();
|
|
}
|
|
|
|
bool WaylandClient::isLocalhost() const
|
|
{
|
|
return true;
|
|
}
|
|
|
|
double WaylandClient::opacity() const
|
|
{
|
|
return m_opacity;
|
|
}
|
|
|
|
void WaylandClient::setOpacity(double opacity)
|
|
{
|
|
const qreal newOpacity = qBound(0.0, opacity, 1.0);
|
|
if (newOpacity == m_opacity) {
|
|
return;
|
|
}
|
|
const qreal oldOpacity = m_opacity;
|
|
m_opacity = newOpacity;
|
|
addRepaintFull();
|
|
emit opacityChanged(this, oldOpacity);
|
|
}
|
|
|
|
AbstractClient *WaylandClient::findModal(bool allow_itself)
|
|
{
|
|
Q_UNUSED(allow_itself)
|
|
return nullptr;
|
|
}
|
|
|
|
void WaylandClient::resizeWithChecks(const QSize &size, ForceGeometry_t force)
|
|
{
|
|
const QRect area = workspace()->clientArea(WorkArea, this);
|
|
|
|
int width = size.width();
|
|
int height = size.height();
|
|
|
|
// don't allow growing larger than workarea
|
|
if (width > area.width()) {
|
|
width = area.width();
|
|
}
|
|
if (height > area.height()) {
|
|
height = area.height();
|
|
}
|
|
setFrameGeometry(QRect(x(), y(), width, height), force);
|
|
}
|
|
|
|
void WaylandClient::killWindow()
|
|
{
|
|
if (!surface()) {
|
|
return;
|
|
}
|
|
auto c = surface()->client();
|
|
if (c->processId() == getpid() || c->processId() == 0) {
|
|
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);
|
|
}
|
|
|
|
QByteArray WaylandClient::windowRole() const
|
|
{
|
|
return QByteArray();
|
|
}
|
|
|
|
bool WaylandClient::belongsToSameApplication(const AbstractClient *other, SameApplicationChecks checks) const
|
|
{
|
|
if (checks.testFlag(SameApplicationCheck::AllowCrossProcesses)) {
|
|
if (other->desktopFileName() == desktopFileName()) {
|
|
return true;
|
|
}
|
|
}
|
|
if (auto s = other->surface()) {
|
|
return s->client() == surface()->client();
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool WaylandClient::belongsToDesktop() const
|
|
{
|
|
const auto clients = waylandServer()->clients();
|
|
|
|
return std::any_of(clients.constBegin(), clients.constEnd(),
|
|
[this](const AbstractClient *client) {
|
|
if (belongsToSameApplication(client, SameApplicationChecks())) {
|
|
return client->isDesktop();
|
|
}
|
|
return false;
|
|
}
|
|
);
|
|
}
|
|
|
|
void WaylandClient::updateClientArea()
|
|
{
|
|
if (hasStrut()) {
|
|
workspace()->updateClientArea();
|
|
}
|
|
}
|
|
|
|
void WaylandClient::updateClientOutputs()
|
|
{
|
|
QVector<OutputInterface *> clientOutputs;
|
|
const auto outputs = waylandServer()->display()->outputs();
|
|
for (const auto output : outputs) {
|
|
if (output->isEnabled()) {
|
|
const QRect outputGeometry(output->globalPosition(), output->pixelSize() / output->scale());
|
|
if (frameGeometry().intersects(outputGeometry)) {
|
|
clientOutputs << output;
|
|
}
|
|
}
|
|
}
|
|
surface()->setOutputs(clientOutputs);
|
|
}
|
|
|
|
void WaylandClient::updateIcon()
|
|
{
|
|
const QString waylandIconName = QStringLiteral("wayland");
|
|
const QString dfIconName = iconFromDesktopFile();
|
|
const QString iconName = dfIconName.isEmpty() ? waylandIconName : dfIconName;
|
|
if (iconName == icon().name()) {
|
|
return;
|
|
}
|
|
setIcon(QIcon::fromTheme(iconName));
|
|
}
|
|
|
|
void WaylandClient::updateResourceName()
|
|
{
|
|
const QFileInfo fileInfo(surface()->client()->executablePath());
|
|
if (fileInfo.exists()) {
|
|
setResourceClass(fileInfo.fileName().toUtf8());
|
|
}
|
|
}
|
|
|
|
void WaylandClient::updateCaption()
|
|
{
|
|
const QString oldSuffix = m_captionSuffix;
|
|
const auto shortcut = shortcutCaptionSuffix();
|
|
m_captionSuffix = shortcut;
|
|
if ((!isSpecialWindow() || isToolbar()) && findClientWithSameCaption()) {
|
|
int i = 2;
|
|
do {
|
|
m_captionSuffix = shortcut + QLatin1String(" <") + QString::number(i) + QLatin1Char('>');
|
|
i++;
|
|
} while (findClientWithSameCaption());
|
|
}
|
|
if (m_captionSuffix != oldSuffix) {
|
|
emit captionChanged();
|
|
}
|
|
}
|
|
|
|
void WaylandClient::setCaption(const QString &caption)
|
|
{
|
|
const QString oldSuffix = m_captionSuffix;
|
|
m_captionNormal = caption.simplified();
|
|
updateCaption();
|
|
if (m_captionSuffix == oldSuffix) {
|
|
// Don't emit caption change twice it already got emitted by the changing suffix.
|
|
emit captionChanged();
|
|
}
|
|
}
|
|
|
|
void WaylandClient::doSetActive()
|
|
{
|
|
if (isActive()) { // TODO: Xwayland clients must be unfocused somewhere else.
|
|
StackingUpdatesBlocker blocker(workspace());
|
|
workspace()->focusToNull();
|
|
}
|
|
}
|
|
|
|
} // namespace KWin
|