diff --git a/abstract_client.cpp b/abstract_client.cpp index 64e6cec19..363e4fa50 100644 --- a/abstract_client.cpp +++ b/abstract_client.cpp @@ -1,1995 +1,1995 @@ /******************************************************************** 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_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 : NULL); + 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; } } diff --git a/activation.cpp b/activation.cpp index 9ffe34450..0c9a3af72 100644 --- a/activation.cpp +++ b/activation.cpp @@ -1,883 +1,883 @@ /******************************************************************** 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 . *********************************************************************/ /* This file contains things relevant to window activation and focus stealing prevention. */ #include "client.h" #include "cursor.h" #include "focuschain.h" #include "netinfo.h" #include "workspace.h" #ifdef KWIN_BUILD_ACTIVITIES #include "activities.h" #endif #include #include #include #include "atoms.h" #include "group.h" #include "rules.h" #include "screens.h" #include "useractions.h" #include namespace KWin { /* Prevention of focus stealing: KWin tries to prevent unwanted changes of focus, that would result from mapping a new window. Also, some nasty applications may try to force focus change even in cases when ICCCM 4.2.7 doesn't allow it (e.g. they may try to activate their main window because the user definitely "needs" to see something happened - misusing of QWidget::setActiveWindow() may be such case). There are 4 ways how a window may become active: - the user changes the active window (e.g. focus follows mouse, clicking on some window's titlebar) - the change of focus will be done by KWin, so there's nothing to solve in this case - the change of active window will be requested using the _NET_ACTIVE_WINDOW message (handled in RootInfo::changeActiveWindow()) - such requests will be obeyed, because this request is meant mainly for e.g. taskbar asking the WM to change the active window as a result of some user action. Normal applications should use this request only rarely in special cases. See also below the discussion of _NET_ACTIVE_WINDOW_TRANSFER. - the change of active window will be done by performing XSetInputFocus() on a window that's not currently active. ICCCM 4.2.7 describes when the application may perform change of input focus. In order to handle misbehaving applications, KWin will try to detect focus changes to windows that don't belong to currently active application, and restore focus back to the currently active window, instead of activating the window that got focus (unfortunately there's no way to FocusChangeRedirect similar to e.g. SubstructureRedirect, so there will be short time when the focus will be changed). The check itself that's done is Workspace::allowClientActivation() (see below). - a new window will be mapped - this is the most complicated case. If the new window belongs to the currently active application, it may be safely mapped on top and activated. The same if there's no active window, or the active window is the desktop. These checks are done by Workspace::allowClientActivation(). Following checks need to compare times. One time is the timestamp of last user action in the currently active window, the other time is the timestamp of the action that originally caused mapping of the new window (e.g. when the application was started). If the first time is newer than the second one, the window will not be activated, as that indicates futher user actions took place after the action leading to this new mapped window. This check is done by Workspace::allowClientActivation(). There are several ways how to get the timestamp of action that caused the new mapped window (done in Client::readUserTimeMapTimestamp()) : - the window may have the _NET_WM_USER_TIME property. This way the application may either explicitly request that the window is not activated (by using 0 timestamp), or the property contains the time of last user action in the application. - KWin itself tries to detect time of last user action in every window, by watching KeyPress and ButtonPress events on windows. This way some events may be missed (if they don't propagate to the toplevel window), but it's good as a fallback for applications that don't provide _NET_WM_USER_TIME, and missing some events may at most lead to unwanted focus stealing. - the timestamp may come from application startup notification. Application startup notification, if it exists for the new mapped window, should include time of the user action that caused it. - if there's no timestamp available, it's checked whether the new window belongs to some already running application - if yes, the timestamp will be 0 (i.e. refuse activation) - if the window is from session restored window, the timestamp will be 0 too, unless this application was the active one at the time when the session was saved, in which case the window will be activated if there wasn't any user interaction since the time KWin was started. - as the last resort, the _KDE_NET_USER_CREATION_TIME timestamp is used. For every toplevel window that is created (see CreateNotify handling), this property is set to the at that time current time. Since at this time it's known that the new window doesn't belong to any existing application (better said, the application doesn't have any other window mapped), it is either the very first window of the application, or it is the only window of the application that was hidden before. The latter case is handled by removing the property from windows before withdrawing them, making the timestamp empty for next mapping of the window. In the sooner case, the timestamp will be used. This helps in case when an application is launched without application startup notification, it creates its mainwindow, and starts its initialization (that may possibly take long time). The timestamp used will be older than any user action done after launching this application. - if no timestamp is found at all, the window is activated. The check whether two windows belong to the same application (same process) is done in Client::belongToSameApplication(). Not 100% reliable, but hopefully 99,99% reliable. As a somewhat special case, window activation is always enabled when session saving is in progress. When session saving, the session manager allows only one application to interact with the user. Not allowing window activation in such case would result in e.g. dialogs not becoming active, so focus stealing prevention would cause here more harm than good. Windows that attempted to become active but KWin prevented this will be marked as demanding user attention. They'll get the _NET_WM_STATE_DEMANDS_ATTENTION state, and the taskbar should mark them specially (blink, etc.). The state will be reset when the window eventually really becomes active. There are two more ways how a window can become obtrusive, window stealing focus: By showing above the active window, by either raising itself, or by moving itself on the active desktop. - KWin will refuse raising non-active window above the active one, unless they belong to the same application. Applications shouldn't raise their windows anyway (unless the app wants to raise one of its windows above another of its windows). - KWin activates windows moved to the current desktop (as that seems logical from the user's point of view, after sending the window there directly from KWin, or e.g. using pager). This means applications shouldn't send their windows to another desktop (SELI TODO - but what if they do?) Special cases I can think of: - konqueror reusing, i.e. kfmclient tells running Konqueror instance to open new window - without focus stealing prevention - no problem - with ASN (application startup notification) - ASN is forwarded, and because it's newer than the instance's user timestamp, it takes precedence - without ASN - user timestamp needs to be reset, otherwise it would be used, and it's old; moreover this new window mustn't be detected as window belonging to already running application, or it wouldn't be activated - see Client::sameAppWindowRoleMatch() for the (rather ugly) hack - konqueror preloading, i.e. window is created in advance, and kfmclient tells this Konqueror instance to show it later - without focus stealing prevention - no problem - with ASN - ASN is forwarded, and because it's newer than the instance's user timestamp, it takes precedence - without ASN - user timestamp needs to be reset, otherwise it would be used, and it's old; also, creation timestamp is changed to the time the instance starts (re-)initializing the window, this ensures creation timestamp will still work somewhat even in this case - KUniqueApplication - when the window is already visible, and the new instance wants it to activate - without focus stealing prevention - _NET_ACTIVE_WINDOW - no problem - with ASN - ASN is forwarded, and set on the already visible window, KWin treats the window as new with that ASN - without ASN - _NET_ACTIVE_WINDOW as application request is used, and there's no really usable timestamp, only timestamp from the time the (new) application instance was started, so KWin will activate the window *sigh* - the bad thing here is that there's absolutely no chance to recognize the case of starting this KUniqueApp from Konsole (and thus wanting the already visible window to become active) from the case when something started this KUniqueApp without ASN (in which case the already visible window shouldn't become active) - the only solution is using ASN for starting applications, at least silent (i.e. without feedback) - when one application wants to activate another application's window (e.g. KMail activating already running KAddressBook window ?) - without focus stealing prevention - _NET_ACTIVE_WINDOW - no problem - with ASN - can't be here, it's the KUniqueApp case then - without ASN - _NET_ACTIVE_WINDOW as application request should be used, KWin will activate the new window depending on the timestamp and whether it belongs to the currently active application _NET_ACTIVE_WINDOW usage: data.l[0]= 1 ->app request = 2 ->pager request = 0 - backwards compatibility data.l[1]= timestamp */ //**************************************** // Workspace //**************************************** /** * Informs the workspace about the active client, i.e. the client that * has the focus (or None if no client has the focus). This functions * is called by the client itself that gets focus. It has no other * effect than fixing the focus chain and the return value of * activeClient(). And of course, to propagate the active client to the * world. */ void Workspace::setActiveClient(AbstractClient* c) { if (active_client == c) return; if (active_popup && active_popup_client != c && set_active_client_recursion == 0) closeActivePopup(); if (m_userActionsMenu->hasClient() && !m_userActionsMenu->isMenuClient(c) && set_active_client_recursion == 0) { m_userActionsMenu->close(); } StackingUpdatesBlocker blocker(this); ++set_active_client_recursion; updateFocusMousePosition(Cursor::pos()); - if (active_client != NULL) { + if (active_client != nullptr) { // note that this may call setActiveClient( NULL ), therefore the recursion counter active_client->setActive(false); } active_client = c; - Q_ASSERT(c == NULL || c->isActive()); + Q_ASSERT(c == nullptr || c->isActive()); if (active_client) { last_active_client = active_client; FocusChain::self()->update(active_client, FocusChain::MakeFirst); active_client->demandAttention(false); // activating a client can cause a non active fullscreen window to loose the ActiveLayer status on > 1 screens if (screens()->count() > 1) { for (auto it = m_allClients.begin(); it != m_allClients.end(); ++it) { if (*it != active_client && (*it)->layer() == ActiveLayer && (*it)->screen() == active_client->screen()) { updateClientLayer(*it); } } } } updateToolWindows(false); if (c) disableGlobalShortcutsForClient(c->rules()->checkDisableGlobalShortcuts(false)); else disableGlobalShortcutsForClient(false); updateStackingOrder(); // e.g. fullscreens have different layer when active/not-active if (rootInfo()) { rootInfo()->setActiveClient(active_client); } emit clientActivated(active_client); --set_active_client_recursion; } /** * Tries to activate the client \a c. This function performs what you * expect when clicking the respective entry in a taskbar: showing and * raising the client (this may imply switching to the another virtual * desktop) and putting the focus onto it. Once X really gave focus to * the client window as requested, the client itself will call * setActiveClient() and the operation is complete. This may not happen * with certain focus policies, though. * * @see setActiveClient * @see requestFocus */ void Workspace::activateClient(AbstractClient* c, bool force) { - if (c == NULL) { + if (c == nullptr) { focusToNull(); - setActiveClient(NULL); + setActiveClient(nullptr); return; } raiseClient(c); if (!c->isOnCurrentDesktop()) { ++block_focus; VirtualDesktopManager::self()->setCurrent(c->desktop()); --block_focus; } #ifdef KWIN_BUILD_ACTIVITIES if (!c->isOnCurrentActivity()) { ++block_focus; //DBUS! Activities::self()->setCurrent(c->activities().first()); //first isn't necessarily best, but it's easiest --block_focus; } #endif if (c->isMinimized()) c->unminimize(); // ensure the window is really visible - could eg. be a hidden utility window, see bug #348083 c->hideClient(false); // TODO force should perhaps allow this only if the window already contains the mouse if (options->focusPolicyIsReasonable() || force) requestFocus(c, force); // Don't update user time for clients that have focus stealing workaround. // As they usually belong to the current active window but fail to provide // this information, updating their user time would make the user time // of the currently active window old, and reject further activation for it. // E.g. typing URL in minicli which will show kio_uiserver dialog (with workaround), // and then kdesktop shows dialog about SSL certificate. // This needs also avoiding user creation time in Client::readUserTimeMapTimestamp(). if (Client *client = dynamic_cast(c)) { // updateUserTime is X11 specific client->updateUserTime(); } } /** * Tries to activate the client by asking X for the input focus. This * function does not perform any show, raise or desktop switching. See * Workspace::activateClient() instead. * * @see activateClient */ void Workspace::requestFocus(AbstractClient* c, bool force) { takeActivity(c, force ? ActivityFocusForce : ActivityFocus); } void Workspace::takeActivity(AbstractClient* c, ActivityFlags flags) { // the 'if ( c == active_client ) return;' optimization mustn't be done here if (!focusChangeEnabled() && (c != active_client)) flags &= ~ActivityFocus; if (!c) { focusToNull(); return; } if (flags & ActivityFocus) { AbstractClient* modal = c->findModal(); - if (modal != NULL && modal != c) { + if (modal != nullptr && modal != c) { if (!modal->isOnDesktop(c->desktop())) modal->setDesktop(c->desktop()); if (!modal->isShown(true) && !modal->isMinimized()) // forced desktop or utility window activateClient(modal); // activating a minimized blocked window will unminimize its modal implicitly // if the click was inside the window (i.e. handled is set), // but it has a modal, there's no need to use handled mode, because // the modal doesn't get the click anyway // raising of the original window needs to be still done if (flags & ActivityRaise) raiseClient(c); c = modal; } cancelDelayFocus(); } if (!flags.testFlag(ActivityFocusForce) && (c->isDock() || c->isSplash())) { // toplevel menus and dock windows don't take focus if not forced // and don't have a flag that they take focus if (!c->dockWantsInput()) { flags &= ~ActivityFocus; } } if (c->isShade()) { if (c->wantsInput() && (flags & ActivityFocus)) { // client cannot accept focus, but at least the window should be active (window menu, et. al. ) c->setActive(true); focusToNull(); } flags &= ~ActivityFocus; } if (!c->isShown(true)) { // shouldn't happen, call activateClient() if needed qCWarning(KWIN_CORE) << "takeActivity: not shown" ; return; } if (flags & ActivityFocus) c->takeFocus(); if (flags & ActivityRaise) workspace()->raiseClient(c); if (!c->isOnActiveScreen()) screens()->setCurrent(c->screen()); } /** * Informs the workspace that the client \a c has been hidden. If it * was the active client (or to-become the active client), * the workspace activates another one. * * @note @p c may already be destroyed. */ void Workspace::clientHidden(AbstractClient* c) { Q_ASSERT(!c->isShown(true) || !c->isOnCurrentDesktop() || !c->isOnCurrentActivity()); activateNextClient(c); } AbstractClient *Workspace::clientUnderMouse(int screen) const { ToplevelList::const_iterator it = stackingOrder().constEnd(); while (it != stackingOrder().constBegin()) { AbstractClient *client = qobject_cast(*(--it)); if (!client) { continue; } // rule out clients which are not really visible. // the screen test is rather superfluous for xrandr & twinview since the geometry would differ -> TODO: might be dropped if (!(client->isShown(false) && client->isOnCurrentDesktop() && client->isOnCurrentActivity() && client->isOnScreen(screen))) continue; if (client->geometry().contains(Cursor::pos())) { return client; } } - return 0; + return nullptr; } // deactivates 'c' and activates next client bool Workspace::activateNextClient(AbstractClient* c) { // if 'c' is not the active or the to-become active one, do nothing if (!(c == active_client || (should_get_focus.count() > 0 && c == should_get_focus.last()))) return false; closeActivePopup(); - if (c != NULL) { + if (c != nullptr) { if (c == active_client) - setActiveClient(NULL); + setActiveClient(nullptr); should_get_focus.removeAll(c); } // if blocking focus, move focus to the desktop later if needed // in order to avoid flickering if (!focusChangeEnabled()) { focusToNull(); return true; } if (!options->focusPolicyIsReasonable()) return false; - AbstractClient* get_focus = NULL; + AbstractClient* get_focus = nullptr; const int desktop = VirtualDesktopManager::self()->current(); if (!get_focus && showingDesktop()) get_focus = findDesktop(true, desktop); // to not break the state if (!get_focus && options->isNextFocusPrefersMouse()) { get_focus = clientUnderMouse(c ? c->screen() : screens()->current()); if (get_focus && (get_focus == c || get_focus->isDesktop())) { // should rather not happen, but it cannot get the focus. rest of usability is tested above - get_focus = NULL; + get_focus = nullptr; } } if (!get_focus) { // no suitable window under the mouse -> find sth. else // first try to pass the focus to the (former) active clients leader if (c && c->isTransient()) { auto leaders = c->mainClients(); if (leaders.count() == 1 && FocusChain::self()->isUsableFocusCandidate(leaders.at(0), c)) { get_focus = leaders.at(0); raiseClient(get_focus); // also raise - we don't know where it came from } } if (!get_focus) { // nope, ask the focus chain for the next candidate get_focus = FocusChain::self()->nextForDesktop(c, desktop); } } - if (get_focus == NULL) // last chance: focus the desktop + if (get_focus == nullptr) // last chance: focus the desktop get_focus = findDesktop(true, desktop); - if (get_focus != NULL) + if (get_focus != nullptr) requestFocus(get_focus); else focusToNull(); return true; } void Workspace::setCurrentScreen(int new_screen) { if (new_screen < 0 || new_screen >= screens()->count()) return; if (!options->focusPolicyIsReasonable()) return; closeActivePopup(); const int desktop = VirtualDesktopManager::self()->current(); AbstractClient *get_focus = FocusChain::self()->getForActivation(desktop, new_screen); - if (get_focus == NULL) + if (get_focus == nullptr) get_focus = findDesktop(true, desktop); - if (get_focus != NULL && get_focus != mostRecentlyActivatedClient()) + if (get_focus != nullptr && get_focus != mostRecentlyActivatedClient()) requestFocus(get_focus); screens()->setCurrent(new_screen); } void Workspace::gotFocusIn(const AbstractClient* c) { if (should_get_focus.contains(const_cast< AbstractClient* >(c))) { // remove also all sooner elements that should have got FocusIn, // but didn't for some reason (and also won't anymore, because they were sooner) while (should_get_focus.first() != c) should_get_focus.pop_front(); should_get_focus.pop_front(); // remove 'c' } } void Workspace::setShouldGetFocus(AbstractClient* c) { should_get_focus.append(c); updateStackingOrder(); // e.g. fullscreens have different layer when active/not-active } namespace FSP { enum Level { None = 0, Low, Medium, High, Extreme }; } // focus_in -> the window got FocusIn event // ignore_desktop - call comes from _NET_ACTIVE_WINDOW message, don't refuse just because of window // is on a different desktop bool Workspace::allowClientActivation(const KWin::AbstractClient *c, xcb_timestamp_t time, bool focus_in, bool ignore_desktop) { // options->focusStealingPreventionLevel : // 0 - none - old KWin behaviour, new windows always get focus // 1 - low - focus stealing prevention is applied normally, when unsure, activation is allowed // 2 - normal - focus stealing prevention is applied normally, when unsure, activation is not allowed, // this is the default // 3 - high - new window gets focus only if it belongs to the active application, // or when no window is currently active // 4 - extreme - no window gets focus without user intervention if (time == -1U) time = c->userTime(); int level = c->rules()->checkFSP(options->focusStealingPreventionLevel()); if (session_saving && level <= FSP::Medium) { // <= normal return true; } AbstractClient* ac = mostRecentlyActivatedClient(); if (focus_in) { if (should_get_focus.contains(const_cast< AbstractClient* >(c))) return true; // FocusIn was result of KWin's action // Before getting FocusIn, the active Client already // got FocusOut, and therefore got deactivated. ac = last_active_client; } if (time == 0) { // explicitly asked not to get focus if (!c->rules()->checkAcceptFocus(false)) return false; } const int protection = ac ? ac->rules()->checkFPP(2) : 0; // stealing is unconditionally allowed (NETWM behavior) if (level == FSP::None || protection == FSP::None) return true; // The active client "grabs" the focus or stealing is generally forbidden if (level == FSP::Extreme || protection == FSP::Extreme) return false; // Desktop switching is only allowed in the "no protection" case if (!ignore_desktop && !c->isOnCurrentDesktop()) return false; // allow only with level == 0 // No active client, it's ok to pass focus // NOTICE that extreme protection needs to be handled before to allow protection on unmanged windows - if (ac == NULL || ac->isDesktop()) { + if (ac == nullptr || ac->isDesktop()) { qCDebug(KWIN_CORE) << "Activation: No client active, allowing"; return true; // no active client -> always allow } // TODO window urgency -> return true? // Unconditionally allow intra-client passing around for lower stealing protections // unless the active client has High interest if (AbstractClient::belongToSameApplication(c, ac, AbstractClient::SameApplicationCheck::RelaxedForActive) && protection < FSP::High) { qCDebug(KWIN_CORE) << "Activation: Belongs to active application"; return true; } if (!c->isOnCurrentDesktop()) // we allowed explicit self-activation across virtual desktops return false; // inside a client or if no client was active, but not otherwise // High FPS, not intr-client change. Only allow if the active client has only minor interest if (level > FSP::Medium && protection > FSP::Low) return false; if (time == -1U) { // no time known qCDebug(KWIN_CORE) << "Activation: No timestamp at all"; // Only allow for Low protection unless active client has High interest in focus if (level < FSP::Medium && protection < FSP::High) return true; // no timestamp at all, don't activate - because there's also creation timestamp // done on CreateNotify, this case should happen only in case application // maps again already used window, i.e. this won't happen after app startup return false; } // Low or medium FSP, usertime comparism is possible const xcb_timestamp_t user_time = ac->userTime(); qCDebug(KWIN_CORE) << "Activation, compared:" << c << ":" << time << ":" << user_time << ":" << (NET::timestampCompare(time, user_time) >= 0); return NET::timestampCompare(time, user_time) >= 0; // time >= user_time } // basically the same like allowClientActivation(), this time allowing // a window to be fully raised upon its own request (XRaiseWindow), // if refused, it will be raised only on top of windows belonging // to the same application bool Workspace::allowFullClientRaising(const KWin::AbstractClient *c, xcb_timestamp_t time) { int level = c->rules()->checkFSP(options->focusStealingPreventionLevel()); if (session_saving && level <= 2) { // <= normal return true; } AbstractClient* ac = mostRecentlyActivatedClient(); if (level == 0) // none return true; if (level == 4) // extreme return false; - if (ac == NULL || ac->isDesktop()) { + if (ac == nullptr || ac->isDesktop()) { qCDebug(KWIN_CORE) << "Raising: No client active, allowing"; return true; // no active client -> always allow } // TODO window urgency -> return true? if (AbstractClient::belongToSameApplication(c, ac, AbstractClient::SameApplicationCheck::RelaxedForActive)) { qCDebug(KWIN_CORE) << "Raising: Belongs to active application"; return true; } if (level == 3) // high return false; xcb_timestamp_t user_time = ac->userTime(); qCDebug(KWIN_CORE) << "Raising, compared:" << time << ":" << user_time << ":" << (NET::timestampCompare(time, user_time) >= 0); return NET::timestampCompare(time, user_time) >= 0; // time >= user_time } // called from Client after FocusIn that wasn't initiated by KWin and the client // wasn't allowed to activate void Workspace::restoreFocus() { // this updateXTime() is necessary - as FocusIn events don't have // a timestamp *sigh*, kwin's timestamp would be older than the timestamp // that was used by whoever caused the focus change, and therefore // the attempt to restore the focus would fail due to old timestamp updateXTime(); if (should_get_focus.count() > 0) requestFocus(should_get_focus.last()); else if (last_active_client) requestFocus(last_active_client); } void Workspace::clientAttentionChanged(AbstractClient* c, bool set) { if (set) { attention_chain.removeAll(c); attention_chain.prepend(c); } else attention_chain.removeAll(c); emit clientDemandsAttentionChanged(c, set); } //******************************************** // Client //******************************************** /** * Updates the user time (time of last action in the active window). * This is called inside kwin for every action with the window * that qualifies for user interaction (clicking on it, activate it * externally, etc.). */ void Client::updateUserTime(xcb_timestamp_t time) { // copied in Group::updateUserTime if (time == XCB_TIME_CURRENT_TIME) { updateXTime(); time = xTime(); } if (time != -1U && (m_userTime == XCB_TIME_CURRENT_TIME || NET::timestampCompare(time, m_userTime) > 0)) { // time > user_time m_userTime = time; - shade_below = NULL; // do not hover re-shade a window after it got interaction + shade_below = nullptr; // do not hover re-shade a window after it got interaction } group()->updateUserTime(m_userTime); } xcb_timestamp_t Client::readUserCreationTime() const { Xcb::Property prop(false, window(), atoms->kde_net_wm_user_creation_time, XCB_ATOM_CARDINAL, 0, 1); return prop.value(-1); } xcb_timestamp_t Client::readUserTimeMapTimestamp(const KStartupInfoId *asn_id, const KStartupInfoData *asn_data, bool session) const { xcb_timestamp_t time = info->userTime(); //qDebug() << "User timestamp, initial:" << time; //^^ this deadlocks kwin --replace sometimes. // newer ASN timestamp always replaces user timestamp, unless user timestamp is 0 // helps e.g. with konqy reusing - if (asn_data != NULL && time != 0) { + if (asn_data != nullptr && time != 0) { if (asn_id->timestamp() != 0 && (time == -1U || NET::timestampCompare(asn_id->timestamp(), time) > 0)) { time = asn_id->timestamp(); } } qCDebug(KWIN_CORE) << "User timestamp, ASN:" << time; if (time == -1U) { // The window doesn't have any timestamp. // If it's the first window for its application // (i.e. there's no other window from the same app), // use the _KDE_NET_WM_USER_CREATION_TIME trick. // Otherwise, refuse activation of a window // from already running application if this application // is not the active one (unless focus stealing prevention is turned off). Client* act = dynamic_cast(workspace()->mostRecentlyActivatedClient()); - if (act != NULL && !belongToSameApplication(act, this, SameApplicationCheck::RelaxedForActive)) { + if (act != nullptr && !belongToSameApplication(act, this, SameApplicationCheck::RelaxedForActive)) { bool first_window = true; auto sameApplicationActiveHackPredicate = [this](const Client *cl) { // ignore already existing splashes, toolbars, utilities and menus, // as the app may show those before the main window return !cl->isSplash() && !cl->isToolbar() && !cl->isUtility() && !cl->isMenu() && cl != this && Client::belongToSameApplication(cl, this, SameApplicationCheck::RelaxedForActive); }; if (isTransient()) { auto clientMainClients = [this] () -> ClientList { ClientList ret; const auto mcs = mainClients(); for (auto mc: mcs) { if (Client *c = dynamic_cast(mc)) { ret << c; } } return ret; }; if (act->hasTransient(this, true)) ; // is transient for currently active window, even though it's not // the same app (e.g. kcookiejar dialog) -> allow activation else if (groupTransient() && - findInList(clientMainClients(), sameApplicationActiveHackPredicate) == NULL) + findInList(clientMainClients(), sameApplicationActiveHackPredicate) == nullptr) ; // standalone transient else first_window = false; } else { if (workspace()->findClient(sameApplicationActiveHackPredicate)) first_window = false; } // don't refuse if focus stealing prevention is turned off if (!first_window && rules()->checkFSP(options->focusStealingPreventionLevel()) > 0) { qCDebug(KWIN_CORE) << "User timestamp, already exists:" << 0; return 0; // refuse activation } } // Creation time would just mess things up during session startup, // as possibly many apps are started up at the same time. // If there's no active window yet, no timestamp will be needed, // as plain Workspace::allowClientActivation() will return true // in such case. And if there's already active window, // it's better not to activate the new one. // Unless it was the active window at the time // of session saving and there was no user interaction yet, // this check will be done in manage(). if (session) return -1U; time = readUserCreationTime(); } qCDebug(KWIN_CORE) << "User timestamp, final:" << this << ":" << time; return time; } xcb_timestamp_t Client::userTime() const { xcb_timestamp_t time = m_userTime; if (time == 0) // doesn't want focus after showing return 0; - Q_ASSERT(group() != NULL); + Q_ASSERT(group() != nullptr); if (time == -1U || (group()->userTime() != -1U && NET::timestampCompare(group()->userTime(), time) > 0)) time = group()->userTime(); return time; } void Client::doSetActive() { updateUrgency(); // demand attention again if it's still urgent info->setState(isActive() ? NET::Focused : NET::States(), NET::Focused); } void Client::startupIdChanged() { KStartupInfoId asn_id; KStartupInfoData asn_data; bool asn_valid = workspace()->checkStartupNotification(window(), asn_id, asn_data); if (!asn_valid) return; // If the ASN contains desktop, move it to the desktop, otherwise move it to the current // desktop (since the new ASN should make the window act like if it's a new application // launched). However don't affect the window's desktop if it's set to be on all desktops. int desktop = VirtualDesktopManager::self()->current(); if (asn_data.desktop() != 0) desktop = asn_data.desktop(); if (!isOnAllDesktops()) workspace()->sendClientToDesktop(this, desktop, true); if (asn_data.xinerama() != -1) workspace()->sendClientToScreen(this, asn_data.xinerama()); const xcb_timestamp_t timestamp = asn_id.timestamp(); if (timestamp != 0) { bool activate = workspace()->allowClientActivation(this, timestamp); if (asn_data.desktop() != 0 && !isOnCurrentDesktop()) activate = false; // it was started on different desktop than current one if (activate) workspace()->activateClient(this); else demandAttention(); } } void Client::updateUrgency() { if (info->urgency()) demandAttention(); } //**************************************** // Group //**************************************** void Group::startupIdChanged() { KStartupInfoId asn_id; KStartupInfoData asn_data; bool asn_valid = workspace()->checkStartupNotification(leader_wid, asn_id, asn_data); if (!asn_valid) return; if (asn_id.timestamp() != 0 && user_time != -1U && NET::timestampCompare(asn_id.timestamp(), user_time) > 0) { user_time = asn_id.timestamp(); } } void Group::updateUserTime(xcb_timestamp_t time) { // copy of Client::updateUserTime if (time == XCB_CURRENT_TIME) { updateXTime(); time = xTime(); } if (time != -1U && (user_time == XCB_CURRENT_TIME || NET::timestampCompare(time, user_time) > 0)) // time > user_time user_time = time; } } // namespace diff --git a/activities.cpp b/activities.cpp index 42c5db801..262cd54bb 100644 --- a/activities.cpp +++ b/activities.cpp @@ -1,218 +1,218 @@ /******************************************************************** 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 "activities.h" // KWin #include "client.h" #include "workspace.h" // KDE #include #include // Qt #include #include #include #include namespace KWin { KWIN_SINGLETON_FACTORY(Activities) Activities::Activities(QObject *parent) : QObject(parent) , m_controller(new KActivities::Controller(this)) { connect(m_controller, &KActivities::Controller::activityRemoved, this, &Activities::slotRemoved); connect(m_controller, &KActivities::Controller::activityRemoved, this, &Activities::removed); connect(m_controller, &KActivities::Controller::activityAdded, this, &Activities::added); connect(m_controller, &KActivities::Controller::currentActivityChanged, this, &Activities::slotCurrentChanged); } Activities::~Activities() { - s_self = NULL; + s_self = nullptr; } KActivities::Consumer::ServiceStatus Activities::serviceStatus() const { return m_controller->serviceStatus(); } void Activities::setCurrent(const QString &activity) { m_controller->setCurrentActivity(activity); } void Activities::slotCurrentChanged(const QString &newActivity) { if (m_current == newActivity) { return; } m_previous = m_current; m_current = newActivity; emit currentChanged(newActivity); } void Activities::slotRemoved(const QString &activity) { foreach (Client * client, Workspace::self()->clientList()) { client->setOnActivity(activity, false); } //toss out any session data for it KConfigGroup cg(KSharedConfig::openConfig(), QByteArray("SubSession: ").append(activity.toUtf8()).constData()); cg.deleteGroup(); } void Activities::toggleClientOnActivity(Client* c, const QString &activity, bool dont_activate) { //int old_desktop = c->desktop(); bool was_on_activity = c->isOnActivity(activity); bool was_on_all = c->isOnAllActivities(); //note: all activities === no activities bool enable = was_on_all || !was_on_activity; c->setOnActivity(activity, enable); if (c->isOnActivity(activity) == was_on_activity && c->isOnAllActivities() == was_on_all) // No change return; Workspace *ws = Workspace::self(); if (c->isOnCurrentActivity()) { if (c->wantsTabFocus() && options->focusPolicyIsReasonable() && !was_on_activity && // for stickyness changes //FIXME not sure if the line above refers to the correct activity !dont_activate) ws->requestFocus(c); else ws->restackClientUnderActive(c); } else ws->raiseClient(c); //notifyWindowDesktopChanged( c, old_desktop ); auto transients_stacking_order = ws->ensureStackingOrder(c->transients()); for (auto it = transients_stacking_order.constBegin(); it != transients_stacking_order.constEnd(); ++it) { Client *c = dynamic_cast(*it); if (!c) { continue; } toggleClientOnActivity(c, activity, dont_activate); } ws->updateClientArea(); } bool Activities::start(const QString &id) { Workspace *ws = Workspace::self(); if (ws->sessionSaving()) { return false; //ksmserver doesn't queue requests (yet) } if (!all().contains(id)) { return false; //bogus id } ws->loadSubSessionInfo(id); QDBusInterface ksmserver("org.kde.ksmserver", "/KSMServer", "org.kde.KSMServerInterface"); if (ksmserver.isValid()) { ksmserver.asyncCall("restoreSubSession", id); } else { qCDebug(KWIN_CORE) << "couldn't get ksmserver interface"; return false; } return true; } bool Activities::stop(const QString &id) { if (Workspace::self()->sessionSaving()) { return false; //ksmserver doesn't queue requests (yet) //FIXME what about session *loading*? } //ugly hack to avoid dbus deadlocks QMetaObject::invokeMethod(this, "reallyStop", Qt::QueuedConnection, Q_ARG(QString, id)); //then lie and assume it worked. return true; } void Activities::reallyStop(const QString &id) { Workspace *ws = Workspace::self(); if (ws->sessionSaving()) return; //ksmserver doesn't queue requests (yet) qCDebug(KWIN_CORE) << id; QSet saveSessionIds; QSet dontCloseSessionIds; const ClientList &clients = ws->clientList(); for (ClientList::const_iterator it = clients.constBegin(); it != clients.constEnd(); ++it) { const Client* c = (*it); const QByteArray sessionId = c->sessionId(); if (sessionId.isEmpty()) { continue; //TODO support old wm_command apps too? } //qDebug() << sessionId; //if it's on the activity that's closing, it needs saving //but if a process is on some other open activity, I don't wanna close it yet //this is, of course, complicated by a process having many windows. if (c->isOnAllActivities()) { dontCloseSessionIds << sessionId; continue; } const QStringList activities = c->activities(); foreach (const QString & activityId, activities) { if (activityId == id) { saveSessionIds << sessionId; } else if (running().contains(activityId)) { dontCloseSessionIds << sessionId; } } } ws->storeSubSession(id, saveSessionIds); QStringList saveAndClose; QStringList saveOnly; foreach (const QByteArray & sessionId, saveSessionIds) { if (dontCloseSessionIds.contains(sessionId)) { saveOnly << sessionId; } else { saveAndClose << sessionId; } } qCDebug(KWIN_CORE) << "saveActivity" << id << saveAndClose << saveOnly; //pass off to ksmserver QDBusInterface ksmserver("org.kde.ksmserver", "/KSMServer", "org.kde.KSMServerInterface"); if (ksmserver.isValid()) { ksmserver.asyncCall("saveSubSession", id, saveAndClose, saveOnly); } else { qCDebug(KWIN_CORE) << "couldn't get ksmserver interface"; } } } // namespace diff --git a/client.cpp b/client.cpp index 2be010353..873522efe 100644 --- a/client.cpp +++ b/client.cpp @@ -1,2129 +1,2129 @@ /******************************************************************** 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 . *********************************************************************/ // own #include "client.h" // kwin #ifdef KWIN_BUILD_ACTIVITIES #include "activities.h" #endif #include "atoms.h" #include "client_machine.h" #include "composite.h" #include "cursor.h" #include "deleted.h" #include "focuschain.h" #include "group.h" #include "shadow.h" #ifdef KWIN_BUILD_TABBOX #include "tabbox.h" #endif #include "workspace.h" #include "screenedge.h" #include "decorations/decorationbridge.h" #include "decorations/decoratedclient.h" #include #include // KDE #include #include #include // Qt #include #include #include #include #include #include #include // xcb #include // system #include // c++ #include // Put all externs before the namespace statement to allow the linker // to resolve them properly namespace KWin { const long ClientWinMask = XCB_EVENT_MASK_KEY_PRESS | XCB_EVENT_MASK_KEY_RELEASE | XCB_EVENT_MASK_BUTTON_PRESS | XCB_EVENT_MASK_BUTTON_RELEASE | XCB_EVENT_MASK_KEYMAP_STATE | XCB_EVENT_MASK_BUTTON_MOTION | XCB_EVENT_MASK_POINTER_MOTION | // need this, too! XCB_EVENT_MASK_ENTER_WINDOW | XCB_EVENT_MASK_LEAVE_WINDOW | XCB_EVENT_MASK_FOCUS_CHANGE | XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT; // Creating a client: // - only by calling Workspace::createClient() // - it creates a new client and calls manage() for it // // Destroying a client: // - destroyClient() - only when the window itself has been destroyed // - releaseWindow() - the window is kept, only the client itself is destroyed /** * \class Client client.h * \brief The Client class encapsulates a window decoration frame. */ /** * This ctor is "dumb" - it only initializes data. All the real initialization * is done in manage(). */ Client::Client() : AbstractClient() , m_client() , m_wrapper() , m_frame() , m_activityUpdatesBlocked(false) , m_blockedActivityUpdatesRequireTransients(false) , m_moveResizeGrabWindow() , move_resize_has_keyboard_grab(false) , m_managed(false) , m_transientForId(XCB_WINDOW_NONE) , m_originalTransientForId(XCB_WINDOW_NONE) - , shade_below(NULL) + , shade_below(nullptr) , m_motif(atoms->motif_wm_hints) , blocks_compositing(false) - , shadeHoverTimer(NULL) + , shadeHoverTimer(nullptr) , m_colormap(XCB_COLORMAP_NONE) - , in_group(NULL) - , ping_timer(NULL) + , in_group(nullptr) + , ping_timer(nullptr) , m_killHelperPID(0) , m_pingTimestamp(XCB_TIME_CURRENT_TIME) , m_userTime(XCB_TIME_CURRENT_TIME) // Not known yet - , allowed_actions(0) + , allowed_actions(nullptr) , shade_geometry_change(false) , sm_stacking_order(-1) , activitiesDefined(false) , sessionActivityOverride(false) , needsXWindowMove(false) , m_decoInputExtent() , m_focusOutTimer(nullptr) , m_clientSideDecorated(false) { // TODO: Do all as initialization syncRequest.counter = syncRequest.alarm = XCB_NONE; - syncRequest.timeout = syncRequest.failsafeTimeout = NULL; + syncRequest.timeout = syncRequest.failsafeTimeout = nullptr; syncRequest.lastTimestamp = xTime(); syncRequest.isPending = false; // Set the initial mapping state mapping_state = Withdrawn; - info = NULL; + info = nullptr; shade_mode = ShadeNone; deleting = false; m_fullscreenMode = FullScreenNone; hidden = false; noborder = false; app_noborder = false; ignore_focus_stealing = false; check_active_modal = false; max_mode = MaximizeRestore; //Client to workspace connections require that each //client constructed be connected to the workspace wrapper geom = QRect(0, 0, 100, 100); // So that decorations don't start with size being (0,0) client_size = QSize(100, 100); ready_for_painting = false; // wait for first damage or sync reply connect(clientMachine(), &ClientMachine::localhostChanged, this, &Client::updateCaption); connect(options, &Options::condensedTitleChanged, this, &Client::updateCaption); connect(this, &Client::moveResizeCursorChanged, this, [this] (CursorShape cursor) { xcb_cursor_t nativeCursor = Cursor::x11Cursor(cursor); m_frame.defineCursor(nativeCursor); if (m_decoInputExtent.isValid()) m_decoInputExtent.defineCursor(nativeCursor); if (isMoveResize()) { // changing window attributes doesn't change cursor if there's pointer grab active xcb_change_active_pointer_grab(connection(), nativeCursor, xTime(), 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); } }); // SELI TODO: Initialize xsizehints?? } /** * "Dumb" destructor. */ Client::~Client() { if (m_killHelperPID && !::kill(m_killHelperPID, 0)) { // means the process is alive ::kill(m_killHelperPID, SIGTERM); m_killHelperPID = 0; } if (syncRequest.alarm != XCB_NONE) xcb_sync_destroy_alarm(connection(), syncRequest.alarm); Q_ASSERT(!isMoveResize()); Q_ASSERT(m_client == XCB_WINDOW_NONE); Q_ASSERT(m_wrapper == XCB_WINDOW_NONE); Q_ASSERT(m_frame == XCB_WINDOW_NONE); Q_ASSERT(!check_active_modal); for (auto it = m_connections.constBegin(); it != m_connections.constEnd(); ++it) { disconnect(*it); } } // Use destroyClient() or releaseWindow(), Client instances cannot be deleted directly void Client::deleteClient(Client* c) { delete c; } /** * Releases the window. The client has done its job and the window is still existing. */ void Client::releaseWindow(bool on_shutdown) { Q_ASSERT(!deleting); deleting = true; #ifdef KWIN_BUILD_TABBOX TabBox::TabBox *tabBox = TabBox::TabBox::self(); if (tabBox && tabBox->isDisplayed() && tabBox->currentClient() == this) { tabBox->nextPrev(true); } #endif destroyWindowManagementInterface(); - Deleted* del = NULL; + Deleted* del = nullptr; if (!on_shutdown) { del = Deleted::create(this); } if (isMoveResize()) emit clientFinishUserMovedResized(this); emit windowClosed(this, del); finishCompositing(); RuleBook::self()->discardUsed(this, true); // Remove ForceTemporarily rules StackingUpdatesBlocker blocker(workspace()); if (isMoveResize()) leaveMoveResize(); finishWindowRules(); blockGeometryUpdates(); if (isOnCurrentDesktop() && isShown(true)) addWorkspaceRepaint(visibleRect()); // Grab X during the release to make removing of properties, setting to withdrawn state // and repareting to root an atomic operation (https://lists.kde.org/?l=kde-devel&m=116448102901184&w=2) grabXServer(); exportMappingState(XCB_ICCCM_WM_STATE_WITHDRAWN); setModal(false); // Otherwise its mainwindow wouldn't get focus hidden = true; // So that it's not considered visible anymore (can't use hideClient(), it would set flags) if (!on_shutdown) workspace()->clientHidden(this); m_frame.unmap(); // Destroying decoration would cause ugly visual effect destroyDecoration(); cleanGrouping(); if (!on_shutdown) { workspace()->removeClient(this); // Only when the window is being unmapped, not when closing down KWin (NETWM sections 5.5,5.7) info->setDesktop(0); info->setState(NET::States(), info->state()); // Reset all state flags } xcb_connection_t *c = connection(); m_client.deleteProperty(atoms->kde_net_wm_user_creation_time); m_client.deleteProperty(atoms->net_frame_extents); m_client.deleteProperty(atoms->kde_net_wm_frame_strut); m_client.reparent(rootWindow(), x(), y()); xcb_change_save_set(c, XCB_SET_MODE_DELETE, m_client); m_client.selectInput(XCB_EVENT_MASK_NO_EVENT); if (on_shutdown) // Map the window, so it can be found after another WM is started m_client.map(); // TODO: Preserve minimized, shaded etc. state? else // Make sure it's not mapped if the app unmapped it (#65279). The app // may do map+unmap before we initially map the window by calling rawShow() from manage(). m_client.unmap(); m_client.reset(); m_wrapper.reset(); m_frame.reset(); unblockGeometryUpdates(); // Don't use GeometryUpdatesBlocker, it would now set the geometry if (!on_shutdown) { disownDataPassedToDeleted(); del->unrefWindow(); } deleteClient(this); ungrabXServer(); } /** * Like releaseWindow(), but this one is called when the window has been already destroyed * (E.g. The application closed it) */ void Client::destroyClient() { Q_ASSERT(!deleting); deleting = true; #ifdef KWIN_BUILD_TABBOX TabBox::TabBox *tabBox = TabBox::TabBox::self(); if (tabBox && tabBox->isDisplayed() && tabBox->currentClient() == this) { tabBox->nextPrev(true); } #endif destroyWindowManagementInterface(); Deleted* del = Deleted::create(this); if (isMoveResize()) emit clientFinishUserMovedResized(this); emit windowClosed(this, del); finishCompositing(ReleaseReason::Destroyed); RuleBook::self()->discardUsed(this, true); // Remove ForceTemporarily rules StackingUpdatesBlocker blocker(workspace()); if (isMoveResize()) leaveMoveResize(); finishWindowRules(); blockGeometryUpdates(); if (isOnCurrentDesktop() && isShown(true)) addWorkspaceRepaint(visibleRect()); setModal(false); hidden = true; // So that it's not considered visible anymore workspace()->clientHidden(this); destroyDecoration(); cleanGrouping(); workspace()->removeClient(this); m_client.reset(); // invalidate m_wrapper.reset(); m_frame.reset(); unblockGeometryUpdates(); // Don't use GeometryUpdatesBlocker, it would now set the geometry disownDataPassedToDeleted(); del->unrefWindow(); deleteClient(this); } void Client::updateInputWindow() { if (!Xcb::Extensions::self()->isShapeInputAvailable()) return; QRegion region; if (!noBorder() && isDecorated()) { const QMargins &r = decoration()->resizeOnlyBorders(); const int left = r.left(); const int top = r.top(); const int right = r.right(); const int bottom = r.bottom(); if (left != 0 || top != 0 || right != 0 || bottom != 0) { region = QRegion(-left, -top, decoration()->size().width() + left + right, decoration()->size().height() + top + bottom); region = region.subtracted(decoration()->rect()); } } if (region.isEmpty()) { m_decoInputExtent.reset(); return; } QRect bounds = region.boundingRect(); input_offset = bounds.topLeft(); // Move the bounding rect to screen coordinates bounds.translate(geometry().topLeft()); // Move the region to input window coordinates region.translate(-input_offset); if (!m_decoInputExtent.isValid()) { const uint32_t mask = XCB_CW_OVERRIDE_REDIRECT | XCB_CW_EVENT_MASK; const uint32_t values[] = {true, XCB_EVENT_MASK_ENTER_WINDOW | XCB_EVENT_MASK_LEAVE_WINDOW | XCB_EVENT_MASK_BUTTON_PRESS | XCB_EVENT_MASK_BUTTON_RELEASE | XCB_EVENT_MASK_POINTER_MOTION }; m_decoInputExtent.create(bounds, XCB_WINDOW_CLASS_INPUT_ONLY, mask, values); if (mapping_state == Mapped) m_decoInputExtent.map(); } else { m_decoInputExtent.setGeometry(bounds); } const QVector rects = Xcb::regionToRects(region); xcb_shape_rectangles(connection(), XCB_SHAPE_SO_SET, XCB_SHAPE_SK_INPUT, XCB_CLIP_ORDERING_UNSORTED, m_decoInputExtent, 0, 0, rects.count(), rects.constData()); } void Client::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(); getShadow(); if (check_workspace_pos) checkWorkspacePosition(oldgeom, -2, oldClientGeom); updateInputWindow(); blockGeometryUpdates(false); updateFrameExtents(); } void Client::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::resizeOnlyBordersChanged, this, &Client::updateInputWindow); connect(decoration, &KDecoration2::Decoration::bordersChanged, this, [this]() { updateFrameExtents(); GeometryUpdatesBlocker blocker(this); // TODO: this is obviously idempotent // calculateGravitation(true) would have to operate on the old border sizes // move(calculateGravitation(true)); // move(calculateGravitation(false)); QRect oldgeom = geometry(); plainResize(sizeForClientSize(clientSize()), ForceGeometrySet); if (!isShade()) checkWorkspacePosition(oldgeom); emit geometryShapeChanged(this, oldgeom); } ); connect(decoratedClient()->decoratedClient(), &KDecoration2::DecoratedClient::widthChanged, this, &Client::updateInputWindow); connect(decoratedClient()->decoratedClient(), &KDecoration2::DecoratedClient::heightChanged, this, &Client::updateInputWindow); } setDecoration(decoration); move(calculateGravitation(false)); plainResize(sizeForClientSize(clientSize()), ForceGeometrySet); if (Compositor::compositing()) { discardWindowPixmap(); } emit geometryShapeChanged(this, oldgeom); } void Client::destroyDecoration() { QRect oldgeom = geometry(); if (isDecorated()) { QPoint grav = calculateGravitation(true); AbstractClient::destroyDecoration(); plainResize(sizeForClientSize(clientSize()), ForceGeometrySet); move(grav); if (compositing()) discardWindowPixmap(); if (!deleting) { emit geometryShapeChanged(this, oldgeom); } } m_decoInputExtent.reset(); } void Client::layoutDecorationRects(QRect &left, QRect &top, QRect &right, QRect &bottom) const { if (!isDecorated()) { return; } QRect r = decoration()->rect(); NETStrut strut = info->frameOverlap(); // Ignore the overlap strut when compositing is disabled if (!compositing()) strut.left = strut.top = strut.right = strut.bottom = 0; else if (strut.left == -1 && strut.top == -1 && strut.right == -1 && strut.bottom == -1) { top = QRect(r.x(), r.y(), r.width(), r.height() / 3); left = QRect(r.x(), r.y() + top.height(), width() / 2, r.height() / 3); right = QRect(r.x() + left.width(), r.y() + top.height(), r.width() - left.width(), left.height()); bottom = QRect(r.x(), r.y() + top.height() + left.height(), r.width(), r.height() - left.height() - top.height()); return; } top = QRect(r.x(), r.y(), r.width(), borderTop() + strut.top); bottom = QRect(r.x(), r.y() + r.height() - borderBottom() - strut.bottom, r.width(), borderBottom() + strut.bottom); left = QRect(r.x(), r.y() + top.height(), borderLeft() + strut.left, r.height() - top.height() - bottom.height()); right = QRect(r.x() + r.width() - borderRight() - strut.right, r.y() + top.height(), borderRight() + strut.right, r.height() - top.height() - bottom.height()); } QRect Client::transparentRect() const { if (isShade()) return QRect(); NETStrut strut = info->frameOverlap(); // Ignore the strut when compositing is disabled or the decoration doesn't support it if (!compositing()) strut.left = strut.top = strut.right = strut.bottom = 0; else if (strut.left == -1 && strut.top == -1 && strut.right == -1 && strut.bottom == -1) return QRect(); const QRect r = QRect(clientPos(), clientSize()) .adjusted(strut.left, strut.top, -strut.right, -strut.bottom); if (r.isValid()) return r; return QRect(); } void Client::detectNoBorder() { if (shape()) { noborder = true; app_noborder = true; return; } switch(windowType()) { case NET::Desktop : case NET::Dock : case NET::TopMenu : case NET::Splash : case NET::Notification : case NET::OnScreenDisplay : case NET::CriticalNotification : noborder = true; app_noborder = true; break; case NET::Unknown : case NET::Normal : case NET::Toolbar : case NET::Menu : case NET::Dialog : case NET::Utility : noborder = false; break; default: abort(); } // NET::Override is some strange beast without clear definition, usually // just meaning "noborder", so let's treat it only as such flag, and ignore it as // a window type otherwise (SUPPORTED_WINDOW_TYPES_MASK doesn't include it) if (info->windowType(NET::OverrideMask) == NET::Override) { noborder = true; app_noborder = true; } } void Client::updateFrameExtents() { NETStrut strut; strut.left = borderLeft(); strut.right = borderRight(); strut.top = borderTop(); strut.bottom = borderBottom(); info->setFrameExtents(strut); } Xcb::Property Client::fetchGtkFrameExtents() const { return Xcb::Property(false, m_client, atoms->gtk_frame_extents, XCB_ATOM_CARDINAL, 0, 4); } void Client::readGtkFrameExtents(Xcb::Property &prop) { m_clientSideDecorated = !prop.isNull() && prop->type != 0; emit clientSideDecoratedChanged(); } void Client::detectGtkFrameExtents() { Xcb::Property prop = fetchGtkFrameExtents(); readGtkFrameExtents(prop); } /** * Resizes the decoration, and makes sure the decoration widget gets resize event * even if the size hasn't changed. This is needed to make sure the decoration * re-layouts (e.g. when maximization state changes, * the decoration may alter some borders, but the actual size * of the decoration stays the same). */ void Client::resizeDecoration() { triggerDecorationRepaint(); updateInputWindow(); } bool Client::userNoBorder() const { return noborder; } bool Client::isFullScreenable() const { if (!rules()->checkFullScreen(true)) { return false; } if (rules()->checkStrictGeometry(true)) { // check geometry constraints (rule to obey is set) const QRect fsarea = workspace()->clientArea(FullScreenArea, this); if (sizeForClientSize(fsarea.size(), SizemodeAny, true) != fsarea.size()) { return false; // the app wouldn't fit exactly fullscreen geometry due to its strict geometry requirements } } // don't check size constrains - some apps request fullscreen despite requesting fixed size return !isSpecialWindow(); // also better disallow only weird types to go fullscreen } bool Client::noBorder() const { return userNoBorder() || isFullScreen(); } bool Client::userCanSetNoBorder() const { return !isFullScreen() && !isShade(); } void Client::setNoBorder(bool set) { if (!userCanSetNoBorder()) return; set = rules()->checkNoBorder(set); if (noborder == set) return; noborder = set; updateDecoration(true, false); updateWindowRules(Rules::NoBorder); } void Client::checkNoBorder() { setNoBorder(app_noborder); } bool Client::wantsShadowToBeRendered() const { return !isFullScreen() && maximizeMode() != MaximizeFull; } void Client::updateShape() { if (shape()) { // Workaround for #19644 - Shaped windows shouldn't have decoration if (!app_noborder) { // Only when shape is detected for the first time, still let the user to override app_noborder = true; noborder = rules()->checkNoBorder(true); updateDecoration(true); } if (noBorder()) { xcb_shape_combine(connection(), XCB_SHAPE_SO_SET, XCB_SHAPE_SK_BOUNDING, XCB_SHAPE_SK_BOUNDING, frameId(), clientPos().x(), clientPos().y(), window()); } } else if (app_noborder) { xcb_shape_mask(connection(), XCB_SHAPE_SO_SET, XCB_SHAPE_SK_BOUNDING, frameId(), 0, 0, XCB_PIXMAP_NONE); detectNoBorder(); app_noborder = noborder; noborder = rules()->checkNoBorder(noborder || m_motif.noBorder()); updateDecoration(true); } // Decoration mask (i.e. 'else' here) setting is done in setMask() // when the decoration calls it or when the decoration is created/destroyed updateInputShape(); if (compositing()) { addRepaintFull(); addWorkspaceRepaint(visibleRect()); // In case shape change removes part of this window } emit geometryShapeChanged(this, geometry()); } static Xcb::Window shape_helper_window(XCB_WINDOW_NONE); void Client::cleanupX11() { shape_helper_window.reset(); } void Client::updateInputShape() { if (hiddenPreview()) // Sets it to none, don't change return; if (Xcb::Extensions::self()->isShapeInputAvailable()) { // There appears to be no way to find out if a window has input // shape set or not, so always propagate the input shape // (it's the same like the bounding shape by default). // Also, build the shape using a helper window, not directly // in the frame window, because the sequence set-shape-to-frame, // remove-shape-of-client, add-input-shape-of-client has the problem // that after the second step there's a hole in the input shape // until the real shape of the client is added and that can make // the window lose focus (which is a problem with mouse focus policies) // TODO: It seems there is, after all - XShapeGetRectangles() - but maybe this is better if (!shape_helper_window.isValid()) shape_helper_window.create(QRect(0, 0, 1, 1)); shape_helper_window.resize(width(), height()); xcb_connection_t *c = connection(); xcb_shape_combine(c, XCB_SHAPE_SO_SET, XCB_SHAPE_SK_INPUT, XCB_SHAPE_SK_BOUNDING, shape_helper_window, 0, 0, frameId()); xcb_shape_combine(c, XCB_SHAPE_SO_SUBTRACT, XCB_SHAPE_SK_INPUT, XCB_SHAPE_SK_BOUNDING, shape_helper_window, clientPos().x(), clientPos().y(), window()); xcb_shape_combine(c, XCB_SHAPE_SO_UNION, XCB_SHAPE_SK_INPUT, XCB_SHAPE_SK_INPUT, shape_helper_window, clientPos().x(), clientPos().y(), window()); xcb_shape_combine(c, XCB_SHAPE_SO_SET, XCB_SHAPE_SK_INPUT, XCB_SHAPE_SK_INPUT, frameId(), 0, 0, shape_helper_window); } } void Client::hideClient(bool hide) { if (hidden == hide) return; hidden = hide; updateVisibility(); } bool Client::setupCompositing() { if (!Toplevel::setupCompositing()){ return false; } updateVisibility(); // for internalKeep() return true; } void Client::finishCompositing(ReleaseReason releaseReason) { Toplevel::finishCompositing(releaseReason); updateVisibility(); // for safety in case KWin is just resizing the window resetHaveResizeEffect(); } /** * Returns whether the window is minimizable or not */ bool Client::isMinimizable() const { if (isSpecialWindow() && !isTransient()) return false; if (!rules()->checkMinimize(true)) return false; if (isTransient()) { // #66868 - Let other xmms windows be minimized when the mainwindow is minimized bool shown_mainwindow = false; auto mainclients = mainClients(); for (auto it = mainclients.constBegin(); it != mainclients.constEnd(); ++it) if ((*it)->isShown(true)) shown_mainwindow = true; if (!shown_mainwindow) return true; } #if 0 // This is here because kicker's taskbar doesn't provide separate entries // for windows with an explicitly given parent // TODO: perhaps this should be redone // Disabled for now, since at least modal dialogs should be minimizable // (resulting in the mainwindow being minimized too). if (transientFor() != NULL) return false; #endif if (!wantsTabFocus()) // SELI, TODO: - NET::Utility? why wantsTabFocus() - skiptaskbar? ? return false; return true; } void Client::doMinimize() { updateVisibility(); updateAllowedActions(); workspace()->updateMinimizedOfTransients(this); } QRect Client::iconGeometry() const { NETRect r = info->iconGeometry(); QRect geom(r.pos.x, r.pos.y, r.size.width, r.size.height); if (geom.isValid()) return geom; else { // Check all mainwindows of this window (recursively) foreach (AbstractClient * amainwin, mainClients()) { Client *mainwin = dynamic_cast(amainwin); if (!mainwin) { continue; } geom = mainwin->iconGeometry(); if (geom.isValid()) return geom; } // No mainwindow (or their parents) with icon geometry was found return AbstractClient::iconGeometry(); } } bool Client::isShadeable() const { return !isSpecialWindow() && !noBorder() && (rules()->checkShade(ShadeNormal) != rules()->checkShade(ShadeNone)); } void Client::setShade(ShadeMode mode) { if (mode == ShadeHover && isMove()) return; // causes geometry breaks and is probably nasty if (isSpecialWindow() || noBorder()) mode = ShadeNone; mode = rules()->checkShade(mode); if (shade_mode == mode) return; bool was_shade = isShade(); ShadeMode was_shade_mode = shade_mode; shade_mode = mode; // Decorations may turn off some borders when shaded // this has to happen _before_ the tab alignment since it will restrict the minimum geometry #if 0 if (decoration) decoration->borders(border_left, border_right, border_top, border_bottom); #endif if (was_shade == isShade()) { // Decoration may want to update after e.g. hover-shade changes emit shadeChanged(); return; // No real change in shaded state } Q_ASSERT(isDecorated()); // noborder windows can't be shaded GeometryUpdatesBlocker blocker(this); // TODO: All this unmapping, resizing etc. feels too much duplicated from elsewhere if (isShade()) { // shade_mode == ShadeNormal addWorkspaceRepaint(visibleRect()); // Shade shade_geometry_change = true; QSize s(sizeForClientSize(QSize(clientSize()))); s.setHeight(borderTop() + borderBottom()); m_wrapper.selectInput(ClientWinMask); // Avoid getting UnmapNotify m_wrapper.unmap(); m_client.unmap(); m_wrapper.selectInput(ClientWinMask | XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY); exportMappingState(XCB_ICCCM_WM_STATE_ICONIC); plainResize(s); shade_geometry_change = false; if (was_shade_mode == ShadeHover) { if (shade_below && workspace()->stackingOrder().indexOf(shade_below) > -1) workspace()->restack(this, shade_below, true); if (isActive()) workspace()->activateNextClient(this); } else if (isActive()) { workspace()->focusToNull(); } } else { shade_geometry_change = true; if (decoratedClient()) decoratedClient()->signalShadeChange(); QSize s(sizeForClientSize(clientSize())); shade_geometry_change = false; plainResize(s); geom_restore = geometry(); if ((shade_mode == ShadeHover || shade_mode == ShadeActivated) && rules()->checkAcceptFocus(info->input())) setActive(true); if (shade_mode == ShadeHover) { ToplevelList order = workspace()->stackingOrder(); // invalidate, since "this" could be the topmost toplevel and shade_below dangeling - shade_below = NULL; + shade_below = nullptr; // this is likely related to the index parameter?! for (int idx = order.indexOf(this) + 1; idx < order.count(); ++idx) { shade_below = qobject_cast(order.at(idx)); if (shade_below) { break; } } if (shade_below && shade_below->isNormalWindow()) workspace()->raiseClient(this); else - shade_below = NULL; + shade_below = nullptr; } m_wrapper.map(); m_client.map(); exportMappingState(XCB_ICCCM_WM_STATE_NORMAL); if (isActive()) workspace()->requestFocus(this); } info->setState(isShade() ? NET::Shaded : NET::States(), NET::Shaded); info->setState(isShown(false) ? NET::States() : NET::Hidden, NET::Hidden); discardWindowPixmap(); updateVisibility(); updateAllowedActions(); updateWindowRules(Rules::Shade); emit shadeChanged(); } void Client::shadeHover() { setShade(ShadeHover); cancelShadeHoverTimer(); } void Client::shadeUnhover() { setShade(ShadeNormal); cancelShadeHoverTimer(); } void Client::cancelShadeHoverTimer() { delete shadeHoverTimer; - shadeHoverTimer = 0; + shadeHoverTimer = nullptr; } void Client::toggleShade() { // If the mode is ShadeHover or ShadeActive, cancel shade too setShade(shade_mode == ShadeNone ? ShadeNormal : ShadeNone); } void Client::updateVisibility() { if (deleting) return; if (hidden) { info->setState(NET::Hidden, NET::Hidden); setSkipTaskbar(true); // Also hide from taskbar if (compositing() && options->hiddenPreviews() == HiddenPreviewsAlways) internalKeep(); else internalHide(); return; } setSkipTaskbar(originalSkipTaskbar()); // Reset from 'hidden' if (isMinimized()) { info->setState(NET::Hidden, NET::Hidden); if (compositing() && options->hiddenPreviews() == HiddenPreviewsAlways) internalKeep(); else internalHide(); return; } info->setState(NET::States(), NET::Hidden); if (!isOnCurrentDesktop()) { if (compositing() && options->hiddenPreviews() != HiddenPreviewsNever) internalKeep(); else internalHide(); return; } if (!isOnCurrentActivity()) { if (compositing() && options->hiddenPreviews() != HiddenPreviewsNever) internalKeep(); else internalHide(); return; } internalShow(); } /** * Sets the client window's mapping state. Possible values are * WithdrawnState, IconicState, NormalState. */ void Client::exportMappingState(int s) { Q_ASSERT(m_client != XCB_WINDOW_NONE); Q_ASSERT(!deleting || s == XCB_ICCCM_WM_STATE_WITHDRAWN); if (s == XCB_ICCCM_WM_STATE_WITHDRAWN) { m_client.deleteProperty(atoms->wm_state); return; } Q_ASSERT(s == XCB_ICCCM_WM_STATE_NORMAL || s == XCB_ICCCM_WM_STATE_ICONIC); int32_t data[2]; data[0] = s; data[1] = XCB_NONE; m_client.changeProperty(atoms->wm_state, atoms->wm_state, 32, 2, data); } void Client::internalShow() { if (mapping_state == Mapped) return; MappingState old = mapping_state; mapping_state = Mapped; if (old == Unmapped || old == Withdrawn) map(); if (old == Kept) { m_decoInputExtent.map(); updateHiddenPreview(); } emit windowShown(this); } void Client::internalHide() { if (mapping_state == Unmapped) return; MappingState old = mapping_state; mapping_state = Unmapped; if (old == Mapped || old == Kept) unmap(); if (old == Kept) updateHiddenPreview(); addWorkspaceRepaint(visibleRect()); workspace()->clientHidden(this); emit windowHidden(this); } void Client::internalKeep() { Q_ASSERT(compositing()); if (mapping_state == Kept) return; MappingState old = mapping_state; mapping_state = Kept; if (old == Unmapped || old == Withdrawn) map(); m_decoInputExtent.unmap(); if (isActive()) workspace()->focusToNull(); // get rid of input focus, bug #317484 updateHiddenPreview(); addWorkspaceRepaint(visibleRect()); workspace()->clientHidden(this); } /** * Maps (shows) the client. Note that it is mapping state of the frame, * not necessarily the client window itself (i.e. a shaded window is here * considered mapped, even though it is in IconicState). */ void Client::map() { // XComposite invalidates backing pixmaps on unmap (minimize, different // virtual desktop, etc.). We kept the last known good pixmap around // for use in effects, but now we want to have access to the new pixmap if (compositing()) discardWindowPixmap(); m_frame.map(); if (!isShade()) { m_wrapper.map(); m_client.map(); m_decoInputExtent.map(); exportMappingState(XCB_ICCCM_WM_STATE_NORMAL); } else exportMappingState(XCB_ICCCM_WM_STATE_ICONIC); addLayerRepaint(visibleRect()); } /** * Unmaps the client. Again, this is about the frame. */ void Client::unmap() { // Here it may look like a race condition, as some other client might try to unmap // the window between these two XSelectInput() calls. However, they're supposed to // use XWithdrawWindow(), which also sends a synthetic event to the root window, // which won't be missed, so this shouldn't be a problem. The chance the real UnmapNotify // will be missed is also very minimal, so I don't think it's needed to grab the server // here. m_wrapper.selectInput(ClientWinMask); // Avoid getting UnmapNotify m_frame.unmap(); m_wrapper.unmap(); m_client.unmap(); m_decoInputExtent.unmap(); m_wrapper.selectInput(ClientWinMask | XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY); exportMappingState(XCB_ICCCM_WM_STATE_ICONIC); } /** * XComposite doesn't keep window pixmaps of unmapped windows, which means * there wouldn't be any previews of windows that are minimized or on another * virtual desktop. Therefore rawHide() actually keeps such windows mapped. * However special care needs to be taken so that such windows don't interfere. * Therefore they're put very low in the stacking order and they have input shape * set to none, which hopefully is enough. If there's no input shape available, * then it's hoped that there will be some other desktop above it *shrug*. * Using normal shape would be better, but that'd affect other things, e.g. painting * of the actual preview. */ void Client::updateHiddenPreview() { if (hiddenPreview()) { workspace()->forceRestacking(); if (Xcb::Extensions::self()->isShapeInputAvailable()) { xcb_shape_rectangles(connection(), XCB_SHAPE_SO_SET, XCB_SHAPE_SK_INPUT, - XCB_CLIP_ORDERING_UNSORTED, frameId(), 0, 0, 0, NULL); + XCB_CLIP_ORDERING_UNSORTED, frameId(), 0, 0, 0, nullptr); } } else { workspace()->forceRestacking(); updateInputShape(); } } void Client::sendClientMessage(xcb_window_t w, xcb_atom_t a, xcb_atom_t protocol, uint32_t data1, uint32_t data2, uint32_t data3, xcb_timestamp_t timestamp) { xcb_client_message_event_t ev; memset(&ev, 0, sizeof(ev)); ev.response_type = XCB_CLIENT_MESSAGE; ev.window = w; ev.type = a; ev.format = 32; ev.data.data32[0] = protocol; ev.data.data32[1] = timestamp; ev.data.data32[2] = data1; ev.data.data32[3] = data2; ev.data.data32[4] = data3; uint32_t eventMask = 0; if (w == rootWindow()) { eventMask = XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT; // Magic! } xcb_send_event(connection(), false, w, eventMask, reinterpret_cast(&ev)); xcb_flush(connection()); } /** * Returns whether the window may be closed (have a close button) */ bool Client::isCloseable() const { return rules()->checkCloseable(m_motif.close() && !isSpecialWindow()); } /** * Closes the window by either sending a delete_window message or using XKill. */ void Client::closeWindow() { if (!isCloseable()) return; // Update user time, because the window may create a confirming dialog. updateUserTime(); if (info->supportsProtocol(NET::DeleteWindowProtocol)) { sendClientMessage(window(), atoms->wm_protocols, atoms->wm_delete_window); pingWindow(); } else // Client will not react on wm_delete_window. We have not choice // but destroy his connection to the XServer. killWindow(); } /** * Kills the window via XKill */ void Client::killWindow() { qCDebug(KWIN_CORE) << "Client::killWindow():" << caption(); killProcess(false); m_client.kill(); // Always kill this client at the server destroyClient(); } /** * Send a ping to the window using _NET_WM_PING if possible if it * doesn't respond within a reasonable time, it will be killed. */ void Client::pingWindow() { if (!info->supportsProtocol(NET::PingProtocol)) return; // Can't ping :( if (options->killPingTimeout() == 0) return; // Turned off - if (ping_timer != NULL) + if (ping_timer != nullptr) return; // Pinging already ping_timer = new QTimer(this); connect(ping_timer, &QTimer::timeout, this, [this]() { if (unresponsive()) { qCDebug(KWIN_CORE) << "Final ping timeout, asking to kill:" << caption(); ping_timer->deleteLater(); ping_timer = nullptr; killProcess(true, m_pingTimestamp); return; } qCDebug(KWIN_CORE) << "First ping timeout:" << caption(); setUnresponsive(true); ping_timer->start(); } ); ping_timer->setSingleShot(true); // we'll run the timer twice, at first we'll desaturate the window // and the second time we'll show the "do you want to kill" prompt ping_timer->start(options->killPingTimeout() / 2); m_pingTimestamp = xTime(); workspace()->sendPingToWindow(window(), m_pingTimestamp); } void Client::gotPing(xcb_timestamp_t timestamp) { // Just plain compare is not good enough because of 64bit and truncating and whatnot if (NET::timestampCompare(timestamp, m_pingTimestamp) != 0) return; delete ping_timer; - ping_timer = NULL; + ping_timer = nullptr; setUnresponsive(false); if (m_killHelperPID && !::kill(m_killHelperPID, 0)) { // means the process is alive ::kill(m_killHelperPID, SIGTERM); m_killHelperPID = 0; } } void Client::killProcess(bool ask, xcb_timestamp_t timestamp) { if (m_killHelperPID && !::kill(m_killHelperPID, 0)) // means the process is alive return; Q_ASSERT(!ask || timestamp != XCB_TIME_CURRENT_TIME); pid_t pid = info->pid(); if (pid <= 0 || clientMachine()->hostName().isEmpty()) // Needed properties missing return; qCDebug(KWIN_CORE) << "Kill process:" << pid << "(" << clientMachine()->hostName() << ")"; if (!ask) { if (!clientMachine()->isLocal()) { QStringList lst; lst << QString::fromUtf8(clientMachine()->hostName()) << QStringLiteral("kill") << QString::number(pid); QProcess::startDetached(QStringLiteral("xon"), lst); } else ::kill(pid, SIGTERM); } else { QString hostname = clientMachine()->isLocal() ? QStringLiteral("localhost") : QString::fromUtf8(clientMachine()->hostName()); // execute helper from build dir or the system installed one const QFileInfo buildDirBinary{QDir{QCoreApplication::applicationDirPath()}, QStringLiteral("kwin_killer_helper")}; QProcess::startDetached(buildDirBinary.exists() ? buildDirBinary.absoluteFilePath() : QStringLiteral(KWIN_KILLER_BIN), QStringList() << QStringLiteral("--pid") << QString::number(unsigned(pid)) << QStringLiteral("--hostname") << hostname << QStringLiteral("--windowname") << captionNormal() << QStringLiteral("--applicationname") << QString::fromUtf8(resourceClass()) << QStringLiteral("--wid") << QString::number(window()) << QStringLiteral("--timestamp") << QString::number(timestamp), QString(), &m_killHelperPID); } } void Client::doSetSkipTaskbar() { info->setState(skipTaskbar() ? NET::SkipTaskbar : NET::States(), NET::SkipTaskbar); } void Client::doSetSkipPager() { info->setState(skipPager() ? NET::SkipPager : NET::States(), NET::SkipPager); } void Client::doSetSkipSwitcher() { info->setState(skipSwitcher() ? NET::SkipSwitcher : NET::States(), NET::SkipSwitcher); } void Client::doSetDesktop(int desktop, int was_desk) { Q_UNUSED(desktop) Q_UNUSED(was_desk) updateVisibility(); } /** * Sets whether the client is on @p activity. * If you remove it from its last activity, then it's on all activities. * * Note: If it was on all activities and you try to remove it from one, nothing will happen; * I don't think that's an important enough use case to handle here. */ void Client::setOnActivity(const QString &activity, bool enable) { #ifdef KWIN_BUILD_ACTIVITIES if (! Activities::self()) { return; } QStringList newActivitiesList = activities(); if (newActivitiesList.contains(activity) == enable) //nothing to do return; if (enable) { QStringList allActivities = Activities::self()->all(); if (!allActivities.contains(activity)) //bogus ID return; newActivitiesList.append(activity); } else newActivitiesList.removeOne(activity); setOnActivities(newActivitiesList); #else Q_UNUSED(activity) Q_UNUSED(enable) #endif } /** * set exactly which activities this client is on */ void Client::setOnActivities(QStringList newActivitiesList) { #ifdef KWIN_BUILD_ACTIVITIES if (!Activities::self()) { return; } QString joinedActivitiesList = newActivitiesList.join(QStringLiteral(",")); joinedActivitiesList = rules()->checkActivity(joinedActivitiesList, false); newActivitiesList = joinedActivitiesList.split(u',', QString::SkipEmptyParts); QStringList allActivities = Activities::self()->all(); auto it = newActivitiesList.begin(); while (it != newActivitiesList.end()) { if (! allActivities.contains(*it)) { it = newActivitiesList.erase(it); } else { it++; } } if (// If we got the request to be on all activities explicitly newActivitiesList.isEmpty() || joinedActivitiesList == Activities::nullUuid() || // If we got a list of activities that covers all activities (newActivitiesList.count() > 1 && newActivitiesList.count() == allActivities.count())) { activityList.clear(); const QByteArray nullUuid = Activities::nullUuid().toUtf8(); m_client.changeProperty(atoms->activities, XCB_ATOM_STRING, 8, nullUuid.length(), nullUuid.constData()); } else { QByteArray joined = joinedActivitiesList.toLatin1(); activityList = newActivitiesList; m_client.changeProperty(atoms->activities, XCB_ATOM_STRING, 8, joined.length(), joined.constData()); } updateActivities(false); #else Q_UNUSED(newActivitiesList) #endif } void Client::blockActivityUpdates(bool b) { if (b) { ++m_activityUpdatesBlocked; } else { Q_ASSERT(m_activityUpdatesBlocked); --m_activityUpdatesBlocked; if (!m_activityUpdatesBlocked) updateActivities(m_blockedActivityUpdatesRequireTransients); } } /** * update after activities changed */ void Client::updateActivities(bool includeTransients) { if (m_activityUpdatesBlocked) { m_blockedActivityUpdatesRequireTransients |= includeTransients; return; } emit activitiesChanged(this); m_blockedActivityUpdatesRequireTransients = false; // reset FocusChain::self()->update(this, FocusChain::MakeFirst); updateVisibility(); updateWindowRules(Rules::Activity); } /** * Returns the list of activities the client window is on. * if it's on all activities, the list will be empty. * Don't use this, use isOnActivity() and friends (from class Toplevel) */ QStringList Client::activities() const { if (sessionActivityOverride) { return QStringList(); } return activityList; } /** * if @p on is true, sets on all activities. * if it's false, sets it to only be on the current activity */ void Client::setOnAllActivities(bool on) { #ifdef KWIN_BUILD_ACTIVITIES if (on == isOnAllActivities()) return; if (on) { setOnActivities(QStringList()); } else { setOnActivity(Activities::self()->current(), true); } #else Q_UNUSED(on) #endif } /** * Performs the actual focusing of the window using XSetInputFocus and WM_TAKE_FOCUS */ void Client::takeFocus() { if (rules()->checkAcceptFocus(info->input())) m_client.focus(); else demandAttention(false); // window cannot take input, at least withdraw urgency if (info->supportsProtocol(NET::TakeFocusProtocol)) { sendClientMessage(window(), atoms->wm_protocols, atoms->wm_take_focus, 0, 0, 0, XCB_CURRENT_TIME); } workspace()->setShouldGetFocus(this); bool breakShowingDesktop = !keepAbove(); if (breakShowingDesktop) { foreach (const Client *c, group()->members()) { if (c->isDesktop()) { breakShowingDesktop = false; break; } } } if (breakShowingDesktop) workspace()->setShowingDesktop(false); } /** * 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. * * \sa contextHelp() */ bool Client::providesContextHelp() const { return info->supportsProtocol(NET::ContextHelpProtocol); } /** * Invokes context help on the window. Only works if the window * actually provides context help. * * \sa providesContextHelp() */ void Client::showContextHelp() { if (info->supportsProtocol(NET::ContextHelpProtocol)) { sendClientMessage(window(), atoms->wm_protocols, atoms->net_wm_context_help); } } /** * Fetches the window's caption (WM_NAME property). It will be * stored in the client's caption(). */ void Client::fetchName() { setCaption(readName()); } static inline QString readNameProperty(xcb_window_t w, xcb_atom_t atom) { const auto cookie = xcb_icccm_get_text_property_unchecked(connection(), w, atom); xcb_icccm_get_text_property_reply_t reply; if (xcb_icccm_get_wm_name_reply(connection(), cookie, &reply, nullptr)) { QString retVal; if (reply.encoding == atoms->utf8_string) { retVal = QString::fromUtf8(QByteArray(reply.name, reply.name_len)); } else if (reply.encoding == XCB_ATOM_STRING) { retVal = QString::fromLocal8Bit(QByteArray(reply.name, reply.name_len)); } xcb_icccm_get_text_property_reply_wipe(&reply); return retVal.simplified(); } return QString(); } QString Client::readName() const { if (info->name() && info->name()[0] != '\0') return QString::fromUtf8(info->name()).simplified(); else { return readNameProperty(window(), XCB_ATOM_WM_NAME); } } // The list is taken from https://www.unicode.org/reports/tr9/ (#154840) static const QChar LRM(0x200E); void Client::setCaption(const QString& _s, bool force) { QString s(_s); for (int i = 0; i < s.length(); ) { if (!s[i].isPrint()) { if (QChar(s[i]).isHighSurrogate() && i + 1 < s.length() && QChar(s[i + 1]).isLowSurrogate()) { const uint uc = QChar::surrogateToUcs4(s[i], s[i + 1]); if (!QChar::isPrint(uc)) { s.remove(i, 2); } else { i += 2; } continue; } s.remove(i, 1); continue; } ++i; } const bool changed = (s != cap_normal); if (!force && !changed) { return; } cap_normal = s; if (!force && !changed) { emit captionChanged(); return; } bool reset_name = force; bool was_suffix = (!cap_suffix.isEmpty()); cap_suffix.clear(); QString machine_suffix; if (!options->condensedTitle()) { // machine doesn't qualify for "clean" if (clientMachine()->hostName() != ClientMachine::localhost() && !clientMachine()->isLocal()) machine_suffix = QLatin1String(" <@") + QString::fromUtf8(clientMachine()->hostName()) + QLatin1Char('>') + LRM; } QString shortcut_suffix = shortcutCaptionSuffix(); cap_suffix = machine_suffix + shortcut_suffix; if ((!isSpecialWindow() || isToolbar()) && findClientWithSameCaption()) { int i = 2; do { cap_suffix = machine_suffix + QLatin1String(" <") + QString::number(i) + QLatin1Char('>') + LRM; i++; } while (findClientWithSameCaption()); info->setVisibleName(caption().toUtf8().constData()); reset_name = false; } if ((was_suffix && cap_suffix.isEmpty()) || reset_name) { // If it was new window, it may have old value still set, if the window is reused info->setVisibleName(""); info->setVisibleIconName(""); } else if (!cap_suffix.isEmpty() && !cap_iconic.isEmpty()) // Keep the same suffix in iconic name if it's set info->setVisibleIconName(QString(cap_iconic + cap_suffix).toUtf8().constData()); emit captionChanged(); } void Client::updateCaption() { setCaption(cap_normal, true); } void Client::fetchIconicName() { QString s; if (info->iconName() && info->iconName()[0] != '\0') s = QString::fromUtf8(info->iconName()); else s = readNameProperty(window(), XCB_ATOM_WM_ICON_NAME); if (s != cap_iconic) { bool was_set = !cap_iconic.isEmpty(); cap_iconic = s; if (!cap_suffix.isEmpty()) { if (!cap_iconic.isEmpty()) // Keep the same suffix in iconic name if it's set info->setVisibleIconName(QString(s + cap_suffix).toUtf8().constData()); else if (was_set) info->setVisibleIconName(""); } } } void Client::setClientShown(bool shown) { if (deleting) return; // Don't change shown status if this client is being deleted if (shown != hidden) return; // nothing to change hidden = !shown; if (shown) { map(); takeFocus(); autoRaise(); FocusChain::self()->update(this, FocusChain::MakeFirst); } else { unmap(); // Don't move tabs to the end of the list when another tab get's activated FocusChain::self()->update(this, FocusChain::MakeLast); addWorkspaceRepaint(visibleRect()); } } void Client::getMotifHints() { const bool wasClosable = m_motif.close(); const bool wasNoBorder = m_motif.noBorder(); if (m_managed) // only on property change, initial read is prefetched m_motif.fetch(); m_motif.read(); if (m_motif.hasDecoration() && m_motif.noBorder() != wasNoBorder) { // If we just got a hint telling us to hide decorations, we do so. if (m_motif.noBorder()) noborder = rules()->checkNoBorder(true); // If the Motif hint is now telling us to show decorations, we only do so if the app didn't // instruct us to hide decorations in some other way, though. else if (!app_noborder) noborder = rules()->checkNoBorder(false); } // mminimize; - Ignore, bogus - E.g. shading or sending to another desktop is "minimizing" too // mmaximize; - Ignore, bogus - Maximizing is basically just resizing const bool closabilityChanged = wasClosable != m_motif.close(); if (isManaged()) updateDecoration(true); // Check if noborder state has changed if (closabilityChanged) { emit closeableChanged(isCloseable()); } } void Client::getIcons() { // First read icons from the window itself const QString themedIconName = iconFromDesktopFile(); if (!themedIconName.isEmpty()) { setIcon(QIcon::fromTheme(themedIconName)); return; } QIcon icon; auto readIcon = [this, &icon](int size, bool scale = true) { const QPixmap pix = KWindowSystem::icon(window(), size, size, scale, KWindowSystem::NETWM | KWindowSystem::WMHints, info); if (!pix.isNull()) { icon.addPixmap(pix); } }; readIcon(16); readIcon(32); readIcon(48, false); readIcon(64, false); readIcon(128, false); if (icon.isNull()) { // Then try window group icon = group()->icon(); } if (icon.isNull() && isTransient()) { // Then mainclients auto mainclients = mainClients(); for (auto it = mainclients.constBegin(); it != mainclients.constEnd() && icon.isNull(); ++it) { if (!(*it)->icon().isNull()) { icon = (*it)->icon(); break; } } } if (icon.isNull()) { // And if nothing else, load icon from classhint or xapp icon icon.addPixmap(KWindowSystem::icon(window(), 32, 32, true, KWindowSystem::ClassHint | KWindowSystem::XApp, info)); icon.addPixmap(KWindowSystem::icon(window(), 16, 16, true, KWindowSystem::ClassHint | KWindowSystem::XApp, info)); icon.addPixmap(KWindowSystem::icon(window(), 64, 64, false, KWindowSystem::ClassHint | KWindowSystem::XApp, info)); icon.addPixmap(KWindowSystem::icon(window(), 128, 128, false, KWindowSystem::ClassHint | KWindowSystem::XApp, info)); } setIcon(icon); } void Client::getSyncCounter() { // TODO: make sync working on XWayland static const bool isX11 = kwinApp()->operationMode() == Application::OperationModeX11; if (!Xcb::Extensions::self()->isSyncAvailable() || !isX11) return; Xcb::Property syncProp(false, window(), atoms->net_wm_sync_request_counter, XCB_ATOM_CARDINAL, 0, 1); const xcb_sync_counter_t counter = syncProp.value(XCB_NONE); if (counter != XCB_NONE) { syncRequest.counter = counter; syncRequest.value.hi = 0; syncRequest.value.lo = 0; auto *c = connection(); xcb_sync_set_counter(c, syncRequest.counter, syncRequest.value); if (syncRequest.alarm == XCB_NONE) { const uint32_t mask = XCB_SYNC_CA_COUNTER | XCB_SYNC_CA_VALUE_TYPE | XCB_SYNC_CA_TEST_TYPE | XCB_SYNC_CA_EVENTS; const uint32_t values[] = { syncRequest.counter, XCB_SYNC_VALUETYPE_RELATIVE, XCB_SYNC_TESTTYPE_POSITIVE_TRANSITION, 1 }; syncRequest.alarm = xcb_generate_id(c); auto cookie = xcb_sync_create_alarm_checked(c, syncRequest.alarm, mask, values); ScopedCPointer error(xcb_request_check(c, cookie)); if (!error.isNull()) { syncRequest.alarm = XCB_NONE; } else { xcb_sync_change_alarm_value_list_t value; memset(&value, 0, sizeof(value)); value.value.hi = 0; value.value.lo = 1; value.delta.hi = 0; value.delta.lo = 1; xcb_sync_change_alarm_aux(c, syncRequest.alarm, XCB_SYNC_CA_DELTA | XCB_SYNC_CA_VALUE, &value); } } } } /** * Send the client a _NET_SYNC_REQUEST */ void Client::sendSyncRequest() { if (syncRequest.counter == XCB_NONE || syncRequest.isPending) return; // do NOT, NEVER send a sync request when there's one on the stack. the clients will just stop respoding. FOREVER! ... if (!syncRequest.failsafeTimeout) { syncRequest.failsafeTimeout = new QTimer(this); connect(syncRequest.failsafeTimeout, &QTimer::timeout, this, [this]() { // client does not respond to XSYNC requests in reasonable time, remove support if (!ready_for_painting) { // failed on initial pre-show request setReadyForPainting(); setupWindowManagementInterface(); return; } // failed during resize syncRequest.isPending = false; syncRequest.counter = syncRequest.alarm = XCB_NONE; delete syncRequest.timeout; delete syncRequest.failsafeTimeout; syncRequest.timeout = syncRequest.failsafeTimeout = nullptr; syncRequest.lastTimestamp = XCB_CURRENT_TIME; } ); syncRequest.failsafeTimeout->setSingleShot(true); } // if there's no response within 10 seconds, sth. went wrong and we remove XSYNC support from this client. // see events.cpp Client::syncEvent() syncRequest.failsafeTimeout->start(ready_for_painting ? 10000 : 1000); // We increment before the notify so that after the notify // syncCounterSerial will equal the value we are expecting // in the acknowledgement const uint32_t oldLo = syncRequest.value.lo; syncRequest.value.lo++;; if (oldLo > syncRequest.value.lo) { syncRequest.value.hi++; } if (syncRequest.lastTimestamp >= xTime()) { updateXTime(); } // Send the message to client sendClientMessage(window(), atoms->wm_protocols, atoms->net_wm_sync_request, syncRequest.value.lo, syncRequest.value.hi); syncRequest.isPending = true; syncRequest.lastTimestamp = xTime(); } bool Client::wantsInput() const { return rules()->checkAcceptFocus(acceptsFocus() || info->supportsProtocol(NET::TakeFocusProtocol)); } bool Client::acceptsFocus() const { return info->input(); } void Client::setBlockingCompositing(bool block) { const bool usedToBlock = blocks_compositing; blocks_compositing = rules()->checkBlockCompositing(block && options->windowsBlockCompositing()); if (usedToBlock != blocks_compositing) { - emit blockingCompositingChanged(blocks_compositing ? this : 0); + emit blockingCompositingChanged(blocks_compositing ? this : nullptr); } } void Client::updateAllowedActions(bool force) { if (!isManaged() && !force) return; NET::Actions old_allowed_actions = NET::Actions(allowed_actions); - allowed_actions = 0; + allowed_actions = nullptr; if (isMovable()) allowed_actions |= NET::ActionMove; if (isResizable()) allowed_actions |= NET::ActionResize; if (isMinimizable()) allowed_actions |= NET::ActionMinimize; if (isShadeable()) allowed_actions |= NET::ActionShade; // Sticky state not supported if (isMaximizable()) allowed_actions |= NET::ActionMax; if (userCanSetFullScreen()) allowed_actions |= NET::ActionFullScreen; allowed_actions |= NET::ActionChangeDesktop; // Always (Pagers shouldn't show Docks etc.) if (isCloseable()) allowed_actions |= NET::ActionClose; if (old_allowed_actions == allowed_actions) return; // TODO: This could be delayed and compressed - It's only for pagers etc. anyway info->setAllowedActions(allowed_actions); // ONLY if relevant features have changed (and the window didn't just get/loose moveresize for maximization state changes) const NET::Actions relevant = ~(NET::ActionMove|NET::ActionResize); if ((allowed_actions & relevant) != (old_allowed_actions & relevant)) { if ((allowed_actions & NET::ActionMinimize) != (old_allowed_actions & NET::ActionMinimize)) { emit minimizeableChanged(allowed_actions & NET::ActionMinimize); } if ((allowed_actions & NET::ActionShade) != (old_allowed_actions & NET::ActionShade)) { emit shadeableChanged(allowed_actions & NET::ActionShade); } if ((allowed_actions & NET::ActionMax) != (old_allowed_actions & NET::ActionMax)) { emit maximizeableChanged(allowed_actions & NET::ActionMax); } } } void Client::debug(QDebug& stream) const { stream.nospace(); print(stream); } Xcb::StringProperty Client::fetchActivities() const { #ifdef KWIN_BUILD_ACTIVITIES return Xcb::StringProperty(window(), atoms->activities); #else return Xcb::StringProperty(); #endif } void Client::readActivities(Xcb::StringProperty &property) { #ifdef KWIN_BUILD_ACTIVITIES QStringList newActivitiesList; QString prop = QString::fromUtf8(property); activitiesDefined = !prop.isEmpty(); if (prop == Activities::nullUuid()) { //copied from setOnAllActivities to avoid a redundant XChangeProperty. if (!activityList.isEmpty()) { activityList.clear(); updateActivities(true); } return; } if (prop.isEmpty()) { //note: this makes it *act* like it's on all activities but doesn't set the property to 'ALL' if (!activityList.isEmpty()) { activityList.clear(); updateActivities(true); } return; } newActivitiesList = prop.split(u','); if (newActivitiesList == activityList) return; //expected change, it's ok. //otherwise, somebody else changed it. we need to validate before reacting. //if the activities are not synced, and there are existing clients with //activities specified, somebody has restarted kwin. we can not validate //activities in this case. we need to trust the old values. if (Activities::self() && Activities::self()->serviceStatus() != KActivities::Consumer::Unknown) { QStringList allActivities = Activities::self()->all(); if (allActivities.isEmpty()) { qCDebug(KWIN_CORE) << "no activities!?!?"; //don't touch anything, there's probably something bad going on and we don't wanna make it worse return; } for (int i = 0; i < newActivitiesList.size(); ++i) { if (! allActivities.contains(newActivitiesList.at(i))) { qCDebug(KWIN_CORE) << "invalid:" << newActivitiesList.at(i); newActivitiesList.removeAt(i--); } } } setOnActivities(newActivitiesList); #else Q_UNUSED(property) #endif } void Client::checkActivities() { #ifdef KWIN_BUILD_ACTIVITIES Xcb::StringProperty property = fetchActivities(); readActivities(property); #endif } void Client::setSessionActivityOverride(bool needed) { sessionActivityOverride = needed; updateActivities(false); } QRect Client::decorationRect() const { return QRect(0, 0, width(), height()); } Xcb::Property Client::fetchFirstInTabBox() const { return Xcb::Property(false, m_client, atoms->kde_first_in_window_list, atoms->kde_first_in_window_list, 0, 1); } void Client::readFirstInTabBox(Xcb::Property &property) { setFirstInTabBox(property.toBool(32, atoms->kde_first_in_window_list)); } void Client::updateFirstInTabBox() { // TODO: move into KWindowInfo Xcb::Property property = fetchFirstInTabBox(); readFirstInTabBox(property); } Xcb::StringProperty Client::fetchColorScheme() const { return Xcb::StringProperty(m_client, atoms->kde_color_sheme); } void Client::readColorScheme(Xcb::StringProperty &property) { AbstractClient::updateColorScheme(rules()->checkDecoColor(QString::fromUtf8(property))); } void Client::updateColorScheme() { Xcb::StringProperty property = fetchColorScheme(); readColorScheme(property); } bool Client::isClient() const { return true; } NET::WindowType Client::windowType(bool direct, int supportedTypes) const { // TODO: does it make sense to cache the returned window type for SUPPORTED_MANAGED_WINDOW_TYPES_MASK? if (supportedTypes == 0) { supportedTypes = SUPPORTED_MANAGED_WINDOW_TYPES_MASK; } NET::WindowType wt = info->windowType(NET::WindowTypes(supportedTypes)); if (direct) { return wt; } NET::WindowType wt2 = rules()->checkType(wt); if (wt != wt2) { wt = wt2; info->setWindowType(wt); // force hint change } // hacks here if (wt == NET::Unknown) // this is more or less suggested in NETWM spec wt = isTransient() ? NET::Dialog : NET::Normal; return wt; } void Client::cancelFocusOutTimer() { if (m_focusOutTimer) { m_focusOutTimer->stop(); } } xcb_window_t Client::frameId() const { return m_frame; } Xcb::Property Client::fetchShowOnScreenEdge() const { return Xcb::Property(false, window(), atoms->kde_screen_edge_show, XCB_ATOM_CARDINAL, 0, 1); } void Client::readShowOnScreenEdge(Xcb::Property &property) { //value comes in two parts, edge in the lower byte //then the type in the upper byte // 0 = autohide // 1 = raise in front on activate const uint32_t value = property.value(ElectricNone); ElectricBorder border = ElectricNone; switch (value & 0xFF) { case 0: border = ElectricTop; break; case 1: border = ElectricRight; break; case 2: border = ElectricBottom; break; case 3: border = ElectricLeft; break; } if (border != ElectricNone) { disconnect(m_edgeRemoveConnection); disconnect(m_edgeGeometryTrackingConnection); bool successfullyHidden = false; if (((value >> 8) & 0xFF) == 1) { setKeepBelow(true); successfullyHidden = keepBelow(); //request could have failed due to user kwin rules m_edgeRemoveConnection = connect(this, &AbstractClient::keepBelowChanged, this, [this](){ if (!keepBelow()) { ScreenEdges::self()->reserve(this, ElectricNone); } }); } else { hideClient(true); successfullyHidden = isHiddenInternal(); m_edgeGeometryTrackingConnection = connect(this, &Client::geometryChanged, this, [this, border](){ hideClient(true); ScreenEdges::self()->reserve(this, border); }); } if (successfullyHidden) { ScreenEdges::self()->reserve(this, border); } else { ScreenEdges::self()->reserve(this, ElectricNone); } } else if (!property.isNull() && property->type != XCB_ATOM_NONE) { // property value is incorrect, delete the property // so that the client knows that it is not hidden xcb_delete_property(connection(), window(), atoms->kde_screen_edge_show); } else { // restore // TODO: add proper unreserve //this will call showOnScreenEdge to reset the state disconnect(m_edgeGeometryTrackingConnection); ScreenEdges::self()->reserve(this, ElectricNone); } } void Client::updateShowOnScreenEdge() { Xcb::Property property = fetchShowOnScreenEdge(); readShowOnScreenEdge(property); } void Client::showOnScreenEdge() { disconnect(m_edgeRemoveConnection); hideClient(false); setKeepBelow(false); xcb_delete_property(connection(), window(), atoms->kde_screen_edge_show); } void Client::addDamage(const QRegion &damage) { if (!ready_for_painting) { // avoid "setReadyForPainting()" function calling overhead if (syncRequest.counter == XCB_NONE) { // cannot detect complete redraw, consider done now setReadyForPainting(); setupWindowManagementInterface(); } } repaints_region += damage; Toplevel::addDamage(damage); } bool Client::belongsToSameApplication(const AbstractClient *other, SameApplicationChecks checks) const { const Client *c2 = dynamic_cast(other); if (!c2) { return false; } return Client::belongToSameApplication(this, c2, checks); } QSize Client::resizeIncrements() const { return m_geometryHints.resizeIncrements(); } Xcb::StringProperty Client::fetchApplicationMenuServiceName() const { return Xcb::StringProperty(m_client, atoms->kde_net_wm_appmenu_service_name); } void Client::readApplicationMenuServiceName(Xcb::StringProperty &property) { updateApplicationMenuServiceName(QString::fromUtf8(property)); } void Client::checkApplicationMenuServiceName() { Xcb::StringProperty property = fetchApplicationMenuServiceName(); readApplicationMenuServiceName(property); } Xcb::StringProperty Client::fetchApplicationMenuObjectPath() const { return Xcb::StringProperty(m_client, atoms->kde_net_wm_appmenu_object_path); } void Client::readApplicationMenuObjectPath(Xcb::StringProperty &property) { updateApplicationMenuObjectPath(QString::fromUtf8(property)); } void Client::checkApplicationMenuObjectPath() { Xcb::StringProperty property = fetchApplicationMenuObjectPath(); readApplicationMenuObjectPath(property); } void Client::handleSync() { setReadyForPainting(); setupWindowManagementInterface(); syncRequest.isPending = false; if (syncRequest.failsafeTimeout) syncRequest.failsafeTimeout->stop(); if (isResize()) { if (syncRequest.timeout) syncRequest.timeout->stop(); performMoveResize(); } else // setReadyForPainting does as well, but there's a small chance for resize syncs after the resize ended addRepaintFull(); } } // namespace diff --git a/client.h b/client.h index 0bf24baa1..2b4b46474 100644 --- a/client.h +++ b/client.h @@ -1,694 +1,694 @@ /******************************************************************** 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 . *********************************************************************/ #ifndef KWIN_CLIENT_H #define KWIN_CLIENT_H // kwin #include "options.h" #include "rules.h" #include "abstract_client.h" #include "xcbutils.h" // Qt #include #include #include #include #include // X #include // TODO: Cleanup the order of things in this .h file class QTimer; class KStartupInfoData; class KStartupInfoId; namespace KWin { /** * @brief Defines Predicates on how to search for a Client. * * Used by Workspace::findClient. */ enum class Predicate { WindowMatch, WrapperIdMatch, FrameIdMatch, InputIdMatch }; class KWIN_EXPORT Client : public AbstractClient { Q_OBJECT /** * By how much the window wishes to grow/shrink at least. Usually QSize(1,1). * MAY BE DISOBEYED BY THE WM! It's only for information, do NOT rely on it at all. * The value is evaluated each time the getter is called. * Because of that no changed signal is provided. */ Q_PROPERTY(QSize basicUnit READ basicUnit) /** * A client can block compositing. That is while the Client is alive and the state is set, * Compositing is suspended and is resumed when there are no Clients blocking compositing any * more. * * This is actually set by a window property, unfortunately not used by the target application * group. For convenience it's exported as a property to the scripts. * * Use with care! */ Q_PROPERTY(bool blocksCompositing READ isBlockingCompositing WRITE setBlockingCompositing NOTIFY blockingCompositingChanged) /** * Whether the Client uses client side window decorations. * Only GTK+ are detected. */ Q_PROPERTY(bool clientSideDecorated READ isClientSideDecorated NOTIFY clientSideDecoratedChanged) public: explicit Client(); xcb_window_t wrapperId() const; xcb_window_t inputId() const { return m_decoInputExtent; } xcb_window_t frameId() const override; bool isTransient() const override; bool groupTransient() const override; bool wasOriginallyGroupTransient() const; QList mainClients() const override; // Call once before loop , is not indirect bool hasTransient(const AbstractClient* c, bool indirect) const override; void checkTransient(xcb_window_t w); AbstractClient* findModal(bool allow_itself = false) override; const Group* group() const override; Group* group() override; - void checkGroup(Group* gr = NULL, bool force = false); + void checkGroup(Group* gr = nullptr, bool force = false); void changeClientLeaderGroup(Group* gr); void updateWindowRules(Rules::Types selection) override; void updateFullscreenMonitors(NETFullscreenMonitors topology); bool hasNETSupport() const; QSize minSize() const override; QSize maxSize() const override; QSize basicUnit() const; QSize clientSize() const override; QPoint inputPos() const { return input_offset; } // Inside of geometry() bool windowEvent(xcb_generic_event_t *e); NET::WindowType windowType(bool direct = false, int supported_types = 0) const override; bool manage(xcb_window_t w, bool isMapped); void releaseWindow(bool on_shutdown = false); void destroyClient(); QStringList activities() const override; void setOnActivity(const QString &activity, bool enable); void setOnAllActivities(bool set) override; void setOnActivities(QStringList newActivitiesList) override; void updateActivities(bool includeTransients); void blockActivityUpdates(bool b = true) override; /// Is not minimized and not hidden. I.e. normally visible on some virtual desktop. bool isShown(bool shaded_is_shown) const override; bool isHiddenInternal() const override; // For compositing ShadeMode shadeMode() const override; // Prefer isShade() void setShade(ShadeMode mode) override; bool isShadeable() const override; bool isMaximizable() const override; QRect geometryRestore() const override; MaximizeMode maximizeMode() const override; bool isMinimizable() const override; QRect iconGeometry() const override; bool isFullScreenable() const override; void setFullScreen(bool set, bool user = true) override; bool isFullScreen() const override; bool userCanSetFullScreen() const override; QRect geometryFSRestore() const { return geom_fs_restore; // only for session saving } int fullScreenMode() const { return m_fullscreenMode; // only for session saving } bool userNoBorder() const; bool noBorder() const override; void setNoBorder(bool set) override; bool userCanSetNoBorder() const override; void checkNoBorder() override; int sessionStackingOrder() const; // Auxiliary functions, depend on the windowType bool wantsInput() const override; bool isResizable() const override; bool isMovable() const override; bool isMovableAcrossScreens() const override; bool isCloseable() const override; ///< May be closed by the user (May have a close button) void takeFocus() override; void updateDecoration(bool check_workspace_pos, bool force = false) override; void updateShape(); using AbstractClient::setGeometry; void setGeometry(int x, int y, int w, int h, ForceGeometry_t force = NormalGeometrySet) override; /// plainResize() simply resizes void plainResize(int w, int h, ForceGeometry_t force = NormalGeometrySet); void plainResize(const QSize& s, ForceGeometry_t force = NormalGeometrySet); /// resizeWithChecks() resizes according to gravity, and checks workarea position using AbstractClient::resizeWithChecks; void resizeWithChecks(int w, int h, ForceGeometry_t force = NormalGeometrySet) override; void resizeWithChecks(int w, int h, xcb_gravity_t gravity, ForceGeometry_t force = NormalGeometrySet); void resizeWithChecks(const QSize& s, xcb_gravity_t gravity, ForceGeometry_t force = NormalGeometrySet); QSize sizeForClientSize(const QSize&, Sizemode mode = SizemodeAny, bool noframe = false) const override; bool providesContextHelp() const override; Options::WindowOperation mouseButtonToWindowOperation(Qt::MouseButtons button); bool performMouseCommand(Options::MouseCommand, const QPoint& globalPos) override; QRect adjustedClientArea(const QRect& desktop, const QRect& area) const; xcb_colormap_t colormap() const; /// Updates visibility depending on being shaded, virtual desktop, etc. void updateVisibility(); /// Hides a client - Basically like minimize, but without effects, it's simply hidden void hideClient(bool hide) override; bool hiddenPreview() const; ///< Window is mapped in order to get a window pixmap bool setupCompositing() override; void finishCompositing(ReleaseReason releaseReason = ReleaseReason::Release) override; void setBlockingCompositing(bool block); inline bool isBlockingCompositing() { return blocks_compositing; } QString captionNormal() const override { return cap_normal; } QString captionSuffix() const override { return cap_suffix; } using AbstractClient::keyPressEvent; void keyPressEvent(uint key_code, xcb_timestamp_t time); // FRAME ?? void updateMouseGrab() override; xcb_window_t moveResizeGrabWindow() const; const QPoint calculateGravitation(bool invert, int gravity = 0) const; // FRAME public? void NETMoveResize(int x_root, int y_root, NET::Direction direction); void NETMoveResizeWindow(int flags, int x, int y, int width, int height); void restackWindow(xcb_window_t above, int detail, NET::RequestSource source, xcb_timestamp_t timestamp, bool send_event = false); void gotPing(xcb_timestamp_t timestamp); void updateUserTime(xcb_timestamp_t time = XCB_TIME_CURRENT_TIME); xcb_timestamp_t userTime() const override; bool hasUserTimeSupport() const; /// Does 'delete c;' static void deleteClient(Client* c); static bool belongToSameApplication(const Client* c1, const Client* c2, SameApplicationChecks checks = SameApplicationChecks()); static bool sameAppWindowRoleMatch(const Client* c1, const Client* c2, bool active_hack); void killWindow() override; void toggleShade(); void showContextHelp() override; void cancelShadeHoverTimer(); void checkActiveModal(); StrutRect strutRect(StrutArea area) const; StrutRects strutRects() const; bool hasStrut() const override; /** * If shown is true the client is mapped and raised, if false * the client is unmapped and hidden, this function is called * when the tabbing group of the client switches its visible * client. */ void setClientShown(bool shown) override; /** * Whether or not the window has a strut that expands through the invisible area of * an xinerama setup where the monitors are not the same resolution. */ bool hasOffscreenXineramaStrut() const; // Decorations <-> Effects QRect decorationRect() const override; QRect transparentRect() const override; bool isClientSideDecorated() const; bool wantsShadowToBeRendered() const override; void layoutDecorationRects(QRect &left, QRect &top, QRect &right, QRect &bottom) const override; Xcb::Property fetchFirstInTabBox() const; void readFirstInTabBox(Xcb::Property &property); void updateFirstInTabBox(); Xcb::StringProperty fetchColorScheme() const; void readColorScheme(Xcb::StringProperty &property); void updateColorScheme() override; //sets whether the client should be faked as being on all activities (and be shown during session save) void setSessionActivityOverride(bool needed); bool isClient() const override; template void print(T &stream) const; void cancelFocusOutTimer(); /** * Restores the Client after it had been hidden due to show on screen edge functionality. * In addition the property gets deleted so that the Client knows that it is visible again. */ void showOnScreenEdge() override; Xcb::StringProperty fetchApplicationMenuServiceName() const; void readApplicationMenuServiceName(Xcb::StringProperty &property); void checkApplicationMenuServiceName(); Xcb::StringProperty fetchApplicationMenuObjectPath() const; void readApplicationMenuObjectPath(Xcb::StringProperty &property); void checkApplicationMenuObjectPath(); struct SyncRequest { xcb_sync_counter_t counter; xcb_sync_int64_t value; xcb_sync_alarm_t alarm; xcb_timestamp_t lastTimestamp; QTimer *timeout, *failsafeTimeout; bool isPending; }; const SyncRequest &getSyncRequest() const { return syncRequest; } void handleSync(); static void cleanupX11(); public Q_SLOTS: void closeWindow() override; void updateCaption() override; private Q_SLOTS: void shadeHover(); void shadeUnhover(); private: // Use Workspace::createClient() ~Client() override; ///< Use destroyClient() or releaseWindow() // Handlers for X11 events bool mapRequestEvent(xcb_map_request_event_t *e); void unmapNotifyEvent(xcb_unmap_notify_event_t *e); void destroyNotifyEvent(xcb_destroy_notify_event_t *e); void configureRequestEvent(xcb_configure_request_event_t *e); void propertyNotifyEvent(xcb_property_notify_event_t *e) override; void clientMessageEvent(xcb_client_message_event_t *e) override; void enterNotifyEvent(xcb_enter_notify_event_t *e); void leaveNotifyEvent(xcb_leave_notify_event_t *e); void focusInEvent(xcb_focus_in_event_t *e); void focusOutEvent(xcb_focus_out_event_t *e); void damageNotifyEvent() override; bool buttonPressEvent(xcb_window_t w, int button, int state, int x, int y, int x_root, int y_root, xcb_timestamp_t time = XCB_CURRENT_TIME); bool buttonReleaseEvent(xcb_window_t w, int button, int state, int x, int y, int x_root, int y_root); bool motionNotifyEvent(xcb_window_t w, int state, int x, int y, int x_root, int y_root); protected: void debug(QDebug& stream) const override; void addDamage(const QRegion &damage) override; bool belongsToSameApplication(const AbstractClient *other, SameApplicationChecks checks) const override; void doSetActive() override; void doSetKeepAbove() override; void doSetKeepBelow() override; void doSetDesktop(int desktop, int was_desk) override; void doMinimize() override; void doSetSkipPager() override; void doSetSkipTaskbar() override; void doSetSkipSwitcher() override; bool belongsToDesktop() const override; void setGeometryRestore(const QRect &geo) override; void doMove(int x, int y) override; bool doStartMoveResize() override; void doPerformMoveResize() override; bool isWaitingForMoveResizeSync() const override; void doResizeSync() override; QSize resizeIncrements() const override; bool acceptsFocus() const override; //Signals for the scripting interface //Signals make an excellent way for communication //in between objects as compared to simple function //calls Q_SIGNALS: void clientManaging(KWin::Client*); void clientFullScreenSet(KWin::Client*, bool, bool); /** * Emitted whenever the Client want to show it menu */ void showRequest(); /** * Emitted whenever the Client's menu is closed */ void menuHidden(); /** * Emitted whenever the Client's menu is available */ void appMenuAvailable(); /** * Emitted whenever the Client's menu is unavailable */ void appMenuUnavailable(); /** * Emitted whenever the Client's block compositing state changes. */ void blockingCompositingChanged(KWin::Client *client); void clientSideDecoratedChanged(); private: void exportMappingState(int s); // ICCCM 4.1.3.1, 4.1.4, NETWM 2.5.1 bool isManaged() const; ///< Returns false if this client is not yet managed void updateAllowedActions(bool force = false); QRect fullscreenMonitorsArea(NETFullscreenMonitors topology) const; void changeMaximize(bool horizontal, bool vertical, bool adjust) override; void getWmNormalHints(); void getMotifHints(); void getIcons(); void fetchName(); void fetchIconicName(); QString readName() const; void setCaption(const QString& s, bool force = false); bool hasTransientInternal(const Client* c, bool indirect, ConstClientList& set) const; void setShortcutInternal() override; void configureRequest(int value_mask, int rx, int ry, int rw, int rh, int gravity, bool from_tool); NETExtendedStrut strut() const; int checkShadeGeometry(int w, int h); void getSyncCounter(); void sendSyncRequest(); void leaveMoveResize() override; void positionGeometryTip() override; void grabButton(int mod); void ungrabButton(int mod); void resizeDecoration(); void createDecoration(const QRect &oldgeom); void pingWindow(); void killProcess(bool ask, xcb_timestamp_t timestamp = XCB_TIME_CURRENT_TIME); void updateUrgency(); static void sendClientMessage(xcb_window_t w, xcb_atom_t a, xcb_atom_t protocol, uint32_t data1 = 0, uint32_t data2 = 0, uint32_t data3 = 0, xcb_timestamp_t timestamp = xTime()); void embedClient(xcb_window_t w, xcb_visualid_t visualid, xcb_colormap_t colormap, uint8_t depth); void detectNoBorder(); Xcb::Property fetchGtkFrameExtents() const; void readGtkFrameExtents(Xcb::Property &prop); void detectGtkFrameExtents(); void destroyDecoration() override; void updateFrameExtents(); void internalShow(); void internalHide(); void internalKeep(); void map(); void unmap(); void updateHiddenPreview(); void updateInputShape(); xcb_timestamp_t readUserTimeMapTimestamp(const KStartupInfoId* asn_id, const KStartupInfoData* asn_data, bool session) const; xcb_timestamp_t readUserCreationTime() const; void startupIdChanged(); void updateInputWindow(); Xcb::Property fetchShowOnScreenEdge() const; void readShowOnScreenEdge(Xcb::Property &property); /** * Reads the property and creates/destroys the screen edge if required * and shows/hides the client. */ void updateShowOnScreenEdge(); Xcb::Window m_client; Xcb::Window m_wrapper; Xcb::Window m_frame; QStringList activityList; int m_activityUpdatesBlocked; bool m_blockedActivityUpdatesRequireTransients; Xcb::Window m_moveResizeGrabWindow; bool move_resize_has_keyboard_grab; bool m_managed; Xcb::GeometryHints m_geometryHints; void sendSyntheticConfigureNotify(); enum MappingState { Withdrawn, ///< Not handled, as per ICCCM WithdrawnState Mapped, ///< The frame is mapped Unmapped, ///< The frame is not mapped Kept ///< The frame should be unmapped, but is kept (For compositing) }; MappingState mapping_state; Xcb::TransientFor fetchTransient() const; void readTransientProperty(Xcb::TransientFor &transientFor); void readTransient(); xcb_window_t verifyTransientFor(xcb_window_t transient_for, bool set); void addTransient(AbstractClient* cl) override; void removeTransient(AbstractClient* cl) override; void removeFromMainClients(); void cleanGrouping(); void checkGroupTransients(); void setTransient(xcb_window_t new_transient_for_id); xcb_window_t m_transientForId; xcb_window_t m_originalTransientForId; ShadeMode shade_mode; Client *shade_below; uint deleting : 1; ///< True when doing cleanup and destroying the client Xcb::MotifHints m_motif; uint hidden : 1; ///< Forcibly hidden by calling hide() uint noborder : 1; uint app_noborder : 1; ///< App requested no border via window type, shape extension, etc. uint ignore_focus_stealing : 1; ///< Don't apply focus stealing prevention to this client bool blocks_compositing; enum FullScreenMode { FullScreenNone, FullScreenNormal } m_fullscreenMode; MaximizeMode max_mode; QRect geom_restore; QRect geom_fs_restore; QTimer* shadeHoverTimer; xcb_colormap_t m_colormap; QString cap_normal, cap_iconic, cap_suffix; Group* in_group; QTimer* ping_timer; qint64 m_killHelperPID; xcb_timestamp_t m_pingTimestamp; xcb_timestamp_t m_userTime; NET::Actions allowed_actions; QSize client_size; bool shade_geometry_change; SyncRequest syncRequest; static bool check_active_modal; ///< \see Client::checkActiveModal() int sm_stacking_order; friend struct ResetupRulesProcedure; friend bool performTransiencyCheck(); Xcb::StringProperty fetchActivities() const; void readActivities(Xcb::StringProperty &property); void checkActivities(); bool activitiesDefined; //whether the x property was actually set bool sessionActivityOverride; bool needsXWindowMove; Xcb::Window m_decoInputExtent; QPoint input_offset; QTimer *m_focusOutTimer; QList m_connections; bool m_clientSideDecorated; QMetaObject::Connection m_edgeRemoveConnection; QMetaObject::Connection m_edgeGeometryTrackingConnection; }; inline xcb_window_t Client::wrapperId() const { return m_wrapper; } inline bool Client::isClientSideDecorated() const { return m_clientSideDecorated; } inline bool Client::groupTransient() const { return m_transientForId == rootWindow(); } // Needed because verifyTransientFor() may set transient_for_id to root window, // if the original value has a problem (window doesn't exist, etc.) inline bool Client::wasOriginallyGroupTransient() const { return m_originalTransientForId == rootWindow(); } inline bool Client::isTransient() const { return m_transientForId != XCB_WINDOW_NONE; } inline const Group* Client::group() const { return in_group; } inline Group* Client::group() { return in_group; } inline bool Client::isShown(bool shaded_is_shown) const { return !isMinimized() && (!isShade() || shaded_is_shown) && !hidden; } inline bool Client::isHiddenInternal() const { return hidden; } inline ShadeMode Client::shadeMode() const { return shade_mode; } inline QRect Client::geometryRestore() const { return geom_restore; } inline void Client::setGeometryRestore(const QRect &geo) { geom_restore = geo; } inline MaximizeMode Client::maximizeMode() const { return max_mode; } inline bool Client::isFullScreen() const { return m_fullscreenMode != FullScreenNone; } inline bool Client::hasNETSupport() const { return info->hasNETSupport(); } inline xcb_colormap_t Client::colormap() const { return m_colormap; } inline int Client::sessionStackingOrder() const { return sm_stacking_order; } inline bool Client::isManaged() const { return m_managed; } inline QSize Client::clientSize() const { return client_size; } inline void Client::plainResize(const QSize& s, ForceGeometry_t force) { plainResize(s.width(), s.height(), force); } inline void Client::resizeWithChecks(int w, int h, AbstractClient::ForceGeometry_t force) { resizeWithChecks(w, h, XCB_GRAVITY_BIT_FORGET, force); } inline void Client::resizeWithChecks(const QSize& s, xcb_gravity_t gravity, ForceGeometry_t force) { resizeWithChecks(s.width(), s.height(), gravity, force); } inline bool Client::hasUserTimeSupport() const { return info->userTime() != -1U; } inline xcb_window_t Client::moveResizeGrabWindow() const { return m_moveResizeGrabWindow; } inline bool Client::hiddenPreview() const { return mapping_state == Kept; } template inline void Client::print(T &stream) const { stream << "\'Client:" << window() << ";WMCLASS:" << resourceClass() << ":" << resourceName() << ";Caption:" << caption() << "\'"; } } // namespace Q_DECLARE_METATYPE(KWin::Client*) Q_DECLARE_METATYPE(QList) #endif diff --git a/client_machine.cpp b/client_machine.cpp index 764b13713..f6c29bf5b 100644 --- a/client_machine.cpp +++ b/client_machine.cpp @@ -1,242 +1,242 @@ /******************************************************************** 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 . *********************************************************************/ // own #include "client_machine.h" #include "utils.h" // KF5 #include // Qt #include #include // system #include #include #include #include namespace KWin { static QByteArray getHostName() { #ifdef HOST_NAME_MAX char hostnamebuf[HOST_NAME_MAX]; #else char hostnamebuf[256]; #endif if (gethostname(hostnamebuf, sizeof hostnamebuf) >= 0) { hostnamebuf[sizeof(hostnamebuf)-1] = 0; return QByteArray(hostnamebuf); } return QByteArray(); } GetAddrInfo::GetAddrInfo(const QByteArray &hostName, QObject *parent) : QObject(parent) , m_resolving(false) , m_resolved(false) , m_ownResolved(false) , m_hostName(hostName) , m_addressHints(new addrinfo) - , m_address(NULL) - , m_ownAddress(NULL) + , m_address(nullptr) + , m_ownAddress(nullptr) , m_watcher(new QFutureWatcher(this)) , m_ownAddressWatcher(new QFutureWatcher(this)) { // watcher will be deleted together with the GetAddrInfo once the future // got canceled or finished connect(m_watcher, SIGNAL(canceled()), SLOT(deleteLater())); connect(m_watcher, SIGNAL(finished()), SLOT(slotResolved())); connect(m_ownAddressWatcher, SIGNAL(canceled()), SLOT(deleteLater())); connect(m_ownAddressWatcher, SIGNAL(finished()), SLOT(slotOwnAddressResolved())); } GetAddrInfo::~GetAddrInfo() { if (m_watcher && m_watcher->isRunning()) { m_watcher->cancel(); m_watcher->waitForFinished(); } if (m_ownAddressWatcher && m_ownAddressWatcher->isRunning()) { m_ownAddressWatcher->cancel(); m_ownAddressWatcher->waitForFinished(); } if (m_address) { freeaddrinfo(m_address); } if (m_ownAddress) { freeaddrinfo(m_ownAddress); } delete m_addressHints; } void GetAddrInfo::resolve() { if (m_resolving) { return; } m_resolving = true; memset(m_addressHints, 0, sizeof(*m_addressHints)); m_addressHints->ai_family = PF_UNSPEC; m_addressHints->ai_socktype = SOCK_STREAM; m_addressHints->ai_flags |= AI_CANONNAME; m_watcher->setFuture(QtConcurrent::run(getaddrinfo, m_hostName.constData(), nullptr, m_addressHints, &m_address)); m_ownAddressWatcher->setFuture(QtConcurrent::run([this] { // needs to be performed in a lambda as getHostName() returns a temporary value which would // get destroyed in the main thread before the getaddrinfo thread is able to read it return getaddrinfo(getHostName().constData(), nullptr, m_addressHints, &m_ownAddress); })); } void GetAddrInfo::slotResolved() { if (resolved(m_watcher)) { m_resolved = true; compare(); } } void GetAddrInfo::slotOwnAddressResolved() { if (resolved(m_ownAddressWatcher)) { m_ownResolved = true; compare(); } } bool GetAddrInfo::resolved(QFutureWatcher< int >* watcher) { if (!watcher->isFinished()) { return false; } if (watcher->result() != 0) { qCDebug(KWIN_CORE) << "getaddrinfo failed with error:" << gai_strerror(watcher->result()); // call failed; deleteLater(); return false; } return true; } void GetAddrInfo::compare() { if (!m_resolved || !m_ownResolved) { return; } addrinfo *address = m_address; while (address) { if (address->ai_canonname && m_hostName == QByteArray(address->ai_canonname).toLower()) { addrinfo *ownAddress = m_ownAddress; bool localFound = false; while (ownAddress) { if (ownAddress->ai_canonname && QByteArray(ownAddress->ai_canonname).toLower() == m_hostName) { localFound = true; break; } ownAddress = ownAddress->ai_next; } if (localFound) { emit local(); break; } } address = address->ai_next; } deleteLater(); } ClientMachine::ClientMachine(QObject *parent) : QObject(parent) , m_localhost(false) , m_resolved(false) , m_resolving(false) { } ClientMachine::~ClientMachine() { } void ClientMachine::resolve(xcb_window_t window, xcb_window_t clientLeader) { if (m_resolved) { return; } QByteArray name = NETWinInfo(connection(), window, rootWindow(), NET::Properties(), NET::WM2ClientMachine).clientMachine(); if (name.isEmpty() && clientLeader && clientLeader != window) { name = NETWinInfo(connection(), clientLeader, rootWindow(), NET::Properties(), NET::WM2ClientMachine).clientMachine(); } if (name.isEmpty()) { name = localhost(); } if (name == localhost()) { setLocal(); } m_hostName = name; checkForLocalhost(); m_resolved = true; } void ClientMachine::checkForLocalhost() { if (isLocal()) { // nothing to do return; } QByteArray host = getHostName(); if (!host.isEmpty()) { host = host.toLower(); const QByteArray lowerHostName(m_hostName.toLower()); if (host == lowerHostName) { setLocal(); return; } if (char *dot = strchr(host.data(), '.')) { *dot = '\0'; if (host == lowerHostName) { setLocal(); return; } } else { m_resolving = true; // check using information from get addr info // GetAddrInfo gets automatically destroyed once it finished or not GetAddrInfo *info = new GetAddrInfo(lowerHostName, this); connect(info, SIGNAL(local()), SLOT(setLocal())); connect(info, SIGNAL(destroyed(QObject*)), SLOT(resolveFinished())); info->resolve(); } } } void ClientMachine::setLocal() { m_localhost = true; emit localhostChanged(); } void ClientMachine::resolveFinished() { m_resolving = false; } } // namespace diff --git a/client_machine.h b/client_machine.h index a3fc1f5a3..63c3b7c00 100644 --- a/client_machine.h +++ b/client_machine.h @@ -1,117 +1,117 @@ /******************************************************************** 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 . *********************************************************************/ #ifndef KWIN_CLIENT_MACHINE_H #define KWIN_CLIENT_MACHINE_H #include #include // forward declaration struct addrinfo; template class QFutureWatcher; namespace KWin { class GetAddrInfo : public QObject { Q_OBJECT public: - explicit GetAddrInfo(const QByteArray &hostName, QObject *parent = NULL); + explicit GetAddrInfo(const QByteArray &hostName, QObject *parent = nullptr); ~GetAddrInfo() override; void resolve(); Q_SIGNALS: void local(); private Q_SLOTS: void slotResolved(); void slotOwnAddressResolved(); private: void compare(); bool resolved(QFutureWatcher *watcher); bool m_resolving; bool m_resolved; bool m_ownResolved; QByteArray m_hostName; addrinfo *m_addressHints; addrinfo *m_address; addrinfo *m_ownAddress; QFutureWatcher *m_watcher; QFutureWatcher *m_ownAddressWatcher; }; class ClientMachine : public QObject { Q_OBJECT public: - explicit ClientMachine(QObject *parent = NULL); + explicit ClientMachine(QObject *parent = nullptr); ~ClientMachine() override; void resolve(xcb_window_t window, xcb_window_t clientLeader); const QByteArray &hostName() const; bool isLocal() const; static QByteArray localhost(); bool isResolving() const; Q_SIGNALS: void localhostChanged(); private Q_SLOTS: void setLocal(); void resolveFinished(); private: void checkForLocalhost(); QByteArray m_hostName; bool m_localhost; bool m_resolved; bool m_resolving; }; inline bool ClientMachine::isLocal() const { return m_localhost; } inline const QByteArray &ClientMachine::hostName() const { return m_hostName; } inline QByteArray ClientMachine::localhost() { return "localhost"; } inline bool ClientMachine::isResolving() const { return m_resolving; } } // namespace #endif diff --git a/colorcorrection/manager.cpp b/colorcorrection/manager.cpp index 70580500d..cb91faa11 100644 --- a/colorcorrection/manager.cpp +++ b/colorcorrection/manager.cpp @@ -1,858 +1,858 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright 2017 Roman Gilg 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 "manager.h" #include "colorcorrectdbusinterface.h" #include "suncalc.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef Q_OS_LINUX #include #endif #include #include namespace KWin { namespace ColorCorrect { static const int QUICK_ADJUST_DURATION = 2000; static const int TEMPERATURE_STEP = 50; static bool checkLocation(double lat, double lng) { return -90 <= lat && lat <= 90 && -180 <= lng && lng <= 180; } Manager::Manager(QObject *parent) : QObject(parent) { m_iface = new ColorCorrectDBusInterface(this); connect(kwinApp(), &Application::workspaceCreated, this, &Manager::init); } void Manager::init() { Settings::instance(kwinApp()->config()); // we may always read in the current config readConfig(); if (!kwinApp()->platform()->supportsGammaControl()) { return; } connect(Screens::self(), &Screens::countChanged, this, &Manager::hardReset); connect(LogindIntegration::self(), &LogindIntegration::sessionActiveChanged, this, [this](bool active) { if (active) { hardReset(); } else { cancelAllTimers(); } } ); #ifdef Q_OS_LINUX // monitor for system clock changes - from the time dataengine auto timeChangedFd = ::timerfd_create(CLOCK_REALTIME, O_CLOEXEC | O_NONBLOCK); ::itimerspec timespec; //set all timers to 0, which creates a timer that won't do anything ::memset(×pec, 0, sizeof(timespec)); // Monitor for the time changing (flags == TFD_TIMER_ABSTIME | TFD_TIMER_CANCEL_ON_SET). // However these are not exposed in glibc so value is hardcoded: - ::timerfd_settime(timeChangedFd, 3, ×pec, 0); + ::timerfd_settime(timeChangedFd, 3, ×pec, nullptr); connect(this, &QObject::destroyed, [timeChangedFd]() { ::close(timeChangedFd); }); auto notifier = new QSocketNotifier(timeChangedFd, QSocketNotifier::Read, this); connect(notifier, &QSocketNotifier::activated, this, [this](int fd) { uint64_t c; ::read(fd, &c, 8); // check if we're resuming from suspend - in this case do a hard reset // Note: We're using the time clock to detect a suspend phase instead of connecting to the // provided logind dbus signal, because this signal would be received way too late. QDBusMessage message = QDBusMessage::createMethodCall("org.freedesktop.login1", "/org/freedesktop/login1", "org.freedesktop.DBus.Properties", QStringLiteral("Get")); message.setArguments(QVariantList({"org.freedesktop.login1.Manager", QStringLiteral("PreparingForSleep")})); QDBusReply reply = QDBusConnection::systemBus().call(message); bool comingFromSuspend; if (reply.isValid()) { comingFromSuspend = reply.value().toBool(); } else { qCDebug(KWIN_COLORCORRECTION) << "Failed to get PreparingForSleep Property of logind session:" << reply.error().message(); // Always do a hard reset in case we have no further information. comingFromSuspend = true; } if (comingFromSuspend) { hardReset(); } else { resetAllTimers(); } }); #else // TODO: Alternative method for BSD. #endif hardReset(); } void Manager::hardReset() { cancelAllTimers(); // Timings of the Sun are not used in the constant mode. if (m_mode != NightColorMode::Constant) { updateSunTimings(true); } if (kwinApp()->platform()->supportsGammaControl() && m_active) { m_running = true; commitGammaRamps(currentTargetTemp()); } resetAllTimers(); } void Manager::reparseConfigAndReset() { cancelAllTimers(); readConfig(); hardReset(); } // FIXME: The internal OSD service doesn't work on X11 right now. Once the QPA // is ported away from Wayland, drop this function in favor of the internal // OSD service. static void showStatusOsd(bool enabled) { // TODO: Maybe use different icons? const QString iconName = enabled ? QStringLiteral("preferences-desktop-display-nightcolor-on") : QStringLiteral("preferences-desktop-display-nightcolor-off"); const QString text = enabled ? i18nc("Night Color was enabled", "Night Color On") : i18nc("Night Color was disabled", "Night Color Off"); QDBusMessage message = QDBusMessage::createMethodCall( QStringLiteral("org.kde.plasmashell"), QStringLiteral("/org/kde/osdService"), QStringLiteral("org.kde.osdService"), QStringLiteral("showText")); message.setArguments({ iconName, text }); QDBusConnection::sessionBus().asyncCall(message); } void Manager::toggle() { if (!kwinApp()->platform()->supportsGammaControl()) { return; } m_active = !m_active; showStatusOsd(m_active); resetAllTimers(); } void Manager::initShortcuts() { QAction *toggleAction = new QAction(this); toggleAction->setProperty("componentName", QStringLiteral(KWIN_NAME)); toggleAction->setObjectName(i18n("Toggle Night Color")); toggleAction->setText(i18n("Toggle Night Color")); KGlobalAccel::setGlobalShortcut(toggleAction, QList()); input()->registerShortcut(QKeySequence(), toggleAction, this, &Manager::toggle); } void Manager::readConfig() { Settings *s = Settings::self(); s->load(); m_active = s->active(); NightColorMode mode = s->mode(); switch (s->mode()) { case NightColorMode::Automatic: case NightColorMode::Location: case NightColorMode::Timings: case NightColorMode::Constant: m_mode = mode; break; default: // Fallback for invalid setting values. m_mode = NightColorMode::Automatic; break; } m_nightTargetTemp = qBound(MIN_TEMPERATURE, s->nightTemperature(), NEUTRAL_TEMPERATURE); double lat, lng; auto correctReadin = [&lat, &lng]() { if (!checkLocation(lat, lng)) { // out of domain lat = 0; lng = 0; } }; // automatic lat = s->latitudeAuto(); lng = s->longitudeAuto(); correctReadin(); m_latAuto = lat; m_lngAuto = lng; // fixed location lat = s->latitudeFixed(); lng = s->longitudeFixed(); correctReadin(); m_latFixed = lat; m_lngFixed = lng; // fixed timings QTime mrB = QTime::fromString(s->morningBeginFixed(), "hhmm"); QTime evB = QTime::fromString(s->eveningBeginFixed(), "hhmm"); int diffME = mrB.msecsTo(evB); if (diffME <= 0) { // morning not strictly before evening - use defaults mrB = QTime(6,0); evB = QTime(18,0); diffME = mrB.msecsTo(evB); } int diffMin = qMin(diffME, MSC_DAY - diffME); int trTime = s->transitionTime() * 1000 * 60; if (trTime < 0 || diffMin <= trTime) { // transition time too long - use defaults mrB = QTime(6,0); evB = QTime(18,0); trTime = FALLBACK_SLOW_UPDATE_TIME; } m_morning = mrB; m_evening = evB; m_trTime = qMax(trTime / 1000 / 60, 1); } void Manager::resetAllTimers() { cancelAllTimers(); if (kwinApp()->platform()->supportsGammaControl()) { if (m_active) { m_running = true; } // we do this also for active being false in order to reset the temperature back to the day value resetQuickAdjustTimer(); } else { m_running = false; } } void Manager::cancelAllTimers() { delete m_slowUpdateStartTimer; delete m_slowUpdateTimer; delete m_quickAdjustTimer; m_slowUpdateStartTimer = nullptr; m_slowUpdateTimer = nullptr; m_quickAdjustTimer = nullptr; } void Manager::resetQuickAdjustTimer() { // We don't use timings of the Sun in the constant mode. if (m_mode != NightColorMode::Constant) { updateSunTimings(false); } int tempDiff = qAbs(currentTargetTemp() - m_currentTemp); // allow tolerance of one TEMPERATURE_STEP to compensate if a slow update is coincidental if (tempDiff > TEMPERATURE_STEP) { cancelAllTimers(); m_quickAdjustTimer = new QTimer(this); m_quickAdjustTimer->setSingleShot(false); connect(m_quickAdjustTimer, &QTimer::timeout, this, &Manager::quickAdjust); int interval = QUICK_ADJUST_DURATION / (tempDiff / TEMPERATURE_STEP); if (interval == 0) { interval = 1; } m_quickAdjustTimer->start(interval); } else { resetSlowUpdateStartTimer(); } } void Manager::quickAdjust() { if (!m_quickAdjustTimer) { return; } int nextTemp; int targetTemp = currentTargetTemp(); if (m_currentTemp < targetTemp) { nextTemp = qMin(m_currentTemp + TEMPERATURE_STEP, targetTemp); } else { nextTemp = qMax(m_currentTemp - TEMPERATURE_STEP, targetTemp); } commitGammaRamps(nextTemp); if (nextTemp == targetTemp) { // stop timer, we reached the target temp delete m_quickAdjustTimer; m_quickAdjustTimer = nullptr; resetSlowUpdateStartTimer(); } } void Manager::resetSlowUpdateStartTimer() { delete m_slowUpdateStartTimer; m_slowUpdateStartTimer = nullptr; if (!m_running || m_quickAdjustTimer) { // only reenable the slow update start timer when quick adjust is not active anymore return; } // There is no need for starting the slow update timer. Screen color temperature // will be constant all the time now. if (m_mode == NightColorMode::Constant) { return; } // set up the next slow update m_slowUpdateStartTimer = new QTimer(this); m_slowUpdateStartTimer->setSingleShot(true); connect(m_slowUpdateStartTimer, &QTimer::timeout, this, &Manager::resetSlowUpdateStartTimer); updateSunTimings(false); int diff; if (m_mode == NightColorMode::Timings) { // Timings mode is in local time diff = QDateTime::currentDateTime().msecsTo(m_next.first); } else { diff = QDateTime::currentDateTimeUtc().msecsTo(m_next.first); } if (diff <= 0) { qCCritical(KWIN_COLORCORRECTION) << "Error in time calculation. Deactivating Night Color."; return; } m_slowUpdateStartTimer->start(diff); // start the current slow update resetSlowUpdateTimer(); } void Manager::resetSlowUpdateTimer() { delete m_slowUpdateTimer; m_slowUpdateTimer = nullptr; QDateTime now = QDateTime::currentDateTimeUtc(); bool isDay = daylight(); int targetTemp = isDay ? m_dayTargetTemp : m_nightTargetTemp; // We've reached the target color temperature or the transition time is zero. if (m_prev.first == m_prev.second || m_currentTemp == targetTemp) { commitGammaRamps(targetTemp); return; } if (m_prev.first <= now && now <= m_prev.second) { int availTime = now.msecsTo(m_prev.second); m_slowUpdateTimer = new QTimer(this); m_slowUpdateTimer->setSingleShot(false); if (isDay) { connect(m_slowUpdateTimer, &QTimer::timeout, this, [this]() {slowUpdate(m_dayTargetTemp);}); } else { connect(m_slowUpdateTimer, &QTimer::timeout, this, [this]() {slowUpdate(m_nightTargetTemp);}); } // calculate interval such as temperature is changed by TEMPERATURE_STEP K per timer timeout int interval = availTime / (qAbs(targetTemp - m_currentTemp) / TEMPERATURE_STEP); if (interval == 0) { interval = 1; } m_slowUpdateTimer->start(interval); } } void Manager::slowUpdate(int targetTemp) { if (!m_slowUpdateTimer) { return; } int nextTemp; if (m_currentTemp < targetTemp) { nextTemp = qMin(m_currentTemp + TEMPERATURE_STEP, targetTemp); } else { nextTemp = qMax(m_currentTemp - TEMPERATURE_STEP, targetTemp); } commitGammaRamps(nextTemp); if (nextTemp == targetTemp) { // stop timer, we reached the target temp delete m_slowUpdateTimer; m_slowUpdateTimer = nullptr; } } void Manager::updateSunTimings(bool force) { QDateTime todayNow = QDateTime::currentDateTimeUtc(); if (m_mode == NightColorMode::Timings) { QDateTime todayNowLocal = QDateTime::currentDateTime(); QDateTime morB = QDateTime(todayNowLocal.date(), m_morning); QDateTime morE = morB.addSecs(m_trTime * 60); QDateTime eveB = QDateTime(todayNowLocal.date(), m_evening); QDateTime eveE = eveB.addSecs(m_trTime * 60); if (morB <= todayNowLocal && todayNowLocal < eveB) { m_next = DateTimes(eveB, eveE); m_prev = DateTimes(morB, morE); } else if (todayNowLocal < morB) { m_next = DateTimes(morB, morE); m_prev = DateTimes(eveB.addDays(-1), eveE.addDays(-1)); } else { m_next = DateTimes(morB.addDays(1), morE.addDays(1)); m_prev = DateTimes(eveB, eveE); } return; } double lat, lng; if (m_mode == NightColorMode::Automatic) { lat = m_latAuto; lng = m_lngAuto; } else { lat = m_latFixed; lng = m_lngFixed; } if (!force) { // first try by only switching the timings if (daylight()) { // next is morning m_prev = m_next; m_next = getSunTimings(todayNow.date().addDays(1), lat, lng, true); } else { // next is evening m_prev = m_next; m_next = getSunTimings(todayNow.date(), lat, lng, false); } } if (force || !checkAutomaticSunTimings()) { // in case this fails, reset them DateTimes morning = getSunTimings(todayNow.date(), lat, lng, true); if (todayNow < morning.first) { m_prev = getSunTimings(todayNow.date().addDays(-1), lat, lng, false); m_next = morning; } else { DateTimes evening = getSunTimings(todayNow.date(), lat, lng, false); if (todayNow < evening.first) { m_prev = morning; m_next = evening; } else { m_prev = evening; m_next = getSunTimings(todayNow.date().addDays(1), lat, lng, true); } } } } DateTimes Manager::getSunTimings(QDate date, double latitude, double longitude, bool morning) const { Times times = calculateSunTimings(date, latitude, longitude, morning); // At locations near the poles it is possible, that we can't // calculate some or all sun timings (midnight sun). // In this case try to fallback to sensible default values. bool beginDefined = !times.first.isNull(); bool endDefined = !times.second.isNull(); if (!beginDefined || !endDefined) { if (beginDefined) { times.second = times.first.addMSecs( FALLBACK_SLOW_UPDATE_TIME ); } else if (endDefined) { times.first = times.second.addMSecs( - FALLBACK_SLOW_UPDATE_TIME); } else { // Just use default values for morning and evening, but the user // will probably deactivate Night Color anyway if he is living // in a region without clear sun rise and set. times.first = morning ? QTime(6,0,0) : QTime(18,0,0); times.second = times.first.addMSecs( FALLBACK_SLOW_UPDATE_TIME ); } } return DateTimes(QDateTime(date, times.first, Qt::UTC), QDateTime(date, times.second, Qt::UTC)); } bool Manager::checkAutomaticSunTimings() const { if (m_prev.first.isValid() && m_prev.second.isValid() && m_next.first.isValid() && m_next.second.isValid()) { QDateTime todayNow = QDateTime::currentDateTimeUtc(); return m_prev.first <= todayNow && todayNow < m_next.first && m_prev.first.msecsTo(m_next.first) < MSC_DAY * 23./24; } return false; } bool Manager::daylight() const { return m_prev.first.date() == m_next.first.date(); } int Manager::currentTargetTemp() const { if (!m_active) { return NEUTRAL_TEMPERATURE; } if (m_mode == NightColorMode::Constant) { return m_nightTargetTemp; } QDateTime todayNow = QDateTime::currentDateTimeUtc(); auto f = [this, todayNow](int target1, int target2) { if (todayNow <= m_prev.second) { double residueQuota = todayNow.msecsTo(m_prev.second) / (double)m_prev.first.msecsTo(m_prev.second); double ret = (int)((1. - residueQuota) * (double)target2 + residueQuota * (double)target1); // remove single digits ret = ((int)(0.1 * ret)) * 10; return (int)ret; } else { return target2; } }; if (daylight()) { return f(m_nightTargetTemp, m_dayTargetTemp); } else { return f(m_dayTargetTemp, m_nightTargetTemp); } } void Manager::commitGammaRamps(int temperature) { const auto outs = kwinApp()->platform()->outputs(); for (auto *o : outs) { int rampsize = o->gammaRampSize(); GammaRamp ramp(rampsize); /* * The gamma calculation below is based on the Redshift app: * https://github.com/jonls/redshift */ uint16_t *red = ramp.red(); uint16_t *green = ramp.green(); uint16_t *blue = ramp.blue(); // linear default state for (int i = 0; i < rampsize; i++) { uint16_t value = (double)i / rampsize * (UINT16_MAX + 1); red[i] = value; green[i] = value; blue[i] = value; } // approximate white point float whitePoint[3]; float alpha = (temperature % 100) / 100.; int bbCIndex = ((temperature - 1000) / 100) * 3; whitePoint[0] = (1. - alpha) * blackbodyColor[bbCIndex] + alpha * blackbodyColor[bbCIndex + 3]; whitePoint[1] = (1. - alpha) * blackbodyColor[bbCIndex + 1] + alpha * blackbodyColor[bbCIndex + 4]; whitePoint[2] = (1. - alpha) * blackbodyColor[bbCIndex + 2] + alpha * blackbodyColor[bbCIndex + 5]; for (int i = 0; i < rampsize; i++) { red[i] = qreal(red[i]) / (UINT16_MAX+1) * whitePoint[0] * (UINT16_MAX+1); green[i] = qreal(green[i]) / (UINT16_MAX+1) * whitePoint[1] * (UINT16_MAX+1); blue[i] = qreal(blue[i]) / (UINT16_MAX+1) * whitePoint[2] * (UINT16_MAX+1); } if (o->setGammaRamp(ramp)) { m_currentTemp = temperature; m_failedCommitAttempts = 0; } else { m_failedCommitAttempts++; if (m_failedCommitAttempts < 10) { qCWarning(KWIN_COLORCORRECTION).nospace() << "Committing Gamma Ramp failed for output " << o->name() << ". Trying " << (10 - m_failedCommitAttempts) << " times more."; } else { // TODO: On multi monitor setups we could try to rollback earlier changes for already commited outputs qCWarning(KWIN_COLORCORRECTION) << "Gamma Ramp commit failed too often. Deactivating color correction for now."; m_failedCommitAttempts = 0; // reset so we can try again later (i.e. after suspend phase or config change) m_running = false; cancelAllTimers(); } } } } QHash Manager::info() const { return QHash { { QStringLiteral("Available"), kwinApp()->platform()->supportsGammaControl() }, { QStringLiteral("ActiveEnabled"), true}, { QStringLiteral("Active"), m_active}, { QStringLiteral("ModeEnabled"), true}, { QStringLiteral("Mode"), (int)m_mode}, { QStringLiteral("NightTemperatureEnabled"), true}, { QStringLiteral("NightTemperature"), m_nightTargetTemp}, { QStringLiteral("Running"), m_running}, { QStringLiteral("CurrentColorTemperature"), m_currentTemp}, { QStringLiteral("LatitudeAuto"), m_latAuto}, { QStringLiteral("LongitudeAuto"), m_lngAuto}, { QStringLiteral("LocationEnabled"), true}, { QStringLiteral("LatitudeFixed"), m_latFixed}, { QStringLiteral("LongitudeFixed"), m_lngFixed}, { QStringLiteral("TimingsEnabled"), true}, { QStringLiteral("MorningBeginFixed"), m_morning.toString(Qt::ISODate)}, { QStringLiteral("EveningBeginFixed"), m_evening.toString(Qt::ISODate)}, { QStringLiteral("TransitionTime"), m_trTime}, }; } bool Manager::changeConfiguration(QHash data) { bool activeUpdate, modeUpdate, tempUpdate, locUpdate, timeUpdate; activeUpdate = modeUpdate = tempUpdate = locUpdate = timeUpdate = false; bool active = m_active; NightColorMode mode = m_mode; int nightT = m_nightTargetTemp; double lat = m_latFixed; double lng = m_lngFixed; QTime mor = m_morning; QTime eve = m_evening; int trT = m_trTime; QHash::const_iterator iter1, iter2, iter3; iter1 = data.constFind("Active"); if (iter1 != data.constEnd()) { if (!iter1.value().canConvert()) { return false; } bool act = iter1.value().toBool(); activeUpdate = m_active != act; active = act; } iter1 = data.constFind("Mode"); if (iter1 != data.constEnd()) { if (!iter1.value().canConvert()) { return false; } int mo = iter1.value().toInt(); if (mo < 0 || 3 < mo) { return false; } NightColorMode moM; switch (mo) { case 0: moM = NightColorMode::Automatic; break; case 1: moM = NightColorMode::Location; break; case 2: moM = NightColorMode::Timings; break; case 3: moM = NightColorMode::Constant; break; } modeUpdate = m_mode != moM; mode = moM; } iter1 = data.constFind("NightTemperature"); if (iter1 != data.constEnd()) { if (!iter1.value().canConvert()) { return false; } int nT = iter1.value().toInt(); if (nT < MIN_TEMPERATURE || NEUTRAL_TEMPERATURE < nT) { return false; } tempUpdate = m_nightTargetTemp != nT; nightT = nT; } iter1 = data.constFind("LatitudeFixed"); iter2 = data.constFind("LongitudeFixed"); if (iter1 != data.constEnd() && iter2 != data.constEnd()) { if (!iter1.value().canConvert() || !iter2.value().canConvert()) { return false; } double la = iter1.value().toDouble(); double ln = iter2.value().toDouble(); if (!checkLocation(la, ln)) { return false; } locUpdate = m_latFixed != la || m_lngFixed != ln; lat = la; lng = ln; } iter1 = data.constFind("MorningBeginFixed"); iter2 = data.constFind("EveningBeginFixed"); iter3 = data.constFind("TransitionTime"); if (iter1 != data.constEnd() && iter2 != data.constEnd() && iter3 != data.constEnd()) { if (!iter1.value().canConvert() || !iter2.value().canConvert() || !iter3.value().canConvert()) { return false; } QTime mo = QTime::fromString(iter1.value().toString(), Qt::ISODate); QTime ev = QTime::fromString(iter2.value().toString(), Qt::ISODate); if (!mo.isValid() || !ev.isValid()) { return false; } int tT = iter3.value().toInt(); int diffME = mo.msecsTo(ev); if (diffME <= 0 || qMin(diffME, MSC_DAY - diffME) <= tT * 60 * 1000 || tT < 1) { // morning not strictly before evening, transition time too long or transition time out of bounds return false; } timeUpdate = m_morning != mo || m_evening != ev || m_trTime != tT; mor = mo; eve = ev; trT = tT; } if (!(activeUpdate || modeUpdate || tempUpdate || locUpdate || timeUpdate)) { return true; } bool resetNeeded = activeUpdate || modeUpdate || tempUpdate || (locUpdate && mode == NightColorMode::Location) || (timeUpdate && mode == NightColorMode::Timings); if (resetNeeded) { cancelAllTimers(); } Settings *s = Settings::self(); if (activeUpdate) { m_active = active; s->setActive(active); } if (modeUpdate) { m_mode = mode; s->setMode(mode); } if (tempUpdate) { m_nightTargetTemp = nightT; s->setNightTemperature(nightT); } if (locUpdate) { m_latFixed = lat; m_lngFixed = lng; s->setLatitudeFixed(lat); s->setLongitudeFixed(lng); } if (timeUpdate) { m_morning = mor; m_evening = eve; m_trTime = trT; s->setMorningBeginFixed(mor.toString("hhmm")); s->setEveningBeginFixed(eve.toString("hhmm")); s->setTransitionTime(trT); } s->save(); if (resetNeeded) { resetAllTimers(); } emit configChange(info()); return true; } void Manager::autoLocationUpdate(double latitude, double longitude) { if (!checkLocation(latitude, longitude)) { return; } // we tolerate small deviations with minimal impact on sun timings if (qAbs(m_latAuto - latitude) < 2 && qAbs(m_lngAuto - longitude) < 1) { return; } cancelAllTimers(); m_latAuto = latitude; m_lngAuto = longitude; Settings *s = Settings::self(); s->setLatitudeAuto(latitude); s->setLongitudeAuto(longitude); s->save(); resetAllTimers(); emit configChange(info()); } } } diff --git a/composite.cpp b/composite.cpp index fd7f93069..3bc79a28c 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 "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(NULL) + , m_selectionOwner(nullptr) , vBlankInterval(0) , fpsInterval(0) , m_timeSinceLastVBlank(0) - , m_scene(NULL) + , 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 = NULL; + 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 == NULL || m_scene->initFailed()) { + 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 = NULL; + 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(); } 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 (Client *c : Workspace::self()->clientList()) { c->finishCompositing(); } for (Client *c : Workspace::self()->desktopList()) { c->finishCompositing(); } for (Unmanaged *c : Workspace::self()->unmanagedList()) { c->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 = NULL; + 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; } } 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/composite.h b/composite.h index 9f9e52d7e..0dc0d8d0d 100644 --- a/composite.h +++ b/composite.h @@ -1,273 +1,273 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2011 Arthur Arlt Copyright (C) 2012 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 . *********************************************************************/ #pragma once #include #include #include #include #include #include namespace KWin { class Client; class CompositorSelectionOwner; class Scene; class KWIN_EXPORT Compositor : public QObject { Q_OBJECT public: enum class State { On = 0, Off, Starting, Stopping }; ~Compositor() override; static Compositor *self(); // when adding repaints caused by a window, you probably want to use // either Toplevel::addRepaint() or Toplevel::addWorkspaceRepaint() void addRepaint(const QRect& r); void addRepaint(const QRegion& r); void addRepaint(int x, int y, int w, int h); void addRepaintFull(); /** * Schedules a new repaint if no repaint is currently scheduled. */ void scheduleRepaint(); /** * Notifies the compositor that SwapBuffers() is about to be called. * Rendering of the next frame will be deferred until bufferSwapComplete() * is called. */ void aboutToSwapBuffers(); /** * Notifies the compositor that a pending buffer swap has completed. */ void bufferSwapComplete(); /** * Toggles compositing, that is if the Compositor is suspended it will be resumed * and if the Compositor is active it will be suspended. * Invoked by keybinding (shortcut default: Shift + Alt + F12). */ virtual void toggleCompositing() = 0; /** * Re-initializes the Compositor completely. * Connected to the D-Bus signal org.kde.KWin /KWin reinitCompositing */ virtual void reinitialize(); /** * Whether the Compositor is active. That is a Scene is present and the Compositor is * not shutting down itself. */ bool isActive(); virtual int refreshRate() const = 0; Scene *scene() const { return m_scene; } /** * @brief Static check to test whether the Compositor is available and active. * * @return bool @c true if there is a Compositor and it is active, @c false otherwise */ static bool compositing() { - return s_compositor != NULL && s_compositor->isActive(); + return s_compositor != nullptr && s_compositor->isActive(); } // for delayed supportproperty management of effects void keepSupportProperty(xcb_atom_t atom); void removeSupportProperty(xcb_atom_t atom); Q_SIGNALS: void compositingToggled(bool active); void aboutToDestroy(); void aboutToToggleCompositing(); void sceneCreated(); void bufferSwapCompleted(); protected: explicit Compositor(QObject *parent = nullptr); void timerEvent(QTimerEvent *te) override; virtual void start() = 0; void stop(); /** * @brief Prepares start. * @return bool @c true if start should be continued and @c if not. */ bool setupStart(); /** * Continues the startup after Scene And Workspace are created */ void startupWithWorkspace(); virtual void performCompositing(); virtual void configChanged(); void destroyCompositorSelection(); static Compositor *s_compositor; private: void claimCompositorSelection(); void setupX11Support(); void setCompositeTimer(); bool windowRepaintsPending() const; void releaseCompositorSelection(); void deleteUnusedSupportProperties(); State m_state; QBasicTimer compositeTimer; CompositorSelectionOwner *m_selectionOwner; QTimer m_releaseSelectionTimer; QList m_unusedSupportProperties; QTimer m_unusedSupportPropertyTimer; qint64 vBlankInterval, fpsInterval; QRegion repaints_region; qint64 m_timeSinceLastVBlank; Scene *m_scene; bool m_bufferSwapPending; bool m_composeAtSwapCompletion; int m_framesToTestForSafety = 3; QElapsedTimer m_monotonicClock; }; class KWIN_EXPORT WaylandCompositor : public Compositor { Q_OBJECT public: static WaylandCompositor *create(QObject *parent = nullptr); int refreshRate() const override; void toggleCompositing() override; protected: void start() override; private: explicit WaylandCompositor(QObject *parent); }; class KWIN_EXPORT X11Compositor : public Compositor { Q_OBJECT public: enum SuspendReason { NoReasonSuspend = 0, UserSuspend = 1 << 0, BlockRuleSuspend = 1 << 1, ScriptSuspend = 1 << 2, AllReasonSuspend = 0xff }; Q_DECLARE_FLAGS(SuspendReasons, SuspendReason) static X11Compositor *create(QObject *parent = nullptr); /** * @brief Suspends the Compositor if it is currently active. * * Note: it is possible that the Compositor is not able to suspend. Use isActive to check * whether the Compositor has been suspended. * * @return void * @see resume * @see isActive */ Q_INVOKABLE void suspend(SuspendReason reason); /** * @brief Resumes the Compositor if it is currently suspended. * * Note: it is possible that the Compositor cannot be resumed, that is there might be Clients * blocking the usage of Compositing or the Scene might be broken. Use isActive to check * whether the Compositor has been resumed. Also check isCompositingPossible and * isOpenGLBroken. * * Note: The starting of the Compositor can require some time and is partially done threaded. * After this method returns the setup may not have been completed. * * @return void * @see suspend * @see isActive * @see isCompositingPossible * @see isOpenGLBroken */ Q_INVOKABLE void resume(SuspendReason reason); void toggleCompositing() override; void reinitialize() override; void configChanged() override; /** * Checks whether @p w is the Scene's overlay window. */ bool checkForOverlayWindow(WId w) const; /** * @returns Whether the Scene's Overlay X Window is visible. */ bool isOverlayWindowVisible() const; int refreshRate() const override; void updateClientCompositeBlocking(Client *client = nullptr); static X11Compositor *self(); protected: void start() override; void performCompositing() override; private: explicit X11Compositor(QObject *parent); /** * Whether the Compositor is currently suspended, 8 bits encoding the reason */ SuspendReasons m_suspended; int m_xrrRefreshRate; }; } diff --git a/cursor.cpp b/cursor.cpp index 47e60e05b..757669006 100644 --- a/cursor.cpp +++ b/cursor.cpp @@ -1,475 +1,475 @@ /******************************************************************** 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 "cursor.h" // kwin #include #include "input.h" #include "keyboard_input.h" #include "main.h" #include "platform.h" #include "utils.h" #include "xcbutils.h" // KDE #include #include #include // Qt #include #include #include #include namespace KWin { Cursor *Cursor::s_self = nullptr; Cursor::Cursor(QObject *parent) : QObject(parent) , m_mousePollingCounter(0) , m_cursorTrackingCounter(0) , m_themeName("default") , m_themeSize(24) { s_self = this; loadThemeSettings(); QDBusConnection::sessionBus().connect(QString(), QStringLiteral("/KGlobalSettings"), QStringLiteral("org.kde.KGlobalSettings"), QStringLiteral("notifyChange"), this, SLOT(slotKGlobalSettingsNotifyChange(int,int))); } Cursor::~Cursor() { - s_self = NULL; + s_self = nullptr; } void Cursor::loadThemeSettings() { QString themeName = QString::fromUtf8(qgetenv("XCURSOR_THEME")); bool ok = false; // XCURSOR_SIZE might not be set (e.g. by startkde) const uint themeSize = qEnvironmentVariableIntValue("XCURSOR_SIZE", &ok); if (!themeName.isEmpty() && ok) { updateTheme(themeName, themeSize); return; } // didn't get from environment variables, read from config file loadThemeFromKConfig(); } void Cursor::loadThemeFromKConfig() { KConfigGroup mousecfg(kwinApp()->inputConfig(), "Mouse"); const QString themeName = mousecfg.readEntry("cursorTheme", "default"); const uint themeSize = mousecfg.readEntry("cursorSize", 0); updateTheme(themeName, themeSize); } void Cursor::updateTheme(const QString &name, int size) { if (m_themeName != name || m_themeSize != size) { m_themeName = name; m_themeSize = size; emit themeChanged(); } } void Cursor::slotKGlobalSettingsNotifyChange(int type, int arg) { Q_UNUSED(arg) if (type == 5 /*CursorChanged*/) { kwinApp()->inputConfig()->reparseConfiguration(); loadThemeFromKConfig(); // sync to environment qputenv("XCURSOR_THEME", m_themeName.toUtf8()); qputenv("XCURSOR_SIZE", QByteArray::number(m_themeSize)); } } QPoint Cursor::pos() { s_self->doGetPos(); return s_self->m_pos; } void Cursor::setPos(const QPoint &pos) { // first query the current pos to not warp to the already existing pos if (pos == Cursor::pos()) { return; } s_self->m_pos = pos; s_self->doSetPos(); } void Cursor::setPos(int x, int y) { Cursor::setPos(QPoint(x, y)); } xcb_cursor_t Cursor::getX11Cursor(CursorShape shape) { Q_UNUSED(shape) return XCB_CURSOR_NONE; } xcb_cursor_t Cursor::getX11Cursor(const QByteArray &name) { Q_UNUSED(name) return XCB_CURSOR_NONE; } xcb_cursor_t Cursor::x11Cursor(CursorShape shape) { return s_self->getX11Cursor(shape); } xcb_cursor_t Cursor::x11Cursor(const QByteArray &name) { return s_self->getX11Cursor(name); } void Cursor::doSetPos() { emit posChanged(m_pos); } void Cursor::doGetPos() { } void Cursor::updatePos(const QPoint &pos) { if (m_pos == pos) { return; } m_pos = pos; emit posChanged(m_pos); } void Cursor::startMousePolling() { ++m_mousePollingCounter; if (m_mousePollingCounter == 1) { doStartMousePolling(); } } void Cursor::stopMousePolling() { Q_ASSERT(m_mousePollingCounter > 0); --m_mousePollingCounter; if (m_mousePollingCounter == 0) { doStopMousePolling(); } } void Cursor::doStartMousePolling() { } void Cursor::doStopMousePolling() { } void Cursor::startCursorTracking() { ++m_cursorTrackingCounter; if (m_cursorTrackingCounter == 1) { doStartCursorTracking(); } } void Cursor::stopCursorTracking() { Q_ASSERT(m_cursorTrackingCounter > 0); --m_cursorTrackingCounter; if (m_cursorTrackingCounter == 0) { doStopCursorTracking(); } } void Cursor::doStartCursorTracking() { } void Cursor::doStopCursorTracking() { } QVector Cursor::cursorAlternativeNames(const QByteArray &name) const { static const QHash> alternatives = { {QByteArrayLiteral("left_ptr"), {QByteArrayLiteral("arrow"), QByteArrayLiteral("dnd-none"), QByteArrayLiteral("op_left_arrow")}}, {QByteArrayLiteral("cross"), {QByteArrayLiteral("crosshair"), QByteArrayLiteral("diamond-cross"), QByteArrayLiteral("cross-reverse")}}, {QByteArrayLiteral("up_arrow"), {QByteArrayLiteral("center_ptr"), QByteArrayLiteral("sb_up_arrow"), QByteArrayLiteral("centre_ptr")}}, {QByteArrayLiteral("wait"), {QByteArrayLiteral("watch"), QByteArrayLiteral("progress")}}, {QByteArrayLiteral("ibeam"), {QByteArrayLiteral("xterm"), QByteArrayLiteral("text")}}, {QByteArrayLiteral("size_all"), {QByteArrayLiteral("fleur")}}, {QByteArrayLiteral("pointing_hand"), {QByteArrayLiteral("hand2"), QByteArrayLiteral("hand"), QByteArrayLiteral("hand1"), QByteArrayLiteral("pointer"), QByteArrayLiteral("e29285e634086352946a0e7090d73106"), QByteArrayLiteral("9d800788f1b08800ae810202380a0822")}}, {QByteArrayLiteral("size_ver"), {QByteArrayLiteral("00008160000006810000408080010102"), QByteArrayLiteral("sb_v_double_arrow"), QByteArrayLiteral("v_double_arrow"), QByteArrayLiteral("n-resize"), QByteArrayLiteral("s-resize"), QByteArrayLiteral("col-resize"), QByteArrayLiteral("top_side"), QByteArrayLiteral("bottom_side"), QByteArrayLiteral("base_arrow_up"), QByteArrayLiteral("base_arrow_down"), QByteArrayLiteral("based_arrow_down"), QByteArrayLiteral("based_arrow_up")}}, {QByteArrayLiteral("size_hor"), {QByteArrayLiteral("028006030e0e7ebffc7f7070c0600140"), QByteArrayLiteral("sb_h_double_arrow"), QByteArrayLiteral("h_double_arrow"), QByteArrayLiteral("e-resize"), QByteArrayLiteral("w-resize"), QByteArrayLiteral("row-resize"), QByteArrayLiteral("right_side"), QByteArrayLiteral("left_side")}}, {QByteArrayLiteral("size_bdiag"), {QByteArrayLiteral("fcf1c3c7cd4491d801f1e1c78f100000"), QByteArrayLiteral("fd_double_arrow"), QByteArrayLiteral("bottom_left_corner"), QByteArrayLiteral("top_right_corner")}}, {QByteArrayLiteral("size_fdiag"), {QByteArrayLiteral("c7088f0f3e6c8088236ef8e1e3e70000"), QByteArrayLiteral("bd_double_arrow"), QByteArrayLiteral("bottom_right_corner"), QByteArrayLiteral("top_left_corner")}}, {QByteArrayLiteral("whats_this"), {QByteArrayLiteral("d9ce0ab605698f320427677b458ad60b"), QByteArrayLiteral("left_ptr_help"), QByteArrayLiteral("help"), QByteArrayLiteral("question_arrow"), QByteArrayLiteral("dnd-ask"), QByteArrayLiteral("5c6cd98b3f3ebcb1f9c7f1c204630408")}}, {QByteArrayLiteral("split_h"), {QByteArrayLiteral("14fef782d02440884392942c11205230"), QByteArrayLiteral("size_hor")}}, {QByteArrayLiteral("split_v"), {QByteArrayLiteral("2870a09082c103050810ffdffffe0204"), QByteArrayLiteral("size_ver")}}, {QByteArrayLiteral("forbidden"), {QByteArrayLiteral("03b6e0fcb3499374a867c041f52298f0"), QByteArrayLiteral("circle"), QByteArrayLiteral("dnd-no-drop"), QByteArrayLiteral("not-allowed")}}, {QByteArrayLiteral("left_ptr_watch"), {QByteArrayLiteral("3ecb610c1bf2410f44200f48c40d3599"), QByteArrayLiteral("00000000000000020006000e7e9ffc3f"), QByteArrayLiteral("08e8e1c95fe2fc01f976f1e063a24ccd")}}, {QByteArrayLiteral("openhand"), {QByteArrayLiteral("9141b49c8149039304290b508d208c40"), QByteArrayLiteral("all_scroll"), QByteArrayLiteral("all-scroll")}}, {QByteArrayLiteral("closedhand"), {QByteArrayLiteral("05e88622050804100c20044008402080"), QByteArrayLiteral("4498f0e0c1937ffe01fd06f973665830"), QByteArrayLiteral("9081237383d90e509aa00f00170e968f"), QByteArrayLiteral("fcf21c00b30f7e3f83fe0dfd12e71cff")}}, {QByteArrayLiteral("dnd-link"), {QByteArrayLiteral("link"), QByteArrayLiteral("alias"), QByteArrayLiteral("3085a0e285430894940527032f8b26df"), QByteArrayLiteral("640fb0e74195791501fd1ed57b41487f"), QByteArrayLiteral("a2a266d0498c3104214a47bd64ab0fc8")}}, {QByteArrayLiteral("dnd-copy"), {QByteArrayLiteral("copy"), QByteArrayLiteral("1081e37283d90000800003c07f3ef6bf"), QByteArrayLiteral("6407b0e94181790501fd1e167b474872"), QByteArrayLiteral("b66166c04f8c3109214a4fbd64a50fc8")}}, {QByteArrayLiteral("dnd-move"), {QByteArrayLiteral("move")}}, {QByteArrayLiteral("sw-resize"), {QByteArrayLiteral("size_bdiag"), QByteArrayLiteral("fcf1c3c7cd4491d801f1e1c78f100000"), QByteArrayLiteral("fd_double_arrow"), QByteArrayLiteral("bottom_left_corner")}}, {QByteArrayLiteral("se-resize"), {QByteArrayLiteral("size_fdiag"), QByteArrayLiteral("c7088f0f3e6c8088236ef8e1e3e70000"), QByteArrayLiteral("bd_double_arrow"), QByteArrayLiteral("bottom_right_corner")}}, {QByteArrayLiteral("ne-resize"), {QByteArrayLiteral("size_bdiag"), QByteArrayLiteral("fcf1c3c7cd4491d801f1e1c78f100000"), QByteArrayLiteral("fd_double_arrow"), QByteArrayLiteral("top_right_corner")}}, {QByteArrayLiteral("nw-resize"), {QByteArrayLiteral("size_fdiag"), QByteArrayLiteral("c7088f0f3e6c8088236ef8e1e3e70000"), QByteArrayLiteral("bd_double_arrow"), QByteArrayLiteral("top_left_corner")}}, {QByteArrayLiteral("n-resize"), {QByteArrayLiteral("size_ver"), QByteArrayLiteral("00008160000006810000408080010102"), QByteArrayLiteral("sb_v_double_arrow"), QByteArrayLiteral("v_double_arrow"), QByteArrayLiteral("col-resize"), QByteArrayLiteral("top_side")}}, {QByteArrayLiteral("e-resize"), {QByteArrayLiteral("size_hor"), QByteArrayLiteral("028006030e0e7ebffc7f7070c0600140"), QByteArrayLiteral("sb_h_double_arrow"), QByteArrayLiteral("h_double_arrow"), QByteArrayLiteral("row-resize"), QByteArrayLiteral("left_side")}}, {QByteArrayLiteral("s-resize"), {QByteArrayLiteral("size_ver"), QByteArrayLiteral("00008160000006810000408080010102"), QByteArrayLiteral("sb_v_double_arrow"), QByteArrayLiteral("v_double_arrow"), QByteArrayLiteral("col-resize"), QByteArrayLiteral("bottom_side")}}, {QByteArrayLiteral("w-resize"), {QByteArrayLiteral("size_hor"), QByteArrayLiteral("028006030e0e7ebffc7f7070c0600140"), QByteArrayLiteral("sb_h_double_arrow"), QByteArrayLiteral("h_double_arrow"), QByteArrayLiteral("right_side")}} }; auto it = alternatives.find(name); if (it != alternatives.end()) { return it.value(); } return QVector(); } QByteArray CursorShape::name() const { switch (m_shape) { case Qt::ArrowCursor: return QByteArrayLiteral("left_ptr"); case Qt::UpArrowCursor: return QByteArrayLiteral("up_arrow"); case Qt::CrossCursor: return QByteArrayLiteral("cross"); case Qt::WaitCursor: return QByteArrayLiteral("wait"); case Qt::IBeamCursor: return QByteArrayLiteral("ibeam"); case Qt::SizeVerCursor: return QByteArrayLiteral("size_ver"); case Qt::SizeHorCursor: return QByteArrayLiteral("size_hor"); case Qt::SizeBDiagCursor: return QByteArrayLiteral("size_bdiag"); case Qt::SizeFDiagCursor: return QByteArrayLiteral("size_fdiag"); case Qt::SizeAllCursor: return QByteArrayLiteral("size_all"); case Qt::SplitVCursor: return QByteArrayLiteral("split_v"); case Qt::SplitHCursor: return QByteArrayLiteral("split_h"); case Qt::PointingHandCursor: return QByteArrayLiteral("pointing_hand"); case Qt::ForbiddenCursor: return QByteArrayLiteral("forbidden"); case Qt::OpenHandCursor: return QByteArrayLiteral("openhand"); case Qt::ClosedHandCursor: return QByteArrayLiteral("closedhand"); case Qt::WhatsThisCursor: return QByteArrayLiteral("whats_this"); case Qt::BusyCursor: return QByteArrayLiteral("left_ptr_watch"); case Qt::DragMoveCursor: return QByteArrayLiteral("dnd-move"); case Qt::DragCopyCursor: return QByteArrayLiteral("dnd-copy"); case Qt::DragLinkCursor: return QByteArrayLiteral("dnd-link"); case KWin::ExtendedCursor::SizeNorthEast: return QByteArrayLiteral("ne-resize"); case KWin::ExtendedCursor::SizeNorth: return QByteArrayLiteral("n-resize"); case KWin::ExtendedCursor::SizeNorthWest: return QByteArrayLiteral("nw-resize"); case KWin::ExtendedCursor::SizeEast: return QByteArrayLiteral("e-resize"); case KWin::ExtendedCursor::SizeWest: return QByteArrayLiteral("w-resize"); case KWin::ExtendedCursor::SizeSouthEast: return QByteArrayLiteral("se-resize"); case KWin::ExtendedCursor::SizeSouth: return QByteArrayLiteral("s-resize"); case KWin::ExtendedCursor::SizeSouthWest: return QByteArrayLiteral("sw-resize"); default: return QByteArray(); } } InputRedirectionCursor::InputRedirectionCursor(QObject *parent) : Cursor(parent) , m_currentButtons(Qt::NoButton) { connect(input(), SIGNAL(globalPointerChanged(QPointF)), SLOT(slotPosChanged(QPointF))); connect(input(), SIGNAL(pointerButtonStateChanged(uint32_t,InputRedirection::PointerButtonState)), SLOT(slotPointerButtonChanged())); #ifndef KCMRULES connect(input(), &InputRedirection::keyboardModifiersChanged, this, &InputRedirectionCursor::slotModifiersChanged); #endif } InputRedirectionCursor::~InputRedirectionCursor() { } void InputRedirectionCursor::doSetPos() { if (input()->supportsPointerWarping()) { input()->warpPointer(currentPos()); } slotPosChanged(input()->globalPointer()); emit posChanged(currentPos()); } void InputRedirectionCursor::slotPosChanged(const QPointF &pos) { const QPoint oldPos = currentPos(); updatePos(pos.toPoint()); emit mouseChanged(pos.toPoint(), oldPos, m_currentButtons, m_currentButtons, input()->keyboardModifiers(), input()->keyboardModifiers()); } void InputRedirectionCursor::slotModifiersChanged(Qt::KeyboardModifiers mods, Qt::KeyboardModifiers oldMods) { emit mouseChanged(currentPos(), currentPos(), m_currentButtons, m_currentButtons, mods, oldMods); } void InputRedirectionCursor::slotPointerButtonChanged() { const Qt::MouseButtons oldButtons = m_currentButtons; m_currentButtons = input()->qtButtonStates(); const QPoint pos = currentPos(); emit mouseChanged(pos, pos, m_currentButtons, oldButtons, input()->keyboardModifiers(), input()->keyboardModifiers()); } void InputRedirectionCursor::doStartCursorTracking() { #ifndef KCMRULES connect(kwinApp()->platform(), &Platform::cursorChanged, this, &Cursor::cursorChanged); #endif } void InputRedirectionCursor::doStopCursorTracking() { #ifndef KCMRULES disconnect(kwinApp()->platform(), &Platform::cursorChanged, this, &Cursor::cursorChanged); #endif } } // namespace diff --git a/deleted.cpp b/deleted.cpp index 1713efc5f..9cf4d49fc 100644 --- a/deleted.cpp +++ b/deleted.cpp @@ -1,298 +1,298 @@ /******************************************************************** 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) == NULL); + Q_ASSERT(dynamic_cast< Deleted* >(c) == nullptr); Toplevel::copyToDeleted(c); 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_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(); } 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/effects.cpp b/effects.cpp index 1d3083eb9..65b8036e2 100644 --- a/effects.cpp +++ b/effects.cpp @@ -1,2386 +1,2386 @@ /******************************************************************** 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 "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()), - NULL)); + 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(NULL) - , fullscreen_effect(0) + , 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() : 0); + 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::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); } 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 NULL; + 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 == NULL || t->effectWindow() == NULL) + 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 == NULL || t->effectWindow() == NULL) + 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 != NULL) + 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 != NULL); + Q_ASSERT(keyboard_grab_effect != nullptr); doUngrabKeyboard(); - keyboard_grab_effect = NULL; + keyboard_grab_effect = nullptr; } void EffectsHandlerImpl::doUngrabKeyboard() { } void EffectsHandlerImpl::grabbedKeyboardEvent(QKeyEvent* e) { - if (keyboard_grab_effect != NULL) + 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 NULL; + 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 != NULL; + 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() : NULL; + 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 NULL; + 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(); } 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 NULL; + 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; } 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); 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(0) + : QObject(nullptr) , EffectFrame() , m_style(style) , m_static(staticSize) , m_point(position) , m_alignment(alignment) - , m_shader(NULL) + , 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 = NULL; + 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/effects/backgroundcontrast/contrastshader.cpp b/effects/backgroundcontrast/contrastshader.cpp index 64a708768..936a88e69 100644 --- a/effects/backgroundcontrast/contrastshader.cpp +++ b/effects/backgroundcontrast/contrastshader.cpp @@ -1,210 +1,210 @@ /* * Copyright © 2010 Fredrik Höglund * Copyright 2014 Marco Martin * * 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; see the file COPYING. if not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "contrastshader.h" #include #include #include #include #include #include #include using namespace KWin; ContrastShader::ContrastShader() - : mValid(false), shader(NULL), m_opacity(1) + : mValid(false), shader(nullptr), m_opacity(1) { } ContrastShader::~ContrastShader() { reset(); } ContrastShader *ContrastShader::create() { return new ContrastShader(); } void ContrastShader::reset() { delete shader; - shader = NULL; + shader = nullptr; setIsValid(false); } void ContrastShader::setOpacity(float opacity) { m_opacity = opacity; ShaderManager::instance()->pushShader(shader); shader->setUniform(opacityLocation, opacity); ShaderManager::instance()->popShader(); } float ContrastShader::opacity() const { return m_opacity; } void ContrastShader::setColorMatrix(const QMatrix4x4 &matrix) { if (!isValid()) return; ShaderManager::instance()->pushShader(shader); shader->setUniform(colorMatrixLocation, matrix); ShaderManager::instance()->popShader(); } void ContrastShader::setTextureMatrix(const QMatrix4x4 &matrix) { if (!isValid()) return; shader->setUniform(textureMatrixLocation, matrix); } void ContrastShader::setModelViewProjectionMatrix(const QMatrix4x4 &matrix) { if (!isValid()) return; shader->setUniform(mvpMatrixLocation, matrix); } void ContrastShader::bind() { if (!isValid()) return; ShaderManager::instance()->pushShader(shader); } void ContrastShader::unbind() { ShaderManager::instance()->popShader(); } void ContrastShader::init() { reset(); const bool gles = GLPlatform::instance()->isGLES(); const bool glsl_140 = !gles && GLPlatform::instance()->glslVersion() >= kVersionNumber(1, 40); const bool core = glsl_140 || (gles && GLPlatform::instance()->glslVersion() >= kVersionNumber(3, 0)); QByteArray vertexSource; QByteArray fragmentSource; const QByteArray attribute = core ? "in" : "attribute"; const QByteArray varying_in = core ? (gles ? "in" : "noperspective in") : "varying"; const QByteArray varying_out = core ? (gles ? "out" : "noperspective out") : "varying"; const QByteArray texture2D = core ? "texture" : "texture2D"; const QByteArray fragColor = core ? "fragColor" : "gl_FragColor"; // Vertex shader // =================================================================== QTextStream stream(&vertexSource); if (gles) { if (core) { stream << "#version 300 es\n\n"; } stream << "precision highp float;\n"; } else if (glsl_140) { stream << "#version 140\n\n"; } stream << "uniform mat4 modelViewProjectionMatrix;\n"; stream << "uniform mat4 textureMatrix;\n"; stream << attribute << " vec4 vertex;\n\n"; stream << varying_out << " vec4 varyingTexCoords;\n"; stream << "\n"; stream << "void main(void)\n"; stream << "{\n"; stream << " varyingTexCoords = vec4(textureMatrix * vertex).stst;\n"; stream << " gl_Position = modelViewProjectionMatrix * vertex;\n"; stream << "}\n"; stream.flush(); // Fragment shader // =================================================================== QTextStream stream2(&fragmentSource); if (gles) { if (core) { stream2 << "#version 300 es\n\n"; } stream2 << "precision highp float;\n"; } else if (glsl_140) { stream2 << "#version 140\n\n"; } stream2 << "uniform mat4 colorMatrix;\n"; stream2 << "uniform sampler2D sampler;\n"; stream2 << "uniform float opacity;\n"; stream2 << varying_in << " vec4 varyingTexCoords;\n"; if (core) stream2 << "out vec4 fragColor;\n\n"; stream2 << "void main(void)\n"; stream2 << "{\n"; stream2 << " vec4 tex = " << texture2D << "(sampler, varyingTexCoords.st);\n"; stream2 << " if (opacity >= 1.0) {\n"; stream2 << " " << fragColor << " = tex * colorMatrix;\n"; stream2 << " } else {\n"; stream2 << " " << fragColor << " = tex * (opacity * colorMatrix + (1.0 - opacity) * mat4(1.0));\n"; stream2 << " }\n"; stream2 << "}\n"; stream2.flush(); shader = ShaderManager::instance()->loadShaderFromCode(vertexSource, fragmentSource); if (shader->isValid()) { colorMatrixLocation = shader->uniformLocation("colorMatrix"); textureMatrixLocation = shader->uniformLocation("textureMatrix"); mvpMatrixLocation = shader->uniformLocation("modelViewProjectionMatrix"); opacityLocation = shader->uniformLocation("opacity"); QMatrix4x4 modelViewProjection; const QSize screenSize = effects->virtualScreenSize(); modelViewProjection.ortho(0, screenSize.width(), screenSize.height(), 0, 0, 65535); ShaderManager::instance()->pushShader(shader); shader->setUniform(colorMatrixLocation, QMatrix4x4()); shader->setUniform(textureMatrixLocation, QMatrix4x4()); shader->setUniform(mvpMatrixLocation, modelViewProjection); shader->setUniform(opacityLocation, (float)1.0); ShaderManager::instance()->popShader(); } setIsValid(shader->isValid()); } diff --git a/effects/blur/blur_config.h b/effects/blur/blur_config.h index a766eec68..60edd08ef 100644 --- a/effects/blur/blur_config.h +++ b/effects/blur/blur_config.h @@ -1,46 +1,46 @@ /* * Copyright © 2010 Fredrik Höglund * * 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; see the file COPYING. if not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef BLUR_CONFIG_H #define BLUR_CONFIG_H #include #include "ui_blur_config.h" namespace KWin { class BlurEffectConfig : public KCModule { Q_OBJECT public: - explicit BlurEffectConfig(QWidget *parent = 0, const QVariantList& args = QVariantList()); + explicit BlurEffectConfig(QWidget *parent = nullptr, const QVariantList& args = QVariantList()); ~BlurEffectConfig() override; void save() override; private: ::Ui::BlurEffectConfig ui; }; } // namespace KWin #endif diff --git a/effects/coverswitch/coverswitch.cpp b/effects/coverswitch/coverswitch.cpp index 29b7009c4..377636677 100644 --- a/effects/coverswitch/coverswitch.cpp +++ b/effects/coverswitch/coverswitch.cpp @@ -1,1005 +1,1005 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2008 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 "coverswitch.h" // KConfigSkeleton #include "coverswitchconfig.h" #include #include #include #include #include #include #include #include #include #include #include #include namespace KWin { CoverSwitchEffect::CoverSwitchEffect() : mActivated(0) , angle(60.0) , animation(false) , start(false) , stop(false) , stopRequested(false) , startRequested(false) , zPosition(900.0) , scaleFactor(0.0) , direction(Left) - , selected_window(0) - , captionFrame(NULL) + , selected_window(nullptr) + , captionFrame(nullptr) , primaryTabBox(false) , secondaryTabBox(false) { initConfig(); reconfigure(ReconfigureAll); // Caption frame captionFont.setBold(true); captionFont.setPointSize(captionFont.pointSize() * 2); if (effects->compositingType() == OpenGL2Compositing) { m_reflectionShader = ShaderManager::instance()->generateShaderFromResources(ShaderTrait::MapTexture, QString(), QStringLiteral("coverswitch-reflection.glsl")); } else { - m_reflectionShader = NULL; + m_reflectionShader = nullptr; } connect(effects, &EffectsHandler::windowClosed, this, &CoverSwitchEffect::slotWindowClosed); connect(effects, &EffectsHandler::tabBoxAdded, this, &CoverSwitchEffect::slotTabBoxAdded); connect(effects, &EffectsHandler::tabBoxClosed, this, &CoverSwitchEffect::slotTabBoxClosed); connect(effects, &EffectsHandler::tabBoxUpdated, this, &CoverSwitchEffect::slotTabBoxUpdated); connect(effects, &EffectsHandler::tabBoxKeyEvent, this, &CoverSwitchEffect::slotTabBoxKeyEvent); } CoverSwitchEffect::~CoverSwitchEffect() { delete captionFrame; delete m_reflectionShader; } bool CoverSwitchEffect::supported() { return effects->isOpenGLCompositing() && effects->animationsSupported(); } void CoverSwitchEffect::reconfigure(ReconfigureFlags) { CoverSwitchConfig::self()->read(); animationDuration = std::chrono::milliseconds( animationTime(200)); animateSwitch = CoverSwitchConfig::animateSwitch(); animateStart = CoverSwitchConfig::animateStart(); animateStop = CoverSwitchConfig::animateStop(); reflection = CoverSwitchConfig::reflection(); windowTitle = CoverSwitchConfig::windowTitle(); zPosition = CoverSwitchConfig::zPosition(); timeLine.setEasingCurve(QEasingCurve::InOutSine); timeLine.setDuration(animationDuration); // Defined outside the ui primaryTabBox = CoverSwitchConfig::tabBox(); secondaryTabBox = CoverSwitchConfig::tabBoxAlternative(); QColor tmp = CoverSwitchConfig::mirrorFrontColor(); mirrorColor[0][0] = tmp.redF(); mirrorColor[0][1] = tmp.greenF(); mirrorColor[0][2] = tmp.blueF(); mirrorColor[0][3] = 1.0; tmp = CoverSwitchConfig::mirrorRearColor(); mirrorColor[1][0] = tmp.redF(); mirrorColor[1][1] = tmp.greenF(); mirrorColor[1][2] = tmp.blueF(); mirrorColor[1][3] = -1.0; } void CoverSwitchEffect::prePaintScreen(ScreenPrePaintData& data, int time) { if (mActivated || stop || stopRequested) { data.mask |= Effect::PAINT_SCREEN_WITH_TRANSFORMED_WINDOWS; if (animation || start || stop) { timeLine.update(std::chrono::milliseconds(time)); } - if (selected_window == NULL) + if (selected_window == nullptr) abort(); } effects->prePaintScreen(data, time); } void CoverSwitchEffect::paintScreen(int mask, QRegion region, ScreenPaintData& data) { effects->paintScreen(mask, region, data); if (mActivated || stop || stopRequested) { QList< EffectWindow* > tempList = currentWindowList; int index = tempList.indexOf(selected_window); if (animation || start || stop) { if (!start && !stop) { if (direction == Right) index++; else index--; if (index < 0) index = tempList.count() + index; if (index >= tempList.count()) index = index % tempList.count(); } foreach (Direction direction, scheduled_directions) { if (direction == Right) index++; else index--; if (index < 0) index = tempList.count() + index; if (index >= tempList.count()) index = index % tempList.count(); } } int leftIndex = index - 1; if (leftIndex < 0) leftIndex = tempList.count() - 1; int rightIndex = index + 1; if (rightIndex == tempList.count()) rightIndex = 0; EffectWindow* frontWindow = tempList[ index ]; leftWindows.clear(); rightWindows.clear(); bool evenWindows = (tempList.count() % 2 == 0) ? true : false; int leftWindowCount = 0; if (evenWindows) leftWindowCount = tempList.count() / 2 - 1; else leftWindowCount = (tempList.count() - 1) / 2; for (int i = 0; i < leftWindowCount; i++) { int tempIndex = (leftIndex - i); if (tempIndex < 0) tempIndex = tempList.count() + tempIndex; leftWindows.prepend(tempList[ tempIndex ]); } int rightWindowCount = 0; if (evenWindows) rightWindowCount = tempList.count() / 2; else rightWindowCount = (tempList.count() - 1) / 2; for (int i = 0; i < rightWindowCount; i++) { int tempIndex = (rightIndex + i) % tempList.count(); rightWindows.prepend(tempList[ tempIndex ]); } if (reflection) { // no reflections during start and stop animation // except when using a shader if ((!start && !stop) || effects->compositingType() == OpenGL2Compositing) paintScene(frontWindow, leftWindows, rightWindows, true); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); // we can use a huge scale factor (needed to calculate the rearground vertices) // as we restrict with a PaintClipper painting on the current screen float reflectionScaleFactor = 100000 * tan(60.0 * M_PI / 360.0f) / area.width(); const float width = area.width(); const float height = area.height(); float vertices[] = { -width * 0.5f, height, 0.0, width * 0.5f, height, 0.0, width*reflectionScaleFactor, height, -5000, -width*reflectionScaleFactor, height, -5000 }; // foreground if (start) { mirrorColor[0][3] = timeLine.value(); } else if (stop) { mirrorColor[0][3] = 1.0 - timeLine.value(); } else { mirrorColor[0][3] = 1.0; } int y = 0; // have to adjust the y values to fit OpenGL // in OpenGL y==0 is at bottom, in Qt at top if (effects->numScreens() > 1) { QRect fullArea = effects->clientArea(FullArea, 0, 1); if (fullArea.height() != area.height()) { if (area.y() == 0) y = fullArea.height() - area.height(); else y = fullArea.height() - area.y() - area.height(); } } // use scissor to restrict painting of the reflection plane to current screen glScissor(area.x(), y, area.width(), area.height()); glEnable(GL_SCISSOR_TEST); if (m_reflectionShader && m_reflectionShader->isValid()) { ShaderManager::instance()->pushShader(m_reflectionShader); QMatrix4x4 windowTransformation = data.projectionMatrix(); windowTransformation.translate(area.x() + area.width() * 0.5f, 0.0, 0.0); m_reflectionShader->setUniform(GLShader::ModelViewProjectionMatrix, windowTransformation); m_reflectionShader->setUniform("u_frontColor", QVector4D(mirrorColor[0][0], mirrorColor[0][1], mirrorColor[0][2], mirrorColor[0][3])); m_reflectionShader->setUniform("u_backColor", QVector4D(mirrorColor[1][0], mirrorColor[1][1], mirrorColor[1][2], mirrorColor[1][3])); // TODO: make this one properly QVector verts; QVector texcoords; verts.reserve(18); texcoords.reserve(12); texcoords << 1.0 << 0.0; verts << vertices[6] << vertices[7] << vertices[8]; texcoords << 1.0 << 0.0; verts << vertices[9] << vertices[10] << vertices[11]; texcoords << 0.0 << 0.0; verts << vertices[0] << vertices[1] << vertices[2]; texcoords << 0.0 << 0.0; verts << vertices[0] << vertices[1] << vertices[2]; texcoords << 0.0 << 0.0; verts << vertices[3] << vertices[4] << vertices[5]; texcoords << 1.0 << 0.0; verts << vertices[6] << vertices[7] << vertices[8]; GLVertexBuffer *vbo = GLVertexBuffer::streamingBuffer(); vbo->reset(); vbo->setData(6, 3, verts.data(), texcoords.data()); vbo->render(GL_TRIANGLES); ShaderManager::instance()->popShader(); } glDisable(GL_SCISSOR_TEST); glDisable(GL_BLEND); } paintScene(frontWindow, leftWindows, rightWindows); // Render the caption frame if (windowTitle) { double opacity = 1.0; if (start) opacity = timeLine.value(); else if (stop) opacity = 1.0 - timeLine.value(); if (animation) captionFrame->setCrossFadeProgress(timeLine.value()); captionFrame->render(region, opacity); } } } void CoverSwitchEffect::postPaintScreen() { if ((mActivated && (animation || start)) || stop || stopRequested) { if (timeLine.done()) { timeLine.reset(); if (stop) { stop = false; - effects->setActiveFullScreenEffect(0); + effects->setActiveFullScreenEffect(nullptr); foreach (EffectWindow * window, referrencedWindows) { window->unrefWindow(); } referrencedWindows.clear(); currentWindowList.clear(); if (startRequested) { startRequested = false; mActivated = true; effects->refTabBox(); currentWindowList = effects->currentTabBoxWindowList(); if (animateStart) { start = true; } } } else if (!scheduled_directions.isEmpty()) { direction = scheduled_directions.dequeue(); if (start) { animation = true; start = false; } } else { animation = false; start = false; if (stopRequested) { stopRequested = false; stop = true; } } } effects->addRepaintFull(); } effects->postPaintScreen(); } void CoverSwitchEffect::paintScene(EffectWindow* frontWindow, const EffectWindowList& leftWindows, const EffectWindowList& rightWindows, bool reflectedWindows) { // LAYOUT // one window in the front. Other windows left and right rotated // for odd number of windows: left: (n-1)/2; front: 1; right: (n-1)/2 // for even number of windows: left: n/2; front: 1; right: n/2 -1 // // ANIMATION // forward (alt+tab) // all left windows are moved to next position // top most left window is rotated and moved to front window position // front window is rotated and moved to next right window position // right windows are moved to next position // last right window becomes totally transparent in half the time // appears transparent on left side and becomes totally opaque again // backward (alt+shift+tab) same as forward but opposite direction int width = area.width(); int leftWindowCount = leftWindows.count(); int rightWindowCount = rightWindows.count(); // Problem during animation: a window which is painted after another window // appears in front of the other // so during animation the painting order has to be rearreanged // paint sequence no animation: left, right, front // paint sequence forward animation: right, front, left if (!animation) { paintWindows(leftWindows, true, reflectedWindows); paintWindows(rightWindows, false, reflectedWindows); paintFrontWindow(frontWindow, width, leftWindowCount, rightWindowCount, reflectedWindows); } else { if (direction == Right) { if (timeLine.value() < 0.5) { // paint in normal way paintWindows(leftWindows, true, reflectedWindows); paintWindows(rightWindows, false, reflectedWindows); paintFrontWindow(frontWindow, width, leftWindowCount, rightWindowCount, reflectedWindows); } else { paintWindows(rightWindows, false, reflectedWindows); paintFrontWindow(frontWindow, width, leftWindowCount, rightWindowCount, reflectedWindows); paintWindows(leftWindows, true, reflectedWindows, rightWindows.at(0)); } } else { paintWindows(leftWindows, true, reflectedWindows); if (timeLine.value() < 0.5) { paintWindows(rightWindows, false, reflectedWindows); paintFrontWindow(frontWindow, width, leftWindowCount, rightWindowCount, reflectedWindows); } else { EffectWindow* leftWindow; if (leftWindowCount > 0) { leftWindow = leftWindows.at(0); paintFrontWindow(frontWindow, width, leftWindowCount, rightWindowCount, reflectedWindows); } else leftWindow = frontWindow; paintWindows(rightWindows, false, reflectedWindows, leftWindow); } } } } void CoverSwitchEffect::paintWindow(EffectWindow* w, int mask, QRegion region, WindowPaintData& data) { if (mActivated || stop || stopRequested) { if (!(mask & PAINT_WINDOW_TRANSFORMED) && !w->isDesktop()) { if ((start || stop) && w->isDock()) { data.setOpacity(1.0 - timeLine.value()); if (stop) data.setOpacity(timeLine.value()); } else return; } } if ((start || stop) && (!w->isOnCurrentDesktop() || w->isMinimized())) { if (stop) // Fade out windows not on the current desktop data.setOpacity(1.0 - timeLine.value()); else // Fade in Windows from other desktops when animation is started data.setOpacity(timeLine.value()); } effects->paintWindow(w, mask, region, data); } void CoverSwitchEffect::slotTabBoxAdded(int mode) { if (effects->activeFullScreenEffect() && effects->activeFullScreenEffect() != this) return; if (!mActivated) { effects->setShowingDesktop(false); // only for windows mode if (((mode == TabBoxWindowsMode && primaryTabBox) || (mode == TabBoxWindowsAlternativeMode && secondaryTabBox) || (mode == TabBoxCurrentAppWindowsMode && primaryTabBox) || (mode == TabBoxCurrentAppWindowsAlternativeMode && secondaryTabBox)) && effects->currentTabBoxWindowList().count() > 0) { effects->startMouseInterception(this, Qt::ArrowCursor); activeScreen = effects->activeScreen(); if (!stop && !stopRequested) { effects->refTabBox(); effects->setActiveFullScreenEffect(this); scheduled_directions.clear(); selected_window = effects->currentTabBoxWindow(); currentWindowList = effects->currentTabBoxWindowList(); direction = Left; mActivated = true; if (animateStart) { start = true; } // Calculation of correct area area = effects->clientArea(FullScreenArea, activeScreen, effects->currentDesktop()); const QSize screenSize = effects->virtualScreenSize(); scaleFactor = (zPosition + 1100) * 2.0 * tan(60.0 * M_PI / 360.0f) / screenSize.width(); if (screenSize.width() - area.width() != 0) { // one of the screens is smaller than the other (horizontal) if (area.width() < screenSize.width() - area.width()) scaleFactor *= (float)area.width() / (float)(screenSize.width() - area.width()); else if (area.width() != screenSize.width() - area.width()) { // vertical layout with different width // but we don't want to catch screens with same width and different height if (screenSize.height() != area.height()) scaleFactor *= (float)area.width() / (float)(screenSize.width()); } } if (effects->numScreens() > 1) { // unfortunatelly we have to change the projection matrix in dual screen mode // code is adapted from SceneOpenGL2::createProjectionMatrix() QRect fullRect = effects->clientArea(FullArea, activeScreen, effects->currentDesktop()); float fovy = 60.0f; float aspect = 1.0f; float zNear = 0.1f; float zFar = 100.0f; float ymax = zNear * std::tan(fovy * M_PI / 360.0f); float ymin = -ymax; float xmin = ymin * aspect; float xmax = ymax * aspect; if (area.width() != fullRect.width()) { if (area.x() == 0) { // horizontal layout: left screen xmin *= (float)area.width() / (float)fullRect.width(); xmax *= (fullRect.width() - 0.5f * area.width()) / (0.5f * fullRect.width()); } else { // horizontal layout: right screen xmin *= (fullRect.width() - 0.5f * area.width()) / (0.5f * fullRect.width()); xmax *= (float)area.width() / (float)fullRect.width(); } } if (area.height() != fullRect.height()) { if (area.y() == 0) { // vertical layout: top screen ymin *= (fullRect.height() - 0.5f * area.height()) / (0.5f * fullRect.height()); ymax *= (float)area.height() / (float)fullRect.height(); } else { // vertical layout: bottom screen ymin *= (float)area.height() / (float)fullRect.height(); ymax *= (fullRect.height() - 0.5f * area.height()) / (0.5f * fullRect.height()); } } m_projectionMatrix = QMatrix4x4(); m_projectionMatrix.frustum(xmin, xmax, ymin, ymax, zNear, zFar); const float scaleFactor = 1.1f / zNear; // Create a second matrix that transforms screen coordinates // to world coordinates. QMatrix4x4 matrix; matrix.translate(xmin * scaleFactor, ymax * scaleFactor, -1.1); matrix.scale( (xmax - xmin) * scaleFactor / fullRect.width(), -(ymax - ymin) * scaleFactor / fullRect.height(), 0.001); // Combine the matrices m_projectionMatrix *= matrix; m_modelviewMatrix = QMatrix4x4(); m_modelviewMatrix.translate(area.x(), area.y(), 0.0); } // Setup caption frame geometry if (windowTitle) { QRect frameRect = QRect(area.width() * 0.25f + area.x(), area.height() * 0.9f + area.y(), area.width() * 0.5f, QFontMetrics(captionFont).height()); if (!captionFrame) { captionFrame = effects->effectFrame(EffectFrameStyled); captionFrame->setFont(captionFont); captionFrame->enableCrossFade(true); } captionFrame->setGeometry(frameRect); captionFrame->setIconSize(QSize(frameRect.height(), frameRect.height())); // And initial contents updateCaption(); } effects->addRepaintFull(); } else { startRequested = true; } } } } void CoverSwitchEffect::slotTabBoxClosed() { if (mActivated) { if (animateStop) { if (!animation && !start) { stop = true; } else if (start && scheduled_directions.isEmpty()) { start = false; stop = true; timeLine.setElapsed(timeLine.duration() - timeLine.elapsed()); } else { stopRequested = true; } } else { - effects->setActiveFullScreenEffect(0); + effects->setActiveFullScreenEffect(nullptr); start = false; animation = false; timeLine.reset(); } mActivated = false; effects->unrefTabBox(); effects->stopMouseInterception(this); effects->addRepaintFull(); } } void CoverSwitchEffect::slotTabBoxUpdated() { if (mActivated) { if (animateSwitch && currentWindowList.count() > 1) { // determine the switch direction if (selected_window != effects->currentTabBoxWindow()) { - if (selected_window != NULL) { + if (selected_window != nullptr) { int old_index = currentWindowList.indexOf(selected_window); int new_index = effects->currentTabBoxWindowList().indexOf(effects->currentTabBoxWindow()); Direction new_direction; int distance = new_index - old_index; if (distance > 0) new_direction = Left; if (distance < 0) new_direction = Right; if (effects->currentTabBoxWindowList().count() == 2) { new_direction = Left; distance = 1; } if (distance != 0) { distance = abs(distance); int tempDistance = effects->currentTabBoxWindowList().count() - distance; if (tempDistance < abs(distance)) { distance = tempDistance; if (new_direction == Left) new_direction = Right; else new_direction = Left; } if (!animation && !start) { animation = true; direction = new_direction; distance--; } for (int i = 0; i < distance; i++) { if (!scheduled_directions.isEmpty() && scheduled_directions.last() != new_direction) scheduled_directions.pop_back(); else scheduled_directions.enqueue(new_direction); if (scheduled_directions.count() == effects->currentTabBoxWindowList().count()) scheduled_directions.clear(); } } } selected_window = effects->currentTabBoxWindow(); currentWindowList = effects->currentTabBoxWindowList(); updateCaption(); } } effects->addRepaintFull(); } } void CoverSwitchEffect::paintWindowCover(EffectWindow* w, bool reflectedWindow, WindowPaintData& data) { QRect windowRect = w->geometry(); data.setYTranslation(area.height() - windowRect.y() - windowRect.height()); data.setZTranslation(-zPosition); if (start) { if (w->isMinimized()) { data.multiplyOpacity(timeLine.value()); } else { const QVector3D translation = data.translation() * timeLine.value(); data.setXTranslation(translation.x()); data.setYTranslation(translation.y()); data.setZTranslation(translation.z()); if (effects->numScreens() > 1) { QRect clientRect = effects->clientArea(FullScreenArea, w->screen(), effects->currentDesktop()); QRect fullRect = effects->clientArea(FullArea, activeScreen, effects->currentDesktop()); if (w->screen() == activeScreen) { if (clientRect.width() != fullRect.width() && clientRect.x() != fullRect.x()) { data.translate(- clientRect.x() * (1.0f - timeLine.value())); } if (clientRect.height() != fullRect.height() && clientRect.y() != fullRect.y()) { data.translate(0.0, - clientRect.y() * (1.0f - timeLine.value())); } } else { if (clientRect.width() != fullRect.width() && clientRect.x() < area.x()) { data.translate(- clientRect.width() * (1.0f - timeLine.value())); } if (clientRect.height() != fullRect.height() && clientRect.y() < area.y()) { data.translate(0.0, - clientRect.height() * (1.0f - timeLine.value())); } } } data.setRotationAngle(data.rotationAngle() * timeLine.value()); } } if (stop) { if (w->isMinimized() && w != effects->activeWindow()) { data.multiplyOpacity(1.0 - timeLine.value()); } else { const QVector3D translation = data.translation() * (1.0 - timeLine.value()); data.setXTranslation(translation.x()); data.setYTranslation(translation.y()); data.setZTranslation(translation.z()); if (effects->numScreens() > 1) { QRect clientRect = effects->clientArea(FullScreenArea, w->screen(), effects->currentDesktop()); QRect rect = effects->clientArea(FullScreenArea, activeScreen, effects->currentDesktop()); QRect fullRect = effects->clientArea(FullArea, activeScreen, effects->currentDesktop()); if (w->screen() == activeScreen) { if (clientRect.width() != fullRect.width() && clientRect.x() != fullRect.x()) { data.translate(- clientRect.x() * timeLine.value()); } if (clientRect.height() != fullRect.height() && clientRect.y() != fullRect.y()) { data.translate(0.0, - clientRect.y() * timeLine.value()); } } else { if (clientRect.width() != fullRect.width() && clientRect.x() < rect.x()) { data.translate(- clientRect.width() * timeLine.value()); } if (clientRect.height() != fullRect.height() && clientRect.y() < area.y()) { data.translate(0.0, - clientRect.height() * timeLine.value()); } } } data.setRotationAngle(data.rotationAngle() * (1.0 - timeLine.value())); } } if (reflectedWindow) { QMatrix4x4 reflectionMatrix; reflectionMatrix.scale(1.0, -1.0, 1.0); data.setModelViewMatrix(reflectionMatrix*data.modelViewMatrix()); data.setYTranslation(- area.height() - windowRect.y() - windowRect.height()); if (start) { data.multiplyOpacity(timeLine.value()); } else if (stop) { data.multiplyOpacity(1.0 - timeLine.value()); } effects->drawWindow(w, PAINT_WINDOW_TRANSFORMED, infiniteRegion(), data); } else { effects->paintWindow(w, PAINT_WINDOW_TRANSFORMED, infiniteRegion(), data); } } void CoverSwitchEffect::paintFrontWindow(EffectWindow* frontWindow, int width, int leftWindows, int rightWindows, bool reflectedWindow) { - if (frontWindow == NULL) + if (frontWindow == nullptr) return; bool specialHandlingForward = false; WindowPaintData data(frontWindow); if (effects->numScreens() > 1) { data.setProjectionMatrix(m_projectionMatrix); data.setModelViewMatrix(m_modelviewMatrix); } data.setXTranslation(area.width() * 0.5 - frontWindow->geometry().x() - frontWindow->geometry().width() * 0.5); if (leftWindows == 0) { leftWindows = 1; if (!start && !stop) specialHandlingForward = true; } if (rightWindows == 0) { rightWindows = 1; } if (animation) { float distance = 0.0; const QSize screenSize = effects->virtualScreenSize(); if (direction == Right) { // move to right distance = -frontWindow->geometry().width() * 0.5f + area.width() * 0.5f + (((float)screenSize.width() * 0.5 * scaleFactor) - (float)area.width() * 0.5f) / rightWindows; data.translate(distance * timeLine.value()); data.setRotationAxis(Qt::YAxis); data.setRotationAngle(-angle * timeLine.value()); data.setRotationOrigin(QVector3D(frontWindow->geometry().width(), 0.0, 0.0)); } else { // move to left distance = frontWindow->geometry().width() * 0.5f - area.width() * 0.5f + ((float)width * 0.5f - ((float)screenSize.width() * 0.5 * scaleFactor)) / leftWindows; float factor = 1.0; if (specialHandlingForward) factor = 2.0; data.translate(distance * timeLine.value() * factor); data.setRotationAxis(Qt::YAxis); data.setRotationAngle(angle * timeLine.value()); } } if (specialHandlingForward && timeLine.value() < 0.5) { data.multiplyOpacity(1.0 - timeLine.value() * 2.0); } paintWindowCover(frontWindow, reflectedWindow, data); } void CoverSwitchEffect::paintWindows(const EffectWindowList& windows, bool left, bool reflectedWindows, EffectWindow* additionalWindow) { int width = area.width(); int windowCount = windows.count(); EffectWindow* window; int rotateFactor = 1; if (!left) { rotateFactor = -1; } const QSize screenSize = effects->virtualScreenSize(); float xTranslate = -((float)(width) * 0.5f - ((float)screenSize.width() * 0.5 * scaleFactor)); if (!left) xTranslate = ((float)screenSize.width() * 0.5 * scaleFactor) - (float)width * 0.5f; // handling for additional window from other side // has to appear on this side after half of the time - if (animation && timeLine.value() >= 0.5 && additionalWindow != NULL) { + if (animation && timeLine.value() >= 0.5 && additionalWindow != nullptr) { WindowPaintData data(additionalWindow); if (effects->numScreens() > 1) { data.setProjectionMatrix(m_projectionMatrix); data.setModelViewMatrix(m_modelviewMatrix); } data.setRotationAxis(Qt::YAxis); data.setRotationAngle(angle * rotateFactor); if (left) { data.translate(-xTranslate - additionalWindow->geometry().x()); } else { data.translate(xTranslate + area.width() - additionalWindow->geometry().x() - additionalWindow->geometry().width()); data.setRotationOrigin(QVector3D(additionalWindow->geometry().width(), 0.0, 0.0)); } data.multiplyOpacity((timeLine.value() - 0.5) * 2.0); paintWindowCover(additionalWindow, reflectedWindows, data); } // normal behaviour for (int i = 0; i < windows.count(); i++) { window = windows.at(i); - if (window == NULL || window->isDeleted()) { + if (window == nullptr || window->isDeleted()) { continue; } WindowPaintData data(window); if (effects->numScreens() > 1) { data.setProjectionMatrix(m_projectionMatrix); data.setModelViewMatrix(m_modelviewMatrix); } data.setRotationAxis(Qt::YAxis); data.setRotationAngle(angle); if (left) data.translate(-xTranslate + xTranslate * i / windowCount - window->geometry().x()); else data.translate(xTranslate + width - xTranslate * i / windowCount - window->geometry().x() - window->geometry().width()); if (animation) { if (direction == Right) { if ((i == windowCount - 1) && left) { // right most window on left side -> move to front // have to move one window distance plus half the difference between the window and the desktop size data.translate((xTranslate / windowCount + (width - window->geometry().width()) * 0.5f) * timeLine.value()); data.setRotationAngle(angle - angle * timeLine.value()); } // right most window does not have to be moved else if (!left && (i == 0)); // do nothing else { // all other windows - move to next position data.translate(xTranslate / windowCount * timeLine.value()); } } else { if ((i == windowCount - 1) && !left) { // left most window on right side -> move to front data.translate(- (xTranslate / windowCount + (width - window->geometry().width()) * 0.5f) * timeLine.value()); data.setRotationAngle(angle - angle * timeLine.value()); } // left most window does not have to be moved else if (i == 0 && left); // do nothing else { // all other windows - move to next position data.translate(- xTranslate / windowCount * timeLine.value()); } } } if (!left) data.setRotationOrigin(QVector3D(window->geometry().width(), 0.0, 0.0)); data.setRotationAngle(data.rotationAngle() * rotateFactor); // make window most to edge transparent if animation if (animation && i == 0 && ((direction == Left && left) || (direction == Right && !left))) { // only for the first half of the animation if (timeLine.value() < 0.5) { data.multiplyOpacity((1.0 - timeLine.value() * 2.0)); paintWindowCover(window, reflectedWindows, data); } } else { paintWindowCover(window, reflectedWindows, data); } } } void CoverSwitchEffect::windowInputMouseEvent(QEvent* e) { if (e->type() != QEvent::MouseButtonPress) return; // we don't want click events during animations if (animation) return; QMouseEvent* event = static_cast< QMouseEvent* >(e); switch (event->button()) { case Qt::XButton1: // wheel up selectPreviousWindow(); break; case Qt::XButton2: // wheel down selectNextWindow(); break; case Qt::LeftButton: case Qt::RightButton: case Qt::MidButton: default: QPoint pos = event->pos(); // determine if a window has been clicked // not interested in events above a fullscreen window (ignoring panel size) if (pos.y() < (area.height()*scaleFactor - area.height()) * 0.5f *(1.0f / scaleFactor)) return; // if there is no selected window (that is no window at all) we cannot click it if (!selected_window) return; if (pos.x() < (area.width()*scaleFactor - selected_window->width()) * 0.5f *(1.0f / scaleFactor)) { float availableSize = (area.width() * scaleFactor - area.width()) * 0.5f * (1.0f / scaleFactor); for (int i = 0; i < leftWindows.count(); i++) { int windowPos = availableSize / leftWindows.count() * i; if (pos.x() < windowPos) continue; if (i + 1 < leftWindows.count()) { if (pos.x() > availableSize / leftWindows.count()*(i + 1)) continue; } effects->setTabBoxWindow(leftWindows[i]); return; } } if (pos.x() > area.width() - (area.width()*scaleFactor - selected_window->width()) * 0.5f *(1.0f / scaleFactor)) { float availableSize = (area.width() * scaleFactor - area.width()) * 0.5f * (1.0f / scaleFactor); for (int i = 0; i < rightWindows.count(); i++) { int windowPos = area.width() - availableSize / rightWindows.count() * i; if (pos.x() > windowPos) continue; if (i + 1 < rightWindows.count()) { if (pos.x() < area.width() - availableSize / rightWindows.count()*(i + 1)) continue; } effects->setTabBoxWindow(rightWindows[i]); return; } } break; } } void CoverSwitchEffect::abort() { // it's possible that abort is called after tabbox has been closed // in this case the cleanup is already done (see bug 207554) if (mActivated) { effects->unrefTabBox(); effects->stopMouseInterception(this); } - effects->setActiveFullScreenEffect(0); + effects->setActiveFullScreenEffect(nullptr); timeLine.reset(); mActivated = false; stop = false; stopRequested = false; effects->addRepaintFull(); captionFrame->free(); } void CoverSwitchEffect::slotWindowClosed(EffectWindow* c) { if (c == selected_window) - selected_window = 0; + selected_window = nullptr; // if the list is not empty, the effect is active if (!currentWindowList.isEmpty()) { c->refWindow(); referrencedWindows.append(c); currentWindowList.removeAll(c); leftWindows.removeAll(c); rightWindows.removeAll(c); } } bool CoverSwitchEffect::isActive() const { return (mActivated || stop || stopRequested) && !effects->isScreenLocked(); } void CoverSwitchEffect::updateCaption() { if (!selected_window || !windowTitle) { return; } if (selected_window->isDesktop()) { captionFrame->setText(i18nc("Special entry in alt+tab list for minimizing all windows", "Show Desktop")); static QPixmap pix = QIcon::fromTheme(QStringLiteral("user-desktop")).pixmap(captionFrame->iconSize()); captionFrame->setIcon(pix); } else { captionFrame->setText(selected_window->caption()); captionFrame->setIcon(selected_window->icon()); } } void CoverSwitchEffect::slotTabBoxKeyEvent(QKeyEvent *event) { if (event->type() == QEvent::KeyPress) { switch (event->key()) { case Qt::Key_Left: selectPreviousWindow(); break; case Qt::Key_Right: selectNextWindow(); break; default: // nothing break; } } } void CoverSwitchEffect::selectNextOrPreviousWindow(bool forward) { if (!mActivated || !selected_window) { return; } const int index = effects->currentTabBoxWindowList().indexOf(selected_window); int newIndex = index; if (forward) { ++newIndex; } else { --newIndex; } if (newIndex == effects->currentTabBoxWindowList().size()) { newIndex = 0; } else if (newIndex < 0) { newIndex = effects->currentTabBoxWindowList().size() -1; } if (index == newIndex) { return; } effects->setTabBoxWindow(effects->currentTabBoxWindowList().at(newIndex)); } } // namespace diff --git a/effects/coverswitch/coverswitch.h b/effects/coverswitch/coverswitch.h index 7b071b987..2629e90b2 100644 --- a/effects/coverswitch/coverswitch.h +++ b/effects/coverswitch/coverswitch.h @@ -1,166 +1,166 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2008 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_COVERSWITCH_H #define KWIN_COVERSWITCH_H #include #include #include #include #include #include #include #include namespace KWin { class CoverSwitchEffect : public Effect { Q_OBJECT Q_PROPERTY(int animationDuration READ configuredAnimationDuration) Q_PROPERTY(bool animateSwitch READ isAnimateSwitch) Q_PROPERTY(bool animateStart READ isAnimateStart) Q_PROPERTY(bool animateStop READ isAnimateStop) Q_PROPERTY(bool reflection READ isReflection) Q_PROPERTY(bool windowTitle READ isWindowTitle) Q_PROPERTY(qreal zPosition READ windowZPosition) Q_PROPERTY(bool primaryTabBox READ isPrimaryTabBox) Q_PROPERTY(bool secondaryTabBox READ isSecondaryTabBox) // TODO: mirror colors public: CoverSwitchEffect(); ~CoverSwitchEffect() override; void reconfigure(ReconfigureFlags) override; void prePaintScreen(ScreenPrePaintData &data, int time) override; void paintScreen(int mask, QRegion region, ScreenPaintData &data) override; void postPaintScreen() override; void paintWindow(EffectWindow *w, int mask, QRegion region, WindowPaintData &data) override; void windowInputMouseEvent(QEvent *e) override; bool isActive() const override; static bool supported(); // for properties int configuredAnimationDuration() const { return animationDuration.count(); } bool isAnimateSwitch() const { return animateSwitch; } bool isAnimateStart() const { return animateStart; } bool isAnimateStop() const { return animateStop; } bool isReflection() const { return reflection; } bool isWindowTitle() const { return windowTitle; } qreal windowZPosition() const { return zPosition; } bool isPrimaryTabBox() const { return primaryTabBox; } bool isSecondaryTabBox() const { return secondaryTabBox; } int requestedEffectChainPosition() const override { return 50; } public Q_SLOTS: void slotWindowClosed(KWin::EffectWindow *c); void slotTabBoxAdded(int mode); void slotTabBoxClosed(); void slotTabBoxUpdated(); void slotTabBoxKeyEvent(QKeyEvent* event); private: void paintScene(EffectWindow* frontWindow, const EffectWindowList& leftWindows, const EffectWindowList& rightWindows, bool reflectedWindows = false); void paintWindowCover(EffectWindow* w, bool reflectedWindow, WindowPaintData& data); void paintFrontWindow(EffectWindow* frontWindow, int width, int leftWindows, int rightWindows, bool reflectedWindow); - void paintWindows(const EffectWindowList& windows, bool left, bool reflectedWindows, EffectWindow* additionalWindow = NULL); + void paintWindows(const EffectWindowList& windows, bool left, bool reflectedWindows, EffectWindow* additionalWindow = nullptr); void selectNextOrPreviousWindow(bool forward); inline void selectNextWindow() { selectNextOrPreviousWindow(true); } inline void selectPreviousWindow() { selectNextOrPreviousWindow(false); } void abort(); /** * Updates the caption of the caption frame. * Taking care of rewording the desktop client. * As well sets the icon for the caption frame. */ void updateCaption(); bool mActivated; float angle; bool animateSwitch; bool animateStart; bool animateStop; bool animation; bool start; bool stop; bool reflection; float mirrorColor[2][4]; bool windowTitle; std::chrono::milliseconds animationDuration; bool stopRequested; bool startRequested; TimeLine timeLine; QRect area; float zPosition; float scaleFactor; enum Direction { Left, Right }; Direction direction; QQueue scheduled_directions; EffectWindow* selected_window; int activeScreen; QList< EffectWindow* > leftWindows; QList< EffectWindow* > rightWindows; EffectWindowList currentWindowList; EffectWindowList referrencedWindows; EffectFrame* captionFrame; QFont captionFont; bool primaryTabBox; bool secondaryTabBox; GLShader *m_reflectionShader; QMatrix4x4 m_projectionMatrix; QMatrix4x4 m_modelviewMatrix; }; } // namespace #endif diff --git a/effects/coverswitch/coverswitch_config.h b/effects/coverswitch/coverswitch_config.h index f6986a5db..c82b76b83 100644 --- a/effects/coverswitch/coverswitch_config.h +++ b/effects/coverswitch/coverswitch_config.h @@ -1,54 +1,54 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2008 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_COVERSWITCH_CONFIG_H #define KWIN_COVERSWITCH_CONFIG_H #include #include "ui_coverswitch_config.h" namespace KWin { class CoverSwitchEffectConfigForm : public QWidget, public Ui::CoverSwitchEffectConfigForm { Q_OBJECT public: explicit CoverSwitchEffectConfigForm(QWidget* parent); }; class CoverSwitchEffectConfig : public KCModule { Q_OBJECT public: - explicit CoverSwitchEffectConfig(QWidget* parent = 0, const QVariantList& args = QVariantList()); + explicit CoverSwitchEffectConfig(QWidget* parent = nullptr, const QVariantList& args = QVariantList()); public Q_SLOTS: void save() override; private: CoverSwitchEffectConfigForm* m_ui; }; } // namespace #endif diff --git a/effects/cube/cube.cpp b/effects/cube/cube.cpp index 6430ad9e2..f6d48c397 100644 --- a/effects/cube/cube.cpp +++ b/effects/cube/cube.cpp @@ -1,1750 +1,1750 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2008 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 "cube.h" // KConfigSkeleton #include "cubeconfig.h" #include "cube_inside.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace KWin { CubeEffect::CubeEffect() : activated(false) , cube_painting(false) , keyboard_grab(false) , painting_desktop(1) , frontDesktop(0) , cubeOpacity(1.0) , opacityDesktopOnly(true) , displayDesktopName(false) - , desktopNameFrame(NULL) + , desktopNameFrame(nullptr) , reflection(true) , desktopChangedWhileRotating(false) , paintCaps(true) - , wallpaper(NULL) + , wallpaper(nullptr) , texturedCaps(true) - , capTexture(NULL) + , capTexture(nullptr) , reflectionPainting(false) , activeScreen(0) , bottomCap(false) , closeOnMouseRelease(false) , zoom(0.0) , zPosition(0.0) , useForTabBox(false) , tabBoxMode(false) , shortcutsRegistered(false) , mode(Cube) , useShaders(false) - , cylinderShader(0) - , sphereShader(0) + , cylinderShader(nullptr) + , sphereShader(nullptr) , zOrderingFactor(0.0f) , mAddedHeightCoeff1(0.0f) , mAddedHeightCoeff2(0.0f) - , m_cubeCapBuffer(NULL) + , m_cubeCapBuffer(nullptr) , m_proxy(this) , m_cubeAction(new QAction(this)) , m_cylinderAction(new QAction(this)) , m_sphereAction(new QAction(this)) { initConfig(); desktopNameFont.setBold(true); desktopNameFont.setPointSize(14); if (effects->compositingType() == OpenGL2Compositing) { m_reflectionShader = ShaderManager::instance()->generateShaderFromResources(ShaderTrait::MapTexture, QString(), QStringLiteral("cube-reflection.glsl")); m_capShader = ShaderManager::instance()->generateShaderFromResources(ShaderTrait::MapTexture, QString(), QStringLiteral("cube-cap.glsl")); } else { - m_reflectionShader = NULL; - m_capShader = NULL; + m_reflectionShader = nullptr; + m_capShader = nullptr; } m_textureMirrorMatrix.scale(1.0, -1.0, 1.0); m_textureMirrorMatrix.translate(0.0, -1.0, 0.0); connect(effects, &EffectsHandler::tabBoxAdded, this, &CubeEffect::slotTabBoxAdded); connect(effects, &EffectsHandler::tabBoxClosed, this, &CubeEffect::slotTabBoxClosed); connect(effects, &EffectsHandler::tabBoxUpdated, this, &CubeEffect::slotTabBoxUpdated); connect(effects, &EffectsHandler::screenAboutToLock, this, [this]() { // Set active(false) does not release key grabs until the animation completes // As we know the lockscreen is trying to grab them, release them early // all other grabs are released in the normal way setActive(false); if (keyboard_grab) { effects->ungrabKeyboard(); keyboard_grab = false; } }); reconfigure(ReconfigureAll); } bool CubeEffect::supported() { return effects->isOpenGLCompositing(); } void CubeEffect::reconfigure(ReconfigureFlags) { CubeConfig::self()->read(); foreach (ElectricBorder border, borderActivate) { effects->unreserveElectricBorder(border, this); } foreach (ElectricBorder border, borderActivateCylinder) { effects->unreserveElectricBorder(border, this); } foreach (ElectricBorder border, borderActivateSphere) { effects->unreserveElectricBorder(border, this); } borderActivate.clear(); borderActivateCylinder.clear(); borderActivateSphere.clear(); QList borderList = QList(); borderList.append(int(ElectricNone)); borderList = CubeConfig::borderActivate(); foreach (int i, borderList) { borderActivate.append(ElectricBorder(i)); effects->reserveElectricBorder(ElectricBorder(i), this); } borderList.clear(); borderList.append(int(ElectricNone)); borderList = CubeConfig::borderActivateCylinder(); foreach (int i, borderList) { borderActivateCylinder.append(ElectricBorder(i)); effects->reserveElectricBorder(ElectricBorder(i), this); } borderList.clear(); borderList.append(int(ElectricNone)); borderList = CubeConfig::borderActivateSphere(); foreach (int i, borderList) { borderActivateSphere.append(ElectricBorder(i)); effects->reserveElectricBorder(ElectricBorder(i), this); } cubeOpacity = (float)CubeConfig::opacity() / 100.0f; opacityDesktopOnly = CubeConfig::opacityDesktopOnly(); displayDesktopName = CubeConfig::displayDesktopName(); reflection = CubeConfig::reflection(); // TODO: Rename rotationDuration to duration so we // can use animationTime(500). const int d = CubeConfig::rotationDuration() != 0 ? CubeConfig::rotationDuration() : 500; rotationDuration = std::chrono::milliseconds(static_cast(animationTime(d))); backgroundColor = CubeConfig::backgroundColor(); capColor = CubeConfig::capColor(); paintCaps = CubeConfig::caps(); closeOnMouseRelease = CubeConfig::closeOnMouseRelease(); zPosition = CubeConfig::zPosition(); useForTabBox = CubeConfig::tabBox(); invertKeys = CubeConfig::invertKeys(); invertMouse = CubeConfig::invertMouse(); capDeformationFactor = (float)CubeConfig::capDeformation() / 100.0f; useZOrdering = CubeConfig::zOrdering(); delete wallpaper; - wallpaper = NULL; + wallpaper = nullptr; delete capTexture; - capTexture = NULL; + capTexture = nullptr; texturedCaps = CubeConfig::texturedCaps(); timeLine.setEasingCurve(QEasingCurve::InOutSine); timeLine.setDuration(rotationDuration); verticalTimeLine.setEasingCurve(QEasingCurve::InOutSine); verticalTimeLine.setDuration(rotationDuration); // do not connect the shortcut if we use cylinder or sphere if (!shortcutsRegistered) { QAction* cubeAction = m_cubeAction; cubeAction->setObjectName(QStringLiteral("Cube")); cubeAction->setText(i18n("Desktop Cube")); KGlobalAccel::self()->setDefaultShortcut(cubeAction, QList() << Qt::CTRL + Qt::Key_F11); KGlobalAccel::self()->setShortcut(cubeAction, QList() << Qt::CTRL + Qt::Key_F11); effects->registerGlobalShortcut(Qt::CTRL + Qt::Key_F11, cubeAction); effects->registerPointerShortcut(Qt::ControlModifier | Qt::AltModifier, Qt::LeftButton, cubeAction); cubeShortcut = KGlobalAccel::self()->shortcut(cubeAction); QAction* cylinderAction = m_cylinderAction; cylinderAction->setObjectName(QStringLiteral("Cylinder")); cylinderAction->setText(i18n("Desktop Cylinder")); KGlobalAccel::self()->setShortcut(cylinderAction, QList()); effects->registerGlobalShortcut(QKeySequence(), cylinderAction); cylinderShortcut = KGlobalAccel::self()->shortcut(cylinderAction); QAction* sphereAction = m_sphereAction; sphereAction->setObjectName(QStringLiteral("Sphere")); sphereAction->setText(i18n("Desktop Sphere")); KGlobalAccel::self()->setShortcut(sphereAction, QList()); sphereShortcut = KGlobalAccel::self()->shortcut(sphereAction); effects->registerGlobalShortcut(QKeySequence(), sphereAction); connect(cubeAction, &QAction::triggered, this, &CubeEffect::toggleCube); connect(cylinderAction, &QAction::triggered, this, &CubeEffect::toggleCylinder); connect(sphereAction, &QAction::triggered, this, &CubeEffect::toggleSphere); connect(KGlobalAccel::self(), &KGlobalAccel::globalShortcutChanged, this, &CubeEffect::globalShortcutChanged); shortcutsRegistered = true; } // set the cap color on the shader if (m_capShader && m_capShader->isValid()) { ShaderBinder binder(m_capShader); m_capShader->setUniform(GLShader::Color, capColor); } // touch borders const QVector relevantBorders{ElectricLeft, ElectricTop, ElectricRight, ElectricBottom}; for (auto e : relevantBorders) { effects->unregisterTouchBorder(e, m_cubeAction); effects->unregisterTouchBorder(e, m_sphereAction); effects->unregisterTouchBorder(e, m_cylinderAction); } auto touchEdge = [&relevantBorders] (const QList touchBorders, QAction *action) { for (int i : touchBorders) { if (!relevantBorders.contains(ElectricBorder(i))) { continue; } effects->registerTouchBorder(ElectricBorder(i), action); } }; touchEdge(CubeConfig::touchBorderActivate(), m_cubeAction); touchEdge(CubeConfig::touchBorderActivateCylinder(), m_cylinderAction); touchEdge(CubeConfig::touchBorderActivateSphere(), m_sphereAction); } CubeEffect::~CubeEffect() { delete wallpaper; delete capTexture; delete cylinderShader; delete sphereShader; delete desktopNameFrame; delete m_reflectionShader; delete m_capShader; delete m_cubeCapBuffer; } QImage CubeEffect::loadCubeCap(const QString &capPath) { if (!texturedCaps) { return QImage(); } return QImage(capPath); } void CubeEffect::slotCubeCapLoaded() { QFutureWatcher *watcher = dynamic_cast*>(sender()); if (!watcher) { // not invoked from future watcher return; } QImage img = watcher->result(); if (!img.isNull()) { effects->makeOpenGLContextCurrent(); capTexture = new GLTexture(img); capTexture->setFilter(GL_LINEAR); if (!GLPlatform::instance()->isGLES()) { capTexture->setWrapMode(GL_CLAMP_TO_BORDER); } // need to recreate the VBO for the cube cap delete m_cubeCapBuffer; - m_cubeCapBuffer = NULL; + m_cubeCapBuffer = nullptr; effects->addRepaintFull(); } watcher->deleteLater(); } QImage CubeEffect::loadWallPaper(const QString &file) { return QImage(file); } void CubeEffect::slotWallPaperLoaded() { QFutureWatcher *watcher = dynamic_cast*>(sender()); if (!watcher) { // not invoked from future watcher return; } QImage img = watcher->result(); if (!img.isNull()) { effects->makeOpenGLContextCurrent(); wallpaper = new GLTexture(img); effects->addRepaintFull(); } watcher->deleteLater(); } bool CubeEffect::loadShader() { effects->makeOpenGLContextCurrent(); if (!(GLPlatform::instance()->supports(GLSL) && (effects->compositingType() == OpenGL2Compositing))) return false; cylinderShader = ShaderManager::instance()->generateShaderFromResources(ShaderTrait::MapTexture | ShaderTrait::AdjustSaturation | ShaderTrait::Modulate, QStringLiteral("cylinder.vert"), QString()); if (!cylinderShader->isValid()) { qCCritical(KWINEFFECTS) << "The cylinder shader failed to load!"; return false; } else { ShaderBinder binder(cylinderShader); cylinderShader->setUniform("sampler", 0); QRect rect = effects->clientArea(FullArea, activeScreen, effects->currentDesktop()); cylinderShader->setUniform("width", (float)rect.width() * 0.5f); } sphereShader = ShaderManager::instance()->generateShaderFromResources(ShaderTrait::MapTexture | ShaderTrait::AdjustSaturation | ShaderTrait::Modulate, QStringLiteral("sphere.vert"), QString()); if (!sphereShader->isValid()) { qCCritical(KWINEFFECTS) << "The sphere shader failed to load!"; return false; } else { ShaderBinder binder(sphereShader); sphereShader->setUniform("sampler", 0); QRect rect = effects->clientArea(FullArea, activeScreen, effects->currentDesktop()); sphereShader->setUniform("width", (float)rect.width() * 0.5f); sphereShader->setUniform("height", (float)rect.height() * 0.5f); sphereShader->setUniform("u_offset", QVector2D(0, 0)); } return true; } void CubeEffect::startAnimation(AnimationState state) { QEasingCurve curve; /* If this is first and only animation -> EaseInOut * there is more -> EaseIn * If there was an animation before, and this is the last one -> EaseOut * there is more -> Linear */ if (animationState == AnimationState::None) { curve.setType(animations.empty() ? QEasingCurve::InOutSine : QEasingCurve::InCurve); } else { curve.setType(animations.empty() ? QEasingCurve::OutCurve : QEasingCurve::Linear); } timeLine.reset(); timeLine.setEasingCurve(curve); startAngle = currentAngle; startFrontDesktop = frontDesktop; animationState = state; } void CubeEffect::startVerticalAnimation(VerticalAnimationState state) { /* Ignore if there is nowhere to rotate */ if ((qFuzzyIsNull(verticalCurrentAngle - 90.0f) && state == VerticalAnimationState::Upwards) || (qFuzzyIsNull(verticalCurrentAngle + 90.0f) && state == VerticalAnimationState::Downwards)) { return; } verticalTimeLine.reset(); verticalStartAngle = verticalCurrentAngle; verticalAnimationState = state; } void CubeEffect::prePaintScreen(ScreenPrePaintData& data, int time) { if (activated) { data.mask |= PAINT_SCREEN_TRANSFORMED | Effect::PAINT_SCREEN_WITH_TRANSFORMED_WINDOWS | PAINT_SCREEN_BACKGROUND_FIRST; if (animationState == AnimationState::None && !animations.empty()) { startAnimation(animations.dequeue()); } if (verticalAnimationState == VerticalAnimationState::None && !verticalAnimations.empty()) { startVerticalAnimation(verticalAnimations.dequeue()); } if (animationState != AnimationState::None || verticalAnimationState != VerticalAnimationState::None) { if (animationState != AnimationState::None) { timeLine.update(std::chrono::milliseconds(time)); } if (verticalAnimationState != VerticalAnimationState::None) { verticalTimeLine.update(std::chrono::milliseconds(time)); } rotateCube(); } } effects->prePaintScreen(data, time); } void CubeEffect::paintScreen(int mask, QRegion region, ScreenPaintData& data) { if (activated) { QRect rect = effects->clientArea(FullArea, activeScreen, effects->currentDesktop()); // background float clearColor[4]; glGetFloatv(GL_COLOR_CLEAR_VALUE, clearColor); glClearColor(backgroundColor.redF(), backgroundColor.greenF(), backgroundColor.blueF(), 1.0); glClear(GL_COLOR_BUFFER_BIT); glClearColor(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); // wallpaper if (wallpaper) { ShaderBinder binder(ShaderTrait::MapTexture); binder.shader()->setUniform(GLShader::ModelViewProjectionMatrix, data.projectionMatrix()); wallpaper->bind(); wallpaper->render(region, rect); wallpaper->unbind(); } glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); // some veriables needed for painting the caps float cubeAngle = (float)((float)(effects->numberOfDesktops() - 2) / (float)effects->numberOfDesktops() * 180.0f); float point = rect.width() / 2 * tan(cubeAngle * 0.5f * M_PI / 180.0f); float zTranslate = zPosition + zoom; if (animationState == AnimationState::Start) { zTranslate *= timeLine.value(); } else if (animationState == AnimationState::Stop) { zTranslate *= (1.0 - timeLine.value()); } // reflection if (reflection) { // we can use a huge scale factor (needed to calculate the rearground vertices) float scaleFactor = 1000000 * tan(60.0 * M_PI / 360.0f) / rect.height(); m_reflectionMatrix.setToIdentity(); m_reflectionMatrix.scale(1.0, -1.0, 1.0); double translate = 0.0; if (mode == Cube) { double addedHeight1 = -rect.height() * cos(verticalCurrentAngle*M_PI/180.0f) - rect.width() * sin(fabs(verticalCurrentAngle)*M_PI/180.0f)/tan(M_PI/effects->numberOfDesktops()); double addedHeight2 = -rect.width() * sin(fabs(verticalCurrentAngle)*M_PI/180.0f)*tan(M_PI*0.5f/effects->numberOfDesktops()); if (verticalCurrentAngle > 0.0f && effects->numberOfDesktops() & 1) translate = cos(fabs(currentAngle)*effects->numberOfDesktops()*M_PI/360.0f) * addedHeight2 + addedHeight1 - float(rect.height()); else translate = sin(fabs(currentAngle)*effects->numberOfDesktops()*M_PI/360.0f) * addedHeight2 + addedHeight1 - float(rect.height()); } else if (mode == Cylinder) { double addedHeight1 = -rect.height() * cos(verticalCurrentAngle*M_PI/180.0f) - rect.width() * sin(fabs(verticalCurrentAngle)*M_PI/180.0f)/tan(M_PI/effects->numberOfDesktops()); translate = addedHeight1 - float(rect.height()); } else { float radius = (rect.width() * 0.5) / cos(cubeAngle * 0.5 * M_PI / 180.0); translate = -rect.height()-2*radius; } m_reflectionMatrix.translate(0.0f, translate, 0.0f); reflectionPainting = true; glEnable(GL_CULL_FACE); paintCap(true, -point - zTranslate, data.projectionMatrix()); // cube glCullFace(GL_BACK); paintCube(mask, region, data); glCullFace(GL_FRONT); paintCube(mask, region, data); paintCap(false, -point - zTranslate, data.projectionMatrix()); glDisable(GL_CULL_FACE); reflectionPainting = false; const float width = rect.width(); const float height = rect.height(); float vertices[] = { -width * 0.5f, height, 0.0, width * 0.5f, height, 0.0, width * scaleFactor, height, -5000, -width * scaleFactor, height, -5000 }; // foreground float alpha = 0.7; if (animationState == AnimationState::Start) { alpha = 0.3 + 0.4 * timeLine.value(); } else if (animationState == AnimationState::Stop) { alpha = 0.3 + 0.4 * (1.0 - timeLine.value()); } glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); if (m_reflectionShader && m_reflectionShader->isValid()) { // ensure blending is enabled - no attribute stack ShaderBinder binder(m_reflectionShader); QMatrix4x4 windowTransformation = data.projectionMatrix(); windowTransformation.translate(rect.x() + rect.width() * 0.5f, 0.0, 0.0); m_reflectionShader->setUniform(GLShader::ModelViewProjectionMatrix, windowTransformation); m_reflectionShader->setUniform("u_alpha", alpha); QVector verts; QVector texcoords; verts.reserve(18); texcoords.reserve(12); texcoords << 0.0 << 0.0; verts << vertices[6] << vertices[7] << vertices[8]; texcoords << 0.0 << 0.0; verts << vertices[9] << vertices[10] << vertices[11]; texcoords << 1.0 << 0.0; verts << vertices[0] << vertices[1] << vertices[2]; texcoords << 1.0 << 0.0; verts << vertices[0] << vertices[1] << vertices[2]; texcoords << 1.0 << 0.0; verts << vertices[3] << vertices[4] << vertices[5]; texcoords << 0.0 << 0.0; verts << vertices[6] << vertices[7] << vertices[8]; GLVertexBuffer *vbo = GLVertexBuffer::streamingBuffer(); vbo->reset(); vbo->setData(6, 3, verts.data(), texcoords.data()); vbo->render(GL_TRIANGLES); } glDisable(GL_BLEND); } glEnable(GL_CULL_FACE); // caps paintCap(false, -point - zTranslate, data.projectionMatrix()); // cube glCullFace(GL_FRONT); paintCube(mask, region, data); glCullFace(GL_BACK); paintCube(mask, region, data); // cap paintCap(true, -point - zTranslate, data.projectionMatrix()); glDisable(GL_CULL_FACE); glDisable(GL_BLEND); // desktop name box - inspired from coverswitch if (displayDesktopName) { double opacity = 1.0; if (animationState == AnimationState::Start) { opacity = timeLine.value(); } else if (animationState == AnimationState::Stop) { opacity = 1.0 - timeLine.value(); } QRect screenRect = effects->clientArea(ScreenArea, activeScreen, frontDesktop); QRect frameRect = QRect(screenRect.width() * 0.33f + screenRect.x(), screenRect.height() * 0.95f + screenRect.y(), screenRect.width() * 0.34f, QFontMetrics(desktopNameFont).height()); if (!desktopNameFrame) { desktopNameFrame = effects->effectFrame(EffectFrameStyled); desktopNameFrame->setFont(desktopNameFont); } desktopNameFrame->setGeometry(frameRect); desktopNameFrame->setText(effects->desktopName(frontDesktop)); desktopNameFrame->render(region, opacity); } } else { effects->paintScreen(mask, region, data); } } void CubeEffect::rotateCube() { QRect rect = effects->clientArea(FullArea, activeScreen, effects->currentDesktop()); m_rotationMatrix.setToIdentity(); float internalCubeAngle = 360.0f / effects->numberOfDesktops(); float zTranslate = zPosition + zoom; float cubeAngle = (float)((float)(effects->numberOfDesktops() - 2) / (float)effects->numberOfDesktops() * 180.0f); float point = rect.width() / 2 * tan(cubeAngle * 0.5f * M_PI / 180.0f); /* Animations */ if (animationState == AnimationState::Start) { zTranslate *= timeLine.value(); } else if (animationState == AnimationState::Stop) { currentAngle = startAngle * (1.0 - timeLine.value()); zTranslate *= (1.0 - timeLine.value()); } else if (animationState != AnimationState::None) { /* Left or right */ float endAngle = animationState == AnimationState::Right ? internalCubeAngle : -internalCubeAngle; currentAngle = startAngle + timeLine.value() * (endAngle - startAngle); frontDesktop = startFrontDesktop; } /* Switching to next desktop: either by mouse or due to animation */ if (currentAngle > internalCubeAngle * 0.5f) { currentAngle -= internalCubeAngle; frontDesktop--; if (frontDesktop < 1) { frontDesktop = effects->numberOfDesktops(); } } if (currentAngle < -internalCubeAngle * 0.5f) { currentAngle += internalCubeAngle; frontDesktop++; if (frontDesktop > effects->numberOfDesktops()) { frontDesktop = 1; } } /* Vertical animations */ if (verticalAnimationState != VerticalAnimationState::None) { float verticalEndAngle = 0.0; if (verticalAnimationState == VerticalAnimationState::Upwards && verticalStartAngle >= 0.0) { verticalEndAngle = 90.0; } if (verticalAnimationState == VerticalAnimationState::Downwards && verticalStartAngle <= 0.0) { verticalEndAngle = -90.0; } // This also handles the "VerticalAnimationState::Stop" correctly, since it has endAngle = 0.0 verticalCurrentAngle = verticalStartAngle + verticalTimeLine.value() * (verticalEndAngle - verticalStartAngle); } /* Updating rotation matrix */ if (verticalAnimationState != VerticalAnimationState::None || verticalCurrentAngle != 0.0f) { m_rotationMatrix.translate(rect.width() / 2, rect.height() / 2, -point - zTranslate); m_rotationMatrix.rotate(verticalCurrentAngle, 1.0, 0.0, 0.0); m_rotationMatrix.translate(-rect.width() / 2, -rect.height() / 2, point + zTranslate); } if (animationState != AnimationState::None || currentAngle != 0.0f) { m_rotationMatrix.translate(rect.width() / 2, rect.height() / 2, -point - zTranslate); m_rotationMatrix.rotate(currentAngle, 0.0, 1.0, 0.0); m_rotationMatrix.translate(-rect.width() / 2, -rect.height() / 2, point + zTranslate); } } void CubeEffect::paintCube(int mask, QRegion region, ScreenPaintData& data) { QRect rect = effects->clientArea(FullArea, activeScreen, effects->currentDesktop()); float internalCubeAngle = 360.0f / effects->numberOfDesktops(); cube_painting = true; float zTranslate = zPosition + zoom; if (animationState == AnimationState::Start) { zTranslate *= timeLine.value(); } else if (animationState == AnimationState::Stop) { zTranslate *= (1.0 - timeLine.value()); } // Rotation of the cube float cubeAngle = (float)((float)(effects->numberOfDesktops() - 2) / (float)effects->numberOfDesktops() * 180.0f); float point = rect.width() / 2 * tan(cubeAngle * 0.5f * M_PI / 180.0f); for (int i = 0; i < effects->numberOfDesktops(); i++) { // start painting the cube painting_desktop = (i + frontDesktop) % effects->numberOfDesktops(); if (painting_desktop == 0) { painting_desktop = effects->numberOfDesktops(); } QMatrix4x4 matrix; matrix.translate(0, 0, -zTranslate); const QVector3D origin(rect.width() / 2, 0.0, -point); matrix.translate(origin); matrix.rotate(internalCubeAngle * i, 0, 1, 0); matrix.translate(-origin); m_currentFaceMatrix = matrix; effects->paintScreen(mask, region, data); } cube_painting = false; painting_desktop = effects->currentDesktop(); } void CubeEffect::paintCap(bool frontFirst, float zOffset, const QMatrix4x4 &projection) { if ((!paintCaps) || effects->numberOfDesktops() <= 2) return; GLenum firstCull = frontFirst ? GL_FRONT : GL_BACK; GLenum secondCull = frontFirst ? GL_BACK : GL_FRONT; const QRect rect = effects->clientArea(FullArea, activeScreen, effects->currentDesktop()); // create the VBO if not yet created if (!m_cubeCapBuffer) { switch(mode) { case Cube: paintCubeCap(); break; case Cylinder: paintCylinderCap(); break; case Sphere: paintSphereCap(); break; default: // impossible break; } } QMatrix4x4 capMvp; QMatrix4x4 capMatrix; capMatrix.translate(rect.width() / 2, 0.0, zOffset); capMatrix.rotate((1 - frontDesktop) * 360.0f / effects->numberOfDesktops(), 0.0, 1.0, 0.0); capMatrix.translate(0.0, rect.height(), 0.0); if (mode == Sphere) { capMatrix.scale(1.0, -1.0, 1.0); } bool capShader = false; if (effects->compositingType() == OpenGL2Compositing && m_capShader && m_capShader->isValid()) { capShader = true; ShaderManager::instance()->pushShader(m_capShader); float opacity = cubeOpacity; if (animationState == AnimationState::Start) { opacity *= timeLine.value(); } else if (animationState == AnimationState::Stop) { opacity *= (1.0 - timeLine.value()); } m_capShader->setUniform("u_opacity", opacity); m_capShader->setUniform("u_mirror", 1); if (reflectionPainting) { capMvp = projection * m_reflectionMatrix * m_rotationMatrix; } else { capMvp = projection * m_rotationMatrix; } m_capShader->setUniform(GLShader::ModelViewProjectionMatrix, capMvp * capMatrix); m_capShader->setUniform("u_untextured", texturedCaps ? 0 : 1); if (texturedCaps && effects->numberOfDesktops() > 3 && capTexture) { capTexture->bind(); } } glEnable(GL_BLEND); glCullFace(firstCull); m_cubeCapBuffer->render(GL_TRIANGLES); if (mode == Sphere) { capMatrix.scale(1.0, -1.0, 1.0); } capMatrix.translate(0.0, -rect.height(), 0.0); if (capShader) { m_capShader->setUniform(GLShader::ModelViewProjectionMatrix, capMvp * capMatrix); m_capShader->setUniform("u_mirror", 0); } glCullFace(secondCull); m_cubeCapBuffer->render(GL_TRIANGLES); glDisable(GL_BLEND); if (capShader) { ShaderManager::instance()->popShader(); if (texturedCaps && effects->numberOfDesktops() > 3 && capTexture) { capTexture->unbind(); } } } void CubeEffect::paintCubeCap() { QRect rect = effects->clientArea(FullArea, activeScreen, effects->currentDesktop()); float cubeAngle = (float)((float)(effects->numberOfDesktops() - 2) / (float)effects->numberOfDesktops() * 180.0f); float z = rect.width() / 2 * tan(cubeAngle * 0.5f * M_PI / 180.0f); float zTexture = rect.width() / 2 * tan(45.0f * M_PI / 180.0f); float angle = 360.0f / effects->numberOfDesktops(); bool texture = texturedCaps && effects->numberOfDesktops() > 3 && capTexture; QVector verts; QVector texCoords; for (int i = 0; i < effects->numberOfDesktops(); i++) { int triangleRows = effects->numberOfDesktops() * 5; float zTriangleDistance = z / (float)triangleRows; float widthTriangle = tan(angle * 0.5 * M_PI / 180.0) * zTriangleDistance; float currentWidth = 0.0; float cosValue = cos(i * angle * M_PI / 180.0); float sinValue = sin(i * angle * M_PI / 180.0); for (int j = 0; j < triangleRows; j++) { float previousWidth = currentWidth; currentWidth = tan(angle * 0.5 * M_PI / 180.0) * zTriangleDistance * (j + 1); int evenTriangles = 0; int oddTriangles = 0; for (int k = 0; k < floor(currentWidth / widthTriangle * 2 - 1 + 0.5f); k++) { float x1 = -previousWidth; float x2 = -currentWidth; float x3 = 0.0; float z1 = 0.0; float z2 = 0.0; float z3 = 0.0; if (k % 2 == 0) { x1 += evenTriangles * widthTriangle * 2; x2 += evenTriangles * widthTriangle * 2; x3 = x2 + widthTriangle * 2; z1 = j * zTriangleDistance; z2 = (j + 1) * zTriangleDistance; z3 = (j + 1) * zTriangleDistance; float xRot = cosValue * x1 - sinValue * z1; float zRot = sinValue * x1 + cosValue * z1; x1 = xRot; z1 = zRot; xRot = cosValue * x2 - sinValue * z2; zRot = sinValue * x2 + cosValue * z2; x2 = xRot; z2 = zRot; xRot = cosValue * x3 - sinValue * z3; zRot = sinValue * x3 + cosValue * z3; x3 = xRot; z3 = zRot; evenTriangles++; } else { x1 += oddTriangles * widthTriangle * 2; x2 += (oddTriangles + 1) * widthTriangle * 2; x3 = x1 + widthTriangle * 2; z1 = j * zTriangleDistance; z2 = (j + 1) * zTriangleDistance; z3 = j * zTriangleDistance; float xRot = cosValue * x1 - sinValue * z1; float zRot = sinValue * x1 + cosValue * z1; x1 = xRot; z1 = zRot; xRot = cosValue * x2 - sinValue * z2; zRot = sinValue * x2 + cosValue * z2; x2 = xRot; z2 = zRot; xRot = cosValue * x3 - sinValue * z3; zRot = sinValue * x3 + cosValue * z3; x3 = xRot; z3 = zRot; oddTriangles++; } float texX1 = 0.0; float texX2 = 0.0; float texX3 = 0.0; float texY1 = 0.0; float texY2 = 0.0; float texY3 = 0.0; if (texture) { if (capTexture->isYInverted()) { texX1 = x1 / (rect.width()) + 0.5; texY1 = 0.5 + z1 / zTexture * 0.5; texX2 = x2 / (rect.width()) + 0.5; texY2 = 0.5 + z2 / zTexture * 0.5; texX3 = x3 / (rect.width()) + 0.5; texY3 = 0.5 + z3 / zTexture * 0.5; texCoords << texX1 << texY1; } else { texX1 = x1 / (rect.width()) + 0.5; texY1 = 0.5 - z1 / zTexture * 0.5; texX2 = x2 / (rect.width()) + 0.5; texY2 = 0.5 - z2 / zTexture * 0.5; texX3 = x3 / (rect.width()) + 0.5; texY3 = 0.5 - z3 / zTexture * 0.5; texCoords << texX1 << texY1; } } verts << x1 << 0.0 << z1; if (texture) { texCoords << texX2 << texY2; } verts << x2 << 0.0 << z2; if (texture) { texCoords << texX3 << texY3; } verts << x3 << 0.0 << z3; } } } delete m_cubeCapBuffer; m_cubeCapBuffer = new GLVertexBuffer(GLVertexBuffer::Static); - m_cubeCapBuffer->setData(verts.count() / 3, 3, verts.constData(), texture ? texCoords.constData() : NULL); + m_cubeCapBuffer->setData(verts.count() / 3, 3, verts.constData(), texture ? texCoords.constData() : nullptr); } void CubeEffect::paintCylinderCap() { QRect rect = effects->clientArea(FullArea, activeScreen, effects->currentDesktop()); float cubeAngle = (float)((float)(effects->numberOfDesktops() - 2) / (float)effects->numberOfDesktops() * 180.0f); float radian = (cubeAngle * 0.5) * M_PI / 180; float radius = (rect.width() * 0.5) * tan(radian); float segment = radius / 30.0f; bool texture = texturedCaps && effects->numberOfDesktops() > 3 && capTexture; QVector verts; QVector texCoords; for (int i = 1; i <= 30; i++) { int steps = 72; for (int j = 0; j <= steps; j++) { const float azimuthAngle = (j * (360.0f / steps)) * M_PI / 180.0f; const float azimuthAngle2 = ((j + 1) * (360.0f / steps)) * M_PI / 180.0f; const float x1 = segment * (i - 1) * sin(azimuthAngle); const float x2 = segment * i * sin(azimuthAngle); const float x3 = segment * (i - 1) * sin(azimuthAngle2); const float x4 = segment * i * sin(azimuthAngle2); const float z1 = segment * (i - 1) * cos(azimuthAngle); const float z2 = segment * i * cos(azimuthAngle); const float z3 = segment * (i - 1) * cos(azimuthAngle2); const float z4 = segment * i * cos(azimuthAngle2); if (texture) { if (capTexture->isYInverted()) { texCoords << (radius + x1) / (radius * 2.0f) << (z1 + radius) / (radius * 2.0f); texCoords << (radius + x2) / (radius * 2.0f) << (z2 + radius) / (radius * 2.0f); texCoords << (radius + x3) / (radius * 2.0f) << (z3 + radius) / (radius * 2.0f); texCoords << (radius + x4) / (radius * 2.0f) << (z4 + radius) / (radius * 2.0f); texCoords << (radius + x3) / (radius * 2.0f) << (z3 + radius) / (radius * 2.0f); texCoords << (radius + x2) / (radius * 2.0f) << (z2 + radius) / (radius * 2.0f); } else { texCoords << (radius + x1) / (radius * 2.0f) << 1.0f - (z1 + radius) / (radius * 2.0f); texCoords << (radius + x2) / (radius * 2.0f) << 1.0f - (z2 + radius) / (radius * 2.0f); texCoords << (radius + x3) / (radius * 2.0f) << 1.0f - (z3 + radius) / (radius * 2.0f); texCoords << (radius + x4) / (radius * 2.0f) << 1.0f - (z4 + radius) / (radius * 2.0f); texCoords << (radius + x3) / (radius * 2.0f) << 1.0f - (z3 + radius) / (radius * 2.0f); texCoords << (radius + x2) / (radius * 2.0f) << 1.0f - (z2 + radius) / (radius * 2.0f); } } verts << x1 << 0.0 << z1; verts << x2 << 0.0 << z2; verts << x3 << 0.0 << z3; verts << x4 << 0.0 << z4; verts << x3 << 0.0 << z3; verts << x2 << 0.0 << z2; } } delete m_cubeCapBuffer; m_cubeCapBuffer = new GLVertexBuffer(GLVertexBuffer::Static); - m_cubeCapBuffer->setData(verts.count() / 3, 3, verts.constData(), texture ? texCoords.constData() : NULL); + m_cubeCapBuffer->setData(verts.count() / 3, 3, verts.constData(), texture ? texCoords.constData() : nullptr); } void CubeEffect::paintSphereCap() { QRect rect = effects->clientArea(FullArea, activeScreen, effects->currentDesktop()); float cubeAngle = (float)((float)(effects->numberOfDesktops() - 2) / (float)effects->numberOfDesktops() * 180.0f); float zTexture = rect.width() / 2 * tan(45.0f * M_PI / 180.0f); float radius = (rect.width() * 0.5) / cos(cubeAngle * 0.5 * M_PI / 180.0); float angle = acos((rect.height() * 0.5) / radius) * 180.0 / M_PI; angle /= 30; bool texture = texturedCaps && effects->numberOfDesktops() > 3 && capTexture; QVector verts; QVector texCoords; for (int i = 0; i < 30; i++) { float topAngle = angle * i * M_PI / 180.0; float bottomAngle = angle * (i + 1) * M_PI / 180.0; float yTop = (rect.height() * 0.5 - radius * cos(topAngle)); yTop *= (1.0f-capDeformationFactor); float yBottom = rect.height() * 0.5 - radius * cos(bottomAngle); yBottom *= (1.0f - capDeformationFactor); for (int j = 0; j < 36; j++) { const float x1 = radius * sin(topAngle) * sin((90.0 + j * 10.0) * M_PI / 180.0); const float z1 = radius * sin(topAngle) * cos((90.0 + j * 10.0) * M_PI / 180.0); const float x2 = radius * sin(bottomAngle) * sin((90.0 + j * 10.0) * M_PI / 180.00); const float z2 = radius * sin(bottomAngle) * cos((90.0 + j * 10.0) * M_PI / 180.0); const float x3 = radius * sin(bottomAngle) * sin((90.0 + (j + 1) * 10.0) * M_PI / 180.0); const float z3 = radius * sin(bottomAngle) * cos((90.0 + (j + 1) * 10.0) * M_PI / 180.0); const float x4 = radius * sin(topAngle) * sin((90.0 + (j + 1) * 10.0) * M_PI / 180.0); const float z4 = radius * sin(topAngle) * cos((90.0 + (j + 1) * 10.0) * M_PI / 180.0); if (texture) { if (capTexture->isYInverted()) { texCoords << x4 / (rect.width()) + 0.5 << 0.5 + z4 / zTexture * 0.5; texCoords << x1 / (rect.width()) + 0.5 << 0.5 + z1 / zTexture * 0.5; texCoords << x2 / (rect.width()) + 0.5 << 0.5 + z2 / zTexture * 0.5; texCoords << x2 / (rect.width()) + 0.5 << 0.5 + z2 / zTexture * 0.5; texCoords << x3 / (rect.width()) + 0.5 << 0.5 + z3 / zTexture * 0.5; texCoords << x4 / (rect.width()) + 0.5 << 0.5 + z4 / zTexture * 0.5; } else { texCoords << x4 / (rect.width()) + 0.5 << 0.5 - z4 / zTexture * 0.5; texCoords << x1 / (rect.width()) + 0.5 << 0.5 - z1 / zTexture * 0.5; texCoords << x2 / (rect.width()) + 0.5 << 0.5 - z2 / zTexture * 0.5; texCoords << x2 / (rect.width()) + 0.5 << 0.5 - z2 / zTexture * 0.5; texCoords << x3 / (rect.width()) + 0.5 << 0.5 - z3 / zTexture * 0.5; texCoords << x4 / (rect.width()) + 0.5 << 0.5 - z4 / zTexture * 0.5; } } verts << x4 << yTop << z4; verts << x1 << yTop << z1; verts << x2 << yBottom << z2; verts << x2 << yBottom << z2; verts << x3 << yBottom << z3; verts << x4 << yTop << z4; } } delete m_cubeCapBuffer; m_cubeCapBuffer = new GLVertexBuffer(GLVertexBuffer::Static); - m_cubeCapBuffer->setData(verts.count() / 3, 3, verts.constData(), texture ? texCoords.constData() : NULL); + m_cubeCapBuffer->setData(verts.count() / 3, 3, verts.constData(), texture ? texCoords.constData() : nullptr); } void CubeEffect::postPaintScreen() { effects->postPaintScreen(); if (!activated) return; bool animation = (animationState != AnimationState::None || verticalAnimationState != VerticalAnimationState::None); if (animationState != AnimationState::None && timeLine.done()) { /* An animation have just finished! */ if (animationState == AnimationState::Stop) { /* If the stop animation is finished, we're done */ if (keyboard_grab) effects->ungrabKeyboard(); keyboard_grab = false; effects->stopMouseInterception(this); effects->setCurrentDesktop(frontDesktop); - effects->setActiveFullScreenEffect(0); + effects->setActiveFullScreenEffect(nullptr); delete m_cubeCapBuffer; - m_cubeCapBuffer = NULL; + m_cubeCapBuffer = nullptr; if (desktopNameFrame) desktopNameFrame->free(); activated = false; // User can press Esc several times, and several Stop animations can be added to queue. We don't want it animationState = AnimationState::None; animations.clear(); verticalAnimationState = VerticalAnimationState::None; verticalAnimations.clear(); } else { if (!animations.empty()) startAnimation(animations.dequeue()); else animationState = AnimationState::None; } } /* Vertical animation have finished */ if (verticalAnimationState != VerticalAnimationState::None && verticalTimeLine.done()) { if (!verticalAnimations.empty()) { startVerticalAnimation(verticalAnimations.dequeue()); } else { verticalAnimationState = VerticalAnimationState::None; } } /* Repaint if there is any animation */ if (animation) { effects->addRepaintFull(); } } void CubeEffect::prePaintWindow(EffectWindow* w, WindowPrePaintData& data, int time) { if (activated) { if (cube_painting) { if (mode == Cylinder || mode == Sphere) { int leftDesktop = frontDesktop - 1; int rightDesktop = frontDesktop + 1; if (leftDesktop == 0) leftDesktop = effects->numberOfDesktops(); if (rightDesktop > effects->numberOfDesktops()) rightDesktop = 1; if (painting_desktop == frontDesktop) data.quads = data.quads.makeGrid(40); else if (painting_desktop == leftDesktop || painting_desktop == rightDesktop) data.quads = data.quads.makeGrid(100); else data.quads = data.quads.makeGrid(250); } if (w->isOnDesktop(painting_desktop)) { QRect rect = effects->clientArea(FullArea, activeScreen, painting_desktop); if (w->x() < rect.x()) { data.quads = data.quads.splitAtX(-w->x()); } if (w->x() + w->width() > rect.x() + rect.width()) { data.quads = data.quads.splitAtX(rect.width() - w->x()); } if (w->y() < rect.y()) { data.quads = data.quads.splitAtY(-w->y()); } if (w->y() + w->height() > rect.y() + rect.height()) { data.quads = data.quads.splitAtY(rect.height() - w->y()); } if (useZOrdering && !w->isDesktop() && !w->isDock() && !w->isOnAllDesktops()) data.setTransformed(); w->enablePainting(EffectWindow::PAINT_DISABLED_BY_DESKTOP); } else { // check for windows belonging to the previous desktop int prev_desktop = painting_desktop - 1; if (prev_desktop == 0) prev_desktop = effects->numberOfDesktops(); if (w->isOnDesktop(prev_desktop) && mode == Cube && !useZOrdering) { QRect rect = effects->clientArea(FullArea, activeScreen, prev_desktop); if (w->x() + w->width() > rect.x() + rect.width()) { w->enablePainting(EffectWindow::PAINT_DISABLED_BY_DESKTOP); data.quads = data.quads.splitAtX(rect.width() - w->x()); if (w->y() < rect.y()) { data.quads = data.quads.splitAtY(-w->y()); } if (w->y() + w->height() > rect.y() + rect.height()) { data.quads = data.quads.splitAtY(rect.height() - w->y()); } data.setTransformed(); effects->prePaintWindow(w, data, time); return; } } // check for windows belonging to the next desktop int next_desktop = painting_desktop + 1; if (next_desktop > effects->numberOfDesktops()) next_desktop = 1; if (w->isOnDesktop(next_desktop) && mode == Cube && !useZOrdering) { QRect rect = effects->clientArea(FullArea, activeScreen, next_desktop); if (w->x() < rect.x()) { w->enablePainting(EffectWindow::PAINT_DISABLED_BY_DESKTOP); data.quads = data.quads.splitAtX(-w->x()); if (w->y() < rect.y()) { data.quads = data.quads.splitAtY(-w->y()); } if (w->y() + w->height() > rect.y() + rect.height()) { data.quads = data.quads.splitAtY(rect.height() - w->y()); } data.setTransformed(); effects->prePaintWindow(w, data, time); return; } } w->disablePainting(EffectWindow::PAINT_DISABLED_BY_DESKTOP); } } } effects->prePaintWindow(w, data, time); } void CubeEffect::paintWindow(EffectWindow* w, int mask, QRegion region, WindowPaintData& data) { ShaderManager *shaderManager = ShaderManager::instance(); if (activated && cube_painting) { region= infiniteRegion(); // we need to explicitly prevent any clipping, bug #325432 //qCDebug(KWINEFFECTS) << w->caption(); float opacity = cubeOpacity; if (animationState == AnimationState::Start) { opacity = 1.0 - (1.0 - opacity) * timeLine.value(); if (reflectionPainting) opacity = 0.5 + (cubeOpacity - 0.5) * timeLine.value(); // fade in windows belonging to different desktops if (painting_desktop == effects->currentDesktop() && (!w->isOnDesktop(painting_desktop))) opacity = timeLine.value() * cubeOpacity; } else if (animationState == AnimationState::Stop) { opacity = 1.0 - (1.0 - opacity) * (1.0 - timeLine.value()); if (reflectionPainting) opacity = 0.5 + (cubeOpacity - 0.5) * (1.0 - timeLine.value()); // fade out windows belonging to different desktops if (painting_desktop == effects->currentDesktop() && (!w->isOnDesktop(painting_desktop))) opacity = cubeOpacity * (1.0 - timeLine.value()); } // z-Ordering if (!w->isDesktop() && !w->isDock() && useZOrdering && !w->isOnAllDesktops()) { float zOrdering = (effects->stackingOrder().indexOf(w) + 1) * zOrderingFactor; if (animationState == AnimationState::Start) { zOrdering *= timeLine.value(); } else if (animationState == AnimationState::Stop) { zOrdering *= (1.0 - timeLine.value()); } data.translate(0.0, 0.0, zOrdering); } // check for windows belonging to the previous desktop int prev_desktop = painting_desktop - 1; if (prev_desktop == 0) prev_desktop = effects->numberOfDesktops(); int next_desktop = painting_desktop + 1; if (next_desktop > effects->numberOfDesktops()) next_desktop = 1; if (w->isOnDesktop(prev_desktop) && (mask & PAINT_WINDOW_TRANSFORMED)) { QRect rect = effects->clientArea(FullArea, activeScreen, prev_desktop); WindowQuadList new_quads; foreach (const WindowQuad & quad, data.quads) { if (quad.right() > rect.width() - w->x()) { new_quads.append(quad); } } data.quads = new_quads; data.setXTranslation(-rect.width()); } if (w->isOnDesktop(next_desktop) && (mask & PAINT_WINDOW_TRANSFORMED)) { QRect rect = effects->clientArea(FullArea, activeScreen, next_desktop); WindowQuadList new_quads; foreach (const WindowQuad & quad, data.quads) { if (w->x() + quad.right() <= rect.x()) { new_quads.append(quad); } } data.quads = new_quads; data.setXTranslation(rect.width()); } QRect rect = effects->clientArea(FullArea, activeScreen, painting_desktop); if (animationState == AnimationState::Start || animationState == AnimationState::Stop) { // we have to change opacity values for fade in/out of windows which are shown on front-desktop if (prev_desktop == effects->currentDesktop() && w->x() < rect.x()) { if (animationState == AnimationState::Start) { opacity = timeLine.value() * cubeOpacity; } else if (animationState == AnimationState::Stop) { opacity = cubeOpacity * (1.0 - timeLine.value()); } } if (next_desktop == effects->currentDesktop() && w->x() + w->width() > rect.x() + rect.width()) { if (animationState == AnimationState::Start) { opacity = timeLine.value() * cubeOpacity; } else if (animationState == AnimationState::Stop) { opacity = cubeOpacity * (1.0 - timeLine.value()); } } } // HACK set opacity to 0.99 in case of fully opaque to ensure that windows are painted in correct sequence // bug #173214 if (opacity > 0.99f) opacity = 0.99f; if (opacityDesktopOnly && !w->isDesktop()) opacity = 0.99f; data.multiplyOpacity(opacity); if (w->isOnDesktop(painting_desktop) && w->x() < rect.x()) { WindowQuadList new_quads; foreach (const WindowQuad & quad, data.quads) { if (quad.right() > -w->x()) { new_quads.append(quad); } } data.quads = new_quads; } if (w->isOnDesktop(painting_desktop) && w->x() + w->width() > rect.x() + rect.width()) { WindowQuadList new_quads; foreach (const WindowQuad & quad, data.quads) { if (quad.right() <= rect.width() - w->x()) { new_quads.append(quad); } } data.quads = new_quads; } if (w->y() < rect.y()) { WindowQuadList new_quads; foreach (const WindowQuad & quad, data.quads) { if (quad.bottom() > -w->y()) { new_quads.append(quad); } } data.quads = new_quads; } if (w->y() + w->height() > rect.y() + rect.height()) { WindowQuadList new_quads; foreach (const WindowQuad & quad, data.quads) { if (quad.bottom() <= rect.height() - w->y()) { new_quads.append(quad); } } data.quads = new_quads; } GLShader *currentShader = nullptr; if (mode == Cylinder) { shaderManager->pushShader(cylinderShader); cylinderShader->setUniform("xCoord", (float)w->x()); cylinderShader->setUniform("cubeAngle", (effects->numberOfDesktops() - 2) / (float)effects->numberOfDesktops() * 90.0f); float factor = 0.0f; if (animationState == AnimationState::Start) { factor = 1.0f - timeLine.value(); } else if (animationState == AnimationState::Stop) { factor = timeLine.value(); } cylinderShader->setUniform("timeLine", factor); currentShader = cylinderShader; } if (mode == Sphere) { shaderManager->pushShader(sphereShader); sphereShader->setUniform("u_offset", QVector2D(w->x(), w->y())); sphereShader->setUniform("cubeAngle", (effects->numberOfDesktops() - 2) / (float)effects->numberOfDesktops() * 90.0f); float factor = 0.0f; if (animationState == AnimationState::Start) { factor = 1.0f - timeLine.value(); } else if (animationState == AnimationState::Stop) { factor = timeLine.value(); } sphereShader->setUniform("timeLine", factor); currentShader = sphereShader; } if (currentShader) { data.shader = currentShader; } data.setProjectionMatrix(data.screenProjectionMatrix()); if (reflectionPainting) { data.setModelViewMatrix(m_reflectionMatrix * m_rotationMatrix * m_currentFaceMatrix); } else { data.setModelViewMatrix(m_rotationMatrix * m_currentFaceMatrix); } } effects->paintWindow(w, mask, region, data); if (activated && cube_painting) { if (mode == Cylinder || mode == Sphere) { shaderManager->popShader(); } if (w->isDesktop() && effects->numScreens() > 1 && paintCaps) { QRect rect = effects->clientArea(FullArea, activeScreen, painting_desktop); QRegion paint = QRegion(rect); for (int i = 0; i < effects->numScreens(); i++) { if (i == w->screen()) continue; paint = paint.subtracted(QRegion(effects->clientArea(ScreenArea, i, painting_desktop))); } paint = paint.subtracted(QRegion(w->geometry())); // in case of free area in multiscreen setup fill it with cap color if (!paint.isEmpty()) { glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); QVector verts; float quadSize = 0.0f; int leftDesktop = frontDesktop - 1; int rightDesktop = frontDesktop + 1; if (leftDesktop == 0) leftDesktop = effects->numberOfDesktops(); if (rightDesktop > effects->numberOfDesktops()) rightDesktop = 1; if (painting_desktop == frontDesktop) quadSize = 100.0f; else if (painting_desktop == leftDesktop || painting_desktop == rightDesktop) quadSize = 150.0f; else quadSize = 250.0f; for (const QRect &paintRect : paint) { for (int i = 0; i <= (paintRect.height() / quadSize); i++) { for (int j = 0; j <= (paintRect.width() / quadSize); j++) { verts << qMin(paintRect.x() + (j + 1)*quadSize, (float)paintRect.x() + paintRect.width()) << paintRect.y() + i*quadSize; verts << paintRect.x() + j*quadSize << paintRect.y() + i*quadSize; verts << paintRect.x() + j*quadSize << qMin(paintRect.y() + (i + 1)*quadSize, (float)paintRect.y() + paintRect.height()); verts << paintRect.x() + j*quadSize << qMin(paintRect.y() + (i + 1)*quadSize, (float)paintRect.y() + paintRect.height()); verts << qMin(paintRect.x() + (j + 1)*quadSize, (float)paintRect.x() + paintRect.width()) << qMin(paintRect.y() + (i + 1)*quadSize, (float)paintRect.y() + paintRect.height()); verts << qMin(paintRect.x() + (j + 1)*quadSize, (float)paintRect.x() + paintRect.width()) << paintRect.y() + i*quadSize; } } } bool capShader = false; if (effects->compositingType() == OpenGL2Compositing && m_capShader && m_capShader->isValid()) { capShader = true; ShaderManager::instance()->pushShader(m_capShader); m_capShader->setUniform("u_mirror", 0); m_capShader->setUniform("u_untextured", 1); QMatrix4x4 mvp = data.screenProjectionMatrix(); if (reflectionPainting) { mvp = mvp * m_reflectionMatrix * m_rotationMatrix * m_currentFaceMatrix; } else { mvp = mvp * m_rotationMatrix * m_currentFaceMatrix; } m_capShader->setUniform(GLShader::ModelViewProjectionMatrix, mvp); } GLVertexBuffer *vbo = GLVertexBuffer::streamingBuffer(); vbo->reset(); QColor color = capColor; capColor.setAlphaF(cubeOpacity); vbo->setColor(color); - vbo->setData(verts.size() / 2, 2, verts.constData(), NULL); + vbo->setData(verts.size() / 2, 2, verts.constData(), nullptr); if (!capShader || mode == Cube) { // TODO: use sphere and cylinder shaders vbo->render(GL_TRIANGLES); } if (capShader) { ShaderManager::instance()->popShader(); } glDisable(GL_BLEND); } } } } bool CubeEffect::borderActivated(ElectricBorder border) { if (!borderActivate.contains(border) && !borderActivateCylinder.contains(border) && !borderActivateSphere.contains(border)) return false; if (effects->activeFullScreenEffect() && effects->activeFullScreenEffect() != this) return false; if (borderActivate.contains(border)) { if (!activated || (activated && mode == Cube)) toggleCube(); else return false; } if (borderActivateCylinder.contains(border)) { if (!activated || (activated && mode == Cylinder)) toggleCylinder(); else return false; } if (borderActivateSphere.contains(border)) { if (!activated || (activated && mode == Sphere)) toggleSphere(); else return false; } return true; } void CubeEffect::toggleCube() { qCDebug(KWINEFFECTS) << "toggle cube"; toggle(Cube); } void CubeEffect::toggleCylinder() { qCDebug(KWINEFFECTS) << "toggle cylinder"; if (!useShaders) useShaders = loadShader(); if (useShaders) toggle(Cylinder); } void CubeEffect::toggleSphere() { qCDebug(KWINEFFECTS) << "toggle sphere"; if (!useShaders) useShaders = loadShader(); if (useShaders) toggle(Sphere); } void CubeEffect::toggle(CubeMode newMode) { if ((effects->activeFullScreenEffect() && effects->activeFullScreenEffect() != this) || effects->numberOfDesktops() < 2) return; if (!activated) { mode = newMode; setActive(true); } else { setActive(false); } } void CubeEffect::grabbedKeyboardEvent(QKeyEvent* e) { // If either stop is running or is scheduled - ignore all events if ((!animations.isEmpty() && animations.last() == AnimationState::Stop) || animationState == AnimationState::Stop) { return; } // taken from desktopgrid.cpp if (e->type() == QEvent::KeyPress) { // check for global shortcuts // HACK: keyboard grab disables the global shortcuts so we have to check for global shortcut (bug 156155) if (mode == Cube && cubeShortcut.contains(e->key() + e->modifiers())) { toggleCube(); return; } if (mode == Cylinder && cylinderShortcut.contains(e->key() + e->modifiers())) { toggleCylinder(); return; } if (mode == Sphere && sphereShortcut.contains(e->key() + e->modifiers())) { toggleSphere(); return; } int desktop = -1; // switch by F or just if (e->key() >= Qt::Key_F1 && e->key() <= Qt::Key_F35) desktop = e->key() - Qt::Key_F1 + 1; else if (e->key() >= Qt::Key_0 && e->key() <= Qt::Key_9) desktop = e->key() == Qt::Key_0 ? 10 : e->key() - Qt::Key_0; if (desktop != -1) { if (desktop <= effects->numberOfDesktops()) { // we have to rotate to chosen desktop // and end effect when rotation finished rotateToDesktop(desktop); setActive(false); } return; } int key = e->key(); if (invertKeys) { if (key == Qt::Key_Left) key = Qt::Key_Right; else if (key == Qt::Key_Right) key = Qt::Key_Left; else if (key == Qt::Key_Up) key = Qt::Key_Down; else if (key == Qt::Key_Down) key = Qt::Key_Up; } switch(key) { // wrap only on autorepeat case Qt::Key_Left: qCDebug(KWINEFFECTS) << "left"; if (animations.count() < effects->numberOfDesktops()) animations.enqueue(AnimationState::Left); break; case Qt::Key_Right: qCDebug(KWINEFFECTS) << "right"; if (animations.count() < effects->numberOfDesktops()) animations.enqueue(AnimationState::Right); break; case Qt::Key_Up: qCDebug(KWINEFFECTS) << "up"; verticalAnimations.enqueue(VerticalAnimationState::Upwards); break; case Qt::Key_Down: qCDebug(KWINEFFECTS) << "down"; verticalAnimations.enqueue(VerticalAnimationState::Downwards); break; case Qt::Key_Escape: rotateToDesktop(effects->currentDesktop()); setActive(false); return; case Qt::Key_Enter: case Qt::Key_Return: case Qt::Key_Space: setActive(false); return; case Qt::Key_Plus: case Qt::Key_Equal: zoom -= 10.0; zoom = qMax(-zPosition, zoom); rotateCube(); break; case Qt::Key_Minus: zoom += 10.0f; rotateCube(); break; default: break; } } effects->addRepaintFull(); } void CubeEffect::rotateToDesktop(int desktop) { // all scheduled animations will be removed as a speed up animations.clear(); verticalAnimations.clear(); // we want only startAnimation to finish gracefully // all the others can be interrupted if (animationState != AnimationState::Start) { animationState = AnimationState::None; } verticalAnimationState = VerticalAnimationState::None; // find the fastest rotation path from frontDesktop to desktop int rightRotations = frontDesktop - desktop; if (rightRotations < 0) { rightRotations += effects->numberOfDesktops(); } int leftRotations = desktop - frontDesktop; if (leftRotations < 0) { leftRotations += effects->numberOfDesktops(); } if (leftRotations <= rightRotations) { for (int i = 0; i < leftRotations; i++) { animations.enqueue(AnimationState::Left); } } else { for (int i = 0; i < rightRotations; i++) { animations.enqueue(AnimationState::Right); } } // we want the face of desktop to appear, it might need also vertical animation if (verticalCurrentAngle > 0.0f) { verticalAnimations.enqueue(VerticalAnimationState::Downwards); } if (verticalCurrentAngle < 0.0f) { verticalAnimations.enqueue(VerticalAnimationState::Upwards); } /* Start immediately, so there is no pause: * during that pause, actual frontDesktop might change * if user moves his mouse fast, leading to incorrect desktop */ if (animationState == AnimationState::None && !animations.empty()) { startAnimation(animations.dequeue()); } if (verticalAnimationState == VerticalAnimationState::None && !verticalAnimations.empty()) { startVerticalAnimation(verticalAnimations.dequeue()); } } void CubeEffect::setActive(bool active) { foreach (CubeInsideEffect * inside, m_cubeInsideEffects) { inside->setActive(true); } if (active) { QString capPath = CubeConfig::capPath(); if (texturedCaps && !capTexture && !capPath.isEmpty()) { QFutureWatcher *watcher = new QFutureWatcher(this); connect(watcher, &QFutureWatcher::finished, this, &CubeEffect::slotCubeCapLoaded); watcher->setFuture(QtConcurrent::run(this, &CubeEffect::loadCubeCap, capPath)); } QString wallpaperPath = CubeConfig::wallpaper().toLocalFile(); if (!wallpaper && !wallpaperPath.isEmpty()) { QFutureWatcher *watcher = new QFutureWatcher(this); connect(watcher, &QFutureWatcher::finished, this, &CubeEffect::slotWallPaperLoaded); watcher->setFuture(QtConcurrent::run(this, &CubeEffect::loadWallPaper, wallpaperPath)); } activated = true; activeScreen = effects->activeScreen(); keyboard_grab = effects->grabKeyboard(this); effects->startMouseInterception(this, Qt::OpenHandCursor); frontDesktop = effects->currentDesktop(); zoom = 0.0; zOrderingFactor = zPosition / (effects->stackingOrder().count() - 1); animations.enqueue(AnimationState::Start); animationState = AnimationState::None; verticalAnimationState = VerticalAnimationState::None; effects->setActiveFullScreenEffect(this); qCDebug(KWINEFFECTS) << "Cube is activated"; currentAngle = 0.0; verticalCurrentAngle = 0.0; if (reflection) { QRect rect = effects->clientArea(FullArea, activeScreen, effects->currentDesktop()); float temporaryCoeff = float(rect.width()) / tan(M_PI / float(effects->numberOfDesktops())); mAddedHeightCoeff1 = sqrt(float(rect.height()) * float(rect.height()) + temporaryCoeff * temporaryCoeff); mAddedHeightCoeff2 = sqrt(float(rect.height()) * float(rect.height()) + float(rect.width()) * float(rect.width()) + temporaryCoeff * temporaryCoeff); } m_rotationMatrix.setToIdentity(); } else { animations.enqueue(AnimationState::Stop); } effects->addRepaintFull(); } void CubeEffect::windowInputMouseEvent(QEvent* e) { if (!activated) return; if (tabBoxMode) return; if ((!animations.isEmpty() && animations.last() == AnimationState::Stop) || animationState == AnimationState::Stop) return; QMouseEvent *mouse = dynamic_cast< QMouseEvent* >(e); if (!mouse) return; static QPoint oldpos; static QElapsedTimer dblClckTime; static int dblClckCounter(0); if (mouse->type() == QEvent::MouseMove && mouse->buttons().testFlag(Qt::LeftButton)) { const QPoint pos = mouse->pos(); QRect rect = effects->clientArea(FullArea, activeScreen, effects->currentDesktop()); bool repaint = false; // vertical movement only if there is not a rotation if (verticalAnimationState == VerticalAnimationState::None) { // display height corresponds to 180* int deltaY = pos.y() - oldpos.y(); float deltaVerticalDegrees = (float)deltaY / rect.height() * 180.0f; if (invertMouse) verticalCurrentAngle += deltaVerticalDegrees; else verticalCurrentAngle -= deltaVerticalDegrees; // don't get too excited verticalCurrentAngle = qBound(-90.0f, verticalCurrentAngle, 90.0f); if (deltaVerticalDegrees != 0.0) repaint = true; } // horizontal movement only if there is not a rotation if (animationState == AnimationState::None) { // display width corresponds to sum of angles of the polyhedron int deltaX = oldpos.x() - pos.x(); float deltaDegrees = (float)deltaX / rect.width() * 360.0f; if (deltaX == 0) { if (pos.x() == 0) deltaDegrees = 5.0f; if (pos.x() == rect.width() - 1) deltaDegrees = -5.0f; } if (invertMouse) currentAngle += deltaDegrees; else currentAngle -= deltaDegrees; if (deltaDegrees != 0.0) repaint = true; } if (repaint) { rotateCube(); effects->addRepaintFull(); } oldpos = pos; } else if (mouse->type() == QEvent::MouseButtonPress && mouse->button() == Qt::LeftButton) { oldpos = mouse->pos(); if (dblClckTime.elapsed() > QApplication::doubleClickInterval()) dblClckCounter = 0; if (!dblClckCounter) dblClckTime.start(); } else if (mouse->type() == QEvent::MouseButtonRelease) { effects->defineCursor(Qt::OpenHandCursor); if (mouse->button() == Qt::LeftButton && ++dblClckCounter == 2) { dblClckCounter = 0; if (dblClckTime.elapsed() < QApplication::doubleClickInterval()) { setActive(false); return; } } else if (mouse->button() == Qt::XButton1) { if (animations.count() < effects->numberOfDesktops()) { if (invertMouse) animations.enqueue(AnimationState::Right); else animations.enqueue(AnimationState::Left); } effects->addRepaintFull(); } else if (mouse->button() == Qt::XButton2) { if (animations.count() < effects->numberOfDesktops()) { if (invertMouse) animations.enqueue(AnimationState::Left); else animations.enqueue(AnimationState::Right); } effects->addRepaintFull(); } else if (mouse->button() == Qt::RightButton || (mouse->button() == Qt::LeftButton && closeOnMouseRelease)) { setActive(false); } } } void CubeEffect::slotTabBoxAdded(int mode) { if (activated) return; if (effects->activeFullScreenEffect() && effects->activeFullScreenEffect() != this) return; if (useForTabBox && mode == TabBoxDesktopListMode) { effects->refTabBox(); tabBoxMode = true; setActive(true); rotateToDesktop(effects->currentTabBoxDesktop()); } } void CubeEffect::slotTabBoxUpdated() { if (activated) { rotateToDesktop(effects->currentTabBoxDesktop()); effects->addRepaintFull(); } } void CubeEffect::slotTabBoxClosed() { if (activated) { effects->unrefTabBox(); tabBoxMode = false; setActive(false); } } void CubeEffect::globalShortcutChanged(QAction *action, const QKeySequence &seq) { if (action->objectName() == QStringLiteral("Cube")) { cubeShortcut.clear(); cubeShortcut.append(seq); } else if (action->objectName() == QStringLiteral("Cylinder")) { cylinderShortcut.clear(); cylinderShortcut.append(seq); } else if (action->objectName() == QStringLiteral("Sphere")) { sphereShortcut.clear(); sphereShortcut.append(seq); } } void* CubeEffect::proxy() { return &m_proxy; } void CubeEffect::registerCubeInsideEffect(CubeInsideEffect* effect) { m_cubeInsideEffects.append(effect); } void CubeEffect::unregisterCubeInsideEffect(CubeInsideEffect* effect) { m_cubeInsideEffects.removeAll(effect); } bool CubeEffect::isActive() const { return activated && !effects->isScreenLocked(); } } // namespace diff --git a/effects/cube/cube_config.h b/effects/cube/cube_config.h index 1cb151ab4..e3964e4f9 100644 --- a/effects/cube/cube_config.h +++ b/effects/cube/cube_config.h @@ -1,57 +1,57 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2008 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_CUBE_CONFIG_H #define KWIN_CUBE_CONFIG_H #include #include "ui_cube_config.h" namespace KWin { class CubeEffectConfigForm : public QWidget, public Ui::CubeEffectConfigForm { Q_OBJECT public: explicit CubeEffectConfigForm(QWidget* parent); }; class CubeEffectConfig : public KCModule { Q_OBJECT public: - explicit CubeEffectConfig(QWidget* parent = 0, const QVariantList& args = QVariantList()); + explicit CubeEffectConfig(QWidget* parent = nullptr, const QVariantList& args = QVariantList()); public Q_SLOTS: void save() override; private Q_SLOTS: void capsSelectionChanged(); private: CubeEffectConfigForm* m_ui; KActionCollection* m_actionCollection; }; } // namespace #endif diff --git a/effects/cubeslide/cubeslide.cpp b/effects/cubeslide/cubeslide.cpp index d3e86eb19..79c75b0a2 100644 --- a/effects/cubeslide/cubeslide.cpp +++ b/effects/cubeslide/cubeslide.cpp @@ -1,671 +1,671 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. 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 . *********************************************************************/ #include "cubeslide.h" // KConfigSkeleton #include "cubeslideconfig.h" #include #include #include #include namespace KWin { CubeSlideEffect::CubeSlideEffect() : stickyPainting(false) , windowMoving(false) , desktopChangedWhileMoving(false) , progressRestriction(0.0f) { initConfig(); connect(effects, &EffectsHandler::windowAdded, this, &CubeSlideEffect::slotWindowAdded); connect(effects, &EffectsHandler::windowDeleted, this, &CubeSlideEffect::slotWindowDeleted); connect(effects, QOverload::of(&EffectsHandler::desktopChanged), this, &CubeSlideEffect::slotDesktopChanged); connect(effects, &EffectsHandler::windowStepUserMovedResized, this, &CubeSlideEffect::slotWindowStepUserMovedResized); connect(effects, &EffectsHandler::windowFinishUserMovedResized, this, &CubeSlideEffect::slotWindowFinishUserMovedResized); connect(effects, &EffectsHandler::numberDesktopsChanged, this, &CubeSlideEffect::slotNumberDesktopsChanged); reconfigure(ReconfigureAll); } CubeSlideEffect::~CubeSlideEffect() { } bool CubeSlideEffect::supported() { return effects->isOpenGLCompositing() && effects->animationsSupported(); } void CubeSlideEffect::reconfigure(ReconfigureFlags) { CubeSlideConfig::self()->read(); // TODO: rename rotationDuration to duration rotationDuration = animationTime(CubeSlideConfig::rotationDuration() != 0 ? CubeSlideConfig::rotationDuration() : 500); timeLine.setCurveShape(QTimeLine::EaseInOutCurve); timeLine.setDuration(rotationDuration); dontSlidePanels = CubeSlideConfig::dontSlidePanels(); dontSlideStickyWindows = CubeSlideConfig::dontSlideStickyWindows(); usePagerLayout = CubeSlideConfig::usePagerLayout(); useWindowMoving = CubeSlideConfig::useWindowMoving(); } void CubeSlideEffect::prePaintScreen(ScreenPrePaintData& data, int time) { if (isActive()) { data.mask |= PAINT_SCREEN_TRANSFORMED | PAINT_SCREEN_WITH_TRANSFORMED_WINDOWS | PAINT_SCREEN_BACKGROUND_FIRST; timeLine.setCurrentTime(timeLine.currentTime() + time); if (windowMoving && timeLine.currentTime() > progressRestriction * (qreal)timeLine.duration()) timeLine.setCurrentTime(progressRestriction * (qreal)timeLine.duration()); } effects->prePaintScreen(data, time); } void CubeSlideEffect::paintScreen(int mask, QRegion region, ScreenPaintData& data) { if (isActive()) { glEnable(GL_CULL_FACE); glCullFace(GL_FRONT); paintSlideCube(mask, region, data); glCullFace(GL_BACK); paintSlideCube(mask, region, data); glDisable(GL_CULL_FACE); // Paint an extra screen with 'sticky' windows. if (!staticWindows.isEmpty()) { stickyPainting = true; effects->paintScreen(mask, region, data); stickyPainting = false; } } else effects->paintScreen(mask, region, data); } void CubeSlideEffect::paintSlideCube(int mask, QRegion region, ScreenPaintData& data) { // slide cube only paints to desktops at a time // first the horizontal rotations followed by vertical rotations QRect rect = effects->clientArea(FullArea, effects->activeScreen(), effects->currentDesktop()); float point = rect.width() / 2 * tan(45.0f * M_PI / 180.0f); cube_painting = true; painting_desktop = front_desktop; ScreenPaintData firstFaceData = data; ScreenPaintData secondFaceData = data; RotationDirection direction = slideRotations.head(); int secondDesktop; switch(direction) { case Left: firstFaceData.setRotationAxis(Qt::YAxis); secondFaceData.setRotationAxis(Qt::YAxis); if (usePagerLayout) secondDesktop = effects->desktopToLeft(front_desktop, true); else { secondDesktop = front_desktop - 1; if (secondDesktop == 0) secondDesktop = effects->numberOfDesktops(); } firstFaceData.setRotationAngle(90.0f * timeLine.currentValue()); secondFaceData.setRotationAngle(-90.0f * (1.0f - timeLine.currentValue())); break; case Right: firstFaceData.setRotationAxis(Qt::YAxis); secondFaceData.setRotationAxis(Qt::YAxis); if (usePagerLayout) secondDesktop = effects->desktopToRight(front_desktop, true); else { secondDesktop = front_desktop + 1; if (secondDesktop > effects->numberOfDesktops()) secondDesktop = 1; } firstFaceData.setRotationAngle(-90.0f * timeLine.currentValue()); secondFaceData.setRotationAngle(90.0f * (1.0f - timeLine.currentValue())); break; case Upwards: firstFaceData.setRotationAxis(Qt::XAxis); secondFaceData.setRotationAxis(Qt::XAxis); secondDesktop = effects->desktopAbove(front_desktop, true); firstFaceData.setRotationAngle(-90.0f * timeLine.currentValue()); secondFaceData.setRotationAngle(90.0f * (1.0f - timeLine.currentValue())); point = rect.height() / 2 * tan(45.0f * M_PI / 180.0f); break; case Downwards: firstFaceData.setRotationAxis(Qt::XAxis); secondFaceData.setRotationAxis(Qt::XAxis); secondDesktop = effects->desktopBelow(front_desktop, true); firstFaceData.setRotationAngle(90.0f * timeLine.currentValue()); secondFaceData.setRotationAngle(-90.0f * (1.0f - timeLine.currentValue())); point = rect.height() / 2 * tan(45.0f * M_PI / 180.0f); break; default: // totally impossible return; } // front desktop firstFaceData.setRotationOrigin(QVector3D(rect.width() / 2, rect.height() / 2, -point)); other_desktop = secondDesktop; firstDesktop = true; effects->paintScreen(mask, region, firstFaceData); // second desktop other_desktop = painting_desktop; painting_desktop = secondDesktop; firstDesktop = false; secondFaceData.setRotationOrigin(QVector3D(rect.width() / 2, rect.height() / 2, -point)); effects->paintScreen(mask, region, secondFaceData); cube_painting = false; painting_desktop = effects->currentDesktop(); } void CubeSlideEffect::prePaintWindow(EffectWindow* w, WindowPrePaintData& data, int time) { if (stickyPainting) { if (staticWindows.contains(w)) { w->enablePainting(EffectWindow::PAINT_DISABLED_BY_DESKTOP); } else { w->disablePainting(EffectWindow::PAINT_DISABLED_BY_DESKTOP); } } else if (isActive() && cube_painting) { if (staticWindows.contains(w)) { w->disablePainting(EffectWindow::PAINT_DISABLED_BY_DESKTOP); effects->prePaintWindow(w, data, time); return; } QRect rect = effects->clientArea(FullArea, effects->activeScreen(), painting_desktop); if (w->isOnDesktop(painting_desktop)) { if (w->x() < rect.x()) { data.quads = data.quads.splitAtX(-w->x()); } if (w->x() + w->width() > rect.x() + rect.width()) { data.quads = data.quads.splitAtX(rect.width() - w->x()); } if (w->y() < rect.y()) { data.quads = data.quads.splitAtY(-w->y()); } if (w->y() + w->height() > rect.y() + rect.height()) { data.quads = data.quads.splitAtY(rect.height() - w->y()); } w->enablePainting(EffectWindow::PAINT_DISABLED_BY_DESKTOP); } else if (w->isOnDesktop(other_desktop)) { RotationDirection direction = slideRotations.head(); bool enable = false; if (w->x() < rect.x() && (direction == Left || direction == Right)) { data.quads = data.quads.splitAtX(-w->x()); enable = true; } if (w->x() + w->width() > rect.x() + rect.width() && (direction == Left || direction == Right)) { data.quads = data.quads.splitAtX(rect.width() - w->x()); enable = true; } if (w->y() < rect.y() && (direction == Upwards || direction == Downwards)) { data.quads = data.quads.splitAtY(-w->y()); enable = true; } if (w->y() + w->height() > rect.y() + rect.height() && (direction == Upwards || direction == Downwards)) { data.quads = data.quads.splitAtY(rect.height() - w->y()); enable = true; } if (enable) { data.setTransformed(); data.setTranslucent(); w->enablePainting(EffectWindow::PAINT_DISABLED_BY_DESKTOP); } else w->disablePainting(EffectWindow::PAINT_DISABLED_BY_DESKTOP); } else w->disablePainting(EffectWindow::PAINT_DISABLED_BY_DESKTOP); } effects->prePaintWindow(w, data, time); } void CubeSlideEffect::paintWindow(EffectWindow* w, int mask, QRegion region, WindowPaintData& data) { if (isActive() && cube_painting && !staticWindows.contains(w)) { // filter out quads overlapping the edges QRect rect = effects->clientArea(FullArea, effects->activeScreen(), painting_desktop); if (w->isOnDesktop(painting_desktop)) { if (w->x() < rect.x()) { WindowQuadList new_quads; foreach (const WindowQuad & quad, data.quads) { if (quad.right() > -w->x()) { new_quads.append(quad); } } data.quads = new_quads; } if (w->x() + w->width() > rect.x() + rect.width()) { WindowQuadList new_quads; foreach (const WindowQuad & quad, data.quads) { if (quad.right() <= rect.width() - w->x()) { new_quads.append(quad); } } data.quads = new_quads; } if (w->y() < rect.y()) { WindowQuadList new_quads; foreach (const WindowQuad & quad, data.quads) { if (quad.bottom() > -w->y()) { new_quads.append(quad); } } data.quads = new_quads; } if (w->y() + w->height() > rect.y() + rect.height()) { WindowQuadList new_quads; foreach (const WindowQuad & quad, data.quads) { if (quad.bottom() <= rect.height() - w->y()) { new_quads.append(quad); } } data.quads = new_quads; } } // paint windows overlapping edges from other desktop if (w->isOnDesktop(other_desktop) && (mask & PAINT_WINDOW_TRANSFORMED)) { RotationDirection direction = slideRotations.head(); if (w->x() < rect.x() && (direction == Left || direction == Right)) { WindowQuadList new_quads; data.setXTranslation(rect.width()); foreach (const WindowQuad & quad, data.quads) { if (quad.right() <= -w->x()) { new_quads.append(quad); } } data.quads = new_quads; } if (w->x() + w->width() > rect.x() + rect.width() && (direction == Left || direction == Right)) { WindowQuadList new_quads; data.setXTranslation(-rect.width()); foreach (const WindowQuad & quad, data.quads) { if (quad.right() > rect.width() - w->x()) { new_quads.append(quad); } } data.quads = new_quads; } if (w->y() < rect.y() && (direction == Upwards || direction == Downwards)) { WindowQuadList new_quads; data.setYTranslation(rect.height()); foreach (const WindowQuad & quad, data.quads) { if (quad.bottom() <= -w->y()) { new_quads.append(quad); } } data.quads = new_quads; } if (w->y() + w->height() > rect.y() + rect.height() && (direction == Upwards || direction == Downwards)) { WindowQuadList new_quads; data.setYTranslation(-rect.height()); foreach (const WindowQuad & quad, data.quads) { if (quad.bottom() > rect.height() - w->y()) { new_quads.append(quad); } } data.quads = new_quads; } if (firstDesktop) data.multiplyOpacity(timeLine.currentValue()); else data.multiplyOpacity((1.0 - timeLine.currentValue())); } } effects->paintWindow(w, mask, region, data); } void CubeSlideEffect::postPaintScreen() { effects->postPaintScreen(); if (isActive()) { if (timeLine.currentValue() == 1.0) { RotationDirection direction = slideRotations.dequeue(); switch(direction) { case Left: if (usePagerLayout) front_desktop = effects->desktopToLeft(front_desktop, true); else { front_desktop--; if (front_desktop == 0) front_desktop = effects->numberOfDesktops(); } break; case Right: if (usePagerLayout) front_desktop = effects->desktopToRight(front_desktop, true); else { front_desktop++; if (front_desktop > effects->numberOfDesktops()) front_desktop = 1; } break; case Upwards: front_desktop = effects->desktopAbove(front_desktop, true); break; case Downwards: front_desktop = effects->desktopBelow(front_desktop, true); break; } timeLine.setCurrentTime(0); if (slideRotations.count() == 1) timeLine.setCurveShape(QTimeLine::EaseOutCurve); else timeLine.setCurveShape(QTimeLine::LinearCurve); if (slideRotations.empty()) { for (EffectWindow* w : staticWindows) { w->setData(WindowForceBlurRole, QVariant()); w->setData(WindowForceBackgroundContrastRole, QVariant()); } staticWindows.clear(); - effects->setActiveFullScreenEffect(0); + effects->setActiveFullScreenEffect(nullptr); } } effects->addRepaintFull(); } } void CubeSlideEffect::slotDesktopChanged(int old, int current, EffectWindow* w) { Q_UNUSED(w) if (effects->activeFullScreenEffect() && effects->activeFullScreenEffect() != this) return; if (old > effects->numberOfDesktops()) { // number of desktops has been reduced -> no animation return; } if (windowMoving) { desktopChangedWhileMoving = true; progressRestriction = 1.0 - progressRestriction; effects->addRepaintFull(); return; } bool activate = true; if (!slideRotations.empty()) { // last slide still in progress activate = false; RotationDirection direction = slideRotations.dequeue(); slideRotations.clear(); slideRotations.enqueue(direction); switch(direction) { case Left: if (usePagerLayout) old = effects->desktopToLeft(front_desktop, true); else { old = front_desktop - 1; if (old == 0) old = effects->numberOfDesktops(); } break; case Right: if (usePagerLayout) old = effects->desktopToRight(front_desktop, true); else { old = front_desktop + 1; if (old > effects->numberOfDesktops()) old = 1; } break; case Upwards: old = effects->desktopAbove(front_desktop, true); break; case Downwards: old = effects->desktopBelow(front_desktop, true); break; } } if (usePagerLayout) { // calculate distance in respect to pager QPoint diff = effects->desktopGridCoords(effects->currentDesktop()) - effects->desktopGridCoords(old); if (qAbs(diff.x()) > effects->desktopGridWidth() / 2) { int sign = -1 * (diff.x() / qAbs(diff.x())); diff.setX(sign *(effects->desktopGridWidth() - qAbs(diff.x()))); } if (diff.x() > 0) { for (int i = 0; i < diff.x(); i++) { slideRotations.enqueue(Right); } } else if (diff.x() < 0) { diff.setX(-diff.x()); for (int i = 0; i < diff.x(); i++) { slideRotations.enqueue(Left); } } if (qAbs(diff.y()) > effects->desktopGridHeight() / 2) { int sign = -1 * (diff.y() / qAbs(diff.y())); diff.setY(sign *(effects->desktopGridHeight() - qAbs(diff.y()))); } if (diff.y() > 0) { for (int i = 0; i < diff.y(); i++) { slideRotations.enqueue(Downwards); } } if (diff.y() < 0) { diff.setY(-diff.y()); for (int i = 0; i < diff.y(); i++) { slideRotations.enqueue(Upwards); } } } else { // ignore pager layout int left = old - current; if (left < 0) left = effects->numberOfDesktops() + left; int right = current - old; if (right < 0) right = effects->numberOfDesktops() + right; if (left < right) { for (int i = 0; i < left; i++) { slideRotations.enqueue(Left); } } else { for (int i = 0; i < right; i++) { slideRotations.enqueue(Right); } } } timeLine.setDuration((float)rotationDuration / (float)slideRotations.count()); if (activate) { startAnimation(); front_desktop = old; effects->addRepaintFull(); } } void CubeSlideEffect::startAnimation() { const EffectWindowList windows = effects->stackingOrder(); for (EffectWindow* w : windows) { if (!shouldAnimate(w)) { w->setData(WindowForceBlurRole, QVariant(true)); w->setData(WindowForceBackgroundContrastRole, QVariant(true)); staticWindows.insert(w); } } if (slideRotations.count() == 1) { timeLine.setCurveShape(QTimeLine::EaseInOutCurve); } else { timeLine.setCurveShape(QTimeLine::EaseInCurve); } effects->setActiveFullScreenEffect(this); timeLine.setCurrentTime(0); } void CubeSlideEffect::slotWindowAdded(EffectWindow* w) { if (!isActive()) { return; } if (!shouldAnimate(w)) { staticWindows.insert(w); w->setData(WindowForceBlurRole, QVariant(true)); w->setData(WindowForceBackgroundContrastRole, QVariant(true)); } } void CubeSlideEffect::slotWindowDeleted(EffectWindow* w) { staticWindows.remove(w); } bool CubeSlideEffect::shouldAnimate(const EffectWindow* w) const { if (w->isDock()) { return !dontSlidePanels; } if (w->isOnAllDesktops()) { if (w->isDesktop()) { return true; } if (w->isSpecialWindow()) { return false; } return !dontSlideStickyWindows; } return true; } void CubeSlideEffect::slotWindowStepUserMovedResized(EffectWindow* w) { if (!useWindowMoving) return; if (!effects->kwinOption(SwitchDesktopOnScreenEdgeMovingWindows).toBool()) return; if (w->isUserResize()) return; const QSize screenSize = effects->virtualScreenSize(); const QPoint cursor = effects->cursorPos(); const int horizontal = screenSize.width() * 0.1; const int vertical = screenSize.height() * 0.1; const QRect leftRect(0, screenSize.height() * 0.1, horizontal, screenSize.height() * 0.8); const QRect rightRect(screenSize.width() - horizontal, screenSize.height() * 0.1, horizontal, screenSize.height() * 0.8); const QRect topRect(horizontal, 0, screenSize.width() * 0.8, vertical); const QRect bottomRect(horizontal, screenSize.height() - vertical, screenSize.width() - horizontal * 2, vertical); if (leftRect.contains(cursor)) { if (effects->desktopToLeft(effects->currentDesktop()) != effects->currentDesktop()) windowMovingChanged(0.3 *(float)(horizontal - cursor.x()) / (float)horizontal, Left); } else if (rightRect.contains(cursor)) { if (effects->desktopToRight(effects->currentDesktop()) != effects->currentDesktop()) windowMovingChanged(0.3 *(float)(cursor.x() - screenSize.width() + horizontal) / (float)horizontal, Right); } else if (topRect.contains(cursor)) { if (effects->desktopAbove(effects->currentDesktop()) != effects->currentDesktop()) windowMovingChanged(0.3 *(float)(vertical - cursor.y()) / (float)vertical, Upwards); } else if (bottomRect.contains(cursor)) { if (effects->desktopBelow(effects->currentDesktop()) != effects->currentDesktop()) windowMovingChanged(0.3 *(float)(cursor.y() - screenSize.height() + vertical) / (float)vertical, Downwards); } else { // not in one of the areas windowMoving = false; desktopChangedWhileMoving = false; timeLine.setCurrentTime(0); if (!slideRotations.isEmpty()) slideRotations.clear(); - effects->setActiveFullScreenEffect(0); + effects->setActiveFullScreenEffect(nullptr); effects->addRepaintFull(); } } void CubeSlideEffect::slotWindowFinishUserMovedResized(EffectWindow* w) { if (!useWindowMoving) return; if (!effects->kwinOption(SwitchDesktopOnScreenEdgeMovingWindows).toBool()) return; if (w->isUserResize()) return; if (!desktopChangedWhileMoving) { if (slideRotations.isEmpty()) return; const RotationDirection direction = slideRotations.dequeue(); switch(direction) { case Left: slideRotations.enqueue(Right); break; case Right: slideRotations.enqueue(Left); break; case Upwards: slideRotations.enqueue(Downwards); break; case Downwards: slideRotations.enqueue(Upwards); break; default: break; // impossible } timeLine.setCurrentTime(timeLine.duration() - timeLine.currentTime()); } desktopChangedWhileMoving = false; windowMoving = false; effects->addRepaintFull(); } void CubeSlideEffect::windowMovingChanged(float progress, RotationDirection direction) { if (desktopChangedWhileMoving) progressRestriction = 1.0 - progress; else progressRestriction = progress; front_desktop = effects->currentDesktop(); if (slideRotations.isEmpty()) { slideRotations.enqueue(direction); windowMoving = true; startAnimation(); } effects->addRepaintFull(); } bool CubeSlideEffect::isActive() const { return !slideRotations.isEmpty(); } void CubeSlideEffect::slotNumberDesktopsChanged() { // This effect animates only aftermaths of desktop switching. There is no any // way to reference removed desktops for animation purposes. So our the best // shot is just to do nothing. It doesn't look nice and we probaby have to // find more proper way to handle this case. if (!isActive()) { return; } for (EffectWindow *w : staticWindows) { w->setData(WindowForceBlurRole, QVariant()); w->setData(WindowForceBackgroundContrastRole, QVariant()); } slideRotations.clear(); staticWindows.clear(); effects->setActiveFullScreenEffect(nullptr); } } // namespace diff --git a/effects/cubeslide/cubeslide_config.h b/effects/cubeslide/cubeslide_config.h index 647a2621c..bb032b1dc 100644 --- a/effects/cubeslide/cubeslide_config.h +++ b/effects/cubeslide/cubeslide_config.h @@ -1,54 +1,54 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. 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 . *********************************************************************/ #ifndef KWIN_CUBESLIDE_CONFIG_H #define KWIN_CUBESLIDE_CONFIG_H #include #include "ui_cubeslide_config.h" namespace KWin { class CubeSlideEffectConfigForm : public QWidget, public Ui::CubeSlideEffectConfigForm { Q_OBJECT public: explicit CubeSlideEffectConfigForm(QWidget* parent); }; class CubeSlideEffectConfig : public KCModule { Q_OBJECT public: - explicit CubeSlideEffectConfig(QWidget* parent = 0, const QVariantList& args = QVariantList()); + explicit CubeSlideEffectConfig(QWidget* parent = nullptr, const QVariantList& args = QVariantList()); public Q_SLOTS: void save() override; private: CubeSlideEffectConfigForm* m_ui; }; } // namespace #endif diff --git a/effects/desktopgrid/desktopgrid.cpp b/effects/desktopgrid/desktopgrid.cpp index 95f6eba59..03925f959 100644 --- a/effects/desktopgrid/desktopgrid.cpp +++ b/effects/desktopgrid/desktopgrid.cpp @@ -1,1518 +1,1518 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2007 Lubos Lunak Copyright (C) 2008 Lucas Murray 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 . *********************************************************************/ #include "desktopgrid.h" // KConfigSkeleton #include "desktopgridconfig.h" #include "../presentwindows/presentwindows_proxy.h" #include "../effect_builtins.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace KWin { // WARNING, TODO: This effect relies on the desktop layout being EWMH-compliant. DesktopGridEffect::DesktopGridEffect() : activated(false) , timeline() , keyboardGrab(false) , wasWindowMove(false) , wasWindowCopy(false) , wasDesktopMove(false) , isValidMove(false) - , windowMove(NULL) + , windowMove(nullptr) , windowMoveDiff() , gridSize() , orientation(Qt::Horizontal) , activeCell(1, 1) , scale() , unscaledBorder() , scaledSize() , scaledOffset() - , m_proxy(0) + , m_proxy(nullptr) , m_activateAction(new QAction(this)) { initConfig(); // Load shortcuts QAction* a = m_activateAction; a->setObjectName(QStringLiteral("ShowDesktopGrid")); a->setText(i18n("Show Desktop Grid")); KGlobalAccel::self()->setDefaultShortcut(a, QList() << Qt::CTRL + Qt::Key_F8); KGlobalAccel::self()->setShortcut(a, QList() << Qt::CTRL + Qt::Key_F8); shortcut = KGlobalAccel::self()->shortcut(a); effects->registerGlobalShortcut(Qt::CTRL + Qt::Key_F8, a); effects->registerTouchpadSwipeShortcut(SwipeDirection::Up, a); connect(a, &QAction::triggered, this, &DesktopGridEffect::toggle); connect(KGlobalAccel::self(), &KGlobalAccel::globalShortcutChanged, this, &DesktopGridEffect::globalShortcutChanged); connect(effects, &EffectsHandler::windowAdded, this, &DesktopGridEffect::slotWindowAdded); connect(effects, &EffectsHandler::windowClosed, this, &DesktopGridEffect::slotWindowClosed); connect(effects, &EffectsHandler::windowDeleted, this, &DesktopGridEffect::slotWindowDeleted); connect(effects, &EffectsHandler::numberDesktopsChanged, this, &DesktopGridEffect::slotNumberDesktopsChanged); connect(effects, &EffectsHandler::windowGeometryShapeChanged, this, &DesktopGridEffect::slotWindowGeometryShapeChanged); connect(effects, &EffectsHandler::numberScreensChanged, this, &DesktopGridEffect::setup); connect(effects, &EffectsHandler::screenAboutToLock, this, [this]() { setActive(false); if (keyboardGrab) { effects->ungrabKeyboard(); keyboardGrab = false; } }); // Load all other configuration details reconfigure(ReconfigureAll); } DesktopGridEffect::~DesktopGridEffect() { foreach (DesktopButtonsView *view, m_desktopButtonsViews) view->deleteLater(); m_desktopButtonsViews.clear(); } void DesktopGridEffect::reconfigure(ReconfigureFlags) { DesktopGridConfig::self()->read(); foreach (ElectricBorder border, borderActivate) { effects->unreserveElectricBorder(border, this); } borderActivate.clear(); foreach (int i, DesktopGridConfig::borderActivate()) { borderActivate.append(ElectricBorder(i)); effects->reserveElectricBorder(ElectricBorder(i), this); } // TODO: rename zoomDuration to duration zoomDuration = animationTime(DesktopGridConfig::zoomDuration() != 0 ? DesktopGridConfig::zoomDuration() : 300); timeline.setCurveShape(QTimeLine::EaseInOutCurve); timeline.setDuration(zoomDuration); border = DesktopGridConfig::borderWidth(); desktopNameAlignment = Qt::Alignment(DesktopGridConfig::desktopNameAlignment()); layoutMode = DesktopGridConfig::layoutMode(); customLayoutRows = DesktopGridConfig::customLayoutRows(); m_usePresentWindows = DesktopGridConfig::presentWindows(); // deactivate and activate all touch border const QVector relevantBorders{ElectricLeft, ElectricTop, ElectricRight, ElectricBottom}; for (auto e : relevantBorders) { effects->unregisterTouchBorder(e, m_activateAction); } const auto touchBorders = DesktopGridConfig::touchBorderActivate(); for (int i : touchBorders) { if (!relevantBorders.contains(ElectricBorder(i))) { continue; } effects->registerTouchBorder(ElectricBorder(i), m_activateAction); } } //----------------------------------------------------------------------------- // Screen painting void DesktopGridEffect::prePaintScreen(ScreenPrePaintData& data, int time) { if (timeline.currentValue() != 0 || activated || (isUsingPresentWindows() && isMotionManagerMovingWindows())) { if (activated) timeline.setCurrentTime(timeline.currentTime() + time); else timeline.setCurrentTime(timeline.currentTime() - time); for (int i = 0; i < effects->numberOfDesktops(); i++) { if (i == highlightedDesktop - 1) hoverTimeline[i]->setCurrentTime(hoverTimeline[i]->currentTime() + time); else hoverTimeline[i]->setCurrentTime(hoverTimeline[i]->currentTime() - time); } if (isUsingPresentWindows()) { QList::iterator i; for (i = m_managers.begin(); i != m_managers.end(); ++i) (*i).calculate(time); } // PAINT_SCREEN_BACKGROUND_FIRST is needed because screen will be actually painted more than once, // so with normal screen painting second screen paint would erase parts of the first paint if (timeline.currentValue() != 0 || (isUsingPresentWindows() && isMotionManagerMovingWindows())) data.mask |= PAINT_SCREEN_TRANSFORMED | PAINT_SCREEN_BACKGROUND_FIRST; if (!activated && timeline.currentValue() == 0 && !(isUsingPresentWindows() && isMotionManagerMovingWindows())) finish(); } for (auto const &w : effects->stackingOrder()) { w->setData(WindowForceBlurRole, QVariant(true)); } effects->prePaintScreen(data, time); } void DesktopGridEffect::paintScreen(int mask, QRegion region, ScreenPaintData& data) { if (timeline.currentValue() == 0 && !isUsingPresentWindows()) { effects->paintScreen(mask, region, data); return; } for (int desktop = 1; desktop <= effects->numberOfDesktops(); desktop++) { ScreenPaintData d = data; paintingDesktop = desktop; effects->paintScreen(mask, region, d); } // paint the add desktop button foreach (DesktopButtonsView *view, m_desktopButtonsViews) { if (!view->effectWindow) { EffectWindow *viewWindow = effects->findWindow(view->winId()); if (viewWindow) { viewWindow->setData(WindowForceBlurRole, QVariant(true)); view->effectWindow = viewWindow; } } if (view->effectWindow) { WindowPaintData d(view->effectWindow); d.multiplyOpacity(timeline.currentValue()); effects->drawWindow(view->effectWindow, PAINT_WINDOW_TRANSLUCENT, infiniteRegion(), d); } } if (isUsingPresentWindows() && windowMove && wasWindowMove) { // the moving window has to be painted on top of all desktops QPoint diff = cursorPos() - m_windowMoveStartPoint; QRect geo = m_windowMoveGeometry.translated(diff); WindowPaintData d(windowMove, data.projectionMatrix()); d *= QVector2D((qreal)geo.width() / (qreal)windowMove->width(), (qreal)geo.height() / (qreal)windowMove->height()); d += QPoint(geo.left() - windowMove->x(), geo.top() - windowMove->y()); effects->drawWindow(windowMove, PAINT_WINDOW_TRANSFORMED | PAINT_WINDOW_LANCZOS, infiniteRegion(), d); } if (desktopNameAlignment) { for (int screen = 0; screen < effects->numScreens(); screen++) { QRect screenGeom = effects->clientArea(ScreenArea, screen, 0); int desktop = 1; foreach (EffectFrame * frame, desktopNames) { QPointF posTL(scalePos(screenGeom.topLeft(), desktop, screen)); QPointF posBR(scalePos(screenGeom.bottomRight(), desktop, screen)); QRect textArea(posTL.x(), posTL.y(), posBR.x() - posTL.x(), posBR.y() - posTL.y()); textArea.adjust(textArea.width() / 10, textArea.height() / 10, -textArea.width() / 10, -textArea.height() / 10); int x, y; if (desktopNameAlignment & Qt::AlignLeft) x = textArea.x(); else if (desktopNameAlignment & Qt::AlignRight) x = textArea.right(); else x = textArea.center().x(); if (desktopNameAlignment & Qt::AlignTop) y = textArea.y(); else if (desktopNameAlignment & Qt::AlignBottom) y = textArea.bottom(); else y = textArea.center().y(); frame->setPosition(QPoint(x, y)); frame->render(region, timeline.currentValue(), 0.7); ++desktop; } } } } void DesktopGridEffect::postPaintScreen() { if (activated ? timeline.currentValue() != 1 : timeline.currentValue() != 0) effects->addRepaintFull(); // Repaint during zoom if (isUsingPresentWindows() && isMotionManagerMovingWindows()) effects->addRepaintFull(); if (activated) { for (int i = 0; i < effects->numberOfDesktops(); i++) { if (hoverTimeline[i]->currentValue() != 0.0 && hoverTimeline[i]->currentValue() != 1.0) { // Repaint during soft highlighting effects->addRepaintFull(); break; } } } for (auto &w : effects->stackingOrder()) { w->setData(WindowForceBlurRole, QVariant()); } effects->postPaintScreen(); } //----------------------------------------------------------------------------- // Window painting void DesktopGridEffect::prePaintWindow(EffectWindow* w, WindowPrePaintData& data, int time) { if (timeline.currentValue() != 0 || (isUsingPresentWindows() && isMotionManagerMovingWindows())) { if (w->isOnDesktop(paintingDesktop)) { w->enablePainting(EffectWindow::PAINT_DISABLED_BY_DESKTOP); if (w->isMinimized() && isUsingPresentWindows()) w->enablePainting(EffectWindow::PAINT_DISABLED_BY_MINIMIZE); data.mask |= PAINT_WINDOW_TRANSFORMED; // Split windows at screen edges for (int screen = 0; screen < effects->numScreens(); screen++) { QRect screenGeom = effects->clientArea(ScreenArea, screen, 0); if (w->x() < screenGeom.x()) data.quads = data.quads.splitAtX(screenGeom.x() - w->x()); if (w->x() + w->width() > screenGeom.x() + screenGeom.width()) data.quads = data.quads.splitAtX(screenGeom.x() + screenGeom.width() - w->x()); if (w->y() < screenGeom.y()) data.quads = data.quads.splitAtY(screenGeom.y() - w->y()); if (w->y() + w->height() > screenGeom.y() + screenGeom.height()) data.quads = data.quads.splitAtY(screenGeom.y() + screenGeom.height() - w->y()); } if (windowMove && wasWindowMove && windowMove->findModal() == w) w->disablePainting(EffectWindow::PAINT_DISABLED_BY_DESKTOP); } else w->disablePainting(EffectWindow::PAINT_DISABLED_BY_DESKTOP); } effects->prePaintWindow(w, data, time); } void DesktopGridEffect::paintWindow(EffectWindow* w, int mask, QRegion region, WindowPaintData& data) { if (timeline.currentValue() != 0 || (isUsingPresentWindows() && isMotionManagerMovingWindows())) { if (isUsingPresentWindows() && w == windowMove && wasWindowMove && ((!wasWindowCopy && sourceDesktop == paintingDesktop) || (sourceDesktop != highlightedDesktop && highlightedDesktop == paintingDesktop))) { return; // will be painted on top of all other windows } foreach (DesktopButtonsView *view, m_desktopButtonsViews) { if (view->effectWindow == w) { if (!activated && timeline.currentValue() < 0.05) { view->hide(); } return; // will be painted on top of all other windows } } qreal xScale = data.xScale(); qreal yScale = data.yScale(); data.multiplyBrightness(1.0 - (0.3 * (1.0 - hoverTimeline[paintingDesktop - 1]->currentValue()))); for (int screen = 0; screen < effects->numScreens(); screen++) { QRect screenGeom = effects->clientArea(ScreenArea, screen, 0); QRectF transformedGeo = w->geometry(); // Display all quads on the same screen on the same pass WindowQuadList screenQuads; bool quadsAdded = false; if (isUsingPresentWindows()) { WindowMotionManager& manager = m_managers[(paintingDesktop-1)*(effects->numScreens())+screen ]; if (manager.isManaging(w)) { foreach (const WindowQuad & quad, data.quads) screenQuads.append(quad); transformedGeo = manager.transformedGeometry(w); quadsAdded = true; if (!manager.areWindowsMoving() && timeline.currentValue() == 1.0) mask |= PAINT_WINDOW_LANCZOS; } else if (w->screen() != screen) quadsAdded = true; // we don't want parts of overlapping windows on the other screen if (w->isDesktop()) quadsAdded = false; } if (!quadsAdded) { foreach (const WindowQuad & quad, data.quads) { QRect quadRect( w->x() + quad.left(), w->y() + quad.top(), quad.right() - quad.left(), quad.bottom() - quad.top() ); if (quadRect.intersects(screenGeom)) screenQuads.append(quad); } } if (screenQuads.isEmpty()) continue; // Nothing is being displayed, don't bother WindowPaintData d = data; d.quads = screenQuads; QPointF newPos = scalePos(transformedGeo.topLeft().toPoint(), paintingDesktop, screen); double progress = timeline.currentValue(); d.setXScale(interpolate(1, xScale * scale[screen] * (float)transformedGeo.width() / (float)w->geometry().width(), progress)); d.setYScale(interpolate(1, yScale * scale[screen] * (float)transformedGeo.height() / (float)w->geometry().height(), progress)); d += QPoint(qRound(newPos.x() - w->x()), qRound(newPos.y() - w->y())); if (isUsingPresentWindows() && (w->isDock() || w->isSkipSwitcher())) { // fade out panels if present windows is used d.multiplyOpacity((1.0 - timeline.currentValue())); } if (isUsingPresentWindows() && w->isMinimized()) { d.multiplyOpacity(timeline.currentValue()); } if (effects->compositingType() == XRenderCompositing) { // More exact clipping as XRender displays the entire window instead of just the quad QPointF screenPosF = scalePos(screenGeom.topLeft(), paintingDesktop).toPoint(); QPoint screenPos( qRound(screenPosF.x()), qRound(screenPosF.y()) ); QSize screenSize( qRound(interpolate(screenGeom.width(), scaledSize[screen].width(), progress)), qRound(interpolate(screenGeom.height(), scaledSize[screen].height(), progress)) ); PaintClipper pc(effects->clientArea(ScreenArea, screen, 0) & QRect(screenPos, screenSize)); effects->paintWindow(w, mask, region, d); } else { if (w->isDesktop() && timeline.currentValue() == 1.0) { // desktop windows are not in a motion manager and can always be rendered with // lanczos sampling except for animations mask |= PAINT_WINDOW_LANCZOS; } effects->paintWindow(w, mask, effects->clientArea(ScreenArea, screen, 0), d); } } } else effects->paintWindow(w, mask, region, data); } //----------------------------------------------------------------------------- // User interaction void DesktopGridEffect::slotWindowAdded(EffectWindow* w) { if (!activated) return; if (isUsingPresentWindows()) { if (!isRelevantWithPresentWindows(w)) return; // don't add foreach (const int i, desktopList(w)) { WindowMotionManager& manager = m_managers[ i*effects->numScreens()+w->screen()]; manager.manage(w); m_proxy->calculateWindowTransformations(manager.managedWindows(), w->screen(), manager); } } effects->addRepaintFull(); } void DesktopGridEffect::slotWindowClosed(EffectWindow* w) { if (!activated && timeline.currentValue() == 0) return; if (w == windowMove) { effects->setElevatedWindow(windowMove, false); - windowMove = NULL; + windowMove = nullptr; } if (isUsingPresentWindows()) { foreach (const int i, desktopList(w)) { WindowMotionManager& manager = m_managers[i*effects->numScreens()+w->screen()]; manager.unmanage(w); m_proxy->calculateWindowTransformations(manager.managedWindows(), w->screen(), manager); } } effects->addRepaintFull(); } void DesktopGridEffect::slotWindowDeleted(EffectWindow* w) { if (w == windowMove) - windowMove = 0; + windowMove = nullptr; foreach (DesktopButtonsView *view, m_desktopButtonsViews) { if (view->effectWindow && view->effectWindow == w) { view->effectWindow = nullptr; break; } } if (isUsingPresentWindows()) { for (QList::iterator it = m_managers.begin(), end = m_managers.end(); it != end; ++it) { it->unmanage(w); } } } void DesktopGridEffect::slotWindowGeometryShapeChanged(EffectWindow* w, const QRect& old) { Q_UNUSED(old) if (!activated) return; if (w == windowMove && wasWindowMove) return; if (isUsingPresentWindows()) { foreach (const int i, desktopList(w)) { WindowMotionManager& manager = m_managers[i*effects->numScreens()+w->screen()]; m_proxy->calculateWindowTransformations(manager.managedWindows(), w->screen(), manager); } } } void DesktopGridEffect::windowInputMouseEvent(QEvent* e) { if ((e->type() != QEvent::MouseMove && e->type() != QEvent::MouseButtonPress && e->type() != QEvent::MouseButtonRelease) || timeline.currentValue() != 1) // Block user input during animations return; QMouseEvent* me = static_cast< QMouseEvent* >(e); if (!(wasWindowMove || wasDesktopMove)) { foreach (DesktopButtonsView *view, m_desktopButtonsViews) { if (view->geometry().contains(me->pos())) { const QPoint widgetPos = view->mapFromGlobal(me->pos()); QMouseEvent event(me->type(), widgetPos, me->pos(), me->button(), me->buttons(), me->modifiers()); view->windowInputMouseEvent(&event); return; } } } if (e->type() == QEvent::MouseMove) { int d = posToDesktop(me->pos()); - if (windowMove != NULL && + if (windowMove != nullptr && (me->pos() - dragStartPos).manhattanLength() > QApplication::startDragDistance()) { // Handle window moving if (!wasWindowMove) { // Activate on move if (isUsingPresentWindows()) { foreach (const int i, desktopList(windowMove)) { WindowMotionManager& manager = m_managers[(i)*(effects->numScreens()) + windowMove->screen()]; if ((i + 1) == sourceDesktop) { const QRectF transformedGeo = manager.transformedGeometry(windowMove); const QPointF pos = scalePos(transformedGeo.topLeft().toPoint(), sourceDesktop, windowMove->screen()); const QSize size(scale[windowMove->screen()] *(float)transformedGeo.width(), scale[windowMove->screen()] *(float)transformedGeo.height()); m_windowMoveGeometry = QRect(pos.toPoint(), size); m_windowMoveStartPoint = me->pos(); } manager.unmanage(windowMove); if (EffectWindow* modal = windowMove->findModal()) { if (manager.isManaging(modal)) manager.unmanage(modal); } m_proxy->calculateWindowTransformations(manager.managedWindows(), windowMove->screen(), manager); } wasWindowMove = true; } } if (windowMove->isMovable() && !isUsingPresentWindows()) { wasWindowMove = true; int screen = effects->screenNumber(me->pos()); - effects->moveWindow(windowMove, unscalePos(me->pos(), NULL) + windowMoveDiff, true, 1.0 / scale[screen]); + effects->moveWindow(windowMove, unscalePos(me->pos(), nullptr) + windowMoveDiff, true, 1.0 / scale[screen]); } if (wasWindowMove) { if (effects->waylandDisplay() && (me->modifiers() & Qt::ControlModifier)) { wasWindowCopy = true; effects->defineCursor(Qt::DragCopyCursor); } else { wasWindowCopy = false; effects->defineCursor(Qt::ClosedHandCursor); } if (d != highlightedDesktop) { auto desktops = windowMove->desktops(); if (!desktops.contains(d)) { desktops.append(d); } if (highlightedDesktop != sourceDesktop || !wasWindowCopy) { desktops.removeOne(highlightedDesktop); } effects->windowToDesktops(windowMove, desktops); const int screen = effects->screenNumber(me->pos()); if (screen != windowMove->screen()) effects->windowToScreen(windowMove, screen); } effects->addRepaintFull(); } } else if ((me->buttons() & Qt::LeftButton) && !wasDesktopMove && (me->pos() - dragStartPos).manhattanLength() > QApplication::startDragDistance()) { wasDesktopMove = true; effects->defineCursor(Qt::ClosedHandCursor); } if (d != highlightedDesktop) { // Highlight desktop if ((me->buttons() & Qt::LeftButton) && isValidMove && !wasWindowMove && d <= effects->numberOfDesktops()) { EffectWindowList windows = effects->stackingOrder(); EffectWindowList stack[3]; for (EffectWindowList::const_iterator it = windows.constBegin(), end = windows.constEnd(); it != end; ++it) { EffectWindow *w = const_cast(*it); // we're not really touching it here but below if (w->isOnAllDesktops()) continue; if (w->isOnDesktop(highlightedDesktop)) stack[0] << w; if (w->isOnDesktop(d)) stack[1] << w; if (w->isOnDesktop(m_originalMovingDesktop)) stack[2] << w; } const int desks[4] = {highlightedDesktop, d, m_originalMovingDesktop, highlightedDesktop}; for (int i = 0; i < 3; ++i ) { if (desks[i] == desks[i+1]) continue; foreach (EffectWindow *w, stack[i]) { auto desktops = w->desktops(); desktops.removeOne(desks[i]); desktops.append(desks[i+1]); effects->windowToDesktops(w, desktops); if (isUsingPresentWindows()) { m_managers[(desks[i]-1)*(effects->numScreens()) + w->screen()].unmanage(w); m_managers[(desks[i+1]-1)*(effects->numScreens()) + w->screen()].manage(w); } } } if (isUsingPresentWindows()) { for (int i = 0; i < effects->numScreens(); i++) { for (int j = 0; j < 3; ++j) { WindowMotionManager& manager = m_managers[(desks[j]-1)*(effects->numScreens()) + i ]; m_proxy->calculateWindowTransformations(manager.managedWindows(), i, manager); } } effects->addRepaintFull(); } } setHighlightedDesktop(d); } } if (e->type() == QEvent::MouseButtonPress) { if (me->buttons() == Qt::LeftButton) { isValidMove = true; dragStartPos = me->pos(); sourceDesktop = posToDesktop(me->pos()); bool isDesktop = (me->modifiers() & Qt::ShiftModifier); - EffectWindow* w = isDesktop ? NULL : windowAt(me->pos()); - if (w != NULL) + EffectWindow* w = isDesktop ? nullptr : windowAt(me->pos()); + if (w != nullptr) isDesktop = w->isDesktop(); if (isDesktop) m_originalMovingDesktop = posToDesktop(me->pos()); else m_originalMovingDesktop = 0; - if (w != NULL && !w->isDesktop() && (w->isMovable() || w->isMovableAcrossScreens() || isUsingPresentWindows())) { + if (w != nullptr && !w->isDesktop() && (w->isMovable() || w->isMovableAcrossScreens() || isUsingPresentWindows())) { // Prepare it for moving - windowMoveDiff = w->pos() - unscalePos(me->pos(), NULL); + windowMoveDiff = w->pos() - unscalePos(me->pos(), nullptr); windowMove = w; effects->setElevatedWindow(windowMove, true); } - } else if ((me->buttons() == Qt::MidButton || me->buttons() == Qt::RightButton) && windowMove == NULL) { + } else if ((me->buttons() == Qt::MidButton || me->buttons() == Qt::RightButton) && windowMove == nullptr) { EffectWindow* w = windowAt(me->pos()); if (w && w->isDesktop()) { w = nullptr; } - if (w != NULL) { + if (w != nullptr) { const int desktop = posToDesktop(me->pos()); if (w->isOnAllDesktops()) { effects->windowToDesktop(w, desktop); } else { effects->windowToDesktop(w, NET::OnAllDesktops); } const bool isOnAllDesktops = w->isOnAllDesktops(); if (isUsingPresentWindows()) { for (int i = 0; i < effects->numberOfDesktops(); i++) { if (i != desktop - 1) { WindowMotionManager& manager = m_managers[ i*effects->numScreens() + w->screen()]; if (isOnAllDesktops) manager.manage(w); else manager.unmanage(w); m_proxy->calculateWindowTransformations(manager.managedWindows(), w->screen(), manager); } } } effects->addRepaintFull(); } } } if (e->type() == QEvent::MouseButtonRelease && me->button() == Qt::LeftButton) { isValidMove = false; if (windowMove) effects->activateWindow(windowMove); if (wasWindowMove || wasDesktopMove) { // reset pointer effects->defineCursor(Qt::PointingHandCursor); } else { // click -> exit const int desk = posToDesktop(me->pos()); if (desk > effects->numberOfDesktops()) return; // don't quit when missing desktop setCurrentDesktop(desk); setActive(false); } if (windowMove) { if (wasWindowMove && isUsingPresentWindows()) { const int targetDesktop = posToDesktop(cursorPos()); foreach (const int i, desktopList(windowMove)) { WindowMotionManager& manager = m_managers[(i)*(effects->numScreens()) + windowMove->screen()]; manager.manage(windowMove); if (EffectWindow* modal = windowMove->findModal()) manager.manage(modal); if (i + 1 == targetDesktop) { // for the desktop the window is dropped on, we use the current geometry manager.setTransformedGeometry(windowMove, moveGeometryToDesktop(targetDesktop)); } m_proxy->calculateWindowTransformations(manager.managedWindows(), windowMove->screen(), manager); } effects->addRepaintFull(); } effects->setElevatedWindow(windowMove, false); - windowMove = NULL; + windowMove = nullptr; } wasWindowMove = false; wasWindowCopy = false; wasDesktopMove = false; } } void DesktopGridEffect::grabbedKeyboardEvent(QKeyEvent* e) { if (timeline.currentValue() != 1) // Block user input during animations return; - if (windowMove != NULL) + if (windowMove != nullptr) return; if (e->type() == QEvent::KeyPress) { // check for global shortcuts // HACK: keyboard grab disables the global shortcuts so we have to check for global shortcut (bug 156155) if (shortcut.contains(e->key() + e->modifiers())) { toggle(); return; } int desktop = -1; // switch by F or just if (e->key() >= Qt::Key_F1 && e->key() <= Qt::Key_F35) desktop = e->key() - Qt::Key_F1 + 1; else if (e->key() >= Qt::Key_0 && e->key() <= Qt::Key_9) desktop = e->key() == Qt::Key_0 ? 10 : e->key() - Qt::Key_0; if (desktop != -1) { if (desktop <= effects->numberOfDesktops()) { setHighlightedDesktop(desktop); setCurrentDesktop(desktop); setActive(false); } return; } switch(e->key()) { // Wrap only on autorepeat case Qt::Key_Left: setHighlightedDesktop(desktopToLeft(highlightedDesktop, !e->isAutoRepeat())); break; case Qt::Key_Right: setHighlightedDesktop(desktopToRight(highlightedDesktop, !e->isAutoRepeat())); break; case Qt::Key_Up: setHighlightedDesktop(desktopUp(highlightedDesktop, !e->isAutoRepeat())); break; case Qt::Key_Down: setHighlightedDesktop(desktopDown(highlightedDesktop, !e->isAutoRepeat())); break; case Qt::Key_Escape: setActive(false); return; case Qt::Key_Enter: case Qt::Key_Return: case Qt::Key_Space: setCurrentDesktop(highlightedDesktop); setActive(false); return; case Qt::Key_Plus: slotAddDesktop(); break; case Qt::Key_Minus: slotRemoveDesktop(); break; default: break; } } } bool DesktopGridEffect::borderActivated(ElectricBorder border) { if (!borderActivate.contains(border)) return false; if (effects->activeFullScreenEffect() && effects->activeFullScreenEffect() != this) return true; toggle(); return true; } //----------------------------------------------------------------------------- // Helper functions // Transform a point to its position on the scaled grid QPointF DesktopGridEffect::scalePos(const QPoint& pos, int desktop, int screen) const { if (screen == -1) screen = effects->screenNumber(pos); QRect screenGeom = effects->clientArea(ScreenArea, screen, 0); QPoint desktopCell; if (orientation == Qt::Horizontal) { desktopCell.setX((desktop - 1) % gridSize.width() + 1); desktopCell.setY((desktop - 1) / gridSize.width() + 1); } else { desktopCell.setX((desktop - 1) / gridSize.height() + 1); desktopCell.setY((desktop - 1) % gridSize.height() + 1); } double progress = timeline.currentValue(); QPointF point( interpolate( ( (screenGeom.width() + unscaledBorder[screen]) *(desktopCell.x() - 1) - (screenGeom.width() + unscaledBorder[screen]) *(activeCell.x() - 1) ) + pos.x(), ( (scaledSize[screen].width() + border) *(desktopCell.x() - 1) + scaledOffset[screen].x() + (pos.x() - screenGeom.x()) * scale[screen] ), progress), interpolate( ( (screenGeom.height() + unscaledBorder[screen]) *(desktopCell.y() - 1) - (screenGeom.height() + unscaledBorder[screen]) *(activeCell.y() - 1) ) + pos.y(), ( (scaledSize[screen].height() + border) *(desktopCell.y() - 1) + scaledOffset[screen].y() + (pos.y() - screenGeom.y()) * scale[screen] ), progress) ); return point; } // Detransform a point to its position on the full grid // TODO: Doesn't correctly interpolate (Final position is correct though), don't forget to copy to posToDesktop() QPoint DesktopGridEffect::unscalePos(const QPoint& pos, int* desktop) const { int screen = effects->screenNumber(pos); QRect screenGeom = effects->clientArea(ScreenArea, screen, 0); //double progress = timeline.currentValue(); double scaledX = /*interpolate( ( pos.x() - screenGeom.x() + unscaledBorder[screen] / 2.0 ) / ( screenGeom.width() + unscaledBorder[screen] ) + activeCell.x() - 1,*/ (pos.x() - scaledOffset[screen].x() + double(border) / 2.0) / (scaledSize[screen].width() + border)/*, progress )*/; double scaledY = /*interpolate( ( pos.y() - screenGeom.y() + unscaledBorder[screen] / 2.0 ) / ( screenGeom.height() + unscaledBorder[screen] ) + activeCell.y() - 1,*/ (pos.y() - scaledOffset[screen].y() + double(border) / 2.0) / (scaledSize[screen].height() + border)/*, progress )*/; int gx = qBound(0, int(scaledX), gridSize.width() - 1); // Zero-based int gy = qBound(0, int(scaledY), gridSize.height() - 1); scaledX -= gx; scaledY -= gy; - if (desktop != NULL) { + if (desktop != nullptr) { if (orientation == Qt::Horizontal) *desktop = gy * gridSize.width() + gx + 1; else *desktop = gx * gridSize.height() + gy + 1; } return QPoint( qBound( screenGeom.x(), qRound( scaledX * (screenGeom.width() + unscaledBorder[screen]) - unscaledBorder[screen] / 2.0 + screenGeom.x() ), screenGeom.right() ), qBound( screenGeom.y(), qRound( scaledY * (screenGeom.height() + unscaledBorder[screen]) - unscaledBorder[screen] / 2.0 + screenGeom.y() ), screenGeom.bottom() ) ); } int DesktopGridEffect::posToDesktop(const QPoint& pos) const { // Copied from unscalePos() int screen = effects->screenNumber(pos); double scaledX = (pos.x() - scaledOffset[screen].x() + double(border) / 2.0) / (scaledSize[screen].width() + border); double scaledY = (pos.y() - scaledOffset[screen].y() + double(border) / 2.0) / (scaledSize[screen].height() + border); int gx = qBound(0, int(scaledX), gridSize.width() - 1); // Zero-based int gy = qBound(0, int(scaledY), gridSize.height() - 1); if (orientation == Qt::Horizontal) return gy * gridSize.width() + gx + 1; return gx * gridSize.height() + gy + 1; } EffectWindow* DesktopGridEffect::windowAt(QPoint pos) const { // Get stacking order top first EffectWindowList windows = effects->stackingOrder(); EffectWindowList::Iterator begin = windows.begin(); EffectWindowList::Iterator end = windows.end(); --end; while (begin < end) qSwap(*begin++, *end--); int desktop; pos = unscalePos(pos, &desktop); if (desktop > effects->numberOfDesktops()) - return NULL; + return nullptr; if (isUsingPresentWindows()) { const int screen = effects->screenNumber(pos); EffectWindow *w = m_managers.at((desktop - 1) * (effects->numScreens()) + screen).windowAtPoint(pos, false); if (w) return w; foreach (EffectWindow * w, windows) { if (w->isOnDesktop(desktop) && w->isDesktop() && w->geometry().contains(pos)) return w; } } else { foreach (EffectWindow * w, windows) { if (w->isOnDesktop(desktop) && w->isOnCurrentActivity() && !w->isMinimized() && w->geometry().contains(pos)) return w; } } - return NULL; + return nullptr; } void DesktopGridEffect::setCurrentDesktop(int desktop) { if (orientation == Qt::Horizontal) { activeCell.setX((desktop - 1) % gridSize.width() + 1); activeCell.setY((desktop - 1) / gridSize.width() + 1); } else { activeCell.setX((desktop - 1) / gridSize.height() + 1); activeCell.setY((desktop - 1) % gridSize.height() + 1); } if (effects->currentDesktop() != desktop) effects->setCurrentDesktop(desktop); } void DesktopGridEffect::setHighlightedDesktop(int d) { if (d == highlightedDesktop || d <= 0 || d > effects->numberOfDesktops()) return; if (highlightedDesktop > 0 && highlightedDesktop <= hoverTimeline.count()) hoverTimeline[highlightedDesktop-1]->setCurrentTime(qMin(hoverTimeline[highlightedDesktop-1]->currentTime(), hoverTimeline[highlightedDesktop-1]->duration())); highlightedDesktop = d; if (highlightedDesktop <= hoverTimeline.count()) hoverTimeline[highlightedDesktop-1]->setCurrentTime(qMax(hoverTimeline[highlightedDesktop-1]->currentTime(), 0)); effects->addRepaintFull(); } int DesktopGridEffect::desktopToRight(int desktop, bool wrap) const { // Copied from Workspace::desktopToRight() int dt = desktop - 1; if (orientation == Qt::Vertical) { dt += gridSize.height(); if (dt >= effects->numberOfDesktops()) { if (wrap) dt -= effects->numberOfDesktops(); else return desktop; } } else { int d = (dt % gridSize.width()) + 1; if (d >= gridSize.width()) { if (wrap) d -= gridSize.width(); else return desktop; } dt = dt - (dt % gridSize.width()) + d; } return dt + 1; } int DesktopGridEffect::desktopToLeft(int desktop, bool wrap) const { // Copied from Workspace::desktopToLeft() int dt = desktop - 1; if (orientation == Qt::Vertical) { dt -= gridSize.height(); if (dt < 0) { if (wrap) dt += effects->numberOfDesktops(); else return desktop; } } else { int d = (dt % gridSize.width()) - 1; if (d < 0) { if (wrap) d += gridSize.width(); else return desktop; } dt = dt - (dt % gridSize.width()) + d; } return dt + 1; } int DesktopGridEffect::desktopUp(int desktop, bool wrap) const { // Copied from Workspace::desktopUp() int dt = desktop - 1; if (orientation == Qt::Horizontal) { dt -= gridSize.width(); if (dt < 0) { if (wrap) dt += effects->numberOfDesktops(); else return desktop; } } else { int d = (dt % gridSize.height()) - 1; if (d < 0) { if (wrap) d += gridSize.height(); else return desktop; } dt = dt - (dt % gridSize.height()) + d; } return dt + 1; } int DesktopGridEffect::desktopDown(int desktop, bool wrap) const { // Copied from Workspace::desktopDown() int dt = desktop - 1; if (orientation == Qt::Horizontal) { dt += gridSize.width(); if (dt >= effects->numberOfDesktops()) { if (wrap) dt -= effects->numberOfDesktops(); else return desktop; } } else { int d = (dt % gridSize.height()) + 1; if (d >= gridSize.height()) { if (wrap) d -= gridSize.height(); else return desktop; } dt = dt - (dt % gridSize.height()) + d; } return dt + 1; } //----------------------------------------------------------------------------- // Activation void DesktopGridEffect::toggle() { setActive(!activated); } void DesktopGridEffect::setActive(bool active) { if (effects->activeFullScreenEffect() && effects->activeFullScreenEffect() != this) return; // Only one fullscreen effect at a time thanks if (active && isMotionManagerMovingWindows()) return; // Still moving windows from last usage - don't activate if (activated == active) return; // Already in that state activated = active; if (activated) { effects->setShowingDesktop(false); if (timeline.currentValue() == 0) setup(); } else { if (isUsingPresentWindows()) { QList::iterator it; for (it = m_managers.begin(); it != m_managers.end(); ++it) { foreach (EffectWindow * w, (*it).managedWindows()) { (*it).moveWindow(w, w->geometry()); } } } QTimer::singleShot(zoomDuration + 1, this, [this] { if (activated) return; foreach (DesktopButtonsView *view, m_desktopButtonsViews) { view->hide(); } } ); setHighlightedDesktop(effects->currentDesktop()); // Ensure selected desktop is highlighted } effects->addRepaintFull(); } void DesktopGridEffect::setup() { if (!isActive()) return; if (!keyboardGrab) { keyboardGrab = effects->grabKeyboard(this); effects->startMouseInterception(this, Qt::PointingHandCursor); effects->setActiveFullScreenEffect(this); } setHighlightedDesktop(effects->currentDesktop()); // Soft highlighting qDeleteAll(hoverTimeline); hoverTimeline.clear(); for (int i = 0; i < effects->numberOfDesktops(); i++) { QTimeLine *newTimeline = new QTimeLine(zoomDuration, this); newTimeline->setCurveShape(QTimeLine::EaseInOutCurve); hoverTimeline.append(newTimeline); } hoverTimeline[effects->currentDesktop() - 1]->setCurrentTime(hoverTimeline[effects->currentDesktop() - 1]->duration()); // Create desktop name textures if enabled if (desktopNameAlignment) { QFont font; font.setBold(true); font.setPointSize(12); for (int i = 0; i < effects->numberOfDesktops(); i++) { EffectFrame* frame = effects->effectFrame(EffectFrameUnstyled, false); frame->setFont(font); frame->setText(effects->desktopName(i + 1)); frame->setAlignment(desktopNameAlignment); desktopNames.append(frame); } } setupGrid(); setCurrentDesktop(effects->currentDesktop()); // setup the motion managers if (m_usePresentWindows) m_proxy = static_cast(effects->getProxy(BuiltInEffects::nameForEffect(BuiltInEffect::PresentWindows))); if (isUsingPresentWindows()) { m_proxy->reCreateGrids(); // revalidation on multiscreen, bug #351724 for (int i = 1; i <= effects->numberOfDesktops(); i++) { for (int j = 0; j < effects->numScreens(); j++) { WindowMotionManager manager; foreach (EffectWindow * w, effects->stackingOrder()) { if (w->isOnDesktop(i) && w->screen() == j &&isRelevantWithPresentWindows(w)) { manager.manage(w); } } m_proxy->calculateWindowTransformations(manager.managedWindows(), j, manager); m_managers.append(manager); } } } bool enableAdd = effects->numberOfDesktops() < 20; bool enableRemove = effects->numberOfDesktops() > 1; QVector::iterator it = m_desktopButtonsViews.begin(); const int n = DesktopGridConfig::showAddRemove() ? effects->numScreens() : 0; for (int i = 0; i < n; ++i) { DesktopButtonsView *view; if (it == m_desktopButtonsViews.end()) { view = new DesktopButtonsView(); m_desktopButtonsViews.append(view); it = m_desktopButtonsViews.end(); // changed through insert! connect(view, &DesktopButtonsView::addDesktop, this, &DesktopGridEffect::slotAddDesktop); connect(view, &DesktopButtonsView::removeDesktop, this, &DesktopGridEffect::slotRemoveDesktop); } else { view = *it; ++it; } view->setAddDesktopEnabled(enableAdd); view->setRemoveDesktopEnabled(enableRemove); const QRect screenRect = effects->clientArea(FullScreenArea, i, 1); view->show(); // pseudo show must happen before geometry changes view->setPosition(screenRect.right() - border/3 - view->width(), screenRect.bottom() - border/3 - view->height()); } while (it != m_desktopButtonsViews.end()) { (*it)->deleteLater(); it = m_desktopButtonsViews.erase(it); } } void DesktopGridEffect::setupGrid() { // We need these variables for every paint so lets cache them int x, y; int numDesktops = effects->numberOfDesktops(); switch(layoutMode) { default: case LayoutPager: orientation = Qt::Horizontal; gridSize = effects->desktopGridSize(); // sanity check: pager may report incorrect size in case of one desktop if (numDesktops == 1) { gridSize = QSize(1, 1); } break; case LayoutAutomatic: y = sqrt(float(numDesktops)) + 0.5; x = float(numDesktops) / float(y) + 0.5; if (x * y < numDesktops) x++; orientation = Qt::Horizontal; gridSize.setWidth(x); gridSize.setHeight(y); break; case LayoutCustom: orientation = Qt::Horizontal; gridSize.setWidth(ceil(effects->numberOfDesktops() / double(customLayoutRows))); gridSize.setHeight(customLayoutRows); break; } scale.clear(); unscaledBorder.clear(); scaledSize.clear(); scaledOffset.clear(); for (int i = 0; i < effects->numScreens(); i++) { QRect geom = effects->clientArea(ScreenArea, i, 0); double sScale; if (gridSize.width() > gridSize.height()) sScale = (geom.width() - border * (gridSize.width() + 1)) / double(geom.width() * gridSize.width()); else sScale = (geom.height() - border * (gridSize.height() + 1)) / double(geom.height() * gridSize.height()); double sBorder = border / sScale; QSizeF size( double(geom.width()) * sScale, double(geom.height()) * sScale ); QPointF offset( geom.x() + (geom.width() - size.width() * gridSize.width() - border *(gridSize.width() - 1)) / 2.0, geom.y() + (geom.height() - size.height() * gridSize.height() - border *(gridSize.height() - 1)) / 2.0 ); scale.append(sScale); unscaledBorder.append(sBorder); scaledSize.append(size); scaledOffset.append(offset); } } void DesktopGridEffect::finish() { if (desktopNameAlignment) { qDeleteAll(desktopNames); desktopNames.clear(); } if (keyboardGrab) effects->ungrabKeyboard(); keyboardGrab = false; effects->stopMouseInterception(this); - effects->setActiveFullScreenEffect(0); + effects->setActiveFullScreenEffect(nullptr); if (isUsingPresentWindows()) { while (!m_managers.isEmpty()) { m_managers.first().unmanageAll(); m_managers.removeFirst(); } - m_proxy = 0; + m_proxy = nullptr; } } void DesktopGridEffect::globalShortcutChanged(QAction *action, const QKeySequence& seq) { if (action->objectName() != QStringLiteral("ShowDesktopGrid")) { return; } shortcut.clear(); shortcut.append(seq); } bool DesktopGridEffect::isMotionManagerMovingWindows() const { if (isUsingPresentWindows()) { QList::const_iterator it; for (it = m_managers.begin(); it != m_managers.end(); ++it) { if ((*it).areWindowsMoving()) return true; } } return false; } bool DesktopGridEffect::isUsingPresentWindows() const { - return (m_proxy != NULL); + return (m_proxy != nullptr); } // transforms the geometry of the moved window to a geometry on the desktop // internal method only used when a window is dropped onto a desktop QRectF DesktopGridEffect::moveGeometryToDesktop(int desktop) const { QPointF point = unscalePos(m_windowMoveGeometry.topLeft() + cursorPos() - m_windowMoveStartPoint); const double scaleFactor = scale[ windowMove->screen()]; if (posToDesktop(m_windowMoveGeometry.topLeft() + cursorPos() - m_windowMoveStartPoint) != desktop) { // topLeft is not on the desktop - check other corners // if all corners are not on the desktop the window is bigger than the desktop - no matter what it will look strange if (posToDesktop(m_windowMoveGeometry.topRight() + cursorPos() - m_windowMoveStartPoint) == desktop) { point = unscalePos(m_windowMoveGeometry.topRight() + cursorPos() - m_windowMoveStartPoint) - QPointF(m_windowMoveGeometry.width(), 0) / scaleFactor; } else if (posToDesktop(m_windowMoveGeometry.bottomLeft() + cursorPos() - m_windowMoveStartPoint) == desktop) { point = unscalePos(m_windowMoveGeometry.bottomLeft() + cursorPos() - m_windowMoveStartPoint) - QPointF(0, m_windowMoveGeometry.height()) / scaleFactor; } else if (posToDesktop(m_windowMoveGeometry.bottomRight() + cursorPos() - m_windowMoveStartPoint) == desktop) { point = unscalePos(m_windowMoveGeometry.bottomRight() + cursorPos() - m_windowMoveStartPoint) - QPointF(m_windowMoveGeometry.width(), m_windowMoveGeometry.height()) / scaleFactor; } } return QRectF(point, m_windowMoveGeometry.size() / scaleFactor); } void DesktopGridEffect::slotAddDesktop() { effects->setNumberOfDesktops(effects->numberOfDesktops() + 1); } void DesktopGridEffect::slotRemoveDesktop() { effects->setNumberOfDesktops(effects->numberOfDesktops() - 1); } void DesktopGridEffect::slotNumberDesktopsChanged(uint old) { if (!activated) return; const uint desktop = effects->numberOfDesktops(); bool enableAdd = desktop < 20; bool enableRemove = desktop > 1; foreach (DesktopButtonsView *view, m_desktopButtonsViews) { view->setAddDesktopEnabled(enableAdd); view->setRemoveDesktopEnabled(enableRemove); } if (old < desktop) desktopsAdded(old); else desktopsRemoved(old); } void DesktopGridEffect::desktopsAdded(int old) { const int desktop = effects->numberOfDesktops(); for (int i = old; i <= effects->numberOfDesktops(); i++) { // add a timeline for the new desktop QTimeLine *newTimeline = new QTimeLine(zoomDuration, this); newTimeline->setCurveShape(QTimeLine::EaseInOutCurve); hoverTimeline.append(newTimeline); } // Create desktop name textures if enabled if (desktopNameAlignment) { QFont font; font.setBold(true); font.setPointSize(12); for (int i = old; i < desktop; i++) { EffectFrame* frame = effects->effectFrame(EffectFrameUnstyled, false); frame->setFont(font); frame->setText(effects->desktopName(i + 1)); frame->setAlignment(desktopNameAlignment); desktopNames.append(frame); } } if (isUsingPresentWindows()) { for (int i = old+1; i <= effects->numberOfDesktops(); ++i) { for (int j = 0; j < effects->numScreens(); ++j) { WindowMotionManager manager; foreach (EffectWindow * w, effects->stackingOrder()) { if (w->isOnDesktop(i) && w->screen() == j &&isRelevantWithPresentWindows(w)) { manager.manage(w); } } m_proxy->calculateWindowTransformations(manager.managedWindows(), j, manager); m_managers.append(manager); } } } setupGrid(); // and repaint effects->addRepaintFull(); } void DesktopGridEffect::desktopsRemoved(int old) { const int desktop = effects->numberOfDesktops(); for (int i = desktop; i < old; i++) { delete hoverTimeline.takeLast(); if (desktopNameAlignment) { delete desktopNames.last(); desktopNames.removeLast(); } if (isUsingPresentWindows()) { for (int j = 0; j < effects->numScreens(); ++j) { WindowMotionManager& manager = m_managers.last(); manager.unmanageAll(); m_managers.removeLast(); } } } // add removed windows to the last desktop if (isUsingPresentWindows()) { for (int j = 0; j < effects->numScreens(); ++j) { WindowMotionManager& manager = m_managers[(desktop-1)*(effects->numScreens())+j ]; foreach (EffectWindow * w, effects->stackingOrder()) { if (manager.isManaging(w)) continue; if (w->isOnDesktop(desktop) && w->screen() == j && isRelevantWithPresentWindows(w)) { manager.manage(w); } } m_proxy->calculateWindowTransformations(manager.managedWindows(), j, manager); } } setupGrid(); // and repaint effects->addRepaintFull(); } //TODO: kill this function? or at least keep a consistent numeration with desktops starting from 1 QVector DesktopGridEffect::desktopList(const EffectWindow *w) const { if (w->isOnAllDesktops()) { static QVector allDesktops; if (allDesktops.count() != effects->numberOfDesktops()) { allDesktops.resize(effects->numberOfDesktops()); for (int i = 0; i < effects->numberOfDesktops(); ++i) allDesktops[i] = i; } return allDesktops; } QVector desks; desks.resize(w->desktops().count()); int i = 0; for (const int desk : w->desktops()) { desks[i++] = desk-1; } return desks; } bool DesktopGridEffect::isActive() const { return (timeline.currentValue() != 0 || activated || (isUsingPresentWindows() && isMotionManagerMovingWindows())) && !effects->isScreenLocked(); } bool DesktopGridEffect::isRelevantWithPresentWindows(EffectWindow *w) const { if (w->isSpecialWindow() || w->isUtility()) { return false; } if (w->isSkipSwitcher()) { return false; } if (w->isDeleted()) { return false; } if (!w->acceptsFocus()) { return false; } if (!w->isOnCurrentActivity()) { return false; } return true; } /************************************************ * DesktopButtonView ************************************************/ DesktopButtonsView::DesktopButtonsView(QWindow *parent) : QQuickView(parent) , effectWindow(nullptr) , m_visible(false) , m_posIsValid(false) { setFlags(Qt::X11BypassWindowManagerHint | Qt::FramelessWindowHint); setColor(Qt::transparent); rootContext()->setContextProperty(QStringLiteral("add"), QVariant(true)); rootContext()->setContextProperty(QStringLiteral("remove"), QVariant(true)); setSource(QUrl(QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("kwin/effects/desktopgrid/main.qml")))); if (QObject *item = rootObject()->findChild(QStringLiteral("addButton"))) { connect(item, SIGNAL(clicked()), SIGNAL(addDesktop())); } if (QObject *item = rootObject()->findChild(QStringLiteral("removeButton"))) { connect(item, SIGNAL(clicked()), SIGNAL(removeDesktop())); } } void DesktopButtonsView::windowInputMouseEvent(QMouseEvent *e) { if (e->type() == QEvent::MouseMove) { mouseMoveEvent(e); } else if (e->type() == QEvent::MouseButtonPress) { mousePressEvent(e); } else if (e->type() == QEvent::MouseButtonDblClick) { mouseDoubleClickEvent(e); } else if (e->type() == QEvent::MouseButtonRelease) { mouseReleaseEvent(e); } } void DesktopButtonsView::setAddDesktopEnabled(bool enable) { rootContext()->setContextProperty(QStringLiteral("add"), QVariant(enable)); } void DesktopButtonsView::setRemoveDesktopEnabled(bool enable) { rootContext()->setContextProperty(QStringLiteral("remove"), QVariant(enable)); } bool DesktopButtonsView::isVisible() const { return m_visible; } void DesktopButtonsView::show() { if (!m_visible && m_posIsValid) { setPosition(m_pos); m_posIsValid = false; } m_visible = true; QQuickView::show(); } void DesktopButtonsView::hide() { if (!m_posIsValid) { m_pos = position(); m_posIsValid = true; setPosition(-width(), -height()); } m_visible = false; } } // namespace diff --git a/effects/desktopgrid/desktopgrid.h b/effects/desktopgrid/desktopgrid.h index abcf98d50..4d6c8df56 100644 --- a/effects/desktopgrid/desktopgrid.h +++ b/effects/desktopgrid/desktopgrid.h @@ -1,193 +1,193 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2007 Lubos Lunak Copyright (C) 2008 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 . *********************************************************************/ #ifndef KWIN_DESKTOPGRID_H #define KWIN_DESKTOPGRID_H #include #include #include #include namespace KWin { class PresentWindowsEffectProxy; class DesktopButtonsView : public QQuickView { Q_OBJECT public: - explicit DesktopButtonsView(QWindow *parent = 0); + explicit DesktopButtonsView(QWindow *parent = nullptr); void windowInputMouseEvent(QMouseEvent* e); void setAddDesktopEnabled(bool enable); void setRemoveDesktopEnabled(bool enable); bool isVisible() const; void show(); void hide(); public: EffectWindow *effectWindow; Q_SIGNALS: void addDesktop(); void removeDesktop(); private: bool m_visible; QPoint m_pos; bool m_posIsValid; }; class DesktopGridEffect : public Effect { Q_OBJECT Q_PROPERTY(int zoomDuration READ configuredZoomDuration) Q_PROPERTY(int border READ configuredBorder) Q_PROPERTY(Qt::Alignment desktopNameAlignment READ configuredDesktopNameAlignment) Q_PROPERTY(int layoutMode READ configuredLayoutMode) Q_PROPERTY(int customLayoutRows READ configuredCustomLayoutRows) Q_PROPERTY(bool usePresentWindows READ isUsePresentWindows) // TODO: electric borders public: DesktopGridEffect(); ~DesktopGridEffect() override; void reconfigure(ReconfigureFlags) override; void prePaintScreen(ScreenPrePaintData& data, int time) override; void paintScreen(int mask, QRegion region, ScreenPaintData& data) override; void postPaintScreen() override; void prePaintWindow(EffectWindow* w, WindowPrePaintData& data, int time) override; void paintWindow(EffectWindow* w, int mask, QRegion region, WindowPaintData& data) override; void windowInputMouseEvent(QEvent* e) override; void grabbedKeyboardEvent(QKeyEvent* e) override; bool borderActivated(ElectricBorder border) override; bool isActive() const override; int requestedEffectChainPosition() const override { return 50; } enum { LayoutPager, LayoutAutomatic, LayoutCustom }; // Layout modes // for properties int configuredZoomDuration() const { return zoomDuration; } int configuredBorder() const { return border; } Qt::Alignment configuredDesktopNameAlignment() const { return desktopNameAlignment; } int configuredLayoutMode() const { return layoutMode; } int configuredCustomLayoutRows() const { return customLayoutRows; } bool isUsePresentWindows() const { return m_usePresentWindows; } private Q_SLOTS: void toggle(); // slots for global shortcut changed // needed to toggle the effect void globalShortcutChanged(QAction *action, const QKeySequence& seq); void slotAddDesktop(); void slotRemoveDesktop(); void slotWindowAdded(KWin::EffectWindow* w); void slotWindowClosed(KWin::EffectWindow *w); void slotWindowDeleted(KWin::EffectWindow *w); void slotNumberDesktopsChanged(uint old); void slotWindowGeometryShapeChanged(KWin::EffectWindow *w, const QRect &old); private: QPointF scalePos(const QPoint& pos, int desktop, int screen = -1) const; - QPoint unscalePos(const QPoint& pos, int* desktop = NULL) const; + QPoint unscalePos(const QPoint& pos, int* desktop = nullptr) const; int posToDesktop(const QPoint& pos) const; EffectWindow* windowAt(QPoint pos) const; void setCurrentDesktop(int desktop); void setHighlightedDesktop(int desktop); int desktopToRight(int desktop, bool wrap = true) const; int desktopToLeft(int desktop, bool wrap = true) const; int desktopUp(int desktop, bool wrap = true) const; int desktopDown(int desktop, bool wrap = true) const; void setActive(bool active); void setup(); void setupGrid(); void finish(); bool isMotionManagerMovingWindows() const; bool isRelevantWithPresentWindows(EffectWindow *w) const; bool isUsingPresentWindows() const; QRectF moveGeometryToDesktop(int desktop) const; void desktopsAdded(int old); void desktopsRemoved(int old); QVector desktopList(const EffectWindow *w) const; QList borderActivate; int zoomDuration; int border; Qt::Alignment desktopNameAlignment; int layoutMode; int customLayoutRows; bool activated; QTimeLine timeline; int paintingDesktop; int highlightedDesktop; int sourceDesktop; int m_originalMovingDesktop; bool keyboardGrab; bool wasWindowMove, wasWindowCopy, wasDesktopMove, isValidMove; EffectWindow* windowMove; QPoint windowMoveDiff; QPoint dragStartPos; // Soft highlighting QList hoverTimeline; QList< EffectFrame* > desktopNames; QSize gridSize; Qt::Orientation orientation; QPoint activeCell; // Per screen variables QList scale; // Because the border isn't a ratio each screen is different QList unscaledBorder; QList scaledSize; QList scaledOffset; // Shortcut - needed to toggle the effect QList shortcut; PresentWindowsEffectProxy* m_proxy; QList m_managers; bool m_usePresentWindows; QRect m_windowMoveGeometry; QPoint m_windowMoveStartPoint; QVector m_desktopButtonsViews; QAction *m_activateAction; }; } // namespace #endif diff --git a/effects/desktopgrid/desktopgrid_config.cpp b/effects/desktopgrid/desktopgrid_config.cpp index 2d8e917a9..ab5525d8f 100644 --- a/effects/desktopgrid/desktopgrid_config.cpp +++ b/effects/desktopgrid/desktopgrid_config.cpp @@ -1,140 +1,140 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2007 Rivo Laks Copyright (C) 2008 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 . *********************************************************************/ #include "desktopgrid_config.h" // KConfigSkeleton #include "desktopgridconfig.h" #include #include #include #include #include #include #include #include #include #include K_PLUGIN_FACTORY_WITH_JSON(DesktopGridEffectConfigFactory, "desktopgrid_config.json", registerPlugin();) namespace KWin { DesktopGridEffectConfigForm::DesktopGridEffectConfigForm(QWidget* parent) : QWidget(parent) { setupUi(this); } DesktopGridEffectConfig::DesktopGridEffectConfig(QWidget* parent, const QVariantList& args) : KCModule(KAboutData::pluginData(QStringLiteral("desktopgrid")), parent, args) { m_ui = new DesktopGridEffectConfigForm(this); QVBoxLayout* layout = new QVBoxLayout(this); layout->addWidget(m_ui); // Shortcut config. The shortcut belongs to the component "kwin"! m_actionCollection = new KActionCollection(this, QStringLiteral("kwin")); m_actionCollection->setComponentDisplayName(i18n("KWin")); m_actionCollection->setConfigGroup(QStringLiteral("DesktopGrid")); m_actionCollection->setConfigGlobal(true); QAction* a = m_actionCollection->addAction(QStringLiteral("ShowDesktopGrid")); a->setText(i18n("Show Desktop Grid")); a->setProperty("isConfigurationAction", true); KGlobalAccel::self()->setDefaultShortcut(a, QList() << Qt::CTRL + Qt::Key_F8); KGlobalAccel::self()->setShortcut(a, QList() << Qt::CTRL + Qt::Key_F8); m_ui->shortcutEditor->addCollection(m_actionCollection); - m_ui->desktopNameAlignmentCombo->addItem(i18nc("Desktop name alignment:", "Disabled"), QVariant(Qt::Alignment(0))); + m_ui->desktopNameAlignmentCombo->addItem(i18nc("Desktop name alignment:", "Disabled"), QVariant(Qt::Alignment(nullptr))); m_ui->desktopNameAlignmentCombo->addItem(i18n("Top"), QVariant(Qt::AlignHCenter | Qt::AlignTop)); m_ui->desktopNameAlignmentCombo->addItem(i18n("Top-Right"), QVariant(Qt::AlignRight | Qt::AlignTop)); m_ui->desktopNameAlignmentCombo->addItem(i18n("Right"), QVariant(Qt::AlignRight | Qt::AlignVCenter)); m_ui->desktopNameAlignmentCombo->addItem(i18n("Bottom-Right"), QVariant(Qt::AlignRight | Qt::AlignBottom)); m_ui->desktopNameAlignmentCombo->addItem(i18n("Bottom"), QVariant(Qt::AlignHCenter | Qt::AlignBottom)); m_ui->desktopNameAlignmentCombo->addItem(i18n("Bottom-Left"), QVariant(Qt::AlignLeft | Qt::AlignBottom)); m_ui->desktopNameAlignmentCombo->addItem(i18n("Left"), QVariant(Qt::AlignLeft | Qt::AlignVCenter)); m_ui->desktopNameAlignmentCombo->addItem(i18n("Top-Left"), QVariant(Qt::AlignLeft | Qt::AlignTop)); m_ui->desktopNameAlignmentCombo->addItem(i18n("Center"), QVariant(Qt::AlignCenter)); DesktopGridConfig::instance(KWIN_CONFIG); addConfig(DesktopGridConfig::self(), m_ui); connect(m_ui->kcfg_LayoutMode, qOverload(&KComboBox::currentIndexChanged), this, &DesktopGridEffectConfig::layoutSelectionChanged); connect(m_ui->desktopNameAlignmentCombo, qOverload(&KComboBox::currentIndexChanged), this, qOverload<>(&DesktopGridEffectConfig::changed)); connect(m_ui->shortcutEditor, &KShortcutsEditor::keyChange, this, qOverload<>(&DesktopGridEffectConfig::changed)); load(); layoutSelectionChanged(); } DesktopGridEffectConfig::~DesktopGridEffectConfig() { // If save() is called undoChanges() has no effect m_ui->shortcutEditor->undoChanges(); } void DesktopGridEffectConfig::save() { m_ui->shortcutEditor->save(); DesktopGridConfig::setDesktopNameAlignment(m_ui->desktopNameAlignmentCombo->itemData(m_ui->desktopNameAlignmentCombo->currentIndex()).toInt()); KCModule::save(); OrgKdeKwinEffectsInterface interface(QStringLiteral("org.kde.KWin"), QStringLiteral("/Effects"), QDBusConnection::sessionBus()); interface.reconfigureEffect(QStringLiteral("desktopgrid")); } void DesktopGridEffectConfig::load() { KCModule::load(); m_ui->desktopNameAlignmentCombo->setCurrentIndex(m_ui->desktopNameAlignmentCombo->findData(QVariant(DesktopGridConfig::desktopNameAlignment()))); } void DesktopGridEffectConfig::layoutSelectionChanged() { if (m_ui->kcfg_LayoutMode->currentIndex() == DesktopGridEffect::LayoutCustom) { m_ui->layoutRowsLabel->setEnabled(true); m_ui->kcfg_CustomLayoutRows->setEnabled(true); } else { m_ui->layoutRowsLabel->setEnabled(false); m_ui->kcfg_CustomLayoutRows->setEnabled(false); } } void DesktopGridEffectConfig::defaults() { KCModule::defaults(); m_ui->desktopNameAlignmentCombo->setCurrentIndex(0); } } // namespace #include "desktopgrid_config.moc" diff --git a/effects/desktopgrid/desktopgrid_config.h b/effects/desktopgrid/desktopgrid_config.h index 1654d3623..616729388 100644 --- a/effects/desktopgrid/desktopgrid_config.h +++ b/effects/desktopgrid/desktopgrid_config.h @@ -1,62 +1,62 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2007 Rivo Laks Copyright (C) 2008 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 . *********************************************************************/ #ifndef KWIN_DESKTOPGRID_CONFIG_H #define KWIN_DESKTOPGRID_CONFIG_H #include #include "ui_desktopgrid_config.h" #include "desktopgrid.h" namespace KWin { class DesktopGridEffectConfigForm : public QWidget, public Ui::DesktopGridEffectConfigForm { Q_OBJECT public: explicit DesktopGridEffectConfigForm(QWidget* parent); }; class DesktopGridEffectConfig : public KCModule { Q_OBJECT public: - explicit DesktopGridEffectConfig(QWidget* parent = 0, const QVariantList& args = QVariantList()); + explicit DesktopGridEffectConfig(QWidget* parent = nullptr, const QVariantList& args = QVariantList()); ~DesktopGridEffectConfig() override; public Q_SLOTS: void save() override; void load() override; void defaults() override; private Q_SLOTS: void layoutSelectionChanged(); private: DesktopGridEffectConfigForm* m_ui; KActionCollection* m_actionCollection; }; } // namespace #endif diff --git a/effects/flipswitch/flipswitch.cpp b/effects/flipswitch/flipswitch.cpp index 15b7446fd..38aedd98d 100644 --- a/effects/flipswitch/flipswitch.cpp +++ b/effects/flipswitch/flipswitch.cpp @@ -1,990 +1,990 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2008, 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 . *********************************************************************/ #include "flipswitch.h" // KConfigSkeleton #include "flipswitchconfig.h" #include #include #include #include #include #include #include #include #include namespace KWin { FlipSwitchEffect::FlipSwitchEffect() : m_selectedWindow(nullptr) , m_currentAnimationShape(QTimeLine::EaseInOutCurve) , m_active(false) , m_start(false) , m_stop(false) , m_animation(false) , m_hasKeyboardGrab(false) - , m_captionFrame(NULL) + , m_captionFrame(nullptr) { initConfig(); reconfigure(ReconfigureAll); // Caption frame m_captionFont.setBold(true); m_captionFont.setPointSize(m_captionFont.pointSize() * 2); QAction* flipSwitchCurrentAction = new QAction(this); flipSwitchCurrentAction->setObjectName(QStringLiteral("FlipSwitchCurrent")); flipSwitchCurrentAction->setText(i18n("Toggle Flip Switch (Current desktop)")); KGlobalAccel::self()->setShortcut(flipSwitchCurrentAction, QList()); m_shortcutCurrent = KGlobalAccel::self()->shortcut(flipSwitchCurrentAction); effects->registerGlobalShortcut(QKeySequence(), flipSwitchCurrentAction); connect(flipSwitchCurrentAction, &QAction::triggered, this, &FlipSwitchEffect::toggleActiveCurrent); QAction* flipSwitchAllAction = new QAction(this); flipSwitchAllAction->setObjectName(QStringLiteral("FlipSwitchAll")); flipSwitchAllAction->setText(i18n("Toggle Flip Switch (All desktops)")); KGlobalAccel::self()->setShortcut(flipSwitchAllAction, QList()); effects->registerGlobalShortcut(QKeySequence(), flipSwitchAllAction); m_shortcutAll = KGlobalAccel::self()->shortcut(flipSwitchAllAction); connect(flipSwitchAllAction, &QAction::triggered, this, &FlipSwitchEffect::toggleActiveAllDesktops); connect(KGlobalAccel::self(), &KGlobalAccel::globalShortcutChanged, this, &FlipSwitchEffect::globalShortcutChanged); connect(effects, &EffectsHandler::windowAdded, this, &FlipSwitchEffect::slotWindowAdded); connect(effects, &EffectsHandler::windowClosed, this, &FlipSwitchEffect::slotWindowClosed); connect(effects, &EffectsHandler::tabBoxAdded, this, &FlipSwitchEffect::slotTabBoxAdded); connect(effects, &EffectsHandler::tabBoxClosed, this, &FlipSwitchEffect::slotTabBoxClosed); connect(effects, &EffectsHandler::tabBoxUpdated, this, &FlipSwitchEffect::slotTabBoxUpdated); connect(effects, &EffectsHandler::tabBoxKeyEvent, this, &FlipSwitchEffect::slotTabBoxKeyEvent); connect(effects, &EffectsHandler::screenAboutToLock, this, [this]() { setActive(false, AllDesktopsMode); setActive(false, CurrentDesktopMode); }); } FlipSwitchEffect::~FlipSwitchEffect() { delete m_captionFrame; } bool FlipSwitchEffect::supported() { return effects->isOpenGLCompositing() && effects->animationsSupported(); } void FlipSwitchEffect::reconfigure(ReconfigureFlags) { FlipSwitchConfig::self()->read(); m_tabbox = FlipSwitchConfig::tabBox(); m_tabboxAlternative = FlipSwitchConfig::tabBoxAlternative(); const int duration = animationTime(200); m_timeLine.setDuration(duration); m_startStopTimeLine.setDuration(duration); m_angle = FlipSwitchConfig::angle(); m_xPosition = FlipSwitchConfig::xPosition() / 100.0f; m_yPosition = FlipSwitchConfig::yPosition() / 100.0f; m_windowTitle = FlipSwitchConfig::windowTitle(); } void FlipSwitchEffect::prePaintScreen(ScreenPrePaintData& data, int time) { if (m_active) { data.mask |= PAINT_SCREEN_WITH_TRANSFORMED_WINDOWS; if (m_start) m_startStopTimeLine.setCurrentTime(m_startStopTimeLine.currentTime() + time); if (m_stop && m_scheduledDirections.isEmpty()) m_startStopTimeLine.setCurrentTime(m_startStopTimeLine.currentTime() - time); if (m_animation) m_timeLine.setCurrentTime(m_timeLine.currentTime() + time); } effects->prePaintScreen(data, time); } void FlipSwitchEffect::paintScreen(int mask, QRegion region, ScreenPaintData& data) { effects->paintScreen(mask, region, data); if (m_active) { EffectWindowList tempList; if (m_mode == TabboxMode) tempList = effects->currentTabBoxWindowList(); else { // we have to setup the list // using stacking order directly is not possible // as not each window in stacking order is shown // TODO: store list instead of calculating in each frame? foreach (EffectWindow * w, effects->stackingOrder()) { if (m_windows.contains(w)) tempList.append(w); } } m_flipOrderedWindows.clear(); int index = tempList.indexOf(m_selectedWindow); int tabIndex = index; if (m_mode == TabboxMode) { foreach (SwitchingDirection direction, m_scheduledDirections) { // krazy:exclude=foreach if (direction == DirectionBackward) index++; else index--; if (index < 0) index = tempList.count() + index; if (index >= tempList.count()) index = index % tempList.count(); } tabIndex = index; - EffectWindow* w = NULL; + EffectWindow* w = nullptr; if (!m_scheduledDirections.isEmpty() && m_scheduledDirections.head() == DirectionBackward) { index--; if (index < 0) index = tempList.count() + index; w = tempList.at(index); } for (int i = index - 1; i >= 0; i--) m_flipOrderedWindows.append(tempList.at(i)); for (int i = effects->currentTabBoxWindowList().count() - 1; i >= index; i--) m_flipOrderedWindows.append(tempList.at(i)); if (w) { m_flipOrderedWindows.removeAll(w); m_flipOrderedWindows.append(w); } } else { foreach (SwitchingDirection direction, m_scheduledDirections) { // krazy:exclude=foreach if (direction == DirectionForward) index++; else index--; if (index < 0) index = tempList.count() - 1; if (index >= tempList.count()) index = 0; } tabIndex = index; - EffectWindow* w = NULL; + EffectWindow* w = nullptr; if (!m_scheduledDirections.isEmpty() && m_scheduledDirections.head() == DirectionBackward) { index++; if (index >= tempList.count()) index = 0; } // sort from stacking order for (int i = index + 1; i < tempList.count(); i++) m_flipOrderedWindows.append(tempList.at(i)); for (int i = 0; i <= index; i++) m_flipOrderedWindows.append(tempList.at(i)); if (w) { m_flipOrderedWindows.removeAll(w); m_flipOrderedWindows.append(w); } } int winMask = PAINT_WINDOW_TRANSFORMED | PAINT_WINDOW_TRANSLUCENT; // fade in/out one window at the end of the stack during animation if (m_animation && !m_scheduledDirections.isEmpty()) { EffectWindow* w = m_flipOrderedWindows.last(); if (ItemInfo *info = m_windows.value(w,0)) { WindowPaintData data(w); if (effects->numScreens() > 1) { data.setProjectionMatrix(m_projectionMatrix); data.setModelViewMatrix(m_modelviewMatrix); } data.setRotationAxis(Qt::YAxis); data.setRotationAngle(m_angle * m_startStopTimeLine.currentValue()); data.setOpacity(info->opacity); data.setBrightness(info->brightness); data.setSaturation(info->saturation); int distance = tempList.count() - 1; float zDistance = 500.0f; data.translate(- (w->x() - m_screenArea.x() + data.xTranslation()) * m_startStopTimeLine.currentValue()); data.translate(m_screenArea.width() * m_xPosition * m_startStopTimeLine.currentValue(), (m_screenArea.y() + m_screenArea.height() * m_yPosition - (w->y() + w->height() + data.yTranslation())) * m_startStopTimeLine.currentValue()); data.translate(- (m_screenArea.width() * 0.25f) * distance * m_startStopTimeLine.currentValue(), - (m_screenArea.height() * 0.10f) * distance * m_startStopTimeLine.currentValue(), - (zDistance * distance) * m_startStopTimeLine.currentValue()); if (m_scheduledDirections.head() == DirectionForward) data.multiplyOpacity(0.8 * m_timeLine.currentValue()); else data.multiplyOpacity(0.8 * (1.0 - m_timeLine.currentValue())); if (effects->numScreens() > 1) { adjustWindowMultiScreen(w, data); } effects->drawWindow(w, winMask, infiniteRegion(), data); } } foreach (EffectWindow *w, m_flipOrderedWindows) { ItemInfo *info = m_windows.value(w,0); if (!info) continue; WindowPaintData data(w); if (effects->numScreens() > 1) { data.setProjectionMatrix(m_projectionMatrix); data.setModelViewMatrix(m_modelviewMatrix); } data.setRotationAxis(Qt::YAxis); data.setRotationAngle(m_angle * m_startStopTimeLine.currentValue()); data.setOpacity(info->opacity); data.setBrightness(info->brightness); data.setSaturation(info->saturation); int windowIndex = tempList.indexOf(w); int distance; if (m_mode == TabboxMode) { if (windowIndex < tabIndex) distance = tempList.count() - (tabIndex - windowIndex); else if (windowIndex > tabIndex) distance = windowIndex - tabIndex; else distance = 0; } else { distance = m_flipOrderedWindows.count() - m_flipOrderedWindows.indexOf(w) - 1; if (!m_scheduledDirections.isEmpty() && m_scheduledDirections.head() == DirectionBackward) { distance--; } } if (!m_scheduledDirections.isEmpty() && m_scheduledDirections.head() == DirectionBackward) { if (w == m_flipOrderedWindows.last()) { distance = -1; data.multiplyOpacity(m_timeLine.currentValue()); } } float zDistance = 500.0f; data.translate(- (w->x() - m_screenArea.x() + data.xTranslation()) * m_startStopTimeLine.currentValue()); data.translate(m_screenArea.width() * m_xPosition * m_startStopTimeLine.currentValue(), (m_screenArea.y() + m_screenArea.height() * m_yPosition - (w->y() + w->height() + data.yTranslation())) * m_startStopTimeLine.currentValue()); data.translate(-(m_screenArea.width() * 0.25f) * distance * m_startStopTimeLine.currentValue(), -(m_screenArea.height() * 0.10f) * distance * m_startStopTimeLine.currentValue(), -(zDistance * distance) * m_startStopTimeLine.currentValue()); if (m_animation && !m_scheduledDirections.isEmpty()) { if (m_scheduledDirections.head() == DirectionForward) { data.translate((m_screenArea.width() * 0.25f) * m_timeLine.currentValue(), (m_screenArea.height() * 0.10f) * m_timeLine.currentValue(), zDistance * m_timeLine.currentValue()); if (distance == 0) data.multiplyOpacity((1.0 - m_timeLine.currentValue())); } else { data.translate(- (m_screenArea.width() * 0.25f) * m_timeLine.currentValue(), - (m_screenArea.height() * 0.10f) * m_timeLine.currentValue(), - zDistance * m_timeLine.currentValue()); } } data.multiplyOpacity((0.8 + 0.2 * (1.0 - m_startStopTimeLine.currentValue()))); if (effects->numScreens() > 1) { adjustWindowMultiScreen(w, data); } effects->drawWindow(w, winMask, infiniteRegion(), data); } if (m_windowTitle) { // Render the caption frame if (m_animation) { m_captionFrame->setCrossFadeProgress(m_timeLine.currentValue()); } m_captionFrame->render(region, m_startStopTimeLine.currentValue()); } } } void FlipSwitchEffect::postPaintScreen() { if (m_active) { if (m_start && m_startStopTimeLine.currentValue() == 1.0f) { m_start = false; if (!m_scheduledDirections.isEmpty()) { m_animation = true; m_timeLine.setCurrentTime(0); if (m_scheduledDirections.count() == 1) { m_currentAnimationShape = QTimeLine::EaseOutCurve; m_timeLine.setCurveShape(m_currentAnimationShape); } else { m_currentAnimationShape = QTimeLine::LinearCurve; m_timeLine.setCurveShape(m_currentAnimationShape); } } effects->addRepaintFull(); } if (m_stop && m_startStopTimeLine.currentValue() == 0.0f) { m_stop = false; m_active = false; m_captionFrame->free(); - effects->setActiveFullScreenEffect(0); + effects->setActiveFullScreenEffect(nullptr); effects->addRepaintFull(); qDeleteAll(m_windows); m_windows.clear(); } if (m_animation && m_timeLine.currentValue() == 1.0f) { m_timeLine.setCurrentTime(0); m_scheduledDirections.dequeue(); if (m_scheduledDirections.isEmpty()) { m_animation = false; effects->addRepaintFull(); } else { if (m_scheduledDirections.count() == 1) { if (m_stop) m_currentAnimationShape = QTimeLine::LinearCurve; else m_currentAnimationShape = QTimeLine::EaseOutCurve; } else { m_currentAnimationShape = QTimeLine::LinearCurve; } m_timeLine.setCurveShape(m_currentAnimationShape); } } if (m_start || m_stop || m_animation) effects->addRepaintFull(); } effects->postPaintScreen(); } void FlipSwitchEffect::prePaintWindow(EffectWindow* w, WindowPrePaintData& data, int time) { if (m_active) { if (m_windows.contains(w)) { data.setTransformed(); data.setTranslucent(); if (!w->isOnCurrentDesktop()) w->enablePainting(EffectWindow::PAINT_DISABLED_BY_DESKTOP); if (w->isMinimized()) w->enablePainting(EffectWindow::PAINT_DISABLED_BY_MINIMIZE); } else { if ((m_start || m_stop) && !w->isDesktop() && w->isOnCurrentDesktop()) data.setTranslucent(); else if (!w->isDesktop()) w->disablePainting(EffectWindow::PAINT_DISABLED_BY_DESKTOP); } } effects->prePaintWindow(w, data, time); } void FlipSwitchEffect::paintWindow(EffectWindow* w, int mask, QRegion region, WindowPaintData& data) { if (m_active) { ItemInfo *info = m_windows.value(w,0); if (info) { info->opacity = data.opacity(); info->brightness = data.brightness(); info->saturation = data.saturation(); } // fade out all windows not in window list except the desktops const bool isFader = (m_start || m_stop) && !info && !w->isDesktop(); if (isFader) data.multiplyOpacity((1.0 - m_startStopTimeLine.currentValue())); // if not a fader or the desktop, skip painting here to prevent flicker if (!(isFader || w->isDesktop())) return; } effects->paintWindow(w, mask, region, data); } //************************************************************* // Tabbox handling //************************************************************* void FlipSwitchEffect::slotTabBoxAdded(int mode) { if (effects->activeFullScreenEffect() && effects->activeFullScreenEffect() != this) return; // only for windows mode effects->setShowingDesktop(false); if (((mode == TabBoxWindowsMode && m_tabbox) || (mode == TabBoxWindowsAlternativeMode && m_tabboxAlternative) || (mode == TabBoxCurrentAppWindowsMode && m_tabbox) || (mode == TabBoxCurrentAppWindowsAlternativeMode && m_tabboxAlternative)) && (!m_active || (m_active && m_stop)) && !effects->currentTabBoxWindowList().isEmpty()) { setActive(true, TabboxMode); if (m_active) effects->refTabBox(); } } void FlipSwitchEffect::slotTabBoxClosed() { if (m_active) { setActive(false, TabboxMode); effects->unrefTabBox(); } } void FlipSwitchEffect::slotTabBoxUpdated() { if (m_active && !m_stop) { if (!effects->currentTabBoxWindowList().isEmpty()) { // determine the switch direction if (m_selectedWindow != effects->currentTabBoxWindow()) { - if (m_selectedWindow != NULL) { + if (m_selectedWindow != nullptr) { int old_index = effects->currentTabBoxWindowList().indexOf(m_selectedWindow); int new_index = effects->currentTabBoxWindowList().indexOf(effects->currentTabBoxWindow()); SwitchingDirection new_direction; int distance = new_index - old_index; if (distance > 0) new_direction = DirectionForward; if (distance < 0) new_direction = DirectionBackward; if (effects->currentTabBoxWindowList().count() == 2) { new_direction = DirectionForward; distance = 1; } if (distance != 0) { distance = abs(distance); int tempDistance = effects->currentTabBoxWindowList().count() - distance; if (tempDistance < abs(distance)) { distance = tempDistance; if (new_direction == DirectionForward) new_direction = DirectionBackward; else new_direction = DirectionForward; } scheduleAnimation(new_direction, distance); } } m_selectedWindow = effects->currentTabBoxWindow(); updateCaption(); } } effects->addRepaintFull(); } } //************************************************************* // Window adding/removing handling //************************************************************* void FlipSwitchEffect::slotWindowAdded(EffectWindow* w) { if (m_active && isSelectableWindow(w)) { m_windows[ w ] = new ItemInfo; } } void FlipSwitchEffect::slotWindowClosed(EffectWindow* w) { if (m_selectedWindow == w) - m_selectedWindow = 0; + m_selectedWindow = nullptr; if (m_active) { QHash< const EffectWindow*, ItemInfo* >::iterator it = m_windows.find(w); if (it != m_windows.end()) { delete *it; m_windows.erase(it); } } } //************************************************************* // Activation //************************************************************* void FlipSwitchEffect::setActive(bool activate, FlipSwitchMode mode) { if (activate) { // effect already active, do some sanity checks if (m_active) { if (m_stop) { if (mode != m_mode) { // only the same mode may reactivate the effect return; } } else { // active, but not scheduled for closing -> abort return; } } m_mode = mode; foreach (EffectWindow * w, effects->stackingOrder()) { if (isSelectableWindow(w) && !m_windows.contains(w)) m_windows[ w ] = new ItemInfo; } if (m_windows.isEmpty()) return; effects->setActiveFullScreenEffect(this); m_active = true; m_start = true; m_startStopTimeLine.setCurveShape(QTimeLine::EaseInOutCurve); m_activeScreen = effects->activeScreen(); m_screenArea = effects->clientArea(ScreenArea, m_activeScreen, effects->currentDesktop()); if (effects->numScreens() > 1) { // unfortunatelly we have to change the projection matrix in dual screen mode // code is copied from Coverswitch QRect fullRect = effects->clientArea(FullArea, m_activeScreen, effects->currentDesktop()); float fovy = 60.0f; float aspect = 1.0f; float zNear = 0.1f; float zFar = 100.0f; float ymax = zNear * std::tan(fovy * M_PI / 360.0f); float ymin = -ymax; float xmin = ymin * aspect; float xmax = ymax * aspect; if (m_screenArea.width() != fullRect.width()) { if (m_screenArea.x() == 0) { // horizontal layout: left screen xmin *= (float)m_screenArea.width() / (float)fullRect.width(); xmax *= (fullRect.width() - 0.5f * m_screenArea.width()) / (0.5f * fullRect.width()); } else { // horizontal layout: right screen xmin *= (fullRect.width() - 0.5f * m_screenArea.width()) / (0.5f * fullRect.width()); xmax *= (float)m_screenArea.width() / (float)fullRect.width(); } } if (m_screenArea.height() != fullRect.height()) { if (m_screenArea.y() == 0) { // vertical layout: top screen ymin *= (fullRect.height() - 0.5f * m_screenArea.height()) / (0.5f * fullRect.height()); ymax *= (float)m_screenArea.height() / (float)fullRect.height(); } else { // vertical layout: bottom screen ymin *= (float)m_screenArea.height() / (float)fullRect.height(); ymax *= (fullRect.height() - 0.5f * m_screenArea.height()) / (0.5f * fullRect.height()); } } m_projectionMatrix = QMatrix4x4(); m_projectionMatrix.frustum(xmin, xmax, ymin, ymax, zNear, zFar); const float scaleFactor = 1.1f / zNear; // Create a second matrix that transforms screen coordinates // to world coordinates. QMatrix4x4 matrix; matrix.translate(xmin * scaleFactor, ymax * scaleFactor, -1.1); matrix.scale( (xmax - xmin) * scaleFactor / fullRect.width(), -(ymax - ymin) * scaleFactor / fullRect.height(), 0.001); // Combine the matrices m_projectionMatrix *= matrix; m_modelviewMatrix = QMatrix4x4(); m_modelviewMatrix.translate(m_screenArea.x(), m_screenArea.y(), 0.0); } if (m_stop) { // effect is still closing from last usage m_stop = false; } else { // things to do only when there is no closing animation m_scheduledDirections.clear(); } switch(m_mode) { case TabboxMode: m_selectedWindow = effects->currentTabBoxWindow(); effects->startMouseInterception(this, Qt::ArrowCursor); break; case CurrentDesktopMode: m_selectedWindow = effects->activeWindow(); effects->startMouseInterception(this, Qt::BlankCursor); m_hasKeyboardGrab = effects->grabKeyboard(this); break; case AllDesktopsMode: m_selectedWindow = effects->activeWindow(); effects->startMouseInterception(this, Qt::BlankCursor); m_hasKeyboardGrab = effects->grabKeyboard(this); break; } // Setup caption frame geometry QRect frameRect = QRect(m_screenArea.width() * 0.25f + m_screenArea.x(), m_screenArea.height() * 0.1f + m_screenArea.y() - QFontMetrics(m_captionFont).height(), m_screenArea.width() * 0.5f, QFontMetrics(m_captionFont).height()); if (!m_captionFrame) { m_captionFrame = effects->effectFrame(EffectFrameStyled); m_captionFrame->setFont(m_captionFont); m_captionFrame->enableCrossFade(true); } m_captionFrame->setGeometry(frameRect); m_captionFrame->setIconSize(QSize(frameRect.height(), frameRect.height())); updateCaption(); effects->addRepaintFull(); } else { // only deactivate if mode is current mode if (mode != m_mode) return; if (m_start && m_scheduledDirections.isEmpty()) { m_start = false; } m_stop = true; if (m_animation) { m_startStopTimeLine.setCurveShape(QTimeLine::EaseOutCurve); if (m_scheduledDirections.count() == 1) { if (m_currentAnimationShape == QTimeLine::EaseInOutCurve) m_currentAnimationShape = QTimeLine::EaseInCurve; else if (m_currentAnimationShape == QTimeLine::EaseOutCurve) m_currentAnimationShape = QTimeLine::LinearCurve; m_timeLine.setCurveShape(m_currentAnimationShape); } } else m_startStopTimeLine.setCurveShape(QTimeLine::EaseInOutCurve); effects->stopMouseInterception(this); if (m_hasKeyboardGrab) { effects->ungrabKeyboard(); m_hasKeyboardGrab = false; } effects->addRepaintFull(); } } void FlipSwitchEffect::toggleActiveAllDesktops() { if (m_active) { if (m_stop) { // reactivate if stopping setActive(true, AllDesktopsMode); } else { // deactivate if not stopping setActive(false, AllDesktopsMode); } } else { setActive(true, AllDesktopsMode); } } void FlipSwitchEffect::toggleActiveCurrent() { if (m_active) { if (m_stop) { // reactivate if stopping setActive(true, CurrentDesktopMode); } else { // deactivate if not stopping setActive(false, CurrentDesktopMode); } } else { setActive(true, CurrentDesktopMode); } } //************************************************************* // Helper function //************************************************************* bool FlipSwitchEffect::isSelectableWindow(EffectWindow* w) const { // desktop windows might be included if ((w->isSpecialWindow() && !w->isDesktop()) || w->isUtility()) return false; if (w->isDesktop()) return (m_mode == TabboxMode && effects->currentTabBoxWindowList().contains(w)); if (w->isDeleted()) return false; if (!w->acceptsFocus()) return false; switch(m_mode) { case TabboxMode: return effects->currentTabBoxWindowList().contains(w); case CurrentDesktopMode: return w->isOnCurrentDesktop(); case AllDesktopsMode: //nothing special break; } return true; } void FlipSwitchEffect::scheduleAnimation(const SwitchingDirection& direction, int distance) { if (m_start) { // start is still active so change the shape to have a nice transition m_startStopTimeLine.setCurveShape(QTimeLine::EaseInCurve); } if (!m_animation && !m_start) { m_animation = true; m_scheduledDirections.enqueue(direction); distance--; // reset shape just to make sure m_currentAnimationShape = QTimeLine::EaseInOutCurve; m_timeLine.setCurveShape(m_currentAnimationShape); } for (int i = 0; i < distance; i++) { if (m_scheduledDirections.count() > 1 && m_scheduledDirections.last() != direction) m_scheduledDirections.pop_back(); else m_scheduledDirections.enqueue(direction); if (m_scheduledDirections.count() == m_windows.count() + 1) { SwitchingDirection temp = m_scheduledDirections.dequeue(); m_scheduledDirections.clear(); m_scheduledDirections.enqueue(temp); } } if (m_scheduledDirections.count() > 1) { QTimeLine::CurveShape newShape = QTimeLine::EaseInOutCurve; switch(m_currentAnimationShape) { case QTimeLine::EaseInOutCurve: newShape = QTimeLine::EaseInCurve; break; case QTimeLine::EaseOutCurve: newShape = QTimeLine::LinearCurve; break; default: newShape = m_currentAnimationShape; } if (newShape != m_currentAnimationShape) { m_currentAnimationShape = newShape; m_timeLine.setCurveShape(m_currentAnimationShape); } } } void FlipSwitchEffect::adjustWindowMultiScreen(const KWin::EffectWindow* w, WindowPaintData& data) { if (effects->numScreens() <= 1) return; QRect clientRect = effects->clientArea(FullScreenArea, w->screen(), effects->currentDesktop()); QRect rect = effects->clientArea(ScreenArea, m_activeScreen, effects->currentDesktop()); QRect fullRect = effects->clientArea(FullArea, m_activeScreen, effects->currentDesktop()); if (w->screen() == m_activeScreen) { if (clientRect.width() != fullRect.width() && clientRect.x() != fullRect.x()) { data.translate(- clientRect.x()); } if (clientRect.height() != fullRect.height() && clientRect.y() != fullRect.y()) { data.translate(0.0, - clientRect.y()); } } else { if (clientRect.width() != fullRect.width() && clientRect.x() < rect.x()) { data.translate(- (m_screenArea.x() - clientRect.x())); } if (clientRect.height() != fullRect.height() && clientRect.y() < m_screenArea.y()) { data.translate(0.0, - (m_screenArea.y() - clientRect.y())); } } } void FlipSwitchEffect::selectNextOrPreviousWindow(bool forward) { if (!m_active || !m_selectedWindow) { return; } const int index = effects->currentTabBoxWindowList().indexOf(m_selectedWindow); int newIndex = index; if (forward) { ++newIndex; } else { --newIndex; } if (newIndex == effects->currentTabBoxWindowList().size()) { newIndex = 0; } else if (newIndex < 0) { newIndex = effects->currentTabBoxWindowList().size() -1; } if (index == newIndex) { return; } effects->setTabBoxWindow(effects->currentTabBoxWindowList().at(newIndex)); } //************************************************************* // Keyboard handling //************************************************************* void FlipSwitchEffect::globalShortcutChanged(QAction *action, QKeySequence shortcut) { if (action->objectName() == QStringLiteral("FlipSwitchAll")) { m_shortcutAll.clear(); m_shortcutAll.append(shortcut); } else if (action->objectName() == QStringLiteral("FlipSwitchCurrent")) { m_shortcutCurrent.clear(); m_shortcutCurrent.append(shortcut); } } void FlipSwitchEffect::grabbedKeyboardEvent(QKeyEvent* e) { if (e->type() == QEvent::KeyPress) { // check for global shortcuts // HACK: keyboard grab disables the global shortcuts so we have to check for global shortcut (bug 156155) if (m_mode == CurrentDesktopMode && m_shortcutCurrent.contains(e->key() + e->modifiers())) { toggleActiveCurrent(); return; } if (m_mode == AllDesktopsMode && m_shortcutAll.contains(e->key() + e->modifiers())) { toggleActiveAllDesktops(); return; } switch(e->key()) { case Qt::Key_Escape: setActive(false, m_mode); return; case Qt::Key_Tab: { // find next window if (m_windows.isEmpty()) return; // sanity check bool found = false; for (int i = effects->stackingOrder().indexOf(m_selectedWindow) - 1; i >= 0; i--) { if (isSelectableWindow(effects->stackingOrder().at(i))) { m_selectedWindow = effects->stackingOrder().at(i); found = true; break; } } if (!found) { for (int i = effects->stackingOrder().count() - 1; i > effects->stackingOrder().indexOf(m_selectedWindow); i--) { if (isSelectableWindow(effects->stackingOrder().at(i))) { m_selectedWindow = effects->stackingOrder().at(i); found = true; break; } } } if (found) { updateCaption(); scheduleAnimation(DirectionForward); } break; } case Qt::Key_Backtab: { // find previous window if (m_windows.isEmpty()) return; // sanity check bool found = false; for (int i = effects->stackingOrder().indexOf(m_selectedWindow) + 1; i < effects->stackingOrder().count(); i++) { if (isSelectableWindow(effects->stackingOrder().at(i))) { m_selectedWindow = effects->stackingOrder().at(i); found = true; break; } } if (!found) { for (int i = 0; i < effects->stackingOrder().indexOf(m_selectedWindow); i++) { if (isSelectableWindow(effects->stackingOrder().at(i))) { m_selectedWindow = effects->stackingOrder().at(i); found = true; break; } } } if (found) { updateCaption(); scheduleAnimation(DirectionBackward); } break; } case Qt::Key_Return: case Qt::Key_Enter: case Qt::Key_Space: if (m_selectedWindow) effects->activateWindow(m_selectedWindow); setActive(false, m_mode); break; default: break; } effects->addRepaintFull(); } } void FlipSwitchEffect::slotTabBoxKeyEvent(QKeyEvent *event) { if (event->type() == QEvent::KeyPress) { switch (event->key()) { case Qt::Key_Up: case Qt::Key_Left: selectPreviousWindow(); break; case Qt::Key_Down: case Qt::Key_Right: selectNextWindow(); break; default: // nothing break; } } } bool FlipSwitchEffect::isActive() const { return m_active && !effects->isScreenLocked(); } void FlipSwitchEffect::updateCaption() { if (!m_selectedWindow) { return; } if (m_selectedWindow->isDesktop()) { m_captionFrame->setText(i18nc("Special entry in alt+tab list for minimizing all windows", "Show Desktop")); static QPixmap pix = QIcon::fromTheme(QStringLiteral("user-desktop")).pixmap(m_captionFrame->iconSize()); m_captionFrame->setIcon(pix); } else { m_captionFrame->setText(m_selectedWindow->caption()); m_captionFrame->setIcon(m_selectedWindow->icon()); } } //************************************************************* // Mouse handling //************************************************************* void FlipSwitchEffect::windowInputMouseEvent(QEvent* e) { if (e->type() != QEvent::MouseButtonPress) return; // we don't want click events during animations if (m_animation) return; QMouseEvent* event = static_cast< QMouseEvent* >(e); switch (event->button()) { case Qt::XButton1: // wheel up selectPreviousWindow(); break; case Qt::XButton2: // wheel down selectNextWindow(); break; case Qt::LeftButton: case Qt::RightButton: case Qt::MidButton: default: // TODO: Change window on mouse button click break; } } //************************************************************* // Item Info //************************************************************* FlipSwitchEffect::ItemInfo::ItemInfo() : deleted(false) , opacity(0.0) , brightness(0.0) , saturation(0.0) { } FlipSwitchEffect::ItemInfo::~ItemInfo() { } } // namespace diff --git a/effects/flipswitch/flipswitch_config.h b/effects/flipswitch/flipswitch_config.h index 27252e474..317763730 100644 --- a/effects/flipswitch/flipswitch_config.h +++ b/effects/flipswitch/flipswitch_config.h @@ -1,57 +1,57 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2008, 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 . *********************************************************************/ #ifndef KWIN_FLIPSWITCH_CONFIG_H #define KWIN_FLIPSWITCH_CONFIG_H #include #include "ui_flipswitch_config.h" class KActionCollection; namespace KWin { class FlipSwitchEffectConfigForm : public QWidget, public Ui::FlipSwitchEffectConfigForm { Q_OBJECT public: explicit FlipSwitchEffectConfigForm(QWidget* parent); }; class FlipSwitchEffectConfig : public KCModule { Q_OBJECT public: - explicit FlipSwitchEffectConfig(QWidget* parent = 0, const QVariantList& args = QVariantList()); + explicit FlipSwitchEffectConfig(QWidget* parent = nullptr, const QVariantList& args = QVariantList()); ~FlipSwitchEffectConfig() override; public Q_SLOTS: void save() override; private: FlipSwitchEffectConfigForm* m_ui; KActionCollection* m_actionCollection; }; } // namespace #endif diff --git a/effects/highlightwindow/highlightwindow.cpp b/effects/highlightwindow/highlightwindow.cpp index 9fa28120c..9de3c1785 100644 --- a/effects/highlightwindow/highlightwindow.cpp +++ b/effects/highlightwindow/highlightwindow.cpp @@ -1,311 +1,311 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. 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 . *********************************************************************/ #include "highlightwindow.h" namespace KWin { HighlightWindowEffect::HighlightWindowEffect() : m_finishing(false) , m_fadeDuration(float(animationTime(150))) - , m_monitorWindow(NULL) + , m_monitorWindow(nullptr) { m_atom = effects->announceSupportProperty("_KDE_WINDOW_HIGHLIGHT", this); connect(effects, &EffectsHandler::windowAdded, this, &HighlightWindowEffect::slotWindowAdded); connect(effects, &EffectsHandler::windowClosed, this, &HighlightWindowEffect::slotWindowClosed); connect(effects, &EffectsHandler::windowDeleted, this, &HighlightWindowEffect::slotWindowDeleted); connect(effects, &EffectsHandler::propertyNotify, this, [this](EffectWindow *w, long atom) { slotPropertyNotify(w, atom, nullptr); } ); connect(effects, &EffectsHandler::xcbConnectionChanged, this, [this] { m_atom = effects->announceSupportProperty("_KDE_WINDOW_HIGHLIGHT", this); } ); } HighlightWindowEffect::~HighlightWindowEffect() { } static bool isInitiallyHidden(EffectWindow* w) { // Is the window initially hidden until it is highlighted? return w->isMinimized() || !w->isOnCurrentDesktop(); } void HighlightWindowEffect::prePaintWindow(EffectWindow* w, WindowPrePaintData& data, int time) { // Calculate window opacities QHash::iterator opacity = m_windowOpacity.find(w); if (!m_highlightedWindows.isEmpty()) { // Initial fade out and changing highlight animation if (opacity == m_windowOpacity.end()) opacity = m_windowOpacity.insertMulti(w, 0.0f); float oldOpacity = *opacity; if (m_highlightedWindows.contains(w)) *opacity = qMin(1.0f, oldOpacity + time / m_fadeDuration); else if (w->isNormalWindow() || w->isDialog()) // Only fade out windows *opacity = qMax(isInitiallyHidden(w) ? 0.0f : 0.15f, oldOpacity - time / m_fadeDuration); if (*opacity < 0.98f) data.setTranslucent(); if (oldOpacity != *opacity) effects->addRepaint(w->expandedGeometry()); } else if (m_finishing && m_windowOpacity.contains(w)) { // Final fading back in animation if (opacity == m_windowOpacity.end()) opacity = m_windowOpacity.insert(w, 0.0f); float oldOpacity = *opacity; if (isInitiallyHidden(w)) *opacity = qMax(0.0f, oldOpacity - time / m_fadeDuration); else *opacity = qMin(1.0f, oldOpacity + time / m_fadeDuration); if (*opacity < 0.98f) data.setTranslucent(); if (oldOpacity != *opacity) effects->addRepaint(w->expandedGeometry()); if (*opacity > 0.98f || *opacity < 0.02f) { m_windowOpacity.remove(w); // We default to 1.0 opacity = m_windowOpacity.end(); } } // Show tabbed windows and windows on other desktops if highlighted if (opacity != m_windowOpacity.end() && *opacity > 0.01) { if (w->isMinimized()) w->enablePainting(EffectWindow::PAINT_DISABLED_BY_MINIMIZE); if (!w->isOnCurrentDesktop()) w->enablePainting(EffectWindow::PAINT_DISABLED_BY_DESKTOP); } effects->prePaintWindow(w, data, time); } void HighlightWindowEffect::paintWindow(EffectWindow* w, int mask, QRegion region, WindowPaintData& data) { data.multiplyOpacity(m_windowOpacity.value(w, 1.0f)); effects->paintWindow(w, mask, region, data); } void HighlightWindowEffect::slotWindowAdded(EffectWindow* w) { if (!m_highlightedWindows.isEmpty()) { // The effect is activated thus we need to add it to the opacity hash foreach (const WId id, m_highlightedIds) { if (w == effects->findWindow(id)) { m_windowOpacity[w] = 1.0; // this window was demanded to be highlighted before it appeared return; } } m_windowOpacity[w] = 0.15; // this window is not currently highlighted } slotPropertyNotify(w, m_atom, w); // Check initial value } void HighlightWindowEffect::slotWindowClosed(EffectWindow* w) { if (m_monitorWindow == w) // The monitoring window was destroyed finishHighlighting(); } void HighlightWindowEffect::slotWindowDeleted(EffectWindow* w) { m_windowOpacity.remove(w); } void HighlightWindowEffect::slotPropertyNotify(EffectWindow* w, long a, EffectWindow *addedWindow) { if (a != m_atom || m_atom == XCB_ATOM_NONE) return; // Not our atom // if the window is null, the property was set on the root window - see events.cpp QByteArray byteData = w ? w->readProperty(m_atom, m_atom, 32) : effects->readRootProperty(m_atom, m_atom, 32); if (byteData.length() < 1) { // Property was removed, clearing highlight if (!addedWindow || w != addedWindow) finishHighlighting(); return; } auto* data = reinterpret_cast(byteData.data()); if (!data[0]) { // Purposely clearing highlight by issuing a NULL target finishHighlighting(); return; } m_monitorWindow = w; bool found = false; int length = byteData.length() / sizeof(data[0]); //foreach ( EffectWindow* e, m_highlightedWindows ) // effects->setElevatedWindow( e, false ); m_highlightedWindows.clear(); m_highlightedIds.clear(); for (int i = 0; i < length; i++) { m_highlightedIds << data[i]; EffectWindow* foundWin = effects->findWindow(data[i]); if (!foundWin) { qCDebug(KWINEFFECTS) << "Invalid window targetted for highlight. Requested:" << data[i]; continue; // might come in later. } m_highlightedWindows.append(foundWin); // TODO: We cannot just simply elevate the window as this will elevate it over // Plasma tooltips and other such windows as well //effects->setElevatedWindow( foundWin, true ); found = true; } if (!found) { finishHighlighting(); return; } prepareHighlighting(); if (w) m_windowOpacity[w] = 1.0; // Because it's not in stackingOrder() yet /* TODO: Finish thumbnails of offscreen windows, not sure if it's worth it though if ( !m_highlightedWindow->isOnCurrentDesktop() ) { // Window is offscreen, determine thumbnail position QRect screenArea = effects->clientArea( MaximizeArea ); // Workable area of the active screen QRect outerArea = outerArea.adjusted( outerArea.width() / 10, outerArea.height() / 10, -outerArea.width() / 10, -outerArea.height() / 10 ); // Add 10% margin around the edge QRect innerArea = outerArea.adjusted( outerArea.width() / 40, outerArea.height() / 40, -outerArea.width() / 40, -outerArea.height() / 40 ); // Outer edge of the thumbnail border (2.5%) QRect thumbArea = outerArea.adjusted( 20, 20, -20, -20 ); // Outer edge of the thumbnail (20px) // Determine the maximum size that we can make the thumbnail within the innerArea double areaAspect = double( thumbArea.width() ) / double( thumbArea.height() ); double windowAspect = aspectRatio( m_highlightedWindow ); QRect thumbRect; // Position doesn't matter right now, but it will later if ( windowAspect > areaAspect ) // Top/bottom will touch first thumbRect = QRect( 0, 0, widthForHeight( thumbArea.height() ), thumbArea.height() ); else // Left/right will touch first thumbRect = QRect( 0, 0, thumbArea.width(), heightForWidth( thumbArea.width() )); if ( thumbRect.width() >= m_highlightedWindow->width() ) // Area is larger than the window, just use the window's size thumbRect = m_highlightedWindow->geometry(); // Determine position of desktop relative to the current one QPoint direction = effects->desktopGridCoords( m_highlightedWindow->desktop() ) - effects->desktopGridCoords( effects->currentDesktop() ); // Draw a line from the center of the current desktop to the center of the target desktop. QPointF desktopLine( 0, 0, direction.x() * screenArea.width(), direction.y() * screenArea.height() ); desktopLeft.translate( screenArea.width() / 2, screenArea.height() / 2 ); // Move to the screen center // Take the point where the line crosses the outerArea, this will be the tip of our arrow QPointF arrowTip; QLineF testLine( // Top outerArea.x(), outerArea.y(), outerArea.x() + outerArea.width(), outerArea.y() ); if ( desktopLine.intersect( testLine, &arrowTip ) != QLineF::BoundedIntersection ) { testLine = QLineF( // Right outerArea.x() + outerArea.width(), outerArea.y(), outerArea.x() + outerArea.width(), outerArea.y() + outerArea.height() ); if ( desktopLine.intersect( testLine, &arrowTip ) != QLineF::BoundedIntersection ) { testLine = QLineF( // Bottom outerArea.x() + outerArea.width(), outerArea.y() + outerArea.height(), outerArea.x(), outerArea.y() + outerArea.height() ); if ( desktopLine.intersect( testLine, &arrowTip ) != QLineF::BoundedIntersection ) { testLine = QLineF( // Left outerArea.x(), outerArea.y() + outerArea.height(), outerArea.x(), outerArea.y() ); desktopLine.intersect( testLine, &arrowTip ); // Should never fail } } } m_arrowTip = arrowTip.toPoint(); } */ } void HighlightWindowEffect::prepareHighlighting() { // Create window data for every window. Just calling [w] creates it. m_finishing = false; foreach (EffectWindow * w, effects->stackingOrder()) { if (!m_windowOpacity.contains(w)) // Just in case we are still finishing from last time m_windowOpacity.insertMulti(w, isInitiallyHidden(w) ? 0.0 : 1.0); if (!m_highlightedWindows.isEmpty()) m_highlightedWindows.at(0)->addRepaintFull(); } } void HighlightWindowEffect::finishHighlighting() { m_finishing = true; - m_monitorWindow = NULL; + m_monitorWindow = nullptr; m_highlightedWindows.clear(); if (!m_windowOpacity.isEmpty()) m_windowOpacity.constBegin().key()->addRepaintFull(); } void HighlightWindowEffect::highlightWindows(const QVector &windows) { if (windows.isEmpty()) { finishHighlighting(); return; } m_monitorWindow = nullptr; m_highlightedWindows.clear(); m_highlightedIds.clear(); for (auto w : windows) { m_highlightedWindows << w; } prepareHighlighting(); } bool HighlightWindowEffect::isActive() const { return !(m_windowOpacity.isEmpty() || effects->isScreenLocked()); } bool HighlightWindowEffect::provides(Feature feature) { switch (feature) { case HighlightWindows: return true; default: return false; } } bool HighlightWindowEffect::perform(Feature feature, const QVariantList &arguments) { if (feature != HighlightWindows) { return false; } if (arguments.size() != 1) { return false; } highlightWindows(arguments.first().value>()); return true; } } // namespace diff --git a/effects/highlightwindow/highlightwindow.h b/effects/highlightwindow/highlightwindow.h index 58158813c..dc3a6d92b 100644 --- a/effects/highlightwindow/highlightwindow.h +++ b/effects/highlightwindow/highlightwindow.h @@ -1,87 +1,87 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. 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 . *********************************************************************/ #ifndef KWIN_HIGHLIGHTWINDOW_H #define KWIN_HIGHLIGHTWINDOW_H #include namespace KWin { class HighlightWindowEffect : public Effect { Q_OBJECT public: HighlightWindowEffect(); ~HighlightWindowEffect() override; void prePaintWindow(EffectWindow* w, WindowPrePaintData& data, int time) override; void paintWindow(EffectWindow* w, int mask, QRegion region, WindowPaintData& data) override; bool isActive() const override; int requestedEffectChainPosition() const override { return 70; } bool provides(Feature feature) override; bool perform(Feature feature, const QVariantList &arguments) override; public Q_SLOTS: void slotWindowAdded(KWin::EffectWindow* w); void slotWindowClosed(KWin::EffectWindow *w); void slotWindowDeleted(KWin::EffectWindow *w); - void slotPropertyNotify(KWin::EffectWindow* w, long atom, EffectWindow *addedWindow = NULL); + void slotPropertyNotify(KWin::EffectWindow* w, long atom, EffectWindow *addedWindow = nullptr); private: void prepareHighlighting(); void finishHighlighting(); void highlightWindows(const QVector &windows); bool m_finishing; float m_fadeDuration; QHash m_windowOpacity; long m_atom; QList m_highlightedWindows; EffectWindow* m_monitorWindow; QList m_highlightedIds; // Offscreen position cache /*QRect m_thumbArea; // Thumbnail area QPoint m_arrowTip; // Position of the arrow's tip QPoint m_arrowA; // Arrow vertex position at the base (First) QPoint m_arrowB; // Arrow vertex position at the base (Second) // Helper functions inline double aspectRatio( EffectWindow *w ) { return w->width() / double( w->height() ); } inline int widthForHeight( EffectWindow *w, int height ) { return int(( height / double( w->height() )) * w->width() ); } inline int heightForWidth( EffectWindow *w, int width ) { return int(( width / double( w->width() )) * w->height() ); }*/ }; } // namespace #endif diff --git a/effects/invert/invert.cpp b/effects/invert/invert.cpp index be1e1f6a3..b8b907017 100644 --- a/effects/invert/invert.cpp +++ b/effects/invert/invert.cpp @@ -1,151 +1,151 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2007 Rivo Laks Copyright (C) 2008 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 . *********************************************************************/ #include "invert.h" #include #include #include #include #include #include #include #include namespace KWin { InvertEffect::InvertEffect() : m_inited(false), m_valid(true), - m_shader(NULL), + m_shader(nullptr), m_allWindows(false) { QAction* a = new QAction(this); a->setObjectName(QStringLiteral("Invert")); a->setText(i18n("Toggle Invert Effect")); KGlobalAccel::self()->setDefaultShortcut(a, QList() << Qt::CTRL + Qt::META + Qt::Key_I); KGlobalAccel::self()->setShortcut(a, QList() << Qt::CTRL + Qt::META + Qt::Key_I); effects->registerGlobalShortcut(Qt::CTRL + Qt::META + Qt::Key_I, a); connect(a, &QAction::triggered, this, &InvertEffect::toggleScreenInversion); QAction* b = new QAction(this); b->setObjectName(QStringLiteral("InvertWindow")); b->setText(i18n("Toggle Invert Effect on Window")); KGlobalAccel::self()->setDefaultShortcut(b, QList() << Qt::CTRL + Qt::META + Qt::Key_U); KGlobalAccel::self()->setShortcut(b, QList() << Qt::CTRL + Qt::META + Qt::Key_U); effects->registerGlobalShortcut(Qt::CTRL + Qt::META + Qt::Key_U, b); connect(b, &QAction::triggered, this, &InvertEffect::toggleWindow); connect(effects, &EffectsHandler::windowClosed, this, &InvertEffect::slotWindowClosed); } InvertEffect::~InvertEffect() { delete m_shader; } bool InvertEffect::supported() { return effects->compositingType() == OpenGL2Compositing; } bool InvertEffect::loadData() { m_inited = true; m_shader = ShaderManager::instance()->generateShaderFromResources(ShaderTrait::MapTexture, QString(), QStringLiteral("invert.frag")); if (!m_shader->isValid()) { qCCritical(KWINEFFECTS) << "The shader failed to load!"; return false; } return true; } void InvertEffect::drawWindow(EffectWindow* w, int mask, QRegion region, WindowPaintData& data) { // Load if we haven't already if (m_valid && !m_inited) m_valid = loadData(); bool useShader = m_valid && (m_allWindows != m_windows.contains(w)); if (useShader) { ShaderManager *shaderManager = ShaderManager::instance(); shaderManager->pushShader(m_shader); data.shader = m_shader; } effects->drawWindow(w, mask, region, data); if (useShader) { ShaderManager::instance()->popShader(); } } void InvertEffect::paintEffectFrame(KWin::EffectFrame* frame, QRegion region, double opacity, double frameOpacity) { if (m_valid && m_allWindows) { frame->setShader(m_shader); ShaderBinder binder(m_shader); effects->paintEffectFrame(frame, region, opacity, frameOpacity); } else { effects->paintEffectFrame(frame, region, opacity, frameOpacity); } } void InvertEffect::slotWindowClosed(EffectWindow* w) { m_windows.removeOne(w); } void InvertEffect::toggleScreenInversion() { m_allWindows = !m_allWindows; effects->addRepaintFull(); } void InvertEffect::toggleWindow() { if (!effects->activeWindow()) { return; } if (!m_windows.contains(effects->activeWindow())) m_windows.append(effects->activeWindow()); else m_windows.removeOne(effects->activeWindow()); effects->activeWindow()->addRepaintFull(); } bool InvertEffect::isActive() const { return m_valid && (m_allWindows || !m_windows.isEmpty()); } bool InvertEffect::provides(Feature f) { return f == ScreenInversion; } } // namespace diff --git a/effects/invert/invert_config.h b/effects/invert/invert_config.h index b4269a19b..e5237bca6 100644 --- a/effects/invert/invert_config.h +++ b/effects/invert/invert_config.h @@ -1,49 +1,49 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2007 Rivo Laks 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_INVERT_CONFIG_H #define KWIN_INVERT_CONFIG_H #include class KShortcutsEditor; namespace KWin { class InvertEffectConfig : public KCModule { Q_OBJECT public: - explicit InvertEffectConfig(QWidget* parent = 0, const QVariantList& args = QVariantList()); + explicit InvertEffectConfig(QWidget* parent = nullptr, const QVariantList& args = QVariantList()); ~InvertEffectConfig() override; public Q_SLOTS: void save() override; void load() override; void defaults() override; private: KShortcutsEditor* mShortcutEditor; }; } // namespace #endif diff --git a/effects/lookingglass/lookingglass.cpp b/effects/lookingglass/lookingglass.cpp index dd806a9f6..a08fa5980 100644 --- a/effects/lookingglass/lookingglass.cpp +++ b/effects/lookingglass/lookingglass.cpp @@ -1,257 +1,257 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2007 Rivo Laks Copyright (C) 2007 Christian Nitschkowski 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 "lookingglass.h" // KConfigSkeleton #include "lookingglassconfig.h" #include #include #include #include #include #include #include #include #include #include namespace KWin { LookingGlassEffect::LookingGlassEffect() : zoom(1.0f) , target_zoom(1.0f) , polling(false) - , m_texture(NULL) - , m_fbo(NULL) - , m_vbo(NULL) - , m_shader(NULL) + , m_texture(nullptr) + , m_fbo(nullptr) + , m_vbo(nullptr) + , m_shader(nullptr) , m_enabled(false) , m_valid(false) { initConfig(); QAction* a; a = KStandardAction::zoomIn(this, SLOT(zoomIn()), this); KGlobalAccel::self()->setDefaultShortcut(a, QList() << Qt::META + Qt::Key_Equal); KGlobalAccel::self()->setShortcut(a, QList() << Qt::META + Qt::Key_Equal); effects->registerGlobalShortcut(Qt::META + Qt::Key_Equal, a); a = KStandardAction::zoomOut(this, SLOT(zoomOut()), this); KGlobalAccel::self()->setDefaultShortcut(a, QList() << Qt::META + Qt::Key_Minus); KGlobalAccel::self()->setShortcut(a, QList() << Qt::META + Qt::Key_Minus); effects->registerGlobalShortcut(Qt::META + Qt::Key_Minus, a); a = KStandardAction::actualSize(this, SLOT(toggle()), this); KGlobalAccel::self()->setDefaultShortcut(a, QList() << Qt::META + Qt::Key_0); KGlobalAccel::self()->setShortcut(a, QList() << Qt::META + Qt::Key_0); effects->registerGlobalShortcut(Qt::META + Qt::Key_0, a); connect(effects, &EffectsHandler::mouseChanged, this, &LookingGlassEffect::slotMouseChanged); reconfigure(ReconfigureAll); } LookingGlassEffect::~LookingGlassEffect() { delete m_texture; delete m_fbo; delete m_shader; delete m_vbo; } bool LookingGlassEffect::supported() { return effects->compositingType() == OpenGL2Compositing && !GLPlatform::instance()->supports(LimitedNPOT); } void LookingGlassEffect::reconfigure(ReconfigureFlags) { LookingGlassConfig::self()->read(); initialradius = LookingGlassConfig::radius(); radius = initialradius; qCDebug(KWINEFFECTS) << "Radius from config:" << radius; m_valid = loadData(); } bool LookingGlassEffect::loadData() { const QSize screenSize = effects->virtualScreenSize(); int texw = screenSize.width(); int texh = screenSize.height(); // Create texture and render target const int levels = std::log2(qMin(texw, texh)) + 1; m_texture = new GLTexture(GL_RGBA8, texw, texh, levels); m_texture->setFilter(GL_LINEAR_MIPMAP_LINEAR); m_texture->setWrapMode(GL_CLAMP_TO_EDGE); m_fbo = new GLRenderTarget(*m_texture); if (!m_fbo->valid()) { return false; } m_shader = ShaderManager::instance()->generateShaderFromResources(ShaderTrait::MapTexture, QString(), QStringLiteral("lookingglass.frag")); if (m_shader->isValid()) { ShaderBinder binder(m_shader); m_shader->setUniform("u_textureSize", QVector2D(screenSize.width(), screenSize.height())); } else { qCCritical(KWINEFFECTS) << "The shader failed to load!"; return false; } m_vbo = new GLVertexBuffer(GLVertexBuffer::Static); QVector verts; QVector texcoords; texcoords << screenSize.width() << 0.0; verts << screenSize.width() << 0.0; texcoords << 0.0 << 0.0; verts << 0.0 << 0.0; texcoords << 0.0 << screenSize.height(); verts << 0.0 << screenSize.height(); texcoords << 0.0 << screenSize.height(); verts << 0.0 << screenSize.height(); texcoords << screenSize.width() << screenSize.height(); verts << screenSize.width() << screenSize.height(); texcoords << screenSize.width() << 0.0; verts << screenSize.width() << 0.0; m_vbo->setData(6, 2, verts.constData(), texcoords.constData()); return true; } void LookingGlassEffect::toggle() { if (target_zoom == 1.0f) { target_zoom = 2.0f; if (!polling) { polling = true; effects->startMousePolling(); } m_enabled = true; } else { target_zoom = 1.0f; if (polling) { polling = false; effects->stopMousePolling(); } if (zoom == target_zoom) { m_enabled = false; } } effects->addRepaint(cursorPos().x() - radius, cursorPos().y() - radius, 2 * radius, 2 * radius); } void LookingGlassEffect::zoomIn() { target_zoom = qMin(7.0, target_zoom + 0.5); m_enabled = true; if (!polling) { polling = true; effects->startMousePolling(); } effects->addRepaint(cursorPos().x() - radius, cursorPos().y() - radius, 2 * radius, 2 * radius); } void LookingGlassEffect::zoomOut() { target_zoom -= 0.5; if (target_zoom < 1) { target_zoom = 1; if (polling) { polling = false; effects->stopMousePolling(); } if (zoom == target_zoom) { m_enabled = false; } } effects->addRepaint(cursorPos().x() - radius, cursorPos().y() - radius, 2 * radius, 2 * radius); } void LookingGlassEffect::prePaintScreen(ScreenPrePaintData& data, int time) { if (zoom != target_zoom) { double diff = time / animationTime(500.0); if (target_zoom > zoom) zoom = qMin(zoom * qMax(1.0 + diff, 1.2), target_zoom); else zoom = qMax(zoom * qMin(1.0 - diff, 0.8), target_zoom); qCDebug(KWINEFFECTS) << "zoom is now " << zoom; radius = qBound((double)initialradius, initialradius * zoom, 3.5 * initialradius); if (zoom <= 1.0f) { m_enabled = false; } effects->addRepaint(cursorPos().x() - radius, cursorPos().y() - radius, 2 * radius, 2 * radius); } if (m_valid && m_enabled) { data.mask |= PAINT_SCREEN_WITH_TRANSFORMED_WINDOWS; // Start rendering to texture GLRenderTarget::pushRenderTarget(m_fbo); } effects->prePaintScreen(data, time); } void LookingGlassEffect::slotMouseChanged(const QPoint& pos, const QPoint& old, Qt::MouseButtons, Qt::MouseButtons, Qt::KeyboardModifiers, Qt::KeyboardModifiers) { if (pos != old && m_enabled) { effects->addRepaint(pos.x() - radius, pos.y() - radius, 2 * radius, 2 * radius); effects->addRepaint(old.x() - radius, old.y() - radius, 2 * radius, 2 * radius); } } void LookingGlassEffect::paintScreen(int mask, QRegion region, ScreenPaintData &data) { // Call the next effect. effects->paintScreen(mask, region, data); if (m_valid && m_enabled) { // Disable render texture GLRenderTarget* target = GLRenderTarget::popRenderTarget(); Q_ASSERT(target == m_fbo); Q_UNUSED(target); m_texture->bind(); m_texture->generateMipmaps(); // Use the shader ShaderBinder binder(m_shader); m_shader->setUniform("u_zoom", (float)zoom); m_shader->setUniform("u_radius", (float)radius); m_shader->setUniform("u_cursor", QVector2D(cursorPos().x(), cursorPos().y())); m_shader->setUniform(GLShader::ModelViewProjectionMatrix, data.projectionMatrix()); m_vbo->render(GL_TRIANGLES); m_texture->unbind(); } } bool LookingGlassEffect::isActive() const { return m_valid && m_enabled; } } // namespace diff --git a/effects/lookingglass/lookingglass_config.h b/effects/lookingglass/lookingglass_config.h index 22adf1b10..21619ee5f 100644 --- a/effects/lookingglass/lookingglass_config.h +++ b/effects/lookingglass/lookingglass_config.h @@ -1,57 +1,57 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2007 Christian Nitschkowski 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_LOOKINGGLASS_CONFIG_H #define KWIN_LOOKINGGLASS_CONFIG_H #include #include "ui_lookingglass_config.h" class KActionCollection; namespace KWin { class LookingGlassEffectConfigForm : public QWidget, public Ui::LookingGlassEffectConfigForm { Q_OBJECT public: explicit LookingGlassEffectConfigForm(QWidget* parent); }; class LookingGlassEffectConfig : public KCModule { Q_OBJECT public: - explicit LookingGlassEffectConfig(QWidget* parent = 0, const QVariantList& args = QVariantList()); + explicit LookingGlassEffectConfig(QWidget* parent = nullptr, const QVariantList& args = QVariantList()); ~LookingGlassEffectConfig() override; void save() override; void defaults() override; private: LookingGlassEffectConfigForm* m_ui; KActionCollection* m_actionCollection; }; } // namespace #endif diff --git a/effects/magiclamp/magiclamp.cpp b/effects/magiclamp/magiclamp.cpp index 76513b710..155ccf39e 100644 --- a/effects/magiclamp/magiclamp.cpp +++ b/effects/magiclamp/magiclamp.cpp @@ -1,374 +1,374 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2008 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 . *********************************************************************/ // based on minimize animation by Rivo Laks #include "magiclamp.h" // KConfigSkeleton #include "magiclampconfig.h" namespace KWin { MagicLampEffect::MagicLampEffect() { initConfig(); reconfigure(ReconfigureAll); connect(effects, &EffectsHandler::windowDeleted, this, &MagicLampEffect::slotWindowDeleted); connect(effects, &EffectsHandler::windowMinimized, this, &MagicLampEffect::slotWindowMinimized); connect(effects, &EffectsHandler::windowUnminimized, this, &MagicLampEffect::slotWindowUnminimized); } bool MagicLampEffect::supported() { return effects->isOpenGLCompositing() && effects->animationsSupported(); } void MagicLampEffect::reconfigure(ReconfigureFlags) { MagicLampConfig::self()->read(); // TODO: Rename animationDuration to duration so we can use // animationTime(250). const int d = MagicLampConfig::animationDuration() != 0 ? MagicLampConfig::animationDuration() : 250; m_duration = std::chrono::milliseconds(static_cast(animationTime(d))); } void MagicLampEffect::prePaintScreen(ScreenPrePaintData& data, int time) { const std::chrono::milliseconds delta(time); auto animationIt = m_animations.begin(); while (animationIt != m_animations.end()) { (*animationIt).update(delta); ++animationIt; } // We need to mark the screen windows as transformed. Otherwise the // whole screen won't be repainted, resulting in artefacts. data.mask |= PAINT_SCREEN_WITH_TRANSFORMED_WINDOWS; effects->prePaintScreen(data, time); } void MagicLampEffect::prePaintWindow(EffectWindow* w, WindowPrePaintData& data, int time) { // Schedule window for transformation if the animation is still in // progress if (m_animations.contains(w)) { // We'll transform this window data.setTransformed(); data.quads = data.quads.makeGrid(40); w->enablePainting(EffectWindow::PAINT_DISABLED_BY_MINIMIZE); } effects->prePaintWindow(w, data, time); } void MagicLampEffect::paintWindow(EffectWindow* w, int mask, QRegion region, WindowPaintData& data) { auto animationIt = m_animations.constFind(w); if (animationIt != m_animations.constEnd()) { // 0 = not minimized, 1 = fully minimized const qreal progress = (*animationIt).value(); QRect geo = w->geometry(); QRect icon = w->iconGeometry(); IconPosition position = Top; // If there's no icon geometry, minimize to the center of the screen if (!icon.isValid()) { QRect extG = geo; QPoint pt = cursorPos(); // focussing inside the window is no good, leads to ugly artefacts, find nearest border if (extG.contains(pt)) { const int d[2][2] = { {pt.x() - extG.x(), extG.right() - pt.x()}, {pt.y() - extG.y(), extG.bottom() - pt.y()} }; int di = d[1][0]; position = Top; if (d[0][0] < di) { di = d[0][0]; position = Left; } if (d[1][1] < di) { di = d[1][1]; position = Bottom; } if (d[0][1] < di) position = Right; switch(position) { case Top: pt.setY(extG.y()); break; case Left: pt.setX(extG.x()); break; case Bottom: pt.setY(extG.bottom()); break; case Right: pt.setX(extG.right()); break; } } else { if (pt.y() < geo.y()) position = Top; else if (pt.x() < geo.x()) position = Left; else if (pt.y() > geo.bottom()) position = Bottom; else if (pt.x() > geo.right()) position = Right; } icon = QRect(pt, QSize(0, 0)); } else { // Assumption: there is a panel containing the icon position - EffectWindow* panel = NULL; + EffectWindow* panel = nullptr; foreach (EffectWindow * window, effects->stackingOrder()) { if (!window->isDock()) continue; // we have to use intersects as there seems to be a Plasma bug // the published icon geometry might be bigger than the panel if (window->geometry().intersects(icon)) { panel = window; break; } } if (panel) { // Assumption: width of horizonal panel is greater than its height and vice versa // The panel has to border one screen edge, so get it's screen area QRect panelScreen = effects->clientArea(ScreenArea, panel); if (panel->width() >= panel->height()) { // horizontal panel if (panel->y() == panelScreen.y()) position = Top; else position = Bottom; } else { // vertical panel if (panel->x() == panelScreen.x()) position = Left; else position = Right; } } else { // we did not find a panel, so it might be autohidden QRect iconScreen = effects->clientArea(ScreenArea, icon.topLeft(), effects->currentDesktop()); // as the icon geometry could be overlap a screen edge we use an intersection QRect rect = iconScreen.intersected(icon); // here we need a different assumption: icon geometry borders one screen edge // this assumption might be wrong for e.g. task applet being the only applet in panel // in this case the icon borders two screen edges // there might be a wrong animation, but not distorted if (rect.x() == iconScreen.x()) { position = Left; } else if (rect.x() + rect.width() == iconScreen.x() + iconScreen.width()) { position = Right; } else if (rect.y() == iconScreen.y()) { position = Top; } else { position = Bottom; } } } #define SANITIZE_PROGRESS if (p_progress[0] < 0)\ p_progress[0] = -p_progress[0];\ if (p_progress[1] < 0)\ p_progress[1] = -p_progress[1] #define SET_QUADS(_SET_A_, _A_, _DA_, _SET_B_, _B_, _O0_, _O1_, _O2_, _O3_) quad[0]._SET_A_((icon._A_() + icon._DA_()*(quad[0]._A_() / geo._DA_()) - (quad[0]._A_() + geo._A_()))*p_progress[_O0_] + quad[0]._A_());\ quad[1]._SET_A_((icon._A_() + icon._DA_()*(quad[1]._A_() / geo._DA_()) - (quad[1]._A_() + geo._A_()))*p_progress[_O1_] + quad[1]._A_());\ quad[2]._SET_A_((icon._A_() + icon._DA_()*(quad[2]._A_() / geo._DA_()) - (quad[2]._A_() + geo._A_()))*p_progress[_O2_] + quad[2]._A_());\ quad[3]._SET_A_((icon._A_() + icon._DA_()*(quad[3]._A_() / geo._DA_()) - (quad[3]._A_() + geo._A_()))*p_progress[_O3_] + quad[3]._A_());\ \ quad[0]._SET_B_(quad[0]._B_() + offset[_O0_]);\ quad[1]._SET_B_(quad[1]._B_() + offset[_O1_]);\ quad[2]._SET_B_(quad[2]._B_() + offset[_O2_]);\ quad[3]._SET_B_(quad[3]._B_() + offset[_O3_]) WindowQuadList newQuads; newQuads.reserve(data.quads.count()); float quadFactor; // defines how fast a quad is vertically moved: y coordinates near to window top are slowed down // it is used as quadFactor^3/windowHeight^3 // quadFactor is the y position of the quad but is changed towards becomming the window height // by that the factor becomes 1 and has no influence any more float offset[2] = {0,0}; // how far has a quad to be moved? Distance between icon and window multiplied by the progress and by the quadFactor float p_progress[2] = {0,0}; // the factor which defines how far the x values have to be changed // factor is the current moved y value diveded by the distance between icon and window WindowQuad lastQuad(WindowQuadError); lastQuad[0].setX(-1); lastQuad[0].setY(-1); lastQuad[1].setX(-1); lastQuad[1].setY(-1); lastQuad[2].setX(-1); lastQuad[2].setY(-1); if (position == Bottom) { float height_cube = float(geo.height()) * float(geo.height()) * float(geo.height()); foreach (WindowQuad quad, data.quads) { // krazy:exclude=foreach if (quad[0].y() != lastQuad[0].y() || quad[2].y() != lastQuad[2].y()) { quadFactor = quad[0].y() + (geo.height() - quad[0].y()) * progress; offset[0] = (icon.y() + quad[0].y() - geo.y()) * progress * ((quadFactor * quadFactor * quadFactor) / height_cube); quadFactor = quad[2].y() + (geo.height() - quad[2].y()) * progress; offset[1] = (icon.y() + quad[2].y() - geo.y()) * progress * ((quadFactor * quadFactor * quadFactor) / height_cube); p_progress[1] = qMin(offset[1] / (icon.y() + icon.height() - geo.y() - float(quad[2].y())), 1.0f); p_progress[0] = qMin(offset[0] / (icon.y() + icon.height() - geo.y() - float(quad[0].y())), 1.0f); } else lastQuad = quad; SANITIZE_PROGRESS; // x values are moved towards the center of the icon SET_QUADS(setX, x, width, setY, y, 0,0,1,1); newQuads.append(quad); } } else if (position == Top) { float height_cube = float(geo.height()) * float(geo.height()) * float(geo.height()); foreach (WindowQuad quad, data.quads) { // krazy:exclude=foreach if (quad[0].y() != lastQuad[0].y() || quad[2].y() != lastQuad[2].y()) { quadFactor = geo.height() - quad[0].y() + (quad[0].y()) * progress; offset[0] = (geo.y() - icon.height() + geo.height() + quad[0].y() - icon.y()) * progress * ((quadFactor * quadFactor * quadFactor) / height_cube); quadFactor = geo.height() - quad[2].y() + (quad[2].y()) * progress; offset[1] = (geo.y() - icon.height() + geo.height() + quad[2].y() - icon.y()) * progress * ((quadFactor * quadFactor * quadFactor) / height_cube); p_progress[0] = qMin(offset[0] / (geo.y() - icon.height() + geo.height() - icon.y() - float(geo.height() - quad[0].y())), 1.0f); p_progress[1] = qMin(offset[1] / (geo.y() - icon.height() + geo.height() - icon.y() - float(geo.height() - quad[2].y())), 1.0f); } else lastQuad = quad; offset[0] = -offset[0]; offset[1] = -offset[1]; SANITIZE_PROGRESS; // x values are moved towards the center of the icon SET_QUADS(setX, x, width, setY, y, 0,0,1,1); newQuads.append(quad); } } else if (position == Left) { float width_cube = float(geo.width()) * float(geo.width()) * float(geo.width()); foreach (WindowQuad quad, data.quads) { // krazy:exclude=foreach if (quad[0].x() != lastQuad[0].x() || quad[1].x() != lastQuad[1].x()) { quadFactor = geo.width() - quad[0].x() + (quad[0].x()) * progress; offset[0] = (geo.x() - icon.width() + geo.width() + quad[0].x() - icon.x()) * progress * ((quadFactor * quadFactor * quadFactor) / width_cube); quadFactor = geo.width() - quad[1].x() + (quad[1].x()) * progress; offset[1] = (geo.x() - icon.width() + geo.width() + quad[1].x() - icon.x()) * progress * ((quadFactor * quadFactor * quadFactor) / width_cube); p_progress[0] = qMin(offset[0] / (geo.x() - icon.width() + geo.width() - icon.x() - float(geo.width() - quad[0].x())), 1.0f); p_progress[1] = qMin(offset[1] / (geo.x() - icon.width() + geo.width() - icon.x() - float(geo.width() - quad[1].x())), 1.0f); } else lastQuad = quad; offset[0] = -offset[0]; offset[1] = -offset[1]; SANITIZE_PROGRESS; // y values are moved towards the center of the icon SET_QUADS(setY, y, height, setX, x, 0,1,1,0); newQuads.append(quad); } } else if (position == Right) { float width_cube = float(geo.width()) * float(geo.width()) * float(geo.width()); foreach (WindowQuad quad, data.quads) { // krazy:exclude=foreach if (quad[0].x() != lastQuad[0].x() || quad[1].x() != lastQuad[1].x()) { quadFactor = quad[0].x() + (geo.width() - quad[0].x()) * progress; offset[0] = (icon.x() + quad[0].x() - geo.x()) * progress * ((quadFactor * quadFactor * quadFactor) / width_cube); quadFactor = quad[1].x() + (geo.width() - quad[1].x()) * progress; offset[1] = (icon.x() + quad[1].x() - geo.x()) * progress * ((quadFactor * quadFactor * quadFactor) / width_cube); p_progress[0] = qMin(offset[0] / (icon.x() + icon.width() - geo.x() - float(quad[0].x())), 1.0f); p_progress[1] = qMin(offset[1] / (icon.x() + icon.width() - geo.x() - float(quad[1].x())), 1.0f); } else lastQuad = quad; SANITIZE_PROGRESS; // y values are moved towards the center of the icon SET_QUADS(setY, y, height, setX, x, 0,1,1,0); newQuads.append(quad); } } data.quads = newQuads; } // Call the next effect. effects->paintWindow(w, mask, region, data); } void MagicLampEffect::postPaintScreen() { auto animationIt = m_animations.begin(); while (animationIt != m_animations.end()) { if ((*animationIt).done()) { animationIt = m_animations.erase(animationIt); } else { ++animationIt; } } effects->addRepaintFull(); // Call the next effect. effects->postPaintScreen(); } void MagicLampEffect::slotWindowDeleted(EffectWindow* w) { m_animations.remove(w); } void MagicLampEffect::slotWindowMinimized(EffectWindow* w) { if (effects->activeFullScreenEffect()) return; TimeLine &timeLine = m_animations[w]; if (timeLine.running()) { timeLine.toggleDirection(); } else { timeLine.setDirection(TimeLine::Forward); timeLine.setDuration(m_duration); timeLine.setEasingCurve(QEasingCurve::Linear); } effects->addRepaintFull(); } void MagicLampEffect::slotWindowUnminimized(EffectWindow* w) { if (effects->activeFullScreenEffect()) return; TimeLine &timeLine = m_animations[w]; if (timeLine.running()) { timeLine.toggleDirection(); } else { timeLine.setDirection(TimeLine::Backward); timeLine.setDuration(m_duration); timeLine.setEasingCurve(QEasingCurve::Linear); } effects->addRepaintFull(); } bool MagicLampEffect::isActive() const { return !m_animations.isEmpty(); } } // namespace diff --git a/effects/magiclamp/magiclamp_config.h b/effects/magiclamp/magiclamp_config.h index 2f074c07d..c4b838d96 100644 --- a/effects/magiclamp/magiclamp_config.h +++ b/effects/magiclamp/magiclamp_config.h @@ -1,54 +1,54 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. 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 . *********************************************************************/ #ifndef KWIN_MAGICLAMP_CONFIG_H #define KWIN_MAGICLAMP_CONFIG_H #include #include "ui_magiclamp_config.h" namespace KWin { class MagicLampEffectConfigForm : public QWidget, public Ui::MagicLampEffectConfigForm { Q_OBJECT public: explicit MagicLampEffectConfigForm(QWidget* parent); }; class MagicLampEffectConfig : public KCModule { Q_OBJECT public: - explicit MagicLampEffectConfig(QWidget* parent = 0, const QVariantList& args = QVariantList()); + explicit MagicLampEffectConfig(QWidget* parent = nullptr, const QVariantList& args = QVariantList()); public Q_SLOTS: void save() override; private: MagicLampEffectConfigForm* m_ui; }; } // namespace #endif diff --git a/effects/magnifier/magnifier.cpp b/effects/magnifier/magnifier.cpp index 9a22b3806..d800ef4f8 100644 --- a/effects/magnifier/magnifier.cpp +++ b/effects/magnifier/magnifier.cpp @@ -1,342 +1,342 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2007 Lubos Lunak Copyright (C) 2007 Christian Nitschkowski Copyright (C) 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 "magnifier.h" // KConfigSkeleton #include "magnifierconfig.h" #include #include #include #include #ifdef KWIN_HAVE_XRENDER_COMPOSITING #include #include #endif #include namespace KWin { const int FRAME_WIDTH = 5; MagnifierEffect::MagnifierEffect() : zoom(1) , target_zoom(1) , polling(false) - , m_texture(0) - , m_fbo(0) + , m_texture(nullptr) + , m_fbo(nullptr) #ifdef KWIN_HAVE_XRENDER_COMPOSITING , m_pixmap(XCB_PIXMAP_NONE) #endif { initConfig(); QAction* a; a = KStandardAction::zoomIn(this, SLOT(zoomIn()), this); KGlobalAccel::self()->setDefaultShortcut(a, QList() << Qt::META + Qt::Key_Equal); KGlobalAccel::self()->setShortcut(a, QList() << Qt::META + Qt::Key_Equal); effects->registerGlobalShortcut(Qt::META + Qt::Key_Equal, a); a = KStandardAction::zoomOut(this, SLOT(zoomOut()), this); KGlobalAccel::self()->setDefaultShortcut(a, QList() << Qt::META + Qt::Key_Minus); KGlobalAccel::self()->setShortcut(a, QList() << Qt::META + Qt::Key_Minus); effects->registerGlobalShortcut(Qt::META + Qt::Key_Minus, a); a = KStandardAction::actualSize(this, SLOT(toggle()), this); KGlobalAccel::self()->setDefaultShortcut(a, QList() << Qt::META + Qt::Key_0); KGlobalAccel::self()->setShortcut(a, QList() << Qt::META + Qt::Key_0); effects->registerGlobalShortcut(Qt::META + Qt::Key_0, a); connect(effects, &EffectsHandler::mouseChanged, this, &MagnifierEffect::slotMouseChanged); reconfigure(ReconfigureAll); } MagnifierEffect::~MagnifierEffect() { delete m_fbo; delete m_texture; destroyPixmap(); // Save the zoom value. MagnifierConfig::setInitialZoom(target_zoom); MagnifierConfig::self()->save(); } void MagnifierEffect::destroyPixmap() { #ifdef KWIN_HAVE_XRENDER_COMPOSITING if (effects->compositingType() != XRenderCompositing) { return; } m_picture.reset(); if (m_pixmap != XCB_PIXMAP_NONE) { xcb_free_pixmap(xcbConnection(), m_pixmap); m_pixmap = XCB_PIXMAP_NONE; } #endif } bool MagnifierEffect::supported() { return effects->compositingType() == XRenderCompositing || (effects->isOpenGLCompositing() && GLRenderTarget::blitSupported()); } void MagnifierEffect::reconfigure(ReconfigureFlags) { MagnifierConfig::self()->read(); int width, height; width = MagnifierConfig::width(); height = MagnifierConfig::height(); magnifier_size = QSize(width, height); // Load the saved zoom value. target_zoom = MagnifierConfig::initialZoom(); if (target_zoom != zoom) toggle(); } void MagnifierEffect::prePaintScreen(ScreenPrePaintData& data, int time) { if (zoom != target_zoom) { double diff = time / animationTime(500.0); if (target_zoom > zoom) zoom = qMin(zoom * qMax(1 + diff, 1.2), target_zoom); else { zoom = qMax(zoom * qMin(1 - diff, 0.8), target_zoom); if (zoom == 1.0) { // zoom ended - delete FBO and texture delete m_fbo; delete m_texture; - m_fbo = NULL; - m_texture = NULL; + m_fbo = nullptr; + m_texture = nullptr; destroyPixmap(); } } } effects->prePaintScreen(data, time); if (zoom != 1.0) data.paint |= magnifierArea().adjusted(-FRAME_WIDTH, -FRAME_WIDTH, FRAME_WIDTH, FRAME_WIDTH); } void MagnifierEffect::paintScreen(int mask, QRegion region, ScreenPaintData& data) { effects->paintScreen(mask, region, data); // paint normal screen if (zoom != 1.0) { // get the right area from the current rendered screen const QRect area = magnifierArea(); const QPoint cursor = cursorPos(); QRect srcArea(cursor.x() - (double)area.width() / (zoom*2), cursor.y() - (double)area.height() / (zoom*2), (double)area.width() / zoom, (double)area.height() / zoom); if (effects->isOpenGLCompositing()) { m_fbo->blitFromFramebuffer(srcArea); // paint magnifier m_texture->bind(); auto s = ShaderManager::instance()->pushShader(ShaderTrait::MapTexture); QMatrix4x4 mvp; const QSize size = effects->virtualScreenSize(); mvp.ortho(0, size.width(), size.height(), 0, 0, 65535); mvp.translate(area.x(), area.y()); s->setUniform(GLShader::ModelViewProjectionMatrix, mvp); m_texture->render(infiniteRegion(), area); ShaderManager::instance()->popShader(); m_texture->unbind(); QVector verts; GLVertexBuffer *vbo = GLVertexBuffer::streamingBuffer(); vbo->reset(); vbo->setColor(QColor(0, 0, 0)); const QRectF areaF = area; // top frame verts << areaF.right() + FRAME_WIDTH << areaF.top() - FRAME_WIDTH; verts << areaF.left() - FRAME_WIDTH << areaF.top() - FRAME_WIDTH; verts << areaF.left() - FRAME_WIDTH << areaF.top(); verts << areaF.left() - FRAME_WIDTH << areaF.top(); verts << areaF.right() + FRAME_WIDTH << areaF.top(); verts << areaF.right() + FRAME_WIDTH << areaF.top() - FRAME_WIDTH; // left frame verts << areaF.left() << areaF.top() - FRAME_WIDTH; verts << areaF.left() - FRAME_WIDTH << areaF.top() - FRAME_WIDTH; verts << areaF.left() - FRAME_WIDTH << areaF.bottom() + FRAME_WIDTH; verts << areaF.left() - FRAME_WIDTH << areaF.bottom() + FRAME_WIDTH; verts << areaF.left() << areaF.bottom() + FRAME_WIDTH; verts << areaF.left() << areaF.top() - FRAME_WIDTH; // right frame verts << areaF.right() + FRAME_WIDTH << areaF.top() - FRAME_WIDTH; verts << areaF.right() << areaF.top() - FRAME_WIDTH; verts << areaF.right() << areaF.bottom() + FRAME_WIDTH; verts << areaF.right() << areaF.bottom() + FRAME_WIDTH; verts << areaF.right() + FRAME_WIDTH << areaF.bottom() + FRAME_WIDTH; verts << areaF.right() + FRAME_WIDTH << areaF.top() - FRAME_WIDTH; // bottom frame verts << areaF.right() + FRAME_WIDTH << areaF.bottom(); verts << areaF.left() - FRAME_WIDTH << areaF.bottom(); verts << areaF.left() - FRAME_WIDTH << areaF.bottom() + FRAME_WIDTH; verts << areaF.left() - FRAME_WIDTH << areaF.bottom() + FRAME_WIDTH; verts << areaF.right() + FRAME_WIDTH << areaF.bottom() + FRAME_WIDTH; verts << areaF.right() + FRAME_WIDTH << areaF.bottom(); - vbo->setData(verts.size() / 2, 2, verts.constData(), NULL); + vbo->setData(verts.size() / 2, 2, verts.constData(), nullptr); ShaderBinder binder(ShaderTrait::UniformColor); binder.shader()->setUniform(GLShader::ModelViewProjectionMatrix, data.projectionMatrix()); vbo->render(GL_TRIANGLES); } if (effects->compositingType() == XRenderCompositing) { #ifdef KWIN_HAVE_XRENDER_COMPOSITING if (m_pixmap == XCB_PIXMAP_NONE || m_pixmapSize != srcArea.size()) { destroyPixmap(); m_pixmap = xcb_generate_id(xcbConnection()); m_pixmapSize = srcArea.size(); xcb_create_pixmap(xcbConnection(), 32, m_pixmap, x11RootWindow(), m_pixmapSize.width(), m_pixmapSize.height()); m_picture.reset(new XRenderPicture(m_pixmap, 32)); } #define DOUBLE_TO_FIXED(d) ((xcb_render_fixed_t) ((d) * 65536)) static const xcb_render_transform_t identity = { DOUBLE_TO_FIXED(1), DOUBLE_TO_FIXED(0), DOUBLE_TO_FIXED(0), DOUBLE_TO_FIXED(0), DOUBLE_TO_FIXED(1), DOUBLE_TO_FIXED(0), DOUBLE_TO_FIXED(0), DOUBLE_TO_FIXED(0), DOUBLE_TO_FIXED(1) }; static xcb_render_transform_t xform = { DOUBLE_TO_FIXED(1), DOUBLE_TO_FIXED(0), DOUBLE_TO_FIXED(0), DOUBLE_TO_FIXED(0), DOUBLE_TO_FIXED(1), DOUBLE_TO_FIXED(0), DOUBLE_TO_FIXED(0), DOUBLE_TO_FIXED(0), DOUBLE_TO_FIXED(1) }; xcb_render_composite(xcbConnection(), XCB_RENDER_PICT_OP_SRC, effects->xrenderBufferPicture(), 0, *m_picture, srcArea.x(), srcArea.y(), 0, 0, 0, 0, srcArea.width(), srcArea.height()); xcb_flush(xcbConnection()); xform.matrix11 = DOUBLE_TO_FIXED(1.0/zoom); xform.matrix22 = DOUBLE_TO_FIXED(1.0/zoom); #undef DOUBLE_TO_FIXED xcb_render_set_picture_transform(xcbConnection(), *m_picture, xform); - xcb_render_set_picture_filter(xcbConnection(), *m_picture, 4, const_cast("good"), 0, NULL); + xcb_render_set_picture_filter(xcbConnection(), *m_picture, 4, const_cast("good"), 0, nullptr); xcb_render_composite(xcbConnection(), XCB_RENDER_PICT_OP_SRC, *m_picture, 0, effects->xrenderBufferPicture(), 0, 0, 0, 0, area.x(), area.y(), area.width(), area.height() ); - xcb_render_set_picture_filter(xcbConnection(), *m_picture, 4, const_cast("fast"), 0, NULL); + xcb_render_set_picture_filter(xcbConnection(), *m_picture, 4, const_cast("fast"), 0, nullptr); xcb_render_set_picture_transform(xcbConnection(), *m_picture, identity); const xcb_rectangle_t rects[4] = { { int16_t(area.x()+FRAME_WIDTH), int16_t(area.y()), uint16_t(area.width()-FRAME_WIDTH), uint16_t(FRAME_WIDTH)}, { int16_t(area.right()-FRAME_WIDTH), int16_t(area.y()+FRAME_WIDTH), uint16_t(FRAME_WIDTH), uint16_t(area.height()-FRAME_WIDTH)}, { int16_t(area.x()), int16_t(area.bottom()-FRAME_WIDTH), uint16_t(area.width()-FRAME_WIDTH), uint16_t(FRAME_WIDTH)}, { int16_t(area.x()), int16_t(area.y()), uint16_t(FRAME_WIDTH), uint16_t(area.height()-FRAME_WIDTH)} }; xcb_render_fill_rectangles(xcbConnection(), XCB_RENDER_PICT_OP_SRC, effects->xrenderBufferPicture(), preMultiply(QColor(0,0,0,255)), 4, rects); #endif } } } void MagnifierEffect::postPaintScreen() { if (zoom != target_zoom) { QRect framedarea = magnifierArea().adjusted(-FRAME_WIDTH, -FRAME_WIDTH, FRAME_WIDTH, FRAME_WIDTH); effects->addRepaint(framedarea); } effects->postPaintScreen(); } QRect MagnifierEffect::magnifierArea(QPoint pos) const { return QRect(pos.x() - magnifier_size.width() / 2, pos.y() - magnifier_size.height() / 2, magnifier_size.width(), magnifier_size.height()); } void MagnifierEffect::zoomIn() { target_zoom *= 1.2; if (!polling) { polling = true; effects->startMousePolling(); } if (effects->isOpenGLCompositing() && !m_texture) { effects->makeOpenGLContextCurrent(); m_texture = new GLTexture(GL_RGBA8, magnifier_size.width(), magnifier_size.height()); m_texture->setYInverted(false); m_fbo = new GLRenderTarget(*m_texture); } effects->addRepaint(magnifierArea().adjusted(-FRAME_WIDTH, -FRAME_WIDTH, FRAME_WIDTH, FRAME_WIDTH)); } void MagnifierEffect::zoomOut() { target_zoom /= 1.2; if (target_zoom <= 1) { target_zoom = 1; if (polling) { polling = false; effects->stopMousePolling(); } if (zoom == target_zoom) { effects->makeOpenGLContextCurrent(); delete m_fbo; delete m_texture; - m_fbo = NULL; - m_texture = NULL; + m_fbo = nullptr; + m_texture = nullptr; destroyPixmap(); } } effects->addRepaint(magnifierArea().adjusted(-FRAME_WIDTH, -FRAME_WIDTH, FRAME_WIDTH, FRAME_WIDTH)); } void MagnifierEffect::toggle() { if (zoom == 1.0) { if (target_zoom == 1.0) { target_zoom = 2; } if (!polling) { polling = true; effects->startMousePolling(); } if (effects->isOpenGLCompositing() && !m_texture) { effects->makeOpenGLContextCurrent(); m_texture = new GLTexture(GL_RGBA8, magnifier_size.width(), magnifier_size.height()); m_texture->setYInverted(false); m_fbo = new GLRenderTarget(*m_texture); } } else { target_zoom = 1; if (polling) { polling = false; effects->stopMousePolling(); } } effects->addRepaint(magnifierArea().adjusted(-FRAME_WIDTH, -FRAME_WIDTH, FRAME_WIDTH, FRAME_WIDTH)); } void MagnifierEffect::slotMouseChanged(const QPoint& pos, const QPoint& old, Qt::MouseButtons, Qt::MouseButtons, Qt::KeyboardModifiers, Qt::KeyboardModifiers) { if (pos != old && zoom != 1) // need full repaint as we might lose some change events on fast mouse movements // see Bug 187658 effects->addRepaintFull(); } bool MagnifierEffect::isActive() const { return zoom != 1.0 || zoom != target_zoom; } } // namespace diff --git a/effects/magnifier/magnifier_config.h b/effects/magnifier/magnifier_config.h index 6dafd4f87..6b7ec1a92 100644 --- a/effects/magnifier/magnifier_config.h +++ b/effects/magnifier/magnifier_config.h @@ -1,57 +1,57 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2007 Christian Nitschkowski 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_MAGNIFIER_CONFIG_H #define KWIN_MAGNIFIER_CONFIG_H #include #include "ui_magnifier_config.h" class KActionCollection; namespace KWin { class MagnifierEffectConfigForm : public QWidget, public Ui::MagnifierEffectConfigForm { Q_OBJECT public: explicit MagnifierEffectConfigForm(QWidget* parent); }; class MagnifierEffectConfig : public KCModule { Q_OBJECT public: - explicit MagnifierEffectConfig(QWidget* parent = 0, const QVariantList& args = QVariantList()); + explicit MagnifierEffectConfig(QWidget* parent = nullptr, const QVariantList& args = QVariantList()); ~MagnifierEffectConfig() override; void save() override; void defaults() override; private: MagnifierEffectConfigForm* m_ui; KActionCollection* m_actionCollection; }; } // namespace #endif diff --git a/effects/mouseclick/mouseclick.cpp b/effects/mouseclick/mouseclick.cpp index baa53f591..a735b1419 100644 --- a/effects/mouseclick/mouseclick.cpp +++ b/effects/mouseclick/mouseclick.cpp @@ -1,389 +1,389 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2012 Filip Wieladek 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 "mouseclick.h" // KConfigSkeleton #include "mouseclickconfig.h" #include #include #ifdef KWIN_HAVE_XRENDER_COMPOSITING #include #include #include #endif #include #include #include #include namespace KWin { MouseClickEffect::MouseClickEffect() { initConfig(); m_enabled = false; QAction* a = new QAction(this); a->setObjectName(QStringLiteral("ToggleMouseClick")); a->setText(i18n("Toggle Mouse Click Effect")); KGlobalAccel::self()->setDefaultShortcut(a, QList() << Qt::META + Qt::Key_Asterisk); KGlobalAccel::self()->setShortcut(a, QList() << Qt::META + Qt::Key_Asterisk); effects->registerGlobalShortcut(Qt::META + Qt::Key_Asterisk, a); connect(a, &QAction::triggered, this, &MouseClickEffect::toggleEnabled); reconfigure(ReconfigureAll); m_buttons[0] = new MouseButton(i18nc("Left mouse button", "Left"), Qt::LeftButton); m_buttons[1] = new MouseButton(i18nc("Middle mouse button", "Middle"), Qt::MiddleButton); m_buttons[2] = new MouseButton(i18nc("Right mouse button", "Right"), Qt::RightButton); } MouseClickEffect::~MouseClickEffect() { if (m_enabled) effects->stopMousePolling(); qDeleteAll(m_clicks); m_clicks.clear(); for (int i = 0; i < BUTTON_COUNT; ++i) { delete m_buttons[i]; } } void MouseClickEffect::reconfigure(ReconfigureFlags) { MouseClickConfig::self()->read(); m_colors[0] = MouseClickConfig::color1(); m_colors[1] = MouseClickConfig::color2(); m_colors[2] = MouseClickConfig::color3(); m_lineWidth = MouseClickConfig::lineWidth(); m_ringLife = MouseClickConfig::ringLife(); m_ringMaxSize = MouseClickConfig::ringSize(); m_ringCount = MouseClickConfig::ringCount(); m_showText = MouseClickConfig::showText(); m_font = MouseClickConfig::font(); } void MouseClickEffect::prePaintScreen(ScreenPrePaintData& data, int time) { foreach (MouseEvent* click, m_clicks) { click->m_time += time; } for (int i = 0; i < BUTTON_COUNT; ++i) { if (m_buttons[i]->m_isPressed) { m_buttons[i]->m_time += time; } } while (m_clicks.size() > 0) { MouseEvent* first = m_clicks[0]; if (first->m_time <= m_ringLife) { break; } m_clicks.pop_front(); delete first; } effects->prePaintScreen(data, time); } void MouseClickEffect::paintScreen(int mask, QRegion region, ScreenPaintData& data) { effects->paintScreen(mask, region, data); paintScreenSetup(mask, region, data); foreach (const MouseEvent* click, m_clicks) { for (int i = 0; i < m_ringCount; ++i) { float alpha = computeAlpha(click, i); float size = computeRadius(click, i); if (size > 0 && alpha > 0) { QColor color = m_colors[click->m_button]; color.setAlphaF(alpha); drawCircle(color, click->m_pos.x(), click->m_pos.y(), size); } } if (m_showText && click->m_frame) { float frameAlpha = (click->m_time * 2.0f - m_ringLife) / m_ringLife; frameAlpha = frameAlpha < 0 ? 1 : -(frameAlpha * frameAlpha) + 1; click->m_frame->render(infiniteRegion(), frameAlpha, frameAlpha); } } paintScreenFinish(mask, region, data); } void MouseClickEffect::postPaintScreen() { effects->postPaintScreen(); repaint(); } float MouseClickEffect::computeRadius(const MouseEvent* click, int ring) { float ringDistance = m_ringLife / (m_ringCount * 3); if (click->m_press) { return ((click->m_time - ringDistance * ring) / m_ringLife) * m_ringMaxSize; } return ((m_ringLife - click->m_time - ringDistance * ring) / m_ringLife) * m_ringMaxSize; } float MouseClickEffect::computeAlpha(const MouseEvent* click, int ring) { float ringDistance = m_ringLife / (m_ringCount * 3); return (m_ringLife - (float)click->m_time - ringDistance * (ring)) / m_ringLife; } void MouseClickEffect::slotMouseChanged(const QPoint& pos, const QPoint&, Qt::MouseButtons buttons, Qt::MouseButtons oldButtons, Qt::KeyboardModifiers, Qt::KeyboardModifiers) { if (buttons == oldButtons) return; - MouseEvent* m = NULL; + MouseEvent* m = nullptr; int i = BUTTON_COUNT; while (--i >= 0) { MouseButton* b = m_buttons[i]; if (isPressed(b->m_button, buttons, oldButtons)) { m = new MouseEvent(i, pos, 0, createEffectFrame(pos, b->m_labelDown), true); break; } else if (isReleased(b->m_button, buttons, oldButtons) && (!b->m_isPressed || b->m_time > m_ringLife)) { // we might miss a press, thus also check !b->m_isPressed, bug #314762 m = new MouseEvent(i, pos, 0, createEffectFrame(pos, b->m_labelUp), false); break; } b->setPressed(b->m_button & buttons); } if (m) { m_clicks.append(m); } repaint(); } EffectFrame* MouseClickEffect::createEffectFrame(const QPoint& pos, const QString& text) { if (!m_showText) { - return NULL; + return nullptr; } QPoint point(pos.x() + m_ringMaxSize, pos.y()); EffectFrame* frame = effects->effectFrame(EffectFrameStyled, false, point, Qt::AlignLeft); frame->setFont(m_font); frame->setText(text); return frame; } void MouseClickEffect::repaint() { if (m_clicks.size() > 0) { QRegion dirtyRegion; const int radius = m_ringMaxSize + m_lineWidth; foreach (MouseEvent* click, m_clicks) { dirtyRegion |= QRect(click->m_pos.x() - radius, click->m_pos.y() - radius, 2*radius, 2*radius); if (click->m_frame) { // we grant the plasma style 32px padding for stuff like shadows... dirtyRegion |= click->m_frame->geometry().adjusted(-32,-32,32,32); } } effects->addRepaint(dirtyRegion); } } bool MouseClickEffect::isReleased(Qt::MouseButtons button, Qt::MouseButtons buttons, Qt::MouseButtons oldButtons) { return !(button & buttons) && (button & oldButtons); } bool MouseClickEffect::isPressed(Qt::MouseButtons button, Qt::MouseButtons buttons, Qt::MouseButtons oldButtons) { return (button & buttons) && !(button & oldButtons); } void MouseClickEffect::toggleEnabled() { m_enabled = !m_enabled; if (m_enabled) { connect(effects, &EffectsHandler::mouseChanged, this, &MouseClickEffect::slotMouseChanged); effects->startMousePolling(); } else { disconnect(effects, &EffectsHandler::mouseChanged, this, &MouseClickEffect::slotMouseChanged); effects->stopMousePolling(); } qDeleteAll(m_clicks); m_clicks.clear(); for (int i = 0; i < BUTTON_COUNT; ++i) { m_buttons[i]->m_time = 0; m_buttons[i]->m_isPressed = false; } } bool MouseClickEffect::isActive() const { return m_enabled && (m_clicks.size() > 0); } void MouseClickEffect::drawCircle(const QColor& color, float cx, float cy, float r) { if (effects->isOpenGLCompositing()) drawCircleGl(color, cx, cy, r); if (effects->compositingType() == XRenderCompositing) drawCircleXr(color, cx, cy, r); if (effects->compositingType() == QPainterCompositing) drawCircleQPainter(color, cx, cy, r); } void MouseClickEffect::paintScreenSetup(int mask, QRegion region, ScreenPaintData& data) { if (effects->isOpenGLCompositing()) paintScreenSetupGl(mask, region, data); } void MouseClickEffect::paintScreenFinish(int mask, QRegion region, ScreenPaintData& data) { if (effects->isOpenGLCompositing()) paintScreenFinishGl(mask, region, data); } void MouseClickEffect::drawCircleGl(const QColor& color, float cx, float cy, float r) { static const int num_segments = 80; static const float theta = 2 * 3.1415926 / float(num_segments); static const float c = cosf(theta); //precalculate the sine and cosine static const float s = sinf(theta); float t; float x = r;//we start at angle = 0 float y = 0; GLVertexBuffer* vbo = GLVertexBuffer::streamingBuffer(); vbo->reset(); vbo->setUseColor(true); vbo->setColor(color); QVector verts; verts.reserve(num_segments * 2); for (int ii = 0; ii < num_segments; ++ii) { verts << x + cx << y + cy;//output vertex //apply the rotation matrix t = x; x = c * x - s * y; y = s * t + c * y; } - vbo->setData(verts.size() / 2, 2, verts.data(), NULL); + vbo->setData(verts.size() / 2, 2, verts.data(), nullptr); vbo->render(GL_LINE_LOOP); } void MouseClickEffect::drawCircleXr(const QColor& color, float cx, float cy, float r) { #ifdef KWIN_HAVE_XRENDER_COMPOSITING if (r <= m_lineWidth) return; int num_segments = r+8; float theta = 2.0 * 3.1415926 / num_segments; float cos = cosf(theta); //precalculate the sine and cosine float sin = sinf(theta); float x[2] = {r, r-m_lineWidth}; float y[2] = {0, 0}; #define DOUBLE_TO_FIXED(d) ((xcb_render_fixed_t) ((d) * 65536)) QVector strip; strip.reserve(2*num_segments+2); xcb_render_pointfix_t point; point.x = DOUBLE_TO_FIXED(x[1]+cx); point.y = DOUBLE_TO_FIXED(y[1]+cy); strip << point; for (int i = 0; i < num_segments; ++i) { //apply the rotation matrix const float h[2] = {x[0], x[1]}; x[0] = cos * x[0] - sin * y[0]; x[1] = cos * x[1] - sin * y[1]; y[0] = sin * h[0] + cos * y[0]; y[1] = sin * h[1] + cos * y[1]; point.x = DOUBLE_TO_FIXED(x[0]+cx); point.y = DOUBLE_TO_FIXED(y[0]+cy); strip << point; point.x = DOUBLE_TO_FIXED(x[1]+cx); point.y = DOUBLE_TO_FIXED(y[1]+cy); strip << point; } const float h = x[0]; x[0] = cos * x[0] - sin * y[0]; y[0] = sin * h + cos * y[0]; point.x = DOUBLE_TO_FIXED(x[0]+cx); point.y = DOUBLE_TO_FIXED(y[0]+cy); strip << point; XRenderPicture fill = xRenderFill(color); xcb_render_tri_strip(xcbConnection(), XCB_RENDER_PICT_OP_OVER, fill, effects->xrenderBufferPicture(), 0, 0, 0, strip.count(), strip.constData()); #undef DOUBLE_TO_FIXED #else Q_UNUSED(color) Q_UNUSED(cx) Q_UNUSED(cy) Q_UNUSED(r) #endif } void MouseClickEffect::drawCircleQPainter(const QColor &color, float cx, float cy, float r) { QPainter *painter = effects->scenePainter(); painter->save(); painter->setPen(color); painter->drawArc(cx - r, cy - r, r * 2, r * 2, 0, 5760); painter->restore(); } void MouseClickEffect::paintScreenSetupGl(int, QRegion, ScreenPaintData &data) { GLShader *shader = ShaderManager::instance()->pushShader(ShaderTrait::UniformColor); shader->setUniform(GLShader::ModelViewProjectionMatrix, data.projectionMatrix()); glLineWidth(m_lineWidth); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); } void MouseClickEffect::paintScreenFinishGl(int, QRegion, ScreenPaintData&) { glDisable(GL_BLEND); ShaderManager::instance()->popShader(); } } // namespace diff --git a/effects/mouseclick/mouseclick_config.h b/effects/mouseclick/mouseclick_config.h index 9e8ef375c..52713c933 100644 --- a/effects/mouseclick/mouseclick_config.h +++ b/effects/mouseclick/mouseclick_config.h @@ -1,56 +1,56 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2012 Filip Wieladek 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_MOUSECLICK_CONFIG_H #define KWIN_MOUSECLICK_CONFIG_H #include #include "ui_mouseclick_config.h" class KActionCollection; namespace KWin { class MouseClickEffectConfigForm : public QWidget, public Ui::MouseClickEffectConfigForm { Q_OBJECT public: explicit MouseClickEffectConfigForm(QWidget* parent); }; class MouseClickEffectConfig : public KCModule { Q_OBJECT public: - explicit MouseClickEffectConfig(QWidget* parent = 0, const QVariantList& args = QVariantList()); + explicit MouseClickEffectConfig(QWidget* parent = nullptr, const QVariantList& args = QVariantList()); ~MouseClickEffectConfig() override; void save() override; private: MouseClickEffectConfigForm* m_ui; KActionCollection* m_actionCollection; }; } // namespace #endif diff --git a/effects/mousemark/mousemark.cpp b/effects/mousemark/mousemark.cpp index a205ea13c..d135b04af 100644 --- a/effects/mousemark/mousemark.cpp +++ b/effects/mousemark/mousemark.cpp @@ -1,294 +1,294 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2006 Lubos Lunak Copyright (C) 2007 Christian Nitschkowski 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 "mousemark.h" // KConfigSkeleton #include "mousemarkconfig.h" #include #include #include #include #include #include #include #include #ifdef KWIN_HAVE_XRENDER_COMPOSITING #include #endif namespace KWin { #define NULL_POINT (QPoint( -1, -1 )) // null point is (0,0), which is valid :-/ MouseMarkEffect::MouseMarkEffect() { initConfig(); QAction* a = new QAction(this); a->setObjectName(QStringLiteral("ClearMouseMarks")); a->setText(i18n("Clear All Mouse Marks")); KGlobalAccel::self()->setDefaultShortcut(a, QList() << Qt::SHIFT + Qt::META + Qt::Key_F11); KGlobalAccel::self()->setShortcut(a, QList() << Qt::SHIFT + Qt::META + Qt::Key_F11); effects->registerGlobalShortcut(Qt::SHIFT + Qt::META + Qt::Key_F11, a); connect(a, &QAction::triggered, this, &MouseMarkEffect::clear); a = new QAction(this); a->setObjectName(QStringLiteral("ClearLastMouseMark")); a->setText(i18n("Clear Last Mouse Mark")); KGlobalAccel::self()->setDefaultShortcut(a, QList() << Qt::SHIFT + Qt::META + Qt::Key_F12); KGlobalAccel::self()->setShortcut(a, QList() << Qt::SHIFT + Qt::META + Qt::Key_F12); effects->registerGlobalShortcut(Qt::SHIFT + Qt::META + Qt::Key_F12, a); connect(a, &QAction::triggered, this, &MouseMarkEffect::clearLast); connect(effects, &EffectsHandler::mouseChanged, this, &MouseMarkEffect::slotMouseChanged); connect(effects, &EffectsHandler::screenLockingChanged, this, &MouseMarkEffect::screenLockingChanged); reconfigure(ReconfigureAll); arrow_start = NULL_POINT; effects->startMousePolling(); // We require it to detect activation as well } MouseMarkEffect::~MouseMarkEffect() { effects->stopMousePolling(); } static int width_2 = 1; void MouseMarkEffect::reconfigure(ReconfigureFlags) { MouseMarkConfig::self()->read(); width = MouseMarkConfig::lineWidth(); width_2 = width / 2; color = MouseMarkConfig::color(); color.setAlphaF(1.0); } #ifdef KWIN_HAVE_XRENDER_COMPOSITING void MouseMarkEffect::addRect(const QPoint &p1, const QPoint &p2, xcb_rectangle_t *r, xcb_render_color_t *c) { r->x = qMin(p1.x(), p2.x()) - width_2; r->y = qMin(p1.y(), p2.y()) - width_2; r->width = qAbs(p1.x()-p2.x()) + 1 + width_2; r->height = qAbs(p1.y()-p2.y()) + 1 + width_2; // fast move -> large rect, tess... interpolate a line if (r->width > 3*width/2 && r->height > 3*width/2) { const int n = sqrt(r->width*r->width + r->height*r->height) / width; xcb_rectangle_t *rects = new xcb_rectangle_t[n-1]; const int w = p1.x() < p2.x() ? r->width : -r->width; const int h = p1.y() < p2.y() ? r->height : -r->height; for (int i = 1; i < n; ++i) { rects[i-1].x = p1.x() + i*w/n; rects[i-1].y = p1.y() + i*h/n; rects[i-1].width = rects[i-1].height = width; } xcb_render_fill_rectangles(xcbConnection(), XCB_RENDER_PICT_OP_SRC, effects->xrenderBufferPicture(), *c, n - 1, rects); delete [] rects; r->x = p1.x(); r->y = p1.y(); r->width = r->height = width; } } #endif void MouseMarkEffect::paintScreen(int mask, QRegion region, ScreenPaintData& data) { effects->paintScreen(mask, region, data); // paint normal screen if (marks.isEmpty() && drawing.isEmpty()) return; if ( effects->isOpenGLCompositing()) { if (!GLPlatform::instance()->isGLES()) { glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glEnable(GL_LINE_SMOOTH); glHint(GL_LINE_SMOOTH_HINT, GL_NICEST); } glLineWidth(width); GLVertexBuffer *vbo = GLVertexBuffer::streamingBuffer(); vbo->reset(); vbo->setUseColor(true); vbo->setColor(color); ShaderBinder binder(ShaderTrait::UniformColor); binder.shader()->setUniform(GLShader::ModelViewProjectionMatrix, data.projectionMatrix()); QVector verts; foreach (const Mark & mark, marks) { verts.clear(); verts.reserve(mark.size() * 2); foreach (const QPoint & p, mark) { verts << p.x() << p.y(); } - vbo->setData(verts.size() / 2, 2, verts.data(), NULL); + vbo->setData(verts.size() / 2, 2, verts.data(), nullptr); vbo->render(GL_LINE_STRIP); } if (!drawing.isEmpty()) { verts.clear(); verts.reserve(drawing.size() * 2); foreach (const QPoint & p, drawing) { verts << p.x() << p.y(); } - vbo->setData(verts.size() / 2, 2, verts.data(), NULL); + vbo->setData(verts.size() / 2, 2, verts.data(), nullptr); vbo->render(GL_LINE_STRIP); } glLineWidth(1.0); if (!GLPlatform::instance()->isGLES()) { glDisable(GL_LINE_SMOOTH); glDisable(GL_BLEND); } } #ifdef KWIN_HAVE_XRENDER_COMPOSITING if ( effects->compositingType() == XRenderCompositing) { xcb_render_color_t c = preMultiply(color); for (int i = 0; i < marks.count(); ++i) { const int n = marks[i].count() - 1; if (n > 0) { xcb_rectangle_t *rects = new xcb_rectangle_t[n]; for (int j = 0; j < marks[i].count()-1; ++j) { addRect(marks[i][j], marks[i][j+1], &rects[j], &c); } xcb_render_fill_rectangles(xcbConnection(), XCB_RENDER_PICT_OP_SRC, effects->xrenderBufferPicture(), c, n, rects); delete [] rects; } } const int n = drawing.count() - 1; if (n > 0) { xcb_rectangle_t *rects = new xcb_rectangle_t[n]; for (int i = 0; i < n; ++i) addRect(drawing[i], drawing[i+1], &rects[i], &c); xcb_render_fill_rectangles(xcbConnection(), XCB_RENDER_PICT_OP_SRC, effects->xrenderBufferPicture(), c, n, rects); delete [] rects; } } #endif if (effects->compositingType() == QPainterCompositing) { QPainter *painter = effects->scenePainter(); painter->save(); QPen pen(color); pen.setWidth(width); painter->setPen(pen); foreach (const Mark &mark, marks) { drawMark(painter, mark); } drawMark(painter, drawing); painter->restore(); } } void MouseMarkEffect::drawMark(QPainter *painter, const Mark &mark) { if (mark.count() <= 1) { return; } for (int i = 0; i < mark.count() - 1; ++i) { painter->drawLine(mark[i], mark[i+1]); } } void MouseMarkEffect::slotMouseChanged(const QPoint& pos, const QPoint&, Qt::MouseButtons, Qt::MouseButtons, Qt::KeyboardModifiers modifiers, Qt::KeyboardModifiers) { if (modifiers == (Qt::META | Qt::SHIFT | Qt::CTRL)) { // start/finish arrow if (arrow_start != NULL_POINT) { marks.append(createArrow(arrow_start, pos)); arrow_start = NULL_POINT; effects->addRepaintFull(); return; } else arrow_start = pos; } if (arrow_start != NULL_POINT) return; // TODO the shortcuts now trigger this right before they're activated if (modifiers == (Qt::META | Qt::SHIFT)) { // activated if (drawing.isEmpty()) drawing.append(pos); if (drawing.last() == pos) return; QPoint pos2 = drawing.last(); drawing.append(pos); QRect repaint = QRect(qMin(pos.x(), pos2.x()), qMin(pos.y(), pos2.y()), qMax(pos.x(), pos2.x()), qMax(pos.y(), pos2.y())); repaint.adjust(-width, -width, width, width); effects->addRepaint(repaint); } else if (!drawing.isEmpty()) { marks.append(drawing); drawing.clear(); } } void MouseMarkEffect::clear() { drawing.clear(); marks.clear(); effects->addRepaintFull(); } void MouseMarkEffect::clearLast() { if (arrow_start != NULL_POINT) { arrow_start = NULL_POINT; } else if (!drawing.isEmpty()) { drawing.clear(); effects->addRepaintFull(); } else if (!marks.isEmpty()) { marks.pop_back(); effects->addRepaintFull(); } } MouseMarkEffect::Mark MouseMarkEffect::createArrow(QPoint arrow_start, QPoint arrow_end) { Mark ret; double angle = atan2((double)(arrow_end.y() - arrow_start.y()), (double)(arrow_end.x() - arrow_start.x())); ret += arrow_start + QPoint(50 * cos(angle + M_PI / 6), 50 * sin(angle + M_PI / 6)); // right one ret += arrow_start; ret += arrow_end; ret += arrow_start; // it's connected lines, so go back with the middle one ret += arrow_start + QPoint(50 * cos(angle - M_PI / 6), 50 * sin(angle - M_PI / 6)); // left one return ret; } void MouseMarkEffect::screenLockingChanged(bool locked) { if (!marks.isEmpty() || !drawing.isEmpty()) { effects->addRepaintFull(); } // disable mouse polling while screen is locked. if (locked) { effects->stopMousePolling(); } else { effects->startMousePolling(); } } bool MouseMarkEffect::isActive() const { return (!marks.isEmpty() || !drawing.isEmpty()) && !effects->isScreenLocked(); } } // namespace diff --git a/effects/mousemark/mousemark_config.h b/effects/mousemark/mousemark_config.h index aea63f0c0..20bd754cf 100644 --- a/effects/mousemark/mousemark_config.h +++ b/effects/mousemark/mousemark_config.h @@ -1,56 +1,56 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2007 Christian Nitschkowski 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_MOUSEMARK_CONFIG_H #define KWIN_MOUSEMARK_CONFIG_H #include #include "ui_mousemark_config.h" class KActionCollection; namespace KWin { class MouseMarkEffectConfigForm : public QWidget, public Ui::MouseMarkEffectConfigForm { Q_OBJECT public: explicit MouseMarkEffectConfigForm(QWidget* parent); }; class MouseMarkEffectConfig : public KCModule { Q_OBJECT public: - explicit MouseMarkEffectConfig(QWidget* parent = 0, const QVariantList& args = QVariantList()); + explicit MouseMarkEffectConfig(QWidget* parent = nullptr, const QVariantList& args = QVariantList()); ~MouseMarkEffectConfig() override; void save() override; private: MouseMarkEffectConfigForm* m_ui; KActionCollection* m_actionCollection; }; } // namespace #endif diff --git a/effects/presentwindows/presentwindows.cpp b/effects/presentwindows/presentwindows.cpp index 35a29fdad..b1238e9dc 100644 --- a/effects/presentwindows/presentwindows.cpp +++ b/effects/presentwindows/presentwindows.cpp @@ -1,2071 +1,2071 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2007 Rivo Laks Copyright (C) 2008 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 . *********************************************************************/ #include "presentwindows.h" //KConfigSkeleton #include "presentwindowsconfig.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace KWin { PresentWindowsEffect::PresentWindowsEffect() : m_proxy(this) , m_activated(false) , m_ignoreMinimized(false) , m_decalOpacity(0.0) , m_hasKeyboardGrab(false) , m_mode(ModeCurrentDesktop) - , m_managerWindow(NULL) + , m_managerWindow(nullptr) , m_needInitialSelection(false) - , m_highlightedWindow(NULL) - , m_filterFrame(NULL) - , m_closeView(NULL) - , m_closeWindow(NULL) + , m_highlightedWindow(nullptr) + , m_filterFrame(nullptr) + , m_closeView(nullptr) + , m_closeWindow(nullptr) , m_exposeAction(new QAction(this)) , m_exposeAllAction(new QAction(this)) , m_exposeClassAction(new QAction(this)) { initConfig(); auto announceSupportProperties = [this] { m_atomDesktop = effects->announceSupportProperty("_KDE_PRESENT_WINDOWS_DESKTOP", this); m_atomWindows = effects->announceSupportProperty("_KDE_PRESENT_WINDOWS_GROUP", this); }; announceSupportProperties(); connect(effects, &EffectsHandler::xcbConnectionChanged, this, announceSupportProperties); QAction* exposeAction = m_exposeAction; exposeAction->setObjectName(QStringLiteral("Expose")); exposeAction->setText(i18n("Toggle Present Windows (Current desktop)")); KGlobalAccel::self()->setDefaultShortcut(exposeAction, QList() << Qt::CTRL + Qt::Key_F9); KGlobalAccel::self()->setShortcut(exposeAction, QList() << Qt::CTRL + Qt::Key_F9); shortcut = KGlobalAccel::self()->shortcut(exposeAction); effects->registerGlobalShortcut(Qt::CTRL + Qt::Key_F9, exposeAction); connect(exposeAction, &QAction::triggered, this, &PresentWindowsEffect::toggleActive); QAction* exposeAllAction = m_exposeAllAction; exposeAllAction->setObjectName(QStringLiteral("ExposeAll")); exposeAllAction->setText(i18n("Toggle Present Windows (All desktops)")); KGlobalAccel::self()->setDefaultShortcut(exposeAllAction, QList() << Qt::CTRL + Qt::Key_F10 << Qt::Key_LaunchC); KGlobalAccel::self()->setShortcut(exposeAllAction, QList() << Qt::CTRL + Qt::Key_F10 << Qt::Key_LaunchC); shortcutAll = KGlobalAccel::self()->shortcut(exposeAllAction); effects->registerGlobalShortcut(Qt::CTRL + Qt::Key_F10, exposeAllAction); effects->registerTouchpadSwipeShortcut(SwipeDirection::Down, exposeAllAction); connect(exposeAllAction, &QAction::triggered, this, &PresentWindowsEffect::toggleActiveAllDesktops); QAction* exposeClassAction = m_exposeClassAction; exposeClassAction->setObjectName(QStringLiteral("ExposeClass")); exposeClassAction->setText(i18n("Toggle Present Windows (Window class)")); KGlobalAccel::self()->setDefaultShortcut(exposeClassAction, QList() << Qt::CTRL + Qt::Key_F7); KGlobalAccel::self()->setShortcut(exposeClassAction, QList() << Qt::CTRL + Qt::Key_F7); effects->registerGlobalShortcut(Qt::CTRL + Qt::Key_F7, exposeClassAction); connect(exposeClassAction, &QAction::triggered, this, &PresentWindowsEffect::toggleActiveClass); shortcutClass = KGlobalAccel::self()->shortcut(exposeClassAction); connect(KGlobalAccel::self(), &KGlobalAccel::globalShortcutChanged, this, &PresentWindowsEffect::globalShortcutChanged); reconfigure(ReconfigureAll); connect(effects, &EffectsHandler::windowAdded, this, &PresentWindowsEffect::slotWindowAdded); connect(effects, &EffectsHandler::windowClosed, this, &PresentWindowsEffect::slotWindowClosed); connect(effects, &EffectsHandler::windowDeleted, this, &PresentWindowsEffect::slotWindowDeleted); connect(effects, &EffectsHandler::windowGeometryShapeChanged, this, &PresentWindowsEffect::slotWindowGeometryShapeChanged); connect(effects, &EffectsHandler::propertyNotify, this, &PresentWindowsEffect::slotPropertyNotify); connect(effects, &EffectsHandler::numberScreensChanged, this, [this] { if (isActive()) reCreateGrids(); } ); connect(effects, &EffectsHandler::screenAboutToLock, this, [this]() { setActive(false); }); } PresentWindowsEffect::~PresentWindowsEffect() { delete m_filterFrame; delete m_closeView; } void PresentWindowsEffect::reconfigure(ReconfigureFlags) { PresentWindowsConfig::self()->read(); foreach (ElectricBorder border, m_borderActivate) { effects->unreserveElectricBorder(border, this); } foreach (ElectricBorder border, m_borderActivateAll) { effects->unreserveElectricBorder(border, this); } m_borderActivate.clear(); m_borderActivateAll.clear(); foreach (int i, PresentWindowsConfig::borderActivate()) { m_borderActivate.append(ElectricBorder(i)); effects->reserveElectricBorder(ElectricBorder(i), this); } foreach (int i, PresentWindowsConfig::borderActivateAll()) { m_borderActivateAll.append(ElectricBorder(i)); effects->reserveElectricBorder(ElectricBorder(i), this); } foreach (int i, PresentWindowsConfig::borderActivateClass()) { m_borderActivateClass.append(ElectricBorder(i)); effects->reserveElectricBorder(ElectricBorder(i), this); } m_layoutMode = PresentWindowsConfig::layoutMode(); m_showCaptions = PresentWindowsConfig::drawWindowCaptions(); m_showIcons = PresentWindowsConfig::drawWindowIcons(); m_doNotCloseWindows = !PresentWindowsConfig::allowClosingWindows(); if (m_doNotCloseWindows) { delete m_closeView; m_closeView = nullptr; m_closeWindow = nullptr; } m_ignoreMinimized = PresentWindowsConfig::ignoreMinimized(); m_accuracy = PresentWindowsConfig::accuracy() * 20; m_fillGaps = PresentWindowsConfig::fillGaps(); m_fadeDuration = double(animationTime(150)); m_showPanel = PresentWindowsConfig::showPanel(); m_leftButtonWindow = (WindowMouseAction)PresentWindowsConfig::leftButtonWindow(); m_middleButtonWindow = (WindowMouseAction)PresentWindowsConfig::middleButtonWindow(); m_rightButtonWindow = (WindowMouseAction)PresentWindowsConfig::rightButtonWindow(); m_leftButtonDesktop = (DesktopMouseAction)PresentWindowsConfig::leftButtonDesktop(); m_middleButtonDesktop = (DesktopMouseAction)PresentWindowsConfig::middleButtonDesktop(); m_rightButtonDesktop = (DesktopMouseAction)PresentWindowsConfig::rightButtonDesktop(); // touch screen edges const QVector relevantBorders{ElectricLeft, ElectricTop, ElectricRight, ElectricBottom}; for (auto e : relevantBorders) { effects->unregisterTouchBorder(e, m_exposeAction); effects->unregisterTouchBorder(e, m_exposeAllAction); effects->unregisterTouchBorder(e, m_exposeClassAction); } auto touchEdge = [&relevantBorders] (const QList touchBorders, QAction *action) { for (int i : touchBorders) { if (!relevantBorders.contains(ElectricBorder(i))) { continue; } effects->registerTouchBorder(ElectricBorder(i), action); } }; touchEdge(PresentWindowsConfig::touchBorderActivate(), m_exposeAction); touchEdge(PresentWindowsConfig::touchBorderActivateAll(), m_exposeAllAction); touchEdge(PresentWindowsConfig::touchBorderActivateClass(), m_exposeClassAction); } void* PresentWindowsEffect::proxy() { return &m_proxy; } void PresentWindowsEffect::toggleActiveClass() { if (!m_activated) { if (!effects->activeWindow()) return; m_mode = ModeWindowClass; m_class = effects->activeWindow()->windowClass(); } setActive(!m_activated); } //----------------------------------------------------------------------------- // Screen painting void PresentWindowsEffect::prePaintScreen(ScreenPrePaintData &data, int time) { m_motionManager.calculate(time); // We need to mark the screen as having been transformed otherwise there will be no repainting if (m_activated || m_motionManager.managingWindows()) data.mask |= Effect::PAINT_SCREEN_WITH_TRANSFORMED_WINDOWS; if (m_activated) m_decalOpacity = qMin(1.0, m_decalOpacity + time / m_fadeDuration); else m_decalOpacity = qMax(0.0, m_decalOpacity - time / m_fadeDuration); effects->prePaintScreen(data, time); } void PresentWindowsEffect::paintScreen(int mask, QRegion region, ScreenPaintData &data) { effects->paintScreen(mask, region, data); // Display the filter box if (!m_windowFilter.isEmpty()) m_filterFrame->render(region); } void PresentWindowsEffect::postPaintScreen() { if (m_motionManager.areWindowsMoving()) effects->addRepaintFull(); else if (!m_activated && m_motionManager.managingWindows() && !(m_closeWindow && m_closeView->isVisible())) { // We have finished moving them back, stop processing m_motionManager.unmanageAll(); DataHash::iterator i = m_windowData.begin(); while (i != m_windowData.end()) { delete i.value().textFrame; delete i.value().iconFrame; ++i; } m_windowData.clear(); foreach (EffectWindow * w, effects->stackingOrder()) { w->setData(WindowForceBlurRole, QVariant()); w->setData(WindowForceBackgroundContrastRole, QVariant()); } - effects->setActiveFullScreenEffect(NULL); + effects->setActiveFullScreenEffect(nullptr); effects->addRepaintFull(); } else if (m_activated && m_needInitialSelection) { m_needInitialSelection = false; QMouseEvent me(QEvent::MouseMove, cursorPos(), Qt::NoButton, Qt::NoButton, Qt::NoModifier); windowInputMouseEvent(&me); } // Update windows that are changing brightness or opacity DataHash::const_iterator i; for (i = m_windowData.constBegin(); i != m_windowData.constEnd(); ++i) { if (i.value().opacity > 0.0 && i.value().opacity < 1.0) i.key()->addRepaintFull(); if (i.key()->isDesktop() && !m_motionManager.isManaging(i.key())) { if (i.value().highlight != 0.3) i.key()->addRepaintFull(); } else if (i.value().highlight > 0.0 && i.value().highlight < 1.0) i.key()->addRepaintFull(); } effects->postPaintScreen(); } //----------------------------------------------------------------------------- // Window painting void PresentWindowsEffect::prePaintWindow(EffectWindow *w, WindowPrePaintData &data, int time) { // TODO: We should also check to see if any windows are fading just in case fading takes longer // than moving the windows when the effect is deactivated. if (m_activated || m_motionManager.areWindowsMoving() || m_closeWindow) { DataHash::iterator winData = m_windowData.find(w); if (winData == m_windowData.end()) { effects->prePaintWindow(w, data, time); return; } w->enablePainting(EffectWindow::PAINT_DISABLED_BY_MINIMIZE); // Display always w->enablePainting(EffectWindow::PAINT_DISABLED_BY_DESKTOP); // Calculate window's opacity // TODO: Minimized windows or windows not on the current desktop are only 75% visible? if (winData->visible) { if (winData->deleted) winData->opacity = qMax(0.0, winData->opacity - time / m_fadeDuration); else winData->opacity = qMin(/*(w->isMinimized() || !w->isOnCurrentDesktop()) ? 0.75 :*/ 1.0, winData->opacity + time / m_fadeDuration); } else winData->opacity = qMax(0.0, winData->opacity - time / m_fadeDuration); if (winData->opacity <= 0.0) { // don't disable painting for panels if show panel is set if (!(m_showPanel && w->isDock())) w->disablePainting(EffectWindow::PAINT_DISABLED); } else if (winData->opacity != 1.0) data.setTranslucent(); const bool isInMotion = m_motionManager.isManaging(w); // Calculate window's brightness if (w == m_highlightedWindow || w == m_closeWindow || !m_activated) winData->highlight = qMin(1.0, winData->highlight + time / m_fadeDuration); else if (!isInMotion && w->isDesktop()) winData->highlight = 0.3; else winData->highlight = qMax(0.0, winData->highlight - time / m_fadeDuration); // Closed windows if (winData->deleted) { data.setTranslucent(); if (winData->opacity <= 0.0 && winData->referenced) { // it's possible that another effect has referenced the window // we have to keep the window in the list to prevent flickering winData->referenced = false; w->unrefWindow(); if (w == m_closeWindow) { - m_closeWindow = NULL; + m_closeWindow = nullptr; } } else w->enablePainting(EffectWindow::PAINT_DISABLED_BY_DELETE); } // desktop windows on other desktops (Plasma activity per desktop) should not be painted if (w->isDesktop() && !w->isOnCurrentDesktop()) w->disablePainting(EffectWindow::PAINT_DISABLED_BY_DESKTOP); if (isInMotion) data.setTransformed(); // We will be moving this window } effects->prePaintWindow(w, data, time); } void PresentWindowsEffect::paintWindow(EffectWindow *w, int mask, QRegion region, WindowPaintData &data) { if (m_activated || m_motionManager.areWindowsMoving()) { DataHash::const_iterator winData = m_windowData.constFind(w); if (winData == m_windowData.constEnd() || (w->isDock() && m_showPanel)) { // in case the panel should be shown just display it without any changes effects->paintWindow(w, mask, region, data); return; } mask |= PAINT_WINDOW_LANCZOS; // Apply opacity and brightness data.multiplyOpacity(winData->opacity); data.multiplyBrightness(interpolate(0.40, 1.0, winData->highlight)); if (m_motionManager.isManaging(w)) { if (w->isDesktop()) { effects->paintWindow(w, mask, region, data); } m_motionManager.apply(w, data); QRect rect = m_motionManager.transformedGeometry(w).toRect(); if (m_activated && winData->highlight > 0.0) { // scale the window (interpolated by the highlight level) to at least 105% or to cover 1/16 of the screen size - yet keep it in screen bounds QRect area = effects->clientArea(FullScreenArea, w); QSizeF effSize(w->width()*data.xScale(), w->height()*data.yScale()); const float xr = area.width()/effSize.width(); const float yr = area.height()/effSize.height(); float tScale = 0.0; if (xr < yr) { tScale = qMax(xr/4.0, yr/32.0); } else { tScale = qMax(xr/32.0, yr/4.0); } if (tScale < 1.05) { tScale = 1.05; } if (effSize.width()*tScale > area.width()) tScale = area.width() / effSize.width(); if (effSize.height()*tScale > area.height()) tScale = area.height() / effSize.height(); const qreal scale = interpolate(1.0, tScale, winData->highlight); if (scale > 1.0) { if (scale < tScale) // don't use lanczos during transition mask &= ~PAINT_WINDOW_LANCZOS; const float df = (tScale-1.0f)*0.5f; int tx = qRound(rect.width()*df); int ty = qRound(rect.height()*df); QRect tRect(rect.adjusted(-tx, -ty, tx, ty)); tx = qMax(tRect.x(), area.x()) + qMin(0, area.right()-tRect.right()); ty = qMax(tRect.y(), area.y()) + qMin(0, area.bottom()-tRect.bottom()); tx = qRound((tx-rect.x())*winData->highlight); ty = qRound((ty-rect.y())*winData->highlight); rect.translate(tx,ty); rect.setWidth(rect.width()*scale); rect.setHeight(rect.height()*scale); data *= QVector2D(scale, scale); data += QPoint(tx, ty); } } if (m_motionManager.areWindowsMoving()) { mask &= ~PAINT_WINDOW_LANCZOS; } effects->paintWindow(w, mask, region, data); if (m_showIcons) { QPoint point(rect.x() + rect.width() * 0.95, rect.y() + rect.height() * 0.95); winData->iconFrame->setPosition(point); if (effects->compositingType() == KWin::OpenGL2Compositing && data.shader) { const float a = 0.9 * data.opacity() * m_decalOpacity * 0.75; data.shader->setUniform(GLShader::ModulationConstant, QVector4D(a, a, a, a)); } winData->iconFrame->render(region, 0.9 * data.opacity() * m_decalOpacity, 0.75); } if (m_showCaptions) { QPoint point(rect.x() + rect.width() / 2, rect.y() + rect.height() / 2); winData->textFrame->setPosition(point); if (effects->compositingType() == KWin::OpenGL2Compositing && data.shader) { const float a = 0.9 * data.opacity() * m_decalOpacity * 0.75; data.shader->setUniform(GLShader::ModulationConstant, QVector4D(a, a, a, a)); } winData->textFrame->render(region, 0.9 * data.opacity() * m_decalOpacity, 0.75); } } else { if (w == m_closeWindow && m_closeView && !m_closeView->isVisible()) { data.setOpacity(0); } effects->paintWindow(w, mask, region, data); } } else effects->paintWindow(w, mask, region, data); } //----------------------------------------------------------------------------- // User interaction void PresentWindowsEffect::slotWindowAdded(EffectWindow *w) { if (!m_activated) return; WindowData *winData = &m_windowData[w]; winData->visible = isVisibleWindow(w); winData->opacity = 0.0; winData->highlight = 0.0; winData->textFrame = effects->effectFrame(EffectFrameUnstyled, false); QFont font; font.setBold(true); font.setPointSize(12); winData->textFrame->setFont(font); winData->iconFrame = effects->effectFrame(EffectFrameUnstyled, false); winData->iconFrame->setAlignment(Qt::AlignRight | Qt::AlignBottom); winData->iconFrame->setIcon(w->icon()); winData->iconFrame->setIconSize(QSize(32, 32)); if (isSelectableWindow(w)) { m_motionManager.manage(w); rearrangeWindows(); } if (m_closeView && w == effects->findWindow(m_closeView->winId())) { if (m_closeWindow != w) { DataHash::iterator winDataIt = m_windowData.find(m_closeWindow); if (winDataIt != m_windowData.end()) { if (winDataIt->referenced) { m_closeWindow->unrefWindow(); } m_windowData.erase(winDataIt); } } winData->visible = true; winData->highlight = 1.0; m_closeWindow = w; w->setData(WindowForceBlurRole, QVariant(true)); w->setData(WindowForceBackgroundContrastRole, QVariant(true)); } } void PresentWindowsEffect::slotWindowClosed(EffectWindow *w) { if (m_managerWindow == w) - m_managerWindow = NULL; + m_managerWindow = nullptr; DataHash::iterator winData = m_windowData.find(w); if (winData == m_windowData.end()) return; winData->deleted = true; if (!winData->referenced) { winData->referenced = true; w->refWindow(); } if (m_highlightedWindow == w) setHighlightedWindow(findFirstWindow()); if (m_closeWindow == w) { return; // don't rearrange, get's nulled when unref'd } rearrangeWindows(); foreach (EffectWindow *w, m_motionManager.managedWindows()) { winData = m_windowData.find(w); if (winData != m_windowData.end() && !winData->deleted) return; // found one that is not deleted? then we go on } setActive(false); //else no need to keep this open } void PresentWindowsEffect::slotWindowDeleted(EffectWindow *w) { DataHash::iterator winData = m_windowData.find(w); if (winData == m_windowData.end()) return; delete winData->textFrame; delete winData->iconFrame; m_windowData.erase(winData); m_motionManager.unmanage(w); } void PresentWindowsEffect::slotWindowGeometryShapeChanged(EffectWindow* w, const QRect& old) { Q_UNUSED(old) if (!m_activated) return; if (!m_windowData.contains(w)) return; if (w != m_closeWindow) rearrangeWindows(); } bool PresentWindowsEffect::borderActivated(ElectricBorder border) { int mode = 0; if (m_borderActivate.contains(border)) mode |= 1; else if (m_borderActivateAll.contains(border)) mode |= 2; else if (m_borderActivateClass.contains(border)) mode |= 4; if (!mode) return false; if (effects->activeFullScreenEffect() && effects->activeFullScreenEffect() != this) return true; if (mode & 1) toggleActive(); else if (mode & 2) toggleActiveAllDesktops(); else if (mode & 4) toggleActiveClass(); return true; } void PresentWindowsEffect::windowInputMouseEvent(QEvent *e) { QMouseEvent* me = dynamic_cast< QMouseEvent* >(e); if (!me) { return; } if (m_closeView) { const bool contains = m_closeView->geometry().contains(me->pos()); if (!m_closeView->isVisible() && contains) { updateCloseWindow(); } if (m_closeView->isVisible()) { const QPoint widgetPos = m_closeView->mapFromGlobal(me->pos()); // const QPointF scenePos = m_closeView->mapToScene(widgetPos); QMouseEvent event(me->type(), widgetPos, me->pos(), me->button(), me->buttons(), me->modifiers()); m_closeView->windowInputMouseEvent(&event); if (contains) { // filter out return; } } } inputEventUpdate(me->pos(), me->type(), me->button()); } void PresentWindowsEffect::inputEventUpdate(const QPoint &pos, QEvent::Type type, Qt::MouseButton button) { // Which window are we hovering over? Always trigger as we don't always get move events before clicking // We cannot use m_motionManager.windowAtPoint() as the window might not be visible EffectWindowList windows = m_motionManager.managedWindows(); bool hovering = false; - EffectWindow *highlightCandidate = NULL; + EffectWindow *highlightCandidate = nullptr; for (int i = 0; i < windows.size(); ++i) { DataHash::const_iterator winData = m_windowData.constFind(windows.at(i)); if (winData == m_windowData.constEnd()) continue; if (m_motionManager.transformedGeometry(windows.at(i)).contains(pos) && winData->visible && !winData->deleted) { hovering = true; if (windows.at(i) && m_highlightedWindow != windows.at(i)) highlightCandidate = windows.at(i); break; } } if (!hovering) - setHighlightedWindow(NULL); + setHighlightedWindow(nullptr); if (m_highlightedWindow && m_motionManager.transformedGeometry(m_highlightedWindow).contains(pos)) updateCloseWindow(); else if (m_closeView) m_closeView->hide(); if (type == QEvent::MouseButtonRelease) { if (highlightCandidate) setHighlightedWindow(highlightCandidate); if (button == Qt::LeftButton) { if (hovering) { // mouse is hovering above a window - use MouseActionsWindow mouseActionWindow(m_leftButtonWindow); } else { // mouse is hovering above desktop - use MouseActionsDesktop mouseActionDesktop(m_leftButtonDesktop); } } if (button == Qt::MidButton) { if (hovering) { // mouse is hovering above a window - use MouseActionsWindow mouseActionWindow(m_middleButtonWindow); } else { // mouse is hovering above desktop - use MouseActionsDesktop mouseActionDesktop(m_middleButtonDesktop); } } if (button == Qt::RightButton) { if (hovering) { // mouse is hovering above a window - use MouseActionsWindow mouseActionWindow(m_rightButtonWindow); } else { // mouse is hovering above desktop - use MouseActionsDesktop mouseActionDesktop(m_rightButtonDesktop); } } } else if (highlightCandidate && !m_motionManager.areWindowsMoving()) setHighlightedWindow(highlightCandidate); } bool PresentWindowsEffect::touchDown(qint32 id, const QPointF &pos, quint32 time) { Q_UNUSED(time) if (!m_activated) { return false; } // only if we don't track a touch id yet if (!m_touch.active) { m_touch.active = true; m_touch.id = id; inputEventUpdate(pos.toPoint()); } return true; } bool PresentWindowsEffect::touchMotion(qint32 id, const QPointF &pos, quint32 time) { Q_UNUSED(id) Q_UNUSED(time) if (!m_activated) { return false; } if (m_touch.active && m_touch.id == id) { // only update for the touch id we track inputEventUpdate(pos.toPoint()); } return true; } bool PresentWindowsEffect::touchUp(qint32 id, quint32 time) { Q_UNUSED(id) Q_UNUSED(time) if (!m_activated) { return false; } if (m_touch.active && m_touch.id == id) { m_touch.active = false; m_touch.id = 0; if (m_highlightedWindow) { mouseActionWindow(m_leftButtonWindow); } } return true; } void PresentWindowsEffect::mouseActionWindow(WindowMouseAction& action) { switch(action) { case WindowActivateAction: if (m_highlightedWindow) effects->activateWindow(m_highlightedWindow); setActive(false); break; case WindowExitAction: setActive(false); break; case WindowToCurrentDesktopAction: if (m_highlightedWindow) effects->windowToDesktop(m_highlightedWindow, effects->currentDesktop()); break; case WindowToAllDesktopsAction: if (m_highlightedWindow) { if (m_highlightedWindow->isOnAllDesktops()) effects->windowToDesktop(m_highlightedWindow, effects->currentDesktop()); else effects->windowToDesktop(m_highlightedWindow, NET::OnAllDesktops); } break; case WindowMinimizeAction: if (m_highlightedWindow) { if (m_highlightedWindow->isMinimized()) m_highlightedWindow->unminimize(); else m_highlightedWindow->minimize(); } break; case WindowCloseAction: if (m_highlightedWindow) { m_highlightedWindow->closeWindow(); } break; default: break; } } void PresentWindowsEffect::mouseActionDesktop(DesktopMouseAction& action) { switch(action) { case DesktopActivateAction: if (m_highlightedWindow) effects->activateWindow(m_highlightedWindow); setActive(false); break; case DesktopExitAction: setActive(false); break; case DesktopShowDesktopAction: effects->setShowingDesktop(true); setActive(false); default: break; } } void PresentWindowsEffect::grabbedKeyboardEvent(QKeyEvent *e) { if (e->type() == QEvent::KeyPress) { // check for global shortcuts // HACK: keyboard grab disables the global shortcuts so we have to check for global shortcut (bug 156155) if (m_mode == ModeCurrentDesktop && shortcut.contains(e->key() + e->modifiers())) { toggleActive(); return; } if (m_mode == ModeAllDesktops && shortcutAll.contains(e->key() + e->modifiers())) { toggleActiveAllDesktops(); return; } if (m_mode == ModeWindowClass && shortcutClass.contains(e->key() + e->modifiers())) { toggleActiveClass(); return; } switch(e->key()) { // Wrap only if not auto-repeating case Qt::Key_Left: setHighlightedWindow(relativeWindow(m_highlightedWindow, -1, 0, !e->isAutoRepeat())); break; case Qt::Key_Right: setHighlightedWindow(relativeWindow(m_highlightedWindow, 1, 0, !e->isAutoRepeat())); break; case Qt::Key_Up: setHighlightedWindow(relativeWindow(m_highlightedWindow, 0, -1, !e->isAutoRepeat())); break; case Qt::Key_Down: setHighlightedWindow(relativeWindow(m_highlightedWindow, 0, 1, !e->isAutoRepeat())); break; case Qt::Key_Home: setHighlightedWindow(relativeWindow(m_highlightedWindow, -1000, 0, false)); break; case Qt::Key_End: setHighlightedWindow(relativeWindow(m_highlightedWindow, 1000, 0, false)); break; case Qt::Key_PageUp: setHighlightedWindow(relativeWindow(m_highlightedWindow, 0, -1000, false)); break; case Qt::Key_PageDown: setHighlightedWindow(relativeWindow(m_highlightedWindow, 0, 1000, false)); break; case Qt::Key_Backspace: if (!m_windowFilter.isEmpty()) { m_windowFilter.remove(m_windowFilter.length() - 1, 1); updateFilterFrame(); rearrangeWindows(); } return; case Qt::Key_Escape: setActive(false); return; case Qt::Key_Return: case Qt::Key_Enter: if (m_highlightedWindow) effects->activateWindow(m_highlightedWindow); setActive(false); return; case Qt::Key_Tab: return; // Nothing at the moment case Qt::Key_Delete: if (!m_windowFilter.isEmpty()) { m_windowFilter.clear(); updateFilterFrame(); rearrangeWindows(); } break; case 0: return; // HACK: Workaround for Qt bug on unbound keys (#178547) default: if (!e->text().isEmpty()) { m_windowFilter.append(e->text()); updateFilterFrame(); rearrangeWindows(); return; } break; } } } //----------------------------------------------------------------------------- // Atom handling void PresentWindowsEffect::slotPropertyNotify(EffectWindow* w, long a) { if (m_atomDesktop == XCB_ATOM_NONE && m_atomWindows == XCB_ATOM_NONE) { return; } if (!w || (a != m_atomDesktop && a != m_atomWindows)) return; // Not our atom if (a == m_atomDesktop) { QByteArray byteData = w->readProperty(m_atomDesktop, m_atomDesktop, 32); if (byteData.length() < 1) { // Property was removed, end present windows setActive(false); return; } auto* data = reinterpret_cast(byteData.data()); if (!data[0]) { // Purposely ending present windows by issuing a NULL target setActive(false); return; } // present windows is active so don't do anything if (m_activated) return; int desktop = data[0]; if (desktop > effects->numberOfDesktops()) return; if (desktop == -1) toggleActiveAllDesktops(); else { m_mode = ModeSelectedDesktop; m_desktop = desktop; m_managerWindow = w; setActive(true); } } else if (a == m_atomWindows) { QByteArray byteData = w->readProperty(m_atomWindows, m_atomWindows, 32); if (byteData.length() < 1) { // Property was removed, end present windows setActive(false); return; } auto* data = reinterpret_cast(byteData.data()); if (!data[0]) { // Purposely ending present windows by issuing a NULL target setActive(false); return; } // present windows is active so don't do anything if (m_activated) return; // for security clear selected windows m_selectedWindows.clear(); int length = byteData.length() / sizeof(data[0]); for (int i = 0; i < length; i++) { EffectWindow* foundWin = effects->findWindow(data[i]); if (!foundWin) { qCDebug(KWINEFFECTS) << "Invalid window targetted for present windows. Requested:" << data[i]; continue; } m_selectedWindows.append(foundWin); } m_mode = ModeWindowGroup; m_managerWindow = w; setActive(true); } } //----------------------------------------------------------------------------- // Window rearranging void PresentWindowsEffect::rearrangeWindows() { if (!m_activated) return; effects->addRepaintFull(); // Trigger the first repaint if (m_closeView) m_closeView->hide(); // Work out which windows are on which screens EffectWindowList windowlist; QList windowlists; for (int i = 0; i < effects->numScreens(); i++) windowlists.append(EffectWindowList()); if (m_windowFilter.isEmpty()) { windowlist = m_motionManager.managedWindows(); foreach (EffectWindow * w, m_motionManager.managedWindows()) { DataHash::iterator winData = m_windowData.find(w); if (winData == m_windowData.end() || winData->deleted) continue; // don't include closed windows windowlists[w->screen()].append(w); winData->visible = true; } } else { // Can we move this filtering somewhere else? foreach (EffectWindow * w, m_motionManager.managedWindows()) { DataHash::iterator winData = m_windowData.find(w); if (winData == m_windowData.end() || winData->deleted) continue; // don't include closed windows if (w->caption().contains(m_windowFilter, Qt::CaseInsensitive) || w->windowClass().contains(m_windowFilter, Qt::CaseInsensitive) || w->windowRole().contains(m_windowFilter, Qt::CaseInsensitive)) { windowlist.append(w); windowlists[w->screen()].append(w); winData->visible = true; } else winData->visible = false; } } if (windowlist.isEmpty()) { - setHighlightedWindow(NULL); + setHighlightedWindow(nullptr); return; } // We filtered out the highlighted window if (m_highlightedWindow) { DataHash::iterator winData = m_windowData.find(m_highlightedWindow); if (winData != m_windowData.end() && !winData->visible) setHighlightedWindow(findFirstWindow()); } else setHighlightedWindow(findFirstWindow()); int screens = effects->numScreens(); for (int screen = 0; screen < screens; screen++) { EffectWindowList windows; windows = windowlists[screen]; // Don't rearrange if the grid is the same size as what it was before to prevent // windows moving to a better spot if one was filtered out. if (m_layoutMode == LayoutRegularGrid && m_gridSizes[screen].columns && m_gridSizes[screen].rows && windows.size() < m_gridSizes[screen].columns * m_gridSizes[screen].rows && windows.size() > (m_gridSizes[screen].columns - 1) * m_gridSizes[screen].rows && windows.size() > m_gridSizes[screen].columns *(m_gridSizes[screen].rows - 1)) continue; // No point continuing if there is no windows to process if (!windows.count()) continue; calculateWindowTransformations(windows, screen, m_motionManager); } // Resize text frames if required - QFontMetrics* metrics = NULL; // All fonts are the same + QFontMetrics* metrics = nullptr; // All fonts are the same foreach (EffectWindow * w, m_motionManager.managedWindows()) { DataHash::iterator winData = m_windowData.find(w); if (winData == m_windowData.end()) continue; if (!metrics) metrics = new QFontMetrics(winData->textFrame->font()); QRect geom = m_motionManager.targetGeometry(w).toRect(); QString string = metrics->elidedText(w->caption(), Qt::ElideRight, geom.width() * 0.9); if (string != winData->textFrame->text()) winData->textFrame->setText(string); } delete metrics; } void PresentWindowsEffect::calculateWindowTransformations(EffectWindowList windowlist, int screen, WindowMotionManager& motionManager, bool external) { if (m_layoutMode == LayoutRegularGrid) calculateWindowTransformationsClosest(windowlist, screen, motionManager); else if (m_layoutMode == LayoutFlexibleGrid) calculateWindowTransformationsKompose(windowlist, screen, motionManager); else calculateWindowTransformationsNatural(windowlist, screen, motionManager); // If called externally we don't need to remember this data if (external) m_windowData.clear(); } static inline int distance(QPoint &pos1, QPoint &pos2) { const int xdiff = pos1.x() - pos2.x(); const int ydiff = pos1.y() - pos2.y(); return int(sqrt(float(xdiff*xdiff + ydiff*ydiff))); } void PresentWindowsEffect::calculateWindowTransformationsClosest(EffectWindowList windowlist, int screen, WindowMotionManager& motionManager) { // This layout mode requires at least one window visible if (windowlist.count() == 0) return; QRect area = effects->clientArea(ScreenArea, screen, effects->currentDesktop()); if (m_showPanel) // reserve space for the panel area = effects->clientArea(MaximizeArea, screen, effects->currentDesktop()); int columns = int(ceil(sqrt(double(windowlist.count())))); int rows = int(ceil(windowlist.count() / double(columns))); // Remember the size for later // If we are using this layout externally we don't need to remember m_gridSizes. if (m_gridSizes.size() != 0) { m_gridSizes[screen].columns = columns; m_gridSizes[screen].rows = rows; } // Assign slots int slotWidth = area.width() / columns; int slotHeight = area.height() / rows; QVector takenSlots; takenSlots.resize(rows*columns); takenSlots.fill(0); // precalculate all slot centers QVector slotCenters; slotCenters.resize(rows*columns); for (int x = 0; x < columns; ++x) for (int y = 0; y < rows; ++y) { slotCenters[x + y*columns] = QPoint(area.x() + slotWidth * x + slotWidth / 2, area.y() + slotHeight * y + slotHeight / 2); } // Assign each window to the closest available slot EffectWindowList tmpList = windowlist; // use a QLinkedList copy instead? QPoint otherPos; while (!tmpList.isEmpty()) { EffectWindow *w = tmpList.first(); int slotCandidate = -1, slotCandidateDistance = INT_MAX; QPoint pos = w->geometry().center(); for (int i = 0; i < columns*rows; ++i) { // all slots const int dist = distance(pos, slotCenters[i]); if (dist < slotCandidateDistance) { // window is interested in this slot EffectWindow *occupier = takenSlots[i]; Q_ASSERT(occupier != w); if (!occupier || dist < distance((otherPos = occupier->geometry().center()), slotCenters[i])) { // either nobody lives here, or we're better - takeover the slot if it's our best slotCandidate = i; slotCandidateDistance = dist; } } } Q_ASSERT(slotCandidate != -1); if (takenSlots[slotCandidate]) tmpList << takenSlots[slotCandidate]; // occupier needs a new home now :p tmpList.removeAll(w); takenSlots[slotCandidate] = w; // ...and we rumble in =) } for (int slot = 0; slot < columns*rows; ++slot) { EffectWindow *w = takenSlots[slot]; if (!w) // some slots might be empty continue; // Work out where the slot is QRect target( area.x() + (slot % columns) * slotWidth, area.y() + (slot / columns) * slotHeight, slotWidth, slotHeight); target.adjust(10, 10, -10, -10); // Borders double scale; if (target.width() / double(w->width()) < target.height() / double(w->height())) { // Center vertically scale = target.width() / double(w->width()); target.moveTop(target.top() + (target.height() - int(w->height() * scale)) / 2); target.setHeight(int(w->height() * scale)); } else { // Center horizontally scale = target.height() / double(w->height()); target.moveLeft(target.left() + (target.width() - int(w->width() * scale)) / 2); target.setWidth(int(w->width() * scale)); } // Don't scale the windows too much if (scale > 2.0 || (scale > 1.0 && (w->width() > 300 || w->height() > 300))) { scale = (w->width() > 300 || w->height() > 300) ? 1.0 : 2.0; target = QRect( target.center().x() - int(w->width() * scale) / 2, target.center().y() - int(w->height() * scale) / 2, scale * w->width(), scale * w->height()); } motionManager.moveWindow(w, target); } } void PresentWindowsEffect::calculateWindowTransformationsKompose(EffectWindowList windowlist, int screen, WindowMotionManager& motionManager) { // This layout mode requires at least one window visible if (windowlist.count() == 0) return; QRect availRect = effects->clientArea(ScreenArea, screen, effects->currentDesktop()); if (m_showPanel) // reserve space for the panel availRect = effects->clientArea(MaximizeArea, screen, effects->currentDesktop()); std::sort(windowlist.begin(), windowlist.end()); // The location of the windows should not depend on the stacking order // Following code is taken from Kompose 0.5.4, src/komposelayout.cpp int spacing = 10; int rows, columns; double parentRatio = availRect.width() / (double)availRect.height(); // Use more columns than rows when parent's width > parent's height if (parentRatio > 1) { columns = (int)ceil(sqrt((double)windowlist.count())); rows = (int)ceil((double)windowlist.count() / (double)columns); } else { rows = (int)ceil(sqrt((double)windowlist.count())); columns = (int)ceil((double)windowlist.count() / (double)rows); } //qCDebug(KWINEFFECTS) << "Using " << rows << " rows & " << columns << " columns for " << windowlist.count() << " clients"; // Calculate width & height int w = (availRect.width() - (columns + 1) * spacing) / columns; int h = (availRect.height() - (rows + 1) * spacing) / rows; EffectWindowList::iterator it(windowlist.begin()); QList geometryRects; QList maxRowHeights; // Process rows for (int i = 0; i < rows; ++i) { int xOffsetFromLastCol = 0; int maxHeightInRow = 0; // Process columns for (int j = 0; j < columns; ++j) { EffectWindow* window; // Check for end of List if (it == windowlist.end()) break; window = *it; // Calculate width and height of widget double ratio = aspectRatio(window); int widgetw = 100; int widgeth = 100; int usableW = w; int usableH = h; // use width of two boxes if there is no right neighbour if (window == windowlist.last() && j != columns - 1) { usableW = 2 * w; } ++it; // We need access to the neighbour in the following // expand if right neighbour has ratio < 1 if (j != columns - 1 && it != windowlist.end() && aspectRatio(*it) < 1) { int addW = w - widthForHeight(*it, h); if (addW > 0) { usableW = w + addW; } } if (ratio == -1) { widgetw = w; widgeth = h; } else { double widthByHeight = widthForHeight(window, usableH); double heightByWidth = heightForWidth(window, usableW); if ((ratio >= 1.0 && heightByWidth <= usableH) || (ratio < 1.0 && widthByHeight > usableW)) { widgetw = usableW; widgeth = (int)heightByWidth; } else if ((ratio < 1.0 && widthByHeight <= usableW) || (ratio >= 1.0 && heightByWidth > usableH)) { widgeth = usableH; widgetw = (int)widthByHeight; } // Don't upscale large-ish windows if (widgetw > window->width() && (window->width() > 300 || window->height() > 300)) { widgetw = window->width(); widgeth = window->height(); } } // Set the Widget's size int alignmentXoffset = 0; int alignmentYoffset = 0; if (i == 0 && h > widgeth) alignmentYoffset = h - widgeth; if (j == 0 && w > widgetw) alignmentXoffset = w - widgetw; QRect geom(availRect.x() + j *(w + spacing) + spacing + alignmentXoffset + xOffsetFromLastCol, availRect.y() + i *(h + spacing) + spacing + alignmentYoffset, widgetw, widgeth); geometryRects.append(geom); // Set the x offset for the next column if (alignmentXoffset == 0) xOffsetFromLastCol += widgetw - w; if (maxHeightInRow < widgeth) maxHeightInRow = widgeth; } maxRowHeights.append(maxHeightInRow); } int topOffset = 0; for (int i = 0; i < rows; i++) { for (int j = 0; j < columns; j++) { int pos = i * columns + j; if (pos >= windowlist.count()) break; EffectWindow* window = windowlist[pos]; QRect target = geometryRects[pos]; target.setY(target.y() + topOffset); // @Marrtin: any idea what this is good for? // DataHash::iterator winData = m_windowData.find(window); // if (winData != m_windowData.end()) // winData->slot = pos; motionManager.moveWindow(window, target); //qCDebug(KWINEFFECTS) << "Window '" << window->caption() << "' gets moved to (" << // mWindowData[window].area.left() << "; " << mWindowData[window].area.right() << // "), scale: " << mWindowData[window].scale << endl; } if (maxRowHeights[i] - h > 0) topOffset += maxRowHeights[i] - h; } } void PresentWindowsEffect::calculateWindowTransformationsNatural(EffectWindowList windowlist, int screen, WindowMotionManager& motionManager) { // If windows do not overlap they scale into nothingness, fix by resetting. To reproduce // just have a single window on a Xinerama screen or have two windows that do not touch. // TODO: Work out why this happens, is most likely a bug in the manager. foreach (EffectWindow * w, windowlist) if (motionManager.transformedGeometry(w) == w->geometry()) motionManager.reset(w); if (windowlist.count() == 1) { // Just move the window to its original location to save time if (effects->clientArea(FullScreenArea, windowlist[0]).contains(windowlist[0]->geometry())) { motionManager.moveWindow(windowlist[0], windowlist[0]->geometry()); return; } } // As we are using pseudo-random movement (See "slot") we need to make sure the list // is always sorted the same way no matter which window is currently active. std::sort(windowlist.begin(), windowlist.end()); QRect area = effects->clientArea(ScreenArea, screen, effects->currentDesktop()); if (m_showPanel) // reserve space for the panel area = effects->clientArea(MaximizeArea, screen, effects->currentDesktop()); QRect bounds = area; int direction = 0; QHash targets; QHash directions; foreach (EffectWindow * w, windowlist) { bounds = bounds.united(w->geometry()); targets[w] = w->geometry(); // Reuse the unused "slot" as a preferred direction attribute. This is used when the window // is on the edge of the screen to try to use as much screen real estate as possible. directions[w] = direction; direction++; if (direction == 4) direction = 0; } // Iterate over all windows, if two overlap push them apart _slightly_ as we try to // brute-force the most optimal positions over many iterations. bool overlap; do { overlap = false; foreach (EffectWindow * w, windowlist) { QRect *target_w = &targets[w]; foreach (EffectWindow * e, windowlist) { if (w == e) continue; QRect *target_e = &targets[e]; if (target_w->adjusted(-5, -5, 5, 5).intersects(target_e->adjusted(-5, -5, 5, 5))) { overlap = true; // Determine pushing direction QPoint diff(target_e->center() - target_w->center()); // Prevent dividing by zero and non-movement if (diff.x() == 0 && diff.y() == 0) diff.setX(1); // Try to keep screen aspect ratio //if (bounds.height() / bounds.width() > area.height() / area.width()) // diff.setY(diff.y() / 2); //else // diff.setX(diff.x() / 2); // Approximate a vector of between 10px and 20px in magnitude in the same direction diff *= m_accuracy / double(diff.manhattanLength()); // Move both windows apart target_w->translate(-diff); target_e->translate(diff); // Try to keep the bounding rect the same aspect as the screen so that more // screen real estate is utilised. We do this by splitting the screen into nine // equal sections, if the window center is in any of the corner sections pull the // window towards the outer corner. If it is in any of the other edge sections // alternate between each corner on that edge. We don't want to determine it // randomly as it will not produce consistant locations when using the filter. // Only move one window so we don't cause large amounts of unnecessary zooming // in some situations. We need to do this even when expanding later just in case // all windows are the same size. // (We are using an old bounding rect for this, hopefully it doesn't matter) int xSection = (target_w->x() - bounds.x()) / (bounds.width() / 3); int ySection = (target_w->y() - bounds.y()) / (bounds.height() / 3); diff = QPoint(0, 0); if (xSection != 1 || ySection != 1) { // Remove this if you want the center to pull as well if (xSection == 1) xSection = (directions[w] / 2 ? 2 : 0); if (ySection == 1) ySection = (directions[w] % 2 ? 2 : 0); } if (xSection == 0 && ySection == 0) diff = QPoint(bounds.topLeft() - target_w->center()); if (xSection == 2 && ySection == 0) diff = QPoint(bounds.topRight() - target_w->center()); if (xSection == 2 && ySection == 2) diff = QPoint(bounds.bottomRight() - target_w->center()); if (xSection == 0 && ySection == 2) diff = QPoint(bounds.bottomLeft() - target_w->center()); if (diff.x() != 0 || diff.y() != 0) { diff *= m_accuracy / double(diff.manhattanLength()); target_w->translate(diff); } // Update bounding rect bounds = bounds.united(*target_w); bounds = bounds.united(*target_e); } } } } while (overlap); // Work out scaling by getting the most top-left and most bottom-right window coords. // The 20's and 10's are so that the windows don't touch the edge of the screen. double scale; if (bounds == area) scale = 1.0; // Don't add borders to the screen else if (area.width() / double(bounds.width()) < area.height() / double(bounds.height())) scale = (area.width() - 20) / double(bounds.width()); else scale = (area.height() - 20) / double(bounds.height()); // Make bounding rect fill the screen size for later steps bounds = QRect( bounds.x() - (area.width() - 20 - bounds.width() * scale) / 2 - 10 / scale, bounds.y() - (area.height() - 20 - bounds.height() * scale) / 2 - 10 / scale, area.width() / scale, area.height() / scale ); // Move all windows back onto the screen and set their scale QHash::iterator target = targets.begin(); while (target != targets.end()) { target->setRect((target->x() - bounds.x()) * scale + area.x(), (target->y() - bounds.y()) * scale + area.y(), target->width() * scale, target->height() * scale ); ++target; } // Try to fill the gaps by enlarging windows if they have the space if (m_fillGaps) { // Don't expand onto or over the border QRegion borderRegion(area.adjusted(-200, -200, 200, 200)); borderRegion ^= area.adjusted(10 / scale, 10 / scale, -10 / scale, -10 / scale); bool moved; do { moved = false; foreach (EffectWindow * w, windowlist) { QRect oldRect; QRect *target = &targets[w]; // This may cause some slight distortion if the windows are enlarged a large amount int widthDiff = m_accuracy; int heightDiff = heightForWidth(w, target->width() + widthDiff) - target->height(); int xDiff = widthDiff / 2; // Also move a bit in the direction of the enlarge, allows the int yDiff = heightDiff / 2; // center windows to be enlarged if there is gaps on the side. // heightDiff (and yDiff) will be re-computed after each successfull enlargement attempt // so that the error introduced in the window's aspect ratio is minimized // Attempt enlarging to the top-right oldRect = *target; target->setRect(target->x() + xDiff, target->y() - yDiff - heightDiff, target->width() + widthDiff, target->height() + heightDiff ); if (isOverlappingAny(w, targets, borderRegion)) *target = oldRect; else { moved = true; heightDiff = heightForWidth(w, target->width() + widthDiff) - target->height(); yDiff = heightDiff / 2; } // Attempt enlarging to the bottom-right oldRect = *target; target->setRect( target->x() + xDiff, target->y() + yDiff, target->width() + widthDiff, target->height() + heightDiff ); if (isOverlappingAny(w, targets, borderRegion)) *target = oldRect; else { moved = true; heightDiff = heightForWidth(w, target->width() + widthDiff) - target->height(); yDiff = heightDiff / 2; } // Attempt enlarging to the bottom-left oldRect = *target; target->setRect( target->x() - xDiff - widthDiff, target->y() + yDiff, target->width() + widthDiff, target->height() + heightDiff ); if (isOverlappingAny(w, targets, borderRegion)) *target = oldRect; else { moved = true; heightDiff = heightForWidth(w, target->width() + widthDiff) - target->height(); yDiff = heightDiff / 2; } // Attempt enlarging to the top-left oldRect = *target; target->setRect( target->x() - xDiff - widthDiff, target->y() - yDiff - heightDiff, target->width() + widthDiff, target->height() + heightDiff ); if (isOverlappingAny(w, targets, borderRegion)) *target = oldRect; else moved = true; } } while (moved); // The expanding code above can actually enlarge windows over 1.0/2.0 scale, we don't like this // We can't add this to the loop above as it would cause a never-ending loop so we have to make // do with the less-than-optimal space usage with using this method. foreach (EffectWindow * w, windowlist) { QRect *target = &targets[w]; double scale = target->width() / double(w->width()); if (scale > 2.0 || (scale > 1.0 && (w->width() > 300 || w->height() > 300))) { scale = (w->width() > 300 || w->height() > 300) ? 1.0 : 2.0; target->setRect( target->center().x() - int(w->width() * scale) / 2, target->center().y() - int(w->height() * scale) / 2, w->width() * scale, w->height() * scale); } } } // Notify the motion manager of the targets foreach (EffectWindow * w, windowlist) motionManager.moveWindow(w, targets.value(w)); } bool PresentWindowsEffect::isOverlappingAny(EffectWindow *w, const QHash &targets, const QRegion &border) { QHash::const_iterator winTarget = targets.find(w); if (winTarget == targets.constEnd()) return false; if (border.intersects(*winTarget)) return true; // Is there a better way to do this? QHash::const_iterator target; for (target = targets.constBegin(); target != targets.constEnd(); ++target) { if (target == winTarget) continue; if (winTarget->adjusted(-5, -5, 5, 5).intersects(target->adjusted(-5, -5, 5, 5))) return true; } return false; } //----------------------------------------------------------------------------- // Activation void PresentWindowsEffect::setActive(bool active) { if (effects->activeFullScreenEffect() && effects->activeFullScreenEffect() != this) return; if (m_activated == active) return; m_activated = active; if (m_activated) { effects->setShowingDesktop(false); m_needInitialSelection = true; m_closeButtonCorner = (Qt::Corner)effects->kwinOption(KWin::CloseButtonCorner).toInt(); m_decalOpacity = 0.0; - m_highlightedWindow = NULL; + m_highlightedWindow = nullptr; m_windowFilter.clear(); if (!(m_doNotCloseWindows || m_closeView)) { m_closeView = new CloseWindowView(); connect(m_closeView, &CloseWindowView::requestClose, this, &PresentWindowsEffect::closeWindow); } // Add every single window to m_windowData (Just calling [w] creates it) foreach (EffectWindow * w, effects->stackingOrder()) { DataHash::iterator winData; if ((winData = m_windowData.find(w)) != m_windowData.end()) { winData->visible = isVisibleWindow(w); continue; // Happens if we reactivate before the ending animation finishes } winData = m_windowData.insert(w, WindowData()); winData->visible = isVisibleWindow(w); winData->deleted = false; winData->referenced = false; winData->opacity = 0.0; if (w->isOnCurrentDesktop() && !w->isMinimized()) winData->opacity = 1.0; winData->highlight = 1.0; winData->textFrame = effects->effectFrame(EffectFrameUnstyled, false); QFont font; font.setBold(true); font.setPointSize(12); winData->textFrame->setFont(font); winData->iconFrame = effects->effectFrame(EffectFrameUnstyled, false); winData->iconFrame->setAlignment(Qt::AlignRight | Qt::AlignBottom); winData->iconFrame->setIcon(w->icon()); winData->iconFrame->setIconSize(QSize(32, 32)); } // Filter out special windows such as panels and taskbars foreach (EffectWindow * w, effects->stackingOrder()) { if (isSelectableWindow(w)) { m_motionManager.manage(w); } } if (m_motionManager.managedWindows().isEmpty() || ((m_motionManager.managedWindows().count() == 1) && m_motionManager.managedWindows().first()->isOnCurrentDesktop() && (m_ignoreMinimized || !m_motionManager.managedWindows().first()->isMinimized()))) { // No point triggering if there is nothing to do m_activated = false; DataHash::iterator i = m_windowData.begin(); while (i != m_windowData.end()) { delete i.value().textFrame; delete i.value().iconFrame; ++i; } m_windowData.clear(); m_motionManager.unmanageAll(); return; } // Create temporary input window to catch mouse events effects->startMouseInterception(this, Qt::PointingHandCursor); m_hasKeyboardGrab = effects->grabKeyboard(this); effects->setActiveFullScreenEffect(this); reCreateGrids(); rearrangeWindows(); setHighlightedWindow(effects->activeWindow()); foreach (EffectWindow * w, effects->stackingOrder()) { w->setData(WindowForceBlurRole, QVariant(true)); w->setData(WindowForceBackgroundContrastRole, QVariant(true)); } } else { m_needInitialSelection = false; if (m_highlightedWindow) effects->setElevatedWindow(m_highlightedWindow, false); // Fade in/out all windows EffectWindow *activeWindow = effects->activeWindow(); int desktop = effects->currentDesktop(); if (activeWindow && !activeWindow->isOnAllDesktops()) desktop = activeWindow->desktop(); foreach (EffectWindow * w, effects->stackingOrder()) { DataHash::iterator winData = m_windowData.find(w); if (winData != m_windowData.end()) winData->visible = (w->isOnDesktop(desktop) || w->isOnAllDesktops()) && !w->isMinimized(); } if (m_closeView) m_closeView->hide(); // Move all windows back to their original position foreach (EffectWindow * w, m_motionManager.managedWindows()) m_motionManager.moveWindow(w, w->geometry()); if (m_filterFrame) { m_filterFrame->free(); } m_windowFilter.clear(); m_selectedWindows.clear(); effects->stopMouseInterception(this); if (m_hasKeyboardGrab) effects->ungrabKeyboard(); m_hasKeyboardGrab = false; // destroy atom on manager window if (m_managerWindow) { if (m_mode == ModeSelectedDesktop && m_atomDesktop != XCB_ATOM_NONE) m_managerWindow->deleteProperty(m_atomDesktop); else if (m_mode == ModeWindowGroup && m_atomWindows != XCB_ATOM_NONE) m_managerWindow->deleteProperty(m_atomWindows); - m_managerWindow = NULL; + m_managerWindow = nullptr; } } effects->addRepaintFull(); // Trigger the first repaint } //----------------------------------------------------------------------------- // Filter box void PresentWindowsEffect::updateFilterFrame() { QRect area = effects->clientArea(ScreenArea, effects->activeScreen(), effects->currentDesktop()); if (!m_filterFrame) { m_filterFrame = effects->effectFrame(EffectFrameStyled, false); QFont font; font.setPointSize(font.pointSize() * 2); font.setBold(true); m_filterFrame->setFont(font); } m_filterFrame->setPosition(QPoint(area.x() + area.width() / 2, area.y() + area.height() / 2)); m_filterFrame->setText(i18n("Filter:\n%1", m_windowFilter)); } //----------------------------------------------------------------------------- // Helper functions bool PresentWindowsEffect::isSelectableWindow(EffectWindow *w) { if (!w->isOnCurrentActivity()) return false; if (w->isSpecialWindow() || w->isUtility()) return false; if (w->isDeleted()) return false; if (!w->acceptsFocus()) return false; if (w->isSkipSwitcher()) return false; if (m_closeView && w == effects->findWindow(m_closeView->winId())) return false; if (m_ignoreMinimized && w->isMinimized()) return false; switch(m_mode) { default: case ModeAllDesktops: return true; case ModeCurrentDesktop: return w->isOnCurrentDesktop(); case ModeSelectedDesktop: return w->isOnDesktop(m_desktop); case ModeWindowGroup: return m_selectedWindows.contains(w); case ModeWindowClass: return m_class == w->windowClass(); } } bool PresentWindowsEffect::isVisibleWindow(EffectWindow *w) { if (w->isDesktop() || w == m_closeWindow) return true; return isSelectableWindow(w); } void PresentWindowsEffect::setHighlightedWindow(EffectWindow *w) { - if (w == m_highlightedWindow || (w != NULL && !m_motionManager.isManaging(w))) + if (w == m_highlightedWindow || (w != nullptr && !m_motionManager.isManaging(w))) return; if (m_closeView) m_closeView->hide(); if (m_highlightedWindow) { effects->setElevatedWindow(m_highlightedWindow, false); m_highlightedWindow->addRepaintFull(); // Trigger the first repaint } m_highlightedWindow = w; if (m_highlightedWindow) { effects->setElevatedWindow(m_highlightedWindow, true); m_highlightedWindow->addRepaintFull(); // Trigger the first repaint } updateCloseWindow(); } void PresentWindowsEffect::elevateCloseWindow() { if (!m_closeView || !m_activated) return; if (EffectWindow *cw = effects->findWindow(m_closeView->winId())) effects->setElevatedWindow(cw, true); } void PresentWindowsEffect::updateCloseWindow() { if (!m_closeView || m_doNotCloseWindows) return; if (!m_activated || !m_highlightedWindow || m_highlightedWindow->isDesktop()) { m_closeView->hide(); return; } if (m_closeView->isVisible()) return; const QRectF rect(m_motionManager.targetGeometry(m_highlightedWindow)); if (2*m_closeView->width() > rect.width() && 2*m_closeView->height() > rect.height()) { // not for tiny windows (eg. with many windows) - they might become unselectable m_closeView->hide(); return; } QRect cvr(QPoint(0,0), m_closeView->size()); switch (m_closeButtonCorner) { case Qt::TopLeftCorner: default: cvr.moveTopLeft(rect.topLeft().toPoint()); break; case Qt::TopRightCorner: cvr.moveTopRight(rect.topRight().toPoint()); break; case Qt::BottomLeftCorner: cvr.moveBottomLeft(rect.bottomLeft().toPoint()); break; case Qt::BottomRightCorner: cvr.moveBottomRight(rect.bottomRight().toPoint()); break; } m_closeView->setGeometry(cvr); if (rect.contains(effects->cursorPos())) { m_closeView->show(); m_closeView->disarm(); // to wait for the next event cycle (or more if the show takes more time) // TODO: make the closeWindow a graphicsviewitem? why should there be an extra scene to be used in an exiting scene?? QTimer::singleShot(50, this, SLOT(elevateCloseWindow())); } else m_closeView->hide(); } void PresentWindowsEffect::closeWindow() { if (m_highlightedWindow) m_highlightedWindow->closeWindow(); } EffectWindow* PresentWindowsEffect::relativeWindow(EffectWindow *w, int xdiff, int ydiff, bool wrap) const { if (!w) return m_motionManager.managedWindows().first(); // TODO: Is it possible to select hidden windows? EffectWindow* next; QRect area = effects->clientArea(FullArea, 0, effects->currentDesktop()); QRect detectRect; // Detect across the width of the desktop if (xdiff != 0) { if (xdiff > 0) { // Detect right for (int i = 0; i < xdiff; i++) { QRectF wArea = m_motionManager.transformedGeometry(w); detectRect = QRect(0, wArea.y(), area.width(), wArea.height()); - next = NULL; + next = nullptr; foreach (EffectWindow * e, m_motionManager.managedWindows()) { DataHash::const_iterator winData = m_windowData.find(e); if (winData == m_windowData.end() || !winData->visible) continue; QRectF eArea = m_motionManager.transformedGeometry(e); if (eArea.intersects(detectRect) && eArea.x() > wArea.x()) { - if (next == NULL) + if (next == nullptr) next = e; else { QRectF nArea = m_motionManager.transformedGeometry(next); if (eArea.x() < nArea.x()) next = e; } } } - if (next == NULL) { + if (next == nullptr) { if (wrap) // We are at the right-most window, now get the left-most one to wrap return relativeWindow(w, -1000, 0, false); break; // No more windows to the right } w = next; } return w; } else { // Detect left for (int i = 0; i < -xdiff; i++) { QRectF wArea = m_motionManager.transformedGeometry(w); detectRect = QRect(0, wArea.y(), area.width(), wArea.height()); - next = NULL; + next = nullptr; foreach (EffectWindow * e, m_motionManager.managedWindows()) { DataHash::const_iterator winData = m_windowData.find(e); if (winData == m_windowData.end() || !winData->visible) continue; QRectF eArea = m_motionManager.transformedGeometry(e); if (eArea.intersects(detectRect) && eArea.x() + eArea.width() < wArea.x() + wArea.width()) { - if (next == NULL) + if (next == nullptr) next = e; else { QRectF nArea = m_motionManager.transformedGeometry(next); if (eArea.x() + eArea.width() > nArea.x() + nArea.width()) next = e; } } } - if (next == NULL) { + if (next == nullptr) { if (wrap) // We are at the left-most window, now get the right-most one to wrap return relativeWindow(w, 1000, 0, false); break; // No more windows to the left } w = next; } return w; } } // Detect across the height of the desktop if (ydiff != 0) { if (ydiff > 0) { // Detect down for (int i = 0; i < ydiff; i++) { QRectF wArea = m_motionManager.transformedGeometry(w); detectRect = QRect(wArea.x(), 0, wArea.width(), area.height()); - next = NULL; + next = nullptr; foreach (EffectWindow * e, m_motionManager.managedWindows()) { DataHash::const_iterator winData = m_windowData.find(e); if (winData == m_windowData.end() || !winData->visible) continue; QRectF eArea = m_motionManager.transformedGeometry(e); if (eArea.intersects(detectRect) && eArea.y() > wArea.y()) { - if (next == NULL) + if (next == nullptr) next = e; else { QRectF nArea = m_motionManager.transformedGeometry(next); if (eArea.y() < nArea.y()) next = e; } } } - if (next == NULL) { + if (next == nullptr) { if (wrap) // We are at the bottom-most window, now get the top-most one to wrap return relativeWindow(w, 0, -1000, false); break; // No more windows to the bottom } w = next; } return w; } else { // Detect up for (int i = 0; i < -ydiff; i++) { QRectF wArea = m_motionManager.transformedGeometry(w); detectRect = QRect(wArea.x(), 0, wArea.width(), area.height()); - next = NULL; + next = nullptr; foreach (EffectWindow * e, m_motionManager.managedWindows()) { DataHash::const_iterator winData = m_windowData.find(e); if (winData == m_windowData.end() || !winData->visible) continue; QRectF eArea = m_motionManager.transformedGeometry(e); if (eArea.intersects(detectRect) && eArea.y() + eArea.height() < wArea.y() + wArea.height()) { - if (next == NULL) + if (next == nullptr) next = e; else { QRectF nArea = m_motionManager.transformedGeometry(next); if (eArea.y() + eArea.height() > nArea.y() + nArea.height()) next = e; } } } - if (next == NULL) { + if (next == nullptr) { if (wrap) // We are at the top-most window, now get the bottom-most one to wrap return relativeWindow(w, 0, 1000, false); break; // No more windows to the top } w = next; } return w; } } abort(); // Should never get here } EffectWindow* PresentWindowsEffect::findFirstWindow() const { - EffectWindow *topLeft = NULL; + EffectWindow *topLeft = nullptr; QRectF topLeftGeometry; foreach (EffectWindow * w, m_motionManager.managedWindows()) { DataHash::const_iterator winData = m_windowData.find(w); if (winData == m_windowData.end()) continue; QRectF geometry = m_motionManager.transformedGeometry(w); if (winData->visible == false) continue; // Not visible if (winData->deleted) continue; // Window has been closed - if (topLeft == NULL) { + if (topLeft == nullptr) { topLeft = w; topLeftGeometry = geometry; } else if (geometry.x() < topLeftGeometry.x() || geometry.y() < topLeftGeometry.y()) { topLeft = w; topLeftGeometry = geometry; } } return topLeft; } void PresentWindowsEffect::globalShortcutChanged(QAction *action, const QKeySequence& seq) { if (action->objectName() == QStringLiteral("Expose")) { shortcut.clear(); shortcut.append(seq); } else if (action->objectName() == QStringLiteral("ExposeAll")) { shortcutAll.clear(); shortcutAll.append(seq); } else if (action->objectName() == QStringLiteral("ExposeClass")) { shortcutClass.clear(); shortcutClass.append(seq); } } bool PresentWindowsEffect::isActive() const { return (m_activated || m_motionManager.managingWindows()) && !effects->isScreenLocked(); } void PresentWindowsEffect::reCreateGrids() { m_gridSizes.clear(); for (int i = 0; i < effects->numScreens(); ++i) { m_gridSizes.append(GridSize()); } rearrangeWindows(); } /************************************************ * CloseWindowView ************************************************/ CloseWindowView::CloseWindowView(QObject *parent) : QObject(parent) , m_armTimer(new QElapsedTimer()) , m_window(new QQuickView()) , m_visible(false) , m_posIsValid(false) { m_window->setFlags(Qt::X11BypassWindowManagerHint | Qt::FramelessWindowHint); m_window->setColor(Qt::transparent); m_window->setSource(QUrl(QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("kwin/effects/presentwindows/main.qml")))); if (QObject *item = m_window->rootObject()->findChild(QStringLiteral("closeButton"))) { connect(item, SIGNAL(clicked()), SIGNAL(requestClose())); } m_armTimer->restart(); } void CloseWindowView::windowInputMouseEvent(QMouseEvent *e) { if (e->type() == QEvent::MouseMove) { qApp->sendEvent(m_window.data(), e); } else if (!m_armTimer->hasExpired(350)) { // 50ms until the window is elevated (seen!) and 300ms more to be "realized" by the user. return; } qApp->sendEvent(m_window.data(), e); } void CloseWindowView::disarm() { m_armTimer->restart(); } bool CloseWindowView::isVisible() const { return m_visible; } void CloseWindowView::show() { if (!m_visible && m_posIsValid) { m_window->setPosition(m_pos); m_posIsValid = false; } m_visible = true; m_window->show(); } void CloseWindowView::hide() { if (!m_posIsValid) { m_pos = m_window->position(); m_posIsValid = true; m_window->setPosition(-m_window->width(), -m_window->height()); } m_visible = false; QEvent event(QEvent::Leave); qApp->sendEvent(m_window.data(), &event); } #define DELEGATE(type, name) \ type CloseWindowView::name() const \ { \ return m_window->name(); \ } DELEGATE(int, width) DELEGATE(int, height) DELEGATE(QSize, size) DELEGATE(QRect, geometry) DELEGATE(WId, winId) #undef DELEGATE void CloseWindowView::setGeometry(const QRect &geometry) { m_posIsValid = false; m_window->setGeometry(geometry); } QPoint CloseWindowView::mapFromGlobal(const QPoint &pos) const { return m_window->mapFromGlobal(pos); } } // namespace diff --git a/effects/presentwindows/presentwindows.h b/effects/presentwindows/presentwindows.h index 7d94136a7..8b7c259a4 100644 --- a/effects/presentwindows/presentwindows.h +++ b/effects/presentwindows/presentwindows.h @@ -1,352 +1,352 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2007 Rivo Laks Copyright (C) 2008 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 . *********************************************************************/ #ifndef KWIN_PRESENTWINDOWS_H #define KWIN_PRESENTWINDOWS_H #include "presentwindows_proxy.h" #include class QMouseEvent; class QElapsedTimer; class QQuickView; namespace KWin { class CloseWindowView : public QObject { Q_OBJECT public: - explicit CloseWindowView(QObject *parent = 0); + explicit CloseWindowView(QObject *parent = nullptr); void windowInputMouseEvent(QMouseEvent* e); void disarm(); void show(); void hide(); bool isVisible() const; // delegate to QWindow int width() const; int height() const; QSize size() const; QRect geometry() const; WId winId() const; void setGeometry(const QRect &geometry); QPoint mapFromGlobal(const QPoint &pos) const; Q_SIGNALS: void requestClose(); private: QScopedPointer m_armTimer; QScopedPointer m_window; bool m_visible; QPoint m_pos; bool m_posIsValid; }; /** * Expose-like effect which shows all windows on current desktop side-by-side, * letting the user select active window. */ class PresentWindowsEffect : public Effect { Q_OBJECT Q_PROPERTY(int layoutMode READ layoutMode) Q_PROPERTY(bool showCaptions READ isShowCaptions) Q_PROPERTY(bool showIcons READ isShowIcons) Q_PROPERTY(bool doNotCloseWindows READ isDoNotCloseWindows) Q_PROPERTY(bool ignoreMinimized READ isIgnoreMinimized) Q_PROPERTY(int accuracy READ accuracy) Q_PROPERTY(bool fillGaps READ isFillGaps) Q_PROPERTY(int fadeDuration READ fadeDuration) Q_PROPERTY(bool showPanel READ isShowPanel) Q_PROPERTY(int leftButtonWindow READ leftButtonWindow) Q_PROPERTY(int rightButtonWindow READ rightButtonWindow) Q_PROPERTY(int middleButtonWindow READ middleButtonWindow) Q_PROPERTY(int leftButtonDesktop READ leftButtonDesktop) Q_PROPERTY(int middleButtonDesktop READ middleButtonDesktop) Q_PROPERTY(int rightButtonDesktop READ rightButtonDesktop) // TODO: electric borders private: // Structures struct WindowData { bool visible; bool deleted; bool referenced; double opacity; double highlight; EffectFrame* textFrame; EffectFrame* iconFrame; }; typedef QHash DataHash; struct GridSize { int columns; int rows; }; public: PresentWindowsEffect(); ~PresentWindowsEffect() override; void reconfigure(ReconfigureFlags) override; void* proxy() override; // Screen painting void prePaintScreen(ScreenPrePaintData &data, int time) override; void paintScreen(int mask, QRegion region, ScreenPaintData &data) override; void postPaintScreen() override; // Window painting void prePaintWindow(EffectWindow *w, WindowPrePaintData &data, int time) override; void paintWindow(EffectWindow *w, int mask, QRegion region, WindowPaintData &data) override; // User interaction bool borderActivated(ElectricBorder border) override; void windowInputMouseEvent(QEvent *e) override; void grabbedKeyboardEvent(QKeyEvent *e) override; bool isActive() const override; bool touchDown(qint32 id, const QPointF &pos, quint32 time) override; bool touchMotion(qint32 id, const QPointF &pos, quint32 time) override; bool touchUp(qint32 id, quint32 time) override; int requestedEffectChainPosition() const override { return 70; } enum { LayoutNatural, LayoutRegularGrid, LayoutFlexibleGrid }; // Layout modes enum PresentWindowsMode { ModeAllDesktops, // Shows windows of all desktops ModeCurrentDesktop, // Shows windows on current desktop ModeSelectedDesktop, // Shows windows of selected desktop via property (m_desktop) ModeWindowGroup, // Shows windows selected via property ModeWindowClass // Shows all windows of same class as selected class }; enum WindowMouseAction { WindowNoAction = 0, // Nothing WindowActivateAction = 1, // Activates the window and deactivates the effect WindowExitAction = 2, // Deactivates the effect without activating new window WindowToCurrentDesktopAction = 3, // Brings window to current desktop WindowToAllDesktopsAction = 4, // Brings window to all desktops WindowMinimizeAction = 5, // Minimizes the window WindowCloseAction = 6 // Closes the window }; enum DesktopMouseAction { DesktopNoAction = 0, // nothing DesktopActivateAction = 1, // Activates the window and deactivates the effect DesktopExitAction = 2, // Deactivates the effect without activating new window DesktopShowDesktopAction = 3 // Minimizes all windows }; // for properties int layoutMode() const { return m_layoutMode; } bool isShowCaptions() const { return m_showCaptions; } bool isShowIcons() const { return m_showIcons; } bool isDoNotCloseWindows() const { return m_doNotCloseWindows; } bool isIgnoreMinimized() const { return m_ignoreMinimized; } int accuracy() const { return m_accuracy; } bool isFillGaps() const { return m_fillGaps; } int fadeDuration() const { return m_fadeDuration; } bool isShowPanel() const { return m_showPanel; } int leftButtonWindow() const { return m_leftButtonWindow; } int rightButtonWindow() const { return m_rightButtonWindow; } int middleButtonWindow() const { return m_middleButtonWindow; } int leftButtonDesktop() const { return m_leftButtonDesktop; } int middleButtonDesktop() const { return m_middleButtonDesktop; } int rightButtonDesktop() const { return m_rightButtonDesktop; } public Q_SLOTS: void setActive(bool active); void toggleActive() { m_mode = ModeCurrentDesktop; setActive(!m_activated); } void toggleActiveAllDesktops() { m_mode = ModeAllDesktops; setActive(!m_activated); } void toggleActiveClass(); // slots for global shortcut changed // needed to toggle the effect void globalShortcutChanged(QAction *action, const QKeySequence &seq); // EffectsHandler void slotWindowAdded(KWin::EffectWindow *w); void slotWindowClosed(KWin::EffectWindow *w); void slotWindowDeleted(KWin::EffectWindow *w); void slotWindowGeometryShapeChanged(KWin::EffectWindow *w, const QRect &old); // atoms void slotPropertyNotify(KWin::EffectWindow* w, long atom); private Q_SLOTS: void closeWindow(); void elevateCloseWindow(); protected: // Window rearranging void rearrangeWindows(); void reCreateGrids(); void calculateWindowTransformations(EffectWindowList windowlist, int screen, WindowMotionManager& motionManager, bool external = false); void calculateWindowTransformationsClosest(EffectWindowList windowlist, int screen, WindowMotionManager& motionManager); void calculateWindowTransformationsKompose(EffectWindowList windowlist, int screen, WindowMotionManager& motionManager); void calculateWindowTransformationsNatural(EffectWindowList windowlist, int screen, WindowMotionManager& motionManager); // Helper functions for window rearranging inline double aspectRatio(EffectWindow *w) { return w->width() / double(w->height()); } inline int widthForHeight(EffectWindow *w, int height) { return int((height / double(w->height())) * w->width()); } inline int heightForWidth(EffectWindow *w, int width) { return int((width / double(w->width())) * w->height()); } bool isOverlappingAny(EffectWindow *w, const QHash &targets, const QRegion &border); // Filter box void updateFilterFrame(); // Helper functions bool isSelectableWindow(EffectWindow *w); bool isVisibleWindow(EffectWindow *w); void setHighlightedWindow(EffectWindow *w); EffectWindow* relativeWindow(EffectWindow *w, int xdiff, int ydiff, bool wrap) const; EffectWindow* findFirstWindow() const; void updateCloseWindow(); // Helper functions for mouse actions void mouseActionWindow(WindowMouseAction& action); void mouseActionDesktop(DesktopMouseAction& action); void inputEventUpdate(const QPoint &pos, QEvent::Type type = QEvent::None, Qt::MouseButton button = Qt::NoButton); private: PresentWindowsEffectProxy m_proxy; friend class PresentWindowsEffectProxy; // User configuration settings QList m_borderActivate; QList m_borderActivateAll; QList m_borderActivateClass; int m_layoutMode; bool m_showCaptions; bool m_showIcons; bool m_doNotCloseWindows; int m_accuracy; bool m_fillGaps; double m_fadeDuration; bool m_showPanel; // Activation bool m_activated; bool m_ignoreMinimized; double m_decalOpacity; bool m_hasKeyboardGrab; PresentWindowsMode m_mode; int m_desktop; EffectWindowList m_selectedWindows; EffectWindow *m_managerWindow; QString m_class; bool m_needInitialSelection; // Window data WindowMotionManager m_motionManager; DataHash m_windowData; EffectWindow *m_highlightedWindow; // Grid layout info QList m_gridSizes; // Filter box EffectFrame* m_filterFrame; QString m_windowFilter; // Shortcut - needed to toggle the effect QList shortcut; QList shortcutAll; QList shortcutClass; // Atoms // Present windows for all windows of given desktop // -1 for all desktops long m_atomDesktop; // Present windows for group of window ids long m_atomWindows; // Mouse Actions WindowMouseAction m_leftButtonWindow; WindowMouseAction m_middleButtonWindow; WindowMouseAction m_rightButtonWindow; DesktopMouseAction m_leftButtonDesktop; DesktopMouseAction m_middleButtonDesktop; DesktopMouseAction m_rightButtonDesktop; CloseWindowView* m_closeView; EffectWindow* m_closeWindow; Qt::Corner m_closeButtonCorner; struct { qint32 id = 0; bool active = false; } m_touch; QAction *m_exposeAction; QAction *m_exposeAllAction; QAction *m_exposeClassAction; }; } // namespace #endif diff --git a/effects/presentwindows/presentwindows_config.h b/effects/presentwindows/presentwindows_config.h index 4607be00b..bc15ea762 100644 --- a/effects/presentwindows/presentwindows_config.h +++ b/effects/presentwindows/presentwindows_config.h @@ -1,57 +1,57 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2007 Rivo Laks Copyright (C) 2008 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 . *********************************************************************/ #ifndef KWIN_PRESENTWINDOWS_CONFIG_H #define KWIN_PRESENTWINDOWS_CONFIG_H #include #include "ui_presentwindows_config.h" namespace KWin { class PresentWindowsEffectConfigForm : public QWidget, public Ui::PresentWindowsEffectConfigForm { Q_OBJECT public: explicit PresentWindowsEffectConfigForm(QWidget* parent); }; class PresentWindowsEffectConfig : public KCModule { Q_OBJECT public: - explicit PresentWindowsEffectConfig(QWidget* parent = 0, const QVariantList& args = QVariantList()); + explicit PresentWindowsEffectConfig(QWidget* parent = nullptr, const QVariantList& args = QVariantList()); ~PresentWindowsEffectConfig() override; public Q_SLOTS: void save() override; void defaults() override; private: PresentWindowsEffectConfigForm* m_ui; KActionCollection* m_actionCollection; }; } // namespace #endif diff --git a/effects/resize/resize.cpp b/effects/resize/resize.cpp index 2b4e51428..4cdbb9049 100644 --- a/effects/resize/resize.cpp +++ b/effects/resize/resize.cpp @@ -1,177 +1,177 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. 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 . *********************************************************************/ #include "resize.h" // KConfigSkeleton #include "resizeconfig.h" #include #ifdef KWIN_HAVE_XRENDER_COMPOSITING #include "kwinxrenderutils.h" #endif #include #include #include namespace KWin { ResizeEffect::ResizeEffect() : AnimationEffect() , m_active(false) - , m_resizeWindow(0) + , m_resizeWindow(nullptr) { initConfig(); reconfigure(ReconfigureAll); connect(effects, &EffectsHandler::windowStartUserMovedResized, this, &ResizeEffect::slotWindowStartUserMovedResized); connect(effects, &EffectsHandler::windowStepUserMovedResized, this, &ResizeEffect::slotWindowStepUserMovedResized); connect(effects, &EffectsHandler::windowFinishUserMovedResized, this, &ResizeEffect::slotWindowFinishUserMovedResized); } ResizeEffect::~ResizeEffect() { } void ResizeEffect::prePaintScreen(ScreenPrePaintData& data, int time) { if (m_active) { data.mask |= PAINT_SCREEN_WITH_TRANSFORMED_WINDOWS; } AnimationEffect::prePaintScreen(data, time); } void ResizeEffect::prePaintWindow(EffectWindow* w, WindowPrePaintData& data, int time) { if (m_active && w == m_resizeWindow) data.mask |= PAINT_WINDOW_TRANSFORMED; AnimationEffect::prePaintWindow(w, data, time); } void ResizeEffect::paintWindow(EffectWindow* w, int mask, QRegion region, WindowPaintData& data) { if (m_active && w == m_resizeWindow) { if (m_features & TextureScale) { data += (m_currentGeometry.topLeft() - m_originalGeometry.topLeft()); data *= QVector2D(float(m_currentGeometry.width())/m_originalGeometry.width(), float(m_currentGeometry.height())/m_originalGeometry.height()); } effects->paintWindow(w, mask, region, data); if (m_features & Outline) { QRegion intersection = m_originalGeometry.intersected(m_currentGeometry); QRegion paintRegion = QRegion(m_originalGeometry).united(m_currentGeometry).subtracted(intersection); float alpha = 0.8f; QColor color = KColorScheme(QPalette::Normal, KColorScheme::Selection).background().color(); if (effects->isOpenGLCompositing()) { GLVertexBuffer *vbo = GLVertexBuffer::streamingBuffer(); vbo->reset(); vbo->setUseColor(true); ShaderBinder binder(ShaderTrait::UniformColor); binder.shader()->setUniform(GLShader::ModelViewProjectionMatrix, data.screenProjectionMatrix()); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); color.setAlphaF(alpha); vbo->setColor(color); QVector verts; verts.reserve(paintRegion.rectCount() * 12); for (const QRect &r : paintRegion) { 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(); } - vbo->setData(verts.count() / 2, 2, verts.data(), NULL); + vbo->setData(verts.count() / 2, 2, verts.data(), nullptr); vbo->render(GL_TRIANGLES); glDisable(GL_BLEND); } #ifdef KWIN_HAVE_XRENDER_COMPOSITING if (effects->compositingType() == XRenderCompositing) { QVector rects; for (const QRect &r : paintRegion) { xcb_rectangle_t rect = {int16_t(r.x()), int16_t(r.y()), uint16_t(r.width()), uint16_t(r.height())}; rects << rect; } xcb_render_fill_rectangles(xcbConnection(), XCB_RENDER_PICT_OP_OVER, effects->xrenderBufferPicture(), preMultiply(color, alpha), rects.count(), rects.constData()); } #endif if (effects->compositingType() == QPainterCompositing) { QPainter *painter = effects->scenePainter(); painter->save(); color.setAlphaF(alpha); for (const QRect &r : paintRegion) { painter->fillRect(r, color); } painter->restore(); } } } else { AnimationEffect::paintWindow(w, mask, region, data); } } void ResizeEffect::reconfigure(ReconfigureFlags) { m_features = 0; ResizeConfig::self()->read(); if (ResizeConfig::textureScale()) m_features |= TextureScale; if (ResizeConfig::outline()) m_features |= Outline; } void ResizeEffect::slotWindowStartUserMovedResized(EffectWindow *w) { if (w->isUserResize() && !w->isUserMove()) { m_active = true; m_resizeWindow = w; m_originalGeometry = w->geometry(); m_currentGeometry = w->geometry(); w->addRepaintFull(); } } void ResizeEffect::slotWindowFinishUserMovedResized(EffectWindow *w) { if (m_active && w == m_resizeWindow) { m_active = false; - m_resizeWindow = NULL; + m_resizeWindow = nullptr; if (m_features & TextureScale) animate(w, CrossFadePrevious, 0, 150, FPx2(1.0)); effects->addRepaintFull(); } } void ResizeEffect::slotWindowStepUserMovedResized(EffectWindow *w, const QRect &geometry) { if (m_active && w == m_resizeWindow) { m_currentGeometry = geometry; effects->addRepaintFull(); } } } // namespace diff --git a/effects/resize/resize_config.h b/effects/resize/resize_config.h index e02e596e3..7c07c23ac 100644 --- a/effects/resize/resize_config.h +++ b/effects/resize/resize_config.h @@ -1,54 +1,54 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2010 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_RESIZE_CONFIG_H #define KWIN_RESIZE_CONFIG_H #include #include "ui_resize_config.h" namespace KWin { class ResizeEffectConfigForm : public QWidget, public Ui::ResizeEffectConfigForm { Q_OBJECT public: - explicit ResizeEffectConfigForm(QWidget* parent = 0); + explicit ResizeEffectConfigForm(QWidget* parent = nullptr); }; class ResizeEffectConfig : public KCModule { Q_OBJECT public: - explicit ResizeEffectConfig(QWidget* parent = 0, const QVariantList& args = QVariantList()); + explicit ResizeEffectConfig(QWidget* parent = nullptr, const QVariantList& args = QVariantList()); public Q_SLOTS: void save() override; private: ResizeEffectConfigForm* m_ui; }; } // namespace #endif diff --git a/effects/screenedge/screenedgeeffect.cpp b/effects/screenedge/screenedgeeffect.cpp index 3c0810eb1..88654bb62 100644 --- a/effects/screenedge/screenedgeeffect.cpp +++ b/effects/screenedge/screenedgeeffect.cpp @@ -1,373 +1,373 @@ /******************************************************************** 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 "screenedgeeffect.h" // KWin #include #include #include // KDE #include // Qt #include #include #include // xcb #ifdef KWIN_HAVE_XRENDER_COMPOSITING #include #endif namespace KWin { ScreenEdgeEffect::ScreenEdgeEffect() : Effect() , m_cleanupTimer(new QTimer(this)) { connect(effects, &EffectsHandler::screenEdgeApproaching, this, &ScreenEdgeEffect::edgeApproaching); m_cleanupTimer->setInterval(5000); m_cleanupTimer->setSingleShot(true); connect(m_cleanupTimer, &QTimer::timeout, this, &ScreenEdgeEffect::cleanup); connect(effects, &EffectsHandler::screenLockingChanged, this, [this] (bool locked) { if (locked) { cleanup(); } } ); } ScreenEdgeEffect::~ScreenEdgeEffect() { cleanup(); } void ScreenEdgeEffect::ensureGlowSvg() { if (!m_glow) { m_glow = new Plasma::Svg(this); m_glow->setImagePath(QStringLiteral("widgets/glowbar")); } } void ScreenEdgeEffect::cleanup() { for (QHash::iterator it = m_borders.begin(); it != m_borders.end(); ++it) { effects->addRepaint((*it)->geometry); } qDeleteAll(m_borders); m_borders.clear(); } void ScreenEdgeEffect::prePaintScreen(ScreenPrePaintData &data, int time) { effects->prePaintScreen(data, time); for (QHash::iterator it = m_borders.begin(); it != m_borders.end(); ++it) { if ((*it)->strength == 0.0) { continue; } data.paint += (*it)->geometry; } } void ScreenEdgeEffect::paintScreen(int mask, QRegion region, ScreenPaintData &data) { effects->paintScreen(mask, region, data); for (QHash::iterator it = m_borders.begin(); it != m_borders.end(); ++it) { const qreal opacity = (*it)->strength; if (opacity == 0.0) { continue; } if (effects->isOpenGLCompositing()) { GLTexture *texture = (*it)->texture.data(); glEnable(GL_BLEND); glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); texture->bind(); ShaderBinder binder(ShaderTrait::MapTexture | ShaderTrait::Modulate); const QVector4D constant(opacity, opacity, opacity, opacity); binder.shader()->setUniform(GLShader::ModulationConstant, constant); QMatrix4x4 mvp = data.projectionMatrix(); mvp.translate((*it)->geometry.x(), (*it)->geometry.y()); binder.shader()->setUniform(GLShader::ModelViewProjectionMatrix, mvp); texture->render(infiniteRegion(), (*it)->geometry); texture->unbind(); glDisable(GL_BLEND); } else if (effects->compositingType() == XRenderCompositing) { #ifdef KWIN_HAVE_XRENDER_COMPOSITING const QRect &rect = (*it)->geometry; const QSize &size = (*it)->pictureSize; int x = rect.x(); int y = rect.y(); int width = rect.width(); int height = rect.height(); switch ((*it)->border) { case ElectricTopRight: x = rect.x() + rect.width() - size.width(); break; case ElectricBottomRight: x = rect.x() + rect.width() - size.width(); y = rect.y() + rect.height() - size.height(); break; case ElectricBottomLeft: y = rect.y() + rect.height() - size.height(); break; default: // nothing break; } xcb_render_composite(xcbConnection(), XCB_RENDER_PICT_OP_OVER, *(*it)->picture.data(), xRenderBlendPicture(opacity), effects->xrenderBufferPicture(), 0, 0, 0, 0, x, y, width, height); #endif } else if (effects->compositingType() == QPainterCompositing) { QImage tmp((*it)->image->size(), QImage::Format_ARGB32_Premultiplied); tmp.fill(Qt::transparent); QPainter p(&tmp); p.drawImage(0, 0, *(*it)->image.data()); QColor color(Qt::transparent); color.setAlphaF(opacity); p.setCompositionMode(QPainter::CompositionMode_DestinationIn); p.fillRect(QRect(QPoint(0, 0), tmp.size()), color); p.end(); QPainter *painter = effects->scenePainter(); const QRect &rect = (*it)->geometry; const QSize &size = (*it)->pictureSize; int x = rect.x(); int y = rect.y(); switch ((*it)->border) { case ElectricTopRight: x = rect.x() + rect.width() - size.width(); break; case ElectricBottomRight: x = rect.x() + rect.width() - size.width(); y = rect.y() + rect.height() - size.height(); break; case ElectricBottomLeft: y = rect.y() + rect.height() - size.height(); break; default: // nothing break; } painter->drawImage(QPoint(x, y), tmp); } } } void ScreenEdgeEffect::edgeApproaching(ElectricBorder border, qreal factor, const QRect &geometry) { QHash::iterator it = m_borders.find(border); if (it != m_borders.end()) { // need to update effects->addRepaint((*it)->geometry); (*it)->strength = factor; if ((*it)->geometry != geometry) { (*it)->geometry = geometry; effects->addRepaint((*it)->geometry); if (border == ElectricLeft || border == ElectricRight || border == ElectricTop || border == ElectricBottom) { if (effects->isOpenGLCompositing()) { (*it)->texture.reset(createEdgeGlow(border, geometry.size())); } else if (effects->compositingType() == XRenderCompositing) { #ifdef KWIN_HAVE_XRENDER_COMPOSITING (*it)->picture.reset(createEdgeGlow(border, geometry.size())); #endif } else if (effects->compositingType() == QPainterCompositing) { (*it)->image.reset(createEdgeGlow(border, geometry.size())); } } } if (factor == 0.0) { m_cleanupTimer->start(); } else { m_cleanupTimer->stop(); } } else if (factor != 0.0) { // need to generate new Glow Glow *glow = createGlow(border, factor, geometry); if (glow) { m_borders.insert(border, glow); effects->addRepaint(glow->geometry); } } } Glow *ScreenEdgeEffect::createGlow(ElectricBorder border, qreal factor, const QRect &geometry) { Glow *glow = new Glow(); glow->border = border; glow->strength = factor; glow->geometry = geometry; // render the glow image if (effects->isOpenGLCompositing()) { effects->makeOpenGLContextCurrent(); if (border == ElectricTopLeft || border == ElectricTopRight || border == ElectricBottomRight || border == ElectricBottomLeft) { glow->texture.reset(createCornerGlow(border)); } else { glow->texture.reset(createEdgeGlow(border, geometry.size())); } if (!glow->texture.isNull()) { glow->texture->setWrapMode(GL_CLAMP_TO_EDGE); } if (glow->texture.isNull()) { delete glow; - return NULL; + return nullptr; } } else if (effects->compositingType() == XRenderCompositing) { #ifdef KWIN_HAVE_XRENDER_COMPOSITING if (border == ElectricTopLeft || border == ElectricTopRight || border == ElectricBottomRight || border == ElectricBottomLeft) { glow->pictureSize = cornerGlowSize(border); glow->picture.reset(createCornerGlow(border)); } else { glow->pictureSize = geometry.size(); glow->picture.reset(createEdgeGlow(border, geometry.size())); } if (glow->picture.isNull()) { delete glow; - return NULL; + return nullptr; } #endif } else if (effects->compositingType() == QPainterCompositing) { if (border == ElectricTopLeft || border == ElectricTopRight || border == ElectricBottomRight || border == ElectricBottomLeft) { glow->image.reset(createCornerGlow(border)); glow->pictureSize = cornerGlowSize(border); } else { glow->image.reset(createEdgeGlow(border, geometry.size())); glow->pictureSize = geometry.size(); } if (glow->image.isNull()) { delete glow; - return NULL; + return nullptr; } } return glow; } template T *ScreenEdgeEffect::createCornerGlow(ElectricBorder border) { ensureGlowSvg(); switch (border) { case ElectricTopLeft: return new T(m_glow->pixmap(QStringLiteral("bottomright")).toImage()); case ElectricTopRight: return new T(m_glow->pixmap(QStringLiteral("bottomleft")).toImage()); case ElectricBottomRight: return new T(m_glow->pixmap(QStringLiteral("topleft")).toImage()); case ElectricBottomLeft: return new T(m_glow->pixmap(QStringLiteral("topright")).toImage()); default: - return NULL; + return nullptr; } } QSize ScreenEdgeEffect::cornerGlowSize(ElectricBorder border) { ensureGlowSvg(); switch (border) { case ElectricTopLeft: return m_glow->elementSize(QStringLiteral("bottomright")); case ElectricTopRight: return m_glow->elementSize(QStringLiteral("bottomleft")); case ElectricBottomRight: return m_glow->elementSize(QStringLiteral("topleft")); case ElectricBottomLeft: return m_glow->elementSize(QStringLiteral("topright")); default: return QSize(); } } template T *ScreenEdgeEffect::createEdgeGlow(ElectricBorder border, const QSize &size) { ensureGlowSvg(); const bool stretchBorder = m_glow->hasElement(QStringLiteral("hint-stretch-borders")); QPoint pixmapPosition(0, 0); QPixmap l, r, c; switch (border) { case ElectricTop: l = m_glow->pixmap(QStringLiteral("bottomleft")); r = m_glow->pixmap(QStringLiteral("bottomright")); c = m_glow->pixmap(QStringLiteral("bottom")); break; case ElectricBottom: l = m_glow->pixmap(QStringLiteral("topleft")); r = m_glow->pixmap(QStringLiteral("topright")); c = m_glow->pixmap(QStringLiteral("top")); pixmapPosition = QPoint(0, size.height() - c.height()); break; case ElectricLeft: l = m_glow->pixmap(QStringLiteral("topright")); r = m_glow->pixmap(QStringLiteral("bottomright")); c = m_glow->pixmap(QStringLiteral("right")); break; case ElectricRight: l = m_glow->pixmap(QStringLiteral("topleft")); r = m_glow->pixmap(QStringLiteral("bottomleft")); c = m_glow->pixmap(QStringLiteral("left")); pixmapPosition = QPoint(size.width() - c.width(), 0); break; default: - return NULL; + return nullptr; } QPixmap image(size); image.fill(Qt::transparent); QPainter p; p.begin(&image); if (border == ElectricBottom || border == ElectricTop) { p.drawPixmap(pixmapPosition, l); const QRect cRect(l.width(), pixmapPosition.y(), size.width() - l.width() - r.width(), c.height()); if (stretchBorder) { p.drawPixmap(cRect, c); } else { p.drawTiledPixmap(cRect, c); } p.drawPixmap(QPoint(size.width() - r.width(), pixmapPosition.y()), r); } else { p.drawPixmap(pixmapPosition, l); const QRect cRect(pixmapPosition.x(), l.height(), c.width(), size.height() - l.height() - r.height()); if (stretchBorder) { p.drawPixmap(cRect, c); } else { p.drawTiledPixmap(cRect, c); } p.drawPixmap(QPoint(pixmapPosition.x(), size.height() - r.height()), r); } p.end(); return new T(image.toImage()); } bool ScreenEdgeEffect::isActive() const { return !m_borders.isEmpty() && !effects->isScreenLocked(); } } // namespace diff --git a/effects/screenshot/screenshot.cpp b/effects/screenshot/screenshot.cpp index 35a64cbeb..a37b684f2 100644 --- a/effects/screenshot/screenshot.cpp +++ b/effects/screenshot/screenshot.cpp @@ -1,712 +1,712 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2010 Martin Gräßlin Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies) 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 "screenshot.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace KWin { const static QString s_errorAlreadyTaking = QStringLiteral("org.kde.kwin.Screenshot.Error.AlreadyTaking"); const static QString s_errorAlreadyTakingMsg = QStringLiteral("A screenshot is already been taken"); const static QString s_errorFd = QStringLiteral("org.kde.kwin.Screenshot.Error.FileDescriptor"); const static QString s_errorFdMsg = QStringLiteral("No valid file descriptor"); const static QString s_errorCancelled = QStringLiteral("org.kde.kwin.Screenshot.Error.Cancelled"); const static QString s_errorCancelledMsg = QStringLiteral("Screenshot got cancelled"); const static QString s_errorInvalidArea = QStringLiteral("org.kde.kwin.Screenshot.Error.InvalidArea"); const static QString s_errorInvalidAreaMsg = QStringLiteral("Invalid area requested"); const static QString s_errorInvalidScreen = QStringLiteral("org.kde.kwin.Screenshot.Error.InvalidScreen"); const static QString s_errorInvalidScreenMsg = QStringLiteral("Invalid screen requested"); bool ScreenShotEffect::supported() { return effects->compositingType() == XRenderCompositing || (effects->isOpenGLCompositing() && GLRenderTarget::supported()); } ScreenShotEffect::ScreenShotEffect() - : m_scheduledScreenshot(0) + : m_scheduledScreenshot(nullptr) { connect(effects, &EffectsHandler::windowClosed, this, &ScreenShotEffect::windowClosed); QDBusConnection::sessionBus().registerObject(QStringLiteral("/Screenshot"), this, QDBusConnection::ExportScriptableContents); } ScreenShotEffect::~ScreenShotEffect() { QDBusConnection::sessionBus().unregisterObject(QStringLiteral("/Screenshot")); } #ifdef KWIN_HAVE_XRENDER_COMPOSITING static QImage xPictureToImage(xcb_render_picture_t srcPic, const QRect &geometry, xcb_image_t **xImage) { xcb_connection_t *c = effects->xcbConnection(); xcb_pixmap_t xpix = xcb_generate_id(c); xcb_create_pixmap(c, 32, xpix, effects->x11RootWindow(), geometry.width(), geometry.height()); XRenderPicture pic(xpix, 32); xcb_render_composite(c, XCB_RENDER_PICT_OP_SRC, srcPic, XCB_RENDER_PICTURE_NONE, pic, geometry.x(), geometry.y(), 0, 0, 0, 0, geometry.width(), geometry.height()); xcb_flush(c); *xImage = xcb_image_get(c, xpix, 0, 0, geometry.width(), geometry.height(), ~0, XCB_IMAGE_FORMAT_Z_PIXMAP); QImage img((*xImage)->data, (*xImage)->width, (*xImage)->height, (*xImage)->stride, QImage::Format_ARGB32_Premultiplied); // TODO: byte order might need swapping xcb_free_pixmap(c, xpix); return img.copy(); } #endif static QSize pickWindowSize(const QImage &image) { xcb_connection_t *c = effects->xcbConnection(); // This will implicitly enable BIG-REQUESTS extension. const uint32_t maximumRequestSize = xcb_get_maximum_request_length(c); const xcb_setup_t *setup = xcb_get_setup(c); uint32_t requestSize = sizeof(xcb_put_image_request_t); // With BIG-REQUESTS extension an additional 32-bit field is inserted into // the request so we better take it into account. if (setup->maximum_request_length < maximumRequestSize) { requestSize += 4; } const uint32_t maximumDataSize = 4 * maximumRequestSize - requestSize; const uint32_t bytesPerPixel = image.depth() >> 3; const uint32_t bytesPerLine = image.bytesPerLine(); if (image.sizeInBytes() <= maximumDataSize) { return image.size(); } if (maximumDataSize < bytesPerLine) { return QSize(maximumDataSize / bytesPerPixel, 1); } return QSize(image.width(), maximumDataSize / bytesPerLine); } static xcb_pixmap_t xpixmapFromImage(const QImage &image) { xcb_connection_t *c = effects->xcbConnection(); xcb_pixmap_t pixmap = xcb_generate_id(c); xcb_gcontext_t gc = xcb_generate_id(c); xcb_create_pixmap(c, image.depth(), pixmap, effects->x11RootWindow(), image.width(), image.height()); xcb_create_gc(c, gc, pixmap, 0, nullptr); const int bytesPerPixel = image.depth() >> 3; // Figure out how much data we can send with one invocation of xcb_put_image. // In contrast to XPutImage, xcb_put_image doesn't implicitly split the data. const QSize window = pickWindowSize(image); for (int i = 0; i < image.height(); i += window.height()) { const int targetHeight = qMin(image.height() - i, window.height()); const uint8_t *line = image.scanLine(i); for (int j = 0; j < image.width(); j += window.width()) { const int targetWidth = qMin(image.width() - j, window.width()); const uint8_t *bytes = line + j * bytesPerPixel; const uint32_t byteCount = targetWidth * targetHeight * bytesPerPixel; xcb_put_image(c, XCB_IMAGE_FORMAT_Z_PIXMAP, pixmap, gc, targetWidth, targetHeight, j, i, 0, image.depth(), byteCount, bytes); } } xcb_flush(c); xcb_free_gc(c, gc); return pixmap; } void ScreenShotEffect::paintScreen(int mask, QRegion region, ScreenPaintData &data) { m_cachedOutputGeometry = data.outputGeometry(); effects->paintScreen(mask, region, data); } void ScreenShotEffect::postPaintScreen() { effects->postPaintScreen(); if (m_scheduledScreenshot) { WindowPaintData d(m_scheduledScreenshot); double left = 0; double top = 0; double right = m_scheduledScreenshot->width(); double bottom = m_scheduledScreenshot->height(); if (m_scheduledScreenshot->hasDecoration() && m_type & INCLUDE_DECORATION) { foreach (const WindowQuad & quad, d.quads) { // we need this loop to include the decoration padding left = qMin(left, quad.left()); top = qMin(top, quad.top()); right = qMax(right, quad.right()); bottom = qMax(bottom, quad.bottom()); } } else if (m_scheduledScreenshot->hasDecoration()) { WindowQuadList newQuads; left = m_scheduledScreenshot->width(); top = m_scheduledScreenshot->height(); right = 0; bottom = 0; foreach (const WindowQuad & quad, d.quads) { if (quad.type() == WindowQuadContents) { newQuads << quad; left = qMin(left, quad.left()); top = qMin(top, quad.top()); right = qMax(right, quad.right()); bottom = qMax(bottom, quad.bottom()); } } d.quads = newQuads; } const int width = right - left; const int height = bottom - top; bool validTarget = true; QScopedPointer offscreenTexture; QScopedPointer target; if (effects->isOpenGLCompositing()) { offscreenTexture.reset(new GLTexture(GL_RGBA8, width, height)); offscreenTexture->setFilter(GL_LINEAR); offscreenTexture->setWrapMode(GL_CLAMP_TO_EDGE); target.reset(new GLRenderTarget(*offscreenTexture)); validTarget = target->valid(); } if (validTarget) { d.setXTranslation(-m_scheduledScreenshot->x() - left); d.setYTranslation(-m_scheduledScreenshot->y() - top); // render window into offscreen texture int mask = PAINT_WINDOW_TRANSFORMED | PAINT_WINDOW_TRANSLUCENT; QImage img; if (effects->isOpenGLCompositing()) { GLRenderTarget::pushRenderTarget(target.data()); glClearColor(0.0, 0.0, 0.0, 0.0); glClear(GL_COLOR_BUFFER_BIT); glClearColor(0.0, 0.0, 0.0, 1.0); QMatrix4x4 projection; projection.ortho(QRect(0, 0, offscreenTexture->width(), offscreenTexture->height())); d.setProjectionMatrix(projection); effects->drawWindow(m_scheduledScreenshot, mask, infiniteRegion(), d); // copy content from framebuffer into image img = QImage(QSize(width, height), QImage::Format_ARGB32); glReadnPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, img.sizeInBytes(), (GLvoid*)img.bits()); GLRenderTarget::popRenderTarget(); ScreenShotEffect::convertFromGLImage(img, width, height); } #ifdef KWIN_HAVE_XRENDER_COMPOSITING - xcb_image_t *xImage = NULL; + xcb_image_t *xImage = nullptr; if (effects->compositingType() == XRenderCompositing) { setXRenderOffscreen(true); effects->drawWindow(m_scheduledScreenshot, mask, QRegion(0, 0, width, height), d); if (xRenderOffscreenTarget()) { img = xPictureToImage(xRenderOffscreenTarget(), QRect(0, 0, width, height), &xImage); } setXRenderOffscreen(false); } #endif if (m_type & INCLUDE_CURSOR) { grabPointerImage(img, m_scheduledScreenshot->x() + left, m_scheduledScreenshot->y() + top); } if (m_windowMode == WindowMode::Xpixmap) { const xcb_pixmap_t xpix = xpixmapFromImage(img); emit screenshotCreated(xpix); m_windowMode = WindowMode::NoCapture; } else if (m_windowMode == WindowMode::File) { sendReplyImage(img); } else if (m_windowMode == WindowMode::FileDescriptor) { QtConcurrent::run( [] (int fd, const QImage &img) { QFile file; if (file.open(fd, QIODevice::WriteOnly, QFileDevice::AutoCloseHandle)) { QDataStream ds(&file); ds << img; file.close(); } else { close(fd); } }, m_fd, img); m_windowMode = WindowMode::NoCapture; m_fd = -1; } #ifdef KWIN_HAVE_XRENDER_COMPOSITING if (xImage) { xcb_image_destroy(xImage); } #endif } - m_scheduledScreenshot = NULL; + m_scheduledScreenshot = nullptr; } if (!m_scheduledGeometry.isNull()) { if (!m_cachedOutputGeometry.isNull()) { // special handling for per-output geometry rendering const QRect intersection = m_scheduledGeometry.intersected(m_cachedOutputGeometry); if (intersection.isEmpty()) { // doesn't intersect, not going onto this screenshot return; } const QImage img = blitScreenshot(intersection); if (img.size() == m_scheduledGeometry.size()) { // we are done sendReplyImage(img); return; } if (m_multipleOutputsImage.isNull()) { m_multipleOutputsImage = QImage(m_scheduledGeometry.size(), QImage::Format_ARGB32); m_multipleOutputsImage.fill(Qt::transparent); } QPainter p; p.begin(&m_multipleOutputsImage); p.drawImage(intersection.topLeft() - m_scheduledGeometry.topLeft(), img); p.end(); m_multipleOutputsRendered = m_multipleOutputsRendered.united(intersection); if (m_multipleOutputsRendered.boundingRect() == m_scheduledGeometry) { sendReplyImage(m_multipleOutputsImage); } } else { const QImage img = blitScreenshot(m_scheduledGeometry); sendReplyImage(img); } } } void ScreenShotEffect::sendReplyImage(const QImage &img) { if (m_fd != -1) { QtConcurrent::run( [] (int fd, const QImage &img) { QFile file; if (file.open(fd, QIODevice::WriteOnly, QFileDevice::AutoCloseHandle)) { QDataStream ds(&file); ds << img; file.close(); } else { close(fd); } }, m_fd, img); m_fd = -1; } else { QDBusConnection::sessionBus().send(m_replyMessage.createReply(saveTempImage(img))); } m_scheduledGeometry = QRect(); m_multipleOutputsImage = QImage(); m_multipleOutputsRendered = QRegion(); m_captureCursor = false; m_windowMode = WindowMode::NoCapture; } QString ScreenShotEffect::saveTempImage(const QImage &img) { if (img.isNull()) { return QString(); } QTemporaryFile temp(QDir::tempPath() + QDir::separator() + QLatin1String("kwin_screenshot_XXXXXX.png")); temp.setAutoRemove(false); if (!temp.open()) { return QString(); } img.save(&temp); temp.close(); KNotification::event(KNotification::Notification, i18nc("Notification caption that a screenshot got saved to file", "Screenshot"), i18nc("Notification with path to screenshot file", "Screenshot saved to %1", temp.fileName()), QStringLiteral("spectacle")); return temp.fileName(); } void ScreenShotEffect::screenshotWindowUnderCursor(int mask) { if (isTakingScreenshot()) { sendErrorReply(s_errorAlreadyTaking, s_errorAlreadyTakingMsg); return; } m_type = (ScreenShotType)mask; const QPoint cursor = effects->cursorPos(); EffectWindowList order = effects->stackingOrder(); EffectWindowList::const_iterator it = order.constEnd(), first = order.constBegin(); while( it != first ) { m_scheduledScreenshot = *(--it); if (m_scheduledScreenshot->isOnCurrentDesktop() && !m_scheduledScreenshot->isMinimized() && !m_scheduledScreenshot->isDeleted() && m_scheduledScreenshot->geometry().contains(cursor)) break; - m_scheduledScreenshot = 0; + m_scheduledScreenshot = nullptr; } if (m_scheduledScreenshot) { m_windowMode = WindowMode::Xpixmap; m_scheduledScreenshot->addRepaintFull(); } } void ScreenShotEffect::screenshotForWindow(qulonglong winid, int mask) { m_type = (ScreenShotType) mask; EffectWindow* w = effects->findWindow(winid); if(w && !w->isMinimized() && !w->isDeleted()) { m_windowMode = WindowMode::Xpixmap; m_scheduledScreenshot = w; m_scheduledScreenshot->addRepaintFull(); } } QString ScreenShotEffect::interactive(int mask) { if (!calledFromDBus()) { return QString(); } if (isTakingScreenshot()) { sendErrorReply(s_errorAlreadyTaking, s_errorAlreadyTakingMsg); return QString(); } m_type = (ScreenShotType) mask; m_windowMode = WindowMode::File; m_replyMessage = message(); setDelayedReply(true); effects->startInteractiveWindowSelection( [this] (EffectWindow *w) { hideInfoMessage(); if (!w) { QDBusConnection::sessionBus().send(m_replyMessage.createErrorReply(s_errorCancelled, s_errorCancelledMsg)); m_windowMode = WindowMode::NoCapture; return; } else { m_scheduledScreenshot = w; m_scheduledScreenshot->addRepaintFull(); } }); showInfoMessage(InfoMessageMode::Window); return QString(); } void ScreenShotEffect::interactive(QDBusUnixFileDescriptor fd, int mask) { if (!calledFromDBus()) { return; } if (isTakingScreenshot()) { sendErrorReply(s_errorAlreadyTaking, s_errorAlreadyTakingMsg); return; } m_fd = dup(fd.fileDescriptor()); if (m_fd == -1) { sendErrorReply(s_errorFd, s_errorFdMsg); return; } m_type = (ScreenShotType) mask; m_windowMode = WindowMode::FileDescriptor; effects->startInteractiveWindowSelection( [this] (EffectWindow *w) { hideInfoMessage(); if (!w) { close(m_fd); m_fd = -1; m_windowMode = WindowMode::NoCapture; return; } else { m_scheduledScreenshot = w; m_scheduledScreenshot->addRepaintFull(); } }); showInfoMessage(InfoMessageMode::Window); } void ScreenShotEffect::showInfoMessage(InfoMessageMode mode) { QString text; switch (mode) { case InfoMessageMode::Window: text = i18n("Select window to screen shot with left click or enter.\nEscape or right click to cancel."); break; case InfoMessageMode::Screen: text = i18n("Create screen shot with left click or enter.\nEscape or right click to cancel."); break; } effects->showOnScreenMessage(text, QStringLiteral("spectacle")); } void ScreenShotEffect::hideInfoMessage() { effects->hideOnScreenMessage(EffectsHandler::OnScreenMessageHideFlag::SkipsCloseAnimation); } QString ScreenShotEffect::screenshotFullscreen(bool captureCursor) { if (!calledFromDBus()) { return QString(); } if (isTakingScreenshot()) { sendErrorReply(s_errorAlreadyTaking, s_errorAlreadyTakingMsg); return QString(); } m_replyMessage = message(); setDelayedReply(true); m_scheduledGeometry = effects->virtualScreenGeometry(); m_captureCursor = captureCursor; effects->addRepaintFull(); return QString(); } void ScreenShotEffect::screenshotFullscreen(QDBusUnixFileDescriptor fd, bool captureCursor) { if (!calledFromDBus()) { return; } if (isTakingScreenshot()) { sendErrorReply(s_errorAlreadyTaking, s_errorAlreadyTakingMsg); return; } m_fd = dup(fd.fileDescriptor()); if (m_fd == -1) { sendErrorReply(s_errorFd, s_errorFdMsg); return; } m_captureCursor = captureCursor; showInfoMessage(InfoMessageMode::Screen); effects->startInteractivePositionSelection( [this] (const QPoint &p) { hideInfoMessage(); if (p == QPoint(-1, -1)) { // error condition close(m_fd); m_fd = -1; } else { m_scheduledGeometry = effects->virtualScreenGeometry(); effects->addRepaint(m_scheduledGeometry); } } ); } QString ScreenShotEffect::screenshotScreen(int screen, bool captureCursor) { if (!calledFromDBus()) { return QString(); } if (isTakingScreenshot()) { sendErrorReply(s_errorAlreadyTaking, s_errorAlreadyTakingMsg); return QString(); } m_scheduledGeometry = effects->clientArea(FullScreenArea, screen, 0); if (m_scheduledGeometry.isNull()) { sendErrorReply(s_errorInvalidScreen, s_errorInvalidScreenMsg); return QString(); } m_captureCursor = captureCursor; m_replyMessage = message(); setDelayedReply(true); effects->addRepaint(m_scheduledGeometry); return QString(); } void ScreenShotEffect::screenshotScreen(QDBusUnixFileDescriptor fd, bool captureCursor) { if (!calledFromDBus()) { return; } if (isTakingScreenshot()) { sendErrorReply(s_errorAlreadyTaking, s_errorAlreadyTakingMsg); return; } m_fd = dup(fd.fileDescriptor()); if (m_fd == -1) { sendErrorReply(s_errorFd, s_errorFdMsg); return; } m_captureCursor = captureCursor; showInfoMessage(InfoMessageMode::Screen); effects->startInteractivePositionSelection( [this] (const QPoint &p) { hideInfoMessage(); if (p == QPoint(-1, -1)) { // error condition close(m_fd); m_fd = -1; } else { m_scheduledGeometry = effects->clientArea(FullScreenArea, effects->screenNumber(p), 0); if (m_scheduledGeometry.isNull()) { close(m_fd); m_fd = -1; return; } effects->addRepaint(m_scheduledGeometry); } } ); } QString ScreenShotEffect::screenshotArea(int x, int y, int width, int height, bool captureCursor) { if (!calledFromDBus()) { return QString(); } if (isTakingScreenshot()) { sendErrorReply(s_errorAlreadyTaking, s_errorAlreadyTakingMsg); return QString(); } m_scheduledGeometry = QRect(x, y, width, height); if (m_scheduledGeometry.isNull() || m_scheduledGeometry.isEmpty()) { m_scheduledGeometry = QRect(); sendErrorReply(s_errorInvalidArea, s_errorInvalidAreaMsg); return QString(); } m_captureCursor = captureCursor; m_replyMessage = message(); setDelayedReply(true); effects->addRepaint(m_scheduledGeometry); return QString(); } QImage ScreenShotEffect::blitScreenshot(const QRect &geometry) { QImage img; if (effects->isOpenGLCompositing()) { img = QImage(geometry.size(), QImage::Format_ARGB32); if (GLRenderTarget::blitSupported() && !GLPlatform::instance()->isGLES()) { GLTexture tex(GL_RGBA8, geometry.width(), geometry.height()); GLRenderTarget target(tex); target.blitFromFramebuffer(geometry); // copy content from framebuffer into image tex.bind(); glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE, (GLvoid*)img.bits()); tex.unbind(); } else { glReadPixels(0, 0, img.width(), img.height(), GL_RGBA, GL_UNSIGNED_BYTE, (GLvoid*)img.bits()); } ScreenShotEffect::convertFromGLImage(img, geometry.width(), geometry.height()); } #ifdef KWIN_HAVE_XRENDER_COMPOSITING if (effects->compositingType() == XRenderCompositing) { - xcb_image_t *xImage = NULL; + xcb_image_t *xImage = nullptr; img = xPictureToImage(effects->xrenderBufferPicture(), geometry, &xImage); if (xImage) { xcb_image_destroy(xImage); } } #endif if (m_captureCursor) { grabPointerImage(img, geometry.x(), geometry.y()); } return img; } void ScreenShotEffect::grabPointerImage(QImage& snapshot, int offsetx, int offsety) { const auto cursor = effects->cursorImage(); if (cursor.image().isNull()) return; QPainter painter(&snapshot); painter.drawImage(effects->cursorPos() - cursor.hotSpot() - QPoint(offsetx, offsety), cursor.image()); } void ScreenShotEffect::convertFromGLImage(QImage &img, int w, int h) { // from QtOpenGL/qgl.cpp // Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies) // see http://qt.gitorious.org/qt/qt/blobs/master/src/opengl/qgl.cpp if (QSysInfo::ByteOrder == QSysInfo::BigEndian) { // OpenGL gives RGBA; Qt wants ARGB uint *p = (uint*)img.bits(); uint *end = p + w * h; while (p < end) { uint a = *p << 24; *p = (*p >> 8) | a; p++; } } else { // OpenGL gives ABGR (i.e. RGBA backwards); Qt wants ARGB for (int y = 0; y < h; y++) { uint *q = (uint*)img.scanLine(y); for (int x = 0; x < w; ++x) { const uint pixel = *q; *q = ((pixel << 16) & 0xff0000) | ((pixel >> 16) & 0xff) | (pixel & 0xff00ff00); q++; } } } img = img.mirrored(); } bool ScreenShotEffect::isActive() const { - return (m_scheduledScreenshot != NULL || !m_scheduledGeometry.isNull()) && !effects->isScreenLocked(); + return (m_scheduledScreenshot != nullptr || !m_scheduledGeometry.isNull()) && !effects->isScreenLocked(); } void ScreenShotEffect::windowClosed( EffectWindow* w ) { if (w == m_scheduledScreenshot) { - m_scheduledScreenshot = NULL; + m_scheduledScreenshot = nullptr; screenshotWindowUnderCursor(m_type); } } bool ScreenShotEffect::isTakingScreenshot() const { if (!m_scheduledGeometry.isNull()) { return true; } if (m_windowMode != WindowMode::NoCapture) { return true; } if (m_fd != -1) { return true; } return false; } } // namespace diff --git a/effects/showfps/showfps.cpp b/effects/showfps/showfps.cpp index ad7f70730..b02ca1eef 100644 --- a/effects/showfps/showfps.cpp +++ b/effects/showfps/showfps.cpp @@ -1,550 +1,550 @@ /******************************************************************** 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 "showfps.h" // KConfigSkeleton #include "showfpsconfig.h" #include #include #ifdef KWIN_HAVE_XRENDER_COMPOSITING #include #include #endif #include #include #include #include #include namespace KWin { const int FPS_WIDTH = 10; const int MAX_TIME = 100; ShowFpsEffect::ShowFpsEffect() : paints_pos(0) , frames_pos(0) , m_noBenchmark(effects->effectFrame(EffectFrameUnstyled, false)) { initConfig(); for (int i = 0; i < NUM_PAINTS; ++i) { paints[ i ] = 0; paint_size[ i ] = 0; } for (int i = 0; i < MAX_FPS; ++i) frames[ i ] = 0; m_noBenchmark->setAlignment(Qt::AlignTop | Qt::AlignRight); m_noBenchmark->setText(i18n("This effect is not a benchmark")); reconfigure(ReconfigureAll); } void ShowFpsEffect::reconfigure(ReconfigureFlags) { ShowFpsConfig::self()->read(); alpha = ShowFpsConfig::alpha(); x = ShowFpsConfig::x(); y = ShowFpsConfig::y(); const QSize screenSize = effects->virtualScreenSize(); if (x == -10000) // there's no -0 :( x = screenSize.width() - 2 * NUM_PAINTS - FPS_WIDTH; else if (x < 0) x = screenSize.width() - 2 * NUM_PAINTS - FPS_WIDTH - x; if (y == -10000) y = screenSize.height() - MAX_TIME; else if (y < 0) y = screenSize.height() - MAX_TIME - y; fps_rect = QRect(x, y, FPS_WIDTH + 2 * NUM_PAINTS, MAX_TIME); m_noBenchmark->setPosition(fps_rect.bottomRight() + QPoint(-6, 6)); int textPosition = ShowFpsConfig::textPosition(); textFont = ShowFpsConfig::textFont(); textColor = ShowFpsConfig::textColor(); double textAlpha = ShowFpsConfig::textAlpha(); if (!textColor.isValid()) textColor = QPalette().color(QPalette::Active, QPalette::WindowText); textColor.setAlphaF(textAlpha); switch(textPosition) { case TOP_LEFT: fpsTextRect = QRect(0, 0, 100, 100); textAlign = Qt::AlignTop | Qt::AlignLeft; break; case TOP_RIGHT: fpsTextRect = QRect(screenSize.width() - 100, 0, 100, 100); textAlign = Qt::AlignTop | Qt::AlignRight; break; case BOTTOM_LEFT: fpsTextRect = QRect(0, screenSize.height() - 100, 100, 100); textAlign = Qt::AlignBottom | Qt::AlignLeft; break; case BOTTOM_RIGHT: fpsTextRect = QRect(screenSize.width() - 100, screenSize.height() - 100, 100, 100); textAlign = Qt::AlignBottom | Qt::AlignRight; break; case NOWHERE: fpsTextRect = QRect(); break; case INSIDE_GRAPH: default: fpsTextRect = QRect(x, y, FPS_WIDTH + NUM_PAINTS, MAX_TIME); textAlign = Qt::AlignTop | Qt::AlignRight; break; } } void ShowFpsEffect::prePaintScreen(ScreenPrePaintData& data, int time) { if (time == 0) { // TODO optimized away } t.start(); frames[ frames_pos ] = t.minute() * 60000 + t.second() * 1000 + t.msec(); if (++frames_pos == MAX_FPS) frames_pos = 0; effects->prePaintScreen(data, time); data.paint += fps_rect; paint_size[ paints_pos ] = 0; } void ShowFpsEffect::paintWindow(EffectWindow* w, int mask, QRegion region, WindowPaintData& data) { effects->paintWindow(w, mask, region, data); // Take intersection of region and actual window's rect, minus the fps area // (since we keep repainting it) and count the pixels. QRegion r2 = region & QRect(w->x(), w->y(), w->width(), w->height()); r2 -= fps_rect; int winsize = 0; for (const QRect &r : r2) { winsize += r.width() * r.height(); } paint_size[ paints_pos ] += winsize; } void ShowFpsEffect::paintScreen(int mask, QRegion region, ScreenPaintData& data) { effects->paintScreen(mask, region, data); int fps = 0; for (int i = 0; i < MAX_FPS; ++i) if (abs(t.minute() * 60000 + t.second() * 1000 + t.msec() - frames[ i ]) < 1000) ++fps; // count all frames in the last second if (fps > MAX_TIME) fps = MAX_TIME; // keep it the same height if (effects->isOpenGLCompositing()) { paintGL(fps, data.projectionMatrix()); glFinish(); // make sure all rendering is done } #ifdef KWIN_HAVE_XRENDER_COMPOSITING if (effects->compositingType() == XRenderCompositing) { paintXrender(fps); xcb_flush(xcbConnection()); // make sure all rendering is done } #endif if (effects->compositingType() == QPainterCompositing) { paintQPainter(fps); } m_noBenchmark->render(infiniteRegion(), 1.0, alpha); } void ShowFpsEffect::paintGL(int fps, const QMatrix4x4 &projectionMatrix) { int x = this->x; int y = this->y; glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); // TODO painting first the background white and then the contents // means that the contents also blend with the background, I guess ShaderBinder binder(ShaderTrait::UniformColor); binder.shader()->setUniform(GLShader::ModelViewProjectionMatrix, projectionMatrix); GLVertexBuffer *vbo = GLVertexBuffer::streamingBuffer(); vbo->reset(); QColor color(255, 255, 255); color.setAlphaF(alpha); vbo->setColor(color); QVector verts; verts.reserve(12); verts << x + 2 * NUM_PAINTS + FPS_WIDTH << y; verts << x << y; verts << x << y + MAX_TIME; verts << x << y + MAX_TIME; verts << x + 2 * NUM_PAINTS + FPS_WIDTH << y + MAX_TIME; verts << x + 2 * NUM_PAINTS + FPS_WIDTH << y; - vbo->setData(6, 2, verts.constData(), NULL); + vbo->setData(6, 2, verts.constData(), nullptr); vbo->render(GL_TRIANGLES); y += MAX_TIME; // paint up from the bottom color.setRed(0); color.setGreen(0); vbo->setColor(color); verts.clear(); verts << x + FPS_WIDTH << y - fps; verts << x << y - fps; verts << x << y; verts << x << y; verts << x + FPS_WIDTH << y; verts << x + FPS_WIDTH << y - fps; - vbo->setData(6, 2, verts.constData(), NULL); + vbo->setData(6, 2, verts.constData(), nullptr); vbo->render(GL_TRIANGLES); color.setBlue(0); vbo->setColor(color); QVector vertices; for (int i = 10; i < MAX_TIME; i += 10) { vertices << x << y - i; vertices << x + FPS_WIDTH << y - i; } - vbo->setData(vertices.size() / 2, 2, vertices.constData(), NULL); + vbo->setData(vertices.size() / 2, 2, vertices.constData(), nullptr); vbo->render(GL_LINES); x += FPS_WIDTH; // Paint FPS graph paintFPSGraph(x, y); x += NUM_PAINTS; // Paint amount of rendered pixels graph paintDrawSizeGraph(x, y); // Paint FPS numerical value if (fpsTextRect.isValid()) { fpsText.reset(new GLTexture(fpsTextImage(fps))); fpsText->bind(); ShaderBinder binder(ShaderTrait::MapTexture); QMatrix4x4 mvp = projectionMatrix; mvp.translate(fpsTextRect.x(), fpsTextRect.y()); binder.shader()->setUniform(GLShader::ModelViewProjectionMatrix, mvp); fpsText->render(QRegion(fpsTextRect), fpsTextRect); fpsText->unbind(); effects->addRepaint(fpsTextRect); } // Paint paint sizes glDisable(GL_BLEND); } #ifdef KWIN_HAVE_XRENDER_COMPOSITING /* Differences between OpenGL and XRender: - differently specified rectangles (X: width/height, O: x2,y2) - XRender uses pre-multiplied alpha */ void ShowFpsEffect::paintXrender(int fps) { xcb_pixmap_t pixmap = xcb_generate_id(xcbConnection()); xcb_create_pixmap(xcbConnection(), 32, pixmap, x11RootWindow(), FPS_WIDTH, MAX_TIME); XRenderPicture p(pixmap, 32); xcb_free_pixmap(xcbConnection(), pixmap); xcb_render_color_t col; col.alpha = int(alpha * 0xffff); col.red = int(alpha * 0xffff); // white col.green = int(alpha * 0xffff); col.blue = int(alpha * 0xffff); xcb_rectangle_t rect = {0, 0, FPS_WIDTH, MAX_TIME}; xcb_render_fill_rectangles(xcbConnection(), XCB_RENDER_PICT_OP_SRC, p, col, 1, &rect); col.red = 0; // blue col.green = 0; col.blue = int(alpha * 0xffff); rect.y = MAX_TIME - fps; rect.width = FPS_WIDTH; rect.height = fps; xcb_render_fill_rectangles(xcbConnection(), XCB_RENDER_PICT_OP_SRC, p, col, 1, &rect); col.red = 0; // black col.green = 0; col.blue = 0; QVector rects; for (int i = 10; i < MAX_TIME; i += 10) { xcb_rectangle_t rect = {0, int16_t(MAX_TIME - i), uint16_t(FPS_WIDTH), 1}; rects << rect; } xcb_render_fill_rectangles(xcbConnection(), XCB_RENDER_PICT_OP_SRC, p, col, rects.count(), rects.constData()); xcb_render_composite(xcbConnection(), alpha != 1.0 ? XCB_RENDER_PICT_OP_OVER : XCB_RENDER_PICT_OP_SRC, p, XCB_RENDER_PICTURE_NONE, effects->xrenderBufferPicture(), 0, 0, 0, 0, x, y, FPS_WIDTH, MAX_TIME); // Paint FPS graph paintFPSGraph(x + FPS_WIDTH, y); // Paint amount of rendered pixels graph paintDrawSizeGraph(x + FPS_WIDTH + MAX_TIME, y); // Paint FPS numerical value if (fpsTextRect.isValid()) { QImage textImg(fpsTextImage(fps)); XRenderPicture textPic(textImg); xcb_render_composite(xcbConnection(), XCB_RENDER_PICT_OP_OVER, textPic, XCB_RENDER_PICTURE_NONE, effects->xrenderBufferPicture(), 0, 0, 0, 0, fpsTextRect.x(), fpsTextRect.y(), textImg.width(), textImg.height()); effects->addRepaint(fpsTextRect); } } #endif void ShowFpsEffect::paintQPainter(int fps) { QPainter *painter = effects->scenePainter(); painter->save(); QColor color(255, 255, 255); color.setAlphaF(alpha); painter->setCompositionMode(QPainter::CompositionMode_SourceOver); painter->fillRect(x, y, 2 * NUM_PAINTS + FPS_WIDTH, MAX_TIME, color); color.setRed(0); color.setGreen(0); painter->fillRect(x, y + MAX_TIME - fps, FPS_WIDTH, fps, color); color.setBlue(0); for (int i = 10; i < MAX_TIME; i += 10) { painter->setPen(color); painter->drawLine(x, y + MAX_TIME - i, x + FPS_WIDTH, y + MAX_TIME - i); } // Paint FPS graph paintFPSGraph(x + FPS_WIDTH, y + MAX_TIME - 1); // Paint amount of rendered pixels graph paintDrawSizeGraph(x + FPS_WIDTH + NUM_PAINTS, y + MAX_TIME - 1); // Paint FPS numerical value painter->setPen(Qt::black); painter->drawText(fpsTextRect, textAlign, QString::number(fps)); painter->restore(); } void ShowFpsEffect::paintFPSGraph(int x, int y) { // Paint FPS graph QList lines; lines << 10 << 20 << 50; QList values; for (int i = 0; i < NUM_PAINTS; ++i) { values.append(paints[(i + paints_pos) % NUM_PAINTS ]); } paintGraph(x, y, values, lines, true); } void ShowFpsEffect::paintDrawSizeGraph(int x, int y) { int max_drawsize = 0; for (int i = 0; i < NUM_PAINTS; i++) max_drawsize = qMax(max_drawsize, paint_size[ i ]); // Log of min/max values shown on graph const float max_pixels_log = 7.2f; const float min_pixels_log = 2.0f; const int minh = 5; // Minimum height of the bar when value > 0 float drawscale = (MAX_TIME - minh) / (max_pixels_log - min_pixels_log); QList drawlines; for (int logh = (int)min_pixels_log; logh <= max_pixels_log; logh++) drawlines.append((int)((logh - min_pixels_log) * drawscale) + minh); QList drawvalues; for (int i = 0; i < NUM_PAINTS; ++i) { int value = paint_size[(i + paints_pos) % NUM_PAINTS ]; int h = 0; if (value > 0) { h = (int)((log10((double)value) - min_pixels_log) * drawscale); h = qMin(qMax(0, h) + minh, MAX_TIME); } drawvalues.append(h); } paintGraph(x, y, drawvalues, drawlines, false); } void ShowFpsEffect::paintGraph(int x, int y, QList values, QList lines, bool colorize) { if (effects->isOpenGLCompositing()) { QColor color(0, 0, 0); color.setAlphaF(alpha); GLVertexBuffer *vbo = GLVertexBuffer::streamingBuffer(); vbo->reset(); vbo->setColor(color); QVector verts; // First draw the lines foreach (int h, lines) { verts << x << y - h; verts << x + values.count() << y - h; } - vbo->setData(verts.size() / 2, 2, verts.constData(), NULL); + vbo->setData(verts.size() / 2, 2, verts.constData(), nullptr); vbo->render(GL_LINES); // Then the graph values int lastValue = 0; verts.clear(); for (int i = 0; i < values.count(); i++) { int value = values[ i ]; if (colorize && value != lastValue) { if (!verts.isEmpty()) { - vbo->setData(verts.size() / 2, 2, verts.constData(), NULL); + vbo->setData(verts.size() / 2, 2, verts.constData(), nullptr); vbo->render(GL_LINES); } verts.clear(); if (value <= 10) { color = QColor(0, 255, 0); } else if (value <= 20) { color = QColor(255, 255, 0); } else if (value <= 50) { color = QColor(255, 0, 0); } else { color = QColor(0, 0, 0); } vbo->setColor(color); } verts << x + values.count() - i << y; verts << x + values.count() - i << y - value; lastValue = value; } if (!verts.isEmpty()) { - vbo->setData(verts.size() / 2, 2, verts.constData(), NULL); + vbo->setData(verts.size() / 2, 2, verts.constData(), nullptr); vbo->render(GL_LINES); } } #ifdef KWIN_HAVE_XRENDER_COMPOSITING if (effects->compositingType() == XRenderCompositing) { xcb_pixmap_t pixmap = xcb_generate_id(xcbConnection()); xcb_create_pixmap(xcbConnection(), 32, pixmap, x11RootWindow(), values.count(), MAX_TIME); XRenderPicture p(pixmap, 32); xcb_free_pixmap(xcbConnection(), pixmap); xcb_render_color_t col; col.alpha = int(alpha * 0xffff); // Draw background col.red = col.green = col.blue = int(alpha * 0xffff); // white xcb_rectangle_t rect = {0, 0, uint16_t(values.count()), uint16_t(MAX_TIME)}; xcb_render_fill_rectangles(xcbConnection(), XCB_RENDER_PICT_OP_SRC, p, col, 1, &rect); // Then the values col.red = col.green = col.blue = int(alpha * 0x8000); // grey for (int i = 0; i < values.count(); i++) { int value = values[ i ]; if (colorize) { if (value <= 10) { // green col.red = 0; col.green = int(alpha * 0xffff); col.blue = 0; } else if (value <= 20) { // yellow col.red = int(alpha * 0xffff); col.green = int(alpha * 0xffff); col.blue = 0; } else if (value <= 50) { // red col.red = int(alpha * 0xffff); col.green = 0; col.blue = 0; } else { // black col.red = 0; col.green = 0; col.blue = 0; } } xcb_rectangle_t rect = {int16_t(values.count() - i), int16_t(MAX_TIME - value), 1, uint16_t(value)}; xcb_render_fill_rectangles(xcbConnection(), XCB_RENDER_PICT_OP_SRC, p, col, 1, &rect); } // Then the lines col.red = col.green = col.blue = 0; // black QVector rects; foreach (int h, lines) { xcb_rectangle_t rect = {0, int16_t(MAX_TIME - h), uint16_t(values.count()), 1}; rects << rect; } xcb_render_fill_rectangles(xcbConnection(), XCB_RENDER_PICT_OP_SRC, p, col, rects.count(), rects.constData()); // Finally render the pixmap onto screen xcb_render_composite(xcbConnection(), alpha != 1.0 ? XCB_RENDER_PICT_OP_OVER : XCB_RENDER_PICT_OP_SRC, p, XCB_RENDER_PICTURE_NONE, effects->xrenderBufferPicture(), 0, 0, 0, 0, x, y, values.count(), MAX_TIME); } #endif if (effects->compositingType() == QPainterCompositing) { QPainter *painter = effects->scenePainter(); painter->setPen(Qt::black); // First draw the lines foreach (int h, lines) { painter->drawLine(x, y - h, x + values.count(), y - h); } QColor color(0, 0, 0); color.setAlphaF(alpha); for (int i = 0; i < values.count(); i++) { int value = values[ i ]; if (colorize) { if (value <= 10) { color = QColor(0, 255, 0); } else if (value <= 20) { color = QColor(255, 255, 0); } else if (value <= 50) { color = QColor(255, 0, 0); } else { color = QColor(0, 0, 0); } } painter->setPen(color); painter->drawLine(x + values.count() - i, y, x + values.count() - i, y - value); } } } void ShowFpsEffect::postPaintScreen() { effects->postPaintScreen(); paints[ paints_pos ] = t.elapsed(); if (++paints_pos == NUM_PAINTS) paints_pos = 0; effects->addRepaint(fps_rect); } QImage ShowFpsEffect::fpsTextImage(int fps) { QImage im(100, 100, QImage::Format_ARGB32); im.fill(Qt::transparent); QPainter painter(&im); painter.setFont(textFont); painter.setPen(textColor); painter.drawText(QRect(0, 0, 100, 100), textAlign, QString::number(fps)); painter.end(); return im; } } // namespace diff --git a/effects/showfps/showfps_config.h b/effects/showfps/showfps_config.h index 7ac264d08..7d79a07fc 100644 --- a/effects/showfps/showfps_config.h +++ b/effects/showfps/showfps_config.h @@ -1,47 +1,47 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2007 Rivo Laks 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_SHOWFPS_CONFIG_H #define KWIN_SHOWFPS_CONFIG_H #include #include "ui_showfps_config.h" namespace KWin { class ShowFpsEffectConfig : public KCModule { Q_OBJECT public: - explicit ShowFpsEffectConfig(QWidget* parent = 0, const QVariantList& args = QVariantList()); + explicit ShowFpsEffectConfig(QWidget* parent = nullptr, const QVariantList& args = QVariantList()); ~ShowFpsEffectConfig() override; public Q_SLOTS: void save() override; private: Ui::ShowFpsEffectConfigForm *m_ui; }; } // namespace #endif diff --git a/effects/slideback/slideback.cpp b/effects/slideback/slideback.cpp index 67970ecd4..00356e8ba 100644 --- a/effects/slideback/slideback.cpp +++ b/effects/slideback/slideback.cpp @@ -1,341 +1,341 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2009 Michael Zanetti 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 "slideback.h" namespace KWin { SlideBackEffect::SlideBackEffect() { m_tabboxActive = 0; - m_justMapped = m_upmostWindow = NULL; + m_justMapped = m_upmostWindow = nullptr; connect(effects, &EffectsHandler::windowAdded, this, &SlideBackEffect::slotWindowAdded); connect(effects, &EffectsHandler::windowDeleted, this, &SlideBackEffect::slotWindowDeleted); connect(effects, &EffectsHandler::windowUnminimized, this, &SlideBackEffect::slotWindowUnminimized); connect(effects, &EffectsHandler::tabBoxAdded, this, &SlideBackEffect::slotTabBoxAdded); connect(effects, &EffectsHandler::stackingOrderChanged, this, &SlideBackEffect::slotStackingOrderChanged); connect(effects, &EffectsHandler::tabBoxClosed, this, &SlideBackEffect::slotTabBoxClosed); } void SlideBackEffect::slotStackingOrderChanged() { if (effects->activeFullScreenEffect() || m_tabboxActive) { oldStackingOrder = effects->stackingOrder(); usableOldStackingOrder = usableWindows(oldStackingOrder); return; } EffectWindowList newStackingOrder = effects->stackingOrder(), usableNewStackingOrder = usableWindows(newStackingOrder); if (usableNewStackingOrder == usableOldStackingOrder || usableNewStackingOrder.isEmpty()) { oldStackingOrder = newStackingOrder; usableOldStackingOrder = usableNewStackingOrder; return; } m_upmostWindow = usableNewStackingOrder.last(); if (m_upmostWindow == m_justMapped ) // a window was added, got on top, stacking changed. Nothing impressive - m_justMapped = 0; + m_justMapped = nullptr; else if (!usableOldStackingOrder.isEmpty() && m_upmostWindow != usableOldStackingOrder.last()) windowRaised(m_upmostWindow); oldStackingOrder = newStackingOrder; usableOldStackingOrder = usableNewStackingOrder; } void SlideBackEffect::windowRaised(EffectWindow *w) { // Determine all windows on top of the activated one bool currentFound = false; foreach (EffectWindow * tmp, oldStackingOrder) { if (!currentFound) { if (tmp == w) { currentFound = true; } } else { if (isWindowUsable(tmp) && tmp->isOnCurrentDesktop() && w->isOnCurrentDesktop()) { // Do we have to move it? if (intersects(w, tmp->geometry())) { QRect slideRect; slideRect = getSlideDestination(getModalGroupGeometry(w), tmp->geometry()); effects->setElevatedWindow(tmp, true); elevatedList.append(tmp); motionManager.manage(tmp); motionManager.moveWindow(tmp, slideRect); destinationList.insert(tmp, slideRect); coveringWindows.append(tmp); } else { //Does it intersect with a moved (elevated) window and do we have to elevate it too? foreach (EffectWindow * elevatedWindow, elevatedList) { if (tmp->geometry().intersects(elevatedWindow->geometry())) { effects->setElevatedWindow(tmp, true); elevatedList.append(tmp); break; } } } } if (tmp->isDock() || tmp->keepAbove()) { effects->setElevatedWindow(tmp, true); elevatedList.append(tmp); } } } // If a window is minimized it could happen that the panels stay elevated without any windows sliding. // clear all elevation settings if (!motionManager.managingWindows()) { foreach (EffectWindow * tmp, elevatedList) { effects->setElevatedWindow(tmp, false); } } } QRect SlideBackEffect::getSlideDestination(const QRect &windowUnderGeometry, const QRect &windowOverGeometry) { // Determine the shortest way: int leftSlide = windowUnderGeometry.left() - windowOverGeometry.right() - 20; int rightSlide = windowUnderGeometry.right() - windowOverGeometry.left() + 20; int upSlide = windowUnderGeometry.top() - windowOverGeometry.bottom() - 20; int downSlide = windowUnderGeometry.bottom() - windowOverGeometry.top() + 20; int horizSlide = leftSlide; if (qAbs(horizSlide) > qAbs(rightSlide)) { horizSlide = rightSlide; } int vertSlide = upSlide; if (qAbs(vertSlide) > qAbs(downSlide)) { vertSlide = downSlide; } QRect slideRect = windowOverGeometry; if (qAbs(horizSlide) < qAbs(vertSlide)) { slideRect.moveLeft(slideRect.x() + horizSlide); } else { slideRect.moveTop(slideRect.y() + vertSlide); } return slideRect; } void SlideBackEffect::prePaintScreen(ScreenPrePaintData &data, int time) { if (motionManager.managingWindows()) { motionManager.calculate(time); data.mask |= Effect::PAINT_SCREEN_WITH_TRANSFORMED_WINDOWS; } for (auto const &w : effects->stackingOrder()) { w->setData(WindowForceBlurRole, QVariant(true)); } effects->prePaintScreen(data, time); } void SlideBackEffect::postPaintScreen() { if (motionManager.areWindowsMoving()) { effects->addRepaintFull(); } for (auto &w : effects->stackingOrder()) { w->setData(WindowForceBlurRole, QVariant()); } effects->postPaintScreen(); } void SlideBackEffect::prePaintWindow(EffectWindow *w, WindowPrePaintData &data, int time) { if (motionManager.isManaging(w)) { data.setTransformed(); } effects->prePaintWindow(w, data, time); } void SlideBackEffect::paintWindow(EffectWindow *w, int mask, QRegion region, WindowPaintData &data) { if (motionManager.isManaging(w)) { motionManager.apply(w, data); } foreach (const QRegion &r, clippedRegions) { region = region.intersected(r); } effects->paintWindow(w, mask, region, data); for (int i = clippedRegions.count() - 1; i > -1; --i) PaintClipper::pop(clippedRegions.at(i)); clippedRegions.clear(); } void SlideBackEffect::postPaintWindow(EffectWindow* w) { if (motionManager.isManaging(w)) { if (destinationList.contains(w)) { if (!motionManager.isWindowMoving(w)) { // has window reched its destination? // If we are still intersecting with the upmostWindow it is moving. slide to somewhere else // restore the stacking order of all windows not intersecting any more except panels if (coveringWindows.contains(w)) { EffectWindowList tmpList; foreach (EffectWindow * tmp, elevatedList) { QRect elevatedGeometry = tmp->geometry(); if (motionManager.isManaging(tmp)) { elevatedGeometry = motionManager.transformedGeometry(tmp).toAlignedRect(); } if (m_upmostWindow && !tmp->isDock() && !tmp->keepAbove() && m_upmostWindow->geometry().intersects(elevatedGeometry)) { QRect newDestination; newDestination = getSlideDestination(getModalGroupGeometry(m_upmostWindow), elevatedGeometry); if (!motionManager.isManaging(tmp)) { motionManager.manage(tmp); } motionManager.moveWindow(tmp, newDestination); destinationList[tmp] = newDestination; } else { if (!tmp->isDock()) { bool keepElevated = false; foreach (EffectWindow * elevatedWindow, tmpList) { if (tmp->geometry().intersects(elevatedWindow->geometry())) { keepElevated = true; } } if (!keepElevated) { effects->setElevatedWindow(tmp, false); elevatedList.removeAll(tmp); } } } tmpList.append(tmp); } } else { // Move the window back where it belongs motionManager.moveWindow(w, w->geometry()); destinationList.remove(w); } } } else { // is window back at its original position? if (!motionManager.isWindowMoving(w)) { motionManager.unmanage(w); effects->addRepaintFull(); } } if (coveringWindows.contains(w)) { // It could happen that there is no aciveWindow() here if the user clicks the close-button on an inactive window. // Just skip... the window will be removed in windowDeleted() later if (m_upmostWindow && !intersects(m_upmostWindow, motionManager.transformedGeometry(w).toAlignedRect())) { coveringWindows.removeAll(w); if (coveringWindows.isEmpty()) { // Restore correct stacking order foreach (EffectWindow * tmp, elevatedList) { effects->setElevatedWindow(tmp, false); } elevatedList.clear(); } } } } effects->postPaintWindow(w); } void SlideBackEffect::slotWindowDeleted(EffectWindow* w) { if (w == m_upmostWindow) - m_upmostWindow = 0; + m_upmostWindow = nullptr; if (w == m_justMapped) - m_justMapped = 0; + m_justMapped = nullptr; usableOldStackingOrder.removeAll(w); oldStackingOrder.removeAll(w); coveringWindows.removeAll(w); elevatedList.removeAll(w); if (motionManager.isManaging(w)) { motionManager.unmanage(w); } } void SlideBackEffect::slotWindowAdded(EffectWindow *w) { m_justMapped = w; } void SlideBackEffect::slotWindowUnminimized(EffectWindow* w) { // SlideBack should not be triggered on an unminimized window. For this we need to store the last unminimized window. m_justMapped = w; // the stackingOrderChanged() signal came before the window turned an effect window // usually this is no problem as the change shall not be caught anyway, but // the window may have changed its stack position, bug #353745 slotStackingOrderChanged(); } void SlideBackEffect::slotTabBoxAdded() { ++m_tabboxActive; } void SlideBackEffect::slotTabBoxClosed() { m_tabboxActive = qMax(m_tabboxActive-1, 0); } bool SlideBackEffect::isWindowUsable(EffectWindow* w) { return w && (w->isNormalWindow() || w->isDialog()) && !w->keepAbove() && !w->isDeleted() && !w->isMinimized() && w->isPaintingEnabled(); } bool SlideBackEffect::intersects(EffectWindow* windowUnder, const QRect &windowOverGeometry) { QRect windowUnderGeometry = getModalGroupGeometry(windowUnder); return windowUnderGeometry.intersects(windowOverGeometry); } EffectWindowList SlideBackEffect::usableWindows(const EffectWindowList & allWindows) { EffectWindowList retList; auto isWindowVisible = [] (const EffectWindow *window) { return window && effects->virtualScreenGeometry().intersects(window->geometry()); }; foreach (EffectWindow * tmp, allWindows) { if (isWindowUsable(tmp) && isWindowVisible(tmp)) { retList.append(tmp); } } return retList; } QRect SlideBackEffect::getModalGroupGeometry(EffectWindow *w) { QRect modalGroupGeometry = w->geometry(); if (w->isModal()) { foreach (EffectWindow * modalWindow, w->mainWindows()) { modalGroupGeometry = modalGroupGeometry.united(getModalGroupGeometry(modalWindow)); } } return modalGroupGeometry; } bool SlideBackEffect::isActive() const { return motionManager.managingWindows(); } } //Namespace diff --git a/effects/startupfeedback/startupfeedback.cpp b/effects/startupfeedback/startupfeedback.cpp index 01f0ae23d..6864d65d9 100644 --- a/effects/startupfeedback/startupfeedback.cpp +++ b/effects/startupfeedback/startupfeedback.cpp @@ -1,401 +1,401 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2010 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 "startupfeedback.h" // Qt #include #include #include #include #include #include // KDE #include #include #include #include #include #include // KWin #include // based on StartupId in KRunner by Lubos Lunak // Copyright (C) 2001 Lubos Lunak namespace KWin { // number of key frames for bouncing animation static const int BOUNCE_FRAMES = 20; // duration between two key frames in msec static const int BOUNCE_FRAME_DURATION = 30; // duration of one bounce animation static const int BOUNCE_DURATION = BOUNCE_FRAME_DURATION * BOUNCE_FRAMES; // number of key frames for blinking animation static const int BLINKING_FRAMES = 5; // duration between two key frames in msec static const int BLINKING_FRAME_DURATION = 100; // duration of one blinking animation static const int BLINKING_DURATION = BLINKING_FRAME_DURATION * BLINKING_FRAMES; //const int color_to_pixmap[] = { 0, 1, 2, 3, 2, 1 }; static const int FRAME_TO_BOUNCE_YOFFSET[] = { -5, -1, 2, 5, 8, 10, 12, 13, 15, 15, 15, 15, 14, 12, 10, 8, 5, 2, -1, -5 }; static const QSize BOUNCE_SIZES[] = { QSize(16, 16), QSize(14, 18), QSize(12, 20), QSize(18, 14), QSize(20, 12) }; static const int FRAME_TO_BOUNCE_TEXTURE[] = { 0, 0, 0, 1, 2, 2, 1, 0, 3, 4, 4, 3, 0, 1, 2, 2, 1, 0, 0, 0 }; static const int FRAME_TO_BLINKING_COLOR[] = { 0, 1, 2, 3, 2, 1 }; static const QColor BLINKING_COLORS[] = { Qt::black, Qt::darkGray, Qt::lightGray, Qt::white, Qt::white }; static const int s_startupDefaultTimeout = 5; StartupFeedbackEffect::StartupFeedbackEffect() : m_bounceSizesRatio(1.0) , m_startupInfo(new KStartupInfo(KStartupInfo::CleanOnCantDetect, this)) , m_selection(nullptr) , m_active(false) , m_frame(0) , m_progress(0) - , m_texture(0) + , m_texture(nullptr) , m_type(BouncingFeedback) - , m_blinkingShader(0) + , m_blinkingShader(nullptr) , m_cursorSize(0) { for (int i = 0; i < 5; ++i) { - m_bouncingTextures[i] = 0; + m_bouncingTextures[i] = nullptr; } if (KWindowSystem::isPlatformX11()) { m_selection = new KSelectionOwner("_KDE_STARTUP_FEEDBACK", xcbConnection(), x11RootWindow(), this); m_selection->claim(true); } connect(m_startupInfo, &KStartupInfo::gotNewStartup, this, &StartupFeedbackEffect::gotNewStartup); connect(m_startupInfo, &KStartupInfo::gotRemoveStartup, this, &StartupFeedbackEffect::gotRemoveStartup); connect(m_startupInfo, &KStartupInfo::gotStartupChange, this, &StartupFeedbackEffect::gotStartupChange); connect(effects, &EffectsHandler::mouseChanged, this, &StartupFeedbackEffect::slotMouseChanged); reconfigure(ReconfigureAll); } StartupFeedbackEffect::~StartupFeedbackEffect() { if (m_active) { effects->stopMousePolling(); } for (int i = 0; i < 5; ++i) { delete m_bouncingTextures[i]; } delete m_texture; delete m_blinkingShader; } bool StartupFeedbackEffect::supported() { return effects->isOpenGLCompositing(); } void StartupFeedbackEffect::reconfigure(Effect::ReconfigureFlags flags) { Q_UNUSED(flags) KConfig conf(QStringLiteral("klaunchrc"), KConfig::NoGlobals); KConfigGroup c = conf.group("FeedbackStyle"); const bool busyCursor = c.readEntry("BusyCursor", true); c = conf.group("BusyCursorSettings"); m_startupInfo->setTimeout(c.readEntry("Timeout", s_startupDefaultTimeout)); const bool busyBlinking = c.readEntry("Blinking", false); const bool busyBouncing = c.readEntry("Bouncing", true); if (!busyCursor) m_type = NoFeedback; else if (busyBouncing) m_type = BouncingFeedback; else if (busyBlinking) { m_type = BlinkingFeedback; if (effects->compositingType() == OpenGL2Compositing) { delete m_blinkingShader; m_blinkingShader = ShaderManager::instance()->generateShaderFromResources(ShaderTrait::MapTexture, QString(), QStringLiteral("blinking-startup-fragment.glsl")); if (m_blinkingShader->isValid()) { qCDebug(KWINEFFECTS) << "Blinking Shader is valid"; } else { qCDebug(KWINEFFECTS) << "Blinking Shader is not valid"; } } } else m_type = PassiveFeedback; if (m_active) { stop(); start(m_startups[ m_currentStartup ]); } } void StartupFeedbackEffect::prePaintScreen(ScreenPrePaintData& data, int time) { if (m_active) { // need the unclipped version switch(m_type) { case BouncingFeedback: m_progress = (m_progress + time) % BOUNCE_DURATION; m_frame = qRound((qreal)m_progress / (qreal)BOUNCE_FRAME_DURATION) % BOUNCE_FRAMES; m_currentGeometry = feedbackRect(); // bounce alters geometry with m_frame data.paint = data.paint.united(m_currentGeometry); break; case BlinkingFeedback: m_progress = (m_progress + time) % BLINKING_DURATION; m_frame = qRound((qreal)m_progress / (qreal)BLINKING_FRAME_DURATION) % BLINKING_FRAMES; break; default: break; // nothing } } effects->prePaintScreen(data, time); } void StartupFeedbackEffect::paintScreen(int mask, QRegion region, ScreenPaintData& data) { effects->paintScreen(mask, region, data); if (m_active) { GLTexture* texture; switch(m_type) { case BouncingFeedback: texture = m_bouncingTextures[ FRAME_TO_BOUNCE_TEXTURE[ m_frame ]]; break; case BlinkingFeedback: // fall through case PassiveFeedback: texture = m_texture; break; default: return; // safety } glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); texture->bind(); if (m_type == BlinkingFeedback && m_blinkingShader && m_blinkingShader->isValid()) { const QColor& blinkingColor = BLINKING_COLORS[ FRAME_TO_BLINKING_COLOR[ m_frame ]]; ShaderManager::instance()->pushShader(m_blinkingShader); m_blinkingShader->setUniform(GLShader::Color, blinkingColor); } else { ShaderManager::instance()->pushShader(ShaderTrait::MapTexture); } QMatrix4x4 mvp = data.projectionMatrix(); mvp.translate(m_currentGeometry.x(), m_currentGeometry.y()); ShaderManager::instance()->getBoundShader()->setUniform(GLShader::ModelViewProjectionMatrix, mvp); texture->render(m_currentGeometry, m_currentGeometry); ShaderManager::instance()->popShader(); texture->unbind(); glDisable(GL_BLEND); } } void StartupFeedbackEffect::postPaintScreen() { if (m_active) { m_dirtyRect = m_currentGeometry; // ensure the now dirty region is cleaned on the next pass if (m_type == BlinkingFeedback || m_type == BouncingFeedback) effects->addRepaint(m_dirtyRect); // we also have to trigger a repaint } effects->postPaintScreen(); } void StartupFeedbackEffect::slotMouseChanged(const QPoint& pos, const QPoint& oldpos, Qt::MouseButtons buttons, Qt::MouseButtons oldbuttons, Qt::KeyboardModifiers modifiers, Qt::KeyboardModifiers oldmodifiers) { Q_UNUSED(pos) Q_UNUSED(oldpos) Q_UNUSED(buttons) Q_UNUSED(oldbuttons) Q_UNUSED(modifiers) Q_UNUSED(oldmodifiers) if (m_active) { m_dirtyRect |= m_currentGeometry; m_currentGeometry = feedbackRect(); m_dirtyRect |= m_currentGeometry; effects->addRepaint(m_dirtyRect); } } void StartupFeedbackEffect::gotNewStartup(const KStartupInfoId& id, const KStartupInfoData& data) { const QString& icon = data.findIcon(); m_currentStartup = id; m_startups[ id ] = icon; start(icon); } void StartupFeedbackEffect::gotRemoveStartup(const KStartupInfoId& id, const KStartupInfoData& data) { Q_UNUSED( data ) m_startups.remove(id); if (m_startups.count() == 0) { m_currentStartup = KStartupInfoId(); // null stop(); return; } m_currentStartup = m_startups.begin().key(); start(m_startups[ m_currentStartup ]); } void StartupFeedbackEffect::gotStartupChange(const KStartupInfoId& id, const KStartupInfoData& data) { if (m_currentStartup == id) { const QString& icon = data.findIcon(); if (!icon.isEmpty() && icon != m_startups[ m_currentStartup ]) { m_startups[ id ] = icon; start(icon); } } } void StartupFeedbackEffect::start(const QString& icon) { if (m_type == NoFeedback) return; if (!m_active) effects->startMousePolling(); m_active = true; // get ratio for bouncing cursor so we don't need to manually calculate the sizes for each icon size if (m_type == BouncingFeedback) m_bounceSizesRatio = IconSize(KIconLoader::Small) / 16.0; QPixmap iconPixmap = KIconLoader::global()->loadIcon(icon, KIconLoader::Small, 0, - KIconLoader::DefaultState, QStringList(), 0, true); // return null pixmap if not found + KIconLoader::DefaultState, QStringList(), nullptr, true); // return null pixmap if not found if (iconPixmap.isNull()) iconPixmap = SmallIcon(QStringLiteral("system-run")); prepareTextures(iconPixmap); auto readCursorSize = []() -> int { // read details about the mouse-cursor theme define per default KConfigGroup mousecfg(effects->inputConfig(), "Mouse"); QString size = mousecfg.readEntry("cursorSize", QString()); // fetch a reasonable size for the cursor-theme image bool ok; int cursorSize = size.toInt(&ok); if (!ok) cursorSize = QApplication::style()->pixelMetric(QStyle::PM_LargeIconSize); return cursorSize; }; m_cursorSize = readCursorSize(); m_dirtyRect = m_currentGeometry = feedbackRect(); effects->addRepaint(m_dirtyRect); } void StartupFeedbackEffect::stop() { if (m_active) effects->stopMousePolling(); m_active = false; effects->makeOpenGLContextCurrent(); switch(m_type) { case BouncingFeedback: for (int i = 0; i < 5; ++i) { delete m_bouncingTextures[i]; - m_bouncingTextures[i] = 0; + m_bouncingTextures[i] = nullptr; } break; case BlinkingFeedback: case PassiveFeedback: delete m_texture; - m_texture = 0; + m_texture = nullptr; break; case NoFeedback: return; // don't want the full repaint default: break; // impossible } effects->addRepaintFull(); } void StartupFeedbackEffect::prepareTextures(const QPixmap& pix) { effects->makeOpenGLContextCurrent(); switch(m_type) { case BouncingFeedback: for (int i = 0; i < 5; ++i) { delete m_bouncingTextures[i]; m_bouncingTextures[i] = new GLTexture(scalePixmap(pix, BOUNCE_SIZES[i])); } break; case BlinkingFeedback: case PassiveFeedback: m_texture = new GLTexture(pix); break; default: // for safety m_active = false; break; } } QImage StartupFeedbackEffect::scalePixmap(const QPixmap& pm, const QSize& size) const { const QSize& adjustedSize = size * m_bounceSizesRatio; QImage scaled = pm.toImage().scaled(adjustedSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); if (scaled.format() != QImage::Format_ARGB32_Premultiplied && scaled.format() != QImage::Format_ARGB32) scaled = scaled.convertToFormat(QImage::Format_ARGB32); QImage result(20 * m_bounceSizesRatio, 20 * m_bounceSizesRatio, QImage::Format_ARGB32); QPainter p(&result); p.setCompositionMode(QPainter::CompositionMode_Source); p.fillRect(result.rect(), Qt::transparent); p.drawImage((20 * m_bounceSizesRatio - adjustedSize.width()) / 2, (20*m_bounceSizesRatio - adjustedSize.height()) / 2, scaled, 0, 0, adjustedSize.width(), adjustedSize.height() * m_bounceSizesRatio); return result; } QRect StartupFeedbackEffect::feedbackRect() const { int xDiff; if (m_cursorSize <= 16) xDiff = 8 + 7; else if (m_cursorSize <= 32) xDiff = 16 + 7; else if (m_cursorSize <= 48) xDiff = 24 + 7; else xDiff = 32 + 7; int yDiff = xDiff; - GLTexture* texture = 0; + GLTexture* texture = nullptr; int yOffset = 0; switch(m_type) { case BouncingFeedback: texture = m_bouncingTextures[ FRAME_TO_BOUNCE_TEXTURE[ m_frame ]]; yOffset = FRAME_TO_BOUNCE_YOFFSET[ m_frame ] * m_bounceSizesRatio; break; case BlinkingFeedback: // fall through case PassiveFeedback: texture = m_texture; break; default: // nothing break; } const QPoint cursorPos = effects->cursorPos() + QPoint(xDiff, yDiff + yOffset); QRect rect; if( texture ) rect = QRect(cursorPos, texture->size()); return rect; } bool StartupFeedbackEffect::isActive() const { return m_active; } } // namespace diff --git a/effects/thumbnailaside/thumbnailaside.cpp b/effects/thumbnailaside/thumbnailaside.cpp index 66ce37770..9bca3c6a7 100644 --- a/effects/thumbnailaside/thumbnailaside.cpp +++ b/effects/thumbnailaside/thumbnailaside.cpp @@ -1,196 +1,196 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2007 Lubos Lunak Copyright (C) 2007 Christian Nitschkowski 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 "thumbnailaside.h" // KConfigSkeleton #include "thumbnailasideconfig.h" #include #include #include #include namespace KWin { ThumbnailAsideEffect::ThumbnailAsideEffect() { initConfig(); QAction* a = new QAction(this); a->setObjectName(QStringLiteral("ToggleCurrentThumbnail")); a->setText(i18n("Toggle Thumbnail for Current Window")); KGlobalAccel::self()->setDefaultShortcut(a, QList() << Qt::META + Qt::CTRL + Qt::Key_T); KGlobalAccel::self()->setShortcut(a, QList() << Qt::META + Qt::CTRL + Qt::Key_T); effects->registerGlobalShortcut(Qt::META + Qt::CTRL + Qt::Key_T, a); connect(a, &QAction::triggered, this, &ThumbnailAsideEffect::toggleCurrentThumbnail); connect(effects, &EffectsHandler::windowClosed, this, &ThumbnailAsideEffect::slotWindowClosed); connect(effects, &EffectsHandler::windowGeometryShapeChanged, this, &ThumbnailAsideEffect::slotWindowGeometryShapeChanged); connect(effects, &EffectsHandler::windowDamaged, this, &ThumbnailAsideEffect::slotWindowDamaged); connect(effects, &EffectsHandler::screenLockingChanged, this, &ThumbnailAsideEffect::repaintAll); reconfigure(ReconfigureAll); } void ThumbnailAsideEffect::reconfigure(ReconfigureFlags) { ThumbnailAsideConfig::self()->read(); maxwidth = ThumbnailAsideConfig::maxWidth(); spacing = ThumbnailAsideConfig::spacing(); opacity = ThumbnailAsideConfig::opacity()/100.0; screen = ThumbnailAsideConfig::screen(); // Xinerama screen TODO add gui option arrange(); } void ThumbnailAsideEffect::paintScreen(int mask, QRegion region, ScreenPaintData& data) { painted = QRegion(); effects->paintScreen(mask, region, data); const QMatrix4x4 projectionMatrix = data.projectionMatrix(); foreach (const Data & d, windows) { if (painted.intersects(d.rect)) { WindowPaintData data(d.window, projectionMatrix); data.multiplyOpacity(opacity); QRect region; setPositionTransformations(data, region, d.window, d.rect, Qt::KeepAspectRatio); effects->drawWindow(d.window, PAINT_WINDOW_OPAQUE | PAINT_WINDOW_TRANSLUCENT | PAINT_WINDOW_TRANSFORMED | PAINT_WINDOW_LANCZOS, region, data); } } } void ThumbnailAsideEffect::paintWindow(EffectWindow *w, int mask, QRegion region, WindowPaintData &data) { effects->paintWindow(w, mask, region, data); painted |= region; } void ThumbnailAsideEffect::slotWindowDamaged(EffectWindow* w, const QRect&) { foreach (const Data & d, windows) { if (d.window == w) effects->addRepaint(d.rect); } } void ThumbnailAsideEffect::slotWindowGeometryShapeChanged(EffectWindow* w, const QRect& old) { foreach (const Data & d, windows) { if (d.window == w) { if (w->size() == old.size()) effects->addRepaint(d.rect); else arrange(); return; } } } void ThumbnailAsideEffect::slotWindowClosed(EffectWindow* w) { removeThumbnail(w); } void ThumbnailAsideEffect::toggleCurrentThumbnail() { EffectWindow* active = effects->activeWindow(); - if (active == NULL) + if (active == nullptr) return; if (windows.contains(active)) removeThumbnail(active); else addThumbnail(active); } void ThumbnailAsideEffect::addThumbnail(EffectWindow* w) { repaintAll(); // repaint old areas Data d; d.window = w; d.index = windows.count(); windows[ w ] = d; arrange(); } void ThumbnailAsideEffect::removeThumbnail(EffectWindow* w) { if (!windows.contains(w)) return; repaintAll(); // repaint old areas int index = windows[ w ].index; windows.remove(w); for (QHash< EffectWindow*, Data >::Iterator it = windows.begin(); it != windows.end(); ++it) { Data& d = *it; if (d.index > index) --d.index; } arrange(); } void ThumbnailAsideEffect::arrange() { if (windows.size() == 0) return; int height = 0; QVector< int > pos(windows.size()); int mwidth = 0; foreach (const Data & d, windows) { height += d.window->height(); mwidth = qMax(mwidth, d.window->width()); pos[ d.index ] = d.window->height(); } QRect area = effects->clientArea(MaximizeArea, screen, effects->currentDesktop()); double scale = area.height() / double(height); scale = qMin(scale, maxwidth / double(mwidth)); // don't be wider than maxwidth pixels int add = 0; for (int i = 0; i < windows.size(); ++i) { pos[ i ] = int(pos[ i ] * scale); pos[ i ] += spacing + add; // compute offset of each item add = pos[ i ]; } for (QHash< EffectWindow*, Data >::Iterator it = windows.begin(); it != windows.end(); ++it) { Data& d = *it; int width = int(d.window->width() * scale); d.rect = QRect(area.right() - width, area.bottom() - pos[ d.index ], width, int(d.window->height() * scale)); } repaintAll(); } void ThumbnailAsideEffect::repaintAll() { foreach (const Data & d, windows) effects->addRepaint(d.rect); } bool ThumbnailAsideEffect::isActive() const { return !windows.isEmpty() && !effects->isScreenLocked(); } } // namespace diff --git a/effects/thumbnailaside/thumbnailaside_config.h b/effects/thumbnailaside/thumbnailaside_config.h index 88973b832..405589265 100644 --- a/effects/thumbnailaside/thumbnailaside_config.h +++ b/effects/thumbnailaside/thumbnailaside_config.h @@ -1,56 +1,56 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2007 Christian Nitschkowski 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_THUMBNAILASIDE_CONFIG_H #define KWIN_THUMBNAILASIDE_CONFIG_H #include #include "ui_thumbnailaside_config.h" class KActionCollection; namespace KWin { class ThumbnailAsideEffectConfigForm : public QWidget, public Ui::ThumbnailAsideEffectConfigForm { Q_OBJECT public: explicit ThumbnailAsideEffectConfigForm(QWidget* parent); }; class ThumbnailAsideEffectConfig : public KCModule { Q_OBJECT public: - explicit ThumbnailAsideEffectConfig(QWidget* parent = 0, const QVariantList& args = QVariantList()); + explicit ThumbnailAsideEffectConfig(QWidget* parent = nullptr, const QVariantList& args = QVariantList()); ~ThumbnailAsideEffectConfig() override; void save() override; private: ThumbnailAsideEffectConfigForm* m_ui; KActionCollection* m_actionCollection; }; } // namespace #endif diff --git a/effects/touchpoints/touchpoints.cpp b/effects/touchpoints/touchpoints.cpp index 5c358831c..52e9e9521 100644 --- a/effects/touchpoints/touchpoints.cpp +++ b/effects/touchpoints/touchpoints.cpp @@ -1,325 +1,325 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2012 Filip Wieladek 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 "touchpoints.h" #include #include #ifdef KWIN_HAVE_XRENDER_COMPOSITING #include #include #include #endif #include #include #include #include namespace KWin { TouchPointsEffect::TouchPointsEffect() : Effect() { } TouchPointsEffect::~TouchPointsEffect() = default; static const Qt::GlobalColor s_colors[] = { Qt::blue, Qt::red, Qt::green, Qt::cyan, Qt::magenta, Qt::yellow, Qt::gray, Qt::darkBlue, Qt::darkRed, Qt::darkGreen }; Qt::GlobalColor TouchPointsEffect::colorForId(quint32 id) { auto it = m_colors.constFind(id); if (it != m_colors.constEnd()) { return it.value(); } static int s_colorIndex = -1; s_colorIndex = (s_colorIndex + 1) % 10; m_colors.insert(id, s_colors[s_colorIndex]); return s_colors[s_colorIndex]; } bool TouchPointsEffect::touchDown(qint32 id, const QPointF &pos, quint32 time) { Q_UNUSED(time) TouchPoint point; point.pos = pos; point.press = true; point.color = colorForId(id); m_points << point; m_latestPositions.insert(id, pos); repaint(); return false; } bool TouchPointsEffect::touchMotion(qint32 id, const QPointF &pos, quint32 time) { Q_UNUSED(time) TouchPoint point; point.pos = pos; point.press = true; point.color = colorForId(id); m_points << point; m_latestPositions.insert(id, pos); repaint(); return false; } bool TouchPointsEffect::touchUp(qint32 id, quint32 time) { Q_UNUSED(time) auto it = m_latestPositions.constFind(id); if (it != m_latestPositions.constEnd()) { TouchPoint point; point.pos = it.value(); point.press = false; point.color = colorForId(id); m_points << point; } return false; } void TouchPointsEffect::prePaintScreen(ScreenPrePaintData& data, int time) { auto it = m_points.begin(); while (it != m_points.end()) { it->time += time; if (it->time > m_ringLife) { it = m_points.erase(it); } else { it++; } } effects->prePaintScreen(data, time); } void TouchPointsEffect::paintScreen(int mask, QRegion region, ScreenPaintData& data) { effects->paintScreen(mask, region, data); paintScreenSetup(mask, region, data); for (auto it = m_points.constBegin(), end = m_points.constEnd(); it != end; ++it) { for (int i = 0; i < m_ringCount; ++i) { float alpha = computeAlpha(it->time, i); float size = computeRadius(it->time, it->press, i); if (size > 0 && alpha > 0) { QColor color = it->color; color.setAlphaF(alpha); drawCircle(color, it->pos.x(), it->pos.y(), size); } } } paintScreenFinish(mask, region, data); } void TouchPointsEffect::postPaintScreen() { effects->postPaintScreen(); repaint(); } float TouchPointsEffect::computeRadius(int time, bool press, int ring) { float ringDistance = m_ringLife / (m_ringCount * 3); if (press) { return ((time - ringDistance * ring) / m_ringLife) * m_ringMaxSize; } return ((m_ringLife - time - ringDistance * ring) / m_ringLife) * m_ringMaxSize; } float TouchPointsEffect::computeAlpha(int time, int ring) { float ringDistance = m_ringLife / (m_ringCount * 3); return (m_ringLife - (float)time - ringDistance * (ring)) / m_ringLife; } void TouchPointsEffect::repaint() { if (!m_points.isEmpty()) { QRegion dirtyRegion; const int radius = m_ringMaxSize + m_lineWidth; for (auto it = m_points.constBegin(), end = m_points.constEnd(); it != end; ++it) { dirtyRegion |= QRect(it->pos.x() - radius, it->pos.y() - radius, 2*radius, 2*radius); } effects->addRepaint(dirtyRegion); } } bool TouchPointsEffect::isActive() const { return !m_points.isEmpty(); } void TouchPointsEffect::drawCircle(const QColor& color, float cx, float cy, float r) { if (effects->isOpenGLCompositing()) drawCircleGl(color, cx, cy, r); if (effects->compositingType() == XRenderCompositing) drawCircleXr(color, cx, cy, r); if (effects->compositingType() == QPainterCompositing) drawCircleQPainter(color, cx, cy, r); } void TouchPointsEffect::paintScreenSetup(int mask, QRegion region, ScreenPaintData& data) { if (effects->isOpenGLCompositing()) paintScreenSetupGl(mask, region, data); } void TouchPointsEffect::paintScreenFinish(int mask, QRegion region, ScreenPaintData& data) { if (effects->isOpenGLCompositing()) paintScreenFinishGl(mask, region, data); } void TouchPointsEffect::drawCircleGl(const QColor& color, float cx, float cy, float r) { static const int num_segments = 80; static const float theta = 2 * 3.1415926 / float(num_segments); static const float c = cosf(theta); //precalculate the sine and cosine static const float s = sinf(theta); float t; float x = r;//we start at angle = 0 float y = 0; GLVertexBuffer* vbo = GLVertexBuffer::streamingBuffer(); vbo->reset(); vbo->setUseColor(true); vbo->setColor(color); QVector verts; verts.reserve(num_segments * 2); for (int ii = 0; ii < num_segments; ++ii) { verts << x + cx << y + cy;//output vertex //apply the rotation matrix t = x; x = c * x - s * y; y = s * t + c * y; } - vbo->setData(verts.size() / 2, 2, verts.data(), NULL); + vbo->setData(verts.size() / 2, 2, verts.data(), nullptr); vbo->render(GL_LINE_LOOP); } void TouchPointsEffect::drawCircleXr(const QColor& color, float cx, float cy, float r) { #ifdef KWIN_HAVE_XRENDER_COMPOSITING if (r <= m_lineWidth) return; int num_segments = r+8; float theta = 2.0 * 3.1415926 / num_segments; float cos = cosf(theta); //precalculate the sine and cosine float sin = sinf(theta); float x[2] = {r, r-m_lineWidth}; float y[2] = {0, 0}; #define DOUBLE_TO_FIXED(d) ((xcb_render_fixed_t) ((d) * 65536)) QVector strip; strip.reserve(2*num_segments+2); xcb_render_pointfix_t point; point.x = DOUBLE_TO_FIXED(x[1]+cx); point.y = DOUBLE_TO_FIXED(y[1]+cy); strip << point; for (int i = 0; i < num_segments; ++i) { //apply the rotation matrix const float h[2] = {x[0], x[1]}; x[0] = cos * x[0] - sin * y[0]; x[1] = cos * x[1] - sin * y[1]; y[0] = sin * h[0] + cos * y[0]; y[1] = sin * h[1] + cos * y[1]; point.x = DOUBLE_TO_FIXED(x[0]+cx); point.y = DOUBLE_TO_FIXED(y[0]+cy); strip << point; point.x = DOUBLE_TO_FIXED(x[1]+cx); point.y = DOUBLE_TO_FIXED(y[1]+cy); strip << point; } const float h = x[0]; x[0] = cos * x[0] - sin * y[0]; y[0] = sin * h + cos * y[0]; point.x = DOUBLE_TO_FIXED(x[0]+cx); point.y = DOUBLE_TO_FIXED(y[0]+cy); strip << point; XRenderPicture fill = xRenderFill(color); xcb_render_tri_strip(xcbConnection(), XCB_RENDER_PICT_OP_OVER, fill, effects->xrenderBufferPicture(), 0, 0, 0, strip.count(), strip.constData()); #undef DOUBLE_TO_FIXED #else Q_UNUSED(color) Q_UNUSED(cx) Q_UNUSED(cy) Q_UNUSED(r) #endif } void TouchPointsEffect::drawCircleQPainter(const QColor &color, float cx, float cy, float r) { QPainter *painter = effects->scenePainter(); painter->save(); painter->setPen(color); painter->drawArc(cx - r, cy - r, r * 2, r * 2, 0, 5760); painter->restore(); } void TouchPointsEffect::paintScreenSetupGl(int, QRegion, ScreenPaintData &data) { GLShader *shader = ShaderManager::instance()->pushShader(ShaderTrait::UniformColor); shader->setUniform(GLShader::ModelViewProjectionMatrix, data.projectionMatrix()); glLineWidth(m_lineWidth); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); } void TouchPointsEffect::paintScreenFinishGl(int, QRegion, ScreenPaintData&) { glDisable(GL_BLEND); ShaderManager::instance()->popShader(); } } // namespace diff --git a/effects/trackmouse/trackmouse.cpp b/effects/trackmouse/trackmouse.cpp index 534b98f24..e84728d98 100644 --- a/effects/trackmouse/trackmouse.cpp +++ b/effects/trackmouse/trackmouse.cpp @@ -1,312 +1,312 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2006 Lubos Lunak Copyright (C) 2010 Jorge Mata 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 "trackmouse.h" // KConfigSkeleton #include "trackmouseconfig.h" #include #include #include #include #include #include #include #include #include #include namespace KWin { TrackMouseEffect::TrackMouseEffect() : m_angle(0) { initConfig(); - m_texture[0] = m_texture[1] = 0; + m_texture[0] = m_texture[1] = nullptr; #ifdef KWIN_HAVE_XRENDER_COMPOSITING - m_picture[0] = m_picture[1] = 0; + m_picture[0] = m_picture[1] = nullptr; if ( effects->compositingType() == XRenderCompositing) m_angleBase = 1.57079632679489661923; // Pi/2 #endif if ( effects->isOpenGLCompositing() || effects->compositingType() == QPainterCompositing) m_angleBase = 90.0; m_mousePolling = false; m_action = new QAction(this); m_action->setObjectName(QStringLiteral("TrackMouse")); m_action->setText(i18n("Track mouse")); KGlobalAccel::self()->setDefaultShortcut(m_action, QList()); KGlobalAccel::self()->setShortcut(m_action, QList()); effects->registerGlobalShortcut(QKeySequence(), m_action); connect(m_action, &QAction::triggered, this, &TrackMouseEffect::toggle); connect(effects, &EffectsHandler::mouseChanged, this, &TrackMouseEffect::slotMouseChanged); reconfigure(ReconfigureAll); } TrackMouseEffect::~TrackMouseEffect() { if (m_mousePolling) effects->stopMousePolling(); for (int i = 0; i < 2; ++i) { - delete m_texture[i]; m_texture[i] = 0; + delete m_texture[i]; m_texture[i] = nullptr; #ifdef KWIN_HAVE_XRENDER_COMPOSITING - delete m_picture[i]; m_picture[i] = 0; + delete m_picture[i]; m_picture[i] = nullptr; #endif } } void TrackMouseEffect::reconfigure(ReconfigureFlags) { - m_modifiers = 0; + m_modifiers = nullptr; TrackMouseConfig::self()->read(); if (TrackMouseConfig::shift()) m_modifiers |= Qt::ShiftModifier; if (TrackMouseConfig::alt()) m_modifiers |= Qt::AltModifier; if (TrackMouseConfig::control()) m_modifiers |= Qt::ControlModifier; if (TrackMouseConfig::meta()) m_modifiers |= Qt::MetaModifier; if (m_modifiers) { if (!m_mousePolling) effects->startMousePolling(); m_mousePolling = true; } else if (m_mousePolling) { effects->stopMousePolling(); m_mousePolling = false; } } void TrackMouseEffect::prePaintScreen(ScreenPrePaintData& data, int time) { QTime t = QTime::currentTime(); m_angle = ((t.second() % 4) * m_angleBase) + (t.msec() / 1000.0 * m_angleBase); m_lastRect[0].moveCenter(cursorPos()); m_lastRect[1].moveCenter(cursorPos()); data.paint |= m_lastRect[0].adjusted(-1,-1,1,1); effects->prePaintScreen(data, time); } void TrackMouseEffect::paintScreen(int mask, QRegion region, ScreenPaintData& data) { effects->paintScreen(mask, region, data); // paint normal screen if ( effects->isOpenGLCompositing() && m_texture[0] && m_texture[1]) { ShaderBinder binder(ShaderTrait::MapTexture); GLShader *shader(binder.shader()); if (!shader) { return; } glEnable(GL_BLEND); glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); QMatrix4x4 matrix(data.projectionMatrix()); const QPointF p = m_lastRect[0].topLeft() + QPoint(m_lastRect[0].width()/2.0, m_lastRect[0].height()/2.0); const float x = p.x()*data.xScale() + data.xTranslation(); const float y = p.y()*data.yScale() + data.yTranslation(); for (int i = 0; i < 2; ++i) { matrix.translate(x, y, 0.0); matrix.rotate(i ? -2*m_angle : m_angle, 0, 0, 1.0); matrix.translate(-x, -y, 0.0); QMatrix4x4 mvp(matrix); mvp.translate(m_lastRect[i].x(), m_lastRect[i].y()); shader->setUniform(GLShader::ModelViewProjectionMatrix, mvp); m_texture[i]->bind(); m_texture[i]->render(region, m_lastRect[i]); m_texture[i]->unbind(); } glDisable(GL_BLEND); } #ifdef KWIN_HAVE_XRENDER_COMPOSITING if ( effects->compositingType() == XRenderCompositing && m_picture[0] && m_picture[1]) { float sine = sin(m_angle); const float cosine = cos(m_angle); for (int i = 0; i < 2; ++i) { if (i) sine = -sine; const float dx = m_size[i].width()/2.0; const float dy = m_size[i].height()/2.0; const xcb_render_picture_t picture = *m_picture[i]; #define DOUBLE_TO_FIXED(d) ((xcb_render_fixed_t) ((d) * 65536)) xcb_render_transform_t xform = { DOUBLE_TO_FIXED( cosine ), DOUBLE_TO_FIXED( -sine ), DOUBLE_TO_FIXED( dx - cosine*dx + sine*dy ), DOUBLE_TO_FIXED( sine ), DOUBLE_TO_FIXED( cosine ), DOUBLE_TO_FIXED( dy - sine*dx - cosine*dy ), DOUBLE_TO_FIXED( 0.0 ), DOUBLE_TO_FIXED( 0.0 ), DOUBLE_TO_FIXED( 1.0 ) }; #undef DOUBLE_TO_FIXED xcb_render_set_picture_transform(xcbConnection(), picture, xform); - xcb_render_set_picture_filter(xcbConnection(), picture, 8, "bilinear", 0, NULL); + xcb_render_set_picture_filter(xcbConnection(), picture, 8, "bilinear", 0, nullptr); const QRect &rect = m_lastRect[i]; xcb_render_composite(xcbConnection(), XCB_RENDER_PICT_OP_OVER, picture, XCB_RENDER_PICTURE_NONE, effects->xrenderBufferPicture(), 0, 0, 0, 0, qRound((rect.x()+rect.width()/2.0)*data.xScale() - rect.width()/2.0 + data.xTranslation()), qRound((rect.y()+rect.height()/2.0)*data.yScale() - rect.height()/2.0 + data.yTranslation()), rect.width(), rect.height()); } } #endif if (effects->compositingType() == QPainterCompositing && !m_image[0].isNull() && !m_image[1].isNull()) { QPainter *painter = effects->scenePainter(); const QPointF p = m_lastRect[0].topLeft() + QPoint(m_lastRect[0].width()/2.0, m_lastRect[0].height()/2.0); for (int i = 0; i < 2; ++i) { painter->save(); painter->translate(p.x(), p.y()); painter->rotate(i ? -2*m_angle : m_angle); painter->translate(-p.x(), -p.y()); painter->drawImage(m_lastRect[i], m_image[i]); painter->restore(); } } } void TrackMouseEffect::postPaintScreen() { effects->addRepaint(m_lastRect[0].adjusted(-1,-1,1,1)); effects->postPaintScreen(); } bool TrackMouseEffect::init() { effects->makeOpenGLContextCurrent(); #ifdef KWIN_HAVE_XRENDER_COMPOSITING if (!(m_texture[0] || m_picture[0] || !m_image[0].isNull())) { loadTexture(); if (!(m_texture[0] || m_picture[0] || !m_image[0].isNull())) return false; } #else if (!m_texture[0] || m_image[0].isNull()) { loadTexture(); if (!m_texture[0] || m_image[0].isNull()) return false; } #endif m_lastRect[0].moveCenter(cursorPos()); m_lastRect[1].moveCenter(cursorPos()); m_angle = 0; return true; } void TrackMouseEffect::toggle() { switch (m_state) { case State::ActivatedByModifiers: m_state = State::ActivatedByShortcut; break; case State::ActivatedByShortcut: m_state = State::Inactive; break; case State::Inactive: if (!init()) { return; } m_state = State::ActivatedByShortcut; break; default: Q_UNREACHABLE(); break; } effects->addRepaint(m_lastRect[0].adjusted(-1, -1, 1, 1)); } void TrackMouseEffect::slotMouseChanged(const QPoint&, const QPoint&, Qt::MouseButtons, Qt::MouseButtons, Qt::KeyboardModifiers modifiers, Qt::KeyboardModifiers) { if (!m_mousePolling) { // we didn't ask for it but maybe someone else did... return; } switch (m_state) { case State::ActivatedByModifiers: if (modifiers == m_modifiers) { return; } m_state = State::Inactive; break; case State::ActivatedByShortcut: return; case State::Inactive: if (modifiers != m_modifiers) { return; } if (!init()) { return; } m_state = State::ActivatedByModifiers; break; default: Q_UNREACHABLE(); break; } effects->addRepaint(m_lastRect[0].adjusted(-1, -1, 1, 1)); } void TrackMouseEffect::loadTexture() { QString f[2] = {QStandardPaths::locate(QStandardPaths::DataLocation, QStringLiteral("tm_outer.png")), QStandardPaths::locate(QStandardPaths::DataLocation, QStringLiteral("tm_inner.png"))}; if (f[0].isEmpty() || f[1].isEmpty()) return; for (int i = 0; i < 2; ++i) { if ( effects->isOpenGLCompositing()) { QImage img(f[i]); m_texture[i] = new GLTexture(img); m_lastRect[i].setSize(img.size()); } #ifdef KWIN_HAVE_XRENDER_COMPOSITING if ( effects->compositingType() == XRenderCompositing) { QImage pixmap(f[i]); m_picture[i] = new XRenderPicture(pixmap); m_size[i] = pixmap.size(); m_lastRect[i].setSize(pixmap.size()); } #endif if (effects->compositingType() == QPainterCompositing) { m_image[i] = QImage(f[i]); m_lastRect[i].setSize(m_image[i].size()); } } } bool TrackMouseEffect::isActive() const { return m_state != State::Inactive; } } // namespace diff --git a/effects/trackmouse/trackmouse_config.h b/effects/trackmouse/trackmouse_config.h index 7a6a8d3f1..a3bdc4b25 100644 --- a/effects/trackmouse/trackmouse_config.h +++ b/effects/trackmouse/trackmouse_config.h @@ -1,61 +1,61 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2007 Rivo Laks Copyright (C) 2010 Jorge Mata 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_TRACKMOUSE_CONFIG_H #define KWIN_TRACKMOUSE_CONFIG_H #include #include "ui_trackmouse_config.h" class KActionCollection; namespace KWin { class TrackMouseEffectConfigForm : public QWidget, public Ui::TrackMouseEffectConfigForm { Q_OBJECT public: explicit TrackMouseEffectConfigForm(QWidget* parent); }; class TrackMouseEffectConfig : public KCModule { Q_OBJECT public: - explicit TrackMouseEffectConfig(QWidget* parent = 0, const QVariantList& args = QVariantList()); + explicit TrackMouseEffectConfig(QWidget* parent = nullptr, const QVariantList& args = QVariantList()); ~TrackMouseEffectConfig() override; public Q_SLOTS: void save() override; void load() override; void defaults() override; private Q_SLOTS: void shortcutChanged(const QKeySequence &seq); private: TrackMouseEffectConfigForm* m_ui; KActionCollection* m_actionCollection; }; } // namespace #endif diff --git a/effects/windowgeometry/windowgeometry.cpp b/effects/windowgeometry/windowgeometry.cpp index e976d664b..c77af6efb 100644 --- a/effects/windowgeometry/windowgeometry.cpp +++ b/effects/windowgeometry/windowgeometry.cpp @@ -1,237 +1,237 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2010 Thomas Lübking 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 "windowgeometry.h" // KConfigSkeleton #include "windowgeometryconfig.h" #include #include #include #include #include #include #include using namespace KWin; WindowGeometry::WindowGeometry() { initConfig(); iAmActivated = true; iAmActive = false; - myResizeWindow = 0L; + myResizeWindow = nullptr; #define myResizeString "Window geometry display, %1 and %2 are the new size," \ " %3 and %4 are pixel increments - avoid reformatting or suffixes like 'px'", \ "Width: %1 (%3)\nHeight: %2 (%4)" #define myCoordString_0 "Window geometry display, %1 and %2 are the cartesian x and y coordinates" \ " - avoid reformatting or suffixes like 'px'", \ "X: %1\nY: %2" #define myCoordString_1 "Window geometry display, %1 and %2 are the cartesian x and y coordinates," \ " %3 and %4 are the resp. increments - avoid reformatting or suffixes like 'px'", \ "X: %1 (%3)\nY: %2 (%4)" reconfigure(ReconfigureAll); QAction* a = new QAction(this); a->setObjectName(QStringLiteral("WindowGeometry")); a->setText(i18n("Toggle window geometry display (effect only)")); KGlobalAccel::self()->setDefaultShortcut(a, QList() << Qt::CTRL + Qt::SHIFT + Qt::Key_F11); KGlobalAccel::self()->setShortcut(a, QList() << Qt::CTRL + Qt::SHIFT + Qt::Key_F11); effects->registerGlobalShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_F11, a); connect(a, &QAction::triggered, this, &WindowGeometry::toggle); connect(effects, &EffectsHandler::windowStartUserMovedResized, this, &WindowGeometry::slotWindowStartUserMovedResized); connect(effects, &EffectsHandler::windowFinishUserMovedResized, this, &WindowGeometry::slotWindowFinishUserMovedResized); connect(effects, &EffectsHandler::windowStepUserMovedResized, this, &WindowGeometry::slotWindowStepUserMovedResized); } WindowGeometry::~WindowGeometry() { for (int i = 0; i < 3; ++i) delete myMeasure[i]; } void WindowGeometry::createFrames() { if (myMeasure[0]) { return; } QFont fnt; fnt.setBold(true); fnt.setPointSize(12); for (int i = 0; i < 3; ++i) { myMeasure[i] = effects->effectFrame(EffectFrameUnstyled, false); myMeasure[i]->setFont(fnt); } myMeasure[0]->setAlignment(Qt::AlignLeft | Qt::AlignTop); myMeasure[1]->setAlignment(Qt::AlignCenter); myMeasure[2]->setAlignment(Qt::AlignRight | Qt::AlignBottom); } void WindowGeometry::reconfigure(ReconfigureFlags) { WindowGeometryConfiguration::self()->read(); iHandleMoves = WindowGeometryConfiguration::move(); iHandleResizes = WindowGeometryConfiguration::resize(); } void WindowGeometry::paintScreen(int mask, QRegion region, ScreenPaintData &data) { effects->paintScreen(mask, region, data); if (iAmActivated && iAmActive) { for (int i = 0; i < 3; ++i) myMeasure[i]->render(infiniteRegion(), 1.0, .66); } } void WindowGeometry::toggle() { iAmActivated = !iAmActivated; createFrames(); } void WindowGeometry::slotWindowStartUserMovedResized(EffectWindow *w) { if (!iAmActivated) return; if (w->isUserResize() && !iHandleResizes) return; if (w->isUserMove() && !iHandleMoves) return; createFrames(); iAmActive = true; myResizeWindow = w; myOriginalGeometry = w->geometry(); myCurrentGeometry = w->geometry(); slotWindowStepUserMovedResized(w, w->geometry()); } void WindowGeometry::slotWindowFinishUserMovedResized(EffectWindow *w) { if (iAmActive && w == myResizeWindow) { iAmActive = false; - myResizeWindow = 0L; + myResizeWindow = nullptr; w->addRepaintFull(); if (myExtraDirtyArea.isValid()) w->addLayerRepaint(myExtraDirtyArea); myExtraDirtyArea = QRect(); } } static inline QString number(int n) { QLocale locale; QString sign; if (n >= 0) { sign = locale.positiveSign(); if (sign.isEmpty()) sign = QStringLiteral("+"); } else { n = -n; sign = locale.negativeSign(); if (sign.isEmpty()) sign = QStringLiteral("-"); } return sign + QString::number(n); } void WindowGeometry::slotWindowStepUserMovedResized(EffectWindow *w, const QRect &geometry) { if (iAmActivated && iAmActive && w == myResizeWindow) { if (myExtraDirtyArea.isValid()) effects->addRepaint(myExtraDirtyArea); myExtraDirtyArea = QRect(); myCurrentGeometry = geometry; QPoint center = geometry.center(); const QRect &r = geometry; const QRect &r2 = myOriginalGeometry; const QRect screen = effects->clientArea(ScreenArea, w); QRect expandedGeometry = w->expandedGeometry(); expandedGeometry = geometry.adjusted(expandedGeometry.x() - w->x(), expandedGeometry.y() - w->y(), expandedGeometry.right() - w->geometry().right(), expandedGeometry.bottom() - w->geometry().bottom()); // sufficient for moves, resizes calculated otherwise int dx = r.x() - r2.x(); int dy = r.y() - r2.y(); // upper left ---------------------- if (w->isUserResize()) myMeasure[0]->setText( i18nc(myCoordString_1, r.x(), r.y(), number(dx), number(dy) ) ); else myMeasure[0]->setText( i18nc(myCoordString_0, r.x(), r.y() ) ); QPoint pos = expandedGeometry.topLeft(); pos = QPoint(qMax(pos.x(), screen.x()), qMax(pos.y(), screen.y())); myMeasure[0]->setPosition(pos + QPoint(6,6)); // "6" is magic number because the unstyled effectframe has 5px padding // center ---------------------- if (w->isUserResize()) { // calc width for center element, otherwise the current dx/dx remains right dx = r.width() - r2.width(); dy = r.height() - r2.height(); const QSize baseInc = w->basicUnit(); if (baseInc != QSize(1,1)) { Q_ASSERT(baseInc.width() && baseInc.height()); const QSize csz = w->contentsRect().size(); myMeasure[1]->setText( i18nc(myResizeString, csz.width()/baseInc.width(), csz.height()/baseInc.height(), number(dx/baseInc.width()), number(dy/baseInc.height()) ) ); } else myMeasure[1]->setText( i18nc(myResizeString, r.width(), r.height(), number(dx), number(dy) ) ); // calc width for bottomright element, superfluous otherwise dx = r.right() - r2.right(); dy = r.bottom() - r2.bottom(); } else myMeasure[1]->setText( i18nc(myCoordString_0, number(dx), number(dy) ) ); const int cdx = myMeasure[1]->geometry().width() / 2 + 3; // "3" = 6/2 is magic number because const int cdy = myMeasure[1]->geometry().height() / 2 + 3; // the unstyled effectframe has 5px padding center = QPoint(qMax(center.x(), screen.x() + cdx), qMax(center.y(), screen.y() + cdy)); center = QPoint(qMin(center.x(), screen.right() - cdx), qMin(center.y(), screen.bottom() - cdy)); myMeasure[1]->setPosition(center); // lower right ---------------------- if (w->isUserResize()) myMeasure[2]->setText( i18nc(myCoordString_1, r.right(), r.bottom(), number(dx), number(dy) ) ); else myMeasure[2]->setText( i18nc(myCoordString_0, r.right(), r.bottom() ) ); pos = expandedGeometry.bottomRight(); pos = QPoint(qMin(pos.x(), screen.right()), qMin(pos.y(), screen.bottom())); myMeasure[2]->setPosition(pos - QPoint(6,6)); // "6" is magic number because the unstyled effectframe has 5px padding myExtraDirtyArea |= myMeasure[0]->geometry(); myExtraDirtyArea |= myMeasure[1]->geometry(); myExtraDirtyArea |= myMeasure[2]->geometry(); myExtraDirtyArea.adjust(-6,-6,6,6); if (myExtraDirtyArea.isValid()) effects->addRepaint(myExtraDirtyArea); } } bool WindowGeometry::isActive() const { return iAmActive; } diff --git a/effects/windowgeometry/windowgeometry_config.h b/effects/windowgeometry/windowgeometry_config.h index b45d30275..247f47c56 100644 --- a/effects/windowgeometry/windowgeometry_config.h +++ b/effects/windowgeometry/windowgeometry_config.h @@ -1,57 +1,57 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2010 Thomas Lübking 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 WINDOWGEOMETRY_CONFIG_H #define WINDOWGEOMETRY_CONFIG_H #include #include "ui_windowgeometry_config.h" namespace KWin { class WindowGeometryConfigForm : public QWidget, public Ui::WindowGeometryConfigForm { Q_OBJECT public: explicit WindowGeometryConfigForm(QWidget* parent); }; class WindowGeometryConfig : public KCModule { Q_OBJECT public: - explicit WindowGeometryConfig(QWidget* parent = 0, const QVariantList& args = QVariantList()); + explicit WindowGeometryConfig(QWidget* parent = nullptr, const QVariantList& args = QVariantList()); ~WindowGeometryConfig() override; public Q_SLOTS: void save() override; void defaults() override; private: WindowGeometryConfigForm* myUi; KActionCollection* myActionCollection; }; } // namespace #endif diff --git a/effects/wobblywindows/wobblywindows_config.h b/effects/wobblywindows/wobblywindows_config.h index 3dadb60fb..f423c9a19 100644 --- a/effects/wobblywindows/wobblywindows_config.h +++ b/effects/wobblywindows/wobblywindows_config.h @@ -1,52 +1,52 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2008 Cédric Borgese Copyright (C) 2008 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 . *********************************************************************/ #ifndef KWIN_WOBBLYWINDOWS_CONFIG_H #define KWIN_WOBBLYWINDOWS_CONFIG_H #include #include "ui_wobblywindows_config.h" namespace KWin { class WobblyWindowsEffectConfig : public KCModule { Q_OBJECT public: - explicit WobblyWindowsEffectConfig(QWidget* parent = 0, const QVariantList& args = QVariantList()); + explicit WobblyWindowsEffectConfig(QWidget* parent = nullptr, const QVariantList& args = QVariantList()); ~WobblyWindowsEffectConfig() override; public Q_SLOTS: void save() override; private Q_SLOTS: void wobblinessChanged(); private: ::Ui::WobblyWindowsEffectConfigForm m_ui; }; } // namespace #endif diff --git a/effects/zoom/zoom.cpp b/effects/zoom/zoom.cpp index d239c9e4f..413d0261c 100644 --- a/effects/zoom/zoom.cpp +++ b/effects/zoom/zoom.cpp @@ -1,534 +1,534 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2006 Lubos Lunak Copyright (C) 2010 Sebastian Sauer 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 "zoom.h" // KConfigSkeleton #include "zoomconfig.h" #include #include #include #include #include #include #include #include #include #include #ifdef KWIN_HAVE_XRENDER_COMPOSITING #include #include #endif namespace KWin { ZoomEffect::ZoomEffect() : Effect() , zoom(1) , target_zoom(1) , polling(false) , zoomFactor(1.25) , mouseTracking(MouseTrackingProportional) , enableFocusTracking(false) , followFocus(true) , mousePointer(MousePointerScale) , focusDelay(350) // in milliseconds , imageWidth(0) , imageHeight(0) , isMouseHidden(false) , xMove(0) , yMove(0) , moveFactor(20.0) { initConfig(); - QAction* a = 0; + QAction* a = nullptr; a = KStandardAction::zoomIn(this, SLOT(zoomIn()), this); KGlobalAccel::self()->setDefaultShortcut(a, QList() << Qt::META + Qt::Key_Equal); KGlobalAccel::self()->setShortcut(a, QList() << Qt::META + Qt::Key_Equal); effects->registerGlobalShortcut(Qt::META + Qt::Key_Equal, a); effects->registerAxisShortcut(Qt::ControlModifier | Qt::MetaModifier, PointerAxisDown, a); a = KStandardAction::zoomOut(this, SLOT(zoomOut()), this); KGlobalAccel::self()->setDefaultShortcut(a, QList() << Qt::META + Qt::Key_Minus); KGlobalAccel::self()->setShortcut(a, QList() << Qt::META + Qt::Key_Minus); effects->registerGlobalShortcut(Qt::META + Qt::Key_Minus, a); effects->registerAxisShortcut(Qt::ControlModifier | Qt::MetaModifier, PointerAxisUp, a); a = KStandardAction::actualSize(this, SLOT(actualSize()), this); KGlobalAccel::self()->setDefaultShortcut(a, QList() << Qt::META + Qt::Key_0); KGlobalAccel::self()->setShortcut(a, QList() << Qt::META + Qt::Key_0); effects->registerGlobalShortcut(Qt::META + Qt::Key_0, a); a = new QAction(this); a->setObjectName(QStringLiteral("MoveZoomLeft")); a->setText(i18n("Move Zoomed Area to Left")); KGlobalAccel::self()->setDefaultShortcut(a, QList()); KGlobalAccel::self()->setShortcut(a, QList()); effects->registerGlobalShortcut(QKeySequence(), a); connect(a, &QAction::triggered, this, &ZoomEffect::moveZoomLeft); a = new QAction(this); a->setObjectName(QStringLiteral("MoveZoomRight")); a->setText(i18n("Move Zoomed Area to Right")); KGlobalAccel::self()->setDefaultShortcut(a, QList()); KGlobalAccel::self()->setShortcut(a, QList()); effects->registerGlobalShortcut(QKeySequence(), a); connect(a, &QAction::triggered, this, &ZoomEffect::moveZoomRight); a = new QAction(this); a->setObjectName(QStringLiteral("MoveZoomUp")); a->setText(i18n("Move Zoomed Area Upwards")); KGlobalAccel::self()->setDefaultShortcut(a, QList()); KGlobalAccel::self()->setShortcut(a, QList()); effects->registerGlobalShortcut(QKeySequence(), a); connect(a, &QAction::triggered, this, &ZoomEffect::moveZoomUp); a = new QAction(this); a->setObjectName(QStringLiteral("MoveZoomDown")); a->setText(i18n("Move Zoomed Area Downwards")); KGlobalAccel::self()->setDefaultShortcut(a, QList()); KGlobalAccel::self()->setShortcut(a, QList()); effects->registerGlobalShortcut(QKeySequence(), a); connect(a, &QAction::triggered, this, &ZoomEffect::moveZoomDown); // TODO: these two actions don't belong into the effect. They need to be moved into KWin core a = new QAction(this); a->setObjectName(QStringLiteral("MoveMouseToFocus")); a->setText(i18n("Move Mouse to Focus")); KGlobalAccel::self()->setDefaultShortcut(a, QList() << Qt::META + Qt::Key_F5); KGlobalAccel::self()->setShortcut(a, QList() << Qt::META + Qt::Key_F5); effects->registerGlobalShortcut(Qt::META + Qt::Key_F5, a); connect(a, &QAction::triggered, this, &ZoomEffect::moveMouseToFocus); a = new QAction(this); a->setObjectName(QStringLiteral("MoveMouseToCenter")); a->setText(i18n("Move Mouse to Center")); KGlobalAccel::self()->setDefaultShortcut(a, QList() << Qt::META + Qt::Key_F6); KGlobalAccel::self()->setShortcut(a, QList() << Qt::META + Qt::Key_F6); effects->registerGlobalShortcut(Qt::META + Qt::Key_F6, a); connect(a, &QAction::triggered, this, &ZoomEffect::moveMouseToCenter); timeline.setDuration(350); timeline.setFrameRange(0, 100); connect(&timeline, &QTimeLine::frameChanged, this, &ZoomEffect::timelineFrameChanged); connect(effects, &EffectsHandler::mouseChanged, this, &ZoomEffect::slotMouseChanged); source_zoom = -1; // used to trigger initialZoom reading reconfigure(ReconfigureAll); } ZoomEffect::~ZoomEffect() { // switch off and free resources showCursor(); // Save the zoom value. ZoomConfig::setInitialZoom(target_zoom); ZoomConfig::self()->save(); } void ZoomEffect::showCursor() { if (isMouseHidden) { disconnect(effects, &EffectsHandler::cursorShapeChanged, this, &ZoomEffect::recreateTexture); // show the previously hidden mouse-pointer again and free the loaded texture/picture. effects->showCursor(); texture.reset(); #ifdef KWIN_HAVE_XRENDER_COMPOSITING xrenderPicture.reset(); #endif isMouseHidden = false; } } void ZoomEffect::hideCursor() { if (mouseTracking == MouseTrackingProportional && mousePointer == MousePointerKeep) return; // don't replace the actual cursor by a static image for no reason. if (!isMouseHidden) { // try to load the cursor-theme into a OpenGL texture and if successful then hide the mouse-pointer recreateTexture(); bool shouldHide = false; if (effects->isOpenGLCompositing()) { shouldHide = !texture.isNull(); } else if (effects->compositingType() == XRenderCompositing) { #ifdef KWIN_HAVE_XRENDER_COMPOSITING shouldHide = !xrenderPicture.isNull(); #endif } if (shouldHide) { effects->hideCursor(); connect(effects, &EffectsHandler::cursorShapeChanged, this, &ZoomEffect::recreateTexture); isMouseHidden = true; } } } void ZoomEffect::recreateTexture() { effects->makeOpenGLContextCurrent(); const auto cursor = effects->cursorImage(); if (!cursor.image().isNull()) { imageWidth = cursor.image().width(); imageHeight = cursor.image().height(); cursorHotSpot = cursor.hotSpot(); if (effects->isOpenGLCompositing()) { texture.reset(new GLTexture(cursor.image())); texture->setWrapMode(GL_CLAMP_TO_EDGE); } #ifdef KWIN_HAVE_XRENDER_COMPOSITING if (effects->compositingType() == XRenderCompositing) xrenderPicture.reset(new XRenderPicture(cursor.image())); #endif } else { qCDebug(KWINEFFECTS) << "Falling back to proportional mouse tracking!"; mouseTracking = MouseTrackingProportional; } } void ZoomEffect::reconfigure(ReconfigureFlags) { ZoomConfig::self()->read(); // On zoom-in and zoom-out change the zoom by the defined zoom-factor. zoomFactor = qMax(0.1, ZoomConfig::zoomFactor()); // Visibility of the mouse-pointer. mousePointer = MousePointerType(ZoomConfig::mousePointer()); // Track moving of the mouse. mouseTracking = MouseTrackingType(ZoomConfig::mouseTracking()); // Enable tracking of the focused location. bool _enableFocusTracking = ZoomConfig::enableFocusTracking(); if (enableFocusTracking != _enableFocusTracking) { enableFocusTracking = _enableFocusTracking; if (QDBusConnection::sessionBus().isConnected()) { if (enableFocusTracking) QDBusConnection::sessionBus().connect(QStringLiteral("org.kde.kaccessibleapp"), QStringLiteral("/Adaptor"), QStringLiteral("org.kde.kaccessibleapp.Adaptor"), QStringLiteral("focusChanged"), this, SLOT(focusChanged(int,int,int,int,int,int))); else QDBusConnection::sessionBus().disconnect(QStringLiteral("org.kde.kaccessibleapp"), QStringLiteral("/Adaptor"), QStringLiteral("org.kde.kaccessibleapp.Adaptor"), QStringLiteral("focusChanged"), this, SLOT(focusChanged(int,int,int,int,int,int))); } } // When the focus changes, move the zoom area to the focused location. followFocus = ZoomConfig::enableFollowFocus(); // The time in milliseconds to wait before a focus-event takes away a mouse-move. focusDelay = qMax(uint(0), ZoomConfig::focusDelay()); // The factor the zoom-area will be moved on touching an edge on push-mode or using the navigation KAction's. moveFactor = qMax(0.1, ZoomConfig::moveFactor()); if (source_zoom < 0) { // Load the saved zoom value. source_zoom = 1.0; target_zoom = ZoomConfig::initialZoom(); if (target_zoom > 1.0) zoomIn(target_zoom); } else { source_zoom = 1.0; } } void ZoomEffect::prePaintScreen(ScreenPrePaintData& data, int time) { if (zoom != target_zoom) { const float zoomDist = qAbs(target_zoom - source_zoom); if (target_zoom > zoom) zoom = qMin(zoom + ((zoomDist * time) / animationTime(150*zoomFactor)), target_zoom); else zoom = qMax(zoom - ((zoomDist * time) / animationTime(150*zoomFactor)), target_zoom); } if (zoom == 1.0) { showCursor(); } else { hideCursor(); data.mask |= PAINT_SCREEN_TRANSFORMED; } effects->prePaintScreen(data, time); } void ZoomEffect::paintScreen(int mask, QRegion region, ScreenPaintData& data) { if (zoom != 1.0) { data *= QVector2D(zoom, zoom); const QSize screenSize = effects->virtualScreenSize(); // mouse-tracking allows navigation of the zoom-area using the mouse. switch(mouseTracking) { case MouseTrackingProportional: data.setXTranslation(- int(cursorPoint.x() * (zoom - 1.0))); data.setYTranslation(- int(cursorPoint.y() * (zoom - 1.0))); prevPoint = cursorPoint; break; case MouseTrackingCentred: prevPoint = cursorPoint; // fall through case MouseTrackingDisabled: data.setXTranslation(qMin(0, qMax(int(screenSize.width() - screenSize.width() * zoom), int(screenSize.width() / 2 - prevPoint.x() * zoom)))); data.setYTranslation(qMin(0, qMax(int(screenSize.height() - screenSize.height() * zoom), int(screenSize.height() / 2 - prevPoint.y() * zoom)))); break; case MouseTrackingPush: { // touching an edge of the screen moves the zoom-area in that direction. int x = cursorPoint.x() * zoom - prevPoint.x() * (zoom - 1.0); int y = cursorPoint.y() * zoom - prevPoint.y() * (zoom - 1.0); int threshold = 4; xMove = yMove = 0; if (x < threshold) xMove = (x - threshold) / zoom; else if (x + threshold > screenSize.width()) xMove = (x + threshold - screenSize.width()) / zoom; if (y < threshold) yMove = (y - threshold) / zoom; else if (y + threshold > screenSize.height()) yMove = (y + threshold - screenSize.height()) / zoom; if (xMove) prevPoint.setX(qMax(0, qMin(screenSize.width(), prevPoint.x() + xMove))); if (yMove) prevPoint.setY(qMax(0, qMin(screenSize.height(), prevPoint.y() + yMove))); data.setXTranslation(- int(prevPoint.x() * (zoom - 1.0))); data.setYTranslation(- int(prevPoint.y() * (zoom - 1.0))); break; } } // use the focusPoint if focus tracking is enabled if (enableFocusTracking && followFocus) { bool acceptFocus = true; if (mouseTracking != MouseTrackingDisabled && focusDelay > 0) { // Wait some time for the mouse before doing the switch. This serves as threshold // to prevent the focus from jumping around to much while working with the mouse. const int msecs = lastMouseEvent.msecsTo(lastFocusEvent); acceptFocus = msecs > focusDelay; } if (acceptFocus) { data.setXTranslation(- int(focusPoint.x() * (zoom - 1.0))); data.setYTranslation(- int(focusPoint.y() * (zoom - 1.0))); prevPoint = focusPoint; } } } effects->paintScreen(mask, region, data); if (zoom != 1.0 && mousePointer != MousePointerHide) { // Draw the mouse-texture at the position matching to zoomed-in image of the desktop. Hiding the // previous mouse-cursor and drawing our own fake mouse-cursor is needed to be able to scale the // mouse-cursor up and to re-position those mouse-cursor to match to the chosen zoom-level. int w = imageWidth; int h = imageHeight; if (mousePointer == MousePointerScale) { w *= zoom; h *= zoom; } const QPoint p = effects->cursorPos() - cursorHotSpot; QRect rect(p.x() * zoom + data.xTranslation(), p.y() * zoom + data.yTranslation(), w, h); if (texture) { texture->bind(); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); auto s = ShaderManager::instance()->pushShader(ShaderTrait::MapTexture); QMatrix4x4 mvp = data.projectionMatrix(); mvp.translate(rect.x(), rect.y()); s->setUniform(GLShader::ModelViewProjectionMatrix, mvp); texture->render(region, rect); ShaderManager::instance()->popShader(); texture->unbind(); glDisable(GL_BLEND); } #ifdef KWIN_HAVE_XRENDER_COMPOSITING if (xrenderPicture) { #define DOUBLE_TO_FIXED(d) ((xcb_render_fixed_t) ((d) * 65536)) static const xcb_render_transform_t xrenderIdentity = { DOUBLE_TO_FIXED(1), DOUBLE_TO_FIXED(0), DOUBLE_TO_FIXED(0), DOUBLE_TO_FIXED(0), DOUBLE_TO_FIXED(1), DOUBLE_TO_FIXED(0), DOUBLE_TO_FIXED(0), DOUBLE_TO_FIXED(0), DOUBLE_TO_FIXED(1) }; if (mousePointer == MousePointerScale) { - xcb_render_set_picture_filter(xcbConnection(), *xrenderPicture, 4, const_cast("good"), 0, NULL); + xcb_render_set_picture_filter(xcbConnection(), *xrenderPicture, 4, const_cast("good"), 0, nullptr); const xcb_render_transform_t xform = { DOUBLE_TO_FIXED(1.0 / zoom), DOUBLE_TO_FIXED(0), DOUBLE_TO_FIXED(0), DOUBLE_TO_FIXED(0), DOUBLE_TO_FIXED(1.0 / zoom), DOUBLE_TO_FIXED(0), DOUBLE_TO_FIXED(0), DOUBLE_TO_FIXED(0), DOUBLE_TO_FIXED(1) }; xcb_render_set_picture_transform(xcbConnection(), *xrenderPicture, xform); } xcb_render_composite(xcbConnection(), XCB_RENDER_PICT_OP_OVER, *xrenderPicture, XCB_RENDER_PICTURE_NONE, effects->xrenderBufferPicture(), 0, 0, 0, 0, rect.x(), rect.y(), rect.width(), rect.height()); if (mousePointer == MousePointerScale) xcb_render_set_picture_transform(xcbConnection(), *xrenderPicture, xrenderIdentity); #undef DOUBLE_TO_FIXED } #endif } } void ZoomEffect::postPaintScreen() { if (zoom != target_zoom) effects->addRepaintFull(); effects->postPaintScreen(); } void ZoomEffect::zoomIn(double to) { source_zoom = zoom; if (to < 0.0) target_zoom *= zoomFactor; else target_zoom = to; if (!polling) { polling = true; effects->startMousePolling(); } cursorPoint = effects->cursorPos(); if (mouseTracking == MouseTrackingDisabled) prevPoint = cursorPoint; effects->addRepaintFull(); } void ZoomEffect::zoomOut() { source_zoom = zoom; target_zoom /= zoomFactor; if ((zoomFactor > 1 && target_zoom < 1.01) || (zoomFactor < 1 && target_zoom > 0.99)) { target_zoom = 1; if (polling) { polling = false; effects->stopMousePolling(); } } if (mouseTracking == MouseTrackingDisabled) prevPoint = effects->cursorPos(); effects->addRepaintFull(); } void ZoomEffect::actualSize() { source_zoom = zoom; target_zoom = 1; if (polling) { polling = false; effects->stopMousePolling(); } effects->addRepaintFull(); } void ZoomEffect::timelineFrameChanged(int /* frame */) { const QSize screenSize = effects->virtualScreenSize(); prevPoint.setX(qMax(0, qMin(screenSize.width(), prevPoint.x() + xMove))); prevPoint.setY(qMax(0, qMin(screenSize.height(), prevPoint.y() + yMove))); cursorPoint = prevPoint; effects->addRepaintFull(); } void ZoomEffect::moveZoom(int x, int y) { if (timeline.state() == QTimeLine::Running) timeline.stop(); const QSize screenSize = effects->virtualScreenSize(); if (x < 0) xMove = - qMax(1.0, screenSize.width() / zoom / moveFactor); else if (x > 0) xMove = qMax(1.0, screenSize.width() / zoom / moveFactor); else xMove = 0; if (y < 0) yMove = - qMax(1.0, screenSize.height() / zoom / moveFactor); else if (y > 0) yMove = qMax(1.0, screenSize.height() / zoom / moveFactor); else yMove = 0; timeline.start(); } void ZoomEffect::moveZoomLeft() { moveZoom(-1, 0); } void ZoomEffect::moveZoomRight() { moveZoom(1, 0); } void ZoomEffect::moveZoomUp() { moveZoom(0, -1); } void ZoomEffect::moveZoomDown() { moveZoom(0, 1); } void ZoomEffect::moveMouseToFocus() { QCursor::setPos(focusPoint.x(), focusPoint.y()); } void ZoomEffect::moveMouseToCenter() { const QRect r = effects->virtualScreenGeometry(); QCursor::setPos(r.x() + r.width() / 2, r.y() + r.height() / 2); } void ZoomEffect::slotMouseChanged(const QPoint& pos, const QPoint& old, Qt::MouseButtons, Qt::MouseButtons, Qt::KeyboardModifiers, Qt::KeyboardModifiers) { if (zoom == 1.0) return; cursorPoint = pos; if (pos != old) { lastMouseEvent = QTime::currentTime(); effects->addRepaintFull(); } } void ZoomEffect::focusChanged(int px, int py, int rx, int ry, int rwidth, int rheight) { if (zoom == 1.0) return; const QSize screenSize = effects->virtualScreenSize(); focusPoint = (px >= 0 && py >= 0) ? QPoint(px, py) : QPoint(rx + qMax(0, (qMin(screenSize.width(), rwidth) / 2) - 60), ry + qMax(0, (qMin(screenSize.height(), rheight) / 2) - 60)); if (enableFocusTracking) { lastFocusEvent = QTime::currentTime(); effects->addRepaintFull(); } } bool ZoomEffect::isActive() const { return zoom != 1.0 || zoom != target_zoom; } } // namespace diff --git a/effects/zoom/zoom_config.h b/effects/zoom/zoom_config.h index c40c8be97..72fe20ddb 100644 --- a/effects/zoom/zoom_config.h +++ b/effects/zoom/zoom_config.h @@ -1,57 +1,57 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2007 Rivo Laks Copyright (C) 2010 Sebastian Sauer 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_ZOOM_CONFIG_H #define KWIN_ZOOM_CONFIG_H #include #include "ui_zoom_config.h" namespace KWin { class ZoomEffectConfigForm : public QWidget, public Ui::ZoomEffectConfigForm { Q_OBJECT public: - explicit ZoomEffectConfigForm(QWidget* parent = 0); + explicit ZoomEffectConfigForm(QWidget* parent = nullptr); }; class ZoomEffectConfig : public KCModule { Q_OBJECT public: - explicit ZoomEffectConfig(QWidget* parent = 0, const QVariantList& args = QVariantList()); + explicit ZoomEffectConfig(QWidget* parent = nullptr, const QVariantList& args = QVariantList()); ~ZoomEffectConfig() override; public Q_SLOTS: void save() override; private: ZoomEffectConfigForm* m_ui; enum MouseTracking { MouseCentred = 0, MouseProportional = 1, MouseDisabled = 2 }; }; } // namespace #endif diff --git a/events.cpp b/events.cpp index b0fe982c8..54d812c51 100644 --- a/events.cpp +++ b/events.cpp @@ -1,1345 +1,1345 @@ /******************************************************************** 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 . *********************************************************************/ /* This file contains things relevant to handling incoming events. */ #include "client.h" #include "cursor.h" #include "focuschain.h" #include "netinfo.h" #include "workspace.h" #include "atoms.h" #ifdef KWIN_BUILD_TABBOX #include "tabbox.h" #endif #include "group.h" #include "rules.h" #include "unmanaged.h" #include "useractions.h" #include "effects.h" #include "screens.h" #include "xcbutils.h" #include #include #include #include #include #include #include #include #include #include #ifdef XCB_ICCCM_FOUND #include #endif #include "composite.h" #include "x11eventfilter.h" #include "wayland_server.h" #include #ifndef XCB_GE_GENERIC #define XCB_GE_GENERIC 35 typedef struct xcb_ge_generic_event_t { uint8_t response_type; /**< */ uint8_t extension; /**< */ uint16_t sequence; /**< */ uint32_t length; /**< */ uint16_t event_type; /**< */ uint8_t pad0[22]; /**< */ uint32_t full_sequence; /**< */ } xcb_ge_generic_event_t; #endif namespace KWin { // **************************************** // Workspace // **************************************** static xcb_window_t findEventWindow(xcb_generic_event_t *event) { const uint8_t eventType = event->response_type & ~0x80; switch(eventType) { case XCB_KEY_PRESS: case XCB_KEY_RELEASE: return reinterpret_cast(event)->event; case XCB_BUTTON_PRESS: case XCB_BUTTON_RELEASE: return reinterpret_cast(event)->event; case XCB_MOTION_NOTIFY: return reinterpret_cast(event)->event; case XCB_ENTER_NOTIFY: case XCB_LEAVE_NOTIFY: return reinterpret_cast(event)->event; case XCB_FOCUS_IN: case XCB_FOCUS_OUT: return reinterpret_cast(event)->event; case XCB_EXPOSE: return reinterpret_cast(event)->window; case XCB_GRAPHICS_EXPOSURE: return reinterpret_cast(event)->drawable; case XCB_NO_EXPOSURE: return reinterpret_cast(event)->drawable; case XCB_VISIBILITY_NOTIFY: return reinterpret_cast(event)->window; case XCB_CREATE_NOTIFY: return reinterpret_cast(event)->window; case XCB_DESTROY_NOTIFY: return reinterpret_cast(event)->window; case XCB_UNMAP_NOTIFY: return reinterpret_cast(event)->window; case XCB_MAP_NOTIFY: return reinterpret_cast(event)->window; case XCB_MAP_REQUEST: return reinterpret_cast(event)->window; case XCB_REPARENT_NOTIFY: return reinterpret_cast(event)->window; case XCB_CONFIGURE_NOTIFY: return reinterpret_cast(event)->window; case XCB_CONFIGURE_REQUEST: return reinterpret_cast(event)->window; case XCB_GRAVITY_NOTIFY: return reinterpret_cast(event)->window; case XCB_RESIZE_REQUEST: return reinterpret_cast(event)->window; case XCB_CIRCULATE_NOTIFY: case XCB_CIRCULATE_REQUEST: return reinterpret_cast(event)->window; case XCB_PROPERTY_NOTIFY: return reinterpret_cast(event)->window; case XCB_COLORMAP_NOTIFY: return reinterpret_cast(event)->window; case XCB_CLIENT_MESSAGE: return reinterpret_cast(event)->window; default: // extension handling if (eventType == Xcb::Extensions::self()->shapeNotifyEvent()) { return reinterpret_cast(event)->affected_window; } if (eventType == Xcb::Extensions::self()->damageNotifyEvent()) { return reinterpret_cast(event)->drawable; } return XCB_WINDOW_NONE; } } QVector s_xcbEerrors({ QByteArrayLiteral("Success"), QByteArrayLiteral("BadRequest"), QByteArrayLiteral("BadValue"), QByteArrayLiteral("BadWindow"), QByteArrayLiteral("BadPixmap"), QByteArrayLiteral("BadAtom"), QByteArrayLiteral("BadCursor"), QByteArrayLiteral("BadFont"), QByteArrayLiteral("BadMatch"), QByteArrayLiteral("BadDrawable"), QByteArrayLiteral("BadAccess"), QByteArrayLiteral("BadAlloc"), QByteArrayLiteral("BadColor"), QByteArrayLiteral("BadGC"), QByteArrayLiteral("BadIDChoice"), QByteArrayLiteral("BadName"), QByteArrayLiteral("BadLength"), QByteArrayLiteral("BadImplementation"), QByteArrayLiteral("Unknown")}); void Workspace::registerEventFilter(X11EventFilter *filter) { if (filter->isGenericEvent()) m_genericEventFilters.append(filter); else m_eventFilters.append(filter); } void Workspace::unregisterEventFilter(X11EventFilter *filter) { if (filter->isGenericEvent()) m_genericEventFilters.removeOne(filter); else m_eventFilters.removeOne(filter); } /** * Handles workspace specific XCB event */ bool Workspace::workspaceEvent(xcb_generic_event_t *e) { const uint8_t eventType = e->response_type & ~0x80; if (!eventType) { // let's check whether it's an error from one of the extensions KWin uses xcb_generic_error_t *error = reinterpret_cast(e); const QVector extensions = Xcb::Extensions::self()->extensions(); for (const auto &extension : extensions) { if (error->major_code == extension.majorOpcode) { QByteArray errorName; if (error->error_code < s_xcbEerrors.size()) { errorName = s_xcbEerrors.at(error->error_code); } else if (error->error_code >= extension.errorBase) { const int index = error->error_code - extension.errorBase; if (index >= 0 && index < extension.errorCodes.size()) { errorName = extension.errorCodes.at(index); } } if (errorName.isEmpty()) { errorName = QByteArrayLiteral("Unknown"); } qCWarning(KWIN_CORE, "XCB error: %d (%s), sequence: %d, resource id: %d, major code: %d (%s), minor code: %d (%s)", int(error->error_code), errorName.constData(), int(error->sequence), int(error->resource_id), int(error->major_code), extension.name.constData(), int(error->minor_code), extension.opCodes.size() > error->minor_code ? extension.opCodes.at(error->minor_code).constData() : "Unknown"); return true; } } return false; } if (eventType == XCB_GE_GENERIC) { xcb_ge_generic_event_t *ge = reinterpret_cast(e); foreach (X11EventFilter *filter, m_genericEventFilters) { if (filter->extension() == ge->extension && filter->genericEventTypes().contains(ge->event_type) && filter->event(e)) { return true; } } } else { foreach (X11EventFilter *filter, m_eventFilters) { if (filter->eventTypes().contains(eventType) && filter->event(e)) { return true; } } } if (effects && static_cast< EffectsHandlerImpl* >(effects)->hasKeyboardGrab() && (eventType == XCB_KEY_PRESS || eventType == XCB_KEY_RELEASE)) return false; // let Qt process it, it'll be intercepted again in eventFilter() // events that should be handled before Clients can get them switch (eventType) { case XCB_CONFIGURE_NOTIFY: if (reinterpret_cast(e)->event == rootWindow()) markXStackingOrderAsDirty(); break; }; const xcb_window_t eventWindow = findEventWindow(e); if (eventWindow != XCB_WINDOW_NONE) { if (Client* c = findClient(Predicate::WindowMatch, eventWindow)) { if (c->windowEvent(e)) return true; } else if (Client* c = findClient(Predicate::WrapperIdMatch, eventWindow)) { if (c->windowEvent(e)) return true; } else if (Client* c = findClient(Predicate::FrameIdMatch, eventWindow)) { if (c->windowEvent(e)) return true; } else if (Client *c = findClient(Predicate::InputIdMatch, eventWindow)) { if (c->windowEvent(e)) return true; } else if (Unmanaged* c = findUnmanaged(eventWindow)) { if (c->windowEvent(e)) return true; } } switch (eventType) { case XCB_CREATE_NOTIFY: { const auto *event = reinterpret_cast(e); if (event->parent == rootWindow() && !QWidget::find(event->window) && !event->override_redirect) { // see comments for allowClientActivation() updateXTime(); const xcb_timestamp_t t = xTime(); xcb_change_property(connection(), XCB_PROP_MODE_REPLACE, event->window, atoms->kde_net_wm_user_creation_time, XCB_ATOM_CARDINAL, 32, 1, &t); } break; } case XCB_UNMAP_NOTIFY: { const auto *event = reinterpret_cast(e); return (event->event != event->window); // hide wm typical event from Qt } case XCB_REPARENT_NOTIFY: { //do not confuse Qt with these events. After all, _we_ are the //window manager who does the reparenting. return true; } case XCB_MAP_REQUEST: { updateXTime(); const auto *event = reinterpret_cast(e); if (Client* c = findClient(Predicate::WindowMatch, event->window)) { // e->xmaprequest.window is different from e->xany.window // TODO this shouldn't be necessary now c->windowEvent(e); FocusChain::self()->update(c, FocusChain::Update); } else if ( true /*|| e->xmaprequest.parent != root */ ) { // NOTICE don't check for the parent being the root window, this breaks when some app unmaps // a window, changes something and immediately maps it back, without giving KWin // a chance to reparent it back to root // since KWin can get MapRequest only for root window children and // children of WindowWrapper (=clients), the check is AFAIK useless anyway // NOTICE: The save-set support in Client::mapRequestEvent() actually requires that // this code doesn't check the parent to be root. if (!createClient(event->window, false)) { xcb_map_window(connection(), event->window); const uint32_t values[] = { XCB_STACK_MODE_ABOVE }; xcb_configure_window(connection(), event->window, XCB_CONFIG_WINDOW_STACK_MODE, values); } } return true; } case XCB_MAP_NOTIFY: { const auto *event = reinterpret_cast(e); if (event->override_redirect) { Unmanaged* c = findUnmanaged(event->window); - if (c == NULL) + if (c == nullptr) c = createUnmanaged(event->window); if (c) return c->windowEvent(e); } return (event->event != event->window); // hide wm typical event from Qt } case XCB_CONFIGURE_REQUEST: { const auto *event = reinterpret_cast(e); if (event->parent == rootWindow()) { uint32_t values[5] = { 0, 0, 0, 0, 0}; const uint32_t value_mask = event->value_mask & (XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y | XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT | XCB_CONFIG_WINDOW_BORDER_WIDTH); int i = 0; if (value_mask & XCB_CONFIG_WINDOW_X) { values[i++] = event->x; } if (value_mask & XCB_CONFIG_WINDOW_Y) { values[i++] = event->y; } if (value_mask & XCB_CONFIG_WINDOW_WIDTH) { values[i++] = event->width; } if (value_mask & XCB_CONFIG_WINDOW_HEIGHT) { values[i++] = event->height; } if (value_mask & XCB_CONFIG_WINDOW_BORDER_WIDTH) { values[i++] = event->border_width; } xcb_configure_window(connection(), event->window, value_mask, values); return true; } break; } case XCB_FOCUS_IN: { const auto *event = reinterpret_cast(e); if (event->event == rootWindow() && (event->detail == XCB_NOTIFY_DETAIL_NONE || event->detail == XCB_NOTIFY_DETAIL_POINTER_ROOT || event->detail == XCB_NOTIFY_DETAIL_INFERIOR)) { Xcb::CurrentInput currentInput; updateXTime(); // focusToNull() uses xTime(), which is old now (FocusIn has no timestamp) // it seems we can "loose" focus reversions when the closing client hold a grab // => catch the typical pattern (though we don't want the focus on the root anyway) #348935 const bool lostFocusPointerToRoot = currentInput->focus == rootWindow() && event->detail == XCB_NOTIFY_DETAIL_INFERIOR; if (!currentInput.isNull() && (currentInput->focus == XCB_WINDOW_NONE || currentInput->focus == XCB_INPUT_FOCUS_POINTER_ROOT || lostFocusPointerToRoot)) { //kWarning( 1212 ) << "X focus set to None/PointerRoot, reseting focus" ; AbstractClient *c = mostRecentlyActivatedClient(); - if (c != NULL) + if (c != nullptr) requestFocus(c, true); - else if (activateNextClient(NULL)) + else if (activateNextClient(nullptr)) ; // ok, activated else focusToNull(); } } } // fall through case XCB_FOCUS_OUT: return true; // always eat these, they would tell Qt that KWin is the active app default: break; } return false; } // Used only to filter events that need to be processed by Qt first // (e.g. keyboard input to be composed), otherwise events are // handle by the XEvent filter above bool Workspace::workspaceEvent(QEvent* e) { if ((e->type() == QEvent::KeyPress || e->type() == QEvent::KeyRelease || e->type() == QEvent::ShortcutOverride) && effects && static_cast< EffectsHandlerImpl* >(effects)->hasKeyboardGrab()) { static_cast< EffectsHandlerImpl* >(effects)->grabbedKeyboardEvent(static_cast< QKeyEvent* >(e)); return true; } return false; } // **************************************** // Client // **************************************** /** * General handler for XEvents concerning the client window */ bool Client::windowEvent(xcb_generic_event_t *e) { if (findEventWindow(e) == window()) { // avoid doing stuff on frame or wrapper NET::Properties dirtyProperties; NET::Properties2 dirtyProperties2; double old_opacity = opacity(); info->event(e, &dirtyProperties, &dirtyProperties2); // pass through the NET stuff if ((dirtyProperties & NET::WMName) != 0) fetchName(); if ((dirtyProperties & NET::WMIconName) != 0) fetchIconicName(); if ((dirtyProperties & NET::WMStrut) != 0 || (dirtyProperties2 & NET::WM2ExtendedStrut) != 0) { workspace()->updateClientArea(); } if ((dirtyProperties & NET::WMIcon) != 0) getIcons(); // Note there's a difference between userTime() and info->userTime() // info->userTime() is the value of the property, userTime() also includes // updates of the time done by KWin (ButtonPress on windowrapper etc.). if ((dirtyProperties2 & NET::WM2UserTime) != 0) { workspace()->setWasUserInteraction(); updateUserTime(info->userTime()); } if ((dirtyProperties2 & NET::WM2StartupId) != 0) startupIdChanged(); if (dirtyProperties2 & NET::WM2Opacity) { if (compositing()) { addRepaintFull(); emit opacityChanged(this, old_opacity); } else { // forward to the frame if there's possibly another compositing manager running - NETWinInfo i(connection(), frameId(), rootWindow(), 0, 0); + NETWinInfo i(connection(), frameId(), rootWindow(), nullptr, nullptr); i.setOpacity(info->opacity()); } } if (dirtyProperties2 & NET::WM2FrameOverlap) { // ### Inform the decoration } if (dirtyProperties2.testFlag(NET::WM2WindowRole)) { emit windowRoleChanged(); } if (dirtyProperties2.testFlag(NET::WM2WindowClass)) { getResourceClass(); } if (dirtyProperties2.testFlag(NET::WM2BlockCompositing)) { setBlockingCompositing(info->isBlockingCompositing()); } if (dirtyProperties2.testFlag(NET::WM2GroupLeader)) { checkGroup(); updateAllowedActions(); // Group affects isMinimizable() } if (dirtyProperties2.testFlag(NET::WM2Urgency)) { updateUrgency(); } if (dirtyProperties2 & NET::WM2OpaqueRegion) { getWmOpaqueRegion(); } if (dirtyProperties2 & NET::WM2DesktopFileName) { setDesktopFileName(QByteArray(info->desktopFileName())); } } const uint8_t eventType = e->response_type & ~0x80; switch(eventType) { case XCB_UNMAP_NOTIFY: unmapNotifyEvent(reinterpret_cast(e)); break; case XCB_DESTROY_NOTIFY: destroyNotifyEvent(reinterpret_cast(e)); break; case XCB_MAP_REQUEST: // this one may pass the event to workspace return mapRequestEvent(reinterpret_cast(e)); case XCB_CONFIGURE_REQUEST: configureRequestEvent(reinterpret_cast(e)); break; case XCB_PROPERTY_NOTIFY: propertyNotifyEvent(reinterpret_cast(e)); break; case XCB_KEY_PRESS: updateUserTime(reinterpret_cast(e)->time); break; case XCB_BUTTON_PRESS: { const auto *event = reinterpret_cast(e); updateUserTime(event->time); buttonPressEvent(event->event, event->detail, event->state, event->event_x, event->event_y, event->root_x, event->root_y, event->time); break; } case XCB_KEY_RELEASE: // don't update user time on releases // e.g. if the user presses Alt+F2, the Alt release // would appear as user input to the currently active window break; case XCB_BUTTON_RELEASE: { const auto *event = reinterpret_cast(e); // don't update user time on releases // e.g. if the user presses Alt+F2, the Alt release // would appear as user input to the currently active window buttonReleaseEvent(event->event, event->detail, event->state, event->event_x, event->event_y, event->root_x, event->root_y); break; } case XCB_MOTION_NOTIFY: { const auto *event = reinterpret_cast(e); motionNotifyEvent(event->event, event->state, event->event_x, event->event_y, event->root_x, event->root_y); workspace()->updateFocusMousePosition(QPoint(event->root_x, event->root_y)); break; } case XCB_ENTER_NOTIFY: { auto *event = reinterpret_cast(e); enterNotifyEvent(event); // MotionNotify is guaranteed to be generated only if the mouse // move start and ends in the window; for cases when it only // starts or only ends there, Enter/LeaveNotify are generated. // Fake a MotionEvent in such cases to make handle of mouse // events simpler (Qt does that too). motionNotifyEvent(event->event, event->state, event->event_x, event->event_y, event->root_x, event->root_y); workspace()->updateFocusMousePosition(QPoint(event->root_x, event->root_y)); break; } case XCB_LEAVE_NOTIFY: { auto *event = reinterpret_cast(e); motionNotifyEvent(event->event, event->state, event->event_x, event->event_y, event->root_x, event->root_y); leaveNotifyEvent(event); // not here, it'd break following enter notify handling // workspace()->updateFocusMousePosition( QPoint( e->xcrossing.x_root, e->xcrossing.y_root )); break; } case XCB_FOCUS_IN: focusInEvent(reinterpret_cast(e)); break; case XCB_FOCUS_OUT: focusOutEvent(reinterpret_cast(e)); break; case XCB_REPARENT_NOTIFY: break; case XCB_CLIENT_MESSAGE: clientMessageEvent(reinterpret_cast(e)); break; case XCB_EXPOSE: { xcb_expose_event_t *event = reinterpret_cast(e); if (event->window == frameId() && !Compositor::self()->isActive()) { // TODO: only repaint required areas triggerDecorationRepaint(); } break; } default: if (eventType == Xcb::Extensions::self()->shapeNotifyEvent() && reinterpret_cast(e)->affected_window == window()) { detectShape(window()); // workaround for #19644 updateShape(); } if (eventType == Xcb::Extensions::self()->damageNotifyEvent() && reinterpret_cast(e)->drawable == frameId()) damageNotifyEvent(); break; } return true; // eat all events } /** * Handles map requests of the client window */ bool Client::mapRequestEvent(xcb_map_request_event_t *e) { if (e->window != window()) { // Special support for the save-set feature, which is a bit broken. // If there's a window from one client embedded in another one, // e.g. using XEMBED, and the embedder suddenly loses its X connection, // save-set will reparent the embedded window to its closest ancestor // that will remains. Unfortunately, with reparenting window managers, // this is not the root window, but the frame (or in KWin's case, // it's the wrapper for the client window). In this case, // the wrapper will get ReparentNotify for a window it won't know, // which will be ignored, and then it gets MapRequest, as save-set // always maps. Returning true here means that Workspace::workspaceEvent() // will handle this MapRequest and manage this window (i.e. act as if // it was reparented to root window). if (e->parent == wrapperId()) return false; return true; // no messing with frame etc. } // also copied in clientMessage() if (isMinimized()) unminimize(); if (isShade()) setShade(ShadeNone); if (!isOnCurrentDesktop()) { if (workspace()->allowClientActivation(this)) workspace()->activateClient(this); else demandAttention(); } return true; } /** * Handles unmap notify events of the client window */ void Client::unmapNotifyEvent(xcb_unmap_notify_event_t *e) { if (e->window != window()) return; if (e->event != wrapperId()) { // most probably event from root window when initially reparenting bool ignore = true; if (e->event == rootWindow() && (e->response_type & 0x80)) ignore = false; // XWithdrawWindow() if (ignore) return; } // check whether this is result of an XReparentWindow - client then won't be parented by wrapper // in this case do not release the client (causes reparent to root, removal from saveSet and what not) // but just destroy the client Xcb::Tree tree(m_client); xcb_window_t daddy = tree.parent(); if (daddy == m_wrapper) { releaseWindow(); // unmapped from a regular client state } else { destroyClient(); // the client was moved to some other parent } } void Client::destroyNotifyEvent(xcb_destroy_notify_event_t *e) { if (e->window != window()) return; destroyClient(); } /** * Handles client messages for the client window */ void Client::clientMessageEvent(xcb_client_message_event_t *e) { Toplevel::clientMessageEvent(e); if (e->window != window()) return; // ignore frame/wrapper // WM_STATE if (e->type == atoms->wm_change_state) { if (e->data.data32[0] == XCB_ICCCM_WM_STATE_ICONIC) minimize(); return; } } /** * Handles configure requests of the client window */ void Client::configureRequestEvent(xcb_configure_request_event_t *e) { if (e->window != window()) return; // ignore frame/wrapper if (isResize() || isMove()) return; // we have better things to do right now if (m_fullscreenMode == FullScreenNormal) { // refuse resizing of fullscreen windows // but allow resizing fullscreen hacks in order to let them cancel fullscreen mode sendSyntheticConfigureNotify(); return; } if (isSplash()) { // no manipulations with splashscreens either sendSyntheticConfigureNotify(); return; } if (e->value_mask & XCB_CONFIG_WINDOW_BORDER_WIDTH) { // first, get rid of a window border m_client.setBorderWidth(0); } if (e->value_mask & (XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y | XCB_CONFIG_WINDOW_HEIGHT | XCB_CONFIG_WINDOW_WIDTH)) configureRequest(e->value_mask, e->x, e->y, e->width, e->height, 0, false); if (e->value_mask & XCB_CONFIG_WINDOW_STACK_MODE) restackWindow(e->sibling, e->stack_mode, NET::FromApplication, userTime(), false); // Sending a synthetic configure notify always is fine, even in cases where // the ICCCM doesn't require this - it can be though of as 'the WM decided to move // the window later'. The client should not cause that many configure request, // so this should not have any significant impact. With user moving/resizing // the it should be optimized though (see also Client::setGeometry()/plainResize()/move()). sendSyntheticConfigureNotify(); // SELI TODO accept configure requests for isDesktop windows (because kdesktop // may get XRANDR resize event before kwin), but check it's still at the bottom? } /** * Handles property changes of the client window */ void Client::propertyNotifyEvent(xcb_property_notify_event_t *e) { Toplevel::propertyNotifyEvent(e); if (e->window != window()) return; // ignore frame/wrapper switch(e->atom) { case XCB_ATOM_WM_NORMAL_HINTS: getWmNormalHints(); break; case XCB_ATOM_WM_NAME: fetchName(); break; case XCB_ATOM_WM_ICON_NAME: fetchIconicName(); break; case XCB_ATOM_WM_TRANSIENT_FOR: readTransient(); break; case XCB_ATOM_WM_HINTS: getIcons(); // because KWin::icon() uses WMHints as fallback break; default: if (e->atom == atoms->motif_wm_hints) { getMotifHints(); } else if (e->atom == atoms->net_wm_sync_request_counter) getSyncCounter(); else if (e->atom == atoms->activities) checkActivities(); else if (e->atom == atoms->kde_first_in_window_list) updateFirstInTabBox(); else if (e->atom == atoms->kde_color_sheme) updateColorScheme(); else if (e->atom == atoms->kde_screen_edge_show) updateShowOnScreenEdge(); else if (e->atom == atoms->gtk_frame_extents) detectGtkFrameExtents(); else if (e->atom == atoms->kde_net_wm_appmenu_service_name) checkApplicationMenuServiceName(); else if (e->atom == atoms->kde_net_wm_appmenu_object_path) checkApplicationMenuObjectPath(); break; } } void Client::enterNotifyEvent(xcb_enter_notify_event_t *e) { if (e->event != frameId()) return; // care only about entering the whole frame #define MOUSE_DRIVEN_FOCUS (!options->focusPolicyIsReasonable() || \ (options->focusPolicy() == Options::FocusFollowsMouse && options->isNextFocusPrefersMouse())) if (e->mode == XCB_NOTIFY_MODE_NORMAL || (e->mode == XCB_NOTIFY_MODE_UNGRAB && MOUSE_DRIVEN_FOCUS)) { if (options->isShadeHover()) { cancelShadeHoverTimer(); if (isShade()) { shadeHoverTimer = new QTimer(this); connect(shadeHoverTimer, SIGNAL(timeout()), this, SLOT(shadeHover())); shadeHoverTimer->setSingleShot(true); shadeHoverTimer->start(options->shadeHoverInterval()); } } #undef MOUSE_DRIVEN_FOCUS enterEvent(QPoint(e->root_x, e->root_y)); return; } } void Client::leaveNotifyEvent(xcb_leave_notify_event_t *e) { if (e->event != frameId()) return; // care only about leaving the whole frame if (e->mode == XCB_NOTIFY_MODE_NORMAL) { if (!isMoveResizePointerButtonDown()) { setMoveResizePointerMode(PositionCenter); updateCursor(); } bool lostMouse = !rect().contains(QPoint(e->event_x, e->event_y)); // 'lostMouse' wouldn't work with e.g. B2 or Keramik, which have non-rectangular decorations // (i.e. the LeaveNotify event comes before leaving the rect and no LeaveNotify event // comes after leaving the rect) - so lets check if the pointer is really outside the window // TODO this still sucks if a window appears above this one - it should lose the mouse // if this window is another client, but not if it's a popup ... maybe after KDE3.1 :( // (repeat after me 'AARGHL!') if (!lostMouse && e->detail != XCB_NOTIFY_DETAIL_INFERIOR) { Xcb::Pointer pointer(frameId()); if (!pointer || !pointer->same_screen || pointer->child == XCB_WINDOW_NONE) { // really lost the mouse lostMouse = true; } } if (lostMouse) { leaveEvent(); cancelShadeHoverTimer(); if (shade_mode == ShadeHover && !isMoveResize() && !isMoveResizePointerButtonDown()) { shadeHoverTimer = new QTimer(this); connect(shadeHoverTimer, SIGNAL(timeout()), this, SLOT(shadeUnhover())); shadeHoverTimer->setSingleShot(true); shadeHoverTimer->start(options->shadeHoverInterval()); } if (isDecorated()) { // sending a move instead of a leave. With leave we need to send proper coords, with move it's handled internally QHoverEvent leaveEvent(QEvent::HoverMove, QPointF(-1, -1), QPointF(-1, -1), Qt::NoModifier); QCoreApplication::sendEvent(decoration(), &leaveEvent); } } if (options->focusPolicy() == Options::FocusStrictlyUnderMouse && isActive() && lostMouse) { - workspace()->requestDelayFocus(0); + workspace()->requestDelayFocus(nullptr); } return; } } #define XCapL KKeyServer::modXLock() #define XNumL KKeyServer::modXNumLock() #define XScrL KKeyServer::modXScrollLock() void Client::grabButton(int modifier) { unsigned int mods[ 8 ] = { 0, XCapL, XNumL, XNumL | XCapL, XScrL, XScrL | XCapL, XScrL | XNumL, XScrL | XNumL | XCapL }; for (int i = 0; i < 8; ++i) m_wrapper.grabButton(XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC, modifier | mods[ i ]); } void Client::ungrabButton(int modifier) { unsigned int mods[ 8 ] = { 0, XCapL, XNumL, XNumL | XCapL, XScrL, XScrL | XCapL, XScrL | XNumL, XScrL | XNumL | XCapL }; for (int i = 0; i < 8; ++i) m_wrapper.ungrabButton(modifier | mods[ i ]); } #undef XCapL #undef XNumL #undef XScrL /** * Releases the passive grab for some modifier combinations when a * window becomes active. This helps broken X programs that * missinterpret LeaveNotify events in grab mode to work properly * (Motif, AWT, Tk, ...) */ void Client::updateMouseGrab() { if (workspace()->globalShortcutsDisabled()) { m_wrapper.ungrabButton(); // keep grab for the simple click without modifiers if needed (see below) bool not_obscured = workspace()->topClientOnDesktop(VirtualDesktopManager::self()->current(), -1, true, false) == this; if (!(!options->isClickRaise() || not_obscured)) grabButton(XCB_NONE); return; } if (isActive() && !TabBox::TabBox::self()->forcedGlobalMouseGrab()) { // see TabBox::establishTabBoxGrab() // first grab all modifier combinations m_wrapper.grabButton(XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC); // remove the grab for no modifiers only if the window // is unobscured or if the user doesn't want click raise // (it is unobscured if it the topmost in the unconstrained stacking order, i.e. it is // the most recently raised window) bool not_obscured = workspace()->topClientOnDesktop(VirtualDesktopManager::self()->current(), -1, true, false) == this; if (!options->isClickRaise() || not_obscured) ungrabButton(XCB_NONE); else grabButton(XCB_NONE); ungrabButton(XCB_MOD_MASK_SHIFT); ungrabButton(XCB_MOD_MASK_CONTROL); ungrabButton(XCB_MOD_MASK_CONTROL | XCB_MOD_MASK_SHIFT); } else { m_wrapper.ungrabButton(); // simply grab all modifier combinations m_wrapper.grabButton(XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC); } } static bool modKeyDown(int state) { const uint keyModX = (options->keyCmdAllModKey() == Qt::Key_Meta) ? KKeyServer::modXMeta() : KKeyServer::modXAlt(); return keyModX && (state & KKeyServer::accelModMaskX()) == keyModX; } // return value matters only when filtering events before decoration gets them bool Client::buttonPressEvent(xcb_window_t w, int button, int state, int x, int y, int x_root, int y_root, xcb_timestamp_t time) { if (isMoveResizePointerButtonDown()) { if (w == wrapperId()) xcb_allow_events(connection(), XCB_ALLOW_SYNC_POINTER, XCB_TIME_CURRENT_TIME); //xTime()); return true; } if (w == wrapperId() || w == frameId() || w == inputId()) { // FRAME neco s tohohle by se melo zpracovat, nez to dostane dekorace updateUserTime(time); const bool bModKeyHeld = modKeyDown(state); if (isSplash() && button == XCB_BUTTON_INDEX_1 && !bModKeyHeld) { // hide splashwindow if the user clicks on it hideClient(true); if (w == wrapperId()) xcb_allow_events(connection(), XCB_ALLOW_SYNC_POINTER, XCB_TIME_CURRENT_TIME); //xTime()); return true; } Options::MouseCommand com = Options::MouseNothing; bool was_action = false; if (bModKeyHeld) { was_action = true; switch(button) { case XCB_BUTTON_INDEX_1: com = options->commandAll1(); break; case XCB_BUTTON_INDEX_2: com = options->commandAll2(); break; case XCB_BUTTON_INDEX_3: com = options->commandAll3(); break; case XCB_BUTTON_INDEX_4: case XCB_BUTTON_INDEX_5: com = options->operationWindowMouseWheel(button == XCB_BUTTON_INDEX_4 ? 120 : -120); break; } } else { if (w == wrapperId()) { if (button < 4) { com = getMouseCommand(x11ToQtMouseButton(button), &was_action); } else if (button < 6) { com = getWheelCommand(Qt::Vertical, &was_action); } } } if (was_action) { bool replay = performMouseCommand(com, QPoint(x_root, y_root)); if (isSpecialWindow()) replay = true; if (w == wrapperId()) // these can come only from a grab xcb_allow_events(connection(), replay ? XCB_ALLOW_REPLAY_POINTER : XCB_ALLOW_SYNC_POINTER, XCB_TIME_CURRENT_TIME); //xTime()); return true; } } if (w == wrapperId()) { // these can come only from a grab xcb_allow_events(connection(), XCB_ALLOW_REPLAY_POINTER, XCB_TIME_CURRENT_TIME); //xTime()); return true; } if (w == inputId()) { x = x_root - geometry().x(); y = y_root - geometry().y(); // New API processes core events FIRST and only passes unused ones to the decoration QMouseEvent ev(QMouseEvent::MouseButtonPress, QPoint(x, y), QPoint(x_root, y_root), x11ToQtMouseButton(button), x11ToQtMouseButtons(state), Qt::KeyboardModifiers()); return processDecorationButtonPress(&ev, true); } if (w == frameId() && isDecorated()) { if (button >= 4 && button <= 7) { const Qt::KeyboardModifiers modifiers = x11ToQtKeyboardModifiers(state); // Logic borrowed from qapplication_x11.cpp const int delta = 120 * ((button == 4 || button == 6) ? 1 : -1); const bool hor = (((button == 4 || button == 5) && (modifiers & Qt::AltModifier)) || (button == 6 || button == 7)); const QPoint angle = hor ? QPoint(delta, 0) : QPoint(0, delta); QWheelEvent event(QPointF(x, y), QPointF(x_root, y_root), QPoint(), angle, delta, hor ? Qt::Horizontal : Qt::Vertical, x11ToQtMouseButtons(state), modifiers); event.setAccepted(false); QCoreApplication::sendEvent(decoration(), &event); if (!event.isAccepted() && !hor) { if (titlebarPositionUnderMouse()) { performMouseCommand(options->operationTitlebarMouseWheel(delta), QPoint(x_root, y_root)); } } } else { QMouseEvent event(QEvent::MouseButtonPress, QPointF(x, y), QPointF(x_root, y_root), x11ToQtMouseButton(button), x11ToQtMouseButtons(state), x11ToQtKeyboardModifiers(state)); event.setAccepted(false); QCoreApplication::sendEvent(decoration(), &event); if (!event.isAccepted()) { processDecorationButtonPress(&event); } } return true; } return true; } // return value matters only when filtering events before decoration gets them bool Client::buttonReleaseEvent(xcb_window_t w, int button, int state, int x, int y, int x_root, int y_root) { if (w == frameId() && isDecorated()) { // wheel handled on buttonPress if (button < 4 || button > 7) { QMouseEvent event(QEvent::MouseButtonRelease, QPointF(x, y), QPointF(x_root, y_root), x11ToQtMouseButton(button), x11ToQtMouseButtons(state) & ~x11ToQtMouseButton(button), x11ToQtKeyboardModifiers(state)); event.setAccepted(false); QCoreApplication::sendEvent(decoration(), &event); if (event.isAccepted() || !titlebarPositionUnderMouse()) { invalidateDecorationDoubleClickTimer(); // click was for the deco and shall not init a doubleclick } } } if (w == wrapperId()) { xcb_allow_events(connection(), XCB_ALLOW_SYNC_POINTER, XCB_TIME_CURRENT_TIME); //xTime()); return true; } if (w != frameId() && w != inputId() && w != moveResizeGrabWindow()) return true; if (w == frameId() && workspace()->userActionsMenu() && workspace()->userActionsMenu()->isShown()) { const_cast(workspace()->userActionsMenu())->grabInput(); } x = this->x(); // translate from grab window to local coords y = this->y(); // Check whether other buttons are still left pressed int buttonMask = XCB_BUTTON_MASK_1 | XCB_BUTTON_MASK_2 | XCB_BUTTON_MASK_3; if (button == XCB_BUTTON_INDEX_1) buttonMask &= ~XCB_BUTTON_MASK_1; else if (button == XCB_BUTTON_INDEX_2) buttonMask &= ~XCB_BUTTON_MASK_2; else if (button == XCB_BUTTON_INDEX_3) buttonMask &= ~XCB_BUTTON_MASK_3; if ((state & buttonMask) == 0) { endMoveResize(); } return true; } // return value matters only when filtering events before decoration gets them bool Client::motionNotifyEvent(xcb_window_t w, int state, int x, int y, int x_root, int y_root) { if (w == frameId() && isDecorated() && !isMinimized()) { // TODO Mouse move event dependent on state QHoverEvent event(QEvent::HoverMove, QPointF(x, y), QPointF(x, y)); QCoreApplication::instance()->sendEvent(decoration(), &event); } if (w != frameId() && w != inputId() && w != moveResizeGrabWindow()) return true; // care only about the whole frame if (!isMoveResizePointerButtonDown()) { if (w == inputId()) { int x = x_root - geometry().x();// + padding_left; int y = y_root - geometry().y();// + padding_top; if (isDecorated()) { QHoverEvent event(QEvent::HoverMove, QPointF(x, y), QPointF(x, y)); QCoreApplication::instance()->sendEvent(decoration(), &event); } } Position newmode = modKeyDown(state) ? PositionCenter : mousePosition(); if (newmode != moveResizePointerMode()) { setMoveResizePointerMode(newmode); updateCursor(); } return false; } if (w == moveResizeGrabWindow()) { x = this->x(); // translate from grab window to local coords y = this->y(); } handleMoveResize(QPoint(x, y), QPoint(x_root, y_root)); return true; } void Client::focusInEvent(xcb_focus_in_event_t *e) { if (e->event != window()) return; // only window gets focus if (e->mode == XCB_NOTIFY_MODE_UNGRAB) return; // we don't care if (e->detail == XCB_NOTIFY_DETAIL_POINTER) return; // we don't care if (!isShown(false) || !isOnCurrentDesktop()) // we unmapped it, but it got focus meanwhile -> return; // activateNextClient() already transferred focus elsewhere workspace()->forEachClient([](Client *client) { client->cancelFocusOutTimer(); }); // check if this client is in should_get_focus list or if activation is allowed bool activate = workspace()->allowClientActivation(this, -1U, true); workspace()->gotFocusIn(this); // remove from should_get_focus list if (activate) setActive(true); else { workspace()->restoreFocus(); demandAttention(); } } void Client::focusOutEvent(xcb_focus_out_event_t *e) { if (e->event != window()) return; // only window gets focus if (e->mode == XCB_NOTIFY_MODE_GRAB) return; // we don't care if (isShade()) return; // here neither if (e->detail != XCB_NOTIFY_DETAIL_NONLINEAR && e->detail != XCB_NOTIFY_DETAIL_NONLINEAR_VIRTUAL) // SELI check all this return; // hack for motif apps like netscape if (QApplication::activePopupWidget()) return; // When a client loses focus, FocusOut events are usually immediatelly // followed by FocusIn events for another client that gains the focus // (unless the focus goes to another screen, or to the nofocus widget). // Without this check, the former focused client would have to be // deactivated, and after that, the new one would be activated, with // a short time when there would be no active client. This can cause // flicker sometimes, e.g. when a fullscreen is shown, and focus is transferred // from it to its transient, the fullscreen would be kept in the Active layer // at the beginning and at the end, but not in the middle, when the active // client would be temporarily none (see Client::belongToLayer() ). // Therefore the setActive(false) call is moved to the end of the current // event queue. If there is a matching FocusIn event in the current queue // this will be processed before the setActive(false) call and the activation // of the Client which gained FocusIn will automatically deactivate the // previously active client. if (!m_focusOutTimer) { m_focusOutTimer = new QTimer(this); m_focusOutTimer->setSingleShot(true); m_focusOutTimer->setInterval(0); connect(m_focusOutTimer, &QTimer::timeout, [this]() { setActive(false); }); } m_focusOutTimer->start(); } // performs _NET_WM_MOVERESIZE void Client::NETMoveResize(int x_root, int y_root, NET::Direction direction) { if (direction == NET::Move) { // move cursor to the provided position to prevent the window jumping there on first movement // the expectation is that the cursor is already at the provided position, // thus it's more a safety measurement Cursor::setPos(QPoint(x_root, y_root)); performMouseCommand(Options::MouseMove, QPoint(x_root, y_root)); } else if (isMoveResize() && direction == NET::MoveResizeCancel) { finishMoveResize(true); setMoveResizePointerButtonDown(false); updateCursor(); } else if (direction >= NET::TopLeft && direction <= NET::Left) { static const Position convert[] = { PositionTopLeft, PositionTop, PositionTopRight, PositionRight, PositionBottomRight, PositionBottom, PositionBottomLeft, PositionLeft }; if (!isResizable() || isShade()) return; if (isMoveResize()) finishMoveResize(false); setMoveResizePointerButtonDown(true); setMoveOffset(QPoint(x_root - x(), y_root - y())); // map from global setInvertedMoveOffset(rect().bottomRight() - moveOffset()); setUnrestrictedMoveResize(false); setMoveResizePointerMode(convert[ direction ]); if (!startMoveResize()) setMoveResizePointerButtonDown(false); updateCursor(); } else if (direction == NET::KeyboardMove) { // ignore mouse coordinates given in the message, mouse position is used by the moving algorithm Cursor::setPos(geometry().center()); performMouseCommand(Options::MouseUnrestrictedMove, geometry().center()); } else if (direction == NET::KeyboardSize) { // ignore mouse coordinates given in the message, mouse position is used by the resizing algorithm Cursor::setPos(geometry().bottomRight()); performMouseCommand(Options::MouseUnrestrictedResize, geometry().bottomRight()); } } void Client::keyPressEvent(uint key_code, xcb_timestamp_t time) { updateUserTime(time); AbstractClient::keyPressEvent(key_code); } // **************************************** // Unmanaged // **************************************** bool Unmanaged::windowEvent(xcb_generic_event_t *e) { double old_opacity = opacity(); NET::Properties dirtyProperties; NET::Properties2 dirtyProperties2; info->event(e, &dirtyProperties, &dirtyProperties2); // pass through the NET stuff if (dirtyProperties2 & NET::WM2Opacity) { if (compositing()) { addRepaintFull(); emit opacityChanged(this, old_opacity); } } if (dirtyProperties2 & NET::WM2OpaqueRegion) { getWmOpaqueRegion(); } if (dirtyProperties2.testFlag(NET::WM2WindowRole)) { emit windowRoleChanged(); } if (dirtyProperties2.testFlag(NET::WM2WindowClass)) { getResourceClass(); } const uint8_t eventType = e->response_type & ~0x80; switch (eventType) { case XCB_DESTROY_NOTIFY: release(ReleaseReason::Destroyed); break; case XCB_UNMAP_NOTIFY:{ workspace()->updateFocusMousePosition(Cursor::pos()); // may cause leave event // unmap notify might have been emitted due to a destroy notify // but unmap notify gets emitted before the destroy notify, nevertheless at this // point the window is already destroyed. This means any XCB request with the window // will cause an error. // To not run into these errors we try to wait for the destroy notify. For this we // generate a round trip to the X server and wait a very short time span before // handling the release. updateXTime(); // using 1 msec to not just move it at the end of the event loop but add an very short // timespan to cover cases like unmap() followed by destroy(). The only other way to // ensure that the window is not destroyed when we do the release handling is to grab // the XServer which we do not want to do for an Unmanaged. The timespan of 1 msec is // short enough to not cause problems in the close window animations. // It's of course still possible that we miss the destroy in which case non-fatal // X errors are reported to the event loop and logged by Qt. QTimer::singleShot(1, this, SLOT(release())); break; } case XCB_CONFIGURE_NOTIFY: configureNotifyEvent(reinterpret_cast(e)); break; case XCB_PROPERTY_NOTIFY: propertyNotifyEvent(reinterpret_cast(e)); break; case XCB_CLIENT_MESSAGE: clientMessageEvent(reinterpret_cast(e)); break; default: { if (eventType == Xcb::Extensions::self()->shapeNotifyEvent()) { detectShape(window()); addRepaintFull(); addWorkspaceRepaint(geometry()); // in case shape change removes part of this window emit geometryShapeChanged(this, geometry()); } if (eventType == Xcb::Extensions::self()->damageNotifyEvent()) damageNotifyEvent(); break; } } return false; // don't eat events, even our own unmanaged widgets are tracked } void Unmanaged::configureNotifyEvent(xcb_configure_notify_event_t *e) { if (effects) static_cast(effects)->checkInputWindowStacking(); // keep them on top QRect newgeom(e->x, e->y, e->width, e->height); if (newgeom != geom) { addWorkspaceRepaint(visibleRect()); // damage old area QRect old = geom; geom = newgeom; emit geometryChanged(); // update shadow region addRepaintFull(); if (old.size() != geom.size()) discardWindowPixmap(); emit geometryShapeChanged(this, old); } } // **************************************** // Toplevel // **************************************** void Toplevel::propertyNotifyEvent(xcb_property_notify_event_t *e) { if (e->window != window()) return; // ignore frame/wrapper switch(e->atom) { default: if (e->atom == atoms->wm_client_leader) getWmClientLeader(); else if (e->atom == atoms->kde_net_wm_shadow) getShadow(); else if (e->atom == atoms->kde_skip_close_animation) getSkipCloseAnimation(); break; } } void Toplevel::clientMessageEvent(xcb_client_message_event_t *e) { if (e->type == atoms->wl_surface_id) { m_surfaceId = e->data.data32[0]; if (auto w = waylandServer()) { if (auto s = KWayland::Server::SurfaceInterface::get(m_surfaceId, w->xWaylandConnection())) { setSurface(s); } } emit surfaceIdChanged(m_surfaceId); } } } // namespace diff --git a/focuschain.cpp b/focuschain.cpp index 1054d69a5..229314968 100644 --- a/focuschain.cpp +++ b/focuschain.cpp @@ -1,269 +1,269 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2012 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 "focuschain.h" #include "abstract_client.h" #include "screens.h" namespace KWin { KWIN_SINGLETON_FACTORY_VARIABLE(FocusChain, s_manager) FocusChain::FocusChain(QObject *parent) : QObject(parent) , m_separateScreenFocus(false) - , m_activeClient(NULL) + , m_activeClient(nullptr) , m_currentDesktop(0) { } FocusChain::~FocusChain() { - s_manager = NULL; + s_manager = nullptr; } void FocusChain::remove(AbstractClient *client) { for (auto it = m_desktopFocusChains.begin(); it != m_desktopFocusChains.end(); ++it) { it.value().removeAll(client); } m_mostRecentlyUsed.removeAll(client); } void FocusChain::resize(uint previousSize, uint newSize) { for (uint i = previousSize + 1; i <= newSize; ++i) { m_desktopFocusChains.insert(i, Chain()); } for (uint i = previousSize; i > newSize; --i) { m_desktopFocusChains.remove(i); } } AbstractClient *FocusChain::getForActivation(uint desktop) const { return getForActivation(desktop, screens()->current()); } AbstractClient *FocusChain::getForActivation(uint desktop, int screen) const { auto it = m_desktopFocusChains.constFind(desktop); if (it == m_desktopFocusChains.constEnd()) { - return NULL; + return nullptr; } const auto &chain = it.value(); for (int i = chain.size() - 1; i >= 0; --i) { auto tmp = chain.at(i); // TODO: move the check into Client if (tmp->isShown(false) && tmp->isOnCurrentActivity() && ( !m_separateScreenFocus || tmp->screen() == screen)) { return tmp; } } - return NULL; + return nullptr; } void FocusChain::update(AbstractClient *client, FocusChain::Change change) { if (!client->wantsTabFocus()) { // Doesn't want tab focus, remove remove(client); return; } if (client->isOnAllDesktops()) { // Now on all desktops, add it to focus chains it is not already in for (auto it = m_desktopFocusChains.begin(); it != m_desktopFocusChains.end(); ++it) { auto &chain = it.value(); // Making first/last works only on current desktop, don't affect all desktops if (it.key() == m_currentDesktop && (change == MakeFirst || change == MakeLast)) { if (change == MakeFirst) { makeFirstInChain(client, chain); } else { makeLastInChain(client, chain); } } else { insertClientIntoChain(client, chain); } } } else { // Now only on desktop, remove it anywhere else for (auto it = m_desktopFocusChains.begin(); it != m_desktopFocusChains.end(); ++it) { auto &chain = it.value(); if (client->isOnDesktop(it.key())) { updateClientInChain(client, change, chain); } else { chain.removeAll(client); } } } // add for most recently used chain updateClientInChain(client, change, m_mostRecentlyUsed); } void FocusChain::updateClientInChain(AbstractClient *client, FocusChain::Change change, Chain &chain) { if (change == MakeFirst) { makeFirstInChain(client, chain); } else if (change == MakeLast) { makeLastInChain(client, chain); } else { insertClientIntoChain(client, chain); } } void FocusChain::insertClientIntoChain(AbstractClient *client, Chain &chain) { if (chain.contains(client)) { return; } if (m_activeClient && m_activeClient != client && !chain.empty() && chain.last() == m_activeClient) { // Add it after the active client chain.insert(chain.size() - 1, client); } else { // Otherwise add as the first one chain.append(client); } } void FocusChain::moveAfterClient(AbstractClient *client, AbstractClient *reference) { if (!client->wantsTabFocus()) { return; } for (auto it = m_desktopFocusChains.begin(); it != m_desktopFocusChains.end(); ++it) { if (!client->isOnDesktop(it.key())) { continue; } moveAfterClientInChain(client, reference, it.value()); } moveAfterClientInChain(client, reference, m_mostRecentlyUsed); } void FocusChain::moveAfterClientInChain(AbstractClient *client, AbstractClient *reference, Chain &chain) { if (!chain.contains(reference)) { return; } if (AbstractClient::belongToSameApplication(reference, client)) { chain.removeAll(client); chain.insert(chain.indexOf(reference), client); } else { chain.removeAll(client); for (int i = chain.size() - 1; i >= 0; --i) { if (AbstractClient::belongToSameApplication(reference, chain.at(i))) { chain.insert(i, client); break; } } } } AbstractClient *FocusChain::firstMostRecentlyUsed() const { if (m_mostRecentlyUsed.isEmpty()) { - return NULL; + return nullptr; } return m_mostRecentlyUsed.first(); } AbstractClient *FocusChain::nextMostRecentlyUsed(AbstractClient *reference) const { if (m_mostRecentlyUsed.isEmpty()) { - return NULL; + return nullptr; } const int index = m_mostRecentlyUsed.indexOf(reference); if (index == -1) { return m_mostRecentlyUsed.first(); } if (index == 0) { return m_mostRecentlyUsed.last(); } return m_mostRecentlyUsed.at(index - 1); } // copied from activation.cpp bool FocusChain::isUsableFocusCandidate(AbstractClient *c, AbstractClient *prev) const { return c != prev && c->isShown(false) && c->isOnCurrentDesktop() && c->isOnCurrentActivity() && (!m_separateScreenFocus || c->isOnScreen(prev ? prev->screen() : screens()->current())); } AbstractClient *FocusChain::nextForDesktop(AbstractClient *reference, uint desktop) const { auto it = m_desktopFocusChains.constFind(desktop); if (it == m_desktopFocusChains.constEnd()) { - return NULL; + return nullptr; } const auto &chain = it.value(); for (int i = chain.size() - 1; i >= 0; --i) { auto client = chain.at(i); if (isUsableFocusCandidate(client, reference)) { return client; } } - return NULL; + return nullptr; } void FocusChain::makeFirstInChain(AbstractClient *client, Chain &chain) { chain.removeAll(client); if (client->isMinimized()) { // add it before the first minimized ... for (int i = chain.count()-1; i >= 0; --i) { if (chain.at(i)->isMinimized()) { chain.insert(i+1, client); return; } } chain.prepend(client); // ... or at end of chain } else { chain.append(client); } } void FocusChain::makeLastInChain(AbstractClient *client, Chain &chain) { chain.removeAll(client); chain.prepend(client); } bool FocusChain::contains(AbstractClient *client, uint desktop) const { auto it = m_desktopFocusChains.constFind(desktop); if (it == m_desktopFocusChains.constEnd()) { return false; } return it.value().contains(client); } } // namespace diff --git a/geometry.cpp b/geometry.cpp index ab6cceb85..7164e6b4f 100644 --- a/geometry.cpp +++ b/geometry.cpp @@ -1,3396 +1,3396 @@ /******************************************************************** 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 #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, 0); + NETWinInfo i(connection(), w, rootWindow(), NET::WMFrameExtents, nullptr); 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 = 0; +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() == NULL); - Q_ASSERT(QWidget::mouseGrabber() == NULL); + Q_ASSERT(QWidget::keyboardGrabber() == nullptr); + Q_ASSERT(QWidget::mouseGrabber() == nullptr); stopDelayedMoveResize(); - if (QApplication::activePopupWidget() != NULL) + 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, NULL, rootWindow()); + 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, NULL)); + 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 = NULL; + 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 = NULL; + 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/geometrytip.cpp b/geometrytip.cpp index 237f3644f..039521364 100644 --- a/geometrytip.cpp +++ b/geometrytip.cpp @@ -1,75 +1,75 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (c) 2003, Karol Szwed 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 "geometrytip.h" namespace KWin { GeometryTip::GeometryTip(const Xcb::GeometryHints* xSizeHints): - QLabel(0) + QLabel(nullptr) { setObjectName(QLatin1String("kwingeometry")); setMargin(1); setIndent(0); setLineWidth(1); setFrameStyle(QFrame::Raised | QFrame::StyledPanel); setAlignment(Qt::AlignCenter | Qt::AlignTop); setWindowFlags(Qt::X11BypassWindowManagerHint); sizeHints = xSizeHints; } GeometryTip::~GeometryTip() { } static QString numberWithSign(int n) { const QLocale locale; const QChar sign = n >= 0 ? locale.positiveSign() : locale.negativeSign(); return sign + QString::number(std::abs(n)); } void GeometryTip::setGeometry(const QRect& geom) { int w = geom.width(); int h = geom.height(); if (sizeHints) { if (sizeHints->hasResizeIncrements()) { w = (w - sizeHints->baseSize().width()) / sizeHints->resizeIncrements().width(); h = (h - sizeHints->baseSize().height()) / sizeHints->resizeIncrements().height(); } } h = qMax(h, 0); // in case of isShade() and PBaseSize const QString pos = QStringLiteral("%1,%2
(%3 x %4)") .arg(numberWithSign(geom.x())) .arg(numberWithSign(geom.y())) .arg(w) .arg(h); setText(pos); adjustSize(); move(geom.x() + ((geom.width() - width()) / 2), geom.y() + ((geom.height() - height()) / 2)); } } // namespace diff --git a/group.cpp b/group.cpp index 0fea1fdbb..44d3821c8 100644 --- a/group.cpp +++ b/group.cpp @@ -1,917 +1,917 @@ /******************************************************************** 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 . *********************************************************************/ /* This file contains things relevant to window grouping. */ //#define QT_CLEAN_NAMESPACE #include "group.h" #include #include "workspace.h" #include "client.h" #include "effects.h" #include #include #include /* TODO Rename as many uses of 'transient' as possible (hasTransient->hasSubwindow,etc.), or I'll get it backwards in half of the cases again. */ namespace KWin { //******************************************** // Group //******************************************** Group::Group(xcb_window_t leader_P) - : leader_client(NULL), + : leader_client(nullptr), leader_wid(leader_P), - leader_info(NULL), + leader_info(nullptr), user_time(-1U), refcount(0) { if (leader_P != XCB_WINDOW_NONE) { leader_client = workspace()->findClient(Predicate::WindowMatch, leader_P); leader_info = new NETWinInfo(connection(), leader_P, rootWindow(), - 0, NET::WM2StartupId); + nullptr, NET::WM2StartupId); } effect_group = new EffectWindowGroupImpl(this); workspace()->addGroup(this); } Group::~Group() { delete leader_info; delete effect_group; } QIcon Group::icon() const { - if (leader_client != NULL) + if (leader_client != nullptr) return leader_client->icon(); else if (leader_wid != XCB_WINDOW_NONE) { QIcon ic; NETWinInfo info(connection(), leader_wid, rootWindow(), NET::WMIcon, NET::WM2IconPixmap); auto readIcon = [&ic, &info, this](int size, bool scale = true) { const QPixmap pix = KWindowSystem::icon(leader_wid, size, size, scale, KWindowSystem::NETWM | KWindowSystem::WMHints, &info); if (!pix.isNull()) { ic.addPixmap(pix); } }; readIcon(16); readIcon(32); readIcon(48, false); readIcon(64, false); readIcon(128, false); return ic; } return QIcon(); } void Group::addMember(Client* member_P) { _members.append(member_P); // qDebug() << "GROUPADD:" << this << ":" << member_P; // qDebug() << kBacktrace(); } void Group::removeMember(Client* member_P) { // qDebug() << "GROUPREMOVE:" << this << ":" << member_P; // qDebug() << kBacktrace(); Q_ASSERT(_members.contains(member_P)); _members.removeAll(member_P); // there are cases when automatic deleting of groups must be delayed, // e.g. when removing a member and doing some operation on the possibly // other members of the group (which would be however deleted already // if there were no other members) if (refcount == 0 && _members.isEmpty()) { workspace()->removeGroup(this); delete this; } } void Group::ref() { ++refcount; } void Group::deref() { if (--refcount == 0 && _members.isEmpty()) { workspace()->removeGroup(this); delete this; } } void Group::gotLeader(Client* leader_P) { Q_ASSERT(leader_P->window() == leader_wid); leader_client = leader_P; } void Group::lostLeader() { Q_ASSERT(!_members.contains(leader_client)); - leader_client = NULL; + leader_client = nullptr; if (_members.isEmpty()) { workspace()->removeGroup(this); delete this; } } //*************************************** // Workspace //*************************************** Group* Workspace::findGroup(xcb_window_t leader) const { Q_ASSERT(leader != XCB_WINDOW_NONE); for (GroupList::ConstIterator it = groups.constBegin(); it != groups.constEnd(); ++it) if ((*it)->leader() == leader) return *it; - return NULL; + return nullptr; } // Client is group transient, but has no group set. Try to find // group with windows with the same client leader. Group* Workspace::findClientLeaderGroup(const Client* c) const { - Group* ret = NULL; + Group* ret = nullptr; for (ClientList::ConstIterator it = clients.constBegin(); it != clients.constEnd(); ++it) { if (*it == c) continue; if ((*it)->wmClientLeader() == c->wmClientLeader()) { - if (ret == NULL || ret == (*it)->group()) + if (ret == nullptr || ret == (*it)->group()) ret = (*it)->group(); else { // There are already two groups with the same client leader. // This most probably means the app uses group transients without // setting group for its windows. Merging the two groups is a bad // hack, but there's no really good solution for this case. ClientList old_group = (*it)->group()->members(); // old_group autodeletes when being empty for (int pos = 0; pos < old_group.count(); ++pos) { Client* tmp = old_group[ pos ]; if (tmp != c) tmp->changeClientLeaderGroup(ret); } } } } return ret; } void Workspace::updateMinimizedOfTransients(AbstractClient* c) { // if mainwindow is minimized or shaded, minimize transients too if (c->isMinimized()) { for (auto it = c->transients().constBegin(); it != c->transients().constEnd(); ++it) { if ((*it)->isModal()) continue; // there's no reason to hide modal dialogs with the main client // but to keep them to eg. watch progress or whatever if (!(*it)->isMinimized()) { (*it)->minimize(); updateMinimizedOfTransients((*it)); } } if (c->isModal()) { // if a modal dialog is minimized, minimize its mainwindow too foreach (AbstractClient * c2, c->mainClients()) c2->minimize(); } } else { // else unmiminize the transients for (auto it = c->transients().constBegin(); it != c->transients().constEnd(); ++it) { if ((*it)->isMinimized()) { (*it)->unminimize(); updateMinimizedOfTransients((*it)); } } if (c->isModal()) { foreach (AbstractClient * c2, c->mainClients()) c2->unminimize(); } } } /** * Sets the client \a c's transient windows' on_all_desktops property to \a on_all_desktops. */ void Workspace::updateOnAllDesktopsOfTransients(AbstractClient* c) { for (auto it = c->transients().constBegin(); it != c->transients().constEnd(); ++it) { if ((*it)->isOnAllDesktops() != c->isOnAllDesktops()) (*it)->setOnAllDesktops(c->isOnAllDesktops()); } } // A new window has been mapped. Check if it's not a mainwindow for some already existing transient window. void Workspace::checkTransients(xcb_window_t w) { for (ClientList::ConstIterator it = clients.constBegin(); it != clients.constEnd(); ++it) (*it)->checkTransient(w); } //**************************************** // Toplevel //**************************************** // hacks for broken apps here // all resource classes are forced to be lowercase bool Toplevel::resourceMatch(const Toplevel* c1, const Toplevel* c2) { return c1->resourceClass() == c2->resourceClass(); } //**************************************** // Client //**************************************** bool Client::belongToSameApplication(const Client* c1, const Client* c2, SameApplicationChecks checks) { bool same_app = false; // tests that definitely mean they belong together if (c1 == c2) same_app = true; else if (c1->isTransient() && c2->hasTransient(c1, true)) same_app = true; // c1 has c2 as mainwindow else if (c2->isTransient() && c1->hasTransient(c2, true)) same_app = true; // c2 has c1 as mainwindow else if (c1->group() == c2->group()) same_app = true; // same group else if (c1->wmClientLeader() == c2->wmClientLeader() && c1->wmClientLeader() != c1->window() // if WM_CLIENT_LEADER is not set, it returns window(), && c2->wmClientLeader() != c2->window()) // don't use in this test then same_app = true; // same client leader // tests that mean they most probably don't belong together else if ((c1->pid() != c2->pid() && !checks.testFlag(SameApplicationCheck::AllowCrossProcesses)) || c1->wmClientMachine(false) != c2->wmClientMachine(false)) ; // different processes else if (c1->wmClientLeader() != c2->wmClientLeader() && c1->wmClientLeader() != c1->window() // if WM_CLIENT_LEADER is not set, it returns window(), && c2->wmClientLeader() != c2->window() // don't use in this test then && !checks.testFlag(SameApplicationCheck::AllowCrossProcesses)) ; // different client leader else if (!resourceMatch(c1, c2)) ; // different apps else if (!sameAppWindowRoleMatch(c1, c2, checks.testFlag(SameApplicationCheck::RelaxedForActive)) && !checks.testFlag(SameApplicationCheck::AllowCrossProcesses)) ; // "different" apps else if (c1->pid() == 0 || c2->pid() == 0) ; // old apps that don't have _NET_WM_PID, consider them different // if they weren't found to match above else same_app = true; // looks like it's the same app return same_app; } // Non-transient windows with window role containing '#' are always // considered belonging to different applications (unless // the window role is exactly the same). KMainWindow sets // window role this way by default, and different KMainWindow // usually "are" different application from user's point of view. // This help with no-focus-stealing for e.g. konqy reusing. // On the other hand, if one of the windows is active, they are // considered belonging to the same application. This is for // the cases when opening new mainwindow directly from the application, // e.g. 'Open New Window' in konqy ( active_hack == true ). bool Client::sameAppWindowRoleMatch(const Client* c1, const Client* c2, bool active_hack) { if (c1->isTransient()) { while (const Client *t = dynamic_cast(c1->transientFor())) c1 = t; if (c1->groupTransient()) return c1->group() == c2->group(); #if 0 // if a group transient is in its own group, it didn't possibly have a group, // and therefore should be considered belonging to the same app like // all other windows from the same app || c1->group()->leaderClient() == c1 || c2->group()->leaderClient() == c2; #endif } if (c2->isTransient()) { while (const Client *t = dynamic_cast(c2->transientFor())) c2 = t; if (c2->groupTransient()) return c1->group() == c2->group(); #if 0 || c1->group()->leaderClient() == c1 || c2->group()->leaderClient() == c2; #endif } int pos1 = c1->windowRole().indexOf('#'); int pos2 = c2->windowRole().indexOf('#'); if ((pos1 >= 0 && pos2 >= 0)) { if (!active_hack) // without the active hack for focus stealing prevention, return c1 == c2; // different mainwindows are always different apps if (!c1->isActive() && !c2->isActive()) return c1 == c2; else return true; } return true; } /* Transiency stuff: ICCCM 4.1.2.6, NETWM 7.3 WM_TRANSIENT_FOR is basically means "this is my mainwindow". For NET::Unknown windows, transient windows are considered to be NET::Dialog windows, for compatibility with non-NETWM clients. KWin may adjust the value of this property in some cases (window pointing to itself or creating a loop, keeping NET::Splash windows above other windows from the same app, etc.). Client::transient_for_id is the value of the WM_TRANSIENT_FOR property, after possibly being adjusted by KWin. Client::transient_for points to the Client this Client is transient for, or is NULL. If Client::transient_for_id is poiting to the root window, the window is considered to be transient for the whole window group, as suggested in NETWM 7.3. In the case of group transient window, Client::transient_for is NULL, and Client::groupTransient() returns true. Such window is treated as if it were transient for every window in its window group that has been mapped _before_ it (or, to be exact, was added to the same group before it). Otherwise two group transients can create loops, which can lead very very nasty things (bug #67914 and all its dupes). Client::original_transient_for_id is the value of the property, which may be different if Client::transient_for_id if e.g. forcing NET::Splash to be kept on top of its window group, or when the mainwindow is not mapped yet, in which case the window is temporarily made group transient, and when the mainwindow is mapped, transiency is re-evaluated. This can get a bit complicated with with e.g. two Konqueror windows created by the same process. They should ideally appear like two independent applications to the user. This should be accomplished by all windows in the same process having the same window group (needs to be changed in Qt at the moment), and using non-group transients poiting to their relevant mainwindow for toolwindows etc. KWin should handle both group and non-group transient dialogs well. In other words: - non-transient windows : isTransient() == false - normal transients : transientFor() != NULL - group transients : groupTransient() == true - list of mainwindows : mainClients() (call once and loop over the result) - list of transients : transients() - every window in the group : group()->members() */ Xcb::TransientFor Client::fetchTransient() const { return Xcb::TransientFor(window()); } void Client::readTransientProperty(Xcb::TransientFor &transientFor) { xcb_window_t new_transient_for_id = XCB_WINDOW_NONE; if (transientFor.getTransientFor(&new_transient_for_id)) { m_originalTransientForId = new_transient_for_id; new_transient_for_id = verifyTransientFor(new_transient_for_id, true); } else { m_originalTransientForId = XCB_WINDOW_NONE; new_transient_for_id = verifyTransientFor(XCB_WINDOW_NONE, false); } setTransient(new_transient_for_id); } void Client::readTransient() { Xcb::TransientFor transientFor = fetchTransient(); readTransientProperty(transientFor); } void Client::setTransient(xcb_window_t new_transient_for_id) { if (new_transient_for_id != m_transientForId) { removeFromMainClients(); Client *transient_for = nullptr; m_transientForId = new_transient_for_id; if (m_transientForId != XCB_WINDOW_NONE && !groupTransient()) { transient_for = workspace()->findClient(Predicate::WindowMatch, m_transientForId); - Q_ASSERT(transient_for != NULL); // verifyTransient() had to check this + Q_ASSERT(transient_for != nullptr); // verifyTransient() had to check this transient_for->addTransient(this); } // checkGroup() will check 'check_active_modal' setTransientFor(transient_for); - checkGroup(NULL, true); // force, because transiency has changed + checkGroup(nullptr, true); // force, because transiency has changed workspace()->updateClientLayer(this); workspace()->resetUpdateToolWindowsTimer(); emit transientChanged(); } } void Client::removeFromMainClients() { if (transientFor()) transientFor()->removeTransient(this); if (groupTransient()) { for (ClientList::ConstIterator it = group()->members().constBegin(); it != group()->members().constEnd(); ++it) (*it)->removeTransient(this); } } // *sigh* this transiency handling is madness :( // This one is called when destroying/releasing a window. // It makes sure this client is removed from all grouping // related lists. void Client::cleanGrouping() { // qDebug() << "CLEANGROUPING:" << this; // for ( ClientList::ConstIterator it = group()->members().begin(); // it != group()->members().end(); // ++it ) // qDebug() << "CL:" << *it; // ClientList mains; // mains = mainClients(); // for ( ClientList::ConstIterator it = mains.begin(); // it != mains.end(); // ++it ) // qDebug() << "MN:" << *it; removeFromMainClients(); // qDebug() << "CLEANGROUPING2:" << this; // for ( ClientList::ConstIterator it = group()->members().begin(); // it != group()->members().end(); // ++it ) // qDebug() << "CL2:" << *it; // mains = mainClients(); // for ( ClientList::ConstIterator it = mains.begin(); // it != mains.end(); // ++it ) // qDebug() << "MN2:" << *it; 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; } // qDebug() << "CLEANGROUPING3:" << this; // for ( ClientList::ConstIterator it = group()->members().begin(); // it != group()->members().end(); // ++it ) // qDebug() << "CL3:" << *it; // mains = mainClients(); // for ( ClientList::ConstIterator it = mains.begin(); // it != mains.end(); // ++it ) // qDebug() << "MN3:" << *it; // HACK // removeFromMainClients() did remove 'this' from transient // lists of all group members, but then made windows that // were transient for 'this' group transient, which again // added 'this' to those transient lists :( ClientList group_members = group()->members(); group()->removeMember(this); - in_group = NULL; + in_group = nullptr; for (ClientList::ConstIterator it = group_members.constBegin(); it != group_members.constEnd(); ++it) (*it)->removeTransient(this); // qDebug() << "CLEANGROUPING4:" << this; // for ( ClientList::ConstIterator it = group_members.begin(); // it != group_members.end(); // ++it ) // qDebug() << "CL4:" << *it; m_transientForId = XCB_WINDOW_NONE; } // Make sure that no group transient is considered transient // for a window that is (directly or indirectly) transient for it // (including another group transients). // Non-group transients not causing loops are checked in verifyTransientFor(). void Client::checkGroupTransients() { for (ClientList::ConstIterator it1 = group()->members().constBegin(); it1 != group()->members().constEnd(); ++it1) { if (!(*it1)->groupTransient()) // check all group transients in the group continue; // TODO optimize to check only the changed ones? for (ClientList::ConstIterator it2 = group()->members().constBegin(); it2 != group()->members().constEnd(); ++it2) { // group transients can be transient only for others in the group, // so don't make them transient for the ones that are transient for it if (*it1 == *it2) continue; for (AbstractClient* cl = (*it2)->transientFor(); - cl != NULL; + cl != nullptr; cl = cl->transientFor()) { if (cl == *it1) { // don't use removeTransient(), that would modify *it2 too (*it2)->removeTransientFromList(*it1); continue; } } // if *it1 and *it2 are both group transients, and are transient for each other, // make only *it2 transient for *it1 (i.e. subwindow), as *it2 came later, // and should be therefore on top of *it1 // TODO This could possibly be optimized, it also requires hasTransient() to check for loops. if ((*it2)->groupTransient() && (*it1)->hasTransient(*it2, true) && (*it2)->hasTransient(*it1, true)) (*it2)->removeTransientFromList(*it1); // if there are already windows W1 and W2, W2 being transient for W1, and group transient W3 // is added, make it transient only for W2, not for W1, because it's already indirectly // transient for it - the indirect transiency actually shouldn't break anything, // but it can lead to exponentially expensive operations (#95231) // TODO this is pretty slow as well for (ClientList::ConstIterator it3 = group()->members().constBegin(); it3 != group()->members().constEnd(); ++it3) { if (*it1 == *it2 || *it2 == *it3 || *it1 == *it3) continue; if ((*it2)->hasTransient(*it1, false) && (*it3)->hasTransient(*it1, false)) { if ((*it2)->hasTransient(*it3, true)) (*it2)->removeTransientFromList(*it1); if ((*it3)->hasTransient(*it2, true)) (*it3)->removeTransientFromList(*it1); } } } } } /** * Check that the window is not transient for itself, and similar nonsense. */ xcb_window_t Client::verifyTransientFor(xcb_window_t new_transient_for, bool set) { xcb_window_t new_property_value = new_transient_for; // make sure splashscreens are shown above all their app's windows, even though // they're in Normal layer if (isSplash() && new_transient_for == XCB_WINDOW_NONE) new_transient_for = rootWindow(); if (new_transient_for == XCB_WINDOW_NONE) { if (set) // sometimes WM_TRANSIENT_FOR is set to None, instead of root window new_property_value = new_transient_for = rootWindow(); else return XCB_WINDOW_NONE; } if (new_transient_for == window()) { // pointing to self // also fix the property itself qCWarning(KWIN_CORE) << "Client " << this << " has WM_TRANSIENT_FOR poiting to itself." ; new_property_value = new_transient_for = rootWindow(); } // The transient_for window may be embedded in another application, // so kwin cannot see it. Try to find the managed client for the // window and fix the transient_for property if possible. xcb_window_t before_search = new_transient_for; while (new_transient_for != XCB_WINDOW_NONE && new_transient_for != rootWindow() && !workspace()->findClient(Predicate::WindowMatch, new_transient_for)) { Xcb::Tree tree(new_transient_for); if (tree.isNull()) { break; } new_transient_for = tree->parent; } if (Client* new_transient_for_client = workspace()->findClient(Predicate::WindowMatch, new_transient_for)) { if (new_transient_for != before_search) { qCDebug(KWIN_CORE) << "Client " << this << " has WM_TRANSIENT_FOR poiting to non-toplevel window " << before_search << ", child of " << new_transient_for_client << ", adjusting."; new_property_value = new_transient_for; // also fix the property } } else new_transient_for = before_search; // nice try // loop detection // group transients cannot cause loops, because they're considered transient only for non-transient // windows in the group int count = 20; xcb_window_t loop_pos = new_transient_for; while (loop_pos != XCB_WINDOW_NONE && loop_pos != rootWindow()) { Client* pos = workspace()->findClient(Predicate::WindowMatch, loop_pos); - if (pos == NULL) + if (pos == nullptr) break; loop_pos = pos->m_transientForId; if (--count == 0 || pos == this) { qCWarning(KWIN_CORE) << "Client " << this << " caused WM_TRANSIENT_FOR loop." ; new_transient_for = rootWindow(); } } if (new_transient_for != rootWindow() - && workspace()->findClient(Predicate::WindowMatch, new_transient_for) == NULL) { + && workspace()->findClient(Predicate::WindowMatch, new_transient_for) == nullptr) { // it's transient for a specific window, but that window is not mapped new_transient_for = rootWindow(); } if (new_property_value != m_originalTransientForId) Xcb::setTransientFor(window(), new_property_value); return new_transient_for; } void Client::addTransient(AbstractClient* cl) { AbstractClient::addTransient(cl); if (workspace()->mostRecentlyActivatedClient() == this && cl->isModal()) check_active_modal = true; // qDebug() << "ADDTRANS:" << this << ":" << cl; // qDebug() << kBacktrace(); // for ( ClientList::ConstIterator it = transients_list.begin(); // it != transients_list.end(); // ++it ) // qDebug() << "AT:" << (*it); } void Client::removeTransient(AbstractClient* cl) { // qDebug() << "REMOVETRANS:" << this << ":" << cl; // qDebug() << kBacktrace(); // cl is transient for this, but this is going away // make cl group transient AbstractClient::removeTransient(cl); if (cl->transientFor() == this) { if (Client *c = dynamic_cast(cl)) { c->m_transientForId = XCB_WINDOW_NONE; c->setTransientFor(nullptr); // SELI // SELI cl->setTransient( rootWindow()); c->setTransient(XCB_WINDOW_NONE); } } } // A new window has been mapped. Check if it's not a mainwindow for this already existing window. void Client::checkTransient(xcb_window_t w) { if (m_originalTransientForId != w) return; w = verifyTransientFor(w, true); setTransient(w); } // returns true if cl is the transient_for window for this client, // or recursively the transient_for window bool Client::hasTransient(const AbstractClient* cl, bool indirect) const { if (const Client *c = dynamic_cast(cl)) { // checkGroupTransients() uses this to break loops, so hasTransient() must detect them ConstClientList set; return hasTransientInternal(c, indirect, set); } return false; } bool Client::hasTransientInternal(const Client* cl, bool indirect, ConstClientList& set) const { if (const Client *t = dynamic_cast(cl->transientFor())) { if (t == this) return true; if (!indirect) return false; if (set.contains(cl)) return false; set.append(cl); return hasTransientInternal(t, indirect, set); } if (!cl->isTransient()) return false; if (group() != cl->group()) return false; // cl is group transient, search from top if (transients().contains(const_cast< Client* >(cl))) return true; if (!indirect) return false; if (set.contains(this)) return false; set.append(this); for (auto it = transients().constBegin(); it != transients().constEnd(); ++it) { const Client *c = qobject_cast(*it); if (!c) { continue; } if (c->hasTransientInternal(cl, indirect, set)) return true; } return false; } QList Client::mainClients() const { if (!isTransient()) return QList(); if (const AbstractClient *t = transientFor()) return QList{const_cast< AbstractClient* >(t)}; QList result; Q_ASSERT(group()); for (ClientList::ConstIterator it = group()->members().constBegin(); it != group()->members().constEnd(); ++it) if ((*it)->hasTransient(this, false)) result.append(*it); return result; } AbstractClient* Client::findModal(bool allow_itself) { for (auto it = transients().constBegin(); it != transients().constEnd(); ++it) if (AbstractClient* ret = (*it)->findModal(true)) return ret; if (isModal() && allow_itself) return this; - return NULL; + return nullptr; } // Client::window_group only holds the contents of the hint, // but it should be used only to find the group, not for anything else // Argument is only when some specific group needs to be set. void Client::checkGroup(Group* set_group, bool force) { Group* old_group = in_group; - if (old_group != NULL) + if (old_group != nullptr) old_group->ref(); // turn off automatic deleting - if (set_group != NULL) { + if (set_group != nullptr) { if (set_group != in_group) { - if (in_group != NULL) + if (in_group != nullptr) in_group->removeMember(this); in_group = set_group; in_group->addMember(this); } } else if (info->groupLeader() != XCB_WINDOW_NONE) { Group* new_group = workspace()->findGroup(info->groupLeader()); Client *t = qobject_cast(transientFor()); - if (t != NULL && t->group() != new_group) { + if (t != nullptr && t->group() != new_group) { // move the window to the right group (e.g. a dialog provided // by different app, but transient for this one, so make it part of that group) new_group = t->group(); } - if (new_group == NULL) // doesn't exist yet + if (new_group == nullptr) // doesn't exist yet new_group = new Group(info->groupLeader()); if (new_group != in_group) { - if (in_group != NULL) + if (in_group != nullptr) in_group->removeMember(this); in_group = new_group; in_group->addMember(this); } } else { if (Client *t = qobject_cast(transientFor())) { // doesn't have window group set, but is transient for something // so make it part of that group Group* new_group = t->group(); if (new_group != in_group) { - if (in_group != NULL) + if (in_group != nullptr) in_group->removeMember(this); in_group = t->group(); in_group->addMember(this); } } else if (groupTransient()) { // group transient which actually doesn't have a group :( // try creating group with other windows with the same client leader Group* new_group = workspace()->findClientLeaderGroup(this); - if (new_group == NULL) + if (new_group == nullptr) new_group = new Group(XCB_WINDOW_NONE); if (new_group != in_group) { - if (in_group != NULL) + if (in_group != nullptr) in_group->removeMember(this); in_group = new_group; in_group->addMember(this); } } else { // Not transient without a group, put it in its client leader group. // This might be stupid if grouping was used for e.g. taskbar grouping // or minimizing together the whole group, but as long as it is used // only for dialogs it's better to keep windows from one app in one group. Group* new_group = workspace()->findClientLeaderGroup(this); - if (in_group != NULL && in_group != new_group) { + if (in_group != nullptr && in_group != new_group) { in_group->removeMember(this); - in_group = NULL; + in_group = nullptr; } - if (new_group == NULL) + if (new_group == nullptr) new_group = new Group(XCB_WINDOW_NONE); if (in_group != new_group) { in_group = new_group; in_group->addMember(this); } } } if (in_group != old_group || force) { for (auto it = transients().constBegin(); it != transients().constEnd(); ) { auto *c = *it; // group transients in the old group are no longer transient for it if (c->groupTransient() && c->group() != group()) { removeTransientFromList(c); it = transients().constBegin(); // restart, just in case something more has changed with the list } else ++it; } if (groupTransient()) { // no longer transient for ones in the old group - if (old_group != NULL) { + if (old_group != nullptr) { for (ClientList::ConstIterator it = old_group->members().constBegin(); it != old_group->members().constEnd(); ++it) (*it)->removeTransient(this); } // and make transient for all in the new group for (ClientList::ConstIterator it = group()->members().constBegin(); it != group()->members().constEnd(); ++it) { if (*it == this) break; // this means the window is only transient for windows mapped before it (*it)->addTransient(this); } } // group transient splashscreens should be transient even for windows // in group mapped later for (ClientList::ConstIterator it = group()->members().constBegin(); it != group()->members().constEnd(); ++it) { if (!(*it)->isSplash()) continue; if (!(*it)->groupTransient()) continue; if (*it == this || hasTransient(*it, true)) // TODO indirect? continue; addTransient(*it); } } - if (old_group != NULL) + if (old_group != nullptr) old_group->deref(); // can be now deleted if empty checkGroupTransients(); checkActiveModal(); workspace()->updateClientLayer(this); } // used by Workspace::findClientLeaderGroup() void Client::changeClientLeaderGroup(Group* gr) { // transientFor() != NULL are in the group of their mainwindow, so keep them there - if (transientFor() != NULL) + if (transientFor() != nullptr) return; // also don't change the group for window which have group set if (info->groupLeader()) return; checkGroup(gr); // change group } bool Client::check_active_modal = false; void Client::checkActiveModal() { // if the active window got new modal transient, activate it. // cannot be done in AddTransient(), because there may temporarily // exist loops, breaking findModal Client* check_modal = dynamic_cast(workspace()->mostRecentlyActivatedClient()); - if (check_modal != NULL && check_modal->check_active_modal) { + if (check_modal != nullptr && check_modal->check_active_modal) { Client* new_modal = dynamic_cast(check_modal->findModal()); - if (new_modal != NULL && new_modal != check_modal) { + if (new_modal != nullptr && new_modal != check_modal) { if (!new_modal->isManaged()) return; // postpone check until end of manage() workspace()->activateClient(new_modal); } check_modal->check_active_modal = false; } } } // namespace diff --git a/input.cpp b/input.cpp index 627da7b35..839e24db2 100644 --- a/input.cpp +++ b/input.cpp @@ -1,2426 +1,2426 @@ /******************************************************************** 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 #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); 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); 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(); 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 = NULL; + 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 NULL; + 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 NULL; + 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); } 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(); 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/kcmkwin/kwincompositing/compositing.h b/kcmkwin/kwincompositing/compositing.h index 0439e81b5..902dd64fe 100644 --- a/kcmkwin/kwincompositing/compositing.h +++ b/kcmkwin/kwincompositing/compositing.h @@ -1,181 +1,181 @@ /************************************************************************** * KWin - the KDE window manager * * This file is part of the KDE project. * * * * Copyright (C) 2013 Antonis Tsiapaliokas * * * * 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 COMPOSITING_H #define COMPOSITING_H #include #include class OrgKdeKwinCompositingInterface; namespace KWin { namespace Compositing { class OpenGLPlatformInterfaceModel; class Compositing : public QObject { Q_OBJECT Q_PROPERTY(int animationSpeed READ animationSpeed WRITE setAnimationSpeed NOTIFY animationSpeedChanged) Q_PROPERTY(int windowThumbnail READ windowThumbnail WRITE setWindowThumbnail NOTIFY windowThumbnailChanged) Q_PROPERTY(int glScaleFilter READ glScaleFilter WRITE setGlScaleFilter NOTIFY glScaleFilterChanged) Q_PROPERTY(bool xrScaleFilter READ xrScaleFilter WRITE setXrScaleFilter NOTIFY xrScaleFilterChanged) Q_PROPERTY(int glSwapStrategy READ glSwapStrategy WRITE setGlSwapStrategy NOTIFY glSwapStrategyChanged) Q_PROPERTY(int compositingType READ compositingType WRITE setCompositingType NOTIFY compositingTypeChanged) Q_PROPERTY(bool compositingEnabled READ compositingEnabled WRITE setCompositingEnabled NOTIFY compositingEnabledChanged) Q_PROPERTY(KWin::Compositing::OpenGLPlatformInterfaceModel *openGLPlatformInterfaceModel READ openGLPlatformInterfaceModel CONSTANT) Q_PROPERTY(int openGLPlatformInterface READ openGLPlatformInterface WRITE setOpenGLPlatformInterface NOTIFY openGLPlatformInterfaceChanged) Q_PROPERTY(bool windowsBlockCompositing READ windowsBlockCompositing WRITE setWindowsBlockCompositing NOTIFY windowsBlockCompositingChanged) Q_PROPERTY(bool compositingRequired READ compositingRequired CONSTANT) public: - explicit Compositing(QObject *parent = 0); + explicit Compositing(QObject *parent = nullptr); Q_INVOKABLE bool OpenGLIsUnsafe() const; Q_INVOKABLE bool OpenGLIsBroken(); Q_INVOKABLE void reenableOpenGLDetection(); int animationSpeed() const; int windowThumbnail() const; int glScaleFilter() const; bool xrScaleFilter() const; int glSwapStrategy() const; int compositingType() const; bool compositingEnabled() const; int openGLPlatformInterface() const; bool windowsBlockCompositing() const; bool compositingRequired() const; OpenGLPlatformInterfaceModel *openGLPlatformInterfaceModel() const; void setAnimationSpeed(int speed); void setWindowThumbnail(int index); void setGlScaleFilter(int index); void setXrScaleFilter(bool filter); void setGlSwapStrategy(int strategy); void setCompositingType(int index); void setCompositingEnabled(bool enalbed); void setOpenGLPlatformInterface(int interface); void setWindowsBlockCompositing(bool set); void save(); public Q_SLOTS: void reset(); void defaults(); Q_SIGNALS: void changed(); void animationSpeedChanged(int); void windowThumbnailChanged(int); void glScaleFilterChanged(int); void xrScaleFilterChanged(int); void glSwapStrategyChanged(int); void compositingTypeChanged(int); void compositingEnabledChanged(bool); void openGLPlatformInterfaceChanged(int); void windowsBlockCompositingChanged(bool); private: int m_animationSpeed; int m_windowThumbnail; int m_glScaleFilter; bool m_xrScaleFilter; int m_glSwapStrategy; int m_compositingType; bool m_compositingEnabled; bool m_changed; OpenGLPlatformInterfaceModel *m_openGLPlatformInterfaceModel; int m_openGLPlatformInterface; bool m_windowsBlockCompositing; bool m_windowsBlockingCompositing; OrgKdeKwinCompositingInterface *m_compositingInterface; }; struct CompositingData; class CompositingType : public QAbstractItemModel { Q_OBJECT Q_ENUMS(CompositingTypeIndex) public: enum CompositingTypeIndex { OPENGL31_INDEX = 0, OPENGL20_INDEX, XRENDER_INDEX }; enum CompositingTypeRoles { NameRole = Qt::UserRole +1, TypeRole = Qt::UserRole +2 }; - explicit CompositingType(QObject *parent = 0); + explicit CompositingType(QObject *parent = nullptr); QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override; QModelIndex parent(const QModelIndex &child) const override; int rowCount(const QModelIndex &parent = QModelIndex()) const override; int columnCount(const QModelIndex &parent = QModelIndex()) const override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; QHash< int, QByteArray > roleNames() const override; Q_INVOKABLE int compositingTypeForIndex(int row) const; Q_INVOKABLE int indexForCompositingType(int type) const; private: void generateCompositing(); QList m_compositingList; }; struct CompositingData { QString name; CompositingType::CompositingTypeIndex type; }; class OpenGLPlatformInterfaceModel : public QAbstractListModel { Q_OBJECT public: explicit OpenGLPlatformInterfaceModel(QObject *parent = nullptr); ~OpenGLPlatformInterfaceModel() override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; int rowCount(const QModelIndex &parent = QModelIndex()) const override; QModelIndex indexForKey(const QString &key) const; QHash< int, QByteArray > roleNames() const override; private: QStringList m_keys; QStringList m_names; }; }//end namespace Compositing }//end namespace KWin Q_DECLARE_METATYPE(KWin::Compositing::OpenGLPlatformInterfaceModel*) #endif diff --git a/kcmkwin/kwincompositing/main.cpp b/kcmkwin/kwincompositing/main.cpp index 81f38ac92..1412776a5 100644 --- a/kcmkwin/kwincompositing/main.cpp +++ b/kcmkwin/kwincompositing/main.cpp @@ -1,209 +1,209 @@ /************************************************************************** * KWin - the KDE window manager * * This file is part of the KDE project. * * * * Copyright (C) 2013 Antonis Tsiapaliokas * * 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 "compositing.h" #include "ui_compositing.h" #include #include #include #include #include class KWinCompositingSettings : public KCModule { Q_OBJECT public: - explicit KWinCompositingSettings(QWidget *parent = 0, const QVariantList &args = QVariantList()); + explicit KWinCompositingSettings(QWidget *parent = nullptr, const QVariantList &args = QVariantList()); public Q_SLOTS: void load() override; void save() override; void defaults() override; private: void init(); KWin::Compositing::Compositing *m_compositing; Ui_CompositingForm m_form; }; KWinCompositingSettings::KWinCompositingSettings(QWidget *parent, const QVariantList &args) : KCModule(parent, args) , m_compositing(new KWin::Compositing::Compositing(this)) { m_form.setupUi(this); m_form.glCrashedWarning->setIcon(QIcon::fromTheme(QStringLiteral("dialog-warning"))); QAction *reenableGLAction = new QAction(i18n("Re-enable OpenGL detection"), this); connect(reenableGLAction, &QAction::triggered, m_compositing, &KWin::Compositing::Compositing::reenableOpenGLDetection); connect(reenableGLAction, &QAction::triggered, m_form.glCrashedWarning, &KMessageWidget::animatedHide); m_form.glCrashedWarning->addAction(reenableGLAction); m_form.scaleWarning->setIcon(QIcon::fromTheme(QStringLiteral("dialog-warning"))); m_form.tearingWarning->setIcon(QIcon::fromTheme(QStringLiteral("dialog-warning"))); m_form.windowThumbnailWarning->setIcon(QIcon::fromTheme(QStringLiteral("dialog-warning"))); m_form.compositingEnabled->setVisible(!m_compositing->compositingRequired()); m_form.windowsBlockCompositing->setVisible(!m_compositing->compositingRequired()); init(); } void KWinCompositingSettings::init() { using namespace KWin::Compositing; auto currentIndexChangedSignal = static_cast(&QComboBox::currentIndexChanged); connect(m_compositing, &Compositing::changed, this, static_cast(&KWinCompositingSettings::changed)); // enabled check box m_form.compositingEnabled->setChecked(m_compositing->compositingEnabled()); connect(m_compositing, &Compositing::compositingEnabledChanged, m_form.compositingEnabled, &QCheckBox::setChecked); connect(m_form.compositingEnabled, &QCheckBox::toggled, m_compositing, &Compositing::setCompositingEnabled); // animation speed m_form.animationSpeed->setValue(m_compositing->animationSpeed()); connect(m_compositing, &Compositing::animationSpeedChanged, m_form.animationSpeed, &QSlider::setValue); connect(m_form.animationSpeed, &QSlider::valueChanged, m_compositing, &Compositing::setAnimationSpeed); // gl scale filter m_form.glScaleFilter->setCurrentIndex(m_compositing->glScaleFilter()); connect(m_compositing, &Compositing::glScaleFilterChanged, m_form.glScaleFilter, &QComboBox::setCurrentIndex); connect(m_form.glScaleFilter, currentIndexChangedSignal, m_compositing, &Compositing::setGlScaleFilter); connect(m_form.glScaleFilter, currentIndexChangedSignal, [this](int index) { if (index == 2) { m_form.scaleWarning->animatedShow(); } else { m_form.scaleWarning->animatedHide(); } } ); // xrender scale filter m_form.xrScaleFilter->setCurrentIndex(m_compositing->xrScaleFilter()); connect(m_compositing, &Compositing::xrScaleFilterChanged, m_form.xrScaleFilter, &QComboBox::setCurrentIndex); connect(m_form.xrScaleFilter, currentIndexChangedSignal, [this](int index) { if (index == 0) { m_compositing->setXrScaleFilter(false); } else { m_compositing->setXrScaleFilter(true); } }); // tearing prevention m_form.tearingPrevention->setCurrentIndex(m_compositing->glSwapStrategy()); connect(m_compositing, &Compositing::glSwapStrategyChanged, m_form.tearingPrevention, &QComboBox::setCurrentIndex); connect(m_form.tearingPrevention, currentIndexChangedSignal, m_compositing, &Compositing::setGlSwapStrategy); connect(m_form.tearingPrevention, currentIndexChangedSignal, [this](int index) { if (index == 2) { // only when cheap - tearing m_form.tearingWarning->setText(i18n("\"Only when cheap\" only prevents tearing for full screen changes like a video.")); m_form.tearingWarning->animatedShow(); } else if (index == 3) { // full screen repaints m_form.tearingWarning->setText(i18n("\"Full screen repaints\" can cause performance problems.")); m_form.tearingWarning->animatedShow(); } else if (index == 4) { // re-use screen content m_form.tearingWarning->setText(i18n("\"Re-use screen content\" causes severe performance problems on MESA drivers.")); m_form.tearingWarning->animatedShow(); } else { m_form.tearingWarning->animatedHide(); } } ); // windowThumbnail m_form.windowThumbnail->setCurrentIndex(m_compositing->windowThumbnail()); connect(m_compositing, &Compositing::windowThumbnailChanged, m_form.windowThumbnail, &QComboBox::setCurrentIndex); connect(m_form.windowThumbnail, currentIndexChangedSignal, m_compositing, &Compositing::setWindowThumbnail); connect(m_form.windowThumbnail, currentIndexChangedSignal, [this](int index) { if (index == 2) { m_form.windowThumbnailWarning->animatedShow(); } else { m_form.windowThumbnailWarning->animatedHide(); } } ); // windows blocking compositing m_form.windowsBlockCompositing->setChecked(m_compositing->windowsBlockCompositing()); connect(m_compositing, &Compositing::windowsBlockCompositingChanged, m_form.windowsBlockCompositing, &QCheckBox::setChecked); connect(m_form.windowsBlockCompositing, &QCheckBox::toggled, m_compositing, &Compositing::setWindowsBlockCompositing); // compositing type CompositingType *type = new CompositingType(this); m_form.type->setModel(type); auto updateCompositingType = [this, type]() { m_form.type->setCurrentIndex(type->indexForCompositingType(m_compositing->compositingType())); }; updateCompositingType(); connect(m_compositing, &Compositing::compositingTypeChanged, [updateCompositingType]() { updateCompositingType(); } ); auto showHideBasedOnType = [this, type]() { const int currentType = type->compositingTypeForIndex(m_form.type->currentIndex()); m_form.glScaleFilter->setVisible(currentType != CompositingType::XRENDER_INDEX); m_form.glScaleFilterLabel->setVisible(currentType != CompositingType::XRENDER_INDEX); m_form.xrScaleFilter->setVisible(currentType == CompositingType::XRENDER_INDEX); m_form.xrScaleFilterLabel->setVisible(currentType == CompositingType::XRENDER_INDEX); }; showHideBasedOnType(); connect(m_form.type, currentIndexChangedSignal, [this, type, showHideBasedOnType]() { m_compositing->setCompositingType(type->compositingTypeForIndex(m_form.type->currentIndex())); showHideBasedOnType(); } ); if (m_compositing->OpenGLIsUnsafe()) { m_form.glCrashedWarning->animatedShow(); } } void KWinCompositingSettings::load() { KCModule::load(); m_compositing->reset(); } void KWinCompositingSettings::defaults() { KCModule::defaults(); m_compositing->defaults(); } void KWinCompositingSettings::save() { KCModule::save(); m_compositing->save(); } K_PLUGIN_FACTORY(KWinCompositingConfigFactory, registerPlugin("compositing"); ) #include "main.moc" diff --git a/kcmkwin/kwindecoration/declarative-plugin/buttonsmodel.h b/kcmkwin/kwindecoration/declarative-plugin/buttonsmodel.h index 9c19faf5f..ae0a1c2af 100644 --- a/kcmkwin/kwindecoration/declarative-plugin/buttonsmodel.h +++ b/kcmkwin/kwindecoration/declarative-plugin/buttonsmodel.h @@ -1,66 +1,66 @@ /* * Copyright 2014 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) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * 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 KDECOARTIONS_PREVIEW_BUTTONS_MODEL_H #define KDECOARTIONS_PREVIEW_BUTTONS_MODEL_H #include #include namespace KDecoration2 { namespace Preview { class PreviewBridge; class ButtonsModel : public QAbstractListModel { Q_OBJECT public: - explicit ButtonsModel(const QVector< DecorationButtonType > &buttons, QObject *parent = 0); + explicit ButtonsModel(const QVector< DecorationButtonType > &buttons, QObject *parent = nullptr); explicit ButtonsModel(QObject *parent = nullptr); ~ButtonsModel() override; QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; int rowCount(const QModelIndex& parent = QModelIndex()) const override; QHash< int, QByteArray > roleNames() const override; QVector< DecorationButtonType > buttons() const { return m_buttons; } Q_INVOKABLE void clear(); Q_INVOKABLE void remove(int index); Q_INVOKABLE void up(int index); Q_INVOKABLE void down(int index); Q_INVOKABLE void move(int sourceIndex, int targetIndex); void replace(const QVector< DecorationButtonType > &buttons); void add(DecorationButtonType type); Q_INVOKABLE void add(int index, int type); private: QVector< DecorationButtonType > m_buttons; }; } } #endif diff --git a/kcmkwin/kwindecoration/declarative-plugin/previewitem.cpp b/kcmkwin/kwindecoration/declarative-plugin/previewitem.cpp index d31c4dd3b..616b88ae2 100644 --- a/kcmkwin/kwindecoration/declarative-plugin/previewitem.cpp +++ b/kcmkwin/kwindecoration/declarative-plugin/previewitem.cpp @@ -1,468 +1,468 @@ /* * Copyright 2014 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) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * 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 "previewitem.h" #include "previewbridge.h" #include "previewsettings.h" #include "previewclient.h" #include #include #include #include #include #include #include #include #include #include #include namespace KDecoration2 { namespace Preview { PreviewItem::PreviewItem(QQuickItem *parent) : QQuickPaintedItem(parent) , m_decoration(nullptr) , m_windowColor(QPalette().window().color()) { setAcceptHoverEvents(true); setFiltersChildMouseEvents(true); setAcceptedMouseButtons(Qt::MouseButtons(~Qt::NoButton)); connect(this, &PreviewItem::widthChanged, this, &PreviewItem::syncSize); connect(this, &PreviewItem::heightChanged, this, &PreviewItem::syncSize); connect(this, &PreviewItem::bridgeChanged, this, &PreviewItem::createDecoration); connect(this, &PreviewItem::settingsChanged, this, &PreviewItem::createDecoration); } PreviewItem::~PreviewItem() { m_decoration->deleteLater(); if (m_bridge){ m_bridge->unregisterPreviewItem(this); } } void PreviewItem::componentComplete() { QQuickPaintedItem::componentComplete(); createDecoration(); if (m_decoration) { m_decoration->setSettings(m_settings->settings()); m_decoration->init(); syncSize(); } } void PreviewItem::createDecoration() { if (m_bridge.isNull() || m_settings.isNull() || m_decoration) { return; } - Decoration *decoration = m_bridge->createDecoration(0); + Decoration *decoration = m_bridge->createDecoration(nullptr); m_client = m_bridge->lastCreatedClient(); setDecoration(decoration); } Decoration *PreviewItem::decoration() const { return m_decoration; } void PreviewItem::setDecoration(Decoration *deco) { if (!deco || m_decoration == deco) { return; } m_decoration = deco; m_decoration->setProperty("visualParent", QVariant::fromValue(this)); connect(m_decoration, &Decoration::bordersChanged, this, &PreviewItem::syncSize); connect(m_decoration, &Decoration::shadowChanged, this, &PreviewItem::syncSize); connect(m_decoration, &Decoration::shadowChanged, this, &PreviewItem::shadowChanged); emit decorationChanged(m_decoration); } QColor PreviewItem::windowColor() const { return m_windowColor; } void PreviewItem::setWindowColor(const QColor &color) { if (m_windowColor == color) { return; } m_windowColor = color; emit windowColorChanged(m_windowColor); update(); } void PreviewItem::paint(QPainter *painter) { if (!m_decoration) { return; } int paddingLeft = 0; int paddingTop = 0; int paddingRight = 0; int paddingBottom = 0; paintShadow(painter, paddingLeft, paddingRight, paddingTop, paddingBottom); m_decoration->paint(painter, QRect(0, 0, width(), height())); if (m_drawBackground) { painter->fillRect(m_decoration->borderLeft(), m_decoration->borderTop(), width() - m_decoration->borderLeft() - m_decoration->borderRight() - paddingLeft - paddingRight, height() - m_decoration->borderTop() - m_decoration->borderBottom() - paddingTop - paddingBottom, m_windowColor); } } void PreviewItem::paintShadow(QPainter *painter, int &paddingLeft, int &paddingRight, int &paddingTop, int &paddingBottom) { const auto &shadow = ((const Decoration*)(m_decoration))->shadow(); if (!shadow) { return; } paddingLeft = shadow->paddingLeft(); paddingTop = shadow->paddingTop(); paddingRight = shadow->paddingRight(); paddingBottom = shadow->paddingBottom(); const QImage shadowPixmap = shadow->shadow(); if (shadowPixmap.isNull()) { return; } const QRect outerRect(-paddingLeft, -paddingTop, width(), height()); const QRect shadowRect(shadowPixmap.rect()); const QSize topLeftSize(shadow->topLeftGeometry().size()); QRect topLeftTarget( QPoint(outerRect.x(), outerRect.y()), topLeftSize); const QSize topRightSize(shadow->topRightGeometry().size()); QRect topRightTarget( QPoint(outerRect.x() + outerRect.width() - topRightSize.width(), outerRect.y()), topRightSize); const QSize bottomRightSize(shadow->bottomRightGeometry().size()); QRect bottomRightTarget( QPoint(outerRect.x() + outerRect.width() - bottomRightSize.width(), outerRect.y() + outerRect.height() - bottomRightSize.height()), bottomRightSize); const QSize bottomLeftSize(shadow->bottomLeftGeometry().size()); QRect bottomLeftTarget( QPoint(outerRect.x(), outerRect.y() + outerRect.height() - bottomLeftSize.height()), bottomLeftSize); // 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 (topLeftTarget.x() + topLeftTarget.width() >= topRightTarget.x()) { const float halfOverlap = qAbs(topLeftTarget.x() + topLeftTarget.width() - topRightTarget.x()) / 2.0f; topLeftTarget.setRight(topLeftTarget.right() - std::floor(halfOverlap)); topRightTarget.setLeft(topRightTarget.left() + std::ceil(halfOverlap)); drawTop = false; } bool drawRight = true; if (topRightTarget.y() + topRightTarget.height() >= bottomRightTarget.y()) { const float halfOverlap = qAbs(topRightTarget.y() + topRightTarget.height() - bottomRightTarget.y()) / 2.0f; topRightTarget.setBottom(topRightTarget.bottom() - std::floor(halfOverlap)); bottomRightTarget.setTop(bottomRightTarget.top() + std::ceil(halfOverlap)); drawRight = false; } bool drawBottom = true; if (bottomLeftTarget.x() + bottomLeftTarget.width() >= bottomRightTarget.x()) { const float halfOverlap = qAbs(bottomLeftTarget.x() + bottomLeftTarget.width() - bottomRightTarget.x()) / 2.0f; bottomLeftTarget.setRight(bottomLeftTarget.right() - std::floor(halfOverlap)); bottomRightTarget.setLeft(bottomRightTarget.left() + std::ceil(halfOverlap)); drawBottom = false; } bool drawLeft = true; if (topLeftTarget.y() + topLeftTarget.height() >= bottomLeftTarget.y()) { const float halfOverlap = qAbs(topLeftTarget.y() + topLeftTarget.height() - bottomLeftTarget.y()) / 2.0f; topLeftTarget.setBottom(topLeftTarget.bottom() - std::floor(halfOverlap)); bottomLeftTarget.setTop(bottomLeftTarget.top() + std::ceil(halfOverlap)); drawLeft = false; } painter->translate(paddingLeft, paddingTop); painter->drawImage(topLeftTarget, shadowPixmap, QRect(QPoint(0, 0), topLeftTarget.size())); painter->drawImage(topRightTarget, shadowPixmap, QRect(QPoint(shadowRect.width() - topRightTarget.width(), 0), topRightTarget.size())); painter->drawImage(bottomRightTarget, shadowPixmap, QRect(QPoint(shadowRect.width() - bottomRightTarget.width(), shadowRect.height() - bottomRightTarget.height()), bottomRightTarget.size())); painter->drawImage(bottomLeftTarget, shadowPixmap, QRect(QPoint(0, shadowRect.height() - bottomLeftTarget.height()), bottomLeftTarget.size())); if (drawTop) { QRect topTarget(topLeftTarget.x() + topLeftTarget.width(), topLeftTarget.y(), topRightTarget.x() - topLeftTarget.x() - topLeftTarget.width(), topRightTarget.height()); QRect topSource(shadow->topGeometry()); topSource.setHeight(topTarget.height()); topSource.moveTop(shadowRect.top()); painter->drawImage(topTarget, shadowPixmap, topSource); } if (drawRight) { QRect rightTarget(topRightTarget.x(), topRightTarget.y() + topRightTarget.height(), topRightTarget.width(), bottomRightTarget.y() - topRightTarget.y() - topRightTarget.height()); QRect rightSource(shadow->rightGeometry()); rightSource.setWidth(rightTarget.width()); rightSource.moveRight(shadowRect.right()); painter->drawImage(rightTarget, shadowPixmap, rightSource); } if (drawBottom) { QRect bottomTarget(bottomLeftTarget.x() + bottomLeftTarget.width(), bottomLeftTarget.y(), bottomRightTarget.x() - bottomLeftTarget.x() - bottomLeftTarget.width(), bottomRightTarget.height()); QRect bottomSource(shadow->bottomGeometry()); bottomSource.setHeight(bottomTarget.height()); bottomSource.moveBottom(shadowRect.bottom()); painter->drawImage(bottomTarget, shadowPixmap, bottomSource); } if (drawLeft) { QRect leftTarget(topLeftTarget.x(), topLeftTarget.y() + topLeftTarget.height(), topLeftTarget.width(), bottomLeftTarget.y() - topLeftTarget.y() - topLeftTarget.height()); QRect leftSource(shadow->leftGeometry()); leftSource.setWidth(leftTarget.width()); leftSource.moveLeft(shadowRect.left()); painter->drawImage(leftTarget, shadowPixmap, leftSource); } } void PreviewItem::mouseDoubleClickEvent(QMouseEvent *event) { const auto &shadow = m_decoration->shadow(); if (shadow) { QMouseEvent e(event->type(), event->localPos() - QPointF(shadow->paddingLeft(), shadow->paddingTop()), event->button(), event->buttons(), event->modifiers()); QCoreApplication::instance()->sendEvent(decoration(), &e); } else { QCoreApplication::instance()->sendEvent(decoration(), event); } } void PreviewItem::mousePressEvent(QMouseEvent *event) { const auto &shadow = m_decoration->shadow(); if (shadow) { QMouseEvent e(event->type(), event->localPos() - QPointF(shadow->paddingLeft(), shadow->paddingTop()), event->button(), event->buttons(), event->modifiers()); QCoreApplication::instance()->sendEvent(decoration(), &e); } else { QCoreApplication::instance()->sendEvent(decoration(), event); } } void PreviewItem::mouseReleaseEvent(QMouseEvent *event) { const auto &shadow = m_decoration->shadow(); if (shadow) { QMouseEvent e(event->type(), event->localPos() - QPointF(shadow->paddingLeft(), shadow->paddingTop()), event->button(), event->buttons(), event->modifiers()); QCoreApplication::instance()->sendEvent(decoration(), &e); } else { QCoreApplication::instance()->sendEvent(decoration(), event); } } void PreviewItem::mouseMoveEvent(QMouseEvent *event) { const auto &shadow = m_decoration->shadow(); if (shadow) { QMouseEvent e(event->type(), event->localPos() - QPointF(shadow->paddingLeft(), shadow->paddingTop()), event->button(), event->buttons(), event->modifiers()); QCoreApplication::instance()->sendEvent(decoration(), &e); } else { QCoreApplication::instance()->sendEvent(decoration(), event); } } void PreviewItem::hoverEnterEvent(QHoverEvent *event) { const auto &shadow = m_decoration->shadow(); if (shadow) { QHoverEvent e(event->type(), event->posF() - QPointF(shadow->paddingLeft(), shadow->paddingTop()), event->oldPosF() - QPointF(shadow->paddingLeft(), shadow->paddingTop()), event->modifiers()); QCoreApplication::instance()->sendEvent(decoration(), &e); } else { QCoreApplication::instance()->sendEvent(decoration(), event); } } void PreviewItem::hoverLeaveEvent(QHoverEvent *event) { const auto &shadow = m_decoration->shadow(); if (shadow) { QHoverEvent e(event->type(), event->posF() - QPointF(shadow->paddingLeft(), shadow->paddingTop()), event->oldPosF() - QPointF(shadow->paddingLeft(), shadow->paddingTop()), event->modifiers()); QCoreApplication::instance()->sendEvent(decoration(), &e); } else { QCoreApplication::instance()->sendEvent(decoration(), event); } } void PreviewItem::hoverMoveEvent(QHoverEvent *event) { const auto &shadow = m_decoration->shadow(); if (shadow) { QHoverEvent e(event->type(), event->posF() - QPointF(shadow->paddingLeft(), shadow->paddingTop()), event->oldPosF() - QPointF(shadow->paddingLeft(), shadow->paddingTop()), event->modifiers()); QCoreApplication::instance()->sendEvent(decoration(), &e); } else { QCoreApplication::instance()->sendEvent(decoration(), event); } } bool PreviewItem::isDrawingBackground() const { return m_drawBackground; } void PreviewItem::setDrawingBackground(bool set) { if (m_drawBackground == set) { return; } m_drawBackground = set; emit drawingBackgroundChanged(set); } PreviewBridge *PreviewItem::bridge() const { return m_bridge.data(); } void PreviewItem::setBridge(PreviewBridge *bridge) { if (m_bridge == bridge) { return; } if (m_bridge) { m_bridge->unregisterPreviewItem(this); } m_bridge = bridge; if (m_bridge) { m_bridge->registerPreviewItem(this); } emit bridgeChanged(); } Settings *PreviewItem::settings() const { return m_settings.data(); } void PreviewItem::setSettings(Settings *settings) { if (m_settings == settings) { return; } m_settings = settings; emit settingsChanged(); } PreviewClient *PreviewItem::client() { return m_client.data(); } void PreviewItem::syncSize() { if (!m_client) { return; } int widthOffset = 0; int heightOffset = 0; auto shadow = m_decoration->shadow(); if (shadow) { widthOffset = shadow->paddingLeft() + shadow->paddingRight(); heightOffset = shadow->paddingTop() + shadow->paddingBottom(); } m_client->setWidth(width() - m_decoration->borderLeft() - m_decoration->borderRight() - widthOffset); m_client->setHeight(height() - m_decoration->borderTop() - m_decoration->borderBottom() - heightOffset); } DecorationShadow *PreviewItem::shadow() const { if (!m_decoration) { return nullptr; } const auto &s = m_decoration->shadow(); if (!s) { return nullptr; } return s.data(); } } } diff --git a/kcmkwin/kwindecoration/declarative-plugin/previewsettings.h b/kcmkwin/kwindecoration/declarative-plugin/previewsettings.h index f206e79ff..abace01eb 100644 --- a/kcmkwin/kwindecoration/declarative-plugin/previewsettings.h +++ b/kcmkwin/kwindecoration/declarative-plugin/previewsettings.h @@ -1,163 +1,163 @@ /* * Copyright 2014 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) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * 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 KDECOARTIONS_PREVIEW_SETTINGS_H #define KDECOARTIONS_PREVIEW_SETTINGS_H #include #include #include #include namespace KDecoration2 { namespace Preview { class ButtonsModel; class PreviewBridge; class BorderSizesModel : public QAbstractListModel { Q_OBJECT public: - explicit BorderSizesModel(QObject *parent = 0); + explicit BorderSizesModel(QObject *parent = nullptr); ~BorderSizesModel() override; QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; int rowCount(const QModelIndex& parent = QModelIndex()) const override; QHash< int, QByteArray > roleNames() const override; private: QList m_borders = QList({ BorderSize::None, BorderSize::NoSides, BorderSize::Tiny, BorderSize::Normal, BorderSize::Large, BorderSize::VeryLarge, BorderSize::Huge, BorderSize::VeryHuge, BorderSize::Oversized }); }; class PreviewSettings : public QObject, public DecorationSettingsPrivate { Q_OBJECT Q_PROPERTY(bool onAllDesktopsAvailable READ isOnAllDesktopsAvailable WRITE setOnAllDesktopsAvailable NOTIFY onAllDesktopsAvailableChanged) Q_PROPERTY(bool alphaChannelSupported READ isAlphaChannelSupported WRITE setAlphaChannelSupported NOTIFY alphaChannelSupportedChanged) Q_PROPERTY(bool closeOnDoubleClickOnMenu READ isCloseOnDoubleClickOnMenu WRITE setCloseOnDoubleClickOnMenu NOTIFY closeOnDoubleClickOnMenuChanged) Q_PROPERTY(QAbstractItemModel *leftButtonsModel READ leftButtonsModel CONSTANT) Q_PROPERTY(QAbstractItemModel *rightButtonsModel READ rightButtonsModel CONSTANT) Q_PROPERTY(QAbstractItemModel *availableButtonsModel READ availableButtonsModel CONSTANT) Q_PROPERTY(QAbstractItemModel *borderSizesModel READ borderSizesModel CONSTANT) Q_PROPERTY(int borderSizesIndex READ borderSizesIndex WRITE setBorderSizesIndex NOTIFY borderSizesIndexChanged) Q_PROPERTY(QFont font READ font WRITE setFont NOTIFY fontChanged) public: explicit PreviewSettings(DecorationSettings *parent); ~PreviewSettings() override; bool isAlphaChannelSupported() const override; bool isOnAllDesktopsAvailable() const override; bool isCloseOnDoubleClickOnMenu() const override { return m_closeOnDoubleClick; } BorderSize borderSize() const override; void setOnAllDesktopsAvailable(bool available); void setAlphaChannelSupported(bool supported); void setCloseOnDoubleClickOnMenu(bool enabled); QAbstractItemModel *leftButtonsModel() const; QAbstractItemModel *rightButtonsModel() const; QAbstractItemModel *availableButtonsModel() const; QAbstractItemModel *borderSizesModel() const { return m_borderSizes; } QVector< DecorationButtonType > decorationButtonsLeft() const override; QVector< DecorationButtonType > decorationButtonsRight() const override; Q_INVOKABLE void addButtonToLeft(int row); Q_INVOKABLE void addButtonToRight(int row); int borderSizesIndex() const { return m_borderSize; } void setBorderSizesIndex(int index); QFont font() const override { return m_font; } void setFont(const QFont &font); Q_SIGNALS: void onAllDesktopsAvailableChanged(bool); void alphaChannelSupportedChanged(bool); void closeOnDoubleClickOnMenuChanged(bool); void borderSizesIndexChanged(int); void fontChanged(const QFont &); private: bool m_alphaChannelSupported; bool m_onAllDesktopsAvailable; bool m_closeOnDoubleClick; ButtonsModel *m_leftButtons; ButtonsModel *m_rightButtons; ButtonsModel *m_availableButtons; BorderSizesModel *m_borderSizes; int m_borderSize; QFont m_font; }; class Settings : public QObject { Q_OBJECT Q_PROPERTY(KDecoration2::Preview::PreviewBridge *bridge READ bridge WRITE setBridge NOTIFY bridgeChanged) Q_PROPERTY(KDecoration2::DecorationSettings *settings READ settingsPointer NOTIFY settingsChanged) Q_PROPERTY(int borderSizesIndex READ borderSizesIndex WRITE setBorderSizesIndex NOTIFY borderSizesIndexChanged) public: explicit Settings(QObject *parent = nullptr); ~Settings() override; PreviewBridge *bridge() const; void setBridge(PreviewBridge *bridge); QSharedPointer settings() const; DecorationSettings *settingsPointer() const; int borderSizesIndex() const { return m_borderSize; } void setBorderSizesIndex(int index); Q_SIGNALS: void bridgeChanged(); void settingsChanged(); void borderSizesIndexChanged(int); private: void createSettings(); QPointer m_bridge; QSharedPointer m_settings; PreviewSettings *m_previewSettings = nullptr; int m_borderSize = 3; }; } } #endif diff --git a/kcmkwin/kwinoptions/mouse.cpp b/kcmkwin/kwinoptions/mouse.cpp index 90ac7faa4..10626f88b 100644 --- a/kcmkwin/kwinoptions/mouse.cpp +++ b/kcmkwin/kwinoptions/mouse.cpp @@ -1,562 +1,562 @@ /* * * Copyright (c) 1998 Matthias Ettrich * * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "mouse.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace { char const * const cnf_Max[] = { "MaximizeButtonLeftClickCommand", "MaximizeButtonMiddleClickCommand", "MaximizeButtonRightClickCommand", }; char const * const tbl_Max[] = { "Maximize", "Maximize (vertical only)", "Maximize (horizontal only)", "" }; QPixmap maxButtonPixmaps[3]; void createMaxButtonPixmaps() { char const * maxButtonXpms[][3 + 13] = { { - 0, 0, 0, + nullptr, nullptr, nullptr, "...............", ".......#.......", "......###......", ".....#####.....", "..#....#....#..", ".##....#....##.", "###############", ".##....#....##.", "..#....#....#..", ".....#####.....", "......###......", ".......#.......", "..............." }, { - 0, 0, 0, + nullptr, nullptr, nullptr, "...............", ".......#.......", "......###......", ".....#####.....", ".......#.......", ".......#.......", ".......#.......", ".......#.......", ".......#.......", ".....#####.....", "......###......", ".......#.......", "..............." }, { - 0, 0, 0, + nullptr, nullptr, nullptr, "...............", "...............", "...............", "...............", "..#.........#..", ".##.........##.", "###############", ".##.........##.", "..#.........#..", "...............", "...............", "...............", "..............." }, }; QByteArray baseColor(". c " + KColorScheme(QPalette::Active, KColorScheme::View).background().color().name().toLatin1()); QByteArray textColor("# c " + KColorScheme(QPalette::Active, KColorScheme::View).foreground().color().name().toLatin1()); for (int t = 0; t < 3; ++t) { maxButtonXpms[t][0] = "15 13 2 1"; maxButtonXpms[t][1] = baseColor.constData(); maxButtonXpms[t][2] = textColor.constData(); maxButtonPixmaps[t] = QPixmap(maxButtonXpms[t]); maxButtonPixmaps[t].setMask(maxButtonPixmaps[t].createHeuristicMask()); } } } // namespace KWinMouseConfigForm::KWinMouseConfigForm(QWidget *parent) : QWidget(parent) { setupUi(parent); } KWinActionsConfigForm::KWinActionsConfigForm(QWidget *parent) : QWidget(parent) { setupUi(parent); } void KTitleBarActionsConfig::paletteChanged() { createMaxButtonPixmaps(); for (int i=0; i<3; ++i) { m_ui->leftClickMaximizeButton->setItemIcon(i, maxButtonPixmaps[i]); m_ui->middleClickMaximizeButton->setItemIcon(i, maxButtonPixmaps[i]); m_ui->rightClickMaximizeButton->setItemIcon(i, maxButtonPixmaps[i]); } } KTitleBarActionsConfig::KTitleBarActionsConfig(bool _standAlone, KConfig *_config, QWidget * parent) : KCModule(parent), config(_config), standAlone(_standAlone) , m_ui(new KWinMouseConfigForm(this)) { // create the items for the maximize button actions createMaxButtonPixmaps(); for (int i=0; i<3; ++i) { m_ui->leftClickMaximizeButton->addItem(maxButtonPixmaps[i], QString()); m_ui->middleClickMaximizeButton->addItem(maxButtonPixmaps[i], QString()); m_ui->rightClickMaximizeButton->addItem(maxButtonPixmaps[i], QString()); } createMaximizeButtonTooltips(m_ui->leftClickMaximizeButton); createMaximizeButtonTooltips(m_ui->middleClickMaximizeButton); createMaximizeButtonTooltips(m_ui->rightClickMaximizeButton); connect(m_ui->coTiDbl, SIGNAL(activated(int)), SLOT(changed())); connect(m_ui->coTiAct1, SIGNAL(activated(int)), SLOT(changed())); connect(m_ui->coTiAct2, SIGNAL(activated(int)), SLOT(changed())); connect(m_ui->coTiAct3, SIGNAL(activated(int)), SLOT(changed())); connect(m_ui->coTiAct4, SIGNAL(activated(int)), SLOT(changed())); connect(m_ui->coTiInAct1, SIGNAL(activated(int)), SLOT(changed())); connect(m_ui->coTiInAct2, SIGNAL(activated(int)), SLOT(changed())); connect(m_ui->coTiInAct3, SIGNAL(activated(int)), SLOT(changed())); connect(m_ui->leftClickMaximizeButton, SIGNAL(activated(int)), SLOT(changed())); connect(m_ui->middleClickMaximizeButton, SIGNAL(activated(int)), SLOT(changed())); connect(m_ui->rightClickMaximizeButton, SIGNAL(activated(int)), SLOT(changed())); load(); } KTitleBarActionsConfig::~KTitleBarActionsConfig() { if (standAlone) delete config; } void KTitleBarActionsConfig::createMaximizeButtonTooltips(KComboBox *combo) { combo->setItemData(0, i18n("Maximize"), Qt::ToolTipRole); combo->setItemData(1, i18n("Maximize (vertical only)"), Qt::ToolTipRole); combo->setItemData(2, i18n("Maximize (horizontal only)"), Qt::ToolTipRole); } // do NOT change the texts below, they are written to config file // and are not shown in the GUI // they have to match the order of items in GUI elements though const char* const tbl_TiDbl[] = { "Maximize", "Maximize (vertical only)", "Maximize (horizontal only)", "Minimize", "Shade", "Lower", "Close", "OnAllDesktops", "Nothing", "" }; const char* const tbl_TiAc[] = { "Raise", "Lower", "Toggle raise and lower", "Minimize", "Shade", "Close", "Operations menu", "Nothing", "" }; const char* const tbl_TiInAc[] = { "Activate and raise", "Activate and lower", "Activate", "Raise", "Lower", "Toggle raise and lower", "Minimize", "Shade", "Close", "Operations menu", "Nothing", "" }; const char* const tbl_Win[] = { "Activate, raise and pass click", "Activate and pass click", "Activate", "Activate and raise", "" }; const char* const tbl_WinWheel[] = { "Scroll", "Activate and scroll", "Activate, raise and scroll", "" }; const char* const tbl_AllKey[] = { "Alt", "Meta", "" }; const char* const tbl_All[] = { "Move", "Activate, raise and move", "Toggle raise and lower", "Resize", "Raise", "Lower", "Minimize", "Decrease Opacity", "Increase Opacity", "Nothing", "" }; const char* const tbl_TiWAc[] = { "Raise/Lower", "Shade/Unshade", "Maximize/Restore", "Above/Below", "Previous/Next Desktop", "Change Opacity", "Nothing", "" }; const char* const tbl_AllW[] = { "Raise/Lower", "Shade/Unshade", "Maximize/Restore", "Above/Below", "Previous/Next Desktop", "Change Opacity", "Nothing", "" }; static const char* tbl_num_lookup(const char* const arr[], int pos) { for (int i = 0; arr[ i ][ 0 ] != '\0' && pos >= 0; ++i) { if (pos == 0) return arr[ i ]; --pos; } abort(); // should never happen this way } static int tbl_txt_lookup(const char* const arr[], const char* txt) { int pos = 0; for (int i = 0; arr[ i ][ 0 ] != '\0'; ++i) { if (qstricmp(txt, arr[ i ]) == 0) return pos; ++pos; } return 0; } void KTitleBarActionsConfig::setComboText(KComboBox* combo, const char*txt) { if (combo == m_ui->coTiDbl) combo->setCurrentIndex(tbl_txt_lookup(tbl_TiDbl, txt)); else if (combo == m_ui->coTiAct1 || combo == m_ui->coTiAct2 || combo == m_ui->coTiAct3) combo->setCurrentIndex(tbl_txt_lookup(tbl_TiAc, txt)); else if (combo == m_ui->coTiInAct1 || combo == m_ui->coTiInAct2 || combo == m_ui->coTiInAct3) combo->setCurrentIndex(tbl_txt_lookup(tbl_TiInAc, txt)); else if (combo == m_ui->coTiAct4) combo->setCurrentIndex(tbl_txt_lookup(tbl_TiWAc, txt)); else if (combo == m_ui->leftClickMaximizeButton || combo == m_ui->middleClickMaximizeButton || combo == m_ui->rightClickMaximizeButton) { combo->setCurrentIndex(tbl_txt_lookup(tbl_Max, txt)); } else abort(); } const char* KTitleBarActionsConfig::functionTiDbl(int i) { return tbl_num_lookup(tbl_TiDbl, i); } const char* KTitleBarActionsConfig::functionTiAc(int i) { return tbl_num_lookup(tbl_TiAc, i); } const char* KTitleBarActionsConfig::functionTiInAc(int i) { return tbl_num_lookup(tbl_TiInAc, i); } const char* KTitleBarActionsConfig::functionTiWAc(int i) { return tbl_num_lookup(tbl_TiWAc, i); } const char* KTitleBarActionsConfig::functionMax(int i) { return tbl_num_lookup(tbl_Max, i); } void KTitleBarActionsConfig::showEvent(QShowEvent *ev) { if (!standAlone) { // Workaround KCModule::showEvent() calling load(), see bug 163817 QWidget::showEvent(ev); return; } KCModule::showEvent(ev); } void KTitleBarActionsConfig::changeEvent(QEvent *ev) { if (ev->type() == QEvent::PaletteChange) { paletteChanged(); } ev->accept(); } void KTitleBarActionsConfig::load() { KConfigGroup windowsConfig(config, "Windows"); setComboText(m_ui->coTiDbl, windowsConfig.readEntry("TitlebarDoubleClickCommand", "Maximize").toLatin1()); setComboText(m_ui->leftClickMaximizeButton, windowsConfig.readEntry(cnf_Max[0], tbl_Max[0]).toLatin1()); setComboText(m_ui->middleClickMaximizeButton, windowsConfig.readEntry(cnf_Max[1], tbl_Max[1]).toLatin1()); setComboText(m_ui->rightClickMaximizeButton, windowsConfig.readEntry(cnf_Max[2], tbl_Max[2]).toLatin1()); KConfigGroup cg(config, "MouseBindings"); setComboText(m_ui->coTiAct1, cg.readEntry("CommandActiveTitlebar1", "Raise").toLatin1()); setComboText(m_ui->coTiAct2, cg.readEntry("CommandActiveTitlebar2", "Nothing").toLatin1()); setComboText(m_ui->coTiAct3, cg.readEntry("CommandActiveTitlebar3", "Operations menu").toLatin1()); setComboText(m_ui->coTiAct4, cg.readEntry("CommandTitlebarWheel", "Nothing").toLatin1()); setComboText(m_ui->coTiInAct1, cg.readEntry("CommandInactiveTitlebar1", "Activate and raise").toLatin1()); setComboText(m_ui->coTiInAct2, cg.readEntry("CommandInactiveTitlebar2", "Nothing").toLatin1()); setComboText(m_ui->coTiInAct3, cg.readEntry("CommandInactiveTitlebar3", "Operations menu").toLatin1()); } void KTitleBarActionsConfig::save() { KConfigGroup windowsConfig(config, "Windows"); windowsConfig.writeEntry("TitlebarDoubleClickCommand", functionTiDbl(m_ui->coTiDbl->currentIndex())); windowsConfig.writeEntry(cnf_Max[0], functionMax(m_ui->leftClickMaximizeButton->currentIndex())); windowsConfig.writeEntry(cnf_Max[1], functionMax(m_ui->middleClickMaximizeButton->currentIndex())); windowsConfig.writeEntry(cnf_Max[2], functionMax(m_ui->rightClickMaximizeButton->currentIndex())); KConfigGroup cg(config, "MouseBindings"); cg.writeEntry("CommandActiveTitlebar1", functionTiAc(m_ui->coTiAct1->currentIndex())); cg.writeEntry("CommandActiveTitlebar2", functionTiAc(m_ui->coTiAct2->currentIndex())); cg.writeEntry("CommandActiveTitlebar3", functionTiAc(m_ui->coTiAct3->currentIndex())); cg.writeEntry("CommandInactiveTitlebar1", functionTiInAc(m_ui->coTiInAct1->currentIndex())); cg.writeEntry("CommandTitlebarWheel", functionTiWAc(m_ui->coTiAct4->currentIndex())); cg.writeEntry("CommandInactiveTitlebar2", functionTiInAc(m_ui->coTiInAct2->currentIndex())); cg.writeEntry("CommandInactiveTitlebar3", functionTiInAc(m_ui->coTiInAct3->currentIndex())); if (standAlone) { config->sync(); // Send signal to all kwin instances QDBusMessage message = QDBusMessage::createSignal("/KWin", "org.kde.KWin", "reloadConfig"); QDBusConnection::sessionBus().send(message); } } void KTitleBarActionsConfig::defaults() { setComboText(m_ui->coTiDbl, "Maximize"); setComboText(m_ui->coTiAct1, "Raise"); setComboText(m_ui->coTiAct2, "Nothing"); setComboText(m_ui->coTiAct3, "Operations menu"); setComboText(m_ui->coTiAct4, "Nothing"); setComboText(m_ui->coTiInAct1, "Activate and raise"); setComboText(m_ui->coTiInAct2, "Nothing"); setComboText(m_ui->coTiInAct3, "Operations menu"); setComboText(m_ui->leftClickMaximizeButton, tbl_Max[0]); setComboText(m_ui->middleClickMaximizeButton, tbl_Max[1]); setComboText(m_ui->rightClickMaximizeButton, tbl_Max[2]); } KWindowActionsConfig::KWindowActionsConfig(bool _standAlone, KConfig *_config, QWidget * parent) : KCModule(parent), config(_config), standAlone(_standAlone) , m_ui(new KWinActionsConfigForm(this)) { connect(m_ui->coWin1, SIGNAL(activated(int)), SLOT(changed())); connect(m_ui->coWin2, SIGNAL(activated(int)), SLOT(changed())); connect(m_ui->coWin3, SIGNAL(activated(int)), SLOT(changed())); connect(m_ui->coWinWheel, SIGNAL(activated(int)), SLOT(changed())); connect(m_ui->coAllKey, SIGNAL(activated(int)), SLOT(changed())); connect(m_ui->coAll1, SIGNAL(activated(int)), SLOT(changed())); connect(m_ui->coAll2, SIGNAL(activated(int)), SLOT(changed())); connect(m_ui->coAll3, SIGNAL(activated(int)), SLOT(changed())); connect(m_ui->coAllW, SIGNAL(activated(int)), SLOT(changed())); load(); } KWindowActionsConfig::~KWindowActionsConfig() { if (standAlone) delete config; } void KWindowActionsConfig::setComboText(KComboBox* combo, const char*txt) { if (combo == m_ui->coWin1 || combo == m_ui->coWin2 || combo == m_ui->coWin3) combo->setCurrentIndex(tbl_txt_lookup(tbl_Win, txt)); else if (combo == m_ui->coWinWheel) combo->setCurrentIndex(tbl_txt_lookup(tbl_WinWheel, txt)); else if (combo == m_ui->coAllKey) combo->setCurrentIndex(tbl_txt_lookup(tbl_AllKey, txt)); else if (combo == m_ui->coAll1 || combo == m_ui->coAll2 || combo == m_ui->coAll3) combo->setCurrentIndex(tbl_txt_lookup(tbl_All, txt)); else if (combo == m_ui->coAllW) combo->setCurrentIndex(tbl_txt_lookup(tbl_AllW, txt)); else abort(); } const char* KWindowActionsConfig::functionWin(int i) { return tbl_num_lookup(tbl_Win, i); } const char* KWindowActionsConfig::functionWinWheel(int i) { return tbl_num_lookup(tbl_WinWheel, i); } const char* KWindowActionsConfig::functionAllKey(int i) { return tbl_num_lookup(tbl_AllKey, i); } const char* KWindowActionsConfig::functionAll(int i) { return tbl_num_lookup(tbl_All, i); } const char* KWindowActionsConfig::functionAllW(int i) { return tbl_num_lookup(tbl_AllW, i); } void KWindowActionsConfig::showEvent(QShowEvent *ev) { if (!standAlone) { QWidget::showEvent(ev); return; } KCModule::showEvent(ev); } void KWindowActionsConfig::load() { KConfigGroup cg(config, "MouseBindings"); setComboText(m_ui->coWin1, cg.readEntry("CommandWindow1", "Activate, raise and pass click").toLatin1()); setComboText(m_ui->coWin2, cg.readEntry("CommandWindow2", "Activate and pass click").toLatin1()); setComboText(m_ui->coWin3, cg.readEntry("CommandWindow3", "Activate and pass click").toLatin1()); setComboText(m_ui->coWinWheel, cg.readEntry("CommandWindowWheel", "Scroll").toLatin1()); setComboText(m_ui->coAllKey, cg.readEntry("CommandAllKey", "Alt").toLatin1()); setComboText(m_ui->coAll1, cg.readEntry("CommandAll1", "Move").toLatin1()); setComboText(m_ui->coAll2, cg.readEntry("CommandAll2", "Toggle raise and lower").toLatin1()); setComboText(m_ui->coAll3, cg.readEntry("CommandAll3", "Resize").toLatin1()); setComboText(m_ui->coAllW, cg.readEntry("CommandAllWheel", "Nothing").toLatin1()); } void KWindowActionsConfig::save() { KConfigGroup cg(config, "MouseBindings"); cg.writeEntry("CommandWindow1", functionWin(m_ui->coWin1->currentIndex())); cg.writeEntry("CommandWindow2", functionWin(m_ui->coWin2->currentIndex())); cg.writeEntry("CommandWindow3", functionWin(m_ui->coWin3->currentIndex())); cg.writeEntry("CommandWindowWheel", functionWinWheel(m_ui->coWinWheel->currentIndex())); cg.writeEntry("CommandAllKey", functionAllKey(m_ui->coAllKey->currentIndex())); cg.writeEntry("CommandAll1", functionAll(m_ui->coAll1->currentIndex())); cg.writeEntry("CommandAll2", functionAll(m_ui->coAll2->currentIndex())); cg.writeEntry("CommandAll3", functionAll(m_ui->coAll3->currentIndex())); cg.writeEntry("CommandAllWheel", functionAllW(m_ui->coAllW->currentIndex())); if (standAlone) { config->sync(); // Send signal to all kwin instances QDBusMessage message = QDBusMessage::createSignal("/KWin", "org.kde.KWin", "reloadConfig"); QDBusConnection::sessionBus().send(message); } } void KWindowActionsConfig::defaults() { setComboText(m_ui->coWin1, "Activate, raise and pass click"); setComboText(m_ui->coWin2, "Activate and pass click"); setComboText(m_ui->coWin3, "Activate and pass click"); setComboText(m_ui->coWinWheel, "Scroll"); setComboText(m_ui->coAllKey, "Alt"); setComboText(m_ui->coAll1, "Move"); setComboText(m_ui->coAll2, "Toggle raise and lower"); setComboText(m_ui->coAll3, "Resize"); setComboText(m_ui->coAllW, "Nothing"); } diff --git a/kcmkwin/kwinrules/ruleswidget.cpp b/kcmkwin/kwinrules/ruleswidget.cpp index 1ff550093..b6029a9e7 100644 --- a/kcmkwin/kwinrules/ruleswidget.cpp +++ b/kcmkwin/kwinrules/ruleswidget.cpp @@ -1,970 +1,970 @@ /* * Copyright (c) 2004 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "ruleswidget.h" #include #include #include #include #include #include #include #include #ifdef KWIN_BUILD_ACTIVITIES #include #endif #include #include #include #include "../../rules.h" #include "detectwidget.h" Q_DECLARE_METATYPE(NET::WindowType) namespace KWin { #define SETUP( var, type ) \ connect( enable_##var, SIGNAL(toggled(bool)), rule_##var, SLOT(setEnabled(bool))); \ connect( enable_##var, SIGNAL(toggled(bool)), this, SLOT(updateEnable##var())); \ connect( rule_##var, SIGNAL(activated(int)), this, SLOT(updateEnable##var())); \ enable_##var->setWhatsThis( enableDesc ); \ rule_##var->setWhatsThis( type##RuleDesc ); RulesWidget::RulesWidget(QWidget* parent) : detect_dlg(nullptr) { Q_UNUSED(parent); setupUi(this); QRegularExpressionValidator* validator = new QRegularExpressionValidator(QRegularExpression("[0-9\\-+,xX:]*"), this); maxsize->setValidator(validator); minsize->setValidator(validator); position->setValidator(validator); Ui::RulesWidgetBase::size->setValidator(validator); QString enableDesc = i18n("Enable this checkbox to alter this window property for the specified window(s)."); QString setRuleDesc = i18n("Specify how the window property should be affected:
    " "
  • Do Not Affect: The window property will not be affected and therefore" " the default handling for it will be used. Specifying this will block more generic" " window settings from taking effect.
  • " "
  • Apply Initially: The window property will be only set to the given value" " after the window is created. No further changes will be affected.
  • " "
  • Remember: The value of the window property will be remembered and every" " time the window is created, the last remembered value will be applied.
  • " "
  • Force: The window property will be always forced to the given value.
  • " "
  • Apply Now: The window property will be set to the given value immediately" " and will not be affected later (this action will be deleted afterwards).
  • " "
  • Force temporarily: The window property will be forced to the given value" " until it is hidden (this action will be deleted after the window is hidden).
  • " "
"); QString forceRuleDesc = i18n("Specify how the window property should be affected:
    " "
  • Do Not Affect: The window property will not be affected and therefore" " the default handling for it will be used. Specifying this will block more generic" " window settings from taking effect.
  • " "
  • Force: The window property will be always forced to the given value.
  • " "
  • Force temporarily: The window property will be forced to the given value" " until it is hidden (this action will be deleted after the window is hidden).
  • " "
"); // window tabs have enable signals done in designer // geometry tab SETUP(position, set); SETUP(size, set); SETUP(desktop, set); SETUP(screen, set); #ifdef KWIN_BUILD_ACTIVITIES SETUP(activity, set); #endif SETUP(maximizehoriz, set); SETUP(maximizevert, set); SETUP(minimize, set); SETUP(shade, set); SETUP(fullscreen, set); SETUP(placement, force); // preferences tab SETUP(above, set); SETUP(below, set); SETUP(noborder, set); SETUP(decocolor, force); SETUP(skiptaskbar, set); SETUP(skippager, set); SETUP(skipswitcher, set); SETUP(acceptfocus, force); SETUP(closeable, force); SETUP(autogroup, force); SETUP(autogroupfg, force); SETUP(autogroupid, force); SETUP(opacityactive, force); SETUP(opacityinactive, force); SETUP(shortcut, force); // workarounds tab SETUP(fsplevel, force); SETUP(fpplevel, force); SETUP(type, force); SETUP(desktopfile, set); SETUP(ignoregeometry, set); SETUP(minsize, force); SETUP(maxsize, force); SETUP(strictgeometry, force); SETUP(disableglobalshortcuts, force); SETUP(blockcompositing, force); connect (shortcut_edit, SIGNAL(clicked()), SLOT(shortcutEditClicked())); edit_reg_wmclass->hide(); edit_reg_role->hide(); edit_reg_title->hide(); edit_reg_machine->hide(); #ifndef KWIN_BUILD_ACTIVITIES rule_activity->hide(); enable_activity->hide(); activity->hide(); #endif int i; for (i = 1; i <= KWindowSystem::numberOfDesktops(); ++i) desktop->addItem(QString::number(i).rightJustified(2) + ':' + KWindowSystem::desktopName(i)); desktop->addItem(i18n("All Desktops")); #ifdef KWIN_BUILD_ACTIVITIES m_activities = new KActivities::Consumer(this); connect(m_activities, &KActivities::Consumer::activitiesChanged, this, [this] { updateActivitiesList(); }); connect(m_activities, &KActivities::Consumer::serviceStatusChanged, this, [this] { updateActivitiesList(); }); updateActivitiesList(); #endif KColorSchemeManager *schemes = new KColorSchemeManager(this); decocolor->setModel(schemes->model()); // hide autogrouping as it's currently not supported // BUG 370301 line_11->hide(); enable_autogroup->hide(); autogroup->hide(); rule_autogroup->hide(); enable_autogroupid->hide(); autogroupid->hide(); rule_autogroupid->hide(); enable_autogroupfg->hide(); autogroupfg->hide(); rule_autogroupfg->hide(); } #undef SETUP #define UPDATE_ENABLE_SLOT(var) \ void RulesWidget::updateEnable##var() \ { \ /* leave the label readable label_##var->setEnabled( enable_##var->isChecked() && rule_##var->currentIndex() != 0 );*/ \ Ui::RulesWidgetBase::var->setEnabled( enable_##var->isChecked() && rule_##var->currentIndex() != 0 ); \ } // geometry tab UPDATE_ENABLE_SLOT(position) UPDATE_ENABLE_SLOT(size) UPDATE_ENABLE_SLOT(desktop) UPDATE_ENABLE_SLOT(screen) #ifdef KWIN_BUILD_ACTIVITIES UPDATE_ENABLE_SLOT(activity) #endif UPDATE_ENABLE_SLOT(maximizehoriz) UPDATE_ENABLE_SLOT(maximizevert) UPDATE_ENABLE_SLOT(minimize) UPDATE_ENABLE_SLOT(shade) UPDATE_ENABLE_SLOT(fullscreen) UPDATE_ENABLE_SLOT(placement) // preferences tab UPDATE_ENABLE_SLOT(above) UPDATE_ENABLE_SLOT(below) UPDATE_ENABLE_SLOT(noborder) UPDATE_ENABLE_SLOT(decocolor) UPDATE_ENABLE_SLOT(skiptaskbar) UPDATE_ENABLE_SLOT(skippager) UPDATE_ENABLE_SLOT(skipswitcher) UPDATE_ENABLE_SLOT(acceptfocus) UPDATE_ENABLE_SLOT(closeable) UPDATE_ENABLE_SLOT(autogroup) UPDATE_ENABLE_SLOT(autogroupfg) UPDATE_ENABLE_SLOT(autogroupid) UPDATE_ENABLE_SLOT(opacityactive) UPDATE_ENABLE_SLOT(opacityinactive) void RulesWidget::updateEnableshortcut() { shortcut->setEnabled(enable_shortcut->isChecked() && rule_shortcut->currentIndex() != 0); shortcut_edit->setEnabled(enable_shortcut->isChecked() && rule_shortcut->currentIndex() != 0); } // workarounds tab UPDATE_ENABLE_SLOT(fsplevel) UPDATE_ENABLE_SLOT(fpplevel) UPDATE_ENABLE_SLOT(type) UPDATE_ENABLE_SLOT(ignoregeometry) UPDATE_ENABLE_SLOT(minsize) UPDATE_ENABLE_SLOT(maxsize) UPDATE_ENABLE_SLOT(strictgeometry) UPDATE_ENABLE_SLOT(disableglobalshortcuts) UPDATE_ENABLE_SLOT(blockcompositing) UPDATE_ENABLE_SLOT(desktopfile) #undef UPDATE_ENABLE_SLOT static const int set_rule_to_combo[] = { 0, // Unused 0, // Don't Affect 3, // Force 1, // Apply 2, // Remember 4, // ApplyNow 5 // ForceTemporarily }; static const Rules::SetRule combo_to_set_rule[] = { (Rules::SetRule)Rules::DontAffect, (Rules::SetRule)Rules::Apply, (Rules::SetRule)Rules::Remember, (Rules::SetRule)Rules::Force, (Rules::SetRule)Rules::ApplyNow, (Rules::SetRule)Rules::ForceTemporarily }; static const int force_rule_to_combo[] = { 0, // Unused 0, // Don't Affect 1, // Force 0, // Apply 0, // Remember 0, // ApplyNow 2 // ForceTemporarily }; static const Rules::ForceRule combo_to_force_rule[] = { (Rules::ForceRule)Rules::DontAffect, (Rules::ForceRule)Rules::Force, (Rules::ForceRule)Rules::ForceTemporarily }; static QString positionToStr(const QPoint& p) { if (p == invalidPoint) return QString(); return QString::number(p.x()) + ',' + QString::number(p.y()); } static QPoint strToPosition(const QString& str) { // two numbers, with + or -, separated by any of , x X : QRegExp reg("\\s*([+-]?[0-9]*)\\s*[,xX:]\\s*([+-]?[0-9]*)\\s*"); if (!reg.exactMatch(str)) return invalidPoint; return QPoint(reg.cap(1).toInt(), reg.cap(2).toInt()); } static QString sizeToStr(const QSize& s) { if (!s.isValid()) return QString(); return QString::number(s.width()) + ',' + QString::number(s.height()); } static QSize strToSize(const QString& str) { // two numbers, with + or -, separated by any of , x X : QRegExp reg("\\s*([+-]?[0-9]*)\\s*[,xX:]\\s*([+-]?[0-9]*)\\s*"); if (!reg.exactMatch(str)) return QSize(); return QSize(reg.cap(1).toInt(), reg.cap(2).toInt()); } int RulesWidget::desktopToCombo(int d) const { if (d >= 1 && d < desktop->count()) return d - 1; return desktop->count() - 1; // on all desktops } int RulesWidget::comboToDesktop(int val) const { if (val == desktop->count() - 1) return NET::OnAllDesktops; return val + 1; } #ifdef KWIN_BUILD_ACTIVITIES int RulesWidget::activityToCombo(QString d) const { // TODO: ivan - do a multiselection list for (int i = 0; i < activity->count(); i++) { if (activity->itemData(i).toString() == d) { return i; } } return activity->count() - 1; // on all activities } QString RulesWidget::comboToActivity(int val) const { // TODO: ivan - do a multiselection list if (val < 0 || val >= activity->count()) return QString(); return activity->itemData(val).toString(); } void RulesWidget::updateActivitiesList() { activity->clear(); // cloned from kactivities/src/lib/core/consumer.cpp #define NULL_UUID "00000000-0000-0000-0000-000000000000" activity->addItem(i18n("All Activities"), QString::fromLatin1(NULL_UUID)); #undef NULL_UUID if (m_activities->serviceStatus() == KActivities::Consumer::Running) { foreach (const QString & activityId, m_activities->activities(KActivities::Info::Running)) { const KActivities::Info info(activityId); activity->addItem(info.name(), activityId); } } auto rules = this->rules(); if (rules->activityrule == Rules::UnusedSetRule) { enable_activity->setChecked(false); Ui::RulesWidgetBase::activity->setCurrentIndex(0); } else { enable_activity->setChecked(true); Ui::RulesWidgetBase::activity->setCurrentIndex(activityToCombo(m_selectedActivityId)); } updateEnableactivity(); } #endif static int placementToCombo(Placement::Policy placement) { static const int conv[] = { 1, // NoPlacement 0, // Default 0, // Unknown 6, // Random 2, // Smart 4, // Cascade 5, // Centered 7, // ZeroCornered 8, // UnderMouse 9, // OnMainWindow 3 // Maximizing }; return conv[ placement ]; } static Placement::Policy comboToPlacement(int val) { static const Placement::Policy conv[] = { Placement::Default, Placement::NoPlacement, Placement::Smart, Placement::Maximizing, Placement::Cascade, Placement::Centered, Placement::Random, Placement::ZeroCornered, Placement::UnderMouse, Placement::OnMainWindow // no Placement::Unknown }; return conv[ val ]; } static int typeToCombo(NET::WindowType type) { if (type < NET::Normal || type > NET::Splash || type == NET::Override) // The user must NOT set a window to be unmanaged. // This case is not handled in KWin and will lead to segfaults. // Even iff it was supported, it would mean to allow the user to shoot himself // since an unmanaged window has to manage itself, what is probably not the case when the hint is not set. // Rule opportunity might be a relict from the Motif Hint window times of KDE1 return 0; // Normal static const int conv[] = { 0, // Normal 7, // Desktop 3, // Dock 4, // Toolbar 5, // Menu 1, // Dialog 8, // Override - ignored. 9, // TopMenu 2, // Utility 6 // Splash }; return conv[ type ]; } static NET::WindowType comboToType(int val) { static const NET::WindowType conv[] = { NET::Normal, NET::Dialog, NET::Utility, NET::Dock, NET::Toolbar, NET::Menu, NET::Splash, NET::Desktop, NET::TopMenu }; return conv[ val ]; } #define GENERIC_RULE( var, func, Type, type, uimethod, uimethod0 ) \ if ( rules->var##rule == Rules::Unused##Type##Rule ) \ { \ enable_##var->setChecked( false ); \ rule_##var->setCurrentIndex( 0 ); \ Ui::RulesWidgetBase::var->uimethod0; \ updateEnable##var(); \ } \ else \ { \ enable_##var->setChecked( true ); \ rule_##var->setCurrentIndex( type##_rule_to_combo[ rules->var##rule ] ); \ Ui::RulesWidgetBase::var->uimethod( func( rules->var )); \ updateEnable##var(); \ } #define CHECKBOX_SET_RULE( var, func ) GENERIC_RULE( var, func, Set, set, setChecked, setChecked( false )) #define LINEEDIT_SET_RULE( var, func ) GENERIC_RULE( var, func, Set, set, setText, setText( QString() )) #define COMBOBOX_SET_RULE( var, func ) GENERIC_RULE( var, func, Set, set, setCurrentIndex, setCurrentIndex( 0 )) #define SPINBOX_SET_RULE( var, func ) GENERIC_RULE( var, func, Set, set, setValue, setValue(0)) #define CHECKBOX_FORCE_RULE( var, func ) GENERIC_RULE( var, func, Force, force, setChecked, setChecked( false )) #define LINEEDIT_FORCE_RULE( var, func ) GENERIC_RULE( var, func, Force, force, setText, setText( QString() )) #define COMBOBOX_FORCE_RULE( var, func ) GENERIC_RULE( var, func, Force, force, setCurrentIndex, setCurrentIndex( 0 )) #define SPINBOX_FORCE_RULE( var, func ) GENERIC_RULE( var, func, Force, force, setValue, setValue(0)) void RulesWidget::setRules(Rules* rules) { Rules tmp; if (rules == nullptr) rules = &tmp; // empty description->setText(rules->description); wmclass->setText(rules->wmclass); whole_wmclass->setChecked(rules->wmclasscomplete); wmclass_match->setCurrentIndex(rules->wmclassmatch); wmclassMatchChanged(); role->setText(rules->windowrole); role_match->setCurrentIndex(rules->windowrolematch); roleMatchChanged(); types->item(0)->setSelected(rules->types & NET::NormalMask); types->item(1)->setSelected(rules->types & NET::DialogMask); types->item(2)->setSelected(rules->types & NET::UtilityMask); types->item(3)->setSelected(rules->types & NET::DockMask); types->item(4)->setSelected(rules->types & NET::ToolbarMask); types->item(5)->setSelected(rules->types & NET::MenuMask); types->item(6)->setSelected(rules->types & NET::SplashMask); types->item(7)->setSelected(rules->types & NET::DesktopMask); types->item(8)->setSelected(rules->types & NET::OverrideMask); types->item(9)->setSelected(rules->types & NET::TopMenuMask); title->setText(rules->title); title_match->setCurrentIndex(rules->titlematch); titleMatchChanged(); machine->setText(rules->clientmachine); machine_match->setCurrentIndex(rules->clientmachinematch); machineMatchChanged(); LINEEDIT_SET_RULE(position, positionToStr); LINEEDIT_SET_RULE(size, sizeToStr); COMBOBOX_SET_RULE(desktop, desktopToCombo); SPINBOX_SET_RULE(screen, inc); #ifdef KWIN_BUILD_ACTIVITIES m_selectedActivityId = rules->activity; COMBOBOX_SET_RULE(activity, activityToCombo); #endif CHECKBOX_SET_RULE(maximizehoriz,); CHECKBOX_SET_RULE(maximizevert,); CHECKBOX_SET_RULE(minimize,); CHECKBOX_SET_RULE(shade,); CHECKBOX_SET_RULE(fullscreen,); COMBOBOX_FORCE_RULE(placement, placementToCombo); CHECKBOX_SET_RULE(above,); CHECKBOX_SET_RULE(below,); CHECKBOX_SET_RULE(noborder,); auto decoColorToCombo = [this](const QString &value) { for (int i = 0; i < decocolor->count(); ++i) { if (decocolor->itemData(i).toString() == value) { return i; } } // search for Breeze for (int i = 0; i < decocolor->count(); ++i) { if (QFileInfo(decocolor->itemData(i).toString()).baseName() == QStringLiteral("Breeze")) { return i; } } return 0; }; COMBOBOX_FORCE_RULE(decocolor, decoColorToCombo); CHECKBOX_SET_RULE(skiptaskbar,); CHECKBOX_SET_RULE(skippager,); CHECKBOX_SET_RULE(skipswitcher,); CHECKBOX_FORCE_RULE(acceptfocus,); CHECKBOX_FORCE_RULE(closeable,); CHECKBOX_FORCE_RULE(autogroup,); CHECKBOX_FORCE_RULE(autogroupfg,); LINEEDIT_FORCE_RULE(autogroupid,); SPINBOX_FORCE_RULE(opacityactive,); SPINBOX_FORCE_RULE(opacityinactive,); LINEEDIT_SET_RULE(shortcut,); COMBOBOX_FORCE_RULE(fsplevel,); COMBOBOX_FORCE_RULE(fpplevel,); COMBOBOX_FORCE_RULE(type, typeToCombo); CHECKBOX_SET_RULE(ignoregeometry,); LINEEDIT_FORCE_RULE(minsize, sizeToStr); LINEEDIT_FORCE_RULE(maxsize, sizeToStr); CHECKBOX_FORCE_RULE(strictgeometry,); CHECKBOX_FORCE_RULE(disableglobalshortcuts,); CHECKBOX_FORCE_RULE(blockcompositing,); LINEEDIT_SET_RULE(desktopfile,) } #undef GENERIC_RULE #undef CHECKBOX_SET_RULE #undef LINEEDIT_SET_RULE #undef COMBOBOX_SET_RULE #undef SPINBOX_SET_RULE #undef CHECKBOX_FORCE_RULE #undef LINEEDIT_FORCE_RULE #undef COMBOBOX_FORCE_RULE #undef SPINBOX_FORCE_RULE #define GENERIC_RULE( var, func, Type, type, uimethod ) \ if ( enable_##var->isChecked() && rule_##var->currentIndex() >= 0) \ { \ rules->var##rule = combo_to_##type##_rule[ rule_##var->currentIndex() ]; \ rules->var = func( Ui::RulesWidgetBase::var->uimethod()); \ } \ else \ rules->var##rule = Rules::Unused##Type##Rule; #define CHECKBOX_SET_RULE( var, func ) GENERIC_RULE( var, func, Set, set, isChecked ) #define LINEEDIT_SET_RULE( var, func ) GENERIC_RULE( var, func, Set, set, text ) #define COMBOBOX_SET_RULE( var, func ) GENERIC_RULE( var, func, Set, set, currentIndex ) #define SPINBOX_SET_RULE( var, func ) GENERIC_RULE( var, func, Set, set, value) #define CHECKBOX_FORCE_RULE( var, func ) GENERIC_RULE( var, func, Force, force, isChecked ) #define LINEEDIT_FORCE_RULE( var, func ) GENERIC_RULE( var, func, Force, force, text ) #define COMBOBOX_FORCE_RULE( var, func ) GENERIC_RULE( var, func, Force, force, currentIndex ) #define SPINBOX_FORCE_RULE( var, func ) GENERIC_RULE( var, func, Force, force, value) Rules* RulesWidget::rules() const { Rules* rules = new Rules(); rules->description = description->text(); rules->wmclass = wmclass->text().toUtf8(); rules->wmclasscomplete = whole_wmclass->isChecked(); rules->wmclassmatch = static_cast< Rules::StringMatch >(wmclass_match->currentIndex()); rules->windowrole = role->text().toUtf8(); rules->windowrolematch = static_cast< Rules::StringMatch >(role_match->currentIndex()); - rules->types = 0; + rules->types = nullptr; bool all_types = true; for (int i = 0; i < types->count(); ++i) if (!types->item(i)->isSelected()) all_types = false; if (all_types) // if all types are selected, use AllTypesMask (for future expansion) rules->types = NET::AllTypesMask; else { rules->types |= types->item(0)->isSelected() ? NET::NormalMask : NET::WindowTypeMask(0); rules->types |= types->item(1)->isSelected() ? NET::DialogMask : NET::WindowTypeMask(0); rules->types |= types->item(2)->isSelected() ? NET::UtilityMask : NET::WindowTypeMask(0); rules->types |= types->item(3)->isSelected() ? NET::DockMask : NET::WindowTypeMask(0); rules->types |= types->item(4)->isSelected() ? NET::ToolbarMask : NET::WindowTypeMask(0); rules->types |= types->item(5)->isSelected() ? NET::MenuMask : NET::WindowTypeMask(0); rules->types |= types->item(6)->isSelected() ? NET::SplashMask : NET::WindowTypeMask(0); rules->types |= types->item(7)->isSelected() ? NET::DesktopMask : NET::WindowTypeMask(0); rules->types |= types->item(8)->isSelected() ? NET::OverrideMask : NET::WindowTypeMask(0); rules->types |= types->item(9)->isSelected() ? NET::TopMenuMask : NET::WindowTypeMask(0); } rules->title = title->text(); rules->titlematch = static_cast< Rules::StringMatch >(title_match->currentIndex()); rules->clientmachine = machine->text().toUtf8(); rules->clientmachinematch = static_cast< Rules::StringMatch >(machine_match->currentIndex()); LINEEDIT_SET_RULE(position, strToPosition); LINEEDIT_SET_RULE(size, strToSize); COMBOBOX_SET_RULE(desktop, comboToDesktop); SPINBOX_SET_RULE(screen, dec); #ifdef KWIN_BUILD_ACTIVITIES COMBOBOX_SET_RULE(activity, comboToActivity); #endif CHECKBOX_SET_RULE(maximizehoriz,); CHECKBOX_SET_RULE(maximizevert,); CHECKBOX_SET_RULE(minimize,); CHECKBOX_SET_RULE(shade,); CHECKBOX_SET_RULE(fullscreen,); COMBOBOX_FORCE_RULE(placement, comboToPlacement); CHECKBOX_SET_RULE(above,); CHECKBOX_SET_RULE(below,); CHECKBOX_SET_RULE(noborder,); auto comboToDecocolor = [this](int index) -> QString { return decocolor->itemData(index).toString(); }; COMBOBOX_FORCE_RULE(decocolor, comboToDecocolor); CHECKBOX_SET_RULE(skiptaskbar,); CHECKBOX_SET_RULE(skippager,); CHECKBOX_SET_RULE(skipswitcher,); CHECKBOX_FORCE_RULE(acceptfocus,); CHECKBOX_FORCE_RULE(closeable,); CHECKBOX_FORCE_RULE(autogroup,); CHECKBOX_FORCE_RULE(autogroupfg,); LINEEDIT_FORCE_RULE(autogroupid,); SPINBOX_FORCE_RULE(opacityactive,); SPINBOX_FORCE_RULE(opacityinactive,); LINEEDIT_SET_RULE(shortcut,); COMBOBOX_FORCE_RULE(fsplevel,); COMBOBOX_FORCE_RULE(fpplevel,); COMBOBOX_FORCE_RULE(type, comboToType); CHECKBOX_SET_RULE(ignoregeometry,); LINEEDIT_FORCE_RULE(minsize, strToSize); LINEEDIT_FORCE_RULE(maxsize, strToSize); CHECKBOX_FORCE_RULE(strictgeometry,); CHECKBOX_FORCE_RULE(disableglobalshortcuts,); CHECKBOX_FORCE_RULE(blockcompositing,); LINEEDIT_SET_RULE(desktopfile,); return rules; } #undef GENERIC_RULE #undef CHECKBOX_SET_RULE #undef LINEEDIT_SET_RULE #undef COMBOBOX_SET_RULE #undef SPINBOX_SET_RULE #undef CHECKBOX_FORCE_RULE #undef LINEEDIT_FORCE_RULE #undef COMBOBOX_FORCE_RULE #undef SPINBOX_FORCE_RULE #define STRING_MATCH_COMBO( type ) \ void RulesWidget::type##MatchChanged() \ { \ edit_reg_##type->setEnabled( type##_match->currentIndex() == Rules::RegExpMatch ); \ type->setEnabled( type##_match->currentIndex() != Rules::UnimportantMatch ); \ } STRING_MATCH_COMBO(wmclass) STRING_MATCH_COMBO(role) STRING_MATCH_COMBO(title) STRING_MATCH_COMBO(machine) #undef STRING_MATCH_COMBO void RulesWidget::detectClicked() { Q_ASSERT(detect_dlg == nullptr); detect_dlg = new DetectDialog; connect(detect_dlg, SIGNAL(detectionDone(bool)), this, SLOT(detected(bool))); detect_dlg->detect(Ui::RulesWidgetBase::detection_delay->value()); Ui::RulesWidgetBase::detect->setEnabled(false); } void RulesWidget::detected(bool ok) { if (ok) { wmclass->setText(detect_dlg->selectedClass()); wmclass_match->setCurrentIndex(Rules::ExactMatch); wmclassMatchChanged(); // grrr whole_wmclass->setChecked(detect_dlg->selectedWholeClass()); role->setText(detect_dlg->selectedRole()); role_match->setCurrentIndex(detect_dlg->selectedRole().isEmpty() ? Rules::UnimportantMatch : Rules::ExactMatch); roleMatchChanged(); if (detect_dlg->selectedWholeApp()) { for (int i = 0; i < types->count(); ++i) types->item(i)->setSelected(true); } else { NET::WindowType type = detect_dlg->selectedType(); for (int i = 0; i < types->count(); ++i) types->item(i)->setSelected(false); types->item(typeToCombo(type))->setSelected(true); } title->setText(detect_dlg->selectedTitle()); title_match->setCurrentIndex(detect_dlg->titleMatch()); titleMatchChanged(); machine->setText(detect_dlg->selectedMachine()); machine_match->setCurrentIndex(Rules::UnimportantMatch); machineMatchChanged(); // prefill values from to window to settings which already set prefillUnusedValues(detect_dlg->windowInfo()); } delete detect_dlg; detect_dlg = nullptr; detect_dlg_ok = ok; Ui::RulesWidgetBase::detect->setEnabled(true); } #define GENERIC_PREFILL( var, func, info, uimethod ) \ if ( !enable_##var->isChecked()) \ { \ Ui::RulesWidgetBase::var->uimethod( func( info )); \ } #define CHECKBOX_PREFILL( var, func, info ) GENERIC_PREFILL( var, func, info, setChecked ) #define LINEEDIT_PREFILL( var, func, info ) GENERIC_PREFILL( var, func, info, setText ) #define COMBOBOX_PREFILL( var, func, info ) GENERIC_PREFILL( var, func, info, setCurrentIndex ) #define SPINBOX_PREFILL( var, func, info ) GENERIC_PREFILL( var, func, info, setValue ) void RulesWidget::prefillUnusedValues(const QVariantMap& info) { const QSize windowSize{info.value("width").toInt(), info.value("height").toInt()}; LINEEDIT_PREFILL(position, positionToStr, QPoint(info.value("x").toInt(), info.value("y").toInt())); LINEEDIT_PREFILL(size, sizeToStr, windowSize); COMBOBOX_PREFILL(desktop, desktopToCombo, info.value("x11DesktopNumber").toInt()); // COMBOBOX_PREFILL(activity, activityToCombo, info.activity()); // TODO: ivan CHECKBOX_PREFILL(maximizehoriz, , info.value("maximizeHorizontal").toBool()); CHECKBOX_PREFILL(maximizevert, , info.value("maximizeVertical").toBool()); CHECKBOX_PREFILL(minimize, , info.value("minimized").toBool()); CHECKBOX_PREFILL(shade, , info.value("shaded").toBool()); CHECKBOX_PREFILL(fullscreen, , info.value("fullscreen").toBool()); //COMBOBOX_PREFILL( placement, placementToCombo ); CHECKBOX_PREFILL(above, , info.value("keepAbove").toBool()); CHECKBOX_PREFILL(below, , info.value("keepBelow").toBool()); CHECKBOX_PREFILL(noborder, , info.value("noBorder").toBool()); CHECKBOX_PREFILL(skiptaskbar, , info.value("skipTaskbar").toBool()); CHECKBOX_PREFILL(skippager, , info.value("skipPager").toBool()); CHECKBOX_PREFILL(skipswitcher, , info.value("skipSwitcher").toBool()); //CHECKBOX_PREFILL( acceptfocus, ); //CHECKBOX_PREFILL( closeable, ); //CHECKBOX_PREFILL( autogroup, ); //CHECKBOX_PREFILL( autogroupfg, ); //LINEEDIT_PREFILL( autogroupid, ); SPINBOX_PREFILL(opacityactive, , 100 /*get the actual opacity somehow*/); SPINBOX_PREFILL(opacityinactive, , 100 /*get the actual opacity somehow*/); //LINEEDIT_PREFILL( shortcut, ); //COMBOBOX_PREFILL( fsplevel, ); //COMBOBOX_PREFILL( fpplevel, ); COMBOBOX_PREFILL(type, typeToCombo, info.value("type").value()); //CHECKBOX_PREFILL( ignoregeometry, ); LINEEDIT_PREFILL(minsize, sizeToStr, windowSize); LINEEDIT_PREFILL(maxsize, sizeToStr, windowSize); //CHECKBOX_PREFILL( strictgeometry, ); //CHECKBOX_PREFILL( disableglobalshortcuts, ); //CHECKBOX_PREFILL( blockcompositing, ); LINEEDIT_PREFILL(desktopfile, , info.value("desktopFile").toString()); } #undef GENERIC_PREFILL #undef CHECKBOX_PREFILL #undef LINEEDIT_PREFILL #undef COMBOBOX_PREFILL #undef SPINBOX_PREFILL bool RulesWidget::finalCheck() { if (description->text().isEmpty()) { if (!wmclass->text().isEmpty()) description->setText(i18n("Settings for %1", wmclass->text())); else description->setText(i18n("Unnamed entry")); } bool all_types = true; for (int i = 0; i < types->count(); ++i) if (!types->item(i)->isSelected()) all_types = false; if (wmclass_match->currentIndex() == Rules::UnimportantMatch && all_types) { if (KMessageBox::warningContinueCancel(window(), i18n("You have specified the window class as unimportant.\n" "This means the settings will possibly apply to windows from all applications. " "If you really want to create a generic setting, it is recommended you at least " "limit the window types to avoid special window types.")) != KMessageBox::Continue) return false; } return true; } void RulesWidget::prepareWindowSpecific(const QVariantMap &info) { tabs->setCurrentIndex(1); // geometry tab, skip tab for window identification prefillUnusedValues(info); } void RulesWidget::shortcutEditClicked() { QPointer dlg = new EditShortcutDialog(window()); dlg->setShortcut(shortcut->text()); if (dlg->exec() == QDialog::Accepted) shortcut->setText(dlg->shortcut()); delete dlg; } RulesDialog::RulesDialog(QWidget* parent, const char* name) : QDialog(parent) { setObjectName(name); setModal(true); setWindowTitle(i18n("Edit Window-Specific Settings")); setWindowIcon(QIcon::fromTheme("preferences-system-windows-actions")); setLayout(new QVBoxLayout); widget = new RulesWidget(this); layout()->addWidget(widget); QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); connect(buttons, SIGNAL(accepted()), SLOT(accept())); connect(buttons, SIGNAL(rejected()), SLOT(reject())); layout()->addWidget(buttons); } // window is set only for Alt+F3/Window-specific settings, because the dialog // is then related to one specific window Rules* RulesDialog::edit(Rules* r, const QVariantMap& info, bool show_hints) { rules = r; widget->setRules(rules); if (!info.isEmpty()) { widget->prepareWindowSpecific(info); } if (show_hints) QTimer::singleShot(0, this, SLOT(displayHints())); exec(); return rules; } void RulesDialog::displayHints() { QString str = "

"; str += i18n("This configuration dialog allows altering settings only for the selected window" " or application. Find the setting you want to affect, enable the setting using the checkbox," " select in what way the setting should be affected and to which value."); #if 0 // maybe later str += "

" + i18n("Consult the documentation for more details."); #endif str += "

"; KMessageBox::information(this, str, QString(), "displayhints"); } void RulesDialog::accept() { if (!widget->finalCheck()) return; rules = widget->rules(); QDialog::accept(); } EditShortcut::EditShortcut(QWidget* parent) : QWidget(parent) { setupUi(this); } void EditShortcut::editShortcut() { QPointer< ShortcutDialog > dlg = new ShortcutDialog(QKeySequence(shortcut->text()), window()); if (dlg->exec() == QDialog::Accepted) shortcut->setText(dlg->shortcut().toString()); delete dlg; } void EditShortcut::clearShortcut() { shortcut->clear(); } EditShortcutDialog::EditShortcutDialog(QWidget* parent, const char* name) : QDialog(parent) , widget(new EditShortcut(this)) { setObjectName(name); setModal(true); setWindowTitle(i18n("Edit Shortcut")); setLayout(new QVBoxLayout); QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); connect(buttons, SIGNAL(accepted()), SLOT(accept())); connect(buttons, SIGNAL(rejected()), SLOT(reject())); layout()->addWidget(widget); layout()->addWidget(buttons); } void EditShortcutDialog::setShortcut(const QString& cut) { widget->shortcut->setText(cut); } QString EditShortcutDialog::shortcut() const { return widget->shortcut->text(); } ShortcutDialog::ShortcutDialog(const QKeySequence& cut, QWidget* parent) : QDialog(parent) , widget(new KKeySequenceWidget(this)) { widget->setKeySequence(cut); // It's a global shortcut so don't allow multikey shortcuts widget->setMultiKeyShortcutsAllowed(false); QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); connect(buttons, SIGNAL(accepted()), SLOT(accept())); connect(buttons, SIGNAL(rejected()), SLOT(reject())); setLayout(new QVBoxLayout); layout()->addWidget(widget); layout()->addWidget(buttons); } void ShortcutDialog::accept() { QKeySequence seq = shortcut(); if (!seq.isEmpty()) { if (seq[0] == Qt::Key_Escape) { reject(); return; } if (seq[0] == Qt::Key_Space || (seq[0] & Qt::KeyboardModifierMask) == 0) { // clear widget->clearKeySequence(); QDialog::accept(); return; } } QDialog::accept(); } QKeySequence ShortcutDialog::shortcut() const { return widget->keySequence(); } } // namespace diff --git a/kcmkwin/kwinscreenedges/monitor.h b/kcmkwin/kwinscreenedges/monitor.h index 300305815..aa50dcd07 100644 --- a/kcmkwin/kwinscreenedges/monitor.h +++ b/kcmkwin/kwinscreenedges/monitor.h @@ -1,113 +1,113 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2008 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 . *********************************************************************/ #ifndef CCSM_MONITOR_H #define CCSM_MONITOR_H #include "screenpreviewwidget.h" #include #include #include class QAction; class QGraphicsView; class QGraphicsScene; class QMenu; namespace Plasma { class FrameSvg; } namespace KWin { class Monitor : public ScreenPreviewWidget { Q_OBJECT public: explicit Monitor(QWidget* parent); void setEdge(int edge, bool set); bool edge(int edge) const; void setEdgeHidden(int edge, bool set); bool edgeHidden(int edge) const; void clear(); void addEdgeItem(int edge, const QString& item); void setEdgeItemEnabled(int edge, int index, bool enabled); bool edgeItemEnabled(int edge, int index) const; void selectEdgeItem(int edge, int index); int selectedEdgeItem(int edge) const; enum Edges { Left, Right, Top, Bottom, TopLeft, TopRight, BottomLeft, BottomRight }; Q_SIGNALS: void changed(); void edgeSelectionChanged(int edge, int index); protected: void resizeEvent(QResizeEvent* e) override; private: class Corner; void popup(Corner* c, QPoint pos); void flip(Corner* c, QPoint pos); void checkSize(); QGraphicsView* view; QGraphicsScene* scene; Corner* items[ 8 ]; bool hidden[ 8 ]; QMenu* popups[ 8 ]; QVector< QAction* > popup_actions[ 8 ]; QActionGroup* grp[ 8 ]; }; class Monitor::Corner : public QGraphicsRectItem { public: Corner(Monitor* m); ~Corner() override; void setActive(bool active); bool active() const; protected: void contextMenuEvent(QGraphicsSceneContextMenuEvent* e) override; void mousePressEvent(QGraphicsSceneMouseEvent* e) override; void hoverEnterEvent(QGraphicsSceneHoverEvent * e) override; void hoverLeaveEvent(QGraphicsSceneHoverEvent * e) override; - void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget = 0) override; + void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget = nullptr) override; private: Monitor* monitor; Plasma::FrameSvg *button; bool m_active; bool m_hover; }; } // namespace #endif diff --git a/kcmkwin/kwintabbox/thumbnailitem.cpp b/kcmkwin/kwintabbox/thumbnailitem.cpp index 8c8edb488..4d8f5762f 100644 --- a/kcmkwin/kwintabbox/thumbnailitem.cpp +++ b/kcmkwin/kwintabbox/thumbnailitem.cpp @@ -1,219 +1,219 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2011, 2014 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 "thumbnailitem.h" // Qt #include #include #include namespace KWin { BrightnessSaturationShader::BrightnessSaturationShader() : QSGMaterialShader() , m_id_matrix(0) , m_id_opacity(0) , m_id_saturation(0) , m_id_brightness(0) { } const char *BrightnessSaturationShader::vertexShader() const { return "attribute highp vec4 vertex; \n" "attribute highp vec2 texCoord; \n" "uniform highp mat4 u_matrix; \n" "varying highp vec2 v_coord; \n" "void main() { \n" " v_coord = texCoord; \n" " gl_Position = u_matrix * vertex; \n" "}"; } const char *BrightnessSaturationShader::fragmentShader() const { return "uniform sampler2D qt_Texture; \n" "uniform lowp float u_opacity; \n" "uniform highp float u_saturation; \n" "uniform highp float u_brightness; \n" "varying highp vec2 v_coord; \n" "void main() { \n" " lowp vec4 tex = texture2D(qt_Texture, v_coord); \n" " if (u_saturation != 1.0) { \n" " tex.rgb = mix(vec3(dot( vec3( 0.30, 0.59, 0.11 ), tex.rgb )), tex.rgb, u_saturation); \n" " } \n" " tex.rgb = tex.rgb * u_brightness; \n" " gl_FragColor = tex * u_opacity; \n" "}"; } const char* const *BrightnessSaturationShader::attributeNames() const { - static char const *const names[] = { "vertex", "texCoord", 0 }; + static char const *const names[] = { "vertex", "texCoord", nullptr }; return names; } void BrightnessSaturationShader::updateState(const QSGMaterialShader::RenderState &state, QSGMaterial *newMaterial, QSGMaterial *oldMaterial) { Q_ASSERT(program()->isLinked()); if (state.isMatrixDirty()) program()->setUniformValue(m_id_matrix, state.combinedMatrix()); if (state.isOpacityDirty()) program()->setUniformValue(m_id_opacity, state.opacity()); auto *tx = static_cast(newMaterial); auto *oldTx = static_cast(oldMaterial); QSGTexture *t = tx->texture(); t->setFiltering(QSGTexture::Linear); if (!oldTx || oldTx->texture()->textureId() != t->textureId()) t->bind(); else t->updateBindOptions(); program()->setUniformValue(m_id_saturation, static_cast(tx->saturation)); program()->setUniformValue(m_id_brightness, static_cast(tx->brightness)); } void BrightnessSaturationShader::initialize() { QSGMaterialShader::initialize(); m_id_matrix = program()->uniformLocation("u_matrix"); m_id_opacity = program()->uniformLocation("u_opacity"); m_id_saturation = program()->uniformLocation("u_saturation"); m_id_brightness = program()->uniformLocation("u_brightness"); } WindowThumbnailItem::WindowThumbnailItem(QQuickItem* parent) : QQuickItem(parent) , m_wId(0) , m_image() , m_clipToItem(nullptr) , m_brightness(1.0) , m_saturation(1.0) { setFlag(ItemHasContents); } WindowThumbnailItem::~WindowThumbnailItem() { } void WindowThumbnailItem::setWId(qulonglong wId) { m_wId = wId; emit wIdChanged(wId); findImage(); } void WindowThumbnailItem::setClipTo(QQuickItem *clip) { if (m_clipToItem == clip) { return; } m_clipToItem = clip; emit clipToChanged(); } void WindowThumbnailItem::findImage() { QString imagePath; switch (m_wId) { case Konqueror: imagePath = QStandardPaths::locate(QStandardPaths::GenericDataLocation, "kwin/kcm_kwintabbox/konqueror.png"); break; case Systemsettings: imagePath = QStandardPaths::locate(QStandardPaths::GenericDataLocation, "kwin/kcm_kwintabbox/systemsettings.png"); break; case KMail: imagePath = QStandardPaths::locate(QStandardPaths::GenericDataLocation, "kwin/kcm_kwintabbox/kmail.png"); break; case Dolphin: imagePath = QStandardPaths::locate(QStandardPaths::GenericDataLocation, "kwin/kcm_kwintabbox/dolphin.png"); break; default: // ignore break; } if (imagePath.isNull()) { m_image = QImage(); } else { m_image = QImage(imagePath); } } QSGNode *WindowThumbnailItem::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *updatePaintNodeData) { Q_UNUSED(updatePaintNodeData) QSGGeometryNode *node = static_cast(oldNode); if (!node) { node = new QSGGeometryNode(); auto *material = new BrightnessSaturationMaterial; material->setFlag(QSGMaterial::Blending); material->setTexture(window()->createTextureFromImage(m_image)); node->setMaterial(material); QSGGeometry *geometry = new QSGGeometry(QSGGeometry::defaultAttributes_TexturedPoint2D(), 4); node->setGeometry(geometry); } auto *material = static_cast(node->material()); const QSize size(material->texture()->textureSize().scaled(boundingRect().size().toSize(), Qt::KeepAspectRatio)); const qreal x = boundingRect().x() + (boundingRect().width() - size.width())/2; const qreal y = boundingRect().y() + (boundingRect().height() - size.height())/2; QSGGeometry::updateTexturedRectGeometry(node->geometry(), QRectF(QPointF(x, y), size), QRectF(0.0, 0.0, 1.0, 1.0)); material->brightness = m_brightness; material->saturation = m_saturation; node->markDirty(QSGNode::DirtyGeometry | QSGNode::DirtyMaterial); return node; } qreal WindowThumbnailItem::brightness() const { return m_brightness; } qreal WindowThumbnailItem::saturation() const { return m_saturation; } void WindowThumbnailItem::setBrightness(qreal brightness) { if (m_brightness == brightness) { return; } m_brightness = brightness; update(); emit brightnessChanged(); } void WindowThumbnailItem::setSaturation(qreal saturation) { if (m_saturation == saturation) { return; } m_saturation = saturation; update(); emit saturationChanged(); } } // namespace KWin diff --git a/layers.cpp b/layers.cpp index fb41bfe9a..e871819bc 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 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 0; + 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 NULL; + return nullptr; } void Workspace::raiseOrLowerClient(AbstractClient *c) { if (!c) return; - AbstractClient* topmost = NULL; + 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 = 0; + 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) ? 0 : 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() : NULL); + 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; } } } 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 = 0; + 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 = 0; + 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/libkwineffects/kwinglplatform.cpp b/libkwineffects/kwinglplatform.cpp index d3c91b75f..9c80bc4c3 100644 --- a/libkwineffects/kwinglplatform.cpp +++ b/libkwineffects/kwinglplatform.cpp @@ -1,1241 +1,1241 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2010 Fredrik Höglund 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 "kwinglplatform.h" // include kwinglutils_funcs.h to avoid the redeclaration issues // between qopengl.h and epoxy/gl.h #include "kwinglutils_funcs.h" #include #include #include #include #include #include #include #include #include namespace KWin { -GLPlatform *GLPlatform::s_platform = 0; +GLPlatform *GLPlatform::s_platform = nullptr; static qint64 parseVersionString(const QByteArray &version) { // Skip any leading non digit int start = 0; while (start < version.length() && !QChar::fromLatin1(version[start]).isDigit()) start++; // Strip any non digit, non '.' characters from the end int end = start; while (end < version.length() && (version[end] == '.' || QChar::fromLatin1(version[end]).isDigit())) end++; const QByteArray result = version.mid(start, end-start); const QList tokens = result.split('.'); const qint64 major = tokens.at(0).toInt(); const qint64 minor = tokens.count() > 1 ? tokens.at(1).toInt() : 0; const qint64 patch = tokens.count() > 2 ? tokens.at(2).toInt() : 0; return kVersionNumber(major, minor, patch); } static qint64 getXServerVersion() { qint64 major, minor, patch; major = 0; minor = 0; patch = 0; if (xcb_connection_t *c = connection()) { auto setup = xcb_get_setup(c); const QByteArray vendorName(xcb_setup_vendor(setup), xcb_setup_vendor_length(setup)); if (vendorName.contains("X.Org")) { const int release = setup->release_number; major = (release / 10000000); minor = (release / 100000) % 100; patch = (release / 1000) % 100; } } return kVersionNumber(major, minor, patch); } static qint64 getKernelVersion() { struct utsname name; uname(&name); if (qstrcmp(name.sysname, "Linux") == 0) return parseVersionString(name.release); return 0; } // Extracts the portion of a string that matches a regular expression static QString extract(const QString &string, const QString &match, int offset = 0) { QString result; QRegExp rx(match); int pos = rx.indexIn(string, offset); if (pos != -1) result = string.mid(pos, rx.matchedLength()); return result; } static ChipClass detectRadeonClass(const QByteArray &chipset) { if (chipset.isEmpty()) return UnknownRadeon; if (chipset.contains("R100") || chipset.contains("RV100") || chipset.contains("RS100")) return R100; if (chipset.contains("RV200") || chipset.contains("RS200") || chipset.contains("R200") || chipset.contains("RV250") || chipset.contains("RS300") || chipset.contains("RV280")) return R200; if (chipset.contains("R300") || chipset.contains("R350") || chipset.contains("R360") || chipset.contains("RV350") || chipset.contains("RV370") || chipset.contains("RV380")) return R300; if (chipset.contains("R420") || chipset.contains("R423") || chipset.contains("R430") || chipset.contains("R480") || chipset.contains("R481") || chipset.contains("RV410") || chipset.contains("RS400") || chipset.contains("RC410") || chipset.contains("RS480") || chipset.contains("RS482") || chipset.contains("RS600") || chipset.contains("RS690") || chipset.contains("RS740")) return R400; if (chipset.contains("RV515") || chipset.contains("R520") || chipset.contains("RV530") || chipset.contains("R580") || chipset.contains("RV560") || chipset.contains("RV570")) return R500; if (chipset.contains("R600") || chipset.contains("RV610") || chipset.contains("RV630") || chipset.contains("RV670") || chipset.contains("RV620") || chipset.contains("RV635") || chipset.contains("RS780") || chipset.contains("RS880")) return R600; if (chipset.contains("R700") || chipset.contains("RV770") || chipset.contains("RV730") || chipset.contains("RV710") || chipset.contains("RV740")) return R700; if (chipset.contains("EVERGREEN") || // Not an actual chipset, but returned by R600G in 7.9 chipset.contains("CEDAR") || chipset.contains("REDWOOD") || chipset.contains("JUNIPER") || chipset.contains("CYPRESS") || chipset.contains("HEMLOCK") || chipset.contains("PALM")) return Evergreen; if (chipset.contains("SUMO") || chipset.contains("SUMO2") || chipset.contains("BARTS") || chipset.contains("TURKS") || chipset.contains("CAICOS") || chipset.contains("CAYMAN")) return NorthernIslands; if (chipset.contains("TAHITI") || chipset.contains("PITCAIRN") || chipset.contains("VERDE") || chipset.contains("OLAND") || chipset.contains("HAINAN")) { return SouthernIslands; } if (chipset.contains("BONAIRE") || chipset.contains("KAVERI") || chipset.contains("KABINI") || chipset.contains("HAWAII") || chipset.contains("MULLINS")) { return SeaIslands; } if (chipset.contains("TONGA") || chipset.contains("TOPAZ") || chipset.contains("FIJI") || chipset.contains("CARRIZO") || chipset.contains("STONEY")) { return VolcanicIslands; } if (chipset.contains("POLARIS10") || chipset.contains("POLARIS11") || chipset.contains("POLARIS12") || chipset.contains("VEGAM")) { return ArcticIslands; } if (chipset.contains("VEGA10") || chipset.contains("VEGA12") || chipset.contains("VEGA20") || chipset.contains("RAVEN")) { return Vega; } const QString chipset16 = QString::fromLatin1(chipset); QString name = extract(chipset16, QStringLiteral("HD [0-9]{4}")); // HD followed by a space and 4 digits if (!name.isEmpty()) { const int id = name.rightRef(4).toInt(); if (id == 6250 || id == 6310) // Palm return Evergreen; if (id >= 6000 && id < 7000) return NorthernIslands; // HD 6xxx if (id >= 5000 && id < 6000) return Evergreen; // HD 5xxx if (id >= 4000 && id < 5000) return R700; // HD 4xxx if (id >= 2000 && id < 4000) // HD 2xxx/3xxx return R600; return UnknownRadeon; } name = extract(chipset16, QStringLiteral("X[0-9]{3,4}")); // X followed by 3-4 digits if (!name.isEmpty()) { const int id = name.midRef(1, -1).toInt(); // X1xxx if (id >= 1300) return R500; // X7xx, X8xx, X12xx, 2100 if ((id >= 700 && id < 1000) || id >= 1200) return R400; // X200, X3xx, X5xx, X6xx, X10xx, X11xx if ((id >= 300 && id < 700) || (id >= 1000 && id < 1200)) return R300; return UnknownRadeon; } name = extract(chipset16, QStringLiteral("\\b[0-9]{4}\\b")); // A group of 4 digits if (!name.isEmpty()) { const int id = name.toInt(); // 7xxx if (id >= 7000 && id < 8000) return R100; // 8xxx, 9xxx if (id >= 8000 && id < 9500) return R200; // 9xxx if (id >= 9500) return R300; if (id == 2100) return R400; } return UnknownRadeon; } static ChipClass detectNVidiaClass(const QString &chipset) { QString name = extract(chipset, QStringLiteral("\\bNV[0-9,A-F]{2}\\b")); // NV followed by two hexadecimal digits if (!name.isEmpty()) { - const int id = chipset.midRef(2, -1).toInt(0, 16); // Strip the 'NV' from the id + const int id = chipset.midRef(2, -1).toInt(nullptr, 16); // Strip the 'NV' from the id switch(id & 0xf0) { case 0x00: case 0x10: return NV10; case 0x20: return NV20; case 0x30: return NV30; case 0x40: case 0x60: return NV40; case 0x50: case 0x80: case 0x90: case 0xA0: return G80; default: return UnknownNVidia; } } if (chipset.contains(QLatin1String("GeForce2")) || chipset.contains(QLatin1String("GeForce 256"))) return NV10; if (chipset.contains(QLatin1String("GeForce3"))) return NV20; if (chipset.contains(QLatin1String("GeForce4"))) { if (chipset.contains(QLatin1String("MX 420")) || chipset.contains(QLatin1String("MX 440")) || // including MX 440SE chipset.contains(QLatin1String("MX 460")) || chipset.contains(QLatin1String("MX 4000")) || chipset.contains(QLatin1String("PCX 4300"))) return NV10; return NV20; } // GeForce 5,6,7,8,9 name = extract(chipset, QStringLiteral("GeForce (FX |PCX |Go )?\\d{4}(M|\\b)")).trimmed(); if (!name.isEmpty()) { if (!name[name.length() - 1].isDigit()) name.chop(1); const int id = name.rightRef(4).toInt(); if (id < 6000) return NV30; if (id >= 6000 && id < 8000) return NV40; if (id >= 8000) return G80; return UnknownNVidia; } // GeForce 100/200/300/400/500 name = extract(chipset, QStringLiteral("GeForce (G |GT |GTX |GTS )?\\d{3}(M|\\b)")).trimmed(); if (!name.isEmpty()) { if (!name[name.length() - 1].isDigit()) name.chop(1); const int id = name.rightRef(3).toInt(); if (id >= 100 && id < 600) { if (id >= 400) return GF100; return G80; } return UnknownNVidia; } return UnknownNVidia; } static inline ChipClass detectNVidiaClass(const QByteArray &chipset) { return detectNVidiaClass(QString::fromLatin1(chipset)); } static ChipClass detectIntelClass(const QByteArray &chipset) { // see mesa repository: src/mesa/drivers/dri/intel/intel_context.c // GL 1.3, DX8? SM ? if (chipset.contains("845G") || chipset.contains("830M") || chipset.contains("852GM/855GM") || chipset.contains("865G")) return I8XX; // GL 1.4, DX 9.0, SM 2.0 if (chipset.contains("915G") || chipset.contains("E7221G") || chipset.contains("915GM") || chipset.contains("945G") || // DX 9.0c chipset.contains("945GM") || chipset.contains("945GME") || chipset.contains("Q33") || // GL1.5 chipset.contains("Q35") || chipset.contains("G33") || chipset.contains("965Q") || // GMA 3000, but apparently considered gen 4 by the driver chipset.contains("946GZ") || // GMA 3000, but apparently considered gen 4 by the driver chipset.contains("IGD")) return I915; // GL 2.0, DX 9.0c, SM 3.0 if (chipset.contains("965G") || chipset.contains("G45/G43") || // SM 4.0 chipset.contains("965GM") || // GL 2.1 chipset.contains("965GME/GLE") || chipset.contains("GM45") || chipset.contains("Q45/Q43") || chipset.contains("G41") || chipset.contains("B43") || chipset.contains("Ironlake")) return I965; // GL 3.1, CL 1.1, DX 10.1 if (chipset.contains("Sandybridge")) { return SandyBridge; } // GL4.0, CL1.1, DX11, SM 5.0 if (chipset.contains("Ivybridge")) { return IvyBridge; } // GL4.0, CL1.2, DX11.1, SM 5.0 if (chipset.contains("Haswell")) { return Haswell; } return UnknownIntel; } static ChipClass detectQualcommClass(const QByteArray &chipClass) { if (!chipClass.contains("Adreno")) { return UnknownChipClass; } const auto parts = chipClass.split(' '); if (parts.count() < 3) { return UnknownAdreno; } bool ok = false; const int value = parts.at(2).toInt(&ok); if (ok) { if (value >= 100 && value < 200) { return Adreno1XX; } if (value >= 200 && value < 300) { return Adreno2XX; } if (value >= 300 && value < 400) { return Adreno3XX; } if (value >= 400 && value < 500) { return Adreno4XX; } if (value >= 500 && value < 600) { return Adreno5XX; } } return UnknownAdreno; } QString GLPlatform::versionToString(qint64 version) { return QString::fromLatin1(versionToString8(version)); } QByteArray GLPlatform::versionToString8(qint64 version) { int major = (version >> 32); int minor = (version >> 16) & 0xffff; int patch = version & 0xffff; QByteArray string = QByteArray::number(major) + '.' + QByteArray::number(minor); if (patch != 0) string += '.' + QByteArray::number(patch); return string; } QString GLPlatform::driverToString(Driver driver) { return QString::fromLatin1(driverToString8(driver)); } QByteArray GLPlatform::driverToString8(Driver driver) { switch(driver) { case Driver_R100: return QByteArrayLiteral("Radeon"); case Driver_R200: return QByteArrayLiteral("R200"); case Driver_R300C: return QByteArrayLiteral("R300C"); case Driver_R300G: return QByteArrayLiteral("R300G"); case Driver_R600C: return QByteArrayLiteral("R600C"); case Driver_R600G: return QByteArrayLiteral("R600G"); case Driver_RadeonSI: return QByteArrayLiteral("RadeonSI"); case Driver_Nouveau: return QByteArrayLiteral("Nouveau"); case Driver_Intel: return QByteArrayLiteral("Intel"); case Driver_NVidia: return QByteArrayLiteral("NVIDIA"); case Driver_Catalyst: return QByteArrayLiteral("Catalyst"); case Driver_Swrast: return QByteArrayLiteral("Software rasterizer"); case Driver_Softpipe: return QByteArrayLiteral("softpipe"); case Driver_Llvmpipe: return QByteArrayLiteral("LLVMpipe"); case Driver_VirtualBox: return QByteArrayLiteral("VirtualBox (Chromium)"); case Driver_VMware: return QByteArrayLiteral("VMware (SVGA3D)"); case Driver_Qualcomm: return QByteArrayLiteral("Qualcomm"); default: return QByteArrayLiteral("Unknown"); } } QString GLPlatform::chipClassToString(ChipClass chipClass) { return QString::fromLatin1(chipClassToString8(chipClass)); } QByteArray GLPlatform::chipClassToString8(ChipClass chipClass) { switch(chipClass) { case R100: return QByteArrayLiteral("R100"); case R200: return QByteArrayLiteral("R200"); case R300: return QByteArrayLiteral("R300"); case R400: return QByteArrayLiteral("R400"); case R500: return QByteArrayLiteral("R500"); case R600: return QByteArrayLiteral("R600"); case R700: return QByteArrayLiteral("R700"); case Evergreen: return QByteArrayLiteral("EVERGREEN"); case NorthernIslands: return QByteArrayLiteral("Northern Islands"); case SouthernIslands: return QByteArrayLiteral("Southern Islands"); case SeaIslands: return QByteArrayLiteral("Sea Islands"); case VolcanicIslands: return QByteArrayLiteral("Volcanic Islands"); case ArcticIslands: return QByteArrayLiteral("Arctic Islands"); case Vega: return QByteArrayLiteral("Vega"); case NV10: return QByteArrayLiteral("NV10"); case NV20: return QByteArrayLiteral("NV20"); case NV30: return QByteArrayLiteral("NV30"); case NV40: return QByteArrayLiteral("NV40/G70"); case G80: return QByteArrayLiteral("G80/G90"); case GF100: return QByteArrayLiteral("GF100"); case I8XX: return QByteArrayLiteral("i830/i835"); case I915: return QByteArrayLiteral("i915/i945"); case I965: return QByteArrayLiteral("i965"); case SandyBridge: return QByteArrayLiteral("SandyBridge"); case IvyBridge: return QByteArrayLiteral("IvyBridge"); case Haswell: return QByteArrayLiteral("Haswell"); case Adreno1XX: return QByteArrayLiteral("Adreno 1xx series"); case Adreno2XX: return QByteArrayLiteral("Adreno 2xx series"); case Adreno3XX: return QByteArrayLiteral("Adreno 3xx series"); case Adreno4XX: return QByteArrayLiteral("Adreno 4xx series"); case Adreno5XX: return QByteArrayLiteral("Adreno 5xx series"); default: return QByteArrayLiteral("Unknown"); } } // ------- GLPlatform::GLPlatform() : m_driver(Driver_Unknown), m_chipClass(UnknownChipClass), m_recommendedCompositor(XRenderCompositing), m_glVersion(0), m_glslVersion(0), m_mesaVersion(0), m_driverVersion(0), m_galliumVersion(0), m_serverVersion(0), m_kernelVersion(0), m_looseBinding(false), m_supportsGLSL(false), m_limitedGLSL(false), m_textureNPOT(false), m_limitedNPOT(false), m_virtualMachine(false), m_preferBufferSubData(false), m_platformInterface(NoOpenGLPlatformInterface), m_gles(false) { } GLPlatform::~GLPlatform() { } void GLPlatform::detect(OpenGLPlatformInterface platformInterface) { m_platformInterface = platformInterface; m_vendor = (const char*)glGetString(GL_VENDOR); m_renderer = (const char*)glGetString(GL_RENDERER); m_version = (const char*)glGetString(GL_VERSION); // Parse the OpenGL version const QList versionTokens = m_version.split(' '); if (versionTokens.count() > 0) { const QByteArray version = QByteArray(m_version); m_glVersion = parseVersionString(version); if (platformInterface == EglPlatformInterface) { // only EGL can have OpenGLES, GLX is OpenGL only if (version.startsWith("OpenGL ES")) { // from GLES 2: "Returns a version or release number of the form OpenGLES." // from GLES 3: "Returns a version or release number." and "The version number uses one of these forms: major_number.minor_number major_number.minor_number.release_number" m_gles = true; } } } if (!isGLES() && m_glVersion >= kVersionNumber(3, 0)) { int count; glGetIntegerv(GL_NUM_EXTENSIONS, &count); for (int i = 0; i < count; i++) { const char *name = (const char *) glGetStringi(GL_EXTENSIONS, i); m_extensions.insert(name); } } else { const QByteArray extensions = (const char *) glGetString(GL_EXTENSIONS); m_extensions = QSet::fromList(extensions.split(' ')); } // Parse the Mesa version const int mesaIndex = versionTokens.indexOf("Mesa"); if (mesaIndex != -1) { const QByteArray version = versionTokens.at(mesaIndex + 1); m_mesaVersion = parseVersionString(version); } if (isGLES()) { m_supportsGLSL = true; m_textureNPOT = true; } else { m_supportsGLSL = m_extensions.contains("GL_ARB_shader_objects") && m_extensions.contains("GL_ARB_fragment_shader") && m_extensions.contains("GL_ARB_vertex_shader"); m_textureNPOT = m_extensions.contains("GL_ARB_texture_non_power_of_two"); } m_serverVersion = getXServerVersion(); m_kernelVersion = getKernelVersion(); m_glslVersion = 0; m_glsl_version.clear(); if (m_supportsGLSL) { // Parse the GLSL version m_glsl_version = (const char*)glGetString(GL_SHADING_LANGUAGE_VERSION); m_glslVersion = parseVersionString(m_glsl_version); } m_chipset = QByteArrayLiteral("Unknown"); m_preferBufferSubData = false; // Mesa classic drivers // ==================================================== // Radeon if (m_renderer.startsWith("Mesa DRI R")) { // Sample renderer string: Mesa DRI R600 (RV740 94B3) 20090101 x86/MMX/SSE2 TCL DRI2 const QList tokens = m_renderer.split(' '); const QByteArray chipClass = tokens.at(2); m_chipset = tokens.at(3).mid(1, -1); // Strip the leading '(' if (chipClass == "R100") // Vendor: Tungsten Graphics, Inc. m_driver = Driver_R100; else if (chipClass == "R200") // Vendor: Tungsten Graphics, Inc. m_driver = Driver_R200; else if (chipClass == "R300") // Vendor: DRI R300 Project m_driver = Driver_R300C; else if (chipClass == "R600") // Vendor: Advanced Micro Devices, Inc. m_driver = Driver_R600C; m_chipClass = detectRadeonClass(m_chipset); } // Intel else if (m_renderer.contains("Intel")) { // Vendor: Tungsten Graphics, Inc. // Sample renderer string: Mesa DRI Mobile Intel® GM45 Express Chipset GEM 20100328 2010Q1 QByteArray chipset; if (m_renderer.startsWith("Intel(R) Integrated Graphics Device")) chipset = "IGD"; else chipset = m_renderer; m_driver = Driver_Intel; m_chipClass = detectIntelClass(chipset); } // Properietary drivers // ==================================================== else if (m_vendor == "ATI Technologies Inc.") { m_chipClass = detectRadeonClass(m_renderer); m_driver = Driver_Catalyst; if (versionTokens.count() > 1 && versionTokens.at(2)[0] == '(') m_driverVersion = parseVersionString(versionTokens.at(1)); else if (versionTokens.count() > 0) m_driverVersion = parseVersionString(versionTokens.at(0)); else m_driverVersion = 0; } else if (m_vendor == "NVIDIA Corporation") { m_chipClass = detectNVidiaClass(m_renderer); m_driver = Driver_NVidia; int index = versionTokens.indexOf("NVIDIA"); if (versionTokens.count() > index) m_driverVersion = parseVersionString(versionTokens.at(index + 1)); else m_driverVersion = 0; } else if (m_vendor == "Qualcomm") { m_driver = Driver_Qualcomm; m_chipClass = detectQualcommClass(m_renderer); } else if (m_renderer == "Software Rasterizer") { m_driver = Driver_Swrast; } // Virtual Hardware // ==================================================== else if (m_vendor == "Humper" && m_renderer == "Chromium") { // Virtual Box m_driver = Driver_VirtualBox; const int index = versionTokens.indexOf("Chromium"); if (versionTokens.count() > index) m_driverVersion = parseVersionString(versionTokens.at(index + 1)); else m_driverVersion = 0; } // Gallium drivers // ==================================================== else { const QList tokens = m_renderer.split(' '); if (m_renderer.contains("Gallium")) { // Sample renderer string: Gallium 0.4 on AMD RV740 m_galliumVersion = parseVersionString(tokens.at(1)); m_chipset = (tokens.at(3) == "AMD" || tokens.at(3) == "ATI") ? tokens.at(4) : tokens.at(3); } else { // The renderer string does not contain "Gallium" anymore. m_chipset = tokens.at(0); // We don't know the actual version anymore, but it's at least 0.4. m_galliumVersion = kVersionNumber(0, 4, 0); } // R300G if (m_vendor == QByteArrayLiteral("X.Org R300 Project")) { m_chipClass = detectRadeonClass(m_chipset); m_driver = Driver_R300G; } // R600G else if (m_vendor == "X.Org" && (m_renderer.contains("R6") || m_renderer.contains("R7") || m_renderer.contains("RV6") || m_renderer.contains("RV7") || m_renderer.contains("RS780") || m_renderer.contains("RS880") || m_renderer.contains("CEDAR") || m_renderer.contains("REDWOOD") || m_renderer.contains("JUNIPER") || m_renderer.contains("CYPRESS") || m_renderer.contains("HEMLOCK") || m_renderer.contains("PALM") || m_renderer.contains("EVERGREEN") || m_renderer.contains("SUMO") || m_renderer.contains("SUMO2") || m_renderer.contains("BARTS") || m_renderer.contains("TURKS") || m_renderer.contains("CAICOS") || m_renderer.contains("CAYMAN"))) { m_chipClass = detectRadeonClass(m_chipset); m_driver = Driver_R600G; } // RadeonSI else if (m_vendor == "X.Org" && (m_renderer.contains("TAHITI") || m_renderer.contains("PITCAIRN") || m_renderer.contains("VERDE") || m_renderer.contains("OLAND") || m_renderer.contains("HAINAN") || m_renderer.contains("BONAIRE") || m_renderer.contains("KAVERI") || m_renderer.contains("KABINI") || m_renderer.contains("HAWAII") || m_renderer.contains("MULLINS") || m_renderer.contains("TOPAZ") || m_renderer.contains("TONGA") || m_renderer.contains("FIJI") || m_renderer.contains("CARRIZO") || m_renderer.contains("STONEY") || m_renderer.contains("POLARIS10") || m_renderer.contains("POLARIS11") || m_renderer.contains("POLARIS12") || m_renderer.contains("VEGAM") || m_renderer.contains("VEGA10") || m_renderer.contains("VEGA12") || m_renderer.contains("VEGA20") || m_renderer.contains("RAVEN"))) { m_chipClass = detectRadeonClass(m_renderer); m_driver = Driver_RadeonSI; } // Nouveau else if (m_vendor == "nouveau") { m_chipClass = detectNVidiaClass(m_chipset); m_driver = Driver_Nouveau; } // softpipe else if (m_vendor == "VMware, Inc." && m_chipset == "softpipe" ) { m_driver = Driver_Softpipe; } // llvmpipe else if (m_vendor == "VMware, Inc." && m_chipset == "llvmpipe") { m_driver = Driver_Llvmpipe; } // SVGA3D else if (m_vendor == "VMware, Inc." && m_chipset.contains("SVGA3D")) { m_driver = Driver_VMware; } } // Driver/GPU specific features // ==================================================== if (isRadeon()) { // R200 technically has a programmable pipeline, but since it's SM 1.4, // it's too limited to to be of any practical value to us. if (m_chipClass < R300) m_supportsGLSL = false; m_limitedGLSL = false; m_limitedNPOT = false; if (m_chipClass < R600) { if (driver() == Driver_Catalyst) m_textureNPOT = m_limitedNPOT = false; // Software fallback else if (driver() == Driver_R300G) m_limitedNPOT = m_textureNPOT; m_limitedGLSL = m_supportsGLSL; } if (m_chipClass < R300) { // fallback to XRender for R100 and R200 m_recommendedCompositor = XRenderCompositing; } else if (m_chipClass < R600) { // XRender due to NPOT limitations not supported by KWin's shaders m_recommendedCompositor = XRenderCompositing; } else { m_recommendedCompositor = OpenGL2Compositing; } if (driver() == Driver_R600G || (driver() == Driver_R600C && m_renderer.contains("DRI2"))) { m_looseBinding = true; } } if (isNvidia()) { if (m_driver == Driver_NVidia && m_chipClass < NV40) m_supportsGLSL = false; // High likelihood of software emulation if (m_driver == Driver_NVidia) { m_looseBinding = true; m_preferBufferSubData = true; } if (m_chipClass < NV40) { m_recommendedCompositor = XRenderCompositing; } else { m_recommendedCompositor = OpenGL2Compositing; } m_limitedNPOT = m_textureNPOT && m_chipClass < NV40; m_limitedGLSL = m_supportsGLSL && m_chipClass < G80; } if (isIntel()) { if (m_chipClass < I915) m_supportsGLSL = false; m_limitedGLSL = m_supportsGLSL && m_chipClass < I965; // see https://bugs.freedesktop.org/show_bug.cgi?id=80349#c1 m_looseBinding = false; if (m_chipClass < I915) { m_recommendedCompositor = XRenderCompositing; } else { m_recommendedCompositor = OpenGL2Compositing; } } if (isMesaDriver() && platformInterface == EglPlatformInterface) { // According to the reference implementation in // mesa/demos/src/egl/opengles1/texture_from_pixmap // the mesa egl implementation does not require a strict binding (so far). m_looseBinding = true; } if (isSoftwareEmulation()) { if (m_driver < Driver_Llvmpipe) { // we recommend XRender m_recommendedCompositor = XRenderCompositing; // Software emulation does not provide GLSL m_limitedGLSL = m_supportsGLSL = false; } else { // llvmpipe does support GLSL m_recommendedCompositor = OpenGL2Compositing; m_limitedGLSL = false; m_supportsGLSL = true; } } if (m_driver == Driver_Qualcomm) { if (m_chipClass == Adreno1XX) { m_recommendedCompositor = NoCompositing; } else { // all other drivers support at least GLES 2 m_recommendedCompositor = OpenGL2Compositing; } } if (m_chipClass == UnknownChipClass && m_driver == Driver_Unknown) { // we don't know the hardware. Let's be optimistic and assume OpenGL compatible hardware m_recommendedCompositor = OpenGL2Compositing; m_supportsGLSL = true; } if (isVirtualBox()) { m_virtualMachine = true; m_recommendedCompositor = OpenGL2Compositing; } if (isVMware()) { m_virtualMachine = true; m_recommendedCompositor = OpenGL2Compositing; } // and force back to shader supported on gles, we wouldn't have got a context if not supported if (isGLES()) { m_supportsGLSL = true; m_limitedGLSL = false; } } static void print(const QByteArray &label, const QByteArray &setting) { std::cout << std::setw(40) << std::left << label.data() << setting.data() << std::endl; } void GLPlatform::printResults() const { print(QByteArrayLiteral("OpenGL vendor string:"), m_vendor); print(QByteArrayLiteral("OpenGL renderer string:"), m_renderer); print(QByteArrayLiteral("OpenGL version string:"), m_version); if (m_supportsGLSL) print(QByteArrayLiteral("OpenGL shading language version string:"), m_glsl_version); print(QByteArrayLiteral("Driver:"), driverToString8(m_driver)); if (!isMesaDriver()) print(QByteArrayLiteral("Driver version:"), versionToString8(m_driverVersion)); print(QByteArrayLiteral("GPU class:"), chipClassToString8(m_chipClass)); print(QByteArrayLiteral("OpenGL version:"), versionToString8(m_glVersion)); if (m_supportsGLSL) print(QByteArrayLiteral("GLSL version:"), versionToString8(m_glslVersion)); if (isMesaDriver()) print(QByteArrayLiteral("Mesa version:"), versionToString8(mesaVersion())); //if (galliumVersion() > 0) // print("Gallium version:", versionToString(m_galliumVersion)); if (serverVersion() > 0) print(QByteArrayLiteral("X server version:"), versionToString8(m_serverVersion)); if (kernelVersion() > 0) print(QByteArrayLiteral("Linux kernel version:"), versionToString8(m_kernelVersion)); print(QByteArrayLiteral("Requires strict binding:"), !m_looseBinding ? QByteArrayLiteral("yes") : QByteArrayLiteral("no")); print(QByteArrayLiteral("GLSL shaders:"), m_supportsGLSL ? (m_limitedGLSL ? QByteArrayLiteral("limited") : QByteArrayLiteral("yes")) : QByteArrayLiteral("no")); print(QByteArrayLiteral("Texture NPOT support:"), m_textureNPOT ? (m_limitedNPOT ? QByteArrayLiteral("limited") : QByteArrayLiteral("yes")) : QByteArrayLiteral("no")); print(QByteArrayLiteral("Virtual Machine:"), m_virtualMachine ? QByteArrayLiteral("yes") : QByteArrayLiteral("no")); } bool GLPlatform::supports(GLFeature feature) const { switch(feature) { case LooseBinding: return m_looseBinding; case GLSL: return m_supportsGLSL; case LimitedGLSL: return m_limitedGLSL; case TextureNPOT: return m_textureNPOT; case LimitedNPOT: return m_limitedNPOT; default: return false; } } qint64 GLPlatform::glVersion() const { return m_glVersion; } qint64 GLPlatform::glslVersion() const { return m_glslVersion; } qint64 GLPlatform::mesaVersion() const { return m_mesaVersion; } qint64 GLPlatform::galliumVersion() const { return m_galliumVersion; } qint64 GLPlatform::serverVersion() const { return m_serverVersion; } qint64 GLPlatform::kernelVersion() const { return m_kernelVersion; } qint64 GLPlatform::driverVersion() const { if (isMesaDriver()) return mesaVersion(); return m_driverVersion; } Driver GLPlatform::driver() const { return m_driver; } ChipClass GLPlatform::chipClass() const { return m_chipClass; } bool GLPlatform::isMesaDriver() const { return mesaVersion() > 0; } bool GLPlatform::isGalliumDriver() const { return galliumVersion() > 0; } bool GLPlatform::isRadeon() const { return m_chipClass >= R100 && m_chipClass <= UnknownRadeon; } bool GLPlatform::isNvidia() const { return m_chipClass >= NV10 && m_chipClass <= UnknownNVidia; } bool GLPlatform::isIntel() const { return m_chipClass >= I8XX && m_chipClass <= UnknownIntel; } bool GLPlatform::isVirtualBox() const { return m_driver == Driver_VirtualBox; } bool GLPlatform::isVMware() const { return m_driver == Driver_VMware; } bool GLPlatform::isSoftwareEmulation() const { return m_driver == Driver_Softpipe || m_driver == Driver_Swrast || m_driver == Driver_Llvmpipe; } bool GLPlatform::isAdreno() const { return m_chipClass >= Adreno1XX && m_chipClass <= UnknownAdreno; } const QByteArray &GLPlatform::glRendererString() const { return m_renderer; } const QByteArray &GLPlatform::glVendorString() const { return m_vendor; } const QByteArray &GLPlatform::glVersionString() const { return m_version; } const QByteArray &GLPlatform::glShadingLanguageVersionString() const { return m_glsl_version; } bool GLPlatform::isLooseBinding() const { return m_looseBinding; } bool GLPlatform::isVirtualMachine() const { return m_virtualMachine; } CompositingType GLPlatform::recommendedCompositor() const { return m_recommendedCompositor; } bool GLPlatform::preferBufferSubData() const { return m_preferBufferSubData; } OpenGLPlatformInterface GLPlatform::platformInterface() const { return m_platformInterface; } bool GLPlatform::isGLES() const { return m_gles; } void GLPlatform::cleanup() { delete s_platform; s_platform = nullptr; } } // namespace KWin diff --git a/libkwineffects/kwinglutils.cpp b/libkwineffects/kwinglutils.cpp index be0843348..fcd885128 100644 --- a/libkwineffects/kwinglutils.cpp +++ b/libkwineffects/kwinglutils.cpp @@ -1,2317 +1,2317 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2006-2007 Rivo Laks 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 "kwinglutils.h" // need to call GLTexturePrivate::initStatic() #include "kwingltexture_p.h" #include "kwineffects.h" #include "kwinglplatform.h" #include "logging_p.h" #include #include #include #include #include #include #include #include #include #include #include #include #define DEBUG_GLRENDERTARGET 0 #ifdef __GNUC__ # define likely(x) __builtin_expect(!!(x), 1) # define unlikely(x) __builtin_expect(!!(x), 0) #else # define likely(x) (x) # define unlikely(x) (x) #endif namespace KWin { // Variables // List of all supported GL extensions static QList glExtensions; // Functions void initGL(std::function resolveFunction) { // Get list of supported OpenGL extensions if (hasGLVersion(3, 0)) { int count; glGetIntegerv(GL_NUM_EXTENSIONS, &count); for (int i = 0; i < count; i++) { const QByteArray name = (const char *) glGetStringi(GL_EXTENSIONS, i); glExtensions << name; } } else glExtensions = QByteArray((const char*)glGetString(GL_EXTENSIONS)).split(' '); // handle OpenGL extensions functions glResolveFunctions(resolveFunction); GLTexturePrivate::initStatic(); GLRenderTarget::initStatic(); GLVertexBuffer::initStatic(); } void cleanupGL() { ShaderManager::cleanup(); GLTexturePrivate::cleanup(); GLRenderTarget::cleanup(); GLVertexBuffer::cleanup(); GLPlatform::cleanup(); glExtensions.clear(); } bool hasGLVersion(int major, int minor, int release) { return GLPlatform::instance()->glVersion() >= kVersionNumber(major, minor, release); } bool hasGLExtension(const QByteArray &extension) { return glExtensions.contains(extension); } QList openGLExtensions() { return glExtensions; } static QString formatGLError(GLenum err) { switch(err) { case GL_NO_ERROR: return QStringLiteral("GL_NO_ERROR"); case GL_INVALID_ENUM: return QStringLiteral("GL_INVALID_ENUM"); case GL_INVALID_VALUE: return QStringLiteral("GL_INVALID_VALUE"); case GL_INVALID_OPERATION: return QStringLiteral("GL_INVALID_OPERATION"); case GL_STACK_OVERFLOW: return QStringLiteral("GL_STACK_OVERFLOW"); case GL_STACK_UNDERFLOW: return QStringLiteral("GL_STACK_UNDERFLOW"); case GL_OUT_OF_MEMORY: return QStringLiteral("GL_OUT_OF_MEMORY"); default: return QLatin1String("0x") + QString::number(err, 16); } } bool checkGLError(const char* txt) { GLenum err = glGetError(); if (err == GL_CONTEXT_LOST) { qCWarning(LIBKWINGLUTILS) << "GL error: context lost"; return true; } bool hasError = false; while (err != GL_NO_ERROR) { qCWarning(LIBKWINGLUTILS) << "GL error (" << txt << "): " << formatGLError(err); hasError = true; err = glGetError(); if (err == GL_CONTEXT_LOST) { qCWarning(LIBKWINGLUTILS) << "GL error: context lost"; break; } } return hasError; } //**************************************** // GLShader //**************************************** GLShader::GLShader(unsigned int flags) : mValid(false) , mLocationsResolved(false) , mExplicitLinking(flags & ExplicitLinking) { mProgram = glCreateProgram(); } GLShader::GLShader(const QString& vertexfile, const QString& fragmentfile, unsigned int flags) : mValid(false) , mLocationsResolved(false) , mExplicitLinking(flags & ExplicitLinking) { mProgram = glCreateProgram(); loadFromFiles(vertexfile, fragmentfile); } GLShader::~GLShader() { if (mProgram) { glDeleteProgram(mProgram); } } bool GLShader::loadFromFiles(const QString &vertexFile, const QString &fragmentFile) { QFile vf(vertexFile); if (!vf.open(QIODevice::ReadOnly)) { qCCritical(LIBKWINGLUTILS) << "Couldn't open" << vertexFile << "for reading!"; return false; } const QByteArray vertexSource = vf.readAll(); QFile ff(fragmentFile); if (!ff.open(QIODevice::ReadOnly)) { qCCritical(LIBKWINGLUTILS) << "Couldn't open" << fragmentFile << "for reading!"; return false; } const QByteArray fragmentSource = ff.readAll(); return load(vertexSource, fragmentSource); } bool GLShader::link() { // Be optimistic mValid = true; glLinkProgram(mProgram); // Get the program info log int maxLength, length; glGetProgramiv(mProgram, GL_INFO_LOG_LENGTH, &maxLength); QByteArray log(maxLength, 0); glGetProgramInfoLog(mProgram, maxLength, &length, log.data()); // Make sure the program linked successfully int status; glGetProgramiv(mProgram, GL_LINK_STATUS, &status); if (status == 0) { qCCritical(LIBKWINGLUTILS) << "Failed to link shader:" << endl << log; mValid = false; } else if (length > 0) { qCDebug(LIBKWINGLUTILS) << "Shader link log:" << log; } return mValid; } const QByteArray GLShader::prepareSource(GLenum shaderType, const QByteArray &source) const { Q_UNUSED(shaderType) // Prepare the source code QByteArray ba; if (GLPlatform::instance()->isGLES() && GLPlatform::instance()->glslVersion() < kVersionNumber(3, 0)) { ba.append("precision highp float;\n"); } if (ShaderManager::instance()->isShaderDebug()) { ba.append("#define KWIN_SHADER_DEBUG 1\n"); } ba.append(source); if (GLPlatform::instance()->isGLES() && GLPlatform::instance()->glslVersion() >= kVersionNumber(3, 0)) { ba.replace("#version 140", "#version 300 es\n\nprecision highp float;\n"); } return ba; } bool GLShader::compile(GLuint program, GLenum shaderType, const QByteArray &source) const { GLuint shader = glCreateShader(shaderType); QByteArray preparedSource = prepareSource(shaderType, source); const char* src = preparedSource.constData(); glShaderSource(shader, 1, &src, nullptr); // Compile the shader glCompileShader(shader); // Get the shader info log int maxLength, length; glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &maxLength); QByteArray log(maxLength, 0); glGetShaderInfoLog(shader, maxLength, &length, log.data()); // Check the status int status; glGetShaderiv(shader, GL_COMPILE_STATUS, &status); if (status == 0) { const char *typeName = (shaderType == GL_VERTEX_SHADER ? "vertex" : "fragment"); qCCritical(LIBKWINGLUTILS) << "Failed to compile" << typeName << "shader:" << endl << log; } else if (length > 0) qCDebug(LIBKWINGLUTILS) << "Shader compile log:" << log; if (status != 0) glAttachShader(program, shader); glDeleteShader(shader); return status != 0; } bool GLShader::load(const QByteArray &vertexSource, const QByteArray &fragmentSource) { // Make sure shaders are actually supported if (!(GLPlatform::instance()->supports(GLSL) && // we lack shader branching for Texture2DRectangle everywhere - and it's probably not worth it GLPlatform::instance()->supports(TextureNPOT))) { qCCritical(LIBKWINGLUTILS) << "Shaders are not supported"; return false; } mValid = false; // Compile the vertex shader if (!vertexSource.isEmpty()) { bool success = compile(mProgram, GL_VERTEX_SHADER, vertexSource); if (!success) return false; } // Compile the fragment shader if (!fragmentSource.isEmpty()) { bool success = compile(mProgram, GL_FRAGMENT_SHADER, fragmentSource); if (!success) return false; } if (mExplicitLinking) return true; // link() sets mValid return link(); } void GLShader::bindAttributeLocation(const char *name, int index) { glBindAttribLocation(mProgram, index, name); } void GLShader::bindFragDataLocation(const char *name, int index) { if (!GLPlatform::instance()->isGLES() && (hasGLVersion(3, 0) || hasGLExtension(QByteArrayLiteral("GL_EXT_gpu_shader4")))) glBindFragDataLocation(mProgram, index, name); } void GLShader::bind() { glUseProgram(mProgram); } void GLShader::unbind() { glUseProgram(0); } void GLShader::resolveLocations() { if (mLocationsResolved) return; mMatrixLocation[TextureMatrix] = uniformLocation("textureMatrix"); mMatrixLocation[ProjectionMatrix] = uniformLocation("projection"); mMatrixLocation[ModelViewMatrix] = uniformLocation("modelview"); mMatrixLocation[ModelViewProjectionMatrix] = uniformLocation("modelViewProjectionMatrix"); mMatrixLocation[WindowTransformation] = uniformLocation("windowTransformation"); mMatrixLocation[ScreenTransformation] = uniformLocation("screenTransformation"); mVec2Location[Offset] = uniformLocation("offset"); mVec4Location[ModulationConstant] = uniformLocation("modulation"); mFloatLocation[Saturation] = uniformLocation("saturation"); mColorLocation[Color] = uniformLocation("geometryColor"); mLocationsResolved = true; } int GLShader::uniformLocation(const char *name) { const int location = glGetUniformLocation(mProgram, name); return location; } bool GLShader::setUniform(GLShader::MatrixUniform uniform, const QMatrix4x4 &matrix) { resolveLocations(); return setUniform(mMatrixLocation[uniform], matrix); } bool GLShader::setUniform(GLShader::Vec2Uniform uniform, const QVector2D &value) { resolveLocations(); return setUniform(mVec2Location[uniform], value); } bool GLShader::setUniform(GLShader::Vec4Uniform uniform, const QVector4D &value) { resolveLocations(); return setUniform(mVec4Location[uniform], value); } bool GLShader::setUniform(GLShader::FloatUniform uniform, float value) { resolveLocations(); return setUniform(mFloatLocation[uniform], value); } bool GLShader::setUniform(GLShader::IntUniform uniform, int value) { resolveLocations(); return setUniform(mIntLocation[uniform], value); } bool GLShader::setUniform(GLShader::ColorUniform uniform, const QVector4D &value) { resolveLocations(); return setUniform(mColorLocation[uniform], value); } bool GLShader::setUniform(GLShader::ColorUniform uniform, const QColor &value) { resolveLocations(); return setUniform(mColorLocation[uniform], value); } bool GLShader::setUniform(const char *name, float value) { const int location = uniformLocation(name); return setUniform(location, value); } bool GLShader::setUniform(const char *name, int value) { const int location = uniformLocation(name); return setUniform(location, value); } bool GLShader::setUniform(const char *name, const QVector2D& value) { const int location = uniformLocation(name); return setUniform(location, value); } bool GLShader::setUniform(const char *name, const QVector3D& value) { const int location = uniformLocation(name); return setUniform(location, value); } bool GLShader::setUniform(const char *name, const QVector4D& value) { const int location = uniformLocation(name); return setUniform(location, value); } bool GLShader::setUniform(const char *name, const QMatrix4x4& value) { const int location = uniformLocation(name); return setUniform(location, value); } bool GLShader::setUniform(const char *name, const QColor& color) { const int location = uniformLocation(name); return setUniform(location, color); } bool GLShader::setUniform(int location, float value) { if (location >= 0) { glUniform1f(location, value); } return (location >= 0); } bool GLShader::setUniform(int location, int value) { if (location >= 0) { glUniform1i(location, value); } return (location >= 0); } bool GLShader::setUniform(int location, const QVector2D &value) { if (location >= 0) { glUniform2fv(location, 1, (const GLfloat*)&value); } return (location >= 0); } bool GLShader::setUniform(int location, const QVector3D &value) { if (location >= 0) { glUniform3fv(location, 1, (const GLfloat*)&value); } return (location >= 0); } bool GLShader::setUniform(int location, const QVector4D &value) { if (location >= 0) { glUniform4fv(location, 1, (const GLfloat*)&value); } return (location >= 0); } bool GLShader::setUniform(int location, const QMatrix4x4 &value) { if (location >= 0) { GLfloat m[16]; const auto *data = value.constData(); // i is column, j is row for m for (int i = 0; i < 16; ++i) { m[i] = data[i]; } glUniformMatrix4fv(location, 1, GL_FALSE, m); } return (location >= 0); } bool GLShader::setUniform(int location, const QColor &color) { if (location >= 0) { glUniform4f(location, color.redF(), color.greenF(), color.blueF(), color.alphaF()); } return (location >= 0); } int GLShader::attributeLocation(const char* name) { int location = glGetAttribLocation(mProgram, name); return location; } bool GLShader::setAttribute(const char* name, float value) { int location = attributeLocation(name); if (location >= 0) { glVertexAttrib1f(location, value); } return (location >= 0); } QMatrix4x4 GLShader::getUniformMatrix4x4(const char* name) { int location = uniformLocation(name); if (location >= 0) { GLfloat m[16]; glGetnUniformfv(mProgram, location, sizeof(m), m); QMatrix4x4 matrix(m[0], m[4], m[8], m[12], m[1], m[5], m[9], m[13], m[2], m[6], m[10], m[14], m[3], m[7], m[11], m[15]); matrix.optimize(); return matrix; } else { return QMatrix4x4(); } } //**************************************** // ShaderManager //**************************************** ShaderManager *ShaderManager::s_shaderManager = nullptr; ShaderManager *ShaderManager::instance() { if (!s_shaderManager) { s_shaderManager = new ShaderManager(); } return s_shaderManager; } void ShaderManager::cleanup() { delete s_shaderManager; s_shaderManager = nullptr; } ShaderManager::ShaderManager() { m_debug = qstrcmp(qgetenv("KWIN_GL_DEBUG"), "1") == 0; const qint64 coreVersionNumber = GLPlatform::instance()->isGLES() ? kVersionNumber(3, 0) : kVersionNumber(1, 40); if (GLPlatform::instance()->glslVersion() >= coreVersionNumber) { m_resourcePath = QStringLiteral(":/effect-shaders-1.40/"); } else { m_resourcePath = QStringLiteral(":/effect-shaders-1.10/"); } } ShaderManager::~ShaderManager() { while (!m_boundShaders.isEmpty()) { popShader(); } qDeleteAll(m_shaderHash); m_shaderHash.clear(); } static bool fuzzyCompare(const QVector4D &lhs, const QVector4D &rhs) { const float epsilon = 1.0f / 255.0f; return lhs[0] >= rhs[0] - epsilon && lhs[0] <= rhs[0] + epsilon && lhs[1] >= rhs[1] - epsilon && lhs[1] <= rhs[1] + epsilon && lhs[2] >= rhs[2] - epsilon && lhs[2] <= rhs[2] + epsilon && lhs[3] >= rhs[3] - epsilon && lhs[3] <= rhs[3] + epsilon; } static bool checkPixel(int x, int y, const QVector4D &expected, const char *file, int line) { uint8_t data[4]; glReadnPixels(x, y, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, 4, data); const QVector4D pixel{data[0] / 255.f, data[1] / 255.f, data[2] / 255.f, data[3] / 255.f}; if (fuzzyCompare(pixel, expected)) return true; QMessageLogger(file, line, nullptr).warning() << "Pixel was" << pixel << "expected" << expected; return false; } #define CHECK_PIXEL(x, y, expected) \ checkPixel(x, y, expected, __FILE__, __LINE__) static QVector4D adjustSaturation(const QVector4D &color, float saturation) { const float gray = QVector3D::dotProduct(color.toVector3D(), {0.2126, 0.7152, 0.0722}); return QVector4D{gray, gray, gray, color.w()} * (1.0f - saturation) + color * saturation; } bool ShaderManager::selfTest() { bool pass = true; if (!GLRenderTarget::supported()) { qCWarning(LIBKWINGLUTILS) << "Framebuffer objects not supported - skipping shader tests"; return true; } if (GLPlatform::instance()->isNvidia() && GLPlatform::instance()->glRendererString().contains("Quadro")) { qCWarning(LIBKWINGLUTILS) << "Skipping self test as it is reported to return false positive results on Quadro hardware"; return true; } if (GLPlatform::instance()->isMesaDriver() && GLPlatform::instance()->mesaVersion() >= kVersionNumber(17, 0)) { qCWarning(LIBKWINGLUTILS) << "Skipping self test as it is reported to return false positive results on Mesa drivers"; return true; } // Create the source texture QImage image(2, 2, QImage::Format_ARGB32_Premultiplied); image.setPixel(0, 0, 0xffff0000); // Red image.setPixel(1, 0, 0xff00ff00); // Green image.setPixel(0, 1, 0xff0000ff); // Blue image.setPixel(1, 1, 0xffffffff); // White GLTexture src(image); src.setFilter(GL_NEAREST); // Create the render target GLTexture dst(GL_RGBA8, 32, 32); GLRenderTarget fbo(dst); GLRenderTarget::pushRenderTarget(&fbo); // Set up the vertex buffer GLVertexBuffer *vbo = GLVertexBuffer::streamingBuffer(); const GLVertexAttrib attribs[] { { VA_Position, 2, GL_FLOAT, offsetof(GLVertex2D, position) }, { VA_TexCoord, 2, GL_FLOAT, offsetof(GLVertex2D, texcoord) }, }; vbo->setAttribLayout(attribs, 2, sizeof(GLVertex2D)); GLVertex2D *verts = (GLVertex2D*) vbo->map(6 * sizeof(GLVertex2D)); verts[0] = GLVertex2D{{0, 0}, {0, 0}}; // Top left verts[1] = GLVertex2D{{0, 32}, {0, 1}}; // Bottom left verts[2] = GLVertex2D{{32, 0}, {1, 0}}; // Top right verts[3] = GLVertex2D{{32, 0}, {1, 0}}; // Top right verts[4] = GLVertex2D{{0, 32}, {0, 1}}; // Bottom left verts[5] = GLVertex2D{{32, 32}, {1, 1}}; // Bottom right vbo->unmap(); vbo->bindArrays(); glViewport(0, 0, 32, 32); glClearColor(0, 0, 0, 0); // Set up the projection matrix QMatrix4x4 matrix; matrix.ortho(QRect(0, 0, 32, 32)); // Bind the source texture src.bind(); const QVector4D red {1.0f, 0.0f, 0.0f, 1.0f}; const QVector4D green {0.0f, 1.0f, 0.0f, 1.0f}; const QVector4D blue {0.0f, 0.0f, 1.0f, 1.0f}; const QVector4D white {1.0f, 1.0f, 1.0f, 1.0f}; // Note: To see the line number in error messages, set // QT_MESSAGE_PATTERN="%{message} (%{file}:%{line})" // Test solid color GLShader *shader = pushShader(ShaderTrait::UniformColor); if (shader->isValid()) { glClear(GL_COLOR_BUFFER_BIT); shader->setUniform(GLShader::ModelViewProjectionMatrix, matrix); shader->setUniform(GLShader::Color, green); vbo->draw(GL_TRIANGLES, 0, 6); pass = CHECK_PIXEL(8, 24, green) && pass; pass = CHECK_PIXEL(24, 24, green) && pass; pass = CHECK_PIXEL(8, 8, green) && pass; pass = CHECK_PIXEL(24, 8, green) && pass; } else { pass = false; } popShader(); // Test texture mapping shader = pushShader(ShaderTrait::MapTexture); if (shader->isValid()) { glClear(GL_COLOR_BUFFER_BIT); shader->setUniform(GLShader::ModelViewProjectionMatrix, matrix); vbo->draw(GL_TRIANGLES, 0, 6); pass = CHECK_PIXEL(8, 24, red) && pass; pass = CHECK_PIXEL(24, 24, green) && pass; pass = CHECK_PIXEL(8, 8, blue) && pass; pass = CHECK_PIXEL(24, 8, white) && pass; } else { pass = false; } popShader(); // Test saturation filter shader = pushShader(ShaderTrait::MapTexture | ShaderTrait::AdjustSaturation); if (shader->isValid()) { glClear(GL_COLOR_BUFFER_BIT); const float saturation = .3; shader->setUniform(GLShader::ModelViewProjectionMatrix, matrix); shader->setUniform(GLShader::Saturation, saturation); vbo->draw(GL_TRIANGLES, 0, 6); pass = CHECK_PIXEL(8, 24, adjustSaturation(red, saturation)) && pass; pass = CHECK_PIXEL(24, 24, adjustSaturation(green, saturation)) && pass; pass = CHECK_PIXEL(8, 8, adjustSaturation(blue, saturation)) && pass; pass = CHECK_PIXEL(24, 8, adjustSaturation(white, saturation)) && pass; } else { pass = false; } popShader(); // Test modulation filter shader = pushShader(ShaderTrait::MapTexture | ShaderTrait::Modulate); if (shader->isValid()) { glClear(GL_COLOR_BUFFER_BIT); const QVector4D modulation{.3f, .4f, .5f, .6f}; shader->setUniform(GLShader::ModelViewProjectionMatrix, matrix); shader->setUniform(GLShader::ModulationConstant, modulation); vbo->draw(GL_TRIANGLES, 0, 6); pass = CHECK_PIXEL(8, 24, red * modulation) && pass; pass = CHECK_PIXEL(24, 24, green * modulation) && pass; pass = CHECK_PIXEL(8, 8, blue * modulation) && pass; pass = CHECK_PIXEL(24, 8, white * modulation) && pass; } else { pass = false; } popShader(); // Test saturation + modulation shader = pushShader(ShaderTrait::MapTexture | ShaderTrait::AdjustSaturation | ShaderTrait::Modulate); if (shader->isValid()) { glClear(GL_COLOR_BUFFER_BIT); const QVector4D modulation{.3f, .4f, .5f, .6f}; const float saturation = .3; shader->setUniform(GLShader::ModelViewProjectionMatrix, matrix); shader->setUniform(GLShader::ModulationConstant, modulation); shader->setUniform(GLShader::Saturation, saturation); vbo->draw(GL_TRIANGLES, 0, 6); pass = CHECK_PIXEL(8, 24, adjustSaturation(red * modulation, saturation)) && pass; pass = CHECK_PIXEL(24, 24, adjustSaturation(green * modulation, saturation)) && pass; pass = CHECK_PIXEL(8, 8, adjustSaturation(blue * modulation, saturation)) && pass; pass = CHECK_PIXEL(24, 8, adjustSaturation(white * modulation, saturation)) && pass; } else { pass = false; } popShader(); vbo->unbindArrays(); GLRenderTarget::popRenderTarget(); return pass; } QByteArray ShaderManager::generateVertexSource(ShaderTraits traits) const { QByteArray source; QTextStream stream(&source); GLPlatform * const gl = GLPlatform::instance(); QByteArray attribute, varying; if (!gl->isGLES()) { const bool glsl_140 = gl->glslVersion() >= kVersionNumber(1, 40); attribute = glsl_140 ? QByteArrayLiteral("in") : QByteArrayLiteral("attribute"); varying = glsl_140 ? QByteArrayLiteral("out") : QByteArrayLiteral("varying"); if (glsl_140) stream << "#version 140\n\n"; } else { const bool glsl_es_300 = gl->glslVersion() >= kVersionNumber(3, 0); attribute = glsl_es_300 ? QByteArrayLiteral("in") : QByteArrayLiteral("attribute"); varying = glsl_es_300 ? QByteArrayLiteral("out") : QByteArrayLiteral("varying"); if (glsl_es_300) stream << "#version 300 es\n\n"; } stream << attribute << " vec4 position;\n"; if (traits & ShaderTrait::MapTexture) { stream << attribute << " vec4 texcoord;\n\n"; stream << varying << " vec2 texcoord0;\n\n"; } else stream << "\n"; stream << "uniform mat4 modelViewProjectionMatrix;\n\n"; stream << "void main()\n{\n"; if (traits & ShaderTrait::MapTexture) stream << " texcoord0 = texcoord.st;\n"; stream << " gl_Position = modelViewProjectionMatrix * position;\n"; stream << "}\n"; stream.flush(); return source; } QByteArray ShaderManager::generateFragmentSource(ShaderTraits traits) const { QByteArray source; QTextStream stream(&source); GLPlatform * const gl = GLPlatform::instance(); QByteArray varying, output, textureLookup; if (!gl->isGLES()) { const bool glsl_140 = gl->glslVersion() >= kVersionNumber(1, 40); if (glsl_140) stream << "#version 140\n\n"; varying = glsl_140 ? QByteArrayLiteral("in") : QByteArrayLiteral("varying"); textureLookup = glsl_140 ? QByteArrayLiteral("texture") : QByteArrayLiteral("texture2D"); output = glsl_140 ? QByteArrayLiteral("fragColor") : QByteArrayLiteral("gl_FragColor"); } else { const bool glsl_es_300 = GLPlatform::instance()->glslVersion() >= kVersionNumber(3, 0); if (glsl_es_300) stream << "#version 300 es\n\n"; // From the GLSL ES specification: // // "The fragment language has no default precision qualifier for floating point types." stream << "precision highp float;\n\n"; varying = glsl_es_300 ? QByteArrayLiteral("in") : QByteArrayLiteral("varying"); textureLookup = glsl_es_300 ? QByteArrayLiteral("texture") : QByteArrayLiteral("texture2D"); output = glsl_es_300 ? QByteArrayLiteral("fragColor") : QByteArrayLiteral("gl_FragColor"); } if (traits & ShaderTrait::MapTexture) { stream << "uniform sampler2D sampler;\n"; if (traits & ShaderTrait::Modulate) stream << "uniform vec4 modulation;\n"; if (traits & ShaderTrait::AdjustSaturation) stream << "uniform float saturation;\n"; stream << "\n" << varying << " vec2 texcoord0;\n"; } else if (traits & ShaderTrait::UniformColor) stream << "uniform vec4 geometryColor;\n"; if (output != QByteArrayLiteral("gl_FragColor")) stream << "\nout vec4 " << output << ";\n"; stream << "\nvoid main(void)\n{\n"; if (traits & ShaderTrait::MapTexture) { if (traits & (ShaderTrait::Modulate | ShaderTrait::AdjustSaturation)) { stream << " vec4 texel = " << textureLookup << "(sampler, texcoord0);\n"; if (traits & ShaderTrait::Modulate) stream << " texel *= modulation;\n"; if (traits & ShaderTrait::AdjustSaturation) stream << " texel.rgb = mix(vec3(dot(texel.rgb, vec3(0.2126, 0.7152, 0.0722))), texel.rgb, saturation);\n"; stream << " " << output << " = texel;\n"; } else { stream << " " << output << " = " << textureLookup << "(sampler, texcoord0);\n"; } } else if (traits & ShaderTrait::UniformColor) stream << " " << output << " = geometryColor;\n"; stream << "}"; stream.flush(); return source; } GLShader *ShaderManager::generateShader(ShaderTraits traits) { return generateCustomShader(traits); } GLShader *ShaderManager::generateCustomShader(ShaderTraits traits, const QByteArray &vertexSource, const QByteArray &fragmentSource) { const QByteArray vertex = vertexSource.isEmpty() ? generateVertexSource(traits) : vertexSource; const QByteArray fragment = fragmentSource.isEmpty() ? generateFragmentSource(traits) : fragmentSource; #if 0 qCDebug(LIBKWINGLUTILS) << "**************"; qCDebug(LIBKWINGLUTILS) << vertex; qCDebug(LIBKWINGLUTILS) << "**************"; qCDebug(LIBKWINGLUTILS) << fragment; qCDebug(LIBKWINGLUTILS) << "**************"; #endif GLShader *shader = new GLShader(GLShader::ExplicitLinking); shader->load(vertex, fragment); shader->bindAttributeLocation("position", VA_Position); shader->bindAttributeLocation("texcoord", VA_TexCoord); shader->bindFragDataLocation("fragColor", 0); shader->link(); return shader; } GLShader *ShaderManager::generateShaderFromResources(ShaderTraits traits, const QString &vertexFile, const QString &fragmentFile) { auto loadShaderFile = [this] (const QString &fileName) { QFile file(m_resourcePath + fileName); if (file.open(QIODevice::ReadOnly)) { return file.readAll(); } qCCritical(LIBKWINGLUTILS) << "Failed to read shader " << fileName; return QByteArray(); }; QByteArray vertexSource; QByteArray fragmentSource; if (!vertexFile.isEmpty()) { vertexSource = loadShaderFile(vertexFile); if (vertexSource.isEmpty()) { return new GLShader(); } } if (!fragmentFile.isEmpty()) { fragmentSource = loadShaderFile(fragmentFile); if (fragmentSource.isEmpty()) { return new GLShader(); } } return generateCustomShader(traits, vertexSource, fragmentSource); } GLShader *ShaderManager::shader(ShaderTraits traits) { GLShader *shader = m_shaderHash.value(traits); if (!shader) { shader = generateShader(traits); m_shaderHash.insert(traits, shader); } return shader; } GLShader *ShaderManager::getBoundShader() const { if (m_boundShaders.isEmpty()) { return nullptr; } else { return m_boundShaders.top(); } } bool ShaderManager::isShaderBound() const { return !m_boundShaders.isEmpty(); } bool ShaderManager::isShaderDebug() const { return m_debug; } GLShader *ShaderManager::pushShader(ShaderTraits traits) { GLShader *shader = this->shader(traits); pushShader(shader); return shader; } void ShaderManager::pushShader(GLShader *shader) { // only bind shader if it is not already bound if (shader != getBoundShader()) { shader->bind(); } m_boundShaders.push(shader); } void ShaderManager::popShader() { if (m_boundShaders.isEmpty()) { return; } GLShader *shader = m_boundShaders.pop(); if (m_boundShaders.isEmpty()) { // no more shader bound - unbind shader->unbind(); } else if (shader != m_boundShaders.top()) { // only rebind if a different shader is on top of stack m_boundShaders.top()->bind(); } } void ShaderManager::bindFragDataLocations(GLShader *shader) { shader->bindFragDataLocation("fragColor", 0); } void ShaderManager::bindAttributeLocations(GLShader *shader) const { shader->bindAttributeLocation("vertex", VA_Position); shader->bindAttributeLocation("texCoord", VA_TexCoord); } GLShader *ShaderManager::loadShaderFromCode(const QByteArray &vertexSource, const QByteArray &fragmentSource) { GLShader *shader = new GLShader(GLShader::ExplicitLinking); shader->load(vertexSource, fragmentSource); bindAttributeLocations(shader); bindFragDataLocations(shader); shader->link(); return shader; } /*** GLRenderTarget ***/ bool GLRenderTarget::sSupported = false; bool GLRenderTarget::s_blitSupported = false; QStack GLRenderTarget::s_renderTargets = QStack(); QSize GLRenderTarget::s_virtualScreenSize; QRect GLRenderTarget::s_virtualScreenGeometry; qreal GLRenderTarget::s_virtualScreenScale = 1.0; GLint GLRenderTarget::s_virtualScreenViewport[4]; void GLRenderTarget::initStatic() { if (GLPlatform::instance()->isGLES()) { sSupported = true; s_blitSupported = hasGLVersion(3, 0); } else { sSupported = hasGLVersion(3, 0) || hasGLExtension(QByteArrayLiteral("GL_ARB_framebuffer_object")) || hasGLExtension(QByteArrayLiteral("GL_EXT_framebuffer_object")); s_blitSupported = hasGLVersion(3, 0) || hasGLExtension(QByteArrayLiteral("GL_ARB_framebuffer_object")) || hasGLExtension(QByteArrayLiteral("GL_EXT_framebuffer_blit")); } } void GLRenderTarget::cleanup() { Q_ASSERT(s_renderTargets.isEmpty()); sSupported = false; s_blitSupported = false; } bool GLRenderTarget::isRenderTargetBound() { return !s_renderTargets.isEmpty(); } bool GLRenderTarget::blitSupported() { return s_blitSupported; } void GLRenderTarget::pushRenderTarget(GLRenderTarget* target) { if (s_renderTargets.isEmpty()) { glGetIntegerv(GL_VIEWPORT, s_virtualScreenViewport); } target->enable(); s_renderTargets.push(target); } void GLRenderTarget::pushRenderTargets(QStack targets) { if (s_renderTargets.isEmpty()) { glGetIntegerv(GL_VIEWPORT, s_virtualScreenViewport); } targets.top()->enable(); s_renderTargets.append(targets); } GLRenderTarget* GLRenderTarget::popRenderTarget() { GLRenderTarget* ret = s_renderTargets.pop(); ret->setTextureDirty(); if (!s_renderTargets.isEmpty()) { s_renderTargets.top()->enable(); } else { ret->disable(); glViewport (s_virtualScreenViewport[0], s_virtualScreenViewport[1], s_virtualScreenViewport[2], s_virtualScreenViewport[3]); } return ret; } GLRenderTarget::GLRenderTarget() { // Reset variables mValid = false; mTexture = GLTexture(); } GLRenderTarget::GLRenderTarget(const GLTexture& color) { // Reset variables mValid = false; mTexture = color; // Make sure FBO is supported if (sSupported && !mTexture.isNull()) { initFBO(); } else qCCritical(LIBKWINGLUTILS) << "Render targets aren't supported!"; } GLRenderTarget::~GLRenderTarget() { if (mValid) { glDeleteFramebuffers(1, &mFramebuffer); } } bool GLRenderTarget::enable() { if (!mValid) { initFBO(); } if (!valid()) { qCCritical(LIBKWINGLUTILS) << "Can't enable invalid render target!"; return false; } glBindFramebuffer(GL_FRAMEBUFFER, mFramebuffer); glViewport(0, 0, mTexture.width(), mTexture.height()); mTexture.setDirty(); return true; } bool GLRenderTarget::disable() { if (!mValid) { initFBO(); } if (!valid()) { qCCritical(LIBKWINGLUTILS) << "Can't disable invalid render target!"; return false; } glBindFramebuffer(GL_FRAMEBUFFER, 0); mTexture.setDirty(); return true; } static QString formatFramebufferStatus(GLenum status) { switch(status) { case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT: // An attachment is the wrong type / is invalid / has 0 width or height return QStringLiteral("GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT"); case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT: // There are no images attached to the framebuffer return QStringLiteral("GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT"); case GL_FRAMEBUFFER_UNSUPPORTED: // A format or the combination of formats of the attachments is unsupported return QStringLiteral("GL_FRAMEBUFFER_UNSUPPORTED"); case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_EXT: // Not all attached images have the same width and height return QStringLiteral("GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_EXT"); case GL_FRAMEBUFFER_INCOMPLETE_FORMATS_EXT: // The color attachments don't have the same format return QStringLiteral("GL_FRAMEBUFFER_INCOMPLETE_FORMATS_EXT"); case GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE_EXT: // The attachments don't have the same number of samples return QStringLiteral("GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE"); case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER_EXT: // The draw buffer is missing return QStringLiteral("GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER"); case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER_EXT: // The read buffer is missing return QStringLiteral("GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER"); default: return QStringLiteral("Unknown (0x") + QString::number(status, 16) + QStringLiteral(")"); } } void GLRenderTarget::initFBO() { #if DEBUG_GLRENDERTARGET GLenum err = glGetError(); if (err != GL_NO_ERROR) qCCritical(LIBKWINGLUTILS) << "Error status when entering GLRenderTarget::initFBO: " << formatGLError(err); #endif glGenFramebuffers(1, &mFramebuffer); #if DEBUG_GLRENDERTARGET if ((err = glGetError()) != GL_NO_ERROR) { qCCritical(LIBKWINGLUTILS) << "glGenFramebuffers failed: " << formatGLError(err); return; } #endif glBindFramebuffer(GL_FRAMEBUFFER, mFramebuffer); #if DEBUG_GLRENDERTARGET if ((err = glGetError()) != GL_NO_ERROR) { qCCritical(LIBKWINGLUTILS) << "glBindFramebuffer failed: " << formatGLError(err); glDeleteFramebuffers(1, &mFramebuffer); return; } #endif glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, mTexture.target(), mTexture.texture(), 0); #if DEBUG_GLRENDERTARGET if ((err = glGetError()) != GL_NO_ERROR) { qCCritical(LIBKWINGLUTILS) << "glFramebufferTexture2D failed: " << formatGLError(err); glBindFramebuffer(GL_FRAMEBUFFER, 0); glDeleteFramebuffers(1, &mFramebuffer); return; } #endif const GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); glBindFramebuffer(GL_FRAMEBUFFER, 0); if (status != GL_FRAMEBUFFER_COMPLETE) { // We have an incomplete framebuffer, consider it invalid if (status == 0) qCCritical(LIBKWINGLUTILS) << "glCheckFramebufferStatus failed: " << formatGLError(glGetError()); else qCCritical(LIBKWINGLUTILS) << "Invalid framebuffer status: " << formatFramebufferStatus(status); glDeleteFramebuffers(1, &mFramebuffer); return; } mValid = true; } void GLRenderTarget::blitFromFramebuffer(const QRect &source, const QRect &destination, GLenum filter) { if (!GLRenderTarget::blitSupported()) { return; } if (!mValid) { initFBO(); } GLRenderTarget::pushRenderTarget(this); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, mFramebuffer); glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); const QRect s = source.isNull() ? s_virtualScreenGeometry : source; const QRect d = destination.isNull() ? QRect(0, 0, mTexture.width(), mTexture.height()) : destination; glBlitFramebuffer((s.x() - s_virtualScreenGeometry.x()) * s_virtualScreenScale, (s_virtualScreenGeometry.height() - (s.y() - s_virtualScreenGeometry.y() + s.height())) * s_virtualScreenScale, (s.x() - s_virtualScreenGeometry.x() + s.width()) * s_virtualScreenScale, (s_virtualScreenGeometry.height() - (s.y() - s_virtualScreenGeometry.y())) * s_virtualScreenScale, d.x(), mTexture.height() - d.y() - d.height(), d.x() + d.width(), mTexture.height() - d.y(), GL_COLOR_BUFFER_BIT, filter); GLRenderTarget::popRenderTarget(); } void GLRenderTarget::attachTexture(const GLTexture& target) { if (!mValid) { initFBO(); } if (mTexture.texture() == target.texture()) { return; } pushRenderTarget(this); mTexture = target; glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, mTexture.target(), mTexture.texture(), 0); popRenderTarget(); } void GLRenderTarget::detachTexture() { if (mTexture.isNull()) { return; } pushRenderTarget(this); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, mTexture.target(), 0, 0); popRenderTarget(); } // ------------------------------------------------------------------ static const uint16_t indices[] = { 1, 0, 3, 3, 2, 1, 5, 4, 7, 7, 6, 5, 9, 8, 11, 11, 10, 9, 13, 12, 15, 15, 14, 13, 17, 16, 19, 19, 18, 17, 21, 20, 23, 23, 22, 21, 25, 24, 27, 27, 26, 25, 29, 28, 31, 31, 30, 29, 33, 32, 35, 35, 34, 33, 37, 36, 39, 39, 38, 37, 41, 40, 43, 43, 42, 41, 45, 44, 47, 47, 46, 45, 49, 48, 51, 51, 50, 49, 53, 52, 55, 55, 54, 53, 57, 56, 59, 59, 58, 57, 61, 60, 63, 63, 62, 61, 65, 64, 67, 67, 66, 65, 69, 68, 71, 71, 70, 69, 73, 72, 75, 75, 74, 73, 77, 76, 79, 79, 78, 77, 81, 80, 83, 83, 82, 81, 85, 84, 87, 87, 86, 85, 89, 88, 91, 91, 90, 89, 93, 92, 95, 95, 94, 93, 97, 96, 99, 99, 98, 97, 101, 100, 103, 103, 102, 101, 105, 104, 107, 107, 106, 105, 109, 108, 111, 111, 110, 109, 113, 112, 115, 115, 114, 113, 117, 116, 119, 119, 118, 117, 121, 120, 123, 123, 122, 121, 125, 124, 127, 127, 126, 125, 129, 128, 131, 131, 130, 129, 133, 132, 135, 135, 134, 133, 137, 136, 139, 139, 138, 137, 141, 140, 143, 143, 142, 141, 145, 144, 147, 147, 146, 145, 149, 148, 151, 151, 150, 149, 153, 152, 155, 155, 154, 153, 157, 156, 159, 159, 158, 157, 161, 160, 163, 163, 162, 161, 165, 164, 167, 167, 166, 165, 169, 168, 171, 171, 170, 169, 173, 172, 175, 175, 174, 173, 177, 176, 179, 179, 178, 177, 181, 180, 183, 183, 182, 181, 185, 184, 187, 187, 186, 185, 189, 188, 191, 191, 190, 189, 193, 192, 195, 195, 194, 193, 197, 196, 199, 199, 198, 197, 201, 200, 203, 203, 202, 201, 205, 204, 207, 207, 206, 205, 209, 208, 211, 211, 210, 209, 213, 212, 215, 215, 214, 213, 217, 216, 219, 219, 218, 217, 221, 220, 223, 223, 222, 221, 225, 224, 227, 227, 226, 225, 229, 228, 231, 231, 230, 229, 233, 232, 235, 235, 234, 233, 237, 236, 239, 239, 238, 237, 241, 240, 243, 243, 242, 241, 245, 244, 247, 247, 246, 245, 249, 248, 251, 251, 250, 249, 253, 252, 255, 255, 254, 253, 257, 256, 259, 259, 258, 257, 261, 260, 263, 263, 262, 261, 265, 264, 267, 267, 266, 265, 269, 268, 271, 271, 270, 269, 273, 272, 275, 275, 274, 273, 277, 276, 279, 279, 278, 277, 281, 280, 283, 283, 282, 281, 285, 284, 287, 287, 286, 285, 289, 288, 291, 291, 290, 289, 293, 292, 295, 295, 294, 293, 297, 296, 299, 299, 298, 297, 301, 300, 303, 303, 302, 301, 305, 304, 307, 307, 306, 305, 309, 308, 311, 311, 310, 309, 313, 312, 315, 315, 314, 313, 317, 316, 319, 319, 318, 317, 321, 320, 323, 323, 322, 321, 325, 324, 327, 327, 326, 325, 329, 328, 331, 331, 330, 329, 333, 332, 335, 335, 334, 333, 337, 336, 339, 339, 338, 337, 341, 340, 343, 343, 342, 341, 345, 344, 347, 347, 346, 345, 349, 348, 351, 351, 350, 349, 353, 352, 355, 355, 354, 353, 357, 356, 359, 359, 358, 357, 361, 360, 363, 363, 362, 361, 365, 364, 367, 367, 366, 365, 369, 368, 371, 371, 370, 369, 373, 372, 375, 375, 374, 373, 377, 376, 379, 379, 378, 377, 381, 380, 383, 383, 382, 381, 385, 384, 387, 387, 386, 385, 389, 388, 391, 391, 390, 389, 393, 392, 395, 395, 394, 393, 397, 396, 399, 399, 398, 397, 401, 400, 403, 403, 402, 401, 405, 404, 407, 407, 406, 405, 409, 408, 411, 411, 410, 409, 413, 412, 415, 415, 414, 413, 417, 416, 419, 419, 418, 417, 421, 420, 423, 423, 422, 421, 425, 424, 427, 427, 426, 425, 429, 428, 431, 431, 430, 429, 433, 432, 435, 435, 434, 433, 437, 436, 439, 439, 438, 437, 441, 440, 443, 443, 442, 441, 445, 444, 447, 447, 446, 445, 449, 448, 451, 451, 450, 449, 453, 452, 455, 455, 454, 453, 457, 456, 459, 459, 458, 457, 461, 460, 463, 463, 462, 461, 465, 464, 467, 467, 466, 465, 469, 468, 471, 471, 470, 469, 473, 472, 475, 475, 474, 473, 477, 476, 479, 479, 478, 477, 481, 480, 483, 483, 482, 481, 485, 484, 487, 487, 486, 485, 489, 488, 491, 491, 490, 489, 493, 492, 495, 495, 494, 493, 497, 496, 499, 499, 498, 497, 501, 500, 503, 503, 502, 501, 505, 504, 507, 507, 506, 505, 509, 508, 511, 511, 510, 509, 513, 512, 515, 515, 514, 513, 517, 516, 519, 519, 518, 517, 521, 520, 523, 523, 522, 521, 525, 524, 527, 527, 526, 525, 529, 528, 531, 531, 530, 529, 533, 532, 535, 535, 534, 533, 537, 536, 539, 539, 538, 537, 541, 540, 543, 543, 542, 541, 545, 544, 547, 547, 546, 545, 549, 548, 551, 551, 550, 549, 553, 552, 555, 555, 554, 553, 557, 556, 559, 559, 558, 557, 561, 560, 563, 563, 562, 561, 565, 564, 567, 567, 566, 565, 569, 568, 571, 571, 570, 569, 573, 572, 575, 575, 574, 573, 577, 576, 579, 579, 578, 577, 581, 580, 583, 583, 582, 581, 585, 584, 587, 587, 586, 585, 589, 588, 591, 591, 590, 589, 593, 592, 595, 595, 594, 593, 597, 596, 599, 599, 598, 597, 601, 600, 603, 603, 602, 601, 605, 604, 607, 607, 606, 605, 609, 608, 611, 611, 610, 609, 613, 612, 615, 615, 614, 613, 617, 616, 619, 619, 618, 617, 621, 620, 623, 623, 622, 621, 625, 624, 627, 627, 626, 625, 629, 628, 631, 631, 630, 629, 633, 632, 635, 635, 634, 633, 637, 636, 639, 639, 638, 637, 641, 640, 643, 643, 642, 641, 645, 644, 647, 647, 646, 645, 649, 648, 651, 651, 650, 649, 653, 652, 655, 655, 654, 653, 657, 656, 659, 659, 658, 657, 661, 660, 663, 663, 662, 661, 665, 664, 667, 667, 666, 665, 669, 668, 671, 671, 670, 669, 673, 672, 675, 675, 674, 673, 677, 676, 679, 679, 678, 677, 681, 680, 683, 683, 682, 681, 685, 684, 687, 687, 686, 685, 689, 688, 691, 691, 690, 689, 693, 692, 695, 695, 694, 693, 697, 696, 699, 699, 698, 697, 701, 700, 703, 703, 702, 701, 705, 704, 707, 707, 706, 705, 709, 708, 711, 711, 710, 709, 713, 712, 715, 715, 714, 713, 717, 716, 719, 719, 718, 717, 721, 720, 723, 723, 722, 721, 725, 724, 727, 727, 726, 725, 729, 728, 731, 731, 730, 729, 733, 732, 735, 735, 734, 733, 737, 736, 739, 739, 738, 737, 741, 740, 743, 743, 742, 741, 745, 744, 747, 747, 746, 745, 749, 748, 751, 751, 750, 749, 753, 752, 755, 755, 754, 753, 757, 756, 759, 759, 758, 757, 761, 760, 763, 763, 762, 761, 765, 764, 767, 767, 766, 765, 769, 768, 771, 771, 770, 769, 773, 772, 775, 775, 774, 773, 777, 776, 779, 779, 778, 777, 781, 780, 783, 783, 782, 781, 785, 784, 787, 787, 786, 785, 789, 788, 791, 791, 790, 789, 793, 792, 795, 795, 794, 793, 797, 796, 799, 799, 798, 797, 801, 800, 803, 803, 802, 801, 805, 804, 807, 807, 806, 805, 809, 808, 811, 811, 810, 809, 813, 812, 815, 815, 814, 813, 817, 816, 819, 819, 818, 817, 821, 820, 823, 823, 822, 821, 825, 824, 827, 827, 826, 825, 829, 828, 831, 831, 830, 829, 833, 832, 835, 835, 834, 833, 837, 836, 839, 839, 838, 837, 841, 840, 843, 843, 842, 841, 845, 844, 847, 847, 846, 845, 849, 848, 851, 851, 850, 849, 853, 852, 855, 855, 854, 853, 857, 856, 859, 859, 858, 857, 861, 860, 863, 863, 862, 861, 865, 864, 867, 867, 866, 865, 869, 868, 871, 871, 870, 869, 873, 872, 875, 875, 874, 873, 877, 876, 879, 879, 878, 877, 881, 880, 883, 883, 882, 881, 885, 884, 887, 887, 886, 885, 889, 888, 891, 891, 890, 889, 893, 892, 895, 895, 894, 893, 897, 896, 899, 899, 898, 897, 901, 900, 903, 903, 902, 901, 905, 904, 907, 907, 906, 905, 909, 908, 911, 911, 910, 909, 913, 912, 915, 915, 914, 913, 917, 916, 919, 919, 918, 917, 921, 920, 923, 923, 922, 921, 925, 924, 927, 927, 926, 925, 929, 928, 931, 931, 930, 929, 933, 932, 935, 935, 934, 933, 937, 936, 939, 939, 938, 937, 941, 940, 943, 943, 942, 941, 945, 944, 947, 947, 946, 945, 949, 948, 951, 951, 950, 949, 953, 952, 955, 955, 954, 953, 957, 956, 959, 959, 958, 957, 961, 960, 963, 963, 962, 961, 965, 964, 967, 967, 966, 965, 969, 968, 971, 971, 970, 969, 973, 972, 975, 975, 974, 973, 977, 976, 979, 979, 978, 977, 981, 980, 983, 983, 982, 981, 985, 984, 987, 987, 986, 985, 989, 988, 991, 991, 990, 989, 993, 992, 995, 995, 994, 993, 997, 996, 999, 999, 998, 997, 1001, 1000, 1003, 1003, 1002, 1001, 1005, 1004, 1007, 1007, 1006, 1005, 1009, 1008, 1011, 1011, 1010, 1009, 1013, 1012, 1015, 1015, 1014, 1013, 1017, 1016, 1019, 1019, 1018, 1017, 1021, 1020, 1023, 1023, 1022, 1021, 1025, 1024, 1027, 1027, 1026, 1025, 1029, 1028, 1031, 1031, 1030, 1029, 1033, 1032, 1035, 1035, 1034, 1033, 1037, 1036, 1039, 1039, 1038, 1037, 1041, 1040, 1043, 1043, 1042, 1041, 1045, 1044, 1047, 1047, 1046, 1045, 1049, 1048, 1051, 1051, 1050, 1049, 1053, 1052, 1055, 1055, 1054, 1053, 1057, 1056, 1059, 1059, 1058, 1057, 1061, 1060, 1063, 1063, 1062, 1061, 1065, 1064, 1067, 1067, 1066, 1065, 1069, 1068, 1071, 1071, 1070, 1069, 1073, 1072, 1075, 1075, 1074, 1073, 1077, 1076, 1079, 1079, 1078, 1077, 1081, 1080, 1083, 1083, 1082, 1081, 1085, 1084, 1087, 1087, 1086, 1085, 1089, 1088, 1091, 1091, 1090, 1089, 1093, 1092, 1095, 1095, 1094, 1093, 1097, 1096, 1099, 1099, 1098, 1097, 1101, 1100, 1103, 1103, 1102, 1101, 1105, 1104, 1107, 1107, 1106, 1105, 1109, 1108, 1111, 1111, 1110, 1109, 1113, 1112, 1115, 1115, 1114, 1113, 1117, 1116, 1119, 1119, 1118, 1117, 1121, 1120, 1123, 1123, 1122, 1121, 1125, 1124, 1127, 1127, 1126, 1125, 1129, 1128, 1131, 1131, 1130, 1129, 1133, 1132, 1135, 1135, 1134, 1133, 1137, 1136, 1139, 1139, 1138, 1137, 1141, 1140, 1143, 1143, 1142, 1141, 1145, 1144, 1147, 1147, 1146, 1145, 1149, 1148, 1151, 1151, 1150, 1149, 1153, 1152, 1155, 1155, 1154, 1153, 1157, 1156, 1159, 1159, 1158, 1157, 1161, 1160, 1163, 1163, 1162, 1161, 1165, 1164, 1167, 1167, 1166, 1165, 1169, 1168, 1171, 1171, 1170, 1169, 1173, 1172, 1175, 1175, 1174, 1173, 1177, 1176, 1179, 1179, 1178, 1177, 1181, 1180, 1183, 1183, 1182, 1181, 1185, 1184, 1187, 1187, 1186, 1185, 1189, 1188, 1191, 1191, 1190, 1189, 1193, 1192, 1195, 1195, 1194, 1193, 1197, 1196, 1199, 1199, 1198, 1197, 1201, 1200, 1203, 1203, 1202, 1201, 1205, 1204, 1207, 1207, 1206, 1205, 1209, 1208, 1211, 1211, 1210, 1209, 1213, 1212, 1215, 1215, 1214, 1213, 1217, 1216, 1219, 1219, 1218, 1217, 1221, 1220, 1223, 1223, 1222, 1221, 1225, 1224, 1227, 1227, 1226, 1225, 1229, 1228, 1231, 1231, 1230, 1229, 1233, 1232, 1235, 1235, 1234, 1233, 1237, 1236, 1239, 1239, 1238, 1237, 1241, 1240, 1243, 1243, 1242, 1241, 1245, 1244, 1247, 1247, 1246, 1245, 1249, 1248, 1251, 1251, 1250, 1249, 1253, 1252, 1255, 1255, 1254, 1253, 1257, 1256, 1259, 1259, 1258, 1257, 1261, 1260, 1263, 1263, 1262, 1261, 1265, 1264, 1267, 1267, 1266, 1265, 1269, 1268, 1271, 1271, 1270, 1269, 1273, 1272, 1275, 1275, 1274, 1273, 1277, 1276, 1279, 1279, 1278, 1277, 1281, 1280, 1283, 1283, 1282, 1281, 1285, 1284, 1287, 1287, 1286, 1285, 1289, 1288, 1291, 1291, 1290, 1289, 1293, 1292, 1295, 1295, 1294, 1293, 1297, 1296, 1299, 1299, 1298, 1297, 1301, 1300, 1303, 1303, 1302, 1301, 1305, 1304, 1307, 1307, 1306, 1305, 1309, 1308, 1311, 1311, 1310, 1309, 1313, 1312, 1315, 1315, 1314, 1313, 1317, 1316, 1319, 1319, 1318, 1317, 1321, 1320, 1323, 1323, 1322, 1321, 1325, 1324, 1327, 1327, 1326, 1325, 1329, 1328, 1331, 1331, 1330, 1329, 1333, 1332, 1335, 1335, 1334, 1333, 1337, 1336, 1339, 1339, 1338, 1337, 1341, 1340, 1343, 1343, 1342, 1341, 1345, 1344, 1347, 1347, 1346, 1345, 1349, 1348, 1351, 1351, 1350, 1349, 1353, 1352, 1355, 1355, 1354, 1353, 1357, 1356, 1359, 1359, 1358, 1357, 1361, 1360, 1363, 1363, 1362, 1361, 1365, 1364, 1367, 1367, 1366, 1365, 1369, 1368, 1371, 1371, 1370, 1369, 1373, 1372, 1375, 1375, 1374, 1373, 1377, 1376, 1379, 1379, 1378, 1377, 1381, 1380, 1383, 1383, 1382, 1381, 1385, 1384, 1387, 1387, 1386, 1385, 1389, 1388, 1391, 1391, 1390, 1389, 1393, 1392, 1395, 1395, 1394, 1393, 1397, 1396, 1399, 1399, 1398, 1397, 1401, 1400, 1403, 1403, 1402, 1401, 1405, 1404, 1407, 1407, 1406, 1405, 1409, 1408, 1411, 1411, 1410, 1409, 1413, 1412, 1415, 1415, 1414, 1413, 1417, 1416, 1419, 1419, 1418, 1417, 1421, 1420, 1423, 1423, 1422, 1421, 1425, 1424, 1427, 1427, 1426, 1425, 1429, 1428, 1431, 1431, 1430, 1429, 1433, 1432, 1435, 1435, 1434, 1433, 1437, 1436, 1439, 1439, 1438, 1437, 1441, 1440, 1443, 1443, 1442, 1441, 1445, 1444, 1447, 1447, 1446, 1445, 1449, 1448, 1451, 1451, 1450, 1449, 1453, 1452, 1455, 1455, 1454, 1453, 1457, 1456, 1459, 1459, 1458, 1457, 1461, 1460, 1463, 1463, 1462, 1461, 1465, 1464, 1467, 1467, 1466, 1465, 1469, 1468, 1471, 1471, 1470, 1469, 1473, 1472, 1475, 1475, 1474, 1473, 1477, 1476, 1479, 1479, 1478, 1477, 1481, 1480, 1483, 1483, 1482, 1481, 1485, 1484, 1487, 1487, 1486, 1485, 1489, 1488, 1491, 1491, 1490, 1489, 1493, 1492, 1495, 1495, 1494, 1493, 1497, 1496, 1499, 1499, 1498, 1497, 1501, 1500, 1503, 1503, 1502, 1501, 1505, 1504, 1507, 1507, 1506, 1505, 1509, 1508, 1511, 1511, 1510, 1509, 1513, 1512, 1515, 1515, 1514, 1513, 1517, 1516, 1519, 1519, 1518, 1517, 1521, 1520, 1523, 1523, 1522, 1521, 1525, 1524, 1527, 1527, 1526, 1525, 1529, 1528, 1531, 1531, 1530, 1529, 1533, 1532, 1535, 1535, 1534, 1533, 1537, 1536, 1539, 1539, 1538, 1537, 1541, 1540, 1543, 1543, 1542, 1541, 1545, 1544, 1547, 1547, 1546, 1545, 1549, 1548, 1551, 1551, 1550, 1549, 1553, 1552, 1555, 1555, 1554, 1553, 1557, 1556, 1559, 1559, 1558, 1557, 1561, 1560, 1563, 1563, 1562, 1561, 1565, 1564, 1567, 1567, 1566, 1565, 1569, 1568, 1571, 1571, 1570, 1569, 1573, 1572, 1575, 1575, 1574, 1573, 1577, 1576, 1579, 1579, 1578, 1577, 1581, 1580, 1583, 1583, 1582, 1581, 1585, 1584, 1587, 1587, 1586, 1585, 1589, 1588, 1591, 1591, 1590, 1589, 1593, 1592, 1595, 1595, 1594, 1593, 1597, 1596, 1599, 1599, 1598, 1597, 1601, 1600, 1603, 1603, 1602, 1601, 1605, 1604, 1607, 1607, 1606, 1605, 1609, 1608, 1611, 1611, 1610, 1609, 1613, 1612, 1615, 1615, 1614, 1613, 1617, 1616, 1619, 1619, 1618, 1617, 1621, 1620, 1623, 1623, 1622, 1621, 1625, 1624, 1627, 1627, 1626, 1625, 1629, 1628, 1631, 1631, 1630, 1629, 1633, 1632, 1635, 1635, 1634, 1633, 1637, 1636, 1639, 1639, 1638, 1637, 1641, 1640, 1643, 1643, 1642, 1641, 1645, 1644, 1647, 1647, 1646, 1645, 1649, 1648, 1651, 1651, 1650, 1649, 1653, 1652, 1655, 1655, 1654, 1653, 1657, 1656, 1659, 1659, 1658, 1657, 1661, 1660, 1663, 1663, 1662, 1661, 1665, 1664, 1667, 1667, 1666, 1665, 1669, 1668, 1671, 1671, 1670, 1669, 1673, 1672, 1675, 1675, 1674, 1673, 1677, 1676, 1679, 1679, 1678, 1677, 1681, 1680, 1683, 1683, 1682, 1681, 1685, 1684, 1687, 1687, 1686, 1685, 1689, 1688, 1691, 1691, 1690, 1689, 1693, 1692, 1695, 1695, 1694, 1693, 1697, 1696, 1699, 1699, 1698, 1697, 1701, 1700, 1703, 1703, 1702, 1701, 1705, 1704, 1707, 1707, 1706, 1705, 1709, 1708, 1711, 1711, 1710, 1709, 1713, 1712, 1715, 1715, 1714, 1713, 1717, 1716, 1719, 1719, 1718, 1717, 1721, 1720, 1723, 1723, 1722, 1721, 1725, 1724, 1727, 1727, 1726, 1725, 1729, 1728, 1731, 1731, 1730, 1729, 1733, 1732, 1735, 1735, 1734, 1733, 1737, 1736, 1739, 1739, 1738, 1737, 1741, 1740, 1743, 1743, 1742, 1741, 1745, 1744, 1747, 1747, 1746, 1745, 1749, 1748, 1751, 1751, 1750, 1749, 1753, 1752, 1755, 1755, 1754, 1753, 1757, 1756, 1759, 1759, 1758, 1757, 1761, 1760, 1763, 1763, 1762, 1761, 1765, 1764, 1767, 1767, 1766, 1765, 1769, 1768, 1771, 1771, 1770, 1769, 1773, 1772, 1775, 1775, 1774, 1773, 1777, 1776, 1779, 1779, 1778, 1777, 1781, 1780, 1783, 1783, 1782, 1781, 1785, 1784, 1787, 1787, 1786, 1785, 1789, 1788, 1791, 1791, 1790, 1789, 1793, 1792, 1795, 1795, 1794, 1793, 1797, 1796, 1799, 1799, 1798, 1797, 1801, 1800, 1803, 1803, 1802, 1801, 1805, 1804, 1807, 1807, 1806, 1805, 1809, 1808, 1811, 1811, 1810, 1809, 1813, 1812, 1815, 1815, 1814, 1813, 1817, 1816, 1819, 1819, 1818, 1817, 1821, 1820, 1823, 1823, 1822, 1821, 1825, 1824, 1827, 1827, 1826, 1825, 1829, 1828, 1831, 1831, 1830, 1829, 1833, 1832, 1835, 1835, 1834, 1833, 1837, 1836, 1839, 1839, 1838, 1837, 1841, 1840, 1843, 1843, 1842, 1841, 1845, 1844, 1847, 1847, 1846, 1845, 1849, 1848, 1851, 1851, 1850, 1849, 1853, 1852, 1855, 1855, 1854, 1853, 1857, 1856, 1859, 1859, 1858, 1857, 1861, 1860, 1863, 1863, 1862, 1861, 1865, 1864, 1867, 1867, 1866, 1865, 1869, 1868, 1871, 1871, 1870, 1869, 1873, 1872, 1875, 1875, 1874, 1873, 1877, 1876, 1879, 1879, 1878, 1877, 1881, 1880, 1883, 1883, 1882, 1881, 1885, 1884, 1887, 1887, 1886, 1885, 1889, 1888, 1891, 1891, 1890, 1889, 1893, 1892, 1895, 1895, 1894, 1893, 1897, 1896, 1899, 1899, 1898, 1897, 1901, 1900, 1903, 1903, 1902, 1901, 1905, 1904, 1907, 1907, 1906, 1905, 1909, 1908, 1911, 1911, 1910, 1909, 1913, 1912, 1915, 1915, 1914, 1913, 1917, 1916, 1919, 1919, 1918, 1917, 1921, 1920, 1923, 1923, 1922, 1921, 1925, 1924, 1927, 1927, 1926, 1925, 1929, 1928, 1931, 1931, 1930, 1929, 1933, 1932, 1935, 1935, 1934, 1933, 1937, 1936, 1939, 1939, 1938, 1937, 1941, 1940, 1943, 1943, 1942, 1941, 1945, 1944, 1947, 1947, 1946, 1945, 1949, 1948, 1951, 1951, 1950, 1949, 1953, 1952, 1955, 1955, 1954, 1953, 1957, 1956, 1959, 1959, 1958, 1957, 1961, 1960, 1963, 1963, 1962, 1961, 1965, 1964, 1967, 1967, 1966, 1965, 1969, 1968, 1971, 1971, 1970, 1969, 1973, 1972, 1975, 1975, 1974, 1973, 1977, 1976, 1979, 1979, 1978, 1977, 1981, 1980, 1983, 1983, 1982, 1981, 1985, 1984, 1987, 1987, 1986, 1985, 1989, 1988, 1991, 1991, 1990, 1989, 1993, 1992, 1995, 1995, 1994, 1993, 1997, 1996, 1999, 1999, 1998, 1997, 2001, 2000, 2003, 2003, 2002, 2001, 2005, 2004, 2007, 2007, 2006, 2005, 2009, 2008, 2011, 2011, 2010, 2009, 2013, 2012, 2015, 2015, 2014, 2013, 2017, 2016, 2019, 2019, 2018, 2017, 2021, 2020, 2023, 2023, 2022, 2021, 2025, 2024, 2027, 2027, 2026, 2025, 2029, 2028, 2031, 2031, 2030, 2029, 2033, 2032, 2035, 2035, 2034, 2033, 2037, 2036, 2039, 2039, 2038, 2037, 2041, 2040, 2043, 2043, 2042, 2041, 2045, 2044, 2047, 2047, 2046, 2045 }; template T align(T value, int bytes) { return (value + bytes - 1) & ~T(bytes - 1); } class IndexBuffer { public: IndexBuffer(); ~IndexBuffer(); void accomodate(int count); void bind(); private: GLuint m_buffer; size_t m_size; int m_count; }; IndexBuffer::IndexBuffer() { // The maximum number of quads we can render with 16 bit indices is 16,384. // But we start with 512 and grow the buffer as needed. m_size = sizeof(indices); m_count = m_size / (6 * sizeof(uint16_t)); glGenBuffers(1, &m_buffer); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_buffer); glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW); } IndexBuffer::~IndexBuffer() { glDeleteBuffers(1, &m_buffer); } void IndexBuffer::accomodate(int count) { // Check if we need to grow the buffer. if (count <= m_count) return; count = align(count, 128); size_t size = 6 * sizeof(uint16_t) * count; // Create a new buffer object GLuint buffer; glGenBuffers(1, &buffer); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer); glBufferData(GL_ELEMENT_ARRAY_BUFFER, size, nullptr, GL_STATIC_DRAW); // Use the GPU to copy the data from the old object to the new object, glBindBuffer(GL_COPY_READ_BUFFER, m_buffer); glCopyBufferSubData(GL_COPY_READ_BUFFER, GL_ELEMENT_ARRAY_BUFFER, 0, 0, m_size); glDeleteBuffers(1, &m_buffer); glFlush(); // Needed to work around what appears to be a CP DMA issue in r600g // Map the new object and fill in the uninitialized section const GLbitfield access = GL_MAP_WRITE_BIT | GL_MAP_UNSYNCHRONIZED_BIT | GL_MAP_INVALIDATE_RANGE_BIT; uint16_t *map = (uint16_t *) glMapBufferRange(GL_ELEMENT_ARRAY_BUFFER, m_size, size - m_size, access); const uint16_t index[] = { 1, 0, 3, 3, 2, 1 }; for (int i = m_count; i < count; i++) { for (int j = 0; j < 6; j++) *(map++) = i * 4 + index[j]; } glUnmapBuffer(GL_ELEMENT_ARRAY_BUFFER); m_buffer = buffer; m_count = count; m_size = size; } void IndexBuffer::bind() { glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_buffer); } // ------------------------------------------------------------------ class BitRef { public: BitRef(uint32_t &bitfield, int bit) : m_bitfield(bitfield), m_mask(1 << bit) {} void operator = (bool val) { if (val) m_bitfield |= m_mask; else m_bitfield &= ~m_mask; } operator bool () const { return m_bitfield & m_mask; } private: uint32_t &m_bitfield; int const m_mask; }; // ------------------------------------------------------------------ class Bitfield { public: Bitfield() : m_bitfield(0) {} Bitfield(uint32_t bits) : m_bitfield(bits) {} void set(int i) { m_bitfield |= (1 << i); } void clear(int i) { m_bitfield &= ~(1 << i); } BitRef operator [] (int i) { return BitRef(m_bitfield, i); } operator uint32_t () const { return m_bitfield; } private: uint32_t m_bitfield; }; // ------------------------------------------------------------------ class BitfieldIterator { public: BitfieldIterator(uint32_t bitfield) : m_bitfield(bitfield) {} bool hasNext() const { return m_bitfield != 0; } int next() { const int bit = ffs(m_bitfield) - 1; m_bitfield ^= (1 << bit); return bit; } private: uint32_t m_bitfield; }; // ------------------------------------------------------------------ struct VertexAttrib { int size; GLenum type; int offset; }; // ------------------------------------------------------------------ struct BufferFence { GLsync sync; intptr_t nextEnd; bool signaled() const { GLint value; glGetSynciv(sync, GL_SYNC_STATUS, 1, nullptr, &value); return value == GL_SIGNALED; } }; static void deleteAll(std::deque &fences) { for (const BufferFence &fence : fences) glDeleteSync(fence.sync); fences.clear(); } // ------------------------------------------------------------------ template struct FrameSizesArray { public: FrameSizesArray() { m_array.fill(0); } void push(size_t size) { m_array[m_index] = size; m_index = (m_index + 1) % Count; } size_t average() const { size_t sum = 0; for (size_t size : m_array) sum += size; return sum / Count; } private: std::array m_array; int m_index = 0; }; //********************************* // GLVertexBufferPrivate //********************************* class GLVertexBufferPrivate { public: GLVertexBufferPrivate(GLVertexBuffer::UsageHint usageHint) : vertexCount(0) , persistent(false) , useColor(false) , color(0, 0, 0, 255) , bufferSize(0) , bufferEnd(0) , mappedSize(0) , frameSize(0) , nextOffset(0) , baseAddress(0) , map(nullptr) { glGenBuffers(1, &buffer); switch(usageHint) { case GLVertexBuffer::Dynamic: usage = GL_DYNAMIC_DRAW; break; case GLVertexBuffer::Static: usage = GL_STATIC_DRAW; break; default: usage = GL_STREAM_DRAW; break; } } ~GLVertexBufferPrivate() { deleteAll(fences); if (buffer != 0) { glDeleteBuffers(1, &buffer); map = nullptr; } } void interleaveArrays(float *array, int dim, const float *vertices, const float *texcoords, int count); void bindArrays(); void unbindArrays(); void reallocateBuffer(size_t size); GLvoid *mapNextFreeRange(size_t size); void reallocatePersistentBuffer(size_t size); bool awaitFence(intptr_t offset); GLvoid *getIdleRange(size_t size); GLuint buffer; GLenum usage; int stride; int vertexCount; static GLVertexBuffer *streamingBuffer; static bool haveBufferStorage; static bool haveSyncFences; static bool hasMapBufferRange; static bool supportsIndexedQuads; QByteArray dataStore; bool persistent; bool useColor; QVector4D color; size_t bufferSize; intptr_t bufferEnd; size_t mappedSize; size_t frameSize; intptr_t nextOffset; intptr_t baseAddress; uint8_t *map; std::deque fences; FrameSizesArray<4> frameSizes; VertexAttrib attrib[VertexAttributeCount]; Bitfield enabledArrays; static IndexBuffer *s_indexBuffer; }; bool GLVertexBufferPrivate::hasMapBufferRange = false; bool GLVertexBufferPrivate::supportsIndexedQuads = false; GLVertexBuffer *GLVertexBufferPrivate::streamingBuffer = nullptr; bool GLVertexBufferPrivate::haveBufferStorage = false; bool GLVertexBufferPrivate::haveSyncFences = false; IndexBuffer *GLVertexBufferPrivate::s_indexBuffer = nullptr; void GLVertexBufferPrivate::interleaveArrays(float *dst, int dim, const float *vertices, const float *texcoords, int count) { if (!texcoords) { memcpy((void *) dst, vertices, dim * sizeof(float) * count); return; } switch (dim) { case 2: for (int i = 0; i < count; i++) { *(dst++) = *(vertices++); *(dst++) = *(vertices++); *(dst++) = *(texcoords++); *(dst++) = *(texcoords++); } break; case 3: for (int i = 0; i < count; i++) { *(dst++) = *(vertices++); *(dst++) = *(vertices++); *(dst++) = *(vertices++); *(dst++) = *(texcoords++); *(dst++) = *(texcoords++); } break; default: for (int i = 0; i < count; i++) { for (int j = 0; j < dim; j++) *(dst++) = *(vertices++); *(dst++) = *(texcoords++); *(dst++) = *(texcoords++); } } } void GLVertexBufferPrivate::bindArrays() { if (useColor) { GLShader *shader = ShaderManager::instance()->getBoundShader(); shader->setUniform(GLShader::Color, color); } glBindBuffer(GL_ARRAY_BUFFER, buffer); BitfieldIterator it(enabledArrays); while (it.hasNext()) { const int index = it.next(); glVertexAttribPointer(index, attrib[index].size, attrib[index].type, GL_FALSE, stride, (const GLvoid *) (baseAddress + attrib[index].offset)); glEnableVertexAttribArray(index); } } void GLVertexBufferPrivate::unbindArrays() { BitfieldIterator it(enabledArrays); while (it.hasNext()) glDisableVertexAttribArray(it.next()); } void GLVertexBufferPrivate::reallocatePersistentBuffer(size_t size) { if (buffer != 0) { // This also unmaps and unbinds the buffer glDeleteBuffers(1, &buffer); buffer = 0; deleteAll(fences); } if (buffer == 0) glGenBuffers(1, &buffer); // Round the size up to 64 kb size_t minSize = qMax(frameSizes.average() * 3, 128 * 1024); bufferSize = align(qMax(size, minSize), 64 * 1024); const GLbitfield storage = GL_DYNAMIC_STORAGE_BIT; const GLbitfield access = GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT | GL_MAP_COHERENT_BIT; glBindBuffer(GL_ARRAY_BUFFER, buffer); glBufferStorage(GL_ARRAY_BUFFER, bufferSize, nullptr, storage | access); map = (uint8_t *) glMapBufferRange(GL_ARRAY_BUFFER, 0, bufferSize, access); nextOffset = 0; bufferEnd = bufferSize; } bool GLVertexBufferPrivate::awaitFence(intptr_t end) { // Skip fences until we reach the end offset while (!fences.empty() && fences.front().nextEnd < end) { glDeleteSync(fences.front().sync); fences.pop_front(); } Q_ASSERT(!fences.empty()); // Wait on the next fence const BufferFence &fence = fences.front(); if (!fence.signaled()) { qCDebug(LIBKWINGLUTILS) << "Stalling on VBO fence"; const GLenum ret = glClientWaitSync(fence.sync, GL_SYNC_FLUSH_COMMANDS_BIT, 1000000000); if (ret == GL_TIMEOUT_EXPIRED || ret == GL_WAIT_FAILED) { qCCritical(LIBKWINGLUTILS) << "Wait failed"; return false; } } glDeleteSync(fence.sync); // Update the end pointer bufferEnd = fence.nextEnd; fences.pop_front(); return true; } GLvoid *GLVertexBufferPrivate::getIdleRange(size_t size) { if (unlikely(size > bufferSize)) reallocatePersistentBuffer(size * 2); // Handle wrap-around if (unlikely(nextOffset + size > bufferSize)) { nextOffset = 0; bufferEnd -= bufferSize; for (BufferFence &fence : fences) fence.nextEnd -= bufferSize; // Emit a fence now BufferFence fence; fence.sync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); fence.nextEnd = bufferSize; fences.emplace_back(fence); } if (unlikely(nextOffset + intptr_t(size) > bufferEnd)) { if (!awaitFence(nextOffset + size)) return nullptr; } return map + nextOffset; } void GLVertexBufferPrivate::reallocateBuffer(size_t size) { // Round the size up to 4 Kb for streaming/dynamic buffers. const size_t minSize = 32768; // Minimum size for streaming buffers const size_t alloc = usage != GL_STATIC_DRAW ? align(qMax(size, minSize), 4096) : size; - glBufferData(GL_ARRAY_BUFFER, alloc, 0, usage); + glBufferData(GL_ARRAY_BUFFER, alloc, nullptr, usage); bufferSize = alloc; } GLvoid *GLVertexBufferPrivate::mapNextFreeRange(size_t size) { GLbitfield access = GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_RANGE_BIT | GL_MAP_UNSYNCHRONIZED_BIT; if ((nextOffset + size) > bufferSize) { // Reallocate the data store if it's too small. if (size > bufferSize) { reallocateBuffer(size); } else { access |= GL_MAP_INVALIDATE_BUFFER_BIT; access ^= GL_MAP_UNSYNCHRONIZED_BIT; } nextOffset = 0; } return glMapBufferRange(GL_ARRAY_BUFFER, nextOffset, size, access); } //********************************* // GLVertexBuffer //********************************* QRect GLVertexBuffer::s_virtualScreenGeometry; qreal GLVertexBuffer::s_virtualScreenScale; GLVertexBuffer::GLVertexBuffer(UsageHint hint) : d(new GLVertexBufferPrivate(hint)) { } GLVertexBuffer::~GLVertexBuffer() { delete d; } void GLVertexBuffer::setData(const void *data, size_t size) { GLvoid *ptr = map(size); memcpy(ptr, data, size); unmap(); } void GLVertexBuffer::setData(int vertexCount, int dim, const float* vertices, const float* texcoords) { const GLVertexAttrib layout[] = { { VA_Position, dim, GL_FLOAT, 0 }, { VA_TexCoord, 2, GL_FLOAT, int(dim * sizeof(float)) } }; int stride = (texcoords ? dim + 2 : dim) * sizeof(float); int attribCount = texcoords ? 2 : 1; setAttribLayout(layout, attribCount, stride); setVertexCount(vertexCount); GLvoid *ptr = map(vertexCount * stride); d->interleaveArrays((float *) ptr, dim, vertices, texcoords, vertexCount); unmap(); } GLvoid *GLVertexBuffer::map(size_t size) { d->mappedSize = size; d->frameSize += size; if (d->persistent) return d->getIdleRange(size); glBindBuffer(GL_ARRAY_BUFFER, d->buffer); bool preferBufferSubData = GLPlatform::instance()->preferBufferSubData(); if (GLVertexBufferPrivate::hasMapBufferRange && !preferBufferSubData) return (GLvoid *) d->mapNextFreeRange(size); // If we can't map the buffer we allocate local memory to hold the // buffer data and return a pointer to it. The data will be submitted // to the actual buffer object when the user calls unmap(). if (size_t(d->dataStore.size()) < size) d->dataStore.resize(size); return (GLvoid *) d->dataStore.data(); } void GLVertexBuffer::unmap() { if (d->persistent) { d->baseAddress = d->nextOffset; d->nextOffset += align(d->mappedSize, 16); // Align to 16 bytes for SSE d->mappedSize = 0; return; } bool preferBufferSubData = GLPlatform::instance()->preferBufferSubData(); if (GLVertexBufferPrivate::hasMapBufferRange && !preferBufferSubData) { glUnmapBuffer(GL_ARRAY_BUFFER); d->baseAddress = d->nextOffset; d->nextOffset += align(d->mappedSize, 16); // Align to 16 bytes for SSE } else { // Upload the data from local memory to the buffer object if (preferBufferSubData) { if ((d->nextOffset + d->mappedSize) > d->bufferSize) { d->reallocateBuffer(d->mappedSize); d->nextOffset = 0; } glBufferSubData(GL_ARRAY_BUFFER, d->nextOffset, d->mappedSize, d->dataStore.constData()); d->baseAddress = d->nextOffset; d->nextOffset += align(d->mappedSize, 16); // Align to 16 bytes for SSE } else { glBufferData(GL_ARRAY_BUFFER, d->mappedSize, d->dataStore.data(), d->usage); d->baseAddress = 0; } // Free the local memory buffer if it's unlikely to be used again if (d->usage == GL_STATIC_DRAW) d->dataStore = QByteArray(); } d->mappedSize = 0; } void GLVertexBuffer::setVertexCount(int count) { d->vertexCount = count; } void GLVertexBuffer::setAttribLayout(const GLVertexAttrib *attribs, int count, int stride) { // Start by disabling all arrays d->enabledArrays = 0; for (int i = 0; i < count; i++) { const int index = attribs[i].index; Q_ASSERT(index >= 0 && index < VertexAttributeCount); Q_ASSERT(!d->enabledArrays[index]); d->attrib[index].size = attribs[i].size; d->attrib[index].type = attribs[i].type; d->attrib[index].offset = attribs[i].relativeOffset; d->enabledArrays[index] = true; } d->stride = stride; } void GLVertexBuffer::render(GLenum primitiveMode) { render(infiniteRegion(), primitiveMode, false); } void GLVertexBuffer::render(const QRegion& region, GLenum primitiveMode, bool hardwareClipping) { d->bindArrays(); draw(region, primitiveMode, 0, d->vertexCount, hardwareClipping); d->unbindArrays(); } void GLVertexBuffer::bindArrays() { d->bindArrays(); } void GLVertexBuffer::unbindArrays() { d->unbindArrays(); } void GLVertexBuffer::draw(GLenum primitiveMode, int first, int count) { draw(infiniteRegion(), primitiveMode, first, count, false); } void GLVertexBuffer::draw(const QRegion ®ion, GLenum primitiveMode, int first, int count, bool hardwareClipping) { if (primitiveMode == GL_QUADS) { IndexBuffer *&indexBuffer = GLVertexBufferPrivate::s_indexBuffer; if (!indexBuffer) indexBuffer = new IndexBuffer; indexBuffer->bind(); indexBuffer->accomodate(count / 4); count = count * 6 / 4; if (!hardwareClipping) { glDrawElementsBaseVertex(GL_TRIANGLES, count, GL_UNSIGNED_SHORT, nullptr, first); } else { // Clip using scissoring for (const QRect &r : region) { glScissor((r.x() - s_virtualScreenGeometry.x()) * s_virtualScreenScale, (s_virtualScreenGeometry.height() + s_virtualScreenGeometry.y() - r.y() - r.height()) * s_virtualScreenScale, r.width() * s_virtualScreenScale, r.height() * s_virtualScreenScale); glDrawElementsBaseVertex(GL_TRIANGLES, count, GL_UNSIGNED_SHORT, nullptr, first); } } return; } if (!hardwareClipping) { glDrawArrays(primitiveMode, first, count); } else { // Clip using scissoring for (const QRect &r : region) { glScissor((r.x() - s_virtualScreenGeometry.x()) * s_virtualScreenScale, (s_virtualScreenGeometry.height() + s_virtualScreenGeometry.y() - r.y() - r.height()) * s_virtualScreenScale, r.width() * s_virtualScreenScale, r.height() * s_virtualScreenScale); glDrawArrays(primitiveMode, first, count); } } } bool GLVertexBuffer::supportsIndexedQuads() { return GLVertexBufferPrivate::supportsIndexedQuads; } bool GLVertexBuffer::isUseColor() const { return d->useColor; } void GLVertexBuffer::setUseColor(bool enable) { d->useColor = enable; } void GLVertexBuffer::setColor(const QColor& color, bool enable) { d->useColor = enable; d->color = QVector4D(color.redF(), color.greenF(), color.blueF(), color.alphaF()); } void GLVertexBuffer::reset() { d->useColor = false; d->color = QVector4D(0, 0, 0, 1); d->vertexCount = 0; } void GLVertexBuffer::endOfFrame() { if (!d->persistent) return; // Emit a fence if we have uploaded data if (d->frameSize > 0) { d->frameSizes.push(d->frameSize); d->frameSize = 0; // Force the buffer to be reallocated at the beginning of the next frame // if the average frame size is greater than half the size of the buffer if (unlikely(d->frameSizes.average() > d->bufferSize / 2)) { deleteAll(d->fences); glDeleteBuffers(1, &d->buffer); d->buffer = 0; d->bufferSize = 0; d->nextOffset = 0; d->map = nullptr; } else { BufferFence fence; fence.sync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); fence.nextEnd = d->nextOffset + d->bufferSize; d->fences.emplace_back(fence); } } } void GLVertexBuffer::framePosted() { if (!d->persistent) return; // Remove finished fences from the list and update the bufferEnd offset while (d->fences.size() > 1 && d->fences.front().signaled()) { const BufferFence &fence = d->fences.front(); glDeleteSync(fence.sync); d->bufferEnd = fence.nextEnd; d->fences.pop_front(); } } void GLVertexBuffer::initStatic() { if (GLPlatform::instance()->isGLES()) { bool haveBaseVertex = hasGLExtension(QByteArrayLiteral("GL_OES_draw_elements_base_vertex")); bool haveCopyBuffer = hasGLVersion(3, 0); bool haveMapBufferRange = hasGLExtension(QByteArrayLiteral("GL_EXT_map_buffer_range")); GLVertexBufferPrivate::hasMapBufferRange = haveMapBufferRange; GLVertexBufferPrivate::supportsIndexedQuads = haveBaseVertex && haveCopyBuffer && haveMapBufferRange; GLVertexBufferPrivate::haveBufferStorage = hasGLExtension("GL_EXT_buffer_storage"); GLVertexBufferPrivate::haveSyncFences = hasGLVersion(3, 0); } else { bool haveBaseVertex = hasGLVersion(3, 2) || hasGLExtension(QByteArrayLiteral("GL_ARB_draw_elements_base_vertex")); bool haveCopyBuffer = hasGLVersion(3, 1) || hasGLExtension(QByteArrayLiteral("GL_ARB_copy_buffer")); bool haveMapBufferRange = hasGLVersion(3, 0) || hasGLExtension(QByteArrayLiteral("GL_ARB_map_buffer_range")); GLVertexBufferPrivate::hasMapBufferRange = haveMapBufferRange; GLVertexBufferPrivate::supportsIndexedQuads = haveBaseVertex && haveCopyBuffer && haveMapBufferRange; GLVertexBufferPrivate::haveBufferStorage = hasGLVersion(4, 4) || hasGLExtension("GL_ARB_buffer_storage"); GLVertexBufferPrivate::haveSyncFences = hasGLVersion(3, 2) || hasGLExtension("GL_ARB_sync"); } GLVertexBufferPrivate::s_indexBuffer = nullptr; GLVertexBufferPrivate::streamingBuffer = new GLVertexBuffer(GLVertexBuffer::Stream); if (GLVertexBufferPrivate::haveBufferStorage && GLVertexBufferPrivate::haveSyncFences) { if (qgetenv("KWIN_PERSISTENT_VBO") != QByteArrayLiteral("0")) { GLVertexBufferPrivate::streamingBuffer->d->persistent = true; } } } void GLVertexBuffer::cleanup() { delete GLVertexBufferPrivate::s_indexBuffer; GLVertexBufferPrivate::s_indexBuffer = nullptr; GLVertexBufferPrivate::hasMapBufferRange = false; GLVertexBufferPrivate::supportsIndexedQuads = false; delete GLVertexBufferPrivate::streamingBuffer; GLVertexBufferPrivate::streamingBuffer = nullptr; } GLVertexBuffer *GLVertexBuffer::streamingBuffer() { return GLVertexBufferPrivate::streamingBuffer; } } // namespace diff --git a/manage.cpp b/manage.cpp index b801fe93b..31cc30910 100644 --- a/manage.cpp +++ b/manage.cpp @@ -1,694 +1,694 @@ /******************************************************************** 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 . *********************************************************************/ // This file contains things relevant to handling incoming events. #include "client.h" #include #ifdef KWIN_BUILD_ACTIVITIES #include "activities.h" #endif #include "composite.h" #include "cursor.h" #include "rules.h" #include "group.h" #include "netinfo.h" #include "screens.h" #include "workspace.h" #include "xcbutils.h" #include namespace KWin { /** * Manages the clients. This means handling the very first maprequest: * reparenting, initial geometry, initial state, placement, etc. * Returns false if KWin is not going to manage this window. */ bool Client::manage(xcb_window_t w, bool isMapped) { StackingUpdatesBlocker stacking_blocker(workspace()); Xcb::WindowAttributes attr(w); Xcb::WindowGeometry windowGeometry(w); if (attr.isNull() || windowGeometry.isNull()) { return false; } // From this place on, manage() must not return false blockGeometryUpdates(); setPendingGeometryUpdate(PendingGeometryForced); // Force update when finishing with geometry changes embedClient(w, attr->visual, attr->colormap, windowGeometry->depth); m_visual = attr->visual; bit_depth = windowGeometry->depth; // SELI TODO: Order all these things in some sane manner const NET::Properties properties = NET::WMDesktop | NET::WMState | NET::WMWindowType | NET::WMStrut | NET::WMName | NET::WMIconGeometry | NET::WMIcon | NET::WMPid | NET::WMIconName; const NET::Properties2 properties2 = NET::WM2BlockCompositing | NET::WM2WindowClass | NET::WM2WindowRole | NET::WM2UserTime | NET::WM2StartupId | NET::WM2ExtendedStrut | NET::WM2Opacity | NET::WM2FullscreenMonitors | NET::WM2FrameOverlap | NET::WM2GroupLeader | NET::WM2Urgency | NET::WM2Input | NET::WM2Protocols | NET::WM2InitialMappingState | NET::WM2IconPixmap | NET::WM2OpaqueRegion | NET::WM2DesktopFileName; auto wmClientLeaderCookie = fetchWmClientLeader(); auto skipCloseAnimationCookie = fetchSkipCloseAnimation(); auto gtkFrameExtentsCookie = fetchGtkFrameExtents(); auto showOnScreenEdgeCookie = fetchShowOnScreenEdge(); auto colorSchemeCookie = fetchColorScheme(); auto firstInTabBoxCookie = fetchFirstInTabBox(); auto transientCookie = fetchTransient(); auto activitiesCookie = fetchActivities(); auto applicationMenuServiceNameCookie = fetchApplicationMenuServiceName(); auto applicationMenuObjectPathCookie = fetchApplicationMenuObjectPath(); m_geometryHints.init(window()); m_motif.init(window()); info = new WinInfo(this, m_client, rootWindow(), properties, properties2); if (isDesktop() && bit_depth == 32) { // force desktop windows to be opaque. It's a desktop after all, there is no window below bit_depth = 24; } // If it's already mapped, ignore hint bool init_minimize = !isMapped && (info->initialMappingState() == NET::Iconic); m_colormap = attr->colormap; getResourceClass(); readWmClientLeader(wmClientLeaderCookie); getWmClientMachine(); getSyncCounter(); // First only read the caption text, so that setupWindowRules() can use it for matching, // and only then really set the caption using setCaption(), which checks for duplicates etc. // and also relies on rules already existing cap_normal = readName(); setupWindowRules(false); setCaption(cap_normal, true); connect(this, &Client::windowClassChanged, this, &Client::evaluateWindowRules); if (Xcb::Extensions::self()->isShapeAvailable()) xcb_shape_select_input(connection(), window(), true); detectShape(window()); readGtkFrameExtents(gtkFrameExtentsCookie); detectNoBorder(); fetchIconicName(); // Needs to be done before readTransient() because of reading the group checkGroup(); updateUrgency(); updateAllowedActions(); // Group affects isMinimizable() setModal((info->state() & NET::Modal) != 0); // Needs to be valid before handling groups readTransientProperty(transientCookie); setDesktopFileName(rules()->checkDesktopFile(QByteArray(info->desktopFileName()), true).toUtf8()); getIcons(); connect(this, &Client::desktopFileNameChanged, this, &Client::getIcons); m_geometryHints.read(); getMotifHints(); getWmOpaqueRegion(); readSkipCloseAnimation(skipCloseAnimationCookie); // TODO: Try to obey all state information from info->state() setOriginalSkipTaskbar((info->state() & NET::SkipTaskbar) != 0); setSkipPager((info->state() & NET::SkipPager) != 0); setSkipSwitcher((info->state() & NET::SkipSwitcher) != 0); readFirstInTabBox(firstInTabBoxCookie); setupCompositing(); KStartupInfoId asn_id; KStartupInfoData asn_data; bool asn_valid = workspace()->checkStartupNotification(window(), asn_id, asn_data); // Make sure that the input window is created before we update the stacking order updateInputWindow(); workspace()->updateClientLayer(this); SessionInfo* session = workspace()->takeSessionInfo(this); if (session) { init_minimize = session->minimized; noborder = session->noBorder; } setShortcut(rules()->checkShortcut(session ? session->shortcut : QString(), true)); init_minimize = rules()->checkMinimize(init_minimize, !isMapped); noborder = rules()->checkNoBorder(noborder, !isMapped); readActivities(activitiesCookie); // Initial desktop placement int desk = 0; if (session) { desk = session->desktop; if (session->onAllDesktops) desk = NET::OnAllDesktops; setOnActivities(session->activities); } else { // If this window is transient, ensure that it is opened on the // same window as its parent. this is necessary when an application // starts up on a different desktop than is currently displayed if (isTransient()) { auto mainclients = mainClients(); bool on_current = false; bool on_all = false; AbstractClient* maincl = nullptr; // This is slightly duplicated from Placement::placeOnMainWindow() for (auto it = mainclients.constBegin(); it != mainclients.constEnd(); ++it) { if (mainclients.count() > 1 && // A group-transient (*it)->isSpecialWindow() && // Don't consider toolbars etc when placing !(info->state() & NET::Modal)) // except when it's modal (blocks specials as well) continue; maincl = *it; if ((*it)->isOnCurrentDesktop()) on_current = true; if ((*it)->isOnAllDesktops()) on_all = true; } if (on_all) desk = NET::OnAllDesktops; else if (on_current) desk = VirtualDesktopManager::self()->current(); - else if (maincl != NULL) + else if (maincl != nullptr) desk = maincl->desktop(); if (maincl) setOnActivities(maincl->activities()); } else { // a transient shall appear on its leader and not drag that around if (info->desktop()) desk = info->desktop(); // Window had the initial desktop property, force it if (desktop() == 0 && asn_valid && asn_data.desktop() != 0) desk = asn_data.desktop(); } #ifdef KWIN_BUILD_ACTIVITIES if (Activities::self() && !isMapped && !noborder && isNormalWindow() && !activitiesDefined) { //a new, regular window, when we're not recovering from a crash, //and it hasn't got an activity. let's try giving it the current one. //TODO: decide whether to keep this before the 4.6 release //TODO: if we are keeping it (at least as an option), replace noborder checking //with a public API for setting windows to be on all activities. //something like KWindowSystem::setOnAllActivities or //KActivityConsumer::setOnAllActivities setOnActivity(Activities::self()->current(), true); } #endif } if (desk == 0) // Assume window wants to be visible on the current desktop desk = isDesktop() ? static_cast(NET::OnAllDesktops) : VirtualDesktopManager::self()->current(); desk = rules()->checkDesktop(desk, !isMapped); if (desk != NET::OnAllDesktops) // Do range check desk = qBound(1, desk, static_cast(VirtualDesktopManager::self()->count())); setDesktop(desk); info->setDesktop(desk); workspace()->updateOnAllDesktopsOfTransients(this); // SELI TODO //onAllDesktopsChange(); // Decoration doesn't exist here yet QString activitiesList; activitiesList = rules()->checkActivity(activitiesList, !isMapped); if (!activitiesList.isEmpty()) setOnActivities(activitiesList.split(QStringLiteral(","))); QRect geom(windowGeometry.rect()); bool placementDone = false; if (session) geom = session->geometry; QRect area; bool partial_keep_in_area = isMapped || session; if (isMapped || session) { area = workspace()->clientArea(FullArea, geom.center(), desktop()); checkOffscreenPosition(&geom, area); } else { int screen = asn_data.xinerama() == -1 ? screens()->current() : asn_data.xinerama(); screen = rules()->checkScreen(screen, !isMapped); area = workspace()->clientArea(PlacementArea, screens()->geometry(screen).center(), desktop()); } if (isDesktop()) // KWin doesn't manage desktop windows placementDone = true; bool usePosition = false; if (isMapped || session || placementDone) placementDone = true; // Use geometry else if (isTransient() && !isUtility() && !isDialog() && !isSplash()) usePosition = true; else if (isTransient() && !hasNETSupport()) usePosition = true; else if (isDialog() && hasNETSupport()) { // If the dialog is actually non-NETWM transient window, don't try to apply placement to it, // it breaks with too many things (xmms, display) if (mainClients().count() >= 1) { #if 1 // #78082 - Ok, it seems there are after all some cases when an application has a good // reason to specify a position for its dialog. Too bad other WMs have never bothered // with placement for dialogs, so apps always specify positions for their dialogs, // including such silly positions like always centered on the screen or under mouse. // Using ignoring requested position in window-specific settings helps, and now // there's also _NET_WM_FULL_PLACEMENT. usePosition = true; #else ; // Force using placement policy #endif } else usePosition = true; } else if (isSplash()) ; // Force using placement policy else usePosition = true; if (!rules()->checkIgnoreGeometry(!usePosition, true)) { if (m_geometryHints.hasPosition()) { placementDone = true; // Disobey xinerama placement option for now (#70943) area = workspace()->clientArea(PlacementArea, geom.center(), desktop()); } } //if ( true ) // Size is always obeyed for now, only with constraints applied // if (( xSizeHint.flags & USSize ) || ( xSizeHint.flags & PSize )) // { // // Keep in mind that we now actually have a size :-) // } if (m_geometryHints.hasMaxSize()) geom.setSize(geom.size().boundedTo( rules()->checkMaxSize(m_geometryHints.maxSize()))); if (m_geometryHints.hasMinSize()) geom.setSize(geom.size().expandedTo( rules()->checkMinSize(m_geometryHints.minSize()))); if (isMovable() && (geom.x() > area.right() || geom.y() > area.bottom())) placementDone = false; // Weird, do not trust. if (placementDone) move(geom.x(), geom.y()); // Before gravitating // Create client group if the window will have a decoration bool dontKeepInArea = false; readColorScheme(colorSchemeCookie); readApplicationMenuServiceName(applicationMenuServiceNameCookie); readApplicationMenuObjectPath(applicationMenuObjectPathCookie); updateDecoration(false); // Also gravitates // TODO: Is CentralGravity right here, when resizing is done after gravitating? plainResize(rules()->checkSize(sizeForClientSize(geom.size()), !isMapped)); QPoint forced_pos = rules()->checkPosition(invalidPoint, !isMapped); if (forced_pos != invalidPoint) { move(forced_pos); placementDone = true; // Don't keep inside workarea if the window has specially configured position partial_keep_in_area = true; area = workspace()->clientArea(FullArea, geom.center(), desktop()); } if (!placementDone) { // Placement needs to be after setting size Placement::self()->place(this, area); // The client may have been moved to another screen, update placement area. area = workspace()->clientArea(PlacementArea, this); dontKeepInArea = true; placementDone = true; } // bugs #285967, #286146, #183694 // geometry() now includes the requested size and the decoration and is at the correct screen/position (hopefully) // Maximization for oversized windows must happen NOW. // If we effectively pass keepInArea(), the window will resizeWithChecks() - i.e. constrained // to the combo of all screen MINUS all struts on the edges // If only one screen struts, this will affect screens as a side-effect, the window is artificailly shrinked // below the screen size and as result no more maximized what breaks KMainWindow's stupid width+1, height+1 hack // TODO: get KMainWindow a correct state storage what will allow to store the restore size as well. if (!session) { // has a better handling of this geom_restore = geometry(); // Remember restore geometry if (isMaximizable() && (width() >= area.width() || height() >= area.height())) { // Window is too large for the screen, maximize in the // directions necessary const QSize ss = workspace()->clientArea(ScreenArea, area.center(), desktop()).size(); const QRect fsa = workspace()->clientArea(FullArea, geom.center(), desktop()); const QSize cs = clientSize(); int pseudo_max = ((info->state() & NET::MaxVert) ? MaximizeVertical : 0) | ((info->state() & NET::MaxHoriz) ? MaximizeHorizontal : 0); if (width() >= area.width()) pseudo_max |= MaximizeHorizontal; if (height() >= area.height()) pseudo_max |= MaximizeVertical; // heuristics: // if decorated client is smaller than the entire screen, the user might want to move it around (multiscreen) // in this case, if the decorated client is bigger than the screen (+1), we don't take this as an // attempt for maximization, but just constrain the size (the window simply wants to be bigger) // NOTICE // i intended a second check on cs < area.size() ("the managed client ("minus border") is smaller // than the workspace") but gtk / gimp seems to store it's size including the decoration, // thus a former maximized window wil become non-maximized bool keepInFsArea = false; if (width() < fsa.width() && (cs.width() > ss.width()+1)) { pseudo_max &= ~MaximizeHorizontal; keepInFsArea = true; } if (height() < fsa.height() && (cs.height() > ss.height()+1)) { pseudo_max &= ~MaximizeVertical; keepInFsArea = true; } if (pseudo_max != MaximizeRestore) { maximize((MaximizeMode)pseudo_max); // from now on, care about maxmode, since the maximization call will override mode for fix aspects dontKeepInArea |= (max_mode == MaximizeFull); geom_restore = QRect(); // Use placement when unmaximizing ... if (!(max_mode & MaximizeVertical)) { geom_restore.setY(y()); // ...but only for horizontal direction geom_restore.setHeight(height()); } if (!(max_mode & MaximizeHorizontal)) { geom_restore.setX(x()); // ...but only for vertical direction geom_restore.setWidth(width()); } } if (keepInFsArea) keepInArea(fsa, partial_keep_in_area); } } if ((!isSpecialWindow() || isToolbar()) && isMovable() && !dontKeepInArea) keepInArea(area, partial_keep_in_area); updateShape(); // CT: Extra check for stupid jdk 1.3.1. But should make sense in general // if client has initial state set to Iconic and is transient with a parent // window that is not Iconic, set init_state to Normal if (init_minimize && isTransient()) { auto mainclients = mainClients(); for (auto it = mainclients.constBegin(); it != mainclients.constEnd(); ++it) if ((*it)->isShown(true)) init_minimize = false; // SELI TODO: Even e.g. for NET::Utility? } // If a dialog is shown for minimized window, minimize it too if (!init_minimize && isTransient() && mainClients().count() > 0 && !workspace()->sessionSaving()) { bool visible_parent = false; // Use allMainClients(), to include also main clients of group transients // that have been optimized out in Client::checkGroupTransients() auto mainclients = allMainClients(); for (auto it = mainclients.constBegin(); it != mainclients.constEnd(); ++it) if ((*it)->isShown(true)) visible_parent = true; if (!visible_parent) { init_minimize = true; demandAttention(); } } if (init_minimize) minimize(true); // No animation // Other settings from the previous session if (session) { // Session restored windows are not considered to be new windows WRT rules, // I.e. obey only forcing rules setKeepAbove(session->keepAbove); setKeepBelow(session->keepBelow); setOriginalSkipTaskbar(session->skipTaskbar); setSkipPager(session->skipPager); setSkipSwitcher(session->skipSwitcher); setShade(session->shaded ? ShadeNormal : ShadeNone); setOpacity(session->opacity); geom_restore = session->restore; if (session->maximized != MaximizeRestore) { maximize(MaximizeMode(session->maximized)); } if (session->fullscreen != FullScreenNone) { setFullScreen(true, false); geom_fs_restore = session->fsrestore; } checkOffscreenPosition(&geom_restore, area); checkOffscreenPosition(&geom_fs_restore, area); } else { // Window may want to be maximized // done after checking that the window isn't larger than the workarea, so that // the restore geometry from the checks above takes precedence, and window // isn't restored larger than the workarea MaximizeMode maxmode = static_cast( ((info->state() & NET::MaxVert) ? MaximizeVertical : 0) | ((info->state() & NET::MaxHoriz) ? MaximizeHorizontal : 0)); MaximizeMode forced_maxmode = rules()->checkMaximize(maxmode, !isMapped); // Either hints were set to maximize, or is forced to maximize, // or is forced to non-maximize and hints were set to maximize if (forced_maxmode != MaximizeRestore || maxmode != MaximizeRestore) maximize(forced_maxmode); // Read other initial states setShade(rules()->checkShade(info->state() & NET::Shaded ? ShadeNormal : ShadeNone, !isMapped)); setKeepAbove(rules()->checkKeepAbove(info->state() & NET::KeepAbove, !isMapped)); setKeepBelow(rules()->checkKeepBelow(info->state() & NET::KeepBelow, !isMapped)); setOriginalSkipTaskbar(rules()->checkSkipTaskbar(info->state() & NET::SkipTaskbar, !isMapped)); setSkipPager(rules()->checkSkipPager(info->state() & NET::SkipPager, !isMapped)); setSkipSwitcher(rules()->checkSkipSwitcher(info->state() & NET::SkipSwitcher, !isMapped)); if (info->state() & NET::DemandsAttention) demandAttention(); if (info->state() & NET::Modal) setModal(true); setFullScreen(rules()->checkFullScreen(info->state() & NET::FullScreen, !isMapped), false); } updateAllowedActions(true); // Set initial user time directly - m_userTime = readUserTimeMapTimestamp(asn_valid ? &asn_id : NULL, asn_valid ? &asn_data : NULL, session); + m_userTime = readUserTimeMapTimestamp(asn_valid ? &asn_id : nullptr, asn_valid ? &asn_data : nullptr, session); group()->updateUserTime(m_userTime); // And do what Client::updateUserTime() does // This should avoid flicker, because real restacking is done // only after manage() finishes because of blocking, but the window is shown sooner m_frame.lower(); if (session && session->stackingOrder != -1) { sm_stacking_order = session->stackingOrder; workspace()->restoreSessionStackingOrder(this); } if (compositing()) // Sending ConfigureNotify is done when setting mapping state below, // Getting the first sync response means window is ready for compositing sendSyncRequest(); else ready_for_painting = true; // set to true in case compositing is turned on later. bug #160393 if (isShown(true)) { bool allow; if (session) allow = session->active && - (!workspace()->wasUserInteraction() || workspace()->activeClient() == NULL || + (!workspace()->wasUserInteraction() || workspace()->activeClient() == nullptr || workspace()->activeClient()->isDesktop()); else allow = workspace()->allowClientActivation(this, userTime(), false); // If session saving, force showing new windows (i.e. "save file?" dialogs etc.) // also force if activation is allowed if( !isOnCurrentDesktop() && !isMapped && !session && ( allow || workspace()->sessionSaving() )) VirtualDesktopManager::self()->setCurrent( desktop()); // If the window is on an inactive activity during session saving, temporarily force it to show. if( !isMapped && !session && workspace()->sessionSaving() && !isOnCurrentActivity()) { setSessionActivityOverride( true ); foreach( AbstractClient* c, mainClients()) { if (Client *mc = dynamic_cast(c)) { mc->setSessionActivityOverride(true); } } } if (isOnCurrentDesktop() && !isMapped && !allow && (!session || session->stackingOrder < 0)) workspace()->restackClientUnderActive(this); updateVisibility(); if (!isMapped) { if (allow && isOnCurrentDesktop()) { if (!isSpecialWindow()) if (options->focusPolicyIsReasonable() && wantsTabFocus()) workspace()->requestFocus(this); } else if (!session && !isSpecialWindow()) demandAttention(); } } else updateVisibility(); Q_ASSERT(mapping_state != Withdrawn); m_managed = true; blockGeometryUpdates(false); if (m_userTime == XCB_TIME_CURRENT_TIME || m_userTime == -1U) { // No known user time, set something old m_userTime = xTime() - 1000000; if (m_userTime == XCB_TIME_CURRENT_TIME || m_userTime == -1U) // Let's be paranoid m_userTime = xTime() - 1000000 + 10; } //sendSyntheticConfigureNotify(); // Done when setting mapping state delete session; discardTemporaryRules(); applyWindowRules(); // Just in case RuleBook::self()->discardUsed(this, false); // Remove ApplyNow rules updateWindowRules(Rules::All); // Was blocked while !isManaged() setBlockingCompositing(info->isBlockingCompositing()); readShowOnScreenEdge(showOnScreenEdgeCookie); // Forward all opacity values to the frame in case there'll be other CM running. connect(Compositor::self(), &Compositor::compositingToggled, this, [this](bool active) { if (active) { return; } if (opacity() == 1.0) { return; } - NETWinInfo info(connection(), frameId(), rootWindow(), 0, 0); + NETWinInfo info(connection(), frameId(), rootWindow(), nullptr, nullptr); info.setOpacity(static_cast(opacity() * 0xffffffff)); } ); // TODO: there's a small problem here - isManaged() depends on the mapping state, // but this client is not yet in Workspace's client list at this point, will // be only done in addClient() emit clientManaging(this); return true; } // Called only from manage() void Client::embedClient(xcb_window_t w, xcb_visualid_t visualid, xcb_colormap_t colormap, uint8_t depth) { Q_ASSERT(m_client == XCB_WINDOW_NONE); Q_ASSERT(frameId() == XCB_WINDOW_NONE); Q_ASSERT(m_wrapper == XCB_WINDOW_NONE); m_client.reset(w, false); const uint32_t zero_value = 0; xcb_connection_t *conn = connection(); // We don't want the window to be destroyed when we quit xcb_change_save_set(conn, XCB_SET_MODE_INSERT, m_client); m_client.selectInput(zero_value); m_client.unmap(); m_client.setBorderWidth(zero_value); // Note: These values must match the order in the xcb_cw_t enum const uint32_t cw_values[] = { 0, // back_pixmap 0, // border_pixel colormap, // colormap Cursor::x11Cursor(Qt::ArrowCursor) }; const uint32_t cw_mask = XCB_CW_BACK_PIXMAP | XCB_CW_BORDER_PIXEL | XCB_CW_COLORMAP | XCB_CW_CURSOR; const uint32_t common_event_mask = XCB_EVENT_MASK_KEY_PRESS | XCB_EVENT_MASK_KEY_RELEASE | XCB_EVENT_MASK_ENTER_WINDOW | XCB_EVENT_MASK_LEAVE_WINDOW | XCB_EVENT_MASK_BUTTON_PRESS | XCB_EVENT_MASK_BUTTON_RELEASE | XCB_EVENT_MASK_BUTTON_MOTION | XCB_EVENT_MASK_POINTER_MOTION | XCB_EVENT_MASK_KEYMAP_STATE | XCB_EVENT_MASK_FOCUS_CHANGE | XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT; const uint32_t frame_event_mask = common_event_mask | XCB_EVENT_MASK_PROPERTY_CHANGE | XCB_EVENT_MASK_VISIBILITY_CHANGE; const uint32_t wrapper_event_mask = common_event_mask | XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY; const uint32_t client_event_mask = XCB_EVENT_MASK_FOCUS_CHANGE | XCB_EVENT_MASK_PROPERTY_CHANGE | XCB_EVENT_MASK_COLOR_MAP_CHANGE | XCB_EVENT_MASK_ENTER_WINDOW | XCB_EVENT_MASK_LEAVE_WINDOW | XCB_EVENT_MASK_KEY_PRESS | XCB_EVENT_MASK_KEY_RELEASE; // Create the frame window xcb_window_t frame = xcb_generate_id(conn); xcb_create_window(conn, depth, frame, rootWindow(), 0, 0, 1, 1, 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, visualid, cw_mask, cw_values); m_frame.reset(frame); setWindowHandles(m_client); // Create the wrapper window xcb_window_t wrapperId = xcb_generate_id(conn); xcb_create_window(conn, depth, wrapperId, frame, 0, 0, 1, 1, 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, visualid, cw_mask, cw_values); m_wrapper.reset(wrapperId); m_client.reparent(m_wrapper); // We could specify the event masks when we create the windows, but the original // Xlib code didn't. Let's preserve that behavior here for now so we don't end up // receiving any unexpected events from the wrapper creation or the reparenting. m_frame.selectInput(frame_event_mask); m_wrapper.selectInput(wrapper_event_mask); m_client.selectInput(client_event_mask); updateMouseGrab(); } } // namespace diff --git a/netinfo.cpp b/netinfo.cpp index fc4024544..1f09c1818 100644 --- a/netinfo.cpp +++ b/netinfo.cpp @@ -1,305 +1,305 @@ /******************************************************************** 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) 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 . *********************************************************************/ // own #include "netinfo.h" // kwin #include "client.h" #include "rootinfo_filter.h" #include "virtualdesktops.h" #include "workspace.h" // Qt #include namespace KWin { extern int screen_number; -RootInfo *RootInfo::s_self = NULL; +RootInfo *RootInfo::s_self = nullptr; RootInfo *RootInfo::create() { Q_ASSERT(!s_self); xcb_window_t supportWindow = xcb_generate_id(connection()); const uint32_t values[] = {true}; xcb_create_window(connection(), XCB_COPY_FROM_PARENT, supportWindow, KWin::rootWindow(), 0, 0, 1, 1, 0, XCB_COPY_FROM_PARENT, XCB_COPY_FROM_PARENT, XCB_CW_OVERRIDE_REDIRECT, values); const uint32_t lowerValues[] = { XCB_STACK_MODE_BELOW }; // See usage in layers.cpp // we need to do the lower window with a roundtrip, otherwise NETRootInfo is not functioning ScopedCPointer error(xcb_request_check(connection(), xcb_configure_window_checked(connection(), supportWindow, XCB_CONFIG_WINDOW_STACK_MODE, lowerValues))); if (!error.isNull()) { qCDebug(KWIN_CORE) << "Error occurred while lowering support window: " << error->error_code; } const NET::Properties properties = NET::Supported | NET::SupportingWMCheck | NET::ClientList | NET::ClientListStacking | NET::DesktopGeometry | NET::NumberOfDesktops | NET::CurrentDesktop | NET::ActiveWindow | NET::WorkArea | NET::CloseWindow | NET::DesktopNames | NET::WMName | NET::WMVisibleName | NET::WMDesktop | NET::WMWindowType | NET::WMState | NET::WMStrut | NET::WMIconGeometry | NET::WMIcon | NET::WMPid | NET::WMMoveResize | NET::WMFrameExtents | NET::WMPing; const NET::WindowTypes types = NET::NormalMask | NET::DesktopMask | NET::DockMask | NET::ToolbarMask | NET::MenuMask | NET::DialogMask | NET::OverrideMask | NET::UtilityMask | NET::SplashMask; // No compositing window types here unless we support them also as managed window types const NET::States states = NET::Modal | //NET::Sticky | // Large desktops not supported (and probably never will be) NET::MaxVert | NET::MaxHoriz | NET::Shaded | NET::SkipTaskbar | NET::KeepAbove | //NET::StaysOnTop | // The same like KeepAbove NET::SkipPager | NET::Hidden | NET::FullScreen | NET::KeepBelow | NET::DemandsAttention | NET::SkipSwitcher | NET::Focused; NET::Properties2 properties2 = NET::WM2UserTime | NET::WM2StartupId | NET::WM2AllowedActions | NET::WM2RestackWindow | NET::WM2MoveResizeWindow | NET::WM2ExtendedStrut | NET::WM2KDETemporaryRules | NET::WM2ShowingDesktop | NET::WM2DesktopLayout | NET::WM2FullPlacement | NET::WM2FullscreenMonitors | NET::WM2KDEShadow | NET::WM2OpaqueRegion; #ifdef KWIN_BUILD_ACTIVITIES properties2 |= NET::WM2Activities; #endif const NET::Actions actions = NET::ActionMove | NET::ActionResize | NET::ActionMinimize | NET::ActionShade | //NET::ActionStick | // Sticky state is not supported NET::ActionMaxVert | NET::ActionMaxHoriz | NET::ActionFullScreen | NET::ActionChangeDesktop | NET::ActionClose; s_self = new RootInfo(supportWindow, "KWin", properties, types, states, properties2, actions, screen_number); return s_self; } void RootInfo::destroy() { if (!s_self) { return; } xcb_window_t supportWindow = s_self->supportWindow(); delete s_self; - s_self = NULL; + s_self = nullptr; xcb_destroy_window(connection(), supportWindow); } RootInfo::RootInfo(xcb_window_t w, const char *name, NET::Properties properties, NET::WindowTypes types, NET::States states, NET::Properties2 properties2, NET::Actions actions, int scr) : NETRootInfo(connection(), w, name, properties, types, states, properties2, actions, scr) , m_activeWindow(activeWindow()) , m_eventFilter(std::make_unique(this)) { } void RootInfo::changeNumberOfDesktops(int n) { VirtualDesktopManager::self()->setCount(n); } void RootInfo::changeCurrentDesktop(int d) { VirtualDesktopManager::self()->setCurrent(d); } void RootInfo::changeActiveWindow(xcb_window_t w, NET::RequestSource src, xcb_timestamp_t timestamp, xcb_window_t active_window) { Workspace *workspace = Workspace::self(); if (Client* c = workspace->findClient(Predicate::WindowMatch, w)) { if (timestamp == XCB_CURRENT_TIME) timestamp = c->userTime(); if (src != NET::FromApplication && src != FromTool) src = NET::FromTool; if (src == NET::FromTool) workspace->activateClient(c, true); // force else if (c == workspace->mostRecentlyActivatedClient()) { return; // WORKAROUND? With > 1 plasma activities, we cause this ourselves. bug #240673 } else { // NET::FromApplication Client* c2; if (workspace->allowClientActivation(c, timestamp, false, true)) workspace->activateClient(c); // if activation of the requestor's window would be allowed, allow activation too else if (active_window != XCB_WINDOW_NONE - && (c2 = workspace->findClient(Predicate::WindowMatch, active_window)) != NULL + && (c2 = workspace->findClient(Predicate::WindowMatch, active_window)) != nullptr && workspace->allowClientActivation(c2, timestampCompare(timestamp, c2->userTime() > 0 ? timestamp : c2->userTime()), false, true)) { workspace->activateClient(c); } else c->demandAttention(); } } } void RootInfo::restackWindow(xcb_window_t w, RequestSource src, xcb_window_t above, int detail, xcb_timestamp_t timestamp) { if (Client* c = Workspace::self()->findClient(Predicate::WindowMatch, w)) { if (timestamp == XCB_CURRENT_TIME) timestamp = c->userTime(); if (src != NET::FromApplication && src != FromTool) src = NET::FromTool; c->restackWindow(above, detail, src, timestamp, true); } } void RootInfo::closeWindow(xcb_window_t w) { Client* c = Workspace::self()->findClient(Predicate::WindowMatch, w); if (c) c->closeWindow(); } void RootInfo::moveResize(xcb_window_t w, int x_root, int y_root, unsigned long direction) { Client* c = Workspace::self()->findClient(Predicate::WindowMatch, w); if (c) { updateXTime(); // otherwise grabbing may have old timestamp - this message should include timestamp c->NETMoveResize(x_root, y_root, (Direction)direction); } } void RootInfo::moveResizeWindow(xcb_window_t w, int flags, int x, int y, int width, int height) { Client* c = Workspace::self()->findClient(Predicate::WindowMatch, w); if (c) c->NETMoveResizeWindow(flags, x, y, width, height); } void RootInfo::gotPing(xcb_window_t w, xcb_timestamp_t timestamp) { if (Client* c = Workspace::self()->findClient(Predicate::WindowMatch, w)) c->gotPing(timestamp); } void RootInfo::changeShowingDesktop(bool showing) { Workspace::self()->setShowingDesktop(showing); } void RootInfo::setActiveClient(AbstractClient *client) { const xcb_window_t w = client ? client->window() : xcb_window_t{XCB_WINDOW_NONE}; if (m_activeWindow == w) { return; } m_activeWindow = w; setActiveWindow(m_activeWindow); } // **************************************** // WinInfo // **************************************** WinInfo::WinInfo(Client * c, xcb_window_t window, xcb_window_t rwin, NET::Properties properties, NET::Properties2 properties2) : NETWinInfo(connection(), window, rwin, properties, properties2, NET::WindowManager), m_client(c) { } void WinInfo::changeDesktop(int desktop) { Workspace::self()->sendClientToDesktop(m_client, desktop, true); } void WinInfo::changeFullscreenMonitors(NETFullscreenMonitors topology) { m_client->updateFullscreenMonitors(topology); } void WinInfo::changeState(NET::States state, NET::States mask) { mask &= ~NET::Sticky; // KWin doesn't support large desktops, ignore mask &= ~NET::Hidden; // clients are not allowed to change this directly state &= mask; // for safety, clear all other bits if ((mask & NET::FullScreen) != 0 && (state & NET::FullScreen) == 0) m_client->setFullScreen(false, false); if ((mask & NET::Max) == NET::Max) m_client->setMaximize(state & NET::MaxVert, state & NET::MaxHoriz); else if (mask & NET::MaxVert) m_client->setMaximize(state & NET::MaxVert, m_client->maximizeMode() & MaximizeHorizontal); else if (mask & NET::MaxHoriz) m_client->setMaximize(m_client->maximizeMode() & MaximizeVertical, state & NET::MaxHoriz); if (mask & NET::Shaded) m_client->setShade(state & NET::Shaded ? ShadeNormal : ShadeNone); if (mask & NET::KeepAbove) m_client->setKeepAbove((state & NET::KeepAbove) != 0); if (mask & NET::KeepBelow) m_client->setKeepBelow((state & NET::KeepBelow) != 0); if (mask & NET::SkipTaskbar) m_client->setOriginalSkipTaskbar((state & NET::SkipTaskbar) != 0); if (mask & NET::SkipPager) m_client->setSkipPager((state & NET::SkipPager) != 0); if (mask & NET::SkipSwitcher) m_client->setSkipSwitcher((state & NET::SkipSwitcher) != 0); if (mask & NET::DemandsAttention) m_client->demandAttention((state & NET::DemandsAttention) != 0); if (mask & NET::Modal) m_client->setModal((state & NET::Modal) != 0); // unsetting fullscreen first, setting it last (because e.g. maximize works only for !isFullScreen() ) if ((mask & NET::FullScreen) != 0 && (state & NET::FullScreen) != 0) m_client->setFullScreen(true, false); } void WinInfo::disable() { - m_client = NULL; // only used when the object is passed to Deleted + m_client = nullptr; // only used when the object is passed to Deleted } } // namespace diff --git a/options.h b/options.h index 7f0996eed..8d92f09a8 100644 --- a/options.h +++ b/options.h @@ -1,905 +1,905 @@ /******************************************************************** 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) 2012 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_OPTIONS_H #define KWIN_OPTIONS_H #include "main.h" #include "placement.h" namespace KWin { // Whether to keep all windows mapped when compositing (i.e. whether to have // actively updated window pixmaps). enum HiddenPreviews { // The normal mode with regard to mapped windows. Hidden (minimized, etc.) // and windows on inactive virtual desktops are not mapped, their pixmaps // are only their icons. HiddenPreviewsNever, // Like normal mode, but shown windows (i.e. on inactive virtual desktops) // are kept mapped, only hidden windows are unmapped. HiddenPreviewsShown, // All windows are kept mapped regardless of their state. HiddenPreviewsAlways }; class Settings; class KWIN_EXPORT Options : public QObject { Q_OBJECT Q_ENUMS(FocusPolicy) Q_ENUMS(GlSwapStrategy) Q_ENUMS(MouseCommand) Q_ENUMS(MouseWheelCommand) Q_ENUMS(WindowOperation) Q_PROPERTY(FocusPolicy focusPolicy READ focusPolicy WRITE setFocusPolicy NOTIFY focusPolicyChanged) Q_PROPERTY(bool nextFocusPrefersMouse READ isNextFocusPrefersMouse WRITE setNextFocusPrefersMouse NOTIFY nextFocusPrefersMouseChanged) /** * Whether clicking on a window raises it in FocusFollowsMouse * mode or not. */ Q_PROPERTY(bool clickRaise READ isClickRaise WRITE setClickRaise NOTIFY clickRaiseChanged) /** * Whether autoraise is enabled FocusFollowsMouse mode or not. */ Q_PROPERTY(bool autoRaise READ isAutoRaise WRITE setAutoRaise NOTIFY autoRaiseChanged) /** * Autoraise interval. */ Q_PROPERTY(int autoRaiseInterval READ autoRaiseInterval WRITE setAutoRaiseInterval NOTIFY autoRaiseIntervalChanged) /** * Delayed focus interval. */ Q_PROPERTY(int delayFocusInterval READ delayFocusInterval WRITE setDelayFocusInterval NOTIFY delayFocusIntervalChanged) /** * Whether shade hover is enabled or not. */ Q_PROPERTY(bool shadeHover READ isShadeHover WRITE setShadeHover NOTIFY shadeHoverChanged) /** * Shade hover interval. */ Q_PROPERTY(int shadeHoverInterval READ shadeHoverInterval WRITE setShadeHoverInterval NOTIFY shadeHoverIntervalChanged) /** * Whether to see Xinerama screens separately for focus (in Alt+Tab, when activating next client) */ Q_PROPERTY(bool separateScreenFocus READ isSeparateScreenFocus WRITE setSeparateScreenFocus NOTIFY separateScreenFocusChanged) Q_PROPERTY(int placement READ placement WRITE setPlacement NOTIFY placementChanged) Q_PROPERTY(bool focusPolicyIsReasonable READ focusPolicyIsReasonable NOTIFY focusPolicyIsResonableChanged) /** * The size of the zone that triggers snapping on desktop borders. */ Q_PROPERTY(int borderSnapZone READ borderSnapZone WRITE setBorderSnapZone NOTIFY borderSnapZoneChanged) /** * The size of the zone that triggers snapping with other windows. */ Q_PROPERTY(int windowSnapZone READ windowSnapZone WRITE setWindowSnapZone NOTIFY windowSnapZoneChanged) /** * The size of the zone that triggers snapping on the screen center. */ Q_PROPERTY(int centerSnapZone READ centerSnapZone WRITE setCenterSnapZone NOTIFY centerSnapZoneChanged) /** * Snap only when windows will overlap. */ Q_PROPERTY(bool snapOnlyWhenOverlapping READ isSnapOnlyWhenOverlapping WRITE setSnapOnlyWhenOverlapping NOTIFY snapOnlyWhenOverlappingChanged) /** * Whether or not we roll over to the other edge when switching desktops past the edge. */ Q_PROPERTY(bool rollOverDesktops READ isRollOverDesktops WRITE setRollOverDesktops NOTIFY rollOverDesktopsChanged) /** * 0 - 4 , see Workspace::allowClientActivation() */ Q_PROPERTY(int focusStealingPreventionLevel READ focusStealingPreventionLevel WRITE setFocusStealingPreventionLevel NOTIFY focusStealingPreventionLevelChanged) Q_PROPERTY(KWin::Options::WindowOperation operationTitlebarDblClick READ operationTitlebarDblClick WRITE setOperationTitlebarDblClick NOTIFY operationTitlebarDblClickChanged) Q_PROPERTY(KWin::Options::WindowOperation operationMaxButtonLeftClick READ operationMaxButtonLeftClick WRITE setOperationMaxButtonLeftClick NOTIFY operationMaxButtonLeftClickChanged) Q_PROPERTY(KWin::Options::WindowOperation operationMaxButtonMiddleClick READ operationMaxButtonMiddleClick WRITE setOperationMaxButtonMiddleClick NOTIFY operationMaxButtonMiddleClickChanged) Q_PROPERTY(KWin::Options::WindowOperation operationMaxButtonRightClick READ operationMaxButtonRightClick WRITE setOperationMaxButtonRightClick NOTIFY operationMaxButtonRightClickChanged) Q_PROPERTY(MouseCommand commandActiveTitlebar1 READ commandActiveTitlebar1 WRITE setCommandActiveTitlebar1 NOTIFY commandActiveTitlebar1Changed) Q_PROPERTY(MouseCommand commandActiveTitlebar2 READ commandActiveTitlebar2 WRITE setCommandActiveTitlebar2 NOTIFY commandActiveTitlebar2Changed) Q_PROPERTY(MouseCommand commandActiveTitlebar3 READ commandActiveTitlebar3 WRITE setCommandActiveTitlebar3 NOTIFY commandActiveTitlebar3Changed) Q_PROPERTY(MouseCommand commandInactiveTitlebar1 READ commandInactiveTitlebar1 WRITE setCommandInactiveTitlebar1 NOTIFY commandInactiveTitlebar1Changed) Q_PROPERTY(MouseCommand commandInactiveTitlebar2 READ commandInactiveTitlebar2 WRITE setCommandInactiveTitlebar2 NOTIFY commandInactiveTitlebar2Changed) Q_PROPERTY(MouseCommand commandInactiveTitlebar3 READ commandInactiveTitlebar3 WRITE setCommandInactiveTitlebar3 NOTIFY commandInactiveTitlebar3Changed) Q_PROPERTY(MouseCommand commandWindow1 READ commandWindow1 WRITE setCommandWindow1 NOTIFY commandWindow1Changed) Q_PROPERTY(MouseCommand commandWindow2 READ commandWindow2 WRITE setCommandWindow2 NOTIFY commandWindow2Changed) Q_PROPERTY(MouseCommand commandWindow3 READ commandWindow3 WRITE setCommandWindow3 NOTIFY commandWindow3Changed) Q_PROPERTY(MouseCommand commandWindowWheel READ commandWindowWheel WRITE setCommandWindowWheel NOTIFY commandWindowWheelChanged) Q_PROPERTY(MouseCommand commandAll1 READ commandAll1 WRITE setCommandAll1 NOTIFY commandAll1Changed) Q_PROPERTY(MouseCommand commandAll2 READ commandAll2 WRITE setCommandAll2 NOTIFY commandAll2Changed) Q_PROPERTY(MouseCommand commandAll3 READ commandAll3 WRITE setCommandAll3 NOTIFY commandAll3Changed) Q_PROPERTY(uint keyCmdAllModKey READ keyCmdAllModKey WRITE setKeyCmdAllModKey NOTIFY keyCmdAllModKeyChanged) /** * Whether the Geometry Tip should be shown during a window move/resize. */ Q_PROPERTY(bool showGeometryTip READ showGeometryTip WRITE setShowGeometryTip NOTIFY showGeometryTipChanged) /** * Whether the visible name should be condensed. */ Q_PROPERTY(bool condensedTitle READ condensedTitle WRITE setCondensedTitle NOTIFY condensedTitleChanged) /** * Whether a window gets maximized when it reaches top screen edge while being moved. */ Q_PROPERTY(bool electricBorderMaximize READ electricBorderMaximize WRITE setElectricBorderMaximize NOTIFY electricBorderMaximizeChanged) /** * Whether a window is tiled to half screen when reaching left or right screen edge while been moved. */ Q_PROPERTY(bool electricBorderTiling READ electricBorderTiling WRITE setElectricBorderTiling NOTIFY electricBorderTilingChanged) /** * Whether a window is tiled to half screen when reaching left or right screen edge while been moved. */ Q_PROPERTY(float electricBorderCornerRatio READ electricBorderCornerRatio WRITE setElectricBorderCornerRatio NOTIFY electricBorderCornerRatioChanged) Q_PROPERTY(bool borderlessMaximizedWindows READ borderlessMaximizedWindows WRITE setBorderlessMaximizedWindows NOTIFY borderlessMaximizedWindowsChanged) /** * timeout before non-responding application will be killed after attempt to close. */ Q_PROPERTY(int killPingTimeout READ killPingTimeout WRITE setKillPingTimeout NOTIFY killPingTimeoutChanged) /** * Whether to hide utility windows for inactive applications. */ Q_PROPERTY(bool hideUtilityWindowsForInactive READ isHideUtilityWindowsForInactive WRITE setHideUtilityWindowsForInactive NOTIFY hideUtilityWindowsForInactiveChanged) Q_PROPERTY(int compositingMode READ compositingMode WRITE setCompositingMode NOTIFY compositingModeChanged) Q_PROPERTY(bool useCompositing READ isUseCompositing WRITE setUseCompositing NOTIFY useCompositingChanged) Q_PROPERTY(int hiddenPreviews READ hiddenPreviews WRITE setHiddenPreviews NOTIFY hiddenPreviewsChanged) /** * 0 = no, 1 = yes when transformed, * 2 = try trilinear when transformed; else 1, * -1 = auto */ Q_PROPERTY(int glSmoothScale READ glSmoothScale WRITE setGlSmoothScale NOTIFY glSmoothScaleChanged) Q_PROPERTY(bool xrenderSmoothScale READ isXrenderSmoothScale WRITE setXrenderSmoothScale NOTIFY xrenderSmoothScaleChanged) Q_PROPERTY(qint64 maxFpsInterval READ maxFpsInterval WRITE setMaxFpsInterval NOTIFY maxFpsIntervalChanged) Q_PROPERTY(uint refreshRate READ refreshRate WRITE setRefreshRate NOTIFY refreshRateChanged) Q_PROPERTY(qint64 vBlankTime READ vBlankTime WRITE setVBlankTime NOTIFY vBlankTimeChanged) Q_PROPERTY(bool glStrictBinding READ isGlStrictBinding WRITE setGlStrictBinding NOTIFY glStrictBindingChanged) /** * Whether strict binding follows the driver or has been overwritten by a user defined config value. * If @c true glStrictBinding is set by the OpenGL Scene during initialization. * If @c false glStrictBinding is set from a config value and not updated during scene initialization. */ Q_PROPERTY(bool glStrictBindingFollowsDriver READ isGlStrictBindingFollowsDriver WRITE setGlStrictBindingFollowsDriver NOTIFY glStrictBindingFollowsDriverChanged) Q_PROPERTY(bool glCoreProfile READ glCoreProfile WRITE setGLCoreProfile NOTIFY glCoreProfileChanged) Q_PROPERTY(GlSwapStrategy glPreferBufferSwap READ glPreferBufferSwap WRITE setGlPreferBufferSwap NOTIFY glPreferBufferSwapChanged) Q_PROPERTY(KWin::OpenGLPlatformInterface glPlatformInterface READ glPlatformInterface WRITE setGlPlatformInterface NOTIFY glPlatformInterfaceChanged) Q_PROPERTY(bool windowsBlockCompositing READ windowsBlockCompositing WRITE setWindowsBlockCompositing NOTIFY windowsBlockCompositingChanged) public: - explicit Options(QObject *parent = NULL); + explicit Options(QObject *parent = nullptr); ~Options() override; void updateSettings(); /** * This enum type is used to specify the focus policy. * * Note that FocusUnderMouse and FocusStrictlyUnderMouse are not * particulary useful. They are only provided for old-fashined * die-hard UNIX people ;-) */ enum FocusPolicy { /** * Clicking into a window activates it. This is also the default. */ ClickToFocus, /** * Moving the mouse pointer actively onto a normal window activates it. * For convenience, the desktop and windows on the dock are excluded. * They require clicking. */ FocusFollowsMouse, /** * The window that happens to be under the mouse pointer becomes active. * The invariant is: no window can have focus that is not under the mouse. * This also means that Alt-Tab won't work properly and popup dialogs are * usually unsable with the keyboard. Note that the desktop and windows on * the dock are excluded for convenience. They get focus only when clicking * on it. */ FocusUnderMouse, /** * This is even worse than FocusUnderMouse. Only the window under the mouse * pointer is active. If the mouse points nowhere, nothing has the focus. If * the mouse points onto the desktop, the desktop has focus. The same holds * for windows on the dock. */ FocusStrictlyUnderMouse }; FocusPolicy focusPolicy() const { return m_focusPolicy; } bool isNextFocusPrefersMouse() const { return m_nextFocusPrefersMouse; } /** * Whether clicking on a window raises it in FocusFollowsMouse * mode or not. */ bool isClickRaise() const { return m_clickRaise; } /** * Whether autoraise is enabled FocusFollowsMouse mode or not. */ bool isAutoRaise() const { return m_autoRaise; } /** * Autoraise interval */ int autoRaiseInterval() const { return m_autoRaiseInterval; } /** * Delayed focus interval. */ int delayFocusInterval() const { return m_delayFocusInterval; } /** * Whether shade hover is enabled or not. */ bool isShadeHover() const { return m_shadeHover; } /** * Shade hover interval. */ int shadeHoverInterval() { return m_shadeHoverInterval; } /** * Whether to see Xinerama screens separately for focus (in Alt+Tab, when activating next client) */ bool isSeparateScreenFocus() const { return m_separateScreenFocus; } Placement::Policy placement() const { return m_placement; } bool focusPolicyIsReasonable() { return m_focusPolicy == ClickToFocus || m_focusPolicy == FocusFollowsMouse; } /** * The size of the zone that triggers snapping on desktop borders. */ int borderSnapZone() const { return m_borderSnapZone; } /** * The size of the zone that triggers snapping with other windows. */ int windowSnapZone() const { return m_windowSnapZone; } /** * The size of the zone that triggers snapping on the screen center. */ int centerSnapZone() const { return m_centerSnapZone; } /** * Snap only when windows will overlap. */ bool isSnapOnlyWhenOverlapping() const { return m_snapOnlyWhenOverlapping; } /** * Whether or not we roll over to the other edge when switching desktops past the edge. */ bool isRollOverDesktops() const { return m_rollOverDesktops; } /** * Returns the focus stealing prevention level. * * @see allowClientActivation */ int focusStealingPreventionLevel() const { return m_focusStealingPreventionLevel; } enum WindowOperation { MaximizeOp = 5000, RestoreOp, MinimizeOp, MoveOp, UnrestrictedMoveOp, ResizeOp, UnrestrictedResizeOp, CloseOp, OnAllDesktopsOp, ShadeOp, KeepAboveOp, KeepBelowOp, OperationsOp, WindowRulesOp, ToggleStoreSettingsOp = WindowRulesOp, ///< @obsolete HMaximizeOp, VMaximizeOp, LowerOp, FullScreenOp, NoBorderOp, NoOp, SetupWindowShortcutOp, ApplicationRulesOp, }; WindowOperation operationTitlebarDblClick() const { return OpTitlebarDblClick; } WindowOperation operationMaxButtonLeftClick() const { return opMaxButtonLeftClick; } WindowOperation operationMaxButtonRightClick() const { return opMaxButtonRightClick; } WindowOperation operationMaxButtonMiddleClick() const { return opMaxButtonMiddleClick; } WindowOperation operationMaxButtonClick(Qt::MouseButtons button) const; enum MouseCommand { MouseRaise, MouseLower, MouseOperationsMenu, MouseToggleRaiseAndLower, MouseActivateAndRaise, MouseActivateAndLower, MouseActivate, MouseActivateRaiseAndPassClick, MouseActivateAndPassClick, MouseMove, MouseUnrestrictedMove, MouseActivateRaiseAndMove, MouseActivateRaiseAndUnrestrictedMove, MouseResize, MouseUnrestrictedResize, MouseShade, MouseSetShade, MouseUnsetShade, MouseMaximize, MouseRestore, MouseMinimize, MouseNextDesktop, MousePreviousDesktop, MouseAbove, MouseBelow, MouseOpacityMore, MouseOpacityLess, MouseClose, MouseNothing }; enum MouseWheelCommand { MouseWheelRaiseLower, MouseWheelShadeUnshade, MouseWheelMaximizeRestore, MouseWheelAboveBelow, MouseWheelPreviousNextDesktop, MouseWheelChangeOpacity, MouseWheelNothing }; MouseCommand operationTitlebarMouseWheel(int delta) const { return wheelToMouseCommand(CmdTitlebarWheel, delta); } MouseCommand operationWindowMouseWheel(int delta) const { return wheelToMouseCommand(CmdAllWheel, delta); } MouseCommand commandActiveTitlebar1() const { return CmdActiveTitlebar1; } MouseCommand commandActiveTitlebar2() const { return CmdActiveTitlebar2; } MouseCommand commandActiveTitlebar3() const { return CmdActiveTitlebar3; } MouseCommand commandInactiveTitlebar1() const { return CmdInactiveTitlebar1; } MouseCommand commandInactiveTitlebar2() const { return CmdInactiveTitlebar2; } MouseCommand commandInactiveTitlebar3() const { return CmdInactiveTitlebar3; } MouseCommand commandWindow1() const { return CmdWindow1; } MouseCommand commandWindow2() const { return CmdWindow2; } MouseCommand commandWindow3() const { return CmdWindow3; } MouseCommand commandWindowWheel() const { return CmdWindowWheel; } MouseCommand commandAll1() const { return CmdAll1; } MouseCommand commandAll2() const { return CmdAll2; } MouseCommand commandAll3() const { return CmdAll3; } uint keyCmdAllModKey() const { return CmdAllModKey; } Qt::KeyboardModifier commandAllModifier() const { switch (CmdAllModKey) { case Qt::Key_Alt: return Qt::AltModifier; case Qt::Key_Meta: return Qt::MetaModifier; default: Q_UNREACHABLE(); } } static WindowOperation windowOperation(const QString &name, bool restricted); static MouseCommand mouseCommand(const QString &name, bool restricted); static MouseWheelCommand mouseWheelCommand(const QString &name); /** * @returns true if the Geometry Tip should be shown during a window move/resize. */ bool showGeometryTip() const; /** * Returns whether the user prefers his caption clean. */ bool condensedTitle() const; /** * @returns true if a window gets maximized when it reaches top screen edge * while being moved. */ bool electricBorderMaximize() const { return electric_border_maximize; } /** * @returns true if window is tiled to half screen when reaching left or * right screen edge while been moved. */ bool electricBorderTiling() const { return electric_border_tiling; } /** * @returns the factor that determines the corner part of the edge (ie. 0.1 means tiny corner) */ float electricBorderCornerRatio() const { return electric_border_corner_ratio; } bool borderlessMaximizedWindows() const { return borderless_maximized_windows; } /** * Timeout before non-responding application will be killed after attempt to close. */ int killPingTimeout() const { return m_killPingTimeout; } /** * Whether to hide utility windows for inactive applications. */ bool isHideUtilityWindowsForInactive() const { return m_hideUtilityWindowsForInactive; } /** * Returns the animation time factor for desktop effects. */ double animationTimeFactor() const; //---------------------- // Compositing settings void reloadCompositingSettings(bool force = false); CompositingType compositingMode() const { return m_compositingMode; } void setCompositingMode(CompositingType mode) { m_compositingMode = mode; } // Separate to mode so the user can toggle bool isUseCompositing() const; // General preferences HiddenPreviews hiddenPreviews() const { return m_hiddenPreviews; } // OpenGL // 0 = no, 1 = yes when transformed, // 2 = try trilinear when transformed; else 1, // -1 = auto int glSmoothScale() const { return m_glSmoothScale; } // XRender bool isXrenderSmoothScale() const { return m_xrenderSmoothScale; } qint64 maxFpsInterval() const { return m_maxFpsInterval; } // Settings that should be auto-detected uint refreshRate() const { return m_refreshRate; } qint64 vBlankTime() const { return m_vBlankTime; } bool isGlStrictBinding() const { return m_glStrictBinding; } bool isGlStrictBindingFollowsDriver() const { return m_glStrictBindingFollowsDriver; } bool glCoreProfile() const { return m_glCoreProfile; } OpenGLPlatformInterface glPlatformInterface() const { return m_glPlatformInterface; } enum GlSwapStrategy { NoSwapEncourage = 0, CopyFrontBuffer = 'c', PaintFullScreen = 'p', ExtendDamage = 'e', AutoSwapStrategy = 'a' }; GlSwapStrategy glPreferBufferSwap() const { return m_glPreferBufferSwap; } bool windowsBlockCompositing() const { return m_windowsBlockCompositing; } QStringList modifierOnlyDBusShortcut(Qt::KeyboardModifier mod) const; // setters void setFocusPolicy(FocusPolicy focusPolicy); void setNextFocusPrefersMouse(bool nextFocusPrefersMouse); void setClickRaise(bool clickRaise); void setAutoRaise(bool autoRaise); void setAutoRaiseInterval(int autoRaiseInterval); void setDelayFocusInterval(int delayFocusInterval); void setShadeHover(bool shadeHover); void setShadeHoverInterval(int shadeHoverInterval); void setSeparateScreenFocus(bool separateScreenFocus); void setPlacement(int placement); void setBorderSnapZone(int borderSnapZone); void setWindowSnapZone(int windowSnapZone); void setCenterSnapZone(int centerSnapZone); void setSnapOnlyWhenOverlapping(bool snapOnlyWhenOverlapping); void setRollOverDesktops(bool rollOverDesktops); void setFocusStealingPreventionLevel(int focusStealingPreventionLevel); void setOperationTitlebarDblClick(WindowOperation operationTitlebarDblClick); void setOperationMaxButtonLeftClick(WindowOperation op); void setOperationMaxButtonRightClick(WindowOperation op); void setOperationMaxButtonMiddleClick(WindowOperation op); void setCommandActiveTitlebar1(MouseCommand commandActiveTitlebar1); void setCommandActiveTitlebar2(MouseCommand commandActiveTitlebar2); void setCommandActiveTitlebar3(MouseCommand commandActiveTitlebar3); void setCommandInactiveTitlebar1(MouseCommand commandInactiveTitlebar1); void setCommandInactiveTitlebar2(MouseCommand commandInactiveTitlebar2); void setCommandInactiveTitlebar3(MouseCommand commandInactiveTitlebar3); void setCommandWindow1(MouseCommand commandWindow1); void setCommandWindow2(MouseCommand commandWindow2); void setCommandWindow3(MouseCommand commandWindow3); void setCommandWindowWheel(MouseCommand commandWindowWheel); void setCommandAll1(MouseCommand commandAll1); void setCommandAll2(MouseCommand commandAll2); void setCommandAll3(MouseCommand commandAll3); void setKeyCmdAllModKey(uint keyCmdAllModKey); void setShowGeometryTip(bool showGeometryTip); void setCondensedTitle(bool condensedTitle); void setElectricBorderMaximize(bool electricBorderMaximize); void setElectricBorderTiling(bool electricBorderTiling); void setElectricBorderCornerRatio(float electricBorderCornerRatio); void setBorderlessMaximizedWindows(bool borderlessMaximizedWindows); void setKillPingTimeout(int killPingTimeout); void setHideUtilityWindowsForInactive(bool hideUtilityWindowsForInactive); void setCompositingMode(int compositingMode); void setUseCompositing(bool useCompositing); void setHiddenPreviews(int hiddenPreviews); void setGlSmoothScale(int glSmoothScale); void setXrenderSmoothScale(bool xrenderSmoothScale); void setMaxFpsInterval(qint64 maxFpsInterval); void setRefreshRate(uint refreshRate); void setVBlankTime(qint64 vBlankTime); void setGlStrictBinding(bool glStrictBinding); void setGlStrictBindingFollowsDriver(bool glStrictBindingFollowsDriver); void setGLCoreProfile(bool glCoreProfile); void setGlPreferBufferSwap(char glPreferBufferSwap); void setGlPlatformInterface(OpenGLPlatformInterface interface); void setWindowsBlockCompositing(bool set); // default values static WindowOperation defaultOperationTitlebarDblClick() { return MaximizeOp; } static WindowOperation defaultOperationMaxButtonLeftClick() { return MaximizeOp; } static WindowOperation defaultOperationMaxButtonRightClick() { return HMaximizeOp; } static WindowOperation defaultOperationMaxButtonMiddleClick() { return VMaximizeOp; } static MouseCommand defaultCommandActiveTitlebar1() { return MouseRaise; } static MouseCommand defaultCommandActiveTitlebar2() { return MouseNothing; } static MouseCommand defaultCommandActiveTitlebar3() { return MouseOperationsMenu; } static MouseCommand defaultCommandInactiveTitlebar1() { return MouseActivateAndRaise; } static MouseCommand defaultCommandInactiveTitlebar2() { return MouseNothing; } static MouseCommand defaultCommandInactiveTitlebar3() { return MouseOperationsMenu; } static MouseCommand defaultCommandWindow1() { return MouseActivateRaiseAndPassClick; } static MouseCommand defaultCommandWindow2() { return MouseActivateAndPassClick; } static MouseCommand defaultCommandWindow3() { return MouseActivateAndPassClick; } static MouseCommand defaultCommandWindowWheel() { return MouseNothing; } static MouseCommand defaultCommandAll1() { return MouseUnrestrictedMove; } static MouseCommand defaultCommandAll2() { return MouseToggleRaiseAndLower; } static MouseCommand defaultCommandAll3() { return MouseUnrestrictedResize; } static MouseWheelCommand defaultCommandTitlebarWheel() { return MouseWheelNothing; } static MouseWheelCommand defaultCommandAllWheel() { return MouseWheelNothing; } static uint defaultKeyCmdAllModKey() { return Qt::Key_Alt; } static CompositingType defaultCompositingMode() { return OpenGLCompositing; } static bool defaultUseCompositing() { return true; } static HiddenPreviews defaultHiddenPreviews() { return HiddenPreviewsShown; } static int defaultGlSmoothScale() { return 2; } static bool defaultXrenderSmoothScale() { return false; } static qint64 defaultMaxFpsInterval() { return (1 * 1000 * 1000 * 1000) /60.0; // nanoseconds / Hz } static int defaultMaxFps() { return 60; } static uint defaultRefreshRate() { return 0; } static uint defaultVBlankTime() { return 6000; // 6ms } static bool defaultGlStrictBinding() { return true; } static bool defaultGlStrictBindingFollowsDriver() { return true; } static bool defaultGLCoreProfile() { return false; } static GlSwapStrategy defaultGlPreferBufferSwap() { return AutoSwapStrategy; } static OpenGLPlatformInterface defaultGlPlatformInterface() { return kwinApp()->shouldUseWaylandForCompositing() ? EglPlatformInterface : GlxPlatformInterface; } static int defaultAnimationSpeed() { return 3; } /** * Performs loading all settings except compositing related. */ void loadConfig(); /** * Performs loading of compositing settings which do not depend on OpenGL. */ bool loadCompositingConfig(bool force); void reparseConfiguration(); static int currentRefreshRate(); //---------------------- Q_SIGNALS: // for properties void focusPolicyChanged(); void focusPolicyIsResonableChanged(); void nextFocusPrefersMouseChanged(); void clickRaiseChanged(); void autoRaiseChanged(); void autoRaiseIntervalChanged(); void delayFocusIntervalChanged(); void shadeHoverChanged(); void shadeHoverIntervalChanged(); void separateScreenFocusChanged(bool); void placementChanged(); void borderSnapZoneChanged(); void windowSnapZoneChanged(); void centerSnapZoneChanged(); void snapOnlyWhenOverlappingChanged(); void rollOverDesktopsChanged(bool enabled); void focusStealingPreventionLevelChanged(); void operationTitlebarDblClickChanged(); void operationMaxButtonLeftClickChanged(); void operationMaxButtonRightClickChanged(); void operationMaxButtonMiddleClickChanged(); void commandActiveTitlebar1Changed(); void commandActiveTitlebar2Changed(); void commandActiveTitlebar3Changed(); void commandInactiveTitlebar1Changed(); void commandInactiveTitlebar2Changed(); void commandInactiveTitlebar3Changed(); void commandWindow1Changed(); void commandWindow2Changed(); void commandWindow3Changed(); void commandWindowWheelChanged(); void commandAll1Changed(); void commandAll2Changed(); void commandAll3Changed(); void keyCmdAllModKeyChanged(); void showGeometryTipChanged(); void condensedTitleChanged(); void electricBorderMaximizeChanged(); void electricBorderTilingChanged(); void electricBorderCornerRatioChanged(); void borderlessMaximizedWindowsChanged(); void killPingTimeoutChanged(); void hideUtilityWindowsForInactiveChanged(); void compositingModeChanged(); void useCompositingChanged(); void hiddenPreviewsChanged(); void glSmoothScaleChanged(); void xrenderSmoothScaleChanged(); void maxFpsIntervalChanged(); void refreshRateChanged(); void vBlankTimeChanged(); void glStrictBindingChanged(); void glStrictBindingFollowsDriverChanged(); void glCoreProfileChanged(); void glPreferBufferSwapChanged(); void glPlatformInterfaceChanged(); void windowsBlockCompositingChanged(); void configChanged(); private: void setElectricBorders(int borders); void syncFromKcfgc(); QScopedPointer m_settings; FocusPolicy m_focusPolicy; bool m_nextFocusPrefersMouse; bool m_clickRaise; bool m_autoRaise; int m_autoRaiseInterval; int m_delayFocusInterval; bool m_shadeHover; int m_shadeHoverInterval; bool m_separateScreenFocus; Placement::Policy m_placement; int m_borderSnapZone; int m_windowSnapZone; int m_centerSnapZone; bool m_snapOnlyWhenOverlapping; bool m_rollOverDesktops; int m_focusStealingPreventionLevel; int m_killPingTimeout; bool m_hideUtilityWindowsForInactive; CompositingType m_compositingMode; bool m_useCompositing; HiddenPreviews m_hiddenPreviews; int m_glSmoothScale; bool m_xrenderSmoothScale; qint64 m_maxFpsInterval; // Settings that should be auto-detected uint m_refreshRate; qint64 m_vBlankTime; bool m_glStrictBinding; bool m_glStrictBindingFollowsDriver; bool m_glCoreProfile; GlSwapStrategy m_glPreferBufferSwap; OpenGLPlatformInterface m_glPlatformInterface; bool m_windowsBlockCompositing; WindowOperation OpTitlebarDblClick; WindowOperation opMaxButtonRightClick = defaultOperationMaxButtonRightClick(); WindowOperation opMaxButtonMiddleClick = defaultOperationMaxButtonMiddleClick(); WindowOperation opMaxButtonLeftClick = defaultOperationMaxButtonRightClick(); // mouse bindings MouseCommand CmdActiveTitlebar1; MouseCommand CmdActiveTitlebar2; MouseCommand CmdActiveTitlebar3; MouseCommand CmdInactiveTitlebar1; MouseCommand CmdInactiveTitlebar2; MouseCommand CmdInactiveTitlebar3; MouseWheelCommand CmdTitlebarWheel; MouseCommand CmdWindow1; MouseCommand CmdWindow2; MouseCommand CmdWindow3; MouseCommand CmdWindowWheel; MouseCommand CmdAll1; MouseCommand CmdAll2; MouseCommand CmdAll3; MouseWheelCommand CmdAllWheel; uint CmdAllModKey; bool electric_border_maximize; bool electric_border_tiling; float electric_border_corner_ratio; bool borderless_maximized_windows; bool show_geometry_tip; bool condensed_title; int animationSpeed; // 0 - instant, 5 - very slow QHash m_modifierOnlyShortcuts; MouseCommand wheelToMouseCommand(MouseWheelCommand com, int delta) const; }; extern KWIN_EXPORT Options* options; } // namespace Q_DECLARE_METATYPE(KWin::Options::WindowOperation) Q_DECLARE_METATYPE(KWin::OpenGLPlatformInterface) #endif diff --git a/placement.cpp b/placement.cpp index fb40c20f7..d61704613 100644 --- a/placement.cpp +++ b/placement.cpp @@ -1,957 +1,957 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 1999, 2000 Matthias Ettrich Copyright (C) 1997 to 2002 Cristian Tibirna 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 . *********************************************************************/ #include "placement.h" #ifndef KCMRULES #include "workspace.h" #include "client.h" #include "cursor.h" #include "options.h" #include "rules.h" #include "screens.h" #endif #include #include namespace KWin { #ifndef KCMRULES KWIN_SINGLETON_FACTORY(Placement) Placement::Placement(QObject*) { reinitCascading(0); } Placement::~Placement() { - s_self = NULL; + s_self = nullptr; } /** * Places the client \a c according to the workspace's layout policy */ void Placement::place(AbstractClient *c, const QRect &area) { Policy policy = c->rules()->checkPlacement(Default); if (policy != Default) { place(c, area, policy); return; } if (c->isUtility()) placeUtility(c, area, options->placement()); else if (c->isDialog()) placeDialog(c, area, options->placement()); else if (c->isSplash()) placeOnMainWindow(c, area); // on mainwindow, if any, otherwise centered else if (c->isOnScreenDisplay() || c->isNotification() || c->isCriticalNotification()) placeOnScreenDisplay(c, area); else if (c->isTransient() && c->hasTransientPlacementHint()) placeTransient(c); else if (c->isTransient() && c->surface()) placeDialog(c, area, options->placement()); else place(c, area, options->placement()); } void Placement::place(AbstractClient *c, const QRect &area, Policy policy, Policy nextPlacement) { if (policy == Unknown) policy = Default; if (policy == Default) policy = options->placement(); if (policy == NoPlacement) return; else if (policy == Random) placeAtRandom(c, area, nextPlacement); else if (policy == Cascade) placeCascaded(c, area, nextPlacement); else if (policy == Centered) placeCentered(c, area, nextPlacement); else if (policy == ZeroCornered) placeZeroCornered(c, area, nextPlacement); else if (policy == UnderMouse) placeUnderMouse(c, area, nextPlacement); else if (policy == OnMainWindow) placeOnMainWindow(c, area, nextPlacement); else if (policy == Maximizing) placeMaximizing(c, area, nextPlacement); else placeSmart(c, area, nextPlacement); if (options->borderSnapZone()) { // snap to titlebar / snap to window borders on inner screen edges const QRect geo(c->geometry()); QPoint corner = geo.topLeft(); const QPoint cp = c->clientPos(); const QSize cs = geo.size() - c->clientSize(); AbstractClient::Position titlePos = c->titlebarPosition(); const QRect fullRect = workspace()->clientArea(FullArea, c); if (!(c->maximizeMode() & MaximizeHorizontal)) { if (titlePos != AbstractClient::PositionRight && geo.right() == fullRect.right()) corner.rx() += cs.width() - cp.x(); if (titlePos != AbstractClient::PositionLeft && geo.x() == fullRect.x()) corner.rx() -= cp.x(); } if (!(c->maximizeMode() & MaximizeVertical)) { if (titlePos != AbstractClient::PositionBottom && geo.bottom() == fullRect.bottom()) corner.ry() += cs.height() - cp.y(); if (titlePos != AbstractClient::PositionTop && geo.y() == fullRect.y()) corner.ry() -= cp.y(); } c->move(corner); } } /** * Place the client \a c according to a simply "random" placement algorithm. */ void Placement::placeAtRandom(AbstractClient* c, const QRect& area, Policy /*next*/) { Q_ASSERT(area.isValid()); const int step = 24; static int px = step; static int py = 2 * step; int tx, ty; if (px < area.x()) { px = area.x(); } if (py < area.y()) { py = area.y(); } px += step; py += 2 * step; if (px > area.width() / 2) { px = area.x() + step; } if (py > area.height() / 2) { py = area.y() + step; } tx = px; ty = py; if (tx + c->width() > area.right()) { tx = area.right() - c->width(); if (tx < 0) tx = 0; px = area.x(); } if (ty + c->height() > area.bottom()) { ty = area.bottom() - c->height(); if (ty < 0) ty = 0; py = area.y(); } c->move(tx, ty); } // TODO: one day, there'll be C++11 ... static inline bool isIrrelevant(const AbstractClient *client, const AbstractClient *regarding, int desktop) { if (!client) return true; if (client == regarding) return true; if (!client->isShown(false)) return true; if (!client->isOnDesktop(desktop)) return true; if (!client->isOnCurrentActivity()) return true; if (client->isDesktop()) return true; return false; } /** * Place the client \a c according to a really smart placement algorithm :-) */ void Placement::placeSmart(AbstractClient* c, const QRect& area, Policy /*next*/) { Q_ASSERT(area.isValid()); /* * SmartPlacement by Cristian Tibirna (tibirna@kde.org) * adapted for kwm (16-19jan98) and for kwin (16Nov1999) using (with * permission) ideas from fvwm, authored by * Anthony Martin (amartin@engr.csulb.edu). * Xinerama supported added by Balaji Ramani (balaji@yablibli.com) * with ideas from xfce. */ if (!c->size().isValid()) { return; } const int none = 0, h_wrong = -1, w_wrong = -2; // overlap types long int overlap, min_overlap = 0; int x_optimal, y_optimal; int possible; int desktop = c->desktop() == 0 || c->isOnAllDesktops() ? VirtualDesktopManager::self()->current() : c->desktop(); int cxl, cxr, cyt, cyb; //temp coords int xl, xr, yt, yb; //temp coords int basket; //temp holder // get the maximum allowed windows space int x = area.left(); int y = area.top(); x_optimal = x; y_optimal = y; //client gabarit int ch = c->height() - 1; int cw = c->width() - 1; bool first_pass = true; //CT lame flag. Don't like it. What else would do? //loop over possible positions do { //test if enough room in x and y directions if (y + ch > area.bottom() && ch < area.height()) { overlap = h_wrong; // this throws the algorithm to an exit } else if (x + cw > area.right()) { overlap = w_wrong; } else { overlap = none; //initialize cxl = x; cxr = x + cw; cyt = y; cyb = y + ch; ToplevelList::ConstIterator l; for (l = workspace()->stackingOrder().constBegin(); l != workspace()->stackingOrder().constEnd() ; ++l) { AbstractClient *client = qobject_cast(*l); if (isIrrelevant(client, c, desktop)) { continue; } xl = client->x(); yt = client->y(); xr = xl + client->width(); yb = yt + client->height(); //if windows overlap, calc the overall overlapping if ((cxl < xr) && (cxr > xl) && (cyt < yb) && (cyb > yt)) { xl = qMax(cxl, xl); xr = qMin(cxr, xr); yt = qMax(cyt, yt); yb = qMin(cyb, yb); if (client->keepAbove()) overlap += 16 * (xr - xl) * (yb - yt); else if (client->keepBelow() && !client->isDock()) // ignore KeepBelow windows overlap += 0; // for placement (see Client::belongsToLayer() for Dock) else overlap += (xr - xl) * (yb - yt); } } } //CT first time we get no overlap we stop. if (overlap == none) { x_optimal = x; y_optimal = y; break; } if (first_pass) { first_pass = false; min_overlap = overlap; } //CT save the best position and the minimum overlap up to now else if (overlap >= none && overlap < min_overlap) { min_overlap = overlap; x_optimal = x; y_optimal = y; } // really need to loop? test if there's any overlap if (overlap > none) { possible = area.right(); if (possible - cw > x) possible -= cw; // compare to the position of each client on the same desk ToplevelList::ConstIterator l; for (l = workspace()->stackingOrder().constBegin(); l != workspace()->stackingOrder().constEnd() ; ++l) { AbstractClient *client = qobject_cast(*l); if (isIrrelevant(client, c, desktop)) { continue; } xl = client->x(); yt = client->y(); xr = xl + client->width(); yb = yt + client->height(); // if not enough room above or under the current tested client // determine the first non-overlapped x position if ((y < yb) && (yt < ch + y)) { if ((xr > x) && (possible > xr)) possible = xr; basket = xl - cw; if ((basket > x) && (possible > basket)) possible = basket; } } x = possible; } // ... else ==> not enough x dimension (overlap was wrong on horizontal) else if (overlap == w_wrong) { x = area.left(); possible = area.bottom(); if (possible - ch > y) possible -= ch; //test the position of each window on the desk ToplevelList::ConstIterator l; for (l = workspace()->stackingOrder().constBegin(); l != workspace()->stackingOrder().constEnd() ; ++l) { AbstractClient *client = qobject_cast(*l); if (isIrrelevant(client, c, desktop)) { continue; } xl = client->x(); yt = client->y(); xr = xl + client->width(); yb = yt + client->height(); // if not enough room to the left or right of the current tested client // determine the first non-overlapped y position if ((yb > y) && (possible > yb)) possible = yb; basket = yt - ch; if ((basket > y) && (possible > basket)) possible = basket; } y = possible; } } while ((overlap != none) && (overlap != h_wrong) && (y < area.bottom())); if (ch >= area.height()) { y_optimal = area.top(); } // place the window c->move(x_optimal, y_optimal); } void Placement::reinitCascading(int desktop) { // desktop == 0 - reinit all if (desktop == 0) { cci.clear(); for (uint i = 0; i < VirtualDesktopManager::self()->count(); ++i) { DesktopCascadingInfo inf; inf.pos = QPoint(-1, -1); inf.col = 0; inf.row = 0; cci.append(inf); } } else { cci[desktop - 1].pos = QPoint(-1, -1); cci[desktop - 1].col = cci[desktop - 1].row = 0; } } QPoint Workspace::cascadeOffset(const AbstractClient *c) const { QRect area = clientArea(PlacementArea, c->geometry().center(), c->desktop()); return QPoint(area.width()/48, area.height()/48); } /** * Place windows in a cascading order, remembering positions for each desktop */ void Placement::placeCascaded(AbstractClient *c, const QRect &area, Policy nextPlacement) { Q_ASSERT(area.isValid()); if (!c->size().isValid()) { return; } /* cascadePlacement by Cristian Tibirna (tibirna@kde.org) (30Jan98) */ // work coords int xp, yp; //CT how do I get from the 'Client' class the size that NW squarish "handle" const QPoint delta = workspace()->cascadeOffset(c); const int dn = c->desktop() == 0 || c->isOnAllDesktops() ? (VirtualDesktopManager::self()->current() - 1) : (c->desktop() - 1); // initialize often used vars: width and height of c; we gain speed const int ch = c->height(); const int cw = c->width(); const int X = area.left(); const int Y = area.top(); const int H = area.height(); const int W = area.width(); if (nextPlacement == Unknown) nextPlacement = Smart; //initialize if needed if (cci[dn].pos.x() < 0 || cci[dn].pos.x() < X || cci[dn].pos.y() < Y) { cci[dn].pos = QPoint(X, Y); cci[dn].col = cci[dn].row = 0; } xp = cci[dn].pos.x(); yp = cci[dn].pos.y(); //here to touch in case people vote for resize on placement if ((yp + ch) > H) yp = Y; if ((xp + cw) > W) { if (!yp) { place(c, area, nextPlacement); return; } else xp = X; } //if this isn't the first window if (cci[dn].pos.x() != X && cci[dn].pos.y() != Y) { /* The following statements cause an internal compiler error with * egcs-2.91.66 on SuSE Linux 6.3. The equivalent forms compile fine. * 22-Dec-1999 CS * * if (xp != X && yp == Y) xp = delta.x() * (++(cci[dn].col)); * if (yp != Y && xp == X) yp = delta.y() * (++(cci[dn].row)); */ if (xp != X && yp == Y) { ++(cci[dn].col); xp = delta.x() * cci[dn].col; } if (yp != Y && xp == X) { ++(cci[dn].row); yp = delta.y() * cci[dn].row; } // last resort: if still doesn't fit, smart place it if (((xp + cw) > W - X) || ((yp + ch) > H - Y)) { place(c, area, nextPlacement); return; } } // place the window c->move(QPoint(xp, yp)); // new position cci[dn].pos = QPoint(xp + delta.x(), yp + delta.y()); } /** * Place windows centered, on top of all others */ void Placement::placeCentered(AbstractClient* c, const QRect& area, Policy /*next*/) { Q_ASSERT(area.isValid()); const int xp = area.left() + (area.width() - c->width()) / 2; const int yp = area.top() + (area.height() - c->height()) / 2; // place the window c->move(QPoint(xp, yp)); } /** * Place windows in the (0,0) corner, on top of all others */ void Placement::placeZeroCornered(AbstractClient* c, const QRect& area, Policy /*next*/) { Q_ASSERT(area.isValid()); // get the maximum allowed windows space and desk's origin c->move(area.topLeft()); } void Placement::placeUtility(AbstractClient *c, const QRect &area, Policy /*next*/) { // TODO kwin should try to place utility windows next to their mainwindow, // preferably at the right edge, and going down if there are more of them // if there's not enough place outside the mainwindow, it should prefer // top-right corner // use the default placement for now place(c, area, Default); } void Placement::placeOnScreenDisplay(AbstractClient *c, const QRect &area) { Q_ASSERT(area.isValid()); // place at lower 1/3 of the screen const int x = area.left() + (area.width() - c->width()) / 2; const int y = area.top() + 2 * (area.height() - c->height()) / 3; c->move(QPoint(x, y)); } void Placement::placeTransient(AbstractClient *c) { const auto parent = c->transientFor(); const QRect screen = Workspace::self()->clientArea(parent->isFullScreen() ? FullScreenArea : PlacementArea, parent); const QRect popupGeometry = c->transientPlacement(screen); c->setGeometry(popupGeometry); // Potentially a client could set no constraint adjustments // and we'll be offscreen. // The spec implies we should place window the offscreen. However, // practically Qt doesn't set any constraint adjustments yet so we can't. // Also kwin generally doesn't let clients do what they want if (!screen.contains(c->geometry())) { c->keepInArea(screen); } } void Placement::placeDialog(AbstractClient *c, const QRect &area, Policy nextPlacement) { placeOnMainWindow(c, area, nextPlacement); } void Placement::placeUnderMouse(AbstractClient *c, const QRect &area, Policy /*next*/) { Q_ASSERT(area.isValid()); QRect geom = c->geometry(); geom.moveCenter(Cursor::pos()); c->move(geom.topLeft()); c->keepInArea(area); // make sure it's kept inside workarea } void Placement::placeOnMainWindow(AbstractClient *c, const QRect &area, Policy nextPlacement) { Q_ASSERT(area.isValid()); if (nextPlacement == Unknown) nextPlacement = Centered; if (nextPlacement == Maximizing) // maximize if needed placeMaximizing(c, area, NoPlacement); auto mainwindows = c->mainClients(); AbstractClient* place_on = nullptr; AbstractClient* place_on2 = nullptr; int mains_count = 0; for (auto it = mainwindows.constBegin(); it != mainwindows.constEnd(); ++it) { if (mainwindows.count() > 1 && (*it)->isSpecialWindow()) continue; // don't consider toolbars etc when placing ++mains_count; place_on2 = *it; if ((*it)->isOnCurrentDesktop()) { - if (place_on == NULL) + if (place_on == nullptr) place_on = *it; else { // two or more on current desktop -> center // That's the default at least. However, with maximizing placement // policy as the default, the dialog should be either maximized or // made as large as its maximum size and then placed centered. // So the nextPlacement argument allows chaining. In this case, nextPlacement // is Maximizing and it will call placeCentered(). place(c, area, Centered); return; } } } - if (place_on == NULL) { + if (place_on == nullptr) { // 'mains_count' is used because it doesn't include ignored mainwindows if (mains_count != 1) { place(c, area, Centered); return; } place_on = place_on2; // use the only window filtered together with 'mains_count' } if (place_on->isDesktop()) { place(c, area, Centered); return; } QRect geom = c->geometry(); geom.moveCenter(place_on->geometry().center()); c->move(geom.topLeft()); // get area again, because the mainwindow may be on different xinerama screen const QRect placementArea = workspace()->clientArea(PlacementArea, c); c->keepInArea(placementArea); // make sure it's kept inside workarea } void Placement::placeMaximizing(AbstractClient *c, const QRect &area, Policy nextPlacement) { Q_ASSERT(area.isValid()); if (nextPlacement == Unknown) nextPlacement = Smart; if (c->isMaximizable() && c->maxSize().width() >= area.width() && c->maxSize().height() >= area.height()) { if (workspace()->clientArea(MaximizeArea, c) == area) c->maximize(MaximizeFull); else { // if the geometry doesn't match default maximize area (xinerama case?), // it's probably better to use the given area c->setGeometry(area); } } else { c->resizeWithChecks(c->maxSize().boundedTo(area.size())); place(c, area, nextPlacement); } } void Placement::cascadeDesktop() { Workspace *ws = Workspace::self(); const int desktop = VirtualDesktopManager::self()->current(); reinitCascading(desktop); foreach (Toplevel *toplevel, ws->stackingOrder()) { auto client = qobject_cast(toplevel); if (!client || (!client->isOnCurrentDesktop()) || (client->isMinimized()) || (client->isOnAllDesktops()) || (!client->isMovable())) continue; const QRect placementArea = workspace()->clientArea(PlacementArea, client); placeCascaded(client, placementArea); } } void Placement::unclutterDesktop() { const auto &clients = Workspace::self()->allClientList(); for (int i = clients.size() - 1; i >= 0; i--) { auto client = clients.at(i); if ((!client->isOnCurrentDesktop()) || (client->isMinimized()) || (client->isOnAllDesktops()) || (!client->isMovable())) continue; const QRect placementArea = workspace()->clientArea(PlacementArea, client); placeSmart(client, placementArea); } } #endif Placement::Policy Placement::policyFromString(const QString& policy, bool no_special) { if (policy == QStringLiteral("NoPlacement")) return NoPlacement; else if (policy == QStringLiteral("Default") && !no_special) return Default; else if (policy == QStringLiteral("Random")) return Random; else if (policy == QStringLiteral("Cascade")) return Cascade; else if (policy == QStringLiteral("Centered")) return Centered; else if (policy == QStringLiteral("ZeroCornered")) return ZeroCornered; else if (policy == QStringLiteral("UnderMouse")) return UnderMouse; else if (policy == QStringLiteral("OnMainWindow") && !no_special) return OnMainWindow; else if (policy == QStringLiteral("Maximizing")) return Maximizing; else return Smart; } const char* Placement::policyToString(Policy policy) { const char* const policies[] = { "NoPlacement", "Default", "XXX should never see", "Random", "Smart", "Cascade", "Centered", "ZeroCornered", "UnderMouse", "OnMainWindow", "Maximizing" }; Q_ASSERT(policy < int(sizeof(policies) / sizeof(policies[ 0 ]))); return policies[ policy ]; } #ifndef KCMRULES // ******************** // Workspace // ******************** void AbstractClient::packTo(int left, int top) { workspace()->updateFocusMousePosition(Cursor::pos()); // may cause leave event; const int oldScreen = screen(); move(left, top); if (screen() != oldScreen) { workspace()->sendClientToScreen(this, screen()); // checks rule validity if (maximizeMode() != MaximizeRestore) checkWorkspacePosition(); } } /** * Moves active window left until in bumps into another window or workarea edge. */ void Workspace::slotWindowPackLeft() { if (active_client && active_client->isMovable()) active_client->packTo(packPositionLeft(active_client, active_client->geometry().left(), true), active_client->y()); } void Workspace::slotWindowPackRight() { if (active_client && active_client->isMovable()) active_client->packTo(packPositionRight(active_client, active_client->geometry().right(), true) - active_client->width() + 1, active_client->y()); } void Workspace::slotWindowPackUp() { if (active_client && active_client->isMovable()) active_client->packTo(active_client->x(), packPositionUp(active_client, active_client->geometry().top(), true)); } void Workspace::slotWindowPackDown() { if (active_client && active_client->isMovable()) active_client->packTo(active_client->x(), packPositionDown(active_client, active_client->geometry().bottom(), true) - active_client->height() + 1); } void Workspace::slotWindowGrowHorizontal() { if (active_client) active_client->growHorizontal(); } void AbstractClient::growHorizontal() { if (!isResizable() || isShade()) return; QRect geom = geometry(); geom.setRight(workspace()->packPositionRight(this, geom.right(), true)); QSize adjsize = adjustedSize(geom.size(), SizemodeFixedW); if (geometry().size() == adjsize && geom.size() != adjsize && resizeIncrements().width() > 1) { // take care of size increments int newright = workspace()->packPositionRight(this, geom.right() + resizeIncrements().width() - 1, true); // check that it hasn't grown outside of the area, due to size increments // TODO this may be wrong? if (workspace()->clientArea(MovementArea, QPoint((x() + newright) / 2, geometry().center().y()), desktop()).right() >= newright) geom.setRight(newright); } geom.setSize(adjustedSize(geom.size(), SizemodeFixedW)); geom.setSize(adjustedSize(geom.size(), SizemodeFixedH)); workspace()->updateFocusMousePosition(Cursor::pos()); // may cause leave event; setGeometry(geom); } void Workspace::slotWindowShrinkHorizontal() { if (active_client) active_client->shrinkHorizontal(); } void AbstractClient::shrinkHorizontal() { if (!isResizable() || isShade()) return; QRect geom = geometry(); geom.setRight(workspace()->packPositionLeft(this, geom.right(), false)); if (geom.width() <= 1) return; geom.setSize(adjustedSize(geom.size(), SizemodeFixedW)); if (geom.width() > 20) { workspace()->updateFocusMousePosition(Cursor::pos()); // may cause leave event; setGeometry(geom); } } void Workspace::slotWindowGrowVertical() { if (active_client) active_client->growVertical(); } void AbstractClient::growVertical() { if (!isResizable() || isShade()) return; QRect geom = geometry(); geom.setBottom(workspace()->packPositionDown(this, geom.bottom(), true)); QSize adjsize = adjustedSize(geom.size(), SizemodeFixedH); if (geometry().size() == adjsize && geom.size() != adjsize && resizeIncrements().height() > 1) { // take care of size increments int newbottom = workspace()->packPositionDown(this, geom.bottom() + resizeIncrements().height() - 1, true); // check that it hasn't grown outside of the area, due to size increments if (workspace()->clientArea(MovementArea, QPoint(geometry().center().x(), (y() + newbottom) / 2), desktop()).bottom() >= newbottom) geom.setBottom(newbottom); } geom.setSize(adjustedSize(geom.size(), SizemodeFixedH)); workspace()->updateFocusMousePosition(Cursor::pos()); // may cause leave event; setGeometry(geom); } void Workspace::slotWindowShrinkVertical() { if (active_client) active_client->shrinkVertical(); } void AbstractClient::shrinkVertical() { if (!isResizable() || isShade()) return; QRect geom = geometry(); geom.setBottom(workspace()->packPositionUp(this, geom.bottom(), false)); if (geom.height() <= 1) return; geom.setSize(adjustedSize(geom.size(), SizemodeFixedH)); if (geom.height() > 20) { workspace()->updateFocusMousePosition(Cursor::pos()); // may cause leave event; setGeometry(geom); } } void Workspace::quickTileWindow(QuickTileMode mode) { if (!active_client) { return; } active_client->setQuickTileMode(mode, true); } int Workspace::packPositionLeft(const AbstractClient* cl, int oldx, bool left_edge) const { int newx = clientArea(MaximizeArea, cl).left(); if (oldx <= newx) // try another Xinerama screen newx = clientArea(MaximizeArea, QPoint(cl->geometry().left() - 1, cl->geometry().center().y()), cl->desktop()).left(); if (cl->titlebarPosition() != AbstractClient::PositionLeft) { QRect geo = cl->geometry(); int rgt = newx - cl->clientPos().x(); geo.moveRight(rgt); if (screens()->intersecting(geo) < 2) newx = rgt; } if (oldx <= newx) return oldx; const int desktop = cl->desktop() == 0 || cl->isOnAllDesktops() ? VirtualDesktopManager::self()->current() : cl->desktop(); for (auto it = m_allClients.constBegin(), end = m_allClients.constEnd(); it != end; ++it) { if (isIrrelevant(*it, cl, desktop)) continue; int x = left_edge ? (*it)->geometry().right() + 1 : (*it)->geometry().left() - 1; if (x > newx && x < oldx && !(cl->geometry().top() > (*it)->geometry().bottom() // they overlap in Y direction || cl->geometry().bottom() < (*it)->geometry().top())) newx = x; } return newx; } int Workspace::packPositionRight(const AbstractClient* cl, int oldx, bool right_edge) const { int newx = clientArea(MaximizeArea, cl).right(); if (oldx >= newx) // try another Xinerama screen newx = clientArea(MaximizeArea, QPoint(cl->geometry().right() + 1, cl->geometry().center().y()), cl->desktop()).right(); if (cl->titlebarPosition() != AbstractClient::PositionRight) { QRect geo = cl->geometry(); int rgt = newx + cl->width() - (cl->clientSize().width() + cl->clientPos().x()); geo.moveRight(rgt); if (screens()->intersecting(geo) < 2) newx = rgt; } if (oldx >= newx) return oldx; const int desktop = cl->desktop() == 0 || cl->isOnAllDesktops() ? VirtualDesktopManager::self()->current() : cl->desktop(); for (auto it = m_allClients.constBegin(), end = m_allClients.constEnd(); it != end; ++it) { if (isIrrelevant(*it, cl, desktop)) continue; int x = right_edge ? (*it)->geometry().left() - 1 : (*it)->geometry().right() + 1; if (x < newx && x > oldx && !(cl->geometry().top() > (*it)->geometry().bottom() || cl->geometry().bottom() < (*it)->geometry().top())) newx = x; } return newx; } int Workspace::packPositionUp(const AbstractClient* cl, int oldy, bool top_edge) const { int newy = clientArea(MaximizeArea, cl).top(); if (oldy <= newy) // try another Xinerama screen newy = clientArea(MaximizeArea, QPoint(cl->geometry().center().x(), cl->geometry().top() - 1), cl->desktop()).top(); if (cl->titlebarPosition() != AbstractClient::PositionTop) { QRect geo = cl->geometry(); int top = newy - cl->clientPos().y(); geo.moveTop(top); if (screens()->intersecting(geo) < 2) newy = top; } if (oldy <= newy) return oldy; const int desktop = cl->desktop() == 0 || cl->isOnAllDesktops() ? VirtualDesktopManager::self()->current() : cl->desktop(); for (auto it = m_allClients.constBegin(), end = m_allClients.constEnd(); it != end; ++it) { if (isIrrelevant(*it, cl, desktop)) continue; int y = top_edge ? (*it)->geometry().bottom() + 1 : (*it)->geometry().top() - 1; if (y > newy && y < oldy && !(cl->geometry().left() > (*it)->geometry().right() // they overlap in X direction || cl->geometry().right() < (*it)->geometry().left())) newy = y; } return newy; } int Workspace::packPositionDown(const AbstractClient* cl, int oldy, bool bottom_edge) const { int newy = clientArea(MaximizeArea, cl).bottom(); if (oldy >= newy) // try another Xinerama screen newy = clientArea(MaximizeArea, QPoint(cl->geometry().center().x(), cl->geometry().bottom() + 1), cl->desktop()).bottom(); if (cl->titlebarPosition() != AbstractClient::PositionBottom) { QRect geo = cl->geometry(); int btm = newy + cl->height() - (cl->clientSize().height() + cl->clientPos().y()); geo.moveBottom(btm); if (screens()->intersecting(geo) < 2) newy = btm; } if (oldy >= newy) return oldy; const int desktop = cl->desktop() == 0 || cl->isOnAllDesktops() ? VirtualDesktopManager::self()->current() : cl->desktop(); for (auto it = m_allClients.constBegin(), end = m_allClients.constEnd(); it != end; ++it) { if (isIrrelevant(*it, cl, desktop)) continue; int y = bottom_edge ? (*it)->geometry().top() - 1 : (*it)->geometry().bottom() + 1; if (y < newy && y > oldy && !(cl->geometry().left() > (*it)->geometry().right() || cl->geometry().right() < (*it)->geometry().left())) newy = y; } return newy; } #endif } // namespace diff --git a/platformsupport/scenes/opengl/backend.cpp b/platformsupport/scenes/opengl/backend.cpp index 5e6c3e626..45b6fbd30 100644 --- a/platformsupport/scenes/opengl/backend.cpp +++ b/platformsupport/scenes/opengl/backend.cpp @@ -1,119 +1,119 @@ /******************************************************************** 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 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 "backend.h" #include #include #include "screens.h" #include namespace KWin { OpenGLBackend::OpenGLBackend() : m_syncsToVBlank(false) , m_blocksForRetrace(false) , m_directRendering(false) , m_haveBufferAge(false) , m_failed(false) { } OpenGLBackend::~OpenGLBackend() { } void OpenGLBackend::setFailed(const QString &reason) { qCWarning(KWIN_OPENGL) << "Creating the OpenGL rendering failed: " << reason; m_failed = true; } void OpenGLBackend::idle() { if (hasPendingFlush()) { effects->makeOpenGLContextCurrent(); present(); } } void OpenGLBackend::addToDamageHistory(const QRegion ®ion) { if (m_damageHistory.count() > 10) m_damageHistory.removeLast(); m_damageHistory.prepend(region); } QRegion OpenGLBackend::accumulatedDamageHistory(int bufferAge) const { QRegion region; // Note: An age of zero means the buffer contents are undefined if (bufferAge > 0 && bufferAge <= m_damageHistory.count()) { for (int i = 0; i < bufferAge - 1; i++) region |= m_damageHistory[i]; } else { const QSize &s = screens()->size(); region = QRegion(0, 0, s.width(), s.height()); } return region; } OverlayWindow* OpenGLBackend::overlayWindow() const { - return NULL; + return nullptr; } QRegion OpenGLBackend::prepareRenderingForScreen(int screenId) { // fallback to repaint complete screen return screens()->geometry(screenId); } void OpenGLBackend::endRenderingFrameForScreen(int screenId, const QRegion &damage, const QRegion &damagedRegion) { Q_UNUSED(screenId) Q_UNUSED(damage) Q_UNUSED(damagedRegion) } bool OpenGLBackend::perScreenRendering() const { return false; } void OpenGLBackend::copyPixels(const QRegion ®ion) { const int height = screens()->size().height(); for (const QRect &r : region) { const int x0 = r.x(); const int y0 = height - r.y() - r.height(); const int x1 = r.x() + r.width(); const int y1 = height - r.y(); glBlitFramebuffer(x0, y0, x1, y1, x0, y0, x1, y1, GL_COLOR_BUFFER_BIT, GL_NEAREST); } } } diff --git a/platformsupport/scenes/opengl/linux_dmabuf.cpp b/platformsupport/scenes/opengl/linux_dmabuf.cpp index c9dd2de33..56941e9b5 100644 --- a/platformsupport/scenes/opengl/linux_dmabuf.cpp +++ b/platformsupport/scenes/opengl/linux_dmabuf.cpp @@ -1,504 +1,504 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright © 2019 Roman Gilg Copyright © 2018 Fredrik Höglund 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 "linux_dmabuf.h" #include "drm_fourcc.h" #include "../../../wayland_server.h" #include #include #include namespace KWin { typedef EGLBoolean (*eglQueryDmaBufFormatsEXT_func) (EGLDisplay dpy, EGLint max_formats, EGLint *formats, EGLint *num_formats); typedef EGLBoolean (*eglQueryDmaBufModifiersEXT_func) (EGLDisplay dpy, EGLint format, EGLint max_modifiers, EGLuint64KHR *modifiers, EGLBoolean *external_only, EGLint *num_modifiers); eglQueryDmaBufFormatsEXT_func eglQueryDmaBufFormatsEXT = nullptr; eglQueryDmaBufModifiersEXT_func eglQueryDmaBufModifiersEXT = nullptr; #ifndef EGL_EXT_image_dma_buf_import #define EGL_LINUX_DMA_BUF_EXT 0x3270 #define EGL_LINUX_DRM_FOURCC_EXT 0x3271 #define EGL_DMA_BUF_PLANE0_FD_EXT 0x3272 #define EGL_DMA_BUF_PLANE0_OFFSET_EXT 0x3273 #define EGL_DMA_BUF_PLANE0_PITCH_EXT 0x3274 #define EGL_DMA_BUF_PLANE1_FD_EXT 0x3275 #define EGL_DMA_BUF_PLANE1_OFFSET_EXT 0x3276 #define EGL_DMA_BUF_PLANE1_PITCH_EXT 0x3277 #define EGL_DMA_BUF_PLANE2_FD_EXT 0x3278 #define EGL_DMA_BUF_PLANE2_OFFSET_EXT 0x3279 #define EGL_DMA_BUF_PLANE2_PITCH_EXT 0x327A #define EGL_YUV_COLOR_SPACE_HINT_EXT 0x327B #define EGL_SAMPLE_RANGE_HINT_EXT 0x327C #define EGL_YUV_CHROMA_HORIZONTAL_SITING_HINT_EXT 0x327D #define EGL_YUV_CHROMA_VERTICAL_SITING_HINT_EXT 0x327E #define EGL_ITU_REC601_EXT 0x327F #define EGL_ITU_REC709_EXT 0x3280 #define EGL_ITU_REC2020_EXT 0x3281 #define EGL_YUV_FULL_RANGE_EXT 0x3282 #define EGL_YUV_NARROW_RANGE_EXT 0x3283 #define EGL_YUV_CHROMA_SITING_0_EXT 0x3284 #define EGL_YUV_CHROMA_SITING_0_5_EXT 0x3285 #endif // EGL_EXT_image_dma_buf_import #ifndef EGL_EXT_image_dma_buf_import_modifiers #define EGL_DMA_BUF_PLANE3_FD_EXT 0x3440 #define EGL_DMA_BUF_PLANE3_OFFSET_EXT 0x3441 #define EGL_DMA_BUF_PLANE3_PITCH_EXT 0x3442 #define EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT 0x3443 #define EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT 0x3444 #define EGL_DMA_BUF_PLANE1_MODIFIER_LO_EXT 0x3445 #define EGL_DMA_BUF_PLANE1_MODIFIER_HI_EXT 0x3446 #define EGL_DMA_BUF_PLANE2_MODIFIER_LO_EXT 0x3447 #define EGL_DMA_BUF_PLANE2_MODIFIER_HI_EXT 0x3448 #define EGL_DMA_BUF_PLANE3_MODIFIER_LO_EXT 0x3449 #define EGL_DMA_BUF_PLANE3_MODIFIER_HI_EXT 0x344A #endif // EGL_EXT_image_dma_buf_import_modifiers struct YuvPlane { int widthDivisor; int heightDivisor; uint32_t format; int planeIndex; }; struct YuvFormat { uint32_t format; int inputPlanes; int outputPlanes; int textureType; struct YuvPlane planes[3]; }; YuvFormat yuvFormats[] = { { DRM_FORMAT_YUYV, 1, 2, EGL_TEXTURE_Y_XUXV_WL, { { 1, 1, DRM_FORMAT_GR88, 0 }, { 2, 1, DRM_FORMAT_ARGB8888, 0 } } }, { DRM_FORMAT_NV12, 2, 2, EGL_TEXTURE_Y_UV_WL, { { 1, 1, DRM_FORMAT_R8, 0 }, { 2, 2, DRM_FORMAT_GR88, 1 } } }, { DRM_FORMAT_YUV420, 3, 3, EGL_TEXTURE_Y_U_V_WL, { { 1, 1, DRM_FORMAT_R8, 0 }, { 2, 2, DRM_FORMAT_R8, 1 }, { 2, 2, DRM_FORMAT_R8, 2 } } }, { DRM_FORMAT_YUV444, 3, 3, EGL_TEXTURE_Y_U_V_WL, { { 1, 1, DRM_FORMAT_R8, 0 }, { 1, 1, DRM_FORMAT_R8, 1 }, { 1, 1, DRM_FORMAT_R8, 2 } } } }; DmabufBuffer::DmabufBuffer(EGLImage image, const QVector &planes, uint32_t format, const QSize &size, Flags flags, LinuxDmabuf *interfaceImpl) : DmabufBuffer(planes, format, size, flags, interfaceImpl) { m_importType = ImportType::Direct; addImage(image); } DmabufBuffer::DmabufBuffer(const QVector &planes, uint32_t format, const QSize &size, Flags flags, LinuxDmabuf *interfaceImpl) : KWayland::Server::LinuxDmabufUnstableV1Buffer(format, size) , m_planes(planes) , m_flags(flags) , m_interfaceImpl(interfaceImpl) { m_importType = ImportType::Conversion; } DmabufBuffer::~DmabufBuffer() { if (m_interfaceImpl) { m_interfaceImpl->removeBuffer(this); removeImages(); } // Close all open file descriptors for (int i = 0; i < m_planes.count(); i++) { if (m_planes[i].fd != -1) ::close(m_planes[i].fd); m_planes[i].fd = -1; } } void DmabufBuffer::addImage(EGLImage image) { m_images << image; } void DmabufBuffer::removeImages() { for (auto image : m_images) { eglDestroyImageKHR(m_interfaceImpl->m_backend->eglDisplay(), image); } m_images.clear(); m_interfaceImpl = nullptr; } using Plane = KWayland::Server::LinuxDmabufUnstableV1Interface::Plane; using Flags = KWayland::Server::LinuxDmabufUnstableV1Interface::Flags; EGLImage LinuxDmabuf::createImage(const QVector &planes, uint32_t format, const QSize &size) { const bool hasModifiers = eglQueryDmaBufModifiersEXT != nullptr && planes[0].modifier != DRM_FORMAT_MOD_INVALID; QVector attribs; attribs << EGL_WIDTH << size.width() << EGL_HEIGHT << size.height() << EGL_LINUX_DRM_FOURCC_EXT << EGLint(format) << EGL_DMA_BUF_PLANE0_FD_EXT << planes[0].fd << EGL_DMA_BUF_PLANE0_OFFSET_EXT << EGLint(planes[0].offset) << EGL_DMA_BUF_PLANE0_PITCH_EXT << EGLint(planes[0].stride); if (hasModifiers) { attribs << EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT << EGLint(planes[0].modifier & 0xffffffff) << EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT << EGLint(planes[0].modifier >> 32); } if (planes.count() > 1) { attribs << EGL_DMA_BUF_PLANE1_FD_EXT << planes[1].fd << EGL_DMA_BUF_PLANE1_OFFSET_EXT << EGLint(planes[1].offset) << EGL_DMA_BUF_PLANE1_PITCH_EXT << EGLint(planes[1].stride); if (hasModifiers) { attribs << EGL_DMA_BUF_PLANE1_MODIFIER_LO_EXT << EGLint(planes[1].modifier & 0xffffffff) << EGL_DMA_BUF_PLANE1_MODIFIER_HI_EXT << EGLint(planes[1].modifier >> 32); } } if (planes.count() > 2) { attribs << EGL_DMA_BUF_PLANE2_FD_EXT << planes[2].fd << EGL_DMA_BUF_PLANE2_OFFSET_EXT << EGLint(planes[2].offset) << EGL_DMA_BUF_PLANE2_PITCH_EXT << EGLint(planes[2].stride); if (hasModifiers) { attribs << EGL_DMA_BUF_PLANE2_MODIFIER_LO_EXT << EGLint(planes[2].modifier & 0xffffffff) << EGL_DMA_BUF_PLANE2_MODIFIER_HI_EXT << EGLint(planes[2].modifier >> 32); } } if (eglQueryDmaBufModifiersEXT != nullptr && planes.count() > 3) { attribs << EGL_DMA_BUF_PLANE3_FD_EXT << planes[3].fd << EGL_DMA_BUF_PLANE3_OFFSET_EXT << EGLint(planes[3].offset) << EGL_DMA_BUF_PLANE3_PITCH_EXT << EGLint(planes[3].stride); if (hasModifiers) { attribs << EGL_DMA_BUF_PLANE3_MODIFIER_LO_EXT << EGLint(planes[3].modifier & 0xffffffff) << EGL_DMA_BUF_PLANE3_MODIFIER_HI_EXT << EGLint(planes[3].modifier >> 32); } } attribs << EGL_NONE; EGLImage image = eglCreateImageKHR(m_backend->eglDisplay(), EGL_NO_CONTEXT, EGL_LINUX_DMA_BUF_EXT, (EGLClientBuffer) nullptr, attribs.data()); if (image == EGL_NO_IMAGE_KHR) { return nullptr; } return image; } KWayland::Server::LinuxDmabufUnstableV1Buffer* LinuxDmabuf::importBuffer(const QVector &planes, uint32_t format, const QSize &size, Flags flags) { Q_ASSERT(planes.count() > 0); // Try first to import as a single image if (auto *img = createImage(planes, format, size)) { return new DmabufBuffer(img, planes, format, size, flags, this); } // TODO: to enable this we must be able to store multiple textures per window pixmap // and when on window draw do yuv to rgb transformation per shader (see Weston) // // not a single image, try yuv import // return yuvImport(planes, format, size, flags); return nullptr; } KWayland::Server::LinuxDmabufUnstableV1Buffer* LinuxDmabuf::yuvImport(const QVector &planes, uint32_t format, const QSize &size, Flags flags) { YuvFormat yuvFormat; for (YuvFormat f : yuvFormats) { if (f.format == format) { yuvFormat = f; break; } } if (yuvFormat.format == 0) { return nullptr; } if (planes.count() != yuvFormat.inputPlanes) { return nullptr; } auto *buf = new DmabufBuffer(planes, format, size, flags, this); for (int i = 0; i < yuvFormat.outputPlanes; i++) { int planeIndex = yuvFormat.planes[i].planeIndex; Plane plane = { planes[planeIndex].fd, planes[planeIndex].offset, planes[planeIndex].stride, planes[planeIndex].modifier }; const auto planeFormat = yuvFormat.planes[i].format; const auto planeSize = QSize(size.width() / yuvFormat.planes[i].widthDivisor, size.height() / yuvFormat.planes[i].heightDivisor); auto *image = createImage(QVector(1, plane), planeFormat, planeSize); if (!image) { delete buf; return nullptr; } buf->addImage(image); } // TODO: add buf import properties return buf; } LinuxDmabuf* LinuxDmabuf::factory(AbstractEglBackend *backend) { if (!backend->hasExtension(QByteArrayLiteral("EGL_EXT_image_dma_buf_import"))) { return nullptr; } if (backend->hasExtension(QByteArrayLiteral("EGL_EXT_image_dma_buf_import_modifiers"))) { eglQueryDmaBufFormatsEXT = (eglQueryDmaBufFormatsEXT_func) eglGetProcAddress("eglQueryDmaBufFormatsEXT"); eglQueryDmaBufModifiersEXT = (eglQueryDmaBufModifiersEXT_func) eglGetProcAddress("eglQueryDmaBufModifiersEXT"); } if (eglQueryDmaBufFormatsEXT == nullptr) { return nullptr; } return new LinuxDmabuf(backend); } LinuxDmabuf::LinuxDmabuf(AbstractEglBackend *backend) : KWayland::Server::LinuxDmabufUnstableV1Interface::Impl() , m_backend(backend) { Q_ASSERT(waylandServer()); m_interface = waylandServer()->display()->createLinuxDmabufInterface(backend); setSupportedFormatsAndModifiers(); m_interface->setImpl(this); m_interface->create(); } LinuxDmabuf::~LinuxDmabuf() { for (auto *dmabuf : qAsConst(m_buffers)) { dmabuf->removeImages(); } } void LinuxDmabuf::removeBuffer(DmabufBuffer *buffer) { m_buffers.remove(buffer); } const uint32_t s_multiPlaneFormats[] = { DRM_FORMAT_XRGB8888_A8, DRM_FORMAT_XBGR8888_A8, DRM_FORMAT_RGBX8888_A8, DRM_FORMAT_BGRX8888_A8, DRM_FORMAT_RGB888_A8, DRM_FORMAT_BGR888_A8, DRM_FORMAT_RGB565_A8, DRM_FORMAT_BGR565_A8, DRM_FORMAT_NV12, DRM_FORMAT_NV21, DRM_FORMAT_NV16, DRM_FORMAT_NV61, DRM_FORMAT_NV24, DRM_FORMAT_NV42, DRM_FORMAT_YUV410, DRM_FORMAT_YVU410, DRM_FORMAT_YUV411, DRM_FORMAT_YVU411, DRM_FORMAT_YUV420, DRM_FORMAT_YVU420, DRM_FORMAT_YUV422, DRM_FORMAT_YVU422, DRM_FORMAT_YUV444, DRM_FORMAT_YVU444 }; void filterFormatsWithMultiplePlanes(QVector &formats) { QVector::iterator it = formats.begin(); while (it != formats.end()) { for (auto linuxFormat : s_multiPlaneFormats) { if (*it == linuxFormat) { qDebug() << "Filter multi-plane format" << *it; it = formats.erase(it); it--; break; } } it++; } } void LinuxDmabuf::setSupportedFormatsAndModifiers() { const EGLDisplay eglDisplay = m_backend->eglDisplay(); EGLint count = 0; - EGLBoolean success = eglQueryDmaBufFormatsEXT(eglDisplay, 0, NULL, &count); + EGLBoolean success = eglQueryDmaBufFormatsEXT(eglDisplay, 0, nullptr, &count); if (!success || count == 0) { return; } QVector formats(count); if (!eglQueryDmaBufFormatsEXT(eglDisplay, count, (EGLint *) formats.data(), &count)) { return; } filterFormatsWithMultiplePlanes(formats); QHash > set; for (auto format : qAsConst(formats)) { if (eglQueryDmaBufModifiersEXT != nullptr) { count = 0; - success = eglQueryDmaBufModifiersEXT(eglDisplay, format, 0, NULL, NULL, &count); + success = eglQueryDmaBufModifiersEXT(eglDisplay, format, 0, nullptr, nullptr, &count); if (success && count > 0) { QVector modifiers(count); if (eglQueryDmaBufModifiersEXT(eglDisplay, format, count, modifiers.data(), - NULL, &count)) { + nullptr, &count)) { QSet modifiersSet; for (auto mod : qAsConst(modifiers)) { modifiersSet.insert(mod); } set.insert(format, modifiersSet); continue; } } } set.insert(format, QSet()); } m_interface->setSupportedFormatsWithModifiers(set); } } diff --git a/plugins/idletime/poller.h b/plugins/idletime/poller.h index 62e6e88ae..7b51275f3 100644 --- a/plugins/idletime/poller.h +++ b/plugins/idletime/poller.h @@ -1,67 +1,67 @@ /******************************************************************** 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 POLLER_H #define POLLER_H #include #include namespace KWayland { namespace Client { class Seat; class Idle; class IdleTimeout; } } class Poller : public AbstractSystemPoller { Q_OBJECT Q_PLUGIN_METADATA(IID "org.kde.kidletime.AbstractSystemPoller" FILE "kwin.json") Q_INTERFACES(AbstractSystemPoller) public: - Poller(QObject *parent = 0); + Poller(QObject *parent = nullptr); ~Poller() override; bool isAvailable() override; bool setUpPoller() override; void unloadPoller() override; public Q_SLOTS: void addTimeout(int nextTimeout) override; void removeTimeout(int nextTimeout) override; QList timeouts() const override; int forcePollRequest() override; void catchIdleEvent() override; void stopCatchingIdleEvents() override; void simulateUserActivity() override; private: KWayland::Client::Seat *m_seat = nullptr; KWayland::Client::Idle *m_idle = nullptr; KWayland::Client::IdleTimeout *m_catchResumeTimeout = nullptr; QHash m_timeouts; }; #endif diff --git a/plugins/kglobalaccel/kglobalaccel_plugin.h b/plugins/kglobalaccel/kglobalaccel_plugin.h index 2b72b6d3e..ac594e6bd 100644 --- a/plugins/kglobalaccel/kglobalaccel_plugin.h +++ b/plugins/kglobalaccel/kglobalaccel_plugin.h @@ -1,48 +1,48 @@ /******************************************************************** 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 KGLOBALACCEL_PLUGIN_H #define KGLOBALACCEL_PLUGIN_H #include #include class KGlobalAccelImpl : public KGlobalAccelInterface { Q_OBJECT Q_PLUGIN_METADATA(IID "org.kde.kglobalaccel5.KGlobalAccelInterface" FILE "kwin.json") Q_INTERFACES(KGlobalAccelInterface) public: - KGlobalAccelImpl(QObject *parent = 0); + KGlobalAccelImpl(QObject *parent = nullptr); ~KGlobalAccelImpl() override; bool grabKey(int key, bool grab) override; void setEnabled(bool) override; public Q_SLOTS: bool checkKeyPressed(int keyQt); private: bool m_shuttingDown = false; QMetaObject::Connection m_inputDestroyedConnection; }; #endif diff --git a/plugins/platforms/wayland/wayland_backend.cpp b/plugins/platforms/wayland/wayland_backend.cpp index 0b10029d0..e0e3b6f39 100644 --- a/plugins/platforms/wayland/wayland_backend.cpp +++ b/plugins/platforms/wayland/wayland_backend.cpp @@ -1,851 +1,851 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright 2019 Roman Gilg Copyright 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 "wayland_backend.h" #if HAVE_WAYLAND_EGL #include "egl_wayland_backend.h" #endif #include "logging.h" #include "scene_qpainter_wayland_backend.h" #include "wayland_output.h" #include "composite.h" #include "cursor.h" #include "input.h" #include "main.h" #include "outputscreens.h" #include "pointer_input.h" #include "screens.h" #include "wayland_cursor_theme.h" #include "wayland_server.h" #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 namespace KWin { namespace Wayland { using namespace KWayland::Client; WaylandCursor::WaylandCursor(WaylandBackend *backend) : QObject(backend) , m_backend(backend) { resetSurface(); } void WaylandCursor::resetSurface() { delete m_surface; m_surface = backend()->compositor()->createSurface(this); } void WaylandCursor::init() { installImage(); } WaylandCursor::~WaylandCursor() { delete m_surface; } void WaylandCursor::installImage() { const QImage image = m_backend->softwareCursor(); if (image.isNull() || image.size().isEmpty()) { doInstallImage(nullptr, QSize()); return; } wl_buffer *imageBuffer = *(m_backend->shmPool()->createBuffer(image).data()); doInstallImage(imageBuffer, image.size()); } void WaylandCursor::doInstallImage(wl_buffer *image, const QSize &size) { auto *pointer = m_backend->seat()->pointer(); if (!pointer || !pointer->isValid()) { return; } pointer->setCursor(m_surface, image ? m_backend->softwareCursorHotspot() : QPoint()); drawSurface(image, size); } void WaylandCursor::drawSurface(wl_buffer *image, const QSize &size) { m_surface->attachBuffer(image); m_surface->damage(QRect(QPoint(0,0), size)); m_surface->commit(Surface::CommitFlag::None); m_backend->flush(); } WaylandSubSurfaceCursor::WaylandSubSurfaceCursor(WaylandBackend *backend) : WaylandCursor(backend) { } void WaylandSubSurfaceCursor::init() { if (auto *pointer = backend()->seat()->pointer()) { pointer->hideCursor(); } } WaylandSubSurfaceCursor::~WaylandSubSurfaceCursor() { delete m_subSurface; } void WaylandSubSurfaceCursor::changeOutput(WaylandOutput *output) { delete m_subSurface; m_subSurface = nullptr; m_output = output; if (!output) { return; } createSubSurface(); surface()->commit(); } void WaylandSubSurfaceCursor::createSubSurface() { if (m_subSurface) { return; } if (!m_output) { return; } resetSurface(); m_subSurface = backend()->subCompositor()->createSubSurface(surface(), m_output->surface(), this); m_subSurface->setMode(SubSurface::Mode::Desynchronized); } void WaylandSubSurfaceCursor::doInstallImage(wl_buffer *image, const QSize &size) { if (!image) { delete m_subSurface; m_subSurface = nullptr; return; } createSubSurface(); // cursor position might have changed due to different cursor hot spot move(input()->pointer()->pos()); drawSurface(image, size); } QPointF WaylandSubSurfaceCursor::absoluteToRelativePosition(const QPointF &position) { auto ret = position - m_output->geometry().topLeft() - backend()->softwareCursorHotspot(); return ret; } void WaylandSubSurfaceCursor::move(const QPointF &globalPosition) { auto *output = backend()->getOutputAt(globalPosition.toPoint()); if (!m_output || (output && m_output != output)) { changeOutput(output); if (!m_output) { // cursor might be off the grid return; } installImage(); return; } if (!m_subSurface) { return; } // place the sub-surface relative to the output it is on and factor in the hotspot const auto relativePosition = globalPosition.toPoint() - backend()->softwareCursorHotspot() - m_output->geometry().topLeft(); m_subSurface->setPosition(relativePosition); Compositor::self()->addRepaintFull(); } WaylandSeat::WaylandSeat(wl_seat *seat, WaylandBackend *backend) - : QObject(NULL) + : QObject(nullptr) , m_seat(new Seat(this)) - , m_pointer(NULL) - , m_keyboard(NULL) + , m_pointer(nullptr) + , m_keyboard(nullptr) , m_touch(nullptr) , m_enteredSerial(0) , m_backend(backend) { m_seat->setup(seat); connect(m_seat, &Seat::hasKeyboardChanged, this, [this](bool hasKeyboard) { if (hasKeyboard) { m_keyboard = m_seat->createKeyboard(this); connect(m_keyboard, &Keyboard::keyChanged, this, [this](quint32 key, Keyboard::KeyState state, quint32 time) { switch (state) { case Keyboard::KeyState::Pressed: if (key == KEY_RIGHTCTRL) { m_backend->togglePointerLock(); } m_backend->keyboardKeyPressed(key, time); break; case Keyboard::KeyState::Released: m_backend->keyboardKeyReleased(key, time); break; default: Q_UNREACHABLE(); } } ); connect(m_keyboard, &Keyboard::modifiersChanged, this, [this](quint32 depressed, quint32 latched, quint32 locked, quint32 group) { m_backend->keyboardModifiers(depressed, latched, locked, group); } ); connect(m_keyboard, &Keyboard::keymapChanged, this, [this](int fd, quint32 size) { m_backend->keymapChange(fd, size); } ); } else { destroyKeyboard(); } } ); connect(m_seat, &Seat::hasPointerChanged, this, [this](bool hasPointer) { if (hasPointer && !m_pointer) { m_pointer = m_seat->createPointer(this); setupPointerGestures(); connect(m_pointer, &Pointer::entered, this, [this](quint32 serial, const QPointF &relativeToSurface) { Q_UNUSED(relativeToSurface) m_enteredSerial = serial; } ); connect(m_pointer, &Pointer::motion, this, [this](const QPointF &relativeToSurface, quint32 time) { m_backend->pointerMotionRelativeToOutput(relativeToSurface, time); } ); connect(m_pointer, &Pointer::buttonStateChanged, this, [this](quint32 serial, quint32 time, quint32 button, Pointer::ButtonState state) { Q_UNUSED(serial) switch (state) { case Pointer::ButtonState::Pressed: m_backend->pointerButtonPressed(button, time); break; case Pointer::ButtonState::Released: m_backend->pointerButtonReleased(button, time); break; default: Q_UNREACHABLE(); } } ); // TODO: Send discreteDelta and source as well. connect(m_pointer, &Pointer::axisChanged, this, [this](quint32 time, Pointer::Axis axis, qreal delta) { switch (axis) { case Pointer::Axis::Horizontal: m_backend->pointerAxisHorizontal(delta, time); break; case Pointer::Axis::Vertical: m_backend->pointerAxisVertical(delta, time); break; default: Q_UNREACHABLE(); } } ); } else { destroyPointer(); } } ); connect(m_seat, &Seat::hasTouchChanged, [this] (bool hasTouch) { if (hasTouch && !m_touch) { m_touch = m_seat->createTouch(this); connect(m_touch, &Touch::sequenceCanceled, m_backend, &Platform::touchCancel); connect(m_touch, &Touch::frameEnded, m_backend, &Platform::touchFrame); connect(m_touch, &Touch::sequenceStarted, this, [this] (TouchPoint *tp) { m_backend->touchDown(tp->id(), tp->position(), tp->time()); } ); connect(m_touch, &Touch::pointAdded, this, [this] (TouchPoint *tp) { m_backend->touchDown(tp->id(), tp->position(), tp->time()); } ); connect(m_touch, &Touch::pointRemoved, this, [this] (TouchPoint *tp) { m_backend->touchUp(tp->id(), tp->time()); } ); connect(m_touch, &Touch::pointMoved, this, [this] (TouchPoint *tp) { m_backend->touchMotion(tp->id(), tp->position(), tp->time()); } ); } else { destroyTouch(); } } ); WaylandServer *server = waylandServer(); if (server) { using namespace KWayland::Server; SeatInterface *si = server->seat(); connect(m_seat, &Seat::hasKeyboardChanged, si, &SeatInterface::setHasKeyboard); connect(m_seat, &Seat::hasPointerChanged, si, &SeatInterface::setHasPointer); connect(m_seat, &Seat::hasTouchChanged, si, &SeatInterface::setHasTouch); connect(m_seat, &Seat::nameChanged, si, &SeatInterface::setName); } } void WaylandBackend::pointerMotionRelativeToOutput(const QPointF &position, quint32 time) { auto outputIt = std::find_if(m_outputs.begin(), m_outputs.end(), [this](WaylandOutput *wo) { return wo->surface() == m_seat->pointer()->enteredSurface(); }); Q_ASSERT(outputIt != m_outputs.end()); const QPointF outputPosition = (*outputIt)->geometry().topLeft() + position; Platform::pointerMotion(outputPosition, time); } WaylandSeat::~WaylandSeat() { destroyPointer(); destroyKeyboard(); destroyTouch(); } void WaylandSeat::destroyPointer() { delete m_pinchGesture; m_pinchGesture = nullptr; delete m_swipeGesture; m_swipeGesture = nullptr; delete m_pointer; m_pointer = nullptr; } void WaylandSeat::destroyKeyboard() { delete m_keyboard; m_keyboard = nullptr; } void WaylandSeat::destroyTouch() { delete m_touch; m_touch = nullptr; } void WaylandSeat::setupPointerGestures() { if (!m_pointer || !m_gesturesInterface) { return; } if (m_pinchGesture || m_swipeGesture) { return; } m_pinchGesture = m_gesturesInterface->createPinchGesture(m_pointer, this); m_swipeGesture = m_gesturesInterface->createSwipeGesture(m_pointer, this); connect(m_pinchGesture, &PointerPinchGesture::started, m_backend, [this] (quint32 serial, quint32 time) { Q_UNUSED(serial); m_backend->processPinchGestureBegin(m_pinchGesture->fingerCount(), time); } ); connect(m_pinchGesture, &PointerPinchGesture::updated, m_backend, [this] (const QSizeF &delta, qreal scale, qreal rotation, quint32 time) { m_backend->processPinchGestureUpdate(scale, rotation, delta, time); } ); connect(m_pinchGesture, &PointerPinchGesture::ended, m_backend, [this] (quint32 serial, quint32 time) { Q_UNUSED(serial) m_backend->processPinchGestureEnd(time); } ); connect(m_pinchGesture, &PointerPinchGesture::cancelled, m_backend, [this] (quint32 serial, quint32 time) { Q_UNUSED(serial) m_backend->processPinchGestureCancelled(time); } ); connect(m_swipeGesture, &PointerSwipeGesture::started, m_backend, [this] (quint32 serial, quint32 time) { Q_UNUSED(serial) m_backend->processSwipeGestureBegin(m_swipeGesture->fingerCount(), time); } ); connect(m_swipeGesture, &PointerSwipeGesture::updated, m_backend, &Platform::processSwipeGestureUpdate); connect(m_swipeGesture, &PointerSwipeGesture::ended, m_backend, [this] (quint32 serial, quint32 time) { Q_UNUSED(serial) m_backend->processSwipeGestureEnd(time); } ); connect(m_swipeGesture, &PointerSwipeGesture::cancelled, m_backend, [this] (quint32 serial, quint32 time) { Q_UNUSED(serial) m_backend->processSwipeGestureCancelled(time); } ); } WaylandBackend::WaylandBackend(QObject *parent) : Platform(parent) , m_display(nullptr) , m_eventQueue(new EventQueue(this)) , m_registry(new Registry(this)) , m_compositor(new KWayland::Client::Compositor(this)) , m_subCompositor(new KWayland::Client::SubCompositor(this)) , m_shell(new Shell(this)) , m_shm(new ShmPool(this)) , m_connectionThreadObject(new ConnectionThread(nullptr)) , m_connectionThread(nullptr) { connect(this, &WaylandBackend::connectionFailed, this, &WaylandBackend::initFailed); } WaylandBackend::~WaylandBackend() { if (m_pointerConstraints) { m_pointerConstraints->release(); } delete m_waylandCursor; qDeleteAll(m_outputs); if (m_xdgShell) { m_xdgShell->release(); } m_shell->release(); m_subCompositor->release(); m_compositor->release(); m_registry->release(); delete m_seat; m_shm->release(); m_eventQueue->release(); m_connectionThreadObject->deleteLater(); m_connectionThread->quit(); m_connectionThread->wait(); qCDebug(KWIN_WAYLAND_BACKEND) << "Destroyed Wayland display"; } void WaylandBackend::init() { connect(m_registry, &Registry::compositorAnnounced, this, [this](quint32 name) { m_compositor->setup(m_registry->bindCompositor(name, 1)); } ); connect(m_registry, &Registry::subCompositorAnnounced, this, [this](quint32 name) { m_subCompositor->setup(m_registry->bindSubCompositor(name, 1)); } ); connect(m_registry, &Registry::shellAnnounced, this, [this](quint32 name) { m_shell->setup(m_registry->bindShell(name, 1)); } ); connect(m_registry, &Registry::seatAnnounced, this, [this](quint32 name) { if (Application::usesLibinput()) { return; } m_seat = new WaylandSeat(m_registry->bindSeat(name, 2), this); } ); connect(m_registry, &Registry::shmAnnounced, this, [this](quint32 name) { m_shm->setup(m_registry->bindShm(name, 1)); } ); connect(m_registry, &Registry::relativePointerManagerUnstableV1Announced, this, [this](quint32 name, quint32 version) { if (m_relativePointerManager) { return; } m_relativePointerManager = m_registry->createRelativePointerManager(name, version, this); if (m_pointerConstraints) { emit pointerLockSupportedChanged(); } } ); connect(m_registry, &Registry::pointerConstraintsUnstableV1Announced, this, [this](quint32 name, quint32 version) { if (m_pointerConstraints) { return; } m_pointerConstraints = m_registry->createPointerConstraints(name, version, this); if (m_relativePointerManager) { emit pointerLockSupportedChanged(); } } ); connect(m_registry, &Registry::interfacesAnnounced, this, &WaylandBackend::createOutputs); connect(m_registry, &Registry::interfacesAnnounced, this, [this] { if (!m_seat) { return; } const auto gi = m_registry->interface(Registry::Interface::PointerGesturesUnstableV1); if (gi.name == 0) { return; } auto gesturesInterface = m_registry->createPointerGestures(gi.name, gi.version, m_seat); m_seat->installGesturesInterface(gesturesInterface); m_waylandCursor = new WaylandCursor(this); } ); if (!deviceIdentifier().isEmpty()) { m_connectionThreadObject->setSocketName(deviceIdentifier()); } connect(this, &WaylandBackend::cursorChanged, this, [this] { if (!m_seat) { return; } m_waylandCursor->installImage(); markCursorAsRendered(); } ); connect(this, &WaylandBackend::pointerLockChanged, this, [this](bool locked) { delete m_waylandCursor; if (locked) { Q_ASSERT(!m_relativePointer); m_waylandCursor = new WaylandSubSurfaceCursor(this); m_waylandCursor->move(input()->pointer()->pos()); m_relativePointer = m_relativePointerManager->createRelativePointer(m_seat->pointer(), this); if (!m_relativePointer->isValid()) { return; } connect(m_relativePointer, &RelativePointer::relativeMotion, this, &WaylandBackend::relativeMotionHandler); } else { delete m_relativePointer; m_relativePointer = nullptr; m_waylandCursor = new WaylandCursor(this); } m_waylandCursor->init(); }); initConnection(); } void WaylandBackend::relativeMotionHandler(const QSizeF &delta, const QSizeF &deltaNonAccelerated, quint64 timestamp) { Q_UNUSED(deltaNonAccelerated) Q_ASSERT(m_waylandCursor); const auto oldGlobalPos = input()->pointer()->pos(); const QPointF newPos = oldGlobalPos + QPointF(delta.width(), delta.height()); m_waylandCursor->move(newPos); Platform::pointerMotion(newPos, timestamp); } void WaylandBackend::initConnection() { connect(m_connectionThreadObject, &ConnectionThread::connected, this, [this]() { // create the event queue for the main gui thread m_display = m_connectionThreadObject->display(); m_eventQueue->setup(m_connectionThreadObject); m_registry->setEventQueue(m_eventQueue); // setup registry m_registry->create(m_display); m_registry->setup(); }, Qt::QueuedConnection); connect(m_connectionThreadObject, &ConnectionThread::connectionDied, this, [this]() { setReady(false); emit systemCompositorDied(); delete m_seat; m_shm->destroy(); qDeleteAll(m_outputs); m_outputs.clear(); if (m_shell) { m_shell->destroy(); } if (m_xdgShell) { m_xdgShell->destroy(); } m_subCompositor->destroy(); m_compositor->destroy(); m_registry->destroy(); m_eventQueue->destroy(); if (m_display) { m_display = nullptr; } }, Qt::QueuedConnection); connect(m_connectionThreadObject, &ConnectionThread::failed, this, &WaylandBackend::connectionFailed, Qt::QueuedConnection); m_connectionThread = new QThread(this); m_connectionThreadObject->moveToThread(m_connectionThread); m_connectionThread->start(); m_connectionThreadObject->initConnection(); } void WaylandBackend::updateScreenSize(WaylandOutput *output) { auto it = std::find(m_outputs.begin(), m_outputs.end(), output); int nextLogicalPosition = output->geometry().topRight().x(); while (++it != m_outputs.end()) { const QRect geo = (*it)->geometry(); (*it)->setGeometry(QPoint(nextLogicalPosition, 0), geo.size()); nextLogicalPosition = geo.topRight().x(); } } void WaylandBackend::createOutputs() { using namespace KWayland::Client; const auto ssdManagerIface = m_registry->interface(Registry::Interface::ServerSideDecorationManager); ServerSideDecorationManager *ssdManager = ssdManagerIface.name == 0 ? nullptr : m_registry->createServerSideDecorationManager(ssdManagerIface.name, ssdManagerIface.version, this); const auto xdgIface = m_registry->interface(Registry::Interface::XdgShellUnstableV6); if (xdgIface.name != 0) { m_xdgShell = m_registry->createXdgShell(xdgIface.name, xdgIface.version, this); } // we need to multiply the initial window size with the scale in order to // create an output window of this size in the end const int pixelWidth = initialWindowSize().width() * initialOutputScale() + 0.5; const int pixelHeight = initialWindowSize().height() * initialOutputScale() + 0.5; const int logicalWidth = initialWindowSize().width(); int logicalWidthSum = 0; for (int i = 0; i < initialOutputCount(); i++) { auto surface = m_compositor->createSurface(this); if (!surface || !surface->isValid()) { qCCritical(KWIN_WAYLAND_BACKEND) << "Creating Wayland Surface failed"; return; } if (ssdManager) { auto decoration = ssdManager->create(surface, this); connect(decoration, &ServerSideDecoration::modeChanged, this, [this, decoration] { if (decoration->mode() != ServerSideDecoration::Mode::Server) { decoration->requestMode(ServerSideDecoration::Mode::Server); } } ); } WaylandOutput *waylandOutput = nullptr; if (m_xdgShell && m_xdgShell->isValid()) { waylandOutput = new XdgShellOutput(surface, m_xdgShell, this, i+1); } else if (m_shell->isValid()) { waylandOutput = new ShellOutput(surface, m_shell, this); } if (!waylandOutput) { qCCritical(KWIN_WAYLAND_BACKEND) << "Binding to all shell interfaces failed for output" << i; return; } waylandOutput->init(QPoint(logicalWidthSum, 0), QSize(pixelWidth, pixelHeight)); connect(waylandOutput, &WaylandOutput::sizeChanged, this, [this, waylandOutput](const QSize &size) { Q_UNUSED(size) updateScreenSize(waylandOutput); Compositor::self()->addRepaintFull(); }); connect(waylandOutput, &WaylandOutput::frameRendered, this, &WaylandBackend::checkBufferSwap); logicalWidthSum += logicalWidth; m_outputs << waylandOutput; } setReady(true); emit screensQueried(); } Screens *WaylandBackend::createScreens(QObject *parent) { return new OutputScreens(this, parent); } OpenGLBackend *WaylandBackend::createOpenGLBackend() { #if HAVE_WAYLAND_EGL return new EglWaylandBackend(this); #else return nullptr; #endif } QPainterBackend *WaylandBackend::createQPainterBackend() { return new WaylandQPainterBackend(this); } void WaylandBackend::checkBufferSwap() { const bool allRendered = std::all_of(m_outputs.begin(), m_outputs.end(), [](WaylandOutput *o) { return o->rendered(); }); if (!allRendered) { // need to wait more // TODO: what if one does not need to be rendered (no damage)? return; } for (auto *output : m_outputs) { if (!output->rendered()) { return; } } Compositor::self()->bufferSwapComplete(); for (auto *output : m_outputs) { output->resetRendered(); } } void WaylandBackend::flush() { if (m_connectionThreadObject) { m_connectionThreadObject->flush(); } } WaylandOutput* WaylandBackend::getOutputAt(const QPointF globalPosition) { const auto pos = globalPosition.toPoint(); auto checkPosition = [pos](WaylandOutput *output) { return output->geometry().contains(pos); }; auto it = std::find_if(m_outputs.begin(), m_outputs.end(), checkPosition); return it == m_outputs.end() ? nullptr : *it; } bool WaylandBackend::supportsPointerLock() { return m_pointerConstraints && m_relativePointerManager; } void WaylandBackend::togglePointerLock() { if (!m_pointerConstraints) { return; } if (!m_relativePointerManager) { return; } if (!m_seat) { return; } auto pointer = m_seat->pointer(); if (!pointer) { return; } if (m_outputs.isEmpty()) { return; } for (auto output : m_outputs) { output->lockPointer(m_seat->pointer(), !m_pointerLockRequested); } m_pointerLockRequested = !m_pointerLockRequested; flush(); } bool WaylandBackend::pointerIsLocked() { for (auto *output : m_outputs) { if (output->pointerIsLocked()) { return true; } } return false; } QVector WaylandBackend::supportedCompositors() const { if (selectedCompositor() != NoCompositing) { return {selectedCompositor()}; } #if HAVE_WAYLAND_EGL return QVector{OpenGLCompositing, QPainterCompositing}; #else return QVector{QPainterCompositing}; #endif } Outputs WaylandBackend::outputs() const { return m_outputs; } Outputs WaylandBackend::enabledOutputs() const { // all outputs are enabled return m_outputs; } } } // KWin diff --git a/plugins/platforms/x11/standalone/glxbackend.cpp b/plugins/platforms/x11/standalone/glxbackend.cpp index d2bbc666a..1ae6ccd9a 100644 --- a/plugins/platforms/x11/standalone/glxbackend.cpp +++ b/plugins/platforms/x11/standalone/glxbackend.cpp @@ -1,948 +1,948 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2006 Lubos Lunak Copyright (C) 2012 Martin Gräßlin Based on glcompmgr code by Felix Bellaby. Using code from Compiz and Beryl. 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 "glxbackend.h" #include "logging.h" #include "glx_context_attribute_builder.h" // kwin #include "options.h" #include "overlaywindow.h" #include "composite.h" #include "platform.h" #include "scene.h" #include "screens.h" #include "xcbutils.h" #include "texture.h" // kwin libs #include #include #include // Qt #include #include #include // system #include #include #include #if HAVE_DL_LIBRARY #include #endif #ifndef XCB_GLX_BUFFER_SWAP_COMPLETE #define XCB_GLX_BUFFER_SWAP_COMPLETE 1 typedef struct xcb_glx_buffer_swap_complete_event_t { uint8_t response_type; /**< */ uint8_t pad0; /**< */ uint16_t sequence; /**< */ uint16_t event_type; /**< */ uint8_t pad1[2]; /**< */ xcb_glx_drawable_t drawable; /**< */ uint32_t ust_hi; /**< */ uint32_t ust_lo; /**< */ uint32_t msc_hi; /**< */ uint32_t msc_lo; /**< */ uint32_t sbc; /**< */ } xcb_glx_buffer_swap_complete_event_t; #endif #include #include namespace KWin { SwapEventFilter::SwapEventFilter(xcb_drawable_t drawable, xcb_glx_drawable_t glxDrawable) : X11EventFilter(Xcb::Extensions::self()->glxEventBase() + XCB_GLX_BUFFER_SWAP_COMPLETE), m_drawable(drawable), m_glxDrawable(glxDrawable) { } bool SwapEventFilter::event(xcb_generic_event_t *event) { xcb_glx_buffer_swap_complete_event_t *ev = reinterpret_cast(event); // The drawable field is the X drawable when the event was synthesized // by a WireToEvent handler, and the GLX drawable when the event was // received over the wire if (ev->drawable == m_drawable || ev->drawable == m_glxDrawable) { Compositor::self()->bufferSwapComplete(); return true; } return false; } // ----------------------------------------------------------------------- GlxBackend::GlxBackend(Display *display) : OpenGLBackend() , m_overlayWindow(kwinApp()->platform()->createOverlayWindow()) , window(None) - , fbconfig(NULL) + , fbconfig(nullptr) , glxWindow(None) , ctx(nullptr) , m_bufferAge(0) , haveSwapInterval(false) , m_x11Display(display) { // Ensures calls to glXSwapBuffers will always block until the next // retrace when using the proprietary NVIDIA driver. This must be // set before libGL.so is loaded. setenv("__GL_MaxFramesAllowed", "1", true); // Force initialization of GLX integration in the Qt's xcb backend // to make it call XESetWireToEvent callbacks, which is required // by Mesa when using DRI2. QOpenGLContext::supportsThreadedOpenGL(); } static bool gs_tripleBufferUndetected = true; static bool gs_tripleBufferNeedsDetection = false; GlxBackend::~GlxBackend() { if (isFailed()) { m_overlayWindow->destroy(); } // TODO: cleanup in error case // do cleanup after initBuffer() cleanupGL(); doneCurrent(); gs_tripleBufferUndetected = true; gs_tripleBufferNeedsDetection = false; if (ctx) glXDestroyContext(display(), ctx); if (glxWindow) glXDestroyWindow(display(), glxWindow); if (window) XDestroyWindow(display(), window); qDeleteAll(m_fbconfigHash); m_fbconfigHash.clear(); overlayWindow()->destroy(); delete m_overlayWindow; } typedef void (*glXFuncPtr)(); static glXFuncPtr getProcAddress(const char* name) { glXFuncPtr ret = nullptr; #if HAVE_EPOXY_GLX ret = glXGetProcAddress((const GLubyte*) name); #endif #if HAVE_DL_LIBRARY if (ret == nullptr) ret = (glXFuncPtr) dlsym(RTLD_DEFAULT, name); #endif return ret; } glXSwapIntervalMESA_func glXSwapIntervalMESA; void GlxBackend::init() { // Require at least GLX 1.3 if (!checkVersion()) { setFailed(QStringLiteral("Requires at least GLX 1.3")); return; } initExtensions(); // resolve glXSwapIntervalMESA if available if (hasExtension(QByteArrayLiteral("GLX_MESA_swap_control"))) { glXSwapIntervalMESA = (glXSwapIntervalMESA_func) getProcAddress("glXSwapIntervalMESA"); } else { glXSwapIntervalMESA = nullptr; } initVisualDepthHashTable(); if (!initBuffer()) { setFailed(QStringLiteral("Could not initialize the buffer")); return; } if (!initRenderingContext()) { setFailed(QStringLiteral("Could not initialize rendering context")); return; } // Initialize OpenGL GLPlatform *glPlatform = GLPlatform::instance(); glPlatform->detect(GlxPlatformInterface); options->setGlPreferBufferSwap(options->glPreferBufferSwap()); // resolve autosetting if (options->glPreferBufferSwap() == Options::AutoSwapStrategy) options->setGlPreferBufferSwap('e'); // for unknown drivers - should not happen glPlatform->printResults(); initGL(&getProcAddress); // Check whether certain features are supported m_haveMESACopySubBuffer = hasExtension(QByteArrayLiteral("GLX_MESA_copy_sub_buffer")); m_haveMESASwapControl = hasExtension(QByteArrayLiteral("GLX_MESA_swap_control")); m_haveEXTSwapControl = hasExtension(QByteArrayLiteral("GLX_EXT_swap_control")); m_haveSGISwapControl = hasExtension(QByteArrayLiteral("GLX_SGI_swap_control")); // only enable Intel swap event if env variable is set, see BUG 342582 m_haveINTELSwapEvent = hasExtension(QByteArrayLiteral("GLX_INTEL_swap_event")) && qgetenv("KWIN_USE_INTEL_SWAP_EVENT") == QByteArrayLiteral("1"); if (m_haveINTELSwapEvent) { m_swapEventFilter = std::make_unique(window, glxWindow); glXSelectEvent(display(), glxWindow, GLX_BUFFER_SWAP_COMPLETE_INTEL_MASK); } haveSwapInterval = m_haveMESASwapControl || m_haveEXTSwapControl || m_haveSGISwapControl; setSupportsBufferAge(false); if (hasExtension(QByteArrayLiteral("GLX_EXT_buffer_age"))) { const QByteArray useBufferAge = qgetenv("KWIN_USE_BUFFER_AGE"); if (useBufferAge != "0") setSupportsBufferAge(true); } setSyncsToVBlank(false); setBlocksForRetrace(false); haveWaitSync = false; gs_tripleBufferNeedsDetection = false; m_swapProfiler.init(); const bool wantSync = options->glPreferBufferSwap() != Options::NoSwapEncourage; if (wantSync && glXIsDirect(display(), ctx)) { if (haveSwapInterval) { // glXSwapInterval is preferred being more reliable setSwapInterval(1); setSyncsToVBlank(true); const QByteArray tripleBuffer = qgetenv("KWIN_TRIPLE_BUFFER"); if (!tripleBuffer.isEmpty()) { setBlocksForRetrace(qstrcmp(tripleBuffer, "0") == 0); gs_tripleBufferUndetected = false; } gs_tripleBufferNeedsDetection = gs_tripleBufferUndetected; } else if (hasExtension(QByteArrayLiteral("GLX_SGI_video_sync"))) { unsigned int sync; if (glXGetVideoSyncSGI(&sync) == 0 && glXWaitVideoSyncSGI(1, 0, &sync) == 0) { setSyncsToVBlank(true); setBlocksForRetrace(true); haveWaitSync = true; } else qCWarning(KWIN_X11STANDALONE) << "NO VSYNC! glXSwapInterval is not supported, glXWaitVideoSync is supported but broken"; } else qCWarning(KWIN_X11STANDALONE) << "NO VSYNC! neither glSwapInterval nor glXWaitVideoSync are supported"; } else { // disable v-sync (if possible) setSwapInterval(0); } if (glPlatform->isVirtualBox()) { // VirtualBox does not support glxQueryDrawable // this should actually be in kwinglutils_funcs, but QueryDrawable seems not to be provided by an extension // and the GLPlatform has not been initialized at the moment when initGLX() is called. - glXQueryDrawable = NULL; + glXQueryDrawable = nullptr; } setIsDirectRendering(bool(glXIsDirect(display(), ctx))); qCDebug(KWIN_X11STANDALONE) << "Direct rendering:" << isDirectRendering(); } bool GlxBackend::checkVersion() { int major, minor; glXQueryVersion(display(), &major, &minor); return kVersionNumber(major, minor) >= kVersionNumber(1, 3); } void GlxBackend::initExtensions() { const QByteArray string = (const char *) glXQueryExtensionsString(display(), QX11Info::appScreen()); setExtensions(string.split(' ')); } bool GlxBackend::initRenderingContext() { const bool direct = true; // Use glXCreateContextAttribsARB() when it's available if (hasExtension(QByteArrayLiteral("GLX_ARB_create_context"))) { const bool have_robustness = hasExtension(QByteArrayLiteral("GLX_ARB_create_context_robustness")); const bool haveVideoMemoryPurge = hasExtension(QByteArrayLiteral("GLX_NV_robustness_video_memory_purge")); std::vector candidates; if (options->glCoreProfile()) { if (have_robustness) { if (haveVideoMemoryPurge) { GlxContextAttributeBuilder purgeMemoryCore; purgeMemoryCore.setVersion(3, 1); purgeMemoryCore.setRobust(true); purgeMemoryCore.setResetOnVideoMemoryPurge(true); candidates.emplace_back(std::move(purgeMemoryCore)); } GlxContextAttributeBuilder robustCore; robustCore.setVersion(3, 1); robustCore.setRobust(true); candidates.emplace_back(std::move(robustCore)); } GlxContextAttributeBuilder core; core.setVersion(3, 1); candidates.emplace_back(std::move(core)); } else { if (have_robustness) { if (haveVideoMemoryPurge) { GlxContextAttributeBuilder purgeMemoryLegacy; purgeMemoryLegacy.setRobust(true); purgeMemoryLegacy.setResetOnVideoMemoryPurge(true); candidates.emplace_back(std::move(purgeMemoryLegacy)); } GlxContextAttributeBuilder robustLegacy; robustLegacy.setRobust(true); candidates.emplace_back(std::move(robustLegacy)); } GlxContextAttributeBuilder legacy; legacy.setVersion(2, 1); candidates.emplace_back(std::move(legacy)); } for (auto it = candidates.begin(); it != candidates.end(); it++) { const auto attribs = it->build(); - ctx = glXCreateContextAttribsARB(display(), fbconfig, 0, true, attribs.data()); + ctx = glXCreateContextAttribsARB(display(), fbconfig, nullptr, true, attribs.data()); if (ctx) { qCDebug(KWIN_X11STANDALONE) << "Created GLX context with attributes:" << &(*it); break; } } } if (!ctx) - ctx = glXCreateNewContext(display(), fbconfig, GLX_RGBA_TYPE, NULL, direct); + ctx = glXCreateNewContext(display(), fbconfig, GLX_RGBA_TYPE, nullptr, direct); if (!ctx) { qCDebug(KWIN_X11STANDALONE) << "Failed to create an OpenGL context."; return false; } if (!glXMakeCurrent(display(), glxWindow, ctx)) { qCDebug(KWIN_X11STANDALONE) << "Failed to make the OpenGL context current."; glXDestroyContext(display(), ctx); - ctx = 0; + ctx = nullptr; return false; } return true; } bool GlxBackend::initBuffer() { if (!initFbConfig()) return false; if (overlayWindow()->create()) { xcb_connection_t * const c = connection(); // Try to create double-buffered window in the overlay xcb_visualid_t visual; glXGetFBConfigAttrib(display(), fbconfig, GLX_VISUAL_ID, (int *) &visual); if (!visual) { qCCritical(KWIN_X11STANDALONE) << "The GLXFBConfig does not have an associated X visual"; return false; } xcb_colormap_t colormap = xcb_generate_id(c); xcb_create_colormap(c, false, colormap, rootWindow(), visual); const QSize size = screens()->size(); window = xcb_generate_id(c); xcb_create_window(c, visualDepth(visual), window, overlayWindow()->window(), 0, 0, size.width(), size.height(), 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, visual, XCB_CW_COLORMAP, &colormap); - glxWindow = glXCreateWindow(display(), fbconfig, window, NULL); + glxWindow = glXCreateWindow(display(), fbconfig, window, nullptr); overlayWindow()->setup(window); } else { qCCritical(KWIN_X11STANDALONE) << "Failed to create overlay window"; return false; } return true; } bool GlxBackend::initFbConfig() { const int attribs[] = { GLX_RENDER_TYPE, GLX_RGBA_BIT, GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT, GLX_RED_SIZE, 1, GLX_GREEN_SIZE, 1, GLX_BLUE_SIZE, 1, GLX_ALPHA_SIZE, 0, GLX_DEPTH_SIZE, 0, GLX_STENCIL_SIZE, 0, GLX_CONFIG_CAVEAT, GLX_NONE, GLX_DOUBLEBUFFER, true, 0 }; const int attribs_srgb[] = { GLX_RENDER_TYPE, GLX_RGBA_BIT, GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT, GLX_RED_SIZE, 1, GLX_GREEN_SIZE, 1, GLX_BLUE_SIZE, 1, GLX_ALPHA_SIZE, 0, GLX_DEPTH_SIZE, 0, GLX_STENCIL_SIZE, 0, GLX_CONFIG_CAVEAT, GLX_NONE, GLX_DOUBLEBUFFER, true, GLX_FRAMEBUFFER_SRGB_CAPABLE_ARB, true, 0 }; // Try to find a double buffered sRGB capable configuration int count = 0; GLXFBConfig *configs = glXChooseFBConfig(display(), DefaultScreen(display()), attribs_srgb, &count); if (count == 0) { // Try to find a double buffered non-sRGB capable configuration configs = glXChooseFBConfig(display(), DefaultScreen(display()), attribs, &count); } struct FBConfig { GLXFBConfig config; int depth; int stencil; }; std::deque candidates; for (int i = 0; i < count; i++) { int depth, stencil; glXGetFBConfigAttrib(display(), configs[i], GLX_DEPTH_SIZE, &depth); glXGetFBConfigAttrib(display(), configs[i], GLX_STENCIL_SIZE, &stencil); candidates.emplace_back(FBConfig{configs[i], depth, stencil}); } if (count > 0) XFree(configs); std::stable_sort(candidates.begin(), candidates.end(), [](const FBConfig &left, const FBConfig &right) { if (left.depth < right.depth) return true; if (left.stencil < right.stencil) return true; return false; }); if (candidates.size() > 0) { fbconfig = candidates.front().config; int fbconfig_id, visual_id, red, green, blue, alpha, depth, stencil, srgb; glXGetFBConfigAttrib(display(), fbconfig, GLX_FBCONFIG_ID, &fbconfig_id); glXGetFBConfigAttrib(display(), fbconfig, GLX_VISUAL_ID, &visual_id); glXGetFBConfigAttrib(display(), fbconfig, GLX_RED_SIZE, &red); glXGetFBConfigAttrib(display(), fbconfig, GLX_GREEN_SIZE, &green); glXGetFBConfigAttrib(display(), fbconfig, GLX_BLUE_SIZE, &blue); glXGetFBConfigAttrib(display(), fbconfig, GLX_ALPHA_SIZE, &alpha); glXGetFBConfigAttrib(display(), fbconfig, GLX_DEPTH_SIZE, &depth); glXGetFBConfigAttrib(display(), fbconfig, GLX_STENCIL_SIZE, &stencil); glXGetFBConfigAttrib(display(), fbconfig, GLX_FRAMEBUFFER_SRGB_CAPABLE_ARB, &srgb); qCDebug(KWIN_X11STANDALONE, "Choosing GLXFBConfig %#x X visual %#x depth %d RGBA %d:%d:%d:%d ZS %d:%d sRGB: %d", fbconfig_id, visual_id, visualDepth(visual_id), red, green, blue, alpha, depth, stencil, srgb); } if (fbconfig == nullptr) { qCCritical(KWIN_X11STANDALONE) << "Failed to find a usable framebuffer configuration"; return false; } return true; } void GlxBackend::initVisualDepthHashTable() { const xcb_setup_t *setup = xcb_get_setup(connection()); for (auto screen = xcb_setup_roots_iterator(setup); screen.rem; xcb_screen_next(&screen)) { for (auto depth = xcb_screen_allowed_depths_iterator(screen.data); depth.rem; xcb_depth_next(&depth)) { const int len = xcb_depth_visuals_length(depth.data); const xcb_visualtype_t *visuals = xcb_depth_visuals(depth.data); for (int i = 0; i < len; i++) m_visualDepthHash.insert(visuals[i].visual_id, depth.data->depth); } } } int GlxBackend::visualDepth(xcb_visualid_t visual) const { return m_visualDepthHash.value(visual); } static inline int bitCount(uint32_t mask) { #if defined(__GNUC__) return __builtin_popcount(mask); #else int count = 0; while (mask) { count += (mask & 1); mask >>= 1; } return count; #endif } FBConfigInfo *GlxBackend::infoForVisual(xcb_visualid_t visual) { auto it = m_fbconfigHash.constFind(visual); if (it != m_fbconfigHash.constEnd()) { return it.value(); } FBConfigInfo *info = new FBConfigInfo; m_fbconfigHash.insert(visual, info); info->fbconfig = nullptr; info->bind_texture_format = 0; info->texture_targets = 0; info->y_inverted = 0; info->mipmap = 0; const xcb_render_pictformat_t format = XRenderUtils::findPictFormat(visual); const xcb_render_directformat_t *direct = XRenderUtils::findPictFormatInfo(format); if (!direct) { qCCritical(KWIN_X11STANDALONE).nospace() << "Could not find a picture format for visual 0x" << hex << visual; return info; } const int red_bits = bitCount(direct->red_mask); const int green_bits = bitCount(direct->green_mask); const int blue_bits = bitCount(direct->blue_mask); const int alpha_bits = bitCount(direct->alpha_mask); const int depth = visualDepth(visual); const auto rgb_sizes = std::tie(red_bits, green_bits, blue_bits); const int attribs[] = { GLX_RENDER_TYPE, GLX_RGBA_BIT, GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT | GLX_PIXMAP_BIT, GLX_X_VISUAL_TYPE, GLX_TRUE_COLOR, GLX_X_RENDERABLE, True, GLX_CONFIG_CAVEAT, int(GLX_DONT_CARE), // The ARGB32 visual is marked non-conformant in Catalyst GLX_FRAMEBUFFER_SRGB_CAPABLE_EXT, int(GLX_DONT_CARE), // The ARGB32 visual is marked sRGB capable in mesa/i965 GLX_BUFFER_SIZE, red_bits + green_bits + blue_bits + alpha_bits, GLX_RED_SIZE, red_bits, GLX_GREEN_SIZE, green_bits, GLX_BLUE_SIZE, blue_bits, GLX_ALPHA_SIZE, alpha_bits, GLX_STENCIL_SIZE, 0, GLX_DEPTH_SIZE, 0, 0 }; int count = 0; GLXFBConfig *configs = glXChooseFBConfig(display(), DefaultScreen(display()), attribs, &count); if (count < 1) { qCCritical(KWIN_X11STANDALONE).nospace() << "Could not find a framebuffer configuration for visual 0x" << hex << visual; return info; } struct FBConfig { GLXFBConfig config; int depth; int stencil; int format; }; std::deque candidates; for (int i = 0; i < count; i++) { int red, green, blue; glXGetFBConfigAttrib(display(), configs[i], GLX_RED_SIZE, &red); glXGetFBConfigAttrib(display(), configs[i], GLX_GREEN_SIZE, &green); glXGetFBConfigAttrib(display(), configs[i], GLX_BLUE_SIZE, &blue); if (std::tie(red, green, blue) != rgb_sizes) continue; xcb_visualid_t visual; glXGetFBConfigAttrib(display(), configs[i], GLX_VISUAL_ID, (int *) &visual); if (visualDepth(visual) != depth) continue; int bind_rgb, bind_rgba; glXGetFBConfigAttrib(display(), configs[i], GLX_BIND_TO_TEXTURE_RGBA_EXT, &bind_rgba); glXGetFBConfigAttrib(display(), configs[i], GLX_BIND_TO_TEXTURE_RGB_EXT, &bind_rgb); if (!bind_rgb && !bind_rgba) continue; int depth, stencil; glXGetFBConfigAttrib(display(), configs[i], GLX_DEPTH_SIZE, &depth); glXGetFBConfigAttrib(display(), configs[i], GLX_STENCIL_SIZE, &stencil); int texture_format; if (alpha_bits) texture_format = bind_rgba ? GLX_TEXTURE_FORMAT_RGBA_EXT : GLX_TEXTURE_FORMAT_RGB_EXT; else texture_format = bind_rgb ? GLX_TEXTURE_FORMAT_RGB_EXT : GLX_TEXTURE_FORMAT_RGBA_EXT; candidates.emplace_back(FBConfig{configs[i], depth, stencil, texture_format}); } if (count > 0) XFree(configs); std::stable_sort(candidates.begin(), candidates.end(), [](const FBConfig &left, const FBConfig &right) { if (left.depth < right.depth) return true; if (left.stencil < right.stencil) return true; return false; }); if (candidates.size() > 0) { const FBConfig &candidate = candidates.front(); int y_inverted, texture_targets; glXGetFBConfigAttrib(display(), candidate.config, GLX_BIND_TO_TEXTURE_TARGETS_EXT, &texture_targets); glXGetFBConfigAttrib(display(), candidate.config, GLX_Y_INVERTED_EXT, &y_inverted); info->fbconfig = candidate.config; info->bind_texture_format = candidate.format; info->texture_targets = texture_targets; info->y_inverted = y_inverted; info->mipmap = 0; } if (info->fbconfig) { int fbc_id = 0; int visual_id = 0; glXGetFBConfigAttrib(display(), info->fbconfig, GLX_FBCONFIG_ID, &fbc_id); glXGetFBConfigAttrib(display(), info->fbconfig, GLX_VISUAL_ID, &visual_id); qCDebug(KWIN_X11STANDALONE).nospace() << "Using FBConfig 0x" << hex << fbc_id << " for visual 0x" << hex << visual_id; } return info; } void GlxBackend::setSwapInterval(int interval) { if (m_haveEXTSwapControl) glXSwapIntervalEXT(display(), glxWindow, interval); else if (m_haveMESASwapControl) glXSwapIntervalMESA(interval); else if (m_haveSGISwapControl) glXSwapIntervalSGI(interval); } void GlxBackend::waitSync() { // NOTE that vsync has no effect with indirect rendering if (haveWaitSync) { uint sync; #if 0 // TODO: why precisely is this important? // the sync counter /can/ perform multiple steps during glXGetVideoSync & glXWaitVideoSync // but this only leads to waiting for two frames??!? glXGetVideoSync(&sync); glXWaitVideoSync(2, (sync + 1) % 2, &sync); #else glXWaitVideoSyncSGI(1, 0, &sync); #endif } } void GlxBackend::present() { if (lastDamage().isEmpty()) return; const QSize &screenSize = screens()->size(); const QRegion displayRegion(0, 0, screenSize.width(), screenSize.height()); const bool fullRepaint = supportsBufferAge() || (lastDamage() == displayRegion); if (fullRepaint) { if (m_haveINTELSwapEvent) Compositor::self()->aboutToSwapBuffers(); if (haveSwapInterval) { if (gs_tripleBufferNeedsDetection) { glXWaitGL(); m_swapProfiler.begin(); } glXSwapBuffers(display(), glxWindow); if (gs_tripleBufferNeedsDetection) { glXWaitGL(); if (char result = m_swapProfiler.end()) { gs_tripleBufferUndetected = gs_tripleBufferNeedsDetection = false; setBlocksForRetrace(result == 'd'); } } } else { waitSync(); glXSwapBuffers(display(), glxWindow); } if (supportsBufferAge()) { glXQueryDrawable(display(), glxWindow, GLX_BACK_BUFFER_AGE_EXT, (GLuint *) &m_bufferAge); } } else if (m_haveMESACopySubBuffer) { for (const QRect &r : lastDamage()) { // convert to OpenGL coordinates int y = screenSize.height() - r.y() - r.height(); glXCopySubBufferMESA(display(), glxWindow, r.x(), y, r.width(), r.height()); } } else { // Copy Pixels (horribly slow on Mesa) glDrawBuffer(GL_FRONT); copyPixels(lastDamage()); glDrawBuffer(GL_BACK); } setLastDamage(QRegion()); if (!supportsBufferAge()) { glXWaitGL(); XFlush(display()); } } void GlxBackend::screenGeometryChanged(const QSize &size) { doneCurrent(); XMoveResizeWindow(display(), window, 0, 0, size.width(), size.height()); overlayWindow()->setup(window); Xcb::sync(); makeCurrent(); glViewport(0, 0, size.width(), size.height()); // The back buffer contents are now undefined m_bufferAge = 0; } SceneOpenGLTexturePrivate *GlxBackend::createBackendTexture(SceneOpenGLTexture *texture) { return new GlxTexture(texture, this); } QRegion GlxBackend::prepareRenderingFrame() { QRegion repaint; if (gs_tripleBufferNeedsDetection) { // the composite timer floors the repaint frequency. This can pollute our triple buffering // detection because the glXSwapBuffers call for the new frame has to wait until the pending // one scanned out. // So we compensate for that by waiting an extra milisecond to give the driver the chance to // fllush the buffer queue usleep(1000); } present(); if (supportsBufferAge()) repaint = accumulatedDamageHistory(m_bufferAge); startRenderTimer(); glXWaitX(); return repaint; } void GlxBackend::endRenderingFrame(const QRegion &renderedRegion, const QRegion &damagedRegion) { if (damagedRegion.isEmpty()) { setLastDamage(QRegion()); // If the damaged region of a window is fully occluded, the only // rendering done, if any, will have been to repair a reused back // buffer, making it identical to the front buffer. // // In this case we won't post the back buffer. Instead we'll just // set the buffer age to 1, so the repaired regions won't be // rendered again in the next frame. if (!renderedRegion.isEmpty()) glFlush(); m_bufferAge = 1; return; } setLastDamage(renderedRegion); if (!blocksForRetrace()) { // This also sets lastDamage to empty which prevents the frame from // being posted again when prepareRenderingFrame() is called. present(); } else { // Make sure that the GPU begins processing the command stream // now and not the next time prepareRenderingFrame() is called. glFlush(); } if (overlayWindow()->window()) // show the window only after the first pass, overlayWindow()->show(); // since that pass may take long // Save the damaged region to history if (supportsBufferAge()) addToDamageHistory(damagedRegion); } bool GlxBackend::makeCurrent() { if (QOpenGLContext *context = QOpenGLContext::currentContext()) { // Workaround to tell Qt that no QOpenGLContext is current context->doneCurrent(); } const bool current = glXMakeCurrent(display(), glxWindow, ctx); return current; } void GlxBackend::doneCurrent() { glXMakeCurrent(display(), None, nullptr); } OverlayWindow* GlxBackend::overlayWindow() const { return m_overlayWindow; } bool GlxBackend::usesOverlayWindow() const { return true; } /******************************************************** * GlxTexture *******************************************************/ GlxTexture::GlxTexture(SceneOpenGLTexture *texture, GlxBackend *backend) : SceneOpenGLTexturePrivate() , q(texture) , m_backend(backend) , m_glxpixmap(None) { } GlxTexture::~GlxTexture() { if (m_glxpixmap != None) { if (!options->isGlStrictBinding()) { glXReleaseTexImageEXT(display(), m_glxpixmap, GLX_FRONT_LEFT_EXT); } glXDestroyPixmap(display(), m_glxpixmap); m_glxpixmap = None; } } void GlxTexture::onDamage() { if (options->isGlStrictBinding() && m_glxpixmap) { glXReleaseTexImageEXT(display(), m_glxpixmap, GLX_FRONT_LEFT_EXT); - glXBindTexImageEXT(display(), m_glxpixmap, GLX_FRONT_LEFT_EXT, NULL); + glXBindTexImageEXT(display(), m_glxpixmap, GLX_FRONT_LEFT_EXT, nullptr); } GLTexturePrivate::onDamage(); } bool GlxTexture::loadTexture(xcb_pixmap_t pixmap, const QSize &size, xcb_visualid_t visual) { if (pixmap == XCB_NONE || size.isEmpty() || visual == XCB_NONE) return false; const FBConfigInfo *info = m_backend->infoForVisual(visual); if (!info || info->fbconfig == nullptr) return false; if (info->texture_targets & GLX_TEXTURE_2D_BIT_EXT) { m_target = GL_TEXTURE_2D; m_scale.setWidth(1.0f / m_size.width()); m_scale.setHeight(1.0f / m_size.height()); } else { Q_ASSERT(info->texture_targets & GLX_TEXTURE_RECTANGLE_BIT_EXT); m_target = GL_TEXTURE_RECTANGLE; m_scale.setWidth(1.0f); m_scale.setHeight(1.0f); } const int attrs[] = { GLX_TEXTURE_FORMAT_EXT, info->bind_texture_format, GLX_MIPMAP_TEXTURE_EXT, false, GLX_TEXTURE_TARGET_EXT, m_target == GL_TEXTURE_2D ? GLX_TEXTURE_2D_EXT : GLX_TEXTURE_RECTANGLE_EXT, 0 }; m_glxpixmap = glXCreatePixmap(display(), info->fbconfig, pixmap, attrs); m_size = size; m_yInverted = info->y_inverted ? true : false; m_canUseMipmaps = false; glGenTextures(1, &m_texture); q->setDirty(); q->setFilter(GL_NEAREST); glBindTexture(m_target, m_texture); glXBindTexImageEXT(display(), m_glxpixmap, GLX_FRONT_LEFT_EXT, nullptr); updateMatrix(); return true; } bool GlxTexture::loadTexture(WindowPixmap *pixmap) { Toplevel *t = pixmap->toplevel(); return loadTexture(pixmap->pixmap(), t->size(), t->visual()); } OpenGLBackend *GlxTexture::backend() { return m_backend; } } // namespace diff --git a/plugins/platforms/x11/standalone/overlaywindow_x11.cpp b/plugins/platforms/x11/standalone/overlaywindow_x11.cpp index 05e70a539..3bd228180 100644 --- a/plugins/platforms/x11/standalone/overlaywindow_x11.cpp +++ b/plugins/platforms/x11/standalone/overlaywindow_x11.cpp @@ -1,201 +1,201 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2011 Arthur Arlt 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 "overlaywindow_x11.h" #include "kwinglobals.h" #include "composite.h" #include "screens.h" #include "utils.h" #include "xcbutils.h" #include #include #include #if XCB_COMPOSITE_MAJOR_VERSION > 0 || XCB_COMPOSITE_MINOR_VERSION >= 3 #define KWIN_HAVE_XCOMPOSITE_OVERLAY #endif namespace KWin { OverlayWindowX11::OverlayWindowX11() : OverlayWindow() , X11EventFilter(QVector{XCB_EXPOSE, XCB_VISIBILITY_NOTIFY}) , m_visible(true) , m_shown(false) , m_window(XCB_WINDOW_NONE) { } OverlayWindowX11::~OverlayWindowX11() { } bool OverlayWindowX11::create() { Q_ASSERT(m_window == XCB_WINDOW_NONE); if (!Xcb::Extensions::self()->isCompositeOverlayAvailable()) return false; if (!Xcb::Extensions::self()->isShapeInputAvailable()) // needed in setupOverlay() return false; #ifdef KWIN_HAVE_XCOMPOSITE_OVERLAY Xcb::OverlayWindow overlay(rootWindow()); if (overlay.isNull()) { return false; } m_window = overlay->overlay_win; if (m_window == XCB_WINDOW_NONE) return false; resize(screens()->size()); return true; #else return false; #endif } void OverlayWindowX11::setup(xcb_window_t window) { Q_ASSERT(m_window != XCB_WINDOW_NONE); Q_ASSERT(Xcb::Extensions::self()->isShapeInputAvailable()); setNoneBackgroundPixmap(m_window); m_shape = QRegion(); const QSize &s = screens()->size(); setShape(QRect(0, 0, s.width(), s.height())); if (window != XCB_WINDOW_NONE) { setNoneBackgroundPixmap(window); setupInputShape(window); } const uint32_t eventMask = XCB_EVENT_MASK_VISIBILITY_CHANGE; xcb_change_window_attributes(connection(), m_window, XCB_CW_EVENT_MASK, &eventMask); } void OverlayWindowX11::setupInputShape(xcb_window_t window) { - xcb_shape_rectangles(connection(), XCB_SHAPE_SO_SET, XCB_SHAPE_SK_INPUT, XCB_CLIP_ORDERING_UNSORTED, window, 0, 0, 0, NULL); + xcb_shape_rectangles(connection(), XCB_SHAPE_SO_SET, XCB_SHAPE_SK_INPUT, XCB_CLIP_ORDERING_UNSORTED, window, 0, 0, 0, nullptr); } void OverlayWindowX11::setNoneBackgroundPixmap(xcb_window_t window) { const uint32_t mask = XCB_BACK_PIXMAP_NONE; xcb_change_window_attributes(connection(), window, XCB_CW_BACK_PIXMAP, &mask); } void OverlayWindowX11::show() { Q_ASSERT(m_window != XCB_WINDOW_NONE); if (m_shown) return; xcb_map_subwindows(connection(), m_window); xcb_map_window(connection(), m_window); m_shown = true; } void OverlayWindowX11::hide() { Q_ASSERT(m_window != XCB_WINDOW_NONE); xcb_unmap_window(connection(), m_window); m_shown = false; const QSize &s = screens()->size(); setShape(QRect(0, 0, s.width(), s.height())); } void OverlayWindowX11::setShape(const QRegion& reg) { // Avoid setting the same shape again, it causes flicker (apparently it is not a no-op // and triggers something). if (reg == m_shape) return; const QVector xrects = Xcb::regionToRects(reg); xcb_shape_rectangles(connection(), XCB_SHAPE_SO_SET, XCB_SHAPE_SK_BOUNDING, XCB_CLIP_ORDERING_UNSORTED, m_window, 0, 0, xrects.count(), xrects.data()); setupInputShape(m_window); m_shape = reg; } void OverlayWindowX11::resize(const QSize &size) { Q_ASSERT(m_window != XCB_WINDOW_NONE); const uint32_t geometry[2] = { static_cast(size.width()), static_cast(size.height()) }; xcb_configure_window(connection(), m_window, XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT, geometry); setShape(QRegion(0, 0, size.width(), size.height())); } bool OverlayWindowX11::isVisible() const { return m_visible; } void OverlayWindowX11::setVisibility(bool visible) { m_visible = visible; } void OverlayWindowX11::destroy() { if (m_window == XCB_WINDOW_NONE) return; // reset the overlay shape const QSize &s = screens()->size(); xcb_rectangle_t rec = { 0, 0, static_cast(s.width()), static_cast(s.height()) }; xcb_shape_rectangles(connection(), XCB_SHAPE_SO_SET, XCB_SHAPE_SK_BOUNDING, XCB_CLIP_ORDERING_UNSORTED, m_window, 0, 0, 1, &rec); xcb_shape_rectangles(connection(), XCB_SHAPE_SO_SET, XCB_SHAPE_SK_INPUT, XCB_CLIP_ORDERING_UNSORTED, m_window, 0, 0, 1, &rec); #ifdef KWIN_HAVE_XCOMPOSITE_OVERLAY xcb_composite_release_overlay_window(connection(), m_window); #endif m_window = XCB_WINDOW_NONE; m_shown = false; } xcb_window_t OverlayWindowX11::window() const { return m_window; } bool OverlayWindowX11::event(xcb_generic_event_t *event) { const uint8_t eventType = event->response_type & ~0x80; if (eventType == XCB_EXPOSE) { const auto *expose = reinterpret_cast(event); if (expose->window == rootWindow() // root window needs repainting || (m_window != XCB_WINDOW_NONE && expose->window == m_window)) { // overlay needs repainting Compositor::self()->addRepaint(expose->x, expose->y, expose->width, expose->height); } } else if (eventType == XCB_VISIBILITY_NOTIFY) { const auto *visibility = reinterpret_cast(event); if (m_window != XCB_WINDOW_NONE && visibility->window == m_window) { bool was_visible = isVisible(); setVisibility((visibility->state != XCB_VISIBILITY_FULLY_OBSCURED)); auto compositor = Compositor::self(); if (!was_visible && m_visible) { // hack for #154825 compositor->addRepaintFull(); QTimer::singleShot(2000, compositor, &Compositor::addRepaintFull); } compositor->scheduleRepaint(); } } return false; } } // namespace KWin diff --git a/plugins/platforms/x11/standalone/windowselector.cpp b/plugins/platforms/x11/standalone/windowselector.cpp index 4f7d39e5e..aa4412e97 100644 --- a/plugins/platforms/x11/standalone/windowselector.cpp +++ b/plugins/platforms/x11/standalone/windowselector.cpp @@ -1,273 +1,273 @@ /******************************************************************** 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) 2012 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 "windowselector.h" #include "client.h" #include "cursor.h" #include "unmanaged.h" #include "workspace.h" #include "xcbutils.h" // XLib #include #include #include // XCB #include namespace KWin { WindowSelector::WindowSelector() : X11EventFilter(QVector{XCB_BUTTON_PRESS, XCB_BUTTON_RELEASE, XCB_MOTION_NOTIFY, XCB_ENTER_NOTIFY, XCB_LEAVE_NOTIFY, XCB_KEY_PRESS, XCB_KEY_RELEASE, XCB_FOCUS_IN, XCB_FOCUS_OUT }) , m_active(false) { } WindowSelector::~WindowSelector() { } void WindowSelector::start(std::function callback, const QByteArray &cursorName) { if (m_active) { callback(nullptr); return; } m_active = activate(cursorName); if (!m_active) { callback(nullptr); return; } m_callback = callback; } void WindowSelector::start(std::function callback) { if (m_active) { callback(QPoint(-1, -1)); return; } m_active = activate(); if (!m_active) { callback(QPoint(-1, -1)); return; } m_pointSelectionFallback = callback; } bool WindowSelector::activate(const QByteArray &cursorName) { xcb_cursor_t cursor = createCursor(cursorName); xcb_connection_t *c = connection(); ScopedCPointer grabPointer(xcb_grab_pointer_reply(c, xcb_grab_pointer_unchecked(c, false, rootWindow(), 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, XCB_WINDOW_NONE, - cursor, XCB_TIME_CURRENT_TIME), NULL)); + cursor, XCB_TIME_CURRENT_TIME), nullptr)); if (grabPointer.isNull() || grabPointer->status != XCB_GRAB_STATUS_SUCCESS) { return false; } const bool grabbed = grabXKeyboard(); if (grabbed) { grabXServer(); } else { xcb_ungrab_pointer(connection(), XCB_TIME_CURRENT_TIME); } return grabbed; } xcb_cursor_t WindowSelector::createCursor(const QByteArray &cursorName) { if (cursorName.isEmpty()) { return Cursor::x11Cursor(Qt::CrossCursor); } xcb_cursor_t cursor = Cursor::x11Cursor(cursorName); if (cursor != XCB_CURSOR_NONE) { return cursor; } if (cursorName == QByteArrayLiteral("pirate")) { // special handling for font pirate cursor static xcb_cursor_t kill_cursor = XCB_CURSOR_NONE; if (kill_cursor != XCB_CURSOR_NONE) { return kill_cursor; } // fallback on font xcb_connection_t *c = connection(); const xcb_font_t cursorFont = xcb_generate_id(c); xcb_open_font(c, cursorFont, strlen ("cursor"), "cursor"); cursor = xcb_generate_id(c); xcb_create_glyph_cursor(c, cursor, cursorFont, cursorFont, XC_pirate, /* source character glyph */ XC_pirate + 1, /* mask character glyph */ 0, 0, 0, 0, 0, 0); /* r b g r b g */ kill_cursor = cursor; } return cursor; } void WindowSelector::processEvent(xcb_generic_event_t *event) { if (event->response_type == XCB_BUTTON_RELEASE) { xcb_button_release_event_t *buttonEvent = reinterpret_cast(event); handleButtonRelease(buttonEvent->detail, buttonEvent->child); } else if (event->response_type == XCB_KEY_PRESS) { xcb_key_press_event_t *keyEvent = reinterpret_cast(event); handleKeyPress(keyEvent->detail, keyEvent->state); } } bool WindowSelector::event(xcb_generic_event_t *event) { if (!m_active) { return false; } processEvent(event); return true; } void WindowSelector::handleButtonRelease(xcb_button_t button, xcb_window_t window) { if (button == XCB_BUTTON_INDEX_3) { cancelCallback(); release(); return; } if (button == XCB_BUTTON_INDEX_1 || button == XCB_BUTTON_INDEX_2) { if (m_callback) { selectWindowId(window); } else if (m_pointSelectionFallback) { m_pointSelectionFallback(Cursor::pos()); } release(); return; } } void WindowSelector::handleKeyPress(xcb_keycode_t keycode, uint16_t state) { xcb_key_symbols_t *symbols = xcb_key_symbols_alloc(connection()); xcb_keysym_t kc = xcb_key_symbols_get_keysym(symbols, keycode, 0); int mx = 0; int my = 0; const bool returnPressed = (kc == XK_Return) || (kc == XK_space); const bool escapePressed = (kc == XK_Escape); if (kc == XK_Left) { mx = -10; } if (kc == XK_Right) { mx = 10; } if (kc == XK_Up) { my = -10; } if (kc == XK_Down) { my = 10; } if (state & XCB_MOD_MASK_CONTROL) { mx /= 10; my /= 10; } Cursor::setPos(Cursor::pos() + QPoint(mx, my)); if (returnPressed) { if (m_callback) { selectWindowUnderPointer(); } else if (m_pointSelectionFallback) { m_pointSelectionFallback(Cursor::pos()); } } if (returnPressed || escapePressed) { if (escapePressed) { cancelCallback(); } release(); } xcb_key_symbols_free(symbols); } void WindowSelector::selectWindowUnderPointer() { Xcb::Pointer pointer(rootWindow()); if (!pointer.isNull() && pointer->child != XCB_WINDOW_NONE) { selectWindowId(pointer->child); } } void WindowSelector::release() { ungrabXKeyboard(); xcb_ungrab_pointer(connection(), XCB_TIME_CURRENT_TIME); ungrabXServer(); m_active = false; m_callback = std::function(); m_pointSelectionFallback = std::function(); } void WindowSelector::selectWindowId(xcb_window_t window_to_select) { if (window_to_select == XCB_WINDOW_NONE) { m_callback(nullptr); return; } xcb_window_t window = window_to_select; - Client* client = NULL; + Client* client = nullptr; while (true) { client = Workspace::self()->findClient(Predicate::FrameIdMatch, window); if (client) { break; // Found the client } Xcb::Tree tree(window); if (window == tree->root) { // We didn't find the client, probably an override-redirect window break; } window = tree->parent; // Go up } if (client) { m_callback(client); } else { m_callback(Workspace::self()->findUnmanaged(window)); } } void WindowSelector::cancelCallback() { if (m_callback) { m_callback(nullptr); } else if (m_pointSelectionFallback) { m_pointSelectionFallback(QPoint(-1, -1)); } } } // namespace diff --git a/plugins/scenes/opengl/lanczosfilter.cpp b/plugins/scenes/opengl/lanczosfilter.cpp index f70c2c12a..6b4cae100 100644 --- a/plugins/scenes/opengl/lanczosfilter.cpp +++ b/plugins/scenes/opengl/lanczosfilter.cpp @@ -1,428 +1,428 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2010 by Fredrik Höglund Copyright (C) 2010 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 "lanczosfilter.h" #include "client.h" #include "deleted.h" #include "effects.h" #include "screens.h" #include "unmanaged.h" #include "options.h" #include "workspace.h" #include #include #include #include #include #include #include namespace KWin { LanczosFilter::LanczosFilter(QObject* parent) : QObject(parent) - , m_offscreenTex(0) - , m_offscreenTarget(0) + , m_offscreenTex(nullptr) + , m_offscreenTarget(nullptr) , m_inited(false) - , m_shader(0) + , m_shader(nullptr) , m_uOffsets(0) , m_uKernel(0) { } LanczosFilter::~LanczosFilter() { delete m_offscreenTarget; delete m_offscreenTex; } void LanczosFilter::init() { if (m_inited) return; m_inited = true; const bool force = (qstrcmp(qgetenv("KWIN_FORCE_LANCZOS"), "1") == 0); if (force) { qCWarning(KWIN_OPENGL) << "Lanczos Filter forced on by environment variable"; } if (!force && options->glSmoothScale() != 2) return; // disabled by config if (!GLRenderTarget::supported()) return; GLPlatform *gl = GLPlatform::instance(); if (!force) { // The lanczos filter is reported to be broken with the Intel driver prior SandyBridge if (gl->driver() == Driver_Intel && gl->chipClass() < SandyBridge) return; // also radeon before R600 has trouble if (gl->isRadeon() && gl->chipClass() < R600) return; // and also for software emulation (e.g. llvmpipe) if (gl->isSoftwareEmulation()) { return; } } QFile ff(gl->glslVersion() >= kVersionNumber(1, 40) ? QStringLiteral(":/scenes/opengl/shaders/1.40/lanczos-fragment.glsl") : QStringLiteral(":/scenes/opengl/shaders/1.10/lanczos-fragment.glsl")); if (!ff.open(QIODevice::ReadOnly)) { qCDebug(KWIN_OPENGL) << "Failed to open lanczos shader"; return; } m_shader.reset(ShaderManager::instance()->generateCustomShader(ShaderTrait::MapTexture, QByteArray(), ff.readAll())); if (m_shader->isValid()) { ShaderBinder binder(m_shader.data()); m_uKernel = m_shader->uniformLocation("kernel"); m_uOffsets = m_shader->uniformLocation("offsets"); } else { qCDebug(KWIN_OPENGL) << "Shader is not valid"; m_shader.reset(); } } void LanczosFilter::updateOffscreenSurfaces() { const QSize &s = screens()->size(); int w = s.width(); int h = s.height(); if (!m_offscreenTex || m_offscreenTex->width() != w || m_offscreenTex->height() != h) { if (m_offscreenTex) { delete m_offscreenTex; delete m_offscreenTarget; } m_offscreenTex = new GLTexture(GL_RGBA8, w, h); m_offscreenTex->setFilter(GL_LINEAR); m_offscreenTex->setWrapMode(GL_CLAMP_TO_EDGE); m_offscreenTarget = new GLRenderTarget(*m_offscreenTex); } } static float sinc(float x) { return std::sin(x * M_PI) / (x * M_PI); } static float lanczos(float x, float a) { if (qFuzzyCompare(x + 1.0, 1.0)) return 1.0; if (qAbs(x) >= a) return 0.0; return sinc(x) * sinc(x / a); } void LanczosFilter::createKernel(float delta, int *size) { const float a = 2.0; // The two outermost samples always fall at points where the lanczos // function returns 0, so we'll skip them. const int sampleCount = qBound(3, qCeil(delta * a) * 2 + 1 - 2, 29); const int center = sampleCount / 2; const int kernelSize = center + 1; const float factor = 1.0 / delta; QVector values(kernelSize); float sum = 0; for (int i = 0; i < kernelSize; i++) { const float val = lanczos(i * factor, a); sum += i > 0 ? val * 2 : val; values[i] = val; } memset(m_kernel, 0, 16 * sizeof(QVector4D)); // Normalize the kernel for (int i = 0; i < kernelSize; i++) { const float val = values[i] / sum; m_kernel[i] = QVector4D(val, val, val, val); } *size = kernelSize; } void LanczosFilter::createOffsets(int count, float width, Qt::Orientation direction) { memset(m_offsets, 0, 16 * sizeof(QVector2D)); for (int i = 0; i < count; i++) { m_offsets[i] = (direction == Qt::Horizontal) ? QVector2D(i / width, 0) : QVector2D(0, i / width); } } void LanczosFilter::performPaint(EffectWindowImpl* w, int mask, QRegion region, WindowPaintData& data) { if (data.xScale() < 0.9 || data.yScale() < 0.9) { if (!m_inited) init(); const QRect screenRect = Workspace::self()->clientArea(ScreenArea, w->screen(), w->desktop()); // window geometry may not be bigger than screen geometry to fit into the FBO QRect winGeo(w->expandedGeometry()); if (m_shader && winGeo.width() <= screenRect.width() && winGeo.height() <= screenRect.height()) { winGeo.translate(-w->geometry().topLeft()); double left = winGeo.left(); double top = winGeo.top(); double width = winGeo.right() - left; double height = winGeo.bottom() - top; int tx = data.xTranslation() + w->x() + left * data.xScale(); int ty = data.yTranslation() + w->y() + top * data.yScale(); int tw = width * data.xScale(); int th = height * data.yScale(); const QRect textureRect(tx, ty, tw, th); const bool hardwareClipping = !(QRegion(textureRect)-region).isEmpty(); int sw = width; int sh = height; GLTexture *cachedTexture = static_cast< GLTexture*>(w->data(LanczosCacheRole).value()); if (cachedTexture) { if (cachedTexture->width() == tw && cachedTexture->height() == th) { cachedTexture->bind(); if (hardwareClipping) { glEnable(GL_SCISSOR_TEST); } glEnable(GL_BLEND); glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); const qreal rgb = data.brightness() * data.opacity(); const qreal a = data.opacity(); ShaderBinder binder(ShaderTrait::MapTexture | ShaderTrait::Modulate | ShaderTrait::AdjustSaturation); GLShader *shader = binder.shader(); QMatrix4x4 mvp = data.screenProjectionMatrix(); mvp.translate(textureRect.x(), textureRect.y()); shader->setUniform(GLShader::ModelViewProjectionMatrix, mvp); shader->setUniform(GLShader::ModulationConstant, QVector4D(rgb, rgb, rgb, a)); shader->setUniform(GLShader::Saturation, data.saturation()); cachedTexture->render(region, textureRect, hardwareClipping); glDisable(GL_BLEND); if (hardwareClipping) { glDisable(GL_SCISSOR_TEST); } cachedTexture->unbind(); m_timer.start(5000, this); return; } else { // offscreen texture not matching - delete delete cachedTexture; - cachedTexture = 0; + cachedTexture = nullptr; w->setData(LanczosCacheRole, QVariant()); } } WindowPaintData thumbData = data; thumbData.setXScale(1.0); thumbData.setYScale(1.0); thumbData.setXTranslation(-w->x() - left); thumbData.setYTranslation(-w->y() - top); thumbData.setBrightness(1.0); thumbData.setOpacity(1.0); thumbData.setSaturation(1.0); // Bind the offscreen FBO and draw the window on it unscaled updateOffscreenSurfaces(); GLRenderTarget::pushRenderTarget(m_offscreenTarget); QMatrix4x4 modelViewProjectionMatrix; modelViewProjectionMatrix.ortho(0, m_offscreenTex->width(), m_offscreenTex->height(), 0 , 0, 65535); thumbData.setProjectionMatrix(modelViewProjectionMatrix); glClearColor(0.0, 0.0, 0.0, 0.0); glClear(GL_COLOR_BUFFER_BIT); w->sceneWindow()->performPaint(mask, infiniteRegion(), thumbData); // Create a scratch texture and copy the rendered window into it GLTexture tex(GL_RGBA8, sw, sh); tex.setFilter(GL_LINEAR); tex.setWrapMode(GL_CLAMP_TO_EDGE); tex.bind(); glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, m_offscreenTex->height() - sh, sw, sh); // Set up the shader for horizontal scaling float dx = sw / float(tw); int kernelSize; createKernel(dx, &kernelSize); createOffsets(kernelSize, sw, Qt::Horizontal); ShaderManager::instance()->pushShader(m_shader.data()); m_shader->setUniform(GLShader::ModelViewProjectionMatrix, modelViewProjectionMatrix); setUniforms(); // Draw the window back into the FBO, this time scaled horizontally glClear(GL_COLOR_BUFFER_BIT); QVector verts; QVector texCoords; verts.reserve(12); texCoords.reserve(12); texCoords << 1.0 << 0.0; verts << tw << 0.0; // Top right texCoords << 0.0 << 0.0; verts << 0.0 << 0.0; // Top left texCoords << 0.0 << 1.0; verts << 0.0 << sh; // Bottom left texCoords << 0.0 << 1.0; verts << 0.0 << sh; // Bottom left texCoords << 1.0 << 1.0; verts << tw << sh; // Bottom right texCoords << 1.0 << 0.0; verts << tw << 0.0; // Top right GLVertexBuffer *vbo = GLVertexBuffer::streamingBuffer(); vbo->reset(); vbo->setData(6, 2, verts.constData(), texCoords.constData()); vbo->render(GL_TRIANGLES); // At this point we don't need the scratch texture anymore tex.unbind(); tex.discard(); // create scratch texture for second rendering pass GLTexture tex2(GL_RGBA8, tw, sh); tex2.setFilter(GL_LINEAR); tex2.setWrapMode(GL_CLAMP_TO_EDGE); tex2.bind(); glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, m_offscreenTex->height() - sh, tw, sh); // Set up the shader for vertical scaling float dy = sh / float(th); createKernel(dy, &kernelSize); createOffsets(kernelSize, m_offscreenTex->height(), Qt::Vertical); setUniforms(); // Now draw the horizontally scaled window in the FBO at the right // coordinates on the screen, while scaling it vertically and blending it. glClear(GL_COLOR_BUFFER_BIT); verts.clear(); verts << tw << 0.0; // Top right verts << 0.0 << 0.0; // Top left verts << 0.0 << th; // Bottom left verts << 0.0 << th; // Bottom left verts << tw << th; // Bottom right verts << tw << 0.0; // Top right vbo->setData(6, 2, verts.constData(), texCoords.constData()); vbo->render(GL_TRIANGLES); tex2.unbind(); tex2.discard(); ShaderManager::instance()->popShader(); // create cache texture GLTexture *cache = new GLTexture(GL_RGBA8, tw, th); cache->setFilter(GL_LINEAR); cache->setWrapMode(GL_CLAMP_TO_EDGE); cache->bind(); glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, m_offscreenTex->height() - th, tw, th); GLRenderTarget::popRenderTarget(); if (hardwareClipping) { glEnable(GL_SCISSOR_TEST); } glEnable(GL_BLEND); glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); const qreal rgb = data.brightness() * data.opacity(); const qreal a = data.opacity(); ShaderBinder binder(ShaderTrait::MapTexture | ShaderTrait::Modulate | ShaderTrait::AdjustSaturation); GLShader *shader = binder.shader(); QMatrix4x4 mvp = data.screenProjectionMatrix(); mvp.translate(textureRect.x(), textureRect.y()); shader->setUniform(GLShader::ModelViewProjectionMatrix, mvp); shader->setUniform(GLShader::ModulationConstant, QVector4D(rgb, rgb, rgb, a)); shader->setUniform(GLShader::Saturation, data.saturation()); cache->render(region, textureRect, hardwareClipping); glDisable(GL_BLEND); if (hardwareClipping) { glDisable(GL_SCISSOR_TEST); } cache->unbind(); w->setData(LanczosCacheRole, QVariant::fromValue(static_cast(cache))); // Delete the offscreen surface after 5 seconds m_timer.start(5000, this); return; } } // if ( effects->compositingType() == KWin::OpenGLCompositing ) w->sceneWindow()->performPaint(mask, region, data); } // End of function void LanczosFilter::timerEvent(QTimerEvent *event) { if (event->timerId() == m_timer.timerId()) { m_timer.stop(); delete m_offscreenTarget; delete m_offscreenTex; - m_offscreenTarget = 0; - m_offscreenTex = 0; + m_offscreenTarget = nullptr; + m_offscreenTex = nullptr; foreach (Client *c, Workspace::self()->clientList()) { discardCacheTexture(c->effectWindow()); } foreach (Client *c, Workspace::self()->desktopList()) { discardCacheTexture(c->effectWindow()); } foreach (Unmanaged *u, Workspace::self()->unmanagedList()) { discardCacheTexture(u->effectWindow()); } foreach (Deleted *d, Workspace::self()->deletedList()) { discardCacheTexture(d->effectWindow()); } } } void LanczosFilter::discardCacheTexture(EffectWindow *w) { QVariant cachedTextureVariant = w->data(LanczosCacheRole); if (cachedTextureVariant.isValid()) { delete static_cast< GLTexture*>(cachedTextureVariant.value()); w->setData(LanczosCacheRole, QVariant()); } } void LanczosFilter::setUniforms() { glUniform2fv(m_uOffsets, 16, (const GLfloat*)m_offsets); glUniform4fv(m_uKernel, 16, (const GLfloat*)m_kernel); } } // namespace diff --git a/plugins/scenes/opengl/lanczosfilter.h b/plugins/scenes/opengl/lanczosfilter.h index bb11a6e1f..f05872db3 100644 --- a/plugins/scenes/opengl/lanczosfilter.h +++ b/plugins/scenes/opengl/lanczosfilter.h @@ -1,73 +1,73 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2010 by Fredrik Höglund Copyright (C) 2010 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_LANCZOSFILTER_P_H #define KWIN_LANCZOSFILTER_P_H #include #include #include #include #include namespace KWin { class EffectWindow; class EffectWindowImpl; class WindowPaintData; class GLTexture; class GLRenderTarget; class GLShader; class LanczosFilter : public QObject { Q_OBJECT public: - explicit LanczosFilter(QObject* parent = 0); + explicit LanczosFilter(QObject* parent = nullptr); ~LanczosFilter() override; void performPaint(EffectWindowImpl* w, int mask, QRegion region, WindowPaintData& data); protected: void timerEvent(QTimerEvent*) override; private: void init(); void updateOffscreenSurfaces(); void setUniforms(); void discardCacheTexture(EffectWindow *w); void createKernel(float delta, int *kernelSize); void createOffsets(int count, float width, Qt::Orientation direction); GLTexture *m_offscreenTex; GLRenderTarget *m_offscreenTarget; QBasicTimer m_timer; bool m_inited; QScopedPointer m_shader; int m_uOffsets; int m_uKernel; QVector2D m_offsets[16]; QVector4D m_kernel[16]; }; } // namespace #endif // KWIN_LANCZOSFILTER_P_H diff --git a/plugins/scenes/opengl/scene_opengl.cpp b/plugins/scenes/opengl/scene_opengl.cpp index e7460375b..b253ee9c9 100644 --- a/plugins/scenes/opengl/scene_opengl.cpp +++ b/plugins/scenes/opengl/scene_opengl.cpp @@ -1,2593 +1,2593 @@ /******************************************************************** 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 NULL; + return nullptr; } - SceneOpenGL *scene = NULL; + 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 = NULL; + 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(NULL) + , 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(), NULL); + 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 = NULL; + 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(NULL) + , m_scene(nullptr) { } SceneOpenGL::Window::~Window() { } -static SceneOpenGLTexture *s_frameTexture = NULL; +static SceneOpenGLTexture *s_frameTexture = nullptr; // Bind the window pixmap to an OpenGL texture. bool SceneOpenGL::Window::bindTexture() { - s_frameTexture = NULL; + 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() : NULL; + 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() { } 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()) { 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 = NULL; -QPixmap* SceneOpenGL::EffectFrame::m_unstyledPixmap = NULL; +GLTexture* SceneOpenGL::EffectFrame::m_unstyledTexture = nullptr; +QPixmap* SceneOpenGL::EffectFrame::m_unstyledPixmap = nullptr; SceneOpenGL::EffectFrame::EffectFrame(EffectFrameImpl* frame, SceneOpenGL *scene) : Scene::EffectFrame(frame) - , m_texture(NULL) - , m_textTexture(NULL) - , m_oldTextTexture(NULL) - , m_textPixmap(NULL) - , m_iconTexture(NULL) - , m_oldIconTexture(NULL) - , m_selectionTexture(NULL) - , m_unstyledVBO(NULL) + , 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 = NULL; + m_texture = nullptr; delete m_textTexture; - m_textTexture = NULL; + m_textTexture = nullptr; delete m_textPixmap; - m_textPixmap = NULL; + m_textPixmap = nullptr; delete m_iconTexture; - m_iconTexture = NULL; + m_iconTexture = nullptr; delete m_selectionTexture; - m_selectionTexture = NULL; + m_selectionTexture = nullptr; delete m_unstyledVBO; - m_unstyledVBO = NULL; + m_unstyledVBO = nullptr; delete m_oldIconTexture; - m_oldIconTexture = NULL; + m_oldIconTexture = nullptr; delete m_oldTextTexture; - m_oldTextTexture = NULL; + m_oldTextTexture = nullptr; } void SceneOpenGL::EffectFrame::freeIconFrame() { delete m_iconTexture; - m_iconTexture = NULL; + m_iconTexture = nullptr; } void SceneOpenGL::EffectFrame::freeTextFrame() { delete m_textTexture; - m_textTexture = NULL; + m_textTexture = nullptr; delete m_textPixmap; - m_textPixmap = NULL; + m_textPixmap = nullptr; } void SceneOpenGL::EffectFrame::freeSelection() { delete m_selectionTexture; - m_selectionTexture = NULL; + m_selectionTexture = nullptr; } void SceneOpenGL::EffectFrame::crossFadeIcon() { delete m_oldIconTexture; m_oldIconTexture = m_iconTexture; - m_iconTexture = NULL; + m_iconTexture = nullptr; } void SceneOpenGL::EffectFrame::crossFadeText() { delete m_oldTextTexture; m_oldTextTexture = m_textTexture; - m_textTexture = NULL; + 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 = 0L; + 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 = 0L; + m_textTexture = nullptr; delete m_textPixmap; - m_textPixmap = 0L; + 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 = 0L; + m_unstyledTexture = nullptr; delete m_unstyledPixmap; - m_unstyledPixmap = 0L; + 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 = NULL; + m_unstyledTexture = nullptr; delete m_unstyledPixmap; - m_unstyledPixmap = NULL; + 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/opengl/scene_opengl.h b/plugins/scenes/opengl/scene_opengl.h index 3c0d54b87..b2eea05f6 100644 --- a/plugins/scenes/opengl/scene_opengl.h +++ b/plugins/scenes/opengl/scene_opengl.h @@ -1,357 +1,357 @@ /******************************************************************** 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 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_OPENGL_H #define KWIN_SCENE_OPENGL_H #include "scene.h" #include "shadow.h" #include "kwinglutils.h" #include "decorations/decorationrenderer.h" #include "platformsupport/scenes/opengl/backend.h" namespace KWin { class LanczosFilter; class OpenGLBackend; class SyncManager; class SyncObject; class KWIN_EXPORT SceneOpenGL : public Scene { Q_OBJECT public: class EffectFrame; class Window; ~SceneOpenGL() override; bool initFailed() const override; bool hasPendingFlush() const override; qint64 paint(QRegion damage, ToplevelList windows) override; Scene::EffectFrame *createEffectFrame(EffectFrameImpl *frame) override; Shadow *createShadow(Toplevel *toplevel) override; void screenGeometryChanged(const QSize &size) override; OverlayWindow *overlayWindow() const override; bool usesOverlayWindow() const override; bool blocksForRetrace() const override; bool syncsToVBlank() const override; bool makeOpenGLContextCurrent() override; void doneOpenGLContextCurrent() override; Decoration::Renderer *createDecorationRenderer(Decoration::DecoratedClientImpl *impl) override; void triggerFence() override; virtual QMatrix4x4 projectionMatrix() const = 0; bool animationsSupported() const override; void insertWait(); void idle() override; bool debug() const { return m_debug; } void initDebugOutput(); /** * @brief Factory method to create a backend specific texture. * * @return :SceneOpenGL::Texture* */ SceneOpenGLTexture *createTexture(); OpenGLBackend *backend() const { return m_backend; } QVector openGLPlatformInterfaceExtensions() const override; static SceneOpenGL *createScene(QObject *parent); protected: SceneOpenGL(OpenGLBackend *backend, QObject *parent = nullptr); void paintBackground(QRegion region) override; void extendPaintRegion(QRegion ®ion, bool opaqueFullscreen) override; QMatrix4x4 transformation(int mask, const ScreenPaintData &data) const; void paintDesktop(int desktop, int mask, const QRegion ®ion, ScreenPaintData &data) override; void handleGraphicsReset(GLenum status); virtual void doPaintBackground(const QVector &vertices) = 0; virtual void updateProjectionMatrix() = 0; protected: bool init_ok; private: bool viewportLimitsMatched(const QSize &size) const; private: bool m_debug; OpenGLBackend *m_backend; SyncManager *m_syncManager; SyncObject *m_currentFence; }; class SceneOpenGL2 : public SceneOpenGL { Q_OBJECT public: explicit SceneOpenGL2(OpenGLBackend *backend, QObject *parent = nullptr); ~SceneOpenGL2() override; CompositingType compositingType() const override { return OpenGL2Compositing; } static bool supported(OpenGLBackend *backend); QMatrix4x4 projectionMatrix() const override { return m_projectionMatrix; } QMatrix4x4 screenProjectionMatrix() const override { return m_screenProjectionMatrix; } protected: void paintSimpleScreen(int mask, QRegion region) override; void paintGenericScreen(int mask, ScreenPaintData data) override; void doPaintBackground(const QVector< float >& vertices) override; Scene::Window *createWindow(Toplevel *t) override; void finalDrawWindow(EffectWindowImpl* w, int mask, QRegion region, WindowPaintData& data) override; void updateProjectionMatrix() override; void paintCursor() override; private: void performPaintWindow(EffectWindowImpl* w, int mask, QRegion region, WindowPaintData& data); QMatrix4x4 createProjectionMatrix() const; private: LanczosFilter *m_lanczosFilter; QScopedPointer m_cursorTexture; QMatrix4x4 m_projectionMatrix; QMatrix4x4 m_screenProjectionMatrix; GLuint vao; }; class SceneOpenGL::Window : public Scene::Window { public: ~Window() override; bool beginRenderWindow(int mask, const QRegion ®ion, WindowPaintData &data); void performPaint(int mask, QRegion region, WindowPaintData data) override = 0; void endRenderWindow(); bool bindTexture(); void setScene(SceneOpenGL *scene) { m_scene = scene; } protected: WindowPixmap* createWindowPixmap() override; Window(Toplevel* c); enum TextureType { Content, Decoration, Shadow }; QMatrix4x4 transformation(int mask, const WindowPaintData &data) const; GLTexture *getDecorationTexture() const; protected: SceneOpenGL *m_scene; bool m_hardwareClipping; }; class OpenGLWindowPixmap; class SceneOpenGL2Window : public SceneOpenGL::Window { public: enum Leaf { ShadowLeaf = 0, DecorationLeaf, ContentLeaf, PreviousContentLeaf, LeafCount }; struct LeafNode { LeafNode() - : texture(0), + : texture(nullptr), firstVertex(0), vertexCount(0), opacity(1.0), hasAlpha(false), coordinateType(UnnormalizedCoordinates) { } GLTexture *texture; int firstVertex; int vertexCount; float opacity; bool hasAlpha; TextureCoordinateType coordinateType; }; explicit SceneOpenGL2Window(Toplevel *c); ~SceneOpenGL2Window() override; protected: QMatrix4x4 modelViewProjectionMatrix(int mask, const WindowPaintData &data) const; QVector4D modulate(float opacity, float brightness) const; void setBlendEnabled(bool enabled); void setupLeafNodes(LeafNode *nodes, const WindowQuadList *quads, const WindowPaintData &data); void performPaint(int mask, QRegion region, WindowPaintData data) override; private: void renderSubSurface(GLShader *shader, const QMatrix4x4 &mvp, const QMatrix4x4 &windowMatrix, OpenGLWindowPixmap *pixmap, const QRegion ®ion, bool hardwareClipping); /** * Whether prepareStates enabled blending and restore states should disable again. */ bool m_blendingEnabled; }; class OpenGLWindowPixmap : public WindowPixmap { public: explicit OpenGLWindowPixmap(Scene::Window *window, SceneOpenGL *scene); ~OpenGLWindowPixmap() override; SceneOpenGLTexture *texture() const; bool bind(); bool isValid() const override; protected: WindowPixmap *createChild(const QPointer &subSurface) override; private: explicit OpenGLWindowPixmap(const QPointer &subSurface, WindowPixmap *parent, SceneOpenGL *scene); QScopedPointer m_texture; SceneOpenGL *m_scene; }; class SceneOpenGL::EffectFrame : public Scene::EffectFrame { public: EffectFrame(EffectFrameImpl* frame, SceneOpenGL *scene); ~EffectFrame() override; void free() override; void freeIconFrame() override; void freeTextFrame() override; void freeSelection() override; void render(QRegion region, double opacity, double frameOpacity) override; void crossFadeIcon() override; void crossFadeText() override; static void cleanup(); private: void updateTexture(); void updateTextTexture(); GLTexture *m_texture; GLTexture *m_textTexture; GLTexture *m_oldTextTexture; QPixmap *m_textPixmap; // need to keep the pixmap around to workaround some driver problems GLTexture *m_iconTexture; GLTexture *m_oldIconTexture; GLTexture *m_selectionTexture; GLVertexBuffer *m_unstyledVBO; SceneOpenGL *m_scene; static GLTexture* m_unstyledTexture; static QPixmap* m_unstyledPixmap; // need to keep the pixmap around to workaround some driver problems static void updateUnstyledTexture(); // Update OpenGL unstyled frame texture }; /** * @short OpenGL implementation of Shadow. * * This class extends Shadow by the Elements required for OpenGL rendering. * @author Martin Gräßlin */ class SceneOpenGLShadow : public Shadow { public: explicit SceneOpenGLShadow(Toplevel *toplevel); ~SceneOpenGLShadow() override; GLTexture *shadowTexture() { return m_texture.data(); } protected: void buildQuads() override; bool prepareBackend() override; private: QSharedPointer m_texture; }; class SceneOpenGLDecorationRenderer : public Decoration::Renderer { Q_OBJECT public: enum class DecorationPart : int { Left, Top, Right, Bottom, Count }; explicit SceneOpenGLDecorationRenderer(Decoration::DecoratedClientImpl *client); ~SceneOpenGLDecorationRenderer() override; void render() override; void reparent(Deleted *deleted) override; GLTexture *texture() { return m_texture.data(); } GLTexture *texture() const { return m_texture.data(); } private: void resizeTexture(); QScopedPointer m_texture; }; inline bool SceneOpenGL::hasPendingFlush() const { return m_backend->hasPendingFlush(); } inline bool SceneOpenGL::usesOverlayWindow() const { return m_backend->usesOverlayWindow(); } inline SceneOpenGLTexture* OpenGLWindowPixmap::texture() const { return m_texture.data(); } class KWIN_EXPORT OpenGLFactory : public SceneFactory { Q_OBJECT Q_INTERFACES(KWin::SceneFactory) Q_PLUGIN_METADATA(IID "org.kde.kwin.Scene" FILE "opengl.json") public: explicit OpenGLFactory(QObject *parent = nullptr); ~OpenGLFactory() override; Scene *create(QObject *parent = nullptr) const override; }; } // namespace #endif diff --git a/plugins/scenes/qpainter/scene_qpainter.cpp b/plugins/scenes/qpainter/scene_qpainter.cpp index bf462071e..0fffdfc39 100644 --- a/plugins/scenes/qpainter/scene_qpainter.cpp +++ b/plugins/scenes/qpainter/scene_qpainter.cpp @@ -1,878 +1,878 @@ /******************************************************************** 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 NULL; + 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; } // 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 (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/plugins/scenes/xrender/scene_xrender.cpp b/plugins/scenes/xrender/scene_xrender.cpp index 24ad72bb2..633cc0b1e 100644 --- a/plugins/scenes/xrender/scene_xrender.cpp +++ b/plugins/scenes/xrender/scene_xrender.cpp @@ -1,1331 +1,1331 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2006 Lubos Lunak Copyright (C) 2009 Fredrik Höglund 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_xrender.h" #include "utils.h" #ifdef KWIN_HAVE_XRENDER_COMPOSITING #include "logging.h" #include "toplevel.h" #include "client.h" #include "composite.h" #include "deleted.h" #include "effects.h" #include "main.h" #include "overlaywindow.h" #include "platform.h" #include "screens.h" #include "xcbutils.h" #include "kwinxrenderutils.h" #include "decorations/decoratedclient.h" #include #include #include #include namespace KWin { ScreenPaintData SceneXrender::screen_paint; #define DOUBLE_TO_FIXED(d) ((xcb_render_fixed_t) ((d) * 65536)) #define FIXED_TO_DOUBLE(f) ((double) ((f) / 65536.0)) //**************************************** // XRenderBackend //**************************************** XRenderBackend::XRenderBackend() : m_buffer(XCB_RENDER_PICTURE_NONE) , m_failed(false) { if (!Xcb::Extensions::self()->isRenderAvailable()) { setFailed("No XRender extension available"); return; } if (!Xcb::Extensions::self()->isFixesRegionAvailable()) { setFailed("No XFixes v3+ extension available"); return; } } XRenderBackend::~XRenderBackend() { if (m_buffer) { xcb_render_free_picture(connection(), m_buffer); } } OverlayWindow* XRenderBackend::overlayWindow() { - return NULL; + return nullptr; } void XRenderBackend::showOverlay() { } void XRenderBackend::setBuffer(xcb_render_picture_t buffer) { if (m_buffer != XCB_RENDER_PICTURE_NONE) { xcb_render_free_picture(connection(), m_buffer); } m_buffer = buffer; } void XRenderBackend::setFailed(const QString& reason) { qCCritical(KWIN_XRENDER) << "Creating the XRender backend failed: " << reason; m_failed = true; } void XRenderBackend::screenGeometryChanged(const QSize &size) { Q_UNUSED(size) } //**************************************** // X11XRenderBackend //**************************************** X11XRenderBackend::X11XRenderBackend() : XRenderBackend() , m_overlayWindow(kwinApp()->platform()->createOverlayWindow()) , m_front(XCB_RENDER_PICTURE_NONE) , m_format(0) { init(true); } X11XRenderBackend::~X11XRenderBackend() { if (m_front) { xcb_render_free_picture(connection(), m_front); } m_overlayWindow->destroy(); } OverlayWindow* X11XRenderBackend::overlayWindow() { return m_overlayWindow.data(); } void X11XRenderBackend::showOverlay() { if (m_overlayWindow->window()) // show the window only after the first pass, since m_overlayWindow->show(); // that pass may take long } void X11XRenderBackend::init(bool createOverlay) { if (m_front != XCB_RENDER_PICTURE_NONE) xcb_render_free_picture(connection(), m_front); bool haveOverlay = createOverlay ? m_overlayWindow->create() : (m_overlayWindow->window() != XCB_WINDOW_NONE); if (haveOverlay) { m_overlayWindow->setup(XCB_WINDOW_NONE); ScopedCPointer attribs(xcb_get_window_attributes_reply(connection(), - xcb_get_window_attributes_unchecked(connection(), m_overlayWindow->window()), NULL)); + xcb_get_window_attributes_unchecked(connection(), m_overlayWindow->window()), nullptr)); if (!attribs) { setFailed("Failed getting window attributes for overlay window"); return; } m_format = XRenderUtils::findPictFormat(attribs->visual); if (m_format == 0) { setFailed("Failed to find XRender format for overlay window"); return; } m_front = xcb_generate_id(connection()); - xcb_render_create_picture(connection(), m_front, m_overlayWindow->window(), m_format, 0, NULL); + xcb_render_create_picture(connection(), m_front, m_overlayWindow->window(), m_format, 0, nullptr); } else { // create XRender picture for the root window m_format = XRenderUtils::findPictFormat(defaultScreen()->root_visual); if (m_format == 0) { setFailed("Failed to find XRender format for root window"); return; // error } m_front = xcb_generate_id(connection()); const uint32_t values[] = {XCB_SUBWINDOW_MODE_INCLUDE_INFERIORS}; xcb_render_create_picture(connection(), m_front, rootWindow(), m_format, XCB_RENDER_CP_SUBWINDOW_MODE, values); } createBuffer(); } void X11XRenderBackend::createBuffer() { xcb_pixmap_t pixmap = xcb_generate_id(connection()); const auto displaySize = screens()->displaySize(); xcb_create_pixmap(connection(), Xcb::defaultDepth(), pixmap, rootWindow(), displaySize.width(), displaySize.height()); xcb_render_picture_t b = xcb_generate_id(connection()); - xcb_render_create_picture(connection(), b, pixmap, m_format, 0, NULL); + xcb_render_create_picture(connection(), b, pixmap, m_format, 0, nullptr); xcb_free_pixmap(connection(), pixmap); // The picture owns the pixmap now setBuffer(b); } void X11XRenderBackend::present(int mask, const QRegion &damage) { const auto displaySize = screens()->displaySize(); if (mask & Scene::PAINT_SCREEN_REGION) { // Use the damage region as the clip region for the root window XFixesRegion frontRegion(damage); xcb_xfixes_set_picture_clip_region(connection(), m_front, frontRegion, 0, 0); // copy composed buffer to the root window xcb_xfixes_set_picture_clip_region(connection(), buffer(), XCB_XFIXES_REGION_NONE, 0, 0); xcb_render_composite(connection(), XCB_RENDER_PICT_OP_SRC, buffer(), XCB_RENDER_PICTURE_NONE, m_front, 0, 0, 0, 0, 0, 0, displaySize.width(), displaySize.height()); xcb_xfixes_set_picture_clip_region(connection(), m_front, XCB_XFIXES_REGION_NONE, 0, 0); xcb_flush(connection()); } else { // copy composed buffer to the root window xcb_render_composite(connection(), XCB_RENDER_PICT_OP_SRC, buffer(), XCB_RENDER_PICTURE_NONE, m_front, 0, 0, 0, 0, 0, 0, displaySize.width(), displaySize.height()); xcb_flush(connection()); } } void X11XRenderBackend::screenGeometryChanged(const QSize &size) { Q_UNUSED(size) init(false); } bool X11XRenderBackend::usesOverlayWindow() const { return true; } //**************************************** // SceneXrender //**************************************** SceneXrender* SceneXrender::createScene(QObject *parent) { QScopedPointer backend; backend.reset(new X11XRenderBackend); if (backend->isFailed()) { - return NULL; + return nullptr; } return new SceneXrender(backend.take(), parent); } SceneXrender::SceneXrender(XRenderBackend *backend, QObject *parent) : Scene(parent) , m_backend(backend) { } SceneXrender::~SceneXrender() { SceneXrender::Window::cleanup(); SceneXrender::EffectFrame::cleanup(); } bool SceneXrender::initFailed() const { return false; } // the entry point for painting qint64 SceneXrender::paint(QRegion damage, ToplevelList toplevels) { QElapsedTimer renderTimer; renderTimer.start(); createStackingOrder(toplevels); int mask = 0; QRegion updateRegion, validRegion; paintScreen(&mask, damage, QRegion(), &updateRegion, &validRegion); m_backend->showOverlay(); m_backend->present(mask, updateRegion); // do cleanup clearStackingOrder(); return renderTimer.nsecsElapsed(); } void SceneXrender::paintGenericScreen(int mask, ScreenPaintData data) { screen_paint = data; // save, transformations will be done when painting windows Scene::paintGenericScreen(mask, data); } void SceneXrender::paintDesktop(int desktop, int mask, const QRegion ®ion, ScreenPaintData &data) { PaintClipper::push(region); KWin::Scene::paintDesktop(desktop, mask, region, data); PaintClipper::pop(region); } // fill the screen background void SceneXrender::paintBackground(QRegion region) { xcb_render_color_t col = { 0, 0, 0, 0xffff }; // black const QVector &rects = Xcb::regionToRects(region); xcb_render_fill_rectangles(connection(), XCB_RENDER_PICT_OP_SRC, xrenderBufferPicture(), col, rects.count(), rects.data()); } Scene::Window *SceneXrender::createWindow(Toplevel *toplevel) { return new Window(toplevel, this); } Scene::EffectFrame *SceneXrender::createEffectFrame(EffectFrameImpl *frame) { return new SceneXrender::EffectFrame(frame); } Shadow *SceneXrender::createShadow(Toplevel *toplevel) { return new SceneXRenderShadow(toplevel); } Decoration::Renderer *SceneXrender::createDecorationRenderer(Decoration::DecoratedClientImpl* client) { return new SceneXRenderDecorationRenderer(client); } //**************************************** // SceneXrender::Window //**************************************** -XRenderPicture *SceneXrender::Window::s_tempPicture = 0; +XRenderPicture *SceneXrender::Window::s_tempPicture = nullptr; QRect SceneXrender::Window::temp_visibleRect; XRenderPicture *SceneXrender::Window::s_fadeAlphaPicture = nullptr; SceneXrender::Window::Window(Toplevel* c, SceneXrender *scene) : Scene::Window(c) , m_scene(scene) , format(XRenderUtils::findPictFormat(c->visual())) { } SceneXrender::Window::~Window() { discardShape(); } void SceneXrender::Window::cleanup() { delete s_tempPicture; - s_tempPicture = NULL; + s_tempPicture = nullptr; delete s_fadeAlphaPicture; s_fadeAlphaPicture = nullptr; } // Maps window coordinates to screen coordinates QRect SceneXrender::Window::mapToScreen(int mask, const WindowPaintData &data, const QRect &rect) const { QRect r = rect; if (mask & PAINT_WINDOW_TRANSFORMED) { // Apply the window transformation r.moveTo(r.x() * data.xScale() + data.xTranslation(), r.y() * data.yScale() + data.yTranslation()); r.setWidth(r.width() * data.xScale()); r.setHeight(r.height() * data.yScale()); } // Move the rectangle to the screen position r.translate(x(), y()); if (mask & PAINT_SCREEN_TRANSFORMED) { // Apply the screen transformation r.moveTo(r.x() * screen_paint.xScale() + screen_paint.xTranslation(), r.y() * screen_paint.yScale() + screen_paint.yTranslation()); r.setWidth(r.width() * screen_paint.xScale()); r.setHeight(r.height() * screen_paint.yScale()); } return r; } // Maps window coordinates to screen coordinates QPoint SceneXrender::Window::mapToScreen(int mask, const WindowPaintData &data, const QPoint &point) const { QPoint pt = point; if (mask & PAINT_WINDOW_TRANSFORMED) { // Apply the window transformation pt.rx() = pt.x() * data.xScale() + data.xTranslation(); pt.ry() = pt.y() * data.yScale() + data.yTranslation(); } // Move the point to the screen position pt += QPoint(x(), y()); if (mask & PAINT_SCREEN_TRANSFORMED) { // Apply the screen transformation pt.rx() = pt.x() * screen_paint.xScale() + screen_paint.xTranslation(); pt.ry() = pt.y() * screen_paint.yScale() + screen_paint.yTranslation(); } return pt; } void SceneXrender::Window::prepareTempPixmap() { const QSize oldSize = temp_visibleRect.size(); temp_visibleRect = toplevel->visibleRect().translated(-toplevel->pos()); if (s_tempPicture && (oldSize.width() < temp_visibleRect.width() || oldSize.height() < temp_visibleRect.height())) { delete s_tempPicture; - s_tempPicture = NULL; + s_tempPicture = nullptr; scene_setXRenderOffscreenTarget(0); // invalidate, better crash than cause weird results for developers } if (!s_tempPicture) { xcb_pixmap_t pix = xcb_generate_id(connection()); xcb_create_pixmap(connection(), 32, pix, rootWindow(), temp_visibleRect.width(), temp_visibleRect.height()); s_tempPicture = new XRenderPicture(pix, 32); xcb_free_pixmap(connection(), pix); } const xcb_render_color_t transparent = {0, 0, 0, 0}; const xcb_rectangle_t rect = {0, 0, uint16_t(temp_visibleRect.width()), uint16_t(temp_visibleRect.height())}; xcb_render_fill_rectangles(connection(), XCB_RENDER_PICT_OP_SRC, *s_tempPicture, transparent, 1, &rect); } // paint the window void SceneXrender::Window::performPaint(int mask, QRegion region, WindowPaintData data) { setTransformedShape(QRegion()); // maybe nothing will be painted // check if there is something to paint bool opaque = isOpaque() && qFuzzyCompare(data.opacity(), 1.0); /* HACK: It seems this causes painting glitches, disable temporarily if (( mask & PAINT_WINDOW_OPAQUE ) ^ ( mask & PAINT_WINDOW_TRANSLUCENT )) { // We are only painting either opaque OR translucent windows, not both if ( mask & PAINT_WINDOW_OPAQUE && !opaque ) return; // Only painting opaque and window is translucent if ( mask & PAINT_WINDOW_TRANSLUCENT && opaque ) return; // Only painting translucent and window is opaque }*/ // Intersect the clip region with the rectangle the window occupies on the screen if (!(mask & (PAINT_WINDOW_TRANSFORMED | PAINT_SCREEN_TRANSFORMED))) region &= toplevel->visibleRect(); if (region.isEmpty()) return; XRenderWindowPixmap *pixmap = windowPixmap(); if (!pixmap || !pixmap->isValid()) { return; } xcb_render_picture_t pic = pixmap->picture(); if (pic == XCB_RENDER_PICTURE_NONE) // The render format can be null for GL and/or Xv visuals return; toplevel->resetDamage(); // set picture filter if (options->isXrenderSmoothScale()) { // only when forced, it's slow if (mask & PAINT_WINDOW_TRANSFORMED) filter = ImageFilterGood; else if (mask & PAINT_SCREEN_TRANSFORMED) filter = ImageFilterGood; else filter = ImageFilterFast; } else filter = ImageFilterFast; // do required transformations const QRect wr = mapToScreen(mask, data, QRect(0, 0, width(), height())); QRect cr = QRect(toplevel->clientPos(), toplevel->clientSize()); // Client rect (in the window) qreal xscale = 1; qreal yscale = 1; bool scaled = false; Client *client = dynamic_cast(toplevel); Deleted *deleted = dynamic_cast(toplevel); const QRect decorationRect = toplevel->decorationRect(); if (((client && !client->noBorder()) || (deleted && !deleted->noBorder())) && true) { // decorated client transformed_shape = decorationRect; if (toplevel->shape()) { // "xeyes" + decoration transformed_shape -= cr; transformed_shape += shape(); } } else { transformed_shape = shape(); } if (toplevel->hasShadow()) transformed_shape |= toplevel->shadow()->shadowRegion(); xcb_render_transform_t xform = { DOUBLE_TO_FIXED(1), DOUBLE_TO_FIXED(0), DOUBLE_TO_FIXED(0), DOUBLE_TO_FIXED(0), DOUBLE_TO_FIXED(1), DOUBLE_TO_FIXED(0), DOUBLE_TO_FIXED(0), DOUBLE_TO_FIXED(0), DOUBLE_TO_FIXED(1) }; static const xcb_render_transform_t identity = { DOUBLE_TO_FIXED(1), DOUBLE_TO_FIXED(0), DOUBLE_TO_FIXED(0), DOUBLE_TO_FIXED(0), DOUBLE_TO_FIXED(1), DOUBLE_TO_FIXED(0), DOUBLE_TO_FIXED(0), DOUBLE_TO_FIXED(0), DOUBLE_TO_FIXED(1) }; if (mask & PAINT_WINDOW_TRANSFORMED) { xscale = data.xScale(); yscale = data.yScale(); } if (mask & PAINT_SCREEN_TRANSFORMED) { xscale *= screen_paint.xScale(); yscale *= screen_paint.yScale(); } if (!qFuzzyCompare(xscale, 1.0) || !qFuzzyCompare(yscale, 1.0)) { scaled = true; xform.matrix11 = DOUBLE_TO_FIXED(1.0 / xscale); xform.matrix22 = DOUBLE_TO_FIXED(1.0 / yscale); // transform the shape for clipping in paintTransformedScreen() QVector rects; rects.reserve(transformed_shape.rectCount()); for (const QRect &rect : transformed_shape) { const QRect transformedRect( qRound(rect.x() * xscale), qRound(rect.y() * yscale), qRound(rect.width() * xscale), qRound(rect.height() * yscale) ); rects.append(transformedRect); } transformed_shape.setRects(rects.constData(), rects.count()); } transformed_shape.translate(mapToScreen(mask, data, QPoint(0, 0))); PaintClipper pcreg(region); // clip by the region to paint PaintClipper pc(transformed_shape); // clip by window's shape const bool wantShadow = m_shadow && !m_shadow->shadowRegion().isEmpty(); // In order to obtain a pixel perfect rescaling // we need to blit the window content togheter with // decorations in a temporary pixmap and scale // the temporary pixmap at the end. // We should do this only if there is scaling and // the window has border // This solves a number of glitches and on top of this // it optimizes painting quite a bit const bool blitInTempPixmap = xRenderOffscreen() || (data.crossFadeProgress() < 1.0 && !opaque) || (scaled && (wantShadow || (client && !client->noBorder()) || (deleted && !deleted->noBorder()))); xcb_render_picture_t renderTarget = m_scene->xrenderBufferPicture(); if (blitInTempPixmap) { if (scene_xRenderOffscreenTarget()) { temp_visibleRect = toplevel->visibleRect().translated(-toplevel->pos()); renderTarget = *scene_xRenderOffscreenTarget(); } else { prepareTempPixmap(); renderTarget = *s_tempPicture; } } else { xcb_render_set_picture_transform(connection(), pic, xform); if (filter == ImageFilterGood) { setPictureFilter(pic, KWin::Scene::ImageFilterGood); } //BEGIN OF STUPID RADEON HACK // This is needed to avoid hitting a fallback in the radeon driver. // The Render specification states that sampling pixels outside the // source picture results in alpha=0 pixels. This can be achieved by // setting the border color to transparent black, but since the border // color has the same format as the texture, it only works when the // texture has an alpha channel. So the driver falls back to software // when the repeat mode is RepeatNone, the picture has a non-identity // transformation matrix, and doesn't have an alpha channel. // Since we only scale the picture, we can work around this by setting // the repeat mode to RepeatPad. if (!window()->hasAlpha()) { const uint32_t values[] = {XCB_RENDER_REPEAT_PAD}; xcb_render_change_picture(connection(), pic, XCB_RENDER_CP_REPEAT, values); } //END OF STUPID RADEON HACK } #define MAP_RECT_TO_TARGET(_RECT_) \ if (blitInTempPixmap) _RECT_.translate(-temp_visibleRect.topLeft()); else _RECT_ = mapToScreen(mask, data, _RECT_) //BEGIN deco preparations bool noBorder = true; xcb_render_picture_t left = XCB_RENDER_PICTURE_NONE; xcb_render_picture_t top = XCB_RENDER_PICTURE_NONE; xcb_render_picture_t right = XCB_RENDER_PICTURE_NONE; xcb_render_picture_t bottom = XCB_RENDER_PICTURE_NONE; QRect dtr, dlr, drr, dbr; const SceneXRenderDecorationRenderer *renderer = nullptr; if (client) { if (client && !client->noBorder()) { if (client->isDecorated()) { SceneXRenderDecorationRenderer *r = static_cast(client->decoratedClient()->renderer()); if (r) { r->render(); renderer = r; } } noBorder = client->noBorder(); client->layoutDecorationRects(dlr, dtr, drr, dbr); } } if (deleted && !deleted->noBorder()) { renderer = static_cast(deleted->decorationRenderer()); noBorder = deleted->noBorder(); deleted->layoutDecorationRects(dlr, dtr, drr, dbr); } if (renderer) { left = renderer->picture(SceneXRenderDecorationRenderer::DecorationPart::Left); top = renderer->picture(SceneXRenderDecorationRenderer::DecorationPart::Top); right = renderer->picture(SceneXRenderDecorationRenderer::DecorationPart::Right); bottom = renderer->picture(SceneXRenderDecorationRenderer::DecorationPart::Bottom); } if (!noBorder) { MAP_RECT_TO_TARGET(dtr); MAP_RECT_TO_TARGET(dlr); MAP_RECT_TO_TARGET(drr); MAP_RECT_TO_TARGET(dbr); } //END deco preparations //BEGIN shadow preparations QRect stlr, str, strr, srr, sbrr, sbr, sblr, slr; SceneXRenderShadow* m_xrenderShadow = static_cast(m_shadow); if (wantShadow) { m_xrenderShadow->layoutShadowRects(str, strr, srr, sbrr, sbr, sblr, slr, stlr); MAP_RECT_TO_TARGET(stlr); MAP_RECT_TO_TARGET(str); MAP_RECT_TO_TARGET(strr); MAP_RECT_TO_TARGET(srr); MAP_RECT_TO_TARGET(sbrr); MAP_RECT_TO_TARGET(sbr); MAP_RECT_TO_TARGET(sblr); MAP_RECT_TO_TARGET(slr); } //BEGIN end preparations //BEGIN client preparations QRect dr = cr; if (blitInTempPixmap) { dr.translate(-temp_visibleRect.topLeft()); } else { dr = mapToScreen(mask, data, dr); // Destination rect if (scaled) { cr.moveLeft(cr.x() * xscale); cr.moveTop(cr.y() * yscale); } } const int clientRenderOp = (opaque || blitInTempPixmap) ? XCB_RENDER_PICT_OP_SRC : XCB_RENDER_PICT_OP_OVER; //END client preparations #undef MAP_RECT_TO_TARGET for (PaintClipper::Iterator iterator; !iterator.isDone(); iterator.next()) { #define RENDER_SHADOW_TILE(_TILE_, _RECT_) \ xcb_render_composite(connection(), XCB_RENDER_PICT_OP_OVER, m_xrenderShadow->picture(SceneXRenderShadow::ShadowElement##_TILE_), \ shadowAlpha, renderTarget, 0, 0, 0, 0, _RECT_.x(), _RECT_.y(), _RECT_.width(), _RECT_.height()) //shadow if (wantShadow) { xcb_render_picture_t shadowAlpha = XCB_RENDER_PICTURE_NONE; if (!opaque) { shadowAlpha = xRenderBlendPicture(data.opacity()); } RENDER_SHADOW_TILE(TopLeft, stlr); RENDER_SHADOW_TILE(Top, str); RENDER_SHADOW_TILE(TopRight, strr); RENDER_SHADOW_TILE(Left, slr); RENDER_SHADOW_TILE(Right, srr); RENDER_SHADOW_TILE(BottomLeft, sblr); RENDER_SHADOW_TILE(Bottom, sbr); RENDER_SHADOW_TILE(BottomRight, sbrr); } #undef RENDER_SHADOW_TILE // Paint the window contents if (!(client && client->isShade())) { xcb_render_picture_t clientAlpha = XCB_RENDER_PICTURE_NONE; if (!opaque) { clientAlpha = xRenderBlendPicture(data.opacity()); } xcb_render_composite(connection(), clientRenderOp, pic, clientAlpha, renderTarget, cr.x(), cr.y(), 0, 0, dr.x(), dr.y(), dr.width(), dr.height()); if (data.crossFadeProgress() < 1.0 && data.crossFadeProgress() > 0.0) { XRenderWindowPixmap *previous = previousWindowPixmap(); if (previous && previous != pixmap) { static xcb_render_color_t cFadeColor = {0, 0, 0, 0}; cFadeColor.alpha = uint16_t((1.0 - data.crossFadeProgress()) * 0xffff); if (!s_fadeAlphaPicture) { s_fadeAlphaPicture = new XRenderPicture(xRenderFill(cFadeColor)); } else { xcb_rectangle_t rect = {0, 0, 1, 1}; xcb_render_fill_rectangles(connection(), XCB_RENDER_PICT_OP_SRC, *s_fadeAlphaPicture, cFadeColor , 1, &rect); } if (previous->size() != pixmap->size()) { xcb_render_transform_t xform2 = { DOUBLE_TO_FIXED(FIXED_TO_DOUBLE(xform.matrix11) * previous->size().width() / pixmap->size().width()), DOUBLE_TO_FIXED(0), DOUBLE_TO_FIXED(0), DOUBLE_TO_FIXED(0), DOUBLE_TO_FIXED(FIXED_TO_DOUBLE(xform.matrix22) * previous->size().height() / pixmap->size().height()), DOUBLE_TO_FIXED(0), DOUBLE_TO_FIXED(0), DOUBLE_TO_FIXED(0), DOUBLE_TO_FIXED(1) }; xcb_render_set_picture_transform(connection(), previous->picture(), xform2); } xcb_render_composite(connection(), opaque ? XCB_RENDER_PICT_OP_OVER : XCB_RENDER_PICT_OP_ATOP, previous->picture(), *s_fadeAlphaPicture, renderTarget, cr.x(), cr.y(), 0, 0, dr.x(), dr.y(), dr.width(), dr.height()); if (previous->size() != pixmap->size()) { xcb_render_set_picture_transform(connection(), previous->picture(), identity); } } } if (!opaque) transformed_shape = QRegion(); } if (client || deleted) { if (!noBorder) { xcb_render_picture_t decorationAlpha = xRenderBlendPicture(data.opacity()); auto renderDeco = [decorationAlpha, renderTarget](xcb_render_picture_t deco, const QRect &rect) { if (deco == XCB_RENDER_PICTURE_NONE) { return; } xcb_render_composite(connection(), XCB_RENDER_PICT_OP_OVER, deco, decorationAlpha, renderTarget, 0, 0, 0, 0, rect.x(), rect.y(), rect.width(), rect.height()); }; renderDeco(top, dtr); renderDeco(left, dlr); renderDeco(right, drr); renderDeco(bottom, dbr); } } if (data.brightness() != 1.0) { // fake brightness change by overlaying black const float alpha = (1 - data.brightness()) * data.opacity(); xcb_rectangle_t rect; if (blitInTempPixmap) { rect.x = -temp_visibleRect.left(); rect.y = -temp_visibleRect.top(); rect.width = width(); rect.height = height(); } else { rect.x = wr.x(); rect.y = wr.y(); rect.width = wr.width(); rect.height = wr.height(); } xcb_render_fill_rectangles(connection(), XCB_RENDER_PICT_OP_OVER, renderTarget, preMultiply(data.brightness() < 1.0 ? QColor(0,0,0,255*alpha) : QColor(255,255,255,-alpha*255)), 1, &rect); } if (blitInTempPixmap) { const QRect r = mapToScreen(mask, data, temp_visibleRect); xcb_render_set_picture_transform(connection(), *s_tempPicture, xform); setPictureFilter(*s_tempPicture, filter); xcb_render_composite(connection(), XCB_RENDER_PICT_OP_OVER, *s_tempPicture, XCB_RENDER_PICTURE_NONE, m_scene->xrenderBufferPicture(), 0, 0, 0, 0, r.x(), r.y(), r.width(), r.height()); xcb_render_set_picture_transform(connection(), *s_tempPicture, identity); } } if (scaled && !blitInTempPixmap) { xcb_render_set_picture_transform(connection(), pic, identity); if (filter == ImageFilterGood) setPictureFilter(pic, KWin::Scene::ImageFilterFast); if (!window()->hasAlpha()) { const uint32_t values[] = {XCB_RENDER_REPEAT_NONE}; xcb_render_change_picture(connection(), pic, XCB_RENDER_CP_REPEAT, values); } } if (xRenderOffscreen()) scene_setXRenderOffscreenTarget(*s_tempPicture); } void SceneXrender::Window::setPictureFilter(xcb_render_picture_t pic, Scene::ImageFilterType filter) { QByteArray filterName; switch (filter) { case KWin::Scene::ImageFilterFast: filterName = QByteArray("fast"); break; case KWin::Scene::ImageFilterGood: filterName = QByteArray("good"); break; } - xcb_render_set_picture_filter(connection(), pic, filterName.length(), filterName.constData(), 0, NULL); + xcb_render_set_picture_filter(connection(), pic, filterName.length(), filterName.constData(), 0, nullptr); } WindowPixmap* SceneXrender::Window::createWindowPixmap() { return new XRenderWindowPixmap(this, format); } void SceneXrender::screenGeometryChanged(const QSize &size) { Scene::screenGeometryChanged(size); m_backend->screenGeometryChanged(size); } //**************************************** // XRenderWindowPixmap //**************************************** XRenderWindowPixmap::XRenderWindowPixmap(Scene::Window *window, xcb_render_pictformat_t format) : WindowPixmap(window) , m_picture(XCB_RENDER_PICTURE_NONE) , m_format(format) { } XRenderWindowPixmap::~XRenderWindowPixmap() { if (m_picture != XCB_RENDER_PICTURE_NONE) { xcb_render_free_picture(connection(), m_picture); } } void XRenderWindowPixmap::create() { if (isValid()) { return; } KWin::WindowPixmap::create(); if (!isValid()) { return; } m_picture = xcb_generate_id(connection()); - xcb_render_create_picture(connection(), m_picture, pixmap(), m_format, 0, NULL); + xcb_render_create_picture(connection(), m_picture, pixmap(), m_format, 0, nullptr); } //**************************************** // SceneXrender::EffectFrame //**************************************** -XRenderPicture *SceneXrender::EffectFrame::s_effectFrameCircle = NULL; +XRenderPicture *SceneXrender::EffectFrame::s_effectFrameCircle = nullptr; SceneXrender::EffectFrame::EffectFrame(EffectFrameImpl* frame) : Scene::EffectFrame(frame) { - m_picture = NULL; - m_textPicture = NULL; - m_iconPicture = NULL; - m_selectionPicture = NULL; + m_picture = nullptr; + m_textPicture = nullptr; + m_iconPicture = nullptr; + m_selectionPicture = nullptr; } SceneXrender::EffectFrame::~EffectFrame() { delete m_picture; delete m_textPicture; delete m_iconPicture; delete m_selectionPicture; } void SceneXrender::EffectFrame::cleanup() { delete s_effectFrameCircle; - s_effectFrameCircle = NULL; + s_effectFrameCircle = nullptr; } void SceneXrender::EffectFrame::free() { delete m_picture; - m_picture = NULL; + m_picture = nullptr; delete m_textPicture; - m_textPicture = NULL; + m_textPicture = nullptr; delete m_iconPicture; - m_iconPicture = NULL; + m_iconPicture = nullptr; delete m_selectionPicture; - m_selectionPicture = NULL; + m_selectionPicture = nullptr; } void SceneXrender::EffectFrame::freeIconFrame() { delete m_iconPicture; - m_iconPicture = NULL; + m_iconPicture = nullptr; } void SceneXrender::EffectFrame::freeTextFrame() { delete m_textPicture; - m_textPicture = NULL; + m_textPicture = nullptr; } void SceneXrender::EffectFrame::freeSelection() { delete m_selectionPicture; - m_selectionPicture = NULL; + m_selectionPicture = nullptr; } void SceneXrender::EffectFrame::crossFadeIcon() { // TODO: implement me } void SceneXrender::EffectFrame::crossFadeText() { // TODO: implement me } void SceneXrender::EffectFrame::render(QRegion region, double opacity, double frameOpacity) { Q_UNUSED(region) if (m_effectFrame->geometry().isEmpty()) { return; // Nothing to display } // Render the actual frame if (m_effectFrame->style() == EffectFrameUnstyled) { renderUnstyled(effects->xrenderBufferPicture(), m_effectFrame->geometry(), opacity * frameOpacity); } else if (m_effectFrame->style() == EffectFrameStyled) { if (!m_picture) { // Lazy creation updatePicture(); } if (m_picture) { 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); xcb_render_composite(connection(), XCB_RENDER_PICT_OP_OVER, *m_picture, XCB_RENDER_PICTURE_NONE, effects->xrenderBufferPicture(), 0, 0, 0, 0, geom.x(), geom.y(), geom.width(), geom.height()); } } if (!m_effectFrame->selection().isNull()) { if (!m_selectionPicture) { // Lazy creation const QPixmap pix = m_effectFrame->selectionFrame().framePixmap(); if (!pix.isNull()) // don't try if there's no content m_selectionPicture = new XRenderPicture(m_effectFrame->selectionFrame().framePixmap().toImage()); } if (m_selectionPicture) { const QRect geom = m_effectFrame->selection(); xcb_render_composite(connection(), XCB_RENDER_PICT_OP_OVER, *m_selectionPicture, XCB_RENDER_PICTURE_NONE, effects->xrenderBufferPicture(), 0, 0, 0, 0, geom.x(), geom.y(), geom.width(), geom.height()); } } XRenderPicture fill = xRenderBlendPicture(opacity); // 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); if (!m_iconPicture) // lazy creation m_iconPicture = new XRenderPicture(m_effectFrame->icon().pixmap(m_effectFrame->iconSize()).toImage()); QRect geom = QRect(topLeft, m_effectFrame->iconSize()); xcb_render_composite(connection(), XCB_RENDER_PICT_OP_OVER, *m_iconPicture, fill, effects->xrenderBufferPicture(), 0, 0, 0, 0, geom.x(), geom.y(), geom.width(), geom.height()); } // Render text if (!m_effectFrame->text().isEmpty()) { if (!m_textPicture) { // Lazy creation updateTextPicture(); } if (m_textPicture) { xcb_render_composite(connection(), XCB_RENDER_PICT_OP_OVER, *m_textPicture, fill, effects->xrenderBufferPicture(), 0, 0, 0, 0, m_effectFrame->geometry().x(), m_effectFrame->geometry().y(), m_effectFrame->geometry().width(), m_effectFrame->geometry().height()); } } } void SceneXrender::EffectFrame::renderUnstyled(xcb_render_picture_t pict, const QRect &rect, qreal opacity) { const int roundness = 5; const QRect area = rect.adjusted(-roundness, -roundness, roundness, roundness); xcb_rectangle_t rects[3]; // center rects[0].x = area.left(); rects[0].y = area.top() + roundness; rects[0].width = area.width(); rects[0].height = area.height() - roundness * 2; // top rects[1].x = area.left() + roundness; rects[1].y = area.top(); rects[1].width = area.width() - roundness * 2; rects[1].height = roundness; // bottom rects[2].x = area.left() + roundness; rects[2].y = area.top() + area.height() - roundness; rects[2].width = area.width() - roundness * 2; rects[2].height = roundness; xcb_render_color_t color = {0, 0, 0, uint16_t(opacity * 0xffff)}; xcb_render_fill_rectangles(connection(), XCB_RENDER_PICT_OP_OVER, pict, color, 3, rects); if (!s_effectFrameCircle) { // create the circle const int diameter = roundness * 2; xcb_pixmap_t pix = xcb_generate_id(connection()); xcb_create_pixmap(connection(), 32, pix, rootWindow(), diameter, diameter); s_effectFrameCircle = new XRenderPicture(pix, 32); xcb_free_pixmap(connection(), pix); // clear it with transparent xcb_rectangle_t xrect = {0, 0, diameter, diameter}; xcb_render_color_t tranparent = {0, 0, 0, 0}; xcb_render_fill_rectangles(connection(), XCB_RENDER_PICT_OP_SRC, *s_effectFrameCircle, tranparent, 1, &xrect); static const int num_segments = 80; static const qreal theta = 2 * M_PI / qreal(num_segments); static const qreal c = qCos(theta); //precalculate the sine and cosine static const qreal s = qSin(theta); qreal t; qreal x = roundness;//we start at angle = 0 qreal y = 0; QVector points; xcb_render_pointfix_t point; point.x = DOUBLE_TO_FIXED(roundness); point.y = DOUBLE_TO_FIXED(roundness); points << point; for (int ii = 0; ii <= num_segments; ++ii) { point.x = DOUBLE_TO_FIXED(x + roundness); point.y = DOUBLE_TO_FIXED(y + roundness); points << point; //apply the rotation matrix t = x; x = c * x - s * y; y = s * t + c * y; } XRenderPicture fill = xRenderFill(Qt::black); xcb_render_tri_fan(connection(), XCB_RENDER_PICT_OP_OVER, fill, *s_effectFrameCircle, 0, 0, 0, points.count(), points.constData()); } // TODO: merge alpha mask with SceneXrender::Window::alphaMask // alpha mask xcb_pixmap_t pix = xcb_generate_id(connection()); xcb_create_pixmap(connection(), 8, pix, rootWindow(), 1, 1); XRenderPicture alphaMask(pix, 8); xcb_free_pixmap(connection(), pix); const uint32_t values[] = {true}; xcb_render_change_picture(connection(), alphaMask, XCB_RENDER_CP_REPEAT, values); color.alpha = int(opacity * 0xffff); xcb_rectangle_t xrect = {0, 0, 1, 1}; xcb_render_fill_rectangles(connection(), XCB_RENDER_PICT_OP_SRC, alphaMask, color, 1, &xrect); // TODO: replace by lambda #define RENDER_CIRCLE(srcX, srcY, destX, destY) \ xcb_render_composite(connection(), XCB_RENDER_PICT_OP_OVER, *s_effectFrameCircle, alphaMask, \ pict, srcX, srcY, 0, 0, destX, destY, roundness, roundness) RENDER_CIRCLE(0, 0, area.left(), area.top()); RENDER_CIRCLE(0, roundness, area.left(), area.top() + area.height() - roundness); RENDER_CIRCLE(roundness, 0, area.left() + area.width() - roundness, area.top()); RENDER_CIRCLE(roundness, roundness, area.left() + area.width() - roundness, area.top() + area.height() - roundness); #undef RENDER_CIRCLE } void SceneXrender::EffectFrame::updatePicture() { delete m_picture; - m_picture = 0L; + m_picture = nullptr; if (m_effectFrame->style() == EffectFrameStyled) { const QPixmap pix = m_effectFrame->frame().framePixmap(); if (!pix.isNull()) m_picture = new XRenderPicture(pix.toImage()); } } void SceneXrender::EffectFrame::updateTextPicture() { // Mostly copied from SceneOpenGL::EffectFrame::updateTextTexture() above delete m_textPicture; - m_textPicture = 0L; + m_textPicture = 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->text()); text = metrics.elidedText(text, Qt::ElideRight, rect.width()); } QPixmap pixmap(m_effectFrame->geometry().size()); pixmap.fill(Qt::transparent); QPainter p(&pixmap); 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_textPicture = new XRenderPicture(pixmap.toImage()); } SceneXRenderShadow::SceneXRenderShadow(Toplevel *toplevel) :Shadow(toplevel) { for (int i=0; iclient(), static_cast(&AbstractClient::addRepaint)); for (int i = 0; i < int(DecorationPart::Count); ++i) { m_pixmaps[i] = XCB_PIXMAP_NONE; m_pictures[i] = nullptr; } } SceneXRenderDecorationRenderer::~SceneXRenderDecorationRenderer() { for (int i = 0; i < int(DecorationPart::Count); ++i) { if (m_pixmaps[i] != XCB_PIXMAP_NONE) { xcb_free_pixmap(connection(), m_pixmaps[i]); } delete m_pictures[i]; } if (m_gc != 0) { xcb_free_gc(connection(), m_gc); } } void SceneXRenderDecorationRenderer::render() { QRegion scheduled = getScheduled(); if (scheduled.isEmpty()) { return; } if (areImageSizesDirty()) { resizePixmaps(); resetImageSizesDirty(); scheduled = client()->client()->decorationRect(); } const QRect top(QPoint(0, 0), m_sizes[int(DecorationPart::Top)]); const QRect left(QPoint(0, top.height()), m_sizes[int(DecorationPart::Left)]); const QRect right(QPoint(top.width() - m_sizes[int(DecorationPart::Right)].width(), top.height()), m_sizes[int(DecorationPart::Right)]); const QRect bottom(QPoint(0, left.y() + left.height()), m_sizes[int(DecorationPart::Bottom)]); xcb_connection_t *c = connection(); if (m_gc == 0) { m_gc = xcb_generate_id(connection()); xcb_create_gc(c, m_gc, m_pixmaps[int(DecorationPart::Top)], 0, nullptr); } auto renderPart = [this, c](const QRect &geo, const QPoint &offset, int index) { if (!geo.isValid()) { return; } QImage image = renderToImage(geo); Q_ASSERT(image.devicePixelRatio() == 1); xcb_put_image(c, XCB_IMAGE_FORMAT_Z_PIXMAP, m_pixmaps[index], m_gc, image.width(), image.height(), geo.x() - offset.x(), geo.y() - offset.y(), 0, 32, image.sizeInBytes(), image.constBits()); }; const QRect geometry = scheduled.boundingRect(); renderPart(left.intersected(geometry), left.topLeft(), int(DecorationPart::Left)); renderPart(top.intersected(geometry), top.topLeft(), int(DecorationPart::Top)); renderPart(right.intersected(geometry), right.topLeft(), int(DecorationPart::Right)); renderPart(bottom.intersected(geometry), bottom.topLeft(), int(DecorationPart::Bottom)); xcb_flush(c); } void SceneXRenderDecorationRenderer::resizePixmaps() { QRect left, top, right, bottom; client()->client()->layoutDecorationRects(left, top, right, bottom); xcb_connection_t *c = connection(); auto checkAndCreate = [this, c](int border, const QRect &rect) { const QSize size = rect.size(); if (m_sizes[border] != size) { m_sizes[border] = size; if (m_pixmaps[border] != XCB_PIXMAP_NONE) { xcb_free_pixmap(c, m_pixmaps[border]); } delete m_pictures[border]; if (!size.isEmpty()) { m_pixmaps[border] = xcb_generate_id(connection()); xcb_create_pixmap(connection(), 32, m_pixmaps[border], rootWindow(), size.width(), size.height()); m_pictures[border] = new XRenderPicture(m_pixmaps[border], 32); } else { m_pixmaps[border] = XCB_PIXMAP_NONE; m_pictures[border] = nullptr; } } if (!m_pictures[border]) { return; } // fill transparent xcb_rectangle_t r = {0, 0, uint16_t(size.width()), uint16_t(size.height())}; xcb_render_fill_rectangles(connection(), XCB_RENDER_PICT_OP_SRC, *m_pictures[border], preMultiply(Qt::transparent), 1, &r); }; checkAndCreate(int(DecorationPart::Left), left); checkAndCreate(int(DecorationPart::Top), top); checkAndCreate(int(DecorationPart::Right), right); checkAndCreate(int(DecorationPart::Bottom), bottom); } xcb_render_picture_t SceneXRenderDecorationRenderer::picture(SceneXRenderDecorationRenderer::DecorationPart part) const { Q_ASSERT(part != DecorationPart::Count); XRenderPicture *picture = m_pictures[int(part)]; if (!picture) { return XCB_RENDER_PICTURE_NONE; } return *picture; } void SceneXRenderDecorationRenderer::reparent(Deleted *deleted) { render(); Renderer::reparent(deleted); } #undef DOUBLE_TO_FIXED #undef FIXED_TO_DOUBLE XRenderFactory::XRenderFactory(QObject *parent) : SceneFactory(parent) { } XRenderFactory::~XRenderFactory() = default; Scene *XRenderFactory::create(QObject *parent) const { auto s = SceneXrender::createScene(parent); if (s && s->initFailed()) { delete s; s = nullptr; } return s; } } // namespace #endif void KWin::SceneXrender::paintCursor() { } diff --git a/scene.cpp b/scene.cpp index 55c0a2d14..4d5a273b0 100644 --- a/scene.cpp +++ b/scene.cpp @@ -1,1159 +1,1159 @@ /******************************************************************** 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 = NULL; +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 = NULL; + 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(NULL) + , m_shadow(nullptr) , m_currentPixmap() , m_previousPixmap() , m_referencePixmapCounter(0) , disable_painting(0) , shape_valid(false) - , cached_quad_list(NULL) + , 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 != NULL && !force) + if (cached_quad_list != nullptr && !force) return *cached_quad_list; WindowQuadList ret; qreal scale = 1.0; if (toplevel->surface()) { scale = toplevel->surface()->scale(); } 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()) { 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 (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/screenedge.cpp b/screenedge.cpp index dce70ab81..4dd6adf50 100644 --- a/screenedge.cpp +++ b/screenedge.cpp @@ -1,1495 +1,1495 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2011 Arthur Arlt Copyright (C) 2013 Martin Gräßlin Since the functionality provided in this class has been moved from class Workspace, it is not clear who exactly has written the code. The list below contains the copyright holders of the class Workspace. 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 . *********************************************************************/ #include "screenedge.h" // KWin #include "gestures.h" #include #include "cursor.h" #include "main.h" #include "platform.h" #include "screens.h" #include "utils.h" #include #include "virtualdesktops.h" #ifdef KWIN_UNIT_TEST #include "plugins/platforms/x11/standalone/edge.h" #endif // DBus generated #include "screenlocker_interface.h" // frameworks #include // Qt #include #include #include #include #include #include #include #include #include namespace KWin { // Mouse should not move more than this many pixels static const int DISTANCE_RESET = 30; Edge::Edge(ScreenEdges *parent) : QObject(parent) , m_edges(parent) , m_border(ElectricNone) , m_action(ElectricActionNone) , m_reserved(0) , m_approaching(false) , m_lastApproachingFactor(0) , m_blocked(false) , m_pushBackBlocked(false) , m_client(nullptr) , m_gesture(new SwipeGesture(this)) { m_gesture->setMinimumFingerCount(1); m_gesture->setMaximumFingerCount(1); connect(m_gesture, &Gesture::triggered, this, [this] { stopApproaching(); if (m_client) { m_client->showOnScreenEdge(); unreserve(); return; } handleTouchAction(); handleTouchCallback(); }, Qt::QueuedConnection ); connect(m_gesture, &SwipeGesture::started, this, &Edge::startApproaching); connect(m_gesture, &SwipeGesture::cancelled, this, &Edge::stopApproaching); connect(m_gesture, &SwipeGesture::progress, this, [this] (qreal progress) { int factor = progress * 256.0f; if (m_lastApproachingFactor != factor) { m_lastApproachingFactor = factor; emit approaching(border(), m_lastApproachingFactor/256.0f, m_approachGeometry); } } ); connect(this, &Edge::activatesForTouchGestureChanged, this, [this] { if (isReserved()) { if (activatesForTouchGesture()) { m_edges->gestureRecognizer()->registerGesture(m_gesture); } else { m_edges->gestureRecognizer()->unregisterGesture(m_gesture); } } } ); } Edge::~Edge() { } void Edge::reserve() { m_reserved++; if (m_reserved == 1) { // got activated activate(); } } void Edge::reserve(QObject *object, const char *slot) { connect(object, SIGNAL(destroyed(QObject*)), SLOT(unreserve(QObject*))); m_callBacks.insert(object, QByteArray(slot)); reserve(); } void Edge::reserveTouchCallBack(QAction *action) { if (m_touchActions.contains(action)) { return; } connect(action, &QAction::destroyed, this, [this, action] { unreserveTouchCallBack(action); } ); m_touchActions << action; reserve(); } void Edge::unreserveTouchCallBack(QAction *action) { auto it = std::find_if(m_touchActions.begin(), m_touchActions.end(), [action] (QAction *a) { return a == action; }); if (it != m_touchActions.end()) { m_touchActions.erase(it); unreserve(); } } void Edge::unreserve() { m_reserved--; if (m_reserved == 0) { // got deactivated stopApproaching(); deactivate(); } } void Edge::unreserve(QObject *object) { if (m_callBacks.contains(object)) { m_callBacks.remove(object); disconnect(object, SIGNAL(destroyed(QObject*)), this, SLOT(unreserve(QObject*))); unreserve(); } } bool Edge::activatesForPointer() const { if (m_client) { return true; } if (m_edges->isDesktopSwitching()) { return true; } if (m_edges->isDesktopSwitchingMovingClients()) { auto c = Workspace::self()->moveResizeClient(); if (c && !c->isResize()) { return true; } } if (!m_callBacks.isEmpty()) { return true; } if (m_action != ElectricActionNone) { return true; } return false; } bool Edge::activatesForTouchGesture() const { if (!isScreenEdge()) { return false; } if (m_blocked) { return false; } if (m_client) { return true; } if (m_touchAction != ElectricActionNone) { return true; } if (!m_touchActions.isEmpty()) { return true; } return false; } bool Edge::triggersFor(const QPoint &cursorPos) const { if (isBlocked()) { return false; } if (!activatesForPointer()) { return false; } if (!m_geometry.contains(cursorPos)) { return false; } if (isLeft() && cursorPos.x() != m_geometry.x()) { return false; } if (isRight() && cursorPos.x() != (m_geometry.x() + m_geometry.width() -1)) { return false; } if (isTop() && cursorPos.y() != m_geometry.y()) { return false; } if (isBottom() && cursorPos.y() != (m_geometry.y() + m_geometry.height() -1)) { return false; } return true; } bool Edge::check(const QPoint &cursorPos, const QDateTime &triggerTime, bool forceNoPushBack) { if (!triggersFor(cursorPos)) { return false; } if (m_lastTrigger.isValid() && // still in cooldown m_lastTrigger.msecsTo(triggerTime) < edges()->reActivationThreshold() - edges()->timeThreshold()) { return false; } // no pushback so we have to activate at once bool directActivate = forceNoPushBack || edges()->cursorPushBackDistance().isNull() || m_client; if (directActivate || canActivate(cursorPos, triggerTime)) { markAsTriggered(cursorPos, triggerTime); handle(cursorPos); return true; } else { pushCursorBack(cursorPos); m_triggeredPoint = cursorPos; } return false; } void Edge::markAsTriggered(const QPoint &cursorPos, const QDateTime &triggerTime) { m_lastTrigger = triggerTime; m_lastReset = QDateTime(); // invalidate m_triggeredPoint = cursorPos; } bool Edge::canActivate(const QPoint &cursorPos, const QDateTime &triggerTime) { // we check whether either the timer has explicitly been invalidated (successful trigger) or is // bigger than the reactivation threshold (activation "aborted", usually due to moving away the cursor // from the corner after successful activation) // either condition means that "this is the first event in a new attempt" if (!m_lastReset.isValid() || m_lastReset.msecsTo(triggerTime) > edges()->reActivationThreshold()) { m_lastReset = triggerTime; return false; } if (m_lastTrigger.isValid() && m_lastTrigger.msecsTo(triggerTime) < edges()->reActivationThreshold() - edges()->timeThreshold()) { return false; } if (m_lastReset.msecsTo(triggerTime) < edges()->timeThreshold()) { return false; } // does the check on position make any sense at all? if ((cursorPos - m_triggeredPoint).manhattanLength() > DISTANCE_RESET) { return false; } return true; } void Edge::handle(const QPoint &cursorPos) { AbstractClient *movingClient = Workspace::self()->moveResizeClient(); if ((edges()->isDesktopSwitchingMovingClients() && movingClient && !movingClient->isResize()) || (edges()->isDesktopSwitching() && isScreenEdge())) { // always switch desktops in case: // moving a Client and option for switch on client move is enabled // or switch on screen edge is enabled switchDesktop(cursorPos); return; } if (movingClient) { // if we are moving a window we don't want to trigger the actions. This just results in // problems, e.g. Desktop Grid activated or screen locker activated which just cannot // work as we hold a grab. return; } if (m_client) { pushCursorBack(cursorPos); m_client->showOnScreenEdge(); unreserve(); return; } if (handlePointerAction() || handleByCallback()) { pushCursorBack(cursorPos); return; } if (edges()->isDesktopSwitching() && isCorner()) { // try again desktop switching for the corner switchDesktop(cursorPos); } } bool Edge::handleAction(ElectricBorderAction action) { switch (action) { case ElectricActionShowDesktop: { Workspace::self()->setShowingDesktop(!Workspace::self()->showingDesktop()); return true; } case ElectricActionLockScreen: { // Lock the screen OrgFreedesktopScreenSaverInterface interface(QStringLiteral("org.freedesktop.ScreenSaver"), QStringLiteral("/ScreenSaver"), QDBusConnection::sessionBus()); if (interface.isValid()) { interface.Lock(); } return true; } case ElectricActionKRunner: { // open krunner QDBusConnection::sessionBus().asyncCall( QDBusMessage::createMethodCall(QStringLiteral("org.kde.krunner"), QStringLiteral("/App"), QStringLiteral("org.kde.krunner.App"), QStringLiteral("display") ) ); return true; } case ElectricActionActivityManager: { // open activity manager QDBusConnection::sessionBus().asyncCall( QDBusMessage::createMethodCall(QStringLiteral("org.kde.plasmashell"), QStringLiteral("/PlasmaShell"), QStringLiteral("org.kde.PlasmaShell"), QStringLiteral("toggleActivityManager") ) ); return true; } case ElectricActionApplicationLauncher: { QDBusConnection::sessionBus().asyncCall( QDBusMessage::createMethodCall(QStringLiteral("org.kde.plasmashell"), QStringLiteral("/PlasmaShell"), QStringLiteral("org.kde.PlasmaShell"), QStringLiteral("activateLauncherMenu") ) ); return true; } default: return false; } } bool Edge::handleByCallback() { if (m_callBacks.isEmpty()) { return false; } for (QHash::iterator it = m_callBacks.begin(); it != m_callBacks.end(); ++it) { bool retVal = false; QMetaObject::invokeMethod(it.key(), it.value().constData(), Q_RETURN_ARG(bool, retVal), Q_ARG(ElectricBorder, m_border)); if (retVal) { return true; } } return false; } void Edge::handleTouchCallback() { if (m_touchActions.isEmpty()) { return; } m_touchActions.first()->trigger(); } void Edge::switchDesktop(const QPoint &cursorPos) { QPoint pos(cursorPos); VirtualDesktopManager *vds = VirtualDesktopManager::self(); const uint oldDesktop = vds->current(); uint desktop = oldDesktop; const int OFFSET = 2; if (isLeft()) { const uint interimDesktop = desktop; desktop = vds->toLeft(desktop, vds->isNavigationWrappingAround()); if (desktop != interimDesktop) pos.setX(screens()->size().width() - 1 - OFFSET); } else if (isRight()) { const uint interimDesktop = desktop; desktop = vds->toRight(desktop, vds->isNavigationWrappingAround()); if (desktop != interimDesktop) pos.setX(OFFSET); } if (isTop()) { const uint interimDesktop = desktop; desktop = vds->above(desktop, vds->isNavigationWrappingAround()); if (desktop != interimDesktop) pos.setY(screens()->size().height() - 1 - OFFSET); } else if (isBottom()) { const uint interimDesktop = desktop; desktop = vds->below(desktop, vds->isNavigationWrappingAround()); if (desktop != interimDesktop) pos.setY(OFFSET); } #ifndef KWIN_UNIT_TEST if (AbstractClient *c = Workspace::self()->moveResizeClient()) { if (c->rules()->checkDesktop(desktop) != int(desktop)) { // user attempts to move a client to another desktop where it is ruleforced to not be return; } } #endif vds->setCurrent(desktop); if (vds->current() != oldDesktop) { m_pushBackBlocked = true; Cursor::setPos(pos); QSharedPointer me(new QMetaObject::Connection); *me = QObject::connect(QCoreApplication::eventDispatcher(), &QAbstractEventDispatcher::aboutToBlock, this, [this, me](){ QObject::disconnect(*me); const_cast*>(&me)->reset(nullptr); m_pushBackBlocked = false; } ); } } void Edge::pushCursorBack(const QPoint &cursorPos) { if (m_pushBackBlocked) return; int x = cursorPos.x(); int y = cursorPos.y(); const QSize &distance = edges()->cursorPushBackDistance(); if (isLeft()) { x += distance.width(); } if (isRight()) { x -= distance.width(); } if (isTop()) { y += distance.height(); } if (isBottom()) { y -= distance.height(); } Cursor::setPos(x, y); } void Edge::setGeometry(const QRect &geometry) { if (m_geometry == geometry) { return; } m_geometry = geometry; int x = m_geometry.x(); int y = m_geometry.y(); int width = m_geometry.width(); int height = m_geometry.height(); const int size = m_edges->cornerOffset(); if (isCorner()) { if (isRight()) { x = x - size +1; } if (isBottom()) { y = y - size +1; } width = size; height = size; } else { if (isLeft()) { y += size + 1; width = size; height = height - size * 2; } else if (isRight()) { x = x - size + 1; y += size; width = size; height = height - size * 2; } else if (isTop()) { x += size; width = width - size * 2; height = size; } else if (isBottom()) { x += size; y = y - size +1; width = width - size * 2; height = size; } } m_approachGeometry = QRect(x, y, width, height); doGeometryUpdate(); if (isScreenEdge()) { m_gesture->setStartGeometry(m_geometry); m_gesture->setMinimumDelta(screens()->size(screens()->number(m_geometry.center())) * 0.2); } } void Edge::checkBlocking() { if (isCorner()) { return; } bool newValue = false; if (AbstractClient *client = Workspace::self()->activeClient()) { newValue = client->isFullScreen() && client->geometry().contains(m_geometry.center()); } if (newValue == m_blocked) { return; } const bool wasTouch = activatesForTouchGesture(); m_blocked = newValue; if (wasTouch != activatesForTouchGesture()) { emit activatesForTouchGestureChanged(); } doUpdateBlocking(); } void Edge::doUpdateBlocking() { } void Edge::doGeometryUpdate() { } void Edge::activate() { if (activatesForTouchGesture()) { m_edges->gestureRecognizer()->registerGesture(m_gesture); } doActivate(); } void Edge::doActivate() { } void Edge::deactivate() { m_edges->gestureRecognizer()->unregisterGesture(m_gesture); doDeactivate(); } void Edge::doDeactivate() { } void Edge::startApproaching() { if (m_approaching) { return; } m_approaching = true; doStartApproaching(); m_lastApproachingFactor = 0; emit approaching(border(), 0.0, m_approachGeometry); } void Edge::doStartApproaching() { } void Edge::stopApproaching() { if (!m_approaching) { return; } m_approaching = false; doStopApproaching(); m_lastApproachingFactor = 0; emit approaching(border(), 0.0, m_approachGeometry); } void Edge::doStopApproaching() { } void Edge::updateApproaching(const QPoint &point) { if (approachGeometry().contains(point)) { int factor = 0; const int edgeDistance = m_edges->cornerOffset(); auto cornerDistance = [=](const QPoint &corner) { return qMax(qAbs(corner.x() - point.x()), qAbs(corner.y() - point.y())); }; switch (border()) { case ElectricTopLeft: factor = (cornerDistance(approachGeometry().topLeft())<<8) / edgeDistance; break; case ElectricTopRight: factor = (cornerDistance(approachGeometry().topRight())<<8) / edgeDistance; break; case ElectricBottomRight: factor = (cornerDistance(approachGeometry().bottomRight())<<8) / edgeDistance; break; case ElectricBottomLeft: factor = (cornerDistance(approachGeometry().bottomLeft())<<8) / edgeDistance; break; case ElectricTop: factor = (qAbs(point.y() - approachGeometry().y())<<8) / edgeDistance; break; case ElectricRight: factor = (qAbs(point.x() - approachGeometry().right())<<8) / edgeDistance; break; case ElectricBottom: factor = (qAbs(point.y() - approachGeometry().bottom())<<8) / edgeDistance; break; case ElectricLeft: factor = (qAbs(point.x() - approachGeometry().x())<<8) / edgeDistance; break; default: break; } factor = 256 - factor; if (m_lastApproachingFactor != factor) { m_lastApproachingFactor = factor; emit approaching(border(), m_lastApproachingFactor/256.0f, m_approachGeometry); } } else { stopApproaching(); } } quint32 Edge::window() const { return 0; } quint32 Edge::approachWindow() const { return 0; } void Edge::setBorder(ElectricBorder border) { m_border = border; switch (m_border) { case ElectricTop: m_gesture->setDirection(SwipeGesture::Direction::Down); break; case ElectricRight: m_gesture->setDirection(SwipeGesture::Direction::Left); break; case ElectricBottom: m_gesture->setDirection(SwipeGesture::Direction::Up); break; case ElectricLeft: m_gesture->setDirection(SwipeGesture::Direction::Right); break; default: break; } } void Edge::setTouchAction(ElectricBorderAction action) { const bool wasTouch = activatesForTouchGesture(); m_touchAction = action; if (wasTouch != activatesForTouchGesture()) { emit activatesForTouchGestureChanged(); } } void Edge::setClient(AbstractClient *client) { const bool wasTouch = activatesForTouchGesture(); m_client = client; if (wasTouch != activatesForTouchGesture()) { emit activatesForTouchGestureChanged(); } } /********************************************************** * ScreenEdges *********************************************************/ KWIN_SINGLETON_FACTORY(ScreenEdges) ScreenEdges::ScreenEdges(QObject *parent) : QObject(parent) , m_desktopSwitching(false) , m_desktopSwitchingMovingClients(false) , m_timeThreshold(0) , m_reactivateThreshold(0) - , m_virtualDesktopLayout(0) + , m_virtualDesktopLayout(nullptr) , m_actionTopLeft(ElectricActionNone) , m_actionTop(ElectricActionNone) , m_actionTopRight(ElectricActionNone) , m_actionRight(ElectricActionNone) , m_actionBottomRight(ElectricActionNone) , m_actionBottom(ElectricActionNone) , m_actionBottomLeft(ElectricActionNone) , m_actionLeft(ElectricActionNone) , m_gestureRecognizer(new GestureRecognizer(this)) { QWidget w; m_cornerOffset = (w.physicalDpiX() + w.physicalDpiY() + 5) / 6; connect(workspace(), &Workspace::clientRemoved, this, &ScreenEdges::deleteEdgeForClient); } ScreenEdges::~ScreenEdges() { - s_self = NULL; + s_self = nullptr; } void ScreenEdges::init() { reconfigure(); updateLayout(); recreateEdges(); } static ElectricBorderAction electricBorderAction(const QString& name) { QString lowerName = name.toLower(); if (lowerName == QStringLiteral("showdesktop")) { return ElectricActionShowDesktop; } else if (lowerName == QStringLiteral("lockscreen")) { return ElectricActionLockScreen; } else if (lowerName == QLatin1String("krunner")) { return ElectricActionKRunner; } else if (lowerName == QLatin1String("activitymanager")) { return ElectricActionActivityManager; } else if (lowerName == QLatin1String("applicationlauncher")) { return ElectricActionApplicationLauncher; } return ElectricActionNone; } void ScreenEdges::reconfigure() { if (!m_config) { return; } // TODO: migrate settings to a group ScreenEdges KConfigGroup windowsConfig = m_config->group("Windows"); setTimeThreshold(windowsConfig.readEntry("ElectricBorderDelay", 150)); setReActivationThreshold(qMax(timeThreshold() + 50, windowsConfig.readEntry("ElectricBorderCooldown", 350))); int desktopSwitching = windowsConfig.readEntry("ElectricBorders", static_cast(ElectricDisabled)); if (desktopSwitching == ElectricDisabled) { setDesktopSwitching(false); setDesktopSwitchingMovingClients(false); } else if (desktopSwitching == ElectricMoveOnly) { setDesktopSwitching(false); setDesktopSwitchingMovingClients(true); } else if (desktopSwitching == ElectricAlways) { setDesktopSwitching(true); setDesktopSwitchingMovingClients(true); } const int pushBack = windowsConfig.readEntry("ElectricBorderPushbackPixels", 1); m_cursorPushBackDistance = QSize(pushBack, pushBack); KConfigGroup borderConfig = m_config->group("ElectricBorders"); setActionForBorder(ElectricTopLeft, &m_actionTopLeft, electricBorderAction(borderConfig.readEntry("TopLeft", "None"))); setActionForBorder(ElectricTop, &m_actionTop, electricBorderAction(borderConfig.readEntry("Top", "None"))); setActionForBorder(ElectricTopRight, &m_actionTopRight, electricBorderAction(borderConfig.readEntry("TopRight", "None"))); setActionForBorder(ElectricRight, &m_actionRight, electricBorderAction(borderConfig.readEntry("Right", "None"))); setActionForBorder(ElectricBottomRight, &m_actionBottomRight, electricBorderAction(borderConfig.readEntry("BottomRight", "None"))); setActionForBorder(ElectricBottom, &m_actionBottom, electricBorderAction(borderConfig.readEntry("Bottom", "None"))); setActionForBorder(ElectricBottomLeft, &m_actionBottomLeft, electricBorderAction(borderConfig.readEntry("BottomLeft", "None"))); setActionForBorder(ElectricLeft, &m_actionLeft, electricBorderAction(borderConfig.readEntry("Left", "None"))); borderConfig = m_config->group("TouchEdges"); setActionForTouchBorder(ElectricTop, electricBorderAction(borderConfig.readEntry("Top", "None"))); setActionForTouchBorder(ElectricRight, electricBorderAction(borderConfig.readEntry("Right", "None"))); setActionForTouchBorder(ElectricBottom, electricBorderAction(borderConfig.readEntry("Bottom", "None"))); setActionForTouchBorder(ElectricLeft, electricBorderAction(borderConfig.readEntry("Left", "None"))); } void ScreenEdges::setActionForBorder(ElectricBorder border, ElectricBorderAction *oldValue, ElectricBorderAction newValue) { if (*oldValue == newValue) { return; } if (*oldValue == ElectricActionNone) { // have to reserve for (auto it = m_edges.begin(); it != m_edges.end(); ++it) { if ((*it)->border() == border) { (*it)->reserve(); } } } if (newValue == ElectricActionNone) { // have to unreserve for (auto it = m_edges.begin(); it != m_edges.end(); ++it) { if ((*it)->border() == border) { (*it)->unreserve(); } } } *oldValue = newValue; // update action on all Edges for given border for (auto it = m_edges.begin(); it != m_edges.end(); ++it) { if ((*it)->border() == border) { (*it)->setAction(newValue); } } } void ScreenEdges::setActionForTouchBorder(ElectricBorder border, ElectricBorderAction newValue) { auto it = m_touchActions.find(border); ElectricBorderAction oldValue = ElectricActionNone; if (it != m_touchActions.end()) { oldValue = it.value(); } if (oldValue == newValue) { return; } if (oldValue == ElectricActionNone) { // have to reserve for (auto it = m_edges.begin(); it != m_edges.end(); ++it) { if ((*it)->border() == border) { (*it)->reserve(); } } } if (newValue == ElectricActionNone) { // have to unreserve for (auto it = m_edges.begin(); it != m_edges.end(); ++it) { if ((*it)->border() == border) { (*it)->unreserve(); } } m_touchActions.erase(it); } else { m_touchActions.insert(border, newValue); } // update action on all Edges for given border for (auto it = m_edges.begin(); it != m_edges.end(); ++it) { if ((*it)->border() == border) { (*it)->setTouchAction(newValue); } } } void ScreenEdges::updateLayout() { const QSize desktopMatrix = VirtualDesktopManager::self()->grid().size(); - Qt::Orientations newLayout = 0; + Qt::Orientations newLayout = nullptr; if (desktopMatrix.width() > 1) { newLayout |= Qt::Horizontal; } if (desktopMatrix.height() > 1) { newLayout |= Qt::Vertical; } if (newLayout == m_virtualDesktopLayout) { return; } if (isDesktopSwitching()) { reserveDesktopSwitching(false, m_virtualDesktopLayout); } m_virtualDesktopLayout = newLayout; if (isDesktopSwitching()) { reserveDesktopSwitching(true, m_virtualDesktopLayout); } } static bool isLeftScreen(const QRect &screen, const QRect &fullArea) { if (screens()->count() == 1) { return true; } if (screen.x() == fullArea.x()) { return true; } // the screen is also on the left in case of a vertical layout with a second screen // more to the left. In that case no screen ends left of screen's x coord for (int i=0; icount(); ++i) { const QRect otherGeo = screens()->geometry(i); if (otherGeo == screen) { // that's our screen to test continue; } if (otherGeo.x() + otherGeo.width() <= screen.x()) { // other screen is completely in the left return false; } } // did not find a screen left of our current screen, so it is the left most return true; } static bool isRightScreen(const QRect &screen, const QRect &fullArea) { if (screens()->count() == 1) { return true; } if (screen.x() + screen.width() == fullArea.x() + fullArea.width()) { return true; } // the screen is also on the right in case of a vertical layout with a second screen // more to the right. In that case no screen starts right of this screen for (int i=0; icount(); ++i) { const QRect otherGeo = screens()->geometry(i); if (otherGeo == screen) { // that's our screen to test continue; } if (otherGeo.x() >= screen.x() + screen.width()) { // other screen is completely in the right return false; } } // did not find a screen right of our current screen, so it is the right most return true; } static bool isTopScreen(const QRect &screen, const QRect &fullArea) { if (screens()->count() == 1) { return true; } if (screen.y() == fullArea.y()) { return true; } // the screen is also top most in case of a horizontal layout with a second screen // more to the top. In that case no screen ends above screen's y coord for (int i=0; icount(); ++i) { const QRect otherGeo = screens()->geometry(i); if (otherGeo == screen) { // that's our screen to test continue; } if (otherGeo.y() + otherGeo.height() <= screen.y()) { // other screen is completely above return false; } } // did not find a screen above our current screen, so it is the top most return true; } static bool isBottomScreen(const QRect &screen, const QRect &fullArea) { if (screens()->count() == 1) { return true; } if (screen.y() + screen.height() == fullArea.y() + fullArea.height()) { return true; } // the screen is also bottom most in case of a horizontal layout with a second screen // more below. In that case no screen starts below screen's y coord + height for (int i=0; icount(); ++i) { const QRect otherGeo = screens()->geometry(i); if (otherGeo == screen) { // that's our screen to test continue; } if (otherGeo.y() >= screen.y() + screen.height()) { // other screen is completely below return false; } } // did not find a screen below our current screen, so it is the bottom most return true; } void ScreenEdges::recreateEdges() { QList oldEdges(m_edges); m_edges.clear(); const QRect fullArea = screens()->geometry(); QRegion processedRegion; for (int i=0; icount(); ++i) { const QRegion screen = QRegion(screens()->geometry(i)).subtracted(processedRegion); processedRegion += screen; for (const QRect &screenPart : screen) { if (isLeftScreen(screenPart, fullArea)) { // left most screen createVerticalEdge(ElectricLeft, screenPart, fullArea); } if (isRightScreen(screenPart, fullArea)) { // right most screen createVerticalEdge(ElectricRight, screenPart, fullArea); } if (isTopScreen(screenPart, fullArea)) { // top most screen createHorizontalEdge(ElectricTop, screenPart, fullArea); } if (isBottomScreen(screenPart, fullArea)) { // bottom most screen createHorizontalEdge(ElectricBottom, screenPart, fullArea); } } } // copy over the effect/script reservations from the old edges for (auto it = m_edges.begin(); it != m_edges.end(); ++it) { Edge *edge = *it; for (auto oldIt = oldEdges.constBegin(); oldIt != oldEdges.constEnd(); ++oldIt) { Edge *oldEdge = *oldIt; if (oldEdge->client()) { // show the client again and don't recreate the edge oldEdge->client()->showOnScreenEdge(); continue; } if (oldEdge->border() != edge->border()) { continue; } const QHash &callbacks = oldEdge->callBacks(); for (QHash::const_iterator callback = callbacks.begin(); callback != callbacks.end(); ++callback) { edge->reserve(callback.key(), callback.value().constData()); } const auto touchCallBacks = oldEdge->touchCallBacks(); for (auto a : touchCallBacks) { edge->reserveTouchCallBack(a); } } } qDeleteAll(oldEdges); } void ScreenEdges::createVerticalEdge(ElectricBorder border, const QRect &screen, const QRect &fullArea) { if (border != ElectricRight && border != KWin::ElectricLeft) { return; } int y = screen.y(); int height = screen.height(); const int x = (border == ElectricLeft) ? screen.x() : screen.x() + screen.width() -1; if (isTopScreen(screen, fullArea)) { // also top most screen height -= m_cornerOffset; y += m_cornerOffset; // create top left/right edge const ElectricBorder edge = (border == ElectricLeft) ? ElectricTopLeft : ElectricTopRight; m_edges << createEdge(edge, x, screen.y(), 1, 1); } if (isBottomScreen(screen, fullArea)) { // also bottom most screen height -= m_cornerOffset; // create bottom left/right edge const ElectricBorder edge = (border == ElectricLeft) ? ElectricBottomLeft : ElectricBottomRight; m_edges << createEdge(edge, x, screen.y() + screen.height() -1, 1, 1); } // create border m_edges << createEdge(border, x, y, 1, height); } void ScreenEdges::createHorizontalEdge(ElectricBorder border, const QRect &screen, const QRect &fullArea) { if (border != ElectricTop && border != ElectricBottom) { return; } int x = screen.x(); int width = screen.width(); if (isLeftScreen(screen, fullArea)) { // also left most - adjust only x and width x += m_cornerOffset; width -= m_cornerOffset; } if (isRightScreen(screen, fullArea)) { // also right most edge width -= m_cornerOffset; } const int y = (border == ElectricTop) ? screen.y() : screen.y() + screen.height() - 1; m_edges << createEdge(border, x, y, width, 1); } Edge *ScreenEdges::createEdge(ElectricBorder border, int x, int y, int width, int height, bool createAction) { #ifdef KWIN_UNIT_TEST Edge *edge = new WindowBasedEdge(this); #else Edge *edge = kwinApp()->platform()->createScreenEdge(this); #endif edge->setBorder(border); edge->setGeometry(QRect(x, y, width, height)); if (createAction) { const ElectricBorderAction action = actionForEdge(edge); if (action != KWin::ElectricActionNone) { edge->reserve(); edge->setAction(action); } const ElectricBorderAction touchAction = actionForTouchEdge(edge); if (touchAction != KWin::ElectricActionNone) { edge->reserve(); edge->setTouchAction(touchAction); } } if (isDesktopSwitching()) { if (edge->isCorner()) { edge->reserve(); } else { if ((m_virtualDesktopLayout & Qt::Horizontal) && (edge->isLeft() || edge->isRight())) { edge->reserve(); } if ((m_virtualDesktopLayout & Qt::Vertical) && (edge->isTop() || edge->isBottom())) { edge->reserve(); } } } connect(edge, SIGNAL(approaching(ElectricBorder,qreal,QRect)), SIGNAL(approaching(ElectricBorder,qreal,QRect))); if (edge->isScreenEdge()) { connect(this, SIGNAL(checkBlocking()), edge, SLOT(checkBlocking())); } return edge; } ElectricBorderAction ScreenEdges::actionForEdge(Edge *edge) const { switch (edge->border()) { case ElectricTopLeft: return m_actionTopLeft; case ElectricTop: return m_actionTop; case ElectricTopRight: return m_actionTopRight; case ElectricRight: return m_actionRight; case ElectricBottomRight: return m_actionBottomRight; case ElectricBottom: return m_actionBottom; case ElectricBottomLeft: return m_actionBottomLeft; case ElectricLeft: return m_actionLeft; default: // fall through break; } return ElectricActionNone; } ElectricBorderAction ScreenEdges::actionForTouchEdge(Edge *edge) const { auto it = m_touchActions.find(edge->border()); if (it != m_touchActions.end()) { return it.value(); } return ElectricActionNone; } void ScreenEdges::reserveDesktopSwitching(bool isToReserve, Qt::Orientations o) { if (!o) return; for (auto it = m_edges.begin(); it != m_edges.end(); ++it) { Edge *edge = *it; if (edge->isCorner()) { isToReserve ? edge->reserve() : edge->unreserve(); } else { if ((m_virtualDesktopLayout & Qt::Horizontal) && (edge->isLeft() || edge->isRight())) { isToReserve ? edge->reserve() : edge->unreserve(); } if ((m_virtualDesktopLayout & Qt::Vertical) && (edge->isTop() || edge->isBottom())) { isToReserve ? edge->reserve() : edge->unreserve(); } } } } void ScreenEdges::reserve(ElectricBorder border, QObject *object, const char *slot) { for (auto it = m_edges.begin(); it != m_edges.end(); ++it) { if ((*it)->border() == border) { (*it)->reserve(object, slot); } } } void ScreenEdges::unreserve(ElectricBorder border, QObject *object) { for (auto it = m_edges.begin(); it != m_edges.end(); ++it) { if ((*it)->border() == border) { (*it)->unreserve(object); } } } void ScreenEdges::reserve(AbstractClient *client, ElectricBorder border) { bool hadBorder = false; auto it = m_edges.begin(); while (it != m_edges.end()) { if ((*it)->client() == client) { hadBorder = true; delete *it; it = m_edges.erase(it); } else { it++; } } if (border != ElectricNone) { createEdgeForClient(client, border); } else { if (hadBorder) // show again client->showOnScreenEdge(); } } void ScreenEdges::reserveTouch(ElectricBorder border, QAction *action) { for (auto it = m_edges.begin(); it != m_edges.end(); ++it) { if ((*it)->border() == border) { (*it)->reserveTouchCallBack(action); } } } void ScreenEdges::unreserveTouch(ElectricBorder border, QAction *action) { for (auto it = m_edges.begin(); it != m_edges.end(); ++it) { if ((*it)->border() == border) { (*it)->unreserveTouchCallBack(action); } } } void ScreenEdges::createEdgeForClient(AbstractClient *client, ElectricBorder border) { int y = 0; int x = 0; int width = 0; int height = 0; const QRect geo = client->geometry(); const QRect fullArea = workspace()->clientArea(FullArea, 0, 1); for (int i = 0; i < screens()->count(); ++i) { const QRect screen = screens()->geometry(i); if (!screen.contains(geo)) { // ignoring Clients having a geometry overlapping with multiple screens // this would make the code more complex. If it's needed in future it can be added continue; } const bool bordersTop = (screen.y() == geo.y()); const bool bordersLeft = (screen.x() == geo.x()); const bool bordersBottom = (screen.y() + screen.height() == geo.y() + geo.height()); const bool bordersRight = (screen.x() + screen.width() == geo.x() + geo.width()); if (bordersTop && border == ElectricTop) { if (!isTopScreen(screen, fullArea)) { continue; } y = geo.y(); x = geo.x(); height = 1; width = geo.width(); break; } if (bordersBottom && border == ElectricBottom) { if (!isBottomScreen(screen, fullArea)) { continue; } y = geo.y() + geo.height() - 1; x = geo.x(); height = 1; width = geo.width(); break; } if (bordersLeft && border == ElectricLeft) { if (!isLeftScreen(screen, fullArea)) { continue; } x = geo.x(); y = geo.y(); width = 1; height = geo.height(); break; } if (bordersRight && border == ElectricRight) { if (!isRightScreen(screen, fullArea)) { continue; } x = geo.x() + geo.width() - 1; y = geo.y(); width = 1; height = geo.height(); break; } } if (width > 0 && height > 0) { Edge *edge = createEdge(border, x, y, width, height, false); edge->setClient(client); m_edges.append(edge); edge->reserve(); } else { // we could not create an edge window, so don't allow the window to hide client->showOnScreenEdge(); } } void ScreenEdges::deleteEdgeForClient(AbstractClient* c) { auto it = m_edges.begin(); while (it != m_edges.end()) { if ((*it)->client() == c) { delete *it; it = m_edges.erase(it); } else { it++; } } } void ScreenEdges::check(const QPoint &pos, const QDateTime &now, bool forceNoPushBack) { bool activatedForClient = false; for (auto it = m_edges.begin(); it != m_edges.end(); ++it) { if (!(*it)->isReserved()) { continue; } if (!(*it)->activatesForPointer()) { continue; } if ((*it)->approachGeometry().contains(pos)) { (*it)->startApproaching(); } if ((*it)->client() != nullptr && activatedForClient) { (*it)->markAsTriggered(pos, now); continue; } if ((*it)->check(pos, now, forceNoPushBack)) { if ((*it)->client()) { activatedForClient = true; } } } } bool ScreenEdges::isEntered(QMouseEvent *event) { if (event->type() != QEvent::MouseMove) { return false; } bool activated = false; bool activatedForClient = false; for (auto it = m_edges.begin(); it != m_edges.end(); ++it) { Edge *edge = *it; if (!edge->isReserved()) { continue; } if (!edge->activatesForPointer()) { continue; } if (edge->approachGeometry().contains(event->globalPos())) { if (!edge->isApproaching()) { edge->startApproaching(); } else { edge->updateApproaching(event->globalPos()); } } else { if (edge->isApproaching()) { edge->stopApproaching(); } } if (edge->geometry().contains(event->globalPos())) { if (edge->check(event->globalPos(), QDateTime::fromMSecsSinceEpoch(event->timestamp()))) { if (edge->client()) { activatedForClient = true; } } } } if (activatedForClient) { for (auto it = m_edges.constBegin(); it != m_edges.constEnd(); ++it) { if ((*it)->client()) { (*it)->markAsTriggered(event->globalPos(), QDateTime::fromMSecsSinceEpoch(event->timestamp())); } } } return activated; } bool ScreenEdges::handleEnterNotifiy(xcb_window_t window, const QPoint &point, const QDateTime ×tamp) { bool activated = false; bool activatedForClient = false; for (auto it = m_edges.begin(); it != m_edges.end(); ++it) { Edge *edge = *it; if (!edge || edge->window() == XCB_WINDOW_NONE) { continue; } if (!edge->isReserved()) { continue; } if (!edge->activatesForPointer()) { continue; } if (edge->window() == window) { if (edge->check(point, timestamp)) { if ((*it)->client()) { activatedForClient = true; } } activated = true; break; } if (edge->approachWindow() == window) { edge->startApproaching(); // TODO: if it's a corner, it should also trigger for other windows return true; } } if (activatedForClient) { for (auto it = m_edges.constBegin(); it != m_edges.constEnd(); ++it) { if ((*it)->client()) { (*it)->markAsTriggered(point, timestamp); } } } return activated; } bool ScreenEdges::handleDndNotify(xcb_window_t window, const QPoint &point) { for (auto it = m_edges.begin(); it != m_edges.end(); ++it) { Edge *edge = *it; if (!edge || edge->window() == XCB_WINDOW_NONE) { continue; } if (edge->isReserved() && edge->window() == window) { updateXTime(); edge->check(point, QDateTime::fromMSecsSinceEpoch(xTime()), true); return true; } } return false; } void ScreenEdges::ensureOnTop() { Xcb::restackWindowsWithRaise(windows()); } QVector< xcb_window_t > ScreenEdges::windows() const { QVector wins; for (auto it = m_edges.constBegin(); it != m_edges.constEnd(); ++it) { Edge *edge = *it; xcb_window_t w = edge->window(); if (w != XCB_WINDOW_NONE) { wins.append(w); } // TODO: lambda w = edge->approachWindow(); if (w != XCB_WINDOW_NONE) { wins.append(w); } } return wins; } } //namespace diff --git a/screens.cpp b/screens.cpp index e0cbb9d61..7b4e40415 100644 --- a/screens.cpp +++ b/screens.cpp @@ -1,249 +1,249 @@ /******************************************************************** 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 "screens.h" #include #include #include "cursor.h" #include "orientation_sensor.h" #include "utils.h" #include "settings.h" #include #include #include "platform.h" #include "wayland_server.h" #ifdef KWIN_UNIT_TEST #include #endif namespace KWin { Screens *Screens::s_self = nullptr; Screens *Screens::create(QObject *parent) { Q_ASSERT(!s_self); #ifdef KWIN_UNIT_TEST s_self = new MockScreens(parent); #else s_self = kwinApp()->platform()->createScreens(parent); #endif Q_ASSERT(s_self); s_self->init(); return s_self; } Screens::Screens(QObject *parent) : QObject(parent) , m_count(0) , m_current(0) , m_currentFollowsMouse(false) , m_changedTimer(new QTimer(this)) , m_orientationSensor(new OrientationSensor(this)) , m_maxScale(1.0) { connect(this, &Screens::changed, this, [this] { int internalIndex = -1; for (int i = 0; i < m_count; i++) { if (isInternal(i)) { internalIndex = i; break; } } m_orientationSensor->setEnabled(internalIndex != -1 && supportsTransformations(internalIndex)); } ); } Screens::~Screens() { - s_self = NULL; + s_self = nullptr; } void Screens::init() { m_changedTimer->setSingleShot(true); m_changedTimer->setInterval(100); connect(m_changedTimer, SIGNAL(timeout()), SLOT(updateCount())); connect(m_changedTimer, SIGNAL(timeout()), SIGNAL(changed())); connect(this, &Screens::countChanged, this, &Screens::changed, Qt::QueuedConnection); connect(this, &Screens::changed, this, &Screens::updateSize); connect(this, &Screens::sizeChanged, this, &Screens::geometryChanged); Settings settings; settings.setDefaults(); m_currentFollowsMouse = settings.activeMouseScreen(); } QString Screens::name(int screen) const { Q_UNUSED(screen) qCWarning(KWIN_CORE, "%s::name(int screen) is a stub, please reimplement it!", metaObject()->className()); return QLatin1String("DUMMY"); } float Screens::refreshRate(int screen) const { Q_UNUSED(screen) qCWarning(KWIN_CORE, "%s::refreshRate(int screen) is a stub, please reimplement it!", metaObject()->className()); return 60.0f; } qreal Screens::maxScale() const { return m_maxScale; } qreal Screens::scale(int screen) const { Q_UNUSED(screen) return 1; } void Screens::reconfigure() { if (!m_config) { return; } Settings settings(m_config); settings.read(); setCurrentFollowsMouse(settings.activeMouseScreen()); } void Screens::updateSize() { QRect bounding; qreal maxScale = 1.0; for (int i = 0; i < count(); ++i) { bounding = bounding.united(geometry(i)); maxScale = qMax(maxScale, scale(i)); } if (m_boundingSize != bounding.size()) { m_boundingSize = bounding.size(); emit sizeChanged(); } if (!qFuzzyCompare(m_maxScale, maxScale)) { m_maxScale = maxScale; emit maxScaleChanged(); } } void Screens::setCount(int count) { if (m_count == count) { return; } const int previous = m_count; m_count = count; emit countChanged(previous, count); } void Screens::setCurrent(int current) { if (m_current == current) { return; } m_current = current; emit currentChanged(); } void Screens::setCurrent(const QPoint &pos) { setCurrent(number(pos)); } void Screens::setCurrent(const AbstractClient *c) { if (!c->isActive()) { return; } if (!c->isOnScreen(m_current)) { setCurrent(c->screen()); } } void Screens::setCurrentFollowsMouse(bool follows) { if (m_currentFollowsMouse == follows) { return; } m_currentFollowsMouse = follows; } int Screens::current() const { if (m_currentFollowsMouse) { return number(Cursor::pos()); } AbstractClient *client = Workspace::self()->activeClient(); if (client && !client->isOnScreen(m_current)) { return client->screen(); } return m_current; } int Screens::intersecting(const QRect &r) const { int cnt = 0; for (int i = 0; i < count(); ++i) { if (geometry(i).intersects(r)) { ++cnt; } } return cnt; } QSize Screens::displaySize() const { return size(); } QSizeF Screens::physicalSize(int screen) const { return QSizeF(size(screen)) / 3.8; } bool Screens::isInternal(int screen) const { Q_UNUSED(screen) return false; } bool Screens::supportsTransformations(int screen) const { Q_UNUSED(screen) return false; } Qt::ScreenOrientation Screens::orientation(int screen) const { Q_UNUSED(screen) return Qt::PrimaryOrientation; } void Screens::setConfig(KSharedConfig::Ptr config) { m_config = config; if (m_orientationSensor) { m_orientationSensor->setConfig(config); } } } // namespace diff --git a/scripting/dbuscall.h b/scripting/dbuscall.h index 7b9391251..53135ec08 100644 --- a/scripting/dbuscall.h +++ b/scripting/dbuscall.h @@ -1,147 +1,147 @@ /******************************************************************** 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 . *********************************************************************/ #ifndef KWIN_DBUSCALL_H #define KWIN_DBUSCALL_H #include #include namespace KWin { /** * @brief Qml export for providing a wrapper for sending a message over the DBus * session bus. * * Allows to setup the connection arguments just like in QDBusMessage and supports * adding arguments to the call. To invoke the message use the slot @ref call. * * If the call succeeds the signal @ref finished is emitted, if the call fails * the signal @ref failed is emitted. * * Note: the DBusCall always uses the session bus and performs an async call. * * Example on how to use in Qml: * @code * DBusCall { * id: dbus * service: "org.kde.KWin" * path: "/KWin" * method: "nextDesktop" * Component.onCompleted: dbus.call() * } * @endcode * * Example with arguments: * @code * DBusCall { * id: dbus * service: "org.kde.KWin" * path: "/KWin" * method: "setCurrentDesktop" * arguments: [1] * Component.onCompleted: dbus.call() * } * @endcode * * Example with a callback: * @code * DBusCall { * id: dbus * service: "org.kde.KWin" * path: "/KWin" * method: "currentDesktop" * onFinished: console.log(returnValue[0]) * } * @endcode */ class DBusCall : public QObject { Q_OBJECT Q_PROPERTY(QString service READ service WRITE setService NOTIFY serviceChanged) Q_PROPERTY(QString path READ path WRITE setPath NOTIFY pathChanged) Q_PROPERTY(QString dbusInterface READ interface WRITE setInterface NOTIFY interfaceChanged) Q_PROPERTY(QString method READ method WRITE setMethod NOTIFY methodChanged) Q_PROPERTY(QVariantList arguments READ arguments WRITE setArguments NOTIFY argumentsChanged) public: - explicit DBusCall(QObject* parent = 0); + explicit DBusCall(QObject* parent = nullptr); ~DBusCall() override; const QString &service() const; const QString &path() const; const QString &interface() const; const QString &method() const; const QVariantList &arguments() const; public Q_SLOTS: void call(); void setService(const QString &service); void setPath(const QString &path); void setInterface(const QString &interface); void setMethod(const QString &method); void setArguments(const QVariantList &arguments); Q_SIGNALS: void finished(QVariantList returnValue); void failed(); void serviceChanged(); void pathChanged(); void interfaceChanged(); void methodChanged(); void argumentsChanged(); private: QString m_service; QString m_path; QString m_interface; QString m_method; QVariantList m_arguments; }; #define GENERIC_WRAPPER(type, name, upperName) \ inline type DBusCall::name() const \ { \ return m_##name; \ }\ inline void DBusCall::set##upperName(type name) \ {\ if (m_##name == name) { \ return; \ } \ m_##name = name; \ emit name##Changed(); \ } #define WRAPPER(name, upperName) \ GENERIC_WRAPPER(const QString&, name, upperName) WRAPPER(interface, Interface) WRAPPER(method, Method) WRAPPER(path, Path) WRAPPER(service, Service) GENERIC_WRAPPER(const QVariantList &, arguments, Arguments) #undef WRAPPER #undef GENERIC_WRAPPER } // KWin #endif // KWIN_SCRIPTING_DBUSCALL_H diff --git a/scripting/screenedgeitem.h b/scripting/screenedgeitem.h index c5e10e85b..64bbf16c3 100644 --- a/scripting/screenedgeitem.h +++ b/scripting/screenedgeitem.h @@ -1,126 +1,126 @@ /******************************************************************** 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 . *********************************************************************/ #ifndef KWIN_SCREENEDGEITEM_H #define KWIN_SCREENEDGEITEM_H #include #include class QAction; namespace KWin { /** * @brief Qml export for reserving a Screen Edge. * * The edge is controlled by the @c enabled property and the @c edge * property. If the edge is enabled and gets triggered the @ref activated() * signal gets emitted. * * Example usage: * @code * ScreenEdgeItem { * edge: ScreenEdgeItem.LeftEdge * onActivated: doSomething() * } * @endcode */ class ScreenEdgeItem : public QObject { Q_OBJECT Q_ENUMS(Edge) Q_ENUMS(Mode) /** * @brief Whether the edge is currently enabled, that is reserved. Default value is @c true. */ Q_PROPERTY(bool enabled READ isEnabled WRITE setEnabled NOTIFY enabledChanged) /** * @brief Which of the screen edges is to be reserved. Default value is @c NoEdge. */ Q_PROPERTY(Edge edge READ edge WRITE setEdge NOTIFY edgeChanged) /** * @brief The operation mode for this edge. Default value is @c Mode::Pointer */ Q_PROPERTY(Mode mode READ mode WRITE setMode NOTIFY modeChanged) public: enum Edge { TopEdge, TopRightEdge, RightEdge, BottomRightEdge, BottomEdge, BottomLeftEdge, LeftEdge, TopLeftEdge, EDGE_COUNT, NoEdge }; /** * Enum describing the operation modes of the edge. */ enum class Mode { Pointer, Touch }; - explicit ScreenEdgeItem(QObject *parent = 0); + explicit ScreenEdgeItem(QObject *parent = nullptr); ~ScreenEdgeItem() override; bool isEnabled() const; Edge edge() const; Mode mode() const { return m_mode; } public Q_SLOTS: void setEnabled(bool enabled); void setEdge(Edge edge); void setMode(Mode mode); Q_SIGNALS: void enabledChanged(); void edgeChanged(); void modeChanged(); void activated(); private Q_SLOTS: bool borderActivated(ElectricBorder edge); private: void enableEdge(); void disableEdge(); bool m_enabled; Edge m_edge; Mode m_mode = Mode::Pointer; QAction *m_action; }; inline bool ScreenEdgeItem::isEnabled() const { return m_enabled; } inline ScreenEdgeItem::Edge ScreenEdgeItem::edge() const { return m_edge; } } // namespace KWin #endif // KWIN_SCREENEDGEITEM_H diff --git a/scripting/workspace_wrapper.cpp b/scripting/workspace_wrapper.cpp index be48d6e9d..a9844e7bc 100644 --- a/scripting/workspace_wrapper.cpp +++ b/scripting/workspace_wrapper.cpp @@ -1,378 +1,378 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2010 Rohan Prabhu Copyright (C) 2011, 2012 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 "workspace_wrapper.h" #include "../client.h" #include "../outline.h" #include "../screens.h" #include "../shell_client.h" #include "../virtualdesktops.h" #include "../wayland_server.h" #include "../workspace.h" #ifdef KWIN_BUILD_ACTIVITIES #include "../activities.h" #endif #include #include namespace KWin { WorkspaceWrapper::WorkspaceWrapper(QObject* parent) : QObject(parent) { KWin::Workspace *ws = KWin::Workspace::self(); KWin::VirtualDesktopManager *vds = KWin::VirtualDesktopManager::self(); connect(ws, &Workspace::desktopPresenceChanged, this, &WorkspaceWrapper::desktopPresenceChanged); connect(ws, &Workspace::currentDesktopChanged, this, &WorkspaceWrapper::currentDesktopChanged); connect(ws, &Workspace::clientAdded, this, &WorkspaceWrapper::clientAdded); connect(ws, &Workspace::clientAdded, this, &WorkspaceWrapper::setupClientConnections); connect(ws, &Workspace::clientRemoved, this, &WorkspaceWrapper::clientRemoved); connect(ws, &Workspace::clientActivated, this, &WorkspaceWrapper::clientActivated); connect(vds, SIGNAL(countChanged(uint,uint)), SIGNAL(numberDesktopsChanged(uint))); connect(vds, SIGNAL(layoutChanged(int,int)), SIGNAL(desktopLayoutChanged())); connect(ws, &Workspace::clientDemandsAttentionChanged, this, &WorkspaceWrapper::clientDemandsAttentionChanged); #ifdef KWIN_BUILD_ACTIVITIES if (KWin::Activities *activities = KWin::Activities::self()) { connect(activities, SIGNAL(currentChanged(QString)), SIGNAL(currentActivityChanged(QString))); connect(activities, SIGNAL(added(QString)), SIGNAL(activitiesChanged(QString))); connect(activities, SIGNAL(added(QString)), SIGNAL(activityAdded(QString))); connect(activities, SIGNAL(removed(QString)), SIGNAL(activitiesChanged(QString))); connect(activities, SIGNAL(removed(QString)), SIGNAL(activityRemoved(QString))); } #endif connect(screens(), &Screens::sizeChanged, this, &WorkspaceWrapper::virtualScreenSizeChanged); connect(screens(), &Screens::geometryChanged, this, &WorkspaceWrapper::virtualScreenGeometryChanged); connect(screens(), &Screens::countChanged, this, [this] (int previousCount, int currentCount) { Q_UNUSED(previousCount) emit numberScreensChanged(currentCount); } ); connect(QApplication::desktop(), SIGNAL(resized(int)), SIGNAL(screenResized(int))); if (waylandServer()) { connect(waylandServer(), &WaylandServer::shellClientAdded, this, &WorkspaceWrapper::clientAdded); connect(waylandServer(), &WaylandServer::shellClientAdded, this, &WorkspaceWrapper::setupAbstractClientConnections); } foreach (KWin::Client *client, ws->clientList()) { setupClientConnections(client); } } int WorkspaceWrapper::currentDesktop() const { return VirtualDesktopManager::self()->current(); } int WorkspaceWrapper::numberOfDesktops() const { return VirtualDesktopManager::self()->count(); } void WorkspaceWrapper::setCurrentDesktop(int desktop) { VirtualDesktopManager::self()->setCurrent(desktop); } void WorkspaceWrapper::setNumberOfDesktops(int count) { VirtualDesktopManager::self()->setCount(count); } AbstractClient *WorkspaceWrapper::activeClient() const { return workspace()->activeClient(); } QString WorkspaceWrapper::currentActivity() const { #ifdef KWIN_BUILD_ACTIVITIES if (!Activities::self()) { return QString(); } return Activities::self()->current(); #else return QString(); #endif } QStringList WorkspaceWrapper::activityList() const { #ifdef KWIN_BUILD_ACTIVITIES if (!Activities::self()) { return QStringList(); } return Activities::self()->all(); #else return QStringList(); #endif } #define SLOTWRAPPER(name) \ void WorkspaceWrapper::name( ) { \ Workspace::self()->name(); \ } SLOTWRAPPER(slotSwitchToNextScreen) SLOTWRAPPER(slotWindowToNextScreen) SLOTWRAPPER(slotToggleShowDesktop) SLOTWRAPPER(slotWindowMaximize) SLOTWRAPPER(slotWindowMaximizeVertical) SLOTWRAPPER(slotWindowMaximizeHorizontal) SLOTWRAPPER(slotWindowMinimize) SLOTWRAPPER(slotWindowShade) SLOTWRAPPER(slotWindowRaise) SLOTWRAPPER(slotWindowLower) SLOTWRAPPER(slotWindowRaiseOrLower) SLOTWRAPPER(slotActivateAttentionWindow) SLOTWRAPPER(slotWindowPackLeft) SLOTWRAPPER(slotWindowPackRight) SLOTWRAPPER(slotWindowPackUp) SLOTWRAPPER(slotWindowPackDown) SLOTWRAPPER(slotWindowGrowHorizontal) SLOTWRAPPER(slotWindowGrowVertical) SLOTWRAPPER(slotWindowShrinkHorizontal) SLOTWRAPPER(slotWindowShrinkVertical) SLOTWRAPPER(slotIncreaseWindowOpacity) SLOTWRAPPER(slotLowerWindowOpacity) SLOTWRAPPER(slotWindowOperations) SLOTWRAPPER(slotWindowClose) SLOTWRAPPER(slotWindowMove) SLOTWRAPPER(slotWindowResize) SLOTWRAPPER(slotWindowAbove) SLOTWRAPPER(slotWindowBelow) SLOTWRAPPER(slotWindowOnAllDesktops) SLOTWRAPPER(slotWindowFullScreen) SLOTWRAPPER(slotWindowNoBorder) SLOTWRAPPER(slotWindowToNextDesktop) SLOTWRAPPER(slotWindowToPreviousDesktop) SLOTWRAPPER(slotWindowToDesktopRight) SLOTWRAPPER(slotWindowToDesktopLeft) SLOTWRAPPER(slotWindowToDesktopUp) SLOTWRAPPER(slotWindowToDesktopDown) #undef SLOTWRAPPER #define SLOTWRAPPER(name,modes) \ void WorkspaceWrapper::name() { \ Workspace::self()->quickTileWindow(modes); \ } SLOTWRAPPER(slotWindowQuickTileLeft, QuickTileFlag::Left) SLOTWRAPPER(slotWindowQuickTileRight, QuickTileFlag::Right) SLOTWRAPPER(slotWindowQuickTileTop, QuickTileFlag::Top) SLOTWRAPPER(slotWindowQuickTileBottom, QuickTileFlag::Bottom) SLOTWRAPPER(slotWindowQuickTileTopLeft, QuickTileFlag::Top | QuickTileFlag::Left) SLOTWRAPPER(slotWindowQuickTileTopRight, QuickTileFlag::Top | QuickTileFlag::Right) SLOTWRAPPER(slotWindowQuickTileBottomLeft, QuickTileFlag::Bottom | QuickTileFlag::Left) SLOTWRAPPER(slotWindowQuickTileBottomRight, QuickTileFlag::Bottom | QuickTileFlag::Right) #undef SLOTWRAPPER #define SLOTWRAPPER(name,direction) \ void WorkspaceWrapper::name() { \ Workspace::self()->switchWindow(Workspace::direction); \ } SLOTWRAPPER(slotSwitchWindowUp, DirectionNorth) SLOTWRAPPER(slotSwitchWindowDown, DirectionSouth) SLOTWRAPPER(slotSwitchWindowRight, DirectionEast) SLOTWRAPPER(slotSwitchWindowLeft, DirectionWest) #undef SLOTWRAPPER #define SLOTWRAPPER(name,direction) \ void WorkspaceWrapper::name( ) { \ VirtualDesktopManager::self()->moveTo(options->isRollOverDesktops()); \ } SLOTWRAPPER(slotSwitchDesktopNext,DesktopNext) SLOTWRAPPER(slotSwitchDesktopPrevious,DesktopPrevious) SLOTWRAPPER(slotSwitchDesktopRight,DesktopRight) SLOTWRAPPER(slotSwitchDesktopLeft,DesktopLeft) SLOTWRAPPER(slotSwitchDesktopUp,DesktopAbove) SLOTWRAPPER(slotSwitchDesktopDown,DesktopBelow) #undef SLOTWRAPPER void WorkspaceWrapper::setActiveClient(KWin::AbstractClient* client) { KWin::Workspace::self()->activateClient(client); } QSize WorkspaceWrapper::workspaceSize() const { return QSize(workspaceWidth(), workspaceHeight()); } QSize WorkspaceWrapper::displaySize() const { return screens()->displaySize(); } int WorkspaceWrapper::displayWidth() const { return displaySize().width(); } int WorkspaceWrapper::displayHeight() const { return displaySize().height(); } QRect WorkspaceWrapper::clientArea(ClientAreaOption option, const QPoint &p, int desktop) const { return Workspace::self()->clientArea(static_cast(option), p, desktop); } QRect WorkspaceWrapper::clientArea(ClientAreaOption option, const KWin::AbstractClient *c) const { return Workspace::self()->clientArea(static_cast(option), c); } QRect WorkspaceWrapper::clientArea(ClientAreaOption option, int screen, int desktop) const { return Workspace::self()->clientArea(static_cast(option), screen, desktop); } QString WorkspaceWrapper::desktopName(int desktop) const { return VirtualDesktopManager::self()->name(desktop); } QString WorkspaceWrapper::supportInformation() const { return Workspace::self()->supportInformation(); } void WorkspaceWrapper::setupAbstractClientConnections(AbstractClient *client) { connect(client, &AbstractClient::clientMinimized, this, &WorkspaceWrapper::clientMinimized); connect(client, &AbstractClient::clientUnminimized, this, &WorkspaceWrapper::clientUnminimized); connect(client, qOverload(&AbstractClient::clientMaximizedStateChanged), this, &WorkspaceWrapper::clientMaximizeSet); } void WorkspaceWrapper::setupClientConnections(Client *client) { setupAbstractClientConnections(client); connect(client, &Client::clientManaging, this, &WorkspaceWrapper::clientManaging); connect(client, &Client::clientFullScreenSet, this, &WorkspaceWrapper::clientFullScreenSet); } void WorkspaceWrapper::showOutline(const QRect &geometry) { outline()->show(geometry); } void WorkspaceWrapper::showOutline(int x, int y, int width, int height) { outline()->show(QRect(x, y, width, height)); } void WorkspaceWrapper::hideOutline() { outline()->hide(); } Client *WorkspaceWrapper::getClient(qulonglong windowId) { return Workspace::self()->findClient(Predicate::WindowMatch, windowId); } QSize WorkspaceWrapper::desktopGridSize() const { return VirtualDesktopManager::self()->grid().size(); } int WorkspaceWrapper::desktopGridWidth() const { return desktopGridSize().width(); } int WorkspaceWrapper::desktopGridHeight() const { return desktopGridSize().height(); } int WorkspaceWrapper::workspaceHeight() const { return desktopGridHeight() * displayHeight(); } int WorkspaceWrapper::workspaceWidth() const { return desktopGridWidth() * displayWidth(); } int WorkspaceWrapper::numScreens() const { return screens()->count(); } int WorkspaceWrapper::activeScreen() const { return screens()->current(); } QRect WorkspaceWrapper::virtualScreenGeometry() const { return screens()->geometry(); } QSize WorkspaceWrapper::virtualScreenSize() const { return screens()->size(); } QtScriptWorkspaceWrapper::QtScriptWorkspaceWrapper(QObject* parent) : WorkspaceWrapper(parent) {} QList QtScriptWorkspaceWrapper::clientList() const { return workspace()->allClientList(); } QQmlListProperty DeclarativeScriptWorkspaceWrapper::clients() { - return QQmlListProperty(this, 0, &DeclarativeScriptWorkspaceWrapper::countClientList, &DeclarativeScriptWorkspaceWrapper::atClientList); + return QQmlListProperty(this, nullptr, &DeclarativeScriptWorkspaceWrapper::countClientList, &DeclarativeScriptWorkspaceWrapper::atClientList); } int DeclarativeScriptWorkspaceWrapper::countClientList(QQmlListProperty *clients) { Q_UNUSED(clients) return workspace()->allClientList().size(); } KWin::AbstractClient *DeclarativeScriptWorkspaceWrapper::atClientList(QQmlListProperty *clients, int index) { Q_UNUSED(clients) return workspace()->allClientList().at(index); } DeclarativeScriptWorkspaceWrapper::DeclarativeScriptWorkspaceWrapper(QObject* parent) : WorkspaceWrapper(parent) {} } // KWin diff --git a/shadow.cpp b/shadow.cpp index 1699b837a..ad53ca5ca 100644 --- a/shadow.cpp +++ b/shadow.cpp @@ -1,425 +1,425 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 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 "shadow.h" // kwin #include "atoms.h" #include "abstract_client.h" #include "composite.h" #include "effects.h" #include "toplevel.h" #include "wayland_server.h" #include #include #include #include #include namespace KWin { Shadow::Shadow(Toplevel *toplevel) : m_topLevel(toplevel) , m_cachedSize(toplevel->geometry().size()) , m_decorationShadow(nullptr) { connect(m_topLevel, SIGNAL(geometryChanged()), SLOT(geometryChanged())); } Shadow::~Shadow() { } Shadow *Shadow::createShadow(Toplevel *toplevel) { if (!effects) { - return NULL; + return nullptr; } Shadow *shadow = createShadowFromDecoration(toplevel); if (!shadow && waylandServer()) { shadow = createShadowFromWayland(toplevel); } if (!shadow && kwinApp()->x11Connection()) { shadow = createShadowFromX11(toplevel); } if (!shadow) { return nullptr; } if (toplevel->effectWindow() && toplevel->effectWindow()->sceneWindow()) { toplevel->effectWindow()->sceneWindow()->updateShadow(shadow); emit toplevel->shadowChanged(); } return shadow; } Shadow *Shadow::createShadowFromX11(Toplevel *toplevel) { auto data = Shadow::readX11ShadowProperty(toplevel->window()); if (!data.isEmpty()) { Shadow *shadow = Compositor::self()->scene()->createShadow(toplevel); if (!shadow->init(data)) { delete shadow; - return NULL; + return nullptr; } return shadow; } else { - return NULL; + return nullptr; } } Shadow *Shadow::createShadowFromDecoration(Toplevel *toplevel) { AbstractClient *c = qobject_cast(toplevel); if (!c) { return nullptr; } if (!c->decoration()) { return nullptr; } Shadow *shadow = Compositor::self()->scene()->createShadow(toplevel); if (!shadow->init(c->decoration())) { delete shadow; return nullptr; } return shadow; } Shadow *Shadow::createShadowFromWayland(Toplevel *toplevel) { auto surface = toplevel->surface(); if (!surface) { return nullptr; } const auto s = surface->shadow(); if (!s) { return nullptr; } Shadow *shadow = Compositor::self()->scene()->createShadow(toplevel); if (!shadow->init(s)) { delete shadow; return nullptr; } return shadow; } QVector< uint32_t > Shadow::readX11ShadowProperty(xcb_window_t id) { QVector ret; if (id != XCB_WINDOW_NONE) { Xcb::Property property(false, id, atoms->kde_net_wm_shadow, XCB_ATOM_CARDINAL, 0, 12); uint32_t *shadow = property.value(); if (shadow) { ret.reserve(12); for (int i=0; i<12; ++i) { ret << shadow[i]; } } } return ret; } bool Shadow::init(const QVector< uint32_t > &data) { QVector pixmapGeometries(ShadowElementsCount); QVector getImageCookies(ShadowElementsCount); auto *c = connection(); for (int i = 0; i < ShadowElementsCount; ++i) { pixmapGeometries[i] = Xcb::WindowGeometry(data[i]); } auto discardReplies = [&getImageCookies](int start) { for (int i = start; i < getImageCookies.size(); ++i) { xcb_discard_reply(connection(), getImageCookies.at(i).sequence); } }; for (int i = 0; i < ShadowElementsCount; ++i) { auto &geo = pixmapGeometries[i]; if (geo.isNull()) { discardReplies(0); return false; } getImageCookies[i] = xcb_get_image_unchecked(c, XCB_IMAGE_FORMAT_Z_PIXMAP, data[i], 0, 0, geo->width, geo->height, ~0); } for (int i = 0; i < ShadowElementsCount; ++i) { auto *reply = xcb_get_image_reply(c, getImageCookies.at(i), nullptr); if (!reply) { discardReplies(i+1); return false; } auto &geo = pixmapGeometries[i]; QImage image(xcb_get_image_data(reply), geo->width, geo->height, QImage::Format_ARGB32); m_shadowElements[i] = QPixmap::fromImage(image); free(reply); } m_topOffset = data[ShadowElementsCount]; m_rightOffset = data[ShadowElementsCount+1]; m_bottomOffset = data[ShadowElementsCount+2]; m_leftOffset = data[ShadowElementsCount+3]; updateShadowRegion(); if (!prepareBackend()) { return false; } buildQuads(); return true; } bool Shadow::init(KDecoration2::Decoration *decoration) { if (m_decorationShadow) { // disconnect previous connections disconnect(m_decorationShadow.data(), &KDecoration2::DecorationShadow::innerShadowRectChanged, m_topLevel, &Toplevel::getShadow); disconnect(m_decorationShadow.data(), &KDecoration2::DecorationShadow::shadowChanged, m_topLevel, &Toplevel::getShadow); disconnect(m_decorationShadow.data(), &KDecoration2::DecorationShadow::paddingChanged, m_topLevel, &Toplevel::getShadow); } m_decorationShadow = decoration->shadow(); if (!m_decorationShadow) { return false; } // setup connections - all just mapped to recreate connect(m_decorationShadow.data(), &KDecoration2::DecorationShadow::innerShadowRectChanged, m_topLevel, &Toplevel::getShadow); connect(m_decorationShadow.data(), &KDecoration2::DecorationShadow::shadowChanged, m_topLevel, &Toplevel::getShadow); connect(m_decorationShadow.data(), &KDecoration2::DecorationShadow::paddingChanged, m_topLevel, &Toplevel::getShadow); const QMargins &p = m_decorationShadow->padding(); m_topOffset = p.top(); m_rightOffset = p.right(); m_bottomOffset = p.bottom(); m_leftOffset = p.left(); updateShadowRegion(); if (!prepareBackend()) { return false; } buildQuads(); return true; } bool Shadow::init(const QPointer< KWayland::Server::ShadowInterface > &shadow) { if (!shadow) { return false; } m_shadowElements[ShadowElementTop] = shadow->top() ? QPixmap::fromImage(shadow->top()->data().copy()) : QPixmap(); m_shadowElements[ShadowElementTopRight] = shadow->topRight() ? QPixmap::fromImage(shadow->topRight()->data().copy()) : QPixmap(); m_shadowElements[ShadowElementRight] = shadow->right() ? QPixmap::fromImage(shadow->right()->data().copy()) : QPixmap(); m_shadowElements[ShadowElementBottomRight] = shadow->bottomRight() ? QPixmap::fromImage(shadow->bottomRight()->data().copy()) : QPixmap(); m_shadowElements[ShadowElementBottom] = shadow->bottom() ? QPixmap::fromImage(shadow->bottom()->data().copy()) : QPixmap(); m_shadowElements[ShadowElementBottomLeft] = shadow->bottomLeft() ? QPixmap::fromImage(shadow->bottomLeft()->data().copy()) : QPixmap(); m_shadowElements[ShadowElementLeft] = shadow->left() ? QPixmap::fromImage(shadow->left()->data().copy()) : QPixmap(); m_shadowElements[ShadowElementTopLeft] = shadow->topLeft() ? QPixmap::fromImage(shadow->topLeft()->data().copy()) : QPixmap(); const QMarginsF &p = shadow->offset(); m_topOffset = p.top(); m_rightOffset = p.right(); m_bottomOffset = p.bottom(); m_leftOffset = p.left(); updateShadowRegion(); if (!prepareBackend()) { return false; } buildQuads(); return true; } void Shadow::updateShadowRegion() { const QRect top(0, - m_topOffset, m_topLevel->width(), m_topOffset); const QRect right(m_topLevel->width(), - m_topOffset, m_rightOffset, m_topLevel->height() + m_topOffset + m_bottomOffset); const QRect bottom(0, m_topLevel->height(), m_topLevel->width(), m_bottomOffset); const QRect left(- m_leftOffset, - m_topOffset, m_leftOffset, m_topLevel->height() + m_topOffset + m_bottomOffset); m_shadowRegion = QRegion(top).united(right).united(bottom).united(left); } void Shadow::buildQuads() { // prepare window quads m_shadowQuads.clear(); const QSize top(m_shadowElements[ShadowElementTop].size()); const QSize topRight(m_shadowElements[ShadowElementTopRight].size()); const QSize right(m_shadowElements[ShadowElementRight].size()); const QSize bottomRight(m_shadowElements[ShadowElementBottomRight].size()); const QSize bottom(m_shadowElements[ShadowElementBottom].size()); const QSize bottomLeft(m_shadowElements[ShadowElementBottomLeft].size()); const QSize left(m_shadowElements[ShadowElementLeft].size()); const QSize topLeft(m_shadowElements[ShadowElementTopLeft].size()); if ((left.width() - m_leftOffset > m_topLevel->width()) || (right.width() - m_rightOffset > m_topLevel->width()) || (top.height() - m_topOffset > m_topLevel->height()) || (bottom.height() - m_bottomOffset > m_topLevel->height())) { // if our shadow is bigger than the window, we don't render the shadow m_shadowRegion = QRegion(); return; } const QRect outerRect(QPoint(-m_leftOffset, -m_topOffset), QPoint(m_topLevel->width() + m_rightOffset, m_topLevel->height() + m_bottomOffset)); WindowQuad topLeftQuad(WindowQuadShadowTopLeft); topLeftQuad[ 0 ] = WindowVertex(outerRect.x(), outerRect.y(), 0.0, 0.0); topLeftQuad[ 1 ] = WindowVertex(outerRect.x() + topLeft.width(), outerRect.y(), 1.0, 0.0); topLeftQuad[ 2 ] = WindowVertex(outerRect.x() + topLeft.width(), outerRect.y() + topLeft.height(), 1.0, 1.0); topLeftQuad[ 3 ] = WindowVertex(outerRect.x(), outerRect.y() + topLeft.height(), 0.0, 1.0); m_shadowQuads.append(topLeftQuad); WindowQuad topQuad(WindowQuadShadowTop); topQuad[ 0 ] = WindowVertex(outerRect.x() + topLeft.width(), outerRect.y(), 0.0, 0.0); topQuad[ 1 ] = WindowVertex(outerRect.right() - topRight.width(), outerRect.y(), 1.0, 0.0); topQuad[ 2 ] = WindowVertex(outerRect.right() - topRight.width(), outerRect.y() + top.height(), 1.0, 1.0); topQuad[ 3 ] = WindowVertex(outerRect.x() + topLeft.width(), outerRect.y() + top.height(), 0.0, 1.0); m_shadowQuads.append(topQuad); WindowQuad topRightQuad(WindowQuadShadowTopRight); topRightQuad[ 0 ] = WindowVertex(outerRect.right() - topRight.width(), outerRect.y(), 0.0, 0.0); topRightQuad[ 1 ] = WindowVertex(outerRect.right(), outerRect.y(), 1.0, 0.0); topRightQuad[ 2 ] = WindowVertex(outerRect.right(), outerRect.y() + topRight.height(), 1.0, 1.0); topRightQuad[ 3 ] = WindowVertex(outerRect.right() - topRight.width(), outerRect.y() + topRight.height(), 0.0, 1.0); m_shadowQuads.append(topRightQuad); WindowQuad rightQuad(WindowQuadShadowRight); rightQuad[ 0 ] = WindowVertex(outerRect.right() - right.width(), outerRect.y() + topRight.height(), 0.0, 0.0); rightQuad[ 1 ] = WindowVertex(outerRect.right(), outerRect.y() + topRight.height(), 1.0, 0.0); rightQuad[ 2 ] = WindowVertex(outerRect.right(), outerRect.bottom() - bottomRight.height(), 1.0, 1.0); rightQuad[ 3 ] = WindowVertex(outerRect.right() - right.width(), outerRect.bottom() - bottomRight.height(), 0.0, 1.0); m_shadowQuads.append(rightQuad); WindowQuad bottomRightQuad(WindowQuadShadowBottomRight); bottomRightQuad[ 0 ] = WindowVertex(outerRect.right() - bottomRight.width(), outerRect.bottom() - bottomRight.height(), 0.0, 0.0); bottomRightQuad[ 1 ] = WindowVertex(outerRect.right(), outerRect.bottom() - bottomRight.height(), 1.0, 0.0); bottomRightQuad[ 2 ] = WindowVertex(outerRect.right(), outerRect.bottom(), 1.0, 1.0); bottomRightQuad[ 3 ] = WindowVertex(outerRect.right() - bottomRight.width(), outerRect.bottom(), 0.0, 1.0); m_shadowQuads.append(bottomRightQuad); WindowQuad bottomQuad(WindowQuadShadowBottom); bottomQuad[ 0 ] = WindowVertex(outerRect.x() + bottomLeft.width(), outerRect.bottom() - bottom.height(), 0.0, 0.0); bottomQuad[ 1 ] = WindowVertex(outerRect.right() - bottomRight.width(), outerRect.bottom() - bottom.height(), 1.0, 0.0); bottomQuad[ 2 ] = WindowVertex(outerRect.right() - bottomRight.width(), outerRect.bottom(), 1.0, 1.0); bottomQuad[ 3 ] = WindowVertex(outerRect.x() + bottomLeft.width(), outerRect.bottom(), 0.0, 1.0); m_shadowQuads.append(bottomQuad); WindowQuad bottomLeftQuad(WindowQuadShadowBottomLeft); bottomLeftQuad[ 0 ] = WindowVertex(outerRect.x(), outerRect.bottom() - bottomLeft.height(), 0.0, 0.0); bottomLeftQuad[ 1 ] = WindowVertex(outerRect.x() + bottomLeft.width(), outerRect.bottom() - bottomLeft.height(), 1.0, 0.0); bottomLeftQuad[ 2 ] = WindowVertex(outerRect.x() + bottomLeft.width(), outerRect.bottom(), 1.0, 1.0); bottomLeftQuad[ 3 ] = WindowVertex(outerRect.x(), outerRect.bottom(), 0.0, 1.0); m_shadowQuads.append(bottomLeftQuad); WindowQuad leftQuad(WindowQuadShadowLeft); leftQuad[ 0 ] = WindowVertex(outerRect.x(), outerRect.y() + topLeft.height(), 0.0, 0.0); leftQuad[ 1 ] = WindowVertex(outerRect.x() + left.width(), outerRect.y() + topLeft.height(), 1.0, 0.0); leftQuad[ 2 ] = WindowVertex(outerRect.x() + left.width(), outerRect.bottom() - bottomLeft.height(), 1.0, 1.0); leftQuad[ 3 ] = WindowVertex(outerRect.x(), outerRect.bottom() - bottomLeft.height(), 0.0, 1.0); m_shadowQuads.append(leftQuad); } bool Shadow::updateShadow() { if (!m_topLevel) { return false; } if (m_decorationShadow) { if (AbstractClient *c = qobject_cast(m_topLevel)) { if (c->decoration()) { if (init(c->decoration())) { return true; } } } return false; } if (waylandServer()) { if (m_topLevel && m_topLevel->surface()) { if (const auto &s = m_topLevel->surface()->shadow()) { if (init(s)) { return true; } } } } auto data = Shadow::readX11ShadowProperty(m_topLevel->window()); if (data.isEmpty()) { return false; } init(data); return true; } void Shadow::setToplevel(Toplevel *topLevel) { m_topLevel = topLevel; connect(m_topLevel, SIGNAL(geometryChanged()), SLOT(geometryChanged())); } void Shadow::geometryChanged() { if (m_cachedSize == m_topLevel->geometry().size()) { return; } m_cachedSize = m_topLevel->geometry().size(); updateShadowRegion(); buildQuads(); } QImage Shadow::decorationShadowImage() const { if (!m_decorationShadow) { return QImage(); } return m_decorationShadow->shadow(); } QSize Shadow::elementSize(Shadow::ShadowElements element) const { if (m_decorationShadow) { switch (element) { case ShadowElementTop: return m_decorationShadow->topGeometry().size(); case ShadowElementTopRight: return m_decorationShadow->topRightGeometry().size(); case ShadowElementRight: return m_decorationShadow->rightGeometry().size(); case ShadowElementBottomRight: return m_decorationShadow->bottomRightGeometry().size(); case ShadowElementBottom: return m_decorationShadow->bottomGeometry().size(); case ShadowElementBottomLeft: return m_decorationShadow->bottomLeftGeometry().size(); case ShadowElementLeft: return m_decorationShadow->leftGeometry().size(); case ShadowElementTopLeft: return m_decorationShadow->topLeftGeometry().size(); default: return QSize(); } } else { return m_shadowElements[element].size(); } } void Shadow::setShadowElement(const QPixmap &shadow, Shadow::ShadowElements element) { m_shadowElements[element] = shadow; } } // namespace diff --git a/sm.cpp b/sm.cpp index 192ff3311..3cd48060d 100644 --- a/sm.cpp +++ b/sm.cpp @@ -1,519 +1,519 @@ /******************************************************************** 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 . *********************************************************************/ #include "sm.h" #include #include #include #include #include "workspace.h" #include "client.h" #include #include #include #include namespace KWin { static bool gs_sessionManagerIsKSMServer = false; static KConfig *sessionConfig(QString id, QString key) { static KConfig *config = nullptr; static QString lastId; static QString lastKey; static QString pattern = QString(QLatin1String("session/%1_%2_%3")).arg(qApp->applicationName()); if (id != lastId || key != lastKey) { delete config; config = nullptr; } lastId = id; lastKey = key; if (!config) { config = new KConfig(pattern.arg(id).arg(key), KConfig::SimpleConfig); } return config; } static const char* const window_type_names[] = { "Unknown", "Normal" , "Desktop", "Dock", "Toolbar", "Menu", "Dialog", "Override", "TopMenu", "Utility", "Splash" }; // change also the two functions below when adding new entries static const char* windowTypeToTxt(NET::WindowType type) { if (type >= NET::Unknown && type <= NET::Splash) return window_type_names[ type + 1 ]; // +1 (unknown==-1) if (type == -2) // undefined (not really part of NET::WindowType) return "Undefined"; qFatal("Unknown Window Type"); - return NULL; + return nullptr; } static NET::WindowType txtToWindowType(const char* txt) { for (int i = NET::Unknown; i <= NET::Splash; ++i) if (qstrcmp(txt, window_type_names[ i + 1 ]) == 0) // +1 return static_cast< NET::WindowType >(i); return static_cast< NET::WindowType >(-2); // undefined } void Workspace::saveState(QSessionManager &sm) { // If the session manager is ksmserver, save stacking // order, active window, active desktop etc. in phase 1, // as ksmserver assures no interaction will be done // before the WM finishes phase 1. Saving in phase 2 is // too late, as possible user interaction may change some things. // Phase2 is still needed though (ICCCM 5.2) KConfig *config = sessionConfig(sm.sessionId(), sm.sessionKey()); if (!sm.isPhase2()) { KConfigGroup cg(config, "Session"); cg.writeEntry("AllowsInteraction", sm.allowsInteraction()); sessionSaveStarted(); if (gs_sessionManagerIsKSMServer) // save stacking order etc. before "save file?" etc. dialogs change it storeSession(config, SMSavePhase0); config->markAsClean(); // don't write Phase #1 data to disk sm.release(); // Qt doesn't automatically release in this case (bug?) sm.requestPhase2(); return; } storeSession(config, gs_sessionManagerIsKSMServer ? SMSavePhase2 : SMSavePhase2Full); config->sync(); // inform the smserver on how to clean-up after us const QString localFilePath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + QLatin1Char('/') + config->name(); if (QFile::exists(localFilePath)) { // expectable for the sync sm.setDiscardCommand(QStringList() << QStringLiteral("rm") << localFilePath); } } // I bet this is broken, just like everywhere else in KDE void Workspace::commitData(QSessionManager &sm) { if (!sm.isPhase2()) sessionSaveStarted(); } // Workspace /** * Stores the current session in the config file * * @see loadSessionInfo */ void Workspace::storeSession(KConfig* config, SMSavePhase phase) { KConfigGroup cg(config, "Session"); int count = 0; int active_client = -1; for (ClientList::Iterator it = clients.begin(); it != clients.end(); ++it) { Client* c = (*it); if (c->windowType() > NET::Splash) { //window types outside this are not tooltips/menus/OSDs //typically these will be unmanaged and not in this list anyway, but that is not enforced continue; } QByteArray sessionId = c->sessionId(); QByteArray wmCommand = c->wmCommand(); if (sessionId.isEmpty()) // remember also applications that are not XSMP capable // and use the obsolete WM_COMMAND / WM_SAVE_YOURSELF if (wmCommand.isEmpty()) continue; count++; if (c->isActive()) active_client = count; if (phase == SMSavePhase2 || phase == SMSavePhase2Full) storeClient(cg, count, c); } if (phase == SMSavePhase0) { // it would be much simpler to save these values to the config file, // but both Qt and KDE treat phase1 and phase2 separately, // which results in different sessionkey and different config file :( session_active_client = active_client; session_desktop = VirtualDesktopManager::self()->current(); } else if (phase == SMSavePhase2) { cg.writeEntry("count", count); cg.writeEntry("active", session_active_client); cg.writeEntry("desktop", session_desktop); } else { // SMSavePhase2Full cg.writeEntry("count", count); cg.writeEntry("active", session_active_client); cg.writeEntry("desktop", VirtualDesktopManager::self()->current()); } } void Workspace::storeClient(KConfigGroup &cg, int num, Client *c) { c->setSessionActivityOverride(false); //make sure we get the real values QString n = QString::number(num); cg.writeEntry(QLatin1String("sessionId") + n, c->sessionId().constData()); cg.writeEntry(QLatin1String("windowRole") + n, c->windowRole().constData()); cg.writeEntry(QLatin1String("wmCommand") + n, c->wmCommand().constData()); cg.writeEntry(QLatin1String("resourceName") + n, c->resourceName().constData()); cg.writeEntry(QLatin1String("resourceClass") + n, c->resourceClass().constData()); cg.writeEntry(QLatin1String("geometry") + n, QRect(c->calculateGravitation(true), c->clientSize())); // FRAME cg.writeEntry(QLatin1String("restore") + n, c->geometryRestore()); cg.writeEntry(QLatin1String("fsrestore") + n, c->geometryFSRestore()); cg.writeEntry(QLatin1String("maximize") + n, (int) c->maximizeMode()); cg.writeEntry(QLatin1String("fullscreen") + n, (int) c->fullScreenMode()); cg.writeEntry(QLatin1String("desktop") + n, c->desktop()); // the config entry is called "iconified" for back. comp. reasons // (kconf_update script for updating session files would be too complicated) cg.writeEntry(QLatin1String("iconified") + n, c->isMinimized()); cg.writeEntry(QLatin1String("opacity") + n, c->opacity()); // the config entry is called "sticky" for back. comp. reasons cg.writeEntry(QLatin1String("sticky") + n, c->isOnAllDesktops()); cg.writeEntry(QLatin1String("shaded") + n, c->isShade()); // the config entry is called "staysOnTop" for back. comp. reasons cg.writeEntry(QLatin1String("staysOnTop") + n, c->keepAbove()); cg.writeEntry(QLatin1String("keepBelow") + n, c->keepBelow()); cg.writeEntry(QLatin1String("skipTaskbar") + n, c->originalSkipTaskbar()); cg.writeEntry(QLatin1String("skipPager") + n, c->skipPager()); cg.writeEntry(QLatin1String("skipSwitcher") + n, c->skipSwitcher()); // not really just set by user, but name kept for back. comp. reasons cg.writeEntry(QLatin1String("userNoBorder") + n, c->userNoBorder()); cg.writeEntry(QLatin1String("windowType") + n, windowTypeToTxt(c->windowType())); cg.writeEntry(QLatin1String("shortcut") + n, c->shortcut().toString()); cg.writeEntry(QLatin1String("stackingOrder") + n, unconstrained_stacking_order.indexOf(c)); cg.writeEntry(QLatin1String("activities") + n, c->activities()); } void Workspace::storeSubSession(const QString &name, QSet sessionIds) { //TODO clear it first KConfigGroup cg(KSharedConfig::openConfig(), QLatin1String("SubSession: ") + name); int count = 0; int active_client = -1; for (ClientList::Iterator it = clients.begin(); it != clients.end(); ++it) { Client* c = (*it); if (c->windowType() > NET::Splash) { continue; } QByteArray sessionId = c->sessionId(); QByteArray wmCommand = c->wmCommand(); if (sessionId.isEmpty()) // remember also applications that are not XSMP capable // and use the obsolete WM_COMMAND / WM_SAVE_YOURSELF if (wmCommand.isEmpty()) continue; if (!sessionIds.contains(sessionId)) continue; qCDebug(KWIN_CORE) << "storing" << sessionId; count++; if (c->isActive()) active_client = count; storeClient(cg, count, c); } cg.writeEntry("count", count); cg.writeEntry("active", active_client); //cg.writeEntry( "desktop", currentDesktop()); } /** * Loads the session information from the config file. * * @see storeSession */ void Workspace::loadSessionInfo(const QString &key) { // NOTICE: qApp->sessionKey() is outdated when this gets invoked // the key parameter is cached from the application constructor. session.clear(); KConfigGroup cg(sessionConfig(qApp->sessionId(), key), "Session"); addSessionInfo(cg); } void Workspace::addSessionInfo(KConfigGroup &cg) { m_initialDesktop = cg.readEntry("desktop", 1); int count = cg.readEntry("count", 0); int active_client = cg.readEntry("active", 0); for (int i = 1; i <= count; i++) { QString n = QString::number(i); SessionInfo* info = new SessionInfo; session.append(info); info->sessionId = cg.readEntry(QLatin1String("sessionId") + n, QString()).toLatin1(); info->windowRole = cg.readEntry(QLatin1String("windowRole") + n, QString()).toLatin1(); info->wmCommand = cg.readEntry(QLatin1String("wmCommand") + n, QString()).toLatin1(); info->resourceName = cg.readEntry(QLatin1String("resourceName") + n, QString()).toLatin1(); info->resourceClass = cg.readEntry(QLatin1String("resourceClass") + n, QString()).toLower().toLatin1(); info->geometry = cg.readEntry(QLatin1String("geometry") + n, QRect()); info->restore = cg.readEntry(QLatin1String("restore") + n, QRect()); info->fsrestore = cg.readEntry(QLatin1String("fsrestore") + n, QRect()); info->maximized = cg.readEntry(QLatin1String("maximize") + n, 0); info->fullscreen = cg.readEntry(QLatin1String("fullscreen") + n, 0); info->desktop = cg.readEntry(QLatin1String("desktop") + n, 0); info->minimized = cg.readEntry(QLatin1String("iconified") + n, false); info->opacity = cg.readEntry(QLatin1String("opacity") + n, 1.0); info->onAllDesktops = cg.readEntry(QLatin1String("sticky") + n, false); info->shaded = cg.readEntry(QLatin1String("shaded") + n, false); info->keepAbove = cg.readEntry(QLatin1String("staysOnTop") + n, false); info->keepBelow = cg.readEntry(QLatin1String("keepBelow") + n, false); info->skipTaskbar = cg.readEntry(QLatin1String("skipTaskbar") + n, false); info->skipPager = cg.readEntry(QLatin1String("skipPager") + n, false); info->skipSwitcher = cg.readEntry(QLatin1String("skipSwitcher") + n, false); info->noBorder = cg.readEntry(QLatin1String("userNoBorder") + n, false); info->windowType = txtToWindowType(cg.readEntry(QLatin1String("windowType") + n, QString()).toLatin1().constData()); info->shortcut = cg.readEntry(QLatin1String("shortcut") + n, QString()); info->active = (active_client == i); info->stackingOrder = cg.readEntry(QLatin1String("stackingOrder") + n, -1); info->activities = cg.readEntry(QLatin1String("activities") + n, QStringList()); } } void Workspace::loadSubSessionInfo(const QString &name) { KConfigGroup cg(KSharedConfig::openConfig(), QLatin1String("SubSession: ") + name); addSessionInfo(cg); } static bool sessionInfoWindowTypeMatch(Client* c, SessionInfo* info) { if (info->windowType == -2) { // undefined (not really part of NET::WindowType) return !c->isSpecialWindow(); } return info->windowType == c->windowType(); } /** * Returns a SessionInfo for client \a c. The returned session * info is removed from the storage. It's up to the caller to delete it. * * This function is called when a new window is mapped and must be managed. * We try to find a matching entry in the session. * * May return 0 if there's no session info for the client. */ SessionInfo* Workspace::takeSessionInfo(Client* c) { - SessionInfo *realInfo = 0; + SessionInfo *realInfo = nullptr; QByteArray sessionId = c->sessionId(); QByteArray windowRole = c->windowRole(); QByteArray wmCommand = c->wmCommand(); QByteArray resourceName = c->resourceName(); QByteArray resourceClass = c->resourceClass(); // First search ``session'' if (! sessionId.isEmpty()) { // look for a real session managed client (algorithm suggested by ICCCM) foreach (SessionInfo * info, session) { if (realInfo) break; if (info->sessionId == sessionId && sessionInfoWindowTypeMatch(c, info)) { if (! windowRole.isEmpty()) { if (info->windowRole == windowRole) { realInfo = info; session.removeAll(info); } } else { if (info->windowRole.isEmpty() && info->resourceName == resourceName && info->resourceClass == resourceClass) { realInfo = info; session.removeAll(info); } } } } } else { // look for a sessioninfo with matching features. foreach (SessionInfo * info, session) { if (realInfo) break; if (info->resourceName == resourceName && info->resourceClass == resourceClass && sessionInfoWindowTypeMatch(c, info)) { if (wmCommand.isEmpty() || info->wmCommand == wmCommand) { realInfo = info; session.removeAll(info); } } } } return realInfo; } // KWin's focus stealing prevention causes problems with user interaction // during session save, as it prevents possible dialogs from getting focus. // Therefore it's temporarily disabled during session saving. Start of // session saving can be detected in SessionManager::saveState() above, // but Qt doesn't have API for saying when session saved finished (either // successfully, or was canceled). Therefore, create another connection // to session manager, that will provide this information. // Similarly the remember feature of window-specific settings should be disabled // during KDE shutdown when windows may move e.g. because of Kicker going away // (struts changing). When session saving starts, it can be cancelled, in which // case the shutdown_cancelled callback is invoked, or it's a checkpoint that // is immediatelly followed by save_complete, or finally it's a shutdown that // is immediatelly followed by die callback. So getting save_yourself with shutdown // set disables window-specific settings remembering, getting shutdown_cancelled // re-enables, otherwise KWin will go away after die. static void save_yourself(SmcConn conn_P, SmPointer ptr, int, Bool shutdown, int, Bool) { SessionSaveDoneHelper* session = reinterpret_cast< SessionSaveDoneHelper* >(ptr); if (conn_P != session->connection()) return; if (shutdown) RuleBook::self()->setUpdatesDisabled(true); SmcSaveYourselfDone(conn_P, True); } static void die(SmcConn conn_P, SmPointer ptr) { SessionSaveDoneHelper* session = reinterpret_cast< SessionSaveDoneHelper* >(ptr); if (conn_P != session->connection()) return; // session->saveDone(); we will quit anyway session->close(); } static void save_complete(SmcConn conn_P, SmPointer ptr) { SessionSaveDoneHelper* session = reinterpret_cast< SessionSaveDoneHelper* >(ptr); if (conn_P != session->connection()) return; session->saveDone(); } static void shutdown_cancelled(SmcConn conn_P, SmPointer ptr) { SessionSaveDoneHelper* session = reinterpret_cast< SessionSaveDoneHelper* >(ptr); if (conn_P != session->connection()) return; RuleBook::self()->setUpdatesDisabled(false); // re-enable // no need to differentiate between successful finish and cancel session->saveDone(); } void SessionSaveDoneHelper::saveDone() { if (Workspace::self()) Workspace::self()->sessionSaveDone(); } SessionSaveDoneHelper::SessionSaveDoneHelper() { SmcCallbacks calls; calls.save_yourself.callback = save_yourself; calls.save_yourself.client_data = reinterpret_cast< SmPointer >(this); calls.die.callback = die; calls.die.client_data = reinterpret_cast< SmPointer >(this); calls.save_complete.callback = save_complete; calls.save_complete.client_data = reinterpret_cast< SmPointer >(this); calls.shutdown_cancelled.callback = shutdown_cancelled; calls.shutdown_cancelled.client_data = reinterpret_cast< SmPointer >(this); - char* id = NULL; + char* id = nullptr; char err[ 11 ]; - conn = SmcOpenConnection(NULL, 0, 1, 0, + conn = SmcOpenConnection(nullptr, nullptr, 1, 0, SmcSaveYourselfProcMask | SmcDieProcMask | SmcSaveCompleteProcMask - | SmcShutdownCancelledProcMask, &calls, NULL, &id, 10, err); - if (id != NULL) + | SmcShutdownCancelledProcMask, &calls, nullptr, &id, 10, err); + if (id != nullptr) free(id); - if (conn == NULL) + if (conn == nullptr) return; // no SM // detect ksmserver char* vendor = SmcVendor(conn); gs_sessionManagerIsKSMServer = qstrcmp(vendor, "KDE") == 0; free(vendor); // set the required properties, mostly dummy values SmPropValue propvalue[ 5 ]; SmProp props[ 5 ]; propvalue[ 0 ].length = sizeof(unsigned char); unsigned char value0 = SmRestartNever; // so that this extra SM connection doesn't interfere propvalue[ 0 ].value = &value0; props[ 0 ].name = const_cast< char* >(SmRestartStyleHint); props[ 0 ].type = const_cast< char* >(SmCARD8); props[ 0 ].num_vals = 1; props[ 0 ].vals = &propvalue[ 0 ]; struct passwd* entry = getpwuid(geteuid()); - propvalue[ 1 ].length = entry != NULL ? strlen(entry->pw_name) : 0; - propvalue[ 1 ].value = (SmPointer)(entry != NULL ? entry->pw_name : ""); + propvalue[ 1 ].length = entry != nullptr ? strlen(entry->pw_name) : 0; + propvalue[ 1 ].value = (SmPointer)(entry != nullptr ? entry->pw_name : ""); props[ 1 ].name = const_cast< char* >(SmUserID); props[ 1 ].type = const_cast< char* >(SmARRAY8); props[ 1 ].num_vals = 1; props[ 1 ].vals = &propvalue[ 1 ]; propvalue[ 2 ].length = 0; propvalue[ 2 ].value = (SmPointer)(""); props[ 2 ].name = const_cast< char* >(SmRestartCommand); props[ 2 ].type = const_cast< char* >(SmLISTofARRAY8); props[ 2 ].num_vals = 1; props[ 2 ].vals = &propvalue[ 2 ]; propvalue[ 3 ].length = strlen("kwinsmhelper"); propvalue[ 3 ].value = (SmPointer)"kwinsmhelper"; props[ 3 ].name = const_cast< char* >(SmProgram); props[ 3 ].type = const_cast< char* >(SmARRAY8); props[ 3 ].num_vals = 1; props[ 3 ].vals = &propvalue[ 3 ]; propvalue[ 4 ].length = 0; propvalue[ 4 ].value = (SmPointer)(""); props[ 4 ].name = const_cast< char* >(SmCloneCommand); props[ 4 ].type = const_cast< char* >(SmLISTofARRAY8); props[ 4 ].num_vals = 1; props[ 4 ].vals = &propvalue[ 4 ]; SmProp* p[ 5 ] = { &props[ 0 ], &props[ 1 ], &props[ 2 ], &props[ 3 ], &props[ 4 ] }; SmcSetProperties(conn, 5, p); notifier = new QSocketNotifier(IceConnectionNumber(SmcGetIceConnection(conn)), QSocketNotifier::Read, this); connect(notifier, SIGNAL(activated(int)), SLOT(processData())); } SessionSaveDoneHelper::~SessionSaveDoneHelper() { close(); } void SessionSaveDoneHelper::close() { - if (conn != NULL) { + if (conn != nullptr) { delete notifier; - SmcCloseConnection(conn, 0, NULL); + SmcCloseConnection(conn, 0, nullptr); } - conn = NULL; + conn = nullptr; } void SessionSaveDoneHelper::processData() { - if (conn != NULL) - IceProcessMessages(SmcGetIceConnection(conn), 0, 0); + if (conn != nullptr) + IceProcessMessages(SmcGetIceConnection(conn), nullptr, nullptr); } void Workspace::sessionSaveDone() { session_saving = false; foreach (Client * c, clients) { c->setSessionActivityOverride(false); } } } // namespace diff --git a/tabbox/tabbox.cpp b/tabbox/tabbox.cpp index 1175eb0bf..9736ee21b 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)) { 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 0; + 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 0; + 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/tests/pointerconstraintstest.cpp b/tests/pointerconstraintstest.cpp index 8334d3b1a..66722fda5 100644 --- a/tests/pointerconstraintstest.cpp +++ b/tests/pointerconstraintstest.cpp @@ -1,417 +1,417 @@ /******************************************************************** Copyright 2018 Roman Gilg This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . *********************************************************************/ #include "pointerconstraintstest.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace KWayland::Client; WaylandBackend::WaylandBackend(QObject *parent) : Backend(parent) , m_connectionThreadObject(ConnectionThread::fromApplication(this)) { setMode(Mode::Wayland); } void WaylandBackend::init(QQuickView *view) { Backend::init(view); Registry *registry = new Registry(this); setupRegistry(registry); } void WaylandBackend::setupRegistry(Registry *registry) { connect(registry, &Registry::compositorAnnounced, this, [this, registry](quint32 name, quint32 version) { m_compositor = registry->createCompositor(name, version, this); } ); connect(registry, &Registry::seatAnnounced, this, [this, registry](quint32 name, quint32 version) { m_seat = registry->createSeat(name, version, this); if (m_seat->hasPointer()) { m_pointer = m_seat->createPointer(this); } connect(m_seat, &Seat::hasPointerChanged, this, [this]() { delete m_pointer; m_pointer = m_seat->createPointer(this); } ); } ); connect(registry, &Registry::pointerConstraintsUnstableV1Announced, this, [this, registry](quint32 name, quint32 version) { m_pointerConstraints = registry->createPointerConstraints(name, version, this); } ); connect(registry, &Registry::interfacesAnnounced, this, [this] { Q_ASSERT(m_compositor); Q_ASSERT(m_seat); Q_ASSERT(m_pointerConstraints); } ); registry->create(m_connectionThreadObject); registry->setup(); } bool WaylandBackend::isLocked() { return m_lockedPointer && m_lockedPointer->isValid(); } bool WaylandBackend::isConfined() { return m_confinedPointer && m_confinedPointer->isValid(); } static PointerConstraints::LifeTime lifeTime(bool persistent) { return persistent ? PointerConstraints::LifeTime::Persistent : PointerConstraints::LifeTime::OneShot; } void WaylandBackend::lockRequest(bool persistent, QRect region) { if (isLocked()) { if (!errorsAllowed()) { qDebug() << "Abort locking because already locked. Allow errors to test relocking (and crashing)."; return; } qDebug() << "Trying to lock although already locked. Crash expected."; } if (isConfined()) { if (!errorsAllowed()) { qDebug() << "Abort locking because already confined. Allow errors to test locking while being confined (and crashing)."; return; } qDebug() << "Trying to lock although already confined. Crash expected."; } qDebug() << "------ Lock requested ------"; qDebug() << "Persistent:" << persistent << "| Region:" << region; QScopedPointer winSurface(Surface::fromWindow(view())); QScopedPointer wlRegion(m_compositor->createRegion(this)); wlRegion->add(region); auto *lockedPointer = m_pointerConstraints->lockPointer(winSurface.data(), m_pointer, wlRegion.data(), lifeTime(persistent), this); if (!lockedPointer) { qDebug() << "ERROR when receiving locked pointer!"; return; } m_lockedPointer = lockedPointer; m_lockedPointerPersistent = persistent; connect(lockedPointer, &LockedPointer::locked, this, [this]() { qDebug() << "------ LOCKED! ------"; if(lockHint()) { m_lockedPointer->setCursorPositionHint(QPointF(10., 10.)); forceSurfaceCommit(); } Q_EMIT lockChanged(true); }); connect(lockedPointer, &LockedPointer::unlocked, this, [this]() { qDebug() << "------ UNLOCKED! ------"; if (!m_lockedPointerPersistent) { cleanupLock(); } Q_EMIT lockChanged(false); }); } void WaylandBackend::unlockRequest() { if (!m_lockedPointer) { qDebug() << "Unlock requested, but there is no lock. Abort."; return; } qDebug() << "------ Unlock requested ------"; cleanupLock(); Q_EMIT lockChanged(false); } void WaylandBackend::cleanupLock() { if (!m_lockedPointer) { return; } m_lockedPointer->release(); m_lockedPointer->deleteLater(); m_lockedPointer = nullptr; } void WaylandBackend::confineRequest(bool persistent, QRect region) { if (isConfined()) { if (!errorsAllowed()) { qDebug() << "Abort confining because already confined. Allow errors to test reconfining (and crashing)."; return; } qDebug() << "Trying to lock although already locked. Crash expected."; } if (isLocked()) { if (!errorsAllowed()) { qDebug() << "Abort confining because already locked. Allow errors to test confining while being locked (and crashing)."; return; } qDebug() << "Trying to confine although already locked. Crash expected."; } qDebug() << "------ Confine requested ------"; qDebug() << "Persistent:" << persistent << "| Region:" << region; QScopedPointer winSurface(Surface::fromWindow(view())); QScopedPointer wlRegion(m_compositor->createRegion(this)); wlRegion->add(region); auto *confinedPointer = m_pointerConstraints->confinePointer(winSurface.data(), m_pointer, wlRegion.data(), lifeTime(persistent), this); if (!confinedPointer) { qDebug() << "ERROR when receiving confined pointer!"; return; } m_confinedPointer = confinedPointer; m_confinedPointerPersistent = persistent; connect(confinedPointer, &ConfinedPointer::confined, this, [this]() { qDebug() << "------ CONFINED! ------"; Q_EMIT confineChanged(true); }); connect(confinedPointer, &ConfinedPointer::unconfined, this, [this]() { qDebug() << "------ UNCONFINED! ------"; if (!m_confinedPointerPersistent) { cleanupConfine(); } Q_EMIT confineChanged(false); }); } void WaylandBackend::unconfineRequest() { if (!m_confinedPointer) { qDebug() << "Unconfine requested, but there is no confine. Abort."; return; } qDebug() << "------ Unconfine requested ------"; cleanupConfine(); Q_EMIT confineChanged(false); } void WaylandBackend::cleanupConfine() { if (!m_confinedPointer) { return; } m_confinedPointer->release(); m_confinedPointer->deleteLater(); m_confinedPointer = nullptr; } XBackend::XBackend(QObject *parent) : Backend(parent) { setMode(Mode::X); if (m_xcbConn) { xcb_disconnect(m_xcbConn); free(m_xcbConn); } } void XBackend::init(QQuickView *view) { Backend::init(view); - m_xcbConn = xcb_connect(NULL, NULL); + m_xcbConn = xcb_connect(nullptr, nullptr); if (!m_xcbConn) { qDebug() << "Could not open XCB connection."; } } void XBackend::lockRequest(bool persistent, QRect region) { Q_UNUSED(persistent); Q_UNUSED(region); auto winId = view()->winId(); /* Cursor needs to be hidden such that Xwayland emulates warps. */ QGuiApplication::setOverrideCursor(QCursor(Qt::BlankCursor)); auto cookie = xcb_warp_pointer_checked(m_xcbConn, /* connection */ XCB_NONE, /* src_w */ winId, /* dest_w */ 0, /* src_x */ 0, /* src_y */ 0, /* src_width */ 0, /* src_height */ 20, /* dest_x */ 20 /* dest_y */ ); xcb_flush(m_xcbConn); xcb_generic_error_t *error = xcb_request_check(m_xcbConn, cookie); if (error) { qDebug() << "Lock (warp) failed with XCB error:" << error->error_code; free(error); return; } qDebug() << "LOCK (warp)"; Q_EMIT lockChanged(true); } void XBackend::unlockRequest() { /* Xwayland unlocks the pointer, when the cursor is shown again. */ QGuiApplication::restoreOverrideCursor(); qDebug() << "------ Unlock requested ------"; Q_EMIT lockChanged(false); } void XBackend::confineRequest(bool persistent, QRect region) { Q_UNUSED(persistent); Q_UNUSED(region); int error; if (!tryConfine(error)) { qDebug() << "Confine (grab) failed with XCB error:" << error; return; } qDebug() << "CONFINE (grab)"; Q_EMIT confineChanged(true); } void XBackend::unconfineRequest() { auto cookie = xcb_ungrab_pointer_checked(m_xcbConn, XCB_CURRENT_TIME); xcb_flush(m_xcbConn); xcb_generic_error_t *error = xcb_request_check(m_xcbConn, cookie); if (error) { qDebug() << "Unconfine failed with XCB error:" << error->error_code; free(error); return; } qDebug() << "UNCONFINE (ungrab)"; Q_EMIT confineChanged(false); } void XBackend::hideAndConfineRequest(bool confineBeforeHide) { if (!confineBeforeHide) { QGuiApplication::setOverrideCursor(QCursor(Qt::BlankCursor)); } int error; if (!tryConfine(error)) { qDebug() << "Confine failed with XCB error:" << error; if (!confineBeforeHide) { QGuiApplication::restoreOverrideCursor(); } return; } if (confineBeforeHide) { QGuiApplication::setOverrideCursor(QCursor(Qt::BlankCursor)); } qDebug() << "HIDE AND CONFINE (lock)"; Q_EMIT confineChanged(true); } void XBackend::undoHideRequest() { QGuiApplication::restoreOverrideCursor(); qDebug() << "UNDO HIDE AND CONFINE (unlock)"; } bool XBackend::tryConfine(int &error) { auto winId = view()->winId(); auto cookie = xcb_grab_pointer(m_xcbConn, /* display */ 1, /* owner_events */ winId, /* grab_window */ 0, /* event_mask */ XCB_GRAB_MODE_ASYNC, /* pointer_mode */ XCB_GRAB_MODE_ASYNC, /* keyboard_mode */ winId, /* confine_to */ XCB_NONE, /* cursor */ XCB_CURRENT_TIME /* time */ ); xcb_flush(m_xcbConn); xcb_generic_error_t *e = nullptr; auto *reply = xcb_grab_pointer_reply(m_xcbConn, cookie, &e); if (!reply) { error = e->error_code; free(e); return false; } free(reply); return true; } int main(int argc, char **argv) { QGuiApplication app(argc, argv); Backend *backend; if (app.platformName() == QStringLiteral("wayland")) { qDebug() << "Starting up: Wayland native mode"; backend = new WaylandBackend(&app); } else { qDebug() << "Starting up: Xserver/Xwayland legacy mode"; backend = new XBackend(&app); } QQuickView view; QQmlContext* context = view.engine()->rootContext(); context->setContextProperty(QStringLiteral("org_kde_kwin_tests_pointerconstraints_backend"), backend); view.setSource(QUrl::fromLocalFile(QStringLiteral(DIR) +QStringLiteral("/pointerconstraintstest.qml"))); view.show(); backend->init(&view); return app.exec(); } diff --git a/thumbnailitem.cpp b/thumbnailitem.cpp index b3cf84cad..662512a85 100644 --- a/thumbnailitem.cpp +++ b/thumbnailitem.cpp @@ -1,221 +1,221 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 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 "thumbnailitem.h" // KWin #include "client.h" #include "composite.h" #include "effects.h" #include "workspace.h" #include "shell_client.h" #include "wayland_server.h" // Qt #include #include #include namespace KWin { AbstractThumbnailItem::AbstractThumbnailItem(QQuickItem *parent) : QQuickPaintedItem(parent) , m_brightness(1.0) , m_saturation(1.0) , m_clipToItem() { connect(Compositor::self(), SIGNAL(compositingToggled(bool)), SLOT(compositingToggled())); compositingToggled(); QTimer::singleShot(0, this, SLOT(init())); } AbstractThumbnailItem::~AbstractThumbnailItem() { } void AbstractThumbnailItem::compositingToggled() { m_parent.clear(); if (effects) { connect(effects, SIGNAL(windowAdded(KWin::EffectWindow*)), SLOT(effectWindowAdded())); connect(effects, SIGNAL(windowDamaged(KWin::EffectWindow*,QRect)), SLOT(repaint(KWin::EffectWindow*))); effectWindowAdded(); } } void AbstractThumbnailItem::init() { findParentEffectWindow(); if (m_parent) { m_parent->registerThumbnail(this); } } void AbstractThumbnailItem::findParentEffectWindow() { if (effects) { QQuickWindow *qw = window(); if (!qw) { qCDebug(KWIN_CORE) << "No QQuickWindow assigned yet"; return; } if (auto *w = static_cast(effects->findWindow(qw))) { m_parent = QPointer(w); } } } void AbstractThumbnailItem::effectWindowAdded() { // the window might be added before the EffectWindow is created // by using this slot we can register the thumbnail when it is finally created if (m_parent.isNull()) { findParentEffectWindow(); if (m_parent) { m_parent->registerThumbnail(this); } } } void AbstractThumbnailItem::setBrightness(qreal brightness) { if (qFuzzyCompare(brightness, m_brightness)) { return; } m_brightness = brightness; update(); emit brightnessChanged(); } void AbstractThumbnailItem::setSaturation(qreal saturation) { if (qFuzzyCompare(saturation, m_saturation)) { return; } m_saturation = saturation; update(); emit saturationChanged(); } void AbstractThumbnailItem::setClipTo(QQuickItem *clip) { m_clipToItem = QPointer(clip); emit clipToChanged(); } WindowThumbnailItem::WindowThumbnailItem(QQuickItem* parent) : AbstractThumbnailItem(parent) - , m_wId(0) - , m_client(NULL) + , m_wId(nullptr) + , m_client(nullptr) { } WindowThumbnailItem::~WindowThumbnailItem() { } void WindowThumbnailItem::setWId(const QUuid &wId) { if (m_wId == wId) { return; } m_wId = wId; - if (m_wId != 0) { + if (m_wId != nullptr) { setClient(workspace()->findAbstractClient([this] (const AbstractClient *c) { return c->internalId() == m_wId; })); } else if (m_client) { - m_client = NULL; + m_client = nullptr; emit clientChanged(); } emit wIdChanged(wId); } void WindowThumbnailItem::setClient(AbstractClient *client) { if (m_client == client) { return; } m_client = client; if (m_client) { setWId(m_client->internalId()); } else { setWId({}); } emit clientChanged(); } void WindowThumbnailItem::paint(QPainter *painter) { if (effects) { return; } auto client = workspace()->findAbstractClient([this] (const AbstractClient *c) { return c->internalId() == m_wId; }); if (!client) { return; } QPixmap pixmap = client->icon().pixmap(boundingRect().size().toSize()); const QSize size(boundingRect().size().toSize() - pixmap.size()); painter->drawPixmap(boundingRect().adjusted(size.width()/2.0, size.height()/2.0, -size.width()/2.0, -size.height()/2.0).toRect(), pixmap); } void WindowThumbnailItem::repaint(KWin::EffectWindow *w) { if (static_cast(w)->window()->internalId() == m_wId) { update(); } } DesktopThumbnailItem::DesktopThumbnailItem(QQuickItem *parent) : AbstractThumbnailItem(parent) , m_desktop(0) { } DesktopThumbnailItem::~DesktopThumbnailItem() { } void DesktopThumbnailItem::setDesktop(int desktop) { desktop = qBound(1, desktop, VirtualDesktopManager::self()->count()); if (desktop == m_desktop) { return; } m_desktop = desktop; update(); emit desktopChanged(m_desktop); } void DesktopThumbnailItem::paint(QPainter *painter) { Q_UNUSED(painter) if (effects) { return; } // TODO: render icon } void DesktopThumbnailItem::repaint(EffectWindow *w) { if (w->isOnDesktop(m_desktop)) { update(); } } } // namespace KWin diff --git a/thumbnailitem.h b/thumbnailitem.h index 2c2cc065a..4c23d7a1a 100644 --- a/thumbnailitem.h +++ b/thumbnailitem.h @@ -1,150 +1,150 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 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 . *********************************************************************/ #ifndef KWIN_THUMBNAILITEM_H #define KWIN_THUMBNAILITEM_H #include #include #include #include namespace KWin { class AbstractClient; class EffectWindow; class EffectWindowImpl; class AbstractThumbnailItem : public QQuickPaintedItem { Q_OBJECT Q_PROPERTY(qreal brightness READ brightness WRITE setBrightness NOTIFY brightnessChanged) Q_PROPERTY(qreal saturation READ saturation WRITE setSaturation NOTIFY saturationChanged) Q_PROPERTY(QQuickItem *clipTo READ clipTo WRITE setClipTo NOTIFY clipToChanged) public: ~AbstractThumbnailItem() override; qreal brightness() const; qreal saturation() const; QQuickItem *clipTo() const; public Q_SLOTS: void setBrightness(qreal brightness); void setSaturation(qreal saturation); void setClipTo(QQuickItem *clip); Q_SIGNALS: void brightnessChanged(); void saturationChanged(); void clipToChanged(); protected: - explicit AbstractThumbnailItem(QQuickItem *parent = 0); + explicit AbstractThumbnailItem(QQuickItem *parent = nullptr); protected Q_SLOTS: virtual void repaint(KWin::EffectWindow* w) = 0; private Q_SLOTS: void init(); void effectWindowAdded(); void compositingToggled(); private: void findParentEffectWindow(); QPointer m_parent; qreal m_brightness; qreal m_saturation; QPointer m_clipToItem; }; class WindowThumbnailItem : public AbstractThumbnailItem { Q_OBJECT Q_PROPERTY(QUuid wId READ wId WRITE setWId NOTIFY wIdChanged SCRIPTABLE true) Q_PROPERTY(KWin::AbstractClient *client READ client WRITE setClient NOTIFY clientChanged) public: - explicit WindowThumbnailItem(QQuickItem *parent = 0); + explicit WindowThumbnailItem(QQuickItem *parent = nullptr); ~WindowThumbnailItem() override; QUuid wId() const { return m_wId; } void setWId(const QUuid &wId); AbstractClient *client() const; void setClient(AbstractClient *client); void paint(QPainter *painter) override; Q_SIGNALS: void wIdChanged(const QUuid &wid); void clientChanged(); protected Q_SLOTS: void repaint(KWin::EffectWindow* w) override; private: QUuid m_wId; AbstractClient *m_client; }; class DesktopThumbnailItem : public AbstractThumbnailItem { Q_OBJECT Q_PROPERTY(int desktop READ desktop WRITE setDesktop NOTIFY desktopChanged) public: - DesktopThumbnailItem(QQuickItem *parent = 0); + DesktopThumbnailItem(QQuickItem *parent = nullptr); ~DesktopThumbnailItem() override; int desktop() const { return m_desktop; } void setDesktop(int desktop); void paint(QPainter *painter) override; Q_SIGNALS: void desktopChanged(int desktop); protected Q_SLOTS: void repaint(KWin::EffectWindow* w) override; private: int m_desktop; }; inline qreal AbstractThumbnailItem::brightness() const { return m_brightness; } inline qreal AbstractThumbnailItem::saturation() const { return m_saturation; } inline QQuickItem* AbstractThumbnailItem::clipTo() const { return m_clipToItem.data(); } inline AbstractClient *WindowThumbnailItem::client() const { return m_client; } } // KWin #endif // KWIN_THUMBNAILITEM_H diff --git a/toplevel.cpp b/toplevel.cpp index ec23d754f..88cee2fb7 100644 --- a/toplevel.cpp +++ b/toplevel.cpp @@ -1,808 +1,808 @@ /******************************************************************** 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(NULL) + , info(nullptr) , ready_for_painting(true) , m_isDamaged(false) , m_internalId(QUuid::createUuid()) , m_client() , damage_handle(XCB_NONE) , is_shape(false) - , effect_window(NULL) + , 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 == NULL) + 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 != NULL) + 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 = NULL; + 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 = NULL; + effect_window = nullptr; } void Toplevel::discardWindowPixmap() { addDamageFull(); - if (effectWindow() != NULL && effectWindow()->sceneWindow() != NULL) + 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, 0); + 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, 0); + 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 = NULL; + 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; } 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() != NULL; + return effectWindow()->sceneWindow()->shadow() != nullptr; } return false; } Shadow *Toplevel::shadow() { if (effectWindow() && effectWindow()->sceneWindow()) { return effectWindow()->sceneWindow()->shadow(); } else { - return NULL; + return nullptr; } } const Shadow *Toplevel::shadow() const { if (effectWindow() && effectWindow()->sceneWindow()) { return effectWindow()->sceneWindow()->shadow(); } else { - return NULL; + 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/unmanaged.cpp b/unmanaged.cpp index 393b1363f..1e5b6231f 100644 --- a/unmanaged.cpp +++ b/unmanaged.cpp @@ -1,204 +1,204 @@ /******************************************************************** 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 "unmanaged.h" #include "workspace.h" #include "effects.h" #include "deleted.h" #include "utils.h" #include "xcbutils.h" #include #include #include #include namespace KWin { Unmanaged::Unmanaged() : Toplevel() { ready_for_painting = false; connect(this, SIGNAL(geometryShapeChanged(KWin::Toplevel*,QRect)), SIGNAL(geometryChanged())); QTimer::singleShot(50, this, SLOT(setReadyForPainting())); } Unmanaged::~Unmanaged() { } bool Unmanaged::track(xcb_window_t w) { GRAB_SERVER_DURING_CONTEXT Xcb::WindowAttributes attr(w); Xcb::WindowGeometry geo(w); if (attr.isNull() || attr->map_state != XCB_MAP_STATE_VIEWABLE) { return false; } if (attr->_class == XCB_WINDOW_CLASS_INPUT_ONLY) { return false; } if (geo.isNull()) { return false; } setWindowHandles(w); // the window is also the frame Xcb::selectInput(w, attr->your_event_mask | XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_PROPERTY_CHANGE); geom = geo.rect(); checkScreen(); m_visual = attr->visual; bit_depth = geo->depth; info = new NETWinInfo(connection(), w, rootWindow(), NET::WMWindowType | NET::WMPid, NET::WM2Opacity | NET::WM2WindowRole | NET::WM2WindowClass | NET::WM2OpaqueRegion); getResourceClass(); getWmClientLeader(); getWmClientMachine(); if (Xcb::Extensions::self()->isShapeAvailable()) xcb_shape_select_input(connection(), w, true); detectShape(w); getWmOpaqueRegion(); getSkipCloseAnimation(); setupCompositing(); if (QWindow *internalWindow = findInternalWindow()) { m_outline = internalWindow->property("__kwin_outline").toBool(); } if (effects) static_cast(effects)->checkInputWindowStacking(); return true; } void Unmanaged::release(ReleaseReason releaseReason) { - Deleted* del = NULL; + Deleted* del = nullptr; if (releaseReason != ReleaseReason::KWinShutsDown) { del = Deleted::create(this); } emit windowClosed(this, del); finishCompositing(releaseReason); if (!QWidget::find(window()) && releaseReason != ReleaseReason::Destroyed) { // don't affect our own windows if (Xcb::Extensions::self()->isShapeAvailable()) xcb_shape_select_input(connection(), window(), false); Xcb::selectInput(window(), XCB_EVENT_MASK_NO_EVENT); } if (releaseReason != ReleaseReason::KWinShutsDown) { workspace()->removeUnmanaged(this); addWorkspaceRepaint(del->visibleRect()); disownDataPassedToDeleted(); del->unrefWindow(); } deleteUnmanaged(this); } void Unmanaged::deleteUnmanaged(Unmanaged* c) { delete c; } int Unmanaged::desktop() const { return NET::OnAllDesktops; // TODO for some window types should be the current desktop? } QStringList Unmanaged::activities() const { return QStringList(); } QVector Unmanaged::desktops() const { return QVector(); } QPoint Unmanaged::clientPos() const { return QPoint(0, 0); // unmanaged windows don't have decorations } QSize Unmanaged::clientSize() const { return size(); } QRect Unmanaged::transparentRect() const { return QRect(clientPos(), clientSize()); } void Unmanaged::debug(QDebug& stream) const { stream << "\'ID:" << window() << "\'"; } NET::WindowType Unmanaged::windowType(bool direct, int supportedTypes) const { // for unmanaged windows the direct does not make any difference // as there are no rules to check and no hacks to apply Q_UNUSED(direct) if (supportedTypes == 0) { supportedTypes = SUPPORTED_UNMANAGED_WINDOW_TYPES_MASK; } return info->windowType(NET::WindowTypes(supportedTypes)); } bool Unmanaged::isOutline() const { return m_outline; } void Unmanaged::addDamage(const QRegion &damage) { repaints_region += damage; Toplevel::addDamage(damage); } QWindow *Unmanaged::findInternalWindow() const { const QWindowList windows = kwinApp()->topLevelWindows(); for (QWindow *w : windows) { if (w->winId() == window()) { return w; } } return nullptr; } bool Unmanaged::setupCompositing() { if (!Toplevel::setupCompositing()) { return false; } // With unmanaged windows there is a race condition between the client painting the window // and us setting up damage tracking. If the client wins we won't get a damage event even // though the window has been painted. To avoid this we mark the whole window as damaged // and schedule a repaint immediately after creating the damage object. addDamageFull(); return true; } } // namespace diff --git a/useractions.cpp b/useractions.cpp index aa998c287..d57674fab 100644 --- a/useractions.cpp +++ b/useractions.cpp @@ -1,1750 +1,1750 @@ /******************************************************************** 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 . *********************************************************************/ /* This file contains things relevant to direct user actions, such as responses to global keyboard shortcuts, or selecting actions from the window operations menu. */ /////////////////////////////////////////////////////////////////////////////// // NOTE: if you change the menu, keep // plasma-desktop/applets/taskmanager/package/contents/ui/ContextMenu.qml // in sync ////////////////////////////////////////////////////////////////////////////// #include "useractions.h" #include "cursor.h" #include "client.h" #include "colorcorrection/manager.h" #include "composite.h" #include "input.h" #include "workspace.h" #include "effects.h" #include "platform.h" #include "screens.h" #include "shell_client.h" #include "virtualdesktops.h" #include "scripting/scripting.h" #ifdef KWIN_BUILD_ACTIVITIES #include "activities.h" #include #endif #include "appmenu.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "killwindow.h" #ifdef KWIN_BUILD_TABBOX #include "tabbox.h" #endif namespace KWin { UserActionsMenu::UserActionsMenu(QObject *parent) : QObject(parent) - , m_menu(NULL) - , m_desktopMenu(NULL) - , m_screenMenu(NULL) - , m_activityMenu(NULL) - , m_scriptsMenu(NULL) - , m_resizeOperation(NULL) - , m_moveOperation(NULL) - , m_maximizeOperation(NULL) - , m_shadeOperation(NULL) - , m_keepAboveOperation(NULL) - , m_keepBelowOperation(NULL) - , m_fullScreenOperation(NULL) - , m_noBorderOperation(NULL) - , m_minimizeOperation(NULL) - , m_closeOperation(NULL) + , m_menu(nullptr) + , m_desktopMenu(nullptr) + , m_screenMenu(nullptr) + , m_activityMenu(nullptr) + , m_scriptsMenu(nullptr) + , m_resizeOperation(nullptr) + , m_moveOperation(nullptr) + , m_maximizeOperation(nullptr) + , m_shadeOperation(nullptr) + , m_keepAboveOperation(nullptr) + , m_keepBelowOperation(nullptr) + , m_fullScreenOperation(nullptr) + , m_noBorderOperation(nullptr) + , m_minimizeOperation(nullptr) + , m_closeOperation(nullptr) { } UserActionsMenu::~UserActionsMenu() { discard(); } bool UserActionsMenu::isShown() const { return m_menu && m_menu->isVisible(); } bool UserActionsMenu::hasClient() const { return m_client && isShown(); } void UserActionsMenu::close() { if (!m_menu) { return; } m_menu->close(); m_client.clear(); } bool UserActionsMenu::isMenuClient(const AbstractClient *c) const { return c && c == m_client; } void UserActionsMenu::show(const QRect &pos, AbstractClient *client) { Q_ASSERT(client); QPointer cl(client); // Presumably client will never be nullptr, // but play it safe and make sure not to crash. if (cl.isNull()) { return; } if (isShown()) { // recursion return; } if (cl->isDesktop() || cl->isDock()) { return; } if (!KAuthorized::authorizeAction(QStringLiteral("kwin_rmb"))) { return; } m_client = cl; init(); m_client->blockActivityUpdates(true); if (kwinApp()->shouldUseWaylandForCompositing()) { m_menu->popup(pos.bottomLeft()); } else { m_menu->exec(pos.bottomLeft()); } if (m_client) { m_client->blockActivityUpdates(false); } } void UserActionsMenu::grabInput() { m_menu->windowHandle()->setMouseGrabEnabled(true); m_menu->windowHandle()->setKeyboardGrabEnabled(true); } void UserActionsMenu::helperDialog(const QString& message, AbstractClient* client) { QStringList args; QString type; auto shortcut = [](const QString &name) { QAction* action = Workspace::self()->findChild(name); - Q_ASSERT(action != NULL); + Q_ASSERT(action != nullptr); const auto shortcuts = KGlobalAccel::self()->shortcut(action); return QStringLiteral("%1 (%2)").arg(action->text()) .arg(shortcuts.isEmpty() ? QString() : shortcuts.first().toString(QKeySequence::NativeText)); }; if (message == QStringLiteral("noborderaltf3")) { args << QStringLiteral("--msgbox") << i18n( "You have selected to show a window without its border.\n" "Without the border, you will not be able to enable the border " "again using the mouse: use the window operations menu instead, " "activated using the %1 keyboard shortcut.", shortcut(QStringLiteral("Window Operations Menu"))); type = QStringLiteral("altf3warning"); } else if (message == QLatin1String("fullscreenaltf3")) { args << QStringLiteral("--msgbox") << i18n( "You have selected to show a window in fullscreen mode.\n" "If the application itself does not have an option to turn the fullscreen " "mode off you will not be able to disable it " "again using the mouse: use the window operations menu instead, " "activated using the %1 keyboard shortcut.", shortcut(QStringLiteral("Window Operations Menu"))); type = QStringLiteral("altf3warning"); } else abort(); if (!type.isEmpty()) { KConfig cfg(QStringLiteral("kwin_dialogsrc")); KConfigGroup cg(&cfg, "Notification Messages"); // Depends on KMessageBox if (!cg.readEntry(type, true)) return; args << QStringLiteral("--dontagain") << QLatin1String("kwin_dialogsrc:") + type; } if (client) args << QStringLiteral("--embed") << QString::number(client->windowId()); QtConcurrent::run([args]() { KProcess::startDetached(QStringLiteral("kdialog"), args); }); } QStringList configModules(bool controlCenter) { QStringList args; args << QStringLiteral("kwindecoration"); if (controlCenter) args << QStringLiteral("kwinoptions"); else if (KAuthorized::authorizeControlModule(QStringLiteral("kde-kwinoptions.desktop"))) args << QStringLiteral("kwinactions") << QStringLiteral("kwinfocus") << QStringLiteral("kwinmoving") << QStringLiteral("kwinadvanced") << QStringLiteral("kwinrules") << QStringLiteral("kwincompositing") << QStringLiteral("kwineffects") #ifdef KWIN_BUILD_TABBOX << QStringLiteral("kwintabbox") #endif << QStringLiteral("kwinscreenedges") << QStringLiteral("kwinscripts") ; return args; } void UserActionsMenu::init() { if (m_menu) { return; } m_menu = new QMenu; connect(m_menu, &QMenu::aboutToShow, this, &UserActionsMenu::menuAboutToShow); connect(m_menu, &QMenu::triggered, this, &UserActionsMenu::slotWindowOperation, Qt::QueuedConnection); QMenu *advancedMenu = new QMenu(m_menu); connect(advancedMenu, &QMenu::aboutToShow, [this, advancedMenu]() { if (m_client) { advancedMenu->setPalette(m_client->palette()); } }); auto setShortcut = [](QAction *action, const QString &actionName) { const auto shortcuts = KGlobalAccel::self()->shortcut(Workspace::self()->findChild(actionName)); if (!shortcuts.isEmpty()) { action->setShortcut(shortcuts.first()); } }; m_moveOperation = advancedMenu->addAction(i18n("&Move")); m_moveOperation->setIcon(QIcon::fromTheme(QStringLiteral("transform-move"))); setShortcut(m_moveOperation, QStringLiteral("Window Move")); m_moveOperation->setData(Options::UnrestrictedMoveOp); m_resizeOperation = advancedMenu->addAction(i18n("&Resize")); m_resizeOperation->setIcon(QIcon::fromTheme(QStringLiteral("transform-scale"))); setShortcut(m_resizeOperation, QStringLiteral("Window Resize")); m_resizeOperation->setData(Options::ResizeOp); m_keepAboveOperation = advancedMenu->addAction(i18n("Keep &Above Others")); m_keepAboveOperation->setIcon(QIcon::fromTheme(QStringLiteral("window-keep-above"))); setShortcut(m_keepAboveOperation, QStringLiteral("Window Above Other Windows")); m_keepAboveOperation->setCheckable(true); m_keepAboveOperation->setData(Options::KeepAboveOp); m_keepBelowOperation = advancedMenu->addAction(i18n("Keep &Below Others")); m_keepBelowOperation->setIcon(QIcon::fromTheme(QStringLiteral("window-keep-below"))); setShortcut(m_keepBelowOperation, QStringLiteral("Window Below Other Windows")); m_keepBelowOperation->setCheckable(true); m_keepBelowOperation->setData(Options::KeepBelowOp); m_fullScreenOperation = advancedMenu->addAction(i18n("&Fullscreen")); m_fullScreenOperation->setIcon(QIcon::fromTheme(QStringLiteral("view-fullscreen"))); setShortcut(m_fullScreenOperation, QStringLiteral("Window Fullscreen")); m_fullScreenOperation->setCheckable(true); m_fullScreenOperation->setData(Options::FullScreenOp); m_shadeOperation = advancedMenu->addAction(i18n("&Shade")); m_shadeOperation->setIcon(QIcon::fromTheme(QStringLiteral("window-shade"))); setShortcut(m_shadeOperation, QStringLiteral("Window Shade")); m_shadeOperation->setCheckable(true); m_shadeOperation->setData(Options::ShadeOp); m_noBorderOperation = advancedMenu->addAction(i18n("&No Border")); m_noBorderOperation->setIcon(QIcon::fromTheme(QStringLiteral("edit-none-border"))); setShortcut(m_noBorderOperation, QStringLiteral("Window No Border")); m_noBorderOperation->setCheckable(true); m_noBorderOperation->setData(Options::NoBorderOp); advancedMenu->addSeparator(); m_shortcutOperation = advancedMenu->addAction(i18n("Set Window Short&cut...")); m_shortcutOperation->setIcon(QIcon::fromTheme(QStringLiteral("configure-shortcuts"))); setShortcut(m_shortcutOperation, QStringLiteral("Setup Window Shortcut")); m_shortcutOperation->setData(Options::SetupWindowShortcutOp); QAction *action = advancedMenu->addAction(i18n("Configure Special &Window Settings...")); action->setIcon(QIcon::fromTheme(QStringLiteral("preferences-system-windows-actions"))); action->setData(Options::WindowRulesOp); m_rulesOperation = action; action = advancedMenu->addAction(i18n("Configure S&pecial Application Settings...")); action->setIcon(QIcon::fromTheme(QStringLiteral("preferences-system-windows-actions"))); action->setData(Options::ApplicationRulesOp); m_applicationRulesOperation = action; if (!kwinApp()->config()->isImmutable() && !KAuthorized::authorizeControlModules(configModules(true)).isEmpty()) { advancedMenu->addSeparator(); action = advancedMenu->addAction(i18nc("Entry in context menu of window decoration to open the configuration module of KWin", "Configure W&indow Manager...")); action->setIcon(QIcon::fromTheme(QStringLiteral("configure"))); connect(action, &QAction::triggered, this, [this]() { // opens the KWin configuration QStringList args; args << QStringLiteral("--icon") << QStringLiteral("preferences-system-windows"); const QString path = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("kservices5/kwinfocus.desktop")); if (!path.isEmpty()) { args << QStringLiteral("--desktopfile") << path; } args << configModules(false); QProcess *p = new Process(this); p->setArguments(args); p->setProcessEnvironment(kwinApp()->processStartupEnvironment()); p->setProgram(QStringLiteral("kcmshell5")); connect(p, static_cast(&QProcess::finished), p, &QProcess::deleteLater); connect(p, static_cast(&QProcess::error), this, [p] (QProcess::ProcessError e) { if (e == QProcess::FailedToStart) { qCDebug(KWIN_CORE) << "Failed to start kcmshell5"; } } ); p->start(); } ); } m_maximizeOperation = m_menu->addAction(i18n("Ma&ximize")); m_maximizeOperation->setIcon(QIcon::fromTheme(QStringLiteral("window-maximize"))); setShortcut(m_maximizeOperation, QStringLiteral("Window Maximize")); m_maximizeOperation->setCheckable(true); m_maximizeOperation->setData(Options::MaximizeOp); m_minimizeOperation = m_menu->addAction(i18n("Mi&nimize")); m_minimizeOperation->setIcon(QIcon::fromTheme(QStringLiteral("window-minimize"))); setShortcut(m_minimizeOperation, QStringLiteral("Window Minimize")); m_minimizeOperation->setData(Options::MinimizeOp); action = m_menu->addMenu(advancedMenu); action->setText(i18n("&More Actions")); action->setIcon(QIcon::fromTheme(QStringLiteral("view-more-symbolic"))); m_closeOperation = m_menu->addAction(i18n("&Close")); m_closeOperation->setIcon(QIcon::fromTheme(QStringLiteral("window-close"))); setShortcut(m_closeOperation, QStringLiteral("Window Close")); m_closeOperation->setData(Options::CloseOp); } void UserActionsMenu::discard() { delete m_menu; - m_menu = NULL; - m_desktopMenu = NULL; + m_menu = nullptr; + m_desktopMenu = nullptr; m_multipleDesktopsMenu = nullptr; - m_screenMenu = NULL; - m_activityMenu = NULL; - m_scriptsMenu = NULL; + m_screenMenu = nullptr; + m_activityMenu = nullptr; + m_scriptsMenu = nullptr; } void UserActionsMenu::menuAboutToShow() { if (m_client.isNull() || !m_menu) return; if (VirtualDesktopManager::self()->count() == 1) { delete m_desktopMenu; - m_desktopMenu = 0; + m_desktopMenu = nullptr; delete m_multipleDesktopsMenu; m_multipleDesktopsMenu = nullptr; } else { initDesktopPopup(); } if (screens()->count() == 1 || (!m_client->isMovable() && !m_client->isMovableAcrossScreens())) { delete m_screenMenu; - m_screenMenu = NULL; + m_screenMenu = nullptr; } else { initScreenPopup(); } m_menu->setPalette(m_client->palette()); m_resizeOperation->setEnabled(m_client->isResizable()); m_moveOperation->setEnabled(m_client->isMovableAcrossScreens()); m_maximizeOperation->setEnabled(m_client->isMaximizable()); m_maximizeOperation->setChecked(m_client->maximizeMode() == MaximizeFull); m_shadeOperation->setEnabled(m_client->isShadeable()); m_shadeOperation->setChecked(m_client->shadeMode() != ShadeNone); m_keepAboveOperation->setChecked(m_client->keepAbove()); m_keepBelowOperation->setChecked(m_client->keepBelow()); m_fullScreenOperation->setEnabled(m_client->userCanSetFullScreen()); m_fullScreenOperation->setChecked(m_client->isFullScreen()); m_noBorderOperation->setEnabled(m_client->userCanSetNoBorder()); m_noBorderOperation->setChecked(m_client->noBorder()); m_minimizeOperation->setEnabled(m_client->isMinimizable()); m_closeOperation->setEnabled(m_client->isCloseable()); m_shortcutOperation->setEnabled(m_client->rules()->checkShortcut(QString()).isNull()); // drop the existing scripts menu delete m_scriptsMenu; m_scriptsMenu = nullptr; // ask scripts whether they want to add entries for the given Client QList scriptActions = Scripting::self()->actionsForUserActionMenu(m_client.data(), m_scriptsMenu); if (!scriptActions.isEmpty()) { m_scriptsMenu = new QMenu(m_menu); m_scriptsMenu->setPalette(m_client->palette()); m_scriptsMenu->addActions(scriptActions); QAction *action = m_scriptsMenu->menuAction(); // set it as the first item after desktop m_menu->insertAction(m_closeOperation, action); action->setText(i18n("&Extensions")); } m_rulesOperation->setEnabled(m_client->supportsWindowRules()); m_applicationRulesOperation->setEnabled(m_client->supportsWindowRules()); showHideActivityMenu(); } void UserActionsMenu::showHideActivityMenu() { #ifdef KWIN_BUILD_ACTIVITIES if (!Activities::self()) { return; } const QStringList &openActivities_ = Activities::self()->running(); qCDebug(KWIN_CORE) << "activities:" << openActivities_.size(); if (openActivities_.size() < 2) { delete m_activityMenu; - m_activityMenu = 0; + m_activityMenu = nullptr; } else { initActivityPopup(); } #endif } void UserActionsMenu::initDesktopPopup() { if (kwinApp()->operationMode() == Application::OperationModeWaylandOnly || kwinApp()->operationMode() == Application::OperationModeXwayland) { if (m_multipleDesktopsMenu) { return; } m_multipleDesktopsMenu = new QMenu(m_menu); connect(m_multipleDesktopsMenu, &QMenu::triggered, this, &UserActionsMenu::slotToggleOnVirtualDesktop); connect(m_multipleDesktopsMenu, &QMenu::aboutToShow, this, &UserActionsMenu::multipleDesktopsPopupAboutToShow); QAction *action = m_multipleDesktopsMenu->menuAction(); // set it as the first item m_menu->insertAction(m_maximizeOperation, action); action->setText(i18n("&Desktops")); action->setIcon(QIcon::fromTheme(QStringLiteral("virtual-desktops"))); } else { if (m_desktopMenu) return; m_desktopMenu = new QMenu(m_menu); connect(m_desktopMenu, &QMenu::triggered, this, &UserActionsMenu::slotSendToDesktop); connect(m_desktopMenu, &QMenu::aboutToShow, this, &UserActionsMenu::desktopPopupAboutToShow); QAction *action = m_desktopMenu->menuAction(); // set it as the first item m_menu->insertAction(m_maximizeOperation, action); action->setText(i18n("Move to &Desktop")); action->setIcon(QIcon::fromTheme(QStringLiteral("virtual-desktops"))); } } void UserActionsMenu::initScreenPopup() { if (m_screenMenu) { return; } m_screenMenu = new QMenu(m_menu); connect(m_screenMenu, &QMenu::triggered, this, &UserActionsMenu::slotSendToScreen); connect(m_screenMenu, &QMenu::aboutToShow, this, &UserActionsMenu::screenPopupAboutToShow); QAction *action = m_screenMenu->menuAction(); // set it as the first item after desktop m_menu->insertAction(m_activityMenu ? m_activityMenu->menuAction() : m_minimizeOperation, action); action->setText(i18n("Move to &Screen")); action->setIcon(QIcon::fromTheme(QStringLiteral("computer"))); } void UserActionsMenu::initActivityPopup() { if (m_activityMenu) return; m_activityMenu = new QMenu(m_menu); connect(m_activityMenu, &QMenu::triggered, this, &UserActionsMenu::slotToggleOnActivity); connect(m_activityMenu, &QMenu::aboutToShow, this, &UserActionsMenu::activityPopupAboutToShow); QAction *action = m_activityMenu->menuAction(); // set it as the first item m_menu->insertAction(m_maximizeOperation, action); action->setText(i18n("Show in &Activities")); action->setIcon(QIcon::fromTheme(QStringLiteral("activities"))); } void UserActionsMenu::desktopPopupAboutToShow() { if (!m_desktopMenu) return; const VirtualDesktopManager *vds = VirtualDesktopManager::self(); m_desktopMenu->clear(); if (m_client) { m_desktopMenu->setPalette(m_client->palette()); } QActionGroup *group = new QActionGroup(m_desktopMenu); QAction *action = m_desktopMenu->addAction(i18n("&All Desktops")); action->setData(0); action->setCheckable(true); group->addAction(action); if (m_client && m_client->isOnAllDesktops()) { action->setChecked(true); } m_desktopMenu->addSeparator(); const uint BASE = 10; for (uint i = 1; i <= vds->count(); ++i) { QString basic_name(QStringLiteral("%1 %2")); if (i < BASE) { basic_name.prepend(QLatin1Char('&')); } action = m_desktopMenu->addAction(basic_name.arg(i).arg(vds->name(i).replace(QLatin1Char('&'), QStringLiteral("&&")))); action->setData(i); action->setCheckable(true); group->addAction(action); if (m_client && !m_client->isOnAllDesktops() && m_client->isOnDesktop(i)) { action->setChecked(true); } } m_desktopMenu->addSeparator(); action = m_desktopMenu->addAction(i18nc("Create a new desktop and move there the window", "&New Desktop")); action->setData(vds->count() + 1); if (vds->count() >= vds->maximum()) action->setEnabled(false); } void UserActionsMenu::multipleDesktopsPopupAboutToShow() { if (!m_multipleDesktopsMenu) return; const VirtualDesktopManager *vds = VirtualDesktopManager::self(); m_multipleDesktopsMenu->clear(); if (m_client) { m_multipleDesktopsMenu->setPalette(m_client->palette()); } QAction *action = m_multipleDesktopsMenu->addAction(i18n("&All Desktops")); action->setData(0); action->setCheckable(true); QActionGroup *allDesktopsGroup = new QActionGroup(m_multipleDesktopsMenu); allDesktopsGroup->addAction(action); if (m_client && m_client->isOnAllDesktops()) { action->setChecked(true); } m_multipleDesktopsMenu->addSeparator(); const uint BASE = 10; for (uint i = 1; i <= vds->count(); ++i) { QString basic_name(QStringLiteral("%1 %2")); if (i < BASE) { basic_name.prepend(QLatin1Char('&')); } QWidgetAction *action = new QWidgetAction(m_multipleDesktopsMenu); QCheckBox *box = new QCheckBox(basic_name.arg(i).arg(vds->name(i).replace(QLatin1Char('&'), QStringLiteral("&&"))), m_multipleDesktopsMenu); action->setDefaultWidget(box); box->setBackgroundRole(m_multipleDesktopsMenu->backgroundRole()); box->setForegroundRole(m_multipleDesktopsMenu->foregroundRole()); box->setPalette(m_multipleDesktopsMenu->palette()); connect(box, &QCheckBox::clicked, action, &QAction::triggered); m_multipleDesktopsMenu->addAction(action); action->setData(i); if (m_client && !m_client->isOnAllDesktops() && m_client->isOnDesktop(i)) { box->setChecked(true); } } m_multipleDesktopsMenu->addSeparator(); action = m_multipleDesktopsMenu->addAction(i18nc("Create a new desktop and move there the window", "&New Desktop")); action->setData(vds->count() + 1); if (vds->count() >= vds->maximum()) action->setEnabled(false); } void UserActionsMenu::screenPopupAboutToShow() { if (!m_screenMenu) { return; } m_screenMenu->clear(); if (!m_client) { return; } m_screenMenu->setPalette(m_client->palette()); QActionGroup *group = new QActionGroup(m_screenMenu); for (int i = 0; icount(); ++i) { // assumption: there are not more than 9 screens attached. QAction *action = m_screenMenu->addAction(i18nc("@item:inmenu List of all Screens to send a window to. First argument is a number, second the output identifier. E.g. Screen 1 (HDMI1)", "Screen &%1 (%2)", (i+1), screens()->name(i))); action->setData(i); action->setCheckable(true); if (m_client && i == m_client->screen()) { action->setChecked(true); } group->addAction(action); } } void UserActionsMenu::activityPopupAboutToShow() { if (!m_activityMenu) return; #ifdef KWIN_BUILD_ACTIVITIES if (!Activities::self()) { return; } m_activityMenu->clear(); if (m_client) { m_activityMenu->setPalette(m_client->palette()); } QAction *action = m_activityMenu->addAction(i18n("&All Activities")); action->setData(QString()); action->setCheckable(true); static QPointer allActivitiesGroup; if (!allActivitiesGroup) { allActivitiesGroup = new QActionGroup(m_activityMenu); } allActivitiesGroup->addAction(action); if (m_client && m_client->isOnAllActivities()) { action->setChecked(true); } m_activityMenu->addSeparator(); foreach (const QString &id, Activities::self()->running()) { KActivities::Info activity(id); QString name = activity.name(); name.replace('&', "&&"); QWidgetAction *action = new QWidgetAction(m_activityMenu); QCheckBox *box = new QCheckBox(name, m_activityMenu); action->setDefaultWidget(box); const QString icon = activity.icon(); if (!icon.isEmpty()) box->setIcon(QIcon::fromTheme(icon)); box->setBackgroundRole(m_activityMenu->backgroundRole()); box->setForegroundRole(m_activityMenu->foregroundRole()); box->setPalette(m_activityMenu->palette()); connect(box, &QCheckBox::clicked, action, &QAction::triggered); m_activityMenu->addAction(action); action->setData(id); if (m_client && !m_client->isOnAllActivities() && m_client->isOnActivity(id)) { box->setChecked(true); } } #endif } void UserActionsMenu::slotWindowOperation(QAction *action) { if (!action->data().isValid()) return; Options::WindowOperation op = static_cast< Options::WindowOperation >(action->data().toInt()); QPointer c = m_client ? m_client : QPointer(Workspace::self()->activeClient()); if (c.isNull()) return; QString type; switch(op) { case Options::FullScreenOp: if (!c->isFullScreen() && c->userCanSetFullScreen()) type = QStringLiteral("fullscreenaltf3"); break; case Options::NoBorderOp: if (!c->noBorder() && c->userCanSetNoBorder()) type = QStringLiteral("noborderaltf3"); break; default: break; } if (!type.isEmpty()) helperDialog(type, c); // need to delay performing the window operation as we need to have the // user actions menu closed before we destroy the decoration. Otherwise Qt crashes qRegisterMetaType(); QMetaObject::invokeMethod(workspace(), "performWindowOperation", Qt::QueuedConnection, Q_ARG(KWin::AbstractClient*, c), Q_ARG(Options::WindowOperation, op)); } void UserActionsMenu::slotSendToDesktop(QAction *action) { bool ok = false; uint desk = action->data().toUInt(&ok); if (!ok) { return; } if (m_client.isNull()) return; Workspace *ws = Workspace::self(); VirtualDesktopManager *vds = VirtualDesktopManager::self(); if (desk == 0) { // the 'on_all_desktops' menu entry if (m_client) { m_client->setOnAllDesktops(!m_client->isOnAllDesktops()); } return; } else if (desk > vds->count()) { vds->setCount(desk); } ws->sendClientToDesktop(m_client.data(), desk, false); } void UserActionsMenu::slotToggleOnVirtualDesktop(QAction *action) { bool ok = false; uint desk = action->data().toUInt(&ok); if (!ok) { return; } if (m_client.isNull()) { return; } VirtualDesktopManager *vds = VirtualDesktopManager::self(); if (desk == 0) { // the 'on_all_desktops' menu entry m_client->setOnAllDesktops(!m_client->isOnAllDesktops()); return; } else if (desk > vds->count()) { vds->setCount(desk); } VirtualDesktop *virtualDesktop = VirtualDesktopManager::self()->desktopForX11Id(desk); if (m_client->desktops().contains(virtualDesktop)) { m_client->leaveDesktop(virtualDesktop); } else { m_client->enterDesktop(virtualDesktop); } } void UserActionsMenu::slotSendToScreen(QAction *action) { const int screen = action->data().toInt(); if (m_client.isNull()) { return; } if (screen >= screens()->count()) { return; } Workspace::self()->sendClientToScreen(m_client.data(), screen); } void UserActionsMenu::slotToggleOnActivity(QAction *action) { #ifdef KWIN_BUILD_ACTIVITIES if (!Activities::self()) { return; } QString activity = action->data().toString(); if (m_client.isNull()) return; if (activity.isEmpty()) { // the 'on_all_activities' menu entry m_client->setOnAllActivities(!m_client->isOnAllActivities()); return; } Client *c = dynamic_cast(m_client.data()); if (!c) { return; } Activities::self()->toggleClientOnActivity(c, activity, false); if (m_activityMenu && m_activityMenu->isVisible() && m_activityMenu->actions().count()) { const bool isOnAll = m_client->isOnAllActivities(); m_activityMenu->actions().at(0)->setChecked(isOnAll); if (isOnAll) { // toggleClientOnActivity interprets "on all" as "on none" and // susequent toggling ("off") would move the client to only that activity. // bug #330838 -> set all but "on all" off to "force proper usage" for (int i = 1; i < m_activityMenu->actions().count(); ++i) { if (QWidgetAction *qwa = qobject_cast(m_activityMenu->actions().at(i))) { if (QCheckBox *qcb = qobject_cast(qwa->defaultWidget())) { qcb->setChecked(false); } } } } } #else Q_UNUSED(action) #endif } //**************************************** // ShortcutDialog //**************************************** ShortcutDialog::ShortcutDialog(const QKeySequence& cut) : _shortcut(cut) { m_ui.setupUi(this); m_ui.keySequenceEdit->setKeySequence(cut); m_ui.warning->hide(); // Listen to changed shortcuts connect(m_ui.keySequenceEdit, &QKeySequenceEdit::editingFinished, this, &ShortcutDialog::keySequenceChanged); connect(m_ui.clearButton, &QToolButton::clicked, [this]{ _shortcut = QKeySequence(); }); m_ui.keySequenceEdit->setFocus(); setWindowFlags(Qt::Popup | Qt::X11BypassWindowManagerHint); } void ShortcutDialog::accept() { QKeySequence seq = shortcut(); if (!seq.isEmpty()) { if (seq[0] == Qt::Key_Escape) { reject(); return; } if (seq[0] == Qt::Key_Space || (seq[0] & Qt::KeyboardModifierMask) == 0) { // clear m_ui.keySequenceEdit->clear(); QDialog::accept(); return; } } QDialog::accept(); } void ShortcutDialog::done(int r) { QDialog::done(r); emit dialogDone(r == Accepted); } void ShortcutDialog::keySequenceChanged() { activateWindow(); // where is the kbd focus lost? cause of popup state? QKeySequence seq = m_ui.keySequenceEdit->keySequence(); if (_shortcut == seq) return; // don't try to update the same if (seq.isEmpty()) { // clear _shortcut = seq; return; } if (seq.count() > 1) { seq = QKeySequence(seq[0]); m_ui.keySequenceEdit->setKeySequence(seq); } // Check if the key sequence is used currently QString sc = seq.toString(); // NOTICE - seq.toString() & the entries in "conflicting" randomly get invalidated after the next call (if no sc has been set & conflicting isn't empty?!) QList conflicting = KGlobalAccel::getGlobalShortcutsByKey(seq); if (!conflicting.isEmpty()) { const KGlobalShortcutInfo &conflict = conflicting.at(0); m_ui.warning->setText(i18nc("'%1' is a keyboard shortcut like 'ctrl+w'", "%1 is already in use", sc)); m_ui.warning->setToolTip(i18nc("keyboard shortcut '%1' is used by action '%2' in application '%3'", "%1 is used by %2 in %3", sc, conflict.friendlyName(), conflict.componentFriendlyName())); m_ui.warning->show(); m_ui.keySequenceEdit->setKeySequence(shortcut()); } else if (seq != _shortcut) { m_ui.warning->hide(); if (QPushButton *ok = m_ui.buttonBox->button(QDialogButtonBox::Ok)) ok->setFocus(); } _shortcut = seq; } QKeySequence ShortcutDialog::shortcut() const { return _shortcut; } //**************************************** // Workspace //**************************************** void Workspace::slotIncreaseWindowOpacity() { if (!active_client) { return; } active_client->setOpacity(qMin(active_client->opacity() + 0.05, 1.0)); } void Workspace::slotLowerWindowOpacity() { if (!active_client) { return; } active_client->setOpacity(qMax(active_client->opacity() - 0.05, 0.05)); } void Workspace::closeActivePopup() { if (active_popup) { active_popup->close(); - active_popup = NULL; - active_popup_client = NULL; + active_popup = nullptr; + active_popup_client = nullptr; } m_userActionsMenu->close(); } template void Workspace::initShortcut(const QString &actionName, const QString &description, const QKeySequence &shortcut, Slot slot, const QVariant &data) { initShortcut(actionName, description, shortcut, this, slot, data); } template void Workspace::initShortcut(const QString &actionName, const QString &description, const QKeySequence &shortcut, T *receiver, Slot slot, const QVariant &data) { QAction *a = new QAction(this); a->setProperty("componentName", QStringLiteral(KWIN_NAME)); a->setObjectName(actionName); a->setText(description); if (data.isValid()) { a->setData(data); } KGlobalAccel::self()->setDefaultShortcut(a, QList() << shortcut); KGlobalAccel::self()->setShortcut(a, QList() << shortcut); input()->registerShortcut(shortcut, a, receiver, slot); } /** * Creates the global accel object \c keys. */ void Workspace::initShortcuts() { #define IN_KWIN #include "kwinbindings.cpp" #ifdef KWIN_BUILD_TABBOX TabBox::TabBox::self()->initShortcuts(); #endif VirtualDesktopManager::self()->initShortcuts(); kwinApp()->platform()->colorCorrectManager()->initShortcuts(); m_userActionsMenu->discard(); // so that it's recreated next time } void Workspace::setupWindowShortcut(AbstractClient* c) { - Q_ASSERT(client_keys_dialog == NULL); + Q_ASSERT(client_keys_dialog == nullptr); // TODO: PORT ME (KGlobalAccel related) //keys->setEnabled( false ); //disable_shortcuts_keys->setEnabled( false ); //client_keys->setEnabled( false ); client_keys_dialog = new ShortcutDialog(c->shortcut()); client_keys_client = c; connect(client_keys_dialog, &ShortcutDialog::dialogDone, this, &Workspace::setupWindowShortcutDone); QRect r = clientArea(ScreenArea, c); QSize size = client_keys_dialog->sizeHint(); QPoint pos = c->pos() + c->clientPos(); if (pos.x() + size.width() >= r.right()) pos.setX(r.right() - size.width()); if (pos.y() + size.height() >= r.bottom()) pos.setY(r.bottom() - size.height()); client_keys_dialog->move(pos); client_keys_dialog->show(); active_popup = client_keys_dialog; active_popup_client = c; } void Workspace::setupWindowShortcutDone(bool ok) { // keys->setEnabled( true ); // disable_shortcuts_keys->setEnabled( true ); // client_keys->setEnabled( true ); if (ok) client_keys_client->setShortcut(client_keys_dialog->shortcut().toString()); closeActivePopup(); client_keys_dialog->deleteLater(); - client_keys_dialog = NULL; - client_keys_client = NULL; + client_keys_dialog = nullptr; + client_keys_client = nullptr; if (active_client) active_client->takeFocus(); } void Workspace::clientShortcutUpdated(AbstractClient* c) { QString key = QStringLiteral("_k_session:%1").arg(c->window()); QAction* action = findChild(key); if (!c->shortcut().isEmpty()) { - if (action == NULL) { // new shortcut + if (action == nullptr) { // new shortcut action = new QAction(this); kwinApp()->platform()->setupActionForGlobalAccel(action); action->setProperty("componentName", QStringLiteral(KWIN_NAME)); action->setObjectName(key); action->setText(i18n("Activate Window (%1)", c->caption())); connect(action, &QAction::triggered, c, std::bind(&Workspace::activateClient, this, c, true)); } // no autoloading, since it's configured explicitly here and is not meant to be reused // (the key is the window id anyway, which is kind of random) KGlobalAccel::self()->setShortcut(action, QList() << c->shortcut(), KGlobalAccel::NoAutoloading); action->setEnabled(true); } else { KGlobalAccel::self()->removeAllShortcuts(action); delete action; } } void Workspace::performWindowOperation(AbstractClient* c, Options::WindowOperation op) { if (!c) return; if (op == Options::MoveOp || op == Options::UnrestrictedMoveOp) Cursor::setPos(c->geometry().center()); if (op == Options::ResizeOp || op == Options::UnrestrictedResizeOp) Cursor::setPos(c->geometry().bottomRight()); switch(op) { case Options::MoveOp: c->performMouseCommand(Options::MouseMove, Cursor::pos()); break; case Options::UnrestrictedMoveOp: c->performMouseCommand(Options::MouseUnrestrictedMove, Cursor::pos()); break; case Options::ResizeOp: c->performMouseCommand(Options::MouseResize, Cursor::pos()); break; case Options::UnrestrictedResizeOp: c->performMouseCommand(Options::MouseUnrestrictedResize, Cursor::pos()); break; case Options::CloseOp: QMetaObject::invokeMethod(c, "closeWindow", Qt::QueuedConnection); break; case Options::MaximizeOp: c->maximize(c->maximizeMode() == MaximizeFull ? MaximizeRestore : MaximizeFull); break; case Options::HMaximizeOp: c->maximize(c->maximizeMode() ^ MaximizeHorizontal); break; case Options::VMaximizeOp: c->maximize(c->maximizeMode() ^ MaximizeVertical); break; case Options::RestoreOp: c->maximize(MaximizeRestore); break; case Options::MinimizeOp: c->minimize(); break; case Options::ShadeOp: c->performMouseCommand(Options::MouseShade, Cursor::pos()); break; case Options::OnAllDesktopsOp: c->setOnAllDesktops(!c->isOnAllDesktops()); break; case Options::FullScreenOp: c->setFullScreen(!c->isFullScreen(), true); break; case Options::NoBorderOp: c->setNoBorder(!c->noBorder()); break; case Options::KeepAboveOp: { StackingUpdatesBlocker blocker(this); bool was = c->keepAbove(); c->setKeepAbove(!c->keepAbove()); if (was && !c->keepAbove()) raiseClient(c); break; } case Options::KeepBelowOp: { StackingUpdatesBlocker blocker(this); bool was = c->keepBelow(); c->setKeepBelow(!c->keepBelow()); if (was && !c->keepBelow()) lowerClient(c); break; } case Options::OperationsOp: c->performMouseCommand(Options::MouseShade, Cursor::pos()); break; case Options::WindowRulesOp: RuleBook::self()->edit(c, false); break; case Options::ApplicationRulesOp: RuleBook::self()->edit(c, true); break; case Options::SetupWindowShortcutOp: setupWindowShortcut(c); break; case Options::LowerOp: lowerClient(c); break; case Options::NoOp: break; } } /** * Called by the decoration in the new API to determine what buttons the user has configured for * window tab dragging and the operations menu. */ Options::WindowOperation Client::mouseButtonToWindowOperation(Qt::MouseButtons button) { Options::MouseCommand com = Options::MouseNothing; bool active = isActive(); if (!wantsInput()) // we cannot be active, use it anyway active = true; if (button == Qt::LeftButton) com = active ? options->commandActiveTitlebar1() : options->commandInactiveTitlebar1(); else if (button == Qt::MidButton) com = active ? options->commandActiveTitlebar2() : options->commandInactiveTitlebar2(); else if (button == Qt::RightButton) com = active ? options->commandActiveTitlebar3() : options->commandInactiveTitlebar3(); if (com == Options::MouseOperationsMenu) return Options::OperationsOp; return Options::NoOp; } /** * Performs a mouse command on this client (see options.h) */ bool Client::performMouseCommand(Options::MouseCommand command, const QPoint &globalPos) { bool replay = false; switch(command) { case Options::MouseShade : toggleShade(); cancelShadeHoverTimer(); break; case Options::MouseSetShade: setShade(ShadeNormal); cancelShadeHoverTimer(); break; case Options::MouseUnsetShade: setShade(ShadeNone); cancelShadeHoverTimer(); break; default: return AbstractClient::performMouseCommand(command, globalPos); } return replay; } void Workspace::slotActivateAttentionWindow() { if (attention_chain.count() > 0) activateClient(attention_chain.first()); } static uint senderValue(QObject *sender) { QAction *act = qobject_cast(sender); bool ok = false; uint i = -1; if (act) i = act->data().toUInt(&ok); if (ok) return i; return -1; } #define USABLE_ACTIVE_CLIENT (active_client && !(active_client->isDesktop() || active_client->isDock())) void Workspace::slotWindowToDesktop(uint i) { if (USABLE_ACTIVE_CLIENT) { if (i < 1) return; if (i >= 1 && i <= VirtualDesktopManager::self()->count()) sendClientToDesktop(active_client, i, true); } } static bool screenSwitchImpossible() { if (!screens()->isCurrentFollowsMouse()) return false; QStringList args; args << QStringLiteral("--passivepopup") << i18n("The window manager is configured to consider the screen with the mouse on it as active one.\n" "Therefore it is not possible to switch to a screen explicitly.") << QStringLiteral("20"); KProcess::startDetached(QStringLiteral("kdialog"), args); return true; } void Workspace::slotSwitchToScreen() { if (screenSwitchImpossible()) return; const int i = senderValue(sender()); if (i > -1) setCurrentScreen(i); } void Workspace::slotSwitchToNextScreen() { if (screenSwitchImpossible()) return; setCurrentScreen((screens()->current() + 1) % screens()->count()); } void Workspace::slotSwitchToPrevScreen() { if (screenSwitchImpossible()) return; setCurrentScreen((screens()->current() + screens()->count() - 1) % screens()->count()); } void Workspace::slotWindowToScreen() { if (USABLE_ACTIVE_CLIENT) { const int i = senderValue(sender()); if (i < 0) return; if (i >= 0 && i <= screens()->count()) { sendClientToScreen(active_client, i); } } } void Workspace::slotWindowToNextScreen() { if (USABLE_ACTIVE_CLIENT) sendClientToScreen(active_client, (active_client->screen() + 1) % screens()->count()); } void Workspace::slotWindowToPrevScreen() { if (USABLE_ACTIVE_CLIENT) sendClientToScreen(active_client, (active_client->screen() + screens()->count() - 1) % screens()->count()); } /** * Maximizes the active client. */ void Workspace::slotWindowMaximize() { if (USABLE_ACTIVE_CLIENT) performWindowOperation(active_client, Options::MaximizeOp); } /** * Maximizes the active client vertically. */ void Workspace::slotWindowMaximizeVertical() { if (USABLE_ACTIVE_CLIENT) performWindowOperation(active_client, Options::VMaximizeOp); } /** * Maximizes the active client horiozontally. */ void Workspace::slotWindowMaximizeHorizontal() { if (USABLE_ACTIVE_CLIENT) performWindowOperation(active_client, Options::HMaximizeOp); } /** * Minimizes the active client. */ void Workspace::slotWindowMinimize() { if (USABLE_ACTIVE_CLIENT) performWindowOperation(active_client, Options::MinimizeOp); } /** * Shades/unshades the active client respectively. */ void Workspace::slotWindowShade() { if (USABLE_ACTIVE_CLIENT) performWindowOperation(active_client, Options::ShadeOp); } /** * Raises the active client. */ void Workspace::slotWindowRaise() { if (USABLE_ACTIVE_CLIENT) raiseClient(active_client); } /** * Lowers the active client. */ void Workspace::slotWindowLower() { if (USABLE_ACTIVE_CLIENT) { lowerClient(active_client); // As this most likely makes the window no longer visible change the // keyboard focus to the next available window. //activateNextClient( c ); // Doesn't work when we lower a child window if (active_client->isActive() && options->focusPolicyIsReasonable()) { if (options->isNextFocusPrefersMouse()) { AbstractClient *next = clientUnderMouse(active_client->screen()); if (next && next != active_client) requestFocus(next, false); } else { activateClient(topClientOnDesktop(VirtualDesktopManager::self()->current(), -1)); } } } } /** * Does a toggle-raise-and-lower on the active client. */ void Workspace::slotWindowRaiseOrLower() { if (USABLE_ACTIVE_CLIENT) raiseOrLowerClient(active_client); } void Workspace::slotWindowOnAllDesktops() { if (USABLE_ACTIVE_CLIENT) active_client->setOnAllDesktops(!active_client->isOnAllDesktops()); } void Workspace::slotWindowFullScreen() { if (USABLE_ACTIVE_CLIENT) performWindowOperation(active_client, Options::FullScreenOp); } void Workspace::slotWindowNoBorder() { if (USABLE_ACTIVE_CLIENT) performWindowOperation(active_client, Options::NoBorderOp); } void Workspace::slotWindowAbove() { if (USABLE_ACTIVE_CLIENT) performWindowOperation(active_client, Options::KeepAboveOp); } void Workspace::slotWindowBelow() { if (USABLE_ACTIVE_CLIENT) performWindowOperation(active_client, Options::KeepBelowOp); } void Workspace::slotSetupWindowShortcut() { if (USABLE_ACTIVE_CLIENT) performWindowOperation(active_client, Options::SetupWindowShortcutOp); } /** * Toggles show desktop. */ void Workspace::slotToggleShowDesktop() { setShowingDesktop(!showingDesktop()); } template void windowToDesktop(AbstractClient *c) { VirtualDesktopManager *vds = VirtualDesktopManager::self(); Workspace *ws = Workspace::self(); Direction functor; // TODO: why is options->isRollOverDesktops() not honored? const auto desktop = functor(nullptr, true); if (c && !c->isDesktop() && !c->isDock()) { ws->setMoveResizeClient(c); vds->setCurrent(desktop); ws->setMoveResizeClient(nullptr); } } /** * Moves the active client to the next desktop. */ void Workspace::slotWindowToNextDesktop() { if (USABLE_ACTIVE_CLIENT) windowToNextDesktop(active_client); } void Workspace::windowToNextDesktop(AbstractClient* c) { windowToDesktop(c); } /** * Moves the active client to the previous desktop. */ void Workspace::slotWindowToPreviousDesktop() { if (USABLE_ACTIVE_CLIENT) windowToPreviousDesktop(active_client); } void Workspace::windowToPreviousDesktop(AbstractClient* c) { windowToDesktop(c); } template void activeClientToDesktop() { VirtualDesktopManager *vds = VirtualDesktopManager::self(); Workspace *ws = Workspace::self(); const int current = vds->current(); Direction functor; const int d = functor(current, options->isRollOverDesktops()); if (d == current) { return; } ws->setMoveResizeClient(ws->activeClient()); vds->setCurrent(d); ws->setMoveResizeClient(nullptr); } void Workspace::slotWindowToDesktopRight() { if (USABLE_ACTIVE_CLIENT) { activeClientToDesktop(); } } void Workspace::slotWindowToDesktopLeft() { if (USABLE_ACTIVE_CLIENT) { activeClientToDesktop(); } } void Workspace::slotWindowToDesktopUp() { if (USABLE_ACTIVE_CLIENT) { activeClientToDesktop(); } } void Workspace::slotWindowToDesktopDown() { if (USABLE_ACTIVE_CLIENT) { activeClientToDesktop(); } } /** * Kill Window feature, similar to xkill. */ void Workspace::slotKillWindow() { if (m_windowKiller.isNull()) { m_windowKiller.reset(new KillWindow()); } m_windowKiller->start(); } /** * Switches to the nearest window in given direction. */ void Workspace::switchWindow(Direction direction) { if (!active_client) return; AbstractClient *c = active_client; int desktopNumber = c->isOnAllDesktops() ? VirtualDesktopManager::self()->current() : c->desktop(); // Centre of the active window QPoint curPos(c->pos().x() + c->geometry().width() / 2, c->pos().y() + c->geometry().height() / 2); if (!switchWindow(c, direction, curPos, desktopNumber)) { auto opposite = [&] { switch(direction) { case DirectionNorth: return QPoint(curPos.x(), screens()->geometry().height()); case DirectionSouth: return QPoint(curPos.x(), 0); case DirectionEast: return QPoint(0, curPos.y()); case DirectionWest: return QPoint(screens()->geometry().width(), curPos.y()); default: Q_UNREACHABLE(); } }; switchWindow(c, direction, opposite(), desktopNumber); } } bool Workspace::switchWindow(AbstractClient *c, Direction direction, QPoint curPos, int d) { AbstractClient *switchTo = nullptr; int bestScore = 0; ToplevelList clist = stackingOrder(); for (auto i = clist.rbegin(); i != clist.rend(); ++i) { auto client = qobject_cast(*i); if (!client) { continue; } if (client->wantsTabFocus() && *i != c && client->isOnDesktop(d) && !client->isMinimized() && (*i)->isOnCurrentActivity()) { // Centre of the other window QPoint other(client->pos().x() + client->geometry().width() / 2, client->pos().y() + client->geometry().height() / 2); int distance; int offset; switch(direction) { case DirectionNorth: distance = curPos.y() - other.y(); offset = qAbs(other.x() - curPos.x()); break; case DirectionEast: distance = other.x() - curPos.x(); offset = qAbs(other.y() - curPos.y()); break; case DirectionSouth: distance = other.y() - curPos.y(); offset = qAbs(other.x() - curPos.x()); break; case DirectionWest: distance = curPos.x() - other.x(); offset = qAbs(other.y() - curPos.y()); break; default: distance = -1; offset = -1; } if (distance > 0) { // Inverse score int score = distance + offset + ((offset * offset) / distance); if (score < bestScore || !switchTo) { switchTo = client; bestScore = score; } } } } if (switchTo) { activateClient(switchTo); } return switchTo; } /** * Shows the window operations popup menu for the active client. */ void Workspace::slotWindowOperations() { if (!active_client) return; QPoint pos = active_client->pos() + active_client->clientPos(); showWindowMenu(QRect(pos, pos), active_client); } void Workspace::showWindowMenu(const QRect &pos, AbstractClient* cl) { m_userActionsMenu->show(pos, cl); } void Workspace::showApplicationMenu(const QRect &pos, AbstractClient *c, int actionId) { ApplicationMenu::self()->showApplicationMenu(c->geometry().topLeft() + pos.bottomLeft(), c, actionId); } /** * Closes the active client. */ void Workspace::slotWindowClose() { // TODO: why? // if ( tab_box->isVisible()) // return; if (USABLE_ACTIVE_CLIENT) performWindowOperation(active_client, Options::CloseOp); } /** * Starts keyboard move mode for the active client. */ void Workspace::slotWindowMove() { if (USABLE_ACTIVE_CLIENT) performWindowOperation(active_client, Options::UnrestrictedMoveOp); } /** * Starts keyboard resize mode for the active client. */ void Workspace::slotWindowResize() { if (USABLE_ACTIVE_CLIENT) performWindowOperation(active_client, Options::UnrestrictedResizeOp); } #undef USABLE_ACTIVE_CLIENT void AbstractClient::setShortcut(const QString& _cut) { QString cut = rules()->checkShortcut(_cut); auto updateShortcut = [this](const QKeySequence &cut = QKeySequence()) { if (_shortcut == cut) return; _shortcut = cut; setShortcutInternal(); }; if (cut.isEmpty()) { updateShortcut(); return; } if (cut == shortcut().toString()) { return; // no change } // Format: // base+(abcdef)base+(abcdef) // E.g. Alt+Ctrl+(ABCDEF);Meta+X,Meta+(ABCDEF) if (!cut.contains(QLatin1Char('(')) && !cut.contains(QLatin1Char(')')) && !cut.contains(QLatin1String(" - "))) { if (workspace()->shortcutAvailable(cut, this)) updateShortcut(QKeySequence(cut)); else updateShortcut(); return; } QList< QKeySequence > keys; QStringList groups = cut.split(QStringLiteral(" - ")); for (QStringList::ConstIterator it = groups.constBegin(); it != groups.constEnd(); ++it) { QRegExp reg(QStringLiteral("(.*\\+)\\((.*)\\)")); if (reg.indexIn(*it) > -1) { QString base = reg.cap(1); QString list = reg.cap(2); for (int i = 0; i < list.length(); ++i) { QKeySequence c(base + list[ i ]); if (!c.isEmpty()) keys.append(c); } } else { // regexp doesn't match, so it should be a normal shortcut QKeySequence c(*it); if (!c.isEmpty()) { keys.append(c); } } } for (auto it = keys.constBegin(); it != keys.constEnd(); ++it) { if (_shortcut == *it) // current one is in the list return; } for (auto it = keys.constBegin(); it != keys.constEnd(); ++it) { if (workspace()->shortcutAvailable(*it, this)) { updateShortcut(*it); return; } } updateShortcut(); } void AbstractClient::setShortcutInternal() { updateCaption(); workspace()->clientShortcutUpdated(this); } void Client::setShortcutInternal() { updateCaption(); #if 0 workspace()->clientShortcutUpdated(this); #else // Workaround for kwin<->kglobalaccel deadlock, when KWin has X grab and the kded // kglobalaccel module tries to create the key grab. KWin should preferably grab // they keys itself anyway :(. QTimer::singleShot(0, this, std::bind(&Workspace::clientShortcutUpdated, workspace(), this)); #endif } bool Workspace::shortcutAvailable(const QKeySequence &cut, AbstractClient* ignore) const { if (ignore && cut == ignore->shortcut()) return true; if (!KGlobalAccel::getGlobalShortcutsByKey(cut).isEmpty()) { return false; } for (auto it = m_allClients.constBegin(); it != m_allClients.constEnd(); ++it) { if ((*it) != ignore && (*it)->shortcut() == cut) return false; } return true; } } // namespace diff --git a/useractions.h b/useractions.h index af93bcc0a..2de180482 100644 --- a/useractions.h +++ b/useractions.h @@ -1,264 +1,264 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2012 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_USERACTIONS_H #define KWIN_USERACTIONS_H #include "ui_shortcutdialog.h" #include // Qt #include #include #include class QAction; class QRect; namespace KWin { class AbstractClient; class Client; /** * @brief Menu shown for a Client. * * The UserActionsMenu implements the Menu which is shown on: * @li context-menu event on Window decoration * @li window menu button * @li Keyboard Shortcut (by default Alt+F3) * * The menu contains various window management related actions for the Client the menu is opened * for, this is normally the active Client. * * The menu which is shown is tried to be as close as possible to the menu implemented in * libtaskmanager, though there are differences as there are some actions only the window manager * can provide and on the other hand the libtaskmanager cares also about things like e.g. grouping. * * Whenever the menu is changed it should be tried to also adjust the menu in libtaskmanager. * * @author Martin Gräßlin */ class KWIN_EXPORT UserActionsMenu : public QObject { Q_OBJECT public: - explicit UserActionsMenu(QObject *parent = 0); + explicit UserActionsMenu(QObject *parent = nullptr); ~UserActionsMenu() override; /** * Discards the constructed menu, so that it gets recreates * on next show event. * @see show */ void discard(); /** * @returns Whether the menu is currently visible */ bool isShown() const; /** * grabs keyboard and mouse, workaround(?) for bug #351112 */ void grabInput(); /** * @returns Whether the menu has a Client set to operate on. */ bool hasClient() const; /** * Checks whether the given Client @p c is the Client * for which the Menu is shown. * @param c The Client to compare * @returns Whether the Client is the one related to this Menu */ bool isMenuClient(const AbstractClient *c) const; /** * Closes the Menu and prepares it for next usage. */ void close(); /** * @brief Shows the menu at the given @p pos for the given @p client. * * @param pos The position where the menu should be shown. * @param client The Client for which the Menu has to be shown. */ void show(const QRect &pos, AbstractClient *client); public Q_SLOTS: /** * Delayed initialization of the activity menu. * * The call to retrieve the current list of activities is performed in a thread and this * slot is invoked once the list has been fetched. Only task of this method is to decide * whether to show the activity menu and to invoke the initialization of it. * * @see initActivityPopup */ void showHideActivityMenu(); private Q_SLOTS: /** * The menu will become visible soon. * * Adjust the items according to the respective Client. */ void menuAboutToShow(); /** * Adjusts the desktop popup to the current values and the location of * the Client. */ void desktopPopupAboutToShow(); /** * Adjusts the multipleDesktopsMenu popup to the current values and the location of * the Client, Wayland only. */ void multipleDesktopsPopupAboutToShow(); /** * Adjusts the screen popup to the current values and the location of * the Client. */ void screenPopupAboutToShow(); /** * Adjusts the activity popup to the current values and the location of * the Client. */ void activityPopupAboutToShow(); /** * Sends the client to desktop \a desk * * @param action Invoked Action containing the Desktop as data element */ void slotSendToDesktop(QAction *action); /** * Toggle whether the Client is on a desktop (Wayland only) * * @param action Invoked Action containing the Desktop as data element */ void slotToggleOnVirtualDesktop(QAction *action); /** * Sends the Client to screen \a screen * * @param action Invoked Action containing the Screen as data element */ void slotSendToScreen(QAction *action); /** * Toggles whether the Client is on the \a activity * * @param action Invoked Action containing the Id of the Activity to toggle the Client on */ void slotToggleOnActivity(QAction *action); /** * Performs a window operation. * * @param action Invoked Action containing the Window Operation to perform for the Client */ void slotWindowOperation(QAction *action); private: /** * Creates the menu if not already created. */ void init(); /** * Creates the Move to Desktop sub-menu. */ void initDesktopPopup(); /** * Creates the Move to Screen sub-menu. */ void initScreenPopup(); /** * Creates activity popup. * I'm going with checkable ones instead of "copy to" and "move to" menus; I *think* it's an easier way. * Oh, and an 'all' option too of course */ void initActivityPopup(); /** * Shows a helper Dialog to inform the user how to get back in case he triggered * an action which hides the window decoration (e.g. NoBorder or Fullscreen). * @param message The message type to be shown * @param c The Client for which the dialog should be shown. */ void helperDialog(const QString &message, AbstractClient *c); /** * The actual main context menu which is show when the UserActionsMenu is invoked. */ QMenu* m_menu; /** * The move to desktop sub menu. */ QMenu* m_desktopMenu; /** * The move to desktop sub menu, with the Wayland protocol. */ QMenu* m_multipleDesktopsMenu; /** * The move to screen sub menu. */ QMenu* m_screenMenu; /** * The activities sub menu. */ QMenu* m_activityMenu; /** * Menu for further entries added by scripts. */ QMenu* m_scriptsMenu; QAction* m_resizeOperation; QAction* m_moveOperation; QAction* m_maximizeOperation; QAction* m_shadeOperation; QAction* m_keepAboveOperation; QAction* m_keepBelowOperation; QAction* m_fullScreenOperation; QAction* m_noBorderOperation; QAction* m_minimizeOperation; QAction* m_closeOperation; QAction* m_shortcutOperation; /** * The Client for which the menu is shown. */ QPointer m_client; QAction *m_rulesOperation = nullptr; QAction *m_applicationRulesOperation = nullptr; }; class ShortcutDialog : public QDialog { Q_OBJECT public: explicit ShortcutDialog(const QKeySequence& cut); void accept() override; QKeySequence shortcut() const; public Q_SLOTS: void keySequenceChanged(); Q_SIGNALS: void dialogDone(bool ok); protected: void done(int r) override; private: Ui::ShortcutDialog m_ui; QKeySequence _shortcut; }; } // namespace #endif // KWIN_USERACTIONS_H diff --git a/utils.cpp b/utils.cpp index 3c63c91dc..b28330fbb 100644 --- a/utils.cpp +++ b/utils.cpp @@ -1,206 +1,206 @@ /******************************************************************** 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 . *********************************************************************/ /* This file is for (very) small utility functions/classes. */ #include "utils.h" #include #include #ifndef KCMRULES #include #include #include "atoms.h" #include "platform.h" #include "workspace.h" #include #include #endif Q_LOGGING_CATEGORY(KWIN_CORE, "kwin_core", QtCriticalMsg) Q_LOGGING_CATEGORY(KWIN_VIRTUALKEYBOARD, "kwin_virtualkeyboard", QtCriticalMsg) namespace KWin { #ifndef KCMRULES //************************************ // StrutRect //************************************ StrutRect::StrutRect(QRect rect, StrutArea area) : QRect(rect) , m_area(area) { } StrutRect::StrutRect(const StrutRect& other) : QRect(other) , m_area(other.area()) { } StrutRect &StrutRect::operator=(const StrutRect &other) { if (this != &other) { QRect::operator=(other); m_area = other.area(); } return *this; } #endif #ifndef KCMRULES void updateXTime() { kwinApp()->platform()->updateXTime(); } static int server_grab_count = 0; void grabXServer() { if (++server_grab_count == 1) xcb_grab_server(connection()); } void ungrabXServer() { Q_ASSERT(server_grab_count > 0); if (--server_grab_count == 0) { xcb_ungrab_server(connection()); xcb_flush(connection()); } } static bool keyboard_grabbed = false; bool grabXKeyboard(xcb_window_t w) { - if (QWidget::keyboardGrabber() != NULL) + if (QWidget::keyboardGrabber() != nullptr) return false; if (keyboard_grabbed) return false; - if (qApp->activePopupWidget() != NULL) + if (qApp->activePopupWidget() != nullptr) return false; if (w == XCB_WINDOW_NONE) w = rootWindow(); const xcb_grab_keyboard_cookie_t c = xcb_grab_keyboard_unchecked(connection(), false, w, xTime(), XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC); - ScopedCPointer grab(xcb_grab_keyboard_reply(connection(), c, NULL)); + ScopedCPointer grab(xcb_grab_keyboard_reply(connection(), c, nullptr)); if (grab.isNull()) { return false; } if (grab->status != XCB_GRAB_STATUS_SUCCESS) { return false; } keyboard_grabbed = true; return true; } void ungrabXKeyboard() { if (!keyboard_grabbed) { // grabXKeyboard() may fail sometimes, so don't fail, but at least warn anyway qCDebug(KWIN_CORE) << "ungrabXKeyboard() called but keyboard not grabbed!"; } keyboard_grabbed = false; xcb_ungrab_keyboard(connection(), XCB_TIME_CURRENT_TIME); } Process::Process(QObject *parent) : QProcess(parent) { } Process::~Process() = default; void Process::setupChildProcess() { sigset_t userSiganls; sigemptyset(&userSiganls); sigaddset(&userSiganls, SIGUSR1); sigaddset(&userSiganls, SIGUSR2); pthread_sigmask(SIG_UNBLOCK, &userSiganls, nullptr); } #endif // converting between X11 mouse/keyboard state mask and Qt button/keyboard states Qt::MouseButton x11ToQtMouseButton(int button) { if (button == XCB_BUTTON_INDEX_1) return Qt::LeftButton; if (button == XCB_BUTTON_INDEX_2) return Qt::MidButton; if (button == XCB_BUTTON_INDEX_3) return Qt::RightButton; if (button == XCB_BUTTON_INDEX_4) return Qt::XButton1; if (button == XCB_BUTTON_INDEX_5) return Qt::XButton2; return Qt::NoButton; } Qt::MouseButtons x11ToQtMouseButtons(int state) { - Qt::MouseButtons ret = 0; + Qt::MouseButtons ret = nullptr; if (state & XCB_KEY_BUT_MASK_BUTTON_1) ret |= Qt::LeftButton; if (state & XCB_KEY_BUT_MASK_BUTTON_2) ret |= Qt::MidButton; if (state & XCB_KEY_BUT_MASK_BUTTON_3) ret |= Qt::RightButton; if (state & XCB_KEY_BUT_MASK_BUTTON_4) ret |= Qt::XButton1; if (state & XCB_KEY_BUT_MASK_BUTTON_5) ret |= Qt::XButton2; return ret; } Qt::KeyboardModifiers x11ToQtKeyboardModifiers(int state) { - Qt::KeyboardModifiers ret = 0; + Qt::KeyboardModifiers ret = nullptr; if (state & XCB_KEY_BUT_MASK_SHIFT) ret |= Qt::ShiftModifier; if (state & XCB_KEY_BUT_MASK_CONTROL) ret |= Qt::ControlModifier; if (state & KKeyServer::modXAlt()) ret |= Qt::AltModifier; if (state & KKeyServer::modXMeta()) ret |= Qt::MetaModifier; return ret; } } // namespace #ifndef KCMRULES #endif diff --git a/virtualdesktops.cpp b/virtualdesktops.cpp index 76ed4819f..31b64badc 100644 --- a/virtualdesktops.cpp +++ b/virtualdesktops.cpp @@ -1,935 +1,935 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2009 Lucas Murray Copyright (C) 2012 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 "virtualdesktops.h" #include "input.h" // KDE #include #include #include #include #include // Qt #include #include #include #include namespace KWin { extern int screen_number; static bool s_loadingDesktopSettings = false; static QByteArray generateDesktopId() { return QUuid::createUuid().toString(QUuid::WithoutBraces).toUtf8(); } VirtualDesktop::VirtualDesktop(QObject *parent) : QObject(parent) { } VirtualDesktop::~VirtualDesktop() { emit aboutToBeDestroyed(); } void VirtualDesktopManager::setVirtualDesktopManagement(KWayland::Server::PlasmaVirtualDesktopManagementInterface *management) { using namespace KWayland::Server; Q_ASSERT(!m_virtualDesktopManagement); m_virtualDesktopManagement = management; auto createPlasmaVirtualDesktop = [this](VirtualDesktop *desktop) { PlasmaVirtualDesktopInterface *pvd = m_virtualDesktopManagement->createDesktop(desktop->id(), desktop->x11DesktopNumber() - 1); pvd->setName(desktop->name()); pvd->sendDone(); connect(desktop, &VirtualDesktop::nameChanged, pvd, [desktop, pvd] { pvd->setName(desktop->name()); pvd->sendDone(); } ); connect(pvd, &PlasmaVirtualDesktopInterface::activateRequested, this, [this, desktop] { setCurrent(desktop); } ); }; connect(this, &VirtualDesktopManager::desktopCreated, m_virtualDesktopManagement, createPlasmaVirtualDesktop); connect(this, &VirtualDesktopManager::rowsChanged, m_virtualDesktopManagement, [this](uint rows) { m_virtualDesktopManagement->setRows(rows); m_virtualDesktopManagement->sendDone(); } ); //handle removed: from VirtualDesktopManager to the wayland interface connect(this, &VirtualDesktopManager::desktopRemoved, m_virtualDesktopManagement, [this](VirtualDesktop *desktop) { m_virtualDesktopManagement->removeDesktop(desktop->id()); } ); //create a new desktop when the client asks to connect (m_virtualDesktopManagement, &PlasmaVirtualDesktopManagementInterface::desktopCreateRequested, this, [this](const QString &name, quint32 position) { createVirtualDesktop(position, name); } ); //remove when the client asks to connect (m_virtualDesktopManagement, &PlasmaVirtualDesktopManagementInterface::desktopRemoveRequested, this, [this](const QString &id) { //here there can be some nice kauthorized check? //remove only from VirtualDesktopManager, the other connections will remove it from m_virtualDesktopManagement as well removeVirtualDesktop(id.toUtf8()); } ); std::for_each(m_desktops.constBegin(), m_desktops.constEnd(), createPlasmaVirtualDesktop); //Now we are sure all ids are there save(); connect(this, &VirtualDesktopManager::currentChanged, m_virtualDesktopManagement, [this]() { for (auto *deskInt : m_virtualDesktopManagement->desktops()) { if (deskInt->id() == currentDesktop()->id()) { deskInt->setActive(true); } else { deskInt->setActive(false); } } } ); } void VirtualDesktop::setId(const QByteArray &id) { Q_ASSERT(m_id.isEmpty()); m_id = id; } void VirtualDesktop::setX11DesktopNumber(uint number) { //x11DesktopNumber can be changed now if (static_cast(m_x11DesktopNumber) == number) { return; } m_x11DesktopNumber = number; if (m_x11DesktopNumber != 0) { emit x11DesktopNumberChanged(); } } void VirtualDesktop::setName(const QString &name) { if (m_name == name) { return; } m_name = name; emit nameChanged(); } VirtualDesktopGrid::VirtualDesktopGrid() : m_size(1, 2) // Default to tow rows , m_grid(QVector>{QVector{}, QVector{}}) { } VirtualDesktopGrid::~VirtualDesktopGrid() = default; void VirtualDesktopGrid::update(const QSize &size, Qt::Orientation orientation, const QVector &desktops) { // Set private variables m_size = size; const uint width = size.width(); const uint height = size.height(); m_grid.clear(); auto it = desktops.begin(); auto end = desktops.end(); if (orientation == Qt::Horizontal) { for (uint y = 0; y < height; ++y) { QVector row; for (uint x = 0; x < width && it != end; ++x) { row << *it; it++; } m_grid << row; } } else { for (uint y = 0; y < height; ++y) { m_grid << QVector(); } for (uint x = 0; x < width; ++x) { for (uint y = 0; y < height && it != end; ++y) { auto &row = m_grid[y]; row << *it; it++; } } } } QPoint VirtualDesktopGrid::gridCoords(uint id) const { return gridCoords(VirtualDesktopManager::self()->desktopForX11Id(id)); } QPoint VirtualDesktopGrid::gridCoords(VirtualDesktop *vd) const { for (int y = 0; y < m_grid.count(); ++y) { const auto &row = m_grid.at(y); for (int x = 0; x < row.count(); ++x) { if (row.at(x) == vd) { return QPoint(x, y); } } } return QPoint(-1, -1); } VirtualDesktop *VirtualDesktopGrid::at(const QPoint &coords) const { if (coords.y() >= m_grid.count()) { return nullptr; } const auto &row = m_grid.at(coords.y()); if (coords.x() >= row.count()) { return nullptr; } return row.at(coords.x()); } KWIN_SINGLETON_FACTORY_VARIABLE(VirtualDesktopManager, s_manager) VirtualDesktopManager::VirtualDesktopManager(QObject *parent) : QObject(parent) , m_navigationWrapsAround(false) - , m_rootInfo(NULL) + , m_rootInfo(nullptr) { } VirtualDesktopManager::~VirtualDesktopManager() { - s_manager = NULL; + s_manager = nullptr; } void VirtualDesktopManager::setRootInfo(NETRootInfo *info) { m_rootInfo = info; // Nothing will be connected to rootInfo if (m_rootInfo) { for (auto *vd : m_desktops) { m_rootInfo->setDesktopName(vd->x11DesktopNumber(), vd->name().toUtf8().data()); } } } QString VirtualDesktopManager::name(uint desktop) const { if (uint(m_desktops.length()) > desktop - 1) { return m_desktops[desktop - 1]->name(); } if (!m_rootInfo) { return defaultName(desktop); } return QString::fromUtf8(m_rootInfo->desktopName(desktop)); } uint VirtualDesktopManager::above(uint id, bool wrap) const { auto vd = above(desktopForX11Id(id), wrap); return vd ? vd->x11DesktopNumber() : 0; } VirtualDesktop *VirtualDesktopManager::above(VirtualDesktop *desktop, bool wrap) const { Q_ASSERT(m_current); if (!desktop) { desktop = m_current; } QPoint coords = m_grid.gridCoords(desktop); Q_ASSERT(coords.x() >= 0); while (true) { coords.ry()--; if (coords.y() < 0) { if (wrap) { coords.setY(m_grid.height() - 1); } else { return desktop; // Already at the top-most desktop } } if (VirtualDesktop *vd = m_grid.at(coords)) { return vd; } } return nullptr; } uint VirtualDesktopManager::toRight(uint id, bool wrap) const { auto vd = toRight(desktopForX11Id(id), wrap); return vd ? vd->x11DesktopNumber() : 0; } VirtualDesktop *VirtualDesktopManager::toRight(VirtualDesktop *desktop, bool wrap) const { Q_ASSERT(m_current); if (!desktop) { desktop = m_current; } QPoint coords = m_grid.gridCoords(desktop); Q_ASSERT(coords.x() >= 0); while (true) { coords.rx()++; if (coords.x() >= m_grid.width()) { if (wrap) { coords.setX(0); } else { return desktop; // Already at the right-most desktop } } if (VirtualDesktop *vd = m_grid.at(coords)) { return vd; } } return nullptr; } uint VirtualDesktopManager::below(uint id, bool wrap) const { auto vd = below(desktopForX11Id(id), wrap); return vd ? vd->x11DesktopNumber() : 0; } VirtualDesktop *VirtualDesktopManager::below(VirtualDesktop *desktop, bool wrap) const { Q_ASSERT(m_current); if (!desktop) { desktop = m_current; } QPoint coords = m_grid.gridCoords(desktop); Q_ASSERT(coords.x() >= 0); while (true) { coords.ry()++; if (coords.y() >= m_grid.height()) { if (wrap) { coords.setY(0); } else { // Already at the bottom-most desktop return desktop; } } if (VirtualDesktop *vd = m_grid.at(coords)) { return vd; } } return nullptr; } uint VirtualDesktopManager::toLeft(uint id, bool wrap) const { auto vd = toLeft(desktopForX11Id(id), wrap); return vd ? vd->x11DesktopNumber() : 0; } VirtualDesktop *VirtualDesktopManager::toLeft(VirtualDesktop *desktop, bool wrap) const { Q_ASSERT(m_current); if (!desktop) { desktop = m_current; } QPoint coords = m_grid.gridCoords(desktop); Q_ASSERT(coords.x() >= 0); while (true) { coords.rx()--; if (coords.x() < 0) { if (wrap) { coords.setX(m_grid.width() - 1); } else { return desktop; // Already at the left-most desktop } } if (VirtualDesktop *vd = m_grid.at(coords)) { return vd; } } return nullptr; } VirtualDesktop *VirtualDesktopManager::next(VirtualDesktop *desktop, bool wrap) const { Q_ASSERT(m_current); if (!desktop) { desktop = m_current; } auto it = std::find(m_desktops.begin(), m_desktops.end(), desktop); Q_ASSERT(it != m_desktops.end()); it++; if (it == m_desktops.end()) { if (wrap) { return m_desktops.first(); } else { return desktop; } } return *it; } VirtualDesktop *VirtualDesktopManager::previous(VirtualDesktop *desktop, bool wrap) const { Q_ASSERT(m_current); if (!desktop) { desktop = m_current; } auto it = std::find(m_desktops.begin(), m_desktops.end(), desktop); Q_ASSERT(it != m_desktops.end()); if (it == m_desktops.begin()) { if (wrap) { return m_desktops.last(); } else { return desktop; } } it--; return *it; } VirtualDesktop *VirtualDesktopManager::desktopForX11Id(uint id) const { if (id == 0 || id > count()) { return nullptr; } return m_desktops.at(id - 1); } VirtualDesktop *VirtualDesktopManager::desktopForId(const QByteArray &id) const { auto desk = std::find_if( m_desktops.constBegin(), m_desktops.constEnd(), [id] (const VirtualDesktop *desk ) { return desk->id() == id; } ); if (desk != m_desktops.constEnd()) { return *desk; } return nullptr; } VirtualDesktop *VirtualDesktopManager::createVirtualDesktop(uint position, const QString &name) { //too many, can't insert new ones if ((uint)m_desktops.count() == VirtualDesktopManager::maximum()) { return nullptr; } position = qBound(0u, position, static_cast(m_desktops.count())); auto *vd = new VirtualDesktop(this); vd->setX11DesktopNumber(position + 1); vd->setId(generateDesktopId()); vd->setName(name); connect(vd, &VirtualDesktop::nameChanged, this, [this, vd]() { if (m_rootInfo) { m_rootInfo->setDesktopName(vd->x11DesktopNumber(), vd->name().toUtf8().data()); } } ); if (m_rootInfo) { m_rootInfo->setDesktopName(vd->x11DesktopNumber(), vd->name().toUtf8().data()); } m_desktops.insert(position, vd); //update the id of displaced desktops for (uint i = position + 1; i < (uint)m_desktops.count(); ++i) { m_desktops[i]->setX11DesktopNumber(i + 1); if (m_rootInfo) { m_rootInfo->setDesktopName(i + 1, m_desktops[i]->name().toUtf8().data()); } } save(); updateRootInfo(); emit desktopCreated(vd); emit countChanged(m_desktops.count()-1, m_desktops.count()); return vd; } void VirtualDesktopManager::removeVirtualDesktop(const QByteArray &id) { //don't end up without any desktop if (m_desktops.count() == 1) { return; } auto desktop = desktopForId(id); if (!desktop) { return; } const uint oldCurrent = m_current->x11DesktopNumber(); const uint i = desktop->x11DesktopNumber() - 1; m_desktops.remove(i); for (uint j = i; j < (uint)m_desktops.count(); ++j) { m_desktops[j]->setX11DesktopNumber(j + 1); if (m_rootInfo) { m_rootInfo->setDesktopName(j + 1, m_desktops[j]->name().toUtf8().data()); } } const uint newCurrent = qMin(oldCurrent, (uint)m_desktops.count()); m_current = m_desktops.at(newCurrent - 1); if (oldCurrent != newCurrent) { emit currentChanged(oldCurrent, newCurrent); } save(); updateRootInfo(); emit desktopRemoved(desktop); emit countChanged(m_desktops.count()+1, m_desktops.count()); desktop->deleteLater(); } uint VirtualDesktopManager::current() const { return m_current ? m_current->x11DesktopNumber() : 0; } VirtualDesktop *VirtualDesktopManager::currentDesktop() const { return m_current; } bool VirtualDesktopManager::setCurrent(uint newDesktop) { if (newDesktop < 1 || newDesktop > count() || newDesktop == current()) { return false; } auto d = desktopForX11Id(newDesktop); Q_ASSERT(d); return setCurrent(d); } bool VirtualDesktopManager::setCurrent(VirtualDesktop *newDesktop) { Q_ASSERT(newDesktop); if (m_current == newDesktop) { return false; } const uint oldDesktop = current(); m_current = newDesktop; emit currentChanged(oldDesktop, newDesktop->x11DesktopNumber()); return true; } void VirtualDesktopManager::setCount(uint count) { count = qBound(1, count, VirtualDesktopManager::maximum()); if (count == uint(m_desktops.count())) { // nothing to change return; } QList newDesktops; const uint oldCount = m_desktops.count(); //this explicit check makes it more readable if ((uint)m_desktops.count() > count) { const auto desktopsToRemove = m_desktops.mid(count); m_desktops.resize(count); if (m_current) { uint oldCurrent = current(); uint newCurrent = qMin(oldCurrent, count); m_current = m_desktops.at(newCurrent - 1); if (oldCurrent != newCurrent) { emit currentChanged(oldCurrent, newCurrent); } } for (auto desktop : desktopsToRemove) { emit desktopRemoved(desktop); desktop->deleteLater(); } } else { while (uint(m_desktops.count()) < count) { auto vd = new VirtualDesktop(this); const int x11Number = m_desktops.count() + 1; vd->setX11DesktopNumber(x11Number); vd->setName(defaultName(x11Number)); if (!s_loadingDesktopSettings) { vd->setId(generateDesktopId()); } m_desktops << vd; newDesktops << vd; connect(vd, &VirtualDesktop::nameChanged, this, [this, vd] { if (m_rootInfo) { m_rootInfo->setDesktopName(vd->x11DesktopNumber(), vd->name().toUtf8().data()); } } ); if (m_rootInfo) { m_rootInfo->setDesktopName(vd->x11DesktopNumber(), vd->name().toUtf8().data()); } } } updateRootInfo(); if (!s_loadingDesktopSettings) { save(); } for (auto vd : newDesktops) { emit desktopCreated(vd); } emit countChanged(oldCount, m_desktops.count()); } uint VirtualDesktopManager::rows() const { return m_rows; } void VirtualDesktopManager::setRows(uint rows) { if (rows == 0 || rows > count() || rows == m_rows) { return; } m_rows = rows; int columns = count() / m_rows; if (count() % m_rows > 0) { columns++; } if (m_rootInfo) { m_rootInfo->setDesktopLayout(NET::OrientationHorizontal, columns, m_rows, NET::DesktopLayoutCornerTopLeft); m_rootInfo->activate(); } updateLayout(); //rowsChanged will be emitted by setNETDesktopLayout called by updateLayout } void VirtualDesktopManager::updateRootInfo() { if (!m_rootInfo) { // Make sure the layout is still valid updateLayout(); return; } const int n = count(); m_rootInfo->setNumberOfDesktops(n); NETPoint *viewports = new NETPoint[n]; m_rootInfo->setDesktopViewport(n, *viewports); delete[] viewports; // Make sure the layout is still valid updateLayout(); } void VirtualDesktopManager::updateLayout() { m_rows = qMin(m_rows, count()); int columns = count() / m_rows; Qt::Orientation orientation = Qt::Horizontal; if (m_rootInfo) { // TODO: Is there a sane way to avoid overriding the existing grid? columns = m_rootInfo->desktopLayoutColumnsRows().width(); m_rows = qMax(1, m_rootInfo->desktopLayoutColumnsRows().height()); orientation = m_rootInfo->desktopLayoutOrientation() == NET::OrientationHorizontal ? Qt::Horizontal : Qt::Vertical; } if (columns == 0) { // Not given, set default layout m_rows = count() == 1u ? 1 : 2; columns = count() / m_rows; } setNETDesktopLayout(orientation, columns, m_rows, 0 //rootInfo->desktopLayoutCorner() // Not really worth implementing right now. ); } void VirtualDesktopManager::load() { s_loadingDesktopSettings = true; if (!m_config) { return; } QString groupname; if (screen_number == 0) { groupname = QStringLiteral("Desktops"); } else { groupname = QStringLiteral("Desktops-screen-%1").arg(screen_number); } KConfigGroup group(m_config, groupname); const int n = group.readEntry("Number", 1); setCount(n); for (int i = 1; i <= n; i++) { QString s = group.readEntry(QStringLiteral("Name_%1").arg(i), i18n("Desktop %1", i)); if (m_rootInfo) { m_rootInfo->setDesktopName(i, s.toUtf8().data()); } m_desktops[i-1]->setName(s.toUtf8().data()); const QString sId = group.readEntry(QStringLiteral("Id_%1").arg(i), QString()); //load gets called 2 times, see workspace.cpp line 416 and BUG 385260 if (m_desktops[i-1]->id().isEmpty()) { m_desktops[i-1]->setId(sId.isEmpty() ? generateDesktopId() : sId.toUtf8()); } else { Q_ASSERT(sId.isEmpty() || m_desktops[i-1]->id() == sId.toUtf8().data()); } // TODO: update desktop focus chain, why? // m_desktopFocusChain.value()[i-1] = i; } int rows = group.readEntry("Rows", 2); m_rows = qBound(1, rows, n); if (m_rootInfo) { // avoid weird cases like having 3 rows for 4 desktops, where the last row is unused int columns = n / m_rows; if (n % m_rows > 0) { columns++; } m_rootInfo->setDesktopLayout(NET::OrientationHorizontal, columns, m_rows, NET::DesktopLayoutCornerTopLeft); m_rootInfo->activate(); } s_loadingDesktopSettings = false; } void VirtualDesktopManager::save() { if (s_loadingDesktopSettings) { return; } if (!m_config) { return; } QString groupname; if (screen_number == 0) { groupname = QStringLiteral("Desktops"); } else { groupname = QStringLiteral("Desktops-screen-%1").arg(screen_number); } KConfigGroup group(m_config, groupname); for (int i = count() + 1; group.hasKey(QStringLiteral("Id_%1").arg(i)); i++) { group.deleteEntry(QStringLiteral("Id_%1").arg(i)); group.deleteEntry(QStringLiteral("Name_%1").arg(i)); } group.writeEntry("Number", count()); for (uint i = 1; i <= count(); ++i) { QString s = name(i); const QString defaultvalue = defaultName(i); if (s.isEmpty()) { s = defaultvalue; if (m_rootInfo) { m_rootInfo->setDesktopName(i, s.toUtf8().data()); } } if (s != defaultvalue) { group.writeEntry(QStringLiteral("Name_%1").arg(i), s); } else { QString currentvalue = group.readEntry(QStringLiteral("Name_%1").arg(i), QString()); if (currentvalue != defaultvalue) { group.deleteEntry(QStringLiteral("Name_%1").arg(i)); } } group.writeEntry(QStringLiteral("Id_%1").arg(i), m_desktops[i-1]->id()); } group.writeEntry("Rows", m_rows); // Save to disk group.sync(); } QString VirtualDesktopManager::defaultName(int desktop) const { return i18n("Desktop %1", desktop); } void VirtualDesktopManager::setNETDesktopLayout(Qt::Orientation orientation, uint width, uint height, int startingCorner) { Q_UNUSED(startingCorner); // Not really worth implementing right now. const uint count = m_desktops.count(); // Calculate valid grid size Q_ASSERT(width > 0 || height > 0); if ((width <= 0) && (height > 0)) { width = (count + height - 1) / height; } else if ((height <= 0) && (width > 0)) { height = (count + width - 1) / width; } while (width * height < count) { if (orientation == Qt::Horizontal) { ++width; } else { ++height; } } m_rows = qMax(1u, height); m_grid.update(QSize(width, height), orientation, m_desktops); // TODO: why is there no call to m_rootInfo->setDesktopLayout? emit layoutChanged(width, height); emit rowsChanged(height); } void VirtualDesktopManager::initShortcuts() { initSwitchToShortcuts(); QAction *nextAction = addAction(QStringLiteral("Switch to Next Desktop"), i18n("Switch to Next Desktop"), &VirtualDesktopManager::slotNext); input()->registerTouchpadSwipeShortcut(SwipeDirection::Right, nextAction); QAction *previousAction = addAction(QStringLiteral("Switch to Previous Desktop"), i18n("Switch to Previous Desktop"), &VirtualDesktopManager::slotPrevious); input()->registerTouchpadSwipeShortcut(SwipeDirection::Left, previousAction); addAction(QStringLiteral("Switch One Desktop to the Right"), i18n("Switch One Desktop to the Right"), &VirtualDesktopManager::slotRight); addAction(QStringLiteral("Switch One Desktop to the Left"), i18n("Switch One Desktop to the Left"), &VirtualDesktopManager::slotLeft); addAction(QStringLiteral("Switch One Desktop Up"), i18n("Switch One Desktop Up"), &VirtualDesktopManager::slotUp); addAction(QStringLiteral("Switch One Desktop Down"), i18n("Switch One Desktop Down"), &VirtualDesktopManager::slotDown); // axis events input()->registerAxisShortcut(Qt::ControlModifier | Qt::AltModifier, PointerAxisDown, findChild(QStringLiteral("Switch to Next Desktop"))); input()->registerAxisShortcut(Qt::ControlModifier | Qt::AltModifier, PointerAxisUp, findChild(QStringLiteral("Switch to Previous Desktop"))); } void VirtualDesktopManager::initSwitchToShortcuts() { const QString toDesktop = QStringLiteral("Switch to Desktop %1"); const KLocalizedString toDesktopLabel = ki18n("Switch to Desktop %1"); addAction(toDesktop, toDesktopLabel, 1, QKeySequence(Qt::CTRL + Qt::Key_F1), &VirtualDesktopManager::slotSwitchTo); addAction(toDesktop, toDesktopLabel, 2, QKeySequence(Qt::CTRL + Qt::Key_F2), &VirtualDesktopManager::slotSwitchTo); addAction(toDesktop, toDesktopLabel, 3, QKeySequence(Qt::CTRL + Qt::Key_F3), &VirtualDesktopManager::slotSwitchTo); addAction(toDesktop, toDesktopLabel, 4, QKeySequence(Qt::CTRL + Qt::Key_F4), &VirtualDesktopManager::slotSwitchTo); for (uint i = 5; i <= maximum(); ++i) { addAction(toDesktop, toDesktopLabel, i, QKeySequence(), &VirtualDesktopManager::slotSwitchTo); } } QAction *VirtualDesktopManager::addAction(const QString &name, const KLocalizedString &label, uint value, const QKeySequence &key, void (VirtualDesktopManager::*slot)()) { QAction *a = new QAction(this); a->setProperty("componentName", QStringLiteral(KWIN_NAME)); a->setObjectName(name.arg(value)); a->setText(label.subs(value).toString()); a->setData(value); KGlobalAccel::setGlobalShortcut(a, key); input()->registerShortcut(key, a, this, slot); return a; } QAction *VirtualDesktopManager::addAction(const QString &name, const QString &label, void (VirtualDesktopManager::*slot)()) { QAction *a = new QAction(this); a->setProperty("componentName", QStringLiteral(KWIN_NAME)); a->setObjectName(name); a->setText(label); KGlobalAccel::setGlobalShortcut(a, QKeySequence()); input()->registerShortcut(QKeySequence(), a, this, slot); return a; } void VirtualDesktopManager::slotSwitchTo() { QAction *act = qobject_cast(sender()); if (!act) { return; } bool ok = false; const uint i = act->data().toUInt(&ok); if (!ok) { return; } setCurrent(i); } void VirtualDesktopManager::setNavigationWrappingAround(bool enabled) { if (enabled == m_navigationWrapsAround) { return; } m_navigationWrapsAround = enabled; emit navigationWrappingAroundChanged(); } void VirtualDesktopManager::slotDown() { moveTo(isNavigationWrappingAround()); } void VirtualDesktopManager::slotLeft() { moveTo(isNavigationWrappingAround()); } void VirtualDesktopManager::slotPrevious() { moveTo(isNavigationWrappingAround()); } void VirtualDesktopManager::slotNext() { moveTo(isNavigationWrappingAround()); } void VirtualDesktopManager::slotRight() { moveTo(isNavigationWrappingAround()); } void VirtualDesktopManager::slotUp() { moveTo(isNavigationWrappingAround()); } } // KWin diff --git a/workspace.cpp b/workspace.cpp index 6d4309b44..298f72653 100644 --- a/workspace.cpp +++ b/workspace.cpp @@ -1,1808 +1,1808 @@ /******************************************************************** 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 . *********************************************************************/ // 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 "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 = 0; +Workspace* Workspace::_self = nullptr; Workspace::Workspace(const QString &sessionKey) - : QObject(0) - , m_compositor(NULL) + : QObject(nullptr) + , m_compositor(nullptr) // Unsorted - , active_popup(NULL) - , active_popup_client(NULL) + , active_popup(nullptr) + , active_popup_client(nullptr) , m_initialDesktop(1) - , active_client(0) - , last_active_client(0) - , most_recently_raised(0) - , movingClient(0) - , delayfocus_client(0) + , 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(NULL) - , client_keys_client(NULL) + , client_keys_dialog(nullptr) + , client_keys_client(nullptr) , global_shortcuts_disabled_for_client(false) , workspaceInit(true) - , startup(0) + , 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 = 0; + 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 = NULL; + 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 == NULL - && activeClient() == NULL && should_get_focus.count() == 0) { + if (new_active_client == nullptr + && activeClient() == nullptr && should_get_focus.count() == 0) { // No client activated in manage() - if (new_active_client == NULL) + if (new_active_client == nullptr) new_active_client = topClientOnDesktop(VirtualDesktopManager::self()->current(), -1); - if (new_active_client == NULL && !desktops.isEmpty()) + if (new_active_client == nullptr && !desktops.isEmpty()) new_active_client = findDesktop(true, VirtualDesktopManager::self()->current()); } - if (new_active_client != NULL) + 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); 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 = 0; + _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 NULL; + 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 NULL; + 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 != NULL) + if (grp != nullptr) grp->gotLeader(c); if (c->isDesktop()) { desktops.append(c); - if (active_client == NULL && should_get_focus.isEmpty() && c->isOnCurrentDesktop()) + 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() == NULL && should_get_focus.count() == 0) + 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 != NULL) + if (group != nullptr) group->lostLeader(); if (c == most_recently_raised) - most_recently_raised = 0; + most_recently_raised = nullptr; should_get_focus.removeAll(c); Q_ASSERT(c != active_client); if (c == last_active_client) - last_active_client = 0; + 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 != NULL && c->group() == client->group()) + else if (client != nullptr && c->group() == client->group()) show = true; else show = false; } else { - if (group != NULL && c->group() == group) + if (group != nullptr && c->group() == group) show = true; - else if (client != NULL && client->hasTransient(c, 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 = NULL; + 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 == NULL && !desktops.isEmpty()) + if (c == nullptr && !desktops.isEmpty()) c = findDesktop(true, desktop); if (c != active_client) - setActiveClient(NULL); + setActiveClient(nullptr); if (c) requestFocus(c); else if (!desktops.isEmpty()) requestFocus(findDesktop(true, desktop)); else focusToNull(); } AbstractClient *Workspace::findClientToActivateOnDesktop(uint desktop) { - if (movingClient != NULL && active_client == movingClient && + 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 = 0; + 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 == NULL && !desktops.isEmpty()) + if (c == nullptr && !desktops.isEmpty()) c = findDesktop(true, VirtualDesktopManager::self()->current()); if (c != active_client) - setActiveClient(NULL); + 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 = 0; + 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; } } 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; } } return findUnmanaged(w->winId()); } 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); } Toplevel *Workspace::findInternal(QWindow *w) const { if (!w) { return nullptr; } if (kwinApp()->operationMode() == Application::OperationModeX11) { return findUnmanaged(w->winId()); } else { return waylandServer()->findClient(w); } } 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 diff --git a/workspace.h b/workspace.h index 1aff18e2e..12a3b15e1 100644 --- a/workspace.h +++ b/workspace.h @@ -1,774 +1,774 @@ /******************************************************************** 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 . *********************************************************************/ #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 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; } 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 = NULL) const; + 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; } 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(); 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; 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 diff --git a/xcbutils.cpp b/xcbutils.cpp index fa9f5936e..1c873205d 100644 --- a/xcbutils.cpp +++ b/xcbutils.cpp @@ -1,620 +1,620 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2006 Lubos Lunak Copyright (C) 2012 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 "xcbutils.h" #include "utils.h" // Qt #include // xcb #include #include #include #include #include #include #include #include // system #include #include namespace KWin { namespace Xcb { static const int COMPOSITE_MAX_MAJOR = 0; static const int COMPOSITE_MAX_MINOR = 4; static const int DAMAGE_MAX_MAJOR = 1; static const int DAMAGE_MIN_MAJOR = 1; static const int SYNC_MAX_MAJOR = 3; static const int SYNC_MAX_MINOR = 0; static const int RANDR_MAX_MAJOR = 1; static const int RANDR_MAX_MINOR = 4; static const int RENDER_MAX_MAJOR = 0; static const int RENDER_MAX_MINOR = 11; static const int XFIXES_MAX_MAJOR = 5; static const int XFIXES_MAX_MINOR = 0; QVector shapeOpCodes() { // see https://www.x.org/releases/X11R7.7/doc/xextproto/shape.html // extracted from return QVector({QByteArrayLiteral("QueryVersion"), QByteArrayLiteral("Rectangles"), QByteArrayLiteral("Mask"), QByteArrayLiteral("Combine"), QByteArrayLiteral("Offset"), QByteArrayLiteral("Extents"), QByteArrayLiteral("Input"), QByteArrayLiteral("InputSelected"), QByteArrayLiteral("GetRectangles")}); } QVector randrOpCodes() { // see https://www.x.org/releases/X11R7.7/doc/randrproto/randrproto.txt // extracted from return QVector({QByteArrayLiteral("QueryVersion"), QByteArray(""), // doesn't exist QByteArrayLiteral("SetScreenConfig"), QByteArray(""), // doesn't exits QByteArrayLiteral("SelectInput"), QByteArrayLiteral("GetScreenInfo"), QByteArrayLiteral("GetScreenSizeRange"), QByteArrayLiteral("SetScreenSize"), QByteArrayLiteral("GetScreenResources"), QByteArrayLiteral("GetOutputInfo"), QByteArrayLiteral("ListOutputProperties"), QByteArrayLiteral("QueryOutputProperty"), QByteArrayLiteral("ConfigureOutputProperty"), QByteArrayLiteral("ChangeOutputProperty"), QByteArrayLiteral("DeleteOutputProperty"), QByteArrayLiteral("GetOutputproperty"), QByteArrayLiteral("CreateMode"), QByteArrayLiteral("DestroyMode"), QByteArrayLiteral("AddOutputMode"), QByteArrayLiteral("DeleteOutputMode"), QByteArrayLiteral("GetCrtcInfo"), QByteArrayLiteral("SetCrtcConfig"), QByteArrayLiteral("GetCrtcGammaSize"), QByteArrayLiteral("GetCrtcGamma"), QByteArrayLiteral("SetCrtcGamma"), QByteArrayLiteral("GetScreenResourcesCurrent"), QByteArrayLiteral("SetCrtcTransform"), QByteArrayLiteral("GetCrtcTransform"), QByteArrayLiteral("GetPanning"), QByteArrayLiteral("SetPanning"), QByteArrayLiteral("SetOutputPrimary"), QByteArrayLiteral("GetOutputPrimary"), QByteArrayLiteral("GetProviders"), QByteArrayLiteral("GetProviderInfo"), QByteArrayLiteral("SetProviderOffloadSink"), QByteArrayLiteral("SetProviderOutputSource"), QByteArrayLiteral("ListProviderProperties"), QByteArrayLiteral("QueryProviderProperty"), QByteArrayLiteral("ConfigureProviderroperty"), QByteArrayLiteral("ChangeProviderProperty"), QByteArrayLiteral("DeleteProviderProperty"), QByteArrayLiteral("GetProviderProperty")}); } QVector randrErrorCodes() { // see https://www.x.org/releases/X11R7.7/doc/randrproto/randrproto.txt // extracted from return QVector({QByteArrayLiteral("BadOutput"), QByteArrayLiteral("BadCrtc"), QByteArrayLiteral("BadMode"), QByteArrayLiteral("BadProvider")}); } QVector damageOpCodes() { // see https://www.x.org/releases/X11R7.7/doc/damageproto/damageproto.txt // extracted from return QVector({QByteArrayLiteral("QueryVersion"), QByteArrayLiteral("Create"), QByteArrayLiteral("Destroy"), QByteArrayLiteral("Subtract"), QByteArrayLiteral("Add")}); } QVector damageErrorCodes() { // see https://www.x.org/releases/X11R7.7/doc/damageproto/damageproto.txt // extracted from return QVector({QByteArrayLiteral("BadDamage")}); } QVector compositeOpCodes() { // see https://www.x.org/releases/X11R7.7/doc/compositeproto/compositeproto.txt // extracted from return QVector({QByteArrayLiteral("QueryVersion"), QByteArrayLiteral("RedirectWindow"), QByteArrayLiteral("RedirectSubwindows"), QByteArrayLiteral("UnredirectWindow"), QByteArrayLiteral("UnredirectSubwindows"), QByteArrayLiteral("CreateRegionFromBorderClip"), QByteArrayLiteral("NameWindowPixmap"), QByteArrayLiteral("GetOverlayWindow"), QByteArrayLiteral("ReleaseOverlayWindow")}); } QVector fixesOpCodes() { // see https://www.x.org/releases/X11R7.7/doc/fixesproto/fixesproto.txt // extracted from return QVector({QByteArrayLiteral("QueryVersion"), QByteArrayLiteral("ChangeSaveSet"), QByteArrayLiteral("SelectSelectionInput"), QByteArrayLiteral("SelectCursorInput"), QByteArrayLiteral("GetCursorImage"), QByteArrayLiteral("CreateRegion"), QByteArrayLiteral("CreateRegionFromBitmap"), QByteArrayLiteral("CreateRegionFromWindow"), QByteArrayLiteral("CreateRegionFromGc"), QByteArrayLiteral("CreateRegionFromPicture"), QByteArrayLiteral("DestroyRegion"), QByteArrayLiteral("SetRegion"), QByteArrayLiteral("CopyRegion"), QByteArrayLiteral("UnionRegion"), QByteArrayLiteral("IntersectRegion"), QByteArrayLiteral("SubtractRegion"), QByteArrayLiteral("InvertRegion"), QByteArrayLiteral("TranslateRegion"), QByteArrayLiteral("RegionExtents"), QByteArrayLiteral("FetchRegion"), QByteArrayLiteral("SetGcClipRegion"), QByteArrayLiteral("SetWindowShapeRegion"), QByteArrayLiteral("SetPictureClipRegion"), QByteArrayLiteral("SetCursorName"), QByteArrayLiteral("GetCursorName"), QByteArrayLiteral("GetCursorImageAndName"), QByteArrayLiteral("ChangeCursor"), QByteArrayLiteral("ChangeCursorByName"), QByteArrayLiteral("ExpandRegion"), QByteArrayLiteral("HideCursor"), QByteArrayLiteral("ShowCursor"), QByteArrayLiteral("CreatePointerBarrier"), QByteArrayLiteral("DeletePointerBarrier")}); } QVector fixesErrorCodes() { // see https://www.x.org/releases/X11R7.7/doc/fixesproto/fixesproto.txt // extracted from return QVector({QByteArrayLiteral("BadRegion")}); } QVector renderOpCodes() { // see https://www.x.org/releases/X11R7.7/doc/renderproto/renderproto.txt // extracted from return QVector({QByteArrayLiteral("QueryVersion"), QByteArrayLiteral("QueryPictFormats"), QByteArrayLiteral("QueryPictIndexValues"), QByteArrayLiteral("CreatePicture"), QByteArrayLiteral("ChangePicture"), QByteArrayLiteral("SetPictureClipRectangles"), QByteArrayLiteral("FreePicture"), QByteArrayLiteral("Composite"), QByteArrayLiteral("Trapezoids"), QByteArrayLiteral("Triangles"), QByteArrayLiteral("TriStrip"), QByteArrayLiteral("TriFan"), QByteArrayLiteral("CreateGlyphSet"), QByteArrayLiteral("ReferenceGlyphSet"), QByteArrayLiteral("FreeGlyphSet"), QByteArrayLiteral("AddGlyphs"), QByteArrayLiteral("FreeGlyphs"), QByteArrayLiteral("CompositeGlyphs8"), QByteArrayLiteral("CompositeGlyphs16"), QByteArrayLiteral("CompositeGlyphs32"), QByteArrayLiteral("FillRectangles"), QByteArrayLiteral("CreateCursor"), QByteArrayLiteral("SetPictureTransform"), QByteArrayLiteral("QueryFilters"), QByteArrayLiteral("SetPictureFilter"), QByteArrayLiteral("CreateAnimCursor"), QByteArrayLiteral("AddTraps"), QByteArrayLiteral("CreateSolidFill"), QByteArrayLiteral("CreateLinearGradient"), QByteArrayLiteral("CreateRadialGradient"), QByteArrayLiteral("CreateConicalGradient")}); } QVector syncOpCodes() { // see https://www.x.org/releases/X11R7.7/doc/xextproto/sync.html // extracted from return QVector({QByteArrayLiteral("Initialize"), QByteArrayLiteral("ListSystemCounters"), QByteArrayLiteral("CreateCounter"), QByteArrayLiteral("DestroyCounter"), QByteArrayLiteral("QueryCounter"), QByteArrayLiteral("Await"), QByteArrayLiteral("ChangeCounter"), QByteArrayLiteral("SetCounter"), QByteArrayLiteral("CreateAlarm"), QByteArrayLiteral("ChangeAlarm"), QByteArrayLiteral("DestroyAlarm"), QByteArrayLiteral("QueryAlarm"), QByteArrayLiteral("SetPriority"), QByteArrayLiteral("GetPriority"), QByteArrayLiteral("CreateFence"), QByteArrayLiteral("TriggerFence"), QByteArrayLiteral("ResetFence"), QByteArrayLiteral("DestroyFence"), QByteArrayLiteral("QueryFence"), QByteArrayLiteral("AwaitFence")}); } static QVector glxOpCodes() { return QVector{ QByteArrayLiteral(""), QByteArrayLiteral("Render"), QByteArrayLiteral("RenderLarge"), QByteArrayLiteral("CreateContext"), QByteArrayLiteral("DestroyContext"), QByteArrayLiteral("MakeCurrent"), QByteArrayLiteral("IsDirect"), QByteArrayLiteral("QueryVersion"), QByteArrayLiteral("WaitGL"), QByteArrayLiteral("WaitX"), QByteArrayLiteral("CopyContext"), QByteArrayLiteral("SwapBuffers"), QByteArrayLiteral("UseXFont"), QByteArrayLiteral("CreateGLXPixmap"), QByteArrayLiteral("GetVisualConfigs"), QByteArrayLiteral("DestroyGLXPixmap"), QByteArrayLiteral("VendorPrivate"), QByteArrayLiteral("VendorPrivateWithReply"), QByteArrayLiteral("QueryExtensionsString"), QByteArrayLiteral("QueryServerString"), QByteArrayLiteral("ClientInfo"), QByteArrayLiteral("GetFBConfigs"), QByteArrayLiteral("CreatePixmap"), QByteArrayLiteral("DestroyPixmap"), QByteArrayLiteral("CreateNewContext"), QByteArrayLiteral("QueryContext"), QByteArrayLiteral("MakeContextCurrent"), QByteArrayLiteral("CreatePbuffer"), QByteArrayLiteral("DestroyPbuffer"), QByteArrayLiteral("GetDrawableAttributes"), QByteArrayLiteral("ChangeDrawableAttributes"), QByteArrayLiteral("CreateWindow"), QByteArrayLiteral("DeleteWindow"), QByteArrayLiteral("SetClientInfoARB"), QByteArrayLiteral("CreateContextAttribsARB"), QByteArrayLiteral("SetClientInfo2ARB") // Opcodes 36-100 are unused // The GL single commands begin at opcode 101 }; } static QVector glxErrorCodes() { return QVector{ QByteArrayLiteral("BadContext"), QByteArrayLiteral("BadContextState"), QByteArrayLiteral("BadDrawable"), QByteArrayLiteral("BadPixmap"), QByteArrayLiteral("BadContextTag"), QByteArrayLiteral("BadCurrentWindow"), QByteArrayLiteral("BadRenderRequest"), QByteArrayLiteral("BadLargeRequest"), QByteArrayLiteral("UnsupportedPrivateRequest"), QByteArrayLiteral("BadFBConfig"), QByteArrayLiteral("BadPbuffer"), QByteArrayLiteral("BadCurrentDrawable"), QByteArrayLiteral("BadWindow"), QByteArrayLiteral("GLXBadProfileARB") }; } ExtensionData::ExtensionData() : version(0) , eventBase(0) , errorBase(0) , majorOpcode(0) , present(0) { } template void Extensions::initVersion(T cookie, F f, ExtensionData *dataToFill) { - ScopedCPointer version(f(connection(), cookie, NULL)); + ScopedCPointer version(f(connection(), cookie, nullptr)); dataToFill->version = version->major_version * 0x10 + version->minor_version; } -Extensions *Extensions::s_self = NULL; +Extensions *Extensions::s_self = nullptr; Extensions *Extensions::self() { if (!s_self) { s_self = new Extensions(); } return s_self; } void Extensions::destroy() { delete s_self; - s_self = NULL; + s_self = nullptr; } Extensions::Extensions() { init(); } Extensions::~Extensions() { } void Extensions::init() { xcb_connection_t *c = connection(); xcb_prefetch_extension_data(c, &xcb_shape_id); xcb_prefetch_extension_data(c, &xcb_randr_id); xcb_prefetch_extension_data(c, &xcb_damage_id); xcb_prefetch_extension_data(c, &xcb_composite_id); xcb_prefetch_extension_data(c, &xcb_xfixes_id); xcb_prefetch_extension_data(c, &xcb_render_id); xcb_prefetch_extension_data(c, &xcb_sync_id); xcb_prefetch_extension_data(c, &xcb_glx_id); m_shape.name = QByteArray("SHAPE"); m_randr.name = QByteArray("RANDR"); m_damage.name = QByteArray("DAMAGE"); m_composite.name = QByteArray("Composite"); m_fixes.name = QByteArray("XFIXES"); m_render.name = QByteArray("RENDER"); m_sync.name = QByteArray("SYNC"); m_glx.name = QByteArray("GLX"); m_shape.opCodes = shapeOpCodes(); m_randr.opCodes = randrOpCodes(); m_damage.opCodes = damageOpCodes(); m_composite.opCodes = compositeOpCodes(); m_fixes.opCodes = fixesOpCodes(); m_render.opCodes = renderOpCodes(); m_sync.opCodes = syncOpCodes(); m_glx.opCodes = glxOpCodes(); m_randr.errorCodes = randrErrorCodes(); m_damage.errorCodes = damageErrorCodes(); m_fixes.errorCodes = fixesErrorCodes(); m_glx.errorCodes = glxErrorCodes(); extensionQueryReply(xcb_get_extension_data(c, &xcb_shape_id), &m_shape); extensionQueryReply(xcb_get_extension_data(c, &xcb_randr_id), &m_randr); extensionQueryReply(xcb_get_extension_data(c, &xcb_damage_id), &m_damage); extensionQueryReply(xcb_get_extension_data(c, &xcb_composite_id), &m_composite); extensionQueryReply(xcb_get_extension_data(c, &xcb_xfixes_id), &m_fixes); extensionQueryReply(xcb_get_extension_data(c, &xcb_render_id), &m_render); extensionQueryReply(xcb_get_extension_data(c, &xcb_sync_id), &m_sync); extensionQueryReply(xcb_get_extension_data(c, &xcb_glx_id), &m_glx); // extension specific queries xcb_shape_query_version_cookie_t shapeVersion; xcb_randr_query_version_cookie_t randrVersion; xcb_damage_query_version_cookie_t damageVersion; xcb_composite_query_version_cookie_t compositeVersion; xcb_xfixes_query_version_cookie_t xfixesVersion; xcb_render_query_version_cookie_t renderVersion; xcb_sync_initialize_cookie_t syncVersion; if (m_shape.present) { shapeVersion = xcb_shape_query_version_unchecked(c); } if (m_randr.present) { randrVersion = xcb_randr_query_version_unchecked(c, RANDR_MAX_MAJOR, RANDR_MAX_MINOR); xcb_randr_select_input(connection(), rootWindow(), XCB_RANDR_NOTIFY_MASK_SCREEN_CHANGE); } if (m_damage.present) { damageVersion = xcb_damage_query_version_unchecked(c, DAMAGE_MAX_MAJOR, DAMAGE_MIN_MAJOR); } if (m_composite.present) { compositeVersion = xcb_composite_query_version_unchecked(c, COMPOSITE_MAX_MAJOR, COMPOSITE_MAX_MINOR); } if (m_fixes.present) { xfixesVersion = xcb_xfixes_query_version_unchecked(c, XFIXES_MAX_MAJOR, XFIXES_MAX_MINOR); } if (m_render.present) { renderVersion = xcb_render_query_version_unchecked(c, RENDER_MAX_MAJOR, RENDER_MAX_MINOR); } if (m_sync.present) { syncVersion = xcb_sync_initialize(c, SYNC_MAX_MAJOR, SYNC_MAX_MINOR); } // handle replies if (m_shape.present) { initVersion(shapeVersion, &xcb_shape_query_version_reply, &m_shape); } if (m_randr.present) { initVersion(randrVersion, &xcb_randr_query_version_reply, &m_randr); } if (m_damage.present) { initVersion(damageVersion, &xcb_damage_query_version_reply, &m_damage); } if (m_composite.present) { initVersion(compositeVersion, &xcb_composite_query_version_reply, &m_composite); } if (m_fixes.present) { initVersion(xfixesVersion, &xcb_xfixes_query_version_reply, &m_fixes); } if (m_render.present) { initVersion(renderVersion, &xcb_render_query_version_reply, &m_render); } if (m_sync.present) { initVersion(syncVersion, &xcb_sync_initialize_reply, &m_sync); } qCDebug(KWIN_CORE) << "Extensions: shape: 0x" << QString::number(m_shape.version, 16) << " composite: 0x" << QString::number(m_composite.version, 16) << " render: 0x" << QString::number(m_render.version, 16) << " fixes: 0x" << QString::number(m_fixes.version, 16) << " randr: 0x" << QString::number(m_randr.version, 16) << " sync: 0x" << QString::number(m_sync.version, 16) << " damage: 0x " << QString::number(m_damage.version, 16); } void Extensions::extensionQueryReply(const xcb_query_extension_reply_t *extension, ExtensionData *dataToFill) { if (!extension) { return; } dataToFill->present = extension->present; dataToFill->eventBase = extension->first_event; dataToFill->errorBase = extension->first_error; dataToFill->majorOpcode = extension->major_opcode; } int Extensions::damageNotifyEvent() const { return m_damage.eventBase + XCB_DAMAGE_NOTIFY; } bool Extensions::hasShape(xcb_window_t w) const { if (!isShapeAvailable()) { return false; } ScopedCPointer extents(xcb_shape_query_extents_reply( - connection(), xcb_shape_query_extents_unchecked(connection(), w), NULL)); + connection(), xcb_shape_query_extents_unchecked(connection(), w), nullptr)); if (extents.isNull()) { return false; } return extents->bounding_shaped > 0; } bool Extensions::isCompositeOverlayAvailable() const { return m_composite.version >= 0x03; // 0.3 } bool Extensions::isFixesRegionAvailable() const { return m_fixes.version >= 0x30; // 3 } int Extensions::fixesCursorNotifyEvent() const { return m_fixes.eventBase + XCB_XFIXES_CURSOR_NOTIFY; } bool Extensions::isShapeInputAvailable() const { return m_shape.version >= 0x11; // 1.1 } int Extensions::randrNotifyEvent() const { return m_randr.eventBase + XCB_RANDR_SCREEN_CHANGE_NOTIFY; } int Extensions::shapeNotifyEvent() const { return m_shape.eventBase + XCB_SHAPE_NOTIFY; } int Extensions::syncAlarmNotifyEvent() const { return m_sync.eventBase + XCB_SYNC_ALARM_NOTIFY; } QVector Extensions::extensions() const { return { m_shape, m_randr, m_damage, m_composite, m_render, m_fixes, m_sync, m_glx }; } //**************************************** // Shm //**************************************** Shm::Shm() : m_shmId(-1) - , m_buffer(NULL) + , m_buffer(nullptr) , m_segment(XCB_NONE) , m_valid(false) , m_pixmapFormat(XCB_IMAGE_FORMAT_XY_BITMAP) { m_valid = init(); } Shm::~Shm() { if (m_valid) { xcb_shm_detach(connection(), m_segment); shmdt(m_buffer); } } bool Shm::init() { const xcb_query_extension_reply_t *ext = xcb_get_extension_data(connection(), &xcb_shm_id); if (!ext || !ext->present) { qCDebug(KWIN_CORE) << "SHM extension not available"; return false; } ScopedCPointer version(xcb_shm_query_version_reply(connection(), - xcb_shm_query_version_unchecked(connection()), NULL)); + xcb_shm_query_version_unchecked(connection()), nullptr)); if (version.isNull()) { qCDebug(KWIN_CORE) << "Failed to get SHM extension version information"; return false; } m_pixmapFormat = version->pixmap_format; const int MAXSIZE = 4096 * 2048 * 4; // TODO check there are not larger windows m_shmId = shmget(IPC_PRIVATE, MAXSIZE, IPC_CREAT | 0600); if (m_shmId < 0) { qCDebug(KWIN_CORE) << "Failed to allocate SHM segment"; return false; } - m_buffer = shmat(m_shmId, NULL, 0 /*read/write*/); + m_buffer = shmat(m_shmId, nullptr, 0 /*read/write*/); if (-1 == reinterpret_cast(m_buffer)) { qCDebug(KWIN_CORE) << "Failed to attach SHM segment"; - shmctl(m_shmId, IPC_RMID, NULL); + shmctl(m_shmId, IPC_RMID, nullptr); return false; } - shmctl(m_shmId, IPC_RMID, NULL); + shmctl(m_shmId, IPC_RMID, nullptr); m_segment = xcb_generate_id(connection()); const xcb_void_cookie_t cookie = xcb_shm_attach_checked(connection(), m_segment, m_shmId, false); ScopedCPointer error(xcb_request_check(connection(), cookie)); if (!error.isNull()) { qCDebug(KWIN_CORE) << "xcb_shm_attach error: " << error->error_code; shmdt(m_buffer); return false; } return true; } } // namespace Xcb } // namespace KWin diff --git a/xcbutils.h b/xcbutils.h index d44629786..827d92498 100644 --- a/xcbutils.h +++ b/xcbutils.h @@ -1,1885 +1,1885 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2012, 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 . *********************************************************************/ #ifndef KWIN_XCB_UTILS_H #define KWIN_XCB_UTILS_H #include #include "main.h" #include #include #include #include #include #include #include #include class TestXcbSizeHints; namespace KWin { template using ScopedCPointer = QScopedPointer; namespace Xcb { typedef xcb_window_t WindowId; // forward declaration of methods static void defineCursor(xcb_window_t window, xcb_cursor_t cursor); static void setInputFocus(xcb_window_t window, uint8_t revertTo = XCB_INPUT_FOCUS_POINTER_ROOT, xcb_timestamp_t time = xTime()); static void moveWindow(xcb_window_t window, const QPoint &pos); static void moveWindow(xcb_window_t window, uint32_t x, uint32_t y); static void lowerWindow(xcb_window_t window); static void selectInput(xcb_window_t window, uint32_t events); /** * @brief Variadic template to wrap an xcb request. * * This struct is part of the generic implementation to wrap xcb requests * and fetching their reply. Each request is represented by two templated * elements: WrapperData and Wrapper. * * The WrapperData defines the following types: * @li reply_type of the xcb request * @li cookie_type of the xcb request * @li function pointer type for the xcb request * @li function pointer type for the reply * This uses variadic template arguments thus it can be used to specify any * xcb request. * * As the WrapperData does not specify the actual function pointers one needs * to derive another struct which specifies the function pointer requestFunc and * the function pointer replyFunc as static constexpr of type reply_func and * reply_type respectively. E.g. for the command xcb_get_geometry: * @code * struct GeometryData : public WrapperData< xcb_get_geometry_reply_t, xcb_get_geometry_cookie_t, xcb_drawable_t > * { * static constexpr request_func requestFunc = &xcb_get_geometry_unchecked; * static constexpr reply_func replyFunc = &xcb_get_geometry_reply; * }; * @endcode * * To simplify this definition the macro XCB_WRAPPER_DATA is provided. * For the same xcb command this looks like this: * @code * XCB_WRAPPER_DATA(GeometryData, xcb_get_geometry, xcb_drawable_t) * @endcode * * The derived WrapperData has to be passed as first template argument to Wrapper. The other * template arguments of Wrapper are the same variadic template arguments as passed into * WrapperData. This is ensured at compile time and will cause a compile error in case there * is a mismatch of the variadic template arguments passed to WrapperData and Wrapper. * Passing another type than a struct derived from WrapperData to Wrapper will result in a * compile error. The following code snippets won't compile: * @code * XCB_WRAPPER_DATA(GeometryData, xcb_get_geometry, xcb_drawable_t) * // fails with "static assertion failed: Argument miss-match between Wrapper and WrapperData" * class IncorrectArguments : public Wrapper * { * public: * IncorrectArguments() = default; * IncorrectArguments(xcb_window_t window) : Wrapper(window) {} * }; * * // fails with "static assertion failed: Data template argument must be derived from WrapperData" * class WrapperDataDirectly : public Wrapper, xcb_drawable_t> * { * public: * WrapperDataDirectly() = default; * WrapperDataDirectly(xcb_window_t window) : Wrapper, xcb_drawable_t>(window) {} * }; * * // fails with "static assertion failed: Data template argument must be derived from WrapperData" * struct FakeWrapperData * { * typedef xcb_get_geometry_reply_t reply_type; * typedef xcb_get_geometry_cookie_t cookie_type; * typedef std::tuple argument_types; * typedef cookie_type (*request_func)(xcb_connection_t*, xcb_drawable_t); * typedef reply_type *(*reply_func)(xcb_connection_t*, cookie_type, xcb_generic_error_t**); * static constexpr std::size_t argumentCount = 1; * static constexpr request_func requestFunc = &xcb_get_geometry_unchecked; * static constexpr reply_func replyFunc = &xcb_get_geometry_reply; * }; * class NotDerivedFromWrapperData : public Wrapper * { * public: * NotDerivedFromWrapperData() = default; * NotDerivedFromWrapperData(xcb_window_t window) : Wrapper(window) {} * }; * @endcode * * The Wrapper provides an easy to use RAII API which calls the WrapperData's requestFunc in * the ctor and fetches the reply the first time it is used. In addition the dtor takes care * of freeing the reply if it got fetched, otherwise it discards the reply. The Wrapper can * be used as if it were the reply_type directly. * * There are several command wrappers defined which either subclass Wrapper to add methods to * simplify the usage of the result_type or use a typedef. To add a new typedef one can use the * macro XCB_WRAPPER which creates the WrapperData struct as XCB_WRAPPER_DATA does and the * typedef. E.g: * @code * XCB_WRAPPER(Geometry, xcb_get_geometry, xcb_drawable_t) * @endcode * * creates a typedef Geometry and the struct GeometryData. * * Overall this allows to simplify the Xcb usage. For example consider the * following xcb code snippet: * @code * xcb_window_t w; // some window * xcb_connection_t *c = connection(); * const xcb_get_geometry_cookie_t cookie = xcb_get_geometry_unchecked(c, w); * // do other stuff * xcb_get_geometry_reply_t *reply = xcb_get_geometry_reply(c, cookie, nullptr); * if (reply) { * reply->x; // do something with the geometry * } * free(reply); * @endcode * * With the help of the Wrapper class this can be simplified to: * @code * xcb_window_t w; // some window * Xcb::Geometry geo(w); * if (!geo.isNull()) { * geo->x; // do something with the geometry * } * @endcode * * @see XCB_WRAPPER_DATA * @see XCB_WRAPPER * @see Wrapper * @see WindowAttributes * @see OverlayWindow * @see WindowGeometry * @see Tree * @see CurrentInput * @see TransientFor */ template struct WrapperData { /** * @brief The type returned by the xcb reply function. */ typedef Reply reply_type; /** * @brief The type returned by the xcb request function. */ typedef Cookie cookie_type; /** * @brief Variadic arguments combined as a std::tuple. * @internal Used for verifying the arguments. */ typedef std::tuple argument_types; /** * @brief The function pointer definition for the xcb request function. */ typedef Cookie (*request_func)(xcb_connection_t*, Args...); /** * @brief The function pointer definition for the xcb reply function. */ typedef Reply *(*reply_func)(xcb_connection_t*, Cookie, xcb_generic_error_t**); /** * @brief Number of variadic arguments. * @internal Used for verifying the arguments. */ static constexpr std::size_t argumentCount = sizeof...(Args); }; /** * @brief Partial template specialization for WrapperData with no further arguments. * * This will be used for xcb requests just taking the xcb_connection_t* argument. */ template struct WrapperData { typedef Reply reply_type; typedef Cookie cookie_type; typedef std::tuple<> argument_types; typedef Cookie (*request_func)(xcb_connection_t*); typedef Reply *(*reply_func)(xcb_connection_t*, Cookie, xcb_generic_error_t**); static constexpr std::size_t argumentCount = 0; }; /** * @brief Abstract base class for the wrapper. * * This class contains the complete functionality of the Wrapper. It's only an abstract * base class to provide partial template specialization for more specific constructors. */ template class AbstractWrapper { public: typedef typename Data::cookie_type Cookie; typedef typename Data::reply_type Reply; virtual ~AbstractWrapper() { cleanup(); } inline AbstractWrapper &operator=(const AbstractWrapper &other) { if (this != &other) { // if we had managed a reply, free it cleanup(); // copy members m_retrieved = other.m_retrieved; m_cookie = other.m_cookie; m_window = other.m_window; m_reply = other.m_reply; // take over the responsibility for the reply pointer takeFromOther(const_cast(other)); } return *this; } inline const Reply *operator->() { getReply(); return m_reply; } inline bool isNull() { getReply(); - return m_reply == NULL; + return m_reply == nullptr; } inline bool isNull() const { const_cast(this)->getReply(); return m_reply == NULL; } inline operator bool() { return !isNull(); } inline operator bool() const { return !isNull(); } inline const Reply *data() { getReply(); return m_reply; } inline const Reply *data() const { const_cast(this)->getReply(); return m_reply; } inline WindowId window() const { return m_window; } inline bool isRetrieved() const { return m_retrieved; } /** * Returns the value of the reply pointer referenced by this object. The reply pointer of * this object will be reset to null. Calling any method which requires the reply to be valid * will crash. * * Callers of this function take ownership of the pointer. */ inline Reply *take() { getReply(); Reply *ret = m_reply; - m_reply = NULL; + m_reply = nullptr; m_window = XCB_WINDOW_NONE; return ret; } protected: AbstractWrapper() : m_retrieved(false) , m_window(XCB_WINDOW_NONE) - , m_reply(NULL) + , m_reply(nullptr) { m_cookie.sequence = 0; } explicit AbstractWrapper(WindowId window, Cookie cookie) : m_retrieved(false) , m_cookie(cookie) , m_window(window) - , m_reply(NULL) + , m_reply(nullptr) { } explicit AbstractWrapper(const AbstractWrapper &other) : m_retrieved(other.m_retrieved) , m_cookie(other.m_cookie) , m_window(other.m_window) - , m_reply(NULL) + , m_reply(nullptr) { takeFromOther(const_cast(other)); } void getReply() { if (m_retrieved || !m_cookie.sequence) { return; } m_reply = Data::replyFunc(connection(), m_cookie, nullptr); m_retrieved = true; } private: inline void cleanup() { if (!m_retrieved && m_cookie.sequence) { xcb_discard_reply(connection(), m_cookie.sequence); } else if (m_reply) { free(m_reply); } } inline void takeFromOther(AbstractWrapper &other) { if (m_retrieved) { m_reply = other.take(); } else { //ensure that other object doesn't try to get the reply or discards it in the dtor other.m_retrieved = true; other.m_window = XCB_WINDOW_NONE; } } bool m_retrieved; Cookie m_cookie; WindowId m_window; Reply *m_reply; }; /** * @brief Template to compare the arguments of two std::tuple. * * @internal Used by static_assert in Wrapper */ template struct tupleCompare { typedef typename std::tuple_element::type tuple1Type; typedef typename std::tuple_element::type tuple2Type; /** * @c true if both tuple have the same arguments, @c false otherwise. */ static constexpr bool value = std::is_same< tuple1Type, tuple2Type >::value && tupleCompare::value; }; /** * @brief Recursive template case for first tuple element. */ template struct tupleCompare { typedef typename std::tuple_element<0, T1>::type tuple1Type; typedef typename std::tuple_element<0, T2>::type tuple2Type; static constexpr bool value = std::is_same< tuple1Type, tuple2Type >::value; }; /** * @brief Wrapper taking a WrapperData as first template argument and xcb request args as variadic args. */ template class Wrapper : public AbstractWrapper { public: static_assert(!std::is_same >::value, "Data template argument must be derived from WrapperData"); static_assert(std::is_base_of, Data>::value, "Data template argument must be derived from WrapperData"); static_assert(sizeof...(Args) == Data::argumentCount, "Wrapper and WrapperData need to have same template argument count"); static_assert(tupleCompare, typename Data::argument_types, sizeof...(Args) - 1>::value, "Argument miss-match between Wrapper and WrapperData"); Wrapper() = default; explicit Wrapper(Args... args) : AbstractWrapper(XCB_WINDOW_NONE, Data::requestFunc(connection(), args...)) { } explicit Wrapper(xcb_window_t w, Args... args) : AbstractWrapper(w, Data::requestFunc(connection(), args...)) { } }; /** * @brief Template specialization for xcb_window_t being first variadic argument. */ template class Wrapper : public AbstractWrapper { public: static_assert(!std::is_same >::value, "Data template argument must be derived from WrapperData"); static_assert(std::is_base_of, Data>::value, "Data template argument must be derived from WrapperData"); static_assert(sizeof...(Args) + 1 == Data::argumentCount, "Wrapper and WrapperData need to have same template argument count"); static_assert(tupleCompare, typename Data::argument_types, sizeof...(Args)>::value, "Argument miss-match between Wrapper and WrapperData"); Wrapper() = default; explicit Wrapper(xcb_window_t w, Args... args) : AbstractWrapper(w, Data::requestFunc(connection(), w, args...)) { } }; /** * @brief Template specialization for no variadic arguments. * * It's needed to prevent ambiguous constructors being generated. */ template class Wrapper : public AbstractWrapper { public: static_assert(!std::is_same >::value, "Data template argument must be derived from WrapperData"); static_assert(std::is_base_of, Data>::value, "Data template argument must be derived from WrapperData"); static_assert(Data::argumentCount == 0, "Wrapper for no arguments constructed with WrapperData with arguments"); explicit Wrapper() : AbstractWrapper(XCB_WINDOW_NONE, Data::requestFunc(connection())) { } }; class Atom { public: explicit Atom(const QByteArray &name, bool onlyIfExists = false, xcb_connection_t *c = connection()) : m_connection(c) , m_retrieved(false) , m_cookie(xcb_intern_atom_unchecked(m_connection, onlyIfExists, name.length(), name.constData())) , m_atom(XCB_ATOM_NONE) , m_name(name) { } Atom() = delete; Atom(const Atom &) = delete; ~Atom() { if (!m_retrieved && m_cookie.sequence) { xcb_discard_reply(m_connection, m_cookie.sequence); } } operator xcb_atom_t() const { (const_cast(this))->getReply(); return m_atom; } bool isValid() { getReply(); return m_atom != XCB_ATOM_NONE; } bool isValid() const { (const_cast(this))->getReply(); return m_atom != XCB_ATOM_NONE; } inline const QByteArray &name() const { return m_name; } private: void getReply() { if (m_retrieved || !m_cookie.sequence) { return; } ScopedCPointer reply(xcb_intern_atom_reply(m_connection, m_cookie, nullptr)); if (!reply.isNull()) { m_atom = reply->atom; } m_retrieved = true; } xcb_connection_t *m_connection; bool m_retrieved; xcb_intern_atom_cookie_t m_cookie; xcb_atom_t m_atom; QByteArray m_name; }; /** * @brief Macro to create the WrapperData subclass. * * Creates a struct with name @p __NAME__ for the xcb request identified by @p __REQUEST__. * The variadic arguments are used to pass as template arguments to the WrapperData. * * The @p __REQUEST__ is the common prefix of the cookie type, reply type, request function and * reply function. E.g. "xcb_get_geometry" is used to create: * @li cookie type xcb_get_geometry_cookie_t * @li reply type xcb_get_geometry_reply_t * @li request function pointer xcb_get_geometry_unchecked * @li reply function pointer xcb_get_geometry_reply * * @param __NAME__ The name of the WrapperData subclass * @param __REQUEST__ The name of the xcb request, e.g. xcb_get_geometry * @param __VA_ARGS__ The variadic template arguments, e.g. xcb_drawable_t * @see XCB_WRAPPER */ #define XCB_WRAPPER_DATA( __NAME__, __REQUEST__, ... ) \ struct __NAME__ : public WrapperData< __REQUEST__##_reply_t, __REQUEST__##_cookie_t, __VA_ARGS__ > \ { \ static constexpr request_func requestFunc = &__REQUEST__##_unchecked; \ static constexpr reply_func replyFunc = &__REQUEST__##_reply; \ }; /** * @brief Macro to create Wrapper typedef and WrapperData. * * This macro expands the XCB_WRAPPER_DATA macro and creates an additional * typedef for Wrapper with name @p __NAME__. The created WrapperData is also derived * from @p __NAME__ with "Data" as suffix. * * @param __NAME__ The name for the Wrapper typedef * @param __REQUEST__ The name of the xcb request, passed to XCB_WRAPPER_DATA * @param __VA_ARGS__ The variadic template arguments for Wrapper and WrapperData * @see XCB_WRAPPER_DATA */ #define XCB_WRAPPER( __NAME__, __REQUEST__, ... ) \ XCB_WRAPPER_DATA( __NAME__##Data, __REQUEST__, __VA_ARGS__ ) \ typedef Wrapper< __NAME__##Data, __VA_ARGS__ > __NAME__; XCB_WRAPPER(WindowAttributes, xcb_get_window_attributes, xcb_window_t) XCB_WRAPPER(OverlayWindow, xcb_composite_get_overlay_window, xcb_window_t) XCB_WRAPPER_DATA(GeometryData, xcb_get_geometry, xcb_drawable_t) class WindowGeometry : public Wrapper { public: WindowGeometry() : Wrapper() {} explicit WindowGeometry(xcb_window_t window) : Wrapper(window) {} inline QRect rect() { const xcb_get_geometry_reply_t *geometry = data(); if (!geometry) { return QRect(); } return QRect(geometry->x, geometry->y, geometry->width, geometry->height); } }; XCB_WRAPPER_DATA(TreeData, xcb_query_tree, xcb_window_t) class Tree : public Wrapper { public: explicit Tree(WindowId window) : Wrapper(window) {} inline WindowId *children() { if (isNull() || data()->children_len == 0) { return nullptr; } return xcb_query_tree_children(data()); } inline xcb_window_t parent() { if (isNull()) return XCB_WINDOW_NONE; return (*this)->parent; } }; XCB_WRAPPER(Pointer, xcb_query_pointer, xcb_window_t) struct CurrentInputData : public WrapperData< xcb_get_input_focus_reply_t, xcb_get_input_focus_cookie_t > { static constexpr request_func requestFunc = &xcb_get_input_focus_unchecked; static constexpr reply_func replyFunc = &xcb_get_input_focus_reply; }; class CurrentInput : public Wrapper { public: CurrentInput() : Wrapper() {} inline xcb_window_t window() { if (isNull()) return XCB_WINDOW_NONE; return (*this)->focus; } }; struct QueryKeymapData : public WrapperData< xcb_query_keymap_reply_t, xcb_query_keymap_cookie_t > { static constexpr request_func requestFunc = &xcb_query_keymap_unchecked; static constexpr reply_func replyFunc = &xcb_query_keymap_reply; }; class QueryKeymap : public Wrapper { public: QueryKeymap() : Wrapper() {} }; struct ModifierMappingData : public WrapperData< xcb_get_modifier_mapping_reply_t, xcb_get_modifier_mapping_cookie_t > { static constexpr request_func requestFunc = &xcb_get_modifier_mapping_unchecked; static constexpr reply_func replyFunc = &xcb_get_modifier_mapping_reply; }; class ModifierMapping : public Wrapper { public: ModifierMapping() : Wrapper() {} inline xcb_keycode_t *keycodes() { if (isNull()) { return nullptr; } return xcb_get_modifier_mapping_keycodes(data()); } inline int size() { if (isNull()) { return 0; } return xcb_get_modifier_mapping_keycodes_length(data()); } }; XCB_WRAPPER_DATA(PropertyData, xcb_get_property, uint8_t, xcb_window_t, xcb_atom_t, xcb_atom_t, uint32_t, uint32_t) class Property : public Wrapper { public: Property() : Wrapper() , m_type(XCB_ATOM_NONE) { } Property(const Property &other) : Wrapper(other) , m_type(other.m_type) { } explicit Property(uint8_t _delete, xcb_window_t window, xcb_atom_t property, xcb_atom_t type, uint32_t long_offset, uint32_t long_length) : Wrapper(window, _delete, window, property, type, long_offset, long_length) , m_type(type) { } Property &operator=(const Property &other) { Wrapper::operator=(other); m_type = other.m_type; return *this; } /** * @brief Overloaded method for convenience. * * Uses the type which got passed into the ctor and derives the format from the sizeof(T). * Note: for the automatic format detection the size of the type T may not vary between * architectures. Thus one needs to use e.g. uint32_t instead of long. In general all xcb * data types can be used, all Xlib data types can not be used. * * @param defaultValue The default value to return in case of error * @param ok Set to @c false in case of error, @c true in case of success * @return The read value or @p defaultValue in error case */ template inline typename std::enable_if::value, T>::type value(T defaultValue = T(), bool *ok = nullptr) { return value(sizeof(T) * 8, m_type, defaultValue, ok); } /** * @brief Reads the property as a POD type. * * Returns the first value of the property data. In case of @p format or @p type mismatch * the @p defaultValue is returned. The optional argument @p ok is set * to @c false in case of error and to @c true in case of successful reading of * the property. * * @param format The expected format of the property value, e.g. 32 for XCB_ATOM_CARDINAL * @param type The expected type of the property value, e.g. XCB_ATOM_CARDINAL * @param defaultValue The default value to return in case of error * @param ok Set to @c false in case of error, @c true in case of success * @return The read value or @p defaultValue in error case */ template inline typename std::enable_if::value, T>::type value(uint8_t format, xcb_atom_t type, T defaultValue = T(), bool *ok = nullptr) { T *reply = value(format, type, nullptr, ok); if (!reply) { return defaultValue; } return reply[0]; } /** * @brief Overloaded method for convenience. * * Uses the type which got passed into the ctor and derives the format from the sizeof(T). * Note: for the automatic format detection the size of the type T may not vary between * architectures. Thus one needs to use e.g. uint32_t instead of long. In general all xcb * data types can be used, all Xlib data types can not be used. * * @param defaultValue The default value to return in case of error * @param ok Set to @c false in case of error, @c true in case of success * @return The read value or @p defaultValue in error case */ template inline typename std::enable_if::value, T>::type value(T defaultValue = nullptr, bool *ok = nullptr) { return value(sizeof(typename std::remove_pointer::type) * 8, m_type, defaultValue, ok); } /** * @brief Reads the property as an array of T. * * This method is an overload for the case that T is a pointer type. * * Return the property value casted to the pointer type T. In case of @p format * or @p type mismatch the @p defaultValue is returned. Also if the value length * is @c 0 the @p defaultValue is returned. The optional argument @p ok is set * to @c false in case of error and to @c true in case of successful reading of * the property. Ok will always be true if the property exists and has been * successfully read, even in the case the property is empty and its length is 0 * * @param format The expected format of the property value, e.g. 32 for XCB_ATOM_CARDINAL * @param type The expected type of the property value, e.g. XCB_ATOM_CARDINAL * @param defaultValue The default value to return in case of error * @param ok Set to @c false in case of error, @c true in case of success * @return The read value or @p defaultValue in error case */ template inline typename std::enable_if::value, T>::type value(uint8_t format, xcb_atom_t type, T defaultValue = nullptr, bool *ok = nullptr) { if (ok) { *ok = false; } const PropertyData::reply_type *reply = data(); if (!reply) { return defaultValue; } if (reply->type != type) { return defaultValue; } if (reply->format != format) { return defaultValue; } if (ok) { *ok = true; } if (xcb_get_property_value_length(reply) == 0) { return defaultValue; } return reinterpret_cast(xcb_get_property_value(reply)); } /** * @brief Reads the property as string and returns a QByteArray. * * In case of error this method returns a null QByteArray. */ inline QByteArray toByteArray(uint8_t format = 8, xcb_atom_t type = XCB_ATOM_STRING, bool *ok = nullptr) { bool valueOk = false; const char *reply = value(format, type, nullptr, &valueOk); if (ok) { *ok = valueOk; } if (valueOk && !reply) { return QByteArray("", 0); // valid, not null, but empty data } else if (!valueOk) { return QByteArray(); // Property not found, data empty and null } return QByteArray(reply, xcb_get_property_value_length(data())); } /** * @brief Overloaded method for convenience. */ inline QByteArray toByteArray(bool *ok) { return toByteArray(8, m_type, ok); } /** * @brief Reads the property as a boolean value. * * If the property reply length is @c 1 the first element is interpreted as a boolean * value returning @c true for any value unequal to @c 0 and @c false otherwise. * * In case of error this method returns @c false. Thus it is not possible to distinguish * between error case and a read @c false value. Use the optional argument @p ok to * distinguish the error case. * * @param format Expected format. Defaults to 32. * @param type Expected type Defaults to XCB_ATOM_CARDINAL. * @param ok Set to @c false in case of error, @c true in case of success * @return bool The first element interpreted as a boolean value or @c false in error case * @see value */ inline bool toBool(uint8_t format = 32, xcb_atom_t type = XCB_ATOM_CARDINAL, bool *ok = nullptr) { bool *reply = value(format, type, nullptr, ok); if (!reply) { return false; } if (data()->value_len != 1) { if (ok) { *ok = false; } return false; } return reply[0] != 0; } /** * @brief Overloaded method for convenience. */ inline bool toBool(bool *ok) { return toBool(32, m_type, ok); } private: xcb_atom_t m_type; }; class StringProperty : public Property { public: StringProperty() = default; explicit StringProperty(xcb_window_t w, xcb_atom_t p) : Property(false, w, p, XCB_ATOM_STRING, 0, 10000) { } operator QByteArray() { return toByteArray(); } }; class TransientFor : public Property { public: explicit TransientFor(WindowId window) : Property(0, window, XCB_ATOM_WM_TRANSIENT_FOR, XCB_ATOM_WINDOW, 0, 1) { } /** * @brief Fill given window pointer with the WM_TRANSIENT_FOR property of a window. * @param prop WM_TRANSIENT_FOR property value. * @returns @c true on success, @c false otherwise */ inline bool getTransientFor(WindowId *prop) { WindowId *windows = value(); if (!windows) { return false; } *prop = *windows; return true; } }; class GeometryHints { public: GeometryHints() = default; void init(xcb_window_t window) { Q_ASSERT(window); if (m_window) { // already initialized return; } m_window = window; fetch(); } void fetch() { if (!m_window) { return; } m_sizeHints = nullptr; m_hints = NormalHints(m_window); } void read() { m_sizeHints = m_hints.sizeHints(); } bool hasPosition() const { return testFlag(NormalHints::SizeHints::UserPosition) || testFlag(NormalHints::SizeHints::ProgramPosition); } bool hasSize() const { return testFlag(NormalHints::SizeHints::UserSize) || testFlag(NormalHints::SizeHints::ProgramSize); } bool hasMinSize() const { return testFlag(NormalHints::SizeHints::MinSize); } bool hasMaxSize() const { return testFlag(NormalHints::SizeHints::MaxSize); } bool hasResizeIncrements() const { return testFlag(NormalHints::SizeHints::ResizeIncrements); } bool hasAspect() const { return testFlag(NormalHints::SizeHints::Aspect); } bool hasBaseSize() const { return testFlag(NormalHints::SizeHints::BaseSize); } bool hasWindowGravity() const { return testFlag(NormalHints::SizeHints::WindowGravity); } QSize maxSize() const { if (!hasMaxSize()) { return QSize(INT_MAX, INT_MAX); } return QSize(qMax(m_sizeHints->maxWidth, 1), qMax(m_sizeHints->maxHeight, 1)); } QSize minSize() const { if (!hasMinSize()) { // according to ICCCM 4.1.23 base size should be used as a fallback return baseSize(); } return QSize(m_sizeHints->minWidth, m_sizeHints->minHeight); } QSize baseSize() const { // Note: not using minSize as fallback if (!hasBaseSize()) { return QSize(0, 0); } return QSize(m_sizeHints->baseWidth, m_sizeHints->baseHeight); } QSize resizeIncrements() const { if (!hasResizeIncrements()) { return QSize(1, 1); } return QSize(qMax(m_sizeHints->widthInc, 1), qMax(m_sizeHints->heightInc, 1)); } xcb_gravity_t windowGravity() const { if (!hasWindowGravity()) { return XCB_GRAVITY_NORTH_WEST; } return xcb_gravity_t(m_sizeHints->winGravity); } QSize minAspect() const { if (!hasAspect()) { return QSize(1, INT_MAX); } // prevent devision by zero return QSize(m_sizeHints->minAspect[0], qMax(m_sizeHints->minAspect[1], 1)); } QSize maxAspect() const { if (!hasAspect()) { return QSize(INT_MAX, 1); } // prevent devision by zero return QSize(m_sizeHints->maxAspect[0], qMax(m_sizeHints->maxAspect[1], 1)); } private: /** * NormalHints as specified in ICCCM 4.1.2.3. */ class NormalHints : public Property { public: struct SizeHints { enum Flags { UserPosition = 1, UserSize = 2, ProgramPosition = 4, ProgramSize = 8, MinSize = 16, MaxSize = 32, ResizeIncrements = 64, Aspect = 128, BaseSize = 256, WindowGravity = 512 }; qint32 flags = 0; qint32 pad[4] = {0, 0, 0, 0}; qint32 minWidth = 0; qint32 minHeight = 0; qint32 maxWidth = 0; qint32 maxHeight = 0; qint32 widthInc = 0; qint32 heightInc = 0; qint32 minAspect[2] = {0, 0}; qint32 maxAspect[2] = {0, 0}; qint32 baseWidth = 0; qint32 baseHeight = 0; qint32 winGravity = 0; }; explicit NormalHints() : Property() {}; explicit NormalHints(WindowId window) : Property(0, window, XCB_ATOM_WM_NORMAL_HINTS, XCB_ATOM_WM_SIZE_HINTS, 0, 18) { } inline SizeHints *sizeHints() { return value(32, XCB_ATOM_WM_SIZE_HINTS, nullptr); } }; friend TestXcbSizeHints; bool testFlag(NormalHints::SizeHints::Flags flag) const { if (!m_window || !m_sizeHints) { return false; } return m_sizeHints->flags & flag; } xcb_window_t m_window = XCB_WINDOW_NONE; NormalHints m_hints; NormalHints::SizeHints *m_sizeHints = nullptr; }; class MotifHints { public: MotifHints(xcb_atom_t atom) : m_atom(atom) {} void init(xcb_window_t window) { Q_ASSERT(window); if (m_window) { // already initialized return; } m_window = window; fetch(); } void fetch() { if (!m_window) { return; } m_hints = nullptr; m_prop = Property(0, m_window, m_atom, m_atom, 0, 5); } void read() { m_hints = m_prop.value(32, m_atom, nullptr); } bool hasDecoration() const { if (!m_window || !m_hints) { return false; } return m_hints->flags & uint32_t(Hints::Decorations); } bool noBorder() const { if (!hasDecoration()) { return false; } return !m_hints->decorations; } bool resize() const { return testFunction(Functions::Resize); } bool move() const { return testFunction(Functions::Move); } bool minimize() const { return testFunction(Functions::Minimize); } bool maximize() const { return testFunction(Functions::Maximize); } bool close() const { return testFunction(Functions::Close); } private: struct MwmHints { uint32_t flags; uint32_t functions; uint32_t decorations; int32_t input_mode; uint32_t status; }; enum class Hints { Functions = (1L << 0), Decorations = (1L << 1) }; enum class Functions { All = (1L << 0), Resize = (1L << 1), Move = (1L << 2), Minimize = (1L << 3), Maximize = (1L << 4), Close = (1L << 5) }; bool testFunction(Functions flag) const { if (!m_window || !m_hints) { return true; } if (!(m_hints->flags & uint32_t(Hints::Functions))) { return true; } // if MWM_FUNC_ALL is set, other flags say what to turn _off_ const bool set_value = ((m_hints->functions & uint32_t(Functions::All)) == 0); if (m_hints->functions & uint32_t(flag)) { return set_value; } return !set_value; } xcb_window_t m_window = XCB_WINDOW_NONE; Property m_prop; xcb_atom_t m_atom; MwmHints *m_hints = nullptr; }; namespace RandR { XCB_WRAPPER(ScreenInfo, xcb_randr_get_screen_info, xcb_window_t) XCB_WRAPPER_DATA(ScreenResourcesData, xcb_randr_get_screen_resources, xcb_window_t) class ScreenResources : public Wrapper { public: explicit ScreenResources(WindowId window) : Wrapper(window) {} inline xcb_randr_crtc_t *crtcs() { if (isNull()) { return nullptr; } return xcb_randr_get_screen_resources_crtcs(data()); } inline xcb_randr_mode_info_t *modes() { if (isNull()) { return nullptr; } return xcb_randr_get_screen_resources_modes(data()); } inline uint8_t *names() { if (isNull()) { return nullptr; } return xcb_randr_get_screen_resources_names(data()); } }; XCB_WRAPPER_DATA(CrtcGammaData, xcb_randr_get_crtc_gamma, xcb_randr_crtc_t) class CrtcGamma : public Wrapper { public: explicit CrtcGamma(xcb_randr_crtc_t c) : Wrapper(c) {} inline uint16_t *red() { return xcb_randr_get_crtc_gamma_red(data()); } inline uint16_t *green() { return xcb_randr_get_crtc_gamma_green(data()); } inline uint16_t *blue() { return xcb_randr_get_crtc_gamma_blue(data()); } }; XCB_WRAPPER_DATA(CrtcInfoData, xcb_randr_get_crtc_info, xcb_randr_crtc_t, xcb_timestamp_t) class CrtcInfo : public Wrapper { public: CrtcInfo() = default; CrtcInfo(const CrtcInfo&) = default; explicit CrtcInfo(xcb_randr_crtc_t c, xcb_timestamp_t t) : Wrapper(c, t) {} inline QRect rect() { const CrtcInfoData::reply_type *info = data(); if (!info || info->num_outputs == 0 || info->mode == XCB_NONE || info->status != XCB_RANDR_SET_CONFIG_SUCCESS) { return QRect(); } return QRect(info->x, info->y, info->width, info->height); } inline xcb_randr_output_t *outputs() { const CrtcInfoData::reply_type *info = data(); if (!info || info->num_outputs == 0 || info->mode == XCB_NONE || info->status != XCB_RANDR_SET_CONFIG_SUCCESS) { return nullptr; } return xcb_randr_get_crtc_info_outputs(info); } }; XCB_WRAPPER_DATA(OutputInfoData, xcb_randr_get_output_info, xcb_randr_output_t, xcb_timestamp_t) class OutputInfo : public Wrapper { public: OutputInfo() = default; OutputInfo(const OutputInfo&) = default; explicit OutputInfo(xcb_randr_output_t c, xcb_timestamp_t t) : Wrapper(c, t) {} inline QString name() { const OutputInfoData::reply_type *info = data(); if (!info || info->num_crtcs == 0 || info->num_modes == 0 || info->status != XCB_RANDR_SET_CONFIG_SUCCESS) { return QString(); } return QString::fromUtf8(reinterpret_cast(xcb_randr_get_output_info_name(info)), info->name_len); } }; XCB_WRAPPER_DATA(CurrentResourcesData, xcb_randr_get_screen_resources_current, xcb_window_t) class CurrentResources : public Wrapper { public: explicit CurrentResources(WindowId window) : Wrapper(window) {} inline xcb_randr_crtc_t *crtcs() { if (isNull()) { return nullptr; } return xcb_randr_get_screen_resources_current_crtcs(data()); } inline xcb_randr_mode_info_t *modes() { if (isNull()) { return nullptr; } return xcb_randr_get_screen_resources_current_modes(data()); } }; XCB_WRAPPER(SetCrtcConfig, xcb_randr_set_crtc_config, xcb_randr_crtc_t, xcb_timestamp_t, xcb_timestamp_t, int16_t, int16_t, xcb_randr_mode_t, uint16_t, uint32_t, const xcb_randr_output_t*) } class ExtensionData { public: ExtensionData(); int version; int eventBase; int errorBase; int majorOpcode; bool present; QByteArray name; QVector opCodes; QVector errorCodes; }; class KWIN_EXPORT Extensions { public: bool isShapeAvailable() const { return m_shape.version > 0; } bool isShapeInputAvailable() const; int shapeNotifyEvent() const; bool hasShape(xcb_window_t w) const; bool isRandrAvailable() const { return m_randr.present; } int randrNotifyEvent() const; bool isDamageAvailable() const { return m_damage.present; } int damageNotifyEvent() const; bool isCompositeAvailable() const { return m_composite.version > 0; } bool isCompositeOverlayAvailable() const; bool isRenderAvailable() const { return m_render.version > 0; } bool isFixesAvailable() const { return m_fixes.version > 0; } int fixesCursorNotifyEvent() const; bool isFixesRegionAvailable() const; bool isSyncAvailable() const { return m_sync.present; } int syncAlarmNotifyEvent() const; QVector extensions() const; bool hasGlx() const { return m_glx.present; } int glxEventBase() const { return m_glx.eventBase; } int glxMajorOpcode() const { return m_glx.majorOpcode; } static Extensions *self(); static void destroy(); private: Extensions(); ~Extensions(); void init(); template void initVersion(T cookie, F f, ExtensionData *dataToFill); void extensionQueryReply(const xcb_query_extension_reply_t *extension, ExtensionData *dataToFill); ExtensionData m_shape; ExtensionData m_randr; ExtensionData m_damage; ExtensionData m_composite; ExtensionData m_render; ExtensionData m_fixes; ExtensionData m_sync; ExtensionData m_glx; static Extensions *s_self; }; /** * This class is an RAII wrapper for an xcb_window_t. An xcb_window_t hold by an instance of this class * will be freed when the instance gets destroyed. * * Furthermore the class provides wrappers around some xcb methods operating on an xcb_window_t. * * For the cases that one is more interested in wrapping the xcb methods the constructor which takes * an existing window and the @ref reset method allow to disable the RAII functionality. */ class Window { public: /** * Takes over responsibility of @p window. If @p window is not provided an invalid Window is * created. Use @ref create to set an xcb_window_t later on. * * If @p destroy is @c true the window will be destroyed together with this object, if @c false * the window will be kept around. This is useful if you are not interested in the RAII capabilities * but still want to use a window like an object. * * @param window The window to manage. * @param destroy Whether the window should be destroyed together with the object. * @see reset */ Window(xcb_window_t window = XCB_WINDOW_NONE, bool destroy = true); /** * Creates an xcb_window_t and manages it. It's a convenient method to create a window with * depth, class and visual being copied from parent and border being @c 0. * @param geometry The geometry for the window to be created * @param mask The mask for the values * @param values The values to be passed to xcb_create_window * @param parent The parent window */ - Window(const QRect &geometry, uint32_t mask = 0, const uint32_t *values = NULL, xcb_window_t parent = rootWindow()); + Window(const QRect &geometry, uint32_t mask = 0, const uint32_t *values = nullptr, xcb_window_t parent = rootWindow()); /** * Creates an xcb_window_t and manages it. It's a convenient method to create a window with * depth and visual being copied from parent and border being @c 0. * @param geometry The geometry for the window to be created * @param windowClass The window class * @param mask The mask for the values * @param values The values to be passed to xcb_create_window * @param parent The parent window */ - Window(const QRect &geometry, uint16_t windowClass, uint32_t mask = 0, const uint32_t *values = NULL, xcb_window_t parent = rootWindow()); + Window(const QRect &geometry, uint16_t windowClass, uint32_t mask = 0, const uint32_t *values = nullptr, xcb_window_t parent = rootWindow()); Window(const Window &other) = delete; ~Window(); /** * Creates a new window for which the responsibility is taken over. If a window had been managed * before it is freed. * * Depth, class and visual are being copied from parent and border is @c 0. * @param geometry The geometry for the window to be created * @param mask The mask for the values * @param values The values to be passed to xcb_create_window * @param parent The parent window */ - void create(const QRect &geometry, uint32_t mask = 0, const uint32_t *values = NULL, xcb_window_t parent = rootWindow()); + void create(const QRect &geometry, uint32_t mask = 0, const uint32_t *values = nullptr, xcb_window_t parent = rootWindow()); /** * Creates a new window for which the responsibility is taken over. If a window had been managed * before it is freed. * * Depth and visual are being copied from parent and border is @c 0. * @param geometry The geometry for the window to be created * @param windowClass The window class * @param mask The mask for the values * @param values The values to be passed to xcb_create_window * @param parent The parent window */ - void create(const QRect &geometry, uint16_t windowClass, uint32_t mask = 0, const uint32_t *values = NULL, xcb_window_t parent = rootWindow()); + void create(const QRect &geometry, uint16_t windowClass, uint32_t mask = 0, const uint32_t *values = nullptr, xcb_window_t parent = rootWindow()); /** * Frees the existing window and starts to manage the new @p window. * If @p destroy is @c true the new managed window will be destroyed together with this * object or when reset is called again. If @p destroy is @c false the window will not * be destroyed. It is then the responsibility of the caller to destroy the window. */ void reset(xcb_window_t window = XCB_WINDOW_NONE, bool destroy = true); /** * @returns @c true if a window is managed, @c false otherwise. */ bool isValid() const; inline const QRect &geometry() const { return m_logicGeometry; } /** * Configures the window with a new geometry. * @param geometry The new window geometry to be used */ void setGeometry(const QRect &geometry); void setGeometry(uint32_t x, uint32_t y, uint32_t width, uint32_t height); void move(const QPoint &pos); void move(uint32_t x, uint32_t y); void resize(const QSize &size); void resize(uint32_t width, uint32_t height); void raise(); void lower(); void map(); void unmap(); void reparent(xcb_window_t parent, int x = 0, int y = 0); void changeProperty(xcb_atom_t property, xcb_atom_t type, uint8_t format, uint32_t length, const void *data, uint8_t mode = XCB_PROP_MODE_REPLACE); void deleteProperty(xcb_atom_t property); void setBorderWidth(uint32_t width); void grabButton(uint8_t pointerMode, uint8_t keyboardmode, uint16_t modifiers = XCB_MOD_MASK_ANY, uint8_t button = XCB_BUTTON_INDEX_ANY, uint16_t eventMask = XCB_EVENT_MASK_BUTTON_PRESS, xcb_window_t confineTo = XCB_WINDOW_NONE, xcb_cursor_t cursor = XCB_CURSOR_NONE, bool ownerEvents = false); void ungrabButton(uint16_t modifiers = XCB_MOD_MASK_ANY, uint8_t button = XCB_BUTTON_INDEX_ANY); /** * Clears the window area. Same as xcb_clear_area with x, y, width, height being @c 0. */ void clear(); void setBackgroundPixmap(xcb_pixmap_t pixmap); void defineCursor(xcb_cursor_t cursor); void focus(uint8_t revertTo = XCB_INPUT_FOCUS_POINTER_ROOT, xcb_timestamp_t time = XCB_TIME_CURRENT_TIME); void selectInput(uint32_t events); void kill(); operator xcb_window_t() const; private: - xcb_window_t doCreate(const QRect &geometry, uint16_t windowClass, uint32_t mask = 0, const uint32_t *values = NULL, xcb_window_t parent = rootWindow()); + xcb_window_t doCreate(const QRect &geometry, uint16_t windowClass, uint32_t mask = 0, const uint32_t *values = nullptr, xcb_window_t parent = rootWindow()); void destroy(); xcb_window_t m_window; bool m_destroy; QRect m_logicGeometry; }; inline Window::Window(xcb_window_t window, bool destroy) : m_window(window) , m_destroy(destroy) { } inline Window::Window(const QRect &geometry, uint32_t mask, const uint32_t *values, xcb_window_t parent) : m_window(doCreate(geometry, XCB_COPY_FROM_PARENT, mask, values, parent)) , m_destroy(true) { } inline Window::Window(const QRect &geometry, uint16_t windowClass, uint32_t mask, const uint32_t *values, xcb_window_t parent) : m_window(doCreate(geometry, windowClass, mask, values, parent)) , m_destroy(true) { } inline Window::~Window() { destroy(); } inline void Window::destroy() { if (!isValid() || !m_destroy) { return; } xcb_destroy_window(connection(), m_window); m_window = XCB_WINDOW_NONE; } inline bool Window::isValid() const { return m_window != XCB_WINDOW_NONE; } inline Window::operator xcb_window_t() const { return m_window; } inline void Window::create(const QRect &geometry, uint16_t windowClass, uint32_t mask, const uint32_t *values, xcb_window_t parent) { destroy(); m_window = doCreate(geometry, windowClass, mask, values, parent); } inline void Window::create(const QRect &geometry, uint32_t mask, const uint32_t *values, xcb_window_t parent) { create(geometry, XCB_COPY_FROM_PARENT, mask, values, parent); } inline xcb_window_t Window::doCreate(const QRect &geometry, uint16_t windowClass, uint32_t mask, const uint32_t *values, xcb_window_t parent) { m_logicGeometry = geometry; xcb_window_t w = xcb_generate_id(connection()); xcb_create_window(connection(), XCB_COPY_FROM_PARENT, w, parent, geometry.x(), geometry.y(), geometry.width(), geometry.height(), 0, windowClass, XCB_COPY_FROM_PARENT, mask, values); return w; } inline void Window::reset(xcb_window_t window, bool shouldDestroy) { destroy(); m_window = window; m_destroy = shouldDestroy; } inline void Window::setGeometry(const QRect &geometry) { setGeometry(geometry.x(), geometry.y(), geometry.width(), geometry.height()); } inline void Window::setGeometry(uint32_t x, uint32_t y, uint32_t width, uint32_t height) { m_logicGeometry.setRect(x, y, width, height); if (!isValid()) { return; } const uint16_t mask = XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y | XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT; const uint32_t values[] = { x, y, width, height }; xcb_configure_window(connection(), m_window, mask, values); } inline void Window::move(const QPoint &pos) { move(pos.x(), pos.y()); } inline void Window::move(uint32_t x, uint32_t y) { m_logicGeometry.moveTo(x, y); if (!isValid()) { return; } moveWindow(m_window, x, y); } inline void Window::resize(const QSize &size) { resize(size.width(), size.height()); } inline void Window::resize(uint32_t width, uint32_t height) { m_logicGeometry.setSize(QSize(width, height)); if (!isValid()) { return; } const uint16_t mask = XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT; const uint32_t values[] = { width, height }; xcb_configure_window(connection(), m_window, mask, values); } inline void Window::raise() { const uint32_t values[] = { XCB_STACK_MODE_ABOVE }; xcb_configure_window(connection(), m_window, XCB_CONFIG_WINDOW_STACK_MODE, values); } inline void Window::lower() { lowerWindow(m_window); } inline void Window::map() { if (!isValid()) { return; } xcb_map_window(connection(), m_window); } inline void Window::unmap() { if (!isValid()) { return; } xcb_unmap_window(connection(), m_window); } inline void Window::reparent(xcb_window_t parent, int x, int y) { if (!isValid()) { return; } xcb_reparent_window(connection(), m_window, parent, x, y); } inline void Window::changeProperty(xcb_atom_t property, xcb_atom_t type, uint8_t format, uint32_t length, const void *data, uint8_t mode) { if (!isValid()) { return; } xcb_change_property(connection(), mode, m_window, property, type, format, length, data); } inline void Window::deleteProperty(xcb_atom_t property) { if (!isValid()) { return; } xcb_delete_property(connection(), m_window, property); } inline void Window::setBorderWidth(uint32_t width) { if (!isValid()) { return; } xcb_configure_window(connection(), m_window, XCB_CONFIG_WINDOW_BORDER_WIDTH, &width); } inline void Window::grabButton(uint8_t pointerMode, uint8_t keyboardmode, uint16_t modifiers, uint8_t button, uint16_t eventMask, xcb_window_t confineTo, xcb_cursor_t cursor, bool ownerEvents) { if (!isValid()) { return; } xcb_grab_button(connection(), ownerEvents, m_window, eventMask, pointerMode, keyboardmode, confineTo, cursor, button, modifiers); } inline void Window::ungrabButton(uint16_t modifiers, uint8_t button) { if (!isValid()) { return; } xcb_ungrab_button(connection(), button, m_window, modifiers); } inline void Window::clear() { if (!isValid()) { return; } xcb_clear_area(connection(), false, m_window, 0, 0, 0, 0); } inline void Window::setBackgroundPixmap(xcb_pixmap_t pixmap) { if (!isValid()) { return; } const uint32_t values[] = {pixmap}; xcb_change_window_attributes(connection(), m_window, XCB_CW_BACK_PIXMAP, values); } inline void Window::defineCursor(xcb_cursor_t cursor) { Xcb::defineCursor(m_window, cursor); } inline void Window::focus(uint8_t revertTo, xcb_timestamp_t time) { setInputFocus(m_window, revertTo, time); } inline void Window::selectInput(uint32_t events) { Xcb::selectInput(m_window, events); } inline void Window::kill() { xcb_kill_client(connection(), m_window); } // helper functions static inline void moveResizeWindow(WindowId window, const QRect &geometry) { const uint16_t mask = XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y | XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT; const uint32_t values[] = { static_cast(geometry.x()), static_cast(geometry.y()), static_cast(geometry.width()), static_cast(geometry.height()) }; xcb_configure_window(connection(), window, mask, values); } static inline void moveWindow(xcb_window_t window, const QPoint& pos) { moveWindow(window, pos.x(), pos.y()); } static inline void moveWindow(xcb_window_t window, uint32_t x, uint32_t y) { const uint16_t mask = XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y; const uint32_t values[] = { x, y }; xcb_configure_window(connection(), window, mask, values); } static inline void lowerWindow(xcb_window_t window) { const uint32_t values[] = { XCB_STACK_MODE_BELOW }; xcb_configure_window(connection(), window, XCB_CONFIG_WINDOW_STACK_MODE, values); } static inline WindowId createInputWindow(const QRect &geometry, uint32_t mask, const uint32_t *values) { WindowId window = xcb_generate_id(connection()); xcb_create_window(connection(), 0, window, rootWindow(), geometry.x(), geometry.y(), geometry.width(), geometry.height(), 0, XCB_WINDOW_CLASS_INPUT_ONLY, XCB_COPY_FROM_PARENT, mask, values); return window; } static inline void restackWindows(const QVector &windows) { if (windows.count() < 2) { // only one window, nothing to do return; } for (int i=1; i &windows) { if (windows.isEmpty()) { return; } const uint32_t values[] = { XCB_STACK_MODE_ABOVE }; xcb_configure_window(connection(), windows.first(), XCB_CONFIG_WINDOW_STACK_MODE, values); restackWindows(windows); } static inline int defaultDepth() { static int depth = 0; if (depth != 0) { return depth; } int screen = Application::x11ScreenNumber(); for (xcb_screen_iterator_t it = xcb_setup_roots_iterator(xcb_get_setup(connection())); it.rem; --screen, xcb_screen_next(&it)) { if (screen == 0) { depth = it.data->root_depth; break; } } return depth; } static inline xcb_rectangle_t fromQt(const QRect &rect) { xcb_rectangle_t rectangle; rectangle.x = rect.x(); rectangle.y = rect.y(); rectangle.width = rect.width(); rectangle.height = rect.height(); return rectangle; } static inline QVector regionToRects(const QRegion ®ion) { QVector rects; rects.reserve(region.rectCount()); for (const QRect &rect : region) { rects.append(Xcb::fromQt(rect)); } return rects; } static inline void defineCursor(xcb_window_t window, xcb_cursor_t cursor) { xcb_change_window_attributes(connection(), window, XCB_CW_CURSOR, &cursor); } static inline void setInputFocus(xcb_window_t window, uint8_t revertTo, xcb_timestamp_t time) { xcb_set_input_focus(connection(), revertTo, window, time); } static inline void setTransientFor(xcb_window_t window, xcb_window_t transient_for_window) { xcb_change_property(connection(), XCB_PROP_MODE_REPLACE, window, XCB_ATOM_WM_TRANSIENT_FOR, XCB_ATOM_WINDOW, 32, 1, &transient_for_window); } static inline void sync() { auto *c = connection(); const auto cookie = xcb_get_input_focus(c); xcb_generic_error_t *error = nullptr; ScopedCPointer sync(xcb_get_input_focus_reply(c, cookie, &error)); if (error) { free(error); } } void selectInput(xcb_window_t window, uint32_t events) { xcb_change_window_attributes(connection(), window, XCB_CW_EVENT_MASK, &events); } /** * @brief Small helper class to encapsulate SHM related functionality. */ class Shm { public: Shm(); ~Shm(); int shmId() const; void *buffer() const; xcb_shm_seg_t segment() const; bool isValid() const; uint8_t pixmapFormat() const; private: bool init(); int m_shmId; void *m_buffer; xcb_shm_seg_t m_segment; bool m_valid; uint8_t m_pixmapFormat; }; inline void *Shm::buffer() const { return m_buffer; } inline bool Shm::isValid() const { return m_valid; } inline xcb_shm_seg_t Shm::segment() const { return m_segment; } inline int Shm::shmId() const { return m_shmId; } inline uint8_t Shm::pixmapFormat() const { return m_pixmapFormat; } } // namespace X11 } // namespace KWin #endif // KWIN_X11_UTILS_H diff --git a/xkb.cpp b/xkb.cpp index d334b1ebb..4b99630f8 100644 --- a/xkb.cpp +++ b/xkb.cpp @@ -1,581 +1,581 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2013, 2016, 2017 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 "xkb.h" #include "xkb_qt_mapping.h" #include "utils.h" // frameworks #include // KWayland #include // Qt #include #include // xkbcommon #include #include #include // system #include #include #include Q_LOGGING_CATEGORY(KWIN_XKB, "kwin_xkbcommon", QtCriticalMsg) namespace KWin { static void xkbLogHandler(xkb_context *context, xkb_log_level priority, const char *format, va_list args) { Q_UNUSED(context) char buf[1024]; if (std::vsnprintf(buf, 1023, format, args) <= 0) { return; } switch (priority) { case XKB_LOG_LEVEL_DEBUG: qCDebug(KWIN_XKB) << "XKB:" << buf; break; case XKB_LOG_LEVEL_INFO: qCInfo(KWIN_XKB) << "XKB:" << buf; break; case XKB_LOG_LEVEL_WARNING: qCWarning(KWIN_XKB) << "XKB:" << buf; break; case XKB_LOG_LEVEL_ERROR: case XKB_LOG_LEVEL_CRITICAL: default: qCCritical(KWIN_XKB) << "XKB:" << buf; break; } } Xkb::Xkb(QObject *parent) : QObject(parent) , m_context(xkb_context_new(XKB_CONTEXT_NO_FLAGS)) - , m_keymap(NULL) - , m_state(NULL) + , m_keymap(nullptr) + , m_state(nullptr) , m_shiftModifier(0) , m_capsModifier(0) , m_controlModifier(0) , m_altModifier(0) , m_metaModifier(0) , m_numModifier(0) , m_numLock(0) , m_capsLock(0) , m_scrollLock(0) , m_modifiers(Qt::NoModifier) , m_consumedModifiers(Qt::NoModifier) , m_keysym(XKB_KEY_NoSymbol) , m_leds() { qRegisterMetaType(); if (!m_context) { qCDebug(KWIN_XKB) << "Could not create xkb context"; } else { xkb_context_set_log_level(m_context, XKB_LOG_LEVEL_DEBUG); xkb_context_set_log_fn(m_context, &xkbLogHandler); // get locale as described in xkbcommon doc // cannot use QLocale as it drops the modifier part QByteArray locale = qgetenv("LC_ALL"); if (locale.isEmpty()) { locale = qgetenv("LC_CTYPE"); } if (locale.isEmpty()) { locale = qgetenv("LANG"); } if (locale.isEmpty()) { locale = QByteArrayLiteral("C"); } m_compose.table = xkb_compose_table_new_from_locale(m_context, locale.constData(), XKB_COMPOSE_COMPILE_NO_FLAGS); if (m_compose.table) { m_compose.state = xkb_compose_state_new(m_compose.table, XKB_COMPOSE_STATE_NO_FLAGS); } } } Xkb::~Xkb() { xkb_compose_state_unref(m_compose.state); xkb_compose_table_unref(m_compose.table); xkb_state_unref(m_state); xkb_keymap_unref(m_keymap); xkb_context_unref(m_context); } void Xkb::reconfigure() { if (!m_context) { return; } xkb_keymap *keymap = nullptr; if (!qEnvironmentVariableIsSet("KWIN_XKB_DEFAULT_KEYMAP")) { keymap = loadKeymapFromConfig(); } if (!keymap) { qCDebug(KWIN_XKB) << "Could not create xkb keymap from configuration"; keymap = loadDefaultKeymap(); } if (keymap) { updateKeymap(keymap); } else { qCDebug(KWIN_XKB) << "Could not create default xkb keymap"; } } static bool stringIsEmptyOrNull(const char *str) { return str == nullptr || str[0] == '\0'; } /** * libxkbcommon uses secure_getenv to read the XKB_DEFAULT_* variables. * As kwin_wayland may have the CAP_SET_NICE capability, it returns nullptr * so we need to do it ourselves (see xkb_context_sanitize_rule_names). **/ static void applyEnvironmentRules(xkb_rule_names &ruleNames) { if (stringIsEmptyOrNull(ruleNames.rules)) { ruleNames.rules = getenv("XKB_DEFAULT_RULES"); } if (stringIsEmptyOrNull(ruleNames.model)) { ruleNames.model = getenv("XKB_DEFAULT_MODEL"); } if (stringIsEmptyOrNull(ruleNames.layout)) { ruleNames.layout = getenv("XKB_DEFAULT_LAYOUT"); ruleNames.variant = getenv("XKB_DEFAULT_VARIANT"); } if (ruleNames.options == nullptr) { ruleNames.options = getenv("XKB_DEFAULT_OPTIONS"); } } xkb_keymap *Xkb::loadKeymapFromConfig() { // load config if (!m_config) { return nullptr; } const KConfigGroup config = m_config->group("Layout"); const QByteArray model = config.readEntry("Model", "pc104").toLocal8Bit(); const QByteArray layout = config.readEntry("LayoutList", "").toLocal8Bit(); const QByteArray options = config.readEntry("Options", "").toLocal8Bit(); xkb_rule_names ruleNames = { .rules = nullptr, .model = model.constData(), .layout = layout.constData(), .variant = nullptr, .options = options.constData() }; applyEnvironmentRules(ruleNames); return xkb_keymap_new_from_names(m_context, &ruleNames, XKB_KEYMAP_COMPILE_NO_FLAGS); } xkb_keymap *Xkb::loadDefaultKeymap() { xkb_rule_names ruleNames = {}; applyEnvironmentRules(ruleNames); return xkb_keymap_new_from_names(m_context, &ruleNames, XKB_KEYMAP_COMPILE_NO_FLAGS); } void Xkb::installKeymap(int fd, uint32_t size) { if (!m_context) { return; } - char *map = reinterpret_cast(mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0)); + char *map = reinterpret_cast(mmap(nullptr, size, PROT_READ, MAP_SHARED, fd, 0)); if (map == MAP_FAILED) { return; } xkb_keymap *keymap = xkb_keymap_new_from_string(m_context, map, XKB_KEYMAP_FORMAT_TEXT_V1, XKB_MAP_COMPILE_PLACEHOLDER); munmap(map, size); if (!keymap) { qCDebug(KWIN_XKB) << "Could not map keymap from file"; return; } m_ownership = Ownership::Client; updateKeymap(keymap); } void Xkb::updateKeymap(xkb_keymap *keymap) { Q_ASSERT(keymap); xkb_state *state = xkb_state_new(keymap); if (!state) { qCDebug(KWIN_XKB) << "Could not create XKB state"; xkb_keymap_unref(keymap); return; } // now release the old ones xkb_state_unref(m_state); xkb_keymap_unref(m_keymap); m_keymap = keymap; m_state = state; m_shiftModifier = xkb_keymap_mod_get_index(m_keymap, XKB_MOD_NAME_SHIFT); m_capsModifier = xkb_keymap_mod_get_index(m_keymap, XKB_MOD_NAME_CAPS); m_controlModifier = xkb_keymap_mod_get_index(m_keymap, XKB_MOD_NAME_CTRL); m_altModifier = xkb_keymap_mod_get_index(m_keymap, XKB_MOD_NAME_ALT); m_metaModifier = xkb_keymap_mod_get_index(m_keymap, XKB_MOD_NAME_LOGO); m_numModifier = xkb_keymap_mod_get_index(m_keymap, XKB_MOD_NAME_NUM); m_numLock = xkb_keymap_led_get_index(m_keymap, XKB_LED_NAME_NUM); m_capsLock = xkb_keymap_led_get_index(m_keymap, XKB_LED_NAME_CAPS); m_scrollLock = xkb_keymap_led_get_index(m_keymap, XKB_LED_NAME_SCROLL); m_currentLayout = xkb_state_serialize_layout(m_state, XKB_STATE_LAYOUT_EFFECTIVE); m_modifierState.depressed = xkb_state_serialize_mods(m_state, xkb_state_component(XKB_STATE_MODS_DEPRESSED)); m_modifierState.latched = xkb_state_serialize_mods(m_state, xkb_state_component(XKB_STATE_MODS_LATCHED)); m_modifierState.locked = xkb_state_serialize_mods(m_state, xkb_state_component(XKB_STATE_MODS_LOCKED)); static bool s_startup = true; if (s_startup || qEnvironmentVariableIsSet("KWIN_FORCE_NUM_LOCK_EVALUATION")) { s_startup = false; if (m_ownership == Ownership::Server && m_numModifier != XKB_MOD_INVALID && m_numLockConfig) { const KConfigGroup config = m_numLockConfig->group("Keyboard"); // STATE_ON = 0, STATE_OFF = 1, STATE_UNCHANGED = 2, see plasma-desktop/kcms/keyboard/kcmmisc.h const auto setting = config.readEntry("NumLock", 2); const bool numLockIsOn = xkb_state_mod_index_is_active(m_state, m_numModifier, XKB_STATE_MODS_LOCKED); if ((setting == 0 && !numLockIsOn) || (setting == 1 && numLockIsOn)) { std::bitset mask{m_modifierState.locked}; if (mask.size() > m_numModifier) { mask[m_numModifier] = (setting == 0); m_modifierState.locked = mask.to_ulong(); xkb_state_update_mask(m_state, m_modifierState.depressed, m_modifierState.latched, m_modifierState.locked, 0, 0, m_currentLayout); m_modifierState.locked = xkb_state_serialize_mods(m_state, xkb_state_component(XKB_STATE_MODS_LOCKED)); } } } } createKeymapFile(); forwardModifiers(); updateModifiers(); } void Xkb::createKeymapFile() { if (!m_seat) { return; } // TODO: uninstall keymap on server? if (!m_keymap) { return; } ScopedCPointer keymapString(xkb_keymap_get_as_string(m_keymap, XKB_KEYMAP_FORMAT_TEXT_V1)); if (keymapString.isNull()) { return; } const uint size = qstrlen(keymapString.data()) + 1; QTemporaryFile *tmp = new QTemporaryFile(this); if (!tmp->open()) { delete tmp; return; } unlink(tmp->fileName().toUtf8().constData()); if (!tmp->resize(size)) { delete tmp; return; } uchar *address = tmp->map(0, size); if (!address) { return; } if (qstrncpy(reinterpret_cast(address), keymapString.data(), size) == nullptr) { delete tmp; return; } m_seat->setKeymap(tmp->handle(), size); } void Xkb::updateModifiers(uint32_t modsDepressed, uint32_t modsLatched, uint32_t modsLocked, uint32_t group) { if (!m_keymap || !m_state) { return; } xkb_state_update_mask(m_state, modsDepressed, modsLatched, modsLocked, 0, 0, group); updateModifiers(); forwardModifiers(); } void Xkb::updateKey(uint32_t key, InputRedirection::KeyboardKeyState state) { if (!m_keymap || !m_state) { return; } xkb_state_update_key(m_state, key + 8, static_cast(state)); if (state == InputRedirection::KeyboardKeyPressed) { const auto sym = toKeysym(key); if (m_compose.state && xkb_compose_state_feed(m_compose.state, sym) == XKB_COMPOSE_FEED_ACCEPTED) { switch (xkb_compose_state_get_status(m_compose.state)) { case XKB_COMPOSE_NOTHING: m_keysym = sym; break; case XKB_COMPOSE_COMPOSED: m_keysym = xkb_compose_state_get_one_sym(m_compose.state); break; default: m_keysym = XKB_KEY_NoSymbol; break; } } else { m_keysym = sym; } } updateModifiers(); updateConsumedModifiers(key); } void Xkb::updateModifiers() { Qt::KeyboardModifiers mods = Qt::NoModifier; if (xkb_state_mod_index_is_active(m_state, m_shiftModifier, XKB_STATE_MODS_EFFECTIVE) == 1 || xkb_state_mod_index_is_active(m_state, m_capsModifier, XKB_STATE_MODS_EFFECTIVE) == 1) { mods |= Qt::ShiftModifier; } if (xkb_state_mod_index_is_active(m_state, m_altModifier, XKB_STATE_MODS_EFFECTIVE) == 1) { mods |= Qt::AltModifier; } if (xkb_state_mod_index_is_active(m_state, m_controlModifier, XKB_STATE_MODS_EFFECTIVE) == 1) { mods |= Qt::ControlModifier; } if (xkb_state_mod_index_is_active(m_state, m_metaModifier, XKB_STATE_MODS_EFFECTIVE) == 1) { mods |= Qt::MetaModifier; } if (xkb_state_mod_index_is_active(m_state, m_numModifier, XKB_STATE_MODS_EFFECTIVE) == 1) { mods |= Qt::KeypadModifier; } m_modifiers = mods; // update LEDs LEDs leds; if (xkb_state_led_index_is_active(m_state, m_numLock) == 1) { leds = leds | LED::NumLock; } if (xkb_state_led_index_is_active(m_state, m_capsLock) == 1) { leds = leds | LED::CapsLock; } if (xkb_state_led_index_is_active(m_state, m_scrollLock) == 1) { leds = leds | LED::ScrollLock; } if (m_leds != leds) { m_leds = leds; emit ledsChanged(m_leds); } m_currentLayout = xkb_state_serialize_layout(m_state, XKB_STATE_LAYOUT_EFFECTIVE); m_modifierState.depressed = xkb_state_serialize_mods(m_state, xkb_state_component(XKB_STATE_MODS_DEPRESSED)); m_modifierState.latched = xkb_state_serialize_mods(m_state, xkb_state_component(XKB_STATE_MODS_LATCHED)); m_modifierState.locked = xkb_state_serialize_mods(m_state, xkb_state_component(XKB_STATE_MODS_LOCKED)); } void Xkb::forwardModifiers() { if (!m_seat) { return; } m_seat->updateKeyboardModifiers(m_modifierState.depressed, m_modifierState.latched, m_modifierState.locked, m_currentLayout); } QString Xkb::layoutName() const { return layoutName(m_currentLayout); } QString Xkb::layoutName(xkb_layout_index_t layout) const { if (!m_keymap) { return QString{}; } return QString::fromLocal8Bit(xkb_keymap_layout_get_name(m_keymap, layout)); } QMap Xkb::layoutNames() const { QMap layouts; const auto size = m_keymap ? xkb_keymap_num_layouts(m_keymap) : 0u; for (xkb_layout_index_t i = 0; i < size; i++) { layouts.insert(i, layoutName(i)); } return layouts; } void Xkb::updateConsumedModifiers(uint32_t key) { Qt::KeyboardModifiers mods = Qt::NoModifier; if (xkb_state_mod_index_is_consumed2(m_state, key + 8, m_shiftModifier, XKB_CONSUMED_MODE_GTK) == 1) { mods |= Qt::ShiftModifier; } if (xkb_state_mod_index_is_consumed2(m_state, key + 8, m_altModifier, XKB_CONSUMED_MODE_GTK) == 1) { mods |= Qt::AltModifier; } if (xkb_state_mod_index_is_consumed2(m_state, key + 8, m_controlModifier, XKB_CONSUMED_MODE_GTK) == 1) { mods |= Qt::ControlModifier; } if (xkb_state_mod_index_is_consumed2(m_state, key + 8, m_metaModifier, XKB_CONSUMED_MODE_GTK) == 1) { mods |= Qt::MetaModifier; } m_consumedModifiers = mods; } Qt::KeyboardModifiers Xkb::modifiersRelevantForGlobalShortcuts() const { if (!m_state) { return Qt::NoModifier; } Qt::KeyboardModifiers mods = Qt::NoModifier; if (xkb_state_mod_index_is_active(m_state, m_shiftModifier, XKB_STATE_MODS_EFFECTIVE) == 1) { mods |= Qt::ShiftModifier; } if (xkb_state_mod_index_is_active(m_state, m_altModifier, XKB_STATE_MODS_EFFECTIVE) == 1) { mods |= Qt::AltModifier; } if (xkb_state_mod_index_is_active(m_state, m_controlModifier, XKB_STATE_MODS_EFFECTIVE) == 1) { mods |= Qt::ControlModifier; } if (xkb_state_mod_index_is_active(m_state, m_metaModifier, XKB_STATE_MODS_EFFECTIVE) == 1) { mods |= Qt::MetaModifier; } Qt::KeyboardModifiers consumedMods = m_consumedModifiers; if ((mods & Qt::ShiftModifier) && (consumedMods == Qt::ShiftModifier)) { // test whether current keysym is a letter // in that case the shift should be removed from the consumed modifiers again // otherwise it would not be possible to trigger e.g. Shift+W as a shortcut // see BUG: 370341 if (QChar(toQtKey(m_keysym)).isLetter()) { consumedMods = Qt::KeyboardModifiers(); } } return mods & ~consumedMods; } xkb_keysym_t Xkb::toKeysym(uint32_t key) { if (!m_state) { return XKB_KEY_NoSymbol; } return xkb_state_key_get_one_sym(m_state, key + 8); } QString Xkb::toString(xkb_keysym_t keysym) { if (!m_state || keysym == XKB_KEY_NoSymbol) { return QString(); } QByteArray byteArray(7, 0); int ok = xkb_keysym_to_utf8(keysym, byteArray.data(), byteArray.size()); if (ok == -1 || ok == 0) { return QString(); } return QString::fromUtf8(byteArray.constData()); } Qt::Key Xkb::toQtKey(xkb_keysym_t keysym) const { return xkbToQtKey(keysym); } xkb_keysym_t Xkb::fromQtKey(Qt::Key key, Qt::KeyboardModifiers mods) const { return qtKeyToXkb(key, mods); } xkb_keysym_t Xkb::fromKeyEvent(QKeyEvent *event) const { xkb_keysym_t sym = xkb_keysym_from_name(event->text().toUtf8().constData(), XKB_KEYSYM_NO_FLAGS); if (sym == XKB_KEY_NoSymbol) { // mapping from text failed, try mapping through KKeyServer sym = fromQtKey(Qt::Key(event->key() & ~Qt::KeyboardModifierMask), event->modifiers()); } return sym; } bool Xkb::shouldKeyRepeat(quint32 key) const { if (!m_keymap) { return false; } return xkb_keymap_key_repeats(m_keymap, key + 8) != 0; } void Xkb::switchToNextLayout() { if (!m_keymap || !m_state) { return; } const xkb_layout_index_t numLayouts = xkb_keymap_num_layouts(m_keymap); const xkb_layout_index_t nextLayout = (xkb_state_serialize_layout(m_state, XKB_STATE_LAYOUT_EFFECTIVE) + 1) % numLayouts; switchToLayout(nextLayout); } void Xkb::switchToPreviousLayout() { if (!m_keymap || !m_state) { return; } const xkb_layout_index_t previousLayout = m_currentLayout == 0 ? numberOfLayouts() - 1 : m_currentLayout -1; switchToLayout(previousLayout); } void Xkb::switchToLayout(xkb_layout_index_t layout) { if (!m_keymap || !m_state) { return; } if (layout >= numberOfLayouts()) { return; } const xkb_mod_mask_t depressed = xkb_state_serialize_mods(m_state, xkb_state_component(XKB_STATE_MODS_DEPRESSED)); const xkb_mod_mask_t latched = xkb_state_serialize_mods(m_state, xkb_state_component(XKB_STATE_MODS_LATCHED)); const xkb_mod_mask_t locked = xkb_state_serialize_mods(m_state, xkb_state_component(XKB_STATE_MODS_LOCKED)); xkb_state_update_mask(m_state, depressed, latched, locked, 0, 0, layout); updateModifiers(); forwardModifiers(); } quint32 Xkb::numberOfLayouts() const { if (!m_keymap) { return 0; } return xkb_keymap_num_layouts(m_keymap); } void Xkb::setSeat(KWayland::Server::SeatInterface *seat) { m_seat = QPointer(seat); } } diff --git a/xwl/drag_x.cpp b/xwl/drag_x.cpp index a0e50f715..b108c07be 100644 --- a/xwl/drag_x.cpp +++ b/xwl/drag_x.cpp @@ -1,550 +1,550 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright 2019 Roman Gilg 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 "drag_x.h" #include "databridge.h" #include "dnd.h" #include "selection_source.h" #include "xwayland.h" #include "abstract_client.h" #include "atoms.h" #include "wayland_server.h" #include "workspace.h" #include #include #include #include #include #include #include namespace KWin { namespace Xwl { static QStringList atomToMimeTypes(xcb_atom_t atom) { QStringList mimeTypes; if (atom == atoms->utf8_string) { mimeTypes << QString::fromLatin1("text/plain;charset=utf-8"); } else if (atom == atoms->text) { mimeTypes << QString::fromLatin1("text/plain"); } else if (atom == atoms->uri_list || atom == atoms->netscape_url || atom == atoms->moz_url) { // We identify netscape and moz format as less detailed formats text/uri-list, // text/x-uri and accept the information loss. mimeTypes << QString::fromLatin1("text/uri-list") << QString::fromLatin1("text/x-uri"); } else { mimeTypes << Selection::atomName(atom); } return mimeTypes; } XToWlDrag::XToWlDrag(X11Source *source) : m_source(source) { connect(DataBridge::self()->dnd(), &Dnd::transferFinished, this, [this](xcb_timestamp_t eventTime) { // we use this mechanism, because the finished call is not // reliable done by Wayland clients auto it = std::find_if(m_dataRequests.begin(), m_dataRequests.end(), [this, eventTime](QPair req) { return req.first == eventTime; }); if (it == m_dataRequests.end()) { // transfer finished for a different drag return; } Q_ASSERT(!(*it).second); (*it).second = true; checkForFinished(); }); connect(source, &X11Source::transferReady, this, [this](xcb_atom_t target, qint32 fd) { Q_UNUSED(target); Q_UNUSED(fd); m_dataRequests << QPair(m_source->timestamp(), false); }); auto *ddm = waylandServer()->internalDataDeviceManager(); m_dataSource = ddm->createDataSource(this); connect(m_dataSource, &KWayland::Client::DataSource::dragAndDropPerformed, this, [this] { m_performed = true; if (m_visit) { connect(m_visit, &WlVisit::finish, this, [this](WlVisit *visit) { Q_UNUSED(visit); checkForFinished(); }); QTimer::singleShot(2000, this, [this]{ if (!m_visit->entered() || !m_visit->dropHandled()) { // X client timed out Q_EMIT finish(this); } else if (m_dataRequests.size() == 0) { // Wl client timed out m_visit->sendFinished(); Q_EMIT finish(this); } }); } checkForFinished(); }); connect(m_dataSource, &KWayland::Client::DataSource::dragAndDropFinished, this, [this] { // this call is not reliably initiated by Wayland clients checkForFinished(); }); // source does _not_ take ownership of m_dataSource source->setDataSource(m_dataSource); auto *dc = new QMetaObject::Connection(); *dc = connect(waylandServer()->dataDeviceManager(), &KWayland::Server::DataDeviceManagerInterface::dataSourceCreated, this, [this, dc](KWayland::Server::DataSourceInterface *dsi) { Q_ASSERT(dsi); if (dsi->client() != waylandServer()->internalConnection()) { return; } QObject::disconnect(*dc); delete dc; connect(dsi, &KWayland::Server::DataSourceInterface::mimeTypeOffered, this, &XToWlDrag::offerCallback); } ); // Start drag with serial of last left pointer button press. // This means X to Wl drags can only be executed with the left pointer button being pressed. // For touch and (maybe) other pointer button drags we have to revisit this. // // Until then we accept the restriction for Xwayland clients. DataBridge::self()->dataDevice()->startDrag(waylandServer()->seat()->pointerButtonSerial(Qt::LeftButton), m_dataSource, DataBridge::self()->dnd()->surface()); waylandServer()->dispatch(); } XToWlDrag::~XToWlDrag() { delete m_dataSource; m_dataSource = nullptr; } DragEventReply XToWlDrag::moveFilter(Toplevel *target, const QPoint &pos) { Q_UNUSED(pos); auto *seat = waylandServer()->seat(); if (m_visit && m_visit->target() == target) { // still same Wl target, wait for X events return DragEventReply::Ignore; } if (m_visit) { if (m_visit->leave()) { delete m_visit; } else { connect(m_visit, &WlVisit::finish, this, [this](WlVisit *visit) { m_oldVisits.removeOne(visit); delete visit; }); m_oldVisits << m_visit; } } const bool hasCurrent = m_visit; m_visit = nullptr; if (!target || !target->surface() || target->surface()->client() == waylandServer()->xWaylandConnection()) { // currently there is no target or target is an Xwayland window // handled here and by X directly if (AbstractClient *ac = qobject_cast(target)) { if (workspace()->activeClient() != ac) { workspace()->activateClient(ac); } } if (hasCurrent) { // last received enter event is now void, // wait for the next one seat->setDragTarget(nullptr); } return DragEventReply::Ignore; } // new Wl native target auto *ac = static_cast(target); m_visit = new WlVisit(ac, this); connect(m_visit, &WlVisit::offersReceived, this, &XToWlDrag::setOffers); return DragEventReply::Ignore; } bool XToWlDrag::handleClientMessage(xcb_client_message_event_t *event) { for (auto *visit : m_oldVisits) { if (visit->handleClientMessage(event)) { return true; } } if (m_visit && m_visit->handleClientMessage(event)) { return true; } return false; } void XToWlDrag::setDragAndDropAction(DnDAction action) { m_dataSource->setDragAndDropActions(action); } DnDAction XToWlDrag::selectedDragAndDropAction() { // Take the last received action only from before the drag was performed, // because the action gets reset as soon as the drag is performed // (this seems to be a bug in KWayland -> TODO). if (!m_performed) { m_lastSelectedDragAndDropAction = m_dataSource->selectedDragAndDropAction(); } return m_lastSelectedDragAndDropAction; } void XToWlDrag::setOffers(const Mimes &offers) { m_source->setOffers(offers); if (offers.isEmpty()) { // There are no offers, so just directly set the drag target, // no transfer possible anyways. setDragTarget(); return; } if (m_offers == offers) { // offers had been set already by a previous visit // Wl side is already configured setDragTarget(); return; } // TODO: make sure that offers are not changed in between visits m_offersPending = m_offers = offers; for (const auto mimePair : offers) { m_dataSource->offer(mimePair.first); } } using Mime = QPair; void XToWlDrag::offerCallback(const QString &mime) { m_offersPending.erase(std::remove_if(m_offersPending.begin(), m_offersPending.end(), [mime](const Mime &m) { return m.first == mime; })); if (m_offersPending.isEmpty() && m_visit && m_visit->entered()) { setDragTarget(); } } void XToWlDrag::setDragTarget() { auto *ac = m_visit->target(); workspace()->activateClient(ac); waylandServer()->seat()->setDragTarget(ac->surface(), ac->inputTransformation()); } bool XToWlDrag::checkForFinished() { if (!m_visit) { // not dropped above Wl native target Q_EMIT finish(this); return true; } if (!m_visit->finished()) { return false; } if (m_dataRequests.size() == 0) { // need to wait for first data request return false; } const bool transfersFinished = std::all_of(m_dataRequests.begin(), m_dataRequests.end(), [](QPair req) { return req.second; }); if (transfersFinished) { m_visit->sendFinished(); Q_EMIT finish(this); } return transfersFinished; } WlVisit::WlVisit(AbstractClient *target, XToWlDrag *drag) : QObject(drag), m_target(target), m_drag(drag) { xcb_connection_t *xcbConn = kwinApp()->x11Connection(); m_window = xcb_generate_id(xcbConn); DataBridge::self()->dnd()->overwriteRequestorWindow(m_window); const uint32_t dndValues[] = { XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY | XCB_EVENT_MASK_PROPERTY_CHANGE }; xcb_create_window(xcbConn, XCB_COPY_FROM_PARENT, m_window, kwinApp()->x11RootWindow(), 0, 0, 8192, 8192, // TODO: get current screen size and connect to changes 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, Xwayland::self()->xcbScreen()->root_visual, XCB_CW_EVENT_MASK, dndValues); uint32_t version = Dnd::version(); xcb_change_property(xcbConn, XCB_PROP_MODE_REPLACE, m_window, atoms->xdnd_aware, XCB_ATOM_ATOM, 32, 1, &version); xcb_map_window(xcbConn, m_window); workspace()->addManualOverlay(m_window); workspace()->updateStackingOrder(true); xcb_flush(xcbConn); m_mapped = true; } WlVisit::~WlVisit() { xcb_connection_t *xcbConn = kwinApp()->x11Connection(); xcb_destroy_window(xcbConn, m_window); xcb_flush(xcbConn); } bool WlVisit::leave() { DataBridge::self()->dnd()->overwriteRequestorWindow(XCB_WINDOW_NONE); unmapProxyWindow(); return m_finished; } bool WlVisit::handleClientMessage(xcb_client_message_event_t *event) { if (event->window != m_window) { // different window return false; } if (event->type == atoms->xdnd_enter) { return handleEnter(event); } else if (event->type == atoms->xdnd_position) { return handlePosition(event); } else if (event->type == atoms->xdnd_drop) { return handleDrop(event); } else if (event->type == atoms->xdnd_leave) { return handleLeave(event); } return false; } static bool hasMimeName(const Mimes &mimes, const QString &name) { return std::any_of(mimes.begin(), mimes.end(), [name](const Mime &m) { return m.first == name; }); } bool WlVisit::handleEnter(xcb_client_message_event_t *event) { if (m_entered) { // a drag already entered return true; } m_entered = true; xcb_client_message_data_t *data = &event->data; m_srcWindow = data->data32[0]; m_version = data->data32[1] >> 24; // get types Mimes offers; if (!(data->data32[1] & 1)) { // message has only max 3 types (which are directly in data) for (size_t i = 0; i < 3; i++) { xcb_atom_t mimeAtom = data->data32[2 + i]; const auto mimeStrings = atomToMimeTypes(mimeAtom); for (const auto mime : mimeStrings ) { if (!hasMimeName(offers, mime)) { offers << Mime(mime, mimeAtom); } } } } else { // more than 3 types -> in window property getMimesFromWinProperty(offers); } Q_EMIT offersReceived(offers); return true; } void WlVisit::getMimesFromWinProperty(Mimes &offers) { xcb_connection_t *xcbConn = kwinApp()->x11Connection(); auto cookie = xcb_get_property(xcbConn, 0, m_srcWindow, atoms->xdnd_type_list, XCB_GET_PROPERTY_TYPE_ANY, 0, 0x1fffffff); - auto *reply = xcb_get_property_reply(xcbConn, cookie, NULL); - if (reply == NULL) { + auto *reply = xcb_get_property_reply(xcbConn, cookie, nullptr); + if (reply == nullptr) { return; } if (reply->type != XCB_ATOM_ATOM || reply->value_len == 0) { // invalid reply value free(reply); return; } xcb_atom_t *mimeAtoms = static_cast(xcb_get_property_value(reply)); for (size_t i = 0; i < reply->value_len; ++i) { const auto mimeStrings = atomToMimeTypes(mimeAtoms[i]); for (const auto mime : mimeStrings) { if (!hasMimeName(offers, mime)) { offers << Mime(mime, mimeAtoms[i]); } } } free(reply); } bool WlVisit::handlePosition(xcb_client_message_event_t *event) { xcb_client_message_data_t *data = &event->data; m_srcWindow = data->data32[0]; if (!m_target) { // not over Wl window at the moment m_action = DnDAction::None; m_actionAtom = XCB_ATOM_NONE; sendStatus(); return true; } const uint32_t pos = data->data32[2]; Q_UNUSED(pos); const xcb_timestamp_t timestamp = data->data32[3]; m_drag->x11Source()->setTimestamp(timestamp); xcb_atom_t actionAtom = m_version > 1 ? data->data32[4] : atoms->xdnd_action_copy; auto action = Drag::atomToClientAction(actionAtom); if (action == DnDAction::None) { // copy action is always possible in XDND action = DnDAction::Copy; actionAtom = atoms->xdnd_action_copy; } if (m_action != action) { m_action = action; m_actionAtom = actionAtom; m_drag->setDragAndDropAction(m_action); } sendStatus(); return true; } bool WlVisit::handleDrop(xcb_client_message_event_t *event) { m_dropHandled = true; xcb_client_message_data_t *data = &event->data; m_srcWindow = data->data32[0]; const xcb_timestamp_t timestamp = data->data32[2]; m_drag->x11Source()->setTimestamp(timestamp); // we do nothing more here, the drop is being processed // through the X11Source object doFinish(); return true; } void WlVisit::doFinish() { m_finished = true; unmapProxyWindow(); Q_EMIT finish(this); } bool WlVisit::handleLeave(xcb_client_message_event_t *event) { m_entered = false; xcb_client_message_data_t *data = &event->data; m_srcWindow = data->data32[0]; doFinish(); return true; } void WlVisit::sendStatus() { // receive position events uint32_t flags = 1 << 1; if (targetAcceptsAction()) { // accept the drop flags |= (1 << 0); } xcb_client_message_data_t data = {0}; data.data32[0] = m_window; data.data32[1] = flags; data.data32[4] = flags & (1 << 0) ? m_actionAtom : static_cast(XCB_ATOM_NONE); Drag::sendClientMessage(m_srcWindow, atoms->xdnd_status, &data); } void WlVisit::sendFinished() { const bool accepted = m_entered && m_action != DnDAction::None; xcb_client_message_data_t data = {0}; data.data32[0] = m_window; data.data32[1] = accepted; data.data32[2] = accepted ? m_actionAtom : static_cast(XCB_ATOM_NONE); Drag::sendClientMessage(m_srcWindow, atoms->xdnd_finished, &data); } bool WlVisit::targetAcceptsAction() const { if (m_action == DnDAction::None) { return false; } const auto selAction = m_drag->selectedDragAndDropAction(); return selAction == m_action || selAction == DnDAction::Copy; } void WlVisit::unmapProxyWindow() { if (!m_mapped) { return; } xcb_connection_t *xcbConn = kwinApp()->x11Connection(); xcb_unmap_window(xcbConn, m_window); workspace()->removeManualOverlay(m_window); workspace()->updateStackingOrder(true); xcb_flush(xcbConn); m_mapped = false; } } // namespace Xwl } // namespace KWin diff --git a/xwl/transfer.cpp b/xwl/transfer.cpp index e2a8f4dd7..8f4945a27 100644 --- a/xwl/transfer.cpp +++ b/xwl/transfer.cpp @@ -1,594 +1,594 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright 2018 Roman Gilg 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 "transfer.h" #include "databridge.h" #include "xwayland.h" #include "abstract_client.h" #include "atoms.h" #include "wayland_server.h" #include "workspace.h" #include #include #include #include #include #include #include #include #include #include #include #include namespace KWin { namespace Xwl { // in Bytes: equals 64KB static const uint32_t s_incrChunkSize = 63 * 1024; Transfer::Transfer(xcb_atom_t selection, qint32 fd, xcb_timestamp_t timestamp, QObject *parent) : QObject(parent) , m_atom(selection) , m_fd(fd) , m_timestamp(timestamp) { } void Transfer::createSocketNotifier(QSocketNotifier::Type type) { delete m_notifier; m_notifier = new QSocketNotifier(m_fd, type, this); } void Transfer::clearSocketNotifier() { delete m_notifier; m_notifier = nullptr; } void Transfer::timeout() { if (m_timeout) { endTransfer(); } m_timeout = true; } void Transfer::endTransfer() { clearSocketNotifier(); closeFd(); Q_EMIT finished(); } void Transfer::closeFd() { if (m_fd < 0) { return; } close(m_fd); m_fd = -1; } TransferWltoX::TransferWltoX(xcb_atom_t selection, xcb_selection_request_event_t *request, qint32 fd, QObject *parent) : Transfer(selection, fd, 0, parent) , m_request(request) { } TransferWltoX::~TransferWltoX() { delete m_request; m_request = nullptr; } void TransferWltoX::startTransferFromSource() { createSocketNotifier(QSocketNotifier::Read); connect(socketNotifier(), &QSocketNotifier::activated, this, [this](int socket) { Q_UNUSED(socket); readWlSource(); } ); } int TransferWltoX::flushSourceData() { xcb_connection_t *xcbConn = kwinApp()->x11Connection(); xcb_change_property(xcbConn, XCB_PROP_MODE_REPLACE, m_request->requestor, m_request->property, m_request->target, 8, m_chunks.first().first.size(), m_chunks.first().first.data()); xcb_flush(xcbConn); m_propertyIsSet = true; resetTimeout(); const auto rm = m_chunks.takeFirst(); return rm.first.size(); } void TransferWltoX::startIncr() { Q_ASSERT(m_chunks.size() == 1); xcb_connection_t *xcbConn = kwinApp()->x11Connection(); uint32_t mask[] = { XCB_EVENT_MASK_PROPERTY_CHANGE }; xcb_change_window_attributes (xcbConn, m_request->requestor, XCB_CW_EVENT_MASK, mask); // spec says to make the available space larger const uint32_t chunkSpace = 1024 + s_incrChunkSize; xcb_change_property(xcbConn, XCB_PROP_MODE_REPLACE, m_request->requestor, m_request->property, atoms->incr, 32, 1, &chunkSpace); xcb_flush(xcbConn); setIncr(true); // first data will be flushed after the property has been deleted // again by the requestor m_flushPropertyOnDelete = true; m_propertyIsSet = true; Q_EMIT selectionNotify(m_request, true); } void TransferWltoX::readWlSource() { if (m_chunks.size() == 0 || m_chunks.last().second == s_incrChunkSize) { // append new chunk auto next = QPair(); next.first.resize(s_incrChunkSize); next.second = 0; m_chunks.append(next); } const auto oldLen = m_chunks.last().second; const auto avail = s_incrChunkSize - m_chunks.last().second; Q_ASSERT(avail > 0); ssize_t readLen = read(fd(), m_chunks.last().first.data() + oldLen, avail); if (readLen == -1) { qCWarning(KWIN_XWL) << "Error reading in Wl data."; // TODO: cleanup X side? endTransfer(); return; } m_chunks.last().second = oldLen + readLen; if (readLen == 0) { // at the fd end - complete transfer now m_chunks.last().first.resize(m_chunks.last().second); if (incr()) { // incremental transfer is to be completed now m_flushPropertyOnDelete = true; if (!m_propertyIsSet) { // flush if target's property is not set at the moment flushSourceData(); } clearSocketNotifier(); } else { // non incremental transfer is to be completed now, // data can be transferred to X client via a single property set flushSourceData(); Q_EMIT selectionNotify(m_request, true); endTransfer(); } } else if (m_chunks.last().second == s_incrChunkSize) { // first chunk full, but not yet at fd end -> go incremental if (incr()) { m_flushPropertyOnDelete = true; if (!m_propertyIsSet) { // flush if target's property is not set at the moment flushSourceData(); } } else { // starting incremental transfer startIncr(); } } resetTimeout(); } bool TransferWltoX::handlePropertyNotify(xcb_property_notify_event_t *event) { if (event->window == m_request->requestor) { if (event->state == XCB_PROPERTY_DELETE && event->atom == m_request->property) { handlePropertyDelete(); } return true; } return false; } void TransferWltoX::handlePropertyDelete() { if (!incr()) { // non-incremental transfer: nothing to do return; } m_propertyIsSet = false; if (m_flushPropertyOnDelete) { if (!socketNotifier() && m_chunks.isEmpty()) { // transfer complete xcb_connection_t *xcbConn = kwinApp()->x11Connection(); uint32_t mask[] = {0}; xcb_change_window_attributes (xcbConn, m_request->requestor, XCB_CW_EVENT_MASK, mask); xcb_change_property(xcbConn, XCB_PROP_MODE_REPLACE, m_request->requestor, m_request->property, m_request->target, - 8, 0, NULL); + 8, 0, nullptr); xcb_flush(xcbConn); m_flushPropertyOnDelete = false; endTransfer(); } else { flushSourceData(); } } } TransferXtoWl::TransferXtoWl(xcb_atom_t selection, xcb_atom_t target, qint32 fd, xcb_timestamp_t timestamp, xcb_window_t parentWindow, QObject *parent) : Transfer(selection, fd, timestamp, parent) { // create transfer window xcb_connection_t *xcbConn = kwinApp()->x11Connection(); m_window = xcb_generate_id(xcbConn); const uint32_t values[] = { XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY | XCB_EVENT_MASK_PROPERTY_CHANGE }; xcb_create_window(xcbConn, XCB_COPY_FROM_PARENT, m_window, parentWindow, 0, 0, 10, 10, 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, Xwayland::self()->xcbScreen()->root_visual, XCB_CW_EVENT_MASK, values); // convert selection xcb_convert_selection(xcbConn, m_window, selection, target, atoms->wl_selection, timestamp); xcb_flush(xcbConn); } TransferXtoWl::~TransferXtoWl() { xcb_connection_t *xcbConn = kwinApp()->x11Connection(); xcb_destroy_window(xcbConn, m_window); xcb_flush(xcbConn); delete m_receiver; m_receiver = nullptr; } bool TransferXtoWl::handlePropertyNotify(xcb_property_notify_event_t *event) { if (event->window == m_window) { if (event->state == XCB_PROPERTY_NEW_VALUE && event->atom == atoms->wl_selection) { getIncrChunk(); } return true; } return false; } bool TransferXtoWl::handleSelectionNotify(xcb_selection_notify_event_t *event) { if (event->requestor != m_window) { return false; } if (event->selection != atom()) { return false; } if (event->property == XCB_ATOM_NONE) { qCWarning(KWIN_XWL) << "Incoming X selection conversion failed"; return true; } if (event->target == atoms->targets) { qCWarning(KWIN_XWL) << "Received targets too late"; // TODO: or allow it? return true; } if (m_receiver) { // second selection notify element - misbehaving source // TODO: cancel this transfer? return True; } if (event->target == atoms->netscape_url) { m_receiver = new NetscapeUrlReceiver; } else if (event->target == atoms->moz_url) { m_receiver = new MozUrlReceiver; } else { m_receiver = new DataReceiver; } startTransfer(); return true; } void TransferXtoWl::startTransfer() { xcb_connection_t *xcbConn = kwinApp()->x11Connection(); auto cookie = xcb_get_property(xcbConn, 1, m_window, atoms->wl_selection, XCB_GET_PROPERTY_TYPE_ANY, 0, 0x1fffffff ); - auto *reply = xcb_get_property_reply(xcbConn, cookie, NULL); - if (reply == NULL) { + auto *reply = xcb_get_property_reply(xcbConn, cookie, nullptr); + if (reply == nullptr) { qCWarning(KWIN_XWL) << "Can't get selection property."; endTransfer(); return; } if (reply->type == atoms->incr) { setIncr(true); free(reply); } else { setIncr(false); // reply's ownership is transferred m_receiver->transferFromProperty(reply); dataSourceWrite(); } } void TransferXtoWl::getIncrChunk() { if (!incr()) { // source tries to sent incrementally, but did not announce it before return; } if (!m_receiver) { // receive mechanism has not yet been setup return; } xcb_connection_t *xcbConn = kwinApp()->x11Connection(); auto cookie = xcb_get_property(xcbConn, 0, m_window, atoms->wl_selection, XCB_GET_PROPERTY_TYPE_ANY, 0, 0x1fffffff); auto *reply = xcb_get_property_reply(xcbConn, cookie, nullptr); if (!reply) { qCWarning(KWIN_XWL) << "Can't get selection property."; endTransfer(); return; } if (xcb_get_property_value_length(reply) > 0) { // reply's ownership is transferred m_receiver->transferFromProperty(reply); dataSourceWrite(); } else { // Transfer complete free(reply); endTransfer(); } } DataReceiver::~DataReceiver() { if (m_propertyReply) { free(m_propertyReply); m_propertyReply = nullptr; } } void DataReceiver::transferFromProperty(xcb_get_property_reply_t *reply) { m_propertyStart = 0; m_propertyReply = reply; setData(static_cast(xcb_get_property_value(reply)), xcb_get_property_value_length(reply)); } void DataReceiver::setData(const char *value, int length) { // simply set data without copy m_data = QByteArray::fromRawData(value, length); } QByteArray DataReceiver::data() const { return QByteArray::fromRawData(m_data.data() + m_propertyStart, m_data.size() - m_propertyStart); } void DataReceiver::partRead(int length) { m_propertyStart += length; if (m_propertyStart == m_data.size()) { Q_ASSERT(m_propertyReply); free(m_propertyReply); m_propertyReply = nullptr; } } void NetscapeUrlReceiver::setData(const char *value, int length) { auto origData = QByteArray::fromRawData(value, length); if (origData.indexOf('\n') == -1) { // there are no line breaks, not in Netscape url format or empty, // but try anyway setDataInternal(origData); return; } // remove every second line QByteArray data; int start = 0; bool remLine = false; while (start < length) { auto part = QByteArray::fromRawData(value + start, length - start); const int linebreak = part.indexOf('\n'); if (linebreak == -1) { // no more linebreaks, end of work if (!remLine) { // append the rest data.append(part); } break; } if (remLine) { // no data to add, but add a linebreak for the next line data.append('\n'); } else { // add data till before linebreak data.append(part.data(), linebreak); } remLine = !remLine; start = linebreak + 1; } setDataInternal(data); } void MozUrlReceiver::setData(const char *value, int length) { // represent as QByteArray (guaranteed '\0'-terminated) const auto origData = QByteArray::fromRawData(value, length); // text/x-moz-url data is sent in utf-16 - copies the content // and converts it into 8 byte representation const auto byteData = QString::fromUtf16(reinterpret_cast(origData.data())).toLatin1(); if (byteData.indexOf('\n') == -1) { // there are no line breaks, not in text/x-moz-url format or empty, // but try anyway setDataInternal(byteData); return; } // remove every second line QByteArray data; int start = 0; bool remLine = false; while (start < length) { auto part = QByteArray::fromRawData(byteData.data() + start, byteData.size() - start); const int linebreak = part.indexOf('\n'); if (linebreak == -1) { // no more linebreaks, end of work if (!remLine) { // append the rest data.append(part); } break; } if (remLine) { // no data to add, but add a linebreak for the next line data.append('\n'); } else { // add data till before linebreak data.append(part.data(), linebreak); } remLine = !remLine; start = linebreak + 1; } setDataInternal(data); } void TransferXtoWl::dataSourceWrite() { QByteArray property = m_receiver->data(); ssize_t len = write(fd(), property.constData(), property.size()); if (len == -1) { qCWarning(KWIN_XWL) << "X11 to Wayland write error on fd:" << fd(); endTransfer(); return; } m_receiver->partRead(len); if (len == property.size()) { // property completely transferred if (incr()) { clearSocketNotifier(); xcb_connection_t *xcbConn = kwinApp()->x11Connection(); xcb_delete_property(xcbConn, m_window, atoms->wl_selection); xcb_flush(xcbConn); } else { // transfer complete endTransfer(); } } else { if (!socketNotifier()) { createSocketNotifier(QSocketNotifier::Write); connect(socketNotifier(), &QSocketNotifier::activated, this, [this](int socket) { Q_UNUSED(socket); dataSourceWrite(); } ); } } resetTimeout(); } } // namespace Xwl } // namespace KWin