#include "mainwindow.h" MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) , ui(new Ui::MainWindow) , settings("kdiskinfo", "kdiskinfo") , initializing(true) { ui->setupUi(this); buttonGroup = new QButtonGroup(this); buttonGroup->setExclusive(true); horizontalLayout = ui->horizontalLayout; diskName = qobject_cast(ui->centralwidget->findChild("diskName")); temperatureValue = qobject_cast(ui->centralwidget->findChild("temperatureValueLabel")); healthStatusValue = qobject_cast(ui->centralwidget->findChild("healthStatusValueLabel")); firmwareLineEdit = qobject_cast(ui->centralwidget->findChild("firmwareLineEdit")); serialNumberLineEdit = qobject_cast(ui->centralwidget->findChild("serialNumberLineEdit")); typeLineEdit = qobject_cast(ui->centralwidget->findChild("typeLineEdit")); protocolLineEdit = qobject_cast(ui->centralwidget->findChild("protocolLineEdit")); deviceNodeLineEdit = qobject_cast(ui->centralwidget->findChild("deviceNodeLineEdit")); totalReadsLineEdit = qobject_cast(ui->centralwidget->findChild("totalReadsLineEdit")); totalWritesLineEdit = qobject_cast(ui->centralwidget->findChild("totalWritesLineEdit")); rotationRateLineEdit = qobject_cast(ui->centralwidget->findChild("rotationRateLineEdit")); powerOnCountLineEdit = qobject_cast(ui->centralwidget->findChild("powerOnCountLineEdit")); powerOnHoursLineEdit = qobject_cast(ui->centralwidget->findChild("powerOnHoursLineEdit")); tableWidget = qobject_cast(ui->centralwidget->findChild("dataTable")); serialNumberLineEdit->setEchoMode(QLineEdit::Password); nextButton = ui->centralwidget->findChild("nextButton"); prevButton = ui->centralwidget->findChild("previousButton"); connect(nextButton, &QPushButton::clicked, this, &MainWindow::onNextButtonClicked); connect(prevButton, &QPushButton::clicked, this, &MainWindow::onPrevButtonClicked); nextButton->setFocusPolicy(Qt::NoFocus); prevButton->setFocusPolicy(Qt::NoFocus); goodColor = QColor(Qt::green); cautionColor = QColor(Qt::yellow); badColor = QColor(Qt::red); naColor = QColor(Qt::gray); ui->actionIgnore_C4_Reallocation_Event_Count->setChecked(settings.value("actionIgnore_C4_Reallocation_Event_Count", true).toBool()); ui->actionHEX->setChecked(settings.value("actionHEX", true).toBool()); ui->actionUse_Fahrenheit->setChecked(settings.value("actionUse_Fahrenheit", false).toBool()); QAction *toggleEchoModeAction = serialNumberLineEdit->addAction(QIcon::fromTheme(QStringLiteral("visibility")), QLineEdit::TrailingPosition); connect(toggleEchoModeAction, &QAction::triggered, this, [=]() { if (serialNumberLineEdit->echoMode() == QLineEdit::Password) { serialNumberLineEdit->setEchoMode(QLineEdit::Normal); toggleEchoModeAction->setIcon(QIcon::fromTheme(QStringLiteral("hint"))); } else if (serialNumberLineEdit->echoMode() == QLineEdit::Normal) { serialNumberLineEdit->setEchoMode(QLineEdit::Password); toggleEchoModeAction->setIcon(QIcon::fromTheme(QStringLiteral("visibility"))); } }); scanDevices(); this->setFocus(); initializing = false; } MainWindow::~MainWindow() { delete ui; } void MainWindow::onNextButtonClicked() { int currentIndex = buttonGroup->buttons().indexOf(buttonGroup->checkedButton()); int nextIndex = (currentIndex + 1) % buttonGroup->buttons().size(); buttonGroup->buttons().at(nextIndex)->click(); updateNavigationButtons(nextIndex); } void MainWindow::onPrevButtonClicked() { int currentIndex = buttonGroup->buttons().indexOf(buttonGroup->checkedButton()); int prevIndex = (currentIndex - 1 + buttonGroup->buttons().size()) % buttonGroup->buttons().size(); buttonGroup->buttons().at(prevIndex)->click(); updateNavigationButtons(prevIndex); } void MainWindow::updateNavigationButtons(int currentIndex) { prevButton->setEnabled(currentIndex > 0); // We can use setVisible if we want to mimic CrystalDiskInfo nextButton->setEnabled(currentIndex < buttonGroup->buttons().size() - 1); } void MainWindow::scanDevices() { QString output = getSmartctlOutput({"--scan", "--json"}, false); QJsonDocument doc = QJsonDocument::fromJson(output.toUtf8()); QJsonObject jsonObj = doc.object(); devices = jsonObj["devices"].toArray(); QStringList commandList; for (const QJsonValue &value : qAsConst(devices)) { QJsonObject device = value.toObject(); QString deviceName = device["name"].toString(); commandList.append(QString("smartctl --all --json %1").arg(deviceName)); } QString command = commandList.join(" ; "); QString allDevicesOutput = getSmartctlOutput({"sh", "-c", command}, true); int startIndex = 0; int endIndex = 0; static const QRegularExpression re("\\}\\n\\{"); while ((endIndex = allDevicesOutput.indexOf(re, startIndex)) != -1) { ++endIndex; QString jsonFragment = allDevicesOutput.mid(startIndex, endIndex - startIndex); deviceOutputs.append(jsonFragment); startIndex = endIndex; } if (startIndex < allDevicesOutput.size()) { QString jsonFragment = allDevicesOutput.mid(startIndex); deviceOutputs.append(jsonFragment); } updateUI(); } void MainWindow::updateUI() { bool firstTime = true; globalIsNvme = false; for (int i = 0; i < devices.size(); ++i) { QJsonObject device = devices[i].toObject(); QString deviceName = device["name"].toString(); QString allOutput; if (i >= 0 && i < deviceOutputs.size()) { allOutput = deviceOutputs[i]; } QJsonDocument localDoc = QJsonDocument::fromJson(allOutput.toUtf8()); QJsonObject localObj = localDoc.object(); QString modelName = localObj["model_name"].toString(); QJsonArray attributes = localObj["ata_smart_attributes"].toObject()["table"].toArray(); QJsonObject nvmeLog = localObj["nvme_smart_health_information_log"].toObject(); QString temperature = "-- °C"; bool healthPassed = localObj["smart_status"].toObject()["passed"].toBool(); bool caution = false; bool bad = false; QString health; QColor healthColor; QString protocol = localObj["device"].toObject()["protocol"].toString(); bool isNvme = (protocol == "NVMe"); int temperatureInt = localObj["temperature"].toObject()["current"].toInt(); if (temperatureInt > 0) { if (ui->actionUse_Fahrenheit->isChecked()) { int fahrenheit = static_cast((temperatureInt * 9.0 / 5.0) + 32.0); temperature = QString::number(fahrenheit) + " °F"; } else { temperature = QString::number(temperatureInt) + " °C"; } } QVector> nvmeSmartOrdered; if (!isNvme) { for (const QJsonValue &attr : attributes) { QJsonObject attrObj = attr.toObject(); if ((attrObj["id"] == 5 || attrObj["id"] == 197 || attrObj["id"] == 198 || (attrObj["id"] == 196 && !(ui->actionIgnore_C4_Reallocation_Event_Count->isChecked()))) && attrObj["raw"].toObject()["value"].toDouble()) { caution = true; qDebug() << "here"; } if (attrObj["thresh"].toInt() && (attrObj["value"].toInt() < attrObj["thresh"].toInt())) { bad = true; } } } else { JsonParser parser; nvmeSmartOrdered = parser.parse(allOutput); int row = 1; for (const QPair &pair : std::as_const(nvmeSmartOrdered)) { QString id = QString("%1").arg(row, 2, 16, QChar('0')).toUpper(); int raw = pair.second; if (id == "01" && raw) { bad = true; } else if (id == "03") { int availableSpareThreshold = nvmeSmartOrdered.at(3).second; if (availableSpareThreshold > 100) { // Thx to crystaldiskinfo for these workarounds ; } else if (raw == 0 && availableSpareThreshold == 0) { ; } else if (raw < availableSpareThreshold) { bad = true; } else if (availableSpareThreshold != 100 && (raw == availableSpareThreshold)) { caution = true; } } else if (id == "05" && raw >= 90) { // Make this configurable, currently hardcoded to 10% caution = true; } ++row; } } if (healthPassed && !caution && !bad) { health = "Good"; healthColor = goodColor; } else if (healthPassed && caution && !bad) { health = "Caution"; healthColor = cautionColor; } else if ((bad || !healthPassed) && !modelName.isEmpty()){ health = "Bad"; healthColor = badColor; } else { health = "Unknown"; healthColor = naColor; } CustomButton *button = new CustomButton(health, deviceName, temperature, healthColor, this); buttonGroup->addButton(button); horizontalLayout->addWidget(button); button->setCheckable(true); button->setAutoExclusive(true); connect(button, &QPushButton::clicked, this, [=]() { if (isNvme) { populateWindow(localObj, health, nvmeSmartOrdered); } else { populateWindow(localObj, health); } updateNavigationButtons(buttonGroup->buttons().indexOf(button)); }); if (firstTime) { globalObj = localObj; globalHealth = health; button->setChecked(true); firstTime = false; globalIsNvme = isNvme; if (isNvme) { globalNvmeSmartOrdered = nvmeSmartOrdered; } } } buttonStretch = new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Minimum); horizontalLayout->addSpacerItem(buttonStretch); if (globalIsNvme) { populateWindow(globalObj, globalHealth, globalNvmeSmartOrdered); } else { populateWindow(globalObj, globalHealth); } updateNavigationButtons(buttonGroup->buttons().indexOf(buttonGroup->checkedButton())); } void MainWindow::populateWindow(const QJsonObject &localObj, const QString &health, const QVector>& nvmeLogOrdered) { QJsonArray attributes = localObj["ata_smart_attributes"].toObject()["table"].toArray(); QJsonObject nvmeLog = localObj["nvme_smart_health_information_log"].toObject(); QString modelName = localObj["model_name"].toString(); QString firmwareVersion = localObj["firmware_version"].toString(); float userCapacityGB = localObj.value("user_capacity").toObject().value("bytes").toDouble() / 1e9; int temperatureInt = localObj["temperature"].toObject()["current"].toInt(); QString userCapacityString = QString::number(static_cast(userCapacityGB)) + "." + QString::number(static_cast((userCapacityGB - static_cast(userCapacityGB)) * 10)) + " GB"; QString totalReads = "----"; QString totalWrites = "----"; QString percentage = ""; QString serialNumber = localObj["serial_number"].toString(); QJsonObject deviceObj = localObj["device"].toObject(); QString protocol = deviceObj["protocol"].toString(); QString type = deviceObj["type"].toString(); QString name = deviceObj["name"].toString(); bool isNvme = (protocol == "NVMe"); deviceJson = localObj; diskName->setText("

