diff --git a/abstract_client.cpp b/abstract_client.cpp index 363e4fa50..4b243d6ff 100644 --- a/abstract_client.cpp +++ b/abstract_client.cpp @@ -1,1995 +1,2001 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2015 Martin Gräßlin +Copyright (C) 2019 Vlad Zagorodniy 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 . *********************************************************************/ #include "abstract_client.h" #include "appmenu.h" #include "decorations/decoratedclient.h" #include "decorations/decorationpalette.h" #include "decorations/decorationbridge.h" #include "cursor.h" #include "effects.h" #include "focuschain.h" #include "outline.h" #include "screens.h" #ifdef KWIN_BUILD_TABBOX #include "tabbox.h" #endif #include "screenedge.h" #include "useractions.h" #include "workspace.h" #include "wayland_server.h" #include #include #include #include #include namespace KWin { QHash> AbstractClient::s_palettes; std::shared_ptr AbstractClient::s_defaultPalette; AbstractClient::AbstractClient() : Toplevel() #ifdef KWIN_BUILD_TABBOX , m_tabBoxClient(QSharedPointer(new TabBox::TabBoxClientImpl(this))) #endif , m_colorScheme(QStringLiteral("kdeglobals")) { connect(this, &AbstractClient::geometryShapeChanged, this, &AbstractClient::geometryChanged); auto signalMaximizeChanged = static_cast(&AbstractClient::clientMaximizedStateChanged); connect(this, signalMaximizeChanged, this, &AbstractClient::geometryChanged); connect(this, &AbstractClient::clientStepUserMovedResized, this, &AbstractClient::geometryChanged); connect(this, &AbstractClient::clientStartUserMovedResized, this, &AbstractClient::moveResizedChanged); connect(this, &AbstractClient::clientFinishUserMovedResized, this, &AbstractClient::moveResizedChanged); connect(this, &AbstractClient::clientStartUserMovedResized, this, &AbstractClient::removeCheckScreenConnection); connect(this, &AbstractClient::clientFinishUserMovedResized, this, &AbstractClient::setupCheckScreenConnection); connect(this, &AbstractClient::paletteChanged, this, &AbstractClient::triggerDecorationRepaint); connect(Decoration::DecorationBridge::self(), &QObject::destroyed, this, &AbstractClient::destroyDecoration); // If the user manually moved the window, don't restore it after the keyboard closes connect(this, &AbstractClient::clientFinishUserMovedResized, this, [this] () { m_keyboardGeometryRestore = QRect(); }); connect(this, qOverload(&AbstractClient::clientMaximizedStateChanged), this, [this] () { m_keyboardGeometryRestore = QRect(); }); connect(this, &AbstractClient::fullScreenChanged, this, [this] () { m_keyboardGeometryRestore = QRect(); }); // replace on-screen-display on size changes connect(this, &AbstractClient::geometryShapeChanged, this, [this] (Toplevel *c, const QRect &old) { Q_UNUSED(c) if (isOnScreenDisplay() && !geometry().isEmpty() && old.size() != geometry().size() && !isInitialPositionSet()) { GeometryUpdatesBlocker blocker(this); const QRect area = workspace()->clientArea(PlacementArea, Screens::self()->current(), desktop()); Placement::self()->place(this, area); setGeometryRestore(geometry()); } } ); connect(this, &AbstractClient::paddingChanged, this, [this]() { m_visibleRectBeforeGeometryUpdate = visibleRect(); }); connect(ApplicationMenu::self(), &ApplicationMenu::applicationMenuEnabledChanged, this, [this] { emit hasApplicationMenuChanged(hasApplicationMenu()); }); } AbstractClient::~AbstractClient() { Q_ASSERT(m_blockGeometryUpdates == 0); Q_ASSERT(m_decoration.decoration == nullptr); } void AbstractClient::updateMouseGrab() { } bool AbstractClient::belongToSameApplication(const AbstractClient *c1, const AbstractClient *c2, SameApplicationChecks checks) { return c1->belongsToSameApplication(c2, checks); } bool AbstractClient::isTransient() const { return false; } void AbstractClient::setClientShown(bool shown) { Q_UNUSED(shown) } MaximizeMode AbstractClient::requestedMaximizeMode() const { return maximizeMode(); } xcb_timestamp_t AbstractClient::userTime() const { return XCB_TIME_CURRENT_TIME; } void AbstractClient::setSkipSwitcher(bool set) { set = rules()->checkSkipSwitcher(set); if (set == skipSwitcher()) return; m_skipSwitcher = set; doSetSkipSwitcher(); updateWindowRules(Rules::SkipSwitcher); emit skipSwitcherChanged(); } void AbstractClient::setSkipPager(bool b) { b = rules()->checkSkipPager(b); if (b == skipPager()) return; m_skipPager = b; doSetSkipPager(); updateWindowRules(Rules::SkipPager); emit skipPagerChanged(); } void AbstractClient::doSetSkipPager() { } void AbstractClient::setSkipTaskbar(bool b) { int was_wants_tab_focus = wantsTabFocus(); if (b == skipTaskbar()) return; m_skipTaskbar = b; doSetSkipTaskbar(); updateWindowRules(Rules::SkipTaskbar); if (was_wants_tab_focus != wantsTabFocus()) { FocusChain::self()->update(this, isActive() ? FocusChain::MakeFirst : FocusChain::Update); } emit skipTaskbarChanged(); } void AbstractClient::setOriginalSkipTaskbar(bool b) { m_originalSkipTaskbar = rules()->checkSkipTaskbar(b); setSkipTaskbar(m_originalSkipTaskbar); } void AbstractClient::doSetSkipTaskbar() { } void AbstractClient::doSetSkipSwitcher() { } void AbstractClient::setIcon(const QIcon &icon) { m_icon = icon; emit iconChanged(); } void AbstractClient::setActive(bool act) { if (m_active == act) { return; } m_active = act; const int ruledOpacity = m_active ? rules()->checkOpacityActive(qRound(opacity() * 100.0)) : rules()->checkOpacityInactive(qRound(opacity() * 100.0)); setOpacity(ruledOpacity / 100.0); workspace()->setActiveClient(act ? this : nullptr); if (!m_active) cancelAutoRaise(); if (!m_active && shadeMode() == ShadeActivated) setShade(ShadeNormal); StackingUpdatesBlocker blocker(workspace()); workspace()->updateClientLayer(this); // active windows may get different layer auto mainclients = mainClients(); for (auto it = mainclients.constBegin(); it != mainclients.constEnd(); ++it) if ((*it)->isFullScreen()) // fullscreens go high even if their transient is active workspace()->updateClientLayer(*it); doSetActive(); emit activeChanged(); updateMouseGrab(); } void AbstractClient::doSetActive() { } Layer AbstractClient::layer() const { if (m_layer == UnknownLayer) const_cast< AbstractClient* >(this)->m_layer = belongsToLayer(); return m_layer; } void AbstractClient::updateLayer() { if (layer() == belongsToLayer()) return; StackingUpdatesBlocker blocker(workspace()); invalidateLayer(); // invalidate, will be updated when doing restacking for (auto it = transients().constBegin(), end = transients().constEnd(); it != end; ++it) (*it)->updateLayer(); } void AbstractClient::invalidateLayer() { m_layer = UnknownLayer; } Layer AbstractClient::belongsToLayer() const { // NOTICE while showingDesktop, desktops move to the AboveLayer // (interchangeable w/ eg. yakuake etc. which will at first remain visible) // and the docks move into the NotificationLayer (which is between Above- and // ActiveLayer, so that active fullscreen windows will still cover everything) // Since the desktop is also activated, nothing should be in the ActiveLayer, though if (isInternal()) return UnmanagedLayer; if (isDesktop()) return workspace()->showingDesktop() ? AboveLayer : DesktopLayer; if (isSplash()) // no damn annoying splashscreens return NormalLayer; // getting in the way of everything else if (isDock()) { if (workspace()->showingDesktop()) return NotificationLayer; return layerForDock(); } if (isOnScreenDisplay()) return OnScreenDisplayLayer; if (isNotification()) return NotificationLayer; if (isCriticalNotification()) return CriticalNotificationLayer; if (workspace()->showingDesktop() && belongsToDesktop()) { return AboveLayer; } if (keepBelow()) return BelowLayer; if (isActiveFullScreen()) return ActiveLayer; if (keepAbove()) return AboveLayer; return NormalLayer; } bool AbstractClient::belongsToDesktop() const { return false; } Layer AbstractClient::layerForDock() const { // slight hack for the 'allow window to cover panel' Kicker setting // don't move keepbelow docks below normal window, but only to the same // layer, so that both may be raised to cover the other if (keepBelow()) return NormalLayer; if (keepAbove()) // slight hack for the autohiding panels return AboveLayer; return DockLayer; } void AbstractClient::setKeepAbove(bool b) { b = rules()->checkKeepAbove(b); if (b && !rules()->checkKeepBelow(false)) setKeepBelow(false); if (b == keepAbove()) { // force hint change if different if (info && bool(info->state() & NET::KeepAbove) != keepAbove()) info->setState(keepAbove() ? NET::KeepAbove : NET::States(), NET::KeepAbove); return; } m_keepAbove = b; if (info) { info->setState(keepAbove() ? NET::KeepAbove : NET::States(), NET::KeepAbove); } workspace()->updateClientLayer(this); updateWindowRules(Rules::Above); doSetKeepAbove(); emit keepAboveChanged(m_keepAbove); } void AbstractClient::doSetKeepAbove() { } void AbstractClient::setKeepBelow(bool b) { b = rules()->checkKeepBelow(b); if (b && !rules()->checkKeepAbove(false)) setKeepAbove(false); if (b == keepBelow()) { // force hint change if different if (info && bool(info->state() & NET::KeepBelow) != keepBelow()) info->setState(keepBelow() ? NET::KeepBelow : NET::States(), NET::KeepBelow); return; } m_keepBelow = b; if (info) { info->setState(keepBelow() ? NET::KeepBelow : NET::States(), NET::KeepBelow); } workspace()->updateClientLayer(this); updateWindowRules(Rules::Below); doSetKeepBelow(); emit keepBelowChanged(m_keepBelow); } void AbstractClient::doSetKeepBelow() { } void AbstractClient::startAutoRaise() { delete m_autoRaiseTimer; m_autoRaiseTimer = new QTimer(this); connect(m_autoRaiseTimer, &QTimer::timeout, this, &AbstractClient::autoRaise); m_autoRaiseTimer->setSingleShot(true); m_autoRaiseTimer->start(options->autoRaiseInterval()); } void AbstractClient::cancelAutoRaise() { delete m_autoRaiseTimer; m_autoRaiseTimer = nullptr; } void AbstractClient::autoRaise() { workspace()->raiseClient(this); cancelAutoRaise(); } bool AbstractClient::wantsTabFocus() const { return (isNormalWindow() || isDialog()) && wantsInput(); } bool AbstractClient::isSpecialWindow() const { // TODO return isDesktop() || isDock() || isSplash() || isToolbar() || isNotification() || isOnScreenDisplay() || isCriticalNotification(); } void AbstractClient::demandAttention(bool set) { if (isActive()) set = false; if (m_demandsAttention == set) return; m_demandsAttention = set; if (info) { info->setState(set ? NET::DemandsAttention : NET::States(), NET::DemandsAttention); } workspace()->clientAttentionChanged(this, set); emit demandsAttentionChanged(); } void AbstractClient::setDesktop(int desktop) { const int numberOfDesktops = VirtualDesktopManager::self()->count(); if (desktop != NET::OnAllDesktops) // Do range check desktop = qMax(1, qMin(numberOfDesktops, desktop)); desktop = qMin(numberOfDesktops, rules()->checkDesktop(desktop)); QVector desktops; if (desktop != NET::OnAllDesktops) { desktops << VirtualDesktopManager::self()->desktopForX11Id(desktop); } setDesktops(desktops); } void AbstractClient::setDesktops(QVector desktops) { //on x11 we can have only one desktop at a time if (kwinApp()->operationMode() == Application::OperationModeX11 && desktops.size() > 1) { desktops = QVector({desktops.last()}); } if (desktops == m_desktops) { return; } int was_desk = AbstractClient::desktop(); const bool wasOnCurrentDesktop = isOnCurrentDesktop() && was_desk >= 0; m_desktops = desktops; if (windowManagementInterface()) { if (m_desktops.isEmpty()) { windowManagementInterface()->setOnAllDesktops(true); } else { windowManagementInterface()->setOnAllDesktops(false); auto currentDesktops = windowManagementInterface()->plasmaVirtualDesktops(); for (auto desktop: m_desktops) { if (!currentDesktops.contains(desktop->id())) { windowManagementInterface()->addPlasmaVirtualDesktop(desktop->id()); } else { currentDesktops.removeOne(desktop->id()); } } for (auto desktopId: currentDesktops) { windowManagementInterface()->removePlasmaVirtualDesktop(desktopId); } } } if (info) { info->setDesktop(desktop()); } if ((was_desk == NET::OnAllDesktops) != (desktop() == NET::OnAllDesktops)) { // onAllDesktops changed workspace()->updateOnAllDesktopsOfTransients(this); } auto transients_stacking_order = workspace()->ensureStackingOrder(transients()); for (auto it = transients_stacking_order.constBegin(); it != transients_stacking_order.constEnd(); ++it) (*it)->setDesktops(desktops); if (isModal()) // if a modal dialog is moved, move the mainwindow with it as otherwise // the (just moved) modal dialog will confusingly return to the mainwindow with // the next desktop change { foreach (AbstractClient * c2, mainClients()) c2->setDesktops(desktops); } doSetDesktop(desktop(), was_desk); FocusChain::self()->update(this, FocusChain::MakeFirst); updateWindowRules(Rules::Desktop); emit desktopChanged(); if (wasOnCurrentDesktop != isOnCurrentDesktop()) emit desktopPresenceChanged(this, was_desk); emit x11DesktopIdsChanged(); } void AbstractClient::doSetDesktop(int desktop, int was_desk) { Q_UNUSED(desktop) Q_UNUSED(was_desk) } void AbstractClient::enterDesktop(VirtualDesktop *virtualDesktop) { if (m_desktops.contains(virtualDesktop)) { return; } auto desktops = m_desktops; desktops.append(virtualDesktop); setDesktops(desktops); } void AbstractClient::leaveDesktop(VirtualDesktop *virtualDesktop) { QVector currentDesktops; if (m_desktops.isEmpty()) { currentDesktops = VirtualDesktopManager::self()->desktops(); } else { currentDesktops = m_desktops; } if (!currentDesktops.contains(virtualDesktop)) { return; } auto desktops = currentDesktops; desktops.removeOne(virtualDesktop); setDesktops(desktops); } void AbstractClient::setOnAllDesktops(bool b) { if ((b && isOnAllDesktops()) || (!b && !isOnAllDesktops())) return; if (b) setDesktop(NET::OnAllDesktops); else setDesktop(VirtualDesktopManager::self()->current()); } QVector AbstractClient::x11DesktopIds() const { const auto desks = desktops(); QVector x11Ids; x11Ids.reserve(desks.count()); std::transform(desks.constBegin(), desks.constEnd(), std::back_inserter(x11Ids), [] (const VirtualDesktop *vd) { return vd->x11DesktopNumber(); } ); return x11Ids; } bool AbstractClient::isShadeable() const { return false; } void AbstractClient::setShade(bool set) { set ? setShade(ShadeNormal) : setShade(ShadeNone); } void AbstractClient::setShade(ShadeMode mode) { Q_UNUSED(mode) } ShadeMode AbstractClient::shadeMode() const { return ShadeNone; } AbstractClient::Position AbstractClient::titlebarPosition() const { // TODO: still needed, remove? return PositionTop; } bool AbstractClient::titlebarPositionUnderMouse() const { if (!isDecorated()) { return false; } const auto sectionUnderMouse = decoration()->sectionUnderMouse(); if (sectionUnderMouse == Qt::TitleBarArea) { return true; } // check other sections based on titlebarPosition switch (titlebarPosition()) { case AbstractClient::PositionTop: return (sectionUnderMouse == Qt::TopLeftSection || sectionUnderMouse == Qt::TopSection || sectionUnderMouse == Qt::TopRightSection); case AbstractClient::PositionLeft: return (sectionUnderMouse == Qt::TopLeftSection || sectionUnderMouse == Qt::LeftSection || sectionUnderMouse == Qt::BottomLeftSection); case AbstractClient::PositionRight: return (sectionUnderMouse == Qt::BottomRightSection || sectionUnderMouse == Qt::RightSection || sectionUnderMouse == Qt::TopRightSection); case AbstractClient::PositionBottom: return (sectionUnderMouse == Qt::BottomLeftSection || sectionUnderMouse == Qt::BottomSection || sectionUnderMouse == Qt::BottomRightSection); default: // nothing return false; } } void AbstractClient::setMinimized(bool set) { set ? minimize() : unminimize(); } void AbstractClient::minimize(bool avoid_animation) { if (!isMinimizable() || isMinimized()) return; if (isShade() && info) // NETWM restriction - KWindowInfo::isMinimized() == Hidden && !Shaded info->setState(NET::States(), NET::Shaded); m_minimized = true; doMinimize(); updateWindowRules(Rules::Minimize); FocusChain::self()->update(this, FocusChain::MakeFirstMinimized); // TODO: merge signal with s_minimized emit clientMinimized(this, !avoid_animation); emit minimizedChanged(); } void AbstractClient::unminimize(bool avoid_animation) { if (!isMinimized()) return; if (rules()->checkMinimize(false)) { return; } if (isShade() && info) // NETWM restriction - KWindowInfo::isMinimized() == Hidden && !Shaded info->setState(NET::Shaded, NET::Shaded); m_minimized = false; doMinimize(); updateWindowRules(Rules::Minimize); emit clientUnminimized(this, !avoid_animation); emit minimizedChanged(); } void AbstractClient::doMinimize() { } QPalette AbstractClient::palette() const { if (!m_palette) { return QPalette(); } return m_palette->palette(); } const Decoration::DecorationPalette *AbstractClient::decorationPalette() const { return m_palette.get(); } void AbstractClient::updateColorScheme(QString path) { if (path.isEmpty()) { path = QStringLiteral("kdeglobals"); } if (!m_palette || m_colorScheme != path) { m_colorScheme = path; if (m_palette) { disconnect(m_palette.get(), &Decoration::DecorationPalette::changed, this, &AbstractClient::handlePaletteChange); } auto it = s_palettes.find(m_colorScheme); if (it == s_palettes.end() || it->expired()) { m_palette = std::make_shared(m_colorScheme); if (m_palette->isValid()) { s_palettes[m_colorScheme] = m_palette; } else { if (!s_defaultPalette) { s_defaultPalette = std::make_shared(QStringLiteral("kdeglobals")); s_palettes[QStringLiteral("kdeglobals")] = s_defaultPalette; } m_palette = s_defaultPalette; } if (m_colorScheme == QStringLiteral("kdeglobals")) { s_defaultPalette = m_palette; } } else { m_palette = it->lock(); } connect(m_palette.get(), &Decoration::DecorationPalette::changed, this, &AbstractClient::handlePaletteChange); emit paletteChanged(palette()); emit colorSchemeChanged(); } } void AbstractClient::handlePaletteChange() { emit paletteChanged(palette()); } void AbstractClient::keepInArea(QRect area, bool partial) { if (partial) { // increase the area so that can have only 100 pixels in the area area.setLeft(qMin(area.left() - width() + 100, area.left())); area.setTop(qMin(area.top() - height() + 100, area.top())); area.setRight(qMax(area.right() + width() - 100, area.right())); area.setBottom(qMax(area.bottom() + height() - 100, area.bottom())); } if (!partial) { // resize to fit into area if (area.width() < width() || area.height() < height()) resizeWithChecks(qMin(area.width(), width()), qMin(area.height(), height())); } int tx = x(), ty = y(); if (geometry().right() > area.right() && width() <= area.width()) tx = area.right() - width() + 1; if (geometry().bottom() > area.bottom() && height() <= area.height()) ty = area.bottom() - height() + 1; if (!area.contains(geometry().topLeft())) { if (tx < area.x()) tx = area.x(); if (ty < area.y()) ty = area.y(); } if (tx != x() || ty != y()) move(tx, ty); } QSize AbstractClient::maxSize() const { return rules()->checkMaxSize(QSize(INT_MAX, INT_MAX)); } QSize AbstractClient::minSize() const { return rules()->checkMinSize(QSize(0, 0)); } void AbstractClient::updateMoveResize(const QPointF ¤tGlobalCursor) { handleMoveResize(pos(), currentGlobalCursor.toPoint()); } bool AbstractClient::hasStrut() const { return false; } void AbstractClient::setupWindowManagementInterface() { if (m_windowManagementInterface) { // already setup return; } if (!waylandServer() || !surface()) { return; } if (!waylandServer()->windowManagement()) { return; } using namespace KWayland::Server; auto w = waylandServer()->windowManagement()->createWindow(waylandServer()->windowManagement()); w->setTitle(caption()); w->setVirtualDesktop(isOnAllDesktops() ? 0 : desktop() - 1); w->setActive(isActive()); w->setFullscreen(isFullScreen()); w->setKeepAbove(keepAbove()); w->setKeepBelow(keepBelow()); w->setMaximized(maximizeMode() == KWin::MaximizeFull); w->setMinimized(isMinimized()); w->setOnAllDesktops(isOnAllDesktops()); w->setDemandsAttention(isDemandingAttention()); w->setCloseable(isCloseable()); w->setMaximizeable(isMaximizable()); w->setMinimizeable(isMinimizable()); w->setFullscreenable(isFullScreenable()); w->setIcon(icon()); auto updateAppId = [this, w] { w->setAppId(QString::fromUtf8(m_desktopFileName.isEmpty() ? resourceClass() : m_desktopFileName)); }; updateAppId(); w->setSkipTaskbar(skipTaskbar()); w->setSkipSwitcher(skipSwitcher()); w->setPid(pid()); w->setShadeable(isShadeable()); w->setShaded(isShade()); w->setResizable(isResizable()); w->setMovable(isMovable()); w->setVirtualDesktopChangeable(true); // FIXME Matches Client::actionSupported(), but both should be implemented. w->setParentWindow(transientFor() ? transientFor()->windowManagementInterface() : nullptr); w->setGeometry(geometry()); connect(this, &AbstractClient::skipTaskbarChanged, w, [w, this] { w->setSkipTaskbar(skipTaskbar()); } ); connect(this, &AbstractClient::skipSwitcherChanged, w, [w, this] { w->setSkipSwitcher(skipSwitcher()); } ); connect(this, &AbstractClient::captionChanged, w, [w, this] { w->setTitle(caption()); }); connect(this, &AbstractClient::activeChanged, w, [w, this] { w->setActive(isActive()); }); connect(this, &AbstractClient::fullScreenChanged, w, [w, this] { w->setFullscreen(isFullScreen()); }); connect(this, &AbstractClient::keepAboveChanged, w, &PlasmaWindowInterface::setKeepAbove); connect(this, &AbstractClient::keepBelowChanged, w, &PlasmaWindowInterface::setKeepBelow); connect(this, &AbstractClient::minimizedChanged, w, [w, this] { w->setMinimized(isMinimized()); }); connect(this, static_cast(&AbstractClient::clientMaximizedStateChanged), w, [w] (KWin::AbstractClient *c, MaximizeMode mode) { Q_UNUSED(c); w->setMaximized(mode == KWin::MaximizeFull); } ); connect(this, &AbstractClient::demandsAttentionChanged, w, [w, this] { w->setDemandsAttention(isDemandingAttention()); }); connect(this, &AbstractClient::iconChanged, w, [w, this] { w->setIcon(icon()); } ); connect(this, &AbstractClient::windowClassChanged, w, updateAppId); connect(this, &AbstractClient::desktopFileNameChanged, w, updateAppId); connect(this, &AbstractClient::shadeChanged, w, [w, this] { w->setShaded(isShade()); }); connect(this, &AbstractClient::transientChanged, w, [w, this] { w->setParentWindow(transientFor() ? transientFor()->windowManagementInterface() : nullptr); } ); connect(this, &AbstractClient::geometryChanged, w, [w, this] { w->setGeometry(geometry()); } ); connect(w, &PlasmaWindowInterface::closeRequested, this, [this] { closeWindow(); }); connect(w, &PlasmaWindowInterface::moveRequested, this, [this] { Cursor::setPos(geometry().center()); performMouseCommand(Options::MouseMove, Cursor::pos()); } ); connect(w, &PlasmaWindowInterface::resizeRequested, this, [this] { Cursor::setPos(geometry().bottomRight()); performMouseCommand(Options::MouseResize, Cursor::pos()); } ); connect(w, &PlasmaWindowInterface::virtualDesktopRequested, this, [this] (quint32 desktop) { workspace()->sendClientToDesktop(this, desktop + 1, true); } ); connect(w, &PlasmaWindowInterface::fullscreenRequested, this, [this] (bool set) { setFullScreen(set, false); } ); connect(w, &PlasmaWindowInterface::minimizedRequested, this, [this] (bool set) { if (set) { minimize(); } else { unminimize(); } } ); connect(w, &PlasmaWindowInterface::maximizedRequested, this, [this] (bool set) { maximize(set ? MaximizeFull : MaximizeRestore); } ); connect(w, &PlasmaWindowInterface::keepAboveRequested, this, [this] (bool set) { setKeepAbove(set); } ); connect(w, &PlasmaWindowInterface::keepBelowRequested, this, [this] (bool set) { setKeepBelow(set); } ); connect(w, &PlasmaWindowInterface::demandsAttentionRequested, this, [this] (bool set) { demandAttention(set); } ); connect(w, &PlasmaWindowInterface::activeRequested, this, [this] (bool set) { if (set) { workspace()->activateClient(this, true); } } ); connect(w, &PlasmaWindowInterface::shadedRequested, this, [this] (bool set) { setShade(set); } ); for (const auto vd : m_desktops) { w->addPlasmaVirtualDesktop(vd->id()); } //this is only for the legacy connect(this, &AbstractClient::desktopChanged, w, [w, this] { if (isOnAllDesktops()) { w->setOnAllDesktops(true); return; } w->setVirtualDesktop(desktop() - 1); w->setOnAllDesktops(false); } ); //Plasma Virtual desktop management //show/hide when the window enters/exits from desktop connect(w, &PlasmaWindowInterface::enterPlasmaVirtualDesktopRequested, this, [this] (const QString &desktopId) { VirtualDesktop *vd = VirtualDesktopManager::self()->desktopForId(desktopId.toUtf8()); if (vd) { enterDesktop(vd); } } ); connect(w, &PlasmaWindowInterface::enterNewPlasmaVirtualDesktopRequested, this, [this] () { VirtualDesktopManager::self()->setCount(VirtualDesktopManager::self()->count() + 1); enterDesktop(VirtualDesktopManager::self()->desktops().last()); } ); connect(w, &PlasmaWindowInterface::leavePlasmaVirtualDesktopRequested, this, [this] (const QString &desktopId) { VirtualDesktop *vd = VirtualDesktopManager::self()->desktopForId(desktopId.toUtf8()); if (vd) { leaveDesktop(vd); } } ); m_windowManagementInterface = w; } void AbstractClient::destroyWindowManagementInterface() { if (m_windowManagementInterface) { m_windowManagementInterface->unmap(); m_windowManagementInterface = nullptr; } } Options::MouseCommand AbstractClient::getMouseCommand(Qt::MouseButton button, bool *handled) const { *handled = false; if (button == Qt::NoButton) { return Options::MouseNothing; } if (isActive()) { if (options->isClickRaise()) { *handled = true; return Options::MouseActivateRaiseAndPassClick; } } else { *handled = true; switch (button) { case Qt::LeftButton: return options->commandWindow1(); case Qt::MiddleButton: return options->commandWindow2(); case Qt::RightButton: return options->commandWindow3(); default: // all other buttons pass Activate & Pass Client return Options::MouseActivateAndPassClick; } } return Options::MouseNothing; } Options::MouseCommand AbstractClient::getWheelCommand(Qt::Orientation orientation, bool *handled) const { *handled = false; if (orientation != Qt::Vertical) { return Options::MouseNothing; } if (!isActive()) { *handled = true; return options->commandWindowWheel(); } return Options::MouseNothing; } bool AbstractClient::performMouseCommand(Options::MouseCommand cmd, const QPoint &globalPos) { bool replay = false; switch(cmd) { case Options::MouseRaise: workspace()->raiseClient(this); break; case Options::MouseLower: { workspace()->lowerClient(this); // used to be activateNextClient(this), then topClientOnDesktop // since this is a mouseOp it's however safe to use the client under the mouse instead if (isActive() && options->focusPolicyIsReasonable()) { AbstractClient *next = workspace()->clientUnderMouse(screen()); if (next && next != this) workspace()->requestFocus(next, false); } break; } case Options::MouseOperationsMenu: if (isActive() && options->isClickRaise()) autoRaise(); workspace()->showWindowMenu(QRect(globalPos, globalPos), this); break; case Options::MouseToggleRaiseAndLower: workspace()->raiseOrLowerClient(this); break; case Options::MouseActivateAndRaise: { replay = isActive(); // for clickraise mode bool mustReplay = !rules()->checkAcceptFocus(acceptsFocus()); if (mustReplay) { ToplevelList::const_iterator it = workspace()->stackingOrder().constEnd(), begin = workspace()->stackingOrder().constBegin(); while (mustReplay && --it != begin && *it != this) { AbstractClient *c = qobject_cast(*it); if (!c || (c->keepAbove() && !keepAbove()) || (keepBelow() && !c->keepBelow())) continue; // can never raise above "it" mustReplay = !(c->isOnCurrentDesktop() && c->isOnCurrentActivity() && c->geometry().intersects(geometry())); } } workspace()->takeActivity(this, Workspace::ActivityFocus | Workspace::ActivityRaise); screens()->setCurrent(globalPos); replay = replay || mustReplay; break; } case Options::MouseActivateAndLower: workspace()->requestFocus(this); workspace()->lowerClient(this); screens()->setCurrent(globalPos); replay = replay || !rules()->checkAcceptFocus(acceptsFocus()); break; case Options::MouseActivate: replay = isActive(); // for clickraise mode workspace()->takeActivity(this, Workspace::ActivityFocus); screens()->setCurrent(globalPos); replay = replay || !rules()->checkAcceptFocus(acceptsFocus()); break; case Options::MouseActivateRaiseAndPassClick: workspace()->takeActivity(this, Workspace::ActivityFocus | Workspace::ActivityRaise); screens()->setCurrent(globalPos); replay = true; break; case Options::MouseActivateAndPassClick: workspace()->takeActivity(this, Workspace::ActivityFocus); screens()->setCurrent(globalPos); replay = true; break; case Options::MouseMaximize: maximize(MaximizeFull); break; case Options::MouseRestore: maximize(MaximizeRestore); break; case Options::MouseMinimize: minimize(); break; case Options::MouseAbove: { StackingUpdatesBlocker blocker(workspace()); if (keepBelow()) setKeepBelow(false); else setKeepAbove(true); break; } case Options::MouseBelow: { StackingUpdatesBlocker blocker(workspace()); if (keepAbove()) setKeepAbove(false); else setKeepBelow(true); break; } case Options::MousePreviousDesktop: workspace()->windowToPreviousDesktop(this); break; case Options::MouseNextDesktop: workspace()->windowToNextDesktop(this); break; case Options::MouseOpacityMore: if (!isDesktop()) // No point in changing the opacity of the desktop setOpacity(qMin(opacity() + 0.1, 1.0)); break; case Options::MouseOpacityLess: if (!isDesktop()) // No point in changing the opacity of the desktop setOpacity(qMax(opacity() - 0.1, 0.1)); break; case Options::MouseClose: closeWindow(); break; case Options::MouseActivateRaiseAndMove: case Options::MouseActivateRaiseAndUnrestrictedMove: workspace()->raiseClient(this); workspace()->requestFocus(this); screens()->setCurrent(globalPos); // fallthrough case Options::MouseMove: case Options::MouseUnrestrictedMove: { if (!isMovableAcrossScreens()) break; if (isMoveResize()) finishMoveResize(false); setMoveResizePointerMode(PositionCenter); setMoveResizePointerButtonDown(true); setMoveOffset(QPoint(globalPos.x() - x(), globalPos.y() - y())); // map from global setInvertedMoveOffset(rect().bottomRight() - moveOffset()); setUnrestrictedMoveResize((cmd == Options::MouseActivateRaiseAndUnrestrictedMove || cmd == Options::MouseUnrestrictedMove)); if (!startMoveResize()) setMoveResizePointerButtonDown(false); updateCursor(); break; } case Options::MouseResize: case Options::MouseUnrestrictedResize: { if (!isResizable() || isShade()) break; if (isMoveResize()) finishMoveResize(false); setMoveResizePointerButtonDown(true); const QPoint moveOffset = QPoint(globalPos.x() - x(), globalPos.y() - y()); // map from global setMoveOffset(moveOffset); int x = moveOffset.x(), y = moveOffset.y(); bool left = x < width() / 3; bool right = x >= 2 * width() / 3; bool top = y < height() / 3; bool bot = y >= 2 * height() / 3; Position mode; if (top) mode = left ? PositionTopLeft : (right ? PositionTopRight : PositionTop); else if (bot) mode = left ? PositionBottomLeft : (right ? PositionBottomRight : PositionBottom); else mode = (x < width() / 2) ? PositionLeft : PositionRight; setMoveResizePointerMode(mode); setInvertedMoveOffset(rect().bottomRight() - moveOffset); setUnrestrictedMoveResize((cmd == Options::MouseUnrestrictedResize)); if (!startMoveResize()) setMoveResizePointerButtonDown(false); updateCursor(); break; } case Options::MouseNothing: default: replay = true; break; } return replay; } void AbstractClient::setTransientFor(AbstractClient *transientFor) { if (transientFor == this) { // cannot be transient for one self return; } if (m_transientFor == transientFor) { return; } m_transientFor = transientFor; emit transientChanged(); } const AbstractClient *AbstractClient::transientFor() const { return m_transientFor; } AbstractClient *AbstractClient::transientFor() { return m_transientFor; } bool AbstractClient::hasTransientPlacementHint() const { return false; } QRect AbstractClient::transientPlacement(const QRect &bounds) const { Q_UNUSED(bounds); Q_UNREACHABLE(); return QRect(); } bool AbstractClient::hasTransient(const AbstractClient *c, bool indirect) const { Q_UNUSED(indirect); return c->transientFor() == this; } QList< AbstractClient* > AbstractClient::mainClients() const { if (const AbstractClient *t = transientFor()) { return QList{const_cast< AbstractClient* >(t)}; } return QList(); } QList AbstractClient::allMainClients() const { auto result = mainClients(); foreach (const auto *cl, result) { result += cl->allMainClients(); } return result; } void AbstractClient::setModal(bool m) { // Qt-3.2 can have even modal normal windows :( if (m_modal == m) return; m_modal = m; emit modalChanged(); // Changing modality for a mapped window is weird (?) // _NET_WM_STATE_MODAL should possibly rather be _NET_WM_WINDOW_TYPE_MODAL_DIALOG } bool AbstractClient::isModal() const { return m_modal; } void AbstractClient::addTransient(AbstractClient *cl) { Q_ASSERT(!m_transients.contains(cl)); Q_ASSERT(cl != this); m_transients.append(cl); } void AbstractClient::removeTransient(AbstractClient *cl) { m_transients.removeAll(cl); if (cl->transientFor() == this) { cl->setTransientFor(nullptr); } } void AbstractClient::removeTransientFromList(AbstractClient *cl) { m_transients.removeAll(cl); } bool AbstractClient::isActiveFullScreen() const { if (!isFullScreen()) return false; const auto ac = workspace()->mostRecentlyActivatedClient(); // instead of activeClient() - avoids flicker // according to NETWM spec implementation notes suggests // "focused windows having state _NET_WM_STATE_FULLSCREEN" to be on the highest layer. // we'll also take the screen into account return ac && (ac == this || ac->screen() != screen()|| ac->allMainClients().contains(const_cast(this))); } #define BORDER(which) \ int AbstractClient::border##which() const \ { \ return isDecorated() ? decoration()->border##which() : 0; \ } BORDER(Bottom) BORDER(Left) BORDER(Right) BORDER(Top) #undef BORDER QSize AbstractClient::sizeForClientSize(const QSize &wsize, Sizemode mode, bool noframe) const { Q_UNUSED(mode) Q_UNUSED(noframe) return wsize + QSize(borderLeft() + borderRight(), borderTop() + borderBottom()); } void AbstractClient::addRepaintDuringGeometryUpdates() { const QRect deco_rect = visibleRect(); addLayerRepaint(m_visibleRectBeforeGeometryUpdate); addLayerRepaint(deco_rect); // trigger repaint of window's new location m_visibleRectBeforeGeometryUpdate = deco_rect; } void AbstractClient::updateGeometryBeforeUpdateBlocking() { m_geometryBeforeUpdateBlocking = geometry(); } void AbstractClient::doMove(int, int) { } void AbstractClient::updateInitialMoveResizeGeometry() { m_moveResize.initialGeometry = geometry(); m_moveResize.geometry = m_moveResize.initialGeometry; m_moveResize.startScreen = screen(); } void AbstractClient::updateCursor() { Position m = moveResizePointerMode(); if (!isResizable() || isShade()) m = PositionCenter; CursorShape c = Qt::ArrowCursor; switch(m) { case PositionTopLeft: c = KWin::ExtendedCursor::SizeNorthWest; break; case PositionBottomRight: c = KWin::ExtendedCursor::SizeSouthEast; break; case PositionBottomLeft: c = KWin::ExtendedCursor::SizeSouthWest; break; case PositionTopRight: c = KWin::ExtendedCursor::SizeNorthEast; break; case PositionTop: c = KWin::ExtendedCursor::SizeNorth; break; case PositionBottom: c = KWin::ExtendedCursor::SizeSouth; break; case PositionLeft: c = KWin::ExtendedCursor::SizeWest; break; case PositionRight: c = KWin::ExtendedCursor::SizeEast; break; default: if (isMoveResize()) c = Qt::SizeAllCursor; else c = Qt::ArrowCursor; break; } if (c == m_moveResize.cursor) return; m_moveResize.cursor = c; emit moveResizeCursorChanged(c); } void AbstractClient::leaveMoveResize() { workspace()->setMoveResizeClient(nullptr); setMoveResize(false); if (ScreenEdges::self()->isDesktopSwitchingMovingClients()) ScreenEdges::self()->reserveDesktopSwitching(false, Qt::Vertical|Qt::Horizontal); if (isElectricBorderMaximizing()) { outline()->hide(); elevate(false); } } bool AbstractClient::s_haveResizeEffect = false; void AbstractClient::updateHaveResizeEffect() { s_haveResizeEffect = effects && static_cast(effects)->provides(Effect::Resize); } bool AbstractClient::doStartMoveResize() { return true; } void AbstractClient::positionGeometryTip() { } void AbstractClient::doPerformMoveResize() { } bool AbstractClient::isWaitingForMoveResizeSync() const { return false; } void AbstractClient::doResizeSync() { } void AbstractClient::checkQuickTilingMaximizationZones(int xroot, int yroot) { QuickTileMode mode = QuickTileFlag::None; bool innerBorder = false; for (int i=0; i < screens()->count(); ++i) { if (!screens()->geometry(i).contains(QPoint(xroot, yroot))) continue; auto isInScreen = [i](const QPoint &pt) { for (int j = 0; j < screens()->count(); ++j) { if (j == i) continue; if (screens()->geometry(j).contains(pt)) { return true; } } return false; }; QRect area = workspace()->clientArea(MaximizeArea, QPoint(xroot, yroot), desktop()); if (options->electricBorderTiling()) { if (xroot <= area.x() + 20) { mode |= QuickTileFlag::Left; innerBorder = isInScreen(QPoint(area.x() - 1, yroot)); } else if (xroot >= area.x() + area.width() - 20) { mode |= QuickTileFlag::Right; innerBorder = isInScreen(QPoint(area.right() + 1, yroot)); } } if (mode != QuickTileMode(QuickTileFlag::None)) { if (yroot <= area.y() + area.height() * options->electricBorderCornerRatio()) mode |= QuickTileFlag::Top; else if (yroot >= area.y() + area.height() - area.height() * options->electricBorderCornerRatio()) mode |= QuickTileFlag::Bottom; } else if (options->electricBorderMaximize() && yroot <= area.y() + 5 && isMaximizable()) { mode = QuickTileFlag::Maximize; innerBorder = isInScreen(QPoint(xroot, area.y() - 1)); } break; // no point in checking other screens to contain this... "point"... } if (mode != electricBorderMode()) { setElectricBorderMode(mode); if (innerBorder) { if (!m_electricMaximizingDelay) { m_electricMaximizingDelay = new QTimer(this); m_electricMaximizingDelay->setInterval(250); m_electricMaximizingDelay->setSingleShot(true); connect(m_electricMaximizingDelay, &QTimer::timeout, [this]() { if (isMove()) setElectricBorderMaximizing(electricBorderMode() != QuickTileMode(QuickTileFlag::None)); }); } m_electricMaximizingDelay->start(); } else { setElectricBorderMaximizing(mode != QuickTileMode(QuickTileFlag::None)); } } } void AbstractClient::keyPressEvent(uint key_code) { if (!isMove() && !isResize()) return; bool is_control = key_code & Qt::CTRL; bool is_alt = key_code & Qt::ALT; key_code = key_code & ~Qt::KeyboardModifierMask; int delta = is_control ? 1 : is_alt ? 32 : 8; QPoint pos = Cursor::pos(); switch(key_code) { case Qt::Key_Left: pos.rx() -= delta; break; case Qt::Key_Right: pos.rx() += delta; break; case Qt::Key_Up: pos.ry() -= delta; break; case Qt::Key_Down: pos.ry() += delta; break; case Qt::Key_Space: case Qt::Key_Return: case Qt::Key_Enter: setMoveResizePointerButtonDown(false); finishMoveResize(false); updateCursor(); break; case Qt::Key_Escape: setMoveResizePointerButtonDown(false); finishMoveResize(true); updateCursor(); break; default: return; } Cursor::setPos(pos); } QSize AbstractClient::resizeIncrements() const { return QSize(1, 1); } void AbstractClient::dontMoveResize() { setMoveResizePointerButtonDown(false); stopDelayedMoveResize(); if (isMoveResize()) finishMoveResize(false); } AbstractClient::Position AbstractClient::mousePosition() const { if (isDecorated()) { switch (decoration()->sectionUnderMouse()) { case Qt::BottomLeftSection: return PositionBottomLeft; case Qt::BottomRightSection: return PositionBottomRight; case Qt::BottomSection: return PositionBottom; case Qt::LeftSection: return PositionLeft; case Qt::RightSection: return PositionRight; case Qt::TopSection: return PositionTop; case Qt::TopLeftSection: return PositionTopLeft; case Qt::TopRightSection: return PositionTopRight; default: return PositionCenter; } } return PositionCenter; } void AbstractClient::endMoveResize() { setMoveResizePointerButtonDown(false); stopDelayedMoveResize(); if (isMoveResize()) { finishMoveResize(false); setMoveResizePointerMode(mousePosition()); } updateCursor(); } void AbstractClient::destroyDecoration() { delete m_decoration.decoration; m_decoration.decoration = nullptr; } bool AbstractClient::decorationHasAlpha() const { if (!isDecorated() || decoration()->isOpaque()) { // either no decoration or decoration has alpha disabled return false; } return true; } void AbstractClient::triggerDecorationRepaint() { if (isDecorated()) { decoration()->update(); } } void AbstractClient::layoutDecorationRects(QRect &left, QRect &top, QRect &right, QRect &bottom) const { if (!isDecorated()) { return; } QRect r = decoration()->rect(); top = QRect(r.x(), r.y(), r.width(), borderTop()); bottom = QRect(r.x(), r.y() + r.height() - borderBottom(), r.width(), borderBottom()); left = QRect(r.x(), r.y() + top.height(), borderLeft(), r.height() - top.height() - bottom.height()); right = QRect(r.x() + r.width() - borderRight(), r.y() + top.height(), borderRight(), r.height() - top.height() - bottom.height()); } void AbstractClient::processDecorationMove(const QPoint &localPos, const QPoint &globalPos) { if (isMoveResizePointerButtonDown()) { handleMoveResize(localPos.x(), localPos.y(), globalPos.x(), globalPos.y()); return; } // TODO: handle modifiers Position newmode = mousePosition(); if (newmode != moveResizePointerMode()) { setMoveResizePointerMode(newmode); updateCursor(); } } bool AbstractClient::processDecorationButtonPress(QMouseEvent *event, bool ignoreMenu) { Options::MouseCommand com = Options::MouseNothing; bool active = isActive(); if (!wantsInput()) // we cannot be active, use it anyway active = true; // check whether it is a double click if (event->button() == Qt::LeftButton && titlebarPositionUnderMouse()) { if (m_decoration.doubleClickTimer.isValid()) { const qint64 interval = m_decoration.doubleClickTimer.elapsed(); m_decoration.doubleClickTimer.invalidate(); if (interval > QGuiApplication::styleHints()->mouseDoubleClickInterval()) { m_decoration.doubleClickTimer.start(); // expired -> new first click and pot. init } else { Workspace::self()->performWindowOperation(this, options->operationTitlebarDblClick()); dontMoveResize(); return false; } } else { m_decoration.doubleClickTimer.start(); // new first click and pot. init, could be invalidated by release - see below } } if (event->button() == Qt::LeftButton) com = active ? options->commandActiveTitlebar1() : options->commandInactiveTitlebar1(); else if (event->button() == Qt::MidButton) com = active ? options->commandActiveTitlebar2() : options->commandInactiveTitlebar2(); else if (event->button() == Qt::RightButton) com = active ? options->commandActiveTitlebar3() : options->commandInactiveTitlebar3(); if (event->button() == Qt::LeftButton && com != Options::MouseOperationsMenu // actions where it's not possible to get the matching && com != Options::MouseMinimize) // mouse release event { setMoveResizePointerMode(mousePosition()); setMoveResizePointerButtonDown(true); setMoveOffset(event->pos()); setInvertedMoveOffset(rect().bottomRight() - moveOffset()); setUnrestrictedMoveResize(false); startDelayedMoveResize(); updateCursor(); } // In the new API the decoration may process the menu action to display an inactive tab's menu. // If the event is unhandled then the core will create one for the active window in the group. if (!ignoreMenu || com != Options::MouseOperationsMenu) performMouseCommand(com, event->globalPos()); return !( // Return events that should be passed to the decoration in the new API com == Options::MouseRaise || com == Options::MouseOperationsMenu || com == Options::MouseActivateAndRaise || com == Options::MouseActivate || com == Options::MouseActivateRaiseAndPassClick || com == Options::MouseActivateAndPassClick || com == Options::MouseNothing); } void AbstractClient::processDecorationButtonRelease(QMouseEvent *event) { if (isDecorated()) { if (event->isAccepted() || !titlebarPositionUnderMouse()) { invalidateDecorationDoubleClickTimer(); // click was for the deco and shall not init a doubleclick } } if (event->buttons() == Qt::NoButton) { setMoveResizePointerButtonDown(false); stopDelayedMoveResize(); if (isMoveResize()) { finishMoveResize(false); setMoveResizePointerMode(mousePosition()); } updateCursor(); } } void AbstractClient::startDecorationDoubleClickTimer() { m_decoration.doubleClickTimer.start(); } void AbstractClient::invalidateDecorationDoubleClickTimer() { m_decoration.doubleClickTimer.invalidate(); } bool AbstractClient::providesContextHelp() const { return false; } void AbstractClient::showContextHelp() { } QPointer AbstractClient::decoratedClient() const { return m_decoration.client; } void AbstractClient::setDecoratedClient(QPointer< Decoration::DecoratedClientImpl > client) { m_decoration.client = client; } void AbstractClient::enterEvent(const QPoint &globalPos) { // TODO: shade hover if (options->focusPolicy() == Options::ClickToFocus || workspace()->userActionsMenu()->isShown()) return; if (options->isAutoRaise() && !isDesktop() && !isDock() && workspace()->focusChangeEnabled() && globalPos != workspace()->focusMousePosition() && workspace()->topClientOnDesktop(VirtualDesktopManager::self()->current(), options->isSeparateScreenFocus() ? screen() : -1) != this) { startAutoRaise(); } if (isDesktop() || isDock()) return; // for FocusFollowsMouse, change focus only if the mouse has actually been moved, not if the focus // change came because of window changes (e.g. closing a window) - #92290 if (options->focusPolicy() != Options::FocusFollowsMouse || globalPos != workspace()->focusMousePosition()) { workspace()->requestDelayFocus(this); } } void AbstractClient::leaveEvent() { cancelAutoRaise(); workspace()->cancelDelayFocus(); // TODO: shade hover // TODO: send hover leave to deco // TODO: handle Options::FocusStrictlyUnderMouse } QRect AbstractClient::iconGeometry() const { if (!windowManagementInterface() || !waylandServer()) { // window management interface is only available if the surface is mapped return QRect(); } int minDistance = INT_MAX; AbstractClient *candidatePanel = nullptr; QRect candidateGeom; for (auto i = windowManagementInterface()->minimizedGeometries().constBegin(), end = windowManagementInterface()->minimizedGeometries().constEnd(); i != end; ++i) { AbstractClient *client = waylandServer()->findAbstractClient(i.key()); if (!client) { continue; } const int distance = QPoint(client->pos() - pos()).manhattanLength(); if (distance < minDistance) { minDistance = distance; candidatePanel = client; candidateGeom = i.value(); } } if (!candidatePanel) { return QRect(); } return candidateGeom.translated(candidatePanel->pos()); } QRect AbstractClient::inputGeometry() const { if (isDecorated()) { return Toplevel::inputGeometry() + decoration()->resizeOnlyBorders(); } return Toplevel::inputGeometry(); } QRect AbstractClient::virtualKeyboardGeometry() const { return m_virtualKeyboardGeometry; } void AbstractClient::setVirtualKeyboardGeometry(const QRect &geo) { // No keyboard anymore if (geo.isEmpty() && !m_keyboardGeometryRestore.isEmpty()) { setGeometry(m_keyboardGeometryRestore); m_keyboardGeometryRestore = QRect(); } else if (geo.isEmpty()) { return; // The keyboard has just been opened (rather than resized) save client geometry for a restore } else if (m_keyboardGeometryRestore.isEmpty()) { m_keyboardGeometryRestore = geometry(); } m_virtualKeyboardGeometry = geo; // Don't resize Desktop and fullscreen windows if (isFullScreen() || isDesktop()) { return; } if (!geo.intersects(m_keyboardGeometryRestore)) { return; } const QRect availableArea = workspace()->clientArea(MaximizeArea, this); QRect newWindowGeometry = m_keyboardGeometryRestore; newWindowGeometry.moveBottom(geo.top()); newWindowGeometry.setTop(qMax(newWindowGeometry.top(), availableArea.top())); setGeometry(newWindowGeometry); } bool AbstractClient::dockWantsInput() const { return false; } void AbstractClient::setDesktopFileName(QByteArray name) { name = rules()->checkDesktopFile(name).toUtf8(); if (name == m_desktopFileName) { return; } m_desktopFileName = name; updateWindowRules(Rules::DesktopFile); emit desktopFileNameChanged(); } QString AbstractClient::iconFromDesktopFile() const { if (m_desktopFileName.isEmpty()) { return QString(); } QString desktopFile = QString::fromUtf8(m_desktopFileName); if (!desktopFile.endsWith(QLatin1String(".desktop"))) { desktopFile.append(QLatin1String(".desktop")); } KDesktopFile df(desktopFile); return df.readIcon(); } bool AbstractClient::hasApplicationMenu() const { return ApplicationMenu::self()->applicationMenuEnabled() && !m_applicationMenuServiceName.isEmpty() && !m_applicationMenuObjectPath.isEmpty(); } void AbstractClient::updateApplicationMenuServiceName(const QString &serviceName) { const bool old_hasApplicationMenu = hasApplicationMenu(); m_applicationMenuServiceName = serviceName; const bool new_hasApplicationMenu = hasApplicationMenu(); if (old_hasApplicationMenu != new_hasApplicationMenu) { emit hasApplicationMenuChanged(new_hasApplicationMenu); } } void AbstractClient::updateApplicationMenuObjectPath(const QString &objectPath) { const bool old_hasApplicationMenu = hasApplicationMenu(); m_applicationMenuObjectPath = objectPath; const bool new_hasApplicationMenu = hasApplicationMenu(); if (old_hasApplicationMenu != new_hasApplicationMenu) { emit hasApplicationMenuChanged(new_hasApplicationMenu); } } void AbstractClient::setApplicationMenuActive(bool applicationMenuActive) { if (m_applicationMenuActive != applicationMenuActive) { m_applicationMenuActive = applicationMenuActive; emit applicationMenuActiveChanged(applicationMenuActive); } } void AbstractClient::showApplicationMenu(int actionId) { if (isDecorated()) { decoration()->showApplicationMenu(actionId); } else { // we don't know where the application menu button will be, show it in the top left corner instead Workspace::self()->showApplicationMenu(QRect(), this, actionId); } } bool AbstractClient::unresponsive() const { return m_unresponsive; } void AbstractClient::setUnresponsive(bool unresponsive) { if (m_unresponsive != unresponsive) { m_unresponsive = unresponsive; emit unresponsiveChanged(m_unresponsive); emit captionChanged(); } } QString AbstractClient::shortcutCaptionSuffix() const { if (shortcut().isEmpty()) { return QString(); } return QLatin1String(" {") + shortcut().toString() + QLatin1Char('}'); } AbstractClient *AbstractClient::findClientWithSameCaption() const { auto fetchNameInternalPredicate = [this](const AbstractClient *cl) { return (!cl->isSpecialWindow() || cl->isToolbar()) && cl != this && cl->captionNormal() == captionNormal() && cl->captionSuffix() == captionSuffix(); }; return workspace()->findAbstractClient(fetchNameInternalPredicate); } QString AbstractClient::caption() const { QString cap = captionNormal() + captionSuffix(); if (unresponsive()) { cap += QLatin1String(" "); cap += i18nc("Application is not responding, appended to window title", "(Not Responding)"); } return cap; } void AbstractClient::removeRule(Rules* rule) { m_rules.remove(rule); } void AbstractClient::discardTemporaryRules() { m_rules.discardTemporary(); } void AbstractClient::evaluateWindowRules() { setupWindowRules(true); applyWindowRules(); } void AbstractClient::setOnActivities(QStringList newActivitiesList) { Q_UNUSED(newActivitiesList) } void AbstractClient::checkNoBorder() { setNoBorder(false); } bool AbstractClient::groupTransient() const { return false; } const Group *AbstractClient::group() const { return nullptr; } Group *AbstractClient::group() { return nullptr; } bool AbstractClient::isInternal() const { return false; } bool AbstractClient::supportsWindowRules() const { return true; } +QMargins AbstractClient::frameMargins() const +{ + return QMargins(borderLeft(), borderTop(), borderRight(), borderBottom()); +} + } diff --git a/abstract_client.h b/abstract_client.h index f4213ab5f..2fee0b21a 100644 --- a/abstract_client.h +++ b/abstract_client.h @@ -1,1328 +1,1337 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2015 Martin Gräßlin +Copyright (C) 2019 Vlad Zagorodniy 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 . *********************************************************************/ #ifndef KWIN_ABSTRACT_CLIENT_H #define KWIN_ABSTRACT_CLIENT_H #include "toplevel.h" #include "options.h" #include "rules.h" #include "cursor.h" #include #include #include namespace KWayland { namespace Server { class PlasmaWindowInterface; } } namespace KDecoration2 { class Decoration; } namespace KWin { namespace TabBox { class TabBoxClientImpl; } namespace Decoration { class DecoratedClientImpl; class DecorationPalette; } class KWIN_EXPORT AbstractClient : public Toplevel { Q_OBJECT /** * Whether this Client is fullScreen. A Client might either be fullScreen due to the _NET_WM property * or through a legacy support hack. The fullScreen state can only be changed if the Client does not * use the legacy hack. To be sure whether the state changed, connect to the notify signal. */ Q_PROPERTY(bool fullScreen READ isFullScreen WRITE setFullScreen NOTIFY fullScreenChanged) /** * Whether the Client can be set to fullScreen. The property is evaluated each time it is invoked. * Because of that there is no notify signal. */ Q_PROPERTY(bool fullScreenable READ isFullScreenable) /** * Whether this Client is active or not. Use Workspace::activateClient() to activate a Client. * @see Workspace::activateClient */ Q_PROPERTY(bool active READ isActive NOTIFY activeChanged) /** * The desktop this Client is on. If the Client is on all desktops the property has value -1. * This is a legacy property, use x11DesktopIds instead */ Q_PROPERTY(int desktop READ desktop WRITE setDesktop NOTIFY desktopChanged) /** * Whether the Client is on all desktops. That is desktop is -1. */ Q_PROPERTY(bool onAllDesktops READ isOnAllDesktops WRITE setOnAllDesktops NOTIFY desktopChanged) /** * The x11 ids for all desktops this client is in. On X11 this list will always have a length of 1 */ Q_PROPERTY(QVector x11DesktopIds READ x11DesktopIds NOTIFY x11DesktopIdsChanged) /** * Indicates that the window should not be included on a taskbar. */ Q_PROPERTY(bool skipTaskbar READ skipTaskbar WRITE setSkipTaskbar NOTIFY skipTaskbarChanged) /** * Indicates that the window should not be included on a Pager. */ Q_PROPERTY(bool skipPager READ skipPager WRITE setSkipPager NOTIFY skipPagerChanged) /** * Whether the Client should be excluded from window switching effects. */ Q_PROPERTY(bool skipSwitcher READ skipSwitcher WRITE setSkipSwitcher NOTIFY skipSwitcherChanged) /** * Whether the window can be closed by the user. The value is evaluated each time the getter is called. * Because of that no changed signal is provided. */ Q_PROPERTY(bool closeable READ isCloseable) Q_PROPERTY(QIcon icon READ icon NOTIFY iconChanged) /** * Whether the Client is set to be kept above other windows. */ Q_PROPERTY(bool keepAbove READ keepAbove WRITE setKeepAbove NOTIFY keepAboveChanged) /** * Whether the Client is set to be kept below other windows. */ Q_PROPERTY(bool keepBelow READ keepBelow WRITE setKeepBelow NOTIFY keepBelowChanged) /** * Whether the Client can be shaded. The property is evaluated each time it is invoked. * Because of that there is no notify signal. */ Q_PROPERTY(bool shadeable READ isShadeable) /** * Whether the Client is shaded. */ Q_PROPERTY(bool shade READ isShade WRITE setShade NOTIFY shadeChanged) /** * Whether the Client can be minimized. The property is evaluated each time it is invoked. * Because of that there is no notify signal. */ Q_PROPERTY(bool minimizable READ isMinimizable) /** * Whether the Client is minimized. */ Q_PROPERTY(bool minimized READ isMinimized WRITE setMinimized NOTIFY minimizedChanged) /** * The optional geometry representing the minimized Client in e.g a taskbar. * See _NET_WM_ICON_GEOMETRY at https://standards.freedesktop.org/wm-spec/wm-spec-latest.html . * The value is evaluated each time the getter is called. * Because of that no changed signal is provided. */ Q_PROPERTY(QRect iconGeometry READ iconGeometry) /** * Returns whether the window is any of special windows types (desktop, dock, splash, ...), * i.e. window types that usually don't have a window frame and the user does not use window * management (moving, raising,...) on them. * The value is evaluated each time the getter is called. * Because of that no changed signal is provided. */ Q_PROPERTY(bool specialWindow READ isSpecialWindow) /** * Whether window state _NET_WM_STATE_DEMANDS_ATTENTION is set. This state indicates that some * action in or with the window happened. For example, it may be set by the Window Manager if * the window requested activation but the Window Manager refused it, or the application may set * it if it finished some work. This state may be set by both the Client and the Window Manager. * It should be unset by the Window Manager when it decides the window got the required attention * (usually, that it got activated). */ Q_PROPERTY(bool demandsAttention READ isDemandingAttention WRITE demandAttention NOTIFY demandsAttentionChanged) /** * The Caption of the Client. Read from WM_NAME property together with a suffix for hostname and shortcut. * To read only the caption as provided by WM_NAME, use the getter with an additional @c false value. */ Q_PROPERTY(QString caption READ caption NOTIFY captionChanged) /** * Minimum size as specified in WM_NORMAL_HINTS */ Q_PROPERTY(QSize minSize READ minSize) /** * Maximum size as specified in WM_NORMAL_HINTS */ Q_PROPERTY(QSize maxSize READ maxSize) /** * Whether the Client can accept keyboard focus. * The value is evaluated each time the getter is called. * Because of that no changed signal is provided. */ Q_PROPERTY(bool wantsInput READ wantsInput) /** * Whether the Client is a transient Window to another Window. * @see transientFor */ Q_PROPERTY(bool transient READ isTransient NOTIFY transientChanged) /** * The Client to which this Client is a transient if any. */ Q_PROPERTY(KWin::AbstractClient *transientFor READ transientFor NOTIFY transientChanged) /** * Whether the Client represents a modal window. */ Q_PROPERTY(bool modal READ isModal NOTIFY modalChanged) /** * The geometry of this Client. Be aware that depending on resize mode the geometryChanged signal * might be emitted at each resize step or only at the end of the resize operation. */ Q_PROPERTY(QRect geometry READ geometry WRITE setGeometry) /** * Whether the Client is currently being moved by the user. * Notify signal is emitted when the Client starts or ends move/resize mode. */ Q_PROPERTY(bool move READ isMove NOTIFY moveResizedChanged) /** * Whether the Client is currently being resized by the user. * Notify signal is emitted when the Client starts or ends move/resize mode. */ Q_PROPERTY(bool resize READ isResize NOTIFY moveResizedChanged) /** * Whether the decoration is currently using an alpha channel. */ Q_PROPERTY(bool decorationHasAlpha READ decorationHasAlpha) /** * Whether the window has a decoration or not. * This property is not allowed to be set by applications themselves. * The decision whether a window has a border or not belongs to the window manager. * If this property gets abused by application developers, it will be removed again. */ Q_PROPERTY(bool noBorder READ noBorder WRITE setNoBorder) /** * Whether the Client provides context help. Mostly needed by decorations to decide whether to * show the help button or not. */ Q_PROPERTY(bool providesContextHelp READ providesContextHelp CONSTANT) /** * Whether the Client can be maximized both horizontally and vertically. * The property is evaluated each time it is invoked. * Because of that there is no notify signal. */ Q_PROPERTY(bool maximizable READ isMaximizable) /** * Whether the Client is moveable. Even if it is not moveable, it might be possible to move * it to another screen. The property is evaluated each time it is invoked. * Because of that there is no notify signal. * @see moveableAcrossScreens */ Q_PROPERTY(bool moveable READ isMovable) /** * Whether the Client can be moved to another screen. The property is evaluated each time it is invoked. * Because of that there is no notify signal. * @see moveable */ Q_PROPERTY(bool moveableAcrossScreens READ isMovableAcrossScreens) /** * Whether the Client can be resized. The property is evaluated each time it is invoked. * Because of that there is no notify signal. */ Q_PROPERTY(bool resizeable READ isResizable) /** * The desktop file name of the application this AbstractClient belongs to. * * This is either the base name without full path and without file extension of the * desktop file for the window's application (e.g. "org.kde.foo"). * * The application's desktop file name can also be the full path to the desktop file * (e.g. "/opt/kde/share/org.kde.foo.desktop") in case it's not in a standard location. */ Q_PROPERTY(QByteArray desktopFileName READ desktopFileName NOTIFY desktopFileNameChanged) /** * Whether an application menu is available for this Client */ Q_PROPERTY(bool hasApplicationMenu READ hasApplicationMenu NOTIFY hasApplicationMenuChanged) /** * Whether the application menu for this Client is currently opened */ Q_PROPERTY(bool applicationMenuActive READ applicationMenuActive NOTIFY applicationMenuActiveChanged) /** * Whether this client is unresponsive. * * When an application failed to react on a ping request in time, it is * considered unresponsive. This usually indicates that the application froze or crashed. */ Q_PROPERTY(bool unresponsive READ unresponsive NOTIFY unresponsiveChanged) /** * The color scheme set on this client * Absolute file path, or name of palette in the user's config directory following KColorSchemes format. * An empty string indicates the default palette from kdeglobals is used. * @note this indicates the colour scheme requested, which might differ from the theme applied if the colorScheme cannot be found */ Q_PROPERTY(QString colorScheme READ colorScheme NOTIFY colorSchemeChanged) public: ~AbstractClient() override; QWeakPointer tabBoxClient() const { return m_tabBoxClient.toWeakRef(); } bool isFirstInTabBox() const { return m_firstInTabBox; } bool skipSwitcher() const { return m_skipSwitcher; } void setSkipSwitcher(bool set); bool skipTaskbar() const { return m_skipTaskbar; } void setSkipTaskbar(bool set); void setOriginalSkipTaskbar(bool set); bool originalSkipTaskbar() const { return m_originalSkipTaskbar; } bool skipPager() const { return m_skipPager; } void setSkipPager(bool set); const QIcon &icon() const { return m_icon; } bool isActive() const { return m_active; } /** * Sets the client's active state to \a act. * * This function does only change the visual appearance of the client, * it does not change the focus setting. Use * Workspace::activateClient() or Workspace::requestFocus() instead. * * If a client receives or looses the focus, it calls setActive() on * its own. */ void setActive(bool); bool keepAbove() const { return m_keepAbove; } void setKeepAbove(bool); bool keepBelow() const { return m_keepBelow; } void setKeepBelow(bool); void demandAttention(bool set = true); bool isDemandingAttention() const { return m_demandsAttention; } void cancelAutoRaise(); bool wantsTabFocus() const; QPoint clientPos() const override { return QPoint(borderLeft(), borderTop()); } virtual void updateMouseGrab(); /** * @returns The caption consisting of captionNormal and captionSuffix * @see captionNormal * @see captionSuffix */ QString caption() const; /** * @returns The caption as set by the AbstractClient without any suffix. * @see caption * @see captionSuffix */ virtual QString captionNormal() const = 0; /** * @returns The suffix added to the caption (e.g. shortcut, machine name, etc.) * @see caption * @see captionNormal */ virtual QString captionSuffix() const = 0; virtual bool isCloseable() const = 0; // TODO: remove boolean trap virtual bool isShown(bool shaded_is_shown) const = 0; virtual bool isHiddenInternal() const = 0; // TODO: remove boolean trap virtual void hideClient(bool hide) = 0; virtual bool isFullScreenable() const = 0; virtual bool isFullScreen() const = 0; // TODO: remove boolean trap virtual AbstractClient *findModal(bool allow_itself = false) = 0; virtual bool isTransient() const; /** * @returns Whether there is a hint available to place the AbstractClient on it's parent, default @c false. * @see transientPlacementHint */ virtual bool hasTransientPlacementHint() const; /** * Only valid id hasTransientPlacementHint is true * @returns The position the transient wishes to position itself */ virtual QRect transientPlacement(const QRect &bounds) const; const AbstractClient* transientFor() const; AbstractClient* transientFor(); /** * @returns @c true if c is the transient_for window for this client, * or recursively the transient_for window * @todo: remove boolean trap */ virtual bool hasTransient(const AbstractClient* c, bool indirect) const; const QList& transients() const; // Is not indirect virtual void removeTransient(AbstractClient* cl); virtual QList mainClients() const; // Call once before loop , is not indirect QList allMainClients() const; // Call once before loop , is indirect /** * Returns true for "special" windows and false for windows which are "normal" * (normal=window which has a border, can be moved by the user, can be closed, etc.) * true for Desktop, Dock, Splash, Override and TopMenu (and Toolbar??? - for now) * false for Normal, Dialog, Utility and Menu (and Toolbar??? - not yet) TODO */ bool isSpecialWindow() const; void sendToScreen(int screen); const QKeySequence &shortcut() const { return _shortcut; } void setShortcut(const QString &cut); virtual bool performMouseCommand(Options::MouseCommand, const QPoint &globalPos); void setOnAllDesktops(bool set); void setDesktop(int); void enterDesktop(VirtualDesktop *desktop); void leaveDesktop(VirtualDesktop *desktop); /** * Set the window as being on the attached list of desktops * On X11 it will be set to the last entry */ void setDesktops(QVector desktops); int desktop() const override { return m_desktops.isEmpty() ? (int)NET::OnAllDesktops : m_desktops.last()->x11DesktopNumber(); } QVector desktops() const override { return m_desktops; } QVector x11DesktopIds() const; void setMinimized(bool set); /** * Minimizes this client plus its transients */ void minimize(bool avoid_animation = false); void unminimize(bool avoid_animation = false); bool isMinimized() const { return m_minimized; } virtual void setFullScreen(bool set, bool user = true) = 0; virtual void setClientShown(bool shown); virtual QRect geometryRestore() const = 0; /** * The currently applied maximize mode */ virtual MaximizeMode maximizeMode() const = 0; /** * The maximise mode requested by the server. * For X this always matches maximizeMode, for wayland clients it * is asyncronous */ virtual MaximizeMode requestedMaximizeMode() const; void maximize(MaximizeMode); /** * Sets the maximization according to @p vertically and @p horizontally. */ Q_INVOKABLE void setMaximize(bool vertically, bool horizontally); virtual bool noBorder() const = 0; virtual void setNoBorder(bool set) = 0; virtual void blockActivityUpdates(bool b = true) = 0; QPalette palette() const; const Decoration::DecorationPalette *decorationPalette() const; /** * Returns whether the window is resizable or has a fixed size. */ virtual bool isResizable() const = 0; /** * Returns whether the window is moveable or has a fixed position. */ virtual bool isMovable() const = 0; /** * Returns whether the window can be moved to another screen. */ virtual bool isMovableAcrossScreens() const = 0; /** * @c true only for @c ShadeNormal */ bool isShade() const { return shadeMode() == ShadeNormal; } /** * Default implementation returns @c ShadeNone */ virtual ShadeMode shadeMode() const; // Prefer isShade() void setShade(bool set); /** * Default implementation does nothing */ virtual void setShade(ShadeMode mode); /** * Whether the Client can be shaded. Default implementation returns @c false. */ virtual bool isShadeable() const; /** * Returns whether the window is maximizable or not. */ virtual bool isMaximizable() const = 0; virtual bool isMinimizable() const = 0; virtual QRect iconGeometry() const; virtual bool userCanSetFullScreen() const = 0; virtual bool userCanSetNoBorder() const = 0; virtual void checkNoBorder(); virtual void setOnActivities(QStringList newActivitiesList); virtual void setOnAllActivities(bool set) = 0; const WindowRules* rules() const { return &m_rules; } void removeRule(Rules* r); void setupWindowRules(bool ignore_temporary); void evaluateWindowRules(); void applyWindowRules(); virtual void takeFocus() = 0; virtual bool wantsInput() const = 0; /** * Whether a dock window wants input. * * By default KWin doesn't pass focus to a dock window unless a force activate * request is provided. * * This method allows to have dock windows take focus also through flags set on * the window. * * The default implementation returns @c false. */ virtual bool dockWantsInput() const; void checkWorkspacePosition(QRect oldGeometry = QRect(), int oldDesktop = -2, QRect oldClientGeometry = QRect()); virtual xcb_timestamp_t userTime() const; virtual void updateWindowRules(Rules::Types selection); void growHorizontal(); void shrinkHorizontal(); void growVertical(); void shrinkVertical(); void updateMoveResize(const QPointF ¤tGlobalCursor); /** * Ends move resize when all pointer buttons are up again. */ void endMoveResize(); void keyPressEvent(uint key_code); void enterEvent(const QPoint &globalPos); void leaveEvent(); /** * These values represent positions inside an area */ enum Position { // without prefix, they'd conflict with Qt::TopLeftCorner etc. :( PositionCenter = 0x00, PositionLeft = 0x01, PositionRight = 0x02, PositionTop = 0x04, PositionBottom = 0x08, PositionTopLeft = PositionLeft | PositionTop, PositionTopRight = PositionRight | PositionTop, PositionBottomLeft = PositionLeft | PositionBottom, PositionBottomRight = PositionRight | PositionBottom }; Position titlebarPosition() const; bool titlebarPositionUnderMouse() const; // a helper for the workspace window packing. tests for screen validity and updates since in maximization case as with normal moving void packTo(int left, int top); /** * Sets the quick tile mode ("snap") of this window. * This will also handle preserving and restoring of window geometry as necessary. * @param mode The tile mode (left/right) to give this window. * @param keyboard Defines whether to take keyboard cursor into account. */ void setQuickTileMode(QuickTileMode mode, bool keyboard = false); QuickTileMode quickTileMode() const { return QuickTileMode(m_quickTileMode); } Layer layer() const override; void updateLayer(); enum ForceGeometry_t { NormalGeometrySet, ForceGeometrySet }; void move(int x, int y, ForceGeometry_t force = NormalGeometrySet); void move(const QPoint &p, ForceGeometry_t force = NormalGeometrySet); virtual void resizeWithChecks(int w, int h, ForceGeometry_t force = NormalGeometrySet) = 0; void resizeWithChecks(const QSize& s, ForceGeometry_t force = NormalGeometrySet); void keepInArea(QRect area, bool partial = false); virtual QSize minSize() const; virtual QSize maxSize() const; virtual void setGeometry(int x, int y, int w, int h, ForceGeometry_t force = NormalGeometrySet) = 0; void setGeometry(const QRect& r, ForceGeometry_t force = NormalGeometrySet); /// How to resize the window in order to obey constains (mainly aspect ratios) enum Sizemode { SizemodeAny, SizemodeFixedW, ///< Try not to affect width SizemodeFixedH, ///< Try not to affect height SizemodeMax ///< Try not to make it larger in either direction }; /** * Calculates the appropriate frame size for the given client size @p wsize. * * @p wsize is adapted according to the window's size hints (minimum, maximum and incremental size changes). * * Default implementation returns the passed in @p wsize. */ virtual QSize sizeForClientSize(const QSize &wsize, Sizemode mode = SizemodeAny, bool noframe = false) const; /** * Adjust the frame size @p frame according to the window's size hints. */ QSize adjustedSize(const QSize&, Sizemode mode = SizemodeAny) const; QSize adjustedSize() const; bool isMove() const { return isMoveResize() && moveResizePointerMode() == PositionCenter; } bool isResize() const { return isMoveResize() && moveResizePointerMode() != PositionCenter; } /** * Cursor shape for move/resize mode. */ CursorShape cursor() const { return m_moveResize.cursor; } virtual bool hasStrut() const; void setModal(bool modal); bool isModal() const; /** * Determines the mouse command for the given @p button in the current state. * * The @p handled argument specifies whether the button was handled or not. * This value should be used to determine whether the mouse button should be * passed to the AbstractClient or being filtered out. */ Options::MouseCommand getMouseCommand(Qt::MouseButton button, bool *handled) const; Options::MouseCommand getWheelCommand(Qt::Orientation orientation, bool *handled) const; // decoration related KDecoration2::Decoration *decoration() { return m_decoration.decoration; } const KDecoration2::Decoration *decoration() const { return m_decoration.decoration; } bool isDecorated() const { return m_decoration.decoration != nullptr; } QPointer decoratedClient() const; void setDecoratedClient(QPointer client); bool decorationHasAlpha() const; void triggerDecorationRepaint(); virtual void layoutDecorationRects(QRect &left, QRect &top, QRect &right, QRect &bottom) const; void processDecorationMove(const QPoint &localPos, const QPoint &globalPos); bool processDecorationButtonPress(QMouseEvent *event, bool ignoreMenu = false); void processDecorationButtonRelease(QMouseEvent *event); /** * TODO: fix boolean traps */ virtual void updateDecoration(bool check_workspace_pos, bool force = false) = 0; /** * Returns whether the window provides context help or not. If it does, * you should show a help menu item or a help button like '?' and call * contextHelp() if this is invoked. * * Default implementation returns @c false. * @see showContextHelp; */ virtual bool providesContextHelp() const; /** * Invokes context help on the window. Only works if the window * actually provides context help. * * Default implementation does nothing. * * @see providesContextHelp() */ virtual void showContextHelp(); QRect inputGeometry() const override; /** * @returns the geometry of the virtual keyboard * This geometry is in global coordinates */ QRect virtualKeyboardGeometry() const; /** * Sets the geometry of the virtual keyboard, The window may resize itself in order to make space for the keybaord * This geometry is in global coordinates */ void setVirtualKeyboardGeometry(const QRect &geo); /** * Restores the AbstractClient after it had been hidden due to show on screen edge functionality. * The AbstractClient also gets raised (e.g. Panel mode windows can cover) and the AbstractClient * gets informed in a window specific way that it is shown and raised again. */ virtual void showOnScreenEdge() = 0; QByteArray desktopFileName() const { 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; enum class SameApplicationCheck { RelaxedForActive = 1 << 0, AllowCrossProcesses = 1 << 1 }; Q_DECLARE_FLAGS(SameApplicationChecks, SameApplicationCheck) static bool belongToSameApplication(const AbstractClient* c1, const AbstractClient* c2, SameApplicationChecks checks = SameApplicationChecks()); bool hasApplicationMenu() const; bool applicationMenuActive() const { return m_applicationMenuActive; } void setApplicationMenuActive(bool applicationMenuActive); QString applicationMenuServiceName() const { return m_applicationMenuServiceName; } QString applicationMenuObjectPath() const { return m_applicationMenuObjectPath; } QString colorScheme() const { return m_colorScheme; } /** * Request showing the application menu bar * @param actionId The DBus menu ID of the action that should be highlighted, 0 for the root menu */ void showApplicationMenu(int actionId); bool unresponsive() const; virtual bool isInitialPositionSet() const { return false; } /** * Default implementation returns @c null. * Mostly intended for X11 clients, from EWMH: * @verbatim * If the WM_TRANSIENT_FOR property is set to None or Root window, the window should be * treated as a transient for all other windows in the same group. It has been noted that this * is a slight ICCCM violation, but as this behavior is pretty standard for many toolkits and * window managers, and is extremely unlikely to break anything, it seems reasonable to document * it as standard. * @endverbatim */ virtual bool groupTransient() const; /** * Default implementation returns @c null. * * Mostly for X11 clients, holds the client group */ virtual const Group *group() const; /** * Default implementation returns @c null. * * Mostly for X11 clients, holds the client group */ virtual Group *group(); /** * Returns whether this is an internal client. * * Internal clients are created by KWin and used for special purpose windows, * like the task switcher, etc. * * Default implementation returns @c false. */ virtual bool isInternal() const; /** * Returns whether window rules can be applied to this client. * * Default implementation returns @c true. */ virtual bool supportsWindowRules() const; + /** + * Returns the extents of the server-side decoration. + * + * Note that the returned margins object will have all margins set to 0 if + * the client doesn't have a server-side decoration. + */ + QMargins frameMargins() const; + public Q_SLOTS: virtual void closeWindow() = 0; Q_SIGNALS: void fullScreenChanged(); void skipTaskbarChanged(); void skipPagerChanged(); void skipSwitcherChanged(); void iconChanged(); void activeChanged(); void keepAboveChanged(bool); void keepBelowChanged(bool); /** * Emitted whenever the demands attention state changes. */ void demandsAttentionChanged(); void desktopPresenceChanged(KWin::AbstractClient*, int); // to be forwarded by Workspace void desktopChanged(); void x11DesktopIdsChanged(); void shadeChanged(); void minimizedChanged(); void clientMinimized(KWin::AbstractClient* client, bool animate); void clientUnminimized(KWin::AbstractClient* client, bool animate); void paletteChanged(const QPalette &p); void colorSchemeChanged(); void captionChanged(); void clientMaximizedStateChanged(KWin::AbstractClient*, MaximizeMode); void clientMaximizedStateChanged(KWin::AbstractClient* c, bool h, bool v); void transientChanged(); void modalChanged(); void quickTileModeChanged(); void moveResizedChanged(); void moveResizeCursorChanged(CursorShape); void clientStartUserMovedResized(KWin::AbstractClient*); void clientStepUserMovedResized(KWin::AbstractClient *, const QRect&); void clientFinishUserMovedResized(KWin::AbstractClient*); void closeableChanged(bool); void minimizeableChanged(bool); void shadeableChanged(bool); void maximizeableChanged(bool); void desktopFileNameChanged(); void hasApplicationMenuChanged(bool); void applicationMenuActiveChanged(bool); void unresponsiveChanged(bool); protected: AbstractClient(); void setFirstInTabBox(bool enable) { m_firstInTabBox = enable; } void setIcon(const QIcon &icon); void startAutoRaise(); void autoRaise(); /** * Whether the window accepts focus. * The difference to wantsInput is that the implementation should not check rules and return * what the window effectively supports. */ virtual bool acceptsFocus() const = 0; /** * Called from setActive once the active value got updated, but before the changed signal * is emitted. * * Default implementation does nothing. */ virtual void doSetActive(); /** * Called from setKeepAbove once the keepBelow value got updated, but before the changed signal * is emitted. * * Default implementation does nothing. */ virtual void doSetKeepAbove(); /** * Called from setKeepBelow once the keepBelow value got updated, but before the changed signal * is emitted. * * Default implementation does nothing. */ virtual void doSetKeepBelow(); /** * Called from setDeskop once the desktop value got updated, but before the changed signal * is emitted. * * Default implementation does nothing. * @param desktop The new desktop the Client is on * @param was_desk The desktop the Client was on before */ virtual void doSetDesktop(int desktop, int was_desk); /** * Called from @ref minimize and @ref unminimize once the minimized value got updated, but before the * changed signal is emitted. * * Default implementation does nothig. */ virtual void doMinimize(); virtual bool belongsToSameApplication(const AbstractClient *other, SameApplicationChecks checks) const = 0; virtual void doSetSkipTaskbar(); virtual void doSetSkipPager(); virtual void doSetSkipSwitcher(); void setupWindowManagementInterface(); void destroyWindowManagementInterface(); void updateColorScheme(QString path); virtual void updateColorScheme() = 0; void setTransientFor(AbstractClient *transientFor); virtual void addTransient(AbstractClient* cl); /** * Just removes the @p cl from the transients without any further checks. */ void removeTransientFromList(AbstractClient* cl); Layer belongsToLayer() const; virtual bool belongsToDesktop() const; void invalidateLayer(); bool isActiveFullScreen() const; virtual Layer layerForDock() const; // electric border / quick tiling void setElectricBorderMode(QuickTileMode mode); QuickTileMode electricBorderMode() const { return m_electricMode; } void setElectricBorderMaximizing(bool maximizing); bool isElectricBorderMaximizing() const { return m_electricMaximizing; } QRect electricBorderMaximizeGeometry(QPoint pos, int desktop); void updateQuickTileMode(QuickTileMode newMode) { m_quickTileMode = newMode; } KWayland::Server::PlasmaWindowInterface *windowManagementInterface() const { return m_windowManagementInterface; } // geometry handling void checkOffscreenPosition(QRect *geom, const QRect &screenArea); int borderLeft() const; int borderRight() const; int borderTop() const; int borderBottom() const; virtual void changeMaximize(bool horizontal, bool vertical, bool adjust) = 0; virtual void setGeometryRestore(const QRect &geo) = 0; /** * Called from move after updating the geometry. Can be reimplemented to perform specific tasks. * The base implementation does nothing. */ virtual void doMove(int x, int y); void blockGeometryUpdates(bool block); void blockGeometryUpdates(); void unblockGeometryUpdates(); bool areGeometryUpdatesBlocked() const; enum PendingGeometry_t { PendingGeometryNone, PendingGeometryNormal, PendingGeometryForced }; PendingGeometry_t pendingGeometryUpdate() const; void setPendingGeometryUpdate(PendingGeometry_t update); QRect geometryBeforeUpdateBlocking() const { return m_geometryBeforeUpdateBlocking; } void updateGeometryBeforeUpdateBlocking(); /** * Schedules a repaint for the visibleRect before and after a * geometry update. The current visibleRect is stored for the * next time this method is called as the before geometry. */ void addRepaintDuringGeometryUpdates(); /** * @returns whether the Client is currently in move resize mode */ bool isMoveResize() const { return m_moveResize.enabled; } /** * Sets whether the Client is in move resize mode to @p enabled. */ void setMoveResize(bool enabled) { m_moveResize.enabled = enabled; } /** * @returns whether the move resize mode is unrestricted. */ bool isUnrestrictedMoveResize() const { return m_moveResize.unrestricted; } /** * Sets whether move resize mode is unrestricted to @p set. */ void setUnrestrictedMoveResize(bool set) { m_moveResize.unrestricted = set; } QPoint moveOffset() const { return m_moveResize.offset; } void setMoveOffset(const QPoint &offset) { m_moveResize.offset = offset; } QPoint invertedMoveOffset() const { return m_moveResize.invertedOffset; } void setInvertedMoveOffset(const QPoint &offset) { m_moveResize.invertedOffset = offset; } QRect initialMoveResizeGeometry() const { return m_moveResize.initialGeometry; } /** * Sets the initial move resize geometry to the current geometry. */ void updateInitialMoveResizeGeometry(); QRect moveResizeGeometry() const { return m_moveResize.geometry; } void setMoveResizeGeometry(const QRect &geo) { m_moveResize.geometry = geo; } Position moveResizePointerMode() const { return m_moveResize.pointer; } void setMoveResizePointerMode(Position mode) { m_moveResize.pointer = mode; } bool isMoveResizePointerButtonDown() const { return m_moveResize.buttonDown; } void setMoveResizePointerButtonDown(bool down) { m_moveResize.buttonDown = down; } int moveResizeStartScreen() const { return m_moveResize.startScreen; } void checkUnrestrictedMoveResize(); /** * Sets an appropriate cursor shape for the logical mouse position. */ void updateCursor(); void startDelayedMoveResize(); void stopDelayedMoveResize(); bool startMoveResize(); /** * Called from startMoveResize. * * Implementing classes should return @c false if starting move resize should * get aborted. In that case startMoveResize will also return @c false. * * Base implementation returns @c true. */ virtual bool doStartMoveResize(); void finishMoveResize(bool cancel); /** * Leaves the move resize mode. * * Inheriting classes must invoke the base implementation which * ensures that the internal mode is properly ended. */ virtual void leaveMoveResize(); virtual void positionGeometryTip(); void performMoveResize(); /** * Called from performMoveResize() after actually performing the change of geometry. * Implementing subclasses can perform windowing system specific handling here. * * Default implementation does nothing. */ virtual void doPerformMoveResize(); /* * Checks if the mouse cursor is near the edge of the screen and if so * activates quick tiling or maximization */ void checkQuickTilingMaximizationZones(int xroot, int yroot); /** * Whether a sync request is still pending. * Default implementation returns @c false. */ virtual bool isWaitingForMoveResizeSync() const; /** * Called during handling a resize. Implementing subclasses can use this * method to perform windowing system specific syncing. * * Default implementation does nothing. */ virtual void doResizeSync(); void handleMoveResize(int x, int y, int x_root, int y_root); void handleMoveResize(const QPoint &local, const QPoint &global); void dontMoveResize(); virtual QSize resizeIncrements() const; /** * Returns the position depending on the Decoration's section under mouse. * If no decoration it returns PositionCenter. */ Position mousePosition() const; static bool haveResizeEffect() { return s_haveResizeEffect; } static void updateHaveResizeEffect(); static void resetHaveResizeEffect() { s_haveResizeEffect = false; } void setDecoration(KDecoration2::Decoration *decoration) { m_decoration.decoration = decoration; } virtual void destroyDecoration(); void startDecorationDoubleClickTimer(); void invalidateDecorationDoubleClickTimer(); void setDesktopFileName(QByteArray name); QString iconFromDesktopFile() const; void updateApplicationMenuServiceName(const QString &serviceName); void updateApplicationMenuObjectPath(const QString &objectPath); void setUnresponsive(bool unresponsive); virtual void setShortcutInternal(); QString shortcutCaptionSuffix() const; virtual void updateCaption() = 0; /** * Looks for another AbstractClient with same captionNormal and captionSuffix. * If no such AbstractClient exists @c nullptr is returned. */ AbstractClient *findClientWithSameCaption() const; void finishWindowRules(); void discardTemporaryRules(); bool tabTo(AbstractClient *other, bool behind, bool activate); private: void handlePaletteChange(); QSharedPointer m_tabBoxClient; bool m_firstInTabBox = false; bool m_skipTaskbar = false; /** * Unaffected by KWin */ bool m_originalSkipTaskbar = false; bool m_skipPager = false; bool m_skipSwitcher = false; QIcon m_icon; bool m_active = false; bool m_keepAbove = false; bool m_keepBelow = false; bool m_demandsAttention = false; bool m_minimized = false; QTimer *m_autoRaiseTimer = nullptr; QVector m_desktops; QString m_colorScheme; std::shared_ptr m_palette; static QHash> s_palettes; static std::shared_ptr s_defaultPalette; KWayland::Server::PlasmaWindowInterface *m_windowManagementInterface = nullptr; AbstractClient *m_transientFor = nullptr; QList m_transients; bool m_modal = false; Layer m_layer = UnknownLayer; // electric border/quick tiling QuickTileMode m_electricMode = QuickTileFlag::None; bool m_electricMaximizing = false; // The quick tile mode of this window. int m_quickTileMode = int(QuickTileFlag::None); QTimer *m_electricMaximizingDelay = nullptr; // geometry int m_blockGeometryUpdates = 0; // > 0 = New geometry is remembered, but not actually set PendingGeometry_t m_pendingGeometryUpdate = PendingGeometryNone; friend class GeometryUpdatesBlocker; QRect m_visibleRectBeforeGeometryUpdate; QRect m_geometryBeforeUpdateBlocking; QRect m_virtualKeyboardGeometry; QRect m_keyboardGeometryRestore; struct { bool enabled = false; bool unrestricted = false; QPoint offset; QPoint invertedOffset; QRect initialGeometry; QRect geometry; Position pointer = PositionCenter; bool buttonDown = false; CursorShape cursor = Qt::ArrowCursor; int startScreen = 0; QTimer *delayedTimer = nullptr; } m_moveResize; struct { KDecoration2::Decoration *decoration = nullptr; QPointer client; QElapsedTimer doubleClickTimer; } m_decoration; QByteArray m_desktopFileName; bool m_applicationMenuActive = false; QString m_applicationMenuServiceName; QString m_applicationMenuObjectPath; bool m_unresponsive = false; QKeySequence _shortcut; WindowRules m_rules; static bool s_haveResizeEffect; }; /** * Helper for AbstractClient::blockGeometryUpdates() being called in pairs (true/false) */ class GeometryUpdatesBlocker { public: explicit GeometryUpdatesBlocker(AbstractClient* c) : cl(c) { cl->blockGeometryUpdates(true); } ~GeometryUpdatesBlocker() { cl->blockGeometryUpdates(false); } private: AbstractClient* cl; }; inline void AbstractClient::move(const QPoint& p, ForceGeometry_t force) { move(p.x(), p.y(), force); } inline void AbstractClient::resizeWithChecks(const QSize& s, AbstractClient::ForceGeometry_t force) { resizeWithChecks(s.width(), s.height(), force); } inline void AbstractClient::setGeometry(const QRect& r, ForceGeometry_t force) { setGeometry(r.x(), r.y(), r.width(), r.height(), force); } inline const QList& AbstractClient::transients() const { return m_transients; } inline bool AbstractClient::areGeometryUpdatesBlocked() const { return m_blockGeometryUpdates != 0; } inline void AbstractClient::blockGeometryUpdates() { m_blockGeometryUpdates++; } inline void AbstractClient::unblockGeometryUpdates() { m_blockGeometryUpdates--; } inline AbstractClient::PendingGeometry_t AbstractClient::pendingGeometryUpdate() const { return m_pendingGeometryUpdate; } inline void AbstractClient::setPendingGeometryUpdate(PendingGeometry_t update) { m_pendingGeometryUpdate = update; } } Q_DECLARE_METATYPE(KWin::AbstractClient*) Q_DECLARE_METATYPE(QList) Q_DECLARE_OPERATORS_FOR_FLAGS(KWin::AbstractClient::SameApplicationChecks) #endif diff --git a/autotests/integration/debug_console_test.cpp b/autotests/integration/debug_console_test.cpp index 9a484f5a1..afae890a4 100644 --- a/autotests/integration/debug_console_test.cpp +++ b/autotests/integration/debug_console_test.cpp @@ -1,531 +1,533 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2016 Martin Gräßlin 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 . *********************************************************************/ #include "kwin_wayland_test.h" -#include "platform.h" #include "debug_console.h" +#include "internal_client.h" +#include "platform.h" #include "screens.h" #include "shell_client.h" #include "wayland_server.h" +#include "workspace.h" #include "xcbutils.h" #include #include #include #include #include #include #include namespace KWin { static const QString s_socketName = QStringLiteral("wayland_test_kwin_debug_console-0"); class DebugConsoleTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void cleanup(); void topLevelTest_data(); void topLevelTest(); void testX11Client(); void testX11Unmanaged(); void testWaylandClient_data(); void testWaylandClient(); void testInternalWindow(); void testClosingDebugConsole(); }; void DebugConsoleTest::initTestCase() { - qRegisterMetaType(); - qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType(); QSignalSpy workspaceCreatedSpy(kwinApp(), &Application::workspaceCreated); QVERIFY(workspaceCreatedSpy.isValid()); kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); QVERIFY(waylandServer()->init(s_socketName.toLocal8Bit())); QMetaObject::invokeMethod(kwinApp()->platform(), "setVirtualOutputs", Qt::DirectConnection, Q_ARG(int, 2)); kwinApp()->start(); QVERIFY(workspaceCreatedSpy.wait()); QCOMPARE(screens()->count(), 2); QCOMPARE(screens()->geometry(0), QRect(0, 0, 1280, 1024)); QCOMPARE(screens()->geometry(1), QRect(1280, 0, 1280, 1024)); setenv("QT_QPA_PLATFORM", "wayland", true); waylandServer()->initWorkspace(); } void DebugConsoleTest::cleanup() { Test::destroyWaylandConnection(); } void DebugConsoleTest::topLevelTest_data() { QTest::addColumn("row"); QTest::addColumn("column"); QTest::addColumn("expectedValid"); // this tests various combinations of row/column on the top level whether they are valid // valid are rows 0-4 with column 0, everything else is invalid QTest::newRow("0/0") << 0 << 0 << true; QTest::newRow("0/1") << 0 << 1 << false; QTest::newRow("0/3") << 0 << 3 << false; QTest::newRow("1/0") << 1 << 0 << true; QTest::newRow("1/1") << 1 << 1 << false; QTest::newRow("1/3") << 1 << 3 << false; QTest::newRow("2/0") << 2 << 0 << true; QTest::newRow("3/0") << 3 << 0 << true; QTest::newRow("4/0") << 4 << 0 << false; QTest::newRow("100/0") << 4 << 0 << false; } void DebugConsoleTest::topLevelTest() { DebugConsoleModel model; QCOMPARE(model.rowCount(QModelIndex()), 4); QCOMPARE(model.columnCount(QModelIndex()), 2); QFETCH(int, row); QFETCH(int, column); const QModelIndex index = model.index(row, column, QModelIndex()); QTEST(index.isValid(), "expectedValid"); if (index.isValid()) { QVERIFY(!model.parent(index).isValid()); QVERIFY(model.data(index, Qt::DisplayRole).isValid()); QCOMPARE(model.data(index, Qt::DisplayRole).userType(), int(QMetaType::QString)); for (int i = Qt::DecorationRole; i <= Qt::UserRole; i++) { QVERIFY(!model.data(index, i).isValid()); } } } void DebugConsoleTest::testX11Client() { DebugConsoleModel model; QModelIndex x11TopLevelIndex = model.index(0, 0, QModelIndex()); QVERIFY(x11TopLevelIndex.isValid()); // we don't have any windows yet QCOMPARE(model.rowCount(x11TopLevelIndex), 0); QVERIFY(!model.hasChildren(x11TopLevelIndex)); // child index must be invalid QVERIFY(!model.index(0, 0, x11TopLevelIndex).isValid()); QVERIFY(!model.index(0, 1, x11TopLevelIndex).isValid()); QVERIFY(!model.index(0, 2, x11TopLevelIndex).isValid()); QVERIFY(!model.index(1, 0, x11TopLevelIndex).isValid()); // start glxgears, to get a window, which should be added to the model QSignalSpy rowsInsertedSpy(&model, &QAbstractItemModel::rowsInserted); QVERIFY(rowsInsertedSpy.isValid()); QProcess glxgears; glxgears.start(QStringLiteral("glxgears")); QVERIFY(glxgears.waitForStarted()); QVERIFY(rowsInsertedSpy.wait()); QCOMPARE(rowsInsertedSpy.count(), 1); QVERIFY(model.hasChildren(x11TopLevelIndex)); QCOMPARE(model.rowCount(x11TopLevelIndex), 1); QCOMPARE(rowsInsertedSpy.first().at(0).value(), x11TopLevelIndex); QCOMPARE(rowsInsertedSpy.first().at(1).value(), 0); QCOMPARE(rowsInsertedSpy.first().at(2).value(), 0); QModelIndex clientIndex = model.index(0, 0, x11TopLevelIndex); QVERIFY(clientIndex.isValid()); QCOMPARE(model.parent(clientIndex), x11TopLevelIndex); QVERIFY(model.hasChildren(clientIndex)); QVERIFY(model.rowCount(clientIndex) != 0); QCOMPARE(model.columnCount(clientIndex), 2); // other indexes are still invalid QVERIFY(!model.index(0, 1, x11TopLevelIndex).isValid()); QVERIFY(!model.index(0, 2, x11TopLevelIndex).isValid()); QVERIFY(!model.index(1, 0, x11TopLevelIndex).isValid()); // the clientIndex has children and those are properties for (int i = 0; i < model.rowCount(clientIndex); i++) { const QModelIndex propNameIndex = model.index(i, 0, clientIndex); QVERIFY(propNameIndex.isValid()); QCOMPARE(model.parent(propNameIndex), clientIndex); QVERIFY(!model.hasChildren(propNameIndex)); QVERIFY(!model.index(0, 0, propNameIndex).isValid()); QVERIFY(model.data(propNameIndex, Qt::DisplayRole).isValid()); QCOMPARE(model.data(propNameIndex, Qt::DisplayRole).userType(), int(QMetaType::QString)); // and the value const QModelIndex propValueIndex = model.index(i, 1, clientIndex); QVERIFY(propValueIndex.isValid()); QCOMPARE(model.parent(propValueIndex), clientIndex); QVERIFY(!model.index(0, 0, propValueIndex).isValid()); QVERIFY(!model.hasChildren(propValueIndex)); // TODO: how to test whether the values actually work? // and on third column we should not get an index any more QVERIFY(!model.index(i, 2, clientIndex).isValid()); } // row after count should be invalid QVERIFY(!model.index(model.rowCount(clientIndex), 0, clientIndex).isValid()); // creating a second model should be initialized directly with the X11 child DebugConsoleModel model2; QVERIFY(model2.hasChildren(model2.index(0, 0, QModelIndex()))); // now close the window again, it should be removed from the model QSignalSpy rowsRemovedSpy(&model, &QAbstractItemModel::rowsRemoved); QVERIFY(rowsRemovedSpy.isValid()); glxgears.terminate(); QVERIFY(glxgears.waitForFinished()); QVERIFY(rowsRemovedSpy.wait()); QCOMPARE(rowsRemovedSpy.count(), 1); QCOMPARE(rowsRemovedSpy.first().first().value(), x11TopLevelIndex); QCOMPARE(rowsRemovedSpy.first().at(1).value(), 0); QCOMPARE(rowsRemovedSpy.first().at(2).value(), 0); // the child should be gone again QVERIFY(!model.hasChildren(x11TopLevelIndex)); QVERIFY(!model2.hasChildren(model2.index(0, 0, QModelIndex()))); } void DebugConsoleTest::testX11Unmanaged() { DebugConsoleModel model; QModelIndex unmanagedTopLevelIndex = model.index(1, 0, QModelIndex()); QVERIFY(unmanagedTopLevelIndex.isValid()); // we don't have any windows yet QCOMPARE(model.rowCount(unmanagedTopLevelIndex), 0); QVERIFY(!model.hasChildren(unmanagedTopLevelIndex)); // child index must be invalid QVERIFY(!model.index(0, 0, unmanagedTopLevelIndex).isValid()); QVERIFY(!model.index(0, 1, unmanagedTopLevelIndex).isValid()); QVERIFY(!model.index(0, 2, unmanagedTopLevelIndex).isValid()); QVERIFY(!model.index(1, 0, unmanagedTopLevelIndex).isValid()); // we need to create an unmanaged window QSignalSpy rowsInsertedSpy(&model, &QAbstractItemModel::rowsInserted); QVERIFY(rowsInsertedSpy.isValid()); // let's create an override redirect window const uint32_t values[] = {true}; Xcb::Window window(QRect(0, 0, 10, 10), XCB_CW_OVERRIDE_REDIRECT, values); window.map(); QVERIFY(rowsInsertedSpy.wait()); QCOMPARE(rowsInsertedSpy.count(), 1); QVERIFY(model.hasChildren(unmanagedTopLevelIndex)); QCOMPARE(model.rowCount(unmanagedTopLevelIndex), 1); QCOMPARE(rowsInsertedSpy.first().at(0).value(), unmanagedTopLevelIndex); QCOMPARE(rowsInsertedSpy.first().at(1).value(), 0); QCOMPARE(rowsInsertedSpy.first().at(2).value(), 0); QModelIndex clientIndex = model.index(0, 0, unmanagedTopLevelIndex); QVERIFY(clientIndex.isValid()); QCOMPARE(model.parent(clientIndex), unmanagedTopLevelIndex); QVERIFY(model.hasChildren(clientIndex)); QVERIFY(model.rowCount(clientIndex) != 0); QCOMPARE(model.columnCount(clientIndex), 2); // other indexes are still invalid QVERIFY(!model.index(0, 1, unmanagedTopLevelIndex).isValid()); QVERIFY(!model.index(0, 2, unmanagedTopLevelIndex).isValid()); QVERIFY(!model.index(1, 0, unmanagedTopLevelIndex).isValid()); QCOMPARE(model.data(clientIndex, Qt::DisplayRole).toString(), QString::number(window)); // the clientIndex has children and those are properties for (int i = 0; i < model.rowCount(clientIndex); i++) { const QModelIndex propNameIndex = model.index(i, 0, clientIndex); QVERIFY(propNameIndex.isValid()); QCOMPARE(model.parent(propNameIndex), clientIndex); QVERIFY(!model.hasChildren(propNameIndex)); QVERIFY(!model.index(0, 0, propNameIndex).isValid()); QVERIFY(model.data(propNameIndex, Qt::DisplayRole).isValid()); QCOMPARE(model.data(propNameIndex, Qt::DisplayRole).userType(), int(QMetaType::QString)); // and the value const QModelIndex propValueIndex = model.index(i, 1, clientIndex); QVERIFY(propValueIndex.isValid()); QCOMPARE(model.parent(propValueIndex), clientIndex); QVERIFY(!model.index(0, 0, propValueIndex).isValid()); QVERIFY(!model.hasChildren(propValueIndex)); // TODO: how to test whether the values actually work? // and on third column we should not get an index any more QVERIFY(!model.index(i, 2, clientIndex).isValid()); } // row after count should be invalid QVERIFY(!model.index(model.rowCount(clientIndex), 0, clientIndex).isValid()); // creating a second model should be initialized directly with the X11 child DebugConsoleModel model2; QVERIFY(model2.hasChildren(model2.index(1, 0, QModelIndex()))); // now close the window again, it should be removed from the model QSignalSpy rowsRemovedSpy(&model, &QAbstractItemModel::rowsRemoved); QVERIFY(rowsRemovedSpy.isValid()); window.unmap(); QVERIFY(rowsRemovedSpy.wait()); QCOMPARE(rowsRemovedSpy.count(), 1); QCOMPARE(rowsRemovedSpy.first().first().value(), unmanagedTopLevelIndex); QCOMPARE(rowsRemovedSpy.first().at(1).value(), 0); QCOMPARE(rowsRemovedSpy.first().at(2).value(), 0); // the child should be gone again QVERIFY(!model.hasChildren(unmanagedTopLevelIndex)); QVERIFY(!model2.hasChildren(model2.index(1, 0, QModelIndex()))); } void DebugConsoleTest::testWaylandClient_data() { QTest::addColumn("type"); QTest::newRow("wlShell") << Test::ShellSurfaceType::WlShell; QTest::newRow("xdgShellV5") << Test::ShellSurfaceType::XdgShellV5; } void DebugConsoleTest::testWaylandClient() { DebugConsoleModel model; QModelIndex waylandTopLevelIndex = model.index(2, 0, QModelIndex()); QVERIFY(waylandTopLevelIndex.isValid()); // we don't have any windows yet QCOMPARE(model.rowCount(waylandTopLevelIndex), 0); QVERIFY(!model.hasChildren(waylandTopLevelIndex)); // child index must be invalid QVERIFY(!model.index(0, 0, waylandTopLevelIndex).isValid()); QVERIFY(!model.index(0, 1, waylandTopLevelIndex).isValid()); QVERIFY(!model.index(0, 2, waylandTopLevelIndex).isValid()); QVERIFY(!model.index(1, 0, waylandTopLevelIndex).isValid()); // we need to create a wayland window QSignalSpy rowsInsertedSpy(&model, &QAbstractItemModel::rowsInserted); QVERIFY(rowsInsertedSpy.isValid()); // create our connection QVERIFY(Test::setupWaylandConnection()); // create the Surface and ShellSurface using namespace KWayland::Client; QScopedPointer surface(Test::createSurface()); QVERIFY(surface->isValid()); QFETCH(Test::ShellSurfaceType, type); QScopedPointer shellSurface(Test::createShellSurface(type, surface.data())); QVERIFY(!shellSurface.isNull()); Test::render(surface.data(), QSize(10, 10), Qt::red); // now we have the window, it should be added to our model QVERIFY(rowsInsertedSpy.wait()); QCOMPARE(rowsInsertedSpy.count(), 1); QVERIFY(model.hasChildren(waylandTopLevelIndex)); QCOMPARE(model.rowCount(waylandTopLevelIndex), 1); QCOMPARE(rowsInsertedSpy.first().at(0).value(), waylandTopLevelIndex); QCOMPARE(rowsInsertedSpy.first().at(1).value(), 0); QCOMPARE(rowsInsertedSpy.first().at(2).value(), 0); QModelIndex clientIndex = model.index(0, 0, waylandTopLevelIndex); QVERIFY(clientIndex.isValid()); QCOMPARE(model.parent(clientIndex), waylandTopLevelIndex); QVERIFY(model.hasChildren(clientIndex)); QVERIFY(model.rowCount(clientIndex) != 0); QCOMPARE(model.columnCount(clientIndex), 2); // other indexes are still invalid QVERIFY(!model.index(0, 1, waylandTopLevelIndex).isValid()); QVERIFY(!model.index(0, 2, waylandTopLevelIndex).isValid()); QVERIFY(!model.index(1, 0, waylandTopLevelIndex).isValid()); // the clientIndex has children and those are properties for (int i = 0; i < model.rowCount(clientIndex); i++) { const QModelIndex propNameIndex = model.index(i, 0, clientIndex); QVERIFY(propNameIndex.isValid()); QCOMPARE(model.parent(propNameIndex), clientIndex); QVERIFY(!model.hasChildren(propNameIndex)); QVERIFY(!model.index(0, 0, propNameIndex).isValid()); QVERIFY(model.data(propNameIndex, Qt::DisplayRole).isValid()); QCOMPARE(model.data(propNameIndex, Qt::DisplayRole).userType(), int(QMetaType::QString)); // and the value const QModelIndex propValueIndex = model.index(i, 1, clientIndex); QVERIFY(propValueIndex.isValid()); QCOMPARE(model.parent(propValueIndex), clientIndex); QVERIFY(!model.index(0, 0, propValueIndex).isValid()); QVERIFY(!model.hasChildren(propValueIndex)); // TODO: how to test whether the values actually work? // and on third column we should not get an index any more QVERIFY(!model.index(i, 2, clientIndex).isValid()); } // row after count should be invalid QVERIFY(!model.index(model.rowCount(clientIndex), 0, clientIndex).isValid()); // creating a second model should be initialized directly with the X11 child DebugConsoleModel model2; QVERIFY(model2.hasChildren(model2.index(2, 0, QModelIndex()))); // now close the window again, it should be removed from the model QSignalSpy rowsRemovedSpy(&model, &QAbstractItemModel::rowsRemoved); QVERIFY(rowsRemovedSpy.isValid()); surface->attachBuffer(Buffer::Ptr()); surface->commit(Surface::CommitFlag::None); shellSurface.reset(); Test::flushWaylandConnection(); qDebug() << rowsRemovedSpy.count(); QEXPECT_FAIL("wlShell", "Deleting a ShellSurface does not result in the server removing the ShellClient", Continue); QVERIFY(rowsRemovedSpy.wait(500)); surface.reset(); if (rowsRemovedSpy.isEmpty()) { QVERIFY(rowsRemovedSpy.wait()); } QCOMPARE(rowsRemovedSpy.count(), 1); QCOMPARE(rowsRemovedSpy.first().first().value(), waylandTopLevelIndex); QCOMPARE(rowsRemovedSpy.first().at(1).value(), 0); QCOMPARE(rowsRemovedSpy.first().at(2).value(), 0); // the child should be gone again QVERIFY(!model.hasChildren(waylandTopLevelIndex)); QVERIFY(!model2.hasChildren(model2.index(2, 0, QModelIndex()))); } class HelperWindow : public QRasterWindow { Q_OBJECT public: HelperWindow() : QRasterWindow(nullptr) {} ~HelperWindow() override = default; Q_SIGNALS: void entered(); void left(); void mouseMoved(const QPoint &global); void mousePressed(); void mouseReleased(); void wheel(); void keyPressed(); void keyReleased(); protected: void paintEvent(QPaintEvent *event) override { Q_UNUSED(event) QPainter p(this); p.fillRect(0, 0, width(), height(), Qt::red); } }; void DebugConsoleTest::testInternalWindow() { DebugConsoleModel model; QModelIndex internalTopLevelIndex = model.index(3, 0, QModelIndex()); QVERIFY(internalTopLevelIndex.isValid()); // there might already be some internal windows, so we cannot reliable test whether there are children // given that we just test whether adding a window works. QSignalSpy rowsInsertedSpy(&model, &QAbstractItemModel::rowsInserted); QVERIFY(rowsInsertedSpy.isValid()); QScopedPointer w(new HelperWindow); w->setGeometry(0, 0, 100, 100); w->show(); QTRY_COMPARE(rowsInsertedSpy.count(), 1); QCOMPARE(rowsInsertedSpy.first().first().value(), internalTopLevelIndex); QModelIndex clientIndex = model.index(rowsInsertedSpy.first().last().toInt(), 0, internalTopLevelIndex); QVERIFY(clientIndex.isValid()); QCOMPARE(model.parent(clientIndex), internalTopLevelIndex); QVERIFY(model.hasChildren(clientIndex)); QVERIFY(model.rowCount(clientIndex) != 0); QCOMPARE(model.columnCount(clientIndex), 2); // other indexes are still invalid QVERIFY(!model.index(rowsInsertedSpy.first().last().toInt(), 1, internalTopLevelIndex).isValid()); QVERIFY(!model.index(rowsInsertedSpy.first().last().toInt(), 2, internalTopLevelIndex).isValid()); QVERIFY(!model.index(rowsInsertedSpy.first().last().toInt() + 1, 0, internalTopLevelIndex).isValid()); // the wayland shell client top level should not have gained this window QVERIFY(!model.hasChildren(model.index(2, 0, QModelIndex()))); // the clientIndex has children and those are properties for (int i = 0; i < model.rowCount(clientIndex); i++) { const QModelIndex propNameIndex = model.index(i, 0, clientIndex); QVERIFY(propNameIndex.isValid()); QCOMPARE(model.parent(propNameIndex), clientIndex); QVERIFY(!model.hasChildren(propNameIndex)); QVERIFY(!model.index(0, 0, propNameIndex).isValid()); QVERIFY(model.data(propNameIndex, Qt::DisplayRole).isValid()); QCOMPARE(model.data(propNameIndex, Qt::DisplayRole).userType(), int(QMetaType::QString)); // and the value const QModelIndex propValueIndex = model.index(i, 1, clientIndex); QVERIFY(propValueIndex.isValid()); QCOMPARE(model.parent(propValueIndex), clientIndex); QVERIFY(!model.index(0, 0, propValueIndex).isValid()); QVERIFY(!model.hasChildren(propValueIndex)); // TODO: how to test whether the values actually work? // and on third column we should not get an index any more QVERIFY(!model.index(i, 2, clientIndex).isValid()); } // row after count should be invalid QVERIFY(!model.index(model.rowCount(clientIndex), 0, clientIndex).isValid()); // now close the window again, it should be removed from the model QSignalSpy rowsRemovedSpy(&model, &QAbstractItemModel::rowsRemoved); QVERIFY(rowsRemovedSpy.isValid()); w->hide(); w.reset(); - QVERIFY(rowsRemovedSpy.wait()); - QCOMPARE(rowsRemovedSpy.count(), 1); + QTRY_COMPARE(rowsRemovedSpy.count(), 1); QCOMPARE(rowsRemovedSpy.first().first().value(), internalTopLevelIndex); } void DebugConsoleTest::testClosingDebugConsole() { // this test verifies that the DebugConsole gets destroyed when closing the window // BUG: 369858 DebugConsole *console = new DebugConsole; QSignalSpy destroyedSpy(console, &QObject::destroyed); QVERIFY(destroyedSpy.isValid()); - QSignalSpy clientAddedSpy(waylandServer(), &WaylandServer::shellClientAdded); + QSignalSpy clientAddedSpy(workspace(), &Workspace::internalClientAdded); QVERIFY(clientAddedSpy.isValid()); console->show(); QCOMPARE(console->windowHandle()->isVisible(), true); QTRY_COMPARE(clientAddedSpy.count(), 1); - ShellClient *c = clientAddedSpy.first().first().value(); + InternalClient *c = clientAddedSpy.first().first().value(); QVERIFY(c->isInternal()); QCOMPARE(c->internalWindow(), console->windowHandle()); QVERIFY(c->isDecorated()); QCOMPARE(c->isMinimizable(), false); c->closeWindow(); QVERIFY(destroyedSpy.wait()); } } WAYLANDTEST_MAIN(KWin::DebugConsoleTest) #include "debug_console_test.moc" diff --git a/autotests/integration/decoration_input_test.cpp b/autotests/integration/decoration_input_test.cpp index ccb4cbd99..106d25f71 100644 --- a/autotests/integration/decoration_input_test.cpp +++ b/autotests/integration/decoration_input_test.cpp @@ -1,936 +1,938 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2016 Martin Gräßlin 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 . *********************************************************************/ #include "kwin_wayland_test.h" -#include "platform.h" #include "abstract_client.h" #include "cursor.h" +#include "internal_client.h" +#include "platform.h" #include "pointer_input.h" #include "touch_input.h" #include "screenedge.h" #include "screens.h" +#include "shell_client.h" #include "wayland_server.h" #include "workspace.h" -#include "shell_client.h" #include #include "decorations/decoratedclient.h" #include "decorations/decorationbridge.h" #include "decorations/settings.h" #include #include #include #include #include #include #include #include #include #include #include #include Q_DECLARE_METATYPE(Qt::WindowFrameSection) namespace KWin { static const QString s_socketName = QStringLiteral("wayland_test_kwin_decoration_input-0"); class DecorationInputTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void init(); void cleanup(); void testAxis_data(); void testAxis(); void testDoubleClick_data(); void testDoubleClick(); void testDoubleTap_data(); void testDoubleTap(); void testHover_data(); void testHover(); void testPressToMove_data(); void testPressToMove(); void testTapToMove_data(); void testTapToMove(); void testResizeOutsideWindow_data(); void testResizeOutsideWindow(); void testModifierClickUnrestrictedMove_data(); void testModifierClickUnrestrictedMove(); void testModifierScrollOpacity_data(); void testModifierScrollOpacity(); void testTouchEvents_data(); void testTouchEvents(); void testTooltipDoesntEatKeyEvents_data(); void testTooltipDoesntEatKeyEvents(); private: AbstractClient *showWindow(Test::ShellSurfaceType type); }; #define MOTION(target) \ kwinApp()->platform()->pointerMotion(target, timestamp++) #define PRESS \ kwinApp()->platform()->pointerButtonPressed(BTN_LEFT, timestamp++) #define RELEASE \ kwinApp()->platform()->pointerButtonReleased(BTN_LEFT, timestamp++) AbstractClient *DecorationInputTest::showWindow(Test::ShellSurfaceType type) { using namespace KWayland::Client; #define VERIFY(statement) \ if (!QTest::qVerify((statement), #statement, "", __FILE__, __LINE__))\ return nullptr; #define COMPARE(actual, expected) \ if (!QTest::qCompare(actual, expected, #actual, #expected, __FILE__, __LINE__))\ return nullptr; Surface *surface = Test::createSurface(Test::waylandCompositor()); VERIFY(surface); auto shellSurface = Test::createShellSurface(type, surface, surface); VERIFY(shellSurface); auto deco = Test::waylandServerSideDecoration()->create(surface, surface); QSignalSpy decoSpy(deco, &ServerSideDecoration::modeChanged); VERIFY(decoSpy.isValid()); VERIFY(decoSpy.wait()); deco->requestMode(ServerSideDecoration::Mode::Server); VERIFY(decoSpy.wait()); COMPARE(deco->mode(), ServerSideDecoration::Mode::Server); // let's render auto c = Test::renderAndWaitForShown(surface, QSize(500, 50), Qt::blue); VERIFY(c); COMPARE(workspace()->activeClient(), c); #undef VERIFY #undef COMPARE return c; } void DecorationInputTest::initTestCase() { - qRegisterMetaType(); - qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType(); QSignalSpy workspaceCreatedSpy(kwinApp(), &Application::workspaceCreated); QVERIFY(workspaceCreatedSpy.isValid()); kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); QVERIFY(waylandServer()->init(s_socketName.toLocal8Bit())); QMetaObject::invokeMethod(kwinApp()->platform(), "setVirtualOutputs", Qt::DirectConnection, Q_ARG(int, 2)); // change some options KSharedConfig::Ptr config = KSharedConfig::openConfig(QString(), KConfig::SimpleConfig); config->group(QStringLiteral("MouseBindings")).writeEntry("CommandTitlebarWheel", QStringLiteral("above/below")); config->group(QStringLiteral("Windows")).writeEntry("TitlebarDoubleClickCommand", QStringLiteral("OnAllDesktops")); config->group(QStringLiteral("Desktops")).writeEntry("Number", 2); config->sync(); kwinApp()->setConfig(config); kwinApp()->start(); QVERIFY(workspaceCreatedSpy.wait()); QCOMPARE(screens()->count(), 2); QCOMPARE(screens()->geometry(0), QRect(0, 0, 1280, 1024)); QCOMPARE(screens()->geometry(1), QRect(1280, 0, 1280, 1024)); setenv("QT_QPA_PLATFORM", "wayland", true); waylandServer()->initWorkspace(); } void DecorationInputTest::init() { using namespace KWayland::Client; QVERIFY(Test::setupWaylandConnection(Test::AdditionalWaylandInterface::Seat | Test::AdditionalWaylandInterface::Decoration)); QVERIFY(Test::waitForWaylandPointer()); screens()->setCurrent(0); Cursor::setPos(QPoint(640, 512)); } void DecorationInputTest::cleanup() { Test::destroyWaylandConnection(); } void DecorationInputTest::testAxis_data() { QTest::addColumn("decoPoint"); QTest::addColumn("expectedSection"); QTest::addColumn("type"); QTest::newRow("topLeft") << QPoint(0, 0) << Qt::TopLeftSection << Test::ShellSurfaceType::WlShell; QTest::newRow("top") << QPoint(250, 0) << Qt::TopSection << Test::ShellSurfaceType::WlShell; QTest::newRow("topRight") << QPoint(499, 0) << Qt::TopRightSection << Test::ShellSurfaceType::WlShell; QTest::newRow("topLeft|xdgv5") << QPoint(0, 0) << Qt::TopLeftSection << Test::ShellSurfaceType::XdgShellV5; QTest::newRow("top|xdgv5") << QPoint(250, 0) << Qt::TopSection << Test::ShellSurfaceType::XdgShellV5; QTest::newRow("topRight|xdgv5") << QPoint(499, 0) << Qt::TopRightSection << Test::ShellSurfaceType::XdgShellV5; QTest::newRow("topLeft|xdgv6") << QPoint(0, 0) << Qt::TopLeftSection << Test::ShellSurfaceType::XdgShellV6; QTest::newRow("top|xdgv6") << QPoint(250, 0) << Qt::TopSection << Test::ShellSurfaceType::XdgShellV6; QTest::newRow("topRight|xdgv6") << QPoint(499, 0) << Qt::TopRightSection << Test::ShellSurfaceType::XdgShellV6; QTest::newRow("topLeft|xdgWmBase") << QPoint(0, 0) << Qt::TopLeftSection << Test::ShellSurfaceType::XdgShellStable; QTest::newRow("top|xdgWmBase") << QPoint(250, 0) << Qt::TopSection << Test::ShellSurfaceType::XdgShellStable; QTest::newRow("topRight|xdgWmBase") << QPoint(499, 0) << Qt::TopRightSection << Test::ShellSurfaceType::XdgShellStable; } void DecorationInputTest::testAxis() { QFETCH(Test::ShellSurfaceType, type); AbstractClient *c = showWindow(type); QVERIFY(c); QVERIFY(c->isDecorated()); QVERIFY(!c->noBorder()); QCOMPARE(c->titlebarPosition(), AbstractClient::PositionTop); QVERIFY(!c->keepAbove()); QVERIFY(!c->keepBelow()); quint32 timestamp = 1; MOTION(QPoint(c->geometry().center().x(), c->clientPos().y() / 2)); QVERIFY(!input()->pointer()->decoration().isNull()); QCOMPARE(input()->pointer()->decoration()->decoration()->sectionUnderMouse(), Qt::TitleBarArea); // TODO: mouse wheel direction looks wrong to me // simulate wheel kwinApp()->platform()->pointerAxisVertical(5.0, timestamp++); QVERIFY(c->keepBelow()); QVERIFY(!c->keepAbove()); kwinApp()->platform()->pointerAxisVertical(-5.0, timestamp++); QVERIFY(!c->keepBelow()); QVERIFY(!c->keepAbove()); kwinApp()->platform()->pointerAxisVertical(-5.0, timestamp++); QVERIFY(!c->keepBelow()); QVERIFY(c->keepAbove()); // test top most deco pixel, BUG: 362860 c->move(0, 0); QFETCH(QPoint, decoPoint); MOTION(decoPoint); QVERIFY(!input()->pointer()->decoration().isNull()); QCOMPARE(input()->pointer()->decoration()->client(), c); QTEST(input()->pointer()->decoration()->decoration()->sectionUnderMouse(), "expectedSection"); kwinApp()->platform()->pointerAxisVertical(5.0, timestamp++); QVERIFY(!c->keepBelow()); QVERIFY(!c->keepAbove()); } void DecorationInputTest::testDoubleClick_data() { QTest::addColumn("decoPoint"); QTest::addColumn("expectedSection"); QTest::addColumn("type"); QTest::newRow("topLeft") << QPoint(0, 0) << Qt::TopLeftSection << Test::ShellSurfaceType::WlShell; QTest::newRow("top") << QPoint(250, 0) << Qt::TopSection << Test::ShellSurfaceType::WlShell; QTest::newRow("topRight") << QPoint(499, 0) << Qt::TopRightSection << Test::ShellSurfaceType::WlShell; QTest::newRow("topLeft|xdgv5") << QPoint(0, 0) << Qt::TopLeftSection << Test::ShellSurfaceType::XdgShellV5; QTest::newRow("top|xdgv5") << QPoint(250, 0) << Qt::TopSection << Test::ShellSurfaceType::XdgShellV5; QTest::newRow("topRight|xdgv5") << QPoint(499, 0) << Qt::TopRightSection << Test::ShellSurfaceType::XdgShellV5; QTest::newRow("topLeft|xdgv6") << QPoint(0, 0) << Qt::TopLeftSection << Test::ShellSurfaceType::XdgShellV6; QTest::newRow("top|xdgv6") << QPoint(250, 0) << Qt::TopSection << Test::ShellSurfaceType::XdgShellV6; QTest::newRow("topRight|xdgv6") << QPoint(499, 0) << Qt::TopRightSection << Test::ShellSurfaceType::XdgShellV6; QTest::newRow("topLeft|xdgWmBase") << QPoint(0, 0) << Qt::TopLeftSection << Test::ShellSurfaceType::XdgShellStable; QTest::newRow("top|xdgWmBase") << QPoint(250, 0) << Qt::TopSection << Test::ShellSurfaceType::XdgShellStable; QTest::newRow("topRight|xdgWmBase") << QPoint(499, 0) << Qt::TopRightSection << Test::ShellSurfaceType::XdgShellStable; } void KWin::DecorationInputTest::testDoubleClick() { QFETCH(Test::ShellSurfaceType, type); AbstractClient *c = showWindow(type); QVERIFY(c); QVERIFY(c->isDecorated()); QVERIFY(!c->noBorder()); QVERIFY(!c->isOnAllDesktops()); quint32 timestamp = 1; MOTION(QPoint(c->geometry().center().x(), c->clientPos().y() / 2)); // double click PRESS; RELEASE; PRESS; RELEASE; QVERIFY(c->isOnAllDesktops()); // double click again PRESS; RELEASE; QVERIFY(c->isOnAllDesktops()); PRESS; RELEASE; QVERIFY(!c->isOnAllDesktops()); // test top most deco pixel, BUG: 362860 c->move(0, 0); QFETCH(QPoint, decoPoint); MOTION(decoPoint); QVERIFY(!input()->pointer()->decoration().isNull()); QCOMPARE(input()->pointer()->decoration()->client(), c); QTEST(input()->pointer()->decoration()->decoration()->sectionUnderMouse(), "expectedSection"); // double click PRESS; RELEASE; QVERIFY(!c->isOnAllDesktops()); PRESS; RELEASE; QVERIFY(c->isOnAllDesktops()); } void DecorationInputTest::testDoubleTap_data() { QTest::addColumn("decoPoint"); QTest::addColumn("expectedSection"); QTest::addColumn("type"); QTest::newRow("topLeft") << QPoint(10, 10) << Qt::TopLeftSection << Test::ShellSurfaceType::WlShell; QTest::newRow("top") << QPoint(260, 10) << Qt::TopSection << Test::ShellSurfaceType::WlShell; QTest::newRow("topRight") << QPoint(509, 10) << Qt::TopRightSection << Test::ShellSurfaceType::WlShell; QTest::newRow("topLeft|xdgv5") << QPoint(10, 10) << Qt::TopLeftSection << Test::ShellSurfaceType::XdgShellV5; QTest::newRow("top|xdgv5") << QPoint(260, 10) << Qt::TopSection << Test::ShellSurfaceType::XdgShellV5; QTest::newRow("topRight|xdgv5") << QPoint(509, 10) << Qt::TopRightSection << Test::ShellSurfaceType::XdgShellV5; QTest::newRow("topLeft|xdgv6") << QPoint(10, 10) << Qt::TopLeftSection << Test::ShellSurfaceType::XdgShellV6; QTest::newRow("top|xdgv6") << QPoint(260, 10) << Qt::TopSection << Test::ShellSurfaceType::XdgShellV6; QTest::newRow("topRight|xdgv6") << QPoint(509, 10) << Qt::TopRightSection << Test::ShellSurfaceType::XdgShellV6; QTest::newRow("topLeft|xdgWmBase") << QPoint(10, 10) << Qt::TopLeftSection << Test::ShellSurfaceType::XdgShellStable; QTest::newRow("top|xdgWmBase") << QPoint(260, 10) << Qt::TopSection << Test::ShellSurfaceType::XdgShellStable; QTest::newRow("topRight|xdgWmBase") << QPoint(509, 10) << Qt::TopRightSection << Test::ShellSurfaceType::XdgShellStable; } void KWin::DecorationInputTest::testDoubleTap() { QFETCH(Test::ShellSurfaceType, type); AbstractClient *c = showWindow(type); QVERIFY(c); QVERIFY(c->isDecorated()); QVERIFY(!c->noBorder()); QVERIFY(!c->isOnAllDesktops()); quint32 timestamp = 1; const QPoint tapPoint(c->geometry().center().x(), c->clientPos().y() / 2); // double tap kwinApp()->platform()->touchDown(0, tapPoint, timestamp++); kwinApp()->platform()->touchUp(0, timestamp++); kwinApp()->platform()->touchDown(0, tapPoint, timestamp++); kwinApp()->platform()->touchUp(0, timestamp++); QVERIFY(c->isOnAllDesktops()); // double tap again kwinApp()->platform()->touchDown(0, tapPoint, timestamp++); kwinApp()->platform()->touchUp(0, timestamp++); QVERIFY(c->isOnAllDesktops()); kwinApp()->platform()->touchDown(0, tapPoint, timestamp++); kwinApp()->platform()->touchUp(0, timestamp++); QVERIFY(!c->isOnAllDesktops()); // test top most deco pixel, BUG: 362860 // // Not directly at (0, 0), otherwise ScreenEdgeInputFilter catches // event before DecorationEventFilter. c->move(10, 10); QFETCH(QPoint, decoPoint); // double click kwinApp()->platform()->touchDown(0, decoPoint, timestamp++); QVERIFY(!input()->touch()->decoration().isNull()); QCOMPARE(input()->touch()->decoration()->client(), c); QTEST(input()->touch()->decoration()->decoration()->sectionUnderMouse(), "expectedSection"); kwinApp()->platform()->touchUp(0, timestamp++); QVERIFY(!c->isOnAllDesktops()); kwinApp()->platform()->touchDown(0, decoPoint, timestamp++); kwinApp()->platform()->touchUp(0, timestamp++); QVERIFY(c->isOnAllDesktops()); } void DecorationInputTest::testHover_data() { QTest::addColumn("type"); QTest::newRow("wlShell") << Test::ShellSurfaceType::WlShell; QTest::newRow("xdgShellV5") << Test::ShellSurfaceType::XdgShellV5; QTest::newRow("xdgShellV6") << Test::ShellSurfaceType::XdgShellV6; QTest::newRow("xdgWmBase") << Test::ShellSurfaceType::XdgShellStable; } void DecorationInputTest::testHover() { QFETCH(Test::ShellSurfaceType, type); AbstractClient *c = showWindow(type); QVERIFY(c); QVERIFY(c->isDecorated()); QVERIFY(!c->noBorder()); // our left border is moved out of the visible area, so move the window to a better place c->move(QPoint(20, 0)); quint32 timestamp = 1; MOTION(QPoint(c->geometry().center().x(), c->clientPos().y() / 2)); QCOMPARE(c->cursor(), CursorShape(Qt::ArrowCursor)); // There is a mismatch of the cursor key positions between windows // with and without borders (with borders one can move inside a bit and still // be on an edge, without not). We should make this consistent in KWin's core. // // TODO: Test input position with different border sizes. // TODO: We should test with the fake decoration to have a fixed test environment. const bool hasBorders = Decoration::DecorationBridge::self()->settings()->borderSize() != KDecoration2::BorderSize::None; auto deviation = [hasBorders] { return hasBorders ? -1 : 0; }; MOTION(QPoint(c->geometry().x(), 0)); QCOMPARE(c->cursor(), CursorShape(KWin::ExtendedCursor::SizeNorthWest)); MOTION(QPoint(c->geometry().x() + c->geometry().width() / 2, 0)); QCOMPARE(c->cursor(), CursorShape(KWin::ExtendedCursor::SizeNorth)); MOTION(QPoint(c->geometry().x() + c->geometry().width() - 1, 0)); QCOMPARE(c->cursor(), CursorShape(KWin::ExtendedCursor::SizeNorthEast)); MOTION(QPoint(c->geometry().x() + c->geometry().width() + deviation(), c->height() / 2)); QCOMPARE(c->cursor(), CursorShape(KWin::ExtendedCursor::SizeEast)); MOTION(QPoint(c->geometry().x() + c->geometry().width() + deviation(), c->height() - 1)); QCOMPARE(c->cursor(), CursorShape(KWin::ExtendedCursor::SizeSouthEast)); MOTION(QPoint(c->geometry().x() + c->geometry().width() / 2, c->height() + deviation())); QCOMPARE(c->cursor(), CursorShape(KWin::ExtendedCursor::SizeSouth)); MOTION(QPoint(c->geometry().x(), c->height() + deviation())); QCOMPARE(c->cursor(), CursorShape(KWin::ExtendedCursor::SizeSouthWest)); MOTION(QPoint(c->geometry().x() - 1, c->height() / 2)); QCOMPARE(c->cursor(), CursorShape(KWin::ExtendedCursor::SizeWest)); MOTION(c->geometry().center()); QEXPECT_FAIL("", "Cursor not set back on leave", Continue); QCOMPARE(c->cursor(), CursorShape(Qt::ArrowCursor)); } void DecorationInputTest::testPressToMove_data() { QTest::addColumn("offset"); QTest::addColumn("offset2"); QTest::addColumn("offset3"); QTest::addColumn("type"); QTest::newRow("To right") << QPoint(10, 0) << QPoint(20, 0) << QPoint(30, 0) << Test::ShellSurfaceType::WlShell; QTest::newRow("To left") << QPoint(-10, 0) << QPoint(-20, 0) << QPoint(-30, 0) << Test::ShellSurfaceType::WlShell; QTest::newRow("To bottom") << QPoint(0, 10) << QPoint(0, 20) << QPoint(0, 30) << Test::ShellSurfaceType::WlShell; QTest::newRow("To top") << QPoint(0, -10) << QPoint(0, -20) << QPoint(0, -30) << Test::ShellSurfaceType::WlShell; QTest::newRow("To right|xdgv5") << QPoint(10, 0) << QPoint(20, 0) << QPoint(30, 0) << Test::ShellSurfaceType::XdgShellV5; QTest::newRow("To left|xdgv5") << QPoint(-10, 0) << QPoint(-20, 0) << QPoint(-30, 0) << Test::ShellSurfaceType::XdgShellV5; QTest::newRow("To bottom|xdgv5") << QPoint(0, 10) << QPoint(0, 20) << QPoint(0, 30) << Test::ShellSurfaceType::XdgShellV5; QTest::newRow("To top|xdgv5") << QPoint(0, -10) << QPoint(0, -20) << QPoint(0, -30) << Test::ShellSurfaceType::XdgShellV5; QTest::newRow("To right|xdgv6") << QPoint(10, 0) << QPoint(20, 0) << QPoint(30, 0) << Test::ShellSurfaceType::XdgShellV6; QTest::newRow("To left|xdgv6") << QPoint(-10, 0) << QPoint(-20, 0) << QPoint(-30, 0) << Test::ShellSurfaceType::XdgShellV6; QTest::newRow("To bottom|xdgv6") << QPoint(0, 10) << QPoint(0, 20) << QPoint(0, 30) << Test::ShellSurfaceType::XdgShellV6; QTest::newRow("To top|xdgv6") << QPoint(0, -10) << QPoint(0, -20) << QPoint(0, -30) << Test::ShellSurfaceType::XdgShellV6; QTest::newRow("To right|xdgWmBase") << QPoint(10, 0) << QPoint(20, 0) << QPoint(30, 0) << Test::ShellSurfaceType::XdgShellStable; QTest::newRow("To left|xdgWmBase") << QPoint(-10, 0) << QPoint(-20, 0) << QPoint(-30, 0) << Test::ShellSurfaceType::XdgShellStable; QTest::newRow("To bottom|xdgWmBase") << QPoint(0, 10) << QPoint(0, 20) << QPoint(0, 30) << Test::ShellSurfaceType::XdgShellStable; QTest::newRow("To top|xdgWmBase") << QPoint(0, -10) << QPoint(0, -20) << QPoint(0, -30) << Test::ShellSurfaceType::XdgShellStable; } void DecorationInputTest::testPressToMove() { QFETCH(Test::ShellSurfaceType, type); AbstractClient *c = showWindow(type); QVERIFY(c); QVERIFY(c->isDecorated()); QVERIFY(!c->noBorder()); c->move(screens()->geometry(0).center() - QPoint(c->width()/2, c->height()/2)); QSignalSpy startMoveResizedSpy(c, &AbstractClient::clientStartUserMovedResized); QVERIFY(startMoveResizedSpy.isValid()); QSignalSpy clientFinishUserMovedResizedSpy(c, &AbstractClient::clientFinishUserMovedResized); QVERIFY(clientFinishUserMovedResizedSpy.isValid()); quint32 timestamp = 1; MOTION(QPoint(c->geometry().center().x(), c->y() + c->clientPos().y() / 2)); QCOMPARE(c->cursor(), CursorShape(Qt::ArrowCursor)); PRESS; QVERIFY(!c->isMove()); QFETCH(QPoint, offset); MOTION(QPoint(c->geometry().center().x(), c->y() + c->clientPos().y() / 2) + offset); const QPoint oldPos = c->pos(); QVERIFY(c->isMove()); QCOMPARE(startMoveResizedSpy.count(), 1); RELEASE; QTRY_VERIFY(!c->isMove()); QCOMPARE(clientFinishUserMovedResizedSpy.count(), 1); QEXPECT_FAIL("", "Just trigger move doesn't move the window", Continue); QCOMPARE(c->pos(), oldPos + offset); // again PRESS; QVERIFY(!c->isMove()); QFETCH(QPoint, offset2); MOTION(QPoint(c->geometry().center().x(), c->y() + c->clientPos().y() / 2) + offset2); QVERIFY(c->isMove()); QCOMPARE(startMoveResizedSpy.count(), 2); QFETCH(QPoint, offset3); MOTION(QPoint(c->geometry().center().x(), c->y() + c->clientPos().y() / 2) + offset3); RELEASE; QTRY_VERIFY(!c->isMove()); QCOMPARE(clientFinishUserMovedResizedSpy.count(), 2); // TODO: the offset should also be included QCOMPARE(c->pos(), oldPos + offset2 + offset3); } void DecorationInputTest::testTapToMove_data() { QTest::addColumn("offset"); QTest::addColumn("offset2"); QTest::addColumn("offset3"); QTest::addColumn("type"); QTest::newRow("To right") << QPoint(10, 0) << QPoint(20, 0) << QPoint(30, 0) << Test::ShellSurfaceType::WlShell; QTest::newRow("To left") << QPoint(-10, 0) << QPoint(-20, 0) << QPoint(-30, 0) << Test::ShellSurfaceType::WlShell; QTest::newRow("To bottom") << QPoint(0, 10) << QPoint(0, 20) << QPoint(0, 30) << Test::ShellSurfaceType::WlShell; QTest::newRow("To top") << QPoint(0, -10) << QPoint(0, -20) << QPoint(0, -30) << Test::ShellSurfaceType::WlShell; QTest::newRow("To right|xdgv5") << QPoint(10, 0) << QPoint(20, 0) << QPoint(30, 0) << Test::ShellSurfaceType::XdgShellV5; QTest::newRow("To left|xdgv5") << QPoint(-10, 0) << QPoint(-20, 0) << QPoint(-30, 0) << Test::ShellSurfaceType::XdgShellV5; QTest::newRow("To bottom|xdgv5") << QPoint(0, 10) << QPoint(0, 20) << QPoint(0, 30) << Test::ShellSurfaceType::XdgShellV5; QTest::newRow("To top|xdgv5") << QPoint(0, -10) << QPoint(0, -20) << QPoint(0, -30) << Test::ShellSurfaceType::XdgShellV5; QTest::newRow("To right|xdgv6") << QPoint(10, 0) << QPoint(20, 0) << QPoint(30, 0) << Test::ShellSurfaceType::XdgShellV6; QTest::newRow("To left|xdgv6") << QPoint(-10, 0) << QPoint(-20, 0) << QPoint(-30, 0) << Test::ShellSurfaceType::XdgShellV6; QTest::newRow("To bottom|xdgv6") << QPoint(0, 10) << QPoint(0, 20) << QPoint(0, 30) << Test::ShellSurfaceType::XdgShellV6; QTest::newRow("To top|xdgv6") << QPoint(0, -10) << QPoint(0, -20) << QPoint(0, -30) << Test::ShellSurfaceType::XdgShellV6; QTest::newRow("To right|xdgWmBase") << QPoint(10, 0) << QPoint(20, 0) << QPoint(30, 0) << Test::ShellSurfaceType::XdgShellStable; QTest::newRow("To left|xdgWmBase") << QPoint(-10, 0) << QPoint(-20, 0) << QPoint(-30, 0) << Test::ShellSurfaceType::XdgShellStable; QTest::newRow("To bottom|xdgWmBase") << QPoint(0, 10) << QPoint(0, 20) << QPoint(0, 30) << Test::ShellSurfaceType::XdgShellStable; QTest::newRow("To top|xdgWmBase") << QPoint(0, -10) << QPoint(0, -20) << QPoint(0, -30) << Test::ShellSurfaceType::XdgShellStable; } void DecorationInputTest::testTapToMove() { QFETCH(Test::ShellSurfaceType, type); AbstractClient *c = showWindow(type); QVERIFY(c); QVERIFY(c->isDecorated()); QVERIFY(!c->noBorder()); c->move(screens()->geometry(0).center() - QPoint(c->width()/2, c->height()/2)); QSignalSpy startMoveResizedSpy(c, &AbstractClient::clientStartUserMovedResized); QVERIFY(startMoveResizedSpy.isValid()); QSignalSpy clientFinishUserMovedResizedSpy(c, &AbstractClient::clientFinishUserMovedResized); QVERIFY(clientFinishUserMovedResizedSpy.isValid()); quint32 timestamp = 1; QPoint p = QPoint(c->geometry().center().x(), c->y() + c->clientPos().y() / 2); kwinApp()->platform()->touchDown(0, p, timestamp++); QVERIFY(!c->isMove()); QFETCH(QPoint, offset); QCOMPARE(input()->touch()->decorationPressId(), 0); kwinApp()->platform()->touchMotion(0, p + offset, timestamp++); const QPoint oldPos = c->pos(); QVERIFY(c->isMove()); QCOMPARE(startMoveResizedSpy.count(), 1); kwinApp()->platform()->touchUp(0, timestamp++); QTRY_VERIFY(!c->isMove()); QCOMPARE(clientFinishUserMovedResizedSpy.count(), 1); QEXPECT_FAIL("", "Just trigger move doesn't move the window", Continue); QCOMPARE(c->pos(), oldPos + offset); // again kwinApp()->platform()->touchDown(1, p + offset, timestamp++); QCOMPARE(input()->touch()->decorationPressId(), 1); QVERIFY(!c->isMove()); QFETCH(QPoint, offset2); kwinApp()->platform()->touchMotion(1, QPoint(c->geometry().center().x(), c->y() + c->clientPos().y() / 2) + offset2, timestamp++); QVERIFY(c->isMove()); QCOMPARE(startMoveResizedSpy.count(), 2); QFETCH(QPoint, offset3); kwinApp()->platform()->touchMotion(1, QPoint(c->geometry().center().x(), c->y() + c->clientPos().y() / 2) + offset3, timestamp++); kwinApp()->platform()->touchUp(1, timestamp++); QTRY_VERIFY(!c->isMove()); QCOMPARE(clientFinishUserMovedResizedSpy.count(), 2); // TODO: the offset should also be included QCOMPARE(c->pos(), oldPos + offset2 + offset3); } void DecorationInputTest::testResizeOutsideWindow_data() { QTest::addColumn("type"); QTest::addColumn("edge"); QTest::addColumn("expectedCursor"); QTest::newRow("wlShell - left") << Test::ShellSurfaceType::WlShell << Qt::LeftEdge << Qt::SizeHorCursor; QTest::newRow("xdgShellV5 - left") << Test::ShellSurfaceType::XdgShellV5 << Qt::LeftEdge << Qt::SizeHorCursor; QTest::newRow("xdgShellV6 - left") << Test::ShellSurfaceType::XdgShellV6 << Qt::LeftEdge << Qt::SizeHorCursor; QTest::newRow("xdgWmBase - left") << Test::ShellSurfaceType::XdgShellStable << Qt::LeftEdge << Qt::SizeHorCursor; QTest::newRow("wlShell - right") << Test::ShellSurfaceType::WlShell << Qt::RightEdge << Qt::SizeHorCursor; QTest::newRow("xdgShellV5 - right") << Test::ShellSurfaceType::XdgShellV5 << Qt::RightEdge << Qt::SizeHorCursor; QTest::newRow("xdgShellV6 - right") << Test::ShellSurfaceType::XdgShellV6 << Qt::RightEdge << Qt::SizeHorCursor; QTest::newRow("xdgWmBase - right") << Test::ShellSurfaceType::XdgShellStable << Qt::RightEdge << Qt::SizeHorCursor; QTest::newRow("wlShell - bottom") << Test::ShellSurfaceType::WlShell << Qt::BottomEdge << Qt::SizeVerCursor; QTest::newRow("xdgShellV5 - bottom") << Test::ShellSurfaceType::XdgShellV5 << Qt::BottomEdge << Qt::SizeVerCursor; QTest::newRow("xdgShellV6 - bottom") << Test::ShellSurfaceType::XdgShellV6 << Qt::BottomEdge << Qt::SizeVerCursor; QTest::newRow("xdgWmBase - bottom") << Test::ShellSurfaceType::XdgShellStable << Qt::BottomEdge << Qt::SizeVerCursor; } void DecorationInputTest::testResizeOutsideWindow() { // this test verifies that one can resize the window outside the decoration with NoSideBorder // first adjust config kwinApp()->config()->group("org.kde.kdecoration2").writeEntry("BorderSize", QStringLiteral("None")); kwinApp()->config()->sync(); workspace()->slotReconfigure(); // now create window QFETCH(Test::ShellSurfaceType, type); AbstractClient *c = showWindow(type); QVERIFY(c); QVERIFY(c->isDecorated()); QVERIFY(!c->noBorder()); c->move(screens()->geometry(0).center() - QPoint(c->width()/2, c->height()/2)); QVERIFY(c->geometry() != c->inputGeometry()); QVERIFY(c->inputGeometry().contains(c->geometry())); QSignalSpy startMoveResizedSpy(c, &AbstractClient::clientStartUserMovedResized); QVERIFY(startMoveResizedSpy.isValid()); // go to border quint32 timestamp = 1; QFETCH(Qt::Edge, edge); switch (edge) { case Qt::LeftEdge: MOTION(QPoint(c->geometry().x() -1, c->geometry().center().y())); break; case Qt::RightEdge: MOTION(QPoint(c->geometry().x() + c->geometry().width() +1, c->geometry().center().y())); break; case Qt::BottomEdge: MOTION(QPoint(c->geometry().center().x(), c->geometry().y() + c->geometry().height() + 1)); break; default: break; } QVERIFY(!c->geometry().contains(KWin::Cursor::pos())); // pressing should trigger resize PRESS; QVERIFY(!c->isResize()); QVERIFY(startMoveResizedSpy.wait()); QVERIFY(c->isResize()); RELEASE; QVERIFY(!c->isResize()); } void DecorationInputTest::testModifierClickUnrestrictedMove_data() { QTest::addColumn("modifierKey"); QTest::addColumn("mouseButton"); QTest::addColumn("modKey"); QTest::addColumn("capsLock"); QTest::addColumn("surfaceType"); const QString alt = QStringLiteral("Alt"); const QString meta = QStringLiteral("Meta"); const QVector> surfaceTypes{ {Test::ShellSurfaceType::WlShell, QByteArrayLiteral("WlShell")}, {Test::ShellSurfaceType::XdgShellV5, QByteArrayLiteral("XdgShellV5")}, {Test::ShellSurfaceType::XdgShellV6, QByteArrayLiteral("XdgShellV6")}, {Test::ShellSurfaceType::XdgShellStable, QByteArrayLiteral("XdgWmBase")}, }; for (const auto &type: surfaceTypes) { QTest::newRow("Left Alt + Left Click" + type.second) << KEY_LEFTALT << BTN_LEFT << alt << false << type.first; QTest::newRow("Left Alt + Right Click" + type.second) << KEY_LEFTALT << BTN_RIGHT << alt << false << type.first; QTest::newRow("Left Alt + Middle Click" + type.second) << KEY_LEFTALT << BTN_MIDDLE << alt << false << type.first; QTest::newRow("Right Alt + Left Click" + type.second) << KEY_RIGHTALT << BTN_LEFT << alt << false << type.first; QTest::newRow("Right Alt + Right Click" + type.second) << KEY_RIGHTALT << BTN_RIGHT << alt << false << type.first; QTest::newRow("Right Alt + Middle Click" + type.second) << KEY_RIGHTALT << BTN_MIDDLE << alt << false << type.first; // now everything with meta QTest::newRow("Left Meta + Left Click" + type.second) << KEY_LEFTMETA << BTN_LEFT << meta << false << type.first; QTest::newRow("Left Meta + Right Click" + type.second) << KEY_LEFTMETA << BTN_RIGHT << meta << false << type.first; QTest::newRow("Left Meta + Middle Click" + type.second) << KEY_LEFTMETA << BTN_MIDDLE << meta << false << type.first; QTest::newRow("Right Meta + Left Click" + type.second) << KEY_RIGHTMETA << BTN_LEFT << meta << false << type.first; QTest::newRow("Right Meta + Right Click" + type.second) << KEY_RIGHTMETA << BTN_RIGHT << meta << false << type.first; QTest::newRow("Right Meta + Middle Click" + type.second) << KEY_RIGHTMETA << BTN_MIDDLE << meta << false << type.first; // and with capslock QTest::newRow("Left Alt + Left Click/CapsLock" + type.second) << KEY_LEFTALT << BTN_LEFT << alt << true << type.first; QTest::newRow("Left Alt + Right Click/CapsLock" + type.second) << KEY_LEFTALT << BTN_RIGHT << alt << true << type.first; QTest::newRow("Left Alt + Middle Click/CapsLock" + type.second) << KEY_LEFTALT << BTN_MIDDLE << alt << true << type.first; QTest::newRow("Right Alt + Left Click/CapsLock" + type.second) << KEY_RIGHTALT << BTN_LEFT << alt << true << type.first; QTest::newRow("Right Alt + Right Click/CapsLock" + type.second) << KEY_RIGHTALT << BTN_RIGHT << alt << true << type.first; QTest::newRow("Right Alt + Middle Click/CapsLock" + type.second) << KEY_RIGHTALT << BTN_MIDDLE << alt << true << type.first; // now everything with meta QTest::newRow("Left Meta + Left Click/CapsLock" + type.second) << KEY_LEFTMETA << BTN_LEFT << meta << true << type.first; QTest::newRow("Left Meta + Right Click/CapsLock" + type.second) << KEY_LEFTMETA << BTN_RIGHT << meta << true << type.first; QTest::newRow("Left Meta + Middle Click/CapsLock" + type.second) << KEY_LEFTMETA << BTN_MIDDLE << meta << true << type.first; QTest::newRow("Right Meta + Left Click/CapsLock" + type.second) << KEY_RIGHTMETA << BTN_LEFT << meta << true << type.first; QTest::newRow("Right Meta + Right Click/CapsLock" + type.second) << KEY_RIGHTMETA << BTN_RIGHT << meta << true << type.first; QTest::newRow("Right Meta + Middle Click/CapsLock" + type.second) << KEY_RIGHTMETA << BTN_MIDDLE << meta << true << type.first; } } void DecorationInputTest::testModifierClickUnrestrictedMove() { // this test ensures that Alt+mouse button press triggers unrestricted move // first modify the config for this run QFETCH(QString, modKey); KConfigGroup group = kwinApp()->config()->group("MouseBindings"); group.writeEntry("CommandAllKey", modKey); group.writeEntry("CommandAll1", "Move"); group.writeEntry("CommandAll2", "Move"); group.writeEntry("CommandAll3", "Move"); group.sync(); workspace()->slotReconfigure(); QCOMPARE(options->commandAllModifier(), modKey == QStringLiteral("Alt") ? Qt::AltModifier : Qt::MetaModifier); QCOMPARE(options->commandAll1(), Options::MouseUnrestrictedMove); QCOMPARE(options->commandAll2(), Options::MouseUnrestrictedMove); QCOMPARE(options->commandAll3(), Options::MouseUnrestrictedMove); // create a window QFETCH(Test::ShellSurfaceType, surfaceType); AbstractClient *c = showWindow(surfaceType); QVERIFY(c); QVERIFY(c->isDecorated()); QVERIFY(!c->noBorder()); c->move(screens()->geometry(0).center() - QPoint(c->width()/2, c->height()/2)); // move cursor on window Cursor::setPos(QPoint(c->geometry().center().x(), c->y() + c->clientPos().y() / 2)); // simulate modifier+click quint32 timestamp = 1; QFETCH(bool, capsLock); if (capsLock) { kwinApp()->platform()->keyboardKeyPressed(KEY_CAPSLOCK, timestamp++); } QFETCH(int, modifierKey); QFETCH(int, mouseButton); kwinApp()->platform()->keyboardKeyPressed(modifierKey, timestamp++); QVERIFY(!c->isMove()); kwinApp()->platform()->pointerButtonPressed(mouseButton, timestamp++); QVERIFY(c->isMove()); // release modifier should not change it kwinApp()->platform()->keyboardKeyReleased(modifierKey, timestamp++); QVERIFY(c->isMove()); // but releasing the key should end move/resize kwinApp()->platform()->pointerButtonReleased(mouseButton, timestamp++); QVERIFY(!c->isMove()); if (capsLock) { kwinApp()->platform()->keyboardKeyReleased(KEY_CAPSLOCK, timestamp++); } } void DecorationInputTest::testModifierScrollOpacity_data() { QTest::addColumn("modifierKey"); QTest::addColumn("modKey"); QTest::addColumn("capsLock"); QTest::addColumn("surfaceType"); const QString alt = QStringLiteral("Alt"); const QString meta = QStringLiteral("Meta"); const QVector> surfaceTypes{ {Test::ShellSurfaceType::WlShell, QByteArrayLiteral("WlShell")}, {Test::ShellSurfaceType::XdgShellV5, QByteArrayLiteral("XdgShellV5")}, {Test::ShellSurfaceType::XdgShellV6, QByteArrayLiteral("XdgShellV6")}, {Test::ShellSurfaceType::XdgShellStable, QByteArrayLiteral("XdgWmBase")}, }; for (const auto &type: surfaceTypes) { QTest::newRow("Left Alt" + type.second) << KEY_LEFTALT << alt << false << type.first; QTest::newRow("Right Alt" + type.second) << KEY_RIGHTALT << alt << false << type.first; QTest::newRow("Left Meta" + type.second) << KEY_LEFTMETA << meta << false << type.first; QTest::newRow("Right Meta" + type.second) << KEY_RIGHTMETA << meta << false << type.first; QTest::newRow("Left Alt/CapsLock" + type.second) << KEY_LEFTALT << alt << true << type.first; QTest::newRow("Right Alt/CapsLock" + type.second) << KEY_RIGHTALT << alt << true << type.first; QTest::newRow("Left Meta/CapsLock" + type.second) << KEY_LEFTMETA << meta << true << type.first; QTest::newRow("Right Meta/CapsLock" + type.second) << KEY_RIGHTMETA << meta << true << type.first; } } void DecorationInputTest::testModifierScrollOpacity() { // this test verifies that mod+wheel performs a window operation // first modify the config for this run QFETCH(QString, modKey); KConfigGroup group = kwinApp()->config()->group("MouseBindings"); group.writeEntry("CommandAllKey", modKey); group.writeEntry("CommandAllWheel", "change opacity"); group.sync(); workspace()->slotReconfigure(); QFETCH(Test::ShellSurfaceType, surfaceType); AbstractClient *c = showWindow(surfaceType); QVERIFY(c); QVERIFY(c->isDecorated()); QVERIFY(!c->noBorder()); c->move(screens()->geometry(0).center() - QPoint(c->width()/2, c->height()/2)); // move cursor on window Cursor::setPos(QPoint(c->geometry().center().x(), c->y() + c->clientPos().y() / 2)); // set the opacity to 0.5 c->setOpacity(0.5); QCOMPARE(c->opacity(), 0.5); // simulate modifier+wheel quint32 timestamp = 1; QFETCH(bool, capsLock); if (capsLock) { kwinApp()->platform()->keyboardKeyPressed(KEY_CAPSLOCK, timestamp++); } QFETCH(int, modifierKey); kwinApp()->platform()->keyboardKeyPressed(modifierKey, timestamp++); kwinApp()->platform()->pointerAxisVertical(-5, timestamp++); QCOMPARE(c->opacity(), 0.6); kwinApp()->platform()->pointerAxisVertical(5, timestamp++); QCOMPARE(c->opacity(), 0.5); kwinApp()->platform()->keyboardKeyReleased(modifierKey, timestamp++); if (capsLock) { kwinApp()->platform()->keyboardKeyReleased(KEY_CAPSLOCK, timestamp++); } } void DecorationInputTest::testTouchEvents_data() { QTest::addColumn("type"); QTest::newRow("wlShell") << Test::ShellSurfaceType::WlShell; QTest::newRow("xdgShellV5") << Test::ShellSurfaceType::XdgShellV5; QTest::newRow("xdgShellV6") << Test::ShellSurfaceType::XdgShellV6; QTest::newRow("xdgWmBase") << Test::ShellSurfaceType::XdgShellStable; } class EventHelper : public QObject { Q_OBJECT public: EventHelper() : QObject() {} ~EventHelper() override = default; bool eventFilter(QObject *watched, QEvent *event) override { Q_UNUSED(watched) if (event->type() == QEvent::HoverMove) { emit hoverMove(); } else if (event->type() == QEvent::HoverLeave) { emit hoverLeave(); } return false; } Q_SIGNALS: void hoverMove(); void hoverLeave(); }; void DecorationInputTest::testTouchEvents() { // this test verifies that the decoration gets a hover leave event on touch release // see BUG 386231 QFETCH(Test::ShellSurfaceType, type); AbstractClient *c = showWindow(type); QVERIFY(c); QVERIFY(c->isDecorated()); QVERIFY(!c->noBorder()); EventHelper helper; c->decoration()->installEventFilter(&helper); QSignalSpy hoverMoveSpy(&helper, &EventHelper::hoverMove); QVERIFY(hoverMoveSpy.isValid()); QSignalSpy hoverLeaveSpy(&helper, &EventHelper::hoverLeave); QVERIFY(hoverLeaveSpy.isValid()); quint32 timestamp = 1; const QPoint tapPoint(c->geometry().center().x(), c->clientPos().y() / 2); QVERIFY(!input()->touch()->decoration()); kwinApp()->platform()->touchDown(0, tapPoint, timestamp++); QVERIFY(input()->touch()->decoration()); QCOMPARE(input()->touch()->decoration()->decoration(), c->decoration()); QCOMPARE(hoverMoveSpy.count(), 1); QCOMPARE(hoverLeaveSpy.count(), 0); kwinApp()->platform()->touchUp(0, timestamp++); QCOMPARE(hoverMoveSpy.count(), 1); QCOMPARE(hoverLeaveSpy.count(), 1); QCOMPARE(c->isMove(), false); // let's check that a hover motion is sent if the pointer is on deco, when touch release Cursor::setPos(tapPoint); QCOMPARE(hoverMoveSpy.count(), 2); kwinApp()->platform()->touchDown(0, tapPoint, timestamp++); QCOMPARE(hoverMoveSpy.count(), 3); QCOMPARE(hoverLeaveSpy.count(), 1); kwinApp()->platform()->touchUp(0, timestamp++); QCOMPARE(hoverMoveSpy.count(), 3); QCOMPARE(hoverLeaveSpy.count(), 2); } void DecorationInputTest::testTooltipDoesntEatKeyEvents_data() { QTest::addColumn("type"); QTest::newRow("wlShell") << Test::ShellSurfaceType::WlShell; QTest::newRow("xdgShellV6") << Test::ShellSurfaceType::XdgShellV6; QTest::newRow("xdgWmBase") << Test::ShellSurfaceType::XdgShellStable; } void DecorationInputTest::testTooltipDoesntEatKeyEvents() { // this test verifies that a tooltip on the decoration does not steal key events // BUG: 393253 // first create a keyboard auto keyboard = Test::waylandSeat()->createKeyboard(Test::waylandSeat()); QVERIFY(keyboard); QSignalSpy enteredSpy(keyboard, &KWayland::Client::Keyboard::entered); QVERIFY(enteredSpy.isValid()); QFETCH(Test::ShellSurfaceType, type); AbstractClient *c = showWindow(type); QVERIFY(c); QVERIFY(c->isDecorated()); QVERIFY(!c->noBorder()); QTRY_COMPARE(enteredSpy.count(), 1); QSignalSpy keyEvent(keyboard, &KWayland::Client::Keyboard::keyChanged); QVERIFY(keyEvent.isValid()); - QSignalSpy clientAddedSpy(waylandServer(), &WaylandServer::shellClientAdded); + QSignalSpy clientAddedSpy(workspace(), &Workspace::internalClientAdded); QVERIFY(clientAddedSpy.isValid()); c->decoratedClient()->requestShowToolTip(QStringLiteral("test")); // now we should get an internal window QVERIFY(clientAddedSpy.wait()); - ShellClient *internal = clientAddedSpy.first().first().value(); + InternalClient *internal = clientAddedSpy.first().first().value(); QVERIFY(internal->isInternal()); QVERIFY(internal->internalWindow()->flags().testFlag(Qt::ToolTip)); // now send a key quint32 timestamp = 0; kwinApp()->platform()->keyboardKeyPressed(KEY_A, timestamp++); QVERIFY(keyEvent.wait()); kwinApp()->platform()->keyboardKeyReleased(KEY_A, timestamp++); QVERIFY(keyEvent.wait()); c->decoratedClient()->requestHideToolTip(); Test::waitForWindowDestroyed(internal); } } WAYLANDTEST_MAIN(KWin::DecorationInputTest) #include "decoration_input_test.moc" diff --git a/autotests/integration/effects/popup_open_close_animation_test.cpp b/autotests/integration/effects/popup_open_close_animation_test.cpp index 447e1d156..9a29871f9 100644 --- a/autotests/integration/effects/popup_open_close_animation_test.cpp +++ b/autotests/integration/effects/popup_open_close_animation_test.cpp @@ -1,280 +1,282 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2019 Vlad Zagorodniy 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 . *********************************************************************/ #include "kwin_wayland_test.h" #include "abstract_client.h" #include "deleted.h" #include "effectloader.h" #include "effects.h" +#include "internal_client.h" #include "platform.h" #include "shell_client.h" #include "useractions.h" #include "wayland_server.h" #include "workspace.h" #include "decorations/decoratedclient.h" #include "effect_builtins.h" #include #include #include #include using namespace KWin; static const QString s_socketName = QStringLiteral("wayland_test_effects_popup_open_close_animation-0"); class PopupOpenCloseAnimationTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void init(); void cleanup(); void testAnimatePopups(); void testAnimateUserActionsPopup(); void testAnimateDecorationTooltips(); }; void PopupOpenCloseAnimationTest::initTestCase() { qputenv("XDG_DATA_DIRS", QCoreApplication::applicationDirPath().toUtf8()); qRegisterMetaType(); qRegisterMetaType(); + qRegisterMetaType(); qRegisterMetaType(); QSignalSpy workspaceCreatedSpy(kwinApp(), &Application::workspaceCreated); QVERIFY(workspaceCreatedSpy.isValid()); kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); QVERIFY(waylandServer()->init(s_socketName.toLocal8Bit())); auto config = KSharedConfig::openConfig(QString(), KConfig::SimpleConfig); KConfigGroup plugins(config, QStringLiteral("Plugins")); ScriptedEffectLoader loader; const auto builtinNames = BuiltInEffects::availableEffectNames() << loader.listOfKnownEffects(); for (const QString &name : builtinNames) { plugins.writeEntry(name + QStringLiteral("Enabled"), false); } config->sync(); kwinApp()->setConfig(config); qputenv("KWIN_EFFECTS_FORCE_ANIMATIONS", QByteArrayLiteral("1")); kwinApp()->start(); QVERIFY(workspaceCreatedSpy.wait()); waylandServer()->initWorkspace(); } void PopupOpenCloseAnimationTest::init() { QVERIFY(Test::setupWaylandConnection(Test::AdditionalWaylandInterface::XdgDecoration)); } void PopupOpenCloseAnimationTest::cleanup() { auto effectsImpl = qobject_cast(effects); QVERIFY(effectsImpl); effectsImpl->unloadAllEffects(); QVERIFY(effectsImpl->loadedEffects().isEmpty()); Test::destroyWaylandConnection(); } void PopupOpenCloseAnimationTest::testAnimatePopups() { // This test verifies that popup open/close animation effects try // to animate popups(e.g. popup menus, tooltips, etc). // Make sure that we have the right effects ptr. auto effectsImpl = qobject_cast(effects); QVERIFY(effectsImpl); // Create the main window. using namespace KWayland::Client; QScopedPointer mainWindowSurface(Test::createSurface()); QVERIFY(!mainWindowSurface.isNull()); QScopedPointer mainWindowShellSurface(Test::createXdgShellStableSurface(mainWindowSurface.data())); QVERIFY(!mainWindowShellSurface.isNull()); ShellClient *mainWindow = Test::renderAndWaitForShown(mainWindowSurface.data(), QSize(100, 50), Qt::blue); QVERIFY(mainWindow); // Load effect that will be tested. const QString effectName = QStringLiteral("kwin4_effect_fadingpopups"); QVERIFY(effectsImpl->loadEffect(effectName)); QCOMPARE(effectsImpl->loadedEffects().count(), 1); QCOMPARE(effectsImpl->loadedEffects().first(), effectName); Effect *effect = effectsImpl->findEffect(effectName); QVERIFY(effect); QVERIFY(!effect->isActive()); // Create a popup, it should be animated. QScopedPointer popupSurface(Test::createSurface()); QVERIFY(!popupSurface.isNull()); XdgPositioner positioner(QSize(20, 20), QRect(0, 0, 10, 10)); positioner.setGravity(Qt::BottomEdge | Qt::RightEdge); positioner.setAnchorEdge(Qt::BottomEdge | Qt::LeftEdge); QScopedPointer popupShellSurface(Test::createXdgShellStablePopup(popupSurface.data(), mainWindowShellSurface.data(), positioner)); QVERIFY(!popupShellSurface.isNull()); ShellClient *popup = Test::renderAndWaitForShown(popupSurface.data(), positioner.initialSize(), Qt::red); QVERIFY(popup); QVERIFY(popup->isPopupWindow()); QCOMPARE(popup->transientFor(), mainWindow); QVERIFY(effect->isActive()); // Eventually, the animation will be complete. QTRY_VERIFY(!effect->isActive()); // Destroy the popup, it should not be animated. QSignalSpy popupClosedSpy(popup, &ShellClient::windowClosed); QVERIFY(popupClosedSpy.isValid()); popupShellSurface.reset(); popupSurface.reset(); QVERIFY(popupClosedSpy.wait()); QVERIFY(effect->isActive()); // Eventually, the animation will be complete. QTRY_VERIFY(!effect->isActive()); // Destroy the main window. mainWindowSurface.reset(); QVERIFY(Test::waitForWindowDestroyed(mainWindow)); } void PopupOpenCloseAnimationTest::testAnimateUserActionsPopup() { QSKIP("Can't animate the disappearing of the user actions popup."); // This test verifies that popup open/close animation effects try // to animate the user actions popup. // Make sure that we have the right effects ptr. auto effectsImpl = qobject_cast(effects); QVERIFY(effectsImpl); // Create the test client. using namespace KWayland::Client; QScopedPointer surface(Test::createSurface()); QVERIFY(!surface.isNull()); QScopedPointer shellSurface(Test::createXdgShellStableSurface(surface.data())); QVERIFY(!shellSurface.isNull()); ShellClient *client = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); QVERIFY(client); // Load effect that will be tested. const QString effectName = QStringLiteral("kwin4_effect_fadingpopups"); QVERIFY(effectsImpl->loadEffect(effectName)); QCOMPARE(effectsImpl->loadedEffects().count(), 1); QCOMPARE(effectsImpl->loadedEffects().first(), effectName); Effect *effect = effectsImpl->findEffect(effectName); QVERIFY(effect); QVERIFY(!effect->isActive()); // Show the user actions popup. workspace()->showWindowMenu(QRect(), client); auto userActionsMenu = workspace()->userActionsMenu(); QTRY_VERIFY(userActionsMenu->isShown()); QVERIFY(userActionsMenu->hasClient()); QVERIFY(effect->isActive()); // Eventually, the animation will be complete. QTRY_VERIFY(!effect->isActive()); // Close the user actions popup. kwinApp()->platform()->keyboardKeyPressed(KEY_ESC, 0); kwinApp()->platform()->keyboardKeyReleased(KEY_ESC, 1); QTRY_VERIFY(!userActionsMenu->isShown()); QVERIFY(!userActionsMenu->hasClient()); QVERIFY(effect->isActive()); // Eventually, the animation will be complete. QTRY_VERIFY(!effect->isActive()); // Destroy the test client. surface.reset(); QVERIFY(Test::waitForWindowDestroyed(client)); } void PopupOpenCloseAnimationTest::testAnimateDecorationTooltips() { // This test verifies that popup open/close animation effects try // to animate decoration tooltips. // Make sure that we have the right effects ptr. auto effectsImpl = qobject_cast(effects); QVERIFY(effectsImpl); // Create the test client. using namespace KWayland::Client; QScopedPointer surface(Test::createSurface()); QVERIFY(!surface.isNull()); QScopedPointer shellSurface(Test::createXdgShellStableSurface(surface.data())); QVERIFY(!shellSurface.isNull()); QScopedPointer deco(Test::xdgDecorationManager()->getToplevelDecoration(shellSurface.data())); QVERIFY(!deco.isNull()); deco->setMode(XdgDecoration::Mode::ServerSide); ShellClient *client = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); QVERIFY(client); QVERIFY(client->isDecorated()); // Load effect that will be tested. const QString effectName = QStringLiteral("kwin4_effect_fadingpopups"); QVERIFY(effectsImpl->loadEffect(effectName)); QCOMPARE(effectsImpl->loadedEffects().count(), 1); QCOMPARE(effectsImpl->loadedEffects().first(), effectName); Effect *effect = effectsImpl->findEffect(effectName); QVERIFY(effect); QVERIFY(!effect->isActive()); // Show a decoration tooltip. - QSignalSpy tooltipAddedSpy(waylandServer(), &WaylandServer::shellClientAdded); + QSignalSpy tooltipAddedSpy(workspace(), &Workspace::internalClientAdded); QVERIFY(tooltipAddedSpy.isValid()); client->decoratedClient()->requestShowToolTip(QStringLiteral("KWin rocks!")); QVERIFY(tooltipAddedSpy.wait()); - ShellClient *tooltip = tooltipAddedSpy.first().first().value(); + InternalClient *tooltip = tooltipAddedSpy.first().first().value(); QVERIFY(tooltip->isInternal()); QVERIFY(tooltip->isPopupWindow()); QVERIFY(tooltip->internalWindow()->flags().testFlag(Qt::ToolTip)); QVERIFY(effect->isActive()); // Eventually, the animation will be complete. QTRY_VERIFY(!effect->isActive()); // Hide the decoration tooltip. - QSignalSpy tooltipClosedSpy(tooltip, &ShellClient::windowClosed); + QSignalSpy tooltipClosedSpy(tooltip, &InternalClient::windowClosed); QVERIFY(tooltipClosedSpy.isValid()); client->decoratedClient()->requestHideToolTip(); QVERIFY(tooltipClosedSpy.wait()); QVERIFY(effect->isActive()); // Eventually, the animation will be complete. QTRY_VERIFY(!effect->isActive()); // Destroy the test client. surface.reset(); QVERIFY(Test::waitForWindowDestroyed(client)); } WAYLANDTEST_MAIN(PopupOpenCloseAnimationTest) #include "popup_open_close_animation_test.moc" diff --git a/autotests/integration/globalshortcuts_test.cpp b/autotests/integration/globalshortcuts_test.cpp index c17721bd1..58cb7e930 100644 --- a/autotests/integration/globalshortcuts_test.cpp +++ b/autotests/integration/globalshortcuts_test.cpp @@ -1,382 +1,384 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2016 Martin Gräßlin 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 . *********************************************************************/ #include "kwin_wayland_test.h" #include "client.h" #include "cursor.h" #include "input.h" +#include "internal_client.h" #include "platform.h" #include "screens.h" #include "shell_client.h" #include "useractions.h" #include "wayland_server.h" #include "workspace.h" #include #include #include #include #include #include #include using namespace KWin; using namespace KWayland::Client; static const QString s_socketName = QStringLiteral("wayland_test_kwin_globalshortcuts-0"); class GlobalShortcutsTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void init(); void cleanup(); void testConsumedShift(); void testRepeatedTrigger(); void testUserActionsMenu(); void testMetaShiftW(); void testComponseKey(); void testX11ClientShortcut(); void testWaylandClientShortcut(); void testSetupWindowShortcut(); }; void GlobalShortcutsTest::initTestCase() { - qRegisterMetaType(); - qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType(); QSignalSpy workspaceCreatedSpy(kwinApp(), &Application::workspaceCreated); QVERIFY(workspaceCreatedSpy.isValid()); kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); QVERIFY(waylandServer()->init(s_socketName.toLocal8Bit())); kwinApp()->setConfig(KSharedConfig::openConfig(QString(), KConfig::SimpleConfig)); qputenv("KWIN_XKB_DEFAULT_KEYMAP", "1"); qputenv("XKB_DEFAULT_RULES", "evdev"); kwinApp()->start(); QVERIFY(workspaceCreatedSpy.wait()); waylandServer()->initWorkspace(); } void GlobalShortcutsTest::init() { QVERIFY(Test::setupWaylandConnection()); screens()->setCurrent(0); KWin::Cursor::setPos(QPoint(640, 512)); } void GlobalShortcutsTest::cleanup() { Test::destroyWaylandConnection(); } void GlobalShortcutsTest::testConsumedShift() { // this test verifies that a shortcut with a consumed shift modifier triggers // create the action QScopedPointer action(new QAction(nullptr)); action->setProperty("componentName", QStringLiteral(KWIN_NAME)); action->setObjectName(QStringLiteral("globalshortcuts-test-consumed-shift")); QSignalSpy triggeredSpy(action.data(), &QAction::triggered); QVERIFY(triggeredSpy.isValid()); KGlobalAccel::self()->setShortcut(action.data(), QList{Qt::Key_Percent}, KGlobalAccel::NoAutoloading); input()->registerShortcut(Qt::Key_Percent, action.data()); // press shift+5 quint32 timestamp = 0; kwinApp()->platform()->keyboardKeyPressed(KEY_LEFTSHIFT, timestamp++); QCOMPARE(input()->keyboardModifiers(), Qt::ShiftModifier); kwinApp()->platform()->keyboardKeyPressed(KEY_5, timestamp++); QTRY_COMPARE(triggeredSpy.count(), 1); kwinApp()->platform()->keyboardKeyReleased(KEY_5, timestamp++); // release shift kwinApp()->platform()->keyboardKeyReleased(KEY_LEFTSHIFT, timestamp++); } void GlobalShortcutsTest::testRepeatedTrigger() { // this test verifies that holding a key, triggers repeated global shortcut // in addition pressing another key should stop triggering the shortcut QScopedPointer action(new QAction(nullptr)); action->setProperty("componentName", QStringLiteral(KWIN_NAME)); action->setObjectName(QStringLiteral("globalshortcuts-test-consumed-shift")); QSignalSpy triggeredSpy(action.data(), &QAction::triggered); QVERIFY(triggeredSpy.isValid()); KGlobalAccel::self()->setShortcut(action.data(), QList{Qt::Key_Percent}, KGlobalAccel::NoAutoloading); input()->registerShortcut(Qt::Key_Percent, action.data()); // we need to configure the key repeat first. It is only enabled on libinput waylandServer()->seat()->setKeyRepeatInfo(25, 300); // press shift+5 quint32 timestamp = 0; kwinApp()->platform()->keyboardKeyPressed(KEY_WAKEUP, timestamp++); kwinApp()->platform()->keyboardKeyPressed(KEY_LEFTSHIFT, timestamp++); QCOMPARE(input()->keyboardModifiers(), Qt::ShiftModifier); kwinApp()->platform()->keyboardKeyPressed(KEY_5, timestamp++); QTRY_COMPARE(triggeredSpy.count(), 1); // and should repeat QVERIFY(triggeredSpy.wait()); QVERIFY(triggeredSpy.wait()); // now release the key kwinApp()->platform()->keyboardKeyReleased(KEY_5, timestamp++); QVERIFY(!triggeredSpy.wait(500)); kwinApp()->platform()->keyboardKeyReleased(KEY_WAKEUP, timestamp++); QVERIFY(!triggeredSpy.wait(500)); // release shift kwinApp()->platform()->keyboardKeyReleased(KEY_LEFTSHIFT, timestamp++); } void GlobalShortcutsTest::testUserActionsMenu() { // this test tries to trigger the user actions menu with Alt+F3 // the problem here is that pressing F3 consumes modifiers as it's part of the // Ctrl+alt+F3 keysym for vt switching. xkbcommon considers all modifiers as consumed // which a transformation to any keysym would cause // for more information see: // https://bugs.freedesktop.org/show_bug.cgi?id=92818 // https://github.com/xkbcommon/libxkbcommon/issues/17 // first create a window QScopedPointer surface(Test::createSurface()); QScopedPointer shellSurface(Test::createShellSurface(surface.data())); auto c = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); QVERIFY(c); QVERIFY(c->isActive()); quint32 timestamp = 0; QVERIFY(!workspace()->userActionsMenu()->isShown()); kwinApp()->platform()->keyboardKeyPressed(KEY_LEFTALT, timestamp++); kwinApp()->platform()->keyboardKeyPressed(KEY_F3, timestamp++); kwinApp()->platform()->keyboardKeyReleased(KEY_F3, timestamp++); QTRY_VERIFY(workspace()->userActionsMenu()->isShown()); kwinApp()->platform()->keyboardKeyReleased(KEY_LEFTALT, timestamp++); } void GlobalShortcutsTest::testMetaShiftW() { // BUG 370341 QScopedPointer action(new QAction(nullptr)); action->setProperty("componentName", QStringLiteral(KWIN_NAME)); action->setObjectName(QStringLiteral("globalshortcuts-test-meta-shift-w")); QSignalSpy triggeredSpy(action.data(), &QAction::triggered); QVERIFY(triggeredSpy.isValid()); KGlobalAccel::self()->setShortcut(action.data(), QList{Qt::META + Qt::SHIFT + Qt::Key_W}, KGlobalAccel::NoAutoloading); input()->registerShortcut(Qt::META + Qt::SHIFT + Qt::Key_W, action.data()); // press meta+shift+w quint32 timestamp = 0; kwinApp()->platform()->keyboardKeyPressed(KEY_LEFTMETA, timestamp++); QCOMPARE(input()->keyboardModifiers(), Qt::MetaModifier); kwinApp()->platform()->keyboardKeyPressed(KEY_LEFTSHIFT, timestamp++); QCOMPARE(input()->keyboardModifiers(), Qt::ShiftModifier | Qt::MetaModifier); kwinApp()->platform()->keyboardKeyPressed(KEY_W, timestamp++); QTRY_COMPARE(triggeredSpy.count(), 1); kwinApp()->platform()->keyboardKeyReleased(KEY_W, timestamp++); // release meta+shift kwinApp()->platform()->keyboardKeyReleased(KEY_LEFTSHIFT, timestamp++); kwinApp()->platform()->keyboardKeyReleased(KEY_LEFTMETA, timestamp++); } void GlobalShortcutsTest::testComponseKey() { // BUG 390110 QScopedPointer action(new QAction(nullptr)); action->setProperty("componentName", QStringLiteral(KWIN_NAME)); action->setObjectName(QStringLiteral("globalshortcuts-accent")); QSignalSpy triggeredSpy(action.data(), &QAction::triggered); QVERIFY(triggeredSpy.isValid()); KGlobalAccel::self()->setShortcut(action.data(), QList{Qt::UNICODE_ACCEL}, KGlobalAccel::NoAutoloading); input()->registerShortcut(Qt::UNICODE_ACCEL, action.data()); // press & release ` quint32 timestamp = 0; kwinApp()->platform()->keyboardKeyPressed(KEY_RESERVED, timestamp++); kwinApp()->platform()->keyboardKeyReleased(KEY_RESERVED, timestamp++); QTRY_COMPARE(triggeredSpy.count(), 0); } struct XcbConnectionDeleter { static inline void cleanup(xcb_connection_t *pointer) { xcb_disconnect(pointer); } }; void GlobalShortcutsTest::testX11ClientShortcut() { // create an X11 window QScopedPointer c(xcb_connect(nullptr, nullptr)); QVERIFY(!xcb_connection_has_error(c.data())); xcb_window_t w = xcb_generate_id(c.data()); const QRect windowGeometry = QRect(0, 0, 10, 20); const uint32_t values[] = { XCB_EVENT_MASK_ENTER_WINDOW | XCB_EVENT_MASK_LEAVE_WINDOW }; xcb_create_window(c.data(), XCB_COPY_FROM_PARENT, w, rootWindow(), windowGeometry.x(), windowGeometry.y(), windowGeometry.width(), windowGeometry.height(), 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, XCB_CW_EVENT_MASK, values); xcb_size_hints_t hints; memset(&hints, 0, sizeof(hints)); xcb_icccm_size_hints_set_position(&hints, 1, windowGeometry.x(), windowGeometry.y()); xcb_icccm_size_hints_set_size(&hints, 1, windowGeometry.width(), windowGeometry.height()); xcb_icccm_set_wm_normal_hints(c.data(), w, &hints); NETWinInfo info(c.data(), w, rootWindow(), NET::WMAllProperties, NET::WM2AllProperties); info.setWindowType(NET::Normal); xcb_map_window(c.data(), w); xcb_flush(c.data()); QSignalSpy windowCreatedSpy(workspace(), &Workspace::clientAdded); QVERIFY(windowCreatedSpy.isValid()); QVERIFY(windowCreatedSpy.wait()); Client *client = windowCreatedSpy.last().first().value(); QVERIFY(client); QCOMPARE(workspace()->activeClient(), client); QVERIFY(client->isActive()); QCOMPARE(client->shortcut(), QKeySequence()); const QKeySequence seq(Qt::META + Qt::SHIFT + Qt::Key_Y); QVERIFY(workspace()->shortcutAvailable(seq)); client->setShortcut(seq.toString()); QCOMPARE(client->shortcut(), seq); QVERIFY(!workspace()->shortcutAvailable(seq)); QCOMPARE(client->caption(), QStringLiteral(" {Meta+Shift+Y}")); // it's delayed QCoreApplication::processEvents(); workspace()->activateClient(nullptr); QVERIFY(!workspace()->activeClient()); QVERIFY(!client->isActive()); // now let's trigger the shortcut quint32 timestamp = 0; kwinApp()->platform()->keyboardKeyPressed(KEY_LEFTMETA, timestamp++); kwinApp()->platform()->keyboardKeyPressed(KEY_LEFTSHIFT, timestamp++); kwinApp()->platform()->keyboardKeyPressed(KEY_Y, timestamp++); QTRY_COMPARE(workspace()->activeClient(), client); kwinApp()->platform()->keyboardKeyReleased(KEY_Y, timestamp++); kwinApp()->platform()->keyboardKeyReleased(KEY_LEFTSHIFT, timestamp++); kwinApp()->platform()->keyboardKeyReleased(KEY_LEFTMETA, timestamp++); // destroy window again QSignalSpy windowClosedSpy(client, &Client::windowClosed); QVERIFY(windowClosedSpy.isValid()); xcb_unmap_window(c.data(), w); xcb_destroy_window(c.data(), w); xcb_flush(c.data()); QVERIFY(windowClosedSpy.wait()); } void GlobalShortcutsTest::testWaylandClientShortcut() { QScopedPointer surface(Test::createSurface()); QScopedPointer shellSurface(Test::createShellSurface(surface.data())); auto client = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); QCOMPARE(workspace()->activeClient(), client); QVERIFY(client->isActive()); QCOMPARE(client->shortcut(), QKeySequence()); const QKeySequence seq(Qt::META + Qt::SHIFT + Qt::Key_Y); QVERIFY(workspace()->shortcutAvailable(seq)); client->setShortcut(seq.toString()); QCOMPARE(client->shortcut(), seq); QVERIFY(!workspace()->shortcutAvailable(seq)); QCOMPARE(client->caption(), QStringLiteral(" {Meta+Shift+Y}")); workspace()->activateClient(nullptr); QVERIFY(!workspace()->activeClient()); QVERIFY(!client->isActive()); // now let's trigger the shortcut quint32 timestamp = 0; kwinApp()->platform()->keyboardKeyPressed(KEY_LEFTMETA, timestamp++); kwinApp()->platform()->keyboardKeyPressed(KEY_LEFTSHIFT, timestamp++); kwinApp()->platform()->keyboardKeyPressed(KEY_Y, timestamp++); QTRY_COMPARE(workspace()->activeClient(), client); kwinApp()->platform()->keyboardKeyReleased(KEY_Y, timestamp++); kwinApp()->platform()->keyboardKeyReleased(KEY_LEFTSHIFT, timestamp++); kwinApp()->platform()->keyboardKeyReleased(KEY_LEFTMETA, timestamp++); shellSurface.reset(); surface.reset(); QVERIFY(Test::waitForWindowDestroyed(client)); QVERIFY(workspace()->shortcutAvailable(seq)); } void GlobalShortcutsTest::testSetupWindowShortcut() { QScopedPointer surface(Test::createSurface()); QScopedPointer shellSurface(Test::createShellSurface(surface.data())); auto client = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); QCOMPARE(workspace()->activeClient(), client); QVERIFY(client->isActive()); QCOMPARE(client->shortcut(), QKeySequence()); - QSignalSpy shortcutDialogAddedSpy(waylandServer(), &WaylandServer::shellClientAdded); + QSignalSpy shortcutDialogAddedSpy(workspace(), &Workspace::internalClientAdded); QVERIFY(shortcutDialogAddedSpy.isValid()); workspace()->slotSetupWindowShortcut(); QTRY_COMPARE(shortcutDialogAddedSpy.count(), 1); - auto dialog = shortcutDialogAddedSpy.first().first().value(); + auto dialog = shortcutDialogAddedSpy.first().first().value(); QVERIFY(dialog); QVERIFY(dialog->isInternal()); auto sequenceEdit = workspace()->shortcutDialog()->findChild(); QVERIFY(sequenceEdit); // the QKeySequenceEdit field does not get focus, we need to pass it focus manually QEXPECT_FAIL("", "Edit does not have focus", Continue); QVERIFY(sequenceEdit->hasFocus()); sequenceEdit->setFocus(); QTRY_VERIFY(sequenceEdit->hasFocus()); quint32 timestamp = 0; kwinApp()->platform()->keyboardKeyPressed(KEY_LEFTMETA, timestamp++); kwinApp()->platform()->keyboardKeyPressed(KEY_LEFTSHIFT, timestamp++); kwinApp()->platform()->keyboardKeyPressed(KEY_Y, timestamp++); kwinApp()->platform()->keyboardKeyReleased(KEY_Y, timestamp++); kwinApp()->platform()->keyboardKeyReleased(KEY_LEFTSHIFT, timestamp++); kwinApp()->platform()->keyboardKeyReleased(KEY_LEFTMETA, timestamp++); // the sequence gets accepted after one second, so wait a bit longer QTest::qWait(2000); // now send in enter kwinApp()->platform()->keyboardKeyPressed(KEY_ENTER, timestamp++); kwinApp()->platform()->keyboardKeyReleased(KEY_ENTER, timestamp++); QTRY_COMPARE(client->shortcut(), QKeySequence(Qt::META + Qt::SHIFT + Qt::Key_Y)); } WAYLANDTEST_MAIN(GlobalShortcutsTest) #include "globalshortcuts_test.moc" diff --git a/autotests/integration/internal_window.cpp b/autotests/integration/internal_window.cpp index 42d662aff..7045faaf5 100644 --- a/autotests/integration/internal_window.cpp +++ b/autotests/integration/internal_window.cpp @@ -1,808 +1,798 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2016 Martin Gräßlin 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 . *********************************************************************/ #include "kwin_wayland_test.h" #include "platform.h" #include "cursor.h" #include "effects.h" #include "internal_client.h" -#include "shell_client.h" #include "screens.h" +#include "shell_client.h" #include "wayland_server.h" #include "workspace.h" #include #include #include #include #include #include #include #include #include using namespace KWayland::Client; Q_DECLARE_METATYPE(NET::WindowType); namespace KWin { static const QString s_socketName = QStringLiteral("wayland_test_kwin_internal_window-0"); class InternalWindowTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void init(); void cleanup(); void testEnterLeave(); void testPointerPressRelease(); void testPointerAxis(); void testKeyboard_data(); void testKeyboard(); void testKeyboardShowWithoutActivating(); void testKeyboardTriggersLeave(); void testTouch(); void testOpacity(); void testMove(); void testSkipCloseAnimation_data(); void testSkipCloseAnimation(); void testModifierClickUnrestrictedMove(); void testModifierScroll(); void testPopup(); void testScale(); void testWindowType_data(); void testWindowType(); void testChangeWindowType_data(); void testChangeWindowType(); void testEffectWindow(); }; class HelperWindow : public QRasterWindow { Q_OBJECT public: HelperWindow(); ~HelperWindow() override; QPoint latestGlobalMousePos() const { return m_latestGlobalMousePos; } Qt::MouseButtons pressedButtons() const { return m_pressedButtons; } Q_SIGNALS: void entered(); void left(); void mouseMoved(const QPoint &global); void mousePressed(); void mouseReleased(); void wheel(); void keyPressed(); void keyReleased(); protected: void paintEvent(QPaintEvent *event) override; bool event(QEvent *event) override; void mouseMoveEvent(QMouseEvent *event) override; void mousePressEvent(QMouseEvent *event) override; void mouseReleaseEvent(QMouseEvent *event) override; void wheelEvent(QWheelEvent *event) override; void keyPressEvent(QKeyEvent *event) override; void keyReleaseEvent(QKeyEvent *event) override; private: QPoint m_latestGlobalMousePos; Qt::MouseButtons m_pressedButtons = Qt::MouseButtons(); }; HelperWindow::HelperWindow() : QRasterWindow(nullptr) { setFlags(Qt::FramelessWindowHint); } HelperWindow::~HelperWindow() = default; void HelperWindow::paintEvent(QPaintEvent *event) { Q_UNUSED(event) QPainter p(this); p.fillRect(0, 0, width(), height(), Qt::red); } bool HelperWindow::event(QEvent *event) { if (event->type() == QEvent::Enter) { emit entered(); } if (event->type() == QEvent::Leave) { emit left(); } return QRasterWindow::event(event); } void HelperWindow::mouseMoveEvent(QMouseEvent *event) { m_latestGlobalMousePos = event->globalPos(); emit mouseMoved(event->globalPos()); } void HelperWindow::mousePressEvent(QMouseEvent *event) { m_latestGlobalMousePos = event->globalPos(); m_pressedButtons = event->buttons(); emit mousePressed(); } void HelperWindow::mouseReleaseEvent(QMouseEvent *event) { m_latestGlobalMousePos = event->globalPos(); m_pressedButtons = event->buttons(); emit mouseReleased(); } void HelperWindow::wheelEvent(QWheelEvent *event) { Q_UNUSED(event) emit wheel(); } void HelperWindow::keyPressEvent(QKeyEvent *event) { Q_UNUSED(event) emit keyPressed(); } void HelperWindow::keyReleaseEvent(QKeyEvent *event) { Q_UNUSED(event) emit keyReleased(); } void InternalWindowTest::initTestCase() { - qRegisterMetaType(); - qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType(); QSignalSpy workspaceCreatedSpy(kwinApp(), &Application::workspaceCreated); QVERIFY(workspaceCreatedSpy.isValid()); kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); QVERIFY(waylandServer()->init(s_socketName.toLocal8Bit())); QMetaObject::invokeMethod(kwinApp()->platform(), "setVirtualOutputs", Qt::DirectConnection, Q_ARG(int, 2)); kwinApp()->setConfig(KSharedConfig::openConfig(QString(), KConfig::SimpleConfig)); kwinApp()->start(); QVERIFY(workspaceCreatedSpy.wait()); QCOMPARE(screens()->count(), 2); QCOMPARE(screens()->geometry(0), QRect(0, 0, 1280, 1024)); QCOMPARE(screens()->geometry(1), QRect(1280, 0, 1280, 1024)); waylandServer()->initWorkspace(); } void InternalWindowTest::init() { Cursor::setPos(QPoint(1280, 512)); QVERIFY(Test::setupWaylandConnection(Test::AdditionalWaylandInterface::Seat)); QVERIFY(Test::waitForWaylandKeyboard()); } void InternalWindowTest::cleanup() { Test::destroyWaylandConnection(); + + QMetaObject::invokeMethod(kwinApp()->platform(), "setVirtualOutputs", Qt::DirectConnection, Q_ARG(int, 2)); } void InternalWindowTest::testEnterLeave() { - QSignalSpy clientAddedSpy(waylandServer(), &WaylandServer::shellClientAdded); + QSignalSpy clientAddedSpy(workspace(), &Workspace::internalClientAdded); QVERIFY(clientAddedSpy.isValid()); HelperWindow win; - QVERIFY(!workspace()->findToplevel(nullptr)); - QVERIFY(!workspace()->findToplevel(&win)); + QVERIFY(!workspace()->findInternal(nullptr)); + QVERIFY(!workspace()->findInternal(&win)); win.setGeometry(0, 0, 100, 100); win.show(); QTRY_COMPARE(clientAddedSpy.count(), 1); QVERIFY(!workspace()->activeClient()); - ShellClient *c = clientAddedSpy.first().first().value(); + InternalClient *c = clientAddedSpy.first().first().value(); + QVERIFY(c); QVERIFY(c->isInternal()); - QVERIFY(qobject_cast(c)); - QCOMPARE(c->icon().name(), QStringLiteral("wayland")); QVERIFY(!c->isDecorated()); - QCOMPARE(workspace()->findToplevel(&win), c); + QCOMPARE(workspace()->findInternal(&win), c); QCOMPARE(c->geometry(), QRect(0, 0, 100, 100)); QVERIFY(c->isShown(false)); QVERIFY(workspace()->xStackingOrder().contains(c)); QSignalSpy enterSpy(&win, &HelperWindow::entered); QVERIFY(enterSpy.isValid()); QSignalSpy leaveSpy(&win, &HelperWindow::left); QVERIFY(leaveSpy.isValid()); QSignalSpy moveSpy(&win, &HelperWindow::mouseMoved); QVERIFY(moveSpy.isValid()); quint32 timestamp = 1; kwinApp()->platform()->pointerMotion(QPoint(50, 50), timestamp++); QTRY_COMPARE(moveSpy.count(), 1); kwinApp()->platform()->pointerMotion(QPoint(60, 50), timestamp++); QTRY_COMPARE(moveSpy.count(), 2); QCOMPARE(moveSpy[1].first().toPoint(), QPoint(60, 50)); kwinApp()->platform()->pointerMotion(QPoint(101, 50), timestamp++); QTRY_COMPARE(leaveSpy.count(), 1); // set a mask on the window win.setMask(QRegion(10, 20, 30, 40)); // outside the mask we should not get an enter kwinApp()->platform()->pointerMotion(QPoint(5, 5), timestamp++); QVERIFY(!enterSpy.wait(100)); QCOMPARE(enterSpy.count(), 1); // inside the mask we should still get an enter kwinApp()->platform()->pointerMotion(QPoint(25, 27), timestamp++); QTRY_COMPARE(enterSpy.count(), 2); - - // hide the window, which should be removed from the stacking order - win.hide(); - QTRY_VERIFY(!c->isShown(false)); - QVERIFY(!workspace()->xStackingOrder().contains(c)); - - // show again - win.show(); - QTRY_VERIFY(c->isShown(false)); - QVERIFY(workspace()->xStackingOrder().contains(c)); } void InternalWindowTest::testPointerPressRelease() { - QSignalSpy clientAddedSpy(waylandServer(), &WaylandServer::shellClientAdded); + QSignalSpy clientAddedSpy(workspace(), &Workspace::internalClientAdded); QVERIFY(clientAddedSpy.isValid()); HelperWindow win; win.setGeometry(0, 0, 100, 100); win.show(); QSignalSpy pressSpy(&win, &HelperWindow::mousePressed); QVERIFY(pressSpy.isValid()); QSignalSpy releaseSpy(&win, &HelperWindow::mouseReleased); QVERIFY(releaseSpy.isValid()); QTRY_COMPARE(clientAddedSpy.count(), 1); quint32 timestamp = 1; kwinApp()->platform()->pointerMotion(QPoint(50, 50), timestamp++); kwinApp()->platform()->pointerButtonPressed(BTN_LEFT, timestamp++); QTRY_COMPARE(pressSpy.count(), 1); kwinApp()->platform()->pointerButtonReleased(BTN_LEFT, timestamp++); QTRY_COMPARE(releaseSpy.count(), 1); } void InternalWindowTest::testPointerAxis() { - QSignalSpy clientAddedSpy(waylandServer(), &WaylandServer::shellClientAdded); + QSignalSpy clientAddedSpy(workspace(), &Workspace::internalClientAdded); QVERIFY(clientAddedSpy.isValid()); HelperWindow win; win.setGeometry(0, 0, 100, 100); win.show(); QSignalSpy wheelSpy(&win, &HelperWindow::wheel); QVERIFY(wheelSpy.isValid()); QTRY_COMPARE(clientAddedSpy.count(), 1); quint32 timestamp = 1; kwinApp()->platform()->pointerMotion(QPoint(50, 50), timestamp++); kwinApp()->platform()->pointerAxisVertical(5.0, timestamp++); QTRY_COMPARE(wheelSpy.count(), 1); kwinApp()->platform()->pointerAxisHorizontal(5.0, timestamp++); QTRY_COMPARE(wheelSpy.count(), 2); } void InternalWindowTest::testKeyboard_data() { QTest::addColumn("cursorPos"); QTest::newRow("on Window") << QPoint(50, 50); QTest::newRow("outside Window") << QPoint(250, 250); } void InternalWindowTest::testKeyboard() { - QSignalSpy clientAddedSpy(waylandServer(), &WaylandServer::shellClientAdded); + QSignalSpy clientAddedSpy(workspace(), &Workspace::internalClientAdded); QVERIFY(clientAddedSpy.isValid()); HelperWindow win; win.setGeometry(0, 0, 100, 100); win.show(); QSignalSpy pressSpy(&win, &HelperWindow::keyPressed); QVERIFY(pressSpy.isValid()); QSignalSpy releaseSpy(&win, &HelperWindow::keyReleased); QVERIFY(releaseSpy.isValid()); QTRY_COMPARE(clientAddedSpy.count(), 1); - auto internalClient = clientAddedSpy.first().first().value(); + auto internalClient = clientAddedSpy.first().first().value(); QVERIFY(internalClient); QVERIFY(internalClient->isInternal()); QVERIFY(internalClient->readyForPainting()); quint32 timestamp = 1; QFETCH(QPoint, cursorPos); kwinApp()->platform()->pointerMotion(cursorPos, timestamp++); kwinApp()->platform()->keyboardKeyPressed(KEY_A, timestamp++); QTRY_COMPARE(pressSpy.count(), 1); QCOMPARE(releaseSpy.count(), 0); kwinApp()->platform()->keyboardKeyReleased(KEY_A, timestamp++); QTRY_COMPARE(releaseSpy.count(), 1); QCOMPARE(pressSpy.count(), 1); } void InternalWindowTest::testKeyboardShowWithoutActivating() { - QSignalSpy clientAddedSpy(waylandServer(), &WaylandServer::shellClientAdded); + QSignalSpy clientAddedSpy(workspace(), &Workspace::internalClientAdded); QVERIFY(clientAddedSpy.isValid()); HelperWindow win; win.setProperty("_q_showWithoutActivating", true); win.setGeometry(0, 0, 100, 100); win.show(); QSignalSpy pressSpy(&win, &HelperWindow::keyPressed); QVERIFY(pressSpy.isValid()); QSignalSpy releaseSpy(&win, &HelperWindow::keyReleased); QVERIFY(releaseSpy.isValid()); QTRY_COMPARE(clientAddedSpy.count(), 1); - auto internalClient = clientAddedSpy.first().first().value(); + auto internalClient = clientAddedSpy.first().first().value(); QVERIFY(internalClient); QVERIFY(internalClient->isInternal()); QVERIFY(internalClient->readyForPainting()); quint32 timestamp = 1; const QPoint cursorPos = QPoint(50, 50); kwinApp()->platform()->pointerMotion(cursorPos, timestamp++); kwinApp()->platform()->keyboardKeyPressed(KEY_A, timestamp++); QCOMPARE(pressSpy.count(), 0); QVERIFY(!pressSpy.wait(100)); QCOMPARE(releaseSpy.count(), 0); kwinApp()->platform()->keyboardKeyReleased(KEY_A, timestamp++); QCOMPARE(releaseSpy.count(), 0); QVERIFY(!releaseSpy.wait(100)); QCOMPARE(pressSpy.count(), 0); } void InternalWindowTest::testKeyboardTriggersLeave() { // this test verifies that a leave event is sent to a client when an internal window // gets a key event QScopedPointer keyboard(Test::waylandSeat()->createKeyboard()); QVERIFY(!keyboard.isNull()); QVERIFY(keyboard->isValid()); QSignalSpy enteredSpy(keyboard.data(), &Keyboard::entered); QVERIFY(enteredSpy.isValid()); QSignalSpy leftSpy(keyboard.data(), &Keyboard::left); QVERIFY(leftSpy.isValid()); QScopedPointer surface(Test::createSurface()); QScopedPointer shellSurface(Test::createShellSurface(Test::ShellSurfaceType::WlShell, surface.data())); // now let's render auto c = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); QVERIFY(c); QVERIFY(c->isActive()); QVERIFY(!c->isInternal()); if (enteredSpy.isEmpty()) { QVERIFY(enteredSpy.wait()); } QCOMPARE(enteredSpy.count(), 1); // create internal window - QSignalSpy clientAddedSpy(waylandServer(), &WaylandServer::shellClientAdded); + QSignalSpy clientAddedSpy(workspace(), &Workspace::internalClientAdded); QVERIFY(clientAddedSpy.isValid()); HelperWindow win; win.setGeometry(0, 0, 100, 100); win.show(); QSignalSpy pressSpy(&win, &HelperWindow::keyPressed); QVERIFY(pressSpy.isValid()); QSignalSpy releaseSpy(&win, &HelperWindow::keyReleased); QVERIFY(releaseSpy.isValid()); QTRY_COMPARE(clientAddedSpy.count(), 1); - auto internalClient = clientAddedSpy.first().first().value(); + auto internalClient = clientAddedSpy.first().first().value(); QVERIFY(internalClient); QVERIFY(internalClient->isInternal()); QVERIFY(internalClient->readyForPainting()); QVERIFY(leftSpy.isEmpty()); QVERIFY(!leftSpy.wait(100)); // now let's trigger a key, which should result in a leave quint32 timestamp = 1; kwinApp()->platform()->keyboardKeyPressed(KEY_A, timestamp++); QVERIFY(leftSpy.wait()); QCOMPARE(pressSpy.count(), 1); kwinApp()->platform()->keyboardKeyReleased(KEY_A, timestamp++); QTRY_COMPARE(releaseSpy.count(), 1); // after hiding the internal window, next key press should trigger an enter win.hide(); kwinApp()->platform()->keyboardKeyPressed(KEY_A, timestamp++); QVERIFY(enteredSpy.wait()); kwinApp()->platform()->keyboardKeyReleased(KEY_A, timestamp++); } void InternalWindowTest::testTouch() { // touch events for internal windows are emulated through mouse events - QSignalSpy clientAddedSpy(waylandServer(), &WaylandServer::shellClientAdded); + QSignalSpy clientAddedSpy(workspace(), &Workspace::internalClientAdded); QVERIFY(clientAddedSpy.isValid()); HelperWindow win; win.setGeometry(0, 0, 100, 100); win.show(); QTRY_COMPARE(clientAddedSpy.count(), 1); QSignalSpy pressSpy(&win, &HelperWindow::mousePressed); QVERIFY(pressSpy.isValid()); QSignalSpy releaseSpy(&win, &HelperWindow::mouseReleased); QVERIFY(releaseSpy.isValid()); QSignalSpy moveSpy(&win, &HelperWindow::mouseMoved); QVERIFY(moveSpy.isValid()); quint32 timestamp = 1; QCOMPARE(win.pressedButtons(), Qt::MouseButtons()); kwinApp()->platform()->touchDown(0, QPointF(50, 50), timestamp++); QCOMPARE(pressSpy.count(), 1); QCOMPARE(win.latestGlobalMousePos(), QPoint(50, 50)); QCOMPARE(win.pressedButtons(), Qt::MouseButtons(Qt::LeftButton)); // further touch down should not trigger kwinApp()->platform()->touchDown(1, QPointF(75, 75), timestamp++); QCOMPARE(pressSpy.count(), 1); kwinApp()->platform()->touchUp(1, timestamp++); QCOMPARE(releaseSpy.count(), 0); QCOMPARE(win.latestGlobalMousePos(), QPoint(50, 50)); QCOMPARE(win.pressedButtons(), Qt::MouseButtons(Qt::LeftButton)); // another press kwinApp()->platform()->touchDown(1, QPointF(10, 10), timestamp++); QCOMPARE(pressSpy.count(), 1); QCOMPARE(win.latestGlobalMousePos(), QPoint(50, 50)); QCOMPARE(win.pressedButtons(), Qt::MouseButtons(Qt::LeftButton)); // simulate the move QCOMPARE(moveSpy.count(), 0); kwinApp()->platform()->touchMotion(0, QPointF(80, 90), timestamp++); QCOMPARE(moveSpy.count(), 1); QCOMPARE(win.latestGlobalMousePos(), QPoint(80, 90)); QCOMPARE(win.pressedButtons(), Qt::MouseButtons(Qt::LeftButton)); // move on other ID should not do anything kwinApp()->platform()->touchMotion(1, QPointF(20, 30), timestamp++); QCOMPARE(moveSpy.count(), 1); QCOMPARE(win.latestGlobalMousePos(), QPoint(80, 90)); QCOMPARE(win.pressedButtons(), Qt::MouseButtons(Qt::LeftButton)); // now up our main point kwinApp()->platform()->touchUp(0, timestamp++); QCOMPARE(releaseSpy.count(), 1); QCOMPARE(win.latestGlobalMousePos(), QPoint(80, 90)); QCOMPARE(win.pressedButtons(), Qt::MouseButtons()); // and up the additional point kwinApp()->platform()->touchUp(1, timestamp++); QCOMPARE(releaseSpy.count(), 1); QCOMPARE(moveSpy.count(), 1); QCOMPARE(win.latestGlobalMousePos(), QPoint(80, 90)); QCOMPARE(win.pressedButtons(), Qt::MouseButtons()); } void InternalWindowTest::testOpacity() { - // this test verifies that opacity is properly synced from QWindow to ShellClient - QSignalSpy clientAddedSpy(waylandServer(), &WaylandServer::shellClientAdded); + // this test verifies that opacity is properly synced from QWindow to InternalClient + QSignalSpy clientAddedSpy(workspace(), &Workspace::internalClientAdded); QVERIFY(clientAddedSpy.isValid()); HelperWindow win; win.setOpacity(0.5); win.setGeometry(0, 0, 100, 100); win.show(); QTRY_COMPARE(clientAddedSpy.count(), 1); - auto internalClient = clientAddedSpy.first().first().value(); + auto internalClient = clientAddedSpy.first().first().value(); QVERIFY(internalClient); QVERIFY(internalClient->isInternal()); QCOMPARE(internalClient->opacity(), 0.5); - QSignalSpy opacityChangedSpy(internalClient, &ShellClient::opacityChanged); + QSignalSpy opacityChangedSpy(internalClient, &InternalClient::opacityChanged); QVERIFY(opacityChangedSpy.isValid()); win.setOpacity(0.75); QCOMPARE(opacityChangedSpy.count(), 1); QCOMPARE(internalClient->opacity(), 0.75); } void InternalWindowTest::testMove() { - QSignalSpy clientAddedSpy(waylandServer(), &WaylandServer::shellClientAdded); + QSignalSpy clientAddedSpy(workspace(), &Workspace::internalClientAdded); QVERIFY(clientAddedSpy.isValid()); HelperWindow win; win.setOpacity(0.5); win.setGeometry(0, 0, 100, 100); win.show(); QTRY_COMPARE(clientAddedSpy.count(), 1); - auto internalClient = clientAddedSpy.first().first().value(); + auto internalClient = clientAddedSpy.first().first().value(); QVERIFY(internalClient); QCOMPARE(internalClient->geometry(), QRect(0, 0, 100, 100)); // normal move should be synced internalClient->move(5, 10); QCOMPARE(internalClient->geometry(), QRect(5, 10, 100, 100)); QTRY_COMPARE(win.geometry(), QRect(5, 10, 100, 100)); // another move should also be synced internalClient->move(10, 20); QCOMPARE(internalClient->geometry(), QRect(10, 20, 100, 100)); QTRY_COMPARE(win.geometry(), QRect(10, 20, 100, 100)); // now move with a Geometry update blocker { GeometryUpdatesBlocker blocker(internalClient); internalClient->move(5, 10); // not synced! QCOMPARE(win.geometry(), QRect(10, 20, 100, 100)); } // after destroying the blocker it should be synced QTRY_COMPARE(win.geometry(), QRect(5, 10, 100, 100)); } void InternalWindowTest::testSkipCloseAnimation_data() { QTest::addColumn("initial"); QTest::newRow("set") << true; QTest::newRow("not set") << false; } void InternalWindowTest::testSkipCloseAnimation() { - QSignalSpy clientAddedSpy(waylandServer(), &WaylandServer::shellClientAdded); + QSignalSpy clientAddedSpy(workspace(), &Workspace::internalClientAdded); QVERIFY(clientAddedSpy.isValid()); HelperWindow win; win.setOpacity(0.5); win.setGeometry(0, 0, 100, 100); QFETCH(bool, initial); win.setProperty("KWIN_SKIP_CLOSE_ANIMATION", initial); win.show(); QTRY_COMPARE(clientAddedSpy.count(), 1); - auto internalClient = clientAddedSpy.first().first().value(); + auto internalClient = clientAddedSpy.first().first().value(); QVERIFY(internalClient); QCOMPARE(internalClient->skipsCloseAnimation(), initial); QSignalSpy skipCloseChangedSpy(internalClient, &Toplevel::skipCloseAnimationChanged); QVERIFY(skipCloseChangedSpy.isValid()); win.setProperty("KWIN_SKIP_CLOSE_ANIMATION", !initial); QCOMPARE(skipCloseChangedSpy.count(), 1); QCOMPARE(internalClient->skipsCloseAnimation(), !initial); win.setProperty("KWIN_SKIP_CLOSE_ANIMATION", initial); QCOMPARE(skipCloseChangedSpy.count(), 2); QCOMPARE(internalClient->skipsCloseAnimation(), initial); } void InternalWindowTest::testModifierClickUnrestrictedMove() { - QSignalSpy clientAddedSpy(waylandServer(), &WaylandServer::shellClientAdded); + QSignalSpy clientAddedSpy(workspace(), &Workspace::internalClientAdded); QVERIFY(clientAddedSpy.isValid()); HelperWindow win; win.setGeometry(0, 0, 100, 100); win.setFlags(win.flags() & ~Qt::FramelessWindowHint); win.show(); QTRY_COMPARE(clientAddedSpy.count(), 1); - auto internalClient = clientAddedSpy.first().first().value(); + auto internalClient = clientAddedSpy.first().first().value(); QVERIFY(internalClient); QVERIFY(internalClient->isDecorated()); KConfigGroup group = kwinApp()->config()->group("MouseBindings"); group.writeEntry("CommandAllKey", "Alt"); group.writeEntry("CommandAll1", "Move"); group.writeEntry("CommandAll2", "Move"); group.writeEntry("CommandAll3", "Move"); group.sync(); workspace()->slotReconfigure(); QCOMPARE(options->commandAllModifier(), Qt::AltModifier); QCOMPARE(options->commandAll1(), Options::MouseUnrestrictedMove); QCOMPARE(options->commandAll2(), Options::MouseUnrestrictedMove); QCOMPARE(options->commandAll3(), Options::MouseUnrestrictedMove); // move cursor on window Cursor::setPos(internalClient->geometry().center()); // simulate modifier+click quint32 timestamp = 1; kwinApp()->platform()->keyboardKeyPressed(KEY_LEFTALT, timestamp++); QVERIFY(!internalClient->isMove()); kwinApp()->platform()->pointerButtonPressed(BTN_LEFT, timestamp++); QVERIFY(internalClient->isMove()); // release modifier should not change it kwinApp()->platform()->keyboardKeyReleased(KEY_LEFTALT, timestamp++); QVERIFY(internalClient->isMove()); // but releasing the key should end move/resize kwinApp()->platform()->pointerButtonReleased(BTN_LEFT, timestamp++); QVERIFY(!internalClient->isMove()); } void InternalWindowTest::testModifierScroll() { - QSignalSpy clientAddedSpy(waylandServer(), &WaylandServer::shellClientAdded); + QSignalSpy clientAddedSpy(workspace(), &Workspace::internalClientAdded); QVERIFY(clientAddedSpy.isValid()); HelperWindow win; win.setGeometry(0, 0, 100, 100); win.setFlags(win.flags() & ~Qt::FramelessWindowHint); win.show(); QTRY_COMPARE(clientAddedSpy.count(), 1); - auto internalClient = clientAddedSpy.first().first().value(); + auto internalClient = clientAddedSpy.first().first().value(); QVERIFY(internalClient); QVERIFY(internalClient->isDecorated()); KConfigGroup group = kwinApp()->config()->group("MouseBindings"); group.writeEntry("CommandAllKey", "Alt"); group.writeEntry("CommandAllWheel", "change opacity"); group.sync(); workspace()->slotReconfigure(); // move cursor on window Cursor::setPos(internalClient->geometry().center()); // set the opacity to 0.5 internalClient->setOpacity(0.5); QCOMPARE(internalClient->opacity(), 0.5); quint32 timestamp = 1; kwinApp()->platform()->keyboardKeyPressed(KEY_LEFTALT, timestamp++); kwinApp()->platform()->pointerAxisVertical(-5, timestamp++); QCOMPARE(internalClient->opacity(), 0.6); kwinApp()->platform()->pointerAxisVertical(5, timestamp++); QCOMPARE(internalClient->opacity(), 0.5); kwinApp()->platform()->keyboardKeyReleased(KEY_LEFTALT, timestamp++); } void InternalWindowTest::testPopup() { - QSignalSpy clientAddedSpy(waylandServer(), &WaylandServer::shellClientAdded); + QSignalSpy clientAddedSpy(workspace(), &Workspace::internalClientAdded); QVERIFY(clientAddedSpy.isValid()); HelperWindow win; win.setGeometry(0, 0, 100, 100); win.setFlags(win.flags() | Qt::Popup); win.show(); QTRY_COMPARE(clientAddedSpy.count(), 1); - auto internalClient = clientAddedSpy.first().first().value(); + auto internalClient = clientAddedSpy.first().first().value(); QVERIFY(internalClient); QCOMPARE(internalClient->isPopupWindow(), true); } void InternalWindowTest::testScale() { QMetaObject::invokeMethod(kwinApp()->platform(), "setVirtualOutputs", Qt::DirectConnection, Q_ARG(int, 2), Q_ARG(QVector, QVector({QRect(0,0,1280, 1024), QRect(1280/2, 0, 1280, 1024)})), Q_ARG(QVector, QVector({2,2}))); - QSignalSpy clientAddedSpy(waylandServer(), &WaylandServer::shellClientAdded); + QSignalSpy clientAddedSpy(workspace(), &Workspace::internalClientAdded); QVERIFY(clientAddedSpy.isValid()); HelperWindow win; win.setGeometry(0, 0, 100, 100); win.setFlags(win.flags() | Qt::Popup); win.show(); QCOMPARE(win.devicePixelRatio(), 2.0); QTRY_COMPARE(clientAddedSpy.count(), 1); - auto internalClient = clientAddedSpy.first().first().value(); - QCOMPARE(internalClient->surface()->scale(), 2); - - QMetaObject::invokeMethod(kwinApp()->platform(), "setVirtualOutputs", Qt::DirectConnection, Q_ARG(int, 2)); + auto internalClient = clientAddedSpy.first().first().value(); + QCOMPARE(internalClient->bufferScale(), 2); } void InternalWindowTest::testWindowType_data() { QTest::addColumn("windowType"); QTest::newRow("normal") << NET::Normal; QTest::newRow("desktop") << NET::Desktop; QTest::newRow("Dock") << NET::Dock; QTest::newRow("Toolbar") << NET::Toolbar; QTest::newRow("Menu") << NET::Menu; QTest::newRow("Dialog") << NET::Dialog; QTest::newRow("Utility") << NET::Utility; QTest::newRow("Splash") << NET::Splash; QTest::newRow("DropdownMenu") << NET::DropdownMenu; QTest::newRow("PopupMenu") << NET::PopupMenu; QTest::newRow("Tooltip") << NET::Tooltip; QTest::newRow("Notification") << NET::Notification; QTest::newRow("ComboBox") << NET::ComboBox; QTest::newRow("OnScreenDisplay") << NET::OnScreenDisplay; QTest::newRow("CriticalNotification") << NET::CriticalNotification; } void InternalWindowTest::testWindowType() { - QSignalSpy clientAddedSpy(waylandServer(), &WaylandServer::shellClientAdded); + QSignalSpy clientAddedSpy(workspace(), &Workspace::internalClientAdded); QVERIFY(clientAddedSpy.isValid()); HelperWindow win; win.setGeometry(0, 0, 100, 100); QFETCH(NET::WindowType, windowType); KWindowSystem::setType(win.winId(), windowType); win.show(); QTRY_COMPARE(clientAddedSpy.count(), 1); - auto internalClient = clientAddedSpy.first().first().value(); + auto internalClient = clientAddedSpy.first().first().value(); QVERIFY(internalClient); QCOMPARE(internalClient->windowType(), windowType); } void InternalWindowTest::testChangeWindowType_data() { QTest::addColumn("windowType"); QTest::newRow("desktop") << NET::Desktop; QTest::newRow("Dock") << NET::Dock; QTest::newRow("Toolbar") << NET::Toolbar; QTest::newRow("Menu") << NET::Menu; QTest::newRow("Dialog") << NET::Dialog; QTest::newRow("Utility") << NET::Utility; QTest::newRow("Splash") << NET::Splash; QTest::newRow("DropdownMenu") << NET::DropdownMenu; QTest::newRow("PopupMenu") << NET::PopupMenu; QTest::newRow("Tooltip") << NET::Tooltip; QTest::newRow("Notification") << NET::Notification; QTest::newRow("ComboBox") << NET::ComboBox; QTest::newRow("OnScreenDisplay") << NET::OnScreenDisplay; QTest::newRow("CriticalNotification") << NET::CriticalNotification; } void InternalWindowTest::testChangeWindowType() { - QSignalSpy clientAddedSpy(waylandServer(), &WaylandServer::shellClientAdded); + QSignalSpy clientAddedSpy(workspace(), &Workspace::internalClientAdded); QVERIFY(clientAddedSpy.isValid()); HelperWindow win; win.setGeometry(0, 0, 100, 100); win.show(); QTRY_COMPARE(clientAddedSpy.count(), 1); - auto internalClient = clientAddedSpy.first().first().value(); + auto internalClient = clientAddedSpy.first().first().value(); QVERIFY(internalClient); QCOMPARE(internalClient->windowType(), NET::Normal); QFETCH(NET::WindowType, windowType); KWindowSystem::setType(win.winId(), windowType); QTRY_COMPARE(internalClient->windowType(), windowType); KWindowSystem::setType(win.winId(), NET::Normal); QTRY_COMPARE(internalClient->windowType(), NET::Normal); } void InternalWindowTest::testEffectWindow() { - QSignalSpy clientAddedSpy(waylandServer(), &WaylandServer::shellClientAdded); + QSignalSpy clientAddedSpy(workspace(), &Workspace::internalClientAdded); QVERIFY(clientAddedSpy.isValid()); HelperWindow win; win.setGeometry(0, 0, 100, 100); win.show(); QTRY_COMPARE(clientAddedSpy.count(), 1); - auto internalClient = clientAddedSpy.first().first().value(); + auto internalClient = clientAddedSpy.first().first().value(); QVERIFY(internalClient); QVERIFY(internalClient->effectWindow()); QCOMPARE(internalClient->effectWindow()->internalWindow(), &win); QCOMPARE(effects->findWindow(&win), internalClient->effectWindow()); QCOMPARE(effects->findWindow(&win)->internalWindow(), &win); } } WAYLANDTEST_MAIN(KWin::InternalWindowTest) #include "internal_window.moc" diff --git a/composite.cpp b/composite.cpp index 3bc79a28c..cee8a8d6e 100644 --- a/composite.cpp +++ b/composite.cpp @@ -1,1048 +1,1048 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2006 Lubos Lunak 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 . *********************************************************************/ #include "composite.h" #include "dbusinterface.h" #include "client.h" #include "decorations/decoratedclient.h" #include "deleted.h" #include "effects.h" +#include "internal_client.h" #include "overlaywindow.h" #include "platform.h" #include "scene.h" #include "screens.h" #include "shadow.h" #include "shell_client.h" #include "unmanaged.h" #include "useractions.h" #include "utils.h" #include "wayland_server.h" #include "workspace.h" #include "xcbutils.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include Q_DECLARE_METATYPE(KWin::X11Compositor::SuspendReason) namespace KWin { // See main.cpp: extern int screen_number; extern bool is_multihead; extern int currentRefreshRate(); Compositor *Compositor::s_compositor = nullptr; Compositor *Compositor::self() { return s_compositor; } WaylandCompositor *WaylandCompositor::create(QObject *parent) { Q_ASSERT(!s_compositor); auto *compositor = new WaylandCompositor(parent); s_compositor = compositor; return compositor; } X11Compositor *X11Compositor::create(QObject *parent) { Q_ASSERT(!s_compositor); auto *compositor = new X11Compositor(parent); s_compositor = compositor; return compositor; } class CompositorSelectionOwner : public KSelectionOwner { Q_OBJECT public: CompositorSelectionOwner(const char *selection) : KSelectionOwner(selection, connection(), rootWindow()) , m_owning(false) { connect (this, &CompositorSelectionOwner::lostOwnership, this, [this]() { m_owning = false; }); } bool owning() const { return m_owning; } void setOwning(bool own) { m_owning = own; } private: bool m_owning; }; static inline qint64 milliToNano(int milli) { return qint64(milli) * 1000 * 1000; } static inline qint64 nanoToMilli(int nano) { return nano / (1000*1000); } Compositor::Compositor(QObject* workspace) : QObject(workspace) , m_state(State::Off) , m_selectionOwner(nullptr) , vBlankInterval(0) , fpsInterval(0) , m_timeSinceLastVBlank(0) , m_scene(nullptr) , m_bufferSwapPending(false) , m_composeAtSwapCompletion(false) { connect(options, &Options::configChanged, this, &Compositor::configChanged); m_monotonicClock.start(); // 2 sec which should be enough to restart the compositor. static const int compositorLostMessageDelay = 2000; m_releaseSelectionTimer.setSingleShot(true); m_releaseSelectionTimer.setInterval(compositorLostMessageDelay); connect(&m_releaseSelectionTimer, &QTimer::timeout, this, &Compositor::releaseCompositorSelection); m_unusedSupportPropertyTimer.setInterval(compositorLostMessageDelay); m_unusedSupportPropertyTimer.setSingleShot(true); connect(&m_unusedSupportPropertyTimer, &QTimer::timeout, this, &Compositor::deleteUnusedSupportProperties); // Delay the call to start by one event cycle. // The ctor of this class is invoked from the Workspace ctor, that means before // Workspace is completely constructed, so calling Workspace::self() would result // in undefined behavior. This is fixed by using a delayed invocation. if (kwinApp()->platform()->isReady()) { QTimer::singleShot(0, this, &Compositor::start); } connect(kwinApp()->platform(), &Platform::readyChanged, this, [this] (bool ready) { if (ready) { start(); } else { stop(); } }, Qt::QueuedConnection ); if (qEnvironmentVariableIsSet("KWIN_MAX_FRAMES_TESTED")) m_framesToTestForSafety = qEnvironmentVariableIntValue("KWIN_MAX_FRAMES_TESTED"); // register DBus new CompositorDBusInterface(this); } Compositor::~Compositor() { emit aboutToDestroy(); stop(); deleteUnusedSupportProperties(); destroyCompositorSelection(); s_compositor = nullptr; } bool Compositor::setupStart() { if (kwinApp()->isTerminating()) { // Don't start while KWin is terminating. An event to restart might be lingering // in the event queue due to graphics reset. return false; } if (m_state != State::Off) { return false; } m_state = State::Starting; options->reloadCompositingSettings(true); setupX11Support(); // There might still be a deleted around, needs to be cleared before // creating the scene (BUG 333275). if (Workspace::self()) { while (!Workspace::self()->deletedList().isEmpty()) { Workspace::self()->deletedList().first()->discard(); } } emit aboutToToggleCompositing(); auto supportedCompositors = kwinApp()->platform()->supportedCompositors(); const auto userConfigIt = std::find(supportedCompositors.begin(), supportedCompositors.end(), options->compositingMode()); if (userConfigIt != supportedCompositors.end()) { supportedCompositors.erase(userConfigIt); supportedCompositors.prepend(options->compositingMode()); } else { qCWarning(KWIN_CORE) << "Configured compositor not supported by Platform. Falling back to defaults"; } const auto availablePlugins = KPluginLoader::findPlugins(QStringLiteral("org.kde.kwin.scenes")); for (auto type : qAsConst(supportedCompositors)) { const auto pluginIt = std::find_if(availablePlugins.begin(), availablePlugins.end(), [type] (const auto &plugin) { const auto &metaData = plugin.rawData(); auto it = metaData.find(QStringLiteral("CompositingType")); if (it != metaData.end()) { if ((*it).toInt() == int{type}) { return true; } } return false; }); if (pluginIt != availablePlugins.end()) { std::unique_ptr factory{ qobject_cast(pluginIt->instantiate()) }; if (factory) { m_scene = factory->create(this); if (m_scene) { if (!m_scene->initFailed()) { qCDebug(KWIN_CORE) << "Instantiated compositing plugin:" << pluginIt->name(); break; } else { delete m_scene; m_scene = nullptr; } } } } } if (m_scene == nullptr || m_scene->initFailed()) { qCCritical(KWIN_CORE) << "Failed to initialize compositing, compositing disabled"; m_state = State::Off; delete m_scene; m_scene = nullptr; if (m_selectionOwner) { m_selectionOwner->setOwning(false); m_selectionOwner->release(); } if (!supportedCompositors.contains(NoCompositing)) { qCCritical(KWIN_CORE) << "The used windowing system requires compositing"; qCCritical(KWIN_CORE) << "We are going to quit KWin now as it is broken"; qApp->quit(); } return false; } CompositingType compositingType = m_scene->compositingType(); if (compositingType & OpenGLCompositing) { // Override for OpenGl sub-type OpenGL2Compositing. compositingType = OpenGLCompositing; } kwinApp()->platform()->setSelectedCompositor(compositingType); if (!Workspace::self() && m_scene && m_scene->compositingType() == QPainterCompositing) { // Force Software QtQuick on first startup with QPainter. QQuickWindow::setSceneGraphBackend(QSGRendererInterface::Software); } connect(m_scene, &Scene::resetCompositing, this, &Compositor::reinitialize); emit sceneCreated(); return true; } void Compositor::claimCompositorSelection() { if (!m_selectionOwner) { char selection_name[ 100 ]; sprintf(selection_name, "_NET_WM_CM_S%d", Application::x11ScreenNumber()); m_selectionOwner = new CompositorSelectionOwner(selection_name); connect(m_selectionOwner, &CompositorSelectionOwner::lostOwnership, this, &Compositor::stop); } if (!m_selectionOwner) { // No X11 yet. return; } if (!m_selectionOwner->owning()) { // Force claim ownership. m_selectionOwner->claim(true); m_selectionOwner->setOwning(true); } } void Compositor::setupX11Support() { auto *con = kwinApp()->x11Connection(); if (!con) { delete m_selectionOwner; m_selectionOwner = nullptr; return; } claimCompositorSelection(); xcb_composite_redirect_subwindows(con, kwinApp()->x11RootWindow(), XCB_COMPOSITE_REDIRECT_MANUAL); } void Compositor::startupWithWorkspace() { connect(kwinApp(), &Application::x11ConnectionChanged, this, &Compositor::setupX11Support, Qt::UniqueConnection); Workspace::self()->markXStackingOrderAsDirty(); Q_ASSERT(m_scene); connect(workspace(), &Workspace::destroyed, this, [this] { compositeTimer.stop(); }); setupX11Support(); fpsInterval = options->maxFpsInterval(); if (m_scene->syncsToVBlank()) { // If we do vsync, set the fps to the next multiple of the vblank rate. vBlankInterval = milliToNano(1000) / currentRefreshRate(); fpsInterval = qMax((fpsInterval / vBlankInterval) * vBlankInterval, vBlankInterval); } else { // No vsync - DO NOT set "0", would cause div-by-zero segfaults. vBlankInterval = milliToNano(1); } // Sets also the 'effects' pointer. kwinApp()->platform()->createEffectsHandler(this, m_scene); connect(Workspace::self(), &Workspace::deletedRemoved, m_scene, &Scene::removeToplevel); connect(effects, &EffectsHandler::screenGeometryChanged, this, &Compositor::addRepaintFull); for (Client *c : Workspace::self()->clientList()) { c->setupCompositing(); c->getShadow(); } for (Client *c : Workspace::self()->desktopList()) { c->setupCompositing(); } for (Unmanaged *c : Workspace::self()->unmanagedList()) { c->setupCompositing(); c->getShadow(); } + for (InternalClient *client : workspace()->internalClients()) { + client->setupCompositing(); + client->getShadow(); + } if (auto *server = waylandServer()) { const auto clients = server->clients(); for (ShellClient *c : clients) { c->setupCompositing(); c->getShadow(); } - const auto internalClients = server->internalClients(); - for (ShellClient *c : internalClients) { - c->setupCompositing(); - c->getShadow(); - } } m_state = State::On; emit compositingToggled(true); if (m_releaseSelectionTimer.isActive()) { m_releaseSelectionTimer.stop(); } // Render at least once. addRepaintFull(); performCompositing(); } void Compositor::scheduleRepaint() { if (!compositeTimer.isActive()) setCompositeTimer(); } void Compositor::stop() { if (m_state == State::Off || m_state == State::Stopping) { return; } m_state = State::Stopping; emit aboutToToggleCompositing(); m_releaseSelectionTimer.start(); // Some effects might need access to effect windows when they are about to // be destroyed, for example to unreference deleted windows, so we have to // make sure that effect windows outlive effects. delete effects; effects = nullptr; if (Workspace::self()) { for (Client *c : Workspace::self()->clientList()) { m_scene->removeToplevel(c); } for (Client *c : Workspace::self()->desktopList()) { m_scene->removeToplevel(c); } for (Unmanaged *c : Workspace::self()->unmanagedList()) { m_scene->removeToplevel(c); } + for (InternalClient *client : workspace()->internalClients()) { + m_scene->removeToplevel(client); + } for (Client *c : Workspace::self()->clientList()) { c->finishCompositing(); } for (Client *c : Workspace::self()->desktopList()) { c->finishCompositing(); } for (Unmanaged *c : Workspace::self()->unmanagedList()) { c->finishCompositing(); } + for (InternalClient *client : workspace()->internalClients()) { + client->finishCompositing(); + } if (auto *con = kwinApp()->x11Connection()) { xcb_composite_unredirect_subwindows(con, kwinApp()->x11RootWindow(), XCB_COMPOSITE_REDIRECT_MANUAL); } while (!workspace()->deletedList().isEmpty()) { workspace()->deletedList().first()->discard(); } } if (waylandServer()) { for (ShellClient *c : waylandServer()->clients()) { m_scene->removeToplevel(c); } - for (ShellClient *c : waylandServer()->internalClients()) { - m_scene->removeToplevel(c); - } for (ShellClient *c : waylandServer()->clients()) { c->finishCompositing(); } - for (ShellClient *c : waylandServer()->internalClients()) { - c->finishCompositing(); - } } delete m_scene; m_scene = nullptr; compositeTimer.stop(); repaints_region = QRegion(); m_state = State::Off; emit compositingToggled(false); } void Compositor::destroyCompositorSelection() { delete m_selectionOwner; m_selectionOwner = nullptr; } void Compositor::releaseCompositorSelection() { switch (m_state) { case State::On: // We are compositing at the moment. Don't release. break; case State::Off: if (m_selectionOwner) { qCDebug(KWIN_CORE) << "Releasing compositor selection"; m_selectionOwner->setOwning(false); m_selectionOwner->release(); } break; case State::Starting: case State::Stopping: // Still starting or shutting down the compositor. Starting might fail // or after stopping a restart might follow. So test again later on. m_releaseSelectionTimer.start(); break; } } void Compositor::keepSupportProperty(xcb_atom_t atom) { m_unusedSupportProperties.removeAll(atom); } void Compositor::removeSupportProperty(xcb_atom_t atom) { m_unusedSupportProperties << atom; m_unusedSupportPropertyTimer.start(); } void Compositor::deleteUnusedSupportProperties() { if (m_state == State::Starting || m_state == State::Stopping) { // Currently still maybe restarting the compositor. m_unusedSupportPropertyTimer.start(); return; } if (auto *con = kwinApp()->x11Connection()) { for (const xcb_atom_t &atom : qAsConst(m_unusedSupportProperties)) { // remove property from root window xcb_delete_property(con, kwinApp()->x11RootWindow(), atom); } m_unusedSupportProperties.clear(); } } void Compositor::configChanged() { reinitialize(); addRepaintFull(); } void Compositor::reinitialize() { // Reparse config. Config options will be reloaded by start() kwinApp()->config()->reparseConfiguration(); // Restart compositing stop(); start(); if (effects) { // start() may fail effects->reconfigure(); } } void Compositor::addRepaint(int x, int y, int w, int h) { if (m_state != State::On) { return; } repaints_region += QRegion(x, y, w, h); scheduleRepaint(); } void Compositor::addRepaint(const QRect& r) { if (m_state != State::On) { return; } repaints_region += r; scheduleRepaint(); } void Compositor::addRepaint(const QRegion& r) { if (m_state != State::On) { return; } repaints_region += r; scheduleRepaint(); } void Compositor::addRepaintFull() { if (m_state != State::On) { return; } const QSize &s = screens()->size(); repaints_region = QRegion(0, 0, s.width(), s.height()); scheduleRepaint(); } void Compositor::timerEvent(QTimerEvent *te) { if (te->timerId() == compositeTimer.timerId()) { performCompositing(); } else QObject::timerEvent(te); } void Compositor::aboutToSwapBuffers() { Q_ASSERT(!m_bufferSwapPending); m_bufferSwapPending = true; } void Compositor::bufferSwapComplete() { Q_ASSERT(m_bufferSwapPending); m_bufferSwapPending = false; emit bufferSwapCompleted(); if (m_composeAtSwapCompletion) { m_composeAtSwapCompletion = false; performCompositing(); } } void Compositor::performCompositing() { // If a buffer swap is still pending, we return to the event loop and // continue processing events until the swap has completed. if (m_bufferSwapPending) { m_composeAtSwapCompletion = true; compositeTimer.stop(); return; } // If outputs are disabled, we return to the event loop and // continue processing events until the outputs are enabled again if (!kwinApp()->platform()->areOutputsEnabled()) { compositeTimer.stop(); return; } // Create a list of all windows in the stacking order ToplevelList windows = Workspace::self()->xStackingOrder(); ToplevelList damaged; // Reset the damage state of each window and fetch the damage region // without waiting for a reply for (Toplevel *win : windows) { if (win->resetAndFetchDamage()) { damaged << win; } } if (damaged.count() > 0) { m_scene->triggerFence(); if (auto c = kwinApp()->x11Connection()) { xcb_flush(c); } } // Move elevated windows to the top of the stacking order for (EffectWindow *c : static_cast(effects)->elevatedWindows()) { Toplevel *t = static_cast(c)->window(); windows.removeAll(t); windows.append(t); } // Get the replies for (Toplevel *win : damaged) { // Discard the cached lanczos texture if (win->effectWindow()) { const QVariant texture = win->effectWindow()->data(LanczosCacheRole); if (texture.isValid()) { delete static_cast(texture.value()); win->effectWindow()->setData(LanczosCacheRole, QVariant()); } } win->getDamageRegionReply(); } if (repaints_region.isEmpty() && !windowRepaintsPending()) { m_scene->idle(); m_timeSinceLastVBlank = fpsInterval - (options->vBlankTime() + 1); // means "start now" // Note: It would seem here we should undo suspended unredirect, but when scenes need // it for some reason, e.g. transformations or translucency, the next pass that does not // need this anymore and paints normally will also reset the suspended unredirect. // Otherwise the window would not be painted normally anyway. compositeTimer.stop(); return; } // Skip windows that are not yet ready for being painted and if screen is locked skip windows // that are neither lockscreen nor inputmethod windows. // // TODO? This cannot be used so carelessly - needs protections against broken clients, the // window should not get focus before it's displayed, handle unredirected windows properly and // so on. for (Toplevel *win : windows) { if (!win->readyForPainting()) { windows.removeAll(win); } if (waylandServer() && waylandServer()->isScreenLocked()) { if(!win->isLockScreen() && !win->isInputMethod()) { windows.removeAll(win); } } } QRegion repaints = repaints_region; // clear all repaints, so that post-pass can add repaints for the next repaint repaints_region = QRegion(); if (m_framesToTestForSafety > 0 && (m_scene->compositingType() & OpenGLCompositing)) { kwinApp()->platform()->createOpenGLSafePoint(Platform::OpenGLSafePoint::PreFrame); } m_timeSinceLastVBlank = m_scene->paint(repaints, windows); if (m_framesToTestForSafety > 0) { if (m_scene->compositingType() & OpenGLCompositing) { kwinApp()->platform()->createOpenGLSafePoint(Platform::OpenGLSafePoint::PostFrame); } m_framesToTestForSafety--; if (m_framesToTestForSafety == 0 && (m_scene->compositingType() & OpenGLCompositing)) { kwinApp()->platform()->createOpenGLSafePoint( Platform::OpenGLSafePoint::PostLastGuardedFrame); } } if (waylandServer()) { const auto currentTime = static_cast(m_monotonicClock.elapsed()); for (Toplevel *win : qAsConst(windows)) { if (auto surface = win->surface()) { surface->frameRendered(currentTime); } } } // Stop here to ensure *we* cause the next repaint schedule - not some effect // through m_scene->paint(). compositeTimer.stop(); // Trigger at least one more pass even if there would be nothing to paint, so that scene->idle() // is called the next time. If there would be nothing pending, it will not restart the timer and // scheduleRepaint() would restart it again somewhen later, called from functions that // would again add something pending. if (m_bufferSwapPending && m_scene->syncsToVBlank()) { m_composeAtSwapCompletion = true; } else { scheduleRepaint(); } } template static bool repaintsPending(const QList &windows) { return std::any_of(windows.begin(), windows.end(), [](T *t) { return !t->repaints().isEmpty(); }); } bool Compositor::windowRepaintsPending() const { if (repaintsPending(Workspace::self()->clientList())) { return true; } if (repaintsPending(Workspace::self()->desktopList())) { return true; } if (repaintsPending(Workspace::self()->unmanagedList())) { return true; } if (repaintsPending(Workspace::self()->deletedList())) { return true; } if (auto *server = waylandServer()) { const auto &clients = server->clients(); auto test = [](ShellClient *c) { return c->readyForPainting() && !c->repaints().isEmpty(); }; if (std::any_of(clients.begin(), clients.end(), test)) { return true; } - const auto &internalClients = server->internalClients(); - auto internalTest = [](ShellClient *c) { - return c->isShown(true) && !c->repaints().isEmpty(); - }; - if (std::any_of(internalClients.begin(), internalClients.end(), internalTest)) { - return true; - } + } + const auto &internalClients = workspace()->internalClients(); + auto internalTest = [] (InternalClient *client) { + return client->isShown(true) && !client->repaints().isEmpty(); + }; + if (std::any_of(internalClients.begin(), internalClients.end(), internalTest)) { + return true; } return false; } void Compositor::setCompositeTimer() { if (m_state != State::On) { return; } // Don't start the timer if we're waiting for a swap event if (m_bufferSwapPending && m_composeAtSwapCompletion) return; // Don't start the timer if all outputs are disabled if (!kwinApp()->platform()->areOutputsEnabled()) { return; } uint waitTime = 1; if (m_scene->blocksForRetrace()) { // TODO: make vBlankTime dynamic?! // It's required because glXWaitVideoSync will *likely* block a full frame if one enters // a retrace pass which can last a variable amount of time, depending on the actual screen // Now, my ooold 19" CRT can do such retrace so that 2ms are entirely sufficient, // while another ooold 15" TFT requires about 6ms qint64 padding = m_timeSinceLastVBlank; if (padding > fpsInterval) { // We're at low repaints or spent more time in painting than the user wanted to wait // for that frame. Align to next vblank: padding = vBlankInterval - (padding % vBlankInterval); } else { // Align to the next maxFps tick: // "remaining time of the first vsync" + "time for the other vsyncs of the frame" padding = ((vBlankInterval - padding % vBlankInterval) + (fpsInterval / vBlankInterval - 1) * vBlankInterval); } if (padding < options->vBlankTime()) { // We'll likely miss this frame so we add one: waitTime = nanoToMilli(padding + vBlankInterval - options->vBlankTime()); } else { waitTime = nanoToMilli(padding - options->vBlankTime()); } } else { // w/o blocking vsync we just jump to the next demanded tick if (fpsInterval > m_timeSinceLastVBlank) { waitTime = nanoToMilli(fpsInterval - m_timeSinceLastVBlank); if (!waitTime) { // Will ensure we don't block out the eventloop - the system's just not faster ... waitTime = 1; } } /* else if (m_scene->syncsToVBlank() && m_timeSinceLastVBlank - fpsInterval < (vBlankInterval<<1)) { // NOTICE - "for later" ------------------------------------------------------------------ // It can happen that we push two frames within one refresh cycle. // Swapping will then block even with triple buffering when the GPU does not discard but // queues frames // now here's the mean part: if we take that as "OMG, we're late - next frame ASAP", // there'll immediately be 2 frames in the pipe, swapping will block, we think we're // late ... ewww // so instead we pad to the clock again and add 2ms safety to ensure the pipe is really // free // NOTICE: obviously m_timeSinceLastVBlank can be too big because we're too slow as well // So if this code was enabled, we'd needlessly half the framerate once more (15 instead of 30) waitTime = nanoToMilli(vBlankInterval - (m_timeSinceLastVBlank - fpsInterval)%vBlankInterval) + 2; }*/ else { // "0" would be sufficient here, but the compositor isn't the WMs only task. waitTime = 1; } } // Force 4fps minimum: compositeTimer.start(qMin(waitTime, 250u), this); } bool Compositor::isActive() { return m_state == State::On; } WaylandCompositor::WaylandCompositor(QObject *parent) : Compositor(parent) { connect(kwinApp(), &Application::x11ConnectionAboutToBeDestroyed, this, &WaylandCompositor::destroyCompositorSelection); } void WaylandCompositor::toggleCompositing() { // For the shortcut. Not possible on Wayland because we always composite. } void WaylandCompositor::start() { if (!Compositor::setupStart()) { // Internal setup failed, abort. return; } if (Workspace::self()) { startupWithWorkspace(); } else { connect(kwinApp(), &Application::workspaceCreated, this, &WaylandCompositor::startupWithWorkspace); } } int WaylandCompositor::refreshRate() const { // TODO: This makes no sense on Wayland. First step would be to atleast // set the refresh rate to the highest available one. Second step // would be to not use a uniform value at all but per screen. return KWin::currentRefreshRate(); } X11Compositor::X11Compositor(QObject *parent) : Compositor(parent) , m_suspended(options->isUseCompositing() ? NoReasonSuspend : UserSuspend) , m_xrrRefreshRate(0) { qRegisterMetaType("X11Compositor::SuspendReason"); } void X11Compositor::toggleCompositing() { if (m_suspended) { // Direct user call; clear all bits. resume(AllReasonSuspend); } else { // But only set the user one (sufficient to suspend). suspend(UserSuspend); } } void X11Compositor::reinitialize() { // Resume compositing if suspended. m_suspended = NoReasonSuspend; Compositor::reinitialize(); } void X11Compositor::configChanged() { if (m_suspended) { stop(); return; } Compositor::configChanged(); } void X11Compositor::suspend(X11Compositor::SuspendReason reason) { Q_ASSERT(reason != NoReasonSuspend); m_suspended |= reason; if (reason & ScriptSuspend) { // When disabled show a shortcut how the user can get back compositing. const auto shortcuts = KGlobalAccel::self()->shortcut( workspace()->findChild(QStringLiteral("Suspend Compositing"))); if (!shortcuts.isEmpty()) { // Display notification only if there is the shortcut. const QString message = i18n("Desktop effects have been suspended by another application.
" "You can resume using the '%1' shortcut.", shortcuts.first().toString(QKeySequence::NativeText)); KNotification::event(QStringLiteral("compositingsuspendeddbus"), message); } } stop(); } void X11Compositor::resume(X11Compositor::SuspendReason reason) { Q_ASSERT(reason != NoReasonSuspend); m_suspended &= ~reason; start(); } void X11Compositor::start() { if (m_suspended) { QStringList reasons; if (m_suspended & UserSuspend) { reasons << QStringLiteral("Disabled by User"); } if (m_suspended & BlockRuleSuspend) { reasons << QStringLiteral("Disabled by Window"); } if (m_suspended & ScriptSuspend) { reasons << QStringLiteral("Disabled by Script"); } qCDebug(KWIN_CORE) << "Compositing is suspended, reason:" << reasons; return; } else if (!kwinApp()->platform()->compositingPossible()) { qCCritical(KWIN_CORE) << "Compositing is not possible"; return; } if (!Compositor::setupStart()) { // Internal setup failed, abort. return; } m_xrrRefreshRate = KWin::currentRefreshRate(); startupWithWorkspace(); } void X11Compositor::performCompositing() { if (scene()->usesOverlayWindow() && !isOverlayWindowVisible()) { // Return since nothing is visible. return; } Compositor::performCompositing(); } bool X11Compositor::checkForOverlayWindow(WId w) const { if (!scene()) { // No scene, so it cannot be the overlay window. return false; } if (!scene()->overlayWindow()) { // No overlay window, it cannot be the overlay. return false; } // Compare the window ID's. return w == scene()->overlayWindow()->window(); } bool X11Compositor::isOverlayWindowVisible() const { if (!scene()) { return false; } if (!scene()->overlayWindow()) { return false; } return scene()->overlayWindow()->isVisible(); } int X11Compositor::refreshRate() const { return m_xrrRefreshRate; } void X11Compositor::updateClientCompositeBlocking(Client *c) { if (c) { if (c->isBlockingCompositing()) { // Do NOT attempt to call suspend(true) from within the eventchain! if (!(m_suspended & BlockRuleSuspend)) QMetaObject::invokeMethod(this, "suspend", Qt::QueuedConnection, Q_ARG(SuspendReason, BlockRuleSuspend)); } } else if (m_suspended & BlockRuleSuspend) { // If !c we just check if we can resume in case a blocking client was lost. bool resume = true; for (ClientList::ConstIterator it = Workspace::self()->clientList().constBegin(); it != Workspace::self()->clientList().constEnd(); ++it) { if ((*it)->isBlockingCompositing()) { resume = false; break; } } if (resume) { // Do NOT attempt to call suspend(false) from within the eventchain! QMetaObject::invokeMethod(this, "resume", Qt::QueuedConnection, Q_ARG(SuspendReason, BlockRuleSuspend)); } } } X11Compositor *X11Compositor::self() { return qobject_cast(Compositor::self()); } } // included for CompositorSelectionOwner #include "composite.moc" diff --git a/debug_console.cpp b/debug_console.cpp index 48c170d09..fcebd2b65 100644 --- a/debug_console.cpp +++ b/debug_console.cpp @@ -1,1528 +1,1514 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2016 Martin Gräßlin 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 . *********************************************************************/ #include "debug_console.h" #include "composite.h" #include "client.h" #include "input_event.h" +#include "internal_client.h" #include "main.h" #include "scene.h" #include "shell_client.h" #include "unmanaged.h" #include "wayland_server.h" #include "workspace.h" #include "keyboard_input.h" #include "libinput/connection.h" #include "libinput/device.h" #include #include #include "ui_debug_console.h" // KWayland #include #include #include #include // frameworks #include #include // Qt #include #include #include // xkb #include #include namespace KWin { static QString tableHeaderRow(const QString &title) { return QStringLiteral("%1").arg(title); } template static QString tableRow(const QString &title, const T &argument) { return QStringLiteral("%1%2").arg(title).arg(argument); } static QString timestampRow(quint32 timestamp) { return tableRow(i18n("Timestamp"), timestamp); } static QString timestampRowUsec(quint64 timestamp) { return tableRow(i18n("Timestamp (µsec)"), timestamp); } static QString buttonToString(Qt::MouseButton button) { switch (button) { case Qt::LeftButton: return i18nc("A mouse button", "Left"); case Qt::RightButton: return i18nc("A mouse button", "Right"); case Qt::MiddleButton: return i18nc("A mouse button", "Middle"); case Qt::BackButton: return i18nc("A mouse button", "Back"); case Qt::ForwardButton: return i18nc("A mouse button", "Forward"); case Qt::TaskButton: return i18nc("A mouse button", "Task"); case Qt::ExtraButton4: return i18nc("A mouse button", "Extra Button 4"); case Qt::ExtraButton5: return i18nc("A mouse button", "Extra Button 5"); case Qt::ExtraButton6: return i18nc("A mouse button", "Extra Button 6"); case Qt::ExtraButton7: return i18nc("A mouse button", "Extra Button 7"); case Qt::ExtraButton8: return i18nc("A mouse button", "Extra Button 8"); case Qt::ExtraButton9: return i18nc("A mouse button", "Extra Button 9"); case Qt::ExtraButton10: return i18nc("A mouse button", "Extra Button 10"); case Qt::ExtraButton11: return i18nc("A mouse button", "Extra Button 11"); case Qt::ExtraButton12: return i18nc("A mouse button", "Extra Button 12"); case Qt::ExtraButton13: return i18nc("A mouse button", "Extra Button 13"); case Qt::ExtraButton14: return i18nc("A mouse button", "Extra Button 14"); case Qt::ExtraButton15: return i18nc("A mouse button", "Extra Button 15"); case Qt::ExtraButton16: return i18nc("A mouse button", "Extra Button 16"); case Qt::ExtraButton17: return i18nc("A mouse button", "Extra Button 17"); case Qt::ExtraButton18: return i18nc("A mouse button", "Extra Button 18"); case Qt::ExtraButton19: return i18nc("A mouse button", "Extra Button 19"); case Qt::ExtraButton20: return i18nc("A mouse button", "Extra Button 20"); case Qt::ExtraButton21: return i18nc("A mouse button", "Extra Button 21"); case Qt::ExtraButton22: return i18nc("A mouse button", "Extra Button 22"); case Qt::ExtraButton23: return i18nc("A mouse button", "Extra Button 23"); case Qt::ExtraButton24: return i18nc("A mouse button", "Extra Button 24"); default: return QString(); } } static QString deviceRow(LibInput::Device *device) { if (!device) { return tableRow(i18n("Input Device"), i18nc("The input device of the event is not known", "Unknown")); } return tableRow(i18n("Input Device"), QStringLiteral("%1 (%2)").arg(device->name()).arg(device->sysName())); } static QString buttonsToString(Qt::MouseButtons buttons) { QString ret; for (uint i = 1; i < Qt::ExtraButton24; i = i << 1) { if (buttons & i) { ret.append(buttonToString(Qt::MouseButton(uint(buttons) & i))); ret.append(QStringLiteral(" ")); } }; return ret.trimmed(); } static const QString s_hr = QStringLiteral("
"); static const QString s_tableStart = QStringLiteral(""); static const QString s_tableEnd = QStringLiteral("
"); DebugConsoleFilter::DebugConsoleFilter(QTextEdit *textEdit) : InputEventSpy() , m_textEdit(textEdit) { } DebugConsoleFilter::~DebugConsoleFilter() = default; void DebugConsoleFilter::pointerEvent(MouseEvent *event) { QString text = s_hr; const QString timestamp = timestampRow(event->timestamp()); text.append(s_tableStart); switch (event->type()) { case QEvent::MouseMove: { text.append(tableHeaderRow(i18nc("A mouse pointer motion event", "Pointer Motion"))); text.append(deviceRow(event->device())); text.append(timestamp); if (event->timestampMicroseconds() != 0) { text.append(timestampRowUsec(event->timestampMicroseconds())); } if (event->delta() != QSizeF()) { text.append(tableRow(i18nc("The relative mouse movement", "Delta"), QStringLiteral("%1/%2").arg(event->delta().width()).arg(event->delta().height()))); } if (event->deltaUnaccelerated() != QSizeF()) { text.append(tableRow(i18nc("The relative mouse movement", "Delta (not accelerated)"), QStringLiteral("%1/%2").arg(event->deltaUnaccelerated().width()).arg(event->deltaUnaccelerated().height()))); } text.append(tableRow(i18nc("The global mouse pointer position", "Global Position"), QStringLiteral("%1/%2").arg(event->pos().x()).arg(event->pos().y()))); break; } case QEvent::MouseButtonPress: text.append(tableHeaderRow(i18nc("A mouse pointer button press event", "Pointer Button Press"))); text.append(deviceRow(event->device())); text.append(timestamp); text.append(tableRow(i18nc("A button in a mouse press/release event", "Button"), buttonToString(event->button()))); text.append(tableRow(i18nc("A button in a mouse press/release event", "Native Button code"), event->nativeButton())); text.append(tableRow(i18nc("All currently pressed buttons in a mouse press/release event", "Pressed Buttons"), buttonsToString(event->buttons()))); break; case QEvent::MouseButtonRelease: text.append(tableHeaderRow(i18nc("A mouse pointer button release event", "Pointer Button Release"))); text.append(deviceRow(event->device())); text.append(timestamp); text.append(tableRow(i18nc("A button in a mouse press/release event", "Button"), buttonToString(event->button()))); text.append(tableRow(i18nc("A button in a mouse press/release event", "Native Button code"), event->nativeButton())); text.append(tableRow(i18nc("All currently pressed buttons in a mouse press/release event", "Pressed Buttons"), buttonsToString(event->buttons()))); break; default: break; } text.append(s_tableEnd); m_textEdit->insertHtml(text); m_textEdit->ensureCursorVisible(); } void DebugConsoleFilter::wheelEvent(WheelEvent *event) { QString text = s_hr; text.append(s_tableStart); text.append(tableHeaderRow(i18nc("A mouse pointer axis (wheel) event", "Pointer Axis"))); text.append(deviceRow(event->device())); text.append(timestampRow(event->timestamp())); const Qt::Orientation orientation = event->angleDelta().x() == 0 ? Qt::Vertical : Qt::Horizontal; text.append(tableRow(i18nc("The orientation of a pointer axis event", "Orientation"), orientation == Qt::Horizontal ? i18nc("An orientation of a pointer axis event", "Horizontal") : i18nc("An orientation of a pointer axis event", "Vertical"))); text.append(tableRow(i18nc("The angle delta of a pointer axis event", "Delta"), orientation == Qt::Horizontal ? event->angleDelta().x() : event->angleDelta().y())); text.append(s_tableEnd); m_textEdit->insertHtml(text); m_textEdit->ensureCursorVisible(); } void DebugConsoleFilter::keyEvent(KeyEvent *event) { QString text = s_hr; text.append(s_tableStart); switch (event->type()) { case QEvent::KeyPress: text.append(tableHeaderRow(i18nc("A key press event", "Key Press"))); break; case QEvent::KeyRelease: text.append(tableHeaderRow(i18nc("A key release event", "Key Release"))); break; default: break; } text.append(deviceRow(event->device())); auto modifiersToString = [event] { QString ret; if (event->modifiers().testFlag(Qt::ShiftModifier)) { ret.append(i18nc("A keyboard modifier", "Shift")); ret.append(QStringLiteral(" ")); } if (event->modifiers().testFlag(Qt::ControlModifier)) { ret.append(i18nc("A keyboard modifier", "Control")); ret.append(QStringLiteral(" ")); } if (event->modifiers().testFlag(Qt::AltModifier)) { ret.append(i18nc("A keyboard modifier", "Alt")); ret.append(QStringLiteral(" ")); } if (event->modifiers().testFlag(Qt::MetaModifier)) { ret.append(i18nc("A keyboard modifier", "Meta")); ret.append(QStringLiteral(" ")); } if (event->modifiers().testFlag(Qt::KeypadModifier)) { ret.append(i18nc("A keyboard modifier", "Keypad")); ret.append(QStringLiteral(" ")); } if (event->modifiers().testFlag(Qt::GroupSwitchModifier)) { ret.append(i18nc("A keyboard modifier", "Group-switch")); ret.append(QStringLiteral(" ")); } return ret; }; text.append(timestampRow(event->timestamp())); text.append(tableRow(i18nc("Whether the event is an automatic key repeat", "Repeat"), event->isAutoRepeat())); text.append(tableRow(i18nc("The code as read from the input device", "Scan code"), event->nativeScanCode())); text.append(tableRow(i18nc("The translated code to an Xkb symbol", "Xkb symbol"), event->nativeVirtualKey())); text.append(tableRow(i18nc("The translated code interpreted as text", "Utf8"), event->text())); text.append(tableRow(i18nc("The currently active modifiers", "Modifiers"), modifiersToString())); text.append(s_tableEnd); m_textEdit->insertHtml(text); m_textEdit->ensureCursorVisible(); } void DebugConsoleFilter::touchDown(qint32 id, const QPointF &pos, quint32 time) { QString text = s_hr; text.append(s_tableStart); text.append(tableHeaderRow(i18nc("A touch down event", "Touch down"))); text.append(timestampRow(time)); text.append(tableRow(i18nc("The id of the touch point in the touch event", "Point identifier"), id)); text.append(tableRow(i18nc("The global position of the touch point", "Global position"), QStringLiteral("%1/%2").arg(pos.x()).arg(pos.y()))); text.append(s_tableEnd); m_textEdit->insertHtml(text); m_textEdit->ensureCursorVisible(); } void DebugConsoleFilter::touchMotion(qint32 id, const QPointF &pos, quint32 time) { QString text = s_hr; text.append(s_tableStart); text.append(tableHeaderRow(i18nc("A touch motion event", "Touch Motion"))); text.append(timestampRow(time)); text.append(tableRow(i18nc("The id of the touch point in the touch event", "Point identifier"), id)); text.append(tableRow(i18nc("The global position of the touch point", "Global position"), QStringLiteral("%1/%2").arg(pos.x()).arg(pos.y()))); text.append(s_tableEnd); m_textEdit->insertHtml(text); m_textEdit->ensureCursorVisible(); } void DebugConsoleFilter::touchUp(qint32 id, quint32 time) { QString text = s_hr; text.append(s_tableStart); text.append(tableHeaderRow(i18nc("A touch up event", "Touch Up"))); text.append(timestampRow(time)); text.append(tableRow(i18nc("The id of the touch point in the touch event", "Point identifier"), id)); text.append(s_tableEnd); m_textEdit->insertHtml(text); m_textEdit->ensureCursorVisible(); } void DebugConsoleFilter::pinchGestureBegin(int fingerCount, quint32 time) { QString text = s_hr; text.append(s_tableStart); text.append(tableHeaderRow(i18nc("A pinch gesture is started", "Pinch start"))); text.append(timestampRow(time)); text.append(tableRow(i18nc("Number of fingers in this pinch gesture", "Finger count"), fingerCount)); text.append(s_tableEnd); m_textEdit->insertHtml(text); m_textEdit->ensureCursorVisible(); } void DebugConsoleFilter::pinchGestureUpdate(qreal scale, qreal angleDelta, const QSizeF &delta, quint32 time) { QString text = s_hr; text.append(s_tableStart); text.append(tableHeaderRow(i18nc("A pinch gesture is updated", "Pinch update"))); text.append(timestampRow(time)); text.append(tableRow(i18nc("Current scale in pinch gesture", "Scale"), scale)); text.append(tableRow(i18nc("Current angle in pinch gesture", "Angle delta"), angleDelta)); text.append(tableRow(i18nc("Current delta in pinch gesture", "Delta x"), delta.width())); text.append(tableRow(i18nc("Current delta in pinch gesture", "Delta y"), delta.height())); text.append(s_tableEnd); m_textEdit->insertHtml(text); m_textEdit->ensureCursorVisible(); } void DebugConsoleFilter::pinchGestureEnd(quint32 time) { QString text = s_hr; text.append(s_tableStart); text.append(tableHeaderRow(i18nc("A pinch gesture ended", "Pinch end"))); text.append(timestampRow(time)); text.append(s_tableEnd); m_textEdit->insertHtml(text); m_textEdit->ensureCursorVisible(); } void DebugConsoleFilter::pinchGestureCancelled(quint32 time) { QString text = s_hr; text.append(s_tableStart); text.append(tableHeaderRow(i18nc("A pinch gesture got cancelled", "Pinch cancelled"))); text.append(timestampRow(time)); text.append(s_tableEnd); m_textEdit->insertHtml(text); m_textEdit->ensureCursorVisible(); } void DebugConsoleFilter::swipeGestureBegin(int fingerCount, quint32 time) { QString text = s_hr; text.append(s_tableStart); text.append(tableHeaderRow(i18nc("A swipe gesture is started", "Swipe start"))); text.append(timestampRow(time)); text.append(tableRow(i18nc("Number of fingers in this swipe gesture", "Finger count"), fingerCount)); text.append(s_tableEnd); m_textEdit->insertHtml(text); m_textEdit->ensureCursorVisible(); } void DebugConsoleFilter::swipeGestureUpdate(const QSizeF &delta, quint32 time) { QString text = s_hr; text.append(s_tableStart); text.append(tableHeaderRow(i18nc("A swipe gesture is updated", "Swipe update"))); text.append(timestampRow(time)); text.append(tableRow(i18nc("Current delta in swipe gesture", "Delta x"), delta.width())); text.append(tableRow(i18nc("Current delta in swipe gesture", "Delta y"), delta.height())); text.append(s_tableEnd); m_textEdit->insertHtml(text); m_textEdit->ensureCursorVisible(); } void DebugConsoleFilter::swipeGestureEnd(quint32 time) { QString text = s_hr; text.append(s_tableStart); text.append(tableHeaderRow(i18nc("A swipe gesture ended", "Swipe end"))); text.append(timestampRow(time)); text.append(s_tableEnd); m_textEdit->insertHtml(text); m_textEdit->ensureCursorVisible(); } void DebugConsoleFilter::swipeGestureCancelled(quint32 time) { QString text = s_hr; text.append(s_tableStart); text.append(tableHeaderRow(i18nc("A swipe gesture got cancelled", "Swipe cancelled"))); text.append(timestampRow(time)); text.append(s_tableEnd); m_textEdit->insertHtml(text); m_textEdit->ensureCursorVisible(); } void DebugConsoleFilter::switchEvent(SwitchEvent *event) { QString text = s_hr; text.append(s_tableStart); text.append(tableHeaderRow(i18nc("A hardware switch (e.g. notebook lid) got toggled", "Switch toggled"))); text.append(timestampRow(event->timestamp())); if (event->timestampMicroseconds() != 0) { text.append(timestampRowUsec(event->timestampMicroseconds())); } text.append(deviceRow(event->device())); QString switchName; if (event->device()->isLidSwitch()) { switchName = i18nc("Name of a hardware switch", "Notebook lid"); } else if (event->device()->isTabletModeSwitch()) { switchName = i18nc("Name of a hardware switch", "Tablet mode"); } text.append(tableRow(i18nc("A hardware switch", "Switch"), switchName)); QString switchState; switch (event->state()) { case SwitchEvent::State::Off: switchState = i18nc("The hardware switch got turned off", "Off"); break; case SwitchEvent::State::On: switchState = i18nc("The hardware switch got turned on", "On"); break; default: Q_UNREACHABLE(); } text.append(tableRow(i18nc("State of a hardware switch (on/off)", "State"), switchState)); text.append(s_tableEnd); m_textEdit->insertHtml(text); m_textEdit->ensureCursorVisible(); } DebugConsole::DebugConsole() : QWidget() , m_ui(new Ui::DebugConsole) { setAttribute(Qt::WA_ShowWithoutActivating); m_ui->setupUi(this); m_ui->windowsView->setItemDelegate(new DebugConsoleDelegate(this)); m_ui->windowsView->setModel(new DebugConsoleModel(this)); m_ui->surfacesView->setModel(new SurfaceTreeModel(this)); if (kwinApp()->usesLibinput()) { m_ui->inputDevicesView->setModel(new InputDeviceModel(this)); m_ui->inputDevicesView->setItemDelegate(new DebugConsoleDelegate(this)); } m_ui->quitButton->setIcon(QIcon::fromTheme(QStringLiteral("application-exit"))); m_ui->tabWidget->setTabIcon(0, QIcon::fromTheme(QStringLiteral("view-list-tree"))); m_ui->tabWidget->setTabIcon(1, QIcon::fromTheme(QStringLiteral("view-list-tree"))); if (kwinApp()->operationMode() == Application::OperationMode::OperationModeX11) { m_ui->tabWidget->setTabEnabled(1, false); m_ui->tabWidget->setTabEnabled(2, false); } if (!kwinApp()->usesLibinput()) { m_ui->tabWidget->setTabEnabled(3, false); } connect(m_ui->quitButton, &QAbstractButton::clicked, this, &DebugConsole::deleteLater); connect(m_ui->tabWidget, &QTabWidget::currentChanged, this, [this] (int index) { // delay creation of input event filter until the tab is selected if (index == 2 && m_inputFilter.isNull()) { m_inputFilter.reset(new DebugConsoleFilter(m_ui->inputTextEdit)); input()->installInputEventSpy(m_inputFilter.data()); } if (index == 5) { updateKeyboardTab(); connect(input(), &InputRedirection::keyStateChanged, this, &DebugConsole::updateKeyboardTab); } } ); // for X11 setWindowFlags(Qt::X11BypassWindowManagerHint); initGLTab(); } DebugConsole::~DebugConsole() = default; void DebugConsole::initGLTab() { if (!effects || !effects->isOpenGLCompositing()) { m_ui->noOpenGLLabel->setVisible(true); m_ui->glInfoScrollArea->setVisible(false); return; } GLPlatform *gl = GLPlatform::instance(); m_ui->noOpenGLLabel->setVisible(false); m_ui->glInfoScrollArea->setVisible(true); m_ui->glVendorStringLabel->setText(QString::fromLocal8Bit(gl->glVendorString())); m_ui->glRendererStringLabel->setText(QString::fromLocal8Bit(gl->glRendererString())); m_ui->glVersionStringLabel->setText(QString::fromLocal8Bit(gl->glVersionString())); m_ui->glslVersionStringLabel->setText(QString::fromLocal8Bit(gl->glShadingLanguageVersionString())); m_ui->glDriverLabel->setText(GLPlatform::driverToString(gl->driver())); m_ui->glGPULabel->setText(GLPlatform::chipClassToString(gl->chipClass())); m_ui->glVersionLabel->setText(GLPlatform::versionToString(gl->glVersion())); m_ui->glslLabel->setText(GLPlatform::versionToString(gl->glslVersion())); auto extensionsString = [] (const auto &extensions) { QString text = QStringLiteral("
    "); for (auto extension : extensions) { text.append(QStringLiteral("
  • %1
  • ").arg(QString::fromLocal8Bit(extension))); } text.append(QStringLiteral("
")); return text; }; m_ui->platformExtensionsLabel->setText(extensionsString(Compositor::self()->scene()->openGLPlatformInterfaceExtensions())); m_ui->openGLExtensionsLabel->setText(extensionsString(openGLExtensions())); } template QString keymapComponentToString(xkb_keymap *map, const T &count, std::function f) { QString text = QStringLiteral("
    "); for (T i = 0; i < count; i++) { text.append(QStringLiteral("
  • %1
  • ").arg(QString::fromLocal8Bit(f(map, i)))); } text.append(QStringLiteral("
")); return text; } template QString stateActiveComponents(xkb_state *state, const T &count, std::function f, std::function name) { QString text = QStringLiteral("
    "); xkb_keymap *map = xkb_state_get_keymap(state); for (T i = 0; i < count; i++) { if (f(state, i) == 1) { text.append(QStringLiteral("
  • %1
  • ").arg(QString::fromLocal8Bit(name(map, i)))); } } text.append(QStringLiteral("
")); return text; } void DebugConsole::updateKeyboardTab() { auto xkb = input()->keyboard()->xkb(); xkb_keymap *map = xkb->keymap(); xkb_state *state = xkb->state(); m_ui->layoutsLabel->setText(keymapComponentToString(map, xkb_keymap_num_layouts(map), &xkb_keymap_layout_get_name)); m_ui->currentLayoutLabel->setText(xkb_keymap_layout_get_name(map, xkb->currentLayout())); m_ui->modifiersLabel->setText(keymapComponentToString(map, xkb_keymap_num_mods(map), &xkb_keymap_mod_get_name)); m_ui->ledsLabel->setText(keymapComponentToString(map, xkb_keymap_num_leds(map), &xkb_keymap_led_get_name)); m_ui->activeLedsLabel->setText(stateActiveComponents(state, xkb_keymap_num_leds(map), &xkb_state_led_index_is_active, &xkb_keymap_led_get_name)); using namespace std::placeholders; auto modActive = std::bind(xkb_state_mod_index_is_active, _1, _2, XKB_STATE_MODS_EFFECTIVE); m_ui->activeModifiersLabel->setText(stateActiveComponents(state, xkb_keymap_num_mods(map), modActive, &xkb_keymap_mod_get_name)); } void DebugConsole::showEvent(QShowEvent *event) { QWidget::showEvent(event); // delay the connection to the show event as in ctor the windowHandle returns null connect(windowHandle(), &QWindow::visibleChanged, this, [this] (bool visible) { if (visible) { // ignore return; } deleteLater(); } ); } DebugConsoleDelegate::DebugConsoleDelegate(QObject *parent) : QStyledItemDelegate(parent) { } DebugConsoleDelegate::~DebugConsoleDelegate() = default; QString DebugConsoleDelegate::displayText(const QVariant &value, const QLocale &locale) const { switch (value.type()) { case QMetaType::QPoint: { const QPoint p = value.toPoint(); return QStringLiteral("%1,%2").arg(p.x()).arg(p.y()); } case QMetaType::QPointF: { const QPointF p = value.toPointF(); return QStringLiteral("%1,%2").arg(p.x()).arg(p.y()); } case QMetaType::QSize: { const QSize s = value.toSize(); return QStringLiteral("%1x%2").arg(s.width()).arg(s.height()); } case QMetaType::QSizeF: { const QSizeF s = value.toSizeF(); return QStringLiteral("%1x%2").arg(s.width()).arg(s.height()); } case QMetaType::QRect: { const QRect r = value.toRect(); return QStringLiteral("%1,%2 %3x%4").arg(r.x()).arg(r.y()).arg(r.width()).arg(r.height()); } default: if (value.userType() == qMetaTypeId()) { if (auto s = value.value()) { return QStringLiteral("KWayland::Server::SurfaceInterface(0x%1)").arg(qulonglong(s), 0, 16); } else { return QStringLiteral("nullptr"); } } if (value.userType() == qMetaTypeId()) { const auto buttons = value.value(); if (buttons == Qt::NoButton) { return i18n("No Mouse Buttons"); } QStringList list; if (buttons.testFlag(Qt::LeftButton)) { list << i18nc("Mouse Button", "left"); } if (buttons.testFlag(Qt::RightButton)) { list << i18nc("Mouse Button", "right"); } if (buttons.testFlag(Qt::MiddleButton)) { list << i18nc("Mouse Button", "middle"); } if (buttons.testFlag(Qt::BackButton)) { list << i18nc("Mouse Button", "back"); } if (buttons.testFlag(Qt::ForwardButton)) { list << i18nc("Mouse Button", "forward"); } if (buttons.testFlag(Qt::ExtraButton1)) { list << i18nc("Mouse Button", "extra 1"); } if (buttons.testFlag(Qt::ExtraButton2)) { list << i18nc("Mouse Button", "extra 2"); } if (buttons.testFlag(Qt::ExtraButton3)) { list << i18nc("Mouse Button", "extra 3"); } if (buttons.testFlag(Qt::ExtraButton4)) { list << i18nc("Mouse Button", "extra 4"); } if (buttons.testFlag(Qt::ExtraButton5)) { list << i18nc("Mouse Button", "extra 5"); } if (buttons.testFlag(Qt::ExtraButton6)) { list << i18nc("Mouse Button", "extra 6"); } if (buttons.testFlag(Qt::ExtraButton7)) { list << i18nc("Mouse Button", "extra 7"); } if (buttons.testFlag(Qt::ExtraButton8)) { list << i18nc("Mouse Button", "extra 8"); } if (buttons.testFlag(Qt::ExtraButton9)) { list << i18nc("Mouse Button", "extra 9"); } if (buttons.testFlag(Qt::ExtraButton10)) { list << i18nc("Mouse Button", "extra 10"); } if (buttons.testFlag(Qt::ExtraButton11)) { list << i18nc("Mouse Button", "extra 11"); } if (buttons.testFlag(Qt::ExtraButton12)) { list << i18nc("Mouse Button", "extra 12"); } if (buttons.testFlag(Qt::ExtraButton13)) { list << i18nc("Mouse Button", "extra 13"); } if (buttons.testFlag(Qt::ExtraButton14)) { list << i18nc("Mouse Button", "extra 14"); } if (buttons.testFlag(Qt::ExtraButton15)) { list << i18nc("Mouse Button", "extra 15"); } if (buttons.testFlag(Qt::ExtraButton16)) { list << i18nc("Mouse Button", "extra 16"); } if (buttons.testFlag(Qt::ExtraButton17)) { list << i18nc("Mouse Button", "extra 17"); } if (buttons.testFlag(Qt::ExtraButton18)) { list << i18nc("Mouse Button", "extra 18"); } if (buttons.testFlag(Qt::ExtraButton19)) { list << i18nc("Mouse Button", "extra 19"); } if (buttons.testFlag(Qt::ExtraButton20)) { list << i18nc("Mouse Button", "extra 20"); } if (buttons.testFlag(Qt::ExtraButton21)) { list << i18nc("Mouse Button", "extra 21"); } if (buttons.testFlag(Qt::ExtraButton22)) { list << i18nc("Mouse Button", "extra 22"); } if (buttons.testFlag(Qt::ExtraButton23)) { list << i18nc("Mouse Button", "extra 23"); } if (buttons.testFlag(Qt::ExtraButton24)) { list << i18nc("Mouse Button", "extra 24"); } if (buttons.testFlag(Qt::TaskButton)) { list << i18nc("Mouse Button", "task"); } return list.join(QStringLiteral(", ")); } break; } return QStyledItemDelegate::displayText(value, locale); } static const int s_x11ClientId = 1; static const int s_x11UnmanagedId = 2; static const int s_waylandClientId = 3; -static const int s_waylandInternalId = 4; +static const int s_workspaceInternalId = 4; static const quint32 s_propertyBitMask = 0xFFFF0000; static const quint32 s_clientBitMask = 0x0000FFFF; static const quint32 s_idDistance = 10000; template void DebugConsoleModel::add(int parentRow, QVector &clients, T *client) { beginInsertRows(index(parentRow, 0, QModelIndex()), clients.count(), clients.count()); clients.append(client); endInsertRows(); } template void DebugConsoleModel::remove(int parentRow, QVector &clients, T *client) { const int remove = clients.indexOf(client); if (remove == -1) { return; } beginRemoveRows(index(parentRow, 0, QModelIndex()), remove, remove); clients.removeAt(remove); endRemoveRows(); } DebugConsoleModel::DebugConsoleModel(QObject *parent) : QAbstractItemModel(parent) { if (waylandServer()) { const auto clients = waylandServer()->clients(); for (auto c : clients) { m_shellClients.append(c); } - const auto internals = waylandServer()->internalClients(); - for (auto c : internals) { - m_internalClients.append(c); - } // TODO: that only includes windows getting shown, not those which are only created connect(waylandServer(), &WaylandServer::shellClientAdded, this, [this] (ShellClient *c) { - if (c->isInternal()) { - add(s_waylandInternalId -1, m_internalClients, c); - } else { - add(s_waylandClientId -1, m_shellClients, c); - } + add(s_waylandClientId -1, m_shellClients, c); } ); connect(waylandServer(), &WaylandServer::shellClientRemoved, this, [this] (ShellClient *c) { - remove(s_waylandInternalId -1, m_internalClients, c); remove(s_waylandClientId -1, m_shellClients, c); } ); } const auto x11Clients = workspace()->clientList(); for (auto c : x11Clients) { m_x11Clients.append(c); } const auto x11DesktopClients = workspace()->desktopList(); for (auto c : x11DesktopClients) { m_x11Clients.append(c); } connect(workspace(), &Workspace::clientAdded, this, [this] (Client *c) { add(s_x11ClientId -1, m_x11Clients, c); } ); connect(workspace(), &Workspace::clientRemoved, this, [this] (AbstractClient *ac) { Client *c = qobject_cast(ac); if (!c) { return; } remove(s_x11ClientId -1, m_x11Clients, c); } ); const auto unmangeds = workspace()->unmanagedList(); for (auto u : unmangeds) { m_unmanageds.append(u); } connect(workspace(), &Workspace::unmanagedAdded, this, [this] (Unmanaged *u) { add(s_x11UnmanagedId -1, m_unmanageds, u); } ); connect(workspace(), &Workspace::unmanagedRemoved, this, [this] (Unmanaged *u) { remove(s_x11UnmanagedId -1, m_unmanageds, u); } ); + for (InternalClient *client : workspace()->internalClients()) { + m_internalClients.append(client); + } + connect(workspace(), &Workspace::internalClientAdded, this, + [this](InternalClient *client) { + add(s_workspaceInternalId -1, m_internalClients, client); + } + ); + connect(workspace(), &Workspace::internalClientRemoved, this, + [this](InternalClient *client) { + remove(s_workspaceInternalId -1, m_internalClients, client); + } + ); } DebugConsoleModel::~DebugConsoleModel() = default; int DebugConsoleModel::columnCount(const QModelIndex &parent) const { Q_UNUSED(parent) return 2; } int DebugConsoleModel::topLevelRowCount() const { return kwinApp()->shouldUseWaylandForCompositing() ? 4 : 2; } template int DebugConsoleModel::propertyCount(const QModelIndex &parent, T *(DebugConsoleModel::*filter)(const QModelIndex&) const) const { if (T *t = (this->*filter)(parent)) { return t->metaObject()->propertyCount(); } return 0; } int DebugConsoleModel::rowCount(const QModelIndex &parent) const { if (!parent.isValid()) { return topLevelRowCount(); } switch (parent.internalId()) { case s_x11ClientId: return m_x11Clients.count(); case s_x11UnmanagedId: return m_unmanageds.count(); case s_waylandClientId: return m_shellClients.count(); - case s_waylandInternalId: + case s_workspaceInternalId: return m_internalClients.count(); default: break; } if (parent.internalId() & s_propertyBitMask) { // properties do not have children return 0; } if (parent.internalId() < s_idDistance * (s_x11ClientId + 1)) { return propertyCount(parent, &DebugConsoleModel::x11Client); } else if (parent.internalId() < s_idDistance * (s_x11UnmanagedId + 1)) { return propertyCount(parent, &DebugConsoleModel::unmanaged); } else if (parent.internalId() < s_idDistance * (s_waylandClientId + 1)) { return propertyCount(parent, &DebugConsoleModel::shellClient); - } else if (parent.internalId() < s_idDistance * (s_waylandInternalId + 1)) { + } else if (parent.internalId() < s_idDistance * (s_workspaceInternalId + 1)) { return propertyCount(parent, &DebugConsoleModel::internalClient); } return 0; } template QModelIndex DebugConsoleModel::indexForClient(int row, int column, const QVector &clients, int id) const { if (column != 0) { return QModelIndex(); } if (row >= clients.count()) { return QModelIndex(); } return createIndex(row, column, s_idDistance * id + row); } template QModelIndex DebugConsoleModel::indexForProperty(int row, int column, const QModelIndex &parent, T *(DebugConsoleModel::*filter)(const QModelIndex&) const) const { if (T *t = (this->*filter)(parent)) { if (row >= t->metaObject()->propertyCount()) { return QModelIndex(); } return createIndex(row, column, quint32(row + 1) << 16 | parent.internalId()); } return QModelIndex(); } QModelIndex DebugConsoleModel::index(int row, int column, const QModelIndex &parent) const { if (!parent.isValid()) { // index for a top level item if (column != 0 || row >= topLevelRowCount()) { return QModelIndex(); } return createIndex(row, column, row + 1); } if (column >= 2) { // max of 2 columns return QModelIndex(); } // index for a client (second level) switch (parent.internalId()) { case s_x11ClientId: return indexForClient(row, column, m_x11Clients, s_x11ClientId); case s_x11UnmanagedId: return indexForClient(row, column, m_unmanageds, s_x11UnmanagedId); case s_waylandClientId: return indexForClient(row, column, m_shellClients, s_waylandClientId); - case s_waylandInternalId: - return indexForClient(row, column, m_internalClients, s_waylandInternalId); + case s_workspaceInternalId: + return indexForClient(row, column, m_internalClients, s_workspaceInternalId); default: break; } // index for a property (third level) if (parent.internalId() < s_idDistance * (s_x11ClientId + 1)) { return indexForProperty(row, column, parent, &DebugConsoleModel::x11Client); } else if (parent.internalId() < s_idDistance * (s_x11UnmanagedId + 1)) { return indexForProperty(row, column, parent, &DebugConsoleModel::unmanaged); } else if (parent.internalId() < s_idDistance * (s_waylandClientId + 1)) { return indexForProperty(row, column, parent, &DebugConsoleModel::shellClient); - } else if (parent.internalId() < s_idDistance * (s_waylandInternalId + 1)) { + } else if (parent.internalId() < s_idDistance * (s_workspaceInternalId + 1)) { return indexForProperty(row, column, parent, &DebugConsoleModel::internalClient); } return QModelIndex(); } QModelIndex DebugConsoleModel::parent(const QModelIndex &child) const { - if (child.internalId() <= s_waylandInternalId) { + if (child.internalId() <= s_workspaceInternalId) { return QModelIndex(); } if (child.internalId() & s_propertyBitMask) { // a property const quint32 parentId = child.internalId() & s_clientBitMask; if (parentId < s_idDistance * (s_x11ClientId + 1)) { return createIndex(parentId - (s_idDistance * s_x11ClientId), 0, parentId); } else if (parentId < s_idDistance * (s_x11UnmanagedId + 1)) { return createIndex(parentId - (s_idDistance * s_x11UnmanagedId), 0, parentId); } else if (parentId < s_idDistance * (s_waylandClientId + 1)) { return createIndex(parentId - (s_idDistance * s_waylandClientId), 0, parentId); - } else if (parentId < s_idDistance * (s_waylandInternalId + 1)) { - return createIndex(parentId - (s_idDistance * s_waylandInternalId), 0, parentId); + } else if (parentId < s_idDistance * (s_workspaceInternalId + 1)) { + return createIndex(parentId - (s_idDistance * s_workspaceInternalId), 0, parentId); } return QModelIndex(); } if (child.internalId() < s_idDistance * (s_x11ClientId + 1)) { return createIndex(s_x11ClientId -1, 0, s_x11ClientId); } else if (child.internalId() < s_idDistance * (s_x11UnmanagedId + 1)) { return createIndex(s_x11UnmanagedId -1, 0, s_x11UnmanagedId); } else if (child.internalId() < s_idDistance * (s_waylandClientId + 1)) { return createIndex(s_waylandClientId -1, 0, s_waylandClientId); - } else if (child.internalId() < s_idDistance * (s_waylandInternalId + 1)) { - return createIndex(s_waylandInternalId -1, 0, s_waylandInternalId); + } else if (child.internalId() < s_idDistance * (s_workspaceInternalId + 1)) { + return createIndex(s_workspaceInternalId -1, 0, s_workspaceInternalId); } return QModelIndex(); } QVariant DebugConsoleModel::propertyData(QObject *object, const QModelIndex &index, int role) const { Q_UNUSED(role) const auto property = object->metaObject()->property(index.row()); if (index.column() == 0) { return property.name(); } else { const QVariant value = property.read(object); if (qstrcmp(property.name(), "windowType") == 0) { switch (value.toInt()) { case NET::Normal: return QStringLiteral("NET::Normal"); case NET::Desktop: return QStringLiteral("NET::Desktop"); case NET::Dock: return QStringLiteral("NET::Dock"); case NET::Toolbar: return QStringLiteral("NET::Toolbar"); case NET::Menu: return QStringLiteral("NET::Menu"); case NET::Dialog: return QStringLiteral("NET::Dialog"); case NET::Override: return QStringLiteral("NET::Override"); case NET::TopMenu: return QStringLiteral("NET::TopMenu"); case NET::Utility: return QStringLiteral("NET::Utility"); case NET::Splash: return QStringLiteral("NET::Splash"); case NET::DropdownMenu: return QStringLiteral("NET::DropdownMenu"); case NET::PopupMenu: return QStringLiteral("NET::PopupMenu"); case NET::Tooltip: return QStringLiteral("NET::Tooltip"); case NET::Notification: return QStringLiteral("NET::Notification"); case NET::ComboBox: return QStringLiteral("NET::ComboBox"); case NET::DNDIcon: return QStringLiteral("NET::DNDIcon"); case NET::OnScreenDisplay: return QStringLiteral("NET::OnScreenDisplay"); case NET::CriticalNotification: return QStringLiteral("NET::CriticalNotification"); case NET::Unknown: default: return QStringLiteral("NET::Unknown"); } } return value; } return QVariant(); } template QVariant DebugConsoleModel::clientData(const QModelIndex &index, int role, const QVector clients) const { if (index.row() >= clients.count()) { return QVariant(); } auto c = clients.at(index.row()); if (role == Qt::DisplayRole) { return QStringLiteral("%1: %2").arg(c->window()).arg(c->caption()); } else if (role == Qt::DecorationRole) { return c->icon(); } return QVariant(); } QVariant DebugConsoleModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) { return QVariant(); } if (!index.parent().isValid()) { // one of the top levels if (index.column() != 0 || role != Qt::DisplayRole) { return QVariant(); } switch (index.internalId()) { case s_x11ClientId: return i18n("X11 Client Windows"); case s_x11UnmanagedId: return i18n("X11 Unmanaged Windows"); case s_waylandClientId: return i18n("Wayland Windows"); - case s_waylandInternalId: + case s_workspaceInternalId: return i18n("Internal Windows"); default: return QVariant(); } } if (index.internalId() & s_propertyBitMask) { if (index.column() >= 2 || role != Qt::DisplayRole) { return QVariant(); } if (ShellClient *c = shellClient(index)) { return propertyData(c, index, role); - } else if (ShellClient *c = internalClient(index)) { + } else if (InternalClient *c = internalClient(index)) { return propertyData(c, index, role); } else if (Client *c = x11Client(index)) { return propertyData(c, index, role); } else if (Unmanaged *u = unmanaged(index)) { return propertyData(u, index, role); } } else { if (index.column() != 0) { return QVariant(); } switch (index.parent().internalId()) { case s_x11ClientId: return clientData(index, role, m_x11Clients); case s_x11UnmanagedId: { if (index.row() >= m_unmanageds.count()) { return QVariant(); } auto u = m_unmanageds.at(index.row()); if (role == Qt::DisplayRole) { return u->window(); } break; } case s_waylandClientId: return clientData(index, role, m_shellClients); - case s_waylandInternalId: + case s_workspaceInternalId: return clientData(index, role, m_internalClients); default: break; } } return QVariant(); } template static T *clientForIndex(const QModelIndex &index, const QVector &clients, int id) { const qint32 row = (index.internalId() & s_clientBitMask) - (s_idDistance * id); if (row < 0 || row >= clients.count()) { return nullptr; } return clients.at(row); } ShellClient *DebugConsoleModel::shellClient(const QModelIndex &index) const { return clientForIndex(index, m_shellClients, s_waylandClientId); } -ShellClient *DebugConsoleModel::internalClient(const QModelIndex &index) const +InternalClient *DebugConsoleModel::internalClient(const QModelIndex &index) const { - return clientForIndex(index, m_internalClients, s_waylandInternalId); + return clientForIndex(index, m_internalClients, s_workspaceInternalId); } Client *DebugConsoleModel::x11Client(const QModelIndex &index) const { return clientForIndex(index, m_x11Clients, s_x11ClientId); } Unmanaged *DebugConsoleModel::unmanaged(const QModelIndex &index) const { return clientForIndex(index, m_unmanageds, s_x11UnmanagedId); } /////////////////////////////////////// SurfaceTreeModel SurfaceTreeModel::SurfaceTreeModel(QObject *parent) : QAbstractItemModel(parent) { // TODO: it would be nice to not have to reset the model on each change auto reset = [this] { beginResetModel(); endResetModel(); }; using namespace KWayland::Server; const auto unmangeds = workspace()->unmanagedList(); for (auto u : unmangeds) { if (!u->surface()) { continue; } connect(u->surface(), &SurfaceInterface::subSurfaceTreeChanged, this, reset); } for (auto c : workspace()->allClientList()) { if (!c->surface()) { continue; } connect(c->surface(), &SurfaceInterface::subSurfaceTreeChanged, this, reset); } for (auto c : workspace()->desktopList()) { if (!c->surface()) { continue; } connect(c->surface(), &SurfaceInterface::subSurfaceTreeChanged, this, reset); } if (waylandServer()) { - for (auto c : waylandServer()->internalClients()) { - connect(c->surface(), &SurfaceInterface::subSurfaceTreeChanged, this, reset); - } connect(waylandServer(), &WaylandServer::shellClientAdded, this, [this, reset] (ShellClient *c) { connect(c->surface(), &SurfaceInterface::subSurfaceTreeChanged, this, reset); reset(); } ); } connect(workspace(), &Workspace::clientAdded, this, [this, reset] (AbstractClient *c) { if (c->surface()) { connect(c->surface(), &SurfaceInterface::subSurfaceTreeChanged, this, reset); } reset(); } ); connect(workspace(), &Workspace::clientRemoved, this, reset); connect(workspace(), &Workspace::unmanagedAdded, this, [this, reset] (Unmanaged *u) { if (u->surface()) { connect(u->surface(), &SurfaceInterface::subSurfaceTreeChanged, this, reset); } reset(); } ); connect(workspace(), &Workspace::unmanagedRemoved, this, reset); } SurfaceTreeModel::~SurfaceTreeModel() = default; int SurfaceTreeModel::columnCount(const QModelIndex &parent) const { Q_UNUSED(parent) return 1; } int SurfaceTreeModel::rowCount(const QModelIndex &parent) const { if (parent.isValid()) { using namespace KWayland::Server; if (SurfaceInterface *surface = static_cast(parent.internalPointer())) { const auto &children = surface->childSubSurfaces(); return children.count(); } return 0; } - const int internalClientsCount = waylandServer() ? waylandServer()->internalClients().count() : 0; // toplevel are all windows return workspace()->allClientList().count() + workspace()->desktopList().count() + - workspace()->unmanagedList().count() + - internalClientsCount; + workspace()->unmanagedList().count(); } QModelIndex SurfaceTreeModel::index(int row, int column, const QModelIndex &parent) const { if (column != 0) { // invalid column return QModelIndex(); } if (parent.isValid()) { using namespace KWayland::Server; if (SurfaceInterface *surface = static_cast(parent.internalPointer())) { const auto &children = surface->childSubSurfaces(); if (row < children.count()) { return createIndex(row, column, children.at(row)->surface().data()); } } return QModelIndex(); } // a window const auto &allClients = workspace()->allClientList(); if (row < allClients.count()) { // references a client return createIndex(row, column, allClients.at(row)->surface()); } int reference = allClients.count(); const auto &desktopClients = workspace()->desktopList(); if (row < reference + desktopClients.count()) { return createIndex(row, column, desktopClients.at(row-reference)->surface()); } reference += desktopClients.count(); const auto &unmanaged = workspace()->unmanagedList(); if (row < reference + unmanaged.count()) { return createIndex(row, column, unmanaged.at(row-reference)->surface()); } reference += unmanaged.count(); - if (waylandServer()) { - const auto &internal = waylandServer()->internalClients(); - if (row < reference + internal.count()) { - return createIndex(row, column, internal.at(row-reference)->surface()); - } - } // not found return QModelIndex(); } QModelIndex SurfaceTreeModel::parent(const QModelIndex &child) const { using namespace KWayland::Server; if (SurfaceInterface *surface = static_cast(child.internalPointer())) { const auto &subsurface = surface->subSurface(); if (subsurface.isNull()) { // doesn't reference a subsurface, this is a top-level window return QModelIndex(); } SurfaceInterface *parent = subsurface->parentSurface().data(); if (!parent) { // something is wrong return QModelIndex(); } // is the parent a subsurface itself? if (parent->subSurface()) { auto grandParent = parent->subSurface()->parentSurface(); if (grandParent.isNull()) { // something is wrong return QModelIndex(); } const auto &children = grandParent->childSubSurfaces(); for (int row = 0; row < children.count(); row++) { if (children.at(row).data() == parent->subSurface().data()) { return createIndex(row, 0, parent); } } return QModelIndex(); } // not a subsurface, thus it's a true window int row = 0; const auto &allClients = workspace()->allClientList(); for (; row < allClients.count(); row++) { if (allClients.at(row)->surface() == parent) { return createIndex(row, 0, parent); } } row = allClients.count(); const auto &desktopClients = workspace()->desktopList(); for (int i = 0; i < desktopClients.count(); i++) { if (desktopClients.at(i)->surface() == parent) { return createIndex(row + i, 0, parent); } } row += desktopClients.count(); const auto &unmanaged = workspace()->unmanagedList(); for (int i = 0; i < unmanaged.count(); i++) { if (unmanaged.at(i)->surface() == parent) { return createIndex(row + i, 0, parent); } } row += unmanaged.count(); - if (waylandServer()) { - const auto &internal = waylandServer()->internalClients(); - for (int i = 0; i < internal.count(); i++) { - if (internal.at(i)->surface() == parent) { - return createIndex(row + i, 0, parent); - } - } - } } return QModelIndex(); } QVariant SurfaceTreeModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) { return QVariant(); } using namespace KWayland::Server; if (SurfaceInterface *surface = static_cast(index.internalPointer())) { if (role == Qt::DisplayRole || role == Qt::ToolTipRole) { return QStringLiteral("%1 (%2) - %3").arg(surface->client()->executablePath()) .arg(surface->client()->processId()) .arg(surface->id()); } else if (role == Qt::DecorationRole) { if (auto buffer = surface->buffer()) { if (buffer->shmBuffer()) { return buffer->data().scaled(QSize(64, 64), Qt::KeepAspectRatio); } } } } return QVariant(); } InputDeviceModel::InputDeviceModel(QObject *parent) : QAbstractItemModel(parent) , m_devices(LibInput::Connection::self()->devices()) { for (auto it = m_devices.constBegin(); it != m_devices.constEnd(); ++it) { setupDeviceConnections(*it); } auto c = LibInput::Connection::self(); connect(c, &LibInput::Connection::deviceAdded, this, [this] (LibInput::Device *d) { beginInsertRows(QModelIndex(), m_devices.count(), m_devices.count()); m_devices << d; setupDeviceConnections(d); endInsertRows(); } ); connect(c, &LibInput::Connection::deviceRemoved, this, [this] (LibInput::Device *d) { const int index = m_devices.indexOf(d); if (index == -1) { return; } beginRemoveRows(QModelIndex(), index, index); m_devices.removeAt(index); endRemoveRows(); } ); } InputDeviceModel::~InputDeviceModel() = default; int InputDeviceModel::columnCount(const QModelIndex &parent) const { Q_UNUSED(parent) return 2; } QVariant InputDeviceModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) { return QVariant(); } if (!index.parent().isValid() && index.column() == 0) { const auto devices = LibInput::Connection::self()->devices(); if (index.row() >= devices.count()) { return QVariant(); } if (role == Qt::DisplayRole) { return devices.at(index.row())->name(); } } if (index.parent().isValid()) { if (role == Qt::DisplayRole) { const auto device = LibInput::Connection::self()->devices().at(index.parent().row()); const auto property = device->metaObject()->property(index.row()); if (index.column() == 0) { return property.name(); } else if (index.column() == 1) { return device->property(property.name()); } } } return QVariant(); } QModelIndex InputDeviceModel::index(int row, int column, const QModelIndex &parent) const { if (column >= 2) { return QModelIndex(); } if (parent.isValid()) { if (parent.internalId() & s_propertyBitMask) { return QModelIndex(); } if (row >= LibInput::Connection::self()->devices().at(parent.row())->metaObject()->propertyCount()) { return QModelIndex(); } return createIndex(row, column, quint32(row + 1) << 16 | parent.internalId()); } if (row >= LibInput::Connection::self()->devices().count()) { return QModelIndex(); } return createIndex(row, column, row + 1); } int InputDeviceModel::rowCount(const QModelIndex &parent) const { if (!parent.isValid()) { return LibInput::Connection::self()->devices().count(); } if (parent.internalId() & s_propertyBitMask) { return 0; } return LibInput::Connection::self()->devices().at(parent.row())->metaObject()->propertyCount(); } QModelIndex InputDeviceModel::parent(const QModelIndex &child) const { if (child.internalId() & s_propertyBitMask) { const quintptr parentId = child.internalId() & s_clientBitMask; return createIndex(parentId - 1, 0, parentId); } return QModelIndex(); } void InputDeviceModel::setupDeviceConnections(LibInput::Device *device) { connect(device, &LibInput::Device::enabledChanged, this, [this, device] { const QModelIndex parent = index(m_devices.indexOf(device), 0, QModelIndex()); const QModelIndex child = index(device->metaObject()->indexOfProperty("enabled"), 1, parent); emit dataChanged(child, child, QVector{Qt::DisplayRole}); } ); connect(device, &LibInput::Device::leftHandedChanged, this, [this, device] { const QModelIndex parent = index(m_devices.indexOf(device), 0, QModelIndex()); const QModelIndex child = index(device->metaObject()->indexOfProperty("leftHanded"), 1, parent); emit dataChanged(child, child, QVector{Qt::DisplayRole}); } ); connect(device, &LibInput::Device::pointerAccelerationChanged, this, [this, device] { const QModelIndex parent = index(m_devices.indexOf(device), 0, QModelIndex()); const QModelIndex child = index(device->metaObject()->indexOfProperty("pointerAcceleration"), 1, parent); emit dataChanged(child, child, QVector{Qt::DisplayRole}); } ); } } diff --git a/debug_console.h b/debug_console.h index 6fc1675b9..09f6308ca 100644 --- a/debug_console.h +++ b/debug_console.h @@ -1,184 +1,185 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2016 Martin Gräßlin 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 . *********************************************************************/ #ifndef KWIN_DEBUG_CONSOLE_H #define KWIN_DEBUG_CONSOLE_H #include #include #include "input.h" #include "input_event_spy.h" #include #include #include class QTextEdit; namespace Ui { class DebugConsole; } namespace KWin { class Client; +class InternalClient; class ShellClient; class Unmanaged; class DebugConsoleFilter; class KWIN_EXPORT DebugConsoleModel : public QAbstractItemModel { Q_OBJECT public: explicit DebugConsoleModel(QObject *parent = nullptr); ~DebugConsoleModel() override; int columnCount(const QModelIndex &parent) const override; QVariant data(const QModelIndex &index, int role) const override; QModelIndex index(int row, int column, const QModelIndex & parent) const override; int rowCount(const QModelIndex &parent) const override; QModelIndex parent(const QModelIndex &child) const override; private: template QModelIndex indexForClient(int row, int column, const QVector &clients, int id) const; template QModelIndex indexForProperty(int row, int column, const QModelIndex &parent, T *(DebugConsoleModel::*filter)(const QModelIndex&) const) const; template int propertyCount(const QModelIndex &parent, T *(DebugConsoleModel::*filter)(const QModelIndex&) const) const; QVariant propertyData(QObject *object, const QModelIndex &index, int role) const; template QVariant clientData(const QModelIndex &index, int role, const QVector clients) const; template void add(int parentRow, QVector &clients, T *client); template void remove(int parentRow, QVector &clients, T *client); ShellClient *shellClient(const QModelIndex &index) const; - ShellClient *internalClient(const QModelIndex &index) const; + InternalClient *internalClient(const QModelIndex &index) const; Client *x11Client(const QModelIndex &index) const; Unmanaged *unmanaged(const QModelIndex &index) const; int topLevelRowCount() const; QVector m_shellClients; - QVector m_internalClients; + QVector m_internalClients; QVector m_x11Clients; QVector m_unmanageds; }; class DebugConsoleDelegate : public QStyledItemDelegate { Q_OBJECT public: explicit DebugConsoleDelegate(QObject *parent = nullptr); ~DebugConsoleDelegate() override; QString displayText(const QVariant &value, const QLocale &locale) const override; }; class KWIN_EXPORT DebugConsole : public QWidget { Q_OBJECT public: DebugConsole(); ~DebugConsole() override; protected: void showEvent(QShowEvent *event) override; private: void initGLTab(); void updateKeyboardTab(); QScopedPointer m_ui; QScopedPointer m_inputFilter; }; class SurfaceTreeModel : public QAbstractItemModel { Q_OBJECT public: explicit SurfaceTreeModel(QObject *parent = nullptr); ~SurfaceTreeModel() override; int columnCount(const QModelIndex &parent) const override; QVariant data(const QModelIndex &index, int role) const override; QModelIndex index(int row, int column, const QModelIndex & parent) const override; int rowCount(const QModelIndex &parent) const override; QModelIndex parent(const QModelIndex &child) const override; }; class DebugConsoleFilter : public InputEventSpy { public: explicit DebugConsoleFilter(QTextEdit *textEdit); ~DebugConsoleFilter() override; void pointerEvent(MouseEvent *event) override; void wheelEvent(WheelEvent *event) override; void keyEvent(KeyEvent *event) override; void touchDown(qint32 id, const QPointF &pos, quint32 time) override; void touchMotion(qint32 id, const QPointF &pos, quint32 time) override; void touchUp(qint32 id, quint32 time) override; void pinchGestureBegin(int fingerCount, quint32 time) override; void pinchGestureUpdate(qreal scale, qreal angleDelta, const QSizeF &delta, quint32 time) override; void pinchGestureEnd(quint32 time) override; void pinchGestureCancelled(quint32 time) override; void swipeGestureBegin(int fingerCount, quint32 time) override; void swipeGestureUpdate(const QSizeF &delta, quint32 time) override; void swipeGestureEnd(quint32 time) override; void swipeGestureCancelled(quint32 time) override; void switchEvent(SwitchEvent *event) override; private: QTextEdit *m_textEdit; }; namespace LibInput { class Device; } class InputDeviceModel : public QAbstractItemModel { Q_OBJECT public: explicit InputDeviceModel(QObject *parent = nullptr); ~InputDeviceModel() override; int columnCount(const QModelIndex &parent) const override; QVariant data(const QModelIndex &index, int role) const override; QModelIndex index(int row, int column, const QModelIndex & parent) const override; int rowCount(const QModelIndex &parent) const override; QModelIndex parent(const QModelIndex &child) const override; private: void setupDeviceConnections(LibInput::Device *device); QVector m_devices; }; } #endif diff --git a/deleted.cpp b/deleted.cpp index 9cf4d49fc..949e317ee 100644 --- a/deleted.cpp +++ b/deleted.cpp @@ -1,298 +1,304 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2006 Lubos Lunak 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 . *********************************************************************/ #include "deleted.h" #include "workspace.h" #include "client.h" #include "group.h" #include "netinfo.h" #include "shadow.h" #include "shell_client.h" #include "decorations/decoratedclient.h" #include "decorations/decorationrenderer.h" #include namespace KWin { Deleted::Deleted() : Toplevel() , delete_refcount(1) , m_frame(XCB_WINDOW_NONE) , no_border(true) , m_layer(UnknownLayer) , m_minimized(false) , m_modal(false) , m_wasClient(false) , m_decorationRenderer(nullptr) , m_fullscreen(false) , m_keepAbove(false) , m_keepBelow(false) , m_wasActive(false) , m_wasX11Client(false) , m_wasWaylandClient(false) , m_wasGroupTransient(false) , m_wasPopupWindow(false) , m_wasOutline(false) { } Deleted::~Deleted() { if (delete_refcount != 0) qCCritical(KWIN_CORE) << "Deleted client has non-zero reference count (" << delete_refcount << ")"; Q_ASSERT(delete_refcount == 0); if (workspace()) { workspace()->removeDeleted(this); } for (Toplevel *toplevel : qAsConst(m_transientFor)) { if (auto *deleted = qobject_cast(toplevel)) { deleted->removeTransient(this); } } for (Deleted *transient : qAsConst(m_transients)) { transient->removeTransientFor(this); } deleteEffectWindow(); } Deleted* Deleted::create(Toplevel* c) { Deleted* d = new Deleted(); d->copyToDeleted(c); workspace()->addDeleted(d, c); return d; } // to be used only from Workspace::finishCompositing() void Deleted::discard() { delete_refcount = 0; delete this; } void Deleted::copyToDeleted(Toplevel* c) { Q_ASSERT(dynamic_cast< Deleted* >(c) == nullptr); Toplevel::copyToDeleted(c); + m_bufferScale = c->bufferScale(); desk = c->desktop(); m_desktops = c->desktops(); activityList = c->activities(); contentsRect = QRect(c->clientPos(), c->clientSize()); m_contentPos = c->clientContentPos(); transparent_rect = c->transparentRect(); m_layer = c->layer(); m_frame = c->frameId(); m_opacity = c->opacity(); m_type = c->windowType(); m_windowRole = c->windowRole(); if (WinInfo* cinfo = dynamic_cast< WinInfo* >(info)) cinfo->disable(); if (AbstractClient *client = dynamic_cast(c)) { no_border = client->noBorder(); if (!no_border) { client->layoutDecorationRects(decoration_left, decoration_top, decoration_right, decoration_bottom); if (client->isDecorated()) { if (Decoration::Renderer *renderer = client->decoratedClient()->renderer()) { m_decorationRenderer = renderer; m_decorationRenderer->reparent(this); } } } m_wasClient = true; m_minimized = client->isMinimized(); m_modal = client->isModal(); m_mainClients = client->mainClients(); foreach (AbstractClient *c, m_mainClients) { addTransientFor(c); connect(c, &AbstractClient::windowClosed, this, &Deleted::mainClientClosed); } m_fullscreen = client->isFullScreen(); m_keepAbove = client->keepAbove(); m_keepBelow = client->keepBelow(); m_caption = client->caption(); m_wasActive = client->isActive(); m_wasGroupTransient = client->groupTransient(); } for (auto vd : m_desktops) { connect(vd, &QObject::destroyed, this, [=] { m_desktops.removeOne(vd); }); } m_wasWaylandClient = qobject_cast(c) != nullptr; - m_wasX11Client = !m_wasWaylandClient; + m_wasX11Client = qobject_cast(c) != nullptr; m_wasPopupWindow = c->isPopupWindow(); m_wasOutline = c->isOutline(); } void Deleted::unrefWindow() { if (--delete_refcount > 0) return; // needs to be delayed // a) when calling from effects, otherwise it'd be rather complicated to handle the case of the // window going away during a painting pass // b) to prevent dangeling pointers in the stacking order, see bug #317765 deleteLater(); } +qreal Deleted::bufferScale() const +{ + return m_bufferScale; +} + int Deleted::desktop() const { return desk; } QStringList Deleted::activities() const { return activityList; } QVector Deleted::desktops() const { return m_desktops; } QPoint Deleted::clientPos() const { return contentsRect.topLeft(); } QSize Deleted::clientSize() const { return contentsRect.size(); } void Deleted::debug(QDebug& stream) const { stream << "\'ID:" << window() << "\' (deleted)"; } void Deleted::layoutDecorationRects(QRect& left, QRect& top, QRect& right, QRect& bottom) const { left = decoration_left; top = decoration_top; right = decoration_right; bottom = decoration_bottom; } QRect Deleted::decorationRect() const { return rect(); } QRect Deleted::transparentRect() const { return transparent_rect; } bool Deleted::isDeleted() const { return true; } NET::WindowType Deleted::windowType(bool direct, int supportedTypes) const { Q_UNUSED(direct) Q_UNUSED(supportedTypes) return m_type; } void Deleted::mainClientClosed(Toplevel *client) { if (AbstractClient *c = dynamic_cast(client)) m_mainClients.removeAll(c); } void Deleted::transientForClosed(Toplevel *toplevel, Deleted *deleted) { if (deleted == nullptr) { m_transientFor.removeAll(toplevel); return; } const int index = m_transientFor.indexOf(toplevel); if (index == -1) { return; } m_transientFor[index] = deleted; deleted->addTransient(this); } xcb_window_t Deleted::frameId() const { return m_frame; } double Deleted::opacity() const { return m_opacity; } QByteArray Deleted::windowRole() const { return m_windowRole; } QVector Deleted::x11DesktopIds() const { const auto desks = desktops(); QVector x11Ids; x11Ids.reserve(desks.count()); std::transform(desks.constBegin(), desks.constEnd(), std::back_inserter(x11Ids), [] (const VirtualDesktop *vd) { return vd->x11DesktopNumber(); } ); return x11Ids; } void Deleted::addTransient(Deleted *transient) { m_transients.append(transient); } void Deleted::removeTransient(Deleted *transient) { m_transients.removeAll(transient); } void Deleted::addTransientFor(AbstractClient *parent) { m_transientFor.append(parent); connect(parent, &AbstractClient::windowClosed, this, &Deleted::transientForClosed); } void Deleted::removeTransientFor(Deleted *parent) { m_transientFor.removeAll(parent); } } // namespace diff --git a/deleted.h b/deleted.h index 918589a74..93001e774 100644 --- a/deleted.h +++ b/deleted.h @@ -1,247 +1,249 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2006 Lubos Lunak 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 . *********************************************************************/ #ifndef KWIN_DELETED_H #define KWIN_DELETED_H #include "toplevel.h" namespace KWin { class AbstractClient; namespace Decoration { class Renderer; } class KWIN_EXPORT Deleted : public Toplevel { Q_OBJECT public: static Deleted* create(Toplevel* c); // used by effects to keep the window around for e.g. fadeout effects when it's destroyed void refWindow(); void unrefWindow(); void discard(); + qreal bufferScale() const override; int desktop() const override; QStringList activities() const override; QVector desktops() const override; QPoint clientPos() const override; QSize clientSize() const override; QPoint clientContentPos() const override { return m_contentPos; } QRect transparentRect() const override; bool isDeleted() const override; xcb_window_t frameId() const override; bool noBorder() const { return no_border; } void layoutDecorationRects(QRect &left, QRect &top, QRect &right, QRect &bottom) const; QRect decorationRect() const override; Layer layer() const override { return m_layer; } bool isMinimized() const { return m_minimized; } bool isModal() const { return m_modal; } QList mainClients() const { return m_mainClients; } NET::WindowType windowType(bool direct = false, int supported_types = 0) const override; bool wasClient() const { return m_wasClient; } double opacity() const override; QByteArray windowRole() const override; const Decoration::Renderer *decorationRenderer() const { return m_decorationRenderer; } bool isFullScreen() const { return m_fullscreen; } bool keepAbove() const { return m_keepAbove; } bool keepBelow() const { return m_keepBelow; } QString caption() const { return m_caption; } /** * Returns whether the client was active. * * @returns @c true if the client was active at the time when it was closed, * @c false otherwise */ bool wasActive() const { return m_wasActive; } /** * Returns whether this was an X11 client. * * @returns @c true if it was an X11 client, @c false otherwise. */ bool wasX11Client() const { return m_wasX11Client; } /** * Returns whether this was a Wayland client. * * @returns @c true if it was a Wayland client, @c false otherwise. */ bool wasWaylandClient() const { return m_wasWaylandClient; } /** * Returns whether the client was a transient. * * @returns @c true if it was a transient, @c false otherwise. */ bool wasTransient() const { return !m_transientFor.isEmpty(); } /** * Returns whether the client was a group transient. * * @returns @c true if it was a group transient, @c false otherwise. * @note This is relevant only for X11 clients. */ bool wasGroupTransient() const { return m_wasGroupTransient; } /** * Checks whether this client was a transient for given toplevel. * * @param toplevel Toplevel against which we are testing. * @returns @c true if it was a transient for given toplevel, @c false otherwise. */ bool wasTransientFor(const Toplevel *toplevel) const { return m_transientFor.contains(const_cast(toplevel)); } /** * Returns the list of transients. * * Because the window is Deleted, it can have only Deleted child transients. */ DeletedList transients() const { return m_transients; } /** * Returns whether the client was a popup. * * @returns @c true if the client was a popup, @c false otherwise. */ bool isPopupWindow() const override { return m_wasPopupWindow; } QVector x11DesktopIds() const; /** * Whether this Deleted represents the outline. */ bool isOutline() const override { return m_wasOutline; } protected: void debug(QDebug& stream) const override; private Q_SLOTS: void mainClientClosed(KWin::Toplevel *client); void transientForClosed(Toplevel *toplevel, Deleted *deleted); private: Deleted(); // use create() void copyToDeleted(Toplevel* c); ~Deleted() override; // deleted only using unrefWindow() void addTransient(Deleted *transient); void removeTransient(Deleted *transient); void addTransientFor(AbstractClient *parent); void removeTransientFor(Deleted *parent); int delete_refcount; int desk; QStringList activityList; QRect contentsRect; // for clientPos()/clientSize() QPoint m_contentPos; QRect transparent_rect; xcb_window_t m_frame; QVector m_desktops; bool no_border; QRect decoration_left; QRect decoration_right; QRect decoration_top; QRect decoration_bottom; Layer m_layer; bool m_minimized; bool m_modal; QList m_mainClients; bool m_wasClient; Decoration::Renderer *m_decorationRenderer; double m_opacity; NET::WindowType m_type = NET::Unknown; QByteArray m_windowRole; bool m_fullscreen; bool m_keepAbove; bool m_keepBelow; QString m_caption; bool m_wasActive; bool m_wasX11Client; bool m_wasWaylandClient; bool m_wasGroupTransient; ToplevelList m_transientFor; DeletedList m_transients; bool m_wasPopupWindow; bool m_wasOutline; + qreal m_bufferScale = 1; }; inline void Deleted::refWindow() { ++delete_refcount; } } // namespace Q_DECLARE_METATYPE(KWin::Deleted*) #endif diff --git a/effects.cpp b/effects.cpp index 65b8036e2..b1aef7e34 100644 --- a/effects.cpp +++ b/effects.cpp @@ -1,2386 +1,2391 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2006 Lubos Lunak Copyright (C) 2010, 2011 Martin Gräßlin 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 . *********************************************************************/ #include "effects.h" #include "effectsadaptor.h" #include "effectloader.h" #ifdef KWIN_BUILD_ACTIVITIES #include "activities.h" #endif #include "deleted.h" #include "client.h" #include "cursor.h" #include "group.h" +#include "internal_client.h" #include "osd.h" #include "pointer_input.h" #include "unmanaged.h" #ifdef KWIN_BUILD_TABBOX #include "tabbox.h" #endif #include "screenedge.h" #include "scripting/scriptedeffect.h" #include "screens.h" #include "screenlockerwatcher.h" #include "thumbnailitem.h" #include "virtualdesktops.h" #include "window_property_notify_x11_filter.h" #include "workspace.h" #include "kwinglutils.h" #include #include #include #include "composite.h" #include "xcbutils.h" #include "platform.h" #include "shell_client.h" #include "wayland_server.h" #include "decorations/decorationbridge.h" #include namespace KWin { //--------------------- // Static static QByteArray readWindowProperty(xcb_window_t win, xcb_atom_t atom, xcb_atom_t type, int format) { if (win == XCB_WINDOW_NONE) { return QByteArray(); } uint32_t len = 32768; for (;;) { Xcb::Property prop(false, win, atom, XCB_ATOM_ANY, 0, len); if (prop.isNull()) { // get property failed return QByteArray(); } if (prop->bytes_after > 0) { len *= 2; continue; } return prop.toByteArray(format, type); } } static void deleteWindowProperty(Window win, long int atom) { if (win == XCB_WINDOW_NONE) { return; } xcb_delete_property(kwinApp()->x11Connection(), win, atom); } static xcb_atom_t registerSupportProperty(const QByteArray &propertyName) { auto c = kwinApp()->x11Connection(); if (!c) { return XCB_ATOM_NONE; } // get the atom for the propertyName ScopedCPointer atomReply(xcb_intern_atom_reply(c, xcb_intern_atom_unchecked(c, false, propertyName.size(), propertyName.constData()), nullptr)); if (atomReply.isNull()) { return XCB_ATOM_NONE; } // announce property on root window unsigned char dummy = 0; xcb_change_property(c, XCB_PROP_MODE_REPLACE, kwinApp()->x11RootWindow(), atomReply->atom, atomReply->atom, 8, 1, &dummy); // TODO: add to _NET_SUPPORTED return atomReply->atom; } //--------------------- EffectsHandlerImpl::EffectsHandlerImpl(Compositor *compositor, Scene *scene) : EffectsHandler(scene->compositingType()) , keyboard_grab_effect(nullptr) , fullscreen_effect(nullptr) , next_window_quad_type(EFFECT_QUAD_TYPE_START) , m_compositor(compositor) , m_scene(scene) , m_desktopRendering(false) , m_currentRenderedDesktop(0) , m_effectLoader(new EffectLoader(this)) , m_trackingCursorChanges(0) { qRegisterMetaType>(); connect(m_effectLoader, &AbstractEffectLoader::effectLoaded, this, [this](Effect *effect, const QString &name) { effect_order.insert(effect->requestedEffectChainPosition(), EffectPair(name, effect)); loaded_effects << EffectPair(name, effect); effectsChanged(); } ); m_effectLoader->setConfig(kwinApp()->config()); new EffectsAdaptor(this); QDBusConnection dbus = QDBusConnection::sessionBus(); dbus.registerObject(QStringLiteral("/Effects"), this); // init is important, otherwise causes crashes when quads are build before the first painting pass start m_currentBuildQuadsIterator = m_activeEffects.constEnd(); Workspace *ws = Workspace::self(); VirtualDesktopManager *vds = VirtualDesktopManager::self(); connect(ws, &Workspace::showingDesktopChanged, this, &EffectsHandlerImpl::showingDesktopChanged); connect(ws, &Workspace::currentDesktopChanged, this, [this](int old, AbstractClient *c) { const int newDesktop = VirtualDesktopManager::self()->current(); if (old != 0 && newDesktop != old) { emit desktopChanged(old, newDesktop, c ? c->effectWindow() : nullptr); // TODO: remove in 4.10 emit desktopChanged(old, newDesktop); } } ); connect(ws, &Workspace::desktopPresenceChanged, this, [this](AbstractClient *c, int old) { if (!c->effectWindow()) { return; } emit desktopPresenceChanged(c->effectWindow(), old, c->desktop()); } ); connect(ws, &Workspace::clientAdded, this, [this](Client *c) { if (c->readyForPainting()) slotClientShown(c); else connect(c, &Toplevel::windowShown, this, &EffectsHandlerImpl::slotClientShown); } ); connect(ws, &Workspace::unmanagedAdded, this, [this](Unmanaged *u) { // it's never initially ready but has synthetic 50ms delay connect(u, &Toplevel::windowShown, this, &EffectsHandlerImpl::slotUnmanagedShown); } ); + connect(ws, &Workspace::internalClientAdded, this, + [this](InternalClient *client) { + setupAbstractClientConnections(client); + emit windowAdded(client->effectWindow()); + } + ); connect(ws, &Workspace::clientActivated, this, [this](KWin::AbstractClient *c) { emit windowActivated(c ? c->effectWindow() : nullptr); } ); connect(ws, &Workspace::deletedRemoved, this, [this](KWin::Deleted *d) { emit windowDeleted(d->effectWindow()); elevated_windows.removeAll(d->effectWindow()); } ); connect(vds, &VirtualDesktopManager::countChanged, this, &EffectsHandler::numberDesktopsChanged); connect(Cursor::self(), &Cursor::mouseChanged, this, &EffectsHandler::mouseChanged); connect(screens(), &Screens::countChanged, this, &EffectsHandler::numberScreensChanged); connect(screens(), &Screens::sizeChanged, this, &EffectsHandler::virtualScreenSizeChanged); connect(screens(), &Screens::geometryChanged, this, &EffectsHandler::virtualScreenGeometryChanged); #ifdef KWIN_BUILD_ACTIVITIES if (Activities *activities = Activities::self()) { connect(activities, &Activities::added, this, &EffectsHandler::activityAdded); connect(activities, &Activities::removed, this, &EffectsHandler::activityRemoved); connect(activities, &Activities::currentChanged, this, &EffectsHandler::currentActivityChanged); } #endif connect(ws, &Workspace::stackingOrderChanged, this, &EffectsHandler::stackingOrderChanged); #ifdef KWIN_BUILD_TABBOX TabBox::TabBox *tabBox = TabBox::TabBox::self(); connect(tabBox, &TabBox::TabBox::tabBoxAdded, this, &EffectsHandler::tabBoxAdded); connect(tabBox, &TabBox::TabBox::tabBoxUpdated, this, &EffectsHandler::tabBoxUpdated); connect(tabBox, &TabBox::TabBox::tabBoxClosed, this, &EffectsHandler::tabBoxClosed); connect(tabBox, &TabBox::TabBox::tabBoxKeyEvent, this, &EffectsHandler::tabBoxKeyEvent); #endif connect(ScreenEdges::self(), &ScreenEdges::approaching, this, &EffectsHandler::screenEdgeApproaching); connect(ScreenLockerWatcher::self(), &ScreenLockerWatcher::locked, this, &EffectsHandler::screenLockingChanged); connect(ScreenLockerWatcher::self(), &ScreenLockerWatcher::aboutToLock, this, &EffectsHandler::screenAboutToLock); connect(kwinApp(), &Application::x11ConnectionChanged, this, [this] { registered_atoms.clear(); for (auto it = m_propertiesForEffects.keyBegin(); it != m_propertiesForEffects.keyEnd(); it++) { const auto atom = registerSupportProperty(*it); if (atom == XCB_ATOM_NONE) { continue; } m_compositor->keepSupportProperty(atom); m_managedProperties.insert(*it, atom); registerPropertyType(atom, true); } if (kwinApp()->x11Connection()) { m_x11WindowPropertyNotify = std::make_unique(this); } else { m_x11WindowPropertyNotify.reset(); } emit xcbConnectionChanged(); } ); if (kwinApp()->x11Connection()) { m_x11WindowPropertyNotify = std::make_unique(this); } // connect all clients for (Client *c : ws->clientList()) { setupClientConnections(c); } for (Unmanaged *u : ws->unmanagedList()) { setupUnmanagedConnections(u); } + for (InternalClient *client : ws->internalClients()) { + setupAbstractClientConnections(client); + } if (auto w = waylandServer()) { connect(w, &WaylandServer::shellClientAdded, this, [this](ShellClient *c) { if (c->readyForPainting()) slotShellClientShown(c); else connect(c, &Toplevel::windowShown, this, &EffectsHandlerImpl::slotShellClientShown); } ); const auto clients = waylandServer()->clients(); for (ShellClient *c : clients) { if (c->readyForPainting()) { setupAbstractClientConnections(c); } else { connect(c, &Toplevel::windowShown, this, &EffectsHandlerImpl::slotShellClientShown); } } } reconfigure(); } EffectsHandlerImpl::~EffectsHandlerImpl() { unloadAllEffects(); } void EffectsHandlerImpl::unloadAllEffects() { for (const EffectPair &pair : loaded_effects) { destroyEffect(pair.second); } effect_order.clear(); m_effectLoader->clear(); effectsChanged(); } void EffectsHandlerImpl::setupAbstractClientConnections(AbstractClient* c) { connect(c, &AbstractClient::windowClosed, this, &EffectsHandlerImpl::slotWindowClosed); connect(c, static_cast(&AbstractClient::clientMaximizedStateChanged), this, &EffectsHandlerImpl::slotClientMaximized); connect(c, &AbstractClient::clientStartUserMovedResized, this, [this](AbstractClient *c) { emit windowStartUserMovedResized(c->effectWindow()); } ); connect(c, &AbstractClient::clientStepUserMovedResized, this, [this](AbstractClient *c, const QRect &geometry) { emit windowStepUserMovedResized(c->effectWindow(), geometry); } ); connect(c, &AbstractClient::clientFinishUserMovedResized, this, [this](AbstractClient *c) { emit windowFinishUserMovedResized(c->effectWindow()); } ); connect(c, &AbstractClient::opacityChanged, this, &EffectsHandlerImpl::slotOpacityChanged); connect(c, &AbstractClient::clientMinimized, this, [this](AbstractClient *c, bool animate) { // TODO: notify effects even if it should not animate? if (animate) { emit windowMinimized(c->effectWindow()); } } ); connect(c, &AbstractClient::clientUnminimized, this, [this](AbstractClient* c, bool animate) { // TODO: notify effects even if it should not animate? if (animate) { emit windowUnminimized(c->effectWindow()); } } ); connect(c, &AbstractClient::modalChanged, this, &EffectsHandlerImpl::slotClientModalityChanged); connect(c, &AbstractClient::geometryShapeChanged, this, &EffectsHandlerImpl::slotGeometryShapeChanged); connect(c, &AbstractClient::damaged, this, &EffectsHandlerImpl::slotWindowDamaged); connect(c, &AbstractClient::unresponsiveChanged, this, [this, c](bool unresponsive) { emit windowUnresponsiveChanged(c->effectWindow(), unresponsive); } ); connect(c, &AbstractClient::windowShown, this, [this](Toplevel *c) { emit windowShown(c->effectWindow()); } ); connect(c, &AbstractClient::windowHidden, this, [this](Toplevel *c) { emit windowHidden(c->effectWindow()); } ); connect(c, &AbstractClient::keepAboveChanged, this, [this, c](bool above) { Q_UNUSED(above) emit windowKeepAboveChanged(c->effectWindow()); } ); connect(c, &AbstractClient::keepBelowChanged, this, [this, c](bool below) { Q_UNUSED(below) emit windowKeepBelowChanged(c->effectWindow()); } ); connect(c, &AbstractClient::fullScreenChanged, this, [this, c]() { emit windowFullScreenChanged(c->effectWindow()); } ); } void EffectsHandlerImpl::setupClientConnections(Client* c) { setupAbstractClientConnections(c); connect(c, &Client::paddingChanged, this, &EffectsHandlerImpl::slotPaddingChanged); } void EffectsHandlerImpl::setupUnmanagedConnections(Unmanaged* u) { connect(u, &Unmanaged::windowClosed, this, &EffectsHandlerImpl::slotWindowClosed); connect(u, &Unmanaged::opacityChanged, this, &EffectsHandlerImpl::slotOpacityChanged); connect(u, &Unmanaged::geometryShapeChanged, this, &EffectsHandlerImpl::slotGeometryShapeChanged); connect(u, &Unmanaged::paddingChanged, this, &EffectsHandlerImpl::slotPaddingChanged); connect(u, &Unmanaged::damaged, this, &EffectsHandlerImpl::slotWindowDamaged); } void EffectsHandlerImpl::reconfigure() { m_effectLoader->queryAndLoadAll(); } // the idea is that effects call this function again which calls the next one void EffectsHandlerImpl::prePaintScreen(ScreenPrePaintData& data, int time) { if (m_currentPaintScreenIterator != m_activeEffects.constEnd()) { (*m_currentPaintScreenIterator++)->prePaintScreen(data, time); --m_currentPaintScreenIterator; } // no special final code } void EffectsHandlerImpl::paintScreen(int mask, QRegion region, ScreenPaintData& data) { if (m_currentPaintScreenIterator != m_activeEffects.constEnd()) { (*m_currentPaintScreenIterator++)->paintScreen(mask, region, data); --m_currentPaintScreenIterator; } else m_scene->finalPaintScreen(mask, region, data); } void EffectsHandlerImpl::paintDesktop(int desktop, int mask, QRegion region, ScreenPaintData &data) { if (desktop < 1 || desktop > numberOfDesktops()) { return; } m_currentRenderedDesktop = desktop; m_desktopRendering = true; // save the paint screen iterator EffectsIterator savedIterator = m_currentPaintScreenIterator; m_currentPaintScreenIterator = m_activeEffects.constBegin(); effects->paintScreen(mask, region, data); // restore the saved iterator m_currentPaintScreenIterator = savedIterator; m_desktopRendering = false; } void EffectsHandlerImpl::postPaintScreen() { if (m_currentPaintScreenIterator != m_activeEffects.constEnd()) { (*m_currentPaintScreenIterator++)->postPaintScreen(); --m_currentPaintScreenIterator; } // no special final code } void EffectsHandlerImpl::prePaintWindow(EffectWindow* w, WindowPrePaintData& data, int time) { if (m_currentPaintWindowIterator != m_activeEffects.constEnd()) { (*m_currentPaintWindowIterator++)->prePaintWindow(w, data, time); --m_currentPaintWindowIterator; } // no special final code } void EffectsHandlerImpl::paintWindow(EffectWindow* w, int mask, QRegion region, WindowPaintData& data) { if (m_currentPaintWindowIterator != m_activeEffects.constEnd()) { (*m_currentPaintWindowIterator++)->paintWindow(w, mask, region, data); --m_currentPaintWindowIterator; } else m_scene->finalPaintWindow(static_cast(w), mask, region, data); } void EffectsHandlerImpl::paintEffectFrame(EffectFrame* frame, QRegion region, double opacity, double frameOpacity) { if (m_currentPaintEffectFrameIterator != m_activeEffects.constEnd()) { (*m_currentPaintEffectFrameIterator++)->paintEffectFrame(frame, region, opacity, frameOpacity); --m_currentPaintEffectFrameIterator; } else { const EffectFrameImpl* frameImpl = static_cast(frame); frameImpl->finalRender(region, opacity, frameOpacity); } } void EffectsHandlerImpl::postPaintWindow(EffectWindow* w) { if (m_currentPaintWindowIterator != m_activeEffects.constEnd()) { (*m_currentPaintWindowIterator++)->postPaintWindow(w); --m_currentPaintWindowIterator; } // no special final code } Effect *EffectsHandlerImpl::provides(Effect::Feature ef) { for (int i = 0; i < loaded_effects.size(); ++i) if (loaded_effects.at(i).second->provides(ef)) return loaded_effects.at(i).second; return nullptr; } void EffectsHandlerImpl::drawWindow(EffectWindow* w, int mask, QRegion region, WindowPaintData& data) { if (m_currentDrawWindowIterator != m_activeEffects.constEnd()) { (*m_currentDrawWindowIterator++)->drawWindow(w, mask, region, data); --m_currentDrawWindowIterator; } else m_scene->finalDrawWindow(static_cast(w), mask, region, data); } void EffectsHandlerImpl::buildQuads(EffectWindow* w, WindowQuadList& quadList) { static bool initIterator = true; if (initIterator) { m_currentBuildQuadsIterator = m_activeEffects.constBegin(); initIterator = false; } if (m_currentBuildQuadsIterator != m_activeEffects.constEnd()) { (*m_currentBuildQuadsIterator++)->buildQuads(w, quadList); --m_currentBuildQuadsIterator; } if (m_currentBuildQuadsIterator == m_activeEffects.constBegin()) initIterator = true; } bool EffectsHandlerImpl::hasDecorationShadows() const { return false; } bool EffectsHandlerImpl::decorationsHaveAlpha() const { return true; } bool EffectsHandlerImpl::decorationSupportsBlurBehind() const { return Decoration::DecorationBridge::self()->needsBlur(); } // start another painting pass void EffectsHandlerImpl::startPaint() { m_activeEffects.clear(); m_activeEffects.reserve(loaded_effects.count()); for(QVector< KWin::EffectPair >::const_iterator it = loaded_effects.constBegin(); it != loaded_effects.constEnd(); ++it) { if (it->second->isActive()) { m_activeEffects << it->second; } } m_currentDrawWindowIterator = m_activeEffects.constBegin(); m_currentPaintWindowIterator = m_activeEffects.constBegin(); m_currentPaintScreenIterator = m_activeEffects.constBegin(); m_currentPaintEffectFrameIterator = m_activeEffects.constBegin(); } void EffectsHandlerImpl::slotClientMaximized(KWin::AbstractClient *c, MaximizeMode maxMode) { bool horizontal = false; bool vertical = false; switch (maxMode) { case MaximizeHorizontal: horizontal = true; break; case MaximizeVertical: vertical = true; break; case MaximizeFull: horizontal = true; vertical = true; break; case MaximizeRestore: // fall through default: // default - nothing to do break; } if (EffectWindowImpl *w = c->effectWindow()) { emit windowMaximizedStateChanged(w, horizontal, vertical); } } void EffectsHandlerImpl::slotOpacityChanged(Toplevel *t, qreal oldOpacity) { if (t->opacity() == oldOpacity || !t->effectWindow()) { return; } emit windowOpacityChanged(t->effectWindow(), oldOpacity, (qreal)t->opacity()); } void EffectsHandlerImpl::slotClientShown(KWin::Toplevel *t) { Q_ASSERT(qobject_cast(t)); Client *c = static_cast(t); disconnect(c, &Toplevel::windowShown, this, &EffectsHandlerImpl::slotClientShown); setupClientConnections(c); emit windowAdded(c->effectWindow()); } void EffectsHandlerImpl::slotShellClientShown(Toplevel *t) { ShellClient *c = static_cast(t); setupAbstractClientConnections(c); emit windowAdded(t->effectWindow()); } void EffectsHandlerImpl::slotUnmanagedShown(KWin::Toplevel *t) { // regardless, unmanaged windows are -yet?- not synced anyway Q_ASSERT(qobject_cast(t)); Unmanaged *u = static_cast(t); setupUnmanagedConnections(u); emit windowAdded(u->effectWindow()); } void EffectsHandlerImpl::slotWindowClosed(KWin::Toplevel *c, KWin::Deleted *d) { c->disconnect(this); if (d) { emit windowClosed(c->effectWindow()); } } void EffectsHandlerImpl::slotClientModalityChanged() { emit windowModalityChanged(static_cast(sender())->effectWindow()); } void EffectsHandlerImpl::slotCurrentTabAboutToChange(EffectWindow *from, EffectWindow *to) { emit currentTabAboutToChange(from, to); } void EffectsHandlerImpl::slotTabAdded(EffectWindow* w, EffectWindow* to) { emit tabAdded(w, to); } void EffectsHandlerImpl::slotTabRemoved(EffectWindow *w, EffectWindow* leaderOfFormerGroup) { emit tabRemoved(w, leaderOfFormerGroup); } void EffectsHandlerImpl::slotWindowDamaged(Toplevel* t, const QRect& r) { if (!t->effectWindow()) { // can happen during tear down of window return; } emit windowDamaged(t->effectWindow(), r); } void EffectsHandlerImpl::slotGeometryShapeChanged(Toplevel* t, const QRect& old) { // during late cleanup effectWindow() may be already NULL // in some functions that may still call this if (t == nullptr || t->effectWindow() == nullptr) return; emit windowGeometryShapeChanged(t->effectWindow(), old); } void EffectsHandlerImpl::slotPaddingChanged(Toplevel* t, const QRect& old) { // during late cleanup effectWindow() may be already NULL // in some functions that may still call this if (t == nullptr || t->effectWindow() == nullptr) return; emit windowPaddingChanged(t->effectWindow(), old); } void EffectsHandlerImpl::setActiveFullScreenEffect(Effect* e) { if (fullscreen_effect == e) { return; } const bool activeChanged = (e == nullptr || fullscreen_effect == nullptr); fullscreen_effect = e; emit activeFullScreenEffectChanged(); if (activeChanged) { emit hasActiveFullScreenEffectChanged(); } } Effect* EffectsHandlerImpl::activeFullScreenEffect() const { return fullscreen_effect; } bool EffectsHandlerImpl::hasActiveFullScreenEffect() const { return fullscreen_effect; } bool EffectsHandlerImpl::grabKeyboard(Effect* effect) { if (keyboard_grab_effect != nullptr) return false; if (!doGrabKeyboard()) { return false; } keyboard_grab_effect = effect; return true; } bool EffectsHandlerImpl::doGrabKeyboard() { return true; } void EffectsHandlerImpl::ungrabKeyboard() { Q_ASSERT(keyboard_grab_effect != nullptr); doUngrabKeyboard(); keyboard_grab_effect = nullptr; } void EffectsHandlerImpl::doUngrabKeyboard() { } void EffectsHandlerImpl::grabbedKeyboardEvent(QKeyEvent* e) { if (keyboard_grab_effect != nullptr) keyboard_grab_effect->grabbedKeyboardEvent(e); } void EffectsHandlerImpl::startMouseInterception(Effect *effect, Qt::CursorShape shape) { if (m_grabbedMouseEffects.contains(effect)) { return; } m_grabbedMouseEffects.append(effect); if (m_grabbedMouseEffects.size() != 1) { return; } doStartMouseInterception(shape); } void EffectsHandlerImpl::doStartMouseInterception(Qt::CursorShape shape) { input()->pointer()->setEffectsOverrideCursor(shape); } void EffectsHandlerImpl::stopMouseInterception(Effect *effect) { if (!m_grabbedMouseEffects.contains(effect)) { return; } m_grabbedMouseEffects.removeAll(effect); if (m_grabbedMouseEffects.isEmpty()) { doStopMouseInterception(); } } void EffectsHandlerImpl::doStopMouseInterception() { input()->pointer()->removeEffectsOverrideCursor(); } bool EffectsHandlerImpl::isMouseInterception() const { return m_grabbedMouseEffects.count() > 0; } bool EffectsHandlerImpl::touchDown(qint32 id, const QPointF &pos, quint32 time) { // TODO: reverse call order? for (auto it = loaded_effects.constBegin(); it != loaded_effects.constEnd(); ++it) { if (it->second->touchDown(id, pos, time)) { return true; } } return false; } bool EffectsHandlerImpl::touchMotion(qint32 id, const QPointF &pos, quint32 time) { // TODO: reverse call order? for (auto it = loaded_effects.constBegin(); it != loaded_effects.constEnd(); ++it) { if (it->second->touchMotion(id, pos, time)) { return true; } } return false; } bool EffectsHandlerImpl::touchUp(qint32 id, quint32 time) { // TODO: reverse call order? for (auto it = loaded_effects.constBegin(); it != loaded_effects.constEnd(); ++it) { if (it->second->touchUp(id, time)) { return true; } } return false; } void EffectsHandlerImpl::registerGlobalShortcut(const QKeySequence &shortcut, QAction *action) { input()->registerShortcut(shortcut, action); } void EffectsHandlerImpl::registerPointerShortcut(Qt::KeyboardModifiers modifiers, Qt::MouseButton pointerButtons, QAction *action) { input()->registerPointerShortcut(modifiers, pointerButtons, action); } void EffectsHandlerImpl::registerAxisShortcut(Qt::KeyboardModifiers modifiers, PointerAxisDirection axis, QAction *action) { input()->registerAxisShortcut(modifiers, axis, action); } void EffectsHandlerImpl::registerTouchpadSwipeShortcut(SwipeDirection direction, QAction *action) { input()->registerTouchpadSwipeShortcut(direction, action); } void* EffectsHandlerImpl::getProxy(QString name) { for (QVector< EffectPair >::const_iterator it = loaded_effects.constBegin(); it != loaded_effects.constEnd(); ++it) if ((*it).first == name) return (*it).second->proxy(); return nullptr; } void EffectsHandlerImpl::startMousePolling() { if (Cursor::self()) Cursor::self()->startMousePolling(); } void EffectsHandlerImpl::stopMousePolling() { if (Cursor::self()) Cursor::self()->stopMousePolling(); } bool EffectsHandlerImpl::hasKeyboardGrab() const { return keyboard_grab_effect != nullptr; } void EffectsHandlerImpl::desktopResized(const QSize &size) { m_scene->screenGeometryChanged(size); emit screenGeometryChanged(size); } void EffectsHandlerImpl::registerPropertyType(long atom, bool reg) { if (reg) ++registered_atoms[ atom ]; // initialized to 0 if not present yet else { if (--registered_atoms[ atom ] == 0) registered_atoms.remove(atom); } } xcb_atom_t EffectsHandlerImpl::announceSupportProperty(const QByteArray &propertyName, Effect *effect) { PropertyEffectMap::iterator it = m_propertiesForEffects.find(propertyName); if (it != m_propertiesForEffects.end()) { // property has already been registered for an effect // just append Effect and return the atom stored in m_managedProperties if (!it.value().contains(effect)) { it.value().append(effect); } return m_managedProperties.value(propertyName, XCB_ATOM_NONE); } m_propertiesForEffects.insert(propertyName, QList() << effect); const auto atom = registerSupportProperty(propertyName); if (atom == XCB_ATOM_NONE) { return atom; } m_compositor->keepSupportProperty(atom); m_managedProperties.insert(propertyName, atom); registerPropertyType(atom, true); return atom; } void EffectsHandlerImpl::removeSupportProperty(const QByteArray &propertyName, Effect *effect) { PropertyEffectMap::iterator it = m_propertiesForEffects.find(propertyName); if (it == m_propertiesForEffects.end()) { // property is not registered - nothing to do return; } if (!it.value().contains(effect)) { // property is not registered for given effect - nothing to do return; } it.value().removeAll(effect); if (!it.value().isEmpty()) { // property still registered for another effect - nothing further to do return; } const xcb_atom_t atom = m_managedProperties.take(propertyName); registerPropertyType(atom, false); m_propertiesForEffects.remove(propertyName); m_compositor->removeSupportProperty(atom); // delayed removal } QByteArray EffectsHandlerImpl::readRootProperty(long atom, long type, int format) const { if (!kwinApp()->x11Connection()) { return QByteArray(); } return readWindowProperty(kwinApp()->x11RootWindow(), atom, type, format); } void EffectsHandlerImpl::activateWindow(EffectWindow* c) { if (auto cl = qobject_cast(static_cast(c)->window())) { Workspace::self()->activateClient(cl, true); } } EffectWindow* EffectsHandlerImpl::activeWindow() const { return Workspace::self()->activeClient() ? Workspace::self()->activeClient()->effectWindow() : nullptr; } void EffectsHandlerImpl::moveWindow(EffectWindow* w, const QPoint& pos, bool snap, double snapAdjust) { auto cl = qobject_cast(static_cast(w)->window()); if (!cl || !cl->isMovable()) return; if (snap) cl->move(Workspace::self()->adjustClientPosition(cl, pos, true, snapAdjust)); else cl->move(pos); } void EffectsHandlerImpl::windowToDesktop(EffectWindow* w, int desktop) { auto cl = qobject_cast(static_cast(w)->window()); if (cl && !cl->isDesktop() && !cl->isDock()) { Workspace::self()->sendClientToDesktop(cl, desktop, true); } } void EffectsHandlerImpl::windowToDesktops(EffectWindow *w, const QVector &desktopIds) { AbstractClient* cl = qobject_cast< AbstractClient* >(static_cast(w)->window()); if (!cl || cl->isDesktop() || cl->isDock()) { return; } QVector desktops; desktops.reserve(desktopIds.count()); for (uint x11Id: desktopIds) { if (x11Id > VirtualDesktopManager::self()->count()) { continue; } VirtualDesktop *d = VirtualDesktopManager::self()->desktopForX11Id(x11Id); Q_ASSERT(d); if (desktops.contains(d)) { continue; } desktops << d; } cl->setDesktops(desktops); } void EffectsHandlerImpl::windowToScreen(EffectWindow* w, int screen) { auto cl = qobject_cast(static_cast(w)->window()); if (cl && !cl->isDesktop() && !cl->isDock()) Workspace::self()->sendClientToScreen(cl, screen); } void EffectsHandlerImpl::setShowingDesktop(bool showing) { Workspace::self()->setShowingDesktop(showing); } QString EffectsHandlerImpl::currentActivity() const { #ifdef KWIN_BUILD_ACTIVITIES if (!Activities::self()) { return QString(); } return Activities::self()->current(); #else return QString(); #endif } int EffectsHandlerImpl::currentDesktop() const { return VirtualDesktopManager::self()->current(); } int EffectsHandlerImpl::numberOfDesktops() const { return VirtualDesktopManager::self()->count(); } void EffectsHandlerImpl::setCurrentDesktop(int desktop) { VirtualDesktopManager::self()->setCurrent(desktop); } void EffectsHandlerImpl::setNumberOfDesktops(int desktops) { VirtualDesktopManager::self()->setCount(desktops); } QSize EffectsHandlerImpl::desktopGridSize() const { return VirtualDesktopManager::self()->grid().size(); } int EffectsHandlerImpl::desktopGridWidth() const { return desktopGridSize().width(); } int EffectsHandlerImpl::desktopGridHeight() const { return desktopGridSize().height(); } int EffectsHandlerImpl::workspaceWidth() const { return desktopGridWidth() * screens()->size().width(); } int EffectsHandlerImpl::workspaceHeight() const { return desktopGridHeight() * screens()->size().height(); } int EffectsHandlerImpl::desktopAtCoords(QPoint coords) const { if (auto vd = VirtualDesktopManager::self()->grid().at(coords)) { return vd->x11DesktopNumber(); } return 0; } QPoint EffectsHandlerImpl::desktopGridCoords(int id) const { return VirtualDesktopManager::self()->grid().gridCoords(id); } QPoint EffectsHandlerImpl::desktopCoords(int id) const { QPoint coords = VirtualDesktopManager::self()->grid().gridCoords(id); if (coords.x() == -1) return QPoint(-1, -1); const QSize displaySize = screens()->size(); return QPoint(coords.x() * displaySize.width(), coords.y() * displaySize.height()); } int EffectsHandlerImpl::desktopAbove(int desktop, bool wrap) const { return getDesktop(desktop, wrap); } int EffectsHandlerImpl::desktopToRight(int desktop, bool wrap) const { return getDesktop(desktop, wrap); } int EffectsHandlerImpl::desktopBelow(int desktop, bool wrap) const { return getDesktop(desktop, wrap); } int EffectsHandlerImpl::desktopToLeft(int desktop, bool wrap) const { return getDesktop(desktop, wrap); } QString EffectsHandlerImpl::desktopName(int desktop) const { return VirtualDesktopManager::self()->name(desktop); } bool EffectsHandlerImpl::optionRollOverDesktops() const { return options->isRollOverDesktops(); } double EffectsHandlerImpl::animationTimeFactor() const { return options->animationTimeFactor(); } WindowQuadType EffectsHandlerImpl::newWindowQuadType() { return WindowQuadType(next_window_quad_type++); } EffectWindow* EffectsHandlerImpl::findWindow(WId id) const { if (Client* w = Workspace::self()->findClient(Predicate::WindowMatch, id)) return w->effectWindow(); if (Unmanaged* w = Workspace::self()->findUnmanaged(id)) return w->effectWindow(); if (waylandServer()) { if (ShellClient *w = waylandServer()->findClient(id)) { return w->effectWindow(); } } return nullptr; } EffectWindow* EffectsHandlerImpl::findWindow(KWayland::Server::SurfaceInterface *surf) const { if (waylandServer()) { if (ShellClient *w = waylandServer()->findClient(surf)) { return w->effectWindow(); } } return nullptr; } EffectWindow *EffectsHandlerImpl::findWindow(QWindow *w) const { - if (waylandServer()) { - if (auto c = waylandServer()->findClient(w)) { - return c->effectWindow(); - } - } - if (auto u = Workspace::self()->findUnmanaged(w->winId())) { - return u->effectWindow(); + if (Toplevel *toplevel = workspace()->findInternal(w)) { + return toplevel->effectWindow(); } return nullptr; } EffectWindow *EffectsHandlerImpl::findWindow(const QUuid &id) const { if (const auto client = workspace()->findAbstractClient([&id] (const AbstractClient *c) { return c->internalId() == id; })) { return client->effectWindow(); } if (const auto unmanaged = workspace()->findUnmanaged([&id] (const Unmanaged *c) { return c->internalId() == id; })) { return unmanaged->effectWindow(); } return nullptr; } EffectWindowList EffectsHandlerImpl::stackingOrder() const { ToplevelList list = Workspace::self()->xStackingOrder(); EffectWindowList ret; for (Toplevel *t : list) { if (EffectWindow *w = effectWindow(t)) ret.append(w); } return ret; } void EffectsHandlerImpl::setElevatedWindow(KWin::EffectWindow* w, bool set) { elevated_windows.removeAll(w); if (set) elevated_windows.append(w); } void EffectsHandlerImpl::setTabBoxWindow(EffectWindow* w) { #ifdef KWIN_BUILD_TABBOX if (auto c = qobject_cast(static_cast(w)->window())) { TabBox::TabBox::self()->setCurrentClient(c); } #else Q_UNUSED(w) #endif } void EffectsHandlerImpl::setTabBoxDesktop(int desktop) { #ifdef KWIN_BUILD_TABBOX TabBox::TabBox::self()->setCurrentDesktop(desktop); #else Q_UNUSED(desktop) #endif } EffectWindowList EffectsHandlerImpl::currentTabBoxWindowList() const { #ifdef KWIN_BUILD_TABBOX const auto clients = TabBox::TabBox::self()->currentClientList(); EffectWindowList ret; ret.reserve(clients.size()); std::transform(std::cbegin(clients), std::cend(clients), std::back_inserter(ret), [](auto client) { return client->effectWindow(); }); return ret; #else return EffectWindowList(); #endif } void EffectsHandlerImpl::refTabBox() { #ifdef KWIN_BUILD_TABBOX TabBox::TabBox::self()->reference(); #endif } void EffectsHandlerImpl::unrefTabBox() { #ifdef KWIN_BUILD_TABBOX TabBox::TabBox::self()->unreference(); #endif } void EffectsHandlerImpl::closeTabBox() { #ifdef KWIN_BUILD_TABBOX TabBox::TabBox::self()->close(); #endif } QList< int > EffectsHandlerImpl::currentTabBoxDesktopList() const { #ifdef KWIN_BUILD_TABBOX return TabBox::TabBox::self()->currentDesktopList(); #else return QList< int >(); #endif } int EffectsHandlerImpl::currentTabBoxDesktop() const { #ifdef KWIN_BUILD_TABBOX return TabBox::TabBox::self()->currentDesktop(); #else return -1; #endif } EffectWindow* EffectsHandlerImpl::currentTabBoxWindow() const { #ifdef KWIN_BUILD_TABBOX if (auto c = TabBox::TabBox::self()->currentClient()) return c->effectWindow(); #endif return nullptr; } void EffectsHandlerImpl::addRepaintFull() { m_compositor->addRepaintFull(); } void EffectsHandlerImpl::addRepaint(const QRect& r) { m_compositor->addRepaint(r); } void EffectsHandlerImpl::addRepaint(const QRegion& r) { m_compositor->addRepaint(r); } void EffectsHandlerImpl::addRepaint(int x, int y, int w, int h) { m_compositor->addRepaint(x, y, w, h); } int EffectsHandlerImpl::activeScreen() const { return screens()->current(); } int EffectsHandlerImpl::numScreens() const { return screens()->count(); } int EffectsHandlerImpl::screenNumber(const QPoint& pos) const { return screens()->number(pos); } QRect EffectsHandlerImpl::clientArea(clientAreaOption opt, int screen, int desktop) const { return Workspace::self()->clientArea(opt, screen, desktop); } QRect EffectsHandlerImpl::clientArea(clientAreaOption opt, const EffectWindow* c) const { const Toplevel* t = static_cast< const EffectWindowImpl* >(c)->window(); if (const auto *cl = qobject_cast(t)) { return Workspace::self()->clientArea(opt, cl); } else { return Workspace::self()->clientArea(opt, t->geometry().center(), VirtualDesktopManager::self()->current()); } } QRect EffectsHandlerImpl::clientArea(clientAreaOption opt, const QPoint& p, int desktop) const { return Workspace::self()->clientArea(opt, p, desktop); } QRect EffectsHandlerImpl::virtualScreenGeometry() const { return screens()->geometry(); } QSize EffectsHandlerImpl::virtualScreenSize() const { return screens()->size(); } void EffectsHandlerImpl::defineCursor(Qt::CursorShape shape) { input()->pointer()->setEffectsOverrideCursor(shape); } bool EffectsHandlerImpl::checkInputWindowEvent(QMouseEvent *e) { if (m_grabbedMouseEffects.isEmpty()) { return false; } foreach (Effect *effect, m_grabbedMouseEffects) { effect->windowInputMouseEvent(e); } return true; } bool EffectsHandlerImpl::checkInputWindowEvent(QWheelEvent *e) { if (m_grabbedMouseEffects.isEmpty()) { return false; } foreach (Effect *effect, m_grabbedMouseEffects) { effect->windowInputMouseEvent(e); } return true; } void EffectsHandlerImpl::connectNotify(const QMetaMethod &signal) { if (signal == QMetaMethod::fromSignal(&EffectsHandler::cursorShapeChanged)) { if (!m_trackingCursorChanges) { connect(Cursor::self(), &Cursor::cursorChanged, this, &EffectsHandler::cursorShapeChanged); Cursor::self()->startCursorTracking(); } ++m_trackingCursorChanges; } EffectsHandler::connectNotify(signal); } void EffectsHandlerImpl::disconnectNotify(const QMetaMethod &signal) { if (signal == QMetaMethod::fromSignal(&EffectsHandler::cursorShapeChanged)) { Q_ASSERT(m_trackingCursorChanges > 0); if (!--m_trackingCursorChanges) { Cursor::self()->stopCursorTracking(); disconnect(Cursor::self(), &Cursor::cursorChanged, this, &EffectsHandler::cursorShapeChanged); } } EffectsHandler::disconnectNotify(signal); } void EffectsHandlerImpl::checkInputWindowStacking() { if (m_grabbedMouseEffects.isEmpty()) { return; } doCheckInputWindowStacking(); } void EffectsHandlerImpl::doCheckInputWindowStacking() { } QPoint EffectsHandlerImpl::cursorPos() const { return Cursor::pos(); } void EffectsHandlerImpl::reserveElectricBorder(ElectricBorder border, Effect *effect) { ScreenEdges::self()->reserve(border, effect, "borderActivated"); } void EffectsHandlerImpl::unreserveElectricBorder(ElectricBorder border, Effect *effect) { ScreenEdges::self()->unreserve(border, effect); } void EffectsHandlerImpl::registerTouchBorder(ElectricBorder border, QAction *action) { ScreenEdges::self()->reserveTouch(border, action); } void EffectsHandlerImpl::unregisterTouchBorder(ElectricBorder border, QAction *action) { ScreenEdges::self()->unreserveTouch(border, action); } unsigned long EffectsHandlerImpl::xrenderBufferPicture() { return m_scene->xrenderBufferPicture(); } QPainter *EffectsHandlerImpl::scenePainter() { return m_scene->scenePainter(); } void EffectsHandlerImpl::toggleEffect(const QString& name) { if (isEffectLoaded(name)) unloadEffect(name); else loadEffect(name); } QStringList EffectsHandlerImpl::loadedEffects() const { QStringList listModules; listModules.reserve(loaded_effects.count()); std::transform(loaded_effects.constBegin(), loaded_effects.constEnd(), std::back_inserter(listModules), [](const EffectPair &pair) { return pair.first; }); return listModules; } QStringList EffectsHandlerImpl::listOfEffects() const { return m_effectLoader->listOfKnownEffects(); } bool EffectsHandlerImpl::loadEffect(const QString& name) { makeOpenGLContextCurrent(); m_compositor->addRepaintFull(); return m_effectLoader->loadEffect(name); } void EffectsHandlerImpl::unloadEffect(const QString& name) { auto it = std::find_if(effect_order.begin(), effect_order.end(), [name](EffectPair &pair) { return pair.first == name; } ); if (it == effect_order.end()) { qCDebug(KWIN_CORE) << "EffectsHandler::unloadEffect : Effect not loaded :" << name; return; } qCDebug(KWIN_CORE) << "EffectsHandler::unloadEffect : Unloading Effect :" << name; destroyEffect((*it).second); effect_order.erase(it); effectsChanged(); m_compositor->addRepaintFull(); } void EffectsHandlerImpl::destroyEffect(Effect *effect) { makeOpenGLContextCurrent(); if (fullscreen_effect == effect) { setActiveFullScreenEffect(nullptr); } if (keyboard_grab_effect == effect) { ungrabKeyboard(); } stopMouseInterception(effect); const QList properties = m_propertiesForEffects.keys(); for (const QByteArray &property : properties) { removeSupportProperty(property, effect); } delete effect; } void EffectsHandlerImpl::reconfigureEffect(const QString& name) { for (QVector< EffectPair >::const_iterator it = loaded_effects.constBegin(); it != loaded_effects.constEnd(); ++it) if ((*it).first == name) { kwinApp()->config()->reparseConfiguration(); makeOpenGLContextCurrent(); (*it).second->reconfigure(Effect::ReconfigureAll); return; } } bool EffectsHandlerImpl::isEffectLoaded(const QString& name) const { auto it = std::find_if(loaded_effects.constBegin(), loaded_effects.constEnd(), [&name](const EffectPair &pair) { return pair.first == name; }); return it != loaded_effects.constEnd(); } bool EffectsHandlerImpl::isEffectSupported(const QString &name) { // If the effect is loaded, it is obviously supported. if (isEffectLoaded(name)) { return true; } // next checks might require a context makeOpenGLContextCurrent(); m_compositor->addRepaintFull(); return m_effectLoader->isEffectSupported(name); } QList EffectsHandlerImpl::areEffectsSupported(const QStringList &names) { QList retList; retList.reserve(names.count()); std::transform(names.constBegin(), names.constEnd(), std::back_inserter(retList), [this](const QString &name) { return isEffectSupported(name); }); return retList; } void EffectsHandlerImpl::reloadEffect(Effect *effect) { QString effectName; for (QVector< EffectPair >::const_iterator it = loaded_effects.constBegin(); it != loaded_effects.constEnd(); ++it) { if ((*it).second == effect) { effectName = (*it).first; break; } } if (!effectName.isNull()) { unloadEffect(effectName); m_effectLoader->loadEffect(effectName); } } void EffectsHandlerImpl::effectsChanged() { loaded_effects.clear(); m_activeEffects.clear(); // it's possible to have a reconfigure and a quad rebuild between two paint cycles - bug #308201 loaded_effects.reserve(effect_order.count()); std::copy(effect_order.constBegin(), effect_order.constEnd(), std::back_inserter(loaded_effects)); m_activeEffects.reserve(loaded_effects.count()); } QStringList EffectsHandlerImpl::activeEffects() const { QStringList ret; for(QVector< KWin::EffectPair >::const_iterator it = loaded_effects.constBegin(), end = loaded_effects.constEnd(); it != end; ++it) { if (it->second->isActive()) { ret << it->first; } } return ret; } KWayland::Server::Display *EffectsHandlerImpl::waylandDisplay() const { if (waylandServer()) { return waylandServer()->display(); } return nullptr; } EffectFrame* EffectsHandlerImpl::effectFrame(EffectFrameStyle style, bool staticSize, const QPoint& position, Qt::Alignment alignment) const { return new EffectFrameImpl(style, staticSize, position, alignment); } QVariant EffectsHandlerImpl::kwinOption(KWinOption kwopt) { switch (kwopt) { case CloseButtonCorner: // TODO: this could become per window and be derived from the actual position in the deco return Decoration::DecorationBridge::self()->settings()->decorationButtonsLeft().contains(KDecoration2::DecorationButtonType::Close) ? Qt::TopLeftCorner : Qt::TopRightCorner; case SwitchDesktopOnScreenEdge: return ScreenEdges::self()->isDesktopSwitching(); case SwitchDesktopOnScreenEdgeMovingWindows: return ScreenEdges::self()->isDesktopSwitchingMovingClients(); default: return QVariant(); // an invalid one } } QString EffectsHandlerImpl::supportInformation(const QString &name) const { auto it = std::find_if(loaded_effects.constBegin(), loaded_effects.constEnd(), [name](const EffectPair &pair) { return pair.first == name; }); if (it == loaded_effects.constEnd()) { return QString(); } QString support((*it).first + QLatin1String(":\n")); const QMetaObject *metaOptions = (*it).second->metaObject(); for (int i=0; ipropertyCount(); ++i) { const QMetaProperty property = metaOptions->property(i); if (qstrcmp(property.name(), "objectName") == 0) { continue; } support += QString::fromUtf8(property.name()) + QLatin1String(": ") + (*it).second->property(property.name()).toString() + QLatin1Char('\n'); } return support; } bool EffectsHandlerImpl::isScreenLocked() const { return ScreenLockerWatcher::self()->isLocked(); } QString EffectsHandlerImpl::debug(const QString& name, const QString& parameter) const { QString internalName = name.toLower();; for (QVector< EffectPair >::const_iterator it = loaded_effects.constBegin(); it != loaded_effects.constEnd(); ++it) { if ((*it).first == internalName) { return it->second->debug(parameter); } } return QString(); } bool EffectsHandlerImpl::makeOpenGLContextCurrent() { return m_scene->makeOpenGLContextCurrent(); } void EffectsHandlerImpl::doneOpenGLContextCurrent() { m_scene->doneOpenGLContextCurrent(); } bool EffectsHandlerImpl::animationsSupported() const { static const QByteArray forceEnvVar = qgetenv("KWIN_EFFECTS_FORCE_ANIMATIONS"); if (!forceEnvVar.isEmpty()) { static const int forceValue = forceEnvVar.toInt(); return forceValue == 1; } return m_scene->animationsSupported(); } void EffectsHandlerImpl::highlightWindows(const QVector &windows) { Effect *e = provides(Effect::HighlightWindows); if (!e) { return; } e->perform(Effect::HighlightWindows, QVariantList{QVariant::fromValue(windows)}); } PlatformCursorImage EffectsHandlerImpl::cursorImage() const { return kwinApp()->platform()->cursorImage(); } void EffectsHandlerImpl::hideCursor() { kwinApp()->platform()->hideCursor(); } void EffectsHandlerImpl::showCursor() { kwinApp()->platform()->showCursor(); } void EffectsHandlerImpl::startInteractiveWindowSelection(std::function callback) { kwinApp()->platform()->startInteractiveWindowSelection( [callback] (KWin::Toplevel *t) { if (t && t->effectWindow()) { callback(t->effectWindow()); } else { callback(nullptr); } } ); } void EffectsHandlerImpl::startInteractivePositionSelection(std::function callback) { kwinApp()->platform()->startInteractivePositionSelection(callback); } void EffectsHandlerImpl::showOnScreenMessage(const QString &message, const QString &iconName) { OSD::show(message, iconName); } void EffectsHandlerImpl::hideOnScreenMessage(OnScreenMessageHideFlags flags) { OSD::HideFlags osdFlags; if (flags.testFlag(OnScreenMessageHideFlag::SkipsCloseAnimation)) { osdFlags |= OSD::HideFlag::SkipCloseAnimation; } OSD::hide(osdFlags); } KSharedConfigPtr EffectsHandlerImpl::config() const { return kwinApp()->config(); } KSharedConfigPtr EffectsHandlerImpl::inputConfig() const { return kwinApp()->inputConfig(); } Effect *EffectsHandlerImpl::findEffect(const QString &name) const { auto it = std::find_if(loaded_effects.constBegin(), loaded_effects.constEnd(), [name] (const EffectPair &pair) { return pair.first == name; } ); if (it == loaded_effects.constEnd()) { return nullptr; } return (*it).second; } //**************************************** // EffectWindowImpl //**************************************** EffectWindowImpl::EffectWindowImpl(Toplevel *toplevel) : EffectWindow(toplevel) , toplevel(toplevel) , sw(nullptr) { // Deleted windows are not managed. So, when windowClosed signal is // emitted, effects can't distinguish managed windows from unmanaged // windows(e.g. combo box popups, popup menus, etc). Save value of the // managed property during construction of EffectWindow. At that time, // parent can be Client, ShellClient, or Unmanaged. So, later on, when // an instance of Deleted becomes parent of the EffectWindow, effects // can still figure out whether it is/was a managed window. managed = toplevel->isClient(); waylandClient = qobject_cast(toplevel) != nullptr; - x11Client = !waylandClient; + x11Client = qobject_cast(toplevel) != nullptr; } EffectWindowImpl::~EffectWindowImpl() { QVariant cachedTextureVariant = data(LanczosCacheRole); if (cachedTextureVariant.isValid()) { GLTexture *cachedTexture = static_cast< GLTexture*>(cachedTextureVariant.value()); delete cachedTexture; } } bool EffectWindowImpl::isPaintingEnabled() { return sceneWindow()->isPaintingEnabled(); } void EffectWindowImpl::enablePainting(int reason) { sceneWindow()->enablePainting(reason); } void EffectWindowImpl::disablePainting(int reason) { sceneWindow()->disablePainting(reason); } void EffectWindowImpl::addRepaint(const QRect &r) { toplevel->addRepaint(r); } void EffectWindowImpl::addRepaint(int x, int y, int w, int h) { toplevel->addRepaint(x, y, w, h); } void EffectWindowImpl::addRepaintFull() { toplevel->addRepaintFull(); } void EffectWindowImpl::addLayerRepaint(const QRect &r) { toplevel->addLayerRepaint(r); } void EffectWindowImpl::addLayerRepaint(int x, int y, int w, int h) { toplevel->addLayerRepaint(x, y, w, h); } const EffectWindowGroup* EffectWindowImpl::group() const { if (auto c = qobject_cast(toplevel)) { return c->group()->effectGroup(); } return nullptr; // TODO } void EffectWindowImpl::refWindow() { if (auto d = qobject_cast(toplevel)) { return d->refWindow(); } abort(); // TODO } void EffectWindowImpl::unrefWindow() { if (auto d = qobject_cast(toplevel)) { return d->unrefWindow(); // delays deletion in case } abort(); // TODO } #define TOPLEVEL_HELPER( rettype, prototype, toplevelPrototype) \ rettype EffectWindowImpl::prototype ( ) const \ { \ return toplevel->toplevelPrototype(); \ } TOPLEVEL_HELPER(double, opacity, opacity) TOPLEVEL_HELPER(bool, hasAlpha, hasAlpha) TOPLEVEL_HELPER(int, x, x) TOPLEVEL_HELPER(int, y, y) TOPLEVEL_HELPER(int, width, width) TOPLEVEL_HELPER(int, height, height) TOPLEVEL_HELPER(QPoint, pos, pos) TOPLEVEL_HELPER(QSize, size, size) TOPLEVEL_HELPER(int, screen, screen) TOPLEVEL_HELPER(QRect, geometry, geometry) TOPLEVEL_HELPER(QRect, expandedGeometry, visibleRect) TOPLEVEL_HELPER(QRect, rect, rect) TOPLEVEL_HELPER(int, desktop, desktop) TOPLEVEL_HELPER(bool, isDesktop, isDesktop) TOPLEVEL_HELPER(bool, isDock, isDock) TOPLEVEL_HELPER(bool, isToolbar, isToolbar) TOPLEVEL_HELPER(bool, isMenu, isMenu) TOPLEVEL_HELPER(bool, isNormalWindow, isNormalWindow) TOPLEVEL_HELPER(bool, isDialog, isDialog) TOPLEVEL_HELPER(bool, isSplash, isSplash) TOPLEVEL_HELPER(bool, isUtility, isUtility) TOPLEVEL_HELPER(bool, isDropdownMenu, isDropdownMenu) TOPLEVEL_HELPER(bool, isPopupMenu, isPopupMenu) TOPLEVEL_HELPER(bool, isTooltip, isTooltip) TOPLEVEL_HELPER(bool, isNotification, isNotification) TOPLEVEL_HELPER(bool, isCriticalNotification, isCriticalNotification) TOPLEVEL_HELPER(bool, isOnScreenDisplay, isOnScreenDisplay) TOPLEVEL_HELPER(bool, isComboBox, isComboBox) TOPLEVEL_HELPER(bool, isDNDIcon, isDNDIcon) TOPLEVEL_HELPER(bool, isDeleted, isDeleted) TOPLEVEL_HELPER(bool, hasOwnShape, shape) TOPLEVEL_HELPER(QString, windowRole, windowRole) TOPLEVEL_HELPER(QStringList, activities, activities) TOPLEVEL_HELPER(bool, skipsCloseAnimation, skipsCloseAnimation) TOPLEVEL_HELPER(KWayland::Server::SurfaceInterface *, surface, surface) TOPLEVEL_HELPER(bool, isPopupWindow, isPopupWindow) TOPLEVEL_HELPER(bool, isOutline, isOutline) #undef TOPLEVEL_HELPER #define CLIENT_HELPER_WITH_DELETED( rettype, prototype, propertyname, defaultValue ) \ rettype EffectWindowImpl::prototype ( ) const \ { \ auto client = qobject_cast(toplevel); \ if (client) { \ return client->propertyname(); \ } \ auto deleted = qobject_cast(toplevel); \ if (deleted) { \ return deleted->propertyname(); \ } \ return defaultValue; \ } CLIENT_HELPER_WITH_DELETED(bool, isMinimized, isMinimized, false) CLIENT_HELPER_WITH_DELETED(bool, isModal, isModal, false) CLIENT_HELPER_WITH_DELETED(bool, isFullScreen, isFullScreen, false) CLIENT_HELPER_WITH_DELETED(bool, keepAbove, keepAbove, false) CLIENT_HELPER_WITH_DELETED(bool, keepBelow, keepBelow, false) CLIENT_HELPER_WITH_DELETED(QString, caption, caption, QString()); CLIENT_HELPER_WITH_DELETED(QVector, desktops, x11DesktopIds, QVector()); #undef CLIENT_HELPER_WITH_DELETED // legacy from tab groups, can be removed when no effects use this any more. bool EffectWindowImpl::isCurrentTab() const { return true; } QString EffectWindowImpl::windowClass() const { return toplevel->resourceName() + QLatin1Char(' ') + toplevel->resourceClass(); } QRect EffectWindowImpl::contentsRect() const { return QRect(toplevel->clientPos(), toplevel->clientSize()); } NET::WindowType EffectWindowImpl::windowType() const { return toplevel->windowType(); } #define CLIENT_HELPER( rettype, prototype, propertyname, defaultValue ) \ rettype EffectWindowImpl::prototype ( ) const \ { \ auto client = qobject_cast(toplevel); \ if (client) { \ return client->propertyname(); \ } \ return defaultValue; \ } CLIENT_HELPER(bool, isMovable, isMovable, false) CLIENT_HELPER(bool, isMovableAcrossScreens, isMovableAcrossScreens, false) CLIENT_HELPER(bool, isUserMove, isMove, false) CLIENT_HELPER(bool, isUserResize, isResize, false) CLIENT_HELPER(QRect, iconGeometry, iconGeometry, QRect()) CLIENT_HELPER(bool, isSpecialWindow, isSpecialWindow, true) CLIENT_HELPER(bool, acceptsFocus, wantsInput, true) // We don't actually know... CLIENT_HELPER(QIcon, icon, icon, QIcon()) CLIENT_HELPER(bool, isSkipSwitcher, skipSwitcher, false) CLIENT_HELPER(bool, decorationHasAlpha, decorationHasAlpha, false) CLIENT_HELPER(bool, isUnresponsive, unresponsive, false) #undef CLIENT_HELPER QSize EffectWindowImpl::basicUnit() const { if (auto client = qobject_cast(toplevel)){ return client->basicUnit(); } return QSize(1,1); } void EffectWindowImpl::setWindow(Toplevel* w) { toplevel = w; setParent(w); } void EffectWindowImpl::setSceneWindow(Scene::Window* w) { sw = w; } QRegion EffectWindowImpl::shape() const { return sw ? sw->shape() : geometry(); } QRect EffectWindowImpl::decorationInnerRect() const { auto client = qobject_cast(toplevel); return client ? client->transparentRect() : contentsRect(); } QByteArray EffectWindowImpl::readProperty(long atom, long type, int format) const { if (!kwinApp()->x11Connection()) { return QByteArray(); } return readWindowProperty(window()->window(), atom, type, format); } void EffectWindowImpl::deleteProperty(long int atom) const { if (kwinApp()->x11Connection()) { deleteWindowProperty(window()->window(), atom); } } EffectWindow* EffectWindowImpl::findModal() { auto client = qobject_cast(toplevel); if (!client) { return nullptr; } AbstractClient *modal = client->findModal(); if (modal) { return modal->effectWindow(); } return nullptr; } QWindow *EffectWindowImpl::internalWindow() const { - auto client = qobject_cast(toplevel); + auto client = qobject_cast(toplevel); if (!client) { return nullptr; } return client->internalWindow(); } template EffectWindowList getMainWindows(T *c) { const auto mainclients = c->mainClients(); EffectWindowList ret; ret.reserve(mainclients.size()); std::transform(std::cbegin(mainclients), std::cend(mainclients), std::back_inserter(ret), [](auto client) { return client->effectWindow(); }); return ret; } EffectWindowList EffectWindowImpl::mainWindows() const { if (auto client = qobject_cast(toplevel)) { return getMainWindows(client); } if (auto deleted = qobject_cast(toplevel)) { return getMainWindows(deleted); } return {}; } WindowQuadList EffectWindowImpl::buildQuads(bool force) const { return sceneWindow()->buildQuads(force); } void EffectWindowImpl::setData(int role, const QVariant &data) { if (!data.isNull()) dataMap[ role ] = data; else dataMap.remove(role); emit effects->windowDataChanged(this, role); } QVariant EffectWindowImpl::data(int role) const { return dataMap.value(role); } EffectWindow* effectWindow(Toplevel* w) { EffectWindowImpl* ret = w->effectWindow(); return ret; } EffectWindow* effectWindow(Scene::Window* w) { EffectWindowImpl* ret = w->window()->effectWindow(); ret->setSceneWindow(w); return ret; } void EffectWindowImpl::elevate(bool elevate) { effects->setElevatedWindow(this, elevate); } void EffectWindowImpl::registerThumbnail(AbstractThumbnailItem *item) { if (WindowThumbnailItem *thumb = qobject_cast(item)) { insertThumbnail(thumb); connect(thumb, SIGNAL(destroyed(QObject*)), SLOT(thumbnailDestroyed(QObject*))); connect(thumb, &WindowThumbnailItem::wIdChanged, this, &EffectWindowImpl::thumbnailTargetChanged); } else if (DesktopThumbnailItem *desktopThumb = qobject_cast(item)) { m_desktopThumbnails.append(desktopThumb); connect(desktopThumb, SIGNAL(destroyed(QObject*)), SLOT(desktopThumbnailDestroyed(QObject*))); } } void EffectWindowImpl::thumbnailDestroyed(QObject *object) { // we know it is a ThumbnailItem m_thumbnails.remove(static_cast(object)); } void EffectWindowImpl::thumbnailTargetChanged() { if (WindowThumbnailItem *item = qobject_cast(sender())) { insertThumbnail(item); } } void EffectWindowImpl::insertThumbnail(WindowThumbnailItem *item) { EffectWindow *w = effects->findWindow(item->wId()); if (w) { m_thumbnails.insert(item, QPointer(static_cast(w))); } else { m_thumbnails.insert(item, QPointer()); } } void EffectWindowImpl::desktopThumbnailDestroyed(QObject *object) { // we know it is a DesktopThumbnailItem m_desktopThumbnails.removeAll(static_cast(object)); } void EffectWindowImpl::minimize() { if (auto client = qobject_cast(toplevel)) { client->minimize(); } } void EffectWindowImpl::unminimize() { if (auto client = qobject_cast(toplevel)) { client->unminimize(); } } void EffectWindowImpl::closeWindow() { if (auto client = qobject_cast(toplevel)) { client->closeWindow(); } } void EffectWindowImpl::referencePreviousWindowPixmap() { if (sw) { sw->referencePreviousPixmap(); } } void EffectWindowImpl::unreferencePreviousWindowPixmap() { if (sw) { sw->unreferencePreviousPixmap(); } } bool EffectWindowImpl::isManaged() const { return managed; } bool EffectWindowImpl::isWaylandClient() const { return waylandClient; } bool EffectWindowImpl::isX11Client() const { return x11Client; } //**************************************** // EffectWindowGroupImpl //**************************************** EffectWindowList EffectWindowGroupImpl::members() const { const auto memberList = group->members(); EffectWindowList ret; ret.reserve(memberList.size()); std::transform(std::cbegin(memberList), std::cend(memberList), std::back_inserter(ret), [](auto toplevel) { return toplevel->effectWindow(); }); return ret; } //**************************************** // EffectFrameImpl //**************************************** EffectFrameImpl::EffectFrameImpl(EffectFrameStyle style, bool staticSize, QPoint position, Qt::Alignment alignment) : QObject(nullptr) , EffectFrame() , m_style(style) , m_static(staticSize) , m_point(position) , m_alignment(alignment) , m_shader(nullptr) , m_theme(new Plasma::Theme(this)) { if (m_style == EffectFrameStyled) { m_frame.setImagePath(QStringLiteral("widgets/background")); m_frame.setCacheAllRenderedFrames(true); connect(m_theme, SIGNAL(themeChanged()), this, SLOT(plasmaThemeChanged())); } m_selection.setImagePath(QStringLiteral("widgets/viewitem")); m_selection.setElementPrefix(QStringLiteral("hover")); m_selection.setCacheAllRenderedFrames(true); m_selection.setEnabledBorders(Plasma::FrameSvg::AllBorders); m_sceneFrame = Compositor::self()->scene()->createEffectFrame(this); } EffectFrameImpl::~EffectFrameImpl() { delete m_sceneFrame; } const QFont& EffectFrameImpl::font() const { return m_font; } void EffectFrameImpl::setFont(const QFont& font) { if (m_font == font) { return; } m_font = font; QRect oldGeom = m_geometry; if (!m_text.isEmpty()) { autoResize(); } if (oldGeom == m_geometry) { // Wasn't updated in autoResize() m_sceneFrame->freeTextFrame(); } } void EffectFrameImpl::free() { m_sceneFrame->free(); } const QRect& EffectFrameImpl::geometry() const { return m_geometry; } void EffectFrameImpl::setGeometry(const QRect& geometry, bool force) { QRect oldGeom = m_geometry; m_geometry = geometry; if (m_geometry == oldGeom && !force) { return; } effects->addRepaint(oldGeom); effects->addRepaint(m_geometry); if (m_geometry.size() == oldGeom.size() && !force) { return; } if (m_style == EffectFrameStyled) { qreal left, top, right, bottom; m_frame.getMargins(left, top, right, bottom); // m_geometry is the inner geometry m_frame.resizeFrame(m_geometry.adjusted(-left, -top, right, bottom).size()); } free(); } const QIcon& EffectFrameImpl::icon() const { return m_icon; } void EffectFrameImpl::setIcon(const QIcon& icon) { m_icon = icon; if (isCrossFade()) { m_sceneFrame->crossFadeIcon(); } if (m_iconSize.isEmpty() && !m_icon.availableSizes().isEmpty()) { // Set a size if we don't already have one setIconSize(m_icon.availableSizes().first()); } m_sceneFrame->freeIconFrame(); } const QSize& EffectFrameImpl::iconSize() const { return m_iconSize; } void EffectFrameImpl::setIconSize(const QSize& size) { if (m_iconSize == size) { return; } m_iconSize = size; autoResize(); m_sceneFrame->freeIconFrame(); } void EffectFrameImpl::plasmaThemeChanged() { free(); } void EffectFrameImpl::render(QRegion region, double opacity, double frameOpacity) { if (m_geometry.isEmpty()) { return; // Nothing to display } m_shader = nullptr; setScreenProjectionMatrix(static_cast(effects)->scene()->screenProjectionMatrix()); effects->paintEffectFrame(this, region, opacity, frameOpacity); } void EffectFrameImpl::finalRender(QRegion region, double opacity, double frameOpacity) const { region = infiniteRegion(); // TODO: Old region doesn't seem to work with OpenGL m_sceneFrame->render(region, opacity, frameOpacity); } Qt::Alignment EffectFrameImpl::alignment() const { return m_alignment; } void EffectFrameImpl::align(QRect &geometry) { if (m_alignment & Qt::AlignLeft) geometry.moveLeft(m_point.x()); else if (m_alignment & Qt::AlignRight) geometry.moveLeft(m_point.x() - geometry.width()); else geometry.moveLeft(m_point.x() - geometry.width() / 2); if (m_alignment & Qt::AlignTop) geometry.moveTop(m_point.y()); else if (m_alignment & Qt::AlignBottom) geometry.moveTop(m_point.y() - geometry.height()); else geometry.moveTop(m_point.y() - geometry.height() / 2); } void EffectFrameImpl::setAlignment(Qt::Alignment alignment) { m_alignment = alignment; align(m_geometry); setGeometry(m_geometry); } void EffectFrameImpl::setPosition(const QPoint& point) { m_point = point; QRect geometry = m_geometry; // this is important, setGeometry need call repaint for old & new geometry align(geometry); setGeometry(geometry); } const QString& EffectFrameImpl::text() const { return m_text; } void EffectFrameImpl::setText(const QString& text) { if (m_text == text) { return; } if (isCrossFade()) { m_sceneFrame->crossFadeText(); } m_text = text; QRect oldGeom = m_geometry; autoResize(); if (oldGeom == m_geometry) { // Wasn't updated in autoResize() m_sceneFrame->freeTextFrame(); } } void EffectFrameImpl::setSelection(const QRect& selection) { if (selection == m_selectionGeometry) { return; } m_selectionGeometry = selection; if (m_selectionGeometry.size() != m_selection.frameSize().toSize()) { m_selection.resizeFrame(m_selectionGeometry.size()); } // TODO; optimize to only recreate when resizing m_sceneFrame->freeSelection(); } void EffectFrameImpl::autoResize() { if (m_static) return; // Not automatically resizing QRect geometry; // Set size if (!m_text.isEmpty()) { QFontMetrics metrics(m_font); geometry.setSize(metrics.size(0, m_text)); } if (!m_icon.isNull() && !m_iconSize.isEmpty()) { geometry.setLeft(-m_iconSize.width()); if (m_iconSize.height() > geometry.height()) geometry.setHeight(m_iconSize.height()); } align(geometry); setGeometry(geometry); } QColor EffectFrameImpl::styledTextColor() { return m_theme->color(Plasma::Theme::TextColor); } } // namespace diff --git a/geometry.cpp b/geometry.cpp index e517ac998..512cf9236 100644 --- a/geometry.cpp +++ b/geometry.cpp @@ -1,3396 +1,3393 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 1999, 2000 Matthias Ettrich Copyright (C) 2003 Lubos Lunak Copyright (C) 2009 Lucas Murray 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 . *********************************************************************/ /* This file contains things relevant to geometry, i.e. workspace size, window positions and window sizes. */ #include "client.h" #include "composite.h" #include "cursor.h" #include "netinfo.h" #include "workspace.h" #include "placement.h" #include "geometrytip.h" #include "rules.h" #include "screens.h" #include "effects.h" #include "screenedge.h" +#include "internal_client.h" #include #include #include #include "outline.h" #include "shell_client.h" #include "wayland_server.h" #include #include namespace KWin { static inline int sign(int v) { return (v > 0) - (v < 0); } //******************************************** // Workspace //******************************************** extern int screen_number; extern bool is_multihead; /** * Resizes the workspace after an XRANDR screen size change */ void Workspace::desktopResized() { QRect geom = screens()->geometry(); if (rootInfo()) { NETSize desktop_geometry; desktop_geometry.width = geom.width(); desktop_geometry.height = geom.height(); rootInfo()->setDesktopGeometry(desktop_geometry); } updateClientArea(); saveOldScreenSizes(); // after updateClientArea(), so that one still uses the previous one // TODO: emit a signal instead and remove the deep function calls into edges and effects ScreenEdges::self()->recreateEdges(); if (effects) { static_cast(effects)->desktopResized(geom.size()); } } void Workspace::saveOldScreenSizes() { olddisplaysize = screens()->displaySize(); oldscreensizes.clear(); for( int i = 0; i < screens()->count(); ++i ) oldscreensizes.append( screens()->geometry( i )); } /** * Updates the current client areas according to the current clients. * * If the area changes or force is @c true, the new areas are propagated to the world. * * The client area is the area that is available for clients (that * which is not taken by windows like panels, the top-of-screen menu * etc). * * @see clientArea() */ void Workspace::updateClientArea(bool force) { const Screens *s = Screens::self(); int nscreens = s->count(); const int numberOfDesktops = VirtualDesktopManager::self()->count(); QVector< QRect > new_wareas(numberOfDesktops + 1); QVector< StrutRects > new_rmoveareas(numberOfDesktops + 1); QVector< QVector< QRect > > new_sareas(numberOfDesktops + 1); QVector< QRect > screens(nscreens); QRect desktopArea; for (int i = 0; i < nscreens; i++) { desktopArea |= s->geometry(i); } for (int iS = 0; iS < nscreens; iS ++) { screens [iS] = s->geometry(iS); } for (int i = 1; i <= numberOfDesktops; ++i) { new_wareas[ i ] = desktopArea; new_sareas[ i ].resize(nscreens); for (int iS = 0; iS < nscreens; iS ++) new_sareas[ i ][ iS ] = screens[ iS ]; } for (ClientList::ConstIterator it = clients.constBegin(); it != clients.constEnd(); ++it) { if (!(*it)->hasStrut()) continue; QRect r = (*it)->adjustedClientArea(desktopArea, desktopArea); // sanity check that a strut doesn't exclude a complete screen geometry // this is a violation to EWMH, as KWin just ignores the strut for (int i = 0; i < Screens::self()->count(); i++) { if (!r.intersects(Screens::self()->geometry(i))) { qCDebug(KWIN_CORE) << "Adjusted client area would exclude a complete screen, ignore"; r = desktopArea; break; } } StrutRects strutRegion = (*it)->strutRects(); const QRect clientsScreenRect = KWin::screens()->geometry((*it)->screen()); for (auto strut = strutRegion.begin(); strut != strutRegion.end(); strut++) { *strut = StrutRect((*strut).intersected(clientsScreenRect), (*strut).area()); } // Ignore offscreen xinerama struts. These interfere with the larger monitors on the setup // and should be ignored so that applications that use the work area to work out where // windows can go can use the entire visible area of the larger monitors. // This goes against the EWMH description of the work area but it is a toss up between // having unusable sections of the screen (Which can be quite large with newer monitors) // or having some content appear offscreen (Relatively rare compared to other). bool hasOffscreenXineramaStrut = (*it)->hasOffscreenXineramaStrut(); if ((*it)->isOnAllDesktops()) { for (int i = 1; i <= numberOfDesktops; ++i) { if (!hasOffscreenXineramaStrut) new_wareas[ i ] = new_wareas[ i ].intersected(r); new_rmoveareas[ i ] += strutRegion; for (int iS = 0; iS < nscreens; iS ++) { const auto geo = new_sareas[ i ][ iS ].intersected( (*it)->adjustedClientArea(desktopArea, screens[ iS ])); // ignore the geometry if it results in the screen getting removed completely if (!geo.isEmpty()) { new_sareas[ i ][ iS ] = geo; } } } } else { if (!hasOffscreenXineramaStrut) new_wareas[(*it)->desktop()] = new_wareas[(*it)->desktop()].intersected(r); new_rmoveareas[(*it)->desktop()] += strutRegion; for (int iS = 0; iS < nscreens; iS ++) { // qDebug() << "adjusting new_sarea: " << screens[ iS ]; const auto geo = new_sareas[(*it)->desktop()][ iS ].intersected( (*it)->adjustedClientArea(desktopArea, screens[ iS ])); // ignore the geometry if it results in the screen getting removed completely if (!geo.isEmpty()) { new_sareas[(*it)->desktop()][ iS ] = geo; } } } } if (waylandServer()) { auto updateStrutsForWaylandClient = [&] (ShellClient *c) { // assuming that only docks have "struts" and that all docks have a strut if (!c->hasStrut()) { return; } auto margins = [c] (const QRect &geometry) { QMargins margins; if (!geometry.intersects(c->geometry())) { return margins; } // figure out which areas of the overall screen setup it borders const bool left = c->geometry().left() == geometry.left(); const bool right = c->geometry().right() == geometry.right(); const bool top = c->geometry().top() == geometry.top(); const bool bottom = c->geometry().bottom() == geometry.bottom(); const bool horizontal = c->geometry().width() >= c->geometry().height(); if (left && ((!top && !bottom) || !horizontal)) { margins.setLeft(c->geometry().width()); } if (right && ((!top && !bottom) || !horizontal)) { margins.setRight(c->geometry().width()); } if (top && ((!left && !right) || horizontal)) { margins.setTop(c->geometry().height()); } if (bottom && ((!left && !right) || horizontal)) { margins.setBottom(c->geometry().height()); } return margins; }; auto marginsToStrutArea = [] (const QMargins &margins) { if (margins.left() != 0) { return StrutAreaLeft; } if (margins.right() != 0) { return StrutAreaRight; } if (margins.top() != 0) { return StrutAreaTop; } if (margins.bottom() != 0) { return StrutAreaBottom; } return StrutAreaInvalid; }; const auto strut = margins(KWin::screens()->geometry(c->screen())); const StrutRects strutRegion = StrutRects{StrutRect(c->geometry(), marginsToStrutArea(strut))}; QRect r = desktopArea - margins(KWin::screens()->geometry()); if (c->isOnAllDesktops()) { for (int i = 1; i <= numberOfDesktops; ++i) { new_wareas[ i ] = new_wareas[ i ].intersected(r); for (int iS = 0; iS < nscreens; ++iS) { new_sareas[ i ][ iS ] = new_sareas[ i ][ iS ].intersected(screens[iS] - margins(screens[iS])); } new_rmoveareas[ i ] += strutRegion; } } else { new_wareas[c->desktop()] = new_wareas[c->desktop()].intersected(r); for (int iS = 0; iS < nscreens; iS++) { new_sareas[c->desktop()][ iS ] = new_sareas[c->desktop()][ iS ].intersected(screens[iS] - margins(screens[iS])); } new_rmoveareas[ c->desktop() ] += strutRegion; } }; const auto clients = waylandServer()->clients(); for (auto c : clients) { updateStrutsForWaylandClient(c); } - const auto internalClients = waylandServer()->internalClients(); - for (auto c : internalClients) { - updateStrutsForWaylandClient(c); - } } #if 0 for (int i = 1; i <= numberOfDesktops(); ++i) { for (int iS = 0; iS < nscreens; iS ++) qCDebug(KWIN_CORE) << "new_sarea: " << new_sareas[ i ][ iS ]; } #endif bool changed = force; if (screenarea.isEmpty()) changed = true; for (int i = 1; !changed && i <= numberOfDesktops; ++i) { if (workarea[ i ] != new_wareas[ i ]) changed = true; if (restrictedmovearea[ i ] != new_rmoveareas[ i ]) changed = true; if (screenarea[ i ].size() != new_sareas[ i ].size()) changed = true; for (int iS = 0; !changed && iS < nscreens; iS ++) if (new_sareas[ i ][ iS ] != screenarea [ i ][ iS ]) changed = true; } if (changed) { workarea = new_wareas; oldrestrictedmovearea = restrictedmovearea; restrictedmovearea = new_rmoveareas; screenarea = new_sareas; if (rootInfo()) { NETRect r; for (int i = 1; i <= numberOfDesktops; i++) { r.pos.x = workarea[ i ].x(); r.pos.y = workarea[ i ].y(); r.size.width = workarea[ i ].width(); r.size.height = workarea[ i ].height(); rootInfo()->setWorkArea(i, r); } } for (auto it = m_allClients.constBegin(); it != m_allClients.constEnd(); ++it) (*it)->checkWorkspacePosition(); oldrestrictedmovearea.clear(); // reset, no longer valid or needed } } void Workspace::updateClientArea() { updateClientArea(false); } /** * Returns the area available for clients. This is the desktop * geometry minus windows on the dock. Placement algorithms should * refer to this rather than Screens::geometry. */ QRect Workspace::clientArea(clientAreaOption opt, int screen, int desktop) const { if (desktop == NETWinInfo::OnAllDesktops || desktop == 0) desktop = VirtualDesktopManager::self()->current(); if (screen == -1) screen = screens()->current(); const QSize displaySize = screens()->displaySize(); QRect sarea, warea; if (is_multihead) { sarea = (!screenarea.isEmpty() && screen < screenarea[ desktop ].size()) // screens may be missing during KWin initialization or screen config changes ? screenarea[ desktop ][ screen_number ] : screens()->geometry(screen_number); warea = workarea[ desktop ].isNull() ? screens()->geometry(screen_number) : workarea[ desktop ]; } else { sarea = (!screenarea.isEmpty() && screen < screenarea[ desktop ].size()) // screens may be missing during KWin initialization or screen config changes ? screenarea[ desktop ][ screen ] : screens()->geometry(screen); warea = workarea[ desktop ].isNull() ? QRect(0, 0, displaySize.width(), displaySize.height()) : workarea[ desktop ]; } switch(opt) { case MaximizeArea: case PlacementArea: return sarea; case MaximizeFullArea: case FullScreenArea: case MovementArea: case ScreenArea: if (is_multihead) return screens()->geometry(screen_number); else return screens()->geometry(screen); case WorkArea: if (is_multihead) return sarea; else return warea; case FullArea: return QRect(0, 0, displaySize.width(), displaySize.height()); } abort(); } QRect Workspace::clientArea(clientAreaOption opt, const QPoint& p, int desktop) const { return clientArea(opt, screens()->number(p), desktop); } QRect Workspace::clientArea(clientAreaOption opt, const AbstractClient* c) const { return clientArea(opt, c->geometry().center(), c->desktop()); } QRegion Workspace::restrictedMoveArea(int desktop, StrutAreas areas) const { if (desktop == NETWinInfo::OnAllDesktops || desktop == 0) desktop = VirtualDesktopManager::self()->current(); QRegion region; foreach (const StrutRect & rect, restrictedmovearea[desktop]) if (areas & rect.area()) region += rect; return region; } bool Workspace::inUpdateClientArea() const { return !oldrestrictedmovearea.isEmpty(); } QRegion Workspace::previousRestrictedMoveArea(int desktop, StrutAreas areas) const { if (desktop == NETWinInfo::OnAllDesktops || desktop == 0) desktop = VirtualDesktopManager::self()->current(); QRegion region; foreach (const StrutRect & rect, oldrestrictedmovearea.at(desktop)) if (areas & rect.area()) region += rect; return region; } QVector< QRect > Workspace::previousScreenSizes() const { return oldscreensizes; } int Workspace::oldDisplayWidth() const { return olddisplaysize.width(); } int Workspace::oldDisplayHeight() const { return olddisplaysize.height(); } /** * Client \a c is moved around to position \a pos. This gives the * workspace the opportunity to interveniate and to implement * snap-to-windows functionality. * * The parameter \a snapAdjust is a multiplier used to calculate the * effective snap zones. When 1.0, it means that the snap zones will be * used without change. */ QPoint Workspace::adjustClientPosition(AbstractClient* c, QPoint pos, bool unrestricted, double snapAdjust) { QSize borderSnapZone(options->borderSnapZone(), options->borderSnapZone()); QRect maxRect; int guideMaximized = MaximizeRestore; if (c->maximizeMode() != MaximizeRestore) { maxRect = clientArea(MaximizeArea, pos + c->rect().center(), c->desktop()); QRect geo = c->geometry(); if (c->maximizeMode() & MaximizeHorizontal && (geo.x() == maxRect.left() || geo.right() == maxRect.right())) { guideMaximized |= MaximizeHorizontal; borderSnapZone.setWidth(qMax(borderSnapZone.width() + 2, maxRect.width() / 16)); } if (c->maximizeMode() & MaximizeVertical && (geo.y() == maxRect.top() || geo.bottom() == maxRect.bottom())) { guideMaximized |= MaximizeVertical; borderSnapZone.setHeight(qMax(borderSnapZone.height() + 2, maxRect.height() / 16)); } } if (options->windowSnapZone() || !borderSnapZone.isNull() || options->centerSnapZone()) { const bool sOWO = options->isSnapOnlyWhenOverlapping(); const int screen = screens()->number(pos + c->rect().center()); if (maxRect.isNull()) maxRect = clientArea(MovementArea, screen, c->desktop()); const int xmin = maxRect.left(); const int xmax = maxRect.right() + 1; //desk size const int ymin = maxRect.top(); const int ymax = maxRect.bottom() + 1; const int cx(pos.x()); const int cy(pos.y()); const int cw(c->width()); const int ch(c->height()); const int rx(cx + cw); const int ry(cy + ch); //these don't change int nx(cx), ny(cy); //buffers int deltaX(xmax); int deltaY(ymax); //minimum distance to other clients int lx, ly, lrx, lry; //coords and size for the comparison client, l // border snap const int snapX = borderSnapZone.width() * snapAdjust; //snap trigger const int snapY = borderSnapZone.height() * snapAdjust; if (snapX || snapY) { QRect geo = c->geometry(); const QPoint cp = c->clientPos(); const QSize cs = geo.size() - c->clientSize(); int padding[4] = { cp.x(), cs.width() - cp.x(), cp.y(), cs.height() - cp.y() }; // snap to titlebar / snap to window borders on inner screen edges AbstractClient::Position titlePos = c->titlebarPosition(); if (padding[0] && (titlePos == AbstractClient::PositionLeft || (c->maximizeMode() & MaximizeHorizontal) || screens()->intersecting(geo.translated(maxRect.x() - (padding[0] + geo.x()), 0)) > 1)) padding[0] = 0; if (padding[1] && (titlePos == AbstractClient::PositionRight || (c->maximizeMode() & MaximizeHorizontal) || screens()->intersecting(geo.translated(maxRect.right() + padding[1] - geo.right(), 0)) > 1)) padding[1] = 0; if (padding[2] && (titlePos == AbstractClient::PositionTop || (c->maximizeMode() & MaximizeVertical) || screens()->intersecting(geo.translated(0, maxRect.y() - (padding[2] + geo.y()))) > 1)) padding[2] = 0; if (padding[3] && (titlePos == AbstractClient::PositionBottom || (c->maximizeMode() & MaximizeVertical) || screens()->intersecting(geo.translated(0, maxRect.bottom() + padding[3] - geo.bottom())) > 1)) padding[3] = 0; if ((sOWO ? (cx < xmin) : true) && (qAbs(xmin - cx) < snapX)) { deltaX = xmin - cx; nx = xmin - padding[0]; } if ((sOWO ? (rx > xmax) : true) && (qAbs(rx - xmax) < snapX) && (qAbs(xmax - rx) < deltaX)) { deltaX = rx - xmax; nx = xmax - cw + padding[1]; } if ((sOWO ? (cy < ymin) : true) && (qAbs(ymin - cy) < snapY)) { deltaY = ymin - cy; ny = ymin - padding[2]; } if ((sOWO ? (ry > ymax) : true) && (qAbs(ry - ymax) < snapY) && (qAbs(ymax - ry) < deltaY)) { deltaY = ry - ymax; ny = ymax - ch + padding[3]; } } // windows snap int snap = options->windowSnapZone() * snapAdjust; if (snap) { for (auto l = m_allClients.constBegin(); l != m_allClients.constEnd(); ++l) { if ((*l) == c) continue; if ((*l)->isMinimized()) continue; // is minimized if (!(*l)->isShown(false)) continue; if (!((*l)->isOnDesktop(c->desktop()) || c->isOnDesktop((*l)->desktop()))) continue; // wrong virtual desktop if (!(*l)->isOnCurrentActivity()) continue; // wrong activity if ((*l)->isDesktop() || (*l)->isSplash()) continue; lx = (*l)->x(); ly = (*l)->y(); lrx = lx + (*l)->width(); lry = ly + (*l)->height(); if (!(guideMaximized & MaximizeHorizontal) && (((cy <= lry) && (cy >= ly)) || ((ry >= ly) && (ry <= lry)) || ((cy <= ly) && (ry >= lry)))) { if ((sOWO ? (cx < lrx) : true) && (qAbs(lrx - cx) < snap) && (qAbs(lrx - cx) < deltaX)) { deltaX = qAbs(lrx - cx); nx = lrx; } if ((sOWO ? (rx > lx) : true) && (qAbs(rx - lx) < snap) && (qAbs(rx - lx) < deltaX)) { deltaX = qAbs(rx - lx); nx = lx - cw; } } if (!(guideMaximized & MaximizeVertical) && (((cx <= lrx) && (cx >= lx)) || ((rx >= lx) && (rx <= lrx)) || ((cx <= lx) && (rx >= lrx)))) { if ((sOWO ? (cy < lry) : true) && (qAbs(lry - cy) < snap) && (qAbs(lry - cy) < deltaY)) { deltaY = qAbs(lry - cy); ny = lry; } //if ( (qAbs( ry-ly ) < snap) && (qAbs( ry - ly ) < deltaY )) if ((sOWO ? (ry > ly) : true) && (qAbs(ry - ly) < snap) && (qAbs(ry - ly) < deltaY)) { deltaY = qAbs(ry - ly); ny = ly - ch; } } // Corner snapping if (!(guideMaximized & MaximizeVertical) && (nx == lrx || nx + cw == lx)) { if ((sOWO ? (ry > lry) : true) && (qAbs(lry - ry) < snap) && (qAbs(lry - ry) < deltaY)) { deltaY = qAbs(lry - ry); ny = lry - ch; } if ((sOWO ? (cy < ly) : true) && (qAbs(cy - ly) < snap) && (qAbs(cy - ly) < deltaY)) { deltaY = qAbs(cy - ly); ny = ly; } } if (!(guideMaximized & MaximizeHorizontal) && (ny == lry || ny + ch == ly)) { if ((sOWO ? (rx > lrx) : true) && (qAbs(lrx - rx) < snap) && (qAbs(lrx - rx) < deltaX)) { deltaX = qAbs(lrx - rx); nx = lrx - cw; } if ((sOWO ? (cx < lx) : true) && (qAbs(cx - lx) < snap) && (qAbs(cx - lx) < deltaX)) { deltaX = qAbs(cx - lx); nx = lx; } } } } // center snap snap = options->centerSnapZone() * snapAdjust; //snap trigger if (snap) { int diffX = qAbs((xmin + xmax) / 2 - (cx + cw / 2)); int diffY = qAbs((ymin + ymax) / 2 - (cy + ch / 2)); if (diffX < snap && diffY < snap && diffX < deltaX && diffY < deltaY) { // Snap to center of screen nx = (xmin + xmax) / 2 - cw / 2; ny = (ymin + ymax) / 2 - ch / 2; } else if (options->borderSnapZone()) { // Enhance border snap if ((nx == xmin || nx == xmax - cw) && diffY < snap && diffY < deltaY) { // Snap to vertical center on screen edge ny = (ymin + ymax) / 2 - ch / 2; } else if (((unrestricted ? ny == ymin : ny <= ymin) || ny == ymax - ch) && diffX < snap && diffX < deltaX) { // Snap to horizontal center on screen edge nx = (xmin + xmax) / 2 - cw / 2; } } } pos = QPoint(nx, ny); } return pos; } QRect Workspace::adjustClientSize(AbstractClient* c, QRect moveResizeGeom, int mode) { //adapted from adjustClientPosition on 29May2004 //this function is called when resizing a window and will modify //the new dimensions to snap to other windows/borders if appropriate if (options->windowSnapZone() || options->borderSnapZone()) { // || options->centerSnapZone ) const bool sOWO = options->isSnapOnlyWhenOverlapping(); const QRect maxRect = clientArea(MovementArea, c->rect().center(), c->desktop()); const int xmin = maxRect.left(); const int xmax = maxRect.right(); //desk size const int ymin = maxRect.top(); const int ymax = maxRect.bottom(); const int cx(moveResizeGeom.left()); const int cy(moveResizeGeom.top()); const int rx(moveResizeGeom.right()); const int ry(moveResizeGeom.bottom()); int newcx(cx), newcy(cy); //buffers int newrx(rx), newry(ry); int deltaX(xmax); int deltaY(ymax); //minimum distance to other clients int lx, ly, lrx, lry; //coords and size for the comparison client, l // border snap int snap = options->borderSnapZone(); //snap trigger if (snap) { deltaX = int(snap); deltaY = int(snap); #define SNAP_BORDER_TOP \ if ((sOWO?(newcyymax):true) && (qAbs(ymax-newry)xmax):true) && (qAbs(xmax-newrx)windowSnapZone(); if (snap) { deltaX = int(snap); deltaY = int(snap); for (auto l = m_allClients.constBegin(); l != m_allClients.constEnd(); ++l) { if ((*l)->isOnDesktop(VirtualDesktopManager::self()->current()) && !(*l)->isMinimized() && (*l) != c) { lx = (*l)->x() - 1; ly = (*l)->y() - 1; lrx = (*l)->x() + (*l)->width(); lry = (*l)->y() + (*l)->height(); #define WITHIN_HEIGHT ((( newcy <= lry ) && ( newcy >= ly )) || \ (( newry >= ly ) && ( newry <= lry )) || \ (( newcy <= ly ) && ( newry >= lry )) ) #define WITHIN_WIDTH ( (( cx <= lrx ) && ( cx >= lx )) || \ (( rx >= lx ) && ( rx <= lrx )) || \ (( cx <= lx ) && ( rx >= lrx )) ) #define SNAP_WINDOW_TOP if ( (sOWO?(newcyly):true) \ && WITHIN_WIDTH \ && (qAbs( ly - newry ) < deltaY) ) { \ deltaY = qAbs( ly - newry ); \ newry=ly; \ } #define SNAP_WINDOW_LEFT if ( (sOWO?(newcxlx):true) \ && WITHIN_HEIGHT \ && (qAbs( lx - newrx ) < deltaX)) \ { \ deltaX = qAbs( lx - newrx ); \ newrx=lx; \ } #define SNAP_WINDOW_C_TOP if ( (sOWO?(newcylry):true) \ && (newcx == lrx || newrx == lx) \ && qAbs(lry-newry) < deltaY ) { \ deltaY = qAbs( lry - newry - 1 ); \ newry = lry - 1; \ } #define SNAP_WINDOW_C_LEFT if ( (sOWO?(newcxlrx):true) \ && (newcy == lry || newry == ly) \ && qAbs(lrx-newrx) < deltaX ) { \ deltaX = qAbs( lrx - newrx - 1 ); \ newrx = lrx - 1; \ } switch(mode) { case AbstractClient::PositionBottomRight: SNAP_WINDOW_BOTTOM SNAP_WINDOW_RIGHT SNAP_WINDOW_C_BOTTOM SNAP_WINDOW_C_RIGHT break; case AbstractClient::PositionRight: SNAP_WINDOW_RIGHT SNAP_WINDOW_C_RIGHT break; case AbstractClient::PositionBottom: SNAP_WINDOW_BOTTOM SNAP_WINDOW_C_BOTTOM break; case AbstractClient::PositionTopLeft: SNAP_WINDOW_TOP SNAP_WINDOW_LEFT SNAP_WINDOW_C_TOP SNAP_WINDOW_C_LEFT break; case AbstractClient::PositionLeft: SNAP_WINDOW_LEFT SNAP_WINDOW_C_LEFT break; case AbstractClient::PositionTop: SNAP_WINDOW_TOP SNAP_WINDOW_C_TOP break; case AbstractClient::PositionTopRight: SNAP_WINDOW_TOP SNAP_WINDOW_RIGHT SNAP_WINDOW_C_TOP SNAP_WINDOW_C_RIGHT break; case AbstractClient::PositionBottomLeft: SNAP_WINDOW_BOTTOM SNAP_WINDOW_LEFT SNAP_WINDOW_C_BOTTOM SNAP_WINDOW_C_LEFT break; default: abort(); break; } } } } // center snap //snap = options->centerSnapZone; //if (snap) // { // // Don't resize snap to center as it interferes too much // // There are two ways of implementing this if wanted: // // 1) Snap only to the same points that the move snap does, and // // 2) Snap to the horizontal and vertical center lines of the screen // } moveResizeGeom = QRect(QPoint(newcx, newcy), QPoint(newrx, newry)); } return moveResizeGeom; } /** * Marks the client as being moved or resized by the user. */ void Workspace::setMoveResizeClient(AbstractClient *c) { Q_ASSERT(!c || !movingClient); // Catch attempts to move a second // window while still moving the first one. movingClient = c; if (movingClient) ++block_focus; else --block_focus; } // When kwin crashes, windows will not be gravitated back to their original position // and will remain offset by the size of the decoration. So when restarting, fix this // (the property with the size of the frame remains on the window after the crash). void Workspace::fixPositionAfterCrash(xcb_window_t w, const xcb_get_geometry_reply_t *geometry) { NETWinInfo i(connection(), w, rootWindow(), NET::WMFrameExtents, NET::Properties2()); NETStrut frame = i.frameExtents(); if (frame.left != 0 || frame.top != 0) { // left and top needed due to narrowing conversations restrictions in C++11 const uint32_t left = frame.left; const uint32_t top = frame.top; const uint32_t values[] = { geometry->x - left, geometry->y - top }; xcb_configure_window(connection(), w, XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y, values); } } //******************************************** // Client //******************************************** /** * Returns \a area with the client's strut taken into account. * * Used from Workspace in updateClientArea. */ // TODO move to Workspace? QRect Client::adjustedClientArea(const QRect &desktopArea, const QRect& area) const { QRect r = area; NETExtendedStrut str = strut(); QRect stareaL = QRect( 0, str . left_start, str . left_width, str . left_end - str . left_start + 1); QRect stareaR = QRect( desktopArea . right() - str . right_width + 1, str . right_start, str . right_width, str . right_end - str . right_start + 1); QRect stareaT = QRect( str . top_start, 0, str . top_end - str . top_start + 1, str . top_width); QRect stareaB = QRect( str . bottom_start, desktopArea . bottom() - str . bottom_width + 1, str . bottom_end - str . bottom_start + 1, str . bottom_width); QRect screenarea = workspace()->clientArea(ScreenArea, this); // HACK: workarea handling is not xinerama aware, so if this strut // reserves place at a xinerama edge that's inside the virtual screen, // ignore the strut for workspace setting. if (area == QRect(QPoint(0, 0), screens()->displaySize())) { if (stareaL.left() < screenarea.left()) stareaL = QRect(); if (stareaR.right() > screenarea.right()) stareaR = QRect(); if (stareaT.top() < screenarea.top()) stareaT = QRect(); if (stareaB.bottom() < screenarea.bottom()) stareaB = QRect(); } // Handle struts at xinerama edges that are inside the virtual screen. // They're given in virtual screen coordinates, make them affect only // their xinerama screen. stareaL.setLeft(qMax(stareaL.left(), screenarea.left())); stareaR.setRight(qMin(stareaR.right(), screenarea.right())); stareaT.setTop(qMax(stareaT.top(), screenarea.top())); stareaB.setBottom(qMin(stareaB.bottom(), screenarea.bottom())); if (stareaL . intersects(area)) { // qDebug() << "Moving left of: " << r << " to " << stareaL.right() + 1; r . setLeft(stareaL . right() + 1); } if (stareaR . intersects(area)) { // qDebug() << "Moving right of: " << r << " to " << stareaR.left() - 1; r . setRight(stareaR . left() - 1); } if (stareaT . intersects(area)) { // qDebug() << "Moving top of: " << r << " to " << stareaT.bottom() + 1; r . setTop(stareaT . bottom() + 1); } if (stareaB . intersects(area)) { // qDebug() << "Moving bottom of: " << r << " to " << stareaB.top() - 1; r . setBottom(stareaB . top() - 1); } return r; } NETExtendedStrut Client::strut() const { NETExtendedStrut ext = info->extendedStrut(); NETStrut str = info->strut(); const QSize displaySize = screens()->displaySize(); if (ext.left_width == 0 && ext.right_width == 0 && ext.top_width == 0 && ext.bottom_width == 0 && (str.left != 0 || str.right != 0 || str.top != 0 || str.bottom != 0)) { // build extended from simple if (str.left != 0) { ext.left_width = str.left; ext.left_start = 0; ext.left_end = displaySize.height(); } if (str.right != 0) { ext.right_width = str.right; ext.right_start = 0; ext.right_end = displaySize.height(); } if (str.top != 0) { ext.top_width = str.top; ext.top_start = 0; ext.top_end = displaySize.width(); } if (str.bottom != 0) { ext.bottom_width = str.bottom; ext.bottom_start = 0; ext.bottom_end = displaySize.width(); } } return ext; } StrutRect Client::strutRect(StrutArea area) const { Q_ASSERT(area != StrutAreaAll); // Not valid const QSize displaySize = screens()->displaySize(); NETExtendedStrut strutArea = strut(); switch(area) { case StrutAreaTop: if (strutArea.top_width != 0) return StrutRect(QRect( strutArea.top_start, 0, strutArea.top_end - strutArea.top_start, strutArea.top_width ), StrutAreaTop); break; case StrutAreaRight: if (strutArea.right_width != 0) return StrutRect(QRect( displaySize.width() - strutArea.right_width, strutArea.right_start, strutArea.right_width, strutArea.right_end - strutArea.right_start ), StrutAreaRight); break; case StrutAreaBottom: if (strutArea.bottom_width != 0) return StrutRect(QRect( strutArea.bottom_start, displaySize.height() - strutArea.bottom_width, strutArea.bottom_end - strutArea.bottom_start, strutArea.bottom_width ), StrutAreaBottom); break; case StrutAreaLeft: if (strutArea.left_width != 0) return StrutRect(QRect( 0, strutArea.left_start, strutArea.left_width, strutArea.left_end - strutArea.left_start ), StrutAreaLeft); break; default: abort(); // Not valid } return StrutRect(); // Null rect } StrutRects Client::strutRects() const { StrutRects region; region += strutRect(StrutAreaTop); region += strutRect(StrutAreaRight); region += strutRect(StrutAreaBottom); region += strutRect(StrutAreaLeft); return region; } bool Client::hasStrut() const { NETExtendedStrut ext = strut(); if (ext.left_width == 0 && ext.right_width == 0 && ext.top_width == 0 && ext.bottom_width == 0) return false; return true; } bool Client::hasOffscreenXineramaStrut() const { // Get strut as a QRegion QRegion region; region += strutRect(StrutAreaTop); region += strutRect(StrutAreaRight); region += strutRect(StrutAreaBottom); region += strutRect(StrutAreaLeft); // Remove all visible areas so that only the invisible remain for (int i = 0; i < screens()->count(); i ++) region -= screens()->geometry(i); // If there's anything left then we have an offscreen strut return !region.isEmpty(); } void AbstractClient::checkWorkspacePosition(QRect oldGeometry, int oldDesktop, QRect oldClientGeometry) { enum { Left = 0, Top, Right, Bottom }; const int border[4] = { borderLeft(), borderTop(), borderRight(), borderBottom() }; if( !oldGeometry.isValid()) oldGeometry = geometry(); if( oldDesktop == -2 ) oldDesktop = desktop(); if (!oldClientGeometry.isValid()) oldClientGeometry = oldGeometry.adjusted(border[Left], border[Top], -border[Right], -border[Bottom]); if (isDesktop()) return; if (isFullScreen()) { QRect area = workspace()->clientArea(FullScreenArea, this); if (geometry() != area) setGeometry(area); return; } if (isDock()) return; if (maximizeMode() != MaximizeRestore) { // TODO update geom_restore? changeMaximize(false, false, true); // adjust size const QRect screenArea = workspace()->clientArea(ScreenArea, this); QRect geom = geometry(); checkOffscreenPosition(&geom, screenArea); setGeometry(geom); return; } if (quickTileMode() != QuickTileMode(QuickTileFlag::None)) { setGeometry(electricBorderMaximizeGeometry(geometry().center(), desktop())); return; } // this can be true only if this window was mapped before KWin // was started - in such case, don't adjust position to workarea, // because the window already had its position, and if a window // with a strut altering the workarea would be managed in initialization // after this one, this window would be moved if (!workspace() || workspace()->initializing()) return; // If the window was touching an edge before but not now move it so it is again. // Old and new maximums have different starting values so windows on the screen // edge will move when a new strut is placed on the edge. QRect oldScreenArea; if( workspace()->inUpdateClientArea()) { // we need to find the screen area as it was before the change oldScreenArea = QRect( 0, 0, workspace()->oldDisplayWidth(), workspace()->oldDisplayHeight()); int distance = INT_MAX; foreach(const QRect &r, workspace()->previousScreenSizes()) { int d = r.contains( oldGeometry.center()) ? 0 : ( r.center() - oldGeometry.center()).manhattanLength(); if( d < distance ) { distance = d; oldScreenArea = r; } } } else { oldScreenArea = workspace()->clientArea(ScreenArea, oldGeometry.center(), oldDesktop); } const QRect oldGeomTall = QRect(oldGeometry.x(), oldScreenArea.y(), oldGeometry.width(), oldScreenArea.height()); // Full screen height const QRect oldGeomWide = QRect(oldScreenArea.x(), oldGeometry.y(), oldScreenArea.width(), oldGeometry.height()); // Full screen width int oldTopMax = oldScreenArea.y(); int oldRightMax = oldScreenArea.x() + oldScreenArea.width(); int oldBottomMax = oldScreenArea.y() + oldScreenArea.height(); int oldLeftMax = oldScreenArea.x(); const QRect screenArea = workspace()->clientArea(ScreenArea, geometryRestore().center(), desktop()); int topMax = screenArea.y(); int rightMax = screenArea.x() + screenArea.width(); int bottomMax = screenArea.y() + screenArea.height(); int leftMax = screenArea.x(); QRect newGeom = geometryRestore(); // geometry(); QRect newClientGeom = newGeom.adjusted(border[Left], border[Top], -border[Right], -border[Bottom]); const QRect newGeomTall = QRect(newGeom.x(), screenArea.y(), newGeom.width(), screenArea.height()); // Full screen height const QRect newGeomWide = QRect(screenArea.x(), newGeom.y(), screenArea.width(), newGeom.height()); // Full screen width // Get the max strut point for each side where the window is (E.g. Highest point for // the bottom struts bounded by the window's left and right sides). // These 4 compute old bounds ... auto moveAreaFunc = workspace()->inUpdateClientArea() ? &Workspace::previousRestrictedMoveArea : //... the restricted areas changed &Workspace::restrictedMoveArea; //... when e.g. active desktop or screen changes for (const QRect &r : (workspace()->*moveAreaFunc)(oldDesktop, StrutAreaTop)) { QRect rect = r & oldGeomTall; if (!rect.isEmpty()) oldTopMax = qMax(oldTopMax, rect.y() + rect.height()); } for (const QRect &r : (workspace()->*moveAreaFunc)(oldDesktop, StrutAreaRight)) { QRect rect = r & oldGeomWide; if (!rect.isEmpty()) oldRightMax = qMin(oldRightMax, rect.x()); } for (const QRect &r : (workspace()->*moveAreaFunc)(oldDesktop, StrutAreaBottom)) { QRect rect = r & oldGeomTall; if (!rect.isEmpty()) oldBottomMax = qMin(oldBottomMax, rect.y()); } for (const QRect &r : (workspace()->*moveAreaFunc)(oldDesktop, StrutAreaLeft)) { QRect rect = r & oldGeomWide; if (!rect.isEmpty()) oldLeftMax = qMax(oldLeftMax, rect.x() + rect.width()); } // These 4 compute new bounds for (const QRect &r : workspace()->restrictedMoveArea(desktop(), StrutAreaTop)) { QRect rect = r & newGeomTall; if (!rect.isEmpty()) topMax = qMax(topMax, rect.y() + rect.height()); } for (const QRect &r : workspace()->restrictedMoveArea(desktop(), StrutAreaRight)) { QRect rect = r & newGeomWide; if (!rect.isEmpty()) rightMax = qMin(rightMax, rect.x()); } for (const QRect &r : workspace()->restrictedMoveArea(desktop(), StrutAreaBottom)) { QRect rect = r & newGeomTall; if (!rect.isEmpty()) bottomMax = qMin(bottomMax, rect.y()); } for (const QRect &r : workspace()->restrictedMoveArea(desktop(), StrutAreaLeft)) { QRect rect = r & newGeomWide; if (!rect.isEmpty()) leftMax = qMax(leftMax, rect.x() + rect.width()); } // Check if the sides were inside or touching but are no longer bool keep[4] = {false, false, false, false}; bool save[4] = {false, false, false, false}; int padding[4] = {0, 0, 0, 0}; if (oldGeometry.x() >= oldLeftMax) save[Left] = newGeom.x() < leftMax; if (oldGeometry.x() == oldLeftMax) keep[Left] = newGeom.x() != leftMax; else if (oldClientGeometry.x() == oldLeftMax && newClientGeom.x() != leftMax) { padding[0] = border[Left]; keep[Left] = true; } if (oldGeometry.y() >= oldTopMax) save[Top] = newGeom.y() < topMax; if (oldGeometry.y() == oldTopMax) keep[Top] = newGeom.y() != topMax; else if (oldClientGeometry.y() == oldTopMax && newClientGeom.y() != topMax) { padding[1] = border[Left]; keep[Top] = true; } if (oldGeometry.right() <= oldRightMax - 1) save[Right] = newGeom.right() > rightMax - 1; if (oldGeometry.right() == oldRightMax - 1) keep[Right] = newGeom.right() != rightMax - 1; else if (oldClientGeometry.right() == oldRightMax - 1 && newClientGeom.right() != rightMax - 1) { padding[2] = border[Right]; keep[Right] = true; } if (oldGeometry.bottom() <= oldBottomMax - 1) save[Bottom] = newGeom.bottom() > bottomMax - 1; if (oldGeometry.bottom() == oldBottomMax - 1) keep[Bottom] = newGeom.bottom() != bottomMax - 1; else if (oldClientGeometry.bottom() == oldBottomMax - 1 && newClientGeom.bottom() != bottomMax - 1) { padding[3] = border[Bottom]; keep[Bottom] = true; } // if randomly touches opposing edges, do not favor either if (keep[Left] && keep[Right]) { keep[Left] = keep[Right] = false; padding[0] = padding[2] = 0; } if (keep[Top] && keep[Bottom]) { keep[Top] = keep[Bottom] = false; padding[1] = padding[3] = 0; } if (save[Left] || keep[Left]) newGeom.moveLeft(qMax(leftMax, screenArea.x()) - padding[0]); if (padding[0] && screens()->intersecting(newGeom) > 1) newGeom.moveLeft(newGeom.left() + padding[0]); if (save[Top] || keep[Top]) newGeom.moveTop(qMax(topMax, screenArea.y()) - padding[1]); if (padding[1] && screens()->intersecting(newGeom) > 1) newGeom.moveTop(newGeom.top() + padding[1]); if (save[Right] || keep[Right]) newGeom.moveRight(qMin(rightMax - 1, screenArea.right()) + padding[2]); if (padding[2] && screens()->intersecting(newGeom) > 1) newGeom.moveRight(newGeom.right() - padding[2]); if (oldGeometry.x() >= oldLeftMax && newGeom.x() < leftMax) newGeom.setLeft(qMax(leftMax, screenArea.x())); else if (oldClientGeometry.x() >= oldLeftMax && newGeom.x() + border[Left] < leftMax) { newGeom.setLeft(qMax(leftMax, screenArea.x()) - border[Left]); if (screens()->intersecting(newGeom) > 1) newGeom.setLeft(newGeom.left() + border[Left]); } if (save[Bottom] || keep[Bottom]) newGeom.moveBottom(qMin(bottomMax - 1, screenArea.bottom()) + padding[3]); if (padding[3] && screens()->intersecting(newGeom) > 1) newGeom.moveBottom(newGeom.bottom() - padding[3]); if (oldGeometry.y() >= oldTopMax && newGeom.y() < topMax) newGeom.setTop(qMax(topMax, screenArea.y())); else if (oldClientGeometry.y() >= oldTopMax && newGeom.y() + border[Top] < topMax) { newGeom.setTop(qMax(topMax, screenArea.y()) - border[Top]); if (screens()->intersecting(newGeom) > 1) newGeom.setTop(newGeom.top() + border[Top]); } checkOffscreenPosition(&newGeom, screenArea); // Obey size hints. TODO: We really should make sure it stays in the right place if (!isShade()) newGeom.setSize(adjustedSize(newGeom.size())); if (newGeom != geometry()) setGeometry(newGeom); } void AbstractClient::checkOffscreenPosition(QRect* geom, const QRect& screenArea) { if (geom->left() > screenArea.right()) { geom->moveLeft(screenArea.right() - screenArea.width()/4); } else if (geom->right() < screenArea.left()) { geom->moveRight(screenArea.left() + screenArea.width()/4); } if (geom->top() > screenArea.bottom()) { geom->moveTop(screenArea.bottom() - screenArea.height()/4); } else if (geom->bottom() < screenArea.top()) { geom->moveBottom(screenArea.top() + screenArea.width()/4); } } QSize AbstractClient::adjustedSize(const QSize& frame, Sizemode mode) const { // first, get the window size for the given frame size s QSize wsize(frame.width() - (borderLeft() + borderRight()), frame.height() - (borderTop() + borderBottom())); if (wsize.isEmpty()) wsize = QSize(qMax(wsize.width(), 1), qMax(wsize.height(), 1)); return sizeForClientSize(wsize, mode, false); } // this helper returns proper size even if the window is shaded // see also the comment in Client::setGeometry() QSize AbstractClient::adjustedSize() const { return sizeForClientSize(clientSize()); } /** * Calculate the appropriate frame size for the given client size \a * wsize. * * \a wsize is adapted according to the window's size hints (minimum, * maximum and incremental size changes). */ QSize Client::sizeForClientSize(const QSize& wsize, Sizemode mode, bool noframe) const { int w = wsize.width(); int h = wsize.height(); if (w < 1 || h < 1) { qCWarning(KWIN_CORE) << "sizeForClientSize() with empty size!" ; } if (w < 1) w = 1; if (h < 1) h = 1; // basesize, minsize, maxsize, paspect and resizeinc have all values defined, // even if they're not set in flags - see getWmNormalHints() QSize min_size = minSize(); QSize max_size = maxSize(); if (isDecorated()) { QSize decominsize(0, 0); QSize border_size(borderLeft() + borderRight(), borderTop() + borderBottom()); if (border_size.width() > decominsize.width()) // just in case decominsize.setWidth(border_size.width()); if (border_size.height() > decominsize.height()) decominsize.setHeight(border_size.height()); if (decominsize.width() > min_size.width()) min_size.setWidth(decominsize.width()); if (decominsize.height() > min_size.height()) min_size.setHeight(decominsize.height()); } w = qMin(max_size.width(), w); h = qMin(max_size.height(), h); w = qMax(min_size.width(), w); h = qMax(min_size.height(), h); int w1 = w; int h1 = h; int width_inc = m_geometryHints.resizeIncrements().width(); int height_inc = m_geometryHints.resizeIncrements().height(); int basew_inc = m_geometryHints.baseSize().width(); int baseh_inc = m_geometryHints.baseSize().height(); if (!m_geometryHints.hasBaseSize()) { basew_inc = m_geometryHints.minSize().width(); baseh_inc = m_geometryHints.minSize().height(); } w = int((w - basew_inc) / width_inc) * width_inc + basew_inc; h = int((h - baseh_inc) / height_inc) * height_inc + baseh_inc; // code for aspect ratios based on code from FVWM /* * The math looks like this: * * minAspectX dwidth maxAspectX * ---------- <= ------- <= ---------- * minAspectY dheight maxAspectY * * If that is multiplied out, then the width and height are * invalid in the following situations: * * minAspectX * dheight > minAspectY * dwidth * maxAspectX * dheight < maxAspectY * dwidth * */ if (m_geometryHints.hasAspect()) { double min_aspect_w = m_geometryHints.minAspect().width(); // use doubles, because the values can be MAX_INT double min_aspect_h = m_geometryHints.minAspect().height(); // and multiplying would go wrong otherwise double max_aspect_w = m_geometryHints.maxAspect().width(); double max_aspect_h = m_geometryHints.maxAspect().height(); // According to ICCCM 4.1.2.3 PMinSize should be a fallback for PBaseSize for size increments, // but not for aspect ratio. Since this code comes from FVWM, handles both at the same time, // and I have no idea how it works, let's hope nobody relies on that. const QSize baseSize = m_geometryHints.baseSize(); w -= baseSize.width(); h -= baseSize.height(); int max_width = max_size.width() - baseSize.width(); int min_width = min_size.width() - baseSize.width(); int max_height = max_size.height() - baseSize.height(); int min_height = min_size.height() - baseSize.height(); #define ASPECT_CHECK_GROW_W \ if ( min_aspect_w * h > min_aspect_h * w ) \ { \ int delta = int( min_aspect_w * h / min_aspect_h - w ) / width_inc * width_inc; \ if ( w + delta <= max_width ) \ w += delta; \ } #define ASPECT_CHECK_SHRINK_H_GROW_W \ if ( min_aspect_w * h > min_aspect_h * w ) \ { \ int delta = int( h - w * min_aspect_h / min_aspect_w ) / height_inc * height_inc; \ if ( h - delta >= min_height ) \ h -= delta; \ else \ { \ int delta = int( min_aspect_w * h / min_aspect_h - w ) / width_inc * width_inc; \ if ( w + delta <= max_width ) \ w += delta; \ } \ } #define ASPECT_CHECK_GROW_H \ if ( max_aspect_w * h < max_aspect_h * w ) \ { \ int delta = int( w * max_aspect_h / max_aspect_w - h ) / height_inc * height_inc; \ if ( h + delta <= max_height ) \ h += delta; \ } #define ASPECT_CHECK_SHRINK_W_GROW_H \ if ( max_aspect_w * h < max_aspect_h * w ) \ { \ int delta = int( w - max_aspect_w * h / max_aspect_h ) / width_inc * width_inc; \ if ( w - delta >= min_width ) \ w -= delta; \ else \ { \ int delta = int( w * max_aspect_h / max_aspect_w - h ) / height_inc * height_inc; \ if ( h + delta <= max_height ) \ h += delta; \ } \ } switch(mode) { case SizemodeAny: #if 0 // make SizemodeAny equal to SizemodeFixedW - prefer keeping fixed width, // so that changing aspect ratio to a different value and back keeps the same size (#87298) { ASPECT_CHECK_SHRINK_H_GROW_W ASPECT_CHECK_SHRINK_W_GROW_H ASPECT_CHECK_GROW_H ASPECT_CHECK_GROW_W break; } #endif case SizemodeFixedW: { // the checks are order so that attempts to modify height are first ASPECT_CHECK_GROW_H ASPECT_CHECK_SHRINK_H_GROW_W ASPECT_CHECK_SHRINK_W_GROW_H ASPECT_CHECK_GROW_W break; } case SizemodeFixedH: { ASPECT_CHECK_GROW_W ASPECT_CHECK_SHRINK_W_GROW_H ASPECT_CHECK_SHRINK_H_GROW_W ASPECT_CHECK_GROW_H break; } case SizemodeMax: { // first checks that try to shrink ASPECT_CHECK_SHRINK_H_GROW_W ASPECT_CHECK_SHRINK_W_GROW_H ASPECT_CHECK_GROW_W ASPECT_CHECK_GROW_H break; } } #undef ASPECT_CHECK_SHRINK_H_GROW_W #undef ASPECT_CHECK_SHRINK_W_GROW_H #undef ASPECT_CHECK_GROW_W #undef ASPECT_CHECK_GROW_H w += baseSize.width(); h += baseSize.height(); } if (!rules()->checkStrictGeometry(!isFullScreen())) { // disobey increments and aspect by explicit rule w = w1; h = h1; } if (!noframe) { w += borderLeft() + borderRight(); h += borderTop() + borderBottom(); } return rules()->checkSize(QSize(w, h)); } /** * Gets the client's normal WM hints and reconfigures itself respectively. */ void Client::getWmNormalHints() { const bool hadFixedAspect = m_geometryHints.hasAspect(); // roundtrip to X server m_geometryHints.fetch(); m_geometryHints.read(); if (!hadFixedAspect && m_geometryHints.hasAspect()) { // align to eventual new contraints maximize(max_mode); } if (isManaged()) { // update to match restrictions QSize new_size = adjustedSize(); if (new_size != size() && !isFullScreen()) { QRect origClientGeometry(pos() + clientPos(), clientSize()); resizeWithChecks(new_size); if ((!isSpecialWindow() || isToolbar()) && !isFullScreen()) { // try to keep the window in its xinerama screen if possible, // if that fails at least keep it visible somewhere QRect area = workspace()->clientArea(MovementArea, this); if (area.contains(origClientGeometry)) keepInArea(area); area = workspace()->clientArea(WorkArea, this); if (area.contains(origClientGeometry)) keepInArea(area); } } } updateAllowedActions(); // affects isResizeable() } QSize Client::minSize() const { return rules()->checkMinSize(m_geometryHints.minSize()); } QSize Client::maxSize() const { return rules()->checkMaxSize(m_geometryHints.maxSize()); } QSize Client::basicUnit() const { return m_geometryHints.resizeIncrements(); } /** * Auxiliary function to inform the client about the current window * configuration. */ void Client::sendSyntheticConfigureNotify() { xcb_configure_notify_event_t c; memset(&c, 0, sizeof(c)); c.response_type = XCB_CONFIGURE_NOTIFY; c.event = window(); c.window = window(); c.x = x() + clientPos().x(); c.y = y() + clientPos().y(); c.width = clientSize().width(); c.height = clientSize().height(); c.border_width = 0; c.above_sibling = XCB_WINDOW_NONE; c.override_redirect = 0; xcb_send_event(connection(), true, c.event, XCB_EVENT_MASK_STRUCTURE_NOTIFY, reinterpret_cast(&c)); xcb_flush(connection()); } const QPoint Client::calculateGravitation(bool invert, int gravity) const { int dx, dy; dx = dy = 0; if (gravity == 0) // default (nonsense) value for the argument gravity = m_geometryHints.windowGravity(); // dx, dy specify how the client window moves to make space for the frame switch(gravity) { case XCB_GRAVITY_NORTH_WEST: // move down right default: dx = borderLeft(); dy = borderTop(); break; case XCB_GRAVITY_NORTH: // move right dx = 0; dy = borderTop(); break; case XCB_GRAVITY_NORTH_EAST: // move down left dx = -borderRight(); dy = borderTop(); break; case XCB_GRAVITY_WEST: // move right dx = borderLeft(); dy = 0; break; case XCB_GRAVITY_CENTER: break; // will be handled specially case XCB_GRAVITY_STATIC: // don't move dx = 0; dy = 0; break; case XCB_GRAVITY_EAST: // move left dx = -borderRight(); dy = 0; break; case XCB_GRAVITY_SOUTH_WEST: // move up right dx = borderLeft() ; dy = -borderBottom(); break; case XCB_GRAVITY_SOUTH: // move up dx = 0; dy = -borderBottom(); break; case XCB_GRAVITY_SOUTH_EAST: // move up left dx = -borderRight(); dy = -borderBottom(); break; } if (gravity != XCB_GRAVITY_CENTER) { // translate from client movement to frame movement dx -= borderLeft(); dy -= borderTop(); } else { // center of the frame will be at the same position client center without frame would be dx = - (borderLeft() + borderRight()) / 2; dy = - (borderTop() + borderBottom()) / 2; } if (!invert) return QPoint(x() + dx, y() + dy); else return QPoint(x() - dx, y() - dy); } void Client::configureRequest(int value_mask, int rx, int ry, int rw, int rh, int gravity, bool from_tool) { const int configurePositionMask = XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y; const int configureSizeMask = XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT; const int configureGeometryMask = configurePositionMask | configureSizeMask; // "maximized" is a user setting -> we do not allow the client to resize itself // away from this & against the users explicit wish qCDebug(KWIN_CORE) << this << bool(value_mask & configureGeometryMask) << bool(maximizeMode() & MaximizeVertical) << bool(maximizeMode() & MaximizeHorizontal); // we want to (partially) ignore the request when the window is somehow maximized or quicktiled bool ignore = !app_noborder && (quickTileMode() != QuickTileMode(QuickTileFlag::None) || maximizeMode() != MaximizeRestore); // however, the user shall be able to force obedience despite and also disobedience in general ignore = rules()->checkIgnoreGeometry(ignore); if (!ignore) { // either we're not max'd / q'tiled or the user allowed the client to break that - so break it. updateQuickTileMode(QuickTileFlag::None); max_mode = MaximizeRestore; emit quickTileModeChanged(); } else if (!app_noborder && quickTileMode() == QuickTileMode(QuickTileFlag::None) && (maximizeMode() == MaximizeVertical || maximizeMode() == MaximizeHorizontal)) { // ignoring can be, because either we do, or the user does explicitly not want it. // for partially maximized windows we want to allow configures in the other dimension. // so we've to ask the user again - to know whether we just ignored for the partial maximization. // the problem here is, that the user can explicitly permit configure requests - even for maximized windows! // we cannot distinguish that from passing "false" for partially maximized windows. ignore = rules()->checkIgnoreGeometry(false); if (!ignore) { // the user is not interested, so we fix up dimensions if (maximizeMode() == MaximizeVertical) value_mask &= ~(XCB_CONFIG_WINDOW_Y | XCB_CONFIG_WINDOW_HEIGHT); if (maximizeMode() == MaximizeHorizontal) value_mask &= ~(XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_WIDTH); if (!(value_mask & configureGeometryMask)) { ignore = true; // the modification turned the request void } } } if (ignore) { qCDebug(KWIN_CORE) << "DENIED"; return; // nothing to (left) to do for use - bugs #158974, #252314, #321491 } qCDebug(KWIN_CORE) << "PERMITTED" << this << bool(value_mask & configureGeometryMask); if (gravity == 0) // default (nonsense) value for the argument gravity = m_geometryHints.windowGravity(); if (value_mask & configurePositionMask) { QPoint new_pos = calculateGravitation(true, gravity); // undo gravitation if (value_mask & XCB_CONFIG_WINDOW_X) { new_pos.setX(rx); } if (value_mask & XCB_CONFIG_WINDOW_Y) { new_pos.setY(ry); } // clever(?) workaround for applications like xv that want to set // the location to the current location but miscalculate the // frame size due to kwin being a double-reparenting window // manager if (new_pos.x() == x() + clientPos().x() && new_pos.y() == y() + clientPos().y() && gravity == XCB_GRAVITY_NORTH_WEST && !from_tool) { new_pos.setX(x()); new_pos.setY(y()); } int nw = clientSize().width(); int nh = clientSize().height(); if (value_mask & XCB_CONFIG_WINDOW_WIDTH) { nw = rw; } if (value_mask & XCB_CONFIG_WINDOW_HEIGHT) { nh = rh; } QSize ns = sizeForClientSize(QSize(nw, nh)); // enforces size if needed new_pos = rules()->checkPosition(new_pos); int newScreen = screens()->number(QRect(new_pos, ns).center()); if (newScreen != rules()->checkScreen(newScreen)) return; // not allowed by rule QRect origClientGeometry(pos() + clientPos(), clientSize()); GeometryUpdatesBlocker blocker(this); move(new_pos); plainResize(ns); setGeometry(QRect(calculateGravitation(false, gravity), size())); QRect area = workspace()->clientArea(WorkArea, this); if (!from_tool && (!isSpecialWindow() || isToolbar()) && !isFullScreen() && area.contains(origClientGeometry)) keepInArea(area); // this is part of the kicker-xinerama-hack... it should be // safe to remove when kicker gets proper ExtendedStrut support; // see Workspace::updateClientArea() and // Client::adjustedClientArea() if (hasStrut()) workspace() -> updateClientArea(); } if (value_mask & configureSizeMask && !(value_mask & configurePositionMask)) { // pure resize int nw = clientSize().width(); int nh = clientSize().height(); if (value_mask & XCB_CONFIG_WINDOW_WIDTH) { nw = rw; } if (value_mask & XCB_CONFIG_WINDOW_HEIGHT) { nh = rh; } QSize ns = sizeForClientSize(QSize(nw, nh)); if (ns != size()) { // don't restore if some app sets its own size again QRect origClientGeometry(pos() + clientPos(), clientSize()); GeometryUpdatesBlocker blocker(this); resizeWithChecks(ns, xcb_gravity_t(gravity)); if (!from_tool && (!isSpecialWindow() || isToolbar()) && !isFullScreen()) { // try to keep the window in its xinerama screen if possible, // if that fails at least keep it visible somewhere QRect area = workspace()->clientArea(MovementArea, this); if (area.contains(origClientGeometry)) keepInArea(area); area = workspace()->clientArea(WorkArea, this); if (area.contains(origClientGeometry)) keepInArea(area); } } } geom_restore = geometry(); // No need to send synthetic configure notify event here, either it's sent together // with geometry change, or there's no need to send it. // Handling of the real ConfigureRequest event forces sending it, as there it's necessary. } void Client::resizeWithChecks(int w, int h, xcb_gravity_t gravity, ForceGeometry_t force) { Q_ASSERT(!shade_geometry_change); if (isShade()) { if (h == borderTop() + borderBottom()) { qCWarning(KWIN_CORE) << "Shaded geometry passed for size:" ; } } int newx = x(); int newy = y(); 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(); QSize tmp = adjustedSize(QSize(w, h)); // checks size constraints, including min/max size w = tmp.width(); h = tmp.height(); if (gravity == 0) { gravity = m_geometryHints.windowGravity(); } switch(gravity) { case XCB_GRAVITY_NORTH_WEST: // top left corner doesn't move default: break; case XCB_GRAVITY_NORTH: // middle of top border doesn't move newx = (newx + width() / 2) - (w / 2); break; case XCB_GRAVITY_NORTH_EAST: // top right corner doesn't move newx = newx + width() - w; break; case XCB_GRAVITY_WEST: // middle of left border doesn't move newy = (newy + height() / 2) - (h / 2); break; case XCB_GRAVITY_CENTER: // middle point doesn't move newx = (newx + width() / 2) - (w / 2); newy = (newy + height() / 2) - (h / 2); break; case XCB_GRAVITY_STATIC: // top left corner of _client_ window doesn't move // since decoration doesn't change, equal to NorthWestGravity break; case XCB_GRAVITY_EAST: // // middle of right border doesn't move newx = newx + width() - w; newy = (newy + height() / 2) - (h / 2); break; case XCB_GRAVITY_SOUTH_WEST: // bottom left corner doesn't move newy = newy + height() - h; break; case XCB_GRAVITY_SOUTH: // middle of bottom border doesn't move newx = (newx + width() / 2) - (w / 2); newy = newy + height() - h; break; case XCB_GRAVITY_SOUTH_EAST: // bottom right corner doesn't move newx = newx + width() - w; newy = newy + height() - h; break; } setGeometry(newx, newy, w, h, force); } // _NET_MOVERESIZE_WINDOW void Client::NETMoveResizeWindow(int flags, int x, int y, int width, int height) { int gravity = flags & 0xff; int value_mask = 0; if (flags & (1 << 8)) { value_mask |= XCB_CONFIG_WINDOW_X; } if (flags & (1 << 9)) { value_mask |= XCB_CONFIG_WINDOW_Y; } if (flags & (1 << 10)) { value_mask |= XCB_CONFIG_WINDOW_WIDTH; } if (flags & (1 << 11)) { value_mask |= XCB_CONFIG_WINDOW_HEIGHT; } configureRequest(value_mask, x, y, width, height, gravity, true); } bool Client::isMovable() const { if (!hasNETSupport() && !m_motif.move()) { return false; } if (isFullScreen()) return false; if (isSpecialWindow() && !isSplash() && !isToolbar()) // allow moving of splashscreens :) return false; if (rules()->checkPosition(invalidPoint) != invalidPoint) // forced position return false; return true; } bool Client::isMovableAcrossScreens() const { if (!hasNETSupport() && !m_motif.move()) { return false; } if (isSpecialWindow() && !isSplash() && !isToolbar()) // allow moving of splashscreens :) return false; if (rules()->checkPosition(invalidPoint) != invalidPoint) // forced position return false; return true; } bool Client::isResizable() const { if (!hasNETSupport() && !m_motif.resize()) { return false; } if (isFullScreen()) return false; if (isSpecialWindow() || isSplash() || isToolbar()) return false; if (rules()->checkSize(QSize()).isValid()) // forced size return false; const Position mode = moveResizePointerMode(); if ((mode == PositionTop || mode == PositionTopLeft || mode == PositionTopRight || mode == PositionLeft || mode == PositionBottomLeft) && rules()->checkPosition(invalidPoint) != invalidPoint) return false; QSize min = minSize(); QSize max = maxSize(); return min.width() < max.width() || min.height() < max.height(); } bool Client::isMaximizable() const { if (!isResizable() || isToolbar()) // SELI isToolbar() ? return false; if (rules()->checkMaximize(MaximizeRestore) == MaximizeRestore && rules()->checkMaximize(MaximizeFull) != MaximizeRestore) return true; return false; } /** * Reimplemented to inform the client about the new window position. */ void Client::setGeometry(int x, int y, int w, int h, ForceGeometry_t force) { // this code is also duplicated in Client::plainResize() // Ok, the shading geometry stuff. Generally, code doesn't care about shaded geometry, // simply because there are too many places dealing with geometry. Those places // ignore shaded state and use normal geometry, which they usually should get // from adjustedSize(). Such geometry comes here, and if the window is shaded, // the geometry is used only for client_size, since that one is not used when // shading. Then the frame geometry is adjusted for the shaded geometry. // This gets more complicated in the case the code does only something like // setGeometry( geometry()) - geometry() will return the shaded frame geometry. // Such code is wrong and should be changed to handle the case when the window is shaded, // for example using Client::clientSize() if (shade_geometry_change) ; // nothing else if (isShade()) { if (h == borderTop() + borderBottom()) { qCDebug(KWIN_CORE) << "Shaded geometry passed for size:"; } else { client_size = QSize(w - borderLeft() - borderRight(), h - borderTop() - borderBottom()); h = borderTop() + borderBottom(); } } else { client_size = QSize(w - borderLeft() - borderRight(), h - borderTop() - borderBottom()); } QRect g(x, y, w, h); if (!areGeometryUpdatesBlocked() && g != rules()->checkGeometry(g)) { qCDebug(KWIN_CORE) << "forced geometry fail:" << g << ":" << rules()->checkGeometry(g); } if (force == NormalGeometrySet && geom == g && pendingGeometryUpdate() == PendingGeometryNone) return; geom = g; if (areGeometryUpdatesBlocked()) { if (pendingGeometryUpdate() == PendingGeometryForced) {} // maximum, nothing needed else if (force == ForceGeometrySet) setPendingGeometryUpdate(PendingGeometryForced); else setPendingGeometryUpdate(PendingGeometryNormal); return; } QSize oldClientSize = m_frame.geometry().size(); bool resized = (geometryBeforeUpdateBlocking().size() != geom.size() || pendingGeometryUpdate() == PendingGeometryForced); if (resized) { resizeDecoration(); m_frame.setGeometry(x, y, w, h); if (!isShade()) { QSize cs = clientSize(); m_wrapper.setGeometry(QRect(clientPos(), cs)); if (!isResize() || syncRequest.counter == XCB_NONE) m_client.setGeometry(0, 0, cs.width(), cs.height()); // SELI - won't this be too expensive? // THOMAS - yes, but gtk+ clients will not resize without ... sendSyntheticConfigureNotify(); } updateShape(); } else { if (isMoveResize()) { if (compositing()) // Defer the X update until we leave this mode needsXWindowMove = true; else m_frame.move(x, y); // sendSyntheticConfigureNotify() on finish shall be sufficient } else { m_frame.move(x, y); sendSyntheticConfigureNotify(); } // Unconditionally move the input window: it won't affect rendering m_decoInputExtent.move(QPoint(x, y) + inputPos()); } updateWindowRules(Rules::Position|Rules::Size); // keep track of old maximize mode // to detect changes screens()->setCurrent(this); workspace()->updateStackingOrder(); // need to regenerate decoration pixmaps when // - size is changed if (resized) { if (oldClientSize != QSize(w,h)) discardWindowPixmap(); } emit geometryShapeChanged(this, geometryBeforeUpdateBlocking()); addRepaintDuringGeometryUpdates(); updateGeometryBeforeUpdateBlocking(); // TODO: this signal is emitted too often emit geometryChanged(); } void Client::plainResize(int w, int h, ForceGeometry_t force) { // this code is also duplicated in Client::setGeometry(), and it's also commented there if (shade_geometry_change) ; // nothing else if (isShade()) { if (h == borderTop() + borderBottom()) { qCDebug(KWIN_CORE) << "Shaded geometry passed for size:"; } else { client_size = QSize(w - borderLeft() - borderRight(), h - borderTop() - borderBottom()); h = borderTop() + borderBottom(); } } else { client_size = QSize(w - borderLeft() - borderRight(), h - borderTop() - borderBottom()); } QSize s(w, h); if (!areGeometryUpdatesBlocked() && s != rules()->checkSize(s)) { qCDebug(KWIN_CORE) << "forced size fail:" << s << ":" << rules()->checkSize(s); } // resuming geometry updates is handled only in setGeometry() Q_ASSERT(pendingGeometryUpdate() == PendingGeometryNone || areGeometryUpdatesBlocked()); if (force == NormalGeometrySet && geom.size() == s) return; geom.setSize(s); if (areGeometryUpdatesBlocked()) { if (pendingGeometryUpdate() == PendingGeometryForced) {} // maximum, nothing needed else if (force == ForceGeometrySet) setPendingGeometryUpdate(PendingGeometryForced); else setPendingGeometryUpdate(PendingGeometryNormal); return; } QSize oldClientSize = m_frame.geometry().size(); resizeDecoration(); m_frame.resize(w, h); // resizeDecoration( s ); if (!isShade()) { QSize cs = clientSize(); m_wrapper.setGeometry(QRect(clientPos(), cs)); m_client.setGeometry(0, 0, cs.width(), cs.height()); } updateShape(); sendSyntheticConfigureNotify(); updateWindowRules(Rules::Position|Rules::Size); screens()->setCurrent(this); workspace()->updateStackingOrder(); if (oldClientSize != QSize(w,h)) discardWindowPixmap(); emit geometryShapeChanged(this, geometryBeforeUpdateBlocking()); addRepaintDuringGeometryUpdates(); updateGeometryBeforeUpdateBlocking(); // TODO: this signal is emitted too often emit geometryChanged(); } /** * Reimplemented to inform the client about the new window position. */ void AbstractClient::move(int x, int y, ForceGeometry_t force) { // resuming geometry updates is handled only in setGeometry() 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); } if (force == NormalGeometrySet && geom.topLeft() == p) return; geom.moveTopLeft(p); if (areGeometryUpdatesBlocked()) { if (pendingGeometryUpdate() == PendingGeometryForced) {} // maximum, nothing needed else if (force == ForceGeometrySet) setPendingGeometryUpdate(PendingGeometryForced); else setPendingGeometryUpdate(PendingGeometryNormal); return; } doMove(x, y); updateWindowRules(Rules::Position); screens()->setCurrent(this); workspace()->updateStackingOrder(); // client itself is not damaged addRepaintDuringGeometryUpdates(); updateGeometryBeforeUpdateBlocking(); emit geometryChanged(); } void Client::doMove(int x, int y) { m_frame.move(x, y); sendSyntheticConfigureNotify(); } void AbstractClient::blockGeometryUpdates(bool block) { if (block) { if (m_blockGeometryUpdates == 0) m_pendingGeometryUpdate = PendingGeometryNone; ++m_blockGeometryUpdates; } else { if (--m_blockGeometryUpdates == 0) { if (m_pendingGeometryUpdate != PendingGeometryNone) { if (isShade()) setGeometry(QRect(pos(), adjustedSize()), NormalGeometrySet); else setGeometry(geometry(), NormalGeometrySet); m_pendingGeometryUpdate = PendingGeometryNone; } } } } void AbstractClient::maximize(MaximizeMode m) { setMaximize(m & MaximizeVertical, m & MaximizeHorizontal); } void AbstractClient::setMaximize(bool vertically, bool horizontally) { // changeMaximize() flips the state, so change from set->flip const MaximizeMode oldMode = maximizeMode(); changeMaximize( oldMode & MaximizeHorizontal ? !horizontally : horizontally, oldMode & MaximizeVertical ? !vertically : vertically, false); const MaximizeMode newMode = maximizeMode(); if (oldMode != newMode) { emit clientMaximizedStateChanged(this, newMode); emit clientMaximizedStateChanged(this, vertically, horizontally); } } static bool changeMaximizeRecursion = false; void Client::changeMaximize(bool horizontal, bool vertical, bool adjust) { if (changeMaximizeRecursion) return; if (!isResizable() || isToolbar()) // SELI isToolbar() ? return; QRect clientArea; if (isElectricBorderMaximizing()) clientArea = workspace()->clientArea(MaximizeArea, Cursor::pos(), desktop()); else clientArea = workspace()->clientArea(MaximizeArea, this); MaximizeMode old_mode = max_mode; // 'adjust == true' means to update the size only, e.g. after changing workspace size if (!adjust) { if (vertical) max_mode = MaximizeMode(max_mode ^ MaximizeVertical); if (horizontal) max_mode = MaximizeMode(max_mode ^ MaximizeHorizontal); } // if the client insist on a fix aspect ratio, we check whether the maximizing will get us // out of screen bounds and take that as a "full maximization with aspect check" then if (m_geometryHints.hasAspect() && // fixed aspect (max_mode == MaximizeVertical || max_mode == MaximizeHorizontal) && // ondimensional maximization rules()->checkStrictGeometry(true)) { // obey aspect const QSize minAspect = m_geometryHints.minAspect(); const QSize maxAspect = m_geometryHints.maxAspect(); if (max_mode == MaximizeVertical || (old_mode & MaximizeVertical)) { const double fx = minAspect.width(); // use doubles, because the values can be MAX_INT const double fy = maxAspect.height(); // use doubles, because the values can be MAX_INT if (fx*clientArea.height()/fy > clientArea.width()) // too big max_mode = old_mode & MaximizeHorizontal ? MaximizeRestore : MaximizeFull; } else { // max_mode == MaximizeHorizontal const double fx = maxAspect.width(); const double fy = minAspect.height(); if (fy*clientArea.width()/fx > clientArea.height()) // too big max_mode = old_mode & MaximizeVertical ? MaximizeRestore : MaximizeFull; } } max_mode = rules()->checkMaximize(max_mode); if (!adjust && max_mode == old_mode) return; GeometryUpdatesBlocker blocker(this); // maximing one way and unmaximizing the other way shouldn't happen, // so restore first and then maximize the other way if ((old_mode == MaximizeVertical && max_mode == MaximizeHorizontal) || (old_mode == MaximizeHorizontal && max_mode == MaximizeVertical)) { changeMaximize(false, false, false); // restore } // save sizes for restoring, if maximalizing QSize sz; if (isShade()) sz = sizeForClientSize(clientSize()); else sz = size(); if (quickTileMode() == QuickTileMode(QuickTileFlag::None)) { if (!adjust && !(old_mode & MaximizeVertical)) { geom_restore.setTop(y()); geom_restore.setHeight(sz.height()); } if (!adjust && !(old_mode & MaximizeHorizontal)) { geom_restore.setLeft(x()); geom_restore.setWidth(sz.width()); } } // call into decoration update borders if (isDecorated() && decoration()->client() && !(options->borderlessMaximizedWindows() && max_mode == KWin::MaximizeFull)) { changeMaximizeRecursion = true; const auto c = decoration()->client().data(); if ((max_mode & MaximizeVertical) != (old_mode & MaximizeVertical)) { emit c->maximizedVerticallyChanged(max_mode & MaximizeVertical); } if ((max_mode & MaximizeHorizontal) != (old_mode & MaximizeHorizontal)) { emit c->maximizedHorizontallyChanged(max_mode & MaximizeHorizontal); } if ((max_mode == MaximizeFull) != (old_mode == MaximizeFull)) { emit c->maximizedChanged(max_mode & MaximizeFull); } changeMaximizeRecursion = false; } if (options->borderlessMaximizedWindows()) { // triggers a maximize change. // The next setNoBorder interation will exit since there's no change but the first recursion pullutes the restore geometry changeMaximizeRecursion = true; setNoBorder(rules()->checkNoBorder(app_noborder || (m_motif.hasDecoration() && m_motif.noBorder()) || max_mode == MaximizeFull)); changeMaximizeRecursion = false; } const ForceGeometry_t geom_mode = isDecorated() ? ForceGeometrySet : NormalGeometrySet; // Conditional quick tiling exit points if (quickTileMode() != QuickTileMode(QuickTileFlag::None)) { if (old_mode == MaximizeFull && !clientArea.contains(geom_restore.center())) { // Not restoring on the same screen // TODO: The following doesn't work for some reason //quick_tile_mode = QuickTileFlag::None; // And exit quick tile mode manually } else if ((old_mode == MaximizeVertical && max_mode == MaximizeRestore) || (old_mode == MaximizeFull && max_mode == MaximizeHorizontal)) { // Modifying geometry of a tiled window updateQuickTileMode(QuickTileFlag::None); // Exit quick tile mode without restoring geometry } } switch(max_mode) { case MaximizeVertical: { if (old_mode & MaximizeHorizontal) { // actually restoring from MaximizeFull if (geom_restore.width() == 0 || !clientArea.contains(geom_restore.center())) { // needs placement plainResize(adjustedSize(QSize(width() * 2 / 3, clientArea.height()), SizemodeFixedH), geom_mode); Placement::self()->placeSmart(this, clientArea); } else { setGeometry(QRect(QPoint(geom_restore.x(), clientArea.top()), adjustedSize(QSize(geom_restore.width(), clientArea.height()), SizemodeFixedH)), geom_mode); } } else { QRect r(x(), clientArea.top(), width(), clientArea.height()); r.setTopLeft(rules()->checkPosition(r.topLeft())); r.setSize(adjustedSize(r.size(), SizemodeFixedH)); setGeometry(r, geom_mode); } info->setState(NET::MaxVert, NET::Max); break; } case MaximizeHorizontal: { if (old_mode & MaximizeVertical) { // actually restoring from MaximizeFull if (geom_restore.height() == 0 || !clientArea.contains(geom_restore.center())) { // needs placement plainResize(adjustedSize(QSize(clientArea.width(), height() * 2 / 3), SizemodeFixedW), geom_mode); Placement::self()->placeSmart(this, clientArea); } else { setGeometry(QRect(QPoint(clientArea.left(), geom_restore.y()), adjustedSize(QSize(clientArea.width(), geom_restore.height()), SizemodeFixedW)), geom_mode); } } else { QRect r(clientArea.left(), y(), clientArea.width(), height()); r.setTopLeft(rules()->checkPosition(r.topLeft())); r.setSize(adjustedSize(r.size(), SizemodeFixedW)); setGeometry(r, geom_mode); } info->setState(NET::MaxHoriz, NET::Max); break; } case MaximizeRestore: { QRect restore = geometry(); // when only partially maximized, geom_restore may not have the other dimension remembered if (old_mode & MaximizeVertical) { restore.setTop(geom_restore.top()); restore.setBottom(geom_restore.bottom()); } if (old_mode & MaximizeHorizontal) { restore.setLeft(geom_restore.left()); restore.setRight(geom_restore.right()); } if (!restore.isValid()) { QSize s = QSize(clientArea.width() * 2 / 3, clientArea.height() * 2 / 3); if (geom_restore.width() > 0) s.setWidth(geom_restore.width()); if (geom_restore.height() > 0) s.setHeight(geom_restore.height()); plainResize(adjustedSize(s)); Placement::self()->placeSmart(this, clientArea); restore = geometry(); if (geom_restore.width() > 0) restore.moveLeft(geom_restore.x()); if (geom_restore.height() > 0) restore.moveTop(geom_restore.y()); geom_restore = restore; // relevant for mouse pos calculation, bug #298646 } if (m_geometryHints.hasAspect()) { restore.setSize(adjustedSize(restore.size(), SizemodeAny)); } setGeometry(restore, geom_mode); if (!clientArea.contains(geom_restore.center())) // Not restoring to the same screen Placement::self()->place(this, clientArea); info->setState(NET::States(), NET::Max); updateQuickTileMode(QuickTileFlag::None); break; } case MaximizeFull: { QRect r(clientArea); r.setTopLeft(rules()->checkPosition(r.topLeft())); r.setSize(adjustedSize(r.size(), SizemodeMax)); if (r.size() != clientArea.size()) { // to avoid off-by-one errors... if (isElectricBorderMaximizing() && r.width() < clientArea.width()) { r.moveLeft(qMax(clientArea.left(), Cursor::pos().x() - r.width()/2)); r.moveRight(qMin(clientArea.right(), r.right())); } else { r.moveCenter(clientArea.center()); const bool closeHeight = r.height() > 97*clientArea.height()/100; const bool closeWidth = r.width() > 97*clientArea.width() /100; const bool overHeight = r.height() > clientArea.height(); const bool overWidth = r.width() > clientArea.width(); if (closeWidth || closeHeight) { Position titlePos = titlebarPosition(); const QRect screenArea = workspace()->clientArea(ScreenArea, clientArea.center(), desktop()); if (closeHeight) { bool tryBottom = titlePos == PositionBottom; if ((overHeight && titlePos == PositionTop) || screenArea.top() == clientArea.top()) r.setTop(clientArea.top()); else tryBottom = true; if (tryBottom && (overHeight || screenArea.bottom() == clientArea.bottom())) r.setBottom(clientArea.bottom()); } if (closeWidth) { bool tryLeft = titlePos == PositionLeft; if ((overWidth && titlePos == PositionRight) || screenArea.right() == clientArea.right()) r.setRight(clientArea.right()); else tryLeft = true; if (tryLeft && (overWidth || screenArea.left() == clientArea.left())) r.setLeft(clientArea.left()); } } } r.moveTopLeft(rules()->checkPosition(r.topLeft())); } setGeometry(r, geom_mode); if (options->electricBorderMaximize() && r.top() == clientArea.top()) updateQuickTileMode(QuickTileFlag::Maximize); else updateQuickTileMode(QuickTileFlag::None); info->setState(NET::Max, NET::Max); break; } default: break; } updateAllowedActions(); updateWindowRules(Rules::MaximizeVert|Rules::MaximizeHoriz|Rules::Position|Rules::Size); emit quickTileModeChanged(); } bool Client::userCanSetFullScreen() const { if (!isFullScreenable()) { return false; } return isNormalWindow() || isDialog(); } void Client::setFullScreen(bool set, bool user) { set = rules()->checkFullScreen(set); const bool wasFullscreen = isFullScreen(); if (wasFullscreen == set) { return; } if (user && !userCanSetFullScreen()) { return; } setShade(ShadeNone); if (wasFullscreen) { workspace()->updateFocusMousePosition(Cursor::pos()); // may cause leave event } else { geom_fs_restore = geometry(); } if (set) { m_fullscreenMode = FullScreenNormal; workspace()->raiseClient(this); } else { m_fullscreenMode = FullScreenNone; } StackingUpdatesBlocker blocker1(workspace()); GeometryUpdatesBlocker blocker2(this); // active fullscreens get different layer workspace()->updateClientLayer(this); info->setState(isFullScreen() ? NET::FullScreen : NET::States(), NET::FullScreen); updateDecoration(false, false); if (set) { if (info->fullscreenMonitors().isSet()) { setGeometry(fullscreenMonitorsArea(info->fullscreenMonitors())); } else { setGeometry(workspace()->clientArea(FullScreenArea, this)); } } else { Q_ASSERT(!geom_fs_restore.isNull()); const int currentScreen = screen(); setGeometry(QRect(geom_fs_restore.topLeft(), adjustedSize(geom_fs_restore.size()))); if(currentScreen != screen()) { workspace()->sendClientToScreen(this, currentScreen); } } updateWindowRules(Rules::Fullscreen | Rules::Position | Rules::Size); emit clientFullScreenSet(this, set, user); emit fullScreenChanged(); } void Client::updateFullscreenMonitors(NETFullscreenMonitors topology) { int nscreens = screens()->count(); // qDebug() << "incoming request with top: " << topology.top << " bottom: " << topology.bottom // << " left: " << topology.left << " right: " << topology.right // << ", we have: " << nscreens << " screens."; if (topology.top >= nscreens || topology.bottom >= nscreens || topology.left >= nscreens || topology.right >= nscreens) { qCWarning(KWIN_CORE) << "fullscreenMonitors update failed. request higher than number of screens."; return; } info->setFullscreenMonitors(topology); if (isFullScreen()) setGeometry(fullscreenMonitorsArea(topology)); } /** * Calculates the bounding rectangle defined by the 4 monitor indices indicating the * top, bottom, left, and right edges of the window when the fullscreen state is enabled. */ QRect Client::fullscreenMonitorsArea(NETFullscreenMonitors requestedTopology) const { QRect top, bottom, left, right, total; top = screens()->geometry(requestedTopology.top); bottom = screens()->geometry(requestedTopology.bottom); left = screens()->geometry(requestedTopology.left); right = screens()->geometry(requestedTopology.right); total = top.united(bottom.united(left.united(right))); // qDebug() << "top: " << top << " bottom: " << bottom // << " left: " << left << " right: " << right; // qDebug() << "returning rect: " << total; return total; } static GeometryTip* geometryTip = nullptr; void Client::positionGeometryTip() { Q_ASSERT(isMove() || isResize()); // Position and Size display if (effects && static_cast(effects)->provides(Effect::GeometryTip)) return; // some effect paints this for us if (options->showGeometryTip()) { if (!geometryTip) { geometryTip = new GeometryTip(&m_geometryHints); } QRect wgeom(moveResizeGeometry()); // position of the frame, size of the window itself wgeom.setWidth(wgeom.width() - (width() - clientSize().width())); wgeom.setHeight(wgeom.height() - (height() - clientSize().height())); if (isShade()) wgeom.setHeight(0); geometryTip->setGeometry(wgeom); if (!geometryTip->isVisible()) geometryTip->show(); geometryTip->raise(); } } bool AbstractClient::startMoveResize() { Q_ASSERT(!isMoveResize()); Q_ASSERT(QWidget::keyboardGrabber() == nullptr); Q_ASSERT(QWidget::mouseGrabber() == nullptr); stopDelayedMoveResize(); if (QApplication::activePopupWidget() != nullptr) return false; // popups have grab if (isFullScreen() && (screens()->count() < 2 || !isMovableAcrossScreens())) return false; if (!doStartMoveResize()) { return false; } invalidateDecorationDoubleClickTimer(); setMoveResize(true); workspace()->setMoveResizeClient(this); const Position mode = moveResizePointerMode(); if (mode != PositionCenter) { // means "isResize()" but moveResizeMode = true is set below if (maximizeMode() == MaximizeFull) { // partial is cond. reset in finishMoveResize setGeometryRestore(geometry()); // "restore" to current geometry setMaximize(false, false); } } if (quickTileMode() != QuickTileMode(QuickTileFlag::None) && mode != PositionCenter) { // Cannot use isResize() yet // Exit quick tile mode when the user attempts to resize a tiled window updateQuickTileMode(QuickTileFlag::None); // Do so without restoring original geometry setGeometryRestore(geometry()); emit quickTileModeChanged(); } updateHaveResizeEffect(); updateInitialMoveResizeGeometry(); checkUnrestrictedMoveResize(); emit clientStartUserMovedResized(this); if (ScreenEdges::self()->isDesktopSwitchingMovingClients()) ScreenEdges::self()->reserveDesktopSwitching(true, Qt::Vertical|Qt::Horizontal); return true; } bool Client::doStartMoveResize() { bool has_grab = false; // This reportedly improves smoothness of the moveresize operation, // something with Enter/LeaveNotify events, looks like XFree performance problem or something *shrug* // (https://lists.kde.org/?t=107302193400001&r=1&w=2) QRect r = workspace()->clientArea(FullArea, this); m_moveResizeGrabWindow.create(r, XCB_WINDOW_CLASS_INPUT_ONLY, 0, nullptr, rootWindow()); m_moveResizeGrabWindow.map(); m_moveResizeGrabWindow.raise(); updateXTime(); const xcb_grab_pointer_cookie_t cookie = xcb_grab_pointer_unchecked(connection(), false, m_moveResizeGrabWindow, XCB_EVENT_MASK_BUTTON_PRESS | XCB_EVENT_MASK_BUTTON_RELEASE | XCB_EVENT_MASK_POINTER_MOTION | XCB_EVENT_MASK_ENTER_WINDOW | XCB_EVENT_MASK_LEAVE_WINDOW, XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC, m_moveResizeGrabWindow, Cursor::x11Cursor(cursor()), xTime()); ScopedCPointer pointerGrab(xcb_grab_pointer_reply(connection(), cookie, nullptr)); if (!pointerGrab.isNull() && pointerGrab->status == XCB_GRAB_STATUS_SUCCESS) { has_grab = true; } if (!has_grab && grabXKeyboard(frameId())) has_grab = move_resize_has_keyboard_grab = true; if (!has_grab) { // at least one grab is necessary in order to be able to finish move/resize m_moveResizeGrabWindow.reset(); return false; } return true; } void AbstractClient::finishMoveResize(bool cancel) { GeometryUpdatesBlocker blocker(this); const bool wasResize = isResize(); // store across leaveMoveResize leaveMoveResize(); if (cancel) setGeometry(initialMoveResizeGeometry()); else { const QRect &moveResizeGeom = moveResizeGeometry(); if (wasResize) { const bool restoreH = maximizeMode() == MaximizeHorizontal && moveResizeGeom.width() != initialMoveResizeGeometry().width(); const bool restoreV = maximizeMode() == MaximizeVertical && moveResizeGeom.height() != initialMoveResizeGeometry().height(); if (restoreH || restoreV) { changeMaximize(restoreV, restoreH, false); } } setGeometry(moveResizeGeom); } checkScreen(); // needs to be done because clientFinishUserMovedResized has not yet re-activated online alignment if (screen() != moveResizeStartScreen()) { workspace()->sendClientToScreen(this, screen()); // checks rule validity if (maximizeMode() != MaximizeRestore) checkWorkspacePosition(); } if (isElectricBorderMaximizing()) { setQuickTileMode(electricBorderMode()); setElectricBorderMaximizing(false); } else if (!cancel) { QRect geom_restore = geometryRestore(); if (!(maximizeMode() & MaximizeHorizontal)) { geom_restore.setX(geometry().x()); geom_restore.setWidth(geometry().width()); } if (!(maximizeMode() & MaximizeVertical)) { geom_restore.setY(geometry().y()); geom_restore.setHeight(geometry().height()); } setGeometryRestore(geom_restore); } // FRAME update(); emit clientFinishUserMovedResized(this); } void Client::leaveMoveResize() { if (needsXWindowMove) { // Do the deferred move m_frame.move(geom.topLeft()); needsXWindowMove = false; } if (!isResize()) sendSyntheticConfigureNotify(); // tell the client about it's new final position if (geometryTip) { geometryTip->hide(); delete geometryTip; geometryTip = nullptr; } if (move_resize_has_keyboard_grab) ungrabXKeyboard(); move_resize_has_keyboard_grab = false; xcb_ungrab_pointer(connection(), xTime()); m_moveResizeGrabWindow.reset(); if (syncRequest.counter == XCB_NONE) // don't forget to sanitize since the timeout will no more fire syncRequest.isPending = false; delete syncRequest.timeout; syncRequest.timeout = nullptr; AbstractClient::leaveMoveResize(); } // This function checks if it actually makes sense to perform a restricted move/resize. // If e.g. the titlebar is already outside of the workarea, there's no point in performing // a restricted move resize, because then e.g. resize would also move the window (#74555). // NOTE: Most of it is duplicated from handleMoveResize(). void AbstractClient::checkUnrestrictedMoveResize() { if (isUnrestrictedMoveResize()) return; const QRect &moveResizeGeom = moveResizeGeometry(); QRect desktopArea = workspace()->clientArea(WorkArea, moveResizeGeom.center(), desktop()); int left_marge, right_marge, top_marge, bottom_marge, titlebar_marge; // restricted move/resize - keep at least part of the titlebar always visible // how much must remain visible when moved away in that direction left_marge = qMin(100 + borderRight(), moveResizeGeom.width()); right_marge = qMin(100 + borderLeft(), moveResizeGeom.width()); // width/height change with opaque resizing, use the initial ones titlebar_marge = initialMoveResizeGeometry().height(); top_marge = borderBottom(); bottom_marge = borderTop(); if (isResize()) { if (moveResizeGeom.bottom() < desktopArea.top() + top_marge) setUnrestrictedMoveResize(true); if (moveResizeGeom.top() > desktopArea.bottom() - bottom_marge) setUnrestrictedMoveResize(true); if (moveResizeGeom.right() < desktopArea.left() + left_marge) setUnrestrictedMoveResize(true); if (moveResizeGeom.left() > desktopArea.right() - right_marge) setUnrestrictedMoveResize(true); if (!isUnrestrictedMoveResize() && moveResizeGeom.top() < desktopArea.top()) // titlebar mustn't go out setUnrestrictedMoveResize(true); } if (isMove()) { if (moveResizeGeom.bottom() < desktopArea.top() + titlebar_marge - 1) setUnrestrictedMoveResize(true); // no need to check top_marge, titlebar_marge already handles it if (moveResizeGeom.top() > desktopArea.bottom() - bottom_marge + 1) // titlebar mustn't go out setUnrestrictedMoveResize(true); if (moveResizeGeom.right() < desktopArea.left() + left_marge) setUnrestrictedMoveResize(true); if (moveResizeGeom.left() > desktopArea.right() - right_marge) setUnrestrictedMoveResize(true); } } // When the user pressed mouse on the titlebar, don't activate move immediatelly, // since it may be just a click. Activate instead after a delay. Move used to be // activated only after moving by several pixels, but that looks bad. void AbstractClient::startDelayedMoveResize() { Q_ASSERT(!m_moveResize.delayedTimer); m_moveResize.delayedTimer = new QTimer(this); m_moveResize.delayedTimer->setSingleShot(true); connect(m_moveResize.delayedTimer, &QTimer::timeout, this, [this]() { Q_ASSERT(isMoveResizePointerButtonDown()); if (!startMoveResize()) { setMoveResizePointerButtonDown(false); } updateCursor(); stopDelayedMoveResize(); } ); m_moveResize.delayedTimer->start(QApplication::startDragTime()); } void AbstractClient::stopDelayedMoveResize() { delete m_moveResize.delayedTimer; m_moveResize.delayedTimer = nullptr; } void AbstractClient::handleMoveResize(const QPoint &local, const QPoint &global) { const QRect oldGeo = geometry(); handleMoveResize(local.x(), local.y(), global.x(), global.y()); if (!isFullScreen() && isMove()) { if (quickTileMode() != QuickTileMode(QuickTileFlag::None) && oldGeo != geometry()) { GeometryUpdatesBlocker blocker(this); setQuickTileMode(QuickTileFlag::None); const QRect &geom_restore = geometryRestore(); setMoveOffset(QPoint(double(moveOffset().x()) / double(oldGeo.width()) * double(geom_restore.width()), double(moveOffset().y()) / double(oldGeo.height()) * double(geom_restore.height()))); if (rules()->checkMaximize(MaximizeRestore) == MaximizeRestore) setMoveResizeGeometry(geom_restore); handleMoveResize(local.x(), local.y(), global.x(), global.y()); // fix position } else if (quickTileMode() == QuickTileMode(QuickTileFlag::None) && isResizable()) { checkQuickTilingMaximizationZones(global.x(), global.y()); } } } bool Client::isWaitingForMoveResizeSync() const { return syncRequest.isPending && isResize(); } void AbstractClient::handleMoveResize(int x, int y, int x_root, int y_root) { if (isWaitingForMoveResizeSync()) return; // we're still waiting for the client or the timeout const Position mode = moveResizePointerMode(); if ((mode == PositionCenter && !isMovableAcrossScreens()) || (mode != PositionCenter && (isShade() || !isResizable()))) return; if (!isMoveResize()) { QPoint p(QPoint(x/* - padding_left*/, y/* - padding_top*/) - moveOffset()); if (p.manhattanLength() >= QApplication::startDragDistance()) { if (!startMoveResize()) { setMoveResizePointerButtonDown(false); updateCursor(); return; } updateCursor(); } else return; } // ShadeHover or ShadeActive, ShadeNormal was already avoided above if (mode != PositionCenter && shadeMode() != ShadeNone) setShade(ShadeNone); QPoint globalPos(x_root, y_root); // these two points limit the geometry rectangle, i.e. if bottomleft resizing is done, // the bottomleft corner should be at is at (topleft.x(), bottomright().y()) QPoint topleft = globalPos - moveOffset(); QPoint bottomright = globalPos + invertedMoveOffset(); QRect previousMoveResizeGeom = moveResizeGeometry(); // TODO move whole group when moving its leader or when the leader is not mapped? auto titleBarRect = [this](bool &transposed, int &requiredPixels) -> QRect { const QRect &moveResizeGeom = moveResizeGeometry(); QRect r(moveResizeGeom); r.moveTopLeft(QPoint(0,0)); switch (titlebarPosition()) { default: case PositionTop: r.setHeight(borderTop()); break; case PositionLeft: r.setWidth(borderLeft()); transposed = true; break; case PositionBottom: r.setTop(r.bottom() - borderBottom()); break; case PositionRight: r.setLeft(r.right() - borderRight()); transposed = true; break; } // When doing a restricted move we must always keep 100px of the titlebar // visible to allow the user to be able to move it again. requiredPixels = qMin(100 * (transposed ? r.width() : r.height()), moveResizeGeom.width() * moveResizeGeom.height()); return r; }; bool update = false; if (isResize()) { QRect orig = initialMoveResizeGeometry(); Sizemode sizemode = SizemodeAny; auto calculateMoveResizeGeom = [this, &topleft, &bottomright, &orig, &sizemode, &mode]() { switch(mode) { case PositionTopLeft: setMoveResizeGeometry(QRect(topleft, orig.bottomRight())); break; case PositionBottomRight: setMoveResizeGeometry(QRect(orig.topLeft(), bottomright)); break; case PositionBottomLeft: setMoveResizeGeometry(QRect(QPoint(topleft.x(), orig.y()), QPoint(orig.right(), bottomright.y()))); break; case PositionTopRight: setMoveResizeGeometry(QRect(QPoint(orig.x(), topleft.y()), QPoint(bottomright.x(), orig.bottom()))); break; case PositionTop: setMoveResizeGeometry(QRect(QPoint(orig.left(), topleft.y()), orig.bottomRight())); sizemode = SizemodeFixedH; // try not to affect height break; case PositionBottom: setMoveResizeGeometry(QRect(orig.topLeft(), QPoint(orig.right(), bottomright.y()))); sizemode = SizemodeFixedH; break; case PositionLeft: setMoveResizeGeometry(QRect(QPoint(topleft.x(), orig.top()), orig.bottomRight())); sizemode = SizemodeFixedW; break; case PositionRight: setMoveResizeGeometry(QRect(orig.topLeft(), QPoint(bottomright.x(), orig.bottom()))); sizemode = SizemodeFixedW; break; case PositionCenter: default: abort(); break; } }; // first resize (without checking constrains), then snap, then check bounds, then check constrains calculateMoveResizeGeom(); // adjust new size to snap to other windows/borders setMoveResizeGeometry(workspace()->adjustClientSize(this, moveResizeGeometry(), mode)); if (!isUnrestrictedMoveResize()) { // Make sure the titlebar isn't behind a restricted area. We don't need to restrict // the other directions. If not visible enough, move the window to the closest valid // point. We bruteforce this by slowly moving the window back to its previous position QRegion availableArea(workspace()->clientArea(FullArea, -1, 0)); // On the screen availableArea -= workspace()->restrictedMoveArea(desktop()); // Strut areas bool transposed = false; int requiredPixels; QRect bTitleRect = titleBarRect(transposed, requiredPixels); int lastVisiblePixels = -1; QRect lastTry = moveResizeGeometry(); bool titleFailed = false; for (;;) { const QRect titleRect(bTitleRect.translated(moveResizeGeometry().topLeft())); int visiblePixels = 0; int realVisiblePixels = 0; for (const QRect &rect : availableArea) { const QRect r = rect & titleRect; realVisiblePixels += r.width() * r.height(); if ((transposed && r.width() == titleRect.width()) || // Only the full size regions... (!transposed && r.height() == titleRect.height())) // ...prevents long slim areas visiblePixels += r.width() * r.height(); } if (visiblePixels >= requiredPixels) break; // We have reached a valid position if (realVisiblePixels <= lastVisiblePixels) { if (titleFailed && realVisiblePixels < lastVisiblePixels) break; // we won't become better else { if (!titleFailed) setMoveResizeGeometry(lastTry); titleFailed = true; } } lastVisiblePixels = realVisiblePixels; QRect moveResizeGeom = moveResizeGeometry(); lastTry = moveResizeGeom; // Not visible enough, move the window to the closest valid point. We bruteforce // this by slowly moving the window back to its previous position. // The geometry changes at up to two edges, the one with the title (if) shall take // precedence. The opposing edge has no impact on visiblePixels and only one of // the adjacent can alter at a time, ie. it's enough to ignore adjacent edges // if the title edge altered bool leftChanged = previousMoveResizeGeom.left() != moveResizeGeom.left(); bool rightChanged = previousMoveResizeGeom.right() != moveResizeGeom.right(); bool topChanged = previousMoveResizeGeom.top() != moveResizeGeom.top(); bool btmChanged = previousMoveResizeGeom.bottom() != moveResizeGeom.bottom(); auto fixChangedState = [titleFailed](bool &major, bool &counter, bool &ad1, bool &ad2) { counter = false; if (titleFailed) major = false; if (major) ad1 = ad2 = false; }; switch (titlebarPosition()) { default: case PositionTop: fixChangedState(topChanged, btmChanged, leftChanged, rightChanged); break; case PositionLeft: fixChangedState(leftChanged, rightChanged, topChanged, btmChanged); break; case PositionBottom: fixChangedState(btmChanged, topChanged, leftChanged, rightChanged); break; case PositionRight: fixChangedState(rightChanged, leftChanged, topChanged, btmChanged); break; } if (topChanged) moveResizeGeom.setTop(moveResizeGeom.y() + sign(previousMoveResizeGeom.y() - moveResizeGeom.y())); else if (leftChanged) moveResizeGeom.setLeft(moveResizeGeom.x() + sign(previousMoveResizeGeom.x() - moveResizeGeom.x())); else if (btmChanged) moveResizeGeom.setBottom(moveResizeGeom.bottom() + sign(previousMoveResizeGeom.bottom() - moveResizeGeom.bottom())); else if (rightChanged) moveResizeGeom.setRight(moveResizeGeom.right() + sign(previousMoveResizeGeom.right() - moveResizeGeom.right())); else break; // no position changed - that's certainly not good setMoveResizeGeometry(moveResizeGeom); } } // Always obey size hints, even when in "unrestricted" mode QSize size = adjustedSize(moveResizeGeometry().size(), sizemode); // the new topleft and bottomright corners (after checking size constrains), if they'll be needed topleft = QPoint(moveResizeGeometry().right() - size.width() + 1, moveResizeGeometry().bottom() - size.height() + 1); bottomright = QPoint(moveResizeGeometry().left() + size.width() - 1, moveResizeGeometry().top() + size.height() - 1); orig = moveResizeGeometry(); // if aspect ratios are specified, both dimensions may change. // Therefore grow to the right/bottom if needed. // TODO it should probably obey gravity rather than always using right/bottom ? if (sizemode == SizemodeFixedH) orig.setRight(bottomright.x()); else if (sizemode == SizemodeFixedW) orig.setBottom(bottomright.y()); calculateMoveResizeGeom(); if (moveResizeGeometry().size() != previousMoveResizeGeom.size()) update = true; } else if (isMove()) { Q_ASSERT(mode == PositionCenter); if (!isMovable()) { // isMovableAcrossScreens() must have been true to get here // Special moving of maximized windows on Xinerama screens int screen = screens()->number(globalPos); if (isFullScreen()) setMoveResizeGeometry(workspace()->clientArea(FullScreenArea, screen, 0)); else { QRect moveResizeGeom = workspace()->clientArea(MaximizeArea, screen, 0); QSize adjSize = adjustedSize(moveResizeGeom.size(), SizemodeMax); if (adjSize != moveResizeGeom.size()) { QRect r(moveResizeGeom); moveResizeGeom.setSize(adjSize); moveResizeGeom.moveCenter(r.center()); } setMoveResizeGeometry(moveResizeGeom); } } else { // first move, then snap, then check bounds QRect moveResizeGeom = moveResizeGeometry(); moveResizeGeom.moveTopLeft(topleft); moveResizeGeom.moveTopLeft(workspace()->adjustClientPosition(this, moveResizeGeom.topLeft(), isUnrestrictedMoveResize())); setMoveResizeGeometry(moveResizeGeom); if (!isUnrestrictedMoveResize()) { const QRegion strut = workspace()->restrictedMoveArea(desktop()); // Strut areas QRegion availableArea(workspace()->clientArea(FullArea, -1, 0)); // On the screen availableArea -= strut; // Strut areas bool transposed = false; int requiredPixels; QRect bTitleRect = titleBarRect(transposed, requiredPixels); for (;;) { QRect moveResizeGeom = moveResizeGeometry(); const QRect titleRect(bTitleRect.translated(moveResizeGeom.topLeft())); int visiblePixels = 0; for (const QRect &rect : availableArea) { const QRect r = rect & titleRect; if ((transposed && r.width() == titleRect.width()) || // Only the full size regions... (!transposed && r.height() == titleRect.height())) // ...prevents long slim areas visiblePixels += r.width() * r.height(); } if (visiblePixels >= requiredPixels) break; // We have reached a valid position // (esp.) if there're more screens with different struts (panels) it the titlebar // will be movable outside the movearea (covering one of the panels) until it // crosses the panel "too much" (not enough visiblePixels) and then stucks because // it's usually only pushed by 1px to either direction // so we first check whether we intersect suc strut and move the window below it // immediately (it's still possible to hit the visiblePixels >= titlebarArea break // by moving the window slightly downwards, but it won't stuck) // see bug #274466 // and bug #301805 for why we can't just match the titlearea against the screen if (screens()->count() > 1) { // optimization // TODO: could be useful on partial screen struts (half-width panels etc.) int newTitleTop = -1; for (const QRect &r : strut) { if (r.top() == 0 && r.width() > r.height() && // "top panel" r.intersects(moveResizeGeom) && moveResizeGeom.top() < r.bottom()) { newTitleTop = r.bottom() + 1; break; } } if (newTitleTop > -1) { moveResizeGeom.moveTop(newTitleTop); // invalid position, possibly on screen change setMoveResizeGeometry(moveResizeGeom); break; } } int dx = sign(previousMoveResizeGeom.x() - moveResizeGeom.x()), dy = sign(previousMoveResizeGeom.y() - moveResizeGeom.y()); if (visiblePixels && dx) // means there's no full width cap -> favor horizontally dy = 0; else if (dy) dx = 0; // Move it back moveResizeGeom.translate(dx, dy); setMoveResizeGeometry(moveResizeGeom); if (moveResizeGeom == previousMoveResizeGeom) { break; // Prevent lockup } } } } if (moveResizeGeometry().topLeft() != previousMoveResizeGeom.topLeft()) update = true; } else abort(); if (!update) return; if (isResize() && !haveResizeEffect()) { doResizeSync(); } else performMoveResize(); if (isMove()) { ScreenEdges::self()->check(globalPos, QDateTime::fromMSecsSinceEpoch(xTime())); } } void Client::doResizeSync() { if (!syncRequest.timeout) { syncRequest.timeout = new QTimer(this); connect(syncRequest.timeout, &QTimer::timeout, this, &Client::performMoveResize); syncRequest.timeout->setSingleShot(true); } if (syncRequest.counter != XCB_NONE) { syncRequest.timeout->start(250); sendSyncRequest(); } else { // for clients not supporting the XSYNC protocol, we syncRequest.isPending = true; // limit the resizes to 30Hz to take pointless load from X11 syncRequest.timeout->start(33); // and the client, the mouse is still moved at full speed } // and no human can control faster resizes anyway const QRect &moveResizeGeom = moveResizeGeometry(); m_client.setGeometry(0, 0, moveResizeGeom.width() - (borderLeft() + borderRight()), moveResizeGeom.height() - (borderTop() + borderBottom())); } void AbstractClient::performMoveResize() { const QRect &moveResizeGeom = moveResizeGeometry(); if (isMove() || (isResize() && !haveResizeEffect())) { setGeometry(moveResizeGeom); } doPerformMoveResize(); if (isResize()) addRepaintFull(); positionGeometryTip(); emit clientStepUserMovedResized(this, moveResizeGeom); } void Client::doPerformMoveResize() { if (syncRequest.counter == XCB_NONE) // client w/o XSYNC support. allow the next resize event syncRequest.isPending = false; // NEVER do this for clients with a valid counter // (leads to sync request races in some clients) } void AbstractClient::setElectricBorderMode(QuickTileMode mode) { if (mode != QuickTileMode(QuickTileFlag::Maximize)) { // sanitize the mode, ie. simplify "invalid" combinations if ((mode & QuickTileFlag::Horizontal) == QuickTileMode(QuickTileFlag::Horizontal)) mode &= ~QuickTileMode(QuickTileFlag::Horizontal); if ((mode & QuickTileFlag::Vertical) == QuickTileMode(QuickTileFlag::Vertical)) mode &= ~QuickTileMode(QuickTileFlag::Vertical); } m_electricMode = mode; } void AbstractClient::setElectricBorderMaximizing(bool maximizing) { m_electricMaximizing = maximizing; if (maximizing) outline()->show(electricBorderMaximizeGeometry(Cursor::pos(), desktop()), moveResizeGeometry()); else outline()->hide(); elevate(maximizing); } QRect AbstractClient::electricBorderMaximizeGeometry(QPoint pos, int desktop) { if (electricBorderMode() == QuickTileMode(QuickTileFlag::Maximize)) { if (maximizeMode() == MaximizeFull) return geometryRestore(); else return workspace()->clientArea(MaximizeArea, pos, desktop); } QRect ret = workspace()->clientArea(MaximizeArea, pos, desktop); if (electricBorderMode() & QuickTileFlag::Left) ret.setRight(ret.left()+ret.width()/2 - 1); else if (electricBorderMode() & QuickTileFlag::Right) ret.setLeft(ret.right()-(ret.width()-ret.width()/2) + 1); if (electricBorderMode() & QuickTileFlag::Top) ret.setBottom(ret.top()+ret.height()/2 - 1); else if (electricBorderMode() & QuickTileFlag::Bottom) ret.setTop(ret.bottom()-(ret.height()-ret.height()/2) + 1); return ret; } void AbstractClient::setQuickTileMode(QuickTileMode mode, bool keyboard) { // Only allow quick tile on a regular window. if (!isResizable()) { return; } workspace()->updateFocusMousePosition(Cursor::pos()); // may cause leave event GeometryUpdatesBlocker blocker(this); if (mode == QuickTileMode(QuickTileFlag::Maximize)) { m_quickTileMode = int(QuickTileFlag::None); if (maximizeMode() == MaximizeFull) { setMaximize(false, false); } else { QRect prev_geom_restore = geometryRestore(); // setMaximize() would set moveResizeGeom as geom_restore m_quickTileMode = int(QuickTileFlag::Maximize); setMaximize(true, true); QRect clientArea = workspace()->clientArea(MaximizeArea, this); if (geometry().top() != clientArea.top()) { QRect r(geometry()); r.moveTop(clientArea.top()); setGeometry(r); } setGeometryRestore(prev_geom_restore); } emit quickTileModeChanged(); return; } // sanitize the mode, ie. simplify "invalid" combinations if ((mode & QuickTileFlag::Horizontal) == QuickTileMode(QuickTileFlag::Horizontal)) mode &= ~QuickTileMode(QuickTileFlag::Horizontal); if ((mode & QuickTileFlag::Vertical) == QuickTileMode(QuickTileFlag::Vertical)) mode &= ~QuickTileMode(QuickTileFlag::Vertical); setElectricBorderMode(mode); // used by ::electricBorderMaximizeGeometry(.) // restore from maximized so that it is possible to tile maximized windows with one hit or by dragging if (maximizeMode() != MaximizeRestore) { if (mode != QuickTileMode(QuickTileFlag::None)) { // decorations may turn off some borders when tiled const ForceGeometry_t geom_mode = isDecorated() ? ForceGeometrySet : NormalGeometrySet; m_quickTileMode = int(QuickTileFlag::None); // Temporary, so the maximize code doesn't get all confused setMaximize(false, false); setGeometry(electricBorderMaximizeGeometry(keyboard ? geometry().center() : Cursor::pos(), desktop()), geom_mode); // Store the mode change m_quickTileMode = mode; } else { m_quickTileMode = mode; setMaximize(false, false); } emit quickTileModeChanged(); return; } if (mode != QuickTileMode(QuickTileFlag::None)) { QPoint whichScreen = keyboard ? geometry().center() : Cursor::pos(); // If trying to tile to the side that the window is already tiled to move the window to the next // screen if it exists, otherwise toggle the mode (set QuickTileFlag::None) if (quickTileMode() == mode) { const int numScreens = screens()->count(); const int curScreen = screen(); int nextScreen = curScreen; QVarLengthArray screens(numScreens); for (int i = 0; i < numScreens; ++i) // Cache screens[i] = Screens::self()->geometry(i); for (int i = 0; i < numScreens; ++i) { if (i == curScreen) continue; if (screens[i].bottom() <= screens[curScreen].top() || screens[i].top() >= screens[curScreen].bottom()) continue; // not in horizontal line const int x = screens[i].center().x(); if ((mode & QuickTileFlag::Horizontal) == QuickTileMode(QuickTileFlag::Left)) { if (x >= screens[curScreen].center().x() || (curScreen != nextScreen && x <= screens[nextScreen].center().x())) continue; // not left of current or more left then found next } else if ((mode & QuickTileFlag::Horizontal) == QuickTileMode(QuickTileFlag::Right)) { if (x <= screens[curScreen].center().x() || (curScreen != nextScreen && x >= screens[nextScreen].center().x())) continue; // not right of current or more right then found next } nextScreen = i; } if (nextScreen == curScreen) { mode = QuickTileFlag::None; // No other screens, toggle tiling } else { // Move to other screen setGeometry(geometryRestore().translated(screens[nextScreen].topLeft() - screens[curScreen].topLeft())); whichScreen = screens[nextScreen].center(); // Swap sides if (mode & QuickTileFlag::Horizontal) { mode = (~mode & QuickTileFlag::Horizontal) | (mode & QuickTileFlag::Vertical); } } setElectricBorderMode(mode); // used by ::electricBorderMaximizeGeometry(.) } else if (quickTileMode() == QuickTileMode(QuickTileFlag::None)) { // Not coming out of an existing tile, not shifting monitors, we're setting a brand new tile. // Store geometry first, so we can go out of this tile later. setGeometryRestore(geometry()); } if (mode != QuickTileMode(QuickTileFlag::None)) { m_quickTileMode = mode; // decorations may turn off some borders when tiled const ForceGeometry_t geom_mode = isDecorated() ? ForceGeometrySet : NormalGeometrySet; // Temporary, so the maximize code doesn't get all confused m_quickTileMode = int(QuickTileFlag::None); setGeometry(electricBorderMaximizeGeometry(whichScreen, desktop()), geom_mode); } // Store the mode change m_quickTileMode = mode; } if (mode == QuickTileMode(QuickTileFlag::None)) { m_quickTileMode = int(QuickTileFlag::None); // Untiling, so just restore geometry, and we're done. if (!geometryRestore().isValid()) // invalid if we started maximized and wait for placement setGeometryRestore(geometry()); // decorations may turn off some borders when tiled const ForceGeometry_t geom_mode = isDecorated() ? ForceGeometrySet : NormalGeometrySet; setGeometry(geometryRestore(), geom_mode); checkWorkspacePosition(); // Just in case it's a different screen } emit quickTileModeChanged(); } void AbstractClient::sendToScreen(int newScreen) { newScreen = rules()->checkScreen(newScreen); if (isActive()) { screens()->setCurrent(newScreen); // might impact the layer of a fullscreen window foreach (AbstractClient *cc, workspace()->allClientList()) { if (cc->isFullScreen() && cc->screen() == newScreen) { cc->updateLayer(); } } } if (screen() == newScreen) // Don't use isOnScreen(), that's true even when only partially return; GeometryUpdatesBlocker blocker(this); // operating on the maximized / quicktiled window would leave the old geom_restore behind, // so we clear the state first MaximizeMode maxMode = maximizeMode(); QuickTileMode qtMode = quickTileMode(); if (maxMode != MaximizeRestore) maximize(MaximizeRestore); if (qtMode != QuickTileMode(QuickTileFlag::None)) setQuickTileMode(QuickTileFlag::None, true); QRect oldScreenArea = workspace()->clientArea(MaximizeArea, this); QRect screenArea = workspace()->clientArea(MaximizeArea, newScreen, desktop()); // the window can have its center so that the position correction moves the new center onto // the old screen, what will tile it where it is. Ie. the screen is not changed // this happens esp. with electric border quicktiling if (qtMode != QuickTileMode(QuickTileFlag::None)) keepInArea(oldScreenArea); QRect oldGeom = geometry(); QRect newGeom = oldGeom; // move the window to have the same relative position to the center of the screen // (i.e. one near the middle of the right edge will also end up near the middle of the right edge) QPoint center = newGeom.center() - oldScreenArea.center(); center.setX(center.x() * screenArea.width() / oldScreenArea.width()); center.setY(center.y() * screenArea.height() / oldScreenArea.height()); center += screenArea.center(); newGeom.moveCenter(center); setGeometry(newGeom); // If the window was inside the old screen area, explicitly make sure its inside also the new screen area. // Calling checkWorkspacePosition() should ensure that, but when moving to a small screen the window could // be big enough to overlap outside of the new screen area, making struts from other screens come into effect, // which could alter the resulting geometry. if (oldScreenArea.contains(oldGeom)) { keepInArea(screenArea); } // align geom_restore - checkWorkspacePosition operates on it setGeometryRestore(geometry()); checkWorkspacePosition(oldGeom); // re-align geom_restore to constrained geometry setGeometryRestore(geometry()); // finally reset special states // NOTICE that MaximizeRestore/QuickTileFlag::None checks are required. // eg. setting QuickTileFlag::None would break maximization if (maxMode != MaximizeRestore) maximize(maxMode); if (qtMode != QuickTileMode(QuickTileFlag::None) && qtMode != quickTileMode()) setQuickTileMode(qtMode, true); auto tso = workspace()->ensureStackingOrder(transients()); for (auto it = tso.constBegin(), end = tso.constEnd(); it != end; ++it) (*it)->sendToScreen(newScreen); } } // namespace diff --git a/idle_inhibition.cpp b/idle_inhibition.cpp index 30c433fb9..aeabb7437 100644 --- a/idle_inhibition.cpp +++ b/idle_inhibition.cpp @@ -1,117 +1,121 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2017 Martin Flöser Copyright (C) 2018 Vlad Zagorodniy 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 . *********************************************************************/ #include "idle_inhibition.h" #include "deleted.h" #include "shell_client.h" #include "workspace.h" #include #include #include #include using KWayland::Server::SurfaceInterface; namespace KWin { IdleInhibition::IdleInhibition(IdleInterface *idle) : QObject(idle) , m_idle(idle) { // Workspace is created after the wayland server is initialized. connect(kwinApp(), &Application::workspaceCreated, this, &IdleInhibition::slotWorkspaceCreated); } IdleInhibition::~IdleInhibition() = default; void IdleInhibition::registerShellClient(ShellClient *client) { auto updateInhibit = [this, client] { update(client); }; m_connections[client] = connect(client->surface(), &SurfaceInterface::inhibitsIdleChanged, this, updateInhibit); connect(client, &ShellClient::desktopChanged, this, updateInhibit); connect(client, &ShellClient::clientMinimized, this, updateInhibit); connect(client, &ShellClient::clientUnminimized, this, updateInhibit); connect(client, &ShellClient::windowHidden, this, updateInhibit); connect(client, &ShellClient::windowShown, this, updateInhibit); connect(client, &ShellClient::windowClosed, this, [this, client] { uninhibit(client); auto it = m_connections.find(client); if (it != m_connections.end()) { disconnect(it.value()); m_connections.erase(it); } } ); updateInhibit(); } void IdleInhibition::inhibit(AbstractClient *client) { if (isInhibited(client)) { // already inhibited return; } m_idleInhibitors << client; m_idle->inhibit(); // TODO: notify powerdevil? } void IdleInhibition::uninhibit(AbstractClient *client) { auto it = std::find(m_idleInhibitors.begin(), m_idleInhibitors.end(), client); if (it == m_idleInhibitors.end()) { // not inhibited return; } m_idleInhibitors.erase(it); m_idle->uninhibit(); } void IdleInhibition::update(AbstractClient *client) { + if (client->isInternal()) { + return; + } + // TODO: Don't honor the idle inhibitor object if the shell client is not // on the current activity (currently, activities are not supported). const bool visible = client->isShown(true) && client->isOnCurrentDesktop(); if (visible && client->surface()->inhibitsIdle()) { inhibit(client); } else { uninhibit(client); } } void IdleInhibition::slotWorkspaceCreated() { connect(workspace(), &Workspace::currentDesktopChanged, this, &IdleInhibition::slotDesktopChanged); } void IdleInhibition::slotDesktopChanged() { workspace()->forEachAbstractClient([this] (AbstractClient *c) { update(c); }); } } diff --git a/input.cpp b/input.cpp index 839e24db2..fee89a68c 100644 --- a/input.cpp +++ b/input.cpp @@ -1,2426 +1,2427 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2013 Martin Gräßlin Copyright (C) 2018 Roman Gilg Copyright (C) 2019 Vlad Zagorodniy 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 . *********************************************************************/ #include "input.h" #include "input_event.h" #include "input_event_spy.h" #include "keyboard_input.h" #include "pointer_input.h" #include "touch_input.h" #include "touch_hide_cursor_spy.h" #include "client.h" #include "effects.h" #include "gestures.h" #include "globalshortcuts.h" #include "logind.h" #include "main.h" #ifdef KWIN_BUILD_TABBOX #include "tabbox/tabbox.h" #endif #include "unmanaged.h" #include "screenedge.h" #include "screens.h" #include "workspace.h" #include "libinput/connection.h" #include "libinput/device.h" #include "platform.h" #include "popup_input_filter.h" #include "shell_client.h" #include "wayland_server.h" #include "xwl/xwayland_interface.h" +#include "internal_client.h" #include #include #include #include #include #include #include //screenlocker #include // Qt #include #include namespace KWin { InputEventFilter::InputEventFilter() = default; InputEventFilter::~InputEventFilter() { if (input()) { input()->uninstallInputEventFilter(this); } } bool InputEventFilter::pointerEvent(QMouseEvent *event, quint32 nativeButton) { Q_UNUSED(event) Q_UNUSED(nativeButton) return false; } bool InputEventFilter::wheelEvent(QWheelEvent *event) { Q_UNUSED(event) return false; } bool InputEventFilter::keyEvent(QKeyEvent *event) { Q_UNUSED(event) return false; } bool InputEventFilter::touchDown(qint32 id, const QPointF &point, quint32 time) { Q_UNUSED(id) Q_UNUSED(point) Q_UNUSED(time) return false; } bool InputEventFilter::touchMotion(qint32 id, const QPointF &point, quint32 time) { Q_UNUSED(id) Q_UNUSED(point) Q_UNUSED(time) return false; } bool InputEventFilter::touchUp(qint32 id, quint32 time) { Q_UNUSED(id) Q_UNUSED(time) return false; } bool InputEventFilter::pinchGestureBegin(int fingerCount, quint32 time) { Q_UNUSED(fingerCount) Q_UNUSED(time) return false; } bool InputEventFilter::pinchGestureUpdate(qreal scale, qreal angleDelta, const QSizeF &delta, quint32 time) { Q_UNUSED(scale) Q_UNUSED(angleDelta) Q_UNUSED(delta) Q_UNUSED(time) return false; } bool InputEventFilter::pinchGestureEnd(quint32 time) { Q_UNUSED(time) return false; } bool InputEventFilter::pinchGestureCancelled(quint32 time) { Q_UNUSED(time) return false; } bool InputEventFilter::swipeGestureBegin(int fingerCount, quint32 time) { Q_UNUSED(fingerCount) Q_UNUSED(time) return false; } bool InputEventFilter::swipeGestureUpdate(const QSizeF &delta, quint32 time) { Q_UNUSED(delta) Q_UNUSED(time) return false; } bool InputEventFilter::swipeGestureEnd(quint32 time) { Q_UNUSED(time) return false; } bool InputEventFilter::swipeGestureCancelled(quint32 time) { Q_UNUSED(time) return false; } bool InputEventFilter::switchEvent(SwitchEvent *event) { Q_UNUSED(event) return false; } void InputEventFilter::passToWaylandServer(QKeyEvent *event) { Q_ASSERT(waylandServer()); if (event->isAutoRepeat()) { return; } switch (event->type()) { case QEvent::KeyPress: waylandServer()->seat()->keyPressed(event->nativeScanCode()); break; case QEvent::KeyRelease: waylandServer()->seat()->keyReleased(event->nativeScanCode()); break; default: break; } } class VirtualTerminalFilter : public InputEventFilter { public: bool keyEvent(QKeyEvent *event) override { // really on press and not on release? X11 switches on press. if (event->type() == QEvent::KeyPress && !event->isAutoRepeat()) { const xkb_keysym_t keysym = event->nativeVirtualKey(); if (keysym >= XKB_KEY_XF86Switch_VT_1 && keysym <= XKB_KEY_XF86Switch_VT_12) { LogindIntegration::self()->switchVirtualTerminal(keysym - XKB_KEY_XF86Switch_VT_1 + 1); return true; } } return false; } }; class TerminateServerFilter : public InputEventFilter { public: bool keyEvent(QKeyEvent *event) override { if (event->type() == QEvent::KeyPress && !event->isAutoRepeat()) { if (event->nativeVirtualKey() == XKB_KEY_Terminate_Server) { qCWarning(KWIN_CORE) << "Request to terminate server"; QMetaObject::invokeMethod(qApp, "quit", Qt::QueuedConnection); return true; } } return false; } }; class LockScreenFilter : public InputEventFilter { public: bool pointerEvent(QMouseEvent *event, quint32 nativeButton) override { if (!waylandServer()->isScreenLocked()) { return false; } auto seat = waylandServer()->seat(); seat->setTimestamp(event->timestamp()); if (event->type() == QEvent::MouseMove) { if (pointerSurfaceAllowed()) { // TODO: should the pointer position always stay in sync, i.e. not do the check? seat->setPointerPos(event->screenPos().toPoint()); } } else if (event->type() == QEvent::MouseButtonPress || event->type() == QEvent::MouseButtonRelease) { if (pointerSurfaceAllowed()) { // TODO: can we leak presses/releases here when we move the mouse in between from an allowed surface to // disallowed one or vice versa? event->type() == QEvent::MouseButtonPress ? seat->pointerButtonPressed(nativeButton) : seat->pointerButtonReleased(nativeButton); } } return true; } bool wheelEvent(QWheelEvent *event) override { if (!waylandServer()->isScreenLocked()) { return false; } auto seat = waylandServer()->seat(); if (pointerSurfaceAllowed()) { seat->setTimestamp(event->timestamp()); const Qt::Orientation orientation = event->angleDelta().x() == 0 ? Qt::Vertical : Qt::Horizontal; seat->pointerAxis(orientation, orientation == Qt::Horizontal ? event->angleDelta().x() : event->angleDelta().y()); } return true; } bool keyEvent(QKeyEvent * event) override { if (!waylandServer()->isScreenLocked()) { return false; } if (event->isAutoRepeat()) { // wayland client takes care of it return true; } // send event to KSldApp for global accel // if event is set to accepted it means a whitelisted shortcut was triggered // in that case we filter it out and don't process it further event->setAccepted(false); QCoreApplication::sendEvent(ScreenLocker::KSldApp::self(), event); if (event->isAccepted()) { return true; } // continue normal processing input()->keyboard()->update(); auto seat = waylandServer()->seat(); seat->setTimestamp(event->timestamp()); if (!keyboardSurfaceAllowed()) { // don't pass event to seat return true; } switch (event->type()) { case QEvent::KeyPress: seat->keyPressed(event->nativeScanCode()); break; case QEvent::KeyRelease: seat->keyReleased(event->nativeScanCode()); break; default: break; } return true; } bool touchDown(qint32 id, const QPointF &pos, quint32 time) override { if (!waylandServer()->isScreenLocked()) { return false; } auto seat = waylandServer()->seat(); seat->setTimestamp(time); if (touchSurfaceAllowed()) { input()->touch()->insertId(id, seat->touchDown(pos)); } return true; } bool touchMotion(qint32 id, const QPointF &pos, quint32 time) override { if (!waylandServer()->isScreenLocked()) { return false; } auto seat = waylandServer()->seat(); seat->setTimestamp(time); if (touchSurfaceAllowed()) { const qint32 kwaylandId = input()->touch()->mappedId(id); if (kwaylandId != -1) { seat->touchMove(kwaylandId, pos); } } return true; } bool touchUp(qint32 id, quint32 time) override { if (!waylandServer()->isScreenLocked()) { return false; } auto seat = waylandServer()->seat(); seat->setTimestamp(time); if (touchSurfaceAllowed()) { const qint32 kwaylandId = input()->touch()->mappedId(id); if (kwaylandId != -1) { seat->touchUp(kwaylandId); input()->touch()->removeId(id); } } return true; } bool pinchGestureBegin(int fingerCount, quint32 time) override { Q_UNUSED(fingerCount) Q_UNUSED(time) // no touchpad multi-finger gestures on lock screen return waylandServer()->isScreenLocked(); } bool pinchGestureUpdate(qreal scale, qreal angleDelta, const QSizeF &delta, quint32 time) override { Q_UNUSED(scale) Q_UNUSED(angleDelta) Q_UNUSED(delta) Q_UNUSED(time) // no touchpad multi-finger gestures on lock screen return waylandServer()->isScreenLocked(); } bool pinchGestureEnd(quint32 time) override { Q_UNUSED(time) // no touchpad multi-finger gestures on lock screen return waylandServer()->isScreenLocked(); } bool pinchGestureCancelled(quint32 time) override { Q_UNUSED(time) // no touchpad multi-finger gestures on lock screen return waylandServer()->isScreenLocked(); } bool swipeGestureBegin(int fingerCount, quint32 time) override { Q_UNUSED(fingerCount) Q_UNUSED(time) // no touchpad multi-finger gestures on lock screen return waylandServer()->isScreenLocked(); } bool swipeGestureUpdate(const QSizeF &delta, quint32 time) override { Q_UNUSED(delta) Q_UNUSED(time) // no touchpad multi-finger gestures on lock screen return waylandServer()->isScreenLocked(); } bool swipeGestureEnd(quint32 time) override { Q_UNUSED(time) // no touchpad multi-finger gestures on lock screen return waylandServer()->isScreenLocked(); } bool swipeGestureCancelled(quint32 time) override { Q_UNUSED(time) // no touchpad multi-finger gestures on lock screen return waylandServer()->isScreenLocked(); } private: bool surfaceAllowed(KWayland::Server::SurfaceInterface *(KWayland::Server::SeatInterface::*method)() const) const { if (KWayland::Server::SurfaceInterface *s = (waylandServer()->seat()->*method)()) { if (Toplevel *t = waylandServer()->findClient(s)) { return t->isLockScreen() || t->isInputMethod(); } return false; } return true; } bool pointerSurfaceAllowed() const { return surfaceAllowed(&KWayland::Server::SeatInterface::focusedPointerSurface); } bool keyboardSurfaceAllowed() const { return surfaceAllowed(&KWayland::Server::SeatInterface::focusedKeyboardSurface); } bool touchSurfaceAllowed() const { return surfaceAllowed(&KWayland::Server::SeatInterface::focusedTouchSurface); } }; class EffectsFilter : public InputEventFilter { public: bool pointerEvent(QMouseEvent *event, quint32 nativeButton) override { Q_UNUSED(nativeButton) if (!effects) { return false; } return static_cast(effects)->checkInputWindowEvent(event); } bool keyEvent(QKeyEvent *event) override { if (!effects || !static_cast< EffectsHandlerImpl* >(effects)->hasKeyboardGrab()) { return false; } waylandServer()->seat()->setFocusedKeyboardSurface(nullptr); passToWaylandServer(event); static_cast< EffectsHandlerImpl* >(effects)->grabbedKeyboardEvent(event); return true; } bool touchDown(qint32 id, const QPointF &pos, quint32 time) override { if (!effects) { return false; } return static_cast< EffectsHandlerImpl* >(effects)->touchDown(id, pos, time); } bool touchMotion(qint32 id, const QPointF &pos, quint32 time) override { if (!effects) { return false; } return static_cast< EffectsHandlerImpl* >(effects)->touchMotion(id, pos, time); } bool touchUp(qint32 id, quint32 time) override { if (!effects) { return false; } return static_cast< EffectsHandlerImpl* >(effects)->touchUp(id, time); } }; class MoveResizeFilter : public InputEventFilter { public: bool pointerEvent(QMouseEvent *event, quint32 nativeButton) override { Q_UNUSED(nativeButton) AbstractClient *c = workspace()->moveResizeClient(); if (!c) { return false; } switch (event->type()) { case QEvent::MouseMove: c->updateMoveResize(event->screenPos().toPoint()); break; case QEvent::MouseButtonRelease: if (event->buttons() == Qt::NoButton) { c->endMoveResize(); } break; default: break; } return true; } bool wheelEvent(QWheelEvent *event) override { Q_UNUSED(event) // filter out while moving a window return workspace()->moveResizeClient() != nullptr; } bool keyEvent(QKeyEvent *event) override { AbstractClient *c = workspace()->moveResizeClient(); if (!c) { return false; } if (event->type() == QEvent::KeyPress) { c->keyPressEvent(event->key() | event->modifiers()); if (c->isMove() || c->isResize()) { // only update if mode didn't end c->updateMoveResize(input()->globalPointer()); } } return true; } bool touchDown(qint32 id, const QPointF &pos, quint32 time) override { Q_UNUSED(id) Q_UNUSED(pos) Q_UNUSED(time) AbstractClient *c = workspace()->moveResizeClient(); if (!c) { return false; } return true; } bool touchMotion(qint32 id, const QPointF &pos, quint32 time) override { Q_UNUSED(time) AbstractClient *c = workspace()->moveResizeClient(); if (!c) { return false; } if (!m_set) { m_id = id; m_set = true; } if (m_id == id) { c->updateMoveResize(pos.toPoint()); } return true; } bool touchUp(qint32 id, quint32 time) override { Q_UNUSED(time) AbstractClient *c = workspace()->moveResizeClient(); if (!c) { return false; } if (m_id == id || !m_set) { c->endMoveResize(); m_set = false; // pass through to update decoration filter later on return false; } m_set = false; return true; } private: qint32 m_id = 0; bool m_set = false; }; class WindowSelectorFilter : public InputEventFilter { public: bool pointerEvent(QMouseEvent *event, quint32 nativeButton) override { Q_UNUSED(nativeButton) if (!m_active) { return false; } switch (event->type()) { case QEvent::MouseButtonRelease: if (event->buttons() == Qt::NoButton) { if (event->button() == Qt::RightButton) { cancel(); } else { accept(event->globalPos()); } } break; default: break; } return true; } bool wheelEvent(QWheelEvent *event) override { Q_UNUSED(event) // filter out while selecting a window return m_active; } bool keyEvent(QKeyEvent *event) override { Q_UNUSED(event) if (!m_active) { return false; } waylandServer()->seat()->setFocusedKeyboardSurface(nullptr); passToWaylandServer(event); if (event->type() == QEvent::KeyPress) { // x11 variant does this on key press, so do the same if (event->key() == Qt::Key_Escape) { cancel(); } else if (event->key() == Qt::Key_Enter || event->key() == Qt::Key_Return || event->key() == Qt::Key_Space) { accept(input()->globalPointer()); } if (input()->supportsPointerWarping()) { int mx = 0; int my = 0; if (event->key() == Qt::Key_Left) { mx = -10; } if (event->key() == Qt::Key_Right) { mx = 10; } if (event->key() == Qt::Key_Up) { my = -10; } if (event->key() == Qt::Key_Down) { my = 10; } if (event->modifiers() & Qt::ControlModifier) { mx /= 10; my /= 10; } input()->warpPointer(input()->globalPointer() + QPointF(mx, my)); } } // filter out while selecting a window return true; } bool touchDown(qint32 id, const QPointF &pos, quint32 time) override { Q_UNUSED(time) if (!isActive()) { return false; } m_touchPoints.insert(id, pos); return true; } bool touchMotion(qint32 id, const QPointF &pos, quint32 time) override { Q_UNUSED(time) if (!isActive()) { return false; } auto it = m_touchPoints.find(id); if (it != m_touchPoints.end()) { *it = pos; } return true; } bool touchUp(qint32 id, quint32 time) override { Q_UNUSED(time) if (!isActive()) { return false; } auto it = m_touchPoints.find(id); if (it != m_touchPoints.end()) { const auto pos = it.value(); m_touchPoints.erase(it); if (m_touchPoints.isEmpty()) { accept(pos); } } return true; } bool isActive() const { return m_active; } void start(std::function callback) { Q_ASSERT(!m_active); m_active = true; m_callback = callback; input()->keyboard()->update(); input()->cancelTouch(); } void start(std::function callback) { Q_ASSERT(!m_active); m_active = true; m_pointSelectionFallback = callback; input()->keyboard()->update(); input()->cancelTouch(); } private: void deactivate() { m_active = false; m_callback = std::function(); m_pointSelectionFallback = std::function(); input()->pointer()->removeWindowSelectionCursor(); input()->keyboard()->update(); m_touchPoints.clear(); } void cancel() { if (m_callback) { m_callback(nullptr); } if (m_pointSelectionFallback) { m_pointSelectionFallback(QPoint(-1, -1)); } deactivate(); } void accept(const QPoint &pos) { if (m_callback) { // TODO: this ignores shaped windows m_callback(input()->findToplevel(pos)); } if (m_pointSelectionFallback) { m_pointSelectionFallback(pos); } deactivate(); } void accept(const QPointF &pos) { accept(pos.toPoint()); } bool m_active = false; std::function m_callback; std::function m_pointSelectionFallback; QMap m_touchPoints; }; class GlobalShortcutFilter : public InputEventFilter { public: bool pointerEvent(QMouseEvent *event, quint32 nativeButton) override { Q_UNUSED(nativeButton); if (event->type() == QEvent::MouseButtonPress) { if (input()->shortcuts()->processPointerPressed(event->modifiers(), event->buttons())) { return true; } } return false; } bool wheelEvent(QWheelEvent *event) override { if (event->modifiers() == Qt::NoModifier) { return false; } PointerAxisDirection direction = PointerAxisUp; if (event->angleDelta().x() < 0) { direction = PointerAxisRight; } else if (event->angleDelta().x() > 0) { direction = PointerAxisLeft; } else if (event->angleDelta().y() < 0) { direction = PointerAxisDown; } else if (event->angleDelta().y() > 0) { direction = PointerAxisUp; } return input()->shortcuts()->processAxis(event->modifiers(), direction); } bool keyEvent(QKeyEvent *event) override { if (event->type() == QEvent::KeyPress) { return input()->shortcuts()->processKey(static_cast(event)->modifiersRelevantForGlobalShortcuts(), event->key()); } return false; } bool swipeGestureBegin(int fingerCount, quint32 time) override { Q_UNUSED(time) input()->shortcuts()->processSwipeStart(fingerCount); return false; } bool swipeGestureUpdate(const QSizeF &delta, quint32 time) override { Q_UNUSED(time) input()->shortcuts()->processSwipeUpdate(delta); return false; } bool swipeGestureCancelled(quint32 time) override { Q_UNUSED(time) input()->shortcuts()->processSwipeCancel(); return false; } bool swipeGestureEnd(quint32 time) override { Q_UNUSED(time) input()->shortcuts()->processSwipeEnd(); return false; } }; namespace { enum class MouseAction { ModifierOnly, ModifierAndWindow }; std::pair performClientMouseAction(QMouseEvent *event, AbstractClient *client, MouseAction action = MouseAction::ModifierOnly) { Options::MouseCommand command = Options::MouseNothing; bool wasAction = false; if (static_cast(event)->modifiersRelevantForGlobalShortcuts() == options->commandAllModifier()) { if (!input()->pointer()->isConstrained() && !workspace()->globalShortcutsDisabled()) { wasAction = true; switch (event->button()) { case Qt::LeftButton: command = options->commandAll1(); break; case Qt::MiddleButton: command = options->commandAll2(); break; case Qt::RightButton: command = options->commandAll3(); break; default: // nothing break; } } } else { if (action == MouseAction::ModifierAndWindow) { command = client->getMouseCommand(event->button(), &wasAction); } } if (wasAction) { return std::make_pair(wasAction, !client->performMouseCommand(command, event->globalPos())); } return std::make_pair(wasAction, false); } std::pair performClientWheelAction(QWheelEvent *event, AbstractClient *c, MouseAction action = MouseAction::ModifierOnly) { bool wasAction = false; Options::MouseCommand command = Options::MouseNothing; if (static_cast(event)->modifiersRelevantForGlobalShortcuts() == options->commandAllModifier()) { if (!input()->pointer()->isConstrained() && !workspace()->globalShortcutsDisabled()) { wasAction = true; command = options->operationWindowMouseWheel(-1 * event->angleDelta().y()); } } else { if (action == MouseAction::ModifierAndWindow) { command = c->getWheelCommand(Qt::Vertical, &wasAction); } } if (wasAction) { return std::make_pair(wasAction, !c->performMouseCommand(command, event->globalPos())); } return std::make_pair(wasAction, false); } } class InternalWindowEventFilter : public InputEventFilter { bool pointerEvent(QMouseEvent *event, quint32 nativeButton) override { Q_UNUSED(nativeButton) auto internal = input()->pointer()->internalWindow(); if (!internal) { return false; } // find client switch (event->type()) { case QEvent::MouseButtonPress: case QEvent::MouseButtonRelease: { - auto s = waylandServer()->findClient(internal); + auto s = qobject_cast(workspace()->findInternal(internal)); if (s && s->isDecorated()) { // only perform mouse commands on decorated internal windows const auto actionResult = performClientMouseAction(event, s); if (actionResult.first) { return actionResult.second; } } break; } default: break; } QMouseEvent e(event->type(), event->pos() - internal->position(), event->globalPos(), event->button(), event->buttons(), event->modifiers()); e.setAccepted(false); QCoreApplication::sendEvent(internal.data(), &e); return e.isAccepted(); } bool wheelEvent(QWheelEvent *event) override { auto internal = input()->pointer()->internalWindow(); if (!internal) { return false; } if (event->angleDelta().y() != 0) { - auto s = waylandServer()->findClient(internal); + auto s = qobject_cast(workspace()->findInternal(internal)); if (s && s->isDecorated()) { // client window action only on vertical scrolling const auto actionResult = performClientWheelAction(event, s); if (actionResult.first) { return actionResult.second; } } } const QPointF localPos = event->globalPosF() - QPointF(internal->x(), internal->y()); const Qt::Orientation orientation = (event->angleDelta().x() != 0) ? Qt::Horizontal : Qt::Vertical; const int delta = event->angleDelta().x() != 0 ? event->angleDelta().x() : event->angleDelta().y(); QWheelEvent e(localPos, event->globalPosF(), QPoint(), event->angleDelta() * -1, delta * -1, orientation, event->buttons(), event->modifiers()); e.setAccepted(false); QCoreApplication::sendEvent(internal.data(), &e); return e.isAccepted(); } bool keyEvent(QKeyEvent *event) override { - const auto &internalClients = waylandServer()->internalClients(); + const QList &internalClients = workspace()->internalClients(); if (internalClients.isEmpty()) { return false; } QWindow *found = nullptr; auto it = internalClients.end(); do { it--; if (QWindow *w = (*it)->internalWindow()) { if (!w->isVisible()) { continue; } if (!screens()->geometry().contains(w->geometry())) { continue; } if (w->property("_q_showWithoutActivating").toBool()) { continue; } if (w->property("outputOnly").toBool()) { continue; } if (w->flags().testFlag(Qt::ToolTip)) { continue; } found = w; break; } } while (it != internalClients.begin()); if (!found) { return false; } auto xkb = input()->keyboard()->xkb(); Qt::Key key = xkb->toQtKey(xkb->toKeysym(event->nativeScanCode())); if (key == Qt::Key_Super_L || key == Qt::Key_Super_R) { // workaround for QTBUG-62102 key = Qt::Key_Meta; } QKeyEvent internalEvent(event->type(), key, event->modifiers(), event->nativeScanCode(), event->nativeVirtualKey(), event->nativeModifiers(), event->text()); internalEvent.setAccepted(false); if (QCoreApplication::sendEvent(found, &internalEvent)) { waylandServer()->seat()->setFocusedKeyboardSurface(nullptr); passToWaylandServer(event); return true; } return false; } bool touchDown(qint32 id, const QPointF &pos, quint32 time) override { auto seat = waylandServer()->seat(); if (seat->isTouchSequence()) { // something else is getting the events return false; } auto touch = input()->touch(); if (touch->internalPressId() != -1) { // already on internal window, ignore further touch points, but filter out return true; } // a new touch point seat->setTimestamp(time); auto internal = touch->internalWindow(); if (!internal) { return false; } touch->setInternalPressId(id); // Qt's touch event API is rather complex, let's do fake mouse events instead m_lastGlobalTouchPos = pos; m_lastLocalTouchPos = pos - QPointF(internal->x(), internal->y()); QEnterEvent enterEvent(m_lastLocalTouchPos, m_lastLocalTouchPos, pos); QCoreApplication::sendEvent(internal.data(), &enterEvent); QMouseEvent e(QEvent::MouseButtonPress, m_lastLocalTouchPos, pos, Qt::LeftButton, Qt::LeftButton, input()->keyboardModifiers()); e.setAccepted(false); QCoreApplication::sendEvent(internal.data(), &e); return true; } bool touchMotion(qint32 id, const QPointF &pos, quint32 time) override { auto touch = input()->touch(); auto internal = touch->internalWindow(); if (!internal) { return false; } if (touch->internalPressId() == -1) { return false; } waylandServer()->seat()->setTimestamp(time); if (touch->internalPressId() != qint32(id)) { // ignore, but filter out return true; } m_lastGlobalTouchPos = pos; m_lastLocalTouchPos = pos - QPointF(internal->x(), internal->y()); QMouseEvent e(QEvent::MouseMove, m_lastLocalTouchPos, m_lastGlobalTouchPos, Qt::LeftButton, Qt::LeftButton, input()->keyboardModifiers()); QCoreApplication::instance()->sendEvent(internal.data(), &e); return true; } bool touchUp(qint32 id, quint32 time) override { auto touch = input()->touch(); auto internal = touch->internalWindow(); if (!internal) { return false; } if (touch->internalPressId() == -1) { return false; } waylandServer()->seat()->setTimestamp(time); if (touch->internalPressId() != qint32(id)) { // ignore, but filter out return true; } // send mouse up QMouseEvent e(QEvent::MouseButtonRelease, m_lastLocalTouchPos, m_lastGlobalTouchPos, Qt::LeftButton, Qt::MouseButtons(), input()->keyboardModifiers()); e.setAccepted(false); QCoreApplication::sendEvent(internal.data(), &e); QEvent leaveEvent(QEvent::Leave); QCoreApplication::sendEvent(internal.data(), &leaveEvent); m_lastGlobalTouchPos = QPointF(); m_lastLocalTouchPos = QPointF(); input()->touch()->setInternalPressId(-1); return true; } private: QPointF m_lastGlobalTouchPos; QPointF m_lastLocalTouchPos; }; class DecorationEventFilter : public InputEventFilter { public: bool pointerEvent(QMouseEvent *event, quint32 nativeButton) override { Q_UNUSED(nativeButton) auto decoration = input()->pointer()->decoration(); if (!decoration) { return false; } const QPointF p = event->globalPos() - decoration->client()->pos(); switch (event->type()) { case QEvent::MouseMove: { QHoverEvent e(QEvent::HoverMove, p, p); QCoreApplication::instance()->sendEvent(decoration->decoration(), &e); decoration->client()->processDecorationMove(p.toPoint(), event->globalPos()); return true; } case QEvent::MouseButtonPress: case QEvent::MouseButtonRelease: { const auto actionResult = performClientMouseAction(event, decoration->client()); if (actionResult.first) { return actionResult.second; } QMouseEvent e(event->type(), p, event->globalPos(), event->button(), event->buttons(), event->modifiers()); e.setAccepted(false); QCoreApplication::sendEvent(decoration->decoration(), &e); if (!e.isAccepted() && event->type() == QEvent::MouseButtonPress) { decoration->client()->processDecorationButtonPress(&e); } if (event->type() == QEvent::MouseButtonRelease) { decoration->client()->processDecorationButtonRelease(&e); } return true; } default: break; } return false; } bool wheelEvent(QWheelEvent *event) override { auto decoration = input()->pointer()->decoration(); if (!decoration) { return false; } if (event->angleDelta().y() != 0) { // client window action only on vertical scrolling const auto actionResult = performClientWheelAction(event, decoration->client()); if (actionResult.first) { return actionResult.second; } } const QPointF localPos = event->globalPosF() - decoration->client()->pos(); const Qt::Orientation orientation = (event->angleDelta().x() != 0) ? Qt::Horizontal : Qt::Vertical; const int delta = event->angleDelta().x() != 0 ? event->angleDelta().x() : event->angleDelta().y(); QWheelEvent e(localPos, event->globalPosF(), QPoint(), event->angleDelta(), delta, orientation, event->buttons(), event->modifiers()); e.setAccepted(false); QCoreApplication::sendEvent(decoration.data(), &e); if (e.isAccepted()) { return true; } if ((orientation == Qt::Vertical) && decoration->client()->titlebarPositionUnderMouse()) { decoration->client()->performMouseCommand(options->operationTitlebarMouseWheel(delta * -1), event->globalPosF().toPoint()); } return true; } bool touchDown(qint32 id, const QPointF &pos, quint32 time) override { auto seat = waylandServer()->seat(); if (seat->isTouchSequence()) { return false; } if (input()->touch()->decorationPressId() != -1) { // already on a decoration, ignore further touch points, but filter out return true; } seat->setTimestamp(time); auto decoration = input()->touch()->decoration(); if (!decoration) { return false; } input()->touch()->setDecorationPressId(id); m_lastGlobalTouchPos = pos; m_lastLocalTouchPos = pos - decoration->client()->pos(); QHoverEvent hoverEvent(QEvent::HoverMove, m_lastLocalTouchPos, m_lastLocalTouchPos); QCoreApplication::sendEvent(decoration->decoration(), &hoverEvent); QMouseEvent e(QEvent::MouseButtonPress, m_lastLocalTouchPos, pos, Qt::LeftButton, Qt::LeftButton, input()->keyboardModifiers()); e.setAccepted(false); QCoreApplication::sendEvent(decoration->decoration(), &e); if (!e.isAccepted()) { decoration->client()->processDecorationButtonPress(&e); } return true; } bool touchMotion(qint32 id, const QPointF &pos, quint32 time) override { Q_UNUSED(time) auto decoration = input()->touch()->decoration(); if (!decoration) { return false; } if (input()->touch()->decorationPressId() == -1) { return false; } if (input()->touch()->decorationPressId() != qint32(id)) { // ignore, but filter out return true; } m_lastGlobalTouchPos = pos; m_lastLocalTouchPos = pos - decoration->client()->pos(); QHoverEvent e(QEvent::HoverMove, m_lastLocalTouchPos, m_lastLocalTouchPos); QCoreApplication::instance()->sendEvent(decoration->decoration(), &e); decoration->client()->processDecorationMove(m_lastLocalTouchPos.toPoint(), pos.toPoint()); return true; } bool touchUp(qint32 id, quint32 time) override { Q_UNUSED(time); auto decoration = input()->touch()->decoration(); if (!decoration) { return false; } if (input()->touch()->decorationPressId() == -1) { return false; } if (input()->touch()->decorationPressId() != qint32(id)) { // ignore, but filter out return true; } // send mouse up QMouseEvent e(QEvent::MouseButtonRelease, m_lastLocalTouchPos, m_lastGlobalTouchPos, Qt::LeftButton, Qt::MouseButtons(), input()->keyboardModifiers()); e.setAccepted(false); QCoreApplication::sendEvent(decoration->decoration(), &e); decoration->client()->processDecorationButtonRelease(&e); QHoverEvent leaveEvent(QEvent::HoverLeave, QPointF(), QPointF()); QCoreApplication::sendEvent(decoration->decoration(), &leaveEvent); m_lastGlobalTouchPos = QPointF(); m_lastLocalTouchPos = QPointF(); input()->touch()->setDecorationPressId(-1); return true; } private: QPointF m_lastGlobalTouchPos; QPointF m_lastLocalTouchPos; }; #ifdef KWIN_BUILD_TABBOX class TabBoxInputFilter : public InputEventFilter { public: bool pointerEvent(QMouseEvent *event, quint32 button) override { Q_UNUSED(button) if (!TabBox::TabBox::self() || !TabBox::TabBox::self()->isGrabbed()) { return false; } return TabBox::TabBox::self()->handleMouseEvent(event); } bool keyEvent(QKeyEvent *event) override { if (!TabBox::TabBox::self() || !TabBox::TabBox::self()->isGrabbed()) { return false; } auto seat = waylandServer()->seat(); seat->setFocusedKeyboardSurface(nullptr); input()->pointer()->setEnableConstraints(false); // pass the key event to the seat, so that it has a proper model of the currently hold keys // this is important for combinations like alt+shift to ensure that shift is not considered pressed passToWaylandServer(event); if (event->type() == QEvent::KeyPress) { TabBox::TabBox::self()->keyPress(event->modifiers() | event->key()); } else if (static_cast(event)->modifiersRelevantForGlobalShortcuts() == Qt::NoModifier) { TabBox::TabBox::self()->modifiersReleased(); } return true; } bool wheelEvent(QWheelEvent *event) override { if (!TabBox::TabBox::self() || !TabBox::TabBox::self()->isGrabbed()) { return false; } return TabBox::TabBox::self()->handleWheelEvent(event); } }; #endif class ScreenEdgeInputFilter : public InputEventFilter { public: bool pointerEvent(QMouseEvent *event, quint32 nativeButton) override { Q_UNUSED(nativeButton) ScreenEdges::self()->isEntered(event); // always forward return false; } bool touchDown(qint32 id, const QPointF &pos, quint32 time) override { Q_UNUSED(time) // TODO: better check whether a touch sequence is in progress if (m_touchInProgress || waylandServer()->seat()->isTouchSequence()) { // cancel existing touch ScreenEdges::self()->gestureRecognizer()->cancelSwipeGesture(); m_touchInProgress = false; m_id = 0; return false; } if (ScreenEdges::self()->gestureRecognizer()->startSwipeGesture(pos) > 0) { m_touchInProgress = true; m_id = id; m_lastPos = pos; return true; } return false; } bool touchMotion(qint32 id, const QPointF &pos, quint32 time) override { Q_UNUSED(time) if (m_touchInProgress && m_id == id) { ScreenEdges::self()->gestureRecognizer()->updateSwipeGesture(QSizeF(pos.x() - m_lastPos.x(), pos.y() - m_lastPos.y())); m_lastPos = pos; return true; } return false; } bool touchUp(qint32 id, quint32 time) override { Q_UNUSED(time) if (m_touchInProgress && m_id == id) { ScreenEdges::self()->gestureRecognizer()->endSwipeGesture(); m_touchInProgress = false; return true; } return false; } private: bool m_touchInProgress = false; qint32 m_id = 0; QPointF m_lastPos; }; /** * This filter implements window actions. If the event should not be passed to the * current pointer window it will filter out the event */ class WindowActionInputFilter : public InputEventFilter { public: bool pointerEvent(QMouseEvent *event, quint32 nativeButton) override { Q_UNUSED(nativeButton) if (event->type() != QEvent::MouseButtonPress) { return false; } AbstractClient *c = dynamic_cast(input()->pointer()->focus().data()); if (!c) { return false; } const auto actionResult = performClientMouseAction(event, c, MouseAction::ModifierAndWindow); if (actionResult.first) { return actionResult.second; } return false; } bool wheelEvent(QWheelEvent *event) override { if (event->angleDelta().y() == 0) { // only actions on vertical scroll return false; } AbstractClient *c = dynamic_cast(input()->pointer()->focus().data()); if (!c) { return false; } const auto actionResult = performClientWheelAction(event, c, MouseAction::ModifierAndWindow); if (actionResult.first) { return actionResult.second; } return false; } bool touchDown(qint32 id, const QPointF &pos, quint32 time) override { Q_UNUSED(id) Q_UNUSED(time) auto seat = waylandServer()->seat(); if (seat->isTouchSequence()) { return false; } AbstractClient *c = dynamic_cast(input()->touch()->focus().data()); if (!c) { return false; } bool wasAction = false; const Options::MouseCommand command = c->getMouseCommand(Qt::LeftButton, &wasAction); if (wasAction) { return !c->performMouseCommand(command, pos.toPoint()); } return false; } }; /** * The remaining default input filter which forwards events to other windows */ class ForwardInputFilter : public InputEventFilter { public: bool pointerEvent(QMouseEvent *event, quint32 nativeButton) override { auto seat = waylandServer()->seat(); seat->setTimestamp(event->timestamp()); switch (event->type()) { case QEvent::MouseMove: { seat->setPointerPos(event->globalPos()); MouseEvent *e = static_cast(event); if (e->delta() != QSizeF()) { seat->relativePointerMotion(e->delta(), e->deltaUnaccelerated(), e->timestampMicroseconds()); } break; } case QEvent::MouseButtonPress: seat->pointerButtonPressed(nativeButton); break; case QEvent::MouseButtonRelease: seat->pointerButtonReleased(nativeButton); break; default: break; } return true; } bool wheelEvent(QWheelEvent *event) override { auto seat = waylandServer()->seat(); seat->setTimestamp(event->timestamp()); auto _event = static_cast(event); KWayland::Server::PointerAxisSource source; switch (_event->axisSource()) { case KWin::InputRedirection::PointerAxisSourceWheel: source = KWayland::Server::PointerAxisSource::Wheel; break; case KWin::InputRedirection::PointerAxisSourceFinger: source = KWayland::Server::PointerAxisSource::Finger; break; case KWin::InputRedirection::PointerAxisSourceContinuous: source = KWayland::Server::PointerAxisSource::Continuous; break; case KWin::InputRedirection::PointerAxisSourceWheelTilt: source = KWayland::Server::PointerAxisSource::WheelTilt; break; case KWin::InputRedirection::PointerAxisSourceUnknown: default: source = KWayland::Server::PointerAxisSource::Unknown; break; } seat->pointerAxisV5(_event->orientation(), _event->delta(), _event->discreteDelta(), source); return true; } bool keyEvent(QKeyEvent *event) override { if (!workspace()) { return false; } if (event->isAutoRepeat()) { // handled by Wayland client return false; } auto seat = waylandServer()->seat(); input()->keyboard()->update(); seat->setTimestamp(event->timestamp()); passToWaylandServer(event); return true; } bool touchDown(qint32 id, const QPointF &pos, quint32 time) override { if (!workspace()) { return false; } auto seat = waylandServer()->seat(); seat->setTimestamp(time); input()->touch()->insertId(id, seat->touchDown(pos)); return true; } bool touchMotion(qint32 id, const QPointF &pos, quint32 time) override { if (!workspace()) { return false; } auto seat = waylandServer()->seat(); seat->setTimestamp(time); const qint32 kwaylandId = input()->touch()->mappedId(id); if (kwaylandId != -1) { seat->touchMove(kwaylandId, pos); } return true; } bool touchUp(qint32 id, quint32 time) override { if (!workspace()) { return false; } auto seat = waylandServer()->seat(); seat->setTimestamp(time); const qint32 kwaylandId = input()->touch()->mappedId(id); if (kwaylandId != -1) { seat->touchUp(kwaylandId); input()->touch()->removeId(id); } return true; } bool pinchGestureBegin(int fingerCount, quint32 time) override { if (!workspace()) { return false; } auto seat = waylandServer()->seat(); seat->setTimestamp(time); seat->startPointerPinchGesture(fingerCount); return true; } bool pinchGestureUpdate(qreal scale, qreal angleDelta, const QSizeF &delta, quint32 time) override { if (!workspace()) { return false; } auto seat = waylandServer()->seat(); seat->setTimestamp(time); seat->updatePointerPinchGesture(delta, scale, angleDelta); return true; } bool pinchGestureEnd(quint32 time) override { if (!workspace()) { return false; } auto seat = waylandServer()->seat(); seat->setTimestamp(time); seat->endPointerPinchGesture(); return true; } bool pinchGestureCancelled(quint32 time) override { if (!workspace()) { return false; } auto seat = waylandServer()->seat(); seat->setTimestamp(time); seat->cancelPointerPinchGesture(); return true; } bool swipeGestureBegin(int fingerCount, quint32 time) override { if (!workspace()) { return false; } auto seat = waylandServer()->seat(); seat->setTimestamp(time); seat->startPointerSwipeGesture(fingerCount); return true; } bool swipeGestureUpdate(const QSizeF &delta, quint32 time) override { if (!workspace()) { return false; } auto seat = waylandServer()->seat(); seat->setTimestamp(time); seat->updatePointerSwipeGesture(delta); return true; } bool swipeGestureEnd(quint32 time) override { if (!workspace()) { return false; } auto seat = waylandServer()->seat(); seat->setTimestamp(time); seat->endPointerSwipeGesture(); return true; } bool swipeGestureCancelled(quint32 time) override { if (!workspace()) { return false; } auto seat = waylandServer()->seat(); seat->setTimestamp(time); seat->cancelPointerSwipeGesture(); return true; } }; class DragAndDropInputFilter : public InputEventFilter { public: bool pointerEvent(QMouseEvent *event, quint32 nativeButton) override { auto seat = waylandServer()->seat(); if (!seat->isDragPointer()) { return false; } if (seat->isDragTouch()) { return true; } seat->setTimestamp(event->timestamp()); switch (event->type()) { case QEvent::MouseMove: { const auto pos = input()->globalPointer(); seat->setPointerPos(pos); const auto eventPos = event->globalPos(); // TODO: use InputDeviceHandler::at() here and check isClient()? Toplevel *t = input()->findManagedToplevel(eventPos); if (auto *xwl = xwayland()) { const auto ret = xwl->dragMoveFilter(t, eventPos); if (ret == Xwl::DragEventReply::Ignore) { return false; } else if (ret == Xwl::DragEventReply::Take) { break; } } if (t) { // TODO: consider decorations if (t->surface() != seat->dragSurface()) { if (AbstractClient *c = qobject_cast(t)) { workspace()->activateClient(c); } seat->setDragTarget(t->surface(), t->inputTransformation()); } } else { // no window at that place, if we have a surface we need to reset seat->setDragTarget(nullptr); } break; } case QEvent::MouseButtonPress: seat->pointerButtonPressed(nativeButton); break; case QEvent::MouseButtonRelease: seat->pointerButtonReleased(nativeButton); break; default: break; } // TODO: should we pass through effects? return true; } bool touchDown(qint32 id, const QPointF &pos, quint32 time) override { auto seat = waylandServer()->seat(); if (seat->isDragPointer()) { return true; } if (!seat->isDragTouch()) { return false; } if (m_touchId != id) { return true; } seat->setTimestamp(time); input()->touch()->insertId(id, seat->touchDown(pos)); return true; } bool touchMotion(qint32 id, const QPointF &pos, quint32 time) override { auto seat = waylandServer()->seat(); if (seat->isDragPointer()) { return true; } if (!seat->isDragTouch()) { return false; } if (m_touchId < 0) { // We take for now the first id appearing as a move after a drag // started. We can optimize by specifying the id the drag is // associated with by implementing a key-value getter in KWayland. m_touchId = id; } if (m_touchId != id) { return true; } seat->setTimestamp(time); const qint32 kwaylandId = input()->touch()->mappedId(id); if (kwaylandId == -1) { return true; } seat->touchMove(kwaylandId, pos); if (Toplevel *t = input()->findToplevel(pos.toPoint())) { // TODO: consider decorations if (t->surface() != seat->dragSurface()) { if (AbstractClient *c = qobject_cast(t)) { workspace()->activateClient(c); } seat->setDragTarget(t->surface(), pos, t->inputTransformation()); } } else { // no window at that place, if we have a surface we need to reset seat->setDragTarget(nullptr); } return true; } bool touchUp(qint32 id, quint32 time) override { auto seat = waylandServer()->seat(); if (!seat->isDragTouch()) { return false; } seat->setTimestamp(time); const qint32 kwaylandId = input()->touch()->mappedId(id); if (kwaylandId != -1) { seat->touchUp(kwaylandId); input()->touch()->removeId(id); } if (m_touchId == id) { m_touchId = -1; } return true; } private: qint32 m_touchId = -1; }; KWIN_SINGLETON_FACTORY(InputRedirection) static const QString s_touchpadComponent = QStringLiteral("kcm_touchpad"); InputRedirection::InputRedirection(QObject *parent) : QObject(parent) , m_keyboard(new KeyboardInputRedirection(this)) , m_pointer(new PointerInputRedirection(this)) , m_touch(new TouchInputRedirection(this)) , m_shortcuts(new GlobalShortcutsManager(this)) { qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); if (Application::usesLibinput()) { if (LogindIntegration::self()->hasSessionControl()) { setupLibInput(); } else { LibInput::Connection::createThread(); if (LogindIntegration::self()->isConnected()) { LogindIntegration::self()->takeControl(); } else { connect(LogindIntegration::self(), &LogindIntegration::connectedChanged, LogindIntegration::self(), &LogindIntegration::takeControl); } connect(LogindIntegration::self(), &LogindIntegration::hasSessionControlChanged, this, [this] (bool sessionControl) { if (sessionControl) { setupLibInput(); } } ); } } connect(kwinApp(), &Application::workspaceCreated, this, &InputRedirection::setupWorkspace); reconfigure(); } InputRedirection::~InputRedirection() { s_self = nullptr; qDeleteAll(m_filters); qDeleteAll(m_spies); } void InputRedirection::installInputEventFilter(InputEventFilter *filter) { Q_ASSERT(!m_filters.contains(filter)); m_filters << filter; } void InputRedirection::prependInputEventFilter(InputEventFilter *filter) { Q_ASSERT(!m_filters.contains(filter)); m_filters.prepend(filter); } void InputRedirection::uninstallInputEventFilter(InputEventFilter *filter) { m_filters.removeOne(filter); } void InputRedirection::installInputEventSpy(InputEventSpy *spy) { m_spies << spy; } void InputRedirection::uninstallInputEventSpy(InputEventSpy *spy) { m_spies.removeOne(spy); } void InputRedirection::init() { m_shortcuts->init(); } void InputRedirection::setupWorkspace() { if (waylandServer()) { using namespace KWayland::Server; FakeInputInterface *fakeInput = waylandServer()->display()->createFakeInput(this); fakeInput->create(); connect(fakeInput, &FakeInputInterface::deviceCreated, this, [this] (FakeInputDevice *device) { connect(device, &FakeInputDevice::authenticationRequested, this, [this, device] (const QString &application, const QString &reason) { Q_UNUSED(application) Q_UNUSED(reason) // TODO: make secure device->setAuthentication(true); } ); connect(device, &FakeInputDevice::pointerMotionRequested, this, [this] (const QSizeF &delta) { // TODO: Fix time m_pointer->processMotion(globalPointer() + QPointF(delta.width(), delta.height()), 0); waylandServer()->simulateUserActivity(); } ); connect(device, &FakeInputDevice::pointerMotionAbsoluteRequested, this, [this] (const QPointF &pos) { // TODO: Fix time m_pointer->processMotion(pos, 0); waylandServer()->simulateUserActivity(); } ); connect(device, &FakeInputDevice::pointerButtonPressRequested, this, [this] (quint32 button) { // TODO: Fix time m_pointer->processButton(button, InputRedirection::PointerButtonPressed, 0); waylandServer()->simulateUserActivity(); } ); connect(device, &FakeInputDevice::pointerButtonReleaseRequested, this, [this] (quint32 button) { // TODO: Fix time m_pointer->processButton(button, InputRedirection::PointerButtonReleased, 0); waylandServer()->simulateUserActivity(); } ); connect(device, &FakeInputDevice::pointerAxisRequested, this, [this] (Qt::Orientation orientation, qreal delta) { // TODO: Fix time InputRedirection::PointerAxis axis; switch (orientation) { case Qt::Horizontal: axis = InputRedirection::PointerAxisHorizontal; break; case Qt::Vertical: axis = InputRedirection::PointerAxisVertical; break; default: Q_UNREACHABLE(); break; } // TODO: Fix time m_pointer->processAxis(axis, delta, 0, InputRedirection::PointerAxisSourceUnknown, 0); waylandServer()->simulateUserActivity(); } ); connect(device, &FakeInputDevice::touchDownRequested, this, [this] (qint32 id, const QPointF &pos) { // TODO: Fix time m_touch->processDown(id, pos, 0); waylandServer()->simulateUserActivity(); } ); connect(device, &FakeInputDevice::touchMotionRequested, this, [this] (qint32 id, const QPointF &pos) { // TODO: Fix time m_touch->processMotion(id, pos, 0); waylandServer()->simulateUserActivity(); } ); connect(device, &FakeInputDevice::touchUpRequested, this, [this] (qint32 id) { // TODO: Fix time m_touch->processUp(id, 0); waylandServer()->simulateUserActivity(); } ); connect(device, &FakeInputDevice::touchCancelRequested, this, [this] () { m_touch->cancel(); } ); connect(device, &FakeInputDevice::touchFrameRequested, this, [this] () { m_touch->frame(); } ); } ); connect(workspace(), &Workspace::configChanged, this, &InputRedirection::reconfigure); m_keyboard->init(); m_pointer->init(); m_touch->init(); } setupInputFilters(); } void InputRedirection::setupInputFilters() { const bool hasGlobalShortcutSupport = !waylandServer() || waylandServer()->hasGlobalShortcutSupport(); if (LogindIntegration::self()->hasSessionControl() && hasGlobalShortcutSupport) { installInputEventFilter(new VirtualTerminalFilter); } if (waylandServer()) { installInputEventSpy(new TouchHideCursorSpy); if (hasGlobalShortcutSupport) { installInputEventFilter(new TerminateServerFilter); } installInputEventFilter(new DragAndDropInputFilter); installInputEventFilter(new LockScreenFilter); installInputEventFilter(new PopupInputFilter); m_windowSelector = new WindowSelectorFilter; installInputEventFilter(m_windowSelector); } if (hasGlobalShortcutSupport) { installInputEventFilter(new ScreenEdgeInputFilter); } installInputEventFilter(new EffectsFilter); installInputEventFilter(new MoveResizeFilter); #ifdef KWIN_BUILD_TABBOX installInputEventFilter(new TabBoxInputFilter); #endif if (hasGlobalShortcutSupport) { installInputEventFilter(new GlobalShortcutFilter); } installInputEventFilter(new DecorationEventFilter); installInputEventFilter(new InternalWindowEventFilter); if (waylandServer()) { installInputEventFilter(new WindowActionInputFilter); installInputEventFilter(new ForwardInputFilter); } } void InputRedirection::reconfigure() { if (Application::usesLibinput()) { auto inputConfig = kwinApp()->inputConfig(); inputConfig->reparseConfiguration(); const auto config = inputConfig->group(QStringLiteral("Keyboard")); const int delay = config.readEntry("RepeatDelay", 660); const int rate = config.readEntry("RepeatRate", 25); const bool enabled = config.readEntry("KeyboardRepeating", 0) == 0; waylandServer()->seat()->setKeyRepeatInfo(enabled ? rate : 0, delay); } } static KWayland::Server::SeatInterface *findSeat() { auto server = waylandServer(); if (!server) { return nullptr; } return server->seat(); } void InputRedirection::setupLibInput() { if (!Application::usesLibinput()) { return; } if (m_libInput) { return; } LibInput::Connection *conn = LibInput::Connection::create(this); m_libInput = conn; if (conn) { if (waylandServer()) { // create relative pointer manager waylandServer()->display()->createRelativePointerManager(KWayland::Server::RelativePointerInterfaceVersion::UnstableV1, waylandServer()->display())->create(); } conn->setInputConfig(kwinApp()->inputConfig()); conn->updateLEDs(m_keyboard->xkb()->leds()); waylandServer()->updateKeyState(m_keyboard->xkb()->leds()); connect(m_keyboard, &KeyboardInputRedirection::ledsChanged, waylandServer(), &WaylandServer::updateKeyState); connect(m_keyboard, &KeyboardInputRedirection::ledsChanged, conn, &LibInput::Connection::updateLEDs); connect(conn, &LibInput::Connection::eventsRead, this, [this] { m_libInput->processEvents(); }, Qt::QueuedConnection ); conn->setup(); connect(conn, &LibInput::Connection::pointerButtonChanged, m_pointer, &PointerInputRedirection::processButton); connect(conn, &LibInput::Connection::pointerAxisChanged, m_pointer, &PointerInputRedirection::processAxis); connect(conn, &LibInput::Connection::pinchGestureBegin, m_pointer, &PointerInputRedirection::processPinchGestureBegin); connect(conn, &LibInput::Connection::pinchGestureUpdate, m_pointer, &PointerInputRedirection::processPinchGestureUpdate); connect(conn, &LibInput::Connection::pinchGestureEnd, m_pointer, &PointerInputRedirection::processPinchGestureEnd); connect(conn, &LibInput::Connection::pinchGestureCancelled, m_pointer, &PointerInputRedirection::processPinchGestureCancelled); connect(conn, &LibInput::Connection::swipeGestureBegin, m_pointer, &PointerInputRedirection::processSwipeGestureBegin); connect(conn, &LibInput::Connection::swipeGestureUpdate, m_pointer, &PointerInputRedirection::processSwipeGestureUpdate); connect(conn, &LibInput::Connection::swipeGestureEnd, m_pointer, &PointerInputRedirection::processSwipeGestureEnd); connect(conn, &LibInput::Connection::swipeGestureCancelled, m_pointer, &PointerInputRedirection::processSwipeGestureCancelled); connect(conn, &LibInput::Connection::keyChanged, m_keyboard, &KeyboardInputRedirection::processKey); connect(conn, &LibInput::Connection::pointerMotion, this, [this] (const QSizeF &delta, const QSizeF &deltaNonAccel, uint32_t time, quint64 timeMicroseconds, LibInput::Device *device) { m_pointer->processMotion(m_pointer->pos() + QPointF(delta.width(), delta.height()), delta, deltaNonAccel, time, timeMicroseconds, device); } ); connect(conn, &LibInput::Connection::pointerMotionAbsolute, this, [this] (QPointF orig, QPointF screen, uint32_t time, LibInput::Device *device) { Q_UNUSED(orig) m_pointer->processMotion(screen, time, device); } ); connect(conn, &LibInput::Connection::touchDown, m_touch, &TouchInputRedirection::processDown); connect(conn, &LibInput::Connection::touchUp, m_touch, &TouchInputRedirection::processUp); connect(conn, &LibInput::Connection::touchMotion, m_touch, &TouchInputRedirection::processMotion); connect(conn, &LibInput::Connection::touchCanceled, m_touch, &TouchInputRedirection::cancel); connect(conn, &LibInput::Connection::touchFrame, m_touch, &TouchInputRedirection::frame); auto handleSwitchEvent = [this] (SwitchEvent::State state, quint32 time, quint64 timeMicroseconds, LibInput::Device *device) { SwitchEvent event(state, time, timeMicroseconds, device); processSpies(std::bind(&InputEventSpy::switchEvent, std::placeholders::_1, &event)); processFilters(std::bind(&InputEventFilter::switchEvent, std::placeholders::_1, &event)); }; connect(conn, &LibInput::Connection::switchToggledOn, this, std::bind(handleSwitchEvent, SwitchEvent::State::On, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); connect(conn, &LibInput::Connection::switchToggledOff, this, std::bind(handleSwitchEvent, SwitchEvent::State::Off, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); if (screens()) { setupLibInputWithScreens(); } else { connect(kwinApp(), &Application::screensCreated, this, &InputRedirection::setupLibInputWithScreens); } if (auto s = findSeat()) { // Workaround for QTBUG-54371: if there is no real keyboard Qt doesn't request virtual keyboard s->setHasKeyboard(true); s->setHasPointer(conn->hasPointer()); s->setHasTouch(conn->hasTouch()); connect(conn, &LibInput::Connection::hasAlphaNumericKeyboardChanged, this, [this] (bool set) { if (m_libInput->isSuspended()) { return; } // TODO: this should update the seat, only workaround for QTBUG-54371 emit hasAlphaNumericKeyboardChanged(set); } ); connect(conn, &LibInput::Connection::hasTabletModeSwitchChanged, this, [this] (bool set) { if (m_libInput->isSuspended()) { return; } emit hasTabletModeSwitchChanged(set); } ); connect(conn, &LibInput::Connection::hasPointerChanged, this, [this, s] (bool set) { if (m_libInput->isSuspended()) { return; } s->setHasPointer(set); } ); connect(conn, &LibInput::Connection::hasTouchChanged, this, [this, s] (bool set) { if (m_libInput->isSuspended()) { return; } s->setHasTouch(set); } ); } connect(LogindIntegration::self(), &LogindIntegration::sessionActiveChanged, m_libInput, [this] (bool active) { if (!active) { m_libInput->deactivate(); } } ); } setupTouchpadShortcuts(); } void InputRedirection::setupTouchpadShortcuts() { if (!m_libInput) { return; } QAction *touchpadToggleAction = new QAction(this); QAction *touchpadOnAction = new QAction(this); QAction *touchpadOffAction = new QAction(this); touchpadToggleAction->setObjectName(QStringLiteral("Toggle Touchpad")); touchpadToggleAction->setProperty("componentName", s_touchpadComponent); touchpadOnAction->setObjectName(QStringLiteral("Enable Touchpad")); touchpadOnAction->setProperty("componentName", s_touchpadComponent); touchpadOffAction->setObjectName(QStringLiteral("Disable Touchpad")); touchpadOffAction->setProperty("componentName", s_touchpadComponent); KGlobalAccel::self()->setDefaultShortcut(touchpadToggleAction, QList{Qt::Key_TouchpadToggle}); KGlobalAccel::self()->setShortcut(touchpadToggleAction, QList{Qt::Key_TouchpadToggle}); KGlobalAccel::self()->setDefaultShortcut(touchpadOnAction, QList{Qt::Key_TouchpadOn}); KGlobalAccel::self()->setShortcut(touchpadOnAction, QList{Qt::Key_TouchpadOn}); KGlobalAccel::self()->setDefaultShortcut(touchpadOffAction, QList{Qt::Key_TouchpadOff}); KGlobalAccel::self()->setShortcut(touchpadOffAction, QList{Qt::Key_TouchpadOff}); #ifndef KWIN_BUILD_TESTING registerShortcut(Qt::Key_TouchpadToggle, touchpadToggleAction); registerShortcut(Qt::Key_TouchpadOn, touchpadOnAction); registerShortcut(Qt::Key_TouchpadOff, touchpadOffAction); #endif connect(touchpadToggleAction, &QAction::triggered, m_libInput, &LibInput::Connection::toggleTouchpads); connect(touchpadOnAction, &QAction::triggered, m_libInput, &LibInput::Connection::enableTouchpads); connect(touchpadOffAction, &QAction::triggered, m_libInput, &LibInput::Connection::disableTouchpads); } bool InputRedirection::hasAlphaNumericKeyboard() { if (m_libInput) { return m_libInput->hasAlphaNumericKeyboard(); } return true; } bool InputRedirection::hasTabletModeSwitch() { if (m_libInput) { return m_libInput->hasTabletModeSwitch(); } return false; } void InputRedirection::setupLibInputWithScreens() { if (!screens() || !m_libInput) { return; } m_libInput->setScreenSize(screens()->size()); m_libInput->updateScreens(); connect(screens(), &Screens::sizeChanged, this, [this] { m_libInput->setScreenSize(screens()->size()); } ); connect(screens(), &Screens::changed, m_libInput, &LibInput::Connection::updateScreens); } void InputRedirection::processPointerMotion(const QPointF &pos, uint32_t time) { m_pointer->processMotion(pos, time); } void InputRedirection::processPointerButton(uint32_t button, InputRedirection::PointerButtonState state, uint32_t time) { m_pointer->processButton(button, state, time); } void InputRedirection::processPointerAxis(InputRedirection::PointerAxis axis, qreal delta, qint32 discreteDelta, PointerAxisSource source, uint32_t time) { m_pointer->processAxis(axis, delta, discreteDelta, source, time); } void InputRedirection::processKeyboardKey(uint32_t key, InputRedirection::KeyboardKeyState state, uint32_t time) { m_keyboard->processKey(key, state, time); } void InputRedirection::processKeyboardModifiers(uint32_t modsDepressed, uint32_t modsLatched, uint32_t modsLocked, uint32_t group) { m_keyboard->processModifiers(modsDepressed, modsLatched, modsLocked, group); } void InputRedirection::processKeymapChange(int fd, uint32_t size) { m_keyboard->processKeymapChange(fd, size); } void InputRedirection::processTouchDown(qint32 id, const QPointF &pos, quint32 time) { m_touch->processDown(id, pos, time); } void InputRedirection::processTouchUp(qint32 id, quint32 time) { m_touch->processUp(id, time); } void InputRedirection::processTouchMotion(qint32 id, const QPointF &pos, quint32 time) { m_touch->processMotion(id, pos, time); } void InputRedirection::cancelTouch() { m_touch->cancel(); } void InputRedirection::touchFrame() { m_touch->frame(); } Qt::MouseButtons InputRedirection::qtButtonStates() const { return m_pointer->buttons(); } static bool acceptsInput(Toplevel *t, const QPoint &pos) { const QRegion input = t->inputShape(); if (input.isEmpty()) { return true; } return input.translated(t->pos()).contains(pos); } Toplevel *InputRedirection::findToplevel(const QPoint &pos) { if (!Workspace::self()) { return nullptr; } const bool isScreenLocked = waylandServer() && waylandServer()->isScreenLocked(); // TODO: check whether the unmanaged wants input events at all if (!isScreenLocked) { // if an effect overrides the cursor we don't have a window to focus if (effects && static_cast(effects)->isMouseInterception()) { return nullptr; } const UnmanagedList &unmanaged = Workspace::self()->unmanagedList(); foreach (Unmanaged *u, unmanaged) { if (u->geometry().contains(pos) && acceptsInput(u, pos)) { return u; } } } return findManagedToplevel(pos); } Toplevel *InputRedirection::findManagedToplevel(const QPoint &pos) { if (!Workspace::self()) { return nullptr; } const bool isScreenLocked = waylandServer() && waylandServer()->isScreenLocked(); const ToplevelList &stacking = Workspace::self()->stackingOrder(); if (stacking.isEmpty()) { return nullptr; } auto it = stacking.end(); do { --it; Toplevel *t = (*it); if (t->isDeleted()) { // a deleted window doesn't get mouse events continue; } if (AbstractClient *c = dynamic_cast(t)) { if (!c->isOnCurrentActivity() || !c->isOnCurrentDesktop() || c->isMinimized() || c->isHiddenInternal()) { continue; } } if (!t->readyForPainting()) { continue; } if (isScreenLocked) { if (!t->isLockScreen() && !t->isInputMethod()) { continue; } } if (t->inputGeometry().contains(pos) && acceptsInput(t, pos)) { return t; } } while (it != stacking.begin()); return nullptr; } Qt::KeyboardModifiers InputRedirection::keyboardModifiers() const { return m_keyboard->modifiers(); } Qt::KeyboardModifiers InputRedirection::modifiersRelevantForGlobalShortcuts() const { return m_keyboard->modifiersRelevantForGlobalShortcuts(); } void InputRedirection::registerShortcut(const QKeySequence &shortcut, QAction *action) { Q_UNUSED(shortcut) kwinApp()->platform()->setupActionForGlobalAccel(action); } void InputRedirection::registerPointerShortcut(Qt::KeyboardModifiers modifiers, Qt::MouseButton pointerButtons, QAction *action) { m_shortcuts->registerPointerShortcut(action, modifiers, pointerButtons); } void InputRedirection::registerAxisShortcut(Qt::KeyboardModifiers modifiers, PointerAxisDirection axis, QAction *action) { m_shortcuts->registerAxisShortcut(action, modifiers, axis); } void InputRedirection::registerTouchpadSwipeShortcut(SwipeDirection direction, QAction *action) { m_shortcuts->registerTouchpadSwipe(action, direction); } void InputRedirection::registerGlobalAccel(KGlobalAccelInterface *interface) { m_shortcuts->setKGlobalAccelInterface(interface); } void InputRedirection::warpPointer(const QPointF &pos) { m_pointer->warp(pos); } bool InputRedirection::supportsPointerWarping() const { return m_pointer->supportsWarping(); } QPointF InputRedirection::globalPointer() const { return m_pointer->pos(); } void InputRedirection::startInteractiveWindowSelection(std::function callback, const QByteArray &cursorName) { if (!m_windowSelector || m_windowSelector->isActive()) { callback(nullptr); return; } m_windowSelector->start(callback); m_pointer->setWindowSelectionCursor(cursorName); } void InputRedirection::startInteractivePositionSelection(std::function callback) { if (!m_windowSelector || m_windowSelector->isActive()) { callback(QPoint(-1, -1)); return; } m_windowSelector->start(callback); m_pointer->setWindowSelectionCursor(QByteArray()); } bool InputRedirection::isSelectingWindow() const { return m_windowSelector ? m_windowSelector->isActive() : false; } InputDeviceHandler::InputDeviceHandler(InputRedirection *input) : QObject(input) { } InputDeviceHandler::~InputDeviceHandler() = default; void InputDeviceHandler::init() { connect(workspace(), &Workspace::stackingOrderChanged, this, &InputDeviceHandler::update); connect(workspace(), &Workspace::clientMinimizedChanged, this, &InputDeviceHandler::update); connect(VirtualDesktopManager::self(), &VirtualDesktopManager::currentChanged, this, &InputDeviceHandler::update); } bool InputDeviceHandler::setAt(Toplevel *toplevel) { if (m_at.at == toplevel) { return false; } auto old = m_at.at; disconnect(m_at.surfaceCreatedConnection); m_at.surfaceCreatedConnection = QMetaObject::Connection(); m_at.at = toplevel; emit atChanged(old, toplevel); return true; } void InputDeviceHandler::setFocus(Toplevel *toplevel) { m_focus.focus = toplevel; //TODO: call focusUpdate? } void InputDeviceHandler::setDecoration(QPointer decoration) { auto oldDeco = m_focus.decoration; m_focus.decoration = decoration; cleanupDecoration(oldDeco.data(), m_focus.decoration.data()); emit decorationChanged(); } void InputDeviceHandler::setInternalWindow(QWindow *window) { m_focus.internalWindow = window; //TODO: call internalWindowUpdate? } void InputDeviceHandler::updateFocus() { auto oldFocus = m_focus.focus; if (m_at.at && !m_at.at->surface()) { // The surface has not yet been created (special XWayland case). // Therefore listen for its creation. if (!m_at.surfaceCreatedConnection) { m_at.surfaceCreatedConnection = connect(m_at.at, &Toplevel::surfaceChanged, this, &InputDeviceHandler::update); } m_focus.focus = nullptr; } else { m_focus.focus = m_at.at; } focusUpdate(oldFocus, m_focus.focus); } bool InputDeviceHandler::updateDecoration() { const auto oldDeco = m_focus.decoration; m_focus.decoration = nullptr; auto *ac = qobject_cast(m_at.at); if (ac && ac->decoratedClient()) { const QRect clientRect = QRect(ac->clientPos(), ac->clientSize()).translated(ac->pos()); if (!clientRect.contains(position().toPoint())) { // input device above decoration m_focus.decoration = ac->decoratedClient(); } } if (m_focus.decoration == oldDeco) { // no change to decoration return false; } cleanupDecoration(oldDeco.data(), m_focus.decoration.data()); emit decorationChanged(); return true; } void InputDeviceHandler::updateInternalWindow(QWindow *window) { if (m_focus.internalWindow == window) { // no change return; } const auto oldInternal = m_focus.internalWindow; m_focus.internalWindow = window; cleanupInternalWindow(oldInternal, window); } void InputDeviceHandler::update() { if (!m_inited) { return; } Toplevel *toplevel = nullptr; QWindow *internalWindow = nullptr; if (!positionValid()) { const auto pos = position().toPoint(); internalWindow = findInternalWindow(pos); if (internalWindow) { - toplevel = waylandServer()->findClient(internalWindow); + toplevel = workspace()->findInternal(internalWindow); } else { toplevel = input()->findToplevel(pos); } } // Always set the toplevel at the position of the input device. setAt(toplevel); if (focusUpdatesBlocked()) { return; } if (internalWindow) { if (m_focus.internalWindow != internalWindow) { // changed internal window updateDecoration(); updateInternalWindow(internalWindow); updateFocus(); } else if (updateDecoration()) { // went onto or off from decoration, update focus updateFocus(); } return; } updateInternalWindow(nullptr); if (m_focus.focus != m_at.at) { // focus change updateDecoration(); updateFocus(); return; } // check if switched to/from decoration while staying on the same Toplevel if (updateDecoration()) { // went onto or off from decoration, update focus updateFocus(); } } QWindow* InputDeviceHandler::findInternalWindow(const QPoint &pos) const { if (waylandServer()->isScreenLocked()) { return nullptr; } - const auto &internalClients = waylandServer()->internalClients(); + const QList &internalClients = workspace()->internalClients(); if (internalClients.isEmpty()) { return nullptr; } auto it = internalClients.end(); do { --it; QWindow *w = (*it)->internalWindow(); if (!w || !w->isVisible()) { continue; } if (!(*it)->geometry().contains(pos)) { continue; } // check input mask const QRegion mask = w->mask().translated(w->geometry().topLeft()); if (!mask.isEmpty() && !mask.contains(pos)) { continue; } if (w->property("outputOnly").toBool()) { continue; } return w; } while (it != internalClients.begin()); return nullptr; } } // namespace diff --git a/internal_client.cpp b/internal_client.cpp index 7c7b6d774..9811a52b3 100644 --- a/internal_client.cpp +++ b/internal_client.cpp @@ -1,336 +1,685 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2019 Martin Flöser +Copyright (C) 2019 Vlad Zagorodniy 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 . *********************************************************************/ #include "internal_client.h" +#include "decorations/decorationbridge.h" +#include "deleted.h" #include "workspace.h" -#include -#include +#include #include +#include Q_DECLARE_METATYPE(NET::WindowType) static const QByteArray s_skipClosePropertyName = QByteArrayLiteral("KWIN_SKIP_CLOSE_ANIMATION"); namespace KWin { -InternalClient::InternalClient(KWayland::Server::ShellSurfaceInterface *surface) - : ShellClient(surface) +InternalClient::InternalClient(QWindow *window) + : m_internalWindow(window) + , m_clientSize(window->size()) + , m_windowId(window->winId()) + , m_internalWindowFlags(window->flags()) { - findInternalWindow(); - updateInternalWindowGeometry(); - updateDecoration(true); -} + // Don't render the client until it provides a buffer. + ready_for_painting = false; -InternalClient::InternalClient(KWayland::Server::XdgShellSurfaceInterface *surface) - : ShellClient(surface) -{ -} + 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); -InternalClient::InternalClient(KWayland::Server::XdgShellPopupInterface *surface) - : ShellClient(surface) -{ -} + connect(this, &InternalClient::opacityChanged, this, &InternalClient::addRepaintFull); -InternalClient::~InternalClient() = default; + const QVariant windowType = m_internalWindow->property("kwin_windowType"); + if (!windowType.isNull()) { + m_windowType = windowType.value(); + } -void InternalClient::findInternalWindow() -{ - const QWindowList windows = kwinApp()->topLevelWindows(); - for (QWindow *w: windows) { - auto s = KWayland::Client::Surface::fromWindow(w); - if (!s) { - continue; - } - if (s->id() != surface()->id()) { - continue; - } - m_internalWindow = w; - m_windowId = m_internalWindow->winId(); - m_internalWindowFlags = m_internalWindow->flags(); - connect(m_internalWindow, &QWindow::xChanged, this, &InternalClient::updateInternalWindowGeometry); - connect(m_internalWindow, &QWindow::yChanged, this, &InternalClient::updateInternalWindowGeometry); - connect(m_internalWindow, &QWindow::destroyed, this, [this] { m_internalWindow = nullptr; }); - connect(m_internalWindow, &QWindow::opacityChanged, this, &InternalClient::setOpacity); - - const QVariant windowType = m_internalWindow->property("kwin_windowType"); - if (!windowType.isNull()) { - m_windowType = windowType.value(); - } - setOpacity(m_internalWindow->opacity()); + setCaption(m_internalWindow->title()); + setIcon(QIcon::fromTheme(QStringLiteral("kwin"))); + setOnAllDesktops(true); + setOpacity(m_internalWindow->opacity()); + setSkipCloseAnimation(m_internalWindow->property(s_skipClosePropertyName).toBool()); - // skip close animation support - setSkipCloseAnimation(m_internalWindow->property(s_skipClosePropertyName).toBool()); - m_internalWindow->installEventFilter(this); - return; - } + setupCompositing(); + updateColorScheme(); - qCWarning(KWIN_CORE, "Couldn't find an internal window for surface with id %x", surface()->id()); + 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(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(); 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 + // We don't kill our internal windows. } bool InternalClient::isPopupWindow() const { - if (Toplevel::isPopupWindow()) { + if (AbstractClient::isPopupWindow()) { return true; } return m_internalWindowFlags.testFlag(Qt::Popup); } -void InternalClient::setInternalFramebufferObject(const QSharedPointer &fbo) +QByteArray InternalClient::windowRole() const { - if (fbo.isNull()) { - unmap(); - return; - } - - setClientSize(fbo->size() / surface()->scale()); - markAsMapped(); - doSetGeometry(QRect(geom.topLeft(), clientSize())); - Toplevel::setInternalFramebufferObject(fbo); - Toplevel::addDamage(QRegion(0, 0, width(), height())); + 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_internalWindowFlags.testFlag(Qt::FramelessWindowHint) || m_internalWindowFlags.testFlag(Qt::Popup); + 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::acceptsFocus() 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; } -void InternalClient::updateInternalWindowGeometry() +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; } - doSetGeometry(QRect(m_internalWindow->geometry().topLeft() - QPoint(borderLeft(), borderTop()), - m_internalWindow->geometry().size() + QSize(borderLeft() + borderRight(), borderTop() + borderBottom()))); + 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)); } -bool InternalClient::requestGeometry(const QRect &rect) +void InternalClient::setGeometry(int x, int y, int w, int h, ForceGeometry_t force) { - if (!ShellClient::requestGeometry(rect)) { - return false; + 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 (m_internalWindow) { - m_internalWindow->setGeometry(QRect(rect.topLeft() + QPoint(borderLeft(), borderTop()), rect.size() - QSize(borderLeft() + borderRight(), borderTop() + borderBottom()))); + + 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); } - return true; } -void InternalClient::doSetGeometry(const QRect &rect) +void InternalClient::setGeometryRestore(const QRect &rect) { - if (geom == rect && pendingGeometryUpdate() == PendingGeometryNone) { + 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 (!isUnmapped()) { - addWorkspaceRepaint(visibleRect()); + if (m_userNoBorder == set) { + return; } - geom = rect; + m_userNoBorder = set; + updateDecoration(true); +} - if (isUnmapped() && geometryRestore().isEmpty() && !geom.isEmpty()) { - // use first valid geometry as restore geometry - setGeometryRestore(geom); +void InternalClient::updateDecoration(bool check_workspace_pos, bool force) +{ + if (!force && isDecorated() == !noBorder()) { + return; } - if (!isUnmapped()) { - addWorkspaceRepaint(visibleRect()); + const QRect oldFrameGeometry = geometry(); + const QRect oldClientGeometry = oldFrameGeometry - frameMargins(); + + GeometryUpdatesBlocker blocker(this); + + if (force) { + destroyDecoration(); } - syncGeometryToInternalWindow(); - if (hasStrut()) { - workspace()->updateClientArea(); + + if (!noBorder()) { + createDecoration(oldClientGeometry); + } else { + destroyDecoration(); } - const auto old = geometryBeforeUpdateBlocking(); - updateGeometryBeforeUpdateBlocking(); - emit geometryShapeChanged(this, old); - if (isResize()) { - performMoveResize(); + 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 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(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::syncGeometryToInternalWindow() +void InternalClient::doResizeSync() { - if (!m_internalWindow) { - return; + 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()); } - const QRect windowRect = QRect(geom.topLeft() + QPoint(borderLeft(), borderTop()), - geom.size() - QSize(borderLeft() + borderRight(), borderTop() + borderBottom())); - if (m_internalWindow->geometry() != windowRect) { - // delay to end of cycle to prevent freeze, see BUG 384441 - QTimer::singleShot(0, m_internalWindow, std::bind(static_cast(&QWindow::setGeometry), m_internalWindow, windowRect)); + if (m_captionSuffix != oldSuffix) { + emit captionChanged(); } } -void InternalClient::resizeWithChecks(int w, int h, ForceGeometry_t force) +QRect InternalClient::mapFromClient(const QRect &rect) const { - Q_UNUSED(force) - if (!m_internalWindow) { - return; + 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); + } + ); } - QRect area = workspace()->clientArea(WorkArea, this); - // don't allow growing larger than workarea - if (w > area.width()) { - w = area.width(); + + 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)); } - if (h > area.height()) { - h = area.height(); +} + +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(); } - m_internalWindow->setGeometry(QRect(pos() + QPoint(borderLeft(), borderTop()), QSize(w, h) - QSize(borderLeft() + borderRight(), borderTop() + borderBottom()))); } -void InternalClient::doResizeSync() +void InternalClient::setCaption(const QString &caption) { - if (!m_internalWindow) { + if (m_captionNormal == caption) { return; } - const auto rect = moveResizeGeometry(); - m_internalWindow->setGeometry(QRect(rect.topLeft() + QPoint(borderLeft(), borderTop()), rect.size() - QSize(borderLeft() + borderRight(), borderTop() + borderBottom()))); + + m_captionNormal = caption; + + const QString oldCaptionSuffix = m_captionSuffix; + updateCaption(); + + if (m_captionSuffix == oldCaptionSuffix) { + emit captionChanged(); + } } -QWindow *InternalClient::internalWindow() const +void InternalClient::markAsMapped() { - return m_internalWindow; + if (!ready_for_painting) { + setReadyForPainting(); + workspace()->addInternalClient(this); + } } -bool InternalClient::supportsWindowRules() const +void InternalClient::syncGeometryToInternalWindow() { - return false; + 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())); } } diff --git a/internal_client.h b/internal_client.h index d424ec972..810f862a7 100644 --- a/internal_client.h +++ b/internal_client.h @@ -1,83 +1,130 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2019 Martin Flöser +Copyright (C) 2019 Vlad Zagorodniy 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 . *********************************************************************/ #pragma once -#include "shell_client.h" - +#include "abstract_client.h" namespace KWin { -class KWIN_EXPORT InternalClient : public ShellClient +class KWIN_EXPORT InternalClient : public AbstractClient { Q_OBJECT + public: - InternalClient(KWayland::Server::ShellSurfaceInterface *surface); - // needed for template void WaylandServer::createSurface(T *surface) - InternalClient(KWayland::Server::XdgShellSurfaceInterface *surface); - // needed for template void WaylandServer::createSurface(T *surface) - InternalClient(KWayland::Server::XdgShellPopupInterface *surface); + explicit InternalClient(QWindow *window); ~InternalClient() override; bool eventFilter(QObject *watched, QEvent *event) override; + QStringList activities() const override; + void blockActivityUpdates(bool b = true) override; + qreal bufferScale() const override; + QString captionNormal() const override; + QString captionSuffix() const override; + QPoint clientContentPos() const override; + QSize clientSize() const override; + void debug(QDebug &stream) const override; + QRect transparentRect() const override; NET::WindowType windowType(bool direct = false, int supported_types = 0) const override; + double opacity() const override; + void setOpacity(double opacity) override; void killWindow() override; bool isPopupWindow() const override; - void setInternalFramebufferObject(const QSharedPointer &fbo) override; + QByteArray windowRole() const override; void closeWindow() override; bool isCloseable() const override; + bool isFullScreenable() const override; + bool isFullScreen() const override; bool isMaximizable() const override; bool isMinimizable() const override; bool isMovable() const override; bool isMovableAcrossScreens() const override; bool isResizable() const override; bool noBorder() const override; bool userCanSetNoBorder() const override; bool wantsInput() const override; bool isInternal() const override; bool isLockScreen() const override; bool isInputMethod() const override; bool isOutline() const override; quint32 windowId() const override; + MaximizeMode maximizeMode() const override; + QRect geometryRestore() const override; + bool isShown(bool shaded_is_shown) const override; + bool isHiddenInternal() const override; + void hideClient(bool hide) override; using AbstractClient::resizeWithChecks; void resizeWithChecks(int w, int h, ForceGeometry_t force = NormalGeometrySet) override; - QWindow *internalWindow() const override; + using AbstractClient::setGeometry; + void setGeometry(int x, int y, int w, int h, ForceGeometry_t force = NormalGeometrySet) override; + void setGeometryRestore(const QRect &rect) override; bool supportsWindowRules() const override; + AbstractClient *findModal(bool allow_itself = false) override; + void setOnAllActivities(bool set) override; + void takeFocus() override; + bool userCanSetFullScreen() const override; + void setFullScreen(bool set, bool user = true) override; + void setNoBorder(bool set) override; + void updateDecoration(bool check_workspace_pos, bool force = false) override; + void updateColorScheme() override; + void showOnScreenEdge() override; + + void destroyClient(); + void present(const QSharedPointer fbo); + void present(const QImage &image, const QRegion &damage); + QWindow *internalWindow() const; protected: bool acceptsFocus() const override; + bool belongsToSameApplication(const AbstractClient *other, SameApplicationChecks checks) const override; + void changeMaximize(bool horizontal, bool vertical, bool adjust) override; + void destroyDecoration() override; void doMove(int x, int y) override; void doResizeSync() override; - bool requestGeometry(const QRect &rect) override; - void doSetGeometry(const QRect &rect) override; + void updateCaption() override; private: - void findInternalWindow(); - void updateInternalWindowGeometry(); + QRect mapFromClient(const QRect &rect) const; + QRect mapToClient(const QRect &rect) const; + void createDecoration(const QRect &rect); + void requestGeometry(const QRect &rect); + void commitGeometry(const QRect &rect); + void setCaption(const QString &caption); + void markAsMapped(); void syncGeometryToInternalWindow(); + void updateInternalWindowGeometry(); + QWindow *m_internalWindow = nullptr; + QRect m_maximizeRestoreGeometry; + QSize m_clientSize = QSize(0, 0); + QString m_captionNormal; + QString m_captionSuffix; + double m_opacity = 1.0; NET::WindowType m_windowType = NET::Normal; quint32 m_windowId = 0; - QWindow *m_internalWindow = nullptr; Qt::WindowFlags m_internalWindowFlags = Qt::WindowFlags(); + bool m_userNoBorder = false; + + Q_DISABLE_COPY(InternalClient) }; } diff --git a/layers.cpp b/layers.cpp index e871819bc..ee9219e08 100644 --- a/layers.cpp +++ b/layers.cpp @@ -1,871 +1,871 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 1999, 2000 Matthias Ettrich Copyright (C) 2003 Lubos Lunak 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 . *********************************************************************/ // SELI zmenit doc /* This file contains things relevant to stacking order and layers. Design: Normal unconstrained stacking order, as requested by the user (by clicking on windows to raise them, etc.), is in Workspace::unconstrained_stacking_order. That list shouldn't be used at all, except for building Workspace::stacking_order. The building is done in Workspace::constrainedStackingOrder(). Only Workspace::stackingOrder() should be used to get the stacking order, because it also checks the stacking order is up to date. All clients are also stored in Workspace::clients (except for isDesktop() clients, as those are very special, and are stored in Workspace::desktops), in the order the clients were created. Every window has one layer assigned in which it is. There are 7 layers, from bottom : DesktopLayer, BelowLayer, NormalLayer, DockLayer, AboveLayer, NotificationLayer, ActiveLayer, CriticalNotificationLayer, and OnScreenDisplayLayer (see also NETWM sect.7.10.). The layer a window is in depends on the window type, and on other things like whether the window is active. We extend the layers provided in NETWM by the NotificationLayer, OnScreenDisplayLayer, and CriticalNotificationLayer. The NoficationLayer contains notification windows which are kept above all windows except the active fullscreen window. The CriticalNotificationLayer contains notification windows which are important enough to keep them even above fullscreen windows. The OnScreenDisplayLayer is used for eg. volume and brightness change feedback and is kept above all windows since it provides immediate response to a user action. NET::Splash clients belong to the Normal layer. NET::TopMenu clients belong to Dock layer. Clients that are both NET::Dock and NET::KeepBelow are in the Normal layer in order to keep the 'allow window to cover the panel' Kicker setting to work as intended (this may look like a slight spec violation, but a) I have no better idea, b) the spec allows adjusting the stacking order if the WM thinks it's a good idea . We put all NET::KeepAbove above all Docks too, even though the spec suggests putting them in the same layer. Most transients are in the same layer as their mainwindow, see Workspace::constrainedStackingOrder(), they may also be in higher layers, but they should never be below their mainwindow. When some client attribute changes (above/below flag, transiency...), Workspace::updateClientLayer() should be called in order to make sure it's moved to the appropriate layer ClientList if needed. Currently the things that affect client in which layer a client belongs: KeepAbove/Keep Below flags, window type, fullscreen state and whether the client is active, mainclient (transiency). Make sure updateStackingOrder() is called in order to make Workspace::stackingOrder() up to date and propagated to the world. Using Workspace::blockStackingUpdates() (or the StackingUpdatesBlocker helper class) it's possible to temporarily disable updates and the stacking order will be updated once after it's allowed again. */ #include "utils.h" #include "client.h" #include "focuschain.h" #include "netinfo.h" #include "workspace.h" #include "tabbox.h" #include "group.h" #include "rules.h" #include "screens.h" #include "unmanaged.h" #include "deleted.h" #include "effects.h" #include "composite.h" #include "screenedge.h" #include "shell_client.h" #include "wayland_server.h" +#include "internal_client.h" #include namespace KWin { //******************************* // Workspace //******************************* void Workspace::updateClientLayer(AbstractClient* c) { if (c) c->updateLayer(); } void Workspace::updateStackingOrder(bool propagate_new_clients) { if (block_stacking_updates > 0) { if (propagate_new_clients) blocked_propagating_new_clients = true; return; } ToplevelList new_stacking_order = constrainedStackingOrder(); bool changed = (force_restacking || new_stacking_order != stacking_order); force_restacking = false; stacking_order = new_stacking_order; if (changed || propagate_new_clients) { propagateClients(propagate_new_clients); emit stackingOrderChanged(); if (m_compositor) { m_compositor->addRepaintFull(); } if (active_client) active_client->updateMouseGrab(); } } /** * Some fullscreen effects have to raise the screenedge on top of an input window, thus all windows * this function puts them back where they belong for regular use and is some cheap variant of * the regular propagateClients function in that it completely ignores managed clients and everything * else and also does not update the NETWM property. * Called from Effects::destroyInputWindow so far. */ void Workspace::stackScreenEdgesUnderOverrideRedirect() { if (!rootInfo()) { return; } Xcb::restackWindows(QVector() << rootInfo()->supportWindow() << ScreenEdges::self()->windows()); } /** * Propagates the managed clients to the world. * Called ONLY from updateStackingOrder(). */ void Workspace::propagateClients(bool propagate_new_clients) { if (!rootInfo()) { return; } // restack the windows according to the stacking order // supportWindow > electric borders > clients > hidden clients QVector newWindowStack; // Stack all windows under the support window. The support window is // not used for anything (besides the NETWM property), and it's not shown, // but it was lowered after kwin startup. Stacking all clients below // it ensures that no client will be ever shown above override-redirect // windows (e.g. popups). newWindowStack << rootInfo()->supportWindow(); newWindowStack << ScreenEdges::self()->windows(); newWindowStack << manual_overlays; newWindowStack.reserve(newWindowStack.size() + 2*stacking_order.size()); // *2 for inputWindow for (int i = stacking_order.size() - 1; i >= 0; --i) { Client *client = qobject_cast(stacking_order.at(i)); if (!client || client->hiddenPreview()) { continue; } if (client->inputId()) // Stack the input window above the frame newWindowStack << client->inputId(); newWindowStack << client->frameId(); } // when having hidden previews, stack hidden windows below everything else // (as far as pure X stacking order is concerned), in order to avoid having // these windows that should be unmapped to interfere with other windows for (int i = stacking_order.size() - 1; i >= 0; --i) { Client *client = qobject_cast(stacking_order.at(i)); if (!client || !client->hiddenPreview()) continue; newWindowStack << client->frameId(); } // TODO isn't it too inefficient to restack always all clients? // TODO don't restack not visible windows? Q_ASSERT(newWindowStack.at(0) == rootInfo()->supportWindow()); Xcb::restackWindows(newWindowStack); int pos = 0; xcb_window_t *cl(nullptr); if (propagate_new_clients) { cl = new xcb_window_t[ manual_overlays.count() + desktops.count() + clients.count()]; for (const auto win : manual_overlays) { cl[pos++] = win; } // TODO this is still not completely in the map order for (ClientList::ConstIterator it = desktops.constBegin(); it != desktops.constEnd(); ++it) cl[pos++] = (*it)->window(); for (ClientList::ConstIterator it = clients.constBegin(); it != clients.constEnd(); ++it) cl[pos++] = (*it)->window(); rootInfo()->setClientList(cl, pos); delete [] cl; } cl = new xcb_window_t[ manual_overlays.count() + stacking_order.count()]; pos = 0; for (ToplevelList::ConstIterator it = stacking_order.constBegin(); it != stacking_order.constEnd(); ++it) { if ((*it)->isClient()) cl[pos++] = (*it)->window(); } for (const auto win : manual_overlays) { cl[pos++] = win; } rootInfo()->setClientListStacking(cl, pos); delete [] cl; // Make the cached stacking order invalid here, in case we need the new stacking order before we get // the matching event, due to X being asynchronous. markXStackingOrderAsDirty(); } /** * Returns topmost visible client. Windows on the dock, the desktop * or of any other special kind are excluded. Also if the window * doesn't accept focus it's excluded. */ // TODO misleading name for this method, too many slightly different ways to use it AbstractClient* Workspace::topClientOnDesktop(int desktop, int screen, bool unconstrained, bool only_normal) const { // TODO Q_ASSERT( block_stacking_updates == 0 ); ToplevelList list; if (!unconstrained) list = stacking_order; else list = unconstrained_stacking_order; for (int i = list.size() - 1; i >= 0; --i) { AbstractClient *c = qobject_cast(list.at(i)); if (!c) { continue; } if (c->isOnDesktop(desktop) && c->isShown(false) && c->isOnCurrentActivity()) { if (screen != -1 && c->screen() != screen) continue; if (!only_normal) return c; if (c->wantsTabFocus() && !c->isSpecialWindow()) return c; } } return nullptr; } AbstractClient* Workspace::findDesktop(bool topmost, int desktop) const { // TODO Q_ASSERT( block_stacking_updates == 0 ); if (topmost) { for (int i = stacking_order.size() - 1; i >= 0; i--) { AbstractClient *c = qobject_cast(stacking_order.at(i)); if (c && c->isOnDesktop(desktop) && c->isDesktop() && c->isShown(true)) return c; } } else { // bottom-most foreach (Toplevel * c, stacking_order) { AbstractClient *client = qobject_cast(c); if (client && c->isOnDesktop(desktop) && c->isDesktop() && client->isShown(true)) return client; } } return nullptr; } void Workspace::raiseOrLowerClient(AbstractClient *c) { if (!c) return; AbstractClient* topmost = nullptr; // TODO Q_ASSERT( block_stacking_updates == 0 ); if (most_recently_raised && stacking_order.contains(most_recently_raised) && most_recently_raised->isShown(true) && c->isOnCurrentDesktop()) topmost = most_recently_raised; else topmost = topClientOnDesktop(c->isOnAllDesktops() ? VirtualDesktopManager::self()->current() : c->desktop(), options->isSeparateScreenFocus() ? c->screen() : -1); if (c == topmost) lowerClient(c); else raiseClient(c); } void Workspace::lowerClient(AbstractClient* c, bool nogroup) { if (!c) return; c->cancelAutoRaise(); StackingUpdatesBlocker blocker(this); unconstrained_stacking_order.removeAll(c); unconstrained_stacking_order.prepend(c); if (!nogroup && c->isTransient()) { // lower also all windows in the group, in their reversed stacking order ClientList wins; if (auto group = c->group()) { wins = ensureStackingOrder(group->members()); } for (int i = wins.size() - 1; i >= 0; --i) { if (wins[ i ] != c) lowerClient(wins[ i ], true); } } if (c == most_recently_raised) most_recently_raised = nullptr; } void Workspace::lowerClientWithinApplication(AbstractClient* c) { if (!c) return; c->cancelAutoRaise(); StackingUpdatesBlocker blocker(this); unconstrained_stacking_order.removeAll(c); bool lowered = false; // first try to put it below the bottom-most window of the application for (ToplevelList::Iterator it = unconstrained_stacking_order.begin(); it != unconstrained_stacking_order.end(); ++it) { AbstractClient *client = qobject_cast(*it); if (!client) { continue; } if (AbstractClient::belongToSameApplication(client, c)) { unconstrained_stacking_order.insert(it, c); lowered = true; break; } } if (!lowered) unconstrained_stacking_order.prepend(c); // ignore mainwindows } void Workspace::raiseClient(AbstractClient* c, bool nogroup) { if (!c) return; c->cancelAutoRaise(); StackingUpdatesBlocker blocker(this); if (!nogroup && c->isTransient()) { QList transients; AbstractClient *transient_parent = c; while ((transient_parent = transient_parent->transientFor())) transients << transient_parent; foreach (transient_parent, transients) raiseClient(transient_parent, true); } unconstrained_stacking_order.removeAll(c); unconstrained_stacking_order.append(c); if (!c->isSpecialWindow()) { most_recently_raised = c; } } void Workspace::raiseClientWithinApplication(AbstractClient* c) { if (!c) return; c->cancelAutoRaise(); StackingUpdatesBlocker blocker(this); // ignore mainwindows // first try to put it above the top-most window of the application for (int i = unconstrained_stacking_order.size() - 1; i > -1 ; --i) { AbstractClient *other = qobject_cast(unconstrained_stacking_order.at(i)); if (!other) { continue; } if (other == c) // don't lower it just because it asked to be raised return; if (AbstractClient::belongToSameApplication(other, c)) { unconstrained_stacking_order.removeAll(c); unconstrained_stacking_order.insert(unconstrained_stacking_order.indexOf(other) + 1, c); // insert after the found one break; } } } void Workspace::raiseClientRequest(KWin::AbstractClient *c, NET::RequestSource src, xcb_timestamp_t timestamp) { if (src == NET::FromTool || allowFullClientRaising(c, timestamp)) raiseClient(c); else { raiseClientWithinApplication(c); c->demandAttention(); } } void Workspace::lowerClientRequest(KWin::Client *c, NET::RequestSource src, xcb_timestamp_t /*timestamp*/) { // If the client has support for all this focus stealing prevention stuff, // do only lowering within the application, as that's the more logical // variant of lowering when application requests it. // No demanding of attention here of course. if (src == NET::FromTool || !c->hasUserTimeSupport()) lowerClient(c); else lowerClientWithinApplication(c); } void Workspace::lowerClientRequest(KWin::AbstractClient *c) { lowerClientWithinApplication(c); } void Workspace::restack(AbstractClient* c, AbstractClient* under, bool force) { Q_ASSERT(unconstrained_stacking_order.contains(under)); if (!force && !AbstractClient::belongToSameApplication(under, c)) { // put in the stacking order below _all_ windows belonging to the active application for (int i = 0; i < unconstrained_stacking_order.size(); ++i) { AbstractClient *other = qobject_cast(unconstrained_stacking_order.at(i)); if (other && other->layer() == c->layer() && AbstractClient::belongToSameApplication(under, other)) { under = (c == other) ? nullptr : other; break; } } } if (under) { unconstrained_stacking_order.removeAll(c); unconstrained_stacking_order.insert(unconstrained_stacking_order.indexOf(under), c); } Q_ASSERT(unconstrained_stacking_order.contains(c)); FocusChain::self()->moveAfterClient(c, under); updateStackingOrder(); } void Workspace::restackClientUnderActive(AbstractClient* c) { if (!active_client || active_client == c || active_client->layer() != c->layer()) { raiseClient(c); return; } restack(c, active_client); } void Workspace::restoreSessionStackingOrder(Client* c) { if (c->sessionStackingOrder() < 0) return; StackingUpdatesBlocker blocker(this); unconstrained_stacking_order.removeAll(c); for (ToplevelList::Iterator it = unconstrained_stacking_order.begin(); // from bottom it != unconstrained_stacking_order.end(); ++it) { Client *current = qobject_cast(*it); if (!current) { continue; } if (current->sessionStackingOrder() > c->sessionStackingOrder()) { unconstrained_stacking_order.insert(it, c); return; } } unconstrained_stacking_order.append(c); } /** * Returns a stacking order based upon \a list that fulfills certain contained. */ ToplevelList Workspace::constrainedStackingOrder() { ToplevelList layer[ NumLayers ]; // build the order from layers QVector< QMap > minimum_layer(screens()->count()); for (ToplevelList::ConstIterator it = unconstrained_stacking_order.constBegin(), end = unconstrained_stacking_order.constEnd(); it != end; ++it) { Layer l = (*it)->layer(); const int screen = (*it)->screen(); Client *c = qobject_cast(*it); QMap< Group*, Layer >::iterator mLayer = minimum_layer[screen].find(c ? c->group() : nullptr); if (mLayer != minimum_layer[screen].end()) { // If a window is raised above some other window in the same window group // which is in the ActiveLayer (i.e. it's fulscreened), make sure it stays // above that window (see #95731). if (*mLayer == ActiveLayer && (l > BelowLayer)) l = ActiveLayer; *mLayer = l; } else if (c) { minimum_layer[screen].insertMulti(c->group(), l); } layer[ l ].append(*it); } ToplevelList stacking; for (int lay = FirstLayer; lay < NumLayers; ++lay) { stacking += layer[lay]; } // now keep transients above their mainwindows // TODO this could(?) use some optimization for (int i = stacking.size() - 1; i >= 0;) { // Index of the main window for the current transient window. int i2 = -1; // If the current transient has "child" transients, we'd like to restart // construction of the constrained stacking order from the position where // the current transient will be moved. bool hasTransients = false; // Find topmost client this one is transient for. if (auto *client = qobject_cast(stacking[i])) { if (!client->isTransient()) { --i; continue; } for (i2 = stacking.size() - 1; i2 >= 0; --i2) { auto *c2 = qobject_cast(stacking[i2]); if (!c2) { continue; } if (c2 == client) { i2 = -1; // Don't reorder, already on top of its main window. break; } if (c2->hasTransient(client, true) && keepTransientAbove(c2, client)) { break; } } hasTransients = !client->transients().isEmpty(); // If the current transient doesn't have any "alive" transients, check // whether it has deleted transients that have to be raised. const bool searchForDeletedTransients = !hasTransients && !deletedList().isEmpty(); if (searchForDeletedTransients) { for (int j = i + 1; j < stacking.count(); ++j) { auto *deleted = qobject_cast(stacking[j]); if (!deleted) { continue; } if (deleted->wasTransientFor(client)) { hasTransients = true; break; } } } } else if (auto *deleted = qobject_cast(stacking[i])) { if (!deleted->wasTransient()) { --i; continue; } for (i2 = stacking.size() - 1; i2 >= 0; --i2) { Toplevel *c2 = stacking[i2]; if (c2 == deleted) { i2 = -1; // Don't reorder, already on top of its main window. break; } if (deleted->wasTransientFor(c2) && keepDeletedTransientAbove(c2, deleted)) { break; } } hasTransients = !deleted->transients().isEmpty(); } if (i2 == -1) { --i; continue; } Toplevel *current = stacking[i]; stacking.removeAt(i); --i; // move onto the next item (for next for () iteration) --i2; // adjust index of the mainwindow after the remove above if (hasTransients) { // this one now can be possibly above its transients, i = i2; // so go again higher in the stack order and possibly move those transients again } ++i2; // insert after (on top of) the mainwindow, it's ok if it2 is now stacking.end() stacking.insert(i2, current); } return stacking; } void Workspace::blockStackingUpdates(bool block) { if (block) { if (block_stacking_updates == 0) blocked_propagating_new_clients = false; ++block_stacking_updates; } else // !block if (--block_stacking_updates == 0) { updateStackingOrder(blocked_propagating_new_clients); if (effects) static_cast(effects)->checkInputWindowStacking(); } } namespace { template QList ensureStackingOrderInList(const ToplevelList &stackingOrder, const QList &list) { static_assert(std::is_base_of::value, "U must be derived from T"); // TODO Q_ASSERT( block_stacking_updates == 0 ); if (list.count() < 2) return list; // TODO is this worth optimizing? QList result = list; for (auto it = stackingOrder.begin(); it != stackingOrder.end(); ++it) { T *c = qobject_cast(*it); if (!c) { continue; } if (result.removeAll(c) != 0) result.append(c); } return result; } } // Ensure list is in stacking order ClientList Workspace::ensureStackingOrder(const ClientList& list) const { return ensureStackingOrderInList(stacking_order, list); } QList Workspace::ensureStackingOrder(const QList &list) const { return ensureStackingOrderInList(stacking_order, list); } // check whether a transient should be actually kept above its mainwindow // there may be some special cases where this rule shouldn't be enfored bool Workspace::keepTransientAbove(const AbstractClient* mainwindow, const AbstractClient* transient) { // #93832 - don't keep splashscreens above dialogs if (transient->isSplash() && mainwindow->isDialog()) return false; // This is rather a hack for #76026. Don't keep non-modal dialogs above // the mainwindow, but only if they're group transient (since only such dialogs // have taskbar entry in Kicker). A proper way of doing this (both kwin and kicker) // needs to be found. if (transient->isDialog() && !transient->isModal() && transient->groupTransient()) return false; // #63223 - don't keep transients above docks, because the dock is kept high, // and e.g. dialogs for them would be too high too // ignore this if the transient has a placement hint which indicates it should go above it's parent if (mainwindow->isDock() && !transient->hasTransientPlacementHint()) return false; return true; } bool Workspace::keepDeletedTransientAbove(const Toplevel *mainWindow, const Deleted *transient) const { // #93832 - Don't keep splashscreens above dialogs. if (transient->isSplash() && mainWindow->isDialog()) { return false; } if (transient->wasX11Client()) { // If a group transient was active, we should keep it above no matter // what, because at the time when the transient was closed, it was above // the main window. if (transient->wasGroupTransient() && transient->wasActive()) { return true; } // This is rather a hack for #76026. Don't keep non-modal dialogs above // the mainwindow, but only if they're group transient (since only such // dialogs have taskbar entry in Kicker). A proper way of doing this // (both kwin and kicker) needs to be found. if (transient->wasGroupTransient() && transient->isDialog() && !transient->isModal()) { return false; } // #63223 - Don't keep transients above docks, because the dock is kept // high, and e.g. dialogs for them would be too high too. if (mainWindow->isDock()) { return false; } } return true; } // Returns all windows in their stacking order on the root window. ToplevelList Workspace::xStackingOrder() const { if (m_xStackingDirty) { const_cast(this)->updateXStackingOrder(); } return x_stacking; } void Workspace::updateXStackingOrder() { x_stacking.clear(); std::unique_ptr tree{std::move(m_xStackingQueryTree)}; // use our own stacking order, not the X one, as they may differ foreach (Toplevel * c, stacking_order) x_stacking.append(c); if (tree && !tree->isNull()) { xcb_window_t *windows = tree->children(); const auto count = tree->data()->children_len; int foundUnmanagedCount = unmanaged.count(); for (unsigned int i = 0; i < count; ++i) { for (auto it = unmanaged.constBegin(); it != unmanaged.constEnd(); ++it) { Unmanaged *u = *it; if (u->window() == windows[i]) { x_stacking.append(u); foundUnmanagedCount--; break; } } if (foundUnmanagedCount == 0) { break; } } } - if (waylandServer()) { - const auto clients = waylandServer()->internalClients(); - for (auto c: clients) { - if (c->isShown(false)) { - x_stacking << c; - } + + for (InternalClient *client : workspace()->internalClients()) { + if (client->isShown(false)) { + x_stacking.append(client); } } + m_xStackingDirty = false; } //******************************* // Client //******************************* void Client::restackWindow(xcb_window_t above, int detail, NET::RequestSource src, xcb_timestamp_t timestamp, bool send_event) { Client *other = nullptr; if (detail == XCB_STACK_MODE_OPPOSITE) { other = workspace()->findClient(Predicate::WindowMatch, above); if (!other) { workspace()->raiseOrLowerClient(this); return; } ToplevelList::const_iterator it = workspace()->stackingOrder().constBegin(), end = workspace()->stackingOrder().constEnd(); while (it != end) { if (*it == this) { detail = XCB_STACK_MODE_ABOVE; break; } else if (*it == other) { detail = XCB_STACK_MODE_BELOW; break; } ++it; } } else if (detail == XCB_STACK_MODE_TOP_IF) { other = workspace()->findClient(Predicate::WindowMatch, above); if (other && other->geometry().intersects(geometry())) workspace()->raiseClientRequest(this, src, timestamp); return; } else if (detail == XCB_STACK_MODE_BOTTOM_IF) { other = workspace()->findClient(Predicate::WindowMatch, above); if (other && other->geometry().intersects(geometry())) workspace()->lowerClientRequest(this, src, timestamp); return; } if (!other) other = workspace()->findClient(Predicate::WindowMatch, above); if (other && detail == XCB_STACK_MODE_ABOVE) { ToplevelList::const_iterator it = workspace()->stackingOrder().constEnd(), begin = workspace()->stackingOrder().constBegin(); while (--it != begin) { if (*it == other) { // the other one is top on stack it = begin; // invalidate src = NET::FromTool; // force break; } Client *c = qobject_cast(*it); if (!c || !( (*it)->isNormalWindow() && c->isShown(true) && (*it)->isOnCurrentDesktop() && (*it)->isOnCurrentActivity() && (*it)->isOnScreen(screen()) )) continue; // irrelevant clients if (*(it - 1) == other) break; // "it" is the one above the target one, stack below "it" } if (it != begin && (*(it - 1) == other)) other = qobject_cast(*it); else other = nullptr; } if (other) workspace()->restack(this, other); else if (detail == XCB_STACK_MODE_BELOW) workspace()->lowerClientRequest(this, src, timestamp); else if (detail == XCB_STACK_MODE_ABOVE) workspace()->raiseClientRequest(this, src, timestamp); if (send_event) sendSyntheticConfigureNotify(); } void Client::doSetKeepAbove() { } void Client::doSetKeepBelow() { } bool Client::belongsToDesktop() const { foreach (const Client *c, group()->members()) { if (c->isDesktop()) return true; } return false; } } // namespace diff --git a/platformsupport/scenes/opengl/abstract_egl_backend.cpp b/platformsupport/scenes/opengl/abstract_egl_backend.cpp index f8050a17b..f650901d0 100644 --- a/platformsupport/scenes/opengl/abstract_egl_backend.cpp +++ b/platformsupport/scenes/opengl/abstract_egl_backend.cpp @@ -1,600 +1,708 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2015 Martin Gräßlin 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 . *********************************************************************/ #include "abstract_egl_backend.h" #include "linux_dmabuf.h" #include "texture.h" #include "composite.h" #include "egl_context_attribute_builder.h" #include "options.h" #include "platform.h" #include "scene.h" #include "wayland_server.h" #include #include #include // kwin libs #include #include #include // Qt #include #include #include namespace KWin { typedef GLboolean(*eglBindWaylandDisplayWL_func)(EGLDisplay dpy, wl_display *display); typedef GLboolean(*eglUnbindWaylandDisplayWL_func)(EGLDisplay dpy, wl_display *display); typedef GLboolean(*eglQueryWaylandBufferWL_func)(EGLDisplay dpy, struct wl_resource *buffer, EGLint attribute, EGLint *value); eglBindWaylandDisplayWL_func eglBindWaylandDisplayWL = nullptr; eglUnbindWaylandDisplayWL_func eglUnbindWaylandDisplayWL = nullptr; eglQueryWaylandBufferWL_func eglQueryWaylandBufferWL = nullptr; #ifndef EGL_WAYLAND_BUFFER_WL #define EGL_WAYLAND_BUFFER_WL 0x31D5 #endif #ifndef EGL_WAYLAND_PLANE_WL #define EGL_WAYLAND_PLANE_WL 0x31D6 #endif #ifndef EGL_WAYLAND_Y_INVERTED_WL #define EGL_WAYLAND_Y_INVERTED_WL 0x31DB #endif AbstractEglBackend::AbstractEglBackend() : QObject(nullptr) , OpenGLBackend() { connect(Compositor::self(), &Compositor::aboutToDestroy, this, &AbstractEglBackend::unbindWaylandDisplay); } AbstractEglBackend::~AbstractEglBackend() = default; void AbstractEglBackend::unbindWaylandDisplay() { if (eglUnbindWaylandDisplayWL && m_display != EGL_NO_DISPLAY) { eglUnbindWaylandDisplayWL(m_display, *(WaylandServer::self()->display())); } } void AbstractEglBackend::cleanup() { cleanupGL(); doneCurrent(); eglDestroyContext(m_display, m_context); cleanupSurfaces(); eglReleaseThread(); kwinApp()->platform()->setSceneEglContext(EGL_NO_CONTEXT); kwinApp()->platform()->setSceneEglSurface(EGL_NO_SURFACE); kwinApp()->platform()->setSceneEglConfig(nullptr); } void AbstractEglBackend::cleanupSurfaces() { if (m_surface != EGL_NO_SURFACE) { eglDestroySurface(m_display, m_surface); } } bool AbstractEglBackend::initEglAPI() { EGLint major, minor; if (eglInitialize(m_display, &major, &minor) == EGL_FALSE) { qCWarning(KWIN_OPENGL) << "eglInitialize failed"; EGLint error = eglGetError(); if (error != EGL_SUCCESS) { qCWarning(KWIN_OPENGL) << "Error during eglInitialize " << error; } return false; } EGLint error = eglGetError(); if (error != EGL_SUCCESS) { qCWarning(KWIN_OPENGL) << "Error during eglInitialize " << error; return false; } qCDebug(KWIN_OPENGL) << "Egl Initialize succeeded"; if (eglBindAPI(isOpenGLES() ? EGL_OPENGL_ES_API : EGL_OPENGL_API) == EGL_FALSE) { qCCritical(KWIN_OPENGL) << "bind OpenGL API failed"; return false; } qCDebug(KWIN_OPENGL) << "EGL version: " << major << "." << minor; const QByteArray eglExtensions = eglQueryString(m_display, EGL_EXTENSIONS); setExtensions(eglExtensions.split(' ')); return true; } typedef void (*eglFuncPtr)(); static eglFuncPtr getProcAddress(const char* name) { return eglGetProcAddress(name); } void AbstractEglBackend::initKWinGL() { GLPlatform *glPlatform = GLPlatform::instance(); glPlatform->detect(EglPlatformInterface); options->setGlPreferBufferSwap(options->glPreferBufferSwap()); // resolve autosetting if (options->glPreferBufferSwap() == Options::AutoSwapStrategy) options->setGlPreferBufferSwap('e'); // for unknown drivers - should not happen glPlatform->printResults(); initGL(&getProcAddress); } void AbstractEglBackend::initBufferAge() { setSupportsBufferAge(false); if (hasExtension(QByteArrayLiteral("EGL_EXT_buffer_age"))) { const QByteArray useBufferAge = qgetenv("KWIN_USE_BUFFER_AGE"); if (useBufferAge != "0") setSupportsBufferAge(true); } } void AbstractEglBackend::initWayland() { if (!WaylandServer::self()) { return; } if (hasExtension(QByteArrayLiteral("EGL_WL_bind_wayland_display"))) { eglBindWaylandDisplayWL = (eglBindWaylandDisplayWL_func)eglGetProcAddress("eglBindWaylandDisplayWL"); eglUnbindWaylandDisplayWL = (eglUnbindWaylandDisplayWL_func)eglGetProcAddress("eglUnbindWaylandDisplayWL"); eglQueryWaylandBufferWL = (eglQueryWaylandBufferWL_func)eglGetProcAddress("eglQueryWaylandBufferWL"); // only bind if not already done if (waylandServer()->display()->eglDisplay() != eglDisplay()) { if (!eglBindWaylandDisplayWL(eglDisplay(), *(WaylandServer::self()->display()))) { eglUnbindWaylandDisplayWL = nullptr; eglQueryWaylandBufferWL = nullptr; } else { waylandServer()->display()->setEglDisplay(eglDisplay()); } } } LinuxDmabuf::factory(this); } void AbstractEglBackend::initClientExtensions() { // Get the list of client extensions const char* clientExtensionsCString = eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS); const QByteArray clientExtensionsString = QByteArray::fromRawData(clientExtensionsCString, qstrlen(clientExtensionsCString)); if (clientExtensionsString.isEmpty()) { // If eglQueryString() returned NULL, the implementation doesn't support // EGL_EXT_client_extensions. Expect an EGL_BAD_DISPLAY error. (void) eglGetError(); } m_clientExtensions = clientExtensionsString.split(' '); } bool AbstractEglBackend::hasClientExtension(const QByteArray &ext) const { return m_clientExtensions.contains(ext); } bool AbstractEglBackend::makeCurrent() { if (QOpenGLContext *context = QOpenGLContext::currentContext()) { // Workaround to tell Qt that no QOpenGLContext is current context->doneCurrent(); } const bool current = eglMakeCurrent(m_display, m_surface, m_surface, m_context); return current; } void AbstractEglBackend::doneCurrent() { eglMakeCurrent(m_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); } bool AbstractEglBackend::isOpenGLES() const { if (qstrcmp(qgetenv("KWIN_COMPOSE"), "O2ES") == 0) { return true; } return QOpenGLContext::openGLModuleType() == QOpenGLContext::LibGLES; } bool AbstractEglBackend::createContext() { const bool haveRobustness = hasExtension(QByteArrayLiteral("EGL_EXT_create_context_robustness")); const bool haveCreateContext = hasExtension(QByteArrayLiteral("EGL_KHR_create_context")); const bool haveContextPriority = hasExtension(QByteArrayLiteral("EGL_IMG_context_priority")); std::vector> candidates; if (isOpenGLES()) { if (haveCreateContext && haveRobustness && haveContextPriority) { auto glesRobustPriority = std::unique_ptr(new EglOpenGLESContextAttributeBuilder); glesRobustPriority->setVersion(2); glesRobustPriority->setRobust(true); glesRobustPriority->setHighPriority(true); candidates.push_back(std::move(glesRobustPriority)); } if (haveCreateContext && haveRobustness) { auto glesRobust = std::unique_ptr(new EglOpenGLESContextAttributeBuilder); glesRobust->setVersion(2); glesRobust->setRobust(true); candidates.push_back(std::move(glesRobust)); } if (haveContextPriority) { auto glesPriority = std::unique_ptr(new EglOpenGLESContextAttributeBuilder); glesPriority->setVersion(2); glesPriority->setHighPriority(true); candidates.push_back(std::move(glesPriority)); } auto gles = std::unique_ptr(new EglOpenGLESContextAttributeBuilder); gles->setVersion(2); candidates.push_back(std::move(gles)); } else { if (options->glCoreProfile() && haveCreateContext) { if (haveRobustness && haveContextPriority) { auto robustCorePriority = std::unique_ptr(new EglContextAttributeBuilder); robustCorePriority->setVersion(3, 1); robustCorePriority->setRobust(true); robustCorePriority->setHighPriority(true); candidates.push_back(std::move(robustCorePriority)); } if (haveRobustness) { auto robustCore = std::unique_ptr(new EglContextAttributeBuilder); robustCore->setVersion(3, 1); robustCore->setRobust(true); candidates.push_back(std::move(robustCore)); } if (haveContextPriority) { auto corePriority = std::unique_ptr(new EglContextAttributeBuilder); corePriority->setVersion(3, 1); corePriority->setHighPriority(true); candidates.push_back(std::move(corePriority)); } auto core = std::unique_ptr(new EglContextAttributeBuilder); core->setVersion(3, 1); candidates.push_back(std::move(core)); } if (haveRobustness && haveCreateContext && haveContextPriority) { auto robustPriority = std::unique_ptr(new EglContextAttributeBuilder); robustPriority->setRobust(true); robustPriority->setHighPriority(true); candidates.push_back(std::move(robustPriority)); } if (haveRobustness && haveCreateContext) { auto robust = std::unique_ptr(new EglContextAttributeBuilder); robust->setRobust(true); candidates.push_back(std::move(robust)); } candidates.emplace_back(new EglContextAttributeBuilder); } EGLContext ctx = EGL_NO_CONTEXT; for (auto it = candidates.begin(); it != candidates.end(); it++) { const auto attribs = (*it)->build(); ctx = eglCreateContext(m_display, config(), EGL_NO_CONTEXT, attribs.data()); if (ctx != EGL_NO_CONTEXT) { qCDebug(KWIN_OPENGL) << "Created EGL context with attributes:" << (*it).get(); break; } } if (ctx == EGL_NO_CONTEXT) { qCCritical(KWIN_OPENGL) << "Create Context failed"; return false; } m_context = ctx; kwinApp()->platform()->setSceneEglContext(m_context); return true; } void AbstractEglBackend::setEglDisplay(const EGLDisplay &display) { m_display = display; kwinApp()->platform()->setSceneEglDisplay(display); } void AbstractEglBackend::setConfig(const EGLConfig &config) { m_config = config; kwinApp()->platform()->setSceneEglConfig(config); } void AbstractEglBackend::setSurface(const EGLSurface &surface) { m_surface = surface; kwinApp()->platform()->setSceneEglSurface(surface); } AbstractEglTexture::AbstractEglTexture(SceneOpenGLTexture *texture, AbstractEglBackend *backend) : SceneOpenGLTexturePrivate() , q(texture) , m_backend(backend) , m_image(EGL_NO_IMAGE_KHR) { m_target = GL_TEXTURE_2D; } AbstractEglTexture::~AbstractEglTexture() { if (m_image != EGL_NO_IMAGE_KHR) { eglDestroyImageKHR(m_backend->eglDisplay(), m_image); } } OpenGLBackend *AbstractEglTexture::backend() { return m_backend; } bool AbstractEglTexture::loadTexture(WindowPixmap *pixmap) { + // FIXME: Refactor this method. + const auto &buffer = pixmap->buffer(); if (buffer.isNull()) { if (updateFromFBO(pixmap->fbo())) { return true; } + if (loadInternalImageObject(pixmap)) { + return true; + } return false; } // try Wayland loading if (auto s = pixmap->surface()) { s->resetTrackedDamage(); } if (buffer->linuxDmabufBuffer()) { return loadDmabufTexture(buffer); } else if (buffer->shmBuffer()) { return loadShmTexture(buffer); } return loadEglTexture(buffer); } void AbstractEglTexture::updateTexture(WindowPixmap *pixmap) { + // FIXME: Refactor this method. + const auto &buffer = pixmap->buffer(); if (buffer.isNull()) { - const auto &fbo = pixmap->fbo(); - if (!fbo.isNull()) { - if (m_texture != fbo->texture()) { - updateFromFBO(fbo); - } + if (updateFromFBO(pixmap->fbo())) { + return; + } + if (updateFromInternalImageObject(pixmap)) { return; } return; } auto s = pixmap->surface(); if (DmabufBuffer *dmabuf = static_cast(buffer->linuxDmabufBuffer())) { q->bind(); glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, (GLeglImageOES) dmabuf->images()[0]); //TODO q->unbind(); if (m_image != EGL_NO_IMAGE_KHR) { eglDestroyImageKHR(m_backend->eglDisplay(), m_image); } m_image = EGL_NO_IMAGE_KHR; // The wl_buffer has ownership of the image // The origin in a dmabuf-buffer is at the upper-left corner, so the meaning // of Y-inverted is the inverse of OpenGL. const bool yInverted = !(dmabuf->flags() & KWayland::Server::LinuxDmabufUnstableV1Interface::YInverted); if (m_size != dmabuf->size() || yInverted != q->isYInverted()) { m_size = dmabuf->size(); q->setYInverted(yInverted); } if (s) { s->resetTrackedDamage(); } return; } if (!buffer->shmBuffer()) { q->bind(); EGLImageKHR image = attach(buffer); q->unbind(); if (image != EGL_NO_IMAGE_KHR) { if (m_image != EGL_NO_IMAGE_KHR) { eglDestroyImageKHR(m_backend->eglDisplay(), m_image); } m_image = image; } if (s) { s->resetTrackedDamage(); } return; } // shm fallback const QImage &image = buffer->data(); if (image.isNull() || !s) { return; } if (image.size() != m_size) { // buffer size has changed, reload shm texture if (!loadTexture(pixmap)) { return; } } Q_ASSERT(image.size() == m_size); q->bind(); const QRegion damage = s->trackedDamage(); s->resetTrackedDamage(); auto scale = s->scale(); //damage is normalised, so needs converting up to match texture // TODO: this should be shared with GLTexture::update if (GLPlatform::instance()->isGLES()) { if (s_supportsARGB32 && (image.format() == QImage::Format_ARGB32 || image.format() == QImage::Format_ARGB32_Premultiplied)) { const QImage im = image.convertToFormat(QImage::Format_ARGB32_Premultiplied); for (const QRect &rect : damage) { auto scaledRect = QRect(rect.x() * scale, rect.y() * scale, rect.width() * scale, rect.height() * scale); glTexSubImage2D(m_target, 0, scaledRect.x(), scaledRect.y(), scaledRect.width(), scaledRect.height(), GL_BGRA_EXT, GL_UNSIGNED_BYTE, im.copy(scaledRect).bits()); } } else { const QImage im = image.convertToFormat(QImage::Format_RGBA8888_Premultiplied); for (const QRect &rect : damage) { auto scaledRect = QRect(rect.x() * scale, rect.y() * scale, rect.width() * scale, rect.height() * scale); glTexSubImage2D(m_target, 0, scaledRect.x(), scaledRect.y(), scaledRect.width(), scaledRect.height(), GL_RGBA, GL_UNSIGNED_BYTE, im.copy(scaledRect).bits()); } } } else { const QImage im = image.convertToFormat(QImage::Format_ARGB32_Premultiplied); for (const QRect &rect : damage) { auto scaledRect = QRect(rect.x() * scale, rect.y() * scale, rect.width() * scale, rect.height() * scale); glTexSubImage2D(m_target, 0, scaledRect.x(), scaledRect.y(), scaledRect.width(), scaledRect.height(), GL_BGRA, GL_UNSIGNED_BYTE, im.copy(scaledRect).bits()); } } q->unbind(); } bool AbstractEglTexture::loadShmTexture(const QPointer< KWayland::Server::BufferInterface > &buffer) { const QImage &image = buffer->data(); if (image.isNull()) { return false; } glGenTextures(1, &m_texture); q->setWrapMode(GL_CLAMP_TO_EDGE); q->setFilter(GL_LINEAR); q->bind(); const QSize &size = image.size(); // TODO: this should be shared with GLTexture(const QImage&, GLenum) GLenum format = 0; switch (image.format()) { case QImage::Format_ARGB32: case QImage::Format_ARGB32_Premultiplied: format = GL_RGBA8; break; case QImage::Format_RGB32: format = GL_RGB8; break; default: return false; } if (GLPlatform::instance()->isGLES()) { if (s_supportsARGB32 && format == GL_RGBA8) { const QImage im = image.convertToFormat(QImage::Format_ARGB32_Premultiplied); glTexImage2D(m_target, 0, GL_BGRA_EXT, im.width(), im.height(), 0, GL_BGRA_EXT, GL_UNSIGNED_BYTE, im.bits()); } else { const QImage im = image.convertToFormat(QImage::Format_RGBA8888_Premultiplied); glTexImage2D(m_target, 0, GL_RGBA, im.width(), im.height(), 0, GL_RGBA, GL_UNSIGNED_BYTE, im.bits()); } } else { glTexImage2D(m_target, 0, format, size.width(), size.height(), 0, GL_BGRA, GL_UNSIGNED_BYTE, image.bits()); } q->unbind(); q->setYInverted(true); m_size = size; updateMatrix(); return true; } bool AbstractEglTexture::loadEglTexture(const QPointer< KWayland::Server::BufferInterface > &buffer) { if (!eglQueryWaylandBufferWL) { return false; } if (!buffer->resource()) { return false; } glGenTextures(1, &m_texture); q->setWrapMode(GL_CLAMP_TO_EDGE); q->setFilter(GL_LINEAR); q->bind(); m_image = attach(buffer); q->unbind(); if (EGL_NO_IMAGE_KHR == m_image) { qCDebug(KWIN_OPENGL) << "failed to create egl image"; q->discard(); return false; } return true; } bool AbstractEglTexture::loadDmabufTexture(const QPointer< KWayland::Server::BufferInterface > &buffer) { DmabufBuffer *dmabuf = static_cast(buffer->linuxDmabufBuffer()); if (!dmabuf || dmabuf->images()[0] == EGL_NO_IMAGE_KHR) { qCritical(KWIN_OPENGL) << "Invalid dmabuf-based wl_buffer"; q->discard(); return false; } Q_ASSERT(m_image == EGL_NO_IMAGE_KHR); glGenTextures(1, &m_texture); q->setWrapMode(GL_CLAMP_TO_EDGE); q->setFilter(GL_NEAREST); q->bind(); glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, (GLeglImageOES) dmabuf->images()[0]); q->unbind(); m_size = dmabuf->size(); q->setYInverted(!(dmabuf->flags() & KWayland::Server::LinuxDmabufUnstableV1Interface::YInverted)); return true; } +bool AbstractEglTexture::loadInternalImageObject(WindowPixmap *pixmap) +{ + // FIXME: Share some code with loadShmTexture(). + + const QImage image = pixmap->internalImage(); + if (image.isNull()) { + return false; + } + + glGenTextures(1, &m_texture); + q->setFilter(GL_LINEAR); + q->setWrapMode(GL_CLAMP_TO_EDGE); + q->setYInverted(true); + q->bind(); + + const QSize &size = image.size(); + // TODO: this should be shared with GLTexture(const QImage&, GLenum) + GLenum format = 0; + switch (image.format()) { + case QImage::Format_ARGB32: + case QImage::Format_ARGB32_Premultiplied: + format = GL_RGBA8; + break; + case QImage::Format_RGB32: + format = GL_RGB8; + break; + default: + return false; + } + if (GLPlatform::instance()->isGLES()) { + if (s_supportsARGB32 && format == GL_RGBA8) { + const QImage im = image.convertToFormat(QImage::Format_ARGB32_Premultiplied); + glTexImage2D(m_target, 0, GL_BGRA_EXT, im.width(), im.height(), + 0, GL_BGRA_EXT, GL_UNSIGNED_BYTE, im.bits()); + } else { + const QImage im = image.convertToFormat(QImage::Format_RGBA8888_Premultiplied); + glTexImage2D(m_target, 0, GL_RGBA, im.width(), im.height(), + 0, GL_RGBA, GL_UNSIGNED_BYTE, im.bits()); + } + } else { + glTexImage2D(m_target, 0, format, size.width(), size.height(), 0, + GL_BGRA, GL_UNSIGNED_BYTE, image.bits()); + } + + q->unbind(); + + m_size = size; + updateMatrix(); + + return true; +} + EGLImageKHR AbstractEglTexture::attach(const QPointer< KWayland::Server::BufferInterface > &buffer) { EGLint format, yInverted; eglQueryWaylandBufferWL(m_backend->eglDisplay(), buffer->resource(), EGL_TEXTURE_FORMAT, &format); if (format != EGL_TEXTURE_RGB && format != EGL_TEXTURE_RGBA) { qCDebug(KWIN_OPENGL) << "Unsupported texture format: " << format; return EGL_NO_IMAGE_KHR; } if (!eglQueryWaylandBufferWL(m_backend->eglDisplay(), buffer->resource(), EGL_WAYLAND_Y_INVERTED_WL, &yInverted)) { // if EGL_WAYLAND_Y_INVERTED_WL is not supported wl_buffer should be treated as if value were EGL_TRUE yInverted = EGL_TRUE; } const EGLint attribs[] = { EGL_WAYLAND_PLANE_WL, 0, EGL_NONE }; EGLImageKHR image = eglCreateImageKHR(m_backend->eglDisplay(), EGL_NO_CONTEXT, EGL_WAYLAND_BUFFER_WL, (EGLClientBuffer)buffer->resource(), attribs); if (image != EGL_NO_IMAGE_KHR) { glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, (GLeglImageOES)image); m_size = buffer->size(); updateMatrix(); q->setYInverted(yInverted); } return image; } bool AbstractEglTexture::updateFromFBO(const QSharedPointer &fbo) { if (fbo.isNull()) { return false; } m_texture = fbo->texture(); m_size = fbo->size(); q->setWrapMode(GL_CLAMP_TO_EDGE); q->setFilter(GL_LINEAR); q->setYInverted(false); updateMatrix(); return true; } +bool AbstractEglTexture::updateFromInternalImageObject(WindowPixmap *pixmap) +{ + // FIXME: Share some code with the shm fallback in updateTexture(). + + const QImage image = pixmap->internalImage(); + if (image.isNull()) { + return false; + } + + if (m_size != image.size()) { + glDeleteTextures(1, &m_texture); + return loadInternalImageObject(pixmap); + } + + const QRegion damage = pixmap->toplevel()->damage(); + const qreal scale = image.devicePixelRatio(); + + q->bind(); + + // TODO: this should be shared with GLTexture::update + if (GLPlatform::instance()->isGLES()) { + if (s_supportsARGB32 && (image.format() == QImage::Format_ARGB32 || image.format() == QImage::Format_ARGB32_Premultiplied)) { + const QImage im = image.convertToFormat(QImage::Format_ARGB32_Premultiplied); + for (const QRect &rect : damage) { + auto scaledRect = QRect(rect.x() * scale, rect.y() * scale, rect.width() * scale, rect.height() * scale); + glTexSubImage2D(m_target, 0, scaledRect.x(), scaledRect.y(), scaledRect.width(), scaledRect.height(), + GL_BGRA_EXT, GL_UNSIGNED_BYTE, im.copy(scaledRect).bits()); + } + } else { + const QImage im = image.convertToFormat(QImage::Format_RGBA8888_Premultiplied); + for (const QRect &rect : damage) { + auto scaledRect = QRect(rect.x() * scale, rect.y() * scale, rect.width() * scale, rect.height() * scale); + glTexSubImage2D(m_target, 0, scaledRect.x(), scaledRect.y(), scaledRect.width(), scaledRect.height(), + GL_RGBA, GL_UNSIGNED_BYTE, im.copy(scaledRect).bits()); + } + } + } else { + const QImage im = image.convertToFormat(QImage::Format_ARGB32_Premultiplied); + for (const QRect &rect : damage) { + auto scaledRect = QRect(rect.x() * scale, rect.y() * scale, rect.width() * scale, rect.height() * scale); + glTexSubImage2D(m_target, 0, scaledRect.x(), scaledRect.y(), scaledRect.width(), scaledRect.height(), + GL_BGRA, GL_UNSIGNED_BYTE, im.copy(scaledRect).bits()); + } + } + + q->unbind(); + + return true; +} + } diff --git a/platformsupport/scenes/opengl/abstract_egl_backend.h b/platformsupport/scenes/opengl/abstract_egl_backend.h index 18232fd04..6a173d5c1 100644 --- a/platformsupport/scenes/opengl/abstract_egl_backend.h +++ b/platformsupport/scenes/opengl/abstract_egl_backend.h @@ -1,123 +1,125 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2015 Martin Gräßlin 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 . *********************************************************************/ #ifndef KWIN_ABSTRACT_EGL_BACKEND_H #define KWIN_ABSTRACT_EGL_BACKEND_H #include "backend.h" #include "texture.h" #include #include #include class QOpenGLFramebufferObject; namespace KWayland { namespace Server { class BufferInterface; } } namespace KWin { class KWIN_EXPORT AbstractEglBackend : public QObject, public OpenGLBackend { Q_OBJECT public: ~AbstractEglBackend() override; bool makeCurrent() override; void doneCurrent() override; EGLDisplay eglDisplay() const { return m_display; } EGLContext context() const { return m_context; } EGLSurface surface() const { return m_surface; } EGLConfig config() const { return m_config; } protected: AbstractEglBackend(); void setEglDisplay(const EGLDisplay &display); void setSurface(const EGLSurface &surface); void setConfig(const EGLConfig &config); void cleanup(); virtual void cleanupSurfaces(); bool initEglAPI(); void initKWinGL(); void initBufferAge(); void initClientExtensions(); void initWayland(); bool hasClientExtension(const QByteArray &ext) const; bool isOpenGLES() const; bool createContext(); private: void unbindWaylandDisplay(); EGLDisplay m_display = EGL_NO_DISPLAY; EGLSurface m_surface = EGL_NO_SURFACE; EGLContext m_context = EGL_NO_CONTEXT; EGLConfig m_config = nullptr; QList m_clientExtensions; }; class KWIN_EXPORT AbstractEglTexture : public SceneOpenGLTexturePrivate { public: ~AbstractEglTexture() override; bool loadTexture(WindowPixmap *pixmap) override; void updateTexture(WindowPixmap *pixmap) override; OpenGLBackend *backend() override; protected: AbstractEglTexture(SceneOpenGLTexture *texture, AbstractEglBackend *backend); EGLImageKHR image() const { return m_image; } void setImage(const EGLImageKHR &img) { m_image = img; } SceneOpenGLTexture *texture() const { return q; } private: bool loadShmTexture(const QPointer &buffer); bool loadEglTexture(const QPointer &buffer); bool loadDmabufTexture(const QPointer< KWayland::Server::BufferInterface > &buffer); + bool loadInternalImageObject(WindowPixmap *pixmap); EGLImageKHR attach(const QPointer &buffer); bool updateFromFBO(const QSharedPointer &fbo); + bool updateFromInternalImageObject(WindowPixmap *pixmap); SceneOpenGLTexture *q; AbstractEglBackend *m_backend; EGLImageKHR m_image; }; } #endif diff --git a/plugins/qpa/CMakeLists.txt b/plugins/qpa/CMakeLists.txt index 06255d2a7..c89820b73 100644 --- a/plugins/qpa/CMakeLists.txt +++ b/plugins/qpa/CMakeLists.txt @@ -1,43 +1,41 @@ include_directories(${Qt5Core_PRIVATE_INCLUDE_DIRS}) include_directories(${Qt5Gui_PRIVATE_INCLUDE_DIRS}) set(QPA_SOURCES abstractplatformcontext.cpp backingstore.cpp eglhelpers.cpp integration.cpp main.cpp - nativeinterface.cpp offscreensurface.cpp platformcursor.cpp screen.cpp sharingplatformcontext.cpp window.cpp ) include(ECMQtDeclareLoggingCategory) ecm_qt_declare_logging_category(QPA_SOURCES HEADER logging.h IDENTIFIER KWIN_QPA CATEGORY_NAME kwin_qpa_plugin DEFAULT_SEVERITY Critical) add_library(KWinQpaPlugin MODULE ${QPA_SOURCES}) set_target_properties(KWinQpaPlugin PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin/platforms/") set(QT5PLATFORMSUPPORT_LIBS Qt5FontDatabaseSupport::Qt5FontDatabaseSupport Qt5ThemeSupport::Qt5ThemeSupport Qt5EventDispatcherSupport::Qt5EventDispatcherSupport ) target_link_libraries(KWinQpaPlugin ${QT5PLATFORMSUPPORT_LIBS} ${FREETYPE_LIBRARIES} # Must be after QT5PLATFORMSUPPORT_LIBS Fontconfig::Fontconfig - KF5::WaylandClient kwin ) install( TARGETS KWinQpaPlugin DESTINATION ${PLUGIN_INSTALL_DIR}/platforms/ ) diff --git a/plugins/qpa/backingstore.cpp b/plugins/qpa/backingstore.cpp index 9940b4047..03fccf7df 100644 --- a/plugins/qpa/backingstore.cpp +++ b/plugins/qpa/backingstore.cpp @@ -1,128 +1,103 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2015 Martin Gräßlin +Copyright (C) 2019 Vlad Zagorodniy 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 . *********************************************************************/ -#include "window.h" #include "backingstore.h" -#include "../../wayland_server.h" +#include "window.h" -#include -#include -#include -#include +#include "internal_client.h" namespace KWin { namespace QPA { -BackingStore::BackingStore(QWindow *w, KWayland::Client::ShmPool *shm) - : QPlatformBackingStore(w) - , m_shm(shm) - , m_backBuffer(QSize(), QImage::Format_ARGB32_Premultiplied) +BackingStore::BackingStore(QWindow *window) + : QPlatformBackingStore(window) { - QObject::connect(m_shm, &KWayland::Client::ShmPool::poolResized, - [this] { - if (!m_buffer) { - return; - } - auto b = m_buffer.toStrongRef(); - if (!b->isUsed()){ - return; - } - const QSize size = m_backBuffer.size(); - m_backBuffer = QImage(b->address(), size.width(), size.height(), QImage::Format_ARGB32_Premultiplied); - m_backBuffer.setDevicePixelRatio(scale()); - } - ); } BackingStore::~BackingStore() = default; QPaintDevice *BackingStore::paintDevice() { return &m_backBuffer; } void BackingStore::resize(const QSize &size, const QRegion &staticContents) { Q_UNUSED(staticContents) - m_size = size * scale(); - if (!m_buffer) { + + if (m_backBuffer.size() == size) { return; } - m_buffer.toStrongRef()->setUsed(false); - m_buffer.clear(); + + const QPlatformWindow *platformWindow = static_cast(window()->handle()); + const qreal devicePixelRatio = platformWindow->devicePixelRatio(); + + m_backBuffer = QImage(size * devicePixelRatio, QImage::Format_ARGB32_Premultiplied); + m_backBuffer.setDevicePixelRatio(devicePixelRatio); + + m_frontBuffer = QImage(size * devicePixelRatio, QImage::Format_ARGB32_Premultiplied); + m_frontBuffer.setDevicePixelRatio(devicePixelRatio); } -void BackingStore::flush(QWindow *window, const QRegion ®ion, const QPoint &offset) +static void blitImage(const QImage &source, QImage &target, const QRect &rect) { - Q_UNUSED(region) - Q_UNUSED(offset) + Q_ASSERT(source.format() == QImage::Format_ARGB32_Premultiplied); + Q_ASSERT(target.format() == QImage::Format_ARGB32_Premultiplied); - auto w = static_cast(window->handle()); - auto s = w->surface(); - if (!s) { - return; + const int devicePixelRatio = target.devicePixelRatio(); + + const int x = rect.x() * devicePixelRatio; + const int y = rect.y() * devicePixelRatio; + const int width = rect.width() * devicePixelRatio; + const int height = rect.height() * devicePixelRatio; + + for (int i = y; i < y + height; ++i) { + const uint32_t *in = reinterpret_cast(source.scanLine(i)); + uint32_t *out = reinterpret_cast(target.scanLine(i)); + std::copy(in + x, in + x + width, out + x); } - s->attachBuffer(m_buffer); - // TODO: proper damage region - s->damage(QRect(QPoint(0, 0), m_backBuffer.size() / scale())); - s->commit(KWayland::Client::Surface::CommitFlag::None); - waylandServer()->internalClientConection()->flush(); - waylandServer()->dispatch(); } -void BackingStore::beginPaint(const QRegion&) +static void blitImage(const QImage &source, QImage &target, const QRegion ®ion) { - if (m_buffer) { - auto b = m_buffer.toStrongRef(); - if (b->isReleased()) { - // we can re-use this buffer - b->setReleased(false); - return; - } else { - // buffer is still in use, get a new one - b->setUsed(false); - } - } - auto oldBuffer = m_buffer.toStrongRef(); - m_buffer.clear(); - m_buffer = m_shm->getBuffer(m_size, m_size.width() * 4); - if (!m_buffer) { - m_backBuffer = QImage(); - return; - } - auto b = m_buffer.toStrongRef(); - b->setUsed(true); - m_backBuffer = QImage(b->address(), m_size.width(), m_size.height(), QImage::Format_ARGB32_Premultiplied); - m_backBuffer.setDevicePixelRatio(scale()); - if (oldBuffer) { - b->copy(oldBuffer->address()); - } else { - m_backBuffer.fill(Qt::transparent); + for (const QRect &rect : region) { + blitImage(source, target, rect); } } -int BackingStore::scale() const +void BackingStore::flush(QWindow *window, const QRegion ®ion, const QPoint &offset) { - return static_cast(window()->handle())->scale(); + Q_UNUSED(offset) + + Window *platformWindow = static_cast(window->handle()); + InternalClient *client = platformWindow->client(); + if (!client) { + return; + } + + blitImage(m_backBuffer, m_frontBuffer, region); + + client->present(m_frontBuffer, region); } } } diff --git a/plugins/qpa/backingstore.h b/plugins/qpa/backingstore.h index b27b0dc07..ccb715e21 100644 --- a/plugins/qpa/backingstore.h +++ b/plugins/qpa/backingstore.h @@ -1,61 +1,49 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2015 Martin Gräßlin +Copyright (C) 2019 Vlad Zagorodniy 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 . *********************************************************************/ #ifndef KWIN_QPA_BACKINGSTORE_H #define KWIN_QPA_BACKINGSTORE_H #include -namespace KWayland -{ -namespace Client -{ -class Buffer; -class ShmPool; -} -} - namespace KWin { namespace QPA { class BackingStore : public QPlatformBackingStore { public: - explicit BackingStore(QWindow *w, KWayland::Client::ShmPool *shm); + explicit BackingStore(QWindow *window); ~BackingStore() override; QPaintDevice *paintDevice() override; void flush(QWindow *window, const QRegion ®ion, const QPoint &offset) override; void resize(const QSize &size, const QRegion &staticContents) override; - void beginPaint(const QRegion &) override; private: - int scale() const; - KWayland::Client::ShmPool *m_shm; - QWeakPointer m_buffer; QImage m_backBuffer; - QSize m_size; + QImage m_frontBuffer; }; } } #endif diff --git a/plugins/qpa/integration.cpp b/plugins/qpa/integration.cpp index 7216c276f..60102e1b1 100644 --- a/plugins/qpa/integration.cpp +++ b/plugins/qpa/integration.cpp @@ -1,275 +1,217 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2015 Martin Gräßlin +Copyright (C) 2019 Vlad Zagorodniy 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 . *********************************************************************/ #include "integration.h" #include "backingstore.h" -#include "nativeinterface.h" #include "offscreensurface.h" #include "screen.h" #include "sharingplatformcontext.h" #include "window.h" #include "../../main.h" #include "../../platform.h" #include "../../screens.h" #include "../../virtualkeyboard.h" -#include "../../wayland_server.h" - -#include -#include -#include -#include -#include -#include #include #include #include #include #include #include #include #include #include namespace KWin { namespace QPA { Integration::Integration() : QObject() , QPlatformIntegration() , m_fontDb(new QGenericUnixFontDatabase()) - , m_nativeInterface(new NativeInterface(this)) , m_inputContext() { } Integration::~Integration() = default; bool Integration::hasCapability(Capability cap) const { switch (cap) { case ThreadedPixmaps: return true; case OpenGL: return true; case ThreadedOpenGL: return false; case BufferQueueingOpenGL: return false; case MultipleWindows: case NonFullScreenWindows: return true; case RasterGLSurface: return false; default: return QPlatformIntegration::hasCapability(cap); } } void Integration::initialize() { connect(kwinApp(), &Application::screensCreated, this, [this] { connect(screens(), &Screens::changed, this, &Integration::initScreens); initScreens(); } ); QPlatformIntegration::initialize(); auto dummyScreen = new Screen(-1); #if (QT_VERSION >= QT_VERSION_CHECK(5, 13, 0)) QWindowSystemInterface::handleScreenAdded(dummyScreen); #else screenAdded(dummyScreen); #endif m_screens << dummyScreen; m_inputContext.reset(QPlatformInputContextFactory::create(QStringLiteral("qtvirtualkeyboard"))); qunsetenv("QT_IM_MODULE"); if (!m_inputContext.isNull()) { connect(qApp, &QGuiApplication::focusObjectChanged, this, [this] { if (VirtualKeyboard::self() && qApp->focusObject() != VirtualKeyboard::self()) { m_inputContext->setFocusObject(VirtualKeyboard::self()); } } ); connect(kwinApp(), &Application::workspaceCreated, this, [this] { if (VirtualKeyboard::self()) { m_inputContext->setFocusObject(VirtualKeyboard::self()); } } ); connect(qApp->inputMethod(), &QInputMethod::visibleChanged, this, [] { if (qApp->inputMethod()->isVisible()) { if (QWindow *w = VirtualKeyboard::self()->inputPanel()) { QWindowSystemInterface::handleWindowActivated(w, Qt::ActiveWindowFocusReason); } } } ); } } QAbstractEventDispatcher *Integration::createEventDispatcher() const { return new QUnixEventDispatcherQPA; } QPlatformBackingStore *Integration::createPlatformBackingStore(QWindow *window) const { - auto registry = waylandServer()->internalClientRegistry(); - const auto shm = registry->interface(KWayland::Client::Registry::Interface::Shm); - if (shm.name == 0u) { - return nullptr; - } - return new BackingStore(window, registry->createShmPool(shm.name, shm.version, window)); + return new BackingStore(window); } QPlatformWindow *Integration::createPlatformWindow(QWindow *window) const { - auto c = compositor(); - auto s = shell(); - if (!s || !c) { - return new QPlatformWindow(window); - } else { - // don't set window as parent, cause infinite recursion in PlasmaQuick::Dialog - auto surface = c->createSurface(c); - return new Window(window, surface, s->createSurface(surface, surface), this); - } + return new Window(window); } QPlatformOffscreenSurface *Integration::createPlatformOffscreenSurface(QOffscreenSurface *surface) const { return new OffscreenSurface(surface); } QPlatformFontDatabase *Integration::fontDatabase() const { return m_fontDb; } QPlatformTheme *Integration::createPlatformTheme(const QString &name) const { return QGenericUnixTheme::createUnixTheme(name); } QStringList Integration::themeNames() const { if (qEnvironmentVariableIsSet("KDE_FULL_SESSION")) { return QStringList({QStringLiteral("kde")}); } return QStringList({QLatin1String(QGenericUnixTheme::name)}); } -QPlatformNativeInterface *Integration::nativeInterface() const -{ - return m_nativeInterface; -} - QPlatformOpenGLContext *Integration::createPlatformOpenGLContext(QOpenGLContext *context) const { if (kwinApp()->platform()->supportsQpaContext()) { return new SharingPlatformContext(context); } if (kwinApp()->platform()->sceneEglDisplay() != EGL_NO_DISPLAY) { auto s = kwinApp()->platform()->sceneEglSurface(); if (s != EGL_NO_SURFACE) { // try a SharingPlatformContext with a created surface return new SharingPlatformContext(context, s, kwinApp()->platform()->sceneEglConfig()); } } return nullptr; } void Integration::initScreens() { QVector newScreens; newScreens.reserve(qMax(screens()->count(), 1)); for (int i = 0; i < screens()->count(); i++) { auto screen = new Screen(i); #if (QT_VERSION >= QT_VERSION_CHECK(5, 13, 0)) QWindowSystemInterface::handleScreenAdded(screen); #else screenAdded(screen); #endif newScreens << screen; } if (newScreens.isEmpty()) { auto dummyScreen = new Screen(-1); #if (QT_VERSION >= QT_VERSION_CHECK(5, 13, 0)) QWindowSystemInterface::handleScreenAdded(dummyScreen); #else screenAdded(dummyScreen); #endif newScreens << dummyScreen; } while (!m_screens.isEmpty()) { #if (QT_VERSION >= QT_VERSION_CHECK(5, 13, 0)) QWindowSystemInterface::handleScreenRemoved(m_screens.takeLast()); #else destroyScreen(m_screens.takeLast()); #endif } m_screens = newScreens; } -KWayland::Client::Compositor *Integration::compositor() const -{ - if (!m_compositor) { - using namespace KWayland::Client; - auto registry = waylandServer()->internalClientRegistry(); - const auto c = registry->interface(Registry::Interface::Compositor); - if (c.name != 0u) { - const_cast(this)->m_compositor = registry->createCompositor(c.name, c.version, registry); - } - } - return m_compositor; -} - -KWayland::Client::Shell *Integration::shell() const -{ - if (!m_shell) { - using namespace KWayland::Client; - auto registry = waylandServer()->internalClientRegistry(); - const auto s = registry->interface(Registry::Interface::Shell); - if (s.name != 0u) { - const_cast(this)->m_shell = registry->createShell(s.name, s.version, registry); - } - } - return m_shell; -} - -EGLDisplay Integration::eglDisplay() const -{ - return m_eglDisplay; -} - QPlatformInputContext *Integration::inputContext() const { return m_inputContext.data(); } } } diff --git a/plugins/qpa/integration.h b/plugins/qpa/integration.h index 6e4cc1059..c04e2fa3c 100644 --- a/plugins/qpa/integration.h +++ b/plugins/qpa/integration.h @@ -1,87 +1,71 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2015 Martin Gräßlin +Copyright (C) 2019 Vlad Zagorodniy 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 . *********************************************************************/ #ifndef KWIN_QPA_INTEGRATION_H #define KWIN_QPA_INTEGRATION_H #include #include "fixqopengl.h" #include #include #include -namespace KWayland -{ -namespace Client -{ -class Compositor; -class Shell; -} -} - namespace KWin { namespace QPA { class Screen; class Integration : public QObject, public QPlatformIntegration { Q_OBJECT public: explicit Integration(); ~Integration() override; bool hasCapability(Capability cap) const override; QPlatformWindow *createPlatformWindow(QWindow *window) const override; QPlatformOffscreenSurface *createPlatformOffscreenSurface(QOffscreenSurface *surface) const override; QPlatformBackingStore *createPlatformBackingStore(QWindow *window) const override; QAbstractEventDispatcher *createEventDispatcher() const override; QPlatformFontDatabase *fontDatabase() const override; QStringList themeNames() const override; QPlatformTheme *createPlatformTheme(const QString &name) const override; - QPlatformNativeInterface *nativeInterface() const override; QPlatformOpenGLContext *createPlatformOpenGLContext(QOpenGLContext *context) const override; - - void initialize() override; QPlatformInputContext *inputContext() const override; - KWayland::Client::Compositor *compositor() const; - EGLDisplay eglDisplay() const; + void initialize() override; private: void initScreens(); - KWayland::Client::Shell *shell() const; QPlatformFontDatabase *m_fontDb; QPlatformNativeInterface *m_nativeInterface; - KWayland::Client::Compositor *m_compositor = nullptr; - KWayland::Client::Shell *m_shell = nullptr; - EGLDisplay m_eglDisplay = EGL_NO_DISPLAY; Screen *m_dummyScreen = nullptr; QScopedPointer m_inputContext; QVector m_screens; }; } } #endif diff --git a/plugins/qpa/nativeinterface.cpp b/plugins/qpa/nativeinterface.cpp deleted file mode 100644 index 26596e3bb..000000000 --- a/plugins/qpa/nativeinterface.cpp +++ /dev/null @@ -1,106 +0,0 @@ -/******************************************************************** - KWin - the KDE window manager - This file is part of the KDE project. - -Copyright (C) 2015 Martin Gräßlin - -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 . -*********************************************************************/ -#include "nativeinterface.h" -#include "integration.h" -#include "window.h" -#include "../../wayland_server.h" - -#include -#include -#include - -#include - -namespace KWin -{ -namespace QPA -{ - -static const QByteArray s_displayKey = QByteArrayLiteral("display"); -static const QByteArray s_wlDisplayKey = QByteArrayLiteral("wl_display"); -static const QByteArray s_compositorKey = QByteArrayLiteral("compositor"); -static const QByteArray s_surfaceKey = QByteArrayLiteral("surface"); - -NativeInterface::NativeInterface(Integration *integration) - : QPlatformNativeInterface() - , m_integration(integration) -{ -} - -void *NativeInterface::nativeResourceForIntegration(const QByteArray &resource) -{ - const QByteArray r = resource.toLower(); - if (r == s_displayKey || r == s_wlDisplayKey) { - if (!waylandServer() || !waylandServer()->internalClientConection()) { - return nullptr; - } - return waylandServer()->internalClientConection()->display(); - } - if (r == s_compositorKey) { - return static_cast(*m_integration->compositor()); - } - return nullptr; -} - -void *NativeInterface::nativeResourceForWindow(const QByteArray &resource, QWindow *window) -{ - const QByteArray r = resource.toLower(); - if (r == s_displayKey || r == s_wlDisplayKey) { - if (!waylandServer() || !waylandServer()->internalClientConection()) { - return nullptr; - } - return waylandServer()->internalClientConection()->display(); - } - if (r == s_compositorKey) { - return static_cast(*m_integration->compositor()); - } - if (r == s_surfaceKey && window) { - if (auto handle = window->handle()) { - if (auto surface = static_cast(handle)->surface()) { - return static_cast(*surface); - } - } - } - return nullptr; -} - -static void roundtrip() -{ - if (!waylandServer()) { - return; - } - auto c = waylandServer()->internalClientConection(); - if (!c) { - return; - } - c->flush(); - waylandServer()->dispatch(); -} - -QFunctionPointer NativeInterface::platformFunction(const QByteArray &function) const -{ - if (qstrcmp(function.toLower(), "roundtrip") == 0) { - return &roundtrip; - } - return nullptr; -} - -} -} diff --git a/plugins/qpa/nativeinterface.h b/plugins/qpa/nativeinterface.h deleted file mode 100644 index 4a9f8e398..000000000 --- a/plugins/qpa/nativeinterface.h +++ /dev/null @@ -1,47 +0,0 @@ -/******************************************************************** - KWin - the KDE window manager - This file is part of the KDE project. - -Copyright (C) 2015 Martin Gräßlin - -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 . -*********************************************************************/ -#ifndef KWIN_QPA_NATIVEINTERFACE_H -#define KWIN_QPA_NATIVEINTERFACE_H - -#include - -namespace KWin -{ -namespace QPA -{ - -class Integration; - -class NativeInterface : public QPlatformNativeInterface -{ -public: - explicit NativeInterface(Integration *integration); - void *nativeResourceForIntegration(const QByteArray &resource) override; - void *nativeResourceForWindow(const QByteArray &resourceString, QWindow *window) override; - QFunctionPointer platformFunction(const QByteArray &function) const override; - -private: - Integration *m_integration; -}; - -} -} - -#endif diff --git a/plugins/qpa/sharingplatformcontext.cpp b/plugins/qpa/sharingplatformcontext.cpp index 01b772b51..0c5463e29 100644 --- a/plugins/qpa/sharingplatformcontext.cpp +++ b/plugins/qpa/sharingplatformcontext.cpp @@ -1,124 +1,126 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2015 Martin Gräßlin 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 . *********************************************************************/ #include "sharingplatformcontext.h" #include "offscreensurface.h" #include "window.h" + +#include "../../internal_client.h" +#include "../../main.h" #include "../../platform.h" -#include "../../shell_client.h" + #include #include #include namespace KWin { namespace QPA { SharingPlatformContext::SharingPlatformContext(QOpenGLContext *context) : SharingPlatformContext(context, EGL_NO_SURFACE) { } SharingPlatformContext::SharingPlatformContext(QOpenGLContext *context, const EGLSurface &surface, EGLConfig config) : AbstractPlatformContext(context, kwinApp()->platform()->sceneEglDisplay(), config) , m_surface(surface) { create(); } bool SharingPlatformContext::makeCurrent(QPlatformSurface *surface) { EGLSurface eglSurface; if (surface->surface()->surfaceClass() == QSurface::Window) { eglSurface = m_surface; } else { eglSurface = static_cast(surface)->nativeHandle(); } const bool ok = eglMakeCurrent(eglDisplay(), eglSurface, eglSurface, eglContext()); if (!ok) { qCWarning(KWIN_QPA, "eglMakeCurrent failed: %x", eglGetError()); return false; } if (surface->surface()->surfaceClass() == QSurface::Window) { // QOpenGLContextPrivate::setCurrentContext will be called after this // method returns, but that's too late, as we need a current context in // order to bind the content framebuffer object. QOpenGLContextPrivate::setCurrentContext(context()); Window *window = static_cast(surface); window->bindContentFBO(); } return true; } bool SharingPlatformContext::isSharing() const { return false; } void SharingPlatformContext::swapBuffers(QPlatformSurface *surface) { if (surface->surface()->surfaceClass() == QSurface::Window) { Window *window = static_cast(surface); - auto c = window->shellClient(); - if (!c) { - qCDebug(KWIN_QPA) << "SwapBuffers called but there is no ShellClient"; + InternalClient *client = window->client(); + if (!client) { return; } context()->makeCurrent(surface->surface()); glFlush(); - c->setInternalFramebufferObject(window->swapFBO()); + client->present(window->swapFBO()); window->bindContentFBO(); } } GLuint SharingPlatformContext::defaultFramebufferObject(QPlatformSurface *surface) const { if (Window *window = dynamic_cast(surface)) { const auto &fbo = window->contentFBO(); if (!fbo.isNull()) { return fbo->handle(); } qCDebug(KWIN_QPA) << "No default framebuffer object for internal window"; } return 0; } void SharingPlatformContext::create() { if (config() == 0) { qCWarning(KWIN_QPA) << "Did not get an EGL config"; return; } if (!bindApi()) { qCWarning(KWIN_QPA) << "Could not bind API."; return; } createContext(kwinApp()->platform()->sceneEglContext()); } } } diff --git a/plugins/qpa/window.cpp b/plugins/qpa/window.cpp index 1d33c2ccf..d05616a74 100644 --- a/plugins/qpa/window.cpp +++ b/plugins/qpa/window.cpp @@ -1,167 +1,158 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2015 Martin Gräßlin +Copyright (C) 2019 Vlad Zagorodniy 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 . *********************************************************************/ #include "window.h" -#include "integration.h" #include "screens.h" -#include "../../shell_client.h" -#include "../../wayland_server.h" + +#include "internal_client.h" + #include #include #include -#include -#include -#include -#include - namespace KWin { namespace QPA { static quint32 s_windowId = 0; -Window::Window(QWindow *window, KWayland::Client::Surface *surface, KWayland::Client::ShellSurface *shellSurface, const Integration *integration) +Window::Window(QWindow *window) : QPlatformWindow(window) - , m_surface(surface) - , m_shellSurface(shellSurface) , m_windowId(++s_windowId) - , m_integration(integration) , m_scale(screens()->maxScale()) { - m_surface->setScale(m_scale); - - QObject::connect(m_surface, &QObject::destroyed, window, [this] { m_surface = nullptr;}); - QObject::connect(m_shellSurface, &QObject::destroyed, window, [this] { m_shellSurface = nullptr;}); - waylandServer()->internalClientConection()->flush(); } Window::~Window() { unmap(); - delete m_shellSurface; - delete m_surface; -} - -WId Window::winId() const -{ - return m_windowId; } void Window::setVisible(bool visible) { - if (!visible) { + if (visible) { + map(); + } else { unmap(); } + QPlatformWindow::setVisible(visible); } void Window::setGeometry(const QRect &rect) { const QRect &oldRect = geometry(); QPlatformWindow::setGeometry(rect); if (rect.x() != oldRect.x()) { emit window()->xChanged(rect.x()); } if (rect.y() != oldRect.y()) { emit window()->yChanged(rect.y()); } if (rect.width() != oldRect.width()) { emit window()->widthChanged(rect.width()); } if (rect.height() != oldRect.height()) { emit window()->heightChanged(rect.height()); } const QSize nativeSize = rect.size() * m_scale; if (m_contentFBO) { if (m_contentFBO->size() != nativeSize) { m_resized = true; } } QWindowSystemInterface::handleGeometryChange(window(), geometry()); } -void Window::unmap() +WId Window::winId() const { - if (m_shellClient) { - m_shellClient->setInternalFramebufferObject(QSharedPointer()); - } - if (m_surface) { - m_surface->attachBuffer(KWayland::Client::Buffer::Ptr()); - m_surface->commit(KWayland::Client::Surface::CommitFlag::None); - } - if (waylandServer()->internalClientConection()) { - waylandServer()->internalClientConection()->flush(); - } + return m_windowId; +} + +qreal Window::devicePixelRatio() const +{ + return m_scale; } void Window::bindContentFBO() { if (m_resized || !m_contentFBO) { createFBO(); } m_contentFBO->bind(); } +const QSharedPointer &Window::contentFBO() const +{ + return m_contentFBO; +} + QSharedPointer Window::swapFBO() { - auto fbo = m_contentFBO; + QSharedPointer fbo = m_contentFBO; m_contentFBO.clear(); - m_surface->commit(KWayland::Client::Surface::CommitFlag::None); return fbo; } +InternalClient *Window::client() const +{ + return m_handle; +} + void Window::createFBO() { const QRect &r = geometry(); if (m_contentFBO && r.size().isEmpty()) { return; } const QSize nativeSize = r.size() * m_scale; m_contentFBO.reset(new QOpenGLFramebufferObject(nativeSize.width(), nativeSize.height(), QOpenGLFramebufferObject::CombinedDepthStencil)); if (!m_contentFBO->isValid()) { qCWarning(KWIN_QPA) << "Content FBO is not valid"; } m_resized = false; } -ShellClient *Window::shellClient() +void Window::map() { - if (!m_shellClient) { - waylandServer()->dispatch(); - m_shellClient = waylandServer()->findClient(window()); + if (m_handle) { + return; } - return m_shellClient; -} -int Window::scale() const -{ - return m_scale; + m_handle = new InternalClient(window()); } -qreal Window::devicePixelRatio() const +void Window::unmap() { - return m_scale; + if (!m_handle) { + return; + } + + m_handle->destroyClient(); + m_handle = nullptr; + + m_contentFBO = nullptr; } } } diff --git a/plugins/qpa/window.h b/plugins/qpa/window.h index 12f8fa0e5..db8215379 100644 --- a/plugins/qpa/window.h +++ b/plugins/qpa/window.h @@ -1,92 +1,68 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2015 Martin Gräßlin +Copyright (C) 2019 Vlad Zagorodniy 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 . *********************************************************************/ #ifndef KWIN_QPA_WINDOW_H #define KWIN_QPA_WINDOW_H -#include -#include "fixqopengl.h" - -#include #include class QOpenGLFramebufferObject; - -namespace KWayland -{ -namespace Client -{ -class Surface; -class ShellSurface; -} -} - namespace KWin { -class ShellClient; +class InternalClient; namespace QPA { -class Integration; - class Window : public QPlatformWindow { public: - explicit Window(QWindow *window, KWayland::Client::Surface *surface, KWayland::Client::ShellSurface *shellSurface, const Integration *integration); + explicit Window(QWindow *window); ~Window() override; void setVisible(bool visible) override; void setGeometry(const QRect &rect) override; WId winId() const override; - - KWayland::Client::Surface *surface() const { - return m_surface; - } - - int scale() const; qreal devicePixelRatio() const override; void bindContentFBO(); - const QSharedPointer &contentFBO() const { - return m_contentFBO; - } + const QSharedPointer &contentFBO() const; QSharedPointer swapFBO(); - ShellClient *shellClient(); + + InternalClient *client() const; private: - void unmap(); void createFBO(); + void map(); + void unmap(); - KWayland::Client::Surface *m_surface; - KWayland::Client::ShellSurface *m_shellSurface; + InternalClient *m_handle = nullptr; QSharedPointer m_contentFBO; - bool m_resized = false; - ShellClient *m_shellClient = nullptr; quint32 m_windowId; - const Integration *m_integration; + bool m_resized = false; int m_scale = 1; }; } } #endif diff --git a/plugins/scenes/opengl/scene_opengl.cpp b/plugins/scenes/opengl/scene_opengl.cpp index b253ee9c9..5d57f07a4 100644 --- a/plugins/scenes/opengl/scene_opengl.cpp +++ b/plugins/scenes/opengl/scene_opengl.cpp @@ -1,2593 +1,2613 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2006 Lubos Lunak Copyright (C) 2009, 2010, 2011 Martin Gräßlin Based on glcompmgr code by Felix Bellaby. Using code from Compiz and Beryl. Explicit command stream synchronization based on the sample implementation by James Jones , Copyright © 2011 NVIDIA Corporation 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 . *********************************************************************/ #include "scene_opengl.h" #include "platform.h" #include "wayland_server.h" #include "platformsupport/scenes/opengl/texture.h" #include #include "utils.h" #include "client.h" #include "composite.h" #include "deleted.h" #include "effects.h" #include "lanczosfilter.h" #include "main.h" #include "overlaywindow.h" #include "screens.h" #include "cursor.h" #include "decorations/decoratedclient.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // HACK: workaround for libepoxy < 1.3 #ifndef GL_GUILTY_CONTEXT_RESET #define GL_GUILTY_CONTEXT_RESET 0x8253 #endif #ifndef GL_INNOCENT_CONTEXT_RESET #define GL_INNOCENT_CONTEXT_RESET 0x8254 #endif #ifndef GL_UNKNOWN_CONTEXT_RESET #define GL_UNKNOWN_CONTEXT_RESET 0x8255 #endif namespace KWin { extern int currentRefreshRate(); /** * SyncObject represents a fence used to synchronize operations in * the kwin command stream with operations in the X command stream. */ class SyncObject { public: enum State { Ready, TriggerSent, Waiting, Done, Resetting }; SyncObject(); ~SyncObject(); State state() const { return m_state; } void trigger(); void wait(); bool finish(); void reset(); void finishResetting(); private: State m_state; GLsync m_sync; xcb_sync_fence_t m_fence; xcb_get_input_focus_cookie_t m_reset_cookie; }; SyncObject::SyncObject() { m_state = Ready; xcb_connection_t * const c = connection(); m_fence = xcb_generate_id(c); xcb_sync_create_fence(c, rootWindow(), m_fence, false); xcb_flush(c); m_sync = glImportSyncEXT(GL_SYNC_X11_FENCE_EXT, m_fence, 0); } SyncObject::~SyncObject() { // If glDeleteSync is called before the xcb fence is signalled // the nvidia driver (the only one to implement GL_SYNC_X11_FENCE_EXT) // deadlocks waiting for the fence to be signalled. // To avoid this, make sure the fence is signalled before // deleting the sync. if (m_state == Resetting || m_state == Ready){ trigger(); // The flush is necessary! // The trigger command needs to be sent to the X server. xcb_flush(connection()); } xcb_sync_destroy_fence(connection(), m_fence); glDeleteSync(m_sync); if (m_state == Resetting) xcb_discard_reply(connection(), m_reset_cookie.sequence); } void SyncObject::trigger() { Q_ASSERT(m_state == Ready || m_state == Resetting); // Finish resetting the fence if necessary if (m_state == Resetting) finishResetting(); xcb_sync_trigger_fence(connection(), m_fence); m_state = TriggerSent; } void SyncObject::wait() { if (m_state != TriggerSent) return; glWaitSync(m_sync, 0, GL_TIMEOUT_IGNORED); m_state = Waiting; } bool SyncObject::finish() { if (m_state == Done) return true; // Note: It is possible that we never inserted a wait for the fence. // This can happen if we ended up not rendering the damaged // window because it is fully occluded. Q_ASSERT(m_state == TriggerSent || m_state == Waiting); // Check if the fence is signaled GLint value; glGetSynciv(m_sync, GL_SYNC_STATUS, 1, nullptr, &value); if (value != GL_SIGNALED) { qCDebug(KWIN_OPENGL) << "Waiting for X fence to finish"; // Wait for the fence to become signaled with a one second timeout const GLenum result = glClientWaitSync(m_sync, 0, 1000000000); switch (result) { case GL_TIMEOUT_EXPIRED: qCWarning(KWIN_OPENGL) << "Timeout while waiting for X fence"; return false; case GL_WAIT_FAILED: qCWarning(KWIN_OPENGL) << "glClientWaitSync() failed"; return false; } } m_state = Done; return true; } void SyncObject::reset() { Q_ASSERT(m_state == Done); xcb_connection_t * const c = connection(); // Send the reset request along with a sync request. // We use the cookie to ensure that the server has processed the reset // request before we trigger the fence and call glWaitSync(). // Otherwise there is a race condition between the reset finishing and // the glWaitSync() call. xcb_sync_reset_fence(c, m_fence); m_reset_cookie = xcb_get_input_focus(c); xcb_flush(c); m_state = Resetting; } void SyncObject::finishResetting() { Q_ASSERT(m_state == Resetting); free(xcb_get_input_focus_reply(connection(), m_reset_cookie, nullptr)); m_state = Ready; } // ----------------------------------------------------------------------- /** * SyncManager manages a set of fences used for explicit synchronization * with the X command stream. */ class SyncManager { public: enum { MaxFences = 4 }; SyncManager(); ~SyncManager(); SyncObject *nextFence(); bool updateFences(); private: std::array m_fences; int m_next; }; SyncManager::SyncManager() : m_next(0) { } SyncManager::~SyncManager() { } SyncObject *SyncManager::nextFence() { SyncObject *fence = &m_fences[m_next]; m_next = (m_next + 1) % MaxFences; return fence; } bool SyncManager::updateFences() { for (int i = 0; i < qMin(2, MaxFences - 1); i++) { const int index = (m_next + i) % MaxFences; SyncObject &fence = m_fences[index]; switch (fence.state()) { case SyncObject::Ready: break; case SyncObject::TriggerSent: case SyncObject::Waiting: if (!fence.finish()) return false; fence.reset(); break; // Should not happen in practice since we always reset the fence // after finishing it case SyncObject::Done: fence.reset(); break; case SyncObject::Resetting: fence.finishResetting(); break; } } return true; } // ----------------------------------------------------------------------- /************************************************ * SceneOpenGL ***********************************************/ SceneOpenGL::SceneOpenGL(OpenGLBackend *backend, QObject *parent) : Scene(parent) , init_ok(true) , m_backend(backend) , m_syncManager(nullptr) , m_currentFence(nullptr) { if (m_backend->isFailed()) { init_ok = false; return; } if (!viewportLimitsMatched(screens()->size())) return; // perform Scene specific checks GLPlatform *glPlatform = GLPlatform::instance(); if (!glPlatform->isGLES() && !hasGLExtension(QByteArrayLiteral("GL_ARB_texture_non_power_of_two")) && !hasGLExtension(QByteArrayLiteral("GL_ARB_texture_rectangle"))) { qCCritical(KWIN_OPENGL) << "GL_ARB_texture_non_power_of_two and GL_ARB_texture_rectangle missing"; init_ok = false; return; // error } if (glPlatform->isMesaDriver() && glPlatform->mesaVersion() < kVersionNumber(10, 0)) { qCCritical(KWIN_OPENGL) << "KWin requires at least Mesa 10.0 for OpenGL compositing."; init_ok = false; return; } if (!glPlatform->isGLES() && !m_backend->isSurfaceLessContext()) { glDrawBuffer(GL_BACK); } m_debug = qstrcmp(qgetenv("KWIN_GL_DEBUG"), "1") == 0; initDebugOutput(); // set strict binding if (options->isGlStrictBindingFollowsDriver()) { options->setGlStrictBinding(!glPlatform->supports(LooseBinding)); } bool haveSyncObjects = glPlatform->isGLES() ? hasGLVersion(3, 0) : hasGLVersion(3, 2) || hasGLExtension("GL_ARB_sync"); if (hasGLExtension("GL_EXT_x11_sync_object") && haveSyncObjects && kwinApp()->operationMode() == Application::OperationModeX11) { const QByteArray useExplicitSync = qgetenv("KWIN_EXPLICIT_SYNC"); if (useExplicitSync != "0") { qCDebug(KWIN_OPENGL) << "Initializing fences for synchronization with the X command stream"; m_syncManager = new SyncManager; } else { qCDebug(KWIN_OPENGL) << "Explicit synchronization with the X command stream disabled by environment variable"; } } } static SceneOpenGL *gs_debuggedScene = nullptr; SceneOpenGL::~SceneOpenGL() { // do cleanup after initBuffer() gs_debuggedScene = nullptr; if (init_ok) { makeOpenGLContextCurrent(); } SceneOpenGL::EffectFrame::cleanup(); delete m_syncManager; // backend might be still needed for a different scene delete m_backend; } static void scheduleVboReInit() { if (!gs_debuggedScene) return; static QPointer timer; if (!timer) { delete timer; timer = new QTimer(gs_debuggedScene); timer->setSingleShot(true); QObject::connect(timer.data(), &QTimer::timeout, gs_debuggedScene, []() { GLVertexBuffer::cleanup(); GLVertexBuffer::initStatic(); }); } timer->start(250); } void SceneOpenGL::initDebugOutput() { const bool have_KHR_debug = hasGLExtension(QByteArrayLiteral("GL_KHR_debug")); const bool have_ARB_debug = hasGLExtension(QByteArrayLiteral("GL_ARB_debug_output")); if (!have_KHR_debug && !have_ARB_debug) return; if (!have_ARB_debug) { // if we don't have ARB debug, but only KHR debug we need to verify whether the context is a debug context // it should work without as well, but empirical tests show: no it doesn't if (GLPlatform::instance()->isGLES()) { if (!hasGLVersion(3, 2)) { // empirical data shows extension doesn't work return; } } else if (!hasGLVersion(3, 0)) { return; } // can only be queried with either OpenGL >= 3.0 or OpenGL ES of at least 3.1 GLint value = 0; glGetIntegerv(GL_CONTEXT_FLAGS, &value); if (!(value & GL_CONTEXT_FLAG_DEBUG_BIT)) { return; } } gs_debuggedScene = this; // Set the callback function auto callback = [](GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar *message, const GLvoid *userParam) { Q_UNUSED(source) Q_UNUSED(severity) Q_UNUSED(userParam) while (message[length] == '\n' || message[length] == '\r') --length; switch (type) { case GL_DEBUG_TYPE_ERROR: case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR: qCWarning(KWIN_OPENGL, "%#x: %.*s", id, length, message); break; case GL_DEBUG_TYPE_OTHER: // at least the nvidia driver seems prone to end up with invalid VBOs after // transferring them between system heap and VRAM // so we re-init them whenever this happens (typically when switching VT, resuming // from STR and XRandR events - #344326 if (strstr(message, "Buffer detailed info:") && strstr(message, "has been updated")) scheduleVboReInit(); // fall through! for general message printing Q_FALLTHROUGH(); case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR: case GL_DEBUG_TYPE_PORTABILITY: case GL_DEBUG_TYPE_PERFORMANCE: default: qCDebug(KWIN_OPENGL, "%#x: %.*s", id, length, message); break; } }; glDebugMessageCallback(callback, nullptr); // This state exists only in GL_KHR_debug if (have_KHR_debug) glEnable(GL_DEBUG_OUTPUT); #if !defined(QT_NO_DEBUG) // Enable all debug messages glDebugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, 0, nullptr, GL_TRUE); #else // Enable error messages glDebugMessageControl(GL_DONT_CARE, GL_DEBUG_TYPE_ERROR, GL_DONT_CARE, 0, nullptr, GL_TRUE); glDebugMessageControl(GL_DONT_CARE, GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR, GL_DONT_CARE, 0, nullptr, GL_TRUE); #endif // Insert a test message const QByteArray message = QByteArrayLiteral("OpenGL debug output initialized"); glDebugMessageInsert(GL_DEBUG_SOURCE_APPLICATION, GL_DEBUG_TYPE_OTHER, 0, GL_DEBUG_SEVERITY_LOW, message.length(), message.constData()); } SceneOpenGL *SceneOpenGL::createScene(QObject *parent) { OpenGLBackend *backend = kwinApp()->platform()->createOpenGLBackend(); if (!backend) { return nullptr; } if (!backend->isFailed()) { backend->init(); } if (backend->isFailed()) { delete backend; return nullptr; } SceneOpenGL *scene = nullptr; // first let's try an OpenGL 2 scene if (SceneOpenGL2::supported(backend)) { scene = new SceneOpenGL2(backend, parent); if (scene->initFailed()) { delete scene; scene = nullptr; } else { return scene; } } if (!scene) { if (GLPlatform::instance()->recommendedCompositor() == XRenderCompositing) { qCCritical(KWIN_OPENGL) << "OpenGL driver recommends XRender based compositing. Falling back to XRender."; qCCritical(KWIN_OPENGL) << "To overwrite the detection use the environment variable KWIN_COMPOSE"; qCCritical(KWIN_OPENGL) << "For more information see https://community.kde.org/KWin/Environment_Variables#KWIN_COMPOSE"; } delete backend; } return scene; } OverlayWindow *SceneOpenGL::overlayWindow() const { return m_backend->overlayWindow(); } bool SceneOpenGL::syncsToVBlank() const { return m_backend->syncsToVBlank(); } bool SceneOpenGL::blocksForRetrace() const { return m_backend->blocksForRetrace(); } void SceneOpenGL::idle() { m_backend->idle(); Scene::idle(); } bool SceneOpenGL::initFailed() const { return !init_ok; } void SceneOpenGL::handleGraphicsReset(GLenum status) { switch (status) { case GL_GUILTY_CONTEXT_RESET: qCDebug(KWIN_OPENGL) << "A graphics reset attributable to the current GL context occurred."; break; case GL_INNOCENT_CONTEXT_RESET: qCDebug(KWIN_OPENGL) << "A graphics reset not attributable to the current GL context occurred."; break; case GL_UNKNOWN_CONTEXT_RESET: qCDebug(KWIN_OPENGL) << "A graphics reset of an unknown cause occurred."; break; default: break; } QElapsedTimer timer; timer.start(); // Wait until the reset is completed or max 10 seconds while (timer.elapsed() < 10000 && glGetGraphicsResetStatus() != GL_NO_ERROR) usleep(50); qCDebug(KWIN_OPENGL) << "Attempting to reset compositing."; QMetaObject::invokeMethod(this, "resetCompositing", Qt::QueuedConnection); KNotification::event(QStringLiteral("graphicsreset"), i18n("Desktop effects were restarted due to a graphics reset")); } void SceneOpenGL::triggerFence() { if (m_syncManager) { m_currentFence = m_syncManager->nextFence(); m_currentFence->trigger(); } } void SceneOpenGL::insertWait() { if (m_currentFence && m_currentFence->state() != SyncObject::Waiting) { m_currentFence->wait(); } } /** * Render cursor texture in case hardware cursor is disabled. * Useful for screen recording apps or backends that can't do planes. */ void SceneOpenGL2::paintCursor() { // don't paint if we use hardware cursor or the cursor is hidden if (!kwinApp()->platform()->usesSoftwareCursor() || kwinApp()->platform()->isCursorHidden() || kwinApp()->platform()->softwareCursor().isNull()) { return; } // lazy init texture cursor only in case we need software rendering if (!m_cursorTexture) { auto updateCursorTexture = [this] { // don't paint if no image for cursor is set const QImage img = kwinApp()->platform()->softwareCursor(); if (img.isNull()) { return; } m_cursorTexture.reset(new GLTexture(img)); }; // init now updateCursorTexture(); // handle shape update on case cursor image changed connect(kwinApp()->platform(), &Platform::cursorChanged, this, updateCursorTexture); } // get cursor position in projection coordinates const QPoint cursorPos = Cursor::pos() - kwinApp()->platform()->softwareCursorHotspot(); const QRect cursorRect(0, 0, m_cursorTexture->width(), m_cursorTexture->height()); QMatrix4x4 mvp = m_projectionMatrix; mvp.translate(cursorPos.x(), cursorPos.y()); // handle transparence glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); // paint texture in cursor offset m_cursorTexture->bind(); ShaderBinder binder(ShaderTrait::MapTexture); binder.shader()->setUniform(GLShader::ModelViewProjectionMatrix, mvp); m_cursorTexture->render(QRegion(cursorRect), cursorRect); m_cursorTexture->unbind(); kwinApp()->platform()->markCursorAsRendered(); glDisable(GL_BLEND); } qint64 SceneOpenGL::paint(QRegion damage, ToplevelList toplevels) { // actually paint the frame, flushed with the NEXT frame createStackingOrder(toplevels); // After this call, updateRegion will contain the damaged region in the // back buffer. This is the region that needs to be posted to repair // the front buffer. It doesn't include the additional damage returned // by prepareRenderingFrame(). validRegion is the region that has been // repainted, and may be larger than updateRegion. QRegion updateRegion, validRegion; if (m_backend->perScreenRendering()) { // trigger start render timer m_backend->prepareRenderingFrame(); for (int i = 0; i < screens()->count(); ++i) { const QRect &geo = screens()->geometry(i); QRegion update; QRegion valid; // prepare rendering makes context current on the output QRegion repaint = m_backend->prepareRenderingForScreen(i); GLVertexBuffer::setVirtualScreenGeometry(geo); GLRenderTarget::setVirtualScreenGeometry(geo); GLVertexBuffer::setVirtualScreenScale(screens()->scale(i)); GLRenderTarget::setVirtualScreenScale(screens()->scale(i)); const GLenum status = glGetGraphicsResetStatus(); if (status != GL_NO_ERROR) { handleGraphicsReset(status); return 0; } int mask = 0; updateProjectionMatrix(); paintScreen(&mask, damage.intersected(geo), repaint, &update, &valid, projectionMatrix(), geo); // call generic implementation paintCursor(); GLVertexBuffer::streamingBuffer()->endOfFrame(); m_backend->endRenderingFrameForScreen(i, valid, update); GLVertexBuffer::streamingBuffer()->framePosted(); } } else { m_backend->makeCurrent(); QRegion repaint = m_backend->prepareRenderingFrame(); const GLenum status = glGetGraphicsResetStatus(); if (status != GL_NO_ERROR) { handleGraphicsReset(status); return 0; } GLVertexBuffer::setVirtualScreenGeometry(screens()->geometry()); GLRenderTarget::setVirtualScreenGeometry(screens()->geometry()); GLVertexBuffer::setVirtualScreenScale(1); GLRenderTarget::setVirtualScreenScale(1); int mask = 0; updateProjectionMatrix(); paintScreen(&mask, damage, repaint, &updateRegion, &validRegion, projectionMatrix()); // call generic implementation if (!GLPlatform::instance()->isGLES()) { const QSize &screenSize = screens()->size(); const QRegion displayRegion(0, 0, screenSize.width(), screenSize.height()); // copy dirty parts from front to backbuffer if (!m_backend->supportsBufferAge() && options->glPreferBufferSwap() == Options::CopyFrontBuffer && validRegion != displayRegion) { glReadBuffer(GL_FRONT); m_backend->copyPixels(displayRegion - validRegion); glReadBuffer(GL_BACK); validRegion = displayRegion; } } GLVertexBuffer::streamingBuffer()->endOfFrame(); m_backend->endRenderingFrame(validRegion, updateRegion); GLVertexBuffer::streamingBuffer()->framePosted(); } if (m_currentFence) { if (!m_syncManager->updateFences()) { qCDebug(KWIN_OPENGL) << "Aborting explicit synchronization with the X command stream."; qCDebug(KWIN_OPENGL) << "Future frames will be rendered unsynchronized."; delete m_syncManager; m_syncManager = nullptr; } m_currentFence = nullptr; } // do cleanup clearStackingOrder(); return m_backend->renderTime(); } QMatrix4x4 SceneOpenGL::transformation(int mask, const ScreenPaintData &data) const { QMatrix4x4 matrix; if (!(mask & PAINT_SCREEN_TRANSFORMED)) return matrix; matrix.translate(data.translation()); data.scale().applyTo(&matrix); if (data.rotationAngle() == 0.0) return matrix; // Apply the rotation // cannot use data.rotation->applyTo(&matrix) as QGraphicsRotation uses projectedRotate to map back to 2D matrix.translate(data.rotationOrigin()); const QVector3D axis = data.rotationAxis(); matrix.rotate(data.rotationAngle(), axis.x(), axis.y(), axis.z()); matrix.translate(-data.rotationOrigin()); return matrix; } void SceneOpenGL::paintBackground(QRegion region) { PaintClipper pc(region); if (!PaintClipper::clip()) { glClearColor(0, 0, 0, 1); glClear(GL_COLOR_BUFFER_BIT); return; } if (pc.clip() && pc.paintArea().isEmpty()) return; // no background to paint QVector verts; for (PaintClipper::Iterator iterator; !iterator.isDone(); iterator.next()) { QRect r = iterator.boundingRect(); verts << r.x() + r.width() << r.y(); verts << r.x() << r.y(); verts << r.x() << r.y() + r.height(); verts << r.x() << r.y() + r.height(); verts << r.x() + r.width() << r.y() + r.height(); verts << r.x() + r.width() << r.y(); } doPaintBackground(verts); } void SceneOpenGL::extendPaintRegion(QRegion ®ion, bool opaqueFullscreen) { if (m_backend->supportsBufferAge()) return; const QSize &screenSize = screens()->size(); if (options->glPreferBufferSwap() == Options::ExtendDamage) { // only Extend "large" repaints const QRegion displayRegion(0, 0, screenSize.width(), screenSize.height()); uint damagedPixels = 0; const uint fullRepaintLimit = (opaqueFullscreen?0.49f:0.748f)*screenSize.width()*screenSize.height(); // 16:9 is 75% of 4:3 and 2.55:1 is 49.01% of 5:4 // (5:4 is the most square format and 2.55:1 is Cinemascope55 - the widest ever shot // movie aspect - two times ;-) It's a Fox format, though, so maybe we want to restrict // to 2.20:1 - Panavision - which has actually been used for interesting movies ...) // would be 57% of 5/4 for (const QRect &r : region) { // damagedPixels += r.width() * r.height(); // combined window damage test damagedPixels = r.width() * r.height(); // experimental single window damage testing if (damagedPixels > fullRepaintLimit) { region = displayRegion; return; } } } else if (options->glPreferBufferSwap() == Options::PaintFullScreen) { // forced full rePaint region = QRegion(0, 0, screenSize.width(), screenSize.height()); } } SceneOpenGLTexture *SceneOpenGL::createTexture() { return new SceneOpenGLTexture(m_backend); } bool SceneOpenGL::viewportLimitsMatched(const QSize &size) const { if (kwinApp()->operationMode() != Application::OperationModeX11) { // TODO: On Wayland we can't suspend. Find a solution that works here as well! return true; } GLint limit[2]; glGetIntegerv(GL_MAX_VIEWPORT_DIMS, limit); if (limit[0] < size.width() || limit[1] < size.height()) { QMetaObject::invokeMethod(static_cast(Compositor::self()), "suspend", Qt::QueuedConnection, Q_ARG(X11Compositor::SuspendReason, X11Compositor::AllReasonSuspend)); return false; } return true; } void SceneOpenGL::screenGeometryChanged(const QSize &size) { if (!viewportLimitsMatched(size)) return; Scene::screenGeometryChanged(size); glViewport(0,0, size.width(), size.height()); m_backend->screenGeometryChanged(size); GLRenderTarget::setVirtualScreenSize(size); } void SceneOpenGL::paintDesktop(int desktop, int mask, const QRegion ®ion, ScreenPaintData &data) { const QRect r = region.boundingRect(); glEnable(GL_SCISSOR_TEST); glScissor(r.x(), screens()->size().height() - r.y() - r.height(), r.width(), r.height()); KWin::Scene::paintDesktop(desktop, mask, region, data); glDisable(GL_SCISSOR_TEST); } bool SceneOpenGL::makeOpenGLContextCurrent() { return m_backend->makeCurrent(); } void SceneOpenGL::doneOpenGLContextCurrent() { m_backend->doneCurrent(); } Scene::EffectFrame *SceneOpenGL::createEffectFrame(EffectFrameImpl *frame) { return new SceneOpenGL::EffectFrame(frame, this); } Shadow *SceneOpenGL::createShadow(Toplevel *toplevel) { return new SceneOpenGLShadow(toplevel); } Decoration::Renderer *SceneOpenGL::createDecorationRenderer(Decoration::DecoratedClientImpl *impl) { return new SceneOpenGLDecorationRenderer(impl); } bool SceneOpenGL::animationsSupported() const { return !GLPlatform::instance()->isSoftwareEmulation(); } QVector SceneOpenGL::openGLPlatformInterfaceExtensions() const { return m_backend->extensions().toVector(); } //**************************************** // SceneOpenGL2 //**************************************** bool SceneOpenGL2::supported(OpenGLBackend *backend) { const QByteArray forceEnv = qgetenv("KWIN_COMPOSE"); if (!forceEnv.isEmpty()) { if (qstrcmp(forceEnv, "O2") == 0 || qstrcmp(forceEnv, "O2ES") == 0) { qCDebug(KWIN_OPENGL) << "OpenGL 2 compositing enforced by environment variable"; return true; } else { // OpenGL 2 disabled by environment variable return false; } } if (!backend->isDirectRendering()) { return false; } if (GLPlatform::instance()->recommendedCompositor() < OpenGL2Compositing) { qCDebug(KWIN_OPENGL) << "Driver does not recommend OpenGL 2 compositing"; return false; } return true; } SceneOpenGL2::SceneOpenGL2(OpenGLBackend *backend, QObject *parent) : SceneOpenGL(backend, parent) , m_lanczosFilter(nullptr) { if (!init_ok) { // base ctor already failed return; } // We only support the OpenGL 2+ shader API, not GL_ARB_shader_objects if (!hasGLVersion(2, 0)) { qCDebug(KWIN_OPENGL) << "OpenGL 2.0 is not supported"; init_ok = false; return; } const QSize &s = screens()->size(); GLRenderTarget::setVirtualScreenSize(s); GLRenderTarget::setVirtualScreenGeometry(screens()->geometry()); // push one shader on the stack so that one is always bound ShaderManager::instance()->pushShader(ShaderTrait::MapTexture); if (checkGLError("Init")) { qCCritical(KWIN_OPENGL) << "OpenGL 2 compositing setup failed"; init_ok = false; return; // error } // It is not legal to not have a vertex array object bound in a core context if (!GLPlatform::instance()->isGLES() && hasGLExtension(QByteArrayLiteral("GL_ARB_vertex_array_object"))) { glGenVertexArrays(1, &vao); glBindVertexArray(vao); } if (!ShaderManager::instance()->selfTest()) { qCCritical(KWIN_OPENGL) << "ShaderManager self test failed"; init_ok = false; return; } qCDebug(KWIN_OPENGL) << "OpenGL 2 compositing successfully initialized"; init_ok = true; } SceneOpenGL2::~SceneOpenGL2() { if (m_lanczosFilter) { makeOpenGLContextCurrent(); delete m_lanczosFilter; m_lanczosFilter = nullptr; } } QMatrix4x4 SceneOpenGL2::createProjectionMatrix() const { // Create a perspective projection with a 60° field-of-view, // and an aspect ratio of 1.0. const float fovY = 60.0f; const float aspect = 1.0f; const float zNear = 0.1f; const float zFar = 100.0f; const float yMax = zNear * std::tan(fovY * M_PI / 360.0f); const float yMin = -yMax; const float xMin = yMin * aspect; const float xMax = yMax * aspect; QMatrix4x4 projection; projection.frustum(xMin, xMax, yMin, yMax, zNear, zFar); // Create a second matrix that transforms screen coordinates // to world coordinates. const float scaleFactor = 1.1 * std::tan(fovY * M_PI / 360.0f) / yMax; const QSize size = screens()->size(); QMatrix4x4 matrix; matrix.translate(xMin * scaleFactor, yMax * scaleFactor, -1.1); matrix.scale( (xMax - xMin) * scaleFactor / size.width(), -(yMax - yMin) * scaleFactor / size.height(), 0.001); // Combine the matrices return projection * matrix; } void SceneOpenGL2::updateProjectionMatrix() { m_projectionMatrix = createProjectionMatrix(); } void SceneOpenGL2::paintSimpleScreen(int mask, QRegion region) { m_screenProjectionMatrix = m_projectionMatrix; Scene::paintSimpleScreen(mask, region); } void SceneOpenGL2::paintGenericScreen(int mask, ScreenPaintData data) { const QMatrix4x4 screenMatrix = transformation(mask, data); m_screenProjectionMatrix = m_projectionMatrix * screenMatrix; Scene::paintGenericScreen(mask, data); } void SceneOpenGL2::doPaintBackground(const QVector< float >& vertices) { GLVertexBuffer *vbo = GLVertexBuffer::streamingBuffer(); vbo->reset(); vbo->setUseColor(true); vbo->setData(vertices.count() / 2, 2, vertices.data(), nullptr); ShaderBinder binder(ShaderTrait::UniformColor); binder.shader()->setUniform(GLShader::ModelViewProjectionMatrix, m_projectionMatrix); vbo->render(GL_TRIANGLES); } Scene::Window *SceneOpenGL2::createWindow(Toplevel *t) { SceneOpenGL2Window *w = new SceneOpenGL2Window(t); w->setScene(this); return w; } void SceneOpenGL2::finalDrawWindow(EffectWindowImpl* w, int mask, QRegion region, WindowPaintData& data) { if (waylandServer() && waylandServer()->isScreenLocked() && !w->window()->isLockScreen() && !w->window()->isInputMethod()) { return; } performPaintWindow(w, mask, region, data); } void SceneOpenGL2::performPaintWindow(EffectWindowImpl* w, int mask, QRegion region, WindowPaintData& data) { if (mask & PAINT_WINDOW_LANCZOS) { if (!m_lanczosFilter) { m_lanczosFilter = new LanczosFilter(this); // reset the lanczos filter when the screen gets resized // it will get created next paint connect(screens(), &Screens::changed, this, [this]() { makeOpenGLContextCurrent(); delete m_lanczosFilter; m_lanczosFilter = nullptr; }); } m_lanczosFilter->performPaint(w, mask, region, data); } else w->sceneWindow()->performPaint(mask, region, data); } //**************************************** // SceneOpenGL::Window //**************************************** SceneOpenGL::Window::Window(Toplevel* c) : Scene::Window(c) , m_scene(nullptr) { } SceneOpenGL::Window::~Window() { } static SceneOpenGLTexture *s_frameTexture = nullptr; // Bind the window pixmap to an OpenGL texture. bool SceneOpenGL::Window::bindTexture() { s_frameTexture = nullptr; OpenGLWindowPixmap *pixmap = windowPixmap(); if (!pixmap) { return false; } s_frameTexture = pixmap->texture(); if (pixmap->isDiscarded()) { return !pixmap->texture()->isNull(); } if (!window()->damage().isEmpty()) m_scene->insertWait(); return pixmap->bind(); } QMatrix4x4 SceneOpenGL::Window::transformation(int mask, const WindowPaintData &data) const { QMatrix4x4 matrix; matrix.translate(x(), y()); if (!(mask & PAINT_WINDOW_TRANSFORMED)) return matrix; matrix.translate(data.translation()); data.scale().applyTo(&matrix); if (data.rotationAngle() == 0.0) return matrix; // Apply the rotation // cannot use data.rotation.applyTo(&matrix) as QGraphicsRotation uses projectedRotate to map back to 2D matrix.translate(data.rotationOrigin()); const QVector3D axis = data.rotationAxis(); matrix.rotate(data.rotationAngle(), axis.x(), axis.y(), axis.z()); matrix.translate(-data.rotationOrigin()); return matrix; } bool SceneOpenGL::Window::beginRenderWindow(int mask, const QRegion ®ion, WindowPaintData &data) { if (region.isEmpty()) return false; m_hardwareClipping = region != infiniteRegion() && (mask & PAINT_WINDOW_TRANSFORMED) && !(mask & PAINT_SCREEN_TRANSFORMED); if (region != infiniteRegion() && !m_hardwareClipping) { WindowQuadList quads; quads.reserve(data.quads.count()); const QRegion filterRegion = region.translated(-x(), -y()); // split all quads in bounding rect with the actual rects in the region foreach (const WindowQuad &quad, data.quads) { for (const QRect &r : filterRegion) { const QRectF rf(r); const QRectF quadRect(QPointF(quad.left(), quad.top()), QPointF(quad.right(), quad.bottom())); const QRectF &intersected = rf.intersected(quadRect); if (intersected.isValid()) { if (quadRect == intersected) { // case 1: completely contains, include and do not check other rects quads << quad; break; } // case 2: intersection quads << quad.makeSubQuad(intersected.left(), intersected.top(), intersected.right(), intersected.bottom()); } } } data.quads = quads; } if (data.quads.isEmpty()) return false; if (!bindTexture() || !s_frameTexture) { return false; } if (m_hardwareClipping) { glEnable(GL_SCISSOR_TEST); } // Update the texture filter if (waylandServer()) { filter = ImageFilterGood; s_frameTexture->setFilter(GL_LINEAR); } else { if (options->glSmoothScale() != 0 && (mask & (PAINT_WINDOW_TRANSFORMED | PAINT_SCREEN_TRANSFORMED))) filter = ImageFilterGood; else filter = ImageFilterFast; s_frameTexture->setFilter(filter == ImageFilterGood ? GL_LINEAR : GL_NEAREST); } const GLVertexAttrib attribs[] = { { VA_Position, 2, GL_FLOAT, offsetof(GLVertex2D, position) }, { VA_TexCoord, 2, GL_FLOAT, offsetof(GLVertex2D, texcoord) }, }; GLVertexBuffer *vbo = GLVertexBuffer::streamingBuffer(); vbo->reset(); vbo->setAttribLayout(attribs, 2, sizeof(GLVertex2D)); return true; } void SceneOpenGL::Window::endRenderWindow() { if (m_hardwareClipping) { glDisable(GL_SCISSOR_TEST); } } GLTexture *SceneOpenGL::Window::getDecorationTexture() const { if (AbstractClient *client = dynamic_cast(toplevel)) { if (client->noBorder()) { return nullptr; } if (!client->isDecorated()) { return nullptr; } if (SceneOpenGLDecorationRenderer *renderer = static_cast(client->decoratedClient()->renderer())) { renderer->render(); return renderer->texture(); } } else if (toplevel->isDeleted()) { Deleted *deleted = static_cast(toplevel); if (!deleted->wasClient() || deleted->noBorder()) { return nullptr; } if (const SceneOpenGLDecorationRenderer *renderer = static_cast(deleted->decorationRenderer())) { return renderer->texture(); } } return nullptr; } WindowPixmap* SceneOpenGL::Window::createWindowPixmap() { return new OpenGLWindowPixmap(this, m_scene); } //*************************************** // SceneOpenGL2Window //*************************************** SceneOpenGL2Window::SceneOpenGL2Window(Toplevel *c) : SceneOpenGL::Window(c) , m_blendingEnabled(false) { } SceneOpenGL2Window::~SceneOpenGL2Window() { } QVector4D SceneOpenGL2Window::modulate(float opacity, float brightness) const { const float a = opacity; const float rgb = opacity * brightness; return QVector4D(rgb, rgb, rgb, a); } void SceneOpenGL2Window::setBlendEnabled(bool enabled) { if (enabled && !m_blendingEnabled) glEnable(GL_BLEND); else if (!enabled && m_blendingEnabled) glDisable(GL_BLEND); m_blendingEnabled = enabled; } void SceneOpenGL2Window::setupLeafNodes(LeafNode *nodes, const WindowQuadList *quads, const WindowPaintData &data) { if (!quads[ShadowLeaf].isEmpty()) { nodes[ShadowLeaf].texture = static_cast(m_shadow)->shadowTexture(); nodes[ShadowLeaf].opacity = data.opacity(); nodes[ShadowLeaf].hasAlpha = true; nodes[ShadowLeaf].coordinateType = NormalizedCoordinates; } if (!quads[DecorationLeaf].isEmpty()) { nodes[DecorationLeaf].texture = getDecorationTexture(); nodes[DecorationLeaf].opacity = data.opacity(); nodes[DecorationLeaf].hasAlpha = true; nodes[DecorationLeaf].coordinateType = UnnormalizedCoordinates; } nodes[ContentLeaf].texture = s_frameTexture; nodes[ContentLeaf].hasAlpha = !isOpaque(); // TODO: ARGB crsoofading is atm. a hack, playing on opacities for two dumb SrcOver operations // Should be a shader if (data.crossFadeProgress() != 1.0 && (data.opacity() < 0.95 || toplevel->hasAlpha())) { const float opacity = 1.0 - data.crossFadeProgress(); nodes[ContentLeaf].opacity = data.opacity() * (1 - pow(opacity, 1.0f + 2.0f * data.opacity())); } else { nodes[ContentLeaf].opacity = data.opacity(); } nodes[ContentLeaf].coordinateType = UnnormalizedCoordinates; if (data.crossFadeProgress() != 1.0) { OpenGLWindowPixmap *previous = previousWindowPixmap(); nodes[PreviousContentLeaf].texture = previous ? previous->texture() : nullptr; nodes[PreviousContentLeaf].hasAlpha = !isOpaque(); nodes[PreviousContentLeaf].opacity = data.opacity() * (1.0 - data.crossFadeProgress()); nodes[PreviousContentLeaf].coordinateType = NormalizedCoordinates; } } QMatrix4x4 SceneOpenGL2Window::modelViewProjectionMatrix(int mask, const WindowPaintData &data) const { SceneOpenGL2 *scene = static_cast(m_scene); const QMatrix4x4 pMatrix = data.projectionMatrix(); const QMatrix4x4 mvMatrix = data.modelViewMatrix(); // An effect may want to override the default projection matrix in some cases, // such as when it is rendering a window on a render target that doesn't have // the same dimensions as the default framebuffer. // // Note that the screen transformation is not applied here. if (!pMatrix.isIdentity()) return pMatrix * mvMatrix; // If an effect has specified a model-view matrix, we multiply that matrix // with the default projection matrix. If the effect hasn't specified a // model-view matrix, mvMatrix will be the identity matrix. if (mask & Scene::PAINT_SCREEN_TRANSFORMED) return scene->screenProjectionMatrix() * mvMatrix; return scene->projectionMatrix() * mvMatrix; } void SceneOpenGL2Window::renderSubSurface(GLShader *shader, const QMatrix4x4 &mvp, const QMatrix4x4 &windowMatrix, OpenGLWindowPixmap *pixmap, const QRegion ®ion, bool hardwareClipping) { QMatrix4x4 newWindowMatrix = windowMatrix; newWindowMatrix.translate(pixmap->subSurface()->position().x(), pixmap->subSurface()->position().y()); qreal scale = 1.0; if (pixmap->surface()) { scale = pixmap->surface()->scale(); } if (!pixmap->texture()->isNull()) { setBlendEnabled(pixmap->buffer() && pixmap->buffer()->hasAlphaChannel()); // render this texture shader->setUniform(GLShader::ModelViewProjectionMatrix, mvp * newWindowMatrix); auto texture = pixmap->texture(); texture->bind(); texture->render(region, QRect(0, 0, texture->width() / scale, texture->height() / scale), hardwareClipping); texture->unbind(); } const auto &children = pixmap->children(); for (auto pixmap : children) { if (pixmap->subSurface().isNull() || pixmap->subSurface()->surface().isNull() || !pixmap->subSurface()->surface()->isMapped()) { continue; } renderSubSurface(shader, mvp, newWindowMatrix, static_cast(pixmap), region, hardwareClipping); } } void SceneOpenGL2Window::performPaint(int mask, QRegion region, WindowPaintData data) { if (!beginRenderWindow(mask, region, data)) return; QMatrix4x4 windowMatrix = transformation(mask, data); const QMatrix4x4 modelViewProjection = modelViewProjectionMatrix(mask, data); const QMatrix4x4 mvpMatrix = modelViewProjection * windowMatrix; GLShader *shader = data.shader; if (!shader) { ShaderTraits traits = ShaderTrait::MapTexture; if (data.opacity() != 1.0 || data.brightness() != 1.0 || data.crossFadeProgress() != 1.0) traits |= ShaderTrait::Modulate; if (data.saturation() != 1.0) traits |= ShaderTrait::AdjustSaturation; shader = ShaderManager::instance()->pushShader(traits); } shader->setUniform(GLShader::ModelViewProjectionMatrix, mvpMatrix); shader->setUniform(GLShader::Saturation, data.saturation()); GLenum filter; if (waylandServer()) { filter = GL_LINEAR; } else { const bool isTransformed = mask & (Effect::PAINT_WINDOW_TRANSFORMED | Effect::PAINT_SCREEN_TRANSFORMED); if (isTransformed && options->glSmoothScale() != 0) { filter = GL_LINEAR; } else { filter = GL_NEAREST; } } WindowQuadList quads[LeafCount]; // Split the quads into separate lists for each type foreach (const WindowQuad &quad, data.quads) { switch (quad.type()) { case WindowQuadDecoration: quads[DecorationLeaf].append(quad); continue; case WindowQuadContents: quads[ContentLeaf].append(quad); continue; case WindowQuadShadow: quads[ShadowLeaf].append(quad); continue; default: continue; } } if (data.crossFadeProgress() != 1.0) { OpenGLWindowPixmap *previous = previousWindowPixmap(); if (previous) { const QRect &oldGeometry = previous->contentsRect(); for (const WindowQuad &quad : quads[ContentLeaf]) { // we need to create new window quads with normalize texture coordinates // normal quads divide the x/y position by width/height. This would not work as the texture // is larger than the visible content in case of a decorated Client resulting in garbage being shown. // So we calculate the normalized texture coordinate in the Client's new content space and map it to // the previous Client's content space. WindowQuad newQuad(WindowQuadContents); for (int i = 0; i < 4; ++i) { const qreal xFactor = qreal(quad[i].textureX() - toplevel->clientPos().x())/qreal(toplevel->clientSize().width()); const qreal yFactor = qreal(quad[i].textureY() - toplevel->clientPos().y())/qreal(toplevel->clientSize().height()); WindowVertex vertex(quad[i].x(), quad[i].y(), (xFactor * oldGeometry.width() + oldGeometry.x())/qreal(previous->size().width()), (yFactor * oldGeometry.height() + oldGeometry.y())/qreal(previous->size().height())); newQuad[i] = vertex; } quads[PreviousContentLeaf].append(newQuad); } } } const bool indexedQuads = GLVertexBuffer::supportsIndexedQuads(); const GLenum primitiveType = indexedQuads ? GL_QUADS : GL_TRIANGLES; const int verticesPerQuad = indexedQuads ? 4 : 6; const size_t size = verticesPerQuad * (quads[0].count() + quads[1].count() + quads[2].count() + quads[3].count()) * sizeof(GLVertex2D); GLVertexBuffer *vbo = GLVertexBuffer::streamingBuffer(); GLVertex2D *map = (GLVertex2D *) vbo->map(size); LeafNode nodes[LeafCount]; setupLeafNodes(nodes, quads, data); for (int i = 0, v = 0; i < LeafCount; i++) { if (quads[i].isEmpty() || !nodes[i].texture) continue; nodes[i].firstVertex = v; nodes[i].vertexCount = quads[i].count() * verticesPerQuad; const QMatrix4x4 matrix = nodes[i].texture->matrix(nodes[i].coordinateType); quads[i].makeInterleavedArrays(primitiveType, &map[v], matrix); v += quads[i].count() * verticesPerQuad; } vbo->unmap(); vbo->bindArrays(); // Make sure the blend function is set up correctly in case we will be doing blending glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); float opacity = -1.0; for (int i = 0; i < LeafCount; i++) { if (nodes[i].vertexCount == 0) continue; setBlendEnabled(nodes[i].hasAlpha || nodes[i].opacity < 1.0); if (opacity != nodes[i].opacity) { shader->setUniform(GLShader::ModulationConstant, modulate(nodes[i].opacity, data.brightness())); opacity = nodes[i].opacity; } nodes[i].texture->setFilter(filter); nodes[i].texture->setWrapMode(GL_CLAMP_TO_EDGE); nodes[i].texture->bind(); vbo->draw(region, primitiveType, nodes[i].firstVertex, nodes[i].vertexCount, m_hardwareClipping); } vbo->unbindArrays(); // render sub-surfaces auto wp = windowPixmap(); const auto &children = wp ? wp->children() : QVector(); windowMatrix.translate(toplevel->clientPos().x(), toplevel->clientPos().y()); for (auto pixmap : children) { if (pixmap->subSurface().isNull() || pixmap->subSurface()->surface().isNull() || !pixmap->subSurface()->surface()->isMapped()) { continue; } renderSubSurface(shader, modelViewProjection, windowMatrix, static_cast(pixmap), region, m_hardwareClipping); } setBlendEnabled(false); if (!data.shader) ShaderManager::instance()->popShader(); endRenderWindow(); } //**************************************** // OpenGLWindowPixmap //**************************************** OpenGLWindowPixmap::OpenGLWindowPixmap(Scene::Window *window, SceneOpenGL* scene) : WindowPixmap(window) , m_texture(scene->createTexture()) , m_scene(scene) { } OpenGLWindowPixmap::OpenGLWindowPixmap(const QPointer &subSurface, WindowPixmap *parent, SceneOpenGL *scene) : WindowPixmap(subSurface, parent) , m_texture(scene->createTexture()) , m_scene(scene) { } OpenGLWindowPixmap::~OpenGLWindowPixmap() { } +static bool needsPixmapUpdate(const OpenGLWindowPixmap *pixmap) +{ + // That's a regular Wayland client. + if (pixmap->surface()) { + return !pixmap->surface()->trackedDamage().isEmpty(); + } + + // That's an internal client with a raster buffer attached. + if (!pixmap->internalImage().isNull()) { + return !pixmap->toplevel()->damage().isEmpty(); + } + + // That's an internal client with an opengl framebuffer object attached. + if (!pixmap->fbo().isNull()) { + return !pixmap->toplevel()->damage().isEmpty(); + } + + // That's an X11 client. + return false; +} + bool OpenGLWindowPixmap::bind() { if (!m_texture->isNull()) { // always call updateBuffer to get the sub-surface tree updated if (subSurface().isNull() && !toplevel()->damage().isEmpty()) { updateBuffer(); } - auto s = surface(); - if (s && !s->trackedDamage().isEmpty()) { + if (needsPixmapUpdate(this)) { m_texture->updateFromPixmap(this); // mipmaps need to be updated m_texture->setDirty(); } if (subSurface().isNull()) { toplevel()->resetDamage(); } // also bind all children for (auto it = children().constBegin(); it != children().constEnd(); ++it) { static_cast(*it)->bind(); } return true; } // also bind all children, needs to be done before checking isValid // as there might be valid children to render, see https://bugreports.qt.io/browse/QTBUG-52192 if (subSurface().isNull()) { updateBuffer(); } for (auto it = children().constBegin(); it != children().constEnd(); ++it) { static_cast(*it)->bind(); } if (!isValid()) { return false; } bool success = m_texture->load(this); if (success) { if (subSurface().isNull()) { toplevel()->resetDamage(); } } else qCDebug(KWIN_OPENGL) << "Failed to bind window"; return success; } WindowPixmap *OpenGLWindowPixmap::createChild(const QPointer &subSurface) { return new OpenGLWindowPixmap(subSurface, this, m_scene); } bool OpenGLWindowPixmap::isValid() const { if (!m_texture->isNull()) { return true; } return WindowPixmap::isValid(); } //**************************************** // SceneOpenGL::EffectFrame //**************************************** GLTexture* SceneOpenGL::EffectFrame::m_unstyledTexture = nullptr; QPixmap* SceneOpenGL::EffectFrame::m_unstyledPixmap = nullptr; SceneOpenGL::EffectFrame::EffectFrame(EffectFrameImpl* frame, SceneOpenGL *scene) : Scene::EffectFrame(frame) , m_texture(nullptr) , m_textTexture(nullptr) , m_oldTextTexture(nullptr) , m_textPixmap(nullptr) , m_iconTexture(nullptr) , m_oldIconTexture(nullptr) , m_selectionTexture(nullptr) , m_unstyledVBO(nullptr) , m_scene(scene) { if (m_effectFrame->style() == EffectFrameUnstyled && !m_unstyledTexture) { updateUnstyledTexture(); } } SceneOpenGL::EffectFrame::~EffectFrame() { delete m_texture; delete m_textTexture; delete m_textPixmap; delete m_oldTextTexture; delete m_iconTexture; delete m_oldIconTexture; delete m_selectionTexture; delete m_unstyledVBO; } void SceneOpenGL::EffectFrame::free() { glFlush(); delete m_texture; m_texture = nullptr; delete m_textTexture; m_textTexture = nullptr; delete m_textPixmap; m_textPixmap = nullptr; delete m_iconTexture; m_iconTexture = nullptr; delete m_selectionTexture; m_selectionTexture = nullptr; delete m_unstyledVBO; m_unstyledVBO = nullptr; delete m_oldIconTexture; m_oldIconTexture = nullptr; delete m_oldTextTexture; m_oldTextTexture = nullptr; } void SceneOpenGL::EffectFrame::freeIconFrame() { delete m_iconTexture; m_iconTexture = nullptr; } void SceneOpenGL::EffectFrame::freeTextFrame() { delete m_textTexture; m_textTexture = nullptr; delete m_textPixmap; m_textPixmap = nullptr; } void SceneOpenGL::EffectFrame::freeSelection() { delete m_selectionTexture; m_selectionTexture = nullptr; } void SceneOpenGL::EffectFrame::crossFadeIcon() { delete m_oldIconTexture; m_oldIconTexture = m_iconTexture; m_iconTexture = nullptr; } void SceneOpenGL::EffectFrame::crossFadeText() { delete m_oldTextTexture; m_oldTextTexture = m_textTexture; m_textTexture = nullptr; } void SceneOpenGL::EffectFrame::render(QRegion region, double opacity, double frameOpacity) { if (m_effectFrame->geometry().isEmpty()) return; // Nothing to display region = infiniteRegion(); // TODO: Old region doesn't seem to work with OpenGL GLShader* shader = m_effectFrame->shader(); if (!shader) { shader = ShaderManager::instance()->pushShader(ShaderTrait::MapTexture | ShaderTrait::Modulate); } else if (shader) { ShaderManager::instance()->pushShader(shader); } if (shader) { shader->setUniform(GLShader::ModulationConstant, QVector4D(1.0, 1.0, 1.0, 1.0)); shader->setUniform(GLShader::Saturation, 1.0f); } const QMatrix4x4 projection = m_scene->projectionMatrix(); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); // Render the actual frame if (m_effectFrame->style() == EffectFrameUnstyled) { if (!m_unstyledVBO) { m_unstyledVBO = new GLVertexBuffer(GLVertexBuffer::Static); QRect area = m_effectFrame->geometry(); area.moveTo(0, 0); area.adjust(-5, -5, 5, 5); const int roundness = 5; QVector verts, texCoords; verts.reserve(84); texCoords.reserve(84); // top left verts << area.left() << area.top(); texCoords << 0.0f << 0.0f; verts << area.left() << area.top() + roundness; texCoords << 0.0f << 0.5f; verts << area.left() + roundness << area.top(); texCoords << 0.5f << 0.0f; verts << area.left() + roundness << area.top() + roundness; texCoords << 0.5f << 0.5f; verts << area.left() << area.top() + roundness; texCoords << 0.0f << 0.5f; verts << area.left() + roundness << area.top(); texCoords << 0.5f << 0.0f; // top verts << area.left() + roundness << area.top(); texCoords << 0.5f << 0.0f; verts << area.left() + roundness << area.top() + roundness; texCoords << 0.5f << 0.5f; verts << area.right() - roundness << area.top(); texCoords << 0.5f << 0.0f; verts << area.left() + roundness << area.top() + roundness; texCoords << 0.5f << 0.5f; verts << area.right() - roundness << area.top() + roundness; texCoords << 0.5f << 0.5f; verts << area.right() - roundness << area.top(); texCoords << 0.5f << 0.0f; // top right verts << area.right() - roundness << area.top(); texCoords << 0.5f << 0.0f; verts << area.right() - roundness << area.top() + roundness; texCoords << 0.5f << 0.5f; verts << area.right() << area.top(); texCoords << 1.0f << 0.0f; verts << area.right() - roundness << area.top() + roundness; texCoords << 0.5f << 0.5f; verts << area.right() << area.top() + roundness; texCoords << 1.0f << 0.5f; verts << area.right() << area.top(); texCoords << 1.0f << 0.0f; // bottom left verts << area.left() << area.bottom() - roundness; texCoords << 0.0f << 0.5f; verts << area.left() << area.bottom(); texCoords << 0.0f << 1.0f; verts << area.left() + roundness << area.bottom() - roundness; texCoords << 0.5f << 0.5f; verts << area.left() + roundness << area.bottom(); texCoords << 0.5f << 1.0f; verts << area.left() << area.bottom(); texCoords << 0.0f << 1.0f; verts << area.left() + roundness << area.bottom() - roundness; texCoords << 0.5f << 0.5f; // bottom verts << area.left() + roundness << area.bottom() - roundness; texCoords << 0.5f << 0.5f; verts << area.left() + roundness << area.bottom(); texCoords << 0.5f << 1.0f; verts << area.right() - roundness << area.bottom() - roundness; texCoords << 0.5f << 0.5f; verts << area.left() + roundness << area.bottom(); texCoords << 0.5f << 1.0f; verts << area.right() - roundness << area.bottom(); texCoords << 0.5f << 1.0f; verts << area.right() - roundness << area.bottom() - roundness; texCoords << 0.5f << 0.5f; // bottom right verts << area.right() - roundness << area.bottom() - roundness; texCoords << 0.5f << 0.5f; verts << area.right() - roundness << area.bottom(); texCoords << 0.5f << 1.0f; verts << area.right() << area.bottom() - roundness; texCoords << 1.0f << 0.5f; verts << area.right() - roundness << area.bottom(); texCoords << 0.5f << 1.0f; verts << area.right() << area.bottom(); texCoords << 1.0f << 1.0f; verts << area.right() << area.bottom() - roundness; texCoords << 1.0f << 0.5f; // center verts << area.left() << area.top() + roundness; texCoords << 0.0f << 0.5f; verts << area.left() << area.bottom() - roundness; texCoords << 0.0f << 0.5f; verts << area.right() << area.top() + roundness; texCoords << 1.0f << 0.5f; verts << area.left() << area.bottom() - roundness; texCoords << 0.0f << 0.5f; verts << area.right() << area.bottom() - roundness; texCoords << 1.0f << 0.5f; verts << area.right() << area.top() + roundness; texCoords << 1.0f << 0.5f; m_unstyledVBO->setData(verts.count() / 2, 2, verts.data(), texCoords.data()); } if (shader) { const float a = opacity * frameOpacity; shader->setUniform(GLShader::ModulationConstant, QVector4D(a, a, a, a)); } m_unstyledTexture->bind(); const QPoint pt = m_effectFrame->geometry().topLeft(); QMatrix4x4 mvp(projection); mvp.translate(pt.x(), pt.y()); shader->setUniform(GLShader::ModelViewProjectionMatrix, mvp); m_unstyledVBO->render(region, GL_TRIANGLES); m_unstyledTexture->unbind(); } else if (m_effectFrame->style() == EffectFrameStyled) { if (!m_texture) // Lazy creation updateTexture(); if (shader) { const float a = opacity * frameOpacity; shader->setUniform(GLShader::ModulationConstant, QVector4D(a, a, a, a)); } m_texture->bind(); qreal left, top, right, bottom; m_effectFrame->frame().getMargins(left, top, right, bottom); // m_geometry is the inner geometry const QRect rect = m_effectFrame->geometry().adjusted(-left, -top, right, bottom); QMatrix4x4 mvp(projection); mvp.translate(rect.x(), rect.y()); shader->setUniform(GLShader::ModelViewProjectionMatrix, mvp); m_texture->render(region, rect); m_texture->unbind(); } if (!m_effectFrame->selection().isNull()) { if (!m_selectionTexture) { // Lazy creation QPixmap pixmap = m_effectFrame->selectionFrame().framePixmap(); if (!pixmap.isNull()) m_selectionTexture = new GLTexture(pixmap); } if (m_selectionTexture) { if (shader) { const float a = opacity * frameOpacity; shader->setUniform(GLShader::ModulationConstant, QVector4D(a, a, a, a)); } QMatrix4x4 mvp(projection); mvp.translate(m_effectFrame->selection().x(), m_effectFrame->selection().y()); shader->setUniform(GLShader::ModelViewProjectionMatrix, mvp); glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); m_selectionTexture->bind(); m_selectionTexture->render(region, m_effectFrame->selection()); m_selectionTexture->unbind(); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); } } // Render icon if (!m_effectFrame->icon().isNull() && !m_effectFrame->iconSize().isEmpty()) { QPoint topLeft(m_effectFrame->geometry().x(), m_effectFrame->geometry().center().y() - m_effectFrame->iconSize().height() / 2); QMatrix4x4 mvp(projection); mvp.translate(topLeft.x(), topLeft.y()); shader->setUniform(GLShader::ModelViewProjectionMatrix, mvp); if (m_effectFrame->isCrossFade() && m_oldIconTexture) { if (shader) { const float a = opacity * (1.0 - m_effectFrame->crossFadeProgress()); shader->setUniform(GLShader::ModulationConstant, QVector4D(a, a, a, a)); } m_oldIconTexture->bind(); m_oldIconTexture->render(region, QRect(topLeft, m_effectFrame->iconSize())); m_oldIconTexture->unbind(); if (shader) { const float a = opacity * m_effectFrame->crossFadeProgress(); shader->setUniform(GLShader::ModulationConstant, QVector4D(a, a, a, a)); } } else { if (shader) { const QVector4D constant(opacity, opacity, opacity, opacity); shader->setUniform(GLShader::ModulationConstant, constant); } } if (!m_iconTexture) { // lazy creation m_iconTexture = new GLTexture(m_effectFrame->icon().pixmap(m_effectFrame->iconSize())); } m_iconTexture->bind(); m_iconTexture->render(region, QRect(topLeft, m_effectFrame->iconSize())); m_iconTexture->unbind(); } // Render text if (!m_effectFrame->text().isEmpty()) { QMatrix4x4 mvp(projection); mvp.translate(m_effectFrame->geometry().x(), m_effectFrame->geometry().y()); shader->setUniform(GLShader::ModelViewProjectionMatrix, mvp); if (m_effectFrame->isCrossFade() && m_oldTextTexture) { if (shader) { const float a = opacity * (1.0 - m_effectFrame->crossFadeProgress()); shader->setUniform(GLShader::ModulationConstant, QVector4D(a, a, a, a)); } m_oldTextTexture->bind(); m_oldTextTexture->render(region, m_effectFrame->geometry()); m_oldTextTexture->unbind(); if (shader) { const float a = opacity * m_effectFrame->crossFadeProgress(); shader->setUniform(GLShader::ModulationConstant, QVector4D(a, a, a, a)); } } else { if (shader) { const QVector4D constant(opacity, opacity, opacity, opacity); shader->setUniform(GLShader::ModulationConstant, constant); } } if (!m_textTexture) // Lazy creation updateTextTexture(); if (m_textTexture) { m_textTexture->bind(); m_textTexture->render(region, m_effectFrame->geometry()); m_textTexture->unbind(); } } if (shader) { ShaderManager::instance()->popShader(); } glDisable(GL_BLEND); } void SceneOpenGL::EffectFrame::updateTexture() { delete m_texture; m_texture = nullptr; if (m_effectFrame->style() == EffectFrameStyled) { QPixmap pixmap = m_effectFrame->frame().framePixmap(); m_texture = new GLTexture(pixmap); } } void SceneOpenGL::EffectFrame::updateTextTexture() { delete m_textTexture; m_textTexture = nullptr; delete m_textPixmap; m_textPixmap = nullptr; if (m_effectFrame->text().isEmpty()) return; // Determine position on texture to paint text QRect rect(QPoint(0, 0), m_effectFrame->geometry().size()); if (!m_effectFrame->icon().isNull() && !m_effectFrame->iconSize().isEmpty()) rect.setLeft(m_effectFrame->iconSize().width()); // If static size elide text as required QString text = m_effectFrame->text(); if (m_effectFrame->isStatic()) { QFontMetrics metrics(m_effectFrame->font()); text = metrics.elidedText(text, Qt::ElideRight, rect.width()); } m_textPixmap = new QPixmap(m_effectFrame->geometry().size()); m_textPixmap->fill(Qt::transparent); QPainter p(m_textPixmap); p.setFont(m_effectFrame->font()); if (m_effectFrame->style() == EffectFrameStyled) p.setPen(m_effectFrame->styledTextColor()); else // TODO: What about no frame? Custom color setting required p.setPen(Qt::white); p.drawText(rect, m_effectFrame->alignment(), text); p.end(); m_textTexture = new GLTexture(*m_textPixmap); } void SceneOpenGL::EffectFrame::updateUnstyledTexture() { delete m_unstyledTexture; m_unstyledTexture = nullptr; delete m_unstyledPixmap; m_unstyledPixmap = nullptr; // Based off circle() from kwinxrenderutils.cpp #define CS 8 m_unstyledPixmap = new QPixmap(2 * CS, 2 * CS); m_unstyledPixmap->fill(Qt::transparent); QPainter p(m_unstyledPixmap); p.setRenderHint(QPainter::Antialiasing); p.setPen(Qt::NoPen); p.setBrush(Qt::black); p.drawEllipse(m_unstyledPixmap->rect()); p.end(); #undef CS m_unstyledTexture = new GLTexture(*m_unstyledPixmap); } void SceneOpenGL::EffectFrame::cleanup() { delete m_unstyledTexture; m_unstyledTexture = nullptr; delete m_unstyledPixmap; m_unstyledPixmap = nullptr; } //**************************************** // SceneOpenGL::Shadow //**************************************** class DecorationShadowTextureCache { public: ~DecorationShadowTextureCache(); DecorationShadowTextureCache(const DecorationShadowTextureCache&) = delete; static DecorationShadowTextureCache &instance(); void unregister(SceneOpenGLShadow *shadow); QSharedPointer getTexture(SceneOpenGLShadow *shadow); private: DecorationShadowTextureCache() = default; struct Data { QSharedPointer texture; QVector shadows; }; QHash m_cache; }; DecorationShadowTextureCache &DecorationShadowTextureCache::instance() { static DecorationShadowTextureCache s_instance; return s_instance; } DecorationShadowTextureCache::~DecorationShadowTextureCache() { Q_ASSERT(m_cache.isEmpty()); } void DecorationShadowTextureCache::unregister(SceneOpenGLShadow *shadow) { auto it = m_cache.begin(); while (it != m_cache.end()) { auto &d = it.value(); // check whether the Vector of Shadows contains our shadow and remove all of them auto glIt = d.shadows.begin(); while (glIt != d.shadows.end()) { if (*glIt == shadow) { glIt = d.shadows.erase(glIt); } else { glIt++; } } // if there are no shadows any more we can erase the cache entry if (d.shadows.isEmpty()) { it = m_cache.erase(it); } else { it++; } } } QSharedPointer DecorationShadowTextureCache::getTexture(SceneOpenGLShadow *shadow) { Q_ASSERT(shadow->hasDecorationShadow()); unregister(shadow); const auto &decoShadow = shadow->decorationShadow(); Q_ASSERT(!decoShadow.isNull()); auto it = m_cache.find(decoShadow.data()); if (it != m_cache.end()) { Q_ASSERT(!it.value().shadows.contains(shadow)); it.value().shadows << shadow; return it.value().texture; } Data d; d.shadows << shadow; d.texture = QSharedPointer::create(shadow->decorationShadowImage()); m_cache.insert(decoShadow.data(), d); return d.texture; } SceneOpenGLShadow::SceneOpenGLShadow(Toplevel *toplevel) : Shadow(toplevel) { } SceneOpenGLShadow::~SceneOpenGLShadow() { Scene *scene = Compositor::self()->scene(); if (scene) { scene->makeOpenGLContextCurrent(); DecorationShadowTextureCache::instance().unregister(this); m_texture.reset(); } } static inline void distributeHorizontally(QRectF &leftRect, QRectF &rightRect) { if (leftRect.right() > rightRect.left()) { const qreal boundedRight = qMin(leftRect.right(), rightRect.right()); const qreal boundedLeft = qMax(leftRect.left(), rightRect.left()); const qreal halfOverlap = (boundedRight - boundedLeft) / 2.0; leftRect.setRight(boundedRight - halfOverlap); rightRect.setLeft(boundedLeft + halfOverlap); } } static inline void distributeVertically(QRectF &topRect, QRectF &bottomRect) { if (topRect.bottom() > bottomRect.top()) { const qreal boundedBottom = qMin(topRect.bottom(), bottomRect.bottom()); const qreal boundedTop = qMax(topRect.top(), bottomRect.top()); const qreal halfOverlap = (boundedBottom - boundedTop) / 2.0; topRect.setBottom(boundedBottom - halfOverlap); bottomRect.setTop(boundedTop + halfOverlap); } } void SceneOpenGLShadow::buildQuads() { // Do not draw shadows if window width or window height is less than // 5 px. 5 is an arbitrary choice. if (topLevel()->width() < 5 || topLevel()->height() < 5) { m_shadowQuads.clear(); setShadowRegion(QRegion()); return; } const QSizeF top(elementSize(ShadowElementTop)); const QSizeF topRight(elementSize(ShadowElementTopRight)); const QSizeF right(elementSize(ShadowElementRight)); const QSizeF bottomRight(elementSize(ShadowElementBottomRight)); const QSizeF bottom(elementSize(ShadowElementBottom)); const QSizeF bottomLeft(elementSize(ShadowElementBottomLeft)); const QSizeF left(elementSize(ShadowElementLeft)); const QSizeF topLeft(elementSize(ShadowElementTopLeft)); const QMarginsF shadowMargins( std::max({topLeft.width(), left.width(), bottomLeft.width()}), std::max({topLeft.height(), top.height(), topRight.height()}), std::max({topRight.width(), right.width(), bottomRight.width()}), std::max({bottomRight.height(), bottom.height(), bottomLeft.height()})); const QRectF outerRect(QPointF(-leftOffset(), -topOffset()), QPointF(topLevel()->width() + rightOffset(), topLevel()->height() + bottomOffset())); const int width = shadowMargins.left() + std::max(top.width(), bottom.width()) + shadowMargins.right(); const int height = shadowMargins.top() + std::max(left.height(), right.height()) + shadowMargins.bottom(); QRectF topLeftRect; if (!topLeft.isEmpty()) { topLeftRect = QRectF(outerRect.topLeft(), topLeft); } else { topLeftRect = QRectF( outerRect.left() + shadowMargins.left(), outerRect.top() + shadowMargins.top(), 0, 0); } QRectF topRightRect; if (!topRight.isEmpty()) { topRightRect = QRectF( outerRect.right() - topRight.width(), outerRect.top(), topRight.width(), topRight.height()); } else { topRightRect = QRectF( outerRect.right() - shadowMargins.right(), outerRect.top() + shadowMargins.top(), 0, 0); } QRectF bottomRightRect; if (!bottomRight.isEmpty()) { bottomRightRect = QRectF( outerRect.right() - bottomRight.width(), outerRect.bottom() - bottomRight.height(), bottomRight.width(), bottomRight.height()); } else { bottomRightRect = QRectF( outerRect.right() - shadowMargins.right(), outerRect.bottom() - shadowMargins.bottom(), 0, 0); } QRectF bottomLeftRect; if (!bottomLeft.isEmpty()) { bottomLeftRect = QRectF( outerRect.left(), outerRect.bottom() - bottomLeft.height(), bottomLeft.width(), bottomLeft.height()); } else { bottomLeftRect = QRectF( outerRect.left() + shadowMargins.left(), outerRect.bottom() - shadowMargins.bottom(), 0, 0); } // Re-distribute the corner tiles so no one of them is overlapping with others. // By doing this, we assume that shadow's corner tiles are symmetric // and it is OK to not draw top/right/bottom/left tile between corners. // For example, let's say top-left and top-right tiles are overlapping. // In that case, the right side of the top-left tile will be shifted to left, // the left side of the top-right tile will shifted to right, and the top // tile won't be rendered. distributeHorizontally(topLeftRect, topRightRect); distributeHorizontally(bottomLeftRect, bottomRightRect); distributeVertically(topLeftRect, bottomLeftRect); distributeVertically(topRightRect, bottomRightRect); qreal tx1 = 0.0, tx2 = 0.0, ty1 = 0.0, ty2 = 0.0; m_shadowQuads.clear(); if (topLeftRect.isValid()) { tx1 = 0.0; ty1 = 0.0; tx2 = topLeftRect.width() / width; ty2 = topLeftRect.height() / height; WindowQuad topLeftQuad(WindowQuadShadow); topLeftQuad[0] = WindowVertex(topLeftRect.left(), topLeftRect.top(), tx1, ty1); topLeftQuad[1] = WindowVertex(topLeftRect.right(), topLeftRect.top(), tx2, ty1); topLeftQuad[2] = WindowVertex(topLeftRect.right(), topLeftRect.bottom(), tx2, ty2); topLeftQuad[3] = WindowVertex(topLeftRect.left(), topLeftRect.bottom(), tx1, ty2); m_shadowQuads.append(topLeftQuad); } if (topRightRect.isValid()) { tx1 = 1.0 - topRightRect.width() / width; ty1 = 0.0; tx2 = 1.0; ty2 = topRightRect.height() / height; WindowQuad topRightQuad(WindowQuadShadow); topRightQuad[0] = WindowVertex(topRightRect.left(), topRightRect.top(), tx1, ty1); topRightQuad[1] = WindowVertex(topRightRect.right(), topRightRect.top(), tx2, ty1); topRightQuad[2] = WindowVertex(topRightRect.right(), topRightRect.bottom(), tx2, ty2); topRightQuad[3] = WindowVertex(topRightRect.left(), topRightRect.bottom(), tx1, ty2); m_shadowQuads.append(topRightQuad); } if (bottomRightRect.isValid()) { tx1 = 1.0 - bottomRightRect.width() / width; tx2 = 1.0; ty1 = 1.0 - bottomRightRect.height() / height; ty2 = 1.0; WindowQuad bottomRightQuad(WindowQuadShadow); bottomRightQuad[0] = WindowVertex(bottomRightRect.left(), bottomRightRect.top(), tx1, ty1); bottomRightQuad[1] = WindowVertex(bottomRightRect.right(), bottomRightRect.top(), tx2, ty1); bottomRightQuad[2] = WindowVertex(bottomRightRect.right(), bottomRightRect.bottom(), tx2, ty2); bottomRightQuad[3] = WindowVertex(bottomRightRect.left(), bottomRightRect.bottom(), tx1, ty2); m_shadowQuads.append(bottomRightQuad); } if (bottomLeftRect.isValid()) { tx1 = 0.0; tx2 = bottomLeftRect.width() / width; ty1 = 1.0 - bottomLeftRect.height() / height; ty2 = 1.0; WindowQuad bottomLeftQuad(WindowQuadShadow); bottomLeftQuad[0] = WindowVertex(bottomLeftRect.left(), bottomLeftRect.top(), tx1, ty1); bottomLeftQuad[1] = WindowVertex(bottomLeftRect.right(), bottomLeftRect.top(), tx2, ty1); bottomLeftQuad[2] = WindowVertex(bottomLeftRect.right(), bottomLeftRect.bottom(), tx2, ty2); bottomLeftQuad[3] = WindowVertex(bottomLeftRect.left(), bottomLeftRect.bottom(), tx1, ty2); m_shadowQuads.append(bottomLeftQuad); } QRectF topRect( QPointF(topLeftRect.right(), outerRect.top()), QPointF(topRightRect.left(), outerRect.top() + top.height())); QRectF rightRect( QPointF(outerRect.right() - right.width(), topRightRect.bottom()), QPointF(outerRect.right(), bottomRightRect.top())); QRectF bottomRect( QPointF(bottomLeftRect.right(), outerRect.bottom() - bottom.height()), QPointF(bottomRightRect.left(), outerRect.bottom())); QRectF leftRect( QPointF(outerRect.left(), topLeftRect.bottom()), QPointF(outerRect.left() + left.width(), bottomLeftRect.top())); // Re-distribute left/right and top/bottom shadow tiles so they don't // overlap when the window is too small. Please notice that we don't // fix overlaps between left/top(left/bottom, right/top, and so on) // corner tiles because corresponding counter parts won't be valid when // the window is too small, which means they won't be rendered. distributeHorizontally(leftRect, rightRect); distributeVertically(topRect, bottomRect); if (topRect.isValid()) { tx1 = shadowMargins.left() / width; ty1 = 0.0; tx2 = tx1 + top.width() / width; ty2 = topRect.height() / height; WindowQuad topQuad(WindowQuadShadow); topQuad[0] = WindowVertex(topRect.left(), topRect.top(), tx1, ty1); topQuad[1] = WindowVertex(topRect.right(), topRect.top(), tx2, ty1); topQuad[2] = WindowVertex(topRect.right(), topRect.bottom(), tx2, ty2); topQuad[3] = WindowVertex(topRect.left(), topRect.bottom(), tx1, ty2); m_shadowQuads.append(topQuad); } if (rightRect.isValid()) { tx1 = 1.0 - rightRect.width() / width; ty1 = shadowMargins.top() / height; tx2 = 1.0; ty2 = ty1 + right.height() / height; WindowQuad rightQuad(WindowQuadShadow); rightQuad[0] = WindowVertex(rightRect.left(), rightRect.top(), tx1, ty1); rightQuad[1] = WindowVertex(rightRect.right(), rightRect.top(), tx2, ty1); rightQuad[2] = WindowVertex(rightRect.right(), rightRect.bottom(), tx2, ty2); rightQuad[3] = WindowVertex(rightRect.left(), rightRect.bottom(), tx1, ty2); m_shadowQuads.append(rightQuad); } if (bottomRect.isValid()) { tx1 = shadowMargins.left() / width; ty1 = 1.0 - bottomRect.height() / height; tx2 = tx1 + bottom.width() / width; ty2 = 1.0; WindowQuad bottomQuad(WindowQuadShadow); bottomQuad[0] = WindowVertex(bottomRect.left(), bottomRect.top(), tx1, ty1); bottomQuad[1] = WindowVertex(bottomRect.right(), bottomRect.top(), tx2, ty1); bottomQuad[2] = WindowVertex(bottomRect.right(), bottomRect.bottom(), tx2, ty2); bottomQuad[3] = WindowVertex(bottomRect.left(), bottomRect.bottom(), tx1, ty2); m_shadowQuads.append(bottomQuad); } if (leftRect.isValid()) { tx1 = 0.0; ty1 = shadowMargins.top() / height; tx2 = leftRect.width() / width; ty2 = ty1 + left.height() / height; WindowQuad leftQuad(WindowQuadShadow); leftQuad[0] = WindowVertex(leftRect.left(), leftRect.top(), tx1, ty1); leftQuad[1] = WindowVertex(leftRect.right(), leftRect.top(), tx2, ty1); leftQuad[2] = WindowVertex(leftRect.right(), leftRect.bottom(), tx2, ty2); leftQuad[3] = WindowVertex(leftRect.left(), leftRect.bottom(), tx1, ty2); m_shadowQuads.append(leftQuad); } } bool SceneOpenGLShadow::prepareBackend() { if (hasDecorationShadow()) { // simplifies a lot by going directly to Scene *scene = Compositor::self()->scene(); scene->makeOpenGLContextCurrent(); m_texture = DecorationShadowTextureCache::instance().getTexture(this); return true; } const QSize top(shadowPixmap(ShadowElementTop).size()); const QSize topRight(shadowPixmap(ShadowElementTopRight).size()); const QSize right(shadowPixmap(ShadowElementRight).size()); const QSize bottom(shadowPixmap(ShadowElementBottom).size()); const QSize bottomLeft(shadowPixmap(ShadowElementBottomLeft).size()); const QSize left(shadowPixmap(ShadowElementLeft).size()); const QSize topLeft(shadowPixmap(ShadowElementTopLeft).size()); const QSize bottomRight(shadowPixmap(ShadowElementBottomRight).size()); const int width = std::max({topLeft.width(), left.width(), bottomLeft.width()}) + std::max(top.width(), bottom.width()) + std::max({topRight.width(), right.width(), bottomRight.width()}); const int height = std::max({topLeft.height(), top.height(), topRight.height()}) + std::max(left.height(), right.height()) + std::max({bottomLeft.height(), bottom.height(), bottomRight.height()}); if (width == 0 || height == 0) { return false; } QImage image(width, height, QImage::Format_ARGB32); image.fill(Qt::transparent); const int innerRectTop = std::max({topLeft.height(), top.height(), topRight.height()}); const int innerRectLeft = std::max({topLeft.width(), left.width(), bottomLeft.width()}); QPainter p; p.begin(&image); p.drawPixmap(0, 0, shadowPixmap(ShadowElementTopLeft)); p.drawPixmap(innerRectLeft, 0, shadowPixmap(ShadowElementTop)); p.drawPixmap(width - topRight.width(), 0, shadowPixmap(ShadowElementTopRight)); p.drawPixmap(0, innerRectTop, shadowPixmap(ShadowElementLeft)); p.drawPixmap(width - right.width(), innerRectTop, shadowPixmap(ShadowElementRight)); p.drawPixmap(0, height - bottomLeft.height(), shadowPixmap(ShadowElementBottomLeft)); p.drawPixmap(innerRectLeft, height - bottom.height(), shadowPixmap(ShadowElementBottom)); p.drawPixmap(width - bottomRight.width(), height - bottomRight.height(), shadowPixmap(ShadowElementBottomRight)); p.end(); // Check if the image is alpha-only in practice, and if so convert it to an 8-bpp format if (!GLPlatform::instance()->isGLES() && GLTexture::supportsSwizzle() && GLTexture::supportsFormatRG()) { QImage alphaImage(image.size(), QImage::Format_Indexed8); // Change to Format_Alpha8 w/ Qt 5.5 bool alphaOnly = true; for (ptrdiff_t y = 0; alphaOnly && y < image.height(); y++) { const uint32_t * const src = reinterpret_cast(image.scanLine(y)); uint8_t * const dst = reinterpret_cast(alphaImage.scanLine(y)); for (ptrdiff_t x = 0; x < image.width(); x++) { if (src[x] & 0x00ffffff) alphaOnly = false; dst[x] = qAlpha(src[x]); } } if (alphaOnly) { image = alphaImage; } } Scene *scene = Compositor::self()->scene(); scene->makeOpenGLContextCurrent(); m_texture = QSharedPointer::create(image); if (m_texture->internalFormat() == GL_R8) { // Swizzle red to alpha and all other channels to zero m_texture->bind(); m_texture->setSwizzle(GL_ZERO, GL_ZERO, GL_ZERO, GL_RED); } return true; } SceneOpenGLDecorationRenderer::SceneOpenGLDecorationRenderer(Decoration::DecoratedClientImpl *client) : Renderer(client) , m_texture() { connect(this, &Renderer::renderScheduled, client->client(), static_cast(&AbstractClient::addRepaint)); } SceneOpenGLDecorationRenderer::~SceneOpenGLDecorationRenderer() { if (Scene *scene = Compositor::self()->scene()) { scene->makeOpenGLContextCurrent(); } } // Rotates the given source rect 90° counter-clockwise, // and flips it vertically static QImage rotate(const QImage &srcImage, const QRect &srcRect) { auto dpr = srcImage.devicePixelRatio(); QImage image(srcRect.height() * dpr, srcRect.width() * dpr, srcImage.format()); image.setDevicePixelRatio(dpr); const QPoint srcPoint(srcRect.x() * dpr, srcRect.y() * dpr); const uint32_t *src = reinterpret_cast(srcImage.bits()); uint32_t *dst = reinterpret_cast(image.bits()); for (int x = 0; x < image.width(); x++) { const uint32_t *s = src + (srcPoint.y() + x) * srcImage.width() + srcPoint.x(); uint32_t *d = dst + x; for (int y = 0; y < image.height(); y++) { *d = s[y]; d += image.width(); } } return image; } void SceneOpenGLDecorationRenderer::render() { const QRegion scheduled = getScheduled(); const bool dirty = areImageSizesDirty(); if (scheduled.isEmpty() && !dirty) { return; } if (dirty) { resizeTexture(); resetImageSizesDirty(); } if (!m_texture) { // for invalid sizes we get no texture, see BUG 361551 return; } QRect left, top, right, bottom; client()->client()->layoutDecorationRects(left, top, right, bottom); const QRect geometry = dirty ? QRect(QPoint(0, 0), client()->client()->geometry().size()) : scheduled.boundingRect(); auto renderPart = [this](const QRect &geo, const QRect &partRect, const QPoint &offset, bool rotated = false) { if (!geo.isValid()) { return; } QImage image = renderToImage(geo); if (rotated) { // TODO: get this done directly when rendering to the image image = rotate(image, QRect(geo.topLeft() - partRect.topLeft(), geo.size())); } m_texture->update(image, (geo.topLeft() - partRect.topLeft() + offset) * image.devicePixelRatio()); }; renderPart(left.intersected(geometry), left, QPoint(0, top.height() + bottom.height() + 2), true); renderPart(top.intersected(geometry), top, QPoint(0, 0)); renderPart(right.intersected(geometry), right, QPoint(0, top.height() + bottom.height() + left.width() + 3), true); renderPart(bottom.intersected(geometry), bottom, QPoint(0, top.height() + 1)); } static int align(int value, int align) { return (value + align - 1) & ~(align - 1); } void SceneOpenGLDecorationRenderer::resizeTexture() { QRect left, top, right, bottom; client()->client()->layoutDecorationRects(left, top, right, bottom); QSize size; size.rwidth() = qMax(qMax(top.width(), bottom.width()), qMax(left.height(), right.height())); size.rheight() = top.height() + bottom.height() + left.width() + right.width() + 3; size.rwidth() = align(size.width(), 128); size *= client()->client()->screenScale(); if (m_texture && m_texture->size() == size) return; if (!size.isEmpty()) { m_texture.reset(new GLTexture(GL_RGBA8, size.width(), size.height())); m_texture->setYInverted(true); m_texture->setWrapMode(GL_CLAMP_TO_EDGE); m_texture->clear(); } else { m_texture.reset(); } } void SceneOpenGLDecorationRenderer::reparent(Deleted *deleted) { render(); Renderer::reparent(deleted); } OpenGLFactory::OpenGLFactory(QObject *parent) : SceneFactory(parent) { } OpenGLFactory::~OpenGLFactory() = default; Scene *OpenGLFactory::create(QObject *parent) const { qCDebug(KWIN_OPENGL) << "Initializing OpenGL compositing"; // Some broken drivers crash on glXQuery() so to prevent constant KWin crashes: if (kwinApp()->platform()->openGLCompositingIsBroken()) { qCWarning(KWIN_OPENGL) << "KWin has detected that your OpenGL library is unsafe to use"; return nullptr; } kwinApp()->platform()->createOpenGLSafePoint(Platform::OpenGLSafePoint::PreInit); auto s = SceneOpenGL::createScene(parent); kwinApp()->platform()->createOpenGLSafePoint(Platform::OpenGLSafePoint::PostInit); if (s && s->initFailed()) { delete s; return nullptr; } return s; } } // namespace diff --git a/plugins/scenes/qpainter/scene_qpainter.cpp b/plugins/scenes/qpainter/scene_qpainter.cpp index 0fffdfc39..79672f89e 100644 --- a/plugins/scenes/qpainter/scene_qpainter.cpp +++ b/plugins/scenes/qpainter/scene_qpainter.cpp @@ -1,878 +1,888 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2013 Martin Gräßlin 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 . *********************************************************************/ #include "scene_qpainter.h" // KWin #include "client.h" #include "composite.h" #include "cursor.h" #include "deleted.h" #include "effects.h" #include "main.h" #include "screens.h" #include "toplevel.h" #include "platform.h" #include "wayland_server.h" #include #include #include #include "decorations/decoratedclient.h" // Qt #include #include #include #include namespace KWin { //**************************************** // SceneQPainter //**************************************** SceneQPainter *SceneQPainter::createScene(QObject *parent) { QScopedPointer backend(kwinApp()->platform()->createQPainterBackend()); if (backend.isNull()) { return nullptr; } if (backend->isFailed()) { return nullptr; } return new SceneQPainter(backend.take(), parent); } SceneQPainter::SceneQPainter(QPainterBackend *backend, QObject *parent) : Scene(parent) , m_backend(backend) , m_painter(new QPainter()) { } SceneQPainter::~SceneQPainter() { } CompositingType SceneQPainter::compositingType() const { return QPainterCompositing; } bool SceneQPainter::initFailed() const { return false; } void SceneQPainter::paintGenericScreen(int mask, ScreenPaintData data) { m_painter->save(); m_painter->translate(data.xTranslation(), data.yTranslation()); m_painter->scale(data.xScale(), data.yScale()); Scene::paintGenericScreen(mask, data); m_painter->restore(); } qint64 SceneQPainter::paint(QRegion damage, ToplevelList toplevels) { QElapsedTimer renderTimer; renderTimer.start(); createStackingOrder(toplevels); int mask = 0; m_backend->prepareRenderingFrame(); if (m_backend->perScreenRendering()) { const bool needsFullRepaint = m_backend->needsFullRepaint(); if (needsFullRepaint) { mask |= Scene::PAINT_SCREEN_BACKGROUND_FIRST; damage = screens()->geometry(); } QRegion overallUpdate; for (int i = 0; i < screens()->count(); ++i) { const QRect geometry = screens()->geometry(i); QImage *buffer = m_backend->bufferForScreen(i); if (!buffer || buffer->isNull()) { continue; } m_painter->begin(buffer); m_painter->save(); m_painter->setWindow(geometry); QRegion updateRegion, validRegion; paintScreen(&mask, damage.intersected(geometry), QRegion(), &updateRegion, &validRegion); overallUpdate = overallUpdate.united(updateRegion); paintCursor(); m_painter->restore(); m_painter->end(); } m_backend->showOverlay(); m_backend->present(mask, overallUpdate); } else { m_painter->begin(m_backend->buffer()); m_painter->setClipping(true); m_painter->setClipRegion(damage); if (m_backend->needsFullRepaint()) { mask |= Scene::PAINT_SCREEN_BACKGROUND_FIRST; damage = screens()->geometry(); } QRegion updateRegion, validRegion; paintScreen(&mask, damage, QRegion(), &updateRegion, &validRegion); paintCursor(); m_backend->showOverlay(); m_painter->end(); m_backend->present(mask, updateRegion); } // do cleanup clearStackingOrder(); emit frameRendered(); return renderTimer.nsecsElapsed(); } void SceneQPainter::paintBackground(QRegion region) { m_painter->setBrush(Qt::black); for (const QRect &rect : region) { m_painter->drawRect(rect); } } void SceneQPainter::paintCursor() { if (!kwinApp()->platform()->usesSoftwareCursor()) { return; } const QImage img = kwinApp()->platform()->softwareCursor(); if (img.isNull()) { return; } const QPoint cursorPos = Cursor::pos(); const QPoint hotspot = kwinApp()->platform()->softwareCursorHotspot(); m_painter->drawImage(cursorPos - hotspot, img); kwinApp()->platform()->markCursorAsRendered(); } Scene::Window *SceneQPainter::createWindow(Toplevel *toplevel) { return new SceneQPainter::Window(this, toplevel); } Scene::EffectFrame *SceneQPainter::createEffectFrame(EffectFrameImpl *frame) { return new QPainterEffectFrame(frame, this); } Shadow *SceneQPainter::createShadow(Toplevel *toplevel) { return new SceneQPainterShadow(toplevel); } void SceneQPainter::screenGeometryChanged(const QSize &size) { Scene::screenGeometryChanged(size); m_backend->screenGeometryChanged(size); } QImage *SceneQPainter::qpainterRenderBuffer() const { return m_backend->buffer(); } //**************************************** // SceneQPainter::Window //**************************************** SceneQPainter::Window::Window(SceneQPainter *scene, Toplevel *c) : Scene::Window(c) , m_scene(scene) { } SceneQPainter::Window::~Window() { discardShape(); } static void paintSubSurface(QPainter *painter, const QPoint &pos, QPainterWindowPixmap *pixmap) { QPoint p = pos; if (!pixmap->subSurface().isNull()) { p += pixmap->subSurface()->position(); } painter->drawImage(QRect(pos, pixmap->size()), pixmap->image()); const auto &children = pixmap->children(); for (auto it = children.begin(); it != children.end(); ++it) { auto pixmap = static_cast(*it); if (pixmap->subSurface().isNull() || pixmap->subSurface()->surface().isNull() || !pixmap->subSurface()->surface()->isMapped()) { continue; } paintSubSurface(painter, p, pixmap); } } void SceneQPainter::Window::performPaint(int mask, QRegion region, WindowPaintData data) { if (!(mask & (PAINT_WINDOW_TRANSFORMED | PAINT_SCREEN_TRANSFORMED))) region &= toplevel->visibleRect(); if (region.isEmpty()) return; QPainterWindowPixmap *pixmap = windowPixmap(); if (!pixmap || !pixmap->isValid()) { return; } if (!toplevel->damage().isEmpty()) { pixmap->updateBuffer(); toplevel->resetDamage(); } QPainter *scenePainter = m_scene->scenePainter(); QPainter *painter = scenePainter; painter->save(); painter->setClipRegion(region); painter->setClipping(true); painter->translate(x(), y()); if (mask & PAINT_WINDOW_TRANSFORMED) { painter->translate(data.xTranslation(), data.yTranslation()); painter->scale(data.xScale(), data.yScale()); } const bool opaque = qFuzzyCompare(1.0, data.opacity()); QImage tempImage; QPainter tempPainter; if (!opaque) { // need a temp render target which we later on blit to the screen tempImage = QImage(toplevel->visibleRect().size(), QImage::Format_ARGB32_Premultiplied); tempImage.fill(Qt::transparent); tempPainter.begin(&tempImage); tempPainter.save(); tempPainter.translate(toplevel->geometry().topLeft() - toplevel->visibleRect().topLeft()); painter = &tempPainter; } renderShadow(painter); renderWindowDecorations(painter); // render content const QRect target = QRect(toplevel->clientPos(), toplevel->clientSize()); QSize srcSize = pixmap->image().size(); if (pixmap->surface() && pixmap->surface()->scale() == 1 && srcSize != toplevel->clientSize()) { // special case for XWayland windows srcSize = toplevel->clientSize(); } const QRect src = QRect(toplevel->clientPos() + toplevel->clientContentPos(), srcSize); painter->drawImage(target, pixmap->image(), src); // render subsurfaces const auto &children = pixmap->children(); for (auto pixmap : children) { if (pixmap->subSurface().isNull() || pixmap->subSurface()->surface().isNull() || !pixmap->subSurface()->surface()->isMapped()) { continue; } paintSubSurface(painter, toplevel->clientPos(), static_cast(pixmap)); } if (!opaque) { tempPainter.restore(); tempPainter.setCompositionMode(QPainter::CompositionMode_DestinationIn); QColor translucent(Qt::transparent); translucent.setAlphaF(data.opacity()); tempPainter.fillRect(QRect(QPoint(0, 0), toplevel->visibleRect().size()), translucent); tempPainter.end(); painter = scenePainter; painter->drawImage(toplevel->visibleRect().topLeft() - toplevel->geometry().topLeft(), tempImage); } painter->restore(); } void SceneQPainter::Window::renderShadow(QPainter* painter) { if (!toplevel->shadow()) { return; } SceneQPainterShadow *shadow = static_cast(toplevel->shadow()); const QImage &shadowTexture = shadow->shadowTexture(); const WindowQuadList &shadowQuads = shadow->shadowQuads(); for (const auto &q : shadowQuads) { auto topLeft = q[0]; auto bottomRight = q[2]; QRectF target(topLeft.x(), topLeft.y(), bottomRight.x() - topLeft.x(), bottomRight.y() - topLeft.y()); QRectF source(topLeft.textureX(), topLeft.textureY(), bottomRight.textureX() - topLeft.textureX(), bottomRight.textureY() - topLeft.textureY()); painter->drawImage(target, shadowTexture, source); } } void SceneQPainter::Window::renderWindowDecorations(QPainter *painter) { // TODO: custom decoration opacity AbstractClient *client = dynamic_cast(toplevel); Deleted *deleted = dynamic_cast(toplevel); if (!client && !deleted) { return; } bool noBorder = true; const SceneQPainterDecorationRenderer *renderer = nullptr; QRect dtr, dlr, drr, dbr; if (client && !client->noBorder()) { if (client->isDecorated()) { if (SceneQPainterDecorationRenderer *r = static_cast(client->decoratedClient()->renderer())) { r->render(); renderer = r; } } client->layoutDecorationRects(dlr, dtr, drr, dbr); noBorder = false; } else if (deleted && !deleted->noBorder()) { noBorder = false; deleted->layoutDecorationRects(dlr, dtr, drr, dbr); renderer = static_cast(deleted->decorationRenderer()); } if (noBorder || !renderer) { return; } painter->drawImage(dtr, renderer->image(SceneQPainterDecorationRenderer::DecorationPart::Top)); painter->drawImage(dlr, renderer->image(SceneQPainterDecorationRenderer::DecorationPart::Left)); painter->drawImage(drr, renderer->image(SceneQPainterDecorationRenderer::DecorationPart::Right)); painter->drawImage(dbr, renderer->image(SceneQPainterDecorationRenderer::DecorationPart::Bottom)); } WindowPixmap *SceneQPainter::Window::createWindowPixmap() { return new QPainterWindowPixmap(this); } Decoration::Renderer *SceneQPainter::createDecorationRenderer(Decoration::DecoratedClientImpl *impl) { return new SceneQPainterDecorationRenderer(impl); } //**************************************** // QPainterWindowPixmap //**************************************** QPainterWindowPixmap::QPainterWindowPixmap(Scene::Window *window) : WindowPixmap(window) { } QPainterWindowPixmap::QPainterWindowPixmap(const QPointer &subSurface, WindowPixmap *parent) : WindowPixmap(subSurface, parent) { } QPainterWindowPixmap::~QPainterWindowPixmap() { } void QPainterWindowPixmap::create() { if (isValid()) { return; } KWin::WindowPixmap::create(); if (!isValid()) { return; } + if (!surface()) { + // That's an internal client. + m_image = internalImage(); + return; + } // performing deep copy, this could probably be improved m_image = buffer()->data().copy(); if (auto s = surface()) { s->resetTrackedDamage(); } } WindowPixmap *QPainterWindowPixmap::createChild(const QPointer &subSurface) { return new QPainterWindowPixmap(subSurface, this); } void QPainterWindowPixmap::updateBuffer() { const auto oldBuffer = buffer(); WindowPixmap::updateBuffer(); const auto &b = buffer(); + if (!surface()) { + // That's an internal client. + m_image = internalImage(); + return; + } if (b.isNull()) { m_image = QImage(); return; } if (b == oldBuffer) { return; } // perform deep copy m_image = b->data().copy(); if (auto s = surface()) { s->resetTrackedDamage(); } } bool QPainterWindowPixmap::isValid() const { if (!m_image.isNull()) { return true; } return WindowPixmap::isValid(); } QPainterEffectFrame::QPainterEffectFrame(EffectFrameImpl *frame, SceneQPainter *scene) : Scene::EffectFrame(frame) , m_scene(scene) { } QPainterEffectFrame::~QPainterEffectFrame() { } void QPainterEffectFrame::render(QRegion region, double opacity, double frameOpacity) { Q_UNUSED(region) Q_UNUSED(opacity) // TODO: adjust opacity if (m_effectFrame->geometry().isEmpty()) { return; // Nothing to display } QPainter *painter = m_scene->scenePainter(); // Render the actual frame if (m_effectFrame->style() == EffectFrameUnstyled) { painter->save(); painter->setPen(Qt::NoPen); QColor color(Qt::black); color.setAlphaF(frameOpacity); painter->setBrush(color); painter->setRenderHint(QPainter::Antialiasing); painter->drawRoundedRect(m_effectFrame->geometry().adjusted(-5, -5, 5, 5), 5.0, 5.0); painter->restore(); } else if (m_effectFrame->style() == EffectFrameStyled) { qreal left, top, right, bottom; m_effectFrame->frame().getMargins(left, top, right, bottom); // m_geometry is the inner geometry QRect geom = m_effectFrame->geometry().adjusted(-left, -top, right, bottom); painter->drawPixmap(geom, m_effectFrame->frame().framePixmap()); } if (!m_effectFrame->selection().isNull()) { painter->drawPixmap(m_effectFrame->selection(), m_effectFrame->selectionFrame().framePixmap()); } // Render icon if (!m_effectFrame->icon().isNull() && !m_effectFrame->iconSize().isEmpty()) { const QPoint topLeft(m_effectFrame->geometry().x(), m_effectFrame->geometry().center().y() - m_effectFrame->iconSize().height() / 2); const QRect geom = QRect(topLeft, m_effectFrame->iconSize()); painter->drawPixmap(geom, m_effectFrame->icon().pixmap(m_effectFrame->iconSize())); } // Render text if (!m_effectFrame->text().isEmpty()) { // Determine position on texture to paint text QRect rect(QPoint(0, 0), m_effectFrame->geometry().size()); if (!m_effectFrame->icon().isNull() && !m_effectFrame->iconSize().isEmpty()) { rect.setLeft(m_effectFrame->iconSize().width()); } // If static size elide text as required QString text = m_effectFrame->text(); if (m_effectFrame->isStatic()) { QFontMetrics metrics(m_effectFrame->text()); text = metrics.elidedText(text, Qt::ElideRight, rect.width()); } painter->save(); painter->setFont(m_effectFrame->font()); if (m_effectFrame->style() == EffectFrameStyled) { painter->setPen(m_effectFrame->styledTextColor()); } else { // TODO: What about no frame? Custom color setting required painter->setPen(Qt::white); } painter->drawText(rect.translated(m_effectFrame->geometry().topLeft()), m_effectFrame->alignment(), text); painter->restore(); } } //**************************************** // QPainterShadow //**************************************** SceneQPainterShadow::SceneQPainterShadow(Toplevel* toplevel) : Shadow(toplevel) { } SceneQPainterShadow::~SceneQPainterShadow() { } void SceneQPainterShadow::buildQuads() { // Do not draw shadows if window width or window height is less than // 5 px. 5 is an arbitrary choice. if (topLevel()->width() < 5 || topLevel()->height() < 5) { m_shadowQuads.clear(); setShadowRegion(QRegion()); return; } const QSizeF top(elementSize(ShadowElementTop)); const QSizeF topRight(elementSize(ShadowElementTopRight)); const QSizeF right(elementSize(ShadowElementRight)); const QSizeF bottomRight(elementSize(ShadowElementBottomRight)); const QSizeF bottom(elementSize(ShadowElementBottom)); const QSizeF bottomLeft(elementSize(ShadowElementBottomLeft)); const QSizeF left(elementSize(ShadowElementLeft)); const QSizeF topLeft(elementSize(ShadowElementTopLeft)); const QRectF outerRect(QPointF(-leftOffset(), -topOffset()), QPointF(topLevel()->width() + rightOffset(), topLevel()->height() + bottomOffset())); const int width = std::max({topLeft.width(), left.width(), bottomLeft.width()}) + std::max(top.width(), bottom.width()) + std::max({topRight.width(), right.width(), bottomRight.width()}); const int height = std::max({topLeft.height(), top.height(), topRight.height()}) + std::max(left.height(), right.height()) + std::max({bottomLeft.height(), bottom.height(), bottomRight.height()}); QRectF topLeftRect(outerRect.topLeft(), topLeft); QRectF topRightRect(outerRect.topRight() - QPointF(topRight.width(), 0), topRight); QRectF bottomRightRect( outerRect.bottomRight() - QPointF(bottomRight.width(), bottomRight.height()), bottomRight); QRectF bottomLeftRect(outerRect.bottomLeft() - QPointF(0, bottomLeft.height()), bottomLeft); // Re-distribute the corner tiles so no one of them is overlapping with others. // By doing this, we assume that shadow's corner tiles are symmetric // and it is OK to not draw top/right/bottom/left tile between corners. // For example, let's say top-left and top-right tiles are overlapping. // In that case, the right side of the top-left tile will be shifted to left, // the left side of the top-right tile will shifted to right, and the top // tile won't be rendered. bool drawTop = true; if (topLeftRect.right() >= topRightRect.left()) { const float halfOverlap = qAbs(topLeftRect.right() - topRightRect.left()) / 2; topLeftRect.setRight(topLeftRect.right() - std::floor(halfOverlap)); topRightRect.setLeft(topRightRect.left() + std::ceil(halfOverlap)); drawTop = false; } bool drawRight = true; if (topRightRect.bottom() >= bottomRightRect.top()) { const float halfOverlap = qAbs(topRightRect.bottom() - bottomRightRect.top()) / 2; topRightRect.setBottom(topRightRect.bottom() - std::floor(halfOverlap)); bottomRightRect.setTop(bottomRightRect.top() + std::ceil(halfOverlap)); drawRight = false; } bool drawBottom = true; if (bottomLeftRect.right() >= bottomRightRect.left()) { const float halfOverlap = qAbs(bottomLeftRect.right() - bottomRightRect.left()) / 2; bottomLeftRect.setRight(bottomLeftRect.right() - std::floor(halfOverlap)); bottomRightRect.setLeft(bottomRightRect.left() + std::ceil(halfOverlap)); drawBottom = false; } bool drawLeft = true; if (topLeftRect.bottom() >= bottomLeftRect.top()) { const float halfOverlap = qAbs(topLeftRect.bottom() - bottomLeftRect.top()) / 2; topLeftRect.setBottom(topLeftRect.bottom() - std::floor(halfOverlap)); bottomLeftRect.setTop(bottomLeftRect.top() + std::ceil(halfOverlap)); drawLeft = false; } qreal tx1 = 0.0, tx2 = 0.0, ty1 = 0.0, ty2 = 0.0; m_shadowQuads.clear(); tx1 = 0.0; ty1 = 0.0; tx2 = topLeftRect.width(); ty2 = topLeftRect.height(); WindowQuad topLeftQuad(WindowQuadShadow); topLeftQuad[0] = WindowVertex(topLeftRect.left(), topLeftRect.top(), tx1, ty1); topLeftQuad[1] = WindowVertex(topLeftRect.right(), topLeftRect.top(), tx2, ty1); topLeftQuad[2] = WindowVertex(topLeftRect.right(), topLeftRect.bottom(), tx2, ty2); topLeftQuad[3] = WindowVertex(topLeftRect.left(), topLeftRect.bottom(), tx1, ty2); m_shadowQuads.append(topLeftQuad); tx1 = width - topRightRect.width(); ty1 = 0.0; tx2 = width; ty2 = topRightRect.height(); WindowQuad topRightQuad(WindowQuadShadow); topRightQuad[0] = WindowVertex(topRightRect.left(), topRightRect.top(), tx1, ty1); topRightQuad[1] = WindowVertex(topRightRect.right(), topRightRect.top(), tx2, ty1); topRightQuad[2] = WindowVertex(topRightRect.right(), topRightRect.bottom(), tx2, ty2); topRightQuad[3] = WindowVertex(topRightRect.left(), topRightRect.bottom(), tx1, ty2); m_shadowQuads.append(topRightQuad); tx1 = width - bottomRightRect.width(); tx2 = width; ty1 = height - bottomRightRect.height(); ty2 = height; WindowQuad bottomRightQuad(WindowQuadShadow); bottomRightQuad[0] = WindowVertex(bottomRightRect.left(), bottomRightRect.top(), tx1, ty1); bottomRightQuad[1] = WindowVertex(bottomRightRect.right(), bottomRightRect.top(), tx2, ty1); bottomRightQuad[2] = WindowVertex(bottomRightRect.right(), bottomRightRect.bottom(), tx2, ty2); bottomRightQuad[3] = WindowVertex(bottomRightRect.left(), bottomRightRect.bottom(), tx1, ty2); m_shadowQuads.append(bottomRightQuad); tx1 = 0.0; tx2 = bottomLeftRect.width(); ty1 = height - bottomLeftRect.height(); ty2 = height; WindowQuad bottomLeftQuad(WindowQuadShadow); bottomLeftQuad[0] = WindowVertex(bottomLeftRect.left(), bottomLeftRect.top(), tx1, ty1); bottomLeftQuad[1] = WindowVertex(bottomLeftRect.right(), bottomLeftRect.top(), tx2, ty1); bottomLeftQuad[2] = WindowVertex(bottomLeftRect.right(), bottomLeftRect.bottom(), tx2, ty2); bottomLeftQuad[3] = WindowVertex(bottomLeftRect.left(), bottomLeftRect.bottom(), tx1, ty2); m_shadowQuads.append(bottomLeftQuad); if (drawTop) { QRectF topRect( topLeftRect.topRight(), topRightRect.bottomLeft()); tx1 = topLeft.width(); ty1 = 0.0; tx2 = width - topRight.width(); ty2 = topRect.height(); WindowQuad topQuad(WindowQuadShadow); topQuad[0] = WindowVertex(topRect.left(), topRect.top(), tx1, ty1); topQuad[1] = WindowVertex(topRect.right(), topRect.top(), tx2, ty1); topQuad[2] = WindowVertex(topRect.right(), topRect.bottom(), tx2, ty2); topQuad[3] = WindowVertex(topRect.left(), topRect.bottom(), tx1, ty2); m_shadowQuads.append(topQuad); } if (drawRight) { QRectF rightRect( topRightRect.bottomLeft(), bottomRightRect.topRight()); tx1 = width - rightRect.width(); ty1 = topRight.height(); tx2 = width; ty2 = height - bottomRight.height(); WindowQuad rightQuad(WindowQuadShadow); rightQuad[0] = WindowVertex(rightRect.left(), rightRect.top(), tx1, ty1); rightQuad[1] = WindowVertex(rightRect.right(), rightRect.top(), tx2, ty1); rightQuad[2] = WindowVertex(rightRect.right(), rightRect.bottom(), tx2, ty2); rightQuad[3] = WindowVertex(rightRect.left(), rightRect.bottom(), tx1, ty2); m_shadowQuads.append(rightQuad); } if (drawBottom) { QRectF bottomRect( bottomLeftRect.topRight(), bottomRightRect.bottomLeft()); tx1 = bottomLeft.width(); ty1 = height - bottomRect.height(); tx2 = width - bottomRight.width(); ty2 = height; WindowQuad bottomQuad(WindowQuadShadow); bottomQuad[0] = WindowVertex(bottomRect.left(), bottomRect.top(), tx1, ty1); bottomQuad[1] = WindowVertex(bottomRect.right(), bottomRect.top(), tx2, ty1); bottomQuad[2] = WindowVertex(bottomRect.right(), bottomRect.bottom(), tx2, ty2); bottomQuad[3] = WindowVertex(bottomRect.left(), bottomRect.bottom(), tx1, ty2); m_shadowQuads.append(bottomQuad); } if (drawLeft) { QRectF leftRect( topLeftRect.bottomLeft(), bottomLeftRect.topRight()); tx1 = 0.0; ty1 = topLeft.height(); tx2 = leftRect.width(); ty2 = height - bottomRight.height(); WindowQuad leftQuad(WindowQuadShadow); leftQuad[0] = WindowVertex(leftRect.left(), leftRect.top(), tx1, ty1); leftQuad[1] = WindowVertex(leftRect.right(), leftRect.top(), tx2, ty1); leftQuad[2] = WindowVertex(leftRect.right(), leftRect.bottom(), tx2, ty2); leftQuad[3] = WindowVertex(leftRect.left(), leftRect.bottom(), tx1, ty2); m_shadowQuads.append(leftQuad); } } bool SceneQPainterShadow::prepareBackend() { if (hasDecorationShadow()) { m_texture = decorationShadowImage(); return true; } const QPixmap &topLeft = shadowPixmap(ShadowElementTopLeft); const QPixmap &top = shadowPixmap(ShadowElementTop); const QPixmap &topRight = shadowPixmap(ShadowElementTopRight); const QPixmap &bottomLeft = shadowPixmap(ShadowElementBottomLeft); const QPixmap &bottom = shadowPixmap(ShadowElementBottom); const QPixmap &bottomRight = shadowPixmap(ShadowElementBottomRight); const QPixmap &left = shadowPixmap(ShadowElementLeft); const QPixmap &right = shadowPixmap(ShadowElementRight); const int width = std::max({topLeft.width(), left.width(), bottomLeft.width()}) + std::max(top.width(), bottom.width()) + std::max({topRight.width(), right.width(), bottomRight.width()}); const int height = std::max({topLeft.height(), top.height(), topRight.height()}) + std::max(left.height(), right.height()) + std::max({bottomLeft.height(), bottom.height(), bottomRight.height()}); if (width == 0 || height == 0) { return false; } QImage image(width, height, QImage::Format_ARGB32_Premultiplied); image.fill(Qt::transparent); QPainter painter; painter.begin(&image); painter.drawPixmap(0, 0, topLeft); painter.drawPixmap(topLeft.width(), 0, top); painter.drawPixmap(width - topRight.width(), 0, topRight); painter.drawPixmap(0, height - bottomLeft.height(), bottomLeft); painter.drawPixmap(bottomLeft.width(), height - bottom.height(), bottom); painter.drawPixmap(width - bottomRight.width(), height - bottomRight.height(), bottomRight); painter.drawPixmap(0, topLeft.height(), left); painter.drawPixmap(width - right.width(), topRight.height(), right); painter.end(); m_texture = image; return true; } //**************************************** // QPainterDecorationRenderer //**************************************** SceneQPainterDecorationRenderer::SceneQPainterDecorationRenderer(Decoration::DecoratedClientImpl *client) : Renderer(client) { connect(this, &Renderer::renderScheduled, client->client(), static_cast(&AbstractClient::addRepaint)); } SceneQPainterDecorationRenderer::~SceneQPainterDecorationRenderer() = default; QImage SceneQPainterDecorationRenderer::image(SceneQPainterDecorationRenderer::DecorationPart part) const { Q_ASSERT(part != DecorationPart::Count); return m_images[int(part)]; } void SceneQPainterDecorationRenderer::render() { const QRegion scheduled = getScheduled(); if (scheduled.isEmpty()) { return; } if (areImageSizesDirty()) { resizeImages(); resetImageSizesDirty(); } auto imageSize = [this](DecorationPart part) { return m_images[int(part)].size() / m_images[int(part)].devicePixelRatio(); }; const QRect top(QPoint(0, 0), imageSize(DecorationPart::Top)); const QRect left(QPoint(0, top.height()), imageSize(DecorationPart::Left)); const QRect right(QPoint(top.width() - imageSize(DecorationPart::Right).width(), top.height()), imageSize(DecorationPart::Right)); const QRect bottom(QPoint(0, left.y() + left.height()), imageSize(DecorationPart::Bottom)); const QRect geometry = scheduled.boundingRect(); auto renderPart = [this](const QRect &rect, const QRect &partRect, int index) { if (rect.isEmpty()) { return; } QPainter painter(&m_images[index]); painter.setRenderHint(QPainter::Antialiasing); painter.setWindow(QRect(partRect.topLeft(), partRect.size() * m_images[index].devicePixelRatio())); painter.setClipRect(rect); painter.save(); // clear existing part painter.setCompositionMode(QPainter::CompositionMode_Source); painter.fillRect(rect, Qt::transparent); painter.restore(); client()->decoration()->paint(&painter, rect); }; renderPart(left.intersected(geometry), left, int(DecorationPart::Left)); renderPart(top.intersected(geometry), top, int(DecorationPart::Top)); renderPart(right.intersected(geometry), right, int(DecorationPart::Right)); renderPart(bottom.intersected(geometry), bottom, int(DecorationPart::Bottom)); } void SceneQPainterDecorationRenderer::resizeImages() { QRect left, top, right, bottom; client()->client()->layoutDecorationRects(left, top, right, bottom); auto checkAndCreate = [this](int index, const QSize &size) { auto dpr = client()->client()->screenScale(); if (m_images[index].size() != size * dpr || m_images[index].devicePixelRatio() != dpr) { m_images[index] = QImage(size * dpr, QImage::Format_ARGB32_Premultiplied); m_images[index].setDevicePixelRatio(dpr); m_images[index].fill(Qt::transparent); } }; checkAndCreate(int(DecorationPart::Left), left.size()); checkAndCreate(int(DecorationPart::Right), right.size()); checkAndCreate(int(DecorationPart::Top), top.size()); checkAndCreate(int(DecorationPart::Bottom), bottom.size()); } void SceneQPainterDecorationRenderer::reparent(Deleted *deleted) { render(); Renderer::reparent(deleted); } QPainterFactory::QPainterFactory(QObject *parent) : SceneFactory(parent) { } QPainterFactory::~QPainterFactory() = default; Scene *QPainterFactory::create(QObject *parent) const { auto s = SceneQPainter::createScene(parent); if (s && s->initFailed()) { delete s; s = nullptr; } return s; } } // KWin diff --git a/pointer_input.cpp b/pointer_input.cpp index 70bddb2dc..35bd01444 100644 --- a/pointer_input.cpp +++ b/pointer_input.cpp @@ -1,1411 +1,1410 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2013, 2016 Martin Gräßlin Copyright (C) 2018 Roman Gilg Copyright (C) 2019 Vlad Zagorodniy 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 . *********************************************************************/ #include "pointer_input.h" #include "platform.h" #include "client.h" #include "effects.h" #include "input_event.h" #include "input_event_spy.h" #include "osd.h" #include "screens.h" #include "shell_client.h" #include "wayland_cursor_theme.h" #include "wayland_server.h" #include "workspace.h" #include "decorations/decoratedclient.h" #include "screens.h" // KDecoration #include // KWayland #include #include #include #include #include #include #include #include // screenlocker #include #include #include #include // Wayland #include #include namespace KWin { static Qt::MouseButton buttonToQtMouseButton(uint32_t button) { switch (button) { case BTN_LEFT: return Qt::LeftButton; case BTN_MIDDLE: return Qt::MiddleButton; case BTN_RIGHT: return Qt::RightButton; case BTN_SIDE: // in QtWayland mapped like that return Qt::ExtraButton1; case BTN_EXTRA: // in QtWayland mapped like that return Qt::ExtraButton2; case BTN_BACK: return Qt::BackButton; case BTN_FORWARD: return Qt::ForwardButton; case BTN_TASK: return Qt::TaskButton; // mapped like that in QtWayland case 0x118: return Qt::ExtraButton6; case 0x119: return Qt::ExtraButton7; case 0x11a: return Qt::ExtraButton8; case 0x11b: return Qt::ExtraButton9; case 0x11c: return Qt::ExtraButton10; case 0x11d: return Qt::ExtraButton11; case 0x11e: return Qt::ExtraButton12; case 0x11f: return Qt::ExtraButton13; } // all other values get mapped to ExtraButton24 // this is actually incorrect but doesn't matter in our usage // KWin internally doesn't use these high extra buttons anyway // it's only needed for recognizing whether buttons are pressed // if multiple buttons are mapped to the value the evaluation whether // buttons are pressed is correct and that's all we care about. return Qt::ExtraButton24; } static bool screenContainsPos(const QPointF &pos) { for (int i = 0; i < screens()->count(); ++i) { if (screens()->geometry(i).contains(pos.toPoint())) { return true; } } return false; } static QPointF confineToBoundingBox(const QPointF &pos, const QRectF &boundingBox) { return QPointF( qBound(boundingBox.left(), pos.x(), boundingBox.right() - 1.0), qBound(boundingBox.top(), pos.y(), boundingBox.bottom() - 1.0) ); } PointerInputRedirection::PointerInputRedirection(InputRedirection* parent) : InputDeviceHandler(parent) , m_cursor(nullptr) , m_supportsWarping(Application::usesLibinput()) { } PointerInputRedirection::~PointerInputRedirection() = default; void PointerInputRedirection::init() { Q_ASSERT(!inited()); m_cursor = new CursorImage(this); setInited(true); InputDeviceHandler::init(); connect(m_cursor, &CursorImage::changed, kwinApp()->platform(), &Platform::cursorChanged); emit m_cursor->changed(); connect(screens(), &Screens::changed, this, &PointerInputRedirection::updateAfterScreenChange); if (waylandServer()->hasScreenLockerIntegration()) { connect(ScreenLocker::KSldApp::self(), &ScreenLocker::KSldApp::lockStateChanged, this, [this] { waylandServer()->seat()->cancelPointerPinchGesture(); waylandServer()->seat()->cancelPointerSwipeGesture(); update(); } ); } connect(workspace(), &QObject::destroyed, this, [this] { setInited(false); }); connect(waylandServer(), &QObject::destroyed, this, [this] { setInited(false); }); connect(waylandServer()->seat(), &KWayland::Server::SeatInterface::dragEnded, this, [this] { // need to force a focused pointer change waylandServer()->seat()->setFocusedPointerSurface(nullptr); setFocus(nullptr); update(); } ); // connect the move resize of all window auto setupMoveResizeConnection = [this] (AbstractClient *c) { connect(c, &AbstractClient::clientStartUserMovedResized, this, &PointerInputRedirection::updateOnStartMoveResize); connect(c, &AbstractClient::clientFinishUserMovedResized, this, &PointerInputRedirection::update); }; const auto clients = workspace()->allClientList(); std::for_each(clients.begin(), clients.end(), setupMoveResizeConnection); connect(workspace(), &Workspace::clientAdded, this, setupMoveResizeConnection); connect(waylandServer(), &WaylandServer::shellClientAdded, this, setupMoveResizeConnection); // warp the cursor to center of screen warp(screens()->geometry().center()); updateAfterScreenChange(); } void PointerInputRedirection::updateOnStartMoveResize() { breakPointerConstraints(focus() ? focus()->surface() : nullptr); disconnectPointerConstraintsConnection(); setFocus(nullptr); waylandServer()->seat()->setFocusedPointerSurface(nullptr); } void PointerInputRedirection::updateToReset() { if (internalWindow()) { disconnect(m_internalWindowConnection); m_internalWindowConnection = QMetaObject::Connection(); QEvent event(QEvent::Leave); QCoreApplication::sendEvent(internalWindow().data(), &event); setInternalWindow(nullptr); } if (decoration()) { QHoverEvent event(QEvent::HoverLeave, QPointF(), QPointF()); QCoreApplication::instance()->sendEvent(decoration()->decoration(), &event); setDecoration(nullptr); } if (focus()) { if (AbstractClient *c = qobject_cast(focus().data())) { c->leaveEvent(); } disconnect(m_focusGeometryConnection); m_focusGeometryConnection = QMetaObject::Connection(); breakPointerConstraints(focus()->surface()); disconnectPointerConstraintsConnection(); setFocus(nullptr); } waylandServer()->seat()->setFocusedPointerSurface(nullptr); } void PointerInputRedirection::processMotion(const QPointF &pos, uint32_t time, LibInput::Device *device) { processMotion(pos, QSizeF(), QSizeF(), time, 0, device); } class PositionUpdateBlocker { public: PositionUpdateBlocker(PointerInputRedirection *pointer) : m_pointer(pointer) { s_counter++; } ~PositionUpdateBlocker() { s_counter--; if (s_counter == 0) { if (!s_scheduledPositions.isEmpty()) { const auto pos = s_scheduledPositions.takeFirst(); m_pointer->processMotion(pos.pos, pos.delta, pos.deltaNonAccelerated, pos.time, pos.timeUsec, nullptr); } } } static bool isPositionBlocked() { return s_counter > 0; } static void schedulePosition(const QPointF &pos, const QSizeF &delta, const QSizeF &deltaNonAccelerated, uint32_t time, quint64 timeUsec) { s_scheduledPositions.append({pos, delta, deltaNonAccelerated, time, timeUsec}); } private: static int s_counter; struct ScheduledPosition { QPointF pos; QSizeF delta; QSizeF deltaNonAccelerated; quint32 time; quint64 timeUsec; }; static QVector s_scheduledPositions; PointerInputRedirection *m_pointer; }; int PositionUpdateBlocker::s_counter = 0; QVector PositionUpdateBlocker::s_scheduledPositions; void PointerInputRedirection::processMotion(const QPointF &pos, const QSizeF &delta, const QSizeF &deltaNonAccelerated, uint32_t time, quint64 timeUsec, LibInput::Device *device) { if (!inited()) { return; } if (PositionUpdateBlocker::isPositionBlocked()) { PositionUpdateBlocker::schedulePosition(pos, delta, deltaNonAccelerated, time, timeUsec); return; } PositionUpdateBlocker blocker(this); updatePosition(pos); MouseEvent event(QEvent::MouseMove, m_pos, Qt::NoButton, m_qtButtons, input()->keyboardModifiers(), time, delta, deltaNonAccelerated, timeUsec, device); event.setModifiersRelevantForGlobalShortcuts(input()->modifiersRelevantForGlobalShortcuts()); update(); input()->processSpies(std::bind(&InputEventSpy::pointerEvent, std::placeholders::_1, &event)); input()->processFilters(std::bind(&InputEventFilter::pointerEvent, std::placeholders::_1, &event, 0)); } void PointerInputRedirection::processButton(uint32_t button, InputRedirection::PointerButtonState state, uint32_t time, LibInput::Device *device) { QEvent::Type type; switch (state) { case InputRedirection::PointerButtonReleased: type = QEvent::MouseButtonRelease; break; case InputRedirection::PointerButtonPressed: type = QEvent::MouseButtonPress; update(); break; default: Q_UNREACHABLE(); return; } updateButton(button, state); MouseEvent event(type, m_pos, buttonToQtMouseButton(button), m_qtButtons, input()->keyboardModifiers(), time, QSizeF(), QSizeF(), 0, device); event.setModifiersRelevantForGlobalShortcuts(input()->modifiersRelevantForGlobalShortcuts()); event.setNativeButton(button); input()->processSpies(std::bind(&InputEventSpy::pointerEvent, std::placeholders::_1, &event)); if (!inited()) { return; } input()->processFilters(std::bind(&InputEventFilter::pointerEvent, std::placeholders::_1, &event, button)); if (state == InputRedirection::PointerButtonReleased) { update(); } } void PointerInputRedirection::processAxis(InputRedirection::PointerAxis axis, qreal delta, qint32 discreteDelta, InputRedirection::PointerAxisSource source, uint32_t time, LibInput::Device *device) { update(); emit input()->pointerAxisChanged(axis, delta); WheelEvent wheelEvent(m_pos, delta, discreteDelta, (axis == InputRedirection::PointerAxisHorizontal) ? Qt::Horizontal : Qt::Vertical, m_qtButtons, input()->keyboardModifiers(), source, time, device); wheelEvent.setModifiersRelevantForGlobalShortcuts(input()->modifiersRelevantForGlobalShortcuts()); input()->processSpies(std::bind(&InputEventSpy::wheelEvent, std::placeholders::_1, &wheelEvent)); if (!inited()) { return; } input()->processFilters(std::bind(&InputEventFilter::wheelEvent, std::placeholders::_1, &wheelEvent)); } void PointerInputRedirection::processSwipeGestureBegin(int fingerCount, quint32 time, KWin::LibInput::Device *device) { Q_UNUSED(device) if (!inited()) { return; } input()->processSpies(std::bind(&InputEventSpy::swipeGestureBegin, std::placeholders::_1, fingerCount, time)); input()->processFilters(std::bind(&InputEventFilter::swipeGestureBegin, std::placeholders::_1, fingerCount, time)); } void PointerInputRedirection::processSwipeGestureUpdate(const QSizeF &delta, quint32 time, KWin::LibInput::Device *device) { Q_UNUSED(device) if (!inited()) { return; } update(); input()->processSpies(std::bind(&InputEventSpy::swipeGestureUpdate, std::placeholders::_1, delta, time)); input()->processFilters(std::bind(&InputEventFilter::swipeGestureUpdate, std::placeholders::_1, delta, time)); } void PointerInputRedirection::processSwipeGestureEnd(quint32 time, KWin::LibInput::Device *device) { Q_UNUSED(device) if (!inited()) { return; } update(); input()->processSpies(std::bind(&InputEventSpy::swipeGestureEnd, std::placeholders::_1, time)); input()->processFilters(std::bind(&InputEventFilter::swipeGestureEnd, std::placeholders::_1, time)); } void PointerInputRedirection::processSwipeGestureCancelled(quint32 time, KWin::LibInput::Device *device) { Q_UNUSED(device) if (!inited()) { return; } update(); input()->processSpies(std::bind(&InputEventSpy::swipeGestureCancelled, std::placeholders::_1, time)); input()->processFilters(std::bind(&InputEventFilter::swipeGestureCancelled, std::placeholders::_1, time)); } void PointerInputRedirection::processPinchGestureBegin(int fingerCount, quint32 time, KWin::LibInput::Device *device) { Q_UNUSED(device) if (!inited()) { return; } update(); input()->processSpies(std::bind(&InputEventSpy::pinchGestureBegin, std::placeholders::_1, fingerCount, time)); input()->processFilters(std::bind(&InputEventFilter::pinchGestureBegin, std::placeholders::_1, fingerCount, time)); } void PointerInputRedirection::processPinchGestureUpdate(qreal scale, qreal angleDelta, const QSizeF &delta, quint32 time, KWin::LibInput::Device *device) { Q_UNUSED(device) if (!inited()) { return; } update(); input()->processSpies(std::bind(&InputEventSpy::pinchGestureUpdate, std::placeholders::_1, scale, angleDelta, delta, time)); input()->processFilters(std::bind(&InputEventFilter::pinchGestureUpdate, std::placeholders::_1, scale, angleDelta, delta, time)); } void PointerInputRedirection::processPinchGestureEnd(quint32 time, KWin::LibInput::Device *device) { Q_UNUSED(device) if (!inited()) { return; } update(); input()->processSpies(std::bind(&InputEventSpy::pinchGestureEnd, std::placeholders::_1, time)); input()->processFilters(std::bind(&InputEventFilter::pinchGestureEnd, std::placeholders::_1, time)); } void PointerInputRedirection::processPinchGestureCancelled(quint32 time, KWin::LibInput::Device *device) { Q_UNUSED(device) if (!inited()) { return; } update(); input()->processSpies(std::bind(&InputEventSpy::pinchGestureCancelled, std::placeholders::_1, time)); input()->processFilters(std::bind(&InputEventFilter::pinchGestureCancelled, std::placeholders::_1, time)); } bool PointerInputRedirection::areButtonsPressed() const { for (auto state : m_buttons) { if (state == InputRedirection::PointerButtonPressed) { return true; } } return false; } bool PointerInputRedirection::focusUpdatesBlocked() { if (!inited()) { return true; } if (waylandServer()->seat()->isDragPointer()) { // ignore during drag and drop return true; } if (waylandServer()->seat()->isTouchSequence()) { // ignore during touch operations return true; } if (input()->isSelectingWindow()) { return true; } if (areButtonsPressed()) { return true; } return false; } void PointerInputRedirection::cleanupInternalWindow(QWindow *old, QWindow *now) { disconnect(m_internalWindowConnection); m_internalWindowConnection = QMetaObject::Connection(); if (old) { // leave internal window - // TODO: do this instead via Wayland protocol as below QEvent leaveEvent(QEvent::Leave); QCoreApplication::sendEvent(old, &leaveEvent); } if (now) { m_internalWindowConnection = connect(internalWindow().data(), &QWindow::visibleChanged, this, [this] (bool visible) { if (!visible) { update(); } } ); } } void PointerInputRedirection::cleanupDecoration(Decoration::DecoratedClientImpl *old, Decoration::DecoratedClientImpl *now) { disconnect(m_decorationGeometryConnection); m_decorationGeometryConnection = QMetaObject::Connection(); workspace()->updateFocusMousePosition(position().toPoint()); if (old) { // send leave event to old decoration QHoverEvent event(QEvent::HoverLeave, QPointF(), QPointF()); QCoreApplication::instance()->sendEvent(old->decoration(), &event); } if (!now) { // left decoration return; } waylandServer()->seat()->setFocusedPointerSurface(nullptr); auto pos = m_pos - now->client()->pos(); QHoverEvent event(QEvent::HoverEnter, pos, pos); QCoreApplication::instance()->sendEvent(now->decoration(), &event); now->client()->processDecorationMove(pos.toPoint(), m_pos.toPoint()); m_decorationGeometryConnection = connect(decoration()->client(), &AbstractClient::geometryChanged, this, [this] { // ensure maximize button gets the leave event when maximizing/restore a window, see BUG 385140 const auto oldDeco = decoration(); update(); if (oldDeco && oldDeco == decoration() && !decoration()->client()->isMove() && !decoration()->client()->isResize() && !areButtonsPressed()) { // position of window did not change, we need to send HoverMotion manually const QPointF p = m_pos - decoration()->client()->pos(); QHoverEvent event(QEvent::HoverMove, p, p); QCoreApplication::instance()->sendEvent(decoration()->decoration(), &event); } }, Qt::QueuedConnection); } static bool s_cursorUpdateBlocking = false; void PointerInputRedirection::focusUpdate(Toplevel *focusOld, Toplevel *focusNow) { if (AbstractClient *ac = qobject_cast(focusOld)) { ac->leaveEvent(); breakPointerConstraints(ac->surface()); disconnectPointerConstraintsConnection(); } disconnect(m_focusGeometryConnection); m_focusGeometryConnection = QMetaObject::Connection(); if (AbstractClient *ac = qobject_cast(focusNow)) { ac->enterEvent(m_pos.toPoint()); workspace()->updateFocusMousePosition(m_pos.toPoint()); } - auto seat = waylandServer()->seat(); - if (!focusNow || !focusNow->surface() || decoration()) { - // no new surface or internal window or on decoration -> cleanup - warpXcbOnSurfaceLeft(nullptr); - seat->setFocusedPointerSurface(nullptr); - return; - } - if (internalWindow()) { // enter internal window - // TODO: do this instead via Wayland protocol as below const auto pos = at()->pos(); QEnterEvent enterEvent(pos, pos, m_pos); QCoreApplication::sendEvent(internalWindow().data(), &enterEvent); } + auto seat = waylandServer()->seat(); + if (!focusNow || !focusNow->surface() || decoration()) { + // Clean up focused pointer surface if there's no client to take focus, + // or the pointer is on a client without surface or on a decoration. + warpXcbOnSurfaceLeft(nullptr); + seat->setFocusedPointerSurface(nullptr); + return; + } + // TODO: add convenient API to update global pos together with updating focused surface warpXcbOnSurfaceLeft(focusNow->surface()); // TODO: why? in order to reset the cursor icon? s_cursorUpdateBlocking = true; seat->setFocusedPointerSurface(nullptr); s_cursorUpdateBlocking = false; seat->setPointerPos(m_pos.toPoint()); seat->setFocusedPointerSurface(focusNow->surface(), focusNow->inputTransformation()); m_focusGeometryConnection = connect(focusNow, &Toplevel::geometryChanged, this, [this] { // TODO: why no assert possible? if (!focus()) { return; } // TODO: can we check on the client instead? if (workspace()->moveResizeClient()) { // don't update while moving return; } auto seat = waylandServer()->seat(); if (focus()->surface() != seat->focusedPointerSurface()) { return; } seat->setFocusedPointerSurfaceTransformation(focus()->inputTransformation()); } ); m_constraintsConnection = connect(focusNow->surface(), &KWayland::Server::SurfaceInterface::pointerConstraintsChanged, this, &PointerInputRedirection::updatePointerConstraints); m_constraintsActivatedConnection = connect(workspace(), &Workspace::clientActivated, this, &PointerInputRedirection::updatePointerConstraints); updatePointerConstraints(); } void PointerInputRedirection::breakPointerConstraints(KWayland::Server::SurfaceInterface *surface) { // cancel pointer constraints if (surface) { auto c = surface->confinedPointer(); if (c && c->isConfined()) { c->setConfined(false); } auto l = surface->lockedPointer(); if (l && l->isLocked()) { l->setLocked(false); } } disconnectConfinedPointerRegionConnection(); m_confined = false; m_locked = false; } void PointerInputRedirection::disconnectConfinedPointerRegionConnection() { disconnect(m_confinedPointerRegionConnection); m_confinedPointerRegionConnection = QMetaObject::Connection(); } void PointerInputRedirection::disconnectLockedPointerAboutToBeUnboundConnection() { disconnect(m_lockedPointerAboutToBeUnboundConnection); m_lockedPointerAboutToBeUnboundConnection = QMetaObject::Connection(); } void PointerInputRedirection::disconnectPointerConstraintsConnection() { disconnect(m_constraintsConnection); m_constraintsConnection = QMetaObject::Connection(); disconnect(m_constraintsActivatedConnection); m_constraintsActivatedConnection = QMetaObject::Connection(); } template static QRegion getConstraintRegion(Toplevel *t, T *constraint) { const QRegion windowShape = t->inputShape(); const QRegion windowRegion = windowShape.isEmpty() ? QRegion(0, 0, t->clientSize().width(), t->clientSize().height()) : windowShape; const QRegion intersected = constraint->region().isEmpty() ? windowRegion : windowRegion.intersected(constraint->region()); return intersected.translated(t->pos() + t->clientPos()); } void PointerInputRedirection::setEnableConstraints(bool set) { if (m_enableConstraints == set) { return; } m_enableConstraints = set; updatePointerConstraints(); } void PointerInputRedirection::updatePointerConstraints() { if (focus().isNull()) { return; } const auto s = focus()->surface(); if (!s) { return; } if (s != waylandServer()->seat()->focusedPointerSurface()) { return; } if (!supportsWarping()) { return; } const bool canConstrain = m_enableConstraints && focus() == workspace()->activeClient(); const auto cf = s->confinedPointer(); if (cf) { if (cf->isConfined()) { if (!canConstrain) { cf->setConfined(false); m_confined = false; disconnectConfinedPointerRegionConnection(); } return; } const QRegion r = getConstraintRegion(focus().data(), cf.data()); if (canConstrain && r.contains(m_pos.toPoint())) { cf->setConfined(true); m_confined = true; m_confinedPointerRegionConnection = connect(cf.data(), &KWayland::Server::ConfinedPointerInterface::regionChanged, this, [this] { if (!focus()) { return; } const auto s = focus()->surface(); if (!s) { return; } const auto cf = s->confinedPointer(); if (!getConstraintRegion(focus().data(), cf.data()).contains(m_pos.toPoint())) { // pointer no longer in confined region, break the confinement cf->setConfined(false); m_confined = false; } else { if (!cf->isConfined()) { cf->setConfined(true); m_confined = true; } } } ); return; } } else { m_confined = false; disconnectConfinedPointerRegionConnection(); } const auto lock = s->lockedPointer(); if (lock) { if (lock->isLocked()) { if (!canConstrain) { const auto hint = lock->cursorPositionHint(); lock->setLocked(false); m_locked = false; disconnectLockedPointerAboutToBeUnboundConnection(); if (! (hint.x() < 0 || hint.y() < 0) && focus()) { processMotion(focus()->pos() - focus()->clientContentPos() + hint, waylandServer()->seat()->timestamp()); } } return; } const QRegion r = getConstraintRegion(focus().data(), lock.data()); if (canConstrain && r.contains(m_pos.toPoint())) { lock->setLocked(true); m_locked = true; // The client might cancel pointer locking from its side by unbinding the LockedPointerInterface. // In this case the cached cursor position hint must be fetched before the resource goes away m_lockedPointerAboutToBeUnboundConnection = connect(lock.data(), &KWayland::Server::LockedPointerInterface::aboutToBeUnbound, this, [this, lock]() { const auto hint = lock->cursorPositionHint(); if (hint.x() < 0 || hint.y() < 0 || !focus()) { return; } auto globalHint = focus()->pos() - focus()->clientContentPos() + hint; // When the resource finally goes away, reposition the cursor according to the hint connect(lock.data(), &KWayland::Server::LockedPointerInterface::unbound, this, [this, globalHint]() { processMotion(globalHint, waylandServer()->seat()->timestamp()); }); } ); // TODO: connect to region change - is it needed at all? If the pointer is locked it's always in the region } } else { m_locked = false; disconnectLockedPointerAboutToBeUnboundConnection(); } } void PointerInputRedirection::warpXcbOnSurfaceLeft(KWayland::Server::SurfaceInterface *newSurface) { auto xc = waylandServer()->xWaylandConnection(); if (!xc) { // No XWayland, no point in warping the x cursor return; } const auto c = kwinApp()->x11Connection(); if (!c) { return; } static bool s_hasXWayland119 = xcb_get_setup(c)->release_number >= 11900000; if (s_hasXWayland119) { return; } if (newSurface && newSurface->client() == xc) { // new window is an X window return; } auto s = waylandServer()->seat()->focusedPointerSurface(); if (!s || s->client() != xc) { // pointer was not on an X window return; } // warp pointer to 0/0 to trigger leave events on previously focused X window xcb_warp_pointer(c, XCB_WINDOW_NONE, kwinApp()->x11RootWindow(), 0, 0, 0, 0, 0, 0), xcb_flush(c); } QPointF PointerInputRedirection::applyPointerConfinement(const QPointF &pos) const { if (!focus()) { return pos; } auto s = focus()->surface(); if (!s) { return pos; } auto cf = s->confinedPointer(); if (!cf) { return pos; } if (!cf->isConfined()) { return pos; } const QRegion confinementRegion = getConstraintRegion(focus().data(), cf.data()); if (confinementRegion.contains(pos.toPoint())) { return pos; } QPointF p = pos; // allow either x or y to pass p = QPointF(m_pos.x(), pos.y()); if (confinementRegion.contains(p.toPoint())) { return p; } p = QPointF(pos.x(), m_pos.y()); if (confinementRegion.contains(p.toPoint())) { return p; } return m_pos; } void PointerInputRedirection::updatePosition(const QPointF &pos) { if (m_locked) { // locked pointer should not move return; } // verify that at least one screen contains the pointer position QPointF p = pos; if (!screenContainsPos(p)) { const QRectF unitedScreensGeometry = screens()->geometry(); p = confineToBoundingBox(p, unitedScreensGeometry); if (!screenContainsPos(p)) { const QRectF currentScreenGeometry = screens()->geometry(screens()->number(m_pos.toPoint())); p = confineToBoundingBox(p, currentScreenGeometry); } } p = applyPointerConfinement(p); if (p == m_pos) { // didn't change due to confinement return; } // verify screen confinement if (!screenContainsPos(p)) { return; } m_pos = p; emit input()->globalPointerChanged(m_pos); } void PointerInputRedirection::updateButton(uint32_t button, InputRedirection::PointerButtonState state) { m_buttons[button] = state; // update Qt buttons m_qtButtons = Qt::NoButton; for (auto it = m_buttons.constBegin(); it != m_buttons.constEnd(); ++it) { if (it.value() == InputRedirection::PointerButtonReleased) { continue; } m_qtButtons |= buttonToQtMouseButton(it.key()); } emit input()->pointerButtonStateChanged(button, state); } void PointerInputRedirection::warp(const QPointF &pos) { if (supportsWarping()) { kwinApp()->platform()->warpPointer(pos); processMotion(pos, waylandServer()->seat()->timestamp()); } } bool PointerInputRedirection::supportsWarping() const { if (!inited()) { return false; } if (m_supportsWarping) { return true; } if (kwinApp()->platform()->supportsPointerWarping()) { return true; } return false; } void PointerInputRedirection::updateAfterScreenChange() { if (!inited()) { return; } if (screenContainsPos(m_pos)) { // pointer still on a screen return; } // pointer no longer on a screen, reposition to closes screen const QPointF pos = screens()->geometry(screens()->number(m_pos.toPoint())).center(); // TODO: better way to get timestamps processMotion(pos, waylandServer()->seat()->timestamp()); } QImage PointerInputRedirection::cursorImage() const { if (!inited()) { return QImage(); } return m_cursor->image(); } QPoint PointerInputRedirection::cursorHotSpot() const { if (!inited()) { return QPoint(); } return m_cursor->hotSpot(); } void PointerInputRedirection::markCursorAsRendered() { if (!inited()) { return; } m_cursor->markAsRendered(); } QPointF PointerInputRedirection::position() const { return m_pos.toPoint(); } void PointerInputRedirection::setEffectsOverrideCursor(Qt::CursorShape shape) { if (!inited()) { return; } // current pointer focus window should get a leave event update(); m_cursor->setEffectsOverrideCursor(shape); } void PointerInputRedirection::removeEffectsOverrideCursor() { if (!inited()) { return; } // cursor position might have changed while there was an effect in place update(); m_cursor->removeEffectsOverrideCursor(); } void PointerInputRedirection::setWindowSelectionCursor(const QByteArray &shape) { if (!inited()) { return; } // send leave to current pointer focus window updateToReset(); m_cursor->setWindowSelectionCursor(shape); } void PointerInputRedirection::removeWindowSelectionCursor() { if (!inited()) { return; } update(); m_cursor->removeWindowSelectionCursor(); } CursorImage::CursorImage(PointerInputRedirection *parent) : QObject(parent) , m_pointer(parent) { connect(waylandServer()->seat(), &KWayland::Server::SeatInterface::focusedPointerChanged, this, &CursorImage::update); connect(waylandServer()->seat(), &KWayland::Server::SeatInterface::dragStarted, this, &CursorImage::updateDrag); connect(waylandServer()->seat(), &KWayland::Server::SeatInterface::dragEnded, this, [this] { disconnect(m_drag.connection); reevaluteSource(); } ); if (waylandServer()->hasScreenLockerIntegration()) { connect(ScreenLocker::KSldApp::self(), &ScreenLocker::KSldApp::lockStateChanged, this, &CursorImage::reevaluteSource); } connect(m_pointer, &PointerInputRedirection::decorationChanged, this, &CursorImage::updateDecoration); // connect the move resize of all window auto setupMoveResizeConnection = [this] (AbstractClient *c) { connect(c, &AbstractClient::moveResizedChanged, this, &CursorImage::updateMoveResize); connect(c, &AbstractClient::moveResizeCursorChanged, this, &CursorImage::updateMoveResize); }; const auto clients = workspace()->allClientList(); std::for_each(clients.begin(), clients.end(), setupMoveResizeConnection); connect(workspace(), &Workspace::clientAdded, this, setupMoveResizeConnection); connect(waylandServer(), &WaylandServer::shellClientAdded, this, setupMoveResizeConnection); loadThemeCursor(Qt::ArrowCursor, &m_fallbackCursor); if (m_cursorTheme) { connect(m_cursorTheme, &WaylandCursorTheme::themeChanged, this, [this] { m_cursors.clear(); m_cursorsByName.clear(); loadThemeCursor(Qt::ArrowCursor, &m_fallbackCursor); updateDecorationCursor(); updateMoveResize(); // TODO: update effects } ); } m_surfaceRenderedTimer.start(); } CursorImage::~CursorImage() = default; void CursorImage::markAsRendered() { if (m_currentSource == CursorSource::DragAndDrop) { // always sending a frame rendered to the drag icon surface to not freeze QtWayland (see https://bugreports.qt.io/browse/QTBUG-51599 ) if (auto ddi = waylandServer()->seat()->dragSource()) { if (auto s = ddi->icon()) { s->frameRendered(m_surfaceRenderedTimer.elapsed()); } } auto p = waylandServer()->seat()->dragPointer(); if (!p) { return; } auto c = p->cursor(); if (!c) { return; } auto cursorSurface = c->surface(); if (cursorSurface.isNull()) { return; } cursorSurface->frameRendered(m_surfaceRenderedTimer.elapsed()); return; } if (m_currentSource != CursorSource::LockScreen && m_currentSource != CursorSource::PointerSurface) { return; } auto p = waylandServer()->seat()->focusedPointer(); if (!p) { return; } auto c = p->cursor(); if (!c) { return; } auto cursorSurface = c->surface(); if (cursorSurface.isNull()) { return; } cursorSurface->frameRendered(m_surfaceRenderedTimer.elapsed()); } void CursorImage::update() { if (s_cursorUpdateBlocking) { return; } using namespace KWayland::Server; disconnect(m_serverCursor.connection); auto p = waylandServer()->seat()->focusedPointer(); if (p) { m_serverCursor.connection = connect(p, &PointerInterface::cursorChanged, this, &CursorImage::updateServerCursor); } else { m_serverCursor.connection = QMetaObject::Connection(); reevaluteSource(); } } void CursorImage::updateDecoration() { disconnect(m_decorationConnection); auto deco = m_pointer->decoration(); AbstractClient *c = deco.isNull() ? nullptr : deco->client(); if (c) { m_decorationConnection = connect(c, &AbstractClient::moveResizeCursorChanged, this, &CursorImage::updateDecorationCursor); } else { m_decorationConnection = QMetaObject::Connection(); } updateDecorationCursor(); } void CursorImage::updateDecorationCursor() { m_decorationCursor.image = QImage(); m_decorationCursor.hotSpot = QPoint(); auto deco = m_pointer->decoration(); if (AbstractClient *c = deco.isNull() ? nullptr : deco->client()) { loadThemeCursor(c->cursor(), &m_decorationCursor); if (m_currentSource == CursorSource::Decoration) { emit changed(); } } reevaluteSource(); } void CursorImage::updateMoveResize() { m_moveResizeCursor.image = QImage(); m_moveResizeCursor.hotSpot = QPoint(); if (AbstractClient *c = workspace()->moveResizeClient()) { loadThemeCursor(c->cursor(), &m_moveResizeCursor); if (m_currentSource == CursorSource::MoveResize) { emit changed(); } } reevaluteSource(); } void CursorImage::updateServerCursor() { m_serverCursor.image = QImage(); m_serverCursor.hotSpot = QPoint(); reevaluteSource(); const bool needsEmit = m_currentSource == CursorSource::LockScreen || m_currentSource == CursorSource::PointerSurface; auto p = waylandServer()->seat()->focusedPointer(); if (!p) { if (needsEmit) { emit changed(); } return; } auto c = p->cursor(); if (!c) { if (needsEmit) { emit changed(); } return; } auto cursorSurface = c->surface(); if (cursorSurface.isNull()) { if (needsEmit) { emit changed(); } return; } auto buffer = cursorSurface.data()->buffer(); if (!buffer) { if (needsEmit) { emit changed(); } return; } m_serverCursor.hotSpot = c->hotspot(); m_serverCursor.image = buffer->data().copy(); m_serverCursor.image.setDevicePixelRatio(cursorSurface->scale()); if (needsEmit) { emit changed(); } } void CursorImage::loadTheme() { if (m_cursorTheme) { return; } // check whether we can create it if (waylandServer()->internalShmPool()) { m_cursorTheme = new WaylandCursorTheme(waylandServer()->internalShmPool(), this); connect(waylandServer(), &WaylandServer::terminatingInternalClientConnection, this, [this] { delete m_cursorTheme; m_cursorTheme = nullptr; } ); } } void CursorImage::setEffectsOverrideCursor(Qt::CursorShape shape) { loadThemeCursor(shape, &m_effectsCursor); if (m_currentSource == CursorSource::EffectsOverride) { emit changed(); } reevaluteSource(); } void CursorImage::removeEffectsOverrideCursor() { reevaluteSource(); } void CursorImage::setWindowSelectionCursor(const QByteArray &shape) { if (shape.isEmpty()) { loadThemeCursor(Qt::CrossCursor, &m_windowSelectionCursor); } else { loadThemeCursor(shape, &m_windowSelectionCursor); } if (m_currentSource == CursorSource::WindowSelector) { emit changed(); } reevaluteSource(); } void CursorImage::removeWindowSelectionCursor() { reevaluteSource(); } void CursorImage::updateDrag() { using namespace KWayland::Server; disconnect(m_drag.connection); m_drag.cursor.image = QImage(); m_drag.cursor.hotSpot = QPoint(); reevaluteSource(); if (auto p = waylandServer()->seat()->dragPointer()) { m_drag.connection = connect(p, &PointerInterface::cursorChanged, this, &CursorImage::updateDragCursor); } else { m_drag.connection = QMetaObject::Connection(); } updateDragCursor(); } void CursorImage::updateDragCursor() { m_drag.cursor.image = QImage(); m_drag.cursor.hotSpot = QPoint(); const bool needsEmit = m_currentSource == CursorSource::DragAndDrop; QImage additionalIcon; if (auto ddi = waylandServer()->seat()->dragSource()) { if (auto dragIcon = ddi->icon()) { if (auto buffer = dragIcon->buffer()) { additionalIcon = buffer->data().copy(); } } } auto p = waylandServer()->seat()->dragPointer(); if (!p) { if (needsEmit) { emit changed(); } return; } auto c = p->cursor(); if (!c) { if (needsEmit) { emit changed(); } return; } auto cursorSurface = c->surface(); if (cursorSurface.isNull()) { if (needsEmit) { emit changed(); } return; } auto buffer = cursorSurface.data()->buffer(); if (!buffer) { if (needsEmit) { emit changed(); } return; } m_drag.cursor.hotSpot = c->hotspot(); m_drag.cursor.image = buffer->data().copy(); if (needsEmit) { emit changed(); } // TODO: add the cursor image } void CursorImage::loadThemeCursor(CursorShape shape, Image *image) { loadThemeCursor(shape, m_cursors, image); } void CursorImage::loadThemeCursor(const QByteArray &shape, Image *image) { loadThemeCursor(shape, m_cursorsByName, image); } template void CursorImage::loadThemeCursor(const T &shape, QHash &cursors, Image *image) { loadTheme(); if (!m_cursorTheme) { return; } auto it = cursors.constFind(shape); if (it == cursors.constEnd()) { image->image = QImage(); image->hotSpot = QPoint(); wl_cursor_image *cursor = m_cursorTheme->get(shape); if (!cursor) { return; } wl_buffer *b = wl_cursor_image_get_buffer(cursor); if (!b) { return; } waylandServer()->internalClientConection()->flush(); waylandServer()->dispatch(); auto buffer = KWayland::Server::BufferInterface::get(waylandServer()->internalConnection()->getResource(KWayland::Client::Buffer::getId(b))); if (!buffer) { return; } auto scale = screens()->maxScale(); int hotSpotX = qRound(cursor->hotspot_x / scale); int hotSpotY = qRound(cursor->hotspot_y / scale); QImage img = buffer->data().copy(); img.setDevicePixelRatio(scale); it = decltype(it)(cursors.insert(shape, {img, QPoint(hotSpotX, hotSpotY)})); } image->hotSpot = it.value().hotSpot; image->image = it.value().image; } void CursorImage::reevaluteSource() { if (waylandServer()->seat()->isDragPointer()) { // TODO: touch drag? setSource(CursorSource::DragAndDrop); return; } if (waylandServer()->isScreenLocked()) { setSource(CursorSource::LockScreen); return; } if (input()->isSelectingWindow()) { setSource(CursorSource::WindowSelector); return; } if (effects && static_cast(effects)->isMouseInterception()) { setSource(CursorSource::EffectsOverride); return; } if (workspace() && workspace()->moveResizeClient()) { setSource(CursorSource::MoveResize); return; } if (!m_pointer->decoration().isNull()) { setSource(CursorSource::Decoration); return; } if (!m_pointer->focus().isNull() && waylandServer()->seat()->focusedPointer()) { setSource(CursorSource::PointerSurface); return; } setSource(CursorSource::Fallback); } void CursorImage::setSource(CursorSource source) { if (m_currentSource == source) { return; } m_currentSource = source; emit changed(); } QImage CursorImage::image() const { switch (m_currentSource) { case CursorSource::EffectsOverride: return m_effectsCursor.image; case CursorSource::MoveResize: return m_moveResizeCursor.image; case CursorSource::LockScreen: case CursorSource::PointerSurface: // lockscreen also uses server cursor image return m_serverCursor.image; case CursorSource::Decoration: return m_decorationCursor.image; case CursorSource::DragAndDrop: return m_drag.cursor.image; case CursorSource::Fallback: return m_fallbackCursor.image; case CursorSource::WindowSelector: return m_windowSelectionCursor.image; default: Q_UNREACHABLE(); } } QPoint CursorImage::hotSpot() const { switch (m_currentSource) { case CursorSource::EffectsOverride: return m_effectsCursor.hotSpot; case CursorSource::MoveResize: return m_moveResizeCursor.hotSpot; case CursorSource::LockScreen: case CursorSource::PointerSurface: // lockscreen also uses server cursor image return m_serverCursor.hotSpot; case CursorSource::Decoration: return m_decorationCursor.hotSpot; case CursorSource::DragAndDrop: return m_drag.cursor.hotSpot; case CursorSource::Fallback: return m_fallbackCursor.hotSpot; case CursorSource::WindowSelector: return m_windowSelectionCursor.hotSpot; default: Q_UNREACHABLE(); } } } diff --git a/scene.cpp b/scene.cpp index 4d5a273b0..debdba4a1 100644 --- a/scene.cpp +++ b/scene.cpp @@ -1,1159 +1,1155 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2006 Lubos Lunak 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 . *********************************************************************/ /* The base class for compositing, implementing shared functionality between the OpenGL and XRender backends. Design: When compositing is turned on, XComposite extension is used to redirect drawing of windows to pixmaps and XDamage extension is used to get informed about damage (changes) to window contents. This code is mostly in composite.cpp . Compositor::performCompositing() starts one painting pass. Painting is done by painting the screen, which in turn paints every window. Painting can be affected using effects, which are chained. E.g. painting a screen means that actually paintScreen() of the first effect is called, which possibly does modifications and calls next effect's paintScreen() and so on, until Scene::finalPaintScreen() is called. There are 3 phases of every paint (not necessarily done together): The pre-paint phase, the paint phase and the post-paint phase. The pre-paint phase is used to find out about how the painting will be actually done (i.e. what the effects will do). For example when only a part of the screen needs to be updated and no effect will do any transformation it is possible to use an optimized paint function. How the painting will be done is controlled by the mask argument, see PAINT_WINDOW_* and PAINT_SCREEN_* flags in scene.h . For example an effect that decides to paint a normal windows as translucent will need to modify the mask in its prePaintWindow() to include the PAINT_WINDOW_TRANSLUCENT flag. The paintWindow() function will then get the mask with this flag turned on and will also paint using transparency. The paint pass does the actual painting, based on the information collected using the pre-paint pass. After running through the effects' paintScreen() either paintGenericScreen() or optimized paintSimpleScreen() are called. Those call paintWindow() on windows (not necessarily all), possibly using clipping to optimize performance and calling paintWindow() first with only PAINT_WINDOW_OPAQUE to paint the opaque parts and then later with PAINT_WINDOW_TRANSLUCENT to paint the transparent parts. Function paintWindow() again goes through effects' paintWindow() until finalPaintWindow() is called, which calls the window's performPaint() to do the actual painting. The post-paint can be used for cleanups and is also used for scheduling repaints during the next painting pass for animations. Effects wanting to repaint certain parts can manually damage them during post-paint and repaint of these parts will be done during the next paint pass. */ #include "scene.h" #include #include #include "client.h" #include "deleted.h" #include "effects.h" #include "overlaywindow.h" #include "screens.h" #include "shadow.h" #include "wayland_server.h" #include "thumbnailitem.h" #include #include #include namespace KWin { //**************************************** // Scene //**************************************** Scene::Scene(QObject *parent) : QObject(parent) { last_time.invalidate(); // Initialize the timer } Scene::~Scene() { Q_ASSERT(m_windows.isEmpty()); } // returns mask and possibly modified region void Scene::paintScreen(int* mask, const QRegion &damage, const QRegion &repaint, QRegion *updateRegion, QRegion *validRegion, const QMatrix4x4 &projection, const QRect &outputGeometry) { const QSize &screenSize = screens()->size(); const QRegion displayRegion(0, 0, screenSize.width(), screenSize.height()); *mask = (damage == displayRegion) ? 0 : PAINT_SCREEN_REGION; updateTimeDiff(); // preparation step static_cast(effects)->startPaint(); QRegion region = damage; ScreenPrePaintData pdata; pdata.mask = *mask; pdata.paint = region; effects->prePaintScreen(pdata, time_diff); *mask = pdata.mask; region = pdata.paint; if (*mask & (PAINT_SCREEN_TRANSFORMED | PAINT_SCREEN_WITH_TRANSFORMED_WINDOWS)) { // Region painting is not possible with transformations, // because screen damage doesn't match transformed positions. *mask &= ~PAINT_SCREEN_REGION; region = infiniteRegion(); } else if (*mask & PAINT_SCREEN_REGION) { // make sure not to go outside visible screen region &= displayRegion; } else { // whole screen, not transformed, force region to be full region = displayRegion; } painted_region = region; repaint_region = repaint; if (*mask & PAINT_SCREEN_BACKGROUND_FIRST) { paintBackground(region); } ScreenPaintData data(projection, outputGeometry); effects->paintScreen(*mask, region, data); foreach (Window *w, stacking_order) { effects->postPaintWindow(effectWindow(w)); } effects->postPaintScreen(); // make sure not to go outside of the screen area *updateRegion = damaged_region; *validRegion = (region | painted_region) & displayRegion; repaint_region = QRegion(); damaged_region = QRegion(); // make sure all clipping is restored Q_ASSERT(!PaintClipper::clip()); } // Compute time since the last painting pass. void Scene::updateTimeDiff() { if (!last_time.isValid()) { // Painting has been idle (optimized out) for some time, // which means time_diff would be huge and would break animations. // Simply set it to one (zero would mean no change at all and could // cause problems). time_diff = 1; last_time.start(); } else time_diff = last_time.restart(); if (time_diff < 0) // check time rollback time_diff = 1; } // Painting pass is optimized away. void Scene::idle() { // Don't break time since last paint for the next pass. last_time.invalidate(); } // the function that'll be eventually called by paintScreen() above void Scene::finalPaintScreen(int mask, QRegion region, ScreenPaintData& data) { if (mask & (PAINT_SCREEN_TRANSFORMED | PAINT_SCREEN_WITH_TRANSFORMED_WINDOWS)) paintGenericScreen(mask, data); else paintSimpleScreen(mask, region); } // The generic painting code that can handle even transformations. // It simply paints bottom-to-top. void Scene::paintGenericScreen(int orig_mask, ScreenPaintData) { if (!(orig_mask & PAINT_SCREEN_BACKGROUND_FIRST)) { paintBackground(infiniteRegion()); } QVector phase2; phase2.reserve(stacking_order.size()); foreach (Window * w, stacking_order) { // bottom to top Toplevel* topw = w->window(); // Reset the repaint_region. // This has to be done here because many effects schedule a repaint for // the next frame within Effects::prePaintWindow. topw->resetRepaints(); WindowPrePaintData data; data.mask = orig_mask | (w->isOpaque() ? PAINT_WINDOW_OPAQUE : PAINT_WINDOW_TRANSLUCENT); w->resetPaintingEnabled(); data.paint = infiniteRegion(); // no clipping, so doesn't really matter data.clip = QRegion(); data.quads = w->buildQuads(); // preparation step effects->prePaintWindow(effectWindow(w), data, time_diff); #if !defined(QT_NO_DEBUG) if (data.quads.isTransformed()) { qFatal("Pre-paint calls are not allowed to transform quads!"); } #endif if (!w->isPaintingEnabled()) { continue; } phase2.append({w, infiniteRegion(), data.clip, data.mask, data.quads}); } foreach (const Phase2Data & d, phase2) { paintWindow(d.window, d.mask, d.region, d.quads); } const QSize &screenSize = screens()->size(); damaged_region = QRegion(0, 0, screenSize.width(), screenSize.height()); } // The optimized case without any transformations at all. // It can paint only the requested region and can use clipping // to reduce painting and improve performance. void Scene::paintSimpleScreen(int orig_mask, QRegion region) { Q_ASSERT((orig_mask & (PAINT_SCREEN_TRANSFORMED | PAINT_SCREEN_WITH_TRANSFORMED_WINDOWS)) == 0); QVector phase2data; phase2data.reserve(stacking_order.size()); QRegion dirtyArea = region; bool opaqueFullscreen(false); for (int i = 0; // do prePaintWindow bottom to top i < stacking_order.count(); ++i) { Window* w = stacking_order[ i ]; Toplevel* topw = w->window(); WindowPrePaintData data; data.mask = orig_mask | (w->isOpaque() ? PAINT_WINDOW_OPAQUE : PAINT_WINDOW_TRANSLUCENT); w->resetPaintingEnabled(); data.paint = region; data.paint |= topw->repaints(); // Reset the repaint_region. // This has to be done here because many effects schedule a repaint for // the next frame within Effects::prePaintWindow. topw->resetRepaints(); // Clip out the decoration for opaque windows; the decoration is drawn in the second pass opaqueFullscreen = false; // TODO: do we care about unmanged windows here (maybe input windows?) if (w->isOpaque()) { AbstractClient *c = dynamic_cast(topw); if (c) { opaqueFullscreen = c->isFullScreen(); } Client *cc = dynamic_cast(c); // the window is fully opaque if (cc && cc->decorationHasAlpha()) { // decoration uses alpha channel, so we may not exclude it in clipping data.clip = w->clientShape().translated(w->x(), w->y()); } else { // decoration is fully opaque if (c && c->isShade()) { data.clip = QRegion(); } else { data.clip = w->shape().translated(w->x(), w->y()); } } } else if (topw->hasAlpha() && topw->opacity() == 1.0) { // the window is partially opaque data.clip = (w->clientShape() & topw->opaqueRegion().translated(topw->clientPos())).translated(w->x(), w->y()); } else { data.clip = QRegion(); } data.quads = w->buildQuads(); // preparation step effects->prePaintWindow(effectWindow(w), data, time_diff); #if !defined(QT_NO_DEBUG) if (data.quads.isTransformed()) { qFatal("Pre-paint calls are not allowed to transform quads!"); } #endif if (!w->isPaintingEnabled()) { continue; } dirtyArea |= data.paint; // Schedule the window for painting phase2data.append({w, data.paint, data.clip, data.mask, data.quads}); } // Save the part of the repaint region that's exclusively rendered to // bring a reused back buffer up to date. Then union the dirty region // with the repaint region. const QRegion repaintClip = repaint_region - dirtyArea; dirtyArea |= repaint_region; const QSize &screenSize = screens()->size(); const QRegion displayRegion(0, 0, screenSize.width(), screenSize.height()); bool fullRepaint(dirtyArea == displayRegion); // spare some expensive region operations if (!fullRepaint) { extendPaintRegion(dirtyArea, opaqueFullscreen); fullRepaint = (dirtyArea == displayRegion); } QRegion allclips, upperTranslucentDamage; upperTranslucentDamage = repaint_region; // This is the occlusion culling pass for (int i = phase2data.count() - 1; i >= 0; --i) { Phase2Data *data = &phase2data[i]; if (fullRepaint) data->region = displayRegion; else data->region |= upperTranslucentDamage; // subtract the parts which will possibly been drawn as part of // a higher opaque window data->region -= allclips; // Here we rely on WindowPrePaintData::setTranslucent() to remove // the clip if needed. if (!data->clip.isEmpty() && !(data->mask & PAINT_WINDOW_TRANSLUCENT)) { // clip away the opaque regions for all windows below this one allclips |= data->clip; // extend the translucent damage for windows below this by remaining (translucent) regions if (!fullRepaint) upperTranslucentDamage |= data->region - data->clip; } else if (!fullRepaint) { upperTranslucentDamage |= data->region; } } QRegion paintedArea; // Fill any areas of the root window not covered by opaque windows if (!(orig_mask & PAINT_SCREEN_BACKGROUND_FIRST)) { paintedArea = dirtyArea - allclips; paintBackground(paintedArea); } // Now walk the list bottom to top and draw the windows. for (int i = 0; i < phase2data.count(); ++i) { Phase2Data *data = &phase2data[i]; // add all regions which have been drawn so far paintedArea |= data->region; data->region = paintedArea; paintWindow(data->window, data->mask, data->region, data->quads); } if (fullRepaint) { painted_region = displayRegion; damaged_region = displayRegion; } else { painted_region |= paintedArea; // Clip the repainted region from the damaged region. // It's important that we don't add the union of the damaged region // and the repainted region to the damage history. Otherwise the // repaint region will grow with every frame until it eventually // covers the whole back buffer, at which point we're always doing // full repaints. damaged_region = paintedArea - repaintClip; } } void Scene::addToplevel(Toplevel *c) { Q_ASSERT(!m_windows.contains(c)); Scene::Window *w = createWindow(c); m_windows[ c ] = w; connect(c, SIGNAL(geometryShapeChanged(KWin::Toplevel*,QRect)), SLOT(windowGeometryShapeChanged(KWin::Toplevel*))); connect(c, SIGNAL(windowClosed(KWin::Toplevel*,KWin::Deleted*)), SLOT(windowClosed(KWin::Toplevel*,KWin::Deleted*))); //A change of scale won't affect the geometry in compositor co-ordinates, but will affect the window quads. if (c->surface()) { connect(c->surface(), &KWayland::Server::SurfaceInterface::scaleChanged, this, std::bind(&Scene::windowGeometryShapeChanged, this, c)); } connect(c, &Toplevel::screenScaleChanged, this, [this, c] { windowGeometryShapeChanged(c); } ); c->effectWindow()->setSceneWindow(w); c->getShadow(); w->updateShadow(c->shadow()); connect(c, &Toplevel::shadowChanged, this, [w] { w->invalidateQuadsCache(); } ); } void Scene::removeToplevel(Toplevel *toplevel) { Q_ASSERT(m_windows.contains(toplevel)); delete m_windows.take(toplevel); toplevel->effectWindow()->setSceneWindow(nullptr); } void Scene::windowClosed(Toplevel *toplevel, Deleted *deleted) { if (!deleted) { removeToplevel(toplevel); return; } Q_ASSERT(m_windows.contains(toplevel)); Window *window = m_windows.take(toplevel); window->updateToplevel(deleted); if (window->shadow()) { window->shadow()->setToplevel(deleted); } m_windows[deleted] = window; } void Scene::windowGeometryShapeChanged(Toplevel *c) { if (!m_windows.contains(c)) // this is ok, shape is not valid by default return; Window *w = m_windows[ c ]; w->discardShape(); } void Scene::createStackingOrder(ToplevelList toplevels) { // TODO: cache the stacking_order in case it has not changed foreach (Toplevel *c, toplevels) { Q_ASSERT(m_windows.contains(c)); stacking_order.append(m_windows[ c ]); } } void Scene::clearStackingOrder() { stacking_order.clear(); } static Scene::Window *s_recursionCheck = nullptr; void Scene::paintWindow(Window* w, int mask, QRegion region, WindowQuadList quads) { // no painting outside visible screen (and no transformations) const QSize &screenSize = screens()->size(); region &= QRect(0, 0, screenSize.width(), screenSize.height()); if (region.isEmpty()) // completely clipped return; if (w->window()->isDeleted() && w->window()->skipsCloseAnimation()) { // should not get painted return; } if (s_recursionCheck == w) { return; } WindowPaintData data(w->window()->effectWindow(), screenProjectionMatrix()); data.quads = quads; effects->paintWindow(effectWindow(w), mask, region, data); // paint thumbnails on top of window paintWindowThumbnails(w, region, data.opacity(), data.brightness(), data.saturation()); // and desktop thumbnails paintDesktopThumbnails(w); } static void adjustClipRegion(AbstractThumbnailItem *item, QRegion &clippingRegion) { if (item->clip() && item->clipTo()) { // the x/y positions of the parent item are not correct. The margins are added, though the size seems fine // that's why we have to get the offset by inspecting the anchors properties QQuickItem *parentItem = item->clipTo(); QPointF offset; QVariant anchors = parentItem->property("anchors"); if (anchors.isValid()) { if (QObject *anchorsObject = anchors.value()) { offset.setX(anchorsObject->property("leftMargin").toReal()); offset.setY(anchorsObject->property("topMargin").toReal()); } } QRectF rect = QRectF(parentItem->position() - offset, QSizeF(parentItem->width(), parentItem->height())); if (QQuickItem *p = parentItem->parentItem()) { rect = p->mapRectToScene(rect); } clippingRegion &= rect.adjusted(0,0,-1,-1).translated(item->window()->position()).toRect(); } } void Scene::paintWindowThumbnails(Scene::Window *w, QRegion region, qreal opacity, qreal brightness, qreal saturation) { EffectWindowImpl *wImpl = static_cast(effectWindow(w)); for (QHash >::const_iterator it = wImpl->thumbnails().constBegin(); it != wImpl->thumbnails().constEnd(); ++it) { if (it.value().isNull()) { continue; } WindowThumbnailItem *item = it.key(); if (!item->isVisible()) { continue; } EffectWindowImpl *thumb = it.value().data(); WindowPaintData thumbData(thumb, screenProjectionMatrix()); thumbData.setOpacity(opacity); thumbData.setBrightness(brightness * item->brightness()); thumbData.setSaturation(saturation * item->saturation()); const QRect visualThumbRect(thumb->expandedGeometry()); QSizeF size = QSizeF(visualThumbRect.size()); size.scale(QSizeF(item->width(), item->height()), Qt::KeepAspectRatio); if (size.width() > visualThumbRect.width() || size.height() > visualThumbRect.height()) { size = QSizeF(visualThumbRect.size()); } thumbData.setXScale(size.width() / static_cast(visualThumbRect.width())); thumbData.setYScale(size.height() / static_cast(visualThumbRect.height())); if (!item->window()) { continue; } const QPointF point = item->mapToScene(item->position()); qreal x = point.x() + w->x() + (item->width() - size.width())/2; qreal y = point.y() + w->y() + (item->height() - size.height()) / 2; x -= thumb->x(); y -= thumb->y(); // compensate shadow topleft padding x += (thumb->x()-visualThumbRect.x())*thumbData.xScale(); y += (thumb->y()-visualThumbRect.y())*thumbData.yScale(); thumbData.setXTranslation(x); thumbData.setYTranslation(y); int thumbMask = PAINT_WINDOW_TRANSFORMED | PAINT_WINDOW_LANCZOS; if (thumbData.opacity() == 1.0) { thumbMask |= PAINT_WINDOW_OPAQUE; } else { thumbMask |= PAINT_WINDOW_TRANSLUCENT; } QRegion clippingRegion = region; clippingRegion &= QRegion(wImpl->x(), wImpl->y(), wImpl->width(), wImpl->height()); adjustClipRegion(item, clippingRegion); effects->drawWindow(thumb, thumbMask, clippingRegion, thumbData); } } void Scene::paintDesktopThumbnails(Scene::Window *w) { EffectWindowImpl *wImpl = static_cast(effectWindow(w)); for (QList::const_iterator it = wImpl->desktopThumbnails().constBegin(); it != wImpl->desktopThumbnails().constEnd(); ++it) { DesktopThumbnailItem *item = *it; if (!item->isVisible()) { continue; } if (!item->window()) { continue; } s_recursionCheck = w; ScreenPaintData data; const QSize &screenSize = screens()->size(); QSize size = screenSize; size.scale(item->width(), item->height(), Qt::KeepAspectRatio); data *= QVector2D(size.width() / double(screenSize.width()), size.height() / double(screenSize.height())); const QPointF point = item->mapToScene(item->position()); const qreal x = point.x() + w->x() + (item->width() - size.width())/2; const qreal y = point.y() + w->y() + (item->height() - size.height()) / 2; const QRect region = QRect(x, y, item->width(), item->height()); QRegion clippingRegion = region; clippingRegion &= QRegion(wImpl->x(), wImpl->y(), wImpl->width(), wImpl->height()); adjustClipRegion(item, clippingRegion); data += QPointF(x, y); const int desktopMask = PAINT_SCREEN_TRANSFORMED | PAINT_WINDOW_TRANSFORMED | PAINT_SCREEN_BACKGROUND_FIRST; paintDesktop(item->desktop(), desktopMask, clippingRegion, data); s_recursionCheck = nullptr; } } void Scene::paintDesktop(int desktop, int mask, const QRegion ®ion, ScreenPaintData &data) { static_cast(effects)->paintDesktop(desktop, mask, region, data); } // the function that'll be eventually called by paintWindow() above void Scene::finalPaintWindow(EffectWindowImpl* w, int mask, QRegion region, WindowPaintData& data) { effects->drawWindow(w, mask, region, data); } // will be eventually called from drawWindow() void Scene::finalDrawWindow(EffectWindowImpl* w, int mask, QRegion region, WindowPaintData& data) { if (waylandServer() && waylandServer()->isScreenLocked() && !w->window()->isLockScreen() && !w->window()->isInputMethod()) { return; } w->sceneWindow()->performPaint(mask, region, data); } void Scene::extendPaintRegion(QRegion ®ion, bool opaqueFullscreen) { Q_UNUSED(region); Q_UNUSED(opaqueFullscreen); } bool Scene::blocksForRetrace() const { return false; } bool Scene::syncsToVBlank() const { return false; } void Scene::screenGeometryChanged(const QSize &size) { if (!overlayWindow()) { return; } overlayWindow()->resize(size); } bool Scene::makeOpenGLContextCurrent() { return false; } void Scene::doneOpenGLContextCurrent() { } void Scene::triggerFence() { } QMatrix4x4 Scene::screenProjectionMatrix() const { return QMatrix4x4(); } xcb_render_picture_t Scene::xrenderBufferPicture() const { return XCB_RENDER_PICTURE_NONE; } QPainter *Scene::scenePainter() const { return nullptr; } QImage *Scene::qpainterRenderBuffer() const { return nullptr; } QVector Scene::openGLPlatformInterfaceExtensions() const { return QVector{}; } //**************************************** // Scene::Window //**************************************** Scene::Window::Window(Toplevel * c) : toplevel(c) , filter(ImageFilterFast) , m_shadow(nullptr) , m_currentPixmap() , m_previousPixmap() , m_referencePixmapCounter(0) , disable_painting(0) , shape_valid(false) , cached_quad_list(nullptr) { } Scene::Window::~Window() { delete m_shadow; } void Scene::Window::referencePreviousPixmap() { if (!m_previousPixmap.isNull() && m_previousPixmap->isDiscarded()) { m_referencePixmapCounter++; } } void Scene::Window::unreferencePreviousPixmap() { if (m_previousPixmap.isNull() || !m_previousPixmap->isDiscarded()) { return; } m_referencePixmapCounter--; if (m_referencePixmapCounter == 0) { m_previousPixmap.reset(); } } void Scene::Window::pixmapDiscarded() { if (!m_currentPixmap.isNull()) { if (m_currentPixmap->isValid()) { m_previousPixmap.reset(m_currentPixmap.take()); m_previousPixmap->markAsDiscarded(); } else { m_currentPixmap.reset(); } } } void Scene::Window::discardShape() { // it is created on-demand and cached, simply // reset the flag shape_valid = false; invalidateQuadsCache(); } // Find out the shape of the window using the XShape extension // or if shape is not set then simply it's the window geometry. const QRegion &Scene::Window::shape() const { if (!shape_valid) { if (toplevel->shape()) { auto cookie = xcb_shape_get_rectangles_unchecked(connection(), toplevel->frameId(), XCB_SHAPE_SK_BOUNDING); ScopedCPointer reply(xcb_shape_get_rectangles_reply(connection(), cookie, nullptr)); if (!reply.isNull()) { shape_region = QRegion(); auto *rects = xcb_shape_get_rectangles_rectangles(reply.data()); for (int i = 0; i < xcb_shape_get_rectangles_rectangles_length(reply.data()); ++i) shape_region += QRegion(rects[ i ].x, rects[ i ].y, rects[ i ].width, rects[ i ].height); // make sure the shape is sane (X is async, maybe even XShape is broken) shape_region &= QRegion(0, 0, width(), height()); } else shape_region = QRegion(); } else shape_region = QRegion(0, 0, width(), height()); shape_valid = true; } return shape_region; } QRegion Scene::Window::clientShape() const { if (AbstractClient *c = dynamic_cast< AbstractClient * > (toplevel)) { if (c->isShade()) return QRegion(); } // TODO: cache const QRegion r = shape() & QRect(toplevel->clientPos(), toplevel->clientSize()); return r.isEmpty() ? QRegion() : r; } bool Scene::Window::isVisible() const { if (toplevel->isDeleted()) return false; if (!toplevel->isOnCurrentDesktop()) return false; if (!toplevel->isOnCurrentActivity()) return false; if (AbstractClient *c = dynamic_cast(toplevel)) return c->isShown(true); return true; // Unmanaged is always visible } bool Scene::Window::isOpaque() const { return toplevel->opacity() == 1.0 && !toplevel->hasAlpha(); } bool Scene::Window::isPaintingEnabled() const { return !disable_painting; } void Scene::Window::resetPaintingEnabled() { disable_painting = 0; if (toplevel->isDeleted()) disable_painting |= PAINT_DISABLED_BY_DELETE; if (static_cast(effects)->isDesktopRendering()) { if (!toplevel->isOnDesktop(static_cast(effects)->currentRenderedDesktop())) { disable_painting |= PAINT_DISABLED_BY_DESKTOP; } } else { if (!toplevel->isOnCurrentDesktop()) disable_painting |= PAINT_DISABLED_BY_DESKTOP; } if (!toplevel->isOnCurrentActivity()) disable_painting |= PAINT_DISABLED_BY_ACTIVITY; if (AbstractClient *c = dynamic_cast(toplevel)) { if (c->isMinimized()) disable_painting |= PAINT_DISABLED_BY_MINIMIZE; if (c->isHiddenInternal()) { disable_painting |= PAINT_DISABLED; } } } void Scene::Window::enablePainting(int reason) { disable_painting &= ~reason; } void Scene::Window::disablePainting(int reason) { disable_painting |= reason; } WindowQuadList Scene::Window::buildQuads(bool force) const { if (cached_quad_list != nullptr && !force) return *cached_quad_list; WindowQuadList ret; - qreal scale = 1.0; - if (toplevel->surface()) { - scale = toplevel->surface()->scale(); - } + + const qreal scale = toplevel->bufferScale(); if (toplevel->clientPos() == QPoint(0, 0) && toplevel->clientSize() == toplevel->decorationRect().size()) ret = makeQuads(WindowQuadContents, shape(), QPoint(0,0), scale); // has no decoration else { AbstractClient *client = dynamic_cast(toplevel); QRegion contents = clientShape(); QRegion center = toplevel->transparentRect(); QRegion decoration = (client ? QRegion(client->decorationRect()) : shape()) - center; qreal decorationScale = 1.0; ret = makeQuads(WindowQuadContents, contents, toplevel->clientContentPos(), scale); QRect rects[4]; bool isShadedClient = false; if (client) { client->layoutDecorationRects(rects[0], rects[1], rects[2], rects[3]); decorationScale = client->screenScale(); isShadedClient = client->isShade() || center.isEmpty(); } if (isShadedClient) { const QRect bounding = rects[0] | rects[1] | rects[2] | rects[3]; ret += makeDecorationQuads(rects, bounding, decorationScale); } else { ret += makeDecorationQuads(rects, decoration, decorationScale); } } if (m_shadow && toplevel->wantsShadowToBeRendered()) { ret << m_shadow->shadowQuads(); } effects->buildQuads(toplevel->effectWindow(), ret); cached_quad_list.reset(new WindowQuadList(ret)); return ret; } WindowQuadList Scene::Window::makeDecorationQuads(const QRect *rects, const QRegion ®ion, qreal textureScale) const { WindowQuadList list; const QPoint offsets[4] = { QPoint(-rects[0].x() + rects[1].height() + rects[3].height() + 2, -rects[0].y()), // Left QPoint(-rects[1].x(), -rects[1].y()), // Top QPoint(-rects[2].x() + rects[1].height() + rects[3].height() + rects[0].width() + 3, -rects[2].y()), // Right QPoint(-rects[3].x(), -rects[3].y() + rects[1].height() + 1) // Bottom }; const Qt::Orientation orientations[4] = { Qt::Vertical, // Left Qt::Horizontal, // Top Qt::Vertical, // Right Qt::Horizontal, // Bottom }; for (int i = 0; i < 4; i++) { const QRegion intersectedRegion = (region & rects[i]); for (const QRect &r : intersectedRegion) { if (!r.isValid()) continue; const bool swap = orientations[i] == Qt::Vertical; const int x0 = r.x(); const int y0 = r.y(); const int x1 = r.x() + r.width(); const int y1 = r.y() + r.height(); const int u0 = (x0 + offsets[i].x()) * textureScale; const int v0 = (y0 + offsets[i].y()) * textureScale; const int u1 = (x1 + offsets[i].x()) * textureScale; const int v1 = (y1 + offsets[i].y()) * textureScale; WindowQuad quad(WindowQuadDecoration); quad.setUVAxisSwapped(swap); if (swap) { quad[0] = WindowVertex(x0, y0, v0, u0); // Top-left quad[1] = WindowVertex(x1, y0, v0, u1); // Top-right quad[2] = WindowVertex(x1, y1, v1, u1); // Bottom-right quad[3] = WindowVertex(x0, y1, v1, u0); // Bottom-left } else { quad[0] = WindowVertex(x0, y0, u0, v0); // Top-left quad[1] = WindowVertex(x1, y0, u1, v0); // Top-right quad[2] = WindowVertex(x1, y1, u1, v1); // Bottom-right quad[3] = WindowVertex(x0, y1, u0, v1); // Bottom-left } list.append(quad); } } return list; } void Scene::Window::invalidateQuadsCache() { cached_quad_list.reset(); } WindowQuadList Scene::Window::makeQuads(WindowQuadType type, const QRegion& reg, const QPoint &textureOffset, qreal scale) const { WindowQuadList ret; ret.reserve(reg.rectCount()); for (const QRect &r : reg) { WindowQuad quad(type); // TODO asi mam spatne pravy dolni roh - bud tady, nebo v jinych castech quad[ 0 ] = WindowVertex(QPointF(r.x(), r.y()), QPointF(r.x() + textureOffset.x(), r.y() + textureOffset.y()) * scale); quad[ 1 ] = WindowVertex(QPointF(r.x() + r.width(), r.y()), QPointF(r.x() + r.width() + textureOffset.x(), r.y() + textureOffset.y()) * scale); quad[ 2 ] = WindowVertex(QPointF(r.x() + r.width(), r.y() + r.height()), QPointF(r.x() + r.width() + textureOffset.x(), r.y() + r.height() + textureOffset.y()) * scale); quad[ 3 ] = WindowVertex(QPointF(r.x(), r.y() + r.height()), QPointF(r.x() + textureOffset.x(), r.y() + r.height() + textureOffset.y()) * scale); ret.append(quad); } return ret; } void Scene::Window::updateShadow(Shadow* shadow) { if (m_shadow == shadow) { return; } delete m_shadow; m_shadow = shadow; } //**************************************** // WindowPixmap //**************************************** WindowPixmap::WindowPixmap(Scene::Window *window) : m_window(window) , m_pixmap(XCB_PIXMAP_NONE) , m_discarded(false) { } WindowPixmap::WindowPixmap(const QPointer &subSurface, WindowPixmap *parent) : m_window(parent->m_window) , m_pixmap(XCB_PIXMAP_NONE) , m_discarded(false) , m_parent(parent) , m_subSurface(subSurface) { } WindowPixmap::~WindowPixmap() { if (m_pixmap != XCB_WINDOW_NONE) { xcb_free_pixmap(connection(), m_pixmap); } if (m_buffer) { using namespace KWayland::Server; QObject::disconnect(m_buffer.data(), &BufferInterface::aboutToBeDestroyed, m_buffer.data(), &BufferInterface::unref); m_buffer->unref(); } } void WindowPixmap::create() { if (isValid() || toplevel()->isDeleted()) { return; } // always update from Buffer on Wayland, don't try using XPixmap if (kwinApp()->shouldUseWaylandForCompositing()) { // use Buffer updateBuffer(); if ((m_buffer || !m_fbo.isNull()) && m_subSurface.isNull()) { m_window->unreferencePreviousPixmap(); } return; } XServerGrabber grabber; xcb_pixmap_t pix = xcb_generate_id(connection()); xcb_void_cookie_t namePixmapCookie = xcb_composite_name_window_pixmap_checked(connection(), toplevel()->frameId(), pix); Xcb::WindowAttributes windowAttributes(toplevel()->frameId()); Xcb::WindowGeometry windowGeometry(toplevel()->frameId()); if (xcb_generic_error_t *error = xcb_request_check(connection(), namePixmapCookie)) { qCDebug(KWIN_CORE) << "Creating window pixmap failed: " << error->error_code; free(error); return; } // check that the received pixmap is valid and actually matches what we // know about the window (i.e. size) if (!windowAttributes || windowAttributes->map_state != XCB_MAP_STATE_VIEWABLE) { qCDebug(KWIN_CORE) << "Creating window pixmap failed: " << this; xcb_free_pixmap(connection(), pix); return; } if (!windowGeometry || windowGeometry->width != toplevel()->width() || windowGeometry->height != toplevel()->height()) { qCDebug(KWIN_CORE) << "Creating window pixmap failed: " << this; xcb_free_pixmap(connection(), pix); return; } m_pixmap = pix; m_pixmapSize = QSize(toplevel()->width(), toplevel()->height()); m_contentsRect = QRect(toplevel()->clientPos(), toplevel()->clientSize()); m_window->unreferencePreviousPixmap(); } WindowPixmap *WindowPixmap::createChild(const QPointer &subSurface) { Q_UNUSED(subSurface) return nullptr; } bool WindowPixmap::isValid() const { - if (!m_buffer.isNull() || !m_fbo.isNull()) { + if (!m_buffer.isNull() || !m_fbo.isNull() || !m_internalImage.isNull()) { return true; } return m_pixmap != XCB_PIXMAP_NONE; } void WindowPixmap::updateBuffer() { using namespace KWayland::Server; if (SurfaceInterface *s = surface()) { QVector oldTree = m_children; QVector children; using namespace KWayland::Server; const auto subSurfaces = s->childSubSurfaces(); for (const auto &subSurface : subSurfaces) { if (subSurface.isNull()) { continue; } auto it = std::find_if(oldTree.begin(), oldTree.end(), [subSurface] (WindowPixmap *p) { return p->m_subSurface == subSurface; }); if (it != oldTree.end()) { children << *it; (*it)->updateBuffer(); oldTree.erase(it); } else { WindowPixmap *p = createChild(subSurface); if (p) { p->create(); children << p; } } } setChildren(children); qDeleteAll(oldTree); if (auto b = s->buffer()) { if (b == m_buffer) { // no change return; } if (m_buffer) { QObject::disconnect(m_buffer.data(), &BufferInterface::aboutToBeDestroyed, m_buffer.data(), &BufferInterface::unref); m_buffer->unref(); } m_buffer = b; m_buffer->ref(); QObject::connect(m_buffer.data(), &BufferInterface::aboutToBeDestroyed, m_buffer.data(), &BufferInterface::unref); } else if (m_subSurface) { if (m_buffer) { QObject::disconnect(m_buffer.data(), &BufferInterface::aboutToBeDestroyed, m_buffer.data(), &BufferInterface::unref); m_buffer->unref(); m_buffer.clear(); } - } else { - // might be an internal window - const auto &fbo = toplevel()->internalFramebufferObject(); - if (!fbo.isNull()) { - m_fbo = fbo; - } } + } else if (toplevel()->internalFramebufferObject()) { + m_fbo = toplevel()->internalFramebufferObject(); + } else if (!toplevel()->internalImageObject().isNull()) { + m_internalImage = toplevel()->internalImageObject(); } else { if (m_buffer) { QObject::disconnect(m_buffer.data(), &BufferInterface::aboutToBeDestroyed, m_buffer.data(), &BufferInterface::unref); m_buffer->unref(); m_buffer.clear(); } } } KWayland::Server::SurfaceInterface *WindowPixmap::surface() const { if (!m_subSurface.isNull()) { return m_subSurface->surface().data(); } else { return toplevel()->surface(); } } //**************************************** // Scene::EffectFrame //**************************************** Scene::EffectFrame::EffectFrame(EffectFrameImpl* frame) : m_effectFrame(frame) { } Scene::EffectFrame::~EffectFrame() { } SceneFactory::SceneFactory(QObject *parent) : QObject(parent) { } SceneFactory::~SceneFactory() { } } // namespace diff --git a/scene.h b/scene.h index 4b128f7fb..7aa987cd2 100644 --- a/scene.h +++ b/scene.h @@ -1,687 +1,695 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2006 Lubos Lunak 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 . *********************************************************************/ #ifndef KWIN_SCENE_H #define KWIN_SCENE_H #include "toplevel.h" #include "utils.h" #include "kwineffects.h" #include #include class QOpenGLFramebufferObject; namespace KWayland { namespace Server { class BufferInterface; class SubSurfaceInterface; } } namespace KWin { namespace Decoration { class DecoratedClientImpl; class Renderer; } class AbstractThumbnailItem; class Deleted; class EffectFrameImpl; class EffectWindowImpl; class OverlayWindow; class Shadow; class WindowPixmap; // The base class for compositing backends. class KWIN_EXPORT Scene : public QObject { Q_OBJECT public: explicit Scene(QObject *parent = nullptr); ~Scene() override = 0; class EffectFrame; class Window; // Returns true if the ctor failed to properly initialize. virtual bool initFailed() const = 0; virtual CompositingType compositingType() const = 0; virtual bool hasPendingFlush() const { return false; } // Repaints the given screen areas, windows provides the stacking order. // The entry point for the main part of the painting pass. // returns the time since the last vblank signal - if there's one // ie. "what of this frame is lost to painting" virtual qint64 paint(QRegion damage, ToplevelList windows) = 0; /** * Adds the Toplevel to the Scene. * * If the toplevel gets deleted, then the scene will try automatically * to re-bind an underlying scene window to the corresponding Deleted. * * @param toplevel The window to be added. * @note You can add a toplevel to scene only once. */ void addToplevel(Toplevel *toplevel); /** * Removes the Toplevel from the Scene. * * @param toplevel The window to be removed. * @note You can remove a toplevel from the scene only once. */ void removeToplevel(Toplevel *toplevel); /** * @brief Creates the Scene backend of an EffectFrame. * * @param frame The EffectFrame this Scene::EffectFrame belongs to. */ virtual Scene::EffectFrame *createEffectFrame(EffectFrameImpl *frame) = 0; /** * @brief Creates the Scene specific Shadow subclass. * * An implementing class has to create a proper instance. It is not allowed to * return @c null. * * @param toplevel The Toplevel for which the Shadow needs to be created. */ virtual Shadow *createShadow(Toplevel *toplevel) = 0; /** * Method invoked when the screen geometry is changed. * Reimplementing classes should also invoke the parent method * as it takes care of resizing the overlay window. * @param size The new screen geometry size */ virtual void screenGeometryChanged(const QSize &size); // Flags controlling how painting is done. enum { // Window (or at least part of it) will be painted opaque. PAINT_WINDOW_OPAQUE = 1 << 0, // Window (or at least part of it) will be painted translucent. PAINT_WINDOW_TRANSLUCENT = 1 << 1, // Window will be painted with transformed geometry. PAINT_WINDOW_TRANSFORMED = 1 << 2, // Paint only a region of the screen (can be optimized, cannot // be used together with TRANSFORMED flags). PAINT_SCREEN_REGION = 1 << 3, // Whole screen will be painted with transformed geometry. PAINT_SCREEN_TRANSFORMED = 1 << 4, // At least one window will be painted with transformed geometry. PAINT_SCREEN_WITH_TRANSFORMED_WINDOWS = 1 << 5, // Clear whole background as the very first step, without optimizing it PAINT_SCREEN_BACKGROUND_FIRST = 1 << 6, // PAINT_DECORATION_ONLY = 1 << 7 has been removed // Window will be painted with a lanczos filter. PAINT_WINDOW_LANCZOS = 1 << 8 // PAINT_SCREEN_WITH_TRANSFORMED_WINDOWS_WITHOUT_FULL_REPAINTS = 1 << 9 has been removed }; // types of filtering available enum ImageFilterType { ImageFilterFast, ImageFilterGood }; // there's nothing to paint (adjust time_diff later) virtual void idle(); virtual bool blocksForRetrace() const; virtual bool syncsToVBlank() const; virtual OverlayWindow* overlayWindow() const = 0; virtual bool makeOpenGLContextCurrent(); virtual void doneOpenGLContextCurrent(); virtual QMatrix4x4 screenProjectionMatrix() const; /** * Whether the Scene uses an X11 overlay window to perform compositing. */ virtual bool usesOverlayWindow() const = 0; virtual void triggerFence(); virtual Decoration::Renderer *createDecorationRenderer(Decoration::DecoratedClientImpl *) = 0; /** * Whether the Scene is able to drive animations. * This is used as a hint to the effects system which effects can be supported. * If the Scene performs software rendering it is supposed to return @c false, * if rendering is hardware accelerated it should return @c true. */ virtual bool animationsSupported() const = 0; /** * The render buffer used by an XRender based compositor scene. * Default implementation returns XCB_RENDER_PICTURE_NONE */ virtual xcb_render_picture_t xrenderBufferPicture() const; /** * The QPainter used by a QPainter based compositor scene. * Default implementation returns @c nullptr; */ virtual QPainter *scenePainter() const; /** * The render buffer used by a QPainter based compositor. * Default implementation returns @c nullptr. */ virtual QImage *qpainterRenderBuffer() const; /** * The backend specific extensions (e.g. EGL/GLX extensions). * * Not the OpenGL (ES) extension! * * Default implementation returns empty list */ virtual QVector openGLPlatformInterfaceExtensions() const; Q_SIGNALS: void frameRendered(); void resetCompositing(); public Q_SLOTS: // shape/size of a window changed void windowGeometryShapeChanged(KWin::Toplevel* c); // a window has been closed void windowClosed(KWin::Toplevel* c, KWin::Deleted* deleted); protected: virtual Window *createWindow(Toplevel *toplevel) = 0; void createStackingOrder(ToplevelList toplevels); void clearStackingOrder(); // shared implementation, starts painting the screen void paintScreen(int *mask, const QRegion &damage, const QRegion &repaint, QRegion *updateRegion, QRegion *validRegion, const QMatrix4x4 &projection = QMatrix4x4(), const QRect &outputGeometry = QRect()); // Render cursor texture in case hardware cursor is disabled/non-applicable virtual void paintCursor() = 0; friend class EffectsHandlerImpl; // called after all effects had their paintScreen() called void finalPaintScreen(int mask, QRegion region, ScreenPaintData& data); // shared implementation of painting the screen in the generic // (unoptimized) way virtual void paintGenericScreen(int mask, ScreenPaintData data); // shared implementation of painting the screen in an optimized way virtual void paintSimpleScreen(int mask, QRegion region); // paint the background (not the desktop background - the whole background) virtual void paintBackground(QRegion region) = 0; // called after all effects had their paintWindow() called void finalPaintWindow(EffectWindowImpl* w, int mask, QRegion region, WindowPaintData& data); // shared implementation, starts painting the window virtual void paintWindow(Window* w, int mask, QRegion region, WindowQuadList quads); // called after all effects had their drawWindow() called virtual void finalDrawWindow(EffectWindowImpl* w, int mask, QRegion region, WindowPaintData& data); // let the scene decide whether it's better to paint more of the screen, eg. in order to allow a buffer swap // the default is NOOP virtual void extendPaintRegion(QRegion ®ion, bool opaqueFullscreen); virtual void paintDesktop(int desktop, int mask, const QRegion ®ion, ScreenPaintData &data); // compute time since the last repaint void updateTimeDiff(); // saved data for 2nd pass of optimized screen painting struct Phase2Data { Window *window = nullptr; QRegion region; QRegion clip; int mask = 0; WindowQuadList quads; }; // The region which actually has been painted by paintScreen() and should be // copied from the buffer to the screen. I.e. the region returned from Scene::paintScreen(). // Since prePaintWindow() can extend areas to paint, these changes would have to propagate // up all the way from paintSimpleScreen() up to paintScreen(), so save them here rather // than propagate them up in arguments. QRegion painted_region; // Additional damage that needs to be repaired to bring a reused back buffer up to date QRegion repaint_region; // The dirty region before it was unioned with repaint_region QRegion damaged_region; // time since last repaint int time_diff; QElapsedTimer last_time; private: void paintWindowThumbnails(Scene::Window *w, QRegion region, qreal opacity, qreal brightness, qreal saturation); void paintDesktopThumbnails(Scene::Window *w); QHash< Toplevel*, Window* > m_windows; // windows in their stacking order QVector< Window* > stacking_order; }; /** * Factory class to create a Scene. Needs to be implemented by the plugins. */ class KWIN_EXPORT SceneFactory : public QObject { Q_OBJECT public: ~SceneFactory() override; /** * @returns The created Scene, may be @c nullptr. */ virtual Scene *create(QObject *parent = nullptr) const = 0; protected: explicit SceneFactory(QObject *parent); }; // The base class for windows representations in composite backends class Scene::Window { public: Window(Toplevel* c); virtual ~Window(); // perform the actual painting of the window virtual void performPaint(int mask, QRegion region, WindowPaintData data) = 0; // do any cleanup needed when the window's composite pixmap is discarded void pixmapDiscarded(); int x() const; int y() const; int width() const; int height() const; QRect geometry() const; QPoint pos() const; QSize size() const; QRect rect() const; // access to the internal window class // TODO eventually get rid of this Toplevel* window() const; // should the window be painted bool isPaintingEnabled() const; void resetPaintingEnabled(); // Flags explaining why painting should be disabled enum { // Window will not be painted PAINT_DISABLED = 1 << 0, // Window will not be painted because it is deleted PAINT_DISABLED_BY_DELETE = 1 << 1, // Window will not be painted because of which desktop it's on PAINT_DISABLED_BY_DESKTOP = 1 << 2, // Window will not be painted because it is minimized PAINT_DISABLED_BY_MINIMIZE = 1 << 3, // Window will not be painted because it's not on the current activity PAINT_DISABLED_BY_ACTIVITY = 1 << 5 }; void enablePainting(int reason); void disablePainting(int reason); // is the window visible at all bool isVisible() const; // is the window fully opaque bool isOpaque() const; // shape of the window const QRegion &shape() const; QRegion clientShape() const; void discardShape(); void updateToplevel(Toplevel* c); // creates initial quad list for the window virtual WindowQuadList buildQuads(bool force = false) const; void updateShadow(Shadow* shadow); const Shadow* shadow() const; Shadow* shadow(); void referencePreviousPixmap(); void unreferencePreviousPixmap(); void invalidateQuadsCache(); protected: WindowQuadList makeQuads(WindowQuadType type, const QRegion& reg, const QPoint &textureOffset = QPoint(0, 0), qreal textureScale = 1.0) const; WindowQuadList makeDecorationQuads(const QRect *rects, const QRegion ®ion, qreal textureScale = 1.0) const; /** * @brief Returns the WindowPixmap for this Window. * * If the WindowPixmap does not yet exist, this method will invoke createWindowPixmap. * If the WindowPixmap is not valid it tries to create it, in case this succeeds the WindowPixmap is * returned. In case it fails, the previous (and still valid) WindowPixmap is returned. * * @note This method can return @c NULL as there might neither be a valid previous nor current WindowPixmap * around. * * The WindowPixmap gets casted to the type passed in as a template parameter. That way this class does not * need to know the actual WindowPixmap subclass used by the concrete Scene implementations. * * @return The WindowPixmap casted to T* or @c NULL if there is no valid window pixmap. */ template T *windowPixmap(); template T *previousWindowPixmap(); /** * @brief Factory method to create a WindowPixmap. * * The inheriting classes need to implement this method to create a new instance of their WindowPixmap subclass. * @note Do not use WindowPixmap::create on the created instance. The Scene will take care of that. */ virtual WindowPixmap *createWindowPixmap() = 0; Toplevel* toplevel; ImageFilterType filter; Shadow *m_shadow; private: QScopedPointer m_currentPixmap; QScopedPointer m_previousPixmap; int m_referencePixmapCounter; int disable_painting; mutable QRegion shape_region; mutable bool shape_valid; mutable QScopedPointer cached_quad_list; Q_DISABLE_COPY(Window) }; /** * @brief Wrapper for a pixmap of the Scene::Window. * * This class encapsulates the functionality to get the pixmap for a window. When initialized the pixmap is not yet * mapped to the window and isValid will return @c false. The pixmap mapping to the window can be established * through @ref create. If it succeeds isValid will return @c true, otherwise it will keep in the non valid * state and it can be tried to create the pixmap mapping again (e.g. in the next frame). * * This class is not intended to be updated when the pixmap is no longer valid due to e.g. resizing the window. * Instead a new instance of this class should be instantiated. The idea behind this is that a valid pixmap does not * get destroyed, but can continue to be used. To indicate that a newer pixmap should in generally be around, one can * use markAsDiscarded. * * This class is intended to be inherited for the needs of the compositor backends which need further mapping from * the native pixmap to the respective rendering format. */ class KWIN_EXPORT WindowPixmap { public: virtual ~WindowPixmap(); /** * @brief Tries to create the mapping between the Window and the pixmap. * * In case this method succeeds in creating the pixmap for the window, isValid will return @c true otherwise * @c false. * * Inheriting classes should re-implement this method in case they need to add further functionality for mapping the * native pixmap to the rendering format. */ virtual void create(); /** * @return @c true if the pixmap has been created and is valid, @c false otherwise */ virtual bool isValid() const; /** * @return The native X11 pixmap handle */ xcb_pixmap_t pixmap() const; /** * @return The Wayland BufferInterface for this WindowPixmap. */ QPointer buffer() const; const QSharedPointer &fbo() const; + QImage internalImage() const; /** * @brief Whether this WindowPixmap is considered as discarded. This means the window has changed in a way that a new * WindowPixmap should have been created already. * * @return @c true if this WindowPixmap is considered as discarded, @c false otherwise. * @see markAsDiscarded */ bool isDiscarded() const; /** * @brief Marks this WindowPixmap as discarded. From now on isDiscarded will return @c true. This method should * only be used by the Window when it changes in a way that a new pixmap is required. * * @see isDiscarded */ void markAsDiscarded(); /** * The size of the pixmap. */ const QSize &size() const; /** * The geometry of the Client's content inside the pixmap. In case of a decorated Client the * pixmap also contains the decoration which is not rendered into this pixmap, though. This * contentsRect tells where inside the complete pixmap the real content is. */ const QRect &contentsRect() const; /** * @brief Returns the Toplevel this WindowPixmap belongs to. * Note: the Toplevel can change over the lifetime of the WindowPixmap in case the Toplevel is copied to Deleted. */ Toplevel *toplevel() const; /** * @returns the parent WindowPixmap in the sub-surface tree */ WindowPixmap *parent() const { return m_parent; } /** * @returns the current sub-surface tree */ QVector children() const { return m_children; } /** * @returns the subsurface this WindowPixmap is for if it is not for a root window */ QPointer subSurface() const { return m_subSurface; } /** * @returns the surface this WindowPixmap references, might be @c null. */ KWayland::Server::SurfaceInterface *surface() const; protected: explicit WindowPixmap(Scene::Window *window); explicit WindowPixmap(const QPointer &subSurface, WindowPixmap *parent); virtual WindowPixmap *createChild(const QPointer &subSurface); /** * @return The Window this WindowPixmap belongs to */ Scene::Window *window(); /** * Should be called by the implementing subclasses when the Wayland Buffer changed and needs * updating. */ virtual void updateBuffer(); /** * Sets the sub-surface tree to @p children. */ void setChildren(const QVector &children) { m_children = children; } private: Scene::Window *m_window; xcb_pixmap_t m_pixmap; QSize m_pixmapSize; bool m_discarded; QRect m_contentsRect; QPointer m_buffer; QSharedPointer m_fbo; + QImage m_internalImage; WindowPixmap *m_parent = nullptr; QVector m_children; QPointer m_subSurface; }; class Scene::EffectFrame { public: EffectFrame(EffectFrameImpl* frame); virtual ~EffectFrame(); virtual void render(QRegion region, double opacity, double frameOpacity) = 0; virtual void free() = 0; virtual void freeIconFrame() = 0; virtual void freeTextFrame() = 0; virtual void freeSelection() = 0; virtual void crossFadeIcon() = 0; virtual void crossFadeText() = 0; protected: EffectFrameImpl* m_effectFrame; }; inline int Scene::Window::x() const { return toplevel->x(); } inline int Scene::Window::y() const { return toplevel->y(); } inline int Scene::Window::width() const { return toplevel->width(); } inline int Scene::Window::height() const { return toplevel->height(); } inline QRect Scene::Window::geometry() const { return toplevel->geometry(); } inline QSize Scene::Window::size() const { return toplevel->size(); } inline QPoint Scene::Window::pos() const { return toplevel->pos(); } inline QRect Scene::Window::rect() const { return toplevel->rect(); } inline Toplevel* Scene::Window::window() const { return toplevel; } inline void Scene::Window::updateToplevel(Toplevel* c) { toplevel = c; } inline const Shadow* Scene::Window::shadow() const { return m_shadow; } inline Shadow* Scene::Window::shadow() { return m_shadow; } inline QPointer WindowPixmap::buffer() const { return m_buffer; } inline const QSharedPointer &WindowPixmap::fbo() const { return m_fbo; } +inline +QImage WindowPixmap::internalImage() const +{ + return m_internalImage; +} + template inline T* Scene::Window::windowPixmap() { if (m_currentPixmap.isNull()) { m_currentPixmap.reset(createWindowPixmap()); } if (m_currentPixmap->isValid()) { return static_cast(m_currentPixmap.data()); } m_currentPixmap->create(); if (m_currentPixmap->isValid()) { return static_cast(m_currentPixmap.data()); } else { return static_cast(m_previousPixmap.data()); } } template inline T* Scene::Window::previousWindowPixmap() { return static_cast(m_previousPixmap.data()); } inline Toplevel* WindowPixmap::toplevel() const { return m_window->window(); } inline xcb_pixmap_t WindowPixmap::pixmap() const { return m_pixmap; } inline bool WindowPixmap::isDiscarded() const { return m_discarded; } inline void WindowPixmap::markAsDiscarded() { m_discarded = true; m_window->referencePreviousPixmap(); } inline const QRect &WindowPixmap::contentsRect() const { return m_contentsRect; } inline const QSize &WindowPixmap::size() const { return m_pixmapSize; } } // namespace Q_DECLARE_INTERFACE(KWin::SceneFactory, "org.kde.kwin.Scene") #endif diff --git a/shell_client.cpp b/shell_client.cpp index f08050363..f453bdec1 100644 --- a/shell_client.cpp +++ b/shell_client.cpp @@ -1,2013 +1,1978 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2015 Martin Gräßlin Copyright (C) 2018 David Edmundson +Copyright (C) 2019 Vlad Zagorodniy 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 . *********************************************************************/ #include "shell_client.h" #include "composite.h" #include "cursor.h" #include "deleted.h" #include "placement.h" #include "screenedge.h" #include "screens.h" #ifdef KWIN_BUILD_TABBOX #include "tabbox.h" #endif #include "virtualdesktops.h" #include "wayland_server.h" #include "workspace.h" #include "decorations/decorationbridge.h" #include "decorations/decoratedclient.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include Q_DECLARE_METATYPE(NET::WindowType) using namespace KWayland::Server; namespace KWin { ShellClient::ShellClient(ShellSurfaceInterface *surface) : AbstractClient() , m_shellSurface(surface) , m_xdgShellSurface(nullptr) , m_xdgShellPopup(nullptr) - , m_internal(surface->client() == waylandServer()->internalConnection()) { setSurface(surface->surface()); init(); m_isInitialized = true; } ShellClient::ShellClient(XdgShellSurfaceInterface *surface) : AbstractClient() , m_shellSurface(nullptr) , m_xdgShellSurface(surface) , m_xdgShellPopup(nullptr) - , m_internal(surface->client() == waylandServer()->internalConnection()) { setSurface(surface->surface()); m_requestGeometryBlockCounter++; init(); connect(surface->surface(), &SurfaceInterface::committed, this, &ShellClient::finishInit); } ShellClient::ShellClient(XdgShellPopupInterface *surface) : AbstractClient() , m_shellSurface(nullptr) , m_xdgShellSurface(nullptr) , m_xdgShellPopup(surface) - , m_internal(surface->client() == waylandServer()->internalConnection()) { setSurface(surface->surface()); m_requestGeometryBlockCounter++; init(); connect(surface->surface(), &SurfaceInterface::committed, this, &ShellClient::finishInit); } ShellClient::~ShellClient() = default; template void ShellClient::initSurface(T *shellSurface) { m_caption = shellSurface->title().simplified(); // delay till end of init QTimer::singleShot(0, this, &ShellClient::updateCaption); connect(shellSurface, &T::destroyed, this, &ShellClient::destroyClient); connect(shellSurface, &T::titleChanged, this, [this] (const QString &s) { const auto oldSuffix = m_captionSuffix; m_caption = s.simplified(); updateCaption(); if (m_captionSuffix == oldSuffix) { // don't emit caption change twice // it already got emitted by the changing suffix emit captionChanged(); } } ); connect(shellSurface, &T::moveRequested, this, [this] { // TODO: check the seat and serial performMouseCommand(Options::MouseMove, Cursor::pos()); } ); // determine the resource name, this is inspired from ICCCM 4.1.2.5 // the binary name of the invoked client QFileInfo info{shellSurface->client()->executablePath()}; QByteArray resourceName; if (info.exists()) { resourceName = info.fileName().toUtf8(); } setResourceClass(resourceName, shellSurface->windowClass()); setDesktopFileName(shellSurface->windowClass()); connect(shellSurface, &T::windowClassChanged, this, [this, resourceName] (const QByteArray &windowClass) { setResourceClass(resourceName, windowClass); if (m_isInitialized && supportsWindowRules()) { setupWindowRules(true); applyWindowRules(); } setDesktopFileName(windowClass); } ); connect(shellSurface, &T::resizeRequested, this, [this] (SeatInterface *seat, quint32 serial, Qt::Edges edges) { // TODO: check the seat and serial Q_UNUSED(seat) Q_UNUSED(serial) if (!isResizable() || isShade()) { return; } if (isMoveResize()) { finishMoveResize(false); } setMoveResizePointerButtonDown(true); setMoveOffset(Cursor::pos() - pos()); // map from global setInvertedMoveOffset(rect().bottomRight() - moveOffset()); setUnrestrictedMoveResize(false); auto toPosition = [edges] { Position pos = PositionCenter; if (edges.testFlag(Qt::TopEdge)) { pos = PositionTop; } else if (edges.testFlag(Qt::BottomEdge)) { pos = PositionBottom; } if (edges.testFlag(Qt::LeftEdge)) { pos = Position(pos | PositionLeft); } else if (edges.testFlag(Qt::RightEdge)) { pos = Position(pos | PositionRight); } return pos; }; setMoveResizePointerMode(toPosition()); if (!startMoveResize()) setMoveResizePointerButtonDown(false); updateCursor(); } ); connect(shellSurface, &T::maximizedChanged, this, [this] (bool maximized) { if (m_shellSurface && isFullScreen()) { // ignore for wl_shell - there it is mutual exclusive and messes with the geometry return; } // If the maximized state of the client hasn't been changed due to a window // rule or because the requested state is the same as the current, then the // compositor still has to send a configure event. RequestGeometryBlocker blocker(this); maximize(maximized ? MaximizeFull : MaximizeRestore); } ); // TODO: consider output! connect(shellSurface, &T::fullscreenChanged, this, &ShellClient::clientFullScreenChanged); connect(shellSurface, &T::transientForChanged, this, &ShellClient::setTransient); connect(this, &ShellClient::geometryChanged, this, &ShellClient::updateClientOutputs); connect(screens(), &Screens::changed, this, &ShellClient::updateClientOutputs); } void ShellClient::init() { connect(this, &ShellClient::desktopFileNameChanged, this, &ShellClient::updateIcon); createWindowId(); setupCompositing(); updateIcon(); SurfaceInterface *s = surface(); Q_ASSERT(s); if (s->buffer()) { setReadyForPainting(); if (shouldExposeToWindowManagement()) { setupWindowManagementInterface(); } m_unmapped = false; m_clientSize = s->size(); } else { ready_for_painting = false; } - if (!m_internal) { - doSetGeometry(QRect(QPoint(0, 0), m_clientSize)); - } + doSetGeometry(QRect(QPoint(0, 0), m_clientSize)); if (waylandServer()->inputMethodConnection() == s->client()) { m_windowType = NET::OnScreenDisplay; } connect(s, &SurfaceInterface::sizeChanged, this, [this] { m_clientSize = surface()->size(); doSetGeometry(QRect(pos(), m_clientSize + QSize(borderLeft() + borderRight(), borderTop() + borderBottom()))); } ); connect(s, &SurfaceInterface::unmapped, this, &ShellClient::unmap); connect(s, &SurfaceInterface::unbound, this, &ShellClient::destroyClient); connect(s, &SurfaceInterface::destroyed, this, &ShellClient::destroyClient); if (m_shellSurface) { initSurface(m_shellSurface); auto setPopup = [this] { // TODO: verify grab serial m_hasPopupGrab = m_shellSurface->isPopup(); }; connect(m_shellSurface, &ShellSurfaceInterface::popupChanged, this, setPopup); setPopup(); } else if (m_xdgShellSurface) { initSurface(m_xdgShellSurface); auto global = static_cast(m_xdgShellSurface->global()); connect(global, &XdgShellInterface::pingDelayed, this, [this](qint32 serial) { auto it = m_pingSerials.find(serial); if (it != m_pingSerials.end()) { qCDebug(KWIN_CORE) << "First ping timeout:" << caption(); setUnresponsive(true); } }); connect(m_xdgShellSurface, &XdgShellSurfaceInterface::configureAcknowledged, this, [this](int serial) { m_lastAckedConfigureRequest = serial; }); connect(global, &XdgShellInterface::pingTimeout, this, [this](qint32 serial) { auto it = m_pingSerials.find(serial); if (it != m_pingSerials.end()) { if (it.value() == PingReason::CloseWindow) { qCDebug(KWIN_CORE) << "Final ping timeout on a close attempt, asking to kill:" << caption(); //for internal windows, killing the window will delete this QPointer guard(this); killWindow(); if (!guard) { return; } } m_pingSerials.erase(it); } }); connect(global, &XdgShellInterface::pongReceived, this, [this](qint32 serial){ auto it = m_pingSerials.find(serial); if (it != m_pingSerials.end()) { setUnresponsive(false); m_pingSerials.erase(it); } }); connect(m_xdgShellSurface, &XdgShellSurfaceInterface::windowMenuRequested, this, [this] (SeatInterface *seat, quint32 serial, const QPoint &surfacePos) { // TODO: check serial on seat Q_UNUSED(seat) Q_UNUSED(serial) performMouseCommand(Options::MouseOperationsMenu, pos() + surfacePos); } ); connect(m_xdgShellSurface, &XdgShellSurfaceInterface::minimizeRequested, this, [this] { performMouseCommand(Options::MouseMinimize, Cursor::pos()); } ); auto configure = [this] { if (m_closing) { return; } if (m_requestGeometryBlockCounter != 0 || areGeometryUpdatesBlocked()) { return; } m_xdgShellSurface->configure(xdgSurfaceStates(), m_requestedClientSize); }; connect(this, &AbstractClient::activeChanged, this, configure); connect(this, &AbstractClient::clientStartUserMovedResized, this, configure); connect(this, &AbstractClient::clientFinishUserMovedResized, this, configure); } else if (m_xdgShellPopup) { connect(m_xdgShellPopup, &XdgShellPopupInterface::grabRequested, this, [this](SeatInterface *seat, quint32 serial) { Q_UNUSED(seat) Q_UNUSED(serial) //TODO - should check the parent had focus m_hasPopupGrab = true; }); connect(m_xdgShellPopup, &XdgShellPopupInterface::configureAcknowledged, this, [this](int serial) { m_lastAckedConfigureRequest = serial; }); connect(m_xdgShellPopup, &XdgShellPopupInterface::destroyed, this, &ShellClient::destroyClient); } // set initial desktop - setDesktop(m_internal ? int(NET::OnAllDesktops) : VirtualDesktopManager::self()->current()); + setDesktop(VirtualDesktopManager::self()->current()); // setup shadow integration getShadow(); connect(s, &SurfaceInterface::shadowChanged, this, &Toplevel::getShadow); connect(waylandServer(), &WaylandServer::foreignTransientChanged, this, [this](KWayland::Server::SurfaceInterface *child) { if (child == surface()) { setTransient(); } }); setTransient(); AbstractClient::updateColorScheme(QString()); } void ShellClient::finishInit() { SurfaceInterface *s = surface(); disconnect(s, &SurfaceInterface::committed, this, &ShellClient::finishInit); bool needsPlacement = !isInitialPositionSet(); if (supportsWindowRules()) { setupWindowRules(false); const QRect originalGeometry = QRect(pos(), sizeForClientSize(clientSize())); const QRect ruledGeometry = rules()->checkGeometry(originalGeometry, true); if (originalGeometry != ruledGeometry) { setGeometry(ruledGeometry); } maximize(rules()->checkMaximize(maximizeMode(), true)); setDesktop(rules()->checkDesktop(desktop(), true)); setDesktopFileName(rules()->checkDesktopFile(desktopFileName(), true).toUtf8()); if (rules()->checkMinimize(isMinimized(), true)) { minimize(true); // No animation. } setSkipTaskbar(rules()->checkSkipTaskbar(skipTaskbar(), true)); setSkipPager(rules()->checkSkipPager(skipPager(), true)); setSkipSwitcher(rules()->checkSkipSwitcher(skipSwitcher(), true)); setKeepAbove(rules()->checkKeepAbove(keepAbove(), true)); setKeepBelow(rules()->checkKeepBelow(keepBelow(), true)); setShortcut(rules()->checkShortcut(shortcut().toString(), true)); updateColorScheme(); // Don't place the client if its position is set by a rule. if (rules()->checkPosition(invalidPoint, true) != invalidPoint) { needsPlacement = false; } // Don't place the client if the maximize state is set by a rule. if (requestedMaximizeMode() != MaximizeRestore) { needsPlacement = false; } discardTemporaryRules(); RuleBook::self()->discardUsed(this, false); // Remove Apply Now rules. updateWindowRules(Rules::All); } if (needsPlacement) { const QRect area = workspace()->clientArea(PlacementArea, Screens::self()->current(), desktop()); placeIn(area); } m_requestGeometryBlockCounter--; if (m_requestGeometryBlockCounter == 0) { requestGeometry(m_blockedRequestGeometry); } m_isInitialized = true; } void ShellClient::destroyClient() { m_closing = true; #ifdef KWIN_BUILD_TABBOX TabBox::TabBox *tabBox = TabBox::TabBox::self(); if (tabBox && tabBox->isDisplayed() && tabBox->currentClient() == this) { tabBox->nextPrev(true); } #endif if (isMoveResize()) { leaveMoveResize(); } // Replace ShellClient with an instance of Deleted in the stacking order. Deleted *deleted = Deleted::create(this); emit windowClosed(this, deleted); // Remove Force Temporarily rules. RuleBook::self()->discardUsed(this, true); destroyWindowManagementInterface(); destroyDecoration(); StackingUpdatesBlocker blocker(workspace()); 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; } } waylandServer()->removeClient(this); deleted->unrefWindow(); m_shellSurface = nullptr; m_xdgShellSurface = nullptr; m_xdgShellPopup = nullptr; deleteClient(this); } void ShellClient::deleteClient(ShellClient *c) { delete c; } QSize ShellClient::toWindowGeometry(const QSize &size) const { QSize adjustedSize = size - QSize(borderLeft() + borderRight(), borderTop() + borderBottom()); // a client going fullscreen should have the window the contents size of the screen if (!isFullScreen() && requestedMaximizeMode() != MaximizeFull) { adjustedSize -= QSize(m_windowMargins.left() + m_windowMargins.right(), m_windowMargins.top() + m_windowMargins.bottom()); } return adjustedSize; } QStringList ShellClient::activities() const { // TODO: implement return QStringList(); } QPoint ShellClient::clientContentPos() const { return -1 * clientPos(); } QSize ShellClient::clientSize() const { return m_clientSize; } void ShellClient::debug(QDebug &stream) const { stream.nospace(); stream << "\'ShellClient:" << surface() << ";WMCLASS:" << resourceClass() << ":" << resourceName() << ";Caption:" << caption() << "\'"; } bool ShellClient::belongsToDesktop() const { const auto clients = waylandServer()->clients(); return std::any_of(clients.constBegin(), clients.constEnd(), [this](const ShellClient *client) { if (belongsToSameApplication(client, SameApplicationChecks())) { return client->isDesktop(); } return false; } ); } Layer ShellClient::layerForDock() const { if (m_plasmaShellSurface) { switch (m_plasmaShellSurface->panelBehavior()) { case PlasmaShellSurfaceInterface::PanelBehavior::WindowsCanCover: return NormalLayer; case PlasmaShellSurfaceInterface::PanelBehavior::AutoHide: return AboveLayer; case PlasmaShellSurfaceInterface::PanelBehavior::WindowsGoBelow: case PlasmaShellSurfaceInterface::PanelBehavior::AlwaysVisible: return DockLayer; default: Q_UNREACHABLE(); break; } } return AbstractClient::layerForDock(); } QRect ShellClient::transparentRect() const { // TODO: implement return QRect(); } NET::WindowType ShellClient::windowType(bool direct, int supported_types) const { // TODO: implement Q_UNUSED(direct) Q_UNUSED(supported_types) return m_windowType; } double ShellClient::opacity() const { return m_opacity; } void ShellClient::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); } void ShellClient::addDamage(const QRegion &damage) { auto s = surface(); if (s->size().isValid()) { m_clientSize = s->size(); updateWindowMargins(); updatePendingGeometry(); } markAsMapped(); setDepth((s->buffer()->hasAlphaChannel() && !isDesktop()) ? 32 : 24); repaints_region += damage.translated(clientPos()); Toplevel::addDamage(damage); } void ShellClient::markAsMapped() { if (!m_unmapped) { return; } m_unmapped = false; if (!ready_for_painting) { setReadyForPainting(); } else { addRepaintFull(); emit windowShown(this); } if (shouldExposeToWindowManagement()) { setupWindowManagementInterface(); } updateShowOnScreenEdge(); } void ShellClient::createDecoration(const QRect &oldGeom) { 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); RequestGeometryBlocker requestBlocker(this); QRect oldgeom = geometry(); if (!isShade()) checkWorkspacePosition(oldgeom); emit geometryShapeChanged(this, oldgeom); } ); } setDecoration(decoration); // TODO: ensure the new geometry still fits into the client area (e.g. maximized windows) doSetGeometry(QRect(oldGeom.topLeft(), m_clientSize + (decoration ? QSize(decoration->borderLeft() + decoration->borderRight(), decoration->borderBottom() + decoration->borderTop()) : QSize()))); emit geometryShapeChanged(this, oldGeom); } void ShellClient::updateDecoration(bool check_workspace_pos, bool force) { if (!force && ((!isDecorated() && noBorder()) || (isDecorated() && !noBorder()))) return; QRect oldgeom = geometry(); QRect oldClientGeom = oldgeom.adjusted(borderLeft(), borderTop(), -borderRight(), -borderBottom()); blockGeometryUpdates(true); if (force) destroyDecoration(); if (!noBorder()) { createDecoration(oldgeom); } else destroyDecoration(); if (m_serverDecoration && isDecorated()) { m_serverDecoration->setMode(KWayland::Server::ServerSideDecorationManagerInterface::Mode::Server); } if (m_xdgDecoration) { auto mode = isDecorated() || m_userNoBorder ? XdgDecorationInterface::Mode::ServerSide: XdgDecorationInterface::Mode::ClientSide; m_xdgDecoration->configure(mode); if (m_requestGeometryBlockCounter == 0) { m_xdgShellSurface->configure(xdgSurfaceStates(), m_requestedClientSize); } } getShadow(); if (check_workspace_pos) checkWorkspacePosition(oldgeom, -2, oldClientGeom); blockGeometryUpdates(false); } void ShellClient::setGeometry(int x, int y, int w, int h, ForceGeometry_t force) { const QRect newGeometry = rules()->checkGeometry(QRect(x, y, w, h)); if (areGeometryUpdatesBlocked()) { // when the GeometryUpdateBlocker exits the current geom is passed to setGeometry // thus we need to set it here. geom = newGeometry; 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(); } const QSize requestedClientSize = newGeometry.size() - QSize(borderLeft() + borderRight(), borderTop() + borderBottom()); const QSize requestedWindowGeometrySize = toWindowGeometry(newGeometry.size()); if (requestedClientSize == m_clientSize && !isWaitingForMoveResizeSync() && (m_requestedClientSize.isEmpty() || requestedWindowGeometrySize == m_requestedClientSize)) { // size didn't change, and we don't need to explicitly request a new size doSetGeometry(newGeometry); updateMaximizeMode(m_requestedMaximizeMode); } else { // size did change, Client needs to provide a new buffer requestGeometry(newGeometry); } } void ShellClient::doSetGeometry(const QRect &rect) { if (geom == rect && pendingGeometryUpdate() == PendingGeometryNone) { return; } if (!m_unmapped) { addWorkspaceRepaint(visibleRect()); } geom = rect; updateWindowRules(Rules::Position | Rules::Size); if (m_unmapped && m_geomMaximizeRestore.isEmpty() && !geom.isEmpty()) { // use first valid geometry as restore geometry m_geomMaximizeRestore = geom; } if (!m_unmapped) { addWorkspaceRepaint(visibleRect()); } if (hasStrut()) { workspace()->updateClientArea(); } const auto old = geometryBeforeUpdateBlocking(); updateGeometryBeforeUpdateBlocking(); emit geometryShapeChanged(this, old); if (isResize()) { performMoveResize(); } } QByteArray ShellClient::windowRole() const { return QByteArray(); } bool ShellClient::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; } void ShellClient::blockActivityUpdates(bool b) { Q_UNUSED(b) } void ShellClient::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 ShellClient::closeWindow() { if (m_xdgShellSurface && isCloseable()) { m_xdgShellSurface->close(); const qint32 pingSerial = static_cast(m_xdgShellSurface->global())->ping(m_xdgShellSurface); m_pingSerials.insert(pingSerial, PingReason::CloseWindow); } } AbstractClient *ShellClient::findModal(bool allow_itself) { Q_UNUSED(allow_itself) return nullptr; } bool ShellClient::isCloseable() const { if (m_windowType == NET::Desktop || m_windowType == NET::Dock) { return false; } if (m_xdgShellSurface) { return true; } return false; } bool ShellClient::isFullScreen() const { return m_fullScreen; } bool ShellClient::isMaximizable() const { if (!isResizable()) { return false; } if (rules()->checkMaximize(MaximizeRestore) != MaximizeRestore || rules()->checkMaximize(MaximizeFull) != MaximizeFull) { return false; } return true; } bool ShellClient::isMinimizable() const { if (!rules()->checkMinimize(true)) { return false; } return (!m_plasmaShellSurface || m_plasmaShellSurface->role() == PlasmaShellSurfaceInterface::Role::Normal); } bool ShellClient::isMovable() const { if (rules()->checkPosition(invalidPoint) != invalidPoint) { return false; } if (m_plasmaShellSurface) { return m_plasmaShellSurface->role() == PlasmaShellSurfaceInterface::Role::Normal; } if (m_xdgShellPopup) { return false; } return true; } bool ShellClient::isMovableAcrossScreens() const { if (rules()->checkPosition(invalidPoint) != invalidPoint) { return false; } if (m_plasmaShellSurface) { return m_plasmaShellSurface->role() == PlasmaShellSurfaceInterface::Role::Normal; } if (m_xdgShellPopup) { return false; } return true; } bool ShellClient::isResizable() const { if (rules()->checkSize(QSize()).isValid()) { return false; } if (m_plasmaShellSurface) { return m_plasmaShellSurface->role() == PlasmaShellSurfaceInterface::Role::Normal; } if (m_xdgShellPopup) { return false; } return true; } bool ShellClient::isShown(bool shaded_is_shown) const { Q_UNUSED(shaded_is_shown) return !m_closing && !m_unmapped && !isMinimized() && !m_hidden; } void ShellClient::hideClient(bool hide) { if (m_hidden == hide) { return; } m_hidden = hide; if (hide) { addWorkspaceRepaint(visibleRect()); workspace()->clientHidden(this); emit windowHidden(this); } else { emit windowShown(this); } } static bool changeMaximizeRecursion = false; void ShellClient::changeMaximize(bool horizontal, bool vertical, bool adjust) { if (changeMaximizeRecursion) { return; } if (!isResizable()) { return; } const QRect clientArea = isElectricBorderMaximizing() ? workspace()->clientArea(MaximizeArea, Cursor::pos(), desktop()) : workspace()->clientArea(MaximizeArea, this); const MaximizeMode oldMode = m_requestedMaximizeMode; const QRect oldGeometry = geometry(); // 'adjust == true' means to update the size only, e.g. after changing workspace size if (!adjust) { if (vertical) m_requestedMaximizeMode = MaximizeMode(m_requestedMaximizeMode ^ MaximizeVertical); if (horizontal) m_requestedMaximizeMode = MaximizeMode(m_requestedMaximizeMode ^ MaximizeHorizontal); } m_requestedMaximizeMode = rules()->checkMaximize(m_requestedMaximizeMode); if (!adjust && m_requestedMaximizeMode == oldMode) { return; } StackingUpdatesBlocker blocker(workspace()); RequestGeometryBlocker geometryBlocker(this); // call into decoration update borders if (isDecorated() && decoration()->client() && !(options->borderlessMaximizedWindows() && m_requestedMaximizeMode == KWin::MaximizeFull)) { changeMaximizeRecursion = true; const auto c = decoration()->client().data(); if ((m_requestedMaximizeMode & MaximizeVertical) != (oldMode & MaximizeVertical)) { emit c->maximizedVerticallyChanged(m_requestedMaximizeMode & MaximizeVertical); } if ((m_requestedMaximizeMode & MaximizeHorizontal) != (oldMode & MaximizeHorizontal)) { emit c->maximizedHorizontallyChanged(m_requestedMaximizeMode & MaximizeHorizontal); } if ((m_requestedMaximizeMode == MaximizeFull) != (oldMode == MaximizeFull)) { emit c->maximizedChanged(m_requestedMaximizeMode & MaximizeFull); } changeMaximizeRecursion = false; } if (options->borderlessMaximizedWindows()) { // triggers a maximize change. // The next setNoBorder interation will exit since there's no change but the first recursion pullutes the restore geometry changeMaximizeRecursion = true; setNoBorder(rules()->checkNoBorder(m_requestedMaximizeMode == MaximizeFull)); changeMaximizeRecursion = false; } // Conditional quick tiling exit points const auto oldQuickTileMode = quickTileMode(); if (quickTileMode() != QuickTileMode(QuickTileFlag::None)) { if (oldMode == MaximizeFull && !clientArea.contains(m_geomMaximizeRestore.center())) { // Not restoring on the same screen // TODO: The following doesn't work for some reason //quick_tile_mode = QuickTileNone; // And exit quick tile mode manually } else if ((oldMode == MaximizeVertical && m_requestedMaximizeMode == MaximizeRestore) || (oldMode == MaximizeFull && m_requestedMaximizeMode == MaximizeHorizontal)) { // Modifying geometry of a tiled window updateQuickTileMode(QuickTileFlag::None); // Exit quick tile mode without restoring geometry } } if (m_requestedMaximizeMode == MaximizeFull) { m_geomMaximizeRestore = oldGeometry; // TODO: Client has more checks if (options->electricBorderMaximize()) { updateQuickTileMode(QuickTileFlag::Maximize); } else { updateQuickTileMode(QuickTileFlag::None); } if (quickTileMode() != oldQuickTileMode) { emit quickTileModeChanged(); } setGeometry(workspace()->clientArea(MaximizeArea, this)); workspace()->raiseClient(this); } else { if (m_requestedMaximizeMode == MaximizeRestore) { updateQuickTileMode(QuickTileFlag::None); } if (quickTileMode() != oldQuickTileMode) { emit quickTileModeChanged(); } if (m_geomMaximizeRestore.isValid()) { setGeometry(m_geomMaximizeRestore); } else { setGeometry(workspace()->clientArea(PlacementArea, this)); } } } MaximizeMode ShellClient::maximizeMode() const { return m_maximizeMode; } MaximizeMode ShellClient::requestedMaximizeMode() const { return m_requestedMaximizeMode; } bool ShellClient::noBorder() const { if (m_serverDecoration) { if (m_serverDecoration->mode() == ServerSideDecorationManagerInterface::Mode::Server) { return m_userNoBorder || isFullScreen(); } } if (m_xdgDecoration && m_xdgDecoration->requestedMode() != XdgDecorationInterface::Mode::ClientSide) { return m_userNoBorder || isFullScreen(); } return true; } bool ShellClient::isFullScreenable() const { if (!rules()->checkFullScreen(true)) { return false; } return !isSpecialWindow(); } void ShellClient::setFullScreen(bool set, bool user) { set = rules()->checkFullScreen(set); const bool wasFullscreen = isFullScreen(); if (wasFullscreen == set) { return; } if (isSpecialWindow()) { return; } if (user && !userCanSetFullScreen()) { return; } if (wasFullscreen) { workspace()->updateFocusMousePosition(Cursor::pos()); // may cause leave event } else { // in shell surface, maximise mode and fullscreen are exclusive // fullscreen->toplevel should restore the state we had before maximising if (m_shellSurface && m_maximizeMode == MaximizeMode::MaximizeFull) { m_geomFsRestore = m_geomMaximizeRestore; } else { m_geomFsRestore = geometry(); } } m_fullScreen = set; if (set) { workspace()->raiseClient(this); } RequestGeometryBlocker requestBlocker(this); StackingUpdatesBlocker blocker1(workspace()); GeometryUpdatesBlocker blocker2(this); workspace()->updateClientLayer(this); // active fullscreens get different layer updateDecoration(false, false); if (set) { setGeometry(workspace()->clientArea(FullScreenArea, this)); } else { if (m_geomFsRestore.isValid()) { int currentScreen = screen(); setGeometry(QRect(m_geomFsRestore.topLeft(), adjustedSize(m_geomFsRestore.size()))); if( currentScreen != screen()) workspace()->sendClientToScreen( this, currentScreen ); } else { // this can happen when the window was first shown already fullscreen, // so let the client set the size by itself setGeometry(QRect(workspace()->clientArea(PlacementArea, this).topLeft(), QSize(0, 0))); } } updateWindowRules(Rules::Fullscreen|Rules::Position|Rules::Size); emit fullScreenChanged(); } void ShellClient::setNoBorder(bool set) { if (!userCanSetNoBorder()) { return; } set = rules()->checkNoBorder(set); if (m_userNoBorder == set) { return; } m_userNoBorder = set; updateDecoration(true, false); updateWindowRules(Rules::NoBorder); } void ShellClient::setOnAllActivities(bool set) { Q_UNUSED(set) } void ShellClient::takeFocus() { if (rules()->checkAcceptFocus(wantsInput())) { if (m_xdgShellSurface) { const qint32 pingSerial = static_cast(m_xdgShellSurface->global())->ping(m_xdgShellSurface); m_pingSerials.insert(pingSerial, PingReason::FocusWindow); } setActive(true); } if (!keepAbove() && !isOnScreenDisplay() && !belongsToDesktop()) { workspace()->setShowingDesktop(false); } } void ShellClient::doSetActive() { if (!isActive()) { return; } StackingUpdatesBlocker blocker(workspace()); workspace()->focusToNull(); } bool ShellClient::userCanSetFullScreen() const { if (m_xdgShellSurface) { return true; } return false; } bool ShellClient::userCanSetNoBorder() const { if (m_serverDecoration && m_serverDecoration->mode() == ServerSideDecorationManagerInterface::Mode::Server) { return !isFullScreen() && !isShade(); } if (m_xdgDecoration && m_xdgDecoration->requestedMode() != XdgDecorationInterface::Mode::ClientSide) { return !isFullScreen() && !isShade(); } return false; } bool ShellClient::wantsInput() const { return rules()->checkAcceptFocus(acceptsFocus()); } bool ShellClient::acceptsFocus() const { if (waylandServer()->inputMethodConnection() == surface()->client()) { return false; } if (m_plasmaShellSurface) { if (m_plasmaShellSurface->role() == PlasmaShellSurfaceInterface::Role::OnScreenDisplay || m_plasmaShellSurface->role() == PlasmaShellSurfaceInterface::Role::ToolTip || m_plasmaShellSurface->role() == PlasmaShellSurfaceInterface::Role::Notification || m_plasmaShellSurface->role() == PlasmaShellSurfaceInterface::Role::CriticalNotification) { return false; } } if (m_closing) { // a closing window does not accept focus return false; } if (m_unmapped) { // an unmapped window does not accept focus return false; } if (m_shellSurface) { if (m_shellSurface->isPopup()) { return false; } return m_shellSurface->acceptsKeyboardFocus(); } if (m_xdgShellSurface) { // TODO: proper return true; } return false; } void ShellClient::createWindowId() { - if (!m_internal) { - m_windowId = waylandServer()->createWindowId(surface()); - } + m_windowId = waylandServer()->createWindowId(surface()); } pid_t ShellClient::pid() const { return surface()->client()->processId(); } bool ShellClient::isLockScreen() const { return surface()->client() == waylandServer()->screenLockerClientConnection(); } bool ShellClient::isInputMethod() const { return surface()->client() == waylandServer()->inputMethodConnection(); } -bool ShellClient::requestGeometry(const QRect &rect) +void ShellClient::requestGeometry(const QRect &rect) { if (m_requestGeometryBlockCounter != 0) { m_blockedRequestGeometry = rect; - return false; + return; } QSize size; if (rect.isValid()) { size = toWindowGeometry(rect.size()); } else { size = QSize(0, 0); } m_requestedClientSize = size; quint64 serialId = 0; if (m_shellSurface && !size.isEmpty()) { m_shellSurface->requestSize(size); } if (m_xdgShellSurface) { serialId = m_xdgShellSurface->configure(xdgSurfaceStates(), size); } if (m_xdgShellPopup) { auto parent = transientFor(); if (parent) { const QPoint globalClientContentPos = parent->geometry().topLeft() + parent->clientPos(); const QPoint relativeOffset = rect.topLeft() - globalClientContentPos; serialId = m_xdgShellPopup->configure(QRect(relativeOffset, size)); } } if (rect.isValid()) { //if there's no requested size, then there's implicity no positional information worth using PendingConfigureRequest configureRequest; configureRequest.serialId = serialId; configureRequest.positionAfterResize = rect.topLeft(); configureRequest.maximizeMode = m_requestedMaximizeMode; m_pendingConfigureRequests.append(configureRequest); } m_blockedRequestGeometry = QRect(); - return true; } void ShellClient::updatePendingGeometry() { QPoint position = pos(); MaximizeMode maximizeMode = m_maximizeMode; for (auto it = m_pendingConfigureRequests.begin(); it != m_pendingConfigureRequests.end(); it++) { if (it->serialId > m_lastAckedConfigureRequest) { //this serial is not acked yet, therefore we know all future serials are not break; } if (it->serialId == m_lastAckedConfigureRequest) { if (position != it->positionAfterResize) { addLayerRepaint(geometry()); } position = it->positionAfterResize; maximizeMode = it->maximizeMode; m_pendingConfigureRequests.erase(m_pendingConfigureRequests.begin(), ++it); break; } //else serialId < m_lastAckedConfigureRequest and the state is now irrelevant and can be ignored } doSetGeometry(QRect(position, m_clientSize + QSize(borderLeft() + borderRight(), borderTop() + borderBottom()))); updateMaximizeMode(maximizeMode); } void ShellClient::clientFullScreenChanged(bool fullScreen) { setFullScreen(fullScreen, false); } void ShellClient::resizeWithChecks(int w, int h, ForceGeometry_t force) { Q_UNUSED(force) 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(); } if (m_shellSurface) { m_shellSurface->requestSize(QSize(w, h)); } if (m_xdgShellSurface) { m_xdgShellSurface->configure(xdgSurfaceStates(), QSize(w, h)); } } void ShellClient::unmap() { m_unmapped = true; if (isMoveResize()) { leaveMoveResize(); } m_requestedClientSize = QSize(0, 0); destroyWindowManagementInterface(); if (Workspace::self()) { addWorkspaceRepaint(visibleRect()); workspace()->clientHidden(this); } emit windowHidden(this); } void ShellClient::installPlasmaShellSurface(PlasmaShellSurfaceInterface *surface) { m_plasmaShellSurface = surface; auto updatePosition = [this, surface] { QRect rect = QRect(surface->position(), m_clientSize + QSize(borderLeft() + borderRight(), borderTop() + borderBottom())); - // Shell surfaces of internal windows are sometimes desync to current value. - // Make sure to not set window geometry of internal windows to invalid values (bug 386304). - // This is a workaround. - if (!m_internal || rect.isValid()) { - doSetGeometry(rect); - } + doSetGeometry(rect); }; auto updateRole = [this, surface] { NET::WindowType type = NET::Unknown; switch (surface->role()) { case PlasmaShellSurfaceInterface::Role::Desktop: type = NET::Desktop; break; case PlasmaShellSurfaceInterface::Role::Panel: type = NET::Dock; break; case PlasmaShellSurfaceInterface::Role::OnScreenDisplay: type = NET::OnScreenDisplay; break; case PlasmaShellSurfaceInterface::Role::Notification: type = NET::Notification; break; case PlasmaShellSurfaceInterface::Role::ToolTip: type = NET::Tooltip; break; case PlasmaShellSurfaceInterface::Role::CriticalNotification: type = NET::CriticalNotification; break; case PlasmaShellSurfaceInterface::Role::Normal: default: type = NET::Normal; break; } if (type != m_windowType) { m_windowType = type; if (m_windowType == NET::Desktop || type == NET::Dock || type == NET::OnScreenDisplay || type == NET::Notification || type == NET::Tooltip || type == NET::CriticalNotification) { setOnAllDesktops(true); } workspace()->updateClientArea(); } }; connect(surface, &PlasmaShellSurfaceInterface::positionChanged, this, updatePosition); connect(surface, &PlasmaShellSurfaceInterface::roleChanged, this, updateRole); connect(surface, &PlasmaShellSurfaceInterface::panelBehaviorChanged, this, [this] { updateShowOnScreenEdge(); workspace()->updateClientArea(); } ); connect(surface, &PlasmaShellSurfaceInterface::panelAutoHideHideRequested, this, [this] { hideClient(true); m_plasmaShellSurface->hideAutoHidingPanel(); updateShowOnScreenEdge(); } ); connect(surface, &PlasmaShellSurfaceInterface::panelAutoHideShowRequested, this, [this] { hideClient(false); ScreenEdges::self()->reserve(this, ElectricNone); m_plasmaShellSurface->showAutoHidingPanel(); } ); updatePosition(); updateRole(); updateShowOnScreenEdge(); connect(this, &ShellClient::geometryChanged, this, &ShellClient::updateShowOnScreenEdge); setSkipTaskbar(surface->skipTaskbar()); connect(surface, &PlasmaShellSurfaceInterface::skipTaskbarChanged, this, [this] { setSkipTaskbar(m_plasmaShellSurface->skipTaskbar()); }); setSkipSwitcher(surface->skipSwitcher()); connect(surface, &PlasmaShellSurfaceInterface::skipSwitcherChanged, this, [this] { setSkipSwitcher(m_plasmaShellSurface->skipSwitcher()); }); } void ShellClient::updateShowOnScreenEdge() { if (!ScreenEdges::self()) { return; } if (m_unmapped || !m_plasmaShellSurface || m_plasmaShellSurface->role() != PlasmaShellSurfaceInterface::Role::Panel) { ScreenEdges::self()->reserve(this, ElectricNone); return; } if ((m_plasmaShellSurface->panelBehavior() == PlasmaShellSurfaceInterface::PanelBehavior::AutoHide && m_hidden) || m_plasmaShellSurface->panelBehavior() == PlasmaShellSurfaceInterface::PanelBehavior::WindowsCanCover) { // screen edge API requires an edge, thus we need to figure out which edge the window borders const QRect clientGeometry = geometry(); Qt::Edges edges; for (int i = 0; i < screens()->count(); i++) { const QRect screenGeometry = screens()->geometry(i); if (screenGeometry.left() == clientGeometry.left()) { edges |= Qt::LeftEdge; } if (screenGeometry.right() == clientGeometry.right()) { edges |= Qt::RightEdge; } if (screenGeometry.top() == clientGeometry.top()) { edges |= Qt::TopEdge; } if (screenGeometry.bottom() == clientGeometry.bottom()) { edges |= Qt::BottomEdge; } } // a panel might border multiple screen edges. E.g. a horizontal panel at the bottom will // also border the left and right edge // let's remove such cases if (edges.testFlag(Qt::LeftEdge) && edges.testFlag(Qt::RightEdge)) { edges = edges & (~(Qt::LeftEdge | Qt::RightEdge)); } if (edges.testFlag(Qt::TopEdge) && edges.testFlag(Qt::BottomEdge)) { edges = edges & (~(Qt::TopEdge | Qt::BottomEdge)); } // it's still possible that a panel borders two edges, e.g. bottom and left // in that case the one which is sharing more with the edge wins auto check = [clientGeometry](Qt::Edges edges, Qt::Edge horiz, Qt::Edge vert) { if (edges.testFlag(horiz) && edges.testFlag(vert)) { if (clientGeometry.width() >= clientGeometry.height()) { return edges & ~horiz; } else { return edges & ~vert; } } return edges; }; edges = check(edges, Qt::LeftEdge, Qt::TopEdge); edges = check(edges, Qt::LeftEdge, Qt::BottomEdge); edges = check(edges, Qt::RightEdge, Qt::TopEdge); edges = check(edges, Qt::RightEdge, Qt::BottomEdge); ElectricBorder border = ElectricNone; if (edges.testFlag(Qt::LeftEdge)) { border = ElectricLeft; } if (edges.testFlag(Qt::RightEdge)) { border = ElectricRight; } if (edges.testFlag(Qt::TopEdge)) { border = ElectricTop; } if (edges.testFlag(Qt::BottomEdge)) { border = ElectricBottom; } ScreenEdges::self()->reserve(this, border); } else { ScreenEdges::self()->reserve(this, ElectricNone); } } bool ShellClient::isInitialPositionSet() const { if (m_plasmaShellSurface) { return m_plasmaShellSurface->isPositionSet(); } return false; } void ShellClient::installAppMenu(AppMenuInterface *menu) { m_appMenuInterface = menu; auto updateMenu = [this](AppMenuInterface::InterfaceAddress address) { updateApplicationMenuServiceName(address.serviceName); updateApplicationMenuObjectPath(address.objectPath); }; connect(m_appMenuInterface, &AppMenuInterface::addressChanged, this, [=](AppMenuInterface::InterfaceAddress address) { updateMenu(address); }); updateMenu(menu->address()); } void ShellClient::installPalette(ServerSideDecorationPaletteInterface *palette) { m_paletteInterface = palette; auto updatePalette = [this](const QString &palette) { AbstractClient::updateColorScheme(rules()->checkDecoColor(palette)); }; connect(m_paletteInterface, &ServerSideDecorationPaletteInterface::paletteChanged, this, [=](const QString &palette) { updatePalette(palette); }); connect(m_paletteInterface, &QObject::destroyed, this, [=]() { updatePalette(QString()); }); updatePalette(palette->palette()); } void ShellClient::updateColorScheme() { if (m_paletteInterface) { AbstractClient::updateColorScheme(rules()->checkDecoColor(m_paletteInterface->palette())); } else { AbstractClient::updateColorScheme(rules()->checkDecoColor(QString())); } } void ShellClient::updateMaximizeMode(MaximizeMode maximizeMode) { if (maximizeMode == m_maximizeMode) { return; } m_maximizeMode = maximizeMode; updateWindowRules(Rules::MaximizeHoriz | Rules::MaximizeVert | Rules::Position | Rules::Size); emit clientMaximizedStateChanged(this, m_maximizeMode); emit clientMaximizedStateChanged(this, m_maximizeMode & MaximizeHorizontal, m_maximizeMode & MaximizeVertical); } bool ShellClient::hasStrut() const { if (!isShown(true)) { return false; } if (!m_plasmaShellSurface) { return false; } if (m_plasmaShellSurface->role() != PlasmaShellSurfaceInterface::Role::Panel) { return false; } return m_plasmaShellSurface->panelBehavior() == PlasmaShellSurfaceInterface::PanelBehavior::AlwaysVisible; } void ShellClient::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)); } bool ShellClient::isTransient() const { return m_transient; } void ShellClient::setTransient() { SurfaceInterface *s = nullptr; if (m_shellSurface) { s = m_shellSurface->transientFor().data(); } if (m_xdgShellSurface) { if (auto transient = m_xdgShellSurface->transientFor().data()) { s = transient->surface(); } } if (m_xdgShellPopup) { s = m_xdgShellPopup->transientFor().data(); } if (!s) { s = waylandServer()->findForeignTransientForSurface(surface()); } auto t = waylandServer()->findClient(s); if (t != transientFor()) { // remove from main client if (transientFor()) transientFor()->removeTransient(this); setTransientFor(t); if (t) { t->addTransient(this); } } m_transient = (s != nullptr); } bool ShellClient::hasTransientPlacementHint() const { return isTransient() && transientFor() != nullptr && (m_shellSurface || m_xdgShellPopup); } QRect ShellClient::transientPlacement(const QRect &bounds) const { QRect anchorRect; Qt::Edges anchorEdge; Qt::Edges gravity; QPoint offset; PositionerConstraints constraintAdjustments; QSize size = geometry().size(); const QPoint parentClientPos = transientFor()->pos() + transientFor()->clientPos(); QRect popupPosition; // returns if a target is within the supplied bounds, optional edges argument states which side to check auto inBounds = [bounds](const QRect &target, Qt::Edges edges = Qt::LeftEdge | Qt::RightEdge | Qt::TopEdge | Qt::BottomEdge) -> bool { if (edges & Qt::LeftEdge && target.left() < bounds.left()) { return false; } if (edges & Qt::TopEdge && target.top() < bounds.top()) { return false; } if (edges & Qt::RightEdge && target.right() > bounds.right()) { //normal QRect::right issue cancels out return false; } if (edges & Qt::BottomEdge && target.bottom() > bounds.bottom()) { return false; } return true; }; if (m_shellSurface) { anchorRect = QRect(m_shellSurface->transientOffset(), QSize(1,1)); anchorEdge = Qt::TopEdge | Qt::LeftEdge; gravity = Qt::BottomEdge | Qt::RightEdge; //our single point represents the top left of the popup constraintAdjustments = (PositionerConstraint::SlideX | PositionerConstraint::SlideY); } else if (m_xdgShellPopup) { anchorRect = m_xdgShellPopup->anchorRect(); anchorEdge = m_xdgShellPopup->anchorEdge(); gravity = m_xdgShellPopup->gravity(); offset = m_xdgShellPopup->anchorOffset(); constraintAdjustments = m_xdgShellPopup->constraintAdjustments(); if (!size.isValid()) { size = m_xdgShellPopup->initialSize(); } } else { Q_UNREACHABLE(); } //initial position popupPosition = QRect(popupOffset(anchorRect, anchorEdge, gravity, size) + offset + parentClientPos, size); //if that fits, we don't need to do anything if (inBounds(popupPosition)) { return popupPosition; } //otherwise apply constraint adjustment per axis in order XDG Shell Popup states if (constraintAdjustments & PositionerConstraint::FlipX) { if (!inBounds(popupPosition, Qt::LeftEdge | Qt::RightEdge)) { //flip both edges (if either bit is set, XOR both) auto flippedAnchorEdge = anchorEdge; if (flippedAnchorEdge & (Qt::LeftEdge | Qt::RightEdge)) { flippedAnchorEdge ^= (Qt::LeftEdge | Qt::RightEdge); } auto flippedGravity = gravity; if (flippedGravity & (Qt::LeftEdge | Qt::RightEdge)) { flippedGravity ^= (Qt::LeftEdge | Qt::RightEdge); } auto flippedPopupPosition = QRect(popupOffset(anchorRect, flippedAnchorEdge, flippedGravity, size) + offset + parentClientPos, size); //if it still doesn't fit we should continue with the unflipped version if (inBounds(flippedPopupPosition, Qt::LeftEdge | Qt::RightEdge)) { popupPosition.moveLeft(flippedPopupPosition.x()); } } } if (constraintAdjustments & PositionerConstraint::SlideX) { if (!inBounds(popupPosition, Qt::LeftEdge)) { popupPosition.moveLeft(bounds.x()); } if (!inBounds(popupPosition, Qt::RightEdge)) { // moveRight suffers from the classic QRect off by one issue popupPosition.moveLeft(bounds.x() + bounds.width() - size.width()); } } if (constraintAdjustments & PositionerConstraint::ResizeX) { //TODO //but we need to sort out when this is run as resize should only happen before first configure } if (constraintAdjustments & PositionerConstraint::FlipY) { if (!inBounds(popupPosition, Qt::TopEdge | Qt::BottomEdge)) { //flip both edges (if either bit is set, XOR both) auto flippedAnchorEdge = anchorEdge; if (flippedAnchorEdge & (Qt::TopEdge | Qt::BottomEdge)) { flippedAnchorEdge ^= (Qt::TopEdge | Qt::BottomEdge); } auto flippedGravity = gravity; if (flippedGravity & (Qt::TopEdge | Qt::BottomEdge)) { flippedGravity ^= (Qt::TopEdge | Qt::BottomEdge); } auto flippedPopupPosition = QRect(popupOffset(anchorRect, flippedAnchorEdge, flippedGravity, size) + offset + parentClientPos, size); //if it still doesn't fit we should continue with the unflipped version if (inBounds(flippedPopupPosition, Qt::TopEdge | Qt::BottomEdge)) { popupPosition.moveTop(flippedPopupPosition.y()); } } } if (constraintAdjustments & PositionerConstraint::SlideY) { if (!inBounds(popupPosition, Qt::TopEdge)) { popupPosition.moveTop(bounds.y()); } if (!inBounds(popupPosition, Qt::BottomEdge)) { popupPosition.moveTop(bounds.y() + bounds.height() - size.height()); } } if (constraintAdjustments & PositionerConstraint::ResizeY) { //TODO } return popupPosition; } QPoint ShellClient::popupOffset(const QRect &anchorRect, const Qt::Edges anchorEdge, const Qt::Edges gravity, const QSize popupSize) const { QPoint anchorPoint; switch (anchorEdge & (Qt::LeftEdge | Qt::RightEdge)) { case Qt::LeftEdge: anchorPoint.setX(anchorRect.x()); break; case Qt::RightEdge: anchorPoint.setX(anchorRect.x() + anchorRect.width()); break; default: anchorPoint.setX(qRound(anchorRect.x() + anchorRect.width() / 2.0)); } switch (anchorEdge & (Qt::TopEdge | Qt::BottomEdge)) { case Qt::TopEdge: anchorPoint.setY(anchorRect.y()); break; case Qt::BottomEdge: anchorPoint.setY(anchorRect.y() + anchorRect.height()); break; default: anchorPoint.setY(qRound(anchorRect.y() + anchorRect.height() / 2.0)); } // calculate where the top left point of the popup will end up with the applied gravity // gravity indicates direction. i.e if gravitating towards the top the popup's bottom edge // will next to the anchor point QPoint popupPosAdjust; switch (gravity & (Qt::LeftEdge | Qt::RightEdge)) { case Qt::LeftEdge: popupPosAdjust.setX(-popupSize.width()); break; case Qt::RightEdge: popupPosAdjust.setX(0); break; default: popupPosAdjust.setX(qRound(-popupSize.width() / 2.0)); } switch (gravity & (Qt::TopEdge | Qt::BottomEdge)) { case Qt::TopEdge: popupPosAdjust.setY(-popupSize.height()); break; case Qt::BottomEdge: popupPosAdjust.setY(0); break; default: popupPosAdjust.setY(qRound(-popupSize.height() / 2.0)); } return anchorPoint + popupPosAdjust; } bool ShellClient::isWaitingForMoveResizeSync() const { if (m_shellSurface) { return !m_pendingConfigureRequests.isEmpty(); } return false; } void ShellClient::doResizeSync() { requestGeometry(moveResizeGeometry()); } QMatrix4x4 ShellClient::inputTransformation() const { QMatrix4x4 m = Toplevel::inputTransformation(); m.translate(-borderLeft(), -borderTop()); return m; } void ShellClient::installServerSideDecoration(KWayland::Server::ServerSideDecorationInterface *deco) { if (m_serverDecoration == deco) { return; } m_serverDecoration = deco; connect(m_serverDecoration, &ServerSideDecorationInterface::destroyed, this, [this] { m_serverDecoration = nullptr; if (m_closing || !Workspace::self()) { return; } if (!m_unmapped) { // maybe delay to next event cycle in case the ShellClient is getting destroyed, too updateDecoration(true); } } ); if (!m_unmapped) { updateDecoration(true); } connect(m_serverDecoration, &ServerSideDecorationInterface::modeRequested, this, [this] (ServerSideDecorationManagerInterface::Mode mode) { const bool changed = mode != m_serverDecoration->mode(); if (changed && !m_unmapped) { updateDecoration(false); } } ); } void ShellClient::installXdgDecoration(XdgDecorationInterface *deco) { Q_ASSERT(m_xdgShellSurface); m_xdgDecoration = deco; connect(m_xdgDecoration, &QObject::destroyed, this, [this] { m_xdgDecoration = nullptr; if (m_closing || !Workspace::self()) { return; } updateDecoration(true); } ); connect(m_xdgDecoration, &XdgDecorationInterface::modeRequested, this, [this] () { //force is true as we must send a new configure response updateDecoration(false, true); }); } bool ShellClient::shouldExposeToWindowManagement() { - if (m_internal) { - return false; - } if (isLockScreen()) { return false; } if (m_xdgShellPopup) { return false; } if (m_shellSurface) { if (m_shellSurface->isTransient() && !m_shellSurface->acceptsKeyboardFocus()) { return false; } } return true; } KWayland::Server::XdgShellSurfaceInterface::States ShellClient::xdgSurfaceStates() const { XdgShellSurfaceInterface::States states; if (isActive()) { states |= XdgShellSurfaceInterface::State::Activated; } if (isFullScreen()) { states |= XdgShellSurfaceInterface::State::Fullscreen; } if (m_requestedMaximizeMode == MaximizeMode::MaximizeFull) { states |= XdgShellSurfaceInterface::State::Maximized; } if (isResize()) { states |= XdgShellSurfaceInterface::State::Resizing; } return states; } void ShellClient::doMinimize() { if (isMinimized()) { workspace()->clientHidden(this); } else { emit windowShown(this); } workspace()->updateMinimizedOfTransients(this); } -bool ShellClient::setupCompositing() -{ - if (m_compositingSetup) { - return true; - } - m_compositingSetup = Toplevel::setupCompositing(); - return m_compositingSetup; -} - -void ShellClient::finishCompositing(ReleaseReason releaseReason) -{ - m_compositingSetup = false; - Toplevel::finishCompositing(releaseReason); -} - void ShellClient::placeIn(const QRect &area) { Placement::self()->place(this, area); setGeometryRestore(geometry()); } void ShellClient::showOnScreenEdge() { if (!m_plasmaShellSurface || m_unmapped) { return; } hideClient(false); workspace()->raiseClient(this); if (m_plasmaShellSurface->panelBehavior() == PlasmaShellSurfaceInterface::PanelBehavior::AutoHide) { m_plasmaShellSurface->showAutoHidingPanel(); } } bool ShellClient::dockWantsInput() const { if (m_plasmaShellSurface) { if (m_plasmaShellSurface->role() == PlasmaShellSurfaceInterface::Role::Panel) { return m_plasmaShellSurface->panelTakesFocus(); } } return false; } void ShellClient::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); } bool ShellClient::hasPopupGrab() const { return m_hasPopupGrab; } void ShellClient::popupDone() { if (m_shellSurface) { m_shellSurface->popupDone(); } if (m_xdgShellPopup) { m_xdgShellPopup->popupDone(); } } void ShellClient::updateClientOutputs() { QVector clientOutputs; const auto outputs = waylandServer()->display()->outputs(); for (OutputInterface* output: qAsConst(outputs)) { const QRect outputGeom(output->globalPosition(), output->pixelSize() / output->scale()); if (geometry().intersects(outputGeom)) { clientOutputs << output; } } surface()->setOutputs(clientOutputs); } void ShellClient::updateWindowMargins() { QRect windowGeometry; QSize clientSize = m_clientSize; if (m_xdgShellSurface) { windowGeometry = m_xdgShellSurface->windowGeometry(); } else if (m_xdgShellPopup) { windowGeometry = m_xdgShellPopup->windowGeometry(); if (!clientSize.isValid()) { clientSize = m_xdgShellPopup->initialSize(); } } else { return; } if (windowGeometry.isEmpty() || windowGeometry.width() > clientSize.width() || windowGeometry.height() > clientSize.height()) { m_windowMargins = QMargins(); } else { m_windowMargins = QMargins(windowGeometry.left(), windowGeometry.top(), clientSize.width() - (windowGeometry.right() + 1), clientSize.height() - (windowGeometry.bottom() + 1)); } } bool ShellClient::isPopupWindow() const { if (Toplevel::isPopupWindow()) { return true; } if (m_shellSurface != nullptr) { return m_shellSurface->isPopup(); } if (m_xdgShellPopup != nullptr) { return true; } return false; } -QWindow *ShellClient::internalWindow() const -{ - return nullptr; -} - bool ShellClient::supportsWindowRules() const { if (m_plasmaShellSurface) { return false; } return m_xdgShellSurface; } } diff --git a/shell_client.h b/shell_client.h index 4e2aa31a6..e6f840c2b 100644 --- a/shell_client.h +++ b/shell_client.h @@ -1,310 +1,295 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2015 Martin Gräßlin Copyright (C) 2018 David Edmundson +Copyright (C) 2019 Vlad Zagorodniy 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 . *********************************************************************/ #ifndef KWIN_SHELL_CLIENT_H #define KWIN_SHELL_CLIENT_H #include "abstract_client.h" #include namespace KWayland { namespace Server { class ShellSurfaceInterface; class ServerSideDecorationInterface; class ServerSideDecorationPaletteInterface; class AppMenuInterface; class PlasmaShellSurfaceInterface; class XdgDecorationInterface; } } namespace KWin { /** * @brief The reason for which the server pinged a client surface */ enum class PingReason { CloseWindow = 0, FocusWindow }; class KWIN_EXPORT ShellClient : public AbstractClient { Q_OBJECT public: ShellClient(KWayland::Server::ShellSurfaceInterface *surface); ShellClient(KWayland::Server::XdgShellSurfaceInterface *surface); ShellClient(KWayland::Server::XdgShellPopupInterface *surface); ~ShellClient() override; QStringList activities() const override; QPoint clientContentPos() const override; QSize clientSize() const override; QRect transparentRect() const override; NET::WindowType windowType(bool direct = false, int supported_types = 0) const override; void debug(QDebug &stream) const override; double opacity() const override; void setOpacity(double opacity) override; QByteArray windowRole() const override; void blockActivityUpdates(bool b = true) override; QString captionNormal() const override { return m_caption; } QString captionSuffix() const override { return m_captionSuffix; } void closeWindow() override; AbstractClient *findModal(bool allow_itself = false) override; bool isCloseable() const override; bool isFullScreenable() const override; bool isFullScreen() const override; bool isMaximizable() const override; bool isMinimizable() const override; bool isMovable() const override; bool isMovableAcrossScreens() const override; bool isResizable() const override; bool isShown(bool shaded_is_shown) const override; bool isHiddenInternal() const override { return m_unmapped || m_hidden; } void hideClient(bool hide) override; MaximizeMode maximizeMode() const override; MaximizeMode requestedMaximizeMode() const override; QRect geometryRestore() const override { return m_geomMaximizeRestore; } bool noBorder() const override; void setFullScreen(bool set, bool user = true) override; void setNoBorder(bool set) override; void updateDecoration(bool check_workspace_pos, bool force = false) override; void setOnAllActivities(bool set) override; void takeFocus() override; bool userCanSetFullScreen() const override; bool userCanSetNoBorder() const override; bool wantsInput() const override; bool dockWantsInput() const override; using AbstractClient::resizeWithChecks; void resizeWithChecks(int w, int h, ForceGeometry_t force = NormalGeometrySet) override; using AbstractClient::setGeometry; void setGeometry(int x, int y, int w, int h, ForceGeometry_t force = NormalGeometrySet) override; bool hasStrut() const override; quint32 windowId() const override { return m_windowId; } /** * The process for this client. * Note that processes started by kwin will share its process id. * @since 5.11 * @returns the process if for this client. */ pid_t pid() const override; bool isLockScreen() const override; bool isInputMethod() const override; - virtual QWindow *internalWindow() const; void installPlasmaShellSurface(KWayland::Server::PlasmaShellSurfaceInterface *surface); void installServerSideDecoration(KWayland::Server::ServerSideDecorationInterface *decoration); void installAppMenu(KWayland::Server::AppMenuInterface *appmenu); void installPalette(KWayland::Server::ServerSideDecorationPaletteInterface *palette); void installXdgDecoration(KWayland::Server::XdgDecorationInterface *decoration); bool isInitialPositionSet() const override; bool isTransient() const override; bool hasTransientPlacementHint() const override; QRect transientPlacement(const QRect &bounds) const override; QMatrix4x4 inputTransformation() const override; - - bool setupCompositing() override; - void finishCompositing(ReleaseReason releaseReason = ReleaseReason::Release) override; - void showOnScreenEdge() override; void killWindow() override; void placeIn(const QRect &area); bool hasPopupGrab() const override; void popupDone() override; void updateColorScheme() override; bool isPopupWindow() const override; bool isLocalhost() const override { return true; } bool supportsWindowRules() const override; protected: void addDamage(const QRegion &damage) override; bool belongsToSameApplication(const AbstractClient *other, SameApplicationChecks checks) const override; void doSetActive() override; bool belongsToDesktop() const override; Layer layerForDock() const override; void changeMaximize(bool horizontal, bool vertical, bool adjust) override; void setGeometryRestore(const QRect &geo) override { m_geomMaximizeRestore = geo; } void doResizeSync() override; bool isWaitingForMoveResizeSync() const override; bool acceptsFocus() const override; void doMinimize() override; void updateCaption() override; - virtual bool requestGeometry(const QRect &rect); - virtual void doSetGeometry(const QRect &rect); - void unmap(); - void markAsMapped(); - - void setClientSize(const QSize &size) { - m_clientSize = size; - } - - bool isUnmapped() const { - return m_unmapped; - } - private Q_SLOTS: void clientFullScreenChanged(bool fullScreen); private: /** * Called when the shell is created. */ void init(); /** * Called for the XDG case when the shell surface is committed to the surface. * At this point all initial properties should have been set by the client. */ void finishInit(); template void initSurface(T *shellSurface); void createDecoration(const QRect &oldgeom); void destroyClient(); void createWindowId(); void updateIcon(); void setTransient(); bool shouldExposeToWindowManagement(); void updateClientOutputs(); void updateWindowMargins(); KWayland::Server::XdgShellSurfaceInterface::States xdgSurfaceStates() const; void updateShowOnScreenEdge(); void updateMaximizeMode(MaximizeMode maximizeMode); // called on surface commit and processes all m_pendingConfigureRequests up to m_lastAckedConfigureReqest void updatePendingGeometry(); QPoint popupOffset(const QRect &anchorRect, const Qt::Edges anchorEdge, const Qt::Edges gravity, const QSize popupSize) const; + void requestGeometry(const QRect &rect); + void doSetGeometry(const QRect &rect); + void unmap(); + void markAsMapped(); static void deleteClient(ShellClient *c); QSize toWindowGeometry(const QSize &geometry) const; KWayland::Server::ShellSurfaceInterface *m_shellSurface; KWayland::Server::XdgShellSurfaceInterface *m_xdgShellSurface; KWayland::Server::XdgShellPopupInterface *m_xdgShellPopup; // size of the last buffer QSize m_clientSize; // last size we requested or empty if we haven't sent an explicit request to the client // if empty the client should choose their own default size QSize m_requestedClientSize = QSize(0, 0); struct PendingConfigureRequest { //note for wl_shell we have no serial, so serialId and m_lastAckedConfigureRequest will always be 0 //meaning we treat a surface commit as having processed all requests quint32 serialId = 0; // position to apply after a resize operation has been completed QPoint positionAfterResize; MaximizeMode maximizeMode; }; QVector m_pendingConfigureRequests; quint32 m_lastAckedConfigureRequest = 0; //mode in use by the current buffer MaximizeMode m_maximizeMode = MaximizeRestore; //mode we currently want to be, could be pending on client updating, could be not sent yet MaximizeMode m_requestedMaximizeMode = MaximizeRestore; QRect m_geomFsRestore; //size and position of the window before it was set to fullscreen bool m_closing = false; quint32 m_windowId = 0; bool m_unmapped = true; QRect m_geomMaximizeRestore; // size and position of the window before it was set to maximize NET::WindowType m_windowType = NET::Normal; QPointer m_plasmaShellSurface; QPointer m_appMenuInterface; QPointer m_paletteInterface; KWayland::Server::ServerSideDecorationInterface *m_serverDecoration = nullptr; KWayland::Server::XdgDecorationInterface *m_xdgDecoration = nullptr; bool m_userNoBorder = false; bool m_fullScreen = false; bool m_transient = false; bool m_hidden = false; - bool m_internal; bool m_hasPopupGrab = false; qreal m_opacity = 1.0; class RequestGeometryBlocker { //TODO rename ConfigureBlocker when this class is Xdg only public: RequestGeometryBlocker(ShellClient *client) : m_client(client) { m_client->m_requestGeometryBlockCounter++; } ~RequestGeometryBlocker() { m_client->m_requestGeometryBlockCounter--; if (m_client->m_requestGeometryBlockCounter == 0) { m_client->requestGeometry(m_client->m_blockedRequestGeometry); } } private: ShellClient *m_client; }; friend class RequestGeometryBlocker; int m_requestGeometryBlockCounter = 0; QRect m_blockedRequestGeometry; QString m_caption; QString m_captionSuffix; QHash m_pingSerials; QMargins m_windowMargins; - bool m_compositingSetup = false; bool m_isInitialized = false; friend class Workspace; }; } Q_DECLARE_METATYPE(KWin::ShellClient*) #endif diff --git a/tabbox/tabbox.cpp b/tabbox/tabbox.cpp index 9736ee21b..fa3e1b355 100644 --- a/tabbox/tabbox.cpp +++ b/tabbox/tabbox.cpp @@ -1,1544 +1,1544 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 1999, 2000 Matthias Ettrich Copyright (C) 2003 Lubos Lunak Copyright (C) 2009 Martin Gräßlin 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 . *********************************************************************/ //#define QT_CLEAN_NAMESPACE // own #include "tabbox.h" // tabbox #include "tabbox/clientmodel.h" #include "tabbox/desktopmodel.h" #include "tabbox/tabboxconfig.h" #include "tabbox/desktopchain.h" #include "tabbox/tabbox_logging.h" #include "tabbox/x11_filter.h" // kwin #ifdef KWIN_BUILD_ACTIVITIES #include "activities.h" #endif #include "client.h" #include "effects.h" #include "input.h" #include "keyboard_input.h" #include "pointer_input.h" #include "focuschain.h" #include "screenedge.h" #include "screens.h" #include "unmanaged.h" #include "virtualdesktops.h" #include "workspace.h" #include "xcbutils.h" // Qt #include #include // KDE #include #include #include #include #include // X11 #include #include // xcb #include // specify externals before namespace namespace KWin { namespace TabBox { TabBoxHandlerImpl::TabBoxHandlerImpl(TabBox* tabBox) : TabBoxHandler(tabBox) , m_tabBox(tabBox) , m_desktopFocusChain(new DesktopChainManager(this)) { // connects for DesktopFocusChainManager VirtualDesktopManager *vds = VirtualDesktopManager::self(); connect(vds, SIGNAL(countChanged(uint,uint)), m_desktopFocusChain, SLOT(resize(uint,uint))); connect(vds, SIGNAL(currentChanged(uint,uint)), m_desktopFocusChain, SLOT(addDesktop(uint,uint))); #ifdef KWIN_BUILD_ACTIVITIES if (Activities::self()) { connect(Activities::self(), SIGNAL(currentChanged(QString)), m_desktopFocusChain, SLOT(useChain(QString))); } #endif } TabBoxHandlerImpl::~TabBoxHandlerImpl() { } int TabBoxHandlerImpl::activeScreen() const { return screens()->current(); } int TabBoxHandlerImpl::currentDesktop() const { return VirtualDesktopManager::self()->current(); } QString TabBoxHandlerImpl::desktopName(TabBoxClient* client) const { if (TabBoxClientImpl* c = static_cast< TabBoxClientImpl* >(client)) { if (!c->client()->isOnAllDesktops()) return VirtualDesktopManager::self()->name(c->client()->desktop()); } return VirtualDesktopManager::self()->name(VirtualDesktopManager::self()->current()); } QString TabBoxHandlerImpl::desktopName(int desktop) const { return VirtualDesktopManager::self()->name(desktop); } QWeakPointer TabBoxHandlerImpl::nextClientFocusChain(TabBoxClient* client) const { if (TabBoxClientImpl* c = static_cast< TabBoxClientImpl* >(client)) { auto next = FocusChain::self()->nextMostRecentlyUsed(c->client()); if (next) return next->tabBoxClient(); } return QWeakPointer(); } QWeakPointer< TabBoxClient > TabBoxHandlerImpl::firstClientFocusChain() const { if (auto c = FocusChain::self()->firstMostRecentlyUsed()) { return QWeakPointer(c->tabBoxClient()); } else { return QWeakPointer(); } } bool TabBoxHandlerImpl::isInFocusChain(TabBoxClient *client) const { if (TabBoxClientImpl *c = static_cast(client)) { return FocusChain::self()->contains(c->client()); } return false; } int TabBoxHandlerImpl::nextDesktopFocusChain(int desktop) const { return m_desktopFocusChain->next(desktop); } int TabBoxHandlerImpl::numberOfDesktops() const { return VirtualDesktopManager::self()->count(); } QWeakPointer TabBoxHandlerImpl::activeClient() const { if (Workspace::self()->activeClient()) return Workspace::self()->activeClient()->tabBoxClient(); else return QWeakPointer(); } bool TabBoxHandlerImpl::checkDesktop(TabBoxClient* client, int desktop) const { auto current = (static_cast< TabBoxClientImpl* >(client))->client(); switch (config().clientDesktopMode()) { case TabBoxConfig::AllDesktopsClients: return true; case TabBoxConfig::ExcludeCurrentDesktopClients: return !current->isOnDesktop(desktop); default: // TabBoxConfig::OnlyCurrentDesktopClients return current->isOnDesktop(desktop); } } bool TabBoxHandlerImpl::checkActivity(TabBoxClient* client) const { auto current = (static_cast< TabBoxClientImpl* >(client))->client(); switch (config().clientActivitiesMode()) { case TabBoxConfig::AllActivitiesClients: return true; case TabBoxConfig::ExcludeCurrentActivityClients: return !current->isOnCurrentActivity(); default: // TabBoxConfig::OnlyCurrentActivityClients return current->isOnCurrentActivity(); } } bool TabBoxHandlerImpl::checkApplications(TabBoxClient* client) const { auto current = (static_cast< TabBoxClientImpl* >(client))->client(); TabBoxClientImpl* c; QListIterator< QWeakPointer > i(clientList()); switch (config().clientApplicationsMode()) { case TabBoxConfig::OneWindowPerApplication: // check if the list already contains an entry of this application while (i.hasNext()) { QSharedPointer client = i.next().toStrongRef(); if (!client) { continue; } if ((c = dynamic_cast< TabBoxClientImpl* >(client.data()))) { if (AbstractClient::belongToSameApplication(c->client(), current, AbstractClient::SameApplicationCheck::AllowCrossProcesses)) { return false; } } } return true; case TabBoxConfig::AllWindowsCurrentApplication: { QSharedPointer pointer = tabBox->activeClient().toStrongRef(); if (!pointer) { return false; } if ((c = dynamic_cast< TabBoxClientImpl* >(pointer.data()))) { if (AbstractClient::belongToSameApplication(c->client(), current, AbstractClient::SameApplicationCheck::AllowCrossProcesses)) { return true; } } return false; } default: // TabBoxConfig::AllWindowsAllApplications return true; } } bool TabBoxHandlerImpl::checkMinimized(TabBoxClient* client) const { switch (config().clientMinimizedMode()) { case TabBoxConfig::ExcludeMinimizedClients: return !client->isMinimized(); case TabBoxConfig::OnlyMinimizedClients: return client->isMinimized(); default: // TabBoxConfig::IgnoreMinimizedStatus return true; } } bool TabBoxHandlerImpl::checkMultiScreen(TabBoxClient* client) const { auto current = (static_cast< TabBoxClientImpl* >(client))->client(); switch (config().clientMultiScreenMode()) { case TabBoxConfig::IgnoreMultiScreen: return true; case TabBoxConfig::ExcludeCurrentScreenClients: return current->screen() != screens()->current(); default: // TabBoxConfig::OnlyCurrentScreenClients return current->screen() == screens()->current(); } } QWeakPointer TabBoxHandlerImpl::clientToAddToList(TabBoxClient* client, int desktop) const { if (!client) { return QWeakPointer(); } AbstractClient* ret = nullptr; AbstractClient* current = (static_cast< TabBoxClientImpl* >(client))->client(); bool addClient = checkDesktop(client, desktop) && checkActivity(client) && checkApplications(client) && checkMinimized(client) && checkMultiScreen(client); addClient = addClient && current->wantsTabFocus() && !current->skipSwitcher(); if (addClient) { // don't add windows that have modal dialogs AbstractClient* modal = current->findModal(); if (modal == nullptr || modal == current) ret = current; else if (!clientList().contains(modal->tabBoxClient())) ret = modal; else { // nothing } } if (ret) return ret->tabBoxClient(); else return QWeakPointer(); } TabBoxClientList TabBoxHandlerImpl::stackingOrder() const { ToplevelList stacking = Workspace::self()->stackingOrder(); TabBoxClientList ret; foreach (Toplevel *toplevel, stacking) { if (auto client = qobject_cast(toplevel)) { ret.append(client->tabBoxClient()); } } return ret; } bool TabBoxHandlerImpl::isKWinCompositing() const { return Workspace::self()->compositing(); } void TabBoxHandlerImpl::raiseClient(TabBoxClient* c) const { Workspace::self()->raiseClient(static_cast(c)->client()); } void TabBoxHandlerImpl::restack(TabBoxClient *c, TabBoxClient *under) { Workspace::self()->restack(static_cast(c)->client(), static_cast(under)->client(), true); } void TabBoxHandlerImpl::elevateClient(TabBoxClient *c, QWindow *tabbox, bool b) const { auto cl = static_cast(c)->client(); cl->elevate(b); if (Toplevel *w = Workspace::self()->findInternal(tabbox)) w->elevate(b); } void TabBoxHandlerImpl::shadeClient(TabBoxClient *c, bool b) const { Client *cl = dynamic_cast(static_cast(c)->client()); if (!cl) { // shading is X11 specific return; } cl->cancelShadeHoverTimer(); // stop core shading action if (!b && cl->shadeMode() == ShadeNormal) cl->setShade(ShadeHover); else if (b && cl->shadeMode() == ShadeHover) cl->setShade(ShadeNormal); } QWeakPointer TabBoxHandlerImpl::desktopClient() const { foreach (Toplevel *toplevel, Workspace::self()->stackingOrder()) { auto client = qobject_cast(toplevel); if (client && client->isDesktop() && client->isOnCurrentDesktop() && client->screen() == screens()->current()) { return client->tabBoxClient(); } } return QWeakPointer(); } void TabBoxHandlerImpl::activateAndClose() { m_tabBox->accept(); } void TabBoxHandlerImpl::highlightWindows(TabBoxClient *window, QWindow *controller) { if (!effects) { return; } QVector windows; if (window) { windows << static_cast(window)->client()->effectWindow(); } - if (auto t = Workspace::self()->findToplevel(controller)) { + if (Toplevel *t = workspace()->findInternal(controller)) { windows << t->effectWindow(); } static_cast(effects)->highlightWindows(windows); } bool TabBoxHandlerImpl::noModifierGrab() const { return m_tabBox->noModifierGrab(); } /********************************************************* * TabBoxClientImpl *********************************************************/ TabBoxClientImpl::TabBoxClientImpl(AbstractClient *client) : TabBoxClient() , m_client(client) { } TabBoxClientImpl::~TabBoxClientImpl() { } QString TabBoxClientImpl::caption() const { if (m_client->isDesktop()) return i18nc("Special entry in alt+tab list for minimizing all windows", "Show Desktop"); return m_client->caption(); } QIcon TabBoxClientImpl::icon() const { if (m_client->isDesktop()) { return QIcon::fromTheme(QStringLiteral("user-desktop")); } return m_client->icon(); } WId TabBoxClientImpl::window() const { return m_client->windowId(); } bool TabBoxClientImpl::isMinimized() const { return m_client->isMinimized(); } int TabBoxClientImpl::x() const { return m_client->x(); } int TabBoxClientImpl::y() const { return m_client->y(); } int TabBoxClientImpl::width() const { return m_client->width(); } int TabBoxClientImpl::height() const { return m_client->height(); } bool TabBoxClientImpl::isCloseable() const { return m_client->isCloseable(); } void TabBoxClientImpl::close() { m_client->closeWindow(); } bool TabBoxClientImpl::isFirstInTabBox() const { return m_client->isFirstInTabBox(); } QUuid TabBoxClientImpl::internalId() const { return m_client->internalId(); } /********************************************************* * TabBox *********************************************************/ TabBox *TabBox::s_self = nullptr; TabBox *TabBox::create(QObject *parent) { Q_ASSERT(!s_self); s_self = new TabBox(parent); return s_self; } TabBox::TabBox(QObject *parent) : QObject(parent) , m_displayRefcount(0) , m_desktopGrab(false) , m_tabGrab(false) , m_noModifierGrab(false) , m_forcedGlobalMouseGrab(false) , m_ready(false) { m_isShown = false; m_defaultConfig = TabBoxConfig(); m_defaultConfig.setTabBoxMode(TabBoxConfig::ClientTabBox); m_defaultConfig.setClientDesktopMode(TabBoxConfig::OnlyCurrentDesktopClients); m_defaultConfig.setClientActivitiesMode(TabBoxConfig::OnlyCurrentActivityClients); m_defaultConfig.setClientApplicationsMode(TabBoxConfig::AllWindowsAllApplications); m_defaultConfig.setClientMinimizedMode(TabBoxConfig::IgnoreMinimizedStatus); m_defaultConfig.setShowDesktopMode(TabBoxConfig::DoNotShowDesktopClient); m_defaultConfig.setClientMultiScreenMode(TabBoxConfig::IgnoreMultiScreen); m_defaultConfig.setClientSwitchingMode(TabBoxConfig::FocusChainSwitching); m_alternativeConfig = TabBoxConfig(); m_alternativeConfig.setTabBoxMode(TabBoxConfig::ClientTabBox); m_alternativeConfig.setClientDesktopMode(TabBoxConfig::AllDesktopsClients); m_alternativeConfig.setClientActivitiesMode(TabBoxConfig::OnlyCurrentActivityClients); m_alternativeConfig.setClientApplicationsMode(TabBoxConfig::AllWindowsAllApplications); m_alternativeConfig.setClientMinimizedMode(TabBoxConfig::IgnoreMinimizedStatus); m_alternativeConfig.setShowDesktopMode(TabBoxConfig::DoNotShowDesktopClient); m_alternativeConfig.setClientMultiScreenMode(TabBoxConfig::IgnoreMultiScreen); m_alternativeConfig.setClientSwitchingMode(TabBoxConfig::FocusChainSwitching); m_defaultCurrentApplicationConfig = m_defaultConfig; m_defaultCurrentApplicationConfig.setClientApplicationsMode(TabBoxConfig::AllWindowsCurrentApplication); m_alternativeCurrentApplicationConfig = m_alternativeConfig; m_alternativeCurrentApplicationConfig.setClientApplicationsMode(TabBoxConfig::AllWindowsCurrentApplication); m_desktopConfig = TabBoxConfig(); m_desktopConfig.setTabBoxMode(TabBoxConfig::DesktopTabBox); m_desktopConfig.setShowTabBox(true); m_desktopConfig.setShowDesktopMode(TabBoxConfig::DoNotShowDesktopClient); m_desktopConfig.setDesktopSwitchingMode(TabBoxConfig::MostRecentlyUsedDesktopSwitching); m_desktopListConfig = TabBoxConfig(); m_desktopListConfig.setTabBoxMode(TabBoxConfig::DesktopTabBox); m_desktopListConfig.setShowTabBox(true); m_desktopListConfig.setShowDesktopMode(TabBoxConfig::DoNotShowDesktopClient); m_desktopListConfig.setDesktopSwitchingMode(TabBoxConfig::StaticDesktopSwitching); m_tabBox = new TabBoxHandlerImpl(this); QTimer::singleShot(0, this, SLOT(handlerReady())); m_tabBoxMode = TabBoxDesktopMode; // init variables connect(&m_delayedShowTimer, SIGNAL(timeout()), this, SLOT(show())); connect(Workspace::self(), SIGNAL(configChanged()), this, SLOT(reconfigure())); } TabBox::~TabBox() { s_self = nullptr; } void TabBox::handlerReady() { m_tabBox->setConfig(m_defaultConfig); reconfigure(); m_ready = true; } template void TabBox::key(const char *actionName, Slot slot, const QKeySequence &shortcut) { QAction *a = new QAction(this); a->setProperty("componentName", QStringLiteral(KWIN_NAME)); a->setObjectName(QString::fromUtf8(actionName)); a->setText(i18n(actionName)); KGlobalAccel::self()->setShortcut(a, QList() << shortcut); input()->registerShortcut(shortcut, a, TabBox::self(), slot); auto cuts = KGlobalAccel::self()->shortcut(a); globalShortcutChanged(a, cuts.isEmpty() ? QKeySequence() : cuts.first()); } static const char s_windows[] = I18N_NOOP("Walk Through Windows"); static const char s_windowsRev[] = I18N_NOOP("Walk Through Windows (Reverse)"); static const char s_windowsAlt[] = I18N_NOOP("Walk Through Windows Alternative"); static const char s_windowsAltRev[] = I18N_NOOP("Walk Through Windows Alternative (Reverse)"); static const char s_app[] = I18N_NOOP("Walk Through Windows of Current Application"); static const char s_appRev[] = I18N_NOOP("Walk Through Windows of Current Application (Reverse)"); static const char s_appAlt[] = I18N_NOOP("Walk Through Windows of Current Application Alternative"); static const char s_appAltRev[] = I18N_NOOP("Walk Through Windows of Current Application Alternative (Reverse)"); static const char s_desktops[] = I18N_NOOP("Walk Through Desktops"); static const char s_desktopsRev[] = I18N_NOOP("Walk Through Desktops (Reverse)"); static const char s_desktopList[] = I18N_NOOP("Walk Through Desktop List"); static const char s_desktopListRev[] = I18N_NOOP("Walk Through Desktop List (Reverse)"); void TabBox::initShortcuts() { key(s_windows, &TabBox::slotWalkThroughWindows, Qt::ALT + Qt::Key_Tab); key(s_windowsRev, &TabBox::slotWalkBackThroughWindows, Qt::ALT + Qt::SHIFT + Qt::Key_Backtab); key(s_app, &TabBox::slotWalkThroughCurrentAppWindows, Qt::ALT + Qt::Key_QuoteLeft); key(s_appRev, &TabBox::slotWalkBackThroughCurrentAppWindows, Qt::ALT + Qt::Key_AsciiTilde); key(s_windowsAlt, &TabBox::slotWalkThroughWindowsAlternative); key(s_windowsAltRev, &TabBox::slotWalkBackThroughWindowsAlternative); key(s_appAlt, &TabBox::slotWalkThroughCurrentAppWindowsAlternative); key(s_appAltRev, &TabBox::slotWalkBackThroughCurrentAppWindowsAlternative); key(s_desktops, &TabBox::slotWalkThroughDesktops); key(s_desktopsRev, &TabBox::slotWalkBackThroughDesktops); key(s_desktopList, &TabBox::slotWalkThroughDesktopList); key(s_desktopListRev, &TabBox::slotWalkBackThroughDesktopList); connect(KGlobalAccel::self(), &KGlobalAccel::globalShortcutChanged, this, &TabBox::globalShortcutChanged); } void TabBox::globalShortcutChanged(QAction *action, const QKeySequence &seq) { if (qstrcmp(qPrintable(action->objectName()), s_windows) == 0) { m_cutWalkThroughWindows = seq; } else if (qstrcmp(qPrintable(action->objectName()), s_windowsRev) == 0) { m_cutWalkThroughWindowsReverse = seq; } else if (qstrcmp(qPrintable(action->objectName()), s_app) == 0) { m_cutWalkThroughCurrentAppWindows = seq; } else if (qstrcmp(qPrintable(action->objectName()), s_appRev) == 0) { m_cutWalkThroughCurrentAppWindowsReverse = seq; } else if (qstrcmp(qPrintable(action->objectName()), s_windowsAlt) == 0) { m_cutWalkThroughWindowsAlternative = seq; } else if (qstrcmp(qPrintable(action->objectName()), s_windowsAltRev) == 0) { m_cutWalkThroughWindowsAlternativeReverse = seq; } else if (qstrcmp(qPrintable(action->objectName()), s_appAlt) == 0) { m_cutWalkThroughCurrentAppWindowsAlternative = seq; } else if (qstrcmp(qPrintable(action->objectName()), s_appAltRev) == 0) { m_cutWalkThroughCurrentAppWindowsAlternativeReverse = seq; } else if (qstrcmp(qPrintable(action->objectName()), s_desktops) == 0) { m_cutWalkThroughDesktops = seq; } else if (qstrcmp(qPrintable(action->objectName()), s_desktopsRev) == 0) { m_cutWalkThroughDesktopsReverse = seq; } else if (qstrcmp(qPrintable(action->objectName()), s_desktopList) == 0) { m_cutWalkThroughDesktopList = seq; } else if (qstrcmp(qPrintable(action->objectName()), s_desktopListRev) == 0) { m_cutWalkThroughDesktopListReverse = seq; } } void TabBox::setMode(TabBoxMode mode) { m_tabBoxMode = mode; switch(mode) { case TabBoxWindowsMode: m_tabBox->setConfig(m_defaultConfig); break; case TabBoxWindowsAlternativeMode: m_tabBox->setConfig(m_alternativeConfig); break; case TabBoxCurrentAppWindowsMode: m_tabBox->setConfig(m_defaultCurrentApplicationConfig); break; case TabBoxCurrentAppWindowsAlternativeMode: m_tabBox->setConfig(m_alternativeCurrentApplicationConfig); break; case TabBoxDesktopMode: m_tabBox->setConfig(m_desktopConfig); break; case TabBoxDesktopListMode: m_tabBox->setConfig(m_desktopListConfig); break; } } void TabBox::reset(bool partial_reset) { switch(m_tabBox->config().tabBoxMode()) { case TabBoxConfig::ClientTabBox: m_tabBox->createModel(partial_reset); if (!partial_reset) { if (Workspace::self()->activeClient()) setCurrentClient(Workspace::self()->activeClient()); // it's possible that the active client is not part of the model // in that case the index is invalid if (!m_tabBox->currentIndex().isValid()) setCurrentIndex(m_tabBox->first()); } else { if (!m_tabBox->currentIndex().isValid() || !m_tabBox->client(m_tabBox->currentIndex())) setCurrentIndex(m_tabBox->first()); } break; case TabBoxConfig::DesktopTabBox: m_tabBox->createModel(); if (!partial_reset) setCurrentDesktop(VirtualDesktopManager::self()->current()); break; } emit tabBoxUpdated(); } void TabBox::nextPrev(bool next) { setCurrentIndex(m_tabBox->nextPrev(next), false); emit tabBoxUpdated(); } AbstractClient* TabBox::currentClient() { if (TabBoxClientImpl* client = static_cast< TabBoxClientImpl* >(m_tabBox->client(m_tabBox->currentIndex()))) { if (!Workspace::self()->hasClient(client->client())) return nullptr; return client->client(); } else return nullptr; } QList TabBox::currentClientList() { TabBoxClientList list = m_tabBox->clientList(); QList ret; foreach (const QWeakPointer &clientPointer, list) { QSharedPointer client = clientPointer.toStrongRef(); if (!client) continue; if (const TabBoxClientImpl* c = static_cast< const TabBoxClientImpl* >(client.data())) ret.append(c->client()); } return ret; } int TabBox::currentDesktop() { return m_tabBox->desktop(m_tabBox->currentIndex()); } QList< int > TabBox::currentDesktopList() { return m_tabBox->desktopList(); } void TabBox::setCurrentClient(AbstractClient *newClient) { setCurrentIndex(m_tabBox->index(newClient->tabBoxClient())); } void TabBox::setCurrentDesktop(int newDesktop) { setCurrentIndex(m_tabBox->desktopIndex(newDesktop)); } void TabBox::setCurrentIndex(QModelIndex index, bool notifyEffects) { if (!index.isValid()) return; m_tabBox->setCurrentIndex(index); if (notifyEffects) { emit tabBoxUpdated(); } } void TabBox::show() { emit tabBoxAdded(m_tabBoxMode); if (isDisplayed()) { m_isShown = false; return; } workspace()->setShowingDesktop(false); reference(); m_isShown = true; m_tabBox->show(); } void TabBox::hide(bool abort) { m_delayedShowTimer.stop(); if (m_isShown) { m_isShown = false; unreference(); } emit tabBoxClosed(); if (isDisplayed()) qCDebug(KWIN_TABBOX) << "Tab box was not properly closed by an effect"; m_tabBox->hide(abort); if (kwinApp()->x11Connection()) { Xcb::sync(); } } void TabBox::reconfigure() { KSharedConfigPtr c = kwinApp()->config(); KConfigGroup config = c->group("TabBox"); loadConfig(c->group("TabBox"), m_defaultConfig); loadConfig(c->group("TabBoxAlternative"), m_alternativeConfig); m_defaultCurrentApplicationConfig = m_defaultConfig; m_defaultCurrentApplicationConfig.setClientApplicationsMode(TabBoxConfig::AllWindowsCurrentApplication); m_alternativeCurrentApplicationConfig = m_alternativeConfig; m_alternativeCurrentApplicationConfig.setClientApplicationsMode(TabBoxConfig::AllWindowsCurrentApplication); m_tabBox->setConfig(m_defaultConfig); m_delayShow = config.readEntry("ShowDelay", true); m_delayShowTime = config.readEntry("DelayTime", 90); const QString defaultDesktopLayout = QStringLiteral("org.kde.breeze.desktop"); m_desktopConfig.setLayoutName(config.readEntry("DesktopLayout", defaultDesktopLayout)); m_desktopListConfig.setLayoutName(config.readEntry("DesktopListLayout", defaultDesktopLayout)); QList *borders = &m_borderActivate; QString borderConfig = QStringLiteral("BorderActivate"); for (int i = 0; i < 2; ++i) { foreach (ElectricBorder border, *borders) { ScreenEdges::self()->unreserve(border, this); } borders->clear(); QStringList list = config.readEntry(borderConfig, QStringList()); foreach (const QString &s, list) { bool ok; const int i = s.toInt(&ok); if (!ok) continue; borders->append(ElectricBorder(i)); ScreenEdges::self()->reserve(ElectricBorder(i), this, "toggle"); } borders = &m_borderAlternativeActivate; borderConfig = QStringLiteral("BorderAlternativeActivate"); } auto touchConfig = [this, config] (const QString &key, QHash &actions, TabBoxMode mode, const QStringList &defaults = QStringList{}) { // fist erase old config for (auto it = actions.begin(); it != actions.end(); ) { delete it.value(); it = actions.erase(it); } // now new config const QStringList list = config.readEntry(key, defaults); for (const auto &s : list) { bool ok; const int i = s.toInt(&ok); if (!ok) { continue; } QAction *a = new QAction(this); connect(a, &QAction::triggered, this, std::bind(&TabBox::toggleMode, this, mode)); ScreenEdges::self()->reserveTouch(ElectricBorder(i), a); actions.insert(ElectricBorder(i), a); } }; touchConfig(QStringLiteral("TouchBorderActivate"), m_touchActivate, TabBoxWindowsMode, QStringList{QString::number(int(ElectricLeft))}); touchConfig(QStringLiteral("TouchBorderAlternativeActivate"), m_touchAlternativeActivate, TabBoxWindowsAlternativeMode); } void TabBox::loadConfig(const KConfigGroup& config, TabBoxConfig& tabBoxConfig) { tabBoxConfig.setClientDesktopMode(TabBoxConfig::ClientDesktopMode( config.readEntry("DesktopMode", TabBoxConfig::defaultDesktopMode()))); tabBoxConfig.setClientActivitiesMode(TabBoxConfig::ClientActivitiesMode( config.readEntry("ActivitiesMode", TabBoxConfig::defaultActivitiesMode()))); tabBoxConfig.setClientApplicationsMode(TabBoxConfig::ClientApplicationsMode( config.readEntry("ApplicationsMode", TabBoxConfig::defaultApplicationsMode()))); tabBoxConfig.setClientMinimizedMode(TabBoxConfig::ClientMinimizedMode( config.readEntry("MinimizedMode", TabBoxConfig::defaultMinimizedMode()))); tabBoxConfig.setShowDesktopMode(TabBoxConfig::ShowDesktopMode( config.readEntry("ShowDesktopMode", TabBoxConfig::defaultShowDesktopMode()))); tabBoxConfig.setClientMultiScreenMode(TabBoxConfig::ClientMultiScreenMode( config.readEntry("MultiScreenMode", TabBoxConfig::defaultMultiScreenMode()))); tabBoxConfig.setClientSwitchingMode(TabBoxConfig::ClientSwitchingMode( config.readEntry("SwitchingMode", TabBoxConfig::defaultSwitchingMode()))); tabBoxConfig.setShowTabBox(config.readEntry("ShowTabBox", TabBoxConfig::defaultShowTabBox())); tabBoxConfig.setHighlightWindows(config.readEntry("HighlightWindows", TabBoxConfig::defaultHighlightWindow())); tabBoxConfig.setLayoutName(config.readEntry("LayoutName", TabBoxConfig::defaultLayoutName())); } void TabBox::delayedShow() { if (isDisplayed() || m_delayedShowTimer.isActive()) // already called show - no need to call it twice return; if (!m_delayShowTime) { show(); return; } m_delayedShowTimer.setSingleShot(true); m_delayedShowTimer.start(m_delayShowTime); } bool TabBox::handleMouseEvent(QMouseEvent *event) { if (!m_isShown && isDisplayed()) { // tabbox has been replaced, check effects if (effects && static_cast(effects)->checkInputWindowEvent(event)) { return true; } } switch (event->type()) { case QEvent::MouseMove: if (!m_tabBox->containsPos(event->globalPos())) { // filter out all events which are not on the TabBox window. // We don't want windows to react on the mouse events return true; } return false; case QEvent::MouseButtonPress: if ((!m_isShown && isDisplayed()) || !m_tabBox->containsPos(event->globalPos())) { close(); // click outside closes tab return true; } // fall through case QEvent::MouseButtonRelease: default: // we do not filter it out, the intenal filter takes care return false; } return false; } bool TabBox::handleWheelEvent(QWheelEvent *event) { if (!m_isShown && isDisplayed()) { // tabbox has been replaced, check effects if (effects && static_cast(effects)->checkInputWindowEvent(event)) { return true; } } if (event->angleDelta().y() == 0) { return false; } const QModelIndex index = m_tabBox->nextPrev(event->angleDelta().y() > 0); if (index.isValid()) { setCurrentIndex(index); } return true; } void TabBox::grabbedKeyEvent(QKeyEvent* event) { emit tabBoxKeyEvent(event); if (!m_isShown && isDisplayed()) { // tabbox has been replaced, check effects return; } if (m_noModifierGrab) { if (event->key() == Qt::Key_Enter || event->key() == Qt::Key_Return || event->key() == Qt::Key_Space) { accept(); return; } } m_tabBox->grabbedKeyEvent(event); } struct KeySymbolsDeleter { static inline void cleanup(xcb_key_symbols_t *symbols) { xcb_key_symbols_free(symbols); } }; /** * Handles alt-tab / control-tab */ static bool areKeySymXsDepressed(const uint keySyms[], int nKeySyms) { Xcb::QueryKeymap keys; QScopedPointer symbols(xcb_key_symbols_alloc(connection())); if (symbols.isNull() || !keys) { return false; } const auto keymap = keys->keys; bool depressed = false; for (int iKeySym = 0; iKeySym < nKeySyms; iKeySym++) { uint keySymX = keySyms[ iKeySym ]; xcb_keycode_t *keyCodes = xcb_key_symbols_get_keycode(symbols.data(), keySymX); if (!keyCodes) { continue; } int j = 0; while (keyCodes[j] != XCB_NO_SYMBOL) { const xcb_keycode_t keyCodeX = keyCodes[j++]; int i = keyCodeX / 8; char mask = 1 << (keyCodeX - (i * 8)); if (i < 0 || i >= 32) { continue; } qCDebug(KWIN_TABBOX) << iKeySym << ": keySymX=0x" << QString::number(keySymX, 16) << " i=" << i << " mask=0x" << QString::number(mask, 16) << " keymap[i]=0x" << QString::number(keymap[i], 16); if (keymap[i] & mask) { depressed = true; break; } } free(keyCodes); } return depressed; } static bool areModKeysDepressedX11(const QKeySequence &seq) { uint rgKeySyms[10]; int nKeySyms = 0; int mod = seq[seq.count()-1] & Qt::KeyboardModifierMask; if (mod & Qt::SHIFT) { rgKeySyms[nKeySyms++] = XK_Shift_L; rgKeySyms[nKeySyms++] = XK_Shift_R; } if (mod & Qt::CTRL) { rgKeySyms[nKeySyms++] = XK_Control_L; rgKeySyms[nKeySyms++] = XK_Control_R; } if (mod & Qt::ALT) { rgKeySyms[nKeySyms++] = XK_Alt_L; rgKeySyms[nKeySyms++] = XK_Alt_R; } if (mod & Qt::META) { // It would take some code to determine whether the Win key // is associated with Super or Meta, so check for both. // See bug #140023 for details. rgKeySyms[nKeySyms++] = XK_Super_L; rgKeySyms[nKeySyms++] = XK_Super_R; rgKeySyms[nKeySyms++] = XK_Meta_L; rgKeySyms[nKeySyms++] = XK_Meta_R; } return areKeySymXsDepressed(rgKeySyms, nKeySyms); } static bool areModKeysDepressedWayland(const QKeySequence &seq) { const int mod = seq[seq.count()-1] & Qt::KeyboardModifierMask; const Qt::KeyboardModifiers mods = input()->modifiersRelevantForGlobalShortcuts(); if ((mod & Qt::SHIFT) && mods.testFlag(Qt::ShiftModifier)) { return true; } if ((mod & Qt::CTRL) && mods.testFlag(Qt::ControlModifier)) { return true; } if ((mod & Qt::ALT) && mods.testFlag(Qt::AltModifier)) { return true; } if ((mod & Qt::META) && mods.testFlag(Qt::MetaModifier)) { return true; } return false; } static bool areModKeysDepressed(const QKeySequence& seq) { if (seq.isEmpty()) return false; if (kwinApp()->shouldUseWaylandForCompositing()) { return areModKeysDepressedWayland(seq); } else { return areModKeysDepressedX11(seq); } } void TabBox::navigatingThroughWindows(bool forward, const QKeySequence &shortcut, TabBoxMode mode) { if (!m_ready || isGrabbed() || !Workspace::self()->isOnCurrentHead()) { return; } if (!options->focusPolicyIsReasonable()) { //ungrabXKeyboard(); // need that because of accelerator raw mode // CDE style raise / lower CDEWalkThroughWindows(forward); } else { if (areModKeysDepressed(shortcut)) { if (startKDEWalkThroughWindows(mode)) KDEWalkThroughWindows(forward); } else // if the shortcut has no modifiers, don't show the tabbox, // don't grab, but simply go to the next window KDEOneStepThroughWindows(forward, mode); } } void TabBox::slotWalkThroughWindows() { navigatingThroughWindows(true, m_cutWalkThroughWindows, TabBoxWindowsMode); } void TabBox::slotWalkBackThroughWindows() { navigatingThroughWindows(false, m_cutWalkThroughWindowsReverse, TabBoxWindowsMode); } void TabBox::slotWalkThroughWindowsAlternative() { navigatingThroughWindows(true, m_cutWalkThroughWindowsAlternative, TabBoxWindowsAlternativeMode); } void TabBox::slotWalkBackThroughWindowsAlternative() { navigatingThroughWindows(false, m_cutWalkThroughWindowsAlternativeReverse, TabBoxWindowsAlternativeMode); } void TabBox::slotWalkThroughCurrentAppWindows() { navigatingThroughWindows(true, m_cutWalkThroughCurrentAppWindows, TabBoxCurrentAppWindowsMode); } void TabBox::slotWalkBackThroughCurrentAppWindows() { navigatingThroughWindows(false, m_cutWalkThroughCurrentAppWindowsReverse, TabBoxCurrentAppWindowsMode); } void TabBox::slotWalkThroughCurrentAppWindowsAlternative() { navigatingThroughWindows(true, m_cutWalkThroughCurrentAppWindowsAlternative, TabBoxCurrentAppWindowsAlternativeMode); } void TabBox::slotWalkBackThroughCurrentAppWindowsAlternative() { navigatingThroughWindows(false, m_cutWalkThroughCurrentAppWindowsAlternativeReverse, TabBoxCurrentAppWindowsAlternativeMode); } void TabBox::slotWalkThroughDesktops() { if (!m_ready || isGrabbed() || !Workspace::self()->isOnCurrentHead()) { return; } if (areModKeysDepressed(m_cutWalkThroughDesktops)) { if (startWalkThroughDesktops()) walkThroughDesktops(true); } else { oneStepThroughDesktops(true); } } void TabBox::slotWalkBackThroughDesktops() { if (!m_ready || isGrabbed() || !Workspace::self()->isOnCurrentHead()) { return; } if (areModKeysDepressed(m_cutWalkThroughDesktopsReverse)) { if (startWalkThroughDesktops()) walkThroughDesktops(false); } else { oneStepThroughDesktops(false); } } void TabBox::slotWalkThroughDesktopList() { if (!m_ready || isGrabbed() || !Workspace::self()->isOnCurrentHead()) { return; } if (areModKeysDepressed(m_cutWalkThroughDesktopList)) { if (startWalkThroughDesktopList()) walkThroughDesktops(true); } else { oneStepThroughDesktopList(true); } } void TabBox::slotWalkBackThroughDesktopList() { if (!m_ready || isGrabbed() || !Workspace::self()->isOnCurrentHead()) { return; } if (areModKeysDepressed(m_cutWalkThroughDesktopListReverse)) { if (startWalkThroughDesktopList()) walkThroughDesktops(false); } else { oneStepThroughDesktopList(false); } } void TabBox::shadeActivate(AbstractClient *c) { if ((c->shadeMode() == ShadeNormal || c->shadeMode() == ShadeHover) && options->isShadeHover()) c->setShade(ShadeActivated); } bool TabBox::toggle(ElectricBorder eb) { if (m_borderAlternativeActivate.contains(eb)) { return toggleMode(TabBoxWindowsAlternativeMode); } else { return toggleMode(TabBoxWindowsMode); } } bool TabBox::toggleMode(TabBoxMode mode) { if (!options->focusPolicyIsReasonable()) return false; // not supported. if (isDisplayed()) { accept(); return true; } if (!establishTabBoxGrab()) return false; m_noModifierGrab = m_tabGrab = true; setMode(mode); reset(); show(); return true; } bool TabBox::startKDEWalkThroughWindows(TabBoxMode mode) { if (!establishTabBoxGrab()) return false; m_tabGrab = true; m_noModifierGrab = false; setMode(mode); reset(); return true; } bool TabBox::startWalkThroughDesktops(TabBoxMode mode) { if (!establishTabBoxGrab()) return false; m_desktopGrab = true; m_noModifierGrab = false; setMode(mode); reset(); return true; } bool TabBox::startWalkThroughDesktops() { return startWalkThroughDesktops(TabBoxDesktopMode); } bool TabBox::startWalkThroughDesktopList() { return startWalkThroughDesktops(TabBoxDesktopListMode); } void TabBox::KDEWalkThroughWindows(bool forward) { nextPrev(forward); delayedShow(); } void TabBox::walkThroughDesktops(bool forward) { nextPrev(forward); delayedShow(); } void TabBox::CDEWalkThroughWindows(bool forward) { AbstractClient* c = nullptr; // this function find the first suitable client for unreasonable focus // policies - the topmost one, with some exceptions (can't be keepabove/below, // otherwise it gets stuck on them) // Q_ASSERT(Workspace::self()->block_stacking_updates == 0); for (int i = Workspace::self()->stackingOrder().size() - 1; i >= 0 ; --i) { auto it = qobject_cast(Workspace::self()->stackingOrder().at(i)); if (it && it->isOnCurrentActivity() && it->isOnCurrentDesktop() && !it->isSpecialWindow() && it->isShown(false) && it->wantsTabFocus() && !it->keepAbove() && !it->keepBelow()) { c = it; break; } } AbstractClient* nc = c; bool options_traverse_all; { KConfigGroup group(kwinApp()->config(), "TabBox"); options_traverse_all = group.readEntry("TraverseAll", false); } AbstractClient* firstClient = nullptr; do { nc = forward ? nextClientStatic(nc) : previousClientStatic(nc); if (!firstClient) { // When we see our first client for the second time, // it's time to stop. firstClient = nc; } else if (nc == firstClient) { // No candidates found. nc = nullptr; break; } } while (nc && nc != c && ((!options_traverse_all && !nc->isOnDesktop(currentDesktop())) || nc->isMinimized() || !nc->wantsTabFocus() || nc->keepAbove() || nc->keepBelow() || !nc->isOnCurrentActivity())); if (nc) { if (c && c != nc) Workspace::self()->lowerClient(c); if (options->focusPolicyIsReasonable()) { Workspace::self()->activateClient(nc); shadeActivate(nc); } else { if (!nc->isOnDesktop(currentDesktop())) setCurrentDesktop(nc->desktop()); Workspace::self()->raiseClient(nc); } } } void TabBox::KDEOneStepThroughWindows(bool forward, TabBoxMode mode) { setMode(mode); reset(); nextPrev(forward); if (AbstractClient* c = currentClient()) { Workspace::self()->activateClient(c); shadeActivate(c); } } void TabBox::oneStepThroughDesktops(bool forward, TabBoxMode mode) { setMode(mode); reset(); nextPrev(forward); if (currentDesktop() != -1) setCurrentDesktop(currentDesktop()); } void TabBox::oneStepThroughDesktops(bool forward) { oneStepThroughDesktops(forward, TabBoxDesktopMode); } void TabBox::oneStepThroughDesktopList(bool forward) { oneStepThroughDesktops(forward, TabBoxDesktopListMode); } void TabBox::keyPress(int keyQt) { enum Direction { Backward = -1, Steady = 0, Forward = 1 }; Direction direction(Steady); auto contains = [](const QKeySequence &shortcut, int key) -> bool { for (int i = 0; i < shortcut.count(); ++i) { if (shortcut[i] == key) { return true; } } return false; }; // tests whether a shortcut matches and handles pitfalls on ShiftKey invocation auto directionFor = [keyQt, contains](const QKeySequence &forward, const QKeySequence &backward) -> Direction { if (contains(forward, keyQt)) return Forward; if (contains(backward, keyQt)) return Backward; if (!(keyQt & Qt::ShiftModifier)) return Steady; // Before testing the unshifted key (Ctrl+A vs. Ctrl+Shift+a etc.), see whether this is +Shift+Tab // and check that against +Shift+Backtab (as well) Qt::KeyboardModifiers mods = Qt::ShiftModifier|Qt::ControlModifier|Qt::AltModifier|Qt::MetaModifier|Qt::KeypadModifier|Qt::GroupSwitchModifier; mods &= keyQt; if ((keyQt & ~mods) == Qt::Key_Tab) { if (contains(forward, mods | Qt::Key_Backtab)) return Forward; if (contains(backward, mods | Qt::Key_Backtab)) return Backward; } // if the shortcuts do not match, try matching again after filtering the shift key from keyQt // it is needed to handle correctly the ALT+~ shorcut for example as it is coded as ALT+SHIFT+~ in keyQt if (contains(forward, keyQt & ~Qt::ShiftModifier)) return Forward; if (contains(backward, keyQt & ~Qt::ShiftModifier)) return Backward; return Steady; }; if (m_tabGrab) { static const int ModeCount = 4; static const TabBoxMode modes[ModeCount] = { TabBoxWindowsMode, TabBoxWindowsAlternativeMode, TabBoxCurrentAppWindowsMode, TabBoxCurrentAppWindowsAlternativeMode }; static const QKeySequence cuts[2*ModeCount] = { // forward m_cutWalkThroughWindows, m_cutWalkThroughWindowsAlternative, m_cutWalkThroughCurrentAppWindows, m_cutWalkThroughCurrentAppWindowsAlternative, // backward m_cutWalkThroughWindowsReverse, m_cutWalkThroughWindowsAlternativeReverse, m_cutWalkThroughCurrentAppWindowsReverse, m_cutWalkThroughCurrentAppWindowsAlternativeReverse }; bool testedCurrent = false; // in case of collision, prefer to stay in the current mode int i = 0, j = 0; while (true) { if (!testedCurrent && modes[i] != mode()) { ++j; i = (i+1) % ModeCount; continue; } if (testedCurrent && modes[i] == mode()) { break; } testedCurrent = true; direction = directionFor(cuts[i], cuts[i+ModeCount]); if (direction != Steady) { if (modes[i] != mode()) { accept(false); setMode(modes[i]); auto replayWithChangedTabboxMode = [this, direction]() { reset(); nextPrev(direction == Forward); }; QTimer::singleShot(50, this, replayWithChangedTabboxMode); } break; } else if (++j > ModeCount) { // guarding counter for invalid modes qCDebug(KWIN_TABBOX) << "Invalid TabBoxMode"; return; } i = (i+1) % ModeCount; } if (direction != Steady) { qCDebug(KWIN_TABBOX) << "== " << cuts[i].toString() << " or " << cuts[i+ModeCount].toString(); KDEWalkThroughWindows(direction == Forward); } } else if (m_desktopGrab) { direction = directionFor(m_cutWalkThroughDesktops, m_cutWalkThroughDesktopsReverse); if (direction == Steady) direction = directionFor(m_cutWalkThroughDesktopList, m_cutWalkThroughDesktopListReverse); if (direction != Steady) walkThroughDesktops(direction == Forward); } if (m_desktopGrab || m_tabGrab) { if (((keyQt & ~Qt::KeyboardModifierMask) == Qt::Key_Escape) && direction == Steady) { // if Escape is part of the shortcut, don't cancel close(true); } else if (direction == Steady) { QKeyEvent* event = new QKeyEvent(QEvent::KeyPress, keyQt & ~Qt::KeyboardModifierMask, Qt::NoModifier); grabbedKeyEvent(event); } } } void TabBox::close(bool abort) { if (isGrabbed()) { removeTabBoxGrab(); } hide(abort); input()->pointer()->setEnableConstraints(true); m_tabGrab = false; m_desktopGrab = false; m_noModifierGrab = false; } void TabBox::accept(bool closeTabBox) { AbstractClient *c = currentClient(); if (closeTabBox) close(); if (c) { Workspace::self()->activateClient(c); shadeActivate(c); if (c->isDesktop()) Workspace::self()->setShowingDesktop(!Workspace::self()->showingDesktop()); } } void TabBox::modifiersReleased() { if (m_noModifierGrab) { return; } if (m_tabGrab) { bool old_control_grab = m_desktopGrab; accept(); m_desktopGrab = old_control_grab; } if (m_desktopGrab) { bool old_tab_grab = m_tabGrab; int desktop = currentDesktop(); close(); m_tabGrab = old_tab_grab; if (desktop != -1) { setCurrentDesktop(desktop); VirtualDesktopManager::self()->setCurrent(desktop); } } } int TabBox::nextDesktopStatic(int iDesktop) const { DesktopNext functor; return functor(iDesktop, true); } int TabBox::previousDesktopStatic(int iDesktop) const { DesktopPrevious functor; return functor(iDesktop, true); } /** * Auxiliary functions to travers all clients according to the static * order. Useful for the CDE-style Alt-tab feature. */ AbstractClient* TabBox::nextClientStatic(AbstractClient* c) const { const auto &list = Workspace::self()->allClientList(); if (!c || list.isEmpty()) return nullptr; int pos = list.indexOf(c); if (pos == -1) return list.first(); ++pos; if (pos == list.count()) return list.first(); return list.at(pos); } /** * Auxiliary functions to travers all clients according to the static * order. Useful for the CDE-style Alt-tab feature. */ AbstractClient* TabBox::previousClientStatic(AbstractClient* c) const { const auto &list = Workspace::self()->allClientList(); if (!c || list.isEmpty()) return nullptr; int pos = list.indexOf(c); if (pos == -1) return list.last(); if (pos == 0) return list.last(); --pos; return list.at(pos); } bool TabBox::establishTabBoxGrab() { if (kwinApp()->shouldUseWaylandForCompositing()) { m_forcedGlobalMouseGrab = true; return true; } updateXTime(); if (!grabXKeyboard()) return false; // Don't try to establish a global mouse grab using XGrabPointer, as that would prevent // using Alt+Tab while DND (#44972). However force passive grabs on all windows // in order to catch MouseRelease events and close the tabbox (#67416). // All clients already have passive grabs in their wrapper windows, so check only // the active client, which may not have it. Q_ASSERT(!m_forcedGlobalMouseGrab); m_forcedGlobalMouseGrab = true; if (Workspace::self()->activeClient() != nullptr) Workspace::self()->activeClient()->updateMouseGrab(); m_x11EventFilter.reset(new X11Filter); return true; } void TabBox::removeTabBoxGrab() { if (kwinApp()->shouldUseWaylandForCompositing()) { m_forcedGlobalMouseGrab = false; return; } updateXTime(); ungrabXKeyboard(); Q_ASSERT(m_forcedGlobalMouseGrab); m_forcedGlobalMouseGrab = false; if (Workspace::self()->activeClient() != nullptr) Workspace::self()->activeClient()->updateMouseGrab(); m_x11EventFilter.reset(); } } // namespace TabBox } // namespace diff --git a/toplevel.cpp b/toplevel.cpp index 88cee2fb7..c346818fc 100644 --- a/toplevel.cpp +++ b/toplevel.cpp @@ -1,808 +1,804 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2006 Lubos Lunak 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 . *********************************************************************/ #include "toplevel.h" #ifdef KWIN_BUILD_ACTIVITIES #include "activities.h" #endif #include "atoms.h" #include "client.h" #include "client_machine.h" #include "composite.h" #include "effects.h" #include "screens.h" #include "shadow.h" #include "workspace.h" #include "xcbutils.h" #include #include namespace KWin { Toplevel::Toplevel() : m_visual(XCB_NONE) , bit_depth(24) , info(nullptr) , ready_for_painting(true) , m_isDamaged(false) , m_internalId(QUuid::createUuid()) , m_client() , damage_handle(XCB_NONE) , is_shape(false) , effect_window(nullptr) , m_clientMachine(new ClientMachine(this)) , m_wmClientLeader(XCB_WINDOW_NONE) , m_damageReplyPending(false) , m_screen(0) , m_skipCloseAnimation(false) { connect(this, SIGNAL(damaged(KWin::Toplevel*,QRect)), SIGNAL(needsRepaint())); connect(screens(), SIGNAL(changed()), SLOT(checkScreen())); connect(screens(), SIGNAL(countChanged(int,int)), SLOT(checkScreen())); setupCheckScreenConnection(); } Toplevel::~Toplevel() { Q_ASSERT(damage_handle == XCB_NONE); delete info; } QDebug& operator<<(QDebug& stream, const Toplevel* cl) { if (cl == nullptr) return stream << "\'NULL\'"; cl->debug(stream); return stream; } QDebug& operator<<(QDebug& stream, const ToplevelList& list) { stream << "LIST:("; bool first = true; for (ToplevelList::ConstIterator it = list.begin(); it != list.end(); ++it) { if (!first) stream << ":"; first = false; stream << *it; } stream << ")"; return stream; } QRect Toplevel::decorationRect() const { return rect(); } void Toplevel::detectShape(xcb_window_t id) { const bool wasShape = is_shape; is_shape = Xcb::Extensions::self()->hasShape(id); if (wasShape != is_shape) { emit shapedChanged(); } } // used only by Deleted::copy() void Toplevel::copyToDeleted(Toplevel* c) { m_internalId = c->internalId(); geom = c->geom; m_visual = c->m_visual; bit_depth = c->bit_depth; info = c->info; m_client.reset(c->m_client, false); ready_for_painting = c->ready_for_painting; damage_handle = XCB_NONE; damage_region = c->damage_region; repaints_region = c->repaints_region; layer_repaints_region = c->layer_repaints_region; is_shape = c->is_shape; effect_window = c->effect_window; if (effect_window != nullptr) effect_window->setWindow(this); resource_name = c->resourceName(); resource_class = c->resourceClass(); m_clientMachine = c->m_clientMachine; m_clientMachine->setParent(this); m_wmClientLeader = c->wmClientLeader(); opaque_region = c->opaqueRegion(); m_screen = c->m_screen; m_skipCloseAnimation = c->m_skipCloseAnimation; m_internalFBO = c->m_internalFBO; } // before being deleted, remove references to everything that's now // owner by Deleted void Toplevel::disownDataPassedToDeleted() { info = nullptr; } QRect Toplevel::visibleRect() const { QRect r = decorationRect(); if (hasShadow() && !shadow()->shadowRegion().isEmpty()) { r |= shadow()->shadowRegion().boundingRect(); } return r.translated(geometry().topLeft()); } Xcb::Property Toplevel::fetchWmClientLeader() const { return Xcb::Property(false, window(), atoms->wm_client_leader, XCB_ATOM_WINDOW, 0, 10000); } void Toplevel::readWmClientLeader(Xcb::Property &prop) { m_wmClientLeader = prop.value(window()); } void Toplevel::getWmClientLeader() { auto prop = fetchWmClientLeader(); readWmClientLeader(prop); } /** * Returns sessionId for this client, * taken either from its window or from the leader window. */ QByteArray Toplevel::sessionId() const { QByteArray result = Xcb::StringProperty(window(), atoms->sm_client_id); if (result.isEmpty() && m_wmClientLeader && m_wmClientLeader != window()) { result = Xcb::StringProperty(m_wmClientLeader, atoms->sm_client_id); } return result; } /** * Returns command property for this client, * taken either from its window or from the leader window. */ QByteArray Toplevel::wmCommand() { QByteArray result = Xcb::StringProperty(window(), XCB_ATOM_WM_COMMAND); if (result.isEmpty() && m_wmClientLeader && m_wmClientLeader != window()) { result = Xcb::StringProperty(m_wmClientLeader, XCB_ATOM_WM_COMMAND); } result.replace(0, ' '); return result; } void Toplevel::getWmClientMachine() { m_clientMachine->resolve(window(), wmClientLeader()); } /** * Returns client machine for this client, * taken either from its window or from the leader window. */ QByteArray Toplevel::wmClientMachine(bool use_localhost) const { if (!m_clientMachine) { // this should never happen return QByteArray(); } if (use_localhost && m_clientMachine->isLocal()) { // special name for the local machine (localhost) return ClientMachine::localhost(); } return m_clientMachine->hostName(); } /** * Returns client leader window for this client. * Returns the client window itself if no leader window is defined. */ xcb_window_t Toplevel::wmClientLeader() const { if (m_wmClientLeader != XCB_WINDOW_NONE) { return m_wmClientLeader; } return window(); } void Toplevel::getResourceClass() { setResourceClass(QByteArray(info->windowClassName()).toLower(), QByteArray(info->windowClassClass()).toLower()); } void Toplevel::setResourceClass(const QByteArray &name, const QByteArray &className) { resource_name = name; resource_class = className; emit windowClassChanged(); } double Toplevel::opacity() const { if (info->opacity() == 0xffffffff) return 1.0; return info->opacity() * 1.0 / 0xffffffff; } void Toplevel::setOpacity(double new_opacity) { double old_opacity = opacity(); new_opacity = qBound(0.0, new_opacity, 1.0); if (old_opacity == new_opacity) return; info->setOpacity(static_cast< unsigned long >(new_opacity * 0xffffffff)); if (compositing()) { addRepaintFull(); emit opacityChanged(this, old_opacity); } } bool Toplevel::setupCompositing() { if (!compositing()) return false; if (damage_handle != XCB_NONE) return false; if (kwinApp()->operationMode() == Application::OperationModeX11 && !surface()) { damage_handle = xcb_generate_id(connection()); xcb_damage_create(connection(), damage_handle, frameId(), XCB_DAMAGE_REPORT_LEVEL_NON_EMPTY); } damage_region = QRegion(0, 0, width(), height()); effect_window = new EffectWindowImpl(this); Compositor::self()->scene()->addToplevel(this); return true; } void Toplevel::finishCompositing(ReleaseReason releaseReason) { if (kwinApp()->operationMode() == Application::OperationModeX11 && damage_handle == XCB_NONE) return; if (effect_window->window() == this) { // otherwise it's already passed to Deleted, don't free data discardWindowPixmap(); delete effect_window; } if (damage_handle != XCB_NONE && releaseReason != ReleaseReason::Destroyed) { xcb_damage_destroy(connection(), damage_handle); } damage_handle = XCB_NONE; damage_region = QRegion(); repaints_region = QRegion(); effect_window = nullptr; } void Toplevel::discardWindowPixmap() { addDamageFull(); if (effectWindow() != nullptr && effectWindow()->sceneWindow() != nullptr) effectWindow()->sceneWindow()->pixmapDiscarded(); } void Toplevel::damageNotifyEvent() { m_isDamaged = true; // Note: The rect is supposed to specify the damage extents, // but we don't know it at this point. No one who connects // to this signal uses the rect however. emit damaged(this, QRect()); } bool Toplevel::compositing() const { if (!Workspace::self()) { return false; } return Workspace::self()->compositing(); } void Client::damageNotifyEvent() { if (syncRequest.isPending && isResize()) { emit damaged(this, QRect()); m_isDamaged = true; return; } if (!ready_for_painting) { // avoid "setReadyForPainting()" function calling overhead if (syncRequest.counter == XCB_NONE) { // cannot detect complete redraw, consider done now setReadyForPainting(); setupWindowManagementInterface(); } } Toplevel::damageNotifyEvent(); } bool Toplevel::resetAndFetchDamage() { if (!m_isDamaged) return false; if (damage_handle == XCB_NONE) { m_isDamaged = false; return true; } xcb_connection_t *conn = connection(); // Create a new region and copy the damage region to it, // resetting the damaged state. xcb_xfixes_region_t region = xcb_generate_id(conn); xcb_xfixes_create_region(conn, region, 0, nullptr); xcb_damage_subtract(conn, damage_handle, 0, region); // Send a fetch-region request and destroy the region m_regionCookie = xcb_xfixes_fetch_region_unchecked(conn, region); xcb_xfixes_destroy_region(conn, region); m_isDamaged = false; m_damageReplyPending = true; return m_damageReplyPending; } void Toplevel::getDamageRegionReply() { if (!m_damageReplyPending) return; m_damageReplyPending = false; // Get the fetch-region reply xcb_xfixes_fetch_region_reply_t *reply = xcb_xfixes_fetch_region_reply(connection(), m_regionCookie, nullptr); if (!reply) return; // Convert the reply to a QRegion int count = xcb_xfixes_fetch_region_rectangles_length(reply); QRegion region; if (count > 1 && count < 16) { xcb_rectangle_t *rects = xcb_xfixes_fetch_region_rectangles(reply); QVector qrects; qrects.reserve(count); for (int i = 0; i < count; i++) qrects << QRect(rects[i].x, rects[i].y, rects[i].width, rects[i].height); region.setRects(qrects.constData(), count); } else region += QRect(reply->extents.x, reply->extents.y, reply->extents.width, reply->extents.height); damage_region += region; repaints_region += region; free(reply); } void Toplevel::addDamageFull() { if (!compositing()) return; damage_region = rect(); repaints_region |= rect(); emit damaged(this, rect()); } void Toplevel::resetDamage() { damage_region = QRegion(); } void Toplevel::addRepaint(const QRect& r) { if (!compositing()) { return; } repaints_region += r; emit needsRepaint(); } void Toplevel::addRepaint(int x, int y, int w, int h) { QRect r(x, y, w, h); addRepaint(r); } void Toplevel::addRepaint(const QRegion& r) { if (!compositing()) { return; } repaints_region += r; emit needsRepaint(); } void Toplevel::addLayerRepaint(const QRect& r) { if (!compositing()) { return; } layer_repaints_region += r; emit needsRepaint(); } void Toplevel::addLayerRepaint(int x, int y, int w, int h) { QRect r(x, y, w, h); addLayerRepaint(r); } void Toplevel::addLayerRepaint(const QRegion& r) { if (!compositing()) return; layer_repaints_region += r; emit needsRepaint(); } void Toplevel::addRepaintFull() { repaints_region = visibleRect().translated(-pos()); emit needsRepaint(); } void Toplevel::resetRepaints() { repaints_region = QRegion(); layer_repaints_region = QRegion(); } void Toplevel::addWorkspaceRepaint(int x, int y, int w, int h) { addWorkspaceRepaint(QRect(x, y, w, h)); } void Toplevel::addWorkspaceRepaint(const QRect& r2) { if (!compositing()) return; Compositor::self()->addRepaint(r2); } void Toplevel::setReadyForPainting() { if (!ready_for_painting) { ready_for_painting = true; if (compositing()) { addRepaintFull(); emit windowShown(this); } } } void Toplevel::deleteEffectWindow() { delete effect_window; effect_window = nullptr; } void Toplevel::checkScreen() { if (screens()->count() == 1) { if (m_screen != 0) { m_screen = 0; emit screenChanged(); } } else { const int s = screens()->number(geometry().center()); if (s != m_screen) { m_screen = s; emit screenChanged(); } } qreal newScale = screens()->scale(m_screen); if (newScale != m_screenScale) { m_screenScale = newScale; emit screenScaleChanged(); } } void Toplevel::setupCheckScreenConnection() { connect(this, SIGNAL(geometryShapeChanged(KWin::Toplevel*,QRect)), SLOT(checkScreen())); connect(this, SIGNAL(geometryChanged()), SLOT(checkScreen())); checkScreen(); } void Toplevel::removeCheckScreenConnection() { disconnect(this, SIGNAL(geometryShapeChanged(KWin::Toplevel*,QRect)), this, SLOT(checkScreen())); disconnect(this, SIGNAL(geometryChanged()), this, SLOT(checkScreen())); } int Toplevel::screen() const { return m_screen; } qreal Toplevel::screenScale() const { return m_screenScale; } +qreal Toplevel::bufferScale() const +{ + return surface() ? surface()->scale() : 1; +} + bool Toplevel::isOnScreen(int screen) const { return screens()->geometry(screen).intersects(geometry()); } bool Toplevel::isOnActiveScreen() const { return isOnScreen(screens()->current()); } void Toplevel::getShadow() { QRect dirtyRect; // old & new shadow region const QRect oldVisibleRect = visibleRect(); if (hasShadow()) { dirtyRect = shadow()->shadowRegion().boundingRect(); if (!effectWindow()->sceneWindow()->shadow()->updateShadow()) { effectWindow()->sceneWindow()->updateShadow(nullptr); } emit shadowChanged(); } else { Shadow::createShadow(this); } if (hasShadow()) dirtyRect |= shadow()->shadowRegion().boundingRect(); if (oldVisibleRect != visibleRect()) emit paddingChanged(this, oldVisibleRect); if (dirtyRect.isValid()) { dirtyRect.translate(pos()); addLayerRepaint(dirtyRect); } } bool Toplevel::hasShadow() const { if (effectWindow() && effectWindow()->sceneWindow()) { return effectWindow()->sceneWindow()->shadow() != nullptr; } return false; } Shadow *Toplevel::shadow() { if (effectWindow() && effectWindow()->sceneWindow()) { return effectWindow()->sceneWindow()->shadow(); } else { return nullptr; } } const Shadow *Toplevel::shadow() const { if (effectWindow() && effectWindow()->sceneWindow()) { return effectWindow()->sceneWindow()->shadow(); } else { return nullptr; } } bool Toplevel::wantsShadowToBeRendered() const { return true; } void Toplevel::getWmOpaqueRegion() { const auto rects = info->opaqueRegion(); QRegion new_opaque_region; for (const auto &r : rects) { new_opaque_region += QRect(r.pos.x, r.pos.y, r.size.width, r.size.height); } opaque_region = new_opaque_region; } bool Toplevel::isClient() const { return false; } bool Toplevel::isDeleted() const { return false; } bool Toplevel::isOnCurrentActivity() const { #ifdef KWIN_BUILD_ACTIVITIES if (!Activities::self()) { return true; } return isOnActivity(Activities::self()->current()); #else return true; #endif } void Toplevel::elevate(bool elevate) { if (!effectWindow()) { return; } effectWindow()->elevate(elevate); addWorkspaceRepaint(visibleRect()); } pid_t Toplevel::pid() const { return info->pid(); } xcb_window_t Toplevel::frameId() const { return m_client; } Xcb::Property Toplevel::fetchSkipCloseAnimation() const { return Xcb::Property(false, window(), atoms->kde_skip_close_animation, XCB_ATOM_CARDINAL, 0, 1); } void Toplevel::readSkipCloseAnimation(Xcb::Property &property) { setSkipCloseAnimation(property.toBool()); } void Toplevel::getSkipCloseAnimation() { Xcb::Property property = fetchSkipCloseAnimation(); readSkipCloseAnimation(property); } bool Toplevel::skipsCloseAnimation() const { return m_skipCloseAnimation; } void Toplevel::setSkipCloseAnimation(bool set) { if (set == m_skipCloseAnimation) { return; } m_skipCloseAnimation = set; emit skipCloseAnimationChanged(); } void Toplevel::setSurface(KWayland::Server::SurfaceInterface *surface) { if (m_surface == surface) { return; } using namespace KWayland::Server; if (m_surface) { disconnect(m_surface, &SurfaceInterface::damaged, this, &Toplevel::addDamage); disconnect(m_surface, &SurfaceInterface::sizeChanged, this, &Toplevel::discardWindowPixmap); } m_surface = surface; connect(m_surface, &SurfaceInterface::damaged, this, &Toplevel::addDamage); connect(m_surface, &SurfaceInterface::sizeChanged, this, &Toplevel::discardWindowPixmap); connect(m_surface, &SurfaceInterface::subSurfaceTreeChanged, this, [this] { // TODO improve to only update actual visual area if (ready_for_painting) { addDamageFull(); m_isDamaged = true; } } ); connect(m_surface, &SurfaceInterface::destroyed, this, [this] { m_surface = nullptr; } ); emit surfaceChanged(); } void Toplevel::addDamage(const QRegion &damage) { m_isDamaged = true; damage_region += damage; for (const QRect &r : damage) { emit damaged(this, r); } } QByteArray Toplevel::windowRole() const { return QByteArray(info->windowRole()); } void Toplevel::setDepth(int depth) { if (bit_depth == depth) { return; } const bool oldAlpha = hasAlpha(); bit_depth = depth; if (oldAlpha != hasAlpha()) { emit hasAlphaChanged(); } } QRegion Toplevel::inputShape() const { if (m_surface) { return m_surface->input(); } else { // TODO: maybe also for X11? return QRegion(); } } -void Toplevel::setInternalFramebufferObject(const QSharedPointer &fbo) -{ - if (m_internalFBO != fbo) { - discardWindowPixmap(); - m_internalFBO = fbo; - } - setDepth(32); -} - QMatrix4x4 Toplevel::inputTransformation() const { QMatrix4x4 m; m.translate(-x(), -y()); return m; } quint32 Toplevel::windowId() const { return window(); } QRect Toplevel::inputGeometry() const { return geometry(); } bool Toplevel::isLocalhost() const { if (!m_clientMachine) { return true; } return m_clientMachine->isLocal(); } } // namespace diff --git a/toplevel.h b/toplevel.h index ba0559c33..14bc5bf71 100644 --- a/toplevel.h +++ b/toplevel.h @@ -1,959 +1,972 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2006 Lubos Lunak 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 . *********************************************************************/ #ifndef KWIN_TOPLEVEL_H #define KWIN_TOPLEVEL_H // kwin #include "input.h" #include "utils.h" #include "virtualdesktops.h" #include "xcbutils.h" // KDE #include // Qt #include #include #include // xcb #include #include // c++ #include class QOpenGLFramebufferObject; namespace KWayland { namespace Server { class SurfaceInterface; } } namespace KWin { class ClientMachine; class EffectWindowImpl; class Shadow; /** * Enum to describe the reason why a Toplevel has to be released. */ enum class ReleaseReason { Release, ///< Normal Release after e.g. an Unmap notify event (window still valid) Destroyed, ///< Release after an Destroy notify event (window no longer valid) KWinShutsDown ///< Release on KWin Shutdown (window still valid) }; class KWIN_EXPORT Toplevel : public QObject { Q_OBJECT Q_PROPERTY(bool alpha READ hasAlpha NOTIFY hasAlphaChanged) Q_PROPERTY(qulonglong frameId READ frameId) Q_PROPERTY(QRect geometry READ geometry NOTIFY geometryChanged) Q_PROPERTY(QRect visibleRect READ visibleRect) Q_PROPERTY(int height READ height) Q_PROPERTY(qreal opacity READ opacity WRITE setOpacity NOTIFY opacityChanged) Q_PROPERTY(QPoint pos READ pos) Q_PROPERTY(int screen READ screen NOTIFY screenChanged) Q_PROPERTY(QSize size READ size) Q_PROPERTY(int width READ width) Q_PROPERTY(qulonglong windowId READ windowId CONSTANT) Q_PROPERTY(int x READ x) Q_PROPERTY(int y READ y) Q_PROPERTY(int desktop READ desktop) /** * Whether the window is on all desktops. That is desktop is -1. */ Q_PROPERTY(bool onAllDesktops READ isOnAllDesktops) Q_PROPERTY(QRect rect READ rect) Q_PROPERTY(QPoint clientPos READ clientPos) Q_PROPERTY(QSize clientSize READ clientSize) Q_PROPERTY(QByteArray resourceName READ resourceName NOTIFY windowClassChanged) Q_PROPERTY(QByteArray resourceClass READ resourceClass NOTIFY windowClassChanged) Q_PROPERTY(QByteArray windowRole READ windowRole NOTIFY windowRoleChanged) /** * Returns whether the window is a desktop background window (the one with wallpaper). * See _NET_WM_WINDOW_TYPE_DESKTOP at https://standards.freedesktop.org/wm-spec/wm-spec-latest.html . */ Q_PROPERTY(bool desktopWindow READ isDesktop) /** * Returns whether the window is a dock (i.e. a panel). * See _NET_WM_WINDOW_TYPE_DOCK at https://standards.freedesktop.org/wm-spec/wm-spec-latest.html . */ Q_PROPERTY(bool dock READ isDock) /** * Returns whether the window is a standalone (detached) toolbar window. * See _NET_WM_WINDOW_TYPE_TOOLBAR at https://standards.freedesktop.org/wm-spec/wm-spec-latest.html . */ Q_PROPERTY(bool toolbar READ isToolbar) /** * Returns whether the window is a torn-off menu. * See _NET_WM_WINDOW_TYPE_MENU at https://standards.freedesktop.org/wm-spec/wm-spec-latest.html . */ Q_PROPERTY(bool menu READ isMenu) /** * Returns whether the window is a "normal" window, i.e. an application or any other window * for which none of the specialized window types fit. * See _NET_WM_WINDOW_TYPE_NORMAL at https://standards.freedesktop.org/wm-spec/wm-spec-latest.html . */ Q_PROPERTY(bool normalWindow READ isNormalWindow) /** * Returns whether the window is a dialog window. * See _NET_WM_WINDOW_TYPE_DIALOG at https://standards.freedesktop.org/wm-spec/wm-spec-latest.html . */ Q_PROPERTY(bool dialog READ isDialog) /** * Returns whether the window is a splashscreen. Note that many (especially older) applications * do not support marking their splash windows with this type. * See _NET_WM_WINDOW_TYPE_SPLASH at https://standards.freedesktop.org/wm-spec/wm-spec-latest.html . */ Q_PROPERTY(bool splash READ isSplash) /** * Returns whether the window is a utility window, such as a tool window. * See _NET_WM_WINDOW_TYPE_UTILITY at https://standards.freedesktop.org/wm-spec/wm-spec-latest.html . */ Q_PROPERTY(bool utility READ isUtility) /** * Returns whether the window is a dropdown menu (i.e. a popup directly or indirectly open * from the applications menubar). * See _NET_WM_WINDOW_TYPE_DROPDOWN_MENU at https://standards.freedesktop.org/wm-spec/wm-spec-latest.html . */ Q_PROPERTY(bool dropdownMenu READ isDropdownMenu) /** * Returns whether the window is a popup menu (that is not a torn-off or dropdown menu). * See _NET_WM_WINDOW_TYPE_POPUP_MENU at https://standards.freedesktop.org/wm-spec/wm-spec-latest.html . */ Q_PROPERTY(bool popupMenu READ isPopupMenu) /** * Returns whether the window is a tooltip. * See _NET_WM_WINDOW_TYPE_TOOLTIP at https://standards.freedesktop.org/wm-spec/wm-spec-latest.html . */ Q_PROPERTY(bool tooltip READ isTooltip) /** * Returns whether the window is a window with a notification. * See _NET_WM_WINDOW_TYPE_NOTIFICATION at https://standards.freedesktop.org/wm-spec/wm-spec-latest.html . */ Q_PROPERTY(bool notification READ isNotification) /** * Returns whether the window is a window with a critical notification. */ Q_PROPERTY(bool criticalNotification READ isCriticalNotification) /** * Returns whether the window is an On Screen Display. */ Q_PROPERTY(bool onScreenDisplay READ isOnScreenDisplay) /** * Returns whether the window is a combobox popup. * See _NET_WM_WINDOW_TYPE_COMBO at https://standards.freedesktop.org/wm-spec/wm-spec-latest.html . */ Q_PROPERTY(bool comboBox READ isComboBox) /** * Returns whether the window is a Drag&Drop icon. * See _NET_WM_WINDOW_TYPE_DND at https://standards.freedesktop.org/wm-spec/wm-spec-latest.html . */ Q_PROPERTY(bool dndIcon READ isDNDIcon) /** * Returns the NETWM window type * See https://standards.freedesktop.org/wm-spec/wm-spec-latest.html . */ Q_PROPERTY(int windowType READ windowType) Q_PROPERTY(QStringList activities READ activities NOTIFY activitiesChanged) /** * Whether this Toplevel is managed by KWin (it has control over its placement and other * aspects, as opposed to override-redirect windows that are entirely handled by the application). */ Q_PROPERTY(bool managed READ isClient CONSTANT) /** * Whether this Toplevel represents an already deleted window and only kept for the compositor for animations. */ Q_PROPERTY(bool deleted READ isDeleted CONSTANT) /** * Whether the window has an own shape */ Q_PROPERTY(bool shaped READ shape NOTIFY shapedChanged) /** * Whether the window does not want to be animated on window close. * There are legit reasons for this like a screenshot application which does not want it's * window being captured. */ Q_PROPERTY(bool skipsCloseAnimation READ skipsCloseAnimation WRITE setSkipCloseAnimation NOTIFY skipCloseAnimationChanged) /** * The Id of the Wayland Surface associated with this Toplevel. * On X11 only setups the value is @c 0. */ Q_PROPERTY(quint32 surfaceId READ surfaceId NOTIFY surfaceIdChanged) /** * Interface to the Wayland Surface. * Relevant only in Wayland, in X11 it will be nullptr */ Q_PROPERTY(KWayland::Server::SurfaceInterface *surface READ surface) /** * Whether the window is a popup. */ Q_PROPERTY(bool popupWindow READ isPopupWindow) /** * Whether this Toplevel represents the outline. * * @note It's always @c false if compositing is turned off. */ Q_PROPERTY(bool outline READ isOutline) /** * This property holds a UUID to uniquely identify this Toplevel. */ Q_PROPERTY(QUuid internalId READ internalId CONSTANT) public: explicit Toplevel(); virtual xcb_window_t frameId() const; xcb_window_t window() const; /** * @return a unique identifier for the Toplevel. On X11 same as @ref window */ virtual quint32 windowId() const; QRect geometry() const; /** * The geometry of the Toplevel which accepts input events. This might be larger * than the actual geometry, e.g. to support resizing outside the window. * * Default implementation returns same as geometry. */ virtual QRect inputGeometry() const; QSize size() const; QPoint pos() const; QRect rect() const; int x() const; int y() const; int width() const; int height() const; bool isOnScreen(int screen) const; // true if it's at least partially there bool isOnActiveScreen() const; int screen() const; // the screen where the center is /** * The scale of the screen this window is currently on * @note The buffer scale can be different. * @since 5.12 */ qreal screenScale() const; // + /** + * Returns the ratio between physical pixels and device-independent pixels for + * the attached buffer (or pixmap). + * + * For X11 clients, this method always returns 1. + */ + virtual qreal bufferScale() const; virtual QPoint clientPos() const = 0; // inside of geometry() /** * Describes how the client's content maps to the window geometry including the frame. * The default implementation is a 1:1 mapping meaning the frame is part of the content. */ virtual QPoint clientContentPos() const; virtual QSize clientSize() const = 0; virtual QRect visibleRect() const; // the area the window occupies on the screen virtual QRect decorationRect() const; // rect including the decoration shadows virtual QRect transparentRect() const = 0; virtual bool isClient() const; virtual bool isDeleted() const; // prefer isXXX() instead // 0 for supported types means default for managed/unmanaged types virtual NET::WindowType windowType(bool direct = false, int supported_types = 0) const = 0; bool hasNETSupport() const; bool isDesktop() const; bool isDock() const; bool isToolbar() const; bool isMenu() const; bool isNormalWindow() const; // normal as in 'NET::Normal or NET::Unknown non-transient' bool isDialog() const; bool isSplash() const; bool isUtility() const; bool isDropdownMenu() const; bool isPopupMenu() const; // a context popup, not dropdown, not torn-off bool isTooltip() const; bool isNotification() const; bool isCriticalNotification() const; bool isOnScreenDisplay() const; bool isComboBox() const; bool isDNDIcon() const; virtual bool isLockScreen() const; virtual bool isInputMethod() const; virtual bool isOutline() const; /** * Returns the virtual desktop within the workspace() the client window * is located in, 0 if it isn't located on any special desktop (not mapped yet), * or NET::OnAllDesktops. Do not use desktop() directly, use * isOnDesktop() instead. */ virtual int desktop() const = 0; virtual QVector desktops() const = 0; virtual QStringList activities() const = 0; bool isOnDesktop(int d) const; bool isOnActivity(const QString &activity) const; bool isOnCurrentDesktop() const; bool isOnCurrentActivity() const; bool isOnAllDesktops() const; bool isOnAllActivities() const; virtual QByteArray windowRole() const; QByteArray sessionId() const; QByteArray resourceName() const; QByteArray resourceClass() const; QByteArray wmCommand(); QByteArray wmClientMachine(bool use_localhost) const; const ClientMachine *clientMachine() const; virtual bool isLocalhost() const; xcb_window_t wmClientLeader() const; virtual pid_t pid() const; static bool resourceMatch(const Toplevel* c1, const Toplevel* c2); bool readyForPainting() const; // true if the window has been already painted its contents xcb_visualid_t visual() const; bool shape() const; QRegion inputShape() const; virtual void setOpacity(double opacity); virtual double opacity() const; int depth() const; bool hasAlpha() const; virtual bool setupCompositing(); virtual void finishCompositing(ReleaseReason releaseReason = ReleaseReason::Release); Q_INVOKABLE void addRepaint(const QRect& r); Q_INVOKABLE void addRepaint(const QRegion& r); Q_INVOKABLE void addRepaint(int x, int y, int w, int h); Q_INVOKABLE void addLayerRepaint(const QRect& r); Q_INVOKABLE void addLayerRepaint(const QRegion& r); Q_INVOKABLE void addLayerRepaint(int x, int y, int w, int h); Q_INVOKABLE virtual void addRepaintFull(); // these call workspace->addRepaint(), but first transform the damage if needed void addWorkspaceRepaint(const QRect& r); void addWorkspaceRepaint(int x, int y, int w, int h); QRegion repaints() const; void resetRepaints(); QRegion damage() const; void resetDamage(); EffectWindowImpl* effectWindow(); const EffectWindowImpl* effectWindow() const; /** * Window will be temporarily painted as if being at the top of the stack. * Only available if Compositor is active, if not active, this method is a no-op. */ void elevate(bool elevate); /** * @returns Whether the Toplevel has a Shadow or not * @see shadow */ bool hasShadow() const; /** * Returns the pointer to the Toplevel's Shadow. A Shadow * is only available if Compositing is enabled and the corresponding X window * has the Shadow property set. * If a shadow is available hasShadow returns @c true. * @returns The Shadow belonging to this Toplevel, may be @c NULL. * @see hasShadow */ const Shadow *shadow() const; Shadow *shadow(); /** * Updates the Shadow associated with this Toplevel from X11 Property. * Call this method when the Property changes or Compositing is started. */ void getShadow(); /** * Whether the Toplevel currently wants the shadow to be rendered. Default * implementation always returns @c true. */ virtual bool wantsShadowToBeRendered() const; /** * This method returns the area that the Toplevel window reports to be opaque. * It is supposed to only provide valuable information if hasAlpha is @c true . * @see hasAlpha */ const QRegion& opaqueRegion() const; virtual Layer layer() const = 0; /** * Resets the damage state and sends a request for the damage region. * A call to this function must be followed by a call to getDamageRegionReply(), * or the reply will be leaked. * * Returns true if the window was damaged, and false otherwise. */ bool resetAndFetchDamage(); /** * Gets the reply from a previous call to resetAndFetchDamage(). * Calling this function is a no-op if there is no pending reply. * Call damage() to return the fetched region. */ void getDamageRegionReply(); bool skipsCloseAnimation() const; void setSkipCloseAnimation(bool set); quint32 surfaceId() const; KWayland::Server::SurfaceInterface *surface() const; void setSurface(KWayland::Server::SurfaceInterface *surface); - virtual void setInternalFramebufferObject(const QSharedPointer &fbo); const QSharedPointer &internalFramebufferObject() const; + QImage internalImageObject() const; /** * @returns Transformation to map from global to window coordinates. * * Default implementation returns a translation on negative pos(). * @see pos */ virtual QMatrix4x4 inputTransformation() const; /** * The window has a popup grab. This means that when it got mapped the * parent window had an implicit (pointer) grab. * * Normally this is only relevant for transient windows. * * Once the popup grab ends (e.g. pointer press outside of any Toplevel of * the client), the method popupDone should be invoked. * * The default implementation returns @c false. * @see popupDone * @since 5.10 */ virtual bool hasPopupGrab() const { return false; } /** * This method should be invoked for Toplevels with a popup grab when * the grab ends. * * The default implementation does nothing. * @see hasPopupGrab * @since 5.10 */ virtual void popupDone() {}; /** * @brief Finds the Toplevel matching the condition expressed in @p func in @p list. * * The method is templated to operate on either a list of Toplevels or on a list of * a subclass type of Toplevel. * @param list The list to search in * @param func The condition function (compare std::find_if) * @return T* The found Toplevel or @c null if there is no matching Toplevel */ template static T *findInList(const QList &list, std::function func); /** * Whether the window is a popup. * * Popups can be used to implement popup menus, tooltips, combo boxes, etc. * * @since 5.15 */ virtual bool isPopupWindow() const; /** * A UUID to uniquely identify this Toplevel independent of windowing system. */ QUuid internalId() const { return m_internalId; } Q_SIGNALS: void opacityChanged(KWin::Toplevel* toplevel, qreal oldOpacity); void damaged(KWin::Toplevel* toplevel, const QRect& damage); void geometryChanged(); void geometryShapeChanged(KWin::Toplevel* toplevel, const QRect& old); void paddingChanged(KWin::Toplevel* toplevel, const QRect& old); void windowClosed(KWin::Toplevel* toplevel, KWin::Deleted* deleted); void windowShown(KWin::Toplevel* toplevel); void windowHidden(KWin::Toplevel* toplevel); /** * Signal emitted when the window's shape state changed. That is if it did not have a shape * and received one or if the shape was withdrawn. Think of Chromium enabling/disabling KWin's * decoration. */ void shapedChanged(); /** * Emitted whenever the state changes in a way, that the Compositor should * schedule a repaint of the scene. */ void needsRepaint(); void activitiesChanged(KWin::Toplevel* toplevel); /** * Emitted whenever the Toplevel's screen changes. This can happen either in consequence to * a screen being removed/added or if the Toplevel's geometry changes. * @since 4.11 */ void screenChanged(); void skipCloseAnimationChanged(); /** * Emitted whenever the window role of the window changes. * @since 5.0 */ void windowRoleChanged(); /** * Emitted whenever the window class name or resource name of the window changes. * @since 5.0 */ void windowClassChanged(); /** * Emitted when a Wayland Surface gets associated with this Toplevel. * @since 5.3 */ void surfaceIdChanged(quint32); /** * @since 5.4 */ void hasAlphaChanged(); /** * Emitted whenever the Surface for this Toplevel changes. */ void surfaceChanged(); /* * Emitted when the client's screen changes onto a screen of a different scale * or the screen we're on changes * @since 5.12 */ void screenScaleChanged(); /** * Emitted whenever the client's shadow changes. * @since 5.15 */ void shadowChanged(); protected Q_SLOTS: /** * Checks whether the screen number for this Toplevel changed and updates if needed. * Any method changing the geometry of the Toplevel should call this method. */ void checkScreen(); void setupCheckScreenConnection(); void removeCheckScreenConnection(); void setReadyForPainting(); protected: ~Toplevel() override; void setWindowHandles(xcb_window_t client); void detectShape(xcb_window_t id); virtual void propertyNotifyEvent(xcb_property_notify_event_t *e); virtual void damageNotifyEvent(); virtual void clientMessageEvent(xcb_client_message_event_t *e); void discardWindowPixmap(); void addDamageFull(); virtual void addDamage(const QRegion &damage); Xcb::Property fetchWmClientLeader() const; void readWmClientLeader(Xcb::Property &p); void getWmClientLeader(); void getWmClientMachine(); /** * @returns Whether there is a compositor and it is active. */ bool compositing() const; /** * This function fetches the opaque region from this Toplevel. * Will only be called on corresponding property changes and for initialization. */ void getWmOpaqueRegion(); void getResourceClass(); void setResourceClass(const QByteArray &name, const QByteArray &className = QByteArray()); Xcb::Property fetchSkipCloseAnimation() const; void readSkipCloseAnimation(Xcb::Property &prop); void getSkipCloseAnimation(); virtual void debug(QDebug& stream) const = 0; void copyToDeleted(Toplevel* c); void disownDataPassedToDeleted(); friend QDebug& operator<<(QDebug& stream, const Toplevel*); void deleteEffectWindow(); void setDepth(int depth); QRect geom; xcb_visualid_t m_visual; int bit_depth; NETWinInfo* info; bool ready_for_painting; QRegion repaints_region; // updating, repaint just requires repaint of that area QRegion layer_repaints_region; + /** + * An FBO object KWin internal windows might render to. + */ + QSharedPointer m_internalFBO; + QImage m_internalImage; protected: bool m_isDamaged; private: // when adding new data members, check also copyToDeleted() QUuid m_internalId; Xcb::Window m_client; xcb_damage_damage_t damage_handle; QRegion damage_region; // damage is really damaged window (XDamage) and texture needs bool is_shape; EffectWindowImpl* effect_window; QByteArray resource_name; QByteArray resource_class; ClientMachine *m_clientMachine; xcb_window_t m_wmClientLeader; bool m_damageReplyPending; QRegion opaque_region; xcb_xfixes_fetch_region_cookie_t m_regionCookie; int m_screen; bool m_skipCloseAnimation; quint32 m_surfaceId = 0; KWayland::Server::SurfaceInterface *m_surface = nullptr; - /** - * An FBO object KWin internal windows might render to. - */ - QSharedPointer m_internalFBO; // when adding new data members, check also copyToDeleted() qreal m_screenScale = 1.0; }; inline xcb_window_t Toplevel::window() const { return m_client; } inline void Toplevel::setWindowHandles(xcb_window_t w) { Q_ASSERT(!m_client.isValid() && w != XCB_WINDOW_NONE); m_client.reset(w, false); } inline QRect Toplevel::geometry() const { return geom; } inline QSize Toplevel::size() const { return geom.size(); } inline QPoint Toplevel::pos() const { return geom.topLeft(); } inline int Toplevel::x() const { return geom.x(); } inline int Toplevel::y() const { return geom.y(); } inline int Toplevel::width() const { return geom.width(); } inline int Toplevel::height() const { return geom.height(); } inline QRect Toplevel::rect() const { return QRect(0, 0, width(), height()); } inline bool Toplevel::readyForPainting() const { return ready_for_painting; } inline xcb_visualid_t Toplevel::visual() const { return m_visual; } inline bool Toplevel::isDesktop() const { return windowType() == NET::Desktop; } inline bool Toplevel::isDock() const { return windowType() == NET::Dock; } inline bool Toplevel::isMenu() const { return windowType() == NET::Menu; } inline bool Toplevel::isToolbar() const { return windowType() == NET::Toolbar; } inline bool Toplevel::isSplash() const { return windowType() == NET::Splash; } inline bool Toplevel::isUtility() const { return windowType() == NET::Utility; } inline bool Toplevel::isDialog() const { return windowType() == NET::Dialog; } inline bool Toplevel::isNormalWindow() const { return windowType() == NET::Normal; } inline bool Toplevel::isDropdownMenu() const { return windowType() == NET::DropdownMenu; } inline bool Toplevel::isPopupMenu() const { return windowType() == NET::PopupMenu; } inline bool Toplevel::isTooltip() const { return windowType() == NET::Tooltip; } inline bool Toplevel::isNotification() const { return windowType() == NET::Notification; } inline bool Toplevel::isCriticalNotification() const { return windowType() == NET::CriticalNotification; } inline bool Toplevel::isOnScreenDisplay() const { return windowType() == NET::OnScreenDisplay; } inline bool Toplevel::isComboBox() const { return windowType() == NET::ComboBox; } inline bool Toplevel::isDNDIcon() const { return windowType() == NET::DNDIcon; } inline bool Toplevel::isLockScreen() const { return false; } inline bool Toplevel::isInputMethod() const { return false; } inline bool Toplevel::isOutline() const { return false; } inline QRegion Toplevel::damage() const { return damage_region; } inline QRegion Toplevel::repaints() const { return repaints_region.translated(pos()) | layer_repaints_region; } inline bool Toplevel::shape() const { return is_shape; } inline int Toplevel::depth() const { return bit_depth; } inline bool Toplevel::hasAlpha() const { return depth() == 32; } inline const QRegion& Toplevel::opaqueRegion() const { return opaque_region; } inline EffectWindowImpl* Toplevel::effectWindow() { return effect_window; } inline const EffectWindowImpl* Toplevel::effectWindow() const { return effect_window; } inline bool Toplevel::isOnAllDesktops() const { return kwinApp()->operationMode() == Application::OperationModeWaylandOnly || kwinApp()->operationMode() == Application::OperationModeXwayland //Wayland ? desktops().isEmpty() //X11 : desktop() == NET::OnAllDesktops; } inline bool Toplevel::isOnAllActivities() const { return activities().isEmpty(); } inline bool Toplevel::isOnDesktop(int d) const { return (kwinApp()->operationMode() == Application::OperationModeWaylandOnly || kwinApp()->operationMode() == Application::OperationModeXwayland ? desktops().contains(VirtualDesktopManager::self()->desktopForX11Id(d)) : desktop() == d ) || isOnAllDesktops(); } inline bool Toplevel::isOnActivity(const QString &activity) const { return activities().isEmpty() || activities().contains(activity); } inline bool Toplevel::isOnCurrentDesktop() const { return isOnDesktop(VirtualDesktopManager::self()->current()); } inline QByteArray Toplevel::resourceName() const { return resource_name; // it is always lowercase } inline QByteArray Toplevel::resourceClass() const { return resource_class; // it is always lowercase } inline const ClientMachine *Toplevel::clientMachine() const { return m_clientMachine; } inline quint32 Toplevel::surfaceId() const { return m_surfaceId; } inline KWayland::Server::SurfaceInterface *Toplevel::surface() const { return m_surface; } inline const QSharedPointer &Toplevel::internalFramebufferObject() const { return m_internalFBO; } +inline QImage Toplevel::internalImageObject() const +{ + return m_internalImage; +} + inline QPoint Toplevel::clientContentPos() const { return QPoint(0, 0); } template inline T *Toplevel::findInList(const QList &list, std::function func) { static_assert(std::is_base_of::value, "U must be derived from T"); const auto it = std::find_if(list.begin(), list.end(), func); if (it == list.end()) { return nullptr; } return *it; } inline bool Toplevel::isPopupWindow() const { switch (windowType()) { case NET::ComboBox: case NET::DropdownMenu: case NET::PopupMenu: case NET::Tooltip: return true; default: return false; } } QDebug& operator<<(QDebug& stream, const Toplevel*); QDebug& operator<<(QDebug& stream, const ToplevelList&); } // namespace Q_DECLARE_METATYPE(KWin::Toplevel*) #endif diff --git a/wayland_server.cpp b/wayland_server.cpp index d6f2f6e87..36f873968 100644 --- a/wayland_server.cpp +++ b/wayland_server.cpp @@ -1,821 +1,788 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2015 Martin Gräßlin 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 . *********************************************************************/ #include "wayland_server.h" #include "client.h" #include "platform.h" #include "composite.h" #include "idle_inhibition.h" -#include "internal_client.h" #include "screens.h" #include "shell_client.h" #include "workspace.h" // Client #include #include #include #include #include #include #include #include // Server #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // KF #include // Qt #include #include #include #include #include // system #include #include #include //screenlocker #include using namespace KWayland::Server; namespace KWin { KWIN_SINGLETON_FACTORY(WaylandServer) WaylandServer::WaylandServer(QObject *parent) : QObject(parent) { qRegisterMetaType(); } WaylandServer::~WaylandServer() { destroyInputMethodConnection(); } void WaylandServer::destroyInternalConnection() { emit terminatingInternalClientConnection(); if (m_internalConnection.client) { // delete all connections hold by plugins like e.g. widget style const auto connections = KWayland::Client::ConnectionThread::connections(); for (auto c : connections) { if (c == m_internalConnection.client) { continue; } emit c->connectionDied(); } delete m_internalConnection.registry; delete m_internalConnection.compositor; delete m_internalConnection.seat; delete m_internalConnection.ddm; delete m_internalConnection.shm; dispatch(); m_internalConnection.client->deleteLater(); m_internalConnection.clientThread->quit(); m_internalConnection.clientThread->wait(); delete m_internalConnection.clientThread; m_internalConnection.client = nullptr; m_internalConnection.server->destroy(); m_internalConnection.server = nullptr; } } void WaylandServer::terminateClientConnections() { destroyInternalConnection(); destroyInputMethodConnection(); if (m_display) { const auto connections = m_display->connections(); for (auto it = connections.begin(); it != connections.end(); ++it) { (*it)->destroy(); } } } template void WaylandServer::createSurface(T *surface) { if (!Workspace::self()) { // it's possible that a Surface gets created before Workspace is created return; } if (surface->client() == m_xwayland.client) { // skip Xwayland clients, those are created using standard X11 way return; } if (surface->client() == m_screenLockerClientConnection) { ScreenLocker::KSldApp::self()->lockScreenShown(); } - ShellClient *client; - if (surface->client() == waylandServer()->internalConnection()) { - client = new InternalClient(surface); - } else { - client = new ShellClient(surface); - } + ShellClient *client = new ShellClient(surface); if (ServerSideDecorationInterface *deco = ServerSideDecorationInterface::get(surface->surface())) { client->installServerSideDecoration(deco); } auto it = std::find_if(m_plasmaShellSurfaces.begin(), m_plasmaShellSurfaces.end(), [client] (PlasmaShellSurfaceInterface *surface) { return client->surface() == surface->surface(); } ); if (it != m_plasmaShellSurfaces.end()) { client->installPlasmaShellSurface(*it); m_plasmaShellSurfaces.erase(it); } if (auto menu = m_appMenuManager->appMenuForSurface(surface->surface())) { client->installAppMenu(menu); } if (auto palette = m_paletteManager->paletteForSurface(surface->surface())) { client->installPalette(palette); } - if (client->isInternal()) { - m_internalClients << client; - } else { - m_clients << client; - } + m_clients << client; if (client->readyForPainting()) { emit shellClientAdded(client); } else { connect(client, &ShellClient::windowShown, this, &WaylandServer::shellClientShown); } //not directly connected as the connection is tied to client instead of this connect(m_XdgForeign, &KWayland::Server::XdgForeignInterface::transientChanged, client, [this](KWayland::Server::SurfaceInterface *child) { emit foreignTransientChanged(child); }); } class KWinDisplay : public KWayland::Server::FilteredDisplay { public: KWinDisplay(QObject *parent) : KWayland::Server::FilteredDisplay(parent) {} static QByteArray sha256(const QString &fileName) { QFile f(fileName); if (f.open(QFile::ReadOnly)) { QCryptographicHash hash(QCryptographicHash::Sha256); if (hash.addData(&f)) { return hash.result(); } } return QByteArray(); } bool isTrustedOrigin(KWayland::Server::ClientConnection *client) const { const auto fullPathSha = sha256(QStringLiteral("/proc/") + QString::number(client->processId()) + QLatin1String("/root") + client->executablePath()); const auto localSha = sha256(QLatin1String("/proc/") + QString::number(client->processId()) + QLatin1String("/exe")); const bool trusted = !localSha.isEmpty() && fullPathSha == localSha; if (!trusted) { qCWarning(KWIN_CORE) << "Could not trust" << client->executablePath() << "sha" << localSha << fullPathSha; } return trusted; } QStringList fetchRequestedInterfaces(KWayland::Server::ClientConnection *client) const { const auto serviceQuery = QStringLiteral("exist Exec and exist [X-KDE-Wayland-Interfaces] and '%1' =~ Exec").arg(client->executablePath()); const auto servicesFound = KServiceTypeTrader::self()->query(QStringLiteral("Application"), serviceQuery); if (servicesFound.isEmpty()) { return {}; } return servicesFound.first()->property("X-KDE-Wayland-Interfaces").toStringList(); } QSet interfacesBlackList = {"org_kde_kwin_remote_access_manager", "org_kde_plasma_window_management", "org_kde_kwin_fake_input", "org_kde_kwin_keystate"}; bool allowInterface(KWayland::Server::ClientConnection *client, const QByteArray &interfaceName) override { if (client->processId() == getpid()) { return true; } if (!interfacesBlackList.contains(interfaceName)) { return true; } if (client->executablePath().isEmpty()) { qCWarning(KWIN_CORE) << "Could not identify process with pid" << client->processId(); return false; } { auto requestedInterfaces = client->property("requestedInterfaces"); if (requestedInterfaces.isNull()) { requestedInterfaces = fetchRequestedInterfaces(client); client->setProperty("requestedInterfaces", requestedInterfaces); } qCDebug(KWIN_CORE) << "interfaces for" << client->executablePath() << requestedInterfaces << interfaceName << requestedInterfaces.toStringList().contains(QString::fromUtf8(interfaceName)); if (!requestedInterfaces.toStringList().contains(QString::fromUtf8(interfaceName))) { qCWarning(KWIN_CORE) << "Did not grant the interface" << interfaceName << "to" << client->executablePath() << ". Please request it under X-KDE-Wayland-Interfaces"; return false; } } { auto trustedOrigin = client->property("isPrivileged"); if (trustedOrigin.isNull()) { trustedOrigin = isTrustedOrigin(client); client->setProperty("isPrivileged", trustedOrigin); } if (!trustedOrigin.toBool()) { return false; } } qCDebug(KWIN_CORE) << "authorized" << client->executablePath() << interfaceName; return true; } }; bool WaylandServer::init(const QByteArray &socketName, InitalizationFlags flags) { m_initFlags = flags; m_display = new KWinDisplay(this); if (!socketName.isNull() && !socketName.isEmpty()) { m_display->setSocketName(QString::fromUtf8(socketName)); } else { m_display->setAutomaticSocketNaming(true); } m_display->start(); if (!m_display->isRunning()) { return false; } m_compositor = m_display->createCompositor(m_display); m_compositor->create(); connect(m_compositor, &CompositorInterface::surfaceCreated, this, [this] (SurfaceInterface *surface) { // check whether we have a Toplevel with the Surface's id Workspace *ws = Workspace::self(); if (!ws) { // it's possible that a Surface gets created before Workspace is created return; } if (surface->client() != xWaylandConnection()) { // setting surface is only relevat for Xwayland clients return; } auto check = [surface] (const Toplevel *t) { return t->surfaceId() == surface->id(); }; if (Toplevel *t = ws->findToplevel(check)) { t->setSurface(surface); } } ); m_shell = m_display->createShell(m_display); m_shell->create(); connect(m_shell, &ShellInterface::surfaceCreated, this, &WaylandServer::createSurface); m_xdgShell5 = m_display->createXdgShell(XdgShellInterfaceVersion::UnstableV5, m_display); m_xdgShell5->create(); connect(m_xdgShell5, &XdgShellInterface::surfaceCreated, this, &WaylandServer::createSurface); // TODO: verify seat and serial connect(m_xdgShell5, &XdgShellInterface::popupCreated, this, &WaylandServer::createSurface); m_xdgShell6 = m_display->createXdgShell(XdgShellInterfaceVersion::UnstableV6, m_display); m_xdgShell6->create(); connect(m_xdgShell6, &XdgShellInterface::surfaceCreated, this, &WaylandServer::createSurface); connect(m_xdgShell6, &XdgShellInterface::xdgPopupCreated, this, &WaylandServer::createSurface); m_xdgShell = m_display->createXdgShell(XdgShellInterfaceVersion::Stable, m_display); m_xdgShell->create(); connect(m_xdgShell, &XdgShellInterface::surfaceCreated, this, &WaylandServer::createSurface); connect(m_xdgShell, &XdgShellInterface::xdgPopupCreated, this, &WaylandServer::createSurface); m_xdgDecorationManager = m_display->createXdgDecorationManager(m_xdgShell, m_display); m_xdgDecorationManager->create(); connect(m_xdgDecorationManager, &XdgDecorationManagerInterface::xdgDecorationInterfaceCreated, this, [this] (XdgDecorationInterface *deco) { if (ShellClient *client = findClient(deco->surface()->surface())) { client->installXdgDecoration(deco); } }); m_display->createShm(); m_seat = m_display->createSeat(m_display); m_seat->create(); m_display->createPointerGestures(PointerGesturesInterfaceVersion::UnstableV1, m_display)->create(); m_display->createPointerConstraints(PointerConstraintsInterfaceVersion::UnstableV1, m_display)->create(); m_dataDeviceManager = m_display->createDataDeviceManager(m_display); m_dataDeviceManager->create(); m_idle = m_display->createIdle(m_display); m_idle->create(); auto idleInhibition = new IdleInhibition(m_idle); connect(this, &WaylandServer::shellClientAdded, idleInhibition, &IdleInhibition::registerShellClient); m_display->createIdleInhibitManager(IdleInhibitManagerInterfaceVersion::UnstableV1, m_display)->create(); m_plasmaShell = m_display->createPlasmaShell(m_display); m_plasmaShell->create(); connect(m_plasmaShell, &PlasmaShellInterface::surfaceCreated, [this] (PlasmaShellSurfaceInterface *surface) { if (ShellClient *client = findClient(surface->surface())) { client->installPlasmaShellSurface(surface); } else { m_plasmaShellSurfaces << surface; connect(surface, &QObject::destroyed, this, [this, surface] { m_plasmaShellSurfaces.removeOne(surface); } ); } } ); m_appMenuManager = m_display->createAppMenuManagerInterface(m_display); m_appMenuManager->create(); connect(m_appMenuManager, &AppMenuManagerInterface::appMenuCreated, [this] (AppMenuInterface *appMenu) { if (ShellClient *client = findClient(appMenu->surface())) { client->installAppMenu(appMenu); } } ); m_paletteManager = m_display->createServerSideDecorationPaletteManager(m_display); m_paletteManager->create(); connect(m_paletteManager, &ServerSideDecorationPaletteManagerInterface::paletteCreated, [this] (ServerSideDecorationPaletteInterface *palette) { if (ShellClient *client = findClient(palette->surface())) { client->installPalette(palette); } } ); m_windowManagement = m_display->createPlasmaWindowManagement(m_display); m_windowManagement->create(); m_windowManagement->setShowingDesktopState(PlasmaWindowManagementInterface::ShowingDesktopState::Disabled); connect(m_windowManagement, &PlasmaWindowManagementInterface::requestChangeShowingDesktop, this, [] (PlasmaWindowManagementInterface::ShowingDesktopState state) { if (!workspace()) { return; } bool set = false; switch (state) { case PlasmaWindowManagementInterface::ShowingDesktopState::Disabled: set = false; break; case PlasmaWindowManagementInterface::ShowingDesktopState::Enabled: set = true; break; default: Q_UNREACHABLE(); break; } if (set == workspace()->showingDesktop()) { return; } workspace()->setShowingDesktop(set); } ); m_virtualDesktopManagement = m_display->createPlasmaVirtualDesktopManagement(m_display); m_virtualDesktopManagement->create(); m_windowManagement->setPlasmaVirtualDesktopManagementInterface(m_virtualDesktopManagement); auto shadowManager = m_display->createShadowManager(m_display); shadowManager->create(); m_display->createDpmsManager(m_display)->create(); m_decorationManager = m_display->createServerSideDecorationManager(m_display); connect(m_decorationManager, &ServerSideDecorationManagerInterface::decorationCreated, this, [this] (ServerSideDecorationInterface *deco) { if (ShellClient *c = findClient(deco->surface())) { c->installServerSideDecoration(deco); } connect(deco, &ServerSideDecorationInterface::modeRequested, this, [this, deco] (ServerSideDecorationManagerInterface::Mode mode) { // always acknowledge the requested mode deco->setMode(mode); } ); } ); m_decorationManager->create(); m_outputManagement = m_display->createOutputManagement(m_display); connect(m_outputManagement, &OutputManagementInterface::configurationChangeRequested, this, [this](KWayland::Server::OutputConfigurationInterface *config) { kwinApp()->platform()->requestOutputsChange(config); }); m_outputManagement->create(); m_xdgOutputManager = m_display->createXdgOutputManager(m_display); m_xdgOutputManager->create(); m_display->createSubCompositor(m_display)->create(); m_XdgForeign = m_display->createXdgForeignInterface(m_display); m_XdgForeign->create(); m_keyState = m_display->createKeyStateInterface(m_display); m_keyState->create(); return true; } SurfaceInterface *WaylandServer::findForeignTransientForSurface(SurfaceInterface *surface) { return m_XdgForeign->transientFor(surface); } void WaylandServer::shellClientShown(Toplevel *t) { ShellClient *c = dynamic_cast(t); if (!c) { qCWarning(KWIN_CORE) << "Failed to cast a Toplevel which is supposed to be a ShellClient to ShellClient"; return; } disconnect(c, &ShellClient::windowShown, this, &WaylandServer::shellClientShown); emit shellClientAdded(c); } void WaylandServer::initWorkspace() { VirtualDesktopManager::self()->setVirtualDesktopManagement(m_virtualDesktopManagement); if (m_windowManagement) { connect(workspace(), &Workspace::showingDesktopChanged, this, [this] (bool set) { using namespace KWayland::Server; m_windowManagement->setShowingDesktopState(set ? PlasmaWindowManagementInterface::ShowingDesktopState::Enabled : PlasmaWindowManagementInterface::ShowingDesktopState::Disabled ); } ); } if (hasScreenLockerIntegration()) { if (m_internalConnection.interfacesAnnounced) { initScreenLocker(); } else { connect(m_internalConnection.registry, &KWayland::Client::Registry::interfacesAnnounced, this, &WaylandServer::initScreenLocker); } } else { emit initialized(); } } void WaylandServer::initScreenLocker() { ScreenLocker::KSldApp::self(); ScreenLocker::KSldApp::self()->setWaylandDisplay(m_display); ScreenLocker::KSldApp::self()->setGreeterEnvironment(kwinApp()->processStartupEnvironment()); ScreenLocker::KSldApp::self()->initialize(); connect(ScreenLocker::KSldApp::self(), &ScreenLocker::KSldApp::greeterClientConnectionChanged, this, [this] () { m_screenLockerClientConnection = ScreenLocker::KSldApp::self()->greeterClientConnection(); } ); connect(ScreenLocker::KSldApp::self(), &ScreenLocker::KSldApp::unlocked, this, [this] () { m_screenLockerClientConnection = nullptr; } ); if (m_initFlags.testFlag(InitalizationFlag::LockScreen)) { ScreenLocker::KSldApp::self()->lock(ScreenLocker::EstablishLock::Immediate); } emit initialized(); } WaylandServer::SocketPairConnection WaylandServer::createConnection() { SocketPairConnection ret; int sx[2]; if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, sx) < 0) { qCWarning(KWIN_CORE) << "Could not create socket"; return ret; } ret.connection = m_display->createClient(sx[0]); ret.fd = sx[1]; return ret; } int WaylandServer::createXWaylandConnection() { const auto socket = createConnection(); if (!socket.connection) { return -1; } m_xwayland.client = socket.connection; m_xwayland.destroyConnection = connect(m_xwayland.client, &KWayland::Server::ClientConnection::disconnected, this, [] { qFatal("Xwayland Connection died"); } ); return socket.fd; } void WaylandServer::destroyXWaylandConnection() { if (!m_xwayland.client) { return; } disconnect(m_xwayland.destroyConnection); m_xwayland.client->destroy(); m_xwayland.client = nullptr; } int WaylandServer::createInputMethodConnection() { const auto socket = createConnection(); if (!socket.connection) { return -1; } m_inputMethodServerConnection = socket.connection; return socket.fd; } void WaylandServer::destroyInputMethodConnection() { if (!m_inputMethodServerConnection) { return; } m_inputMethodServerConnection->destroy(); m_inputMethodServerConnection = nullptr; } void WaylandServer::createInternalConnection() { const auto socket = createConnection(); if (!socket.connection) { return; } m_internalConnection.server = socket.connection; using namespace KWayland::Client; m_internalConnection.client = new ConnectionThread(); m_internalConnection.client->setSocketFd(socket.fd); m_internalConnection.clientThread = new QThread; m_internalConnection.client->moveToThread(m_internalConnection.clientThread); m_internalConnection.clientThread->start(); connect(m_internalConnection.client, &ConnectionThread::connected, this, [this] { Registry *registry = new Registry(this); EventQueue *eventQueue = new EventQueue(this); eventQueue->setup(m_internalConnection.client); registry->setEventQueue(eventQueue); registry->create(m_internalConnection.client); m_internalConnection.registry = registry; connect(registry, &Registry::shmAnnounced, this, [this] (quint32 name, quint32 version) { m_internalConnection.shm = m_internalConnection.registry->createShmPool(name, version, this); } ); connect(registry, &Registry::interfacesAnnounced, this, [this, registry] { m_internalConnection.interfacesAnnounced = true; const auto compInterface = registry->interface(Registry::Interface::Compositor); if (compInterface.name != 0) { m_internalConnection.compositor = registry->createCompositor(compInterface.name, compInterface.version, this); } const auto seatInterface = registry->interface(Registry::Interface::Seat); if (seatInterface.name != 0) { m_internalConnection.seat = registry->createSeat(seatInterface.name, seatInterface.version, this); } const auto ddmInterface = registry->interface(Registry::Interface::DataDeviceManager); if (ddmInterface.name != 0) { m_internalConnection.ddm = registry->createDataDeviceManager(ddmInterface.name, ddmInterface.version, this); } } ); registry->setup(); } ); m_internalConnection.client->initConnection(); } void WaylandServer::removeClient(ShellClient *c) { m_clients.removeAll(c); - m_internalClients.removeAll(c); emit shellClientRemoved(c); } void WaylandServer::dispatch() { if (!m_display) { return; } if (m_internalConnection.server) { m_internalConnection.server->flush(); } m_display->dispatchEvents(0); } static ShellClient *findClientInList(const QList &clients, quint32 id) { auto it = std::find_if(clients.begin(), clients.end(), [id] (ShellClient *c) { return c->windowId() == id; } ); if (it == clients.end()) { return nullptr; } return *it; } static ShellClient *findClientInList(const QList &clients, KWayland::Server::SurfaceInterface *surface) { auto it = std::find_if(clients.begin(), clients.end(), [surface] (ShellClient *c) { return c->surface() == surface; } ); if (it == clients.end()) { return nullptr; } return *it; } ShellClient *WaylandServer::findClient(quint32 id) const { if (id == 0) { return nullptr; } if (ShellClient *c = findClientInList(m_clients, id)) { return c; } - if (ShellClient *c = findClientInList(m_internalClients, id)) { - return c; - } return nullptr; } ShellClient *WaylandServer::findClient(SurfaceInterface *surface) const { if (!surface) { return nullptr; } if (ShellClient *c = findClientInList(m_clients, surface)) { return c; } - if (ShellClient *c = findClientInList(m_internalClients, surface)) { - return c; - } return nullptr; } AbstractClient *WaylandServer::findAbstractClient(SurfaceInterface *surface) const { return findClient(surface); } -ShellClient *WaylandServer::findClient(QWindow *w) const -{ - if (!w) { - return nullptr; - } - auto it = std::find_if(m_internalClients.constBegin(), m_internalClients.constEnd(), - [w] (const ShellClient *c) { - return c->internalWindow() == w; - } - ); - if (it != m_internalClients.constEnd()) { - return *it; - } - return nullptr; -} - quint32 WaylandServer::createWindowId(SurfaceInterface *surface) { auto it = m_clientIds.constFind(surface->client()); quint16 clientId = 0; if (it != m_clientIds.constEnd()) { clientId = it.value(); } else { clientId = createClientId(surface->client()); } Q_ASSERT(clientId != 0); quint32 id = clientId; // TODO: this does not prevent that two surfaces of same client get same id id = (id << 16) | (surface->id() & 0xFFFF); if (findClient(id)) { qCWarning(KWIN_CORE) << "Invalid client windowId generated:" << id; return 0; } return id; } quint16 WaylandServer::createClientId(ClientConnection *c) { auto ids = m_clientIds.values().toSet(); quint16 id = 1; if (!ids.isEmpty()) { for (quint16 i = ids.count() + 1; i >= 1 ; i--) { if (!ids.contains(i)) { id = i; break; } } } Q_ASSERT(!ids.contains(id)); m_clientIds.insert(c, id); connect(c, &ClientConnection::disconnected, this, [this] (ClientConnection *c) { m_clientIds.remove(c); } ); return id; } bool WaylandServer::isScreenLocked() const { if (!hasScreenLockerIntegration()) { return false; } return ScreenLocker::KSldApp::self()->lockState() == ScreenLocker::KSldApp::Locked || ScreenLocker::KSldApp::self()->lockState() == ScreenLocker::KSldApp::AcquiringLock; } bool WaylandServer::hasScreenLockerIntegration() const { return !m_initFlags.testFlag(InitalizationFlag::NoLockScreenIntegration); } bool WaylandServer::hasGlobalShortcutSupport() const { return !m_initFlags.testFlag(InitalizationFlag::NoGlobalShortcuts); } void WaylandServer::simulateUserActivity() { if (m_idle) { m_idle->simulateUserActivity(); } } void WaylandServer::updateKeyState(KWin::Xkb::LEDs leds) { if (!m_keyState) return; m_keyState->setState(KeyStateInterface::Key::CapsLock, leds & KWin::Xkb::LED::CapsLock ? KeyStateInterface::State::Locked : KeyStateInterface::State::Unlocked); m_keyState->setState(KeyStateInterface::Key::NumLock, leds & KWin::Xkb::LED::NumLock ? KeyStateInterface::State::Locked : KeyStateInterface::State::Unlocked); m_keyState->setState(KeyStateInterface::Key::ScrollLock, leds & KWin::Xkb::LED::ScrollLock ? KeyStateInterface::State::Locked : KeyStateInterface::State::Unlocked); } } diff --git a/wayland_server.h b/wayland_server.h index 6dde8ff68..dc0838a8d 100644 --- a/wayland_server.h +++ b/wayland_server.h @@ -1,295 +1,290 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2015 Martin Gräßlin 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 . *********************************************************************/ #ifndef KWIN_WAYLAND_SERVER_H #define KWIN_WAYLAND_SERVER_H #include #include "keyboard_input.h" #include class QThread; class QProcess; class QWindow; namespace KWayland { namespace Client { class ConnectionThread; class Registry; class Compositor; class Seat; class DataDeviceManager; class ShmPool; class Surface; } namespace Server { class AppMenuManagerInterface; class ClientConnection; class CompositorInterface; class Display; class DataDeviceInterface; class IdleInterface; class ShellInterface; class SeatInterface; class DataDeviceManagerInterface; class ServerSideDecorationManagerInterface; class ServerSideDecorationPaletteManagerInterface; class SurfaceInterface; class OutputInterface; class PlasmaShellInterface; class PlasmaShellSurfaceInterface; class PlasmaVirtualDesktopManagementInterface; class PlasmaWindowManagementInterface; class QtSurfaceExtensionInterface; class OutputManagementInterface; class OutputConfigurationInterface; class XdgDecorationManagerInterface; class XdgShellInterface; class XdgForeignInterface; class XdgOutputManagerInterface; class KeyStateInterface; } } namespace KWin { class ShellClient; class AbstractClient; class Toplevel; class KWIN_EXPORT WaylandServer : public QObject { Q_OBJECT public: enum class InitalizationFlag { NoOptions = 0x0, LockScreen = 0x1, NoLockScreenIntegration = 0x2, NoGlobalShortcuts = 0x4 }; Q_DECLARE_FLAGS(InitalizationFlags, InitalizationFlag) ~WaylandServer() override; bool init(const QByteArray &socketName = QByteArray(), InitalizationFlags flags = InitalizationFlag::NoOptions); void terminateClientConnections(); KWayland::Server::Display *display() { return m_display; } KWayland::Server::CompositorInterface *compositor() { return m_compositor; } KWayland::Server::SeatInterface *seat() { return m_seat; } KWayland::Server::DataDeviceManagerInterface *dataDeviceManager() { return m_dataDeviceManager; } KWayland::Server::ShellInterface *shell() { return m_shell; } KWayland::Server::PlasmaVirtualDesktopManagementInterface *virtualDesktopManagement() { return m_virtualDesktopManagement; } KWayland::Server::PlasmaWindowManagementInterface *windowManagement() { return m_windowManagement; } KWayland::Server::ServerSideDecorationManagerInterface *decorationManager() const { return m_decorationManager; } KWayland::Server::XdgOutputManagerInterface *xdgOutputManager() const { return m_xdgOutputManager; } QList clients() const { return m_clients; } - QList internalClients() const { - return m_internalClients; - } void removeClient(ShellClient *c); ShellClient *findClient(quint32 id) const; ShellClient *findClient(KWayland::Server::SurfaceInterface *surface) const; AbstractClient *findAbstractClient(KWayland::Server::SurfaceInterface *surface) const; - ShellClient *findClient(QWindow *w) const; /** * @returns a transient parent of a surface imported with the foreign protocol, if any */ KWayland::Server::SurfaceInterface *findForeignTransientForSurface(KWayland::Server::SurfaceInterface *surface); /** * @returns file descriptor for Xwayland to connect to. */ int createXWaylandConnection(); void destroyXWaylandConnection(); /** * @returns file descriptor to the input method server's socket. */ int createInputMethodConnection(); void destroyInputMethodConnection(); /** * @returns true if screen is locked. */ bool isScreenLocked() const; /** * @returns whether integration with KScreenLocker is available. */ bool hasScreenLockerIntegration() const; /** * @returns whether any kind of global shortcuts are supported. */ bool hasGlobalShortcutSupport() const; void createInternalConnection(); void initWorkspace(); KWayland::Server::ClientConnection *xWaylandConnection() const { return m_xwayland.client; } KWayland::Server::ClientConnection *inputMethodConnection() const { return m_inputMethodServerConnection; } KWayland::Server::ClientConnection *internalConnection() const { return m_internalConnection.server; } KWayland::Server::ClientConnection *screenLockerClientConnection() const { return m_screenLockerClientConnection; } KWayland::Client::Compositor *internalCompositor() { return m_internalConnection.compositor; } KWayland::Client::Seat *internalSeat() { return m_internalConnection.seat; } KWayland::Client::DataDeviceManager *internalDataDeviceManager() { return m_internalConnection.ddm; } KWayland::Client::ShmPool *internalShmPool() { return m_internalConnection.shm; } KWayland::Client::ConnectionThread *internalClientConection() { return m_internalConnection.client; } KWayland::Client::Registry *internalClientRegistry() { return m_internalConnection.registry; } void dispatch(); quint32 createWindowId(KWayland::Server::SurfaceInterface *surface); /** * Struct containing information for a created Wayland connection through a * socketpair. */ struct SocketPairConnection { /** * ServerSide Connection */ KWayland::Server::ClientConnection *connection = nullptr; /** * client-side file descriptor for the socket */ int fd = -1; }; /** * Creates a Wayland connection using a socket pair. */ SocketPairConnection createConnection(); void simulateUserActivity(); void updateKeyState(KWin::Xkb::LEDs leds); Q_SIGNALS: void shellClientAdded(KWin::ShellClient*); void shellClientRemoved(KWin::ShellClient*); void terminatingInternalClientConnection(); void initialized(); void foreignTransientChanged(KWayland::Server::SurfaceInterface *child); private: void shellClientShown(Toplevel *t); quint16 createClientId(KWayland::Server::ClientConnection *c); void destroyInternalConnection(); template void createSurface(T *surface); void initScreenLocker(); KWayland::Server::Display *m_display = nullptr; KWayland::Server::CompositorInterface *m_compositor = nullptr; KWayland::Server::SeatInterface *m_seat = nullptr; KWayland::Server::DataDeviceManagerInterface *m_dataDeviceManager = nullptr; KWayland::Server::ShellInterface *m_shell = nullptr; KWayland::Server::XdgShellInterface *m_xdgShell5 = nullptr; KWayland::Server::XdgShellInterface *m_xdgShell6 = nullptr; KWayland::Server::XdgShellInterface *m_xdgShell = nullptr; KWayland::Server::PlasmaShellInterface *m_plasmaShell = nullptr; KWayland::Server::PlasmaWindowManagementInterface *m_windowManagement = nullptr; KWayland::Server::PlasmaVirtualDesktopManagementInterface *m_virtualDesktopManagement = nullptr; KWayland::Server::ServerSideDecorationManagerInterface *m_decorationManager = nullptr; KWayland::Server::OutputManagementInterface *m_outputManagement = nullptr; KWayland::Server::AppMenuManagerInterface *m_appMenuManager = nullptr; KWayland::Server::ServerSideDecorationPaletteManagerInterface *m_paletteManager = nullptr; KWayland::Server::IdleInterface *m_idle = nullptr; KWayland::Server::XdgOutputManagerInterface *m_xdgOutputManager = nullptr; KWayland::Server::XdgDecorationManagerInterface *m_xdgDecorationManager = nullptr; struct { KWayland::Server::ClientConnection *client = nullptr; QMetaObject::Connection destroyConnection; } m_xwayland; KWayland::Server::ClientConnection *m_inputMethodServerConnection = nullptr; KWayland::Server::ClientConnection *m_screenLockerClientConnection = nullptr; struct { KWayland::Server::ClientConnection *server = nullptr; KWayland::Client::ConnectionThread *client = nullptr; QThread *clientThread = nullptr; KWayland::Client::Registry *registry = nullptr; KWayland::Client::Compositor *compositor = nullptr; KWayland::Client::Seat *seat = nullptr; KWayland::Client::DataDeviceManager *ddm = nullptr; KWayland::Client::ShmPool *shm = nullptr; bool interfacesAnnounced = false; } m_internalConnection; KWayland::Server::XdgForeignInterface *m_XdgForeign = nullptr; KWayland::Server::KeyStateInterface *m_keyState = nullptr; QList m_clients; - QList m_internalClients; QHash m_clientIds; InitalizationFlags m_initFlags; QVector m_plasmaShellSurfaces; KWIN_SINGLETON(WaylandServer) }; inline WaylandServer *waylandServer() { return WaylandServer::self(); } } // namespace KWin #endif diff --git a/workspace.cpp b/workspace.cpp index 298f72653..19ee5b6d8 100644 --- a/workspace.cpp +++ b/workspace.cpp @@ -1,1808 +1,1830 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 1999, 2000 Matthias Ettrich Copyright (C) 2003 Lubos Lunak +Copyright (C) 2019 Vlad Zagorodniy 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 . *********************************************************************/ // own #include "workspace.h" // kwin libs #include // kwin #ifdef KWIN_BUILD_ACTIVITIES #include "activities.h" #endif #include "appmenu.h" #include "atoms.h" #include "client.h" #include "composite.h" #include "cursor.h" #include "dbusinterface.h" #include "deleted.h" #include "effects.h" #include "focuschain.h" #include "group.h" #include "input.h" +#include "internal_client.h" #include "logind.h" #include "moving_client_x11_filter.h" #include "killwindow.h" #include "netinfo.h" #include "outline.h" #include "placement.h" #include "rules.h" #include "screenedge.h" #include "screens.h" #include "platform.h" #include "scripting/scripting.h" #ifdef KWIN_BUILD_TABBOX #include "tabbox.h" #endif #include "unmanaged.h" #include "useractions.h" #include "virtualdesktops.h" #include "shell_client.h" #include "was_user_interaction_x11_filter.h" #include "wayland_server.h" #include "xcbutils.h" #include "main.h" #include "decorations/decorationbridge.h" // KDE #include #include #include #include // Qt #include namespace KWin { extern int screen_number; extern bool is_multihead; ColorMapper::ColorMapper(QObject *parent) : QObject(parent) , m_default(defaultScreen()->default_colormap) , m_installed(defaultScreen()->default_colormap) { } ColorMapper::~ColorMapper() { } void ColorMapper::update() { xcb_colormap_t cmap = m_default; if (Client *c = dynamic_cast(Workspace::self()->activeClient())) { if (c->colormap() != XCB_COLORMAP_NONE) { cmap = c->colormap(); } } if (cmap != m_installed) { xcb_install_colormap(connection(), cmap); m_installed = cmap; } } Workspace* Workspace::_self = nullptr; Workspace::Workspace(const QString &sessionKey) : QObject(nullptr) , m_compositor(nullptr) // Unsorted , active_popup(nullptr) , active_popup_client(nullptr) , m_initialDesktop(1) , active_client(nullptr) , last_active_client(nullptr) , most_recently_raised(nullptr) , movingClient(nullptr) , delayfocus_client(nullptr) , force_restacking(false) , showing_desktop(false) , was_user_interaction(false) , session_saving(false) , block_focus(0) , m_userActionsMenu(new UserActionsMenu(this)) , client_keys_dialog(nullptr) , client_keys_client(nullptr) , global_shortcuts_disabled_for_client(false) , workspaceInit(true) , startup(nullptr) , set_active_client_recursion(0) , block_stacking_updates(0) { // If KWin was already running it saved its configuration after loosing the selection -> Reread QFuture reparseConfigFuture = QtConcurrent::run(options, &Options::reparseConfiguration); ApplicationMenu::create(this); _self = this; #ifdef KWIN_BUILD_ACTIVITIES Activities *activities = nullptr; if (kwinApp()->usesKActivities()) { activities = Activities::create(this); } if (activities) { connect(activities, SIGNAL(currentChanged(QString)), SLOT(updateCurrentActivity(QString))); } #endif // PluginMgr needs access to the config file, so we need to wait for it for finishing reparseConfigFuture.waitForFinished(); options->loadConfig(); options->loadCompositingConfig(false); delayFocusTimer = nullptr; if (!sessionKey.isEmpty()) loadSessionInfo(sessionKey); connect(qApp, &QGuiApplication::commitDataRequest, this, &Workspace::commitData); connect(qApp, &QGuiApplication::saveStateRequest, this, &Workspace::saveState); RuleBook::create(this)->load(); ScreenEdges::create(this); // VirtualDesktopManager needs to be created prior to init shortcuts // and prior to TabBox, due to TabBox connecting to signals // actual initialization happens in init() VirtualDesktopManager::create(this); //dbus interface new VirtualDesktopManagerDBusInterface(VirtualDesktopManager::self()); #ifdef KWIN_BUILD_TABBOX // need to create the tabbox before compositing scene is setup TabBox::TabBox::create(this); #endif if (Compositor::self()) { m_compositor = Compositor::self(); } else { Q_ASSERT(kwinApp()->operationMode() == Application::OperationMode::OperationModeX11); m_compositor = X11Compositor::create(this); } connect(this, &Workspace::currentDesktopChanged, m_compositor, &Compositor::addRepaintFull); connect(m_compositor, &QObject::destroyed, this, [this] { m_compositor = nullptr; }); auto decorationBridge = Decoration::DecorationBridge::create(this); decorationBridge->init(); connect(this, &Workspace::configChanged, decorationBridge, &Decoration::DecorationBridge::reconfigure); new DBusInterface(this); Outline::create(this); initShortcuts(); init(); } void Workspace::init() { KSharedConfigPtr config = kwinApp()->config(); kwinApp()->createScreens(); Screens *screens = Screens::self(); // get screen support connect(screens, SIGNAL(changed()), SLOT(desktopResized())); screens->setConfig(config); screens->reconfigure(); connect(options, SIGNAL(configChanged()), screens, SLOT(reconfigure())); ScreenEdges *screenEdges = ScreenEdges::self(); screenEdges->setConfig(config); screenEdges->init(); connect(options, SIGNAL(configChanged()), screenEdges, SLOT(reconfigure())); connect(VirtualDesktopManager::self(), SIGNAL(layoutChanged(int,int)), screenEdges, SLOT(updateLayout())); connect(this, &Workspace::clientActivated, screenEdges, &ScreenEdges::checkBlocking); FocusChain *focusChain = FocusChain::create(this); connect(this, &Workspace::clientRemoved, focusChain, &FocusChain::remove); connect(this, &Workspace::clientActivated, focusChain, &FocusChain::setActiveClient); connect(VirtualDesktopManager::self(), SIGNAL(countChanged(uint,uint)), focusChain, SLOT(resize(uint,uint))); connect(VirtualDesktopManager::self(), SIGNAL(currentChanged(uint,uint)), focusChain, SLOT(setCurrentDesktop(uint,uint))); connect(options, SIGNAL(separateScreenFocusChanged(bool)), focusChain, SLOT(setSeparateScreenFocus(bool))); focusChain->setSeparateScreenFocus(options->isSeparateScreenFocus()); // create VirtualDesktopManager and perform dependency injection VirtualDesktopManager *vds = VirtualDesktopManager::self(); connect(vds, &VirtualDesktopManager::desktopRemoved, this, [this](KWin::VirtualDesktop *desktop) { //Wayland if (kwinApp()->operationMode() == Application::OperationModeWaylandOnly || kwinApp()->operationMode() == Application::OperationModeXwayland) { for (auto it = m_allClients.constBegin(); it != m_allClients.constEnd(); ++it) { if (!(*it)->desktops().contains(desktop)) { continue; } if ((*it)->desktops().count() > 1) { (*it)->leaveDesktop(desktop); } else { sendClientToDesktop(*it, qMin(desktop->x11DesktopNumber(), VirtualDesktopManager::self()->count()), true); } } //X11 } else { for (auto it = m_allClients.constBegin(); it != m_allClients.constEnd(); ++it) { if (!(*it)->isOnAllDesktops() && ((*it)->desktop() > static_cast(VirtualDesktopManager::self()->count()))) { sendClientToDesktop(*it, VirtualDesktopManager::self()->count(), true); } } } } ); connect(vds, SIGNAL(countChanged(uint,uint)), SLOT(slotDesktopCountChanged(uint,uint))); connect(vds, SIGNAL(currentChanged(uint,uint)), SLOT(slotCurrentDesktopChanged(uint,uint))); vds->setNavigationWrappingAround(options->isRollOverDesktops()); connect(options, SIGNAL(rollOverDesktopsChanged(bool)), vds, SLOT(setNavigationWrappingAround(bool))); vds->setConfig(config); // Now we know how many desktops we'll have, thus we initialize the positioning object Placement::create(this); // positioning object needs to be created before the virtual desktops are loaded. vds->load(); vds->updateLayout(); //makes sure any autogenerated id is saved, necessary as in case of xwayland, load will be called 2 times // load is needed to be called again when starting xwayalnd to sync to RootInfo, see BUG 385260 vds->save(); if (!VirtualDesktopManager::self()->setCurrent(m_initialDesktop)) VirtualDesktopManager::self()->setCurrent(1); reconfigureTimer.setSingleShot(true); updateToolWindowsTimer.setSingleShot(true); connect(&reconfigureTimer, SIGNAL(timeout()), this, SLOT(slotReconfigure())); connect(&updateToolWindowsTimer, SIGNAL(timeout()), this, SLOT(slotUpdateToolWindows())); // TODO: do we really need to reconfigure everything when fonts change? // maybe just reconfigure the decorations? Move this into libkdecoration? QDBusConnection::sessionBus().connect(QString(), QStringLiteral("/KDEPlatformTheme"), QStringLiteral("org.kde.KDEPlatformTheme"), QStringLiteral("refreshFonts"), this, SLOT(reconfigure())); active_client = nullptr; initWithX11(); Scripting::create(this); if (auto w = waylandServer()) { connect(w, &WaylandServer::shellClientAdded, this, [this] (ShellClient *c) { setupClientConnections(c); c->updateDecoration(false); updateClientLayer(c); if (!c->isInternal()) { const QRect area = clientArea(PlacementArea, Screens::self()->current(), c->desktop()); bool placementDone = false; if (c->isInitialPositionSet()) { placementDone = true; } if (c->isFullScreen()) { placementDone = true; } if (c->maximizeMode() == MaximizeMode::MaximizeFull) { placementDone = true; } if (c->rules()->checkPosition(invalidPoint, true) != invalidPoint) { placementDone = true; } if (!placementDone) { c->placeIn(area); } m_allClients.append(c); if (!unconstrained_stacking_order.contains(c)) unconstrained_stacking_order.append(c); // Raise if it hasn't got any stacking position yet if (!stacking_order.contains(c)) // It'll be updated later, and updateToolWindows() requires stacking_order.append(c); // c to be in stacking_order } markXStackingOrderAsDirty(); updateStackingOrder(true); updateClientArea(); if (c->wantsInput() && !c->isMinimized()) { activateClient(c); } updateTabbox(); connect(c, &ShellClient::windowShown, this, [this, c] { updateClientLayer(c); // TODO: when else should we send the client through placement? if (c->hasTransientPlacementHint()) { const QRect area = clientArea(PlacementArea, Screens::self()->current(), c->desktop()); c->placeIn(area); } markXStackingOrderAsDirty(); updateStackingOrder(true); updateClientArea(); if (c->wantsInput()) { activateClient(c); } } ); connect(c, &ShellClient::windowHidden, this, [this] { // TODO: update tabbox if it's displayed markXStackingOrderAsDirty(); updateStackingOrder(true); updateClientArea(); } ); } ); connect(w, &WaylandServer::shellClientRemoved, this, [this] (ShellClient *c) { m_allClients.removeAll(c); if (c == most_recently_raised) { most_recently_raised = nullptr; } if (c == delayfocus_client) { cancelDelayFocus(); } if (c == last_active_client) { last_active_client = nullptr; } if (client_keys_client == c) { setupWindowShortcutDone(false); } if (!c->shortcut().isEmpty()) { c->setShortcut(QString()); // Remove from client_keys } clientHidden(c); emit clientRemoved(c); markXStackingOrderAsDirty(); updateStackingOrder(true); updateClientArea(); updateTabbox(); } ); } // SELI TODO: This won't work with unreasonable focus policies, // and maybe in rare cases also if the selected client doesn't // want focus workspaceInit = false; // broadcast that Workspace is ready, but first process all events. QMetaObject::invokeMethod(this, "workspaceInitialized", Qt::QueuedConnection); // TODO: ungrabXServer() } void Workspace::initWithX11() { if (!kwinApp()->x11Connection()) { connect(kwinApp(), &Application::x11ConnectionChanged, this, &Workspace::initWithX11, Qt::UniqueConnection); return; } disconnect(kwinApp(), &Application::x11ConnectionChanged, this, &Workspace::initWithX11); atoms->retrieveHelpers(); // first initialize the extensions Xcb::Extensions::self(); ColorMapper *colormaps = new ColorMapper(this); connect(this, &Workspace::clientActivated, colormaps, &ColorMapper::update); // Call this before XSelectInput() on the root window startup = new KStartupInfo( KStartupInfo::DisableKWinModule | KStartupInfo::AnnounceSilenceChanges, this); // Select windowmanager privileges selectWmInputEventMask(); // Compatibility int32_t data = 1; xcb_change_property(connection(), XCB_PROP_MODE_APPEND, rootWindow(), atoms->kwin_running, atoms->kwin_running, 32, 1, &data); if (kwinApp()->operationMode() == Application::OperationModeX11) { m_wasUserInteractionFilter.reset(new WasUserInteractionX11Filter); m_movingClientFilter.reset(new MovingClientX11Filter); } updateXTime(); // Needed for proper initialization of user_time in Client ctor const uint32_t nullFocusValues[] = {true}; m_nullFocus.reset(new Xcb::Window(QRect(-1, -1, 1, 1), XCB_WINDOW_CLASS_INPUT_ONLY, XCB_CW_OVERRIDE_REDIRECT, nullFocusValues)); m_nullFocus->map(); RootInfo *rootInfo = RootInfo::create(); const auto vds = VirtualDesktopManager::self(); vds->setRootInfo(rootInfo); // load again to sync to RootInfo, see BUG 385260 vds->load(); vds->updateRootInfo(); rootInfo->setCurrentDesktop(vds->currentDesktop()->x11DesktopNumber()); // TODO: only in X11 mode // Extra NETRootInfo instance in Client mode is needed to get the values of the properties NETRootInfo client_info(connection(), NET::ActiveWindow | NET::CurrentDesktop); if (!qApp->isSessionRestored()) { m_initialDesktop = client_info.currentDesktop(); vds->setCurrent(m_initialDesktop); } // TODO: better value rootInfo->setActiveWindow(None); focusToNull(); if (!qApp->isSessionRestored()) ++block_focus; // Because it will be set below { // Begin updates blocker block StackingUpdatesBlocker blocker(this); Xcb::Tree tree(rootWindow()); xcb_window_t *wins = xcb_query_tree_children(tree.data()); QVector windowAttributes(tree->children_len); QVector windowGeometries(tree->children_len); // Request the attributes and geometries of all toplevel windows for (int i = 0; i < tree->children_len; i++) { windowAttributes[i] = Xcb::WindowAttributes(wins[i]); windowGeometries[i] = Xcb::WindowGeometry(wins[i]); } // Get the replies for (int i = 0; i < tree->children_len; i++) { Xcb::WindowAttributes attr(windowAttributes.at(i)); if (attr.isNull()) { continue; } if (attr->override_redirect) { if (attr->map_state == XCB_MAP_STATE_VIEWABLE && attr->_class != XCB_WINDOW_CLASS_INPUT_ONLY) // ### This will request the attributes again createUnmanaged(wins[i]); } else if (attr->map_state != XCB_MAP_STATE_UNMAPPED) { if (Application::wasCrash()) { fixPositionAfterCrash(wins[i], windowGeometries.at(i).data()); } // ### This will request the attributes again createClient(wins[i], true); } } // Propagate clients, will really happen at the end of the updates blocker block updateStackingOrder(true); saveOldScreenSizes(); updateClientArea(); // NETWM spec says we have to set it to (0,0) if we don't support it NETPoint* viewports = new NETPoint[VirtualDesktopManager::self()->count()]; rootInfo->setDesktopViewport(VirtualDesktopManager::self()->count(), *viewports); delete[] viewports; QRect geom; for (int i = 0; i < screens()->count(); i++) { geom |= screens()->geometry(i); } NETSize desktop_geometry; desktop_geometry.width = geom.width(); desktop_geometry.height = geom.height(); rootInfo->setDesktopGeometry(desktop_geometry); setShowingDesktop(false); } // End updates blocker block // TODO: only on X11? AbstractClient* new_active_client = nullptr; if (!qApp->isSessionRestored()) { --block_focus; new_active_client = findClient(Predicate::WindowMatch, client_info.activeWindow()); } if (new_active_client == nullptr && activeClient() == nullptr && should_get_focus.count() == 0) { // No client activated in manage() if (new_active_client == nullptr) new_active_client = topClientOnDesktop(VirtualDesktopManager::self()->current(), -1); if (new_active_client == nullptr && !desktops.isEmpty()) new_active_client = findDesktop(true, VirtualDesktopManager::self()->current()); } if (new_active_client != nullptr) activateClient(new_active_client); } Workspace::~Workspace() { blockStackingUpdates(true); // TODO: grabXServer(); // Use stacking_order, so that kwin --replace keeps stacking order const ToplevelList stack = stacking_order; // "mutex" the stackingorder, since anything trying to access it from now on will find // many dangeling pointers and crash stacking_order.clear(); for (ToplevelList::const_iterator it = stack.constBegin(), end = stack.constEnd(); it != end; ++it) { Client *c = qobject_cast(const_cast(*it)); if (!c) { continue; } // Only release the window c->releaseWindow(true); // No removeClient() is called, it does more than just removing. // However, remove from some lists to e.g. prevent performTransiencyCheck() // from crashing. clients.removeAll(c); m_allClients.removeAll(c); desktops.removeAll(c); } Client::cleanupX11(); if (waylandServer()) { const QList shellClients = waylandServer()->clients(); for (ShellClient *shellClient : shellClients) { shellClient->destroyClient(); } - - const QList internalClients = waylandServer()->internalClients(); - for (ShellClient *internalClient : internalClients) { - internalClient->destroyClient(); - } } for (UnmanagedList::iterator it = unmanaged.begin(), end = unmanaged.end(); it != end; ++it) (*it)->release(ReleaseReason::KWinShutsDown); + for (InternalClient *client : m_internalClients) { + client->destroyClient(); + } + if (auto c = kwinApp()->x11Connection()) { xcb_delete_property(c, kwinApp()->x11RootWindow(), atoms->kwin_running); } for (auto it = deleted.begin(); it != deleted.end();) { emit deletedRemoved(*it); it = deleted.erase(it); } delete RuleBook::self(); kwinApp()->config()->sync(); RootInfo::destroy(); delete startup; delete Placement::self(); delete client_keys_dialog; foreach (SessionInfo * s, session) delete s; // TODO: ungrabXServer(); Xcb::Extensions::destroy(); _self = nullptr; } void Workspace::setupClientConnections(AbstractClient *c) { connect(c, &Toplevel::needsRepaint, m_compositor, &Compositor::scheduleRepaint); connect(c, &AbstractClient::desktopPresenceChanged, this, &Workspace::desktopPresenceChanged); connect(c, &AbstractClient::minimizedChanged, this, std::bind(&Workspace::clientMinimizedChanged, this, c)); } Client* Workspace::createClient(xcb_window_t w, bool is_mapped) { StackingUpdatesBlocker blocker(this); Client* c = new Client(); setupClientConnections(c); if (X11Compositor *compositor = X11Compositor::self()) { connect(c, &Client::blockingCompositingChanged, compositor, &X11Compositor::updateClientCompositeBlocking); } connect(c, SIGNAL(clientFullScreenSet(KWin::Client*,bool,bool)), ScreenEdges::self(), SIGNAL(checkBlocking())); if (!c->manage(w, is_mapped)) { Client::deleteClient(c); return nullptr; } addClient(c); return c; } Unmanaged* Workspace::createUnmanaged(xcb_window_t w) { if (X11Compositor *compositor = X11Compositor::self()) { if (compositor->checkForOverlayWindow(w)) { return nullptr; } } Unmanaged* c = new Unmanaged(); if (!c->track(w)) { Unmanaged::deleteUnmanaged(c); return nullptr; } connect(c, &Unmanaged::needsRepaint, m_compositor, &Compositor::scheduleRepaint); addUnmanaged(c); emit unmanagedAdded(c); return c; } void Workspace::addClient(Client* c) { Group* grp = findGroup(c->window()); emit clientAdded(c); if (grp != nullptr) grp->gotLeader(c); if (c->isDesktop()) { desktops.append(c); if (active_client == nullptr && should_get_focus.isEmpty() && c->isOnCurrentDesktop()) requestFocus(c); // TODO: Make sure desktop is active after startup if there's no other window active } else { FocusChain::self()->update(c, FocusChain::Update); clients.append(c); m_allClients.append(c); } if (!unconstrained_stacking_order.contains(c)) unconstrained_stacking_order.append(c); // Raise if it hasn't got any stacking position yet if (!stacking_order.contains(c)) // It'll be updated later, and updateToolWindows() requires stacking_order.append(c); // c to be in stacking_order markXStackingOrderAsDirty(); updateClientArea(); // This cannot be in manage(), because the client got added only now updateClientLayer(c); if (c->isDesktop()) { raiseClient(c); // If there's no active client, make this desktop the active one if (activeClient() == nullptr && should_get_focus.count() == 0) activateClient(findDesktop(true, VirtualDesktopManager::self()->current())); } c->checkActiveModal(); checkTransients(c->window()); // SELI TODO: Does this really belong here? updateStackingOrder(true); // Propagate new client if (c->isUtility() || c->isMenu() || c->isToolbar()) updateToolWindows(true); updateTabbox(); } void Workspace::addUnmanaged(Unmanaged* c) { unmanaged.append(c); markXStackingOrderAsDirty(); } /** * Destroys the client \a c */ void Workspace::removeClient(Client* c) { if (c == active_popup_client) closeActivePopup(); if (m_userActionsMenu->isMenuClient(c)) { m_userActionsMenu->close(); } if (client_keys_client == c) setupWindowShortcutDone(false); if (!c->shortcut().isEmpty()) { c->setShortcut(QString()); // Remove from client_keys clientShortcutUpdated(c); // Needed, since this is otherwise delayed by setShortcut() and wouldn't run } Q_ASSERT(clients.contains(c) || desktops.contains(c)); // TODO: if marked client is removed, notify the marked list clients.removeAll(c); m_allClients.removeAll(c); desktops.removeAll(c); markXStackingOrderAsDirty(); attention_chain.removeAll(c); Group* group = findGroup(c->window()); if (group != nullptr) group->lostLeader(); if (c == most_recently_raised) most_recently_raised = nullptr; should_get_focus.removeAll(c); Q_ASSERT(c != active_client); if (c == last_active_client) last_active_client = nullptr; if (c == delayfocus_client) cancelDelayFocus(); emit clientRemoved(c); updateStackingOrder(true); updateClientArea(); updateTabbox(); } void Workspace::removeUnmanaged(Unmanaged* c) { Q_ASSERT(unmanaged.contains(c)); unmanaged.removeAll(c); emit unmanagedRemoved(c); markXStackingOrderAsDirty(); } void Workspace::addDeleted(Deleted* c, Toplevel *orig) { Q_ASSERT(!deleted.contains(c)); deleted.append(c); const int unconstraintedIndex = unconstrained_stacking_order.indexOf(orig); if (unconstraintedIndex != -1) { unconstrained_stacking_order.replace(unconstraintedIndex, c); } else { unconstrained_stacking_order.append(c); } const int index = stacking_order.indexOf(orig); if (index != -1) { stacking_order.replace(index, c); } else { stacking_order.append(c); } markXStackingOrderAsDirty(); connect(c, &Deleted::needsRepaint, m_compositor, &Compositor::scheduleRepaint); } void Workspace::removeDeleted(Deleted* c) { Q_ASSERT(deleted.contains(c)); emit deletedRemoved(c); deleted.removeAll(c); unconstrained_stacking_order.removeAll(c); stacking_order.removeAll(c); markXStackingOrderAsDirty(); if (!c->wasClient()) { return; } if (X11Compositor *compositor = X11Compositor::self()) { compositor->updateClientCompositeBlocking(); } } void Workspace::updateToolWindows(bool also_hide) { // TODO: What if Client's transiency/group changes? should this be called too? (I'm paranoid, am I not?) if (!options->isHideUtilityWindowsForInactive()) { for (ClientList::ConstIterator it = clients.constBegin(); it != clients.constEnd(); ++it) (*it)->hideClient(false); return; } const Group* group = nullptr; auto client = active_client; // Go up in transiency hiearchy, if the top is found, only tool transients for the top mainwindow // will be shown; if a group transient is group, all tools in the group will be shown while (client != nullptr) { if (!client->isTransient()) break; if (client->groupTransient()) { group = client->group(); break; } client = client->transientFor(); } // Use stacking order only to reduce flicker, it doesn't matter if block_stacking_updates == 0, // I.e. if it's not up to date // SELI TODO: But maybe it should - what if a new client has been added that's not in stacking order yet? QVector to_show, to_hide; for (ToplevelList::ConstIterator it = stacking_order.constBegin(); it != stacking_order.constEnd(); ++it) { auto c = qobject_cast(*it); if (!c) { continue; } if (c->isUtility() || c->isMenu() || c->isToolbar()) { bool show = true; if (!c->isTransient()) { if (!c->group() || c->group()->members().count() == 1) // Has its own group, keep always visible show = true; else if (client != nullptr && c->group() == client->group()) show = true; else show = false; } else { if (group != nullptr && c->group() == group) show = true; else if (client != nullptr && client->hasTransient(c, true)) show = true; else show = false; } if (!show && also_hide) { const auto mainclients = c->mainClients(); // Don't hide utility windows which are standalone(?) or // have e.g. kicker as mainwindow if (mainclients.isEmpty()) show = true; for (auto it2 = mainclients.constBegin(); it2 != mainclients.constEnd(); ++it2) { if ((*it2)->isSpecialWindow()) show = true; } if (!show) to_hide.append(c); } if (show) to_show.append(c); } } // First show new ones, then hide for (int i = to_show.size() - 1; i >= 0; --i) // From topmost // TODO: Since this is in stacking order, the order of taskbar entries changes :( to_show.at(i)->hideClient(false); if (also_hide) { for (auto it = to_hide.constBegin(); it != to_hide.constEnd(); ++it) // From bottommost (*it)->hideClient(true); updateToolWindowsTimer.stop(); } else // setActiveClient() is after called with NULL client, quickly followed // by setting a new client, which would result in flickering resetUpdateToolWindowsTimer(); } void Workspace::resetUpdateToolWindowsTimer() { updateToolWindowsTimer.start(200); } void Workspace::slotUpdateToolWindows() { updateToolWindows(true); } void Workspace::slotReloadConfig() { reconfigure(); } void Workspace::reconfigure() { reconfigureTimer.start(200); } /** * Reread settings */ void Workspace::slotReconfigure() { qCDebug(KWIN_CORE) << "Workspace::slotReconfigure()"; reconfigureTimer.stop(); bool borderlessMaximizedWindows = options->borderlessMaximizedWindows(); kwinApp()->config()->reparseConfiguration(); options->updateSettings(); emit configChanged(); m_userActionsMenu->discard(); updateToolWindows(true); RuleBook::self()->load(); for (auto it = m_allClients.begin(); it != m_allClients.end(); ++it) { (*it)->setupWindowRules(true); (*it)->applyWindowRules(); RuleBook::self()->discardUsed(*it, false); } if (borderlessMaximizedWindows != options->borderlessMaximizedWindows() && !options->borderlessMaximizedWindows()) { // in case borderless maximized windows option changed and new option // is to have borders, we need to unset the borders for all maximized windows for (auto it = m_allClients.begin(); it != m_allClients.end(); ++it) { if ((*it)->maximizeMode() == MaximizeFull) (*it)->checkNoBorder(); } } } void Workspace::slotCurrentDesktopChanged(uint oldDesktop, uint newDesktop) { closeActivePopup(); ++block_focus; StackingUpdatesBlocker blocker(this); updateClientVisibilityOnDesktopChange(newDesktop); // Restore the focus on this desktop --block_focus; activateClientOnNewDesktop(newDesktop); emit currentDesktopChanged(oldDesktop, movingClient); } void Workspace::updateClientVisibilityOnDesktopChange(uint newDesktop) { for (ToplevelList::ConstIterator it = stacking_order.constBegin(); it != stacking_order.constEnd(); ++it) { Client *c = qobject_cast(*it); if (!c) { continue; } if (!c->isOnDesktop(newDesktop) && c != movingClient && c->isOnCurrentActivity()) { (c)->updateVisibility(); } } // Now propagate the change, after hiding, before showing if (rootInfo()) { rootInfo()->setCurrentDesktop(VirtualDesktopManager::self()->current()); } if (movingClient && !movingClient->isOnDesktop(newDesktop)) { movingClient->setDesktop(newDesktop); } for (int i = stacking_order.size() - 1; i >= 0 ; --i) { Client *c = qobject_cast(stacking_order.at(i)); if (!c) { continue; } if (c->isOnDesktop(newDesktop) && c->isOnCurrentActivity()) c->updateVisibility(); } if (showingDesktop()) // Do this only after desktop change to avoid flicker setShowingDesktop(false); } void Workspace::activateClientOnNewDesktop(uint desktop) { AbstractClient* c = nullptr; if (options->focusPolicyIsReasonable()) { c = findClientToActivateOnDesktop(desktop); } // If "unreasonable focus policy" and active_client is on_all_desktops and // under mouse (Hence == old_active_client), conserve focus. // (Thanks to Volker Schatz ) else if (active_client && active_client->isShown(true) && active_client->isOnCurrentDesktop()) c = active_client; if (c == nullptr && !desktops.isEmpty()) c = findDesktop(true, desktop); if (c != active_client) setActiveClient(nullptr); if (c) requestFocus(c); else if (!desktops.isEmpty()) requestFocus(findDesktop(true, desktop)); else focusToNull(); } AbstractClient *Workspace::findClientToActivateOnDesktop(uint desktop) { if (movingClient != nullptr && active_client == movingClient && FocusChain::self()->contains(active_client, desktop) && active_client->isShown(true) && active_client->isOnCurrentDesktop()) { // A requestFocus call will fail, as the client is already active return active_client; } // from actiavtion.cpp if (options->isNextFocusPrefersMouse()) { ToplevelList::const_iterator it = stackingOrder().constEnd(); while (it != stackingOrder().constBegin()) { Client *client = qobject_cast(*(--it)); if (!client) { continue; } if (!(client->isShown(false) && client->isOnDesktop(desktop) && client->isOnCurrentActivity() && client->isOnActiveScreen())) continue; if (client->geometry().contains(Cursor::pos())) { if (!client->isDesktop()) return client; break; // unconditional break - we do not pass the focus to some client below an unusable one } } } return FocusChain::self()->getForActivation(desktop); } /** * Updates the current activity when it changes * do *not* call this directly; it does not set the activity. * * Shows/Hides windows according to the stacking order */ void Workspace::updateCurrentActivity(const QString &new_activity) { #ifdef KWIN_BUILD_ACTIVITIES if (!Activities::self()) { return; } //closeActivePopup(); ++block_focus; // TODO: Q_ASSERT( block_stacking_updates == 0 ); // Make sure stacking_order is up to date StackingUpdatesBlocker blocker(this); // Optimized Desktop switching: unmapping done from back to front // mapping done from front to back => less exposure events //Notify::raise((Notify::Event) (Notify::DesktopChange+new_desktop)); for (ToplevelList::ConstIterator it = stacking_order.constBegin(); it != stacking_order.constEnd(); ++it) { Client *c = qobject_cast(*it); if (!c) { continue; } if (!c->isOnActivity(new_activity) && c != movingClient && c->isOnCurrentDesktop()) { c->updateVisibility(); } } // Now propagate the change, after hiding, before showing //rootInfo->setCurrentDesktop( currentDesktop() ); /* TODO someday enable dragging windows to other activities if ( movingClient && !movingClient->isOnDesktop( new_desktop )) { movingClient->setDesktop( new_desktop ); */ for (int i = stacking_order.size() - 1; i >= 0 ; --i) { Client *c = qobject_cast(stacking_order.at(i)); if (!c) { continue; } if (c->isOnActivity(new_activity)) c->updateVisibility(); } //FIXME not sure if I should do this either if (showingDesktop()) // Do this only after desktop change to avoid flicker setShowingDesktop(false); // Restore the focus on this desktop --block_focus; AbstractClient* c = nullptr; //FIXME below here is a lot of focuschain stuff, probably all wrong now if (options->focusPolicyIsReasonable()) { // Search in focus chain c = FocusChain::self()->getForActivation(VirtualDesktopManager::self()->current()); } // If "unreasonable focus policy" and active_client is on_all_desktops and // under mouse (Hence == old_active_client), conserve focus. // (Thanks to Volker Schatz ) else if (active_client && active_client->isShown(true) && active_client->isOnCurrentDesktop() && active_client->isOnCurrentActivity()) c = active_client; if (c == nullptr && !desktops.isEmpty()) c = findDesktop(true, VirtualDesktopManager::self()->current()); if (c != active_client) setActiveClient(nullptr); if (c) requestFocus(c); else if (!desktops.isEmpty()) requestFocus(findDesktop(true, VirtualDesktopManager::self()->current())); else focusToNull(); // Not for the very first time, only if something changed and there are more than 1 desktops //if ( effects != NULL && old_desktop != 0 && old_desktop != new_desktop ) // static_cast( effects )->desktopChanged( old_desktop ); if (compositing() && m_compositor) m_compositor->addRepaintFull(); #else Q_UNUSED(new_activity) #endif } void Workspace::slotDesktopCountChanged(uint previousCount, uint newCount) { Q_UNUSED(previousCount) Placement::self()->reinitCascading(0); resetClientAreas(newCount); } void Workspace::resetClientAreas(uint desktopCount) { // Make it +1, so that it can be accessed as [1..numberofdesktops] workarea.clear(); workarea.resize(desktopCount + 1); restrictedmovearea.clear(); restrictedmovearea.resize(desktopCount + 1); screenarea.clear(); updateClientArea(true); } void Workspace::selectWmInputEventMask() { uint32_t presentMask = 0; Xcb::WindowAttributes attr(rootWindow()); if (!attr.isNull()) { presentMask = attr->your_event_mask; } Xcb::selectInput(rootWindow(), presentMask | XCB_EVENT_MASK_KEY_PRESS | XCB_EVENT_MASK_PROPERTY_CHANGE | XCB_EVENT_MASK_COLOR_MAP_CHANGE | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT | XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY | XCB_EVENT_MASK_FOCUS_CHANGE | // For NotifyDetailNone XCB_EVENT_MASK_EXPOSURE ); } /** * Sends client \a c to desktop \a desk. * * Takes care of transients as well. */ void Workspace::sendClientToDesktop(AbstractClient* c, int desk, bool dont_activate) { if ((desk < 1 && desk != NET::OnAllDesktops) || desk > static_cast(VirtualDesktopManager::self()->count())) return; int old_desktop = c->desktop(); bool was_on_desktop = c->isOnDesktop(desk) || c->isOnAllDesktops(); c->setDesktop(desk); if (c->desktop() != desk) // No change or desktop forced return; desk = c->desktop(); // Client did range checking if (c->isOnDesktop(VirtualDesktopManager::self()->current())) { if (c->wantsTabFocus() && options->focusPolicyIsReasonable() && !was_on_desktop && // for stickyness changes !dont_activate) requestFocus(c); else restackClientUnderActive(c); } else raiseClient(c); c->checkWorkspacePosition( QRect(), old_desktop ); auto transients_stacking_order = ensureStackingOrder(c->transients()); for (auto it = transients_stacking_order.constBegin(); it != transients_stacking_order.constEnd(); ++it) sendClientToDesktop(*it, desk, dont_activate); updateClientArea(); } /** * checks whether the X Window with the input focus is on our X11 screen * if the window cannot be determined or inspected, resturn depends on whether there's actually * more than one screen * * this is NOT in any way related to XRandR multiscreen * */ extern bool is_multihead; // main.cpp bool Workspace::isOnCurrentHead() { if (!is_multihead) { return true; } Xcb::CurrentInput currentInput; if (currentInput.window() == XCB_WINDOW_NONE) { return !is_multihead; } Xcb::WindowGeometry geometry(currentInput.window()); if (geometry.isNull()) { // should not happen return !is_multihead; } return rootWindow() == geometry->root; } void Workspace::sendClientToScreen(AbstractClient* c, int screen) { c->sendToScreen(screen); } void Workspace::sendPingToWindow(xcb_window_t window, xcb_timestamp_t timestamp) { if (rootInfo()) { rootInfo()->sendPing(window, timestamp); } } /** * Delayed focus functions */ void Workspace::delayFocus() { requestFocus(delayfocus_client); cancelDelayFocus(); } void Workspace::requestDelayFocus(AbstractClient* c) { delayfocus_client = c; delete delayFocusTimer; delayFocusTimer = new QTimer(this); connect(delayFocusTimer, SIGNAL(timeout()), this, SLOT(delayFocus())); delayFocusTimer->setSingleShot(true); delayFocusTimer->start(options->delayFocusInterval()); } void Workspace::cancelDelayFocus() { delete delayFocusTimer; delayFocusTimer = nullptr; } bool Workspace::checkStartupNotification(xcb_window_t w, KStartupInfoId &id, KStartupInfoData &data) { return startup->checkStartup(w, id, data) == KStartupInfo::Match; } /** * Puts the focus on a dummy window * Just using XSetInputFocus() with None would block keyboard input */ void Workspace::focusToNull() { if (m_nullFocus) { m_nullFocus->focus(); } } void Workspace::setShowingDesktop(bool showing) { const bool changed = showing != showing_desktop; if (rootInfo() && changed) { rootInfo()->setShowingDesktop(showing); } showing_desktop = showing; AbstractClient *topDesk = nullptr; { // for the blocker RAII StackingUpdatesBlocker blocker(this); // updateLayer & lowerClient would invalidate stacking_order for (int i = stacking_order.count() - 1; i > -1; --i) { AbstractClient *c = qobject_cast(stacking_order.at(i)); if (c && c->isOnCurrentDesktop()) { if (c->isDock()) { c->updateLayer(); } else if (c->isDesktop() && c->isShown(true)) { c->updateLayer(); lowerClient(c); if (!topDesk) topDesk = c; if (auto group = c->group()) { foreach (Client *cm, group->members()) { cm->updateLayer(); } } } } } } // ~StackingUpdatesBlocker if (showing_desktop && topDesk) { requestFocus(topDesk); } else if (!showing_desktop && changed) { const auto client = FocusChain::self()->getForActivation(VirtualDesktopManager::self()->current()); if (client) { activateClient(client); } } if (changed) emit showingDesktopChanged(showing); } void Workspace::disableGlobalShortcutsForClient(bool disable) { if (global_shortcuts_disabled_for_client == disable) return; QDBusMessage message = QDBusMessage::createMethodCall(QStringLiteral("org.kde.kglobalaccel"), QStringLiteral("/kglobalaccel"), QStringLiteral("org.kde.KGlobalAccel"), QStringLiteral("blockGlobalShortcuts")); message.setArguments(QList() << disable); QDBusConnection::sessionBus().asyncCall(message); global_shortcuts_disabled_for_client = disable; // Update also Alt+LMB actions etc. for (ClientList::ConstIterator it = clients.constBegin(); it != clients.constEnd(); ++it) (*it)->updateMouseGrab(); } QString Workspace::supportInformation() const { QString support; const QString yes = QStringLiteral("yes\n"); const QString no = QStringLiteral("no\n"); support.append(ki18nc("Introductory text shown in the support information.", "KWin Support Information:\n" "The following information should be used when requesting support on e.g. https://forum.kde.org.\n" "It provides information about the currently running instance, which options are used,\n" "what OpenGL driver and which effects are running.\n" "Please post the information provided underneath this introductory text to a paste bin service\n" "like https://paste.kde.org instead of pasting into support threads.\n").toString()); support.append(QStringLiteral("\n==========================\n\n")); // all following strings are intended for support. They need to be pasted to e.g forums.kde.org // it is expected that the support will happen in English language or that the people providing // help understand English. Because of that all texts are not translated support.append(QStringLiteral("Version\n")); support.append(QStringLiteral("=======\n")); support.append(QStringLiteral("KWin version: ")); support.append(QStringLiteral(KWIN_VERSION_STRING)); support.append(QStringLiteral("\n")); support.append(QStringLiteral("Qt Version: ")); support.append(QString::fromUtf8(qVersion())); support.append(QStringLiteral("\n")); support.append(QStringLiteral("Qt compile version: %1\n").arg(QStringLiteral(QT_VERSION_STR))); support.append(QStringLiteral("XCB compile version: %1\n\n").arg(QStringLiteral(XCB_VERSION_STRING))); support.append(QStringLiteral("Operation Mode: ")); switch (kwinApp()->operationMode()) { case Application::OperationModeX11: support.append(QStringLiteral("X11 only")); break; case Application::OperationModeWaylandOnly: support.append(QStringLiteral("Wayland Only")); break; case Application::OperationModeXwayland: support.append(QStringLiteral("Xwayland")); break; } support.append(QStringLiteral("\n\n")); support.append(QStringLiteral("Build Options\n")); support.append(QStringLiteral("=============\n")); support.append(QStringLiteral("KWIN_BUILD_DECORATIONS: ")); #ifdef KWIN_BUILD_DECORATIONS support.append(yes); #else support.append(no); #endif support.append(QStringLiteral("KWIN_BUILD_TABBOX: ")); #ifdef KWIN_BUILD_TABBOX support.append(yes); #else support.append(no); #endif support.append(QStringLiteral("KWIN_BUILD_ACTIVITIES: ")); #ifdef KWIN_BUILD_ACTIVITIES support.append(yes); #else support.append(no); #endif support.append(QStringLiteral("HAVE_DRM: ")); #if HAVE_DRM support.append(yes); #else support.append(no); #endif support.append(QStringLiteral("HAVE_GBM: ")); #if HAVE_GBM support.append(yes); #else support.append(no); #endif support.append(QStringLiteral("HAVE_EGL_STREAMS: ")); #if HAVE_EGL_STREAMS support.append(yes); #else support.append(no); #endif support.append(QStringLiteral("HAVE_X11_XCB: ")); #if HAVE_X11_XCB support.append(yes); #else support.append(no); #endif support.append(QStringLiteral("HAVE_EPOXY_GLX: ")); #if HAVE_EPOXY_GLX support.append(yes); #else support.append(no); #endif support.append(QStringLiteral("HAVE_WAYLAND_EGL: ")); #if HAVE_WAYLAND_EGL support.append(yes); #else support.append(no); #endif support.append(QStringLiteral("\n")); if (auto c = kwinApp()->x11Connection()) { support.append(QStringLiteral("X11\n")); support.append(QStringLiteral("===\n")); auto x11setup = xcb_get_setup(c); support.append(QStringLiteral("Vendor: %1\n").arg(QString::fromUtf8(QByteArray::fromRawData(xcb_setup_vendor(x11setup), xcb_setup_vendor_length(x11setup))))); support.append(QStringLiteral("Vendor Release: %1\n").arg(x11setup->release_number)); support.append(QStringLiteral("Protocol Version/Revision: %1/%2\n").arg(x11setup->protocol_major_version).arg(x11setup->protocol_minor_version)); const auto extensions = Xcb::Extensions::self()->extensions(); for (const auto &e : extensions) { support.append(QStringLiteral("%1: %2; Version: 0x%3\n").arg(QString::fromUtf8(e.name)) .arg(e.present ? yes.trimmed() : no.trimmed()) .arg(QString::number(e.version, 16))); } support.append(QStringLiteral("\n")); } if (auto bridge = Decoration::DecorationBridge::self()) { support.append(QStringLiteral("Decoration\n")); support.append(QStringLiteral("==========\n")); support.append(bridge->supportInformation()); support.append(QStringLiteral("\n")); } support.append(QStringLiteral("Platform\n")); support.append(QStringLiteral("==========\n")); support.append(kwinApp()->platform()->supportInformation()); support.append(QStringLiteral("\n")); support.append(QStringLiteral("Options\n")); support.append(QStringLiteral("=======\n")); const QMetaObject *metaOptions = options->metaObject(); auto printProperty = [] (const QVariant &variant) { if (variant.type() == QVariant::Size) { const QSize &s = variant.toSize(); return QStringLiteral("%1x%2").arg(QString::number(s.width())).arg(QString::number(s.height())); } if (QLatin1String(variant.typeName()) == QLatin1String("KWin::OpenGLPlatformInterface") || QLatin1String(variant.typeName()) == QLatin1String("KWin::Options::WindowOperation")) { return QString::number(variant.toInt()); } return variant.toString(); }; for (int i=0; ipropertyCount(); ++i) { const QMetaProperty property = metaOptions->property(i); if (QLatin1String(property.name()) == QLatin1String("objectName")) { continue; } support.append(QStringLiteral("%1: %2\n").arg(property.name()).arg(printProperty(options->property(property.name())))); } support.append(QStringLiteral("\nScreen Edges\n")); support.append(QStringLiteral( "============\n")); const QMetaObject *metaScreenEdges = ScreenEdges::self()->metaObject(); for (int i=0; ipropertyCount(); ++i) { const QMetaProperty property = metaScreenEdges->property(i); if (QLatin1String(property.name()) == QLatin1String("objectName")) { continue; } support.append(QStringLiteral("%1: %2\n").arg(property.name()).arg(printProperty(ScreenEdges::self()->property(property.name())))); } support.append(QStringLiteral("\nScreens\n")); support.append(QStringLiteral( "=======\n")); support.append(QStringLiteral("Multi-Head: ")); if (is_multihead) { support.append(QStringLiteral("yes\n")); support.append(QStringLiteral("Head: %1\n").arg(screen_number)); } else { support.append(QStringLiteral("no\n")); } support.append(QStringLiteral("Active screen follows mouse: ")); if (screens()->isCurrentFollowsMouse()) support.append(QStringLiteral(" yes\n")); else support.append(QStringLiteral(" no\n")); support.append(QStringLiteral("Number of Screens: %1\n\n").arg(screens()->count())); for (int i=0; icount(); ++i) { const QRect geo = screens()->geometry(i); support.append(QStringLiteral("Screen %1:\n").arg(i)); support.append(QStringLiteral("---------\n")); support.append(QStringLiteral("Name: %1\n").arg(screens()->name(i))); support.append(QStringLiteral("Geometry: %1,%2,%3x%4\n") .arg(geo.x()) .arg(geo.y()) .arg(geo.width()) .arg(geo.height())); support.append(QStringLiteral("Scale: %1\n").arg(screens()->scale(i))); support.append(QStringLiteral("Refresh Rate: %1\n\n").arg(screens()->refreshRate(i))); } support.append(QStringLiteral("\nCompositing\n")); support.append(QStringLiteral( "===========\n")); if (effects) { support.append(QStringLiteral("Compositing is active\n")); switch (effects->compositingType()) { case OpenGL2Compositing: case OpenGLCompositing: { GLPlatform *platform = GLPlatform::instance(); if (platform->isGLES()) { support.append(QStringLiteral("Compositing Type: OpenGL ES 2.0\n")); } else { support.append(QStringLiteral("Compositing Type: OpenGL\n")); } support.append(QStringLiteral("OpenGL vendor string: ") + QString::fromUtf8(platform->glVendorString()) + QStringLiteral("\n")); support.append(QStringLiteral("OpenGL renderer string: ") + QString::fromUtf8(platform->glRendererString()) + QStringLiteral("\n")); support.append(QStringLiteral("OpenGL version string: ") + QString::fromUtf8(platform->glVersionString()) + QStringLiteral("\n")); support.append(QStringLiteral("OpenGL platform interface: ")); switch (platform->platformInterface()) { case GlxPlatformInterface: support.append(QStringLiteral("GLX")); break; case EglPlatformInterface: support.append(QStringLiteral("EGL")); break; default: support.append(QStringLiteral("UNKNOWN")); } support.append(QStringLiteral("\n")); if (platform->supports(LimitedGLSL) || platform->supports(GLSL)) support.append(QStringLiteral("OpenGL shading language version string: ") + QString::fromUtf8(platform->glShadingLanguageVersionString()) + QStringLiteral("\n")); support.append(QStringLiteral("Driver: ") + GLPlatform::driverToString(platform->driver()) + QStringLiteral("\n")); if (!platform->isMesaDriver()) support.append(QStringLiteral("Driver version: ") + GLPlatform::versionToString(platform->driverVersion()) + QStringLiteral("\n")); support.append(QStringLiteral("GPU class: ") + GLPlatform::chipClassToString(platform->chipClass()) + QStringLiteral("\n")); support.append(QStringLiteral("OpenGL version: ") + GLPlatform::versionToString(platform->glVersion()) + QStringLiteral("\n")); if (platform->supports(LimitedGLSL) || platform->supports(GLSL)) support.append(QStringLiteral("GLSL version: ") + GLPlatform::versionToString(platform->glslVersion()) + QStringLiteral("\n")); if (platform->isMesaDriver()) support.append(QStringLiteral("Mesa version: ") + GLPlatform::versionToString(platform->mesaVersion()) + QStringLiteral("\n")); if (platform->serverVersion() > 0) support.append(QStringLiteral("X server version: ") + GLPlatform::versionToString(platform->serverVersion()) + QStringLiteral("\n")); if (platform->kernelVersion() > 0) support.append(QStringLiteral("Linux kernel version: ") + GLPlatform::versionToString(platform->kernelVersion()) + QStringLiteral("\n")); support.append(QStringLiteral("Direct rendering: ")); support.append(QStringLiteral("Requires strict binding: ")); if (!platform->isLooseBinding()) { support.append(QStringLiteral("yes\n")); } else { support.append(QStringLiteral("no\n")); } support.append(QStringLiteral("GLSL shaders: ")); if (platform->supports(GLSL)) { if (platform->supports(LimitedGLSL)) { support.append(QStringLiteral(" limited\n")); } else { support.append(QStringLiteral(" yes\n")); } } else { support.append(QStringLiteral(" no\n")); } support.append(QStringLiteral("Texture NPOT support: ")); if (platform->supports(TextureNPOT)) { if (platform->supports(LimitedNPOT)) { support.append(QStringLiteral(" limited\n")); } else { support.append(QStringLiteral(" yes\n")); } } else { support.append(QStringLiteral(" no\n")); } support.append(QStringLiteral("Virtual Machine: ")); if (platform->isVirtualMachine()) { support.append(QStringLiteral(" yes\n")); } else { support.append(QStringLiteral(" no\n")); } support.append(QStringLiteral("OpenGL 2 Shaders are used\n")); support.append(QStringLiteral("Painting blocks for vertical retrace: ")); if (m_compositor->scene()->blocksForRetrace()) support.append(QStringLiteral(" yes\n")); else support.append(QStringLiteral(" no\n")); break; } case XRenderCompositing: support.append(QStringLiteral("Compositing Type: XRender\n")); break; case QPainterCompositing: support.append("Compositing Type: QPainter\n"); break; case NoCompositing: default: support.append(QStringLiteral("Something is really broken, neither OpenGL nor XRender is used")); } support.append(QStringLiteral("\nLoaded Effects:\n")); support.append(QStringLiteral( "---------------\n")); foreach (const QString &effect, static_cast(effects)->loadedEffects()) { support.append(effect + QStringLiteral("\n")); } support.append(QStringLiteral("\nCurrently Active Effects:\n")); support.append(QStringLiteral( "-------------------------\n")); foreach (const QString &effect, static_cast(effects)->activeEffects()) { support.append(effect + QStringLiteral("\n")); } support.append(QStringLiteral("\nEffect Settings:\n")); support.append(QStringLiteral( "----------------\n")); foreach (const QString &effect, static_cast(effects)->loadedEffects()) { support.append(static_cast(effects)->supportInformation(effect)); support.append(QStringLiteral("\n")); } } else { support.append(QStringLiteral("Compositing is not active\n")); } return support; } Client *Workspace::findClient(std::function func) const { if (Client *ret = Toplevel::findInList(clients, func)) { return ret; } if (Client *ret = Toplevel::findInList(desktops, func)) { return ret; } return nullptr; } AbstractClient *Workspace::findAbstractClient(std::function func) const { if (AbstractClient *ret = Toplevel::findInList(m_allClients, func)) { return ret; } if (Client *ret = Toplevel::findInList(desktops, func)) { return ret; } - if (waylandServer()) { - if (AbstractClient *ret = Toplevel::findInList(waylandServer()->internalClients(), func)) { - return ret; - } + if (InternalClient *ret = Toplevel::findInList(m_internalClients, func)) { + return ret; } return nullptr; } Unmanaged *Workspace::findUnmanaged(std::function func) const { return Toplevel::findInList(unmanaged, func); } Unmanaged *Workspace::findUnmanaged(xcb_window_t w) const { return findUnmanaged([w](const Unmanaged *u) { return u->window() == w; }); } Client *Workspace::findClient(Predicate predicate, xcb_window_t w) const { switch (predicate) { case Predicate::WindowMatch: return findClient([w](const Client *c) { return c->window() == w; }); case Predicate::WrapperIdMatch: return findClient([w](const Client *c) { return c->wrapperId() == w; }); case Predicate::FrameIdMatch: return findClient([w](const Client *c) { return c->frameId() == w; }); case Predicate::InputIdMatch: return findClient([w](const Client *c) { return c->inputId() == w; }); } return nullptr; } Toplevel *Workspace::findToplevel(std::function func) const { if (Client *ret = Toplevel::findInList(clients, func)) { return ret; } if (Client *ret = Toplevel::findInList(desktops, func)) { return ret; } if (Unmanaged *ret = Toplevel::findInList(unmanaged, func)) { return ret; } - return nullptr; -} - -Toplevel *Workspace::findToplevel(QWindow *w) const -{ - if (!w) { - return nullptr; - } - if (waylandServer()) { - if (auto c = waylandServer()->findClient(w)) { - return c; - } + if (InternalClient *ret = Toplevel::findInList(m_internalClients, func)) { + return ret; } - return findUnmanaged(w->winId()); + return nullptr; } bool Workspace::hasClient(const AbstractClient *c) { if (auto cc = dynamic_cast(c)) { return hasClient(cc); } else { return findAbstractClient([c](const AbstractClient *test) { return test == c; }) != nullptr; } return false; } void Workspace::forEachAbstractClient(std::function< void (AbstractClient*) > func) { std::for_each(m_allClients.constBegin(), m_allClients.constEnd(), func); std::for_each(desktops.constBegin(), desktops.constEnd(), func); + std::for_each(m_internalClients.constBegin(), m_internalClients.constEnd(), func); } Toplevel *Workspace::findInternal(QWindow *w) const { if (!w) { return nullptr; } if (kwinApp()->operationMode() == Application::OperationModeX11) { return findUnmanaged(w->winId()); - } else { - return waylandServer()->findClient(w); } + for (InternalClient *client : m_internalClients) { + if (client->internalWindow() == w) { + return client; + } + } + return nullptr; } bool Workspace::compositing() const { return m_compositor && m_compositor->scene(); } void Workspace::markXStackingOrderAsDirty() { m_xStackingDirty = true; if (kwinApp()->x11Connection()) { m_xStackingQueryTree.reset(new Xcb::Tree(kwinApp()->x11RootWindow())); } } void Workspace::setWasUserInteraction() { if (was_user_interaction) { return; } was_user_interaction = true; // might be called from within the filter, so delay till we now the filter returned QTimer::singleShot(0, this, [this] { m_wasUserInteractionFilter.reset(); } ); } void Workspace::updateTabbox() { #ifdef KWIN_BUILD_TABBOX TabBox::TabBox *tabBox = TabBox::TabBox::self(); if (tabBox && tabBox->isDisplayed()) { tabBox->reset(true); } #endif } -} // namespace +void Workspace::addInternalClient(InternalClient *client) +{ + m_internalClients.append(client); + + setupClientConnections(client); + client->updateLayer(); + + if (client->isDecorated()) { + client->keepInArea(clientArea(FullScreenArea, client)); + } + markXStackingOrderAsDirty(); + updateStackingOrder(true); + updateClientArea(); + + emit internalClientAdded(client); +} + +void Workspace::removeInternalClient(InternalClient *client) +{ + m_internalClients.removeOne(client); + + markXStackingOrderAsDirty(); + updateStackingOrder(true); + updateClientArea(); + + emit internalClientRemoved(client); +} + +} // namespace diff --git a/workspace.h b/workspace.h index 12a3b15e1..dc0bd941d 100644 --- a/workspace.h +++ b/workspace.h @@ -1,774 +1,809 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 1999, 2000 Matthias Ettrich Copyright (C) 2003 Lubos Lunak Copyright (C) 2009 Lucas Murray +Copyright (C) 2019 Vlad Zagorodniy 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 . *********************************************************************/ #ifndef KWIN_WORKSPACE_H #define KWIN_WORKSPACE_H // kwin #include "options.h" #include "sm.h" #include "utils.h" // Qt #include #include // std #include #include class KConfig; class KConfigGroup; class KStartupInfo; class KStartupInfoData; class KStartupInfoId; class QStringList; namespace KWin { namespace Xcb { class Tree; class Window; } class AbstractClient; class Client; class Compositor; +class InternalClient; class KillWindow; class ShortcutDialog; class UserActionsMenu; class X11EventFilter; enum class Predicate; class KWIN_EXPORT Workspace : public QObject { Q_OBJECT public: explicit Workspace(const QString &sessionKey = QString()); ~Workspace() override; static Workspace* self() { return _self; } bool workspaceEvent(xcb_generic_event_t*); bool workspaceEvent(QEvent*); bool hasClient(const Client*); bool hasClient(const AbstractClient*); /** * @brief Finds the first Client matching the condition expressed by passed in @p func. * * Internally findClient uses the std::find_if algorithm and that determines how the function * needs to be implemented. An example usage for finding a Client with a matching windowId * @code * xcb_window_t w; // our test window * Client *client = findClient([w](const Client *c) -> bool { * return c->window() == w; * }); * @endcode * * For the standard cases of matching the window id with one of the Client's windows use * the simplified overload method findClient(Predicate, xcb_window_t). Above example * can be simplified to: * @code * xcb_window_t w; // our test window * Client *client = findClient(Predicate::WindowMatch, w); * @endcode * * @param func Unary function that accepts a Client* as argument and * returns a value convertible to bool. The value returned indicates whether the * Client* is considered a match in the context of this function. * The function shall not modify its argument. * This can either be a function pointer or a function object. * @return KWin::Client* The found Client or @c null * @see findClient(Predicate, xcb_window_t) */ Client *findClient(std::function func) const; AbstractClient *findAbstractClient(std::function func) const; /** * @brief Finds the Client matching the given match @p predicate for the given window. * * @param predicate Which window should be compared * @param w The window id to test against * @return KWin::Client* The found Client or @c null * @see findClient(std::function) */ Client *findClient(Predicate predicate, xcb_window_t w) const; void forEachClient(std::function func); void forEachAbstractClient(std::function func); Unmanaged *findUnmanaged(std::function func) const; /** * @brief Finds the Unmanaged with the given window id. * * @param w The window id to search for * @return KWin::Unmanaged* Found Unmanaged or @c null if there is no Unmanaged with given Id. */ Unmanaged *findUnmanaged(xcb_window_t w) const; void forEachUnmanaged(std::function func); Toplevel *findToplevel(std::function func) const; - /** - * Finds the Toplevel for the KWin internal window @p w. - * On Wayland this is normally a ShellClient. For X11 an Unmanaged. - */ - Toplevel *findToplevel(QWindow *w) const; /** * @brief Finds a Toplevel for the internal window @p w. * * Internal window means a window created by KWin itself. On X11 this is an Unmanaged * and mapped by the window id, on Wayland a ShellClient mapped on the internal window id. * * @returns Toplevel */ Toplevel *findInternal(QWindow *w) const; QRect clientArea(clientAreaOption, const QPoint& p, int desktop) const; QRect clientArea(clientAreaOption, const AbstractClient* c) const; QRect clientArea(clientAreaOption, int screen, int desktop) const; QRegion restrictedMoveArea(int desktop, StrutAreas areas = StrutAreaAll) const; bool initializing() const; /** * Returns the active client, i.e. the client that has the focus (or None * if no client has the focus) */ AbstractClient* activeClient() const; /** * Client that was activated, but it's not yet really activeClient(), because * we didn't process yet the matching FocusIn event. Used mostly in focus * stealing prevention code. */ AbstractClient* mostRecentlyActivatedClient() const; AbstractClient* clientUnderMouse(int screen) const; void activateClient(AbstractClient*, bool force = false); void requestFocus(AbstractClient* c, bool force = false); enum ActivityFlag { ActivityFocus = 1 << 0, // focus the window ActivityFocusForce = 1 << 1 | ActivityFocus, // focus even if Dock etc. ActivityRaise = 1 << 2 // raise the window }; Q_DECLARE_FLAGS(ActivityFlags, ActivityFlag) void takeActivity(AbstractClient* c, ActivityFlags flags); bool allowClientActivation(const AbstractClient* c, xcb_timestamp_t time = -1U, bool focus_in = false, bool ignore_desktop = false); void restoreFocus(); void gotFocusIn(const AbstractClient*); void setShouldGetFocus(AbstractClient*); bool activateNextClient(AbstractClient* c); bool focusChangeEnabled() { return block_focus == 0; } /** * Indicates that the client c is being moved or resized by the user. */ void setMoveResizeClient(AbstractClient* c); QPoint adjustClientPosition(AbstractClient* c, QPoint pos, bool unrestricted, double snapAdjust = 1.0); QRect adjustClientSize(AbstractClient* c, QRect moveResizeGeom, int mode); void raiseClient(AbstractClient* c, bool nogroup = false); void lowerClient(AbstractClient* c, bool nogroup = false); void raiseClientRequest(AbstractClient* c, NET::RequestSource src = NET::FromApplication, xcb_timestamp_t timestamp = 0); void lowerClientRequest(Client* c, NET::RequestSource src, xcb_timestamp_t timestamp); void lowerClientRequest(AbstractClient* c); void restackClientUnderActive(AbstractClient*); void restack(AbstractClient *c, AbstractClient *under, bool force = false); void updateClientLayer(AbstractClient* c); void raiseOrLowerClient(AbstractClient*); void resetUpdateToolWindowsTimer(); void restoreSessionStackingOrder(Client* c); void updateStackingOrder(bool propagate_new_clients = false); void forceRestacking(); void clientHidden(AbstractClient*); void clientAttentionChanged(AbstractClient* c, bool set); /** * @return List of clients currently managed by Workspace */ const ClientList &clientList() const { return clients; } /** * @return List of unmanaged "clients" currently registered in Workspace */ const UnmanagedList &unmanagedList() const { return unmanaged; } /** * @return List of desktop "clients" currently managed by Workspace */ const ClientList &desktopList() const { return desktops; } /** * @return List of deleted "clients" currently managed by Workspace */ const DeletedList &deletedList() const { return deleted; } /** * @returns List of all clients (either X11 or Wayland) currently managed by Workspace */ const QList allClientList() const { return m_allClients; } + /** + * @returns List of all internal clients currently managed by Workspace + */ + const QList &internalClients() const { + return m_internalClients; + } + void stackScreenEdgesUnderOverrideRedirect(); public: QPoint cascadeOffset(const AbstractClient *c) const; private: Compositor *m_compositor; //------------------------------------------------- // Unsorted public: bool isOnCurrentHead(); // True when performing Workspace::updateClientArea(). // The calls below are valid only in that case. bool inUpdateClientArea() const; QRegion previousRestrictedMoveArea(int desktop, StrutAreas areas = StrutAreaAll) const; QVector< QRect > previousScreenSizes() const; int oldDisplayWidth() const; int oldDisplayHeight() const; /** * Returns the list of clients sorted in stacking order, with topmost client * at the last position */ const ToplevelList& stackingOrder() const; ToplevelList xStackingOrder() const; ClientList ensureStackingOrder(const ClientList& clients) const; QList ensureStackingOrder(const QList &clients) const; AbstractClient* topClientOnDesktop(int desktop, int screen, bool unconstrained = false, bool only_normal = true) const; AbstractClient* findDesktop(bool topmost, int desktop) const; void sendClientToDesktop(AbstractClient* c, int desktop, bool dont_activate); void windowToPreviousDesktop(AbstractClient* c); void windowToNextDesktop(AbstractClient* c); void sendClientToScreen(AbstractClient* c, int screen); void addManualOverlay(xcb_window_t id) { manual_overlays << id; } void removeManualOverlay(xcb_window_t id) { manual_overlays.removeOne(id); } /** * Shows the menu operations menu for the client and makes it active if * it's not already. */ void showWindowMenu(const QRect& pos, AbstractClient* cl); const UserActionsMenu *userActionsMenu() const { return m_userActionsMenu; } void showApplicationMenu(const QRect &pos, AbstractClient *c, int actionId); void updateMinimizedOfTransients(AbstractClient*); void updateOnAllDesktopsOfTransients(AbstractClient*); void checkTransients(xcb_window_t w); void storeSession(KConfig* config, SMSavePhase phase); void storeClient(KConfigGroup &cg, int num, Client *c); void storeSubSession(const QString &name, QSet sessionIds); void loadSubSessionInfo(const QString &name); SessionInfo* takeSessionInfo(Client*); // D-Bus interface QString supportInformation() const; void setCurrentScreen(int new_screen); void setShowingDesktop(bool showing); bool showingDesktop() const; void sendPingToWindow(xcb_window_t w, xcb_timestamp_t timestamp); // Called from Client::pingWindow() void removeClient(Client*); // Only called from Client::destroyClient() or Client::releaseWindow() void setActiveClient(AbstractClient*); Group* findGroup(xcb_window_t leader) const; void addGroup(Group* group); void removeGroup(Group* group); Group* findClientLeaderGroup(const Client* c) const; void removeUnmanaged(Unmanaged*); // Only called from Unmanaged::release() void removeDeleted(Deleted*); void addDeleted(Deleted*, Toplevel*); bool checkStartupNotification(xcb_window_t w, KStartupInfoId& id, KStartupInfoData& data); void focusToNull(); // SELI TODO: Public? void clientShortcutUpdated(AbstractClient* c); bool shortcutAvailable(const QKeySequence &cut, AbstractClient* ignore = nullptr) const; bool globalShortcutsDisabled() const; void disableGlobalShortcutsForClient(bool disable); void sessionSaveStarted(); void sessionSaveDone(); void setWasUserInteraction(); bool wasUserInteraction() const; bool sessionSaving() const; int packPositionLeft(const AbstractClient* cl, int oldx, bool left_edge) const; int packPositionRight(const AbstractClient* cl, int oldx, bool right_edge) const; int packPositionUp(const AbstractClient* cl, int oldy, bool top_edge) const; int packPositionDown(const AbstractClient* cl, int oldy, bool bottom_edge) const; void cancelDelayFocus(); void requestDelayFocus(AbstractClient*); /** * updates the mouse position to track whether a focus follow mouse focus change was caused by * an actual mouse move * is esp. called on enter/motion events of inactive windows * since an active window doesn't receive mouse events, it must also be invoked if a (potentially) * active window might be moved/resize away from the cursor (causing a leave event) */ void updateFocusMousePosition(const QPoint& pos); QPoint focusMousePosition() const; /** * Returns a client that is currently being moved or resized by the user. * * If none of clients is being moved or resized, @c null will be returned. */ AbstractClient* moveResizeClient() { return movingClient; } /** * @returns Whether we have a Compositor and it is active (Scene created) */ bool compositing() const; void registerEventFilter(X11EventFilter *filter); void unregisterEventFilter(X11EventFilter *filter); void markXStackingOrderAsDirty(); void quickTileWindow(QuickTileMode mode); enum Direction { DirectionNorth, DirectionEast, DirectionSouth, DirectionWest }; void switchWindow(Direction direction); ShortcutDialog *shortcutDialog() const { return client_keys_dialog; } + /** + * Adds the internal client to Workspace. + * + * This method will be called by InternalClient when it's mapped. + * + * @see internalClientAdded + * @internal + */ + void addInternalClient(InternalClient *client); + + /** + * Removes the internal client from Workspace. + * + * This method is meant to be called only by InternalClient. + * + * @see internalClientRemoved + * @internal + */ + void removeInternalClient(InternalClient *client); + public Q_SLOTS: void performWindowOperation(KWin::AbstractClient* c, Options::WindowOperation op); // Keybindings //void slotSwitchToWindow( int ); void slotWindowToDesktop(uint i); //void slotWindowToListPosition( int ); void slotSwitchToScreen(); void slotWindowToScreen(); void slotSwitchToNextScreen(); void slotWindowToNextScreen(); void slotSwitchToPrevScreen(); void slotWindowToPrevScreen(); void slotToggleShowDesktop(); void slotWindowMaximize(); void slotWindowMaximizeVertical(); void slotWindowMaximizeHorizontal(); void slotWindowMinimize(); void slotWindowShade(); void slotWindowRaise(); void slotWindowLower(); void slotWindowRaiseOrLower(); void slotActivateAttentionWindow(); void slotWindowPackLeft(); void slotWindowPackRight(); void slotWindowPackUp(); void slotWindowPackDown(); void slotWindowGrowHorizontal(); void slotWindowGrowVertical(); void slotWindowShrinkHorizontal(); void slotWindowShrinkVertical(); void slotIncreaseWindowOpacity(); void slotLowerWindowOpacity(); void slotWindowOperations(); void slotWindowClose(); void slotWindowMove(); void slotWindowResize(); void slotWindowAbove(); void slotWindowBelow(); void slotWindowOnAllDesktops(); void slotWindowFullScreen(); void slotWindowNoBorder(); void slotWindowToNextDesktop(); void slotWindowToPreviousDesktop(); void slotWindowToDesktopRight(); void slotWindowToDesktopLeft(); void slotWindowToDesktopUp(); void slotWindowToDesktopDown(); void reconfigure(); void slotReconfigure(); void slotKillWindow(); void slotSetupWindowShortcut(); void setupWindowShortcutDone(bool); void updateClientArea(); private Q_SLOTS: void desktopResized(); void selectWmInputEventMask(); void slotUpdateToolWindows(); void delayFocus(); void slotReloadConfig(); void updateCurrentActivity(const QString &new_activity); // virtual desktop handling void slotDesktopCountChanged(uint previousCount, uint newCount); void slotCurrentDesktopChanged(uint oldDesktop, uint newDesktop); // session management void saveState(QSessionManager &sm); void commitData(QSessionManager &sm); Q_SIGNALS: /** * Emitted after the Workspace has setup the complete initialization process. * This can be used to connect to for performing post-workspace initialization. */ void workspaceInitialized(); //Signals required for the scripting interface void desktopPresenceChanged(KWin::AbstractClient*, int); void currentDesktopChanged(int, KWin::AbstractClient*); void clientAdded(KWin::Client*); void clientRemoved(KWin::AbstractClient*); void clientActivated(KWin::AbstractClient*); void clientDemandsAttentionChanged(KWin::AbstractClient*, bool); void clientMinimizedChanged(KWin::AbstractClient*); void groupAdded(KWin::Group*); void unmanagedAdded(KWin::Unmanaged*); void unmanagedRemoved(KWin::Unmanaged*); void deletedRemoved(KWin::Deleted*); void configChanged(); void showingDesktopChanged(bool showing); /** * This signels is emitted when ever the stacking order is change, ie. a window is risen * or lowered */ void stackingOrderChanged(); + /** + * This signal is emitted whenever an internal client is created. + */ + void internalClientAdded(KWin::InternalClient *client); + + /** + * This signal is emitted whenever an internal client gets removed. + */ + void internalClientRemoved(KWin::InternalClient *client); + private: void init(); void initWithX11(); void initShortcuts(); template void initShortcut(const QString &actionName, const QString &description, const QKeySequence &shortcut, Slot slot, const QVariant &data = QVariant()); template void initShortcut(const QString &actionName, const QString &description, const QKeySequence &shortcut, T *receiver, Slot slot, const QVariant &data = QVariant()); void setupWindowShortcut(AbstractClient* c); bool switchWindow(AbstractClient *c, Direction direction, QPoint curPos, int desktop); void propagateClients(bool propagate_new_clients); // Called only from updateStackingOrder ToplevelList constrainedStackingOrder(); void raiseClientWithinApplication(AbstractClient* c); void lowerClientWithinApplication(AbstractClient* c); bool allowFullClientRaising(const AbstractClient* c, xcb_timestamp_t timestamp); bool keepTransientAbove(const AbstractClient* mainwindow, const AbstractClient* transient); bool keepDeletedTransientAbove(const Toplevel *mainWindow, const Deleted *transient) const; void blockStackingUpdates(bool block); void updateToolWindows(bool also_hide); void fixPositionAfterCrash(xcb_window_t w, const xcb_get_geometry_reply_t *geom); void saveOldScreenSizes(); /// This is the right way to create a new client Client* createClient(xcb_window_t w, bool is_mapped); void setupClientConnections(AbstractClient *client); void addClient(Client* c); Unmanaged* createUnmanaged(xcb_window_t w); void addUnmanaged(Unmanaged* c); //--------------------------------------------------------------------- void closeActivePopup(); void updateClientArea(bool force); void resetClientAreas(uint desktopCount); void updateClientVisibilityOnDesktopChange(uint newDesktop); void activateClientOnNewDesktop(uint desktop); AbstractClient *findClientToActivateOnDesktop(uint desktop); QWidget* active_popup; AbstractClient* active_popup_client; int m_initialDesktop; void loadSessionInfo(const QString &key); void addSessionInfo(KConfigGroup &cg); QList session; void updateXStackingOrder(); void updateTabbox(); AbstractClient* active_client; AbstractClient* last_active_client; AbstractClient* most_recently_raised; // Used ONLY by raiseOrLowerClient() AbstractClient* movingClient; // Delay(ed) window focus timer and client QTimer* delayFocusTimer; AbstractClient* delayfocus_client; QPoint focusMousePos; ClientList clients; QList m_allClients; ClientList desktops; UnmanagedList unmanaged; DeletedList deleted; + QList m_internalClients; ToplevelList unconstrained_stacking_order; // Topmost last ToplevelList stacking_order; // Topmost last QVector manual_overlays; //Topmost last bool force_restacking; ToplevelList x_stacking; // From XQueryTree() std::unique_ptr m_xStackingQueryTree; bool m_xStackingDirty = false; QList should_get_focus; // Last is most recent QList attention_chain; bool showing_desktop; GroupList groups; bool was_user_interaction; QScopedPointer m_wasUserInteractionFilter; bool session_saving; int session_active_client; int session_desktop; int block_focus; /** * Holds the menu containing the user actions which is shown * on e.g. right click the window decoration. */ UserActionsMenu *m_userActionsMenu; void modalActionsSwitch(bool enabled); ShortcutDialog* client_keys_dialog; AbstractClient* client_keys_client; bool global_shortcuts_disabled_for_client; // Timer to collect requests for 'reconfigure' QTimer reconfigureTimer; QTimer updateToolWindowsTimer; static Workspace* _self; bool workspaceInit; KStartupInfo* startup; QVector workarea; // Array of workareas for virtual desktops // Array of restricted areas that window cannot be moved into QVector restrictedmovearea; // Array of the previous restricted areas that window cannot be moved into QVector oldrestrictedmovearea; QVector< QVector > screenarea; // Array of workareas per xinerama screen for all virtual desktops QVector< QRect > oldscreensizes; // array of previous sizes of xinerama screens QSize olddisplaysize; // previous sizes od displayWidth()/displayHeight() int set_active_client_recursion; int block_stacking_updates; // When > 0, stacking updates are temporarily disabled bool blocked_propagating_new_clients; // Propagate also new clients after enabling stacking updates? QScopedPointer m_nullFocus; friend class StackingUpdatesBlocker; QScopedPointer m_windowKiller; QList m_eventFilters; QList m_genericEventFilters; QScopedPointer m_movingClientFilter; private: friend bool performTransiencyCheck(); friend Workspace *workspace(); }; /** * Helper for Workspace::blockStackingUpdates() being called in pairs (True/false) */ class StackingUpdatesBlocker { public: explicit StackingUpdatesBlocker(Workspace* w) : ws(w) { ws->blockStackingUpdates(true); } ~StackingUpdatesBlocker() { ws->blockStackingUpdates(false); } private: Workspace* ws; }; class ColorMapper : public QObject { Q_OBJECT public: ColorMapper(QObject *parent); ~ColorMapper() override; public Q_SLOTS: void update(); private: xcb_colormap_t m_default; xcb_colormap_t m_installed; }; //--------------------------------------------------------- // Unsorted inline bool Workspace::initializing() const { return workspaceInit; } inline AbstractClient *Workspace::activeClient() const { return active_client; } inline AbstractClient *Workspace::mostRecentlyActivatedClient() const { return should_get_focus.count() > 0 ? should_get_focus.last() : active_client; } inline void Workspace::addGroup(Group* group) { emit groupAdded(group); groups.append(group); } inline void Workspace::removeGroup(Group* group) { groups.removeAll(group); } inline const ToplevelList& Workspace::stackingOrder() const { // TODO: Q_ASSERT( block_stacking_updates == 0 ); return stacking_order; } inline bool Workspace::wasUserInteraction() const { return was_user_interaction; } inline void Workspace::sessionSaveStarted() { session_saving = true; } inline bool Workspace::sessionSaving() const { return session_saving; } inline bool Workspace::showingDesktop() const { return showing_desktop; } inline bool Workspace::globalShortcutsDisabled() const { return global_shortcuts_disabled_for_client; } inline void Workspace::forceRestacking() { force_restacking = true; StackingUpdatesBlocker blocker(this); // Do restacking if not blocked } inline void Workspace::updateFocusMousePosition(const QPoint& pos) { focusMousePos = pos; } inline QPoint Workspace::focusMousePosition() const { return focusMousePos; } inline void Workspace::forEachClient(std::function< void (Client*) > func) { std::for_each(clients.constBegin(), clients.constEnd(), func); std::for_each(desktops.constBegin(), desktops.constEnd(), func); } inline void Workspace::forEachUnmanaged(std::function< void (Unmanaged*) > func) { std::for_each(unmanaged.constBegin(), unmanaged.constEnd(), func); } inline bool Workspace::hasClient(const Client* c) { return findClient([c](const Client *test) { return test == c; }); } inline Workspace *workspace() { return Workspace::_self; } } // namespace Q_DECLARE_OPERATORS_FOR_FLAGS(KWin::Workspace::ActivityFlags) #endif