Changeset View
Changeset View
Standalone View
Standalone View
Modules/about-distro/src/Module.cpp
1 | /* | 1 | /* | ||
---|---|---|---|---|---|
2 | Copyright (C) 2012-2014 Harald Sitter <apachelogger@ubuntu.com> | 2 | SPDX-FileCopyrightText: 2012-2020 Harald Sitter <sitter@kde.org> | ||
3 | 3 | SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL | |||
4 | This program is free software; you can redistribute it and/or | | |||
5 | modify it under the terms of the GNU General Public License as | | |||
6 | published by the Free Software Foundation; either version 2 of | | |||
7 | the License or (at your option) version 3 or any later version | | |||
8 | accepted by the membership of KDE e.V. (or its successor approved | | |||
9 | by the membership of KDE e.V.), which shall act as a proxy | | |||
10 | defined in Section 14 of version 3 of the license. | | |||
11 | | ||||
12 | This program is distributed in the hope that it will be useful, | | |||
13 | but WITHOUT ANY WARRANTY; without even the implied warranty of | | |||
14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | | |||
15 | GNU General Public License for more details. | | |||
16 | | ||||
17 | You should have received a copy of the GNU General Public License | | |||
18 | along with this program. If not, see <http://www.gnu.org/licenses/>. | | |||
19 | */ | 4 | */ | ||
20 | 5 | | |||
21 | #include "Module.h" | 6 | #include "Module.h" | ||
22 | #include "ui_Module.h" | 7 | #include "ui_Module.h" | ||
23 | 8 | | |||
24 | #include <QClipboard> | 9 | #include <QClipboard> | ||
25 | #include <QIcon> | 10 | #include <QIcon> | ||
26 | #include <QLocale> | 11 | #include <QLocale> | ||
27 | #include <QStandardPaths> | | |||
28 | 12 | | |||
29 | #include <KAboutData> | 13 | #include <KAboutData> | ||
30 | #include <KCoreAddons> | 14 | #include <KCoreAddons> | ||
31 | #include <KConfig> | 15 | #include <KConfig> | ||
32 | #include <KConfigGroup> | 16 | #include <KConfigGroup> | ||
33 | #include <KDesktopFile> | | |||
34 | #include <KFormat> | | |||
35 | #include <KLocalizedString> | 17 | #include <KLocalizedString> | ||
36 | #include <KOSRelease> | 18 | #include <KOSRelease> | ||
37 | #include <KSharedConfig> | 19 | #include <KSharedConfig> | ||
38 | 20 | | |||
39 | #include <solid/device.h> | 21 | #include "CPUEntry.h" | ||
40 | #include <solid/processor.h> | 22 | #include "BitEntry.h" | ||
41 | 23 | #include "KernelEntry.h" | |||
42 | #ifdef Q_OS_LINUX | 24 | #include "MemoryEntry.h" | ||
43 | #include <sys/sysinfo.h> | 25 | #include "PlasmaEntry.h" | ||
44 | #elif defined(Q_OS_FREEBSD) | 26 | #include "SectionLabel.h" | ||
45 | #include <sys/types.h> | | |||
46 | #include <sys/sysctl.h> | | |||
47 | #endif | | |||
48 | #include <sys/utsname.h> | | |||
49 | | ||||
50 | #include "Version.h" | 27 | #include "Version.h" | ||
51 | 28 | | |||
52 | static qlonglong calculateTotalRam() | | |||
53 | { | | |||
54 | qlonglong ret = -1; | | |||
55 | #ifdef Q_OS_LINUX | | |||
56 | struct sysinfo info; | | |||
57 | if (sysinfo(&info) == 0) | | |||
58 | // manpage "sizes are given as multiples of mem_unit bytes" | | |||
59 | ret = qlonglong(info.totalram) * info.mem_unit; | | |||
60 | #elif defined(Q_OS_FREEBSD) | | |||
61 | /* Stuff for sysctl */ | | |||
62 | size_t len; | | |||
63 | | ||||
64 | unsigned long memory; | | |||
65 | len = sizeof(memory); | | |||
66 | sysctlbyname("hw.physmem", &memory, &len, NULL, 0); | | |||
67 | | ||||
68 | ret = memory; | | |||
69 | #endif | | |||
70 | return ret; | | |||
71 | } | | |||
72 | | ||||
73 | Module::Module(QWidget *parent, const QVariantList &args) : | 29 | Module::Module(QWidget *parent, const QVariantList &args) : | ||
74 | KCModule(parent, args), | 30 | KCModule(parent, args), | ||
75 | ui(new Ui::Module) | 31 | ui(new Ui::Module) | ||
76 | { | 32 | { | ||
77 | KAboutData *aboutData = new KAboutData(QStringLiteral("kcm-about-distro"), | 33 | KAboutData *aboutData = new KAboutData(QStringLiteral("kcm-about-distro"), | ||
78 | i18nc("@title", "About Distribution"), | 34 | i18nc("@title", "About System"), | ||
79 | QString::fromLatin1(global_s_versionStringFull), | 35 | QString::fromLatin1(global_s_versionStringFull), | ||
80 | QString(), | 36 | QString(), | ||
81 | KAboutLicense::LicenseKey::GPL_V3, | 37 | KAboutLicense::LicenseKey::GPL_V3, | ||
82 | i18nc("@info:credit", "Copyright 2012-2014 Harald Sitter")); | 38 | i18nc("@info:credit", "Copyright 2012-2020 Harald Sitter")); | ||
83 | 39 | | |||
84 | aboutData->addAuthor(i18nc("@info:credit", "Harald Sitter"), | 40 | aboutData->addAuthor(i18nc("@info:credit", "Harald Sitter"), | ||
85 | i18nc("@info:credit", "Author"), | 41 | i18nc("@info:credit", "Author"), | ||
86 | QStringLiteral("apachelogger@kubuntu.org")); | 42 | QStringLiteral("sitter@kde.org")); | ||
87 | 43 | | |||
88 | setAboutData(aboutData); | 44 | setAboutData(aboutData); | ||
89 | 45 | | |||
90 | ui->setupUi(this); | 46 | ui->setupUi(this); | ||
91 | 47 | | |||
92 | QFont font = ui->nameVersionLabel->font(); | 48 | QFont font = ui->nameVersionLabel->font(); | ||
93 | font.setPixelSize(24); | 49 | font.setPixelSize(24); | ||
94 | font.setBold(true); | 50 | font.setBold(true); | ||
Show All 22 Lines | |||||
117 | // Otherwise there is a notciable rendering gap where dummy/no data is | 73 | // Otherwise there is a notciable rendering gap where dummy/no data is | ||
118 | // shown. Makes it look bad. | 74 | // shown. Makes it look bad. | ||
119 | load(); | 75 | load(); | ||
120 | } | 76 | } | ||
121 | 77 | | |||
122 | Module::~Module() | 78 | Module::~Module() | ||
123 | { | 79 | { | ||
124 | delete ui; | 80 | delete ui; | ||
81 | qDeleteAll(m_entries); | ||||
125 | } | 82 | } | ||
126 | 83 | | |||
127 | void Module::load() | 84 | void Module::load() | ||
128 | { | 85 | { | ||
129 | labelsForClipboard.clear(); | 86 | // load is called lazly, but also from the ctor -> prevent double init. | ||
130 | englishTextForClipboard = QStringLiteral(""); | 87 | static bool initd = false; | ||
131 | loadSoftware(); | 88 | if (initd) { | ||
132 | loadHardware(); | 89 | return; | ||
90 | } | ||||
91 | initd = true; | ||||
92 | | ||||
93 | loadOSData(); | ||||
94 | loadEntries(); | ||||
133 | } | 95 | } | ||
134 | 96 | | |||
135 | void Module::save() | 97 | void Module::save() | ||
136 | { | 98 | { | ||
137 | } | 99 | } | ||
138 | 100 | | |||
139 | void Module::defaults() | 101 | void Module::defaults() | ||
140 | { | 102 | { | ||
141 | } | 103 | } | ||
142 | 104 | | |||
143 | void Module::loadSoftware() | 105 | void Module::loadOSData() | ||
144 | { | 106 | { | ||
145 | // NOTE: do not include globals, otherwise kdeglobals could provide values | 107 | // NOTE: do not include globals, otherwise kdeglobals could provide values | ||
146 | // even though we only explicitly want them from our own config. | 108 | // even though we only explicitly want them from our own config. | ||
147 | KSharedConfig::Ptr config = KSharedConfig::openConfig(QStringLiteral("kcm-about-distrorc"), | 109 | KSharedConfig::Ptr config = KSharedConfig::openConfig(QStringLiteral("kcm-about-distrorc"), | ||
148 | KConfig::NoGlobals); | 110 | KConfig::NoGlobals); | ||
149 | KConfigGroup cg = KConfigGroup(config, "General"); | 111 | KConfigGroup cg = KConfigGroup(config, "General"); | ||
150 | 112 | | |||
151 | KOSRelease os; | 113 | KOSRelease os; | ||
Show All 11 Lines | |||||
163 | const QString distroName = cg.readEntry("Name", os.name()); | 125 | const QString distroName = cg.readEntry("Name", os.name()); | ||
164 | const QString osrVersion = cg.readEntry("UseOSReleaseVersion", false) | 126 | const QString osrVersion = cg.readEntry("UseOSReleaseVersion", false) | ||
165 | ? os.version() | 127 | ? os.version() | ||
166 | : os.versionId(); | 128 | : os.versionId(); | ||
167 | const QString versionId = cg.readEntry("Version", osrVersion); | 129 | const QString versionId = cg.readEntry("Version", osrVersion); | ||
168 | const QString distroNameVersion = QStringLiteral("%1 %2").arg(distroName, versionId); | 130 | const QString distroNameVersion = QStringLiteral("%1 %2").arg(distroName, versionId); | ||
169 | ui->nameVersionLabel->setText(distroNameVersion); | 131 | ui->nameVersionLabel->setText(distroNameVersion); | ||
170 | 132 | | |||
171 | const auto dummyDistroDescriptionLabel = new QLabel(i18nc("@title:row", "Operating System:"), this); | 133 | // Insert a dummy entry for debug info dumps. | ||
172 | dummyDistroDescriptionLabel->hide(); | 134 | m_entries.push_back(new Entry(ki18n("Operating System:"), distroNameVersion)); | ||
173 | labelsForClipboard << qMakePair(dummyDistroDescriptionLabel, ui->nameVersionLabel); | | |||
174 | englishTextForClipboard += QStringLiteral("Operating System: %1\n").arg(distroNameVersion); | | |||
175 | 135 | | |||
176 | const QString variant = cg.readEntry("Variant", os.variant()); | 136 | const QString variant = cg.readEntry("Variant", os.variant()); | ||
177 | if (variant.isEmpty()) { | 137 | if (variant.isEmpty()) { | ||
178 | ui->variantLabel->hide(); | 138 | ui->variantLabel->hide(); | ||
179 | } else { | 139 | } else { | ||
180 | ui->variantLabel->setText(variant); | 140 | ui->variantLabel->setText(variant); | ||
181 | } | 141 | } | ||
182 | 142 | | |||
183 | const QString url = cg.readEntry("Website", os.homeUrl()); | 143 | const QString url = cg.readEntry("Website", os.homeUrl()); | ||
184 | if (url.isEmpty()) { | 144 | if (url.isEmpty()) { | ||
185 | ui->urlLabel->hide(); | 145 | ui->urlLabel->hide(); | ||
186 | } else { | 146 | } else { | ||
187 | ui->urlLabel->setText(QStringLiteral("<a href='%1'>%1</a>").arg(url)); | 147 | ui->urlLabel->setText(QStringLiteral("<a href='%1'>%1</a>").arg(url)); | ||
188 | } | 148 | } | ||
189 | | ||||
190 | // Since Plasma version detection isn't based on a library query it can fail | | |||
191 | // in weird cases; instead of admitting defeat we simply hide everything :P | | |||
192 | const QString plasma = plasmaVersion(); | | |||
193 | if (plasma.isEmpty()) { | | |||
194 | ui->plasma->hide(); | | |||
195 | ui->plasmaLabel->hide(); | | |||
196 | } else { | | |||
197 | ui->plasmaLabel->setText(plasma); | | |||
198 | labelsForClipboard << qMakePair(ui->plasma, ui->plasmaLabel); | | |||
199 | englishTextForClipboard += QStringLiteral("KDE Plasma Version: %1\n").arg(plasma); | | |||
200 | } | | |||
201 | | ||||
202 | const QString frameworksVersion = KCoreAddons::versionString(); | | |||
203 | ui->frameworksLabel->setText(frameworksVersion); | | |||
204 | labelsForClipboard << qMakePair(ui->frameworksLabelKey, ui->frameworksLabel); | | |||
205 | englishTextForClipboard += QStringLiteral("KDE Frameworks Version: %1\n").arg(frameworksVersion); | | |||
206 | | ||||
207 | const QString qversion = QString::fromLatin1(qVersion()); | | |||
208 | ui->qtLabel->setText(qversion); | | |||
209 | labelsForClipboard << qMakePair(ui->qt, ui->qtLabel); | | |||
210 | englishTextForClipboard += QStringLiteral("Qt Version: %1\n").arg(qversion); | | |||
211 | } | 149 | } | ||
212 | 150 | | |||
213 | void Module::loadHardware() | 151 | void Module::loadEntries() | ||
214 | { | 152 | { | ||
215 | struct utsname utsName; | 153 | auto addSectionHeader = [this](const QString &text) | ||
216 | if(uname(&utsName) != 0) { | 154 | { | ||
217 | ui->kernel->hide(); | 155 | int row = ui->infoGrid->rowCount(); | ||
218 | ui->kernelLabel->hide(); | 156 | // Random sizes stolen from original UI file values :S | ||
219 | } else { | 157 | ui->infoGrid->addItem(new QSpacerItem(17, 21, QSizePolicy::Minimum, QSizePolicy::Fixed), row, 1, 1, 1); | ||
220 | QString kernelVersion = QString::fromLatin1(utsName.release); | 158 | ++row; | ||
221 | ui->kernelLabel->setText(kernelVersion); | 159 | ui->infoGrid->addWidget(new SectionLabel(text), row, 1, Qt::AlignLeft); | ||
222 | labelsForClipboard << qMakePair(ui->kernel, ui->kernelLabel); | 160 | ++row; | ||
223 | englishTextForClipboard += QStringLiteral("Kernel Version: %1\n").arg(kernelVersion); | 161 | }; | ||
224 | } | 162 | | ||
225 | 163 | auto addEntriesToGrid = [this](std::vector<const Entry *> entries) | |||
226 | const int bits = QT_POINTER_SIZE == 8 ? 64 : 32; | 164 | { | ||
227 | const QString bitsStr = QString::number(bits); | 165 | int row = ui->infoGrid->rowCount(); | ||
228 | ui->bitsLabel->setText(i18nc("@label %1 is the CPU bit width (e.g. 32 or 64)", | 166 | for (auto entry : entries) { | ||
229 | "%1-bit", bitsStr)); | 167 | if (!entry->isValid()) { | ||
230 | labelsForClipboard << qMakePair(ui->bitsKey, ui->bitsLabel); | 168 | delete entry; // since we do not keep it around | ||
231 | englishTextForClipboard += QStringLiteral("OS Type: %1-bit\n").arg(bitsStr); | 169 | continue; | ||
232 | 170 | } | |||
233 | const QList<Solid::Device> list = Solid::Device::listFromType(Solid::DeviceInterface::Processor); | 171 | ui->infoGrid->addWidget(new QLabel(entry->label.toString()), row, 0, Qt::AlignRight); | ||
234 | ui->processor->setText(i18np("Processor:", "Processors:", list.count())); | 172 | ui->infoGrid->addWidget(new QLabel(entry->value), row, 1, Qt::AlignLeft); | ||
235 | // Format processor string | 173 | m_entries.push_back(entry); | ||
236 | // Group by processor name | 174 | ++row; | ||
237 | QMap<QString, int> processorMap; | 175 | } | ||
238 | Q_FOREACH(const Solid::Device &device, list) { | 176 | }; | ||
239 | const QString name = device.product(); | 177 | | ||
240 | auto it = processorMap.find(name); | 178 | // software | ||
241 | if (it == processorMap.end()) { | 179 | addSectionHeader(i18nc("@title:group", "Software")); | ||
242 | processorMap.insert(name, 1); | 180 | addEntriesToGrid({ | ||
243 | } else { | 181 | new PlasmaEntry(), | ||
244 | ++it.value(); | 182 | new Entry(ki18n("KDE Frameworks Version:"), KCoreAddons::versionString()), | ||
245 | } | 183 | new Entry(ki18n("Qt Version:"), QString::fromLatin1(qVersion())), | ||
246 | } | 184 | new KernelEntry(), | ||
247 | // Create a formatted list of grouped processors | 185 | new BitEntry() | ||
248 | QStringList names; | 186 | }); | ||
249 | names.reserve(processorMap.count()); | 187 | | ||
250 | for (auto it = processorMap.constBegin(); it != processorMap.constEnd(); ++it) { | 188 | // hardware | ||
251 | const int count = it.value(); | 189 | addSectionHeader(i18nc("@title:group", "Hardware")); | ||
252 | QString name = it.key(); | 190 | addEntriesToGrid({ | ||
253 | name.replace(QStringLiteral("(TM)"), QChar(8482)); | 191 | new CPUEntry(), | ||
254 | name.replace(QStringLiteral("(R)"), QChar(174)); | 192 | new MemoryEntry() | ||
255 | name = name.simplified(); | 193 | }); | ||
256 | names.append(QStringLiteral("%1 × %2").arg(count).arg(name)); | | |||
257 | } | | |||
258 | | ||||
259 | const QString processorLabel = names.join(QLatin1String(", ")); | | |||
260 | ui->processorLabel->setText(processorLabel); | | |||
261 | if (ui->processorLabel->text().isEmpty()) { | | |||
262 | ui->processor->setHidden(true); | | |||
263 | ui->processorLabel->setHidden(true); | | |||
264 | } else { | | |||
265 | labelsForClipboard << qMakePair(ui->processor, ui->processorLabel); | | |||
266 | englishTextForClipboard += QStringLiteral("Processors: %1\n").arg(processorLabel); | | |||
267 | } | | |||
268 | | ||||
269 | const qlonglong totalRam = calculateTotalRam(); | | |||
270 | const QString memoryLabel = totalRam > 0 | | |||
271 | ? i18nc("@label %1 is the formatted amount of system memory (e.g. 7,7 GiB)", | | |||
272 | "%1 of RAM", KFormat().formatByteSize(totalRam)) | | |||
273 | : i18nc("Unknown amount of RAM", "Unknown"); | | |||
274 | ui->memoryLabel->setText(memoryLabel); | | |||
275 | labelsForClipboard << qMakePair(ui->memory, ui->memoryLabel); | | |||
276 | englishTextForClipboard += QStringLiteral("Memory: %1\n").arg(KFormat().formatByteSize(totalRam)); | | |||
277 | } | 194 | } | ||
278 | 195 | | |||
279 | void Module::copyToClipboard() | 196 | void Module::copyToClipboard() | ||
280 | { | 197 | { | ||
281 | QString text; | 198 | QString text; | ||
282 | // note that this loop does not necessarily represent the same order as in the GUI | 199 | for (auto entry : m_entries) { | ||
283 | for (auto labelPair : qAsConst(labelsForClipboard)) { | 200 | text += entry->diagnosticLine(Entry::Language::System); | ||
284 | const auto valueLabel = labelPair.second; | | |||
285 | if (!valueLabel->isHidden()) { | | |||
286 | const auto descriptionLabelText = labelPair.first->text(); | | |||
287 | const auto valueLabelText = valueLabel->text(); | | |||
288 | text += i18nc("%1 is a label already including a colon, %2 is the corresponding value", "%1 %2", descriptionLabelText, valueLabelText) + QStringLiteral("\n"); | | |||
289 | } | 201 | } | ||
290 | } | | |||
291 | | ||||
292 | QGuiApplication::clipboard()->setText(text); | 202 | QGuiApplication::clipboard()->setText(text); | ||
293 | } | 203 | } | ||
294 | 204 | | |||
295 | void Module::copyToClipboardInEnglish() | 205 | void Module::copyToClipboardInEnglish() | ||
296 | { | 206 | { | ||
297 | QGuiApplication::clipboard()->setText(englishTextForClipboard); | 207 | QString text; | ||
298 | } | 208 | for (auto entry : m_entries) { | ||
299 | 209 | text += entry->diagnosticLine(Entry::Language::English); | |||
300 | QString Module::plasmaVersion() const | | |||
301 | { | | |||
302 | const QStringList &filePaths = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, | | |||
303 | QStringLiteral("xsessions/plasma.desktop")); | | |||
304 | | ||||
305 | if (filePaths.length() < 1) { | | |||
306 | return QString(); | | |||
307 | } | 210 | } | ||
308 | 211 | QGuiApplication::clipboard()->setText(text); | |||
309 | // Despite the fact that there can be multiple desktop files we simply take | | |||
310 | // the first one as users usually don't have xsessions/ in their $HOME | | |||
311 | // data location, so the first match should (usually) be the only one and | | |||
312 | // reflect the plasma session run. | | |||
313 | KDesktopFile desktopFile(filePaths.first()); | | |||
314 | return desktopFile.desktopGroup().readEntry("X-KDE-PluginInfo-Version", QString()); | | |||
315 | } | 212 | } |