diff --git a/isoimagewriter/CMakeLists.txt b/isoimagewriter/CMakeLists.txt index 1728034..1a01f27 100644 --- a/isoimagewriter/CMakeLists.txt +++ b/isoimagewriter/CMakeLists.txt @@ -1,94 +1,95 @@ set(GPGME_REQUIRED_VERSION "1.8.0") find_package(Gpgmepp ${GPGME_REQUIRED_VERSION} CONFIG REQUIRED) find_package(QGpgme ${GPGME_REQUIRED_VERSION} CONFIG REQUIRED) #find_package(KF5 REQUIRED COMPONENTS QGpgme) set(isoimagewriter_SRCS usbdevicemonitor.h common.cpp mainapplication.cpp mainwindow.cpp imagewriter.cpp physicaldevice.cpp main.cpp verifyisoworker.cpp verifyiso.cpp verifyneoniso.cpp verifykubuntuiso.cpp verifynetrunneriso.cpp verifyarchiso.cpp + isoverifier.cpp ) ecm_qt_declare_logging_category(isoimagewriter_SRCS HEADER isoimagewriter_debug.h IDENTIFIER ISOIMAGEWRITER_LOG CATEGORY_NAME org.kde.isoimagewriter DEFAULT_SEVERITY Debug) if(CMAKE_SYSTEM_NAME STREQUAL Windows) message("Compiling isoimagewriter for Windaes") set(isoimagewriter_SRCS ${isoimagewriter_SRCS} platform_win.cpp usbdevicemonitor_win.cpp externalprogressbar_win.cpp isoimagewriter.rc ) elseif(CMAKE_SYSTEM_NAME STREQUAL Linux) message("Compiling isoimagewriter for Linux") set(isoimagewriter_SRCS ${isoimagewriter_SRCS} platform_lin.cpp usbdevicemonitor_lin.cpp externalprogressbar_lin.cpp ) else() message("Unsupported Platform " . ${CMAKE_SYSTEM_NAME}) endif() add_executable(isoimagewriter ${isoimagewriter_SRCS}) #add_executable(testy testy.cpp ${isoimagewriter_common_SRCS}) #target_link_libraries(testy Qt5::Widgets KF5::Auth) if (ROSA_BRANDING) target_compile_definitions(isoimagewriter PRIVATE -DROSA_BRANDING="1") endif (ROSA_BRANDING) target_compile_definitions(isoimagewriter PRIVATE -DPROJECT_VERSION="${PROJECT_VERSION}") target_link_libraries(isoimagewriter Qt5::Widgets KF5::I18n KF5::CoreAddons QGpgme KF5::WidgetsAddons KF5::IconThemes ) if(CMAKE_SYSTEM_NAME STREQUAL Linux) target_link_libraries(isoimagewriter udev KF5::AuthCore) endif() #target_link_libraries(testy # Qt5::Widgets # KF5::I18n # KF5::CoreAddons # dl # QGpgme # Gpgmepp #) install(TARGETS isoimagewriter ${INSTALL_TARGETS_DEFAULT_ARGS}) if(CMAKE_SYSTEM_NAME STREQUAL Linux) install(PROGRAMS org.kde.isoimagewriter.desktop DESTINATION ${XDG_APPS_INSTALL_DIR}) install(FILES org.kde.isoimagewriter.appdata.xml DESTINATION ${KDE_INSTALL_METAINFODIR}) endif() # KAuth if(CMAKE_SYSTEM_NAME STREQUAL Linux) add_executable(isoimagewriter_helper common.cpp imagewriter_helper.cpp imagewriter.cpp physicaldevice.cpp) target_link_libraries(isoimagewriter_helper Qt5::Widgets KF5::AuthCore KF5::I18n KF5::WidgetsAddons) install(TARGETS isoimagewriter_helper DESTINATION ${KAUTH_HELPER_INSTALL_DIR}) kauth_install_helper_files(isoimagewriter_helper org.kde.isoimagewriter root) kauth_install_actions(org.kde.isoimagewriter isoimagewriter.actions) endif() diff --git a/isoimagewriter/isoverifier.cpp b/isoimagewriter/isoverifier.cpp new file mode 100644 index 0000000..ffa4b8f --- /dev/null +++ b/isoimagewriter/isoverifier.cpp @@ -0,0 +1,128 @@ +#include "isoverifier.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +IsoVerifier::IsoVerifier(const QString &filePath) + : m_filePath(filePath), + m_error(), + m_isIsoValid(false) +{} + +void IsoVerifier::verifyIso() +{ + QFileInfo fileInfo(m_filePath); + QString fileName = fileInfo.fileName(); + QString keyFingerprint; + + if (fileName.startsWith("neon-")) { + m_verificationMean = VerificationMean::DotSigFile; + if (!importSigningKey("neon-signing-key.gpg", keyFingerprint)) return; + } else if (fileName.startsWith("archlinux-")) { + m_verificationMean = VerificationMean::DotSigFile; + if (!importSigningKey("arch-signing-key.gpg", keyFingerprint)) return; + } else if (fileName.startsWith("kubuntu-")) { + m_verificationMean = VerificationMean::Sha256SumsFile; + if (!importSigningKey("ubuntu-signing-key.gpg", keyFingerprint)) return; + } else { + m_error = QString(i18n("Could not verify as a known distro image.")); + return; + } + + switch (m_verificationMean) { + case VerificationMean::DotSigFile: + verifyWithDotSigFile(keyFingerprint); + break; + case VerificationMean::Sha256SumsFile: + verifyWithSha256SumsFile(); + break; + } + + emit finished(m_isIsoValid, m_error); +} + +bool IsoVerifier::importSigningKey(const QString &fileName, QString &keyFingerprint) +{ + QString signingKeyFile = QStandardPaths::locate(QStandardPaths::AppDataLocation, fileName); + if (signingKeyFile.isEmpty()) { + qDebug() << "error can't find signing key" << signingKeyFile; + return false; + } + + QFile signingKey(signingKeyFile); + if (!signingKey.open(QIODevice::ReadOnly)) { + qDebug() << "error" << signingKey.errorString(); + return false; + } + + QByteArray signingKeyData = signingKey.readAll(); + QGpgME::ImportJob *importJob = QGpgME::openpgp()->importJob(); + GpgME::ImportResult importResult = importJob->exec(signingKeyData); + + if (!(importResult.numConsidered() == 1 + && (importResult.numImported() == 1 + || importResult.numUnchanged() == 1))) { + qDebug() << "Could not import gpg signature"; + return false; + } + + keyFingerprint = QString(importResult.import(0).fingerprint()); + + return true; +} + +void IsoVerifier::verifyWithDotSigFile(const QString &keyFingerprint) +{ + QString sigFilePath = m_filePath + ".sig"; + QFileInfo fileInfo(sigFilePath); + QString sigFileName = fileInfo.fileName(); + if (!QFile::exists(sigFilePath)) { + m_error = i18n("Could not find %1, please download PGP signature file " + "to same directory.", sigFileName); + return; + } + + QFile signatureFile(sigFilePath); + if (!signatureFile.open(QIODevice::ReadOnly)) { + m_error = i18n("Could not open signature file"); + return; + } + QByteArray signatureData = signatureFile.readAll(); + + QFile isoFile(m_filePath); + if (!isoFile.open(QIODevice::ReadOnly)) { + m_error = i18n("Could not open ISO image"); + return; + } + QByteArray isoData = isoFile.readAll(); + + QGpgME::VerifyDetachedJob *job = QGpgME::openpgp()->verifyDetachedJob(); + GpgME::VerificationResult result = job->exec(signatureData, isoData); + GpgME::Signature signature = result.signature(0); + + if (signature.summary() == GpgME::Signature::None + && signature.fingerprint() == keyFingerprint) { + m_isIsoValid = true; + } else if (signature.summary() & GpgME::Signature::Valid) { + m_isIsoValid = true; + } else if (signature.summary() & GpgME::Signature::KeyRevoked) { + m_error = i18n("Key is revoked."); + } else { + m_error = i18n("Uses wrong signature."); + } +} + +void IsoVerifier::verifyWithSha256SumsFile() +{ +} diff --git a/isoimagewriter/isoverifier.h b/isoimagewriter/isoverifier.h new file mode 100644 index 0000000..8768b7b --- /dev/null +++ b/isoimagewriter/isoverifier.h @@ -0,0 +1,30 @@ +#ifndef ISOVERIFIER_H +#define ISOVERIFIER_H + +#include + +class IsoVerifier : public QObject +{ + Q_OBJECT + +public: + IsoVerifier(const QString &filePath); + +public slots: + void verifyIso(); + +signals: + void finished(const bool &isIsoValid, const QString &error); + +private: + QString m_filePath; + QString m_error; + bool m_isIsoValid; + enum VerificationMean { DotSigFile, Sha256SumsFile } m_verificationMean; + + bool importSigningKey(const QString &fileName, QString &keyFingerPrint); + void verifyWithDotSigFile(const QString &keyFingerPrint); + void verifyWithSha256SumsFile(); +}; + +#endif // ISOVERIFIER_H diff --git a/isoimagewriter/mainwindow.cpp b/isoimagewriter/mainwindow.cpp index 8393048..37d86cb 100644 --- a/isoimagewriter/mainwindow.cpp +++ b/isoimagewriter/mainwindow.cpp @@ -1,562 +1,606 @@ #include "mainwindow.h" #include "mainapplication.h" #include "common.h" #include "imagewriter.h" +#include "isoverifier.h" #include "isoimagewriter_debug.h" #include #include #include #include #include #include #include #include #include #include #include +#include +#include #include MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), m_lastOpenedDir(""), m_isWriting(false), m_enumFlashDevicesWaiting(false), m_externalProgressBar(this) { setupUi(); setAcceptDrops(true); // Set initial directory m_lastOpenedDir = mApp->getInitialDir(); // Get path to ISO image from command line args (if supplied) QString isoImagePath = mApp->getInitialImage(); if (!isoImagePath.isEmpty()) { if (isoImagePath.left(7) == "file://") isoImagePath = QUrl(isoImagePath).toLocalFile(); if (!isoImagePath.isEmpty()) { isoImagePath = QDir(isoImagePath).absolutePath(); // Update the default open dir m_lastOpenedDir = isoImagePath.left(isoImagePath.lastIndexOf('/')); preprocessIsoImage(isoImagePath); } } // Load the list of USB flash devices QTimer::singleShot(0, this, &MainWindow::enumFlashDevices); } void MainWindow::scheduleEnumFlashDevices() { if (m_isWriting) m_enumFlashDevicesWaiting = true; else enumFlashDevices(); } void MainWindow::setupUi() { // Logo QLabel *logoLabel = new QLabel; logoLabel->setPixmap(QIcon::fromTheme("drive-removable-media").pixmap(QSize(50, 50))); logoLabel->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); QLabel *titleLabel = new QLabel; titleLabel->setTextFormat(Qt::RichText); titleLabel->setText("

