Build changes

- Add support for embedded Linux without X, dbus, udisks, ntp, etc.
- Misc minor changes
This commit is contained in:
Floris Bos 2020-05-25 00:36:16 +02:00
parent 71eefa47cf
commit 5b072f3196
13 changed files with 189 additions and 222 deletions

View file

@ -20,18 +20,26 @@ set(CMAKE_AUTORCC ON)
# Adding headers explicity so they are displayed in Qt Creator
set(HEADERS config.h imagewriter.h networkaccessmanagerfactory.h nan.h drivelistitem.h drivelistmodel.h driveformatthread.h powersaveblocker.h
downloadthread.h downloadextractthread.h acceleratedcryptographichash.h dependencies/mountutils/src/mountutils.hpp)
downloadthread.h downloadextractthread.h dependencies/mountutils/src/mountutils.hpp)
# Add 3rd-party dependencies
# Add dependencies
if (APPLE)
set_source_files_properties("icons/rpi-imager.icns" PROPERTIES MACOSX_PACKAGE_LOCATION "Resources")
set(DEPENDENCIES mac/macfile.cpp mac/macfile.h mac/Info.plist dependencies/mountutils/src/darwin/functions.cpp
dependencies/drivelist/src/darwin/list.mm dependencies/drivelist/src/darwin/REDiskList.m icons/rpi-imager.icns)
enable_language(OBJC C)
elseif (UNIX)
set(DEPENDENCIES dependencies/mountutils/src/linux/functions.cpp linux/linuxdrivelist.cpp linux/udisks2api.cpp linux/udisks2api.h)
set(DEPENDENCIES acceleratedcryptographichash.cpp dependencies/mountutils/src/linux/functions.cpp linux/linuxdrivelist.cpp)
find_package(Qt5DBus)
if(Qt5DBus_FOUND)
set(DEPENDENCIES ${DEPENDENCIES} linux/udisks2api.cpp linux/udisks2api.h)
set(EXTRALIBS Qt5::DBus)
message("udisks2 support enabled")
else()
message("DBUS not found. Disabling udisks2 support")
endif()
elseif (WIN32)
set(DEPENDENCIES dependencies/mountutils/src/windows/functions.cpp dependencies/drivelist/src/windows/list.cpp
set(DEPENDENCIES acceleratedcryptographichash.cpp dependencies/mountutils/src/windows/functions.cpp dependencies/drivelist/src/windows/list.cpp
windows/winfile.cpp windows/winfile.h
windows/rpi-imager.rc)
set(EXTRALIBS setupapi)
@ -60,10 +68,12 @@ endif()
set(SOURCES "main.cpp" "imagewriter.cpp" "networkaccessmanagerfactory.cpp"
"drivelistitem.cpp" "drivelistmodel.cpp" "downloadthread.cpp" "downloadextractthread.cpp"
"driveformatthread.cpp" "powersaveblocker.cpp" "acceleratedcryptographichash.cpp" "qml.qrc")
"driveformatthread.cpp" "powersaveblocker.cpp" "qml.qrc")
find_package(OpenSSL REQUIRED)
find_package(Qt5 COMPONENTS Core Quick Widgets LinguistTools REQUIRED)
find_package(Qt5 COMPONENTS Core Quick LinguistTools Svg OPTIONAL_COMPONENTS Widgets)
if (Qt5Widgets_FOUND)
set(EXTRALIBS ${EXTRALIBS} Qt5::Widgets)
endif()
#qt5_create_translation(QM_FILES ${CMAKE_SOURCE_DIR} i18n/rpi-imager_nl.ts i18n/rpi-imager_zh_cn.ts)
qt5_add_translation(QM_FILES i18n/rpi-imager_nl.ts i18n/rpi-imager_zh_cn.ts)
@ -82,6 +92,8 @@ if (WIN32)
# Target Windows 7 (needed for drivelist module)
add_definitions(-DWINVER=0x0601 -D_WIN32_WINNT=0x0601)
find_package(OpenSSL REQUIRED)
# Bundled zlib
add_subdirectory(dependencies/zlib-1.2.11)
set(ZLIB_LIBRARY zlibstatic)
@ -195,9 +207,7 @@ elseif(APPLE)
find_library(CoreFoundation CoreFoundation)
find_library(DiskArbitration DiskArbitration)
find_library(Security Security)
#find_package(Qt5 COMPONENTS Svg)
#set(EXTRALIBS ${CoreFoundation} ${DiskArbitration} ${Security} ${Cocoa} Qt5::Svg)
set(EXTRALIBS ${CoreFoundation} ${DiskArbitration} ${Security} ${Cocoa})
set(EXTRALIBS ${EXTRALIBS} ${CoreFoundation} ${DiskArbitration} ${Security} ${Cocoa})
set_target_properties(${PROJECT_NAME} PROPERTIES MACOSX_BUNDLE YES MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/mac/Info.plist)
find_program(MACDEPLOYQT "macdeployqt" PATHS "${Qt5_DIR}/../../../bin")
@ -212,8 +222,7 @@ elseif(APPLE)
else()
find_package(CURL REQUIRED 7.32.0)
find_package(LibArchive REQUIRED 3.2.0)
find_package(Qt5 COMPONENTS DBus)
set(EXTRALIBS Qt5::DBus)
find_package(OpenSSL REQUIRED)
if (NOT CMAKE_CROSSCOMPILING)
find_program(LSBLK "lsblk")
if (NOT LSBLK)
@ -232,4 +241,4 @@ else()
endif()
include_directories(${CURL_INCLUDE_DIR} ${LibArchive_INCLUDE_DIR} ${OPENSSL_INCLUDE_DIR})
target_link_libraries(${PROJECT_NAME} Qt5::Core Qt5::Quick Qt5::Widgets ${CURL_LIBRARIES} ${LibArchive_LIBRARIES} ${OPENSSL_LIBRARIES} ${ATOMIC_LIBRARY} ${EXTRALIBS})
target_link_libraries(${PROJECT_NAME} Qt5::Core Qt5::Quick Qt5::Svg ${CURL_LIBRARIES} ${LibArchive_LIBRARIES} ${OPENSSL_LIBRARIES} ${ATOMIC_LIBRARY} ${EXTRALIBS})

