diff --git a/Modules/CMakeLists.txt b/Modules/CMakeLists.txt index cfe964253a3463c58fb122178b28f5ebb0c38aa7..e52f2286eb882173562e64895f44cfe0db5d1b21 100644 --- a/Modules/CMakeLists.txt +++ b/Modules/CMakeLists.txt @@ -9,6 +9,7 @@ add_subdirectory(xserver) add_subdirectory(egl) add_subdirectory(glx) add_subdirectory(pci) +add_subdirectory(firmware_security) add_subdirectory( samba ) add_subdirectory( nics ) diff --git a/Modules/firmware_security/CMakeLists.txt b/Modules/firmware_security/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..dfac87492495cc459077f3242a6fd885e83efb62 --- /dev/null +++ b/Modules/firmware_security/CMakeLists.txt @@ -0,0 +1,17 @@ +# SPDX-License-Identifier: BSD-3-Clause +# SPDX-FileCopyrightText: 2021-2022 Harald Sitter + +find_package(fwupdmgr) +set_package_properties(fwupdmgr PROPERTIES TYPE RUNTIME) + +find_package(aha) +set_package_properties(aha PROPERTIES TYPE RUNTIME) + +add_definitions(-DTRANSLATION_DOMAIN=\"kcm_firmware_security\") + +add_library(kcm_firmware_security MODULE main.cpp) +target_link_libraries(kcm_firmware_security KF5::CoreAddons KF5::QuickAddons KF5::I18n KF5::Package KInfoCenterInternal) + +install(TARGETS kcm_firmware_security DESTINATION ${PLUGIN_INSTALL_DIR}/plasma/kcms/kinfocenter) + +kpackage_install_package(package kcm_firmware_security kcms) diff --git a/Modules/firmware_security/Messages.sh b/Modules/firmware_security/Messages.sh new file mode 100644 index 0000000000000000000000000000000000000000..df7f266b8ab8bc3712d717d354be15a3f24aaaa8 --- /dev/null +++ b/Modules/firmware_security/Messages.sh @@ -0,0 +1,8 @@ +#!/bin/sh +# SPDX-License-Identifier: BSD-3-Clause +# SPDX-FileCopyrightText: 2020-2022 Harald Sitter + +$XGETTEXT `find . -name \*.cpp -o -name \*.h` -o $podir/kcm_firmware_security.pot +# Extract JavaScripty files as what they are, otherwise for example template literals won't work correctly (by default we extract as C++). +# https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals +$XGETTEXT --join-existing --language=JavaScript `find . -name \*.qml -o -name \*.js` -o $podir/kcm_firmware_security.pot diff --git a/Modules/firmware_security/kcm_firmware_security.json b/Modules/firmware_security/kcm_firmware_security.json new file mode 100644 index 0000000000000000000000000000000000000000..94103989b0725f772c85e8dc79f7059a37a38761 --- /dev/null +++ b/Modules/firmware_security/kcm_firmware_security.json @@ -0,0 +1,11 @@ +{ + "Categories": "Qt;KDE;X-KDE-information;", + "KPlugin": { + "Description": "Firmware Security Information", + "Icon": "preferences-security", + "Name": "Firmware Security" + }, + "TryExec": "fwupdmgr", + "X-KDE-KInfoCenter-Category": "device_information", + "X-KDE-Keywords": "firmware,security,hsi,secureboot,fwupd,bootguard,lockdown" +} diff --git a/Modules/firmware_security/kcm_firmware_security.json.license b/Modules/firmware_security/kcm_firmware_security.json.license new file mode 100644 index 0000000000000000000000000000000000000000..cf53d2bce18234c34a9c0edba12d2c69e52b17e6 --- /dev/null +++ b/Modules/firmware_security/kcm_firmware_security.json.license @@ -0,0 +1,2 @@ +SPDX-FileCopyrightText: None +SPDX-License-Identifier: CC0-1.0 diff --git a/Modules/firmware_security/main.cpp b/Modules/firmware_security/main.cpp new file mode 100644 index 0000000000000000000000000000000000000000..676992ce923b20a5c187c636168e61a7ca2c450e --- /dev/null +++ b/Modules/firmware_security/main.cpp @@ -0,0 +1,43 @@ +/* + SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL + SPDX-FileCopyrightText: 2021-2022 Harald Sitter +*/ + +#include +#include +#include +#include +#include + +#include + +class KCMFirmwareSecurity : public KQuickAddons::ConfigModule +{ + Q_OBJECT +public: + explicit KCMFirmwareSecurity(QObject *parent, const KPluginMetaData &data, const QVariantList &args) + : ConfigModule(parent, data, args) + { + auto *about = new KAboutData(QStringLiteral("kcm_firmware_security"), + i18nc("@label kcm name", "Firmware Security"), + QStringLiteral("1.0"), + QString(), + KAboutLicense::GPL); + about->addAuthor(i18n("Harald Sitter"), QString(), QStringLiteral("sitter@kde.org")); + setAboutData(about); + + KPackage::Package package = KPackage::PackageLoader::self()->loadPackage(QStringLiteral("KPackage/GenericQML")); + package.setDefaultPackageRoot(QStringLiteral("kpackage/kcms")); + package.setPath(about->componentName()); + + auto outputContext = new CommandOutputContext({QStringLiteral("fwupdmgr"), QStringLiteral("aha")}, + QStringLiteral("/bin/sh"), + {package.path() + QStringLiteral("contents/code/fwupdmgr.sh")}, + parent); + qmlRegisterSingletonInstance("org.kde.kinfocenter.firmware_security.private", 1, 0, "InfoOutputContext", outputContext); + } +}; + +K_PLUGIN_CLASS_WITH_JSON(KCMFirmwareSecurity, "kcm_firmware_security.json") + +#include "main.moc" diff --git a/Modules/firmware_security/package/contents/code/fwupdmgr.sh b/Modules/firmware_security/package/contents/code/fwupdmgr.sh new file mode 100755 index 0000000000000000000000000000000000000000..45994d18e81954b593fca222cb39e808be4292d1 --- /dev/null +++ b/Modules/firmware_security/package/contents/code/fwupdmgr.sh @@ -0,0 +1,8 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL +# SPDX-FileCopyrightText: 2022 Harald Sitter + +set -e + +# For sed testing please use --posix on GNU sed so you don't end up using gnu-isms. +fwupdmgr security --force | aha | sed -E 's@(https:[^[:space:]]+)@\1@g' diff --git a/Modules/firmware_security/package/contents/ui/main.qml b/Modules/firmware_security/package/contents/ui/main.qml new file mode 100644 index 0000000000000000000000000000000000000000..68e87e15aa5c235c2c272dfc83c82d76b66b5e27 --- /dev/null +++ b/Modules/firmware_security/package/contents/ui/main.qml @@ -0,0 +1,18 @@ +/* + SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL + SPDX-FileCopyrightText: 2021-2022 Harald Sitter +*/ + +import QtQuick 2.5 + +import org.kde.kcm 1.4 as KCM + +import org.kde.kinfocenter.private 1.0 as KInfoCenter +import org.kde.kinfocenter.firmware_security.private 1.0 + +KInfoCenter.CommandOutputKCM { + KCM.ConfigModule.quickHelp: i18nc("@info:whatsthis", "Firmware Security Information") + + output: InfoOutputContext + textFormat: TextEdit.RichText +} diff --git a/cmake/Findaha.cmake b/cmake/Findaha.cmake new file mode 100644 index 0000000000000000000000000000000000000000..6cc2de9be552176db5c26d8a1b4bce9aeb294b67 --- /dev/null +++ b/cmake/Findaha.cmake @@ -0,0 +1,10 @@ +# This module defines the following variables: +# +# aha_FOUND - true if found +# aha_PATH - path to the bin (only when found) +# +# SPDX-License-Identifier: BSD-2-Clause +# SPDX-FileCopyrightText: 2022 Harald Sitter + +include(ProgramFinder) +program_finder(aha) diff --git a/cmake/Findfwupdmgr.cmake b/cmake/Findfwupdmgr.cmake new file mode 100644 index 0000000000000000000000000000000000000000..332d95c67b360abee913e794415138cb300ca6fd --- /dev/null +++ b/cmake/Findfwupdmgr.cmake @@ -0,0 +1,10 @@ +# This module defines the following variables: +# +# fwupdmgr_FOUND - true if found +# fwupdmgr_PATH - path to the bin (only when found) +# +# SPDX-License-Identifier: BSD-2-Clause +# SPDX-FileCopyrightText: 2022 Harald Sitter + +include(ProgramFinder) +program_finder(fwupdmgr) diff --git a/src/CommandOutputContext.cpp b/src/CommandOutputContext.cpp index ec3cd3d2caccc84dda641595b16e216866a3a382..800117b29fbca231624e85869146255b878728f1 100644 --- a/src/CommandOutputContext.cpp +++ b/src/CommandOutputContext.cpp @@ -1,6 +1,6 @@ /* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL - SPDX-FileCopyrightText: 2021 Harald Sitter + SPDX-FileCopyrightText: 2021-2022 Harald Sitter */ #include "CommandOutputContext.h" @@ -13,7 +13,7 @@ #include -CommandOutputContext::CommandOutputContext(const QString &executable, const QStringList &arguments, QObject *parent) +CommandOutputContext::CommandOutputContext(const QStringList &findExecutables, const QString &executable, const QStringList &arguments, QObject *parent) : QObject(parent) , m_executableName(executable) , m_executablePath(QStandardPaths::findExecutable(m_executableName)) @@ -25,9 +25,19 @@ CommandOutputContext::CommandOutputContext(const QString &executable, const QStr QStandardPaths::findExecutable(m_executableName, {QStringLiteral("/usr/local/sbin"), QStringLiteral("/usr/sbin"), QStringLiteral("/sbin")}); } + m_foundExecutablePaths[executable] = m_executablePath; + for (const QString &findExecutable : findExecutables) { + m_foundExecutablePaths[findExecutable] = QStandardPaths::findExecutable(findExecutable); + } + metaObject()->invokeMethod(this, &CommandOutputContext::load); } +CommandOutputContext::CommandOutputContext(const QString &executable, const QStringList &arguments, QObject *parent) + : CommandOutputContext({/* executable is by default always searched for */}, executable, arguments, parent) +{ +} + QString CommandOutputContext::executableName() const { return m_executableName; @@ -79,9 +89,11 @@ void CommandOutputContext::load() { reset(); - if (m_executablePath.isEmpty()) { - setError(xi18nc("@info", "The executable %1 could not be found in $PATH.", m_executableName)); - return; + for (auto it = m_foundExecutablePaths.cbegin(); it != m_foundExecutablePaths.cend(); ++it) { + if (it.value().isEmpty()) { + setError(xi18nc("@info", "The executable %1 could not be found in $PATH.", it.key())); + return; + } } auto proc = new QProcess(this); diff --git a/src/CommandOutputContext.h b/src/CommandOutputContext.h index c818b4274ffc59eba17fee0f0dbf18b7dca7161e..cde07eaf016f9e204d5ba65db154bb8f34c6f407 100644 --- a/src/CommandOutputContext.h +++ b/src/CommandOutputContext.h @@ -1,10 +1,11 @@ /* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL - SPDX-FileCopyrightText: 2021 Harald Sitter + SPDX-FileCopyrightText: 2021-2022 Harald Sitter */ #pragma once +#include #include // Somewhat general-purpose command executor. This class runs the executable with arguments, collecting all its output @@ -23,7 +24,8 @@ class CommandOutputContext : public QObject // Potential error description. Empty when there is no error to report. Q_PROPERTY(QString error MEMBER m_error NOTIFY errorChanged) public: - explicit CommandOutputContext(const QString &executable, const QStringList &arguments, QObject *parent = nullptr); + CommandOutputContext(const QStringList &findExecutables, const QString &executable, const QStringList &arguments, QObject *parent = nullptr); + CommandOutputContext(const QString &executable, const QStringList &arguments, QObject *parent = nullptr); QString executableName() const; QStringList arguments() const; @@ -45,6 +47,7 @@ private: const QString m_executableName; QString m_executablePath; + QMap m_foundExecutablePaths; const QStringList m_arguments; QStringList m_originalLines; diff --git a/src/qml/CommandOutputKCM.qml b/src/qml/CommandOutputKCM.qml index 428e17d76ff335785f5a40bb5bfa4bc0d210e4e4..166ead77559ff8173f4aad51b491038a1ed7af66 100644 --- a/src/qml/CommandOutputKCM.qml +++ b/src/qml/CommandOutputKCM.qml @@ -1,6 +1,6 @@ /* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL - SPDX-FileCopyrightText: 2021 Harald Sitter + SPDX-FileCopyrightText: 2021-2022 Harald Sitter */ import QtQuick 2.5 @@ -25,6 +25,7 @@ KCM.SimpleKCM { // The CommandOutputContext object. required property QtObject output property int wrapMode: TextEdit.NoWrap + property var textFormat: TextEdit.PlainText Component { id: dataComponent @@ -33,9 +34,12 @@ KCM.SimpleKCM { text: output.text font.family: "monospace" wrapMode: root.wrapMode - textFormat: TextEdit.PlainText + textFormat: root.textFormat + onLinkActivated: Qt.openUrlExternally(link) + onLinkHovered: labelsMouseArea.cursorShape = link === "" ? undefined : Qt.PointingHandCursor MouseArea { + id: labelsMouseArea anchors.fill: parent acceptedButtons: Qt.RightButton onClicked: contextMenu.popup() @@ -99,7 +103,7 @@ KCM.SimpleKCM { } footer: QQC2.ToolBar { - visible: root.state !== "loading" + visible: root.state !== "loading" && root.textFormat === TextEdit.PlainText Kirigami.SearchField { id: filterField