bebe81209c
Summary: So far wayland was used by internal clients to submit raster buffers and position themselves on the screen. While we didn't have issues with submitting raster buffers, there were some problems with positioning task switchers. Mostly, because we had effectively two paths that may alter geometry. A better approach to deal with internal clients is to let our QPA use kwin core api directly. This way we can eliminate unnecessary roundtrips as well make geometry handling much easier and comprehensible. The last missing piece is shadows. Both Plasma::Dialog and Breeze widget style use platform-specific APIs to set and unset shadows. We need to add shadows API to KWindowSystem. Even though some internal clients lack drop-shadows at the moment, I don't consider it to be a blocker. We can add shadows back later on. CCBUG: 386304 Reviewers: #kwin, davidedmundson, romangg Reviewed By: #kwin, romangg Subscribers: romangg, kwin Tags: #kwin Maniphest Tasks: T9600 Differential Revision: https://phabricator.kde.org/D22810
685 lines
16 KiB
C++
685 lines
16 KiB
C++
/********************************************************************
|
|
KWin - the KDE window manager
|
|
This file is part of the KDE project.
|
|
|
|
Copyright (C) 2019 Martin Flöser <mgraesslin@kde.org>
|
|
Copyright (C) 2019 Vlad Zagorodniy <vladzzag@gmail.com>
|
|
|
|
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 "internal_client.h"
|
|
#include "decorations/decorationbridge.h"
|
|
#include "deleted.h"
|
|
#include "workspace.h"
|
|
|
|
#include <KDecoration2/Decoration>
|
|
|
|
#include <QOpenGLFramebufferObject>
|
|
#include <QWindow>
|
|
|
|
Q_DECLARE_METATYPE(NET::WindowType)
|
|
|
|
static const QByteArray s_skipClosePropertyName = QByteArrayLiteral("KWIN_SKIP_CLOSE_ANIMATION");
|
|
|
|
namespace KWin
|
|
{
|
|
|
|
InternalClient::InternalClient(QWindow *window)
|
|
: m_internalWindow(window)
|
|
, m_clientSize(window->size())
|
|
, m_windowId(window->winId())
|
|
, m_internalWindowFlags(window->flags())
|
|
{
|
|
// Don't render the client until it provides a buffer.
|
|
ready_for_painting = false;
|
|
|
|
connect(m_internalWindow, &QWindow::xChanged, this, &InternalClient::updateInternalWindowGeometry);
|
|
connect(m_internalWindow, &QWindow::yChanged, this, &InternalClient::updateInternalWindowGeometry);
|
|
connect(m_internalWindow, &QWindow::widthChanged, this, &InternalClient::updateInternalWindowGeometry);
|
|
connect(m_internalWindow, &QWindow::heightChanged, this, &InternalClient::updateInternalWindowGeometry);
|
|
connect(m_internalWindow, &QWindow::windowTitleChanged, this, &InternalClient::setCaption);
|
|
connect(m_internalWindow, &QWindow::opacityChanged, this, &InternalClient::setOpacity);
|
|
connect(m_internalWindow, &QWindow::destroyed, this, &InternalClient::destroyClient);
|
|
|
|
connect(this, &InternalClient::opacityChanged, this, &InternalClient::addRepaintFull);
|
|
|
|
const QVariant windowType = m_internalWindow->property("kwin_windowType");
|
|
if (!windowType.isNull()) {
|
|
m_windowType = windowType.value<NET::WindowType>();
|
|
}
|
|
|
|
setCaption(m_internalWindow->title());
|
|
setIcon(QIcon::fromTheme(QStringLiteral("kwin")));
|
|
setOnAllDesktops(true);
|
|
setOpacity(m_internalWindow->opacity());
|
|
setSkipCloseAnimation(m_internalWindow->property(s_skipClosePropertyName).toBool());
|
|
|
|
setupCompositing();
|
|
updateColorScheme();
|
|
|
|
blockGeometryUpdates(true);
|
|
commitGeometry(m_internalWindow->geometry());
|
|
updateDecoration(true);
|
|
setGeometry(mapFromClient(m_internalWindow->geometry()));
|
|
setGeometryRestore(geometry());
|
|
blockGeometryUpdates(false);
|
|
|
|
m_internalWindow->installEventFilter(this);
|
|
}
|
|
|
|
InternalClient::~InternalClient()
|
|
{
|
|
}
|
|
|
|
bool InternalClient::eventFilter(QObject *watched, QEvent *event)
|
|
{
|
|
if (watched == m_internalWindow && event->type() == QEvent::DynamicPropertyChange) {
|
|
QDynamicPropertyChangeEvent *pe = static_cast<QDynamicPropertyChangeEvent*>(event);
|
|
if (pe->propertyName() == s_skipClosePropertyName) {
|
|
setSkipCloseAnimation(m_internalWindow->property(s_skipClosePropertyName).toBool());
|
|
}
|
|
if (pe->propertyName() == "kwin_windowType") {
|
|
m_windowType = m_internalWindow->property("kwin_windowType").value<NET::WindowType>();
|
|
workspace()->updateClientArea();
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
QStringList InternalClient::activities() const
|
|
{
|
|
return QStringList();
|
|
}
|
|
|
|
void InternalClient::blockActivityUpdates(bool b)
|
|
{
|
|
Q_UNUSED(b)
|
|
|
|
// Internal clients do not support activities.
|
|
}
|
|
|
|
qreal InternalClient::bufferScale() const
|
|
{
|
|
if (m_internalWindow) {
|
|
return m_internalWindow->devicePixelRatio();
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
QString InternalClient::captionNormal() const
|
|
{
|
|
return m_captionNormal;
|
|
}
|
|
|
|
QString InternalClient::captionSuffix() const
|
|
{
|
|
return m_captionSuffix;
|
|
}
|
|
|
|
QPoint InternalClient::clientContentPos() const
|
|
{
|
|
return -1 * clientPos();
|
|
}
|
|
|
|
QSize InternalClient::clientSize() const
|
|
{
|
|
return m_clientSize;
|
|
}
|
|
|
|
void InternalClient::debug(QDebug &stream) const
|
|
{
|
|
stream.nospace() << "\'InternalClient:" << m_internalWindow << "\'";
|
|
}
|
|
|
|
QRect InternalClient::transparentRect() const
|
|
{
|
|
return QRect();
|
|
}
|
|
|
|
NET::WindowType InternalClient::windowType(bool direct, int supported_types) const
|
|
{
|
|
Q_UNUSED(direct)
|
|
Q_UNUSED(supported_types)
|
|
return m_windowType;
|
|
}
|
|
|
|
double InternalClient::opacity() const
|
|
{
|
|
return m_opacity;
|
|
}
|
|
|
|
void InternalClient::setOpacity(double opacity)
|
|
{
|
|
if (m_opacity == opacity) {
|
|
return;
|
|
}
|
|
|
|
const double oldOpacity = m_opacity;
|
|
m_opacity = opacity;
|
|
|
|
emit opacityChanged(this, oldOpacity);
|
|
}
|
|
|
|
void InternalClient::killWindow()
|
|
{
|
|
// We don't kill our internal windows.
|
|
}
|
|
|
|
bool InternalClient::isPopupWindow() const
|
|
{
|
|
if (AbstractClient::isPopupWindow()) {
|
|
return true;
|
|
}
|
|
return m_internalWindowFlags.testFlag(Qt::Popup);
|
|
}
|
|
|
|
QByteArray InternalClient::windowRole() const
|
|
{
|
|
return QByteArray();
|
|
}
|
|
|
|
void InternalClient::closeWindow()
|
|
{
|
|
if (m_internalWindow) {
|
|
m_internalWindow->hide();
|
|
}
|
|
}
|
|
|
|
bool InternalClient::isCloseable() const
|
|
{
|
|
return true;
|
|
}
|
|
|
|
bool InternalClient::isFullScreenable() const
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bool InternalClient::isFullScreen() const
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bool InternalClient::isMaximizable() const
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bool InternalClient::isMinimizable() const
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bool InternalClient::isMovable() const
|
|
{
|
|
return true;
|
|
}
|
|
|
|
bool InternalClient::isMovableAcrossScreens() const
|
|
{
|
|
return true;
|
|
}
|
|
|
|
bool InternalClient::isResizable() const
|
|
{
|
|
return true;
|
|
}
|
|
|
|
bool InternalClient::noBorder() const
|
|
{
|
|
return m_userNoBorder || m_internalWindowFlags.testFlag(Qt::FramelessWindowHint) || m_internalWindowFlags.testFlag(Qt::Popup);
|
|
}
|
|
|
|
bool InternalClient::userCanSetNoBorder() const
|
|
{
|
|
return !m_internalWindowFlags.testFlag(Qt::FramelessWindowHint) || m_internalWindowFlags.testFlag(Qt::Popup);
|
|
}
|
|
|
|
bool InternalClient::wantsInput() const
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bool InternalClient::isInternal() const
|
|
{
|
|
return true;
|
|
}
|
|
|
|
bool InternalClient::isLockScreen() const
|
|
{
|
|
if (m_internalWindow) {
|
|
return m_internalWindow->property("org_kde_ksld_emergency").toBool();
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool InternalClient::isInputMethod() const
|
|
{
|
|
if (m_internalWindow) {
|
|
return m_internalWindow->property("__kwin_input_method").toBool();
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool InternalClient::isOutline() const
|
|
{
|
|
if (m_internalWindow) {
|
|
return m_internalWindow->property("__kwin_outline").toBool();
|
|
}
|
|
return false;
|
|
}
|
|
|
|
quint32 InternalClient::windowId() const
|
|
{
|
|
return m_windowId;
|
|
}
|
|
|
|
MaximizeMode InternalClient::maximizeMode() const
|
|
{
|
|
return MaximizeRestore;
|
|
}
|
|
|
|
QRect InternalClient::geometryRestore() const
|
|
{
|
|
return m_maximizeRestoreGeometry;
|
|
}
|
|
|
|
bool InternalClient::isShown(bool shaded_is_shown) const
|
|
{
|
|
Q_UNUSED(shaded_is_shown)
|
|
|
|
return readyForPainting();
|
|
}
|
|
|
|
bool InternalClient::isHiddenInternal() const
|
|
{
|
|
return false;
|
|
}
|
|
|
|
void InternalClient::hideClient(bool hide)
|
|
{
|
|
Q_UNUSED(hide)
|
|
}
|
|
|
|
void InternalClient::resizeWithChecks(int w, int h, ForceGeometry_t force)
|
|
{
|
|
Q_UNUSED(force)
|
|
if (!m_internalWindow) {
|
|
return;
|
|
}
|
|
QRect area = workspace()->clientArea(WorkArea, this);
|
|
// don't allow growing larger than workarea
|
|
if (w > area.width()) {
|
|
w = area.width();
|
|
}
|
|
if (h > area.height()) {
|
|
h = area.height();
|
|
}
|
|
setGeometry(QRect(x(), y(), w, h));
|
|
}
|
|
|
|
void InternalClient::setGeometry(int x, int y, int w, int h, ForceGeometry_t force)
|
|
{
|
|
const QRect rect(x, y, w, h);
|
|
|
|
if (areGeometryUpdatesBlocked()) {
|
|
geom = rect;
|
|
if (pendingGeometryUpdate() == PendingGeometryForced) {
|
|
// Maximum, nothing needed.
|
|
} else if (force == ForceGeometrySet) {
|
|
setPendingGeometryUpdate(PendingGeometryForced);
|
|
} else {
|
|
setPendingGeometryUpdate(PendingGeometryNormal);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (pendingGeometryUpdate() != PendingGeometryNone) {
|
|
// Reset geometry to the one before blocking, so that we can compare properly.
|
|
geom = geometryBeforeUpdateBlocking();
|
|
}
|
|
|
|
if (geom == rect) {
|
|
return;
|
|
}
|
|
|
|
const QRect newClientGeometry = mapToClient(rect);
|
|
|
|
if (m_clientSize == newClientGeometry.size()) {
|
|
commitGeometry(rect);
|
|
} else {
|
|
requestGeometry(rect);
|
|
}
|
|
}
|
|
|
|
void InternalClient::setGeometryRestore(const QRect &rect)
|
|
{
|
|
m_maximizeRestoreGeometry = rect;
|
|
}
|
|
|
|
bool InternalClient::supportsWindowRules() const
|
|
{
|
|
return false;
|
|
}
|
|
|
|
AbstractClient *InternalClient::findModal(bool allow_itself)
|
|
{
|
|
Q_UNUSED(allow_itself)
|
|
return nullptr;
|
|
}
|
|
|
|
void InternalClient::setOnAllActivities(bool set)
|
|
{
|
|
Q_UNUSED(set)
|
|
|
|
// Internal clients do not support activities.
|
|
}
|
|
|
|
void InternalClient::takeFocus()
|
|
{
|
|
}
|
|
|
|
bool InternalClient::userCanSetFullScreen() const
|
|
{
|
|
return false;
|
|
}
|
|
|
|
void InternalClient::setFullScreen(bool set, bool user)
|
|
{
|
|
Q_UNUSED(set)
|
|
Q_UNUSED(user)
|
|
}
|
|
|
|
void InternalClient::setNoBorder(bool set)
|
|
{
|
|
if (!userCanSetNoBorder()) {
|
|
return;
|
|
}
|
|
if (m_userNoBorder == set) {
|
|
return;
|
|
}
|
|
m_userNoBorder = set;
|
|
updateDecoration(true);
|
|
}
|
|
|
|
void InternalClient::updateDecoration(bool check_workspace_pos, bool force)
|
|
{
|
|
if (!force && isDecorated() == !noBorder()) {
|
|
return;
|
|
}
|
|
|
|
const QRect oldFrameGeometry = geometry();
|
|
const QRect oldClientGeometry = oldFrameGeometry - frameMargins();
|
|
|
|
GeometryUpdatesBlocker blocker(this);
|
|
|
|
if (force) {
|
|
destroyDecoration();
|
|
}
|
|
|
|
if (!noBorder()) {
|
|
createDecoration(oldClientGeometry);
|
|
} else {
|
|
destroyDecoration();
|
|
}
|
|
|
|
getShadow();
|
|
|
|
if (check_workspace_pos) {
|
|
checkWorkspacePosition(oldFrameGeometry, -2, oldClientGeometry);
|
|
}
|
|
}
|
|
|
|
void InternalClient::updateColorScheme()
|
|
{
|
|
AbstractClient::updateColorScheme(QString());
|
|
}
|
|
|
|
void InternalClient::showOnScreenEdge()
|
|
{
|
|
}
|
|
|
|
void InternalClient::destroyClient()
|
|
{
|
|
if (isMoveResize()) {
|
|
leaveMoveResize();
|
|
}
|
|
|
|
Deleted *deleted = Deleted::create(this);
|
|
emit windowClosed(this, deleted);
|
|
|
|
destroyDecoration();
|
|
|
|
workspace()->removeInternalClient(this);
|
|
|
|
deleted->unrefWindow();
|
|
m_internalWindow = nullptr;
|
|
|
|
delete this;
|
|
}
|
|
|
|
void InternalClient::present(const QSharedPointer<QOpenGLFramebufferObject> fbo)
|
|
{
|
|
Q_ASSERT(m_internalImage.isNull());
|
|
|
|
const QSize bufferSize = fbo->size() / bufferScale();
|
|
|
|
commitGeometry(QRect(pos(), sizeForClientSize(bufferSize)));
|
|
markAsMapped();
|
|
|
|
if (m_internalFBO != fbo) {
|
|
discardWindowPixmap();
|
|
m_internalFBO = fbo;
|
|
}
|
|
|
|
setDepth(32);
|
|
addDamageFull();
|
|
addRepaintFull();
|
|
}
|
|
|
|
void InternalClient::present(const QImage &image, const QRegion &damage)
|
|
{
|
|
Q_ASSERT(m_internalFBO.isNull());
|
|
|
|
const QSize bufferSize = image.size() / bufferScale();
|
|
|
|
commitGeometry(QRect(pos(), sizeForClientSize(bufferSize)));
|
|
markAsMapped();
|
|
|
|
if (m_internalImage.size() != image.size()) {
|
|
discardWindowPixmap();
|
|
}
|
|
|
|
m_internalImage = image;
|
|
|
|
setDepth(32);
|
|
addDamage(damage);
|
|
addRepaint(damage.translated(borderLeft(), borderTop()));
|
|
}
|
|
|
|
QWindow *InternalClient::internalWindow() const
|
|
{
|
|
return m_internalWindow;
|
|
}
|
|
|
|
bool InternalClient::acceptsFocus() const
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bool InternalClient::belongsToSameApplication(const AbstractClient *other, SameApplicationChecks checks) const
|
|
{
|
|
Q_UNUSED(checks)
|
|
|
|
return qobject_cast<const InternalClient *>(other) != nullptr;
|
|
}
|
|
|
|
void InternalClient::changeMaximize(bool horizontal, bool vertical, bool adjust)
|
|
{
|
|
Q_UNUSED(horizontal)
|
|
Q_UNUSED(vertical)
|
|
Q_UNUSED(adjust)
|
|
|
|
// Internal clients are not maximizable.
|
|
}
|
|
|
|
void InternalClient::destroyDecoration()
|
|
{
|
|
if (!isDecorated()) {
|
|
return;
|
|
}
|
|
|
|
const QRect clientGeometry = mapToClient(geometry());
|
|
AbstractClient::destroyDecoration();
|
|
setGeometry(clientGeometry);
|
|
}
|
|
|
|
void InternalClient::doMove(int x, int y)
|
|
{
|
|
Q_UNUSED(x)
|
|
Q_UNUSED(y)
|
|
|
|
syncGeometryToInternalWindow();
|
|
}
|
|
|
|
void InternalClient::doResizeSync()
|
|
{
|
|
requestGeometry(moveResizeGeometry());
|
|
}
|
|
|
|
void InternalClient::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();
|
|
}
|
|
}
|
|
|
|
QRect InternalClient::mapFromClient(const QRect &rect) const
|
|
{
|
|
return rect + frameMargins();
|
|
}
|
|
|
|
QRect InternalClient::mapToClient(const QRect &rect) const
|
|
{
|
|
return rect - frameMargins();
|
|
}
|
|
|
|
void InternalClient::createDecoration(const QRect &rect)
|
|
{
|
|
KDecoration2::Decoration *decoration = Decoration::DecorationBridge::self()->createDecoration(this);
|
|
if (decoration) {
|
|
QMetaObject::invokeMethod(decoration, "update", Qt::QueuedConnection);
|
|
connect(decoration, &KDecoration2::Decoration::shadowChanged, this, &Toplevel::getShadow);
|
|
connect(decoration, &KDecoration2::Decoration::bordersChanged, this,
|
|
[this]() {
|
|
GeometryUpdatesBlocker blocker(this);
|
|
const QRect oldGeometry = geometry();
|
|
if (!isShade()) {
|
|
checkWorkspacePosition(oldGeometry);
|
|
}
|
|
emit geometryShapeChanged(this, oldGeometry);
|
|
}
|
|
);
|
|
}
|
|
|
|
const QRect oldFrameGeometry = geometry();
|
|
|
|
setDecoration(decoration);
|
|
setGeometry(mapFromClient(rect));
|
|
|
|
emit geometryShapeChanged(this, oldFrameGeometry);
|
|
}
|
|
|
|
void InternalClient::requestGeometry(const QRect &rect)
|
|
{
|
|
if (m_internalWindow) {
|
|
m_internalWindow->setGeometry(mapToClient(rect));
|
|
}
|
|
}
|
|
|
|
void InternalClient::commitGeometry(const QRect &rect)
|
|
{
|
|
if (geom == rect && pendingGeometryUpdate() == PendingGeometryNone) {
|
|
return;
|
|
}
|
|
|
|
geom = rect;
|
|
|
|
m_clientSize = mapToClient(geometry()).size();
|
|
|
|
addWorkspaceRepaint(visibleRect());
|
|
syncGeometryToInternalWindow();
|
|
|
|
const QRect oldGeometry = geometryBeforeUpdateBlocking();
|
|
updateGeometryBeforeUpdateBlocking();
|
|
emit geometryShapeChanged(this, oldGeometry);
|
|
|
|
if (isResize()) {
|
|
performMoveResize();
|
|
}
|
|
}
|
|
|
|
void InternalClient::setCaption(const QString &caption)
|
|
{
|
|
if (m_captionNormal == caption) {
|
|
return;
|
|
}
|
|
|
|
m_captionNormal = caption;
|
|
|
|
const QString oldCaptionSuffix = m_captionSuffix;
|
|
updateCaption();
|
|
|
|
if (m_captionSuffix == oldCaptionSuffix) {
|
|
emit captionChanged();
|
|
}
|
|
}
|
|
|
|
void InternalClient::markAsMapped()
|
|
{
|
|
if (!ready_for_painting) {
|
|
setReadyForPainting();
|
|
workspace()->addInternalClient(this);
|
|
}
|
|
}
|
|
|
|
void InternalClient::syncGeometryToInternalWindow()
|
|
{
|
|
if (m_internalWindow->geometry() == mapToClient(geometry())) {
|
|
return;
|
|
}
|
|
|
|
QTimer::singleShot(0, this, [this] { requestGeometry(geometry()); });
|
|
}
|
|
|
|
void InternalClient::updateInternalWindowGeometry()
|
|
{
|
|
if (isMoveResize()) {
|
|
return;
|
|
}
|
|
|
|
commitGeometry(mapFromClient(m_internalWindow->geometry()));
|
|
}
|
|
|
|
}
|