View file

@ -7,6 +7,11 @@
*/
#include <QCryptographicHash>
#ifdef Q_OS_DARWIN
typedef QCryptographicHash AcceleratedCryptographicHash;
#else
#include "openssl/sha.h"
class AcceleratedCryptographicHash
@ -22,4 +27,5 @@ protected:
SHA256_CTX _sha256;
};
#endif
#endif // ACCELERATEDCRYPTOGRAPHICHASH_H

View file

@ -10,6 +10,9 @@
/* Repository URL */
#define OSLIST_URL "https://downloads.raspberrypi.org/os_list_imagingutility.json"
/* Time synchronization URL (only used on eglfs QPA platform, URL must be HTTP) */
#define TIME_URL "http://downloads.raspberrypi.org/os_list_imagingutility.json?time_synchronization"
/* Hash algorithm for verifying (uncompressed image) checksum */
#define OSLIST_HASH_ALGORITHM QCryptographicHash::Sha256

View file

@ -1,36 +0,0 @@
#!/usr/bin/env node
/*
* Copyright 2017 resin.io
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
'use strict';
const mountUtils = require('..');
let argv = process.argv.slice(2);
let disk = argv.shift();
if (!disk) {
console.error(`Usage: node example/eject <disk>`);
process.exit(1);
}
process.env.MOUNTUTILS_DEBUG = true;
console.log('Ejecting', disk, '...');
mountUtils.eject(disk, function(error) {
console.log(error || 'OK');
});

View file

@ -1,36 +0,0 @@
#!/usr/bin/env node
/*
* Copyright 2017 resin.io
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
'use strict';
const mountUtils = require('..');
let argv = process.argv.slice(2);
let disk = argv.shift();
if (!disk) {
console.error(`Usage: node example/unmount <disk>`);
process.exit(1);
}
process.env.MOUNTUTILS_DEBUG = true;
console.log('Unmounting', disk, '...');
mountUtils.unmountDisk(disk, function(error) {
console.log(error || 'OK');
});

View file

@ -1,106 +0,0 @@
/*
* Copyright 2017 resin.io
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
const chai = require('chai');
const mountutils = require('../');
describe('MountUtils', function() {
describe('.unmountDisk()', function() {
it('should be a function', function() {
chai.expect(mountutils.unmountDisk).to.be.a('function');
});
context('missing / wrong arguments', function() {
specify('throws on missing device', function() {
chai.expect(function() {
mountutils.unmountDisk(null, function() {});
}).to.throw(/must be a string/i);
});
specify('throws on missing callback', function() {
chai.expect(function() {
mountutils.unmountDisk('novalue');
}).to.throw(/must be a function/i);
});
});
context('invalid device', function() {
specify('device is a directory', function( done ) {
mountutils.unmountDisk( __dirname, function( error ) {
chai.expect(error.message).to.match(/Unmount failed/);
done();
});
});
specify('device is an empty string', function( done ) {
mountutils.unmountDisk( '', function( error ) {
chai.expect(error.message).to.match(/Unmount failed/);
done();
});
});
});
});
describe('.eject()', function() {
it('should be a function', function() {
chai.expect(mountutils.eject).to.be.a('function');
});
context('missing / wrong arguments', function() {
specify('throws on missing device', function() {
chai.expect(function() {
mountutils.eject(null, function() {});
}).to.throw(/must be a string/i);
});
specify('throws on missing callback', function() {
chai.expect(function() {
mountutils.eject('novalue');
}).to.throw(/must be a function/i);
});
});
context('invalid device', function() {
specify('device is a directory', function( done ) {
mountutils.eject( __dirname, function( error ) {
chai.expect(error.message).to.match(/Eject failed/);
done();
});
});
specify('device is an empty string', function( done ) {
mountutils.eject( '', function( error ) {
chai.expect(error.message).to.match(/Eject failed/);
done();
});
});
});
});
});

View file

@ -178,6 +178,7 @@ bool DownloadThread::_openAndPrepareDevice()
if (!_file.open(QIODevice::ReadWrite | QIODevice::Unbuffered))
{
#ifdef Q_OS_LINUX
#ifndef QT_NO_DBUS
/* Opening device directly did not work, ask udisks2 to do it for us,
* if necessary prompting for authorization */
UDisks2Api udisks;
@ -187,11 +188,10 @@ bool DownloadThread::_openAndPrepareDevice()
_file.open(fd, QIODevice::ReadWrite | QIODevice::Unbuffered, QFileDevice::AutoCloseHandle);
}
else
{
#endif
{
emit error(tr("Cannot open storage device '%1'.").arg(QString(_filename)));
return false;
#ifdef Q_OS_LINUX
}
#endif
}

