/* KWin - the KDE window manager This file is part of the KDE project. SPDX-FileCopyrightText: 2006 Lubos Lunak SPDX-FileCopyrightText: 2010, 2011 Martin Gräßlin SPDX-License-Identifier: GPL-2.0-or-later */ #include "effects.h" #include "effectsadaptor.h" #include "effectloader.h" #ifdef KWIN_BUILD_ACTIVITIES #include "activities.h" #endif #include "deleted.h" #include "x11client.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 "kwineffectquickview.h" #include #include #include "composite.h" #include "xcbutils.h" #include "platform.h" #include "waylandclient.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](AbstractClient *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) { setupClientConnections(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(ws->sessionManager(), &SessionManager::stateChanged, this, &KWin::EffectsHandler::sessionStateChanged); connect(vds, &VirtualDesktopManager::countChanged, this, &EffectsHandler::numberDesktopsChanged); connect(Cursors::self()->mouse(), &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 (AbstractClient *client : ws->allClientList()) { if (client->readyForPainting()) { setupClientConnections(client); } else { connect(client, &Toplevel::windowShown, this, &EffectsHandlerImpl::slotClientShown); } } for (Unmanaged *u : ws->unmanagedList()) { setupUnmanagedConnections(u); } for (InternalClient *client : ws->internalClients()) { setupClientConnections(client); } 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::setupClientConnections(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::frameGeometryChanged, this, &EffectsHandlerImpl::slotFrameGeometryChanged); connect(c, &AbstractClient::damaged, this, &EffectsHandlerImpl::slotWindowDamaged); connect(c, &AbstractClient::paddingChanged, this, &EffectsHandlerImpl::slotPaddingChanged); 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::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::frameGeometryChanged, this, &EffectsHandlerImpl::slotFrameGeometryChanged); 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, const QRegion ®ion, 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, const QRegion ®ion, 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, const QRegion ®ion, 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, const QRegion ®ion, 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)); AbstractClient *c = static_cast(t); disconnect(c, &Toplevel::windowShown, this, &EffectsHandlerImpl::slotClientShown); setupClientConnections(c); emit windowAdded(c->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 QRegion& 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::slotFrameGeometryChanged(Toplevel *toplevel, const QRect &oldGeometry) { // effectWindow() might be nullptr during tear down of the client. if (toplevel->effectWindow()) { emit windowFrameGeometryChanged(toplevel->effectWindow(), oldGeometry); } } 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 (Cursors::self()->mouse()) Cursors::self()->mouse()->startMousePolling(); } void EffectsHandlerImpl::stopMousePolling() { if (Cursors::self()->mouse()) Cursors::self()->mouse()->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 (X11Client *w = Workspace::self()->findClient(Predicate::WindowMatch, id)) return w->effectWindow(); if (Unmanaged* w = Workspace::self()->findUnmanaged(id)) return w->effectWindow(); return nullptr; } EffectWindow* EffectsHandlerImpl::findWindow(KWaylandServer::SurfaceInterface *surf) const { if (waylandServer()) { if (AbstractClient *w = waylandServer()->findClient(surf)) { return w->effectWindow(); } } return nullptr; } EffectWindow *EffectsHandlerImpl::findWindow(QWindow *w) const { 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 { QList 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->frameGeometry().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(Cursors::self()->mouse(), &Cursor::cursorChanged, this, &EffectsHandler::cursorShapeChanged); Cursors::self()->mouse()->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) { Cursors::self()->mouse()->stopCursorTracking(); disconnect(Cursors::self()->mouse(), &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 Cursors::self()->mouse()->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; } KWaylandServer::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 InputConfig::self()->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; } void EffectsHandlerImpl::renderEffectQuickView(EffectQuickView *w) const { if (!w->isVisible()) { return; } scene()->paintEffectQuickView(w); } SessionState EffectsHandlerImpl::sessionState() const { return Workspace::self()->sessionManager()->state(); } //**************************************** // 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, XdgShellClient, 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 = qobject_cast(toplevel) != nullptr || 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, frameGeometry) TOPLEVEL_HELPER(QRect, frameGeometry, frameGeometry) TOPLEVEL_HELPER(QRect, bufferGeometry, bufferGeometry) 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(KWaylandServer::SurfaceInterface *, surface, surface) TOPLEVEL_HELPER(bool, isPopupWindow, isPopupWindow) TOPLEVEL_HELPER(bool, isOutline, isOutline) TOPLEVEL_HELPER(pid_t, pid, pid) TOPLEVEL_HELPER(qlonglong, windowId, window) #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 { if (isX11Client() && sceneWindow()) { return sceneWindow()->bufferShape(); } return toplevel->rect(); } 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; } EffectWindow* EffectWindowImpl::transientFor() { auto client = qobject_cast(toplevel); if (!client) { return nullptr; } AbstractClient *transientFor = client->transientFor(); if (transientFor) { return transientFor->effectWindow(); } return nullptr; } QWindow *EffectWindowImpl::internalWindow() const { 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, &QObject::destroyed, this, &EffectWindowImpl::thumbnailDestroyed); connect(thumb, &WindowThumbnailItem::wIdChanged, this, &EffectWindowImpl::thumbnailTargetChanged); } else if (DesktopThumbnailItem *desktopThumb = qobject_cast(item)) { m_desktopThumbnails.append(desktopThumb); connect(desktopThumb, &QObject::destroyed, this, &EffectWindowImpl::desktopThumbnailDestroyed); } } 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, &Plasma::Theme::themeChanged, this, &EffectFrameImpl::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(const QRegion ®ion, 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