Linux embedded: misc improvements

- Allow static build
- Use .svg icons
- Linux Embedded lacks normal "open file dialog". So simply scan
  files in root folder of USB stick and return list.
- Change QProcess::execute(command) calls to
  QProcess::execute(command, args) calls to silence Qt 5.15.0
  depreciation warning.
This commit is contained in:
Floris Bos 2020-06-01 17:45:41 +02:00
parent f3bc47a309
commit 123542a66b
26 changed files with 216 additions and 39 deletions

View file

@ -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})

View file

@ -186,7 +186,7 @@ void DriveFormatThread::run()
return;
}
proc.execute("partprobe");
proc.execute("partprobe", QStringList() );
args.clear();
args << fatpartition;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 493 B

6
icons/ic_build_40px.svg Normal file
View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="40pt" height="40pt" viewBox="0 0 40 40" version="1.1">
<g id="surface1">
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 37.832031 31.582031 L 22.582031 16.332031 C 24.082031 12.5 23.25 7.917969 20.167969 4.832031 C 16.832031 1.5 11.832031 0.832031 7.832031 2.75 L 15.082031 10 L 10 15.082031 L 2.75 7.832031 C 0.832031 11.832031 1.5 16.832031 4.832031 20.167969 C 7.917969 23.25 12.5 24.082031 16.332031 22.582031 L 31.582031 37.832031 C 32.25 38.5 33.332031 38.5 33.917969 37.832031 L 37.832031 33.917969 C 38.5 33.332031 38.5 32.25 37.832031 31.582031 Z M 37.832031 31.582031 "/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 761 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 224 B

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="40pt" height="40pt" viewBox="0 0 40 40" version="1.1">
<g id="surface1">
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 25.691406 12.359375 L 23.332031 10 L 13.332031 20 L 23.332031 30 L 25.691406 27.640625 L 18.050781 20 Z M 25.691406 12.359375 "/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 429 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 225 B

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="40pt" height="40pt" viewBox="0 0 40 40" version="1.1">
<g id="surface1">
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 16.667969 10 L 14.308594 12.359375 L 21.949219 20 L 14.308594 27.640625 L 16.667969 30 L 26.667969 20 Z M 16.667969 10 "/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 422 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 202 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 321 B

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="40pt" height="40pt" viewBox="0 0 40 40" version="1.1">
<g id="surface1">
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 33.332031 30 C 35.175781 30 36.648438 28.507812 36.648438 26.667969 L 36.667969 10 C 36.667969 8.160156 35.175781 6.667969 33.332031 6.667969 L 6.667969 6.667969 C 4.824219 6.667969 3.332031 8.160156 3.332031 10 L 3.332031 26.667969 C 3.332031 28.507812 4.824219 30 6.667969 30 L 0 30 L 0 33.332031 L 40 33.332031 L 40 30 Z M 6.667969 10 L 33.332031 10 L 33.332031 26.667969 L 6.667969 26.667969 Z M 6.667969 10 "/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 715 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 278 B

6
icons/ic_delete_40px.svg Normal file
View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="40pt" height="40pt" viewBox="0 0 40 40" version="1.1">
<g id="surface1">
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 10 31.667969 C 10 33.507812 11.492188 35 13.332031 35 L 26.667969 35 C 28.507812 35 30 33.507812 30 31.667969 L 30 11.667969 L 10 11.667969 Z M 31.667969 6.667969 L 25.832031 6.667969 L 24.167969 5 L 15.832031 5 L 14.167969 6.667969 L 8.332031 6.667969 L 8.332031 10 L 31.667969 10 Z M 31.667969 6.667969 "/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 608 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 372 B

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="40pt" height="40pt" viewBox="0 0 40 40" version="1.1">
<g id="surface1">
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 30 3.332031 L 16.667969 3.332031 L 6.699219 13.332031 L 6.667969 33.332031 C 6.667969 35.167969 8.167969 36.667969 10 36.667969 L 30 36.667969 C 31.832031 36.667969 33.332031 35.167969 33.332031 33.332031 L 33.332031 6.667969 C 33.332031 4.832031 31.832031 3.332031 30 3.332031 Z M 20 13.332031 L 16.667969 13.332031 L 16.667969 6.667969 L 20 6.667969 Z M 25 13.332031 L 21.667969 13.332031 L 21.667969 6.667969 L 25 6.667969 Z M 30 13.332031 L 26.667969 13.332031 L 26.667969 6.667969 L 30 6.667969 Z M 30 13.332031 "/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 820 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 235 B

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="40pt" height="40pt" viewBox="0 0 40 40" version="1.1">
<g id="surface1">
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 3.332031 33.332031 L 36.667969 33.332031 L 36.667969 26.667969 L 3.332031 26.667969 Z M 6.667969 28.332031 L 10 28.332031 L 10 31.667969 L 6.667969 31.667969 Z M 3.332031 6.667969 L 3.332031 13.332031 L 36.667969 13.332031 L 36.667969 6.667969 Z M 10 11.667969 L 6.667969 11.667969 L 6.667969 8.332031 L 10 8.332031 Z M 3.332031 23.332031 L 36.667969 23.332031 L 36.667969 16.667969 L 3.332031 16.667969 Z M 6.667969 18.332031 L 10 18.332031 L 10 21.667969 L 6.667969 21.667969 Z M 6.667969 18.332031 "/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 804 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 513 B