View file

@ -134,6 +134,7 @@ void DriveFormatThread::run()
{
/* Not running as root, try to outsource formatting to udisks2 */
#ifndef QT_NO_DBUS
UDisks2Api udisks2;
if (udisks2.formatDrive(_device))
{
@ -141,8 +142,11 @@ void DriveFormatThread::run()
}
else
{
#endif
emit error(tr("Error formatting (through udisks2)"));
#ifndef QT_NO_DBUS
}
#endif
return;
}

View file

@ -14,11 +14,19 @@
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include <QProcess>
#include <QFileDialog>
#include <QStandardPaths>
#include <QStorageInfo>
#include <QWindow>
#include <QGuiApplication>
#include <QNetworkInterface>
#include <QHostAddress>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QDateTime>
#include <QDebug>
#ifndef QT_NO_WIDGETS
#include <QFileDialog>
#endif
#ifdef Q_OS_WIN
#include <windows.h>
@ -27,10 +35,19 @@
ImageWriter::ImageWriter(QObject *parent)
: QObject(parent), _repo(QUrl(QString(OSLIST_URL))), _dlnow(0), _verifynow(0),
_engine(nullptr), _thread(nullptr), _verifyEnabled(false), _cachingEnabled(false)
_engine(nullptr), _thread(nullptr), _verifyEnabled(false), _cachingEnabled(false),
_embeddedMode(false), _online(false)
{
connect(&_polltimer, SIGNAL(timeout()), SLOT(pollProgress()));
QString platform = QGuiApplication::platformName();
if (platform == "eglfs" || platform == "wayland" || platform == "linuxfb")
{
_embeddedMode = true;
connect(&_networkchecktimer, SIGNAL(timeout()), SLOT(pollNetwork()));
_networkchecktimer.start(100);
}
#ifdef Q_OS_WIN
QProcess *p = new QProcess(this);
p->start("net stop ShellHWDetection");
@ -63,7 +80,7 @@ ImageWriter::ImageWriter(QObject *parent)
}
_settings.beginGroup("caching");
_cachingEnabled = _settings.value("enabled", IMAGEWRITER_ENABLE_CACHE_DEFAULT).toBool();
_cachingEnabled = !_embeddedMode && _settings.value("enabled", IMAGEWRITER_ENABLE_CACHE_DEFAULT).toBool();
_cachedFileHash = _settings.value("lastDownloadSHA256").toByteArray();
_cacheFileName = QStandardPaths::writableLocation(QStandardPaths::CacheLocation)+QDir::separator()+"lastdownload.cache";
if (!_cachedFileHash.isEmpty())
@ -377,6 +394,7 @@ void ImageWriter::onFinalizing()
void ImageWriter::openFileDialog()
{
#ifndef QT_NO_WIDGETS
QFileDialog *fd = new QFileDialog(nullptr, tr("Select image"),
QStandardPaths::writableLocation(QStandardPaths::DownloadLocation),
"Image files (*.img *.zip *.gz *.xz);;All files (*.*)");
@ -394,6 +412,7 @@ void ImageWriter::openFileDialog()
}
fd->show();
#endif
}
void ImageWriter::onFileSelected(QString filename)
@ -441,6 +460,66 @@ void ImageWriter::_parseCompressedFile()
qDebug() << "Parsed .zip file containing" << numFiles << "files, uncompressed size:" << _extrLen;
}
bool ImageWriter::isOnline()
{
return _online || !_embeddedMode;
}
void ImageWriter::pollNetwork()
{
#ifdef Q_OS_LINUX
/* Check if we have an IP-address other than localhost */
QList<QHostAddress> addresses = QNetworkInterface::allAddresses();
foreach (QHostAddress a, addresses)
{
if (!a.isLoopback() && a.scopeId().isEmpty())
{
/* Not a loopback or IPv6 link-local address, so online */
_online = true;
break;
}
}
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)));
}
#endif
}
void ImageWriter::onTimeSyncReply(QNetworkReply *reply)
{
#ifdef Q_OS_LINUX
if (reply->hasRawHeader("Date"))
{
QDateTime dt = QDateTime::fromString(reply->rawHeader("Date"), Qt::RFC2822Date);
qDebug() << "Received current time from server:" << dt;
struct timeval tv = {
(time_t) dt.toSecsSinceEpoch(), 0
};
::settimeofday(&tv, NULL);
emit networkOnline();
}
else
{
// TODO: try again later?
}
reply->deleteLater();
#endif
}
bool ImageWriter::isEmbeddedMode()
{
return _embeddedMode;
}
void MountUtilsLog(std::string msg) {
qDebug() << "mountutils:" << msg.c_str();
}

