/* KWin - the KDE window manager This file is part of the KDE project. SPDX-FileCopyrightText: 2019 Martin Flöser SPDX-FileCopyrightText: 2019 Vlad Zahorodnii SPDX-License-Identifier: GPL-2.0-or-later */ #include "internalwindow.h" #include "decorations/decorationbridge.h" #include "deleted.h" #include "platform.h" #include "surfaceitem.h" #include "windowitem.h" #include "workspace.h" #include #include #include #include Q_DECLARE_METATYPE(NET::WindowType) static const QByteArray s_skipClosePropertyName = QByteArrayLiteral("KWIN_SKIP_CLOSE_ANIMATION"); static const QByteArray s_shadowEnabledPropertyName = QByteArrayLiteral("kwin_shadow_enabled"); namespace KWin { InternalWindow::InternalWindow(QWindow *handle) : m_handle(handle) , m_internalWindowFlags(handle->flags()) { connect(m_handle, &QWindow::xChanged, this, &InternalWindow::updateInternalWindowGeometry); connect(m_handle, &QWindow::yChanged, this, &InternalWindow::updateInternalWindowGeometry); connect(m_handle, &QWindow::widthChanged, this, &InternalWindow::updateInternalWindowGeometry); connect(m_handle, &QWindow::heightChanged, this, &InternalWindow::updateInternalWindowGeometry); connect(m_handle, &QWindow::windowTitleChanged, this, &InternalWindow::setCaption); connect(m_handle, &QWindow::opacityChanged, this, &InternalWindow::setOpacity); connect(m_handle, &QWindow::destroyed, this, &InternalWindow::destroyWindow); const QVariant windowType = m_handle->property("kwin_windowType"); if (!windowType.isNull()) { m_windowType = windowType.value(); } setCaption(m_handle->title()); setIcon(QIcon::fromTheme(QStringLiteral("kwin"))); setOnAllDesktops(true); setOpacity(m_handle->opacity()); setSkipCloseAnimation(m_handle->property(s_skipClosePropertyName).toBool()); // Create scene window, effect window, and update server-side shadow. setupCompositing(); updateColorScheme(); blockGeometryUpdates(true); commitGeometry(m_handle->geometry()); updateDecoration(true); moveResize(clientRectToFrameRect(m_handle->geometry())); blockGeometryUpdates(false); m_handle->installEventFilter(this); } InternalWindow::~InternalWindow() { } WindowItem *InternalWindow::createItem() { return new WindowItemInternal(this); } bool InternalWindow::isClient() const { return true; } bool InternalWindow::hitTest(const QPoint &point) const { if (!Window::hitTest(point)) { return false; } const QRegion mask = m_handle->mask(); if (!mask.isEmpty() && !mask.contains(mapToLocal(point))) { return false; } else if (m_handle->property("outputOnly").toBool()) { return false; } return true; } void InternalWindow::pointerEnterEvent(const QPoint &globalPos) { Window::pointerEnterEvent(globalPos); QEnterEvent enterEvent(pos(), pos(), globalPos); QCoreApplication::sendEvent(m_handle, &enterEvent); } void InternalWindow::pointerLeaveEvent() { Window::pointerLeaveEvent(); QEvent event(QEvent::Leave); QCoreApplication::sendEvent(m_handle, &event); } bool InternalWindow::eventFilter(QObject *watched, QEvent *event) { if (watched == m_handle && event->type() == QEvent::DynamicPropertyChange) { QDynamicPropertyChangeEvent *pe = static_cast(event); if (pe->propertyName() == s_skipClosePropertyName) { setSkipCloseAnimation(m_handle->property(s_skipClosePropertyName).toBool()); } if (pe->propertyName() == s_shadowEnabledPropertyName) { // Some dialog e.g. Plasma::Dialog may update shadow in the middle of rendering. // The opengl context changed by updateShadow may break the QML Window rendering // and cause crash. QMetaObject::invokeMethod( this, [this]() { updateShadow(); }, Qt::QueuedConnection); } if (pe->propertyName() == "kwin_windowType") { m_windowType = m_handle->property("kwin_windowType").value(); workspace()->updateClientArea(); } } return false; } qreal InternalWindow::bufferScale() const { if (m_handle) { return m_handle->devicePixelRatio(); } return 1; } QString InternalWindow::captionNormal() const { return m_captionNormal; } QString InternalWindow::captionSuffix() const { return m_captionSuffix; } QSize InternalWindow::minSize() const { return m_handle->minimumSize(); } QSize InternalWindow::maxSize() const { return m_handle->maximumSize(); } NET::WindowType InternalWindow::windowType(bool direct, int supported_types) const { Q_UNUSED(direct) Q_UNUSED(supported_types) return m_windowType; } void InternalWindow::killWindow() { // We don't kill our internal windows. } bool InternalWindow::isPopupWindow() const { if (Window::isPopupWindow()) { return true; } return m_internalWindowFlags.testFlag(Qt::Popup); } QByteArray InternalWindow::windowRole() const { return QByteArray(); } void InternalWindow::closeWindow() { if (m_handle) { m_handle->hide(); } } bool InternalWindow::isCloseable() const { return true; } bool InternalWindow::isMovable() const { return true; } bool InternalWindow::isMovableAcrossScreens() const { return true; } bool InternalWindow::isResizable() const { return true; } bool InternalWindow::isPlaceable() const { return !m_internalWindowFlags.testFlag(Qt::BypassWindowManagerHint) && !m_internalWindowFlags.testFlag(Qt::Popup); } bool InternalWindow::noBorder() const { return m_userNoBorder || m_internalWindowFlags.testFlag(Qt::FramelessWindowHint) || m_internalWindowFlags.testFlag(Qt::Popup); } bool InternalWindow::userCanSetNoBorder() const { return !m_internalWindowFlags.testFlag(Qt::FramelessWindowHint) || m_internalWindowFlags.testFlag(Qt::Popup); } bool InternalWindow::wantsInput() const { return false; } bool InternalWindow::isInternal() const { return true; } bool InternalWindow::isLockScreen() const { if (m_handle) { return m_handle->property("org_kde_ksld_emergency").toBool(); } return false; } bool InternalWindow::isOutline() const { if (m_handle) { return m_handle->property("__kwin_outline").toBool(); } return false; } bool InternalWindow::isShown() const { return readyForPainting(); } bool InternalWindow::isHiddenInternal() const { return false; } void InternalWindow::hideClient() { } void InternalWindow::showClient() { } void InternalWindow::resizeWithChecks(const QSize &size) { if (!m_handle) { return; } const QRect area = workspace()->clientArea(WorkArea, this); resize(size.boundedTo(area.size())); } void InternalWindow::moveResizeInternal(const QRect &rect, MoveResizeMode mode) { if (areGeometryUpdatesBlocked()) { setPendingMoveResizeMode(mode); return; } const QSize requestedClientSize = frameSizeToClientSize(rect.size()); if (clientSize() == requestedClientSize) { commitGeometry(rect); } else { requestGeometry(rect); } } Window *InternalWindow::findModal(bool allow_itself) { Q_UNUSED(allow_itself) return nullptr; } bool InternalWindow::takeFocus() { return false; } void InternalWindow::setNoBorder(bool set) { if (!userCanSetNoBorder()) { return; } if (m_userNoBorder == set) { return; } m_userNoBorder = set; updateDecoration(true); } void InternalWindow::createDecoration(const QRect &oldGeometry) { setDecoration(std::shared_ptr(Decoration::DecorationBridge::self()->createDecoration(this))); moveResize(oldGeometry); Q_EMIT geometryShapeChanged(this, oldGeometry); } void InternalWindow::destroyDecoration() { const QSize clientSize = frameSizeToClientSize(moveResizeGeometry().size()); setDecoration(nullptr); resize(clientSize); } void InternalWindow::updateDecoration(bool check_workspace_pos, bool force) { if (!force && isDecorated() == !noBorder()) { return; } GeometryUpdatesBlocker blocker(this); const QRect oldFrameGeometry = frameGeometry(); if (force) { destroyDecoration(); } if (!noBorder()) { createDecoration(oldFrameGeometry); } else { destroyDecoration(); } updateShadow(); if (check_workspace_pos) { checkWorkspacePosition(oldFrameGeometry); } } void InternalWindow::invalidateDecoration() { updateDecoration(true, true); } void InternalWindow::destroyWindow() { markAsZombie(); if (isInteractiveMoveResize()) { leaveInteractiveMoveResize(); Q_EMIT clientFinishUserMovedResized(this); } Deleted *deleted = Deleted::create(this); Q_EMIT windowClosed(this, deleted); destroyDecoration(); workspace()->removeInternalWindow(this); deleted->unrefWindow(); m_handle = nullptr; delete this; } bool InternalWindow::hasPopupGrab() const { return !m_handle->flags().testFlag(Qt::WindowTransparentForInput) && m_handle->flags().testFlag(Qt::Popup) && !m_handle->flags().testFlag(Qt::ToolTip); } void InternalWindow::popupDone() { m_handle->hide(); } void InternalWindow::present(const std::shared_ptr fbo) { Q_ASSERT(m_internalImage.isNull()); const QSize bufferSize = fbo->size() / bufferScale(); commitGeometry(QRect(pos(), clientSizeToFrameSize(bufferSize))); markAsMapped(); m_internalFBO = fbo; setDepth(32); surfaceItem()->addDamage(surfaceItem()->rect()); } void InternalWindow::present(const QImage &image, const QRegion &damage) { Q_ASSERT(m_internalFBO == nullptr); const QSize bufferSize = image.size() / bufferScale(); commitGeometry(QRect(pos(), clientSizeToFrameSize(bufferSize))); markAsMapped(); m_internalImage = image; setDepth(32); surfaceItem()->addDamage(damage); } QWindow *InternalWindow::handle() const { return m_handle; } bool InternalWindow::acceptsFocus() const { return false; } bool InternalWindow::belongsToSameApplication(const Window *other, SameApplicationChecks checks) const { Q_UNUSED(checks) const InternalWindow *otherInternal = qobject_cast(other); if (!otherInternal) { return false; } if (otherInternal == this) { return true; } return otherInternal->handle()->isAncestorOf(handle()) || handle()->isAncestorOf(otherInternal->handle()); } void InternalWindow::doInteractiveResizeSync() { requestGeometry(moveResizeGeometry()); } void InternalWindow::updateCaption() { const QString oldSuffix = m_captionSuffix; const auto shortcut = shortcutCaptionSuffix(); m_captionSuffix = shortcut; if ((!isSpecialWindow() || isToolbar()) && findWindowWithSameCaption()) { int i = 2; do { m_captionSuffix = shortcut + QLatin1String(" <") + QString::number(i) + QLatin1Char('>'); i++; } while (findWindowWithSameCaption()); } if (m_captionSuffix != oldSuffix) { Q_EMIT captionChanged(); } } void InternalWindow::requestGeometry(const QRect &rect) { if (m_handle) { m_handle->setGeometry(frameRectToClientRect(rect)); } } void InternalWindow::commitGeometry(const QRect &rect) { // The client geometry and the buffer geometry are the same. const QRect oldClientGeometry = m_clientGeometry; const QRect oldFrameGeometry = m_frameGeometry; const Output *oldOutput = m_output; m_clientGeometry = frameRectToClientRect(rect); m_frameGeometry = rect; m_bufferGeometry = m_clientGeometry; if (oldClientGeometry == m_clientGeometry && oldFrameGeometry == m_frameGeometry) { return; } m_output = kwinApp()->platform()->outputAt(rect.center()); syncGeometryToInternalWindow(); if (oldClientGeometry != m_clientGeometry) { Q_EMIT bufferGeometryChanged(this, oldClientGeometry); Q_EMIT clientGeometryChanged(this, oldClientGeometry); } if (oldFrameGeometry != m_frameGeometry) { Q_EMIT frameGeometryChanged(this, oldFrameGeometry); } if (oldOutput != m_output) { Q_EMIT screenChanged(); } Q_EMIT geometryShapeChanged(this, oldFrameGeometry); } void InternalWindow::setCaption(const QString &caption) { if (m_captionNormal == caption) { return; } m_captionNormal = caption; const QString oldCaptionSuffix = m_captionSuffix; updateCaption(); if (m_captionSuffix == oldCaptionSuffix) { Q_EMIT captionChanged(); } } void InternalWindow::markAsMapped() { if (!ready_for_painting) { setReadyForPainting(); workspace()->addInternalWindow(this); } } void InternalWindow::syncGeometryToInternalWindow() { if (m_handle->geometry() == frameRectToClientRect(frameGeometry())) { return; } QTimer::singleShot(0, this, [this] { requestGeometry(frameGeometry()); }); } void InternalWindow::updateInternalWindowGeometry() { if (!isInteractiveMoveResize()) { const QRect rect = clientRectToFrameRect(m_handle->geometry()); setMoveResizeGeometry(rect); commitGeometry(rect); } } }