" + modelName + " " + userCapacityString + "

"); firmwareLineEdit->setText(firmwareVersion); serialNumberLineEdit->setText(serialNumber); typeLineEdit->setText(type); protocolLineEdit->setText(protocol); deviceNodeLineEdit->setText(name); int rotationRateInt = localObj["rotation_rate"].toInt(-1); QString rotationRate; if (rotationRateInt > 0) { rotationRate = QString::number(rotationRateInt); } else if (rotationRateInt == 0 || isNvme) { rotationRate = "---- (SSD)"; } else { rotationRate = "----"; } rotationRateLineEdit->setText(rotationRate); rotationRateLineEdit->setAlignment(Qt::AlignRight); int powerCycleCountInt = localObj["power_cycle_count"].toInt(-1); QString powerCycleCount; if (powerCycleCountInt >= 0) { powerCycleCount = QString::number(powerCycleCountInt) + " count"; } else { powerCycleCount = "Unknown"; } powerOnCountLineEdit->setText(powerCycleCount); powerOnCountLineEdit->setAlignment(Qt::AlignRight); int powerOnTimeInt = localObj["power_on_time"].toObject().value("hours").toInt(-1); QString powerOnTime; if (powerOnTimeInt >= 0) { powerOnTime = QString::number(powerOnTimeInt) + " hours"; } else { powerOnTime = "Unknown"; } powerOnHoursLineEdit->setText(powerOnTime); powerOnHoursLineEdit->setAlignment(Qt::AlignRight); if (!isNvme) { for (const QJsonValue &attr : attributes) { QJsonObject attrObj = attr.toObject(); if (attrObj["id"] == 202) { if (attrObj["name"] == "Percent_Lifetime_Remain") { int percentageUsed = 100 - attrObj["raw"].toObject()["value"].toInt(); percentage = QString::number(percentageUsed) + " %"; } } else if (attrObj["id"] == 241) { if (attrObj["name"] == "Total_Writes_GB") { totalWrites = QString::number(attrObj["raw"].toObject()["value"].toInt()) + " GB"; } else if (attrObj["name"] == "Host_Writes_32MiB") { double gigabytes = (attrObj["raw"].toObject()["value"].toInt() * 32 * 1024.0 * 1024.0) / 1e9; totalWrites = QString::number(static_cast(gigabytes)) + " GB"; } else if (attrObj["name"] == "Total_LBAs_Written") { unsigned int logicalBlockSize = localObj["logical_block_size"].toInt(); unsigned long long lbaWritten = attrObj["raw"].toObject()["value"].toVariant().toLongLong(); unsigned long long oneGB = static_cast(std::pow(2, 30)); unsigned long long totalGbWritten = (lbaWritten * logicalBlockSize) / oneGB; totalWrites = QString::number(static_cast(totalGbWritten)) + " GB"; } else if (attrObj["name"] == "Host_Writes_GiB" || attrObj["name"] == "Lifetime_Writes_GiB") { double gibibytes = attrObj["raw"].toObject()["value"].toDouble(); double bytesPerGiB = static_cast(1ULL << 30); double bytesPerGB = 1e9; double conversionFactor = bytesPerGiB / bytesPerGB; double gigabytes = gibibytes * conversionFactor; totalWrites = QString::number(static_cast(gigabytes)) + " GB"; } } else if (attrObj["id"] == 242) { if (attrObj["name"] == "Total_Reads_GB") { totalReads = QString::number(attrObj["raw"].toObject()["value"].toInt()) + " GB"; } else if (attrObj["name"] == "Host_Reads_32MiB") { double gigabytes = (attrObj["raw"].toObject()["value"].toInt() * 32 * 1024.0 * 1024.0) / 1e9; totalReads = QString::number(static_cast(gigabytes)) + " GB"; } else if (attrObj["name"] == "Total_LBAs_Read") { unsigned int logicalBlockSize = localObj["logical_block_size"].toInt(); unsigned long long lbaRead = attrObj["raw"].toObject()["value"].toVariant().toLongLong(); unsigned long long oneGB = static_cast(std::pow(2, 30)); unsigned long long totalGbRead = (lbaRead * logicalBlockSize) / oneGB; totalReads = QString::number(static_cast(totalGbRead)) + " GB"; } else if (attrObj["name"] == "Host_Reads_GiB" || attrObj["name"] == "Lifetime_Reads_GiB") { double gibibytes = attrObj["raw"].toObject()["value"].toDouble(); double bytesPerGiB = static_cast(1ULL << 30); double bytesPerGB = 1e9; double conversionFactor = bytesPerGiB / bytesPerGB; double gigabytes = gibibytes * conversionFactor; totalReads = QString::number(static_cast(gigabytes)) + " GB"; } } else if (attrObj["id"] == 246) { // MX500 if (attrObj["name"] == "Total_LBAs_Written") { unsigned int logicalBlockSize = localObj["logical_block_size"].toInt(); unsigned long long lbaWritten = attrObj["raw"].toObject()["value"].toVariant().toLongLong(); unsigned long long oneGB = static_cast(std::pow(2, 30)); unsigned long long totalGbWritten = (lbaWritten * logicalBlockSize) / oneGB; totalWrites = QString::number(static_cast(totalGbWritten)) + " GB"; } } else if (attrObj["name"] == "Remaining_Lifetime_Perc") { int percentageUsed = attrObj["raw"].toObject()["value"].toInt(); percentage = QString::number(percentageUsed) + " %"; } else if (attrObj["name"] == "Media_Wearout_Indicator" || attrObj["name"] == "SSD_Life_Left") { int percentageUsed = attrObj["value"].toInt(); percentage = QString::number(percentageUsed) + " %"; } } if (percentage.isEmpty() && rotationRate == "---- (SSD)") { // Workaround for some drives which have this and another attribute for (const QJsonValue &attr : attributes) { QJsonObject attrObj = attr.toObject(); if (attrObj["name"] == "Wear_Leveling_Count") { int percentageUsed = attrObj["value"].toInt(); percentage = QString::number(percentageUsed) + " %"; break; } } } } else { for (auto smartItem = nvmeLog.begin(); smartItem != nvmeLog.end(); ++smartItem) { QString key = smartItem.key(); QJsonValue value = smartItem.value(); if (key == "data_units_written") { double dataUnitsWritten = value.toDouble(); double totalGbWritten = (dataUnitsWritten * 512) / 1'000'000; totalWrites = QString::number(static_cast(totalGbWritten)) + " GB"; } else if (key == "data_units_read") { double dataUnitsRead = value.toDouble(); double totalGbRead = (dataUnitsRead * 512) / 1'000'000; totalReads = QString::number(static_cast(totalGbRead)) + " GB"; } else if (key == "percentage_used") { int percentageUsed = 100 - value.toInt(); percentage = QString::number(percentageUsed) + " %"; } } } if (totalReads == "0 GB") { totalReads = "----"; } if (totalWrites == "0 GB") { totalWrites = "----"; } totalReadsLineEdit->setText(totalReads); totalReadsLineEdit->setAlignment(Qt::AlignRight); totalWritesLineEdit->setText(totalWrites); totalWritesLineEdit->setAlignment(Qt::AlignRight); if (temperatureInt > 55) { temperatureValue->setStyleSheet("background-color: " + badColor.name() + ";"); } else if ((temperatureInt < 55) && (temperatureInt > 50)){ temperatureValue->setStyleSheet("background-color: " + cautionColor.name() + ";"); } else if (temperatureInt == 0) { temperatureValue->setStyleSheet("background-color: " + naColor.name() + ";"); } else { temperatureValue->setStyleSheet("background-color: " + goodColor.name() + ";"); } QString labelStyle = "font-size:12pt; font-weight:700; color:black"; if (temperatureInt > 0) { if (ui->actionUse_Fahrenheit->isChecked()) { int fahrenheit = static_cast((temperatureInt * 9.0 / 5.0) + 32.0); temperatureValue->setText("