View file

@ -17,6 +17,7 @@
class QQmlApplicationEngine;
class DownloadThread;
class QNetworkReply;
class ImageWriter : public QObject
{
@ -71,6 +72,11 @@ public:
/* Return filename part of URL set */
Q_INVOKABLE QString srcFileName();
/* Returns true if online */
Q_INVOKABLE bool isOnline();
Q_INVOKABLE bool isEmbeddedMode();
signals:
/* We are emiting signals with QVariant as parameters because QML likes it that way */
@ -81,16 +87,19 @@ signals:
void fileSelected(QVariant filename);
void cancelled();
void finalizing();
void networkOnline();
protected slots:
void pollProgress();
void pollNetwork();
void onSuccess();
void onError(QString msg);
void onFileSelected(QString filename);
void onCancelled();
void onCacheFileUpdated(QByteArray sha256);
void onFinalizing();
void onTimeSyncReply(QNetworkReply *reply);
protected:
QUrl _src, _repo;
@ -99,10 +108,10 @@ protected:
quint64 _downloadLen, _extrLen, _devLen, _dlnow, _verifynow;
DriveListModel _drivelist;
QQmlApplicationEngine *_engine;
QTimer _polltimer;
QTimer _polltimer, _networkchecktimer;
PowerSaveBlocker _powersave;
DownloadThread *_thread;
bool _verifyEnabled, _multipleFilesInZip, _cachingEnabled;
bool _verifyEnabled, _multipleFilesInZip, _cachingEnabled, _embeddedMode, _online;
QSettings _settings;
void _parseCompressedFile();

View file

@ -103,6 +103,12 @@ namespace Drivelist
bdev["vendor"].toString().trimmed(),
bdev["model"].toString().trimmed()
};
if (name == "/dev/mmcblk0")
{
dp.removeAll("");
if (dp.empty())
dp.append(QObject::tr("Internal SD card reader"));
}
QString mp = bdev["mountpoint"].toString();
if (!mp.isEmpty())