6
icons/ic_usb_40px.svg Normal file
View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="40pt" height="40pt" viewBox="0 0 40 40" version="1.1">
<g id="surface1">
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 25 11.667969 L 25 18.332031 L 26.667969 18.332031 L 26.667969 21.667969 L 21.667969 21.667969 L 21.667969 8.332031 L 25 8.332031 L 20 1.667969 L 15 8.332031 L 18.332031 8.332031 L 18.332031 21.667969 L 13.332031 21.667969 L 13.332031 18.214844 C 14.507812 17.609375 15.332031 16.417969 15.332031 15 C 15.332031 12.976562 13.691406 11.332031 11.667969 11.332031 C 9.640625 11.332031 8 12.976562 8 15 C 8 16.417969 8.824219 17.609375 10 18.214844 L 10 21.667969 C 10 23.507812 11.492188 25 13.332031 25 L 18.332031 25 L 18.332031 30.082031 C 17.148438 30.691406 16.332031 31.910156 16.332031 33.332031 C 16.332031 35.359375 17.976562 37 20 37 C 22.023438 37 23.667969 35.359375 23.667969 33.332031 C 23.667969 31.910156 22.851562 30.691406 21.667969 30.082031 L 21.667969 25 L 26.667969 25 C 28.507812 25 30 23.507812 30 21.667969 L 30 18.332031 L 31.667969 18.332031 L 31.667969 11.667969 Z M 25 11.667969 "/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

BIN
icons/rpi2-hires.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

View file

@ -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();
}

View file

@ -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);

View file

@ -48,7 +48,8 @@ namespace Drivelist
std::vector<DeviceDescriptor> 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();

View file

@ -17,6 +17,7 @@
#include <QQuickWindow>
#include <QTranslator>
#include <QLocale>
#include <QScreen>
#ifndef QT_NO_WIDGETS
#include <QtWidgets/QApplication>
#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);

View file

@ -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 += "<br>"+qsTr("Cached on your computer")
} else if (url.startsWith("file://")) {
txt += "<br>"+qsTr("Local file")
} else {
txt += "<br>"+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.<br>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

18
qml.qrc
View file

@ -7,14 +7,14 @@
<file>fonts/Roboto-Bold.ttf</file>
<file>fonts/Roboto-Light.ttf</file>
<file>fonts/Roboto-Regular.ttf</file>
<file>icons/ic_chevron_right_40px.png</file>
<file>icons/ic_chevron_left_40px.png</file>
<file>icons/ic_computer_40px.png</file>
<file>icons/ic_delete_40px.png</file>
<file>icons/ic_usb_40px.png</file>
<file>icons/ic_storage_40px.png</file>
<file>icons/ic_sd_storage_40px.png</file>
<file>icons/ic_build_40px.png</file>
<file>icons/ic_close_18px.png</file>
<file>icons/rpi2-hires.png</file>
<file>icons/ic_build_40px.svg</file>
<file>icons/ic_chevron_left_40px.svg</file>
<file>icons/ic_chevron_right_40px.svg</file>
<file>icons/ic_computer_40px.svg</file>
<file>icons/ic_delete_40px.svg</file>
<file>icons/ic_sd_storage_40px.svg</file>
<file>icons/ic_storage_40px.svg</file>
<file>icons/ic_usb_40px.svg</file>
</qresource>
</RCC>