diff --git a/CMakeLists.txt b/CMakeLists.txt index 399943e..cf2dfd0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -240,5 +240,12 @@ else() install(FILES linux/rpi-imager.desktop DESTINATION share/applications) endif() +get_target_property(QT_TARGET_TYPE Qt5::Core TYPE) +if(${QT_TARGET_TYPE} STREQUAL "STATIC_LIBRARY") + find_package(Qt5QmlImportScanner REQUIRED) + qt5_import_qml_plugins(${PROJECT_NAME}) + qt5_import_plugins(${PROJECT_NAME} INCLUDE Qt5::QSvgPlugin) +endif() + include_directories(${CURL_INCLUDE_DIR} ${LibArchive_INCLUDE_DIR} ${OPENSSL_INCLUDE_DIR}) -target_link_libraries(${PROJECT_NAME} Qt5::Core Qt5::Quick Qt5::Svg ${CURL_LIBRARIES} ${LibArchive_LIBRARIES} ${OPENSSL_LIBRARIES} ${ATOMIC_LIBRARY} ${EXTRALIBS}) +target_link_libraries(${PROJECT_NAME} PRIVATE Qt5::Core Qt5::Quick Qt5::Svg ${CURL_LIBRARIES} ${LibArchive_LIBRARIES} ${OPENSSL_LIBRARIES} ${ATOMIC_LIBRARY} ${EXTRALIBS}) diff --git a/driveformatthread.cpp b/driveformatthread.cpp index 49a220b..c83113a 100644 --- a/driveformatthread.cpp +++ b/driveformatthread.cpp @@ -186,7 +186,7 @@ void DriveFormatThread::run() return; } - proc.execute("partprobe"); + proc.execute("partprobe", QStringList() ); args.clear(); args << fatpartition; diff --git a/icons/ic_build_40px.png b/icons/ic_build_40px.png deleted file mode 100644 index 4790c36..0000000 Binary files a/icons/ic_build_40px.png and /dev/null differ diff --git a/icons/ic_build_40px.svg b/icons/ic_build_40px.svg new file mode 100644 index 0000000..e414923 --- /dev/null +++ b/icons/ic_build_40px.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/icons/ic_chevron_left_40px.png b/icons/ic_chevron_left_40px.png deleted file mode 100644 index 4d3f16c..0000000 Binary files a/icons/ic_chevron_left_40px.png and /dev/null differ diff --git a/icons/ic_chevron_left_40px.svg b/icons/ic_chevron_left_40px.svg new file mode 100644 index 0000000..c6122c7 --- /dev/null +++ b/icons/ic_chevron_left_40px.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/icons/ic_chevron_right_40px.png b/icons/ic_chevron_right_40px.png deleted file mode 100644 index 2b8b449..0000000 Binary files a/icons/ic_chevron_right_40px.png and /dev/null differ diff --git a/icons/ic_chevron_right_40px.svg b/icons/ic_chevron_right_40px.svg new file mode 100644 index 0000000..b8996ba --- /dev/null +++ b/icons/ic_chevron_right_40px.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/icons/ic_close_18px.png b/icons/ic_close_18px.png deleted file mode 100644 index 6825159..0000000 Binary files a/icons/ic_close_18px.png and /dev/null differ diff --git a/icons/ic_computer_40px.png b/icons/ic_computer_40px.png deleted file mode 100644 index 5cf5172..0000000 Binary files a/icons/ic_computer_40px.png and /dev/null differ diff --git a/icons/ic_computer_40px.svg b/icons/ic_computer_40px.svg new file mode 100644 index 0000000..e92b53c --- /dev/null +++ b/icons/ic_computer_40px.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/icons/ic_delete_40px.png b/icons/ic_delete_40px.png deleted file mode 100644 index 699a10e..0000000 Binary files a/icons/ic_delete_40px.png and /dev/null differ diff --git a/icons/ic_delete_40px.svg b/icons/ic_delete_40px.svg new file mode 100644 index 0000000..c2bcbf3 --- /dev/null +++ b/icons/ic_delete_40px.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/icons/ic_sd_storage_40px.png b/icons/ic_sd_storage_40px.png deleted file mode 100644 index c0bea0a..0000000 Binary files a/icons/ic_sd_storage_40px.png and /dev/null differ diff --git a/icons/ic_sd_storage_40px.svg b/icons/ic_sd_storage_40px.svg new file mode 100644 index 0000000..ef75ae8 --- /dev/null +++ b/icons/ic_sd_storage_40px.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/icons/ic_storage_40px.png b/icons/ic_storage_40px.png deleted file mode 100644 index f5ce8d7..0000000 Binary files a/icons/ic_storage_40px.png and /dev/null differ diff --git a/icons/ic_storage_40px.svg b/icons/ic_storage_40px.svg new file mode 100644 index 0000000..22f5fc3 --- /dev/null +++ b/icons/ic_storage_40px.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/icons/ic_usb_40px.png b/icons/ic_usb_40px.png deleted file mode 100644 index 667e9fd..0000000 Binary files a/icons/ic_usb_40px.png and /dev/null differ diff --git a/icons/ic_usb_40px.svg b/icons/ic_usb_40px.svg new file mode 100644 index 0000000..21567e5 --- /dev/null +++ b/icons/ic_usb_40px.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/icons/rpi2-hires.png b/icons/rpi2-hires.png new file mode 100644 index 0000000..e997a61 Binary files /dev/null and b/icons/rpi2-hires.png differ diff --git a/imagewriter.cpp b/imagewriter.cpp index 69d955c..e0d6e40 100644 --- a/imagewriter.cpp +++ b/imagewriter.cpp @@ -476,6 +476,7 @@ void ImageWriter::pollNetwork() if (!a.isLoopback() && a.scopeId().isEmpty()) { /* Not a loopback or IPv6 link-local address, so online */ + qDebug() << "IP:" << a; _online = true; break; } @@ -484,14 +485,23 @@ void ImageWriter::pollNetwork() if (_online) { _networkchecktimer.stop(); - qDebug() << "Network online. Synchronizing time."; - QNetworkAccessManager *manager = new QNetworkAccessManager(this); - connect(manager, SIGNAL(finished(QNetworkReply*)), SLOT(onTimeSyncReply(QNetworkReply*))); - manager->head(QNetworkRequest(QUrl(TIME_URL))); + + // Wait another 0.1 sec, as dhcpcd may not have set up nameservers yet + QTimer::singleShot(100, this, SLOT(syncTime())); } #endif } +void ImageWriter::syncTime() +{ +#ifdef Q_OS_LINUX + qDebug() << "Network online. Synchronizing time."; + QNetworkAccessManager *manager = new QNetworkAccessManager(this); + connect(manager, SIGNAL(finished(QNetworkReply*)), SLOT(onTimeSyncReply(QNetworkReply*))); + manager->head(QNetworkRequest(QUrl(TIME_URL))); +#endif +} + void ImageWriter::onTimeSyncReply(QNetworkReply *reply) { #ifdef Q_OS_LINUX @@ -508,7 +518,8 @@ void ImageWriter::onTimeSyncReply(QNetworkReply *reply) } else { - // TODO: try again later? + qDebug() << "Error synchronizing time. Trying again in 3 seconds"; + QTimer::singleShot(3000, this, SLOT(syncTime())); } reply->deleteLater(); @@ -520,6 +531,76 @@ bool ImageWriter::isEmbeddedMode() return _embeddedMode; } +/* Mount any USB sticks that can contain source images under /media */ +bool ImageWriter::mountUsbSourceMedia() +{ + int devices = 0; +#ifdef Q_OS_LINUX + QDir dir("/sys/class/block"); + QStringList list = dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot); + + if (!dir.exists("/media")) + dir.mkdir("/media"); + + for (auto devname : list) + { + if (!devname.startsWith("mmcblk0") && !QFile::symLinkTarget("/sys/class/block/"+devname).contains("/devices/virtual/")) + { + QString mntdir = "/media/"+devname; + + if (dir.exists(mntdir)) + { + devices++; + continue; + } + + dir.mkdir(mntdir); + QStringList args = { "-o", "ro", QString("/dev/")+devname, mntdir }; + + if ( QProcess::execute("mount", args) == 0 ) + devices++; + else + dir.rmdir(mntdir); + } + } +#endif + return devices > 0; +} + +QByteArray ImageWriter::getUsbSourceOSlist() +{ +#ifdef Q_OS_LINUX + QJsonArray oslist; + QDir dir("/media"); + QStringList medialist = dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot); + QStringList namefilters = {"*.img", "*.zip", "*.gz", "*.xz"}; + + for (auto devname : medialist) + { + QDir subdir("/media/"+devname); + QStringList files = subdir.entryList(namefilters, QDir::Files, QDir::Name); + for (auto file : files) + { + QString path = "/media/"+devname+"/"+file; + QFileInfo fi(path); + + QJsonObject f = { + {"name", file}, + {"description", devname+"/"+file}, + {"url", QUrl::fromLocalFile(path).toString() }, + {"release_date", ""}, + {"image_download_size", fi.size()} + }; + oslist.append(f); + } + } + + return QJsonDocument(oslist).toJson(); +#else + return QByteArray(); +#endif +} + void MountUtilsLog(std::string msg) { qDebug() << "mountutils:" << msg.c_str(); } diff --git a/imagewriter.h b/imagewriter.h index 2d23a30..a6edfa8 100644 --- a/imagewriter.h +++ b/imagewriter.h @@ -75,8 +75,16 @@ public: /* Returns true if online */ Q_INVOKABLE bool isOnline(); + /* Returns true if run on embedded Linux platform */ Q_INVOKABLE bool isEmbeddedMode(); + /* Mount any USB sticks that can contain source images under /media + Returns true if at least one device was mounted */ + Q_INVOKABLE bool mountUsbSourceMedia(); + + /* Returns a json formatted list of the OS images found on USB stick */ + Q_INVOKABLE QByteArray getUsbSourceOSlist(); + signals: /* We are emiting signals with QVariant as parameters because QML likes it that way */ @@ -93,6 +101,7 @@ protected slots: void pollProgress(); void pollNetwork(); + void syncTime(); void onSuccess(); void onError(QString msg); void onFileSelected(QString filename); diff --git a/linux/linuxdrivelist.cpp b/linux/linuxdrivelist.cpp index 77ab43d..c375ac4 100644 --- a/linux/linuxdrivelist.cpp +++ b/linux/linuxdrivelist.cpp @@ -48,7 +48,8 @@ namespace Drivelist std::vector deviceList; QProcess p; - p.start("lsblk --bytes --json --paths --output-all"); + QStringList args = { "--bytes", "--json", "--paths", "--output-all" }; + p.start("lsblk", args); p.waitForFinished(2000); QByteArray output = p.readAll(); diff --git a/main.cpp b/main.cpp index 0e38780..0af33c6 100644 --- a/main.cpp +++ b/main.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #ifndef QT_NO_WIDGETS #include #endif @@ -37,6 +38,15 @@ int main(int argc, char *argv[]) QCoreApplication::setAttribute(Qt::AA_UseOpenGLES); #endif #ifdef QT_NO_WIDGETS + { + QGuiApplication tmp(argc, argv); + int h = QGuiApplication::primaryScreen()->geometry().height(); + if (h > 720) + { + qputenv("QT_SCALE_FACTOR", QByteArray::number(h / 720.0, 'f', 2)); + } + } + QGuiApplication app(argc, argv); #else QApplication app(argc, argv); diff --git a/main.qml b/main.qml index bb4abf5..d49db15 100644 --- a/main.qml +++ b/main.qml @@ -13,12 +13,13 @@ import Qt.labs.settings 1.0 ApplicationWindow { id: window visible: true - width: 680 - height: 420 - minimumWidth: 680 - maximumWidth: 680 - minimumHeight: 420 - maximumHeight: 420 + + width: imageWriter.isEmbeddedMode() ? -1 : 680 + height: imageWriter.isEmbeddedMode() ? -1 : 420 + minimumWidth: imageWriter.isEmbeddedMode() ? -1 : 680 + maximumWidth: imageWriter.isEmbeddedMode() ? -1 : 680 + minimumHeight: imageWriter.isEmbeddedMode() ? -1 : 420 + maximumHeight: imageWriter.isEmbeddedMode() ? -1 : 420 title: qsTr("Raspberry Pi Imager v%1").arg(imageWriter.constantVersion()) @@ -50,13 +51,18 @@ ApplicationWindow { id: bg spacing: 0 - Image { - id: image - Layout.fillWidth: true - Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter - Layout.preferredWidth: window.width - fillMode: Image.PreserveAspectFit - source: "icons/rpi2.png" + Rectangle { + implicitHeight: window.height/2 + + Image { + id: image + Layout.fillWidth: true + Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter + fillMode: Image.PreserveAspectFit + source: imageWriter.isEmbeddedMode() ? "icons/rpi2-hires.png" : "icons/rpi2.png" + width: window.width + height: window.height/2 + } } Rectangle { @@ -347,7 +353,7 @@ ApplicationWindow { ListElement { url: "internal://format" - icon: "icons/ic_delete_40px.png" + icon: "icons/ic_delete_40px.svg" extract_size: 0 image_download_size: 0 extract_sha256: "" @@ -362,7 +368,7 @@ ApplicationWindow { ListElement { url: "" - icon: "icons/ic_computer_40px.png" + icon: "icons/ic_computer_40px.svg" name: qsTr("Use custom") description: qsTr("Select a custom .img from your computer") } @@ -379,7 +385,7 @@ ApplicationWindow { ListElement { url: "" - icon: "icons/ic_chevron_left_40px.png" + icon: "icons/ic_chevron_left_40px.svg" extract_size: 0 image_download_size: 0 extract_sha256: "" @@ -421,7 +427,7 @@ ApplicationWindow { width: 64 Image { - source: icon == "icons/ic_build_48px.svg" ? "icons/ic_build_40px.png": icon + source: icon == "icons/ic_build_48px.svg" ? "icons/ic_build_40px.svg": icon verticalAlignment: Image.AlignVCenter height: parent.parent.parent.height fillMode: Image.Pad @@ -447,6 +453,8 @@ ApplicationWindow { if (typeof(url) == "string" && url != "" && url != "internal://format") { if (typeof(extract_sha256) != "undefined" && imageWriter.isCached(url,extract_sha256)) { txt += "
"+qsTr("Cached on your computer") + } else if (url.startsWith("file://")) { + txt += "
"+qsTr("Local file") } else { txt += "
"+qsTr("Online - %1 GB download").arg((image_download_size/1073741824).toFixed(1)); } @@ -467,7 +475,7 @@ ApplicationWindow { } Column { Image { - source: "icons/ic_chevron_right_40px.png" + source: "icons/ic_chevron_right_40px.svg" visible: (typeof(subitems) == "object" && subitems.count) || (typeof(subitems_url) == "string" && subitems_url != "" && subitems_url != "internal://back") height: parent.parent.parent.height fillMode: Image.Pad @@ -531,8 +539,22 @@ ApplicationWindow { imageWriter.openFileDialog() } else { - // FIXME: provide QML file dialog - onError("Using custom images is not implemented on this platform yet.") + if (imageWriter.mountUsbSourceMedia()) { + if (subosmodel.count>1) + { + subosmodel.remove(1, subosmodel.count-1) + } + + var oslist = JSON.parse(imageWriter.getUsbSourceOSlist()) + for (var i in oslist) { + subosmodel.append(oslist[i]) + } + osswipeview.setCurrentIndex(1) + } + else + { + onError(qsTr("Connect an USB stick containing images first.
The images must be located in the root folder of the USB stick.")) + } } } else { imageWriter.setSrc(url, image_download_size, extract_size, typeof(extract_sha256) != "undefined" ? extract_sha256 : "", typeof(contains_multiple_files) != "undefined" ? contains_multiple_files : false) @@ -547,7 +569,6 @@ ApplicationWindow { } } - /* Popup for SD card device selection */ @@ -658,7 +679,7 @@ ApplicationWindow { width: 64 Image { - source: isUsb ? "icons/ic_usb_40px.png" : isScsi ? "icons/ic_storage_40px.png" : "icons/ic_sd_storage_40px.png" + source: isUsb ? "icons/ic_usb_40px.svg" : isScsi ? "icons/ic_storage_40px.svg" : "icons/ic_sd_storage_40px.svg" verticalAlignment: Image.AlignVCenter height: parent.parent.parent.height fillMode: Image.Pad diff --git a/qml.qrc b/qml.qrc index 4c80f1a..0648227 100644 --- a/qml.qrc +++ b/qml.qrc @@ -7,14 +7,14 @@ fonts/Roboto-Bold.ttf fonts/Roboto-Light.ttf fonts/Roboto-Regular.ttf - icons/ic_chevron_right_40px.png - icons/ic_chevron_left_40px.png - icons/ic_computer_40px.png - icons/ic_delete_40px.png - icons/ic_usb_40px.png - icons/ic_storage_40px.png - icons/ic_sd_storage_40px.png - icons/ic_build_40px.png - icons/ic_close_18px.png + icons/rpi2-hires.png + icons/ic_build_40px.svg + icons/ic_chevron_left_40px.svg + icons/ic_chevron_right_40px.svg + icons/ic_computer_40px.svg + icons/ic_delete_40px.svg + icons/ic_sd_storage_40px.svg + icons/ic_storage_40px.svg + icons/ic_usb_40px.svg