View file

@ -13,11 +13,13 @@
#include "imagewriter.h"
#include "drivelistmodel.h"
#include "networkaccessmanagerfactory.h"
#include <QtWidgets/QApplication>
#include <QMessageLogContext>
#include <QQuickWindow>
#include <QTranslator>
#include <QLocale>
#ifndef QT_NO_WIDGETS
#include <QtWidgets/QApplication>
#endif
static QTextStream cerr(stderr);
@ -29,12 +31,16 @@ static void consoleMsgHandler(QtMsgType, const QMessageLogContext &, const QStri
int main(int argc, char *argv[])
{
QApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
#ifdef Q_OS_WIN
// prefer ANGLE (DirectX) over desktop OpenGL
QApplication::setAttribute(Qt::AA_UseOpenGLES);
QCoreApplication::setAttribute(Qt::AA_UseOpenGLES);
#endif
#ifdef QT_NO_WIDGETS
QGuiApplication app(argc, argv);
#else
QApplication app(argc, argv);
#endif
app.setOrganizationName("Raspberry Pi");
app.setOrganizationDomain("raspberrypi.org");
app.setApplicationName("Imager");
@ -146,6 +152,7 @@ int main(int argc, char *argv[])
qmlwindow->connect(&imageWriter, SIGNAL(fileSelected(QVariant)), qmlwindow, SLOT(onFileSelected(QVariant)));
qmlwindow->connect(&imageWriter, SIGNAL(cancelled()), qmlwindow, SLOT(onCancelled()));
qmlwindow->connect(&imageWriter, SIGNAL(finalizing()), qmlwindow, SLOT(onFinalizing()));
qmlwindow->connect(&imageWriter, SIGNAL(networkOnline()), qmlwindow, SLOT(fetchOSlist()));
int rc = app.exec();
return rc;

View file

@ -36,6 +36,16 @@ ApplicationWindow {
}
}
Shortcut {
sequence: StandardKey.Quit
context: Qt.ApplicationShortcut
onActivated: {
if (!progressBar.visible) {
Qt.quit()
}
}
}
ColumnLayout {
id: bg
spacing: 0
@ -358,17 +368,9 @@ ApplicationWindow {
}
Component.onCompleted: {
httpRequest(imageWriter.constantOsListUrl(), function (x) {
var o = JSON.parse(x.responseText)
if (!"os_list" in o) {
onError(qsTr("Error parsing os_list.json"))
return;
if (imageWriter.isOnline()) {
fetchOSlist();
}
var oslist = o["os_list"]
for (var i in oslist) {
osmodel.insert(osmodel.count-2, oslist[i])
}
})
}
}
@ -525,7 +527,13 @@ ApplicationWindow {
osswipeview.setCurrentIndex(1)
}
} else if (url == "") {
if (!imageWriter.isEmbeddedMode()) {
imageWriter.openFileDialog()
}
else {
// FIXME: provide QML file dialog
onError("Using custom images is not implemented on this platform yet.")
}
} else {
imageWriter.setSrc(url, image_download_size, extract_size, typeof(extract_sha256) != "undefined" ? extract_sha256 : "", typeof(contains_multiple_files) != "undefined" ? contains_multiple_files : false)
osbutton.text = name
@ -787,7 +795,7 @@ ApplicationWindow {
Material.foreground: "#ffffff"
Material.background: "#c51a4a"
font.family: roboto.name
visible: false
visible: imageWriter.isEmbeddedMode()
}
Button {
@ -795,7 +803,7 @@ ApplicationWindow {
text: qsTr("CONTINUE")
onClicked: {
msgpopup.close()
quitbutton.visible = false
quitbutton.visible = imageWriter.isEmbeddedMode()
}
Material.foreground: "#ffffff"
Material.background: "#c51a4a"
@ -931,4 +939,18 @@ ApplicationWindow {
function onFinalizing() {
progressText.text = qsTr("Finalizing...")
}
function fetchOSlist() {
httpRequest(imageWriter.constantOsListUrl(), function (x) {
var o = JSON.parse(x.responseText)
if (!"os_list" in o) {
onError(qsTr("Error parsing os_list.json"))
return;
}
var oslist = o["os_list"]
for (var i in oslist) {
osmodel.insert(osmodel.count-2, oslist[i])
}
})
}
}