kwin/waylandclient.cpp
Vlad Zahorodnii 3e1443e003 wayland: Don't force a bpp of 24 for desktop windows
plasmashell provides the compositor buffers with an alpha channel and
without indicating the opaque region. Given that, kwin forces a bpp of
24 for desktop windows to improve performance. Unfortunately, there are
legit cases where plasma may attach a buffer that is not fully opaque,
for example when switching between activities.

BUG: 423545
BUG: 421237
2020-12-14 14:09:24 +00:00

530 lines
14 KiB
C++

/*
SPDX-FileCopyrightText: 2015 Martin Flöser <mgraesslin@kde.org>
SPDX-FileCopyrightText: 2018 David Edmundson <davidedmundson@kde.org>
SPDX-FileCopyrightText: 2020 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "waylandclient.h"
#include "screens.h"
#include "wayland_server.h"
#include "workspace.h"
#ifdef KWIN_BUILD_TABBOX
#include "tabbox.h"
#endif
#include <KWaylandServer/display.h>
#include <KWaylandServer/clientconnection.h>
#include <KWaylandServer/surface_interface.h>
#include <KWaylandServer/buffer_interface.h>
#include <QFileInfo>
#include <csignal>
#include <sys/types.h>
#include <unistd.h>
using namespace KWaylandServer;
namespace KWin
{
enum WaylandGeometryType {
WaylandGeometryClient = 0x1,
WaylandGeometryFrame = 0x2,
WaylandGeometryBuffer = 0x4,
};
Q_DECLARE_FLAGS(WaylandGeometryTypes, WaylandGeometryType)
WaylandClient::WaylandClient(SurfaceInterface *surface)
{
setSurface(surface);
setupCompositing();
connect(surface, &SurfaceInterface::shadowChanged,
this, &WaylandClient::updateShadow);
connect(this, &WaylandClient::frameGeometryChanged,
this, &WaylandClient::updateClientOutputs);
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();
}
pid_t WaylandClient::pid() const
{
return surface()->client()->processId();
}
bool WaylandClient::isLockScreen() const
{
return surface()->client() == waylandServer()->screenLockerClientConnection();
}
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::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();
}
}
void WaylandClient::updateDepth()
{
if (surface()->buffer()->hasAlphaChannel()) {
setDepth(32);
} else {
setDepth(24);
}
}
void WaylandClient::cleanGrouping()
{
if (transientFor()) {
transientFor()->removeTransient(this);
}
for (auto it = transients().constBegin(); it != transients().constEnd();) {
if ((*it)->transientFor() == this) {
removeTransient(*it);
it = transients().constBegin(); // restart, just in case something more has changed with the list
} else {
++it;
}
}
}
void WaylandClient::cleanTabBox()
{
#ifdef KWIN_BUILD_TABBOX
TabBox::TabBox *tabBox = TabBox::TabBox::self();
if (tabBox->isDisplayed() && tabBox->currentClient() == this) {
tabBox->nextPrev(true);
}
#endif
}
bool WaylandClient::isShown(bool shaded_is_shown) const
{
Q_UNUSED(shaded_is_shown)
return !isZombie() && !isHidden() && !isMinimized();
}
bool WaylandClient::isHiddenInternal() const
{
return isHidden();
}
void WaylandClient::hideClient(bool hide)
{
if (hide) {
internalHide();
} else {
internalShow();
}
}
bool WaylandClient::isHidden() const
{
return m_isHidden;
}
void WaylandClient::internalShow()
{
if (!isHidden()) {
return;
}
m_isHidden = false;
addRepaintFull();
emit windowShown(this);
}
void WaylandClient::internalHide()
{
if (isHidden()) {
return;
}
if (isMoveResize()) {
leaveMoveResize();
}
m_isHidden = true;
addWorkspaceRepaint(visibleRect());
workspace()->clientHidden(this);
emit windowHidden(this);
}
QRect WaylandClient::frameRectToBufferRect(const QRect &rect) const
{
return QRect(rect.topLeft(), surface()->size());
}
QRect WaylandClient::requestedFrameGeometry() const
{
return m_requestedFrameGeometry;
}
QPoint WaylandClient::requestedPos() const
{
return m_requestedFrameGeometry.topLeft();
}
QSize WaylandClient::requestedSize() const
{
return m_requestedFrameGeometry.size();
}
QRect WaylandClient::requestedClientGeometry() const
{
return m_requestedClientGeometry;
}
QRect WaylandClient::bufferGeometry() const
{
return m_bufferGeometry;
}
QSize WaylandClient::requestedClientSize() const
{
return requestedClientGeometry().size();
}
void WaylandClient::setFrameGeometry(const QRect &rect, ForceGeometry_t force)
{
m_requestedFrameGeometry = rect;
if (isShade()) {
if (m_requestedFrameGeometry.height() == borderTop() + borderBottom()) {
qCDebug(KWIN_CORE) << "Passed shaded frame geometry to setFrameGeometry()";
} else {
m_requestedClientGeometry = frameRectToClientRect(m_requestedFrameGeometry);
m_requestedFrameGeometry.setHeight(borderTop() + borderBottom());
}
} else {
m_requestedClientGeometry = frameRectToClientRect(m_requestedFrameGeometry);
}
if (areGeometryUpdatesBlocked()) {
m_frameGeometry = m_requestedFrameGeometry;
if (pendingGeometryUpdate() == PendingGeometryForced) {
return;
}
if (force == ForceGeometrySet) {
setPendingGeometryUpdate(PendingGeometryForced);
} else {
setPendingGeometryUpdate(PendingGeometryNormal);
}
return;
}
m_frameGeometry = frameGeometryBeforeUpdateBlocking();
if (requestedClientSize() != clientSize()) {
requestGeometry(requestedFrameGeometry());
} else {
updateGeometry(requestedFrameGeometry());
return;
}
QRect updateRect = m_frameGeometry;
if (m_positionSyncMode == SyncMode::Sync) {
updateRect.moveTopLeft(requestedPos());
}
if (m_sizeSyncMode == SyncMode::Sync) {
updateRect.setSize(requestedSize());
}
updateGeometry(updateRect);
}
void WaylandClient::move(int x, int y, ForceGeometry_t force)
{
Q_ASSERT(pendingGeometryUpdate() == PendingGeometryNone || areGeometryUpdatesBlocked());
QPoint p(x, y);
if (!areGeometryUpdatesBlocked() && p != rules()->checkPosition(p)) {
qCDebug(KWIN_CORE) << "forced position fail:" << p << ":" << rules()->checkPosition(p);
}
m_requestedFrameGeometry.moveTopLeft(p);
m_requestedClientGeometry.moveTopLeft(framePosToClientPos(p));
if (force == NormalGeometrySet && m_frameGeometry.topLeft() == p) {
return;
}
m_frameGeometry.moveTopLeft(m_requestedFrameGeometry.topLeft());
if (areGeometryUpdatesBlocked()) {
if (pendingGeometryUpdate() == PendingGeometryForced) {
return;
}
if (force == ForceGeometrySet) {
setPendingGeometryUpdate(PendingGeometryForced);
} else {
setPendingGeometryUpdate(PendingGeometryNormal);
}
return;
}
const QRect oldBufferGeometry = bufferGeometryBeforeUpdateBlocking();
const QRect oldClientGeometry = clientGeometryBeforeUpdateBlocking();
const QRect oldFrameGeometry = frameGeometryBeforeUpdateBlocking();
m_clientGeometry.moveTopLeft(m_requestedClientGeometry.topLeft());
m_bufferGeometry = frameRectToBufferRect(m_frameGeometry);
updateGeometryBeforeUpdateBlocking();
updateWindowRules(Rules::Position);
screens()->setCurrent(this);
workspace()->updateStackingOrder();
emit bufferGeometryChanged(this, oldBufferGeometry);
emit clientGeometryChanged(this, oldClientGeometry);
emit frameGeometryChanged(this, oldFrameGeometry);
addRepaintDuringGeometryUpdates();
}
void WaylandClient::requestGeometry(const QRect &rect)
{
m_requestedFrameGeometry = rect;
m_requestedClientGeometry = frameRectToClientRect(rect);
}
void WaylandClient::updateGeometry(const QRect &rect)
{
const QRect oldClientGeometry = m_clientGeometry;
const QRect oldFrameGeometry = m_frameGeometry;
const QRect oldBufferGeometry = m_bufferGeometry;
m_clientGeometry = frameRectToClientRect(rect);
m_frameGeometry = rect;
m_bufferGeometry = frameRectToBufferRect(rect);
WaylandGeometryTypes changedGeometries;
if (m_clientGeometry != oldClientGeometry) {
changedGeometries |= WaylandGeometryClient;
}
if (m_frameGeometry != oldFrameGeometry) {
changedGeometries |= WaylandGeometryFrame;
}
if (m_bufferGeometry != oldBufferGeometry) {
changedGeometries |= WaylandGeometryBuffer;
}
if (!changedGeometries) {
return;
}
updateWindowRules(Rules::Position | Rules::Size);
updateGeometryBeforeUpdateBlocking();
if (changedGeometries & WaylandGeometryBuffer) {
emit bufferGeometryChanged(this, oldBufferGeometry);
}
if (changedGeometries & WaylandGeometryClient) {
emit clientGeometryChanged(this, oldClientGeometry);
}
if (changedGeometries & WaylandGeometryFrame) {
emit frameGeometryChanged(this, oldFrameGeometry);
}
emit geometryShapeChanged(this, oldFrameGeometry);
addRepaintDuringGeometryUpdates();
}
void WaylandClient::setPositionSyncMode(SyncMode syncMode)
{
m_positionSyncMode = syncMode;
}
void WaylandClient::setSizeSyncMode(SyncMode syncMode)
{
m_sizeSyncMode = syncMode;
}
} // namespace KWin