Bug#1107344: unblock: kf6-kio/6.13.0-4
Package: release.debian.org
Severity: normal
X-Debbugs-Cc: kf6-kio@packages.debian.org, Debian Qt/KDE Maintainers <debian-qt-kde@lists.debian.org>
Control: affects -1 + src:kf6-kio
User: release.debian.org@packages.debian.org
Usertags: unblock
Dear Release Team,
please unblock package kf6-kio.
[ Reason ]
It contains the following changes:
* Backport upstream commits:
- Show single-click selection emblem in open/save dialogs when using
single-click mouse mode. (kde#185793)
- When saving a file, don’t override user provided filename with folder
name when navigating inside a folder with the keyboard. (kde#502794)
- Fix actions described by D-Bus activatable desktop files not being
triggered when there’s no Exec key.
- Inhibit suspend while there’s a file copy job running. (kde#362542)
- Update symbols accordingly.
- Show busy indictor while emptying the trash.
- Fix keyboard navigation unexpectedly switching to file name input when
navigating the files panel in save dialogs. (kde#466206)
- Fix excessive I/O when hovering over a folder showing thumbnails of its
contents.
- Fix incorrect usage percentage in Properties dialog for virtual
filesystems.
- Fix opening the “Advanced Options” window from the file properties dialog
freezing Plasma completely until it’s closed. (kde#504608)
[ Tests ]
Upstream testsuite passes in sbuild.
Each changes tested but this one for which I don’t have a test case and
only non-regression was tested:
- Fix actions described by D-Bus activatable desktop files not being
triggered when there’s no Exec key.
[ Risks ]
Only backport of upstream commits that apply cleanly. Further fixes can
easily be backported or the changes reverted.
[ Checklist ]
[x] all changes are documented in the d/changelog
[x] I reviewed all changes and I approve them
[x] attach debdiff against the package in testing
Thanks!
unblock kf6-kio/6.13.0-4
diff -Nru kf6-kio-6.13.0/debian/changelog kf6-kio-6.13.0/debian/changelog
--- kf6-kio-6.13.0/debian/changelog 2025-04-25 22:41:52.000000000 +0200
+++ kf6-kio-6.13.0/debian/changelog 2025-05-20 08:47:36.000000000 +0200
@@ -1,3 +1,27 @@
+kf6-kio (6.13.0-4) unstable; urgency=medium
+
+ [ Aurélien COUDERC ]
+ * Backport upstream commits:
+ - Show single-click selection emblem in open/save dialogs when using
+ single-click mouse mode. (kde#185793)
+ - When saving a file, don’t override user provided filename with folder
+ name when navigating inside a folder with the keyboard. (kde#502794)
+ - Fix actions described by D-Bus activatable desktop files not being
+ triggered when there’s no Exec key.
+ - Inhibit suspend while there’s a file copy job running. (kde#362542)
+ - Update symbols accordingly.
+ - Show busy indictor while emptying the trash.
+ - Fix keyboard navigation unexpectedly switching to file name input when
+ navigating the files panel in save dialogs. (kde#466206)
+ - Fix excessive I/O when hovering over a folder showing thumbnails of its
+ contents.
+ - Fix incorrect usage percentage in Properties dialog for virtual
+ filesystems.
+ - Fix opening the “Advanced Options” window from the file properties dialog
+ freezing Plasma completely until it’s closed. (kde#504608)
+
+ -- Aurélien COUDERC <coucouf@debian.org> Tue, 20 May 2025 08:47:36 +0200
+
kf6-kio (6.13.0-3) unstable; urgency=medium
[ Patrick Franz ]
diff -Nru kf6-kio-6.13.0/debian/libkf6kiocore6.symbols kf6-kio-6.13.0/debian/libkf6kiocore6.symbols
--- kf6-kio-6.13.0/debian/libkf6kiocore6.symbols 2025-04-23 19:07:08.000000000 +0200
+++ kf6-kio-6.13.0/debian/libkf6kiocore6.symbols 2025-05-20 08:47:36.000000000 +0200
@@ -279,8 +279,11 @@
_ZN3KIO10JobPrivate12emitMountingEPNS_3JobERK7QStringS5_@Base 6.0.0
_ZN3KIO10JobPrivate12emitRenamingEPNS_3JobERK4QUrlS5_@Base 6.0.0
_ZN3KIO10JobPrivate14emitUnmountingEPNS_3JobERK7QString@Base 6.0.0
+ (arch=amd64)_ZN3KIO10JobPrivate14inhibitSuspendERK7QString@Base 6.13.0
_ZN3KIO10JobPrivate15emitCreatingDirEPNS_3JobERK4QUrl@Base 6.0.0
+ (arch=amd64)_ZN3KIO10JobPrivate16doInhibitSuspendEv@Base 6.13.0
_ZN3KIO10JobPrivate16emitTransferringEPNS_3JobERK4QUrl@Base 6.0.0
+ (arch=amd64)_ZN3KIO10JobPrivate16uninhibitSuspendEv@Base 6.13.0
_ZN3KIO10JobPrivate22privilegeOperationDataEv@Base 6.0.0
_ZN3KIO10JobPrivateD0Ev@Base 6.0.0
_ZN3KIO10JobPrivateD1Ev@Base 6.0.0
diff -Nru kf6-kio-6.13.0/debian/libkf6kiofilewidgets6.symbols kf6-kio-6.13.0/debian/libkf6kiofilewidgets6.symbols
--- kf6-kio-6.13.0/debian/libkf6kiofilewidgets6.symbols 2025-04-23 19:14:52.000000000 +0200
+++ kf6-kio-6.13.0/debian/libkf6kiofilewidgets6.symbols 2025-05-20 08:47:36.000000000 +0200
@@ -104,6 +104,7 @@
_ZN12KDirOperator17updateSortActionsEv@Base 6.0.0
_ZN12KDirOperator17updateViewActionsEv@Base 6.0.0
_ZN12KDirOperator18setShowHiddenFilesEb@Base 6.0.0
+ _ZN12KDirOperator18usingKeyNavigationEv@Base 6.13.0
_ZN12KDirOperator19checkPreviewSupportEv@Base 6.0.0
_ZN12KDirOperator19setSupportedSchemesERK5QListI7QStringE@Base 6.0.0
_ZN12KDirOperator19showOpenWithActionsEb@Base 6.0.0
diff -Nru kf6-kio-6.13.0/debian/libkf6kiogui6.symbols kf6-kio-6.13.0/debian/libkf6kiogui6.symbols
--- kf6-kio-6.13.0/debian/libkf6kiogui6.symbols 2024-12-08 05:08:50.000000000 +0100
+++ kf6-kio-6.13.0/debian/libkf6kiogui6.symbols 2025-05-20 08:47:36.000000000 +0200
@@ -1,4 +1,4 @@
-# SymbolsHelper-Confirmed: 6.8.0 amd64
+# SymbolsHelper-Confirmed: 6.13.0 amd64
libKF6KIOGui.so.6 libkf6kiogui6 #MINVER#
* Build-Depends-Package: libkf6kio-dev
(arch=linux-any)_ZGVZN9QMetaType21registerConverterImplI5QListI11ExecCommandE9QIterableI13QMetaSequenceEEEbSt8functionIFbPKvPvEES_S_E10unregister@Base 6.0.0
@@ -31,7 +31,7 @@
_ZN14KProcessRunner14escapeUnitNameERK7QString@Base 6.0.0
_ZN14KProcessRunner14fromExecutableERK7QStringRK5QListIS0_ES2_RK10QByteArrayS2_RK19QProcessEnvironment@Base 6.0.0
_ZN14KProcessRunner14processStartedEx@Base 6.0.0
- _ZN14KProcessRunner15fromApplicationERK28QExplicitlySharedDataPointerI8KServiceERK7QStringRK5QListI4QUrlE6QFlagsIN3KIO22ApplicationLauncherJob7RunFlagEES7_RK10QByteArray@Base 6.0.0
+ _ZN14KProcessRunner15fromApplicationERK28QExplicitlySharedDataPointerI8KServiceERK7QStringRK5QListI4QUrlES7_6QFlagsIN3KIO22ApplicationLauncherJob7RunFlagEES7_RK10QByteArray@Base 6.13.0
_ZN14KProcessRunner16emitDelayedErrorERK7QString@Base 6.0.0
_ZN14KProcessRunner16staticMetaObjectE@Base 6.0.0
_ZN14KProcessRunner19initFromDesktopNameERK7QStringS2_RK10QByteArrayS2_RK19QProcessEnvironment@Base 6.0.0
diff -Nru kf6-kio-6.13.0/debian/libkf6kiowidgets6.symbols kf6-kio-6.13.0/debian/libkf6kiowidgets6.symbols
--- kf6-kio-6.13.0/debian/libkf6kiowidgets6.symbols 2024-09-08 09:38:40.000000000 +0200
+++ kf6-kio-6.13.0/debian/libkf6kiowidgets6.symbols 2025-05-20 08:47:36.000000000 +0200
@@ -1,4 +1,4 @@
-# SymbolsHelper-Confirmed: 6.0.0 amd64 armel armhf riscv64
+# SymbolsHelper-Confirmed: 6.13.0 amd64
libKF6KIOWidgets.so.6 libkf6kiowidgets6 #MINVER#, kio6
* Build-Depends-Package: libkf6kio-dev
_ZGVZN9QMetaType21registerConverterImplI5QListI4QUrlE9QIterableI13QMetaSequenceEEEbSt8functionIFbPKvPvEES_S_E10unregister@Base 6.0.0
@@ -164,6 +164,7 @@
_ZN17KFileItemDelegate18setShowInformationENS_11InformationE@Base 6.0.0
_ZN17KFileItemDelegate18setShowInformationERK5QListINS_11InformationEE@Base 6.0.0
_ZN17KFileItemDelegate22setJobTransfersVisibleEb@Base 6.0.0
+ _ZN17KFileItemDelegate22setSelectionEmblemRectE5QRecti@Base 6.13.0
_ZN17KFileItemDelegate24setShowToolTipWhenElidedEb@Base 6.0.0
_ZN17KFileItemDelegate5shapeERK20QStyleOptionViewItemRK11QModelIndex@Base 6.0.0
_ZN17KFileItemDelegate9helpEventEP10QHelpEventP17QAbstractItemViewRK20QStyleOptionViewItemRK11QModelIndex@Base 6.0.0
@@ -330,6 +331,7 @@
_ZN3KIO16DeleteOrTrashJob11qt_metacastEPKc@Base 6.0.0
_ZN3KIO16DeleteOrTrashJob16staticMetaObjectE@Base 6.0.0
_ZN3KIO16DeleteOrTrashJob5startEv@Base 6.0.0
+ _ZN3KIO16DeleteOrTrashJob7startedEv@Base 6.13.0
_ZN3KIO16DeleteOrTrashJobC1ERK5QListI4QUrlENS_22AskUserActionInterface12DeletionTypeENS6_16ConfirmationTypeEP7QObject@Base 6.0.0
_ZN3KIO16DeleteOrTrashJobC2ERK5QListI4QUrlENS_22AskUserActionInterface12DeletionTypeENS6_16ConfirmationTypeEP7QObject@Base 6.0.0
_ZN3KIO16DeleteOrTrashJobD0Ev@Base 6.0.0
@@ -489,10 +491,13 @@
_ZNK17KFileItemDelegate12shadowOffsetEv@Base 6.0.0
_ZNK17KFileItemDelegate13setEditorDataEP7QWidgetRK11QModelIndex@Base 6.0.0
_ZNK17KFileItemDelegate15showInformationEv@Base 6.0.0
+ _ZNK17KFileItemDelegate19drawSelectionEmblemE20QStyleOptionViewItemP8QPainterRK11QModelIndex@Base 6.13.0
_ZNK17KFileItemDelegate19jobTransfersVisibleEv@Base 6.0.0
+ _ZNK17KFileItemDelegate19selectionEmblemRectEv@Base 6.13.0
_ZNK17KFileItemDelegate20updateEditorGeometryEP7QWidgetRK20QStyleOptionViewItemRK11QModelIndex@Base 6.0.0
_ZNK17KFileItemDelegate21showToolTipWhenElidedEv@Base 6.0.0
_ZNK17KFileItemDelegate5paintEP8QPainterRK20QStyleOptionViewItemRK11QModelIndex@Base 6.0.0
+ _ZNK17KFileItemDelegate8fileItemERK11QModelIndex@Base 6.13.0
_ZNK17KFileItemDelegate8iconRectERK20QStyleOptionViewItemRK11QModelIndex@Base 6.0.0
_ZNK17KFileItemDelegate8sizeHintERK20QStyleOptionViewItemRK11QModelIndex@Base 6.0.0
_ZNK17KFileItemDelegate8wrapModeEv@Base 6.0.0
diff -Nru kf6-kio-6.13.0/debian/patches/series kf6-kio-6.13.0/debian/patches/series
--- kf6-kio-6.13.0/debian/patches/series 2025-04-23 19:10:13.000000000 +0200
+++ kf6-kio-6.13.0/debian/patches/series 2025-05-20 08:47:36.000000000 +0200
@@ -1,6 +1,17 @@
-# fixed in 6.14
-upstream_KUrlNavigatorButton.patch
-
report_error_removing_dirs
hurd_disable_unimplemented.diff
Use-CXX_FLAGS-for-moc_predefs.h.patch
+
+upstream_d8441b7b_Show-single-click-selection-emblem-when-using-single-click-mouse-mode.patch
+# fixed in 6.14
+upstream_KUrlNavigatorButton.patch
+upstream_cd0810f8_KFileWidget-Do-not-override-filename-with-folder-name-if-edited.patch
+upstream_31806c51_KProcessRunner-Fix-launching-actions-without-executable.patch
+upstream_3c3d5904_core-Add-infrastructure-for-inhibiting-suspend-in-jobs.patch
+upstream_60191c04_CopyJob-Inhibit-suspend-during-copy-operation.patch
+upstream_17cd1c9b_DeleteOrTrashJob-Add-started-signal.patch
+upstream_3e6175e4_KFilePlacesView-Show-busy-indicator-while-emptying-trash.patch
+upstream_719e0b00_KFileWidget-Fix-key-navigation-escaping-in-save-dialogs.patch
+upstream_c747fa0f_Avoid-unnecessary-sequencing-jobs-in-PreviewGenerator.patch
+upstream_81ca0e2c_Fix-incorrect-usage-percentage-in-Properties-dialog-for-virtual-filesystems.patch
+upstream_9143fc2a_Fix-dialog-modality-settings.patch
diff -Nru kf6-kio-6.13.0/debian/patches/upstream_17cd1c9b_DeleteOrTrashJob-Add-started-signal.patch kf6-kio-6.13.0/debian/patches/upstream_17cd1c9b_DeleteOrTrashJob-Add-started-signal.patch
--- kf6-kio-6.13.0/debian/patches/upstream_17cd1c9b_DeleteOrTrashJob-Add-started-signal.patch 1970-01-01 01:00:00.000000000 +0100
+++ kf6-kio-6.13.0/debian/patches/upstream_17cd1c9b_DeleteOrTrashJob-Add-started-signal.patch 2025-05-20 08:47:36.000000000 +0200
@@ -0,0 +1,50 @@
+From 17cd1c9bb99c4043018eda7b1a789af98b29f2b4 Mon Sep 17 00:00:00 2001
+From: Kai Uwe Broulik <kde@privat.broulik.de>
+Date: Thu, 1 May 2025 19:01:23 +0200
+Subject: [PATCH] DeleteOrTrashJob: Add started signal
+
+It's emitted when the user has confirmed emptying trash and
+it will actually commence emptying it.
+---
+ src/widgets/deleteortrashjob.cpp | 2 ++
+ src/widgets/deleteortrashjob.h | 11 +++++++++++
+ 2 files changed, 13 insertions(+)
+
+diff --git a/src/widgets/deleteortrashjob.cpp b/src/widgets/deleteortrashjob.cpp
+index 1f182c99c..1af01a236 100644
+--- a/src/widgets/deleteortrashjob.cpp
++++ b/src/widgets/deleteortrashjob.cpp
+@@ -87,6 +87,8 @@ void DeleteOrTrashJobPrivate::slotAskUser(bool allowDelete, const QList<QUrl> &u
+ // show the "File is too large to Trash" error message
+ job->uiDelegate()->setAutoErrorHandlingEnabled(false);
+ q->addSubjob(job);
++
++ Q_EMIT q->started();
+ }
+ }
+
+diff --git a/src/widgets/deleteortrashjob.h b/src/widgets/deleteortrashjob.h
+index dfcc6c8ee..486a1be70 100644
+--- a/src/widgets/deleteortrashjob.h
++++ b/src/widgets/deleteortrashjob.h
+@@ -74,6 +74,17 @@ public:
+ */
+ void start() override;
+
++Q_SIGNALS:
++ /**
++ * Emitted when the actual delete or trash job has been started.
++ *
++ * This can be used to display a busy indicator after the user has confirmed
++ * this operation.
++ *
++ * @since 6.15
++ */
++ void started();
++
+ private:
+ void slotResult(KJob *job) override;
+
+--
+GitLab
+
diff -Nru kf6-kio-6.13.0/debian/patches/upstream_31806c51_KProcessRunner-Fix-launching-actions-without-executable.patch kf6-kio-6.13.0/debian/patches/upstream_31806c51_KProcessRunner-Fix-launching-actions-without-executable.patch
--- kf6-kio-6.13.0/debian/patches/upstream_31806c51_KProcessRunner-Fix-launching-actions-without-executable.patch 1970-01-01 01:00:00.000000000 +0100
+++ kf6-kio-6.13.0/debian/patches/upstream_31806c51_KProcessRunner-Fix-launching-actions-without-executable.patch 2025-05-20 08:47:36.000000000 +0200
@@ -0,0 +1,108 @@
+From 31806c5180eb7cd1ad2f354eafd84f8fc490eae2 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Ball=C3=B3=20Gy=C3=B6rgy?= <ballogyor@gmail.com>
+Date: Fri, 4 Apr 2025 08:08:31 +0200
+Subject: [PATCH] KProcessRunner: Fix launching actions without executable
+
+If D-Bus activation is possible, it's not needed to have an executable,
+since everything is done via D-Bus. Therefore look for the matching action
+name instead of the exec value when launching an action. The specification
+allows to omit the "Exec" key if "DBusActivatable" is true.
+
+Most applications specify the Exec parameter for compatibility reasons, but
+if the action's "Exec" line got removed from a D-Bus activatable desktop
+file, then KIO just activates the application rather than calling the
+requested action. This change fixes that issue.
+---
+ src/gui/applicationlauncherjob.cpp | 7 +++++--
+ src/gui/kprocessrunner.cpp | 8 ++++++--
+ src/gui/kprocessrunner_p.h | 1 +
+ 3 files changed, 12 insertions(+), 4 deletions(-)
+
+diff --git a/src/gui/applicationlauncherjob.cpp b/src/gui/applicationlauncherjob.cpp
+index 463cec1be..f3cafafc8 100644
+--- a/src/gui/applicationlauncherjob.cpp
++++ b/src/gui/applicationlauncherjob.cpp
+@@ -49,6 +49,7 @@ public:
+ KService::Ptr m_service;
+ QString m_serviceEntryPath;
+ QList<QUrl> m_urls;
++ QString m_actionName;
+ KIO::ApplicationLauncherJob::RunFlags m_runFlags;
+ QString m_suggestedFileName;
+ QString m_mimeTypeName;
+@@ -75,6 +76,7 @@ KIO::ApplicationLauncherJob::ApplicationLauncherJob(const KServiceAction &servic
+ Q_ASSERT(d->m_service);
+ d->m_service.detach();
+ d->m_service->setExec(serviceAction.exec());
++ d->m_actionName = serviceAction.name();
+ }
+ KIO::ApplicationLauncherJob::ApplicationLauncherJob(const KDesktopFileAction &desktopFileAction, QObject *parent)
+ : ApplicationLauncherJob(KService::Ptr(new KService(desktopFileAction.desktopFilePath())), parent)
+@@ -82,6 +84,7 @@ KIO::ApplicationLauncherJob::ApplicationLauncherJob(const KDesktopFileAction &de
+ Q_ASSERT(d->m_service);
+ d->m_service.detach();
+ d->m_service->setExec(desktopFileAction.exec());
++ d->m_actionName = desktopFileAction.name();
+ }
+
+ KIO::ApplicationLauncherJob::ApplicationLauncherJob(QObject *parent)
+@@ -196,7 +199,7 @@ void KIO::ApplicationLauncherJob::proceedAfterSecurityChecks()
+ d->m_processRunners.reserve(d->m_numProcessesPending);
+ for (int i = 1; i < d->m_urls.count(); ++i) {
+ auto *processRunner =
+- KProcessRunner::fromApplication(d->m_service, d->m_serviceEntryPath, {d->m_urls.at(i)}, d->m_runFlags, d->m_suggestedFileName, QByteArray{});
++ KProcessRunner::fromApplication(d->m_service, d->m_serviceEntryPath, {d->m_urls.at(i)}, d->m_actionName, d->m_runFlags, d->m_suggestedFileName, QByteArray{});
+ d->m_processRunners.push_back(processRunner);
+ connect(processRunner, &KProcessRunner::processStarted, this, [this](qint64 pid) {
+ d->slotStarted(pid);
+@@ -208,7 +211,7 @@ void KIO::ApplicationLauncherJob::proceedAfterSecurityChecks()
+ }
+
+ auto *processRunner =
+- KProcessRunner::fromApplication(d->m_service, d->m_serviceEntryPath, d->m_urls, d->m_runFlags, d->m_suggestedFileName, d->m_startupId);
++ KProcessRunner::fromApplication(d->m_service, d->m_serviceEntryPath, d->m_urls, d->m_actionName, d->m_runFlags, d->m_suggestedFileName, d->m_startupId);
+ d->m_processRunners.push_back(processRunner);
+ connect(processRunner, &KProcessRunner::error, this, [this](const QString &errorText) {
+ setError(KJob::UserDefinedError);
+diff --git a/src/gui/kprocessrunner.cpp b/src/gui/kprocessrunner.cpp
+index 64fa3afb2..00f7811e8 100644
+--- a/src/gui/kprocessrunner.cpp
++++ b/src/gui/kprocessrunner.cpp
+@@ -88,6 +88,7 @@ static void modifyEnv(KProcess &process, QProcessEnvironment mod)
+ KProcessRunner *KProcessRunner::fromApplication(const KService::Ptr &service,
+ const QString &serviceEntryPath,
+ const QList<QUrl> &urls,
++ const QString &actionName,
+ KIO::ApplicationLauncherJob::RunFlags flags,
+ const QString &suggestedFileName,
+ const QByteArray &asn)
+@@ -103,9 +104,12 @@ KProcessRunner *KProcessRunner::fromApplication(const KService::Ptr &service,
+ const bool notYetSupportedOpenActivationNeeded = !urls.isEmpty();
+ if (!notYetSupportedOpenActivationNeeded && DBusActivationRunner::activationPossible(service, flags, suggestedFileName)) {
+ const auto actions = service->actions();
+- auto action = std::find_if(actions.cbegin(), actions.cend(), [service](const KServiceAction &action) {
+- return action.exec() == service->exec();
++ auto action = std::find_if(actions.cbegin(), actions.cend(), [actionName](const KServiceAction &action) {
++ return action.name() == actionName;
+ });
++ if (!actionName.isEmpty() && action == actions.cend()) {
++ qCWarning(KIO_GUI) << "Requested action" << actionName << "cannot be found for" << service->name();
++ }
+ instance = new DBusActivationRunner(action != actions.cend() ? action->name() : QString());
+ } else {
+ instance = makeInstance();
+diff --git a/src/gui/kprocessrunner_p.h b/src/gui/kprocessrunner_p.h
+index 63e517d37..7f084a49e 100644
+--- a/src/gui/kprocessrunner_p.h
++++ b/src/gui/kprocessrunner_p.h
+@@ -59,6 +59,7 @@ public:
+ static KProcessRunner *fromApplication(const KService::Ptr &service,
+ const QString &serviceEntryPath,
+ const QList<QUrl> &urls,
++ const QString &actionName = {},
+ KIO::ApplicationLauncherJob::RunFlags flags = {},
+ const QString &suggestedFileName = {},
+ const QByteArray &asn = {});
+--
+GitLab
+
diff -Nru kf6-kio-6.13.0/debian/patches/upstream_3c3d5904_core-Add-infrastructure-for-inhibiting-suspend-in-jobs.patch kf6-kio-6.13.0/debian/patches/upstream_3c3d5904_core-Add-infrastructure-for-inhibiting-suspend-in-jobs.patch
--- kf6-kio-6.13.0/debian/patches/upstream_3c3d5904_core-Add-infrastructure-for-inhibiting-suspend-in-jobs.patch 1970-01-01 01:00:00.000000000 +0100
+++ kf6-kio-6.13.0/debian/patches/upstream_3c3d5904_core-Add-infrastructure-for-inhibiting-suspend-in-jobs.patch 2025-05-20 08:47:36.000000000 +0200
@@ -0,0 +1,594 @@
+From 3c3d590472b1878660ae55f0f7c1bed5600ebb65 Mon Sep 17 00:00:00 2001
+From: Kai Uwe Broulik <kde@privat.broulik.de>
+Date: Sun, 20 Apr 2025 12:48:26 +0200
+Subject: [PATCH] core: Add infrastructure for inhibiting suspend in jobs
+
+This calls the freedesktop Inhibit interface on DBus which will
+inhibit suspend (but not display power management/screensaver).
+When inside a sandbox it instead calls the XDG Desktop Portal
+Inhibit interface.
+
+When a job is destroyed or gets suspended, the inhibition is lifted.
+When a job is resumed, `doInhibitSuspend` is called again to re-instate
+the inhibition.
+
+It is the job's responsibility to call `doInhibitSuspend` at
+the appropriate time (e.g. in doStart/slotStart).
+---
+ src/core/CMakeLists.txt | 5 +
+ src/core/config-kiocore.h.cmake | 2 +
+ src/core/job.cpp | 139 +++++++++++++-
+ src/core/job_p.h | 16 ++
+ ...rg.freedesktop.PowerManagement.Inhibit.xml | 20 ++
+ src/core/org.freedesktop.portal.Inhibit.xml | 173 ++++++++++++++++++
+ src/core/org.freedesktop.portal.Request.xml | 93 ++++++++++
+ 7 files changed, 446 insertions(+), 2 deletions(-)
+ create mode 100644 src/core/org.freedesktop.PowerManagement.Inhibit.xml
+ create mode 100644 src/core/org.freedesktop.portal.Inhibit.xml
+ create mode 100644 src/core/org.freedesktop.portal.Request.xml
+
+diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
+index 1dacfa081..599e001f9 100644
+--- a/src/core/CMakeLists.txt
++++ b/src/core/CMakeLists.txt
+@@ -164,6 +164,11 @@ if (HAVE_QTDBUS)
+ PROPERTIES INCLUDE authinfo.h
+ )
+ qt_add_dbus_interface(kiocore_dbus_SRCS org.kde.KPasswdServer.xml kpasswdserver_interface)
++
++ qt_add_dbus_interface(kiocore_dbus_SRCS org.freedesktop.PowerManagement.Inhibit.xml inhibit_interface)
++
++ qt_add_dbus_interface(kiocore_dbus_SRCS org.freedesktop.portal.Inhibit.xml portal_inhibit_interface)
++ qt_add_dbus_interface(kiocore_dbus_SRCS org.freedesktop.portal.Request.xml portal_request_interface)
+ endif()
+
+ target_sources(KF6KIOCore PRIVATE
+diff --git a/src/core/config-kiocore.h.cmake b/src/core/config-kiocore.h.cmake
+index 1f0bc42f2..bd9e7582a 100644
+--- a/src/core/config-kiocore.h.cmake
++++ b/src/core/config-kiocore.h.cmake
+@@ -7,6 +7,8 @@
+ /* Defined if sys/acl.h exists */
+ #cmakedefine01 HAVE_SYS_ACL_H
+
++#cmakedefine01 HAVE_QTDBUS
++
+ #define KDE_INSTALL_FULL_LIBEXECDIR_KF "${KDE_INSTALL_FULL_LIBEXECDIR_KF}"
+
+ #define KDE_INSTALL_FULL_KIO_PLUGINDIR "${KDE_INSTALL_FULL_PLUGINDIR}/kf6/kio/"
+diff --git a/src/core/job.cpp b/src/core/job.cpp
+index e8360d468..4eb037816 100644
+--- a/src/core/job.cpp
++++ b/src/core/job.cpp
+@@ -13,13 +13,30 @@
+ #include <time.h>
+
+ #include <KLocalizedString>
++#include <KSandbox>
+ #include <KStringHandler>
+
++#include "kiocoredebug.h"
+ #include "worker_p.h"
+ #include <kio/jobuidelegateextension.h>
+
++#if HAVE_QTDBUS
++#include <QDBusConnection>
++#include <QDBusPendingCallWatcher>
++
++#include "inhibit_interface.h"
++#include "portal_inhibit_interface.h"
++#include "portal_request_interface.h"
++#endif
++
+ using namespace KIO;
+
++static constexpr QLatin1String g_portalServiceName{"org.freedesktop.portal.Desktop"};
++static constexpr QLatin1String g_portalInhibitObjectPath{"/org/freedesktop/portal/desktop"};
++
++static constexpr QLatin1String g_inhibitServiceName{"org.freedesktop.PowerManagement.Inhibit"};
++static constexpr QLatin1String g_inhibitObjectPath{"/org/freedesktop/PowerManagement/Inhibit"};
++
+ Job::Job()
+ : KCompositeJob(nullptr)
+ , d_ptr(new JobPrivate)
+@@ -89,9 +106,127 @@ static QString url_description_string(const QUrl &url)
+ }
+
+ KIO::JobPrivate::~JobPrivate()
++{
++ uninhibitSuspend();
++}
++
++void JobPrivate::doInhibitSuspend()
+ {
+ }
+
++void JobPrivate::inhibitSuspend(const QString &reason)
++{
++#if HAVE_QTDBUS
++ if (KSandbox::isInside()) {
++ Q_ASSERT(m_portalInhibitionRequest.path().isEmpty());
++
++ org::freedesktop::portal::Inhibit inhibitInterface{g_portalServiceName, g_portalInhibitObjectPath, QDBusConnection::sessionBus()};
++ QVariantMap args;
++ if (!reason.isEmpty()) {
++ args.insert(QStringLiteral("reason"), reason);
++ }
++ auto call = inhibitInterface.Inhibit(QString() /* TODO window. */, 4 /* Suspend */, args);
++ // This is not parented to the job, so we can properly clean up the inhibiton
++ // should the job finish before the inhibition has been processed.
++ auto *watcher = new QDBusPendingCallWatcher(call);
++ QPointer<Job> guard(q_ptr);
++ QObject::connect(watcher, &QDBusPendingCallWatcher::finished, watcher, [this, guard, watcher, reason] {
++ QDBusPendingReply<QDBusObjectPath> reply = *watcher;
++
++ if (reply.isError()) {
++ qCWarning(KIO_CORE).nospace() << "Failed to inhibit suspend with reason " << reason << ": " << reply.error().message();
++ } else {
++ const QDBusObjectPath requestPath = reply.value();
++
++ // By the time the inhibition returned, the job was already gone. Uninhibit again.
++ if (!guard) {
++ org::freedesktop::portal::Request requestInterface{g_portalServiceName, requestPath.path(), QDBusConnection::sessionBus()};
++ requestInterface.Close();
++ } else {
++ m_portalInhibitionRequest = requestPath;
++ }
++ }
++
++ watcher->deleteLater();
++ });
++ } else {
++ Q_ASSERT(!m_inhibitionCookie);
++
++ QString appName = q_ptr->property("desktopFileName").toString();
++ if (appName.isEmpty()) {
++ // desktopFileName is in QGuiApplication but we're in KIO Core here.
++ appName = QCoreApplication::instance()->property("desktopFileName").toString();
++ }
++ if (appName.isEmpty()) {
++ appName = QCoreApplication::applicationName();
++ }
++
++ org::freedesktop::PowerManagement::Inhibit inhibitInterface{g_inhibitServiceName, g_inhibitObjectPath, QDBusConnection::sessionBus()};
++ auto call = inhibitInterface.Inhibit(appName, reason);
++ auto *watcher = new QDBusPendingCallWatcher(call);
++ QPointer<Job> guard(q_ptr);
++ QObject::connect(watcher, &QDBusPendingCallWatcher::finished, watcher, [this, guard, watcher, appName, reason] {
++ QDBusPendingReply<uint> reply = *watcher;
++
++ if (reply.isError()) {
++ qCWarning(KIO_CORE).nospace() << "Failed to inhibit suspend for " << appName << " with reason " << reason << ": " << reply.error().message();
++ } else {
++ const uint cookie = reply.value();
++
++ if (!guard) {
++ org::freedesktop::PowerManagement::Inhibit inhibitInterface{g_inhibitServiceName, g_inhibitObjectPath, QDBusConnection::sessionBus()};
++ inhibitInterface.UnInhibit(cookie);
++ } else {
++ m_inhibitionCookie = cookie;
++ }
++ }
++
++ watcher->deleteLater();
++ });
++ }
++#else
++ Q_UNUSED(reason)
++#endif
++}
++
++void JobPrivate::uninhibitSuspend()
++{
++#if HAVE_QTDBUS
++ if (!m_portalInhibitionRequest.path().isEmpty()) {
++ org::freedesktop::portal::Request requestInterface{g_portalServiceName, m_portalInhibitionRequest.path(), QDBusConnection::sessionBus()};
++ auto call = requestInterface.Close();
++ auto *watcher = new QDBusPendingCallWatcher(call, q_ptr);
++ QObject::connect(watcher, &QDBusPendingCallWatcher::finished, q_ptr, [this, watcher] {
++ QDBusPendingReply<> reply = *watcher;
++
++ if (reply.isError()) {
++ qCWarning(KIO_CORE) << "Failed to uninhibit suspend:" << reply.error().message();
++ } else {
++ m_portalInhibitionRequest = QDBusObjectPath();
++ }
++
++ watcher->deleteLater();
++ });
++ } else if (m_inhibitionCookie) {
++ org::freedesktop::PowerManagement::Inhibit inhibitInterface{g_inhibitServiceName, g_inhibitObjectPath, QDBusConnection::sessionBus()};
++ const int cookie = *m_inhibitionCookie;
++ auto call = inhibitInterface.UnInhibit(cookie);
++ auto *watcher = new QDBusPendingCallWatcher(call, q_ptr);
++ QObject::connect(watcher, &QDBusPendingCallWatcher::finished, q_ptr, [this, watcher, cookie] {
++ QDBusPendingReply<> reply = *watcher;
++
++ if (reply.isError()) {
++ qCWarning(KIO_CORE).nospace() << "Failed to uninhibit suspend for cookie" << cookie << ": " << reply.error().message();
++ } else {
++ m_inhibitionCookie.reset();
++ }
++
++ watcher->deleteLater();
++ });
++ }
++#endif
++}
++
+ void JobPrivate::emitMoving(KIO::Job *job, const QUrl &src, const QUrl &dest)
+ {
+ static const QString s_title = i18nc("@title job", "Moving");
+@@ -172,7 +307,7 @@ bool Job::doSuspend()
+ return false;
+ }
+ }
+-
++ d_ptr->uninhibitSuspend();
+ return true;
+ }
+
+@@ -183,7 +318,7 @@ bool Job::doResume()
+ return false;
+ }
+ }
+-
++ d_ptr->doInhibitSuspend();
+ return true;
+ }
+
+diff --git a/src/core/job_p.h b/src/core/job_p.h
+index e9eab0cf6..e8191198c 100644
+--- a/src/core/job_p.h
++++ b/src/core/job_p.h
+@@ -12,6 +12,8 @@
+ #ifndef KIO_JOB_P_H
+ #define KIO_JOB_P_H
+
++#include "config-kiocore.h"
++
+ #include "commands_p.h"
+ #include "global.h"
+ #include "jobtracker.h"
+@@ -26,6 +28,12 @@
+ #include <kio/jobuidelegateextension.h>
+ #include <kio/jobuidelegatefactory.h>
+
++#if HAVE_QTDBUS
++#include <QDBusObjectPath>
++
++#include <optional>
++#endif
++
+ /* clang-format off */
+ #define KIO_ARGS \
+ QByteArray packedArgs; \
+@@ -84,6 +92,10 @@ public:
+ MetaData m_outgoingMetaData;
+ JobUiDelegateExtension *m_uiDelegateExtension;
+ Job *q_ptr;
++#if HAVE_QTDBUS
++ std::optional<uint> m_inhibitionCookie; // fdo.
++ QDBusObjectPath m_portalInhibitionRequest; // portal.
++#endif
+ // For privilege operation
+ bool m_privilegeExecutionEnabled;
+ QString m_title, m_message;
+@@ -92,6 +104,10 @@ public:
+ QByteArray privilegeOperationData();
+ void slotSpeed(KJob *job, unsigned long speed);
+
++ void inhibitSuspend(const QString &reason);
++ void uninhibitSuspend();
++ virtual void doInhibitSuspend();
++
+ static void emitMoving(KIO::Job *, const QUrl &src, const QUrl &dest);
+ static void emitRenaming(KIO::Job *, const QUrl &src, const QUrl &dest);
+ static void emitCopying(KIO::Job *, const QUrl &src, const QUrl &dest);
+diff --git a/src/core/org.freedesktop.PowerManagement.Inhibit.xml b/src/core/org.freedesktop.PowerManagement.Inhibit.xml
+new file mode 100644
+index 000000000..21dfce8c7
+--- /dev/null
++++ b/src/core/org.freedesktop.PowerManagement.Inhibit.xml
+@@ -0,0 +1,20 @@
++<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
++"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
++<node>
++ <interface name="org.freedesktop.PowerManagement.Inhibit">
++ <method name="Inhibit">
++ <arg direction="in" type="s" name="application"/>
++ <arg direction="in" type="s" name="reason"/>
++ <arg direction="out" type="u" name="cookie"/>
++ </method>
++ <method name="UnInhibit">
++ <arg direction="in" type="u" name="cookie"/>
++ </method>
++ <signal name="HasInhibitChanged">
++ <arg direction="out" type="b" name="has_inhibit"/>
++ </signal>
++ <method name="HasInhibit">
++ <arg direction="out" type="b" name="has_inhibit"/>
++ </method>
++ </interface>
++</node>
+diff --git a/src/core/org.freedesktop.portal.Inhibit.xml b/src/core/org.freedesktop.portal.Inhibit.xml
+new file mode 100644
+index 000000000..1ae413e18
+--- /dev/null
++++ b/src/core/org.freedesktop.portal.Inhibit.xml
+@@ -0,0 +1,173 @@
++<?xml version="1.0"?>
++<!--
++ Copyright (C) 2016 Red Hat, Inc.
++
++ SPDX-License-Identifier: LGPL-2.1-or-later
++
++ This library is free software; you can redistribute it and/or
++ modify it under the terms of the GNU Lesser General Public
++ License as published by the Free Software Foundation; either
++ version 2.1 of the License, or (at your option) any later version.
++
++ This library is distributed in the hope that it will be useful,
++ but WITHOUT ANY WARRANTY; without even the implied warranty of
++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
++ Lesser General Public License for more details.
++
++ You should have received a copy of the GNU Lesser General Public
++ License along with this library. If not, see <http://www.gnu.org/licenses/>.
++
++ Author: Matthias Clasen <mclasen@redhat.com>
++-->
++
++<node name="/" xmlns:doc="http://www.freedesktop.org/dbus/1.0/doc.dtd">
++ <!--
++ org.freedesktop.portal.Inhibit:
++ @short_description: Portal for inhibiting session transitions
++
++ This simple interface lets sandboxed applications inhibit the user
++ session from ending, suspending, idling or getting switched away.
++
++ This documentation describes version 3 of this interface.
++ -->
++ <interface name="org.freedesktop.portal.Inhibit">
++ <!--
++ Inhibit:
++ @window: Identifier for the window
++ @flags: Flags identifying what is inhibited
++ @options: Vardict with optional further information
++ @handle: Object path for the :ref:`org.freedesktop.portal.Request` object representing this call
++
++ Inhibits a session status changes. To remove the inhibition,
++ call :ref:`org.freedesktop.portal.Request.Close` on the returned
++ handle.
++
++ The flags determine what changes are inhibited:
++
++ - ``1``: Logout
++ - ``2``: User Switch
++ - ``4``: Suspend
++ - ``8``: Idle
++
++ Supported keys in the @options vardict include:
++
++ * ``handle_token`` (``s``)
++
++ A string that will be used as the last element of the @handle. Must be a valid
++ object path element. See the :ref:`org.freedesktop.portal.Request` documentation for
++ more information about the @handle.
++
++ * ``reason`` (``s``)
++
++ User-visible reason for the inhibition.
++ -->
++ <method name="Inhibit">
++ <arg type="s" name="window" direction="in"/>
++ <arg type="u" name="flags" direction="in"/>
++ <annotation name="org.qtproject.QtDBus.QtTypeName.In2" value="QVariantMap"/>
++ <arg type="a{sv}" name="options" direction="in"/>
++ <arg type="o" name="handle" direction="out"/>
++ </method>
++
++ <!--
++ CreateMonitor:
++ @window: the parent window
++ @options: Vardict with optional further information
++ @handle: Object path for the :ref:`org.freedesktop.portal.Request` object representing this call
++
++ Creates a monitoring session. While this session is
++ active, the caller will receive StateChanged signals
++ with updates on the session state.
++
++ A successfully created session can at any time be closed using
++ org.freedesktop.portal.Session::Close, or may at any time be closed
++ by the portal implementation, which will be signalled via
++ :ref:`org.freedesktop.portal.Session::Closed`.
++
++ Supported keys in the @options vardict include:
++
++ * ``handle_token`` (``s``)
++
++ A string that will be used as the last element of the @handle. Must be a valid
++ object path element. See the :ref:`org.freedesktop.portal.Request` documentation for
++ more information about the @handle.
++
++ * ``session_handle_token`` (``s``)
++
++ A string that will be used as the last element of the session handle. Must be a valid
++ object path element. See the :ref:`org.freedesktop.portal.Session` documentation for
++ more information about the session handle.
++
++ The following results get returned via the :ref:`org.freedesktop.portal.Request::Response` signal:
++
++ * ``session_handle`` (``s``)
++
++ The session handle. An object path for the
++ :ref:`org.freedesktop.portal.Session` object representing the created
++ session.
++
++ .. note::
++ The ``session_handle`` is an object path that was erroneously implemented
++ as ``s``. For backwards compatibility it will remain this type.
++
++ This method was added in version 2 of this interface.
++ -->
++ <method name="CreateMonitor">
++ <arg type="s" name="window" direction="in"/>
++ <annotation name="org.qtproject.QtDBus.QtTypeName.In1" value="QVariantMap"/>
++ <arg type="a{sv}" name="options" direction="in"/>
++ <arg type="o" name="handle" direction="out"/>
++ </method>
++
++ <!--
++ StateChanged:
++ @session_handle: Object path for the :ref:`org.freedesktop.portal.Session` object
++ @state: Vardict with information about the session state
++
++ The StateChanged signal is sent to active monitoring sessions when
++ the session state changes.
++
++ When the session state changes to 'Query End', clients with active monitoring
++ sessions are expected to respond by calling
++ org.freedesktop.portal.Inhibit.QueryEndResponse() within a second
++ of receiving the StateChanged signal. They may call org.freedesktop.portal.Inhibit.Inhibit()
++ first to inhibit logout, to prevent the session from proceeding to the Ending state.
++
++ The following information may get returned in the @state vardict:
++
++ * ``screensaver-active`` (``b``)
++
++ Whether the screensaver is active.
++
++ * ``session-state`` (``u``)
++
++ The state of the session. This member is new in version 3.
++
++ - ``1``: Running
++ - ``2``: Query End
++ - ``3``: Ending
++
++ -->
++ <signal name="StateChanged">
++ <arg type="o" name="session_handle" direction="out"/>
++ <annotation name="org.qtproject.QtDBus.QtTypeName.Out1" value="QVariantMap"/>
++ <arg type="a{sv}" name="state" direction="out"/>
++ </signal>
++
++ <!--
++ QueryEndResponse:
++ @session_handle: Object path for the :ref:`org.freedesktop.portal.Session` object
++
++ Acknowledges that the caller received the #org.freedesktop.portal.Inhibit::StateChanged
++ signal. This method should be called within one second or receiving a StateChanged
++ signal with the 'Query End' state.
++
++ Since version 3.
++ -->
++ <method name="QueryEndResponse">
++ <arg type="o" name="session_handle" direction="in"/>
++ </method>
++
++ <property name="version" type="u" access="read"/>
++ </interface>
++</node>
+diff --git a/src/core/org.freedesktop.portal.Request.xml b/src/core/org.freedesktop.portal.Request.xml
+new file mode 100644
+index 000000000..e8a26484e
+--- /dev/null
++++ b/src/core/org.freedesktop.portal.Request.xml
+@@ -0,0 +1,93 @@
++<?xml version="1.0"?>
++<!--
++ Copyright (C) 2015 Red Hat, Inc.
++
++ SPDX-License-Identifier: LGPL-2.1-or-later
++
++ This library is free software; you can redistribute it and/or
++ modify it under the terms of the GNU Lesser General Public
++ License as published by the Free Software Foundation; either
++ version 2.1 of the License, or (at your option) any later version.
++
++ This library is distributed in the hope that it will be useful,
++ but WITHOUT ANY WARRANTY; without even the implied warranty of
++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
++ Lesser General Public License for more details.
++
++ You should have received a copy of the GNU Lesser General Public
++ License along with this library. If not, see <http://www.gnu.org/licenses/>.
++
++ Author: Alexander Larsson <alexl@redhat.com>
++-->
++
++<node name="/" xmlns:doc="http://www.freedesktop.org/dbus/1.0/doc.dtd">
++ <!--
++ org.freedesktop.portal.Request:
++ @short_description: Shared request interface
++
++ The Request interface is shared by all portal interfaces. When a
++ portal method is called, the reply includes a handle (i.e. object path)
++ for a Request object, which will stay alive for the duration of the
++ user interaction related to the method call.
++
++ The portal indicates that a portal request interaction is over by
++ emitting the #org.freedesktop.portal.Request::Response signal on the
++ Request object.
++
++ The application can abort the interaction calling
++ org.freedesktop.portal.Request.Close() on the Request object.
++
++ Since version 0.9 of xdg-desktop-portal, the handle will be of the form
++
++ ::
++
++ /org/freedesktop/portal/desktop/request/SENDER/TOKEN
++
++
++ where ``SENDER`` is the callers unique name, with the initial ``':'`` removed and
++ all ``'.'`` replaced by ``'_'``, and ``TOKEN`` is a unique token that the caller provided
++ with the handle_token key in the options vardict.
++
++ This change was made to let applications subscribe to the Response signal before
++ making the initial portal call, thereby avoiding a race condition. It is recommended
++ that the caller should verify that the returned handle is what it expected, and update
++ its signal subscription if it isn't. This ensures that applications will work with both
++ old and new versions of xdg-desktop-portal.
++
++ The token that the caller provides should be unique and not guessable. To avoid clashes
++ with calls made from unrelated libraries, it is a good idea to use a per-library prefix
++ combined with a random number.
++ -->
++ <interface name="org.freedesktop.portal.Request">
++
++ <!--
++ Close:
++
++ Closes the portal request to which this object refers and ends all
++ related user interaction (dialogs, etc).
++
++ A Response signal will not be emitted in this case.
++ -->
++ <method name="Close">
++ </method>
++
++ <!--
++ Response:
++ @response: Numeric response
++ @results: Vardict with results. The keys and values in the vardict depend on the request.
++
++ Emitted when the user interaction for a portal request is over.
++
++ The @response indicates how the user interaction ended:
++
++ - 0: Success, the request is carried out
++ - 1: The user cancelled the interaction
++ - 2: The user interaction was ended in some other way
++ -->
++ <signal name="Response">
++ <arg type="u" name="response"/>
++ <annotation name="org.qtproject.QtDBus.QtTypeName.Out1" value="QVariantMap"/>
++ <arg type="a{sv}" name="results"/>
++ </signal>
++ </interface>
++</node>
+--
+GitLab
+
diff -Nru kf6-kio-6.13.0/debian/patches/upstream_3e6175e4_KFilePlacesView-Show-busy-indicator-while-emptying-trash.patch kf6-kio-6.13.0/debian/patches/upstream_3e6175e4_KFilePlacesView-Show-busy-indicator-while-emptying-trash.patch
--- kf6-kio-6.13.0/debian/patches/upstream_3e6175e4_KFilePlacesView-Show-busy-indicator-while-emptying-trash.patch 1970-01-01 01:00:00.000000000 +0100
+++ kf6-kio-6.13.0/debian/patches/upstream_3e6175e4_KFilePlacesView-Show-busy-indicator-while-emptying-trash.patch 2025-05-20 08:47:36.000000000 +0200
@@ -0,0 +1,151 @@
+From 3e6175e4cf2898a0b9a15c6218765f6e8d7fd57c Mon Sep 17 00:00:00 2001
+From: Kai Uwe Broulik <kde@privat.broulik.de>
+Date: Thu, 1 May 2025 19:02:48 +0200
+Subject: [PATCH] KFilePlacesView: Show busy indicator while emptying trash
+
+Provide some feedback to the user in case it's taking a bit.
+---
+ src/filewidgets/kfileplacesview.cpp | 49 +++++++++++++++++++++++------
+ src/filewidgets/kfileplacesview_p.h | 4 +++
+ 2 files changed, 43 insertions(+), 10 deletions(-)
+
+diff --git a/src/filewidgets/kfileplacesview.cpp b/src/filewidgets/kfileplacesview.cpp
+index d2488ec32..30ca95f22 100644
+--- a/src/filewidgets/kfileplacesview.cpp
++++ b/src/filewidgets/kfileplacesview.cpp
+@@ -154,7 +154,8 @@ void KFilePlacesViewDelegate::paint(QPainter *painter, const QStyleOptionViewIte
+ QApplication::style()->drawPrimitive(QStyle::PE_PanelItemViewItem, &opt, painter);
+
+ const auto accessibility = placesModel->deviceAccessibility(index);
+- const bool isBusy = (accessibility == KFilePlacesModel::SetupInProgress || accessibility == KFilePlacesModel::TeardownInProgress);
++ const bool isBusy = (accessibility == KFilePlacesModel::SetupInProgress || accessibility == KFilePlacesModel::TeardownInProgress)
++ || (m_emptyingTrashIndex.isValid() && m_emptyingTrashIndex == index);
+
+ QIcon actionIcon;
+ if (isBusy) {
+@@ -458,6 +459,16 @@ void KFilePlacesViewDelegate::setHoveredAction(const QModelIndex &index)
+ m_hoveredAction = index;
+ }
+
++QModelIndex KFilePlacesViewDelegate::emptyingTrashIndex() const
++{
++ return m_emptyingTrashIndex;
++}
++
++void KFilePlacesViewDelegate::setEmptyingTrashIndex(const QModelIndex &index)
++{
++ m_emptyingTrashIndex = index;
++}
++
+ bool KFilePlacesViewDelegate::pointIsHeaderArea(const QPoint &pos) const
+ {
+ // we only accept drag events starting from item body, ignore drag request from header
+@@ -769,9 +780,11 @@ public:
+ void itemAppearUpdate(qreal value);
+ void itemDisappearUpdate(qreal value);
+ void enableSmoothItemResizing();
+- void slotEmptyTrash();
++ void slotEmptyTrash(const QModelIndex &index);
+
+ void deviceBusyAnimationValueChanged(const QVariant &value);
++ void startOrStopBusyAnimation();
++ void setEmptyingTrashIndex(const QModelIndex &index);
+
+ KFilePlacesView *const q;
+
+@@ -1123,8 +1136,9 @@ void KFilePlacesViewPrivate::writeConfig()
+ cg.sync();
+ }
+
+-void KFilePlacesViewPrivate::slotEmptyTrash()
++void KFilePlacesViewPrivate::slotEmptyTrash(const QModelIndex &index)
+ {
++ QPersistentModelIndex persistentIndex(index);
+ auto *parentWindow = q->window();
+
+ using AskIface = KIO::AskUserActionInterface;
+@@ -1132,9 +1146,26 @@ void KFilePlacesViewPrivate::slotEmptyTrash()
+ AskIface::EmptyTrash,
+ AskIface::DefaultConfirmation,
+ parentWindow);
++ QObject::connect(emptyTrashJob, &KIO::DeleteOrTrashJob::started, q, [this, persistentIndex] {
++ m_delegate->setEmptyingTrashIndex(persistentIndex);
++ startOrStopBusyAnimation();
++ });
++ QObject::connect(emptyTrashJob, &KJob::finished, q, [this] {
++ m_delegate->setEmptyingTrashIndex({});
++ startOrStopBusyAnimation();
++ });
+ emptyTrashJob->start();
+ }
+
++void KFilePlacesViewPrivate::startOrStopBusyAnimation()
++{
++ if (!m_busyDevices.isEmpty() || m_delegate->emptyingTrashIndex().isValid()) {
++ m_deviceBusyAnimation.start();
++ } else {
++ m_deviceBusyAnimation.stop();
++ }
++}
++
+ void KFilePlacesView::contextMenuEvent(QContextMenuEvent *event)
+ {
+ KFilePlacesModel *placesModel = qobject_cast<KFilePlacesModel *>(model());
+@@ -1327,7 +1358,7 @@ void KFilePlacesView::contextMenuEvent(QContextMenuEvent *event)
+
+ if (result) {
+ if (result == emptyTrash) {
+- d->slotEmptyTrash();
++ d->slotEmptyTrash(index);
+
+ } else if (result == eject) {
+ placesModel->requestEject(index);
+@@ -2136,6 +2167,9 @@ void KFilePlacesViewPrivate::deviceBusyAnimationValueChanged(const QVariant &val
+ for (const auto &idx : std::as_const(m_busyDevices)) {
+ q->update(idx);
+ }
++ if (m_delegate->emptyingTrashIndex().isValid()) {
++ q->update(m_delegate->emptyingTrashIndex());
++ }
+ }
+
+ void KFilePlacesView::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QList<int> &roles)
+@@ -2156,12 +2190,7 @@ void KFilePlacesView::dataChanged(const QModelIndex &topLeft, const QModelIndex
+ }
+
+ d->m_busyDevices = busyDevices;
+-
+- if (busyDevices.isEmpty()) {
+- d->m_deviceBusyAnimation.stop();
+- } else {
+- d->m_deviceBusyAnimation.start();
+- }
++ d->startOrStopBusyAnimation();
+ }
+ }
+
+diff --git a/src/filewidgets/kfileplacesview_p.h b/src/filewidgets/kfileplacesview_p.h
+index 694d555e0..b51516dce 100644
+--- a/src/filewidgets/kfileplacesview_p.h
++++ b/src/filewidgets/kfileplacesview_p.h
+@@ -58,6 +58,9 @@ public:
+ void setHoveredHeaderArea(const QModelIndex &index);
+ void setHoveredAction(const QModelIndex &index);
+
++ QModelIndex emptyingTrashIndex() const;
++ void setEmptyingTrashIndex(const QModelIndex &index);
++
+ qreal contentsOpacity(const QModelIndex &index) const;
+
+ bool pointIsHeaderArea(const QPoint &pos) const;
+@@ -100,6 +103,7 @@ private:
+ qreal m_disappearingOpacity;
+
+ qreal m_busyAnimationRotation = 0.0;
++ QPersistentModelIndex m_emptyingTrashIndex;
+
+ bool m_showHoverIndication;
+ QPersistentModelIndex m_hoveredHeaderArea;
+--
+GitLab
+
diff -Nru kf6-kio-6.13.0/debian/patches/upstream_60191c04_CopyJob-Inhibit-suspend-during-copy-operation.patch kf6-kio-6.13.0/debian/patches/upstream_60191c04_CopyJob-Inhibit-suspend-during-copy-operation.patch
--- kf6-kio-6.13.0/debian/patches/upstream_60191c04_CopyJob-Inhibit-suspend-during-copy-operation.patch 1970-01-01 01:00:00.000000000 +0100
+++ kf6-kio-6.13.0/debian/patches/upstream_60191c04_CopyJob-Inhibit-suspend-during-copy-operation.patch 2025-05-20 08:47:36.000000000 +0200
@@ -0,0 +1,75 @@
+From 60191c0473a96c41279519845030efa12fdd0bb4 Mon Sep 17 00:00:00 2001
+From: Kai Uwe Broulik <kde@privat.broulik.de>
+Date: Sun, 20 Apr 2025 12:48:58 +0200
+Subject: [PATCH] CopyJob: Inhibit suspend during copy operation
+
+Avoids the system going to sleep during a lengthy copy operation.
+
+BUG: 362542
+---
+ src/core/copyjob.cpp | 21 +++++++++++++++++++++
+ 1 file changed, 21 insertions(+)
+
+diff --git a/src/core/copyjob.cpp b/src/core/copyjob.cpp
+index d1e3eef61..21e1ff487 100644
+--- a/src/core/copyjob.cpp
++++ b/src/core/copyjob.cpp
+@@ -57,6 +57,7 @@
+ #include <KFileUtils>
+ #include <KIO/FileSystemFreeSpaceJob>
+
++#include <chrono>
+ #include <list>
+ #include <set>
+
+@@ -65,6 +66,7 @@ Q_DECLARE_LOGGING_CATEGORY(KIO_COPYJOB_DEBUG)
+ Q_LOGGING_CATEGORY(KIO_COPYJOB_DEBUG, "kf.kio.core.copyjob", QtWarningMsg)
+
+ using namespace KIO;
++using namespace std::literals::chrono_literals;
+
+ // this will update the report dialog with 5 Hz, I think this is fast enough, aleXXX
+ static constexpr int s_reportTimeout = 200;
+@@ -399,6 +401,8 @@ public:
+
+ void slotReport();
+
++ void doInhibitSuspend() override;
++
+ Q_DECLARE_PUBLIC(CopyJob)
+
+ static inline CopyJob *newJob(const QList<QUrl> &src, const QUrl &dest, CopyJob::CopyMode mode, bool asMethod, JobFlags flags)
+@@ -493,6 +497,11 @@ void CopyJobPrivate::slotStart()
+ }
+ }
+
++ // Avoid DBus traffic for short-lived jobs.
++ QTimer::singleShot(10s, q, [this] {
++ doInhibitSuspend();
++ });
++
+ /**
+ We call the functions directly instead of using signals.
+ Calling a function via a signal takes approx. 65 times the time
+@@ -736,6 +745,18 @@ bool CopyJob::doResume()
+ return Job::doResume();
+ }
+
++void CopyJobPrivate::doInhibitSuspend()
++{
++ QString reason;
++ if (m_mode == CopyJob::Move) {
++ reason = i18nc("Reason why standby is blocked", "Files are being moved");
++ } else {
++ reason = i18nc("Reason why standby is blocked", "Files are being copied");
++ }
++
++ inhibitSuspend(reason);
++}
++
+ void CopyJobPrivate::slotReport()
+ {
+ Q_Q(CopyJob);
+--
+GitLab
+
diff -Nru kf6-kio-6.13.0/debian/patches/upstream_719e0b00_KFileWidget-Fix-key-navigation-escaping-in-save-dialogs.patch kf6-kio-6.13.0/debian/patches/upstream_719e0b00_KFileWidget-Fix-key-navigation-escaping-in-save-dialogs.patch
--- kf6-kio-6.13.0/debian/patches/upstream_719e0b00_KFileWidget-Fix-key-navigation-escaping-in-save-dialogs.patch 1970-01-01 01:00:00.000000000 +0100
+++ kf6-kio-6.13.0/debian/patches/upstream_719e0b00_KFileWidget-Fix-key-navigation-escaping-in-save-dialogs.patch 2025-05-20 08:47:36.000000000 +0200
@@ -0,0 +1,143 @@
+From 719e0b0031155ef83a2cf71d6d24114dea181353 Mon Sep 17 00:00:00 2001
+From: Akseli Lahtinen <akselmo@akselmo.dev>
+Date: Wed, 7 May 2025 16:35:19 +0300
+Subject: [PATCH] KFileWidget: Fix key navigation escaping in save dialogs
+
+In save dialogs the keyboard navigation would escape during file
+highlighting, since the fileHighlight sets the focus for fileName bar
+for mouse operations.
+
+This makes sure the user has to press Tab to explicitly escape the
+keyboard navigation mode. For clicking the items, it should not affect
+at all.
+
+CCBUG: 466206
+FIXED-IN: 6.14
+(cherry picked from commit 8e4e84f045b7459c0b02b1b1b51a9df73cea068a)
+---
+ src/filewidgets/kdiroperator.cpp | 21 +++++++++++++++++++++
+ src/filewidgets/kdiroperator.h | 7 +++++++
+ src/filewidgets/kfilewidget.cpp | 8 ++++----
+ 3 files changed, 32 insertions(+), 4 deletions(-)
+
+diff --git a/src/filewidgets/kdiroperator.cpp b/src/filewidgets/kdiroperator.cpp
+index e1d3afeea..4a2e400c7 100644
+--- a/src/filewidgets/kdiroperator.cpp
++++ b/src/filewidgets/kdiroperator.cpp
+@@ -221,6 +221,7 @@ public:
+ bool m_showOpenWithActions = false;
+ bool m_isTouchEvent = false;
+ bool m_isTouchDrag = false;
++ bool m_keyNavigation = false;
+
+ QList<QUrl> m_itemsToBeSetAsCurrent;
+ QStringList m_supportedSchemes;
+@@ -1244,6 +1245,11 @@ void KDirOperator::showOpenWithActions(bool enable)
+ d->m_showOpenWithActions = enable;
+ }
+
++bool KDirOperator::usingKeyNavigation()
++{
++ return d->m_keyNavigation;
++}
++
+ void KDirOperator::changeEvent(QEvent *event)
+ {
+ QWidget::changeEvent(event);
+@@ -1429,6 +1435,19 @@ bool KDirOperator::eventFilter(QObject *watched, QEvent *event)
+ return true;
+ }
+ }
++ // Only use tab key to escape the view navigation
++ if (evt->key() == Qt::Key_Tab) {
++ d->m_keyNavigation = false;
++ d->slotSelectionChanged();
++ // When saving we need to return here,
++ // otherwise we skip over the next item with our tab press
++ // since we focus on that item in slotSelectionChanged
++ if (d->m_isSaving) {
++ return true;
++ }
++ } else {
++ d->m_keyNavigation = true;
++ }
+ break;
+ }
+ case QEvent::Resize: {
+@@ -1833,6 +1852,7 @@ void KDirOperator::selectFile(const KFileItem &item)
+ QApplication::restoreOverrideCursor();
+
+ Q_EMIT fileSelected(item);
++ d->m_keyNavigation = false;
+ }
+
+ void KDirOperator::highlightFile(const KFileItem &item)
+@@ -1842,6 +1862,7 @@ void KDirOperator::highlightFile(const KFileItem &item)
+ }
+
+ Q_EMIT fileHighlighted(item);
++ d->m_keyNavigation = false;
+ }
+
+ void KDirOperator::setCurrentItem(const QUrl &url)
+diff --git a/src/filewidgets/kdiroperator.h b/src/filewidgets/kdiroperator.h
+index c34880564..71a20ed2e 100644
+--- a/src/filewidgets/kdiroperator.h
++++ b/src/filewidgets/kdiroperator.h
+@@ -744,6 +744,13 @@ public:
+ */
+ void showOpenWithActions(bool enable);
+
++ /*!
++ * @returns true if the user was using keys to navigate.
++ *
++ * \since 6.14
++ */
++ bool usingKeyNavigation();
++
+ protected:
+ /**
+ * A view factory for creating predefined fileviews. Called internally by setView,
+diff --git a/src/filewidgets/kfilewidget.cpp b/src/filewidgets/kfilewidget.cpp
+index 5175a085a..017e65d1e 100644
+--- a/src/filewidgets/kfilewidget.cpp
++++ b/src/filewidgets/kfilewidget.cpp
+@@ -168,7 +168,7 @@ public:
+ void enterUrl(const QString &);
+ void locationAccepted(const QString &);
+ void slotFilterChanged();
+- void fileHighlighted(const KFileItem &);
++ void fileHighlighted(const KFileItem &, bool);
+ void fileSelected(const KFileItem &);
+ void slotLoadingFinished();
+ void togglePlacesPanel(bool show, QObject *sender = nullptr);
+@@ -894,7 +894,7 @@ void KFileWidget::accept()
+ d->m_ops->close();
+ }
+
+-void KFileWidgetPrivate::fileHighlighted(const KFileItem &i)
++void KFileWidgetPrivate::fileHighlighted(const KFileItem &i, bool isKeyNavigation)
+ {
+ if ((m_locationEdit->hasFocus() && !m_locationEdit->currentText().isEmpty())) { // don't disturb
+ return;
+@@ -933,7 +933,7 @@ void KFileWidgetPrivate::fileHighlighted(const KFileItem &i)
+ // rename it if desired
+ // Note that double-clicking will override this and overwrite regardless of
+ // single/double click mouse setting (see slotViewDoubleClicked() )
+- if (m_operationMode == KFileWidget::Saving) {
++ if (!isKeyNavigation && m_operationMode == KFileWidget::Saving) {
+ m_locationEdit->setFocus();
+ }
+ }
+@@ -1100,7 +1100,7 @@ void KFileWidgetPrivate::initDirOpWidgets()
+ urlEntered(url);
+ });
+ q->connect(m_ops, &KDirOperator::fileHighlighted, q, [this](const KFileItem &item) {
+- fileHighlighted(item);
++ fileHighlighted(item, m_ops->usingKeyNavigation());
+ });
+ q->connect(m_ops, &KDirOperator::fileSelected, q, [this](const KFileItem &item) {
+ fileSelected(item);
+--
+GitLab
+
diff -Nru kf6-kio-6.13.0/debian/patches/upstream_81ca0e2c_Fix-incorrect-usage-percentage-in-Properties-dialog-for-virtual-filesystems.patch kf6-kio-6.13.0/debian/patches/upstream_81ca0e2c_Fix-incorrect-usage-percentage-in-Properties-dialog-for-virtual-filesystems.patch
--- kf6-kio-6.13.0/debian/patches/upstream_81ca0e2c_Fix-incorrect-usage-percentage-in-Properties-dialog-for-virtual-filesystems.patch 1970-01-01 01:00:00.000000000 +0100
+++ kf6-kio-6.13.0/debian/patches/upstream_81ca0e2c_Fix-incorrect-usage-percentage-in-Properties-dialog-for-virtual-filesystems.patch 2025-05-20 08:47:36.000000000 +0200
@@ -0,0 +1,30 @@
+From 81ca0e2c50dc02873f04dfc6266ae4e4b88607a7 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Efe=20=C3=87iftci?= <efeciftci@gmail.com>
+Date: Fri, 16 May 2025 20:50:21 +0300
+Subject: [PATCH] Fix incorrect usage percentage in Properties dialog for
+ virtual filesystems
+
+The Properties dialog for filesystems such as `/proc/` or `/sys/` shows an
+incorrect usage percentage (i.e., "-2,147,483,648% used"). This commit fixes
+that issue by avoiding a divbyzero: if the `size` variable is `0`, the
+`percentUsed` variable is set to `0`.
+---
+ src/widgets/kpropertiesdialogbuiltin_p.cpp | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/src/widgets/kpropertiesdialogbuiltin_p.cpp b/src/widgets/kpropertiesdialogbuiltin_p.cpp
+index 45091fe755..919c044c4c 100644
+--- a/src/widgets/kpropertiesdialogbuiltin_p.cpp
++++ b/src/widgets/kpropertiesdialogbuiltin_p.cpp
+@@ -664,7 +664,7 @@ void KFilePropsPlugin::slotFreeSpaceResult(KJob *_job)
+ const qint64 size = job->size();
+ const qint64 available = job->availableSize();
+ const quint64 used = size - available;
+- const int percentUsed = qRound(100.0 * qreal(used) / qreal(size));
++ const int percentUsed = (size == 0) ? 0 : qRound(100.0 * qreal(used) / qreal(size));
+
+ d->m_ui->capacityBar->setText(i18nc("Available space out of total partition size (percent used)",
+ "%1 free of %2 (%3% used)",
+--
+GitLab
+
diff -Nru kf6-kio-6.13.0/debian/patches/upstream_9143fc2a_Fix-dialog-modality-settings.patch kf6-kio-6.13.0/debian/patches/upstream_9143fc2a_Fix-dialog-modality-settings.patch
--- kf6-kio-6.13.0/debian/patches/upstream_9143fc2a_Fix-dialog-modality-settings.patch 1970-01-01 01:00:00.000000000 +0100
+++ kf6-kio-6.13.0/debian/patches/upstream_9143fc2a_Fix-dialog-modality-settings.patch 2025-05-20 08:47:36.000000000 +0200
@@ -0,0 +1,38 @@
+From 9143fc2a5962d190e36f6e99d9ca1c02506097a5 Mon Sep 17 00:00:00 2001
+From: Akseli Lahtinen <akselmo@akselmo.dev>
+Date: Thu, 22 May 2025 17:35:24 +0300
+Subject: [PATCH] Fix dialog modality settings
+
+By default all dialogs are ApplicationModal, thus they block clicking
+on any other app while the dialog is open.
+
+Set them to WindowModal so they only block the parent window.
+
+BUG: 504608
+---
+ src/widgets/kpropertiesdialogbuiltin_p.cpp | 2 ++
+ 1 file changed, 2 insertions(+)
+
+diff --git a/src/widgets/kpropertiesdialogbuiltin_p.cpp b/src/widgets/kpropertiesdialogbuiltin_p.cpp
+index 919c044c4c..a21330d0b2 100644
+--- a/src/widgets/kpropertiesdialogbuiltin_p.cpp
++++ b/src/widgets/kpropertiesdialogbuiltin_p.cpp
+@@ -1354,6 +1354,7 @@ void KFilePermissionsPropsPlugin::slotShowAdvancedPermissions()
+ {
+ bool isDir = (d->pmode == PermissionsOnlyDirs) || (d->pmode == PermissionsMixed);
+ QDialog dlg(properties);
++ dlg.setWindowModality(Qt::WindowModal);
+ dlg.setModal(true);
+ dlg.setWindowTitle(i18n("Advanced Permissions"));
+
+@@ -2880,6 +2881,7 @@ void KDesktopPropsPlugin::slotAdvanced()
+ {
+ auto *dlg = new QDialog(d->m_frame);
+ dlg->setObjectName(QStringLiteral("KPropertiesDesktopAdv"));
++ dlg->setWindowModality(Qt::WindowModal);
+ dlg->setModal(true);
+ dlg->setAttribute(Qt::WA_DeleteOnClose);
+ dlg->setWindowTitle(i18n("Advanced Options for %1", properties->url().fileName()));
+--
+GitLab
+
diff -Nru kf6-kio-6.13.0/debian/patches/upstream_c747fa0f_Avoid-unnecessary-sequencing-jobs-in-PreviewGenerator.patch kf6-kio-6.13.0/debian/patches/upstream_c747fa0f_Avoid-unnecessary-sequencing-jobs-in-PreviewGenerator.patch
--- kf6-kio-6.13.0/debian/patches/upstream_c747fa0f_Avoid-unnecessary-sequencing-jobs-in-PreviewGenerator.patch 1970-01-01 01:00:00.000000000 +0100
+++ kf6-kio-6.13.0/debian/patches/upstream_c747fa0f_Avoid-unnecessary-sequencing-jobs-in-PreviewGenerator.patch 2025-05-20 08:47:36.000000000 +0200
@@ -0,0 +1,138 @@
+From c747fa0f4143ee730cefd6cd26d14c09e89cf3ef Mon Sep 17 00:00:00 2001
+From: Akseli Lahtinen <akselmo@akselmo.dev>
+Date: Tue, 13 May 2025 13:01:07 +0300
+Subject: [PATCH] Avoid unnecessary sequencing jobs in PreviewGenerator
+
+Currently we keep constantly asking if the current item has sequences
+support or not.
+
+By sequences we mean things like hovering mouse over a folder thumbnail
+and it goes through the files in it.
+
+This MR will always run for the first sequence (the initial thumbnail)
+but for the rest, then toggles a flag for the node with
+Qt::DecorationPropertyRole. If the propertyrole is false, it skips any
+further updates to avoid any unnecessary previewjob runs.
+---
+ src/filewidgets/kfilepreviewgenerator.cpp | 5 +++++
+ src/gui/previewjob.cpp | 2 ++
+ src/widgets/delegateanimationhandler.cpp | 7 +++++--
+ src/widgets/kdirmodel.cpp | 24 +++++++++++++++++++++++
+ src/widgets/kdirmodel.h | 1 +
+ 5 files changed, 37 insertions(+), 2 deletions(-)
+
+diff --git a/src/filewidgets/kfilepreviewgenerator.cpp b/src/filewidgets/kfilepreviewgenerator.cpp
+index 019124c618..639221fbe5 100644
+--- a/src/filewidgets/kfilepreviewgenerator.cpp
++++ b/src/filewidgets/kfilepreviewgenerator.cpp
+@@ -1015,6 +1015,11 @@ void KFilePreviewGeneratorPrivate::startPreviewJob(const KFileItemList &items, i
+
+ q->connect(job, &KIO::PreviewJob::gotPreview, q, [this, job](const KFileItem &item, const QPixmap &pixmap) {
+ addToPreviewQueue(item, pixmap, job);
++ m_dirModel->setData(m_dirModel->indexForItem(item), job->handlesSequences(), KDirModel::HandleSequencesRole);
++ });
++
++ q->connect(job, &KIO::PreviewJob::failed, q, [this, job](const KFileItem &item) {
++ m_dirModel->setData(m_dirModel->indexForItem(item), job->handlesSequences(), KDirModel::HandleSequencesRole);
+ });
+
+ q->connect(job, &KIO::PreviewJob::finished, q, [this, job]() {
+diff --git a/src/gui/previewjob.cpp b/src/gui/previewjob.cpp
+index bca18c9907..4d0ed030df 100644
+--- a/src/gui/previewjob.cpp
++++ b/src/gui/previewjob.cpp
+@@ -410,6 +410,8 @@ void PreviewJobPrivate::startPreview()
+ item.standardThumbnailer = plugin.description() == QStringLiteral("standardthumbnailer");
+ item.plugin = plugin;
+ items.push_back(item);
++ bool handlesSequencesValue = item.plugin.value(QStringLiteral("HandleSequences"), false);
++ thumbnailWorkerMetaData.insert(QStringLiteral("handlesSequences"), QString::number(handlesSequencesValue));
+
+ if (!bNeedCache && bSave && plugin.value(QStringLiteral("CacheThumbnail"), true)) {
+ const QUrl url = fileItem.targetUrl();
+diff --git a/src/widgets/delegateanimationhandler.cpp b/src/widgets/delegateanimationhandler.cpp
+index 9d8f81beb8..741416a944 100644
+--- a/src/widgets/delegateanimationhandler.cpp
++++ b/src/widgets/delegateanimationhandler.cpp
+@@ -200,8 +200,11 @@ void DelegateAnimationHandler::sequenceTimerTimeout()
+ KDirModel *dirModel = dynamic_cast<KDirModel *>(model);
+ if (dirModel) {
+ // qDebug() << "requesting" << currentSequenceIndex;
+- dirModel->requestSequenceIcon(index, currentSequenceIndex);
+- iconSequenceTimer.start(); // Some upper-bound interval is needed, in case items are not generated
++ // Only request sequence icons for items that have them
++ if (dirModel->data(index, KDirModel::HandleSequencesRole).toBool()) {
++ dirModel->requestSequenceIcon(index, currentSequenceIndex);
++ iconSequenceTimer.start(); // Some upper-bound interval is needed, in case items are not generated
++ }
+ }
+ }
+
+diff --git a/src/widgets/kdirmodel.cpp b/src/widgets/kdirmodel.cpp
+index 281a8a69e4..aa73a7f776 100644
+--- a/src/widgets/kdirmodel.cpp
++++ b/src/widgets/kdirmodel.cpp
+@@ -101,10 +101,21 @@ public:
+ m_preview = icn;
+ }
+
++ bool previewHandlesSequences()
++ {
++ return m_previewHandlesSequences;
++ }
++
++ void setPreviewHandlesSequences(bool handlesSequences)
++ {
++ m_previewHandlesSequences = handlesSequences;
++ }
++
+ private:
+ KFileItem m_item;
+ KDirModelDirNode *const m_parent;
+ QIcon m_preview;
++ bool m_previewHandlesSequences = true; // First sequence is always allowed
+ };
+
+ // Specialization for directory nodes
+@@ -915,6 +926,11 @@ QVariant KDirModel::data(const QModelIndex &index, int role) const
+ }
+ }
+ break;
++ case HandleSequencesRole:
++ if (index.column() == Name) {
++ return node->previewHandlesSequences();
++ }
++ break;
+ case Qt::TextAlignmentRole:
+ if (index.column() == Size) {
+ // use a right alignment for L2R and R2L languages
+@@ -1034,6 +1050,14 @@ bool KDirModel::setData(const QModelIndex &index, const QVariant &value, int rol
+ return true;
+ }
+ break;
++ case HandleSequencesRole:
++ if (index.column() == Name) {
++ KDirModelNode *node = static_cast<KDirModelNode *>(index.internalPointer());
++ Q_ASSERT(node);
++ node->setPreviewHandlesSequences(value.toBool());
++ return true;
++ }
++ break;
+ default:
+ break;
+ }
+diff --git a/src/widgets/kdirmodel.h b/src/widgets/kdirmodel.h
+index 64d315c43b..bf5a72c28c 100644
+--- a/src/widgets/kdirmodel.h
++++ b/src/widgets/kdirmodel.h
+@@ -180,6 +180,7 @@ public:
+ FileItemRole = 0x07A263FF, ///< returns the KFileItem for a given index. roleName is "fileItem".
+ ChildCountRole = 0x2C4D0A40, ///< returns the number of items in a directory, or ChildCountUnknown. roleName is "childCount".
+ HasJobRole = 0x01E555A5, ///< returns whether or not there is a job on an item (file/directory). roleName is "hasJob".
++ HandleSequencesRole = 0x1E642272,
+ };
+
+ /**
+--
+GitLab
+
diff -Nru kf6-kio-6.13.0/debian/patches/upstream_cd0810f8_KFileWidget-Do-not-override-filename-with-folder-name-if-edited.patch kf6-kio-6.13.0/debian/patches/upstream_cd0810f8_KFileWidget-Do-not-override-filename-with-folder-name-if-edited.patch
--- kf6-kio-6.13.0/debian/patches/upstream_cd0810f8_KFileWidget-Do-not-override-filename-with-folder-name-if-edited.patch 1970-01-01 01:00:00.000000000 +0100
+++ kf6-kio-6.13.0/debian/patches/upstream_cd0810f8_KFileWidget-Do-not-override-filename-with-folder-name-if-edited.patch 2025-05-20 08:47:36.000000000 +0200
@@ -0,0 +1,150 @@
+From cd0810f82d9fce629fa27e2a1d330fbcc8d2edf6 Mon Sep 17 00:00:00 2001
+From: Akseli Lahtinen <akselmo@akselmo.dev>
+Date: Thu, 17 Apr 2025 08:43:26 +0000
+Subject: [PATCH] KFileWidget: Do not override filename with folder name if
+ edited
+
+If the filename input has been modified, do not override it with
+the name of the folder when selecting a folder.
+
+BUG: 502794
+FIXED-IN: 6.14
+---
+ autotests/kfilewidgettest.cpp | 85 +++++++++++++++++++++++++++++++++
+ src/filewidgets/kdiroperator.h | 1 +
+ src/filewidgets/kfilewidget.cpp | 4 ++
+ 3 files changed, 90 insertions(+)
+
+diff --git a/autotests/kfilewidgettest.cpp b/autotests/kfilewidgettest.cpp
+index cd04b57c4..e0ff3945d 100644
+--- a/autotests/kfilewidgettest.cpp
++++ b/autotests/kfilewidgettest.cpp
+@@ -87,6 +87,8 @@ private Q_SLOTS:
+ void testTokenizeForSave_data();
+ void testTokenizeForSave();
+ void testThumbnailPreviewSetting();
++ void testReplaceLocationEditFilename_data();
++ void testReplaceLocationEditFilename();
+ };
+
+ void KFileWidgetTest::initTestCase()
+@@ -932,6 +934,89 @@ void KFileWidgetTest::testThumbnailPreviewSetting()
+ fwPreviewFalse.cancelButton()->click();
+ }
+
++struct LocationTestItem {
++ bool dir;
++ QString name;
++};
++
++void KFileWidgetTest::testReplaceLocationEditFilename_data()
++{
++ QTest::addColumn<LocationTestItem>("initialItem");
++ QTest::addColumn<LocationTestItem>("selectedItem");
++ QTest::addColumn<QString>("lineEditTextResult");
++ QTest::addColumn<bool>("overrideModifiedText");
++
++ QTest::newRow("replace-dir-with-dir") << LocationTestItem(true, "folder1") << LocationTestItem(true, "folder2") << "" << false;
++ QTest::newRow("replace-dir-with-file") << LocationTestItem(true, "folder1") << LocationTestItem(false, "file1") << "file1" << true;
++ QTest::newRow("replace-file-with-file") << LocationTestItem(false, "file1") << LocationTestItem(false, "file2") << "file2" << true;
++ QTest::newRow("replace-file-with-dir") << LocationTestItem(false, "file1") << LocationTestItem(true, "folder1") << "file1" << false;
++}
++
++// BUG: 502794
++// Test that we don't override file names with folder names
++void KFileWidgetTest::testReplaceLocationEditFilename()
++{
++ QFETCH(LocationTestItem, initialItem);
++ QFETCH(LocationTestItem, selectedItem);
++ QFETCH(QString, lineEditTextResult);
++ QFETCH(bool, overrideModifiedText);
++
++ // Setup - Create folders/files in temp dir
++ QTemporaryDir tempDir;
++ const QString tempDirPath = tempDir.path();
++ QUrl tempDirUrl = QUrl::fromLocalFile(tempDirPath);
++ QUrl replacedUrl = QUrl::fromLocalFile(tempDirPath + QLatin1Char('/') + initialItem.name);
++ QUrl selectedUrl = QUrl::fromLocalFile(tempDirPath + QLatin1Char('/') + selectedItem.name);
++
++ auto createTestItem = [tempDirUrl](LocationTestItem item, const QUrl &url) {
++ if (item.dir) {
++ QDir(tempDirUrl.toLocalFile()).mkdir(url.toLocalFile());
++ QVERIFY(QDir(url.toLocalFile()).exists());
++ } else {
++ QFile file(url.toLocalFile());
++ if (!file.open(QIODevice::WriteOnly)) {
++ qFatal("Couldn't create %s", qPrintable(url.toLocalFile()));
++ }
++ file.write(QByteArray("Test file"));
++ file.close();
++ QVERIFY(file.exists());
++ }
++ };
++
++ createTestItem(initialItem, replacedUrl);
++ createTestItem(selectedItem, selectedUrl);
++
++ // Open the filewidget in tempdir
++ KFileWidget fw(tempDirUrl);
++ fw.setOperationMode(KFileWidget::Saving);
++
++ // Highlight the item, then another
++ auto highlightItem = [&fw](QUrl url) {
++ KFileItem fileItem(url);
++ QSignalSpy fileHighlightedSpy(fw.dirOperator(), &KDirOperator::fileHighlighted);
++ fw.dirOperator()->highlightFile(fileItem);
++ fileHighlightedSpy.wait(500);
++ QVERIFY(fileHighlightedSpy.count());
++ };
++
++ highlightItem(replacedUrl);
++ highlightItem(selectedUrl);
++
++ // Compare that we have the wanted result when selecting items
++ QCOMPARE(fw.locationEdit()->lineEdit()->text(), lineEditTextResult);
++
++ // Make sure we don't overwrite any text user has modified in some cases
++ const QString modifiedText("New Filename.txt");
++ fw.locationEdit()->setEditText(modifiedText);
++ highlightItem(selectedUrl);
++
++ if (overrideModifiedText) {
++ QCOMPARE(fw.locationEdit()->lineEdit()->text(), lineEditTextResult);
++ } else {
++ QCOMPARE(fw.locationEdit()->lineEdit()->text(), modifiedText);
++ }
++}
++
+ QTEST_MAIN(KFileWidgetTest)
+
+ #include "kfilewidgettest.moc"
+diff --git a/src/filewidgets/kdiroperator.h b/src/filewidgets/kdiroperator.h
+index 6013a39cc..818432141 100644
+--- a/src/filewidgets/kdiroperator.h
++++ b/src/filewidgets/kdiroperator.h
+@@ -1042,6 +1042,7 @@ private:
+ KIOFILEWIDGETS_NO_EXPORT void setViewInternal(QAbstractItemView *view);
+
+ friend class KDirOperatorPrivate;
++ friend class KFileWidgetTest; // For testing
+ std::unique_ptr<KDirOperatorPrivate> d;
+ };
+
+diff --git a/src/filewidgets/kfilewidget.cpp b/src/filewidgets/kfilewidget.cpp
+index 064efc3b3..d5afb9de3 100644
+--- a/src/filewidgets/kfilewidget.cpp
++++ b/src/filewidgets/kfilewidget.cpp
+@@ -900,6 +900,10 @@ void KFileWidgetPrivate::fileHighlighted(const KFileItem &i, bool isKeyNavigatio
+ return;
+ }
+
++ if (!i.isNull() && i.isDir() && !(m_ops->mode() & KFile::Directory)) {
++ return;
++ }
++
+ const bool modified = m_locationEdit->lineEdit()->isModified();
+
+ if (!(m_ops->mode() & KFile::Files)) {
+--
+GitLab
+
diff -Nru kf6-kio-6.13.0/debian/patches/upstream_d8441b7b_Show-single-click-selection-emblem-when-using-single-click-mouse-mode.patch kf6-kio-6.13.0/debian/patches/upstream_d8441b7b_Show-single-click-selection-emblem-when-using-single-click-mouse-mode.patch
--- kf6-kio-6.13.0/debian/patches/upstream_d8441b7b_Show-single-click-selection-emblem-when-using-single-click-mouse-mode.patch 1970-01-01 01:00:00.000000000 +0100
+++ kf6-kio-6.13.0/debian/patches/upstream_d8441b7b_Show-single-click-selection-emblem-when-using-single-click-mouse-mode.patch 2025-05-16 22:07:04.000000000 +0200
@@ -0,0 +1,587 @@
+From d8441b7b5b2c8ebda18f5bde01f77ae802f4f48e Mon Sep 17 00:00:00 2001
+From: Akseli Lahtinen <akselmo@akselmo.dev>
+Date: Wed, 9 Apr 2025 07:57:20 +0000
+Subject: [PATCH] Show single-click selection emblem when using single-click
+ mouse mode
+
+In open/save dialog, we had no way to select items when using single-click mouse mode. (Except holding down control key).
+
+This adds the same emblem that Dolphin uses for its selections.
+
+It is shown in both treeviews and listviews, but only when single-click mode is active and the filewidget allows selecting multiple items.
+
+BUG: 185793
+FIXED-IN: 6.14
+---
+ src/filewidgets/CMakeLists.txt | 1 +
+ src/filewidgets/kdiroperator.cpp | 13 ++--
+ src/filewidgets/kdiroperatordetailview.cpp | 31 +++++++++-
+ src/filewidgets/kdiroperatordetailview_p.h | 7 ++-
+ src/filewidgets/kdiroperatoriconview.cpp | 33 +++++++++-
+ src/filewidgets/kdiroperatoriconview_p.h | 9 ++-
+ src/filewidgets/kfileitemselectionemblem.cpp | 63 ++++++++++++++++++++
+ src/filewidgets/kfileitemselectionemblem.h | 39 ++++++++++++
+ src/widgets/kfileitemdelegate.cpp | 58 ++++++++++++++++++
+ src/widgets/kfileitemdelegate.h | 15 +++++
+ 10 files changed, 259 insertions(+), 10 deletions(-)
+ create mode 100644 src/filewidgets/kfileitemselectionemblem.cpp
+ create mode 100644 src/filewidgets/kfileitemselectionemblem.h
+
+diff --git a/src/filewidgets/CMakeLists.txt b/src/filewidgets/CMakeLists.txt
+index e5fc87f75..cf4520191 100644
+--- a/src/filewidgets/CMakeLists.txt
++++ b/src/filewidgets/CMakeLists.txt
+@@ -22,6 +22,7 @@ target_sources(KF6KIOFileWidgets PRIVATE
+ kdiroperatoriconview.cpp
+ kdirsortfilterproxymodel.cpp #used in combination with kdirmodel.cpp
+ kencodingfiledialog.cpp
++ kfileitemselectionemblem.cpp
+ kfilebookmarkhandler.cpp
+ kfilecopytomenu.cpp
+ kfilecustomdialog.cpp
+diff --git a/src/filewidgets/kdiroperator.cpp b/src/filewidgets/kdiroperator.cpp
+index d2d8a5513..e1d3afeea 100644
+--- a/src/filewidgets/kdiroperator.cpp
++++ b/src/filewidgets/kdiroperator.cpp
+@@ -17,6 +17,7 @@
+ #include "kdiroperatoriconview_p.h"
+ #include "kdirsortfilterproxymodel.h"
+ #include "kfileitem.h"
++#include "kfileitemselectionemblem.h"
+ #include "kfilemetapreview_p.h"
+ #include "knewfilemenu.h"
+ #include "kpreviewwidgetbase.h"
+@@ -1260,9 +1261,13 @@ bool KDirOperator::eventFilter(QObject *watched, QEvent *event)
+ if (d->m_isTouchEvent) {
+ return true;
+ }
+- if (d->m_preview && !d->m_preview->isHidden()) {
+- const QModelIndex hoveredIndex = d->m_itemView->indexAt(d->m_itemView->viewport()->mapFromGlobal(QCursor::pos()));
+
++ const QModelIndex hoveredIndex = d->m_itemView->indexAt(d->m_itemView->viewport()->mapFromGlobal(QCursor::pos()));
++ if (hoveredIndex.isValid()) {
++ KFileItemSelectionEmblem(d->m_itemView, hoveredIndex, this).updateSelectionEmblemRectForIndex(iconSize());
++ }
++
++ if (d->m_preview && !d->m_preview->isHidden()) {
+ if (d->m_lastHoveredIndex == hoveredIndex) {
+ return QWidget::eventFilter(watched, event);
+ }
+@@ -1510,11 +1515,11 @@ QAbstractItemView *KDirOperator::createView(QWidget *parent, KFile::FileView vie
+ {
+ QAbstractItemView *itemView = nullptr;
+ if (KFile::isDetailView(viewKind) || KFile::isTreeView(viewKind) || KFile::isDetailTreeView(viewKind)) {
+- KDirOperatorDetailView *detailView = new KDirOperatorDetailView(parent);
++ KDirOperatorDetailView *detailView = new KDirOperatorDetailView(this, parent);
+ detailView->setViewMode(viewKind);
+ itemView = detailView;
+ } else {
+- itemView = new KDirOperatorIconView(parent, decorationPosition());
++ itemView = new KDirOperatorIconView(this, parent, decorationPosition());
+ }
+
+ return itemView;
+diff --git a/src/filewidgets/kdiroperatordetailview.cpp b/src/filewidgets/kdiroperatordetailview.cpp
+index 8bbd3e42c..b693e6d9d 100644
+--- a/src/filewidgets/kdiroperatordetailview.cpp
++++ b/src/filewidgets/kdiroperatordetailview.cpp
+@@ -5,6 +5,7 @@
+ */
+
+ #include "kdiroperatordetailview_p.h"
++#include "kfileitemselectionemblem.h"
+
+ #include <kdirlister.h>
+ #include <kdirmodel.h>
+@@ -17,9 +18,11 @@
+ #include <QMimeData>
+ #include <QScrollBar>
+
+-KDirOperatorDetailView::KDirOperatorDetailView(QWidget *parent)
++KDirOperatorDetailView::KDirOperatorDetailView(KDirOperator *dirOperator, QWidget *parent)
+ : QTreeView(parent)
+ , m_hideDetailColumns(false)
++ , m_isEmblemClicked(false)
++ , m_dirOperator(dirOperator)
+ {
+ setRootIsDecorated(false);
+ setSortingEnabled(true);
+@@ -115,9 +118,15 @@ void KDirOperatorDetailView::dragEnterEvent(QDragEnterEvent *event)
+
+ void KDirOperatorDetailView::mousePressEvent(QMouseEvent *event)
+ {
++ const QModelIndex index = indexAt(event->pos());
++ // When selection emblem is clicked, select it and don't do anything else
++ m_isEmblemClicked = KFileItemSelectionEmblem(this, index, m_dirOperator).handleMousePressEvent(event->pos());
++ if (m_isEmblemClicked) {
++ return;
++ }
++
+ QTreeView::mousePressEvent(event);
+
+- const QModelIndex index = indexAt(event->pos());
+ if (!index.isValid() || (index.column() != KDirModel::Name)) {
+ const Qt::KeyboardModifiers modifiers = QApplication::keyboardModifiers();
+ if (!(modifiers & Qt::ShiftModifier) && !(modifiers & Qt::ControlModifier)) {
+@@ -126,6 +135,24 @@ void KDirOperatorDetailView::mousePressEvent(QMouseEvent *event)
+ }
+ }
+
++void KDirOperatorDetailView::mouseMoveEvent(QMouseEvent *event)
++{
++ // Disallow selection dragging when emblem is clicked
++ if (m_isEmblemClicked) {
++ return;
++ }
++ QTreeView::mouseMoveEvent(event);
++}
++
++void KDirOperatorDetailView::mouseReleaseEvent(QMouseEvent *event)
++{
++ // Reset the emblem selection
++ if (m_isEmblemClicked) {
++ m_isEmblemClicked = false;
++ }
++ QTreeView::mouseReleaseEvent(event);
++}
++
+ void KDirOperatorDetailView::currentChanged(const QModelIndex ¤t, const QModelIndex &previous)
+ {
+ QTreeView::currentChanged(current, previous);
+diff --git a/src/filewidgets/kdiroperatordetailview_p.h b/src/filewidgets/kdiroperatordetailview_p.h
+index f30ab9482..df45142d4 100644
+--- a/src/filewidgets/kdiroperatordetailview_p.h
++++ b/src/filewidgets/kdiroperatordetailview_p.h
+@@ -7,6 +7,7 @@
+ #ifndef KDIROPERATORDETAILVIEW_P_H
+ #define KDIROPERATORDETAILVIEW_P_H
+
++#include <KDirOperator>
+ #include <QTreeView>
+
+ #include <kfile.h>
+@@ -22,7 +23,7 @@ class KDirOperatorDetailView : public QTreeView
+ Q_OBJECT
+
+ public:
+- explicit KDirOperatorDetailView(QWidget *parent = nullptr);
++ explicit KDirOperatorDetailView(KDirOperator *dirOperator, QWidget *parent = nullptr);
+ ~KDirOperatorDetailView() override;
+
+ /**
+@@ -36,10 +37,14 @@ protected:
+ bool event(QEvent *event) override;
+ void dragEnterEvent(QDragEnterEvent *event) override;
+ void mousePressEvent(QMouseEvent *event) override;
++ void mouseMoveEvent(QMouseEvent *event) override;
++ void mouseReleaseEvent(QMouseEvent *event) override;
+ void currentChanged(const QModelIndex ¤t, const QModelIndex &previous) override;
+
+ private:
+ bool m_hideDetailColumns;
++ bool m_isEmblemClicked;
++ KDirOperator *m_dirOperator;
+ };
+
+ #endif
+diff --git a/src/filewidgets/kdiroperatoriconview.cpp b/src/filewidgets/kdiroperatoriconview.cpp
+index ace0c0098..9180ea1ed 100644
+--- a/src/filewidgets/kdiroperatoriconview.cpp
++++ b/src/filewidgets/kdiroperatoriconview.cpp
+@@ -6,6 +6,7 @@
+ */
+
+ #include "kdiroperatoriconview_p.h"
++#include "kfileitemselectionemblem.h"
+
+ #include <QApplication>
+ #include <QDragEnterEvent>
+@@ -15,8 +16,10 @@
+ #include <KFileItemDelegate>
+ #include <KIconLoader>
+
+-KDirOperatorIconView::KDirOperatorIconView(QWidget *parent, QStyleOptionViewItem::Position aDecorationPosition)
++KDirOperatorIconView::KDirOperatorIconView(KDirOperator *dirOperator, QWidget *parent, QStyleOptionViewItem::Position aDecorationPosition)
+ : QListView(parent)
++ , m_isEmblemClicked(false)
++ , m_dirOperator(dirOperator)
+ {
+ setViewMode(QListView::IconMode);
+ setResizeMode(QListView::Adjust);
+@@ -74,7 +77,15 @@ void KDirOperatorIconView::dragEnterEvent(QDragEnterEvent *event)
+
+ void KDirOperatorIconView::mousePressEvent(QMouseEvent *event)
+ {
+- if (!indexAt(event->pos()).isValid()) {
++ const QModelIndex index = indexAt(event->pos());
++
++ // When selection emblem is clicked, select it and don't do anything else
++ m_isEmblemClicked = KFileItemSelectionEmblem(this, index, m_dirOperator).handleMousePressEvent(event->pos());
++ if (m_isEmblemClicked) {
++ return;
++ }
++
++ if (!index.isValid()) {
+ const Qt::KeyboardModifiers modifiers = QApplication::keyboardModifiers();
+ if (!(modifiers & Qt::ShiftModifier) && !(modifiers & Qt::ControlModifier)) {
+ clearSelection();
+@@ -84,6 +95,24 @@ void KDirOperatorIconView::mousePressEvent(QMouseEvent *event)
+ QListView::mousePressEvent(event);
+ }
+
++void KDirOperatorIconView::mouseMoveEvent(QMouseEvent *event)
++{
++ // Disallow selection dragging when emblem is clicked
++ if (m_isEmblemClicked) {
++ return;
++ }
++ QListView::mouseMoveEvent(event);
++}
++
++void KDirOperatorIconView::mouseReleaseEvent(QMouseEvent *event)
++{
++ // Reset the emblem selection
++ if (m_isEmblemClicked) {
++ m_isEmblemClicked = false;
++ }
++ QListView::mouseReleaseEvent(event);
++}
++
+ void KDirOperatorIconView::wheelEvent(QWheelEvent *event)
+ {
+ QListView::wheelEvent(event);
+diff --git a/src/filewidgets/kdiroperatoriconview_p.h b/src/filewidgets/kdiroperatoriconview_p.h
+index c4ab96738..f1caba9e0 100644
+--- a/src/filewidgets/kdiroperatoriconview_p.h
++++ b/src/filewidgets/kdiroperatoriconview_p.h
+@@ -8,6 +8,7 @@
+ #ifndef KDIROPERATORICONVIEW_P_H
+ #define KDIROPERATORICONVIEW_P_H
+
++#include <KDirOperator>
+ #include <QListView>
+
+ /**
+@@ -18,7 +19,9 @@ class KDirOperatorIconView : public QListView
+ {
+ Q_OBJECT
+ public:
+- KDirOperatorIconView(QWidget *parent = nullptr, QStyleOptionViewItem::Position decorationPosition = QStyleOptionViewItem::Position::Top);
++ KDirOperatorIconView(KDirOperator *dirOperator,
++ QWidget *parent = nullptr,
++ QStyleOptionViewItem::Position decorationPosition = QStyleOptionViewItem::Position::Top);
+ ~KDirOperatorIconView() override;
+ void setDecorationPosition(QStyleOptionViewItem::Position decorationPosition);
+
+@@ -26,6 +29,8 @@ protected:
+ void initViewItemOption(QStyleOptionViewItem *option) const override;
+ void dragEnterEvent(QDragEnterEvent *event) override;
+ void mousePressEvent(QMouseEvent *event) override;
++ void mouseMoveEvent(QMouseEvent *event) override;
++ void mouseReleaseEvent(QMouseEvent *event) override;
+ void wheelEvent(QWheelEvent *event) override;
+ void resizeEvent(QResizeEvent *event) override;
+
+@@ -34,6 +39,8 @@ protected
+
+ private:
+ QStyleOptionViewItem::Position decorationPosition;
++ bool m_isEmblemClicked;
++ KDirOperator *m_dirOperator;
+ };
+
+ #endif // KDIROPERATORICONVIEW_P_H
+diff --git a/src/filewidgets/kfileitemselectionemblem.cpp b/src/filewidgets/kfileitemselectionemblem.cpp
+new file mode 100644
+index 000000000..566dee306
+--- /dev/null
++++ b/src/filewidgets/kfileitemselectionemblem.cpp
+@@ -0,0 +1,63 @@
++/*
++ SPDX-FileCopyrightText: 2025 Akseli Lahtinen <akselmo@akselmo.dev>
++
++ SPDX-License-Identifier: LGPL-2.0-or-later
++*/
++
++#include "kfileitemselectionemblem.h"
++#include "kfileitemdelegate.h"
++
++#include <QAbstractItemDelegate>
++#include <QAbstractItemView>
++#include <QApplication>
++#include <QModelIndex>
++#include <QPoint>
++
++KFileItemSelectionEmblem::KFileItemSelectionEmblem(QAbstractItemView *itemView, QModelIndex index, KDirOperator *dirOperator)
++{
++ m_itemView = itemView;
++ m_index = index;
++ m_fileItemDelegate = fileItemDelegate();
++ m_dirOperator = dirOperator;
++ m_fileItem = m_fileItemDelegate->fileItem(m_index);
++}
++
++KFileItemSelectionEmblem::~KFileItemSelectionEmblem()
++{
++}
++
++bool KFileItemSelectionEmblem::isEmblemEnabled()
++{
++ if (m_itemView->selectionMode() == QAbstractItemView::ExtendedSelection && qApp->style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick)) {
++ if (m_fileItem.isDir()) {
++ return m_dirOperator->isSelected(m_fileItem);
++ }
++ return true;
++ }
++ return false;
++}
++
++KFileItemDelegate *KFileItemSelectionEmblem::fileItemDelegate()
++{
++ auto itemDelegate = m_itemView->itemDelegateForIndex(m_index);
++ if (itemDelegate) {
++ return qobject_cast<KFileItemDelegate *>(itemDelegate);
++ }
++ return nullptr;
++}
++
++void KFileItemSelectionEmblem::updateSelectionEmblemRectForIndex(const int iconSize)
++{
++ if (isEmblemEnabled() && m_fileItemDelegate) {
++ m_fileItemDelegate->setSelectionEmblemRect(m_itemView->visualRect(m_index), iconSize);
++ }
++}
++
++bool KFileItemSelectionEmblem::handleMousePressEvent(const QPoint mousePos)
++{
++ if (isEmblemEnabled() && m_fileItemDelegate && m_fileItemDelegate->selectionEmblemRect().contains(mousePos)) {
++ m_itemView->selectionModel()->select(m_index, QItemSelectionModel::Toggle);
++ return true;
++ }
++ return false;
++}
+diff --git a/src/filewidgets/kfileitemselectionemblem.h b/src/filewidgets/kfileitemselectionemblem.h
+new file mode 100644
+index 000000000..4bfd78a8b
+--- /dev/null
++++ b/src/filewidgets/kfileitemselectionemblem.h
+@@ -0,0 +1,39 @@
++/*
++ SPDX-FileCopyrightText: 2025 Akseli Lahtinen <akselmo@akselmo.dev>
++
++ SPDX-License-Identifier: LGPL-2.0-or-later
++*/
++
++#ifndef KFILEITEMSELECTIONEMBLEM_H
++#define KFILEITEMSELECTIONEMBLEM_H
++
++#include <KDirOperator>
++#include <QAbstractItemView>
++#include <QModelIndex>
++
++class KFileItem;
++class KFileItemDelegate;
++class QPoint;
++
++class KFileItemSelectionEmblem
++{
++public:
++ KFileItemSelectionEmblem(QAbstractItemView *itemView, QModelIndex index, KDirOperator *dirOperator);
++ ~KFileItemSelectionEmblem();
++
++ void updateSelectionEmblemRectForIndex(const int iconSize);
++ bool handleMousePressEvent(const QPoint mousePos);
++ bool isEmblemEnabled();
++
++private:
++ KFileItemDelegate *fileItemDelegate();
++
++ QAbstractItemView *m_itemView;
++ QModelIndex m_index;
++ KDirOperator *m_dirOperator;
++ KFileItemDelegate *m_fileItemDelegate;
++ KFileItem m_fileItem;
++ bool m_isDir;
++};
++
++#endif
+diff --git a/src/widgets/kfileitemdelegate.cpp b/src/widgets/kfileitemdelegate.cpp
+index c00c69523..83acd9910 100644
+--- a/src/widgets/kfileitemdelegate.cpp
++++ b/src/widgets/kfileitemdelegate.cpp
+@@ -101,6 +101,7 @@ public:
+ void gotNewIcon(const QModelIndex &index);
+
+ void paintJobTransfers(QPainter *painter, const qreal &jobAnimationAngle, const QPoint &iconPos, const QStyleOptionViewItem &opt);
++ int scaledEmblemSize(int iconSize) const;
+
+ public:
+ KFileItemDelegate::InformationList informationList;
+@@ -112,6 +113,7 @@ public:
+ QTextOption::WrapMode wrapMode;
+ bool jobTransfersVisible;
+ QIcon downArrowIcon;
++ QRect emblemRect;
+
+ private:
+ KIO::DelegateAnimationHandler *animationHandler;
+@@ -128,6 +130,7 @@ KFileItemDelegate::Private::Private(KFileItemDelegate *parent)
+ , showToolTipWhenElided(true)
+ , wrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere)
+ , jobTransfersVisible(false)
++ , emblemRect(QRect())
+ , animationHandler(new KIO::DelegateAnimationHandler(parent))
+ , activeMargins(nullptr)
+ {
+@@ -1153,6 +1156,7 @@ void KFileItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &opt
+ QPixmap icon = opt.icon.pixmap(opt.decorationSize, iconMode, iconState);
+
+ const KFileItem fileItem = d->fileItem(index);
++ const bool isDir = fileItem.isDir();
+ if (fileItem.isHidden()) {
+ KIconEffect::semiTransparent(icon);
+ }
+@@ -1176,6 +1180,7 @@ void KFileItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &opt
+ pixmap = d->transition(fadeFromPixmap, pixmap, state->fadeProgress());
+ }
+ painter->drawPixmap(option.rect.topLeft(), pixmap);
++ drawSelectionEmblem(option, painter, index);
+ if (d->jobTransfersVisible && index.column() == 0) {
+ if (index.data(KDirModel::HasJobRole).toBool()) {
+ d->paintJobTransfers(painter, state->jobAnimationAngle(), iconPos, opt);
+@@ -1251,6 +1256,7 @@ void KFileItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &opt
+ p.setRenderHint(QPainter::Antialiasing);
+ style->drawPrimitive(QStyle::PE_PanelItemViewItem, &opt, &p, opt.widget);
+ p.drawPixmap(iconPos, icon);
++ drawSelectionEmblem(option, painter, index);
+ d->drawTextItems(&p, labelLayout, labelColor, infoLayout, infoColor, textBoundingRect);
+ d->drawFocusRect(&p, opt, focusRect);
+ p.end();
+@@ -1263,6 +1269,7 @@ void KFileItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &opt
+ p.setRenderHint(QPainter::Antialiasing);
+ style->drawPrimitive(QStyle::PE_PanelItemViewItem, &opt, &p, opt.widget);
+ p.drawPixmap(iconPos, icon);
++ drawSelectionEmblem(option, painter, index);
+ d->drawTextItems(&p, labelLayout, labelColor, infoLayout, infoColor, textBoundingRect);
+ d->drawFocusRect(&p, opt, focusRect);
+ p.end();
+@@ -1282,6 +1289,7 @@ void KFileItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &opt
+ }
+
+ painter->drawPixmap(option.rect.topLeft(), pixmap);
++ drawSelectionEmblem(option, painter, index);
+ painter->setRenderHint(QPainter::Antialiasing);
+ if (d->jobTransfersVisible && index.column() == 0) {
+ if (index.data(KDirModel::HasJobRole).toBool()) {
+@@ -1306,6 +1314,7 @@ void KFileItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &opt
+
+ d->drawTextItems(painter, labelLayout, labelColor, infoLayout, infoColor, textBoundingRect);
+ d->drawFocusRect(painter, opt, focusRect);
++ drawSelectionEmblem(option, painter, index);
+
+ if (d->jobTransfersVisible && index.column() == 0 && state) {
+ if (index.data(KDirModel::HasJobRole).toBool()) {
+@@ -1315,6 +1324,33 @@ void KFileItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &opt
+ painter->restore();
+ }
+
++void KFileItemDelegate::drawSelectionEmblem(QStyleOptionViewItem option, QPainter *painter, const QModelIndex &index) const
++{
++ if (index.column() != 0 || !qApp->style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick)) {
++ return;
++ }
++ const auto state = option.state;
++ if ((state & QStyle::State_MouseOver && !fileItem(index).isDir()) || (state & QStyle::State_Selected)) {
++ const QString selectionEmblem = state & QStyle::State_Selected ? QStringLiteral("emblem-remove") : QStringLiteral("emblem-added");
++ const auto emblem = QIcon::fromTheme(selectionEmblem).pixmap(d->emblemRect.size(), state & QStyle::State_MouseOver ? QIcon::Active : QIcon::Disabled);
++
++ painter->drawPixmap(d->emblemRect.topLeft(), emblem);
++ }
++}
++
++int KFileItemDelegate::Private::scaledEmblemSize(int iconSize) const
++{
++ if (iconSize <= KIconLoader::SizeSmallMedium) {
++ return KIconLoader::SizeSmall;
++ } else if (iconSize <= KIconLoader::SizeHuge) {
++ return KIconLoader::SizeSmallMedium;
++ } else if (iconSize <= KIconLoader::SizeEnormous) {
++ return KIconLoader::SizeMedium;
++ }
++
++ return KIconLoader::SizeHuge;
++}
++
+ QWidget *KFileItemDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const
+ {
+ QStyleOptionViewItem opt(option);
+@@ -1551,4 +1587,26 @@ bool KFileItemDelegate::eventFilter(QObject *object, QEvent *event)
+ } // switch (event->type())
+ }
+
++void KFileItemDelegate::setSelectionEmblemRect(QRect rect, int iconSize)
++{
++ const auto emblemSize = d->scaledEmblemSize(iconSize);
++
++ // With small icons, try to center the emblem on top of the icon
++ if (iconSize <= KIconLoader::SizeSmallMedium) {
++ d->emblemRect = QRect(rect.topLeft().x() + emblemSize / 4, rect.topLeft().y() + emblemSize / 4, emblemSize, emblemSize);
++ } else {
++ d->emblemRect = QRect(rect.topLeft().x(), rect.topLeft().y(), emblemSize, emblemSize);
++ }
++}
++
++QRect KFileItemDelegate::selectionEmblemRect() const
++{
++ return d->emblemRect;
++}
++
++KFileItem KFileItemDelegate::fileItem(const QModelIndex &index) const
++{
++ return d->fileItem(index);
++}
++
+ #include "moc_kfileitemdelegate.cpp"
+diff --git a/src/widgets/kfileitemdelegate.h b/src/widgets/kfileitemdelegate.h
+index b4dd7eb05..83ecfe405 100644
+--- a/src/widgets/kfileitemdelegate.h
++++ b/src/widgets/kfileitemdelegate.h
+@@ -9,6 +9,7 @@
+ #define KFILEITEMDELEGATE_H
+
+ #include "kiowidgets_export.h"
++#include <KFileItem>
+ #include <QAbstractItemDelegate>
+ #include <QTextOption>
+
+@@ -398,6 +399,18 @@ public:
+ */
+ bool eventFilter(QObject *object, QEvent *event) override;
+
++ /**
++ * @return The rectangle where selectionEmblem is being drawn
++ */
++ QRect selectionEmblemRect() const;
++
++ /**
++ * Set the rectangle where selectionEmblem should be drawn in.
++ */
++ void setSelectionEmblemRect(QRect rect, int iconSize);
++
++ KFileItem fileItem(const QModelIndex &index) const;
++
+ public Q_SLOTS:
+ /**
+ * Reimplemented from @ref QAbstractItemDelegate.
+@@ -414,6 +427,8 @@ private:
+ class Private;
+ std::unique_ptr<Private> const d; /// @internal
+ Q_DISABLE_COPY(KFileItemDelegate)
++
++ void drawSelectionEmblem(QStyleOptionViewItem option, QPainter *painter, const QModelIndex &index) const;
+ };
+
+ #endif // KFILEITEMDELEGATE_H
+--
+GitLab
+
Reply to: