diff --git a/autotests/drm/mock_drm.h b/autotests/drm/mock_drm.h index ae8076977..bba6edfa3 100644 --- a/autotests/drm/mock_drm.h +++ b/autotests/drm/mock_drm.h @@ -1,32 +1,32 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2017 Martin Flöser 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 MockDrm { void addDrmModeProperties(int fd, const QVector<_drmModeProperty> &properties); } diff --git a/autotests/integration/helper/kill.cpp b/autotests/integration/helper/kill.cpp index 586806d65..68647dac3 100644 --- a/autotests/integration/helper/kill.cpp +++ b/autotests/integration/helper/kill.cpp @@ -1,46 +1,46 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2016 Martin Gräßlin Copyright (C) 2019 David Edmundson 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 #include #include #include #include -#include +#include int main(int argc, char *argv[]) { qputenv("QT_QPA_PLATFORM", QByteArrayLiteral("wayland")); QApplication app(argc, argv); QWidget w; w.setGeometry(QRect(0, 0, 100, 200)); w.show(); auto freezeHandler = [](int) { while(true) { sleep(10000); } }; signal(SIGUSR1, freezeHandler); return app.exec(); } diff --git a/autotests/integration/shell_client_test.cpp b/autotests/integration/shell_client_test.cpp index 5a10cda00..fd503764a 100644 --- a/autotests/integration/shell_client_test.cpp +++ b/autotests/integration/shell_client_test.cpp @@ -1,1518 +1,1519 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2016 Martin Gräßlin Copyright (C) 2019 David Edmundson This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "kwin_wayland_test.h" #include "cursor.h" #include "decorations/decorationbridge.h" #include "decorations/settings.h" #include "effects.h" #include "deleted.h" #include "platform.h" #include "shell_client.h" #include "screens.h" #include "wayland_server.h" #include "workspace.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // system #include #include #include -#include + +#include using namespace KWin; using namespace KWayland::Client; static const QString s_socketName = QStringLiteral("wayland_test_kwin_shell_client-0"); class TestShellClient : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void init(); void cleanup(); void testMapUnmapMap_data(); void testMapUnmapMap(); void testDesktopPresenceChanged(); void testTransientPositionAfterRemap(); void testWindowOutputs_data(); void testWindowOutputs(); void testMinimizeActiveWindow_data(); void testMinimizeActiveWindow(); void testFullscreen_data(); void testFullscreen(); void testFullscreenRestore_data(); void testFullscreenRestore(); void testUserCanSetFullscreen_data(); void testUserCanSetFullscreen(); void testUserSetFullscreenWlShell(); void testUserSetFullscreenXdgShell_data(); void testUserSetFullscreenXdgShell(); void testMaximizedToFullscreenWlShell_data(); void testMaximizedToFullscreenWlShell(); void testMaximizedToFullscreenXdgShell_data(); void testMaximizedToFullscreenXdgShell(); void testWindowOpensLargerThanScreen_data(); void testWindowOpensLargerThanScreen(); void testHidden_data(); void testHidden(); void testDesktopFileName(); void testCaptionSimplified(); void testCaptionMultipleWindows(); void testUnresponsiveWindow_data(); void testUnresponsiveWindow(); void testX11WindowId_data(); void testX11WindowId(); void testAppMenu(); void testNoDecorationModeRequested_data(); void testNoDecorationModeRequested(); void testSendClientWithTransientToDesktop_data(); void testSendClientWithTransientToDesktop(); void testMinimizeWindowWithTransients_data(); void testMinimizeWindowWithTransients(); void testXdgDecoration_data(); void testXdgDecoration(); void testXdgNeverCommitted(); void testXdgInitialState(); void testXdgInitiallyMaximised(); void testXdgInitiallyMinimized(); void testXdgWindowGeometry(); }; void TestShellClient::initTestCase() { qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); QSignalSpy workspaceCreatedSpy(kwinApp(), &Application::workspaceCreated); QVERIFY(workspaceCreatedSpy.isValid()); kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); QMetaObject::invokeMethod(kwinApp()->platform(), "setVirtualOutputs", Qt::DirectConnection, Q_ARG(int, 2)); QVERIFY(waylandServer()->init(s_socketName.toLocal8Bit())); kwinApp()->start(); QVERIFY(workspaceCreatedSpy.wait()); QCOMPARE(screens()->count(), 2); QCOMPARE(screens()->geometry(0), QRect(0, 0, 1280, 1024)); QCOMPARE(screens()->geometry(1), QRect(1280, 0, 1280, 1024)); waylandServer()->initWorkspace(); } void TestShellClient::init() { QVERIFY(Test::setupWaylandConnection(Test::AdditionalWaylandInterface::Decoration | Test::AdditionalWaylandInterface::XdgDecoration | Test::AdditionalWaylandInterface::AppMenu)); screens()->setCurrent(0); KWin::Cursor::setPos(QPoint(1280, 512)); } void TestShellClient::cleanup() { Test::destroyWaylandConnection(); } void TestShellClient::testMapUnmapMap_data() { QTest::addColumn("type"); QTest::newRow("wlShell") << Test::ShellSurfaceType::WlShell; QTest::newRow("xdgShellV5") << Test::ShellSurfaceType::XdgShellV5; QTest::newRow("xdgShellV6") << Test::ShellSurfaceType::XdgShellV6; QTest::newRow("xdgWmBase") << Test::ShellSurfaceType::XdgShellStable; } void TestShellClient::testMapUnmapMap() { // this test verifies that mapping a previously mapped window works correctly QSignalSpy clientAddedSpy(waylandServer(), &WaylandServer::shellClientAdded); QVERIFY(clientAddedSpy.isValid()); QSignalSpy effectsWindowShownSpy(effects, &EffectsHandler::windowShown); QVERIFY(effectsWindowShownSpy.isValid()); QSignalSpy effectsWindowHiddenSpy(effects, &EffectsHandler::windowHidden); QVERIFY(effectsWindowHiddenSpy.isValid()); QScopedPointer surface(Test::createSurface()); QFETCH(Test::ShellSurfaceType, type); QScopedPointer shellSurface(Test::createShellSurface(type, surface.data())); // now let's render Test::render(surface.data(), QSize(100, 50), Qt::blue); QVERIFY(clientAddedSpy.isEmpty()); QVERIFY(clientAddedSpy.wait()); auto client = clientAddedSpy.first().first().value(); QVERIFY(client); QVERIFY(client->isShown(true)); QCOMPARE(client->isHiddenInternal(), false); QCOMPARE(client->readyForPainting(), true); QCOMPARE(client->depth(), 32); QVERIFY(client->hasAlpha()); QCOMPARE(client->icon().name(), QStringLiteral("wayland")); QCOMPARE(workspace()->activeClient(), client); QVERIFY(effectsWindowShownSpy.isEmpty()); QVERIFY(client->isMaximizable()); QVERIFY(client->isMovable()); QVERIFY(client->isMovableAcrossScreens()); QVERIFY(client->isResizable()); QVERIFY(client->property("maximizable").toBool()); QVERIFY(client->property("moveable").toBool()); QVERIFY(client->property("moveableAcrossScreens").toBool()); QVERIFY(client->property("resizeable").toBool()); QCOMPARE(client->isInternal(), false); QVERIFY(client->effectWindow()); QVERIFY(!client->effectWindow()->internalWindow()); QCOMPARE(client->internalId().isNull(), false); const auto uuid = client->internalId(); QUuid deletedUuid; QCOMPARE(deletedUuid.isNull(), true); connect(client, &ShellClient::windowClosed, this, [&deletedUuid] (Toplevel *, Deleted *d) { deletedUuid = d->internalId(); }); // now unmap QSignalSpy hiddenSpy(client, &ShellClient::windowHidden); QVERIFY(hiddenSpy.isValid()); QSignalSpy windowClosedSpy(client, &ShellClient::windowClosed); QVERIFY(windowClosedSpy.isValid()); surface->attachBuffer(Buffer::Ptr()); surface->commit(Surface::CommitFlag::None); QVERIFY(hiddenSpy.wait()); QCOMPARE(client->readyForPainting(), true); QCOMPARE(client->isHiddenInternal(), true); QVERIFY(windowClosedSpy.isEmpty()); QVERIFY(!workspace()->activeClient()); QCOMPARE(effectsWindowHiddenSpy.count(), 1); QCOMPARE(effectsWindowHiddenSpy.first().first().value(), client->effectWindow()); QSignalSpy windowShownSpy(client, &ShellClient::windowShown); QVERIFY(windowShownSpy.isValid()); Test::render(surface.data(), QSize(100, 50), Qt::blue, QImage::Format_RGB32); QCOMPARE(clientAddedSpy.count(), 1); QVERIFY(windowShownSpy.wait()); QCOMPARE(windowShownSpy.count(), 1); QCOMPARE(clientAddedSpy.count(), 1); QCOMPARE(client->readyForPainting(), true); QCOMPARE(client->isHiddenInternal(), false); QCOMPARE(client->depth(), 24); QVERIFY(!client->hasAlpha()); QCOMPARE(workspace()->activeClient(), client); QCOMPARE(effectsWindowShownSpy.count(), 1); QCOMPARE(effectsWindowShownSpy.first().first().value(), client->effectWindow()); // let's unmap again surface->attachBuffer(Buffer::Ptr()); surface->commit(Surface::CommitFlag::None); QVERIFY(hiddenSpy.wait()); QCOMPARE(hiddenSpy.count(), 2); QCOMPARE(client->readyForPainting(), true); QCOMPARE(client->isHiddenInternal(), true); QCOMPARE(client->internalId(), uuid); QVERIFY(windowClosedSpy.isEmpty()); QCOMPARE(effectsWindowHiddenSpy.count(), 2); QCOMPARE(effectsWindowHiddenSpy.last().first().value(), client->effectWindow()); shellSurface.reset(); surface.reset(); QVERIFY(windowClosedSpy.wait()); QCOMPARE(windowClosedSpy.count(), 1); QCOMPARE(effectsWindowHiddenSpy.count(), 2); QCOMPARE(deletedUuid.isNull(), false); QCOMPARE(deletedUuid, uuid); } void TestShellClient::testDesktopPresenceChanged() { // this test verifies that the desktop presence changed signals are properly emitted QScopedPointer surface(Test::createSurface()); QScopedPointer shellSurface(Test::createShellSurface(surface.data())); auto c = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); QVERIFY(c); QCOMPARE(c->desktop(), 1); effects->setNumberOfDesktops(4); QSignalSpy desktopPresenceChangedClientSpy(c, &ShellClient::desktopPresenceChanged); QVERIFY(desktopPresenceChangedClientSpy.isValid()); QSignalSpy desktopPresenceChangedWorkspaceSpy(workspace(), &Workspace::desktopPresenceChanged); QVERIFY(desktopPresenceChangedWorkspaceSpy.isValid()); QSignalSpy desktopPresenceChangedEffectsSpy(effects, &EffectsHandler::desktopPresenceChanged); QVERIFY(desktopPresenceChangedEffectsSpy.isValid()); // let's change the desktop workspace()->sendClientToDesktop(c, 2, false); QCOMPARE(c->desktop(), 2); QCOMPARE(desktopPresenceChangedClientSpy.count(), 1); QCOMPARE(desktopPresenceChangedWorkspaceSpy.count(), 1); QCOMPARE(desktopPresenceChangedEffectsSpy.count(), 1); // verify the arguments QCOMPARE(desktopPresenceChangedClientSpy.first().at(0).value(), c); QCOMPARE(desktopPresenceChangedClientSpy.first().at(1).toInt(), 1); QCOMPARE(desktopPresenceChangedWorkspaceSpy.first().at(0).value(), c); QCOMPARE(desktopPresenceChangedWorkspaceSpy.first().at(1).toInt(), 1); QCOMPARE(desktopPresenceChangedEffectsSpy.first().at(0).value(), c->effectWindow()); QCOMPARE(desktopPresenceChangedEffectsSpy.first().at(1).toInt(), 1); QCOMPARE(desktopPresenceChangedEffectsSpy.first().at(2).toInt(), 2); } void TestShellClient::testTransientPositionAfterRemap() { // this test simulates the situation that a transient window gets reused and the parent window // moved between the two usages QScopedPointer surface(Test::createSurface()); QScopedPointer shellSurface(Test::createShellSurface(surface.data())); auto c = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); QVERIFY(c); // create the Transient window QScopedPointer transientSurface(Test::createSurface()); QScopedPointer transientShellSurface(Test::createShellSurface(transientSurface.data())); transientShellSurface->setTransient(surface.data(), QPoint(5, 10)); auto transient = Test::renderAndWaitForShown(transientSurface.data(), QSize(50, 40), Qt::blue); QVERIFY(transient); QCOMPARE(transient->geometry(), QRect(c->geometry().topLeft() + QPoint(5, 10), QSize(50, 40))); // unmap the transient QSignalSpy windowHiddenSpy(transient, &ShellClient::windowHidden); QVERIFY(windowHiddenSpy.isValid()); transientSurface->attachBuffer(Buffer::Ptr()); transientSurface->commit(Surface::CommitFlag::None); QVERIFY(windowHiddenSpy.wait()); // now move the parent surface c->setGeometry(c->geometry().translated(5, 10)); // now map the transient again QSignalSpy windowShownSpy(transient, &ShellClient::windowShown); QVERIFY(windowShownSpy.isValid()); Test::render(transientSurface.data(), QSize(50, 40), Qt::blue); QVERIFY(windowShownSpy.wait()); QCOMPARE(transient->geometry(), QRect(c->geometry().topLeft() + QPoint(5, 10), QSize(50, 40))); } void TestShellClient::testWindowOutputs_data() { QTest::addColumn("type"); QTest::newRow("wlShell") << Test::ShellSurfaceType::WlShell; QTest::newRow("xdgShellV5") << Test::ShellSurfaceType::XdgShellV5; QTest::newRow("xdgShellV6") << Test::ShellSurfaceType::XdgShellV6; QTest::newRow("xdgWmBase") << Test::ShellSurfaceType::XdgShellStable; } void TestShellClient::testWindowOutputs() { QScopedPointer surface(Test::createSurface()); QFETCH(Test::ShellSurfaceType, type); QScopedPointer shellSurface(Test::createShellSurface(type, surface.data())); auto size = QSize(200,200); QSignalSpy outputEnteredSpy(surface.data(), &Surface::outputEntered); QSignalSpy outputLeftSpy(surface.data(), &Surface::outputLeft); auto c = Test::renderAndWaitForShown(surface.data(), size, Qt::blue); //move to be in the first screen c->setGeometry(QRect(QPoint(100,100), size)); //we don't don't know where the compositor first placed this window, //this might fire, it might not outputEnteredSpy.wait(5); outputEnteredSpy.clear(); QCOMPARE(surface->outputs().count(), 1); QCOMPARE(surface->outputs().first()->globalPosition(), QPoint(0,0)); //move to overlapping both first and second screen c->setGeometry(QRect(QPoint(1250,100), size)); QVERIFY(outputEnteredSpy.wait()); QCOMPARE(outputEnteredSpy.count(), 1); QCOMPARE(outputLeftSpy.count(), 0); QCOMPARE(surface->outputs().count(), 2); QVERIFY(surface->outputs()[0] != surface->outputs()[1]); //move entirely into second screen c->setGeometry(QRect(QPoint(1400,100), size)); QVERIFY(outputLeftSpy.wait()); QCOMPARE(outputEnteredSpy.count(), 1); QCOMPARE(outputLeftSpy.count(), 1); QCOMPARE(surface->outputs().count(), 1); QCOMPARE(surface->outputs().first()->globalPosition(), QPoint(1280,0)); } void TestShellClient::testMinimizeActiveWindow_data() { QTest::addColumn("type"); QTest::newRow("wlShell") << Test::ShellSurfaceType::WlShell; QTest::newRow("xdgShellV5") << Test::ShellSurfaceType::XdgShellV5; QTest::newRow("xdgShellV6") << Test::ShellSurfaceType::XdgShellV6; QTest::newRow("xdgWmBase") << Test::ShellSurfaceType::XdgShellStable; } void TestShellClient::testMinimizeActiveWindow() { // this test verifies that when minimizing the active window it gets deactivated QScopedPointer surface(Test::createSurface()); QFETCH(Test::ShellSurfaceType, type); QScopedPointer shellSurface(Test::createShellSurface(type, surface.data())); auto c = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); QVERIFY(c); QVERIFY(c->isActive()); QCOMPARE(workspace()->activeClient(), c); QVERIFY(c->wantsInput()); QVERIFY(c->wantsTabFocus()); QVERIFY(c->isShown(true)); workspace()->slotWindowMinimize(); QVERIFY(!c->isShown(true)); QVERIFY(c->wantsInput()); QVERIFY(c->wantsTabFocus()); QVERIFY(!c->isActive()); QVERIFY(!workspace()->activeClient()); QVERIFY(c->isMinimized()); // unminimize again c->unminimize(); QVERIFY(!c->isMinimized()); QVERIFY(c->isActive()); QVERIFY(c->wantsInput()); QVERIFY(c->wantsTabFocus()); QVERIFY(c->isShown(true)); QCOMPARE(workspace()->activeClient(), c); } void TestShellClient::testFullscreen_data() { QTest::addColumn("type"); QTest::addColumn("decoMode"); QTest::newRow("wlShell") << Test::ShellSurfaceType::WlShell << ServerSideDecoration::Mode::Client; QTest::newRow("xdgShellV5") << Test::ShellSurfaceType::XdgShellV5 << ServerSideDecoration::Mode::Client; QTest::newRow("xdgShellV6") << Test::ShellSurfaceType::XdgShellV6 << ServerSideDecoration::Mode::Client; QTest::newRow("xdgShellWmBase") << Test::ShellSurfaceType::XdgShellStable << ServerSideDecoration::Mode::Client; QTest::newRow("wlShell - deco") << Test::ShellSurfaceType::WlShell << ServerSideDecoration::Mode::Server; QTest::newRow("xdgShellV5 - deco") << Test::ShellSurfaceType::XdgShellV5 << ServerSideDecoration::Mode::Server; QTest::newRow("xdgShellV6 - deco") << Test::ShellSurfaceType::XdgShellV6 << ServerSideDecoration::Mode::Server; QTest::newRow("xdgShellWmBase - deco") << Test::ShellSurfaceType::XdgShellStable << ServerSideDecoration::Mode::Server; } void TestShellClient::testFullscreen() { // this test verifies that a window can be properly fullscreened QScopedPointer surface(Test::createSurface()); QFETCH(Test::ShellSurfaceType, type); QScopedPointer shellSurface(Test::createShellSurface(type, surface.data())); ShellSurface* wlShellSurface = nullptr; XdgShellSurface *xdgShellSurface = nullptr; // fullscreen the window switch (type) { case Test::ShellSurfaceType::WlShell: wlShellSurface = qobject_cast(shellSurface.data()); break; case Test::ShellSurfaceType::XdgShellV5: case Test::ShellSurfaceType::XdgShellV6: case Test::ShellSurfaceType::XdgShellStable: xdgShellSurface = qobject_cast(shellSurface.data()); break; default: Q_UNREACHABLE(); break; } QVERIFY(wlShellSurface || xdgShellSurface); QVERIFY(!(wlShellSurface && xdgShellSurface)); // create deco QScopedPointer deco(Test::waylandServerSideDecoration()->create(surface.data())); QSignalSpy decoSpy(deco.data(), &ServerSideDecoration::modeChanged); QVERIFY(decoSpy.isValid()); QVERIFY(decoSpy.wait()); QFETCH(ServerSideDecoration::Mode, decoMode); deco->requestMode(decoMode); QVERIFY(decoSpy.wait()); QCOMPARE(deco->mode(), decoMode); auto c = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); QVERIFY(c); QVERIFY(c->isActive()); QCOMPARE(c->layer(), NormalLayer); QVERIFY(!c->isFullScreen()); QCOMPARE(c->clientSize(), QSize(100, 50)); QCOMPARE(c->isDecorated(), decoMode == ServerSideDecoration::Mode::Server); QCOMPARE(c->sizeForClientSize(c->clientSize()), c->geometry().size()); QSignalSpy fullscreenChangedSpy(c, &ShellClient::fullScreenChanged); QVERIFY(fullscreenChangedSpy.isValid()); QSignalSpy geometryChangedSpy(c, &ShellClient::geometryChanged); QVERIFY(geometryChangedSpy.isValid()); QSignalSpy sizeChangeRequestedSpy(shellSurface.data(), SIGNAL(sizeChanged(QSize))); QVERIFY(sizeChangeRequestedSpy.isValid()); QSignalSpy configureRequestedSpy(shellSurface.data(), SIGNAL(configureRequested(QSize, KWayland::Client::XdgShellSurface::States, quint32))); if (xdgShellSurface) { QVERIFY(configureRequestedSpy.isValid()); } if (wlShellSurface) { wlShellSurface->setFullscreen(); } if (xdgShellSurface) { xdgShellSurface->setFullscreen(true); } QVERIFY(fullscreenChangedSpy.wait()); QVERIFY(sizeChangeRequestedSpy.wait()); QCOMPARE(sizeChangeRequestedSpy.count(), 1); QCOMPARE(sizeChangeRequestedSpy.first().first().toSize(), QSize(screens()->size(0))); // TODO: should switch to fullscreen once it's updated QVERIFY(c->isFullScreen()); QCOMPARE(c->clientSize(), QSize(100, 50)); QVERIFY(geometryChangedSpy.isEmpty()); if (xdgShellSurface) { for (const auto &it: configureRequestedSpy) { xdgShellSurface->ackConfigure(it[2].toInt()); } } Test::render(surface.data(), sizeChangeRequestedSpy.first().first().toSize(), Qt::red); QVERIFY(geometryChangedSpy.wait()); QCOMPARE(geometryChangedSpy.count(), 1); QVERIFY(c->isFullScreen()); QVERIFY(!c->isDecorated()); QCOMPARE(c->geometry(), QRect(QPoint(0, 0), sizeChangeRequestedSpy.first().first().toSize())); QCOMPARE(c->layer(), ActiveLayer); // swap back to normal if (wlShellSurface) { wlShellSurface->setToplevel(); } if (xdgShellSurface) { xdgShellSurface->setFullscreen(false); } QVERIFY(fullscreenChangedSpy.wait()); QVERIFY(sizeChangeRequestedSpy.wait()); QCOMPARE(sizeChangeRequestedSpy.count(), 2); QCOMPARE(sizeChangeRequestedSpy.last().first().toSize(), QSize(100, 50)); // TODO: should switch to fullscreen once it's updated QVERIFY(!c->isFullScreen()); QCOMPARE(c->layer(), NormalLayer); QCOMPARE(c->isDecorated(), decoMode == ServerSideDecoration::Mode::Server); } void TestShellClient::testFullscreenRestore_data() { QTest::addColumn("type"); QTest::newRow("xdgShellV5") << Test::ShellSurfaceType::XdgShellV5; QTest::newRow("xdgShellV6") << Test::ShellSurfaceType::XdgShellV6; QTest::newRow("xdgShellWmBase") << Test::ShellSurfaceType::XdgShellStable; } void TestShellClient::testFullscreenRestore() { // this test verifies that windows created fullscreen can be later properly restored QScopedPointer surface(Test::createSurface()); QFETCH(Test::ShellSurfaceType, type); XdgShellSurface *xdgShellSurface = Test::createXdgShellSurface(type, surface.data(), surface.data(), Test::CreationSetup::CreateOnly); QSignalSpy configureRequestedSpy(xdgShellSurface, SIGNAL(configureRequested(QSize, KWayland::Client::XdgShellSurface::States, quint32))); // fullscreen the window xdgShellSurface->setFullscreen(true); surface->commit(Surface::CommitFlag::None); configureRequestedSpy.wait(); QCOMPARE(configureRequestedSpy.count(), 1); const auto size = configureRequestedSpy.first()[0].value(); const auto state = configureRequestedSpy.first()[1].value(); QCOMPARE(size, screens()->size(0)); QVERIFY(state & KWayland::Client::XdgShellSurface::State::Fullscreen); xdgShellSurface->ackConfigure(configureRequestedSpy.first()[2].toUInt()); auto c = Test::renderAndWaitForShown(surface.data(), size, Qt::blue); QVERIFY(c); QVERIFY(c->isFullScreen()); configureRequestedSpy.wait(100); QSignalSpy fullscreenChangedSpy(c, &ShellClient::fullScreenChanged); QVERIFY(fullscreenChangedSpy.isValid()); QSignalSpy geometryChangedSpy(c, &ShellClient::geometryChanged); QVERIFY(geometryChangedSpy.isValid()); // swap back to normal configureRequestedSpy.clear(); xdgShellSurface->setFullscreen(false); QVERIFY(fullscreenChangedSpy.wait()); QVERIFY(configureRequestedSpy.wait()); QCOMPARE(configureRequestedSpy.last().first().toSize(), QSize(0, 0)); QVERIFY(!c->isFullScreen()); for (const auto &it: configureRequestedSpy) { xdgShellSurface->ackConfigure(it[2].toUInt()); } Test::render(surface.data(), QSize(100, 50), Qt::red); QVERIFY(geometryChangedSpy.wait()); QCOMPARE(geometryChangedSpy.count(), 1); QVERIFY(!c->isFullScreen()); QCOMPARE(c->geometry().size(), QSize(100, 50)); } void TestShellClient::testUserCanSetFullscreen_data() { QTest::addColumn("type"); QTest::addColumn("expected"); QTest::newRow("wlShell") << Test::ShellSurfaceType::WlShell << false; QTest::newRow("xdgShellV5") << Test::ShellSurfaceType::XdgShellV5 << true; QTest::newRow("xdgShellV6") << Test::ShellSurfaceType::XdgShellV6 << true; QTest::newRow("xdgWmBase") << Test::ShellSurfaceType::XdgShellStable << true; } void TestShellClient::testUserCanSetFullscreen() { QScopedPointer surface(Test::createSurface()); QFETCH(Test::ShellSurfaceType, type); QScopedPointer shellSurface(Test::createShellSurface(type, surface.data())); auto c = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); QVERIFY(c); QVERIFY(c->isActive()); QVERIFY(!c->isFullScreen()); QTEST(c->userCanSetFullScreen(), "expected"); } void TestShellClient::testUserSetFullscreenWlShell() { // wlshell cannot sync fullscreen to the client QScopedPointer surface(Test::createSurface()); QScopedPointer shellSurface(Test::createShellSurface(surface.data())); auto c = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); QVERIFY(c); QVERIFY(c->isActive()); QVERIFY(!c->isFullScreen()); QSignalSpy fullscreenChangedSpy(c, &AbstractClient::fullScreenChanged); QVERIFY(fullscreenChangedSpy.isValid()); c->setFullScreen(true); QCOMPARE(fullscreenChangedSpy.count(), 0); QVERIFY(!c->isFullScreen()); } void TestShellClient::testUserSetFullscreenXdgShell_data() { QTest::addColumn("type"); QTest::newRow("xdgShellV5") << Test::ShellSurfaceType::XdgShellV5; QTest::newRow("xdgShellV6") << Test::ShellSurfaceType::XdgShellV6; QTest::newRow("xdgWmBase") << Test::ShellSurfaceType::XdgShellStable; } void TestShellClient::testUserSetFullscreenXdgShell() { QScopedPointer surface(Test::createSurface()); QFETCH(Test::ShellSurfaceType, type); QScopedPointer shellSurface(Test::createXdgShellSurface( type, surface.data(), surface.data(), Test::CreationSetup::CreateOnly)); QVERIFY(!shellSurface.isNull()); // wait for the initial configure event QSignalSpy configureRequestedSpy(shellSurface.data(), &XdgShellSurface::configureRequested); QVERIFY(configureRequestedSpy.isValid()); surface->commit(Surface::CommitFlag::None); QVERIFY(configureRequestedSpy.wait()); QCOMPARE(configureRequestedSpy.count(), 1); shellSurface->ackConfigure(configureRequestedSpy.last().at(2).value()); auto c = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); QVERIFY(c); QVERIFY(c->isActive()); QVERIFY(!c->isFullScreen()); // The client gets activated, which gets another configure event. Though that's not relevant to the test configureRequestedSpy.wait(10); QSignalSpy fullscreenChangedSpy(c, &AbstractClient::fullScreenChanged); QVERIFY(fullscreenChangedSpy.isValid()); c->setFullScreen(true); QCOMPARE(c->isFullScreen(), true); configureRequestedSpy.clear(); QVERIFY(configureRequestedSpy.wait()); QCOMPARE(configureRequestedSpy.count(), 1); QCOMPARE(configureRequestedSpy.first().at(0).toSize(), screens()->size(0)); const auto states = configureRequestedSpy.first().at(1).value(); QVERIFY(states.testFlag(KWayland::Client::XdgShellSurface::State::Fullscreen)); QVERIFY(states.testFlag(KWayland::Client::XdgShellSurface::State::Activated)); QVERIFY(!states.testFlag(KWayland::Client::XdgShellSurface::State::Maximized)); QVERIFY(!states.testFlag(KWayland::Client::XdgShellSurface::State::Resizing)); QCOMPARE(fullscreenChangedSpy.count(), 1); QVERIFY(c->isFullScreen()); shellSurface->ackConfigure(configureRequestedSpy.first().at(2).value()); // unset fullscreen again c->setFullScreen(false); QCOMPARE(c->isFullScreen(), false); configureRequestedSpy.clear(); QVERIFY(configureRequestedSpy.wait()); QCOMPARE(configureRequestedSpy.count(), 1); QCOMPARE(configureRequestedSpy.first().at(0).toSize(), QSize(100, 50)); QVERIFY(!configureRequestedSpy.first().at(1).value().testFlag(KWayland::Client::XdgShellSurface::State::Fullscreen)); QCOMPARE(fullscreenChangedSpy.count(), 2); QVERIFY(!c->isFullScreen()); } void TestShellClient::testMaximizedToFullscreenWlShell_data() { QTest::addColumn("decoMode"); QTest::newRow("wlShell") << ServerSideDecoration::Mode::Client; QTest::newRow("wlShell - deco") << ServerSideDecoration::Mode::Server; } void TestShellClient::testMaximizedToFullscreenWlShell() { // this test verifies that a window can be properly fullscreened after maximizing QScopedPointer surface(Test::createSurface()); QScopedPointer shellSurface(Test::createShellSurface(surface.data())); QVERIFY(shellSurface.data()); // create deco QScopedPointer deco(Test::waylandServerSideDecoration()->create(surface.data())); QSignalSpy decoSpy(deco.data(), &ServerSideDecoration::modeChanged); QVERIFY(decoSpy.isValid()); QVERIFY(decoSpy.wait()); QFETCH(ServerSideDecoration::Mode, decoMode); deco->requestMode(decoMode); QVERIFY(decoSpy.wait()); QCOMPARE(deco->mode(), decoMode); auto c = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); QVERIFY(c); QVERIFY(c->isActive()); QVERIFY(!c->isFullScreen()); QCOMPARE(c->clientSize(), QSize(100, 50)); QCOMPARE(c->isDecorated(), decoMode == ServerSideDecoration::Mode::Server); QSignalSpy fullscreenChangedSpy(c, &ShellClient::fullScreenChanged); QVERIFY(fullscreenChangedSpy.isValid()); QSignalSpy geometryChangedSpy(c, &ShellClient::geometryChanged); QVERIFY(geometryChangedSpy.isValid()); QSignalSpy sizeChangeRequestedSpy(shellSurface.data(), SIGNAL(sizeChanged(QSize))); QVERIFY(sizeChangeRequestedSpy.isValid()); QSignalSpy configureRequestedSpy(shellSurface.data(), SIGNAL(configureRequested(QSize, KWayland::Client::XdgShellSurface::States, quint32))); shellSurface->setMaximized(); QVERIFY(sizeChangeRequestedSpy.wait()); QCOMPARE(sizeChangeRequestedSpy.count(), 1); Test::render(surface.data(), sizeChangeRequestedSpy.last().first().toSize(), Qt::red); QVERIFY(geometryChangedSpy.wait()); QCOMPARE(c->maximizeMode(), MaximizeFull); QCOMPARE(geometryChangedSpy.isEmpty(), false); geometryChangedSpy.clear(); // fullscreen the window shellSurface->setFullscreen(); QVERIFY(fullscreenChangedSpy.wait()); if (decoMode == ServerSideDecoration::Mode::Server) { QVERIFY(sizeChangeRequestedSpy.wait()); QCOMPARE(sizeChangeRequestedSpy.count(), 2); } QCOMPARE(sizeChangeRequestedSpy.last().first().toSize(), QSize(screens()->size(0))); // TODO: should switch to fullscreen once it's updated QVERIFY(c->isFullScreen()); // render at the new size Test::render(surface.data(), sizeChangeRequestedSpy.last().first().toSize(), Qt::red); QVERIFY(c->isFullScreen()); QVERIFY(!c->isDecorated()); QCOMPARE(c->geometry(), QRect(QPoint(0, 0), sizeChangeRequestedSpy.last().first().toSize())); sizeChangeRequestedSpy.clear(); // swap back to normal shellSurface->setToplevel(); QVERIFY(fullscreenChangedSpy.wait()); if (decoMode == ServerSideDecoration::Mode::Server) { QVERIFY(sizeChangeRequestedSpy.wait()); // TODO: we should test both cases with fixed fake decoration for autotests. const bool hasBorders = Decoration::DecorationBridge::self()->settings()->borderSize() != KDecoration2::BorderSize::None; // fails if we have borders as we don't correctly call setMaximize(false) // but realistically the only toolkits that support the deco also use XDGShell if (hasBorders) { QEXPECT_FAIL("wlShell - deco", "With decoration incorrect geometry requested", Continue); } QCOMPARE(sizeChangeRequestedSpy.last().first().toSize(), QSize(100, 50)); } // TODO: should switch to fullscreen once it's updated QVERIFY(!c->isFullScreen()); QCOMPARE(c->isDecorated(), decoMode == ServerSideDecoration::Mode::Server); } void TestShellClient::testMaximizedToFullscreenXdgShell_data() { QTest::addColumn("type"); QTest::addColumn("decoMode"); QTest::newRow("xdgShellV5") << Test::ShellSurfaceType::XdgShellV5 << ServerSideDecoration::Mode::Client; QTest::newRow("xdgShellV6") << Test::ShellSurfaceType::XdgShellV6 << ServerSideDecoration::Mode::Client; QTest::newRow("xdgShellWmBase") << Test::ShellSurfaceType::XdgShellStable << ServerSideDecoration::Mode::Client; QTest::newRow("xdgShellV5 - deco") << Test::ShellSurfaceType::XdgShellV5 << ServerSideDecoration::Mode::Server; QTest::newRow("xdgShellV6 - deco") << Test::ShellSurfaceType::XdgShellV6 << ServerSideDecoration::Mode::Server; QTest::newRow("xdgShellWmBase - deco") << Test::ShellSurfaceType::XdgShellStable << ServerSideDecoration::Mode::Server; } void TestShellClient::testMaximizedToFullscreenXdgShell() { // this test verifies that a window can be properly fullscreened after maximizing QScopedPointer surface(Test::createSurface()); QFETCH(Test::ShellSurfaceType, type); QScopedPointer shellSurface(Test::createShellSurface(type, surface.data())); XdgShellSurface *xdgShellSurface = nullptr; // fullscreen the window switch (type) { case Test::ShellSurfaceType::XdgShellV5: case Test::ShellSurfaceType::XdgShellV6: case Test::ShellSurfaceType::XdgShellStable: xdgShellSurface = qobject_cast(shellSurface.data()); break; default: Q_UNREACHABLE(); break; } QVERIFY(xdgShellSurface); // create deco QScopedPointer deco(Test::waylandServerSideDecoration()->create(surface.data())); QSignalSpy decoSpy(deco.data(), &ServerSideDecoration::modeChanged); QVERIFY(decoSpy.isValid()); QVERIFY(decoSpy.wait()); QFETCH(ServerSideDecoration::Mode, decoMode); deco->requestMode(decoMode); QVERIFY(decoSpy.wait()); QCOMPARE(deco->mode(), decoMode); auto c = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); QVERIFY(c); QVERIFY(c->isActive()); QVERIFY(!c->isFullScreen()); QCOMPARE(c->clientSize(), QSize(100, 50)); QCOMPARE(c->isDecorated(), decoMode == ServerSideDecoration::Mode::Server); QSignalSpy fullscreenChangedSpy(c, &ShellClient::fullScreenChanged); QVERIFY(fullscreenChangedSpy.isValid()); QSignalSpy geometryChangedSpy(c, &ShellClient::geometryChanged); QVERIFY(geometryChangedSpy.isValid()); QSignalSpy sizeChangeRequestedSpy(shellSurface.data(), SIGNAL(sizeChanged(QSize))); QVERIFY(sizeChangeRequestedSpy.isValid()); QSignalSpy configureRequestedSpy(shellSurface.data(), SIGNAL(configureRequested(QSize, KWayland::Client::XdgShellSurface::States, quint32))); QVERIFY(configureRequestedSpy.isValid()); xdgShellSurface->setMaximized(true); QVERIFY(sizeChangeRequestedSpy.wait()); QCOMPARE(sizeChangeRequestedSpy.count(), 1); for (const auto &it: configureRequestedSpy) { xdgShellSurface->ackConfigure(it[2].toInt()); } Test::render(surface.data(), sizeChangeRequestedSpy.last().first().toSize(), Qt::red); QVERIFY(geometryChangedSpy.wait()); QCOMPARE(c->maximizeMode(), MaximizeFull); QCOMPARE(geometryChangedSpy.isEmpty(), false); geometryChangedSpy.clear(); // fullscreen the window xdgShellSurface->setFullscreen(true); QVERIFY(fullscreenChangedSpy.wait()); if (decoMode == ServerSideDecoration::Mode::Server) { QVERIFY(sizeChangeRequestedSpy.wait()); QCOMPARE(sizeChangeRequestedSpy.count(), 2); } QCOMPARE(sizeChangeRequestedSpy.last().first().toSize(), QSize(screens()->size(0))); // TODO: should switch to fullscreen once it's updated QVERIFY(c->isFullScreen()); for (const auto &it: configureRequestedSpy) { xdgShellSurface->ackConfigure(it[2].toInt()); } // render at the new size Test::render(surface.data(), sizeChangeRequestedSpy.last().first().toSize(), Qt::red); QVERIFY(c->isFullScreen()); QVERIFY(!c->isDecorated()); QCOMPARE(c->geometry(), QRect(QPoint(0, 0), sizeChangeRequestedSpy.last().first().toSize())); sizeChangeRequestedSpy.clear(); // swap back to normal switch (type) { case Test::ShellSurfaceType::XdgShellV5: case Test::ShellSurfaceType::XdgShellV6: case Test::ShellSurfaceType::XdgShellStable: qobject_cast(shellSurface.data())->setFullscreen(false); qobject_cast(shellSurface.data())->setMaximized(false); break; default: Q_UNREACHABLE(); } QVERIFY(fullscreenChangedSpy.wait()); if (decoMode == ServerSideDecoration::Mode::Server) { QVERIFY(sizeChangeRequestedSpy.wait()); // XDG will legitimately get two updates. They might be batched if (xdgShellSurface && sizeChangeRequestedSpy.count() == 1) { QVERIFY(sizeChangeRequestedSpy.wait()); } QCOMPARE(sizeChangeRequestedSpy.last().first().toSize(), QSize(100, 50)); } // TODO: should switch to fullscreen once it's updated QVERIFY(!c->isFullScreen()); QCOMPARE(c->isDecorated(), decoMode == ServerSideDecoration::Mode::Server); } void TestShellClient::testWindowOpensLargerThanScreen_data() { QTest::addColumn("type"); QTest::newRow("wlShell") << Test::ShellSurfaceType::WlShell; QTest::newRow("xdgShellV5") << Test::ShellSurfaceType::XdgShellV5; QTest::newRow("xdgShellV6") << Test::ShellSurfaceType::XdgShellV6; QTest::newRow("xdgWmBase") << Test::ShellSurfaceType::XdgShellStable; } void TestShellClient::testWindowOpensLargerThanScreen() { // this test creates a window which is as large as the screen, but is decorated // the window should get resized to fit into the screen, BUG: 366632 QScopedPointer surface(Test::createSurface()); QFETCH(Test::ShellSurfaceType, type); QScopedPointer shellSurface(Test::createShellSurface(type, surface.data())); QSignalSpy sizeChangeRequestedSpy(shellSurface.data(), SIGNAL(sizeChanged(QSize))); QVERIFY(sizeChangeRequestedSpy.isValid()); // create deco QScopedPointer deco(Test::waylandServerSideDecoration()->create(surface.data())); QSignalSpy decoSpy(deco.data(), &ServerSideDecoration::modeChanged); QVERIFY(decoSpy.isValid()); QVERIFY(decoSpy.wait()); deco->requestMode(ServerSideDecoration::Mode::Server); QVERIFY(decoSpy.wait()); QCOMPARE(deco->mode(), ServerSideDecoration::Mode::Server); auto c = Test::renderAndWaitForShown(surface.data(), screens()->size(0), Qt::blue); QVERIFY(c); QVERIFY(c->isActive()); QCOMPARE(c->clientSize(), screens()->size(0)); QVERIFY(c->isDecorated()); QEXPECT_FAIL("", "BUG 366632", Continue); QVERIFY(sizeChangeRequestedSpy.wait(10)); } void TestShellClient::testHidden_data() { QTest::addColumn("type"); QTest::newRow("wlShell") << Test::ShellSurfaceType::WlShell; QTest::newRow("xdgShellV5") << Test::ShellSurfaceType::XdgShellV5; QTest::newRow("xdgShellV6") << Test::ShellSurfaceType::XdgShellV6; QTest::newRow("xdgWmBase") << Test::ShellSurfaceType::XdgShellStable; } void TestShellClient::testHidden() { // this test verifies that when hiding window it doesn't get shown QScopedPointer surface(Test::createSurface()); QFETCH(Test::ShellSurfaceType, type); QScopedPointer shellSurface(Test::createShellSurface(type, surface.data())); auto c = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); QVERIFY(c); QVERIFY(c->isActive()); QCOMPARE(workspace()->activeClient(), c); QVERIFY(c->wantsInput()); QVERIFY(c->wantsTabFocus()); QVERIFY(c->isShown(true)); c->hideClient(true); QVERIFY(!c->isShown(true)); QVERIFY(!c->isActive()); QVERIFY(c->wantsInput()); QVERIFY(c->wantsTabFocus()); // unhide again c->hideClient(false); QVERIFY(c->isShown(true)); QVERIFY(c->wantsInput()); QVERIFY(c->wantsTabFocus()); //QCOMPARE(workspace()->activeClient(), c); } void TestShellClient::testDesktopFileName() { QIcon::setThemeName(QStringLiteral("breeze")); // this test verifies that desktop file name is passed correctly to the window QScopedPointer surface(Test::createSurface()); // only xdg-shell as ShellSurface misses the setter QScopedPointer shellSurface(qobject_cast(Test::createShellSurface(Test::ShellSurfaceType::XdgShellV5, surface.data()))); shellSurface->setAppId(QByteArrayLiteral("org.kde.foo")); auto c = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); QVERIFY(c); QCOMPARE(c->desktopFileName(), QByteArrayLiteral("org.kde.foo")); QCOMPARE(c->resourceClass(), QByteArrayLiteral("org.kde.foo")); QVERIFY(c->resourceName().startsWith("testShellClient")); // the desktop file does not exist, so icon should be generic Wayland QCOMPARE(c->icon().name(), QStringLiteral("wayland")); QSignalSpy desktopFileNameChangedSpy(c, &AbstractClient::desktopFileNameChanged); QVERIFY(desktopFileNameChangedSpy.isValid()); QSignalSpy iconChangedSpy(c, &ShellClient::iconChanged); QVERIFY(iconChangedSpy.isValid()); shellSurface->setAppId(QByteArrayLiteral("org.kde.bar")); QVERIFY(desktopFileNameChangedSpy.wait()); QCOMPARE(c->desktopFileName(), QByteArrayLiteral("org.kde.bar")); QCOMPARE(c->resourceClass(), QByteArrayLiteral("org.kde.bar")); QVERIFY(c->resourceName().startsWith("testShellClient")); // icon should still be wayland QCOMPARE(c->icon().name(), QStringLiteral("wayland")); QVERIFY(iconChangedSpy.isEmpty()); const QString dfPath = QFINDTESTDATA("data/example.desktop"); shellSurface->setAppId(dfPath.toUtf8()); QVERIFY(desktopFileNameChangedSpy.wait()); QCOMPARE(iconChangedSpy.count(), 1); QCOMPARE(QString::fromUtf8(c->desktopFileName()), dfPath); QCOMPARE(c->icon().name(), QStringLiteral("kwin")); } void TestShellClient::testCaptionSimplified() { // this test verifies that caption is properly trimmed // see BUG 323798 comment #12 QScopedPointer surface(Test::createSurface()); // only done for xdg-shell as ShellSurface misses the setter QScopedPointer shellSurface(qobject_cast(Test::createShellSurface(Test::ShellSurfaceType::XdgShellV5, surface.data()))); const QString origTitle = QString::fromUtf8(QByteArrayLiteral("Was tun, wenn Schüler Autismus haben?\342\200\250\342\200\250\342\200\250 – Marlies Hübner - Mozilla Firefox")); shellSurface->setTitle(origTitle); auto c = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); QVERIFY(c); QVERIFY(c->caption() != origTitle); QCOMPARE(c->caption(), origTitle.simplified()); } void TestShellClient::testCaptionMultipleWindows() { QScopedPointer surface(Test::createSurface()); QScopedPointer shellSurface(qobject_cast(Test::createShellSurface(Test::ShellSurfaceType::XdgShellV5, surface.data()))); shellSurface->setTitle(QStringLiteral("foo")); auto c = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); QVERIFY(c); QCOMPARE(c->caption(), QStringLiteral("foo")); QCOMPARE(c->captionNormal(), QStringLiteral("foo")); QCOMPARE(c->captionSuffix(), QString()); QScopedPointer surface2(Test::createSurface()); QScopedPointer shellSurface2(qobject_cast(Test::createShellSurface(Test::ShellSurfaceType::XdgShellV5, surface2.data()))); shellSurface2->setTitle(QStringLiteral("foo")); auto c2 = Test::renderAndWaitForShown(surface2.data(), QSize(100, 50), Qt::blue); QVERIFY(c2); QCOMPARE(c2->caption(), QStringLiteral("foo <2>")); QCOMPARE(c2->captionNormal(), QStringLiteral("foo")); QCOMPARE(c2->captionSuffix(), QStringLiteral(" <2>")); QScopedPointer surface3(Test::createSurface()); QScopedPointer shellSurface3(qobject_cast(Test::createShellSurface(Test::ShellSurfaceType::XdgShellV5, surface3.data()))); shellSurface3->setTitle(QStringLiteral("foo")); auto c3 = Test::renderAndWaitForShown(surface3.data(), QSize(100, 50), Qt::blue); QVERIFY(c3); QCOMPARE(c3->caption(), QStringLiteral("foo <3>")); QCOMPARE(c3->captionNormal(), QStringLiteral("foo")); QCOMPARE(c3->captionSuffix(), QStringLiteral(" <3>")); QScopedPointer surface4(Test::createSurface()); QScopedPointer shellSurface4(qobject_cast(Test::createShellSurface(Test::ShellSurfaceType::XdgShellV5, surface4.data()))); shellSurface4->setTitle(QStringLiteral("bar")); auto c4 = Test::renderAndWaitForShown(surface4.data(), QSize(100, 50), Qt::blue); QVERIFY(c4); QCOMPARE(c4->caption(), QStringLiteral("bar")); QCOMPARE(c4->captionNormal(), QStringLiteral("bar")); QCOMPARE(c4->captionSuffix(), QString()); QSignalSpy captionChangedSpy(c4, &ShellClient::captionChanged); QVERIFY(captionChangedSpy.isValid()); shellSurface4->setTitle(QStringLiteral("foo")); QVERIFY(captionChangedSpy.wait()); QCOMPARE(captionChangedSpy.count(), 1); QCOMPARE(c4->caption(), QStringLiteral("foo <4>")); QCOMPARE(c4->captionNormal(), QStringLiteral("foo")); QCOMPARE(c4->captionSuffix(), QStringLiteral(" <4>")); } void TestShellClient::testUnresponsiveWindow_data() { QTest::addColumn("shellInterface");//see env selection in qwaylandintegration.cpp QTest::addColumn("socketMode"); //wl-shell ping is not implemented //QTest::newRow("wl-shell display") << "wl-shell" << false; //QTest::newRow("wl-shell socket") << "wl-shell" << true; QTest::newRow("xdgv5 display") << "xdg-shell-v5" << false; QTest::newRow("xdgv5 socket") << "xdg-shell-v5" << true; QTest::newRow("xdgv6 display") << "xdg-shell-v6" << false; QTest::newRow("xdgv6 socket") << "xdg-shell-v6" << true; //TODO add XDG WM Base when Kwin relies on Qt 5.12 } void TestShellClient::testUnresponsiveWindow() { // this test verifies that killWindow properly terminates a process // for this an external binary is launched const QString kill = QFINDTESTDATA(QStringLiteral("kill")); QVERIFY(!kill.isEmpty()); QSignalSpy shellClientAddedSpy(waylandServer(), &WaylandServer::shellClientAdded); QVERIFY(shellClientAddedSpy.isValid()); QScopedPointer process(new QProcess); QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); QFETCH(QString, shellInterface); QFETCH(bool, socketMode); env.insert("QT_WAYLAND_SHELL_INTEGRATION", shellInterface); if (socketMode) { int sx[2]; QVERIFY(socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, sx) >= 0); waylandServer()->display()->createClient(sx[0]); int socket = dup(sx[1]); QVERIFY(socket != -1); env.insert(QStringLiteral("WAYLAND_SOCKET"), QByteArray::number(socket)); env.remove("WAYLAND_DISPLAY"); } else { env.insert("WAYLAND_DISPLAY", s_socketName); } process->setProcessEnvironment(env); process->setProcessChannelMode(QProcess::ForwardedChannels); process->setProgram(kill); QSignalSpy processStartedSpy{process.data(), &QProcess::started}; QVERIFY(processStartedSpy.isValid()); process->start(); QVERIFY(processStartedSpy.wait()); AbstractClient *killClient = nullptr; if (shellClientAddedSpy.isEmpty()) { QVERIFY(shellClientAddedSpy.wait()); } ::kill(process->processId(), SIGUSR1); // send a signal to freeze the process killClient = shellClientAddedSpy.first().first().value(); QVERIFY(killClient); QSignalSpy unresponsiveSpy(killClient, &AbstractClient::unresponsiveChanged); QSignalSpy killedSpy(process.data(), static_cast(&QProcess::finished)); QSignalSpy deletedSpy(killClient, &QObject::destroyed); qint64 startTime = QDateTime::currentMSecsSinceEpoch(); //wait for the process to be frozen QTest::qWait(10); //pretend the user clicked the close button killClient->closeWindow(); //client should not yet be marked unresponsive nor killed QVERIFY(!killClient->unresponsive()); QVERIFY(killedSpy.isEmpty()); QVERIFY(unresponsiveSpy.wait()); //client should be marked unresponsive but not killed auto elapsed1 = QDateTime::currentMSecsSinceEpoch() - startTime; QVERIFY(elapsed1 > 900 && elapsed1 < 1200); //ping timer is 1s, but coarse timers on a test across two processes means we need a fuzzy compare QVERIFY(killClient->unresponsive()); QVERIFY(killedSpy.isEmpty()); QVERIFY(deletedSpy.wait()); if (!socketMode) { //process was killed - because we're across process this could happen in either order QVERIFY(killedSpy.count() || killedSpy.wait()); } auto elapsed2 = QDateTime::currentMSecsSinceEpoch() - startTime; QVERIFY(elapsed2 > 1800); //second ping comes in a second later } void TestShellClient::testX11WindowId_data() { QTest::addColumn("type"); QTest::newRow("wlShell") << Test::ShellSurfaceType::WlShell; QTest::newRow("xdgShellV5") << Test::ShellSurfaceType::XdgShellV5; QTest::newRow("xdgShellV6") << Test::ShellSurfaceType::XdgShellV6; QTest::newRow("xdgWmBase") << Test::ShellSurfaceType::XdgShellStable; } void TestShellClient::testX11WindowId() { QScopedPointer surface(Test::createSurface()); QFETCH(Test::ShellSurfaceType, type); QScopedPointer shellSurface(Test::createShellSurface(type, surface.data())); auto c = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); QVERIFY(c); QVERIFY(c->windowId() != 0); QCOMPARE(c->window(), 0u); } void TestShellClient::testAppMenu() { //register a faux appmenu client QVERIFY (QDBusConnection::sessionBus().registerService("org.kde.kappmenu")); QScopedPointer surface(Test::createSurface()); QScopedPointer shellSurface(Test::createShellSurface(Test::ShellSurfaceType::XdgShellV6, surface.data())); auto c = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); QVERIFY(c); QScopedPointer menu(Test::waylandAppMenuManager()->create(surface.data())); QSignalSpy spy(c, &ShellClient::hasApplicationMenuChanged); menu->setAddress("service.name", "object/path"); spy.wait(); QCOMPARE(c->hasApplicationMenu(), true); QCOMPARE(c->applicationMenuServiceName(), QString("service.name")); QCOMPARE(c->applicationMenuObjectPath(), QString("object/path")); QVERIFY (QDBusConnection::sessionBus().unregisterService("org.kde.kappmenu")); } void TestShellClient::testNoDecorationModeRequested_data() { QTest::addColumn("type"); QTest::newRow("wlShell") << Test::ShellSurfaceType::WlShell; QTest::newRow("xdgShellV6") << Test::ShellSurfaceType::XdgShellV6; QTest::newRow("xdgWmBase") << Test::ShellSurfaceType::XdgShellStable; } void TestShellClient::testNoDecorationModeRequested() { // this test verifies that the decoration follows the default mode if no mode is explicitly requested QScopedPointer surface(Test::createSurface()); QFETCH(Test::ShellSurfaceType, type); QScopedPointer shellSurface(Test::createShellSurface(type, surface.data())); QScopedPointer deco(Test::waylandServerSideDecoration()->create(surface.data())); QSignalSpy decoSpy(deco.data(), &ServerSideDecoration::modeChanged); QVERIFY(decoSpy.isValid()); if (deco->mode() != ServerSideDecoration::Mode::Server) { QVERIFY(decoSpy.wait()); } QCOMPARE(deco->mode(), ServerSideDecoration::Mode::Server); auto c = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); QVERIFY(c); QCOMPARE(c->noBorder(), false); QCOMPARE(c->isDecorated(), true); } void TestShellClient::testSendClientWithTransientToDesktop_data() { QTest::addColumn("type"); QTest::newRow("xdgShellV5") << Test::ShellSurfaceType::XdgShellV5; QTest::newRow("xdgShellV6") << Test::ShellSurfaceType::XdgShellV6; QTest::newRow("xdgWmBase") << Test::ShellSurfaceType::XdgShellStable; } void TestShellClient::testSendClientWithTransientToDesktop() { // this test verifies that when sending a client to a desktop all transients are also send to that desktop VirtualDesktopManager::self()->setCount(2); QScopedPointer surface{Test::createSurface()}; QFETCH(Test::ShellSurfaceType, type); QScopedPointer shellSurface{qobject_cast(Test::createShellSurface(type, surface.data()))}; auto c = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); QVERIFY(c); // let's create a transient window QScopedPointer transientSurface{Test::createSurface()}; QScopedPointer transientShellSurface{qobject_cast(Test::createShellSurface(type, transientSurface.data()))}; transientShellSurface->setTransientFor(shellSurface.data()); auto transient = Test::renderAndWaitForShown(transientSurface.data(), QSize(100, 50), Qt::blue); QVERIFY(transient); QCOMPARE(workspace()->activeClient(), transient); QCOMPARE(transient->transientFor(), c); QVERIFY(c->transients().contains(transient)); QCOMPARE(c->desktop(), 1); QVERIFY(!c->isOnAllDesktops()); QCOMPARE(transient->desktop(), 1); QVERIFY(!transient->isOnAllDesktops()); workspace()->slotWindowToDesktop(2); QCOMPARE(c->desktop(), 1); QCOMPARE(transient->desktop(), 2); // activate c workspace()->activateClient(c); QCOMPARE(workspace()->activeClient(), c); QVERIFY(c->isActive()); // and send it to the desktop it's already on QCOMPARE(c->desktop(), 1); QCOMPARE(transient->desktop(), 2); workspace()->slotWindowToDesktop(1); // which should move the transient back to the desktop QCOMPARE(c->desktop(), 1); QCOMPARE(transient->desktop(), 1); } void TestShellClient::testMinimizeWindowWithTransients_data() { QTest::addColumn("type"); QTest::newRow("xdgShellV5") << Test::ShellSurfaceType::XdgShellV5; QTest::newRow("xdgShellV6") << Test::ShellSurfaceType::XdgShellV6; QTest::newRow("xdgWmBase") << Test::ShellSurfaceType::XdgShellStable; } void TestShellClient::testMinimizeWindowWithTransients() { // this test verifies that when minimizing/unminimizing a window all its // transients will be minimized/unminimized as well // create the main window QScopedPointer surface(Test::createSurface()); QFETCH(Test::ShellSurfaceType, type); QScopedPointer shellSurface(qobject_cast( Test::createShellSurface(type, surface.data()))); auto c = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); QVERIFY(c); QVERIFY(!c->isMinimized()); // create a transient window QScopedPointer transientSurface(Test::createSurface()); QScopedPointer transientShellSurface(qobject_cast( Test::createShellSurface(type, transientSurface.data()))); transientShellSurface->setTransientFor(shellSurface.data()); auto transient = Test::renderAndWaitForShown(transientSurface.data(), QSize(100, 50), Qt::red); QVERIFY(transient); QVERIFY(!transient->isMinimized()); QCOMPARE(transient->transientFor(), c); QVERIFY(c->hasTransient(transient, false)); // minimize the main window, the transient should be minimized as well c->minimize(); QVERIFY(c->isMinimized()); QVERIFY(transient->isMinimized()); // unminimize the main window, the transient should be unminimized as well c->unminimize(); QVERIFY(!c->isMinimized()); QVERIFY(!transient->isMinimized()); } void TestShellClient::testXdgDecoration_data() { QTest::addColumn("requestedMode"); QTest::addColumn("expectedMode"); QTest::newRow("client side requested") << XdgDecoration::Mode::ClientSide << XdgDecoration::Mode::ClientSide; QTest::newRow("server side requested") << XdgDecoration::Mode::ServerSide << XdgDecoration::Mode::ServerSide; } void TestShellClient::testXdgDecoration() { QScopedPointer surface(Test::createSurface()); QScopedPointer shellSurface(Test::createXdgShellStableSurface(surface.data())); QScopedPointer deco(Test::xdgDecorationManager()->getToplevelDecoration(shellSurface.data())); QSignalSpy decorationConfiguredSpy(deco.data(), &XdgDecoration::modeChanged); QSignalSpy configureRequestedSpy(shellSurface.data(), &XdgShellSurface::configureRequested); QFETCH(KWayland::Client::XdgDecoration::Mode, requestedMode); QFETCH(KWayland::Client::XdgDecoration::Mode, expectedMode); //request a mode deco->setMode(requestedMode); //kwin will send a configure decorationConfiguredSpy.wait(); configureRequestedSpy.wait(); QCOMPARE(decorationConfiguredSpy.count(), 1); QCOMPARE(decorationConfiguredSpy.first()[0].value(), expectedMode); QVERIFY(configureRequestedSpy.count() > 0); shellSurface->ackConfigure(configureRequestedSpy.last()[2].toInt()); auto c = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); QCOMPARE(c->userCanSetNoBorder(), expectedMode == XdgDecoration::Mode::ServerSide); QCOMPARE(c->isDecorated(), expectedMode == XdgDecoration::Mode::ServerSide); } void TestShellClient::testXdgNeverCommitted() { //check we don't crash if we create a shell object but delete the ShellClient before committing it QScopedPointer surface(Test::createSurface()); QScopedPointer shellSurface(Test::createXdgShellStableSurface(surface.data(), nullptr, Test::CreationSetup::CreateOnly)); } void TestShellClient::testXdgInitialState() { QScopedPointer surface(Test::createSurface()); QScopedPointer shellSurface(Test::createXdgShellStableSurface(surface.data(), nullptr, Test::CreationSetup::CreateOnly)); QSignalSpy configureRequestedSpy(shellSurface.data(), &XdgShellSurface::configureRequested); surface->commit(Surface::CommitFlag::None); configureRequestedSpy.wait(); QCOMPARE(configureRequestedSpy.count(), 1); const auto size = configureRequestedSpy.first()[0].value(); QCOMPARE(size, QSize(0, 0)); //client should chose it's preferred size shellSurface->ackConfigure(configureRequestedSpy.first()[2].toUInt()); auto c = Test::renderAndWaitForShown(surface.data(), QSize(200,100), Qt::blue); QCOMPARE(c->size(), QSize(200, 100)); } void TestShellClient::testXdgInitiallyMaximised() { QScopedPointer surface(Test::createSurface()); QScopedPointer shellSurface(Test::createXdgShellStableSurface(surface.data(), nullptr, Test::CreationSetup::CreateOnly)); QSignalSpy configureRequestedSpy(shellSurface.data(), &XdgShellSurface::configureRequested); shellSurface->setMaximized(true); surface->commit(Surface::CommitFlag::None); configureRequestedSpy.wait(); QCOMPARE(configureRequestedSpy.count(), 1); const auto size = configureRequestedSpy.first()[0].value(); const auto state = configureRequestedSpy.first()[1].value(); QCOMPARE(size, QSize(1280, 1024)); QVERIFY(state & KWayland::Client::XdgShellSurface::State::Maximized); shellSurface->ackConfigure(configureRequestedSpy.first()[2].toUInt()); auto c = Test::renderAndWaitForShown(surface.data(), size, Qt::blue); QCOMPARE(c->maximizeMode(), MaximizeFull); QCOMPARE(c->size(), QSize(1280, 1024)); } void TestShellClient::testXdgInitiallyMinimized() { QScopedPointer surface(Test::createSurface()); QScopedPointer shellSurface(Test::createXdgShellStableSurface(surface.data(), nullptr, Test::CreationSetup::CreateOnly)); QSignalSpy configureRequestedSpy(shellSurface.data(), &XdgShellSurface::configureRequested); shellSurface->requestMinimize(); surface->commit(Surface::CommitFlag::None); configureRequestedSpy.wait(); QCOMPARE(configureRequestedSpy.count(), 1); const auto size = configureRequestedSpy.first()[0].value(); const auto state = configureRequestedSpy.first()[1].value(); QCOMPARE(size, QSize(0, 0)); QCOMPARE(state, 0); shellSurface->ackConfigure(configureRequestedSpy.first()[2].toUInt()); QEXPECT_FAIL("", "Client created in a minimised state is not exposed to kwin bug 404838", Abort); auto c = Test::renderAndWaitForShown(surface.data(), size, Qt::blue, QImage::Format_ARGB32, 10); QVERIFY(c); QVERIFY(c->isMinimized()); } void TestShellClient::testXdgWindowGeometry() { QScopedPointer surface(Test::createSurface()); QScopedPointer shellSurface(Test::createXdgShellStableSurface(surface.data(), nullptr, Test::CreationSetup::CreateOnly)); QSignalSpy configureRequestedSpy(shellSurface.data(), &XdgShellSurface::configureRequested); surface->commit(Surface::CommitFlag::None); configureRequestedSpy.wait(); shellSurface->ackConfigure(configureRequestedSpy.first()[2].toUInt()); // Create a 160x140 window in with a margin of 10(left), 20(top), 30(right), 40(bottom). Giving a total buffer size 200, 100 shellSurface->setWindowGeometry(QRect(10, 20, 160, 40)); auto c = Test::renderAndWaitForShown(surface.data(), QSize(200,100), Qt::blue); configureRequestedSpy.wait(); //window activated after being shown QSignalSpy geometryChangedSpy(c, &ShellClient::geometryChanged); // resize to 300,200 in kwin terms c->setGeometry(QRect(100, 100, 300, 200)); QVERIFY(configureRequestedSpy.wait()); // requested geometry should not include the margins we had above const QSize requestedSize = configureRequestedSpy.last()[0].value(); QCOMPARE(requestedSize, QSize(300, 200) - QSize(10 + 30, 20 + 40)); shellSurface->ackConfigure(configureRequestedSpy.last()[2].toUInt()); Test::render(surface.data(), requestedSize + QSize(10 + 30, 20 + 40), Qt::blue); geometryChangedSpy.wait(); // kwin's concept of geometry should remain the same QCOMPARE(c->geometry(), QRect(100, 100, 300, 200)); c->setFullScreen(true); configureRequestedSpy.wait(); // when full screen, the window geometry (i.e without margins) should fill the screen const QSize requestedFullScreenSize = configureRequestedSpy.last()[0].value(); QCOMPARE(requestedFullScreenSize, QSize(1280, 1024)); } WAYLANDTEST_MAIN(TestShellClient) #include "shell_client_test.moc" diff --git a/client.cpp b/client.cpp index 17799253d..bd2f0aeba 100644 --- a/client.cpp +++ b/client.cpp @@ -1,2173 +1,2174 @@ /******************************************************************** 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 // XLib #include #include #include // system #include -#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) , m_motif(atoms->motif_wm_hints) , blocks_compositing(false) , shadeHoverTimer(NULL) , m_colormap(XCB_COLORMAP_NONE) , in_group(NULL) , ping_timer(NULL) , m_killHelperPID(0) , m_pingTimestamp(XCB_TIME_CURRENT_TIME) , m_userTime(XCB_TIME_CURRENT_TIME) // Not known yet , allowed_actions(0) , 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.lastTimestamp = xTime(); syncRequest.isPending = false; // Set the initial mapping state mapping_state = Withdrawn; info = NULL; 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); } }); connect(this, &Client::tabGroupChanged, this, [this] { auto group = tabGroup(); if (group) { unsigned long data[] = {qHash(group)}; //->id(); m_client.changeProperty(atoms->kde_net_wm_tab_group, XCB_ATOM_CARDINAL, 32, 1, data); } else m_client.deleteProperty(atoms->kde_net_wm_tab_group); }); // 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; } //SWrapper::Client::clientRelease(this); if (syncRequest.alarm != XCB_NONE) xcb_sync_destroy_alarm(connection(), syncRequest.alarm); assert(!isMoveResize()); assert(m_client == XCB_WINDOW_NONE); assert(m_wrapper == XCB_WINDOW_NONE); //assert( frameId() == None ); 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) { assert(!deleting); deleting = true; #ifdef KWIN_BUILD_TABBOX TabBox::TabBox *tabBox = TabBox::TabBox::self(); if (tabBox->isDisplayed() && tabBox->currentClient() == this) { tabBox->nextPrev(true); } #endif destroyWindowManagementInterface(); Deleted* del = NULL; 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(WithdrawnState); 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(0, info->state()); // Reset all state flags } else untab(); 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(); //frame = None; 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() { assert(!deleting); deleting = true; #ifdef KWIN_BUILD_TABBOX TabBox::TabBox *tabBox = TabBox::TabBox::self(); if (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(); //frame = None; 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() && !tabGroup(); } 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); // Update states of all other windows in this group if (tabGroup()) tabGroup()->updateStates(this, TabGroup::Minimized); } 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 // Update states of all other windows in this group if (tabGroup()) tabGroup()->updateStates(this, TabGroup::Shaded); if (was_shade == isShade()) { // Decoration may want to update after e.g. hover-shade changes emit shadeChanged(); return; // No real change in shaded state } 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(IconicState); 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; // 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; } m_wrapper.map(); m_client.map(); exportMappingState(NormalState); if (isActive()) workspace()->requestFocus(this); } info->setState(isShade() ? NET::Shaded : NET::States(0), NET::Shaded); info->setState(isShown(false) ? NET::States(0) : NET::Hidden, NET::Hidden); discardWindowPixmap(); updateVisibility(); updateAllowedActions(); updateWindowRules(Rules::Shade); emit shadeChanged(); } void Client::shadeHover() { setShade(ShadeHover); cancelShadeHoverTimer(); } void Client::shadeUnhover() { if (!tabGroup() || tabGroup()->current() == this || tabGroup()->current()->shadeMode() == ShadeNormal) setShade(ShadeNormal); cancelShadeHoverTimer(); } void Client::cancelShadeHoverTimer() { delete shadeHoverTimer; shadeHoverTimer = 0; } 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 && isCurrentTab()) { info->setState(NET::Hidden, NET::Hidden); setSkipTaskbar(true); // Also hide from taskbar if (compositing() && options->hiddenPreviews() == HiddenPreviewsAlways) internalKeep(); else internalHide(); return; } if (isCurrentTab()) setSkipTaskbar(originalSkipTaskbar()); // Reset from 'hidden' if (isMinimized()) { info->setState(NET::Hidden, NET::Hidden); if (compositing() && options->hiddenPreviews() == HiddenPreviewsAlways) internalKeep(); else internalHide(); return; } info->setState(0, 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) { assert(m_client != XCB_WINDOW_NONE); assert(!deleting || s == WithdrawnState); if (s == WithdrawnState) { m_client.deleteProperty(atoms->wm_state); return; } assert(s == NormalState || s == IconicState); 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() { 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(NormalState); } else exportMappingState(IconicState); 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(IconicState); } /** * 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); } } 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) 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; 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(0), NET::SkipTaskbar); } void Client::doSetSkipPager() { info->setState(skipPager() ? NET::SkipPager : NET::States(0), NET::SkipPager); } void Client::doSetSkipSwitcher() { info->setState(skipSwitcher() ? NET::SkipSwitcher : NET::States(0), NET::SkipSwitcher); } void Client::doSetDesktop(int desktop, int was_desk) { Q_UNUSED(desktop) Q_UNUSED(was_desk) updateVisibility(); // Update states of all other windows in this group if (tabGroup()) tabGroup()->updateStates(this, TabGroup::Desktop); } /** * 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.toAscii(); 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); // Update states of all other windows in this group if (tabGroup()) tabGroup()->updateStates(this, TabGroup::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 (options->isInactiveTabsSkipTaskbar()) setSkipTaskbar(hidden); // TODO: Causes reshuffle of the taskbar 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 if (isCurrentTab()) 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); } } void Client::updateAllowedActions(bool force) { if (!isManaged() && !force) return; NET::Actions old_allowed_actions = NET::Actions(allowed_actions); allowed_actions = 0; 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); } void Client::updateTabGroupStates(TabGroup::States states) { if (auto t = tabGroup()) { t->updateStates(this, states); } } 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/colorcorrection/suncalc.cpp b/colorcorrection/suncalc.cpp index 64cc2cfb0..217d788b6 100644 --- a/colorcorrection/suncalc.cpp +++ b/colorcorrection/suncalc.cpp @@ -1,163 +1,163 @@ /******************************************************************** 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 "suncalc.h" #include "constants.h" #include -#include +#include namespace KWin { namespace ColorCorrect { #define TWILIGHT_NAUT -12.0 #define TWILIGHT_CIVIL -6.0 #define SUN_RISE_SET -0.833 #define SUN_HIGH 2.0 QPair calculateSunTimings(QDate prompt, double latitude, double longitude, bool morning) { // calculations based on https://aa.quae.nl/en/reken/zonpositie.html // accuracy: +/- 5min // positioning const double rad = M_PI / 180.; const double earthObliquity = 23.4397; // epsilon const double lat = latitude; // phi const double lng = -longitude; // lw // times const double juPrompt = prompt.toJulianDay(); // J const double ju2000 = 2451545.; // J2000 // geometry auto mod360 = [](double number) -> double { return std::fmod(number, 360.); }; auto sin = [&rad](double angle) -> double { return std::sin(angle * rad); }; auto cos = [&rad](double angle) -> double { return std::cos(angle * rad); }; auto asin = [&rad](double val) -> double { return std::asin(val) / rad; }; auto acos = [&rad](double val) -> double { return std::acos(val) / rad; }; auto anomaly = [&](const double date) -> double { // M return mod360(357.5291 + 0.98560028 * (date - ju2000)); }; auto center = [&sin](double anomaly) -> double { // C return 1.9148 * sin(anomaly) + 0.02 * sin(2 * anomaly) + 0.0003 * sin(3 * anomaly); }; auto ecliptLngMean = [](double anom) -> double { // Mean ecliptical longitude L_sun = Mean Anomaly + Perihelion + 180° return anom + 282.9372; // anom + 102.9372 + 180° }; auto ecliptLng = [&](double anom) -> double { // lambda = L_sun + C return ecliptLngMean(anom) + center(anom); }; auto declination = [&](const double date) -> double { // delta const double anom = anomaly(date); const double eclLng = ecliptLng(anom); return mod360(asin(sin(earthObliquity) * sin(eclLng))); }; // sun hour angle at specific angle auto hourAngle = [&](const double date, double angle) -> double { // H_t const double decl = declination(date); const double ret0 = (sin(angle) - sin(lat) * sin(decl)) / (cos(lat) * cos(decl)); double ret = mod360(acos( ret0 )); if (180. < ret) { ret = ret - 360.; } return ret; }; /* * Sun positions */ // transit is at noon auto getTransit = [&](const double date) -> double { // Jtransit const double juMeanSolTime = juPrompt - ju2000 - 0.0009 - lng / 360.; // n_x = J - J_2000 - J_0 - l_w / 360° const double juTrEstimate = date + qRound64(juMeanSolTime) - juMeanSolTime; // J_x = J + n - n_x const double anom = anomaly(juTrEstimate); // M const double eclLngM = ecliptLngMean(anom); // L_sun return juTrEstimate + 0.0053 * sin(anom) - 0.0068 * sin(2 * eclLngM); }; auto getSunMorning = [&hourAngle](const double angle, const double transit) -> double { return transit - hourAngle(transit, angle) / 360.; }; auto getSunEvening = [&hourAngle](const double angle, const double transit) -> double { return transit + hourAngle(transit, angle) / 360.; }; /* * Begin calculations */ // noon - sun at the highest point const double juNoon = getTransit(juPrompt); double begin, end; if (morning) { begin = getSunMorning(TWILIGHT_CIVIL, juNoon); end = getSunMorning(SUN_HIGH, juNoon); } else { begin = getSunEvening(SUN_HIGH, juNoon); end = getSunEvening(TWILIGHT_CIVIL, juNoon); } // transform to QDateTime begin += 0.5; end += 0.5; QTime timeBegin, timeEnd; if (std::isnan(begin)) { timeBegin = QTime(); } else { double timePart = begin - (int)begin; timeBegin = QTime::fromMSecsSinceStartOfDay((int)( timePart * MSC_DAY )); } if (std::isnan(end)) { timeEnd = QTime(); } else { double timePart = end - (int)end; timeEnd = QTime::fromMSecsSinceStartOfDay((int)( timePart * MSC_DAY )); } return QPair (timeBegin, timeEnd); } } } diff --git a/effects.cpp b/effects.cpp index d26951007..916b71917 100644 --- a/effects.cpp +++ b/effects.cpp @@ -1,2381 +1,2382 @@ /******************************************************************** 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 #include "composite.h" #include "xcbutils.h" #include "platform.h" #include "shell_client.h" #include "wayland_server.h" #include "decorations/decorationbridge.h" #include +#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)); 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) , 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); // 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; } 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); if (!c->tabGroup()) // the "window" has already been there 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) 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) 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) return false; if (!doGrabKeyboard()) { return false; } keyboard_grab_effect = effect; return true; } bool EffectsHandlerImpl::doGrabKeyboard() { return true; } void EffectsHandlerImpl::ungrabKeyboard() { assert(keyboard_grab_effect != NULL); doUngrabKeyboard(); keyboard_grab_effect = NULL; } void EffectsHandlerImpl::doUngrabKeyboard() { } void EffectsHandlerImpl::grabbedKeyboardEvent(QKeyEvent* e) { if (keyboard_grab_effect != NULL) 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(quint32 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(quint32 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(quint32 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; } 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; } 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; } 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; } 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(); #endif return QList< int >(); } int EffectsHandlerImpl::currentTabBoxDesktop() const { #ifdef KWIN_BUILD_TABBOX return TabBox::TabBox::self()->currentDesktop(); #endif return -1; } EffectWindow* EffectsHandlerImpl::currentTabBoxWindow() const { #ifdef KWIN_BUILD_TABBOX if (auto c = TabBox::TabBox::self()->currentClient()) return c->effectWindow(); #endif return NULL; } 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, isCurrentTab, isCurrentTab, 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 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, QWeakPointer(static_cast(w))); } else { m_thumbnails.insert(item, QWeakPointer()); } } 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) , EffectFrame() , m_style(style) , m_static(staticSize) , m_point(position) , m_alignment(alignment) , m_shader(NULL) , 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; 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/cube/cube.cpp b/effects/cube/cube.cpp index 986c37304..576a000e3 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 #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) , reflection(true) , desktopChangedWhileRotating(false) , paintCaps(true) , wallpaper(NULL) , texturedCaps(true) , capTexture(NULL) , 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) , zOrderingFactor(0.0f) , mAddedHeightCoeff1(0.0f) , mAddedHeightCoeff2(0.0f) , m_cubeCapBuffer(NULL) , 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_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; delete capTexture; capTexture = NULL; 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; 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); } 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); } 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); } 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); delete m_cubeCapBuffer; m_cubeCapBuffer = NULL; 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; foreach (const QRect & paintRect, paint.rects()) { 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); 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/cubeslide/cubeslide.cpp b/effects/cubeslide/cubeslide.cpp index f4725e291..d3e86eb19 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 +#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->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->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/desktopgrid/desktopgrid.cpp b/effects/desktopgrid/desktopgrid.cpp index 2430544fc..b162f22dc 100644 --- a/effects/desktopgrid/desktopgrid.cpp +++ b/effects/desktopgrid/desktopgrid.cpp @@ -1,1522 +1,1522 @@ /******************************************************************** 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 +#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) , windowMoveDiff() , gridSize() , orientation(Qt::Horizontal) , activeCell(1, 1) , scale() , unscaledBorder() , scaledSize() , scaledOffset() , m_proxy(0) , 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; } 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; 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 && (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]); } 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) isDesktop = w->isDesktop(); if (isDesktop) m_originalMovingDesktop = posToDesktop(me->pos()); else m_originalMovingDesktop = 0; if (w != NULL && !w->isDesktop() && (w->isMovable() || w->isMovableAcrossScreens() || isUsingPresentWindows())) { // Prepare it for moving windowMoveDiff = w->pos() - unscalePos(me->pos(), NULL); windowMove = w; effects->setElevatedWindow(windowMove, true); } } else if ((me->buttons() == Qt::MidButton || me->buttons() == Qt::RightButton) && windowMove == NULL) { EffectWindow* w = windowAt(me->pos()); if (w && w->isDesktop()) { w = nullptr; } if (w != NULL) { 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; } wasWindowMove = false; wasWindowCopy = false; wasDesktopMove = false; } } void DesktopGridEffect::grabbedKeyboardEvent(QKeyEvent* e) { if (timeline.currentValue() != 1) // Block user input during animations return; if (windowMove != NULL) 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 (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; 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; } 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); if (isUsingPresentWindows()) { while (!m_managers.isEmpty()) { m_managers.first().unmanageAll(); m_managers.removeFirst(); } m_proxy = 0; } } 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); } // 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->isCurrentTab()) { 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/fallapart/fallapart.cpp b/effects/fallapart/fallapart.cpp index 20d44f8d5..73f89d34b 100644 --- a/effects/fallapart/fallapart.cpp +++ b/effects/fallapart/fallapart.cpp @@ -1,203 +1,204 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2007 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 "fallapart.h" // KConfigSkeleton #include "fallapartconfig.h" -#include -#include + +#include +#include namespace KWin { bool FallApartEffect::supported() { return effects->isOpenGLCompositing() && effects->animationsSupported(); } FallApartEffect::FallApartEffect() { initConfig(); reconfigure(ReconfigureAll); connect(effects, &EffectsHandler::windowClosed, this, &FallApartEffect::slotWindowClosed); connect(effects, &EffectsHandler::windowDeleted, this, &FallApartEffect::slotWindowDeleted); connect(effects, &EffectsHandler::windowDataChanged, this, &FallApartEffect::slotWindowDataChanged); } void FallApartEffect::reconfigure(ReconfigureFlags) { FallApartConfig::self()->read(); blockSize = FallApartConfig::blockSize(); } void FallApartEffect::prePaintScreen(ScreenPrePaintData& data, int time) { if (!windows.isEmpty()) data.mask |= PAINT_SCREEN_WITH_TRANSFORMED_WINDOWS; effects->prePaintScreen(data, time); } void FallApartEffect::prePaintWindow(EffectWindow* w, WindowPrePaintData& data, int time) { if (windows.contains(w) && isRealWindow(w)) { if (windows[ w ] < 1) { windows[ w ] += time / animationTime(1000.); data.setTransformed(); w->enablePainting(EffectWindow::PAINT_DISABLED_BY_DELETE); // Request the window to be divided into cells data.quads = data.quads.makeGrid(blockSize); } else { windows.remove(w); w->unrefWindow(); } } effects->prePaintWindow(w, data, time); } void FallApartEffect::paintWindow(EffectWindow* w, int mask, QRegion region, WindowPaintData& data) { if (windows.contains(w) && isRealWindow(w)) { const qreal t = windows[w]; WindowQuadList new_quads; int cnt = 0; foreach (WindowQuad quad, data.quads) { // krazy:exclude=foreach // make fragments move in various directions, based on where // they are (left pieces generally move to the left, etc.) QPointF p1(quad[ 0 ].x(), quad[ 0 ].y()); double xdiff = 0; if (p1.x() < w->width() / 2) xdiff = -(w->width() / 2 - p1.x()) / w->width() * 100; if (p1.x() > w->width() / 2) xdiff = (p1.x() - w->width() / 2) / w->width() * 100; double ydiff = 0; if (p1.y() < w->height() / 2) ydiff = -(w->height() / 2 - p1.y()) / w->height() * 100; if (p1.y() > w->height() / 2) ydiff = (p1.y() - w->height() / 2) / w->height() * 100; double modif = t * t * 64; srandom(cnt); // change direction randomly but consistently xdiff += (rand() % 21 - 10); ydiff += (rand() % 21 - 10); for (int j = 0; j < 4; ++j) { quad[ j ].move(quad[ j ].x() + xdiff * modif, quad[ j ].y() + ydiff * modif); } // also make the fragments rotate around their center QPointF center((quad[ 0 ].x() + quad[ 1 ].x() + quad[ 2 ].x() + quad[ 3 ].x()) / 4, (quad[ 0 ].y() + quad[ 1 ].y() + quad[ 2 ].y() + quad[ 3 ].y()) / 4); double adiff = (rand() % 720 - 360) / 360. * 2 * M_PI; // spin randomly for (int j = 0; j < 4; ++j) { double x = quad[ j ].x() - center.x(); double y = quad[ j ].y() - center.y(); double angle = atan2(y, x); angle += windows[ w ] * adiff; double dist = sqrt(x * x + y * y); x = dist * cos(angle); y = dist * sin(angle); quad[ j ].move(center.x() + x, center.y() + y); } new_quads.append(quad); ++cnt; } data.quads = new_quads; data.multiplyOpacity(interpolate(1.0, 0.0, t)); } effects->paintWindow(w, mask, region, data); } void FallApartEffect::postPaintScreen() { if (!windows.isEmpty()) effects->addRepaintFull(); effects->postPaintScreen(); } bool FallApartEffect::isRealWindow(EffectWindow* w) { // TODO: isSpecialWindow is rather generic, maybe tell windowtypes separately? /* qCDebug(KWINEFFECTS) << "--" << w->caption() << "--------------------------------"; qCDebug(KWINEFFECTS) << "Tooltip:" << w->isTooltip(); qCDebug(KWINEFFECTS) << "Toolbar:" << w->isToolbar(); qCDebug(KWINEFFECTS) << "Desktop:" << w->isDesktop(); qCDebug(KWINEFFECTS) << "Special:" << w->isSpecialWindow(); qCDebug(KWINEFFECTS) << "TopMenu:" << w->isTopMenu(); qCDebug(KWINEFFECTS) << "Notific:" << w->isNotification(); qCDebug(KWINEFFECTS) << "Splash:" << w->isSplash(); qCDebug(KWINEFFECTS) << "Normal:" << w->isNormalWindow(); */ if (w->isPopupWindow()) { return false; } if (w->isX11Client() && !w->isManaged()) { return false; } if (!w->isNormalWindow()) return false; return true; } void FallApartEffect::slotWindowClosed(EffectWindow* c) { if (!isRealWindow(c)) return; if (!c->isVisible()) return; const void* e = c->data(WindowClosedGrabRole).value(); if (e && e != this) return; c->setData(WindowClosedGrabRole, QVariant::fromValue(static_cast(this))); windows[ c ] = 0; c->refWindow(); } void FallApartEffect::slotWindowDeleted(EffectWindow* c) { windows.remove(c); } void FallApartEffect::slotWindowDataChanged(EffectWindow* w, int role) { if (role != WindowClosedGrabRole) { return; } if (w->data(role).value() == this) { return; } auto it = windows.find(w); if (it == windows.end()) { return; } it.key()->unrefWindow(); windows.erase(it); } bool FallApartEffect::isActive() const { return !windows.isEmpty(); } } // namespace diff --git a/effects/mouseclick/mouseclick.cpp b/effects/mouseclick/mouseclick.cpp index 4cf5788a4..baa53f591 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 +#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; 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; } 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->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/mousemark/mousemark.cpp b/effects/mousemark/mousemark.cpp index e969d16bf..a205ea13c 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 +#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->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->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/presentwindows/presentwindows.cpp b/effects/presentwindows/presentwindows.cpp index b37c771e4..31f19e8c0 100644 --- a/effects/presentwindows/presentwindows.cpp +++ b/effects/presentwindows/presentwindows.cpp @@ -1,2075 +1,2076 @@ /******************************************************************** 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 #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_needInitialSelection(false) , m_highlightedWindow(NULL) , m_filterFrame(NULL) , m_closeView(NULL) , m_closeWindow(NULL) , 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->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); if (winData->visible) w->enablePainting(EffectWindow::PAINT_DISABLED_BY_TAB_GROUP); // 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; } } 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; 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; 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); 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(quint32 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(quint32 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(quint32 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); 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 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]; 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; } } } 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()); qSort(windowlist); // 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. qSort(windowlist); 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_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() && (w->isCurrentTab() || winData->visible); } 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; } } 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->isCurrentTab()) 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))) 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; 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) next = e; else { QRectF nArea = m_motionManager.transformedGeometry(next); if (eArea.x() < nArea.x()) next = e; } } } if (next == NULL) { 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; 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) next = e; else { QRectF nArea = m_motionManager.transformedGeometry(next); if (eArea.x() + eArea.width() > nArea.x() + nArea.width()) next = e; } } } if (next == NULL) { 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; 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) next = e; else { QRectF nArea = m_motionManager.transformedGeometry(next); if (eArea.y() < nArea.y()) next = e; } } } if (next == NULL) { 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; 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) next = e; else { QRectF nArea = m_motionManager.transformedGeometry(next); if (eArea.y() + eArea.height() > nArea.y() + nArea.height()) next = e; } } } if (next == NULL) { 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; 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) { 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/showfps/showfps.cpp b/effects/showfps/showfps.cpp index b17aa2342..8224565e7 100644 --- a/effects/showfps/showfps.cpp +++ b/effects/showfps/showfps.cpp @@ -1,547 +1,549 @@ /******************************************************************** 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 +#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; foreach (const QRect & r, r2.rects()) 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->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->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->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->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->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->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/thumbnailaside/thumbnailaside.h b/effects/thumbnailaside/thumbnailaside.h index bfe4931cd..ac38ea5b2 100644 --- a/effects/thumbnailaside/thumbnailaside.h +++ b/effects/thumbnailaside/thumbnailaside.h @@ -1,91 +1,91 @@ /******************************************************************** 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 . *********************************************************************/ /* Testing of painting a window more than once. */ #ifndef KWIN_THUMBNAILASIDE_H #define KWIN_THUMBNAILASIDE_H #include -#include +#include namespace KWin { class ThumbnailAsideEffect : public Effect { Q_OBJECT Q_PROPERTY(int maxWidth READ configuredMaxWidth) Q_PROPERTY(int spacing READ configuredSpacing) Q_PROPERTY(qreal opacity READ configuredOpacity) Q_PROPERTY(int screen READ configuredScreen) public: ThumbnailAsideEffect(); virtual void reconfigure(ReconfigureFlags); virtual void paintScreen(int mask, QRegion region, ScreenPaintData& data); virtual void paintWindow(EffectWindow *w, int mask, QRegion region, WindowPaintData &data); // for properties int configuredMaxWidth() const { return maxwidth; } int configuredSpacing() const { return spacing; } qreal configuredOpacity() const { return opacity; } int configuredScreen() const { return screen; } private Q_SLOTS: void toggleCurrentThumbnail(); void slotWindowClosed(KWin::EffectWindow *w); void slotWindowGeometryShapeChanged(KWin::EffectWindow *w, const QRect &old); void slotWindowDamaged(KWin::EffectWindow* w, const QRect& damage); virtual bool isActive() const; void repaintAll(); private: void addThumbnail(EffectWindow* w); void removeThumbnail(EffectWindow* w); void arrange(); struct Data { EffectWindow* window; // the same like the key in the hash (makes code simpler) int index; QRect rect; }; QHash< EffectWindow*, Data > windows; int maxwidth; int spacing; double opacity; int screen; QRegion painted; }; } // namespace #endif diff --git a/effects/touchpoints/touchpoints.cpp b/effects/touchpoints/touchpoints.cpp index 71a746943..99063d274 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 +#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(quint32 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(quint32 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(quint32 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->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 8a2630089..534b98f24 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 +#include namespace KWin { TrackMouseEffect::TrackMouseEffect() : m_angle(0) { initConfig(); m_texture[0] = m_texture[1] = 0; #ifdef KWIN_HAVE_XRENDER_COMPOSITING m_picture[0] = m_picture[1] = 0; 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; #ifdef KWIN_HAVE_XRENDER_COMPOSITING delete m_picture[i]; m_picture[i] = 0; #endif } } void TrackMouseEffect::reconfigure(ReconfigureFlags) { m_modifiers = 0; 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); 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/wobblywindows/wobblywindows.cpp b/effects/wobblywindows/wobblywindows.cpp index 40f543aef..6dcaeecf9 100644 --- a/effects/wobblywindows/wobblywindows.cpp +++ b/effects/wobblywindows/wobblywindows.cpp @@ -1,1227 +1,1227 @@ /***************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2008 Cédric Borgese You can Freely distribute this program under the GNU General Public License. See the file "COPYING" for the exact licensing terms. ******************************************************************/ #include "wobblywindows.h" #include "wobblywindowsconfig.h" -#include +#include //#define COMPUTE_STATS // if you enable it and run kwin in a terminal from the session it manages, // be sure to redirect the output of kwin in a file or // you'll propably get deadlocks. //#define VERBOSE_MODE #if defined COMPUTE_STATS && !defined VERBOSE_MODE # ifdef __GNUC__ # warning "You enable COMPUTE_STATS without VERBOSE_MODE, computed stats will not be printed." # endif #endif namespace KWin { struct ParameterSet { qreal stiffness; qreal drag; qreal move_factor; qreal xTesselation; qreal yTesselation; qreal minVelocity; qreal maxVelocity; qreal stopVelocity; qreal minAcceleration; qreal maxAcceleration; qreal stopAcceleration; bool moveEffectEnabled; bool openEffectEnabled; bool closeEffectEnabled; }; static const ParameterSet set_0 = { 0.15, 0.80, 0.10, 20.0, 20.0, 0.0, 1000.0, 0.5, 0.0, 1000.0, 0.5, true, false, false }; static const ParameterSet set_1 = { 0.10, 0.85, 0.10, 20.0, 20.0, 0.0, 1000.0, 0.5, 0.0, 1000.0, 0.5, true, false, false }; static const ParameterSet set_2 = { 0.06, 0.90, 0.10, 20.0, 20.0, 0.0, 1000.0, 0.5, 0.0, 1000.0, 0.5, true, false, false }; static const ParameterSet set_3 = { 0.03, 0.92, 0.20, 20.0, 20.0, 0.0, 1000.0, 0.5, 0.0, 1000.0, 0.5, true, false, false }; static const ParameterSet set_4 = { 0.01, 0.97, 0.25, 20.0, 20.0, 0.0, 1000.0, 0.5, 0.0, 1000.0, 0.5, true, false, false }; static const ParameterSet pset[5] = { set_0, set_1, set_2, set_3, set_4 }; WobblyWindowsEffect::WobblyWindowsEffect() { initConfig(); reconfigure(ReconfigureAll); connect(effects, &EffectsHandler::windowAdded, this, &WobblyWindowsEffect::slotWindowAdded); connect(effects, &EffectsHandler::windowClosed, this, &WobblyWindowsEffect::slotWindowClosed); connect(effects, &EffectsHandler::windowStartUserMovedResized, this, &WobblyWindowsEffect::slotWindowStartUserMovedResized); connect(effects, &EffectsHandler::windowStepUserMovedResized, this, &WobblyWindowsEffect::slotWindowStepUserMovedResized); connect(effects, &EffectsHandler::windowFinishUserMovedResized, this, &WobblyWindowsEffect::slotWindowFinishUserMovedResized); connect(effects, &EffectsHandler::windowMaximizedStateChanged, this, &WobblyWindowsEffect::slotWindowMaximizeStateChanged); connect(effects, &EffectsHandler::windowDataChanged, this, &WobblyWindowsEffect::cancelWindowGrab); } WobblyWindowsEffect::~WobblyWindowsEffect() { if (!windows.empty()) { // we should be empty at this point... // emit a warning and clean the list. qCDebug(KWINEFFECTS) << "Windows list not empty. Left items : " << windows.count(); QHash< const EffectWindow*, WindowWobblyInfos >::iterator i; for (i = windows.begin(); i != windows.end(); ++i) { freeWobblyInfo(i.value()); } } } void WobblyWindowsEffect::reconfigure(ReconfigureFlags) { WobblyWindowsConfig::self()->read(); QString settingsMode = WobblyWindowsConfig::settings(); if (settingsMode != QStringLiteral("Custom")) { unsigned int wobblynessLevel = WobblyWindowsConfig::wobblynessLevel(); if (wobblynessLevel > 4) { qCDebug(KWINEFFECTS) << "Wrong value for \"WobblynessLevel\" : " << wobblynessLevel; wobblynessLevel = 4; } setParameterSet(pset[wobblynessLevel]); if (WobblyWindowsConfig::advancedMode()) { m_stiffness = WobblyWindowsConfig::stiffness() / 100.0; m_drag = WobblyWindowsConfig::drag() / 100.0; m_move_factor = WobblyWindowsConfig::moveFactor() / 100.0; } } else { // Custom method, read all values from config file. m_stiffness = WobblyWindowsConfig::stiffness() / 100.0; m_drag = WobblyWindowsConfig::drag() / 100.0; m_move_factor = WobblyWindowsConfig::moveFactor() / 100.0; m_xTesselation = WobblyWindowsConfig::xTesselation(); m_yTesselation = WobblyWindowsConfig::yTesselation(); m_minVelocity = WobblyWindowsConfig::minVelocity(); m_maxVelocity = WobblyWindowsConfig::maxVelocity(); m_stopVelocity = WobblyWindowsConfig::stopVelocity(); m_minAcceleration = WobblyWindowsConfig::minAcceleration(); m_maxAcceleration = WobblyWindowsConfig::maxAcceleration(); m_stopAcceleration = WobblyWindowsConfig::stopAcceleration(); m_moveEffectEnabled = WobblyWindowsConfig::moveEffect(); m_openEffectEnabled = WobblyWindowsConfig::openEffect(); // disable close effect by default for now as it doesn't do what I want. m_closeEffectEnabled = WobblyWindowsConfig::closeEffect(); } m_moveWobble = WobblyWindowsConfig::moveWobble(); m_resizeWobble = WobblyWindowsConfig::resizeWobble(); #if defined VERBOSE_MODE qCDebug(KWINEFFECTS) << "Parameters :\n" << "move : " << m_moveEffectEnabled << ", open : " << m_openEffectEnabled << ", close : " << m_closeEffectEnabled << "\n" "grid(" << m_stiffness << ", " << m_drag << ", " << m_move_factor << ")\n" << "velocity(" << m_minVelocity << ", " << m_maxVelocity << ", " << m_stopVelocity << ")\n" << "acceleration(" << m_minAcceleration << ", " << m_maxAcceleration << ", " << m_stopAcceleration << ")\n" << "tesselation(" << m_xTesselation << ", " << m_yTesselation << ")"; #endif } bool WobblyWindowsEffect::supported() { return effects->isOpenGLCompositing() && effects->animationsSupported(); } void WobblyWindowsEffect::setParameterSet(const ParameterSet& pset) { m_stiffness = pset.stiffness; m_drag = pset.drag; m_move_factor = pset.move_factor; m_xTesselation = pset.xTesselation; m_yTesselation = pset.yTesselation; m_minVelocity = pset.minVelocity; m_maxVelocity = pset.maxVelocity; m_stopVelocity = pset.stopVelocity; m_minAcceleration = pset.minAcceleration; m_maxAcceleration = pset.maxAcceleration; m_stopAcceleration = pset.stopAcceleration; m_moveEffectEnabled = pset.moveEffectEnabled; m_openEffectEnabled = pset.openEffectEnabled; m_closeEffectEnabled = pset.closeEffectEnabled; } void WobblyWindowsEffect::setVelocityThreshold(qreal m_minVelocity) { this->m_minVelocity = m_minVelocity; } void WobblyWindowsEffect::setMoveFactor(qreal factor) { m_move_factor = factor; } void WobblyWindowsEffect::setStiffness(qreal stiffness) { m_stiffness = stiffness; } void WobblyWindowsEffect::setDrag(qreal drag) { m_drag = drag; } void WobblyWindowsEffect::prePaintScreen(ScreenPrePaintData& data, int time) { // We need to mark the screen windows as transformed. Otherwise the whole // screen won't be repainted, resulting in artefacts. // Could we just set a subset of the screen to be repainted ? if (windows.count() != 0) { m_updateRegion = QRegion(); } effects->prePaintScreen(data, time); } const qreal maxTime = 10.0; void WobblyWindowsEffect::prePaintWindow(EffectWindow* w, WindowPrePaintData& data, int time) { if (windows.contains(w)) { data.setTransformed(); data.quads = data.quads.makeRegularGrid(m_xTesselation, m_yTesselation); bool stop = false; qreal updateTime = time; while (!stop && (updateTime > maxTime)) { #if defined VERBOSE_MODE qCDebug(KWINEFFECTS) << "loop time " << updateTime << " / " << time; #endif stop = !updateWindowWobblyDatas(w, maxTime); updateTime -= maxTime; } if (!stop && updateTime > 0) { updateWindowWobblyDatas(w, updateTime); } } effects->prePaintWindow(w, data, time); } void WobblyWindowsEffect::paintWindow(EffectWindow* w, int mask, QRegion region, WindowPaintData& data) { if (!(mask & PAINT_SCREEN_TRANSFORMED) && windows.contains(w)) { WindowWobblyInfos& wwi = windows[w]; int tx = w->geometry().x(); int ty = w->geometry().y(); double left = 0.0; double top = 0.0; double right = w->width(); double bottom = w->height(); for (int i = 0; i < data.quads.count(); ++i) { for (int j = 0; j < 4; ++j) { WindowVertex& v = data.quads[i][j]; Pair oldPos = {tx + v.x(), ty + v.y()}; Pair newPos = computeBezierPoint(wwi, oldPos); v.move(newPos.x - tx, newPos.y - ty); } left = qMin(left, data.quads[i].left()); top = qMin(top, data.quads[i].top()); right = qMax(right, data.quads[i].right()); bottom = qMax(bottom, data.quads[i].bottom()); } QRectF dirtyRect( left * data.xScale() + w->x() + data.xTranslation(), top * data.yScale() + w->y() + data.yTranslation(), (right - left + 1.0) * data.xScale(), (bottom - top + 1.0) * data.yScale()); // Expand the dirty region by 1px to fix potential round/floor issues. dirtyRect.adjust(-1.0, -1.0, 1.0, 1.0); m_updateRegion = m_updateRegion.united(dirtyRect.toRect()); } // Call the next effect. effects->paintWindow(w, mask, region, data); } void WobblyWindowsEffect::postPaintScreen() { if (!windows.isEmpty()) { effects->addRepaint(m_updateRegion); } // Call the next effect. effects->postPaintScreen(); } void WobblyWindowsEffect::slotWindowStartUserMovedResized(EffectWindow *w) { if (!m_moveEffectEnabled || w->isSpecialWindow()) return; if ((w->isUserMove() && m_moveWobble) || (w->isUserResize() && m_resizeWobble)) { startMovedResized(w); } } void WobblyWindowsEffect::slotWindowStepUserMovedResized(EffectWindow *w, const QRect &geometry) { Q_UNUSED(geometry) if (windows.contains(w)) { WindowWobblyInfos& wwi = windows[w]; QRect rect = w->geometry(); if (rect.y() != wwi.resize_original_rect.y()) wwi.can_wobble_top = true; if (rect.x() != wwi.resize_original_rect.x()) wwi.can_wobble_left = true; if (rect.right() != wwi.resize_original_rect.right()) wwi.can_wobble_right = true; if (rect.bottom() != wwi.resize_original_rect.bottom()) wwi.can_wobble_bottom = true; } } void WobblyWindowsEffect::slotWindowFinishUserMovedResized(EffectWindow *w) { if (windows.contains(w)) { WindowWobblyInfos& wwi = windows[w]; wwi.status = Free; QRect rect = w->geometry(); if (rect.y() != wwi.resize_original_rect.y()) wwi.can_wobble_top = true; if (rect.x() != wwi.resize_original_rect.x()) wwi.can_wobble_left = true; if (rect.right() != wwi.resize_original_rect.right()) wwi.can_wobble_right = true; if (rect.bottom() != wwi.resize_original_rect.bottom()) wwi.can_wobble_bottom = true; } } void WobblyWindowsEffect::slotWindowMaximizeStateChanged(EffectWindow *w, bool horizontal, bool vertical) { Q_UNUSED(horizontal) Q_UNUSED(vertical) if (w->isUserMove() || !m_moveEffectEnabled || w->isSpecialWindow()) return; if (m_moveWobble && m_resizeWobble) { stepMovedResized(w); } if (windows.contains(w)) { WindowWobblyInfos& wwi = windows[w]; QRect rect = w->geometry(); if (rect.y() != wwi.resize_original_rect.y()) wwi.can_wobble_top = true; if (rect.x() != wwi.resize_original_rect.x()) wwi.can_wobble_left = true; if (rect.right() != wwi.resize_original_rect.right()) wwi.can_wobble_right = true; if (rect.bottom() != wwi.resize_original_rect.bottom()) wwi.can_wobble_bottom = true; } } void WobblyWindowsEffect::startMovedResized(EffectWindow* w) { if (!windows.contains(w)) { WindowWobblyInfos new_wwi; initWobblyInfo(new_wwi, w->geometry()); windows[w] = new_wwi; } WindowWobblyInfos& wwi = windows[w]; wwi.status = Moving; const QRectF& rect = w->geometry(); qreal x_increment = rect.width() / (wwi.width - 1.0); qreal y_increment = rect.height() / (wwi.height - 1.0); Pair picked = {static_cast(cursorPos().x()), static_cast(cursorPos().y())}; int indx = (picked.x - rect.x()) / x_increment + 0.5; int indy = (picked.y - rect.y()) / y_increment + 0.5; int pickedPointIndex = indy * wwi.width + indx; if (pickedPointIndex < 0) { qCDebug(KWINEFFECTS) << "Picked index == " << pickedPointIndex << " with (" << cursorPos().x() << "," << cursorPos().y() << ")"; pickedPointIndex = 0; } else if (static_cast(pickedPointIndex) > wwi.count - 1) { qCDebug(KWINEFFECTS) << "Picked index == " << pickedPointIndex << " with (" << cursorPos().x() << "," << cursorPos().y() << ")"; pickedPointIndex = wwi.count - 1; } #if defined VERBOSE_MODE qCDebug(KWINEFFECTS) << "Original Picked point -- x : " << picked.x << " - y : " << picked.y; #endif wwi.constraint[pickedPointIndex] = true; if (w->isUserResize()) { // on a resize, do not allow any edges to wobble until it has been moved from // its original location wwi.can_wobble_top = wwi.can_wobble_left = wwi.can_wobble_right = wwi.can_wobble_bottom = false; wwi.resize_original_rect = w->geometry(); } else { wwi.can_wobble_top = wwi.can_wobble_left = wwi.can_wobble_right = wwi.can_wobble_bottom = true; } } void WobblyWindowsEffect::stepMovedResized(EffectWindow* w) { QRect new_geometry = w->geometry(); if (!windows.contains(w)) { WindowWobblyInfos new_wwi; initWobblyInfo(new_wwi, new_geometry); windows[w] = new_wwi; } WindowWobblyInfos& wwi = windows[w]; wwi.status = Free; QRect maximized_area = effects->clientArea(MaximizeArea, w); bool throb_direction_out = (new_geometry.top() == maximized_area.top() && new_geometry.bottom() == maximized_area.bottom()) || (new_geometry.left() == maximized_area.left() && new_geometry.right() == maximized_area.right()); qreal magnitude = throb_direction_out ? 10 : -30; // a small throb out when maximized, a larger throb inwards when restored for (unsigned int j = 0; j < wwi.height; ++j) { for (unsigned int i = 0; i < wwi.width; ++i) { Pair v = { magnitude*(i / qreal(wwi.width - 1) - 0.5), magnitude*(j / qreal(wwi.height - 1) - 0.5) }; wwi.velocity[j*wwi.width+i] = v; } } // constrain the middle of the window, so that any asymetry wont cause it to drift off-center for (unsigned int j = 1; j < wwi.height - 1; ++j) { for (unsigned int i = 1; i < wwi.width - 1; ++i) { wwi.constraint[j*wwi.width+i] = true; } } } void WobblyWindowsEffect::slotWindowAdded(EffectWindow* w) { if (m_openEffectEnabled && w->data(WindowAddedGrabRole).value() == nullptr) { if (windows.contains(w)) { // could this happen ?? WindowWobblyInfos& wwi = windows[w]; wobblyOpenInit(wwi); } else { WindowWobblyInfos new_wwi; initWobblyInfo(new_wwi, w->geometry()); wobblyOpenInit(new_wwi); windows[w] = new_wwi; } } } void WobblyWindowsEffect::slotWindowClosed(EffectWindow* w) { if (windows.contains(w)) { WindowWobblyInfos& wwi = windows[w]; if (m_closeEffectEnabled) { wobblyCloseInit(wwi, w); w->refWindow(); } else { freeWobblyInfo(wwi); windows.remove(w); if (windows.isEmpty()) effects->addRepaintFull(); } } else if (m_closeEffectEnabled && w->data(WindowClosedGrabRole).value() == nullptr) { WindowWobblyInfos new_wwi; initWobblyInfo(new_wwi, w->geometry()); wobblyCloseInit(new_wwi, w); windows[w] = new_wwi; w->refWindow(); } } void WobblyWindowsEffect::wobblyOpenInit(WindowWobblyInfos& wwi) const { Pair middle = { (wwi.origin[0].x + wwi.origin[15].x) / 2, (wwi.origin[0].y + wwi.origin[15].y) / 2 }; for (unsigned int j = 0; j < 4; ++j) { for (unsigned int i = 0; i < 4; ++i) { unsigned int idx = j * 4 + i; wwi.constraint[idx] = false; wwi.position[idx].x = (wwi.position[idx].x + 3 * middle.x) / 4; wwi.position[idx].y = (wwi.position[idx].y + 3 * middle.y) / 4; } } wwi.status = Openning; wwi.can_wobble_top = wwi.can_wobble_left = wwi.can_wobble_right = wwi.can_wobble_bottom = true; } void WobblyWindowsEffect::wobblyCloseInit(WindowWobblyInfos& wwi, EffectWindow* w) const { const QRectF& rect = w->geometry(); QPointF center = rect.center(); int x1 = (rect.x() + 3 * center.x()) / 4; int x2 = (rect.x() + rect.width() + 3 * center.x()) / 4; int y1 = (rect.y() + 3 * center.y()) / 4; int y2 = (rect.y() + rect.height() + 3 * center.y()) / 4; wwi.closeRect.setCoords(x1, y1, x2, y2); // for closing, not yet used... for (unsigned int j = 0; j < 4; ++j) { for (unsigned int i = 0; i < 4; ++i) { unsigned int idx = j * 4 + i; wwi.constraint[idx] = false; } } wwi.status = Closing; } void WobblyWindowsEffect::initWobblyInfo(WindowWobblyInfos& wwi, QRect geometry) const { wwi.count = 4 * 4; wwi.width = 4; wwi.height = 4; wwi.bezierWidth = m_xTesselation; wwi.bezierHeight = m_yTesselation; wwi.bezierCount = m_xTesselation * m_yTesselation; wwi.origin = new Pair[wwi.count]; wwi.position = new Pair[wwi.count]; wwi.velocity = new Pair[wwi.count]; wwi.acceleration = new Pair[wwi.count]; wwi.buffer = new Pair[wwi.count]; wwi.constraint = new bool[wwi.count]; wwi.bezierSurface = new Pair[wwi.bezierCount]; wwi.status = Moving; qreal x = geometry.x(), y = geometry.y(); qreal width = geometry.width(), height = geometry.height(); Pair initValue = {x, y}; static const Pair nullPair = {0.0, 0.0}; qreal x_increment = width / (wwi.width - 1.0); qreal y_increment = height / (wwi.height - 1.0); for (unsigned int j = 0; j < 4; ++j) { for (unsigned int i = 0; i < 4; ++i) { unsigned int idx = j * 4 + i; wwi.origin[idx] = initValue; wwi.position[idx] = initValue; wwi.velocity[idx] = nullPair; wwi.constraint[idx] = false; if (i != 4 - 2) { // x grid count - 2, i.e. not the last point initValue.x += x_increment; } else { initValue.x = width + x; } initValue.x = initValue.x; } initValue.x = x; initValue.x = initValue.x; if (j != 4 - 2) { // y grid count - 2, i.e. not the last point initValue.y += y_increment; } else { initValue.y = height + y; } initValue.y = initValue.y; } } void WobblyWindowsEffect::freeWobblyInfo(WindowWobblyInfos& wwi) const { delete[] wwi.origin; delete[] wwi.position; delete[] wwi.velocity; delete[] wwi.acceleration; delete[] wwi.buffer; delete[] wwi.constraint; delete[] wwi.bezierSurface; } WobblyWindowsEffect::Pair WobblyWindowsEffect::computeBezierPoint(const WindowWobblyInfos& wwi, Pair point) const { // compute the input value Pair topleft = wwi.origin[0]; Pair bottomright = wwi.origin[wwi.count-1]; qreal tx = (point.x - topleft.x) / (bottomright.x - topleft.x); qreal ty = (point.y - topleft.y) / (bottomright.y - topleft.y); // compute polynomial coeff qreal px[4]; px[0] = (1 - tx) * (1 - tx) * (1 - tx); px[1] = 3 * (1 - tx) * (1 - tx) * tx; px[2] = 3 * (1 - tx) * tx * tx; px[3] = tx * tx * tx; qreal py[4]; py[0] = (1 - ty) * (1 - ty) * (1 - ty); py[1] = 3 * (1 - ty) * (1 - ty) * ty; py[2] = 3 * (1 - ty) * ty * ty; py[3] = ty * ty * ty; Pair res = {0.0, 0.0}; for (unsigned int j = 0; j < 4; ++j) { for (unsigned int i = 0; i < 4; ++i) { // this assume the grid is 4*4 res.x += px[i] * py[j] * wwi.position[i + j * wwi.width].x; res.y += px[i] * py[j] * wwi.position[i + j * wwi.width].y; } } return res; } namespace { static inline void fixVectorBounds(WobblyWindowsEffect::Pair& vec, qreal min, qreal max) { if (fabs(vec.x) < min) { vec.x = 0.0; } else if (fabs(vec.x) > max) { if (vec.x > 0.0) { vec.x = max; } else { vec.x = -max; } } if (fabs(vec.y) < min) { vec.y = 0.0; } else if (fabs(vec.y) > max) { if (vec.y > 0.0) { vec.y = max; } else { vec.y = -max; } } } #if defined COMPUTE_STATS static inline void computeVectorBounds(WobblyWindowsEffect::Pair& vec, WobblyWindowsEffect::Pair& bound) { if (fabs(vec.x) < bound.x) { bound.x = fabs(vec.x); } else if (fabs(vec.x) > bound.y) { bound.y = fabs(vec.x); } if (fabs(vec.y) < bound.x) { bound.x = fabs(vec.y); } else if (fabs(vec.y) > bound.y) { bound.y = fabs(vec.y); } } #endif } // close the anonymous namespace bool WobblyWindowsEffect::updateWindowWobblyDatas(EffectWindow* w, qreal time) { QRectF rect = w->geometry(); WindowWobblyInfos& wwi = windows[w]; if (wwi.status == Closing) { rect = wwi.closeRect; } qreal x_length = rect.width() / (wwi.width - 1.0); qreal y_length = rect.height() / (wwi.height - 1.0); #if defined VERBOSE_MODE qCDebug(KWINEFFECTS) << "time " << time; qCDebug(KWINEFFECTS) << "increment x " << x_length << " // y" << y_length; #endif Pair origine = {rect.x(), rect.y()}; for (unsigned int j = 0; j < wwi.height; ++j) { for (unsigned int i = 0; i < wwi.width; ++i) { wwi.origin[wwi.width*j + i] = origine; if (i != wwi.width - 2) { origine.x += x_length; } else { origine.x = rect.width() + rect.x(); } } origine.x = rect.x(); if (j != wwi.height - 2) { origine.y += y_length; } else { origine.y = rect.height() + rect.y(); } } Pair neibourgs[4]; Pair acceleration; qreal acc_sum = 0.0; qreal vel_sum = 0.0; // compute acceleration, velocity and position for each point // for corners // top-left if (wwi.constraint[0]) { Pair window_pos = wwi.origin[0]; Pair current_pos = wwi.position[0]; Pair move = {window_pos.x - current_pos.x, window_pos.y - current_pos.y}; Pair accel = {move.x*m_stiffness, move.y*m_stiffness}; wwi.acceleration[0] = accel; } else { Pair& pos = wwi.position[0]; neibourgs[0] = wwi.position[1]; neibourgs[1] = wwi.position[wwi.width]; acceleration.x = ((neibourgs[0].x - pos.x) - x_length) * m_stiffness + (neibourgs[1].x - pos.x) * m_stiffness; acceleration.y = ((neibourgs[1].y - pos.y) - y_length) * m_stiffness + (neibourgs[0].y - pos.y) * m_stiffness; acceleration.x /= 2; acceleration.y /= 2; wwi.acceleration[0] = acceleration; } // top-right if (wwi.constraint[wwi.width-1]) { Pair window_pos = wwi.origin[wwi.width-1]; Pair current_pos = wwi.position[wwi.width-1]; Pair move = {window_pos.x - current_pos.x, window_pos.y - current_pos.y}; Pair accel = {move.x*m_stiffness, move.y*m_stiffness}; wwi.acceleration[wwi.width-1] = accel; } else { Pair& pos = wwi.position[wwi.width-1]; neibourgs[0] = wwi.position[wwi.width-2]; neibourgs[1] = wwi.position[2*wwi.width-1]; acceleration.x = (x_length - (pos.x - neibourgs[0].x)) * m_stiffness + (neibourgs[1].x - pos.x) * m_stiffness; acceleration.y = ((neibourgs[1].y - pos.y) - y_length) * m_stiffness + (neibourgs[0].y - pos.y) * m_stiffness; acceleration.x /= 2; acceleration.y /= 2; wwi.acceleration[wwi.width-1] = acceleration; } // bottom-left if (wwi.constraint[wwi.width*(wwi.height-1)]) { Pair window_pos = wwi.origin[wwi.width*(wwi.height-1)]; Pair current_pos = wwi.position[wwi.width*(wwi.height-1)]; Pair move = {window_pos.x - current_pos.x, window_pos.y - current_pos.y}; Pair accel = {move.x*m_stiffness, move.y*m_stiffness}; wwi.acceleration[wwi.width*(wwi.height-1)] = accel; } else { Pair& pos = wwi.position[wwi.width*(wwi.height-1)]; neibourgs[0] = wwi.position[wwi.width*(wwi.height-1)+1]; neibourgs[1] = wwi.position[wwi.width*(wwi.height-2)]; acceleration.x = ((neibourgs[0].x - pos.x) - x_length) * m_stiffness + (neibourgs[1].x - pos.x) * m_stiffness; acceleration.y = (y_length - (pos.y - neibourgs[1].y)) * m_stiffness + (neibourgs[0].y - pos.y) * m_stiffness; acceleration.x /= 2; acceleration.y /= 2; wwi.acceleration[wwi.width*(wwi.height-1)] = acceleration; } // bottom-right if (wwi.constraint[wwi.count-1]) { Pair window_pos = wwi.origin[wwi.count-1]; Pair current_pos = wwi.position[wwi.count-1]; Pair move = {window_pos.x - current_pos.x, window_pos.y - current_pos.y}; Pair accel = {move.x*m_stiffness, move.y*m_stiffness}; wwi.acceleration[wwi.count-1] = accel; } else { Pair& pos = wwi.position[wwi.count-1]; neibourgs[0] = wwi.position[wwi.count-2]; neibourgs[1] = wwi.position[wwi.width*(wwi.height-1)-1]; acceleration.x = (x_length - (pos.x - neibourgs[0].x)) * m_stiffness + (neibourgs[1].x - pos.x) * m_stiffness; acceleration.y = (y_length - (pos.y - neibourgs[1].y)) * m_stiffness + (neibourgs[0].y - pos.y) * m_stiffness; acceleration.x /= 2; acceleration.y /= 2; wwi.acceleration[wwi.count-1] = acceleration; } // for borders // top border for (unsigned int i = 1; i < wwi.width - 1; ++i) { if (wwi.constraint[i]) { Pair window_pos = wwi.origin[i]; Pair current_pos = wwi.position[i]; Pair move = {window_pos.x - current_pos.x, window_pos.y - current_pos.y}; Pair accel = {move.x*m_stiffness, move.y*m_stiffness}; wwi.acceleration[i] = accel; } else { Pair& pos = wwi.position[i]; neibourgs[0] = wwi.position[i-1]; neibourgs[1] = wwi.position[i+1]; neibourgs[2] = wwi.position[i+wwi.width]; acceleration.x = (x_length - (pos.x - neibourgs[0].x)) * m_stiffness + ((neibourgs[1].x - pos.x) - x_length) * m_stiffness + (neibourgs[2].x - pos.x) * m_stiffness; acceleration.y = ((neibourgs[2].y - pos.y) - y_length) * m_stiffness + (neibourgs[0].y - pos.y) * m_stiffness + (neibourgs[1].y - pos.y) * m_stiffness; acceleration.x /= 3; acceleration.y /= 3; wwi.acceleration[i] = acceleration; } } // bottom border for (unsigned int i = wwi.width * (wwi.height - 1) + 1; i < wwi.count - 1; ++i) { if (wwi.constraint[i]) { Pair window_pos = wwi.origin[i]; Pair current_pos = wwi.position[i]; Pair move = {window_pos.x - current_pos.x, window_pos.y - current_pos.y}; Pair accel = {move.x*m_stiffness, move.y*m_stiffness}; wwi.acceleration[i] = accel; } else { Pair& pos = wwi.position[i]; neibourgs[0] = wwi.position[i-1]; neibourgs[1] = wwi.position[i+1]; neibourgs[2] = wwi.position[i-wwi.width]; acceleration.x = (x_length - (pos.x - neibourgs[0].x)) * m_stiffness + ((neibourgs[1].x - pos.x) - x_length) * m_stiffness + (neibourgs[2].x - pos.x) * m_stiffness; acceleration.y = (y_length - (pos.y - neibourgs[2].y)) * m_stiffness + (neibourgs[0].y - pos.y) * m_stiffness + (neibourgs[1].y - pos.y) * m_stiffness; acceleration.x /= 3; acceleration.y /= 3; wwi.acceleration[i] = acceleration; } } // left border for (unsigned int i = wwi.width; i < wwi.width*(wwi.height - 1); i += wwi.width) { if (wwi.constraint[i]) { Pair window_pos = wwi.origin[i]; Pair current_pos = wwi.position[i]; Pair move = {window_pos.x - current_pos.x, window_pos.y - current_pos.y}; Pair accel = {move.x*m_stiffness, move.y*m_stiffness}; wwi.acceleration[i] = accel; } else { Pair& pos = wwi.position[i]; neibourgs[0] = wwi.position[i+1]; neibourgs[1] = wwi.position[i-wwi.width]; neibourgs[2] = wwi.position[i+wwi.width]; acceleration.x = ((neibourgs[0].x - pos.x) - x_length) * m_stiffness + (neibourgs[1].x - pos.x) * m_stiffness + (neibourgs[2].x - pos.x) * m_stiffness; acceleration.y = (y_length - (pos.y - neibourgs[1].y)) * m_stiffness + ((neibourgs[2].y - pos.y) - y_length) * m_stiffness + (neibourgs[0].y - pos.y) * m_stiffness; acceleration.x /= 3; acceleration.y /= 3; wwi.acceleration[i] = acceleration; } } // right border for (unsigned int i = 2 * wwi.width - 1; i < wwi.count - 1; i += wwi.width) { if (wwi.constraint[i]) { Pair window_pos = wwi.origin[i]; Pair current_pos = wwi.position[i]; Pair move = {window_pos.x - current_pos.x, window_pos.y - current_pos.y}; Pair accel = {move.x*m_stiffness, move.y*m_stiffness}; wwi.acceleration[i] = accel; } else { Pair& pos = wwi.position[i]; neibourgs[0] = wwi.position[i-1]; neibourgs[1] = wwi.position[i-wwi.width]; neibourgs[2] = wwi.position[i+wwi.width]; acceleration.x = (x_length - (pos.x - neibourgs[0].x)) * m_stiffness + (neibourgs[1].x - pos.x) * m_stiffness + (neibourgs[2].x - pos.x) * m_stiffness; acceleration.y = (y_length - (pos.y - neibourgs[1].y)) * m_stiffness + ((neibourgs[2].y - pos.y) - y_length) * m_stiffness + (neibourgs[0].y - pos.y) * m_stiffness; acceleration.x /= 3; acceleration.y /= 3; wwi.acceleration[i] = acceleration; } } // for the inner points for (unsigned int j = 1; j < wwi.height - 1; ++j) { for (unsigned int i = 1; i < wwi.width - 1; ++i) { unsigned int index = i + j * wwi.width; if (wwi.constraint[index]) { Pair window_pos = wwi.origin[index]; Pair current_pos = wwi.position[index]; Pair move = {window_pos.x - current_pos.x, window_pos.y - current_pos.y}; Pair accel = {move.x*m_stiffness, move.y*m_stiffness}; wwi.acceleration[index] = accel; } else { Pair& pos = wwi.position[index]; neibourgs[0] = wwi.position[index-1]; neibourgs[1] = wwi.position[index+1]; neibourgs[2] = wwi.position[index-wwi.width]; neibourgs[3] = wwi.position[index+wwi.width]; acceleration.x = ((neibourgs[0].x - pos.x) - x_length) * m_stiffness + (x_length - (pos.x - neibourgs[1].x)) * m_stiffness + (neibourgs[2].x - pos.x) * m_stiffness + (neibourgs[3].x - pos.x) * m_stiffness; acceleration.y = (y_length - (pos.y - neibourgs[2].y)) * m_stiffness + ((neibourgs[3].y - pos.y) - y_length) * m_stiffness + (neibourgs[0].y - pos.y) * m_stiffness + (neibourgs[1].y - pos.y) * m_stiffness; acceleration.x /= 4; acceleration.y /= 4; wwi.acceleration[index] = acceleration; } } } heightRingLinearMean(&wwi.acceleration, wwi); #if defined COMPUTE_STATS Pair accBound = {m_maxAcceleration, m_minAcceleration}; Pair velBound = {m_maxVelocity, m_minVelocity}; #endif // compute the new velocity of each vertex. for (unsigned int i = 0; i < wwi.count; ++i) { Pair acc = wwi.acceleration[i]; fixVectorBounds(acc, m_minAcceleration, m_maxAcceleration); #if defined COMPUTE_STATS computeVectorBounds(acc, accBound); #endif Pair& vel = wwi.velocity[i]; vel.x = acc.x * time + vel.x * m_drag; vel.y = acc.y * time + vel.y * m_drag; acc_sum += fabs(acc.x) + fabs(acc.y); } heightRingLinearMean(&wwi.velocity, wwi); // compute the new pos of each vertex. for (unsigned int i = 0; i < wwi.count; ++i) { Pair& pos = wwi.position[i]; Pair& vel = wwi.velocity[i]; fixVectorBounds(vel, m_minVelocity, m_maxVelocity); #if defined COMPUTE_STATS computeVectorBounds(vel, velBound); #endif pos.x += vel.x * time * m_move_factor; pos.y += vel.y * time * m_move_factor; vel_sum += fabs(vel.x) + fabs(vel.y); #if defined VERBOSE_MODE if (wwi.constraint[i]) { qCDebug(KWINEFFECTS) << "Constraint point ** vel : " << vel.x << "," << vel.y << " ** move : " << vel.x*time << "," << vel.y*time; } #endif } if (!wwi.can_wobble_top) { for (unsigned int i = 0; i < wwi.width; ++i) for (unsigned j = 0; j < wwi.width - 1; ++j) wwi.position[i+wwi.width*j].y = wwi.origin[i+wwi.width*j].y; } if (!wwi.can_wobble_bottom) { for (unsigned int i = wwi.width * (wwi.height - 1); i < wwi.count; ++i) for (unsigned j = 0; j < wwi.width - 1; ++j) wwi.position[i-wwi.width*j].y = wwi.origin[i-wwi.width*j].y; } if (!wwi.can_wobble_left) { for (unsigned int i = 0; i < wwi.count; i += wwi.width) for (unsigned j = 0; j < wwi.width - 1; ++j) wwi.position[i+j].x = wwi.origin[i+j].x; } if (!wwi.can_wobble_right) { for (unsigned int i = wwi.width - 1; i < wwi.count; i += wwi.width) for (unsigned j = 0; j < wwi.width - 1; ++j) wwi.position[i-j].x = wwi.origin[i-j].x; } #if defined VERBOSE_MODE # if defined COMPUTE_STATS qCDebug(KWINEFFECTS) << "Acceleration bounds (" << accBound.x << ", " << accBound.y << ")"; qCDebug(KWINEFFECTS) << "Velocity bounds (" << velBound.x << ", " << velBound.y << ")"; # endif qCDebug(KWINEFFECTS) << "sum_acc : " << acc_sum << " *** sum_vel :" << vel_sum; #endif if (wwi.status != Moving && acc_sum < m_stopAcceleration && vel_sum < m_stopVelocity) { if (wwi.status == Closing) { w->unrefWindow(); } freeWobblyInfo(wwi); windows.remove(w); if (windows.isEmpty()) effects->addRepaintFull(); return false; } return true; } void WobblyWindowsEffect::heightRingLinearMean(Pair** data_pointer, WindowWobblyInfos& wwi) { Pair* data = *data_pointer; Pair neibourgs[8]; // for corners // top-left { Pair& res = wwi.buffer[0]; Pair vit = data[0]; neibourgs[0] = data[1]; neibourgs[1] = data[wwi.width]; neibourgs[2] = data[wwi.width+1]; res.x = (neibourgs[0].x + neibourgs[1].x + neibourgs[2].x + 3.0 * vit.x) / 6.0; res.y = (neibourgs[0].y + neibourgs[1].y + neibourgs[2].y + 3.0 * vit.y) / 6.0; } // top-right { Pair& res = wwi.buffer[wwi.width-1]; Pair vit = data[wwi.width-1]; neibourgs[0] = data[wwi.width-2]; neibourgs[1] = data[2*wwi.width-1]; neibourgs[2] = data[2*wwi.width-2]; res.x = (neibourgs[0].x + neibourgs[1].x + neibourgs[2].x + 3.0 * vit.x) / 6.0; res.y = (neibourgs[0].y + neibourgs[1].y + neibourgs[2].y + 3.0 * vit.y) / 6.0; } // bottom-left { Pair& res = wwi.buffer[wwi.width*(wwi.height-1)]; Pair vit = data[wwi.width*(wwi.height-1)]; neibourgs[0] = data[wwi.width*(wwi.height-1)+1]; neibourgs[1] = data[wwi.width*(wwi.height-2)]; neibourgs[2] = data[wwi.width*(wwi.height-2)+1]; res.x = (neibourgs[0].x + neibourgs[1].x + neibourgs[2].x + 3.0 * vit.x) / 6.0; res.y = (neibourgs[0].y + neibourgs[1].y + neibourgs[2].y + 3.0 * vit.y) / 6.0; } // bottom-right { Pair& res = wwi.buffer[wwi.count-1]; Pair vit = data[wwi.count-1]; neibourgs[0] = data[wwi.count-2]; neibourgs[1] = data[wwi.width*(wwi.height-1)-1]; neibourgs[2] = data[wwi.width*(wwi.height-1)-2]; res.x = (neibourgs[0].x + neibourgs[1].x + neibourgs[2].x + 3.0 * vit.x) / 6.0; res.y = (neibourgs[0].y + neibourgs[1].y + neibourgs[2].y + 3.0 * vit.y) / 6.0; } // for borders // top border for (unsigned int i = 1; i < wwi.width - 1; ++i) { Pair& res = wwi.buffer[i]; Pair vit = data[i]; neibourgs[0] = data[i-1]; neibourgs[1] = data[i+1]; neibourgs[2] = data[i+wwi.width]; neibourgs[3] = data[i+wwi.width-1]; neibourgs[4] = data[i+wwi.width+1]; res.x = (neibourgs[0].x + neibourgs[1].x + neibourgs[2].x + neibourgs[3].x + neibourgs[4].x + 5.0 * vit.x) / 10.0; res.y = (neibourgs[0].y + neibourgs[1].y + neibourgs[2].y + neibourgs[3].y + neibourgs[4].y + 5.0 * vit.y) / 10.0; } // bottom border for (unsigned int i = wwi.width * (wwi.height - 1) + 1; i < wwi.count - 1; ++i) { Pair& res = wwi.buffer[i]; Pair vit = data[i]; neibourgs[0] = data[i-1]; neibourgs[1] = data[i+1]; neibourgs[2] = data[i-wwi.width]; neibourgs[3] = data[i-wwi.width-1]; neibourgs[4] = data[i-wwi.width+1]; res.x = (neibourgs[0].x + neibourgs[1].x + neibourgs[2].x + neibourgs[3].x + neibourgs[4].x + 5.0 * vit.x) / 10.0; res.y = (neibourgs[0].y + neibourgs[1].y + neibourgs[2].y + neibourgs[3].y + neibourgs[4].y + 5.0 * vit.y) / 10.0; } // left border for (unsigned int i = wwi.width; i < wwi.width*(wwi.height - 1); i += wwi.width) { Pair& res = wwi.buffer[i]; Pair vit = data[i]; neibourgs[0] = data[i+1]; neibourgs[1] = data[i-wwi.width]; neibourgs[2] = data[i+wwi.width]; neibourgs[3] = data[i-wwi.width+1]; neibourgs[4] = data[i+wwi.width+1]; res.x = (neibourgs[0].x + neibourgs[1].x + neibourgs[2].x + neibourgs[3].x + neibourgs[4].x + 5.0 * vit.x) / 10.0; res.y = (neibourgs[0].y + neibourgs[1].y + neibourgs[2].y + neibourgs[3].y + neibourgs[4].y + 5.0 * vit.y) / 10.0; } // right border for (unsigned int i = 2 * wwi.width - 1; i < wwi.count - 1; i += wwi.width) { Pair& res = wwi.buffer[i]; Pair vit = data[i]; neibourgs[0] = data[i-1]; neibourgs[1] = data[i-wwi.width]; neibourgs[2] = data[i+wwi.width]; neibourgs[3] = data[i-wwi.width-1]; neibourgs[4] = data[i+wwi.width-1]; res.x = (neibourgs[0].x + neibourgs[1].x + neibourgs[2].x + neibourgs[3].x + neibourgs[4].x + 5.0 * vit.x) / 10.0; res.y = (neibourgs[0].y + neibourgs[1].y + neibourgs[2].y + neibourgs[3].y + neibourgs[4].y + 5.0 * vit.y) / 10.0; } // for the inner points for (unsigned int j = 1; j < wwi.height - 1; ++j) { for (unsigned int i = 1; i < wwi.width - 1; ++i) { unsigned int index = i + j * wwi.width; Pair& res = wwi.buffer[index]; Pair& vit = data[index]; neibourgs[0] = data[index-1]; neibourgs[1] = data[index+1]; neibourgs[2] = data[index-wwi.width]; neibourgs[3] = data[index+wwi.width]; neibourgs[4] = data[index-wwi.width-1]; neibourgs[5] = data[index-wwi.width+1]; neibourgs[6] = data[index+wwi.width-1]; neibourgs[7] = data[index+wwi.width+1]; res.x = (neibourgs[0].x + neibourgs[1].x + neibourgs[2].x + neibourgs[3].x + neibourgs[4].x + neibourgs[5].x + neibourgs[6].x + neibourgs[7].x + 8.0 * vit.x) / 16.0; res.y = (neibourgs[0].y + neibourgs[1].y + neibourgs[2].y + neibourgs[3].y + neibourgs[4].y + neibourgs[5].y + neibourgs[6].y + neibourgs[7].y + 8.0 * vit.y) / 16.0; } } Pair* tmp = data; *data_pointer = wwi.buffer; wwi.buffer = tmp; } void WobblyWindowsEffect::cancelWindowGrab(KWin::EffectWindow *w, int grabRole) { if (grabRole == WindowAddedGrabRole) { if (w->data(WindowAddedGrabRole).value() != this) { auto it = windows.find(w); if (it != windows.end()) { freeWobblyInfo(it.value()); windows.erase(it); } } } else if (grabRole == WindowClosedGrabRole) { if (w->data(WindowClosedGrabRole).value() != this) { auto it = windows.find(w); if (it != windows.end()) { if (it.value().status == Closing) { w->unrefWindow(); } freeWobblyInfo(it.value()); windows.erase(it); } } } } bool WobblyWindowsEffect::isActive() const { return !windows.isEmpty(); } } // namespace KWin diff --git a/group.cpp b/group.cpp index 70bb2bfa9..2183ef855 100644 --- a/group.cpp +++ b/group.cpp @@ -1,919 +1,920 @@ /******************************************************************** 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 #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(Window leader_P) : leader_client(NULL), leader_wid(leader_P), leader_info(NULL), user_time(-1U), refcount(0) { if (leader_P != None) { leader_client = workspace()->findClient(Predicate::WindowMatch, leader_P); leader_info = new NETWinInfo(connection(), leader_P, rootWindow(), 0, 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) return leader_client->icon(); else if (leader_wid != 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) { assert(leader_P->window() == leader_wid); leader_client = leader_P; } void Group::lostLeader() { assert(!_members.contains(leader_client)); leader_client = NULL; if (_members.isEmpty()) { workspace()->removeGroup(this); delete this; } } //*************************************** // Workspace //*************************************** Group* Workspace::findGroup(xcb_window_t leader) const { assert(leader != None); for (GroupList::ConstIterator it = groups.constBegin(); it != groups.constEnd(); ++it) if ((*it)->leader() == leader) return *it; return NULL; } // 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; 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()) 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); assert(transient_for != NULL); // 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 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; 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 = 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) 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) { // 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; } // 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) old_group->ref(); // turn off automatic deleting if (set_group != NULL) { if (set_group != in_group) { if (in_group != NULL) 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) { // 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 new_group = new Group(info->groupLeader()); if (new_group != in_group) { if (in_group != NULL) 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) 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) new_group = new Group(None); if (new_group != in_group) { if (in_group != NULL) 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) { in_group->removeMember(this); in_group = NULL; } if (new_group == NULL) new_group = new Group(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) { 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) 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) 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) { Client* new_modal = dynamic_cast(check_modal->findModal()); if (new_modal != NULL && 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/helpers/killer/killer.cpp b/helpers/killer/killer.cpp index 1dd25ea61..608a91aaf 100644 --- a/helpers/killer/killer.cpp +++ b/helpers/killer/killer.cpp @@ -1,131 +1,132 @@ /**************************************************************************** Copyright (C) 2003 Lubos Lunak Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ****************************************************************************/ #include #include #include #include -#include +#include #include #include #include -#include -#include #include +#include +#include + int main(int argc, char* argv[]) { KLocalizedString::setApplicationDomain("kwin"); qputenv("QT_QPA_PLATFORM", QByteArrayLiteral("xcb")); QApplication app(argc, argv); QApplication::setWindowIcon(QIcon::fromTheme(QStringLiteral("dialog-warning"))); QCoreApplication::setApplicationName(QStringLiteral("kwin_killer_helper")); QCoreApplication::setOrganizationDomain(QStringLiteral("kde.org")); QApplication::setApplicationDisplayName(i18n("Window Manager")); QCoreApplication::setApplicationVersion(QStringLiteral("1.0")); QCommandLineOption pidOption(QStringLiteral("pid"), i18n("PID of the application to terminate"), i18n("pid")); QCommandLineOption hostNameOption(QStringLiteral("hostname"), i18n("Hostname on which the application is running"), i18n("hostname")); QCommandLineOption windowNameOption(QStringLiteral("windowname"), i18n("Caption of the window to be terminated"), i18n("caption")); QCommandLineOption applicationNameOption(QStringLiteral("applicationname"), i18n("Name of the application to be terminated"), i18n("name")); QCommandLineOption widOption(QStringLiteral("wid"), i18n("ID of resource belonging to the application"), i18n("id")); QCommandLineOption timestampOption(QStringLiteral("timestamp"), i18n("Time of user action causing termination"), i18n("time")); QCommandLineParser parser; parser.setApplicationDescription(i18n("KWin helper utility")); parser.addHelpOption(); parser.addVersionOption(); parser.addOption(pidOption); parser.addOption(hostNameOption); parser.addOption(windowNameOption); parser.addOption(applicationNameOption); parser.addOption(widOption); parser.addOption(timestampOption); parser.process(app); QString hostname = parser.value(hostNameOption); bool pid_ok = false; pid_t pid = parser.value(pidOption).toULong(&pid_ok); QString caption = parser.value(windowNameOption); QString appname = parser.value(applicationNameOption); bool id_ok = false; xcb_window_t id = parser.value(widOption).toULong(&id_ok); bool time_ok = false; xcb_timestamp_t timestamp = parser.value(timestampOption).toULong(&time_ok); if (!pid_ok || pid == 0 || !id_ok || id == XCB_WINDOW_NONE || !time_ok || timestamp == XCB_TIME_CURRENT_TIME || hostname.isEmpty() || caption.isEmpty() || appname.isEmpty()) { fprintf(stdout, "%s\n", qPrintable(i18n("This helper utility is not supposed to be called directly."))); parser.showHelp(1); } bool isLocal = hostname == QStringLiteral("localhost"); caption = caption.toHtmlEscaped(); appname = appname.toHtmlEscaped(); hostname = hostname.toHtmlEscaped(); QString pidString = QString::number(pid); // format pid ourself as it does not make sense to format an ID according to locale settings QString question = i18nc("@info", "Application \"%1\" is not responding", appname); question += isLocal ? xi18nc("@info", "

You tried to close window \"%1\" from application \"%2\" (Process ID: %3) but the application is not responding.

", caption, appname, pidString) : xi18nc("@info", "

You tried to close window \"%1\" from application \"%2\" (Process ID: %3), running on host \"%4\", but the application is not responding.

", caption, appname, pidString, hostname); question += xi18nc("@info", "

Do you want to terminate this application?

" "

Terminating the application will close all of its child windows. Any unsaved data will be lost.

" ); KGuiItem continueButton = KGuiItem(i18n("&Terminate Application %1", appname), QStringLiteral("edit-bomb")); KGuiItem cancelButton = KGuiItem(i18n("Wait Longer"), QStringLiteral("chronometer")); QX11Info::setAppUserTime(timestamp); if (KMessageBox::warningContinueCancelWId(id, question, QString(), continueButton, cancelButton) == KMessageBox::Continue) { if (!isLocal) { QStringList lst; lst << hostname << QStringLiteral("kill") << QString::number(pid); QProcess::startDetached(QStringLiteral("xon"), lst); } else { if (::kill(pid, SIGKILL) && errno == EPERM) { KAuth::Action killer(QStringLiteral("org.kde.ksysguard.processlisthelper.sendsignal")); killer.setHelperId(QStringLiteral("org.kde.ksysguard.processlisthelper")); killer.addArgument(QStringLiteral("pid0"), pid); killer.addArgument(QStringLiteral("pidcount"), 1); killer.addArgument(QStringLiteral("signal"), SIGKILL); if (killer.isValid()) { qDebug() << "Using KAuth to kill pid: " << pid; killer.execute(); } else { qDebug() << "KWin process killer action not valid"; } } } } } diff --git a/kcmkwin/kwindecoration/declarative-plugin/plugin.cpp b/kcmkwin/kwindecoration/declarative-plugin/plugin.cpp index 1eb823b52..54144f516 100644 --- a/kcmkwin/kwindecoration/declarative-plugin/plugin.cpp +++ b/kcmkwin/kwindecoration/declarative-plugin/plugin.cpp @@ -1,55 +1,53 @@ /* * 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 "plugin.h" #include "buttonsmodel.h" #include "previewbutton.h" #include "previewbridge.h" #include "previewclient.h" #include "previewitem.h" #include "previewsettings.h" #include #include -#include - namespace KDecoration2 { namespace Preview { void Plugin::registerTypes(const char *uri) { Q_ASSERT(QLatin1String(uri) == QLatin1String("org.kde.kwin.private.kdecoration")); qmlRegisterType(uri, 1, 0, "Bridge"); qmlRegisterType(uri, 1, 0, "Settings"); qmlRegisterType(uri, 1, 0, "Decoration"); qmlRegisterType(uri, 1, 0, "Button"); qmlRegisterType(uri, 1, 0, "ButtonsModel"); qmlRegisterType(); qmlRegisterType(); qmlRegisterType(); qmlRegisterType(); } } } diff --git a/kcmkwin/kwinoptions/mouse.cpp b/kcmkwin/kwinoptions/mouse.cpp index 0d9678c30..fd2ca2b68 100644 --- a/kcmkwin/kwinoptions/mouse.cpp +++ b/kcmkwin/kwinoptions/mouse.cpp @@ -1,570 +1,570 @@ /* * * 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 #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, "...............", ".......#.......", "......###......", ".....#####.....", "..#....#....#..", ".##....#....##.", "###############", ".##....#....##.", "..#....#....#..", ".....#####.....", "......###......", ".......#.......", "..............." }, { 0, 0, 0, "...............", ".......#.......", "......###......", ".....#####.....", ".......#.......", ".......#.......", ".......#.......", ".......#.......", ".......#.......", ".....#####.....", "......###......", ".......#.......", "..............." }, { 0, 0, 0, "...............", "...............", "...............", "...............", "..#.........#..", ".##.........##.", "###############", ".##.........##.", "..#.........#..", "...............", "...............", "...............", "..............." }, }; QByteArray baseColor(". c " + KColorScheme(QPalette::Active, KColorScheme::View).background().color().name().toAscii()); QByteArray textColor("# c " + KColorScheme(QPalette::Active, KColorScheme::View).foreground().color().name().toAscii()); 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", "Start window tab drag", "Nothing", "" }; const char* const tbl_TiInAc[] = { "Activate and raise", "Activate and lower", "Activate", "Raise", "Lower", "Toggle raise and lower", "Minimize", "Shade", "Close", "Operations menu", "Start window tab drag", "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[] = { "Meta", "Alt", "" }; 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", "Switch to Window Tab to the Left/Right", "Nothing", "" }; const char* const tbl_AllW[] = { "Raise/Lower", "Shade/Unshade", "Maximize/Restore", "Above/Below", "Previous/Next Desktop", "Change Opacity", "Switch to Window Tab to the Left/Right", "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").toAscii()); setComboText(m_ui->leftClickMaximizeButton, windowsConfig.readEntry(cnf_Max[0], tbl_Max[0]).toAscii()); setComboText(m_ui->middleClickMaximizeButton, windowsConfig.readEntry(cnf_Max[1], tbl_Max[1]).toAscii()); setComboText(m_ui->rightClickMaximizeButton, windowsConfig.readEntry(cnf_Max[2], tbl_Max[2]).toAscii()); KConfigGroup cg(config, "MouseBindings"); setComboText(m_ui->coTiAct1, cg.readEntry("CommandActiveTitlebar1", "Raise").toAscii()); setComboText(m_ui->coTiAct2, cg.readEntry("CommandActiveTitlebar2", "Start Window Tab Drag").toAscii()); setComboText(m_ui->coTiAct3, cg.readEntry("CommandActiveTitlebar3", "Operations menu").toAscii()); setComboText(m_ui->coTiAct4, cg.readEntry("CommandTitlebarWheel", "Switch to Window Tab to the Left/Right").toAscii()); setComboText(m_ui->coTiInAct1, cg.readEntry("CommandInactiveTitlebar1", "Activate and raise").toAscii()); setComboText(m_ui->coTiInAct2, cg.readEntry("CommandInactiveTitlebar2", "Start Window Tab Drag").toAscii()); setComboText(m_ui->coTiInAct3, cg.readEntry("CommandInactiveTitlebar3", "Operations menu").toAscii()); } 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, "Start Window Tab Drag"); setComboText(m_ui->coTiAct3, "Operations menu"); setComboText(m_ui->coTiAct4, "Switch to Window Tab to the Left/Right"); setComboText(m_ui->coTiInAct1, "Activate and raise"); setComboText(m_ui->coTiInAct2, "Start Window Tab Drag"); 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").toAscii()); setComboText(m_ui->coWin2, cg.readEntry("CommandWindow2", "Activate and pass click").toAscii()); setComboText(m_ui->coWin3, cg.readEntry("CommandWindow3", "Activate and pass click").toAscii()); setComboText(m_ui->coWinWheel, cg.readEntry("CommandWindowWheel", "Scroll").toAscii()); setComboText(m_ui->coAllKey, cg.readEntry("CommandAllKey", "Alt").toAscii()); setComboText(m_ui->coAll1, cg.readEntry("CommandAll1", "Move").toAscii()); setComboText(m_ui->coAll2, cg.readEntry("CommandAll2", "Toggle raise and lower").toAscii()); setComboText(m_ui->coAll3, cg.readEntry("CommandAll3", "Resize").toAscii()); setComboText(m_ui->coAllW, cg.readEntry("CommandAllWheel", "Nothing").toAscii()); } 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/ruleslist.cpp b/kcmkwin/kwinrules/ruleslist.cpp index ae5fc68a3..c1b470414 100644 --- a/kcmkwin/kwinrules/ruleslist.cpp +++ b/kcmkwin/kwinrules/ruleslist.cpp @@ -1,264 +1,265 @@ /* * 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 "ruleslist.h" -#include #include #include #include #include "ruleswidget.h" +#include + namespace KWin { KCMRulesList::KCMRulesList(QWidget* parent) : QWidget(parent) { setupUi(this); // connect both current/selected, so that current==selected (stupid QListBox :( ) connect(rules_listbox, SIGNAL(itemChanged(QListWidgetItem*)), SLOT(activeChanged())); connect(rules_listbox, SIGNAL(itemSelectionChanged()), SLOT(activeChanged())); connect(new_button, SIGNAL(clicked()), SLOT(newClicked())); connect(modify_button, SIGNAL(clicked()), SLOT(modifyClicked())); connect(delete_button, SIGNAL(clicked()), SLOT(deleteClicked())); connect(moveup_button, SIGNAL(clicked()), SLOT(moveupClicked())); connect(movedown_button, SIGNAL(clicked()), SLOT(movedownClicked())); connect(export_button, SIGNAL(clicked()), SLOT(exportClicked())); connect(import_button, SIGNAL(clicked()), SLOT(importClicked())); connect(rules_listbox, SIGNAL(itemDoubleClicked(QListWidgetItem*)), SLOT(modifyClicked())); load(); } KCMRulesList::~KCMRulesList() { for (QVector< Rules* >::Iterator it = rules.begin(); it != rules.end(); ++it) delete *it; rules.clear(); } void KCMRulesList::activeChanged() { QListWidgetItem *item = rules_listbox->currentItem(); int itemRow = rules_listbox->row(item); if (item != nullptr) // make current==selected rules_listbox->setCurrentItem(item, QItemSelectionModel::ClearAndSelect); modify_button->setEnabled(item != nullptr); delete_button->setEnabled(item != nullptr); export_button->setEnabled(item != nullptr); moveup_button->setEnabled(item != nullptr && itemRow > 0); movedown_button->setEnabled(item != nullptr && itemRow < (rules_listbox->count() - 1)); } void KCMRulesList::newClicked() { RulesDialog dlg(this); Rules* rule = dlg.edit(nullptr, {}, false); if (rule == nullptr) return; int pos = rules_listbox->currentRow() + 1; rules_listbox->insertItem(pos , rule->description); rules_listbox->setCurrentRow(pos, QItemSelectionModel::ClearAndSelect); rules.insert(rules.begin() + pos, rule); emit changed(true); } void KCMRulesList::modifyClicked() { int pos = rules_listbox->currentRow(); if (pos == -1) return; RulesDialog dlg(this); Rules* rule = dlg.edit(rules[ pos ], {}, false); if (rule == rules[ pos ]) return; delete rules[ pos ]; rules[ pos ] = rule; rules_listbox->item(pos)->setText(rule->description); emit changed(true); } void KCMRulesList::deleteClicked() { int pos = rules_listbox->currentRow(); assert(pos != -1); delete rules_listbox->takeItem(pos); rules.erase(rules.begin() + pos); emit changed(true); } void KCMRulesList::moveupClicked() { int pos = rules_listbox->currentRow(); assert(pos != -1); if (pos > 0) { QListWidgetItem * item = rules_listbox->takeItem(pos); rules_listbox->insertItem(pos - 1 , item); rules_listbox->setCurrentItem(item, QItemSelectionModel::ClearAndSelect); Rules* rule = rules[ pos ]; rules[ pos ] = rules[ pos - 1 ]; rules[ pos - 1 ] = rule; } emit changed(true); } void KCMRulesList::movedownClicked() { int pos = rules_listbox->currentRow(); assert(pos != -1); if (pos < int(rules_listbox->count()) - 1) { QListWidgetItem * item = rules_listbox->takeItem(pos); rules_listbox->insertItem(pos + 1 , item); rules_listbox->setCurrentItem(item, QItemSelectionModel::ClearAndSelect); Rules* rule = rules[ pos ]; rules[ pos ] = rules[ pos + 1 ]; rules[ pos + 1 ] = rule; } emit changed(true); } void KCMRulesList::exportClicked() { int pos = rules_listbox->currentRow(); assert(pos != -1); QString path = QFileDialog::getSaveFileName(this, i18n("Export Rules"), QDir::home().absolutePath(), i18n("KWin Rules (*.kwinrule)")); if (path.isEmpty()) return; KConfig config(path, KConfig::SimpleConfig); KConfigGroup group(&config, rules[pos]->description); group.deleteGroup(); rules[pos]->write(group); } void KCMRulesList::importClicked() { QString path = QFileDialog::getOpenFileName(this, i18n("Import Rules"), QDir::home().absolutePath(), i18n("KWin Rules (*.kwinrule)")); if (path.isEmpty()) return; KConfig config(path, KConfig::SimpleConfig); QStringList groups = config.groupList(); if (groups.isEmpty()) return; int pos = qMax(0, rules_listbox->currentRow()); foreach (const QString &group, groups) { KConfigGroup grp(&config, group); const bool remove = grp.readEntry("DeleteRule", false); Rules* new_rule = new Rules(grp); // try to replace existing rule first for (int i = 0; i < rules.count(); ++i) { if (rules[i]->description == new_rule->description) { delete rules[i]; if (remove) { rules.remove(i); delete rules_listbox->takeItem(i); delete new_rule; pos = qMax(0, rules_listbox->currentRow()); // might have changed! } else rules[i] = new_rule; new_rule = nullptr; break; } } // don't add "to be deleted" if not present if (remove) { delete new_rule; new_rule = nullptr; } // plain insertion if (new_rule) { rules.insert(pos, new_rule); rules_listbox->insertItem(pos++, new_rule->description); } } emit changed(true); } void KCMRulesList::load() { rules_listbox->clear(); for (QVector< Rules* >::Iterator it = rules.begin(); it != rules.end(); ++it) delete *it; rules.clear(); KConfig _cfg("kwinrulesrc"); KConfigGroup cfg(&_cfg, "General"); int count = cfg.readEntry("count", 0); rules.reserve(count); for (int i = 1; i <= count; ++i) { cfg = KConfigGroup(&_cfg, QString::number(i)); Rules* rule = new Rules(cfg); rules.append(rule); rules_listbox->addItem(rule->description); } if (rules.count() > 0) rules_listbox->setCurrentItem(rules_listbox->item(0)); else rules_listbox->setCurrentItem(nullptr); activeChanged(); } void KCMRulesList::save() { KConfig cfg(QLatin1String("kwinrulesrc")); QStringList groups = cfg.groupList(); for (QStringList::ConstIterator it = groups.constBegin(); it != groups.constEnd(); ++it) cfg.deleteGroup(*it); cfg.group("General").writeEntry("count", rules.count()); int i = 1; for (QVector< Rules* >::ConstIterator it = rules.constBegin(); it != rules.constEnd(); ++it) { KConfigGroup cg(&cfg, QString::number(i)); (*it)->write(cg); ++i; } } void KCMRulesList::defaults() { load(); } } // namespace diff --git a/kcmkwin/kwinrules/ruleswidget.cpp b/kcmkwin/kwinrules/ruleswidget.cpp index 4228820ad..8354a85bb 100644 --- a/kcmkwin/kwinrules/ruleswidget.cpp +++ b/kcmkwin/kwinrules/ruleswidget.cpp @@ -1,971 +1,972 @@ /* * 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 #include "../../rules.h" #include "detectwidget.h" +#include + 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; 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() { 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.cpp b/kcmkwin/kwinscreenedges/monitor.cpp index 543800dff..d1f0fb0d0 100644 --- a/kcmkwin/kwinscreenedges/monitor.cpp +++ b/kcmkwin/kwinscreenedges/monitor.cpp @@ -1,292 +1,292 @@ /******************************************************************** 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 . *********************************************************************/ #include "monitor.h" -#include #include -#include -#include -#include -#include -#include -#include -#include -#include +#include + +#include +#include +#include +#include +#include +#include +#include namespace KWin { Monitor::Monitor(QWidget* parent) : ScreenPreviewWidget(parent) { QDesktopWidget *desktop = QApplication::desktop(); QRect avail = desktop->availableGeometry(desktop->screenNumber(this)); setRatio((qreal)avail.width() / (qreal)avail.height()); for (int i = 0; i < 8; ++i) popups[ i ] = new QMenu(this); scene = new QGraphicsScene(this); view = new QGraphicsView(scene, this); view->setBackgroundBrush(Qt::black); view->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); view->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); view->setFocusPolicy(Qt::NoFocus); view->setFrameShape(QFrame::NoFrame); for (int i = 0; i < 8; ++i) { items[ i ] = new Corner(this); scene->addItem(items[ i ]); hidden[ i ] = false; grp[ i ] = new QActionGroup(this); } checkSize(); } void Monitor::clear() { for (int i = 0; i < 8; ++i) { popups[ i ]->clear(); setEdge(i, false); setEdgeHidden(i, false); delete grp[ i ]; grp[ i ] = new QActionGroup(this); } } void Monitor::resizeEvent(QResizeEvent* e) { ScreenPreviewWidget::resizeEvent(e); checkSize(); } void Monitor::checkSize() { QRect contentsRect = previewRect(); //int w = 151; //int h = 115; view->setGeometry(contentsRect); scene->setSceneRect(QRect(QPoint(0, 0), contentsRect.size())); int x2 = (contentsRect.width() - 20) / 2; int x3 = contentsRect.width() - 20; int y2 = (contentsRect.height() - 20) / 2; int y3 = contentsRect.height() - 20; items[ 0 ]->setRect(0, y2, 20, 20); items[ 1 ]->setRect(x3, y2, 20, 20); items[ 2 ]->setRect(x2, 0, 20, 20); items[ 3 ]->setRect(x2, y3, 20, 20); items[ 4 ]->setRect(0, 0, 20, 20); items[ 5 ]->setRect(x3, 0, 20, 20); items[ 6 ]->setRect(0, y3, 20, 20); items[ 7 ]->setRect(x3, y3, 20, 20); } void Monitor::setEdge(int edge, bool set) { items[ edge ]->setActive(set); } bool Monitor::edge(int edge) const { return items[ edge ]->brush() == Qt::green; } void Monitor::setEdgeHidden(int edge, bool set) { hidden[ edge ] = set; if (set) items[ edge ]->hide(); else items[ edge ]->show(); } bool Monitor::edgeHidden(int edge) const { return hidden[ edge ]; } void Monitor::addEdgeItem(int edge, const QString& item) { QAction* act = popups[ edge ]->addAction(item); act->setCheckable(true); popup_actions[ edge ].append(act); grp[ edge ]->addAction(act); if (popup_actions[ edge ].count() == 1) { act->setChecked(true); items[ edge ]->setToolTip(item); } setEdge(edge, !popup_actions[ edge ][ 0 ]->isChecked()); } void Monitor::setEdgeItemEnabled(int edge, int index, bool enabled) { popup_actions[ edge ][ index ]->setEnabled(enabled); } bool Monitor::edgeItemEnabled(int edge, int index) const { return popup_actions[ edge ][ index ]->isEnabled(); } void Monitor::selectEdgeItem(int edge, int index) { popup_actions[ edge ][ index ]->setChecked(true); setEdge(edge, !popup_actions[ edge ][ 0 ]->isChecked()); QString actionText = popup_actions[ edge ][ index ]->text(); // remove accelerators added by KAcceleratorManager actionText = KLocalizedString::removeAcceleratorMarker(actionText); items[ edge ]->setToolTip(actionText); } int Monitor::selectedEdgeItem(int edge) const { foreach (QAction * act, popup_actions[ edge ]) if (act->isChecked()) return popup_actions[ edge ].indexOf(act); abort(); } void Monitor::popup(Corner* c, QPoint pos) { for (int i = 0; i < 8; ++i) { if (items[ i ] == c) { if (popup_actions[ i ].count() == 0) return; if (QAction* a = popups[ i ]->exec(pos)) { selectEdgeItem(i, popup_actions[ i ].indexOf(a)); emit changed(); emit edgeSelectionChanged(i, popup_actions[ i ].indexOf(a)); c->setToolTip(KLocalizedString::removeAcceleratorMarker(a->text())); } return; } } abort(); } void Monitor::flip(Corner* c, QPoint pos) { for (int i = 0; i < 8; ++i) { if (items[ i ] == c) { if (popup_actions[ i ].count() == 0) setEdge(i, !edge(i)); else popup(c, pos); return; } } abort(); } Monitor::Corner::Corner(Monitor* m) : monitor(m), m_active(false), m_hover(false) { button = new Plasma::FrameSvg(); button->setImagePath("widgets/button"); setAcceptHoverEvents(true); } Monitor::Corner::~Corner() { delete button; } void Monitor::Corner::contextMenuEvent(QGraphicsSceneContextMenuEvent* e) { monitor->popup(this, e->screenPos()); } void Monitor::Corner::mousePressEvent(QGraphicsSceneMouseEvent* e) { monitor->flip(this, e->screenPos()); } void Monitor::Corner::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) { Q_UNUSED(option) Q_UNUSED(widget) if (m_hover) { button->setElementPrefix("normal"); qreal left, top, right, bottom; button->getMargins(left, top, right, bottom); button->setElementPrefix("active"); qreal activeLeft, activeTop, activeRight, activeBottom; button->getMargins(activeLeft, activeTop, activeRight, activeBottom); QRectF activeRect = QRectF(QPointF(0, 0), rect().size()); activeRect.adjust(left - activeLeft, top - activeTop, -(right - activeRight), -(bottom - activeBottom)); button->setElementPrefix("active"); button->resizeFrame(activeRect.size()); button->paintFrame(painter, rect().topLeft() + activeRect.topLeft()); } else { button->setElementPrefix(m_active ? "pressed" : "normal"); button->resizeFrame(rect().size()); button->paintFrame(painter, rect().topLeft()); } if (m_active) { QPainterPath roundedRect; painter->setRenderHint(QPainter::Antialiasing); roundedRect.addRoundedRect(rect().adjusted(5, 5, -5, -5), 2, 2); painter->fillPath(roundedRect, QApplication::palette().text()); } } void Monitor::Corner::hoverEnterEvent(QGraphicsSceneHoverEvent * e) { Q_UNUSED(e); m_hover = true; update(); } void Monitor::Corner::hoverLeaveEvent(QGraphicsSceneHoverEvent * e) { Q_UNUSED(e); m_hover = false; update(); } void Monitor::Corner::setActive(bool active) { m_active = active; update(); } bool Monitor::Corner::active() const { return m_active; } } // namespace diff --git a/kcmkwin/kwinscreenedges/monitor.h b/kcmkwin/kwinscreenedges/monitor.h index 919937da8..c40f10573 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 +#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: virtual void resizeEvent(QResizeEvent* e); 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(); void setActive(bool active); bool active() const; protected: virtual void contextMenuEvent(QGraphicsSceneContextMenuEvent* e); virtual void mousePressEvent(QGraphicsSceneMouseEvent* e); virtual void hoverEnterEvent(QGraphicsSceneHoverEvent * e); virtual void hoverLeaveEvent(QGraphicsSceneHoverEvent * e); virtual void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget = 0); private: Monitor* monitor; Plasma::FrameSvg *button; bool m_active; bool m_hover; }; } // namespace #endif diff --git a/layers.cpp b/layers.cpp index 954da9ef1..07156d961 100644 --- a/layers.cpp +++ b/layers.cpp @@ -1,880 +1,880 @@ /******************************************************************** 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 - #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 +#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? 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; } 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; } void Workspace::raiseOrLowerClient(AbstractClient *c) { if (!c) return; AbstractClient* topmost = NULL; // 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; } 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) { 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; break; } } } if (under) { unconstrained_stacking_order.removeAll(c); unconstrained_stacking_order.insert(unconstrained_stacking_order.indexOf(under), c); } 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); 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 (Layer 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; 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; } 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() { // Update states of all other windows in this group if (tabGroup()) tabGroup()->updateStates(this, TabGroup::Layer); } void Client::doSetKeepBelow() { // Update states of all other windows in this group if (tabGroup()) tabGroup()->updateStates(this, TabGroup::Layer); } bool Client::belongsToDesktop() const { foreach (const Client *c, group()->members()) { if (c->isDesktop()) return true; } return false; } } // namespace diff --git a/libinput/context.cpp b/libinput/context.cpp index dd11619c3..d9338247a 100644 --- a/libinput/context.cpp +++ b/libinput/context.cpp @@ -1,184 +1,185 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 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 "context.h" #include "events.h" #include "libinput_logging.h" #include "../logind.h" #include "../udev.h" #include -#include #include +#include + namespace KWin { namespace LibInput { static void libinputLogHandler(libinput *libinput, libinput_log_priority priority, const char *format, va_list args) { Q_UNUSED(libinput) char buf[1024]; if (std::vsnprintf(buf, 1023, format, args) <= 0) { return; } switch (priority) { case LIBINPUT_LOG_PRIORITY_DEBUG: qCDebug(KWIN_LIBINPUT) << "Libinput:" << buf; break; case LIBINPUT_LOG_PRIORITY_INFO: qCInfo(KWIN_LIBINPUT) << "Libinput:" << buf; break; case LIBINPUT_LOG_PRIORITY_ERROR: default: qCCritical(KWIN_LIBINPUT) << "Libinput:" << buf; break; } } Context::Context(const Udev &udev) : m_libinput(libinput_udev_create_context(&Context::s_interface, this, udev)) , m_suspended(false) { libinput_log_set_priority(m_libinput, LIBINPUT_LOG_PRIORITY_DEBUG); libinput_log_set_handler(m_libinput, &libinputLogHandler); } Context::~Context() { if (m_libinput) { libinput_unref(m_libinput); } } bool Context::assignSeat(const char *seat) { if (!isValid()) { return false; } return libinput_udev_assign_seat(m_libinput, seat) == 0; } int Context::fileDescriptor() { if (!isValid()) { return -1; } return libinput_get_fd(m_libinput); } void Context::dispatch() { libinput_dispatch(m_libinput); } const struct libinput_interface Context::s_interface = { Context::openRestrictedCallback, Context::closeRestrictedCallBack }; int Context::openRestrictedCallback(const char *path, int flags, void *user_data) { return ((Context*)user_data)->openRestricted(path, flags); } void Context::closeRestrictedCallBack(int fd, void *user_data) { ((Context*)user_data)->closeRestricted(fd); } int Context::openRestricted(const char *path, int flags) { LogindIntegration *logind = LogindIntegration::self(); Q_ASSERT(logind); int fd = logind->takeDevice(path); if (fd < 0) { // failed return fd; } // adjust flags - based on Weston (logind-util.c) int fl = fcntl(fd, F_GETFL); auto errorHandling = [fd, this]() { close(fd); closeRestricted(fd); }; if (fl < 0) { errorHandling(); return -1; } if (flags & O_NONBLOCK) { fl |= O_NONBLOCK; } if (fcntl(fd, F_SETFL, fl) < 0) { errorHandling(); return -1; } fl = fcntl(fd, F_GETFD); if (fl < 0) { errorHandling(); return -1; } if (!(flags & O_CLOEXEC)) { fl &= ~FD_CLOEXEC; } if (fcntl(fd, F_SETFD, fl) < 0) { errorHandling(); return -1; } return fd; } void Context::closeRestricted(int fd) { LogindIntegration *logind = LogindIntegration::self(); Q_ASSERT(logind); logind->releaseDevice(fd); } Event *Context::event() { return Event::create(libinput_get_event(m_libinput)); } void Context::suspend() { if (m_suspended) { return; } libinput_suspend(m_libinput); m_suspended = true; } void Context::resume() { if (!m_suspended) { return; } libinput_resume(m_libinput); m_suspended = false; } } } diff --git a/libkwineffects/kwinanimationeffect.h b/libkwineffects/kwinanimationeffect.h index c8b412867..0bbe25d74 100644 --- a/libkwineffects/kwinanimationeffect.h +++ b/libkwineffects/kwinanimationeffect.h @@ -1,418 +1,418 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2011 Thomas Lübking 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 . *********************************************************************/ #ifndef ANIMATION_EFFECT_H #define ANIMATION_EFFECT_H #include #include -#include +#include #include #include namespace KWin { class KWINEFFECTS_EXPORT FPx2 { public: FPx2() { f[0] = f[1] = 0.0; valid = false; } explicit FPx2(float v) { f[0] = f[1] = v; valid = true; } FPx2(float v1, float v2) { f[0] = v1; f[1] = v2; valid = true; } FPx2(const FPx2 &other) { f[0] = other.f[0]; f[1] = other.f[1]; valid = other.valid; } explicit FPx2(const QPoint &other) { f[0] = other.x(); f[1] = other.y(); valid = true; } explicit FPx2(const QPointF &other) { f[0] = other.x(); f[1] = other.y(); valid = true; } explicit FPx2(const QSize &other) { f[0] = other.width(); f[1] = other.height(); valid = true; } explicit FPx2(const QSizeF &other) { f[0] = other.width(); f[1] = other.height(); valid = true; } inline void invalidate() { valid = false; } inline bool isValid() const { return valid; } inline float operator[](int n) const { return f[n]; } inline QString toString() const { QString ret; if (valid) ret = QString::number(f[0]) + QLatin1Char(',') + QString::number(f[1]); else ret = QString(); return ret; } inline FPx2 &operator+=(const FPx2 &other) { f[0] += other[0]; f[1] += other[1]; return *this; } inline FPx2 &operator-=(const FPx2 &other) { f[0] -= other[0]; f[1] -= other[1]; return *this; } inline FPx2 &operator*=(float fl) { f[0] *= fl; f[1] *= fl; return *this; } inline FPx2 &operator/=(float fl) { f[0] /= fl; f[1] /= fl; return *this; } friend inline bool operator==(const FPx2 &f1, const FPx2 &f2) { return f1[0] == f2[0] && f1[1] == f2[1]; } friend inline bool operator!=(const FPx2 &f1, const FPx2 &f2) { return f1[0] != f2[0] || f1[1] != f2[1]; } friend inline const FPx2 operator+(const FPx2 &f1, const FPx2 &f2) { return FPx2( f1[0] + f2[0], f1[1] + f2[1] ); } friend inline const FPx2 operator-(const FPx2 &f1, const FPx2 &f2) { return FPx2( f1[0] - f2[0], f1[1] - f2[1] ); } friend inline const FPx2 operator*(const FPx2 &f, float fl) { return FPx2( f[0] * fl, f[1] * fl ); } friend inline const FPx2 operator*(float fl, const FPx2 &f) { return FPx2( f[0] * fl, f[1] *fl ); } friend inline const FPx2 operator-(const FPx2 &f) { return FPx2( -f[0], -f[1] ); } friend inline const FPx2 operator/(const FPx2 &f, float fl) { return FPx2( f[0] / fl, f[1] / fl ); } inline void set(float v) { f[0] = v; valid = true; } inline void set(float v1, float v2) { f[0] = v1; f[1] = v2; valid = true; } private: float f[2]; bool valid; }; class AniData; class AnimationEffectPrivate; /** * Base class for animation effects. * * AnimationEffect serves as a base class for animation effects. It makes easier * implementing animated transitions, without having to worry about low-level * specific stuff, e.g. referencing and unreferencing deleted windows, scheduling * repaints for the next frame, etc. * * Each animation animates one specific attribute, e.g. size, position, scale, etc. * You can provide your own implementation of the Generic attribute if none of the * standard attributes(e.g. size, position, etc) satisfy your requirements. * * @since 4.8 **/ class KWINEFFECTS_EXPORT AnimationEffect : public Effect { Q_OBJECT public: enum Anchor { Left = 1<<0, Top = 1<<1, Right = 1<<2, Bottom = 1<<3, Horizontal = Left|Right, Vertical = Top|Bottom, Mouse = 1<<4 }; Q_ENUM(Anchor) enum Attribute { Opacity = 0, Brightness, Saturation, Scale, Rotation, Position, Size, Translation, Clip, Generic, CrossFadePrevious, NonFloatBase = Position }; Q_ENUM(Attribute) enum MetaType { SourceAnchor, TargetAnchor, RelativeSourceX, RelativeSourceY, RelativeTargetX, RelativeTargetY, Axis }; Q_ENUM(MetaType) /** * This enum type is used to specify the direction of the animation. * * @since 5.15 **/ enum Direction { Forward, ///< The animation goes from source to target. Backward ///< The animation goes from target to source. }; Q_ENUM(Direction) /** * This enum type is used to specify when the animation should be terminated. * * @since 5.15 **/ enum TerminationFlag { /** * Don't terminate the animation when it reaches source or target position. **/ DontTerminate = 0x00, /** * Terminate the animation when it reaches the source position. An animation * can reach the source position if its direction was changed to go backward * (from target to source). **/ TerminateAtSource = 0x01, /** * Terminate the animation when it reaches the target position. If this flag * is not set, then the animation will be persistent. **/ TerminateAtTarget = 0x02 }; Q_FLAGS(TerminationFlag) Q_DECLARE_FLAGS(TerminationFlags, TerminationFlag) /** * Constructs AnimationEffect. * * Whenever you intend to connect to the EffectsHandler::windowClosed() signal, * do so when reimplementing the constructor. Do not add private slots named * _windowClosed or _windowDeleted! The AnimationEffect connects them right after * the construction. * * If you shadow the _windowDeleted slot (it doesn't matter that it's a private * slot), this will lead to segfaults. * * If you shadow _windowClosed or connect your slot to EffectsHandler::windowClosed() * after _windowClosed was connected, animations for closing windows will fail. **/ AnimationEffect(); ~AnimationEffect(); bool isActive() const; /** * Gets stored metadata. * * Metadata can be used to store some extra information, for example rotation axis, * etc. The first 24 bits are reserved for the AnimationEffect class, you can use * the last 8 bits for custom hints. In case when you transform a Generic attribute, * all 32 bits are yours and you can use them as you want and read them in your * genericAnimation() implementation. * * @param type The type of the metadata. * @param meta Where the metadata is stored. * @returns Stored metadata. * @since 4.8 **/ static int metaData(MetaType type, uint meta ); /** * Sets metadata. * * @param type The type of the metadata. * @param value The data to be stored. * @param meta Where the metadata will be stored. * @since 4.8 **/ static void setMetaData(MetaType type, uint value, uint &meta ); // Reimplemented from KWin::Effect. QString debug(const QString ¶meter) const; virtual void prePaintScreen( ScreenPrePaintData& data, int time ); virtual void prePaintWindow( EffectWindow* w, WindowPrePaintData& data, int time ); virtual void paintWindow( EffectWindow* w, int mask, QRegion region, WindowPaintData& data ); virtual void postPaintScreen(); /** * Gaussian (bumper) animation curve for QEasingCurve. * * @since 4.8 **/ static qreal qecGaussian(qreal progress) { progress = 2*progress - 1; progress *= -5*progress; return qExp(progress); } /** * @since 4.8 **/ static inline qint64 clock() { return s_clock.elapsed(); } protected: /** * Starts an animated transition of any supported attribute. * * @param w The animated window. * @param a The animated attribute. * @param meta Basically a wildcard to carry various extra information, e.g. * the anchor, relativity or rotation axis. You will probably use it when * performing Generic animations. * @param ms How long the transition will last. * @param to The target value. FPx2 is an agnostic two component float type * (like QPointF or QSizeF, but without requiring to be either and supporting * an invalid state). * @param curve How the animation progresses, e.g. Linear progresses constantly * while Exponential start slow and becomes very fast in the end. * @param delay When the animation will start compared to "now" (the window will * remain at the "from" position until then). * @param from The starting value, the default is invalid, ie. the attribute for * the window is not transformed in the beginning. * @param fullScreen Sets this effect as the active full screen effect for the * duration of the animation. * @param keepAlive Whether closed windows should be kept alive during animation. * @returns An ID that you can use to cancel a running animation. * @since 4.8 **/ quint64 animate( EffectWindow *w, Attribute a, uint meta, int ms, FPx2 to, QEasingCurve curve = QEasingCurve(), int delay = 0, FPx2 from = FPx2(), bool fullScreen = false, bool keepAlive = true) { return p_animate(w, a, meta, ms, to, curve, delay, from, false, fullScreen, keepAlive); } /** * Starts a persistent animated transition of any supported attribute. * * This method is equal to animate() with one important difference: * the target value for the attribute is kept until you call cancel(). * * @param w The animated window. * @param a The animated attribute. * @param meta Basically a wildcard to carry various extra information, e.g. * the anchor, relativity or rotation axis. You will probably use it when * performing Generic animations. * @param ms How long the transition will last. * @param to The target value. FPx2 is an agnostic two component float type * (like QPointF or QSizeF, but without requiring to be either and supporting * an invalid state). * @param curve How the animation progresses, e.g. Linear progresses constantly * while Exponential start slow and becomes very fast in the end. * @param delay When the animation will start compared to "now" (the window will * remain at the "from" position until then). * @param from The starting value, the default is invalid, ie. the attribute for * the window is not transformed in the beginning. * @param fullScreen Sets this effect as the active full screen effect for the * duration of the animation. * @param keepAlive Whether closed windows should be kept alive during animation. * @returns An ID that you need to use to cancel this manipulation. * @since 4.11 **/ quint64 set( EffectWindow *w, Attribute a, uint meta, int ms, FPx2 to, QEasingCurve curve = QEasingCurve(), int delay = 0, FPx2 from = FPx2(), bool fullScreen = false, bool keepAlive = true) { return p_animate(w, a, meta, ms, to, curve, delay, from, true, fullScreen, keepAlive); } /** * Changes the target (but not type or curve) of a running animation. * * Please use cancel() to cancel an animation rather than altering it. * * @param animationId The id of the animation to be retargetted. * @param newTarget The new target. * @param newRemainingTime The new duration of the transition. By default (-1), * the remaining time remains unchanged. * @returns @c true if the animation was retargetted successfully, @c false otherwise. * @note You can NOT retarget an animation that just has just ended! * @since 5.6 **/ bool retarget(quint64 animationId, FPx2 newTarget, int newRemainingTime = -1); /** * Changes the direction of the animation. * * @param animationId The id of the animation. * @param direction The new direction of the animation. * @param terminationFlags Whether the animation should be terminated when it * reaches the source position after its direction was changed to go backward. * Currently, TerminationFlag::TerminateAtTarget has no effect. * @returns @c true if the direction of the animation was changed successfully, * otherwise @c false. * @since 5.15 **/ bool redirect(quint64 animationId, Direction direction, TerminationFlags terminationFlags = TerminateAtSource); /** * Fast-forwards the animation to the target position. * * @param animationId The id of the animation. * @returns @c true if the animation was fast-forwarded successfully, otherwise * @c false. * @since 5.15 **/ bool complete(quint64 animationId); /** * Called whenever an animation ends. * * You can reimplement this method to keep a constant transformation for the window * (i.e. keep it at some opacity or position) or to start another animation. * * @param w The animated window. * @param a The animated attribute. * @param meta Originally supplied metadata to animate() or set(). * @since 4.8 **/ virtual void animationEnded(EffectWindow *w, Attribute a, uint meta) {Q_UNUSED(w); Q_UNUSED(a); Q_UNUSED(meta);} /** * Cancels a running animation. * * @param animationId The id of the animation. * @returns @c true if the animation was found (and canceled), @c false otherwise. * @note There is NO animated reset of the original value. You'll have to provide * that with a second animation. * @note This will eventually release a Deleted window as well. * @note If you intend to run another animation on the (Deleted) window, you have * to do that before cancelling the old animation (to keep the window around). * @since 4.11 **/ bool cancel(quint64 animationId); /** * Called whenever animation that transforms Generic attribute needs to be painted. * * You should reimplement this method if you transform Generic attribute. @p meta * can be used to support more than one additional animations. * * @param w The animated window. * @param data The paint data. * @param progress Current progress value. * @param meta The metadata. * @since 4.8 **/ virtual void genericAnimation( EffectWindow *w, WindowPaintData &data, float progress, uint meta ) {Q_UNUSED(w); Q_UNUSED(data); Q_UNUSED(progress); Q_UNUSED(meta);} /** * @internal **/ typedef QMap, QRect> > AniMap; /** * @internal **/ AniMap state() const; private: quint64 p_animate(EffectWindow *w, Attribute a, uint meta, int ms, FPx2 to, QEasingCurve curve, int delay, FPx2 from, bool keepAtTarget, bool fullScreenEffect, bool keepAlive); QRect clipRect(const QRect &windowRect, const AniData&) const; void clipWindow(const EffectWindow *, const AniData &, WindowQuadList &) const; float interpolated( const AniData&, int i = 0 ) const; float progress( const AniData& ) const; void disconnectGeometryChanges(); void updateLayerRepaints(); void validate(Attribute a, uint &meta, FPx2 *from, FPx2 *to, const EffectWindow *w) const; private Q_SLOTS: void init(); void triggerRepaint(); void _windowClosed( KWin::EffectWindow* w ); void _windowDeleted( KWin::EffectWindow* w ); void _expandedGeometryChanged(KWin::EffectWindow *w, const QRect &old); private: static QElapsedTimer s_clock; AnimationEffectPrivate * const d_ptr; Q_DECLARE_PRIVATE(AnimationEffect) Q_DISABLE_COPY(AnimationEffect) }; } // namespace QDebug operator<<(QDebug dbg, const KWin::FPx2 &fpx2); Q_DECLARE_METATYPE(KWin::FPx2) Q_DECLARE_OPERATORS_FOR_FLAGS(KWin::AnimationEffect::TerminationFlags) #endif // ANIMATION_EFFECT_H diff --git a/libkwineffects/kwineffects.cpp b/libkwineffects/kwineffects.cpp index 8f70ba1b3..78f7a2b36 100644 --- a/libkwineffects/kwineffects.cpp +++ b/libkwineffects/kwineffects.cpp @@ -1,1957 +1,1957 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2006 Lubos Lunak Copyright (C) 2009 Lucas Murray 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 "kwineffects.h" #include "config-kwin.h" #ifdef KWIN_HAVE_XRENDER_COMPOSITING #include "kwinxrenderutils.h" #endif -#include #include #include #include #include #include #include #include #include #include +#include #include #include -#include +#include #include #ifdef KWIN_HAVE_XRENDER_COMPOSITING #include #endif #if defined(__GNUC__) # define KWIN_ALIGN(n) __attribute((aligned(n))) # if defined(__SSE2__) # define HAVE_SSE2 # endif #elif defined(__INTEL_COMPILER) # define KWIN_ALIGN(n) __declspec(align(n)) # define HAVE_SSE2 #else # define KWIN_ALIGN(n) #endif #ifdef HAVE_SSE2 # include #endif namespace KWin { void WindowPrePaintData::setTranslucent() { mask |= Effect::PAINT_WINDOW_TRANSLUCENT; mask &= ~Effect::PAINT_WINDOW_OPAQUE; clip = QRegion(); // cannot clip, will be transparent } void WindowPrePaintData::setTransformed() { mask |= Effect::PAINT_WINDOW_TRANSFORMED; } class PaintDataPrivate { public: QGraphicsScale scale; QVector3D translation; QGraphicsRotation rotation; }; PaintData::PaintData() : d(new PaintDataPrivate()) { } PaintData::~PaintData() { delete d; } qreal PaintData::xScale() const { return d->scale.xScale(); } qreal PaintData::yScale() const { return d->scale.yScale(); } qreal PaintData::zScale() const { return d->scale.zScale(); } void PaintData::setScale(const QVector2D &scale) { d->scale.setXScale(scale.x()); d->scale.setYScale(scale.y()); } void PaintData::setScale(const QVector3D &scale) { d->scale.setXScale(scale.x()); d->scale.setYScale(scale.y()); d->scale.setZScale(scale.z()); } void PaintData::setXScale(qreal scale) { d->scale.setXScale(scale); } void PaintData::setYScale(qreal scale) { d->scale.setYScale(scale); } void PaintData::setZScale(qreal scale) { d->scale.setZScale(scale); } const QGraphicsScale &PaintData::scale() const { return d->scale; } void PaintData::setXTranslation(qreal translate) { d->translation.setX(translate); } void PaintData::setYTranslation(qreal translate) { d->translation.setY(translate); } void PaintData::setZTranslation(qreal translate) { d->translation.setZ(translate); } void PaintData::translate(qreal x, qreal y, qreal z) { translate(QVector3D(x, y, z)); } void PaintData::translate(const QVector3D &t) { d->translation += t; } qreal PaintData::xTranslation() const { return d->translation.x(); } qreal PaintData::yTranslation() const { return d->translation.y(); } qreal PaintData::zTranslation() const { return d->translation.z(); } const QVector3D &PaintData::translation() const { return d->translation; } qreal PaintData::rotationAngle() const { return d->rotation.angle(); } QVector3D PaintData::rotationAxis() const { return d->rotation.axis(); } QVector3D PaintData::rotationOrigin() const { return d->rotation.origin(); } void PaintData::setRotationAngle(qreal angle) { d->rotation.setAngle(angle); } void PaintData::setRotationAxis(Qt::Axis axis) { d->rotation.setAxis(axis); } void PaintData::setRotationAxis(const QVector3D &axis) { d->rotation.setAxis(axis); } void PaintData::setRotationOrigin(const QVector3D &origin) { d->rotation.setOrigin(origin); } class WindowPaintDataPrivate { public: qreal opacity; qreal saturation; qreal brightness; int screen; qreal crossFadeProgress; QMatrix4x4 pMatrix; QMatrix4x4 mvMatrix; QMatrix4x4 screenProjectionMatrix; }; WindowPaintData::WindowPaintData(EffectWindow *w) : WindowPaintData(w, QMatrix4x4()) { } WindowPaintData::WindowPaintData(EffectWindow* w, const QMatrix4x4 &screenProjectionMatrix) : PaintData() , shader(nullptr) , d(new WindowPaintDataPrivate()) { d->screenProjectionMatrix = screenProjectionMatrix; quads = w->buildQuads(); setOpacity(w->opacity()); setSaturation(1.0); setBrightness(1.0); setScreen(0); setCrossFadeProgress(1.0); } WindowPaintData::WindowPaintData(const WindowPaintData &other) : PaintData() , quads(other.quads) , shader(other.shader) , d(new WindowPaintDataPrivate()) { setXScale(other.xScale()); setYScale(other.yScale()); setZScale(other.zScale()); translate(other.translation()); setRotationOrigin(other.rotationOrigin()); setRotationAxis(other.rotationAxis()); setRotationAngle(other.rotationAngle()); setOpacity(other.opacity()); setSaturation(other.saturation()); setBrightness(other.brightness()); setScreen(other.screen()); setCrossFadeProgress(other.crossFadeProgress()); setProjectionMatrix(other.projectionMatrix()); setModelViewMatrix(other.modelViewMatrix()); d->screenProjectionMatrix = other.d->screenProjectionMatrix; } WindowPaintData::~WindowPaintData() { delete d; } qreal WindowPaintData::opacity() const { return d->opacity; } qreal WindowPaintData::saturation() const { return d->saturation; } qreal WindowPaintData::brightness() const { return d->brightness; } int WindowPaintData::screen() const { return d->screen; } void WindowPaintData::setOpacity(qreal opacity) { d->opacity = opacity; } void WindowPaintData::setSaturation(qreal saturation) const { d->saturation = saturation; } void WindowPaintData::setBrightness(qreal brightness) { d->brightness = brightness; } void WindowPaintData::setScreen(int screen) const { d->screen = screen; } qreal WindowPaintData::crossFadeProgress() const { return d->crossFadeProgress; } void WindowPaintData::setCrossFadeProgress(qreal factor) { d->crossFadeProgress = qBound(qreal(0.0), factor, qreal(1.0)); } qreal WindowPaintData::multiplyOpacity(qreal factor) { d->opacity *= factor; return d->opacity; } qreal WindowPaintData::multiplySaturation(qreal factor) { d->saturation *= factor; return d->saturation; } qreal WindowPaintData::multiplyBrightness(qreal factor) { d->brightness *= factor; return d->brightness; } void WindowPaintData::setProjectionMatrix(const QMatrix4x4 &matrix) { d->pMatrix = matrix; } QMatrix4x4 WindowPaintData::projectionMatrix() const { return d->pMatrix; } QMatrix4x4 &WindowPaintData::rprojectionMatrix() { return d->pMatrix; } void WindowPaintData::setModelViewMatrix(const QMatrix4x4 &matrix) { d->mvMatrix = matrix; } QMatrix4x4 WindowPaintData::modelViewMatrix() const { return d->mvMatrix; } QMatrix4x4 &WindowPaintData::rmodelViewMatrix() { return d->mvMatrix; } WindowPaintData &WindowPaintData::operator*=(qreal scale) { this->setXScale(this->xScale() * scale); this->setYScale(this->yScale() * scale); this->setZScale(this->zScale() * scale); return *this; } WindowPaintData &WindowPaintData::operator*=(const QVector2D &scale) { this->setXScale(this->xScale() * scale.x()); this->setYScale(this->yScale() * scale.y()); return *this; } WindowPaintData &WindowPaintData::operator*=(const QVector3D &scale) { this->setXScale(this->xScale() * scale.x()); this->setYScale(this->yScale() * scale.y()); this->setZScale(this->zScale() * scale.z()); return *this; } WindowPaintData &WindowPaintData::operator+=(const QPointF &translation) { return this->operator+=(QVector3D(translation)); } WindowPaintData &WindowPaintData::operator+=(const QPoint &translation) { return this->operator+=(QVector3D(translation)); } WindowPaintData &WindowPaintData::operator+=(const QVector2D &translation) { return this->operator+=(QVector3D(translation)); } WindowPaintData &WindowPaintData::operator+=(const QVector3D &translation) { translate(translation); return *this; } QMatrix4x4 WindowPaintData::screenProjectionMatrix() const { return d->screenProjectionMatrix; } class ScreenPaintData::Private { public: QMatrix4x4 projectionMatrix; QRect outputGeometry; }; ScreenPaintData::ScreenPaintData() : PaintData() , d(new Private()) { } ScreenPaintData::ScreenPaintData(const QMatrix4x4 &projectionMatrix, const QRect &outputGeometry) : PaintData() , d(new Private()) { d->projectionMatrix = projectionMatrix; d->outputGeometry = outputGeometry; } ScreenPaintData::~ScreenPaintData() = default; ScreenPaintData::ScreenPaintData(const ScreenPaintData &other) : PaintData() , d(new Private()) { translate(other.translation()); setXScale(other.xScale()); setYScale(other.yScale()); setZScale(other.zScale()); setRotationOrigin(other.rotationOrigin()); setRotationAxis(other.rotationAxis()); setRotationAngle(other.rotationAngle()); d->projectionMatrix = other.d->projectionMatrix; d->outputGeometry = other.d->outputGeometry; } ScreenPaintData &ScreenPaintData::operator=(const ScreenPaintData &rhs) { setXScale(rhs.xScale()); setYScale(rhs.yScale()); setZScale(rhs.zScale()); setXTranslation(rhs.xTranslation()); setYTranslation(rhs.yTranslation()); setZTranslation(rhs.zTranslation()); setRotationOrigin(rhs.rotationOrigin()); setRotationAxis(rhs.rotationAxis()); setRotationAngle(rhs.rotationAngle()); d->projectionMatrix = rhs.d->projectionMatrix; d->outputGeometry = rhs.d->outputGeometry; return *this; } ScreenPaintData &ScreenPaintData::operator*=(qreal scale) { setXScale(this->xScale() * scale); setYScale(this->yScale() * scale); setZScale(this->zScale() * scale); return *this; } ScreenPaintData &ScreenPaintData::operator*=(const QVector2D &scale) { setXScale(this->xScale() * scale.x()); setYScale(this->yScale() * scale.y()); return *this; } ScreenPaintData &ScreenPaintData::operator*=(const QVector3D &scale) { setXScale(this->xScale() * scale.x()); setYScale(this->yScale() * scale.y()); setZScale(this->zScale() * scale.z()); return *this; } ScreenPaintData &ScreenPaintData::operator+=(const QPointF &translation) { return this->operator+=(QVector3D(translation)); } ScreenPaintData &ScreenPaintData::operator+=(const QPoint &translation) { return this->operator+=(QVector3D(translation)); } ScreenPaintData &ScreenPaintData::operator+=(const QVector2D &translation) { return this->operator+=(QVector3D(translation)); } ScreenPaintData &ScreenPaintData::operator+=(const QVector3D &translation) { translate(translation); return *this; } QMatrix4x4 ScreenPaintData::projectionMatrix() const { return d->projectionMatrix; } QRect ScreenPaintData::outputGeometry() const { return d->outputGeometry; } //**************************************** // Effect //**************************************** Effect::Effect() { } Effect::~Effect() { } void Effect::reconfigure(ReconfigureFlags) { } void* Effect::proxy() { return nullptr; } void Effect::windowInputMouseEvent(QEvent*) { } void Effect::grabbedKeyboardEvent(QKeyEvent*) { } bool Effect::borderActivated(ElectricBorder) { return false; } void Effect::prePaintScreen(ScreenPrePaintData& data, int time) { effects->prePaintScreen(data, time); } void Effect::paintScreen(int mask, QRegion region, ScreenPaintData& data) { effects->paintScreen(mask, region, data); } void Effect::postPaintScreen() { effects->postPaintScreen(); } void Effect::prePaintWindow(EffectWindow* w, WindowPrePaintData& data, int time) { effects->prePaintWindow(w, data, time); } void Effect::paintWindow(EffectWindow* w, int mask, QRegion region, WindowPaintData& data) { effects->paintWindow(w, mask, region, data); } void Effect::postPaintWindow(EffectWindow* w) { effects->postPaintWindow(w); } void Effect::paintEffectFrame(KWin::EffectFrame* frame, QRegion region, double opacity, double frameOpacity) { effects->paintEffectFrame(frame, region, opacity, frameOpacity); } bool Effect::provides(Feature) { return false; } bool Effect::isActive() const { return true; } QString Effect::debug(const QString &) const { return QString(); } void Effect::drawWindow(EffectWindow* w, int mask, QRegion region, WindowPaintData& data) { effects->drawWindow(w, mask, region, data); } void Effect::buildQuads(EffectWindow* w, WindowQuadList& quadList) { effects->buildQuads(w, quadList); } void Effect::setPositionTransformations(WindowPaintData& data, QRect& region, EffectWindow* w, const QRect& r, Qt::AspectRatioMode aspect) { QSize size = w->size(); size.scale(r.size(), aspect); data.setXScale(size.width() / double(w->width())); data.setYScale(size.height() / double(w->height())); int width = int(w->width() * data.xScale()); int height = int(w->height() * data.yScale()); int x = r.x() + (r.width() - width) / 2; int y = r.y() + (r.height() - height) / 2; region = QRect(x, y, width, height); data.setXTranslation(x - w->x()); data.setYTranslation(y - w->y()); } QPoint Effect::cursorPos() { return effects->cursorPos(); } double Effect::animationTime(const KConfigGroup& cfg, const QString& key, int defaultTime) { int time = cfg.readEntry(key, 0); return time != 0 ? time : qMax(defaultTime * effects->animationTimeFactor(), 1.); } double Effect::animationTime(int defaultTime) { // at least 1ms, otherwise 0ms times can break some things return qMax(defaultTime * effects->animationTimeFactor(), 1.); } int Effect::requestedEffectChainPosition() const { return 0; } xcb_connection_t *Effect::xcbConnection() const { return effects->xcbConnection(); } xcb_window_t Effect::x11RootWindow() const { return effects->x11RootWindow(); } bool Effect::touchDown(quint32 id, const QPointF &pos, quint32 time) { Q_UNUSED(id) Q_UNUSED(pos) Q_UNUSED(time) return false; } bool Effect::touchMotion(quint32 id, const QPointF &pos, quint32 time) { Q_UNUSED(id) Q_UNUSED(pos) Q_UNUSED(time) return false; } bool Effect::touchUp(quint32 id, quint32 time) { Q_UNUSED(id) Q_UNUSED(time) return false; } bool Effect::perform(Feature feature, const QVariantList &arguments) { Q_UNUSED(feature) Q_UNUSED(arguments) return false; } //**************************************** // EffectFactory //**************************************** EffectPluginFactory::EffectPluginFactory() { } EffectPluginFactory::~EffectPluginFactory() { } bool EffectPluginFactory::enabledByDefault() const { return true; } bool EffectPluginFactory::isSupported() const { return true; } //**************************************** // EffectsHandler //**************************************** EffectsHandler::EffectsHandler(CompositingType type) : compositing_type(type) { if (compositing_type == NoCompositing) return; KWin::effects = this; } EffectsHandler::~EffectsHandler() { // All effects should already be unloaded by Impl dtor assert(loaded_effects.count() == 0); KWin::effects = nullptr; } CompositingType EffectsHandler::compositingType() const { return compositing_type; } bool EffectsHandler::isOpenGLCompositing() const { return compositing_type & OpenGLCompositing; } EffectsHandler* effects = nullptr; //**************************************** // EffectWindow //**************************************** class Q_DECL_HIDDEN EffectWindow::Private { public: Private(EffectWindow *q); EffectWindow *q; }; EffectWindow::Private::Private(EffectWindow *q) : q(q) { } EffectWindow::EffectWindow(QObject *parent) : QObject(parent) , d(new Private(this)) { } EffectWindow::~EffectWindow() { } bool EffectWindow::isOnActivity(QString activity) const { const QStringList _activities = activities(); return _activities.isEmpty() || _activities.contains(activity); } bool EffectWindow::isOnAllActivities() const { return activities().isEmpty(); } void EffectWindow::setMinimized(bool min) { if (min) { minimize(); } else { unminimize(); } } bool EffectWindow::isOnCurrentActivity() const { return isOnActivity(effects->currentActivity()); } bool EffectWindow::isOnCurrentDesktop() const { return isOnDesktop(effects->currentDesktop()); } bool EffectWindow::isOnDesktop(int d) const { const QVector ds = desktops(); return ds.isEmpty() || ds.contains(d); } bool EffectWindow::isOnAllDesktops() const { return desktops().isEmpty(); } bool EffectWindow::hasDecoration() const { return contentsRect() != QRect(0, 0, width(), height()); } bool EffectWindow::isVisible() const { return !isMinimized() && isOnCurrentDesktop() && isOnCurrentActivity(); } //**************************************** // EffectWindowGroup //**************************************** EffectWindowGroup::~EffectWindowGroup() { } /*************************************************************** WindowQuad ***************************************************************/ WindowQuad WindowQuad::makeSubQuad(double x1, double y1, double x2, double y2) const { assert(x1 < x2 && y1 < y2 && x1 >= left() && x2 <= right() && y1 >= top() && y2 <= bottom()); #ifndef NDEBUG if (isTransformed()) qFatal("Splitting quads is allowed only in pre-paint calls!"); #endif WindowQuad ret(*this); // vertices are clockwise starting from topleft ret.verts[ 0 ].px = x1; ret.verts[ 3 ].px = x1; ret.verts[ 1 ].px = x2; ret.verts[ 2 ].px = x2; ret.verts[ 0 ].py = y1; ret.verts[ 1 ].py = y1; ret.verts[ 2 ].py = y2; ret.verts[ 3 ].py = y2; // original x/y are supposed to be the same, no transforming is done here ret.verts[ 0 ].ox = x1; ret.verts[ 3 ].ox = x1; ret.verts[ 1 ].ox = x2; ret.verts[ 2 ].ox = x2; ret.verts[ 0 ].oy = y1; ret.verts[ 1 ].oy = y1; ret.verts[ 2 ].oy = y2; ret.verts[ 3 ].oy = y2; const double my_u0 = verts[0].tx; const double my_u1 = verts[2].tx; const double my_v0 = verts[0].ty; const double my_v1 = verts[2].ty; const double width = right() - left(); const double height = bottom() - top(); const double texWidth = my_u1 - my_u0; const double texHeight = my_v1 - my_v0; if (!uvAxisSwapped()) { const double u0 = (x1 - left()) / width * texWidth + my_u0; const double u1 = (x2 - left()) / width * texWidth + my_u0; const double v0 = (y1 - top()) / height * texHeight + my_v0; const double v1 = (y2 - top()) / height * texHeight + my_v0; ret.verts[0].tx = u0; ret.verts[3].tx = u0; ret.verts[1].tx = u1; ret.verts[2].tx = u1; ret.verts[0].ty = v0; ret.verts[1].ty = v0; ret.verts[2].ty = v1; ret.verts[3].ty = v1; } else { const double u0 = (y1 - top()) / height * texWidth + my_u0; const double u1 = (y2 - top()) / height * texWidth + my_u0; const double v0 = (x1 - left()) / width * texHeight + my_v0; const double v1 = (x2 - left()) / width * texHeight + my_v0; ret.verts[0].tx = u0; ret.verts[1].tx = u0; ret.verts[2].tx = u1; ret.verts[3].tx = u1; ret.verts[0].ty = v0; ret.verts[3].ty = v0; ret.verts[1].ty = v1; ret.verts[2].ty = v1; } ret.setUVAxisSwapped(uvAxisSwapped()); return ret; } bool WindowQuad::smoothNeeded() const { // smoothing is needed if the width or height of the quad does not match the original size double width = verts[ 1 ].ox - verts[ 0 ].ox; double height = verts[ 2 ].oy - verts[ 1 ].oy; return(verts[ 1 ].px - verts[ 0 ].px != width || verts[ 2 ].px - verts[ 3 ].px != width || verts[ 2 ].py - verts[ 1 ].py != height || verts[ 3 ].py - verts[ 0 ].py != height); } /*************************************************************** WindowQuadList ***************************************************************/ WindowQuadList WindowQuadList::splitAtX(double x) const { WindowQuadList ret; foreach (const WindowQuad & quad, *this) { #ifndef NDEBUG if (quad.isTransformed()) qFatal("Splitting quads is allowed only in pre-paint calls!"); #endif bool wholeleft = true; bool wholeright = true; for (int i = 0; i < 4; ++i) { if (quad[ i ].x() < x) wholeright = false; if (quad[ i ].x() > x) wholeleft = false; } if (wholeleft || wholeright) { // is whole in one split part ret.append(quad); continue; } if (quad.top() == quad.bottom() || quad.left() == quad.right()) { // quad has no size ret.append(quad); continue; } ret.append(quad.makeSubQuad(quad.left(), quad.top(), x, quad.bottom())); ret.append(quad.makeSubQuad(x, quad.top(), quad.right(), quad.bottom())); } return ret; } WindowQuadList WindowQuadList::splitAtY(double y) const { WindowQuadList ret; foreach (const WindowQuad & quad, *this) { #ifndef NDEBUG if (quad.isTransformed()) qFatal("Splitting quads is allowed only in pre-paint calls!"); #endif bool wholetop = true; bool wholebottom = true; for (int i = 0; i < 4; ++i) { if (quad[ i ].y() < y) wholebottom = false; if (quad[ i ].y() > y) wholetop = false; } if (wholetop || wholebottom) { // is whole in one split part ret.append(quad); continue; } if (quad.top() == quad.bottom() || quad.left() == quad.right()) { // quad has no size ret.append(quad); continue; } ret.append(quad.makeSubQuad(quad.left(), quad.top(), quad.right(), y)); ret.append(quad.makeSubQuad(quad.left(), y, quad.right(), quad.bottom())); } return ret; } WindowQuadList WindowQuadList::makeGrid(int maxQuadSize) const { if (empty()) return *this; // Find the bounding rectangle double left = first().left(); double right = first().right(); double top = first().top(); double bottom = first().bottom(); foreach (const WindowQuad &quad, *this) { #ifndef NDEBUG if (quad.isTransformed()) qFatal("Splitting quads is allowed only in pre-paint calls!"); #endif left = qMin(left, quad.left()); right = qMax(right, quad.right()); top = qMin(top, quad.top()); bottom = qMax(bottom, quad.bottom()); } WindowQuadList ret; foreach (const WindowQuad &quad, *this) { const double quadLeft = quad.left(); const double quadRight = quad.right(); const double quadTop = quad.top(); const double quadBottom = quad.bottom(); // sanity check, see BUG 390953 if (quadLeft == quadRight || quadTop == quadBottom) { ret.append(quad); continue; } // Compute the top-left corner of the first intersecting grid cell const double xBegin = left + qFloor((quadLeft - left) / maxQuadSize) * maxQuadSize; const double yBegin = top + qFloor((quadTop - top) / maxQuadSize) * maxQuadSize; // Loop over all intersecting cells and add sub-quads for (double y = yBegin; y < quadBottom; y += maxQuadSize) { const double y0 = qMax(y, quadTop); const double y1 = qMin(quadBottom, y + maxQuadSize); for (double x = xBegin; x < quadRight; x += maxQuadSize) { const double x0 = qMax(x, quadLeft); const double x1 = qMin(quadRight, x + maxQuadSize); ret.append(quad.makeSubQuad(x0, y0, x1, y1)); } } } return ret; } WindowQuadList WindowQuadList::makeRegularGrid(int xSubdivisions, int ySubdivisions) const { if (empty()) return *this; // Find the bounding rectangle double left = first().left(); double right = first().right(); double top = first().top(); double bottom = first().bottom(); foreach (const WindowQuad &quad, *this) { #ifndef NDEBUG if (quad.isTransformed()) qFatal("Splitting quads is allowed only in pre-paint calls!"); #endif left = qMin(left, quad.left()); right = qMax(right, quad.right()); top = qMin(top, quad.top()); bottom = qMax(bottom, quad.bottom()); } double xIncrement = (right - left) / xSubdivisions; double yIncrement = (bottom - top) / ySubdivisions; WindowQuadList ret; foreach (const WindowQuad &quad, *this) { const double quadLeft = quad.left(); const double quadRight = quad.right(); const double quadTop = quad.top(); const double quadBottom = quad.bottom(); // sanity check, see BUG 390953 if (quadLeft == quadRight || quadTop == quadBottom) { ret.append(quad); continue; } // Compute the top-left corner of the first intersecting grid cell const double xBegin = left + qFloor((quadLeft - left) / xIncrement) * xIncrement; const double yBegin = top + qFloor((quadTop - top) / yIncrement) * yIncrement; // Loop over all intersecting cells and add sub-quads for (double y = yBegin; y < quadBottom; y += yIncrement) { const double y0 = qMax(y, quadTop); const double y1 = qMin(quadBottom, y + yIncrement); for (double x = xBegin; x < quadRight; x += xIncrement) { const double x0 = qMax(x, quadLeft); const double x1 = qMin(quadRight, x + xIncrement); ret.append(quad.makeSubQuad(x0, y0, x1, y1)); } } } return ret; } #ifndef GL_TRIANGLES # define GL_TRIANGLES 0x0004 #endif #ifndef GL_QUADS # define GL_QUADS 0x0007 #endif void WindowQuadList::makeInterleavedArrays(unsigned int type, GLVertex2D *vertices, const QMatrix4x4 &textureMatrix) const { // Since we know that the texture matrix just scales and translates // we can use this information to optimize the transformation const QVector2D coeff(textureMatrix(0, 0), textureMatrix(1, 1)); const QVector2D offset(textureMatrix(0, 3), textureMatrix(1, 3)); GLVertex2D *vertex = vertices; assert(type == GL_QUADS || type == GL_TRIANGLES); switch (type) { case GL_QUADS: #ifdef HAVE_SSE2 if (!(intptr_t(vertex) & 0xf)) { for (int i = 0; i < count(); i++) { const WindowQuad &quad = at(i); KWIN_ALIGN(16) GLVertex2D v[4]; for (int j = 0; j < 4; j++) { const WindowVertex &wv = quad[j]; v[j].position = QVector2D(wv.x(), wv.y()); v[j].texcoord = QVector2D(wv.u(), wv.v()) * coeff + offset; } const __m128i *srcP = (const __m128i *) &v; __m128i *dstP = (__m128i *) vertex; _mm_stream_si128(&dstP[0], _mm_load_si128(&srcP[0])); // Top-left _mm_stream_si128(&dstP[1], _mm_load_si128(&srcP[1])); // Top-right _mm_stream_si128(&dstP[2], _mm_load_si128(&srcP[2])); // Bottom-right _mm_stream_si128(&dstP[3], _mm_load_si128(&srcP[3])); // Bottom-left vertex += 4; } } else #endif // HAVE_SSE2 { for (int i = 0; i < count(); i++) { const WindowQuad &quad = at(i); for (int j = 0; j < 4; j++) { const WindowVertex &wv = quad[j]; GLVertex2D v; v.position = QVector2D(wv.x(), wv.y()); v.texcoord = QVector2D(wv.u(), wv.v()) * coeff + offset; *(vertex++) = v; } } } break; case GL_TRIANGLES: #ifdef HAVE_SSE2 if (!(intptr_t(vertex) & 0xf)) { for (int i = 0; i < count(); i++) { const WindowQuad &quad = at(i); KWIN_ALIGN(16) GLVertex2D v[4]; for (int j = 0; j < 4; j++) { const WindowVertex &wv = quad[j]; v[j].position = QVector2D(wv.x(), wv.y()); v[j].texcoord = QVector2D(wv.u(), wv.v()) * coeff + offset; } const __m128i *srcP = (const __m128i *) &v; __m128i *dstP = (__m128i *) vertex; __m128i src[4]; src[0] = _mm_load_si128(&srcP[0]); // Top-left src[1] = _mm_load_si128(&srcP[1]); // Top-right src[2] = _mm_load_si128(&srcP[2]); // Bottom-right src[3] = _mm_load_si128(&srcP[3]); // Bottom-left // First triangle _mm_stream_si128(&dstP[0], src[1]); // Top-right _mm_stream_si128(&dstP[1], src[0]); // Top-left _mm_stream_si128(&dstP[2], src[3]); // Bottom-left // Second triangle _mm_stream_si128(&dstP[3], src[3]); // Bottom-left _mm_stream_si128(&dstP[4], src[2]); // Bottom-right _mm_stream_si128(&dstP[5], src[1]); // Top-right vertex += 6; } } else #endif // HAVE_SSE2 { for (int i = 0; i < count(); i++) { const WindowQuad &quad = at(i); GLVertex2D v[4]; // Four unique vertices / quad for (int j = 0; j < 4; j++) { const WindowVertex &wv = quad[j]; v[j].position = QVector2D(wv.x(), wv.y()); v[j].texcoord = QVector2D(wv.u(), wv.v()) * coeff + offset; } // First triangle *(vertex++) = v[1]; // Top-right *(vertex++) = v[0]; // Top-left *(vertex++) = v[3]; // Bottom-left // Second triangle *(vertex++) = v[3]; // Bottom-left *(vertex++) = v[2]; // Bottom-right *(vertex++) = v[1]; // Top-right } } break; default: break; } } void WindowQuadList::makeArrays(float **vertices, float **texcoords, const QSizeF &size, bool yInverted) const { *vertices = new float[count() * 6 * 2]; *texcoords = new float[count() * 6 * 2]; float *vpos = *vertices; float *tpos = *texcoords; // Note: The positions in a WindowQuad are stored in clockwise order const int index[] = { 1, 0, 3, 3, 2, 1 }; for (int i = 0; i < count(); i++) { const WindowQuad &quad = at(i); for (int j = 0; j < 6; j++) { const WindowVertex &wv = quad[index[j]]; *vpos++ = wv.x(); *vpos++ = wv.y(); *tpos++ = wv.u() / size.width(); *tpos++ = yInverted ? (wv.v() / size.height()) : (1.0 - wv.v() / size.height()); } } } WindowQuadList WindowQuadList::select(WindowQuadType type) const { foreach (const WindowQuad & q, *this) { if (q.type() != type) { // something else than ones to select, make a copy and filter WindowQuadList ret; foreach (const WindowQuad & q, *this) { if (q.type() == type) ret.append(q); } return ret; } } return *this; // nothing to filter out } WindowQuadList WindowQuadList::filterOut(WindowQuadType type) const { foreach (const WindowQuad & q, *this) { if (q.type() == type) { // something to filter out, make a copy and filter WindowQuadList ret; foreach (const WindowQuad & q, *this) { if (q.type() != type) ret.append(q); } return ret; } } return *this; // nothing to filter out } bool WindowQuadList::smoothNeeded() const { foreach (const WindowQuad & q, *this) if (q.smoothNeeded()) return true; return false; } bool WindowQuadList::isTransformed() const { foreach (const WindowQuad & q, *this) if (q.isTransformed()) return true; return false; } /*************************************************************** PaintClipper ***************************************************************/ QStack< QRegion >* PaintClipper::areas = nullptr; PaintClipper::PaintClipper(const QRegion& allowed_area) : area(allowed_area) { push(area); } PaintClipper::~PaintClipper() { pop(area); } void PaintClipper::push(const QRegion& allowed_area) { if (allowed_area == infiniteRegion()) // don't push these return; if (areas == nullptr) areas = new QStack< QRegion >; areas->push(allowed_area); } void PaintClipper::pop(const QRegion& allowed_area) { if (allowed_area == infiniteRegion()) return; Q_ASSERT(areas != nullptr); Q_ASSERT(areas->top() == allowed_area); areas->pop(); if (areas->isEmpty()) { delete areas; areas = nullptr; } } bool PaintClipper::clip() { return areas != nullptr; } QRegion PaintClipper::paintArea() { assert(areas != nullptr); // can be called only with clip() == true const QSize &s = effects->virtualScreenSize(); QRegion ret = QRegion(0, 0, s.width(), s.height()); foreach (const QRegion & r, *areas) ret &= r; return ret; } struct PaintClipper::Iterator::Data { Data() : index(0) {} int index; QRegion region; }; PaintClipper::Iterator::Iterator() : data(new Data) { if (clip() && effects->isOpenGLCompositing()) { data->region = paintArea(); data->index = -1; next(); // move to the first one } #ifdef KWIN_HAVE_XRENDER_COMPOSITING if (clip() && effects->compositingType() == XRenderCompositing) { XFixesRegion region(paintArea()); xcb_xfixes_set_picture_clip_region(connection(), effects->xrenderBufferPicture(), region, 0, 0); } #endif } PaintClipper::Iterator::~Iterator() { #ifdef KWIN_HAVE_XRENDER_COMPOSITING if (clip() && effects->compositingType() == XRenderCompositing) xcb_xfixes_set_picture_clip_region(connection(), effects->xrenderBufferPicture(), XCB_XFIXES_REGION_NONE, 0, 0); #endif delete data; } bool PaintClipper::Iterator::isDone() { if (!clip()) return data->index == 1; // run once if (effects->isOpenGLCompositing()) return data->index >= data->region.rectCount(); // run once per each area #ifdef KWIN_HAVE_XRENDER_COMPOSITING if (effects->compositingType() == XRenderCompositing) return data->index == 1; // run once #endif abort(); } void PaintClipper::Iterator::next() { data->index++; } QRect PaintClipper::Iterator::boundingRect() const { if (!clip()) return infiniteRegion(); if (effects->isOpenGLCompositing()) return *(data->region.begin() + data->index); #ifdef KWIN_HAVE_XRENDER_COMPOSITING if (effects->compositingType() == XRenderCompositing) return data->region.boundingRect(); #endif abort(); return infiniteRegion(); } /*************************************************************** Motion1D ***************************************************************/ Motion1D::Motion1D(double initial, double strength, double smoothness) : Motion(initial, strength, smoothness) { } Motion1D::Motion1D(const Motion1D &other) : Motion(other) { } Motion1D::~Motion1D() { } /*************************************************************** Motion2D ***************************************************************/ Motion2D::Motion2D(QPointF initial, double strength, double smoothness) : Motion(initial, strength, smoothness) { } Motion2D::Motion2D(const Motion2D &other) : Motion(other) { } Motion2D::~Motion2D() { } /*************************************************************** WindowMotionManager ***************************************************************/ WindowMotionManager::WindowMotionManager(bool useGlobalAnimationModifier) : m_useGlobalAnimationModifier(useGlobalAnimationModifier) { // TODO: Allow developer to modify motion attributes } // TODO: What happens when the window moves by an external force? WindowMotionManager::~WindowMotionManager() { } void WindowMotionManager::manage(EffectWindow *w) { if (m_managedWindows.contains(w)) return; double strength = 0.08; double smoothness = 4.0; if (m_useGlobalAnimationModifier && effects->animationTimeFactor()) { // If the factor is == 0 then we just skip the calculation completely strength = 0.08 / effects->animationTimeFactor(); smoothness = effects->animationTimeFactor() * 4.0; } WindowMotion &motion = m_managedWindows[ w ]; motion.translation.setStrength(strength); motion.translation.setSmoothness(smoothness); motion.scale.setStrength(strength * 1.33); motion.scale.setSmoothness(smoothness / 2.0); motion.translation.setValue(w->pos()); motion.scale.setValue(QPointF(1.0, 1.0)); } void WindowMotionManager::unmanage(EffectWindow *w) { m_movingWindowsSet.remove(w); m_managedWindows.remove(w); } void WindowMotionManager::unmanageAll() { m_managedWindows.clear(); m_movingWindowsSet.clear(); } void WindowMotionManager::calculate(int time) { if (!effects->animationTimeFactor()) { // Just skip it completely if the user wants no animation m_movingWindowsSet.clear(); QHash::iterator it = m_managedWindows.begin(); for (; it != m_managedWindows.end(); ++it) { WindowMotion *motion = &it.value(); motion->translation.finish(); motion->scale.finish(); } } QHash::iterator it = m_managedWindows.begin(); for (; it != m_managedWindows.end(); ++it) { WindowMotion *motion = &it.value(); int stopped = 0; // TODO: What happens when distance() == 0 but we are still moving fast? // TODO: Motion needs to be calculated from the window's center Motion2D *trans = &motion->translation; if (trans->distance().isNull()) ++stopped; else { // Still moving trans->calculate(time); const short fx = trans->target().x() <= trans->startValue().x() ? -1 : 1; const short fy = trans->target().y() <= trans->startValue().y() ? -1 : 1; if (trans->distance().x()*fx/0.5 < 1.0 && trans->velocity().x()*fx/0.2 < 1.0 && trans->distance().y()*fy/0.5 < 1.0 && trans->velocity().y()*fy/0.2 < 1.0) { // Hide tiny oscillations motion->translation.finish(); ++stopped; } } Motion2D *scale = &motion->scale; if (scale->distance().isNull()) ++stopped; else { // Still scaling scale->calculate(time); const short fx = scale->target().x() < 1.0 ? -1 : 1; const short fy = scale->target().y() < 1.0 ? -1 : 1; if (scale->distance().x()*fx/0.001 < 1.0 && scale->velocity().x()*fx/0.05 < 1.0 && scale->distance().y()*fy/0.001 < 1.0 && scale->velocity().y()*fy/0.05 < 1.0) { // Hide tiny oscillations motion->scale.finish(); ++stopped; } } // We just finished this window's motion if (stopped == 2) m_movingWindowsSet.remove(it.key()); } } void WindowMotionManager::reset() { QHash::iterator it = m_managedWindows.begin(); for (; it != m_managedWindows.end(); ++it) { WindowMotion *motion = &it.value(); EffectWindow *window = it.key(); motion->translation.setTarget(window->pos()); motion->translation.finish(); motion->scale.setTarget(QPointF(1.0, 1.0)); motion->scale.finish(); } } void WindowMotionManager::reset(EffectWindow *w) { QHash::iterator it = m_managedWindows.find(w); if (it == m_managedWindows.end()) return; WindowMotion *motion = &it.value(); motion->translation.setTarget(w->pos()); motion->translation.finish(); motion->scale.setTarget(QPointF(1.0, 1.0)); motion->scale.finish(); } void WindowMotionManager::apply(EffectWindow *w, WindowPaintData &data) { QHash::iterator it = m_managedWindows.find(w); if (it == m_managedWindows.end()) return; // TODO: Take into account existing scale so that we can work with multiple managers (E.g. Present windows + grid) WindowMotion *motion = &it.value(); data += (motion->translation.value() - QPointF(w->x(), w->y())); data *= QVector2D(motion->scale.value()); } void WindowMotionManager::moveWindow(EffectWindow *w, QPoint target, double scale, double yScale) { QHash::iterator it = m_managedWindows.find(w); if (it == m_managedWindows.end()) abort(); // Notify the effect author that they did something wrong WindowMotion *motion = &it.value(); if (yScale == 0.0) yScale = scale; QPointF scalePoint(scale, yScale); if (motion->translation.value() == target && motion->scale.value() == scalePoint) return; // Window already at that position motion->translation.setTarget(target); motion->scale.setTarget(scalePoint); m_movingWindowsSet << w; } QRectF WindowMotionManager::transformedGeometry(EffectWindow *w) const { QHash::const_iterator it = m_managedWindows.constFind(w); if (it == m_managedWindows.end()) return w->geometry(); const WindowMotion *motion = &it.value(); QRectF geometry(w->geometry()); // TODO: Take into account existing scale so that we can work with multiple managers (E.g. Present windows + grid) geometry.moveTo(motion->translation.value()); geometry.setWidth(geometry.width() * motion->scale.value().x()); geometry.setHeight(geometry.height() * motion->scale.value().y()); return geometry; } void WindowMotionManager::setTransformedGeometry(EffectWindow *w, const QRectF &geometry) { QHash::iterator it = m_managedWindows.find(w); if (it == m_managedWindows.end()) return; WindowMotion *motion = &it.value(); motion->translation.setValue(geometry.topLeft()); motion->scale.setValue(QPointF(geometry.width() / qreal(w->width()), geometry.height() / qreal(w->height()))); } QRectF WindowMotionManager::targetGeometry(EffectWindow *w) const { QHash::const_iterator it = m_managedWindows.constFind(w); if (it == m_managedWindows.end()) return w->geometry(); const WindowMotion *motion = &it.value(); QRectF geometry(w->geometry()); // TODO: Take into account existing scale so that we can work with multiple managers (E.g. Present windows + grid) geometry.moveTo(motion->translation.target()); geometry.setWidth(geometry.width() * motion->scale.target().x()); geometry.setHeight(geometry.height() * motion->scale.target().y()); return geometry; } EffectWindow* WindowMotionManager::windowAtPoint(QPoint point, bool useStackingOrder) const { Q_UNUSED(useStackingOrder); // TODO: Stacking order uses EffectsHandler::stackingOrder() then filters by m_managedWindows QHash< EffectWindow*, WindowMotion >::ConstIterator it = m_managedWindows.constBegin(); while (it != m_managedWindows.constEnd()) { if (transformedGeometry(it.key()).contains(point)) return it.key(); ++it; } return nullptr; } /*************************************************************** EffectFramePrivate ***************************************************************/ class EffectFramePrivate { public: EffectFramePrivate(); ~EffectFramePrivate(); bool crossFading; qreal crossFadeProgress; QMatrix4x4 screenProjectionMatrix; }; EffectFramePrivate::EffectFramePrivate() : crossFading(false) , crossFadeProgress(1.0) { } EffectFramePrivate::~EffectFramePrivate() { } /*************************************************************** EffectFrame ***************************************************************/ EffectFrame::EffectFrame() : d(new EffectFramePrivate) { } EffectFrame::~EffectFrame() { delete d; } qreal EffectFrame::crossFadeProgress() const { return d->crossFadeProgress; } void EffectFrame::setCrossFadeProgress(qreal progress) { d->crossFadeProgress = progress; } bool EffectFrame::isCrossFade() const { return d->crossFading; } void EffectFrame::enableCrossFade(bool enable) { d->crossFading = enable; } QMatrix4x4 EffectFrame::screenProjectionMatrix() const { return d->screenProjectionMatrix; } void EffectFrame::setScreenProjectionMatrix(const QMatrix4x4 &spm) { d->screenProjectionMatrix = spm; } /*************************************************************** TimeLine ***************************************************************/ class Q_DECL_HIDDEN TimeLine::Data : public QSharedData { public: std::chrono::milliseconds duration; Direction direction; QEasingCurve easingCurve; std::chrono::milliseconds elapsed = std::chrono::milliseconds::zero(); bool done = false; RedirectMode sourceRedirectMode = RedirectMode::Relaxed; RedirectMode targetRedirectMode = RedirectMode::Strict; }; TimeLine::TimeLine(std::chrono::milliseconds duration, Direction direction) : d(new Data) { Q_ASSERT(duration > std::chrono::milliseconds::zero()); d->duration = duration; d->direction = direction; } TimeLine::TimeLine(const TimeLine &other) : d(other.d) { } TimeLine::~TimeLine() = default; qreal TimeLine::progress() const { return static_cast(d->elapsed.count()) / d->duration.count(); } qreal TimeLine::value() const { const qreal t = progress(); return d->easingCurve.valueForProgress( d->direction == Backward ? 1.0 - t : t); } void TimeLine::update(std::chrono::milliseconds delta) { Q_ASSERT(delta >= std::chrono::milliseconds::zero()); if (d->done) { return; } d->elapsed += delta; if (d->elapsed >= d->duration) { d->done = true; d->elapsed = d->duration; } } std::chrono::milliseconds TimeLine::elapsed() const { return d->elapsed; } void TimeLine::setElapsed(std::chrono::milliseconds elapsed) { Q_ASSERT(elapsed >= std::chrono::milliseconds::zero()); if (elapsed == d->elapsed) { return; } reset(); update(elapsed); } std::chrono::milliseconds TimeLine::duration() const { return d->duration; } void TimeLine::setDuration(std::chrono::milliseconds duration) { Q_ASSERT(duration > std::chrono::milliseconds::zero()); if (duration == d->duration) { return; } d->elapsed = std::chrono::milliseconds(qRound(progress() * duration.count())); d->duration = duration; if (d->elapsed == d->duration) { d->done = true; } } TimeLine::Direction TimeLine::direction() const { return d->direction; } void TimeLine::setDirection(TimeLine::Direction direction) { if (d->direction == direction) { return; } d->direction = direction; if (d->elapsed > std::chrono::milliseconds::zero() || d->sourceRedirectMode == RedirectMode::Strict) { d->elapsed = d->duration - d->elapsed; } if (d->done && d->targetRedirectMode == RedirectMode::Relaxed) { d->done = false; } if (d->elapsed >= d->duration) { d->done = true; } } void TimeLine::toggleDirection() { setDirection(d->direction == Forward ? Backward : Forward); } QEasingCurve TimeLine::easingCurve() const { return d->easingCurve; } void TimeLine::setEasingCurve(const QEasingCurve &easingCurve) { d->easingCurve = easingCurve; } void TimeLine::setEasingCurve(QEasingCurve::Type type) { d->easingCurve.setType(type); } bool TimeLine::running() const { return d->elapsed != std::chrono::milliseconds::zero() && d->elapsed != d->duration; } bool TimeLine::done() const { return d->done; } void TimeLine::reset() { d->elapsed = std::chrono::milliseconds::zero(); d->done = false; } TimeLine::RedirectMode TimeLine::sourceRedirectMode() const { return d->sourceRedirectMode; } void TimeLine::setSourceRedirectMode(RedirectMode mode) { d->sourceRedirectMode = mode; } TimeLine::RedirectMode TimeLine::targetRedirectMode() const { return d->targetRedirectMode; } void TimeLine::setTargetRedirectMode(RedirectMode mode) { d->targetRedirectMode = mode; } TimeLine &TimeLine::operator=(const TimeLine &other) { d = other.d; return *this; } } // namespace diff --git a/libkwineffects/kwineffects.h b/libkwineffects/kwineffects.h index 7b2f3d73b..6e8b86854 100644 --- a/libkwineffects/kwineffects.h +++ b/libkwineffects/kwineffects.h @@ -1,3953 +1,3953 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2006 Lubos Lunak Copyright (C) 2009 Lucas Murray Copyright (C) 2010, 2011 Martin Gräßlin 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 . *********************************************************************/ #ifndef KWINEFFECTS_H #define KWINEFFECTS_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 class KConfigGroup; class QFont; class QGraphicsScale; class QKeyEvent; class QMatrix4x4; class QAction; /** * Logging category to be used inside the KWin effects. * Do not use in this library. **/ Q_DECLARE_LOGGING_CATEGORY(KWINEFFECTS) namespace KWayland { namespace Server { class SurfaceInterface; class Display; } } namespace KWin { class PaintDataPrivate; class WindowPaintDataPrivate; class EffectWindow; class EffectWindowGroup; class EffectFrame; class EffectFramePrivate; class Effect; class WindowQuad; class GLShader; class XRenderPicture; class WindowQuadList; class WindowPrePaintData; class WindowPaintData; class ScreenPrePaintData; class ScreenPaintData; typedef QPair< QString, Effect* > EffectPair; typedef QList< KWin::EffectWindow* > EffectWindowList; /** @defgroup kwineffects KWin effects library * KWin effects library contains necessary classes for creating new KWin * compositing effects. * * @section creating Creating new effects * This example will demonstrate the basics of creating an effect. We'll use * CoolEffect as the class name, cooleffect as internal name and * "Cool Effect" as user-visible name of the effect. * * This example doesn't demonstrate how to write the effect's code. For that, * see the documentation of the Effect class. * * @subsection creating-class CoolEffect class * First you need to create CoolEffect class which has to be a subclass of * @ref KWin::Effect. In that class you can reimplement various virtual * methods to control how and where the windows are drawn. * * @subsection creating-macro KWIN_EFFECT_FACTORY macro * This library provides a specialized KPluginFactory subclass and macros to * create a sub class. This subclass of KPluginFactory has to be used, otherwise * KWin won't load the plugin. Use the @ref KWIN_EFFECT_FACTORY macro to create the * plugin factory. * * @subsection creating-buildsystem Buildsystem * To build the effect, you can use the KWIN_ADD_EFFECT() cmake macro which * can be found in effects/CMakeLists.txt file in KWin's source. First * argument of the macro is the name of the library that will contain * your effect. Although not strictly required, it is usually a good idea to * use the same name as your effect's internal name there. Following arguments * to the macro are the files containing your effect's source. If our effect's * source is in cooleffect.cpp, we'd use following: * @code * KWIN_ADD_EFFECT(cooleffect cooleffect.cpp) * @endcode * * This macro takes care of compiling your effect. You'll also need to install * your effect's .desktop file, so the example CMakeLists.txt file would be * as follows: * @code * KWIN_ADD_EFFECT(cooleffect cooleffect.cpp) * install( FILES cooleffect.desktop DESTINATION ${SERVICES_INSTALL_DIR}/kwin ) * @endcode * * @subsection creating-desktop Effect's .desktop file * You will also need to create .desktop file to set name, description, icon * and other properties of your effect. Important fields of the .desktop file * are: * @li Name User-visible name of your effect * @li Icon Name of the icon of the effect * @li Comment Short description of the effect * @li Type must be "Service" * @li X-KDE-ServiceTypes must be "KWin/Effect" * @li X-KDE-PluginInfo-Name effect's internal name as passed to the KWIN_EFFECT macro plus "kwin4_effect_" prefix * @li X-KDE-PluginInfo-Category effect's category. Should be one of Appearance, Accessibility, Window Management, Demos, Tests, Misc * @li X-KDE-PluginInfo-EnabledByDefault whether the effect should be enabled by default (use sparingly). Default is false * @li X-KDE-Library name of the library containing the effect. This is the first argument passed to the KWIN_ADD_EFFECT macro in cmake file plus "kwin4_effect_" prefix. * * Example cooleffect.desktop file follows: * @code [Desktop Entry] Name=Cool Effect Comment=The coolest effect you've ever seen Icon=preferences-system-windows-effect-cooleffect Type=Service X-KDE-ServiceTypes=KWin/Effect X-KDE-PluginInfo-Author=My Name X-KDE-PluginInfo-Email=my@email.here X-KDE-PluginInfo-Name=kwin4_effect_cooleffect X-KDE-PluginInfo-Category=Misc X-KDE-Library=kwin4_effect_cooleffect * @endcode * * * @section accessing Accessing windows and workspace * Effects can gain access to the properties of windows and workspace via * EffectWindow and EffectsHandler classes. * * There is one global EffectsHandler object which you can access using the * @ref effects pointer. * For each window, there is an EffectWindow object which can be used to read * window properties such as position and also to change them. * * For more information about this, see the documentation of the corresponding * classes. * * @{ **/ #define KWIN_EFFECT_API_MAKE_VERSION( major, minor ) (( major ) << 8 | ( minor )) #define KWIN_EFFECT_API_VERSION_MAJOR 0 #define KWIN_EFFECT_API_VERSION_MINOR 228 #define KWIN_EFFECT_API_VERSION KWIN_EFFECT_API_MAKE_VERSION( \ KWIN_EFFECT_API_VERSION_MAJOR, KWIN_EFFECT_API_VERSION_MINOR ) enum WindowQuadType { WindowQuadError, // for the stupid default ctor WindowQuadContents, WindowQuadDecoration, // Shadow Quad types WindowQuadShadow, // OpenGL only. The other shadow types are only used by Xrender WindowQuadShadowTop, WindowQuadShadowTopRight, WindowQuadShadowRight, WindowQuadShadowBottomRight, WindowQuadShadowBottom, WindowQuadShadowBottomLeft, WindowQuadShadowLeft, WindowQuadShadowTopLeft, EFFECT_QUAD_TYPE_START = 100 ///< @internal }; /** * EffectWindow::setData() and EffectWindow::data() global roles. * All values between 0 and 999 are reserved for global roles. **/ enum DataRole { // Grab roles are used to force all other animations to ignore the window. // The value of the data is set to the Effect's `this` value. WindowAddedGrabRole = 1, WindowClosedGrabRole, WindowMinimizedGrabRole, WindowUnminimizedGrabRole, WindowForceBlurRole, ///< For fullscreen effects to enforce blurring of windows, WindowBlurBehindRole, ///< For single windows to blur behind WindowForceBackgroundContrastRole, ///< For fullscreen effects to enforce the background contrast, WindowBackgroundContrastRole, ///< For single windows to enable Background contrast LanczosCacheRole }; /** * Style types used by @ref EffectFrame. * @since 4.6 **/ enum EffectFrameStyle { EffectFrameNone, ///< Displays no frame around the contents. EffectFrameUnstyled, ///< Displays a basic box around the contents. EffectFrameStyled ///< Displays a Plasma-styled frame around the contents. }; /** * Infinite region (i.e. a special region type saying that everything needs to be painted). **/ KWINEFFECTS_EXPORT inline QRect infiniteRegion() { // INT_MIN / 2 because width/height is used (INT_MIN+INT_MAX==-1) return QRect(INT_MIN / 2, INT_MIN / 2, INT_MAX, INT_MAX); } /** * @short Base class for all KWin effects * * This is the base class for all effects. By reimplementing virtual methods * of this class, you can customize how the windows are painted. * * The virtual methods are used for painting and need to be implemented for * custom painting. * * In order to react to state changes (e.g. a window gets closed) the effect * should provide slots for the signals emitted by the EffectsHandler. * * @section Chaining * Most methods of this class are called in chain style. This means that when * effects A and B area active then first e.g. A::paintWindow() is called and * then from within that method B::paintWindow() is called (although * indirectly). To achieve this, you need to make sure to call corresponding * method in EffectsHandler class from each such method (using @ref effects * pointer): * @code * void MyEffect::postPaintScreen() * { * // Do your own processing here * ... * // Call corresponding EffectsHandler method * effects->postPaintScreen(); * } * @endcode * * @section Effectsptr Effects pointer * @ref effects pointer points to the global EffectsHandler object that you can * use to interact with the windows. * * @section painting Painting stages * Painting of windows is done in three stages: * @li First, the prepaint pass.
* Here you can specify how the windows will be painted, e.g. that they will * be translucent and transformed. * @li Second, the paint pass.
* Here the actual painting takes place. You can change attributes such as * opacity of windows as well as apply transformations to them. You can also * paint something onto the screen yourself. * @li Finally, the postpaint pass.
* Here you can mark windows, part of windows or even the entire screen for * repainting to create animations. * * For each stage there are *Screen() and *Window() methods. The window method * is called for every window which the screen method is usually called just * once. * * @section OpenGL * Effects can use OpenGL if EffectsHandler::isOpenGLCompositing() returns @c true. * The OpenGL context may not always be current when code inside the effect is * executed. The framework ensures that the OpenGL context is current when the Effect * gets created, destroyed or reconfigured and during the painting stages. All virtual * methods which have the OpenGL context current are documented. * * If OpenGL code is going to be executed outside the painting stages, e.g. in reaction * to a global shortcut, it is the task of the Effect to make the OpenGL context current: * @code * effects->makeOpenGLContextCurrent(); * @endcode * * There is in general no need to call the matching doneCurrent method. **/ class KWINEFFECTS_EXPORT Effect : public QObject { Q_OBJECT public: /** Flags controlling how painting is done. */ // TODO: is that ok here? enum { /** * Window (or at least part of it) will be painted opaque. **/ PAINT_WINDOW_OPAQUE = 1 << 0, /** * Window (or at least part of it) will be painted translucent. **/ PAINT_WINDOW_TRANSLUCENT = 1 << 1, /** * Window will be painted with transformed geometry. **/ PAINT_WINDOW_TRANSFORMED = 1 << 2, /** * Paint only a region of the screen (can be optimized, cannot * be used together with TRANSFORMED flags). **/ PAINT_SCREEN_REGION = 1 << 3, /** * The whole screen will be painted with transformed geometry. * Forces the entire screen to be painted. **/ PAINT_SCREEN_TRANSFORMED = 1 << 4, /** * At least one window will be painted with transformed geometry. * Forces the entire screen to be painted. **/ PAINT_SCREEN_WITH_TRANSFORMED_WINDOWS = 1 << 5, /** * Clear whole background as the very first step, without optimizing it **/ PAINT_SCREEN_BACKGROUND_FIRST = 1 << 6, // PAINT_DECORATION_ONLY = 1 << 7 has been deprecated /** * Window will be painted with a lanczos filter. **/ PAINT_WINDOW_LANCZOS = 1 << 8 // PAINT_SCREEN_WITH_TRANSFORMED_WINDOWS_WITHOUT_FULL_REPAINTS = 1 << 9 has been removed }; enum Feature { Nothing = 0, Resize, GeometryTip, Outline, /**< @deprecated */ ScreenInversion, Blur, Contrast, HighlightWindows }; /** * Constructs new Effect object. * * In OpenGL based compositing, the frameworks ensures that the context is current * when the Effect is constructed. **/ Effect(); /** * Destructs the Effect object. * * In OpenGL based compositing, the frameworks ensures that the context is current * when the Effect is destroyed. **/ virtual ~Effect(); /** * Flags describing which parts of configuration have changed. **/ enum ReconfigureFlag { ReconfigureAll = 1 << 0 /// Everything needs to be reconfigured. }; Q_DECLARE_FLAGS(ReconfigureFlags, ReconfigureFlag) /** * Called when configuration changes (either the effect's or KWin's global). * * In OpenGL based compositing, the frameworks ensures that the context is current * when the Effect is reconfigured. If this method is called from within the Effect it is * required to ensure that the context is current if the implementation does OpenGL calls. **/ virtual void reconfigure(ReconfigureFlags flags); /** * Called when another effect requests the proxy for this effect. **/ virtual void* proxy(); /** * Called before starting to paint the screen. * In this method you can: * @li set whether the windows or the entire screen will be transformed * @li change the region of the screen that will be painted * @li do various housekeeping tasks such as initing your effect's variables for the upcoming paint pass or updating animation's progress * * In OpenGL based compositing, the frameworks ensures that the context is current * when this method is invoked. **/ virtual void prePaintScreen(ScreenPrePaintData& data, int time); /** * In this method you can: * @li paint something on top of the windows (by painting after calling * effects->paintScreen()) * @li paint multiple desktops and/or multiple copies of the same desktop * by calling effects->paintScreen() multiple times * * In OpenGL based compositing, the frameworks ensures that the context is current * when this method is invoked. **/ virtual void paintScreen(int mask, QRegion region, ScreenPaintData& data); /** * Called after all the painting has been finished. * In this method you can: * @li schedule next repaint in case of animations * You shouldn't paint anything here. * * In OpenGL based compositing, the frameworks ensures that the context is current * when this method is invoked. **/ virtual void postPaintScreen(); /** * Called for every window before the actual paint pass * In this method you can: * @li enable or disable painting of the window (e.g. enable paiting of minimized window) * @li set window to be painted with translucency * @li set window to be transformed * @li request the window to be divided into multiple parts * * In OpenGL based compositing, the frameworks ensures that the context is current * when this method is invoked. **/ virtual void prePaintWindow(EffectWindow* w, WindowPrePaintData& data, int time); /** * This is the main method for painting windows. * In this method you can: * @li do various transformations * @li change opacity of the window * @li change brightness and/or saturation, if it's supported * * In OpenGL based compositing, the frameworks ensures that the context is current * when this method is invoked. **/ virtual void paintWindow(EffectWindow* w, int mask, QRegion region, WindowPaintData& data); /** * Called for every window after all painting has been finished. * In this method you can: * @li schedule next repaint for individual window(s) in case of animations * You shouldn't paint anything here. * * In OpenGL based compositing, the frameworks ensures that the context is current * when this method is invoked. **/ virtual void postPaintWindow(EffectWindow* w); /** * This method is called directly before painting an @ref EffectFrame. * You can implement this method if you need to bind a shader or perform * other operations before the frame is rendered. * @param frame The EffectFrame which will be rendered * @param region Region to restrict painting to * @param opacity Opacity of text/icon * @param frameOpacity Opacity of background * @since 4.6 * * In OpenGL based compositing, the frameworks ensures that the context is current * when this method is invoked. **/ virtual void paintEffectFrame(EffectFrame* frame, QRegion region, double opacity, double frameOpacity); /** * Called on Transparent resizes. * return true if your effect substitutes questioned feature **/ virtual bool provides(Feature); /** * Performs the @p feature with the @p arguments. * * This allows to have specific protocols between KWin core and an Effect. * * The method is supposed to return @c true if it performed the features, * @c false otherwise. * * The default implementation returns @c false. * @since 5.8 **/ virtual bool perform(Feature feature, const QVariantList &arguments); /** * Can be called to draw multiple copies (e.g. thumbnails) of a window. * You can change window's opacity/brightness/etc here, but you can't * do any transformations. * * In OpenGL based compositing, the frameworks ensures that the context is current * when this method is invoked. **/ virtual void drawWindow(EffectWindow* w, int mask, QRegion region, WindowPaintData& data); /** * Define new window quads so that they can be transformed by other effects. * It's up to the effect to keep track of them. **/ virtual void buildQuads(EffectWindow* w, WindowQuadList& quadList); virtual void windowInputMouseEvent(QEvent* e); virtual void grabbedKeyboardEvent(QKeyEvent* e); /** * Overwrite this method to indicate whether your effect will be doing something in * the next frame to be rendered. If the method returns @c false the effect will be * excluded from the chained methods in the next rendered frame. * * This method is called always directly before the paint loop begins. So it is totally * fine to e.g. react on a window event, issue a repaint to trigger an animation and * change a flag to indicate that this method returns @c true. * * As the method is called each frame, you should not perform complex calculations. * Best use just a boolean flag. * * The default implementation of this method returns @c true. * @since 4.8 **/ virtual bool isActive() const; /** * Reimplement this method to provide online debugging. * This could be as trivial as printing specific detail information about the effect state * but could also be used to move the effect in and out of a special debug modes, clear bogus * data, etc. * Notice that the functions is const by intent! Whenever you alter the state of the object * due to random user input, you should do so with greatest care, hence const_cast<> your * object - signalling "let me alone, i know what i'm doing" * @param parameter A freeform string user input for your effect to interpret. * @since 4.11 **/ virtual QString debug(const QString ¶meter) const; /** * Reimplement this method to indicate where in the Effect chain the Effect should be placed. * * A low number indicates early chain position, thus before other Effects got called, a high * number indicates a late position. The returned number should be in the interval [0, 100]. * The default value is 0. * * In KWin4 this information was provided in the Effect's desktop file as property * X-KDE-Ordering. In the case of Scripted Effects this property is still used. * * @since 5.0 **/ virtual int requestedEffectChainPosition() const; /** * A touch point was pressed. * * If the effect wants to exclusively use the touch event it should return @c true. * If @c false is returned the touch event is passed to further effects. * * In general an Effect should only return @c true if it is the exclusive effect getting * input events. E.g. has grabbed mouse events. * * Default implementation returns @c false. * * @param id The unique id of the touch point * @param pos The position of the touch point in global coordinates * @param time Timestamp * * @see touchMotion * @see touchUp * @since 5.8 **/ virtual bool touchDown(quint32 id, const QPointF &pos, quint32 time); /** * A touch point moved. * * If the effect wants to exclusively use the touch event it should return @c true. * If @c false is returned the touch event is passed to further effects. * * In general an Effect should only return @c true if it is the exclusive effect getting * input events. E.g. has grabbed mouse events. * * Default implementation returns @c false. * * @param id The unique id of the touch point * @param pos The position of the touch point in global coordinates * @param time Timestamp * * @see touchDown * @see touchUp * @since 5.8 **/ virtual bool touchMotion(quint32 id, const QPointF &pos, quint32 time); /** * A touch point was released. * * If the effect wants to exclusively use the touch event it should return @c true. * If @c false is returned the touch event is passed to further effects. * * In general an Effect should only return @c true if it is the exclusive effect getting * input events. E.g. has grabbed mouse events. * * Default implementation returns @c false. * * @param id The unique id of the touch point * @param time Timestamp * * @see touchDown * @see touchMotion * @since 5.8 **/ virtual bool touchUp(quint32 id, quint32 time); static QPoint cursorPos(); /** * Read animation time from the configuration and possibly adjust using animationTimeFactor(). * The configuration value in the effect should also have special value 'default' (set using * QSpinBox::setSpecialValueText()) with the value 0. This special value is adjusted * using the global animation speed, otherwise the exact time configured is returned. * @param cfg configuration group to read value from * @param key configuration key to read value from * @param defaultTime default animation time in milliseconds **/ // return type is intentionally double so that one can divide using it without losing data static double animationTime(const KConfigGroup& cfg, const QString& key, int defaultTime); /** * @overload Use this variant if the animation time is hardcoded and not configurable * in the effect itself. **/ static double animationTime(int defaultTime); /** * @overload Use this variant if animation time is provided through a KConfigXT generated class * having a property called "duration". **/ template int animationTime(int defaultDuration); /** * Linearly interpolates between @p x and @p y. * * Returns @p x when @p a = 0; returns @p y when @p a = 1. **/ static double interpolate(double x, double y, double a) { return x * (1 - a) + y * a; } /** Helper to set WindowPaintData and QRegion to necessary transformations so that * a following drawWindow() would put the window at the requested geometry (useful for thumbnails) **/ static void setPositionTransformations(WindowPaintData& data, QRect& region, EffectWindow* w, const QRect& r, Qt::AspectRatioMode aspect); public Q_SLOTS: virtual bool borderActivated(ElectricBorder border); protected: xcb_connection_t *xcbConnection() const; xcb_window_t x11RootWindow() const; /** * An implementing class can call this with it's kconfig compiled singleton class. * This method will perform the instance on the class. * @since 5.9 **/ template void initConfig(); }; /** * Prefer the KWIN_EFFECT_FACTORY macros. **/ class KWINEFFECTS_EXPORT EffectPluginFactory : public KPluginFactory { Q_OBJECT public: EffectPluginFactory(); virtual ~EffectPluginFactory(); /** * Returns whether the Effect is supported. * * An Effect can implement this method to determine at runtime whether the Effect is supported. * * If the current compositing backend is not supported it should return @c false. * * This method is optional, by default @c true is returned. **/ virtual bool isSupported() const; /** * Returns whether the Effect should get enabled by default. * * This function provides a way for an effect to override the default at runtime, * e.g. based on the capabilities of the hardware. * * This method is optional; the effect doesn't have to provide it. * * Note that this function is only called if the supported() function returns true, * and if X-KDE-PluginInfo-EnabledByDefault is set to true in the .desktop file. * * This method is optional, by default @c true is returned. **/ virtual bool enabledByDefault() const; /** * This method returns the created Effect. **/ virtual KWin::Effect *createEffect() const = 0; }; /** * Defines an EffectPluginFactory sub class with customized isSupported and enabledByDefault methods. * * If the Effect to be created does not need the isSupported or enabledByDefault methods prefer * the simplified KWIN_EFFECT_FACTORY, KWIN_EFFECT_FACTORY_SUPPORTED or KWIN_EFFECT_FACTORY_ENABLED * macros which create an EffectPluginFactory with a useable default value. * * The macro also adds a useable K_EXPORT_PLUGIN_VERSION to the definition. KWin will not load * any Effect with a non-matching plugin version. This API is not providing binary compatibility * and thus the effect plugin must be compiled against the same kwineffects library version as * KWin. * * @param factoryName The name to be used for the EffectPluginFactory * @param className The class name of the Effect sub class which is to be created by the factory * @param jsonFile Name of the json file to be compiled into the plugin as metadata * @param supported Source code to go into the isSupported() method, must return a boolean * @param enabled Source code to go into the enabledByDefault() method, must return a boolean **/ #define KWIN_EFFECT_FACTORY_SUPPORTED_ENABLED( factoryName, className, jsonFile, supported, enabled ) \ class factoryName : public KWin::EffectPluginFactory \ { \ Q_OBJECT \ Q_PLUGIN_METADATA(IID KPluginFactory_iid FILE jsonFile) \ Q_INTERFACES(KPluginFactory) \ public: \ explicit factoryName() {} \ ~factoryName() {} \ bool isSupported() const override { \ supported \ } \ bool enabledByDefault() const override { \ enabled \ } \ KWin::Effect *createEffect() const override { \ return new className(); \ } \ }; \ K_EXPORT_PLUGIN_VERSION(quint32(KWIN_EFFECT_API_VERSION)) #define KWIN_EFFECT_FACTORY_ENABLED( factoryName, className, jsonFile, enabled ) \ KWIN_EFFECT_FACTORY_SUPPORTED_ENABLED( factoryName, className, jsonFile, return true;, enabled ) #define KWIN_EFFECT_FACTORY_SUPPORTED( factoryName, classname, jsonFile, supported ) \ KWIN_EFFECT_FACTORY_SUPPORTED_ENABLED( factoryName, className, jsonFile, supported, return true; ) #define KWIN_EFFECT_FACTORY( factoryName, classname, jsonFile ) \ KWIN_EFFECT_FACTORY_SUPPORTED_ENABLED( factoryName, className, jsonFile, return true;, return true; ) /** * @short Manager class that handles all the effects. * * This class creates Effect objects and calls it's appropriate methods. * * Effect objects can call methods of this class to interact with the * workspace, e.g. to activate or move a specific window, change current * desktop or create a special input window to receive mouse and keyboard * events. **/ class KWINEFFECTS_EXPORT EffectsHandler : public QObject { Q_OBJECT Q_PROPERTY(int currentDesktop READ currentDesktop WRITE setCurrentDesktop NOTIFY desktopChanged) Q_PROPERTY(QString currentActivity READ currentActivity NOTIFY currentActivityChanged) Q_PROPERTY(KWin::EffectWindow *activeWindow READ activeWindow WRITE activateWindow NOTIFY windowActivated) Q_PROPERTY(QSize desktopGridSize READ desktopGridSize) Q_PROPERTY(int desktopGridWidth READ desktopGridWidth) Q_PROPERTY(int desktopGridHeight READ desktopGridHeight) Q_PROPERTY(int workspaceWidth READ workspaceWidth) Q_PROPERTY(int workspaceHeight READ workspaceHeight) /** * The number of desktops currently used. Minimum number of desktops is 1, maximum 20. **/ Q_PROPERTY(int desktops READ numberOfDesktops WRITE setNumberOfDesktops NOTIFY numberDesktopsChanged) Q_PROPERTY(bool optionRollOverDesktops READ optionRollOverDesktops) Q_PROPERTY(int activeScreen READ activeScreen) Q_PROPERTY(int numScreens READ numScreens NOTIFY numberScreensChanged) /** * Factor by which animation speed in the effect should be modified (multiplied). * If configurable in the effect itself, the option should have also 'default' * animation speed. The actual value should be determined using animationTime(). * Note: The factor can be also 0, so make sure your code can cope with 0ms time * if used manually. **/ Q_PROPERTY(qreal animationTimeFactor READ animationTimeFactor) Q_PROPERTY(QList< KWin::EffectWindow* > stackingOrder READ stackingOrder) /** * Whether window decorations use the alpha channel. **/ Q_PROPERTY(bool decorationsHaveAlpha READ decorationsHaveAlpha) /** * Whether the window decorations support blurring behind the decoration. **/ Q_PROPERTY(bool decorationSupportsBlurBehind READ decorationSupportsBlurBehind) Q_PROPERTY(CompositingType compositingType READ compositingType CONSTANT) Q_PROPERTY(QPoint cursorPos READ cursorPos) Q_PROPERTY(QSize virtualScreenSize READ virtualScreenSize NOTIFY virtualScreenSizeChanged) Q_PROPERTY(QRect virtualScreenGeometry READ virtualScreenGeometry NOTIFY virtualScreenGeometryChanged) Q_PROPERTY(bool hasActiveFullScreenEffect READ hasActiveFullScreenEffect NOTIFY hasActiveFullScreenEffectChanged) friend class Effect; public: explicit EffectsHandler(CompositingType type); virtual ~EffectsHandler(); // for use by effects virtual void prePaintScreen(ScreenPrePaintData& data, int time) = 0; virtual void paintScreen(int mask, QRegion region, ScreenPaintData& data) = 0; virtual void postPaintScreen() = 0; virtual void prePaintWindow(EffectWindow* w, WindowPrePaintData& data, int time) = 0; virtual void paintWindow(EffectWindow* w, int mask, QRegion region, WindowPaintData& data) = 0; virtual void postPaintWindow(EffectWindow* w) = 0; virtual void paintEffectFrame(EffectFrame* frame, QRegion region, double opacity, double frameOpacity) = 0; virtual void drawWindow(EffectWindow* w, int mask, QRegion region, WindowPaintData& data) = 0; virtual void buildQuads(EffectWindow* w, WindowQuadList& quadList) = 0; virtual QVariant kwinOption(KWinOption kwopt) = 0; /** * Sets the cursor while the mouse is intercepted. * @see startMouseInterception * @since 4.11 **/ virtual void defineCursor(Qt::CursorShape shape) = 0; virtual QPoint cursorPos() const = 0; virtual bool grabKeyboard(Effect* effect) = 0; virtual void ungrabKeyboard() = 0; /** * Ensures that all mouse events are sent to the @p effect. * No window will get the mouse events. Only fullscreen effects providing a custom user interface should * be using this method. The input events are delivered to Effect::windowInputMouseEvent. * * @note This method does not perform an X11 mouse grab. On X11 a fullscreen input window is raised above * all other windows, but no grab is performed. * * @param effect The effect * @param shape Sets the cursor to be used while the mouse is intercepted * @see stopMouseInterception * @see Effect::windowInputMouseEvent * @since 4.11 **/ virtual void startMouseInterception(Effect *effect, Qt::CursorShape shape) = 0; /** * Releases the hold mouse interception for @p effect * @see startMouseInterception * @since 4.11 **/ virtual void stopMouseInterception(Effect *effect) = 0; /** * @brief Registers a global shortcut with the provided @p action. * * @param shortcut The global shortcut which should trigger the action * @param action The action which gets triggered when the shortcut matches **/ virtual void registerGlobalShortcut(const QKeySequence &shortcut, QAction *action) = 0; /** * @brief Registers a global pointer shortcut with the provided @p action. * * @param modifiers The keyboard modifiers which need to be holded * @param pointerButtons The pointer buttons which need to be pressed * @param action The action which gets triggered when the shortcut matches **/ virtual void registerPointerShortcut(Qt::KeyboardModifiers modifiers, Qt::MouseButton pointerButtons, QAction *action) = 0; /** * @brief Registers a global axis shortcut with the provided @p action. * * @param modifiers The keyboard modifiers which need to be holded * @param axis The direction in which the axis needs to be moved * @param action The action which gets triggered when the shortcut matches **/ virtual void registerAxisShortcut(Qt::KeyboardModifiers modifiers, PointerAxisDirection axis, QAction *action) = 0; /** * @brief Registers a global touchpad swipe gesture shortcut with the provided @p action. * * @param direction The direction for the swipe * @param action The action which gets triggered when the gesture triggers * @since 5.10 **/ virtual void registerTouchpadSwipeShortcut(SwipeDirection direction, QAction *action) = 0; /** * Retrieve the proxy class for an effect if it has one. Will return NULL if * the effect isn't loaded or doesn't have a proxy class. **/ virtual void* getProxy(QString name) = 0; // Mouse polling virtual void startMousePolling() = 0; virtual void stopMousePolling() = 0; virtual void reserveElectricBorder(ElectricBorder border, Effect *effect) = 0; virtual void unreserveElectricBorder(ElectricBorder border, Effect *effect) = 0; /** * Registers the given @p action for the given @p border to be activated through * a touch swipe gesture. * * If the @p border gets triggered through a touch swipe gesture the QAction::triggered * signal gets invoked. * * To unregister the touch screen action either delete the @p action or * invoke unregisterTouchBorder. * * @see unregisterTouchBorder * @since 5.10 **/ virtual void registerTouchBorder(ElectricBorder border, QAction *action) = 0; /** * Unregisters the given @p action for the given touch @p border. * * @see registerTouchBorder * @since 5.10 **/ virtual void unregisterTouchBorder(ElectricBorder border, QAction *action) = 0; // functions that allow controlling windows/desktop virtual void activateWindow(KWin::EffectWindow* c) = 0; virtual KWin::EffectWindow* activeWindow() const = 0 ; Q_SCRIPTABLE virtual void moveWindow(KWin::EffectWindow* w, const QPoint& pos, bool snap = false, double snapAdjust = 1.0) = 0; /** * Moves the window to the specific desktop * Setting desktop to NET::OnAllDesktops will set the window on all desktops **/ Q_SCRIPTABLE virtual void windowToDesktop(KWin::EffectWindow* w, int desktop) = 0; /** * Moves a window to the given desktops * On X11, the window will end up on the last window in the list * Setting this to an empty list will set the window on all desktops * * @arg desktopIds a list of desktops the window should be placed on. NET::OnAllDesktops is not a valid desktop X11Id **/ Q_SCRIPTABLE virtual void windowToDesktops(KWin::EffectWindow* w, const QVector &desktopIds) = 0; Q_SCRIPTABLE virtual void windowToScreen(KWin::EffectWindow* w, int screen) = 0; virtual void setShowingDesktop(bool showing) = 0; // Activities /** * @returns The ID of the current activity. **/ virtual QString currentActivity() const = 0; // Desktops /** * @returns The ID of the current desktop. **/ virtual int currentDesktop() const = 0; /** * @returns Total number of desktops currently in existence. **/ virtual int numberOfDesktops() const = 0; /** * Set the current desktop to @a desktop. **/ virtual void setCurrentDesktop(int desktop) = 0; /** * Sets the total number of desktops to @a desktops. **/ virtual void setNumberOfDesktops(int desktops) = 0; /** * @returns The size of desktop layout in grid units. **/ virtual QSize desktopGridSize() const = 0; /** * @returns The width of desktop layout in grid units. **/ virtual int desktopGridWidth() const = 0; /** * @returns The height of desktop layout in grid units. **/ virtual int desktopGridHeight() const = 0; /** * @returns The width of desktop layout in pixels. **/ virtual int workspaceWidth() const = 0; /** * @returns The height of desktop layout in pixels. **/ virtual int workspaceHeight() const = 0; /** * @returns The ID of the desktop at the point @a coords or 0 if no desktop exists at that * point. @a coords is to be in grid units. **/ virtual int desktopAtCoords(QPoint coords) const = 0; /** * @returns The coords of desktop @a id in grid units. **/ virtual QPoint desktopGridCoords(int id) const = 0; /** * @returns The coords of the top-left corner of desktop @a id in pixels. **/ virtual QPoint desktopCoords(int id) const = 0; /** * @returns The ID of the desktop above desktop @a id. Wraps around to the bottom of * the layout if @a wrap is set. If @a id is not set use the current one. **/ Q_SCRIPTABLE virtual int desktopAbove(int desktop = 0, bool wrap = true) const = 0; /** * @returns The ID of the desktop to the right of desktop @a id. Wraps around to the * left of the layout if @a wrap is set. If @a id is not set use the current one. **/ Q_SCRIPTABLE virtual int desktopToRight(int desktop = 0, bool wrap = true) const = 0; /** * @returns The ID of the desktop below desktop @a id. Wraps around to the top of the * layout if @a wrap is set. If @a id is not set use the current one. **/ Q_SCRIPTABLE virtual int desktopBelow(int desktop = 0, bool wrap = true) const = 0; /** * @returns The ID of the desktop to the left of desktop @a id. Wraps around to the * right of the layout if @a wrap is set. If @a id is not set use the current one. **/ Q_SCRIPTABLE virtual int desktopToLeft(int desktop = 0, bool wrap = true) const = 0; Q_SCRIPTABLE virtual QString desktopName(int desktop) const = 0; virtual bool optionRollOverDesktops() const = 0; virtual int activeScreen() const = 0; // Xinerama virtual int numScreens() const = 0; // Xinerama Q_SCRIPTABLE virtual int screenNumber(const QPoint& pos) const = 0; // Xinerama virtual QRect clientArea(clientAreaOption, int screen, int desktop) const = 0; virtual QRect clientArea(clientAreaOption, const EffectWindow* c) const = 0; virtual QRect clientArea(clientAreaOption, const QPoint& p, int desktop) const = 0; /** * The bounding size of all screens combined. Overlapping areas * are not counted multiple times. * * @see virtualScreenGeometry() * @see virtualScreenSizeChanged() * @since 5.0 **/ virtual QSize virtualScreenSize() const = 0; /** * The bounding geometry of all outputs combined. Always starts at (0,0) and has * virtualScreenSize as it's size. * * @see virtualScreenSize() * @see virtualScreenGeometryChanged() * @since 5.0 **/ virtual QRect virtualScreenGeometry() const = 0; /** * Factor by which animation speed in the effect should be modified (multiplied). * If configurable in the effect itself, the option should have also 'default' * animation speed. The actual value should be determined using animationTime(). * Note: The factor can be also 0, so make sure your code can cope with 0ms time * if used manually. **/ virtual double animationTimeFactor() const = 0; virtual WindowQuadType newWindowQuadType() = 0; Q_SCRIPTABLE virtual KWin::EffectWindow* findWindow(WId id) const = 0; Q_SCRIPTABLE virtual KWin::EffectWindow* findWindow(KWayland::Server::SurfaceInterface *surf) const = 0; /** * Finds the EffectWindow for the internal window @p w. * If there is no such window @c null is returned. * * On Wayland this returns the internal window. On X11 it returns an Unamanged with the * window id matching that of the provided window @p w. * * @since 5.16 **/ Q_SCRIPTABLE virtual KWin::EffectWindow *findWindow(QWindow *w) const = 0; /** * Finds the EffectWindow for the Toplevel with KWin internal @p id. * If there is no such window @c null is returned. * * @since 5.16 **/ Q_SCRIPTABLE virtual KWin::EffectWindow *findWindow(const QUuid &id) const = 0; virtual EffectWindowList stackingOrder() const = 0; // window will be temporarily painted as if being at the top of the stack Q_SCRIPTABLE virtual void setElevatedWindow(KWin::EffectWindow* w, bool set) = 0; virtual void setTabBoxWindow(EffectWindow*) = 0; virtual void setTabBoxDesktop(int) = 0; virtual EffectWindowList currentTabBoxWindowList() const = 0; virtual void refTabBox() = 0; virtual void unrefTabBox() = 0; virtual void closeTabBox() = 0; virtual QList< int > currentTabBoxDesktopList() const = 0; virtual int currentTabBoxDesktop() const = 0; virtual EffectWindow* currentTabBoxWindow() const = 0; virtual void setActiveFullScreenEffect(Effect* e) = 0; virtual Effect* activeFullScreenEffect() const = 0; /** * Schedules the entire workspace to be repainted next time. * If you call it during painting (including prepaint) then it does not * affect the current painting. **/ Q_SCRIPTABLE virtual void addRepaintFull() = 0; Q_SCRIPTABLE virtual void addRepaint(const QRect& r) = 0; Q_SCRIPTABLE virtual void addRepaint(const QRegion& r) = 0; Q_SCRIPTABLE virtual void addRepaint(int x, int y, int w, int h) = 0; CompositingType compositingType() const; /** * @brief Whether the Compositor is OpenGL based (either GL 1 or 2). * * @return bool @c true in case of OpenGL based Compositor, @c false otherwise **/ bool isOpenGLCompositing() const; virtual unsigned long xrenderBufferPicture() = 0; /** * @brief Provides access to the QPainter which is rendering to the back buffer. * * Only relevant for CompositingType QPainterCompositing. For all other compositing types * @c null is returned. * * @return QPainter* The Scene's QPainter or @c null. **/ virtual QPainter *scenePainter() = 0; virtual void reconfigure() = 0; virtual QByteArray readRootProperty(long atom, long type, int format) const = 0; /** * @brief Announces support for the feature with the given name. If no other Effect * has announced support for this feature yet, an X11 property will be installed on * the root window. * * The Effect will be notified for events through the signal propertyNotify(). * * To remove the support again use removeSupportProperty. When an Effect is * destroyed it is automatically taken care of removing the support. It is not * required to call removeSupportProperty in the Effect's cleanup handling. * * @param propertyName The name of the property to announce support for * @param effect The effect which announces support * @return xcb_atom_t The created X11 atom * @see removeSupportProperty * @since 4.11 **/ virtual xcb_atom_t announceSupportProperty(const QByteArray &propertyName, Effect *effect) = 0; /** * @brief Removes support for the feature with the given name. If there is no other Effect left * which has announced support for the given property, the property will be removed from the * root window. * * In case the Effect had not registered support, calling this function does not change anything. * * @param propertyName The name of the property to remove support for * @param effect The effect which had registered the property. * @see announceSupportProperty * @since 4.11 **/ virtual void removeSupportProperty(const QByteArray &propertyName, Effect *effect) = 0; /** * Returns @a true if the active window decoration has shadow API hooks. **/ virtual bool hasDecorationShadows() const = 0; /** * Returns @a true if the window decorations use the alpha channel, and @a false otherwise. * @since 4.5 **/ virtual bool decorationsHaveAlpha() const = 0; /** * Returns @a true if the window decorations support blurring behind the decoration, and @a false otherwise * @since 4.6 **/ virtual bool decorationSupportsBlurBehind() const = 0; /** * Creates a new frame object. If the frame does not have a static size * then it will be located at @a position with @a alignment. A * non-static frame will automatically adjust its size to fit the contents. * @returns A new @ref EffectFrame. It is the responsibility of the caller to delete the * EffectFrame. * @since 4.6 **/ virtual EffectFrame* effectFrame(EffectFrameStyle style, bool staticSize = true, const QPoint& position = QPoint(-1, -1), Qt::Alignment alignment = Qt::AlignCenter) const = 0; /** * Allows an effect to trigger a reload of itself. * This can be used by an effect which needs to be reloaded when screen geometry changes. * It is possible that the effect cannot be loaded again as it's supported method does no longer * hold. * @param effect The effect to reload * @since 4.8 **/ virtual void reloadEffect(Effect *effect) = 0; /** * Whether the screen is currently considered as locked. * Note for technical reasons this is not always possible to detect. The screen will only * be considered as locked if the screen locking process implements the * org.freedesktop.ScreenSaver interface. * * @returns @c true if the screen is currently locked, @c false otherwise * @see screenLockingChanged * @since 4.11 **/ virtual bool isScreenLocked() const = 0; /** * @brief Makes the OpenGL compositing context current. * * If the compositing backend is not using OpenGL, this method returns @c false. * * @return bool @c true if the context became current, @c false otherwise. **/ virtual bool makeOpenGLContextCurrent() = 0; /** * @brief Makes a null OpenGL context current resulting in no context * being current. * * If the compositing backend is not OpenGL based, this method is a noop. * * There is normally no reason for an Effect to call this method. **/ virtual void doneOpenGLContextCurrent() = 0; virtual xcb_connection_t *xcbConnection() const = 0; virtual xcb_window_t x11RootWindow() const = 0; /** * Interface to the Wayland display: this is relevant only * on Wayland, on X11 it will be nullptr * @since 5.5 **/ virtual KWayland::Server::Display *waylandDisplay() const = 0; /** * Whether animations are supported by the Scene. * If this method returns @c false Effects are supposed to not * animate transitions. * * @returns Whether the Scene can drive animations * @since 5.8 **/ virtual bool animationsSupported() const = 0; /** * The current cursor image of the Platform. * @see cursorPos * @since 5.9 **/ virtual PlatformCursorImage cursorImage() const = 0; /** * The cursor image should be hidden. * @see showCursor * @since 5.9 **/ virtual void hideCursor() = 0; /** * The cursor image should be shown again after having been hidden. * @see hideCursor * @since 5.9 **/ virtual void showCursor() = 0; /** * Starts an interactive window selection process. * * Once the user selected a window the @p callback is invoked with the selected EffectWindow as * argument. In case the user cancels the interactive window selection or selecting a window is currently * not possible (e.g. screen locked) the @p callback is invoked with a @c nullptr argument. * * During the interactive window selection the cursor is turned into a crosshair cursor. * * @param callback The function to invoke once the interactive window selection ends * @since 5.9 **/ virtual void startInteractiveWindowSelection(std::function callback) = 0; /** * Starts an interactive position selection process. * * Once the user selected a position on the screen the @p callback is invoked with * the selected point as argument. In case the user cancels the interactive position selection * or selecting a position is currently not possible (e.g. screen locked) the @p callback * is invoked with a point at @c -1 as x and y argument. * * During the interactive window selection the cursor is turned into a crosshair cursor. * * @param callback The function to invoke once the interactive position selection ends * @since 5.9 **/ virtual void startInteractivePositionSelection(std::function callback) = 0; /** * Shows an on-screen-message. To hide it again use hideOnScreenMessage. * * @param message The message to show * @param iconName The optional themed icon name * @see hideOnScreenMessage * @since 5.9 **/ virtual void showOnScreenMessage(const QString &message, const QString &iconName = QString()) = 0; /** * Flags for how to hide a shown on-screen-message * @see hideOnScreenMessage * @since 5.9 **/ enum class OnScreenMessageHideFlag { /** * The on-screen-message should skip the close window animation. * @see EffectWindow::skipsCloseAnimation **/ SkipsCloseAnimation = 1 }; Q_DECLARE_FLAGS(OnScreenMessageHideFlags, OnScreenMessageHideFlag) /** * Hides a previously shown on-screen-message again. * @param flags The flags for how to hide the message * @see showOnScreenMessage * @since 5.9 **/ virtual void hideOnScreenMessage(OnScreenMessageHideFlags flags = OnScreenMessageHideFlags()) = 0; /* * @returns The configuration used by the EffectsHandler. * @since 5.10 **/ virtual KSharedConfigPtr config() const = 0; /** * @returns The global input configuration (kcminputrc) * @since 5.10 **/ virtual KSharedConfigPtr inputConfig() const = 0; /** * Returns if activeFullScreenEffect is set **/ virtual bool hasActiveFullScreenEffect() const = 0; Q_SIGNALS: /** * Signal emitted when the current desktop changed. * @param oldDesktop The previously current desktop * @param newDesktop The new current desktop * @param with The window which is taken over to the new desktop, can be NULL * @since 4.9 **/ void desktopChanged(int oldDesktop, int newDesktop, KWin::EffectWindow *with); /** * @since 4.7 * @deprecated **/ void desktopChanged(int oldDesktop, int newDesktop); /** * Signal emitted when a window moved to another desktop * NOTICE that this does NOT imply that the desktop has changed * The @param window which is moved to the new desktop * @param oldDesktop The previous desktop of the window * @param newDesktop The new desktop of the window * @since 4.11.4 **/ void desktopPresenceChanged(KWin::EffectWindow *window, int oldDesktop, int newDesktop); /** * Signal emitted when the number of currently existing desktops is changed. * @param old The previous number of desktops in used. * @see EffectsHandler::numberOfDesktops. * @since 4.7 **/ void numberDesktopsChanged(uint old); /** * Signal emitted when the number of screens changed. * @since 5.0 **/ void numberScreensChanged(); /** * Signal emitted when the desktop showing ("dashboard") state changed * The desktop is risen to the keepAbove layer, you may want to elevate * windows or such. * @since 5.3 **/ void showingDesktopChanged(bool); /** * Signal emitted when a new window has been added to the Workspace. * @param w The added window * @since 4.7 **/ void windowAdded(KWin::EffectWindow *w); /** * Signal emitted when a window is being removed from the Workspace. * An effect which wants to animate the window closing should connect * to this signal and reference the window by using * refWindow * @param w The window which is being closed * @since 4.7 **/ void windowClosed(KWin::EffectWindow *w); /** * Signal emitted when a window get's activated. * @param w The new active window, or @c NULL if there is no active window. * @since 4.7 **/ void windowActivated(KWin::EffectWindow *w); /** * Signal emitted when a window is deleted. * This means that a closed window is not referenced any more. * An effect bookkeeping the closed windows should connect to this * signal to clean up the internal references. * @param w The window which is going to be deleted. * @see EffectWindow::refWindow * @see EffectWindow::unrefWindow * @see windowClosed * @since 4.7 **/ void windowDeleted(KWin::EffectWindow *w); /** * Signal emitted when a user begins a window move or resize operation. * To figure out whether the user resizes or moves the window use * isUserMove or isUserResize. * Whenever the geometry is updated the signal @ref windowStepUserMovedResized * is emitted with the current geometry. * The move/resize operation ends with the signal @ref windowFinishUserMovedResized. * Only one window can be moved/resized by the user at the same time! * @param w The window which is being moved/resized * @see windowStepUserMovedResized * @see windowFinishUserMovedResized * @see EffectWindow::isUserMove * @see EffectWindow::isUserResize * @since 4.7 **/ void windowStartUserMovedResized(KWin::EffectWindow *w); /** * Signal emitted during a move/resize operation when the user changed the geometry. * Please note: KWin supports two operation modes. In one mode all changes are applied * instantly. This means the window's geometry matches the passed in @p geometry. In the * other mode the geometry is changed after the user ended the move/resize mode. * The @p geometry differs from the window's geometry. Also the window's pixmap still has * the same size as before. Depending what the effect wants to do it would be recommended * to scale/translate the window. * @param w The window which is being moved/resized * @param geometry The geometry of the window in the current move/resize step. * @see windowStartUserMovedResized * @see windowFinishUserMovedResized * @see EffectWindow::isUserMove * @see EffectWindow::isUserResize * @since 4.7 **/ void windowStepUserMovedResized(KWin::EffectWindow *w, const QRect &geometry); /** * Signal emitted when the user finishes move/resize of window @p w. * @param w The window which has been moved/resized * @see windowStartUserMovedResized * @see windowFinishUserMovedResized * @since 4.7 **/ void windowFinishUserMovedResized(KWin::EffectWindow *w); /** * Signal emitted when the maximized state of the window @p w changed. * A window can be in one of four states: * @li restored: both @p horizontal and @p vertical are @c false * @li horizontally maximized: @p horizontal is @c true and @p vertical is @c false * @li vertically maximized: @p horizontal is @c false and @p vertical is @c true * @li completely maximized: both @p horizontal and @p vertical are @c true * @param w The window whose maximized state changed * @param horizontal If @c true maximized horizontally * @param vertical If @c true maximized vertically * @since 4.7 **/ void windowMaximizedStateChanged(KWin::EffectWindow *w, bool horizontal, bool vertical); /** * Signal emitted when the geometry or shape of a window changed. * This is caused if the window changes geometry without user interaction. * E.g. the decoration is changed. This is in opposite to windowUserMovedResized * which is caused by direct user interaction. * @param w The window whose geometry changed * @param old The previous geometry * @see windowUserMovedResized * @since 4.7 **/ void windowGeometryShapeChanged(KWin::EffectWindow *w, const QRect &old); /** * Signal emitted when the padding of a window changed. (eg. shadow size) * @param w The window whose geometry changed * @param old The previous expandedGeometry() * @since 4.9 **/ void windowPaddingChanged(KWin::EffectWindow *w, const QRect &old); /** * Signal emitted when the windows opacity is changed. * @param w The window whose opacity level is changed. * @param oldOpacity The previous opacity level * @param newOpacity The new opacity level * @since 4.7 **/ void windowOpacityChanged(KWin::EffectWindow *w, qreal oldOpacity, qreal newOpacity); /** * Signal emitted when a window got minimized. * @param w The window which was minimized * @since 4.7 **/ void windowMinimized(KWin::EffectWindow *w); /** * Signal emitted when a window got unminimized. * @param w The window which was unminimized * @since 4.7 **/ void windowUnminimized(KWin::EffectWindow *w); /** * Signal emitted when a window either becomes modal (ie. blocking for its main client) or looses that state. * @param w The window which was unminimized * @since 4.11 **/ void windowModalityChanged(KWin::EffectWindow *w); /** * Signal emitted when a window either became unresponsive (eg. app froze or crashed) * or respoonsive * @param w The window that became (un)responsive * @param unresponsive Whether the window is responsive or unresponsive * @since 5.10 **/ void windowUnresponsiveChanged(KWin::EffectWindow *w, bool unresponsive); /** * Signal emitted when an area of a window is scheduled for repainting. * Use this signal in an effect if another area needs to be synced as well. * @param w The window which is scheduled for repainting * @param r Always empty. * @since 4.7 **/ void windowDamaged(KWin::EffectWindow *w, const QRect &r); /** * Signal emitted when a tabbox is added. * An effect who wants to replace the tabbox with itself should use refTabBox. * @param mode The TabBoxMode. * @see refTabBox * @see tabBoxClosed * @see tabBoxUpdated * @see tabBoxKeyEvent * @since 4.7 **/ void tabBoxAdded(int mode); /** * Signal emitted when the TabBox was closed by KWin core. * An effect which referenced the TabBox should use unrefTabBox to unref again. * @see unrefTabBox * @see tabBoxAdded * @since 4.7 **/ void tabBoxClosed(); /** * Signal emitted when the selected TabBox window changed or the TabBox List changed. * An effect should only response to this signal if it referenced the TabBox with refTabBox. * @see refTabBox * @see currentTabBoxWindowList * @see currentTabBoxDesktopList * @see currentTabBoxWindow * @see currentTabBoxDesktop * @since 4.7 **/ void tabBoxUpdated(); /** * Signal emitted when a key event, which is not handled by TabBox directly is, happens while * TabBox is active. An effect might use the key event to e.g. change the selected window. * An effect should only response to this signal if it referenced the TabBox with refTabBox. * @param event The key event not handled by TabBox directly * @see refTabBox * @since 4.7 **/ void tabBoxKeyEvent(QKeyEvent* event); void currentTabAboutToChange(KWin::EffectWindow* from, KWin::EffectWindow* to); void tabAdded(KWin::EffectWindow* from, KWin::EffectWindow* to); // from merged with to void tabRemoved(KWin::EffectWindow* c, KWin::EffectWindow* group); // c removed from group /** * Signal emitted when mouse changed. * If an effect needs to get updated mouse positions, it needs to first call startMousePolling. * For a fullscreen effect it is better to use an input window and react on windowInputMouseEvent. * @param pos The new mouse position * @param oldpos The previously mouse position * @param buttons The pressed mouse buttons * @param oldbuttons The previously pressed mouse buttons * @param modifiers Pressed keyboard modifiers * @param oldmodifiers Previously pressed keyboard modifiers. * @see startMousePolling * @since 4.7 **/ void mouseChanged(const QPoint& pos, const QPoint& oldpos, Qt::MouseButtons buttons, Qt::MouseButtons oldbuttons, Qt::KeyboardModifiers modifiers, Qt::KeyboardModifiers oldmodifiers); /** * Signal emitted when the cursor shape changed. * You'll likely want to query the current cursor as reaction: xcb_xfixes_get_cursor_image_unchecked * Connection to this signal is tracked, so if you don't need it anymore, disconnect from it to stop cursor event filtering **/ void cursorShapeChanged(); /** * Receives events registered for using registerPropertyType. * Use readProperty() to get the property data. * Note that the property may be already set on the window, so doing the same * processing from windowAdded() (e.g. simply calling propertyNotify() from it) * is usually needed. * @param w The window whose property changed, is @c null if it is a root window property * @param atom The property * @since 4.7 **/ void propertyNotify(KWin::EffectWindow* w, long atom); /** * Signal emitted after the screen geometry changed (e.g. add of a monitor). * Effects using displayWidth()/displayHeight() to cache information should * react on this signal and update the caches. * @param size The new screen size * @since 4.8 **/ void screenGeometryChanged(const QSize &size); /** * This signal is emitted when the global * activity is changed * @param id id of the new current activity * @since 4.9 **/ void currentActivityChanged(const QString &id); /** * This signal is emitted when a new activity is added * @param id id of the new activity * @since 4.9 **/ void activityAdded(const QString &id); /** * This signal is emitted when the activity * is removed * @param id id of the removed activity * @since 4.9 **/ void activityRemoved(const QString &id); /** * This signal is emitted when the screen got locked or unlocked. * @param locked @c true if the screen is now locked, @c false if it is now unlocked * @since 4.11 **/ void screenLockingChanged(bool locked); /** * This signal is emitted just before the screen locker tries to grab keys and lock the screen * Effects should release any grabs immediately * @since 5.17 **/ void screenAboutToLock(); /** * This signels is emitted when ever the stacking order is change, ie. a window is risen * or lowered * @since 4.10 **/ void stackingOrderChanged(); /** * This signal is emitted when the user starts to approach the @p border with the mouse. * The @p factor describes how far away the mouse is in a relative mean. The values are in * [0.0, 1.0] with 0.0 being emitted when first entered and on leaving. The value 1.0 means that * the @p border is reached with the mouse. So the values are well suited for animations. * The signal is always emitted when the mouse cursor position changes. * @param border The screen edge which is being approached * @param factor Value in range [0.0,1.0] to describe how close the mouse is to the border * @param geometry The geometry of the edge which is being approached * @since 4.11 **/ void screenEdgeApproaching(ElectricBorder border, qreal factor, const QRect &geometry); /** * Emitted whenever the virtualScreenSize changes. * @see virtualScreenSize() * @since 5.0 **/ void virtualScreenSizeChanged(); /** * Emitted whenever the virtualScreenGeometry changes. * @see virtualScreenGeometry() * @since 5.0 **/ void virtualScreenGeometryChanged(); /** * The window @p w gets shown again. The window was previously * initially shown with windowAdded and hidden with windowHidden. * * @see windowHidden * @see windowAdded * @since 5.8 **/ void windowShown(KWin::EffectWindow *w); /** * The window @p w got hidden but not yet closed. * This can happen when a window is still being used and is supposed to be shown again * with windowShown. On X11 an example is autohiding panels. On Wayland every * window first goes through the window hidden state and might get shown again, or might * get closed the normal way. * * @see windowShown * @see windowClosed * @since 5.8 **/ void windowHidden(KWin::EffectWindow *w); /** * This signal gets emitted when the data on EffectWindow @p w for @p role changed. * * An Effect can connect to this signal to read the new value and react on it. * E.g. an Effect which does not operate on windows grabbed by another Effect wants * to cancel the already scheduled animation if another Effect adds a grab. * * @param w The EffectWindow for which the data changed * @param role The data role which changed * @see EffectWindow::setData * @see EffectWindow::data * @since 5.8.4 **/ void windowDataChanged(KWin::EffectWindow *w, int role); /** * The xcb connection changed, either a new xcbConnection got created or the existing one * got destroyed. * Effects can use this to refetch the properties they want to set. * * When the xcbConnection changes also the x11RootWindow becomes invalid. * @see xcbConnection * @see x11RootWindow * @since 5.11 **/ void xcbConnectionChanged(); /** * This signal is emitted when active fullscreen effect changed. * * @see activeFullScreenEffect * @see setActiveFullScreenEffect * @since 5.14 **/ void activeFullScreenEffectChanged(); /** * This signal is emitted when active fullscreen effect changed to being * set or unset * * @see activeFullScreenEffect * @see setActiveFullScreenEffect * @since 5.15 **/ void hasActiveFullScreenEffectChanged(); /** * This signal is emitted when the keep above state of @p w was changed. * * @param w The window whose the keep above state was changed. * @since 5.15 **/ void windowKeepAboveChanged(KWin::EffectWindow *w); /** * This signal is emitted when the keep below state of @p was changed. * * @param w The window whose the keep below state was changed. * @since 5.15 **/ void windowKeepBelowChanged(KWin::EffectWindow *w); /** * This signal is emitted when the full screen state of @p w was changed. * * @param w The window whose the full screen state was changed. * @since 5.15 **/ void windowFullScreenChanged(KWin::EffectWindow *w); protected: QVector< EffectPair > loaded_effects; //QHash< QString, EffectFactory* > effect_factories; CompositingType compositing_type; }; /** * @short Representation of a window used by/for Effect classes. * * The purpose is to hide internal data and also to serve as a single * representation for the case when Client/Unmanaged becomes Deleted. **/ class KWINEFFECTS_EXPORT EffectWindow : public QObject { Q_OBJECT Q_PROPERTY(bool alpha READ hasAlpha CONSTANT) Q_PROPERTY(QRect geometry READ geometry) Q_PROPERTY(QRect expandedGeometry READ expandedGeometry) Q_PROPERTY(int height READ height) Q_PROPERTY(qreal opacity READ opacity) Q_PROPERTY(QPoint pos READ pos) Q_PROPERTY(int screen READ screen) Q_PROPERTY(QSize size READ size) Q_PROPERTY(int width READ width) Q_PROPERTY(int x READ x) Q_PROPERTY(int y READ y) Q_PROPERTY(int desktop READ desktop) Q_PROPERTY(bool onAllDesktops READ isOnAllDesktops) Q_PROPERTY(bool onCurrentDesktop READ isOnCurrentDesktop) Q_PROPERTY(QRect rect READ rect) Q_PROPERTY(QString windowClass READ windowClass) Q_PROPERTY(QString windowRole READ windowRole) /** * Returns whether the window is a desktop background window (the one with wallpaper). * See _NET_WM_WINDOW_TYPE_DESKTOP at https://standards.freedesktop.org/wm-spec/wm-spec-latest.html . **/ Q_PROPERTY(bool desktopWindow READ isDesktop) /** * Returns whether the window is a dock (i.e. a panel). * See _NET_WM_WINDOW_TYPE_DOCK at https://standards.freedesktop.org/wm-spec/wm-spec-latest.html . **/ Q_PROPERTY(bool dock READ isDock) /** * Returns whether the window is a standalone (detached) toolbar window. * See _NET_WM_WINDOW_TYPE_TOOLBAR at https://standards.freedesktop.org/wm-spec/wm-spec-latest.html . **/ Q_PROPERTY(bool toolbar READ isToolbar) /** * Returns whether the window is a torn-off menu. * See _NET_WM_WINDOW_TYPE_MENU at https://standards.freedesktop.org/wm-spec/wm-spec-latest.html . **/ Q_PROPERTY(bool menu READ isMenu) /** * Returns whether the window is a "normal" window, i.e. an application or any other window * for which none of the specialized window types fit. * See _NET_WM_WINDOW_TYPE_NORMAL at https://standards.freedesktop.org/wm-spec/wm-spec-latest.html . **/ Q_PROPERTY(bool normalWindow READ isNormalWindow) /** * Returns whether the window is a dialog window. * See _NET_WM_WINDOW_TYPE_DIALOG at https://standards.freedesktop.org/wm-spec/wm-spec-latest.html . **/ Q_PROPERTY(bool dialog READ isDialog) /** * Returns whether the window is a splashscreen. Note that many (especially older) applications * do not support marking their splash windows with this type. * See _NET_WM_WINDOW_TYPE_SPLASH at https://standards.freedesktop.org/wm-spec/wm-spec-latest.html . **/ Q_PROPERTY(bool splash READ isSplash) /** * Returns whether the window is a utility window, such as a tool window. * See _NET_WM_WINDOW_TYPE_UTILITY at https://standards.freedesktop.org/wm-spec/wm-spec-latest.html . **/ Q_PROPERTY(bool utility READ isUtility) /** * Returns whether the window is a dropdown menu (i.e. a popup directly or indirectly open * from the applications menubar). * See _NET_WM_WINDOW_TYPE_DROPDOWN_MENU at https://standards.freedesktop.org/wm-spec/wm-spec-latest.html . **/ Q_PROPERTY(bool dropdownMenu READ isDropdownMenu) /** * Returns whether the window is a popup menu (that is not a torn-off or dropdown menu). * See _NET_WM_WINDOW_TYPE_POPUP_MENU at https://standards.freedesktop.org/wm-spec/wm-spec-latest.html . **/ Q_PROPERTY(bool popupMenu READ isPopupMenu) /** * Returns whether the window is a tooltip. * See _NET_WM_WINDOW_TYPE_TOOLTIP at https://standards.freedesktop.org/wm-spec/wm-spec-latest.html . **/ Q_PROPERTY(bool tooltip READ isTooltip) /** * Returns whether the window is a window with a notification. * See _NET_WM_WINDOW_TYPE_NOTIFICATION at https://standards.freedesktop.org/wm-spec/wm-spec-latest.html . **/ Q_PROPERTY(bool notification READ isNotification) /** * Returns whether the window is a window with a critical notification. * using the non-standard _KDE_NET_WM_WINDOW_TYPE_CRITICAL_NOTIFICATION **/ Q_PROPERTY(bool criticalNotification READ isCriticalNotification) /** * Returns whether the window is an on screen display window * using the non-standard _KDE_NET_WM_WINDOW_TYPE_ON_SCREEN_DISPLAY **/ Q_PROPERTY(bool onScreenDisplay READ isOnScreenDisplay) /** * Returns whether the window is a combobox popup. * See _NET_WM_WINDOW_TYPE_COMBO at https://standards.freedesktop.org/wm-spec/wm-spec-latest.html . **/ Q_PROPERTY(bool comboBox READ isComboBox) /** * Returns whether the window is a Drag&Drop icon. * See _NET_WM_WINDOW_TYPE_DND at https://standards.freedesktop.org/wm-spec/wm-spec-latest.html . **/ Q_PROPERTY(bool dndIcon READ isDNDIcon) /** * Returns the NETWM window type * See https://standards.freedesktop.org/wm-spec/wm-spec-latest.html . **/ Q_PROPERTY(int windowType READ windowType) /** * Whether this EffectWindow is managed by KWin (it has control over its placement and other * aspects, as opposed to override-redirect windows that are entirely handled by the application). **/ Q_PROPERTY(bool managed READ isManaged) /** * Whether this EffectWindow represents an already deleted window and only kept for the compositor for animations. **/ Q_PROPERTY(bool deleted READ isDeleted) /** * Whether the window has an own shape **/ Q_PROPERTY(bool shaped READ hasOwnShape) /** * The Window's shape **/ Q_PROPERTY(QRegion shape READ shape) /** * The Caption of the window. Read from WM_NAME property together with a suffix for hostname and shortcut. **/ Q_PROPERTY(QString caption READ caption) /** * Whether the window is set to be kept above other windows. **/ Q_PROPERTY(bool keepAbove READ keepAbove) /** * Whether the window is set to be kept below other windows. **/ Q_PROPERTY(bool keepBelow READ keepBelow) /** * Whether the window is minimized. **/ Q_PROPERTY(bool minimized READ isMinimized WRITE setMinimized) /** * Whether the window represents a modal window. **/ Q_PROPERTY(bool modal READ isModal) /** * Whether the window is moveable. Even if it is not moveable, it might be possible to move * it to another screen. * @see moveableAcrossScreens **/ Q_PROPERTY(bool moveable READ isMovable) /** * Whether the window can be moved to another screen. * @see moveable **/ Q_PROPERTY(bool moveableAcrossScreens READ isMovableAcrossScreens) /** * 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. **/ Q_PROPERTY(QSize basicUnit READ basicUnit) /** * Whether the window is currently being moved by the user. **/ Q_PROPERTY(bool move READ isUserMove) /** * Whether the window is currently being resized by the user. **/ Q_PROPERTY(bool resize READ isUserResize) /** * The optional geometry representing the minimized Client in e.g a taskbar. * See _NET_WM_ICON_GEOMETRY at https://standards.freedesktop.org/wm-spec/wm-spec-latest.html . **/ Q_PROPERTY(QRect iconGeometry READ iconGeometry) /** * Returns whether the window is any of special windows types (desktop, dock, splash, ...), * i.e. window types that usually don't have a window frame and the user does not use window * management (moving, raising,...) on them. **/ Q_PROPERTY(bool specialWindow READ isSpecialWindow) Q_PROPERTY(QIcon icon READ icon) /** * Whether the window should be excluded from window switching effects. **/ Q_PROPERTY(bool skipSwitcher READ isSkipSwitcher) /** * Geometry of the actual window contents inside the whole (including decorations) window. **/ Q_PROPERTY(QRect contentsRect READ contentsRect) /** * Geometry of the transparent rect in the decoration. * May be different from contentsRect if the decoration is extended into the client area. **/ Q_PROPERTY(QRect decorationInnerRect READ decorationInnerRect) Q_PROPERTY(bool hasDecoration READ hasDecoration) Q_PROPERTY(QStringList activities READ activities) Q_PROPERTY(bool onCurrentActivity READ isOnCurrentActivity) Q_PROPERTY(bool onAllActivities READ isOnAllActivities) /** * Whether the decoration currently uses an alpha channel. * @since 4.10 **/ Q_PROPERTY(bool decorationHasAlpha READ decorationHasAlpha) /** * Whether the window is currently visible to the user, that is: *
    *
  • Not minimized
  • *
  • On current desktop
  • *
  • On current activity
  • *
* @since 4.11 **/ Q_PROPERTY(bool visible READ isVisible) /** * Whether the window does not want to be animated on window close. * In case this property is @c true it is not useful to start an animation on window close. * The window will not be visible, but the animation hooks are executed. * @since 5.0 **/ Q_PROPERTY(bool skipsCloseAnimation READ skipsCloseAnimation) /** * Interface to the corresponding wayland surface. * relevant only in Wayland, on X11 it will be nullptr **/ Q_PROPERTY(KWayland::Server::SurfaceInterface *surface READ surface) /** * Whether the window is fullscreen. * @since 5.6 **/ Q_PROPERTY(bool fullScreen READ isFullScreen) /** * Whether this client is unresponsive. * * When an application failed to react on a ping request in time, it is * considered unresponsive. This usually indicates that the application froze or crashed. * * @since 5.10 **/ Q_PROPERTY(bool unresponsive READ isUnresponsive) /** * Whether this is a Wayland client. * @since 5.15 **/ Q_PROPERTY(bool waylandClient READ isWaylandClient CONSTANT) /** * Whether this is an X11 client. * @since 5.15 **/ Q_PROPERTY(bool x11Client READ isX11Client CONSTANT) /** * Whether the window is a popup. * * A popup is a window that can be used to implement tooltips, combo box popups, * popup menus and other similar user interface concepts. * * @since 5.15 **/ Q_PROPERTY(bool popupWindow READ isPopupWindow CONSTANT) /** * KWin internal window. Specific to Wayland platform. * * If the EffectWindow does not reference an internal window, this property is @c null. * @since 5.16 **/ Q_PROPERTY(QWindow *internalWindow READ internalWindow CONSTANT) /** * Whether this EffectWindow represents the outline. * * When compositing is turned on, the outline is an actual window. * * @since 5.16 **/ Q_PROPERTY(bool outline READ isOutline CONSTANT) public: /** Flags explaining why painting should be disabled */ enum { /** Window will not be painted */ PAINT_DISABLED = 1 << 0, /** Window will not be painted because it is deleted */ PAINT_DISABLED_BY_DELETE = 1 << 1, /** Window will not be painted because of which desktop it's on */ PAINT_DISABLED_BY_DESKTOP = 1 << 2, /** Window will not be painted because it is minimized */ PAINT_DISABLED_BY_MINIMIZE = 1 << 3, /** Window will not be painted because it is not the active window in a client group */ PAINT_DISABLED_BY_TAB_GROUP = 1 << 4, /** Window will not be painted because it's not on the current activity */ PAINT_DISABLED_BY_ACTIVITY = 1 << 5 }; explicit EffectWindow(QObject *parent = nullptr); virtual ~EffectWindow(); virtual void enablePainting(int reason) = 0; virtual void disablePainting(int reason) = 0; virtual bool isPaintingEnabled() = 0; Q_SCRIPTABLE virtual void addRepaint(const QRect &r) = 0; Q_SCRIPTABLE virtual void addRepaint(int x, int y, int w, int h) = 0; Q_SCRIPTABLE virtual void addRepaintFull() = 0; Q_SCRIPTABLE virtual void addLayerRepaint(const QRect &r) = 0; Q_SCRIPTABLE virtual void addLayerRepaint(int x, int y, int w, int h) = 0; virtual void refWindow() = 0; virtual void unrefWindow() = 0; virtual bool isDeleted() const = 0; virtual bool isMinimized() const = 0; virtual double opacity() const = 0; virtual bool hasAlpha() const = 0; bool isOnCurrentActivity() const; Q_SCRIPTABLE bool isOnActivity(QString id) const; bool isOnAllActivities() const; virtual QStringList activities() const = 0; Q_SCRIPTABLE bool isOnDesktop(int d) const; bool isOnCurrentDesktop() const; bool isOnAllDesktops() const; /** * The desktop this window is in. This mkaes sense only on X11 * where desktops are mutually exclusive, on Wayland it's the last * desktop the window has been added to. * use desktops() instead. * @see desktops() * @deprecated **/ #ifndef KWIN_NO_DEPRECATED virtual int KWIN_DEPRECATED desktop() const = 0; // prefer isOnXXX() #endif /** * All the desktops by number that the window is in. On X11 this list will always have * a length of 1, on Wayland can be any subset. * If the list is empty it means the window is on all desktops **/ virtual QVector desktops() const = 0; virtual int x() const = 0; virtual int y() const = 0; virtual int width() const = 0; virtual int height() const = 0; /** * 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. **/ virtual QSize basicUnit() const = 0; virtual QRect geometry() const = 0; /** * Geometry of the window including decoration and potentially shadows. * May be different from geometry() if the window has a shadow. * @since 4.9 **/ virtual QRect expandedGeometry() const = 0; virtual QRegion shape() const = 0; virtual int screen() const = 0; /** @internal Do not use */ virtual bool hasOwnShape() const = 0; // only for shadow effect, for now virtual QPoint pos() const = 0; virtual QSize size() const = 0; virtual QRect rect() const = 0; virtual bool isMovable() const = 0; virtual bool isMovableAcrossScreens() const = 0; virtual bool isUserMove() const = 0; virtual bool isUserResize() const = 0; virtual QRect iconGeometry() const = 0; /** * Geometry of the actual window contents inside the whole (including decorations) window. **/ virtual QRect contentsRect() const = 0; /** * Geometry of the transparent rect in the decoration. * May be different from contentsRect() if the decoration is extended into the client area. * @since 4.5 **/ virtual QRect decorationInnerRect() const = 0; bool hasDecoration() const; virtual bool decorationHasAlpha() const = 0; virtual QByteArray readProperty(long atom, long type, int format) const = 0; virtual void deleteProperty(long atom) const = 0; virtual QString caption() const = 0; virtual QIcon icon() const = 0; virtual QString windowClass() const = 0; virtual QString windowRole() const = 0; virtual const EffectWindowGroup* group() const = 0; /** * Returns whether the window is a desktop background window (the one with wallpaper). * See _NET_WM_WINDOW_TYPE_DESKTOP at https://standards.freedesktop.org/wm-spec/wm-spec-latest.html . **/ virtual bool isDesktop() const = 0; /** * Returns whether the window is a dock (i.e. a panel). * See _NET_WM_WINDOW_TYPE_DOCK at https://standards.freedesktop.org/wm-spec/wm-spec-latest.html . **/ virtual bool isDock() const = 0; /** * Returns whether the window is a standalone (detached) toolbar window. * See _NET_WM_WINDOW_TYPE_TOOLBAR at https://standards.freedesktop.org/wm-spec/wm-spec-latest.html . **/ virtual bool isToolbar() const = 0; /** * Returns whether the window is a torn-off menu. * See _NET_WM_WINDOW_TYPE_MENU at https://standards.freedesktop.org/wm-spec/wm-spec-latest.html . **/ virtual bool isMenu() const = 0; /** * Returns whether the window is a "normal" window, i.e. an application or any other window * for which none of the specialized window types fit. * See _NET_WM_WINDOW_TYPE_NORMAL at https://standards.freedesktop.org/wm-spec/wm-spec-latest.html . **/ virtual bool isNormalWindow() const = 0; // normal as in 'NET::Normal or NET::Unknown non-transient' /** * Returns whether the window is any of special windows types (desktop, dock, splash, ...), * i.e. window types that usually don't have a window frame and the user does not use window * management (moving, raising,...) on them. **/ virtual bool isSpecialWindow() const = 0; /** * Returns whether the window is a dialog window. * See _NET_WM_WINDOW_TYPE_DIALOG at https://standards.freedesktop.org/wm-spec/wm-spec-latest.html . **/ virtual bool isDialog() const = 0; /** * Returns whether the window is a splashscreen. Note that many (especially older) applications * do not support marking their splash windows with this type. * See _NET_WM_WINDOW_TYPE_SPLASH at https://standards.freedesktop.org/wm-spec/wm-spec-latest.html . **/ virtual bool isSplash() const = 0; /** * Returns whether the window is a utility window, such as a tool window. * See _NET_WM_WINDOW_TYPE_UTILITY at https://standards.freedesktop.org/wm-spec/wm-spec-latest.html . **/ virtual bool isUtility() const = 0; /** * Returns whether the window is a dropdown menu (i.e. a popup directly or indirectly open * from the applications menubar). * See _NET_WM_WINDOW_TYPE_DROPDOWN_MENU at https://standards.freedesktop.org/wm-spec/wm-spec-latest.html . **/ virtual bool isDropdownMenu() const = 0; /** * Returns whether the window is a popup menu (that is not a torn-off or dropdown menu). * See _NET_WM_WINDOW_TYPE_POPUP_MENU at https://standards.freedesktop.org/wm-spec/wm-spec-latest.html . **/ virtual bool isPopupMenu() const = 0; // a context popup, not dropdown, not torn-off /** * Returns whether the window is a tooltip. * See _NET_WM_WINDOW_TYPE_TOOLTIP at https://standards.freedesktop.org/wm-spec/wm-spec-latest.html . **/ virtual bool isTooltip() const = 0; /** * Returns whether the window is a window with a notification. * See _NET_WM_WINDOW_TYPE_NOTIFICATION at https://standards.freedesktop.org/wm-spec/wm-spec-latest.html . **/ virtual bool isNotification() const = 0; /** * Returns whether the window is a window with a critical notification. * using the non-standard _KDE_NET_WM_WINDOW_TYPE_CRITICAL_NOTIFICATION **/ virtual bool isCriticalNotification() const = 0; /** * Returns whether the window is an on screen display window * using the non-standard _KDE_NET_WM_WINDOW_TYPE_ON_SCREEN_DISPLAY **/ virtual bool isOnScreenDisplay() const = 0; /** * Returns whether the window is a combobox popup. * See _NET_WM_WINDOW_TYPE_COMBO at https://standards.freedesktop.org/wm-spec/wm-spec-latest.html . **/ virtual bool isComboBox() const = 0; /** * Returns whether the window is a Drag&Drop icon. * See _NET_WM_WINDOW_TYPE_DND at https://standards.freedesktop.org/wm-spec/wm-spec-latest.html . **/ virtual bool isDNDIcon() const = 0; /** * Returns the NETWM window type * See https://standards.freedesktop.org/wm-spec/wm-spec-latest.html . **/ virtual NET::WindowType windowType() const = 0; /** * Returns whether the window is managed by KWin (it has control over its placement and other * aspects, as opposed to override-redirect windows that are entirely handled by the application). **/ virtual bool isManaged() const = 0; // whether it's managed or override-redirect /** * Returns whether or not the window can accept keyboard focus. **/ virtual bool acceptsFocus() const = 0; /** * Returns whether or not the window is kept above all other windows. **/ virtual bool keepAbove() const = 0; /** * Returns whether the window is kept below all other windows. **/ virtual bool keepBelow() const = 0; virtual bool isModal() const = 0; Q_SCRIPTABLE virtual KWin::EffectWindow* findModal() = 0; Q_SCRIPTABLE virtual QList mainWindows() const = 0; /** * Returns whether the window should be excluded from window switching effects. * @since 4.5 **/ virtual bool isSkipSwitcher() const = 0; /** * Returns the unmodified window quad list. Can also be used to force rebuilding. **/ virtual WindowQuadList buildQuads(bool force = false) const = 0; void setMinimized(bool minimize); virtual void minimize() = 0; virtual void unminimize() = 0; Q_SCRIPTABLE virtual void closeWindow() = 0; virtual bool isCurrentTab() const = 0; /** * @since 4.11 **/ bool isVisible() const; /** * @since 5.0 **/ virtual bool skipsCloseAnimation() const = 0; /** * @since 5.5 **/ virtual KWayland::Server::SurfaceInterface *surface() const = 0; /** * @since 5.6 **/ virtual bool isFullScreen() const = 0; /** * @since 5.10 **/ virtual bool isUnresponsive() const = 0; /** * @since 5.15 **/ virtual bool isWaylandClient() const = 0; /** * @since 5.15 **/ virtual bool isX11Client() const = 0; /** * @since 5.15 **/ virtual bool isPopupWindow() const = 0; /** * @since 5.16 **/ virtual QWindow *internalWindow() const = 0; /** * @since 5.16 **/ virtual bool isOutline() const = 0; /** * Can be used to by effects to store arbitrary data in the EffectWindow. * * Invoking this method will emit the signal EffectsHandler::windowDataChanged. * @see EffectsHandler::windowDataChanged **/ Q_SCRIPTABLE virtual void setData(int role, const QVariant &data) = 0; Q_SCRIPTABLE virtual QVariant data(int role) const = 0; /** * @brief References the previous window pixmap to prevent discarding. * * This method allows to reference the previous window pixmap in case that a window changed * its size, which requires a new window pixmap. By referencing the previous (and then outdated) * window pixmap an effect can for example cross fade the current window pixmap with the previous * one. This allows for smoother transitions for window geometry changes. * * If an effect calls this method on a window it also needs to call unreferencePreviousWindowPixmap * once it does no longer need the previous window pixmap. * * Note: the window pixmap is not kept forever even when referenced. If the geometry changes again, so that * a new window pixmap is created, the previous window pixmap will be exchanged with the current one. This * means it's still possible to have rendering glitches. An effect is supposed to track for itself the changes * to the window's geometry and decide how the transition should continue in such a situation. * * @see unreferencePreviousWindowPixmap * @since 4.11 **/ virtual void referencePreviousWindowPixmap() = 0; /** * @brief Unreferences the previous window pixmap. Only relevant after referencePreviousWindowPixmap had * been called. * * @see referencePreviousWindowPixmap * @since 4.11 **/ virtual void unreferencePreviousWindowPixmap() = 0; private: class Private; QScopedPointer d; }; class KWINEFFECTS_EXPORT EffectWindowGroup { public: virtual ~EffectWindowGroup(); virtual EffectWindowList members() const = 0; }; struct GLVertex2D { QVector2D position; QVector2D texcoord; }; struct GLVertex3D { QVector3D position; QVector2D texcoord; }; /** * @short Vertex class * * A vertex is one position in a window. WindowQuad consists of four WindowVertex objects * and represents one part of a window. **/ class KWINEFFECTS_EXPORT WindowVertex { public: WindowVertex(); WindowVertex(const QPointF &position, const QPointF &textureCoordinate); WindowVertex(double x, double y, double tx, double ty); double x() const { return px; } double y() const { return py; } double u() const { return tx; } double v() const { return ty; } double originalX() const { return ox; } double originalY() const { return oy; } double textureX() const { return tx; } double textureY() const { return ty; } void move(double x, double y); void setX(double x); void setY(double y); private: friend class WindowQuad; friend class WindowQuadList; double px, py; // position double ox, oy; // origional position double tx, ty; // texture coords }; /** * @short Class representing one area of a window. * * WindowQuads consists of four WindowVertex objects and represents one part of a window. **/ // NOTE: This class expects the (original) vertices to be in the clockwise order starting from topleft. class KWINEFFECTS_EXPORT WindowQuad { public: explicit WindowQuad(WindowQuadType type, int id = -1); WindowQuad makeSubQuad(double x1, double y1, double x2, double y2) const; WindowVertex& operator[](int index); const WindowVertex& operator[](int index) const; WindowQuadType type() const; void setUVAxisSwapped(bool value) { uvSwapped = value; } bool uvAxisSwapped() const { return uvSwapped; } int id() const; bool decoration() const; bool effect() const; double left() const; double right() const; double top() const; double bottom() const; double originalLeft() const; double originalRight() const; double originalTop() const; double originalBottom() const; bool smoothNeeded() const; bool isTransformed() const; private: friend class WindowQuadList; WindowVertex verts[ 4 ]; WindowQuadType quadType; // 0 - contents, 1 - decoration bool uvSwapped; int quadID; }; class KWINEFFECTS_EXPORT WindowQuadList : public QList< WindowQuad > { public: WindowQuadList splitAtX(double x) const; WindowQuadList splitAtY(double y) const; WindowQuadList makeGrid(int maxquadsize) const; WindowQuadList makeRegularGrid(int xSubdivisions, int ySubdivisions) const; WindowQuadList select(WindowQuadType type) const; WindowQuadList filterOut(WindowQuadType type) const; bool smoothNeeded() const; void makeInterleavedArrays(unsigned int type, GLVertex2D *vertices, const QMatrix4x4 &matrix) const; void makeArrays(float** vertices, float** texcoords, const QSizeF &size, bool yInverted) const; bool isTransformed() const; }; class KWINEFFECTS_EXPORT WindowPrePaintData { public: int mask; /** * Region that will be painted, in screen coordinates. **/ QRegion paint; /** * The clip region will be subtracted from paint region of following windows. * I.e. window will definitely cover it's clip region **/ QRegion clip; WindowQuadList quads; /** * Simple helper that sets data to say the window will be painted as non-opaque. * Takes also care of changing the regions. **/ void setTranslucent(); /** * Helper to mark that this window will be transformed **/ void setTransformed(); }; class KWINEFFECTS_EXPORT PaintData { public: virtual ~PaintData(); /** * @returns scale factor in X direction. * @since 4.10 **/ qreal xScale() const; /** * @returns scale factor in Y direction. * @since 4.10 **/ qreal yScale() const; /** * @returns scale factor in Z direction. * @since 4.10 **/ qreal zScale() const; /** * Sets the scale factor in X direction to @p scale * @param scale The scale factor in X direction * @since 4.10 **/ void setXScale(qreal scale); /** * Sets the scale factor in Y direction to @p scale * @param scale The scale factor in Y direction * @since 4.10 **/ void setYScale(qreal scale); /** * Sets the scale factor in Z direction to @p scale * @param scale The scale factor in Z direction * @since 4.10 **/ void setZScale(qreal scale); /** * Sets the scale factor in X and Y direction. * @param scale The scale factor for X and Y direction * @since 4.10 **/ void setScale(const QVector2D &scale); /** * Sets the scale factor in X, Y and Z direction * @param scale The scale factor for X, Y and Z direction * @since 4.10 **/ void setScale(const QVector3D &scale); const QGraphicsScale &scale() const; const QVector3D &translation() const; /** * @returns the translation in X direction. * @since 4.10 **/ qreal xTranslation() const; /** * @returns the translation in Y direction. * @since 4.10 **/ qreal yTranslation() const; /** * @returns the translation in Z direction. * @since 4.10 **/ qreal zTranslation() const; /** * Sets the translation in X direction to @p translate. * @since 4.10 **/ void setXTranslation(qreal translate); /** * Sets the translation in Y direction to @p translate. * @since 4.10 **/ void setYTranslation(qreal translate); /** * Sets the translation in Z direction to @p translate. * @since 4.10 **/ void setZTranslation(qreal translate); /** * Performs a translation by adding the values component wise. * @param x Translation in X direction * @param y Translation in Y direction * @param z Translation in Z direction * @since 4.10 **/ void translate(qreal x, qreal y = 0.0, qreal z = 0.0); /** * Performs a translation by adding the values component wise. * Overloaded method for convenience. * @param translate The translation * @since 4.10 **/ void translate(const QVector3D &translate); /** * Sets the rotation angle. * @param angle The new rotation angle. * @since 4.10 * @see rotationAngle() **/ void setRotationAngle(qreal angle); /** * Returns the rotation angle. * Initially 0.0. * @returns The current rotation angle. * @since 4.10 * @see setRotationAngle **/ qreal rotationAngle() const; /** * Sets the rotation origin. * @param origin The new rotation origin. * @since 4.10 * @see rotationOrigin() **/ void setRotationOrigin(const QVector3D &origin); /** * Returns the rotation origin. That is the point in space which is fixed during the rotation. * Initially this is 0/0/0. * @returns The rotation's origin * @since 4.10 * @see setRotationOrigin() **/ QVector3D rotationOrigin() const; /** * Sets the rotation axis. * Set a component to 1.0 to rotate around this axis and to 0.0 to disable rotation around the * axis. * @param axis A vector holding information on which axis to rotate * @since 4.10 * @see rotationAxis() **/ void setRotationAxis(const QVector3D &axis); /** * Sets the rotation axis. * Overloaded method for convenience. * @param axis The axis around which should be rotated. * @since 4.10 * @see rotationAxis() **/ void setRotationAxis(Qt::Axis axis); /** * The current rotation axis. * By default the rotation is (0/0/1) which means a rotation around the z axis. * @returns The current rotation axis. * @since 4.10 * @see setRotationAxis **/ QVector3D rotationAxis() const; protected: PaintData(); PaintData(const PaintData &other); private: PaintDataPrivate * const d; }; class KWINEFFECTS_EXPORT WindowPaintData : public PaintData { public: explicit WindowPaintData(EffectWindow* w); explicit WindowPaintData(EffectWindow* w, const QMatrix4x4 &screenProjectionMatrix); WindowPaintData(const WindowPaintData &other); virtual ~WindowPaintData(); /** * Scales the window by @p scale factor. * Multiplies all three components by the given factor. * @since 4.10 **/ WindowPaintData& operator*=(qreal scale); /** * Scales the window by @p scale factor. * Performs a component wise multiplication on x and y components. * @since 4.10 **/ WindowPaintData& operator*=(const QVector2D &scale); /** * Scales the window by @p scale factor. * Performs a component wise multiplication. * @since 4.10 **/ WindowPaintData& operator*=(const QVector3D &scale); /** * Translates the window by the given @p translation and returns a reference to the ScreenPaintData. * @since 4.10 **/ WindowPaintData& operator+=(const QPointF &translation); /** * Translates the window by the given @p translation and returns a reference to the ScreenPaintData. * Overloaded method for convenience. * @since 4.10 **/ WindowPaintData& operator+=(const QPoint &translation); /** * Translates the window by the given @p translation and returns a reference to the ScreenPaintData. * Overloaded method for convenience. * @since 4.10 **/ WindowPaintData& operator+=(const QVector2D &translation); /** * Translates the window by the given @p translation and returns a reference to the ScreenPaintData. * Overloaded method for convenience. * @since 4.10 **/ WindowPaintData& operator+=(const QVector3D &translation); /** * Window opacity, in range 0 = transparent to 1 = fully opaque * @see setOpacity * @since 4.10 **/ qreal opacity() const; /** * Sets the window opacity to the new @p opacity. * If you want to modify the existing opacity level consider using multiplyOpacity. * @param opacity The new opacity level * @since 4.10 **/ void setOpacity(qreal opacity); /** * Multiplies the current opacity with the @p factor. * @param factor Factor with which the opacity should be multiplied * @return New opacity level * @since 4.10 **/ qreal multiplyOpacity(qreal factor); /** * Saturation of the window, in range [0; 1] * 1 means that the window is unchanged, 0 means that it's completely * unsaturated (greyscale). 0.5 would make the colors less intense, * but not completely grey * Use EffectsHandler::saturationSupported() to find out whether saturation * is supported by the system, otherwise this value has no effect. * @return The current saturation * @see setSaturation() * @since 4.10 **/ qreal saturation() const; /** * Sets the window saturation level to @p saturation. * If you want to modify the existing saturation level consider using multiplySaturation. * @param saturation The new saturation level * @since 4.10 **/ void setSaturation(qreal saturation) const; /** * Multiplies the current saturation with @p factor. * @param factor with which the saturation should be multiplied * @return New saturation level * @since 4.10 **/ qreal multiplySaturation(qreal factor); /** * Brightness of the window, in range [0; 1] * 1 means that the window is unchanged, 0 means that it's completely * black. 0.5 would make it 50% darker than usual **/ qreal brightness() const; /** * Sets the window brightness level to @p brightness. * If you want to modify the existing brightness level consider using multiplyBrightness. * @param brightness The new brightness level **/ void setBrightness(qreal brightness); /** * Multiplies the current brightness level with @p factor. * @param factor with which the brightness should be multiplied. * @return New brightness level * @since 4.10 **/ qreal multiplyBrightness(qreal factor); /** * The screen number for which the painting should be done. * This affects color correction (different screens may need different * color correction lookup tables because they have different ICC profiles). * @return screen for which painting should be done **/ int screen() const; /** * @param screen New screen number * A value less than 0 will indicate that a default profile should be done. **/ void setScreen(int screen) const; /** * @brief Sets the cross fading @p factor to fade over with previously sized window. * If @c 1.0 only the current window is used, if @c 0.0 only the previous window is used. * * By default only the current window is used. This factor can only make any visual difference * if the previous window get referenced. * * @param factor The cross fade factor between @c 0.0 (previous window) and @c 1.0 (current window) * @see crossFadeProgress **/ void setCrossFadeProgress(qreal factor); /** * @see setCrossFadeProgress **/ qreal crossFadeProgress() const; /** * Sets the projection matrix that will be used when painting the window. * * The default projection matrix can be overridden by setting this matrix * to a non-identity matrix. **/ void setProjectionMatrix(const QMatrix4x4 &matrix); /** * Returns the current projection matrix. * * The default value for this matrix is the identity matrix. **/ QMatrix4x4 projectionMatrix() const; /** * Returns a reference to the projection matrix. **/ QMatrix4x4 &rprojectionMatrix(); /** * Sets the model-view matrix that will be used when painting the window. * * The default model-view matrix can be overridden by setting this matrix * to a non-identity matrix. **/ void setModelViewMatrix(const QMatrix4x4 &matrix); /** * Returns the current model-view matrix. * * The default value for this matrix is the identity matrix. **/ QMatrix4x4 modelViewMatrix() const; /** * Returns a reference to the model-view matrix. **/ QMatrix4x4 &rmodelViewMatrix(); /** * Returns The projection matrix as used by the current screen painting pass * including screen transformations. * * @since 5.6 **/ QMatrix4x4 screenProjectionMatrix() const; WindowQuadList quads; /** * Shader to be used for rendering, if any. **/ GLShader* shader; private: WindowPaintDataPrivate * const d; }; class KWINEFFECTS_EXPORT ScreenPaintData : public PaintData { public: ScreenPaintData(); ScreenPaintData(const QMatrix4x4 &projectionMatrix, const QRect &outputGeometry = QRect()); ScreenPaintData(const ScreenPaintData &other); virtual ~ScreenPaintData(); /** * Scales the screen by @p scale factor. * Multiplies all three components by the given factor. * @since 4.10 **/ ScreenPaintData& operator*=(qreal scale); /** * Scales the screen by @p scale factor. * Performs a component wise multiplication on x and y components. * @since 4.10 **/ ScreenPaintData& operator*=(const QVector2D &scale); /** * Scales the screen by @p scale factor. * Performs a component wise multiplication. * @since 4.10 **/ ScreenPaintData& operator*=(const QVector3D &scale); /** * Translates the screen by the given @p translation and returns a reference to the ScreenPaintData. * @since 4.10 **/ ScreenPaintData& operator+=(const QPointF &translation); /** * Translates the screen by the given @p translation and returns a reference to the ScreenPaintData. * Overloaded method for convenience. * @since 4.10 **/ ScreenPaintData& operator+=(const QPoint &translation); /** * Translates the screen by the given @p translation and returns a reference to the ScreenPaintData. * Overloaded method for convenience. * @since 4.10 **/ ScreenPaintData& operator+=(const QVector2D &translation); /** * Translates the screen by the given @p translation and returns a reference to the ScreenPaintData. * Overloaded method for convenience. * @since 4.10 **/ ScreenPaintData& operator+=(const QVector3D &translation); ScreenPaintData& operator=(const ScreenPaintData &rhs); /** * The projection matrix used by the scene for the current rendering pass. * On non-OpenGL compositors it's set to Identity matrix. * @since 5.6 **/ QMatrix4x4 projectionMatrix() const; /** * The geometry of the currently rendered output. * Only set for per-output rendering (e.g. Wayland). * * This geometry can be used as a hint about the native window the OpenGL context * is bound. OpenGL calls need to be translated to this geometry. * @since 5.9 **/ QRect outputGeometry() const; private: class Private; QScopedPointer d; }; class KWINEFFECTS_EXPORT ScreenPrePaintData { public: int mask; QRegion paint; }; /** * @short Helper class for restricting painting area only to allowed area. * * This helper class helps specifying areas that should be painted, clipping * out the rest. The simplest usage is creating an object on the stack * and giving it the area that is allowed to be painted to. When the object * is destroyed, the restriction will be removed. * Note that all painting code must use paintArea() to actually perform the clipping. **/ class KWINEFFECTS_EXPORT PaintClipper { public: /** * Calls push(). **/ explicit PaintClipper(const QRegion& allowed_area); /** * Calls pop(). **/ ~PaintClipper(); /** * Allows painting only in the given area. When areas have been already * specified, painting is allowed only in the intersection of all areas. **/ static void push(const QRegion& allowed_area); /** * Removes the given area. It must match the top item in the stack. **/ static void pop(const QRegion& allowed_area); /** * Returns true if any clipping should be performed. **/ static bool clip(); /** * If clip() returns true, this function gives the resulting area in which * painting is allowed. It is usually simpler to use the helper Iterator class. **/ static QRegion paintArea(); /** * Helper class to perform the clipped painting. The usage is: * @code * for ( PaintClipper::Iterator iterator; * !iterator.isDone(); * iterator.next()) * { // do the painting, possibly use iterator.boundingRect() * } * @endcode **/ class KWINEFFECTS_EXPORT Iterator { public: Iterator(); ~Iterator(); bool isDone(); void next(); QRect boundingRect() const; private: struct Data; Data* data; }; private: QRegion area; static QStack< QRegion >* areas; }; /** * @internal **/ template class KWINEFFECTS_EXPORT Motion { public: /** * Creates a new motion object. "Strength" is the amount of * acceleration that is applied to the object when the target * changes and "smoothness" relates to how fast the object * can change its direction and speed. **/ explicit Motion(T initial, double strength, double smoothness); /** * Creates an exact copy of another motion object, including * position, target and velocity. **/ Motion(const Motion &other); ~Motion(); inline T value() const { return m_value; } inline void setValue(const T value) { m_value = value; } inline T target() const { return m_target; } inline void setTarget(const T target) { m_start = m_value; m_target = target; } inline T velocity() const { return m_velocity; } inline void setVelocity(const T velocity) { m_velocity = velocity; } inline double strength() const { return m_strength; } inline void setStrength(const double strength) { m_strength = strength; } inline double smoothness() const { return m_smoothness; } inline void setSmoothness(const double smoothness) { m_smoothness = smoothness; } inline T startValue() { return m_start; } /** * The distance between the current position and the target. **/ inline T distance() const { return m_target - m_value; } /** * Calculates the new position if not at the target. Called * once per frame only. **/ void calculate(const int msec); /** * Place the object on top of the target immediately, * bypassing all movement calculation. **/ void finish(); private: T m_value; T m_start; T m_target; T m_velocity; double m_strength; double m_smoothness; }; /** * @short A single 1D motion dynamics object. * * This class represents a single object that can be moved around a * 1D space. Although it can be used directly by itself it is * recommended to use a motion manager instead. **/ class KWINEFFECTS_EXPORT Motion1D : public Motion { public: explicit Motion1D(double initial = 0.0, double strength = 0.08, double smoothness = 4.0); Motion1D(const Motion1D &other); ~Motion1D(); }; /** * @short A single 2D motion dynamics object. * * This class represents a single object that can be moved around a * 2D space. Although it can be used directly by itself it is * recommended to use a motion manager instead. **/ class KWINEFFECTS_EXPORT Motion2D : public Motion { public: explicit Motion2D(QPointF initial = QPointF(), double strength = 0.08, double smoothness = 4.0); Motion2D(const Motion2D &other); ~Motion2D(); }; /** * @short Helper class for motion dynamics in KWin effects. * * This motion manager class is intended to help KWin effect authors * move windows across the screen smoothly and naturally. Once * windows are registered by the manager the effect can issue move * commands with the moveWindow() methods. The position of any * managed window can be determined in realtime by the * transformedGeometry() method. As the manager knows if any windows * are moving at any given time it can also be used as a notifier as * to see whether the effect is active or not. **/ class KWINEFFECTS_EXPORT WindowMotionManager { public: /** * Creates a new window manager object. **/ explicit WindowMotionManager(bool useGlobalAnimationModifier = true); ~WindowMotionManager(); /** * Register a window for managing. **/ void manage(EffectWindow *w); /** * Register a list of windows for managing. **/ inline void manage(EffectWindowList list) { for (int i = 0; i < list.size(); i++) manage(list.at(i)); } /** * Deregister a window. All transformations applied to the * window will be permanently removed and cannot be recovered. **/ void unmanage(EffectWindow *w); /** * Deregister all windows, returning the manager to its * originally initiated state. **/ void unmanageAll(); /** * Determine the new positions for windows that have not * reached their target. Called once per frame, usually in * prePaintScreen(). Remember to set the * Effect::PAINT_SCREEN_WITH_TRANSFORMED_WINDOWS flag. **/ void calculate(int time); /** * Modify a registered window's paint data to make it appear * at its real location on the screen. Usually called in * paintWindow(). Remember to flag the window as having been * transformed in prePaintWindow() by calling * WindowPrePaintData::setTransformed() **/ void apply(EffectWindow *w, WindowPaintData &data); /** * Set all motion targets and values back to where the * windows were before transformations. The same as * unmanaging then remanaging all windows. **/ void reset(); /** * Resets the motion target and current value of a single * window. **/ void reset(EffectWindow *w); /** * Ask the manager to move the window to the target position * with the specified scale. If `yScale` is not provided or * set to 0.0, `scale` will be used as the scale in the * vertical direction as well as in the horizontal direction. **/ void moveWindow(EffectWindow *w, QPoint target, double scale = 1.0, double yScale = 0.0); /** * This is an overloaded method, provided for convenience. * * Ask the manager to move the window to the target rectangle. * Automatically determines scale. **/ inline void moveWindow(EffectWindow *w, QRect target) { // TODO: Scale might be slightly different in the comparison due to rounding moveWindow(w, target.topLeft(), target.width() / double(w->width()), target.height() / double(w->height())); } /** * Retrieve the current tranformed geometry of a registered * window. **/ QRectF transformedGeometry(EffectWindow *w) const; /** * Sets the current transformed geometry of a registered window to the given geometry. * @see transformedGeometry * @since 4.5 **/ void setTransformedGeometry(EffectWindow *w, const QRectF &geometry); /** * Retrieve the current target geometry of a registered * window. **/ QRectF targetGeometry(EffectWindow *w) const; /** * Return the window that has its transformed geometry under * the specified point. It is recommended to use the stacking * order as it's what the user sees, but it is slightly * slower to process. **/ EffectWindow* windowAtPoint(QPoint point, bool useStackingOrder = true) const; /** * Return a list of all currently registered windows. **/ inline EffectWindowList managedWindows() const { return m_managedWindows.keys(); } /** * Returns whether or not a specified window is being managed * by this manager object. **/ inline bool isManaging(EffectWindow *w) const { return m_managedWindows.contains(w); } /** * Returns whether or not this manager object is actually * managing any windows or not. **/ inline bool managingWindows() const { return !m_managedWindows.empty(); } /** * Returns whether all windows have reached their targets yet * or not. Can be used to see if an effect should be * processed and displayed or not. **/ inline bool areWindowsMoving() const { return !m_movingWindowsSet.isEmpty(); } /** * Returns whether a window has reached its targets yet * or not. **/ inline bool isWindowMoving(EffectWindow *w) const { return m_movingWindowsSet.contains(w); } private: bool m_useGlobalAnimationModifier; struct WindowMotion { // TODO: Rotation, etc? Motion2D translation; // Absolute position Motion2D scale; // xScale and yScale }; QHash m_managedWindows; QSet m_movingWindowsSet; }; /** * @short Helper class for displaying text and icons in frames. * * Paints text and/or and icon with an optional frame around them. The * available frames includes one that follows the default Plasma theme and * another that doesn't. * It is recommended to use this class whenever displaying text. **/ class KWINEFFECTS_EXPORT EffectFrame { public: EffectFrame(); virtual ~EffectFrame(); /** * Delete any existing textures to free up graphics memory. They will * be automatically recreated the next time they are required. **/ virtual void free() = 0; /** * Render the frame. **/ virtual void render(QRegion region = infiniteRegion(), double opacity = 1.0, double frameOpacity = 1.0) = 0; virtual void setPosition(const QPoint& point) = 0; /** * Set the text alignment for static frames and the position alignment * for non-static. **/ virtual void setAlignment(Qt::Alignment alignment) = 0; virtual Qt::Alignment alignment() const = 0; virtual void setGeometry(const QRect& geometry, bool force = false) = 0; virtual const QRect& geometry() const = 0; virtual void setText(const QString& text) = 0; virtual const QString& text() const = 0; virtual void setFont(const QFont& font) = 0; virtual const QFont& font() const = 0; /** * Set the icon that will appear on the left-hand size of the frame. **/ virtual void setIcon(const QIcon& icon) = 0; virtual const QIcon& icon() const = 0; virtual void setIconSize(const QSize& size) = 0; virtual const QSize& iconSize() const = 0; /** * Sets the geometry of a selection. * To remove the selection set a null rect. * @param selection The geometry of the selection in screen coordinates. **/ virtual void setSelection(const QRect& selection) = 0; /** * @param shader The GLShader for rendering. **/ virtual void setShader(GLShader* shader) = 0; /** * @returns The GLShader used for rendering or null if none. **/ virtual GLShader* shader() const = 0; /** * @returns The style of this EffectFrame. **/ virtual EffectFrameStyle style() const = 0; /** * If @p enable is @c true cross fading between icons and text is enabled * By default disabled. Use setCrossFadeProgress to cross fade. * Cross Fading is currently only available if OpenGL is used. * @param enable @c true enables cross fading, @c false disables it again * @see isCrossFade * @see setCrossFadeProgress * @since 4.6 **/ void enableCrossFade(bool enable); /** * @returns @c true if cross fading is enabled, @c false otherwise * @see enableCrossFade * @since 4.6 **/ bool isCrossFade() const; /** * Sets the current progress for cross fading the last used icon/text * with current icon/text to @p progress. * A value of 0.0 means completely old icon/text, a value of 1.0 means * completely current icon/text. * Default value is 1.0. You have to enable cross fade before using it. * Cross Fading is currently only available if OpenGL is used. * @see enableCrossFade * @see isCrossFade * @see crossFadeProgress * @since 4.6 **/ void setCrossFadeProgress(qreal progress); /** * @returns The current progress for cross fading * @see setCrossFadeProgress * @see enableCrossFade * @see isCrossFade * @since 4.6 **/ qreal crossFadeProgress() const; /** * Returns The projection matrix as used by the current screen painting pass * including screen transformations. * * This matrix is only valid during a rendering pass started by render. * * @since 5.6 * @see render * @see EffectsHandler::paintEffectFrame * @see Effect::paintEffectFrame **/ QMatrix4x4 screenProjectionMatrix() const; protected: void setScreenProjectionMatrix(const QMatrix4x4 &projection); private: EffectFramePrivate* const d; }; /** * The TimeLine class is a helper for controlling animations. **/ class KWINEFFECTS_EXPORT TimeLine { public: /** * Direction of the timeline. * * When the direction of the timeline is Forward, the progress * value will go from 0.0 to 1.0. * * When the direction of the timeline is Backward, the progress * value will go from 1.0 to 0.0. **/ enum Direction { Forward, Backward }; /** * Constructs a new instance of TimeLine. * * @param duration Duration of the timeline, in milliseconds * @param direction Direction of the timeline * @since 5.14 **/ explicit TimeLine(std::chrono::milliseconds duration = std::chrono::milliseconds(1000), Direction direction = Forward); TimeLine(const TimeLine &other); ~TimeLine(); /** * Returns the current value of the timeline. * * @since 5.14 **/ qreal value() const; /** * Updates the progress of the timeline. * * @note The delta value should be a non-negative number, i.e. it * should be greater or equal to 0. * * @param delta The number milliseconds passed since last frame * @since 5.14 **/ void update(std::chrono::milliseconds delta); /** * Returns the number of elapsed milliseconds. * * @see setElapsed * @since 5.14 **/ std::chrono::milliseconds elapsed() const; /** * Sets the number of elapsed milliseconds. * * This method overwrites previous value of elapsed milliseconds. * If the new value of elapsed milliseconds is greater or equal * to duration of the timeline, the timeline will be finished, i.e. * proceeding TimeLine::done method calls will return @c true. * Please don't use it. Instead, use TimeLine::update. * * @note The new number of elapsed milliseconds should be a non-negative * number, i.e. it should be greater or equal to 0. * * @param elapsed The new number of elapsed milliseconds * @see elapsed * @since 5.14 **/ void setElapsed(std::chrono::milliseconds elapsed); /** * Returns the duration of the timeline. * * @returns Duration of the timeline, in milliseconds * @see setDuration * @since 5.14 **/ std::chrono::milliseconds duration() const; /** * Sets the duration of the timeline. * * In addition to setting new value of duration, the timeline will * try to retarget the number of elapsed milliseconds to match * as close as possible old progress value. If the new duration * is much smaller than old duration, there is a big chance that * the timeline will be finished after setting new duration. * * @note The new duration should be a positive number, i.e. it * should be greater or equal to 1. * * @param duration The new duration of the timeline, in milliseconds * @see duration * @since 5.14 **/ void setDuration(std::chrono::milliseconds duration); /** * Returns the direction of the timeline. * * @returns Direction of the timeline(TimeLine::Forward or TimeLine::Backward) * @see setDirection * @see toggleDirection * @since 5.14 **/ Direction direction() const; /** * Sets the direction of the timeline. * * @param direction The new direction of the timeline * @see direction * @see toggleDirection * @since 5.14 **/ void setDirection(Direction direction); /** * Toggles the direction of the timeline. * * If the direction of the timeline was TimeLine::Forward, it becomes * TimeLine::Backward, and vice verca. * * @see direction * @see setDirection * @since 5.14 **/ void toggleDirection(); /** * Returns the easing curve of the timeline. * * @see setEasingCurve * @since 5.14 **/ QEasingCurve easingCurve() const; /** * Sets new easing curve. * * @param easingCurve An easing curve to be set * @see easingCurve * @since 5.14 **/ void setEasingCurve(const QEasingCurve &easingCurve); /** * Sets new easing curve by providing its type. * * @param type Type of the easing curve(e.g. QEasingCurve::InQuad, etc) * @see easingCurve * @since 5.14 **/ void setEasingCurve(QEasingCurve::Type type); /** * Returns whether the timeline is currently in progress. * * @see done * @since 5.14 **/ bool running() const; /** * Returns whether the timeline is finished. * * @see reset * @since 5.14 **/ bool done() const; /** * Resets the timeline to initial state. * * @since 5.14 **/ void reset(); enum class RedirectMode { Strict, Relaxed }; /** * Returns the redirect mode for the source position. * * The redirect mode controls behavior of the timeline when its direction is * changed at the source position, e.g. what should we do when the timeline * initially goes forward and we change its direction to go backward. * * In the strict mode, the timeline will stop. * * In the relaxed mode, the timeline will go in the new direction. For example, * if the timeline goes forward(from 0 to 1), then with the new direction it * will go backward(from 1 to 0). * * The default is RedirectMode::Relaxed. * * @see targetRedirectMode * @since 5.15 **/ RedirectMode sourceRedirectMode() const; /** * Sets the redirect mode for the source position. * * @param mode The new mode. * @since 5.15 **/ void setSourceRedirectMode(RedirectMode mode); /** * Returns the redirect mode for the target position. * * The redirect mode controls behavior of the timeline when its direction is * changed at the target position. * * In the strict mode, subsequent update calls won't have any effect on the * current value of the timeline. * * In the relaxed mode, the timeline will go in the new direction. * * The default is RedirectMode::Strict. * * @see sourceRedirectMode * @since 5.15 **/ RedirectMode targetRedirectMode() const; /** * Sets the redirect mode for the target position. * * @param mode The new mode. * @since 5.15 **/ void setTargetRedirectMode(RedirectMode mode); TimeLine &operator=(const TimeLine &other); private: qreal progress() const; private: class Data; QSharedDataPointer d; }; /** * Pointer to the global EffectsHandler object. **/ extern KWINEFFECTS_EXPORT EffectsHandler* effects; /*************************************************************** WindowVertex ***************************************************************/ inline WindowVertex::WindowVertex() : px(0), py(0), ox(0), oy(0), tx(0), ty(0) { } inline WindowVertex::WindowVertex(double _x, double _y, double _tx, double _ty) : px(_x), py(_y), ox(_x), oy(_y), tx(_tx), ty(_ty) { } inline WindowVertex::WindowVertex(const QPointF &position, const QPointF &texturePosition) : px(position.x()), py(position.y()), ox(position.x()), oy(position.y()), tx(texturePosition.x()), ty(texturePosition.y()) { } inline void WindowVertex::move(double x, double y) { px = x; py = y; } inline void WindowVertex::setX(double x) { px = x; } inline void WindowVertex::setY(double y) { py = y; } /*************************************************************** WindowQuad ***************************************************************/ inline WindowQuad::WindowQuad(WindowQuadType t, int id) : quadType(t) , uvSwapped(false) , quadID(id) { } inline WindowVertex& WindowQuad::operator[](int index) { assert(index >= 0 && index < 4); return verts[ index ]; } inline const WindowVertex& WindowQuad::operator[](int index) const { assert(index >= 0 && index < 4); return verts[ index ]; } inline WindowQuadType WindowQuad::type() const { assert(quadType != WindowQuadError); return quadType; } inline int WindowQuad::id() const { return quadID; } inline bool WindowQuad::decoration() const { assert(quadType != WindowQuadError); return quadType == WindowQuadDecoration; } inline bool WindowQuad::effect() const { assert(quadType != WindowQuadError); return quadType >= EFFECT_QUAD_TYPE_START; } inline bool WindowQuad::isTransformed() const { return !(verts[ 0 ].px == verts[ 0 ].ox && verts[ 0 ].py == verts[ 0 ].oy && verts[ 1 ].px == verts[ 1 ].ox && verts[ 1 ].py == verts[ 1 ].oy && verts[ 2 ].px == verts[ 2 ].ox && verts[ 2 ].py == verts[ 2 ].oy && verts[ 3 ].px == verts[ 3 ].ox && verts[ 3 ].py == verts[ 3 ].oy); } inline double WindowQuad::left() const { return qMin(verts[ 0 ].px, qMin(verts[ 1 ].px, qMin(verts[ 2 ].px, verts[ 3 ].px))); } inline double WindowQuad::right() const { return qMax(verts[ 0 ].px, qMax(verts[ 1 ].px, qMax(verts[ 2 ].px, verts[ 3 ].px))); } inline double WindowQuad::top() const { return qMin(verts[ 0 ].py, qMin(verts[ 1 ].py, qMin(verts[ 2 ].py, verts[ 3 ].py))); } inline double WindowQuad::bottom() const { return qMax(verts[ 0 ].py, qMax(verts[ 1 ].py, qMax(verts[ 2 ].py, verts[ 3 ].py))); } inline double WindowQuad::originalLeft() const { return verts[ 0 ].ox; } inline double WindowQuad::originalRight() const { return verts[ 2 ].ox; } inline double WindowQuad::originalTop() const { return verts[ 0 ].oy; } inline double WindowQuad::originalBottom() const { return verts[ 2 ].oy; } /*************************************************************** Motion ***************************************************************/ template Motion::Motion(T initial, double strength, double smoothness) : m_value(initial) , m_start(initial) , m_target(initial) , m_velocity() , m_strength(strength) , m_smoothness(smoothness) { } template Motion::Motion(const Motion &other) : m_value(other.value()) , m_start(other.target()) , m_target(other.target()) , m_velocity(other.velocity()) , m_strength(other.strength()) , m_smoothness(other.smoothness()) { } template Motion::~Motion() { } template void Motion::calculate(const int msec) { if (m_value == m_target && m_velocity == T()) // At target and not moving return; // Poor man's time independent calculation int steps = qMax(1, msec / 5); for (int i = 0; i < steps; i++) { T diff = m_target - m_value; T strength = diff * m_strength; m_velocity = (m_smoothness * m_velocity + strength) / (m_smoothness + 1.0); m_value += m_velocity; } } template void Motion::finish() { m_value = m_target; m_velocity = T(); } /*************************************************************** Effect ***************************************************************/ template int Effect::animationTime(int defaultDuration) { return animationTime(T::duration() != 0 ? T::duration() : defaultDuration); } template void Effect::initConfig() { T::instance(effects->config()); } } // namespace Q_DECLARE_METATYPE(KWin::EffectWindow*) Q_DECLARE_METATYPE(QList) Q_DECLARE_METATYPE(KWin::TimeLine) Q_DECLARE_METATYPE(KWin::TimeLine::Direction) /** @} */ #endif // KWINEFFECTS_H diff --git a/libkwineffects/kwinglutils.cpp b/libkwineffects/kwinglutils.cpp index 5dbcc54f0..b635ea388 100644 --- a/libkwineffects/kwinglutils.cpp +++ b/libkwineffects/kwinglutils.cpp @@ -1,2319 +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 -#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(); } 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); 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; assert(index >= 0 && index < VertexAttributeCount); 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/main.cpp b/main.cpp index 82061ef8d..943412e7c 100644 --- a/main.cpp +++ b/main.cpp @@ -1,469 +1,469 @@ /******************************************************************** 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 "main.h" #include // kwin #include "platform.h" #include "atoms.h" #include "composite.h" #include "cursor.h" #include "input.h" #include "logind.h" #include "options.h" #include "screens.h" #include "screenlockerwatcher.h" #include "sm.h" #include "workspace.h" #include "xcbutils.h" #include // KDE #include #include #include #include #include // Qt #include -#include +#include #include #include #include // system #ifdef HAVE_UNISTD_H #include #endif // HAVE_UNISTD_H #ifdef HAVE_MALLOC_H #include #endif // HAVE_MALLOC_H // xcb #include #ifndef XCB_GE_GENERIC #define XCB_GE_GENERIC 35 #endif Q_DECLARE_METATYPE(KSharedConfigPtr) namespace KWin { Options* options; Atoms* atoms; int screen_number = -1; bool is_multihead = false; int Application::crashes = 0; bool Application::isX11MultiHead() { return is_multihead; } void Application::setX11MultiHead(bool multiHead) { is_multihead = multiHead; } void Application::setX11ScreenNumber(int screenNumber) { screen_number = screenNumber; } int Application::x11ScreenNumber() { return screen_number; } Application::Application(Application::OperationMode mode, int &argc, char **argv) : QApplication(argc, argv) , m_eventFilter(new XcbEventFilter()) , m_configLock(false) , m_config() , m_kxkbConfig() , m_inputConfig() , m_operationMode(mode) { qRegisterMetaType("Options::WindowOperation"); qRegisterMetaType(); qRegisterMetaType("KWayland::Server::SurfaceInterface *"); qRegisterMetaType(); } void Application::setConfigLock(bool lock) { m_configLock = lock; } Application::OperationMode Application::operationMode() const { return m_operationMode; } void Application::setOperationMode(OperationMode mode) { m_operationMode = mode; } bool Application::shouldUseWaylandForCompositing() const { return m_operationMode == OperationModeWaylandOnly || m_operationMode == OperationModeXwayland; } void Application::start() { setQuitOnLastWindowClosed(false); if (!m_config) { m_config = KSharedConfig::openConfig(); } if (!m_config->isImmutable() && m_configLock) { // TODO: This shouldn't be necessary //config->setReadOnly( true ); m_config->reparseConfiguration(); } if (!m_kxkbConfig) { m_kxkbConfig = KSharedConfig::openConfig(QStringLiteral("kxkbrc"), KConfig::NoGlobals); } if (!m_inputConfig) { m_inputConfig = KSharedConfig::openConfig(QStringLiteral("kcminputrc"), KConfig::NoGlobals); } performStartup(); } Application::~Application() { delete options; destroyAtoms(); } void Application::destroyAtoms() { delete atoms; atoms = nullptr; } void Application::resetCrashesCount() { crashes = 0; } void Application::setCrashCount(int count) { crashes = count; } bool Application::wasCrash() { return crashes > 0; } static const char description[] = I18N_NOOP("KDE window manager"); void Application::createAboutData() { KAboutData aboutData(QStringLiteral(KWIN_NAME), // The program name used internally i18n("KWin"), // A displayable program name string QStringLiteral(KWIN_VERSION_STRING), // The program version string i18n(description), // Short description of what the app does KAboutLicense::GPL, // The license this code is released under i18n("(c) 1999-2018, The KDE Developers")); // Copyright Statement aboutData.addAuthor(i18n("Matthias Ettrich"), QString(), QStringLiteral("ettrich@kde.org")); aboutData.addAuthor(i18n("Cristian Tibirna"), QString(), QStringLiteral("tibirna@kde.org")); aboutData.addAuthor(i18n("Daniel M. Duley"), QString(), QStringLiteral("mosfet@kde.org")); aboutData.addAuthor(i18n("Luboš Luňák"), QString(), QStringLiteral("l.lunak@kde.org")); aboutData.addAuthor(i18n("Martin Flöser"), QString(), QStringLiteral("mgraesslin@kde.org")); KAboutData::setApplicationData(aboutData); } static const QString s_lockOption = QStringLiteral("lock"); static const QString s_crashesOption = QStringLiteral("crashes"); void Application::setupCommandLine(QCommandLineParser *parser) { QCommandLineOption lockOption(s_lockOption, i18n("Disable configuration options")); QCommandLineOption crashesOption(s_crashesOption, i18n("Indicate that KWin has recently crashed n times"), QStringLiteral("n")); parser->setApplicationDescription(i18n("KDE window manager")); parser->addVersionOption(); parser->addHelpOption(); parser->addOption(lockOption); parser->addOption(crashesOption); KAboutData::applicationData().setupCommandLine(parser); } void Application::processCommandLine(QCommandLineParser *parser) { KAboutData aboutData = KAboutData::applicationData(); aboutData.processCommandLine(parser); setConfigLock(parser->isSet(s_lockOption)); Application::setCrashCount(parser->value(s_crashesOption).toInt()); } void Application::setupTranslator() { QTranslator *qtTranslator = new QTranslator(qApp); qtTranslator->load("qt_" + QLocale::system().name(), QLibraryInfo::location(QLibraryInfo::TranslationsPath)); installTranslator(qtTranslator); } void Application::setupMalloc() { #ifdef M_TRIM_THRESHOLD // Prevent fragmentation of the heap by malloc (glibc). // // The default threshold is 128*1024, which can result in a large memory usage // due to fragmentation especially if we use the raster graphicssystem. On the // otherside if the threshold is too low, free() starts to permanently ask the kernel // about shrinking the heap. #ifdef HAVE_UNISTD_H const int pagesize = sysconf(_SC_PAGESIZE); #else const int pagesize = 4*1024; #endif // HAVE_UNISTD_H mallopt(M_TRIM_THRESHOLD, 5*pagesize); #endif // M_TRIM_THRESHOLD } void Application::setupLocalizedString() { KLocalizedString::setApplicationDomain("kwin"); } void Application::notifyKSplash() { // Tell KSplash that KWin has started QDBusMessage ksplashProgressMessage = QDBusMessage::createMethodCall(QStringLiteral("org.kde.KSplash"), QStringLiteral("/KSplash"), QStringLiteral("org.kde.KSplash"), QStringLiteral("setStage")); ksplashProgressMessage.setArguments(QList() << QStringLiteral("wm")); QDBusConnection::sessionBus().asyncCall(ksplashProgressMessage); } void Application::createWorkspace() { // we want all QQuickWindows with an alpha buffer, do here as Workspace might create QQuickWindows QQuickWindow::setDefaultAlphaBuffer(true); // This tries to detect compositing options and can use GLX. GLX problems // (X errors) shouldn't cause kwin to abort, so this is out of the // critical startup section where x errors cause kwin to abort. // create workspace. (void) new Workspace(m_originalSessionKey); emit workspaceCreated(); } void Application::createInput() { ScreenLockerWatcher::create(this); LogindIntegration::create(this); auto input = InputRedirection::create(this); input->init(); m_platform->createPlatformCursor(this); } void Application::createScreens() { if (Screens::self()) { return; } Screens::create(this); emit screensCreated(); } void Application::createAtoms() { atoms = new Atoms; } void Application::createOptions() { options = new Options; } void Application::createCompositor() { Compositor::create(this); } void Application::setupEventFilters() { installNativeEventFilter(m_eventFilter.data()); } void Application::destroyWorkspace() { delete Workspace::self(); } void Application::destroyCompositor() { delete Compositor::self(); } void Application::updateX11Time(xcb_generic_event_t *event) { xcb_timestamp_t time = XCB_TIME_CURRENT_TIME; const uint8_t eventType = event->response_type & ~0x80; switch(eventType) { case XCB_KEY_PRESS: case XCB_KEY_RELEASE: time = reinterpret_cast(event)->time; break; case XCB_BUTTON_PRESS: case XCB_BUTTON_RELEASE: time = reinterpret_cast(event)->time; break; case XCB_MOTION_NOTIFY: time = reinterpret_cast(event)->time; break; case XCB_ENTER_NOTIFY: case XCB_LEAVE_NOTIFY: time = reinterpret_cast(event)->time; break; case XCB_FOCUS_IN: case XCB_FOCUS_OUT: case XCB_KEYMAP_NOTIFY: case XCB_EXPOSE: case XCB_GRAPHICS_EXPOSURE: case XCB_NO_EXPOSURE: case XCB_VISIBILITY_NOTIFY: case XCB_CREATE_NOTIFY: case XCB_DESTROY_NOTIFY: case XCB_UNMAP_NOTIFY: case XCB_MAP_NOTIFY: case XCB_MAP_REQUEST: case XCB_REPARENT_NOTIFY: case XCB_CONFIGURE_NOTIFY: case XCB_CONFIGURE_REQUEST: case XCB_GRAVITY_NOTIFY: case XCB_RESIZE_REQUEST: case XCB_CIRCULATE_NOTIFY: case XCB_CIRCULATE_REQUEST: // no timestamp return; case XCB_PROPERTY_NOTIFY: time = reinterpret_cast(event)->time; break; case XCB_SELECTION_CLEAR: time = reinterpret_cast(event)->time; break; case XCB_SELECTION_REQUEST: time = reinterpret_cast(event)->time; break; case XCB_SELECTION_NOTIFY: time = reinterpret_cast(event)->time; break; case XCB_COLORMAP_NOTIFY: case XCB_CLIENT_MESSAGE: case XCB_MAPPING_NOTIFY: case XCB_GE_GENERIC: // no timestamp return; default: // extension handling if (Xcb::Extensions::self()) { if (eventType == Xcb::Extensions::self()->shapeNotifyEvent()) { time = reinterpret_cast(event)->server_time; } if (eventType == Xcb::Extensions::self()->damageNotifyEvent()) { time = reinterpret_cast(event)->timestamp; } } break; } setX11Time(time); } bool XcbEventFilter::nativeEventFilter(const QByteArray &eventType, void *message, long int *result) { Q_UNUSED(result) if (eventType != "xcb_generic_event_t") { return false; } auto event = static_cast(message); kwinApp()->updateX11Time(event); if (!Workspace::self()) { // Workspace not yet created return false; } return Workspace::self()->workspaceEvent(event); } static bool s_useLibinput = false; void Application::setUseLibinput(bool use) { s_useLibinput = use; } bool Application::usesLibinput() { return s_useLibinput; } QProcessEnvironment Application::processStartupEnvironment() const { return QProcessEnvironment::systemEnvironment(); } void Application::initPlatform(const KPluginMetaData &plugin) { Q_ASSERT(!m_platform); m_platform = qobject_cast(plugin.instantiate()); if (m_platform) { m_platform->setParent(this); // check whether it needs libinput const QJsonObject &metaData = plugin.rawData(); auto it = metaData.find(QStringLiteral("input")); if (it != metaData.end()) { if ((*it).isBool()) { if (!(*it).toBool()) { qCDebug(KWIN_CORE) << "Platform does not support input, enforcing libinput support"; setUseLibinput(true); } } } } } ApplicationWaylandAbstract::ApplicationWaylandAbstract(OperationMode mode, int &argc, char **argv) : Application(mode, argc, argv) { } ApplicationWaylandAbstract::~ApplicationWaylandAbstract() { } } // namespace diff --git a/placement.cpp b/placement.cpp index 4decbaa61..220f5aae3 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" -#include -#include - -#include - #ifndef KCMRULES #include "workspace.h" #include "client.h" #include "cursor.h" #include "options.h" #include "rules.h" #include "screens.h" #endif +#include +#include + +#include + namespace KWin { #ifndef KCMRULES KWIN_SINGLETON_FACTORY(Placement) Placement::Placement(QObject*) { reinitCascading(0); } Placement::~Placement() { s_self = NULL; } /** * Places the client \a c according to the workspace's layout policy **/ void Placement::place(AbstractClient* c, 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, 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*/) { const int step = 24; static int px = step; static int py = 2 * step; int tx, ty; const QRect maxRect = checkArea(c, area); if (px < maxRect.x()) px = maxRect.x(); if (py < maxRect.y()) py = maxRect.y(); px += step; py += 2 * step; if (px > maxRect.width() / 2) px = maxRect.x() + step; if (py > maxRect.height() / 2) py = maxRect.y() + step; tx = px; ty = py; if (tx + c->width() > maxRect.right()) { tx = maxRect.right() - c->width(); if (tx < 0) tx = 0; px = maxRect.x(); } if (ty + c->height() > maxRect.bottom()) { ty = maxRect.bottom() - c->height(); if (ty < 0) ty = 0; py = maxRect.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->isCurrentTab()) 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*/) { /* * 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 const QRect maxRect = checkArea(c, area); int x = maxRect.left(), y = maxRect.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 > maxRect.bottom() && ch < maxRect.height()) overlap = h_wrong; // this throws the algorithm to an exit else if (x + cw > maxRect.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 = maxRect.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 = maxRect.left(); possible = maxRect.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 < maxRect.bottom())); if (ch >= maxRect.height()) y_optimal = maxRect.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, QRect& area, Policy nextPlacement) { 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); // get the maximum allowed windows space and desk's origin QRect maxRect = checkArea(c, area); // 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 = maxRect.left(); const int Y = maxRect.top(); const int H = maxRect.height(); const int W = maxRect.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*/) { // get the maximum allowed windows space and desk's origin const QRect maxRect = checkArea(c, area); const int xp = maxRect.left() + (maxRect.width() - c->width()) / 2; const int yp = maxRect.top() + (maxRect.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*/) { // get the maximum allowed windows space and desk's origin c->move(checkArea(c, area).topLeft()); } void Placement::placeUtility(AbstractClient* c, 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, QRect& area) { // 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, QRect& area, Policy nextPlacement) { placeOnMainWindow(c, area, nextPlacement); } void Placement::placeUnderMouse(AbstractClient* c, QRect& area, Policy /*next*/) { area = checkArea(c, area); 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, QRect& area, Policy nextPlacement) { if (nextPlacement == Unknown) nextPlacement = Centered; if (nextPlacement == Maximizing) // maximize if needed placeMaximizing(c, area, NoPlacement); area = checkArea(c, area); 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) 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) { // '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 area = checkArea(c, QRect()); c->keepInArea(area); // make sure it's kept inside workarea } void Placement::placeMaximizing(AbstractClient* c, QRect& area, Policy nextPlacement) { 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() { // TODO XINERAMA this probably is not right for xinerama Workspace *ws = Workspace::self(); const int desktop = VirtualDesktopManager::self()->current(); reinitCascading(desktop); // TODO: make area const once placeFoo methods are fixed to take a const QRect& QRect area = ws->clientArea(PlacementArea, QPoint(0, 0), desktop); foreach (Toplevel *toplevel, ws->stackingOrder()) { auto client = qobject_cast(toplevel); if (!client || (!client->isOnCurrentDesktop()) || (client->isMinimized()) || (client->isOnAllDesktops()) || (!client->isMovable())) continue; placeCascaded(client, area); } } 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; placeSmart(client, QRect()); } } QRect Placement::checkArea(const AbstractClient* c, const QRect& area) { if (area.isNull()) return workspace()->clientArea(PlacementArea, c->geometry().center(), c->desktop()); return area; } #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" }; 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/plugins/platforms/drm/drm_buffer.cpp b/plugins/platforms/drm/drm_buffer.cpp index b640c593c..a5fc83976 100644 --- a/plugins/platforms/drm/drm_buffer.cpp +++ b/plugins/platforms/drm/drm_buffer.cpp @@ -1,107 +1,108 @@ /******************************************************************** 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 "drm_buffer.h" #include "logging.h" // system #include -#include +// c++ +#include // drm #include #include namespace KWin { DrmBuffer:: DrmBuffer(int fd) : m_fd(fd) { } // DrmDumbBuffer DrmDumbBuffer::DrmDumbBuffer(int fd, const QSize &size) : DrmBuffer(fd) { m_size = size; drm_mode_create_dumb createArgs; memset(&createArgs, 0, sizeof createArgs); createArgs.bpp = 32; createArgs.width = size.width(); createArgs.height = size.height(); if (drmIoctl(fd, DRM_IOCTL_MODE_CREATE_DUMB, &createArgs) != 0) { qCWarning(KWIN_DRM) << "DRM_IOCTL_MODE_CREATE_DUMB failed"; return; } m_handle = createArgs.handle; m_bufferSize = createArgs.size; m_stride = createArgs.pitch; if (drmModeAddFB(fd, size.width(), size.height(), 24, 32, m_stride, createArgs.handle, &m_bufferId) != 0) { qCWarning(KWIN_DRM) << "drmModeAddFB failed with errno" << errno; } } DrmDumbBuffer::~DrmDumbBuffer() { if (m_bufferId) { drmModeRmFB(fd(), m_bufferId); } delete m_image; if (m_memory) { munmap(m_memory, m_bufferSize); } if (m_handle) { drm_mode_destroy_dumb destroyArgs; destroyArgs.handle = m_handle; drmIoctl(fd(), DRM_IOCTL_MODE_DESTROY_DUMB, &destroyArgs); } } bool DrmDumbBuffer::needsModeChange(DrmBuffer *b) const { if (DrmDumbBuffer *db = dynamic_cast(b)) { return m_stride != db->stride(); } else { return true; } } bool DrmDumbBuffer::map(QImage::Format format) { if (!m_handle || !m_bufferId) { return false; } drm_mode_map_dumb mapArgs; memset(&mapArgs, 0, sizeof mapArgs); mapArgs.handle = m_handle; if (drmIoctl(fd(), DRM_IOCTL_MODE_MAP_DUMB, &mapArgs) != 0) { return false; } void *address = mmap(nullptr, m_bufferSize, PROT_WRITE, MAP_SHARED, fd(), mapArgs.offset); if (address == MAP_FAILED) { return false; } m_memory = address; m_image = new QImage((uchar*)m_memory, m_size.width(), m_size.height(), m_stride, format); return !m_image->isNull(); } } diff --git a/plugins/platforms/drm/drm_buffer_gbm.cpp b/plugins/platforms/drm/drm_buffer_gbm.cpp index cbc0d3abd..136fbb828 100644 --- a/plugins/platforms/drm/drm_buffer_gbm.cpp +++ b/plugins/platforms/drm/drm_buffer_gbm.cpp @@ -1,68 +1,69 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright 2017 Roman Gilg Copyright 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 "drm_buffer_gbm.h" #include "gbm_surface.h" #include "logging.h" // system #include -#include +// c++ +#include // drm #include #include #include namespace KWin { // DrmSurfaceBuffer DrmSurfaceBuffer::DrmSurfaceBuffer(int fd, const std::shared_ptr &surface) : DrmBuffer(fd) , m_surface(surface) { m_bo = m_surface->lockFrontBuffer(); if (!m_bo) { qCWarning(KWIN_DRM) << "Locking front buffer failed"; return; } m_size = QSize(gbm_bo_get_width(m_bo), gbm_bo_get_height(m_bo)); if (drmModeAddFB(fd, m_size.width(), m_size.height(), 24, 32, gbm_bo_get_stride(m_bo), gbm_bo_get_handle(m_bo).u32, &m_bufferId) != 0) { qCWarning(KWIN_DRM) << "drmModeAddFB failed"; } gbm_bo_set_user_data(m_bo, this, nullptr); } DrmSurfaceBuffer::~DrmSurfaceBuffer() { if (m_bufferId) { drmModeRmFB(fd(), m_bufferId); } releaseGbm(); } void DrmSurfaceBuffer::releaseGbm() { m_surface->releaseBuffer(m_bo); m_bo = nullptr; } } diff --git a/plugins/platforms/drm/drm_output.cpp b/plugins/platforms/drm/drm_output.cpp index 7c88628bb..c609aae27 100644 --- a/plugins/platforms/drm/drm_output.cpp +++ b/plugins/platforms/drm/drm_output.cpp @@ -1,1077 +1,1077 @@ /******************************************************************** 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 "drm_output.h" #include "drm_backend.h" #include "drm_object_plane.h" #include "drm_object_crtc.h" #include "drm_object_connector.h" -#include - #include "composite.h" #include "logind.h" #include "logging.h" #include "main.h" #include "orientation_sensor.h" #include "screens_drm.h" #include "wayland_server.h" // KWayland #include // KF5 #include #include #include // Qt #include #include #include +// c++ +#include // drm #include #include #include namespace KWin { DrmOutput::DrmOutput(DrmBackend *backend) : AbstractWaylandOutput(backend) , m_backend(backend) { } DrmOutput::~DrmOutput() { Q_ASSERT(!m_pageFlipPending); if (!m_deleted) { teardown(); } } void DrmOutput::teardown() { m_deleted = true; hideCursor(); m_crtc->blank(); if (m_primaryPlane) { // TODO: when having multiple planes, also clean up these m_primaryPlane->setOutput(nullptr); if (m_backend->deleteBufferAfterPageFlip()) { delete m_primaryPlane->current(); } m_primaryPlane->setCurrent(nullptr); } m_crtc->setOutput(nullptr); m_conn->setOutput(nullptr); delete m_cursor[0]; delete m_cursor[1]; if (!m_pageFlipPending) { deleteLater(); } //else will be deleted in the page flip handler //this is needed so that the pageflipcallback handle isn't deleted } void DrmOutput::releaseGbm() { if (DrmBuffer *b = m_crtc->current()) { b->releaseGbm(); } if (m_primaryPlane && m_primaryPlane->current()) { m_primaryPlane->current()->releaseGbm(); } } bool DrmOutput::hideCursor() { return drmModeSetCursor(m_backend->fd(), m_crtc->id(), 0, 0, 0) == 0; } bool DrmOutput::showCursor(DrmDumbBuffer *c) { const QSize &s = c->size(); return drmModeSetCursor(m_backend->fd(), m_crtc->id(), c->handle(), s.width(), s.height()) == 0; } bool DrmOutput::showCursor() { const bool ret = showCursor(m_cursor[m_cursorIndex]); if (!ret) { return ret; } if (m_hasNewCursor) { m_cursorIndex = (m_cursorIndex + 1) % 2; m_hasNewCursor = false; } return ret; } int orientationToRotation(Qt::ScreenOrientation orientation) { switch (orientation) { case Qt::PrimaryOrientation: case Qt::LandscapeOrientation: return 0; case Qt::InvertedPortraitOrientation: return 90; case Qt::InvertedLandscapeOrientation: return 180; case Qt::PortraitOrientation: return 270; } Q_UNREACHABLE(); return 0; } QMatrix4x4 DrmOutput::matrixDisplay(const QSize &s) const { QMatrix4x4 matrix; const int angle = orientationToRotation(orientation()); if (angle) { const QSize center = s / 2; matrix.translate(center.width(), center.height()); matrix.rotate(angle, 0, 0, 1); matrix.translate(-center.width(), -center.height()); } matrix.scale(scale()); return matrix; } void DrmOutput::updateCursor() { QImage cursorImage = m_backend->softwareCursor(); if (cursorImage.isNull()) { return; } m_hasNewCursor = true; QImage *c = m_cursor[m_cursorIndex]->image(); c->fill(Qt::transparent); QPainter p; p.begin(c); p.setWorldTransform(matrixDisplay(QSize(cursorImage.width(), cursorImage.height())).toTransform()); p.drawImage(QPoint(0, 0), cursorImage); p.end(); } void DrmOutput::moveCursor(const QPoint &globalPos) { const QMatrix4x4 hotspotMatrix = matrixDisplay(m_backend->softwareCursor().size()); QPoint p = globalPos - AbstractWaylandOutput::globalPos(); switch (orientation()) { case Qt::PrimaryOrientation: case Qt::LandscapeOrientation: break; case Qt::PortraitOrientation: p = QPoint(p.y(), pixelSize().height() - p.x()); break; case Qt::InvertedPortraitOrientation: p = QPoint(pixelSize().width() - p.y(), p.x()); break; case Qt::InvertedLandscapeOrientation: p = QPoint(pixelSize().width() - p.x(), pixelSize().height() - p.y()); break; } p *= scale(); p -= hotspotMatrix.map(m_backend->softwareCursorHotspot()); drmModeMoveCursor(m_backend->fd(), m_crtc->id(), p.x(), p.y()); } static QHash s_connectorNames = { {DRM_MODE_CONNECTOR_Unknown, QByteArrayLiteral("Unknown")}, {DRM_MODE_CONNECTOR_VGA, QByteArrayLiteral("VGA")}, {DRM_MODE_CONNECTOR_DVII, QByteArrayLiteral("DVI-I")}, {DRM_MODE_CONNECTOR_DVID, QByteArrayLiteral("DVI-D")}, {DRM_MODE_CONNECTOR_DVIA, QByteArrayLiteral("DVI-A")}, {DRM_MODE_CONNECTOR_Composite, QByteArrayLiteral("Composite")}, {DRM_MODE_CONNECTOR_SVIDEO, QByteArrayLiteral("SVIDEO")}, {DRM_MODE_CONNECTOR_LVDS, QByteArrayLiteral("LVDS")}, {DRM_MODE_CONNECTOR_Component, QByteArrayLiteral("Component")}, {DRM_MODE_CONNECTOR_9PinDIN, QByteArrayLiteral("DIN")}, {DRM_MODE_CONNECTOR_DisplayPort, QByteArrayLiteral("DP")}, {DRM_MODE_CONNECTOR_HDMIA, QByteArrayLiteral("HDMI-A")}, {DRM_MODE_CONNECTOR_HDMIB, QByteArrayLiteral("HDMI-B")}, {DRM_MODE_CONNECTOR_TV, QByteArrayLiteral("TV")}, {DRM_MODE_CONNECTOR_eDP, QByteArrayLiteral("eDP")}, {DRM_MODE_CONNECTOR_VIRTUAL, QByteArrayLiteral("Virtual")}, {DRM_MODE_CONNECTOR_DSI, QByteArrayLiteral("DSI")}, #ifdef DRM_MODE_CONNECTOR_DPI {DRM_MODE_CONNECTOR_DPI, QByteArrayLiteral("DPI")}, #endif }; namespace { quint64 refreshRateForMode(_drmModeModeInfo *m) { // Calculate higher precision (mHz) refresh rate // logic based on Weston, see compositor-drm.c quint64 refreshRate = (m->clock * 1000000LL / m->htotal + m->vtotal / 2) / m->vtotal; if (m->flags & DRM_MODE_FLAG_INTERLACE) { refreshRate *= 2; } if (m->flags & DRM_MODE_FLAG_DBLSCAN) { refreshRate /= 2; } if (m->vscan > 1) { refreshRate /= m->vscan; } return refreshRate; } } bool DrmOutput::init(drmModeConnector *connector) { initEdid(connector); initDpms(connector); initUuid(); if (m_backend->atomicModeSetting()) { if (!initPrimaryPlane()) { return false; } } else if (!m_crtc->blank()) { return false; } setInternal(connector->connector_type == DRM_MODE_CONNECTOR_LVDS || connector->connector_type == DRM_MODE_CONNECTOR_eDP); setDpmsSupported(true); if (isInternal()) { connect(kwinApp(), &Application::screensCreated, this, [this] { connect(screens()->orientationSensor(), &OrientationSensor::orientationChanged, this, &DrmOutput::automaticRotation); } ); } QSize physicalSize = !m_edid.physicalSize().isEmpty() ? m_edid.physicalSize() : QSize(connector->mmWidth, connector->mmHeight); // the size might be completely borked. E.g. Samsung SyncMaster 2494HS reports 160x90 while in truth it's 520x292 // as this information is used to calculate DPI info, it's going to result in everything being huge const QByteArray unknown = QByteArrayLiteral("unknown"); KConfigGroup group = kwinApp()->config()->group("EdidOverwrite").group(m_edid.eisaId().isEmpty() ? unknown : m_edid.eisaId()) .group(m_edid.monitorName().isEmpty() ? unknown : m_edid.monitorName()) .group(m_edid.serialNumber().isEmpty() ? unknown : m_edid.serialNumber()); if (group.hasKey("PhysicalSize")) { const QSize overwriteSize = group.readEntry("PhysicalSize", physicalSize); qCWarning(KWIN_DRM) << "Overwriting monitor physical size for" << m_edid.eisaId() << "/" << m_edid.monitorName() << "/" << m_edid.serialNumber() << " from " << physicalSize << "to " << overwriteSize; physicalSize = overwriteSize; } setRawPhysicalSize(physicalSize); initOutputDevice(connector); setEnabled(true); return true; } void DrmOutput::initUuid() { QCryptographicHash hash(QCryptographicHash::Md5); hash.addData(QByteArray::number(m_conn->id())); hash.addData(m_edid.eisaId()); hash.addData(m_edid.monitorName()); hash.addData(m_edid.serialNumber()); m_uuid = hash.result().toHex().left(10); } void DrmOutput::initOutputDevice(drmModeConnector *connector) { QString manufacturer; if (!m_edid.eisaId().isEmpty()) { manufacturer = QString::fromLatin1(m_edid.eisaId()); } QString connectorName = s_connectorNames.value(connector->connector_type, QByteArrayLiteral("Unknown")); QString modelName; if (!m_edid.monitorName().isEmpty()) { QString m = QString::fromLatin1(m_edid.monitorName()); if (!m_edid.serialNumber().isEmpty()) { m.append('/'); m.append(QString::fromLatin1(m_edid.serialNumber())); } modelName = m; } else if (!m_edid.serialNumber().isEmpty()) { modelName = QString::fromLatin1(m_edid.serialNumber()); } else { modelName = i18n("unknown"); } const QString model = connectorName + QStringLiteral("-") + QString::number(connector->connector_type_id) + QStringLiteral("-") + modelName; // read in mode information QVector modes; for (int i = 0; i < connector->count_modes; ++i) { // TODO: in AMS here we could read and store for later every mode's blob_id // would simplify isCurrentMode(..) and presentAtomically(..) in case of mode set auto *m = &connector->modes[i]; KWayland::Server::OutputDeviceInterface::ModeFlags deviceflags; if (isCurrentMode(m)) { deviceflags |= KWayland::Server::OutputDeviceInterface::ModeFlag::Current; } if (m->type & DRM_MODE_TYPE_PREFERRED) { deviceflags |= KWayland::Server::OutputDeviceInterface::ModeFlag::Preferred; } KWayland::Server::OutputDeviceInterface::Mode mode; mode.id = i; mode.size = QSize(m->hdisplay, m->vdisplay); mode.flags = deviceflags; mode.refreshRate = refreshRateForMode(m); modes << mode; } AbstractWaylandOutput::initWaylandOutputDevice(model, manufacturer, m_uuid, modes); } bool DrmOutput::isCurrentMode(const drmModeModeInfo *mode) const { return mode->clock == m_mode.clock && mode->hdisplay == m_mode.hdisplay && mode->hsync_start == m_mode.hsync_start && mode->hsync_end == m_mode.hsync_end && mode->htotal == m_mode.htotal && mode->hskew == m_mode.hskew && mode->vdisplay == m_mode.vdisplay && mode->vsync_start == m_mode.vsync_start && mode->vsync_end == m_mode.vsync_end && mode->vtotal == m_mode.vtotal && mode->vscan == m_mode.vscan && mode->vrefresh == m_mode.vrefresh && mode->flags == m_mode.flags && mode->type == m_mode.type && qstrcmp(mode->name, m_mode.name) == 0; } void DrmOutput::initEdid(drmModeConnector *connector) { DrmScopedPointer edid; for (int i = 0; i < connector->count_props; ++i) { DrmScopedPointer property(drmModeGetProperty(m_backend->fd(), connector->props[i])); if (!property) { continue; } if ((property->flags & DRM_MODE_PROP_BLOB) && qstrcmp(property->name, "EDID") == 0) { edid.reset(drmModeGetPropertyBlob(m_backend->fd(), connector->prop_values[i])); } } if (!edid) { return; } m_edid = Edid(edid->data, edid->length); if (!m_edid.isValid()) { qCWarning(KWIN_DRM, "Couldn't parse EDID for connector with id %d", m_conn->id()); } } bool DrmOutput::initPrimaryPlane() { for (int i = 0; i < m_backend->planes().size(); ++i) { DrmPlane* p = m_backend->planes()[i]; if (!p) { continue; } if (p->type() != DrmPlane::TypeIndex::Primary) { continue; } if (p->output()) { // Plane already has an output continue; } if (m_primaryPlane) { // Output already has a primary plane continue; } if (!p->isCrtcSupported(m_crtc->resIndex())) { continue; } p->setOutput(this); m_primaryPlane = p; qCDebug(KWIN_DRM) << "Initialized primary plane" << p->id() << "on CRTC" << m_crtc->id(); return true; } qCCritical(KWIN_DRM) << "Failed to initialize primary plane."; return false; } bool DrmOutput::initCursorPlane() // TODO: Add call in init (but needs layer support in general first) { for (int i = 0; i < m_backend->planes().size(); ++i) { DrmPlane* p = m_backend->planes()[i]; if (!p) { continue; } if (p->type() != DrmPlane::TypeIndex::Cursor) { continue; } if (p->output()) { // Plane already has an output continue; } if (m_cursorPlane) { // Output already has a cursor plane continue; } if (!p->isCrtcSupported(m_crtc->resIndex())) { continue; } p->setOutput(this); m_cursorPlane = p; qCDebug(KWIN_DRM) << "Initialized cursor plane" << p->id() << "on CRTC" << m_crtc->id(); return true; } return false; } bool DrmOutput::initCursor(const QSize &cursorSize) { auto createCursor = [this, cursorSize] (int index) { m_cursor[index] = m_backend->createBuffer(cursorSize); if (!m_cursor[index]->map(QImage::Format_ARGB32_Premultiplied)) { return false; } return true; }; if (!createCursor(0) || !createCursor(1)) { return false; } return true; } void DrmOutput::initDpms(drmModeConnector *connector) { for (int i = 0; i < connector->count_props; ++i) { DrmScopedPointer property(drmModeGetProperty(m_backend->fd(), connector->props[i])); if (!property) { continue; } if (qstrcmp(property->name, "DPMS") == 0) { m_dpms.swap(property); break; } } } static DrmOutput::DpmsMode fromWaylandDpmsMode(KWayland::Server::OutputInterface::DpmsMode wlMode) { using namespace KWayland::Server; switch (wlMode) { case OutputInterface::DpmsMode::On: return DrmOutput::DpmsMode::On; case OutputInterface::DpmsMode::Standby: return DrmOutput::DpmsMode::Standby; case OutputInterface::DpmsMode::Suspend: return DrmOutput::DpmsMode::Suspend; case OutputInterface::DpmsMode::Off: return DrmOutput::DpmsMode::Off; default: Q_UNREACHABLE(); } } static KWayland::Server::OutputInterface::DpmsMode toWaylandDpmsMode(DrmOutput::DpmsMode mode) { using namespace KWayland::Server; switch (mode) { case DrmOutput::DpmsMode::On: return OutputInterface::DpmsMode::On; case DrmOutput::DpmsMode::Standby: return OutputInterface::DpmsMode::Standby; case DrmOutput::DpmsMode::Suspend: return OutputInterface::DpmsMode::Suspend; case DrmOutput::DpmsMode::Off: return OutputInterface::DpmsMode::Off; default: Q_UNREACHABLE(); } } void DrmOutput::updateDpms(KWayland::Server::OutputInterface::DpmsMode mode) { if (m_dpms.isNull()) { return; } const auto drmMode = fromWaylandDpmsMode(mode); if (drmMode == m_dpmsModePending) { qCDebug(KWIN_DRM) << "New DPMS mode equals old mode. DPMS unchanged."; return; } m_dpmsModePending = drmMode; if (m_backend->atomicModeSetting()) { m_modesetRequested = true; if (drmMode == DpmsMode::On) { if (m_pageFlipPending) { m_pageFlipPending = false; Compositor::self()->bufferSwapComplete(); } dpmsOnHandler(); } else { m_dpmsAtomicOffPending = true; if (!m_pageFlipPending) { dpmsAtomicOff(); } } } else { if (drmModeConnectorSetProperty(m_backend->fd(), m_conn->id(), m_dpms->prop_id, uint64_t(drmMode)) < 0) { m_dpmsModePending = m_dpmsMode; qCWarning(KWIN_DRM) << "Setting DPMS failed"; return; } if (drmMode == DpmsMode::On) { dpmsOnHandler(); } else { dpmsOffHandler(); } m_dpmsMode = m_dpmsModePending; } } void DrmOutput::dpmsOnHandler() { qCDebug(KWIN_DRM) << "DPMS mode set for output" << m_crtc->id() << "to On."; auto wlOutput = waylandOutput(); if (wlOutput) { wlOutput->setDpmsMode(toWaylandDpmsMode(m_dpmsModePending)); } emit dpmsChanged(); m_backend->checkOutputsAreOn(); if (!m_backend->atomicModeSetting()) { m_crtc->blank(); } if (Compositor *compositor = Compositor::self()) { compositor->addRepaintFull(); } } void DrmOutput::dpmsOffHandler() { qCDebug(KWIN_DRM) << "DPMS mode set for output" << m_crtc->id() << "to Off."; auto wlOutput = waylandOutput(); if (wlOutput) { wlOutput->setDpmsMode(toWaylandDpmsMode(m_dpmsModePending)); } emit dpmsChanged(); m_backend->outputWentOff(); } void DrmOutput::transform(KWayland::Server::OutputDeviceInterface::Transform transform) { waylandOutputDevice()->setTransform(transform); using KWayland::Server::OutputDeviceInterface; using KWayland::Server::OutputInterface; auto wlOutput = waylandOutput(); switch (transform) { case OutputDeviceInterface::Transform::Normal: if (m_primaryPlane) { m_primaryPlane->setTransformation(DrmPlane::Transformation::Rotate0); } if (wlOutput) { wlOutput->setTransform(OutputInterface::Transform::Normal); } setOrientation(Qt::PrimaryOrientation); break; case OutputDeviceInterface::Transform::Rotated90: if (m_primaryPlane) { m_primaryPlane->setTransformation(DrmPlane::Transformation::Rotate90); } if (wlOutput) { wlOutput->setTransform(OutputInterface::Transform::Rotated90); } setOrientation(Qt::PortraitOrientation); break; case OutputDeviceInterface::Transform::Rotated180: if (m_primaryPlane) { m_primaryPlane->setTransformation(DrmPlane::Transformation::Rotate180); } if (wlOutput) { wlOutput->setTransform(OutputInterface::Transform::Rotated180); } setOrientation(Qt::InvertedLandscapeOrientation); break; case OutputDeviceInterface::Transform::Rotated270: if (m_primaryPlane) { m_primaryPlane->setTransformation(DrmPlane::Transformation::Rotate270); } if (wlOutput) { wlOutput->setTransform(OutputInterface::Transform::Rotated270); } setOrientation(Qt::InvertedPortraitOrientation); break; case OutputDeviceInterface::Transform::Flipped: // TODO: what is this exactly? if (wlOutput) { wlOutput->setTransform(OutputInterface::Transform::Flipped); } break; case OutputDeviceInterface::Transform::Flipped90: // TODO: what is this exactly? if (wlOutput) { wlOutput->setTransform(OutputInterface::Transform::Flipped90); } break; case OutputDeviceInterface::Transform::Flipped180: // TODO: what is this exactly? if (wlOutput) { wlOutput->setTransform(OutputInterface::Transform::Flipped180); } break; case OutputDeviceInterface::Transform::Flipped270: // TODO: what is this exactly? if (wlOutput) { wlOutput->setTransform(OutputInterface::Transform::Flipped270); } break; } m_modesetRequested = true; // the cursor might need to get rotated updateCursor(); showCursor(); // TODO: are these calls not enough in updateMode already? setWaylandMode(); } void DrmOutput::updateMode(int modeIndex) { // get all modes on the connector DrmScopedPointer connector(drmModeGetConnector(m_backend->fd(), m_conn->id())); if (connector->count_modes <= modeIndex) { // TODO: error? return; } if (isCurrentMode(&connector->modes[modeIndex])) { // nothing to do return; } m_mode = connector->modes[modeIndex]; m_modesetRequested = true; setWaylandMode(); } QSize DrmOutput::pixelSize() const { return orientateSize(QSize(m_mode.hdisplay, m_mode.vdisplay)); } void DrmOutput::setWaylandMode() { AbstractWaylandOutput::setWaylandMode(QSize(m_mode.hdisplay, m_mode.vdisplay), refreshRateForMode(&m_mode)); } void DrmOutput::pageFlipped() { m_pageFlipPending = false; if (m_deleted) { deleteLater(); return; } if (!m_crtc) { return; } // Egl based surface buffers get destroyed, QPainter based dumb buffers not // TODO: split up DrmOutput in two for dumb and egl/gbm surface buffer compatible subclasses completely? if (m_backend->deleteBufferAfterPageFlip()) { if (m_backend->atomicModeSetting()) { if (!m_primaryPlane->next()) { // on manual vt switch // TODO: when we later use overlay planes it might happen, that we have a page flip with only // damage on one of these, and therefore the primary plane has no next buffer // -> Then we don't want to return here! if (m_primaryPlane->current()) { m_primaryPlane->current()->releaseGbm(); } return; } for (DrmPlane *p : m_nextPlanesFlipList) { p->flipBufferWithDelete(); } m_nextPlanesFlipList.clear(); } else { if (!m_crtc->next()) { // on manual vt switch if (DrmBuffer *b = m_crtc->current()) { b->releaseGbm(); } } m_crtc->flipBuffer(); } } else { if (m_backend->atomicModeSetting()){ for (DrmPlane *p : m_nextPlanesFlipList) { p->flipBuffer(); } m_nextPlanesFlipList.clear(); } else { m_crtc->flipBuffer(); } m_crtc->flipBuffer(); } } bool DrmOutput::present(DrmBuffer *buffer) { if (m_backend->atomicModeSetting()) { return presentAtomically(buffer); } else { return presentLegacy(buffer); } } bool DrmOutput::dpmsAtomicOff() { m_dpmsAtomicOffPending = false; // TODO: With multiple planes: deactivate all of them here delete m_primaryPlane->next(); m_primaryPlane->setNext(nullptr); m_nextPlanesFlipList << m_primaryPlane; if (!doAtomicCommit(AtomicCommitMode::Test)) { qCDebug(KWIN_DRM) << "Atomic test commit to Dpms Off failed. Aborting."; return false; } if (!doAtomicCommit(AtomicCommitMode::Real)) { qCDebug(KWIN_DRM) << "Atomic commit to Dpms Off failed. This should have never happened! Aborting."; return false; } m_nextPlanesFlipList.clear(); dpmsOffHandler(); return true; } bool DrmOutput::presentAtomically(DrmBuffer *buffer) { if (!LogindIntegration::self()->isActiveSession()) { qCWarning(KWIN_DRM) << "Logind session not active."; return false; } if (m_pageFlipPending) { qCWarning(KWIN_DRM) << "Page not yet flipped."; return false; } #if HAVE_EGL_STREAMS if (m_backend->useEglStreams() && !m_modesetRequested) { // EglStreamBackend queues normal page flips through EGL, // modesets are still performed through DRM-KMS m_pageFlipPending = true; return true; } #endif m_primaryPlane->setNext(buffer); m_nextPlanesFlipList << m_primaryPlane; if (!doAtomicCommit(AtomicCommitMode::Test)) { //TODO: When we use planes for layered rendering, fallback to renderer instead. Also for direct scanout? //TODO: Probably should undo setNext and reset the flip list qCDebug(KWIN_DRM) << "Atomic test commit failed. Aborting present."; // go back to previous state if (m_lastWorkingState.valid) { m_mode = m_lastWorkingState.mode; setOrientation(m_lastWorkingState.orientation); setGlobalPos(m_lastWorkingState.globalPos); if (m_primaryPlane) { m_primaryPlane->setTransformation(m_lastWorkingState.planeTransformations); } m_modesetRequested = true; // the cursor might need to get rotated updateCursor(); showCursor(); // TODO: forward to OutputInterface and OutputDeviceInterface setWaylandMode(); emit screens()->changed(); } return false; } const bool wasModeset = m_modesetRequested; if (!doAtomicCommit(AtomicCommitMode::Real)) { qCDebug(KWIN_DRM) << "Atomic commit failed. This should have never happened! Aborting present."; //TODO: Probably should undo setNext and reset the flip list return false; } if (wasModeset) { // store current mode set as new good state m_lastWorkingState.mode = m_mode; m_lastWorkingState.orientation = orientation(); m_lastWorkingState.globalPos = globalPos(); if (m_primaryPlane) { m_lastWorkingState.planeTransformations = m_primaryPlane->transformation(); } m_lastWorkingState.valid = true; } m_pageFlipPending = true; return true; } bool DrmOutput::presentLegacy(DrmBuffer *buffer) { if (m_crtc->next()) { return false; } if (!LogindIntegration::self()->isActiveSession()) { m_crtc->setNext(buffer); return false; } if (m_dpmsMode != DpmsMode::On) { return false; } // Do we need to set a new mode first? if (!m_crtc->current() || m_crtc->current()->needsModeChange(buffer)) { if (!setModeLegacy(buffer)) { return false; } } const bool ok = drmModePageFlip(m_backend->fd(), m_crtc->id(), buffer->bufferId(), DRM_MODE_PAGE_FLIP_EVENT, this) == 0; if (ok) { m_crtc->setNext(buffer); } else { qCWarning(KWIN_DRM) << "Page flip failed:" << strerror(errno); } return ok; } bool DrmOutput::setModeLegacy(DrmBuffer *buffer) { uint32_t connId = m_conn->id(); if (drmModeSetCrtc(m_backend->fd(), m_crtc->id(), buffer->bufferId(), 0, 0, &connId, 1, &m_mode) == 0) { return true; } else { qCWarning(KWIN_DRM) << "Mode setting failed"; return false; } } bool DrmOutput::doAtomicCommit(AtomicCommitMode mode) { drmModeAtomicReq *req = drmModeAtomicAlloc(); auto errorHandler = [this, mode, req] () { if (mode == AtomicCommitMode::Test) { // TODO: when we later test overlay planes, make sure we change only the right stuff back } if (req) { drmModeAtomicFree(req); } if (m_dpmsMode != m_dpmsModePending) { qCWarning(KWIN_DRM) << "Setting DPMS failed"; m_dpmsModePending = m_dpmsMode; if (m_dpmsMode != DpmsMode::On) { dpmsOffHandler(); } } // TODO: see above, rework later for overlay planes! for (DrmPlane *p : m_nextPlanesFlipList) { p->setNext(nullptr); } m_nextPlanesFlipList.clear(); }; if (!req) { qCWarning(KWIN_DRM) << "DRM: couldn't allocate atomic request"; errorHandler(); return false; } uint32_t flags = 0; // Do we need to set a new mode? if (m_modesetRequested) { if (m_dpmsModePending == DpmsMode::On) { if (drmModeCreatePropertyBlob(m_backend->fd(), &m_mode, sizeof(m_mode), &m_blobId) != 0) { qCWarning(KWIN_DRM) << "Failed to create property blob"; errorHandler(); return false; } } if (!atomicReqModesetPopulate(req, m_dpmsModePending == DpmsMode::On)){ qCWarning(KWIN_DRM) << "Failed to populate Atomic Modeset"; errorHandler(); return false; } flags |= DRM_MODE_ATOMIC_ALLOW_MODESET; } if (mode == AtomicCommitMode::Real) { if (m_dpmsModePending == DpmsMode::On) { if (!(flags & DRM_MODE_ATOMIC_ALLOW_MODESET)) { // TODO: Evaluating this condition should only be necessary, as long as we expect older kernels than 4.10. flags |= DRM_MODE_ATOMIC_NONBLOCK; } #if HAVE_EGL_STREAMS if (!m_backend->useEglStreams()) // EglStreamBackend uses the NV_output_drm_flip_event EGL extension // to register the flip event through eglStreamConsumerAcquireAttribNV #endif flags |= DRM_MODE_PAGE_FLIP_EVENT; } } else { flags |= DRM_MODE_ATOMIC_TEST_ONLY; } bool ret = true; // TODO: Make sure when we use more than one plane at a time, that we go through this list in the right order. for (int i = m_nextPlanesFlipList.size() - 1; 0 <= i; i-- ) { DrmPlane *p = m_nextPlanesFlipList[i]; ret &= p->atomicPopulate(req); } if (!ret) { qCWarning(KWIN_DRM) << "Failed to populate atomic planes. Abort atomic commit!"; errorHandler(); return false; } if (drmModeAtomicCommit(m_backend->fd(), req, flags, this)) { qCWarning(KWIN_DRM) << "Atomic request failed to commit:" << strerror(errno); errorHandler(); return false; } if (mode == AtomicCommitMode::Real && (flags & DRM_MODE_ATOMIC_ALLOW_MODESET)) { qCDebug(KWIN_DRM) << "Atomic Modeset successful."; m_modesetRequested = false; m_dpmsMode = m_dpmsModePending; } drmModeAtomicFree(req); return true; } bool DrmOutput::atomicReqModesetPopulate(drmModeAtomicReq *req, bool enable) { if (enable) { m_primaryPlane->setValue(int(DrmPlane::PropertyIndex::SrcX), 0); m_primaryPlane->setValue(int(DrmPlane::PropertyIndex::SrcY), 0); m_primaryPlane->setValue(int(DrmPlane::PropertyIndex::SrcW), m_mode.hdisplay << 16); m_primaryPlane->setValue(int(DrmPlane::PropertyIndex::SrcH), m_mode.vdisplay << 16); m_primaryPlane->setValue(int(DrmPlane::PropertyIndex::CrtcW), m_mode.hdisplay); m_primaryPlane->setValue(int(DrmPlane::PropertyIndex::CrtcH), m_mode.vdisplay); m_primaryPlane->setValue(int(DrmPlane::PropertyIndex::CrtcId), m_crtc->id()); } else { if (m_backend->deleteBufferAfterPageFlip()) { delete m_primaryPlane->current(); delete m_primaryPlane->next(); } m_primaryPlane->setCurrent(nullptr); m_primaryPlane->setNext(nullptr); m_primaryPlane->setValue(int(DrmPlane::PropertyIndex::SrcX), 0); m_primaryPlane->setValue(int(DrmPlane::PropertyIndex::SrcY), 0); m_primaryPlane->setValue(int(DrmPlane::PropertyIndex::SrcW), 0); m_primaryPlane->setValue(int(DrmPlane::PropertyIndex::SrcH), 0); m_primaryPlane->setValue(int(DrmPlane::PropertyIndex::CrtcW), 0); m_primaryPlane->setValue(int(DrmPlane::PropertyIndex::CrtcH), 0); m_primaryPlane->setValue(int(DrmPlane::PropertyIndex::CrtcId), 0); } m_conn->setValue(int(DrmConnector::PropertyIndex::CrtcId), enable ? m_crtc->id() : 0); m_crtc->setValue(int(DrmCrtc::PropertyIndex::ModeId), enable ? m_blobId : 0); m_crtc->setValue(int(DrmCrtc::PropertyIndex::Active), enable); bool ret = true; ret &= m_conn->atomicPopulate(req); ret &= m_crtc->atomicPopulate(req); return ret; } bool DrmOutput::supportsTransformations() const { if (!m_primaryPlane) { return false; } const auto transformations = m_primaryPlane->supportedTransformations(); return transformations.testFlag(DrmPlane::Transformation::Rotate90) || transformations.testFlag(DrmPlane::Transformation::Rotate180) || transformations.testFlag(DrmPlane::Transformation::Rotate270); } void DrmOutput::automaticRotation() { if (!m_primaryPlane) { return; } const auto supportedTransformations = m_primaryPlane->supportedTransformations(); const auto requestedTransformation = screens()->orientationSensor()->orientation(); using KWayland::Server::OutputDeviceInterface; OutputDeviceInterface::Transform newTransformation = OutputDeviceInterface::Transform::Normal; switch (requestedTransformation) { case OrientationSensor::Orientation::TopUp: newTransformation = OutputDeviceInterface::Transform::Normal; break; case OrientationSensor::Orientation::TopDown: if (!supportedTransformations.testFlag(DrmPlane::Transformation::Rotate180)) { return; } newTransformation = OutputDeviceInterface::Transform::Rotated180; break; case OrientationSensor::Orientation::LeftUp: if (!supportedTransformations.testFlag(DrmPlane::Transformation::Rotate90)) { return; } newTransformation = OutputDeviceInterface::Transform::Rotated90; break; case OrientationSensor::Orientation::RightUp: if (!supportedTransformations.testFlag(DrmPlane::Transformation::Rotate270)) { return; } newTransformation = OutputDeviceInterface::Transform::Rotated270; break; case OrientationSensor::Orientation::FaceUp: case OrientationSensor::Orientation::FaceDown: case OrientationSensor::Orientation::Undefined: // unsupported return; } transform(newTransformation); emit screens()->changed(); } int DrmOutput::gammaRampSize() const { return m_crtc->gammaRampSize(); } bool DrmOutput::setGammaRamp(const GammaRamp &gamma) { return m_crtc->setGammaRamp(gamma); } } diff --git a/plugins/platforms/drm/gbm_surface.h b/plugins/platforms/drm/gbm_surface.h index 5eaa4c908..6c3220cba 100644 --- a/plugins/platforms/drm/gbm_surface.h +++ b/plugins/platforms/drm/gbm_surface.h @@ -1,55 +1,55 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2017 Martin Flöser 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_DRM_GBM_SURFACE_H #define KWIN_DRM_GBM_SURFACE_H -#include +#include struct gbm_bo; struct gbm_device; struct gbm_surface; namespace KWin { class GbmSurface { public: explicit GbmSurface(gbm_device *gbm, uint32_t width, uint32_t height, uint32_t format, uint32_t flags); ~GbmSurface(); gbm_bo *lockFrontBuffer(); void releaseBuffer(gbm_bo *bo); operator bool() const { return m_surface != nullptr; } gbm_surface* surface() const { return m_surface; } private: gbm_surface *m_surface; }; } #endif diff --git a/plugins/platforms/drm/remoteaccess_manager.cpp b/plugins/platforms/drm/remoteaccess_manager.cpp index 201fd1994..abb0b37ef 100644 --- a/plugins/platforms/drm/remoteaccess_manager.cpp +++ b/plugins/platforms/drm/remoteaccess_manager.cpp @@ -1,88 +1,89 @@ /******************************************************************** * KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2016 Oleg Chernovskiy 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 "drm_output.h" #include "remoteaccess_manager.h" #include "logging.h" #include "drm_backend.h" #include "../../../wayland_server.h" // system #include #include #include -#include + +#include namespace KWin { RemoteAccessManager::RemoteAccessManager(QObject *parent) : QObject(parent) { if (waylandServer()) { m_interface = waylandServer()->display()->createRemoteAccessManager(this); m_interface->create(); connect(m_interface, &RemoteAccessManagerInterface::bufferReleased, this, &RemoteAccessManager::releaseBuffer); } } RemoteAccessManager::~RemoteAccessManager() { if (m_interface) { m_interface->destroy(); } } void RemoteAccessManager::releaseBuffer(const BufferHandle *buf) { int ret = close(buf->fd()); if (Q_UNLIKELY(ret)) { qCWarning(KWIN_DRM) << "Couldn't close released GBM fd:" << strerror(errno); } delete buf; } void RemoteAccessManager::passBuffer(DrmOutput *output, DrmBuffer *buffer) { DrmSurfaceBuffer* gbmbuf = static_cast(buffer); // no connected RemoteAccess instance if (!m_interface || !m_interface->isBound()) { return; } // first buffer may be null if (!gbmbuf || !gbmbuf->hasBo()) { return; } auto buf = new BufferHandle; auto bo = gbmbuf->getBo(); buf->setFd(gbm_bo_get_fd(bo)); buf->setSize(gbm_bo_get_width(bo), gbm_bo_get_height(bo)); buf->setStride(gbm_bo_get_stride(bo)); buf->setFormat(gbm_bo_get_format(bo)); m_interface->sendBufferReady(output->waylandOutput().data(), buf); } } // KWin namespace diff --git a/plugins/platforms/x11/common/ge_event_mem_mover.h b/plugins/platforms/x11/common/ge_event_mem_mover.h index 8b6b96db1..2b693f409 100644 --- a/plugins/platforms/x11/common/ge_event_mem_mover.h +++ b/plugins/platforms/x11/common/ge_event_mem_mover.h @@ -1,55 +1,55 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2018 Martin Flöser 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 namespace KWin { class GeEventMemMover { public: GeEventMemMover(xcb_generic_event_t *event) : m_event(reinterpret_cast(event)) { // xcb event structs contain stuff that wasn't on the wire, the full_sequence field // adds an extra 4 bytes and generic events cookie data is on the wire right after the standard 32 bytes. // Move this data back to have the same layout in memory as it was on the wire // and allow casting, overwriting the full_sequence field. memmove((char*) m_event + 32, (char*) m_event + 36, m_event->length * 4); } ~GeEventMemMover() { // move memory layout back, so that Qt can do the same without breaking memmove((char*) m_event + 36, (char *) m_event + 32, m_event->length * 4); } xcb_ge_generic_event_t *operator->() const { return m_event; } private: xcb_ge_generic_event_t *m_event; }; } diff --git a/plugins/platforms/x11/standalone/glxbackend.cpp b/plugins/platforms/x11/standalone/glxbackend.cpp index 1d31b447f..c6579c617 100644 --- a/plugins/platforms/x11/standalone/glxbackend.cpp +++ b/plugins/platforms/x11/standalone/glxbackend.cpp @@ -1,949 +1,949 @@ /******************************************************************** 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 -#include +#include #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) , 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; } 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()); if (ctx) { qCDebug(KWIN_X11STANDALONE) << "Created GLX context with attributes:" << &(*it); break; } } } if (!ctx) ctx = glXCreateNewContext(display(), fbconfig, GLX_RGBA_TYPE, NULL, 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; 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); 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) { foreach (const QRect & r, lastDamage().rects()) { // 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() { 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); } 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 { 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 5a787665f..c8125a96d 100644 --- a/plugins/platforms/x11/standalone/overlaywindow_x11.cpp +++ b/plugins/platforms/x11/standalone/overlaywindow_x11.cpp @@ -1,213 +1,213 @@ /******************************************************************** 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 "assert.h" - #include +#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() { 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) { assert(m_window != XCB_WINDOW_NONE); 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); } 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() { 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() { 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; QVector< QRect > rects = reg.rects(); xcb_rectangle_t *xrects = new xcb_rectangle_t[rects.count()]; for (int i = 0; i < rects.count(); ++i) { xrects[ i ].x = rects[ i ].x(); xrects[ i ].y = rects[ i ].y(); xrects[ i ].width = rects[ i ].width(); xrects[ i ].height = rects[ i ].height(); } xcb_shape_rectangles(connection(), XCB_SHAPE_SO_SET, XCB_SHAPE_SK_BOUNDING, XCB_CLIP_ORDERING_UNSORTED, m_window, 0, 0, rects.count(), xrects); delete[] xrects; setupInputShape(m_window); m_shape = reg; } void OverlayWindowX11::resize(const QSize &size) { 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/scenes/opengl/lanczosfilter.cpp b/plugins/scenes/opengl/lanczosfilter.cpp index c02158dd6..f70c2c12a 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 #include namespace KWin { LanczosFilter::LanczosFilter(QObject* parent) : QObject(parent) , m_offscreenTex(0) , m_offscreenTarget(0) , m_inited(false) , m_shader(0) , 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; 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; 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/scene_opengl.cpp b/plugins/scenes/opengl/scene_opengl.cpp index 8da2d358d..6c3a08300 100644 --- a/plugins/scenes/opengl/scene_opengl.cpp +++ b/plugins/scenes/opengl/scene_opengl.cpp @@ -1,2574 +1,2574 @@ /******************************************************************** 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 #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() { 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. 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() { 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() { 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); #ifndef NDEBUG // 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; } SceneOpenGL *scene = NULL; // first let's try an OpenGL 2 scene if (SceneOpenGL2::supported(backend)) { scene = new SceneOpenGL2(backend, parent); if (scene->initFailed()) { delete scene; scene = NULL; } 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() { 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 { GLint limit[2]; glGetIntegerv(GL_MAX_VIEWPORT_DIMS, limit); if (limit[0] < size.width() || limit[1] < size.height()) { QMetaObject::invokeMethod(Compositor::self(), "suspend", Qt::QueuedConnection, Q_ARG(Compositor::SuspendReason, Compositor::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) { 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); 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->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) { } SceneOpenGL::Window::~Window() { } static SceneOpenGLTexture *s_frameTexture = NULL; // Bind the window pixmap to an OpenGL texture. bool SceneOpenGL::Window::bindTexture() { s_frameTexture = NULL; 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 (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].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()); const GLenum filter = (mask & (Effect::PAINT_WINDOW_TRANSFORMED | Effect::PAINT_SCREEN_TRANSFORMED)) && options->glSmoothScale() != 0 ? GL_LINEAR : 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; 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_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; delete m_textTexture; m_textTexture = NULL; delete m_textPixmap; m_textPixmap = NULL; delete m_iconTexture; m_iconTexture = NULL; delete m_selectionTexture; m_selectionTexture = NULL; delete m_unstyledVBO; m_unstyledVBO = NULL; delete m_oldIconTexture; m_oldIconTexture = NULL; delete m_oldTextTexture; m_oldTextTexture = NULL; } void SceneOpenGL::EffectFrame::freeIconFrame() { delete m_iconTexture; m_iconTexture = NULL; } void SceneOpenGL::EffectFrame::freeTextFrame() { delete m_textTexture; m_textTexture = NULL; delete m_textPixmap; m_textPixmap = NULL; } void SceneOpenGL::EffectFrame::freeSelection() { delete m_selectionTexture; m_selectionTexture = NULL; } void SceneOpenGL::EffectFrame::crossFadeIcon() { delete m_oldIconTexture; m_oldIconTexture = m_iconTexture; m_iconTexture = NULL; } void SceneOpenGL::EffectFrame::crossFadeText() { delete m_oldTextTexture; m_oldTextTexture = m_textTexture; m_textTexture = NULL; } 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; 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; delete m_textPixmap; m_textPixmap = 0L; 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; delete m_unstyledPixmap; m_unstyledPixmap = 0L; // 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; delete m_unstyledPixmap; m_unstyledPixmap = NULL; } //**************************************** // 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.isNull()) { 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/xrender/scene_xrender.cpp b/plugins/scenes/xrender/scene_xrender.cpp index 30911f15a..38bb8c97c 100644 --- a/plugins/scenes/xrender/scene_xrender.cpp +++ b/plugins/scenes/xrender/scene_xrender.cpp @@ -1,1326 +1,1326 @@ /******************************************************************** 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 +#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; } 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)); 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); } 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_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 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; 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; 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; 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 = transformed_shape.rects(); for (int i = 0; i < rects.count(); ++i) { QRect& r = rects[ i ]; r.setRect(qRound(r.x() * xscale), qRound(r.y() * yscale), qRound(r.width() * xscale), qRound(r.height() * yscale)); } 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); } 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); } //**************************************** // SceneXrender::EffectFrame //**************************************** XRenderPicture *SceneXrender::EffectFrame::s_effectFrameCircle = NULL; SceneXrender::EffectFrame::EffectFrame(EffectFrameImpl* frame) : Scene::EffectFrame(frame) { m_picture = NULL; m_textPicture = NULL; m_iconPicture = NULL; m_selectionPicture = NULL; } 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; } void SceneXrender::EffectFrame::free() { delete m_picture; m_picture = NULL; delete m_textPicture; m_textPicture = NULL; delete m_iconPicture; m_iconPicture = NULL; delete m_selectionPicture; m_selectionPicture = NULL; } void SceneXrender::EffectFrame::freeIconFrame() { delete m_iconPicture; m_iconPicture = NULL; } void SceneXrender::EffectFrame::freeTextFrame() { delete m_textPicture; m_textPicture = NULL; } void SceneXrender::EffectFrame::freeSelection() { delete m_selectionPicture; m_selectionPicture = NULL; } 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; 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; 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.isNull()) { 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.byteCount(), 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/shadow.h b/shadow.h index 399b9f446..df92c2883 100644 --- a/shadow.h +++ b/shadow.h @@ -1,193 +1,192 @@ /******************************************************************** 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_SHADOW_H #define KWIN_SHADOW_H #include #include #include -#include namespace KDecoration2 { class Decoration; class DecorationShadow; } namespace KWayland { namespace Server { class ShadowInterface; } } namespace KWin { class Toplevel; /** * @short Class representing a Window's Shadow to be rendered by the Compositor. * * This class holds all information about the Shadow to be rendered together with the * window during the Compositing stage. The Shadow consists of several pixmaps and offsets. * For a complete description please refer to https://community.kde.org/KWin/Shadow * * To create a Shadow instance use the static factory method createShadow which will * create an instance for the currently used Compositing Backend. It will read the X11 Property * and create the Shadow and all required data (such as WindowQuads). If there is no Shadow * defined for the Toplevel the factory method returns @c NULL. * * @author Martin Gräßlin * @todo React on Toplevel size changes. **/ class KWIN_EXPORT Shadow : public QObject { Q_OBJECT public: virtual ~Shadow(); /** * @return Region of the shadow. **/ const QRegion &shadowRegion() const { return m_shadowRegion; }; /** * @return Cached Shadow Quads **/ const WindowQuadList &shadowQuads() const { return m_shadowQuads; }; WindowQuadList &shadowQuads() { return m_shadowQuads; }; /** * This method updates the Shadow when the property has been changed. * It is the responsibility of the owner of the Shadow to call this method * whenever the owner receives a PropertyNotify event. * This method will invoke a re-read of the Property. In case the Property has * been withdrawn the method returns @c false. In that case the owner should * delete the Shadow. * @returns @c true when the shadow has been updated, @c false if the property is not set anymore. **/ virtual bool updateShadow(); /** * Factory Method to create the shadow from the property. * This method takes care of creating an instance of the * Shadow class for the current Compositing Backend. * * If there is no shadow defined for @p toplevel this method * will return @c NULL. * @param toplevel The Toplevel for which the shadow should be created * @return Created Shadow or @c NULL in case there is no shadow defined. **/ static Shadow *createShadow(Toplevel *toplevel); /** * Reparents the shadow to @p toplevel. * Used when a window is deleted. * @param toplevel The new parent **/ void setToplevel(Toplevel *toplevel); bool hasDecorationShadow() const { return !m_decorationShadow.isNull(); } QImage decorationShadowImage() const; QWeakPointer decorationShadow() const { return m_decorationShadow.toWeakRef(); } public Q_SLOTS: void geometryChanged(); protected: Shadow(Toplevel *toplevel); enum ShadowElements { ShadowElementTop, ShadowElementTopRight, ShadowElementRight, ShadowElementBottomRight, ShadowElementBottom, ShadowElementBottomLeft, ShadowElementLeft, ShadowElementTopLeft, ShadowElementsCount }; inline const QPixmap &shadowPixmap(ShadowElements element) const { return m_shadowElements[element]; }; QSize elementSize(ShadowElements element) const; int topOffset() const { return m_topOffset; }; int rightOffset() const { return m_rightOffset; }; int bottomOffset() const { return m_bottomOffset; }; int leftOffset() const { return m_leftOffset; }; virtual void buildQuads(); void updateShadowRegion(); Toplevel *topLevel() { return m_topLevel; }; void setShadowRegion(const QRegion ®ion) { m_shadowRegion = region; }; virtual bool prepareBackend() = 0; WindowQuadList m_shadowQuads; void setShadowElement(const QPixmap &shadow, ShadowElements element); private: static Shadow *createShadowFromX11(Toplevel *toplevel); static Shadow *createShadowFromDecoration(Toplevel *toplevel); static Shadow *createShadowFromWayland(Toplevel *toplevel); static QVector readX11ShadowProperty(xcb_window_t id); bool init(const QVector &data); bool init(KDecoration2::Decoration *decoration); bool init(const QPointer &shadow); Toplevel *m_topLevel; // shadow pixmaps QPixmap m_shadowElements[ShadowElementsCount]; // shadow offsets int m_topOffset; int m_rightOffset; int m_bottomOffset; int m_leftOffset; // caches QRegion m_shadowRegion; QSize m_cachedSize; // Decoration based shadows QSharedPointer m_decorationShadow; }; } #endif // KWIN_SHADOW_H diff --git a/shell_client.cpp b/shell_client.cpp index d3a7b0da3..ca7e24643 100644 --- a/shell_client.cpp +++ b/shell_client.cpp @@ -1,1967 +1,1968 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2015 Martin Gräßlin Copyright (C) 2018 David Edmundson This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "shell_client.h" #include "composite.h" #include "cursor.h" #include "deleted.h" #include "placement.h" #include "screenedge.h" #include "screens.h" #include "wayland_server.h" #include "workspace.h" #include "virtualdesktops.h" #include "screens.h" #include "decorations/decorationbridge.h" #include "decorations/decoratedclient.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include -#include + +#include Q_DECLARE_METATYPE(NET::WindowType) using namespace KWayland::Server; namespace KWin { ShellClient::ShellClient(ShellSurfaceInterface *surface) : AbstractClient() , m_shellSurface(surface) , m_xdgShellSurface(nullptr) , m_xdgShellPopup(nullptr) , m_internal(surface->client() == waylandServer()->internalConnection()) { setSurface(surface->surface()); init(); m_isInitialized = true; } ShellClient::ShellClient(XdgShellSurfaceInterface *surface) : AbstractClient() , m_shellSurface(nullptr) , m_xdgShellSurface(surface) , m_xdgShellPopup(nullptr) , m_internal(surface->client() == waylandServer()->internalConnection()) { setSurface(surface->surface()); m_requestGeometryBlockCounter++; init(); connect(surface->surface(), &SurfaceInterface::committed, this, &ShellClient::finishInit); } ShellClient::ShellClient(XdgShellPopupInterface *surface) : AbstractClient() , m_shellSurface(nullptr) , m_xdgShellSurface(nullptr) , m_xdgShellPopup(surface) , m_internal(surface->client() == waylandServer()->internalConnection()) { setSurface(surface->surface()); m_requestGeometryBlockCounter++; init(); connect(surface->surface(), &SurfaceInterface::committed, this, &ShellClient::finishInit); } ShellClient::~ShellClient() = default; template void ShellClient::initSurface(T *shellSurface) { m_caption = shellSurface->title().simplified(); // delay till end of init QTimer::singleShot(0, this, &ShellClient::updateCaption); connect(shellSurface, &T::destroyed, this, &ShellClient::destroyClient); connect(shellSurface, &T::titleChanged, this, [this] (const QString &s) { const auto oldSuffix = m_captionSuffix; m_caption = s.simplified(); updateCaption(); if (m_captionSuffix == oldSuffix) { // don't emit caption change twice // it already got emitted by the changing suffix emit captionChanged(); } } ); connect(shellSurface, &T::moveRequested, this, [this] { // TODO: check the seat and serial performMouseCommand(Options::MouseMove, Cursor::pos()); } ); // determine the resource name, this is inspired from ICCCM 4.1.2.5 // the binary name of the invoked client QFileInfo info{shellSurface->client()->executablePath()}; QByteArray resourceName; if (info.exists()) { resourceName = info.fileName().toUtf8(); } setResourceClass(resourceName, shellSurface->windowClass()); setDesktopFileName(shellSurface->windowClass()); connect(shellSurface, &T::windowClassChanged, this, [this, resourceName] (const QByteArray &windowClass) { setResourceClass(resourceName, windowClass); if (m_isInitialized && supportsWindowRules()) { setupWindowRules(true); applyWindowRules(); } setDesktopFileName(windowClass); } ); connect(shellSurface, &T::resizeRequested, this, [this] (SeatInterface *seat, quint32 serial, Qt::Edges edges) { // TODO: check the seat and serial Q_UNUSED(seat) Q_UNUSED(serial) if (!isResizable() || isShade()) { return; } if (isMoveResize()) { finishMoveResize(false); } setMoveResizePointerButtonDown(true); setMoveOffset(Cursor::pos() - pos()); // map from global setInvertedMoveOffset(rect().bottomRight() - moveOffset()); setUnrestrictedMoveResize(false); auto toPosition = [edges] { Position pos = PositionCenter; if (edges.testFlag(Qt::TopEdge)) { pos = PositionTop; } else if (edges.testFlag(Qt::BottomEdge)) { pos = PositionBottom; } if (edges.testFlag(Qt::LeftEdge)) { pos = Position(pos | PositionLeft); } else if (edges.testFlag(Qt::RightEdge)) { pos = Position(pos | PositionRight); } return pos; }; setMoveResizePointerMode(toPosition()); if (!startMoveResize()) setMoveResizePointerButtonDown(false); updateCursor(); } ); connect(shellSurface, &T::maximizedChanged, this, [this] (bool maximized) { if (m_shellSurface && isFullScreen()) { // ignore for wl_shell - there it is mutual exclusive and messes with the geometry return; } maximize(maximized ? MaximizeFull : MaximizeRestore); } ); // TODO: consider output! connect(shellSurface, &T::fullscreenChanged, this, &ShellClient::clientFullScreenChanged); connect(shellSurface, &T::transientForChanged, this, &ShellClient::setTransient); connect(this, &ShellClient::geometryChanged, this, &ShellClient::updateClientOutputs); connect(screens(), &Screens::changed, this, &ShellClient::updateClientOutputs); } void ShellClient::init() { connect(this, &ShellClient::desktopFileNameChanged, this, &ShellClient::updateIcon); createWindowId(); setupCompositing(); updateIcon(); SurfaceInterface *s = surface(); Q_ASSERT(s); if (s->buffer()) { setReadyForPainting(); if (shouldExposeToWindowManagement()) { setupWindowManagementInterface(); } m_unmapped = false; m_clientSize = s->size(); } else { ready_for_painting = false; } if (!m_internal) { doSetGeometry(QRect(QPoint(0, 0), m_clientSize)); } if (waylandServer()->inputMethodConnection() == s->client()) { m_windowType = NET::OnScreenDisplay; } connect(s, &SurfaceInterface::sizeChanged, this, [this] { m_clientSize = surface()->size(); doSetGeometry(QRect(geom.topLeft(), m_clientSize + QSize(borderLeft() + borderRight(), borderTop() + borderBottom()))); } ); connect(s, &SurfaceInterface::unmapped, this, &ShellClient::unmap); connect(s, &SurfaceInterface::unbound, this, &ShellClient::destroyClient); connect(s, &SurfaceInterface::destroyed, this, &ShellClient::destroyClient); if (m_shellSurface) { initSurface(m_shellSurface); auto setPopup = [this] { // TODO: verify grab serial m_hasPopupGrab = m_shellSurface->isPopup(); }; connect(m_shellSurface, &ShellSurfaceInterface::popupChanged, this, setPopup); setPopup(); } else if (m_xdgShellSurface) { initSurface(m_xdgShellSurface); auto global = static_cast(m_xdgShellSurface->global()); connect(global, &XdgShellInterface::pingDelayed, this, [this](qint32 serial) { auto it = m_pingSerials.find(serial); if (it != m_pingSerials.end()) { qCDebug(KWIN_CORE) << "First ping timeout:" << caption(); setUnresponsive(true); } }); connect(m_xdgShellSurface, &XdgShellSurfaceInterface::configureAcknowledged, this, [this](int serial) { m_lastAckedConfigureRequest = serial; }); connect(global, &XdgShellInterface::pingTimeout, this, [this](qint32 serial) { auto it = m_pingSerials.find(serial); if (it != m_pingSerials.end()) { if (it.value() == PingReason::CloseWindow) { qCDebug(KWIN_CORE) << "Final ping timeout on a close attempt, asking to kill:" << caption(); //for internal windows, killing the window will delete this QPointer guard(this); killWindow(); if (!guard) { return; } } m_pingSerials.erase(it); } }); connect(global, &XdgShellInterface::pongReceived, this, [this](qint32 serial){ auto it = m_pingSerials.find(serial); if (it != m_pingSerials.end()) { setUnresponsive(false); m_pingSerials.erase(it); } }); connect(m_xdgShellSurface, &XdgShellSurfaceInterface::windowMenuRequested, this, [this] (SeatInterface *seat, quint32 serial, const QPoint &surfacePos) { // TODO: check serial on seat Q_UNUSED(seat) Q_UNUSED(serial) performMouseCommand(Options::MouseOperationsMenu, pos() + surfacePos); } ); connect(m_xdgShellSurface, &XdgShellSurfaceInterface::minimizeRequested, this, [this] { performMouseCommand(Options::MouseMinimize, Cursor::pos()); } ); auto configure = [this] { if (m_closing) { return; } if (m_requestGeometryBlockCounter != 0 || areGeometryUpdatesBlocked()) { return; } m_xdgShellSurface->configure(xdgSurfaceStates(), m_requestedClientSize); }; connect(this, &AbstractClient::activeChanged, this, configure); connect(this, &AbstractClient::clientStartUserMovedResized, this, configure); connect(this, &AbstractClient::clientFinishUserMovedResized, this, configure); } else if (m_xdgShellPopup) { connect(m_xdgShellPopup, &XdgShellPopupInterface::grabRequested, this, [this](SeatInterface *seat, quint32 serial) { Q_UNUSED(seat) Q_UNUSED(serial) //TODO - should check the parent had focus m_hasPopupGrab = true; }); connect(m_xdgShellPopup, &XdgShellPopupInterface::configureAcknowledged, this, [this](int serial) { m_lastAckedConfigureRequest = serial; }); connect(m_xdgShellPopup, &XdgShellPopupInterface::destroyed, this, &ShellClient::destroyClient); } // set initial desktop setDesktop(m_internal ? int(NET::OnAllDesktops) : VirtualDesktopManager::self()->current()); // setup shadow integration getShadow(); connect(s, &SurfaceInterface::shadowChanged, this, &Toplevel::getShadow); connect(waylandServer(), &WaylandServer::foreignTransientChanged, this, [this](KWayland::Server::SurfaceInterface *child) { if (child == surface()) { setTransient(); } }); setTransient(); AbstractClient::updateColorScheme(QString()); } void ShellClient::finishInit() { SurfaceInterface *s = surface(); disconnect(s, &SurfaceInterface::committed, this, &ShellClient::finishInit); updateWindowMargins(); if (supportsWindowRules()) { setupWindowRules(false); setDesktop(rules()->checkDesktop(desktop(), true)); setDesktopFileName(rules()->checkDesktopFile(desktopFileName(), true).toUtf8()); if (rules()->checkMinimize(isMinimized(), true)) { minimize(true); // No animation. } setSkipTaskbar(rules()->checkSkipTaskbar(skipTaskbar(), true)); setSkipPager(rules()->checkSkipPager(skipPager(), true)); setSkipSwitcher(rules()->checkSkipSwitcher(skipSwitcher(), true)); setKeepAbove(rules()->checkKeepAbove(keepAbove(), true)); setKeepBelow(rules()->checkKeepBelow(keepBelow(), true)); setShortcut(rules()->checkShortcut(shortcut().toString(), true)); updateColorScheme(); discardTemporaryRules(); RuleBook::self()->discardUsed(this, false); // Remove Apply Now rules. updateWindowRules(Rules::All); } if (!isInitialPositionSet()) { QRect area = workspace()->clientArea(PlacementArea, Screens::self()->current(), desktop()); placeIn(area); } m_requestGeometryBlockCounter--; if (m_requestGeometryBlockCounter == 0) { requestGeometry(m_blockedRequestGeometry); } m_isInitialized = true; } void ShellClient::destroyClient() { m_closing = true; if (isMoveResize()) { leaveMoveResize(); } Deleted *del = nullptr; if (workspace()) { del = Deleted::create(this); } emit windowClosed(this, del); // Remove Force Temporarily rules. RuleBook::self()->discardUsed(this, true); destroyWindowManagementInterface(); destroyDecoration(); if (workspace()) { StackingUpdatesBlocker blocker(workspace()); if (transientFor()) { transientFor()->removeTransient(this); } for (auto it = transients().constBegin(); it != transients().constEnd();) { if ((*it)->transientFor() == this) { removeTransient(*it); it = transients().constBegin(); // restart, just in case something more has changed with the list } else { ++it; } } } waylandServer()->removeClient(this); if (del) { del->unrefWindow(); } m_shellSurface = nullptr; m_xdgShellSurface = nullptr; m_xdgShellPopup = nullptr; deleteClient(this); } void ShellClient::deleteClient(ShellClient *c) { delete c; } QSize ShellClient::toWindowGeometry(const QSize &size) const { QSize adjustedSize = size - QSize(borderLeft() + borderRight(), borderTop() + borderBottom()); // a client going fullscreen should have the window the contents size of the screen if (!isFullScreen() && requestedMaximizeMode() != MaximizeFull) { adjustedSize -= QSize(m_windowMargins.left() + m_windowMargins.right(), m_windowMargins.top() + m_windowMargins.bottom()); } return adjustedSize; } QStringList ShellClient::activities() const { // TODO: implement return QStringList(); } QPoint ShellClient::clientContentPos() const { return -1 * clientPos(); } QSize ShellClient::clientSize() const { return m_clientSize; } void ShellClient::debug(QDebug &stream) const { stream.nospace(); stream << "\'ShellClient:" << surface() << ";WMCLASS:" << resourceClass() << ":" << resourceName() << ";Caption:" << caption() << "\'"; } bool ShellClient::belongsToDesktop() const { const auto clients = waylandServer()->clients(); return std::any_of(clients.constBegin(), clients.constEnd(), [this](const ShellClient *client) { if (belongsToSameApplication(client, SameApplicationChecks())) { return client->isDesktop(); } return false; } ); } Layer ShellClient::layerForDock() const { if (m_plasmaShellSurface) { switch (m_plasmaShellSurface->panelBehavior()) { case PlasmaShellSurfaceInterface::PanelBehavior::WindowsCanCover: return NormalLayer; case PlasmaShellSurfaceInterface::PanelBehavior::AutoHide: return AboveLayer; case PlasmaShellSurfaceInterface::PanelBehavior::WindowsGoBelow: case PlasmaShellSurfaceInterface::PanelBehavior::AlwaysVisible: return DockLayer; default: Q_UNREACHABLE(); break; } } return AbstractClient::layerForDock(); } QRect ShellClient::transparentRect() const { // TODO: implement return QRect(); } NET::WindowType ShellClient::windowType(bool direct, int supported_types) const { // TODO: implement Q_UNUSED(direct) Q_UNUSED(supported_types) return m_windowType; } double ShellClient::opacity() const { return m_opacity; } void ShellClient::setOpacity(double opacity) { const qreal newOpacity = qBound(0.0, opacity, 1.0); if (newOpacity == m_opacity) { return; } const qreal oldOpacity = m_opacity; m_opacity = newOpacity; addRepaintFull(); emit opacityChanged(this, oldOpacity); } void ShellClient::addDamage(const QRegion &damage) { auto s = surface(); if (s->size().isValid()) { m_clientSize = s->size(); updateWindowMargins(); updatePendingGeometry(); } markAsMapped(); setDepth((s->buffer()->hasAlphaChannel() && !isDesktop()) ? 32 : 24); repaints_region += damage.translated(clientPos()); Toplevel::addDamage(damage); } void ShellClient::markAsMapped() { if (!m_unmapped) { return; } m_unmapped = false; if (!ready_for_painting) { setReadyForPainting(); } else { addRepaintFull(); emit windowShown(this); } if (shouldExposeToWindowManagement()) { setupWindowManagementInterface(); } updateShowOnScreenEdge(); } void ShellClient::createDecoration(const QRect &oldGeom) { KDecoration2::Decoration *decoration = Decoration::DecorationBridge::self()->createDecoration(this); if (decoration) { QMetaObject::invokeMethod(decoration, "update", Qt::QueuedConnection); connect(decoration, &KDecoration2::Decoration::shadowChanged, this, &Toplevel::getShadow); connect(decoration, &KDecoration2::Decoration::bordersChanged, this, [this]() { GeometryUpdatesBlocker blocker(this); RequestGeometryBlocker requestBlocker(this); QRect oldgeom = geometry(); if (!isShade()) checkWorkspacePosition(oldgeom); emit geometryShapeChanged(this, oldgeom); } ); } setDecoration(decoration); // TODO: ensure the new geometry still fits into the client area (e.g. maximized windows) doSetGeometry(QRect(oldGeom.topLeft(), m_clientSize + (decoration ? QSize(decoration->borderLeft() + decoration->borderRight(), decoration->borderBottom() + decoration->borderTop()) : QSize()))); emit geometryShapeChanged(this, oldGeom); } void ShellClient::updateDecoration(bool check_workspace_pos, bool force) { if (!force && ((!isDecorated() && noBorder()) || (isDecorated() && !noBorder()))) return; QRect oldgeom = geometry(); QRect oldClientGeom = oldgeom.adjusted(borderLeft(), borderTop(), -borderRight(), -borderBottom()); blockGeometryUpdates(true); if (force) destroyDecoration(); if (!noBorder()) { createDecoration(oldgeom); } else destroyDecoration(); if (m_serverDecoration && isDecorated()) { m_serverDecoration->setMode(KWayland::Server::ServerSideDecorationManagerInterface::Mode::Server); } if (m_xdgDecoration) { auto mode = isDecorated() || m_userNoBorder ? XdgDecorationInterface::Mode::ServerSide: XdgDecorationInterface::Mode::ClientSide; m_xdgDecoration->configure(mode); if (m_requestGeometryBlockCounter == 0) { m_xdgShellSurface->configure(xdgSurfaceStates(), m_requestedClientSize); } } getShadow(); if (check_workspace_pos) checkWorkspacePosition(oldgeom, -2, oldClientGeom); blockGeometryUpdates(false); } void ShellClient::setGeometry(int x, int y, int w, int h, ForceGeometry_t force) { if (areGeometryUpdatesBlocked()) { // when the GeometryUpdateBlocker exits the current geom is passed to setGeometry // thus we need to set it here. geom = QRect(x, y, w, h); if (pendingGeometryUpdate() == PendingGeometryForced) {} // maximum, nothing needed else if (force == ForceGeometrySet) setPendingGeometryUpdate(PendingGeometryForced); else setPendingGeometryUpdate(PendingGeometryNormal); return; } if (pendingGeometryUpdate() != PendingGeometryNone) { // reset geometry to the one before blocking, so that we can compare properly geom = geometryBeforeUpdateBlocking(); } const QSize requestedClientSize = QSize(w, h) - QSize(borderLeft() + borderRight(), borderTop() + borderBottom()); const QSize requestedWindowGeometrySize = toWindowGeometry(QSize(w, h)); if (requestedClientSize == m_clientSize && !isWaitingForMoveResizeSync() && (m_requestedClientSize.isEmpty() || requestedWindowGeometrySize == m_requestedClientSize)) { // size didn't change, and we don't need to explicitly request a new size doSetGeometry(QRect(x, y, w, h)); updateMaximizeMode(m_requestedMaximizeMode); } else { // size did change, Client needs to provide a new buffer requestGeometry(QRect(x, y, w, h)); } } void ShellClient::doSetGeometry(const QRect &rect) { if (geom == rect && pendingGeometryUpdate() == PendingGeometryNone) { return; } if (!m_unmapped) { addWorkspaceRepaint(visibleRect()); } geom = rect; if (m_unmapped && m_geomMaximizeRestore.isEmpty() && !geom.isEmpty()) { // use first valid geometry as restore geometry m_geomMaximizeRestore = geom; } if (!m_unmapped) { addWorkspaceRepaint(visibleRect()); } if (hasStrut()) { workspace()->updateClientArea(); } const auto old = geometryBeforeUpdateBlocking(); updateGeometryBeforeUpdateBlocking(); emit geometryShapeChanged(this, old); if (isResize()) { performMoveResize(); } } QByteArray ShellClient::windowRole() const { return QByteArray(); } bool ShellClient::belongsToSameApplication(const AbstractClient *other, SameApplicationChecks checks) const { if (checks.testFlag(SameApplicationCheck::AllowCrossProcesses)) { if (other->desktopFileName() == desktopFileName()) { return true; } } if (auto s = other->surface()) { return s->client() == surface()->client(); } return false; } void ShellClient::blockActivityUpdates(bool b) { Q_UNUSED(b) } void ShellClient::updateCaption() { const QString oldSuffix = m_captionSuffix; const auto shortcut = shortcutCaptionSuffix(); m_captionSuffix = shortcut; if ((!isSpecialWindow() || isToolbar()) && findClientWithSameCaption()) { int i = 2; do { m_captionSuffix = shortcut + QLatin1String(" <") + QString::number(i) + QLatin1Char('>'); i++; } while (findClientWithSameCaption()); } if (m_captionSuffix != oldSuffix) { emit captionChanged(); } } void ShellClient::closeWindow() { if (m_xdgShellSurface && isCloseable()) { m_xdgShellSurface->close(); const qint32 pingSerial = static_cast(m_xdgShellSurface->global())->ping(m_xdgShellSurface); m_pingSerials.insert(pingSerial, PingReason::CloseWindow); } } AbstractClient *ShellClient::findModal(bool allow_itself) { Q_UNUSED(allow_itself) return nullptr; } bool ShellClient::isCloseable() const { if (m_windowType == NET::Desktop || m_windowType == NET::Dock) { return false; } if (m_xdgShellSurface) { return true; } return false; } bool ShellClient::isFullScreen() const { return m_fullScreen; } bool ShellClient::isMaximizable() const { return true; } bool ShellClient::isMinimizable() const { if (!rules()->checkMinimize(true)) { return false; } return (!m_plasmaShellSurface || m_plasmaShellSurface->role() == PlasmaShellSurfaceInterface::Role::Normal); } bool ShellClient::isMovable() const { if (m_plasmaShellSurface) { return m_plasmaShellSurface->role() == PlasmaShellSurfaceInterface::Role::Normal; } if (m_xdgShellPopup) { return false; } return true; } bool ShellClient::isMovableAcrossScreens() const { if (m_plasmaShellSurface) { return m_plasmaShellSurface->role() == PlasmaShellSurfaceInterface::Role::Normal; } if (m_xdgShellPopup) { return false; } return true; } bool ShellClient::isResizable() const { if (m_plasmaShellSurface) { return m_plasmaShellSurface->role() == PlasmaShellSurfaceInterface::Role::Normal; } if (m_xdgShellPopup) { return false; } return true; } bool ShellClient::isShown(bool shaded_is_shown) const { Q_UNUSED(shaded_is_shown) return !m_closing && !m_unmapped && !isMinimized() && !m_hidden; } void ShellClient::hideClient(bool hide) { if (m_hidden == hide) { return; } m_hidden = hide; if (hide) { addWorkspaceRepaint(visibleRect()); workspace()->clientHidden(this); emit windowHidden(this); } else { emit windowShown(this); } } static bool changeMaximizeRecursion = false; void ShellClient::changeMaximize(bool horizontal, bool vertical, bool adjust) { if (changeMaximizeRecursion) { return; } if (!isResizable()) { return; } const QRect clientArea = isElectricBorderMaximizing() ? workspace()->clientArea(MaximizeArea, Cursor::pos(), desktop()) : workspace()->clientArea(MaximizeArea, this); const MaximizeMode oldMode = m_requestedMaximizeMode; const QRect oldGeometry = geometry(); StackingUpdatesBlocker blocker(workspace()); RequestGeometryBlocker geometryBlocker(this); // 'adjust == true' means to update the size only, e.g. after changing workspace size if (!adjust) { if (vertical) m_requestedMaximizeMode = MaximizeMode(m_requestedMaximizeMode ^ MaximizeVertical); if (horizontal) m_requestedMaximizeMode = MaximizeMode(m_requestedMaximizeMode ^ MaximizeHorizontal); } // TODO: add more checks as in Client if (m_requestedMaximizeMode == oldMode) { return; } // call into decoration update borders if (isDecorated() && decoration()->client() && !(options->borderlessMaximizedWindows() && m_requestedMaximizeMode == KWin::MaximizeFull)) { changeMaximizeRecursion = true; const auto c = decoration()->client().data(); if ((m_requestedMaximizeMode & MaximizeVertical) != (oldMode & MaximizeVertical)) { emit c->maximizedVerticallyChanged(m_requestedMaximizeMode & MaximizeVertical); } if ((m_requestedMaximizeMode & MaximizeHorizontal) != (oldMode & MaximizeHorizontal)) { emit c->maximizedHorizontallyChanged(m_requestedMaximizeMode & MaximizeHorizontal); } if ((m_requestedMaximizeMode == MaximizeFull) != (oldMode == MaximizeFull)) { emit c->maximizedChanged(m_requestedMaximizeMode & MaximizeFull); } changeMaximizeRecursion = false; } if (options->borderlessMaximizedWindows()) { // triggers a maximize change. // The next setNoBorder interation will exit since there's no change but the first recursion pullutes the restore geometry changeMaximizeRecursion = true; setNoBorder(rules()->checkNoBorder(m_requestedMaximizeMode == MaximizeFull)); changeMaximizeRecursion = false; } // Conditional quick tiling exit points const auto oldQuickTileMode = quickTileMode(); if (quickTileMode() != QuickTileMode(QuickTileFlag::None)) { if (oldMode == MaximizeFull && !clientArea.contains(m_geomMaximizeRestore.center())) { // Not restoring on the same screen // TODO: The following doesn't work for some reason //quick_tile_mode = QuickTileNone; // And exit quick tile mode manually } else if ((oldMode == MaximizeVertical && m_requestedMaximizeMode == MaximizeRestore) || (oldMode == MaximizeFull && m_requestedMaximizeMode == MaximizeHorizontal)) { // Modifying geometry of a tiled window updateQuickTileMode(QuickTileFlag::None); // Exit quick tile mode without restoring geometry } } // TODO: check rules if (m_requestedMaximizeMode == MaximizeFull) { m_geomMaximizeRestore = oldGeometry; // TODO: Client has more checks if (options->electricBorderMaximize()) { updateQuickTileMode(QuickTileFlag::Maximize); } else { updateQuickTileMode(QuickTileFlag::None); } if (quickTileMode() != oldQuickTileMode) { emit quickTileModeChanged(); } setGeometry(workspace()->clientArea(MaximizeArea, this)); workspace()->raiseClient(this); } else { if (m_requestedMaximizeMode == MaximizeRestore) { updateQuickTileMode(QuickTileFlag::None); } if (quickTileMode() != oldQuickTileMode) { emit quickTileModeChanged(); } if (m_geomMaximizeRestore.isValid()) { setGeometry(m_geomMaximizeRestore); } else { setGeometry(workspace()->clientArea(PlacementArea, this)); } } } MaximizeMode ShellClient::maximizeMode() const { return m_maximizeMode; } MaximizeMode ShellClient::requestedMaximizeMode() const { return m_requestedMaximizeMode; } bool ShellClient::noBorder() const { if (m_serverDecoration) { if (m_serverDecoration->mode() == ServerSideDecorationManagerInterface::Mode::Server) { return m_userNoBorder || isFullScreen(); } } if (m_xdgDecoration && m_xdgDecoration->requestedMode() != XdgDecorationInterface::Mode::ClientSide) { return m_userNoBorder || isFullScreen(); } return true; } bool ShellClient::isFullScreenable() const { if (!rules()->checkFullScreen(true)) { return false; } return !isSpecialWindow(); } void ShellClient::setFullScreen(bool set, bool user) { set = rules()->checkFullScreen(set); const bool wasFullscreen = isFullScreen(); if (wasFullscreen == set) { return; } if (isSpecialWindow()) { return; } if (user && !userCanSetFullScreen()) { return; } if (wasFullscreen) { workspace()->updateFocusMousePosition(Cursor::pos()); // may cause leave event } else { // in shell surface, maximise mode and fullscreen are exclusive // fullscreen->toplevel should restore the state we had before maximising if (m_shellSurface && m_maximizeMode == MaximizeMode::MaximizeFull) { m_geomFsRestore = m_geomMaximizeRestore; } else { m_geomFsRestore = geometry(); } } m_fullScreen = set; if (set) { untab(); workspace()->raiseClient(this); } RequestGeometryBlocker requestBlocker(this); StackingUpdatesBlocker blocker1(workspace()); GeometryUpdatesBlocker blocker2(this); workspace()->updateClientLayer(this); // active fullscreens get different layer updateDecoration(false, false); if (set) { setGeometry(workspace()->clientArea(FullScreenArea, this)); } else { if (m_geomFsRestore.isValid()) { int currentScreen = screen(); setGeometry(QRect(m_geomFsRestore.topLeft(), adjustedSize(m_geomFsRestore.size()))); if( currentScreen != screen()) workspace()->sendClientToScreen( this, currentScreen ); } else { // this can happen when the window was first shown already fullscreen, // so let the client set the size by itself setGeometry(QRect(workspace()->clientArea(PlacementArea, this).topLeft(), QSize(0, 0))); } } updateWindowRules(Rules::Fullscreen|Rules::Position|Rules::Size); emit fullScreenChanged(); } void ShellClient::setNoBorder(bool set) { if (!userCanSetNoBorder()) { return; } set = rules()->checkNoBorder(set); if (m_userNoBorder == set) { return; } m_userNoBorder = set; updateDecoration(true, false); updateWindowRules(Rules::NoBorder); } void ShellClient::setOnAllActivities(bool set) { Q_UNUSED(set) } void ShellClient::takeFocus() { if (rules()->checkAcceptFocus(wantsInput())) { if (m_xdgShellSurface) { const qint32 pingSerial = static_cast(m_xdgShellSurface->global())->ping(m_xdgShellSurface); m_pingSerials.insert(pingSerial, PingReason::FocusWindow); } setActive(true); } if (!keepAbove() && !isOnScreenDisplay() && !belongsToDesktop()) { workspace()->setShowingDesktop(false); } } void ShellClient::doSetActive() { if (!isActive()) { return; } StackingUpdatesBlocker blocker(workspace()); workspace()->focusToNull(); } bool ShellClient::userCanSetFullScreen() const { if (m_xdgShellSurface) { return true; } return false; } bool ShellClient::userCanSetNoBorder() const { if (m_serverDecoration && m_serverDecoration->mode() == ServerSideDecorationManagerInterface::Mode::Server) { return !isFullScreen() && !isShade() && !tabGroup(); } if (m_xdgDecoration && m_xdgDecoration->requestedMode() != XdgDecorationInterface::Mode::ClientSide) { return !isFullScreen() && !isShade() && !tabGroup(); } return false; } bool ShellClient::wantsInput() const { return rules()->checkAcceptFocus(acceptsFocus()); } bool ShellClient::acceptsFocus() const { if (waylandServer()->inputMethodConnection() == surface()->client()) { return false; } if (m_plasmaShellSurface) { if (m_plasmaShellSurface->role() == PlasmaShellSurfaceInterface::Role::OnScreenDisplay || m_plasmaShellSurface->role() == PlasmaShellSurfaceInterface::Role::ToolTip || m_plasmaShellSurface->role() == PlasmaShellSurfaceInterface::Role::Notification || m_plasmaShellSurface->role() == PlasmaShellSurfaceInterface::Role::CriticalNotification) { return false; } } if (m_closing) { // a closing window does not accept focus return false; } if (m_unmapped) { // an unmapped window does not accept focus return false; } if (m_shellSurface) { if (m_shellSurface->isPopup()) { return false; } return m_shellSurface->acceptsKeyboardFocus(); } if (m_xdgShellSurface) { // TODO: proper return true; } return false; } void ShellClient::createWindowId() { if (!m_internal) { m_windowId = waylandServer()->createWindowId(surface()); } } pid_t ShellClient::pid() const { return surface()->client()->processId(); } bool ShellClient::isLockScreen() const { return surface()->client() == waylandServer()->screenLockerClientConnection(); } bool ShellClient::isInputMethod() const { return surface()->client() == waylandServer()->inputMethodConnection(); } bool ShellClient::requestGeometry(const QRect &rect) { if (m_requestGeometryBlockCounter != 0) { m_blockedRequestGeometry = rect; return false; } QSize size; if (rect.isValid()) { size = toWindowGeometry(rect.size()); } else { size = QSize(0, 0); } m_requestedClientSize = size; quint64 serialId = 0; if (m_shellSurface && !size.isEmpty()) { m_shellSurface->requestSize(size); } if (m_xdgShellSurface) { serialId = m_xdgShellSurface->configure(xdgSurfaceStates(), size); } if (m_xdgShellPopup) { auto parent = transientFor(); if (parent) { const QPoint globalClientContentPos = parent->geometry().topLeft() + parent->clientPos(); const QPoint relativeOffset = rect.topLeft() - globalClientContentPos; serialId = m_xdgShellPopup->configure(QRect(relativeOffset, size)); } } if (rect.isValid()) { //if there's no requested size, then there's implicity no positional information worth using PendingConfigureRequest configureRequest; configureRequest.serialId = serialId; configureRequest.positionAfterResize = rect.topLeft(); configureRequest.maximizeMode = m_requestedMaximizeMode; m_pendingConfigureRequests.append(configureRequest); } m_blockedRequestGeometry = QRect(); return true; } void ShellClient::updatePendingGeometry() { QPoint position = geom.topLeft(); MaximizeMode maximizeMode = m_maximizeMode; for (auto it = m_pendingConfigureRequests.begin(); it != m_pendingConfigureRequests.end(); it++) { if (it->serialId > m_lastAckedConfigureRequest) { //this serial is not acked yet, therefore we know all future serials are not break; } if (it->serialId == m_lastAckedConfigureRequest) { if (position != it->positionAfterResize) { addLayerRepaint(geometry()); } position = it->positionAfterResize; maximizeMode = it->maximizeMode; m_pendingConfigureRequests.erase(m_pendingConfigureRequests.begin(), ++it); break; } //else serialId < m_lastAckedConfigureRequest and the state is now irrelevant and can be ignored } doSetGeometry(QRect(position, m_clientSize + QSize(borderLeft() + borderRight(), borderTop() + borderBottom()))); updateMaximizeMode(maximizeMode); } void ShellClient::clientFullScreenChanged(bool fullScreen) { setFullScreen(fullScreen, false); } void ShellClient::resizeWithChecks(int w, int h, ForceGeometry_t force) { Q_UNUSED(force) QRect area = workspace()->clientArea(WorkArea, this); // don't allow growing larger than workarea if (w > area.width()) { w = area.width(); } if (h > area.height()) { h = area.height(); } if (m_shellSurface) { m_shellSurface->requestSize(QSize(w, h)); } if (m_xdgShellSurface) { m_xdgShellSurface->configure(xdgSurfaceStates(), QSize(w, h)); } } void ShellClient::unmap() { m_unmapped = true; if (isMoveResize()) { leaveMoveResize(); } m_requestedClientSize = QSize(0, 0); destroyWindowManagementInterface(); if (Workspace::self()) { addWorkspaceRepaint(visibleRect()); workspace()->clientHidden(this); } emit windowHidden(this); } void ShellClient::installPlasmaShellSurface(PlasmaShellSurfaceInterface *surface) { m_plasmaShellSurface = surface; auto updatePosition = [this, surface] { QRect rect = QRect(surface->position(), m_clientSize + QSize(borderLeft() + borderRight(), borderTop() + borderBottom())); // Shell surfaces of internal windows are sometimes desync to current value. // Make sure to not set window geometry of internal windows to invalid values (bug 386304). // This is a workaround. if (!m_internal || rect.isValid()) { doSetGeometry(rect); } }; auto updateRole = [this, surface] { NET::WindowType type = NET::Unknown; switch (surface->role()) { case PlasmaShellSurfaceInterface::Role::Desktop: type = NET::Desktop; break; case PlasmaShellSurfaceInterface::Role::Panel: type = NET::Dock; break; case PlasmaShellSurfaceInterface::Role::OnScreenDisplay: type = NET::OnScreenDisplay; break; case PlasmaShellSurfaceInterface::Role::Notification: type = NET::Notification; break; case PlasmaShellSurfaceInterface::Role::ToolTip: type = NET::Tooltip; break; case PlasmaShellSurfaceInterface::Role::CriticalNotification: type = NET::CriticalNotification; break; case PlasmaShellSurfaceInterface::Role::Normal: default: type = NET::Normal; break; } if (type != m_windowType) { m_windowType = type; if (m_windowType == NET::Desktop || type == NET::Dock || type == NET::OnScreenDisplay || type == NET::Notification || type == NET::Tooltip || type == NET::CriticalNotification) { setOnAllDesktops(true); } workspace()->updateClientArea(); } }; connect(surface, &PlasmaShellSurfaceInterface::positionChanged, this, updatePosition); connect(surface, &PlasmaShellSurfaceInterface::roleChanged, this, updateRole); connect(surface, &PlasmaShellSurfaceInterface::panelBehaviorChanged, this, [this] { updateShowOnScreenEdge(); workspace()->updateClientArea(); } ); connect(surface, &PlasmaShellSurfaceInterface::panelAutoHideHideRequested, this, [this] { hideClient(true); m_plasmaShellSurface->hideAutoHidingPanel(); updateShowOnScreenEdge(); } ); connect(surface, &PlasmaShellSurfaceInterface::panelAutoHideShowRequested, this, [this] { hideClient(false); ScreenEdges::self()->reserve(this, ElectricNone); m_plasmaShellSurface->showAutoHidingPanel(); } ); updatePosition(); updateRole(); updateShowOnScreenEdge(); connect(this, &ShellClient::geometryChanged, this, &ShellClient::updateShowOnScreenEdge); setSkipTaskbar(surface->skipTaskbar()); connect(surface, &PlasmaShellSurfaceInterface::skipTaskbarChanged, this, [this] { setSkipTaskbar(m_plasmaShellSurface->skipTaskbar()); }); setSkipSwitcher(surface->skipSwitcher()); connect(surface, &PlasmaShellSurfaceInterface::skipSwitcherChanged, this, [this] { setSkipSwitcher(m_plasmaShellSurface->skipSwitcher()); }); } void ShellClient::updateShowOnScreenEdge() { if (!ScreenEdges::self()) { return; } if (m_unmapped || !m_plasmaShellSurface || m_plasmaShellSurface->role() != PlasmaShellSurfaceInterface::Role::Panel) { ScreenEdges::self()->reserve(this, ElectricNone); return; } if ((m_plasmaShellSurface->panelBehavior() == PlasmaShellSurfaceInterface::PanelBehavior::AutoHide && m_hidden) || m_plasmaShellSurface->panelBehavior() == PlasmaShellSurfaceInterface::PanelBehavior::WindowsCanCover) { // screen edge API requires an edge, thus we need to figure out which edge the window borders Qt::Edges edges; for (int i = 0; i < screens()->count(); i++) { const auto &screenGeo = screens()->geometry(i); if (screenGeo.x() == geom.x()) { edges |= Qt::LeftEdge; } if (screenGeo.x() + screenGeo.width() == geom.x() + geom.width()) { edges |= Qt::RightEdge; } if (screenGeo.y() == geom.y()) { edges |= Qt::TopEdge; } if (screenGeo.y() + screenGeo.height() == geom.y() + geom.height()) { edges |= Qt::BottomEdge; } } // a panel might border multiple screen edges. E.g. a horizontal panel at the bottom will // also border the left and right edge // let's remove such cases if (edges.testFlag(Qt::LeftEdge) && edges.testFlag(Qt::RightEdge)) { edges = edges & (~(Qt::LeftEdge | Qt::RightEdge)); } if (edges.testFlag(Qt::TopEdge) && edges.testFlag(Qt::BottomEdge)) { edges = edges & (~(Qt::TopEdge | Qt::BottomEdge)); } // it's still possible that a panel borders two edges, e.g. bottom and left // in that case the one which is sharing more with the edge wins auto check = [this](Qt::Edges edges, Qt::Edge horiz, Qt::Edge vert) { if (edges.testFlag(horiz) && edges.testFlag(vert)) { if (geom.width() >= geom.height()) { return edges & ~horiz; } else { return edges & ~vert; } } return edges; }; edges = check(edges, Qt::LeftEdge, Qt::TopEdge); edges = check(edges, Qt::LeftEdge, Qt::BottomEdge); edges = check(edges, Qt::RightEdge, Qt::TopEdge); edges = check(edges, Qt::RightEdge, Qt::BottomEdge); ElectricBorder border = ElectricNone; if (edges.testFlag(Qt::LeftEdge)) { border = ElectricLeft; } if (edges.testFlag(Qt::RightEdge)) { border = ElectricRight; } if (edges.testFlag(Qt::TopEdge)) { border = ElectricTop; } if (edges.testFlag(Qt::BottomEdge)) { border = ElectricBottom; } ScreenEdges::self()->reserve(this, border); } else { ScreenEdges::self()->reserve(this, ElectricNone); } } bool ShellClient::isInitialPositionSet() const { if (m_plasmaShellSurface) { return m_plasmaShellSurface->isPositionSet(); } return false; } void ShellClient::installAppMenu(AppMenuInterface *menu) { m_appMenuInterface = menu; auto updateMenu = [this](AppMenuInterface::InterfaceAddress address) { updateApplicationMenuServiceName(address.serviceName); updateApplicationMenuObjectPath(address.objectPath); }; connect(m_appMenuInterface, &AppMenuInterface::addressChanged, this, [=](AppMenuInterface::InterfaceAddress address) { updateMenu(address); }); updateMenu(menu->address()); } void ShellClient::installPalette(ServerSideDecorationPaletteInterface *palette) { m_paletteInterface = palette; auto updatePalette = [this](const QString &palette) { AbstractClient::updateColorScheme(rules()->checkDecoColor(palette)); }; connect(m_paletteInterface, &ServerSideDecorationPaletteInterface::paletteChanged, this, [=](const QString &palette) { updatePalette(palette); }); connect(m_paletteInterface, &QObject::destroyed, this, [=]() { updatePalette(QString()); }); updatePalette(palette->palette()); } void ShellClient::updateColorScheme() { if (m_paletteInterface) { AbstractClient::updateColorScheme(rules()->checkDecoColor(m_paletteInterface->palette())); } else { AbstractClient::updateColorScheme(rules()->checkDecoColor(QString())); } } void ShellClient::updateMaximizeMode(MaximizeMode maximizeMode) { if (maximizeMode == m_maximizeMode) { return; } m_maximizeMode = maximizeMode; emit clientMaximizedStateChanged(this, m_maximizeMode); emit clientMaximizedStateChanged(this, m_maximizeMode & MaximizeHorizontal, m_maximizeMode & MaximizeVertical); } bool ShellClient::hasStrut() const { if (!isShown(true)) { return false; } if (!m_plasmaShellSurface) { return false; } if (m_plasmaShellSurface->role() != PlasmaShellSurfaceInterface::Role::Panel) { return false; } return m_plasmaShellSurface->panelBehavior() == PlasmaShellSurfaceInterface::PanelBehavior::AlwaysVisible; } void ShellClient::updateIcon() { const QString waylandIconName = QStringLiteral("wayland"); const QString dfIconName = iconFromDesktopFile(); const QString iconName = dfIconName.isEmpty() ? waylandIconName : dfIconName; if (iconName == icon().name()) { return; } setIcon(QIcon::fromTheme(iconName)); } bool ShellClient::isTransient() const { return m_transient; } void ShellClient::setTransient() { SurfaceInterface *s = nullptr; if (m_shellSurface) { s = m_shellSurface->transientFor().data(); } if (m_xdgShellSurface) { if (auto transient = m_xdgShellSurface->transientFor().data()) { s = transient->surface(); } } if (m_xdgShellPopup) { s = m_xdgShellPopup->transientFor().data(); } if (!s) { s = waylandServer()->findForeignTransientForSurface(surface()); } auto t = waylandServer()->findClient(s); if (t != transientFor()) { // remove from main client if (transientFor()) transientFor()->removeTransient(this); setTransientFor(t); if (t) { t->addTransient(this); } } m_transient = (s != nullptr); } bool ShellClient::hasTransientPlacementHint() const { return isTransient() && transientFor() != nullptr && (m_shellSurface || m_xdgShellPopup); } QRect ShellClient::transientPlacement(const QRect &bounds) const { QRect anchorRect; Qt::Edges anchorEdge; Qt::Edges gravity; QPoint offset; PositionerConstraints constraintAdjustments; QSize size = geometry().size(); const QPoint parentClientPos = transientFor()->pos() + transientFor()->clientPos(); QRect popupPosition; // returns if a target is within the supplied bounds, optional edges argument states which side to check auto inBounds = [bounds](const QRect &target, Qt::Edges edges = Qt::LeftEdge | Qt::RightEdge | Qt::TopEdge | Qt::BottomEdge) -> bool { if (edges & Qt::LeftEdge && target.left() < bounds.left()) { return false; } if (edges & Qt::TopEdge && target.top() < bounds.top()) { return false; } if (edges & Qt::RightEdge && target.right() > bounds.right()) { //normal QRect::right issue cancels out return false; } if (edges & Qt::BottomEdge && target.bottom() > bounds.bottom()) { return false; } return true; }; if (m_shellSurface) { anchorRect = QRect(m_shellSurface->transientOffset(), QSize(1,1)); anchorEdge = Qt::TopEdge | Qt::LeftEdge; gravity = Qt::BottomEdge | Qt::RightEdge; //our single point represents the top left of the popup constraintAdjustments = (PositionerConstraint::SlideX | PositionerConstraint::SlideY); } else if (m_xdgShellPopup) { anchorRect = m_xdgShellPopup->anchorRect(); anchorEdge = m_xdgShellPopup->anchorEdge(); gravity = m_xdgShellPopup->gravity(); offset = m_xdgShellPopup->anchorOffset(); constraintAdjustments = m_xdgShellPopup->constraintAdjustments(); if (!size.isValid()) { size = m_xdgShellPopup->initialSize(); } } else { Q_UNREACHABLE(); } //initial position popupPosition = QRect(popupOffset(anchorRect, anchorEdge, gravity, size) + offset + parentClientPos, size); //if that fits, we don't need to do anything if (inBounds(popupPosition)) { return popupPosition; } //otherwise apply constraint adjustment per axis in order XDG Shell Popup states if (constraintAdjustments & PositionerConstraint::FlipX) { if (!inBounds(popupPosition, Qt::LeftEdge | Qt::RightEdge)) { //flip both edges (if either bit is set, XOR both) auto flippedAnchorEdge = anchorEdge; if (flippedAnchorEdge & (Qt::LeftEdge | Qt::RightEdge)) { flippedAnchorEdge ^= (Qt::LeftEdge | Qt::RightEdge); } auto flippedGravity = gravity; if (flippedGravity & (Qt::LeftEdge | Qt::RightEdge)) { flippedGravity ^= (Qt::LeftEdge | Qt::RightEdge); } auto flippedPopupPosition = QRect(popupOffset(anchorRect, flippedAnchorEdge, flippedGravity, size) + offset + parentClientPos, size); //if it still doesn't fit we should continue with the unflipped version if (inBounds(flippedPopupPosition, Qt::LeftEdge | Qt::RightEdge)) { popupPosition.moveLeft(flippedPopupPosition.x()); } } } if (constraintAdjustments & PositionerConstraint::SlideX) { if (!inBounds(popupPosition, Qt::LeftEdge)) { popupPosition.moveLeft(bounds.x()); } if (!inBounds(popupPosition, Qt::RightEdge)) { // moveRight suffers from the classic QRect off by one issue popupPosition.moveLeft(bounds.x() + bounds.width() - size.width()); } } if (constraintAdjustments & PositionerConstraint::ResizeX) { //TODO //but we need to sort out when this is run as resize should only happen before first configure } if (constraintAdjustments & PositionerConstraint::FlipY) { if (!inBounds(popupPosition, Qt::TopEdge | Qt::BottomEdge)) { //flip both edges (if either bit is set, XOR both) auto flippedAnchorEdge = anchorEdge; if (flippedAnchorEdge & (Qt::TopEdge | Qt::BottomEdge)) { flippedAnchorEdge ^= (Qt::TopEdge | Qt::BottomEdge); } auto flippedGravity = gravity; if (flippedGravity & (Qt::TopEdge | Qt::BottomEdge)) { flippedGravity ^= (Qt::TopEdge | Qt::BottomEdge); } auto flippedPopupPosition = QRect(popupOffset(anchorRect, flippedAnchorEdge, flippedGravity, size) + offset + parentClientPos, size); //if it still doesn't fit we should continue with the unflipped version if (inBounds(flippedPopupPosition, Qt::TopEdge | Qt::BottomEdge)) { popupPosition.moveTop(flippedPopupPosition.y()); } } } if (constraintAdjustments & PositionerConstraint::SlideY) { if (!inBounds(popupPosition, Qt::TopEdge)) { popupPosition.moveTop(bounds.y()); } if (!inBounds(popupPosition, Qt::BottomEdge)) { popupPosition.moveTop(bounds.y() + bounds.height() - size.height()); } } if (constraintAdjustments & PositionerConstraint::ResizeY) { //TODO } return popupPosition; } QPoint ShellClient::popupOffset(const QRect &anchorRect, const Qt::Edges anchorEdge, const Qt::Edges gravity, const QSize popupSize) const { QPoint anchorPoint; switch (anchorEdge & (Qt::LeftEdge | Qt::RightEdge)) { case Qt::LeftEdge: anchorPoint.setX(anchorRect.x()); break; case Qt::RightEdge: anchorPoint.setX(anchorRect.x() + anchorRect.width()); break; default: anchorPoint.setX(qRound(anchorRect.x() + anchorRect.width() / 2.0)); } switch (anchorEdge & (Qt::TopEdge | Qt::BottomEdge)) { case Qt::TopEdge: anchorPoint.setY(anchorRect.y()); break; case Qt::BottomEdge: anchorPoint.setY(anchorRect.y() + anchorRect.height()); break; default: anchorPoint.setY(qRound(anchorRect.y() + anchorRect.height() / 2.0)); } // calculate where the top left point of the popup will end up with the applied gravity // gravity indicates direction. i.e if gravitating towards the top the popup's bottom edge // will next to the anchor point QPoint popupPosAdjust; switch (gravity & (Qt::LeftEdge | Qt::RightEdge)) { case Qt::LeftEdge: popupPosAdjust.setX(-popupSize.width()); break; case Qt::RightEdge: popupPosAdjust.setX(0); break; default: popupPosAdjust.setX(qRound(-popupSize.width() / 2.0)); } switch (gravity & (Qt::TopEdge | Qt::BottomEdge)) { case Qt::TopEdge: popupPosAdjust.setY(-popupSize.height()); break; case Qt::BottomEdge: popupPosAdjust.setY(0); break; default: popupPosAdjust.setY(qRound(-popupSize.height() / 2.0)); } return anchorPoint + popupPosAdjust; } bool ShellClient::isWaitingForMoveResizeSync() const { if (m_shellSurface) { return !m_pendingConfigureRequests.isEmpty(); } return false; } void ShellClient::doResizeSync() { requestGeometry(moveResizeGeometry()); } QMatrix4x4 ShellClient::inputTransformation() const { QMatrix4x4 m = Toplevel::inputTransformation(); m.translate(-borderLeft(), -borderTop()); return m; } void ShellClient::installServerSideDecoration(KWayland::Server::ServerSideDecorationInterface *deco) { if (m_serverDecoration == deco) { return; } m_serverDecoration = deco; connect(m_serverDecoration, &ServerSideDecorationInterface::destroyed, this, [this] { m_serverDecoration = nullptr; if (m_closing || !Workspace::self()) { return; } if (!m_unmapped) { // maybe delay to next event cycle in case the ShellClient is getting destroyed, too updateDecoration(true); } } ); if (!m_unmapped) { updateDecoration(true); } connect(m_serverDecoration, &ServerSideDecorationInterface::modeRequested, this, [this] (ServerSideDecorationManagerInterface::Mode mode) { const bool changed = mode != m_serverDecoration->mode(); if (changed && !m_unmapped) { updateDecoration(false); } } ); } void ShellClient::installXdgDecoration(XdgDecorationInterface *deco) { Q_ASSERT(m_xdgShellSurface); m_xdgDecoration = deco; connect(m_xdgDecoration, &QObject::destroyed, this, [this] { m_xdgDecoration = nullptr; if (m_closing || !Workspace::self()) { return; } updateDecoration(true); } ); connect(m_xdgDecoration, &XdgDecorationInterface::modeRequested, this, [this] () { //force is true as we must send a new configure response updateDecoration(false, true); }); } bool ShellClient::shouldExposeToWindowManagement() { if (m_internal) { return false; } if (isLockScreen()) { return false; } if (m_xdgShellPopup) { return false; } if (m_shellSurface) { if (m_shellSurface->isTransient() && !m_shellSurface->acceptsKeyboardFocus()) { return false; } } return true; } KWayland::Server::XdgShellSurfaceInterface::States ShellClient::xdgSurfaceStates() const { XdgShellSurfaceInterface::States states; if (isActive()) { states |= XdgShellSurfaceInterface::State::Activated; } if (isFullScreen()) { states |= XdgShellSurfaceInterface::State::Fullscreen; } if (m_requestedMaximizeMode == MaximizeMode::MaximizeFull) { states |= XdgShellSurfaceInterface::State::Maximized; } if (isResize()) { states |= XdgShellSurfaceInterface::State::Resizing; } return states; } void ShellClient::doMinimize() { if (isMinimized()) { workspace()->clientHidden(this); } else { emit windowShown(this); } workspace()->updateMinimizedOfTransients(this); } bool ShellClient::setupCompositing() { if (m_compositingSetup) { return true; } m_compositingSetup = Toplevel::setupCompositing(); return m_compositingSetup; } void ShellClient::finishCompositing(ReleaseReason releaseReason) { m_compositingSetup = false; Toplevel::finishCompositing(releaseReason); } void ShellClient::placeIn(QRect &area) { Placement::self()->place(this, area); setGeometryRestore(geometry()); } void ShellClient::showOnScreenEdge() { if (!m_plasmaShellSurface || m_unmapped) { return; } hideClient(false); workspace()->raiseClient(this); if (m_plasmaShellSurface->panelBehavior() == PlasmaShellSurfaceInterface::PanelBehavior::AutoHide) { m_plasmaShellSurface->showAutoHidingPanel(); } } bool ShellClient::dockWantsInput() const { if (m_plasmaShellSurface) { if (m_plasmaShellSurface->role() == PlasmaShellSurfaceInterface::Role::Panel) { return m_plasmaShellSurface->panelTakesFocus(); } } return false; } void ShellClient::killWindow() { if (!surface()) { return; } auto c = surface()->client(); if (c->processId() == getpid() || c->processId() == 0) { c->destroy(); return; } ::kill(c->processId(), SIGTERM); // give it time to terminate and only if terminate fails, try destroy Wayland connection QTimer::singleShot(5000, c, &ClientConnection::destroy); } bool ShellClient::hasPopupGrab() const { return m_hasPopupGrab; } void ShellClient::popupDone() { if (m_shellSurface) { m_shellSurface->popupDone(); } if (m_xdgShellPopup) { m_xdgShellPopup->popupDone(); } } void ShellClient::updateClientOutputs() { QVector clientOutputs; const auto outputs = waylandServer()->display()->outputs(); for (OutputInterface* output: qAsConst(outputs)) { const QRect outputGeom(output->globalPosition(), output->pixelSize() / output->scale()); if (geometry().intersects(outputGeom)) { clientOutputs << output; } } surface()->setOutputs(clientOutputs); } void ShellClient::updateWindowMargins() { QRect windowGeometry; QSize clientSize = m_clientSize; if (m_xdgShellSurface) { windowGeometry = m_xdgShellSurface->windowGeometry(); } else if (m_xdgShellPopup) { windowGeometry = m_xdgShellPopup->windowGeometry(); if (!clientSize.isValid()) { clientSize = m_xdgShellPopup->initialSize(); } } else { return; } if (windowGeometry.isEmpty() || windowGeometry.width() > clientSize.width() || windowGeometry.height() > clientSize.height()) { m_windowMargins = QMargins(); } else { m_windowMargins = QMargins(windowGeometry.left(), windowGeometry.top(), clientSize.width() - (windowGeometry.right() + 1), clientSize.height() - (windowGeometry.bottom() + 1)); } } bool ShellClient::isPopupWindow() const { if (Toplevel::isPopupWindow()) { return true; } if (m_shellSurface != nullptr) { return m_shellSurface->isPopup(); } if (m_xdgShellPopup != nullptr) { return true; } return false; } QWindow *ShellClient::internalWindow() const { return nullptr; } bool ShellClient::supportsWindowRules() const { if (m_plasmaShellSurface) { return false; } return m_xdgShellSurface; } } diff --git a/sm.cpp b/sm.cpp index dd4cc89f0..cb39bec8e 100644 --- a/sm.cpp +++ b/sm.cpp @@ -1,533 +1,533 @@ /******************************************************************** 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 #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; } 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)); // KConfig doesn't support long so we need to live with less precision on 64-bit systems cg.writeEntry(QLatin1String("tabGroup") + n, static_cast(reinterpret_cast(c->tabGroup()))); 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->tabGroup = cg.readEntry(QLatin1String("tabGroup") + n, 0); info->tabGroupClient = NULL; 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; 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); } } } } // Set tabGroupClient for other clients in the same group if (realInfo && realInfo->tabGroup) { foreach (SessionInfo * info, session) { if (!info->tabGroupClient && info->tabGroup == realInfo->tabGroup) info->tabGroupClient = c; } } 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 err[ 11 ]; conn = SmcOpenConnection(NULL, 0, 1, 0, SmcSaveYourselfProcMask | SmcDieProcMask | SmcSaveCompleteProcMask | SmcShutdownCancelledProcMask, &calls, NULL, &id, 10, err); if (id != NULL) free(id); if (conn == NULL) 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 : ""); 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) { delete notifier; SmcCloseConnection(conn, 0, NULL); } conn = NULL; } void SessionSaveDoneHelper::processData() { if (conn != NULL) IceProcessMessages(SmcGetIceConnection(conn), 0, 0); } void Workspace::sessionSaveDone() { session_saving = false; foreach (Client * c, clients) { c->setSessionActivityOverride(false); } } } // namespace diff --git a/tabbox/clientmodel.cpp b/tabbox/clientmodel.cpp index 7a7625095..048677f18 100644 --- a/tabbox/clientmodel.cpp +++ b/tabbox/clientmodel.cpp @@ -1,264 +1,264 @@ /******************************************************************** 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 . *********************************************************************/ // own #include "clientmodel.h" // tabbox #include "tabboxconfig.h" #include "tabboxhandler.h" // Qt #include #include // TODO: remove with Qt 5, only for HTML escaping the caption #include #include // other -#include +#include namespace KWin { namespace TabBox { ClientModel::ClientModel(QObject* parent) : QAbstractItemModel(parent) { QHash roles; roles[CaptionRole] = "caption"; roles[DesktopNameRole] = "desktopName"; roles[MinimizedRole] = "minimized"; roles[WIdRole] = "windowId"; roles[CloseableRole] = "closeable"; roles[IconRole] = "icon"; setRoleNames(roles); } ClientModel::~ClientModel() { } QVariant ClientModel::data(const QModelIndex& index, int role) const { if (!index.isValid()) return QVariant(); if (m_clientList.isEmpty()) { return QVariant(); } int clientIndex = index.row(); if (clientIndex >= m_clientList.count()) return QVariant(); QSharedPointer client = m_clientList[ clientIndex ].toStrongRef(); if (!client) { return QVariant(); } switch(role) { case Qt::DisplayRole: case CaptionRole: { QString caption = client->caption(); if (Qt::mightBeRichText(caption)) { caption = Qt::escape(caption); } return caption; } case ClientRole: return qVariantFromValue((void*)client.data()); case DesktopNameRole: { return tabBox->desktopName(client.data()); } case WIdRole: return client->internalId(); case MinimizedRole: return client->isMinimized(); case CloseableRole: //clients that claim to be first are not closeable return client->isCloseable() && !client->isFirstInTabBox(); case IconRole: return client->icon(); default: return QVariant(); } } QString ClientModel::longestCaption() const { QString caption; foreach (const QWeakPointer &clientPointer, m_clientList) { QSharedPointer client = clientPointer.toStrongRef(); if (!client) { continue; } if (client->caption().size() > caption.size()) { caption = client->caption(); } } return caption; } int ClientModel::columnCount(const QModelIndex& parent) const { Q_UNUSED(parent) return 1; } int ClientModel::rowCount(const QModelIndex& parent) const { if (parent.isValid()) { return 0; } return m_clientList.count(); } QModelIndex ClientModel::parent(const QModelIndex& child) const { Q_UNUSED(child) return QModelIndex(); } QModelIndex ClientModel::index(int row, int column, const QModelIndex& parent) const { if (row < 0 || column != 0 || parent.isValid()) { return QModelIndex(); } int index = row * columnCount(); if (index >= m_clientList.count() && !m_clientList.isEmpty()) return QModelIndex(); return createIndex(row, 0); } QModelIndex ClientModel::index(QWeakPointer client) const { if (!m_clientList.contains(client)) return QModelIndex(); int index = m_clientList.indexOf(client); int row = index / columnCount(); int column = index % columnCount(); return createIndex(row, column); } void ClientModel::createClientList(bool partialReset) { createClientList(tabBox->currentDesktop(), partialReset); } void ClientModel::createClientList(int desktop, bool partialReset) { TabBoxClient* start = tabBox->activeClient().toStrongRef().data(); // TODO: new clients are not added at correct position if (partialReset && !m_clientList.isEmpty()) { QSharedPointer firstClient = m_clientList.first().toStrongRef(); if (firstClient) { start = firstClient.data(); } } m_clientList.clear(); QList< QWeakPointer< TabBoxClient > > stickyClients; switch(tabBox->config().clientSwitchingMode()) { case TabBoxConfig::FocusChainSwitching: { TabBoxClient* c = start; if (!tabBox->isInFocusChain(c)) { QSharedPointer firstClient = tabBox->firstClientFocusChain().toStrongRef(); if (firstClient) { c = firstClient.data(); } } TabBoxClient* stop = c; do { QWeakPointer add = tabBox->clientToAddToList(c, desktop); if (!add.isNull()) { m_clientList += add; if (add.data()->isFirstInTabBox()) { stickyClients << add; } } c = tabBox->nextClientFocusChain(c).data(); } while (c && c != stop); break; } case TabBoxConfig::StackingOrderSwitching: { // TODO: needs improvement TabBoxClientList stacking = tabBox->stackingOrder(); TabBoxClient* c = stacking.first().data(); TabBoxClient* stop = c; int index = 0; while (c) { QWeakPointer add = tabBox->clientToAddToList(c, desktop); if (!add.isNull()) { if (start == add.data()) { m_clientList.removeAll(add); m_clientList.prepend(add); } else m_clientList += add; if (add.data()->isFirstInTabBox()) { stickyClients << add; } } if (index >= stacking.size() - 1) { c = nullptr; } else { c = stacking[++index].data(); } if (c == stop) break; } break; } } foreach (const QWeakPointer< TabBoxClient > &c, stickyClients) { m_clientList.removeAll(c); m_clientList.prepend(c); } if (tabBox->config().clientApplicationsMode() != TabBoxConfig::AllWindowsCurrentApplication && (tabBox->config().showDesktopMode() == TabBoxConfig::ShowDesktopClient || m_clientList.isEmpty())) { QWeakPointer desktopClient = tabBox->desktopClient(); if (!desktopClient.isNull()) m_clientList.append(desktopClient); } reset(); } void ClientModel::close(int i) { QModelIndex ind = index(i, 0); if (!ind.isValid()) { return; } QSharedPointer client = m_clientList.at(i).toStrongRef(); if (client) { client->close(); } } void ClientModel::activate(int i) { QModelIndex ind = index(i, 0); if (!ind.isValid()) { return; } tabBox->setCurrentIndex(ind); tabBox->activateAndClose(); } } // namespace Tabbox } // namespace KWin diff --git a/tabbox/desktopmodel.cpp b/tabbox/desktopmodel.cpp index dbb4a7014..41949ebf2 100644 --- a/tabbox/desktopmodel.cpp +++ b/tabbox/desktopmodel.cpp @@ -1,183 +1,183 @@ /******************************************************************** 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 . *********************************************************************/ // own #include "desktopmodel.h" // tabbox #include "clientmodel.h" #include "tabboxconfig.h" #include "tabboxhandler.h" -#include +#include namespace KWin { namespace TabBox { DesktopModel::DesktopModel(QObject* parent) : QAbstractItemModel(parent) { QHash roleNames; roleNames.insert(Qt::DisplayRole, "display"); roleNames.insert(DesktopNameRole, "caption"); roleNames.insert(DesktopRole, "desktop"); roleNames.insert(ClientModelRole, "client"); setRoleNames(roleNames); } DesktopModel::~DesktopModel() { } QVariant DesktopModel::data(const QModelIndex& index, int role) const { if (!index.isValid() || index.column() != 0) return QVariant(); if (index.parent().isValid()) { // parent is valid -> access to Client ClientModel *model = m_clientModels[ m_desktopList[ index.internalId() - 1] ]; return model->data(model->index(index.row(), 0), role); } const int desktopIndex = index.row(); if (desktopIndex >= m_desktopList.count()) return QVariant(); switch(role) { case Qt::DisplayRole: case DesktopNameRole: return tabBox->desktopName(m_desktopList[ desktopIndex ]); case DesktopRole: return m_desktopList[ desktopIndex ]; case ClientModelRole: return qVariantFromValue((void*)m_clientModels[ m_desktopList[ desktopIndex ] ]); default: return QVariant(); } } QString DesktopModel::longestCaption() const { QString caption; for (int desktop : m_desktopList) { QString desktopName = tabBox->desktopName(desktop); if (desktopName.size() > caption.size()) { caption = desktopName; } } return caption; } int DesktopModel::columnCount(const QModelIndex& parent) const { Q_UNUSED(parent) return 1; } int DesktopModel::rowCount(const QModelIndex& parent) const { if (parent.isValid()) { if (parent.internalId() != 0 || parent.row() >= m_desktopList.count()) { return 0; } const int desktop = m_desktopList.at(parent.row()); const ClientModel *model = m_clientModels.value(desktop); return model->rowCount(); } return m_desktopList.count(); } QModelIndex DesktopModel::parent(const QModelIndex& child) const { if (!child.isValid() || child.internalId() == 0) { return QModelIndex(); } const int row = child.internalId() -1; if (row >= m_desktopList.count()) { return QModelIndex(); } return createIndex(row, 0); } QModelIndex DesktopModel::index(int row, int column, const QModelIndex& parent) const { if (column != 0) { return QModelIndex(); } if (row < 0) { return QModelIndex(); } if (parent.isValid()) { if (parent.row() < 0 || parent.row() >= m_desktopList.count() || parent.internalId() != 0) { return QModelIndex(); } const int desktop = m_desktopList.at(parent.row()); const ClientModel *model = m_clientModels.value(desktop); if (row >= model->rowCount()) { return QModelIndex(); } return createIndex(row, column, parent.row() + 1); } if (row > m_desktopList.count() || m_desktopList.isEmpty()) return QModelIndex(); return createIndex(row, column); } QModelIndex DesktopModel::desktopIndex(int desktop) const { if (desktop > m_desktopList.count()) return QModelIndex(); return createIndex(m_desktopList.indexOf(desktop), 0); } void DesktopModel::createDesktopList() { beginResetModel(); m_desktopList.clear(); qDeleteAll(m_clientModels); m_clientModels.clear(); switch(tabBox->config().desktopSwitchingMode()) { case TabBoxConfig::MostRecentlyUsedDesktopSwitching: { int desktop = tabBox->currentDesktop(); do { m_desktopList.append(desktop); ClientModel* clientModel = new ClientModel(this); clientModel->createClientList(desktop); m_clientModels.insert(desktop, clientModel); desktop = tabBox->nextDesktopFocusChain(desktop); } while (desktop != tabBox->currentDesktop()); break; } case TabBoxConfig::StaticDesktopSwitching: { for (int i = 1; i <= tabBox->numberOfDesktops(); i++) { m_desktopList.append(i); ClientModel* clientModel = new ClientModel(this); clientModel->createClientList(i); m_clientModels.insert(i, clientModel); } break; } } endResetModel(); } } // namespace Tabbox } // namespace KWin diff --git a/tests/normalhintsbasesizetest.cpp b/tests/normalhintsbasesizetest.cpp index 54ac0e448..ed0136c93 100644 --- a/tests/normalhintsbasesizetest.cpp +++ b/tests/normalhintsbasesizetest.cpp @@ -1,117 +1,117 @@ /* * 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 +#include #include #include #include /* * This is a small test app to ensure that KWin calculates the size of a window correctly * according to ICCCM section 4.1.2.3 * * The application creates a window and specifies the normal hints with: * * min size * * base size * * size increment * * With these normal flags the size should be calculated as: * width = base_width + (i * width_inc) * height = base_height + (j * height_inc) * * With i and j being non-negative integers! * * This application waits for configure notify events and calculates the i and j and * tries to calculate the size it expects. If it doesn't match it exits with a non-zero * exit code and prints the mismatching i and/or j value to stderr. * * To simply quit the application just click into the window. This will return with exit code 0. */ int main(int, char **) { int screenNumber; xcb_connection_t *c = xcb_connect(nullptr, &screenNumber); auto getScreen = [=]() { const xcb_setup_t *setup = xcb_get_setup(c); auto it = xcb_setup_roots_iterator (setup); for (int i = 0; i < screenNumber; ++i) { xcb_screen_next(&it); } return it.data; }; xcb_screen_t *screen = getScreen(); xcb_window_t w = xcb_generate_id(c); const uint32_t values[2] = { screen->white_pixel, XCB_EVENT_MASK_BUTTON_PRESS | XCB_EVENT_MASK_BUTTON_RELEASE | XCB_EVENT_MASK_STRUCTURE_NOTIFY }; xcb_create_window(c, 0, w, screen->root, 0, 0, 365, 104, 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, screen->root_visual, XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK, values); // set the normal hints xcb_size_hints_t hints; hints.flags = XCB_ICCCM_SIZE_HINT_P_MIN_SIZE | XCB_ICCCM_SIZE_HINT_BASE_SIZE | XCB_ICCCM_SIZE_HINT_P_RESIZE_INC; hints.min_width = 365; hints.min_height = 104; hints.base_width = 15; hints.base_height = 64; hints.width_inc = 9; hints.height_inc = 18; xcb_icccm_set_wm_normal_hints(c, w, &hints); // and map the window xcb_map_window(c, w); xcb_flush(c); bool error = false; while (xcb_generic_event_t *event = xcb_wait_for_event(c)) { bool exit = false; if ((event->response_type & ~0x80) == XCB_BUTTON_RELEASE) { exit = true; } else if ((event->response_type & ~0x80) == XCB_CONFIGURE_NOTIFY) { auto *ce = reinterpret_cast(event); const double i = (ce->width - hints.base_width) / (double)hints.width_inc; const double j = (ce->height - hints.base_height) / (double)hints.height_inc; // according to ICCCM the size should be: // width = base_width + (i * width_inc) // height = base_height + (j * height_inc) // thus if the window manager configured correctly we get the same result if (hints.base_width + (int(i) * hints.width_inc) != ce->width) { std::cerr << "Incorrect width - i factor is " << i << std::endl; exit = true; error = true; } if (hints.base_height + (int(j) * hints.height_inc) != ce->height) { std::cerr << "Incorrect height - j factor is " << i << std::endl; exit = true; error = true; } } free(event); if (exit) { break; } } xcb_disconnect(c); return error ? 1 : 0; } diff --git a/toplevel.h b/toplevel.h index 97e90f206..fe32ae056 100644 --- a/toplevel.h +++ b/toplevel.h @@ -1,959 +1,958 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2006 Lubos Lunak This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #ifndef KWIN_TOPLEVEL_H #define KWIN_TOPLEVEL_H // kwin #include "input.h" #include "utils.h" #include "virtualdesktops.h" #include "xcbutils.h" // KDE #include // Qt #include #include #include // xcb #include #include // XLib #include #include -// system -#include // c++ +#include #include class QOpenGLFramebufferObject; namespace KWayland { namespace Server { class SurfaceInterface; } } namespace KWin { class ClientMachine; class EffectWindowImpl; class Shadow; /** * Enum to describe the reason why a Toplevel has to be released. **/ enum class ReleaseReason { Release, ///< Normal Release after e.g. an Unmap notify event (window still valid) Destroyed, ///< Release after an Destroy notify event (window no longer valid) KWinShutsDown ///< Release on KWin Shutdown (window still valid) }; class KWIN_EXPORT Toplevel : public QObject { Q_OBJECT Q_PROPERTY(bool alpha READ hasAlpha NOTIFY hasAlphaChanged) Q_PROPERTY(qulonglong frameId READ frameId) Q_PROPERTY(QRect geometry READ geometry NOTIFY geometryChanged) Q_PROPERTY(QRect visibleRect READ visibleRect) Q_PROPERTY(int height READ height) Q_PROPERTY(qreal opacity READ opacity WRITE setOpacity NOTIFY opacityChanged) Q_PROPERTY(QPoint pos READ pos) Q_PROPERTY(int screen READ screen NOTIFY screenChanged) Q_PROPERTY(QSize size READ size) Q_PROPERTY(int width READ width) Q_PROPERTY(qulonglong windowId READ windowId CONSTANT) Q_PROPERTY(int x READ x) Q_PROPERTY(int y READ y) Q_PROPERTY(int desktop READ desktop) /** * Whether the window is on all desktops. That is desktop is -1. **/ Q_PROPERTY(bool onAllDesktops READ isOnAllDesktops) Q_PROPERTY(QRect rect READ rect) Q_PROPERTY(QPoint clientPos READ clientPos) Q_PROPERTY(QSize clientSize READ clientSize) Q_PROPERTY(QByteArray resourceName READ resourceName NOTIFY windowClassChanged) Q_PROPERTY(QByteArray resourceClass READ resourceClass NOTIFY windowClassChanged) Q_PROPERTY(QByteArray windowRole READ windowRole NOTIFY windowRoleChanged) /** * Returns whether the window is a desktop background window (the one with wallpaper). * See _NET_WM_WINDOW_TYPE_DESKTOP at https://standards.freedesktop.org/wm-spec/wm-spec-latest.html . **/ Q_PROPERTY(bool desktopWindow READ isDesktop) /** * Returns whether the window is a dock (i.e. a panel). * See _NET_WM_WINDOW_TYPE_DOCK at https://standards.freedesktop.org/wm-spec/wm-spec-latest.html . **/ Q_PROPERTY(bool dock READ isDock) /** * Returns whether the window is a standalone (detached) toolbar window. * See _NET_WM_WINDOW_TYPE_TOOLBAR at https://standards.freedesktop.org/wm-spec/wm-spec-latest.html . **/ Q_PROPERTY(bool toolbar READ isToolbar) /** * Returns whether the window is a torn-off menu. * See _NET_WM_WINDOW_TYPE_MENU at https://standards.freedesktop.org/wm-spec/wm-spec-latest.html . **/ Q_PROPERTY(bool menu READ isMenu) /** * Returns whether the window is a "normal" window, i.e. an application or any other window * for which none of the specialized window types fit. * See _NET_WM_WINDOW_TYPE_NORMAL at https://standards.freedesktop.org/wm-spec/wm-spec-latest.html . **/ Q_PROPERTY(bool normalWindow READ isNormalWindow) /** * Returns whether the window is a dialog window. * See _NET_WM_WINDOW_TYPE_DIALOG at https://standards.freedesktop.org/wm-spec/wm-spec-latest.html . **/ Q_PROPERTY(bool dialog READ isDialog) /** * Returns whether the window is a splashscreen. Note that many (especially older) applications * do not support marking their splash windows with this type. * See _NET_WM_WINDOW_TYPE_SPLASH at https://standards.freedesktop.org/wm-spec/wm-spec-latest.html . **/ Q_PROPERTY(bool splash READ isSplash) /** * Returns whether the window is a utility window, such as a tool window. * See _NET_WM_WINDOW_TYPE_UTILITY at https://standards.freedesktop.org/wm-spec/wm-spec-latest.html . **/ Q_PROPERTY(bool utility READ isUtility) /** * Returns whether the window is a dropdown menu (i.e. a popup directly or indirectly open * from the applications menubar). * See _NET_WM_WINDOW_TYPE_DROPDOWN_MENU at https://standards.freedesktop.org/wm-spec/wm-spec-latest.html . **/ Q_PROPERTY(bool dropdownMenu READ isDropdownMenu) /** * Returns whether the window is a popup menu (that is not a torn-off or dropdown menu). * See _NET_WM_WINDOW_TYPE_POPUP_MENU at https://standards.freedesktop.org/wm-spec/wm-spec-latest.html . **/ Q_PROPERTY(bool popupMenu READ isPopupMenu) /** * Returns whether the window is a tooltip. * See _NET_WM_WINDOW_TYPE_TOOLTIP at https://standards.freedesktop.org/wm-spec/wm-spec-latest.html . **/ Q_PROPERTY(bool tooltip READ isTooltip) /** * Returns whether the window is a window with a notification. * See _NET_WM_WINDOW_TYPE_NOTIFICATION at https://standards.freedesktop.org/wm-spec/wm-spec-latest.html . **/ Q_PROPERTY(bool notification READ isNotification) /** * Returns whether the window is a window with a critical notification. **/ Q_PROPERTY(bool criticalNotification READ isCriticalNotification) /** * Returns whether the window is an On Screen Display. **/ Q_PROPERTY(bool onScreenDisplay READ isOnScreenDisplay) /** * Returns whether the window is a combobox popup. * See _NET_WM_WINDOW_TYPE_COMBO at https://standards.freedesktop.org/wm-spec/wm-spec-latest.html . **/ Q_PROPERTY(bool comboBox READ isComboBox) /** * Returns whether the window is a Drag&Drop icon. * See _NET_WM_WINDOW_TYPE_DND at https://standards.freedesktop.org/wm-spec/wm-spec-latest.html . **/ Q_PROPERTY(bool dndIcon READ isDNDIcon) /** * Returns the NETWM window type * See https://standards.freedesktop.org/wm-spec/wm-spec-latest.html . **/ Q_PROPERTY(int windowType READ windowType) Q_PROPERTY(QStringList activities READ activities NOTIFY activitiesChanged) /** * Whether this Toplevel is managed by KWin (it has control over its placement and other * aspects, as opposed to override-redirect windows that are entirely handled by the application). **/ Q_PROPERTY(bool managed READ isClient CONSTANT) /** * Whether this Toplevel represents an already deleted window and only kept for the compositor for animations. **/ Q_PROPERTY(bool deleted READ isDeleted CONSTANT) /** * Whether the window has an own shape **/ Q_PROPERTY(bool shaped READ shape NOTIFY shapedChanged) /** * Whether the window does not want to be animated on window close. * There are legit reasons for this like a screenshot application which does not want it's * window being captured. **/ Q_PROPERTY(bool skipsCloseAnimation READ skipsCloseAnimation WRITE setSkipCloseAnimation NOTIFY skipCloseAnimationChanged) /** * The Id of the Wayland Surface associated with this Toplevel. * On X11 only setups the value is @c 0. **/ Q_PROPERTY(quint32 surfaceId READ surfaceId NOTIFY surfaceIdChanged) /** * Interface to the Wayland Surface. * Relevant only in Wayland, in X11 it will be nullptr **/ Q_PROPERTY(KWayland::Server::SurfaceInterface *surface READ surface) /** * Whether the window is a popup. **/ Q_PROPERTY(bool popupWindow READ isPopupWindow) /** * Whether this Toplevel represents the outline. * * @note It's always @c false if compositing is turned off. **/ Q_PROPERTY(bool outline READ isOutline) public: explicit Toplevel(); virtual xcb_window_t frameId() const; xcb_window_t window() const; /** * @return a unique identifier for the Toplevel. On X11 same as @ref window **/ virtual quint32 windowId() const; QRect geometry() const; /** * The geometry of the Toplevel which accepts input events. This might be larger * than the actual geometry, e.g. to support resizing outside the window. * * Default implementation returns same as geometry. **/ virtual QRect inputGeometry() const; QSize size() const; QPoint pos() const; QRect rect() const; int x() const; int y() const; int width() const; int height() const; bool isOnScreen(int screen) const; // true if it's at least partially there bool isOnActiveScreen() const; int screen() const; // the screen where the center is /** * The scale of the screen this window is currently on * @note The buffer scale can be different. * @since 5.12 **/ qreal screenScale() const; // virtual QPoint clientPos() const = 0; // inside of geometry() /** * Describes how the client's content maps to the window geometry including the frame. * The default implementation is a 1:1 mapping meaning the frame is part of the content. **/ virtual QPoint clientContentPos() const; virtual QSize clientSize() const = 0; virtual QRect visibleRect() const; // the area the window occupies on the screen virtual QRect decorationRect() const; // rect including the decoration shadows virtual QRect transparentRect() const = 0; virtual bool isClient() const; virtual bool isDeleted() const; // prefer isXXX() instead // 0 for supported types means default for managed/unmanaged types virtual NET::WindowType windowType(bool direct = false, int supported_types = 0) const = 0; bool hasNETSupport() const; bool isDesktop() const; bool isDock() const; bool isToolbar() const; bool isMenu() const; bool isNormalWindow() const; // normal as in 'NET::Normal or NET::Unknown non-transient' bool isDialog() const; bool isSplash() const; bool isUtility() const; bool isDropdownMenu() const; bool isPopupMenu() const; // a context popup, not dropdown, not torn-off bool isTooltip() const; bool isNotification() const; bool isCriticalNotification() const; bool isOnScreenDisplay() const; bool isComboBox() const; bool isDNDIcon() const; virtual bool isLockScreen() const; virtual bool isInputMethod() const; virtual bool isOutline() const; /** * Returns the virtual desktop within the workspace() the client window * is located in, 0 if it isn't located on any special desktop (not mapped yet), * or NET::OnAllDesktops. Do not use desktop() directly, use * isOnDesktop() instead. **/ virtual int desktop() const = 0; virtual QVector desktops() const = 0; virtual QStringList activities() const = 0; bool isOnDesktop(int d) const; bool isOnActivity(const QString &activity) const; bool isOnCurrentDesktop() const; bool isOnCurrentActivity() const; bool isOnAllDesktops() const; bool isOnAllActivities() const; virtual QByteArray windowRole() const; QByteArray sessionId() const; QByteArray resourceName() const; QByteArray resourceClass() const; QByteArray wmCommand(); QByteArray wmClientMachine(bool use_localhost) const; const ClientMachine *clientMachine() const; virtual bool isLocalhost() const; Window wmClientLeader() const; virtual pid_t pid() const; static bool resourceMatch(const Toplevel* c1, const Toplevel* c2); bool readyForPainting() const; // true if the window has been already painted its contents xcb_visualid_t visual() const; bool shape() const; QRegion inputShape() const; virtual void setOpacity(double opacity); virtual double opacity() const; int depth() const; bool hasAlpha() const; virtual bool setupCompositing(); virtual void finishCompositing(ReleaseReason releaseReason = ReleaseReason::Release); Q_INVOKABLE void addRepaint(const QRect& r); Q_INVOKABLE void addRepaint(const QRegion& r); Q_INVOKABLE void addRepaint(int x, int y, int w, int h); Q_INVOKABLE void addLayerRepaint(const QRect& r); Q_INVOKABLE void addLayerRepaint(const QRegion& r); Q_INVOKABLE void addLayerRepaint(int x, int y, int w, int h); Q_INVOKABLE virtual void addRepaintFull(); // these call workspace->addRepaint(), but first transform the damage if needed void addWorkspaceRepaint(const QRect& r); void addWorkspaceRepaint(int x, int y, int w, int h); QRegion repaints() const; void resetRepaints(); QRegion damage() const; void resetDamage(); EffectWindowImpl* effectWindow(); const EffectWindowImpl* effectWindow() const; /** * Window will be temporarily painted as if being at the top of the stack. * Only available if Compositor is active, if not active, this method is a no-op. **/ void elevate(bool elevate); /** * @returns Whether the Toplevel has a Shadow or not * @see shadow **/ bool hasShadow() const; /** * Returns the pointer to the Toplevel's Shadow. A Shadow * is only available if Compositing is enabled and the corresponding X window * has the Shadow property set. * If a shadow is available hasShadow returns @c true. * @returns The Shadow belonging to this Toplevel, may be @c NULL. * @see hasShadow **/ const Shadow *shadow() const; Shadow *shadow(); /** * Updates the Shadow associated with this Toplevel from X11 Property. * Call this method when the Property changes or Compositing is started. **/ void getShadow(); /** * Whether the Toplevel currently wants the shadow to be rendered. Default * implementation always returns @c true. **/ virtual bool wantsShadowToBeRendered() const; /** * This method returns the area that the Toplevel window reports to be opaque. * It is supposed to only provide valuable information if hasAlpha is @c true . * @see hasAlpha **/ const QRegion& opaqueRegion() const; virtual Layer layer() const = 0; /** * Resets the damage state and sends a request for the damage region. * A call to this function must be followed by a call to getDamageRegionReply(), * or the reply will be leaked. * * Returns true if the window was damaged, and false otherwise. **/ bool resetAndFetchDamage(); /** * Gets the reply from a previous call to resetAndFetchDamage(). * Calling this function is a no-op if there is no pending reply. * Call damage() to return the fetched region. **/ void getDamageRegionReply(); bool skipsCloseAnimation() const; void setSkipCloseAnimation(bool set); quint32 surfaceId() const; KWayland::Server::SurfaceInterface *surface() const; void setSurface(KWayland::Server::SurfaceInterface *surface); virtual void setInternalFramebufferObject(const QSharedPointer &fbo); const QSharedPointer &internalFramebufferObject() const; /** * @returns Transformation to map from global to window coordinates. * * Default implementation returns a translation on negative pos(). * @see pos **/ virtual QMatrix4x4 inputTransformation() const; /** * The window has a popup grab. This means that when it got mapped the * parent window had an implicit (pointer) grab. * * Normally this is only relevant for transient windows. * * Once the popup grab ends (e.g. pointer press outside of any Toplevel of * the client), the method popupDone should be invoked. * * The default implementation returns @c false. * @see popupDone * @since 5.10 **/ virtual bool hasPopupGrab() const { return false; } /** * This method should be invoked for Toplevels with a popup grab when * the grab ends. * * The default implementation does nothing. * @see hasPopupGrab * @since 5.10 **/ virtual void popupDone() {}; /** * @brief Finds the Toplevel matching the condition expressed in @p func in @p list. * * The method is templated to operate on either a list of Toplevels or on a list of * a subclass type of Toplevel. * @param list The list to search in * @param func The condition function (compare std::find_if) * @return T* The found Toplevel or @c null if there is no matching Toplevel **/ template static T *findInList(const QList &list, std::function func); /** * Whether the window is a popup. * * Popups can be used to implement popup menus, tooltips, combo boxes, etc. * * @since 5.15 **/ virtual bool isPopupWindow() const; /** * A UUID to uniquely identify this Toplevel independent of windowing system. **/ QUuid internalId() const { return m_internalId; } Q_SIGNALS: void opacityChanged(KWin::Toplevel* toplevel, qreal oldOpacity); void damaged(KWin::Toplevel* toplevel, const QRect& damage); void geometryChanged(); void geometryShapeChanged(KWin::Toplevel* toplevel, const QRect& old); void paddingChanged(KWin::Toplevel* toplevel, const QRect& old); void windowClosed(KWin::Toplevel* toplevel, KWin::Deleted* deleted); void windowShown(KWin::Toplevel* toplevel); void windowHidden(KWin::Toplevel* toplevel); /** * Signal emitted when the window's shape state changed. That is if it did not have a shape * and received one or if the shape was withdrawn. Think of Chromium enabling/disabling KWin's * decoration. **/ void shapedChanged(); /** * Emitted whenever the state changes in a way, that the Compositor should * schedule a repaint of the scene. **/ void needsRepaint(); void activitiesChanged(KWin::Toplevel* toplevel); /** * Emitted whenever the Toplevel's screen changes. This can happen either in consequence to * a screen being removed/added or if the Toplevel's geometry changes. * @since 4.11 **/ void screenChanged(); void skipCloseAnimationChanged(); /** * Emitted whenever the window role of the window changes. * @since 5.0 **/ void windowRoleChanged(); /** * Emitted whenever the window class name or resource name of the window changes. * @since 5.0 **/ void windowClassChanged(); /** * Emitted when a Wayland Surface gets associated with this Toplevel. * @since 5.3 **/ void surfaceIdChanged(quint32); /** * @since 5.4 **/ void hasAlphaChanged(); /** * Emitted whenever the Surface for this Toplevel changes. **/ void surfaceChanged(); /* * Emitted when the client's screen changes onto a screen of a different scale * or the screen we're on changes * @since 5.12 **/ void screenScaleChanged(); /** * Emitted whenever the client's shadow changes. * @since 5.15 **/ void shadowChanged(); protected Q_SLOTS: /** * Checks whether the screen number for this Toplevel changed and updates if needed. * Any method changing the geometry of the Toplevel should call this method. **/ void checkScreen(); void setupCheckScreenConnection(); void removeCheckScreenConnection(); void setReadyForPainting(); protected: virtual ~Toplevel(); void setWindowHandles(xcb_window_t client); void detectShape(Window id); virtual void propertyNotifyEvent(xcb_property_notify_event_t *e); virtual void damageNotifyEvent(); virtual void clientMessageEvent(xcb_client_message_event_t *e); void discardWindowPixmap(); void addDamageFull(); virtual void addDamage(const QRegion &damage); Xcb::Property fetchWmClientLeader() const; void readWmClientLeader(Xcb::Property &p); void getWmClientLeader(); void getWmClientMachine(); /** * @returns Whether there is a compositor and it is active. **/ bool compositing() const; /** * This function fetches the opaque region from this Toplevel. * Will only be called on corresponding property changes and for initialization. **/ void getWmOpaqueRegion(); void getResourceClass(); void setResourceClass(const QByteArray &name, const QByteArray &className = QByteArray()); Xcb::Property fetchSkipCloseAnimation() const; void readSkipCloseAnimation(Xcb::Property &prop); void getSkipCloseAnimation(); virtual void debug(QDebug& stream) const = 0; void copyToDeleted(Toplevel* c); void disownDataPassedToDeleted(); friend QDebug& operator<<(QDebug& stream, const Toplevel*); void deleteEffectWindow(); void setDepth(int depth); QRect geom; xcb_visualid_t m_visual; int bit_depth; NETWinInfo* info; bool ready_for_painting; QRegion repaints_region; // updating, repaint just requires repaint of that area QRegion layer_repaints_region; protected: bool m_isDamaged; private: // when adding new data members, check also copyToDeleted() QUuid m_internalId; Xcb::Window m_client; xcb_damage_damage_t damage_handle; QRegion damage_region; // damage is really damaged window (XDamage) and texture needs bool is_shape; EffectWindowImpl* effect_window; QByteArray resource_name; QByteArray resource_class; ClientMachine *m_clientMachine; WId wmClientLeaderWin; bool m_damageReplyPending; QRegion opaque_region; xcb_xfixes_fetch_region_cookie_t m_regionCookie; int m_screen; bool m_skipCloseAnimation; quint32 m_surfaceId = 0; KWayland::Server::SurfaceInterface *m_surface = nullptr; /** * An FBO object KWin internal windows might render to. **/ QSharedPointer m_internalFBO; // when adding new data members, check also copyToDeleted() qreal m_screenScale = 1.0; }; inline xcb_window_t Toplevel::window() const { return m_client; } inline void Toplevel::setWindowHandles(xcb_window_t w) { assert(!m_client.isValid() && w != XCB_WINDOW_NONE); m_client.reset(w, false); } inline QRect Toplevel::geometry() const { return geom; } inline QSize Toplevel::size() const { return geom.size(); } inline QPoint Toplevel::pos() const { return geom.topLeft(); } inline int Toplevel::x() const { return geom.x(); } inline int Toplevel::y() const { return geom.y(); } inline int Toplevel::width() const { return geom.width(); } inline int Toplevel::height() const { return geom.height(); } inline QRect Toplevel::rect() const { return QRect(0, 0, width(), height()); } inline bool Toplevel::readyForPainting() const { return ready_for_painting; } inline xcb_visualid_t Toplevel::visual() const { return m_visual; } inline bool Toplevel::isDesktop() const { return windowType() == NET::Desktop; } inline bool Toplevel::isDock() const { return windowType() == NET::Dock; } inline bool Toplevel::isMenu() const { return windowType() == NET::Menu; } inline bool Toplevel::isToolbar() const { return windowType() == NET::Toolbar; } inline bool Toplevel::isSplash() const { return windowType() == NET::Splash; } inline bool Toplevel::isUtility() const { return windowType() == NET::Utility; } inline bool Toplevel::isDialog() const { return windowType() == NET::Dialog; } inline bool Toplevel::isNormalWindow() const { return windowType() == NET::Normal; } inline bool Toplevel::isDropdownMenu() const { return windowType() == NET::DropdownMenu; } inline bool Toplevel::isPopupMenu() const { return windowType() == NET::PopupMenu; } inline bool Toplevel::isTooltip() const { return windowType() == NET::Tooltip; } inline bool Toplevel::isNotification() const { return windowType() == NET::Notification; } inline bool Toplevel::isCriticalNotification() const { return windowType() == NET::CriticalNotification; } inline bool Toplevel::isOnScreenDisplay() const { return windowType() == NET::OnScreenDisplay; } inline bool Toplevel::isComboBox() const { return windowType() == NET::ComboBox; } inline bool Toplevel::isDNDIcon() const { return windowType() == NET::DNDIcon; } inline bool Toplevel::isLockScreen() const { return false; } inline bool Toplevel::isInputMethod() const { return false; } inline bool Toplevel::isOutline() const { return false; } inline QRegion Toplevel::damage() const { return damage_region; } inline QRegion Toplevel::repaints() const { return repaints_region.translated(pos()) | layer_repaints_region; } inline bool Toplevel::shape() const { return is_shape; } inline int Toplevel::depth() const { return bit_depth; } inline bool Toplevel::hasAlpha() const { return depth() == 32; } inline const QRegion& Toplevel::opaqueRegion() const { return opaque_region; } inline EffectWindowImpl* Toplevel::effectWindow() { return effect_window; } inline const EffectWindowImpl* Toplevel::effectWindow() const { return effect_window; } inline bool Toplevel::isOnAllDesktops() const { return kwinApp()->operationMode() == Application::OperationModeWaylandOnly || kwinApp()->operationMode() == Application::OperationModeXwayland //Wayland ? desktops().isEmpty() //X11 : desktop() == NET::OnAllDesktops; } inline bool Toplevel::isOnAllActivities() const { return activities().isEmpty(); } inline bool Toplevel::isOnDesktop(int d) const { return (kwinApp()->operationMode() == Application::OperationModeWaylandOnly || kwinApp()->operationMode() == Application::OperationModeXwayland ? desktops().contains(VirtualDesktopManager::self()->desktopForX11Id(d)) : desktop() == d ) || isOnAllDesktops(); } inline bool Toplevel::isOnActivity(const QString &activity) const { return activities().isEmpty() || activities().contains(activity); } inline bool Toplevel::isOnCurrentDesktop() const { return isOnDesktop(VirtualDesktopManager::self()->current()); } inline QByteArray Toplevel::resourceName() const { return resource_name; // it is always lowercase } inline QByteArray Toplevel::resourceClass() const { return resource_class; // it is always lowercase } inline const ClientMachine *Toplevel::clientMachine() const { return m_clientMachine; } inline quint32 Toplevel::surfaceId() const { return m_surfaceId; } inline KWayland::Server::SurfaceInterface *Toplevel::surface() const { return m_surface; } inline const QSharedPointer &Toplevel::internalFramebufferObject() const { return m_internalFBO; } inline QPoint Toplevel::clientContentPos() const { return QPoint(0, 0); } template inline T *Toplevel::findInList(const QList &list, std::function func) { static_assert(std::is_base_of::value, "U must be derived from T"); const auto it = std::find_if(list.begin(), list.end(), func); if (it == list.end()) { return nullptr; } return *it; } inline bool Toplevel::isPopupWindow() const { switch (windowType()) { case NET::ComboBox: case NET::DropdownMenu: case NET::PopupMenu: case NET::Tooltip: return true; default: return false; } } QDebug& operator<<(QDebug& stream, const Toplevel*); QDebug& operator<<(QDebug& stream, const ToplevelList&); } // namespace Q_DECLARE_METATYPE(KWin::Toplevel*) #endif diff --git a/utils.cpp b/utils.cpp index 91f345a7d..45d5d0e83 100644 --- a/utils.cpp +++ b/utils.cpp @@ -1,201 +1,200 @@ /******************************************************************** 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 #include -#include - #include "atoms.h" #include "platform.h" #include "workspace.h" -#include +#include +#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()) { } #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() { 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) return false; if (keyboard_grabbed) return false; if (qApp->activePopupWidget() != NULL) 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)); 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; 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; 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/utils.h b/utils.h index 22491d556..459cae8df 100644 --- a/utils.h +++ b/utils.h @@ -1,247 +1,247 @@ /******************************************************************** 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_UTILS_H #define KWIN_UTILS_H // cmake stuff #include #include // kwin #include // KDE #include // Qt #include #include #include #include #include #include // system -#include +#include Q_DECLARE_LOGGING_CATEGORY(KWIN_CORE) Q_DECLARE_LOGGING_CATEGORY(KWIN_VIRTUALKEYBOARD) namespace KWin { // window types that are supported as normal windows (i.e. KWin actually manages them) const NET::WindowTypes SUPPORTED_MANAGED_WINDOW_TYPES_MASK = NET::NormalMask | NET::DesktopMask | NET::DockMask | NET::ToolbarMask | NET::MenuMask | NET::DialogMask /*| NET::OverrideMask*/ | NET::TopMenuMask | NET::UtilityMask | NET::SplashMask | NET::NotificationMask | NET::OnScreenDisplayMask | NET::CriticalNotificationMask; // window types that are supported as unmanaged (mainly for compositing) const NET::WindowTypes SUPPORTED_UNMANAGED_WINDOW_TYPES_MASK = NET::NormalMask | NET::DesktopMask | NET::DockMask | NET::ToolbarMask | NET::MenuMask | NET::DialogMask /*| NET::OverrideMask*/ | NET::TopMenuMask | NET::UtilityMask | NET::SplashMask | NET::DropdownMenuMask | NET::PopupMenuMask | NET::TooltipMask | NET::NotificationMask | NET::ComboBoxMask | NET::DNDIconMask | NET::OnScreenDisplayMask | NET::CriticalNotificationMask; const QPoint invalidPoint(INT_MIN, INT_MIN); class Toplevel; class Client; class Unmanaged; class Deleted; class Group; class Options; typedef QList< Toplevel* > ToplevelList; typedef QList< Client* > ClientList; typedef QList< const Client* > ConstClientList; typedef QList< Unmanaged* > UnmanagedList; typedef QList< Deleted* > DeletedList; typedef QList< Group* > GroupList; extern Options* options; enum Layer { UnknownLayer = -1, FirstLayer = 0, DesktopLayer = FirstLayer, BelowLayer, NormalLayer, DockLayer, AboveLayer, NotificationLayer, // layer for windows of type notification ActiveLayer, // active fullscreen, or active dialog CriticalNotificationLayer, // layer for notifications that should be shown even on top of fullscreen OnScreenDisplayLayer, // layer for On Screen Display windows such as volume feedback UnmanagedLayer, // layer for override redirect windows. NumLayers // number of layers, must be last }; // yes, I know this is not 100% like standard operator++ inline void operator++(Layer& lay) { lay = static_cast< Layer >(lay + 1); } enum StrutArea { StrutAreaInvalid = 0, // Null StrutAreaTop = 1 << 0, StrutAreaRight = 1 << 1, StrutAreaBottom = 1 << 2, StrutAreaLeft = 1 << 3, StrutAreaAll = StrutAreaTop | StrutAreaRight | StrutAreaBottom | StrutAreaLeft }; Q_DECLARE_FLAGS(StrutAreas, StrutArea) class StrutRect : public QRect { public: explicit StrutRect(QRect rect = QRect(), StrutArea area = StrutAreaInvalid); StrutRect(const StrutRect& other); inline StrutArea area() const { return m_area; }; private: StrutArea m_area; }; typedef QVector StrutRects; enum ShadeMode { ShadeNone, // not shaded ShadeNormal, // normally shaded - isShade() is true only here ShadeHover, // "shaded", but visible due to hover unshade ShadeActivated // "shaded", but visible due to alt+tab to the window }; /** * Maximize mode. These values specify how a window is maximized. * * @note these values are written to session files, don't change the order **/ enum MaximizeMode { MaximizeRestore = 0, ///< The window is not maximized in any direction. MaximizeVertical = 1, ///< The window is maximized vertically. MaximizeHorizontal = 2, ///< The window is maximized horizontally. /// Equal to @p MaximizeVertical | @p MaximizeHorizontal MaximizeFull = MaximizeVertical | MaximizeHorizontal }; inline MaximizeMode operator^(MaximizeMode m1, MaximizeMode m2) { return MaximizeMode(int(m1) ^ int(m2)); } enum class QuickTileFlag { None = 0, Left = 1 << 0, Right = 1 << 1, Top = 1 << 2, Bottom = 1 << 3, Horizontal = Left | Right, Vertical = Top | Bottom, Maximize = Left | Right | Top | Bottom, }; Q_DECLARE_FLAGS(QuickTileMode, QuickTileFlag) template using ScopedCPointer = QScopedPointer; void KWIN_EXPORT updateXTime(); void KWIN_EXPORT grabXServer(); void KWIN_EXPORT ungrabXServer(); bool KWIN_EXPORT grabXKeyboard(xcb_window_t w = XCB_WINDOW_NONE); void KWIN_EXPORT ungrabXKeyboard(); /** * Small helper class which performs grabXServer in the ctor and * ungrabXServer in the dtor. Use this class to ensure that grab and * ungrab are matched. * * To simplify usage consider using the macro GRAB_SERVER_DURING_CONTEXT **/ class XServerGrabber { public: XServerGrabber() { grabXServer(); } ~XServerGrabber() { ungrabXServer(); } }; #define GRAB_SERVER_DURING_CONTEXT XServerGrabber xserverGrabber; // the docs say it's UrgencyHint, but it's often #defined as XUrgencyHint #ifndef UrgencyHint #define UrgencyHint XUrgencyHint #endif // converting between X11 mouse/keyboard state mask and Qt button/keyboard states Qt::MouseButton x11ToQtMouseButton(int button); Qt::MouseButton KWIN_EXPORT x11ToQtMouseButton(int button); Qt::MouseButtons KWIN_EXPORT x11ToQtMouseButtons(int state); Qt::KeyboardModifiers KWIN_EXPORT x11ToQtKeyboardModifiers(int state); /** * Separate the concept of an unet QPoint and 0,0 **/ class ClearablePoint { public: inline bool isValid() const { return m_valid; } inline void clear(){ m_valid = false; } inline void setPoint(const QPoint &point) { m_point = point; m_valid = true; } inline QPoint point() const { return m_point; } private: QPoint m_point; bool m_valid = false; }; /** * QProcess subclass which unblocks SIGUSR in the child process. **/ class KWIN_EXPORT Process : public QProcess { Q_OBJECT public: explicit Process(QObject *parent = nullptr); virtual ~Process(); protected: void setupChildProcess() override; }; } // namespace // Must be outside namespace Q_DECLARE_OPERATORS_FOR_FLAGS(KWin::StrutAreas) Q_DECLARE_OPERATORS_FOR_FLAGS(KWin::QuickTileMode) #endif diff --git a/virtual_terminal.cpp b/virtual_terminal.cpp index 8ceb74d3a..ecdf9d1d1 100644 --- a/virtual_terminal.cpp +++ b/virtual_terminal.cpp @@ -1,215 +1,216 @@ /******************************************************************** 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 "virtual_terminal.h" // kwin #include "logind.h" #include "main.h" #include "utils.h" // Qt #include #include // linux #include #include #include // system #include #include -#include #include #include #include #include +// c++ +#include #define RELEASE_SIGNAL SIGUSR1 #define ACQUISITION_SIGNAL SIGUSR2 namespace KWin { KWIN_SINGLETON_FACTORY(VirtualTerminal) VirtualTerminal::VirtualTerminal(QObject *parent) : QObject(parent) { } void VirtualTerminal::init() { auto logind = LogindIntegration::self(); if (logind->vt() != -1) { setup(logind->vt()); } connect(logind, &LogindIntegration::virtualTerminalChanged, this, &VirtualTerminal::setup); if (logind->isConnected()) { logind->takeControl(); } else { connect(logind, &LogindIntegration::connectedChanged, logind, &LogindIntegration::takeControl); } } VirtualTerminal::~VirtualTerminal() { s_self = nullptr; closeFd(); } static bool isTty(int fd) { if (fd < 0) { return false; } struct stat st; if (fstat(fd, &st) == -1) { return false; } if (major(st.st_rdev) != TTY_MAJOR || minor (st.st_rdev) <= 0 || minor(st.st_rdev) >= 64) { return false; } return true; } void VirtualTerminal::setup(int vtNr) { if (m_vt != -1) { return; } if (vtNr == -1) { // error condition return; } QString ttyName = QStringLiteral("/dev/tty%1").arg(vtNr); m_vt = open(ttyName.toUtf8().constData(), O_RDWR|O_CLOEXEC|O_NONBLOCK); if (m_vt < 0) { qCWarning(KWIN_CORE) << "Failed to open tty" << vtNr; return; } if (!isTty(m_vt)) { qCWarning(KWIN_CORE) << vtNr << " is no tty"; closeFd(); return; } if (ioctl(m_vt, KDSETMODE, KD_GRAPHICS) < 0) { qCWarning(KWIN_CORE()) << "Failed to set tty " << vtNr << " in graphics mode"; closeFd(); return; } if (!createSignalHandler()) { qCWarning(KWIN_CORE) << "Failed to create signalfd"; closeFd(); return; } vt_mode mode = { VT_PROCESS, 0, RELEASE_SIGNAL, ACQUISITION_SIGNAL, 0 }; if (ioctl(m_vt, VT_SETMODE, &mode) < 0) { qCWarning(KWIN_CORE) << "Failed to take over virtual terminal"; closeFd(); return; } m_vtNumber = vtNr; setActive(true); emit kwinApp()->virtualTerminalCreated(); } void VirtualTerminal::closeFd() { if (m_vt < 0) { return; } if (m_notifier) { const int sfd = m_notifier->socket(); delete m_notifier; m_notifier = nullptr; close(sfd); } close(m_vt); m_vt = -1; } bool VirtualTerminal::createSignalHandler() { if (m_notifier) { return false; } sigset_t mask; sigemptyset(&mask); sigaddset(&mask, RELEASE_SIGNAL); sigaddset(&mask, ACQUISITION_SIGNAL); pthread_sigmask(SIG_BLOCK, &mask, nullptr); const int fd = signalfd(-1, &mask, SFD_NONBLOCK | SFD_CLOEXEC); if (fd < 0) { return false; } m_notifier = new QSocketNotifier(fd, QSocketNotifier::Read, this); connect(m_notifier, &QSocketNotifier::activated, this, [this] { if (m_vt < 0) { return; } while (true) { signalfd_siginfo sigInfo; if (read(m_notifier->socket(), &sigInfo, sizeof(sigInfo)) != sizeof(sigInfo)) { break; } switch (sigInfo.ssi_signo) { case RELEASE_SIGNAL: setActive(false); ioctl(m_vt, VT_RELDISP, 1); break; case ACQUISITION_SIGNAL: ioctl(m_vt, VT_RELDISP, VT_ACKACQ); setActive(true); break; } } } ); return true; } void VirtualTerminal::activate(int vt) { if (m_vt < 0) { return; } if (vt == m_vtNumber) { return; } ioctl(m_vt, VT_ACTIVATE, vt); setActive(false); } void VirtualTerminal::setActive(bool active) { if (m_active == active) { return; } m_active = active; emit activeChanged(m_active); } }