Commit e5884a1e authored by Harald Sitter's avatar Harald Sitter 🏳️‍🌈 Committed by Nate Graham
Browse files

Add some DMI data to about-distro

Now it shows things like the hardware name, manufacturer, and serial number.

Serial number requires an extra click to see so that you don't accidentally reveal
it when you take a screenshot of the window.

FEATURE: 400079
FIXED-IN: 5.25
parent a8266144
Pipeline #158810 passed with stage
in 2 minutes and 59 seconds
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/Version.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/Version.h)
include_directories(${CMAKE_CURRENT_BINARY_DIR})
add_subdirectory(dmidecode-helper)
set(kcm_SRCS
CPUEntry.cpp
CPUEntry.h
......@@ -31,6 +33,7 @@ target_link_libraries(kcm_about-distro
KF5::QuickAddons
KF5::Solid
KF5::Service
KF5::KIOGui)
KF5::KIOGui
KF5::AuthCore)
kpackage_install_package(package kcm_about-distro kcms)
/*
SPDX-FileCopyrightText: 2012-2020 Harald Sitter <sitter@kde.org>
SPDX-FileCopyrightText: 2012-2022 Harald Sitter <sitter@kde.org>
SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/
#include "Entry.h"
Entry::Entry(const KLocalizedString &label_, const QString &value_)
Entry::Entry(const KLocalizedString &label_, const QString &value_, Hidden hidden)
: m_label(label_)
, m_value(value_)
, m_hidden(hidden)
{
Q_ASSERT(m_label.isEmpty() || localizedLabel(Language::English).endsWith(':'));
}
......@@ -57,6 +58,11 @@ QString Entry::localizedValue(Language language) const
return m_value;
}
bool Entry::isHidden() const
{
return m_hidden == Hidden::Yes;
}
QLocale Entry::localeForLanguage(Language language) const
{
switch (language) {
......
/*
SPDX-FileCopyrightText: 2012-2020 Harald Sitter <sitter@kde.org>
SPDX-FileCopyrightText: 2012-2022 Harald Sitter <sitter@kde.org>
SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/
......@@ -28,8 +28,14 @@ public:
};
Q_ENUM(Language);
enum class Hidden {
No = false,
Yes = true,
};
Q_ENUM(Hidden);
// value may be empty if localizedValue is overridden
Entry(const KLocalizedString &label_, const QString &value_);
Entry(const KLocalizedString &label_, const QString &value_, Hidden hidden = Hidden::No);
~Entry() override;
// When false this entry is garbage (e.g. incomplete data) and shouldn't be rendered.
......@@ -44,6 +50,9 @@ public:
// is needed for the value.
Q_SCRIPTABLE virtual QString localizedValue(Language language = Language::System) const;
// Returns whether this Entry should be hidden by default (i.e. only shown upon user request)
Q_INVOKABLE virtual bool isHidden() const;
protected:
// Returns localized QString for the given language.
QString localize(const KLocalizedString &string, Language language) const;
......@@ -55,6 +64,8 @@ protected:
KLocalizedString m_label;
// Value of the entry (e.g. the version of plasma)
QString m_value;
// Entries may be hidden behind a button so they aren't visible by default.
Hidden m_hidden;
};
#endif // ENTRY_H
# SPDX-License-Identifier: BSD-3-Clause
# SPDX-FileCopyrightText: 2021-2022 Harald Sitter <sitter@kde.org>
find_package(dmidecode)
set_package_properties(dmidecode PROPERTIES TYPE RUNTIME)
add_executable(kinfocenter-dmidecode-helper helper.cpp)
target_link_libraries(kinfocenter-dmidecode-helper
Qt::Widgets
KF5::Auth
KF5::I18n
)
kauth_install_actions(org.kde.kinfocenter.dmidecode org.kde.kinfocenter.dmidecode.actions)
kauth_install_helper_files(kinfocenter-dmidecode-helper org.kde.kinfocenter.dmidecode root)
install(TARGETS kinfocenter-dmidecode-helper DESTINATION ${KAUTH_HELPER_INSTALL_DIR})
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
// SPDX-FileCopyrightText: 2021-2022 Harald Sitter <sitter@kde.org>
#include "helper.h"
#include <QDebug>
#include <QProcess>
#include <QStandardPaths>
#include <KAuthHelperSupport>
template<typename Output, typename... Input>
auto make_array(Input&&... args) -> std::array<Output, sizeof...(args)> // NB: we need suffix notation here so args is defined
{
return {std::forward<Input>(args)...};
}
KAuth::ActionReply DMIDecodeHelper::systeminformation(const QVariantMap &args)
{
Q_UNUSED(args);
// PATH is super minimal when invoked through dbus
setenv("PATH", "/usr/sbin:/sbin:/usr/local/sbin", 1);
const QString dmidecode = QStandardPaths::findExecutable("dmidecode");
if (dmidecode.isEmpty()) {
return KAuth::ActionReply::HelperErrorReply();
}
// NB: Microsoft also outlines a limited set of DMI values to be required for IOT OEM licensing, as such we
// can rely on the same fields to have sound content . Since this only applies to OEMs we still need to filter
// out dummy values though and because of that we can grab more fields, since we'll filter them anyway.
// https://docs.microsoft.com/en-us/windows-hardware/manufacture/iot/license-requirements?view=windows-11#smbios-support
KAuth::ActionReply reply;
for (const auto &key : {QStringLiteral("system-manufacturer"),
QStringLiteral("system-product-name"),
QStringLiteral("system-version"),
QStringLiteral("system-serial-number")}) {
QProcess proc;
proc.start(dmidecode, {QStringLiteral("--string"), key});
proc.waitForFinished();
const QByteArray output = proc.readAllStandardOutput().trimmed();
if (output.isEmpty() || proc.error() != QProcess::UnknownError || proc.exitStatus() != QProcess::NormalExit) {
continue;
}
// Fairly exhaustive filter list based on a dozen or so samples gathered from reddit and other places.
// These are values that may appear in the DMI system information but aren't really useful.
static const auto dummyData = make_array<QString>(QStringLiteral("system version"),
QStringLiteral("system product name"),
QStringLiteral("system serial number"),
QStringLiteral("system manufacturer"),
QStringLiteral("to be filled by o.e.m."),
QStringLiteral("standard"), /* sometimes the version is useless */
QStringLiteral("sku"),
QStringLiteral("default string"),
QStringLiteral("not specified")
/* may also be empty, but that is filtered above already */);
if (std::find(dummyData.cbegin(), dummyData.cend(), output.toLower()) != dummyData.cend()) {
continue;
}
reply.addData(key, output);
}
return reply;
}
KAUTH_HELPER_MAIN("org.kde.kinfocenter.dmidecode", DMIDecodeHelper)
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
// SPDX-FileCopyrightText: 2021 Harald Sitter <sitter@kde.org>
#pragma once
#include <KAuthActionReply>
class DMIDecodeHelper : public QObject
{
Q_OBJECT
public Q_SLOTS:
KAuth::ActionReply systeminformation(const QVariantMap &args);
};
# SPDX-License-Identifier: CC0-1.0
# SPDX-FileCopyrightText: 2021 Harald Sitter <sitter@kde.org>
[Domain]
Icon=computer
URL=https://www.kde.org
Name=Desktop Management Interface
[org.kde.kinfocenter.dmidecode.systeminformation]
Policy=yes
PolicyInactive=yes
Name=System Information
Description=Read system information from system's Desktop Management Interface (DMI)
/*
SPDX-FileCopyrightText: 2012-2021 Harald Sitter <sitter@kde.org>
SPDX-FileCopyrightText: 2012-2022 Harald Sitter <sitter@kde.org>
SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/
......@@ -9,6 +9,8 @@
#include <QLocale>
#include <KAboutData>
#include <KAuthAction>
#include <KAuthExecuteJob>
#include <KConfig>
#include <KConfigGroup>
#include <KCoreAddons>
......@@ -30,6 +32,62 @@
#include <KPluginFactory>
#include <KQuickAddons/ConfigModule>
class EntryModel : public QAbstractListModel
{
Q_OBJECT
public:
enum Role {
ModelData = Qt::UserRole,
};
Q_ENUM(Role)
using QAbstractListModel::QAbstractListModel;
[[nodiscard]] int rowCount(const QModelIndex &parent) const override
{
Q_UNUSED(parent);
return m_entries.size();
}
[[nodiscard]] QVariant data(const QModelIndex &index, int intRole) const override
{
if (!index.isValid()) {
return {};
}
switch (static_cast<Role>(intRole)) {
case Role::ModelData:
return QVariant::fromValue(m_entries.at(index.row()));
}
return {};
}
[[nodiscard]] QHash<int, QByteArray> roleNames() const override
{
static QHash<int, QByteArray> roles;
if (!roles.isEmpty()) {
return roles;
}
roles = QAbstractListModel::roleNames();
roles.insert(Role::ModelData, QByteArrayLiteral("modelData"));
return roles;
}
void append(Entry *entry)
{
beginInsertRows(QModelIndex(), m_entries.size(), m_entries.size());
m_entries.push_back(entry);
endInsertRows();
}
private:
std::vector<Entry *> m_entries;
};
Q_DECLARE_METATYPE(EntryModel *)
Q_DECLARE_METATYPE(Entry *)
class KCMAboutSystem : public KQuickAddons::ConfigModule
{
Q_OBJECT
......@@ -110,21 +168,39 @@ public:
Q_EMIT changed();
}
static KLocalizedString systemInfoKey(const QString &key)
{
if (key == QStringLiteral("system-manufacturer")) {
return ki18nc("@label", "Manufacturer:");
}
if (key == QStringLiteral("system-product-name")) {
return ki18nc("@label", "Product Name:");
}
if (key == QStringLiteral("system-version")) {
return ki18nc("@label", "System Version:");
}
if (key == QStringLiteral("system-serial-number")) {
return ki18nc("@label", "Serial Number:");
}
qFatal("unexpected systeminfo key %s\n", qUtf8Printable(key));
Q_UNREACHABLE();
}
void loadEntries()
{
auto addEntriesToGrid = [this](QList<QObject *> *list, const std::vector<Entry *> &entries) {
auto addEntriesToGrid = [this](EntryModel *model, const std::vector<Entry *> &entries) {
for (auto entry : entries) {
if (!entry->isValid()) {
delete entry; // since we do not keep it around
continue;
}
list->append(entry);
model->append(entry);
m_entries.push_back(entry);
}
};
// software
addEntriesToGrid(&m_softwareEntries,
addEntriesToGrid(m_softwareEntries,
{new PlasmaEntry(),
new Entry(ki18n("KDE Frameworks Version:"), KCoreAddons::versionString()),
new Entry(ki18n("Qt Version:"), QString::fromLatin1(qVersion())),
......@@ -132,7 +208,26 @@ public:
new GraphicsPlatformEntry()});
// hardware
addEntriesToGrid(&m_hardwareEntries, {new CPUEntry(), new MemoryEntry(), new GPUEntry()});
addEntriesToGrid(m_hardwareEntries, {new CPUEntry, new MemoryEntry, new GPUEntry});
KAuth::Action action(QStringLiteral("org.kde.kinfocenter.dmidecode.systeminformation"));
action.setHelperId(QStringLiteral("org.kde.kinfocenter.dmidecode"));
KAuth::ExecuteJob *job = action.execute();
connect(job, &KJob::result, this, [this, job, addEntriesToGrid] {
QVariantMap data = job->data();
static const QString systemSerialNumberKey = QStringLiteral("system-serial-number");
const QString systemSerialNumber = data.take(systemSerialNumberKey).toString();
for (auto it = data.cbegin(); it != data.cend(); ++it) {
addEntriesToGrid(m_hardwareEntries, {new Entry(systemInfoKey(it.key()), it.value().toString())});
}
// Insert hidden entries at the end so it doesn't look weird visually to have a button mid-layout.
if (!systemSerialNumber.isEmpty()) {
addEntriesToGrid(m_hardwareEntries, {new Entry(systemInfoKey(systemSerialNumberKey), systemSerialNumber, Entry::Hidden::Yes)});
}
Q_EMIT changed();
});
job->start();
Q_EMIT changed();
}
......@@ -141,17 +236,28 @@ public:
{
QString text;
for (auto entry : m_entries) {
if (entry->isHidden()) {
continue;
}
text += entry->diagnosticLine(Entry::Language::System);
}
QGuiApplication::clipboard()->setText(text.trimmed());
storeInClipboard(text);
}
Q_SCRIPTABLE void copyToClipboardInEnglish()
{
QString text;
for (auto entry : m_entries) {
if (entry->isHidden()) {
continue;
}
text += entry->diagnosticLine(Entry::Language::English);
}
storeInClipboard(text);
}
Q_SCRIPTABLE static void storeInClipboard(const QString &text)
{
QGuiApplication::clipboard()->setText(text.trimmed());
}
......@@ -160,10 +266,10 @@ private:
Q_SIGNAL void changed();
Q_PROPERTY(QList<QObject *> softwareEntries MEMBER m_softwareEntries NOTIFY changed);
QList<QObject *> m_softwareEntries;
Q_PROPERTY(QList<QObject *> hardwareEntries MEMBER m_hardwareEntries NOTIFY changed);
QList<QObject *> m_hardwareEntries;
Q_PROPERTY(EntryModel *softwareEntries MEMBER m_softwareEntries CONSTANT);
EntryModel *m_softwareEntries = new EntryModel(this);
Q_PROPERTY(EntryModel *hardwareEntries MEMBER m_hardwareEntries CONSTANT);
EntryModel *m_hardwareEntries = new EntryModel(this);
Q_PROPERTY(QString distroLogo MEMBER m_distroLogo NOTIFY changed);
QString m_distroLogo;
......
/*
SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
SPDX-FileCopyrightText: 2021 Harald Sitter <sitter@kde.org>
SPDX-FileCopyrightText: 2021-2022 Harald Sitter <sitter@kde.org>
*/
import QtQuick 2.15
import QtQuick.Controls 2.5 as QQC2
import QtQuick.Layouts 1.1
import org.kde.kirigami 2.12 as Kirigami
import org.kde.kcm 1.4 as KCM
import org.kde.kirigami 2.19 as Kirigami
import org.kde.kcm 1.6 as KCM
import org.kde.kinfocenter.about_distro.private 1.0
......@@ -74,9 +74,42 @@ KCM.SimpleKCM {
Component {
id: entryComponent
QQC2.Label {
RowLayout {
Kirigami.FormData.label: modelData.localizedLabel()
text: modelData.localizedValue()
readonly property bool hidden: modelData.isHidden()
Component {
id: unhideDialog
Kirigami.PromptDialog {
// NB: should we ever have other entries that need this dialog then this needs refactoring on the Entry side.
// Do NOT simply add if else logic here!
title: i18nc("@title", "Serial Number")
subtitle: modelData.localizedValue()
flatFooterButtons: true
standardButtons: Kirigami.Dialog.NoButton
customFooterActions: [
Kirigami.Action {
text: i18nc("@action:button", "Copy to Clipboard")
icon.name: "edit-copy"
onTriggered: kcm.storeInClipboard(subtitle)
shortcut: StandardKey.Copy
}
]
}
}
QQC2.Label {
visible: !hidden
text: modelData.localizedValue()
}
QQC2.Button {
visible: hidden
text: i18nc("@action:button show a hidden entry in an overlay", "Show")
onClicked: {
const dialog = unhideDialog.createObject(root, {})
dialog.open()
}
}
}
}
......
# This module defines the following variables:
#
# dmidecode_FOUND - true if found
# dmidecode_PATH - path to the bin (only when found)
#
# SPDX-License-Identifier: BSD-2-Clause
# SPDX-FileCopyrightText: 2022 Harald Sitter <sitter@kde.org>
include(ProgramFinder)
program_finder(dmidecode)
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment