From e15a9c0c0122963d00256a5eac15c03507d99206 Mon Sep 17 00:00:00 2001 From: Toast Date: Fri, 24 Oct 2025 01:07:37 +0200 Subject: [PATCH] Kde/patches: remove patches that are in plasma 6.5 --- flake.nix | 1 - .../plasma-workspace-patch-trimming.patch | 65 - roles/kde/patches/bluedevil/patches.txt | 2 - roles/kde/patches/bluedevil/pr218.patch | 53 - roles/kde/patches/breeze/patches.txt | 2 - roles/kde/patches/breeze/pr545.patch | 333 --- roles/kde/patches/kwin/patches.txt | 6 - roles/kde/patches/kwin/pr3612.patch | 2101 ----------------- roles/kde/patches/kwin/pr7823.patch | 26 - roles/kde/patches/kwin/pr7927.patch | 669 ------ roles/kde/patches/kwin/pr8005.patch | 71 - roles/kde/patches/kwin/pr8155.patch | 60 - roles/kde/patches/plasma-nm/patches.txt | 4 - roles/kde/patches/plasma-nm/pr439.patch | 140 -- roles/kde/patches/plasma-nm/pr442.patch | 168 -- .../plasma-workspace/commit8202ba92.patch | 248 -- .../kde/patches/plasma-workspace/patches.txt | 18 - .../kde/patches/plasma-workspace/pr5589.patch | 37 - .../kde/patches/plasma-workspace/pr5609.patch | 456 ---- .../kde/patches/plasma-workspace/pr5626.patch | 102 - .../kde/patches/plasma-workspace/pr5627.patch | 157 -- .../kde/patches/plasma-workspace/pr5657.patch | 177 -- .../kde/patches/plasma-workspace/pr5673.patch | 392 --- .../patches/plasma-workspace/pr5678.9.patch | 913 ------- .../kde/patches/plasma-workspace/pr5734.patch | 133 -- .../kde/patches/plasma-workspace/pr5746.patch | 33 - .../kde/patches/plasma-workspace/pr5782.patch | 32 - .../kde/patches/plasma-workspace/pr5788.patch | 42 - .../kde/patches/plasma-workspace/pr5816.patch | 349 --- roles/kde/patches/spectacle/patches.txt | 3 - roles/kde/patches/spectacle/pr460.patch | 228 -- 31 files changed, 7021 deletions(-) delete mode 100644 nixpkgs-patches/plasma-workspace-patch-trimming.patch delete mode 100644 roles/kde/patches/bluedevil/patches.txt delete mode 100644 roles/kde/patches/bluedevil/pr218.patch delete mode 100644 roles/kde/patches/breeze/patches.txt delete mode 100644 roles/kde/patches/breeze/pr545.patch delete mode 100644 roles/kde/patches/kwin/patches.txt delete mode 100644 roles/kde/patches/kwin/pr3612.patch delete mode 100644 roles/kde/patches/kwin/pr7823.patch delete mode 100644 roles/kde/patches/kwin/pr7927.patch delete mode 100644 roles/kde/patches/kwin/pr8005.patch delete mode 100644 roles/kde/patches/kwin/pr8155.patch delete mode 100644 roles/kde/patches/plasma-nm/patches.txt delete mode 100644 roles/kde/patches/plasma-nm/pr439.patch delete mode 100644 roles/kde/patches/plasma-nm/pr442.patch delete mode 100644 roles/kde/patches/plasma-workspace/commit8202ba92.patch delete mode 100644 roles/kde/patches/plasma-workspace/pr5589.patch delete mode 100644 roles/kde/patches/plasma-workspace/pr5609.patch delete mode 100644 roles/kde/patches/plasma-workspace/pr5626.patch delete mode 100644 roles/kde/patches/plasma-workspace/pr5627.patch delete mode 100644 roles/kde/patches/plasma-workspace/pr5657.patch delete mode 100644 roles/kde/patches/plasma-workspace/pr5673.patch delete mode 100644 roles/kde/patches/plasma-workspace/pr5678.9.patch delete mode 100644 roles/kde/patches/plasma-workspace/pr5734.patch delete mode 100644 roles/kde/patches/plasma-workspace/pr5746.patch delete mode 100644 roles/kde/patches/plasma-workspace/pr5782.patch delete mode 100644 roles/kde/patches/plasma-workspace/pr5788.patch delete mode 100644 roles/kde/patches/plasma-workspace/pr5816.patch delete mode 100644 roles/kde/patches/spectacle/patches.txt delete mode 100644 roles/kde/patches/spectacle/pr460.patch diff --git a/flake.nix b/flake.nix index 4bc669d..0392399 100644 --- a/flake.nix +++ b/flake.nix @@ -101,7 +101,6 @@ name = "patched-nixpkgs-unstable"; src = nixpkgs-unstable-raw; patches = [ - ./nixpkgs-patches/plasma-workspace-patch-trimming.patch ]; }; nixpkgs-patched = nixpkgs-raw.legacyPackages.x86_64-linux.applyPatches { diff --git a/nixpkgs-patches/plasma-workspace-patch-trimming.patch b/nixpkgs-patches/plasma-workspace-patch-trimming.patch deleted file mode 100644 index bfd45ce..0000000 --- a/nixpkgs-patches/plasma-workspace-patch-trimming.patch +++ /dev/null @@ -1,65 +0,0 @@ -diff --git a/pkgs/kde/plasma/plasma-workspace/dependency-paths.patch b/pkgs/kde/plasma/plasma-workspace/dependency-paths.patch -index b4d46cd869bb..ca5bfd2c4889 100644 ---- a/pkgs/kde/plasma/plasma-workspace/dependency-paths.patch -+++ b/pkgs/kde/plasma/plasma-workspace/dependency-paths.patch -@@ -1,13 +1,14 @@ - diff --git a/applets/devicenotifier/plugin/deviceerrormonitor_p.cpp b/applets/devicenotifier/plugin/deviceerrormonitor_p.cpp --index ba214a555d..421d940738 100644 -+index 183f207946..0f64b068ab 100644 - --- a/applets/devicenotifier/plugin/deviceerrormonitor_p.cpp - +++ b/applets/devicenotifier/plugin/deviceerrormonitor_p.cpp --@@ -155,7 +155,7 @@ void DeviceErrorMonitor::queryBlockingApps(const QString &devicePath) -+@@ -168,7 +168,8 @@ void DeviceErrorMonitor::queryBlockingApps(const QString &devicePath) - Q_EMIT blockingAppsReady(blockApps); - p->deleteLater(); - }); - - p->start(QStringLiteral("lsof"), {QStringLiteral("-t"), devicePath}); - + p->start(QStringLiteral("@lsof@"), {QStringLiteral("-t"), devicePath}); -++ // @dbusSend@ - // p.start(QStringLiteral("fuser"), {QStringLiteral("-m"), devicePath}); - } - -@@ -51,7 +52,7 @@ index e4d1ad4311..d45bdfad98 100644 - - void CFcQuery::procExited() - diff --git a/kcms/krdb/krdb.cpp b/kcms/krdb/krdb.cpp --index f3c9956921..09c818739d 100644 -+index 53f77d0a18..680e81b6e4 100644 - --- a/kcms/krdb/krdb.cpp - +++ b/kcms/krdb/krdb.cpp - @@ -425,7 +425,7 @@ void runRdb(unsigned int flags) -@@ -107,7 +108,7 @@ index 7218628ce9..9126475ea4 100644 - +ExecStart=@qdbus@ org.kde.kcminit /kcminit org.kde.KCMInit.runPhase1 - Slice=session.slice - diff --git a/startkde/startplasma.cpp b/startkde/startplasma.cpp --index 4d31c6f408..17418b1ff7 100644 -+index 02c3f260fb..795244064d 100644 - --- a/startkde/startplasma.cpp - +++ b/startkde/startplasma.cpp - @@ -57,7 +57,7 @@ void sigtermHandler(int signalNumber) -@@ -119,7 +120,7 @@ index 4d31c6f408..17418b1ff7 100644 - } - - QStringList allServices(const QLatin1String &prefix) --@@ -512,7 +512,7 @@ QProcess *setupKSplash() -+@@ -508,7 +508,7 @@ QProcess *setupKSplash() - if (ksplashCfg.readEntry("Engine", QStringLiteral("KSplashQML")) == QLatin1String("KSplashQML")) { - p = new QProcess; - p->setProcessChannelMode(QProcess::ForwardedChannels); -@@ -128,14 +129,4 @@ index 4d31c6f408..17418b1ff7 100644 - } - } - return p; --diff --git a/startkde/systemd/plasma-ksplash-ready.service.in b/startkde/systemd/plasma-ksplash-ready.service.in --index 1e903130a9..0861c3d136 100644 ----- a/startkde/systemd/plasma-ksplash-ready.service.in --+++ b/startkde/systemd/plasma-ksplash-ready.service.in --@@ -6,5 +6,5 @@ PartOf=graphical-session.target -- -- [Service] -- Type=oneshot ---ExecStart=dbus-send --session --reply-timeout=1 --type=method_call --dest=org.kde.KSplash /KSplash org.kde.KSplash.setStage string:ready --+ExecStart=@dbusSend@ --session --reply-timeout=1 --type=method_call --dest=org.kde.KSplash /KSplash org.kde.KSplash.setStage string:ready -- Slice=session.slice -+ - diff --git a/roles/kde/patches/bluedevil/patches.txt b/roles/kde/patches/bluedevil/patches.txt deleted file mode 100644 index f1e689d..0000000 --- a/roles/kde/patches/bluedevil/patches.txt +++ /dev/null @@ -1,2 +0,0 @@ -Plasma 6.5.0: -Pr 218 https://invent.kde.org/plasma/bluedevil/-/merge_requests/218 diff --git a/roles/kde/patches/bluedevil/pr218.patch b/roles/kde/patches/bluedevil/pr218.patch deleted file mode 100644 index c9eca02..0000000 --- a/roles/kde/patches/bluedevil/pr218.patch +++ /dev/null @@ -1,53 +0,0 @@ -From ddbf7452c6f2fe8849de723175710dbf39242422 Mon Sep 17 00:00:00 2001 -From: Nate Graham -Date: Fri, 13 Jun 2025 11:55:47 -0600 -Subject: [PATCH] applet: use standard section headers - -We have them; might as well use them. This lets us get rid of a bunch of -complex and fragile code that's responsible for the existing separator. ---- - .../contents/ui/FullRepresentation.qml | 27 +++---------------- - 1 file changed, 3 insertions(+), 24 deletions(-) - -diff --git a/src/applet/package/contents/ui/FullRepresentation.qml b/src/applet/package/contents/ui/FullRepresentation.qml -index df989267d..27295de21 100644 ---- a/src/applet/package/contents/ui/FullRepresentation.qml -+++ b/src/applet/package/contents/ui/FullRepresentation.qml -@@ -111,31 +111,10 @@ PlasmaExtras.Representation { - bottomMargin: Kirigami.Units.largeSpacing - - section.property: "Section" -- // We want to hide the section delegate for the "Connected" -- // group because it's unnecessary; all we want to do here is -- // separate the connected devices from the available ones -- section.delegate: Loader { -+ section.delegate: PlasmaExtras.ListSectionHeader { - required property string section -- -- active: section !== "Connected" && BluezQt.Manager.connectedDevices.length > 0 -- -- width: ListView.view.width - ListView.view.leftMargin - ListView.view.rightMargin -- // Need to manually set the height or else the loader takes up -- // space after the first time it unloads a previously-loaded item -- height: active ? Kirigami.Units.gridUnit : 0 -- -- // give us 2 frames to try and figure out a layout, this reduces jumpyness quite a bit but doesn't -- // entirely eliminate it https://bugs.kde.org/show_bug.cgi?id=438610 -- Behavior on height { PropertyAnimation { duration: 32 } } -- -- sourceComponent: Item { -- KSvg.SvgItem { -- width: parent.width - Kirigami.Units.gridUnit * 2 -- anchors.centerIn: parent -- imagePath: "widgets/line" -- elementId: "horizontal-line" -- } -- } -+ width: listView.width - listView.leftMargin - listView.rightMargin -+ text: section - } - highlight: PlasmaExtras.Highlight {} - highlightMoveDuration: Kirigami.Units.shortDuration --- -GitLab - diff --git a/roles/kde/patches/breeze/patches.txt b/roles/kde/patches/breeze/patches.txt deleted file mode 100644 index bf22bd1..0000000 --- a/roles/kde/patches/breeze/patches.txt +++ /dev/null @@ -1,2 +0,0 @@ -Plasma 6.5.0: -Pr 545 https://invent.kde.org/plasma/breeze/-/merge_requests/545 diff --git a/roles/kde/patches/breeze/pr545.patch b/roles/kde/patches/breeze/pr545.patch deleted file mode 100644 index 926a40d..0000000 --- a/roles/kde/patches/breeze/pr545.patch +++ /dev/null @@ -1,333 +0,0 @@ -From 38567c2f2d2d0f8524e42224dff53a929cbf086a Mon Sep 17 00:00:00 2001 -From: Kai Uwe Broulik -Date: Sat, 24 May 2025 09:33:44 +0200 -Subject: [PATCH 1/3] kstyle/animations: Use QObject as base type rather than - QWidget - -This prepares for the animation engine being used with Qt Quick Controls. - -In most cases the type is cast to the relevant widget type for checking -anyway, so might as well use the cast object further down but not require -a QWidget at the beginning. ---- - kstyle/animations/breezeanimations.cpp | 48 +++++++++++++------------- - kstyle/animations/breezeanimations.h | 4 +-- - 2 files changed, 26 insertions(+), 26 deletions(-) - -diff --git a/kstyle/animations/breezeanimations.cpp b/kstyle/animations/breezeanimations.cpp -index 12e5107c5..ceab53802 100644 ---- a/kstyle/animations/breezeanimations.cpp -+++ b/kstyle/animations/breezeanimations.cpp -@@ -82,7 +82,7 @@ void Animations::setupEngines() - } - - //____________________________________________________________ --void Animations::registerWidget(QWidget *widget) const -+void Animations::registerWidget(QObject *widget) const - { - if (!widget) { - return; -@@ -101,37 +101,37 @@ void Animations::registerWidget(QWidget *widget) const - // for optimization, one should put with most used widgets here first - - // buttons -- if (qobject_cast(widget)) { -- _toolButtonEngine->registerWidget(widget, AnimationHover | AnimationFocus); -- _widgetStateEngine->registerWidget(widget, AnimationHover | AnimationFocus); -+ if (auto toolButton = qobject_cast(widget)) { -+ _toolButtonEngine->registerWidget(toolButton, AnimationHover | AnimationFocus); -+ _widgetStateEngine->registerWidget(toolButton, AnimationHover | AnimationFocus); - - } else if (qobject_cast(widget) || qobject_cast(widget)) { - _widgetStateEngine->registerWidget(widget, AnimationHover | AnimationFocus | AnimationPressed); - -- } else if (qobject_cast(widget)) { -+ } else if (auto button = qobject_cast(widget)) { - // register to toolbox engine if needed -- if (qobject_cast(widget->parent())) { -- _toolBoxEngine->registerWidget(widget); -+ if (auto toolBox = qobject_cast(widget->parent())) { -+ _toolBoxEngine->registerWidget(toolBox); - } - -- _widgetStateEngine->registerWidget(widget, AnimationHover | AnimationFocus); -+ _widgetStateEngine->registerWidget(button, AnimationHover | AnimationFocus); - - } - - // groupboxes - else if (QGroupBox *groupBox = qobject_cast(widget)) { - if (groupBox->isCheckable()) { -- _widgetStateEngine->registerWidget(widget, AnimationHover | AnimationFocus); -+ _widgetStateEngine->registerWidget(groupBox, AnimationHover | AnimationFocus); - } - } - - // sliders -- else if (qobject_cast(widget)) { -- _scrollBarEngine->registerWidget(widget, AnimationHover | AnimationFocus); -- } else if (qobject_cast(widget)) { -- _widgetStateEngine->registerWidget(widget, AnimationHover | AnimationFocus); -- } else if (qobject_cast(widget)) { -- _dialEngine->registerWidget(widget, AnimationHover | AnimationFocus); -+ else if (auto scrollBar = qobject_cast(widget)) { -+ _scrollBarEngine->registerWidget(scrollBar, AnimationHover | AnimationFocus); -+ } else if (auto slider = qobject_cast(widget)) { -+ _widgetStateEngine->registerWidget(slider, AnimationHover | AnimationFocus); -+ } else if (auto dial = qobject_cast(widget)) { -+ _dialEngine->registerWidget(dial, AnimationHover | AnimationFocus); - } - - // progress bar -@@ -162,24 +162,24 @@ void Animations::registerWidget(QWidget *widget) const - - // header views - // need to come before abstract item view, otherwise is skipped -- else if (qobject_cast(widget)) { -- _headerViewEngine->registerWidget(widget); -+ else if (auto headerView = qobject_cast(widget)) { -+ _headerViewEngine->registerWidget(headerView); - } - - // lists -- else if (qobject_cast(widget)) { -- _inputWidgetEngine->registerWidget(widget, AnimationHover | AnimationFocus); -+ else if (auto itemView = qobject_cast(widget)) { -+ _inputWidgetEngine->registerWidget(itemView, AnimationHover | AnimationFocus); - } - - // tabbar -- else if (qobject_cast(widget)) { -- _tabBarEngine->registerWidget(widget); -+ else if (auto tabBar = qobject_cast(widget)) { -+ _tabBarEngine->registerWidget(tabBar); - } - - // scrollarea - else if (QAbstractScrollArea *scrollArea = qobject_cast(widget)) { -- if (scrollArea->frameShadow() == QFrame::Sunken && (widget->focusPolicy() & Qt::StrongFocus)) { -- _inputWidgetEngine->registerWidget(widget, AnimationHover | AnimationFocus); -+ if (scrollArea->frameShadow() == QFrame::Sunken && (scrollArea->focusPolicy() & Qt::StrongFocus)) { -+ _inputWidgetEngine->registerWidget(scrollArea, AnimationHover | AnimationFocus); - } - } - -@@ -190,7 +190,7 @@ void Animations::registerWidget(QWidget *widget) const - } - - //____________________________________________________________ --void Animations::unregisterWidget(QWidget *widget) const -+void Animations::unregisterWidget(QObject *widget) const - { - if (!widget) { - return; -diff --git a/kstyle/animations/breezeanimations.h b/kstyle/animations/breezeanimations.h -index 6551900a5..3215e8fb9 100644 ---- a/kstyle/animations/breezeanimations.h -+++ b/kstyle/animations/breezeanimations.h -@@ -31,10 +31,10 @@ public: - explicit Animations(); - - //* register animations corresponding to given widget, depending on its type. -- void registerWidget(QWidget *widget) const; -+ void registerWidget(QObject *widget) const; - - /** unregister all animations associated to a widget */ -- void unregisterWidget(QWidget *widget) const; -+ void unregisterWidget(QObject *widget) const; - - //* enability engine - [[nodiscard]] WidgetStateEngine &widgetEnabilityEngine() const --- -GitLab - - -From 75201d1e63d82f7282c8bedcc7cc3d2417b1e123 Mon Sep 17 00:00:00 2001 -From: Kai Uwe Broulik -Date: Sat, 24 May 2025 09:40:01 +0200 -Subject: [PATCH 2/3] kstyle: Use styleObject for widgetStateEngine in - CheckBox/RadioButton - -Prepares it for doing the animation also with Qt Quick Controls -without an actual QWidget. ---- - kstyle/breezestyle.cpp | 44 ++++++++++++++++++++++++++++-------------- - 1 file changed, 29 insertions(+), 15 deletions(-) - -diff --git a/kstyle/breezestyle.cpp b/kstyle/breezestyle.cpp -index 09cee6003..19149c382 100644 ---- a/kstyle/breezestyle.cpp -+++ b/kstyle/breezestyle.cpp -@@ -4738,6 +4738,8 @@ bool Style::drawPanelItemViewItemPrimitive(const QStyleOption *option, QPainter - //___________________________________________________________________________________ - bool Style::drawIndicatorCheckBoxPrimitive(const QStyleOption *option, QPainter *painter, const QWidget *widget) const - { -+ const QObject *styleObject = widget ? widget : option->styleObject; -+ - // copy rect and palette - const auto &rect(option->rect); - const auto &palette(option->palette); -@@ -4757,15 +4759,15 @@ bool Style::drawIndicatorCheckBoxPrimitive(const QStyleOption *option, QPainter - } - - // animation state -- _animations->widgetStateEngine().updateState(widget, AnimationHover, mouseOver); -- _animations->widgetStateEngine().updateState(widget, AnimationPressed, checkBoxState != CheckOff); -+ _animations->widgetStateEngine().updateState(styleObject, AnimationHover, mouseOver); -+ _animations->widgetStateEngine().updateState(styleObject, AnimationPressed, checkBoxState != CheckOff); - auto target = checkBoxState; -- if (_animations->widgetStateEngine().isAnimated(widget, AnimationPressed)) { -+ if (_animations->widgetStateEngine().isAnimated(styleObject, AnimationPressed)) { - checkBoxState = CheckAnimated; - } -- const qreal animation(_animations->widgetStateEngine().opacity(widget, AnimationPressed)); -+ const qreal animation(_animations->widgetStateEngine().opacity(styleObject, AnimationPressed)); - -- const qreal opacity(_animations->widgetStateEngine().opacity(widget, AnimationHover)); -+ const qreal opacity(_animations->widgetStateEngine().opacity(styleObject, AnimationHover)); - - // render - _helper->renderCheckBoxBackground(painter, rect, palette, checkBoxState, hasHighlightNeutral(widget, option, mouseOver), sunken, animation); -@@ -4777,6 +4779,8 @@ bool Style::drawIndicatorCheckBoxPrimitive(const QStyleOption *option, QPainter - //___________________________________________________________________________________ - bool Style::drawIndicatorRadioButtonPrimitive(const QStyleOption *option, QPainter *painter, const QWidget *widget) const - { -+ const QObject *styleObject = widget ? widget : option->styleObject; -+ - // copy rect and palette - const auto &rect(option->rect); - const auto &palette(option->palette); -@@ -4791,19 +4795,27 @@ bool Style::drawIndicatorRadioButtonPrimitive(const QStyleOption *option, QPaint - RadioButtonState radioButtonState((state & State_On) ? RadioOn : RadioOff); - - // animation state -- _animations->widgetStateEngine().updateState(widget, AnimationHover, mouseOver); -- _animations->widgetStateEngine().updateState(widget, AnimationPressed, radioButtonState != RadioOff); -- if (_animations->widgetStateEngine().isAnimated(widget, AnimationPressed)) { -+ _animations->widgetStateEngine().updateState(styleObject, AnimationHover, mouseOver); -+ _animations->widgetStateEngine().updateState(styleObject, AnimationPressed, radioButtonState != RadioOff); -+ if (_animations->widgetStateEngine().isAnimated(styleObject, AnimationPressed)) { - radioButtonState = RadioAnimated; - } -- const qreal animation(_animations->widgetStateEngine().opacity(widget, AnimationPressed)); -+ const qreal animation(_animations->widgetStateEngine().opacity(styleObject, AnimationPressed)); - - // colors -- const qreal opacity(_animations->widgetStateEngine().opacity(widget, AnimationHover)); -+ const qreal opacity(_animations->widgetStateEngine().opacity(styleObject, AnimationHover)); - - // render -- _helper->renderRadioButtonBackground(painter, rect, palette, radioButtonState, hasHighlightNeutral(widget, option, mouseOver), sunken, animation); -- _helper->renderRadioButton(painter, rect, palette, mouseOver, radioButtonState, hasHighlightNeutral(widget, option, mouseOver), sunken, animation, opacity); -+ _helper->renderRadioButtonBackground(painter, rect, palette, radioButtonState, hasHighlightNeutral(styleObject, option, mouseOver), sunken, animation); -+ _helper->renderRadioButton(painter, -+ rect, -+ palette, -+ mouseOver, -+ radioButtonState, -+ hasHighlightNeutral(styleObject, option, mouseOver), -+ sunken, -+ animation, -+ opacity); - - return true; - } -@@ -5450,6 +5462,8 @@ bool Style::drawCheckBoxLabelControl(const QStyleOption *option, QPainter *paint - return true; - } - -+ const QObject *styleObject = widget ? widget : option->styleObject; -+ - // copy palette and rect - const auto &palette(option->palette); - const auto &rect(option->rect); -@@ -5501,10 +5515,10 @@ bool Style::drawCheckBoxLabelControl(const QStyleOption *option, QPainter *paint - const bool hasFocus(enabled && (state & State_HasFocus)); - - // update animation state -- _animations->widgetStateEngine().updateState(widget, AnimationFocus, hasFocus); -+ _animations->widgetStateEngine().updateState(styleObject, AnimationFocus, hasFocus); - -- const bool isFocusAnimated(_animations->widgetStateEngine().isAnimated(widget, AnimationFocus)); -- const qreal opacity(_animations->widgetStateEngine().opacity(widget, AnimationFocus)); -+ const bool isFocusAnimated(_animations->widgetStateEngine().isAnimated(styleObject, AnimationFocus)); -+ const qreal opacity(_animations->widgetStateEngine().opacity(styleObject, AnimationFocus)); - // focus color - QColor focusColor; - if (isFocusAnimated) { --- -GitLab - - -From a572df8f099969919544dbc2478de6aa2082a705 Mon Sep 17 00:00:00 2001 -From: Kai Uwe Broulik -Date: Sat, 24 May 2025 09:54:44 +0200 -Subject: [PATCH 3/3] kstyle: Register Qt Quick Controls with animations - -Since it's all software-rendered and indirect, only the -CheckBox and RadioButton controls are actually wired up -since the rest of the animations is quite subtle. ---- - kstyle/animations/breezeanimations.cpp | 9 +++++++++ - kstyle/breezestyle.cpp | 4 ++++ - 2 files changed, 13 insertions(+) - -diff --git a/kstyle/animations/breezeanimations.cpp b/kstyle/animations/breezeanimations.cpp -index ceab53802..5285f5daf 100644 ---- a/kstyle/animations/breezeanimations.cpp -+++ b/kstyle/animations/breezeanimations.cpp -@@ -100,6 +100,15 @@ void Animations::registerWidget(QObject *widget) const - // install animation timers - // for optimization, one should put with most used widgets here first - -+ // KQuickStyleItem. -+ const QString elementType = widget->property("elementType").toString(); -+ if (!elementType.isEmpty()) { -+ if (elementType == QLatin1String("checkbox") || elementType == QLatin1String("radiobutton")) { -+ _widgetStateEngine->registerWidget(widget, AnimationHover | AnimationFocus | AnimationPressed); -+ } -+ return; -+ } -+ - // buttons - if (auto toolButton = qobject_cast(widget)) { - _toolButtonEngine->registerWidget(toolButton, AnimationHover | AnimationFocus); -diff --git a/kstyle/breezestyle.cpp b/kstyle/breezestyle.cpp -index 19149c382..e549803f1 100644 ---- a/kstyle/breezestyle.cpp -+++ b/kstyle/breezestyle.cpp -@@ -4739,6 +4739,7 @@ bool Style::drawPanelItemViewItemPrimitive(const QStyleOption *option, QPainter - bool Style::drawIndicatorCheckBoxPrimitive(const QStyleOption *option, QPainter *painter, const QWidget *widget) const - { - const QObject *styleObject = widget ? widget : option->styleObject; -+ isQtQuickControl(option, widget); // registers it with widgetStateEngine. - - // copy rect and palette - const auto &rect(option->rect); -@@ -4780,6 +4781,7 @@ bool Style::drawIndicatorCheckBoxPrimitive(const QStyleOption *option, QPainter - bool Style::drawIndicatorRadioButtonPrimitive(const QStyleOption *option, QPainter *painter, const QWidget *widget) const - { - const QObject *styleObject = widget ? widget : option->styleObject; -+ isQtQuickControl(option, widget); // registers it with widgetStateEngine. - - // copy rect and palette - const auto &rect(option->rect); -@@ -5463,6 +5465,7 @@ bool Style::drawCheckBoxLabelControl(const QStyleOption *option, QPainter *paint - } - - const QObject *styleObject = widget ? widget : option->styleObject; -+ isQtQuickControl(option, widget); // registers it with widgetStateEngine. - - // copy palette and rect - const auto &palette(option->palette); -@@ -8460,6 +8463,7 @@ bool Style::isQtQuickControl(const QStyleOption *option, const QWidget *widget) - if (!widget && option) { - if (const auto item = qobject_cast(option->styleObject)) { - _windowManager->registerQuickItem(item); -+ _animations->registerWidget(item); - return true; - } - } --- -GitLab - diff --git a/roles/kde/patches/kwin/patches.txt b/roles/kde/patches/kwin/patches.txt deleted file mode 100644 index 7ac9343..0000000 --- a/roles/kde/patches/kwin/patches.txt +++ /dev/null @@ -1,6 +0,0 @@ -Plasma 6.5.0: -Pr 3612 https://invent.kde.org/plasma/kwin/-/merge_requests/3612 -Pr 7823 https://invent.kde.org/plasma/kwin/-/merge_requests/7823 -Pr 8005 https://invent.kde.org/plasma/kwin/-/merge_requests/8005 - Depends on Pr 7927 https://invent.kde.org/plasma/kwin/-/merge_requests/7927 -Pr 8155 https://invent.kde.org/plasma/kwin/-/merge_requests/8155 diff --git a/roles/kde/patches/kwin/pr3612.patch b/roles/kde/patches/kwin/pr3612.patch deleted file mode 100644 index 8a42f11..0000000 --- a/roles/kde/patches/kwin/pr3612.patch +++ /dev/null @@ -1,2101 +0,0 @@ -From 677241efbba5f2a725b247baf86a822127571225 Mon Sep 17 00:00:00 2001 -From: Vlad Zahorodnii -Date: Thu, 16 Feb 2023 10:16:17 +0200 -Subject: [PATCH 1/2] wayland: Implement xx-pip-v1 - -The xx-pip-v1 protocol provides clients a way to create floating windows -with miniature contents, for example a video (a movie or a video call), -a map with directions, a timer countdown, etc. - -These windows are placed in the overlay layer above all windows, including -fullscreen windows. - -This is an experimental version of the protocol. ---- - CMakeLists.txt | 4 + - src/CMakeLists.txt | 2 + - src/placement.cpp | 18 ++ - src/placement.h | 1 + - src/wayland/CMakeLists.txt | 2 + - src/wayland/protocols/xx-pip-v1.xml | 305 +++++++++++++++++++++++++++ - src/wayland/xdgshell.cpp | 2 +- - src/wayland/xdgshell_p.h | 2 + - src/wayland/xxpip_v1.cpp | 312 ++++++++++++++++++++++++++++ - src/wayland/xxpip_v1.h | 159 ++++++++++++++ - src/wayland_server.cpp | 7 + - src/window.cpp | 3 + - src/window.h | 6 + - src/xxpipv1integration.cpp | 43 ++++ - src/xxpipv1integration.h | 28 +++ - src/xxpipv1window.cpp | 179 ++++++++++++++++ - src/xxpipv1window.h | 48 +++++ - tests/CMakeLists.txt | 4 + - tests/pip/CMakeLists.txt | 22 ++ - tests/pip/main.cpp | 19 ++ - tests/pip/pip.cpp | 227 ++++++++++++++++++++ - tests/pip/pip.h | 102 +++++++++ - tests/pip/pipshellsurface.cpp | 156 ++++++++++++++ - tests/pip/pipshellsurface.h | 70 +++++++ - tests/pip/window.cpp | 19 ++ - tests/pip/window.h | 22 ++ - 26 files changed, 1761 insertions(+), 1 deletion(-) - create mode 100644 src/wayland/protocols/xx-pip-v1.xml - create mode 100644 src/wayland/xxpip_v1.cpp - create mode 100644 src/wayland/xxpip_v1.h - create mode 100644 src/xxpipv1integration.cpp - create mode 100644 src/xxpipv1integration.h - create mode 100644 src/xxpipv1window.cpp - create mode 100644 src/xxpipv1window.h - create mode 100644 tests/pip/CMakeLists.txt - create mode 100644 tests/pip/main.cpp - create mode 100644 tests/pip/pip.cpp - create mode 100644 tests/pip/pip.h - create mode 100644 tests/pip/pipshellsurface.cpp - create mode 100644 tests/pip/pipshellsurface.h - create mode 100644 tests/pip/window.cpp - create mode 100644 tests/pip/window.h - -diff --git a/CMakeLists.txt b/CMakeLists.txt -index 8d4d03b8459..6ac395557c8 100644 ---- a/CMakeLists.txt -+++ b/CMakeLists.txt -@@ -84,6 +84,10 @@ endif() - - if (BUILD_TESTING) - find_package(KPipeWire) -+ -+ if (Qt6WaylandClient_VERSION VERSION_GREATER_EQUAL "6.10.0") -+ find_package(Qt6WaylandClientPrivate ${REQUIRED_QT_VERSION} REQUIRED NO_MODULE) -+ endif() - endif() - - # required frameworks by Core -diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt -index 48fb847ca7c..978c98f7ff3 100644 ---- a/src/CMakeLists.txt -+++ b/src/CMakeLists.txt -@@ -211,6 +211,8 @@ target_sources(kwin PRIVATE - xdgshellintegration.cpp - xdgshellwindow.cpp - xkb.cpp -+ xxpipv1integration.cpp -+ xxpipv1window.cpp - ) - - target_link_libraries(kwin -diff --git a/src/placement.cpp b/src/placement.cpp -index 6c984282e5e..8890da56159 100644 ---- a/src/placement.cpp -+++ b/src/placement.cpp -@@ -48,6 +48,8 @@ std::optional Placement::place(const Window *c, const QRectF & - return placeOnScreenDisplay(c, area.toRect()); - } else if (c->isTransient() && c->surface()) { - return placeDialog(c, area.toRect(), options->placement()); -+ } else if (c->isPictureInPicture()) { -+ return placePictureInPicture(c, area.toRect()); - } else { - return place(c, area, options->placement()); - } -@@ -403,6 +405,22 @@ std::optional Placement::placeDialog(const Window *c, const QR - return placeOnMainWindow(c, area, nextPlacement); - } - -+std::optional Placement::placePictureInPicture(const Window *c, const QRect &area) -+{ -+ Q_ASSERT(area.isValid()); -+ -+ const QSizeF size = c->size(); -+ if (size.isEmpty()) { -+ return std::nullopt; -+ } -+ -+ const qreal x = area.x() + area.width() - size.width(); -+ const qreal y = area.y() + area.height() - size.height(); -+ -+ return QPointF(x, y); -+} -+ -+ - std::optional Placement::placeUnderMouse(const Window *c, const QRect &area, PlacementPolicy /*next*/) - { - const QSizeF size = c->size(); -diff --git a/src/placement.h b/src/placement.h -index 63be9df117f..e0da0f51ace 100644 ---- a/src/placement.h -+++ b/src/placement.h -@@ -44,6 +44,7 @@ private: - std::optional placeDialog(const Window *c, const QRect &area, PlacementPolicy next = PlacementUnknown); - std::optional placeUtility(const Window *c, const QRect &area, PlacementPolicy next = PlacementUnknown); - std::optional placeOnScreenDisplay(const Window *c, const QRect &area); -+ std::optional placePictureInPicture(const Window *c, const QRect &area); - }; - - } // namespace -diff --git a/src/wayland/CMakeLists.txt b/src/wayland/CMakeLists.txt -index fde7fc50348..7261395ecf9 100644 ---- a/src/wayland/CMakeLists.txt -+++ b/src/wayland/CMakeLists.txt -@@ -38,6 +38,7 @@ ecm_add_qtwayland_server_protocol_kde(WaylandProtocols_xml - ${PROJECT_SOURCE_DIR}/src/wayland/protocols/drm.xml - ${PROJECT_SOURCE_DIR}/src/wayland/protocols/frog-color-management-v1.xml - ${PROJECT_SOURCE_DIR}/src/wayland/protocols/wlr-layer-shell-unstable-v1.xml -+ ${PROJECT_SOURCE_DIR}/src/wayland/protocols/xx-pip-v1.xml - ${PROJECT_SOURCE_DIR}/src/wayland/protocols/xx-session-management-v1.xml - - ${WaylandProtocols_DATADIR}/stable/presentation-time/presentation-time.xml -@@ -174,6 +175,7 @@ target_sources(kwin PRIVATE - xdgtopleveltag_v1.cpp - xwaylandkeyboardgrab_v1.cpp - xwaylandshell_v1.cpp -+ xxpip_v1.cpp - ) - - install(FILES -diff --git a/src/wayland/protocols/xx-pip-v1.xml b/src/wayland/protocols/xx-pip-v1.xml -new file mode 100644 -index 00000000000..c5eb7636ee9 ---- /dev/null -+++ b/src/wayland/protocols/xx-pip-v1.xml -@@ -0,0 +1,305 @@ -+ -+ -+ -+ Copyright © 2025 Vlad Zahorodnii -+ -+ Permission is hereby granted, free of charge, to any person obtaining a -+ copy of this software and associated documentation files (the "Software"), -+ to deal in the Software without restriction, including without limitation -+ the rights to use, copy, modify, merge, publish, distribute, sublicense, -+ and/or sell copies of the Software, and to permit persons to whom the -+ Software is furnished to do so, subject to the following conditions: -+ -+ The above copyright notice and this permission notice (including the next -+ paragraph) shall be included in all copies or substantial portions of the -+ Software. -+ -+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL -+ THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -+ DEALINGS IN THE SOFTWARE. -+ -+ -+ -+ -+ The xx_pip_shell_v1 interface provides a way to create picture-in-picture -+ windows. -+ -+ Use cases are for example playing a video in a separate floating window. -+ -+ Warning! The protocol described in this file is currently in the testing -+ phase. Backward compatible changes may be added together with the -+ corresponding interface version bump. Backward incompatible changes can -+ only be done by creating a new major version of the extension. -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ Destroy this xx_pip_shell_v1 object. Objects that have been created -+ through this instance are unaffected. -+ -+ -+ -+ -+ -+ This creates an xx_pip_v1 for the given xdg_surface and gives the -+ associated wl_surface the xx_pip_v1 role. -+ -+ If the wl_surface already has a role assigned, a role protocol error -+ will be raised. -+ -+ Creating a picture-in-picture surface from a wl_surface which has a -+ buffer attached or committed is a client error, and any attempts by -+ a client to attach or manipulate a buffer prior to the first -+ xx_pip_v1.configure event must also be treated as errors. -+ -+ After creating an xx_pip_v1 object and setting it up, the client -+ must perform an initial commit without any buffer attached. -+ The compositor will reply with a xx_pip_v1.configure event. -+ The client must acknowledge it and is then allowed to attach a buffer -+ to map the surface. -+ -+ The compositor may deny showing the picture-in-picture surface, in -+ which case it will send the closed event before the first configure -+ event. -+ -+ See the documentation of xdg_surface for more details about what an -+ xdg_surface is and how it is used. -+ -+ -+ -+ -+ -+ -+ -+ -+ This interface defines an xdg_surface role which represents a floating -+ window with some miniature contents, for example a video. -+ -+ The picture-in-picture window will be placed above all other windows. -+ Compositor-specific policies may override or customize the behavior -+ and the placement of the xx_pip_v1. For example, the compositor may -+ choose to put the xx_pip_v1 in a screen corner, etc. -+ -+ Unmapping an xx_pip_v1 means that the surface cannot be shown -+ by the compositor until it is explicitly mapped again. -+ All active operations (e.g., move, resize) are canceled and all -+ attributes (e.g. title, state, stacking, ...) are discarded for -+ an xx_pip_v1 surface when it is unmapped. The xx_pip_v1 returns to -+ the state it had right after xx_pip_shell_v1.get_pip. The client -+ can re-map the pip by perfoming a commit without any buffer -+ attached, waiting for a configure event and handling it as usual (see -+ xdg_surface description). -+ -+ Attaching a null buffer to a picture-in-picture unmaps the surface. -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ This request destroys the role surface and unmaps the surface. -+ -+ -+ -+ -+ -+ Set an application identifier for the surface. -+ -+ The app ID identifies the general class of applications to which -+ the surface belongs. The compositor can use this to group multiple -+ surfaces together, or to determine how to launch a new application. -+ -+ For D-Bus activatable applications, the app ID is used as the D-Bus -+ service name. -+ -+ The compositor shell will try to group application surfaces together -+ by their app ID. As a best practice, it is suggested to select app -+ ID's that match the basename of the application's .desktop file. -+ For example, "org.freedesktop.FooViewer" where the .desktop file is -+ "org.freedesktop.FooViewer.desktop". -+ -+ Like other properties, a set_app_id request can be sent after the -+ xx_pip_v1 has been mapped to update the property. -+ -+ See the desktop-entry specification [0] for more details on -+ application identifiers and how they relate to well-known D-Bus -+ names and .desktop files. -+ -+ [0] http://standards.freedesktop.org/desktop-entry-spec/ -+ -+ -+ -+ -+ -+ -+ Set the origin surface for the picture-in-picture surface. -+ -+ The origin surface is an optional property that specifies a surface -+ from which the picture-in-picture surface has been launched. If set, -+ the compositor may use this hint to play an animation when the -+ picture-in-picture surface is mapped or unmapped. For example, smoothly -+ move the surface from the origin to a screen corner. -+ -+ If the specified origin surface is the same as the picture-in-picture -+ surface, the invalid_origin protocol error will be posted. -+ -+ The origin surface is double-buffered state, see wl_surface.commit. -+ -+ -+ -+ -+ -+ -+ Set the origin rect within the origin surface for the picture-in-picture -+ surface. -+ -+ The origin rect is an optional property that specifies the launch -+ rectangle within the origin surface. The compositor may use this hint -+ to play an animation when the picture-in-picture surface is mapped or -+ unmapped. For example, smoothly move the surface from the origin rect -+ to a screen corner. -+ -+ The origin rect is specified in the surface-local coordinate space. -+ -+ The compositor ignores the parts of the origin rect that fall outside -+ of the origin surface. -+ -+ The origin rect is double-buffered state, see wl_surface.commit. -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ Start an interactive, user-driven move of the surface. -+ -+ This request must be used in response to some sort of user action -+ like a button press, key press, or touch down event. The passed -+ serial is used to determine the type of interactive move (touch, -+ pointer, etc). -+ -+ The server may ignore move requests depending on the state of -+ the surface, or if the passed serial is no longer valid. -+ -+ If triggered, the surface will lose the focus of the device -+ (wl_pointer, wl_touch, etc) used for the move. It is up to the -+ compositor to visually indicate that the move is taking place, such as -+ updating a pointer cursor, during the move. There is no guarantee -+ that the device focus will return when the move is completed. -+ -+ -+ -+ -+ -+ -+ -+ These values are used to indicate which edge of a surface -+ is being dragged in a resize operation. -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ Start a user-driven, interactive resize of the surface. -+ -+ This request must be used in response to some sort of user action -+ like a button press, key press, or touch down event. The passed -+ serial is used to determine the type of interactive resize (touch, -+ pointer, etc). -+ -+ The server may ignore resize requests depending on the state of -+ the surface, or if the passed serial is no longer valid. -+ -+ If triggered, the surface also will lose the focus of the device -+ (wl_pointer, wl_touch, etc) used for the resize. It is up to the -+ compositor to visually indicate that the resize is taking place, -+ such as updating a pointer cursor, during the resize. There is no -+ guarantee that the device focus will return when the resize is -+ completed. -+ -+ The edges parameter specifies how the surface should be resized, -+ and is one of the values of the resize_edge enum. The compositor -+ may use this information to update the surface position for -+ example when dragging the top left corner. The compositor may also -+ use this information to adapt its behavior, e.g. choose an -+ appropriate cursor image. -+ -+ -+ -+ -+ -+ -+ -+ -+ The closed event is sent by the compositor when the surface will -+ no longer be shown. Further changes to the surface will be ignored. -+ The client should destroy the resource after receiving this event. -+ -+ -+ -+ -+ -+ The configure_bounds event may be sent prior to a xx_pip_v1.configure -+ event to communicate the bounds a surface size must be constrained to. -+ -+ The passed width and height are in surface coordinate space. -+ -+ If the surface width or the surface height is greater than the specified -+ surface size bounds, an invalid_size protocol error will be posted. -+ -+ The surface bounds subject to compositor policies. -+ -+ The bounds may change at any point, and in such a case, a new -+ xx_pip_v1.configure_bounds will be sent, followed by xx_pip_v1.configure and -+ xdg_surface.configure. -+ -+ -+ -+ -+ -+ -+ -+ This configure event asks the client to resize its pip surface. -+ The configured state should not be applied immediately. See -+ xdg_surface.configure for details. -+ -+ The width and height arguments specify a hint to the window -+ about how its surface should be resized in window geometry -+ coordinates. See set_window_geometry. -+ -+ If the width or height arguments are zero, it means the client -+ should decide its own window dimension. -+ -+ Clients must send an ack_configure in response to this event. See -+ xdg_surface.configure and xdg_surface.ack_configure for details. -+ -+ -+ -+ -+ -+ -diff --git a/src/wayland/xdgshell.cpp b/src/wayland/xdgshell.cpp -index 282678c0938..f1217e9d37a 100644 ---- a/src/wayland/xdgshell.cpp -+++ b/src/wayland/xdgshell.cpp -@@ -186,7 +186,7 @@ void XdgSurfaceInterfacePrivate::xdg_surface_destroy_resource(Resource *resource - - void XdgSurfaceInterfacePrivate::xdg_surface_destroy(Resource *resource) - { -- if (toplevel || popup) { -+ if (!toplevel.isNull() || !popup.isNull() || !pip.isNull()) { - qWarning() << "Tried to destroy xdg_surface before its role object"; - } - wl_resource_destroy(resource->handle); -diff --git a/src/wayland/xdgshell_p.h b/src/wayland/xdgshell_p.h -index 46244b23523..e48dfd8d78d 100644 ---- a/src/wayland/xdgshell_p.h -+++ b/src/wayland/xdgshell_p.h -@@ -16,6 +16,7 @@ - namespace KWin - { - class XdgToplevelDecorationV1Interface; -+class XXPipV1Interface; - - class XdgShellInterfacePrivate : public QtWaylandServer::xdg_wm_base - { -@@ -118,6 +119,7 @@ public: - XdgShellInterface *shell = nullptr; - QPointer toplevel; - QPointer popup; -+ QPointer pip; - QPointer surface; - QRect windowGeometry; - bool firstBufferAttached = false; -diff --git a/src/wayland/xxpip_v1.cpp b/src/wayland/xxpip_v1.cpp -new file mode 100644 -index 00000000000..e0b1f36134f ---- /dev/null -+++ b/src/wayland/xxpip_v1.cpp -@@ -0,0 +1,312 @@ -+/* -+ SPDX-FileCopyrightText: 2023 Vlad Zahorodnii -+ -+ SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL -+*/ -+ -+#include "wayland/xxpip_v1.h" -+#include "utils/resource.h" -+#include "wayland/display.h" -+#include "wayland/seat.h" -+#include "wayland/surface.h" -+#include "wayland/xdgshell_p.h" -+ -+#include "qwayland-server-xx-pip-v1.h" -+ -+namespace KWin -+{ -+ -+static const int s_version = 1; -+ -+class XXPipShellV1InterfacePrivate : public QtWaylandServer::xx_pip_shell_v1 -+{ -+public: -+ explicit XXPipShellV1InterfacePrivate(XXPipShellV1Interface *q, Display *display); -+ -+ XXPipShellV1Interface *q; -+ Display *display; -+ -+protected: -+ void xx_pip_shell_v1_destroy(Resource *resource) override; -+ void xx_pip_shell_v1_get_pip(Resource *resource, uint32_t id, struct ::wl_resource *xdg_surface) override; -+}; -+ -+XXPipShellV1InterfacePrivate::XXPipShellV1InterfacePrivate(XXPipShellV1Interface *q, Display *display) -+ : QtWaylandServer::xx_pip_shell_v1(*display, s_version) -+ , q(q) -+ , display(display) -+{ -+} -+ -+void XXPipShellV1InterfacePrivate::xx_pip_shell_v1_destroy(Resource *resource) -+{ -+ wl_resource_destroy(resource->handle); -+} -+ -+void XXPipShellV1InterfacePrivate::xx_pip_shell_v1_get_pip(Resource *resource, uint32_t id, struct ::wl_resource *xdg_surface) -+{ -+ XdgSurfaceInterface *xdgSurface = XdgSurfaceInterface::get(xdg_surface); -+ -+ if (const SurfaceRole *role = xdgSurface->surface()->role()) { -+ if (role != XXPipV1Interface::role()) { -+ wl_resource_post_error(resource->handle, error_already_constructed, "the surface already has a role assigned %s", role->name().constData()); -+ return; -+ } -+ } else { -+ xdgSurface->surface()->setRole(XXPipV1Interface::role()); -+ } -+ -+ wl_resource *pipResource = wl_resource_create(resource->client(), &xx_pip_v1_interface, resource->version(), id); -+ auto pip = new XXPipV1Interface(q, xdgSurface, pipResource); -+ -+ Q_EMIT q->pipCreated(pip); -+} -+ -+XXPipShellV1Interface::XXPipShellV1Interface(Display *display, QObject *parent) -+ : QObject(parent) -+ , d(std::make_unique(this, display)) -+{ -+} -+ -+XXPipShellV1Interface::~XXPipShellV1Interface() -+{ -+} -+ -+Display *XXPipShellV1Interface::display() const -+{ -+ return d->display; -+} -+ -+class XXPipV1Commit : public SurfaceAttachedState, public XdgSurfaceCommit -+{ -+public: -+ QPointer origin; -+ QRect originRect; -+}; -+ -+class XXPipV1InterfacePrivate : public SurfaceExtension, public QtWaylandServer::xx_pip_v1 -+{ -+public: -+ XXPipV1InterfacePrivate(XXPipV1Interface *q, XXPipShellV1Interface *shell, XdgSurfaceInterface *xdgSurface); -+ -+ void apply(XXPipV1Commit *comit); -+ void reset(); -+ -+ XXPipV1Interface *q; -+ XXPipShellV1Interface *shell; -+ XdgSurfaceInterface *xdgSurface; -+ QString applicationId; -+ QPointer origin; -+ QRect originRect; -+ -+protected: -+ void xx_pip_v1_destroy_resource(Resource *resource) override; -+ void xx_pip_v1_destroy(Resource *resource) override; -+ void xx_pip_v1_set_app_id(Resource *resource, const QString &app_id) override; -+ void xx_pip_v1_set_origin(Resource *resource, struct ::wl_resource *origin) override; -+ void xx_pip_v1_set_origin_rect(Resource *resource, int32_t x, int32_t y, uint32_t width, uint32_t height) override; -+ void xx_pip_v1_move(Resource *resource, struct ::wl_resource *seat, uint32_t serial) override; -+ void xx_pip_v1_resize(Resource *resource, struct ::wl_resource *seat, uint32_t serial, uint32_t edges) override; -+}; -+ -+XXPipV1InterfacePrivate::XXPipV1InterfacePrivate(XXPipV1Interface *q, XXPipShellV1Interface *shell, XdgSurfaceInterface *xdgSurface) -+ : SurfaceExtension(xdgSurface->surface()) -+ , q(q) -+ , shell(shell) -+ , xdgSurface(xdgSurface) -+{ -+} -+ -+void XXPipV1InterfacePrivate::apply(XXPipV1Commit *commit) -+{ -+ auto xdgSurfacePrivate = XdgSurfaceInterfacePrivate::get(xdgSurface); -+ if (xdgSurfacePrivate->firstBufferAttached && !xdgSurfacePrivate->surface->buffer()) { -+ reset(); -+ return; -+ } -+ -+ if (!commit->origin.isNull()) { -+ origin = commit->origin; -+ } -+ if (!commit->originRect.isNull()) { -+ originRect = commit->originRect; -+ } -+ -+ xdgSurfacePrivate->apply(commit); -+ -+ if (!xdgSurfacePrivate->isConfigured) { -+ Q_EMIT q->initializeRequested(); -+ } -+} -+ -+void XXPipV1InterfacePrivate::reset() -+{ -+ auto xdgSurfacePrivate = XdgSurfaceInterfacePrivate::get(xdgSurface); -+ xdgSurfacePrivate->reset(); -+ -+ Q_EMIT q->resetOccurred(); -+} -+ -+void XXPipV1InterfacePrivate::xx_pip_v1_destroy_resource(Resource *resource) -+{ -+ Q_EMIT q->aboutToBeDestroyed(); -+ delete q; -+} -+ -+void XXPipV1InterfacePrivate::xx_pip_v1_destroy(Resource *resource) -+{ -+ wl_resource_destroy(resource->handle); -+} -+ -+void XXPipV1InterfacePrivate::xx_pip_v1_set_app_id(Resource *resource, const QString &app_id) -+{ -+ if (applicationId != app_id) { -+ applicationId = app_id; -+ Q_EMIT q->applicationIdChanged(); -+ } -+} -+ -+void XXPipV1InterfacePrivate::xx_pip_v1_set_origin(Resource *resource, struct ::wl_resource *origin_resource) -+{ -+ SurfaceInterface *origin = SurfaceInterface::get(origin_resource); -+ if (origin == xdgSurface->surface()) { -+ wl_resource_post_error(resource->handle, error_invalid_origin, "pip surface cannot be its own origin"); -+ return; -+ } -+ -+ pending->origin = origin; -+} -+ -+void XXPipV1InterfacePrivate::xx_pip_v1_set_origin_rect(Resource *resource, int32_t x, int32_t y, uint32_t width, uint32_t height) -+{ -+ pending->originRect = QRect(x, y, width, height); -+} -+ -+void XXPipV1InterfacePrivate::xx_pip_v1_move(Resource *resource, struct ::wl_resource *seat_resource, uint32_t serial) -+{ -+ auto xdgSurfacePrivate = XdgSurfaceInterfacePrivate::get(xdgSurface); -+ if (!xdgSurfacePrivate->isConfigured) { -+ wl_resource_post_error(resource->handle, QtWaylandServer::xdg_surface::error_not_constructed, "surface has not been configured yet"); -+ return; -+ } -+ -+ Q_EMIT q->moveRequested(SeatInterface::get(seat_resource), serial); -+} -+ -+void XXPipV1InterfacePrivate::xx_pip_v1_resize(Resource *resource, struct ::wl_resource *seat_resource, uint32_t serial, uint32_t edges) -+{ -+ auto xdgSurfacePrivate = XdgSurfaceInterfacePrivate::get(xdgSurface); -+ if (!xdgSurfacePrivate->isConfigured) { -+ wl_resource_post_error(resource->handle, QtWaylandServer::xdg_surface::error_not_constructed, "surface has not been configured yet"); -+ return; -+ } -+ -+ Gravity gravity; -+ switch (edges) { -+ case resize_edge_none: -+ gravity = Gravity::None; -+ break; -+ case resize_edge_top: -+ gravity = Gravity::Top; -+ break; -+ case resize_edge_bottom: -+ gravity = Gravity::Bottom; -+ break; -+ case resize_edge_left: -+ gravity = Gravity::Left; -+ break; -+ case resize_edge_top_left: -+ gravity = Gravity::TopLeft; -+ break; -+ case resize_edge_bottom_left: -+ gravity = Gravity::BottomLeft; -+ break; -+ case resize_edge_right: -+ gravity = Gravity::Right; -+ break; -+ case resize_edge_top_right: -+ gravity = Gravity::TopRight; -+ break; -+ case resize_edge_bottom_right: -+ gravity = Gravity::BottomRight; -+ break; -+ default: -+ wl_resource_post_error(resource->handle, error_invalid_resize_edge, "invalid resize edge"); -+ return; -+ } -+ -+ Q_EMIT q->resizeRequested(SeatInterface::get(seat_resource), gravity, serial); -+} -+ -+XXPipV1Interface::XXPipV1Interface(XXPipShellV1Interface *shell, XdgSurfaceInterface *xdgSurface, wl_resource *resource) -+ : d(std::make_unique(this, shell, xdgSurface)) -+{ -+ XdgSurfaceInterfacePrivate *surfacePrivate = XdgSurfaceInterfacePrivate::get(xdgSurface); -+ surfacePrivate->pip = this; -+ surfacePrivate->pending = d->pending; -+ -+ d->init(resource); -+} -+ -+XXPipV1Interface::~XXPipV1Interface() -+{ -+} -+ -+SurfaceRole *XXPipV1Interface::role() -+{ -+ static SurfaceRole role(QByteArrayLiteral("xx_pip_v1")); -+ return &role; -+} -+ -+bool XXPipV1Interface::isConfigured() const -+{ -+ return d->xdgSurface->isConfigured(); -+} -+ -+XdgSurfaceInterface *XXPipV1Interface::xdgSurface() const -+{ -+ return d->xdgSurface; -+} -+ -+SurfaceInterface *XXPipV1Interface::surface() const -+{ -+ return d->xdgSurface->surface(); -+} -+ -+QString XXPipV1Interface::applicationId() const -+{ -+ return d->applicationId; -+} -+ -+quint32 XXPipV1Interface::sendConfigureSize(const QSizeF &size) -+{ -+ const quint32 serial = d->shell->display()->nextSerial(); -+ -+ d->send_configure_size(size.width(), size.height()); -+ -+ auto xdgSurfacePrivate = XdgSurfaceInterfacePrivate::get(xdgSurface()); -+ xdgSurfacePrivate->send_configure(serial); -+ xdgSurfacePrivate->isConfigured = true; -+ -+ return serial; -+} -+ -+void XXPipV1Interface::sendClosed() -+{ -+ d->send_closed(); -+} -+ -+void XXPipV1Interface::sendConfigureBounds(const QSizeF &size) -+{ -+ d->send_configure_bounds(size.width(), size.height()); -+} -+ -+XXPipV1Interface *XXPipV1Interface::get(::wl_resource *resource) -+{ -+ if (auto pipPrivate = resource_cast(resource)) { -+ return pipPrivate->q; -+ } -+ return nullptr; -+} -+ -+} // namespace KWin -diff --git a/src/wayland/xxpip_v1.h b/src/wayland/xxpip_v1.h -new file mode 100644 -index 00000000000..c2737f2ec26 ---- /dev/null -+++ b/src/wayland/xxpip_v1.h -@@ -0,0 +1,159 @@ -+/* -+ SPDX-FileCopyrightText: 2023 Vlad Zahorodnii -+ -+ SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL -+*/ -+ -+#pragma once -+ -+#include "kwin_export.h" -+ -+#include -+ -+#include -+ -+struct wl_resource; -+ -+namespace KWin -+{ -+ -+class Display; -+class SeatInterface; -+class SurfaceInterface; -+class SurfaceRole; -+class XXPipV1Interface; -+class XXPipV1InterfacePrivate; -+class XdgSurfaceInterface; -+class XXPipShellV1InterfacePrivate; -+ -+enum class Gravity; -+ -+/** -+ * The XXPipShellV1Interface extension provides clients a way to create picture-in-picture -+ * surfaces. -+ */ -+class KWIN_EXPORT XXPipShellV1Interface : public QObject -+{ -+ Q_OBJECT -+ -+public: -+ explicit XXPipShellV1Interface(Display *display, QObject *parent = nullptr); -+ ~XXPipShellV1Interface() override; -+ -+ Display *display() const; -+ -+Q_SIGNALS: -+ void pipCreated(XXPipV1Interface *pip); -+ -+private: -+ std::unique_ptr d; -+}; -+ -+/** -+ * The XXPipV1Interface class represents a picture-in-picture surface. -+ * -+ * XXPipV1Interface corresponds to the Wayland interface \c xx_pip_v1. -+ */ -+class KWIN_EXPORT XXPipV1Interface : public QObject -+{ -+ Q_OBJECT -+ -+public: -+ XXPipV1Interface(XXPipShellV1Interface *shell, XdgSurfaceInterface *xdgSurface, wl_resource *resource); -+ ~XXPipV1Interface() override; -+ -+ static SurfaceRole *role(); -+ -+ /** -+ * Returns \c true if the popup has been configured; otherwise returns \c false. -+ */ -+ bool isConfigured() const; -+ -+ /** -+ * Returns the XdgSurfaceInterface associated with the XXPipV1Interface. -+ */ -+ XdgSurfaceInterface *xdgSurface() const; -+ -+ /** -+ * Returns the SurfaceInterface associated with the XXPipV1Interface. -+ */ -+ SurfaceInterface *surface() const; -+ -+ /** -+ * Returns the desktop file name of the pip surface. -+ */ -+ QString applicationId() const; -+ -+ /** -+ * Returns the surface from which the picture-in-picture surface has been launched, or \c null. -+ */ -+ SurfaceInterface *origin() const; -+ -+ /** -+ * Specifies the bounds within the origin surface from which the picture-in-picture surface has -+ * been launched. -+ */ -+ QRect originRect() const; -+ -+ /** -+ * Sends a configure event to the client. \a size specifies the new window geometry size. A size -+ * of zero means the client should decide its own window dimensions. -+ */ -+ quint32 sendConfigureSize(const QSizeF &size); -+ -+ /** -+ * Sends a close event to the client. The client may choose to ignore this request. -+ */ -+ void sendClosed(); -+ -+ /** -+ * Sends an event to the client specifying the maximum bounds for the surface size. Must be -+ * called before sendConfigure(). -+ */ -+ void sendConfigureBounds(const QSizeF &size); -+ -+ /** -+ * Returns the XXPipV1Interface for the specified wayland resource object \a resource. -+ */ -+ static XXPipV1Interface *get(::wl_resource *resource); -+ -+Q_SIGNALS: -+ /** -+ * This signal is emitted when the xx-pip-v1 is about to be destroyed. -+ */ -+ void aboutToBeDestroyed(); -+ -+ /** -+ * This signal is emitted when the xx-pip-v1 has commited the initial state and wants to -+ * be configured. After initializing the pip surface, you must send a configure event. -+ */ -+ void initializeRequested(); -+ -+ /** -+ * This signal is emitted when the pip surface has been unmapped and its state has been reset. -+ */ -+ void resetOccurred(); -+ -+ /** -+ * This signal is emitted when the pip wants to be interactively moved. The \a seat and -+ * the \a serial indicate the user action in response to which this request has been issued. -+ */ -+ void moveRequested(SeatInterface *seat, quint32 serial); -+ -+ /** -+ * This signal is emitted when the pip wants to be interactively resized with -+ * the specified \a gravity. The \a seat and the \a serial indicate the user action -+ * in response to which this request has been issued. -+ */ -+ void resizeRequested(SeatInterface *seat, Gravity anchor, quint32 serial); -+ -+ /** -+ * This signal is emitted when the application id changes. -+ */ -+ void applicationIdChanged(); -+ -+private: -+ std::unique_ptr d; -+}; -+ -+} // namespace KWin -diff --git a/src/wayland_server.cpp b/src/wayland_server.cpp -index 24f7fa71ebe..0d54680313a 100644 ---- a/src/wayland_server.cpp -+++ b/src/wayland_server.cpp -@@ -91,6 +91,7 @@ - #include "xdgactivationv1.h" - #include "xdgshellintegration.h" - #include "xdgshellwindow.h" -+#include "xxpipv1integration.h" - #if KWIN_BUILD_X11 - #include "wayland/xwaylandkeyboardgrab_v1.h" - #include "wayland/xwaylandshell_v1.h" -@@ -581,6 +582,12 @@ void WaylandServer::initWorkspace() - connect(layerShellV1Integration, &LayerShellV1Integration::windowCreated, - this, &WaylandServer::registerWindow); - -+ if (qEnvironmentVariableIntValue("KWIN_WAYLAND_SUPPORT_XX_PIP_V1") == 1) { -+ auto pipV1Integration = new XXPipV1Integration(this); -+ connect(pipV1Integration, &XXPipV1Integration::windowCreated, -+ this, &WaylandServer::registerWindow); -+ } -+ - new KeyStateInterface(m_display, m_display); - - VirtualDesktopManager::self()->setVirtualDesktopManagement(m_virtualDesktopManagement); -diff --git a/src/window.cpp b/src/window.cpp -index 591950ab74a..f5a4be143b8 100644 ---- a/src/window.cpp -+++ b/src/window.cpp -@@ -563,6 +563,9 @@ Layer Window::belongsToLayer() const - if (isUnmanaged() || isInternal()) { - return OverlayLayer; - } -+ if (isPictureInPicture()) { -+ return OverlayLayer; -+ } - if (isLockScreen() && !waylandServer()) { - return OverlayLayer; - } -diff --git a/src/window.h b/src/window.h -index 57be2c6891e..9ae5758de85 100644 ---- a/src/window.h -+++ b/src/window.h -@@ -781,6 +781,7 @@ public: - virtual bool isClient() const; - bool isDeleted() const; - virtual bool isUnmanaged() const; -+ virtual bool isPictureInPicture() const; - - bool isLockScreenOverlay() const; - void setLockScreenOverlay(bool allowed); -@@ -2086,6 +2087,11 @@ inline bool Window::isInternal() const - return false; - } - -+inline bool Window::isPictureInPicture() const -+{ -+ return false; -+} -+ - inline WindowItem *Window::windowItem() const - { - return m_windowItem.get(); -diff --git a/src/xxpipv1integration.cpp b/src/xxpipv1integration.cpp -new file mode 100644 -index 00000000000..b72f2cba140 ---- /dev/null -+++ b/src/xxpipv1integration.cpp -@@ -0,0 +1,43 @@ -+/* -+ SPDX-FileCopyrightText: 2023 Vlad Zahorodnii -+ -+ SPDX-License-Identifier: GPL-2.0-or-later -+*/ -+ -+#include "xxpipv1integration.h" -+#include "wayland/xxpip_v1.h" -+#include "wayland_server.h" -+#include "workspace.h" -+#include "xxpipv1window.h" -+ -+namespace KWin -+{ -+ -+XXPipV1Integration::XXPipV1Integration(QObject *parent) -+ : WaylandShellIntegration(parent) -+{ -+ XXPipShellV1Interface *shell = new XXPipShellV1Interface(waylandServer()->display(), this); -+ connect(shell, &XXPipShellV1Interface::pipCreated, -+ this, &XXPipV1Integration::registerPipV1Surface); -+} -+ -+void XXPipV1Integration::registerPipV1Surface(XXPipV1Interface *pip) -+{ -+ createPipV1Window(pip); -+ connect(pip, &XXPipV1Interface::resetOccurred, this, [this, pip] { -+ createPipV1Window(pip); -+ }); -+} -+ -+void XXPipV1Integration::createPipV1Window(XXPipV1Interface *pip) -+{ -+ if (!workspace()) { -+ qCWarning(KWIN_CORE, "An xx-pip-v1 surface has been created while the compositor " -+ "is still not fully initialized. That is a compositor bug!"); -+ return; -+ } -+ -+ Q_EMIT windowCreated(new XXPipV1Window(pip)); -+} -+ -+} // namespace KWin -diff --git a/src/xxpipv1integration.h b/src/xxpipv1integration.h -new file mode 100644 -index 00000000000..8d2be310fe5 ---- /dev/null -+++ b/src/xxpipv1integration.h -@@ -0,0 +1,28 @@ -+/* -+ SPDX-FileCopyrightText: 2023 Vlad Zahorodnii -+ -+ SPDX-License-Identifier: GPL-2.0-or-later -+*/ -+ -+#pragma once -+ -+#include "waylandshellintegration.h" -+ -+namespace KWin -+{ -+ -+class XXPipV1Interface; -+ -+class XXPipV1Integration : public WaylandShellIntegration -+{ -+ Q_OBJECT -+ -+public: -+ explicit XXPipV1Integration(QObject *parent = nullptr); -+ -+private: -+ void registerPipV1Surface(XXPipV1Interface *pip); -+ void createPipV1Window(XXPipV1Interface *pip); -+}; -+ -+} // namespace KWin -diff --git a/src/xxpipv1window.cpp b/src/xxpipv1window.cpp -new file mode 100644 -index 00000000000..0d6b6bafc8b ---- /dev/null -+++ b/src/xxpipv1window.cpp -@@ -0,0 +1,179 @@ -+/* -+ SPDX-FileCopyrightText: 2023 Vlad Zahorodnii -+ -+ SPDX-License-Identifier: GPL-2.0-or-later -+*/ -+ -+#include "xxpipv1window.h" -+#include "input.h" -+#include "wayland/seat.h" -+#include "wayland/surface.h" -+#include "wayland/tablet_v2.h" -+#include "wayland_server.h" -+#include "workspace.h" -+ -+namespace KWin -+{ -+ -+XXPipV1Window::XXPipV1Window(XXPipV1Interface *shellSurface) -+ : XdgSurfaceWindow(shellSurface->xdgSurface()) -+ , m_shellSurface(shellSurface) -+{ -+ setOutput(workspace()->activeOutput()); -+ setMoveResizeOutput(workspace()->activeOutput()); -+ setOnAllDesktops(true); -+ setOnAllActivities(true); -+ -+ connect(shellSurface, &XXPipV1Interface::initializeRequested, -+ this, &XXPipV1Window::initialize); -+ connect(shellSurface, &XXPipV1Interface::aboutToBeDestroyed, -+ this, &XXPipV1Window::destroyWindow); -+ connect(shellSurface, &XXPipV1Interface::moveRequested, -+ this, &XXPipV1Window::handleMoveRequested); -+ connect(shellSurface, &XXPipV1Interface::resizeRequested, -+ this, &XXPipV1Window::handleResizeRequested); -+ connect(shellSurface, &XXPipV1Interface::applicationIdChanged, -+ this, &XXPipV1Window::handleApplicationIdChanged); -+} -+ -+void XXPipV1Window::initialize() -+{ -+ scheduleConfigure(); -+} -+ -+bool XXPipV1Window::isPictureInPicture() const -+{ -+ return true; -+} -+ -+bool XXPipV1Window::isResizable() const -+{ -+ return true; -+} -+ -+bool XXPipV1Window::isMovable() const -+{ -+ return true; -+} -+ -+bool XXPipV1Window::isMovableAcrossScreens() const -+{ -+ return true; -+} -+ -+bool XXPipV1Window::isCloseable() const -+{ -+ return true; -+} -+ -+void XXPipV1Window::closeWindow() -+{ -+ m_shellSurface->sendClosed(); -+} -+ -+bool XXPipV1Window::wantsInput() const -+{ -+ return false; -+} -+ -+bool XXPipV1Window::takeFocus() -+{ -+ return false; -+} -+ -+bool XXPipV1Window::acceptsFocus() const -+{ -+ return false; -+} -+ -+XdgSurfaceConfigure *XXPipV1Window::sendRoleConfigure() const -+{ -+ surface()->setPreferredBufferScale(nextTargetScale()); -+ surface()->setPreferredBufferTransform(preferredBufferTransform()); -+ surface()->setPreferredColorDescription(preferredColorDescription()); -+ -+ const QRectF geometry = moveResizeGeometry(); -+ if (geometry.isEmpty()) { -+ const QRectF workArea = workspace()->clientArea(PlacementArea, this, moveResizeOutput()); -+ m_shellSurface->sendConfigureBounds(workArea.size() * 0.25); -+ } -+ -+ XdgSurfaceConfigure *configureEvent = new XdgSurfaceConfigure(); -+ configureEvent->bounds = moveResizeGeometry(); -+ configureEvent->serial = m_shellSurface->sendConfigureSize(geometry.size()); -+ -+ return configureEvent; -+} -+ -+void XXPipV1Window::handleRoleDestroyed() -+{ -+ m_shellSurface->disconnect(this); -+ -+ XdgSurfaceWindow::handleRoleDestroyed(); -+} -+ -+void XXPipV1Window::handleApplicationIdChanged() -+{ -+ setResourceClass(resourceName(), m_shellSurface->applicationId()); -+ setDesktopFileName(m_shellSurface->applicationId()); -+} -+ -+void XXPipV1Window::handleMoveRequested(SeatInterface *seat, quint32 serial) -+{ -+ if (const auto anchor = input()->implicitGrabPositionBySerial(seat, serial)) { -+ performMousePressCommand(Options::MouseMove, *anchor); -+ } -+} -+ -+void XXPipV1Window::handleResizeRequested(SeatInterface *seat, Gravity gravity, quint32 serial) -+{ -+ const auto anchor = input()->implicitGrabPositionBySerial(seat, serial); -+ if (!anchor) { -+ return; -+ } -+ if (isInteractiveMoveResize()) { -+ finishInteractiveMoveResize(false); -+ } -+ setInteractiveMoveResizePointerButtonDown(true); -+ setInteractiveMoveResizeAnchor(*anchor); -+ setInteractiveMoveResizeModifiers(Qt::KeyboardModifiers()); -+ setInteractiveMoveOffset(QPointF((anchor->x() - x()) / width(), (anchor->y() - y()) / height())); -+ setUnrestrictedInteractiveMoveResize(false); -+ setInteractiveMoveResizeGravity(gravity); -+ if (!startInteractiveMoveResize()) { -+ setInteractiveMoveResizePointerButtonDown(false); -+ } -+ updateCursor(); -+} -+ -+void XXPipV1Window::doSetNextTargetScale() -+{ -+ if (isDeleted()) { -+ return; -+ } -+ if (m_shellSurface->isConfigured()) { -+ scheduleConfigure(); -+ } -+} -+ -+void XXPipV1Window::doSetPreferredBufferTransform() -+{ -+ if (isDeleted()) { -+ return; -+ } -+ if (m_shellSurface->isConfigured()) { -+ scheduleConfigure(); -+ } -+} -+ -+void XXPipV1Window::doSetPreferredColorDescription() -+{ -+ if (isDeleted()) { -+ return; -+ } -+ if (m_shellSurface->isConfigured()) { -+ scheduleConfigure(); -+ } -+} -+ -+} // namespace KWin -diff --git a/src/xxpipv1window.h b/src/xxpipv1window.h -new file mode 100644 -index 00000000000..15873e3b25f ---- /dev/null -+++ b/src/xxpipv1window.h -@@ -0,0 +1,48 @@ -+/* -+ SPDX-FileCopyrightText: 2023 Vlad Zahorodnii -+ -+ SPDX-License-Identifier: GPL-2.0-or-later -+*/ -+ -+#pragma once -+ -+#include "wayland/xxpip_v1.h" -+#include "xdgshellwindow.h" -+ -+namespace KWin -+{ -+ -+class XXPipV1Window final : public XdgSurfaceWindow -+{ -+ Q_OBJECT -+ -+public: -+ explicit XXPipV1Window(XXPipV1Interface *shellSurface); -+ -+ bool isPictureInPicture() const override; -+ bool isResizable() const override; -+ bool isMovable() const override; -+ bool isMovableAcrossScreens() const override; -+ bool isCloseable() const override; -+ void closeWindow() override; -+ bool wantsInput() const override; -+ bool takeFocus() override; -+ -+protected: -+ bool acceptsFocus() const override; -+ XdgSurfaceConfigure *sendRoleConfigure() const override; -+ void handleRoleDestroyed() override; -+ void doSetNextTargetScale() override; -+ void doSetPreferredBufferTransform() override; -+ void doSetPreferredColorDescription() override; -+ -+private: -+ void initialize(); -+ void handleApplicationIdChanged(); -+ void handleMoveRequested(SeatInterface *seat, quint32 serial); -+ void handleResizeRequested(SeatInterface *seat, Gravity gravity, quint32 serial); -+ -+ XXPipV1Interface *m_shellSurface; -+}; -+ -+} // namespace KWin -diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt -index 134f5416975..824cd67083c 100644 ---- a/tests/CMakeLists.txt -+++ b/tests/CMakeLists.txt -@@ -1,3 +1,7 @@ -+if (Qt6_VERSION VERSION_GREATER_EQUAL "6.9.0") -+ add_subdirectory(pip) -+endif() -+ - if(KWIN_BUILD_X11) - set(normalhintsbasesizetest_SRCS normalhintsbasesizetest.cpp) - add_executable(normalhintsbasesizetest ${normalhintsbasesizetest_SRCS}) -diff --git a/tests/pip/CMakeLists.txt b/tests/pip/CMakeLists.txt -new file mode 100644 -index 00000000000..9135a5ab072 ---- /dev/null -+++ b/tests/pip/CMakeLists.txt -@@ -0,0 +1,22 @@ -+add_executable(piptest) -+ -+target_sources(piptest PRIVATE -+ main.cpp -+ pipshellsurface.cpp -+ pip.cpp -+ window.cpp -+) -+ -+qt6_generate_wayland_protocol_client_sources(piptest -+ PRIVATE_CODE -+ FILES -+ ${PROJECT_SOURCE_DIR}/src/wayland/protocols/xx-pip-v1.xml -+ ${WaylandProtocols_DATADIR}/stable/xdg-shell/xdg-shell.xml -+ ${Wayland_DATADIR}/wayland.xml -+) -+ -+target_link_libraries(piptest PRIVATE -+ Qt::Gui -+ Qt::WaylandClientPrivate -+ Qt::Widgets -+) -diff --git a/tests/pip/main.cpp b/tests/pip/main.cpp -new file mode 100644 -index 00000000000..6d7c4f5b3b4 ---- /dev/null -+++ b/tests/pip/main.cpp -@@ -0,0 +1,19 @@ -+/* -+ SPDX-FileCopyrightText: 2025 Vlad Zahorodnii -+ -+ SPDX-License-Identifier: GPL-2.0-or-later -+*/ -+ -+#include -+ -+#include "window.h" -+ -+int main(int argc, char **argv) -+{ -+ QApplication app(argc, argv); -+ -+ Window w; -+ w.show(); -+ -+ return app.exec(); -+} -diff --git a/tests/pip/pip.cpp b/tests/pip/pip.cpp -new file mode 100644 -index 00000000000..ed14f5f98d4 ---- /dev/null -+++ b/tests/pip/pip.cpp -@@ -0,0 +1,227 @@ -+/* -+ SPDX-FileCopyrightText: 2025 Vlad Zahorodnii -+ -+ SPDX-License-Identifier: GPL-2.0-or-later -+*/ -+ -+#include "pip.h" -+#include "pipshellsurface.h" -+ -+#include -+#include -+ -+PipPin::PipPin(QWidget *parent) -+ : QWidget(parent) -+{ -+ resize(100, 50); -+} -+ -+bool PipPin::isPinned() const -+{ -+ return m_pinned; -+} -+ -+void PipPin::setPinned(bool pinned) -+{ -+ if (m_pinned != pinned) { -+ m_pinned = pinned; -+ update(); -+ } -+} -+ -+void PipPin::paintEvent(QPaintEvent *event) -+{ -+ QPainter painter(this); -+ painter.setClipRegion(event->region()); -+ -+ if (m_hovered) { -+ painter.setOpacity(1.0); -+ } else { -+ painter.setOpacity(0.5); -+ } -+ -+ painter.fillRect(rect(), Qt::black); -+ painter.setPen(Qt::white); -+ painter.drawText(rect(), Qt::AlignCenter, m_pinned ? QStringLiteral("Unpin") : QStringLiteral("Pin")); -+} -+ -+void PipPin::mousePressEvent(QMouseEvent *event) -+{ -+ if (event->button() == Qt::LeftButton) { -+ event->accept(); -+ Q_EMIT clicked(); -+ } -+} -+ -+void PipPin::enterEvent(QEnterEvent *event) -+{ -+ m_hovered = true; -+ update(); -+} -+ -+void PipPin::leaveEvent(QEvent *event) -+{ -+ m_hovered = false; -+ update(); -+} -+ -+Media::Media(QWidget *parent) -+ : QWidget(parent) -+{ -+ m_pip = std::make_unique(); -+ connect(m_pip.get(), &Pip::pinned, this, [this]() { -+ m_pin->setPinned(true); -+ }); -+ connect(m_pip.get(), &Pip::unpinned, this, [this]() { -+ m_pin->setPinned(false); -+ }); -+ -+ m_pin = new PipPin(this); -+ connect(m_pin, &PipPin::clicked, this, [this]() { -+ if (m_pin->isPinned()) { -+ m_pip->show(); -+ } else { -+ m_pip->hide(); -+ } -+ }); -+} -+ -+void Media::paintEvent(QPaintEvent *event) -+{ -+ QPainter painter(this); -+ painter.setClipRegion(event->region()); -+ painter.fillRect(rect(), QColor(0, 0, 0, 128)); -+} -+ -+void Media::resizeEvent(QResizeEvent *event) -+{ -+ m_pin->move(width() - m_pin->width() - 50, height() - m_pin->height() - 50); -+ m_pip->resize(width(), height()); -+} -+ -+PipResizeHandle::PipResizeHandle(Qt::Edges edges, QWidget *parent) -+ : QWidget(parent) -+ , m_edges(edges) -+{ -+ switch (edges) { -+ case Qt::LeftEdge: -+ case Qt::RightEdge: -+ setCursor(Qt::SizeHorCursor); -+ break; -+ case Qt::TopEdge: -+ case Qt::BottomEdge: -+ setCursor(Qt::SizeVerCursor); -+ break; -+ case Qt::TopEdge | Qt::LeftEdge: -+ case Qt::BottomEdge | Qt::RightEdge: -+ setCursor(Qt::SizeFDiagCursor); -+ break; -+ case Qt::TopEdge | Qt::RightEdge: -+ case Qt::BottomEdge | Qt::LeftEdge: -+ setCursor(Qt::SizeBDiagCursor); -+ break; -+ default: -+ Q_UNREACHABLE(); -+ } -+} -+ -+void PipResizeHandle::enterEvent(QEnterEvent *event) -+{ -+ m_hovered = true; -+ update(); -+} -+ -+void PipResizeHandle::leaveEvent(QEvent *event) -+{ -+ m_hovered = false; -+ update(); -+} -+ -+void PipResizeHandle::mousePressEvent(QMouseEvent *event) -+{ -+ if (event->button() == Qt::LeftButton) { -+ event->accept(); -+ window()->windowHandle()->startSystemResize(m_edges); -+ } -+} -+ -+void PipResizeHandle::paintEvent(QPaintEvent *event) -+{ -+ QPainter painter(this); -+ painter.setClipRegion(event->region()); -+ painter.fillRect(rect(), QColor(222, 137, 190, m_hovered ? 128 : 0)); -+} -+ -+Pip::Pip(QWidget *parent) -+ : QWidget(parent) -+{ -+ m_closeButton = new QPushButton(this); -+ m_closeButton->setIcon(QIcon::fromTheme(QStringLiteral("dialog-close"))); -+ m_closeButton->setText(QStringLiteral("Close")); -+ connect(m_closeButton, &QPushButton::clicked, this, &Pip::hide); -+ -+ m_topLeftResizeHandle = new PipResizeHandle(Qt::TopEdge | Qt::LeftEdge, this); -+ m_topResizeHandle = new PipResizeHandle(Qt::TopEdge, this); -+ m_topRightResizeHandle = new PipResizeHandle(Qt::TopEdge | Qt::RightEdge, this); -+ m_rightResizeHandle = new PipResizeHandle(Qt::RightEdge, this); -+ m_bottomRightResizeHandle = new PipResizeHandle(Qt::BottomEdge | Qt::BottomEdge, this); -+ m_bottomResizeHandle = new PipResizeHandle(Qt::BottomEdge, this); -+ m_bottomLeftResizeHandle = new PipResizeHandle(Qt::BottomEdge | Qt::LeftEdge, this); -+ m_leftResizeHandle = new PipResizeHandle(Qt::LeftEdge, this); -+ -+ winId(); -+ PipShellIntegration::assignPipRole(windowHandle()); -+} -+ -+void Pip::layout() -+{ -+ const int gridUnit = 5; -+ const int resizeZone = 2 * gridUnit; -+ -+ m_topLeftResizeHandle->setGeometry(0, 0, resizeZone, resizeZone); -+ m_topResizeHandle->setGeometry(resizeZone, 0, width() - 2 * resizeZone, resizeZone); -+ m_topRightResizeHandle->setGeometry(width() - resizeZone, 0, resizeZone, resizeZone); -+ m_rightResizeHandle->setGeometry(width() - resizeZone, resizeZone, resizeZone, height() - 2 * resizeZone); -+ m_bottomRightResizeHandle->setGeometry(width() - resizeZone, height() - resizeZone, resizeZone, resizeZone); -+ m_bottomResizeHandle->setGeometry(resizeZone, height() - resizeZone, width() - 2 * resizeZone, resizeZone); -+ m_bottomLeftResizeHandle->setGeometry(0, height() - resizeZone, resizeZone, resizeZone); -+ m_leftResizeHandle->setGeometry(0, resizeZone, resizeZone, height() - 2 * resizeZone); -+ -+ m_closeButton->move(width() - resizeZone - gridUnit - m_closeButton->width(), resizeZone + gridUnit); -+} -+ -+void Pip::paintEvent(QPaintEvent *event) -+{ -+ QPainter painter(this); -+ painter.setClipRegion(event->region()); -+ painter.fillRect(rect(), QColor(64, 67, 78)); -+} -+ -+void Pip::resizeEvent(QResizeEvent *event) -+{ -+ layout(); -+} -+ -+void Pip::mousePressEvent(QMouseEvent *event) -+{ -+ switch (event->button()) { -+ case Qt::LeftButton: -+ event->accept(); -+ windowHandle()->startSystemMove(); -+ break; -+ default: -+ break; -+ } -+} -+ -+void Pip::showEvent(QShowEvent *event) -+{ -+ Q_EMIT unpinned(); -+} -+ -+void Pip::hideEvent(QHideEvent *event) -+{ -+ Q_EMIT pinned(); -+} -+ -+#include "moc_pip.cpp" -diff --git a/tests/pip/pip.h b/tests/pip/pip.h -new file mode 100644 -index 00000000000..2f6e4a87d86 ---- /dev/null -+++ b/tests/pip/pip.h -@@ -0,0 +1,102 @@ -+/* -+ SPDX-FileCopyrightText: 2025 Vlad Zahorodnii -+ -+ SPDX-License-Identifier: GPL-2.0-or-later -+*/ -+ -+#pragma once -+ -+#include -+#include -+ -+class PipResizeHandle : public QWidget -+{ -+ Q_OBJECT -+ -+public: -+ explicit PipResizeHandle(Qt::Edges edges, QWidget *parent = nullptr); -+ -+protected: -+ void enterEvent(QEnterEvent *event) override; -+ void leaveEvent(QEvent *event) override; -+ void mousePressEvent(QMouseEvent *event) override; -+ void paintEvent(QPaintEvent *event) override; -+ -+private: -+ Qt::Edges m_edges; -+ bool m_hovered = false; -+}; -+ -+class Pip : public QWidget -+{ -+ Q_OBJECT -+ -+public: -+ explicit Pip(QWidget *parent = nullptr); -+ -+Q_SIGNALS: -+ void pinned(); -+ void unpinned(); -+ -+protected: -+ void paintEvent(QPaintEvent *event) override; -+ void resizeEvent(QResizeEvent *event) override; -+ void mousePressEvent(QMouseEvent *event) override; -+ void showEvent(QShowEvent *event) override; -+ void hideEvent(QHideEvent *event) override; -+ -+private: -+ void layout(); -+ -+ QPushButton *m_closeButton = nullptr; -+ PipResizeHandle *m_topLeftResizeHandle = nullptr; -+ PipResizeHandle *m_topResizeHandle = nullptr; -+ PipResizeHandle *m_topRightResizeHandle = nullptr; -+ PipResizeHandle *m_rightResizeHandle = nullptr; -+ PipResizeHandle *m_bottomRightResizeHandle = nullptr; -+ PipResizeHandle *m_bottomResizeHandle = nullptr; -+ PipResizeHandle *m_bottomLeftResizeHandle = nullptr; -+ PipResizeHandle *m_leftResizeHandle = nullptr; -+}; -+ -+class PipPin : public QWidget -+{ -+ Q_OBJECT -+ -+public: -+ explicit PipPin(QWidget *parent = nullptr); -+ -+ bool isPinned() const; -+ void setPinned(bool pinned); -+ -+Q_SIGNALS: -+ void clicked(); -+ -+protected: -+ void paintEvent(QPaintEvent *event) override; -+ void mousePressEvent(QMouseEvent *event) override; -+ void enterEvent(QEnterEvent *event) override; -+ void leaveEvent(QEvent *event) override; -+ -+private: -+ bool m_pinned = true; -+ bool m_hovered = false; -+}; -+ -+class Media : public QWidget -+{ -+ Q_OBJECT -+ -+public: -+ explicit Media(QWidget *parent = nullptr); -+ -+protected: -+ void paintEvent(QPaintEvent *event) override; -+ void resizeEvent(QResizeEvent *event) override; -+ -+private: -+ void layout(); -+ -+ std::unique_ptr m_pip; -+ PipPin *m_pin = nullptr; -+}; -diff --git a/tests/pip/pipshellsurface.cpp b/tests/pip/pipshellsurface.cpp -new file mode 100644 -index 00000000000..082a51a3c24 ---- /dev/null -+++ b/tests/pip/pipshellsurface.cpp -@@ -0,0 +1,156 @@ -+/* -+ SPDX-FileCopyrightText: 2025 Vlad Zahorodnii -+ -+ SPDX-License-Identifier: GPL-2.0-or-later -+*/ -+ -+#include "pipshellsurface.h" -+ -+#include -+#include -+ -+XdgWmBase::XdgWmBase() -+ : QWaylandClientExtensionTemplate(6) -+{ -+ initialize(); -+ if (!isActive()) { -+ qFatal("The xdg-shell protocol is unsupported by the compositor"); -+ } -+} -+ -+XXPipShell::XXPipShell() -+ : QWaylandClientExtensionTemplate(1) -+{ -+ initialize(); -+ if (!isActive()) { -+ qFatal("The xx-pip-v1 protocol is unsupported by the compositor"); -+ } -+} -+ -+void PipShellIntegration::assignPipRole(QWindow *window) -+{ -+ window->create(); -+ -+ auto waylandWindow = dynamic_cast(window->handle()); -+ if (!waylandWindow) { -+ return; -+ } -+ -+ static PipShellIntegration *shellIntegration = nullptr; -+ if (!shellIntegration) { -+ shellIntegration = new PipShellIntegration(); -+ } -+ -+ waylandWindow->setShellIntegration(shellIntegration); -+} -+ -+PipShellIntegration::PipShellIntegration() -+ : m_xdgWmBase(std::make_unique()) -+ , m_xxPipShell(std::make_unique()) -+{ -+} -+ -+bool PipShellIntegration::initialize(QtWaylandClient::QWaylandDisplay *display) -+{ -+ return m_xdgWmBase->isInitialized() && m_xxPipShell->isInitialized(); -+} -+ -+QtWaylandClient::QWaylandShellSurface *PipShellIntegration::createShellSurface(QtWaylandClient::QWaylandWindow *window) -+{ -+ ::xdg_surface *xdgSurface = m_xdgWmBase->get_xdg_surface(window->wlSurface()); -+ ::xx_pip_v1 *xxPip = m_xxPipShell->get_pip(xdgSurface); -+ return new PipShellSurface(xdgSurface, xxPip, window); -+} -+ -+PipShellSurface::PipShellSurface(::xdg_surface *xdgSurface, ::xx_pip_v1 *xxPip, QtWaylandClient::QWaylandWindow *window) -+ : QWaylandShellSurface(window) -+ , QtWayland::xdg_surface(xdgSurface) -+ , QtWayland::xx_pip_v1(xxPip) -+{ -+} -+ -+PipShellSurface::~PipShellSurface() -+{ -+ xx_pip_v1::destroy(); -+ xdg_surface::destroy(); -+} -+ -+bool PipShellSurface::isExposed() const -+{ -+ return m_configured; -+} -+ -+void PipShellSurface::applyConfigure() -+{ -+ QSize size = window()->windowContentGeometry().size(); -+ if (m_pendingSize.width() > 0) { -+ size.setWidth(m_pendingSize.width()); -+ } -+ if (m_pendingSize.height() > 0) { -+ size.setHeight(m_pendingSize.height()); -+ } -+ -+ window()->resizeFromApplyConfigure(size); -+} -+ -+void PipShellSurface::setWindowGeometry(const QRect &rect) -+{ -+ if (window()->isExposed()) { -+ xdg_surface::set_window_geometry(rect.x(), rect.y(), rect.width(), rect.height()); -+ } -+} -+ -+bool PipShellSurface::move(QtWaylandClient::QWaylandInputDevice *inputDevice) -+{ -+ if (!m_configured) { -+ return false; -+ } -+ xx_pip_v1::move(inputDevice->wl_seat(), inputDevice->serial()); -+ return true; -+} -+ -+bool PipShellSurface::resize(QtWaylandClient::QWaylandInputDevice *inputDevice, Qt::Edges edges) -+{ -+ if (!m_configured) { -+ return false; -+ } -+ -+ const resize_edge edge = static_cast( -+ ((edges & Qt::TopEdge) ? resize_edge_top : 0) -+ | ((edges & Qt::BottomEdge) ? resize_edge_bottom : 0) -+ | ((edges & Qt::LeftEdge) ? resize_edge_left : 0) -+ | ((edges & Qt::RightEdge) ? resize_edge_right : 0)); -+ -+ xx_pip_v1::resize(inputDevice->wl_seat(), inputDevice->serial(), edge); -+ return true; -+} -+ -+void PipShellSurface::xdg_surface_configure(uint32_t serial) -+{ -+ xdg_surface::ack_configure(serial); -+ -+ if (!m_configured) { -+ m_configured = true; -+ applyConfigure(); -+ } else { -+ window()->applyConfigureWhenPossible(); -+ } -+ -+ window()->updateExposure(); -+} -+ -+void PipShellSurface::xx_pip_v1_configure_bounds(int32_t width, int32_t height) -+{ -+} -+ -+void PipShellSurface::xx_pip_v1_configure_size(int32_t width, int32_t height) -+{ -+ m_pendingSize = QSize(width, height); -+} -+ -+void PipShellSurface::xx_pip_v1_closed() -+{ -+ QWindowSystemInterface::handleCloseEvent(window()->window()); -+} -+ -+#include "moc_pipshellsurface.cpp" -diff --git a/tests/pip/pipshellsurface.h b/tests/pip/pipshellsurface.h -new file mode 100644 -index 00000000000..b02e83b3fc9 ---- /dev/null -+++ b/tests/pip/pipshellsurface.h -@@ -0,0 +1,70 @@ -+/* -+ SPDX-FileCopyrightText: 2025 Vlad Zahorodnii -+ -+ SPDX-License-Identifier: GPL-2.0-or-later -+*/ -+ -+#pragma once -+ -+#include -+#include -+#include -+#include -+ -+#include "qwayland-xdg-shell.h" -+#include "qwayland-xx-pip-v1.h" -+ -+class XdgWmBase : public QWaylandClientExtensionTemplate, public QtWayland::xdg_wm_base -+{ -+ Q_OBJECT -+ -+public: -+ XdgWmBase(); -+}; -+ -+class XXPipShell : public QWaylandClientExtensionTemplate, public QtWayland::xx_pip_shell_v1 -+{ -+ Q_OBJECT -+ -+public: -+ XXPipShell(); -+}; -+ -+class PipShellIntegration : public QtWaylandClient::QWaylandShellIntegration -+{ -+public: -+ PipShellIntegration(); -+ -+ bool initialize(QtWaylandClient::QWaylandDisplay *display) override; -+ QtWaylandClient::QWaylandShellSurface *createShellSurface(QtWaylandClient::QWaylandWindow *window) override; -+ -+ static void assignPipRole(QWindow *window); -+ -+private: -+ std::unique_ptr m_xdgWmBase; -+ std::unique_ptr m_xxPipShell; -+}; -+ -+class PipShellSurface : public QtWaylandClient::QWaylandShellSurface, public QtWayland::xdg_surface, public QtWayland::xx_pip_v1 -+{ -+ Q_OBJECT -+ -+public: -+ PipShellSurface(::xdg_surface *xdgSurface, ::xx_pip_v1 *xxPip, QtWaylandClient::QWaylandWindow *window); -+ ~PipShellSurface() override; -+ -+ bool isExposed() const override; -+ void applyConfigure() override; -+ void setWindowGeometry(const QRect &rect) override; -+ bool move(QtWaylandClient::QWaylandInputDevice *inputDevice) override; -+ bool resize(QtWaylandClient::QWaylandInputDevice *inputDevice, Qt::Edges edges) override; -+ -+private: -+ void xdg_surface_configure(uint32_t serial) override; -+ void xx_pip_v1_closed() override; -+ void xx_pip_v1_configure_bounds(int32_t width, int32_t height) override; -+ void xx_pip_v1_configure_size(int32_t width, int32_t height) override; -+ -+ QSize m_pendingSize; -+ bool m_configured = false; -+}; -diff --git a/tests/pip/window.cpp b/tests/pip/window.cpp -new file mode 100644 -index 00000000000..2ac98bde845 ---- /dev/null -+++ b/tests/pip/window.cpp -@@ -0,0 +1,19 @@ -+/* -+ SPDX-FileCopyrightText: 2025 Vlad Zahorodnii -+ -+ SPDX-License-Identifier: GPL-2.0-or-later -+*/ -+ -+#include "window.h" -+#include "pip.h" -+ -+Window::Window(QWidget *parent) -+ : QWidget(parent) -+{ -+ resize(800, 600); -+ -+ m_media = new Media(this); -+ m_media->setGeometry(100, 100, 400, 300); -+} -+ -+#include "moc_window.cpp" -diff --git a/tests/pip/window.h b/tests/pip/window.h -new file mode 100644 -index 00000000000..bfd83e9d467 ---- /dev/null -+++ b/tests/pip/window.h -@@ -0,0 +1,22 @@ -+/* -+ SPDX-FileCopyrightText: 2025 Vlad Zahorodnii -+ -+ SPDX-License-Identifier: GPL-2.0-or-later -+*/ -+ -+#pragma once -+ -+#include -+ -+class Media; -+ -+class Window : public QWidget -+{ -+ Q_OBJECT -+ -+public: -+ explicit Window(QWidget *parent = nullptr); -+ -+private: -+ Media *m_media = nullptr; -+}; --- -GitLab - - -From cab39d37c8cb7d1c37d04a76cc7fa7af39fb7908 Mon Sep 17 00:00:00 2001 -From: Vlad Zahorodnii -Date: Thu, 12 Jun 2025 14:22:55 +0000 -Subject: [PATCH 2/2] Apply 2 suggestion(s) to 1 file(s) - -Co-authored-by: Xaver Hugl ---- - src/wayland/xxpip_v1.cpp | 4 ++-- - 1 file changed, 2 insertions(+), 2 deletions(-) - -diff --git a/src/wayland/xxpip_v1.cpp b/src/wayland/xxpip_v1.cpp -index e0b1f36134f..bc1b65d0212 100644 ---- a/src/wayland/xxpip_v1.cpp -+++ b/src/wayland/xxpip_v1.cpp -@@ -282,7 +282,7 @@ quint32 XXPipV1Interface::sendConfigureSize(const QSizeF &size) - { - const quint32 serial = d->shell->display()->nextSerial(); - -- d->send_configure_size(size.width(), size.height()); -+ d->send_configure_size(std::round(size.width()), std::round(size.height())); - - auto xdgSurfacePrivate = XdgSurfaceInterfacePrivate::get(xdgSurface()); - xdgSurfacePrivate->send_configure(serial); -@@ -298,7 +298,7 @@ void XXPipV1Interface::sendClosed() - - void XXPipV1Interface::sendConfigureBounds(const QSizeF &size) - { -- d->send_configure_bounds(size.width(), size.height()); -+ d->send_configure_bounds(std::round(size.width()), std::round(size.height())); - } - - XXPipV1Interface *XXPipV1Interface::get(::wl_resource *resource) --- -GitLab - diff --git a/roles/kde/patches/kwin/pr7823.patch b/roles/kde/patches/kwin/pr7823.patch deleted file mode 100644 index 5e54a43..0000000 --- a/roles/kde/patches/kwin/pr7823.patch +++ /dev/null @@ -1,26 +0,0 @@ -From a1868942fbc2ad5de4c053dd17335dff34d81779 Mon Sep 17 00:00:00 2001 -From: Vlad Zahorodnii -Date: Wed, 25 Jun 2025 14:10:18 +0300 -Subject: [PATCH] plugins/login: Reduce animation duration - -This makes the startup feel a bit faster. ---- - src/plugins/login/package/contents/code/main.js | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/src/plugins/login/package/contents/code/main.js b/src/plugins/login/package/contents/code/main.js -index 7f849a6e2e6..e3ad0b76c07 100644 ---- a/src/plugins/login/package/contents/code/main.js -+++ b/src/plugins/login/package/contents/code/main.js -@@ -11,7 +11,7 @@ - "use strict"; - - var loginEffect = { -- duration: animationTime(1000), -+ duration: animationTime(500), - isFadeToBlack: false, - loadConfig: function () { - loginEffect.isFadeToBlack = effect.readConfig("FadeToBlack", false); --- -GitLab - diff --git a/roles/kde/patches/kwin/pr7927.patch b/roles/kde/patches/kwin/pr7927.patch deleted file mode 100644 index d35ef4b..0000000 --- a/roles/kde/patches/kwin/pr7927.patch +++ /dev/null @@ -1,669 +0,0 @@ -From 9f6c92806490d662117575a766f9fcb01e253344 Mon Sep 17 00:00:00 2001 -From: Xaver Hugl -Date: Tue, 22 Jul 2025 23:24:51 +0200 -Subject: [PATCH 1/4] xdgactivation: move the activation token to workspace - ---- - src/activation.cpp | 14 ++++++++++++ - src/workspace.h | 6 +++++ - src/xdgactivationv1.cpp | 50 +++++++++-------------------------------- - src/xdgactivationv1.h | 14 ++---------- - 4 files changed, 32 insertions(+), 52 deletions(-) - -diff --git a/src/activation.cpp b/src/activation.cpp -index 60b921247ce..9e743b8b098 100644 ---- a/src/activation.cpp -+++ b/src/activation.cpp -@@ -630,4 +630,18 @@ void Workspace::windowAttentionChanged(Window *window, bool set) - } - } - -+void Workspace::setActivationToken(const QString &token, uint32_t serial) -+{ -+ m_activationToken = token; -+ m_activationTokenSerial = serial; -+} -+ -+bool Workspace::mayActivate(const QString &token) const -+{ -+ if (!m_activeWindow) { -+ return true; -+ } -+ return !m_activationToken.isEmpty() && token == m_activationToken && m_activeWindow->lastUsageSerial() <= m_activationTokenSerial; -+} -+ - } // namespace -diff --git a/src/workspace.h b/src/workspace.h -index 2082bbe148d..03c54e06750 100644 ---- a/src/workspace.h -+++ b/src/workspace.h -@@ -436,6 +436,9 @@ public: - OutputConfigurationError applyOutputConfiguration(OutputConfiguration &config, const std::optional> &outputOrder = std::nullopt); - void updateXwaylandScale(); - -+ void setActivationToken(const QString &token, uint32_t serial); -+ bool mayActivate(const QString &token) const; -+ - public Q_SLOTS: - void performWindowOperation(KWin::Window *window, Options::WindowOperation op); - // Keybindings -@@ -729,6 +732,9 @@ private: - std::unique_ptr m_dpmsFilter; - KConfigWatcher::Ptr m_kdeglobalsWatcher; - -+ QString m_activationToken; -+ uint32_t m_activationTokenSerial = 0; -+ - private: - friend bool performTransiencyCheck(); - friend Workspace *workspace(); -diff --git a/src/xdgactivationv1.cpp b/src/xdgactivationv1.cpp -index 39dade95332..a6be350399a 100644 ---- a/src/xdgactivationv1.cpp -+++ b/src/xdgactivationv1.cpp -@@ -33,22 +33,6 @@ static bool isPrivilegedInWindowManagement(const ClientConnection *client) - XdgActivationV1Integration::XdgActivationV1Integration(XdgActivationV1Interface *activation, QObject *parent) - : QObject(parent) - { -- Workspace *ws = Workspace::self(); -- connect(ws, &Workspace::windowActivated, this, [this](Window *window) { -- if (!m_currentActivationToken || !window || window->property("token").toString() == m_currentActivationToken->token) { -- return; -- } -- -- // We check that it's not the app that we are trying to activate -- if (window->desktopFileName() != m_currentActivationToken->applicationId) { -- // But also that the new one has been requested after the token was requested -- if (window->lastUsageSerial() < m_currentActivationToken->serial) { -- return; -- } -- } -- -- clear(); -- }); - activation->setActivationTokenCreator([this](ClientConnection *client, SurfaceInterface *surface, uint serial, SeatInterface *seat, const QString &appId) -> QString { - Workspace *ws = Workspace::self(); - Q_ASSERT(client); // Should always be available as it's coming straight from the wayland implementation -@@ -69,9 +53,6 @@ QString XdgActivationV1Integration::requestToken(bool isPrivileged, SurfaceInter - static int i = 0; - const auto newToken = QStringLiteral("kwin-%1").arg(++i); - -- if (m_currentActivationToken) { -- clear(); -- } - bool showNotify = false; - QIcon icon = QIcon::fromTheme(QStringLiteral("system-run")); - if (const QString desktopFilePath = Window::findDesktopFile(appId); !desktopFilePath.isEmpty()) { -@@ -83,13 +64,13 @@ QString XdgActivationV1Integration::requestToken(bool isPrivileged, SurfaceInter - } - icon = QIcon::fromTheme(df.readIcon(), icon); - } -- std::unique_ptr activation; - if (showNotify) { -- activation = waylandServer()->plasmaActivationFeedback()->createActivation(appId); -+ m_lastToken = newToken; -+ m_activation = waylandServer()->plasmaActivationFeedback()->createActivation(appId); - } -- m_currentActivationToken = std::make_unique(ActivationToken{newToken, isPrivileged, surface, serial, seat, appId, showNotify, std::move(activation)}); -+ workspace()->setActivationToken(newToken, serial); - if (showNotify) { -- Q_EMIT effects->startupAdded(m_currentActivationToken->token, icon); -+ Q_EMIT effects->startupAdded(newToken, icon); - } - return newToken; - } -@@ -103,31 +84,20 @@ void XdgActivationV1Integration::activateSurface(SurfaceInterface *surface, cons - return; - } - -- if (!m_currentActivationToken || m_currentActivationToken->token != token) { -- qCDebug(KWIN_CORE) << "Refusing to activate " << window << " (provided token: " << token << ", current token:" << (m_currentActivationToken ? m_currentActivationToken->token : QStringLiteral("null")) << ")"; -+ if (!ws->mayActivate(token)) { - window->demandAttention(); - return; - } -- -- auto ownerWindow = waylandServer()->findWindow(m_currentActivationToken->surface); -- qCDebug(KWIN_CORE) << "activating" << window << surface << "on behalf of" << m_currentActivationToken->surface << "into" << ownerWindow; -- if (!ws->activeWindow() || ws->activeWindow() == ownerWindow || ws->activeWindow()->lastUsageSerial() < m_currentActivationToken->serial || m_currentActivationToken->isPrivileged) { -- ws->activateWindow(window); -- } else { -- qCWarning(KWIN_CORE) << "Activation requested while owner isn't active" << (ownerWindow ? ownerWindow->desktopFileName() : "null") -- << m_currentActivationToken->applicationId; -- window->demandAttention(); -- clear(); -- } -+ ws->activateWindow(window); -+ clear(); - } - - void XdgActivationV1Integration::clear() - { -- Q_ASSERT(m_currentActivationToken); -- if (m_currentActivationToken->showNotify) { -- Q_EMIT effects->startupRemoved(m_currentActivationToken->token); -+ if (m_activation) { -+ Q_EMIT effects->startupRemoved(m_lastToken); -+ m_activation.reset(); - } -- m_currentActivationToken.reset(); - } - - } -diff --git a/src/xdgactivationv1.h b/src/xdgactivationv1.h -index 98835def8aa..77d21856095 100644 ---- a/src/xdgactivationv1.h -+++ b/src/xdgactivationv1.h -@@ -38,18 +38,8 @@ private: - QString requestToken(bool isPrivileged, SurfaceInterface *surface, uint serial, SeatInterface *seat, const QString &appId); - void clear(); - -- struct ActivationToken -- { -- QString token; -- bool isPrivileged; -- QPointer surface; -- uint serial; -- SeatInterface *seat; -- QString applicationId; -- bool showNotify; -- std::unique_ptr activation; -- }; -- std::unique_ptr m_currentActivationToken; -+ QString m_lastToken; -+ std::unique_ptr m_activation; - }; - - } --- -GitLab - - -From 6c673a479412902a14c06046199f976e2192dc65 Mon Sep 17 00:00:00 2001 -From: Xaver Hugl -Date: Tue, 22 Jul 2025 23:43:14 +0200 -Subject: [PATCH 2/4] xdgactivation: also allow using activation tokens before - the window is mapped - -We just store the token in the window, and then Workspace uses it to decide whether -or not to activate the window when it's actually shown. ---- - src/window.cpp | 10 ++++++++++ - src/window.h | 5 +++++ - src/workspace.cpp | 4 ++-- - src/xdgactivationv1.cpp | 6 +++++- - 4 files changed, 22 insertions(+), 3 deletions(-) - -diff --git a/src/window.cpp b/src/window.cpp -index 4a97a54aa74..879147ec673 100644 ---- a/src/window.cpp -+++ b/src/window.cpp -@@ -4675,6 +4675,16 @@ void Window::setDescription(const QString &description) - } - } - -+void Window::setActivationToken(const QString &token) -+{ -+ m_activationToken = token; -+} -+ -+QString Window::activationToken() const -+{ -+ return m_activationToken; -+} -+ - } // namespace KWin - - #include "moc_window.cpp" -diff --git a/src/window.h b/src/window.h -index 1eb371018ca..91addb46819 100644 ---- a/src/window.h -+++ b/src/window.h -@@ -1357,6 +1357,9 @@ public: - QString tag() const; - QString description() const; - -+ void setActivationToken(const QString &token); -+ QString activationToken() const; -+ - public Q_SLOTS: - virtual void closeWindow() = 0; - -@@ -1880,6 +1883,8 @@ protected: - - QString m_tag; - QString m_description; -+ -+ QString m_activationToken; - }; - - inline QRectF Window::bufferGeometry() const -diff --git a/src/workspace.cpp b/src/workspace.cpp -index 3a5cb12677b..866abb8080d 100644 ---- a/src/workspace.cpp -+++ b/src/workspace.cpp -@@ -781,8 +781,8 @@ void Workspace::addWaylandWindow(Window *window) - rearrange(); - } - if (window->wantsInput() && !window->isMinimized()) { -- // Never activate a window on its own in "Extreme" mode. -- if (options->focusStealingPreventionLevel() < 4) { -+ // In "Extreme" mode, require an activation token to activate new windows -+ if (options->focusStealingPreventionLevel() < 4 || (!m_activationToken.isEmpty() && window->activationToken() == m_activationToken)) { - if (!window->isDesktop() - // If there's no active window, make this desktop the active one. - || (activeWindow() == nullptr && should_get_focus.count() == 0)) { -diff --git a/src/xdgactivationv1.cpp b/src/xdgactivationv1.cpp -index a6be350399a..11c95c84b32 100644 ---- a/src/xdgactivationv1.cpp -+++ b/src/xdgactivationv1.cpp -@@ -88,7 +88,11 @@ void XdgActivationV1Integration::activateSurface(SurfaceInterface *surface, cons - window->demandAttention(); - return; - } -- ws->activateWindow(window); -+ if (window->readyForPainting()) { -+ ws->activateWindow(window); -+ } else { -+ window->setActivationToken(token); -+ } - clear(); - } - --- -GitLab - - -From 2436625a66257f586a0934c3d678c910bbdb3705 Mon Sep 17 00:00:00 2001 -From: Xaver Hugl -Date: Wed, 23 Jul 2025 13:18:39 +0200 -Subject: [PATCH 3/4] xdgactivation: move the "not granted" token to - requestToken - -Having some but not all checks in that method is a bit confusing ---- - src/xdgactivationv1.cpp | 14 ++++++-------- - 1 file changed, 6 insertions(+), 8 deletions(-) - -diff --git a/src/xdgactivationv1.cpp b/src/xdgactivationv1.cpp -index 11c95c84b32..e818466f8ad 100644 ---- a/src/xdgactivationv1.cpp -+++ b/src/xdgactivationv1.cpp -@@ -34,15 +34,8 @@ XdgActivationV1Integration::XdgActivationV1Integration(XdgActivationV1Interface - : QObject(parent) - { - activation->setActivationTokenCreator([this](ClientConnection *client, SurfaceInterface *surface, uint serial, SeatInterface *seat, const QString &appId) -> QString { -- Workspace *ws = Workspace::self(); - Q_ASSERT(client); // Should always be available as it's coming straight from the wayland implementation -- const bool isPrivileged = isPrivilegedInWindowManagement(client); -- if (!isPrivileged && ws->activeWindow() && ws->activeWindow()->surface() != surface) { -- qCDebug(KWIN_CORE) << "Cannot grant a token to" << client; -- return QStringLiteral("not-granted-666"); -- } -- -- return requestToken(isPrivileged, surface, serial, seat, appId); -+ return requestToken(isPrivilegedInWindowManagement(client), surface, serial, seat, appId); - }); - - connect(activation, &XdgActivationV1Interface::activateRequested, this, &XdgActivationV1Integration::activateSurface); -@@ -50,6 +43,11 @@ XdgActivationV1Integration::XdgActivationV1Integration(XdgActivationV1Interface - - QString XdgActivationV1Integration::requestToken(bool isPrivileged, SurfaceInterface *surface, uint serial, SeatInterface *seat, const QString &appId) - { -+ auto window = waylandServer()->findWindow(surface); -+ if (!isPrivileged && workspace()->activeWindow() && workspace()->activeWindow()->surface() != surface) { -+ qCWarning(KWIN_CORE) << "Cannot grant a token to" << window; -+ return QStringLiteral("not-granted-666"); -+ } - static int i = 0; - const auto newToken = QStringLiteral("kwin-%1").arg(++i); - --- -GitLab - - -From 8508b8060813c07fe035801381bad1f9a375acf0 Mon Sep 17 00:00:00 2001 -From: Xaver Hugl -Date: Thu, 24 Jul 2025 20:43:46 +0200 -Subject: [PATCH 4/4] autotests/integration: add a test case for xdg activation - ---- - autotests/integration/CMakeLists.txt | 3 +- - autotests/integration/activation_test.cpp | 126 +++++++++++++++++++++- - autotests/integration/kwin_wayland_test.h | 31 ++++++ - autotests/integration/test_helpers.cpp | 50 +++++++++ - 4 files changed, 208 insertions(+), 2 deletions(-) - -diff --git a/autotests/integration/CMakeLists.txt b/autotests/integration/CMakeLists.txt -index 13b0cde04f5..408af6ac8b8 100644 ---- a/autotests/integration/CMakeLists.txt -+++ b/autotests/integration/CMakeLists.txt -@@ -16,13 +16,14 @@ qt6_generate_wayland_protocol_client_sources(KWinIntegrationTestFramework - FILES - ${CMAKE_SOURCE_DIR}/src/wayland/protocols/wlr-layer-shell-unstable-v1.xml - ${WaylandProtocols_DATADIR}/stable/presentation-time/presentation-time.xml -+ ${WaylandProtocols_DATADIR}/stable/tablet/tablet-v2.xml - ${WaylandProtocols_DATADIR}/stable/xdg-shell/xdg-shell.xml - ${WaylandProtocols_DATADIR}/staging/color-management/color-management-v1.xml -- ${WaylandProtocols_DATADIR}/stable/tablet/tablet-v2.xml - ${WaylandProtocols_DATADIR}/staging/cursor-shape/cursor-shape-v1.xml - ${WaylandProtocols_DATADIR}/staging/fifo/fifo-v1.xml - ${WaylandProtocols_DATADIR}/staging/fractional-scale/fractional-scale-v1.xml - ${WaylandProtocols_DATADIR}/staging/security-context/security-context-v1.xml -+ ${WaylandProtocols_DATADIR}/staging/xdg-activation/xdg-activation-v1.xml - ${WaylandProtocols_DATADIR}/staging/xdg-dialog/xdg-dialog-v1.xml - ${WaylandProtocols_DATADIR}/unstable/idle-inhibit/idle-inhibit-unstable-v1.xml - ${WaylandProtocols_DATADIR}/unstable/text-input/text-input-unstable-v3.xml -diff --git a/autotests/integration/activation_test.cpp b/autotests/integration/activation_test.cpp -index 75c9e7e8c7b..30f671d6fe2 100644 ---- a/autotests/integration/activation_test.cpp -+++ b/autotests/integration/activation_test.cpp -@@ -15,6 +15,7 @@ - #include "workspace.h" - #include "x11window.h" - -+#include - #include - #include - #include -@@ -40,6 +41,7 @@ private Q_SLOTS: - void testSwitchToWindowMaximized(); - void testSwitchToWindowFullScreen(); - void testActiveFullscreen(); -+ void testXdgActivation(); - - private: - void stackScreensHorizontally(); -@@ -64,7 +66,7 @@ void ActivationTest::initTestCase() - - void ActivationTest::init() - { -- QVERIFY(Test::setupWaylandConnection()); -+ QVERIFY(Test::setupWaylandConnection(Test::AdditionalWaylandInterface::XdgActivation | Test::AdditionalWaylandInterface::Seat)); - - workspace()->setActiveOutput(QPoint(640, 512)); - input()->pointer()->warp(QPoint(640, 512)); -@@ -592,6 +594,128 @@ void ActivationTest::testActiveFullscreen() - QCOMPARE(workspace()->activeWindow(), waylandWindow); - QCOMPARE(x11Window->layer(), Layer::NormalLayer); - } -+ -+void ActivationTest::testXdgActivation() -+{ -+ Test::setOutputConfig({QRect(0, 0, 1280, 1024)}); -+ -+ uint32_t time = 0; -+ -+ std::vector> surfaces; -+ std::vector> shellSurfaces; -+ std::vector windows; -+ const auto setupWindows = [&]() { -+ windows.clear(); -+ shellSurfaces.clear(); -+ surfaces.clear(); -+ -+ // re-create the same setup every time for reduced confusion -+ for (int i = 0; i < 3; i++) { -+ surfaces.push_back(Test::createSurface()); -+ shellSurfaces.push_back(Test::createXdgToplevelSurface(surfaces.back().get())); -+ windows.push_back(Test::renderAndWaitForShown(surfaces.back().get(), QSize(100, 50), Qt::blue)); -+ windows.back()->move(QPoint(150 * i, 0)); -+ -+ Test::pointerMotion(windows.back()->frameGeometry().center(), time++); -+ Test::pointerButtonPressed(1, time++); -+ Test::pointerButtonReleased(1, time++); -+ } -+ }; -+ setupWindows(); -+ -+ QSignalSpy activationSpy(workspace(), &Workspace::windowActivated); -+ -+ // activating a window without a valid token should fail -+ Test::xdgActivation()->activate(QString(), *surfaces[1]); -+ QVERIFY(!activationSpy.wait(10)); -+ -+ // activating it without a surface should fail as well, even if a serial is present -+ auto token = Test::xdgActivation()->createToken(); -+ token->set_serial(windows.back()->lastUsageSerial(), *Test::waylandSeat()); -+ Test::xdgActivation()->activate(token->commitAndWait(), *surfaces[1]); -+ QVERIFY(!activationSpy.wait(10)); -+ -+ // adding the surface should make it work -+ token = Test::xdgActivation()->createToken(); -+ token->set_surface(*surfaces.back()); -+ token->set_serial(windows.back()->lastUsageSerial(), *Test::waylandSeat()); -+ Test::xdgActivation()->activate(token->commitAndWait(), *surfaces[1]); -+ QVERIFY(activationSpy.wait(10)); -+ QCOMPARE(workspace()->activeWindow(), windows[1]); -+ -+ // activation should still work if the window is closed after creating the token -+ setupWindows(); -+ token = Test::xdgActivation()->createToken(); -+ token->set_surface(*surfaces[2]); -+ token->set_serial(windows[2]->lastUsageSerial(), *Test::waylandSeat()); -+ QString result = token->commitAndWait(); -+ -+ surfaces[2]->attachBuffer((wl_buffer *)nullptr); -+ surfaces[2]->commit(KWayland::Client::Surface::CommitFlag::None); -+ QVERIFY(activationSpy.wait(10)); -+ QCOMPARE(workspace()->activeWindow(), windows[1]); -+ -+ Test::xdgActivation()->activate(result, *surfaces[0]); -+ QVERIFY(activationSpy.wait(10)); -+ QCOMPARE(workspace()->activeWindow(), windows[0]); -+ -+ // ...unless the user interacted with another window in between -+ setupWindows(); -+ token = Test::xdgActivation()->createToken(); -+ token->set_surface(*surfaces[2]); -+ token->set_serial(windows[2]->lastUsageSerial(), *Test::waylandSeat()); -+ result = token->commitAndWait(); -+ -+ surfaces[2]->attachBuffer((wl_buffer *)nullptr); -+ surfaces[2]->commit(KWayland::Client::Surface::CommitFlag::None); -+ QVERIFY(activationSpy.wait(10)); -+ QCOMPARE(workspace()->activeWindow(), windows[1]); -+ -+ Test::pointerMotion(windows[1]->frameGeometry().center(), time++); -+ Test::pointerButtonPressed(1, time++); -+ Test::pointerButtonReleased(1, time++); -+ -+ Test::xdgActivation()->activate(result, *surfaces[0]); -+ QVERIFY(!activationSpy.wait(10)); -+ QCOMPARE(workspace()->activeWindow(), windows[1]); -+ -+ // ensure that windows are only activated on show with a valid activation token -+ options->setFocusStealingPreventionLevel(4); -+ -+ // creating a new window and immediately activating it should work -+ setupWindows(); -+ token = Test::xdgActivation()->createToken(); -+ token->set_surface(*surfaces[2]); -+ token->set_serial(windows[2]->lastUsageSerial(), *Test::waylandSeat()); -+ result = token->commitAndWait(); -+ surfaces.push_back(Test::createSurface()); -+ shellSurfaces.push_back(Test::createXdgToplevelSurface(surfaces.back().get(), [&](Test::XdgToplevel *toplevel) { -+ Test::xdgActivation()->activate(result, *surfaces.back()); -+ })); -+ windows.push_back(Test::renderAndWaitForShown(surfaces.back().get(), QSize(100, 50), Qt::blue)); -+ QCOMPARE(workspace()->activeWindow(), windows.back()); -+ windows.back()->move(QPoint(150 * 3, 0)); -+ -+ // activation should fail if the user clicks on another window in between -+ // creating the activation token and using it -+ setupWindows(); -+ token = Test::xdgActivation()->createToken(); -+ token->set_surface(*surfaces[2]); -+ token->set_serial(windows[2]->lastUsageSerial(), *Test::waylandSeat()); -+ result = token->commitAndWait(); -+ -+ Test::pointerMotion(windows[1]->frameGeometry().center(), time++); -+ Test::pointerButtonPressed(1, time++); -+ Test::pointerButtonReleased(1, time++); -+ -+ surfaces.push_back(Test::createSurface()); -+ shellSurfaces.push_back(Test::createXdgToplevelSurface(surfaces.back().get(), [&](Test::XdgToplevel *toplevel) { -+ Test::xdgActivation()->activate(result, *surfaces.back()); -+ })); -+ windows.push_back(Test::renderAndWaitForShown(surfaces.back().get(), QSize(100, 50), Qt::blue)); -+ QCOMPARE(workspace()->activeWindow(), windows[1]); -+ windows.back()->move(QPoint(150 * 3, 0)); -+} - } - - WAYLANDTEST_MAIN(KWin::ActivationTest) -diff --git a/autotests/integration/kwin_wayland_test.h b/autotests/integration/kwin_wayland_test.h -index b0f63dadbc5..a6616189617 100644 ---- a/autotests/integration/kwin_wayland_test.h -+++ b/autotests/integration/kwin_wayland_test.h -@@ -34,6 +34,7 @@ - #include "qwayland-security-context-v1.h" - #include "qwayland-text-input-unstable-v3.h" - #include "qwayland-wlr-layer-shell-unstable-v1.h" -+#include "qwayland-xdg-activation-v1.h" - #include "qwayland-xdg-decoration-unstable-v1.h" - #include "qwayland-xdg-dialog-v1.h" - #include "qwayland-xdg-shell.h" -@@ -611,6 +612,7 @@ enum class AdditionalWaylandInterface { - ColorManagement = 1 << 22, - FifoV1 = 1 << 23, - PresentationTime = 1 << 24, -+ XdgActivation = 1 << 25, - }; - Q_DECLARE_FLAGS(AdditionalWaylandInterfaces, AdditionalWaylandInterface) - -@@ -717,6 +719,33 @@ private: - void wp_presentation_feedback_discarded() override; - }; - -+class XdgActivationToken : public QObject, public QtWayland::xdg_activation_token_v1 -+{ -+ Q_OBJECT -+public: -+ explicit XdgActivationToken(::xdg_activation_token_v1 *object); -+ ~XdgActivationToken() override; -+ -+ QString commitAndWait(); -+ -+Q_SIGNALS: -+ void tokenReceived(); -+ -+private: -+ void xdg_activation_token_v1_done(const QString &token) override; -+ -+ QString m_token; -+}; -+ -+class XdgActivation : public QtWayland::xdg_activation_v1 -+{ -+public: -+ explicit XdgActivation(::wl_registry *registry, uint32_t id, int version); -+ ~XdgActivation() override; -+ -+ std::unique_ptr createToken(); -+}; -+ - struct Connection - { - static std::unique_ptr setup(AdditionalWaylandInterfaces interfaces = AdditionalWaylandInterfaces()); -@@ -757,6 +786,7 @@ struct Connection - std::unique_ptr colorManager; - std::unique_ptr fifoManager; - std::unique_ptr presentationTime; -+ std::unique_ptr xdgActivation; - }; - - void keyboardKeyPressed(quint32 key, quint32 time); -@@ -821,6 +851,7 @@ SecurityContextManagerV1 *waylandSecurityContextManagerV1(); - ColorManagerV1 *colorManager(); - FifoManagerV1 *fifoManager(); - PresentationTime *presentationTime(); -+XdgActivation *xdgActivation(); - - bool waitForWaylandSurface(Window *window); - -diff --git a/autotests/integration/test_helpers.cpp b/autotests/integration/test_helpers.cpp -index 22380c947d3..e524f10826d 100644 ---- a/autotests/integration/test_helpers.cpp -+++ b/autotests/integration/test_helpers.cpp -@@ -535,6 +535,11 @@ std::unique_ptr Connection::setup(AdditionalWaylandInterfaces flags) - c->presentationTime = std::make_unique(*c->registry, name, version); - } - } -+ if (flags & AdditionalWaylandInterface::XdgActivation) { -+ if (interface == xdg_activation_v1_interface.name) { -+ c->xdgActivation = std::make_unique(*c->registry, name, version); -+ } -+ } - }); - - QSignalSpy allAnnounced(registry, &KWayland::Client::Registry::interfacesAnnounced); -@@ -665,6 +670,7 @@ Connection::~Connection() - colorManager.reset(); - fifoManager.reset(); - presentationTime.reset(); -+ xdgActivation.reset(); - - delete queue; // Must be destroyed last - queue = nullptr; -@@ -796,6 +802,11 @@ PresentationTime *presentationTime() - return s_waylandConnection->presentationTime.get(); - } - -+XdgActivation *xdgActivation() -+{ -+ return s_waylandConnection->xdgActivation.get(); -+} -+ - bool waitForWaylandSurface(Window *window) - { - if (window->surface()) { -@@ -1817,6 +1828,45 @@ void WpPresentationFeedback::wp_presentation_feedback_discarded() - Q_EMIT discarded(); - } - -+XdgActivationToken::XdgActivationToken(::xdg_activation_token_v1 *object) -+ : QtWayland::xdg_activation_token_v1(object) -+{ -+} -+ -+XdgActivationToken::~XdgActivationToken() -+{ -+ destroy(); -+} -+ -+QString XdgActivationToken::commitAndWait() -+{ -+ QSignalSpy received(this, &XdgActivationToken::tokenReceived); -+ commit(); -+ received.wait(); -+ return m_token; -+} -+ -+void XdgActivationToken::xdg_activation_token_v1_done(const QString &token) -+{ -+ m_token = token; -+ Q_EMIT tokenReceived(); -+} -+ -+XdgActivation::XdgActivation(::wl_registry *registry, uint32_t id, int version) -+ : QtWayland::xdg_activation_v1(registry, id, version) -+{ -+} -+ -+XdgActivation::~XdgActivation() -+{ -+ destroy(); -+} -+ -+std::unique_ptr XdgActivation::createToken() -+{ -+ return std::make_unique(get_activation_token()); -+} -+ - void keyboardKeyPressed(quint32 key, quint32 time) - { - auto virtualKeyboard = static_cast(kwinApp())->virtualKeyboard(); --- -GitLab - diff --git a/roles/kde/patches/kwin/pr8005.patch b/roles/kde/patches/kwin/pr8005.patch deleted file mode 100644 index b95d140..0000000 --- a/roles/kde/patches/kwin/pr8005.patch +++ /dev/null @@ -1,71 +0,0 @@ -From dc692e89f101a47b9049b1f6ae4cc3cebef46edb Mon Sep 17 00:00:00 2001 -From: Xaver Hugl -Date: Tue, 12 Aug 2025 15:59:16 +0200 -Subject: [PATCH] xdgactivation: clear activation feedback if no token is - provided too - -If the window is activated, the user expectation is that feedback stops. The underlying -reason for why it's activated doesn't matter. ---- - src/xdgactivationv1.cpp | 12 ++++++++++-- - src/xdgactivationv1.h | 3 ++- - 2 files changed, 12 insertions(+), 3 deletions(-) - -diff --git a/src/xdgactivationv1.cpp b/src/xdgactivationv1.cpp -index 360ca9b8743..567b792025f 100644 ---- a/src/xdgactivationv1.cpp -+++ b/src/xdgactivationv1.cpp -@@ -33,6 +33,13 @@ static bool isPrivilegedInWindowManagement(const ClientConnection *client) - XdgActivationV1Integration::XdgActivationV1Integration(XdgActivationV1Interface *activation, QObject *parent) - : QObject(parent) - { -+ connect(Workspace::self(), &Workspace::windowActivated, this, [this](Window *window) { -+ if (!m_activation || !window || m_lastTokenAppId != window->desktopFileName()) { -+ return; -+ } -+ clearFeedback(); -+ }); -+ - activation->setActivationTokenCreator([this](ClientConnection *client, SurfaceInterface *surface, uint serial, SeatInterface *seat, const QString &appId) -> QString { - Q_ASSERT(client); // Should always be available as it's coming straight from the wayland implementation - return requestToken(isPrivilegedInWindowManagement(client), surface, serial, seat, appId); -@@ -64,6 +71,7 @@ QString XdgActivationV1Integration::requestToken(bool isPrivileged, SurfaceInter - } - if (showNotify) { - m_lastToken = newToken; -+ m_lastTokenAppId = appId; - m_activation = waylandServer()->plasmaActivationFeedback()->createActivation(appId); - } - if (isPrivileged && workspace()->activeWindow()) { -@@ -95,10 +103,10 @@ void XdgActivationV1Integration::activateSurface(SurfaceInterface *surface, cons - } else { - window->setActivationToken(token); - } -- clear(); -+ clearFeedback(); - } - --void XdgActivationV1Integration::clear() -+void XdgActivationV1Integration::clearFeedback() - { - if (m_activation) { - Q_EMIT effects->startupRemoved(m_lastToken); -diff --git a/src/xdgactivationv1.h b/src/xdgactivationv1.h -index 77d21856095..ad007c088b6 100644 ---- a/src/xdgactivationv1.h -+++ b/src/xdgactivationv1.h -@@ -36,9 +36,10 @@ public: - - private: - QString requestToken(bool isPrivileged, SurfaceInterface *surface, uint serial, SeatInterface *seat, const QString &appId); -- void clear(); -+ void clearFeedback(); - - QString m_lastToken; -+ QString m_lastTokenAppId; - std::unique_ptr m_activation; - }; - --- -GitLab - diff --git a/roles/kde/patches/kwin/pr8155.patch b/roles/kde/patches/kwin/pr8155.patch deleted file mode 100644 index ff8eb4b..0000000 --- a/roles/kde/patches/kwin/pr8155.patch +++ /dev/null @@ -1,60 +0,0 @@ -From bab7c4a4c5e2fb7be83ba94fdd0da7fd196654fa Mon Sep 17 00:00:00 2001 -From: David Redondo -Date: Thu, 25 Sep 2025 12:00:51 +0200 -Subject: [PATCH] Make sure XdgToplevelWindow always has an icon - -If the client never called set_app_id and did not set a custom -icon the window would end up without an icon at all. This ensures -that all windows always have at least the default icon. ---- - autotests/integration/xdgshellwindow_test.cpp | 13 +++++++++++-- - src/xdgshellwindow.cpp | 1 + - 2 files changed, 12 insertions(+), 2 deletions(-) - -diff --git a/autotests/integration/xdgshellwindow_test.cpp b/autotests/integration/xdgshellwindow_test.cpp -index 0903043808d..a3658ee62e7 100644 ---- a/autotests/integration/xdgshellwindow_test.cpp -+++ b/autotests/integration/xdgshellwindow_test.cpp -@@ -683,16 +683,25 @@ void TestXdgShellWindow::testDesktopFileName() - std::unique_ptr surface(Test::createSurface()); - // only xdg-shell as ShellSurface misses the setter - std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get())); -- shellSurface->set_app_id(QStringLiteral("org.kde.foo")); - auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue); - QVERIFY(window); -+ -+ // A client that never call set_app_id still gets the default icon -+ QCOMPARE(window->desktopFileName(), QString()); -+ QVERIFY(window->resourceClass().startsWith("testXdgShellWindow")); -+ QVERIFY(window->resourceName().startsWith("testXdgShellWindow")); -+ QCOMPARE(window->icon().name(), QStringLiteral("wayland")); -+ -+ QSignalSpy desktopFileNameChangedSpy(window, &Window::desktopFileNameChanged); -+ -+ shellSurface->set_app_id(QStringLiteral("org.kde.foo")); -+ QVERIFY(desktopFileNameChangedSpy.wait()); - QCOMPARE(window->desktopFileName(), QStringLiteral("org.kde.foo")); - QCOMPARE(window->resourceClass(), QStringLiteral("org.kde.foo")); - QVERIFY(window->resourceName().startsWith("testXdgShellWindow")); - // the desktop file does not exist, so icon should be generic Wayland - QCOMPARE(window->icon().name(), QStringLiteral("wayland")); - -- QSignalSpy desktopFileNameChangedSpy(window, &Window::desktopFileNameChanged); - QSignalSpy iconChangedSpy(window, &Window::iconChanged); - shellSurface->set_app_id(QStringLiteral("org.kde.bar")); - QVERIFY(desktopFileNameChangedSpy.wait()); -diff --git a/src/xdgshellwindow.cpp b/src/xdgshellwindow.cpp -index f585d3aef95..092c284b8c2 100644 ---- a/src/xdgshellwindow.cpp -+++ b/src/xdgshellwindow.cpp -@@ -1426,6 +1426,7 @@ void XdgToplevelWindow::initialize() - scheduleConfigure(); - updateColorScheme(); - updateCapabilities(); -+ updateIcon(); - setupWindowManagementInterface(); - - m_isInitialized = true; --- -GitLab - diff --git a/roles/kde/patches/plasma-nm/patches.txt b/roles/kde/patches/plasma-nm/patches.txt deleted file mode 100644 index c5e71a7..0000000 --- a/roles/kde/patches/plasma-nm/patches.txt +++ /dev/null @@ -1,4 +0,0 @@ -Plasma 6.5.0: - -Pr 439 https://invent.kde.org/plasma/plasma-nm/-/merge_requests/439 -Pr 442 https://invent.kde.org/plasma/plasma-nm/-/merge_requests/442 diff --git a/roles/kde/patches/plasma-nm/pr439.patch b/roles/kde/patches/plasma-nm/pr439.patch deleted file mode 100644 index 885e3dd..0000000 --- a/roles/kde/patches/plasma-nm/pr439.patch +++ /dev/null @@ -1,140 +0,0 @@ -From ac32824009397188131aab4fcc3394fcc4551c5c Mon Sep 17 00:00:00 2001 -From: Nate Graham -Date: Tue, 10 Jun 2025 15:43:13 -0600 -Subject: [PATCH 1/2] applet: handle some more states - -1. Looking for Wi-Fi networks but haven't found any yet -2. NetworkManager isn't running - -FEATURE: 485982 -BUG: 462454 -FIXED-IN: 6.5.0 ---- - applet/contents/ui/ConnectionListPage.qml | 15 ++++++++++++--- - applet/contents/ui/Toolbar.qml | 3 ++- - applet/metadata.json | 1 - - libs/networkstatus.h | 3 +-- - 4 files changed, 15 insertions(+), 7 deletions(-) - -diff --git a/applet/contents/ui/ConnectionListPage.qml b/applet/contents/ui/ConnectionListPage.qml -index b9a949d87..056f52de3 100644 ---- a/applet/contents/ui/ConnectionListPage.qml -+++ b/applet/contents/ui/ConnectionListPage.qml -@@ -101,9 +101,12 @@ ColumnLayout { - if (toolbar.displayplaneModeMessage) { - return "network-flightmode-on" - } -- if (toolbar.displayWifiMessage) { -+ if (toolbar.displayWifiOffMessage) { - return "network-wireless-off" - } -+ if (toolbar.displayWifiConnectingMessage) { -+ return "view-refresh-symbolic" -+ } - if (toolbar.displayWwanMessage) { - return "network-mobile-off" - } -@@ -113,19 +116,25 @@ ColumnLayout { - if (toolbar.displayplaneModeMessage) { - return i18n("Airplane mode is enabled") - } -- if (toolbar.displayWifiMessage) { -+ if (toolbar.displayWifiOffMessage) { - if (toolbar.displayWwanMessage) { - return i18n("Wireless and mobile networks are deactivated") - } - return i18n("Wireless is deactivated") - } -+ if (toolbar.displayWifiConnectingMessage) { -+ return i18n("Looking for wireless networks") -+ } - if (toolbar.displayWwanMessage) { - return i18n("Mobile network is deactivated") - } - if (toolbar.searchTextField.text.length > 0) { - return i18n("No matches") - } -- return i18n("No available connections") -+ if (connectionListPage.nmStatus.connectivity === NMQt.NetworkManager.Full) { -+ return i18n("No available connections") -+ } -+ return nmStatus.checkUnknownReason() - } - } - } -diff --git a/applet/contents/ui/Toolbar.qml b/applet/contents/ui/Toolbar.qml -index 87b3f1654..0d382109b 100644 ---- a/applet/contents/ui/Toolbar.qml -+++ b/applet/contents/ui/Toolbar.qml -@@ -16,7 +16,8 @@ import org.kde.kcmutils as KCMUtils - RowLayout { - id: toolbar - -- readonly property var displayWifiMessage: !wifiSwitchButton.checked && wifiSwitchButton.visible -+ readonly property var displayWifiOffMessage: !wifiSwitchButton.checked && wifiSwitchButton.visible -+ readonly property var displayWifiConnectingMessage: wifiSwitchButton.checked && wifiSwitchButton.visible - readonly property var displayWwanMessage: !wwanSwitchButton.checked && wwanSwitchButton.visible - readonly property var displayplaneModeMessage: planeModeSwitchButton.checked && planeModeSwitchButton.visible - -diff --git a/applet/metadata.json b/applet/metadata.json -index 0ca36a2b7..2f12891b5 100644 ---- a/applet/metadata.json -+++ b/applet/metadata.json -@@ -210,6 +210,5 @@ - "X-KDE-Keywords[zh_CN]": "network,internet,ethernet,wireless,wifi,wlan,vpn,wangluo,hulianwang,yitaiwang,juyuwang,wuxianwangluo,xunizhuanyongwangluo,网络,互联网,以太网,局域网,无线网络,虚拟专用网络", - "X-KDE-Keywords[zh_TW]": "網路,網絡,網際網路,乙太網路,以太網路,無線", - "X-Plasma-API-Minimum-Version": "6.0", -- "X-Plasma-DBusActivationService": "org.freedesktop.NetworkManager", - "X-Plasma-NotificationAreaCategory": "Hardware" - } -diff --git a/libs/networkstatus.h b/libs/networkstatus.h -index aa88bf7c5..a885f04dd 100644 ---- a/libs/networkstatus.h -+++ b/libs/networkstatus.h -@@ -59,6 +59,7 @@ public: - QString activeConnections() const; - QString networkStatus() const; - NetworkManager::Connectivity connectivity() const; -+ Q_INVOKABLE QString checkUnknownReason() const; - - private Q_SLOTS: - void activeConnectionsChanged(); -@@ -75,8 +76,6 @@ private: - QString m_activeConnections; - QString m_networkStatus; - NetworkManager::Connectivity m_connectivity = NetworkManager::UnknownConnectivity; -- -- QString checkUnknownReason() const; - }; - - #endif // PLAMA_NM_NETWORK_STATUS_H --- -GitLab - - -From a45534cd9c19f267075fe4261087045f1f3a9318 Mon Sep 17 00:00:00 2001 -From: Nate Graham -Date: Wed, 11 Jun 2025 10:14:40 -0600 -Subject: [PATCH 2/2] Rephrase "NetworkManager not running" message to be more - user-friendly - ---- - libs/networkstatus.cpp | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/libs/networkstatus.cpp b/libs/networkstatus.cpp -index c89ca9fca..b778eedda 100644 ---- a/libs/networkstatus.cpp -+++ b/libs/networkstatus.cpp -@@ -253,7 +253,7 @@ QString NetworkStatus::checkUnknownReason() const - { - // Check if NetworkManager is running. - if (!QDBusConnection::systemBus().interface()->isServiceRegistered(QStringLiteral(NM_DBUS_INTERFACE))) { -- return i18n("NetworkManager not running"); -+ return i18nc("@info:status", "NetworkManager service is not running"); - } - - // Check for compatible NetworkManager version. --- -GitLab - diff --git a/roles/kde/patches/plasma-nm/pr442.patch b/roles/kde/patches/plasma-nm/pr442.patch deleted file mode 100644 index cd6dda6..0000000 --- a/roles/kde/patches/plasma-nm/pr442.patch +++ /dev/null @@ -1,168 +0,0 @@ -From 63a9d544dc4f5160f28e5f3203a2c0a5e639e5ac Mon Sep 17 00:00:00 2001 -From: Nate Graham -Date: Fri, 13 Jun 2025 12:24:22 -0600 -Subject: [PATCH] applet: use standard section headers - -We have them, so we might as well use them. This lets us get rid of a -bunch of complex and fragile code that's responsible for the existing -separator. ---- - applet/contents/ui/ConnectionItem.qml | 14 ----- - applet/contents/ui/ConnectionListPage.qml | 10 ++-- - applet/contents/ui/ListItem.qml | 64 ----------------------- - libs/models/networkmodelitem.cpp | 8 ++- - 4 files changed, 12 insertions(+), 84 deletions(-) - delete mode 100644 applet/contents/ui/ListItem.qml - -diff --git a/applet/contents/ui/ConnectionItem.qml b/applet/contents/ui/ConnectionItem.qml -index ed154e633..5bb5c530c 100644 ---- a/applet/contents/ui/ConnectionItem.qml -+++ b/applet/contents/ui/ConnectionItem.qml -@@ -321,20 +321,6 @@ PlasmaExtras.ExpandableListItem { - } - } - -- onDeactivatedChanged: { -- /* Separator is part of section, which is visible only when available connections exist. Need to determine -- if there is a connection in use, to show Separator. Otherwise need to hide it from the top of the list. -- Connections in use are always on top, only need to check the first one. */ -- if (appletProxyModel.data(appletProxyModel.index(0, 0), PlasmaNM.NetworkModel.SectionRole) !== "Available connections") { -- if (connectionView.showSeparator != true) { -- connectionView.showSeparator = true -- } -- return -- } -- connectionView.showSeparator = false -- return -- } -- - onItemCollapsed: { - connectionItem.customExpandedViewContent = detailsComponent; - setDelayModelUpdates(false); -diff --git a/applet/contents/ui/ConnectionListPage.qml b/applet/contents/ui/ConnectionListPage.qml -index b62da252c..d8ebd2c54 100644 ---- a/applet/contents/ui/ConnectionListPage.qml -+++ b/applet/contents/ui/ConnectionListPage.qml -@@ -8,7 +8,7 @@ import QtQuick 2.15 - import QtQuick.Layouts 1.2 - import org.kde.plasma.components 3.0 as PlasmaComponents3 - import org.kde.kirigami 2.20 as Kirigami --import org.kde.plasma.extras 2.0 as PlasmaExtras -+import org.kde.plasma.extras as PlasmaExtras - import org.kde.plasma.networkmanagement as PlasmaNM - import org.kde.networkmanager as NMQt - -@@ -55,7 +55,6 @@ ColumnLayout { - id: connectionView - - property int currentVisibleButtonIndex: -1 -- property bool showSeparator: false - - Keys.onDownPressed: event => { - connectionView.incrementCurrentIndex(); -@@ -80,9 +79,10 @@ ColumnLayout { - model: appletProxyModel - currentIndex: -1 - boundsBehavior: Flickable.StopAtBounds -- section.property: showSeparator ? "Section" : "" -- section.delegate: ListItem { -- separator: true -+ section.property: "Section" -+ section.delegate: PlasmaExtras.ListSectionHeader { -+ width: connectionView.width - connectionView.leftMargin - connectionView.rightMargin -+ text: section - } - highlight: PlasmaExtras.Highlight { } - highlightMoveDuration: Kirigami.Units.shortDuration -diff --git a/applet/contents/ui/ListItem.qml b/applet/contents/ui/ListItem.qml -deleted file mode 100644 -index bfe1390d1..000000000 ---- a/applet/contents/ui/ListItem.qml -+++ /dev/null -@@ -1,64 +0,0 @@ --/* -- SPDX-FileCopyrightText: 2010 Marco Martin -- SPDX-FileCopyrightText: 2016 Jan Grulich -- SPDX-FileCopyrightText: 2020 George Vogiatzis -- -- SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL --*/ -- --import QtQuick 2.1 --import org.kde.kirigami 2.20 as Kirigami --import org.kde.ksvg 1.0 as KSvg -- --/** -- * Ignores the theme's listItem margins, and uses custom highlight(pressed) area. -- * Could break some themes but the majority look fine. -- * Also includes a separator to be used in sections. -- */ --MouseArea { -- id: listItem -- -- property bool checked: false -- property bool separator: false -- property rect highlightRect: Qt.rect(0, 0, width, height) -- -- width: parent.width -- -- // Sections have spacing above but not below. Will use 2 of them below. -- height: separator ? separatorLine.height + Kirigami.Units.smallSpacing * 3 : parent.height -- hoverEnabled: true -- -- KSvg.SvgItem { -- id: separatorLine -- anchors { -- horizontalCenter: parent.horizontalCenter -- top: parent.top -- topMargin: Kirigami.Units.smallSpacing -- } -- imagePath: "widgets/line" -- elementId: "horizontal-line" -- width: parent.width - Kirigami.Units.gridUnit * 2 -- visible: listItem.separator -- } -- -- KSvg.FrameSvgItem { -- id: background -- imagePath: "widgets/listitem" -- prefix: "normal" -- anchors.fill: parent -- visible: listItem.separator ? false : true -- } -- -- KSvg.FrameSvgItem { -- id: pressed -- imagePath: "widgets/listitem" -- prefix: "pressed" -- opacity: listItem.checked ? 1 : 0 -- Behavior on opacity { NumberAnimation { duration: Kirigami.Units.shortDuration } } -- -- x: listItem.highlightRect.x -- y: listItem.highlightRect.y -- height: listItem.highlightRect.height -- width: listItem.highlightRect.width -- } --} -diff --git a/libs/models/networkmodelitem.cpp b/libs/models/networkmodelitem.cpp -index b92ec0df2..4d7cbd77d 100644 ---- a/libs/models/networkmodelitem.cpp -+++ b/libs/models/networkmodelitem.cpp -@@ -344,7 +344,13 @@ QString NetworkModelItem::originalName() const - QString NetworkModelItem::sectionType() const - { - if (m_connectionState == NetworkManager::ActiveConnection::Deactivated) { -- return QStringLiteral("Available connections"); -+ return i18nc("@title:column header for list of available network connections", "Available"); -+ // clang-format off -+ } else if (m_connectionState == NetworkManager::ActiveConnection::Activating -+ || m_connectionState == NetworkManager::ActiveConnection::Activated -+ || m_connectionState == NetworkManager::ActiveConnection::Deactivating) { -+ // clang-format on -+ return i18nc("@title:column header for list of connected network connections", "Connected"); - } else { - return {}; - } --- -GitLab - diff --git a/roles/kde/patches/plasma-workspace/commit8202ba92.patch b/roles/kde/patches/plasma-workspace/commit8202ba92.patch deleted file mode 100644 index cb5fb13..0000000 --- a/roles/kde/patches/plasma-workspace/commit8202ba92.patch +++ /dev/null @@ -1,248 +0,0 @@ -From 8202ba92b610c691b8bc6bab8ad5a1c3b9ac73da Mon Sep 17 00:00:00 2001 -From: Vlad Zahorodnii -Date: Wed, 25 Jun 2025 17:29:19 +0300 -Subject: [PATCH] startkde: Drop ready stage - -In order to hide the splash screen, it is sufficient just to see the -wallpaper. If more desktop environment components are loaded soon -afterwards, it is okay. - -With systemd boot, the way the ready stage is integrated is also kind -of a hack. ---- - appiumtests/CMakeLists.txt | 1 - - appiumtests/ksplash/CMakeLists.txt | 14 ---- - appiumtests/ksplash/ksplashtest.py | 79 ------------------- - ksplash/ksplashqml/splashapp.cpp | 5 +- - startkde/plasma-session/startup.cpp | 11 --- - startkde/plasma-session/startup.h | 1 - - startkde/systemd/CMakeLists.txt | 3 - - .../systemd/plasma-ksplash-ready.service.in | 10 --- - startkde/systemd/plasma-workspace.target | 1 - - 9 files changed, 2 insertions(+), 123 deletions(-) - delete mode 100644 appiumtests/ksplash/CMakeLists.txt - delete mode 100755 appiumtests/ksplash/ksplashtest.py - delete mode 100644 startkde/systemd/plasma-ksplash-ready.service.in - -diff --git a/appiumtests/CMakeLists.txt b/appiumtests/CMakeLists.txt -index 68d0b6895ba..22234aeb031 100644 ---- a/appiumtests/CMakeLists.txt -+++ b/appiumtests/CMakeLists.txt -@@ -20,5 +20,4 @@ add_subdirectory(applets) - add_subdirectory(components_tests) - add_subdirectory(kcms) - add_subdirectory(krunner) --add_subdirectory(ksplash) - add_subdirectory(wallpapers) -diff --git a/appiumtests/ksplash/CMakeLists.txt b/appiumtests/ksplash/CMakeLists.txt -deleted file mode 100644 -index 3bea5174f5a..00000000000 ---- a/appiumtests/ksplash/CMakeLists.txt -+++ /dev/null -@@ -1,14 +0,0 @@ --# SPDX-FileCopyrightText: 2024 Fushan Wen --# SPDX-License-Identifier: BSD-3-Clause -- --add_test( -- NAME ksplashtest_wayland -- COMMAND sh -c "mkdir -p /tmp/appium/ksplashtest_wayland;dbus-launch selenium-webdriver-at-spi-run ${CMAKE_CURRENT_SOURCE_DIR}/ksplashtest.py --failfast" --) --set_tests_properties(ksplashtest_wayland PROPERTIES TIMEOUT 60 ENVIRONMENT "XDG_RUNTIME_DIR=/tmp/appium/ksplashtest_wayland;FLASK_PORT=5701") -- --add_test( -- NAME ksplashtest_x11 -- COMMAND sh -c "mkdir -p /tmp/appium/ksplashtest_x11;dbus-launch selenium-webdriver-at-spi-run ${CMAKE_CURRENT_SOURCE_DIR}/ksplashtest.py --failfast" --) --set_tests_properties(ksplashtest_x11 PROPERTIES TIMEOUT 60 ENVIRONMENT "XDG_RUNTIME_DIR=/tmp/appium/ksplashtest_x11;FLASK_PORT=5702;TEST_WITH_KWIN_WAYLAND=0") -diff --git a/appiumtests/ksplash/ksplashtest.py b/appiumtests/ksplash/ksplashtest.py -deleted file mode 100755 -index b7ca43c0519..00000000000 ---- a/appiumtests/ksplash/ksplashtest.py -+++ /dev/null -@@ -1,79 +0,0 @@ --#!/usr/bin/env python3 -- --# SPDX-FileCopyrightText: 2024 Fushan Wen --# SPDX-License-Identifier: MIT -- --# pylint: disable=too-many-arguments -- --import os --import subprocess --import sys --import time --import unittest -- --from appium import webdriver --from appium.options.common.base import AppiumOptions --from appium.webdriver.common.appiumby import AppiumBy --from gi.repository import Gio, GLib -- -- --class KSplashTest(unittest.TestCase): -- -- driver: webdriver.Remote -- -- @classmethod -- def setUpClass(cls) -> None: -- options = AppiumOptions() -- options.set_capability("app", "ksplashqml --window") -- options.set_capability("environ", { -- "LC_ALL": "en_US.UTF-8", -- "QT_FATAL_WARNINGS": "1", -- "QT_LOGGING_RULES": "qt.accessibility.atspi.warning=false;kf.plasma.core.warning=false;kf.windowsystem.warning=false;kf.kirigami.platform.warning=false;org.kde.plasma.ksplashqml.debug=true", -- }) -- options.set_capability("timeouts", {'implicit': 10000}) -- cls.driver = webdriver.Remote(command_executor=f'http://127.0.0.1:{os.getenv("FLASK_PORT", "4723")}', options=options) -- -- def tearDown(self) -> None: -- """ -- Take screenshot when the current test fails -- """ -- if not self._outcome.result.wasSuccessful(): -- self.driver.get_screenshot_as_file(f"failed_test_shot_ksplash_#{self.id()}.png") -- -- def test_1_bug494840_setStage(self) -> None: -- """ -- Checks if the setStage method is ever called after starting plasma-ksplash-ready.service. -- """ -- if os.getenv("TEST_WITH_KWIN_WAYLAND", "1") == "0": -- stages = ("wm", "kcminit", "ksmserver", "startPlasma", "desktop") -- else: -- stages = ("kcminit", "ksmserver", "startPlasma", "desktop") -- -- session_bus = Gio.bus_get_sync(Gio.BusType.SESSION) -- for stage in stages: -- message: Gio.DBusMessage = Gio.DBusMessage.new_method_call("org.kde.KSplash", "/KSplash", "org.kde.KSplash", "setStage") -- message.set_body(GLib.Variant("(s)", [stage])) -- session_bus.send_message_with_reply_sync(message, Gio.DBusSendMessageFlags.NONE, 3000) -- -- self.driver.find_element(AppiumBy.NAME, "Plasma made by KDE") -- -- with open(os.path.join(os.path.dirname(os.path.abspath(__file__)), os.pardir, os.pardir, "startkde", "systemd", "plasma-ksplash-ready.service.in"), encoding="utf-8") as handler: -- for line in handler: -- if line.startswith("ExecStart="): -- command = line.removeprefix("ExecStart=").strip().split(" ") -- subprocess.check_call(command, stdout=sys.stderr, stderr=sys.stderr) -- break -- -- success = False -- for _ in range(10): -- try: -- subprocess.check_call(["pidof", "ksplashqml"]) -- except subprocess.CalledProcessError: -- success = True -- break -- time.sleep(1) -- self.assertTrue(success) -- -- --if __name__ == '__main__': -- unittest.main() -diff --git a/ksplash/ksplashqml/splashapp.cpp b/ksplash/ksplashqml/splashapp.cpp -index b60a58724be..2262503b1c3 100644 ---- a/ksplash/ksplashqml/splashapp.cpp -+++ b/ksplash/ksplashqml/splashapp.cpp -@@ -26,13 +26,12 @@ - #define TEST_STEP_INTERVAL 2000 - - /** -- * There are 7 stages in ksplash -+ * There are 6 stages in ksplash - * - initial (from this class) - * - startPlasma (from startplasma) - * - kcminit - * - ksmserver - * - wm (for X11 from KWin, for Wayland from this class) -- * - ready (from plasma-session startup) - * - desktop (from shellcorona) - */ - -@@ -114,7 +113,7 @@ void SplashApp::setStage(const QString &stage) - void SplashApp::setStage(int stage) - { - m_stage = stage; -- if (m_stage == 7) { -+ if (m_stage == 6) { - QGuiApplication::exit(EXIT_SUCCESS); - } - for (SplashWindow *w : std::as_const(m_windows)) { -diff --git a/startkde/plasma-session/startup.cpp b/startkde/plasma-session/startup.cpp -index a731c7b2791..0567e00881f 100644 ---- a/startkde/plasma-session/startup.cpp -+++ b/startkde/plasma-session/startup.cpp -@@ -206,20 +206,9 @@ Startup::Startup(QObject *parent) - // app will be closed when all KJobs finish thanks to the QEventLoopLocker in each KJob - } - --void Startup::upAndRunning(const QString &msg) --{ -- QDBusMessage ksplashProgressMessage = QDBusMessage::createMethodCall(QStringLiteral("org.kde.KSplash"), -- QStringLiteral("/KSplash"), -- QStringLiteral("org.kde.KSplash"), -- QStringLiteral("setStage")); -- ksplashProgressMessage.setArguments(QList() << msg); -- QDBusConnection::sessionBus().asyncCall(ksplashProgressMessage); --} -- - void Startup::finishStartup() - { - qCDebug(PLASMA_SESSION) << "Finished"; -- upAndRunning(QStringLiteral("ready")); - - playStartupSound(); - new SessionTrack(m_processes); -diff --git a/startkde/plasma-session/startup.h b/startkde/plasma-session/startup.h -index 6ef4fee9bdd..876a1439fce 100644 ---- a/startkde/plasma-session/startup.h -+++ b/startkde/plasma-session/startup.h -@@ -20,7 +20,6 @@ class Startup : public QObject - Q_OBJECT - public: - Startup(QObject *parent); -- void upAndRunning(const QString &msg); - void finishStartup(); - - static Startup *self() -diff --git a/startkde/systemd/CMakeLists.txt b/startkde/systemd/CMakeLists.txt -index 2f5d30e8456..c3455ebae81 100644 ---- a/startkde/systemd/CMakeLists.txt -+++ b/startkde/systemd/CMakeLists.txt -@@ -1,6 +1,3 @@ --ecm_install_configured_files(INPUT plasma-ksplash-ready.service.in @ONLY -- DESTINATION ${KDE_INSTALL_SYSTEMDUSERUNITDIR}) -- - install(FILES plasma-core.target DESTINATION ${KDE_INSTALL_SYSTEMDUSERUNITDIR}) - install(FILES plasma-workspace.target DESTINATION ${KDE_INSTALL_SYSTEMDUSERUNITDIR}) - install(FILES plasma-workspace-wayland.target DESTINATION ${KDE_INSTALL_SYSTEMDUSERUNITDIR}) -diff --git a/startkde/systemd/plasma-ksplash-ready.service.in b/startkde/systemd/plasma-ksplash-ready.service.in -deleted file mode 100644 -index 1e903130a96..00000000000 ---- a/startkde/systemd/plasma-ksplash-ready.service.in -+++ /dev/null -@@ -1,10 +0,0 @@ --[Unit] --Description=KSplash "ready" Stage --Wants=plasma-core.target --After=plasma-core.target --PartOf=graphical-session.target -- --[Service] --Type=oneshot --ExecStart=dbus-send --session --reply-timeout=1 --type=method_call --dest=org.kde.KSplash /KSplash org.kde.KSplash.setStage string:ready --Slice=session.slice -diff --git a/startkde/systemd/plasma-workspace.target b/startkde/systemd/plasma-workspace.target -index a9113f49112..4cc8d9330c3 100644 ---- a/startkde/systemd/plasma-workspace.target -+++ b/startkde/systemd/plasma-workspace.target -@@ -6,7 +6,6 @@ Wants=plasma-restoresession.service - Wants=plasma-xembedsniproxy.service - Wants=plasma-gmenudbusmenuproxy.service - Wants=plasma-powerdevil.service --Wants=plasma-ksplash-ready.service - Wants=plasma-polkit-agent.service - Wants=kde-baloo.service - Wants=plasma-foreground-booster.service --- -GitLab - diff --git a/roles/kde/patches/plasma-workspace/patches.txt b/roles/kde/patches/plasma-workspace/patches.txt index 49de017..3e0146f 100644 --- a/roles/kde/patches/plasma-workspace/patches.txt +++ b/roles/kde/patches/plasma-workspace/patches.txt @@ -1,21 +1,3 @@ -Plasma 6.5.0: - -Pr 5589 https://invent.kde.org/plasma/plasma-workspace/-/merge_requests/5589 -Pr 5626 https://invent.kde.org/plasma/plasma-workspace/-/merge_requests/5626 -Pr 5627 https://invent.kde.org/plasma/plasma-workspace/-/merge_requests/5627 -Commit 8202ba92 https://invent.kde.org/plasma/plasma-workspace/-/commit/8202ba92b610c691b8bc6bab8ad5a1c3b9ac73da - Part of https://invent.kde.org/plasma/plasma-workspace/-/merge_requests/5628, the other part got cherry picked on 6.4.2 -Pr 5657 https://invent.kde.org/plasma/plasma-workspace/-/merge_requests/5657 -Pr 5734 https://invent.kde.org/plasma/plasma-workspace/-/merge_requests/5734 - Depends on Pr 5609 https://invent.kde.org/plasma/plasma-workspace/-/merge_requests/5609 -Pr 5746 https://invent.kde.org/plasma/plasma-workspace/-/merge_requests/5746 -Pr 5782 https://invent.kde.org/plasma/plasma-workspace/-/merge_requests/5782 -Pr 5678 https://invent.kde.org/plasma/plasma-workspace/-/merge_requests/5678 - Depends on Pr 5673 https://invent.kde.org/plasma/plasma-workspace/-/merge_requests/5673 -Pr 5788 https://invent.kde.org/plasma/plasma-workspace/-/merge_requests/5788 - Allows compiling on gcc 14 after applying pr 5678 - Plasma 6.6.0: -Pr 5818 https://invent.kde.org/plasma/plasma-workspace/-/merge_requests/5818 Pr 5816 https://invent.kde.org/plasma/plasma-workspace/-/merge_requests/5816 diff --git a/roles/kde/patches/plasma-workspace/pr5589.patch b/roles/kde/patches/plasma-workspace/pr5589.patch deleted file mode 100644 index b305b9f..0000000 --- a/roles/kde/patches/plasma-workspace/pr5589.patch +++ /dev/null @@ -1,37 +0,0 @@ -From 2278398309c68ce401a8e35b193fca3782391a4a Mon Sep 17 00:00:00 2001 -From: Nate Graham -Date: Thu, 12 Jun 2025 09:15:15 -0600 -Subject: [PATCH] applets/devicenotifier: use standard section header - -No need for a custom header here when we have a standard one. - -CCBUG: 442724 ---- - .../package/contents/ui/FullRepresentation.qml | 10 ++-------- - 1 file changed, 2 insertions(+), 8 deletions(-) - -diff --git a/applets/devicenotifier/package/contents/ui/FullRepresentation.qml b/applets/devicenotifier/package/contents/ui/FullRepresentation.qml -index 967223e0a1a..a0c28df45fe 100644 ---- a/applets/devicenotifier/package/contents/ui/FullRepresentation.qml -+++ b/applets/devicenotifier/package/contents/ui/FullRepresentation.qml -@@ -132,15 +132,9 @@ PlasmaExtras.Representation { - - section { - property: "deviceType" -- delegate: Item { -- height: Math.floor(childrenRect.height) -+ delegate: PlasmaExtras.ListSectionHeader { - width: notifierDialog.width - (scrollView.PlasmaComponents3.ScrollBar.vertical.visible ? Kirigami.Units.largeSpacing * 2 : 0) -- Kirigami.Heading { -- level: 3 -- opacity: 0.6 -- text: section -- textFormat: Text.PlainText -- } -+ text: section - } - } - --- -GitLab - diff --git a/roles/kde/patches/plasma-workspace/pr5609.patch b/roles/kde/patches/plasma-workspace/pr5609.patch deleted file mode 100644 index 74c9d39..0000000 --- a/roles/kde/patches/plasma-workspace/pr5609.patch +++ /dev/null @@ -1,456 +0,0 @@ -From 439b251bcb3ea24f52c052e7244fb7ced04503aa Mon Sep 17 00:00:00 2001 -From: Bohdan Onofriichuk -Date: Thu, 19 Jun 2025 14:51:30 +0000 -Subject: [PATCH] applets/devicenotifier: port to plasma_add_applet - ---- - applets/devicenotifier/CMakeLists.txt | 58 ++++++++++++++++--- - .../{plugin => }/actioninterface.cpp | 0 - .../{plugin => }/actioninterface.h | 0 - .../{plugin => }/actions/defaultaction.cpp | 0 - .../{plugin => }/actions/defaultaction.h | 0 - .../{plugin => }/actions/mountaction.cpp | 0 - .../{plugin => }/actions/mountaction.h | 0 - .../actions/mountandopenaction.cpp | 0 - .../{plugin => }/actions/mountandopenaction.h | 0 - .../actions/openwithfilemanageraction.cpp | 0 - .../actions/openwithfilemanageraction.h | 0 - .../{plugin => }/actions/unmountaction.cpp | 0 - .../{plugin => }/actions/unmountaction.h | 0 - .../{plugin => }/actionscontrol.cpp | 0 - .../{plugin => }/actionscontrol.h | 0 - .../{plugin => }/devicecontrol.cpp | 0 - .../{plugin => }/devicecontrol.h | 0 - .../{plugin => }/deviceerrormonitor_p.cpp | 0 - .../{plugin => }/deviceerrormonitor_p.h | 0 - .../{plugin => }/devicefiltercontrol.cpp | 0 - .../{plugin => }/devicefiltercontrol.h | 0 - .../{plugin => }/devicenotifications.notifyrc | 0 - .../{plugin => }/deviceserviceaction.cpp | 0 - .../{plugin => }/deviceserviceaction.h | 0 - .../{plugin => }/devicestatemonitor_p.cpp | 0 - .../{plugin => }/devicestatemonitor_p.h | 0 - .../{package/contents/config => }/main.xml | 0 - .../{package => }/metadata.json | 1 - - applets/devicenotifier/plugin/CMakeLists.txt | 51 ---------------- - .../{plugin => }/predicatesmonitor_p.cpp | 0 - .../{plugin => }/predicatesmonitor_p.h | 0 - .../contents/ui => qml}/DeviceItem.qml | 22 ++++--- - .../ui => qml}/FullRepresentation.qml | 0 - .../{package/contents/ui => qml}/main.qml | 9 ++- - .../{plugin => }/spacemonitor_p.cpp | 0 - .../{plugin => }/spacemonitor_p.h | 0 - 36 files changed, 65 insertions(+), 76 deletions(-) - rename applets/devicenotifier/{plugin => }/actioninterface.cpp (100%) - rename applets/devicenotifier/{plugin => }/actioninterface.h (100%) - rename applets/devicenotifier/{plugin => }/actions/defaultaction.cpp (100%) - rename applets/devicenotifier/{plugin => }/actions/defaultaction.h (100%) - rename applets/devicenotifier/{plugin => }/actions/mountaction.cpp (100%) - rename applets/devicenotifier/{plugin => }/actions/mountaction.h (100%) - rename applets/devicenotifier/{plugin => }/actions/mountandopenaction.cpp (100%) - rename applets/devicenotifier/{plugin => }/actions/mountandopenaction.h (100%) - rename applets/devicenotifier/{plugin => }/actions/openwithfilemanageraction.cpp (100%) - rename applets/devicenotifier/{plugin => }/actions/openwithfilemanageraction.h (100%) - rename applets/devicenotifier/{plugin => }/actions/unmountaction.cpp (100%) - rename applets/devicenotifier/{plugin => }/actions/unmountaction.h (100%) - rename applets/devicenotifier/{plugin => }/actionscontrol.cpp (100%) - rename applets/devicenotifier/{plugin => }/actionscontrol.h (100%) - rename applets/devicenotifier/{plugin => }/devicecontrol.cpp (100%) - rename applets/devicenotifier/{plugin => }/devicecontrol.h (100%) - rename applets/devicenotifier/{plugin => }/deviceerrormonitor_p.cpp (100%) - rename applets/devicenotifier/{plugin => }/deviceerrormonitor_p.h (100%) - rename applets/devicenotifier/{plugin => }/devicefiltercontrol.cpp (100%) - rename applets/devicenotifier/{plugin => }/devicefiltercontrol.h (100%) - rename applets/devicenotifier/{plugin => }/devicenotifications.notifyrc (100%) - rename applets/devicenotifier/{plugin => }/deviceserviceaction.cpp (100%) - rename applets/devicenotifier/{plugin => }/deviceserviceaction.h (100%) - rename applets/devicenotifier/{plugin => }/devicestatemonitor_p.cpp (100%) - rename applets/devicenotifier/{plugin => }/devicestatemonitor_p.h (100%) - rename applets/devicenotifier/{package/contents/config => }/main.xml (100%) - rename applets/devicenotifier/{package => }/metadata.json (99%) - delete mode 100644 applets/devicenotifier/plugin/CMakeLists.txt - rename applets/devicenotifier/{plugin => }/predicatesmonitor_p.cpp (100%) - rename applets/devicenotifier/{plugin => }/predicatesmonitor_p.h (100%) - rename applets/devicenotifier/{package/contents/ui => qml}/DeviceItem.qml (81%) - rename applets/devicenotifier/{package/contents/ui => qml}/FullRepresentation.qml (100%) - rename applets/devicenotifier/{package/contents/ui => qml}/main.qml (96%) - rename applets/devicenotifier/{plugin => }/spacemonitor_p.cpp (100%) - rename applets/devicenotifier/{plugin => }/spacemonitor_p.h (100%) - -diff --git a/applets/devicenotifier/CMakeLists.txt b/applets/devicenotifier/CMakeLists.txt -index bde4a38dd30..f336db13a69 100644 ---- a/applets/devicenotifier/CMakeLists.txt -+++ b/applets/devicenotifier/CMakeLists.txt -@@ -1,11 +1,55 @@ --add_subdirectory(plugin) -+# SPDX-FileCopyrightText: 2024 Fushan Wen -+# SPDX-License-Identifier: BSD-3-Clause - --ecm_qt_install_logging_categories( -- EXPORT APPLETS::DEVICENOTIFIER -- FILE applets/devicenotifier.categories -- DESTINATION ${KDE_INSTALL_LOGGINGCATEGORIESDIR} -+add_definitions(-DTRANSLATION_DOMAIN=\"plasma_applet_org.kde.plasma.devicenotifier\") -+ -+plasma_add_applet(org.kde.plasma.devicenotifier -+ QML_SOURCES -+ qml/DeviceItem.qml -+ qml/FullRepresentation.qml -+ qml/main.qml -+ CPP_SOURCES -+ actionscontrol.cpp -+ devicecontrol.cpp -+ spacemonitor_p.cpp -+ devicestatemonitor_p.cpp -+ deviceserviceaction.cpp -+ predicatesmonitor_p.cpp -+ deviceerrormonitor_p.cpp -+ actioninterface.cpp -+ devicefiltercontrol.cpp -+ actions/defaultaction.cpp -+ actions/mountandopenaction.cpp -+ actions/mountaction.cpp -+ actions/unmountaction.cpp -+ actions/openwithfilemanageraction.cpp -+ RESOURCES -+ main.xml -+ GENERATE_APPLET_CLASS -+) -+ -+target_link_libraries(org.kde.plasma.devicenotifier -+ PRIVATE -+ Qt::Qml -+ Plasma::Plasma -+ KF6::Solid -+ KF6::I18n -+ KF6::CoreAddons -+ KF6::Service -+ KF6::KIOCore -+ KF6::KIOGui # KIO::CommandLauncherJob -+ KF6::JobWidgets # KNotificationJobUiDelegate -+ KSysGuard::ProcessCore -+ KF6::Notifications - ) - --plasma_install_package(package org.kde.plasma.devicenotifier) -+ecm_qt_declare_logging_category(org.kde.plasma.devicenotifier -+ HEADER "devicenotifier_debug.h" -+ IDENTIFIER "APPLETS::DEVICENOTIFIER" -+ CATEGORY_NAME org.kde.applets.devicenotifier -+ DEFAULT_SEVERITY Warning -+ DESCRIPTION "Device Notifier applet" EXPORT "APPLETS::DEVICENOTIFIER" -+) - --install(FILES openWithFileManager.desktop DESTINATION ${KDE_INSTALL_DATADIR}/solid/actions ) -+install(FILES devicenotifications.notifyrc DESTINATION ${KDE_INSTALL_KNOTIFYRCDIR}) -+install(FILES openWithFileManager.desktop DESTINATION ${KDE_INSTALL_DATADIR}/solid/actions) -diff --git a/applets/devicenotifier/plugin/actioninterface.cpp b/applets/devicenotifier/actioninterface.cpp -similarity index 100% -rename from applets/devicenotifier/plugin/actioninterface.cpp -rename to applets/devicenotifier/actioninterface.cpp -diff --git a/applets/devicenotifier/plugin/actioninterface.h b/applets/devicenotifier/actioninterface.h -similarity index 100% -rename from applets/devicenotifier/plugin/actioninterface.h -rename to applets/devicenotifier/actioninterface.h -diff --git a/applets/devicenotifier/plugin/actions/defaultaction.cpp b/applets/devicenotifier/actions/defaultaction.cpp -similarity index 100% -rename from applets/devicenotifier/plugin/actions/defaultaction.cpp -rename to applets/devicenotifier/actions/defaultaction.cpp -diff --git a/applets/devicenotifier/plugin/actions/defaultaction.h b/applets/devicenotifier/actions/defaultaction.h -similarity index 100% -rename from applets/devicenotifier/plugin/actions/defaultaction.h -rename to applets/devicenotifier/actions/defaultaction.h -diff --git a/applets/devicenotifier/plugin/actions/mountaction.cpp b/applets/devicenotifier/actions/mountaction.cpp -similarity index 100% -rename from applets/devicenotifier/plugin/actions/mountaction.cpp -rename to applets/devicenotifier/actions/mountaction.cpp -diff --git a/applets/devicenotifier/plugin/actions/mountaction.h b/applets/devicenotifier/actions/mountaction.h -similarity index 100% -rename from applets/devicenotifier/plugin/actions/mountaction.h -rename to applets/devicenotifier/actions/mountaction.h -diff --git a/applets/devicenotifier/plugin/actions/mountandopenaction.cpp b/applets/devicenotifier/actions/mountandopenaction.cpp -similarity index 100% -rename from applets/devicenotifier/plugin/actions/mountandopenaction.cpp -rename to applets/devicenotifier/actions/mountandopenaction.cpp -diff --git a/applets/devicenotifier/plugin/actions/mountandopenaction.h b/applets/devicenotifier/actions/mountandopenaction.h -similarity index 100% -rename from applets/devicenotifier/plugin/actions/mountandopenaction.h -rename to applets/devicenotifier/actions/mountandopenaction.h -diff --git a/applets/devicenotifier/plugin/actions/openwithfilemanageraction.cpp b/applets/devicenotifier/actions/openwithfilemanageraction.cpp -similarity index 100% -rename from applets/devicenotifier/plugin/actions/openwithfilemanageraction.cpp -rename to applets/devicenotifier/actions/openwithfilemanageraction.cpp -diff --git a/applets/devicenotifier/plugin/actions/openwithfilemanageraction.h b/applets/devicenotifier/actions/openwithfilemanageraction.h -similarity index 100% -rename from applets/devicenotifier/plugin/actions/openwithfilemanageraction.h -rename to applets/devicenotifier/actions/openwithfilemanageraction.h -diff --git a/applets/devicenotifier/plugin/actions/unmountaction.cpp b/applets/devicenotifier/actions/unmountaction.cpp -similarity index 100% -rename from applets/devicenotifier/plugin/actions/unmountaction.cpp -rename to applets/devicenotifier/actions/unmountaction.cpp -diff --git a/applets/devicenotifier/plugin/actions/unmountaction.h b/applets/devicenotifier/actions/unmountaction.h -similarity index 100% -rename from applets/devicenotifier/plugin/actions/unmountaction.h -rename to applets/devicenotifier/actions/unmountaction.h -diff --git a/applets/devicenotifier/plugin/actionscontrol.cpp b/applets/devicenotifier/actionscontrol.cpp -similarity index 100% -rename from applets/devicenotifier/plugin/actionscontrol.cpp -rename to applets/devicenotifier/actionscontrol.cpp -diff --git a/applets/devicenotifier/plugin/actionscontrol.h b/applets/devicenotifier/actionscontrol.h -similarity index 100% -rename from applets/devicenotifier/plugin/actionscontrol.h -rename to applets/devicenotifier/actionscontrol.h -diff --git a/applets/devicenotifier/plugin/devicecontrol.cpp b/applets/devicenotifier/devicecontrol.cpp -similarity index 100% -rename from applets/devicenotifier/plugin/devicecontrol.cpp -rename to applets/devicenotifier/devicecontrol.cpp -diff --git a/applets/devicenotifier/plugin/devicecontrol.h b/applets/devicenotifier/devicecontrol.h -similarity index 100% -rename from applets/devicenotifier/plugin/devicecontrol.h -rename to applets/devicenotifier/devicecontrol.h -diff --git a/applets/devicenotifier/plugin/deviceerrormonitor_p.cpp b/applets/devicenotifier/deviceerrormonitor_p.cpp -similarity index 100% -rename from applets/devicenotifier/plugin/deviceerrormonitor_p.cpp -rename to applets/devicenotifier/deviceerrormonitor_p.cpp -diff --git a/applets/devicenotifier/plugin/deviceerrormonitor_p.h b/applets/devicenotifier/deviceerrormonitor_p.h -similarity index 100% -rename from applets/devicenotifier/plugin/deviceerrormonitor_p.h -rename to applets/devicenotifier/deviceerrormonitor_p.h -diff --git a/applets/devicenotifier/plugin/devicefiltercontrol.cpp b/applets/devicenotifier/devicefiltercontrol.cpp -similarity index 100% -rename from applets/devicenotifier/plugin/devicefiltercontrol.cpp -rename to applets/devicenotifier/devicefiltercontrol.cpp -diff --git a/applets/devicenotifier/plugin/devicefiltercontrol.h b/applets/devicenotifier/devicefiltercontrol.h -similarity index 100% -rename from applets/devicenotifier/plugin/devicefiltercontrol.h -rename to applets/devicenotifier/devicefiltercontrol.h -diff --git a/applets/devicenotifier/plugin/devicenotifications.notifyrc b/applets/devicenotifier/devicenotifications.notifyrc -similarity index 100% -rename from applets/devicenotifier/plugin/devicenotifications.notifyrc -rename to applets/devicenotifier/devicenotifications.notifyrc -diff --git a/applets/devicenotifier/plugin/deviceserviceaction.cpp b/applets/devicenotifier/deviceserviceaction.cpp -similarity index 100% -rename from applets/devicenotifier/plugin/deviceserviceaction.cpp -rename to applets/devicenotifier/deviceserviceaction.cpp -diff --git a/applets/devicenotifier/plugin/deviceserviceaction.h b/applets/devicenotifier/deviceserviceaction.h -similarity index 100% -rename from applets/devicenotifier/plugin/deviceserviceaction.h -rename to applets/devicenotifier/deviceserviceaction.h -diff --git a/applets/devicenotifier/plugin/devicestatemonitor_p.cpp b/applets/devicenotifier/devicestatemonitor_p.cpp -similarity index 100% -rename from applets/devicenotifier/plugin/devicestatemonitor_p.cpp -rename to applets/devicenotifier/devicestatemonitor_p.cpp -diff --git a/applets/devicenotifier/plugin/devicestatemonitor_p.h b/applets/devicenotifier/devicestatemonitor_p.h -similarity index 100% -rename from applets/devicenotifier/plugin/devicestatemonitor_p.h -rename to applets/devicenotifier/devicestatemonitor_p.h -diff --git a/applets/devicenotifier/package/contents/config/main.xml b/applets/devicenotifier/main.xml -similarity index 100% -rename from applets/devicenotifier/package/contents/config/main.xml -rename to applets/devicenotifier/main.xml -diff --git a/applets/devicenotifier/package/metadata.json b/applets/devicenotifier/metadata.json -similarity index 99% -rename from applets/devicenotifier/package/metadata.json -rename to applets/devicenotifier/metadata.json -index 0a330dfc189..77d7feac1cb 100644 ---- a/applets/devicenotifier/package/metadata.json -+++ b/applets/devicenotifier/metadata.json -@@ -115,7 +115,6 @@ - "desktop" - ], - "Icon": "device-notifier", -- "Id": "org.kde.plasma.devicenotifier", - "License": "GPL-2.0+", - "Name": "Disks & Devices", - "Name[ar]": "الأجهزة والأقراص", -diff --git a/applets/devicenotifier/plugin/CMakeLists.txt b/applets/devicenotifier/plugin/CMakeLists.txt -deleted file mode 100644 -index 34a3456d690..00000000000 ---- a/applets/devicenotifier/plugin/CMakeLists.txt -+++ /dev/null -@@ -1,51 +0,0 @@ --# SPDX-FileCopyrightText: 2024 Fushan Wen --# SPDX-License-Identifier: BSD-3-Clause -- --add_definitions(-DTRANSLATION_DOMAIN=\"plasma_applet_org.kde.plasma.devicenotifier\") -- --ecm_add_qml_module(devicenotifierplugin URI org.kde.plasma.private.devicenotifier GENERATE_PLUGIN_SOURCE) -- --target_sources(devicenotifierplugin -- PRIVATE -- actionscontrol.cpp actionscontrol.h -- devicecontrol.cpp devicecontrol.h -- spacemonitor_p.cpp spacemonitor_p.h -- devicestatemonitor_p.cpp devicestatemonitor_p.h -- deviceserviceaction.cpp deviceserviceaction.h -- predicatesmonitor_p.cpp predicatesmonitor_p.h -- deviceerrormonitor_p.cpp deviceerrormonitor_p.h -- actioninterface.cpp actioninterface.h -- devicefiltercontrol.cpp devicefiltercontrol.h -- actions/defaultaction.cpp actions/defaultaction.h -- actions/mountandopenaction.cpp actions/mountandopenaction.h -- actions/mountaction.cpp actions/mountaction.h -- actions/unmountaction.cpp actions/unmountaction.h -- actions/openwithfilemanageraction.cpp actions/openwithfilemanageraction.h --) -- --target_link_libraries(devicenotifierplugin -- PRIVATE -- Qt::Qml -- Plasma::Plasma -- KF6::Solid -- KF6::I18n -- KF6::CoreAddons -- KF6::Service -- KF6::KIOCore -- KF6::KIOGui # KIO::CommandLauncherJob -- KF6::JobWidgets # KNotificationJobUiDelegate -- KSysGuard::ProcessCore -- KF6::Notifications --) -- --ecm_qt_declare_logging_category(devicenotifierplugin -- HEADER "devicenotifier_debug.h" -- IDENTIFIER "APPLETS::DEVICENOTIFIER" -- CATEGORY_NAME org.kde.applets.devicenotifier -- DEFAULT_SEVERITY Warning -- DESCRIPTION "Device Notifier applet" EXPORT "APPLETS::DEVICENOTIFIER" --) -- --ecm_finalize_qml_module(devicenotifierplugin) -- --install(FILES devicenotifications.notifyrc DESTINATION ${KDE_INSTALL_KNOTIFYRCDIR}) -diff --git a/applets/devicenotifier/plugin/predicatesmonitor_p.cpp b/applets/devicenotifier/predicatesmonitor_p.cpp -similarity index 100% -rename from applets/devicenotifier/plugin/predicatesmonitor_p.cpp -rename to applets/devicenotifier/predicatesmonitor_p.cpp -diff --git a/applets/devicenotifier/plugin/predicatesmonitor_p.h b/applets/devicenotifier/predicatesmonitor_p.h -similarity index 100% -rename from applets/devicenotifier/plugin/predicatesmonitor_p.h -rename to applets/devicenotifier/predicatesmonitor_p.h -diff --git a/applets/devicenotifier/package/contents/ui/DeviceItem.qml b/applets/devicenotifier/qml/DeviceItem.qml -similarity index 81% -rename from applets/devicenotifier/package/contents/ui/DeviceItem.qml -rename to applets/devicenotifier/qml/DeviceItem.qml -index c19c9535b04..861996af45c 100644 ---- a/applets/devicenotifier/package/contents/ui/DeviceItem.qml -+++ b/applets/devicenotifier/qml/DeviceItem.qml -@@ -19,8 +19,6 @@ import org.kde.kirigami as Kirigami - - import org.kde.kquickcontrolsaddons - --import org.kde.plasma.private.devicenotifier as DN -- - PlasmaExtras.ExpandableListItem { - id: deviceItem - -@@ -41,18 +39,18 @@ PlasmaExtras.ExpandableListItem { - - property bool hasMessage: deviceItem.deviceErrorMessage !== "" - -- property bool isFree: deviceItem.deviceOperationResult !== DN.DevicesStateMonitor.Working && deviceItem.deviceOperationResult !== DN.DevicesStateMonitor.Checking && deviceItem.deviceOperationResult !== DN.DevicesStateMonitor.Repairing && deviceItem.deviceOperationResult !== DN.DevicesStateMonitor.NotPresent && !(deviceItem.deviceMounted === false && deviceItem.deviceOperationResult === DN.DevicesStateMonitor.Successful) -+ property bool isFree: deviceItem.deviceOperationResult !== DevicesStateMonitor.Working && deviceItem.deviceOperationResult !== DevicesStateMonitor.Checking && deviceItem.deviceOperationResult !== DevicesStateMonitor.Repairing && deviceItem.deviceOperationResult !== DevicesStateMonitor.NotPresent && !(deviceItem.deviceMounted === false && deviceItem.deviceOperationResult === DevicesStateMonitor.Successful) - - onDeviceOperationResultChanged: { - if (!popupIconTimer.running) { -- if (deviceItem.deviceOperationResult === DN.DevicesStateMonitor.Working) { -+ if (deviceItem.deviceOperationResult === DevicesStateMonitor.Working) { - if(deviceMounted){ - unmountTimer.restart(); - } -- } else if (deviceItem.deviceOperationResult === DN.DevicesStateMonitor.Successful) { -+ } else if (deviceItem.deviceOperationResult === DevicesStateMonitor.Successful) { - devicenotifier.popupIcon = "dialog-ok" - popupIconTimer.restart() -- } else if (deviceItem.deviceOperationResult === DN.DevicesStateMonitor.Unsuccessful) { -+ } else if (deviceItem.deviceOperationResult === DevicesStateMonitor.Unsuccessful) { - devicenotifier.popupIcon = "dialog-error" - popupIconTimer.restart() - } -@@ -80,7 +78,7 @@ PlasmaExtras.ExpandableListItem { - } else { - return "emblem-error" - } -- } else if (deviceItem.deviceOperationResult !== DN.DevicesStateMonitor.Working && deviceItem.deviceEmblems[0]) { -+ } else if (deviceItem.deviceOperationResult !== DevicesStateMonitor.Working && deviceItem.deviceEmblems[0]) { - return deviceItem.deviceEmblems[0] - } else { - return "" -@@ -93,16 +91,16 @@ PlasmaExtras.ExpandableListItem { - if (deviceItem.hasMessage) { - return deviceItem.deviceErrorMessage - } -- if (deviceItem.deviceOperationResult === DN.DevicesStateMonitor.Checking) { -+ if (deviceItem.deviceOperationResult === DevicesStateMonitor.Checking) { - return i18nc("Accessing is a less technical word for Mounting; translation should be short and mean \'Currently mounting this device\'", "Checking…") -- } else if (deviceItem.deviceOperationResult === DN.DevicesStateMonitor.Repairing) { -+ } else if (deviceItem.deviceOperationResult === DevicesStateMonitor.Repairing) { - return i18nc("Accessing is a less technical word for Mounting; translation should be short and mean \'Currently mounting this device\'", "Repairing…") -- } else if (deviceItem.deviceOperationResult !== DN.DevicesStateMonitor.Working) { -+ } else if (deviceItem.deviceOperationResult !== DevicesStateMonitor.Working) { - if (deviceItem.deviceFreeSpace > 0 && deviceItem.deviceSize > 0) { - return i18nc("@info:status Free disk space", "%1 free of %2", deviceItem.deviceFreeSpaceText, deviceItem.deviceSizeText) - } - return "" -- } else if (!deviceItem.deviceMounted && deviceItem.deviceOperationResult === DN.DevicesStateMonitor.Working) { -+ } else if (!deviceItem.deviceMounted && deviceItem.deviceOperationResult === DevicesStateMonitor.Working) { - return i18nc("Accessing is a less technical word for Mounting; translation should be short and mean \'Currently mounting this device\'", "Accessing…") - } else if (unmountTimer.running) { - // Unmounting; shown if unmount takes less than 1 second -@@ -139,7 +137,7 @@ PlasmaExtras.ExpandableListItem { - } - } - -- isBusy: deviceItem.deviceOperationResult === DN.DevicesStateMonitor.Working || deviceItem.deviceOperationResult === DN.DevicesStateMonitor.Checking || deviceItem.deviceOperationResult === DN.DevicesStateMonitor.Repairing -+ isBusy: deviceItem.deviceOperationResult === DevicesStateMonitor.Working || deviceItem.deviceOperationResult === DevicesStateMonitor.Checking || deviceItem.deviceOperationResult === DevicesStateMonitor.Repairing - - customExpandedViewContent: deviceActions !== undefined && deviceActions.rowCount() !== 0 && isFree ? actionComponent : null - -diff --git a/applets/devicenotifier/package/contents/ui/FullRepresentation.qml b/applets/devicenotifier/qml/FullRepresentation.qml -similarity index 100% -rename from applets/devicenotifier/package/contents/ui/FullRepresentation.qml -rename to applets/devicenotifier/qml/FullRepresentation.qml -diff --git a/applets/devicenotifier/package/contents/ui/main.qml b/applets/devicenotifier/qml/main.qml -similarity index 96% -rename from applets/devicenotifier/package/contents/ui/main.qml -rename to applets/devicenotifier/qml/main.qml -index 4061480becc..7fcd76a6d16 100644 ---- a/applets/devicenotifier/package/contents/ui/main.qml -+++ b/applets/devicenotifier/qml/main.qml -@@ -15,21 +15,20 @@ import org.kde.kirigami as Kirigami - - import org.kde.kcmutils // For KCMLauncher - import org.kde.config // KAuthorized --import org.kde.plasma.private.devicenotifier as DN - - PlasmoidItem { - id: devicenotifier - -- DN.DeviceFilterControl { -+ DeviceFilterControl { - id: filterModel - - filterType: { - if (Plasmoid.configuration.allDevices) { -- return DN.DeviceFilterControl.All -+ return DeviceFilterControl.All - } else if (Plasmoid.configuration.removableDevices) { -- return DN.DeviceFilterControl.Removable -+ return DeviceFilterControl.Removable - } else { -- return DN.DeviceFilterControl.Unremovable -+ return DeviceFilterControl.Unremovable - } - } - -diff --git a/applets/devicenotifier/plugin/spacemonitor_p.cpp b/applets/devicenotifier/spacemonitor_p.cpp -similarity index 100% -rename from applets/devicenotifier/plugin/spacemonitor_p.cpp -rename to applets/devicenotifier/spacemonitor_p.cpp -diff --git a/applets/devicenotifier/plugin/spacemonitor_p.h b/applets/devicenotifier/spacemonitor_p.h -similarity index 100% -rename from applets/devicenotifier/plugin/spacemonitor_p.h -rename to applets/devicenotifier/spacemonitor_p.h --- -GitLab - diff --git a/roles/kde/patches/plasma-workspace/pr5626.patch b/roles/kde/patches/plasma-workspace/pr5626.patch deleted file mode 100644 index 6ad487f..0000000 --- a/roles/kde/patches/plasma-workspace/pr5626.patch +++ /dev/null @@ -1,102 +0,0 @@ -From 9fabf42c39a25308739dd3483881cc889243bf58 Mon Sep 17 00:00:00 2001 -From: Kristen McWilliam -Date: Tue, 24 Jun 2025 17:44:33 -0400 -Subject: [PATCH] applets/notifications: Add actions to missed notifications - notification - -When a notification is displayed informing the user that notifications were missed while in Do Not -Disturb, now clicking the button or the notification itself will open the notifications applet to -show the missed notifications. - -BUG: 502423 ---- - .../package/contents/ui/main.qml | 10 ++++++ - libnotificationmanager/notifications.cpp | 34 ++++++++++++++----- - libnotificationmanager/notifications.h | 7 ++++ - 3 files changed, 42 insertions(+), 9 deletions(-) - -diff --git a/applets/notifications/package/contents/ui/main.qml b/applets/notifications/package/contents/ui/main.qml -index c96afa0558d..ab66a52f45e 100644 ---- a/applets/notifications/package/contents/ui/main.qml -+++ b/applets/notifications/package/contents/ui/main.qml -@@ -268,4 +268,14 @@ PlasmoidItem { - Component.onDestruction: { - Globals.forget(root); - } -+ -+ Connections { -+ target: Globals.popupNotificationsModel -+ -+ // The user requested to show the notifications popup, probably by -+ // clicking the "Missed Notifications in Do Not Disturb" notification. -+ function onShowNotificationsRequested(): void { -+ root.expanded = true; -+ } -+ } - } -diff --git a/libnotificationmanager/notifications.cpp b/libnotificationmanager/notifications.cpp -index 2cba1360b10..7b5cd9d68c4 100644 ---- a/libnotificationmanager/notifications.cpp -+++ b/libnotificationmanager/notifications.cpp -@@ -903,15 +903,31 @@ void Notifications::showInhibitionSummary(Urgency urgency, const QStringList &bl - return; - } - -- KNotification::event(u"inhibitionSummary"_s, -- i18ncp("@title", "Unread Notification", "Unread Notifications", inhibited), -- i18ncp("@info", -- "%1 notification was received while Do Not Disturb was active.", -- "%1 notifications were received while Do Not Disturb was active.", -- inhibited), -- u"preferences-desktop-notification-bell"_s, -- KNotification::CloseOnTimeout, -- u"libnotificationmanager"_s); -+ KNotification *notification = new KNotification(u"inhibitionSummary"_s); -+ notification->setTitle(i18ncp("@title", "Unread Notification", "Unread Notifications", inhibited)); -+ notification->setText(i18ncp("@info", -+ "%1 notification was received while Do Not Disturb was active.", -+ "%1 notifications were received while Do Not Disturb was active.", -+ inhibited)); -+ notification->setIconName(u"preferences-desktop-notification-bell"_s); -+ notification->setFlags(KNotification::CloseOnTimeout); -+ notification->setComponentName(u"libnotificationmanager"_s); -+ -+ const QString showNotificationsText = i18nc( // -+ "@action:button Show the notifications popup; translate this in as short a form as possible", -+ "Show Notifications"); -+ -+ const KNotificationAction *defaultShowNotificationsAction = notification->addDefaultAction(showNotificationsText); -+ connect(defaultShowNotificationsAction, &KNotificationAction::activated, this, [this]() { -+ Q_EMIT showNotificationsRequested(); -+ }); -+ -+ const KNotificationAction *showNotificationsAction = notification->addAction(showNotificationsText); -+ connect(showNotificationsAction, &KNotificationAction::activated, this, [this]() { -+ Q_EMIT showNotificationsRequested(); -+ }); -+ -+ notification->sendEvent(); - } - - QVariant Notifications::data(const QModelIndex &index, int role) const -diff --git a/libnotificationmanager/notifications.h b/libnotificationmanager/notifications.h -index e927c472c8f..8544b6271ae 100644 ---- a/libnotificationmanager/notifications.h -+++ b/libnotificationmanager/notifications.h -@@ -597,6 +597,13 @@ Q_SIGNALS: - void jobsPercentageChanged(); - void windowChanged(QWindow *window); - -+ /** -+ * Emitted when the user has requested to show the notifications popup. -+ * -+ * This is typically connected to a button in the "Missed Notifications in Do Not Disturb" notification. -+ */ -+ void showNotificationsRequested(); -+ - protected: - void classBegin() override; - void componentComplete() override; --- -GitLab - diff --git a/roles/kde/patches/plasma-workspace/pr5627.patch b/roles/kde/patches/plasma-workspace/pr5627.patch deleted file mode 100644 index dc61149..0000000 --- a/roles/kde/patches/plasma-workspace/pr5627.patch +++ /dev/null @@ -1,157 +0,0 @@ -From 1641ea3897d565d672e29a7524ce4171ddbcfd32 Mon Sep 17 00:00:00 2001 -From: Vlad Zahorodnii -Date: Wed, 25 Jun 2025 14:00:05 +0300 -Subject: [PATCH 1/2] shell: Set desktop ksplash stage when all desktop views - are ready - -If there are two outputs and the wallpaper for the first one is ready -but for the second one is not, the ksplash will be notified about the -desktop stage. - -This changes fixes checkAllDesktopsUiReady() so it sets the desktop -stage only if all desktop views are ready. ---- - shell/shellcorona.cpp | 28 +++++++++++++++------------- - shell/shellcorona.h | 2 +- - 2 files changed, 16 insertions(+), 14 deletions(-) - -diff --git a/shell/shellcorona.cpp b/shell/shellcorona.cpp -index 37d93d05353..65c8ccc272e 100644 ---- a/shell/shellcorona.cpp -+++ b/shell/shellcorona.cpp -@@ -1338,6 +1338,8 @@ void ShellCorona::removeDesktop(DesktopView *desktopView) - - desktopView->destroy(); - desktopView->containment()->reactToScreenChange(); -+ -+ checkAllDesktopsUiReady(); - } - - PanelView *ShellCorona::panelView(Plasma::Containment *containment) const -@@ -1529,22 +1531,22 @@ void ShellCorona::addOutput(QScreen *screen) - #endif - } - --void ShellCorona::checkAllDesktopsUiReady(bool ready) -+void ShellCorona::checkAllDesktopsUiReady() - { -- if (!ready) -+ const bool ready = std::ranges::all_of(std::as_const(m_desktopViewForScreen), [](const DesktopView *view) { -+ return view->containment()->isUiReady(); -+ }); -+ if (!ready) { - return; -- for (auto v : std::as_const(m_desktopViewForScreen)) { -- if (!v->containment()->isUiReady()) -- return; -- -- qCDebug(PLASMASHELL) << "Plasma Shell startup completed"; -- QDBusMessage ksplashProgressMessage = QDBusMessage::createMethodCall(QStringLiteral("org.kde.KSplash"), -- QStringLiteral("/KSplash"), -- QStringLiteral("org.kde.KSplash"), -- QStringLiteral("setStage")); -- ksplashProgressMessage.setArguments(QList() << QStringLiteral("desktop")); -- QDBusConnection::sessionBus().asyncCall(ksplashProgressMessage); - } -+ -+ qCDebug(PLASMASHELL) << "Plasma Shell startup completed"; -+ QDBusMessage ksplashProgressMessage = QDBusMessage::createMethodCall(QStringLiteral("org.kde.KSplash"), -+ QStringLiteral("/KSplash"), -+ QStringLiteral("org.kde.KSplash"), -+ QStringLiteral("setStage")); -+ ksplashProgressMessage.setArguments(QList() << QStringLiteral("desktop")); -+ QDBusConnection::sessionBus().asyncCall(ksplashProgressMessage); - } - - Plasma::Containment *ShellCorona::createContainmentForActivity(const QString &activity, int screenNum) -diff --git a/shell/shellcorona.h b/shell/shellcorona.h -index 0f544ca266f..7b6c6a559a1 100644 ---- a/shell/shellcorona.h -+++ b/shell/shellcorona.h -@@ -275,7 +275,7 @@ private: - DesktopView *desktopForScreen(QScreen *screen) const; - void setupWaylandIntegration(); - void executeSetupPlasmoidScript(Plasma::Containment *containment, Plasma::Applet *applet); -- void checkAllDesktopsUiReady(bool ready); -+ void checkAllDesktopsUiReady(); - void activateLauncherMenu(const QString &screenName); - void handleColorRequestedFromDBus(const QDBusMessage &msg); - --- -GitLab - - -From 00bd19ecaccbb10d5cfcc6006b06a9714c615741 Mon Sep 17 00:00:00 2001 -From: Vlad Zahorodnii -Date: Wed, 25 Jun 2025 14:03:28 +0300 -Subject: [PATCH 2/2] shell: Create panel views after desktop views are ready - -With the current code, the splash screen will be hidden as soon as the -wallpapers are loaded. - -However, the splash screnn is actually notified about the desktop stage -about 1-1.5 second later after the wallpaper plugin resets the loading -property. - -The reason for that is that the panel is loaded between -`wallpaper.loading = false` in the wallpaper and ShellCorona::checkAllDesktopsUiReady(). - -This change re-arranges the startup sequence so the panels are loaded -after the desktop views become ready. It reduces plasma startup time a bit. - -In long term, we should look for making panel loading as async as -possible so the main thread doesn't get blocked for too long. ---- - shell/shellcorona.cpp | 15 +++++---------- - 1 file changed, 5 insertions(+), 10 deletions(-) - -diff --git a/shell/shellcorona.cpp b/shell/shellcorona.cpp -index 65c8ccc272e..6a59683dba7 100644 ---- a/shell/shellcorona.cpp -+++ b/shell/shellcorona.cpp -@@ -868,10 +868,6 @@ void ShellCorona::load() - connect(m_screenPool, &ScreenPool::screenOrderChanged, this, &ShellCorona::handleScreenOrderChanged, Qt::UniqueConnection); - connect(m_screenPool, &ScreenPool::screenRemoved, this, &ShellCorona::handleScreenRemoved, Qt::UniqueConnection); - -- if (!m_waitingPanels.isEmpty()) { -- m_waitingPanelsTimer.start(); -- } -- - if (config()->isImmutable() || !KAuthorized::authorize(QStringLiteral("plasma/plasmashell/unlockedDesktop"))) { - setImmutability(Plasma::Types::SystemImmutable); - } else { -@@ -1517,11 +1513,6 @@ void ShellCorona::addOutput(QScreen *screen) - // in the list. We still don't want to have an invisible view added. - containment->reactToScreenChange(); - -- // were there any panels for this screen before it popped up? -- if (!m_waitingPanels.isEmpty()) { -- m_waitingPanelsTimer.start(); -- } -- - if (!m_screenReorderInProgress) { - Q_EMIT availableScreenRectChanged(m_screenPool->idForScreen(screen)); - } -@@ -1547,6 +1538,10 @@ void ShellCorona::checkAllDesktopsUiReady() - QStringLiteral("setStage")); - ksplashProgressMessage.setArguments(QList() << QStringLiteral("desktop")); - QDBusConnection::sessionBus().asyncCall(ksplashProgressMessage); -+ -+ if (!m_waitingPanels.isEmpty()) { -+ m_waitingPanelsTimer.start(); -+ } - } - - Plasma::Containment *ShellCorona::createContainmentForActivity(const QString &activity, int screenNum) -@@ -1604,7 +1599,7 @@ void ShellCorona::createWaitingPanels() - - QScreen *screen = m_screenPool->screenForId(requestedScreen); - DesktopView *desktopView = desktopForScreen(screen); -- if (!screen || !desktopView) { -+ if (!screen || !desktopView || !desktopView->containment()->isUiReady()) { - stillWaitingPanels << cont; - continue; - } --- -GitLab - diff --git a/roles/kde/patches/plasma-workspace/pr5657.patch b/roles/kde/patches/plasma-workspace/pr5657.patch deleted file mode 100644 index 71620a0..0000000 --- a/roles/kde/patches/plasma-workspace/pr5657.patch +++ /dev/null @@ -1,177 +0,0 @@ -From aa1e466e5f7684a8b624c34d466dda9d10a331d2 Mon Sep 17 00:00:00 2001 -From: Nate Graham -Date: Sun, 6 Jul 2025 23:20:47 -0400 -Subject: [PATCH 1/2] Improve UX of USB plug/unplug notifications when popup is - shown - -1. When plugged or unplugged, revoke the opposite notification if it's - visible. -2. Set the urgency to low so it won't clutter up the notification - history. ---- - devicenotifications/devicenotifications.cpp | 55 ++++++++++++++++----- - devicenotifications/devicenotifications.h | 5 ++ - 2 files changed, 48 insertions(+), 12 deletions(-) - -diff --git a/devicenotifications/devicenotifications.cpp b/devicenotifications/devicenotifications.cpp -index cc462f58f5..f326691c11 100644 ---- a/devicenotifications/devicenotifications.cpp -+++ b/devicenotifications/devicenotifications.cpp -@@ -15,6 +15,7 @@ - - #include - -+#include - #include - - K_PLUGIN_CLASS_WITH_JSON(KdedDeviceNotifications, "devicenotifications.json") -@@ -375,13 +376,28 @@ void KdedDeviceNotifications::onDeviceAdded(const UdevDevice &device) - return; - } - -- const QString text = !displayName.isEmpty() ? i18n("%1 has been plugged in.", displayName.toHtmlEscaped()) : i18n("A USB device has been plugged in."); -+ // If the user unplugged something and then immediately plugged it in again, -+ // there's no need to keep the unplug notification around. -+ if (m_usbDeviceRemovedNotification) { -+ m_usbDeviceRemovedNotification->close(); -+ } -+ -+ // Only show one of these at a time. We already suppressed creating a bunch -+ // in quick succession for the dock/hub use case, so any that are created -+ // over that time limit anyway are not necessary to stack up. -+ if (m_usbDeviceAddedNotification) { -+ m_usbDeviceAddedNotification->close(); -+ } -+ -+ const QString text = !displayName.isEmpty() ? i18n("%1 has been connected.", displayName.toHtmlEscaped()) : i18n("A USB device has been connected."); -+ -+ m_usbDeviceAddedNotification = new KNotification(QStringLiteral("deviceAdded")); -+ m_usbDeviceAddedNotification->setFlags(KNotification::DefaultEvent); -+ m_usbDeviceAddedNotification->setIconName(QStringLiteral("drive-removable-media-usb")); -+ m_usbDeviceAddedNotification->setTitle(i18nc("@title:notifications", "USB Device Detected")); -+ m_usbDeviceAddedNotification->setText(text); -+ m_usbDeviceAddedNotification->sendEvent(); - -- KNotification::event(QStringLiteral("deviceAdded"), -- i18nc("@title:notifications", "USB Device Detected"), -- text, -- QStringLiteral("drive-removable-media-usb"), -- KNotification::DefaultEvent); - m_deviceAddedTimer.start(); - } - -@@ -401,13 +417,28 @@ void KdedDeviceNotifications::onDeviceRemoved(const UdevDevice &device) - return; - } - -- const QString text = !displayName.isEmpty() ? i18n("%1 has been unplugged.", displayName.toHtmlEscaped()) : i18n("A USB device has been unplugged."); -+ // If the user plugged something in and then immediately unplugged it again, -+ // there's no need to keep the plug notification around. -+ if (m_usbDeviceAddedNotification) { -+ m_usbDeviceAddedNotification->close(); -+ } -+ -+ // Only show one of these at a time. We already suppressed removing a bunch -+ // in quick succession for the dock/hub use case, so any that are removed -+ // over that time limit anyway are not necessary to stack up. -+ if (m_usbDeviceRemovedNotification) { -+ m_usbDeviceRemovedNotification->close(); -+ } -+ -+ const QString text = !displayName.isEmpty() ? i18n("%1 has been disconnected.", displayName.toHtmlEscaped()) : i18n("A USB device has been disconnected."); -+ -+ m_usbDeviceRemovedNotification = new KNotification(QStringLiteral("deviceRemoved")); -+ m_usbDeviceRemovedNotification->setFlags(KNotification::DefaultEvent); -+ m_usbDeviceRemovedNotification->setIconName(QStringLiteral("drive-removable-media-usb")); -+ m_usbDeviceRemovedNotification->setTitle(i18nc("@title:notifications", "USB Device Went Away")); -+ m_usbDeviceRemovedNotification->setText(text); -+ m_usbDeviceRemovedNotification->sendEvent(); - -- KNotification::event(QStringLiteral("deviceRemoved"), -- i18nc("@title:notifications", "USB Device Removed"), -- text, -- QStringLiteral("drive-removable-media-usb"), -- KNotification::DefaultEvent); - m_deviceRemovedTimer.start(); - } - -diff --git a/devicenotifications/devicenotifications.h b/devicenotifications/devicenotifications.h -index 11334008b0..ab7e6b3ff9 100644 ---- a/devicenotifications/devicenotifications.h -+++ b/devicenotifications/devicenotifications.h -@@ -8,11 +8,13 @@ - - #include - #include -+#include - #include - #include - #include - - #include -+#include - - #include - -@@ -98,4 +100,7 @@ private: - - QTimer m_deviceAddedTimer; - QTimer m_deviceRemovedTimer; -+ -+ QPointer m_usbDeviceAddedNotification; -+ QPointer m_usbDeviceRemovedNotification; - }; --- -2.51.0 - - -From 05f72383fd0b29105f3b5494759500d26b38ffc2 Mon Sep 17 00:00:00 2001 -From: Nate Graham -Date: Fri, 11 Jul 2025 11:25:23 -0600 -Subject: [PATCH 2/2] Delete closed notifications too - -Closing is async; make sure we actually delete them when we want them -gone. ---- - devicenotifications/devicenotifications.cpp | 4 ++++ - 1 file changed, 4 insertions(+) - -diff --git a/devicenotifications/devicenotifications.cpp b/devicenotifications/devicenotifications.cpp -index f326691c11..987d65d805 100644 ---- a/devicenotifications/devicenotifications.cpp -+++ b/devicenotifications/devicenotifications.cpp -@@ -380,6 +380,7 @@ void KdedDeviceNotifications::onDeviceAdded(const UdevDevice &device) - // there's no need to keep the unplug notification around. - if (m_usbDeviceRemovedNotification) { - m_usbDeviceRemovedNotification->close(); -+ m_usbDeviceRemovedNotification = nullptr; - } - - // Only show one of these at a time. We already suppressed creating a bunch -@@ -387,6 +388,7 @@ void KdedDeviceNotifications::onDeviceAdded(const UdevDevice &device) - // over that time limit anyway are not necessary to stack up. - if (m_usbDeviceAddedNotification) { - m_usbDeviceAddedNotification->close(); -+ m_usbDeviceAddedNotification = nullptr; - } - - const QString text = !displayName.isEmpty() ? i18n("%1 has been connected.", displayName.toHtmlEscaped()) : i18n("A USB device has been connected."); -@@ -421,6 +423,7 @@ void KdedDeviceNotifications::onDeviceRemoved(const UdevDevice &device) - // there's no need to keep the plug notification around. - if (m_usbDeviceAddedNotification) { - m_usbDeviceAddedNotification->close(); -+ m_usbDeviceAddedNotification = nullptr; - } - - // Only show one of these at a time. We already suppressed removing a bunch -@@ -428,6 +431,7 @@ void KdedDeviceNotifications::onDeviceRemoved(const UdevDevice &device) - // over that time limit anyway are not necessary to stack up. - if (m_usbDeviceRemovedNotification) { - m_usbDeviceRemovedNotification->close(); -+ m_usbDeviceRemovedNotification = nullptr; - } - - const QString text = !displayName.isEmpty() ? i18n("%1 has been disconnected.", displayName.toHtmlEscaped()) : i18n("A USB device has been disconnected."); --- -2.51.0 - diff --git a/roles/kde/patches/plasma-workspace/pr5673.patch b/roles/kde/patches/plasma-workspace/pr5673.patch deleted file mode 100644 index c78dca1..0000000 --- a/roles/kde/patches/plasma-workspace/pr5673.patch +++ /dev/null @@ -1,392 +0,0 @@ -From 97c77a8e3259d77cb615dadd1c92185545513ebb Mon Sep 17 00:00:00 2001 -From: Harald Sitter -Date: Sun, 13 Jul 2025 16:06:08 +0200 -Subject: [PATCH 1/7] servicerunner: en_US spelling please - ---- - runners/services/servicerunner.cpp | 14 +++++++------- - runners/services/servicerunner.h | 4 ++-- - 2 files changed, 9 insertions(+), 9 deletions(-) - -diff --git a/runners/services/servicerunner.cpp b/runners/services/servicerunner.cpp -index 454cf4e99f..357558a77d 100644 ---- a/runners/services/servicerunner.cpp -+++ b/runners/services/servicerunner.cpp -@@ -296,7 +296,7 @@ private: - relevance += .09; - } - -- if (const auto foundIt = m_runner->m_favourites.constFind(service->desktopEntryName()); foundIt != m_runner->m_favourites.cend()) { -+ if (const auto foundIt = m_runner->m_favorites.constFind(service->desktopEntryName()); foundIt != m_runner->m_favorites.cend()) { - if (foundIt->isGlobal || foundIt->linkedActivities.contains(m_currentActivity)) { - qCDebug(RUNNER_SERVICES) << "entry is a favorite" << id << match.subtext() << relevance; - relevance *= 1.25; // Give favorites a relative boost, -@@ -423,7 +423,7 @@ ServiceRunner::ServiceRunner(QObject *parent, const KPluginMetaData &metaData) - }); - - connect(&m_kactivitiesWatcher, &ResultWatcher::resultUnlinked, [this](QString resource) { -- m_favourites.remove(resource.remove(".desktop"_L1)); -+ m_favorites.remove(resource.remove(".desktop"_L1)); - // In case it was only unlinked from one activity - processActivitiesResults(ResultSet(m_kactivitiesQuery | Terms::Url::contains(resource))); - }); -@@ -466,11 +466,11 @@ void ServiceRunner::processActivitiesResults(const ResultSet &results) - const static QLatin1String applicationScheme("applications"); - for (const ResultSet::Result &result : results) { - if (result.url().scheme() == applicationScheme) { -- m_favourites.insert(result.url().path().remove(QLatin1String(".desktop")), -- ActivityFavourite{ -- result.linkedActivities(), -- result.linkedActivities().contains(globalActivity), -- }); -+ m_favorites.insert(result.url().path().remove(QLatin1String(".desktop")), -+ ActivityFavorite{ -+ result.linkedActivities(), -+ result.linkedActivities().contains(globalActivity), -+ }); - } - } - } -diff --git a/runners/services/servicerunner.h b/runners/services/servicerunner.h -index e0507ea459..571d22d90c 100644 ---- a/runners/services/servicerunner.h -+++ b/runners/services/servicerunner.h -@@ -33,11 +33,11 @@ public: - void run(const KRunner::RunnerContext &context, const KRunner::QueryMatch &match) override; - void init() override; - -- struct ActivityFavourite { -+ struct ActivityFavorite { - QStringList linkedActivities; - bool isGlobal; - }; -- QMap m_favourites; -+ QMap m_favorites; - - protected: - void setupMatch(const KService::Ptr &service, KRunner::QueryMatch &action); --- -2.51.0 - - -From 537d0cf67d600cb40636f9aaef7db6957f002eb2 Mon Sep 17 00:00:00 2001 -From: Harald Sitter -Date: Sun, 13 Jul 2025 16:06:50 +0200 -Subject: [PATCH 2/7] servicerunner: use designated initializers - -makes code easier to read ---- - runners/services/servicerunner.cpp | 4 ++-- - 1 file changed, 2 insertions(+), 2 deletions(-) - -diff --git a/runners/services/servicerunner.cpp b/runners/services/servicerunner.cpp -index 357558a77d..2cade45b26 100644 ---- a/runners/services/servicerunner.cpp -+++ b/runners/services/servicerunner.cpp -@@ -468,8 +468,8 @@ void ServiceRunner::processActivitiesResults(const ResultSet &results) - if (result.url().scheme() == applicationScheme) { - m_favorites.insert(result.url().path().remove(QLatin1String(".desktop")), - ActivityFavorite{ -- result.linkedActivities(), -- result.linkedActivities().contains(globalActivity), -+ .linkedActivities = result.linkedActivities(), -+ .isGlobal = result.linkedActivities().contains(globalActivity), - }); - } - } --- -2.51.0 - - -From 2c5eb156410c022a50a5b6e08a6abc454dd49b83 Mon Sep 17 00:00:00 2001 -From: Harald Sitter -Date: Sun, 13 Jul 2025 16:09:37 +0200 -Subject: [PATCH 3/7] servicerunner: use ranges algorithms - -makes for nicer to read code ---- - runners/services/servicerunner.cpp | 10 +++++----- - 1 file changed, 5 insertions(+), 5 deletions(-) - -diff --git a/runners/services/servicerunner.cpp b/runners/services/servicerunner.cpp -index 2cade45b26..87b38a2da3 100644 ---- a/runners/services/servicerunner.cpp -+++ b/runners/services/servicerunner.cpp -@@ -46,15 +46,15 @@ int weightedLength(const QString &query) - - inline bool contains(const QString &result, const QList &queryList) - { -- return std::all_of(queryList.cbegin(), queryList.cend(), [&result](QStringView query) { -+ return std::ranges::all_of(queryList, [&result](QStringView query) { - return result.contains(query, Qt::CaseInsensitive); - }); - } - - inline bool contains(const QStringList &results, const QList &queryList) - { -- return std::all_of(queryList.cbegin(), queryList.cend(), [&results](QStringView query) { -- return std::any_of(results.cbegin(), results.cend(), [&query](QStringView result) { -+ return std::ranges::all_of(queryList, [&results](QStringView query) { -+ return std::ranges::any_of(results, [&query](QStringView result) { - return result.contains(query, Qt::CaseInsensitive); - }); - }); -@@ -327,7 +327,7 @@ private: - setupMatch(service, match); - - qreal relevance = 0.4; -- if (std::any_of(categories.begin(), categories.end(), [this](const QString &category) { -+ if (std::ranges::any_of(categories, [this](const QString &category) { - return category.compare(query, Qt::CaseInsensitive) == 0; - })) { - relevance = 0.6; -@@ -499,7 +499,7 @@ void ServiceRunner::run(const KRunner::RunnerContext & /*context*/, const KRunne - job = new KIO::ApplicationLauncherJob(service); - } else { - const auto actions = service->actions(); -- auto it = std::find_if(actions.begin(), actions.end(), [&actionName](const KServiceAction &action) { -+ auto it = std::ranges::find_if(actions, [&actionName](const KServiceAction &action) { - return action.name() == actionName; - }); - Q_ASSERT(it != actions.end()); --- -2.51.0 - - -From 0599abb0af9d1da43d8067dd59b8afad7c7be9c6 Mon Sep 17 00:00:00 2001 -From: Harald Sitter -Date: Sun, 13 Jul 2025 16:11:05 +0200 -Subject: [PATCH 4/7] servicerunner: put helper functions into anon namespace - -they are translation unit local after all ---- - runners/services/servicerunner.cpp | 4 ++++ - 1 file changed, 4 insertions(+) - -diff --git a/runners/services/servicerunner.cpp b/runners/services/servicerunner.cpp -index 87b38a2da3..baef7ae50f 100644 ---- a/runners/services/servicerunner.cpp -+++ b/runners/services/servicerunner.cpp -@@ -38,6 +38,8 @@ - #include "debug.h" - - using namespace Qt::StringLiterals; -+namespace -+{ - - int weightedLength(const QString &query) - { -@@ -60,6 +62,8 @@ inline bool contains(const QStringList &results, const QList &query - }); - } - -+} // namespace -+ - /** - * @brief Finds all KServices for a given runner query - */ --- -2.51.0 - - -From 2f81c3ab0520729ed4f97d666b5c74258eed149b Mon Sep 17 00:00:00 2001 -From: Harald Sitter -Date: Sun, 13 Jul 2025 16:12:37 +0200 -Subject: [PATCH 5/7] servicerunner: typos-- - ---- - runners/services/autotests/servicerunnertest.cpp | 4 ++-- - runners/services/servicerunner.cpp | 10 +++++----- - runners/services/servicerunner.h | 2 +- - 3 files changed, 8 insertions(+), 8 deletions(-) - -diff --git a/runners/services/autotests/servicerunnertest.cpp b/runners/services/autotests/servicerunnertest.cpp -index ecb8a4816c..fcfd3275ac 100644 ---- a/runners/services/autotests/servicerunnertest.cpp -+++ b/runners/services/autotests/servicerunnertest.cpp -@@ -27,7 +27,7 @@ private Q_SLOTS: - void initTestCase(); - void cleanupTestCase(); - -- void testExcutableExactMatch(); -+ void testExecutableExactMatch(); - void testKonsoleVsYakuakeComment(); - void testSystemSettings(); - void testSystemSettings2(); -@@ -76,7 +76,7 @@ void ServiceRunnerTest::cleanupTestCase() - { - } - --void ServiceRunnerTest::testExcutableExactMatch() -+void ServiceRunnerTest::testExecutableExactMatch() - { - const auto matches = launchQuery(QStringLiteral("Virtual Machine Manager ServiceRunnerTest")); // virt-manager.desktop - QVERIFY(std::any_of(matches.cbegin(), matches.cend(), [](const KRunner::QueryMatch &match) { -diff --git a/runners/services/servicerunner.cpp b/runners/services/servicerunner.cpp -index baef7ae50f..ced1b526ce 100644 ---- a/runners/services/servicerunner.cpp -+++ b/runners/services/servicerunner.cpp -@@ -125,7 +125,7 @@ private: - GenericName, - Comment, - }; -- qreal increaseMatchRelavance(const QString &serviceProperty, const QList &strList, Category category) -+ qreal increaseMatchRelevance(const QString &serviceProperty, const QList &strList, Category category) - { - // Increment the relevance based on all the words (other than the first) of the query list - qreal relevanceIncrement = 0; -@@ -273,20 +273,20 @@ private: - categoryRelevance = KRunner::QueryMatch::CategoryRelevance::Highest; - } else if (const int idx = name.indexOf(queryList[0], 0, Qt::CaseInsensitive); idx != -1) { - relevance = 0.8; -- relevance += increaseMatchRelavance(name, queryList, Category::Name); -+ relevance += increaseMatchRelevance(name, queryList, Category::Name); - if (idx == 0) { - relevance += 0.1; - categoryRelevance = KRunner::QueryMatch::CategoryRelevance::High; - } - } else if (const int idx = service->genericName().indexOf(queryList[0], 0, Qt::CaseInsensitive); idx != -1) { - relevance = 0.65; -- relevance += increaseMatchRelavance(service->genericName(), queryList, Category::GenericName); -+ relevance += increaseMatchRelevance(service->genericName(), queryList, Category::GenericName); - if (idx == 0) { - relevance += 0.05; - } - } else if (const int idx = service->comment().indexOf(queryList[0], 0, Qt::CaseInsensitive); idx != -1) { - relevance = 0.5; -- relevance += increaseMatchRelavance(service->comment(), queryList, Category::Comment); -+ relevance += increaseMatchRelevance(service->comment(), queryList, Category::Comment); - if (idx == 0) { - relevance += 0.05; - } -@@ -481,7 +481,7 @@ void ServiceRunner::processActivitiesResults(const ResultSet &results) - - void ServiceRunner::match(KRunner::RunnerContext &context) - { -- ServiceFinder finder(this, m_services, m_activitiesConsuer.currentActivity()); -+ ServiceFinder finder(this, m_services, m_activitiesConsumer.currentActivity()); - finder.match(context); - } - -diff --git a/runners/services/servicerunner.h b/runners/services/servicerunner.h -index 571d22d90c..96a110789b 100644 ---- a/runners/services/servicerunner.h -+++ b/runners/services/servicerunner.h -@@ -46,7 +46,7 @@ private: - void processActivitiesResults(const ResultSet &results); - const Query m_kactivitiesQuery; - const ResultWatcher m_kactivitiesWatcher; -- const KActivities::Consumer m_activitiesConsuer; -+ const KActivities::Consumer m_activitiesConsumer; - QList m_services; - bool m_matching = false; - }; --- -2.51.0 - - -From d25a269e9dbf6209ae51f94c298cb1ef640b045c Mon Sep 17 00:00:00 2001 -From: Harald Sitter -Date: Sun, 13 Jul 2025 16:14:53 +0200 -Subject: [PATCH 6/7] servicerunner: don't narrow qsizetype to int - -use auto instead since we don't actually care about their size anyway -since we only perform trivial >=0 checks ---- - runners/services/servicerunner.cpp | 10 +++++----- - 1 file changed, 5 insertions(+), 5 deletions(-) - -diff --git a/runners/services/servicerunner.cpp b/runners/services/servicerunner.cpp -index ced1b526ce..551717947f 100644 ---- a/runners/services/servicerunner.cpp -+++ b/runners/services/servicerunner.cpp -@@ -206,7 +206,7 @@ private: - static const auto specialArgs = {QStringLiteral("-qwindowtitle"), QStringLiteral("-qwindowicon"), QStringLiteral("--started-from-file")}; - - for (const auto &specialArg : specialArgs) { -- int index = resultingArgs.indexOf(specialArg); -+ auto index = resultingArgs.indexOf(specialArg); - if (index > -1) { - if (resultingArgs.count() > index) { - resultingArgs.removeAt(index); -@@ -271,20 +271,20 @@ private: - } else if (name.compare(query, Qt::CaseInsensitive) == 0) { - relevance = 1; - categoryRelevance = KRunner::QueryMatch::CategoryRelevance::Highest; -- } else if (const int idx = name.indexOf(queryList[0], 0, Qt::CaseInsensitive); idx != -1) { -+ } else if (const auto idx = name.indexOf(queryList[0], 0, Qt::CaseInsensitive); idx != -1) { - relevance = 0.8; - relevance += increaseMatchRelevance(name, queryList, Category::Name); - if (idx == 0) { - relevance += 0.1; - categoryRelevance = KRunner::QueryMatch::CategoryRelevance::High; - } -- } else if (const int idx = service->genericName().indexOf(queryList[0], 0, Qt::CaseInsensitive); idx != -1) { -+ } else if (const auto idx = service->genericName().indexOf(queryList[0], 0, Qt::CaseInsensitive); idx != -1) { - relevance = 0.65; - relevance += increaseMatchRelevance(service->genericName(), queryList, Category::GenericName); - if (idx == 0) { - relevance += 0.05; - } -- } else if (const int idx = service->comment().indexOf(queryList[0], 0, Qt::CaseInsensitive); idx != -1) { -+ } else if (const auto idx = service->comment().indexOf(queryList[0], 0, Qt::CaseInsensitive); idx != -1) { - relevance = 0.5; - relevance += increaseMatchRelevance(service->comment(), queryList, Category::Comment); - if (idx == 0) { -@@ -364,7 +364,7 @@ private: - } - seen(action); - -- const int matchIndex = action.text().indexOf(query, 0, Qt::CaseInsensitive); -+ const auto matchIndex = action.text().indexOf(query, 0, Qt::CaseInsensitive); - if (matchIndex < 0) { - continue; - } --- -2.51.0 - - -From 1a14af41b78a192d10fb5dcef93bba430872eab4 Mon Sep 17 00:00:00 2001 -From: Harald Sitter -Date: Sun, 13 Jul 2025 16:15:55 +0200 -Subject: [PATCH 7/7] servicerunner: remove inline noise - -functions defined inside a definition are always inline ---- - runners/services/servicerunner.cpp | 8 ++++---- - 1 file changed, 4 insertions(+), 4 deletions(-) - -diff --git a/runners/services/servicerunner.cpp b/runners/services/servicerunner.cpp -index 551717947f..eb9f02e74b 100644 ---- a/runners/services/servicerunner.cpp -+++ b/runners/services/servicerunner.cpp -@@ -92,22 +92,22 @@ public: - } - - private: -- inline void seen(const KService::Ptr &service) -+ void seen(const KService::Ptr &service) - { - m_seen.insert(service->exec()); - } - -- inline void seen(const KServiceAction &action) -+ void seen(const KServiceAction &action) - { - m_seen.insert(action.exec()); - } - -- inline bool hasSeen(const KService::Ptr &service) -+ bool hasSeen(const KService::Ptr &service) - { - return m_seen.contains(service->exec()); - } - -- inline bool hasSeen(const KServiceAction &action) -+ bool hasSeen(const KServiceAction &action) - { - return m_seen.contains(action.exec()); - } --- -2.51.0 - diff --git a/roles/kde/patches/plasma-workspace/pr5678.9.patch b/roles/kde/patches/plasma-workspace/pr5678.9.patch deleted file mode 100644 index 1c9421a..0000000 --- a/roles/kde/patches/plasma-workspace/pr5678.9.patch +++ /dev/null @@ -1,913 +0,0 @@ -From 312c215e717654e55fa48ec968f412201d2a5544 Mon Sep 17 00:00:00 2001 -From: Harald Sitter -Date: Mon, 14 Jul 2025 17:28:14 +0200 -Subject: [PATCH] servicerunner: fuzzy match - -use a bitap implementation instead of doing awkward contains dances. -this should lead to somewhat more reliable results, which are now more -comprehensively asserted in the unit test - -at the heart of this is a new fuzzyScore function that assigns a score -to a service vis a vis a query. this score is adjusted depending on -which field it is regarding (name > genericname > keywords). -this should hopefully ensure that a match against name outweighs most -other matches. all scores are eventually assembled into a final score -that gets used as match relevance ---- - runners/services/autotests/CMakeLists.txt | 3 + - runners/services/autotests/bitaptest.cpp | 70 +++++ - .../autotests/fixtures/audacity.desktop | 2 +- - .../fixtures/org.kde.discover.desktop | 17 ++ - .../autotests/fixtures/org.kde.kpat.desktop | 2 +- - .../services/autotests/servicerunnertest.cpp | 94 ++++-- - runners/services/bitap.h | 178 +++++++++++ - runners/services/levenshtein.h | 58 ++++ - runners/services/servicerunner.cpp | 286 +++++++++++------- - 9 files changed, 576 insertions(+), 134 deletions(-) - create mode 100644 runners/services/autotests/bitaptest.cpp - create mode 100755 runners/services/autotests/fixtures/org.kde.discover.desktop - create mode 100644 runners/services/bitap.h - create mode 100644 runners/services/levenshtein.h - -diff --git a/runners/services/autotests/CMakeLists.txt b/runners/services/autotests/CMakeLists.txt -index 04849a2928..ff7ec66634 100644 ---- a/runners/services/autotests/CMakeLists.txt -+++ b/runners/services/autotests/CMakeLists.txt -@@ -6,3 +6,6 @@ remove_definitions(-DQT_NO_CAST_FROM_ASCII) - ecm_add_test(servicerunnertest.cpp TEST_NAME servicerunnertest - LINK_LIBRARIES Qt::Test KF6::Service KF6::Runner) - krunner_configure_test(servicerunnertest krunner_services) -+ -+ecm_add_test(bitaptest.cpp TEST_NAME bitaptest -+ LINK_LIBRARIES Qt::Test) -diff --git a/runners/services/autotests/bitaptest.cpp b/runners/services/autotests/bitaptest.cpp -new file mode 100644 -index 0000000000..1a1cb856ec ---- /dev/null -+++ b/runners/services/autotests/bitaptest.cpp -@@ -0,0 +1,70 @@ -+// SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL -+// SPDX-FileCopyrightText: 2025 Harald Sitter -+ -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#include "../bitap.h" -+ -+class BitapTest : public QObject -+{ -+ Q_OBJECT -+private Q_SLOTS: -+ void initTestCase() -+ { -+ } -+ void cleanupTestCase() -+ { -+ } -+ -+ void testBitap() -+ { -+ using namespace Bitap; -+ // The macro has trouble with designated initializers, so we wrap them in (). -+ QCOMPARE(bitap(u"hello world", u"hello", 1), (Match{.end = 4, .distance = 0})); -+ QCOMPARE(bitap(u"wireshark", u"di", 1), (Match{.end = 1, .distance = 1})); -+ QCOMPARE(bitap(u"discover", u"disk", 1), (Match{.end = 2, .distance = 1})); -+ QCOMPARE(bitap(u"discover", u"disc", 1), (Match{.end = 3, .distance = 0})); -+ QCOMPARE(bitap(u"discover", u"scov", 1), (Match{.end = 5, .distance = 0})); -+ QCOMPARE(bitap(u"discover", u"diki", 1), std::nullopt); -+ QCOMPARE(bitap(u"discover", u"obo", 1), std::nullopt); -+ // With a hamming distance of 1 this may match because it is a single transposition. -+ QCOMPARE(bitap(u"discover", u"dicsover", 1), (Match{.end = 7, .distance = 1})); -+ // … but with three characters out of place things should not match. -+ QCOMPARE(bitap(u"discover", u"dicosver", 1), std::nullopt); -+ // pattern too long -+ QCOMPARE(bitap(u"discover", u" !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~", 1), std::nullopt); -+ // This is not a transposition as per Damerau–Levenshtein distance because the characters are not adjacent. -+ QCOMPARE(bitap(u"steam", u"skeap", 1), std::nullopt); -+ // Deletion required -+ QCOMPARE(bitap(u"discover", u"discover", 1), (Match{.end = 7, .distance = 0})); -+ QCOMPARE(bitap(u"discover", u"discovery", 1), (Match{.end = 7, .distance = 1})); -+ // Insertion required -+ QCOMPARE(bitap(u"discover", u"dicover", 1), (Match{.end = 7, .distance = 1})); -+ } -+ -+ void testScore() -+ { -+ using namespace Bitap; -+ // aperfectten has 10 big beautiful indexes. The maximum end is therefore 10. -+ QCOMPARE(score(u"aperfectten", Match{.end = 10, .distance = 0}, 1), 1.0); -+ QCOMPARE(score(u"aperfectten", Match{.end = 4, .distance = 0}, 1), 0.4); -+ QCOMPARE(score(u"aperfectten", Match{.end = 4, .distance = 1}, 1), 0.35); -+ QCOMPARE(score(u"aperfectten", Match{.end = 0, .distance = 0}, 0), 0); -+ QCOMPARE(score(u"aperfectten", Match{.end = 0, .distance = 0}, 1), 0); -+ QCOMPARE(score(u"aperfectten", Match{.end = 1, .distance = 1}, 1), 0.05); -+ -+ QCOMPARE(score(u"abc", Match{.end = 2, .distance = 1}, 1), 0.95); -+ // Ask for distance 0 but it has a distance so this is a super bad match. -+ QCOMPARE(score(u"abc", Match{.end = 2, .distance = 1}, 0), 0); -+ } -+}; -+ -+QTEST_MAIN(BitapTest) -+ -+#include "bitaptest.moc" -diff --git a/runners/services/autotests/fixtures/audacity.desktop b/runners/services/autotests/fixtures/audacity.desktop -index 7613d9f32f..05e1b9d929 100644 ---- a/runners/services/autotests/fixtures/audacity.desktop -+++ b/runners/services/autotests/fixtures/audacity.desktop -@@ -1,5 +1,5 @@ - [Desktop Entry] --Name=Audacity -+Name=Audacity ServiceRunnerTest - GenericName=Sound Editor - Comment=Record and edit audio files - Keywords=audio;sound;alsa;jack;editor; -diff --git a/runners/services/autotests/fixtures/org.kde.discover.desktop b/runners/services/autotests/fixtures/org.kde.discover.desktop -new file mode 100755 -index 0000000000..978b2b4152 ---- /dev/null -+++ b/runners/services/autotests/fixtures/org.kde.discover.desktop -@@ -0,0 +1,17 @@ -+# SPDX-FileCopyrightText: None -+# SPDX-License-Identifier: CC0-1.0 -+[Desktop Entry] -+Name=Discover ServiceRunnerTest -+Comment=Install and remove apps and add-ons -+MimeType=application/vnd.flatpak;application/vnd.flatpak.repo;application/vnd.flatpak.ref; -+Exec=plasma-discover %F -+Icon=plasmadiscover -+Type=Application -+X-DocPath=plasma-discover/index.html -+InitialPreference=5 -+NoDisplay=false -+Actions=Updates; -+SingleMainWindow=true -+GenericName=Software Center -+Categories=Qt;KDE;System; -+Keywords=program;software;store;repository;package;add;install;uninstall;remove;update;apps;applications;games;flatpak;snap;addons;add-ons;firmware; -diff --git a/runners/services/autotests/fixtures/org.kde.kpat.desktop b/runners/services/autotests/fixtures/org.kde.kpat.desktop -index 71d7fd2a89..3a91d89afe 100644 ---- a/runners/services/autotests/fixtures/org.kde.kpat.desktop -+++ b/runners/services/autotests/fixtures/org.kde.kpat.desktop -@@ -1,7 +1,7 @@ - # SPDX-FileCopyrightText: 2022 Alexander Lohnau - # SPDX-License-Identifier: CC0-1.0 - [Desktop Entry] --Name=KPatience -+Name=KPatience ServiceRunnerTest - Exec=true -qwindowtitle %c %u - Type=Application - Icon=kpat -diff --git a/runners/services/autotests/servicerunnertest.cpp b/runners/services/autotests/servicerunnertest.cpp -index fcfd3275ac..b911667a3b 100644 ---- a/runners/services/autotests/servicerunnertest.cpp -+++ b/runners/services/autotests/servicerunnertest.cpp -@@ -36,6 +36,10 @@ private Q_SLOTS: - void testINotifyUsage(); - void testSpecialArgs(); - void testEnv(); -+ void testDisassociation(); -+ void testMultipleKeywords(); -+ void testMultipleNameWords(); -+ void testDiscover(); - }; - - void ServiceRunnerTest::initTestCase() -@@ -86,8 +90,8 @@ void ServiceRunnerTest::testExecutableExactMatch() - - void ServiceRunnerTest::testKonsoleVsYakuakeComment() - { -- // Yakuake has konsole mentioned in comment, should be rated lower. -- const auto matches = launchQuery(QStringLiteral("kons")); -+ // Yakuake has konsole mentioned in comment, should not be listed (if it was it should be lower) -+ auto matches = launchQueryAndSort(QStringLiteral("kons")); - - bool konsoleFound = false; - bool yakuakeFound = false; -@@ -97,17 +101,10 @@ void ServiceRunnerTest::testKonsoleVsYakuakeComment() - continue; - } - -- if (match.text() == QLatin1String("Konsole ServiceRunnerTest")) { -- QCOMPARE(match.relevance(), 0.99); -- konsoleFound = true; -- } else if (match.text() == QLatin1String("Yakuake ServiceRunnerTest")) { -- // Rates lower because it doesn't have it in the name. -- QCOMPARE(match.relevance(), 0.59); -- yakuakeFound = true; -- } -- } -- QVERIFY(konsoleFound); -- QVERIFY(yakuakeFound); -+ QCOMPARE(texts, -+ QStringList({ -+ u"Konsole ServiceRunnerTest"_s, -+ })); - } - - void ServiceRunnerTest::testSystemSettings() -@@ -150,8 +147,9 @@ void ServiceRunnerTest::testSystemSettings2() - foreignSystemSettingsFound = true; - } - } -- QVERIFY(systemSettingsFound); -- QVERIFY(!foreignSystemSettingsFound); -+ -+ // The matched texts will contain much more because of the generic search term. Make sure our settings win. -+ QCOMPARE(texts.at(0), u"System Settings ServiceRunnerTest"_s); - } - - void ServiceRunnerTest::testCategories() -@@ -172,10 +170,6 @@ void ServiceRunnerTest::testCategories() - QVERIFY(std::none_of(matches.cbegin(), matches.cend(), [](const KRunner::QueryMatch &match) { - return match.text() == QLatin1String("Konsole ServiceRunnerTest"); - })); -- -- // Query too short to match any category -- matches = launchQuery(QStringLiteral("Dumm")); -- QVERIFY(matches.isEmpty()); - } - - void ServiceRunnerTest::testJumpListActions() -@@ -234,6 +228,68 @@ void ServiceRunnerTest::testEnv() - })); - } - -+void ServiceRunnerTest::testDisassociation() -+{ -+ // This test makes sure that we do not associate a service with a query that is not relevant. -+ auto matches = launchQueryAndSort(u"new laptop com"_s); // particularly notorious because it has two three letter words; 'com' is an incomplete word -+ -+ QStringList texts; -+ for (const auto &match : matches) { -+ texts.push_back(match.text()); -+ } -+ -+ QCOMPARE(texts, QStringList()); -+} -+ -+void ServiceRunnerTest::testMultipleKeywords() -+{ -+ auto matches = launchQueryAndSort(u"text editor programming"_s); -+ -+ QStringList texts; -+ for (const auto &match : matches) { -+ texts.push_back(match.text()); -+ } -+ -+ QCOMPARE(texts, -+ QStringList({ -+ u"Kate ServiceRunnerTest"_s, -+ })); -+} -+ -+void ServiceRunnerTest::testMultipleNameWords() -+{ -+ auto matches = launchQueryAndSort(u"system settings"_s); -+ -+ QStringList texts; -+ for (const auto &match : matches) { -+ if (!match.text().contains("ServiceRunnerTest"_L1)) { -+ continue; -+ } -+ texts.push_back(match.text()); -+ } -+ -+ QCOMPARE(texts, -+ QStringList({ -+ u"System Settings ServiceRunnerTest"_s, -+ })); -+} -+ -+void ServiceRunnerTest::testDiscover() -+{ -+ auto matches = launchQueryAndSort(u"disco"_s); -+ -+ QStringList texts; -+ for (const auto &match : matches) { -+ texts.push_back(match.text()); -+ } -+ -+ qDebug() << texts; -+ QCOMPARE(texts, -+ QStringList({ -+ u"Discover ServiceRunnerTest"_s, -+ })); -+} -+ - QTEST_MAIN(ServiceRunnerTest) - - #include "servicerunnertest.moc" -diff --git a/runners/services/bitap.h b/runners/services/bitap.h -new file mode 100644 -index 0000000000..a6aedb7eaf ---- /dev/null -+++ b/runners/services/bitap.h -@@ -0,0 +1,178 @@ -+// SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL -+// SPDX-FileCopyrightText: 2025 Harald Sitter -+ -+#pragma once -+ -+#include -+#include -+ -+#include -+#include -+#include -+ -+namespace Bitap -+{ -+ -+Q_DECLARE_LOGGING_CATEGORY(BITAP) -+Q_LOGGING_CATEGORY(BITAP, "org.kde.plasma.runner.services.bitap", QtWarningMsg) -+ -+struct Match { -+ qsizetype end; -+ qsizetype distance; -+ -+ bool operator==(const Match &other) const = default; -+}; -+ -+inline QDebug operator<<(QDebug dbg, const Bitap::Match &match) -+{ -+ dbg.nospace() << "Bitap::Match(" << match.end << ", " << match.distance << ")"; -+ return dbg; -+} -+ -+// Bitap is a bit of a complicated algorithm thanks to bitwise operations. I've opted to replace them with bitsets for readability. -+// It creates a patternMask based on all characters in the pattern. Basically each character gets assigned a representative bit. -+// e.g. in the pattern 'abc' the character 'a' would be 110, 'b' 101, 'c' 011. -+// This is a bit expensive up front but allows it to carry out everything else using bitwise operations. -+// For each match we set a matching bit in the bits vector. -+// Matching happens within a hamming distance, meaning up to `hammingDistance` characters can be out of place. -+inline std::optional bitap(const QStringView &name, const QStringView &pattern, int hammingDistance) -+{ -+ qCDebug(BITAP) << "Bitap called with name:" << name << "and pattern:" << pattern << "with hamming distance:" << hammingDistance; -+ const auto patternEndIndex = pattern.size() - 1; -+ if (name == pattern) { -+ return Match{.end = patternEndIndex, .distance = 0}; // Perfect match -+ } -+ -+ if (pattern.isEmpty() || name.isEmpty()) { -+ return std::nullopt; -+ } -+ -+ // Being a bitset we could have any number of bits, but practically we probably don't need more than 64, most bitaps I've seen even use 32. -+ constexpr auto maxMaskBits = 64; -+ using Mask = std::bitset; -+ using PatternMask = std::array::max()>; -+ -+ // The way bitap works is that each bit of the Mask represents a character position. Because of this we cannot match -+ // more characters than we have bits for. -+ // -1 because one bit is used for the result (I think) -+ if (pattern.size() >= qsizetype(Mask().size()) - 1) { -+ qCWarning(BITAP) << "Pattern is too long for bitap algorithm, max length is" << Mask().size() - 1; -+ return std::nullopt; -+ } -+ -+ const PatternMask patternMask = [&pattern, &name] { -+ PatternMask patternMask; -+ // The following is an optimized version of patternMask.fill(Mask().set()); to set all **necessary** bits to 1. -+ for (const auto &qchar : pattern) { -+ patternMask.at(qchar.unicode()).set(); -+ } -+ for (const auto &qchar : name) { -+ patternMask.at(qchar.unicode()).set(); -+ } -+ -+ for (int i = 0; i < pattern.size(); ++i) { -+ const auto char_ = pattern.at(i).unicode(); -+ patternMask.at(char_).reset(i); // unset the relevant index bits -+ } -+ -+ if (BITAP().isDebugEnabled()) { -+ for (const auto &i : pattern) { -+ const auto char_ = i.unicode(); -+ qCDebug(BITAP) << "Pattern mask for" << char_ << "is" << patternMask.at(char_).to_string(); -+ } -+ } -+ -+ return patternMask; -+ }(); -+ -+ Match match{ -+ .end = -1, // -1 means no match found for convenience -+ .distance = name.size(), -+ }; -+ -+ std::vector bits((hammingDistance + 1), Mask().set().reset(0)); -+ std::vector transpositions(bits.cbegin(), bits.cend()); -+ for (int i = 0; i < name.size(); ++i) { -+ const auto &char_ = name.at(i); -+ auto previousBit = bits[0]; -+ const auto mask = patternMask.at(char_.unicode()); -+ bits[0] |= mask; -+ bits[0] <<= 1; -+ -+ for (int j = 1; j <= hammingDistance; ++j) { -+ auto bit = bits[j]; -+ auto current = (bit | mask) << 1; -+ // https://en.wikipedia.org/wiki/Damerau%E2%80%93Levenshtein_distance -+ auto substitute = previousBit << 1; -+ auto delete_ = bits[j - 1] << 1; -+ auto insert = previousBit; -+ auto transpose = (transpositions[j - 1] | (mask << 1)) << 1; -+ bits[j] = current & substitute & transpose & delete_ & insert; -+ transpositions[j - 1] = (previousBit << 1) | mask; -+ previousBit = bit; -+ } -+ -+ if (BITAP().isDebugEnabled()) { -+ qCDebug(BITAP) << "After processing character" << char_ << "at index" << i; -+ for (const auto &bit : bits) { -+ qCDebug(BITAP) << "bit" << bit.to_string(); -+ } -+ } -+ -+ for (int k = 0; k <= hammingDistance; ++k) { -+ // If the bit at the end of the mask is 0, it means we have a match. -+ if (0 == (bits[k] & Mask().set(pattern.size()))) { -+ if (k < match.distance && match.end < i) { -+ qCDebug(BITAP) << "Match found at index" << i << "with hamming distance" << k << "better than previous match with distance" -+ << match.distance << "at index" << match.end; -+ match = { -+ .end = i, -+ .distance = k, -+ }; -+ } -+ // We do not return early because we want to find the best match, not just any. -+ // e.g. with a maximum distance of 1 `disc` could match `disc` either at index two with distance one, or at index three with distance zero. -+ } -+ } -+ } -+ -+ // Because we use a complete Damerau–Levenshtein distance the return value is a bit complicated. The trick is that the distance incurs a negative penalty -+ // in relation to the max distance. While an end that is closer to the real end is generally favorably. Combining the two into a single value -+ // would complicate the meaning of the return value to mean "approximate end with random penalty". This is garbage to reason about so instead we return -+ // both values and then assign them meaning in the score function. -+ if (match.end != -1) { -+ return match; -+ } -+ -+ qCDebug(BITAP) << "No match found for pattern" << pattern << "in name" << name; -+ return std::nullopt; -+} -+ -+inline qreal score(const QStringView &name, const auto &match, auto hammingDistance) -+{ -+ // Normalize the score to a value between 0.0 and 1.0 -+ // No distance means the score is directly correlated to the end index. The more characters matched the higher the score. -+ // Any distance will lower the score by a sub 0.1 margin. -+ -+ if (name.size() == 0) { -+ return 0.0; // No name, no score. -+ } -+ -+ const auto maxEnd = name.size() - 1; -+ const auto penalty = [&] { -+ if (hammingDistance <= 0) { -+ return 1.0; // No penalty for no distance -+ } -+ constexpr auto tenth = 10.0; -+ constexpr auto half = 2.0; -+ return qreal(match.distance) / qreal(hammingDistance) / tenth / half; -+ }(); -+ auto score = qreal(match.end) / qreal(maxEnd); -+ // Prevent underflows when the penalty is larger than the score. -+ score = std::max(0.0, score - penalty); -+ -+ Q_ASSERT(score >= 0.0 && score <= 1.0); -+ return score; -+} -+ -+} // namespace Bitap -diff --git a/runners/services/levenshtein.h b/runners/services/levenshtein.h -new file mode 100644 -index 0000000000..0efb960be3 ---- /dev/null -+++ b/runners/services/levenshtein.h -@@ -0,0 +1,58 @@ -+// SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL -+// SPDX-FileCopyrightText: 2025 Harald Sitter -+ -+#pragma -+ -+#include -+#include -+ -+namespace Levenshtein -+{ -+ -+inline int distance(const QStringView &name, const QStringView &query) -+{ -+ if (name == query) { -+ return 0; -+ } -+ -+ std::vector distance0(query.size() + 1, 0); -+ std::vector distance1(query.size() + 1, 0); -+ -+ for (int i = 0; i <= query.size(); ++i) { -+ distance0[i] = i; -+ } -+ -+ for (int i = 0; i < name.size(); ++i) { -+ distance1[0] = i + 1; -+ for (int j = 0; j < query.size(); ++j) { -+ const auto deletionCost = distance0[j + 1] + 1; -+ const auto insertionCost = distance1[j] + 1; -+ const auto substitutionCost = [&] { -+ if (name[i] == query[j]) { -+ return distance0[j]; -+ } -+ return distance0[j] + 1; -+ }(); -+ distance1[j + 1] = std::min({deletionCost, insertionCost, substitutionCost}); -+ } -+ std::swap(distance0, distance1); -+ } -+ return distance0[query.size()]; -+} -+ -+inline qreal score(const QStringView &name, int distance) -+{ -+ // Normalize the distance to a value between 0.0 and 1.0 -+ // The maximum distance is the length of the pattern. -+ // If the distance is 0, it means a perfect match, so we return 1.0. -+ // If the distance is equal to the length of the pattern, we return 0.0. -+ if (distance == 0) { -+ return 1.0; -+ } -+ if (distance >= name.size()) { -+ return 0.0; -+ } -+ return 1.0 - (qreal(distance) / qreal(name.size())); -+} -+ -+} // namespace Levenshtein -diff --git a/runners/services/servicerunner.cpp b/runners/services/servicerunner.cpp -index eb9f02e74b..3d5de8feb2 100644 ---- a/runners/services/servicerunner.cpp -+++ b/runners/services/servicerunner.cpp -@@ -1,7 +1,7 @@ - /* - SPDX-FileCopyrightText: 2006 Aaron Seigo - SPDX-FileCopyrightText: 2014 Vishesh Handa -- SPDX-FileCopyrightText: 2016-2020 Harald Sitter -+ SPDX-FileCopyrightText: 2016-2025 Harald Sitter - SPDX-FileCopyrightText: 2022-2023 Alexander Lohnau - - SPDX-License-Identifier: LGPL-2.0-only -@@ -21,6 +21,7 @@ - #include - - #include -+#include - #include - #include - #include -@@ -35,22 +36,130 @@ - #include - #include - -+#include "bitap.h" - #include "debug.h" -+#include "levenshtein.h" - - using namespace Qt::StringLiterals; - namespace - { - --int weightedLength(const QString &query) -+struct Score { -+ qreal value = 0.0; // The final score, it is the sum of all scores. -+ KRunner::QueryMatch::CategoryRelevance categoryRelevance = KRunner::QueryMatch::CategoryRelevance::Lowest; // The category relevance of the match. -+}; -+ -+struct ScoreCard { -+ Bitap::Match bitap; -+ qreal bitapScore; -+ int levenshtein; -+ qreal levenshteinScore; -+}; -+ -+QDebug operator<<(QDebug dbg, const ScoreCard &card) - { -- return KStringHandler::logicalLength(query); -+ dbg.nospace() << "Scorecard(" << "bitap: " << card.bitap << ", bitapScore: " << card.bitapScore << ", levenshtein: " << card.levenshtein -+ << ", levenshteinScore: " << card.levenshteinScore << ")"; -+ return dbg; - } - --inline bool contains(const QString &result, const QList &queryList) -+using ScoreCards = std::vector; -+ -+struct WeightedScoreCard { -+ ScoreCards cards; -+ qreal weight; -+}; -+ -+QDebug operator<<(QDebug dbg, const WeightedScoreCard &card) - { -- return std::ranges::all_of(queryList, [&result](QStringView query) { -- return result.contains(query, Qt::CaseInsensitive); -- }); -+ -+ dbg.nospace() << "WeightedCard["; -+ for (const auto &scoreCard : card.cards) { -+ dbg.nospace() << scoreCard; -+ if (&scoreCard != &card.cards.back()) { -+ dbg.nospace() << ", "; -+ } -+ } -+ dbg.nospace() << "]"; -+ return dbg; -+} -+ -+auto makeScores(const auto ¬NormalizedString, const auto &queryList) { -+ if (notNormalizedString.isEmpty()) { -+ return ScoreCards{}; // No string, no score. -+ } -+ -+ const auto string = notNormalizedString.toLower(); -+ -+ ScoreCards cards; -+ for (const auto &queryItem : queryList) { -+ constexpr auto maxDistance = 1; -+ const auto bitap = Bitap::bitap(string, queryItem, maxDistance); -+ if (!bitap) { -+ // One of the query items didn't match. This means the entire query is not a match -+ return ScoreCards{}; -+ } -+ -+ const auto bitapScore = Bitap::score(string, bitap.value(), maxDistance); -+ -+ // Mind that we give different levels of bonus. This is important to imply ordering within competing matches of the same "type". -+ // If we perfectly match that gives a bonus for not requiring any changes. -+ const auto noSubstitionBonus = Bitap::score(string, bitap.value(), 0) == 1.0 ? 4.0 : 1.0; -+ // If we match the entire length of the string that gets a bonus (disregarding distance, that was considered above). -+ const auto completeMatchBonus = bitap->end >= (queryItem.size() - 1) ? 3.0 : 1.0; -+ // If the string starts with the query item that gets a bonus. -+ const auto startsWithBonus = (string.startsWith(queryItem, Qt::CaseInsensitive)) ? 2.0 : 1.0; -+ -+ // Also consider the distance between the input and the query item. -+ // If one is "yolotrollingservice" and the other is "yolo" then we must consider them worse matches than say "yolotroll". -+ const auto levenshtein = Levenshtein::distance(string, queryItem); -+ -+ cards.emplace_back(ScoreCard{ -+ .bitap = *bitap, -+ .bitapScore = bitapScore + completeMatchBonus + noSubstitionBonus + startsWithBonus, -+ .levenshtein = levenshtein, -+ .levenshteinScore = Levenshtein::score(string, levenshtein), -+ }); -+ } -+ -+ return cards; -+}; -+ -+ -+auto makeScoreFromList(const auto &queryList, const QStringList &strings) { -+ // This turns the loop inside out. For every query item we must find a match in our keywords or we discard -+ ScoreCards cards; -+ // e.g. text,editor,programming -+ for (const auto &queryItem : queryList) { -+ // e.g. text;txt;editor;programming;programmer;development;developer;code; -+ auto found = false; -+ ScoreCards queryCards; -+ for (const auto &string : strings) { -+ auto stringCards = makeScores(string, QList{queryItem}); -+ if (stringCards.empty()) { -+ continue; // The combination didn't match. -+ } -+ for (auto &scoreCard : stringCards) { -+ if (scoreCard.levenshteinScore < 0.8) { -+ continue; // Not a good match, skip it. We are very strict with keywords -+ } -+ found = true; -+ queryCards.append_range(stringCards); -+ } -+ // We do not break because other string might also match, improving the score. -+ } -+ if (!found) { -+ // No item in strings matched the query item. This means the entire query is not a match. -+ return ScoreCards{}; -+ } -+ cards.append_range(queryCards); -+ } -+ return cards; -+}; -+ -+int weightedLength(const QString &query) -+{ -+ return KStringHandler::logicalLength(query); - } - - inline bool contains(const QStringList &results, const QList &queryList) -@@ -79,7 +188,7 @@ public: - - void match(KRunner::RunnerContext &context) - { -- query = context.query(); -+ query = context.query().toLower(); - // Splitting the query term to match using subsequences - queryList = QStringView(query).split(QLatin1Char(' ')); - weightedTermLength = weightedLength(query); -@@ -120,36 +229,6 @@ private: - return ret; - } - -- enum class Category { -- Name, -- GenericName, -- Comment, -- }; -- qreal increaseMatchRelevance(const QString &serviceProperty, const QList &strList, Category category) -- { -- // Increment the relevance based on all the words (other than the first) of the query list -- qreal relevanceIncrement = 0; -- -- for (int i = 1; i < strList.size(); ++i) { -- const auto &str = strList.at(i); -- if (category == Category::Name) { -- if (serviceProperty.contains(str, Qt::CaseInsensitive)) { -- relevanceIncrement += 0.01; -- } -- } else if (category == Category::GenericName) { -- if (serviceProperty.contains(str, Qt::CaseInsensitive)) { -- relevanceIncrement += 0.01; -- } -- } else if (category == Category::Comment) { -- if (serviceProperty.contains(str, Qt::CaseInsensitive)) { -- relevanceIncrement += 0.01; -- } -- } -- } -- -- return relevanceIncrement; -- } -- - void setupMatch(const KService::Ptr &service, KRunner::QueryMatch &match) - { - const QString name = service->name(); -@@ -219,96 +298,77 @@ private: - return resultingArgs.join(QLatin1Char(' ')); - } - -- void matchNameKeywordAndGenericName() -+ [[nodiscard]] std::optional fuzzyScore(KService::Ptr service) - { -- const auto nameKeywordAndGenericNameFilter = [this](const KService::Ptr &service) { -- // Name -- if (contains(service->name(), queryList)) { -- return true; -- } -- // If the term length is < 3, no real point searching the untranslated Name, Keywords and GenericName -- if (weightedTermLength < 3) { -- return false; -- } -- if (contains(service->untranslatedName(), queryList)) { -- return true; -- } -+ if (queryList.isEmpty()) { -+ return std::nullopt; // No query, no score. -+ } -+ -+ const auto name = service->name(); -+ if (name.compare(query, Qt::CaseInsensitive) == 0) { -+ // Absolute match. Can't get any better than this. -+ return Score{.value = std::numeric_limits::max(), .categoryRelevance = KRunner::QueryMatch::CategoryRelevance::Highest}; -+ } - -- // Keywords -- if (contains(service->keywords(), queryList)) { -- return true; -+ std::array weightedCards = { -+ WeightedScoreCard{.cards = makeScores(name, queryList), .weight = 1.0}, -+ WeightedScoreCard{.cards = makeScores(service->untranslatedName(), queryList), .weight = 0.8}, -+ WeightedScoreCard{.cards = makeScores(service->genericName(), queryList), .weight = 0.6}, -+ WeightedScoreCard{.cards = makeScoreFromList(queryList, service->keywords()), .weight = 0.1}, -+ }; -+ -+ if (RUNNER_SERVICES().isDebugEnabled()) { -+ qCDebug(RUNNER_SERVICES) << "+++++++ Weighted Cards for" << name; -+ for (const auto &weightedCard : weightedCards) { -+ qCDebug(RUNNER_SERVICES) << weightedCard; - } -- // GenericName -- if (contains(service->genericName(), queryList) || contains(service->untranslatedGenericName(), queryList)) { -- return true; -+ qCDebug(RUNNER_SERVICES) << "-------"; -+ } -+ -+ int scores = 1; // starts at 1 to avoid division by zero -+ qreal finalScore = 0.0; -+ for (const auto &weightedCard : weightedCards) { -+ if (weightedCard.cards.empty()) { -+ continue; // No scores, no match. - } -- // Comment -- if (contains(service->comment(), queryList)) { -- return true; -+ -+ qreal weightedScore = 0.0; -+ for (const auto &scoreCard : weightedCard.cards) { -+ weightedScore += (scoreCard.bitapScore + scoreCard.levenshteinScore) * weightedCard.weight; -+ scores++; - } - -- return false; -- }; -+ finalScore += weightedScore; -+ } -+ finalScore = finalScore / scores; // Average the score for this card - -- for (const KService::Ptr &service : m_services) { -- if (!nameKeywordAndGenericNameFilter(service) || disqualify(service)) { -- continue; -- } -+ qCDebug(RUNNER_SERVICES) << "Final score for" << name << "is" << finalScore; -+ if (finalScore > 0.0) { -+ return Score{.value = finalScore, .categoryRelevance = KRunner::QueryMatch::CategoryRelevance::Moderate}; -+ } - -- const QString id = service->storageId(); -- const QString name = service->name(); -+ return std::nullopt; -+ } - -- KRunner::QueryMatch::CategoryRelevance categoryRelevance = KRunner::QueryMatch::CategoryRelevance::Moderate; -- qreal relevance(0.6); -+ void matchNameKeywordAndGenericName() -+ { -+ static auto isTest = QStandardPaths::isTestModeEnabled(); - -- // If the term was < 3 chars and NOT at the beginning of the App's name, then chances are the user doesn't want that app -- if (weightedTermLength < 3) { -- if (name.startsWith(query, Qt::CaseInsensitive)) { -- relevance = 0.9; -- } else { -- continue; -- } -- } else if (name.compare(query, Qt::CaseInsensitive) == 0) { -- relevance = 1; -- categoryRelevance = KRunner::QueryMatch::CategoryRelevance::Highest; -- } else if (const auto idx = name.indexOf(queryList[0], 0, Qt::CaseInsensitive); idx != -1) { -- relevance = 0.8; -- relevance += increaseMatchRelevance(name, queryList, Category::Name); -- if (idx == 0) { -- relevance += 0.1; -- categoryRelevance = KRunner::QueryMatch::CategoryRelevance::High; -- } -- } else if (const auto idx = service->genericName().indexOf(queryList[0], 0, Qt::CaseInsensitive); idx != -1) { -- relevance = 0.65; -- relevance += increaseMatchRelevance(service->genericName(), queryList, Category::GenericName); -- if (idx == 0) { -- relevance += 0.05; -- } -- } else if (const auto idx = service->comment().indexOf(queryList[0], 0, Qt::CaseInsensitive); idx != -1) { -- relevance = 0.5; -- relevance += increaseMatchRelevance(service->comment(), queryList, Category::Comment); -- if (idx == 0) { -- relevance += 0.05; -- } -+ for (const KService::Ptr &service : m_services) { -+ if (isTest && !service->name().contains("ServiceRunnerTest"_L1)) { -+ continue; // Skip services that are not part of the test. - } - - KRunner::QueryMatch match(m_runner); -- match.setCategoryRelevance(categoryRelevance); -- setupMatch(service, match); -- if (service->categories().contains(QLatin1String("KDE"))) { -- qCDebug(RUNNER_SERVICES) << "found a kde thing" << id << match.subtext() << relevance; -- relevance += .09; -- } -- -- if (const auto foundIt = m_runner->m_favorites.constFind(service->desktopEntryName()); foundIt != m_runner->m_favorites.cend()) { -- if (foundIt->isGlobal || foundIt->linkedActivities.contains(m_currentActivity)) { -- qCDebug(RUNNER_SERVICES) << "entry is a favorite" << id << match.subtext() << relevance; -- relevance *= 1.25; // Give favorites a relative boost, -- } -+ auto score = fuzzyScore(service); -+ if (!score || disqualify(service)) { -+ continue; - } - -- qCDebug(RUNNER_SERVICES) << name << "is this relevant:" << relevance; -- match.setRelevance(relevance); -+ setupMatch(service, match); -+ match.setCategoryRelevance(score->categoryRelevance); -+ match.setRelevance(score->value); -+ qCDebug(RUNNER_SERVICES) << match.text() << "is this relevant:" << match.relevance() << "category relevance" << match.categoryRelevance(); - - matches << match; - } --- -2.51.0 - diff --git a/roles/kde/patches/plasma-workspace/pr5734.patch b/roles/kde/patches/plasma-workspace/pr5734.patch deleted file mode 100644 index ffe6605..0000000 --- a/roles/kde/patches/plasma-workspace/pr5734.patch +++ /dev/null @@ -1,133 +0,0 @@ -From 0168ee68b484995ed9398d31004dd80678ac7e37 Mon Sep 17 00:00:00 2001 -From: Kai Uwe Broulik -Date: Tue, 5 Aug 2025 12:57:00 +0200 -Subject: [PATCH] Close USB device added notification when devicenotifier pops - up - -The device notification is supposed to be super quick feedback that -"something" got detected. Once it has been identified as storage -device (disks spun up and what not), devicenotifier will show up -and then you have two popups. ---- - applets/devicenotifier/CMakeLists.txt | 1 + - applets/devicenotifier/devicefiltercontrol.cpp | 14 ++++++++++++++ - applets/devicenotifier/devicefiltercontrol.h | 1 + - applets/devicenotifier/qml/main.qml | 1 + - devicenotifications/devicenotifications.cpp | 8 ++++++++ - devicenotifications/devicenotifications.h | 3 +++ - 6 files changed, 28 insertions(+) - -diff --git a/applets/devicenotifier/CMakeLists.txt b/applets/devicenotifier/CMakeLists.txt -index d87964dc46d..fa415837e68 100644 ---- a/applets/devicenotifier/CMakeLists.txt -+++ b/applets/devicenotifier/CMakeLists.txt -@@ -31,6 +31,7 @@ plasma_add_applet(org.kde.plasma.devicenotifier - - target_link_libraries(org.kde.plasma.devicenotifier - PRIVATE -+ Qt::DBus - Qt::Qml - Plasma::Plasma - KF6::Solid -diff --git a/applets/devicenotifier/devicefiltercontrol.cpp b/applets/devicenotifier/devicefiltercontrol.cpp -index dfdb51a4304..f585eb2f063 100644 ---- a/applets/devicenotifier/devicefiltercontrol.cpp -+++ b/applets/devicenotifier/devicefiltercontrol.cpp -@@ -11,9 +11,14 @@ - #include "devicecontrol.h" - #include "devicestatemonitor_p.h" - -+#include -+#include -+ - #include - #include - -+using namespace Qt::Literals::StringLiterals; -+ - DeviceFilterControl::DeviceFilterControl(QObject *parent) - : QSortFilterProxyModel(parent) - , m_filterType(Removable) -@@ -54,6 +59,15 @@ void DeviceFilterControl::unmountAllRemovables() - qCDebug(APPLETS::DEVICENOTIFIER) << "Device Filter Control: unmount all removables function finished"; - } - -+void DeviceFilterControl::dismissUsbDeviceAddedNotification() -+{ -+ QDBusMessage msg = QDBusMessage::createMethodCall(u"org.kde.kded6"_s, -+ u"/modules/devicenotifications"_s, -+ u"org.kde.plasma.devicenotifications"_s, -+ u"dismissUsbDeviceAdded"_s); -+ QDBusConnection::sessionBus().call(msg, QDBus::NoBlock); -+} -+ - QBindable DeviceFilterControl::bindableLastUdi() - { - return &m_lastUdi; -diff --git a/applets/devicenotifier/devicefiltercontrol.h b/applets/devicenotifier/devicefiltercontrol.h -index e4c0a321657..fa6266fb197 100644 ---- a/applets/devicenotifier/devicefiltercontrol.h -+++ b/applets/devicenotifier/devicefiltercontrol.h -@@ -41,6 +41,7 @@ public: - Q_ENUM(DevicesType) - - Q_INVOKABLE void unmountAllRemovables(); -+ Q_INVOKABLE void dismissUsbDeviceAddedNotification(); - - explicit DeviceFilterControl(QObject *parent = nullptr); - ~DeviceFilterControl() override; -diff --git a/applets/devicenotifier/qml/main.qml b/applets/devicenotifier/qml/main.qml -index 7fcd76a6d16..c7fe6e6197d 100644 ---- a/applets/devicenotifier/qml/main.qml -+++ b/applets/devicenotifier/qml/main.qml -@@ -35,6 +35,7 @@ PlasmoidItem { - onLastUdiChanged: { - if (lastDeviceAdded) { - if (Plasmoid.configuration.popupOnNewDevice) { -+ filterModel.dismissUsbDeviceAddedNotification(); - devicenotifier.expanded = true; - fullRepresentationItem.spontaneousOpen = true; - } -diff --git a/devicenotifications/devicenotifications.cpp b/devicenotifications/devicenotifications.cpp -index 71ae0ff340e..196e28ca948 100644 ---- a/devicenotifications/devicenotifications.cpp -+++ b/devicenotifications/devicenotifications.cpp -@@ -323,6 +323,14 @@ void KdedDeviceNotifications::setupWaylandOutputListener() - wl_callback_add_listener(syncCallback, &syncCallbackListener, this); - } - -+void KdedDeviceNotifications::dismissUsbDeviceAdded() -+{ -+ if (m_usbDeviceAddedNotification) { -+ m_usbDeviceAddedNotification->close(); -+ m_usbDeviceAddedNotification = nullptr; -+ } -+} -+ - void KdedDeviceNotifications::notifyOutputAdded() - { - if (m_deviceAddedTimer.isActive()) { -diff --git a/devicenotifications/devicenotifications.h b/devicenotifications/devicenotifications.h -index ab7e6b3ff9b..75005193287 100644 ---- a/devicenotifications/devicenotifications.h -+++ b/devicenotifications/devicenotifications.h -@@ -77,6 +77,7 @@ private: - class KdedDeviceNotifications : public KDEDModule - { - Q_OBJECT -+ Q_CLASSINFO("D-Bus Interface", "org.kde.plasma.devicenotifications") - - public: - KdedDeviceNotifications(QObject *parent, const QVariantList &args); -@@ -84,6 +85,8 @@ public: - - void setupWaylandOutputListener(); - -+ Q_SCRIPTABLE void dismissUsbDeviceAdded(); -+ - private: - void notifyOutputAdded(); - void notifyOutputRemoved(); --- -GitLab - diff --git a/roles/kde/patches/plasma-workspace/pr5746.patch b/roles/kde/patches/plasma-workspace/pr5746.patch deleted file mode 100644 index d4eace1..0000000 --- a/roles/kde/patches/plasma-workspace/pr5746.patch +++ /dev/null @@ -1,33 +0,0 @@ -From f6ec2847358178a5b6ff0497e52d1e2be43d2a48 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Luan=20Vitor=20Simi=C3=A3o=20oliveira?= - -Date: Fri, 8 Aug 2025 14:41:49 -0300 -Subject: [PATCH] kcms/style: add special case for Adwaita gtk theme - -Allows the user to set the GTK theme to the default "Adwaita" -needs to be special cased because the theme is implemented in code. ---- - kcms/style/gtkthemesmodel.cpp | 7 ++++++- - 1 file changed, 6 insertions(+), 1 deletion(-) - -diff --git a/kcms/style/gtkthemesmodel.cpp b/kcms/style/gtkthemesmodel.cpp -index 002e87dbd0d..6dcdf4f1a1d 100644 ---- a/kcms/style/gtkthemesmodel.cpp -+++ b/kcms/style/gtkthemesmodel.cpp -@@ -35,7 +35,12 @@ void GtkThemesModel::load() - if (possibleThemeDirectory.dirName() == u"Breeze-Dark") { - continue; - } -- -+ if (possibleThemeDirectory.dirName() == u"Default") { -+ // Adwaita is a special case, since it is implemented inside GTK itself -+ // also setting gtk-theme-name to "Default" breaks dark theme -+ gtk3ThemesNames.insert(QStringLiteral("Adwaita"), possibleThemeDirectory.path()); -+ continue; -+ } - gtk3ThemesNames.insert(possibleThemeDirectory.dirName(), possibleThemeDirectory.path()); - } - } --- -GitLab - diff --git a/roles/kde/patches/plasma-workspace/pr5782.patch b/roles/kde/patches/plasma-workspace/pr5782.patch deleted file mode 100644 index a20c5e1..0000000 --- a/roles/kde/patches/plasma-workspace/pr5782.patch +++ /dev/null @@ -1,32 +0,0 @@ -From 4ab3894d75e1f9c6c7738a893a9b707ff0575953 Mon Sep 17 00:00:00 2001 -From: Nate Graham -Date: Thu, 21 Aug 2025 19:37:33 -0600 -Subject: [PATCH] notifications: make "you missed some notifications" - notification transient - -Its purpose is to direct you to the notifications history. If you're -seeing it *in* the notification history, its purpose has been bypassed -because you're already where it wanted to take you. - -Don't show it in the notification history. ---- - libnotificationmanager/notifications.cpp | 3 +++ - 1 file changed, 3 insertions(+) - -diff --git a/libnotificationmanager/notifications.cpp b/libnotificationmanager/notifications.cpp -index f68c342e7e9..128665f4de9 100644 ---- a/libnotificationmanager/notifications.cpp -+++ b/libnotificationmanager/notifications.cpp -@@ -917,6 +917,9 @@ void Notifications::showInhibitionSummary(Urgency urgency, const QStringList &bl - notification->setIconName(u"preferences-desktop-notification-bell"_s); - notification->setFlags(KNotification::CloseOnTimeout); - notification->setComponentName(u"libnotificationmanager"_s); -+ // Don't put it in the history because this doesn't make sense; if you're seeing it -+ // in the history, you're seeing the notifications it was telling you about! -+ notification->setHint(u"transient"_s, true); - - const QString showNotificationsText = i18nc("@action:button Show the notifications popup", "Show Notifications"); - --- -GitLab - diff --git a/roles/kde/patches/plasma-workspace/pr5788.patch b/roles/kde/patches/plasma-workspace/pr5788.patch deleted file mode 100644 index 96f3980..0000000 --- a/roles/kde/patches/plasma-workspace/pr5788.patch +++ /dev/null @@ -1,42 +0,0 @@ -From f0d2dd20803f2eee364d26656715b89e7c74366c Mon Sep 17 00:00:00 2001 -From: David Redondo -Date: Wed, 27 Aug 2025 09:40:43 +0200 -Subject: [PATCH] servicerunner: use vector::insert on compilers that don't - support append_range yet - -g++ only gained support for it with g++ 15 which was released this month. ---- - runners/services/servicerunner.cpp | 8 ++++++++ - 1 file changed, 8 insertions(+) - -diff --git a/runners/services/servicerunner.cpp b/runners/services/servicerunner.cpp -index 2ccc9a0af37..9e9d4e70a72 100644 ---- a/runners/services/servicerunner.cpp -+++ b/runners/services/servicerunner.cpp -@@ -142,7 +142,11 @@ auto makeScoreFromList(const auto &queryList, const QStringList &strings) { - continue; // Not a good match, skip it. We are very strict with keywords - } - found = true; -+#ifdef __cpp_lib_containers_ranges - queryCards.append_range(stringCards); -+#else -+ queryCards.insert(queryCards.end(), stringCards.cbegin(), stringCards.cend()); -+#endif - } - // We do not break because other string might also match, improving the score. - } -@@ -150,7 +154,11 @@ auto makeScoreFromList(const auto &queryList, const QStringList &strings) { - // No item in strings matched the query item. This means the entire query is not a match. - return ScoreCards{}; - } -+#ifdef __cpp_lib_containers_ranges - cards.append_range(queryCards); -+#else -+ cards.insert(cards.end(), queryCards.cbegin(), queryCards.cend()); -+#endif - } - return cards; - }; --- -GitLab - diff --git a/roles/kde/patches/plasma-workspace/pr5816.patch b/roles/kde/patches/plasma-workspace/pr5816.patch deleted file mode 100644 index 7a1925a..0000000 --- a/roles/kde/patches/plasma-workspace/pr5816.patch +++ /dev/null @@ -1,349 +0,0 @@ -From 77b3ec8cf4fb9414167bb15bcc3c69b1be6c2dbf Mon Sep 17 00:00:00 2001 -From: Vlad Zahorodnii -Date: Tue, 16 Sep 2025 15:52:29 +0300 -Subject: [PATCH 1/2] shell: Pass an xdg activation token from DesktopView to - KRunner - -This makes sure that krunner gets focused after starting to type while -a desktop view is focused when using medium or high focus stealing -prevention level. ---- - krunner/dbus/org.kde.krunner.App.xml | 4 ++ - krunner/view.cpp | 14 +++++++ - krunner/view.h | 1 + - shell/desktopview.cpp | 61 ++++++++++++++++++++++++++-- - shell/desktopview.h | 2 + - 5 files changed, 79 insertions(+), 3 deletions(-) - -diff --git a/krunner/dbus/org.kde.krunner.App.xml b/krunner/dbus/org.kde.krunner.App.xml -index 6553e9a1b56..e6926b1b63a 100644 ---- a/krunner/dbus/org.kde.krunner.App.xml -+++ b/krunner/dbus/org.kde.krunner.App.xml -@@ -17,5 +17,9 @@ - - - -+ -+ -+ -+ - - -diff --git a/krunner/view.cpp b/krunner/view.cpp -index afefe9ab486..bb7edd5d654 100644 ---- a/krunner/view.cpp -+++ b/krunner/view.cpp -@@ -263,6 +263,20 @@ void View::querySingleRunner(const QString &runnerName, const QString &term) - m_engine->rootObject()->setProperty("query", term); - } - -+void View::queryWithActivationToken(const QString &term, const QString &activationToken) -+{ -+ qputenv("XDG_ACTIVATION_TOKEN", activationToken.toUtf8()); -+ -+ if (!isVisible()) { -+ display(); -+ } else { -+ requestActivate(); -+ } -+ -+ m_engine->rootObject()->setProperty("singleRunner", QString()); -+ m_engine->rootObject()->setProperty("query", term); -+} -+ - bool View::pinned() const - { - return m_pinned; -diff --git a/krunner/view.h b/krunner/view.h -index 979c6d3b483..af1d477d6f3 100644 ---- a/krunner/view.h -+++ b/krunner/view.h -@@ -93,6 +93,7 @@ public Q_SLOTS: - void displayWithClipboardContents(); - void query(const QString &term); - void querySingleRunner(const QString &runnerName, const QString &term); -+ void queryWithActivationToken(const QString &term, const QString &activationToken); - - protected Q_SLOTS: - void loadConfig(); -diff --git a/shell/desktopview.cpp b/shell/desktopview.cpp -index 36e23149272..744176e6d3e 100644 ---- a/shell/desktopview.cpp -+++ b/shell/desktopview.cpp -@@ -23,6 +23,7 @@ - - #include - #include -+#include - #include - #include - #include -@@ -384,11 +385,54 @@ bool DesktopView::event(QEvent *e) - { - if (e->type() == QEvent::FocusOut) { - m_krunnerText.clear(); -+ -+ if (!m_krunnerFuture.isCanceled()) { -+ m_krunnerFuture.cancel(); -+ m_krunnerFuture = {}; -+ } - } - - return PlasmaQuick::ContainmentView::event(e); - } - -+class ActivationTokenRequest : public QObject -+{ -+ Q_OBJECT -+ -+public: -+ explicit ActivationTokenRequest(QWindow *window) -+ : m_serial(KWaylandExtras::lastInputSerial(window)) -+ { -+ m_promise.start(); -+ -+ connect(KWaylandExtras::self(), &KWaylandExtras::xdgActivationTokenArrived, this, [this](int serial, const QString &token) { -+ if (m_serial == serial) { -+ if (!m_promise.isCanceled()) { -+ m_promise.addResult(token); -+ } -+ m_promise.finish(); -+ delete this; -+ } -+ }); -+ KWaylandExtras::requestXdgActivationToken(window, m_serial, QString()); -+ } -+ -+ QFuture future() const -+ { -+ return m_promise.future(); -+ } -+ -+private: -+ QPromise m_promise; -+ int m_serial; -+}; -+ -+static QFuture fetchActivationToken(QWindow *window) -+{ -+ auto request = new ActivationTokenRequest(window); -+ return request->future(); -+} -+ - bool DesktopView::handleKRunnerTextInput(QKeyEvent *e) - { - // allow only Shift and GroupSwitch modifiers -@@ -408,12 +452,22 @@ bool DesktopView::handleKRunnerTextInput(QKeyEvent *e) - krunnerTextChanged = true; - } - if (krunnerTextChanged) { -- const QString interface(QStringLiteral("org.kde.krunner")); - if (!KAuthorized::authorize(QStringLiteral("run_command"))) { - return false; - } -- org::kde::krunner::App krunner(interface, QStringLiteral("/App"), QDBusConnection::sessionBus()); -- krunner.query(m_krunnerText); -+ if (KWindowSystem::isPlatformWayland()) { -+ if (!m_krunnerFuture.isCanceled()) { -+ m_krunnerFuture.cancel(); -+ } -+ m_krunnerFuture = fetchActivationToken(this); -+ m_krunnerFuture.then(this, [this](const QString &token) { -+ org::kde::krunner::App krunner(QStringLiteral("org.kde.krunner"), QStringLiteral("/App"), QDBusConnection::sessionBus()); -+ krunner.queryWithActivationToken(m_krunnerText, token); -+ }); -+ } else { -+ org::kde::krunner::App krunner(QStringLiteral("org.kde.krunner"), QStringLiteral("/App"), QDBusConnection::sessionBus()); -+ krunner.query(m_krunnerText); -+ } - return true; - } - return false; -@@ -596,4 +650,5 @@ void DesktopView::setAccentColorFromWallpaper(const QColor &accentColor) - QDBusConnection::sessionBus().send(applyAccentColor); - } - -+#include "desktopview.moc" - #include "moc_desktopview.cpp" -diff --git a/shell/desktopview.h b/shell/desktopview.h -index c2b642d9a15..9e152611a31 100644 ---- a/shell/desktopview.h -+++ b/shell/desktopview.h -@@ -10,6 +10,7 @@ - - #include - #include -+#include - #include - - #include -@@ -127,6 +128,7 @@ private: - QPointer m_screenToFollow; - LayerShellQt::Window *m_layerWindow = nullptr; - QString m_krunnerText; -+ QFuture m_krunnerFuture; - - // KRunner config - KConfigWatcher::Ptr m_configWatcher; --- -GitLab - - -From b3d97405cb5b0ee9859576309bd826dea6b74274 Mon Sep 17 00:00:00 2001 -From: Vlad Zahorodnii -Date: Wed, 17 Sep 2025 09:59:07 +0300 -Subject: [PATCH 2/2] shell: Use org.freedesktop.Application.ActivateAction to - pass xdg activation token to krunner - ---- - krunner/dbus/org.kde.krunner.App.xml | 4 ---- - krunner/main.cpp | 4 +++- - krunner/view.cpp | 14 ------------ - krunner/view.h | 1 - - shell/CMakeLists.txt | 4 ---- - shell/desktopview.cpp | 32 +++++++++++++++++++++++----- - 6 files changed, 30 insertions(+), 29 deletions(-) - -diff --git a/krunner/dbus/org.kde.krunner.App.xml b/krunner/dbus/org.kde.krunner.App.xml -index e6926b1b63a..6553e9a1b56 100644 ---- a/krunner/dbus/org.kde.krunner.App.xml -+++ b/krunner/dbus/org.kde.krunner.App.xml -@@ -17,9 +17,5 @@ - - - -- -- -- -- - - -diff --git a/krunner/main.cpp b/krunner/main.cpp -index 514c34b032f..02174bb2cda 100644 ---- a/krunner/main.cpp -+++ b/krunner/main.cpp -@@ -136,9 +136,11 @@ int main(int argc, char **argv) - parser.parse(arguments); - updateVisibility(); - }); -- QObject::connect(&service, &KDBusService::activateActionRequested, &view, [&view](const QString &action) { -+ QObject::connect(&service, &KDBusService::activateActionRequested, &view, [&view](const QString &action, const QVariant ¶meter) { - if (action == QLatin1String("RunClipboard")) { - view.displayWithClipboardContents(); -+ } else if (action == QLatin1String("Query")) { -+ view.query(parameter.toString()); - } - }); - -diff --git a/krunner/view.cpp b/krunner/view.cpp -index bb7edd5d654..afefe9ab486 100644 ---- a/krunner/view.cpp -+++ b/krunner/view.cpp -@@ -263,20 +263,6 @@ void View::querySingleRunner(const QString &runnerName, const QString &term) - m_engine->rootObject()->setProperty("query", term); - } - --void View::queryWithActivationToken(const QString &term, const QString &activationToken) --{ -- qputenv("XDG_ACTIVATION_TOKEN", activationToken.toUtf8()); -- -- if (!isVisible()) { -- display(); -- } else { -- requestActivate(); -- } -- -- m_engine->rootObject()->setProperty("singleRunner", QString()); -- m_engine->rootObject()->setProperty("query", term); --} -- - bool View::pinned() const - { - return m_pinned; -diff --git a/krunner/view.h b/krunner/view.h -index af1d477d6f3..979c6d3b483 100644 ---- a/krunner/view.h -+++ b/krunner/view.h -@@ -93,7 +93,6 @@ public Q_SLOTS: - void displayWithClipboardContents(); - void query(const QString &term); - void querySingleRunner(const QString &runnerName, const QString &term); -- void queryWithActivationToken(const QString &term, const QString &activationToken); - - protected Q_SLOTS: - void loadConfig(); -diff --git a/shell/CMakeLists.txt b/shell/CMakeLists.txt -index 96b91888f1b..814413ac827 100644 ---- a/shell/CMakeLists.txt -+++ b/shell/CMakeLists.txt -@@ -92,10 +92,6 @@ qt6_generate_wayland_protocol_client_sources(plasmashell - ${PLASMA_WAYLAND_PROTOCOLS_DIR}/plasma-shell.xml - ) - --set(krunner_xml ${plasma-workspace_SOURCE_DIR}/krunner/dbus/org.kde.krunner.App.xml) --qt_add_dbus_interface(plasma_shell_SRCS ${krunner_xml} krunner_interface) -- -- - target_sources(plasmashell PRIVATE ${plasma_shell_SRCS}) - - target_link_libraries(plasmashell PRIVATE -diff --git a/shell/desktopview.cpp b/shell/desktopview.cpp -index 744176e6d3e..9c3baa84b91 100644 ---- a/shell/desktopview.cpp -+++ b/shell/desktopview.cpp -@@ -6,12 +6,12 @@ - - #include "desktopview.h" - #include "containmentconfigview.h" --#include "krunner_interface.h" - #include "screenpool.h" - #include "shellcorona.h" - - #include - #include -+#include - #include - #include - #include -@@ -461,12 +461,34 @@ bool DesktopView::handleKRunnerTextInput(QKeyEvent *e) - } - m_krunnerFuture = fetchActivationToken(this); - m_krunnerFuture.then(this, [this](const QString &token) { -- org::kde::krunner::App krunner(QStringLiteral("org.kde.krunner"), QStringLiteral("/App"), QDBusConnection::sessionBus()); -- krunner.queryWithActivationToken(m_krunnerText, token); -+ auto message = QDBusMessage::createMethodCall(QStringLiteral("org.kde.krunner"), -+ QStringLiteral("/org/kde/krunner"), -+ QStringLiteral("org.freedesktop.Application"), -+ QStringLiteral("ActivateAction")); -+ message.setArguments({ -+ QStringLiteral("Query"), -+ QVariantList{ -+ m_krunnerText, -+ }, -+ QVariantMap{ -+ {QStringLiteral("activation-token"), token}, -+ }, -+ }); -+ QDBusConnection::sessionBus().asyncCall(message); - }); - } else { -- org::kde::krunner::App krunner(QStringLiteral("org.kde.krunner"), QStringLiteral("/App"), QDBusConnection::sessionBus()); -- krunner.query(m_krunnerText); -+ auto message = QDBusMessage::createMethodCall(QStringLiteral("org.kde.krunner"), -+ QStringLiteral("/org/kde/krunner"), -+ QStringLiteral("org.freedesktop.Application"), -+ QStringLiteral("ActivateAction")); -+ message.setArguments({ -+ QStringLiteral("Query"), -+ QVariantList{ -+ m_krunnerText, -+ }, -+ QVariantMap{}, -+ }); -+ QDBusConnection::sessionBus().asyncCall(message); - } - return true; - } --- -GitLab - diff --git a/roles/kde/patches/spectacle/patches.txt b/roles/kde/patches/spectacle/patches.txt deleted file mode 100644 index 587da02..0000000 --- a/roles/kde/patches/spectacle/patches.txt +++ /dev/null @@ -1,3 +0,0 @@ -Plasma 6.5.0: - -Pr 460 https://invent.kde.org/plasma/spectacle/-/merge_requests/460 diff --git a/roles/kde/patches/spectacle/pr460.patch b/roles/kde/patches/spectacle/pr460.patch deleted file mode 100644 index 63c6cfa..0000000 --- a/roles/kde/patches/spectacle/pr460.patch +++ /dev/null @@ -1,228 +0,0 @@ -From 97f209559c00acc1ea6d0736bb318ac0254a3e37 Mon Sep 17 00:00:00 2001 -From: Noah Davis -Date: Wed, 4 Jun 2025 16:06:06 -0400 -Subject: [PATCH 1/2] Say which shortcut can stop recording in recording - notification - -It will say Meta+R by default, but will show the shortcut used to initiate the recording instead if Meta+R isn't assigned to region recording. - -BUG: 505081 ---- - src/Platforms/VideoPlatform.cpp | 16 +++++++++++++++ - src/Platforms/VideoPlatform.h | 4 ++++ - src/Platforms/VideoPlatformWayland.cpp | 6 ++++-- - src/SpectacleCore.cpp | 28 +++++++++++++++++++++++++- - 4 files changed, 51 insertions(+), 3 deletions(-) - -diff --git a/src/Platforms/VideoPlatform.cpp b/src/Platforms/VideoPlatform.cpp -index 592539048..333692326 100644 ---- a/src/Platforms/VideoPlatform.cpp -+++ b/src/Platforms/VideoPlatform.cpp -@@ -69,6 +69,11 @@ VideoPlatform::RecordingState VideoPlatform::recordingState() const - return m_recordingState; - } - -+VideoPlatform::RecordingMode VideoPlatform::recordingMode() const -+{ -+ return m_recordingMode; -+} -+ - void VideoPlatform::setRecordingState(RecordingState state) - { - if (state == m_recordingState) { -@@ -91,9 +96,20 @@ void VideoPlatform::setRecordingState(RecordingState state) - m_elapsedTimer.invalidate(); - m_basicTimer.stop(); - } -+ if (state != RecordingState::Recording) { -+ setRecordingMode(NoRecordingModes); -+ } - m_recordingState = state; - Q_EMIT recordingStateChanged(state); - Q_EMIT recordedTimeChanged(); - } - -+void VideoPlatform::setRecordingMode(RecordingMode mode) -+{ -+ if (m_recordingMode == mode) { -+ return; -+ } -+ m_recordingMode = mode; -+} -+ - #include "moc_VideoPlatform.cpp" -diff --git a/src/Platforms/VideoPlatform.h b/src/Platforms/VideoPlatform.h -index 4f5fba720..eea81e32a 100644 ---- a/src/Platforms/VideoPlatform.h -+++ b/src/Platforms/VideoPlatform.h -@@ -129,8 +129,11 @@ public: - - RecordingState recordingState() const; - -+ RecordingMode recordingMode() const; -+ - protected: - void setRecordingState(RecordingState state); -+ void setRecordingMode(RecordingMode mode); - void timerEvent(QTimerEvent *event) override; - - public Q_SLOTS: -@@ -157,6 +160,7 @@ private: - QBasicTimer m_basicTimer; - qint64 m_recordedTime = 0; - RecordingState m_recordingState = RecordingState::NotRecording; -+ RecordingMode m_recordingMode = RecordingMode::NoRecordingModes; - }; - - Q_DECLARE_OPERATORS_FOR_FLAGS(VideoPlatform::RecordingModes) -diff --git a/src/Platforms/VideoPlatformWayland.cpp b/src/Platforms/VideoPlatformWayland.cpp -index 648f8adbf..984ede0eb 100644 ---- a/src/Platforms/VideoPlatformWayland.cpp -+++ b/src/Platforms/VideoPlatformWayland.cpp -@@ -245,11 +245,12 @@ void VideoPlatformWayland::startRecording(const QUrl &fileUrl, RecordingMode rec - m_recorder->setNodeId(0); - - Q_ASSERT(stream); -- connect(stream, &ScreencastingStream::created, this, [this, stream] { -+ connect(stream, &ScreencastingStream::created, this, [this, stream, recordingMode] { - m_recorder->setNodeId(stream->nodeId()); - if (!m_recorder->output().isEmpty()) { - m_recorder->start(); - } -+ setRecordingMode(recordingMode); - setRecordingState(VideoPlatform::RecordingState::Recording); - }); - connect(stream, &ScreencastingStream::failed, this, [this](const QString &error) { -@@ -302,7 +303,7 @@ void VideoPlatformWayland::startRecording(const QUrl &fileUrl, RecordingMode rec - m_recorder->start(); - } - -- connect(m_recorder.get(), &PipeWireRecord::stateChanged, this, [this] { -+ connect(m_recorder.get(), &PipeWireRecord::stateChanged, this, [this, recordingMode] { - if (m_recorder->state() == PipeWireRecord::Idle) { - m_memoryTimer.stop(); - if (recordingState() != RecordingState::NotRecording && recordingState() != RecordingState::Finished) { -@@ -311,6 +312,7 @@ void VideoPlatformWayland::startRecording(const QUrl &fileUrl, RecordingMode rec - } - } else if (m_recorder->state() == PipeWireRecord::Recording) { - m_memoryTimer.start(5000, Qt::CoarseTimer, this); -+ setRecordingMode(recordingMode); - setRecordingState(VideoPlatform::RecordingState::Recording); - } else if (m_recorder->state() == PipeWireRecord::Rendering) { - m_memoryTimer.stop(); -diff --git a/src/SpectacleCore.cpp b/src/SpectacleCore.cpp -index 8033751c9..aa2e8c6e1 100644 ---- a/src/SpectacleCore.cpp -+++ b/src/SpectacleCore.cpp -@@ -281,7 +281,33 @@ SpectacleCore::SpectacleCore(QObject *parent) - SpectacleCore::instance()->finishRecording(); - }); - const auto messageTitle = i18nc("recording notification title", "Spectacle is Recording"); -- const auto messageBody = i18nc("recording notification message", "Click the system tray icon to finish recording"); -+ auto getSimpleDefaultShortcut = [] { -+ const auto shortcuts = KGlobalAccel::self()->shortcut(ShortcutActions::self()->recordRegionAction()); -+ if (shortcuts.contains(QKeySequence{Qt::META | Qt::Key_R})) { -+ return QKeySequence{Qt::META | Qt::Key_R}; -+ } -+ return QKeySequence{}; -+ }; -+ auto getShortcut = [](const auto &list) { -+ auto it = std::find_if(list.cbegin(), list.cend(), [](const QKeySequence &shortcut) { -+ return !shortcut.isEmpty(); -+ }); -+ return it != list.cend() ? *it : QKeySequence{}; -+ }; -+ QKeySequence stopShortcut = getSimpleDefaultShortcut(); -+ if (stopShortcut.isEmpty()) { -+ auto mode = m_videoPlatform->recordingMode(); -+ if (mode == VideoPlatform::Screen) { -+ stopShortcut = getShortcut(KGlobalAccel::self()->shortcut(ShortcutActions::self()->recordScreenAction())); -+ } else if (mode == VideoPlatform::Window) { -+ stopShortcut = getShortcut(KGlobalAccel::self()->shortcut(ShortcutActions::self()->recordWindowAction())); -+ } else if (mode == VideoPlatform::Region) { -+ stopShortcut = getShortcut(KGlobalAccel::self()->shortcut(ShortcutActions::self()->recordRegionAction())); -+ } -+ } -+ const auto messageBody = stopShortcut.isEmpty() -+ ? i18nc("recording notification message without shortcut", "To finish the recording, click the pulsing red System Tray icon.") -+ : xi18nc("recording notification message with shortcut", "To finish the recording, click the pulsing red System Tray icon or press %1.", stopShortcut.toString(QKeySequence::NativeText)); - auto notification = new KNotification(u"notification"_s, KNotification::CloseOnTimeout | KNotification::DefaultEvent, this); - notification->setTitle(messageTitle); - notification->setText(messageBody); --- -GitLab - - -From 7fd2fe158408b6285f9855f7fcf34e77d4ecb764 Mon Sep 17 00:00:00 2001 -From: Noah Davis -Date: Thu, 12 Jun 2025 16:12:48 -0400 -Subject: [PATCH 2/2] Rename shortcuts to show that they can start or stop - recording - -BUG: 505081 ---- - desktop/org.kde.spectacle.desktop.cmake | 6 +++--- - src/ShortcutActions.cpp | 6 +++--- - 2 files changed, 6 insertions(+), 6 deletions(-) - -diff --git a/desktop/org.kde.spectacle.desktop.cmake b/desktop/org.kde.spectacle.desktop.cmake -index 4e36b18e6..800fcb62b 100644 ---- a/desktop/org.kde.spectacle.desktop.cmake -+++ b/desktop/org.kde.spectacle.desktop.cmake -@@ -474,7 +474,7 @@ Exec=${KDE_INSTALL_FULL_BINDIR}/spectacle -u - X-KDE-Shortcuts=Meta+Ctrl+Print - - [Desktop Action RecordRegion] --Name=Record Rectangular Region -+Name=Start/Stop Region Recording - Name[ar]=يسجل منطقة مستطيلة - Name[az]=Düzbucaqlı sahəni yazmaq - Name[bg]=Заснемане на правоъгълен регион -@@ -522,7 +522,7 @@ Exec=${KDE_INSTALL_FULL_BINDIR}/spectacle -R region - X-KDE-Shortcuts=Meta+Shift+R,Meta+R - - [Desktop Action RecordScreen] --Name=Record Screen -+Name=Start/Stop Screen Recording - Name[ar]=سجّل الشاشة - Name[az]=Ekranı yazmaq - Name[bg]=Запис на екрана -@@ -570,7 +570,7 @@ Exec=${KDE_INSTALL_FULL_BINDIR}/spectacle -R screen - X-KDE-Shortcuts=Meta+Alt+R - - [Desktop Action RecordWindow] --Name=Record Window -+Name=Start/Stop Window Recording - Name[ar]=سجل النافذة - Name[az]=Pəncərəni yazmaq - Name[bg]=Запис на прозорец -diff --git a/src/ShortcutActions.cpp b/src/ShortcutActions.cpp -index ee73bcfe5..a1464d06d 100644 ---- a/src/ShortcutActions.cpp -+++ b/src/ShortcutActions.cpp -@@ -73,19 +73,19 @@ ShortcutActions::ShortcutActions() - mActions.addAction(action->objectName(), action); - } - { -- QAction *action = new QAction(i18nc("@action global shortcut", "Record Screen"), &mActions); -+ QAction *action = new QAction(i18nc("@action global shortcut", "Start/Stop Screen Recording"), &mActions); - action->setObjectName(u"RecordScreen"_s); - action->setProperty("isConfigurationAction", true); - mActions.addAction(action->objectName(), action); - } - { -- QAction *action = new QAction(i18nc("@action global shortcut", "Record Window"), &mActions); -+ QAction *action = new QAction(i18nc("@action global shortcut", "Start/Stop Window Recording"), &mActions); - action->setObjectName(u"RecordWindow"_s); - action->setProperty("isConfigurationAction", true); - mActions.addAction(action->objectName(), action); - } - { -- QAction *action = new QAction(i18nc("@action global shortcut", "Record Rectangular Region"), &mActions); -+ QAction *action = new QAction(i18nc("@action global shortcut", "Start/Stop Region Recording"), &mActions); - action->setObjectName(u"RecordRegion"_s); - action->setProperty("isConfigurationAction", true); - mActions.addAction(action->objectName(), action); --- -GitLab -