diff --git a/Modules/about-distro/src/Module.h b/Modules/about-distro/src/Module.h --- a/Modules/about-distro/src/Module.h +++ b/Modules/about-distro/src/Module.h @@ -1,5 +1,5 @@ /* - Copyright (C) 2012 Harald Sitter + Copyright (C) 2012-2020 Harald Sitter This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as @@ -28,6 +28,7 @@ } class QLabel; +class Entry; class Module : public KCModule { @@ -62,25 +63,24 @@ void defaults() override; private: - void loadSoftware(); - void loadHardware(); + // Load os-release data into UI. + void loadOSData(); + // Load generic entries into UI. + void loadEntries(); - /** - * Copies the software and hardware information to clipboard. - */ + // Copy data dump to clipboard void copyToClipboard(); + // Same as copyToClipboard but in en_US when the system language + // is something else void copyToClipboardInEnglish(); - QVector > labelsForClipboard; - QString englishTextForClipboard; - /** * UI */ Ui::Module *ui = nullptr; - /** \returns Version of plasmashell or an empty string when none was found */ - QString plasmaVersion() const; + /*** Description entries for dumping into textual form. Already excludes invalids. */ + std::vector m_entries; }; #endif // MODULE_H diff --git a/Modules/about-distro/src/Module.cpp b/Modules/about-distro/src/Module.cpp --- a/Modules/about-distro/src/Module.cpp +++ b/Modules/about-distro/src/Module.cpp @@ -1,5 +1,5 @@ /* - Copyright (C) 2012-2014 Harald Sitter + Copyright (C) 2012-2020 Harald Sitter This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as @@ -49,41 +49,235 @@ #include "Version.h" -static qlonglong calculateTotalRam() +// Generic dumpable info entry. +// This encapsulates a table entry so that it may be put into the UI +// and also serialized into textual form for copy to clipboard. +// All entries that are meant to be serializable should derive from this! +// This class may either be subclassed or used as-is if label/value are trivial +// to obtain. +class Entry { - qlonglong ret = -1; +public: + enum class Language { + System, + English + }; + + Entry(const KLocalizedString &label_, const QString &value_) + : label(label_) + , value(value_) + { + Q_ASSERT(label.isEmpty() || localizedLabel(Language::English).endsWith(':')); + } + + ~Entry() = default; + + // When false this entry is garbage (e.g. incomplete data) and shouldn't be rendered. + bool isValid() const + { + return !label.toString().isEmpty() && !value.isEmpty(); + } + + // Returns textual representation of entry. + QString diagnosticLine(Language language = Language::System) const + { + // FIXME: This isn't really working for right-to-left + // The answer probably is in uncide control characters, but + // didn't work when tried. + // Essentially what needs to happen is that the label should be RTL + // that is to say the colon should be on the left, BUT englishy words + // within that should be LTR, everything besides the label should be LTR + // because we do not localize the values I don't think? + return localizedLabel(language) + ' ' + value + '\n'; + } + + // Descriptive label + KLocalizedString label; + // Value of the entry (e.g. the version of plasma) + QString value; + +private: + QString localizedLabel(Language language) const + { + switch (language) { + case Language::System: + return label.toString(); + case Language::English: + // https://bugs.kde.org/show_bug.cgi?id=416247 + return label.toString(QStringList { QStringLiteral("en_US") }); + } + Q_UNREACHABLE(); + return QStringLiteral("Unknown Label Language %1 (bug in KInfocenter!):").arg( + QString::number(static_cast(language))); + } +}; + +class PlasmaEntry : public Entry +{ +public: + PlasmaEntry() : Entry(ki18n("KDE Plasma Version:"), plasmaVersion()) + { + // Since Plasma version detection isn't based on a library query it can fail + // in weird cases; instead of admitting defeat we simply hide everything :P + if (value.isEmpty()) { + return; + } + } + + static QString plasmaVersion() + { + const QStringList &filePaths = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, + QStringLiteral("xsessions/plasma.desktop")); + + if (filePaths.length() < 1) { + return QString(); + } + + // Despite the fact that there can be multiple desktop files we simply take + // the first one as users usually don't have xsessions/ in their $HOME + // data location, so the first match should (usually) be the only one and + // reflect the plasma session run. + KDesktopFile desktopFile(filePaths.first()); + return desktopFile.desktopGroup().readEntry("X-KDE-PluginInfo-Version", QString()); + } +}; + +class KernelEntry : public Entry +{ +public: + KernelEntry() : Entry(ki18n("Kernel Version:"), kernelVersion()) + { + } + + static QString kernelVersion() + { + struct utsname utsName; + if (uname(&utsName) == 0) { + return QString::fromLatin1(utsName.release); + } + return QString(); + } +}; + +class BitEntry : public Entry +{ +public: + BitEntry() : Entry(ki18n("OS Type:"), bitString()) + { + } + + static QString bitString() + { + const int bits = QT_POINTER_SIZE == 8 ? 64 : 32; + return i18nc("@label %1 is the CPU bit width (e.g. 32 or 64)", + "%1-bit", QString::number(bits)); + } +}; + +class CPUEntry : public Entry +{ +public: + CPUEntry() : Entry(KLocalizedString(), QString()) + { + const QList list = Solid::Device::listFromType(Solid::DeviceInterface::Processor); + + label = ki18np("Processor:", "Processors:").subs(list.count()); + + // Format processor string + // Group by processor name + QMap processorMap; + for (const Solid::Device &device : list) { + const QString name = device.product(); + auto it = processorMap.find(name); + if (it == processorMap.end()) { + processorMap.insert(name, 1); + } else { + ++it.value(); + } + } + // Create a formatted list of grouped processors + QStringList names; + names.reserve(processorMap.count()); + for (auto it = processorMap.constBegin(); it != processorMap.constEnd(); ++it) { + const int count = it.value(); + QString name = it.key(); + name.replace(QStringLiteral("(TM)"), QChar(8482)); + name.replace(QStringLiteral("(R)"), QChar(174)); + name = name.simplified(); + names.append(QStringLiteral("%1 × %2").arg(count).arg(name)); + } + + value = names.join(QLatin1String(", ")); + } +}; + +class MemoryEntry : public Entry +{ +public: + MemoryEntry() : Entry(ki18n("Memory:"), text()) + { + } + + static qlonglong calculateTotalRam() + { + qlonglong ret = -1; #ifdef Q_OS_LINUX - struct sysinfo info; - if (sysinfo(&info) == 0) - // manpage "sizes are given as multiples of mem_unit bytes" - ret = qlonglong(info.totalram) * info.mem_unit; + struct sysinfo info; + if (sysinfo(&info) == 0) + // manpage "sizes are given as multiples of mem_unit bytes" + ret = qlonglong(info.totalram) * info.mem_unit; #elif defined(Q_OS_FREEBSD) - /* Stuff for sysctl */ - size_t len; + /* Stuff for sysctl */ + size_t len; - unsigned long memory; - len = sizeof(memory); - sysctlbyname("hw.physmem", &memory, &len, NULL, 0); + unsigned long memory; + len = sizeof(memory); + sysctlbyname("hw.physmem", &memory, &len, NULL, 0); - ret = memory; + ret = memory; #endif - return ret; -} + return ret; + } + + static QString text() + { + const qlonglong totalRam = calculateTotalRam(); + if (totalRam > 0) { + return i18nc("@label %1 is the formatted amount of system memory (e.g. 7,7 GiB)", + "%1 of RAM", KFormat().formatByteSize(totalRam)); + } + return i18nc("Unknown amount of RAM", "Unknown"); + } +}; + +// Label with font styling for section heading such as 'Software' +class SectionLabel : public QLabel +{ +public: + explicit SectionLabel(const QString &text, QWidget *parent = nullptr) + : QLabel(text, parent) + { + QFont font; + font.setBold(true); + font.setWeight(75); + setFont(font); + } +}; Module::Module(QWidget *parent, const QVariantList &args) : KCModule(parent, args), ui(new Ui::Module) { KAboutData *aboutData = new KAboutData(QStringLiteral("kcm-about-distro"), - i18nc("@title", "About Distribution"), + i18nc("@title", "About System"), QString::fromLatin1(global_s_versionStringFull), QString(), KAboutLicense::LicenseKey::GPL_V3, - i18nc("@info:credit", "Copyright 2012-2014 Harald Sitter")); + i18nc("@info:credit", "Copyright 2012-2020 Harald Sitter")); aboutData->addAuthor(i18nc("@info:credit", "Harald Sitter"), i18nc("@info:credit", "Author"), - QStringLiteral("apachelogger@kubuntu.org")); + QStringLiteral("sitter@kde.org")); setAboutData(aboutData); @@ -126,10 +320,15 @@ void Module::load() { - labelsForClipboard.clear(); - englishTextForClipboard = QStringLiteral(""); - loadSoftware(); - loadHardware(); + // load is called lazly, but also from the ctor -> prevent double init. + static bool initd = false; + if (initd) { + return; + } + initd = true; + + loadOSData(); + loadEntries(); } void Module::save() @@ -140,7 +339,7 @@ { } -void Module::loadSoftware() +void Module::loadOSData() { // NOTE: do not include globals, otherwise kdeglobals could provide values // even though we only explicitly want them from our own config. @@ -168,10 +367,8 @@ const QString distroNameVersion = QStringLiteral("%1 %2").arg(distroName, versionId); ui->nameVersionLabel->setText(distroNameVersion); - const auto dummyDistroDescriptionLabel = new QLabel(i18nc("@title:row", "Operating System:"), this); - dummyDistroDescriptionLabel->hide(); - labelsForClipboard << qMakePair(dummyDistroDescriptionLabel, ui->nameVersionLabel); - englishTextForClipboard += QStringLiteral("Operating System: %1\n").arg(distroNameVersion); + // Insert a dummy entry for debug info dumps. + m_entries.push_back(new Entry(ki18n("Operating System:"), distroNameVersion)); const QString variant = cg.readEntry("Variant", os.variant()); if (variant.isEmpty()) { @@ -186,130 +383,66 @@ } else { ui->urlLabel->setText(QStringLiteral("%1").arg(url)); } - - // Since Plasma version detection isn't based on a library query it can fail - // in weird cases; instead of admitting defeat we simply hide everything :P - const QString plasma = plasmaVersion(); - if (plasma.isEmpty()) { - ui->plasma->hide(); - ui->plasmaLabel->hide(); - } else { - ui->plasmaLabel->setText(plasma); - labelsForClipboard << qMakePair(ui->plasma, ui->plasmaLabel); - englishTextForClipboard += QStringLiteral("KDE Plasma Version: %1\n").arg(plasma); - } - - const QString frameworksVersion = KCoreAddons::versionString(); - ui->frameworksLabel->setText(frameworksVersion); - labelsForClipboard << qMakePair(ui->frameworksLabelKey, ui->frameworksLabel); - englishTextForClipboard += QStringLiteral("KDE Frameworks Version: %1\n").arg(frameworksVersion); - - const QString qversion = QString::fromLatin1(qVersion()); - ui->qtLabel->setText(qversion); - labelsForClipboard << qMakePair(ui->qt, ui->qtLabel); - englishTextForClipboard += QStringLiteral("Qt Version: %1\n").arg(qversion); } -void Module::loadHardware() +void Module::loadEntries() { - struct utsname utsName; - if(uname(&utsName) != 0) { - ui->kernel->hide(); - ui->kernelLabel->hide(); - } else { - QString kernelVersion = QString::fromLatin1(utsName.release); - ui->kernelLabel->setText(kernelVersion); - labelsForClipboard << qMakePair(ui->kernel, ui->kernelLabel); - englishTextForClipboard += QStringLiteral("Kernel Version: %1\n").arg(kernelVersion); - } - - const int bits = QT_POINTER_SIZE == 8 ? 64 : 32; - const QString bitsStr = QString::number(bits); - ui->bitsLabel->setText(i18nc("@label %1 is the CPU bit width (e.g. 32 or 64)", - "%1-bit", bitsStr)); - labelsForClipboard << qMakePair(ui->bitsKey, ui->bitsLabel); - englishTextForClipboard += QStringLiteral("OS Type: %1-bit\n").arg(bitsStr); - - const QList list = Solid::Device::listFromType(Solid::DeviceInterface::Processor); - ui->processor->setText(i18np("Processor:", "Processors:", list.count())); - // Format processor string - // Group by processor name - QMap processorMap; - Q_FOREACH(const Solid::Device &device, list) { - const QString name = device.product(); - auto it = processorMap.find(name); - if (it == processorMap.end()) { - processorMap.insert(name, 1); - } else { - ++it.value(); + auto addSectionHeader = [this](const QString &text) + { + int row = ui->infoGrid->rowCount(); + // Random sizes stolen from original UI file values :S + ui->infoGrid->addItem(new QSpacerItem(17, 21, QSizePolicy::Minimum, QSizePolicy::Fixed), row, 1, 1, 1); + ++row; + ui->infoGrid->addWidget(new SectionLabel(text), row, 1, Qt::AlignLeft); + ++row; + }; + + auto addEntriesToGrid = [this](std::vector entries) + { + int row = ui->infoGrid->rowCount(); + for (auto entry : entries) { + if (!entry->isValid()) { + continue; + } + ui->infoGrid->addWidget(new QLabel(entry->label.toString()), row, 0, Qt::AlignRight); + ui->infoGrid->addWidget(new QLabel(entry->value), row, 1, Qt::AlignLeft); + m_entries.push_back(entry); + ++row; } - } - // Create a formatted list of grouped processors - QStringList names; - names.reserve(processorMap.count()); - for (auto it = processorMap.constBegin(); it != processorMap.constEnd(); ++it) { - const int count = it.value(); - QString name = it.key(); - name.replace(QStringLiteral("(TM)"), QChar(8482)); - name.replace(QStringLiteral("(R)"), QChar(174)); - name = name.simplified(); - names.append(QStringLiteral("%1 × %2").arg(count).arg(name)); - } - - const QString processorLabel = names.join(QLatin1String(", ")); - ui->processorLabel->setText(processorLabel); - if (ui->processorLabel->text().isEmpty()) { - ui->processor->setHidden(true); - ui->processorLabel->setHidden(true); - } else { - labelsForClipboard << qMakePair(ui->processor, ui->processorLabel); - englishTextForClipboard += QStringLiteral("Processors: %1\n").arg(processorLabel); - } - - const qlonglong totalRam = calculateTotalRam(); - const QString memoryLabel = totalRam > 0 - ? i18nc("@label %1 is the formatted amount of system memory (e.g. 7,7 GiB)", - "%1 of RAM", KFormat().formatByteSize(totalRam)) - : i18nc("Unknown amount of RAM", "Unknown"); - ui->memoryLabel->setText(memoryLabel); - labelsForClipboard << qMakePair(ui->memory, ui->memoryLabel); - englishTextForClipboard += QStringLiteral("Memory: %1\n").arg(KFormat().formatByteSize(totalRam)); + }; + + // software + addSectionHeader(i18nc("@title:group", "Software")); + addEntriesToGrid({ + new PlasmaEntry(), + new Entry(ki18n("KDE Frameworks Version:"), KCoreAddons::versionString()), + new Entry(ki18n("Qt Version:"), QString::fromLatin1(qVersion())), + new KernelEntry(), + new BitEntry() + }); + + // hardware + addSectionHeader(i18nc("@title:group", "Hardware")); + addEntriesToGrid({ + new CPUEntry(), + new MemoryEntry() + }); } void Module::copyToClipboard() { QString text; - // note that this loop does not necessarily represent the same order as in the GUI - for (auto labelPair : qAsConst(labelsForClipboard)) { - const auto valueLabel = labelPair.second; - if (!valueLabel->isHidden()) { - const auto descriptionLabelText = labelPair.first->text(); - const auto valueLabelText = valueLabel->text(); - text += i18nc("%1 is a label already including a colon, %2 is the corresponding value", "%1 %2", descriptionLabelText, valueLabelText) + QStringLiteral("\n"); - } + for (auto entry : m_entries) { + text += entry->diagnosticLine(Entry::Language::System); } - QGuiApplication::clipboard()->setText(text); } void Module::copyToClipboardInEnglish() { - QGuiApplication::clipboard()->setText(englishTextForClipboard); -} - -QString Module::plasmaVersion() const -{ - const QStringList &filePaths = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, - QStringLiteral("xsessions/plasma.desktop")); - - if (filePaths.length() < 1) { - return QString(); + QString text; + for (auto entry : m_entries) { + text += entry->diagnosticLine(Entry::Language::English); } - - // Despite the fact that there can be multiple desktop files we simply take - // the first one as users usually don't have xsessions/ in their $HOME - // data location, so the first match should (usually) be the only one and - // reflect the plasma session run. - KDesktopFile desktopFile(filePaths.first()); - return desktopFile.desktopGroup().readEntry("X-KDE-PluginInfo-Version", QString()); + QGuiApplication::clipboard()->setText(text); } diff --git a/Modules/about-distro/src/Module.ui b/Modules/about-distro/src/Module.ui --- a/Modules/about-distro/src/Module.ui +++ b/Modules/about-distro/src/Module.ui @@ -46,7 +46,7 @@ - + @@ -137,189 +137,6 @@ - - - - Qt::Vertical - - - QSizePolicy::Fixed - - - - 17 - 21 - - - - - - - - - 75 - true - - - - Software - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - - - - - KDE Plasma Version: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - {KDEVersion} - - - - - - - KDE Frameworks Version: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - {FrameworksVersion} - - - - - - - Qt Version: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - {QtVersion} - - - - - - - Kernel Version: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - {KernelName} - - - - - - - OS Type: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - {QtArchitecutre} - - - - - - - Qt::Vertical - - - QSizePolicy::Fixed - - - - 17 - 21 - - - - - - - - - 75 - true - - - - Hardware - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - - - - - Processor: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - {PrcoessorName} - - - - - - - Memory: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - {MemoryAmount} - - - @@ -367,18 +184,18 @@ - - - Copy software and hardware information to clipboard in English - - - Copy to Clipboard in English - - - - .. - - + + + Copy software and hardware information to clipboard in English + + + Copy to Clipboard in English + + + + .. + +