From 329e84b859525482a5a293ff8372bef95dc2f644 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?M=C3=A9ven=20Car?= <meven@kde.org>
Date: Mon, 10 Aug 2026 10:13:14 +0000
Subject: [PATCH 1/3] openurljob: open a shell script rather than refuse it as
 a program

shared-mime-info 2.5.1 renamed application/x-shellscript to
text/x-shellscript and left application/x-executable as its only parent,
where a script used to be a kind of text/plain as well.

Being both is how a script was told apart from a binary, so a shell script
now reads as a binary and opening one where running is not allowed ends in
an error rather than in the editor. The same loss took the text editors
away from scripts, so one that nothing else claims is opened with whatever
opens text.

BUG: 522948
FIXED-IN: 6.30
---
 src/gui/openurljob.cpp | 18 +++++++++++++++++-
 1 file changed, 17 insertions(+), 1 deletion(-)

diff --git a/src/gui/openurljob.cpp b/src/gui/openurljob.cpp
index 5fc0b93261..f3701661e4 100644
--- a/src/gui/openurljob.cpp
+++ b/src/gui/openurljob.cpp
@@ -367,7 +367,13 @@ static bool isBinary(const QMimeType &mimeType)
 // e.g. ".sh", ".csh", ".py", ".js"
 static bool isTextScript(const QMimeType &mimeType)
 {
-    return (mimeType.inherits(QStringLiteral("application/x-executable")) && mimeType.inherits(QStringLiteral("text/plain")));
+    if (!mimeType.inherits(QStringLiteral("application/x-executable"))) {
+        return false;
+    }
+
+    // A type under text/ holds text whether or not the database spells out that it is a
+    // kind of text/plain, and shared-mime-info stopped saying so for shell scripts.
+    return mimeType.inherits(QStringLiteral("text/plain")) || mimeType.name().startsWith(QLatin1String("text/"));
 }
 
 // Helper function that returns whether a file has the execute bit set or not.
