diff --git a/flake.lock b/flake.lock index f6a3060..57afc98 100644 --- a/flake.lock +++ b/flake.lock @@ -28,11 +28,11 @@ "nixpkgs": "nixpkgs" }, "locked": { - "lastModified": 1737579274, - "narHash": "sha256-8kBIYfn8TI9jbffhDNS12SdbQHb9ITXflwcgIJBeGqw=", + "lastModified": 1739934729, + "narHash": "sha256-PcrLk10meIJICzUJqtCMOJxoITzbH52fZg2XAB7SSsM=", "owner": "catppuccin", "repo": "nix", - "rev": "06f0ea19334bcc8112e6d671fd53e61f9e3ad63a", + "rev": "b1ff2a638afa827f1473498190a2c1cae1cf41cf", "type": "github" }, "original": { @@ -60,11 +60,11 @@ "eza-themes": { "flake": false, "locked": { - "lastModified": 1737730475, - "narHash": "sha256-d+bbjgI1JrOGenqZ2aIRK8itkTUV2L4L3vtEN9tEgf8=", + "lastModified": 1739960775, + "narHash": "sha256-vu6QLz0RvPavpD2VED25D2PJlHgQ8Yis+DnL+BPlvHw=", "owner": "eza-community", "repo": "eza-themes", - "rev": "a99a5f1bbb5fec2c0bfa945ec8e2aabcb6f24d71", + "rev": "57149851f07b3ee6ca94f5fe3d9d552f73f8b8b4", "type": "github" }, "original": { @@ -188,11 +188,11 @@ ] }, "locked": { - "lastModified": 1738275749, - "narHash": "sha256-PM+cGduJ05EZ+YXulqAwUFjvfKpPmW080mcuN6R1POw=", + "lastModified": 1739913864, + "narHash": "sha256-WhzgQjadrwnwPJQLLxZUUEIxojxa7UWDkf7raAkB1Lw=", "owner": "nix-community", "repo": "home-manager", - "rev": "a8159195bfaef3c64df75d3b1e6a68d49d392be9", + "rev": "97ac0801d187b2911e8caa45316399de12f6f199", "type": "github" }, "original": { @@ -208,11 +208,11 @@ ] }, "locked": { - "lastModified": 1736373539, - "narHash": "sha256-dinzAqCjenWDxuy+MqUQq0I4zUSfaCvN9rzuCmgMZJY=", + "lastModified": 1739757849, + "narHash": "sha256-Gs076ot1YuAAsYVcyidLKUMIc4ooOaRGO0PqTY7sBzA=", "owner": "nix-community", "repo": "home-manager", - "rev": "bd65bc3cde04c16755955630b344bc9e35272c56", + "rev": "9d3d080aec2a35e05a15cedd281c2384767c2cfe", "type": "github" }, "original": { @@ -230,11 +230,11 @@ ] }, "locked": { - "lastModified": 1737617533, - "narHash": "sha256-9hHaUxeRDZ5PTk7TqBbHPAjgKuBl67asaMdyehYLqFs=", + "lastModified": 1739952453, + "narHash": "sha256-+tyFW6nNj1fJ1VTtLeqe1PMp5F7Fb9zIkT6mUvdQHrM=", "owner": "Jovian-Experiments", "repo": "Jovian-NixOS", - "rev": "2dd65c3c92a4b8b1bf653657ae8648b883a4d427", + "rev": "b2ed82d3ff837960df4518308dfe409dda3ae406", "type": "github" }, "original": { @@ -246,11 +246,11 @@ "lix": { "flake": false, "locked": { - "lastModified": 1738295316, - "narHash": "sha256-i6dSbuPQXv3iqARBqxL0tIbSZw4Rq5UuoVrI9a8vjV0=", - "rev": "e529074cb2c21e66c300806ebf6bf03abcd681b3", + "lastModified": 1739940699, + "narHash": "sha256-9jlwrVWXzqdEEl611VE+fMnBLKzRFoONJYHUQeeKM0Q=", + "rev": "df336747e6d9fdd36330ca4105fa30232b23a202", "type": "tarball", - "url": "https://git.lix.systems/api/v1/repos/lix-project/lix/archive/e529074cb2c21e66c300806ebf6bf03abcd681b3.tar.gz?rev=e529074cb2c21e66c300806ebf6bf03abcd681b3" + "url": "https://git.lix.systems/api/v1/repos/lix-project/lix/archive/df336747e6d9fdd36330ca4105fa30232b23a202.tar.gz?rev=df336747e6d9fdd36330ca4105fa30232b23a202" }, "original": { "type": "tarball", @@ -305,11 +305,11 @@ }, "nix-flatpak": { "locked": { - "lastModified": 1738175805, - "narHash": "sha256-fPjaARmK522JLJ7wxFebxG4eE/3HHSmuAA78iAZ+A7g=", + "lastModified": 1739444422, + "narHash": "sha256-iAVVHi7X3kWORftY+LVbRiStRnQEob2TULWyjMS6dWg=", "owner": "gmodena", "repo": "nix-flatpak", - "rev": "d4c75a33c4a7a16bf87cfd804fb5444a1ec53d49", + "rev": "5e54c3ca05a7c7d968ae1ddeabe01d2a9bc1e177", "type": "github" }, "original": { @@ -363,11 +363,11 @@ ] }, "locked": { - "lastModified": 1737861961, - "narHash": "sha256-LIRtMvAwLGb8pBoamzgEF67oKlNPz4LuXiRPVZf+TpE=", + "lastModified": 1739676768, + "narHash": "sha256-U1HQ7nzhJyVVXUgjU028UCkbLQLEIkg42+G7iIiBmlU=", "owner": "Mic92", "repo": "nix-index-database", - "rev": "79b7b8eae3243fc5aa9aad34ba6b9bbb2266f523", + "rev": "ae15068e79e22b76c344f0d7f8aed1bb1c5b0b63", "type": "github" }, "original": { @@ -383,11 +383,11 @@ ] }, "locked": { - "lastModified": 1737861961, - "narHash": "sha256-LIRtMvAwLGb8pBoamzgEF67oKlNPz4LuXiRPVZf+TpE=", + "lastModified": 1739676768, + "narHash": "sha256-U1HQ7nzhJyVVXUgjU028UCkbLQLEIkg42+G7iIiBmlU=", "owner": "Mic92", "repo": "nix-index-database", - "rev": "79b7b8eae3243fc5aa9aad34ba6b9bbb2266f523", + "rev": "ae15068e79e22b76c344f0d7f8aed1bb1c5b0b63", "type": "github" }, "original": { @@ -398,11 +398,11 @@ }, "nixos-hardware": { "locked": { - "lastModified": 1737751639, - "narHash": "sha256-ZEbOJ9iT72iwqXsiEMbEa8wWjyFvRA9Ugx8utmYbpz4=", + "lastModified": 1739798439, + "narHash": "sha256-GyipmjbbQEaosel/+wq1xihCKbv0/e1LU00x/8b/fP4=", "owner": "NixOS", "repo": "nixos-hardware", - "rev": "dfad538f751a5aa5d4436d9781ab27a6128ec9d4", + "rev": "3e2ea8a49d4d76276b0f4e2041df8ca5c0771371", "type": "github" }, "original": { @@ -430,11 +430,11 @@ }, "nixpkgs-raw": { "locked": { - "lastModified": 1738163270, - "narHash": "sha256-B/7Y1v4y+msFFBW1JAdFjNvVthvNdJKiN6EGRPnqfno=", + "lastModified": 1739758141, + "narHash": "sha256-uq6A2L7o1/tR6VfmYhZWoVAwb3gTy7j4Jx30MIrH0rE=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "59e618d90c065f55ae48446f307e8c09565d5ab0", + "rev": "c618e28f70257593de75a7044438efc1c1fc0791", "type": "github" }, "original": { @@ -445,11 +445,11 @@ }, "nixpkgs-unstable-raw": { "locked": { - "lastModified": 1738142207, - "narHash": "sha256-NGqpVVxNAHwIicXpgaVqJEJWeyqzoQJ9oc8lnK9+WC4=", + "lastModified": 1739866667, + "narHash": "sha256-EO1ygNKZlsAC9avfcwHkKGMsmipUk1Uc0TbrEZpkn64=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "9d3ae807ebd2981d593cddd0080856873139aa40", + "rev": "73cf49b8ad837ade2de76f87eb53fc85ed5d4680", "type": "github" }, "original": { @@ -460,11 +460,11 @@ }, "nixpkgs_2": { "locked": { - "lastModified": 1738142207, - "narHash": "sha256-NGqpVVxNAHwIicXpgaVqJEJWeyqzoQJ9oc8lnK9+WC4=", + "lastModified": 1739736696, + "narHash": "sha256-zON2GNBkzsIyALlOCFiEBcIjI4w38GYOb+P+R4S8Jsw=", "owner": "nixos", "repo": "nixpkgs", - "rev": "9d3ae807ebd2981d593cddd0080856873139aa40", + "rev": "d74a2335ac9c133d6bbec9fc98d91a77f1604c1f", "type": "github" }, "original": { @@ -493,11 +493,11 @@ "treefmt-nix": "treefmt-nix" }, "locked": { - "lastModified": 1738308670, - "narHash": "sha256-88WNLo6WdYmlO5u+1vaVRIakliO0B9hiNxBEMTZ9yOM=", + "lastModified": 1739903703, + "narHash": "sha256-w2tTcjx39lJoPDaFbIxi+INIjAKE0jbIx9TNjj9ghmg=", "owner": "nix-community", "repo": "NUR", - "rev": "a75432a6ffa9ca5af9f885194b64e0d4ad10b96b", + "rev": "2215ad5c4347f522523715e809f5f2022509f504", "type": "github" }, "original": { @@ -516,11 +516,11 @@ ] }, "locked": { - "lastModified": 1736549395, - "narHash": "sha256-XzwkB62Tt5UYoL1jXiHzgk/qz2fUpGHExcSIbyGTtI0=", + "lastModified": 1739557722, + "narHash": "sha256-XikzLpPUDYiNyJ4w2SfRShdbSkIgE3btYdxCGInmtc4=", "owner": "nix-community", "repo": "plasma-manager", - "rev": "a53af7f1514ef4cce8620a9d6a50f238cdedec8b", + "rev": "1f3e1f38dedbbb8aad77e184fb54ec518e2d9522", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index cb474fd..af19980 100644 --- a/flake.nix +++ b/flake.nix @@ -172,7 +172,7 @@ then home-manager else home-manager-unstable ) - .nixosModule + .nixosModules.home-manager ( if stable then nix-index-db diff --git a/roles/kde/patches/kwin-pr6406.patch b/roles/kde/patches/kwin-pr6406.patch deleted file mode 100644 index e3a55a8..0000000 --- a/roles/kde/patches/kwin-pr6406.patch +++ /dev/null @@ -1,1045 +0,0 @@ -From 3ea8c3758e75f06c71b0b279c4e51f22ec554954 Mon Sep 17 00:00:00 2001 -From: Vlad Zahorodnii -Date: Fri, 4 Oct 2024 22:49:16 +0300 -Subject: [PATCH 1/9] Resize X11 window immediately if it doesn't support - _NET_WM_SYNC_REQUEST - -The fact that X11Window::handleSyncTimeout() is shared by code that runs -when the client supports or doesn't support _NET_WM_SYNC_REQUEST makes -the XSync code difficult to follow and more error prone. - -On the other hand, it appears that other window managers, e.g. Mutter and -openbox, don't bother about throttling interactive resize when the client -doesn't opt in into _NET_WM_SYNC_REQUEST, so let's do the same to simplify -the code. - -As a backup plan, if it indeed becomes a problem later, we can always -save current monotonic time in X11Window::doInteractiveResizeSync() and -make X11Window::isWaitingForInteractiveResizeSync() check whether enough -of time has passed since the last resize sync. ---- - autotests/integration/x11_window_test.cpp | 16 +++-------- - src/x11window.cpp | 35 +++++++++-------------- - 2 files changed, 17 insertions(+), 34 deletions(-) - -diff --git a/autotests/integration/x11_window_test.cpp b/autotests/integration/x11_window_test.cpp -index 12f53c4220..a208fa3adb 100644 ---- a/autotests/integration/x11_window_test.cpp -+++ b/autotests/integration/x11_window_test.cpp -@@ -1492,7 +1492,6 @@ void X11WindowTest::testNetWmKeyboardResize() - QSignalSpy interactiveMoveResizeStartedSpy(window, &X11Window::interactiveMoveResizeStarted); - QSignalSpy interactiveMoveResizeFinishedSpy(window, &X11Window::interactiveMoveResizeFinished); - QSignalSpy interactiveMoveResizeSteppedSpy(window, &X11Window::interactiveMoveResizeStepped); -- QSignalSpy frameGeometryChangedSpy(window, &X11Window::frameGeometryChanged); - QVERIFY(interactiveMoveResizeStartedSpy.wait()); - QCOMPARE(interactiveMoveResizeSteppedSpy.count(), 0); - QVERIFY(window->isInteractiveResize()); -@@ -1503,7 +1502,6 @@ void X11WindowTest::testNetWmKeyboardResize() - Test::keyboardKeyPressed(KEY_RIGHT, timestamp++); - Test::keyboardKeyReleased(KEY_RIGHT, timestamp++); - QCOMPARE(interactiveMoveResizeSteppedSpy.count(), 1); -- QVERIFY(frameGeometryChangedSpy.wait()); - QCOMPARE(window->frameGeometry(), originalGeometry.adjusted(0, 0, 8, 0)); - - // Finish the interactive move. -@@ -1531,7 +1529,6 @@ void X11WindowTest::testNetWmKeyboardResizeCancel() - QSignalSpy interactiveMoveResizeStartedSpy(window, &X11Window::interactiveMoveResizeStarted); - QSignalSpy interactiveMoveResizeFinishedSpy(window, &X11Window::interactiveMoveResizeFinished); - QSignalSpy interactiveMoveResizeSteppedSpy(window, &X11Window::interactiveMoveResizeStepped); -- QSignalSpy frameGeometryChangedSpy(window, &X11Window::frameGeometryChanged); - QVERIFY(interactiveMoveResizeStartedSpy.wait()); - QCOMPARE(interactiveMoveResizeSteppedSpy.count(), 0); - QVERIFY(window->isInteractiveResize()); -@@ -1542,7 +1539,6 @@ void X11WindowTest::testNetWmKeyboardResizeCancel() - Test::keyboardKeyPressed(KEY_RIGHT, timestamp++); - Test::keyboardKeyReleased(KEY_RIGHT, timestamp++); - QCOMPARE(interactiveMoveResizeSteppedSpy.count(), 1); -- QVERIFY(frameGeometryChangedSpy.wait()); - QCOMPARE(window->frameGeometry(), originalGeometry.adjusted(0, 0, 8, 0)); - - // Cancel the interactive move. -@@ -1741,7 +1737,6 @@ void X11WindowTest::testNetWmButtonSize() - QSignalSpy interactiveMoveResizeStartedSpy(window, &X11Window::interactiveMoveResizeStarted); - QSignalSpy interactiveMoveResizeFinishedSpy(window, &X11Window::interactiveMoveResizeFinished); - QSignalSpy interactiveMoveResizeSteppedSpy(window, &X11Window::interactiveMoveResizeStepped); -- QSignalSpy frameGeometryChangedSpy(window, &X11Window::frameGeometryChanged); - QVERIFY(interactiveMoveResizeStartedSpy.wait()); - QCOMPARE(interactiveMoveResizeSteppedSpy.count(), 0); - QVERIFY(window->isInteractiveResize()); -@@ -1750,7 +1745,6 @@ void X11WindowTest::testNetWmButtonSize() - // Resize the window a tiny bit. - Test::pointerMotionRelative(directionToVector(direction, QSizeF(8, 8)), timestamp++); - QCOMPARE(interactiveMoveResizeSteppedSpy.count(), 1); -- QVERIFY(frameGeometryChangedSpy.wait()); - QCOMPARE(window->frameGeometry(), expandRect(originalGeometry, direction, QSizeF(8, 8))); - - // Finish the interactive move. -@@ -1803,7 +1797,6 @@ void X11WindowTest::testNetWmButtonSizeCancel() - QSignalSpy interactiveMoveResizeStartedSpy(window, &X11Window::interactiveMoveResizeStarted); - QSignalSpy interactiveMoveResizeFinishedSpy(window, &X11Window::interactiveMoveResizeFinished); - QSignalSpy interactiveMoveResizeSteppedSpy(window, &X11Window::interactiveMoveResizeStepped); -- QSignalSpy frameGeometryChangedSpy(window, &X11Window::frameGeometryChanged); - QVERIFY(interactiveMoveResizeStartedSpy.wait()); - QCOMPARE(interactiveMoveResizeSteppedSpy.count(), 0); - QVERIFY(window->isInteractiveResize()); -@@ -1811,7 +1804,6 @@ void X11WindowTest::testNetWmButtonSizeCancel() - // Resize the window a tiny bit. - Test::pointerMotionRelative(QPointF(-8, -8), timestamp++); - QCOMPARE(interactiveMoveResizeSteppedSpy.count(), 1); -- QVERIFY(frameGeometryChangedSpy.wait()); - QCOMPARE(window->frameGeometry(), originalGeometry.adjusted(-8, -8, 0, 0)); - - // Cancel the interactive resize. -@@ -1888,7 +1880,7 @@ void X11WindowTest::testMinimumSize() - window->updateInteractiveMoveResize(KWin::Cursors::self()->mouse()->pos(), Qt::KeyboardModifiers()); - QCOMPARE(KWin::Cursors::self()->mouse()->pos(), cursorPos + QPoint(8, 0)); - QCOMPARE(interactiveMoveResizeSteppedSpy.count(), 1); -- QVERIFY(frameGeometryChangedSpy.wait()); -+ QVERIFY(!frameGeometryChangedSpy.wait(10)); - // whilst X11 window size goes through scale, the increment is a logical value kwin side - QCOMPARE(window->clientSize().width(), 100 / scale + 8); - -@@ -1910,7 +1902,7 @@ void X11WindowTest::testMinimumSize() - window->updateInteractiveMoveResize(KWin::Cursors::self()->mouse()->pos(), Qt::KeyboardModifiers()); - QCOMPARE(KWin::Cursors::self()->mouse()->pos(), cursorPos + QPoint(8, 8)); - QCOMPARE(interactiveMoveResizeSteppedSpy.count(), 2); -- QVERIFY(frameGeometryChangedSpy.wait()); -+ QVERIFY(!frameGeometryChangedSpy.wait(10)); - QCOMPARE(window->clientSize().height(), 200 / scale + 8); - - // Finish the resize operation. -@@ -1993,7 +1985,7 @@ void X11WindowTest::testMaximumSize() - window->updateInteractiveMoveResize(KWin::Cursors::self()->mouse()->pos(), Qt::KeyboardModifiers()); - QCOMPARE(KWin::Cursors::self()->mouse()->pos(), cursorPos + QPoint(-8, 0)); - QCOMPARE(interactiveMoveResizeSteppedSpy.count(), 1); -- QVERIFY(frameGeometryChangedSpy.wait()); -+ QVERIFY(!frameGeometryChangedSpy.wait(10)); - QCOMPARE(window->clientSize().width(), 100 / scale - 8); - - window->keyPressEvent(Qt::Key_Down); -@@ -2014,7 +2006,7 @@ void X11WindowTest::testMaximumSize() - window->updateInteractiveMoveResize(KWin::Cursors::self()->mouse()->pos(), Qt::KeyboardModifiers()); - QCOMPARE(KWin::Cursors::self()->mouse()->pos(), cursorPos + QPoint(-8, -8)); - QCOMPARE(interactiveMoveResizeSteppedSpy.count(), 2); -- QVERIFY(frameGeometryChangedSpy.wait()); -+ QVERIFY(!frameGeometryChangedSpy.wait(10)); - QCOMPARE(window->clientSize().height(), 200 / scale - 8); - - // Finish the resize operation. -diff --git a/src/x11window.cpp b/src/x11window.cpp -index b71a9d3640..780ccfbc7e 100644 ---- a/src/x11window.cpp -+++ b/src/x11window.cpp -@@ -4857,36 +4857,27 @@ void X11Window::doInteractiveResizeSync(const QRectF &rect) - return; - } - -- setMoveResizeGeometry(moveResizeFrameGeometry); -- setAllowCommits(false); -+ if (m_syncRequest.counter == XCB_NONE) { -+ moveResize(rect); -+ } else { -+ if (!m_syncRequest.timeout) { -+ m_syncRequest.timeout = new QTimer(this); -+ connect(m_syncRequest.timeout, &QTimer::timeout, this, &X11Window::handleSyncTimeout); -+ m_syncRequest.timeout->setSingleShot(true); -+ } - -- if (!m_syncRequest.timeout) { -- m_syncRequest.timeout = new QTimer(this); -- connect(m_syncRequest.timeout, &QTimer::timeout, this, &X11Window::handleSyncTimeout); -- m_syncRequest.timeout->setSingleShot(true); -- } -+ setMoveResizeGeometry(moveResizeFrameGeometry); -+ setAllowCommits(false); - -- if (m_syncRequest.counter != XCB_NONE) { -- m_syncRequest.timeout->start(250); - sendSyncRequest(); -- } else { -- // For clients not supporting the XSYNC protocol, we limit the resizes to 30Hz -- // to take pointless load from X11 and the client, the mouse is still moved at -- // full speed and no human can control faster resizes anyway. -- m_syncRequest.isPending = true; -- m_syncRequest.interactiveResize = true; -- m_syncRequest.timeout->start(33); -- } -+ configure(nativeFrameGeometry, nativeWrapperGeometry, nativeClientGeometry); - -- configure(nativeFrameGeometry, nativeWrapperGeometry, nativeClientGeometry); -+ m_syncRequest.timeout->start(250); -+ } - } - - void X11Window::handleSyncTimeout() - { -- if (m_syncRequest.counter == XCB_NONE) { // client w/o XSYNC support. allow the next resize event -- m_syncRequest.isPending = false; // NEVER do this for clients with a valid counter -- m_syncRequest.interactiveResize = false; // (leads to sync request races in some clients) -- } - performInteractiveResize(); - } - --- -2.47.0 - - -From 1be3dfa4b40732997ecb1bd1959ba775331d603a Mon Sep 17 00:00:00 2001 -From: Vlad Zahorodnii -Date: Fri, 4 Oct 2024 22:55:44 +0300 -Subject: [PATCH 2/9] Rename X11Window::handleSync,handleSyncTimeout - ---- - src/syncalarmx11filter.cpp | 2 +- - src/x11window.cpp | 6 +++--- - src/x11window.h | 4 ++-- - 3 files changed, 6 insertions(+), 6 deletions(-) - -diff --git a/src/syncalarmx11filter.cpp b/src/syncalarmx11filter.cpp -index f4b4d49caa..133c3c6b99 100644 ---- a/src/syncalarmx11filter.cpp -+++ b/src/syncalarmx11filter.cpp -@@ -28,7 +28,7 @@ bool SyncAlarmX11Filter::event(xcb_generic_event_t *event) - return alarmEvent->alarm == syncRequest.alarm && alarmEvent->counter_value.hi == syncRequest.value.hi && alarmEvent->counter_value.lo == syncRequest.value.lo; - }); - if (client) { -- client->handleSync(); -+ client->ackSync(); - } - return false; - } -diff --git a/src/x11window.cpp b/src/x11window.cpp -index 780ccfbc7e..14d45abb27 100644 ---- a/src/x11window.cpp -+++ b/src/x11window.cpp -@@ -3059,7 +3059,7 @@ void X11Window::checkApplicationMenuObjectPath() - readApplicationMenuObjectPath(property); - } - --void X11Window::handleSync() -+void X11Window::ackSync() - { - setReadyForPainting(); - m_syncRequest.isPending = false; -@@ -4862,7 +4862,7 @@ void X11Window::doInteractiveResizeSync(const QRectF &rect) - } else { - if (!m_syncRequest.timeout) { - m_syncRequest.timeout = new QTimer(this); -- connect(m_syncRequest.timeout, &QTimer::timeout, this, &X11Window::handleSyncTimeout); -+ connect(m_syncRequest.timeout, &QTimer::timeout, this, &X11Window::ackSyncTimeout); - m_syncRequest.timeout->setSingleShot(true); - } - -@@ -4876,7 +4876,7 @@ void X11Window::doInteractiveResizeSync(const QRectF &rect) - } - } - --void X11Window::handleSyncTimeout() -+void X11Window::ackSyncTimeout() - { - performInteractiveResize(); - } -diff --git a/src/x11window.h b/src/x11window.h -index 4f3b43c8b0..b54db23345 100644 ---- a/src/x11window.h -+++ b/src/x11window.h -@@ -293,8 +293,8 @@ public: - return m_syncRequest; - } - bool wantsSyncCounter() const; -- void handleSync(); -- void handleSyncTimeout(); -+ void ackSync(); -+ void ackSyncTimeout(); - - bool allowWindowActivation(xcb_timestamp_t time = -1U, bool focus_in = false); - --- -2.47.0 - - -From ea53098c38b64cb5870ef4f3b47fb53f509e03a4 Mon Sep 17 00:00:00 2001 -From: Vlad Zahorodnii -Date: Fri, 4 Oct 2024 22:59:34 +0300 -Subject: [PATCH 3/9] Rename X11Window::SyncRequest::isPending - ---- - src/x11window.cpp | 12 ++++++------ - src/x11window.h | 2 +- - 2 files changed, 7 insertions(+), 7 deletions(-) - -diff --git a/src/x11window.cpp b/src/x11window.cpp -index 14d45abb27..2ebb4a5957 100644 ---- a/src/x11window.cpp -+++ b/src/x11window.cpp -@@ -309,7 +309,7 @@ X11Window::X11Window() - m_syncRequest.counter = m_syncRequest.alarm = XCB_NONE; - m_syncRequest.timeout = m_syncRequest.failsafeTimeout = nullptr; - m_syncRequest.lastTimestamp = xTime(); -- m_syncRequest.isPending = false; -+ m_syncRequest.pending = false; - m_syncRequest.interactiveResize = false; - - // Set the initial mapping state -@@ -2585,7 +2585,7 @@ void X11Window::getSyncCounter() - */ - void X11Window::sendSyncRequest() - { -- if (m_syncRequest.counter == XCB_NONE || m_syncRequest.isPending) { -+ if (m_syncRequest.counter == XCB_NONE || m_syncRequest.pending) { - return; // do NOT, NEVER send a sync request when there's one on the stack. the clients will just stop respoding. FOREVER! ... - } - -@@ -2599,7 +2599,7 @@ void X11Window::sendSyncRequest() - return; - } - // failed during resize -- m_syncRequest.isPending = false; -+ m_syncRequest.pending = false; - m_syncRequest.interactiveResize = false; - m_syncRequest.counter = XCB_NONE; - m_syncRequest.alarm = XCB_NONE; -@@ -2630,7 +2630,7 @@ void X11Window::sendSyncRequest() - // Send the message to client - sendClientMessage(window(), atoms->wm_protocols, atoms->net_wm_sync_request, - m_syncRequest.value.lo, m_syncRequest.value.hi); -- m_syncRequest.isPending = true; -+ m_syncRequest.pending = true; - m_syncRequest.interactiveResize = isInteractiveResize(); - m_syncRequest.lastTimestamp = xTime(); - } -@@ -3062,7 +3062,7 @@ void X11Window::checkApplicationMenuObjectPath() - void X11Window::ackSync() - { - setReadyForPainting(); -- m_syncRequest.isPending = false; -+ m_syncRequest.pending = false; - if (m_syncRequest.failsafeTimeout) { - m_syncRequest.failsafeTimeout->stop(); - } -@@ -4840,7 +4840,7 @@ void X11Window::leaveInteractiveMoveResize() - - bool X11Window::isWaitingForInteractiveResizeSync() const - { -- return m_syncRequest.isPending && m_syncRequest.interactiveResize; -+ return m_syncRequest.pending && m_syncRequest.interactiveResize; - } - - void X11Window::doInteractiveResizeSync(const QRectF &rect) -diff --git a/src/x11window.h b/src/x11window.h -index b54db23345..10fa57eaf2 100644 ---- a/src/x11window.h -+++ b/src/x11window.h -@@ -285,7 +285,7 @@ public: - xcb_sync_alarm_t alarm; - xcb_timestamp_t lastTimestamp; - QTimer *timeout, *failsafeTimeout; -- bool isPending; -+ bool pending; - bool interactiveResize; - }; - const SyncRequest &syncRequest() const --- -2.47.0 - - -From 49319a3f3e2a1d468e12bdf3dfd7eeb2e3db0899 Mon Sep 17 00:00:00 2001 -From: Vlad Zahorodnii -Date: Fri, 4 Oct 2024 23:12:44 +0300 -Subject: [PATCH 4/9] Make XSync timeout more permissive - -There are two timeouts: a soft one and a hard one. If the soft timeout -occurs, then the scheduled operation will be performed. For example, -the window will be marked as ready for painting or resized. If the hard -timeout occurs, _NET_WM_SYNC_REQUEST will be disabled. - -Such an arrangement is fine in its current present form, but in order -to make Xwayland window resizing less glitchy, _NET_WM_SYNC_REQUEST -code needs to interact with wl_surface event flow, which in its turn -means that the timeout code also needs to be prepared for that. It is -very challenging to make the timeout code not break, so this change -seeks to re-arrange the timeout handling so it's easier to integrate -with wl_surface commits. - -With the proposed changes, there is going to be only one timer: timeout. -If it fires, the window will be resized immediately and XSync is going -to be kept disabled until an acknowledgement arrives from the client. ---- - src/x11window.cpp | 101 +++++++++++++++++----------------------------- - src/x11window.h | 5 ++- - 2 files changed, 41 insertions(+), 65 deletions(-) - -diff --git a/src/x11window.cpp b/src/x11window.cpp -index 2ebb4a5957..1d25ab82b1 100644 ---- a/src/x11window.cpp -+++ b/src/x11window.cpp -@@ -307,8 +307,9 @@ X11Window::X11Window() - - // TODO: Do all as initialization - m_syncRequest.counter = m_syncRequest.alarm = XCB_NONE; -- m_syncRequest.timeout = m_syncRequest.failsafeTimeout = nullptr; -+ m_syncRequest.timeout = nullptr; - m_syncRequest.lastTimestamp = xTime(); -+ m_syncRequest.enabled = false; - m_syncRequest.pending = false; - m_syncRequest.interactiveResize = false; - -@@ -460,9 +461,6 @@ void X11Window::releaseWindow(bool on_shutdown) - ungrabXServer(); - } - -- if (m_syncRequest.failsafeTimeout) { -- m_syncRequest.failsafeTimeout->stop(); -- } - if (m_syncRequest.timeout) { - m_syncRequest.timeout->stop(); - } -@@ -516,9 +514,6 @@ void X11Window::destroyWindow() - m_frame.reset(); - } - -- if (m_syncRequest.failsafeTimeout) { -- m_syncRequest.failsafeTimeout->stop(); -- } - if (m_syncRequest.timeout) { - m_syncRequest.timeout->stop(); - } -@@ -2550,6 +2545,7 @@ void X11Window::getSyncCounter() - 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) { -+ m_syncRequest.enabled = true; - m_syncRequest.counter = counter; - m_syncRequest.value.hi = 0; - m_syncRequest.value.lo = 0; -@@ -2585,35 +2581,16 @@ void X11Window::getSyncCounter() - */ - void X11Window::sendSyncRequest() - { -- if (m_syncRequest.counter == XCB_NONE || m_syncRequest.pending) { -+ if (!m_syncRequest.enabled || m_syncRequest.pending) { - return; // do NOT, NEVER send a sync request when there's one on the stack. the clients will just stop respoding. FOREVER! ... - } - -- if (!m_syncRequest.failsafeTimeout) { -- m_syncRequest.failsafeTimeout = new QTimer(this); -- connect(m_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(); -- return; -- } -- // failed during resize -- m_syncRequest.pending = false; -- m_syncRequest.interactiveResize = false; -- m_syncRequest.counter = XCB_NONE; -- m_syncRequest.alarm = XCB_NONE; -- delete m_syncRequest.timeout; -- delete m_syncRequest.failsafeTimeout; -- m_syncRequest.timeout = nullptr; -- m_syncRequest.failsafeTimeout = nullptr; -- m_syncRequest.lastTimestamp = XCB_CURRENT_TIME; -- }); -- m_syncRequest.failsafeTimeout->setSingleShot(true); -+ if (!m_syncRequest.timeout) { -+ m_syncRequest.timeout = new QTimer(this); -+ m_syncRequest.timeout->setSingleShot(true); -+ connect(m_syncRequest.timeout, &QTimer::timeout, this, &X11Window::ackSyncTimeout); - } -- // if there's no response within 10 seconds, sth. went wrong and we remove XSYNC support from this client. -- // see events.cpp X11Window::syncEvent() -- m_syncRequest.failsafeTimeout->start(ready_for_painting ? 10000 : 1000); -+ m_syncRequest.timeout->start(ready_for_painting ? 10000 : 1000); - - // We increment before the notify so that after the notify - // syncCounterSerial will equal the value we are expecting -@@ -3061,29 +3038,40 @@ void X11Window::checkApplicationMenuObjectPath() - - void X11Window::ackSync() - { -- setReadyForPainting(); -+ // Note that a sync request can be ack'ed after the timeout. If that happens, just re-enable -+ // XSync back and do nothing more. - m_syncRequest.pending = false; -- if (m_syncRequest.failsafeTimeout) { -- m_syncRequest.failsafeTimeout->stop(); -+ if (!m_syncRequest.enabled) { -+ m_syncRequest.enabled = true; -+ return; - } - -- // Sync request can be acknowledged shortly after finishing resize. -+ m_syncRequest.timeout->stop(); -+ -+ finishSync(); -+} -+ -+void X11Window::ackSyncTimeout() -+{ -+ // If a sync request times out, disable XSync temporarily until the client comes back to its senses. -+ m_syncRequest.enabled = false; -+ -+ finishSync(); -+} -+ -+void X11Window::finishSync() -+{ -+ setReadyForPainting(); -+ - if (m_syncRequest.interactiveResize) { - m_syncRequest.interactiveResize = false; -- if (m_syncRequest.timeout) { -- m_syncRequest.timeout->stop(); -- } -- performInteractiveResize(); -+ -+ moveResize(moveResizeGeometry()); - updateWindowPixmap(); -+ setAllowCommits(true); - } - } - --void X11Window::performInteractiveResize() --{ -- resize(moveResizeGeometry().size()); -- setAllowCommits(true); --} -- - bool X11Window::belongToSameApplication(const X11Window *c1, const X11Window *c2, SameApplicationChecks checks) - { - bool same_app = false; -@@ -4840,7 +4828,7 @@ void X11Window::leaveInteractiveMoveResize() - - bool X11Window::isWaitingForInteractiveResizeSync() const - { -- return m_syncRequest.pending && m_syncRequest.interactiveResize; -+ return m_syncRequest.enabled && m_syncRequest.pending && m_syncRequest.interactiveResize; - } - - void X11Window::doInteractiveResizeSync(const QRectF &rect) -@@ -4857,30 +4845,17 @@ void X11Window::doInteractiveResizeSync(const QRectF &rect) - return; - } - -- if (m_syncRequest.counter == XCB_NONE) { -+ if (!m_syncRequest.enabled) { - moveResize(rect); - } else { -- if (!m_syncRequest.timeout) { -- m_syncRequest.timeout = new QTimer(this); -- connect(m_syncRequest.timeout, &QTimer::timeout, this, &X11Window::ackSyncTimeout); -- m_syncRequest.timeout->setSingleShot(true); -- } -- - setMoveResizeGeometry(moveResizeFrameGeometry); - setAllowCommits(false); - - sendSyncRequest(); - configure(nativeFrameGeometry, nativeWrapperGeometry, nativeClientGeometry); -- -- m_syncRequest.timeout->start(250); - } - } - --void X11Window::ackSyncTimeout() --{ -- performInteractiveResize(); --} -- - NETExtendedStrut X11Window::strut() const - { - NETExtendedStrut ext = info->extendedStrut(); -@@ -4991,7 +4966,7 @@ void X11Window::damageNotifyEvent() - Q_ASSERT(kwinApp()->operationMode() == Application::OperationModeX11); - - if (!readyForPainting()) { // avoid "setReadyForPainting()" function calling overhead -- if (m_syncRequest.counter == XCB_NONE) { // cannot detect complete redraw, consider done now -+ if (!m_syncRequest.enabled) { // cannot detect complete redraw, consider done now - setReadyForPainting(); - } - } -@@ -5025,7 +5000,7 @@ void X11Window::updateWindowPixmap() - void X11Window::associate() - { - auto handleMapped = [this]() { -- if (syncRequest().counter == XCB_NONE) { // cannot detect complete redraw, consider done now -+ if (!m_syncRequest.enabled) { // cannot detect complete redraw, consider done now - setReadyForPainting(); - } - }; -diff --git a/src/x11window.h b/src/x11window.h -index 10fa57eaf2..6b430b861a 100644 ---- a/src/x11window.h -+++ b/src/x11window.h -@@ -284,7 +284,8 @@ public: - xcb_sync_int64_t value; - xcb_sync_alarm_t alarm; - xcb_timestamp_t lastTimestamp; -- QTimer *timeout, *failsafeTimeout; -+ QTimer *timeout; -+ bool enabled; - bool pending; - bool interactiveResize; - }; -@@ -295,6 +296,7 @@ public: - bool wantsSyncCounter() const; - void ackSync(); - void ackSyncTimeout(); -+ void finishSync(); - - bool allowWindowActivation(xcb_timestamp_t time = -1U, bool focus_in = false); - -@@ -385,7 +387,6 @@ private: - void getSyncCounter(); - void sendSyncRequest(); - void leaveInteractiveMoveResize() override; -- void performInteractiveResize(); - void establishCommandWindowGrab(uint8_t button); - void establishCommandAllGrab(uint8_t button); - --- -2.47.0 - - -From 79e1853e9ecd20c1d9549399b46d434475b753bd Mon Sep 17 00:00:00 2001 -From: Vlad Zahorodnii -Date: Fri, 4 Oct 2024 23:20:03 +0300 -Subject: [PATCH 5/9] Disable Xwayland surface commits for all sync requests - ---- - src/x11window.cpp | 7 +++---- - 1 file changed, 3 insertions(+), 4 deletions(-) - -diff --git a/src/x11window.cpp b/src/x11window.cpp -index 1d25ab82b1..5688ea6da8 100644 ---- a/src/x11window.cpp -+++ b/src/x11window.cpp -@@ -2604,7 +2604,7 @@ void X11Window::sendSyncRequest() - kwinApp()->updateXTime(); - } - -- // Send the message to client -+ setAllowCommits(false); - sendClientMessage(window(), atoms->wm_protocols, atoms->net_wm_sync_request, - m_syncRequest.value.lo, m_syncRequest.value.hi); - m_syncRequest.pending = true; -@@ -3049,6 +3049,7 @@ void X11Window::ackSync() - m_syncRequest.timeout->stop(); - - finishSync(); -+ setAllowCommits(true); - } - - void X11Window::ackSyncTimeout() -@@ -3057,6 +3058,7 @@ void X11Window::ackSyncTimeout() - m_syncRequest.enabled = false; - - finishSync(); -+ setAllowCommits(true); - } - - void X11Window::finishSync() -@@ -3068,7 +3070,6 @@ void X11Window::finishSync() - - moveResize(moveResizeGeometry()); - updateWindowPixmap(); -- setAllowCommits(true); - } - } - -@@ -4849,8 +4850,6 @@ void X11Window::doInteractiveResizeSync(const QRectF &rect) - moveResize(rect); - } else { - setMoveResizeGeometry(moveResizeFrameGeometry); -- setAllowCommits(false); -- - sendSyncRequest(); - configure(nativeFrameGeometry, nativeWrapperGeometry, nativeClientGeometry); - } --- -2.47.0 - - -From 038a798c8be170eb5b7132b823d77474d118ca42 Mon Sep 17 00:00:00 2001 -From: Vlad Zahorodnii -Date: Fri, 4 Oct 2024 23:40:41 +0300 -Subject: [PATCH 6/9] Make Xwayland resizing less glitchy - -XSYNC requests and wl_surface commits are unsynchronized. This results -in the window bouncing when it's being resized. - -Let's consider a concrete example. Assume that there is a window with 0,0 -100x100 geometry and it is being resized by dragging the left edge. If -the left edge is dragged by 10px, the following will occur: - -- wl_surface commits will be blocked (it is needed to ensure the - consistent order of XSync acknowledgements and wl_surface commits) -- an XSync request will be sent -- a ConfigureNotify event will be sent -- a client processes the ConfigureNotify by repainting the window and - acknowledges the XSync request -- kwin notices that the XSync request has been acked and updates the - window position to 10,0 and unblocks surface commits - -The problem is that it can take a while for Xwayland to attach a new -buffer to the surface with a size of 90x100. If kwin composes a frame in -meanwhile, the effective geometry of the window will be 10,0 100x100, -i.e. the right window edge will stick by 10px. Some time later, Xwayland -would attach a buffer with the right size (90x100), and the right window -edge will be put back in the right place. - -In order to address the bouncing, this change reworks how the -synchronization is performed: - -- when a window is asked to resize, kwin will freeze wl_surface commits, - send a sync request, and configure the window -- after the client repaints the window, it acks the sync request -- when kwin sees that the sync request has been acked, it will unfreeze - the wl_surface commits. And here's the most important part: it will NOT - update the window position until Xwayland commits something -- when Xwayland commits the wl_surface, kwin will update the window geometry - -It's important to wait until Xwayland commits the wl_surface because, as -it was said previously, it can take a while until Xwayland commits the -wl_surface and kwin could compose a frame with the new position but old -surface size in meanwhile. - -BUG: 486464 ---- - src/x11window.cpp | 40 +++++++++++++++++++++++++++++----------- - src/x11window.h | 2 ++ - 2 files changed, 31 insertions(+), 11 deletions(-) - -diff --git a/src/x11window.cpp b/src/x11window.cpp -index 5688ea6da8..b22de557d8 100644 ---- a/src/x11window.cpp -+++ b/src/x11window.cpp -@@ -311,6 +311,7 @@ X11Window::X11Window() - m_syncRequest.lastTimestamp = xTime(); - m_syncRequest.enabled = false; - m_syncRequest.pending = false; -+ m_syncRequest.acked = false; - m_syncRequest.interactiveResize = false; - - // Set the initial mapping state -@@ -3046,9 +3047,13 @@ void X11Window::ackSync() - return; - } - -+ m_syncRequest.acked = true; - m_syncRequest.timeout->stop(); - -- finishSync(); -+ // With Xwayland, the sync request will be completed after the wl_surface is committed. -+ if (!waylandServer()) { -+ finishSync(); -+ } - setAllowCommits(true); - } - -@@ -3071,6 +3076,8 @@ void X11Window::finishSync() - moveResize(moveResizeGeometry()); - updateWindowPixmap(); - } -+ -+ m_syncRequest.acked = false; - } - - bool X11Window::belongToSameApplication(const X11Window *c1, const X11Window *c2, SameApplicationChecks checks) -@@ -3945,6 +3952,19 @@ void X11Window::handleXwaylandScaleChanged() - resize(moveResizeGeometry().size()); - } - -+void X11Window::handleCommitted() -+{ -+ if (surface()->isMapped()) { -+ if (m_syncRequest.acked) { -+ finishSync(); -+ } -+ -+ if (!m_syncRequest.enabled) { -+ setReadyForPainting(); -+ } -+ } -+} -+ - void X11Window::setAllowCommits(bool allow) - { - if (!waylandServer()) { -@@ -4829,7 +4849,7 @@ void X11Window::leaveInteractiveMoveResize() - - bool X11Window::isWaitingForInteractiveResizeSync() const - { -- return m_syncRequest.enabled && m_syncRequest.pending && m_syncRequest.interactiveResize; -+ return m_syncRequest.enabled && m_syncRequest.interactiveResize && (m_syncRequest.pending || m_syncRequest.acked); - } - - void X11Window::doInteractiveResizeSync(const QRectF &rect) -@@ -4998,19 +5018,17 @@ void X11Window::updateWindowPixmap() - - void X11Window::associate() - { -- auto handleMapped = [this]() { -- if (!m_syncRequest.enabled) { // cannot detect complete redraw, consider done now -- setReadyForPainting(); -+ if (surface()->isMapped()) { -+ if (m_syncRequest.acked) { -+ finishSync(); - } -- }; - -- if (surface()->isMapped()) { -- handleMapped(); -- } else { -- connect(surface(), &SurfaceInterface::mapped, this, handleMapped); -+ if (!m_syncRequest.enabled) { -+ setReadyForPainting(); -+ } - } - -- m_pendingSurfaceId = 0; -+ connect(surface(), &SurfaceInterface::committed, this, &X11Window::handleCommitted); - } - - QWindow *X11Window::findInternalWindow() const -diff --git a/src/x11window.h b/src/x11window.h -index 6b430b861a..73d4c23a4c 100644 ---- a/src/x11window.h -+++ b/src/x11window.h -@@ -287,6 +287,7 @@ public: - QTimer *timeout; - bool enabled; - bool pending; -+ bool acked; - bool interactiveResize; - }; - const SyncRequest &syncRequest() const -@@ -438,6 +439,7 @@ private: - void checkOutput(); - void associate(); - void handleXwaylandScaleChanged(); -+ void handleCommitted(); - - void setAllowCommits(bool allow); - --- -2.47.0 - - -From 10c5298221f5dc3cc90255846580b608f6f3c18c Mon Sep 17 00:00:00 2001 -From: Vlad Zahorodnii -Date: Thu, 3 Oct 2024 00:43:57 +0300 -Subject: [PATCH 7/9] scene: Use standard wl_surface item for Xwayland surfaces - -Besides unifying the code, it fixes some visual glitches caused by the -opaque region getting out of sync. - -Xwayland MR: https://gitlab.freedesktop.org/xorg/xserver/-/merge_requests/1698 ---- - src/scene/surfaceitem_wayland.cpp | 36 ------------------------------- - src/scene/surfaceitem_wayland.h | 20 ----------------- - src/scene/windowitem.cpp | 2 +- - 3 files changed, 1 insertion(+), 57 deletions(-) - -diff --git a/src/scene/surfaceitem_wayland.cpp b/src/scene/surfaceitem_wayland.cpp -index 4a307565c0..04961952b9 100644 ---- a/src/scene/surfaceitem_wayland.cpp -+++ b/src/scene/surfaceitem_wayland.cpp -@@ -12,11 +12,6 @@ - #include "wayland/linuxdmabufv1clientbuffer.h" - #include "wayland/subcompositor.h" - #include "wayland/surface.h" --#include "window.h" -- --#if KWIN_BUILD_X11 --#include "x11window.h" --#endif - - namespace KWin - { -@@ -247,37 +242,6 @@ bool SurfacePixmapWayland::isValid() const - return m_bufferRef; - } - --#if KWIN_BUILD_X11 --SurfaceItemXwayland::SurfaceItemXwayland(X11Window *window, Item *parent) -- : SurfaceItemWayland(window->surface(), parent) -- , m_window(window) --{ -- connect(window, &X11Window::shapeChanged, this, &SurfaceItemXwayland::discardQuads); --} -- --QList SurfaceItemXwayland::shape() const --{ -- QList shape = m_window->shapeRegion(); -- for (QRectF &shapePart : shape) { -- shapePart = shapePart.intersected(rect()); -- } -- return shape; --} -- --QRegion SurfaceItemXwayland::opaque() const --{ -- QRegion shapeRegion; -- for (const QRectF &shapePart : shape()) { -- shapeRegion += shapePart.toRect(); -- } -- if (!m_window->hasAlpha()) { -- return shapeRegion; -- } else { -- return m_window->opaqueRegion() & shapeRegion; -- } -- return QRegion(); --} --#endif - } // namespace KWin - - #include "moc_surfaceitem_wayland.cpp" -diff --git a/src/scene/surfaceitem_wayland.h b/src/scene/surfaceitem_wayland.h -index f769284172..fbc7498446 100644 ---- a/src/scene/surfaceitem_wayland.h -+++ b/src/scene/surfaceitem_wayland.h -@@ -16,7 +16,6 @@ namespace KWin - class GraphicsBuffer; - class SubSurfaceInterface; - class SurfaceInterface; --class X11Window; - - /** - * The SurfaceItemWayland class represents a Wayland surface in the scene. -@@ -83,23 +82,4 @@ private: - SurfaceItemWayland *m_item; - }; - --#if KWIN_BUILD_X11 --/** -- * The SurfaceItemXwayland class represents an Xwayland surface in the scene. -- */ --class KWIN_EXPORT SurfaceItemXwayland : public SurfaceItemWayland --{ -- Q_OBJECT -- --public: -- explicit SurfaceItemXwayland(X11Window *window, Item *parent = nullptr); -- -- QRegion opaque() const override; -- QList shape() const override; -- --private: -- X11Window *m_window; --}; --#endif -- - } // namespace KWin -diff --git a/src/scene/windowitem.cpp b/src/scene/windowitem.cpp -index 4283051e7e..913d2708f5 100644 ---- a/src/scene/windowitem.cpp -+++ b/src/scene/windowitem.cpp -@@ -323,7 +323,7 @@ void WindowItemX11::initialize() - if (!window()->surface()) { - updateSurfaceItem(nullptr); - } else { -- updateSurfaceItem(std::make_unique(static_cast(window()), this)); -+ updateSurfaceItem(std::make_unique(window()->surface(), this)); - } - break; - case Application::OperationModeWaylandOnly: --- -2.47.0 - - -From 91ca5127fbf590a383fe311b76da8dce553e2a8a Mon Sep 17 00:00:00 2001 -From: Vlad Zahorodnii -Date: Mon, 14 Oct 2024 00:34:28 +0300 -Subject: [PATCH 8/9] Send the initial sync request before mapping the frame - window - -When the wl_surface commits are blocked by the initial sync request, it -is really important that Xwayland doesn't commit the surface until the -sync request is acked. However, with the current arrangement of the code, -Xwayland may render something before the sync request is acked. - -It can happen because the frame window is mapped before the initial sync -request is sent. There are many places where it can happen, e.g. in the -setupCompositing() function, or the setMinimized() function, etc. - -In order to ensure that Xwayland won't render the wl_surface while we -are waiting for the sync request to get acked, this change moves the initial -sync request all the way to the top of the manage() function so the -surface commits are blocked before the frame window is mapped. ---- - src/x11window.cpp | 20 ++++++++++++-------- - 1 file changed, 12 insertions(+), 8 deletions(-) - -diff --git a/src/x11window.cpp b/src/x11window.cpp -index b22de557d8..be35fe07fd 100644 ---- a/src/x11window.cpp -+++ b/src/x11window.cpp -@@ -655,6 +655,18 @@ bool X11Window::manage(xcb_window_t w, bool isMapped) - getSyncCounter(); - setCaption(readName()); - -+ if (Compositor::compositing()) { -+ // Sending ConfigureNotify is done when setting mapping state below, getting the -+ // first sync response means window is ready for compositing. -+ // -+ // The sync request will block wl_surface commits, and with Xwayland, it is really -+ // important that wl_surfaces commits are blocked before the frame window is mapped. -+ // Otherwise Xwayland can attach a buffer before the sync request is acked. -+ sendSyncRequest(); -+ } else { -+ ready_for_painting = true; // set to true in case compositing is turned on later -+ } -+ - setupWindowRules(); - connect(this, &X11Window::windowClassChanged, this, &X11Window::evaluateWindowRules); - -@@ -1093,14 +1105,6 @@ bool X11Window::manage(xcb_window_t w, bool isMapped) - workspace()->restoreSessionStackingOrder(this); - } - -- if (Compositor::compositing()) { -- // Sending ConfigureNotify is done when setting mapping state below, -- // Getting the first sync response means window is ready for compositing -- sendSyncRequest(); -- } else { -- ready_for_painting = true; // set to true in case compositing is turned on later. bug #160393 -- } -- - if (isShown()) { - bool allow; - if (session) { --- -2.47.0 - - -From cffaa20e83b7d9de69dcc835f6ab5d2609a49be5 Mon Sep 17 00:00:00 2001 -From: Vlad Zahorodnii -Date: Thu, 17 Oct 2024 19:21:32 +0300 -Subject: [PATCH 9/9] Block interactive resizing if there is a pending XSync - request - -There cannot be two XSync requests in flight, the type of the sync -requests doesn't matter either, e.g. one for interactive resize or -one to determine whether the client has painted the initial frame. ---- - src/x11window.cpp | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/src/x11window.cpp b/src/x11window.cpp -index be35fe07fd..3e989f29bd 100644 ---- a/src/x11window.cpp -+++ b/src/x11window.cpp -@@ -4853,7 +4853,7 @@ void X11Window::leaveInteractiveMoveResize() - - bool X11Window::isWaitingForInteractiveResizeSync() const - { -- return m_syncRequest.enabled && m_syncRequest.interactiveResize && (m_syncRequest.pending || m_syncRequest.acked); -+ return m_syncRequest.enabled && (m_syncRequest.pending || m_syncRequest.acked); - } - - void X11Window::doInteractiveResizeSync(const QRectF &rect) --- -2.47.0 - diff --git a/roles/kde/patches/kwin-pr6844.patch b/roles/kde/patches/kwin-pr6844.patch deleted file mode 100644 index f6e5e63..0000000 --- a/roles/kde/patches/kwin-pr6844.patch +++ /dev/null @@ -1,41 +0,0 @@ -diff --git a/src/plugins/private/expolayout.cpp b/src/plugins/private/expolayout.cpp -index 52e8bd6616..d4f2a41acc 100644 ---- a/src/plugins/private/expolayout.cpp -+++ b/src/plugins/private/expolayout.cpp -@@ -7,6 +7,7 @@ - - #include "expolayout.h" - -+#include - #include - #include - #include -@@ -41,6 +42,12 @@ ExpoCell::~ExpoCell() - void ExpoCell::componentComplete() - { - QQuickItem::componentComplete(); -+ -+ QQmlProperty xProperty(this, "Kirigami.ScenePosition.x", qmlContext(this)); -+ xProperty.connectNotifySignal(this, SLOT(updateContentItemGeometry())); -+ QQmlProperty yProperty(this, "Kirigami.ScenePosition.y", qmlContext(this)); -+ yProperty.connectNotifySignal(this, SLOT(updateContentItemGeometry())); -+ - updateContentItemGeometry(); - } - -diff --git a/src/plugins/private/expolayout.h b/src/plugins/private/expolayout.h -index 0d4f9d5cde..d980c2aabb 100644 ---- a/src/plugins/private/expolayout.h -+++ b/src/plugins/private/expolayout.h -@@ -264,8 +264,10 @@ Q_SIGNALS: - void persistentKeyChanged(); - void bottomMarginChanged(); - --private: -+private Q_SLOTS: - void updateContentItemGeometry(); -+ -+private: - void updateLayout(); - - QString m_persistentKey; diff --git a/roles/kde/patches/kwin-pr6878.patch b/roles/kde/patches/kwin-pr6878.patch deleted file mode 100644 index 41fffc9..0000000 --- a/roles/kde/patches/kwin-pr6878.patch +++ /dev/null @@ -1,753 +0,0 @@ -diff --git a/src/plugins/zoom/CMakeLists.txt b/src/plugins/zoom/CMakeLists.txt -index c27bdbbb2d..b5fb343e17 100644 ---- a/src/plugins/zoom/CMakeLists.txt -+++ b/src/plugins/zoom/CMakeLists.txt -@@ -4,6 +4,7 @@ - set(zoom_SOURCES - main.cpp - zoom.cpp -+ zoom.qrc - ) - - if (HAVE_ACCESSIBILITY) -diff --git a/src/plugins/zoom/shaders/pixelgrid.frag b/src/plugins/zoom/shaders/pixelgrid.frag -new file mode 100644 -index 0000000000..1e9ee9431f ---- /dev/null -+++ b/src/plugins/zoom/shaders/pixelgrid.frag -@@ -0,0 +1,25 @@ -+#include "colormanagement.glsl" -+ -+uniform sampler2D sampler; -+uniform int textureWidth; -+uniform int textureHeight; -+ -+varying vec2 texcoord0; -+ -+void main() -+{ -+ vec2 texSize = vec2(textureWidth, textureHeight); -+ vec2 samplePosition = texcoord0 * texSize; -+ vec2 pixelCenter = floor(samplePosition) + vec2(0.5); -+ vec2 pixelCenterDistance = abs(samplePosition - pixelCenter); -+ -+ vec4 tex; -+ if (pixelCenterDistance.x > 0.4 || pixelCenterDistance.y > 0.4) { -+ tex = vec4(0, 0, 0, 1); -+ } else { -+ tex = texture2D(sampler, pixelCenter / texSize); -+ } -+ -+ tex = sourceEncodingToNitsInDestinationColorspace(tex); -+ gl_FragColor = nitsToDestinationEncoding(tex); -+} -diff --git a/src/plugins/zoom/shaders/pixelgrid_core.frag b/src/plugins/zoom/shaders/pixelgrid_core.frag -new file mode 100644 -index 0000000000..a1cc7f27f2 ---- /dev/null -+++ b/src/plugins/zoom/shaders/pixelgrid_core.frag -@@ -0,0 +1,24 @@ -+#version 140 -+ -+#include "colormanagement.glsl" -+ -+uniform sampler2D sampler; -+uniform int textureWidth; -+uniform int textureHeight; -+ -+in vec2 texcoord0; -+ -+out vec4 fragColor; -+ -+void main() -+{ -+ vec2 texSize = vec2(textureWidth, textureHeight); -+ vec2 samplePosition = texcoord0 * texSize; -+ vec2 pixelCenter = floor(samplePosition) + vec2(0.5); -+ vec2 pixelCenterDistance = abs(samplePosition - pixelCenter); -+ -+ float t = smoothstep(0.4, 0.5, max(pixelCenterDistance.x, pixelCenterDistance.y)); -+ vec4 tex = mix(texture(sampler, pixelCenter / texSize), vec4(0, 0, 0, 1), t); -+ tex = sourceEncodingToNitsInDestinationColorspace(tex); -+ fragColor = nitsToDestinationEncoding(tex); -+} -diff --git a/src/plugins/zoom/ui_zoom_config.h b/src/plugins/zoom/ui_zoom_config.h -new file mode 100644 -index 0000000000..7e5944a902 ---- /dev/null -+++ b/src/plugins/zoom/ui_zoom_config.h -@@ -0,0 +1,195 @@ -+/******************************************************************************** -+** Form generated from reading UI file 'zoom_config.ui' -+** -+** Created by: Qt User Interface Compiler version 6.8.1 -+** -+** WARNING! All changes made in this file will be lost when recompiling UI file! -+********************************************************************************/ -+ -+#ifndef UI_ZOOM_CONFIG_H -+#define UI_ZOOM_CONFIG_H -+ -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include "kshortcutseditor.h" -+ -+namespace KWin { -+ -+class Ui_ZoomEffectConfigForm -+{ -+public: -+ QVBoxLayout *verticalLayout_2; -+ QGroupBox *groupSize; -+ QHBoxLayout *horizontalLayout; -+ QGridLayout *gridLayout; -+ QLabel *label; -+ QDoubleSpinBox *kcfg_ZoomFactor; -+ QCheckBox *kcfg_EnableFocusTracking; -+ QCheckBox *kcfg_EnableTextCaretTracking; -+ QLabel *label_2; -+ QComboBox *kcfg_MousePointer; -+ QComboBox *kcfg_MouseTracking; -+ QLabel *label_3; -+ KShortcutsEditor *editor; -+ -+ void setupUi(QWidget *KWin__ZoomEffectConfigForm) -+ { -+ if (KWin__ZoomEffectConfigForm->objectName().isEmpty()) -+ KWin__ZoomEffectConfigForm->setObjectName("KWin__ZoomEffectConfigForm"); -+ KWin__ZoomEffectConfigForm->resize(304, 288); -+ verticalLayout_2 = new QVBoxLayout(KWin__ZoomEffectConfigForm); -+ verticalLayout_2->setObjectName("verticalLayout_2"); -+ groupSize = new QGroupBox(KWin__ZoomEffectConfigForm); -+ groupSize->setObjectName("groupSize"); -+ horizontalLayout = new QHBoxLayout(groupSize); -+ horizontalLayout->setObjectName("horizontalLayout"); -+ gridLayout = new QGridLayout(); -+ gridLayout->setObjectName("gridLayout"); -+ label = new QLabel(groupSize); -+ label->setObjectName("label"); -+ label->setAlignment(Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter); -+ -+ gridLayout->addWidget(label, 0, 0, 1, 1); -+ -+ kcfg_ZoomFactor = new QDoubleSpinBox(groupSize); -+ kcfg_ZoomFactor->setObjectName("kcfg_ZoomFactor"); -+ kcfg_ZoomFactor->setDecimals(2); -+ kcfg_ZoomFactor->setMaximum(9999.000000000000000); -+ kcfg_ZoomFactor->setSingleStep(0.050000000000000); -+ kcfg_ZoomFactor->setValue(1.250000000000000); -+ -+ gridLayout->addWidget(kcfg_ZoomFactor, 0, 1, 1, 1); -+ -+ kcfg_EnableFocusTracking = new QCheckBox(groupSize); -+ kcfg_EnableFocusTracking->setObjectName("kcfg_EnableFocusTracking"); -+ -+ gridLayout->addWidget(kcfg_EnableFocusTracking, 4, 0, 1, 2); -+ -+ kcfg_EnableTextCaretTracking = new QCheckBox(groupSize); -+ kcfg_EnableTextCaretTracking->setObjectName("kcfg_EnableTextCaretTracking"); -+ -+ gridLayout->addWidget(kcfg_EnableTextCaretTracking, 5, 0, 1, 2); -+ -+ label_2 = new QLabel(groupSize); -+ label_2->setObjectName("label_2"); -+ label_2->setAlignment(Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter); -+ -+ gridLayout->addWidget(label_2, 2, 0, 1, 1); -+ -+ kcfg_MousePointer = new QComboBox(groupSize); -+ kcfg_MousePointer->addItem(QString()); -+ kcfg_MousePointer->addItem(QString()); -+ kcfg_MousePointer->addItem(QString()); -+ kcfg_MousePointer->setObjectName("kcfg_MousePointer"); -+ -+ gridLayout->addWidget(kcfg_MousePointer, 2, 1, 1, 1); -+ -+ kcfg_MouseTracking = new QComboBox(groupSize); -+ kcfg_MouseTracking->addItem(QString()); -+ kcfg_MouseTracking->addItem(QString()); -+ kcfg_MouseTracking->addItem(QString()); -+ kcfg_MouseTracking->addItem(QString()); -+ kcfg_MouseTracking->setObjectName("kcfg_MouseTracking"); -+ -+ gridLayout->addWidget(kcfg_MouseTracking, 3, 1, 1, 1); -+ -+ label_3 = new QLabel(groupSize); -+ label_3->setObjectName("label_3"); -+ label_3->setAlignment(Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter); -+ -+ gridLayout->addWidget(label_3, 3, 0, 1, 1); -+ -+ -+ horizontalLayout->addLayout(gridLayout); -+ -+ -+ verticalLayout_2->addWidget(groupSize); -+ -+ editor = new KShortcutsEditor(KWin__ZoomEffectConfigForm); -+ editor->setObjectName("editor"); -+ QSizePolicy sizePolicy(QSizePolicy::Policy::Preferred, QSizePolicy::Policy::Expanding); -+ sizePolicy.setHorizontalStretch(0); -+ sizePolicy.setVerticalStretch(0); -+ sizePolicy.setHeightForWidth(editor->sizePolicy().hasHeightForWidth()); -+ editor->setSizePolicy(sizePolicy); -+ editor->setActionTypes(KShortcutsEditor::GlobalAction); -+ -+ verticalLayout_2->addWidget(editor); -+ -+#if QT_CONFIG(shortcut) -+ label->setBuddy(kcfg_ZoomFactor); -+ label_2->setBuddy(kcfg_MousePointer); -+ label_3->setBuddy(kcfg_MouseTracking); -+#endif // QT_CONFIG(shortcut) -+ QWidget::setTabOrder(kcfg_ZoomFactor, kcfg_MousePointer); -+ QWidget::setTabOrder(kcfg_MousePointer, kcfg_MouseTracking); -+ QWidget::setTabOrder(kcfg_MouseTracking, kcfg_EnableFocusTracking); -+ QWidget::setTabOrder(kcfg_EnableFocusTracking, kcfg_EnableTextCaretTracking); -+ -+ retranslateUi(KWin__ZoomEffectConfigForm); -+ -+ QMetaObject::connectSlotsByName(KWin__ZoomEffectConfigForm); -+ } // setupUi -+ -+ void retranslateUi(QWidget *KWin__ZoomEffectConfigForm) -+ { -+ groupSize->setTitle(QString()); -+#if QT_CONFIG(whatsthis) -+ label->setWhatsThis(QCoreApplication::translate("KWin::ZoomEffectConfigForm", "On zoom-in and zoom-out change the zoom by the defined zoom-factor.", nullptr)); -+#endif // QT_CONFIG(whatsthis) -+ label->setText(QCoreApplication::translate("KWin::ZoomEffectConfigForm", "Zoom Factor:", nullptr)); -+#if QT_CONFIG(whatsthis) -+ kcfg_ZoomFactor->setWhatsThis(QCoreApplication::translate("KWin::ZoomEffectConfigForm", "On zoom-in and zoom-out change the zoom by the defined zoom-factor.", nullptr)); -+#endif // QT_CONFIG(whatsthis) -+ kcfg_ZoomFactor->setSuffix(QString()); -+#if QT_CONFIG(tooltip) -+ kcfg_EnableFocusTracking->setToolTip(QString()); -+#endif // QT_CONFIG(tooltip) -+#if QT_CONFIG(whatsthis) -+ kcfg_EnableFocusTracking->setWhatsThis(QCoreApplication::translate("KWin::ZoomEffectConfigForm", "Enable tracking of the focused location. This needs QAccessible to be enabled per application (\"export QT_LINUX_ACCESSIBILITY_ALWAYS_ON=1\").", nullptr)); -+#endif // QT_CONFIG(whatsthis) -+ kcfg_EnableFocusTracking->setText(QCoreApplication::translate("KWin::ZoomEffectConfigForm", "Enable Focus Tracking", nullptr)); -+#if QT_CONFIG(whatsthis) -+ kcfg_EnableTextCaretTracking->setWhatsThis(QCoreApplication::translate("KWin::ZoomEffectConfigForm", "Enable tracking of the text cursor. This needs QAccessible to be enabled per application (\"export QT_LINUX_ACCESSIBILITY_ALWAYS_ON=1\").", nullptr)); -+#endif // QT_CONFIG(whatsthis) -+ kcfg_EnableTextCaretTracking->setText(QCoreApplication::translate("KWin::ZoomEffectConfigForm", "Enable Text Cursor Tracking", nullptr)); -+ label_2->setText(QCoreApplication::translate("KWin::ZoomEffectConfigForm", "Mouse Pointer:", nullptr)); -+ kcfg_MousePointer->setItemText(0, QCoreApplication::translate("KWin::ZoomEffectConfigForm", "Scale", nullptr)); -+ kcfg_MousePointer->setItemText(1, QCoreApplication::translate("KWin::ZoomEffectConfigForm", "Keep", nullptr)); -+ kcfg_MousePointer->setItemText(2, QCoreApplication::translate("KWin::ZoomEffectConfigForm", "Hide", nullptr)); -+ -+#if QT_CONFIG(whatsthis) -+ kcfg_MousePointer->setWhatsThis(QCoreApplication::translate("KWin::ZoomEffectConfigForm", "Visibility of the mouse-pointer.", nullptr)); -+#endif // QT_CONFIG(whatsthis) -+ kcfg_MouseTracking->setItemText(0, QCoreApplication::translate("KWin::ZoomEffectConfigForm", "Proportional", nullptr)); -+ kcfg_MouseTracking->setItemText(1, QCoreApplication::translate("KWin::ZoomEffectConfigForm", "Centered", nullptr)); -+ kcfg_MouseTracking->setItemText(2, QCoreApplication::translate("KWin::ZoomEffectConfigForm", "Push", nullptr)); -+ kcfg_MouseTracking->setItemText(3, QCoreApplication::translate("KWin::ZoomEffectConfigForm", "Disabled", nullptr)); -+ -+#if QT_CONFIG(whatsthis) -+ kcfg_MouseTracking->setWhatsThis(QCoreApplication::translate("KWin::ZoomEffectConfigForm", "Track moving of the mouse.", nullptr)); -+#endif // QT_CONFIG(whatsthis) -+ label_3->setText(QCoreApplication::translate("KWin::ZoomEffectConfigForm", "Mouse Tracking:", nullptr)); -+ (void)KWin__ZoomEffectConfigForm; -+ } // retranslateUi -+ -+}; -+ -+} // namespace KWin -+ -+namespace KWin { -+namespace Ui { -+ class ZoomEffectConfigForm: public Ui_ZoomEffectConfigForm {}; -+} // namespace Ui -+} // namespace KWin -+ -+#endif // UI_ZOOM_CONFIG_H -diff --git a/src/plugins/zoom/zoom.cpp b/src/plugins/zoom/zoom.cpp -index a6eb528767..950583c15b 100644 ---- a/src/plugins/zoom/zoom.cpp -+++ b/src/plugins/zoom/zoom.cpp -@@ -31,6 +31,12 @@ - - using namespace std::chrono_literals; - -+static void ensureResources() -+{ -+ // Must initialize resources manually because the effect is a static lib. -+ Q_INIT_RESOURCE(zoom); -+} -+ - namespace KWin - { - -@@ -48,6 +54,8 @@ ZoomEffect::ZoomEffect() - , moveFactor(20.0) - , lastPresentTime(std::chrono::milliseconds::zero()) - { -+ ensureResources(); -+ - ZoomConfig::instance(effects->config()); - QAction *a = nullptr; - a = KStandardAction::zoomIn(this, SLOT(zoomIn()), this); -@@ -214,6 +222,7 @@ void ZoomEffect::reconfigure(ReconfigureFlags) - ZoomConfig::self()->read(); - // On zoom-in and zoom-out change the zoom by the defined zoom-factor. - zoomFactor = std::max(0.1, ZoomConfig::zoomFactor()); -+ m_pixelGridZoom = ZoomConfig::pixelGridZoom(); - // Visibility of the mouse-pointer. - mousePointer = MousePointerType(ZoomConfig::mousePointer()); - // Track moving of the mouse. -@@ -271,12 +280,10 @@ void ZoomEffect::prePaintScreen(ScreenPrePaintData &data, std::chrono::milliseco - - ZoomEffect::OffscreenData *ZoomEffect::ensureOffscreenData(const RenderTarget &renderTarget, const RenderViewport &viewport, Output *screen) - { -- const QRect rect = viewport.renderRect().toRect(); -- const qreal devicePixelRatio = viewport.scale(); -- const QSize nativeSize = (viewport.renderRect().size() * devicePixelRatio).toSize(); -+ const QSize nativeSize = renderTarget.size(); - - OffscreenData &data = m_offscreenData[effects->waylandDisplay() ? screen : nullptr]; -- data.viewport = rect; -+ data.viewport = viewport.renderRect(); - data.color = renderTarget.colorDescription(); - - const GLenum textureFormat = renderTarget.colorDescription() == ColorDescription::sRGB ? GL_RGBA8 : GL_RGBA16F; -@@ -290,9 +297,22 @@ ZoomEffect::OffscreenData *ZoomEffect::ensureOffscreenData(const RenderTarget &r - data.framebuffer = std::make_unique(data.texture.get()); - } - -+ data.texture->setContentTransform(renderTarget.transform()); - return &data; - } - -+GLShader *ZoomEffect::shaderForZoom(double zoom) -+{ -+ if (zoom < m_pixelGridZoom) { -+ return ShaderManager::instance()->shader(ShaderTrait::MapTexture | ShaderTrait::TransformColorspace); -+ } else { -+ if (!m_pixelGridShader) { -+ m_pixelGridShader = ShaderManager::instance()->generateShaderFromFile(ShaderTrait::MapTexture, QString(), QStringLiteral(":/effects/zoom/shaders/pixelgrid.frag")); -+ } -+ return m_pixelGridShader.get(); -+ } -+} -+ - void ZoomEffect::paintScreen(const RenderTarget &renderTarget, const RenderViewport &viewport, int mask, const QRegion ®ion, Output *screen) - { - OffscreenData *offscreenData = ensureOffscreenData(renderTarget, viewport, screen); -@@ -391,7 +411,8 @@ void ZoomEffect::paintScreen(const RenderTarget &renderTarget, const RenderViewp - glClearColor(0.0, 0.0, 0.0, 0.0); - glClear(GL_COLOR_BUFFER_BIT); - -- auto shader = ShaderManager::instance()->pushShader(ShaderTrait::MapTexture | ShaderTrait::TransformColorspace); -+ GLShader *shader = shaderForZoom(zoom); -+ ShaderManager::instance()->pushShader(shader); - for (auto &[screen, offscreen] : m_offscreenData) { - QMatrix4x4 matrix; - matrix.translate(xTranslation * scale, yTranslation * scale); -@@ -399,6 +420,8 @@ void ZoomEffect::paintScreen(const RenderTarget &renderTarget, const RenderViewp - matrix.translate(offscreen.viewport.x() * scale, offscreen.viewport.y() * scale); - - shader->setUniform(GLShader::Mat4Uniform::ModelViewProjectionMatrix, viewport.projectionMatrix() * matrix); -+ shader->setUniform(GLShader::IntUniform::TextureWidth, offscreen.texture->width()); -+ shader->setUniform(GLShader::IntUniform::TextureHeight, offscreen.texture->height()); - shader->setColorspaceUniforms(offscreen.color, renderTarget.colorDescription(), RenderingIntent::Perceptual); - - offscreen.texture->render(offscreen.viewport.size() * scale); -diff --git a/src/plugins/zoom/zoom.h b/src/plugins/zoom/zoom.h -index 2a44395d74..1c9abb4b4e 100644 ---- a/src/plugins/zoom/zoom.h -+++ b/src/plugins/zoom/zoom.h -@@ -26,6 +26,7 @@ class ZoomAccessibilityIntegration; - class GLFramebuffer; - class GLTexture; - class GLVertexBuffer; -+class GLShader; - - class ZoomEffect - : public Effect -@@ -94,7 +95,7 @@ private: - { - std::unique_ptr texture; - std::unique_ptr framebuffer; -- QRect viewport; -+ QRectF viewport; - ColorDescription color = ColorDescription::sRGB; - }; - -@@ -102,6 +103,8 @@ private: - OffscreenData *ensureOffscreenData(const RenderTarget &renderTarget, const RenderViewport &viewport, Output *screen); - void markCursorTextureDirty(); - -+ GLShader *shaderForZoom(double zoom); -+ - #if HAVE_ACCESSIBILITY - ZoomAccessibilityIntegration *m_accessibilityIntegration = nullptr; - #endif -@@ -136,6 +139,8 @@ private: - double moveFactor; - std::chrono::milliseconds lastPresentTime; - std::map m_offscreenData; -+ std::unique_ptr m_pixelGridShader; -+ double m_pixelGridZoom; - }; - - } // namespace -diff --git a/src/plugins/zoom/zoom.kcfg b/src/plugins/zoom/zoom.kcfg -index 63887e32aa..ed43e225c8 100644 ---- a/src/plugins/zoom/zoom.kcfg -+++ b/src/plugins/zoom/zoom.kcfg -@@ -29,5 +29,8 @@ - - 1.0 - -+ -+ 15.0 -+ - - -diff --git a/src/plugins/zoom/zoom.qrc b/src/plugins/zoom/zoom.qrc -new file mode 100644 -index 0000000000..56e7369003 ---- /dev/null -+++ b/src/plugins/zoom/zoom.qrc -@@ -0,0 +1,6 @@ -+ -+ -+ shaders/pixelgrid.frag -+ shaders/pixelgrid_core.frag -+ -+ -diff --git a/src/plugins/zoom/zoom_config.ui b/src/plugins/zoom/zoom_config.ui -index 454f9f9161..4b8a5c000e 100644 ---- a/src/plugins/zoom/zoom_config.ui -+++ b/src/plugins/zoom/zoom_config.ui -@@ -6,159 +6,157 @@ - - 0 - 0 -- 304 -- 288 -+ 595 -+ 551 - - - - -- -- -- -+ -+ -+ Qt::AlignHCenter|Qt::AlignTop - -- -- -- -- -- -- -- On zoom-in and zoom-out change the zoom by the defined zoom-factor. -- -- -- Zoom Factor: -- -- -- Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter -- -- -- kcfg_ZoomFactor -- -- -- -- -- -- -- On zoom-in and zoom-out change the zoom by the defined zoom-factor. -- -- -- -- -- -- 2 -- -- -- 9999.000000000000000 -- -- -- 0.050000000000000 -- -- -- 1.250000000000000 -- -- -- -- -- -- -- -- -- -- Enable tracking of the focused location. This needs QAccessible to be enabled per application ("export QT_LINUX_ACCESSIBILITY_ALWAYS_ON=1"). -- -- -- Enable Focus Tracking -- -- -- -- -- -- -- Enable tracking of the text cursor. This needs QAccessible to be enabled per application ("export QT_LINUX_ACCESSIBILITY_ALWAYS_ON=1"). -- -- -- Enable Text Cursor Tracking -- -- -- -- -- -- -- Mouse Pointer: -- -- -- Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter -- -- -- kcfg_MousePointer -- -- -- -- -- -- -- Visibility of the mouse-pointer. -- -- -- -- Scale -- -- -- -- -- Keep -- -- -- -- -- Hide -- -- -- -- -- -- -- -- Track moving of the mouse. -- -- -- -- Proportional -- -- -- -- -- Centered -- -- -- -- -- Push -- -- -- -- -- Disabled -- -- -- -- -- -- -- -- Mouse Tracking: -- -- -- Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter -- -- -- kcfg_MouseTracking -- -- -- -- -- -- -- -+ -+ -+ -+ On zoom-in and zoom-out change the zoom by the defined zoom-factor. -+ -+ -+ Zoom factor: -+ -+ -+ kcfg_ZoomFactor -+ -+ -+ -+ -+ -+ -+ On zoom-in and zoom-out change the zoom by the defined zoom-factor. -+ -+ -+ -+ -+ -+ 2 -+ -+ -+ 9999.000000000000000 -+ -+ -+ 0.050000000000000 -+ -+ -+ 1.250000000000000 -+ -+ -+ -+ -+ -+ -+ Show pixel grid at zoom level: -+ -+ -+ kcfg_PixelGridZoom -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ Mouse pointer: -+ -+ -+ kcfg_MousePointer -+ -+ -+ -+ -+ -+ -+ Visibility of the mouse-pointer. -+ -+ -+ -+ Scale -+ -+ -+ -+ -+ Keep -+ -+ -+ -+ -+ Hide -+ -+ -+ -+ -+ -+ -+ -+ Mouse tracking: -+ -+ -+ kcfg_MouseTracking -+ -+ -+ -+ -+ -+ -+ Track moving of the mouse. -+ -+ -+ -+ Proportional -+ -+ -+ -+ -+ Centered -+ -+ -+ -+ -+ Push -+ -+ -+ -+ -+ Disabled -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ Enable tracking of the focused location. This needs QAccessible to be enabled per application ("export QT_LINUX_ACCESSIBILITY_ALWAYS_ON=1"). -+ -+ -+ Enable focus tracking -+ -+ -+ -+ -+ -+ -+ Enable tracking of the text cursor. This needs QAccessible to be enabled per application ("export QT_LINUX_ACCESSIBILITY_ALWAYS_ON=1"). -+ -+ -+ Enable text cursor tracking -+ -+ -+ -+ - - - -@@ -183,13 +181,6 @@ - 1 - - -- -- kcfg_ZoomFactor -- kcfg_MousePointer -- kcfg_MouseTracking -- kcfg_EnableFocusTracking -- kcfg_EnableTextCaretTracking -- - - - diff --git a/roles/kde/patches/kwin-pr6985.patch b/roles/kde/patches/kwin-pr6985.patch deleted file mode 100644 index 95d196d..0000000 --- a/roles/kde/patches/kwin-pr6985.patch +++ /dev/null @@ -1,41 +0,0 @@ -From 45a5d8844b36404334301f5da6e75f1a345e0c80 Mon Sep 17 00:00:00 2001 -From: Xaver Hugl -Date: Fri, 10 Jan 2025 13:45:30 +0000 -Subject: [PATCH] plugins/screencast: call ItemRenderer::begin/endFrame - -The OpenGL renderer references the explicit sync release points for client buffers -during rendering, and releases them in endFrame. If endFrame never gets called though -(for example because we're doing direct scanout) then the release points never get -signaled, and the client very quickly runs out of buffers to use and freezes. - -BUG: 495287 - - -(cherry picked from commit b1031ea63eaa8c9bf5c70157d1b6bf8eb0f5a74a) - -Co-authored-by: Xaver Hugl ---- - src/plugins/screencast/windowscreencastsource.cpp | 4 ++-- - 1 file changed, 2 insertions(+), 2 deletions(-) - -diff --git a/src/plugins/screencast/windowscreencastsource.cpp b/src/plugins/screencast/windowscreencastsource.cpp -index 24ef92aad7b..b396eed46f9 100644 ---- a/src/plugins/screencast/windowscreencastsource.cpp -+++ b/src/plugins/screencast/windowscreencastsource.cpp -@@ -75,11 +75,11 @@ void WindowScreenCastSource::render(GLFramebuffer *target) - RenderTarget renderTarget(target); - RenderViewport viewport(m_window->clientGeometry(), 1, renderTarget); - -- GLFramebuffer::pushFramebuffer(target); -+ Compositor::self()->scene()->renderer()->beginFrame(renderTarget, viewport); - glClearColor(0.0, 0.0, 0.0, 0.0); - glClear(GL_COLOR_BUFFER_BIT); - Compositor::self()->scene()->renderer()->renderItem(renderTarget, viewport, m_window->windowItem(), Scene::PAINT_WINDOW_TRANSFORMED, infiniteRegion(), WindowPaintData{}); -- GLFramebuffer::popFramebuffer(); -+ Compositor::self()->scene()->renderer()->endFrame(); - } - - std::chrono::nanoseconds WindowScreenCastSource::clock() const --- -GitLab - diff --git a/roles/kde/patches/libplasma-pr1214.patch b/roles/kde/patches/libplasma-pr1214.patch deleted file mode 100644 index 709b13e..0000000 --- a/roles/kde/patches/libplasma-pr1214.patch +++ /dev/null @@ -1,48 +0,0 @@ -From 626a52ae30dcda0ca407d1de02e30fcf5c109862 Mon Sep 17 00:00:00 2001 -From: Tem PQD -Date: Sun, 27 Oct 2024 03:54:44 +0000 -Subject: [PATCH] applets/taskmanager: Make group indicator icon follow accent - color - -The green plus icon is pretty eye-catching, and normally green plus buttons indicate something is going to be added or created. Change this to follow the user-selected color scheme, also matching the focus/active indicator color. ---- - src/desktoptheme/breeze/widgets/tasks.svg | Bin 78569 -> 78717 bytes - 1 file changed, 0 insertions(+), 0 deletions(-) - -diff --git a/src/desktoptheme/breeze/widgets/tasks.svg b/src/desktoptheme/breeze/widgets/tasks.svg -index b76d1a345fe59ca48cc3c52de53495202734d00f..c60707355f082749661ffc059744a0f2b0f94eee 100644 ---- a/src/desktoptheme/breeze/widgets/tasks.svg -+++ b/src/desktoptheme/breeze/widgets/tasks.svg -@@ -952,25 +952,25 @@ - - - -- -+ - - - - - -- -+ - - - - - -- -+ - - - - - -- -+ - - - --- -GitLab - diff --git a/roles/kde/patches/plasma_desktop-pr2661.patch b/roles/kde/patches/plasma_desktop-pr2661.patch deleted file mode 100644 index b2ce58d..0000000 --- a/roles/kde/patches/plasma_desktop-pr2661.patch +++ /dev/null @@ -1,39 +0,0 @@ -From 5301b211fc87f1b2253e87da61ff82618be9e899 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Niccol=C3=B2=20Venerandi?= -Date: Thu, 5 Dec 2024 16:12:58 +0100 -Subject: [PATCH] Only return valid task item size if task manager has been - resized to fit panel - -Previously the task manager would briefly have null width and height, before -being resized to fit the panel. However, the "preferredMaxWidth" for tasks -would still be positive, as it adds margins to it, and tasks would then -assume a positive width. When the proper values are set, this casuses -an extra resize animation. - -CCBUG:447476 ---- - .../taskmanager/package/contents/ui/code/layoutmetrics.js | 6 ++++++ - 1 file changed, 6 insertions(+) - -diff --git a/applets/taskmanager/package/contents/ui/code/layoutmetrics.js b/applets/taskmanager/package/contents/ui/code/layoutmetrics.js -index c7f11c99a6..895cc725d2 100644 ---- a/applets/taskmanager/package/contents/ui/code/layoutmetrics.js -+++ b/applets/taskmanager/package/contents/ui/code/layoutmetrics.js -@@ -63,8 +63,14 @@ function preferredMinWidth() { - function preferredMaxWidth() { - if (tasks.iconsOnly) { - if (tasks.vertical) { -+ if (tasks.width === 0) { -+ return 0 -+ } - return tasks.width + verticalMargins(); - } else { -+ if (tasks.height === 0) { -+ return 0 -+ } - return tasks.height + horizontalMargins(); - } - } --- -GitLab - diff --git a/roles/kde/patches/plasma_workspace-pr4883.patch b/roles/kde/patches/plasma_workspace-pr4883.patch deleted file mode 100644 index 7b575d0..0000000 --- a/roles/kde/patches/plasma_workspace-pr4883.patch +++ /dev/null @@ -1,40 +0,0 @@ -From 11e7f5306fa013ec5c2b894a28457dabf5c42bad Mon Sep 17 00:00:00 2001 -From: Nate Graham -Date: Wed, 30 Oct 2024 21:55:49 -0600 -Subject: [PATCH] Merge "Settings" menu category into "System" - -This category is a bit of an odd duck. On a default install, it contains -only one item: System Settings. Random other apps you install will also -appear there, but all of these could just as logically live in the -"System" category. Let's put them there, so as to consolidate two -nebulous overlapping categories into one clear and obvious one. ---- - menu/desktop/plasma-applications.menu | 8 +------- - 1 file changed, 1 insertion(+), 7 deletions(-) - -diff --git a/menu/desktop/plasma-applications.menu b/menu/desktop/plasma-applications.menu -index e153604252e..85f842d6a04 100644 ---- a/menu/desktop/plasma-applications.menu -+++ b/menu/desktop/plasma-applications.menu -@@ -334,17 +334,11 @@ - - - -- -- Settingsmenu -- kf5-settingsmenu.directory -- -- Settings -- -- - - System - kf5-system.directory - -+ Settings - - System - X-KDE-More --- -GitLab - diff --git a/roles/kde/patches/plasma_workspace-pr4965.patch b/roles/kde/patches/plasma_workspace-pr4965.patch deleted file mode 100644 index f677504..0000000 --- a/roles/kde/patches/plasma_workspace-pr4965.patch +++ /dev/null @@ -1,915 +0,0 @@ -diff --git a/appiumtests/applets/CMakeLists.txt b/appiumtests/applets/CMakeLists.txt -index 19229541aa..6715dd1f8f 100644 ---- a/appiumtests/applets/CMakeLists.txt -+++ b/appiumtests/applets/CMakeLists.txt -@@ -59,7 +59,7 @@ add_test( - NAME notificationstest - COMMAND selenium-webdriver-at-spi-run ${CMAKE_CURRENT_SOURCE_DIR}/notificationstest.py --failfast - ) --set_tests_properties(notificationstest PROPERTIES TIMEOUT 120) -+set_tests_properties(notificationstest PROPERTIES TIMEOUT 120 ENVIRONMENT "KACTIVITYMANAGERD_PATH=${KDE_INSTALL_FULL_LIBEXECDIR}/kactivitymanagerd;USE_CUSTOM_BUS=1") - - add_test( - NAME digitalclocktest -diff --git a/appiumtests/applets/kicker/favoritetest.py b/appiumtests/applets/kicker/favoritetest.py -new file mode 100755 -index 0000000000..50613c3db1 ---- /dev/null -+++ b/appiumtests/applets/kicker/favoritetest.py -@@ -0,0 +1,162 @@ -+#!/usr/bin/env python3 -+ -+# SPDX-License-Identifier: BSD-3-Clause -+# SPDX-FileCopyrightText: 2022-2023 Harald Sitter -+ -+import logging -+import os -+import shutil -+import subprocess -+import sys -+import tempfile -+import time -+import unittest -+from typing import Final -+ -+from gi.repository import Gio, GLib -+ -+sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), os.pardir, os.pardir, "utils")) -+from GLibMainLoopThread import GLibMainLoopThread -+ -+KDE_VERSION: Final = 6 -+KACTIVITYMANAGERD_SERVICE_NAME: Final = "org.kde.ActivityManager" -+KACTIVITYMANAGERD_PATH: Final = os.getenv("KACTIVITYMANAGERD_PATH", "/usr/libexec/kactivitymanagerd") -+QMLTEST_EXEC: Final = os.getenv("QMLTEST_EXEC", "/usr/bin/qmltestrunner6") -+ -+ -+def name_has_owner(session_bus: Gio.DBusConnection, name: str) -> bool: -+ """ -+ Whether the given name is available on session bus -+ """ -+ message: Gio.DBusMessage = Gio.DBusMessage.new_method_call("org.freedesktop.DBus", "/", "org.freedesktop.DBus", "NameHasOwner") -+ message.set_body(GLib.Variant("(s)", [name])) -+ reply, _ = session_bus.send_message_with_reply_sync(message, Gio.DBusSendMessageFlags.NONE, 1000) -+ return reply and reply.get_signature() == 'b' and reply.get_body().get_child_value(0).get_boolean() -+ -+ -+def build_ksycoca() -> None: -+ subprocess.check_call([f"kbuildsycoca{KDE_VERSION}"], stdout=sys.stderr, stderr=sys.stderr, env=os.environ) -+ -+ -+def start_kactivitymanagerd() -> subprocess.Popen: -+ session_bus: Gio.DBusConnection = Gio.bus_get_sync(Gio.BusType.SESSION) -+ assert not name_has_owner(session_bus, KACTIVITYMANAGERD_SERVICE_NAME) -+ -+ os.makedirs(os.path.join(GLib.get_user_config_dir(), "menus")) -+ shutil.copy(os.path.join(os.path.dirname(os.path.abspath(__file__)), "applications.menu"), os.path.join(GLib.get_user_config_dir(), "menus")) -+ -+ kactivitymanagerd = subprocess.Popen([KACTIVITYMANAGERD_PATH], stdout=sys.stderr, stderr=sys.stderr, env=os.environ) -+ kactivitymanagerd_started: bool = False -+ for _ in range(10): -+ if name_has_owner(session_bus, KACTIVITYMANAGERD_SERVICE_NAME): -+ kactivitymanagerd_started = True -+ break -+ logging.info("waiting for kactivitymanagerd to appear on the DBus session") -+ time.sleep(1) -+ assert kactivitymanagerd_started -+ -+ build_ksycoca() -+ -+ return kactivitymanagerd -+ -+ -+class TestDBusInterface: -+ """ -+ D-Bus interface for org.kde.kickertest -+ """ -+ -+ BUS_NAME: Final = "org.kde.kickertest" -+ OBJECT_PATH: Final = "/test" -+ INTERFACE_NAME: Final = "org.kde.kickertest" -+ -+ connection: Gio.DBusConnection -+ -+ def __init__(self) -> None: -+ self.reg_id: int = 0 -+ self.owner_id: int = Gio.bus_own_name(Gio.BusType.SESSION, self.BUS_NAME, Gio.BusNameOwnerFlags.NONE, self.on_bus_acquired, None, None) -+ assert self.owner_id > 0 -+ -+ def on_bus_acquired(self, connection: Gio.DBusConnection, name: str, *args) -> None: -+ """ -+ The interface is ready, now register objects. -+ """ -+ self.connection = connection -+ introspection_data = Gio.DBusNodeInfo.new_for_xml(""" -+ -+ -+ -+ -+ -+ -+ -+""") -+ self.reg_id = connection.register_object(self.OBJECT_PATH, introspection_data.interfaces[0], self.handle_method_call, None, None) -+ assert self.reg_id > 0 -+ logging.info("interface registered") -+ -+ def handle_method_call(self, connection: Gio.DBusConnection, sender: str, object_path: str, interface_name: str, method_name: str, parameters: GLib.Variant, invocation: Gio.DBusMethodInvocation) -> None: -+ logging.info("method call %s", method_name) -+ -+ if method_name == "DeleteAndRebuildDatabase1": -+ os.remove(KickerTest.desktop_entry_1) -+ build_ksycoca() -+ invocation.return_value(None) -+ elif method_name == "DeleteAndRebuildDatabase2": -+ os.remove(KickerTest.desktop_entry_2) -+ build_ksycoca() -+ invocation.return_value(None) -+ -+ -+class KickerTest(unittest.TestCase): -+ kactivitymanagerd: subprocess.Popen -+ loop_thread: GLibMainLoopThread -+ dbus_interface: TestDBusInterface -+ -+ temp_dir: tempfile.TemporaryDirectory -+ desktop_entry_1: str -+ desktop_entry_2: str -+ -+ @classmethod -+ def setUpClass(cls) -> None: -+ # Prepare desktop files -+ # 1 -+ os.makedirs(os.path.join(GLib.get_user_data_dir(), "applications")) -+ shutil.copy(os.path.join(os.path.dirname(os.path.abspath(__file__)), "kickertest.desktop"), os.path.join(GLib.get_user_data_dir(), "applications")) -+ cls.desktop_entry_1 = os.path.join(GLib.get_user_data_dir(), "applications", "kickertest.desktop") -+ # 2 -+ cls.temp_dir = tempfile.TemporaryDirectory() -+ os.makedirs(os.path.join(cls.temp_dir.name, "applications")) -+ shutil.copy(os.path.join(os.path.dirname(os.path.abspath(__file__)), "kickertest.desktop"), os.path.join(cls.temp_dir.name, "applications")) -+ cls.desktop_entry_2 = os.path.join(cls.temp_dir.name, "applications", "kickertest.desktop") -+ -+ os.environ["LC_ALL"] = "en_US.UTF-8" -+ os.environ["QT_LOGGING_RULES"] = "org.kde.plasma.kicker.debug=true;kf.coreaddons.kdirwatch.debug=true" -+ os.environ["XDG_DATA_DIRS"] = os.environ["XDG_DATA_DIRS"] + ":" + cls.temp_dir.name -+ -+ cls.kactivitymanagerd = start_kactivitymanagerd() -+ -+ cls.loop_thread = GLibMainLoopThread() -+ cls.loop_thread.start() -+ cls.dbus_interface = TestDBusInterface() -+ -+ @classmethod -+ def tearDownClass(cls) -> None: -+ cls.loop_thread.quit() -+ cls.kactivitymanagerd.kill() -+ cls.kactivitymanagerd.wait(10) -+ -+ def test_qml(self) -> None: -+ """ -+ 1. Add an entry to Favorites -+ 2. Remove the entry from Favorites -+ 3. Hide invalid entries automatically and don't crash when there are multiple entries with the same desktop name -+ """ -+ with subprocess.Popen([QMLTEST_EXEC, "-input", os.path.join(os.path.dirname(os.path.abspath(__file__)), 'favoritetest.qml')], stdout=sys.stderr, stderr=sys.stderr) as process: -+ self.assertEqual(process.wait(60), 0) -+ -+ -+if __name__ == '__main__': -+ assert "USE_CUSTOM_BUS" in os.environ -+ logging.getLogger().setLevel(logging.INFO) -+ unittest.main() -diff --git a/appiumtests/applets/notificationstest.py b/appiumtests/applets/notificationstest.py -index 8917121a94..cd34a32905 100755 ---- a/appiumtests/applets/notificationstest.py -+++ b/appiumtests/applets/notificationstest.py -@@ -5,6 +5,7 @@ - - import base64 - import os -+import shutil - import subprocess - import tempfile - import time -@@ -15,6 +16,7 @@ import gi - from appium import webdriver - from appium.options.common.base import AppiumOptions - from appium.webdriver.common.appiumby import AppiumBy -+from selenium.common.exceptions import (NoSuchElementException, WebDriverException) - from selenium.webdriver.support import expected_conditions as EC - from selenium.webdriver.support.ui import WebDriverWait - -@@ -22,6 +24,8 @@ gi.require_version('Gdk', '4.0') - gi.require_version('GdkPixbuf', '2.0') - from gi.repository import Gdk, GdkPixbuf, Gio, GLib - -+from kicker.favoritetest import start_kactivitymanagerd -+ - WIDGET_ID: Final = "org.kde.plasma.notifications" - KDE_VERSION: Final = 6 - -@@ -48,17 +52,25 @@ class NotificationsTest(unittest.TestCase): - """ - - driver: webdriver.Remote -+ kactivitymanagerd: subprocess.Popen - - @classmethod - def setUpClass(cls) -> None: - """ - Opens the widget and initialize the webdriver - """ -+ # Make history work -+ cls.kactivitymanagerd = start_kactivitymanagerd() -+ -+ os.makedirs(os.path.join(GLib.get_user_data_dir(), "knotifications6")) -+ shutil.copy(os.path.join(os.path.dirname(os.path.abspath(__file__)), os.pardir, os.pardir, "libnotificationmanager", "libnotificationmanager.notifyrc"), os.path.join(GLib.get_user_data_dir(), "knotifications6")) -+ - options = AppiumOptions() - options.set_capability("app", f"plasmawindowed -p org.kde.plasma.nano {WIDGET_ID}") - options.set_capability("timeouts", {'implicit': 10000}) - options.set_capability("environ", { - "LC_ALL": "en_US.UTF-8", -+ "QT_LOGGING_RULES": "kf.notification*.debug=true;org.kde.plasma.notificationmanager.debug=true", - }) - cls.driver = webdriver.Remote(command_executor='http://127.0.0.1:4723', options=options) - -@@ -75,6 +87,8 @@ class NotificationsTest(unittest.TestCase): - Make sure to terminate the driver again, lest it dangles. - """ - subprocess.check_call([f"kquitapp{KDE_VERSION}", "plasmawindowed"]) -+ cls.kactivitymanagerd.kill() -+ cls.kactivitymanagerd.wait(10) - for _ in range(10): - try: - subprocess.check_call(["pidof", "plasmawindowed"]) -@@ -83,6 +97,15 @@ class NotificationsTest(unittest.TestCase): - time.sleep(1) - cls.driver.quit() - -+ def close_notifications(self) -> None: -+ wait = WebDriverWait(self.driver, 5) -+ for button in self.driver.find_elements(AppiumBy.XPATH, "//button[@name='Close']"): -+ try: -+ button.click() -+ wait.until_not(lambda _: button.is_displayed()) -+ except WebDriverException: -+ pass -+ - def test_0_open(self) -> None: - """ - Tests the widget can be opened -@@ -106,6 +129,10 @@ class NotificationsTest(unittest.TestCase): - - wait = WebDriverWait(self.driver, 5) - wait.until(EC.presence_of_element_located((AppiumBy.NAME, summary))) -+<<<<<<< HEAD -+======= -+ self.close_notifications() -+>>>>>>> 5145d877d8 (applets/notifications: suppress inhibited notifications after "Do not disturb" is off) - - def take_screenshot(self) -> str: - with tempfile.TemporaryDirectory() as temp_dir: -@@ -123,6 +150,7 @@ class NotificationsTest(unittest.TestCase): - partial_pixbuf = GdkPixbuf.Pixbuf.new(GdkPixbuf.Colorspace.RGB, True, 8, 16, 16) - colors = (0xff0000ff, 0x00ff00ff, 0x0000ffff) - for color in colors: -+ logging.info(f"Testing color: {color}") - pixbuf.fill(color) - send_notification({ - "app_name": "Appium Test", -@@ -145,7 +173,29 @@ class NotificationsTest(unittest.TestCase): - wait.until(EC.presence_of_element_located((AppiumBy.NAME, summary + str(color)))) - partial_pixbuf.fill(color) - partial_image = base64.b64encode(Gdk.Texture.new_for_pixbuf(partial_pixbuf).save_to_png_bytes().get_data()).decode() -+<<<<<<< HEAD - self.driver.find_image_occurrence(self.take_screenshot(), partial_image) -+======= -+ try: -+ self.driver.find_image_occurrence(self.take_screenshot(), partial_image) -+ except WebDriverException: # Popup animation -+ self.driver.find_image_occurrence(self.take_screenshot(), partial_image) -+ self.close_notifications() -+ -+ def test_2_notification_with_explicit_timeout(self) -> None: -+ """ -+ Sends notifications with expire_timeout -+ """ -+ summary = "expire_timeout" -+ send_notification({ -+ "app_name": "Appium Test", -+ "summary": summary, -+ "body": "Will it disappear automatically?", -+ "timeout": 2000, -+ }) -+ element = self.driver.find_element(AppiumBy.NAME, summary) -+ WebDriverWait(self.driver, 5).until_not(lambda _: element.is_displayed()) -+>>>>>>> 5145d877d8 (applets/notifications: suppress inhibited notifications after "Do not disturb" is off) - - def test_3_accessible_description_html_to_plaintext(self) -> None: - """ -@@ -157,6 +207,202 @@ class NotificationsTest(unittest.TestCase): - }) - wait = WebDriverWait(self.driver, 5) - wait.until(EC.presence_of_element_located(("description", "biublinkwww.example.org from Appium Test"))) -+<<<<<<< HEAD -+======= -+ self.close_notifications() -+ -+ def test_4_actions(self) -> None: -+ """ -+ When the "actions" key is set, a notification can provide actions. -+ """ -+ loop = GLib.MainLoop() -+ activation_token = False -+ params_1: list[Any] = [] -+ action_invoked = False -+ params_2: list[Any] = [] -+ notification_closed = False -+ params_3: list[Any] = [] -+ -+ def notification_signal_handler(d_bus_proxy: Gio.DBusProxy, sender_name: str, signal_name: str, parameters: GLib.Variant) -> None: -+ nonlocal params_2, params_3, params_1, activation_token, action_invoked, notification_closed -+ logging.info(f"received signal {signal_name}") -+ match signal_name: -+ case "ActivationToken": -+ params_1 = parameters.unpack() -+ activation_token = True -+ case "ActionInvoked": -+ params_2 = parameters.unpack() -+ action_invoked = True -+ case "NotificationClosed": -+ params_3 = parameters.unpack() -+ notification_closed = True -+ loop.quit() -+ -+ connection_id = self.notification_proxy.connect("g-signal", notification_signal_handler) -+ self.addCleanup(lambda: self.notification_proxy.disconnect(connection_id)) -+ -+ notification_id = send_notification({ -+ "app_name": "Appium Test", -+ "body": "A notification with actions", -+ "actions": ["action1", "FooAction", "action2", "BarAction"], -+ }) -+ self.driver.find_element(AppiumBy.NAME, "BarAction") -+ element = self.driver.find_element(AppiumBy.NAME, "FooAction") -+ element.click() -+ loop.run() -+ self.assertTrue(activation_token) -+ self.assertEqual(params_1[0], notification_id) -+ self.assertTrue(action_invoked) -+ self.assertEqual(params_2[0], notification_id) -+ self.assertEqual(params_2[1], "action1") -+ self.assertTrue(notification_closed) -+ self.assertEqual(params_3[0], notification_id) -+ self.assertEqual(params_3[1], 3) # reason: Revoked -+ self.assertFalse(element.is_displayed()) -+ -+ def test_5_inline_reply(self) -> None: -+ """ -+ When the action list has "inline-reply", the notification popup will contain a text field and a reply button. -+ """ -+ loop = GLib.MainLoop() -+ notification_replied = False -+ params: list[Any] = [] # id, text -+ -+ def notification_signal_handler(d_bus_proxy: Gio.DBusProxy, sender_name: str, signal_name: str, parameters: GLib.Variant) -> None: -+ nonlocal params, notification_replied -+ logging.info(f"received signal {signal_name}") -+ if signal_name == "NotificationReplied": -+ params = parameters.unpack() -+ notification_replied = True -+ loop.quit() -+ -+ connection_id = self.notification_proxy.connect("g-signal", notification_signal_handler) -+ self.addCleanup(lambda: self.notification_proxy.disconnect(connection_id)) -+ -+ # When there is only one action and it is a reply action, show text field right away -+ notification_id = send_notification({ -+ "app_name": "Appium Test", -+ "body": "A notification with actions 1", -+ "actions": ["inline-reply", ""], # Use the default label -+ }) -+ reply_text = "this is a reply" -+ self.driver.find_element(AppiumBy.NAME, "begin reply").click() -+ self.driver.find_element(AppiumBy.NAME, "Type a reply…").send_keys(reply_text) -+ element = self.driver.find_element(AppiumBy.NAME, "Send") -+ element.click() -+ loop.run() -+ self.assertTrue(notification_replied) -+ self.assertEqual(params[0], notification_id) -+ self.assertEqual(params[1], reply_text) -+ self.assertFalse(element.is_displayed()) -+ -+ notification_replied = False -+ notification_id = send_notification({ -+ "app_name": "Appium Test", -+ "body": "A notification with actions 2", -+ "actions": ["inline-reply", ""], -+ "hints": { -+ "x-kde-reply-submit-button-text": GLib.Variant("s", "Reeply"), # Use a custom label -+ "x-kde-reply-placeholder-text": GLib.Variant("s", "A placeholder"), # Use a custom placeholder -+ }, -+ }) -+ reply_text = "this is another reply" -+ self.driver.find_element(AppiumBy.NAME, "begin reply").click() -+ self.driver.find_element(AppiumBy.NAME, "A placeholder").send_keys(reply_text) -+ element = self.driver.find_element(AppiumBy.NAME, "Reeply") -+ element.click() -+ loop.run() -+ self.assertTrue(notification_replied) -+ self.assertEqual(params[0], notification_id) -+ self.assertEqual(params[1], reply_text) -+ self.assertFalse(element.is_displayed()) -+ -+ notification_replied = False -+ notification_id = send_notification({ -+ "app_name": "Appium Test", -+ "body": "A notification with actions 3", -+ "actions": ["inline-reply", "Replyy", "foo", "Foo", "bar", "Bar"], # Click to show the text field -+ }) -+ self.driver.find_element(AppiumBy.NAME, "Foo") -+ self.driver.find_element(AppiumBy.NAME, "Bar") -+ element = self.driver.find_element(AppiumBy.NAME, "Replyy") -+ element.click() -+ reply_text = "Click Replyy to reply" -+ self.driver.find_element(AppiumBy.NAME, "Type a reply…").send_keys(reply_text) -+ self.assertFalse(element.is_displayed()) -+ element = self.driver.find_element(AppiumBy.NAME, "Send") -+ element.click() -+ loop.run() -+ self.assertTrue(notification_replied) -+ self.assertEqual(params[0], notification_id) -+ self.assertEqual(params[1], reply_text) -+ -+ def test_6_thumbnail(self) -> None: -+ """ -+ When a notification has "x-kde-urls" hint, a thumbnail will be shown for the first url in the list -+ """ -+ with tempfile.TemporaryDirectory() as temp_dir: -+ pixbuf = GdkPixbuf.Pixbuf.new(GdkPixbuf.Colorspace.RGB, True, 8, 256, 256) -+ colors = (0xff0000ff, 0x00ff00ff, 0x0000ffff) -+ for color in colors: -+ pixbuf.fill(color) -+ pixbuf.savev(os.path.join(temp_dir, f"{str(color)}.png"), "png") -+ -+ url_list = [f"file://{os.path.join(temp_dir, path)}" for path in os.listdir(temp_dir)] -+ url_list.sort() -+ send_notification({ -+ "app_name": "Appium Test", -+ "body": "Thumbnail", -+ "hints": { -+ "x-kde-urls": GLib.Variant("as", url_list), -+ }, -+ "timeout": 10 * 1000, -+ }) -+ -+ self.driver.find_element(AppiumBy.NAME, "More Options…") -+ -+ partial_pixbuf = GdkPixbuf.Pixbuf.new(GdkPixbuf.Colorspace.RGB, True, 8, 100, 100) -+ partial_pixbuf.fill(colors[1]) # Green is the first item -+ partial_image = base64.b64encode(Gdk.Texture.new_for_pixbuf(partial_pixbuf).save_to_png_bytes().get_data()).decode() -+ -+ def match_image(driver) -> bool: -+ try: -+ self.driver.find_image_occurrence(self.take_screenshot(), partial_image) -+ return True -+ except WebDriverException: -+ return False -+ -+ WebDriverWait(self.driver, 10).until(match_image) -+ self.close_notifications() -+ -+ def test_7_do_not_disturb(self) -> None: -+ """ -+ Suppress inhibited notifications after "Do not disturb" is turned off, and show a summary for unread inhibited notifications. -+ """ -+ self.driver.find_element(AppiumBy.NAME, "Do not disturb").click() -+ dnd_button = self.driver.find_element(AppiumBy.XPATH, "//*[@name='Do not disturb' and contains(@states, 'checked')]") -+ -+ summary = "Do not disturb me" -+ for i in range(2): -+ send_notification({ -+ "app_name": "Appium Test", -+ "summary": summary + str(i), -+ "hints": { -+ "desktop-entry": GLib.Variant("s", "org.kde.plasmashell"), -+ }, -+ "timeout": 60 * 1000, -+ }) -+ title = self.driver.find_element(AppiumBy.XPATH, f"//heading[starts-with(@name, '{summary}') and contains(@accessibility-id, 'FullRepresentation')]") -+ self.assertRaises(NoSuchElementException, self.driver.find_element, AppiumBy.XPATH, f"//notification[starts-with(@name, '{summary}')]") -+ -+ dnd_button.click() -+ self.driver.find_element(AppiumBy.XPATH, "//notification[@name='Unread Notifications' and @description='2 notifications were received while Do Not Disturb was active. from Notification Manager']") -+ self.driver.find_element(AppiumBy.XPATH, "//button[@name='Close' and contains(@accessibility-id, 'NotificationPopup')]").click() -+ -+ # Notifications can only be cleared after they are expired, otherwise they will stay in the list -+ self.driver.find_element(AppiumBy.NAME, "Clear All Notifications").click() -+ WebDriverWait(self.driver, 5).until_not(lambda _: title.is_displayed()) -+>>>>>>> 5145d877d8 (applets/notifications: suppress inhibited notifications after "Do not disturb" is off) - - - if __name__ == '__main__': -diff --git a/applets/notifications/package/contents/ui/FullRepresentation.qml b/applets/notifications/package/contents/ui/FullRepresentation.qml -index f949dad46a..33838599f6 100644 ---- a/applets/notifications/package/contents/ui/FullRepresentation.qml -+++ b/applets/notifications/package/contents/ui/FullRepresentation.qml -@@ -71,6 +71,14 @@ PlasmaExtras.Representation { - checkable: true - checked: Globals.inhibited - -+ Accessible.onPressAction: if (Globals.inhibited) { -+ Globals.revokeInhibitions(); -+ } else { -+ let date = new Date(); -+ date.setFullYear(date.getFullYear() + 1); -+ notificationSettings.notificationsInhibitedUntil = date; -+ notificationSettings.save(); -+ } - KeyNavigation.down: list - KeyNavigation.tab: list - -diff --git a/applets/notifications/package/contents/ui/global/Globals.qml b/applets/notifications/package/contents/ui/global/Globals.qml -index c46c32921a..2238653eff 100644 ---- a/applets/notifications/package/contents/ui/global/Globals.qml -+++ b/applets/notifications/package/contents/ui/global/Globals.qml -@@ -34,6 +34,10 @@ QtObject { - property bool inhibited: false - - onInhibitedChanged: { -+ if (!inhibited) { -+ popupNotificationsModel.showInhibitionSummary(); -+ } -+ - var pa = pulseAudio.item; - if (!pa) { - return; -@@ -405,6 +409,7 @@ QtObject { - limit: plasmoid ? (Math.ceil(globals.screenRect.height / (Kirigami.Units.gridUnit * 4))) : 0 - showExpired: false - showDismissed: false -+ showAddedDuringInhibition: false - blacklistedDesktopEntries: notificationSettings.popupBlacklistedApplications - blacklistedNotifyRcNames: notificationSettings.popupBlacklistedServices - whitelistedDesktopEntries: globals.inhibited ? notificationSettings.doNotDisturbPopupWhitelistedApplications : [] -@@ -613,9 +618,13 @@ QtObject { - onKillJobClicked: popupNotificationsModel.killJob(popupNotificationsModel.index(index, 0)) - - // popup width is fixed -- onHeightChanged: positionPopups() -+ onHeightChanged: globals.positionPopups() - - Component.onCompleted: { -+ if (globals.inhibited) { -+ model.wasAddedDuringInhibition = false; // Don't count already shown notifications -+ } -+ - if (model.type === NotificationManager.Notifications.NotificationType && model.desktopEntry) { - // Register apps that were seen spawning a popup so they can be configured later - // Apps with notifyrc can already be configured anyway -diff --git a/libnotificationmanager/CMakeLists.txt b/libnotificationmanager/CMakeLists.txt -index d04d8a4a24..5c48653ede 100644 ---- a/libnotificationmanager/CMakeLists.txt -+++ b/libnotificationmanager/CMakeLists.txt -@@ -84,6 +84,7 @@ target_link_libraries(notificationmanager - KF6::I18n - KF6::WindowSystem - KF6::ItemModels # KDescendantsProxyModel -+ KF6::Notifications # Inhibition summary - KF6::KIOFileWidgets - Plasma::Plasma - KF6::Screen -@@ -135,3 +136,6 @@ install(EXPORT notificationmanagerLibraryTargets - - install(FILES plasmanotifyrc - DESTINATION ${KDE_INSTALL_CONFDIR}) -+ -+install(FILES libnotificationmanager.notifyrc -+ DESTINATION ${KDE_INSTALL_KNOTIFYRCDIR}) -diff --git a/libnotificationmanager/abstractnotificationsmodel.cpp b/libnotificationmanager/abstractnotificationsmodel.cpp -index 8bd55bc26c..8307a1e8f7 100644 ---- a/libnotificationmanager/abstractnotificationsmodel.cpp -+++ b/libnotificationmanager/abstractnotificationsmodel.cpp -@@ -104,6 +104,7 @@ void AbstractNotificationsModel::Private::onNotificationReplaced(uint replacedId - newNotification.setExpired(oldNotification.expired()); - newNotification.setDismissed(oldNotification.dismissed()); - newNotification.setRead(oldNotification.read()); -+ newNotification.setWasAddedDuringInhibition(Server::self().inhibited()); - - notifications[row] = newNotification; - const QModelIndex idx = q->index(row, 0); -@@ -378,6 +379,9 @@ QVariant AbstractNotificationsModel::data(const QModelIndex &index, int role) co - case Notifications::TransientRole: - return notification.transient(); - -+ case Notifications::WasAddedDuringInhibitionRole: -+ return notification.wasAddedDuringInhibition(); -+ - case Notifications::HasReplyActionRole: - return notification.hasReplyAction(); - case Notifications::ReplyActionLabelRole: -@@ -416,6 +420,12 @@ bool AbstractNotificationsModel::setData(const QModelIndex &index, const QVarian - dirty = true; - } - break; -+ case Notifications::WasAddedDuringInhibitionRole: -+ if (bool v = value.toBool(); v != notification.wasAddedDuringInhibition()) { -+ notification.setWasAddedDuringInhibition(v); -+ dirty = true; -+ } -+ break; - } - - if (dirty) { -@@ -471,7 +481,7 @@ void AbstractNotificationsModel::clear(Notifications::ClearFlags flags) - for (int i = 0; i < d->notifications.count(); ++i) { - const Notification ¬ification = d->notifications.at(i); - -- if (flags.testFlag(Notifications::ClearExpired) && notification.expired()) { -+ if (flags.testFlag(Notifications::ClearExpired) && (notification.expired() || notification.wasAddedDuringInhibition())) { - close(notification.id()); - } - } -diff --git a/libnotificationmanager/libnotificationmanager.notifyrc b/libnotificationmanager/libnotificationmanager.notifyrc -new file mode 100644 -index 0000000000..79304e62bc ---- /dev/null -+++ b/libnotificationmanager/libnotificationmanager.notifyrc -@@ -0,0 +1,11 @@ -+# SPDX-License-Identifier: CC0-1.0 -+# SPDX-FileCopyrightText: None -+ -+[Global] -+Name=Notification Manager -+IconName=preferences-desktop-notification-bell -+ -+[Event/inhibitionSummary] -+Name=Summary for unread inhibited notifications -+Action=Popup -+Urgency=Low -diff --git a/libnotificationmanager/notification.cpp b/libnotificationmanager/notification.cpp -index 276611310c..320c837617 100644 ---- a/libnotificationmanager/notification.cpp -+++ b/libnotificationmanager/notification.cpp -@@ -826,3 +826,13 @@ void Notification::processHints(const QVariantMap &hints) - { - d->processHints(hints); - } -+ -+bool Notification::wasAddedDuringInhibition() const -+{ -+ return d->wasAddedDuringInhibition; -+} -+ -+void Notification::setWasAddedDuringInhibition(bool value) -+{ -+ d->wasAddedDuringInhibition = value; -+} -diff --git a/libnotificationmanager/notification.h b/libnotificationmanager/notification.h -index 9f04a4e200..8613534fd2 100644 ---- a/libnotificationmanager/notification.h -+++ b/libnotificationmanager/notification.h -@@ -131,6 +131,9 @@ public: - - void processHints(const QVariantMap &hints); - -+ bool wasAddedDuringInhibition() const; -+ void setWasAddedDuringInhibition(bool value); -+ - private: - friend class NotificationsModel; - friend class AbstractNotificationsModel; -diff --git a/libnotificationmanager/notification_p.h b/libnotificationmanager/notification_p.h -index 6cae23c21e..923bde7882 100644 ---- a/libnotificationmanager/notification_p.h -+++ b/libnotificationmanager/notification_p.h -@@ -96,6 +96,8 @@ public: - - bool resident = false; - bool transient = false; -+ -+ bool wasAddedDuringInhibition = false; - }; - - } // namespace NotificationManager -diff --git a/libnotificationmanager/notificationfilterproxymodel.cpp b/libnotificationmanager/notificationfilterproxymodel.cpp -index 98a32b6645..bb573713af 100644 ---- a/libnotificationmanager/notificationfilterproxymodel.cpp -+++ b/libnotificationmanager/notificationfilterproxymodel.cpp -@@ -58,6 +58,20 @@ void NotificationFilterProxyModel::setShowDismissed(bool show) - } - } - -+bool NotificationFilterProxyModel::showAddedDuringInhibition() const -+{ -+ return m_showDismissed; -+} -+ -+void NotificationFilterProxyModel::setShowAddedDuringInhibition(bool show) -+{ -+ if (m_showAddedDuringInhibition != show) { -+ m_showAddedDuringInhibition = show; -+ invalidateFilter(); -+ Q_EMIT showAddedDuringInhibitionChanged(); -+ } -+} -+ - QStringList NotificationFilterProxyModel::blacklistedDesktopEntries() const - { - return m_blacklistedDesktopEntries; -@@ -177,5 +191,9 @@ bool NotificationFilterProxyModel::filterAcceptsRow(int source_row, const QModel - } - } - -+ if (!m_showAddedDuringInhibition && sourceIdx.data(Notifications::WasAddedDuringInhibitionRole).toBool()) { -+ return false; -+ } -+ - return true; - } -diff --git a/libnotificationmanager/notificationfilterproxymodel_p.h b/libnotificationmanager/notificationfilterproxymodel_p.h -index 4029320e8e..af04a9fac1 100644 ---- a/libnotificationmanager/notificationfilterproxymodel_p.h -+++ b/libnotificationmanager/notificationfilterproxymodel_p.h -@@ -30,6 +30,9 @@ public: - bool showDismissed() const; - void setShowDismissed(bool show); - -+ bool showAddedDuringInhibition() const; -+ void setShowAddedDuringInhibition(bool show); -+ - QStringList blacklistedDesktopEntries() const; - void setBlackListedDesktopEntries(const QStringList &blacklist); - -@@ -46,6 +49,7 @@ Q_SIGNALS: - void urgenciesChanged(); - void showExpiredChanged(); - void showDismissedChanged(); -+ void showAddedDuringInhibitionChanged(); - void blacklistedDesktopEntriesChanged(); - void blacklistedNotifyRcNamesChanged(); - void whitelistedDesktopEntriesChanged(); -@@ -58,6 +62,7 @@ private: - Notifications::Urgencies m_urgencies = Notifications::LowUrgency | Notifications::NormalUrgency | Notifications::CriticalUrgency; - bool m_showDismissed = false; - bool m_showExpired = false; -+ bool m_showAddedDuringInhibition = true; - - QStringList m_blacklistedDesktopEntries; - QStringList m_blacklistedNotifyRcNames; -diff --git a/libnotificationmanager/notifications.cpp b/libnotificationmanager/notifications.cpp -index 9b3e18018b..3d5662f926 100644 ---- a/libnotificationmanager/notifications.cpp -+++ b/libnotificationmanager/notifications.cpp -@@ -12,6 +12,8 @@ - #include - - #include -+#include -+#include - - #include "limitedrowcountproxymodel_p.h" - #include "notificationfilterproxymodel_p.h" -@@ -30,6 +32,7 @@ - - #include "debug.h" - -+using namespace Qt::StringLiterals; - using namespace NotificationManager; - - class Q_DECL_HIDDEN Notifications::Private -@@ -166,6 +169,7 @@ void Notifications::Private::initProxyModels() - connect(filterModel, &NotificationFilterProxyModel::urgenciesChanged, q, &Notifications::urgenciesChanged); - connect(filterModel, &NotificationFilterProxyModel::showExpiredChanged, q, &Notifications::showExpiredChanged); - connect(filterModel, &NotificationFilterProxyModel::showDismissedChanged, q, &Notifications::showDismissedChanged); -+ connect(filterModel, &NotificationFilterProxyModel::showAddedDuringInhibitionChanged, q, &Notifications::showAddedDuringInhibitionChanged); - connect(filterModel, &NotificationFilterProxyModel::blacklistedDesktopEntriesChanged, q, &Notifications::blacklistedDesktopEntriesChanged); - connect(filterModel, &NotificationFilterProxyModel::blacklistedNotifyRcNamesChanged, q, &Notifications::blacklistedNotifyRcNamesChanged); - -@@ -245,7 +249,7 @@ void Notifications::Private::updateCount() - for (int i = 0; i < filterModel->rowCount(); ++i) { - const QModelIndex idx = filterModel->index(i, 0); - -- if (idx.data(Notifications::ExpiredRole).toBool()) { -+ if (idx.data(Notifications::ExpiredRole).toBool() || idx.data(Notifications::WasAddedDuringInhibitionRole).toBool()) { - ++expired; - } else { - ++active; -@@ -477,6 +481,16 @@ void Notifications::setShowDismissed(bool show) - d->filterModel->setShowDismissed(show); - } - -+bool Notifications::showAddedDuringInhibition() const -+{ -+ return d->filterModel->showAddedDuringInhibition(); -+} -+ -+void Notifications::setShowAddedDuringInhibition(bool show) -+{ -+ d->filterModel->setShowAddedDuringInhibition(show); -+} -+ - QStringList Notifications::blacklistedDesktopEntries() const - { - return d->filterModel->blacklistedDesktopEntries(); -@@ -812,6 +826,28 @@ void Notifications::collapseAllGroups() - } - } - -+void Notifications::showInhibitionSummary() -+{ -+ int inhibited = 0; -+ for (int i = 0, count = d->notificationsAndJobsModel->rowCount(); i < count; ++i) { -+ const QModelIndex idx = d->notificationsAndJobsModel->index(i, 0); -+ if (!idx.data(Notifications::ReadRole).toBool() && idx.data(Notifications::WasAddedDuringInhibitionRole).toBool()) { -+ ++inhibited; -+ } -+ } -+ -+ if (!inhibited) { -+ return; -+ } -+ -+ KNotification::event(u"inhibitionSummary"_s, -+ i18nc("@title", "Unread Notifications"), -+ i18nc("@info", "%1 notifications were received while Do Not Disturb was active.", QString::number(inhibited)), -+ u"preferences-desktop-notification-bell"_s, -+ KNotification::CloseOnTimeout, -+ u"libnotificationmanager"_s); -+} -+ - QVariant Notifications::data(const QModelIndex &index, int role) const - { - return QSortFilterProxyModel::data(index, role); -diff --git a/libnotificationmanager/notifications.h b/libnotificationmanager/notifications.h -index edb898988f..ef500b0c7b 100644 ---- a/libnotificationmanager/notifications.h -+++ b/libnotificationmanager/notifications.h -@@ -61,6 +61,15 @@ class NOTIFICATIONMANAGER_EXPORT Notifications : public QSortFilterProxyModel, p - */ - Q_PROPERTY(bool showDismissed READ showDismissed WRITE setShowDismissed NOTIFY showDismissedChanged) - -+ /** -+ * Whether to show notifications added during inhibition. -+ * -+ * If set to @c false, notifications are suppressed even after leaving "Do not disturb" mode. -+ * -+ * Default is @c true. -+ */ -+ Q_PROPERTY(bool showAddedDuringInhibition READ showAddedDuringInhibition WRITE setShowAddedDuringInhibition NOTIFY showAddedDuringInhibitionChanged) -+ - /** - * A list of desktop entries for which no notifications should be shown. - * -@@ -285,6 +294,8 @@ public: - ///< notification in a certain way, or group notifications of similar types. @since 5.21 - ResidentRole, ///< Whether the notification should keep its actions even when they were invoked. @since 5.22 - TransientRole, ///< Whether the notification is transient and should not be kept in history. @since 5.22 -+ -+ WasAddedDuringInhibitionRole, ///< Whether the notification was added while inhibition was active. @since 6.3 - }; - Q_ENUM(Roles) - -@@ -371,6 +382,9 @@ public: - bool showDismissed() const; - void setShowDismissed(bool show); - -+ bool showAddedDuringInhibition() const; -+ void setShowAddedDuringInhibition(bool show); -+ - QStringList blacklistedDesktopEntries() const; - void setBlacklistedDesktopEntries(const QStringList &blacklist); - -@@ -529,6 +543,11 @@ public: - - Q_INVOKABLE void collapseAllGroups(); - -+ /** -+ * Shows a notification to report the number of unread inhibited notifications. -+ */ -+ Q_INVOKABLE void showInhibitionSummary(); -+ - QVariant data(const QModelIndex &index, int role) const override; - bool setData(const QModelIndex &index, const QVariant &value, int role) override; - int rowCount(const QModelIndex &parent = QModelIndex()) const override; -@@ -541,6 +560,7 @@ Q_SIGNALS: - void limitChanged(); - void showExpiredChanged(); - void showDismissedChanged(); -+ void showAddedDuringInhibitionChanged(); - void blacklistedDesktopEntriesChanged(); - void blacklistedNotifyRcNamesChanged(); - void whitelistedDesktopEntriesChanged(); -diff --git a/libnotificationmanager/server_p.cpp b/libnotificationmanager/server_p.cpp -index 84fe37afa9..66cd621033 100644 ---- a/libnotificationmanager/server_p.cpp -+++ b/libnotificationmanager/server_p.cpp -@@ -164,6 +164,7 @@ uint ServerPrivate::Notify(const QString &app_name, - notification.setActions(actions); - - notification.setTimeout(timeout); -+ notification.setWasAddedDuringInhibition(m_inhibited); - - // might override some of the things we set above (like application name) - notification.d->processHints(hints); diff --git a/roles/kde/plasma.nix b/roles/kde/plasma.nix index 1f0f5db..00c4099 100644 --- a/roles/kde/plasma.nix +++ b/roles/kde/plasma.nix @@ -39,37 +39,6 @@ in { qt.enable = true; - nixpkgs.overlays = [ - ( - final: prev: { - kdePackages = prev.kdePackages.overrideScope ( - kFinal: kPrev: { - libplasma = kPrev.libplasma.overrideAttrs { - patches = [ - ./patches/libplasma-pr1214.patch - ]; - }; - plasma-workspace = kPrev.plasma-workspace.overrideAttrs { - patches = - kPrev.plasma-workspace.patches - ++ [ - ./patches/plasma_workspace-pr4883.patch - ./patches/plasma_workspace-pr4965.patch - ]; - }; - plasma-desktop = kPrev.plasma-desktop.overrideAttrs { - patches = - kPrev.plasma-desktop.patches - ++ [ - ./patches/plasma_desktop-pr2661.patch - ]; - }; - } - ); - } - ) - ]; - # GTK apps need dconf to grab the correct theme on Wayland programs.dconf.enable = true; diff --git a/roles/kde/programs/kwin.nix b/roles/kde/programs/kwin.nix index f6b176b..68b9f0a 100644 --- a/roles/kde/programs/kwin.nix +++ b/roles/kde/programs/kwin.nix @@ -1,24 +1,4 @@ {...}: { - nixpkgs.overlays = [ - ( - final: prev: { - kdePackages = prev.kdePackages.overrideScope ( - kFinal: kPrev: { - kwin = kPrev.kwin.overrideAttrs { - patches = - kPrev.kwin.patches - ++ [ - ../patches/kwin-pr6406.patch - ../patches/kwin-pr6878.patch - ../patches/kwin-pr6844.patch - ../patches/kwin-pr6985.patch - ]; - }; - } - ); - } - ) - ]; home-manager.users.toast = { programs.plasma = { kwin = {