Desktop/kde: add patches
This commit is contained in:
parent
b6119bd2cc
commit
09d0847ea4
14 changed files with 2981 additions and 0 deletions
2
roles/kde/patches/frameworkintegration/patches.txt
Normal file
2
roles/kde/patches/frameworkintegration/patches.txt
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
Frameworks 6.18:
|
||||
Pr 54 https://invent.kde.org/frameworks/frameworkintegration/-/merge_requests/54
|
||||
50
roles/kde/patches/frameworkintegration/pr54.patch
Normal file
50
roles/kde/patches/frameworkintegration/pr54.patch
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
From 516a3642796563bcc7a13cd02795de3077a861b7 Mon Sep 17 00:00:00 2001
|
||||
From: Nate Graham <nate@kde.org>
|
||||
Date: Tue, 5 Aug 2025 09:27:36 -0600
|
||||
Subject: [PATCH] Turn on popups for device notifications
|
||||
|
||||
Part of https://invent.kde.org/plasma/plasma-desktop/-/issues/149
|
||||
|
||||
These notifications are a useful way to communicate to the user that
|
||||
their device isn't dead and was actuallt detected. Currently we have
|
||||
only sounds turned on, and no popups. This problematic for
|
||||
accessibility because deaf people won't hear the sound.
|
||||
|
||||
For this reason, EU Directive 2019/882 requires that all sound-based
|
||||
notifications be accompanied by something visual as well.
|
||||
|
||||
Let's turn on popups to comply with that. We did some work to make the
|
||||
popups not annoying, including:
|
||||
- Not appearing in the history
|
||||
- Removing the connection notification when disconnected (and vice
|
||||
versa)
|
||||
- Removing the notification when it's for a USB disk and the Disks &
|
||||
Devices widget appears automatically.
|
||||
---
|
||||
plasma_workspace.notifyrc | 4 ++--
|
||||
1 file changed, 2 insertions(+), 2 deletions(-)
|
||||
|
||||
diff --git a/plasma_workspace.notifyrc b/plasma_workspace.notifyrc
|
||||
index 7b9311d..4394192 100644
|
||||
--- a/plasma_workspace.notifyrc
|
||||
+++ b/plasma_workspace.notifyrc
|
||||
@@ -2380,7 +2380,7 @@ Comment[uk]=Було з'єднано пристрій
|
||||
Comment[x-test]=xxA device was plugged inxx
|
||||
Comment[zh_CN]=已插入一个设备
|
||||
Comment[zh_TW]=有裝置剛被插入
|
||||
-Action=Sound
|
||||
+Action=Popup|Sound
|
||||
Sound=device-added
|
||||
Urgency=Low
|
||||
|
||||
@@ -2465,6 +2465,6 @@ Comment[uk]=Було від'єднано пристрій
|
||||
Comment[x-test]=xxA device was unpluggedxx
|
||||
Comment[zh_CN]=已拔出一个设备
|
||||
Comment[zh_TW]=有裝置剛被拔出
|
||||
-Action=Sound
|
||||
+Action=Popup|Sound
|
||||
Sound=device-removed
|
||||
Urgency=Low
|
||||
--
|
||||
GitLab
|
||||
|
||||
|
|
@ -1,3 +1,5 @@
|
|||
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
|
||||
|
|
|
|||
669
roles/kde/patches/kwin/pr7927.patch
Normal file
669
roles/kde/patches/kwin/pr7927.patch
Normal file
|
|
@ -0,0 +1,669 @@
|
|||
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
|
||||
|
||||
71
roles/kde/patches/kwin/pr8005.patch
Normal file
71
roles/kde/patches/kwin/pr8005.patch
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
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
|
||||
|
||||
|
|
@ -5,3 +5,12 @@ 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
|
||||
|
|
|
|||
456
roles/kde/patches/plasma-workspace/pr5609.patch
Normal file
456
roles/kde/patches/plasma-workspace/pr5609.patch
Normal file
|
|
@ -0,0 +1,456 @@
|
|||
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
|
||||
|
||||
177
roles/kde/patches/plasma-workspace/pr5657.patch
Normal file
177
roles/kde/patches/plasma-workspace/pr5657.patch
Normal file
|
|
@ -0,0 +1,177 @@
|
|||
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
|
||||
|
||||
392
roles/kde/patches/plasma-workspace/pr5673.patch
Normal file
392
roles/kde/patches/plasma-workspace/pr5673.patch
Normal file
|
|
@ -0,0 +1,392 @@
|
|||
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
|
||||
|
||||
913
roles/kde/patches/plasma-workspace/pr5678.9.patch
Normal file
913
roles/kde/patches/plasma-workspace/pr5678.9.patch
Normal file
|
|
@ -0,0 +1,913 @@
|
|||
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 Damerau–Levenshtein distance because the characters are not adjacent.
|
||||
+ QCOMPARE(bitap(u"steam", u"skeap", 1), std::nullopt);
|
||||
+ // Deletion required
|
||||
+ QCOMPARE(bitap(u"discover", u"discover", 1), (Match{.end = 7, .distance = 0}));
|
||||
+ QCOMPARE(bitap(u"discover", u"discovery", 1), (Match{.end = 7, .distance = 1}));
|
||||
+ // Insertion required
|
||||
+ QCOMPARE(bitap(u"discover", u"dicover", 1), (Match{.end = 7, .distance = 1}));
|
||||
+ }
|
||||
+
|
||||
+ void testScore()
|
||||
+ {
|
||||
+ using namespace Bitap;
|
||||
+ // aperfectten has 10 big beautiful indexes. The maximum end is therefore 10.
|
||||
+ QCOMPARE(score(u"aperfectten", Match{.end = 10, .distance = 0}, 1), 1.0);
|
||||
+ QCOMPARE(score(u"aperfectten", Match{.end = 4, .distance = 0}, 1), 0.4);
|
||||
+ QCOMPARE(score(u"aperfectten", Match{.end = 4, .distance = 1}, 1), 0.35);
|
||||
+ QCOMPARE(score(u"aperfectten", Match{.end = 0, .distance = 0}, 0), 0);
|
||||
+ QCOMPARE(score(u"aperfectten", Match{.end = 0, .distance = 0}, 1), 0);
|
||||
+ QCOMPARE(score(u"aperfectten", Match{.end = 1, .distance = 1}, 1), 0.05);
|
||||
+
|
||||
+ QCOMPARE(score(u"abc", Match{.end = 2, .distance = 1}, 1), 0.95);
|
||||
+ // Ask for distance 0 but it has a distance so this is a super bad match.
|
||||
+ QCOMPARE(score(u"abc", Match{.end = 2, .distance = 1}, 0), 0);
|
||||
+ }
|
||||
+};
|
||||
+
|
||||
+QTEST_MAIN(BitapTest)
|
||||
+
|
||||
+#include "bitaptest.moc"
|
||||
diff --git a/runners/services/autotests/fixtures/audacity.desktop b/runners/services/autotests/fixtures/audacity.desktop
|
||||
index 7613d9f32f..05e1b9d929 100644
|
||||
--- a/runners/services/autotests/fixtures/audacity.desktop
|
||||
+++ b/runners/services/autotests/fixtures/audacity.desktop
|
||||
@@ -1,5 +1,5 @@
|
||||
[Desktop Entry]
|
||||
-Name=Audacity
|
||||
+Name=Audacity ServiceRunnerTest
|
||||
GenericName=Sound Editor
|
||||
Comment=Record and edit audio files
|
||||
Keywords=audio;sound;alsa;jack;editor;
|
||||
diff --git a/runners/services/autotests/fixtures/org.kde.discover.desktop b/runners/services/autotests/fixtures/org.kde.discover.desktop
|
||||
new file mode 100755
|
||||
index 0000000000..978b2b4152
|
||||
--- /dev/null
|
||||
+++ b/runners/services/autotests/fixtures/org.kde.discover.desktop
|
||||
@@ -0,0 +1,17 @@
|
||||
+# SPDX-FileCopyrightText: None
|
||||
+# SPDX-License-Identifier: CC0-1.0
|
||||
+[Desktop Entry]
|
||||
+Name=Discover ServiceRunnerTest
|
||||
+Comment=Install and remove apps and add-ons
|
||||
+MimeType=application/vnd.flatpak;application/vnd.flatpak.repo;application/vnd.flatpak.ref;
|
||||
+Exec=plasma-discover %F
|
||||
+Icon=plasmadiscover
|
||||
+Type=Application
|
||||
+X-DocPath=plasma-discover/index.html
|
||||
+InitialPreference=5
|
||||
+NoDisplay=false
|
||||
+Actions=Updates;
|
||||
+SingleMainWindow=true
|
||||
+GenericName=Software Center
|
||||
+Categories=Qt;KDE;System;
|
||||
+Keywords=program;software;store;repository;package;add;install;uninstall;remove;update;apps;applications;games;flatpak;snap;addons;add-ons;firmware;
|
||||
diff --git a/runners/services/autotests/fixtures/org.kde.kpat.desktop b/runners/services/autotests/fixtures/org.kde.kpat.desktop
|
||||
index 71d7fd2a89..3a91d89afe 100644
|
||||
--- a/runners/services/autotests/fixtures/org.kde.kpat.desktop
|
||||
+++ b/runners/services/autotests/fixtures/org.kde.kpat.desktop
|
||||
@@ -1,7 +1,7 @@
|
||||
# SPDX-FileCopyrightText: 2022 Alexander Lohnau <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 Damerau–Levenshtein distance the return value is a bit complicated. The trick is that the distance incurs a negative penalty
|
||||
+ // in relation to the max distance. While an end that is closer to the real end is generally favorably. Combining the two into a single value
|
||||
+ // would complicate the meaning of the return value to mean "approximate end with random penalty". This is garbage to reason about so instead we return
|
||||
+ // both values and then assign them meaning in the score function.
|
||||
+ if (match.end != -1) {
|
||||
+ return match;
|
||||
+ }
|
||||
+
|
||||
+ qCDebug(BITAP) << "No match found for pattern" << pattern << "in name" << name;
|
||||
+ return std::nullopt;
|
||||
+}
|
||||
+
|
||||
+inline qreal score(const QStringView &name, const auto &match, auto hammingDistance)
|
||||
+{
|
||||
+ // Normalize the score to a value between 0.0 and 1.0
|
||||
+ // No distance means the score is directly correlated to the end index. The more characters matched the higher the score.
|
||||
+ // Any distance will lower the score by a sub 0.1 margin.
|
||||
+
|
||||
+ if (name.size() == 0) {
|
||||
+ return 0.0; // No name, no score.
|
||||
+ }
|
||||
+
|
||||
+ const auto maxEnd = name.size() - 1;
|
||||
+ const auto penalty = [&] {
|
||||
+ if (hammingDistance <= 0) {
|
||||
+ return 1.0; // No penalty for no distance
|
||||
+ }
|
||||
+ constexpr auto tenth = 10.0;
|
||||
+ constexpr auto half = 2.0;
|
||||
+ return qreal(match.distance) / qreal(hammingDistance) / tenth / half;
|
||||
+ }();
|
||||
+ auto score = qreal(match.end) / qreal(maxEnd);
|
||||
+ // Prevent underflows when the penalty is larger than the score.
|
||||
+ score = std::max(0.0, score - penalty);
|
||||
+
|
||||
+ Q_ASSERT(score >= 0.0 && score <= 1.0);
|
||||
+ return score;
|
||||
+}
|
||||
+
|
||||
+} // namespace Bitap
|
||||
diff --git a/runners/services/levenshtein.h b/runners/services/levenshtein.h
|
||||
new file mode 100644
|
||||
index 0000000000..0efb960be3
|
||||
--- /dev/null
|
||||
+++ b/runners/services/levenshtein.h
|
||||
@@ -0,0 +1,58 @@
|
||||
+// SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
|
||||
+// SPDX-FileCopyrightText: 2025 Harald Sitter <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 ¬NormalizedString, const auto &queryList) {
|
||||
+ if (notNormalizedString.isEmpty()) {
|
||||
+ return ScoreCards{}; // No string, no score.
|
||||
+ }
|
||||
+
|
||||
+ const auto string = notNormalizedString.toLower();
|
||||
+
|
||||
+ ScoreCards cards;
|
||||
+ for (const auto &queryItem : queryList) {
|
||||
+ constexpr auto maxDistance = 1;
|
||||
+ const auto bitap = Bitap::bitap(string, queryItem, maxDistance);
|
||||
+ if (!bitap) {
|
||||
+ // One of the query items didn't match. This means the entire query is not a match
|
||||
+ return ScoreCards{};
|
||||
+ }
|
||||
+
|
||||
+ const auto bitapScore = Bitap::score(string, bitap.value(), maxDistance);
|
||||
+
|
||||
+ // Mind that we give different levels of bonus. This is important to imply ordering within competing matches of the same "type".
|
||||
+ // If we perfectly match that gives a bonus for not requiring any changes.
|
||||
+ const auto noSubstitionBonus = Bitap::score(string, bitap.value(), 0) == 1.0 ? 4.0 : 1.0;
|
||||
+ // If we match the entire length of the string that gets a bonus (disregarding distance, that was considered above).
|
||||
+ const auto completeMatchBonus = bitap->end >= (queryItem.size() - 1) ? 3.0 : 1.0;
|
||||
+ // If the string starts with the query item that gets a bonus.
|
||||
+ const auto startsWithBonus = (string.startsWith(queryItem, Qt::CaseInsensitive)) ? 2.0 : 1.0;
|
||||
+
|
||||
+ // Also consider the distance between the input and the query item.
|
||||
+ // If one is "yolotrollingservice" and the other is "yolo" then we must consider them worse matches than say "yolotroll".
|
||||
+ const auto levenshtein = Levenshtein::distance(string, queryItem);
|
||||
+
|
||||
+ cards.emplace_back(ScoreCard{
|
||||
+ .bitap = *bitap,
|
||||
+ .bitapScore = bitapScore + completeMatchBonus + noSubstitionBonus + startsWithBonus,
|
||||
+ .levenshtein = levenshtein,
|
||||
+ .levenshteinScore = Levenshtein::score(string, levenshtein),
|
||||
+ });
|
||||
+ }
|
||||
+
|
||||
+ return cards;
|
||||
+};
|
||||
+
|
||||
+
|
||||
+auto makeScoreFromList(const auto &queryList, const QStringList &strings) {
|
||||
+ // This turns the loop inside out. For every query item we must find a match in our keywords or we discard
|
||||
+ ScoreCards cards;
|
||||
+ // e.g. text,editor,programming
|
||||
+ for (const auto &queryItem : queryList) {
|
||||
+ // e.g. text;txt;editor;programming;programmer;development;developer;code;
|
||||
+ auto found = false;
|
||||
+ ScoreCards queryCards;
|
||||
+ for (const auto &string : strings) {
|
||||
+ auto stringCards = makeScores(string, QList{queryItem});
|
||||
+ if (stringCards.empty()) {
|
||||
+ continue; // The combination didn't match.
|
||||
+ }
|
||||
+ for (auto &scoreCard : stringCards) {
|
||||
+ if (scoreCard.levenshteinScore < 0.8) {
|
||||
+ continue; // Not a good match, skip it. We are very strict with keywords
|
||||
+ }
|
||||
+ found = true;
|
||||
+ queryCards.append_range(stringCards);
|
||||
+ }
|
||||
+ // We do not break because other string might also match, improving the score.
|
||||
+ }
|
||||
+ if (!found) {
|
||||
+ // No item in strings matched the query item. This means the entire query is not a match.
|
||||
+ return ScoreCards{};
|
||||
+ }
|
||||
+ cards.append_range(queryCards);
|
||||
+ }
|
||||
+ return cards;
|
||||
+};
|
||||
+
|
||||
+int weightedLength(const QString &query)
|
||||
+{
|
||||
+ return KStringHandler::logicalLength(query);
|
||||
}
|
||||
|
||||
inline bool contains(const QStringList &results, const QList<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
|
||||
|
||||
133
roles/kde/patches/plasma-workspace/pr5734.patch
Normal file
133
roles/kde/patches/plasma-workspace/pr5734.patch
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
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
|
||||
|
||||
33
roles/kde/patches/plasma-workspace/pr5746.patch
Normal file
33
roles/kde/patches/plasma-workspace/pr5746.patch
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
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
|
||||
|
||||
32
roles/kde/patches/plasma-workspace/pr5782.patch
Normal file
32
roles/kde/patches/plasma-workspace/pr5782.patch
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
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
|
||||
|
||||
42
roles/kde/patches/plasma-workspace/pr5788.patch
Normal file
42
roles/kde/patches/plasma-workspace/pr5788.patch
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
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
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue