Kde/patches: remove patches that are in plasma 6.5

This commit is contained in:
Toast 2025-10-24 01:07:37 +02:00
parent 2c49a7318b
commit e15a9c0c01
31 changed files with 0 additions and 7021 deletions

View file

@ -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 {

View file

@ -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
+

View file

@ -1,2 +0,0 @@
Plasma 6.5.0:
Pr 218 https://invent.kde.org/plasma/bluedevil/-/merge_requests/218

View file

@ -1,53 +0,0 @@
From ddbf7452c6f2fe8849de723175710dbf39242422 Mon Sep 17 00:00:00 2001
From: Nate Graham <nate@kde.org>
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

View file

@ -1,2 +0,0 @@
Plasma 6.5.0:
Pr 545 https://invent.kde.org/plasma/breeze/-/merge_requests/545

View file

@ -1,333 +0,0 @@
From 38567c2f2d2d0f8524e42224dff53a929cbf086a Mon Sep 17 00:00:00 2001
From: Kai Uwe Broulik <kde@privat.broulik.de>
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<QToolButton *>(widget)) {
- _toolButtonEngine->registerWidget(widget, AnimationHover | AnimationFocus);
- _widgetStateEngine->registerWidget(widget, AnimationHover | AnimationFocus);
+ if (auto toolButton = qobject_cast<QToolButton *>(widget)) {
+ _toolButtonEngine->registerWidget(toolButton, AnimationHover | AnimationFocus);
+ _widgetStateEngine->registerWidget(toolButton, AnimationHover | AnimationFocus);
} else if (qobject_cast<QCheckBox *>(widget) || qobject_cast<QRadioButton *>(widget)) {
_widgetStateEngine->registerWidget(widget, AnimationHover | AnimationFocus | AnimationPressed);
- } else if (qobject_cast<QAbstractButton *>(widget)) {
+ } else if (auto button = qobject_cast<QAbstractButton *>(widget)) {
// register to toolbox engine if needed
- if (qobject_cast<QToolBox *>(widget->parent())) {
- _toolBoxEngine->registerWidget(widget);
+ if (auto toolBox = qobject_cast<QToolBox *>(widget->parent())) {
+ _toolBoxEngine->registerWidget(toolBox);
}
- _widgetStateEngine->registerWidget(widget, AnimationHover | AnimationFocus);
+ _widgetStateEngine->registerWidget(button, AnimationHover | AnimationFocus);
}
// groupboxes
else if (QGroupBox *groupBox = qobject_cast<QGroupBox *>(widget)) {
if (groupBox->isCheckable()) {
- _widgetStateEngine->registerWidget(widget, AnimationHover | AnimationFocus);
+ _widgetStateEngine->registerWidget(groupBox, AnimationHover | AnimationFocus);
}
}
// sliders
- else if (qobject_cast<QScrollBar *>(widget)) {
- _scrollBarEngine->registerWidget(widget, AnimationHover | AnimationFocus);
- } else if (qobject_cast<QSlider *>(widget)) {
- _widgetStateEngine->registerWidget(widget, AnimationHover | AnimationFocus);
- } else if (qobject_cast<QDial *>(widget)) {
- _dialEngine->registerWidget(widget, AnimationHover | AnimationFocus);
+ else if (auto scrollBar = qobject_cast<QScrollBar *>(widget)) {
+ _scrollBarEngine->registerWidget(scrollBar, AnimationHover | AnimationFocus);
+ } else if (auto slider = qobject_cast<QSlider *>(widget)) {
+ _widgetStateEngine->registerWidget(slider, AnimationHover | AnimationFocus);
+ } else if (auto dial = qobject_cast<QDial *>(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<QHeaderView *>(widget)) {
- _headerViewEngine->registerWidget(widget);
+ else if (auto headerView = qobject_cast<QHeaderView *>(widget)) {
+ _headerViewEngine->registerWidget(headerView);
}
// lists
- else if (qobject_cast<QAbstractItemView *>(widget)) {
- _inputWidgetEngine->registerWidget(widget, AnimationHover | AnimationFocus);
+ else if (auto itemView = qobject_cast<QAbstractItemView *>(widget)) {
+ _inputWidgetEngine->registerWidget(itemView, AnimationHover | AnimationFocus);
}
// tabbar
- else if (qobject_cast<QTabBar *>(widget)) {
- _tabBarEngine->registerWidget(widget);
+ else if (auto tabBar = qobject_cast<QTabBar *>(widget)) {
+ _tabBarEngine->registerWidget(tabBar);
}
// scrollarea
else if (QAbstractScrollArea *scrollArea = qobject_cast<QAbstractScrollArea *>(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 <kde@privat.broulik.de>
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 <kde@privat.broulik.de>
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<QToolButton *>(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<QQuickItem *>(option->styleObject)) {
_windowManager->registerQuickItem(item);
+ _animations->registerWidget(item);
return true;
}
}
--
GitLab

View file

@ -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

File diff suppressed because it is too large Load diff

View file

@ -1,26 +0,0 @@
From a1868942fbc2ad5de4c053dd17335dff34d81779 Mon Sep 17 00:00:00 2001
From: Vlad Zahorodnii <vlad.zahorodnii@kde.org>
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

View file

@ -1,669 +0,0 @@
From 9f6c92806490d662117575a766f9fcb01e253344 Mon Sep 17 00:00:00 2001
From: Xaver Hugl <xaver.hugl@kde.org>
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<QList<Output *>> &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<DpmsInputEventFilter> 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<PlasmaWindowActivationInterface> activation;
if (showNotify) {
- activation = waylandServer()->plasmaActivationFeedback()->createActivation(appId);
+ m_lastToken = newToken;
+ m_activation = waylandServer()->plasmaActivationFeedback()->createActivation(appId);
}
- m_currentActivationToken = std::make_unique<ActivationToken>(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<const SurfaceInterface> surface;
- uint serial;
- SeatInterface *seat;
- QString applicationId;
- bool showNotify;
- std::unique_ptr<PlasmaWindowActivationInterface> activation;
- };
- std::unique_ptr<ActivationToken> m_currentActivationToken;
+ QString m_lastToken;
+ std::unique_ptr<PlasmaWindowActivationInterface> m_activation;
};
}
--
GitLab
From 6c673a479412902a14c06046199f976e2192dc65 Mon Sep 17 00:00:00 2001
From: Xaver Hugl <xaver.hugl@kde.org>
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 <xaver.hugl@kde.org>
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 <xaver.hugl@kde.org>
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 <KWayland/Client/seat.h>
#include <KWayland/Client/surface.h>
#include <netwm.h>
#include <xcb/xcb_icccm.h>
@@ -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<std::unique_ptr<KWayland::Client::Surface>> surfaces;
+ std::vector<std::unique_ptr<Test::XdgToplevel>> shellSurfaces;
+ std::vector<Window *> 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<XdgActivationToken> createToken();
+};
+
struct Connection
{
static std::unique_ptr<Connection> setup(AdditionalWaylandInterfaces interfaces = AdditionalWaylandInterfaces());
@@ -757,6 +786,7 @@ struct Connection
std::unique_ptr<ColorManagerV1> colorManager;
std::unique_ptr<FifoManagerV1> fifoManager;
std::unique_ptr<PresentationTime> presentationTime;
+ std::unique_ptr<XdgActivation> 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> Connection::setup(AdditionalWaylandInterfaces flags)
c->presentationTime = std::make_unique<PresentationTime>(*c->registry, name, version);
}
}
+ if (flags & AdditionalWaylandInterface::XdgActivation) {
+ if (interface == xdg_activation_v1_interface.name) {
+ c->xdgActivation = std::make_unique<XdgActivation>(*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<XdgActivationToken> XdgActivation::createToken()
+{
+ return std::make_unique<XdgActivationToken>(get_activation_token());
+}
+
void keyboardKeyPressed(quint32 key, quint32 time)
{
auto virtualKeyboard = static_cast<WaylandTestApplication *>(kwinApp())->virtualKeyboard();
--
GitLab

View file

@ -1,71 +0,0 @@
From dc692e89f101a47b9049b1f6ae4cc3cebef46edb Mon Sep 17 00:00:00 2001
From: Xaver Hugl <xaver.hugl@kde.org>
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<PlasmaWindowActivationInterface> m_activation;
};
--
GitLab

View file

@ -1,60 +0,0 @@
From bab7c4a4c5e2fb7be83ba94fdd0da7fd196654fa Mon Sep 17 00:00:00 2001
From: David Redondo <kde@david-redondo.de>
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<KWayland::Client::Surface> surface(Test::createSurface());
// only xdg-shell as ShellSurface misses the setter
std::unique_ptr<Test::XdgToplevel> 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

View file

@ -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

View file

@ -1,140 +0,0 @@
From ac32824009397188131aab4fcc3394fcc4551c5c Mon Sep 17 00:00:00 2001
From: Nate Graham <nate@kde.org>
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 <nate@kde.org>
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

View file

@ -1,168 +0,0 @@
From 63a9d544dc4f5160f28e5f3203a2c0a5e639e5ac Mon Sep 17 00:00:00 2001
From: Nate Graham <nate@kde.org>
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 <notmart@gmail.com>
- SPDX-FileCopyrightText: 2016 Jan Grulich <jgrulich@redhat.com>
- SPDX-FileCopyrightText: 2020 George Vogiatzis <gvgeo@protonmail.com>
-
- 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

View file

@ -1,248 +0,0 @@
From 8202ba92b610c691b8bc6bab8ad5a1c3b9ac73da Mon Sep 17 00:00:00 2001
From: Vlad Zahorodnii <vlad.zahorodnii@kde.org>
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 <qydwhotmail@gmail.com>
-# 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 <qydwhotmail@gmail.com>
-# 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<QVariant>() << 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

View file

@ -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

View file

@ -1,37 +0,0 @@
From 2278398309c68ce401a8e35b193fca3782391a4a Mon Sep 17 00:00:00 2001
From: Nate Graham <nate@kde.org>
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

View file

@ -1,456 +0,0 @@
From 439b251bcb3ea24f52c052e7244fb7ced04503aa Mon Sep 17 00:00:00 2001
From: Bohdan Onofriichuk <bogdan.onofriuchuk@gmail.com>
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 <qydwhotmail@gmail.com>
+# 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 <qydwhotmail@gmail.com>
-# 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

View file

@ -1,102 +0,0 @@
From 9fabf42c39a25308739dd3483881cc889243bf58 Mon Sep 17 00:00:00 2001
From: Kristen McWilliam <kristen@kde.org>
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

View file

@ -1,157 +0,0 @@
From 1641ea3897d565d672e29a7524ce4171ddbcfd32 Mon Sep 17 00:00:00 2001
From: Vlad Zahorodnii <vlad.zahorodnii@kde.org>
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<QVariant>() << 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<QVariant>() << 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 <vlad.zahorodnii@kde.org>
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<QVariant>() << 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

View file

@ -1,177 +0,0 @@
From aa1e466e5f7684a8b624c34d466dda9d10a331d2 Mon Sep 17 00:00:00 2001
From: Nate Graham <nate@kde.org>
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 <chrono>
+#include <knotification.h>
#include <wayland-client.h>
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 <QHash>
#include <QList>
+#include <QPointer>
#include <QSocketNotifier>
#include <QString>
#include <QTimer>
#include <KDEDModule>
+#include <KNotification>
#include <libudev.h>
@@ -98,4 +100,7 @@ private:
QTimer m_deviceAddedTimer;
QTimer m_deviceRemovedTimer;
+
+ QPointer<KNotification> m_usbDeviceAddedNotification;
+ QPointer<KNotification> m_usbDeviceRemovedNotification;
};
--
2.51.0
From 05f72383fd0b29105f3b5494759500d26b38ffc2 Mon Sep 17 00:00:00 2001
From: Nate Graham <nate@kde.org>
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

View file

@ -1,392 +0,0 @@
From 97c77a8e3259d77cb615dadd1c92185545513ebb Mon Sep 17 00:00:00 2001
From: Harald Sitter <sitter@kde.org>
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<QString, ActivityFavourite> m_favourites;
+ QMap<QString, ActivityFavorite> 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 <sitter@kde.org>
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 <sitter@kde.org>
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<QStringView> &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<QStringView> &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 <sitter@kde.org>
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<QStringView> &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 <sitter@kde.org>
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<QStringView> &strList, Category category)
+ qreal increaseMatchRelevance(const QString &serviceProperty, const QList<QStringView> &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<KService::Ptr> m_services;
bool m_matching = false;
};
--
2.51.0
From d25a269e9dbf6209ae51f94c298cb1ef640b045c Mon Sep 17 00:00:00 2001
From: Harald Sitter <sitter@kde.org>
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 <sitter@kde.org>
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

View file

@ -1,913 +0,0 @@
From 312c215e717654e55fa48ec968f412201d2a5544 Mon Sep 17 00:00:00 2001
From: Harald Sitter <sitter@kde.org>
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 <sitter@kde.org>
+
+#include <QDebug>
+#include <QDir>
+#include <QFile>
+#include <QObject>
+#include <QStandardPaths>
+#include <QTest>
+#include <QThread>
+
+#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 DamerauLevenshtein 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 <alexander.lohnau@gmx.de>
# 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 <sitter@kde.org>
+
+#pragma once
+
+#include <bitset>
+#include <optional>
+
+#include <QDebug>
+#include <QLoggingCategory>
+#include <QString>
+
+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<Match> 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<maxMaskBits>;
+ using PatternMask = std::array<Mask, std::numeric_limits<char16_t>::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<Mask> bits((hammingDistance + 1), Mask().set().reset(0));
+ std::vector<Mask> 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 DamerauLevenshtein 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 <sitter@kde.org>
+
+#pragma
+
+#include <QLoggingCategory>
+#include <QString>
+
+namespace Levenshtein
+{
+
+inline int distance(const QStringView &name, const QStringView &query)
+{
+ if (name == query) {
+ return 0;
+ }
+
+ std::vector<int> distance0(query.size() + 1, 0);
+ std::vector<int> 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 <aseigo@kde.org>
SPDX-FileCopyrightText: 2014 Vishesh Handa <vhanda@kde.org>
- SPDX-FileCopyrightText: 2016-2020 Harald Sitter <sitter@kde.org>
+ SPDX-FileCopyrightText: 2016-2025 Harald Sitter <sitter@kde.org>
SPDX-FileCopyrightText: 2022-2023 Alexander Lohnau <alexander.lohnau@gmx.de>
SPDX-License-Identifier: LGPL-2.0-only
@@ -21,6 +21,7 @@
#include <QUrlQuery>
#include <KApplicationTrader>
+#include <KFuzzyMatcher>
#include <KLocalizedString>
#include <KNotificationJobUiDelegate>
#include <KServiceAction>
@@ -35,22 +36,130 @@
#include <KIO/ApplicationLauncherJob>
#include <KIO/DesktopExecParser>
+#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<QStringView> &queryList)
+using ScoreCards = std::vector<ScoreCard>;
+
+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 &notNormalizedString, 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<QStringView> &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<QStringView> &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<Score> 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<decltype(Score::value)>::max(), .categoryRelevance = KRunner::QueryMatch::CategoryRelevance::Highest};
+ }
- // Keywords
- if (contains(service->keywords(), queryList)) {
- return true;
+ std::array<WeightedScoreCard, 4> 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

View file

@ -1,133 +0,0 @@
From 0168ee68b484995ed9398d31004dd80678ac7e37 Mon Sep 17 00:00:00 2001
From: Kai Uwe Broulik <kde@privat.broulik.de>
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 <QDBusConnection>
+#include <QDBusMessage>
+
#include <Solid/Device>
#include <Solid/OpticalDrive>
+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<QString> 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

View file

@ -1,33 +0,0 @@
From f6ec2847358178a5b6ff0497e52d1e2be43d2a48 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Luan=20Vitor=20Simi=C3=A3o=20oliveira?=
<luanv.oliveira@outlook.com>
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

View file

@ -1,32 +0,0 @@
From 4ab3894d75e1f9c6c7738a893a9b707ff0575953 Mon Sep 17 00:00:00 2001
From: Nate Graham <nate@kde.org>
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

View file

@ -1,42 +0,0 @@
From f0d2dd20803f2eee364d26656715b89e7c74366c Mon Sep 17 00:00:00 2001
From: David Redondo <kde@david-redondo.de>
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

View file

@ -1,349 +0,0 @@
From 77b3ec8cf4fb9414167bb15bcc3c69b1be6c2dbf Mon Sep 17 00:00:00 2001
From: Vlad Zahorodnii <vlad.zahorodnii@kde.org>
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 @@
<arg name="runnerName" type="s" direction="in"/>
<arg name="term" type="s" direction="in"/>
</method>
+ <method name="queryWithActivationToken">
+ <arg name="term" type="s" direction="in"/>
+ <arg name="activationToken" type="s" direction="in"/>
+ </method>
</interface>
</node>
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 <KAuthorized>
#include <KStartupInfo>
+#include <KWaylandExtras>
#include <KX11Extras>
#include <klocalizedstring.h>
#include <kwindowsystem.h>
@@ -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<QString> future() const
+ {
+ return m_promise.future();
+ }
+
+private:
+ QPromise<QString> m_promise;
+ int m_serial;
+};
+
+static QFuture<QString> 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 <PlasmaQuick/ConfigView>
#include <PlasmaQuick/ContainmentView>
+#include <QFuture>
#include <QPointer>
#include <KConfigWatcher>
@@ -127,6 +128,7 @@ private:
QPointer<QScreen> m_screenToFollow;
LayerShellQt::Window *m_layerWindow = nullptr;
QString m_krunnerText;
+ QFuture<QString> m_krunnerFuture;
// KRunner config
KConfigWatcher::Ptr m_configWatcher;
--
GitLab
From b3d97405cb5b0ee9859576309bd826dea6b74274 Mon Sep 17 00:00:00 2001
From: Vlad Zahorodnii <vlad.zahorodnii@kde.org>
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 @@
<arg name="runnerName" type="s" direction="in"/>
<arg name="term" type="s" direction="in"/>
</method>
- <method name="queryWithActivationToken">
- <arg name="term" type="s" direction="in"/>
- <arg name="activationToken" type="s" direction="in"/>
- </method>
</interface>
</node>
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 &parameter) {
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 <QDBusConnection>
#include <QDBusMessage>
+#include <QDBusPendingCall>
#include <QGuiApplication>
#include <QQmlContext>
#include <QQmlEngine>
@@ -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

View file

@ -1,3 +0,0 @@
Plasma 6.5.0:
Pr 460 https://invent.kde.org/plasma/spectacle/-/merge_requests/460

View file

@ -1,228 +0,0 @@
From 97f209559c00acc1ea6d0736bb318ac0254a3e37 Mon Sep 17 00:00:00 2001
From: Noah Davis <noahadvs@gmail.com>
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 <shortcut>%1</shortcut>.", 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 <noahadvs@gmail.com>
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