Add Fedora Tools settings module
This commit is contained in:
@@ -18,6 +18,7 @@ sudo dnf install touchpad-hold-tap
|
||||
sudo dnf install plasma-always-show-unlock
|
||||
sudo dnf install plasma-task-group-shortcuts
|
||||
sudo dnf install plasma-fingerprint-workaround
|
||||
sudo dnf install fedora-tools-settings
|
||||
```
|
||||
|
||||
## Compatibility
|
||||
@@ -29,6 +30,13 @@ systems, architectures, or distributions may be limited.
|
||||
|
||||
## Tools
|
||||
|
||||
### Fedora Tools settings
|
||||
|
||||
The optional `fedora-tools-settings` package adds a **Fedora Tools** page to
|
||||
Plasma System Settings. It discovers tools from this RPM repository, shows
|
||||
which are installed, and can install available tools after administrator
|
||||
authorization. Individual tools do not require the settings module.
|
||||
|
||||
### Touchpad hold-tap
|
||||
|
||||
Experimental. Recognizes a one-finger hold followed by a quick tap from a
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
project(fedora-tools-settings VERSION 0.1.0 LANGUAGES CXX)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 20)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
find_package(ECM 6.0 REQUIRED NO_MODULE)
|
||||
set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH})
|
||||
|
||||
include(CTest)
|
||||
include(KDEInstallDirs)
|
||||
include(KDECMakeSettings)
|
||||
include(KDECompilerSettings NO_POLICY_SCOPE)
|
||||
|
||||
find_package(Qt6 6.8 REQUIRED COMPONENTS Core Quick Test)
|
||||
find_package(KF6 6.0 REQUIRED COMPONENTS Auth CoreAddons I18n KCMUtils)
|
||||
|
||||
add_subdirectory(src)
|
||||
if(BUILD_TESTING)
|
||||
add_subdirectory(tests)
|
||||
endif()
|
||||
@@ -0,0 +1,22 @@
|
||||
# Fedora Tools settings
|
||||
|
||||
This optional KDE Configuration Module lists tools published in the configured
|
||||
`fedora-tools` RPM repository. It appears as **Fedora Tools** in Plasma System
|
||||
Settings and can also be opened directly:
|
||||
|
||||
```bash
|
||||
kcmshell6 kcm_fedora_tools
|
||||
```
|
||||
|
||||
Packages advertise themselves with the RPM capability `fedora-tools-tool`.
|
||||
The module uses DNF's repository metadata for discovery and delegates package
|
||||
installation to a narrowly scoped KAuth helper. The helper accepts only valid
|
||||
package names that independently resolve to that capability in the
|
||||
`fedora-tools` repository.
|
||||
|
||||
Installing this module does not install or activate any other tool. Tools
|
||||
remain usable without the module.
|
||||
|
||||
The fingerprint-workaround page can enable the tested build, select a locally
|
||||
built RPM, allow an intentional version change, and restore Fedora's official
|
||||
KScreenLocker package. These operations require administrator authentication.
|
||||
@@ -0,0 +1,62 @@
|
||||
Name: fedora-tools-settings
|
||||
Version: 0.1.0
|
||||
Release: 1%{?dist}
|
||||
Summary: Plasma System Settings module for Fedora Tools
|
||||
|
||||
License: MIT
|
||||
URL: https://git.ajpanton.se/ajp_anton/fedora-tools
|
||||
Source0: %{name}-%{version}.tar.gz
|
||||
Source1: LICENSE
|
||||
Source2: README.md
|
||||
|
||||
BuildRequires: cmake
|
||||
BuildRequires: extra-cmake-modules
|
||||
BuildRequires: gcc-c++
|
||||
BuildRequires: kf6-kauth-devel
|
||||
BuildRequires: kf6-kcmutils-devel
|
||||
BuildRequires: kf6-ki18n-devel
|
||||
BuildRequires: kf6-kirigami-devel
|
||||
BuildRequires: qt6-qtdeclarative-devel
|
||||
|
||||
Requires: dnf5
|
||||
Requires: kf6-kauth
|
||||
Requires: kf6-kcmutils
|
||||
Requires: kf6-kirigami
|
||||
Requires: plasma-systemsettings
|
||||
Requires: rpm
|
||||
|
||||
%description
|
||||
Adds a Fedora Tools page to KDE Plasma System Settings. It discovers tools from
|
||||
the configured RPM repository, reports installation state, and can install
|
||||
tools after administrator authorization.
|
||||
|
||||
%prep
|
||||
%autosetup
|
||||
|
||||
%build
|
||||
%cmake
|
||||
%cmake_build
|
||||
|
||||
%check
|
||||
%ctest
|
||||
|
||||
%install
|
||||
%cmake_install
|
||||
install -Dpm 0644 %{SOURCE1} \
|
||||
%{buildroot}%{_licensedir}/%{name}/LICENSE
|
||||
install -Dpm 0644 %{SOURCE2} \
|
||||
%{buildroot}%{_docdir}/%{name}/README.md
|
||||
|
||||
%files
|
||||
%license %{_licensedir}/%{name}/LICENSE
|
||||
%doc %{_docdir}/%{name}/README.md
|
||||
%{_libdir}/qt6/plugins/plasma/kcms/systemsettings/kcm_fedora_tools.so
|
||||
%{_libexecdir}/kf6/kauth/fedora-tools-settings-helper
|
||||
%{_datadir}/applications/kcm_fedora_tools.desktop
|
||||
%{_datadir}/dbus-1/system-services/se.ajpanton.fedoratools.service
|
||||
%{_datadir}/dbus-1/system.d/se.ajpanton.fedoratools.conf
|
||||
%{_datadir}/polkit-1/actions/se.ajpanton.fedoratools.policy
|
||||
|
||||
%changelog
|
||||
* Sat Sep 05 2026 fedora-tools contributors - 0.1.0-1
|
||||
- Initial package
|
||||
@@ -0,0 +1,42 @@
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
kcmutils_add_qml_kcm(kcm_fedora_tools
|
||||
SOURCES
|
||||
fedoratoolskcm.cpp
|
||||
fedoratoolskcm.h
|
||||
packageutils.cpp
|
||||
packageutils.h
|
||||
toolmodel.cpp
|
||||
toolmodel.h
|
||||
)
|
||||
target_link_libraries(kcm_fedora_tools PRIVATE
|
||||
Qt6::Core
|
||||
Qt6::Quick
|
||||
KF6::AuthCore
|
||||
KF6::CoreAddons
|
||||
KF6::I18n
|
||||
KF6::KCMUtilsQuick
|
||||
)
|
||||
|
||||
add_executable(fedora-tools-settings-helper
|
||||
helper.cpp
|
||||
packageutils.cpp
|
||||
packageutils.h
|
||||
)
|
||||
target_link_libraries(fedora-tools-settings-helper PRIVATE
|
||||
Qt6::Core
|
||||
KF6::AuthCore
|
||||
)
|
||||
|
||||
install(TARGETS fedora-tools-settings-helper
|
||||
DESTINATION ${KAUTH_HELPER_INSTALL_DIR}
|
||||
)
|
||||
kauth_install_helper_files(
|
||||
fedora-tools-settings-helper
|
||||
se.ajpanton.fedoratools
|
||||
root
|
||||
)
|
||||
kauth_install_actions(
|
||||
se.ajpanton.fedoratools
|
||||
se.ajpanton.fedoratools.actions
|
||||
)
|
||||
@@ -0,0 +1,323 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include "fedoratoolskcm.h"
|
||||
|
||||
#include <KAuth/Action>
|
||||
#include <KAuth/ExecuteJob>
|
||||
#include <KJob>
|
||||
#include <KLocalizedString>
|
||||
#include <KPluginFactory>
|
||||
|
||||
#include <QTimer>
|
||||
|
||||
#include <algorithm>
|
||||
#include <utility>
|
||||
|
||||
namespace
|
||||
{
|
||||
const QString packageFormat = QStringLiteral("%{name}\t%{evr}\t%{arch}\t%{summary}\n");
|
||||
const QString helperId = QStringLiteral("se.ajpanton.fedoratools");
|
||||
}
|
||||
|
||||
K_PLUGIN_CLASS_WITH_JSON(FedoraToolsKcm, "kcm_fedora_tools.json")
|
||||
|
||||
FedoraToolsKcm::FedoraToolsKcm(QObject *parent, const KPluginMetaData &data)
|
||||
: KQuickConfigModule(parent, data)
|
||||
, m_tools(this)
|
||||
{
|
||||
setButtons(NoAdditionalButton);
|
||||
connect(&m_query, &QProcess::finished, this, &FedoraToolsKcm::queryFinished);
|
||||
connect(&m_query, &QProcess::errorOccurred, this, [this](QProcess::ProcessError processError) {
|
||||
if (processError != QProcess::FailedToStart) {
|
||||
return;
|
||||
}
|
||||
setMessage(i18n("Could not start %1.", m_query.program()), true);
|
||||
m_stage = QueryStage::Idle;
|
||||
setBusy(false);
|
||||
});
|
||||
QTimer::singleShot(0, this, [this] {
|
||||
refresh();
|
||||
});
|
||||
}
|
||||
|
||||
QAbstractItemModel *FedoraToolsKcm::tools()
|
||||
{
|
||||
return &m_tools;
|
||||
}
|
||||
|
||||
bool FedoraToolsKcm::busy() const
|
||||
{
|
||||
return m_busy;
|
||||
}
|
||||
|
||||
QString FedoraToolsKcm::message() const
|
||||
{
|
||||
return m_message;
|
||||
}
|
||||
|
||||
bool FedoraToolsKcm::error() const
|
||||
{
|
||||
return m_error;
|
||||
}
|
||||
|
||||
bool FedoraToolsKcm::fingerprintEnabled() const
|
||||
{
|
||||
return m_fingerprintEnabled;
|
||||
}
|
||||
|
||||
void FedoraToolsKcm::refresh(bool refreshMetadata)
|
||||
{
|
||||
if (m_busy) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_refreshMetadata = refreshMetadata;
|
||||
m_installedPackages.clear();
|
||||
m_availablePackages.clear();
|
||||
m_updates.clear();
|
||||
setMessage({});
|
||||
setBusy(true);
|
||||
|
||||
startQuery(QStringLiteral("/usr/bin/rpm"),
|
||||
{QStringLiteral("-q"),
|
||||
QStringLiteral("--whatprovides"),
|
||||
QStringLiteral("fedora-tools-tool"),
|
||||
QStringLiteral("--qf"),
|
||||
packageFormat},
|
||||
QueryStage::InstalledProviders);
|
||||
}
|
||||
|
||||
void FedoraToolsKcm::clearMessage()
|
||||
{
|
||||
setMessage({});
|
||||
}
|
||||
|
||||
void FedoraToolsKcm::installTool(const QString &packageName)
|
||||
{
|
||||
if (m_busy || !m_tools.mayInstall(packageName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
runAuthorizedAction(QStringLiteral("installpackage"),
|
||||
{{QStringLiteral("packageName"), packageName}},
|
||||
i18n("%1 is now installed.", packageName),
|
||||
true);
|
||||
}
|
||||
|
||||
void FedoraToolsKcm::enableFingerprintWorkaround(const QUrl &rpmFile, bool force)
|
||||
{
|
||||
if (m_busy) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!rpmFile.isEmpty() && !rpmFile.isLocalFile()) {
|
||||
setMessage(i18n("Select a local RPM file."), true);
|
||||
return;
|
||||
}
|
||||
|
||||
QVariantMap arguments{
|
||||
{QStringLiteral("operation"), QStringLiteral("enable")},
|
||||
{QStringLiteral("force"), force},
|
||||
};
|
||||
if (rpmFile.isLocalFile()) {
|
||||
arguments.insert(QStringLiteral("rpmPath"), rpmFile.toLocalFile());
|
||||
}
|
||||
|
||||
runAuthorizedAction(QStringLiteral("fingerprintworkaround"),
|
||||
arguments,
|
||||
i18n("The fingerprint workaround was enabled. Reboot before testing it."),
|
||||
false);
|
||||
}
|
||||
|
||||
void FedoraToolsKcm::disableFingerprintWorkaround()
|
||||
{
|
||||
if (m_busy) {
|
||||
return;
|
||||
}
|
||||
|
||||
runAuthorizedAction(QStringLiteral("fingerprintworkaround"),
|
||||
{{QStringLiteral("operation"), QStringLiteral("disable")}},
|
||||
i18n("The fingerprint workaround was disabled. Reboot before testing the lock screen."),
|
||||
false);
|
||||
}
|
||||
|
||||
void FedoraToolsKcm::startQuery(const QString &program, const QStringList &arguments, QueryStage stage)
|
||||
{
|
||||
m_stage = stage;
|
||||
m_query.start(program, arguments);
|
||||
}
|
||||
|
||||
void FedoraToolsKcm::queryFinished(int exitCode, QProcess::ExitStatus exitStatus)
|
||||
{
|
||||
const QByteArray output = m_query.readAllStandardOutput();
|
||||
const QByteArray errorOutput = m_query.readAllStandardError();
|
||||
|
||||
if (exitStatus != QProcess::NormalExit) {
|
||||
setMessage(i18n("Package discovery terminated unexpectedly."), true);
|
||||
m_stage = QueryStage::Idle;
|
||||
setBusy(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_stage == QueryStage::InstalledProviders) {
|
||||
m_installedPackages = parsePackageRecords(output);
|
||||
|
||||
QStringList arguments{QStringLiteral("--repo=fedora-tools"), QStringLiteral("-q")};
|
||||
if (m_refreshMetadata) {
|
||||
arguments.append(QStringLiteral("--refresh"));
|
||||
}
|
||||
arguments.append({QStringLiteral("repoquery"),
|
||||
QStringLiteral("--available"),
|
||||
QStringLiteral("--latest-limit=1"),
|
||||
QStringLiteral("--whatprovides=fedora-tools-tool"),
|
||||
QStringLiteral("--queryformat"),
|
||||
packageFormat});
|
||||
startQuery(QStringLiteral("/usr/bin/dnf5"), arguments, QueryStage::Available);
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_stage == QueryStage::Available) {
|
||||
if (exitCode != 0) {
|
||||
m_tools.setPackages(m_installedPackages, {});
|
||||
const QString details = QString::fromUtf8(errorOutput).trimmed();
|
||||
setMessage(details.isEmpty() ? i18n("The Fedora Tools repository is unavailable.") : details, true);
|
||||
updateFingerprintStatus();
|
||||
m_stage = QueryStage::Idle;
|
||||
setBusy(false);
|
||||
return;
|
||||
}
|
||||
|
||||
m_availablePackages = parsePackageRecords(output);
|
||||
if (m_availablePackages.isEmpty()) {
|
||||
finishRefresh();
|
||||
return;
|
||||
}
|
||||
|
||||
startQuery(QStringLiteral("/usr/bin/dnf5"),
|
||||
{QStringLiteral("--repo=fedora-tools"),
|
||||
QStringLiteral("-q"),
|
||||
QStringLiteral("repoquery"),
|
||||
QStringLiteral("--available"),
|
||||
QStringLiteral("--upgrades"),
|
||||
QStringLiteral("--whatprovides=fedora-tools-tool"),
|
||||
QStringLiteral("--queryformat"),
|
||||
packageFormat},
|
||||
QueryStage::Updates);
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_stage == QueryStage::Updates) {
|
||||
if (exitCode == 0) {
|
||||
for (const PackageRecord &package : parsePackageRecords(output)) {
|
||||
m_updates.insert(package.name);
|
||||
}
|
||||
}
|
||||
|
||||
QStringList packageNames;
|
||||
for (const PackageRecord &package : std::as_const(m_availablePackages)) {
|
||||
packageNames.append(package.name);
|
||||
}
|
||||
QStringList arguments{QStringLiteral("-q"), QStringLiteral("--qf"), packageFormat};
|
||||
arguments.append(packageNames);
|
||||
startQuery(QStringLiteral("/usr/bin/rpm"), arguments, QueryStage::KnownInstalled);
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_stage == QueryStage::KnownInstalled) {
|
||||
const QList<PackageRecord> knownInstalled = parsePackageRecords(output);
|
||||
for (const PackageRecord &package : knownInstalled) {
|
||||
auto existing = std::find_if(m_installedPackages.begin(), m_installedPackages.end(), [&package](const PackageRecord &candidate) {
|
||||
return candidate.name == package.name;
|
||||
});
|
||||
if (existing == m_installedPackages.end()) {
|
||||
m_installedPackages.append(package);
|
||||
} else {
|
||||
*existing = package;
|
||||
}
|
||||
}
|
||||
finishRefresh();
|
||||
}
|
||||
}
|
||||
|
||||
void FedoraToolsKcm::finishRefresh()
|
||||
{
|
||||
m_tools.setPackages(m_installedPackages, m_availablePackages, m_updates);
|
||||
updateFingerprintStatus();
|
||||
m_stage = QueryStage::Idle;
|
||||
setBusy(false);
|
||||
}
|
||||
|
||||
void FedoraToolsKcm::runAuthorizedAction(const QString &name,
|
||||
const QVariantMap &arguments,
|
||||
const QString &successMessage,
|
||||
bool refreshTools)
|
||||
{
|
||||
setMessage({});
|
||||
setBusy(true);
|
||||
|
||||
KAuth::Action action(helperId + QLatin1Char('.') + name);
|
||||
action.setHelperId(helperId);
|
||||
action.setArguments(arguments);
|
||||
action.setTimeout(10 * 60 * 1000);
|
||||
|
||||
KAuth::ExecuteJob *job = action.execute();
|
||||
connect(job, &KJob::result, this, [this, successMessage, refreshTools](KJob *finishedJob) {
|
||||
actionFinished(finishedJob, successMessage, refreshTools);
|
||||
});
|
||||
job->start();
|
||||
}
|
||||
|
||||
void FedoraToolsKcm::actionFinished(KJob *job, const QString &successMessage, bool refreshTools)
|
||||
{
|
||||
auto *executeJob = qobject_cast<KAuth::ExecuteJob *>(job);
|
||||
if (job->error()) {
|
||||
const QString helperMessage = executeJob ? executeJob->data().value(QStringLiteral("message")).toString() : QString();
|
||||
const QString errorMessage = helperMessage.isEmpty() ? job->errorString() : helperMessage;
|
||||
setMessage(errorMessage, true);
|
||||
setBusy(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (refreshTools) {
|
||||
setBusy(false);
|
||||
refresh();
|
||||
} else {
|
||||
updateFingerprintStatus();
|
||||
setBusy(false);
|
||||
}
|
||||
setMessage(successMessage);
|
||||
}
|
||||
|
||||
void FedoraToolsKcm::setBusy(bool busy)
|
||||
{
|
||||
if (m_busy == busy) {
|
||||
return;
|
||||
}
|
||||
m_busy = busy;
|
||||
Q_EMIT busyChanged();
|
||||
}
|
||||
|
||||
void FedoraToolsKcm::setMessage(const QString &message, bool error)
|
||||
{
|
||||
if (m_message == message && m_error == error) {
|
||||
return;
|
||||
}
|
||||
m_message = message;
|
||||
m_error = error;
|
||||
Q_EMIT messageChanged();
|
||||
}
|
||||
|
||||
void FedoraToolsKcm::updateFingerprintStatus()
|
||||
{
|
||||
QProcess process;
|
||||
process.start(QStringLiteral("/usr/bin/plasma-fingerprint-workaround"), {QStringLiteral("status")});
|
||||
const bool finished = process.waitForFinished(2000);
|
||||
const bool enabled = finished && process.exitStatus() == QProcess::NormalExit && process.exitCode() == 0
|
||||
&& process.readAllStandardOutput().startsWith("Fingerprint workaround: enabled");
|
||||
if (m_fingerprintEnabled != enabled) {
|
||||
m_fingerprintEnabled = enabled;
|
||||
Q_EMIT fingerprintEnabledChanged();
|
||||
}
|
||||
}
|
||||
|
||||
#include "fedoratoolskcm.moc"
|
||||
@@ -0,0 +1,74 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "packageutils.h"
|
||||
#include "toolmodel.h"
|
||||
|
||||
#include <KQuickConfigModule>
|
||||
|
||||
#include <QProcess>
|
||||
#include <QSet>
|
||||
#include <QUrl>
|
||||
|
||||
class KJob;
|
||||
|
||||
class FedoraToolsKcm : public KQuickConfigModule
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(QAbstractItemModel *tools READ tools CONSTANT)
|
||||
Q_PROPERTY(bool busy READ busy NOTIFY busyChanged)
|
||||
Q_PROPERTY(QString message READ message NOTIFY messageChanged)
|
||||
Q_PROPERTY(bool error READ error NOTIFY messageChanged)
|
||||
Q_PROPERTY(bool fingerprintEnabled READ fingerprintEnabled NOTIFY fingerprintEnabledChanged)
|
||||
|
||||
public:
|
||||
FedoraToolsKcm(QObject *parent, const KPluginMetaData &data);
|
||||
|
||||
QAbstractItemModel *tools();
|
||||
bool busy() const;
|
||||
QString message() const;
|
||||
bool error() const;
|
||||
bool fingerprintEnabled() const;
|
||||
|
||||
Q_INVOKABLE void refresh(bool refreshMetadata = false);
|
||||
Q_INVOKABLE void clearMessage();
|
||||
Q_INVOKABLE void installTool(const QString &packageName);
|
||||
Q_INVOKABLE void enableFingerprintWorkaround(const QUrl &rpmFile, bool force);
|
||||
Q_INVOKABLE void disableFingerprintWorkaround();
|
||||
|
||||
Q_SIGNALS:
|
||||
void busyChanged();
|
||||
void messageChanged();
|
||||
void fingerprintEnabledChanged();
|
||||
|
||||
private:
|
||||
enum class QueryStage {
|
||||
Idle,
|
||||
InstalledProviders,
|
||||
Available,
|
||||
Updates,
|
||||
KnownInstalled,
|
||||
};
|
||||
|
||||
void startQuery(const QString &program, const QStringList &arguments, QueryStage stage);
|
||||
void queryFinished(int exitCode, QProcess::ExitStatus exitStatus);
|
||||
void finishRefresh();
|
||||
void runAuthorizedAction(const QString &name, const QVariantMap &arguments, const QString &successMessage, bool refreshTools);
|
||||
void actionFinished(KJob *job, const QString &successMessage, bool refreshTools);
|
||||
void setBusy(bool busy);
|
||||
void setMessage(const QString &message, bool error = false);
|
||||
void updateFingerprintStatus();
|
||||
|
||||
ToolModel m_tools;
|
||||
QProcess m_query;
|
||||
QueryStage m_stage = QueryStage::Idle;
|
||||
QList<PackageRecord> m_installedPackages;
|
||||
QList<PackageRecord> m_availablePackages;
|
||||
QSet<QString> m_updates;
|
||||
bool m_refreshMetadata = false;
|
||||
bool m_busy = false;
|
||||
bool m_error = false;
|
||||
bool m_fingerprintEnabled = false;
|
||||
QString m_message;
|
||||
};
|
||||
@@ -0,0 +1,134 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include "packageutils.h"
|
||||
|
||||
#include <KAuth/ActionReply>
|
||||
#include <KAuth/HelperSupport>
|
||||
|
||||
#include <QFileInfo>
|
||||
#include <QObject>
|
||||
#include <QProcess>
|
||||
|
||||
namespace
|
||||
{
|
||||
constexpr int commandTimeoutMs = 10 * 60 * 1000;
|
||||
|
||||
KAuth::ActionReply commandReply(const QString &program, const QStringList &arguments)
|
||||
{
|
||||
QProcess process;
|
||||
process.setProcessChannelMode(QProcess::MergedChannels);
|
||||
process.start(program, arguments);
|
||||
if (!process.waitForStarted()) {
|
||||
KAuth::ActionReply reply = KAuth::ActionReply::HelperErrorReply();
|
||||
reply.addData(QStringLiteral("message"), QStringLiteral("Could not start %1.").arg(program));
|
||||
return reply;
|
||||
}
|
||||
|
||||
if (!process.waitForFinished(commandTimeoutMs)) {
|
||||
process.kill();
|
||||
process.waitForFinished();
|
||||
KAuth::ActionReply reply = KAuth::ActionReply::HelperErrorReply();
|
||||
reply.addData(QStringLiteral("message"), QStringLiteral("The operation timed out."));
|
||||
return reply;
|
||||
}
|
||||
|
||||
QString output = QString::fromUtf8(process.readAll()).trimmed();
|
||||
if (output.size() > 16 * 1024) {
|
||||
output = output.right(16 * 1024);
|
||||
}
|
||||
|
||||
if (process.exitStatus() != QProcess::NormalExit || process.exitCode() != 0) {
|
||||
KAuth::ActionReply reply = KAuth::ActionReply::HelperErrorReply(process.exitCode());
|
||||
reply.addData(QStringLiteral("message"), output.isEmpty() ? QStringLiteral("The operation failed.") : output);
|
||||
return reply;
|
||||
}
|
||||
|
||||
KAuth::ActionReply reply = KAuth::ActionReply::SuccessReply();
|
||||
reply.addData(QStringLiteral("output"), output);
|
||||
return reply;
|
||||
}
|
||||
|
||||
bool isPublishedTool(const QString &packageName)
|
||||
{
|
||||
QProcess process;
|
||||
process.start(QStringLiteral("/usr/bin/dnf5"),
|
||||
{QStringLiteral("--repo=fedora-tools"),
|
||||
QStringLiteral("-q"),
|
||||
QStringLiteral("repoquery"),
|
||||
QStringLiteral("--available"),
|
||||
QStringLiteral("--whatprovides=fedora-tools-tool"),
|
||||
QStringLiteral("--queryformat"),
|
||||
QStringLiteral("%{name}\n"),
|
||||
packageName});
|
||||
if (!process.waitForFinished(60 * 1000) || process.exitCode() != 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const QByteArray &line : process.readAllStandardOutput().split('\n')) {
|
||||
if (QString::fromUtf8(line) == packageName) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
class FedoraToolsHelper : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public Q_SLOTS:
|
||||
KAuth::ActionReply installpackage(const QVariantMap &arguments)
|
||||
{
|
||||
const QString packageName = arguments.value(QStringLiteral("packageName")).toString();
|
||||
if (!isValidPackageName(packageName) || !isPublishedTool(packageName)) {
|
||||
KAuth::ActionReply reply = KAuth::ActionReply::HelperErrorReply();
|
||||
reply.addData(QStringLiteral("message"), QStringLiteral("The requested package is not a published Fedora tool."));
|
||||
return reply;
|
||||
}
|
||||
|
||||
return commandReply(QStringLiteral("/usr/bin/dnf5"),
|
||||
{QStringLiteral("install"),
|
||||
QStringLiteral("--assumeyes"),
|
||||
QStringLiteral("--from-repo=fedora-tools"),
|
||||
packageName});
|
||||
}
|
||||
|
||||
KAuth::ActionReply fingerprintworkaround(const QVariantMap &arguments)
|
||||
{
|
||||
const QString operation = arguments.value(QStringLiteral("operation")).toString();
|
||||
if (operation != QLatin1String("enable") && operation != QLatin1String("disable")) {
|
||||
return KAuth::ActionReply::InvalidActionReply();
|
||||
}
|
||||
|
||||
const QString controller = QStringLiteral("/usr/bin/plasma-fingerprint-workaround");
|
||||
if (!QFileInfo(controller).isExecutable()) {
|
||||
KAuth::ActionReply reply = KAuth::ActionReply::HelperErrorReply();
|
||||
reply.addData(QStringLiteral("message"), QStringLiteral("The fingerprint workaround controller is not installed."));
|
||||
return reply;
|
||||
}
|
||||
|
||||
QStringList commandArguments{operation};
|
||||
if (operation == QLatin1String("enable")) {
|
||||
const QString rpmPath = arguments.value(QStringLiteral("rpmPath")).toString();
|
||||
if (!rpmPath.isEmpty()) {
|
||||
if (!QFileInfo(rpmPath).isAbsolute() || !QFileInfo(rpmPath).isFile()) {
|
||||
KAuth::ActionReply reply = KAuth::ActionReply::HelperErrorReply();
|
||||
reply.addData(QStringLiteral("message"), QStringLiteral("The selected RPM does not exist."));
|
||||
return reply;
|
||||
}
|
||||
commandArguments.append({QStringLiteral("--rpm"), rpmPath});
|
||||
}
|
||||
if (arguments.value(QStringLiteral("force")).toBool()) {
|
||||
commandArguments.append(QStringLiteral("--force"));
|
||||
}
|
||||
}
|
||||
commandArguments.append(QStringLiteral("--yes"));
|
||||
|
||||
return commandReply(controller, commandArguments);
|
||||
}
|
||||
};
|
||||
|
||||
KAUTH_HELPER_MAIN("se.ajpanton.fedoratools", FedoraToolsHelper)
|
||||
|
||||
#include "helper.moc"
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"KPlugin": {
|
||||
"Authors": [
|
||||
{
|
||||
"Name": "fedora-tools contributors"
|
||||
}
|
||||
],
|
||||
"Description": "Install and configure small Fedora tools",
|
||||
"Icon": "preferences-system",
|
||||
"License": "MIT",
|
||||
"Name": "Fedora Tools",
|
||||
"Version": "0.1.0",
|
||||
"Website": "https://git.ajpanton.se/ajp_anton/fedora-tools"
|
||||
},
|
||||
"X-KDE-Keywords": "Fedora,tools,utilities",
|
||||
"X-KDE-System-Settings-Parent-Category": "system-administration"
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include "packageutils.h"
|
||||
|
||||
#include <QRegularExpression>
|
||||
|
||||
#include <utility>
|
||||
|
||||
QList<PackageRecord> parsePackageRecords(const QByteArray &output)
|
||||
{
|
||||
QList<PackageRecord> records;
|
||||
|
||||
for (const QByteArray &line : output.split('\n')) {
|
||||
if (line.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const QList<QByteArray> fields = line.split('\t');
|
||||
if (fields.size() < 4) {
|
||||
continue;
|
||||
}
|
||||
|
||||
PackageRecord record;
|
||||
record.name = QString::fromUtf8(fields.at(0));
|
||||
record.version = QString::fromUtf8(fields.at(1));
|
||||
record.architecture = QString::fromUtf8(fields.at(2));
|
||||
record.summary = QString::fromUtf8(fields.mid(3).join('\t'));
|
||||
if (isValidPackageName(record.name)) {
|
||||
records.append(std::move(record));
|
||||
}
|
||||
}
|
||||
|
||||
return records;
|
||||
}
|
||||
|
||||
bool isValidPackageName(const QString &name)
|
||||
{
|
||||
static const QRegularExpression expression(QStringLiteral("^[a-z0-9][a-z0-9+._-]*$"));
|
||||
return expression.match(name).hasMatch();
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QList>
|
||||
#include <QString>
|
||||
|
||||
struct PackageRecord
|
||||
{
|
||||
QString name;
|
||||
QString version;
|
||||
QString architecture;
|
||||
QString summary;
|
||||
};
|
||||
|
||||
QList<PackageRecord> parsePackageRecords(const QByteArray &output);
|
||||
bool isValidPackageName(const QString &name);
|
||||
@@ -0,0 +1,13 @@
|
||||
[Domain]
|
||||
Name=Fedora Tools
|
||||
Icon=preferences-system
|
||||
|
||||
[se.ajpanton.fedoratools.installpackage]
|
||||
Name=Install a Fedora tool
|
||||
Description=Install a package from the Fedora Tools repository
|
||||
Policy=auth_admin
|
||||
|
||||
[se.ajpanton.fedoratools.fingerprintworkaround]
|
||||
Name=Change the Plasma fingerprint workaround
|
||||
Description=Install or remove the experimental patched KScreenLocker package
|
||||
Policy=auth_admin
|
||||
@@ -0,0 +1,109 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include "toolmodel.h"
|
||||
|
||||
#include <QMap>
|
||||
|
||||
#include <algorithm>
|
||||
#include <utility>
|
||||
|
||||
ToolModel::ToolModel(QObject *parent)
|
||||
: QAbstractListModel(parent)
|
||||
{
|
||||
}
|
||||
|
||||
int ToolModel::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
return parent.isValid() ? 0 : m_tools.size();
|
||||
}
|
||||
|
||||
QVariant ToolModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
if (!index.isValid() || index.row() < 0 || index.row() >= m_tools.size()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const Tool &tool = m_tools.at(index.row());
|
||||
switch (role) {
|
||||
case PackageNameRole:
|
||||
return tool.packageName;
|
||||
case VersionRole:
|
||||
return tool.version;
|
||||
case AvailableVersionRole:
|
||||
return tool.availableVersion;
|
||||
case ArchitectureRole:
|
||||
return tool.architecture;
|
||||
case SummaryRole:
|
||||
return tool.summary;
|
||||
case InstalledRole:
|
||||
return tool.installed;
|
||||
case AvailableRole:
|
||||
return tool.available;
|
||||
case UpdateAvailableRole:
|
||||
return tool.updateAvailable;
|
||||
case ConfigurableRole:
|
||||
return tool.installed && tool.packageName == QStringLiteral("plasma-fingerprint-workaround");
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
QHash<int, QByteArray> ToolModel::roleNames() const
|
||||
{
|
||||
return {
|
||||
{PackageNameRole, "packageName"},
|
||||
{VersionRole, "version"},
|
||||
{AvailableVersionRole, "availableVersion"},
|
||||
{ArchitectureRole, "architecture"},
|
||||
{SummaryRole, "summary"},
|
||||
{InstalledRole, "installed"},
|
||||
{AvailableRole, "available"},
|
||||
{UpdateAvailableRole, "updateAvailable"},
|
||||
{ConfigurableRole, "configurable"},
|
||||
};
|
||||
}
|
||||
|
||||
void ToolModel::setPackages(const QList<PackageRecord> &installed,
|
||||
const QList<PackageRecord> &available,
|
||||
const QSet<QString> &updates)
|
||||
{
|
||||
QMap<QString, Tool> tools;
|
||||
|
||||
for (const PackageRecord &package : installed) {
|
||||
Tool &tool = tools[package.name];
|
||||
tool.packageName = package.name;
|
||||
tool.version = package.version;
|
||||
tool.architecture = package.architecture;
|
||||
tool.summary = package.summary;
|
||||
tool.installed = true;
|
||||
}
|
||||
|
||||
for (const PackageRecord &package : available) {
|
||||
Tool &tool = tools[package.name];
|
||||
tool.packageName = package.name;
|
||||
tool.availableVersion = package.version;
|
||||
tool.architecture = package.architecture;
|
||||
tool.summary = package.summary;
|
||||
tool.available = true;
|
||||
tool.updateAvailable = tool.installed && updates.contains(package.name);
|
||||
}
|
||||
|
||||
QList<Tool> merged = tools.values();
|
||||
std::ranges::sort(merged, [](const Tool &left, const Tool &right) {
|
||||
if (left.installed != right.installed) {
|
||||
return left.installed > right.installed;
|
||||
}
|
||||
return left.packageName < right.packageName;
|
||||
});
|
||||
|
||||
beginResetModel();
|
||||
m_tools = std::move(merged);
|
||||
endResetModel();
|
||||
}
|
||||
|
||||
bool ToolModel::mayInstall(const QString &packageName) const
|
||||
{
|
||||
return std::ranges::any_of(m_tools, [&packageName](const Tool &tool) {
|
||||
return tool.packageName == packageName && tool.available;
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "packageutils.h"
|
||||
|
||||
#include <QAbstractListModel>
|
||||
#include <QSet>
|
||||
|
||||
struct Tool
|
||||
{
|
||||
QString packageName;
|
||||
QString version;
|
||||
QString availableVersion;
|
||||
QString architecture;
|
||||
QString summary;
|
||||
bool installed = false;
|
||||
bool available = false;
|
||||
bool updateAvailable = false;
|
||||
};
|
||||
|
||||
class ToolModel : public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
enum Role {
|
||||
PackageNameRole = Qt::UserRole + 1,
|
||||
VersionRole,
|
||||
AvailableVersionRole,
|
||||
ArchitectureRole,
|
||||
SummaryRole,
|
||||
InstalledRole,
|
||||
AvailableRole,
|
||||
UpdateAvailableRole,
|
||||
ConfigurableRole,
|
||||
};
|
||||
|
||||
explicit ToolModel(QObject *parent = nullptr);
|
||||
|
||||
int rowCount(const QModelIndex &parent = {}) const override;
|
||||
QVariant data(const QModelIndex &index, int role) const override;
|
||||
QHash<int, QByteArray> roleNames() const override;
|
||||
|
||||
void setPackages(const QList<PackageRecord> &installed,
|
||||
const QList<PackageRecord> &available,
|
||||
const QSet<QString> &updates = {});
|
||||
bool mayInstall(const QString &packageName) const;
|
||||
|
||||
private:
|
||||
QList<Tool> m_tools;
|
||||
};
|
||||
@@ -0,0 +1,268 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Controls as Controls
|
||||
import QtQuick.Dialogs as Dialogs
|
||||
import QtQuick.Layouts
|
||||
import org.kde.kcmutils as KCM
|
||||
import org.kde.kirigami as Kirigami
|
||||
|
||||
KCM.SimpleKCM {
|
||||
id: root
|
||||
|
||||
property bool showingFingerprintSettings: false
|
||||
|
||||
implicitWidth: Kirigami.Units.gridUnit * 36
|
||||
implicitHeight: Kirigami.Units.gridUnit * 30
|
||||
|
||||
StackLayout {
|
||||
anchors.fill: parent
|
||||
currentIndex: root.showingFingerprintSettings ? 1 : 0
|
||||
|
||||
ColumnLayout {
|
||||
spacing: Kirigami.Units.largeSpacing
|
||||
|
||||
Kirigami.InlineMessage {
|
||||
Layout.fillWidth: true
|
||||
visible: kcm.message.length > 0
|
||||
text: kcm.message
|
||||
type: kcm.error ? Kirigami.MessageType.Error : Kirigami.MessageType.Positive
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
|
||||
Controls.Label {
|
||||
Layout.fillWidth: true
|
||||
text: i18n("Tools published in the configured Fedora Tools repository")
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
|
||||
Controls.Button {
|
||||
text: i18n("Refresh")
|
||||
icon.name: "view-refresh"
|
||||
enabled: !kcm.busy
|
||||
onClicked: kcm.refresh(true)
|
||||
}
|
||||
}
|
||||
|
||||
Controls.BusyIndicator {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
visible: kcm.busy
|
||||
running: visible
|
||||
}
|
||||
|
||||
ListView {
|
||||
id: toolList
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
clip: true
|
||||
spacing: Kirigami.Units.smallSpacing
|
||||
model: kcm.tools
|
||||
|
||||
delegate: Kirigami.AbstractCard {
|
||||
id: toolCard
|
||||
|
||||
required property string packageName
|
||||
required property string version
|
||||
required property string availableVersion
|
||||
required property string architecture
|
||||
required property string summary
|
||||
required property bool installed
|
||||
required property bool available
|
||||
required property bool updateAvailable
|
||||
required property bool configurable
|
||||
|
||||
width: toolList.width
|
||||
|
||||
contentItem: RowLayout {
|
||||
spacing: Kirigami.Units.largeSpacing
|
||||
|
||||
Kirigami.Icon {
|
||||
source: "applications-system"
|
||||
implicitWidth: Kirigami.Units.iconSizes.medium
|
||||
implicitHeight: width
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 0
|
||||
|
||||
Controls.Label {
|
||||
Layout.fillWidth: true
|
||||
text: toolCard.packageName
|
||||
font.bold: true
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
Controls.Label {
|
||||
Layout.fillWidth: true
|
||||
text: toolCard.summary
|
||||
wrapMode: Text.WordWrap
|
||||
color: Kirigami.Theme.disabledTextColor
|
||||
}
|
||||
|
||||
Controls.Label {
|
||||
Layout.fillWidth: true
|
||||
text: {
|
||||
if (toolCard.updateAvailable) {
|
||||
return i18n("Installed %1; %2 is available", toolCard.version, toolCard.availableVersion)
|
||||
}
|
||||
if (toolCard.installed) {
|
||||
return i18n("Installed: %1", toolCard.version)
|
||||
}
|
||||
return i18n("Available: %1", toolCard.availableVersion)
|
||||
}
|
||||
color: Kirigami.Theme.disabledTextColor
|
||||
}
|
||||
}
|
||||
|
||||
Controls.Button {
|
||||
visible: toolCard.configurable
|
||||
text: i18n("Configure…")
|
||||
enabled: !kcm.busy
|
||||
onClicked: root.showingFingerprintSettings = true
|
||||
}
|
||||
|
||||
Controls.Button {
|
||||
visible: toolCard.available && (!toolCard.installed || toolCard.updateAvailable)
|
||||
text: toolCard.installed ? i18n("Update") : i18n("Install")
|
||||
enabled: !kcm.busy
|
||||
onClicked: kcm.installTool(toolCard.packageName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Kirigami.PlaceholderMessage {
|
||||
anchors.centerIn: parent
|
||||
width: parent.width - Kirigami.Units.gridUnit * 4
|
||||
visible: toolList.count === 0 && !kcm.busy
|
||||
text: i18n("No Fedora tools were found")
|
||||
explanation: i18n("Check that the fedora-tools repository is installed and enabled.")
|
||||
icon.name: "package-x-generic"
|
||||
}
|
||||
}
|
||||
|
||||
Controls.Label {
|
||||
Layout.fillWidth: true
|
||||
text: i18n("Availability is filtered for this system's architecture. DNF validates dependencies and the transaction before installation.")
|
||||
wrapMode: Text.WordWrap
|
||||
color: Kirigami.Theme.disabledTextColor
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
spacing: Kirigami.Units.largeSpacing
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
|
||||
Controls.ToolButton {
|
||||
text: i18n("Back to tools")
|
||||
icon.name: "go-previous"
|
||||
enabled: !kcm.busy
|
||||
onClicked: {
|
||||
kcm.clearMessage()
|
||||
root.showingFingerprintSettings = false
|
||||
}
|
||||
}
|
||||
|
||||
Kirigami.Heading {
|
||||
Layout.fillWidth: true
|
||||
text: i18n("Fingerprint workaround")
|
||||
level: 2
|
||||
}
|
||||
}
|
||||
|
||||
Kirigami.InlineMessage {
|
||||
Layout.fillWidth: true
|
||||
visible: true
|
||||
type: kcm.message.length > 0
|
||||
? (kcm.error ? Kirigami.MessageType.Error : Kirigami.MessageType.Positive)
|
||||
: Kirigami.MessageType.Warning
|
||||
text: kcm.message.length > 0
|
||||
? kcm.message
|
||||
: i18n("This experimental workaround replaces Fedora's security-sensitive KScreenLocker package with an unaudited local build. Review its source before enabling it on another system.")
|
||||
}
|
||||
|
||||
Kirigami.FormLayout {
|
||||
Layout.fillWidth: true
|
||||
|
||||
Controls.Label {
|
||||
Kirigami.FormData.label: i18n("Status:")
|
||||
text: kcm.fingerprintEnabled ? i18n("Enabled") : i18n("Disabled")
|
||||
}
|
||||
|
||||
Controls.CheckBox {
|
||||
id: forceVersion
|
||||
|
||||
Kirigami.FormData.label: i18n("Version:")
|
||||
text: i18n("Allow a different KScreenLocker base version")
|
||||
enabled: !kcm.busy
|
||||
}
|
||||
}
|
||||
|
||||
Controls.Label {
|
||||
Layout.fillWidth: true
|
||||
text: i18n("Use the tested published build, or select an RPM built from the workaround spec. A version mismatch requires the option above and may downgrade KScreenLocker.")
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
visible: kcm.busy
|
||||
|
||||
Controls.BusyIndicator {
|
||||
running: parent.visible
|
||||
}
|
||||
|
||||
Controls.Label {
|
||||
text: i18n("Applying change…")
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
|
||||
Controls.Button {
|
||||
text: i18n("Enable tested build")
|
||||
icon.name: "security-high"
|
||||
enabled: !kcm.busy
|
||||
onClicked: kcm.enableFingerprintWorkaround("", forceVersion.checked)
|
||||
}
|
||||
|
||||
Controls.Button {
|
||||
text: i18n("Install RPM from disk…")
|
||||
icon.name: "document-open"
|
||||
enabled: !kcm.busy
|
||||
onClicked: rpmDialog.open()
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
Controls.Button {
|
||||
text: i18n("Disable workaround")
|
||||
enabled: !kcm.busy && kcm.fingerprintEnabled
|
||||
onClicked: kcm.disableFingerprintWorkaround()
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillHeight: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Dialogs.FileDialog {
|
||||
id: rpmDialog
|
||||
|
||||
title: i18n("Select a patched KScreenLocker RPM")
|
||||
nameFilters: [i18n("RPM packages (*.rpm)")]
|
||||
onAccepted: kcm.enableFingerprintWorkaround(selectedFile, forceVersion.checked)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
add_executable(test-packageutils
|
||||
test-packageutils.cpp
|
||||
../src/packageutils.cpp
|
||||
)
|
||||
target_include_directories(test-packageutils PRIVATE ../src)
|
||||
target_link_libraries(test-packageutils PRIVATE Qt6::Core Qt6::Test)
|
||||
add_test(NAME packageutils COMMAND test-packageutils)
|
||||
|
||||
add_executable(test-toolmodel
|
||||
test-toolmodel.cpp
|
||||
../src/toolmodel.cpp
|
||||
../src/packageutils.cpp
|
||||
)
|
||||
target_include_directories(test-toolmodel PRIVATE ../src)
|
||||
target_link_libraries(test-toolmodel PRIVATE Qt6::Core Qt6::Test)
|
||||
add_test(NAME toolmodel COMMAND test-toolmodel)
|
||||
@@ -0,0 +1,35 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include "packageutils.h"
|
||||
|
||||
#include <QTest>
|
||||
|
||||
class PackageUtilsTest : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
private Q_SLOTS:
|
||||
void parsesQueryOutput()
|
||||
{
|
||||
const auto records = parsePackageRecords(
|
||||
"touchpad-hold-tap\t0.1.0-6.fc44\tnoarch\tTouchpad gesture\n"
|
||||
"broken line\n");
|
||||
|
||||
QCOMPARE(records.size(), 1);
|
||||
QCOMPARE(records.first().name, QStringLiteral("touchpad-hold-tap"));
|
||||
QCOMPARE(records.first().version, QStringLiteral("0.1.0-6.fc44"));
|
||||
QCOMPARE(records.first().summary, QStringLiteral("Touchpad gesture"));
|
||||
}
|
||||
|
||||
void validatesPackageNames()
|
||||
{
|
||||
QVERIFY(isValidPackageName(QStringLiteral("plasma-task-group-shortcuts")));
|
||||
QVERIFY(!isValidPackageName(QStringLiteral("--installroot=/tmp")));
|
||||
QVERIFY(!isValidPackageName(QStringLiteral("tool; reboot")));
|
||||
QVERIFY(!isValidPackageName(QStringLiteral("Tool")));
|
||||
}
|
||||
};
|
||||
|
||||
QTEST_MAIN(PackageUtilsTest)
|
||||
|
||||
#include "test-packageutils.moc"
|
||||
@@ -0,0 +1,51 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include "toolmodel.h"
|
||||
|
||||
#include <QTest>
|
||||
|
||||
class ToolModelTest : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
private Q_SLOTS:
|
||||
void mergesInstalledAndAvailablePackages()
|
||||
{
|
||||
ToolModel model;
|
||||
const PackageRecord installed{QStringLiteral("touchpad-hold-tap"), QStringLiteral("0.1.0-5.fc44"), QStringLiteral("noarch"), QStringLiteral("Gesture")};
|
||||
const PackageRecord available{QStringLiteral("touchpad-hold-tap"), QStringLiteral("0.1.0-6.fc44"), QStringLiteral("noarch"), QStringLiteral("Gesture")};
|
||||
|
||||
model.setPackages({installed}, {available}, {QStringLiteral("touchpad-hold-tap")});
|
||||
|
||||
QCOMPARE(model.rowCount(), 1);
|
||||
const QModelIndex index = model.index(0);
|
||||
QCOMPARE(model.data(index, ToolModel::InstalledRole).toBool(), true);
|
||||
QCOMPARE(model.data(index, ToolModel::AvailableRole).toBool(), true);
|
||||
QCOMPARE(model.data(index, ToolModel::UpdateAvailableRole).toBool(), true);
|
||||
QCOMPARE(model.mayInstall(QStringLiteral("touchpad-hold-tap")), true);
|
||||
}
|
||||
|
||||
void doesNotGuessUpdatesFromDifferentVersionStrings()
|
||||
{
|
||||
ToolModel model;
|
||||
const PackageRecord installed{QStringLiteral("touchpad-hold-tap"), QStringLiteral("0.1.0-7.fc44"), QStringLiteral("noarch"), QStringLiteral("Gesture")};
|
||||
const PackageRecord available{QStringLiteral("touchpad-hold-tap"), QStringLiteral("0.1.0-6.fc44"), QStringLiteral("noarch"), QStringLiteral("Gesture")};
|
||||
|
||||
model.setPackages({installed}, {available});
|
||||
|
||||
QCOMPARE(model.data(model.index(0), ToolModel::UpdateAvailableRole).toBool(), false);
|
||||
}
|
||||
|
||||
void makesFingerprintControllerConfigurable()
|
||||
{
|
||||
ToolModel model;
|
||||
const PackageRecord package{QStringLiteral("plasma-fingerprint-workaround"), QStringLiteral("0.1.0-2.fc44"), QStringLiteral("noarch"), QStringLiteral("Fingerprint")};
|
||||
model.setPackages({package}, {package});
|
||||
|
||||
QCOMPARE(model.data(model.index(0), ToolModel::ConfigurableRole).toBool(), true);
|
||||
}
|
||||
};
|
||||
|
||||
QTEST_MAIN(ToolModelTest)
|
||||
|
||||
#include "test-toolmodel.moc"
|
||||
@@ -1,6 +1,6 @@
|
||||
Name: plasma-always-show-unlock
|
||||
Version: 0.1.0
|
||||
Release: 8%{?dist}
|
||||
Release: 9%{?dist}
|
||||
Summary: Always show the Plasma lock-screen unlock prompt
|
||||
|
||||
License: MIT AND GPL-2.0-or-later
|
||||
@@ -18,6 +18,7 @@ BuildRequires: patch
|
||||
Requires: bash
|
||||
Requires: patch
|
||||
Requires: plasma-desktop >= 6.7.4
|
||||
Provides: fedora-tools-tool
|
||||
|
||||
%description
|
||||
Keeps KDE Plasma's unlock prompt visible when the screen is locked, after the
|
||||
@@ -56,6 +57,9 @@ fi
|
||||
%{_datadir}/plasma-always-show-unlock/LockScreenUi.patch
|
||||
|
||||
%changelog
|
||||
* Sat Sep 05 2026 fedora-tools contributors - 0.1.0-9
|
||||
- Advertise the tool to Fedora Tools settings
|
||||
|
||||
* Thu Sep 03 2026 fedora-tools contributors - 0.1.0-8
|
||||
- Keep the prompt visible without continuously restarting authentication
|
||||
- Retry authentication after resume and the automatic-lock grace period
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
Name: plasma-fingerprint-workaround
|
||||
Version: 0.1.0
|
||||
Release: 1%{?dist}
|
||||
Release: 2%{?dist}
|
||||
Summary: Opt-in installer for a patched Fedora KScreenLocker
|
||||
|
||||
License: MIT
|
||||
@@ -21,6 +21,7 @@ Requires: dnf5
|
||||
Requires: grep
|
||||
Requires: rpm
|
||||
Requires: sed
|
||||
Provides: fedora-tools-tool
|
||||
|
||||
%description
|
||||
Provides an explicit, reversible installer for an experimental KScreenLocker
|
||||
@@ -50,5 +51,8 @@ install -D -m 0644 %{SOURCE3} \
|
||||
%{_datadir}/plasma-fingerprint-workaround/payload.conf
|
||||
|
||||
%changelog
|
||||
* Sat Sep 05 2026 fedora-tools contributors - 0.1.0-2
|
||||
- Advertise the tool to Fedora Tools settings
|
||||
|
||||
* Fri Sep 04 2026 Anton - 0.1.0-1
|
||||
- Initial package
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
Name: plasma-task-group-shortcuts
|
||||
Version: 0.1.0
|
||||
Release: 12%{?dist}
|
||||
Release: 13%{?dist}
|
||||
Summary: Application-group shortcuts for the Plasma Task Manager
|
||||
|
||||
License: MIT
|
||||
@@ -22,6 +22,7 @@ BuildRequires: plasma-workspace-devel >= 6.7
|
||||
BuildRequires: qt6-qtbase-devel
|
||||
BuildRequires: qt6-qtwayland-devel
|
||||
Requires: plasma-workspace >= 6.7
|
||||
Provides: fedora-tools-tool
|
||||
|
||||
%description
|
||||
Changes Plasma's Meta+number shortcuts to address application groups while
|
||||
@@ -53,5 +54,8 @@ install -Dpm 0644 %{SOURCE2} \
|
||||
%{_sysconfdir}/xdg/autostart/se.ajpanton.plasma-task-group-shortcuts-autostart.desktop
|
||||
|
||||
%changelog
|
||||
* Sat Sep 05 2026 fedora-tools contributors - 0.1.0-13
|
||||
- Advertise the tool to Fedora Tools settings
|
||||
|
||||
* Fri Sep 04 2026 fedora-tools contributors - 0.1.0-12
|
||||
- Initial package
|
||||
|
||||
Executable
+24
@@ -0,0 +1,24 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
repo_root=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)
|
||||
tool_dir="$repo_root/fedora-tools-settings"
|
||||
topdir="$repo_root/rpmbuild"
|
||||
source_dir="$topdir/SOURCES/fedora-tools-settings-0.1.0"
|
||||
|
||||
mkdir -p "$topdir"/{BUILD,BUILDROOT,RPMS,SOURCES,SPECS,SRPMS}
|
||||
rm -rf -- "$source_dir"
|
||||
mkdir -p "$source_dir"
|
||||
cp -a "$tool_dir/CMakeLists.txt" "$tool_dir/src" "$tool_dir/tests" "$source_dir/"
|
||||
tar -C "$topdir/SOURCES" -czf \
|
||||
"$topdir/SOURCES/fedora-tools-settings-0.1.0.tar.gz" \
|
||||
fedora-tools-settings-0.1.0
|
||||
install -m 0644 "$repo_root/LICENSE" "$topdir/SOURCES/LICENSE"
|
||||
install -m 0644 "$tool_dir/README.md" "$topdir/SOURCES/README.md"
|
||||
|
||||
rpmbuild \
|
||||
--define "_topdir $topdir" \
|
||||
-bb "$tool_dir/fedora-tools-settings.spec"
|
||||
@@ -1,6 +1,6 @@
|
||||
Name: touchpad-hold-tap
|
||||
Version: 0.1.0
|
||||
Release: 5%{?dist}
|
||||
Release: 6%{?dist}
|
||||
Summary: Hold-tap middle-click gesture for touchpads
|
||||
|
||||
License: MIT
|
||||
@@ -12,6 +12,7 @@ Source3: README.md
|
||||
|
||||
BuildRequires: lua
|
||||
Requires: libinput >= 1.30
|
||||
Provides: fedora-tools-tool
|
||||
|
||||
%description
|
||||
A libinput Lua plugin that recognizes a one-finger hold followed by a quick tap
|
||||
@@ -39,6 +40,9 @@ install -Dpm 0644 %{SOURCE3} \
|
||||
%{_libdir}/libinput/plugins/90-touchpad-hold-tap.lua
|
||||
|
||||
%changelog
|
||||
* Sat Sep 05 2026 fedora-tools contributors - 0.1.0-6
|
||||
- Advertise the tool to Fedora Tools settings
|
||||
|
||||
* Sun Aug 30 2026 fedora-tools contributors - 0.1.0-5
|
||||
- Run state-machine tests during RPM builds and package the README
|
||||
|
||||
|
||||
Reference in New Issue
Block a user