@@ -645,6 +651,16 @@ void KIO::OpenUrlJobPrivate::handleScripts()
 void KIO::OpenUrlJobPrivate::openInPreferredApp()
 {
     KService::Ptr service = KApplicationTrader::preferredService(m_mimeTypeName);
+    if (!service) {
+        // A script is text, and the program someone picked for text is the one to show it
+        // to them in. The MIME database used to say a script is a kind of text/plain and
+        // no longer does for every script, so the text program is asked for by name.
+        const QMimeType mimeType = QMimeDatabase().mimeTypeForName(m_mimeTypeName);
+        if (isTextScript(mimeType)) {
+            service = KApplicationTrader::preferredService(QStringLiteral("text/plain"));
+        }
+    }
+
     if (service) {
         // If file mimetype is set to xdg-open or kde-open, the file will be opened in endless loop
         // In these cases, showOpenWithDialog instead
-- 
GitLab


From 71fcfa96e292a15d395b2c756bb03afff1063d90 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?M=C3=A9ven=20Car?= <meven@kde.org>
Date: Mon, 10 Aug 2026 10:13:14 +0000
Subject: [PATCH 2/3] autotests: ask the mime database which type a test means

shared-mime-info 2.5.1 made application/x-shellscript another name of
text/x-shellscript and made text/x-csrc a kind of text/x-c++src. Tests
that spell those names out fail on the renames alone, although the files
they look at are the same as ever.
---
 autotests/kdirlistertest.cpp        | 4 +++-
 autotests/mimetypefinderjobtest.cpp | 9 +++++++--
 2 files changed, 10 insertions(+), 3 deletions(-)

diff --git a/autotests/kdirlistertest.cpp b/autotests/kdirlistertest.cpp
index d45472757a..4fa88be50b 100644
--- a/autotests/kdirlistertest.cpp
+++ b/autotests/kdirlistertest.cpp
@@ -1536,7 +1536,9 @@ void KDirListerTest::testMimeFilter_data()
 
     const QStringList files = {"bla.txt", "main.cpp", "main.c", "image.jpeg", "picture.png"};
 
-    QTest::newRow("single_file_exact_mimetype") << files << QStringList{"text/x-c++src"} << QStringList{"main.cpp"};
+    // Not a C or C++ source: shared-mime-info made C source a kind of C++ source, so a
+    // filter on one of them takes both and says nothing about matching a type exactly.
+    QTest::newRow("single_file_exact_mimetype") << files << QStringList{"image/png"} << QStringList{"picture.png"};
     QTest::newRow("inherited_mimetype") << files << QStringList{"text/plain"} << QStringList{"bla.txt", "main.cpp", "main.c"};
     QTest::newRow("no_match") << files << QStringList{"audio/flac"} << QStringList{};
     QTest::newRow("glob") << files << QStringList{"image/*"} << QStringList{"image.jpeg", "picture.png"};
diff --git a/autotests/mimetypefinderjobtest.cpp b/autotests/mimetypefinderjobtest.cpp
index 9a41ac7fc3..0be00dd119 100644
--- a/autotests/mimetypefinderjobtest.cpp
+++ b/autotests/mimetypefinderjobtest.cpp
@@ -12,6 +12,7 @@
 
 #include <KConfigGroup>
 #include <KSharedConfig>
+#include <QMimeDatabase>
 #include <QStandardPaths>
 #include <QTemporaryDir>
 #include <QTest>
@@ -74,7 +75,11 @@ void MimeTypeFinderJobTest::determineMimeType()
     // When running a MimeTypeFinderJob
     KIO::MimeTypeFinderJob *job = new KIO::MimeTypeFinderJob(url, this);
     QVERIFY2(job->exec(), qPrintable(job->errorString()));
-    QCOMPARE(job->mimeType(), mimeType);
+    // The database gives a type one name of its own and knows it under others as well, so
+    // both sides are asked which type they mean rather than compared as they are spelled.
+    QMimeDatabase db;
+    const QString expected = db.mimeTypeForName(mimeType).name();
+    QCOMPARE(db.mimeTypeForName(job->mimeType()).name(), expected);
 
     // Check that the result is the same when accessing the source, skip on Windows
 #ifndef Q_OS_WIN
@@ -86,7 +91,7 @@ void MimeTypeFinderJobTest::determineMimeType()
 
     job = new KIO::MimeTypeFinderJob(linkUrl, this);
     QVERIFY2(job->exec(), qPrintable(job->errorString()));
-    QCOMPARE(job->mimeType(), mimeType);
+    QCOMPARE(db.mimeTypeForName(job->mimeType()).name(), expected);
 #endif
 }
 
From 92594ac39daeba6ffe117812055fcde2ae50d6b2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?M=C3=A9ven=20Car?= <meven@kde.org>
Date: Tue, 4 Aug 2026 16:00:51 +0000
Subject: [PATCH 3/3] openurljobtest: wait for the launched processes before ending
 a test

A KProcessRunner watches its process until it exits, then deletes itself. A
test that ends before that leaves the runner behind, and the CI leak checker
reports it, failing the test even though every case passed. Each case now
waits for the runners to be gone, as applicationlauncherjobtest and
commandlauncherjobtest already do.
---
 autotests/openurljobtest.cpp | 7 +++++++
 autotests/openurljobtest.h   | 1 +
 2 files changed, 8 insertions(+)

diff --git a/autotests/openurljobtest.cpp b/autotests/openurljobtest.cpp
index 456c086c1a..0cee9a6e79 100644
--- a/autotests/openurljobtest.cpp
+++ b/autotests/openurljobtest.cpp
@@ -91,6 +91,13 @@ void OpenUrlJobTest::init()
     QFile::remove(m_tempDir.path() + "/dest");
 }
 
+void OpenUrlJobTest::cleanup()
+{
+    // A launched process is watched by a runner that deletes itself once the process is gone. A test that
+    // ends before that leaves the runner behind, which the leak checker of the CI reports.
+    QTRY_COMPARE(KProcessRunner::instanceCount(), 0);
+}
+
 static void createSrcFile(const QString &path)
 {
     QFile srcFile(path);
diff --git a/autotests/openurljobtest.h b/autotests/openurljobtest.h
index b874d52e34..4c046b94e1 100644
--- a/autotests/openurljobtest.h
+++ b/autotests/openurljobtest.h
@@ -20,6 +20,7 @@ private Q_SLOTS:
     void initTestCase();
     void cleanupTestCase();
     void init();
+    void cleanup();
 
     void startProcess_data();
     void startProcess();
-- 
GitLab