KDE ISO Image Writer

" "A quick and simple way to create a bootable USB drive."); QHBoxLayout *headerHBoxLayout = new QHBoxLayout; headerHBoxLayout->addWidget(logoLabel); headerHBoxLayout->addWidget(titleLabel); m_centralStackedWidget = new QStackedWidget; m_centralStackedWidget->addWidget(createFormWidget()); m_centralStackedWidget->addWidget(createConfirmWidget()); m_centralStackedWidget->addWidget(createProgressWidget()); m_centralStackedWidget->addWidget(createSuccessWidget()); QVBoxLayout *mainVBoxLayout = new QVBoxLayout; mainVBoxLayout->addLayout(headerHBoxLayout); mainVBoxLayout->addSpacing(15); mainVBoxLayout->addWidget(m_centralStackedWidget); QWidget *centralWidget = new QWidget; centralWidget->setLayout(mainVBoxLayout); setCentralWidget(centralWidget); } QWidget* MainWindow::createFormWidget() { // Form m_isoImageLineEdit = new QLineEdit; m_isoImageLineEdit->setReadOnly(true); m_isoImageLineEdit->setPlaceholderText(i18n("Path to ISO image...")); QAction *openIsoImageAction = m_isoImageLineEdit->addAction( QIcon::fromTheme("folder-open"), QLineEdit::TrailingPosition); connect(openIsoImageAction, &QAction::triggered, this, &MainWindow::openIsoImage); m_usbDriveComboBox = new QComboBox; m_createButton = new QPushButton(i18n("Create")); m_createButton->setEnabled(false); connect(m_createButton, &QPushButton::clicked, this, &MainWindow::showConfirmMessage); + m_busyLabel = new QLabel; + m_busyWidget = new QWidget; + m_busySpinner = new KPixmapSequenceOverlayPainter(this); + m_busySpinner->setAlignment(Qt::AlignHCenter | Qt::AlignVCenter); + m_busySpinner->setWidget(m_busyWidget); + m_busyWidget->setFixedSize(24, 24); + + QHBoxLayout *footerBoxLayout = new QHBoxLayout; + footerBoxLayout->addWidget(m_busyWidget); + footerBoxLayout->addWidget(m_busyLabel); + footerBoxLayout->addWidget(m_createButton, 0, Qt::AlignRight); + QVBoxLayout *mainVBoxLayout = new QVBoxLayout; mainVBoxLayout->addWidget(new QLabel(i18n("Write this ISO image:"))); mainVBoxLayout->addWidget(m_isoImageLineEdit); mainVBoxLayout->addSpacing(5); mainVBoxLayout->addWidget(new QLabel(i18n("To this USB drive:"))); mainVBoxLayout->addWidget(m_usbDriveComboBox); mainVBoxLayout->addSpacing(15); mainVBoxLayout->addStretch(); - mainVBoxLayout->addWidget(m_createButton, 0, Qt::AlignRight); + mainVBoxLayout->addLayout(footerBoxLayout); QWidget *formWidget = new QWidget; formWidget->setLayout(mainVBoxLayout); return formWidget; } QWidget* MainWindow::createConfirmWidget() { QLabel *iconLabel = new QLabel; iconLabel->setPixmap(QIcon::fromTheme("dialog-warning").pixmap(QSize(64, 64))); iconLabel->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); QLabel *messageLabel = new QLabel(i18n("Everything on the USB drive will " "be overwritten." "\n\nDo you want to continue?")); QHBoxLayout *messageHBoxLayout = new QHBoxLayout; messageHBoxLayout->addWidget(iconLabel, 0, Qt::AlignTop); messageHBoxLayout->addWidget(messageLabel, 0, Qt::AlignTop); QPushButton *abortButton = new QPushButton(i18n("Abort")); connect(abortButton, &QPushButton::clicked, this, &MainWindow::hideWritingProgress); QPushButton *continueButton = new QPushButton(i18n("Continue")); connect(continueButton, &QPushButton::clicked, this, &MainWindow::writeIsoImage); QHBoxLayout *buttonsHBoxLayout = new QHBoxLayout; buttonsHBoxLayout->addWidget(abortButton, 0, Qt::AlignLeft); buttonsHBoxLayout->addWidget(continueButton, 0, Qt::AlignRight); QVBoxLayout *mainVBoxLayout = new QVBoxLayout; mainVBoxLayout->addLayout(messageHBoxLayout); mainVBoxLayout->addLayout(buttonsHBoxLayout); QWidget *confirmWidget = new QWidget; confirmWidget->setLayout(mainVBoxLayout); return confirmWidget; } QWidget* MainWindow::createProgressWidget() { QLabel *messageLabel = new QLabel(i18n("Your USB drive is being created.\n\n" "This may take some time depending " "on the size of the ISO image file " "and the transfer speed.")); messageLabel->setWordWrap(true); m_progressBar = new QProgressBar; m_cancelButton = new QPushButton(i18n("Cancel")); QVBoxLayout *mainVBoxLayout = new QVBoxLayout; mainVBoxLayout->addWidget(messageLabel, 0, Qt::AlignTop | Qt::AlignHCenter); mainVBoxLayout->addWidget(m_progressBar); mainVBoxLayout->addSpacing(15); mainVBoxLayout->addWidget(m_cancelButton, 0, Qt::AlignLeft); QWidget *progressWidget = new QWidget; progressWidget->setLayout(mainVBoxLayout); return progressWidget; } QWidget* MainWindow::createSuccessWidget() { QLabel *messageLabel = new QLabel(i18n("Your live USB flash drive is now " "complete and ready to use!")); messageLabel->setWordWrap(true); QLabel *successIconLabel = new QLabel(); successIconLabel->setPixmap(QIcon::fromTheme("emblem-success").pixmap(QSize(64, 64))); QVBoxLayout *mainVBoxLayout = new QVBoxLayout; mainVBoxLayout->addWidget(messageLabel, 0, Qt::AlignCenter); mainVBoxLayout->addWidget(successIconLabel, 0, Qt::AlignHCenter); mainVBoxLayout->addSpacing(15); QWidget *successWidget = new QWidget; successWidget->setLayout(mainVBoxLayout); return successWidget; } void MainWindow::preprocessIsoImage(const QString& isoImagePath) { QFile file(isoImagePath); if (!file.open(QIODevice::ReadOnly)) { QMessageBox::critical(this, "Error", i18n("Failed to open the image file:") + "\n" + QDir::toNativeSeparators(isoImagePath) + "\n" + file.errorString()); return; } m_isoImageSize = file.size(); m_isoImagePath = isoImagePath; m_isoImageLineEdit->setText(QDir::toNativeSeparators(m_isoImagePath) + " (" + KFormat().formatByteSize(m_isoImageSize) + ")"); file.close(); - // TODO: Verify ISO image + // Verify ISO image + m_busyLabel->setText(i18n("Verifying ISO image")); + m_busyWidget->show(); + m_busySpinner->setSequence(KIconLoader::global()->loadPixmapSequence("process-working", KIconLoader::SizeSmallMedium)); + m_busySpinner->start(); + QApplication::setOverrideCursor(Qt::WaitCursor); + + IsoVerifier *isoVerifier = new IsoVerifier(m_isoImagePath); + QThread *verifierThread = new QThread(this); + + connect(verifierThread, &QThread::started, isoVerifier, &IsoVerifier::verifyIso); + connect(verifierThread, &QThread::finished, verifierThread, &QThread::deleteLater); + + connect(isoVerifier, &IsoVerifier::finished, verifierThread, &QThread::quit); + connect(isoVerifier, &IsoVerifier::finished, isoVerifier, &IsoVerifier::deleteLater); + connect(isoVerifier, &IsoVerifier::finished, + this, [this](const bool &isIsoValid, const QString &error) { + QApplication::setOverrideCursor(Qt::ArrowCursor); + + if (isIsoValid) { + m_busyLabel->setText(i18n("The ISO image is valid")); + m_busySpinner->setSequence(KIconLoader::global()->loadPixmapSequence("checkmark", KIconLoader::SizeSmallMedium)); + } else { + m_busyLabel->setText(error); + m_busySpinner->setSequence(KIconLoader::global()->loadPixmapSequence("error", KIconLoader::SizeSmallMedium)); + } + }); + + isoVerifier->moveToThread(verifierThread); + verifierThread->start(); // Enable the Write button (if there are USB flash disks present) m_createButton->setEnabled(m_usbDriveComboBox->count() > 0); } void MainWindow::cleanUp() { // Delete all the allocated UsbDevice objects attached to the combobox for (int i = 0; i < m_usbDriveComboBox->count(); ++i) { delete m_usbDriveComboBox->itemData(i).value(); } } void MainWindow::enumFlashDevices() { m_enumFlashDevicesWaiting = false; // Remember the currently selected device QString selectedDevice = ""; int idx = m_usbDriveComboBox->currentIndex(); if (idx >= 0) { UsbDevice* dev = m_usbDriveComboBox->itemData(idx).value(); selectedDevice = dev->m_PhysicalDevice; } // Remove the existing entries cleanUp(); m_usbDriveComboBox->clear(); // Disable the combobox m_usbDriveComboBox->setEnabled(false); // Add the USB flash devices to the combobox platformEnumFlashDevices(addFlashDeviceCallback, m_usbDriveComboBox); // Restore the previously selected device (if present) if (!selectedDevice.isEmpty()) for (int i = 0; i < m_usbDriveComboBox->count(); ++i) { UsbDevice* dev = m_usbDriveComboBox->itemData(i).value(); if (dev->m_PhysicalDevice == selectedDevice) { m_usbDriveComboBox->setCurrentIndex(i); break; } } // Re-enable the combobox m_usbDriveComboBox->setEnabled(true); // Update the Write button enabled/disabled state m_createButton->setEnabled(m_usbDriveComboBox->count() > 0 && m_isoImagePath != ""); // Update the Clear button enabled/disabled state // m_clearButton->setEnabled(m_usbDriveComboBox->count() > 0); } void MainWindow::writeToDevice(bool zeroing) { UsbDevice* selectedDevice = m_usbDriveComboBox->itemData( m_usbDriveComboBox->currentIndex()).value(); // Use KAuth to get required previleges in supported platforms #if defined(Q_OS_LINUX) connect(m_cancelButton, &QPushButton::clicked, this, &MainWindow::cancelWriting); KAuth::Action action("org.kde.isoimagewriter.write"); action.setHelperId("org.kde.isoimagewriter"); QVariantMap args; args[QStringLiteral("zeroing")] = QVariant(zeroing); args[QStringLiteral("imagefile")] = m_isoImagePath; args[QStringLiteral("usbdevice_visiblename")] = selectedDevice->m_VisibleName; args[QStringLiteral("usbdevice_volumes")] = selectedDevice->m_Volumes[0]; args[QStringLiteral("usbdevice_size")] = QString("%1").arg(selectedDevice->m_Size); args[QStringLiteral("usbdevice_sectorsize")] = selectedDevice->m_SectorSize; args[QStringLiteral("usbdevice_physicaldevice")] = selectedDevice->m_PhysicalDevice; action.setArguments(args); action.setTimeout(3600000); // an hour m_job = action.execute(); connect(m_job, SIGNAL(percent(KJob*,ulong)), this, SLOT(progressStep(KJob*,ulong)), Qt::DirectConnection); connect(m_job, SIGNAL(newData(QVariantMap)), this, SLOT(progressStep(QVariantMap))); connect(m_job, &KAuth::ExecuteJob::statusChanged, this, &MainWindow::statusChanged); connect(m_job, &KAuth::ExecuteJob::result, this, &MainWindow::finished); m_job->start(); #else ImageWriter* writer = new ImageWriter(zeroing ? "" : m_isoImagePath, selectedDevice); QThread *writerThread = new QThread(this); // Connect start and end signals connect(writerThread, &QThread::started, writer, &ImageWriter::writeImage); // When writer finishes its job, quit the thread connect(writer, &ImageWriter::finished, writerThread, &QThread::quit); // Guarantee deleting the objects after completion connect(writer, &ImageWriter::finished, writer, &ImageWriter::deleteLater); connect(writerThread, &QThread::finished, writerThread, &QThread::deleteLater); // If the Cancel button is pressed, inform the writer to stop the operation // Using DirectConnection because the thread does not read its own event queue until completion connect(m_cancelButton, &QPushButton::clicked, writer, &ImageWriter::cancelWriting, Qt::DirectConnection); // Each time a block is written, update the progress bar connect(writer, &ImageWriter::blockWritten, this, &MainWindow::updateProgressBar); // Show the message about successful completion on success connect(writer, &ImageWriter::success, this, &MainWindow::showSuccessMessage); // Show error message if error is sent by the worker connect(writer, &ImageWriter::error, this, &MainWindow::showErrorMessage); // Silently return back to normal dialog form if the operation was cancelled connect(writer, &ImageWriter::cancelled, this, &MainWindow::hideWritingProgress); // Now start the writer thread writer->moveToThread(writerThread); writerThread->start(); #endif showWritingProgress(alignNumberDiv((zeroing ? DEFAULT_UNIT : m_isoImageSize), DEFAULT_UNIT)); } void MainWindow::dragEnterEvent(QDragEnterEvent* event) { // Accept only files with ANSI or Unicode paths (Windows) and URIs (Linux) if (event->mimeData()->hasFormat("application/x-qt-windows-mime;value=\"FileName\"") || event->mimeData()->hasFormat("application/x-qt-windows-mime;value=\"FileNameW\"") || event->mimeData()->hasFormat("text/uri-list")) event->accept(); } void MainWindow::dropEvent(QDropEvent* event) { QString newImageFile = ""; QByteArray droppedFileName; // First, try to use the Unicode file name droppedFileName = event->mimeData()->data("application/x-qt-windows-mime;value=\"FileNameW\""); if (!droppedFileName.isEmpty()) { newImageFile = QString::fromWCharArray(reinterpret_cast(droppedFileName.constData())); } else { // If failed, use the ANSI name with the local codepage droppedFileName = event->mimeData()->data("application/x-qt-windows-mime;value=\"FileName\""); if (!droppedFileName.isEmpty()) { newImageFile = QString::fromLocal8Bit(droppedFileName.constData()); } else { // And, finally, try the URI droppedFileName = event->mimeData()->data("text/uri-list"); if (!droppedFileName.isEmpty()) { // If several files are dropped they are separated by newlines, // take the first file int newLineIndexLF = droppedFileName.indexOf('\n'); int newLineIndex = droppedFileName.indexOf("\r\n"); // Make sure both CRLF and LF are accepted if ((newLineIndexLF != -1) && (newLineIndexLF < newLineIndex)) newLineIndex = newLineIndexLF; if (newLineIndex != -1) droppedFileName = droppedFileName.left(newLineIndex); // Decode the file path from percent-encoding QUrl url = QUrl::fromEncoded(droppedFileName); if (url.isLocalFile()) newImageFile = url.toLocalFile(); } } } if (!newImageFile.isEmpty()) { // If something was really received update the information preprocessIsoImage(newImageFile); } } void MainWindow::closeEvent(QCloseEvent* event) { if (m_isWriting) { const int answer = QMessageBox::question( this, i18n("Cancel?"), i18n("Writing is in progress, abort it?")); if (answer == QMessageBox::No) event->ignore(); } } void MainWindow::addFlashDeviceCallback(void* cbParam, UsbDevice* device) { auto usbDriveComboBox = (QComboBox*)cbParam; usbDriveComboBox->addItem(device->formatDisplayName(), QVariant::fromValue(device)); } void MainWindow::openIsoImage() { const QString filter = i18n("Disk Images (%1)", QString("*.iso *.bin *.img")) + ";;" + i18n("All Files (%1)", QString("*")); QString isoImagePath = QFileDialog::getOpenFileName(this, "", m_lastOpenedDir, filter, nullptr, QFileDialog::ReadOnly); if (!isoImagePath.isEmpty()) { m_lastOpenedDir = isoImagePath.left(isoImagePath.lastIndexOf('/')); preprocessIsoImage(isoImagePath); } } void MainWindow::writeIsoImage() { if (m_usbDriveComboBox->count() == 0 || m_isoImagePath == "") return; UsbDevice* selectedDevice = m_usbDriveComboBox->itemData( m_usbDriveComboBox->currentIndex()).value(); if (m_isoImageSize > selectedDevice->m_Size) { QMessageBox::critical( this, i18n("Error"), i18n("The image is larger than your selected device!\n\n" "Image size: %1 (%2 b)\n" "Disk size: %3 (%4 b)", KFormat().formatByteSize(m_isoImageSize), m_isoImageSize, KFormat().formatByteSize(selectedDevice->m_Size), selectedDevice->m_Size), QMessageBox::Ok); return; } writeToDevice(false); } void MainWindow::updateProgressBar(int increment) { int newValue = m_progressBar->value() + increment; m_progressBar->setValue(newValue); m_externalProgressBar.SetProgressValue(newValue); } void MainWindow::showWritingProgress(int maxValue) { m_isWriting = true; // Do not accept dropped files while writing setAcceptDrops(false); // Display and customize the progress bar part m_progressBar->setMinimum(0); m_progressBar->setMaximum(maxValue); m_progressBar->setValue(0); // Expose the progress bar state to the OS m_externalProgressBar.InitProgressBar(maxValue); m_centralStackedWidget->setCurrentIndex(2); } void MainWindow::hideWritingProgress() { m_isWriting = false; // Enable drag & drop setAcceptDrops(true); // Send a signal that progressbar is no longer present m_externalProgressBar.DestroyProgressBar(); m_centralStackedWidget->setCurrentIndex(0); // If device list changed during writing update it now if (m_enumFlashDevicesWaiting) enumFlashDevices(); } void MainWindow::showErrorMessage(const QString &message) { m_externalProgressBar.ProgressSetError(); QMessageBox::critical(this, i18n("Error"), message); hideWritingProgress(); } void MainWindow::showSuccessMessage() { m_isWriting = false; // Do not accept dropped files setAcceptDrops(false); m_centralStackedWidget->setCurrentIndex(3); } void MainWindow::showConfirmMessage() { // Do not accept dropped files setAcceptDrops(false); m_centralStackedWidget->setCurrentIndex(1); } #if defined(Q_OS_LINUX) void MainWindow::cancelWriting() { qCDebug(ISOIMAGEWRITER_LOG) << "cancelWriting()"; m_job->kill(); qCDebug(ISOIMAGEWRITER_LOG) << "cancelWriting() done"; } void MainWindow::progressStep(KJob* job, unsigned long step) { Q_UNUSED(job) qCDebug(ISOIMAGEWRITER_LOG) << "progressStep %() " << step; updateProgressBar(step); } void MainWindow::progressStep(const QVariantMap & data) { qCDebug(ISOIMAGEWRITER_LOG) << "progressStep(QVariantMap) ";// << step; if (data[QStringLiteral("progress")].isValid()) { int step = data[QStringLiteral("progress")].toInt(); updateProgressBar(step); } else if (data[QStringLiteral("error")].isValid()) { showErrorMessage(data[QStringLiteral("error")].toString()); } else if (data[QStringLiteral("success")].isValid()) { showSuccessMessage(); } } void MainWindow::statusChanged(KAuth::Action::AuthStatus status) { qCDebug(ISOIMAGEWRITER_LOG) << "statusChanged: " << status; } void MainWindow::finished(KJob* job) { qCDebug(ISOIMAGEWRITER_LOG) << "finished() " << job->error(); KAuth::ExecuteJob *job2 = (KAuth::ExecuteJob *)job; qCDebug(ISOIMAGEWRITER_LOG) << "finished() " << job2->data(); if (job2->data()[QStringLiteral("success")].isValid()) showSuccessMessage(); } #endif diff --git a/isoimagewriter/mainwindow.h b/isoimagewriter/mainwindow.h index 76ee6fd..271de67 100644 --- a/isoimagewriter/mainwindow.h +++ b/isoimagewriter/mainwindow.h @@ -1,82 +1,87 @@ #ifndef MAINWINDOW_H #define MAINWINDOW_H #include "usbdevice.h" #include "externalprogressbar.h" #include +#include #include #include #include #include #include +#include #if defined(Q_OS_LINUX) #include #endif class MainWindow : public QMainWindow { Q_OBJECT public: MainWindow(QWidget *parent = nullptr); public slots: void scheduleEnumFlashDevices(); private: + QLabel *m_busyLabel; + QWidget *m_busyWidget; QLineEdit *m_isoImageLineEdit; QComboBox *m_usbDriveComboBox; QPushButton *m_createButton; QPushButton *m_cancelButton; QProgressBar *m_progressBar; QStackedWidget *m_centralStackedWidget; + KPixmapSequenceOverlayPainter *m_busySpinner; QString m_isoImagePath; quint64 m_isoImageSize; QString m_lastOpenedDir; bool m_isWriting; bool m_enumFlashDevicesWaiting; ExternalProgressBar m_externalProgressBar; #if defined(Q_OS_LINUX) KAuth::ExecuteJob *m_job; #endif void setupUi(); QWidget* createFormWidget(); QWidget* createConfirmWidget(); QWidget* createProgressWidget(); QWidget* createSuccessWidget(); void preprocessIsoImage(const QString& isoImagePath); void cleanUp(); void enumFlashDevices(); void writeToDevice(bool zeroing); void dragEnterEvent(QDragEnterEvent* event) override; void dropEvent(QDropEvent* event) override; void closeEvent(QCloseEvent* event) override; static void addFlashDeviceCallback(void* cbParam, UsbDevice* device); private slots: void openIsoImage(); void writeIsoImage(); void updateProgressBar(int increment); void showWritingProgress(int maxValue); void hideWritingProgress(); void showErrorMessage(const QString &message); void showSuccessMessage(); void showConfirmMessage(); #if defined(Q_OS_LINUX) void cancelWriting(); void progressStep(KJob* job, unsigned long step); void progressStep(const QVariantMap &); void statusChanged(KAuth::Action::AuthStatus status); void finished(KJob* job); #endif }; #endif // MAINWINDOW_H