diff options
71 files changed, 3721 insertions, 314 deletions
diff --git a/src/b2qt-flashing-wizard/.gitignore b/src/b2qt-flashing-wizard/.gitignore new file mode 100644 index 0000000..9f9ec21 --- /dev/null +++ b/src/b2qt-flashing-wizard/.gitignore @@ -0,0 +1,5 @@ +*.o +Makefile +b2qt-wand +qrc_*.cpp +moc_*.cpp diff --git a/src/b2qt-flashing-wizard/B2Qt_QtC_icon@2x.png b/src/b2qt-flashing-wizard/B2Qt_QtC_icon@2x.png Binary files differnew file mode 100644 index 0000000..d407a1f --- /dev/null +++ b/src/b2qt-flashing-wizard/B2Qt_QtC_icon@2x.png diff --git a/src/b2qt-flashing-wizard/README b/src/b2qt-flashing-wizard/README new file mode 100644 index 0000000..a04bc96 --- /dev/null +++ b/src/b2qt-flashing-wizard/README @@ -0,0 +1,24 @@ +If you want to use this tool outside of the SDK installation directory +you have to set SDKDIR environment variable. + +How to get root permission: + +sudo -A /bin/askpass-tool + +Sudo will ask when starting scripts + +sudo -E ./b2qt-flashing-wizard + +-E is needed to keep the X vars + +Variables needed: + +#!/bin/bash +export HOME=/home/rkeller +export XDG_SESSION_COOKIE=0dbc1b47e88f5cfa62c5818b00000237-1381472004.761811-1314725543 +./b2qt-flashing-wizard + +GUI stuff: + +kdesu +gsu diff --git a/src/b2qt-flashing-wizard/actor.cpp b/src/b2qt-flashing-wizard/actor.cpp new file mode 100644 index 0000000..1cd76bf --- /dev/null +++ b/src/b2qt-flashing-wizard/actor.cpp @@ -0,0 +1,29 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc +** All rights reserved. +** For any questions to Digia, please use the contact form at +** http://qt.digia.com/ +** +** This file is part of Qt Enterprise Embedded. +** +** Licensees holding valid Qt Enterprise licenses may use this file in +** accordance with the Qt Enterprise License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. +** +** If you have questions regarding the use of this file, please use +** the contact form at http://qt.digia.com/ +** +****************************************************************************/ + +#include "actor.h" + +Actor::Actor(QObject *parent) + : QObject(parent) +{ +} + +Actor::~Actor() +{ +} diff --git a/src/b2qt-flashing-wizard/actor.h b/src/b2qt-flashing-wizard/actor.h new file mode 100644 index 0000000..f310f0b --- /dev/null +++ b/src/b2qt-flashing-wizard/actor.h @@ -0,0 +1,41 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc +** All rights reserved. +** For any questions to Digia, please use the contact form at +** http://qt.digia.com/ +** +** This file is part of Qt Enterprise Embedded. +** +** Licensees holding valid Qt Enterprise licenses may use this file in +** accordance with the Qt Enterprise License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. +** +** If you have questions regarding the use of this file, please use +** the contact form at http://qt.digia.com/ +** +****************************************************************************/ + +#ifndef ACTOR_H +#define ACTOR_H + +#include <QObject> + +class Actor : public QObject +{ + Q_OBJECT + +public: + Actor(QObject *parent); + virtual ~Actor(); + virtual bool ready(QString &) const = 0; + virtual void start() = 0; + +signals: + void progress(const QString &step); + void finished(); + void details(const QByteArray &); +}; + +#endif // ACTOR_H diff --git a/src/b2qt-flashing-wizard/b2qt-flashing-wizard.pro b/src/b2qt-flashing-wizard/b2qt-flashing-wizard.pro new file mode 100644 index 0000000..05403b9 --- /dev/null +++ b/src/b2qt-flashing-wizard/b2qt-flashing-wizard.pro @@ -0,0 +1,35 @@ +QT += widgets +CONFIG += c++11 + +SOURCES += \ + actor.cpp \ + commit_page.cpp \ + common.cpp \ + device_page.cpp \ + disk_page.cpp \ + elevator.cpp \ + finish_page.cpp \ + introduction_page.cpp \ + main.cpp \ + mainwindow.cpp \ + platform_page.cpp \ + progress_page.cpp \ + scriptwriter.cpp \ + +HEADERS += \ + actor.h \ + commit_page.h \ + common.h \ + device_page.h \ + disk_page.h \ + elevator.h \ + finish_page.h \ + introduction_page.h \ + mainwindow.h \ + platform_page.h \ + progress_page.h \ + scriptwriter.h \ + +RESOURCES += b2qt-flashing-wizard.qrc +INSTALLS += target +target.path = /bin diff --git a/src/b2qt-flashing-wizard/b2qt-flashing-wizard.qrc b/src/b2qt-flashing-wizard/b2qt-flashing-wizard.qrc new file mode 100644 index 0000000..1522ca4 --- /dev/null +++ b/src/b2qt-flashing-wizard/b2qt-flashing-wizard.qrc @@ -0,0 +1,7 @@ +<RCC> + <qresource> + <file>logo.png</file> + <file>background.png</file> + <file>watermark.png</file> + </qresource> +</RCC> diff --git a/src/b2qt-flashing-wizard/background.png b/src/b2qt-flashing-wizard/background.png Binary files differnew file mode 100644 index 0000000..330b301 --- /dev/null +++ b/src/b2qt-flashing-wizard/background.png diff --git a/src/b2qt-flashing-wizard/commit_page.cpp b/src/b2qt-flashing-wizard/commit_page.cpp new file mode 100644 index 0000000..e7a3c16 --- /dev/null +++ b/src/b2qt-flashing-wizard/commit_page.cpp @@ -0,0 +1,157 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc +** All rights reserved. +** For any questions to Digia, please use the contact form at +** http://qt.digia.com/ +** +** This file is part of Qt Enterprise Embedded. +** +** Licensees holding valid Qt Enterprise licenses may use this file in +** accordance with the Qt Enterprise License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. +** +** If you have questions regarding the use of this file, please use +** the contact form at http://qt.digia.com/ +** +****************************************************************************/ + +#include "commit_page.h" +#include "scriptwriter.h" +#include "progress_page.h" +#include "mainwindow.h" +#include <QDebug> +#include <QVBoxLayout> +#include <QLabel> + +extern QString G_platform; +extern QString G_version; +extern QString G_os; +extern QString G_device; +extern QString G_board; +extern QString G_SDKDIR; +extern QString G_mode; +QLabel *createErrorLabel(QWidget *parent); + +CommitPage::CommitPage(QWidget *parent) + : QWizardPage(parent) + , mText(new QLabel(this)) + , mActor(0) + , mError(createErrorLabel(this)) + , mLayout(new QVBoxLayout(this)) + , mComplete(false) +{ + setButtonText(QWizard::CommitButton, "Write"); + setCommitPage(true); + setTitle("Confirm action"); + setSubTitle("Confirm the action to be done"); + mLayout->addWidget(mText); + mLayout->addSpacerItem(new QSpacerItem(40,40,QSizePolicy::Minimum, QSizePolicy::Expanding)); + mLayout->addWidget(mError); + setLayout(mLayout); +} + +CommitPage::~CommitPage() +{ +} + +bool CommitPage::isComplete() const +{ + if (!mComplete) + return false; + + QString error; + bool result = mActor->ready(error); + if (result) + mError->clear(); + else + mError->setText(error); + + return result; +} + +void CommitPage::initializePage() +{ + qDebug() << "platform:" << G_platform << "version:" << G_version << "os:" << G_os + << "device:" << G_device << "board:" << G_board << "mode:" << G_mode; + QString text("Write %1-%2 (%3) to device %4."); + mText->setText(text.arg(G_platform, G_os, G_version, G_device)); + mComplete = true; + + qDebug() << "os:" << G_os << "platform:" << G_platform << "device:" << G_device; + + if (G_platform == "generic-4.2" && G_os == "eAndroid" && G_board == "iMX6") { + ScriptWriter *i = new ScriptWriter(this); + i->setScriptFile(G_SDKDIR + G_version + "/" + G_platform + "-" + G_os + "/images/" + G_board + "/deploy.sh"); + i->setAdditionalArgs(QStringList() << G_device << "--verbose"); + i->setEnvironment("VERBOSE","1"); + mActor = i; + } else if (G_platform == "iMX6" && G_os == "eLinux") { + ScriptWriter *i = new ScriptWriter(this); + i->setScriptFile(G_SDKDIR + G_version + "/" + G_platform + "-" + G_os + "/images/deploy.sh"); + i->setAdditionalArgs(QStringList() << G_device << "--verbose"); + i->setEnvironment("VERBOSE","1"); + mActor = i; + } else if (G_platform == "beaglebone" && G_os == "eLinux") { + ScriptWriter *i = new ScriptWriter(this); + i->setScriptFile(G_SDKDIR + G_version + "/" + G_platform + "-" + G_os + "/images/deploy.sh"); + i->setAdditionalArgs(QStringList() << G_device << "--verbose"); + i->setEnvironment("VERBOSE","1"); + mActor = i; + } else if (G_platform == "generic-4.4" && G_os == "eAndroid" && G_board == "beaglebone") { + ScriptWriter *i = new ScriptWriter(this); + i->setScriptFile(G_SDKDIR + G_version + "/" + G_platform + "-" + G_os + "/images/" + G_board + "/deploy.sh"); + i->setAdditionalArgs(QStringList() << G_device << "--verbose"); + i->setEnvironment("VERBOSE","1"); + mActor = i; + } else if (G_platform == "generic-4.4" && G_os == "eAndroid" && G_board == "nexus7v2") { + ScriptWriter *i = new ScriptWriter(this); + i->setScriptFile(G_SDKDIR + G_version + "/" + G_platform + "-" + G_os + "/images/" + G_board + "/deploy.sh"); + if (G_mode == "fastboot") + i->setAdditionalArgs(QStringList() << "-fastboot"); + mActor = i; + } else if (G_platform == "generic-4.4" && G_os == "eAndroid" && G_board == "nexus7") { + ScriptWriter *i = new ScriptWriter(this); + i->setScriptFile(G_SDKDIR + G_version + "/" + G_platform + "-" + G_os + "/images/" + G_board + "/deploy.sh"); + if (G_mode == "fastboot") + i->setAdditionalArgs(QStringList() << "-fastboot"); + mActor = i; + } else if (G_platform == "generic-4.2" && G_os == "eAndroid" && G_board == "nexus7") { + ScriptWriter *i = new ScriptWriter(this); + i->setScriptFile(G_SDKDIR + G_version + "/" + G_platform + "-" + G_os + "/images/" + G_board + "/deploy.sh"); + if (G_mode == "fastboot") + i->setAdditionalArgs(QStringList() << "-fastboot"); + mActor = i; + } else if (G_platform == "raspberrypi" && G_os == "eLinux") { + ScriptWriter *i = new ScriptWriter(this); + i->setScriptFile(G_SDKDIR + G_version + "/" + G_platform + "-" + G_os + "/images/deploy.sh"); + i->setAdditionalArgs(QStringList() << G_device << "--verbose"); + i->setEnvironment("VERBOSE","1"); + mActor = i; + } else if (G_platform == "imx6qsabresd" && G_os == "eLinux") { + ScriptWriter *i = new ScriptWriter(this); + i->setScriptFile(G_SDKDIR + G_version + "/" + G_platform + "-" + G_os + "/images/deploy.sh"); + i->setAdditionalArgs(QStringList() << G_device << "--verbose"); + i->setEnvironment("VERBOSE","1"); + mActor = i; + } else if (G_platform == "apalis-imx6" && G_os == "eLinux") { + ScriptWriter *i = new ScriptWriter(this); + i->setScriptFile(G_SDKDIR + G_version + "/" + G_platform + "-" + G_os + "/images/deploy.sh"); + i->setAdditionalArgs(QStringList() << G_device << "--verbose"); + i->setEnvironment("VERBOSE","1"); + mActor = i; + } else { + mError->setText("Unsupported platform combination"); + mComplete = false; + } +} + +bool CommitPage::validatePage() +{ + ProgressPage *p = qobject_cast<ProgressPage*>(wizard()->page(MainWindow::Page_Progress)); + Q_ASSERT(p); + + p->setActor(mActor); + return true; +} diff --git a/src/b2qt-flashing-wizard/commit_page.h b/src/b2qt-flashing-wizard/commit_page.h new file mode 100644 index 0000000..e934152 --- /dev/null +++ b/src/b2qt-flashing-wizard/commit_page.h @@ -0,0 +1,47 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc +** All rights reserved. +** For any questions to Digia, please use the contact form at +** http://qt.digia.com/ +** +** This file is part of Qt Enterprise Embedded. +** +** Licensees holding valid Qt Enterprise licenses may use this file in +** accordance with the Qt Enterprise License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. +** +** If you have questions regarding the use of this file, please use +** the contact form at http://qt.digia.com/ +** +****************************************************************************/ + +#ifndef COMMIT_PAGE_H +#define COMMIT_PAGE_H + +#include <QWizardPage> +class QLabel; +class Actor; +class QLabel; +class QVBoxLayout; + +class CommitPage : public QWizardPage +{ + Q_OBJECT +public: + CommitPage(QWidget *parent = 0); + virtual ~CommitPage(); + void initializePage(); + bool validatePage(); + bool isComplete() const; + +private: + QLabel *mText; + Actor *mActor; + QLabel *mError; + QVBoxLayout *mLayout; + bool mComplete; +}; + +#endif // COMMIT_PAGE_H diff --git a/src/b2qt-flashing-wizard/common.cpp b/src/b2qt-flashing-wizard/common.cpp new file mode 100644 index 0000000..bd95a3c --- /dev/null +++ b/src/b2qt-flashing-wizard/common.cpp @@ -0,0 +1,78 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc +** All rights reserved. +** For any questions to Digia, please use the contact form at +** http://qt.digia.com/ +** +** This file is part of Qt Enterprise Embedded. +** +** Licensees holding valid Qt Enterprise licenses may use this file in +** accordance with the Qt Enterprise License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. +** +** If you have questions regarding the use of this file, please use +** the contact form at http://qt.digia.com/ +** +****************************************************************************/ + +#include <QByteArray> +#include <QFile> +#include <QPair> +#include <QDebug> + +QByteArray readAll(const QString &fileName) +{ + QByteArray rc; + QFile file(fileName); + if (!file.open(QFile::ReadOnly)) { + qWarning() << "Could not open file:" << fileName; + return rc; + } + + rc = file.readAll(); + file.close(); + return rc; +} + + +static QList<QPair<QString, QString> > getMounts() +{ + QList<QPair<QString, QString> > rc; + + QByteArray ba = readAll("/proc/mounts"); + foreach (const QString &line, ba.split('\n')) { + if (line.isEmpty()) + continue; + + QStringList token = line.split(' '); + if (token.size() < 2) { + qWarning() << "Error parsing mounts:" << line; + continue; + } + rc.append(qMakePair(token[0], token[1])); + } + return rc; +} + +bool checkForDeviceMounted(const QString &device, QString &mountPoint, QString &partition) +{ + QList<QPair<QString, QString> > mounts = getMounts(); + if (mounts.isEmpty()) { + qWarning() << "No mounts found"; + return false; + } + + foreach (const auto &item, mounts) { + if (item.first.startsWith(device)) { + mountPoint = item.second; + partition = item.first; + return true; + } + } + mountPoint.clear(); + partition.clear(); + return false; +} + diff --git a/src/b2qt-flashing-wizard/common.h b/src/b2qt-flashing-wizard/common.h new file mode 100644 index 0000000..1884532 --- /dev/null +++ b/src/b2qt-flashing-wizard/common.h @@ -0,0 +1,21 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc +** All rights reserved. +** For any questions to Digia, please use the contact form at +** http://qt.digia.com/ +** +** This file is part of Qt Enterprise Embedded. +** +** Licensees holding valid Qt Enterprise licenses may use this file in +** accordance with the Qt Enterprise License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. +** +** If you have questions regarding the use of this file, please use +** the contact form at http://qt.digia.com/ +** +****************************************************************************/ + +QByteArray readAll(const QString &fileName); +bool checkForDeviceMounted(const QString &device, QString &mountPoint, QString &partition); diff --git a/src/b2qt-flashing-wizard/device_page.cpp b/src/b2qt-flashing-wizard/device_page.cpp new file mode 100644 index 0000000..313ccce --- /dev/null +++ b/src/b2qt-flashing-wizard/device_page.cpp @@ -0,0 +1,269 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc +** All rights reserved. +** For any questions to Digia, please use the contact form at +** http://qt.digia.com/ +** +** This file is part of Qt Enterprise Embedded. +** +** Licensees holding valid Qt Enterprise licenses may use this file in +** accordance with the Qt Enterprise License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. +** +** If you have questions regarding the use of this file, please use +** the contact form at http://qt.digia.com/ +** +****************************************************************************/ + +#include "device_page.h" +#include "common.h" +#include "mainwindow.h" // for Page_ enum +#include <QRadioButton> +#include <QLayout> +#include <QDebug> +#include <QDir> +#include <QListWidget> +#include <QLabel> +#include <QProcess> +#include <QTimer> + +extern QString G_SDKDIR; +extern QString G_device; +extern QString G_board; +extern QString G_mode; + +QLabel *createErrorLabel(QWidget *parent); + +DevicePage::DevicePage(QWidget *parent) + : QWizardPage(parent) + , mListWidget(new QListWidget(this)) + , mError(createErrorLabel(this)) + , mLayout(new QVBoxLayout(this)) +{ + setTitle("Device selection"); + setSubTitle("Select a device to be used"); + mLayout->addWidget(mListWidget); + mLayout->addSpacerItem(new QSpacerItem(40,40,QSizePolicy::Minimum, QSizePolicy::Expanding)); + mLayout->addWidget(mError); + setLayout(mLayout); + connect(mListWidget, &QListWidget::itemClicked, this, &DevicePage::itemSelected); + + QTimer *timer = new QTimer(this); + timer->setInterval(1000); + connect(timer, &QTimer::timeout, this, &DevicePage::updateDeviceList); + timer->start(); +} + +DevicePage::~DevicePage() +{ +} + +QList<DevicePage::DeviceInfo> DevicePage::list() const +{ + QList<DeviceInfo> diList; + + QProcess process; + process.start(G_SDKDIR + "/Tools/b2qt/adb", QStringList() << "devices" << "-l"); + process.waitForFinished(); + + QList<QByteArray> lines = process.readAll().split('\n'); + foreach (const QByteArray &ba, lines) { + if (ba.startsWith("List of")) + continue; + if (ba.isEmpty()) + continue; + if (ba.startsWith("192.168.56.101:")) + continue; + QList<QByteArray> token = ba.simplified().split(' '); + + if (token.size() == 6){ + DeviceInfo d; + + if (token[5].startsWith("device:")) { + QString deviceName = token[5].mid(7); + if (deviceName == "grouper") + d.name = "Nexus7"; + else if (deviceName == "flo") + d.name = "Nexus7v2"; + else + d.name = deviceName; + } + d.serial = token[0]; + d.mode = "adb"; + if (token[1] == "device") + d.state = "ready"; + else + d.state = "not ready"; + diList.append(d); + } else if (token.size() == 3) { + DeviceInfo d; + d.serial = token[0]; + d.mode = "adb"; + d.name = "Unknown device"; + if (token[1] == "offline") + d.state = "not ready"; + else { + qDebug() << "Unknown state:" << token[1]; + continue; + } + diList.append(d); + } else { + qDebug() << "Invalid token count:" << token.size() << token; + continue; + } + + } + + process.start(G_SDKDIR + "/Tools/b2qt/fastboot", QStringList("devices") << "-l"); + process.waitForFinished(); + lines = process.readAll().split('\n'); + foreach (const QByteArray &ba, lines) { + if (ba.isEmpty()) + continue; + + QList<QByteArray> token = ba.simplified().split(' '); + + if (token.size() != 3) { + qDebug() << "Invalid token count:" << token; + continue; + } + + + DeviceInfo d; + d.serial = token[0]; + + QProcess p2; + p2.start(G_SDKDIR + "/Tools/b2qt/fastboot", QStringList() << "-s" << d.serial << "getvar" << "product"); + if (!p2.waitForFinished()) { + qDebug() << "Could not get product type"; + d.name = "Unknown"; + } + + QString productString = p2.readAllStandardError().split('\n')[0].simplified(); + if (productString.startsWith("product: ")) { + QString productName = productString.mid(9); + if (productName == "grouper") + d.name = "Nexus7"; + else if (productName == "flo") + d.name = "Nexus7v2"; + else + d.name = "Unknown"; + } else + d.name = "Unknown"; + + d.mode = "fastboot"; + d.state = "ready"; + diList.append(d); + } + + return diList; +} + +bool DevicePage::isComplete() const +{ + int index = mListWidget->currentRow(); + if (index < 0 || index >= mListItems.size()) { + return false; + } + + return true; +} + +void DevicePage::itemSelected() +{ + emit completeChanged(); +} + +void DevicePage::updateDeviceList() +{ + if (wizard()->currentPage() != this) + return; + + QList<DeviceInfo> diList = list(); + + QStringList currentDevices; + + foreach (const DeviceInfo &di, diList) + currentDevices += di.serial; + + QMutableMapIterator<QString, QListWidgetItem*> iter(mListItems); + while (iter.hasNext()) { + iter.next(); + if (!currentDevices.contains(iter.key())) { + mDeviceInfo.remove(iter.key()); + delete iter.value(); + iter.remove(); + } + } + + foreach (const DeviceInfo &di, diList) { + QString text = "<h3>%1</h3><table style=\"margin-left:10px\"><tr><td>Serial number</td><td style=\"padding-left:10\">%2</td></tr><tr><td>Mode</td><td style=\"padding-left:10\">%3</td></tr><tr><td>State</td><td style=\"padding-left:10\">%4</td></tr></table>"; + text = text.arg(di.name, di.serial, di.mode, di.state); + + mDeviceInfo[di.serial] = di; + if (mListItems.contains(di.serial)) { + // update existing item + QListWidgetItem *item = mListItems.value(di.serial); + QLabel *label = qobject_cast<QLabel*>(mListWidget->itemWidget(item)); + label->setText(text); + } else { + // new item + QListWidgetItem *item = new QListWidgetItem(); + item->setData(Qt::UserRole, di.serial); + QLabel *label = new QLabel; + label->setText(text); + mListWidget->addItem(item); + mListWidget->setItemWidget(item, label); + item->setSizeHint(label->size()); + mListItems.insert(di.serial, item); + } + } + + if (mListWidget->count() == 0) + mError->setText("No suitable device found"); + else + mError->setText(""); + + emit completeChanged(); +} + + +void DevicePage::initializePage() +{ + mError->clear(); + updateDeviceList(); +} + +bool DevicePage::validatePage() +{ + QListWidgetItem *item = mListWidget->currentItem(); + if (!item) { + return false; + } + + QString serial = item->data(Qt::UserRole).toString(); + if (serial.isEmpty()) { + return false; + } + + if (!mDeviceInfo.contains(serial)) { + return false; + } + + DeviceInfo deviceInfo = mDeviceInfo[serial]; + if (deviceInfo.state != "ready") { + return false; + } + + G_device = deviceInfo.serial; + G_board = deviceInfo.name.toLower(); + G_mode = deviceInfo.mode; + return true; +} + +int DevicePage::nextId() const +{ + return MainWindow::Page_Commit; +} diff --git a/src/b2qt-flashing-wizard/device_page.h b/src/b2qt-flashing-wizard/device_page.h new file mode 100644 index 0000000..d0f26a2 --- /dev/null +++ b/src/b2qt-flashing-wizard/device_page.h @@ -0,0 +1,63 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc +** All rights reserved. +** For any questions to Digia, please use the contact form at +** http://qt.digia.com/ +** +** This file is part of Qt Enterprise Embedded. +** +** Licensees holding valid Qt Enterprise licenses may use this file in +** accordance with the Qt Enterprise License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. +** +** If you have questions regarding the use of this file, please use +** the contact form at http://qt.digia.com/ +** +****************************************************************************/ + +#ifndef DEVICE_PAGE_H +#define DEVICE_PAGE_H + +#include <QWizardPage> +#include <QMap> +class QListWidget; +class QLabel; +class QVBoxLayout; +class QListWidgetItem; + +class DevicePage : public QWizardPage +{ + Q_OBJECT +public: + DevicePage(QWidget *parent = 0); + virtual ~DevicePage(); + bool isComplete() const; + void initializePage(); + bool validatePage(); + int nextId() const; + +private slots: + void itemSelected(); + void updateDeviceList(); + +private: + struct DeviceInfo + { + QString name; + QString serial; + QString mode; + QString state; + }; + QList<DeviceInfo> list() const; + +private: + QListWidget *mListWidget; + QMap<QString, QListWidgetItem*> mListItems; + QMap<QString, DeviceInfo> mDeviceInfo; + QLabel *mError; + QVBoxLayout *mLayout; +}; + +#endif // DEVICE_PAGE_H diff --git a/src/b2qt-flashing-wizard/disk_page.cpp b/src/b2qt-flashing-wizard/disk_page.cpp new file mode 100644 index 0000000..c96e347 --- /dev/null +++ b/src/b2qt-flashing-wizard/disk_page.cpp @@ -0,0 +1,246 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc +** All rights reserved. +** For any questions to Digia, please use the contact form at +** http://qt.digia.com/ +** +** This file is part of Qt Enterprise Embedded. +** +** Licensees holding valid Qt Enterprise licenses may use this file in +** accordance with the Qt Enterprise License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. +** +** If you have questions regarding the use of this file, please use +** the contact form at http://qt.digia.com/ +** +****************************************************************************/ + +#include "disk_page.h" +#include "common.h" +#include "elevator.h" +#include "mainwindow.h" // for Page_ enum +#include <QRadioButton> +#include <QLayout> +#include <QDebug> +#include <QDir> +#include <QListWidget> +#include <QLabel> +#include <QTimer> +#include <QProcess> +#include <QMessageBox> + +extern QString G_device; +QLabel *createErrorLabel(QWidget *parent); + +DiskPage::DiskPage(QWidget *parent) + : QWizardPage(parent) + , mListWidget(new QListWidget(this)) + , mError(createErrorLabel(this)) + , mLayout(new QVBoxLayout(this)) +{ + setTitle("Disk selection"); + setSubTitle("Select a disk to be used"); + mLayout->addWidget(mListWidget); + mLayout->addSpacerItem(new QSpacerItem(40,40,QSizePolicy::Minimum, QSizePolicy::Expanding)); + mLayout->addWidget(mError); + setLayout(mLayout); + connect(mListWidget, &QListWidget::itemClicked, this, &DiskPage::itemSelected); + + QTimer *timer = new QTimer(this); + timer->setInterval(1000); + connect(timer, &QTimer::timeout, this, &DiskPage::updateDeviceList); + timer->start(); + + // Remember devices already present at startup + foreach (const DiskInfo &di, list()) { + if (!di.removable) + mHiddenDevices += di.path; + } +} + +DiskPage::~DiskPage() +{ +} + +QList<DiskPage::DiskInfo> DiskPage::list() const +{ + QList<DiskInfo> diList; + + QDir sys("/sys/block"); + + foreach (const QString &i, sys.entryList(QDir::Dirs | QDir::NoDotAndDotDot)) { + DiskInfo di; + + if (readAll(sys.absoluteFilePath(i) + "/removable").trimmed() == "1") + di.removable = true; + else + di.removable = false; + + di.logicalBlockSize = readAll(sys.absoluteFilePath(i) + "/queue/logical_block_size").trimmed().toULong(); + di.blocks = readAll(sys.absoluteFilePath(i) + "/size").trimmed().toULong(); + di.name = i; + di.path = "/dev/" + i; + + checkForDeviceMounted(di.path, di.mountPoint, di.mountPartition); + + diList.append(di); + } + + return diList; +} + +bool DiskPage::isComplete() const +{ + QListWidgetItem *item = mListWidget->currentItem(); + if (!item) + return false; + + QString path = item->data(Qt::UserRole).toString(); + if (path.isEmpty()) + return false; + if (!mListItems.contains(path)) + return false; + + return true; +} + +void DiskPage::itemSelected() +{ + emit completeChanged(); +} + +void DiskPage::updateDeviceList() +{ + if (wizard()->currentPage() != this) + return; + + QList<DiskInfo> diList = list(); + + QSet<QString> currentDevices; + + { // filter devices by name, size etc. + QMutableListIterator<DiskInfo> iter(diList); + + while (iter.hasNext()) { + iter.next(); + const DiskInfo &di = iter.value(); + + if (di.logicalBlockSize == 0 || di.blocks == 0 || di.name.startsWith("ram")) { + iter.remove(); + continue; + } + currentDevices += di.path; + } + } + + { // Update hidden devices list if a device was removed + QMutableSetIterator<QString> iter(mHiddenDevices); + + while (iter.hasNext()) { + iter.next(); + if (!currentDevices.contains(iter.value())) + iter.remove(); + } + } + currentDevices.subtract(mHiddenDevices); + + // Remove devices from map and UI + QMutableMapIterator<QString, QListWidgetItem*> iter(mListItems); + while (iter.hasNext()) { + iter.next(); + if (!currentDevices.contains(iter.key())) { + mDiskInfo.remove(iter.key()); + delete iter.value(); + iter.remove(); + } + } + + foreach (const DiskInfo &di, diList) { + if (!currentDevices.contains(di.path)) + continue; + + QString text = "<h3>%1</h3><table style=\"margin-left:10px\"><tr><td>Size</td><td style=\"padding-left:10\">%2</td></tr><tr><td>Removable</td><td style=\"padding-left:10\">%3</td><tr><td>Mounted</td><td style=\"padding-left:10\">%4</td></tr></table>"; + double size = di.logicalBlockSize * di.blocks; + QString sizeText; + + if (size < 1000) { + sizeText = QString::number(size) + QLatin1String(" B"); + } else if (size < 1000000) { + sizeText = QString::number(size / 1000) + QLatin1String(" KB"); + } else if (size < 1000000000) { + sizeText = QString::number(size / 1000000) + QLatin1String(" MB"); + } else { + sizeText = QString::number(qRound(size / 1000 / 1000 / 1000)) + QLatin1String(" GB"); + } + + text = text.arg(di.name, sizeText, di.removable?QLatin1String("yes"):QLatin1String("no"), di.mountPoint.isEmpty()?"no":di.mountPoint); + + mDiskInfo[di.path] = di; + if (mListItems.contains(di.path)) { + // update existing item + QListWidgetItem *item = mListItems.value(di.path); + QLabel *label = qobject_cast<QLabel*>(mListWidget->itemWidget(item)); + label->setText(text); + } else { + // new item + QListWidgetItem *item = new QListWidgetItem(); + item->setData(Qt::UserRole, di.path); + QLabel *label = new QLabel; + label->setText(text); + mListWidget->addItem(item); + mListWidget->setItemWidget(item, label); + item->setSizeHint(label->size()); + mListItems.insert(di.path, item); + } + } + + if (mListWidget->count() == 0) + mError->setText("No suitable disk device found. Insert device now."); + else + mError->setText(""); + + emit completeChanged(); +} + +void DiskPage::initializePage() +{ + mError->clear(); + updateDeviceList(); +} + +bool DiskPage::validatePage() +{ + QListWidgetItem *item = mListWidget->currentItem(); + if (!item) + return false; + QString path = item->data(Qt::UserRole).toString(); + if (path.isEmpty()) + return false; + + DiskInfo di = mDiskInfo[path]; + + if (!di.mountPoint.isEmpty()) { + // Ask the user before unmount + if (QMessageBox::Ok != QMessageBox::warning(this, "Unmount action", "The disk you selected is mounted at " + di.mountPoint + ". It will be unmounted now.", QMessageBox::Ok | QMessageBox::Cancel)) + return false; + + QStringList args = elevate(); + args << "umount" << di.mountPoint; + + int rc = QProcess::execute(args.takeFirst(), args); + if (rc != 0) { + QMessageBox::critical(this, "Unmount failed", "Could not umount the disk."); + return false; + } + } + + G_device = path; + return true; +} + +int DiskPage::nextId() const +{ + return MainWindow::Page_Commit; +} diff --git a/src/b2qt-flashing-wizard/disk_page.h b/src/b2qt-flashing-wizard/disk_page.h new file mode 100644 index 0000000..abc0ba5 --- /dev/null +++ b/src/b2qt-flashing-wizard/disk_page.h @@ -0,0 +1,68 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc +** All rights reserved. +** For any questions to Digia, please use the contact form at +** http://qt.digia.com/ +** +** This file is part of Qt Enterprise Embedded. +** +** Licensees holding valid Qt Enterprise licenses may use this file in +** accordance with the Qt Enterprise License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. +** +** If you have questions regarding the use of this file, please use +** the contact form at http://qt.digia.com/ +** +****************************************************************************/ + +#ifndef DISK_PAGE_H +#define DISK_PAGE_H + +#include <QWizardPage> +#include <QMap> +#include <QSet> +class QListWidget; +class QLabel; +class QVBoxLayout; +class QListWidgetItem; + +class DiskPage : public QWizardPage +{ + Q_OBJECT +public: + DiskPage(QWidget *parent = 0); + virtual ~DiskPage(); + bool isComplete() const; + void initializePage(); + bool validatePage(); + int nextId() const; + +private slots: + void itemSelected(); + void updateDeviceList(); + +private: + struct DiskInfo + { + QString path; + bool removable; + QString name; + quint64 logicalBlockSize; + quint64 blocks; + QString mountPoint; + QString mountPartition; + }; + QList<DiskInfo> list() const; + +private: + QListWidget *mListWidget; + QMap<QString, QListWidgetItem*> mListItems; + QMap<QString, DiskInfo> mDiskInfo; + QLabel *mError; + QVBoxLayout *mLayout; + QSet<QString> mHiddenDevices; +}; + +#endif // DISK_PAGE_H diff --git a/src/b2qt-flashing-wizard/elevator.cpp b/src/b2qt-flashing-wizard/elevator.cpp new file mode 100644 index 0000000..f4e087a --- /dev/null +++ b/src/b2qt-flashing-wizard/elevator.cpp @@ -0,0 +1,48 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc +** All rights reserved. +** For any questions to Digia, please use the contact form at +** http://qt.digia.com/ +** +** This file is part of Qt Enterprise Embedded. +** +** Licensees holding valid Qt Enterprise licenses may use this file in +** accordance with the Qt Enterprise License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. +** +** If you have questions regarding the use of this file, please use +** the contact form at http://qt.digia.com/ +** +****************************************************************************/ + +#include "elevator.h" + +#include <QFile> +#include <QDebug> +#include <QProcess> + +QStringList elevate() +{ + QStringList rc; + + int trySudo = QProcess::execute("sudo", QStringList() << "-n" << "echo" << "foo"); + if (trySudo == 0) { + qDebug() << "Sudo seems to work"; + rc << "sudo" << "--"; + return rc; + } + + if (QFile::exists("/usr/bin/kdesu")) { + rc << "/usr/bin/kdesu" << "-u" << "root" << "-t" << "--noignorebutton" << "--"; // @ARGS + } else if (QFile::exists("/usr/bin/gksu")) { + rc << "/usr/bin/gksu" << "-m" << "message" << "-u" << "root" << "--"; // @ARGS +// } else if (!QFile::exists("/usr/lib/ssh/x11-ssh-askpass")) { +// // SUDO_ASKPASS = @askpass-tool; +// qDebug() << "askpass"; +// rc << "sudo" << "-A" << "--"; // @ARGS + } + return rc; +} + diff --git a/src/b2qt-flashing-wizard/elevator.h b/src/b2qt-flashing-wizard/elevator.h new file mode 100644 index 0000000..903acc6 --- /dev/null +++ b/src/b2qt-flashing-wizard/elevator.h @@ -0,0 +1,22 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc +** All rights reserved. +** For any questions to Digia, please use the contact form at +** http://qt.digia.com/ +** +** This file is part of Qt Enterprise Embedded. +** +** Licensees holding valid Qt Enterprise licenses may use this file in +** accordance with the Qt Enterprise License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. +** +** If you have questions regarding the use of this file, please use +** the contact form at http://qt.digia.com/ +** +****************************************************************************/ + +#include <QStringList> + +QStringList elevate(); diff --git a/src/b2qt-flashing-wizard/finish_page.cpp b/src/b2qt-flashing-wizard/finish_page.cpp new file mode 100644 index 0000000..854f21b --- /dev/null +++ b/src/b2qt-flashing-wizard/finish_page.cpp @@ -0,0 +1,33 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc +** All rights reserved. +** For any questions to Digia, please use the contact form at +** http://qt.digia.com/ +** +** This file is part of Qt Enterprise Embedded. +** +** Licensees holding valid Qt Enterprise licenses may use this file in +** accordance with the Qt Enterprise License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. +** +** If you have questions regarding the use of this file, please use +** the contact form at http://qt.digia.com/ +** +****************************************************************************/ + +#include "finish_page.h" +#include <QLabel> + +FinishPage::FinishPage(QWidget *parent) + : QWizardPage(parent) +{ + setCommitPage(true); + setTitle("Finish"); + new QLabel("Setting up a Boot to Qt device completed.", this); +} + +FinishPage::~FinishPage() +{ +} diff --git a/src/b2qt-flashing-wizard/finish_page.h b/src/b2qt-flashing-wizard/finish_page.h new file mode 100644 index 0000000..a333e7c --- /dev/null +++ b/src/b2qt-flashing-wizard/finish_page.h @@ -0,0 +1,34 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc +** All rights reserved. +** For any questions to Digia, please use the contact form at +** http://qt.digia.com/ +** +** This file is part of Qt Enterprise Embedded. +** +** Licensees holding valid Qt Enterprise licenses may use this file in +** accordance with the Qt Enterprise License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. +** +** If you have questions regarding the use of this file, please use +** the contact form at http://qt.digia.com/ +** +****************************************************************************/ + +#ifndef FINISH_PAGE_H +#define FINISH_PAGE_H + +#include <QWizardPage> + +class FinishPage : public QWizardPage +{ + Q_OBJECT + +public: + FinishPage(QWidget *parent = 0); + virtual ~FinishPage(); +}; + +#endif // FINISH_PAGE_H diff --git a/src/b2qt-flashing-wizard/introduction_page.cpp b/src/b2qt-flashing-wizard/introduction_page.cpp new file mode 100644 index 0000000..1417fe7 --- /dev/null +++ b/src/b2qt-flashing-wizard/introduction_page.cpp @@ -0,0 +1,33 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc +** All rights reserved. +** For any questions to Digia, please use the contact form at +** http://qt.digia.com/ +** +** This file is part of Qt Enterprise Embedded. +** +** Licensees holding valid Qt Enterprise licenses may use this file in +** accordance with the Qt Enterprise License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. +** +** If you have questions regarding the use of this file, please use +** the contact form at http://qt.digia.com/ +** +****************************************************************************/ + +#include "introduction_page.h" +#include <QLabel> + +IntroductionPage::IntroductionPage(QWidget *parent) + : QWizardPage(parent) +{ + setTitle("Introduction"); +// setSubTitle(" "); + new QLabel("This wizard helps you setting up a Boot to Qt device", this); +} + +IntroductionPage::~IntroductionPage() +{ +} diff --git a/src/b2qt-flashing-wizard/introduction_page.h b/src/b2qt-flashing-wizard/introduction_page.h new file mode 100644 index 0000000..43575d6 --- /dev/null +++ b/src/b2qt-flashing-wizard/introduction_page.h @@ -0,0 +1,35 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc +** All rights reserved. +** For any questions to Digia, please use the contact form at +** http://qt.digia.com/ +** +** This file is part of Qt Enterprise Embedded. +** +** Licensees holding valid Qt Enterprise licenses may use this file in +** accordance with the Qt Enterprise License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. +** +** If you have questions regarding the use of this file, please use +** the contact form at http://qt.digia.com/ +** +****************************************************************************/ + +#ifndef INTRODUCTION_PAGE_H +#define INTRODUCTION_PAGE_H + +#include <QWizardPage> + +class IntroductionPage : public QWizardPage +{ + Q_OBJECT + +public: + IntroductionPage(QWidget *parent = 0); + virtual ~IntroductionPage(); +// void initializePage(); +}; + +#endif // INTRODUCTION_PAGE_H diff --git a/src/b2qt-flashing-wizard/logo.png b/src/b2qt-flashing-wizard/logo.png Binary files differnew file mode 100644 index 0000000..1dfa8dc --- /dev/null +++ b/src/b2qt-flashing-wizard/logo.png diff --git a/src/b2qt-flashing-wizard/main.cpp b/src/b2qt-flashing-wizard/main.cpp new file mode 100644 index 0000000..0321e3e --- /dev/null +++ b/src/b2qt-flashing-wizard/main.cpp @@ -0,0 +1,38 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc +** All rights reserved. +** For any questions to Digia, please use the contact form at +** http://qt.digia.com/ +** +** This file is part of Qt Enterprise Embedded. +** +** Licensees holding valid Qt Enterprise licenses may use this file in +** accordance with the Qt Enterprise License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. +** +** If you have questions regarding the use of this file, please use +** the contact form at http://qt.digia.com/ +** +****************************************************************************/ + +#include "mainwindow.h" +#include <QApplication> +//#include <QCleanlooksStyle> +#include <QStyleFactory> +#include <QDebug> +#include <QIcon> + +int main(int argc, char **argv) +{ + QApplication app(argc, argv); +// qDebug() << QStyleFactory::keys(); + QStyle *style = QStyleFactory::create("Fusion"); + if (style) + app.setStyle(style); + app.setWindowIcon(QIcon(":logo.png")); + MainWindow mw; + mw.show(); + return app.exec(); +} diff --git a/src/b2qt-flashing-wizard/mainwindow.cpp b/src/b2qt-flashing-wizard/mainwindow.cpp new file mode 100644 index 0000000..fe66ed0 --- /dev/null +++ b/src/b2qt-flashing-wizard/mainwindow.cpp @@ -0,0 +1,64 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc +** All rights reserved. +** For any questions to Digia, please use the contact form at +** http://qt.digia.com/ +** +** This file is part of Qt Enterprise Embedded. +** +** Licensees holding valid Qt Enterprise licenses may use this file in +** accordance with the Qt Enterprise License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. +** +** If you have questions regarding the use of this file, please use +** the contact form at http://qt.digia.com/ +** +****************************************************************************/ + +#include "mainwindow.h" +#include "introduction_page.h" +#include "platform_page.h" +#include "disk_page.h" +#include "device_page.h" +#include "commit_page.h" +#include "progress_page.h" +#include "finish_page.h" +#include <QDebug> +#include <QDir> + +QString G_platform; // generic-4.4 / iMX6 +QString G_version; // Boot2Qt version +QString G_os; // eAndroid / eLinux +QString G_device; // serial number or SD Card +QString G_board; // nexus7v2,nexus7, etc... +QString G_SDKDIR; // install directory +QString G_mode; // adb or fastboot + +MainWindow::MainWindow() + : QWizard() +{ + setPixmap(QWizard::LogoPixmap, QPixmap(":logo.png")); + setPixmap(QWizard::BackgroundPixmap, QPixmap(":background.png")); + setPixmap(QWizard::WatermarkPixmap, QPixmap(":watermark.png")); + G_SDKDIR = qgetenv("SDKDIR"); + if (G_SDKDIR.isEmpty()) + G_SDKDIR = QDir::currentPath(); + if (!G_SDKDIR.endsWith('/')) + G_SDKDIR.append('/'); + + setWizardStyle(QWizard::ModernStyle); + setPage(Page_Intro, new IntroductionPage); + setPage(Page_Platform, new PlatformPage); + setPage(Page_Disk, new DiskPage); + setPage(Page_Device, new DevicePage); + setPage(Page_Commit, new CommitPage); + setPage(Page_Progress, new ProgressPage); + setPage(Page_Finish, new FinishPage); +} + +MainWindow::~MainWindow() +{ +} + diff --git a/src/b2qt-flashing-wizard/mainwindow.h b/src/b2qt-flashing-wizard/mainwindow.h new file mode 100644 index 0000000..517520e --- /dev/null +++ b/src/b2qt-flashing-wizard/mainwindow.h @@ -0,0 +1,35 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc +** All rights reserved. +** For any questions to Digia, please use the contact form at +** http://qt.digia.com/ +** +** This file is part of Qt Enterprise Embedded. +** +** Licensees holding valid Qt Enterprise licenses may use this file in +** accordance with the Qt Enterprise License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. +** +** If you have questions regarding the use of this file, please use +** the contact form at http://qt.digia.com/ +** +****************************************************************************/ + +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include <QWizard> + +class MainWindow : public QWizard +{ + Q_OBJECT + +public: + enum {Page_Intro, Page_Platform, Page_Disk, Page_Device, Page_Commit, Page_Progress, Page_Finish}; + MainWindow(); + virtual ~MainWindow(); +}; + +#endif // MAINWINDOW_H diff --git a/src/b2qt-flashing-wizard/platform_page.cpp b/src/b2qt-flashing-wizard/platform_page.cpp new file mode 100644 index 0000000..3adf9b9 --- /dev/null +++ b/src/b2qt-flashing-wizard/platform_page.cpp @@ -0,0 +1,180 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc +** All rights reserved. +** For any questions to Digia, please use the contact form at +** http://qt.digia.com/ +** +** This file is part of Qt Enterprise Embedded. +** +** Licensees holding valid Qt Enterprise licenses may use this file in +** accordance with the Qt Enterprise License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. +** +** If you have questions regarding the use of this file, please use +** the contact form at http://qt.digia.com/ +** +****************************************************************************/ + +#include "platform_page.h" +#include <QRadioButton> +#include <QLayout> +#include <QDebug> +#include <QDir> +#include <QLabel> +#include <QSpacerItem> +#include "mainwindow.h" // Page_ enum + +extern QString G_platform; +extern QString G_version; +extern QString G_os; +extern QString G_device; +extern QString G_board; +extern QString G_SDKDIR; + +QLabel *createErrorLabel(QWidget *parent) +{ + QLabel *label = new QLabel(parent); + label->setAlignment(Qt::AlignHCenter); + label->setWordWrap(true); + + QFont f = label->font(); + f.setBold(true); + label->setFont(f); + + QPalette p = label->palette(); + p.setColor(QPalette::WindowText, Qt::red); + label->setPalette(p); + + return label; +} + +PlatformPage::PlatformPage(QWidget *parent) + : QWizardPage(parent) + , mError(createErrorLabel(this)) + , mLayout(new QVBoxLayout(this)) +{ + setTitle("Platform"); + setSubTitle("Select a platform to create a disk for"); + mLayout->addSpacerItem(new QSpacerItem(40,40,QSizePolicy::Minimum, QSizePolicy::Expanding)); + mLayout->addWidget(mError); + setLayout(mLayout); +} + +PlatformPage::~PlatformPage() +{ +} + +bool PlatformPage::isComplete() const +{ + QStringList data = buttonData(); + if (data.isEmpty()) + return false; + + if (data[0] == "nexus7") { + mError->setText("The selected platform is currently not supported."); + return false; + } + if (data[0] == "iMX6" && data[1] == "eAndroid") { + mError->setText("The selected platform is currently not supported."); + return false; + } + + return !data.isEmpty(); +} + +void PlatformPage::itemSelected() +{ + mError->clear(); + emit completeChanged(); +} + +void PlatformPage::initializePage() +{ + mError->clear(); + + qDeleteAll(mButtons); + mButtons.clear(); + mButtonData.clear(); + + QDir dir(G_SDKDIR); + foreach (const QString i, dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot)) { + if (!i.startsWith("Boot2Qt-")) + continue; + + QDir dir2(dir.absoluteFilePath(i)); + foreach (const QString j, dir2.entryList(QDir::Dirs | QDir::NoDotAndDotDot)) { + if (j.startsWith("emulator-")) + continue; + QStringList token = j.split('-'); + QString os = token.takeLast(); + QString name = token.join("-"); + + if (os == "eAndroid" && name.startsWith("generic-")) { + QString version = token[1]; + QDir dir3(dir2.absoluteFilePath(j) + "/images"); + foreach (const QString k, dir3.entryList(QDir::Dirs | QDir::NoDotAndDotDot)) { + if (k == "common") + continue; + + if (!QFile::exists(dir3.absoluteFilePath(k) + "/deploy.sh")) + continue; + + QRadioButton *button = new QRadioButton; + button->setText(k + "-" + version + "-" + os + " (" + i + ")"); + mLayout->insertWidget(0, button); + connect(button, &QRadioButton::toggled, this, &PlatformPage::itemSelected); + mButtons.append(button); + mButtonData.insert(button, QStringList() << name << os << i << k); + } + + } else if (os == "eAndroid" || os == "eLinux") { + QRadioButton *button = new QRadioButton; + button->setText(j + " (" + i + ")"); + mLayout->insertWidget(0, button); + connect(button, &QRadioButton::toggled, this, &PlatformPage::itemSelected); + mButtons.append(button); + mButtonData.insert(button, QStringList() << name << os << i << name); + } + } + } + if (mButtons.isEmpty()) { + mError->setText("No suitable platform found.\nMake sure you have installed at least one hardware platform."); + } +} + +QStringList PlatformPage::buttonData() const +{ + QStringList data; + + foreach (QRadioButton *button, mButtons) { + if (button->isChecked()) { + data = mButtonData[button]; + break; + } + } + + return data; +} + +bool PlatformPage::validatePage() +{ + QStringList data = buttonData(); + + G_platform = data[0]; + G_os = data[1]; + G_version = data[2]; + G_board = data[3]; + + qDebug() << "Selected:" << G_platform << G_os << G_version << G_board; + return true; +} + +int PlatformPage::nextId() const +{ + if (G_board.startsWith("nexus7")) + return MainWindow::Page_Device; + else + return MainWindow::Page_Disk; +} diff --git a/src/b2qt-flashing-wizard/platform_page.h b/src/b2qt-flashing-wizard/platform_page.h new file mode 100644 index 0000000..215023a --- /dev/null +++ b/src/b2qt-flashing-wizard/platform_page.h @@ -0,0 +1,52 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc +** All rights reserved. +** For any questions to Digia, please use the contact form at +** http://qt.digia.com/ +** +** This file is part of Qt Enterprise Embedded. +** +** Licensees holding valid Qt Enterprise licenses may use this file in +** accordance with the Qt Enterprise License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. +** +** If you have questions regarding the use of this file, please use +** the contact form at http://qt.digia.com/ +** +****************************************************************************/ + +#ifndef PLATFORM_PAGE_H +#define PLATFORM_PAGE_H + +#include <QWizardPage> +#include <QMap> +class QRadioButton; +class QLabel; +class QVBoxLayout; + +class PlatformPage : public QWizardPage +{ + Q_OBJECT +public: + PlatformPage(QWidget *parent = 0); + virtual ~PlatformPage(); + bool isComplete() const; + void initializePage(); + bool validatePage(); + int nextId() const; + +private slots: + void itemSelected(); + +private: + QStringList buttonData() const; + + QList<QRadioButton*> mButtons; + QMap<QRadioButton*, QStringList> mButtonData; + QLabel *mError; + QVBoxLayout *mLayout; +}; + +#endif // PLATFORM_PAGE_H diff --git a/src/b2qt-flashing-wizard/progress_page.cpp b/src/b2qt-flashing-wizard/progress_page.cpp new file mode 100644 index 0000000..7986df9 --- /dev/null +++ b/src/b2qt-flashing-wizard/progress_page.cpp @@ -0,0 +1,90 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc +** All rights reserved. +** For any questions to Digia, please use the contact form at +** http://qt.digia.com/ +** +** This file is part of Qt Enterprise Embedded. +** +** Licensees holding valid Qt Enterprise licenses may use this file in +** accordance with the Qt Enterprise License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. +** +** If you have questions regarding the use of this file, please use +** the contact form at http://qt.digia.com/ +** +****************************************************************************/ + +#include "progress_page.h" +#include "actor.h" +#include <QDebug> +#include <QLabel> +#include <QLayout> +#include <QPushButton> +#include <QTextEdit> +#include <QTimer> + +ProgressPage::ProgressPage(QWidget *parent) + : QWizardPage(parent) + , mProgress(new QLabel(this)) + , mActor(0) + , mFinished(false) + , mTextEdit(new QTextEdit(this)) +{ + setTitle("Disk creation"); + setSubTitle("Progress of writing the disk"); + setLayout(new QVBoxLayout(this)); + mProgress->setText(tr("Starting")); + layout()->addWidget(mProgress); + QPushButton *button = new QPushButton(this); + button->setText("Show details"); + layout()->addWidget(button); + layout()->addWidget(mTextEdit); + QPushButton *copy = new QPushButton(this); + copy->setText("Copy to clipboard"); + layout()->addWidget(copy); +} + +ProgressPage::~ProgressPage() +{ +} + +bool ProgressPage::isComplete() const +{ + return mFinished; +} + +void ProgressPage::initializePage() +{ + Q_ASSERT(mActor); + mActor->start(); +} + +void ProgressPage::progress(const QString &step) +{ + mProgress->setText(step); +} + +void ProgressPage::finished() +{ + mFinished = true; + emit completeChanged(); + wizard()->next(); // progress to next page automatically +} + +void ProgressPage::setActor(Actor *actor) +{ + Q_ASSERT(actor); + mActor = actor; + connect(actor, &Actor::finished, this, &ProgressPage::finished); + connect(actor, &Actor::details, this, &ProgressPage::addDetails); + connect(actor, &Actor::progress, this, &ProgressPage::progress); +} + +void ProgressPage::addDetails(QByteArray newData) +{ + newData.replace(0x08 /* backspace */, ' '); + mTextEdit->append(QString::fromLocal8Bit(newData)); +} diff --git a/src/b2qt-flashing-wizard/progress_page.h b/src/b2qt-flashing-wizard/progress_page.h new file mode 100644 index 0000000..d0a3b79 --- /dev/null +++ b/src/b2qt-flashing-wizard/progress_page.h @@ -0,0 +1,50 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc +** All rights reserved. +** For any questions to Digia, please use the contact form at +** http://qt.digia.com/ +** +** This file is part of Qt Enterprise Embedded. +** +** Licensees holding valid Qt Enterprise licenses may use this file in +** accordance with the Qt Enterprise License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. +** +** If you have questions regarding the use of this file, please use +** the contact form at http://qt.digia.com/ +** +****************************************************************************/ + +#ifndef PROGRESS_PAGE_H +#define PROGRESS_PAGE_H + +#include <QWizardPage> +class QLabel; +class Actor; +class QTextEdit; + +class ProgressPage : public QWizardPage +{ + Q_OBJECT +public: + ProgressPage(QWidget *parent = 0); + virtual ~ProgressPage(); + bool isComplete() const; + void initializePage(); + void setActor(Actor *actor); + +public slots: + void progress(const QString &step); + void finished(); + void addDetails(QByteArray newData); + +private: + QLabel *mProgress; + Actor *mActor; + bool mFinished; + QTextEdit *mTextEdit; +}; + +#endif // PROGRESS_PAGE_H diff --git a/src/b2qt-flashing-wizard/scriptwriter.cpp b/src/b2qt-flashing-wizard/scriptwriter.cpp new file mode 100644 index 0000000..dfe554e --- /dev/null +++ b/src/b2qt-flashing-wizard/scriptwriter.cpp @@ -0,0 +1,148 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc +** All rights reserved. +** For any questions to Digia, please use the contact form at +** http://qt.digia.com/ +** +** This file is part of Qt Enterprise Embedded. +** +** Licensees holding valid Qt Enterprise licenses may use this file in +** accordance with the Qt Enterprise License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. +** +** If you have questions regarding the use of this file, please use +** the contact form at http://qt.digia.com/ +** +****************************************************************************/ + +#include "scriptwriter.h" +#include <QPair> +#include <QStringList> +#include <QFileInfo> +#include <QDebug> +#include <QTimer> +#include <QProcessEnvironment> +#include <QElapsedTimer> +#include <sys/stat.h> +#include "elevator.h" +#include "common.h" + +ScriptWriter::ScriptWriter(QObject *parent) + : Actor(parent) + , mDebug(false) +{ + mProcess.setProcessChannelMode(QProcess::MergedChannels); + mDebug = qEnvironmentVariableIsSet("DEBUG"); +} + +ScriptWriter::~ScriptWriter() +{ +} + +void ScriptWriter::setScriptFile(const QString &fileName) +{ + mScriptName = fileName; +} + +bool ScriptWriter::ready(QString &error) const +{ + if (mScriptName.isEmpty()) { + error = "File name is empty"; + return false; + } + + QFileInfo file(mScriptName); + if (!file.exists()) { + error = "File does not exist: " + mScriptName; + return false; + } + if (!file.isFile()) { + error = "File " + mScriptName + " is not a file"; + return false; + } + if (!file.isReadable()) { + error = "File is not readable"; + return false; + } + if (!file.isExecutable()) { + error = "File is not executable"; + return false; + } + if (file.size() == 0) { + error = "File is empty"; + return false; + } + + return true; +} + +void ScriptWriter::start() +{ + connect(&mProcess, &QProcess::readyReadStandardOutput, this, &ScriptWriter::readOutput); + connect(&mProcess, (void (QProcess::*)(QProcess::ProcessError))&QProcess::error, this, &ScriptWriter::processError); + connect(&mProcess, (void (QProcess::*)(int, QProcess::ExitStatus))&QProcess::finished, this, &ScriptWriter::processFinished); + + // Due to some random convenience output in the deploy scripts "set -x" has to be used to synchronize + QStringList args = elevate() << "/bin/sh" << "-x" << mScriptName << mAdditionalArgs; + qDebug() << "Executing" << args; + +/* QProcessEnvironment pe ; + QString var = qgetenv("XDG_SESSION_COOKIE"); + pe.insert("XDG_SESSION_COOKIE", var); + var = qgetenv("HOME"); + pe.insert("HOME", var); + mProcess.setProcessEnvironment(pe); + */ + mProcess.setProcessEnvironment(QProcessEnvironment::systemEnvironment()); + + mProcess.start(args.takeFirst(), args); + if (!mProcess.waitForStarted()) + qFatal("Failed to start script"); + mProcess.write("y\n"); +} + +void ScriptWriter::readOutput() +{ + QByteArray ba = mProcess.readAllStandardOutput(); + QList<QByteArray> baList = ba.split('\n'); + + foreach (const QByteArray &line, baList) { + if (line.startsWith("-- STEP -- ")) + emit progress(QString::fromLocal8Bit(line.mid(11).trimmed())); + } + + emit details(ba); +} + +void ScriptWriter::processFinished(int exitCode, QProcess::ExitStatus exitStatus) +{ + if (exitStatus != QProcess::NormalExit) { + qWarning("process crashed"); + return; + } + + if (exitCode != 0) { + qWarning("process failed"); + return; + } + emit finished(); +} + +void ScriptWriter::processError(QProcess::ProcessError error) +{ + qWarning() << "processError" << error; +} + +void ScriptWriter::setEnvironment(const QString &key, const QString &value) +{ + QProcessEnvironment env = mProcess.processEnvironment(); + env.insert(key, value); + mProcess.setProcessEnvironment(env); +} + +void ScriptWriter::setAdditionalArgs(const QStringList &args) +{ + mAdditionalArgs = args; +} diff --git a/src/b2qt-flashing-wizard/scriptwriter.h b/src/b2qt-flashing-wizard/scriptwriter.h new file mode 100644 index 0000000..a1ed7a3 --- /dev/null +++ b/src/b2qt-flashing-wizard/scriptwriter.h @@ -0,0 +1,52 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc +** All rights reserved. +** For any questions to Digia, please use the contact form at +** http://qt.digia.com/ +** +** This file is part of Qt Enterprise Embedded. +** +** Licensees holding valid Qt Enterprise licenses may use this file in +** accordance with the Qt Enterprise License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. +** +** If you have questions regarding the use of this file, please use +** the contact form at http://qt.digia.com/ +** +****************************************************************************/ + +#ifndef SCRIPTWRITER_H +#define SCRIPTWRITER_H + +#include "actor.h" +#include <QProcess> +class QElapsedTimer; + +class ScriptWriter : public Actor +{ + Q_OBJECT + +public: + ScriptWriter(QObject *parent); + virtual ~ScriptWriter(); + void setScriptFile(const QString &fileName); + bool ready(QString &) const; + void start(); + void setEnvironment(const QString &key, const QString &value); + void setAdditionalArgs(const QStringList &); + +private slots: + void readOutput(); + void processFinished(int, QProcess::ExitStatus); + void processError(QProcess::ProcessError); + +private: + QString mScriptName; + QProcess mProcess; + QStringList mAdditionalArgs; + bool mDebug; +}; + +#endif // SCRIPTWRITER_H diff --git a/src/b2qt-flashing-wizard/watermark.png b/src/b2qt-flashing-wizard/watermark.png Binary files differnew file mode 100644 index 0000000..3ad314a --- /dev/null +++ b/src/b2qt-flashing-wizard/watermark.png diff --git a/src/b2qt-update-application/.gitignore b/src/b2qt-update-application/.gitignore new file mode 100644 index 0000000..d2a78c0 --- /dev/null +++ b/src/b2qt-update-application/.gitignore @@ -0,0 +1,4 @@ +Makefile +b2qt-update-application +moc_*.cpp +*.o diff --git a/src/b2qt-update-application/b2qt-update-application.pro b/src/b2qt-update-application/b2qt-update-application.pro new file mode 100644 index 0000000..51fc06f --- /dev/null +++ b/src/b2qt-update-application/b2qt-update-application.pro @@ -0,0 +1,35 @@ +CONFIG += c++11 +QT = core network +SOURCES += \ + main.cpp \ + tar.cpp \ + filewrapper.cpp \ + update.cpp + +HEADERS += \ + tar.h \ + filewrapper.h \ + update.h + +LIBS += -lcrypto +INSTALLS += target +target.path = /usr/bin + +# Find out git hash +unix:system(which git):HAS_GIT=TRUE +win32:system(where git.exe):HAS_GIT=TRUE +contains(HAS_GIT, TRUE) { + GIT_HASH=$$system(git log -1 --format=%H) + !system(git diff-index --quiet HEAD): GIT_HASH="$$GIT_HASH-dirty" + GIT_VERSION=$$system(git describe --tags --exact-match) + isEmpty(GIT_VERSION) : GIT_VERSION="unknown" +} else { + GIT_HASH="unknown" + GIT_VERSION="unknown" +} + +isEmpty(GIT_VERSION) : error("No suitable tag found") +isEmpty(GIT_HASH) : error("No hash available") + +DEFINES+="GIT_HASH=\\\"$$GIT_HASH\\\"" +DEFINES+="GIT_VERSION=\\\"$$GIT_VERSION\\\"" diff --git a/src/b2qt-update-application/filewrapper.cpp b/src/b2qt-update-application/filewrapper.cpp new file mode 100644 index 0000000..6b4a759 --- /dev/null +++ b/src/b2qt-update-application/filewrapper.cpp @@ -0,0 +1,53 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc +** All rights reserved. +** For any questions to Digia, please use the contact form at +** http://qt.digia.com/ +** +** This file is part of Qt Enterprise Embedded. +** +** Licensees holding valid Qt Enterprise licenses may use this file in +** accordance with the Qt Enterprise License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. +** +** If you have questions regarding the use of this file, please use +** the contact form at http://qt.digia.com/ +** +****************************************************************************/ + +#include "filewrapper.h" +#include <QDebug> +#include <QTimer> + +FileWrapper::FileWrapper(const QString &fileName, QObject *parent) + : QFile(fileName, parent) + , mTimer(new QTimer(this)) +{ + mTimer->setInterval(50); + connect(mTimer, &QTimer::timeout, this, &FileWrapper::emitReadyRead); +} + +FileWrapper::~FileWrapper() +{ +} + +bool FileWrapper::open(OpenMode mode) +{ + bool rc = QFile::open(mode); + if (rc) { + mTimer->start(); + } + return rc; +} + +void FileWrapper::emitReadyRead() +{ + if (!atEnd()) { + emit readyRead(); + } else { + mTimer->stop(); + close(); + } +} diff --git a/src/b2qt-update-application/filewrapper.h b/src/b2qt-update-application/filewrapper.h new file mode 100644 index 0000000..fc8a977 --- /dev/null +++ b/src/b2qt-update-application/filewrapper.h @@ -0,0 +1,40 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc +** All rights reserved. +** For any questions to Digia, please use the contact form at +** http://qt.digia.com/ +** +** This file is part of Qt Enterprise Embedded. +** +** Licensees holding valid Qt Enterprise licenses may use this file in +** accordance with the Qt Enterprise License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. +** +** If you have questions regarding the use of this file, please use +** the contact form at http://qt.digia.com/ +** +****************************************************************************/ + +#ifndef FILEWRAPPER_H +#define FILEWRAPPER_H + +#include <QFile> +class QTimer; + +class FileWrapper : public QFile +{ + Q_OBJECT + +public: + FileWrapper(const QString &fileName, QObject *parent = 0); + virtual ~FileWrapper(); + bool open(OpenMode mode); + +private: + void emitReadyRead(); + QTimer *mTimer; +}; + +#endif // FILEWRAPPER_H diff --git a/src/b2qt-update-application/main.cpp b/src/b2qt-update-application/main.cpp new file mode 100644 index 0000000..353edd9 --- /dev/null +++ b/src/b2qt-update-application/main.cpp @@ -0,0 +1,204 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc +** All rights reserved. +** For any questions to Digia, please use the contact form at +** http://qt.digia.com/ +** +** This file is part of Qt Enterprise Embedded. +** +** Licensees holding valid Qt Enterprise licenses may use this file in +** accordance with the Qt Enterprise License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. +** +** If you have questions regarding the use of this file, please use +** the contact form at http://qt.digia.com/ +** +****************************************************************************/ + +#include <QCoreApplication> +#include <QProcess> +#include <QDebug> +#include <QFile> +#include <QDir> +#include <unistd.h> +#include <sys/reboot.h> +#include <QNetworkAccessManager> +#include <QUrl> +#include <QNetworkRequest> +#include <QNetworkReply> +#include "update.h" +#include "filewrapper.h" + +#define UPDATE_MOUNTPOINT "/mnt/update" + +QStringList mounts; + +void cleanup() +{ + foreach (const QString &m, mounts) { + QProcess::execute("umount", QStringList() << m); + } + mounts.clear(); +} + +void error(const QString &message) +{ + fprintf(stderr, "%s\n", message.toLocal8Bit().constData()); + cleanup(); + exit(1); +} + +bool execute(const QString &binary, const QStringList &arguments) +{ + int rc = QProcess::execute(binary, arguments); + if (rc != 0) + error("Failed to execute command '" + binary + " " + arguments.join(" ")); + return rc == 0; +} + +bool mount(const QString &device, const QString &mountpoint, const QString &arguments = QString()) +{ + QStringList tmp; + if (!arguments.isEmpty()) + tmp << "-o" << arguments; + + if (!execute("mount", QStringList() << tmp << device << mountpoint)) + return false; + + if (!arguments.contains("remount")) + mounts << mountpoint; + return true; +} + +QByteArray readAll(const QString &fileName) +{ + QFile f(fileName); + if (!f.open(QFile::ReadOnly)) { + qWarning() << "Could not read" << fileName; + return QByteArray(); + } + + return f.readAll(); +} + +bool writeAll(const QString &fileName, const QByteArray &content) +{ + QFile f(fileName); + if (!f.open(QFile::WriteOnly)) { + qWarning() << "Could not write" << fileName; + return false; + } + if (f.write(content) != content.size()) { + qWarning() << "write size mismatch"; + return false; + } + f.close(); + return true; +} + +QStringList find_usb_storage() +{ + QStringList rc; + QDir d("/sys/dev/block"); + + foreach (QString s, d.entryList()) { + QFileInfo fi(d.absoluteFilePath(s)); + QString path = fi.canonicalFilePath(); + if (path.contains("/usb")) + rc += path.mid(path.lastIndexOf('/')); + } + return rc; +} + +int main(int argc, char **argv) +{ + QCoreApplication app(argc, argv); + printf("Version %s, SHA1 %s\n", GIT_VERSION, GIT_HASH); + + mount(QString(), "/", "remount,rw"); + execute("mkdir", QStringList() << "-p" << "/mnt/boot"); + execute("mkdir", QStringList() << "-p" << "/mnt/root"); + execute("mkdir", QStringList() << "-p" << UPDATE_MOUNTPOINT); + mount("/dev/mmcblk0p1", "/mnt/boot", "ro"); + + QByteArray update_state = readAll("/mnt/boot/update/state").trimmed(); + if (update_state.isEmpty()) + qFatal("Update state is empty"); + + if (update_state == "v") + qFatal("Update state is 'valid', this should not happen"); + else if (update_state == "t") + qDebug() << "Update state is 'testing', this should not happen"; + else if (update_state == "u") + qDebug() << "Update state is 'update'"; + else + qDebug() << "Unknown update state:" << update_state; + + QStringList usbsticks = find_usb_storage(); + + qDebug() << "Found USB storage devices:" << usbsticks; + + bool update_found = false; + foreach (QString s, usbsticks) { + if (QProcess::execute("mount", QStringList() << "-o" << "ro" << "/dev/" + s << UPDATE_MOUNTPOINT) == 0) { + mounts << UPDATE_MOUNTPOINT; + qDebug() << "mount successful"; + + if (QFile::exists(UPDATE_MOUNTPOINT "/b2qt-update")) { + qDebug() << "update found"; + update_found = true; + break; + } + execute("umount", QStringList() << UPDATE_MOUNTPOINT); + mounts.removeAt(mounts.lastIndexOf(UPDATE_MOUNTPOINT)); + qDebug() << "no update found"; + } else { + qDebug() << "mount of" << s << "failed"; + } + } + + Update update; + + if (update_found) { + FileWrapper *fw = new FileWrapper(UPDATE_MOUNTPOINT "/b2qt-update", &update); + if (!fw->open(QFile::ReadOnly)) + qFatal("Failed to open update"); + qDebug() << "Starting update from USB"; + update.setDevice(fw); + } else { + QByteArray update_source = readAll("/mnt/boot/update/source").trimmed(); + if (update_source.isEmpty()) { + execute("umount", QStringList() << "/mnt/boot"); // FIXME + mounts.removeAt(mounts.lastIndexOf("/mnt/boot")); + qFatal("Update source is empty"); + } + + execute("udhcpc", QStringList() << "-i" << "eth0"); + + QNetworkAccessManager *manager = new QNetworkAccessManager(0); + QNetworkReply *reply = manager->get(QNetworkRequest(QUrl(update_source))); + reply->setReadBufferSize(2000000); // 2 MB + + QObject::connect(reply, (void (QNetworkReply::*)(QNetworkReply::NetworkError))&QNetworkReply::error, + [](QNetworkReply::NetworkError e){qDebug() << "network error" << e;}); + QObject::connect(reply, &QNetworkReply::sslErrors, + [](QList<QSslError>){ qDebug() << "ssl errors";}); + + qDebug() << "Starting update from Internet" << update_source; + update.setDevice(reply); + } + + app.exec(); + + qDebug() << "unmount"; + cleanup(); + + qDebug() << "sync"; + sync(); + qDebug() << "reboot; waiting 2 seconds"; + sleep(2); + reboot(RB_AUTOBOOT); + return 0; +} diff --git a/src/b2qt-update-application/tar.cpp b/src/b2qt-update-application/tar.cpp new file mode 100644 index 0000000..c0369ff --- /dev/null +++ b/src/b2qt-update-application/tar.cpp @@ -0,0 +1,309 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc +** All rights reserved. +** For any questions to Digia, please use the contact form at +** http://qt.digia.com/ +** +** This file is part of Qt Enterprise Embedded. +** +** Licensees holding valid Qt Enterprise licenses may use this file in +** accordance with the Qt Enterprise License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. +** +** If you have questions regarding the use of this file, please use +** the contact form at http://qt.digia.com/ +** +****************************************************************************/ + +#include "tar.h" +#include <QProcess> +#include <QDebug> +#include <openssl/x509.h> +#include <openssl/pem.h> + +static bool isZeroed(const char *ptr, unsigned int size) +{ + while (size) { + if (*ptr) { + return false; + } + ++ptr; + --size; + } + return true; +} + +Tar::Tar(QIODevice *source) + : QObject(source) + , mSource(source) + , mSize(0) + , mRemainingSize(0) + , mRemainingFileBytes(0) + , mProcess(0) + , mState(WaitForHeader) + , x509(0) + , mdctx(0) +{ + // Initialize table with all digests in order to look them up by string later + OpenSSL_add_all_digests(); + + connect(source, &QIODevice::readyRead, this, &Tar::dataIncoming); + connect(source, &QIODevice::aboutToClose, this, &Tar::aboutToClose); +} + +Tar::~Tar() +{ + if (x509) + X509_free(x509); + x509 = 0; + if (mdctx) + EVP_MD_CTX_destroy(mdctx); + mdctx = 0; +} + +const QByteArray &Tar::currentContent() const +{ + return mContent; +} + +unsigned long Tar::currentSize() const +{ + return mSize; +} + +QString Tar::currentFileName() const +{ + if (mHeader.name[sizeof(mHeader.name)-1]) + return QString::fromLatin1(mHeader.name, sizeof(mHeader.name)); + else + return QString::fromLatin1(mHeader.name); +} + +void Tar::dataIncoming() +{ + while (mSource->bytesAvailable() > 512) { + + if (mState == WaitForHeader) { + if (mSource->bytesAvailable() < 512) + return; + if (mSource->read((char*)&mHeader, sizeof(mHeader)) != sizeof(mHeader)) + qFatal("Tar read error"); + if (isZeroed((char*)&mHeader, sizeof(mHeader))) { + qDebug() << "Zero Tar block"; + qDebug() << "Bytes left" << mSource->bytesAvailable(); + mSource->close(); + return; + } + if (mHeader.name[0] == 0) + qFatal("Header starts with 0"); + + mSize = QString::fromLatin1(mHeader.size, sizeof(mHeader.size)-1).toULongLong(0, 8); + mRemainingFileBytes = mSize; + mRemainingSize = mSize; + if (mSize % 512) // padding to 512 byte block + mRemainingSize += 512 - (mSize % 512); + + if (mSize == 0) { + qFatal("Size of file is 0"); + } + + mState = WaitForDecision; + qDebug() << "Started file" << currentFileName() << mSize; + if (x509) + setupMDContext(); + emit startingFile(currentFileName()); + } else if (mState == WaitForDecision) { + return; + } else if (mState == Extract) { + QByteArray ba; + + quint64 bytesToHandle = mRemainingSize; + + if (mSource->bytesAvailable() < mRemainingSize) + bytesToHandle = mSource->bytesAvailable(); + if (bytesToHandle > 60000) + bytesToHandle = 60000; + ba = mSource->read(bytesToHandle); + + updateMDContext(ba); + mProcess->write(ba); + mProcess->waitForBytesWritten(); + mRemainingSize -= ba.size(); + // do not return + } else if (mState == Receive) { + QByteArray ba; + + if (mSource->bytesAvailable() > mRemainingSize) + ba = mSource->read(mRemainingSize); + else + ba = mSource->readAll(); + updateMDContext(ba); + mContent += ba; + mRemainingSize -= ba.size(); + if (mContent.size() > mSize) + mContent.resize(mSize); + // do not return + } else if (mState == Skip) { + QByteArray ba; + + if (mSource->bytesAvailable() > mRemainingSize) { + ba = mSource->read(mRemainingSize); + } else { + ba = mSource->readAll(); + } + updateMDContext(ba); + mRemainingSize -= ba.size(); + // drop data + // do not return + } else if (mState == WaitForContinue) { + return; + } else { + qFatal("Unknown tar state"); + } + + if (mState != WaitForContinue && mState != WaitForHeader && mRemainingSize == 0) { + if (mState == Extract) { + qDebug() << "End tar process"; + // please tar + // it needs 2 zeroed blocks + char buffer[sizeof(mHeader)]; + memset(buffer, 0, sizeof(buffer)); + mProcess->write(buffer, sizeof(buffer)); + mProcess->write(buffer, sizeof(buffer)); + mProcess->waitForBytesWritten(); + mProcess->closeWriteChannel(); + mProcess->waitForFinished(-1); + mProcess->deleteLater(); + mProcess = 0; + } + mState = WaitForContinue; + + emit(endingFile(currentFileName())); + } + + } +} + +void Tar::extractContent(const QString &targetDir) +{ + if (mState == WaitForDecision) { + qDebug() << Q_FUNC_INFO; + mState = Extract; + delete mProcess; + mProcess = new QProcess(this); + mProcess->setProcessChannelMode(QProcess::ForwardedChannels); + mProcess->start("tar", QStringList() << "xvf" << "-" << "-C" << targetDir); + + // if we want to extract a file directly, the header needs to be passed to tar + if (!currentFileName().endsWith(".tar")) + mProcess->write((const char*)&mHeader, sizeof(mHeader)); + } +} + +void Tar::receiveContent() +{ + if (mState == WaitForDecision) + mState = Receive; +} + +void Tar::skipContent() +{ + if (mState == WaitForDecision) + mState = Skip; +} + +void Tar::continueContent() +{ + if (mState == WaitForContinue) { + mState = WaitForHeader; + mSize = 0; + mContent.clear(); + delete mProcess; + mProcess = 0; + } else + qDebug() << "Called" << Q_FUNC_INFO << "in wrong state"; +} + +void Tar::aboutToClose() +{ + if (mState != WaitForHeader) { + qWarning() << "unexpected close"; + } + emit finished(); +} + +bool Tar::setVerificationData(const QString &certificateFileName, const QString &mdType) +{ + if (x509) + qFatal("Setting key twice"); + + // Load key from file + BIO *i = BIO_new(BIO_s_file()); + BIO *o = BIO_new_fp(stdout,BIO_NOCLOSE); + + if ((BIO_read_filename(i, certificateFileName.toLatin1().constData()) <= 0) || + ((x509 = PEM_read_bio_X509_AUX(i, NULL, NULL, NULL)) == NULL)) { + qWarning() << "Invalid certificate, failed to read file" << certificateFileName; + return false; + } + // Showing used certificate + X509_print_ex(o, x509, XN_FLAG_COMPAT, X509_FLAG_COMPAT); + BIO_free(i); + BIO_free(o); + qDebug() << "Using verification key:" << certificateFileName; + + md = EVP_get_digestbyname(mdType.toLatin1().constData()); + if (md == NULL) + qFatal("Digest not found"); + return true; +} + +void Tar::setupMDContext() +{ + if (mdctx) + EVP_MD_CTX_destroy(mdctx); + if (!(mdctx = EVP_MD_CTX_create())) + qFatal("EVP_MD_CTX_create failed"); + + EVP_PKEY *key = X509_get_pubkey(x509); + if (key == NULL) { + qFatal("X509_get_pubkey failed"); + } + + if (md == NULL) + qFatal("No digest set"); + + if (1 != EVP_DigestVerifyInit(mdctx, NULL, md, NULL, key)) + qFatal("EVP_DigestVerifyInit failed"); +} + +void Tar::updateMDContext(QByteArray data) +{ + if (mRemainingFileBytes == 0) + qFatal("Trying to update MD context when no more bytes are expected"); + + if (mRemainingFileBytes < data.size()) + data = data.left(mRemainingFileBytes); + mRemainingFileBytes -= data.size(); + + if (!mdctx) + return; + + if (1 != EVP_DigestVerifyUpdate(mdctx, data.constData(), data.size())) + qFatal("EVP_DigestVerifyUpdate failed"); +} + +bool Tar::checkSignature(const QByteArray &signature) +{ + return 1 == EVP_DigestVerifyFinal(mdctx, (unsigned char*)signature.constData(), signature.size()); +} + +bool Tar::verifyCurrentContent(const QByteArray &signature) +{ + setupMDContext(); + if (1 != EVP_DigestVerifyUpdate(mdctx, mContent.constData(), mContent.size())) + qFatal("EVP_DigestVerifyUpdate failed"); + return checkSignature(signature); +} diff --git a/src/b2qt-update-application/tar.h b/src/b2qt-update-application/tar.h new file mode 100644 index 0000000..e3ca1d2 --- /dev/null +++ b/src/b2qt-update-application/tar.h @@ -0,0 +1,99 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc +** All rights reserved. +** For any questions to Digia, please use the contact form at +** http://qt.digia.com/ +** +** This file is part of Qt Enterprise Embedded. +** +** Licensees holding valid Qt Enterprise licenses may use this file in +** accordance with the Qt Enterprise License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. +** +** If you have questions regarding the use of this file, please use +** the contact form at http://qt.digia.com/ +** +****************************************************************************/ + +#ifndef TAR_H +#define TAR_H + +#include <QObject> +#include <openssl/x509.h> +class QIODevice; +class QProcess; +#define USTAR_HEADER_SIZE 512 + +struct ustar_header +{ +char name[100]; /* File name. Null-terminated if room. */ +char mode[8]; /* Permissions as octal string. */ +char uid[8]; /* User ID as octal string. */ +char gid[8]; /* Group ID as octal string. */ +char size[12]; /* File size in bytes as octal string. */ +char mtime[12]; /* Modification time in seconds from Jan 1, 1970, as octal string. */ +char chksum[8]; /* Sum of octets in header as octal string. */ +char typeflag; /* An enum ustar_type value. */ +char linkname[100]; /* Name of link target. Null-terminated if room. */ +char magic[6]; /* "ustar\0" */ +char version[2]; /* "00" */ +char uname[32]; /* User name, always null-terminated. */ +char gname[32]; /* Group name, always null-terminated. */ +char devmajor[8]; /* Device major number as octal string. */ +char devminor[8]; /* Device minor number as octal string. */ +char prefix[155]; /* Prefix to file name. Null-terminated if room. */ +char padding[12]; /* Pad to 512 bytes. */ +} __attribute__((packed)); + +class Tar : public QObject +{ + Q_OBJECT + +public: + Tar(QIODevice *source); + virtual ~Tar(); + + const QByteArray ¤tContent() const; + unsigned long currentSize() const; + QString currentFileName() const; + + enum State { WaitForHeader, WaitForDecision, Extract, Receive, Skip, WaitForContinue}; + bool setVerificationData(const QString &certificateFileName, const QString &md); + bool checkSignature(const QByteArray &signature); + bool verifyCurrentContent(const QByteArray &signature); + +signals: + void startingFile(const QString &name); + void endingFile(const QString &name); + void finished(); + +public slots: + void extractContent(const QString &targetDir); + void receiveContent(); + void skipContent(); + void dataIncoming(); + void continueContent(); + +private slots: + void aboutToClose(); + +private: + void setupMDContext(); + void updateMDContext(QByteArray); + + QIODevice *mSource; + qint64 mSize; + qint64 mRemainingSize; // File Size + Padding + qint64 mRemainingFileBytes; // File Size without Padding + ustar_header mHeader; + QByteArray mContent; + QProcess *mProcess; + State mState; + X509 *x509; + EVP_MD_CTX *mdctx; + const EVP_MD *md; +}; + +#endif // TAR_H diff --git a/src/b2qt-update-application/update.cpp b/src/b2qt-update-application/update.cpp new file mode 100644 index 0000000..79fea64 --- /dev/null +++ b/src/b2qt-update-application/update.cpp @@ -0,0 +1,202 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc +** All rights reserved. +** For any questions to Digia, please use the contact form at +** http://qt.digia.com/ +** +** This file is part of Qt Enterprise Embedded. +** +** Licensees holding valid Qt Enterprise licenses may use this file in +** accordance with the Qt Enterprise License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. +** +** If you have questions regarding the use of this file, please use +** the contact form at http://qt.digia.com/ +** +****************************************************************************/ + +#include "update.h" +#include "tar.h" +#include <QDebug> +#include <QCoreApplication> +#include <QProcess> +#include <QFile> + +extern QStringList mounts; +bool writeAll(const QString &fileName, const QByteArray &content); +static QStringList allowedFiles; +QByteArray readAll(const QString &fileName); +bool execute(const QString &binary, const QStringList &arguments); +bool mount(const QString &device, const QString &mountpoint, const QString &arguments = QString()); + +QMap<QString,QString> parseMetaInfo(const QByteArray &data) +{ + QMap<QString,QString> rc; + QList<QByteArray> list = data.split('\n'); + foreach (const QByteArray &ba, list) { + if (ba.isEmpty()) + continue; + int i = ba.indexOf(':'); + if (i < 0 || i == ba.size()) + qFatal("Invalid meta.info content: No colon found or no data after colon"); + + QString key = ba.left(i); + QString value = ba.mid(i+1).trimmed(); + + if (rc.contains(key)) + qFatal("Invalid meta.info content: Same key given twice"); + rc.insert(key, value); + } + if (rc.isEmpty()) + qFatal("Invalid meta.info content: No keys given"); + return rc; +} + +Update::Update(QObject *parent) + : QObject(parent) + , mTar(0) + , mSource(0) +{ + allowedFiles << "key.info" << "meta.info" << "keys.tar" << "uImage" << "rootfs.tar"; +} + +Update::~Update() +{ +} + +//# openssl dgst -sha256 -sign priv.pem file.txt > file.txt.sig +//# openssl dgst -sha256 -verify public.pem -signature file.txt.sig file.txt + +void Update::setDevice(QIODevice *source) +{ + if (!source) { + qFatal("Source device is NULL"); + return; + } + + if (mSource) { + qFatal("Source already set"); + return; + } + + mSource = source; + mTar = new Tar(mSource); + + connect(mTar, &Tar::startingFile, this, &Update::tarStartingFile); + connect(mTar, &Tar::endingFile, this, &Update::tarEndingFile); + connect(mTar, &Tar::finished, this, &Update::tarFinished); +} + +void Update::tarStartingFile(const QString &name) +{ + if (name == "key.info") { + mTar->receiveContent(); + } else if (name.endsWith(".sig")) { + mTar->receiveContent(); + mCurrentFile = name; + mCurrentFile.chop(4); + + bool found = false; + while (!allowedFiles.isEmpty()) { + QString n = allowedFiles.takeFirst(); + if (n == mCurrentFile) { + found = true; + break; + } + } + + if (!found) + qFatal("File not allowed"); + + } else if (mCurrentFile != name) { + qFatal("Invalid file order"); + } else { + if (mCurrentFile == "meta.info") { + mTar->receiveContent(); + } else if (mCurrentFile == "uImage") { + mount(QString(), "/mnt/boot", "remount,rw"); + mTar->extractContent("/mnt/boot"); + } else if (mCurrentFile == "rootfs.tar") { + qDebug() << "Formatting rootfs"; + execute("mkfs." + mMetaInfo["filesystemType"], QStringList() << mMetaInfo["rootDevice"]); + mount(mMetaInfo["rootDevice"], "/mnt/root"); + mTar->extractContent("/mnt/root"); + } else if (mCurrentFile == "keys.tar") { + mount(QString(), "/mnt/boot", "remount,rw"); + execute("rm", QStringList() << "-r" << "/mnt/boot/update/keys/"); + execute("mkdir", QStringList() << "/mnt/boot/update/keys/"); + mTar->extractContent("/mnt/boot/update/keys"); + } else { + qFatal("Unexpected file in update data"); + } + } +} + +void Update::tarEndingFile(const QString &name) +{ + if (name == "meta.info") { + QMap<QString,QString> map = parseMetaInfo(mTar->currentContent()); + + // Some rough sanity checks + if (!map.contains("version")) + qFatal("No version information found" ); + if (map["version"] != "1") + qFatal("Invalid update version"); + if (!map.contains("platform") || map["platform"].isEmpty()) + qFatal("Platform information missing"); + if (map["platform"] != readAll("/mnt/boot/update/platform").trimmed()) + qFatal("Invalid platform information"); + else + qDebug() << "Platform matches"; + if (!map.contains("rootDevice") || map["rootDevice"].isEmpty()) + qFatal("Root device information missing"); + if (!QFile::exists(map["rootDevice"])) + qFatal("Root device not found"); + if (!map.contains("key") || map["key"].isEmpty() || map["key"].contains('/')) + qFatal("Invalid or no key fingerprint"); + if (!map.contains("digest") || map["digest"].isEmpty()) + qFatal("Invalid digest type"); + if (!map.contains("filesystemType") || map["filesystemType"].isEmpty() || map["filesystemType"].contains(' ') || map["filesystemType"].contains('/')) + qFatal("Invalid filesystem type"); + + mMetaInfo = map; + + if (!mTar->setVerificationData("/mnt/boot/update/keys/" + mMetaInfo["key"], mMetaInfo["digest"])) + qFatal("Loading of certificate failed"); + + // Now verify the meta.info itself + if (mTar->verifyCurrentContent(mCurrentSignature)) { + qDebug() << "meta.info VERIFIED OK"; + } else { + qDebug() << "meta.info VERIFICATION FAILED"; + qFatal("VERIFICATION FAILED"); + return; + } + + } else if (name.endsWith(".sig")) { + mCurrentSignature = mTar->currentContent(); + qDebug() << "Received signature with" << mCurrentSignature.size() << "bytes."; + } else { + if (mTar->checkSignature(mCurrentSignature)) { + qDebug() << "VERIFIED OK"; + // FIXME: do something here + } else { + qDebug() << "VERIFICATION FAILED"; + qFatal("VERIFICATION FAILED"); + } + } + + mTar->continueContent(); +} + +void Update::tarFinished() +{ + qDebug() << "Update finished"; + mount(QString(), "/mnt/boot", "remount,rw"); + if (!writeAll("/mnt/boot/update/state", "t")) + qFatal("Could not set state to testing"); + mount(QString(), "/mnt/boot", "remount,ro"); + qApp->exit(); +} diff --git a/src/b2qt-update-application/update.h b/src/b2qt-update-application/update.h new file mode 100644 index 0000000..830be56 --- /dev/null +++ b/src/b2qt-update-application/update.h @@ -0,0 +1,50 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc +** All rights reserved. +** For any questions to Digia, please use the contact form at +** http://qt.digia.com/ +** +** This file is part of Qt Enterprise Embedded. +** +** Licensees holding valid Qt Enterprise licenses may use this file in +** accordance with the Qt Enterprise License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. +** +** If you have questions regarding the use of this file, please use +** the contact form at http://qt.digia.com/ +** +****************************************************************************/ + +#ifndef UPDATE_H +#define UPDATE_H + +#include <QObject> +#include <QMap> +class Tar; +class QIODevice; + +class Update : public QObject +{ + Q_OBJECT + +public: + Update(QObject *parent = 0); + virtual ~Update(); + void setDevice(QIODevice *source); + +private slots: + void tarStartingFile(const QString &name); + void tarEndingFile(const QString &name); + void tarFinished(); + +private: + Tar *mTar; + QIODevice *mSource; + QString mCurrentFile; + QByteArray mCurrentSignature; + QMap<QString, QString> mMetaInfo; +}; + +#endif // UPDATE_H diff --git a/src/b2qt-update-util/.gitignore b/src/b2qt-update-util/.gitignore new file mode 100644 index 0000000..67f0098 --- /dev/null +++ b/src/b2qt-update-util/.gitignore @@ -0,0 +1,4 @@ +Makefile +b2qt-update-util +*.o +moc_*.cpp diff --git a/src/b2qt-update-util/b2qt-update-util.pro b/src/b2qt-update-util/b2qt-update-util.pro new file mode 100644 index 0000000..0b5ab32 --- /dev/null +++ b/src/b2qt-update-util/b2qt-update-util.pro @@ -0,0 +1,23 @@ +QT = core +SOURCES = main.cpp +INSTALLS += target +target.path = /usr/bin + +# Find out git hash +unix:system(which git):HAS_GIT=TRUE +win32:system(where git.exe):HAS_GIT=TRUE +contains(HAS_GIT, TRUE) { + GIT_HASH=$$system(git log -1 --format=%H) + !system(git diff-index --quiet HEAD): GIT_HASH="$$GIT_HASH-dirty" + GIT_VERSION=$$system(git describe --tags --exact-match) + isEmpty(GIT_VERSION) : GIT_VERSION="unknown" +} else { + GIT_HASH="unknown" + GIT_VERSION="unknown" +} + +isEmpty(GIT_VERSION) : error("No suitable tag found") +isEmpty(GIT_HASH) : error("No hash available") + +DEFINES+="GIT_HASH=\\\"$$GIT_HASH\\\"" +DEFINES+="GIT_VERSION=\\\"$$GIT_VERSION\\\"" diff --git a/src/b2qt-update-util/main.cpp b/src/b2qt-update-util/main.cpp new file mode 100644 index 0000000..137cbd5 --- /dev/null +++ b/src/b2qt-update-util/main.cpp @@ -0,0 +1,144 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc +** All rights reserved. +** For any questions to Digia, please use the contact form at +** http://qt.digia.com/ +** +** This file is part of Qt Enterprise Embedded. +** +** Licensees holding valid Qt Enterprise licenses may use this file in +** accordance with the Qt Enterprise License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. +** +** If you have questions regarding the use of this file, please use +** the contact form at http://qt.digia.com/ +** +****************************************************************************/ + +#include <QCoreApplication> +#include <sys/reboot.h> +#include <QFile> +#include <QDebug> +#include <unistd.h> +#include <QStringList> +#include <QProcess> + +bool mount_boot() +{ + // In some cases the boot partition is already mounted somewhere else. + // Mounting it again is no problem but data loss will happen. + QProcess::execute("umount", QStringList() << "/dev/mmcblk0p1"); // Ignore return value + return QProcess::execute("mount", QStringList() << "/dev/mmcblk0p1" << "/boot") == 0; +} + +bool umount_boot() +{ + return QProcess::execute("umount", QStringList() << "/boot") == 0; +} + +bool finish_update() +{ + if (!mount_boot()) { + qWarning() << "Could not mount /boot"; + return false; + } + QFile f("/boot/update/state"); + if (!f.open(QFile::WriteOnly)) { + qWarning() << "Could not open file for writing"; + return false; + } + if (f.write("v") != 1) { + qWarning() << "Write error"; + return false; + } + fsync(f.handle()); + f.close(); + if (!umount_boot()) { + qWarning() << "Could not unmount /boot"; + return false; + } + return true; +} + +int start_update(const QString &source) +{ + if (!mount_boot()) { + qWarning() << "Could not mount /boot"; + return false; + } + { + QFile f("/boot/update/source"); + if (!f.open(QFile::WriteOnly)) { + qWarning() << "Could not open file for writing"; + return false; + } + QByteArray ba = source.toUtf8(); + + if (f.write(ba) != ba.size()) { + qWarning() << "Write error"; + return false; + } + fsync(f.handle()); + f.close(); + } + + { + QFile f("/boot/update/state"); + if (!f.open(QFile::WriteOnly)) { + qWarning() << "Could not open file for writing"; + return false; + } + if (f.write("u") != 1) { + qWarning() << "Write error"; + return false; + } + fsync(f.handle()); + f.close(); + } + if (!umount_boot()) { + qWarning() << "Could not unmount /boot"; + return false; + } + reboot(RB_AUTOBOOT); + return true; +} + +void usage() +{ + fprintf(stderr, + "b2qt-update [start|finish] [...]\n" + " start: For internet update provide a http URL as parameter\n" + " finish: An update\n" + ); +} + +int main(int argc, char **argv) +{ + QCoreApplication app(argc, argv); + QStringList args = app.arguments(); + + args.removeFirst(); + if (args.size() == 0) { + usage(); + return 1; + } + + QString arg = args.takeFirst(); + + if (arg == "finish") + return finish_update(); + else if (arg == "start") { + if (args.size() == 0) + return start_update(QString()); + else + return start_update(args.takeFirst()); + } else if (arg == "version") { + printf("Version %s, SHA1 %s\n", GIT_VERSION, GIT_HASH); + return 0; + } else { + usage(); + return 1; + } +} diff --git a/src/doc/config/b2qt.qdocconf b/src/doc/config/b2qt.qdocconf index 33f4e7d..1e499dc 100644 --- a/src/doc/config/b2qt.qdocconf +++ b/src/doc/config/b2qt.qdocconf @@ -9,7 +9,8 @@ description = Qt Enterprise Embedded Documentation version = 3.2.0 sourcedirs = ../src \ - ../../imports/wifi + ../../imports/wifi \ + ../../imports/utils headerdirs = ../../imports/wifi diff --git a/src/doc/src/devices/qtee-bd-sl-imx6.qdoc b/src/doc/src/devices/qtee-bd-sl-imx6.qdoc index 54e4b64..f81876c 100644 --- a/src/doc/src/devices/qtee-bd-sl-imx6.qdoc +++ b/src/doc/src/devices/qtee-bd-sl-imx6.qdoc @@ -85,12 +85,12 @@ \li \b{\B2QA} \badcode cd <INSTALL_DIR> - ./Boot2Qt-3.x/generic-4.2-eAndroid/images/iMX6/deploy.sh /dev/<device_name> + ./Boot2Qt-4.x/generic-4.2-eAndroid/images/iMX6/deploy.sh /dev/<device_name> \endcode \li \b{\B2QL} \badcode cd <INSTALL_DIR> - sudo ./Boot2Qt-3.x/iMX6-eLinux/images/deploy.sh /dev/<device_name> + sudo ./Boot2Qt-4.x/iMX6-eLinux/images/deploy.sh /dev/<device_name> \endcode \endlist diff --git a/src/doc/src/devices/qtee-beagleboard-xm.qdoc b/src/doc/src/devices/qtee-beagleboard-xm.qdoc index 3d54c15..cee898c 100644 --- a/src/doc/src/devices/qtee-beagleboard-xm.qdoc +++ b/src/doc/src/devices/qtee-beagleboard-xm.qdoc @@ -50,7 +50,7 @@ \badcode cd <INSTALL_DIR> - sudo Boot2Qt-3.x/beagleboard-eLinux/images/deploy.sh /dev/<device_name> + sudo Boot2Qt-4.x/beagleboard-eLinux/images/deploy.sh /dev/<device_name> \endcode After the image has been deployed, power on the device and check that diff --git a/src/doc/src/devices/qtee-beaglebone-black.qdoc b/src/doc/src/devices/qtee-beaglebone-black.qdoc index 5b0b700..8bd7e73 100644 --- a/src/doc/src/devices/qtee-beaglebone-black.qdoc +++ b/src/doc/src/devices/qtee-beaglebone-black.qdoc @@ -76,12 +76,12 @@ \li \b{\B2QA} \badcode cd <INSTALL_DIR> - ./Boot2Qt-3.x/generic-4.2-eAndroid/images/beaglebone/deploy.sh + ./Boot2Qt-4.x/generic-4.2-eAndroid/images/beaglebone/deploy.sh \endcode \li \b{\B2QL} \badcode cd <INSTALL_DIR> - sudo ./Boot2Qt-3.x/beaglebone-eLinux/images/deploy.sh /dev/<device_name> + sudo ./Boot2Qt-4.x/beaglebone-eLinux/images/deploy.sh /dev/<device_name> \endcode \endlist diff --git a/src/doc/src/devices/qtee-nexus-7.qdoc b/src/doc/src/devices/qtee-nexus-7.qdoc index 30cbc82..c2e7c6a 100644 --- a/src/doc/src/devices/qtee-nexus-7.qdoc +++ b/src/doc/src/devices/qtee-nexus-7.qdoc @@ -79,12 +79,12 @@ \li \b{Nexus 7 (2013):} \badcode cd <INSTALL_DIR> - ./Boot2Qt-3.x/generic-X.Y-eAndroid/images/nexus7v2/deploy.sh + ./Boot2Qt-4.x/generic-X.Y-eAndroid/images/nexus7v2/deploy.sh \endcode \li \b{Nexus 7 (2012):} \badcode cd <INSTALL_DIR> - ./Boot2Qt-3.x/generic-X.Y-eAndroid/images/nexus7/deploy.sh + ./Boot2Qt-4.x/generic-X.Y-eAndroid/images/nexus7/deploy.sh \endcode \endlist diff --git a/src/doc/src/devices/qtee-raspberry-pi.qdoc b/src/doc/src/devices/qtee-raspberry-pi.qdoc index 10a2b98..a4e0a69 100644 --- a/src/doc/src/devices/qtee-raspberry-pi.qdoc +++ b/src/doc/src/devices/qtee-raspberry-pi.qdoc @@ -54,7 +54,7 @@ \badcode cd <INSTALL_DIR> - sudo Boot2Qt-3.x/raspberrypi-eLinux/images/deploy.sh /dev/<device_name> + sudo Boot2Qt-4.x/raspberrypi-eLinux/images/deploy.sh /dev/<device_name> \endcode After the image has been deployed, insert the SD card, power on the device and check that diff --git a/src/doc/src/devices/qtee-sabre-sd-imx6quad.qdoc b/src/doc/src/devices/qtee-sabre-sd-imx6quad.qdoc index ef368d2..ad845d3 100644 --- a/src/doc/src/devices/qtee-sabre-sd-imx6quad.qdoc +++ b/src/doc/src/devices/qtee-sabre-sd-imx6quad.qdoc @@ -49,7 +49,7 @@ \badcode cd <INSTALL_DIR> - sudo Boot2Qt-3.x/imx6qsabresd-eLinux/images/deploy.sh /dev/<device_name> + sudo Boot2Qt-4.x/imx6qsabresd-eLinux/images/deploy.sh /dev/<device_name> \endcode After the image has been deployed, insert the SD card, power on the device and check that diff --git a/src/doc/src/devices/qtee-toradex-apalis.qdoc b/src/doc/src/devices/qtee-toradex-apalis.qdoc index df98ba3..6979253 100644 --- a/src/doc/src/devices/qtee-toradex-apalis.qdoc +++ b/src/doc/src/devices/qtee-toradex-apalis.qdoc @@ -49,21 +49,32 @@ \badcode cd <INSTALL_DIR> - sudo Boot2Qt-3.x/apalis-imx6-eLinux/images/deploy.sh /dev/<device_name> + sudo Boot2Qt-4.x/apalis-imx6-eLinux/images/deploy.sh /dev/<device_name> \endcode By default, the Toradex Apalis iMX6 boots from its internal eMMC. In order to boot from - the external SD card, the U-Boot environment needs to be updated. Connect a serial cable + the external SD card, the U-Boot needs to be updated. Connect a serial cable to the device and enter into the U-Boot environment by pressing any key before the autoboot. - Enter following lines into U-Boot: + Enter following commands into U-Boot: \badcode - setenv bootcmd 'run sdboot ; echo sdboot failed ; run emmcboot ; echo ; echo emmcboot failed ; run nfsboot ; echo ; echo nfsboot failed ; usb start ;setenv stdout serial,vga ; setenv stdin serial,usbkbd' - setenv sdboot 'run setup; setenv bootargs ${defargs} ${sdargs} ${setupargs} ${vidargs};echo Booting from SD card in 4bit slot...; fatload mmc 2:1 10800000 uImage && bootm 10800000' - setenv sdargs 'ip=off root=/dev/mmcblk1p2 rw,noatime rootfstype=ext3 rootwait' + setenv drive 2 + setenv setupdate 'fatload mmc ${drive}:1 ${loadaddr} flash_mmc.img; source' + + run setupdate + run update_uboot + \endcode + + Reset or power cycle the device to start the new U-Boot. + To reset the U-Boot environment to new default values, enter the following commands + in the U-Boot command line + + \badcode + env default -a saveenv \endcode + New U-Boot command are now stored into the device, and you can start \B2Q. For more information about the boot process on Toredex Apalis iMX6, see \l{http://developer.toradex.com/software-resources/arm-family/linux/linux-booting}{Toradex Linux Booting} diff --git a/src/doc/src/qtee-custom-embedded-linux.qdoc b/src/doc/src/qtee-custom-embedded-linux.qdoc index bf26501..a33e41a 100644 --- a/src/doc/src/qtee-custom-embedded-linux.qdoc +++ b/src/doc/src/qtee-custom-embedded-linux.qdoc @@ -60,11 +60,18 @@ \section1 Setting Up Yocto Build Environment - Run the setup script that initializes the Yocto environment: + Run the setup script that initializes the Yocto environment. Using Raspberry Pi as + an example: \badcode cd <BuildDir> - <INSTALL_DIR>/Boot2Qt-3.x/sources/b2qt-yocto-meta/b2qt-init-build-env . + <INSTALL_DIR>/Boot2Qt-4.x/sources/b2qt-yocto-meta/b2qt-init-build-env init --device raspberrypi + \endcode + + For more command line options, see: + + \badcode + <INSTALL_DIR>/Boot2Qt-4.x/sources/b2qt-yocto-meta/b2qt-init-build-env help \endcode \section1 Building the Image and Toolchain @@ -74,9 +81,8 @@ an example: \badcode - export TEMPLATECONF=meta-b2qt/conf export MACHINE=raspberrypi - source poky/oe-init-build-env build-raspberrypi + source ./setup-environment.sh \endcode The following table lists the \c MACHINE values for our reference platforms: @@ -128,7 +134,7 @@ script. Using Raspberry Pi as an example: \badcode - <INSTALL_DIR>/Boot2Qt-3.x/sources/b2qt-build-scripts/embedded-common/init_build_env.sh <INSTALL_DIR>/Boot2Qt-3.x/sources/b2qt-build-scripts/embedded-linux/config.raspberrypi + <INSTALL_DIR>/Boot2Qt-4.x/sources/b2qt-build-scripts/embedded-common/init_build_env.sh <INSTALL_DIR>/Boot2Qt-4.x/sources/b2qt-build-scripts/embedded-linux/config.raspberrypi \endcode \note You can use the same build directory for Qt and the Yocto image. @@ -137,12 +143,12 @@ You can use following scripts to build different parts of the \B2Q stack. \badcode - <INSTALL_DIR>/Boot2Qt-3.x/sources/b2qt-build-scripts/embedded-linux/build_qt.sh - <INSTALL_DIR>/Boot2Qt-3.x/sources/b2qt-build-scripts/embedded-linux/build_extras.sh - <INSTALL_DIR>/Boot2Qt-3.x/sources/b2qt-build-scripts/embedded-linux/build_image.sh + ./build_qt.sh + ./build_extras.sh + ./build_image.sh \endcode - After \e {embedded-linux/build_image.sh} has finished, you can flash the device with + After \e {build_image.sh} has finished, you can flash the device with the updated image located in the build folder. \section1 Configuring Qt Creator @@ -151,7 +157,7 @@ developing for your device. The following script does this for you. \badcode - <INSTALL_DIR>/Boot2Qt-3.x/sources/b2qt-build-scripts/embedded-common/setup_qtcreator.sh + <INSTALL_DIR>/Boot2Qt-4.x/sources/b2qt-build-scripts/embedded-common/setup_qtcreator.sh \endcode This will set up a new kit in Qt Creator, using the toolchain and Qt from diff --git a/src/doc/src/qtee-install-guide.qdoc b/src/doc/src/qtee-install-guide.qdoc index 4fba4fd..6ffc8e6 100644 --- a/src/doc/src/qtee-install-guide.qdoc +++ b/src/doc/src/qtee-install-guide.qdoc @@ -61,9 +61,9 @@ download it from \l{https://www.virtualbox.org/wiki/Linux_Downloads} or install it via distribution tools. - If you have older Ubuntu versions such as 12.04 you have to install a newer - version of VirtualBox than your distribution does provide. - You have to add a foreign package source: + If you are running an older Ubuntu system such as 12.04, the version of + VirtualBox provided by the distribution is not recent enough. To install a + newer version, first add a foreign package source: \badcode wget -q http://download.virtualbox.org/virtualbox/debian/oracle_vbox.asc -O- | sudo apt-key add - @@ -71,7 +71,7 @@ sudo apt-get update \endcode - Now install VirtualBox on your computer + Then, install VirtualBox on your computer: \badcode sudo apt-get install virtualbox-4.3 \endcode diff --git a/src/doc/src/qtee-qml-reference.qdoc b/src/doc/src/qtee-qml-reference.qdoc index 6ba5900..7ee86a3 100644 --- a/src/doc/src/qtee-qml-reference.qdoc +++ b/src/doc/src/qtee-qml-reference.qdoc @@ -27,6 +27,10 @@ \annotatedlist qtee-qmlmodules + \section1 B2Qt Utils Module + + \annotatedlist utils-qmltypes + \section1 WiFi Module \annotatedlist wifi-qmltypes diff --git a/src/doppelganger/appops.cpp b/src/doppelganger/appops.cpp new file mode 100644 index 0000000..adeddcc --- /dev/null +++ b/src/doppelganger/appops.cpp @@ -0,0 +1,50 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc +** All rights reserved. +** For any questions to Digia, please use the contact form at +** http://qt.digia.com/ +** +** This file is part of Qt Enterprise Embedded. +** +** Licensees holding valid Qt Enterprise licenses may use this file in +** accordance with the Qt Enterprise License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. +** +** If you have questions regarding the use of this file, please use +** the contact form at http://qt.digia.com/ +** +****************************************************************************/ + +#include "appops.h" + +#if Q_ANDROID_VERSION_MAJOR > 4 || (Q_ANDROID_VERSION_MAJOR == 4 && Q_ANDROID_VERSION_MINOR >= 4) +#include <binder/IServiceManager.h> +#include <binder/IAppOpsCallback.h> + +class AppOpsPrivate : public android::BnAppOpsCallback +{ +public: + virtual void opChanged(int32_t, const android::String16&) + { + + } + + virtual android::status_t onTransact(uint32_t, + const android::Parcel&, + android::Parcel*, + uint32_t flags = 0) + { + (void)flags; + return android::OK; + } +}; +#endif + +void AppOps::instantiate() +{ +#if Q_ANDROID_VERSION_MAJOR > 4 || (Q_ANDROID_VERSION_MAJOR == 4 && Q_ANDROID_VERSION_MINOR >= 4) + android::defaultServiceManager()->addService(android::String16("appops"), new AppOpsPrivate); +#endif +} diff --git a/src/doppelganger/appops.h b/src/doppelganger/appops.h new file mode 100644 index 0000000..5e765a6 --- /dev/null +++ b/src/doppelganger/appops.h @@ -0,0 +1,29 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc +** All rights reserved. +** For any questions to Digia, please use the contact form at +** http://qt.digia.com/ +** +** This file is part of Qt Enterprise Embedded. +** +** Licensees holding valid Qt Enterprise licenses may use this file in +** accordance with the Qt Enterprise License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. +** +** If you have questions regarding the use of this file, please use +** the contact form at http://qt.digia.com/ +** +****************************************************************************/ + +#ifndef APPOPS_H +#define APPOPS_H + +class AppOps +{ +public: + static void instantiate(); +}; + +#endif // APPOPS_H diff --git a/src/doppelganger/doppelganger.pro b/src/doppelganger/doppelganger.pro index 40c7650..fa91169 100644 --- a/src/doppelganger/doppelganger.pro +++ b/src/doppelganger/doppelganger.pro @@ -10,11 +10,13 @@ TEMPLATE = app SOURCES += main.cpp \ permissioncontroller.cpp \ schedulingpolicyservice.cpp \ - powermanager.cpp + powermanager.cpp \ + appops.cpp HEADERS += \ permissioncontroller.h \ schedulingpolicyservice.h \ - powermanager.h + powermanager.h \ + appops.h load(qt_tool) diff --git a/src/doppelganger/main.cpp b/src/doppelganger/main.cpp index c548083..3e86a66 100644 --- a/src/doppelganger/main.cpp +++ b/src/doppelganger/main.cpp @@ -21,6 +21,7 @@ #include "permissioncontroller.h" #include "schedulingpolicyservice.h" #include "powermanager.h" +#include "appops.h" using namespace android; @@ -30,5 +31,6 @@ int main(int, char *[]) SchedulingPolicyService::instantiate(); PermissionController::instantiate(); PowerManager::instantiate(); + AppOps::instantiate(); IPCThreadState::self()->joinThreadPool(); } diff --git a/src/imports/utils/plugin.cpp b/src/imports/utils/plugin.cpp index cc96fb0..4012e96 100644 --- a/src/imports/utils/plugin.cpp +++ b/src/imports/utils/plugin.cpp @@ -16,32 +16,135 @@ ** the contact form at http://www.qt.io ** ****************************************************************************/ -#include <QtDroidUtils/qdroidutils.h> +#include <b2qtdevice.h> #include <QtQml> +/*! + \qmlmodule B2Qt.Utils 1.0 + \title B2Qt Utils Module + \ingroup qtee-qmlmodules + \brief A collection of utility functions, accessible from QML. + + Provides various utility functions for controlling an embedded + device, such as display brightness, IP address and hostname, and + device shutdown/reboot. + + Import the module as follows: + + \badcode + import B2Qt.Utils 1.0 + \endcode + + This will give you access to the singleton QML type B2QtDevice. + + \note Some functions are currently only implemented for one of + the platforms. + + \section1 QML Types +*/ + +/*! + \qmltype B2QtDevice + \inqmlmodule B2Qt.Utils + \ingroup utils-qmltypes + \brief Singleton QML type providing access to utility functions. + + B2QtDevice QML type is the interface to various utility + functions. + + There is no need to create an instance of this object. To use it, + simply import the \c {B2Qt.Utils} module: + + \qml + import B2Qt.Utils 1.0 + + Text { + text: qsTr("IP Address:") + B2QtDevice.ipAddress + } + \endqml + + \note Some functions are currently only implemented for one of + the platforms. +*/ + +/*! + \qmlmethod B2Qt.Utils::B2QtDevice::reboot() + + Reboots the system. Does not return. + + \sa powerOff() +*/ + +/*! + \qmlmethod B2Qt.Utils::B2QtDevice::powerOff() + + Shuts down the system. Does not return. + + \sa reboot() +*/ + +/*! + \qmlproperty int B2Qt.Utils::B2QtDevice::masterVolume + + This property holds the master volume of the device. + The volume can range from \c 0 to \c 100 and is linear. + Changing the master volume will affect all audio streams. + + \note Currently implemented only for \B2QA. +*/ + +/*! + \qmlproperty int B2Qt.Utils::B2QtDevice::displayBrightness + This property holds the display brightness (the intensity of the backlight). + The value is in the range from \c 0 to \c 255, where 255 is the maximum + brightness, and 0 is the minimum (typically, the backlight is turned off). + + \note Currently implemented only for \B2QA. +*/ + +/*! + \qmlproperty string B2Qt.Utils::B2QtDevice::ipAddress + \readonly + + This property holds the current IP address(es) of the device + for all active network interfaces. If multiple IP addresses are defined, + this property holds a comma-separated list. The localhost (loopback) + IP addresses are omitted. + + \sa hostname +*/ + +/*! + \qmlproperty string B2Qt.Utils::B2QtDevice::hostname + + This property holds the current hostname of the device. + + \sa ipAddress +*/ + static QObject *module_api_factory(QQmlEngine *engine, QJSEngine *scriptEngine) { Q_UNUSED(engine) Q_UNUSED(scriptEngine) - QDroidUtils *api = new QDroidUtils(); + B2QtDevice *api = new B2QtDevice(); return api; } -class QDroidUtilsPlugin : public QQmlExtensionPlugin +class B2QtUtilsPlugin : public QQmlExtensionPlugin { Q_OBJECT Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface") public: - QDroidUtilsPlugin() + B2QtUtilsPlugin() { } void registerTypes(const char *uri) { - Q_ASSERT(QLatin1String(uri) == "QtDroid.Utils"); - qmlRegisterSingletonType<QDroidUtils>(uri, 1, 0, "DroidUtils", module_api_factory); + Q_ASSERT(QLatin1String(uri) == "B2Qt.Utils"); + qmlRegisterSingletonType<B2QtDevice>(uri, 1, 0, "B2QtDevice", module_api_factory); } }; diff --git a/src/imports/utils/qmldir b/src/imports/utils/qmldir index 6db1b0f..e5e0b31 100644 --- a/src/imports/utils/qmldir +++ b/src/imports/utils/qmldir @@ -1,3 +1,3 @@ -module QtDroid.Utils -plugin qtdroidutilsplugin +module B2Qt.Utils +plugin b2qtutilsplugin typeinfo plugins.qmltypes diff --git a/src/imports/utils/utils.pro b/src/imports/utils/utils.pro index a589b46..a580643 100644 --- a/src/imports/utils/utils.pro +++ b/src/imports/utils/utils.pro @@ -1,14 +1,10 @@ CXX_MODULE = qml -TARGET = qtdroidutilsplugin -TARGETPATH = QtDroid/Utils +TARGET = b2qtutilsplugin +TARGETPATH = B2Qt/Utils IMPORT_VERSION = 1.0 -QT += qml +QT += qml b2qtutils SOURCES += plugin.cpp -### kludge -INCLUDEPATH += $$PWD/../../../include -LIBS += -L$$PWD/../../../lib -lQt5DroidUtils - load(qml_plugin) diff --git a/src/qt_hw_init/main.cpp b/src/qt_hw_init/main.cpp index e6411ac..15cc807 100644 --- a/src/qt_hw_init/main.cpp +++ b/src/qt_hw_init/main.cpp @@ -16,33 +16,15 @@ ** the contact form at http://www.qt.io ** ****************************************************************************/ -#include "../utils/qdroidutils.h" -#include <QDebug> - -static void setMaxVolume(QDroidUtils *utils) -{ - // Set the audio orientation to something to force the HW driver to reconfigure - // audio routing (workaround for bug on Nexus 7) - utils->setOrientationForAudioSystem(QDroidUtils::LandscapeAudioOrientation); - utils->setMasterVolume(100); - utils->setMasterMute(false); - utils->setStreamVolume(QDroidUtils::SystemAudioStream, 100); - utils->setStreamVolume(QDroidUtils::MusicAudioStream, 100); - utils->setStreamVolume(QDroidUtils::NotificationAudioStream, 100); - utils->setStreamVolume(QDroidUtils::EnforcedAudibleAudioStream, 100); -} - -static void setDisplayBrightness(QDroidUtils *utils) -{ - utils->setDisplayBrightness(255); -} +#include "b2qtdevice.h" int main(int, char *[]) { - QDroidUtils utils; + B2QtDevice device; + + device.initAudio(); - setMaxVolume(&utils); - setDisplayBrightness(&utils); + device.setDisplayBrightness(255); return 0; } diff --git a/src/qt_hw_init/qt_hw_init.pro b/src/qt_hw_init/qt_hw_init.pro index 879e450..4784926 100644 --- a/src/qt_hw_init/qt_hw_init.pro +++ b/src/qt_hw_init/qt_hw_init.pro @@ -1,10 +1,8 @@ TEMPLATE = app TARGET = qt_hw_init QT -= gui -QT += droidutils +QT += b2qtutils SOURCES += main.cpp -LIBS += -lQt5DroidUtils - load(qt_tool) diff --git a/src/src.pro b/src/src.pro index fe2f463..e8ecbaf 100644 --- a/src/src.pro +++ b/src/src.pro @@ -5,6 +5,7 @@ SUBDIRS += \ imports \ doc \ plugins \ + b2qt-update-util android: SUBDIRS += doppelganger qt_hw_init qconnectivity diff --git a/src/utils/qdroidutils.cpp b/src/utils/b2qtdevice.cpp index d852a2e..d7a7cd2 100644 --- a/src/utils/qdroidutils.cpp +++ b/src/utils/b2qtdevice.cpp @@ -16,7 +16,7 @@ ** the contact form at http://www.qt.io ** ****************************************************************************/ -#include "qdroidutils.h" +#include "b2qtdevice.h" #include <unistd.h> #include <QDebug> #include <math.h> @@ -32,61 +32,167 @@ #include <utils/String8.h> #endif +// When we can't query directly, at least remember what we have set it to +static quint8 knownBrightness = 255; + +B2QtDevice::B2QtDevice(QObject *parent) + : QObject(parent) +{ +} + +B2QtDevice::~B2QtDevice() +{ +} + /*! * Reboots the system. Does not return. * - * \sa powerOffSystem() + * \sa powerOff() */ -void QDroidUtils::rebootSystem() +void B2QtDevice::reboot() { sync(); - reboot(RB_AUTOBOOT); + ::reboot(RB_AUTOBOOT); qWarning("reboot returned"); } + /*! * Shuts down the system. Does not return. * - * \sa rebootSystem() + * \sa reboot() */ -void QDroidUtils::powerOffSystem() +void B2QtDevice::powerOff() { sync(); - reboot(RB_POWER_OFF); + ::reboot(RB_POWER_OFF); qWarning("powerOff returned"); } -void QDroidUtils::setOrientationForAudioSystem(AudioOrientation orientation) + +/*! + * Sets the display brightness (i.e. the intensity of the backlight) + * to \a value. A value of 255 requests maximum brightness, while 0 requests + * minimum (typically, the backlight turned off). + * + * Returns true on success. + */ +bool B2QtDevice::setDisplayBrightness(quint8 value) { #ifdef Q_OS_ANDROID_NO_SDK - QString orientationString = QStringLiteral("undefined"); - switch (orientation) { - case LandscapeAudioOrientation: - orientationString = QStringLiteral("landscape"); - break; - case PortraitAudioOrientation: - orientationString = QStringLiteral("portrait"); - break; - case SquareAudioOrientation: - orientationString = QStringLiteral("square"); - break; - default: - break; + const struct hw_module_t* module = 0; + if (hw_get_module(LIGHTS_HARDWARE_MODULE_ID, &module)) + return false; + if (!module || !module->methods || !module->methods->open) + return false; + + struct light_device_t* device = 0; + if (module->methods->open(module, LIGHT_ID_BACKLIGHT, (struct hw_device_t**)&device)) + return false; + if (!device || !device->set_light || !device->common.close) + return false; + + struct light_state_t state; + memset(&state, 0, sizeof(light_state_t)); + state.color = 0xff000000 | (value << 16) | (value << 8) | value; + if (!device->set_light(device, &state)) + return false; + + device->common.close(&device->common); + knownBrightness = value; + emit displayBrightnessChanged(value); + return true; +#else + Q_UNUSED(value); + return false; +#endif +} + + +/*! + * Returns the current backlight intensity. + * \sa setDisplayBrightness + */ +quint8 B2QtDevice::displayBrightness() const +{ + QFile sysFile(QStringLiteral("/sys/class/leds/lcd-backlight/brightness")); + if (sysFile.open(QIODevice::ReadOnly | QIODevice::Unbuffered)) { + bool ok = false; + int sysVal = sysFile.read(3).simplified().toInt(&ok); + if (ok) + knownBrightness = qBound(0, sysVal, 255); } - android::AudioSystem::setParameters(0, android::String8(QStringLiteral("orientation=%2") - .arg(orientationString).toLatin1().constData())); + return knownBrightness; +} + + +/*! + * Gets the current IP address(es) of the device + */ +QString B2QtDevice::getIPAddress() const +{ + QStringList addresses; + foreach (const QNetworkInterface &interface, QNetworkInterface::allInterfaces()) { + QNetworkInterface::InterfaceFlags flags = interface.flags(); + if (flags.testFlag(QNetworkInterface::IsRunning) && !flags.testFlag(QNetworkInterface::IsLoopBack)) { + foreach (const QNetworkAddressEntry &entry, interface.addressEntries()) + addresses.append(entry.ip().toString().split('%').first()); + } + } + return addresses.join(QStringLiteral(", ")); +} + + +/*! + * Gets the current hostname of the device + */ +QString B2QtDevice::hostname() const +{ + QString name; +#ifdef Q_OS_ANDROID_NO_SDK + char prop_value[PROPERTY_VALUE_MAX]; + int len = property_get("net.hostname", prop_value, 0); + if (len) + name = QString::fromLocal8Bit(prop_value, len); +#else + name = QHostInfo::localHostName(); #endif + return name; } + +/*! + * Sets new hostname for the device + */ +bool B2QtDevice::setHostname(const QString &name) +{ +#ifdef Q_OS_ANDROID_NO_SDK + property_set("net.hostname", name.toLocal8Bit().constData()); +#else + QByteArray lname = name.toLocal8Bit(); + if (::sethostname(lname.constData(), lname.length())) { + qWarning("Could not set system hostname"); + return false; + } + // Also store it for next boot: + QFile file(QStringLiteral("/etc/hostname")); + if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { + qWarning("Could not write to /etc/hostname"); + return false; + } + file.write(lname.append('\n')); + file.close(); +#endif + emit hostnameChanged(name); + return true; +} + + /*! * Sets the master volume to \a volume. * The volume can range from 0 to 100 and is linear. - * Changing the master volume will affect all audio streams. - * - * \sa setStreamVolume() - * \sa setMasterMute() */ -void QDroidUtils::setMasterVolume(int volume) +void B2QtDevice::setMasterVolume(int volume) { #ifdef Q_OS_ANDROID_NO_SDK android::status_t rc; @@ -94,28 +200,57 @@ void QDroidUtils::setMasterVolume(int volume) rc = android::AudioSystem::setMasterVolume(android::AudioSystem::linearToLog(volume)); if (rc != android::NO_ERROR) qWarning() << Q_FUNC_INFO << "Error while setting audio properties."; + else + emit masterVolumeChanged(volume); +#else + Q_UNUSED(volume) #endif } + /*! - * Sets the master mute to \a mute. Setting it to true will disable all - * sounds on the device. - * - * \sa setMasterVolume() - * \sa setStreamMute() + * Returns the current master volume. + * The volume can range from 0 to 100 and is linear. */ -void QDroidUtils::setMasterMute(bool mute) +int B2QtDevice::masterVolume() const { + float volume = 0; #ifdef Q_OS_ANDROID_NO_SDK android::status_t rc; - rc = android::AudioSystem::setMasterMute(mute); + rc = android::AudioSystem::getMasterVolume(&volume); if (rc != android::NO_ERROR) - qWarning() << Q_FUNC_INFO << "Error while setting audio properties."; + qWarning() << Q_FUNC_INFO << "Error while getting audio properties."; #endif + return qBound(0, qRound(volume), 100); } -/*! - \enum QDroidUtils::AudioStreamType +#ifdef Q_OS_ANDROID_NO_SDK +// Android audio handling + +enum AudioOrientation { + LandscapeAudioOrientation, + PortraitAudioOrientation, + SquareAudioOrientation, + UndefinedAudioOrientation, +}; + +enum AudioStreamType { + DefaultAudioStream = -1, + VoiceCallAudioStream = 0, + SystemAudioStream = 1, + RingAudioStream = 2, + MusicAudioStream = 3, + AlarmAudioStream = 4, + NotificationAudioStream = 5, + BluetoothAudioStream = 6, + EnforcedAudibleAudioStream = 7, + DTMFAudioStream = 8, + TTSAudioStream = 9 +}; + + +/* + \enum AudioStreamType \value DefaultAudioStream The default audio stream @@ -147,7 +282,7 @@ void QDroidUtils::setMasterMute(bool mute) The audio stream for text-to-speech */ -/*! +/* * Sets the volume for a specific audio \a stream type to \a volume. * The volume can range from 0 to 100 and is linear. * All streams of the specified type will be affected. @@ -155,172 +290,114 @@ void QDroidUtils::setMasterMute(bool mute) * \sa setMasterVolume() * \sa setStreamMute() */ -void QDroidUtils::setStreamVolume(AudioStreamType streamType, int volume) +void setStreamVolume(AudioStreamType streamType, int volume) { -#ifdef Q_OS_ANDROID_NO_SDK android::status_t rc; volume = qBound(0, volume, 100); rc = android::AudioSystem::setStreamVolume(audio_stream_type_t(streamType), android::AudioSystem::linearToLog(volume), 0); if (rc != android::NO_ERROR) qWarning() << Q_FUNC_INFO << "Error while setting audio properties."; -#endif } -/*! +/* * Mutes all audio \a streams of type \a streamType. * * \sa setStreamVolume() * \sa setMasterMute() */ -void QDroidUtils::setStreamMute(AudioStreamType streamType, bool mute) +void setStreamMute(AudioStreamType streamType, bool mute) { -#ifdef Q_OS_ANDROID_NO_SDK android::status_t rc; rc = android::AudioSystem::setStreamMute(audio_stream_type_t(streamType), mute); if (rc != android::NO_ERROR) qWarning() << Q_FUNC_INFO << "Error while setting audio properties."; -#endif } -/*! - * Sets the display brightness (i.e. the intensity of the backlight) - * to \a value. A value of 255 requests maximum brightness, while 0 requests - * minimum (typically, the backlight turned off). - * - * Returns true on success. - */ -//### TBD: add the user/sensor setting as parameter! -bool QDroidUtils::setDisplayBrightness(quint8 value) +void setOrientationForAudioSystem(AudioOrientation orientation) { -#ifdef Q_OS_ANDROID_NO_SDK - const struct hw_module_t* module = 0; - if (hw_get_module(LIGHTS_HARDWARE_MODULE_ID, &module)) - return false; - if (!module || !module->methods || !module->methods->open) - return false; - - struct light_device_t* device = 0; - if (module->methods->open(module, LIGHT_ID_BACKLIGHT, (struct hw_device_t**)&device)) - return false; - if (!device || !device->set_light || !device->common.close) - return false; - - struct light_state_t state; - memset(&state, 0, sizeof(light_state_t)); - state.color = 0xff000000 | (value << 16) | (value << 8) | value; - if (!device->set_light(device, &state)) - return false; - - device->common.close(&device->common); -#else - qDebug("QDroidUtils::setDisplayBrightness(%i)", value); -#endif - return true; -} - - -/*! - * Gets the current IP address(es) of the device - */ -QString QDroidUtils::getIPAddress() -{ - QList<QNetworkInterface> availableInterfaces = QNetworkInterface::allInterfaces(); - if (availableInterfaces.length() > 0) { - foreach (const QNetworkInterface &interface, availableInterfaces) { - if (interface.flags() & QNetworkInterface::IsRunning - && (interface.flags() & QNetworkInterface::IsLoopBack) == 0) { - QList<QNetworkAddressEntry> entries = interface.addressEntries(); - QStringList addresses; - foreach (const QNetworkAddressEntry &entry, entries) - addresses.append(entry.ip().toString().split('%').first()); - return addresses.join(QStringLiteral(", ")); - } - } + QString orientationString = QStringLiteral("undefined"); + switch (orientation) { + case LandscapeAudioOrientation: + orientationString = QStringLiteral("landscape"); + break; + case PortraitAudioOrientation: + orientationString = QStringLiteral("portrait"); + break; + case SquareAudioOrientation: + orientationString = QStringLiteral("square"); + break; + default: + break; } - return QString(); + android::AudioSystem::setParameters(0, android::String8(QStringLiteral("orientation=%2") + .arg(orientationString).toLatin1().constData())); } -/*! - * Gets the current hostname of the device - */ -QString QDroidUtils::getHostname() -{ - QString hostname; -#ifdef Q_OS_ANDROID_NO_SDK - char prop_value[PROPERTY_VALUE_MAX]; - int len = property_get("net.hostname", prop_value, 0); - if (len) - hostname = QString::fromLocal8Bit(prop_value, len); -#else - hostname = QHostInfo::localHostName(); -#endif - return hostname; -} /*! - * Sets new hostname for the device + * Sets the master mute to \a mute. Setting it to true will disable all + * sounds on the device. + * + * \sa setMasterVolume() + * \sa setStreamMute() */ -bool QDroidUtils::setHostname(QString hostname) +void setMasterMute(bool mute) { -#ifdef Q_OS_ANDROID_NO_SDK - property_set("net.hostname", hostname.toLocal8Bit().constData()); -#else - QFile file("/etc/hostname"); - if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { - qWarning("Could not open hostname file"); - return false; - } - file.write(hostname.toLocal8Bit()); - file.close(); -#endif - return true; -} -float QDroidUtils::masterVolume() const -{ - float volume = NAN; -#ifdef Q_OS_ANDROID_NO_SDK android::status_t rc; - rc = android::AudioSystem::getMasterVolume(&volume); + rc = android::AudioSystem::setMasterMute(mute); if (rc != android::NO_ERROR) - qWarning() << Q_FUNC_INFO << "Error while getting audio properties."; -#endif - return volume; + qWarning() << Q_FUNC_INFO << "Error while setting audio properties."; } -bool QDroidUtils::masterMute() const +bool masterMute() { bool mute = false; -#ifdef Q_OS_ANDROID_NO_SDK android::status_t rc; rc = android::AudioSystem::getMasterMute(&mute); if (rc != android::NO_ERROR) qWarning() << Q_FUNC_INFO << "Error while getting audio properties."; -#endif return mute; } -float QDroidUtils::streamVolume(AudioStreamType stream) const +float streamVolume(AudioStreamType stream) { float volume = NAN; -#ifdef Q_OS_ANDROID_NO_SDK android::status_t rc; rc = android::AudioSystem::getStreamVolume(audio_stream_type_t(stream), &volume, 0); if (rc != android::NO_ERROR) qWarning() << Q_FUNC_INFO << "Error while getting audio properties."; -#endif return volume; } -bool QDroidUtils::streamMute(AudioStreamType stream) const +bool streamMute(AudioStreamType stream) { bool mute = false; -#ifdef Q_OS_ANDROID_NO_SDK android::status_t rc; rc = android::AudioSystem::getStreamMute(audio_stream_type_t(stream), &mute); if (rc != android::NO_ERROR) qWarning() << Q_FUNC_INFO << "Error while getting audio properties."; -#endif return mute; } + +#endif + +/*! + * Initializes the audio subsystem, setting the volume to max. + * This is done during system startup, so there is normally no need to call this function from applications. + */ +void B2QtDevice::initAudio() +{ +#ifdef Q_OS_ANDROID_NO_SDK + // Set the audio orientation to something to force the HW driver to reconfigure + // audio routing (workaround for bug on Nexus 7) + setOrientationForAudioSystem(LandscapeAudioOrientation); + setMasterVolume(100); + setMasterMute(false); + setStreamVolume(SystemAudioStream, 100); + setStreamVolume(MusicAudioStream, 100); + setStreamVolume(NotificationAudioStream, 100); + setStreamVolume(EnforcedAudibleAudioStream, 100); +#endif +} diff --git a/src/utils/b2qtdevice.h b/src/utils/b2qtdevice.h new file mode 100644 index 0000000..0563e3f --- /dev/null +++ b/src/utils/b2qtdevice.h @@ -0,0 +1,59 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc +** All rights reserved. +** For any questions to Digia, please use the contact form at +** http://www.qt.io +** +** This file is part of Qt Enterprise Embedded. +** +** Licensees holding valid Qt Enterprise licenses may use this file in +** accordance with the Qt Enterprise License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. +** +** If you have questions regarding the use of this file, please use +** the contact form at http://www.qt.io +** +****************************************************************************/ +#ifndef B2QTDEVICE_H +#define B2QTDEVICE_H + +#include <qobject.h> + +class Q_DECL_EXPORT B2QtDevice : public QObject +{ + Q_OBJECT + + Q_PROPERTY(quint8 displayBrightness READ displayBrightness WRITE setDisplayBrightness NOTIFY displayBrightnessChanged) + Q_PROPERTY(QString hostname READ hostname WRITE setHostname NOTIFY hostnameChanged) + Q_PROPERTY(QString ipAddress READ getIPAddress NOTIFY ipAddressChanged) + Q_PROPERTY(int masterVolume READ masterVolume WRITE setMasterVolume NOTIFY masterVolumeChanged) + +public: + B2QtDevice(QObject *parent = 0); + ~B2QtDevice(); + + quint8 displayBrightness() const; + QString hostname() const; + QString getIPAddress() const; + int masterVolume() const; + + void initAudio(); + +public Q_SLOTS: + void reboot(); + void powerOff(); + + bool setDisplayBrightness(quint8 value); + bool setHostname(const QString &name); + void setMasterVolume(int volume); + +signals: + void displayBrightnessChanged(quint8 newValue); + void hostnameChanged(const QString &newName); + void ipAddressChanged(const QString &newAddress); + void masterVolumeChanged(int newVolume); +}; + +#endif // B2QTDEVICE_H diff --git a/src/utils/qdroidutils.h b/src/utils/qdroidutils.h deleted file mode 100644 index 747caad..0000000 --- a/src/utils/qdroidutils.h +++ /dev/null @@ -1,83 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2014 Digia Plc -** All rights reserved. -** For any questions to Digia, please use the contact form at -** http://www.qt.io -** -** This file is part of Qt Enterprise Embedded. -** -** Licensees holding valid Qt Enterprise licenses may use this file in -** accordance with the Qt Enterprise License Agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and Digia. -** -** If you have questions regarding the use of this file, please use -** the contact form at http://www.qt.io -** -****************************************************************************/ -#ifndef QDROIDUTILS_H -#define QDROIDUTILS_H - -#include <qobject.h> - -class Q_DECL_EXPORT QDroidUtils : public QObject -{ - Q_OBJECT - Q_ENUMS(AudioStreamType) -public: - enum AudioOrientation { - LandscapeAudioOrientation, - PortraitAudioOrientation, - SquareAudioOrientation, - UndefinedAudioOrientation, - }; - - enum AudioStreamType { - DefaultAudioStream = -1, - VoiceCallAudioStream = 0, - SystemAudioStream = 1, - RingAudioStream = 2, - MusicAudioStream = 3, - AlarmAudioStream = 4, - NotificationAudioStream = 5, - BluetoothAudioStream = 6, - EnforcedAudibleAudioStream = 7, - DTMFAudioStream = 8, - TTSAudioStream = 9 - }; - - QDroidUtils(QObject* parent = 0) : QObject(parent) - { - } - ~QDroidUtils() - { - } - - //### TBD: make an instance() method, for singleton use from C++ ? - //e.g. connect(myobj, mysig, QDroidUtils::instance(), slot(rebootSystem()); - -public Q_SLOTS: - void rebootSystem(); - void powerOffSystem(); - - void setOrientationForAudioSystem(AudioOrientation orientation); - - void setMasterVolume(int volume); - void setMasterMute(bool mute); - void setStreamVolume(AudioStreamType stream, int volume); - void setStreamMute(AudioStreamType stream, bool mute); - - float masterVolume() const; - bool masterMute() const; - float streamVolume(AudioStreamType stream) const; - bool streamMute(AudioStreamType stream) const; - - bool setDisplayBrightness(quint8 value); - - QString getIPAddress(); - QString getHostname(); - bool setHostname(QString hostname); -}; - -#endif // QDROIDUTILS_H diff --git a/src/utils/utils.pro b/src/utils/utils.pro index e28b111..13287d5 100644 --- a/src/utils/utils.pro +++ b/src/utils/utils.pro @@ -1,20 +1,18 @@ -TARGET = QtDroidUtils -VERSION = 5.2 +load(qt_build_config) + +TARGET = B2QtUtils +VERSION = 1.0 CONFIG += dll warn_on QT = core network -#QT = core-private gui-private qml-private quick-private -#QT_PRIVATE = v8-private - -#DEFINES += QT_NO_URL_CAST_FROM_STRING QT_NO_INTEGER_EVENT_COORDINATES -MODULE = droidutils +MODULE = b2qtutils load(qt_module) android: LIBS += -lmedia -lhardware -lcutils -lutils HEADERS += \ - $$PWD/qdroidutils.h + $$PWD/b2qtdevice.h SOURCES += \ - $$PWD/qdroidutils.cpp + $$PWD/b2qtdevice.cpp diff --git a/sync.profile b/sync.profile index 22d148b..ede245e 100644 --- a/sync.profile +++ b/sync.profile @@ -1,5 +1,5 @@ %modules = ( # path to module name map - "QtDroidUtils" => "$basedir/src/utils" + "B2QtUtils" => "$basedir/src/utils" ); %moduleheaders = ( # restrict the module headers to those found in relative path ); |