2020-03-04 16:55:40 +01:00
|
|
|
/*
|
|
|
|
* SPDX-License-Identifier: Apache-2.0
|
2022-02-03 11:48:21 +01:00
|
|
|
* Copyright (C) 2020 Raspberry Pi Ltd
|
2020-03-04 16:55:40 +01:00
|
|
|
*/
|
|
|
|
|
|
|
|
#include <QFileInfo>
|
|
|
|
#include <QGuiApplication>
|
|
|
|
#include <QQmlApplicationEngine>
|
|
|
|
#include <QQmlContext>
|
|
|
|
#include <QIcon>
|
|
|
|
#include <QDebug>
|
|
|
|
#include <QTextStream>
|
|
|
|
#include "imagewriter.h"
|
|
|
|
#include "drivelistmodel.h"
|
|
|
|
#include "networkaccessmanagerfactory.h"
|
2021-05-06 01:47:34 +02:00
|
|
|
#include "cli.h"
|
2020-03-04 16:55:40 +01:00
|
|
|
#include <QMessageLogContext>
|
|
|
|
#include <QQuickWindow>
|
2020-05-23 15:57:58 +02:00
|
|
|
#include <QTranslator>
|
|
|
|
#include <QLocale>
|
2020-06-01 17:45:41 +02:00
|
|
|
#include <QScreen>
|
2020-07-21 16:10:43 +02:00
|
|
|
#include <QSettings>
|
2021-11-22 00:21:30 +01:00
|
|
|
#include <QFont>
|
|
|
|
#include <QFontDatabase>
|
2023-10-16 22:15:55 +02:00
|
|
|
#ifdef QT_NO_WIDGETS
|
|
|
|
#include <xf86drm.h>
|
|
|
|
#include <xf86drmMode.h>
|
|
|
|
#include <QDir>
|
|
|
|
#endif
|
2020-05-25 00:36:16 +02:00
|
|
|
#ifndef QT_NO_WIDGETS
|
|
|
|
#include <QtWidgets/QApplication>
|
|
|
|
#endif
|
2021-03-26 16:17:03 +01:00
|
|
|
#ifdef Q_OS_DARWIN
|
|
|
|
#include <CoreFoundation/CoreFoundation.h>
|
|
|
|
#endif
|
2020-03-04 16:55:40 +01:00
|
|
|
|
|
|
|
static QTextStream cerr(stderr);
|
|
|
|
|
2021-12-09 12:22:14 +01:00
|
|
|
/* Newer Qt versions throw warnings if using ::endl instead of Qt::endl
|
|
|
|
Older versions lack Qt::endl */
|
|
|
|
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
|
|
|
|
//using Qt::endl;
|
|
|
|
#define endl Qt::endl
|
|
|
|
#endif
|
|
|
|
|
2020-03-04 16:55:40 +01:00
|
|
|
#ifdef Q_OS_WIN
|
|
|
|
static void consoleMsgHandler(QtMsgType, const QMessageLogContext &, const QString &str) {
|
|
|
|
cerr << str << endl;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2023-10-16 22:15:55 +02:00
|
|
|
#ifdef QT_NO_WIDGETS
|
|
|
|
/* Embedded: deal with Pi having multiple DRI devices when vc4-kms (instead of fkms) is used */
|
|
|
|
bool handleDri()
|
|
|
|
{
|
|
|
|
QByteArray driDev;
|
|
|
|
QDir driDir("/dev/dri");
|
|
|
|
QStringList entries = driDir.entryList(QDir::System, QDir::Name);
|
|
|
|
|
|
|
|
for (const QString &fn : entries)
|
|
|
|
{
|
|
|
|
QFile f("/dev/dri/"+fn);
|
|
|
|
if (f.open(f.ReadWrite))
|
|
|
|
{
|
|
|
|
drmModeResPtr resources = drmModeGetResources(f.handle());
|
|
|
|
if (resources)
|
|
|
|
{
|
|
|
|
driDev = "/dev/dri/"+fn.toLatin1();
|
|
|
|
cerr << "Using " << driDev << endl;
|
|
|
|
|
|
|
|
/* Get current resolution to calculate scaling factor while we are at it */
|
|
|
|
for (int i=0; i<resources->count_connectors; i++)
|
|
|
|
{
|
|
|
|
drmModeConnectorPtr connector = drmModeGetConnector(f.handle(), resources->connectors[i]);
|
|
|
|
if (connector)
|
|
|
|
{
|
|
|
|
if (connector->connection != DRM_MODE_DISCONNECTED)
|
|
|
|
{
|
|
|
|
drmModeEncoderPtr encoder = drmModeGetEncoder(f.handle(), connector->encoder_id);
|
|
|
|
if (encoder)
|
|
|
|
{
|
|
|
|
drmModeModeInfo crtcMode = {0};
|
|
|
|
drmModeCrtcPtr crtc = drmModeGetCrtc(f.handle(), encoder->crtc_id);
|
|
|
|
if (crtc)
|
|
|
|
{
|
|
|
|
if (crtc->mode_valid)
|
|
|
|
{
|
|
|
|
cerr << "Current mode: connector " << i << " crtc_id " << crtc->crtc_id << " width: " << crtc->width << "px height: " << crtc->height << "px" << endl;
|
|
|
|
/*if (crtc->height > 720)
|
|
|
|
{
|
|
|
|
qputenv("QT_SCALE_FACTOR", QByteArray::number(crtc->height / 720.0, 'f', 2));
|
|
|
|
}*/
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
drmModeFreeCrtc(crtc);
|
|
|
|
}
|
|
|
|
drmModeFreeEncoder(encoder);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
drmModeFreeConnector(connector);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
drmModeFreeResources(resources);
|
|
|
|
}
|
|
|
|
f.close();
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
cerr << "Error opening /dev/dri/"+fn << endl;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (driDev.isEmpty())
|
|
|
|
{
|
|
|
|
cerr << "No capable /dev/dri device found" << endl;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
QFile f("/tmp/qt-kms-config.json");
|
|
|
|
f.open(f.WriteOnly);
|
|
|
|
f.write("{ \"device\": \""+driDev+"\", \"hwcursor\": false }\n");
|
|
|
|
f.close();
|
|
|
|
qputenv("QT_QPA_EGLFS_KMS_CONFIG", "/tmp/qt-kms-config.json");
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2020-03-04 16:55:40 +01:00
|
|
|
int main(int argc, char *argv[])
|
|
|
|
{
|
2021-05-06 01:47:34 +02:00
|
|
|
for (int i = 1; i < argc; i++)
|
|
|
|
{
|
|
|
|
if (strcmp(argv[i], "--cli") == 0)
|
|
|
|
{
|
|
|
|
/* CLI mode */
|
|
|
|
Cli cli(argc, argv);
|
|
|
|
return cli.main();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-25 00:36:16 +02:00
|
|
|
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
|
2023-11-02 15:54:12 +00:00
|
|
|
|
|
|
|
/** QtQuick on QT5 exhibits spurious disk cache failures that cannot be
|
|
|
|
* resolved by a user in a trivial manner (they have to delete the cache manually).
|
|
|
|
*
|
|
|
|
* This flag can potentially noticeably increase the start time of the application, however
|
|
|
|
* between this and a hard-to-detect spurious failure affecting Linux, macOS and Windows,
|
|
|
|
* this trade is the one most likely to result in a good experience for the widest group
|
|
|
|
* of users.
|
|
|
|
*/
|
|
|
|
qputenv("QML_DISABLE_DISK_CACHE", "true");
|
2020-03-04 16:55:40 +01:00
|
|
|
#ifdef Q_OS_WIN
|
|
|
|
// prefer ANGLE (DirectX) over desktop OpenGL
|
2020-05-25 00:36:16 +02:00
|
|
|
QCoreApplication::setAttribute(Qt::AA_UseOpenGLES);
|
2020-03-04 16:55:40 +01:00
|
|
|
#endif
|
2020-05-25 00:36:16 +02:00
|
|
|
#ifdef QT_NO_WIDGETS
|
2023-10-16 22:15:55 +02:00
|
|
|
if ( !handleDri() )
|
|
|
|
return 1;
|
2021-03-06 11:39:58 +01:00
|
|
|
|
2020-05-25 00:36:16 +02:00
|
|
|
QGuiApplication app(argc, argv);
|
2021-11-22 00:21:30 +01:00
|
|
|
|
|
|
|
/* Set default font */
|
|
|
|
QStringList fontList = QFontDatabase::applicationFontFamilies(QFontDatabase::addApplicationFont(":/fonts/Roboto-Regular.ttf"));
|
|
|
|
QGuiApplication::setFont(QFont(fontList.first(), 10));
|
2022-03-24 20:04:24 +01:00
|
|
|
if (QFile::exists("/usr/share/fonts/truetype/droid/DroidSansFallback.ttf"))
|
|
|
|
QFontDatabase::addApplicationFont("/usr/share/fonts/truetype/droid/DroidSansFallback.ttf");
|
2021-11-22 00:21:30 +01:00
|
|
|
|
|
|
|
QLocale::Language l = QLocale::system().language();
|
|
|
|
if (l == QLocale::AnyLanguage || l == QLocale::C)
|
|
|
|
QLocale::setDefault(QLocale("en"));
|
2020-05-25 00:36:16 +02:00
|
|
|
#else
|
2020-03-04 16:55:40 +01:00
|
|
|
QApplication app(argc, argv);
|
2020-05-25 00:36:16 +02:00
|
|
|
#endif
|
2020-03-04 16:55:40 +01:00
|
|
|
app.setOrganizationName("Raspberry Pi");
|
|
|
|
app.setOrganizationDomain("raspberrypi.org");
|
|
|
|
app.setApplicationName("Imager");
|
2020-03-05 15:40:38 +01:00
|
|
|
app.setWindowIcon(QIcon(":/icons/rpi-imager.ico"));
|
2020-03-04 16:55:40 +01:00
|
|
|
ImageWriter imageWriter;
|
|
|
|
NetworkAccessManagerFactory namf;
|
|
|
|
QQmlApplicationEngine engine;
|
2020-11-19 20:23:09 +01:00
|
|
|
QString customQm;
|
2020-12-07 13:57:21 +01:00
|
|
|
QSettings settings;
|
2020-03-04 16:55:40 +01:00
|
|
|
|
|
|
|
/* Parse commandline arguments (if any) */
|
|
|
|
QString customRepo;
|
|
|
|
QUrl url;
|
|
|
|
QStringList args = app.arguments();
|
|
|
|
for (int i=1; i < args.size(); i++)
|
|
|
|
{
|
|
|
|
if (!args[i].startsWith("-") && url.isEmpty())
|
|
|
|
{
|
|
|
|
if (args[i].startsWith("http:", Qt::CaseInsensitive) || args[i].startsWith("https:", Qt::CaseInsensitive))
|
|
|
|
{
|
|
|
|
url = args[i];
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
QFileInfo fi(args[i]);
|
|
|
|
|
|
|
|
if (fi.isFile())
|
|
|
|
{
|
|
|
|
url = QUrl::fromLocalFile(args[i]);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
cerr << "Argument ignored because it is not a regular file: " << args[i] << endl;;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (args[i] == "--repo")
|
|
|
|
{
|
|
|
|
if (args.size()-i < 2 || args[i+1].startsWith("-"))
|
|
|
|
{
|
|
|
|
cerr << "Missing URL after --repo" << endl;
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
customRepo = args[++i];
|
|
|
|
if (customRepo.startsWith("http://") || customRepo.startsWith("https://"))
|
|
|
|
{
|
|
|
|
imageWriter.setCustomOsListUrl(customRepo);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
QFileInfo fi(customRepo);
|
|
|
|
if (!fi.isFile())
|
|
|
|
{
|
|
|
|
cerr << "Custom repository file does not exist or is not a regular file: " << customRepo << endl;
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
imageWriter.setCustomOsListUrl(QUrl::fromLocalFile(customRepo));
|
|
|
|
}
|
|
|
|
}
|
2020-11-19 20:23:09 +01:00
|
|
|
else if (args[i] == "--qm")
|
|
|
|
{
|
|
|
|
if (args.size()-i < 2 || args[i+1].startsWith("-"))
|
|
|
|
{
|
|
|
|
cerr << "Missing QM file after --qm" << endl;
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
customQm = args[++i];
|
|
|
|
|
|
|
|
QFileInfo fi(customQm);
|
|
|
|
if (!fi.isFile())
|
|
|
|
{
|
|
|
|
cerr << "Custom QM file does not exist or is not a regular file: " << customQm << endl;
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
}
|
2020-03-04 16:55:40 +01:00
|
|
|
else if (args[i] == "--debug")
|
|
|
|
{
|
|
|
|
#ifdef Q_OS_WIN
|
|
|
|
/* Allocate console for debug messages on Windows */
|
|
|
|
if (::AttachConsole(ATTACH_PARENT_PROCESS) || ::AllocConsole())
|
|
|
|
{
|
|
|
|
freopen("CONOUT$", "w", stdout);
|
|
|
|
freopen("CONOUT$", "w", stderr);
|
|
|
|
std::ios::sync_with_stdio();
|
|
|
|
qInstallMessageHandler(consoleMsgHandler);
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
else if (args[i] == "--help")
|
|
|
|
{
|
2020-12-07 13:57:21 +01:00
|
|
|
cerr << args[0] << " [--debug] [--version] [--repo <repository URL>] [--qm <custom qm translation file>] [--disable-telemetry] [<image file to write>]" << endl;
|
2021-05-06 01:47:34 +02:00
|
|
|
cerr << "-OR- " << args[0] << " --cli [--disable-verify] [--sha256 <expected hash>] [--debug] [--quiet] <image file to write> <destination drive device>" << endl;
|
2020-03-10 17:43:48 +01:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
else if (args[i] == "--version")
|
|
|
|
{
|
|
|
|
cerr << args[0] << " version " << imageWriter.constantVersion() << endl;
|
|
|
|
cerr << "Repository: " << imageWriter.constantOsListUrl().toString() << endl;
|
2020-03-04 16:55:40 +01:00
|
|
|
return 0;
|
|
|
|
}
|
2020-12-07 13:57:21 +01:00
|
|
|
else if (args[i] == "--disable-telemetry")
|
|
|
|
{
|
|
|
|
cerr << "Disabled telemetry" << endl;
|
|
|
|
settings.setValue("telemetry", false);
|
|
|
|
settings.sync();
|
|
|
|
}
|
|
|
|
else if (args[i] == "--enable-telemetry")
|
|
|
|
{
|
|
|
|
cerr << "Using default telemetry setting" << endl;
|
|
|
|
settings.remove("telemetry");
|
|
|
|
settings.sync();
|
|
|
|
}
|
2020-03-04 16:55:40 +01:00
|
|
|
else
|
|
|
|
{
|
|
|
|
cerr << "Ignoring unknown argument: " << args[i] << endl;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-07-26 23:13:52 +02:00
|
|
|
QTranslator *translator = new QTranslator;
|
2020-11-19 20:23:09 +01:00
|
|
|
if (customQm.isEmpty())
|
|
|
|
{
|
2021-03-26 16:17:03 +01:00
|
|
|
#ifdef Q_OS_DARWIN
|
|
|
|
QString langcode = "en_GB";
|
|
|
|
CFArrayRef prefLangs = CFLocaleCopyPreferredLanguages();
|
|
|
|
if (CFArrayGetCount(prefLangs))
|
|
|
|
{
|
|
|
|
char buf[32] = {0};
|
|
|
|
CFStringRef strRef = (CFStringRef) CFArrayGetValueAtIndex(prefLangs, 0);
|
|
|
|
CFStringGetCString(strRef, buf, sizeof(buf), kCFStringEncodingUTF8);
|
|
|
|
langcode = buf;
|
|
|
|
langcode.replace('-', '_');
|
|
|
|
qDebug() << "OSX most preferred language:" << langcode;
|
|
|
|
}
|
|
|
|
|
|
|
|
CFRelease(prefLangs);
|
|
|
|
QLocale::setDefault(QLocale(langcode));
|
|
|
|
#endif
|
|
|
|
|
2021-11-22 00:21:30 +01:00
|
|
|
if (translator->load(QLocale(), "rpi-imager", "_", QLatin1String(":/i18n")))
|
|
|
|
imageWriter.replaceTranslator(translator);
|
|
|
|
else
|
|
|
|
delete translator;
|
2020-11-19 20:23:09 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2021-11-22 00:21:30 +01:00
|
|
|
if (translator->load(customQm))
|
|
|
|
imageWriter.replaceTranslator(translator);
|
|
|
|
else
|
|
|
|
delete translator;
|
2020-11-19 20:23:09 +01:00
|
|
|
}
|
|
|
|
|
2020-03-04 16:55:40 +01:00
|
|
|
if (!url.isEmpty())
|
|
|
|
imageWriter.setSrc(url);
|
|
|
|
imageWriter.setEngine(&engine);
|
|
|
|
engine.setNetworkAccessManagerFactory(&namf);
|
|
|
|
engine.rootContext()->setContextProperty("imageWriter", &imageWriter);
|
|
|
|
engine.rootContext()->setContextProperty("driveListModel", imageWriter.getDriveList());
|
|
|
|
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
|
|
|
|
|
|
|
|
if (engine.rootObjects().isEmpty())
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
QObject *qmlwindow = engine.rootObjects().value(0);
|
|
|
|
qmlwindow->connect(&imageWriter, SIGNAL(downloadProgress(QVariant,QVariant)), qmlwindow, SLOT(onDownloadProgress(QVariant,QVariant)));
|
|
|
|
qmlwindow->connect(&imageWriter, SIGNAL(verifyProgress(QVariant,QVariant)), qmlwindow, SLOT(onVerifyProgress(QVariant,QVariant)));
|
2020-11-19 18:10:05 +01:00
|
|
|
qmlwindow->connect(&imageWriter, SIGNAL(preparationStatusUpdate(QVariant)), qmlwindow, SLOT(onPreparationStatusUpdate(QVariant)));
|
2020-03-04 16:55:40 +01:00
|
|
|
qmlwindow->connect(&imageWriter, SIGNAL(error(QVariant)), qmlwindow, SLOT(onError(QVariant)));
|
|
|
|
qmlwindow->connect(&imageWriter, SIGNAL(success()), qmlwindow, SLOT(onSuccess()));
|
|
|
|
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()));
|
2020-05-25 00:36:16 +02:00
|
|
|
qmlwindow->connect(&imageWriter, SIGNAL(networkOnline()), qmlwindow, SLOT(fetchOSlist()));
|
2023-11-09 14:38:34 +00:00
|
|
|
qmlwindow->connect(&imageWriter, SIGNAL(osListPrepared()), qmlwindow, SLOT(onOsListPrepared()));
|
2024-01-15 00:16:43 +01:00
|
|
|
qmlwindow->connect(&imageWriter, SIGNAL(networkInfo(QVariant)), qmlwindow, SLOT(onNetworkInfo(QVariant)));
|
2020-03-04 16:55:40 +01:00
|
|
|
|
2020-07-21 16:10:43 +02:00
|
|
|
#ifndef QT_NO_WIDGETS
|
|
|
|
/* Set window position */
|
|
|
|
auto screensize = app.primaryScreen()->geometry();
|
2020-12-07 13:57:21 +01:00
|
|
|
int x = settings.value("x", -1).toInt();
|
|
|
|
int y = settings.value("y", -1).toInt();
|
2020-07-21 16:10:43 +02:00
|
|
|
int w = qmlwindow->property("width").toInt();
|
|
|
|
int h = qmlwindow->property("height").toInt();
|
|
|
|
|
|
|
|
if (x != -1 && y != -1)
|
|
|
|
{
|
2021-05-04 22:53:12 +02:00
|
|
|
if ( !app.screenAt(QPoint(x,y)) || !app.screenAt(QPoint(x+w,y+h)) )
|
2020-07-21 16:10:43 +02:00
|
|
|
{
|
2021-05-04 22:53:12 +02:00
|
|
|
qDebug() << "Not restoring saved window position as it falls outside any currently attached screen";
|
2020-07-21 16:10:43 +02:00
|
|
|
x = y = -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (x == -1 || y == -1)
|
|
|
|
{
|
|
|
|
x = qMax(1, (screensize.width()-w)/2);
|
|
|
|
y = qMax(1, (screensize.height()-h)/2);
|
|
|
|
}
|
|
|
|
|
|
|
|
qmlwindow->setProperty("x", x);
|
|
|
|
qmlwindow->setProperty("y", y);
|
|
|
|
#endif
|
|
|
|
|
2024-01-15 00:16:43 +01:00
|
|
|
if (imageWriter.isOnline())
|
|
|
|
imageWriter.beginOSListFetch();
|
2023-11-09 14:38:34 +00:00
|
|
|
|
2020-03-04 16:55:40 +01:00
|
|
|
int rc = app.exec();
|
2020-07-21 16:10:43 +02:00
|
|
|
|
|
|
|
#ifndef QT_NO_WIDGETS
|
|
|
|
int newX = qmlwindow->property("x").toInt();
|
|
|
|
int newY = qmlwindow->property("y").toInt();
|
|
|
|
if (x != newX || y != newY)
|
|
|
|
{
|
2020-12-07 13:57:21 +01:00
|
|
|
settings.setValue("x", newX);
|
|
|
|
settings.setValue("y", newY);
|
2020-07-21 16:10:43 +02:00
|
|
|
settings.sync();
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2020-03-04 16:55:40 +01:00
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|