" + QString::number(fahrenheit) + " °F

"); } else { temperatureValue->setText("

" + QString::number(temperatureInt) + " °C

"); } } else { temperatureValue->setText("

" + "-- °C" + "

"); } temperatureValue->setAlignment(Qt::AlignCenter); if (health == "Bad") { healthStatusValue->setStyleSheet("background-color: " + badColor.name() + ";"); } else if (health == "Caution"){ healthStatusValue->setStyleSheet("background-color: " + cautionColor.name() + ";"); } else if (health == "Good") { healthStatusValue->setStyleSheet("background-color: " + goodColor.name() + ";"); } else { healthStatusValue->setStyleSheet("background-color: " + naColor.name() + ";"); } QString percentageText = ""; if (!percentage.isEmpty()) { percentageText = "
" + percentage; } healthStatusValue->setText("

" + health + percentageText + "

"); healthStatusValue->setAlignment(Qt::AlignCenter); if (protocol != "NVMe") { addSmartAttributesTable(attributes); } else { addNvmeLogTable(nvmeLogOrdered); } } void MainWindow::addNvmeLogTable(const QVector>& nvmeLogOrdered) { QString warningMessage = ""; tableWidget->setColumnCount(4); tableWidget->setHorizontalHeaderLabels({"", "ID", "Attribute Name", "Raw Values"}); tableWidget->verticalHeader()->setVisible(false); tableWidget->setItemDelegateForColumn(0, new StatusDot(tableWidget)); tableWidget->setRowCount(nvmeLogOrdered.size()); for (int i = 0; i < tableWidget->columnCount(); ++i) { QTableWidgetItem *headerItem = tableWidget->horizontalHeaderItem(i); if (headerItem) { if (i == 2) { headerItem->setTextAlignment(Qt::AlignLeft | Qt::AlignVCenter); } else if (i == 3) { headerItem->setTextAlignment(Qt::AlignRight | Qt::AlignVCenter); } } } int row = 0; for (const QPair &pair : std::as_const(nvmeLogOrdered)) { QString id = QString("%1").arg(row + 1, 2, 16, QChar('0')).toUpper(); QString key = pair.first; QString name = key.replace("_", " "); name = toTitleCase(name); int rawInt = pair.second; QString raw = QString::number(rawInt); if ((ui->actionHEX->isChecked())) { raw = QString("%1").arg(raw.toUInt(nullptr), 14, 16, QChar('0')).toUpper(); } QColor statusColor; if (id == "01" && rawInt) { statusColor = badColor; if (rawInt == 1) { // Still need to figure out if this is DEC or HEX in JSON warningMessage = tr("Available spare capacity has fallen below the threshold"); // THX to CrystalDiskInfo for the messages } else if (rawInt == 2) { warningMessage = tr("Temperature error (Overheat or Overcool)"); } else if (rawInt == 4) { warningMessage = tr("NVM subsystem reliability has been degraded"); } else if (rawInt == 8) { warningMessage = tr("Media has been placed in Read Only Mode"); } else if (rawInt == 10) { warningMessage = tr("Volatile memory backup device has Failed"); } else if (rawInt == 20) { warningMessage = tr("Persistent memory region has become Read-Only"); } } else if (id == "03") { int availableSpareThreshold = nvmeLogOrdered.at(3).second; if (availableSpareThreshold > 100) { // Thx to crystaldiskinfo for these workarounds statusColor = goodColor; } else if (rawInt == 0 && availableSpareThreshold == 0) { statusColor = goodColor; } else if (rawInt < availableSpareThreshold) { statusColor = badColor; } else if (availableSpareThreshold != 100 && (rawInt == availableSpareThreshold)) { statusColor = cautionColor; } else { statusColor = goodColor; } } else if (id == "05" && rawInt >= 90) { // Make this configurable, currently hardcoded to 10% statusColor = cautionColor; } else { statusColor = goodColor; } QTableWidgetItem *statusItem = new QTableWidgetItem(); statusItem->setData(Qt::BackgroundRole, QVariant(statusColor)); QTableWidgetItem *idItem = new QTableWidgetItem(id); idItem->setTextAlignment(Qt::AlignCenter); QTableWidgetItem *nameItem = new QTableWidgetItem(name); QTableWidgetItem *rawItem = new QTableWidgetItem(raw); rawItem->setTextAlignment(Qt::AlignRight | Qt::AlignVCenter); tableWidget->setItem(row, 0, statusItem); tableWidget->setItem(row, 1, idItem); tableWidget->setItem(row, 2, nameItem); tableWidget->setItem(row, 3, rawItem); ++row; } if(!warningMessage.isEmpty()) { QMessageBox::warning(nullptr, tr("Critical Warning"), warningMessage); } } void MainWindow::addSmartAttributesTable(const QJsonArray &attributes) { tableWidget->setColumnCount(7); tableWidget->setHorizontalHeaderLabels({"", "ID", "Attribute Name", "Current", "Worst", "Threshold", "Raw Values"}); tableWidget->verticalHeader()->setVisible(false); tableWidget->setItemDelegateForColumn(0, new StatusDot(tableWidget)); tableWidget->setRowCount(attributes.size()); for (int i = 0; i < tableWidget->columnCount(); ++i) { QTableWidgetItem *headerItem = tableWidget->horizontalHeaderItem(i); if (headerItem) { if (i == 2) { headerItem->setTextAlignment(Qt::AlignLeft | Qt::AlignVCenter); } else if (i > 2) { headerItem->setTextAlignment(Qt::AlignRight | Qt::AlignVCenter); } } } int row = 0; for (const QJsonValue &attr : attributes) { QJsonObject attrObj = attr.toObject(); QString id = QString("%1").arg(attrObj["id"].toInt(), 2, 16, QChar('0')).toUpper(); QString name = attrObj["name"].toString().replace("_", " "); int value = attrObj["value"].toInt(); int worst = attrObj["worst"].toInt(); int thresh = attrObj["thresh"].toInt(); QString rawString = attrObj["raw"].toObject()["string"].toString(); QString rawHEX = rawString; int spaceIndex = rawHEX.indexOf(' '); if (spaceIndex != -1) { rawHEX = rawHEX.left(spaceIndex); } if (rawHEX.startsWith("0x") && rawHEX.length() == 14) { rawHEX = rawHEX.mid(2).toUpper(); } else { rawHEX = QString("%1").arg(rawHEX.toUInt(nullptr), 12, 16, QChar('0')).toUpper(); } QColor statusColor; if (thresh && (value < thresh)) { statusColor = badColor; } else if ((id == "05" || id == "C5" || id == "C6" || (id == "C4" && !(ui->actionIgnore_C4_Reallocation_Event_Count->isChecked()))) && (rawHEX != "000000000000")) { statusColor = cautionColor; } else { statusColor = goodColor; } QTableWidgetItem *statusItem = new QTableWidgetItem(); statusItem->setData(Qt::BackgroundRole, QVariant(statusColor)); QTableWidgetItem *idItem = new QTableWidgetItem(id); idItem->setTextAlignment(Qt::AlignCenter); QTableWidgetItem *nameItem = new QTableWidgetItem(name); QTableWidgetItem *valueItem = new QTableWidgetItem(QString::number(value)); valueItem->setTextAlignment(Qt::AlignRight | Qt::AlignVCenter); QTableWidgetItem *worstItem = new QTableWidgetItem(QString::number(worst)); worstItem->setTextAlignment(Qt::AlignRight | Qt::AlignVCenter); QTableWidgetItem *threshItem = new QTableWidgetItem(QString::number(thresh)); threshItem->setTextAlignment(Qt::AlignRight | Qt::AlignVCenter); QTableWidgetItem *rawItem; if (ui->actionHEX->isChecked()) { rawItem = new QTableWidgetItem(rawHEX); } else { rawItem = new QTableWidgetItem(rawString); } rawItem->setTextAlignment(Qt::AlignRight | Qt::AlignVCenter); tableWidget->setItem(row, 0, statusItem); tableWidget->setItem(row, 1, idItem); tableWidget->setItem(row, 2, nameItem); tableWidget->setItem(row, 3, valueItem); tableWidget->setItem(row, 4, worstItem); tableWidget->setItem(row, 5, threshItem); tableWidget->setItem(row, 6, rawItem); ++row; } tableWidget->horizontalHeader()->setSectionResizeMode(2, QHeaderView::Stretch); for (int i = 0; i < tableWidget->columnCount(); ++i) { if (i != 2) { tableWidget->horizontalHeader()->setSectionResizeMode(i, QHeaderView::ResizeToContents); } } tableWidget->verticalHeader()->setSectionResizeMode(QHeaderView::Stretch); tableWidget->setEditTriggers(QAbstractItemView::NoEditTriggers); } bool commandExists(const QString &command) { QStringList paths = QString::fromLocal8Bit(qgetenv("PATH")).split(QDir::listSeparator(), Qt::SkipEmptyParts); for (const QString &path : paths) { QString absolutePath = QDir(path).absoluteFilePath(command); if (QFile::exists(absolutePath) && QFileInfo(absolutePath).isExecutable()) { return true; } } return false; } QString MainWindow::getSmartctlOutput(const QStringList &arguments, bool root) { QProcess process; QString command; QStringList commandArgs; if (root) { command = "pkexec"; commandArgs = QStringList(); } else { command = "smartctl"; commandArgs = QStringList(); } if (!commandExists("smartctl")) { QMessageBox::critical(this, tr("KDiskInfo Error"), tr("smartctl was not found, please install it!")); QTimer::singleShot(0, qApp, &QApplication::quit); } else { commandArgs.append(arguments); process.start(command, commandArgs); process.waitForFinished(-1); } if (process.exitCode() == 127) { QMessageBox::critical(this, tr("KDiskInfo Error"), tr("KDiskInfo needs root access in order to read S.M.A.R.T. data!")); QTimer::singleShot(0, qApp, &QApplication::quit); } return process.readAllStandardOutput(); } QString MainWindow::toTitleCase(const QString& sentence) { QString result; bool capitalizeNext = true; for (const QChar& c : sentence) { if (c.isLetter()) { if (capitalizeNext) { result += c.toUpper(); capitalizeNext = false; } else { result += c.toLower(); } } else { result += c; if (c == ' ') { capitalizeNext = true; } } } return result; } void MainWindow::clearButtonGroup() { QList buttons = buttonGroup->buttons(); for (QAbstractButton* button : buttons) { buttonGroup->removeButton(button); delete button; } horizontalLayout->removeItem(buttonStretch); delete buttonStretch; } void MainWindow::on_actionExit_triggered() { qApp->quit(); } void MainWindow::on_actionSave_JSON_triggered() { if (deviceJson.isEmpty()) { QMessageBox::information(this, tr("Empty JSON"), tr("The JSON is empty")); return; } QString fileName = QFileDialog::getSaveFileName(this, tr("Save JSON"), "", tr("JSON (*.json);;All Files (*)")); if (fileName.isEmpty()) return; else { QFile file(fileName); if (!file.open(QIODevice::WriteOnly)) { QMessageBox::information(this, tr("Unable to open file for writing"), file.errorString()); return; } QJsonDocument doc(deviceJson); QByteArray jsonData = doc.toJson(); file.write(jsonData); file.close(); } } void MainWindow::on_actionGitHub_triggered() { QDesktopServices::openUrl(QUrl("https://github.com/edisionnano/KDiskInfo")); } void MainWindow::on_actionRescan_Refresh_triggered() { clearButtonGroup(); scanDevices(); } void MainWindow::on_actionAbout_triggered() { QString message = "An ATA and NVMe S.M.A.R.T. data viewer for Linux\n\n"; message += "Licensed under the GNU G.P.L. Version 3\n\n"; message += "Made by Samantas5855"; QMessageBox::about(this, "About KDiskInfo", message); } void MainWindow::on_actionIgnore_C4_Reallocation_Event_Count_toggled(bool enabled) { settings.setValue("actionIgnore_C4_Reallocation_Event_Count", ui->actionIgnore_C4_Reallocation_Event_Count->isChecked()); if (!initializing) { clearButtonGroup(); updateUI(); } } void MainWindow::on_actionHEX_toggled(bool enabled) { settings.setValue("actionHEX", ui->actionHEX->isChecked()); if (!initializing) { clearButtonGroup(); updateUI(); } } void MainWindow::on_actionUse_Fahrenheit_toggled(bool enabled) { settings.setValue("actionUse_Fahrenheit", ui->actionUse_Fahrenheit->isChecked()); if (!initializing) { clearButtonGroup(); updateUI(); } }