From 763279f1f14099034517b336e8d9dfee85a0f9ed Mon Sep 17 00:00:00 2001 From: Nicolas Mailloux Date: Fri, 17 Jun 2022 23:59:21 -0400 Subject: [PATCH] Half-working setup Huge thanks to @Szybet for writing at least 95% of the code in this commit --- apps.cpp | 185 +++++++++++ apps.h | 14 + apps.ui | 763 ++++++++++++++++++++++++++++----------------- eink.qrc | 1 + functions.h | 7 + generaldialog.cpp | 65 +++- generaldialog.h | 6 + inkbox.pro | 3 + main.cpp | 95 ++++++ resources/edit.png | Bin 0 -> 2626 bytes settings.cpp | 4 + userapps.cpp | 246 +++++++++++++++ userapps.h | 58 ++++ userapps.ui | 221 +++++++++++++ 14 files changed, 1376 insertions(+), 292 deletions(-) create mode 100644 resources/edit.png create mode 100644 userapps.cpp create mode 100644 userapps.h create mode 100644 userapps.ui diff --git a/apps.cpp b/apps.cpp index e22257d..00e2df8 100644 --- a/apps.cpp +++ b/apps.cpp @@ -1,8 +1,14 @@ #include "apps.h" #include "ui_apps.h" #include "mainwindow.h" +#include "userapps.h" + #include #include +#include +#include +#include +#include apps::apps(QWidget *parent) : QWidget(parent), @@ -61,6 +67,14 @@ apps::apps(QWidget *parent) : ui->lightmapsLaunchBtn->deleteLater(); } + ui->editUserAppsBtn->setProperty("type", "borderless"); + + // Refresh because Qt shows the scrollbar for one second then hides it, leaving a mark on the eInk screen + QTimer::singleShot(1750, this, SLOT(refreshScreenNative())); + + jsonParseSuccess = parseJson(); + showUserApps(false); + QFile stylesheetFile("/mnt/onboard/.adds/inkbox/eink.qss"); stylesheetFile.open(QFile::ReadOnly); this->setStyleSheet(stylesheetFile.readAll()); @@ -160,3 +174,174 @@ void apps::on_g2048LaunchBtn_clicked() void apps::showToastNative(QString messageToDisplay) { emit showToast(messageToDisplay); } + +/* + * This function opens and reads the main GUI user applications' JSON file, parses and verifies its content to avoid issues in the future. +*/ +bool apps::parseJson() { + // If the path changes, it is also used statically in showUserApps() + jsonFile.setFileName("/mnt/onboard/onboard/.apps/apps.json"); + bool jsonCheckSuccess = true; + + if(jsonFile.exists() == false) { + log("GUI user applications' main JSON file is missing, creating it", className); + jsonCheckSuccess = false; + } + else { + jsonFile.open(QIODevice::ReadOnly | QIODevice::Text); + QString fileRead = jsonFile.readAll(); + jsonFile.close(); + + jsonDocument = QJsonDocument::fromJson(fileRead.toUtf8()); + if(jsonDocument.isNull() == true) { + log("GUI user applications' main JSON file is invalid", className); + jsonCheckSuccess = false; + } + else { + QJsonObject jsonObject = jsonDocument.object(); + if(jsonObject["list"].isArray() == true) { + QJsonArray jsonArray = jsonObject["list"].toArray(); + for(QJsonValueRef refJsonObject: jsonArray) { + if(refJsonObject.isObject()) { + QJsonObject jsonMainObject = refJsonObject.toObject(); + if(jsonMainObject.size() == 9) { + if(!jsonMainObject["Name"].isString()) { + QString function = __func__; log(function + ": Invalid 'Name' type inside object", className); + jsonCheckSuccess = false; + + } + if(!jsonMainObject["Author"].isString()) { + QString function = __func__; log(function + ": Invalid 'Author' type inside object", className); + jsonCheckSuccess = false; + + } + if(!jsonMainObject["AuthorContact"].isString()) { + QString function = __func__; log(function + ": Invalid 'AuthorContact' type inside object", className); + jsonCheckSuccess = false; + + } + if(!jsonMainObject["Version"].isString()) { + QString function = __func__; log(function + ": Invalid 'Version' type inside object", className); + jsonCheckSuccess = false; + + } + if(!jsonMainObject["IconPath"].isString()) { + log("JSON: Invalid IconPath type inside object", className); + jsonCheckSuccess = false; + + } + if(!jsonMainObject["ExecPath"].isString()) { + QString function = __func__; log(function + ": Invalid 'ExecPath' type inside object", className); + jsonCheckSuccess = false; + } + else { + if(jsonMainObject["ExecPath"].toString().contains("..")) { + // Possible security risk + showToastNative("ERROR: 'ExecPath' has invalid path"); + QString function = __func__; log(function + ": 'ExecPath' contains \"..\"", className); + jsonCheckSuccess = false; + } + } + if(!jsonMainObject["Enabled"].isBool()) { + QString function = __func__; log(function + ": Invalid 'Enabled' type inside object", className); + jsonCheckSuccess = false; + + } + if(!jsonMainObject["SupportedDevices"].isString()) { + QString function = __func__; log(function + ": Invalid 'SupportedDevices' type inside object", className); + jsonCheckSuccess = false; + + } + if(!jsonMainObject["RequiredFeatures"].isArray()) { + QString function = __func__; log(function + ": Invalid 'RequiredFeatures' type inside object", className); + jsonCheckSuccess = false; + + } + else { + QJsonArray jsonArray = jsonMainObject["RequiredFeatures"].toArray(); + for(QJsonValueRef refJsonObject: jsonArray) { + // https://doc.qt.io/qt-5/qjsonvalue.html#toInt + if(!refJsonObject.isDouble()) { + QString function = __func__; log(function + ": Array from 'RequiredFeatures' contains a wrong type", className); + jsonCheckSuccess = false; + } + } + } + } + else { + QString function = __func__; QString message = function + ": An object inside list array has too many items (or not enough): "; + message.append(QString::number(jsonMainObject.size())); + log(message, className); + jsonCheckSuccess = false; + } + } + else { + QString function = __func__; log(function + ": List array has an item of other type than object", className); + jsonCheckSuccess = false; + } + } + } + } + } + return jsonCheckSuccess; +} + +void apps::on_editUserAppsBtn_clicked() +{ + if(userAppsSecondPage == false) { + userAppsSecondPage = true; + + // Settings page + showUserApps(userAppsSecondPage); + emit showUserAppsEdit(userAppsSecondPage); + } + else { + userAppsSecondPage = false; + // Launch page + + // It changed via updateJsonFileSlot, and now it writes it only once + jsonFile.open(QFile::WriteOnly | QFile::Text | QFile::Truncate); + jsonFile.write(jsonDocument.toJson()); + jsonFile.flush(); + jsonFile.close(); + + showUserApps(userAppsSecondPage); + emit showUserAppsEdit(userAppsSecondPage); + + QTimer::singleShot(1000, this, SLOT(refreshScreenNative())); + } +} + +void apps::showUserApps(bool showDisabledJson) +{ + emit clearAppsLayout(); + + if(jsonParseSuccess == true) { + QString function = __func__; log(function + ": JSON is valid", className); + QJsonArray jsonListArray = jsonDocument.object()["list"].toArray(); + for(QJsonValueRef refJsonObject: jsonListArray) { + QJsonObject appInfo = refJsonObject.toObject(); + if(appInfo["Enabled"].toBool() == true or showDisabledJson == true) { + userapps * newUserApp = new userapps; + newUserApp->provideInfo(appInfo); + connect(this, SIGNAL(clearAppsLayout()), newUserApp, SLOT(deleteLater())); + connect(this, SIGNAL(showUserAppsEdit(bool)), newUserApp, SLOT(changePageEnabling(bool))); + connect(this, SIGNAL(updateJsonFileSignal(QJsonDocument)), newUserApp, SLOT(updateJsonFileSlotUA(QJsonDocument))); + connect(newUserApp, SIGNAL(updateJsonFileSignalUA(QJsonDocument)), this, SLOT(updateJsonFileSlot(QJsonDocument))); + newUserApp->jsonDocument = jsonDocument; + newUserApp->jsonFilePath = jsonFile.fileName(); + ui->verticalLayout_4UserApps->addWidget(newUserApp); + } + } + } + else { + QString function = __func__; log(function + ": JSON is invalid", className); + emit showToast("ERROR: Failed to parse 'apps.json'"); + } +} + +void apps::updateJsonFileSlot(QJsonDocument jsonDocumentFunc) +{ + jsonDocument = jsonDocumentFunc; + emit updateJsonFileSignal(jsonDocument); +} diff --git a/apps.h b/apps.h index 44ed62a..aee4fa5 100644 --- a/apps.h +++ b/apps.h @@ -7,6 +7,8 @@ #include #include +#include + namespace Ui { class apps; } @@ -30,9 +32,13 @@ private slots: void on_koboxAppsOpenButton_clicked(); void on_vncLaunchBtn_clicked(); void on_reversiLaunchBtn_clicked(); + void on_editUserAppsBtn_clicked(); void refreshScreenNative(); void on_g2048LaunchBtn_clicked(); void showToastNative(QString messageToDisplay); + bool parseJson(); + void showUserApps(bool showDisabledJson); + void updateJsonFileSlot(QJsonDocument jsonDocument); private: Ui::apps *ui; @@ -41,9 +47,17 @@ private: koboxAppsDialog *koboxAppsDialogWindow; generalDialog *generalDialogWindow; + QFile jsonFile; + QJsonDocument jsonDocument; + bool jsonParseSuccess = false; + bool userAppsSecondPage = false; + signals: void refreshScreen(); void showToast(QString messageToDisplay); + void updateJsonFileSignal(QJsonDocument jsonDocument); + void clearAppsLayout(); + void showUserAppsEdit(bool show); }; #endif // APPS_H diff --git a/apps.ui b/apps.ui index 6af18ea..2cc41d1 100644 --- a/apps.ui +++ b/apps.ui @@ -6,8 +6,8 @@ 0 0 - 400 - 321 + 430 + 544 @@ -29,302 +29,483 @@ - - - 0 + + + QFrame::NoFrame - - - - - - - 75 - true - - - - Apps - - - Qt::AlignCenter - - - - - - - 0 - - - - - - Inter - 75 - true - - - - Launch - - - - - - - - Inter - 75 - true - - - - Launch - - - - - - - - 75 - true - - - - Light Maps - - - - - - - - 75 - true - - - - Scribble - - - - - - - - Inter - 75 - true - - - - Launch - - - - - - - - 75 - true - - - - Reversi - - - - - - - - 75 - true - - - - Saved words - - - - - - - - 75 - true - - - - KoBox apps - - - - - - - - 75 - true - - - - Calendar - - - - - - - - Inter - 75 - false - true - - - - Launch - - - - - - - - 75 - true - - - - Launch - - - - - - - - Inter - 75 - true - - - - Launch - - - - - - - - Inter - 75 - true - - - - Open - - - - - - - - 75 - true - - - - Calculator - - - - - - - - 75 - true - - - - VNC viewer - - - - - - - - Inter - 75 - false - true - - - - Launch - - - - - - - - 75 - true - - - - Launch - - - - - - - - 75 - true - - - - 2048 - - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - QFrame::Plain - 3 + 0 - - Qt::Horizontal + + Qt::ScrollBarAlwaysOff + + true + + + + + 0 + 0 + 428 + 542 + + + + + 6 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 0 + + + 6 + + + 10 + + + + + + 75 + true + + + + KoBox apps + + + + + + + + 75 + true + + + + VNC viewer + + + + + + + + Inter + 75 + false + true + + + + Launch + + + + + + + + 75 + true + + + + Saved words + + + + + + + + 75 + true + + + + Scribble + + + + + + + + 75 + true + + + + Calculator + + + + + + + + Inter + 75 + true + + + + Launch + + + + + + + + Inter + 75 + true + + + + Open + + + + + + + + 75 + true + + + + Light Maps + + + + + + + + 75 + true + + + + Reversi + + + + + + + + 75 + true + + + + Launch + + + + + + + + Inter + 75 + true + + + + Launch + + + + + + + + Inter + 75 + true + + + + Launch + + + + + + + + 75 + true + + + + 2048 + + + + + + + + 75 + true + + + + Launch + + + + + + + + Inter + 75 + true + + + + Launch + + + + + + + + Inter + 75 + false + true + + + + Launch + + + + + + + + 75 + true + + + + Built-in apps + + + Qt::AlignCenter + + + + + + + + 75 + true + + + + Calendar + + + + + + + QFrame::Plain + + + 3 + + + Qt::Horizontal + + + + + + + + + QFrame::Plain + + + 3 + + + Qt::Horizontal + + + + + + + 0 + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 75 + true + + + + User apps + + + Qt::AlignCenter + + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 10 + 0 + + + + + + + + + 0 + 0 + + + + + 40 + 40 + + + + + + + + ://resources/edit.png://resources/edit.png + + + + 34 + 34 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + QFrame::Plain + + + 3 + + + Qt::Horizontal + + + + + + + 10 + + + 2 + + + 0 + + + + + + + Qt::Vertical + + + QSizePolicy::MinimumExpanding + + + + 0 + 0 + + + + + + diff --git a/eink.qrc b/eink.qrc index 0cf1bd9..5d6b3d4 100644 --- a/eink.qrc +++ b/eink.qrc @@ -74,5 +74,6 @@ resources/clock.png resources/eink-square-encfs.qss resources/tzlist + resources/edit.png diff --git a/functions.h b/functions.h index d2934b2..c77092f 100644 --- a/functions.h +++ b/functions.h @@ -122,6 +122,13 @@ namespace global { namespace logger { inline bool status; } + namespace userApps { + inline bool appCompatibilityDialog; + inline QString appCompatibilityText; + inline bool appCompatibilityLastContinueStatus = true; // This is for RequiredFeatures to show only one dialog if 'Cancel' is clicked. + inline bool appInfoDialog; + inline bool launchApp; + } inline QString systemInfoText; inline bool forbidOpenSearchDialog; inline bool isN705 = false; diff --git a/generaldialog.cpp b/generaldialog.cpp index 3d86b44..68469c7 100644 --- a/generaldialog.cpp +++ b/generaldialog.cpp @@ -13,6 +13,7 @@ #include #include #include +#include generalDialog::generalDialog(QWidget *parent) : QDialog(parent), @@ -131,6 +132,7 @@ generalDialog::generalDialog(QWidget *parent) : } else if(global::usbms::usbmsDialog == true) { usbmsDialog = true; + ui->stackedWidget->setCurrentIndex(0); ui->okBtn->setText("Connect"); ui->cancelBtn->setText("Not now"); ui->bodyLabel->setText("Do you want to connect your device to a computer to manage books?"); @@ -187,9 +189,29 @@ generalDialog::generalDialog(QWidget *parent) : ui->bodyLabel->setText("New files have been found in 'encfs-dropbox'. Would you want to repack your encrypted storage?"); QTimer::singleShot(50, this, SLOT(adjust_size())); } + else if(global::userApps::appCompatibilityDialog == true) { + appCompabilityDialog = true; + global::userApps::appCompatibilityLastContinueStatus = true; + ui->okBtn->setText("Launch"); + ui->cancelBtn->setText("Cancel"); + ui->bodyLabel->setText(global::userApps::appCompatibilityText); + ui->headerLabel->setText("Compatibility warning"); + QTimer::singleShot(50, this, SLOT(adjust_size())); + } + else if(global::userApps::appInfoDialog == true) { + appInfoDialog = true; + textwidgetWindow = new textwidget(); + + ui->headerLabel->setText("App info"); + ui->stackedWidget->setCurrentIndex(1); + ui->mainStackedWidget->insertWidget(1, textwidgetWindow); + ui->mainStackedWidget->setCurrentIndex(1); + yIncrease = 1.8; + QTimer::singleShot(50, this, SLOT(increaseSize())); + } else { // We shouldn't be there ;) - ; + log("generalDialog launched without settings", className); } // Centering dialog @@ -248,6 +270,12 @@ void generalDialog::on_cancelBtn_clicked() else if(global::encfs::repackDialog == true) { global::encfs::repackDialog = false; } + else if(global::userApps::appCompatibilityDialog == true) { + global::userApps::launchApp = false; + global::userApps::appCompatibilityLastContinueStatus = false; + global::userApps::appCompatibilityText = ""; + global::userApps::appCompatibilityDialog = false; + } generalDialog::close(); } } @@ -507,6 +535,13 @@ void generalDialog::on_okBtn_clicked() string_writeconfig("/external_root/run/encfs_repack", "true"); quit_restart(); } + else if(global::userApps::appCompatibilityDialog == true) { + global::userApps::launchApp = true; + global::userApps::appCompatibilityText = ""; + global::userApps::appCompatibilityLastContinueStatus = true; // Not really necceserry, only if something fails horibly + global::userApps::appCompatibilityDialog = false; + generalDialog::close(); + } } void generalDialog::on_acceptBtn_clicked() { @@ -533,6 +568,11 @@ void generalDialog::on_acceptBtn_clicked() global::text::textBrowserDialog = false; } + if(appInfoDialog == true) { + global::text::textBrowserContents = ""; + global::userApps::appInfoDialog = false; + } + // We don't have any other option ;p generalDialog::close(); } @@ -552,6 +592,7 @@ void generalDialog::restartSearchDialog() { } void generalDialog::setupKeyboardDialog() { + ui->stackedWidget->setCurrentIndex(0); keyboardDialog = true; ui->stackedWidget->setVisible(true); if(global::keyboard::searchDialog == true) { @@ -732,3 +773,25 @@ void generalDialog::waitForGutenbergSearchDone() { } } } + +void generalDialog::increaseSize() +{ + log("Resizing generalDialog", className); + + ui->topStackedWidget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); + ui->mainStackedWidget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::MinimumExpanding); + ui->stackedWidget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); + + QRect screenGeometry = QGuiApplication::screens()[0]->geometry(); + int wx = screenGeometry.width(); + + int x = wx - 25; + int y = this->height() * yIncrease; + this->setFixedWidth(x); + this->setFixedHeight(y); + + ui->bodyLabel->sizePolicy().setVerticalPolicy(QSizePolicy::Expanding); + ui->bodyLabel->sizePolicy().setHorizontalPolicy(QSizePolicy::Expanding); + + adjust_size(); +} diff --git a/generaldialog.h b/generaldialog.h index bb9bd24..1270521 100644 --- a/generaldialog.h +++ b/generaldialog.h @@ -32,6 +32,8 @@ public: bool lowBatteryDialog = false; bool usbmsDialog = false; bool textBrowserDialog = false; + bool appCompabilityDialog = false; + bool appInfoDialog = false; bool resetKoboxDialog = false; bool keyboardDialog = false; bool keypadDialog = false; @@ -49,6 +51,10 @@ public: QString wifiPassphrase; void setupKeyboardDialog(); void startVNC(QString server, QString password, QString port); + float yIncrease; + +public slots: + void increaseSize(); private slots: void on_cancelBtn_clicked(); diff --git a/inkbox.pro b/inkbox.pro index 000f92d..f41890e 100644 --- a/inkbox.pro +++ b/inkbox.pro @@ -48,6 +48,7 @@ SOURCES += \ textwidget.cpp \ toast.cpp \ usbms_splash.cpp \ + userapps.cpp \ virtualkeyboard.cpp \ virtualkeypad.cpp \ wifidialog.cpp @@ -77,6 +78,7 @@ HEADERS += \ textwidget.h \ toast.h \ usbms_splash.h \ + userapps.h \ virtualkeyboard.h \ virtualkeypad.h \ wifidialog.h @@ -105,6 +107,7 @@ FORMS += \ textwidget.ui \ toast.ui \ usbms_splash.ui \ + userapps.ui \ virtualkeyboard.ui \ virtualkeypad.ui \ wifidialog.ui diff --git a/main.cpp b/main.cpp index fa64d7f..33e9666 100644 --- a/main.cpp +++ b/main.cpp @@ -27,6 +27,101 @@ #include #include #include +#include + +#include +#include +#include +#include +#include + +void createMainJson() +{ + QDirIterator appsDir("/mnt/onboard/onboard/.apps", QDirIterator::NoIteratorFlags); + QFile newJsonFile = QFile{"/mnt/onboard/onboard/.apps/apps.json"}; + QJsonDocument newJsonDocument; + QJsonArray array; + + while (appsDir.hasNext()) + { + QDir dir(appsDir.next()); + if(dir.exists() == true) { + if(dir.path().split("/").last().contains(".") == false) { + QFile jsonSmall = QFile{dir.path() + "/app.json"}; + if(jsonSmall.exists() == true) { + jsonSmall.open(QIODevice::ReadOnly | QIODevice::Text); + QString jsonString = jsonSmall.readAll(); + jsonSmall.close(); + + QJsonDocument jsonSmallDoc = QJsonDocument::fromJson(jsonString.toUtf8()); + if(jsonSmallDoc["app"].isObject() == true) { + QJsonObject jsonSmallMainObj = jsonSmallDoc["app"].toObject(); + array.append(jsonSmallMainObj); + + } + else { + log("Small JSON user application file is missing main object 'app'", "main"); + } + + } + else { + QString message = "User applications folder does not contain any 'app.json' file: "; + message.append(jsonSmall.fileName()); + log(message, "main"); + } + } + } + } + // https://forum.qt.io/topic/104791/how-i-can-create-json-format-in-qt/5 + QJsonObject root; + root["list"] = array; + newJsonDocument.setObject(root); + + newJsonFile.open(QFile::WriteOnly | QFile::Text | QFile::Truncate); + newJsonFile.write(newJsonDocument.toJson()); + newJsonFile.flush(); + newJsonFile.close(); +} + +void createSmallJsonFiles() +{ + QFile jsonFile = QFile{"/mnt/onboard/onboard/.apps/apps.json"}; + + jsonFile.open(QIODevice::ReadOnly | QIODevice::Text); + QString fileRead = jsonFile.readAll(); + jsonFile.close(); + QJsonDocument jsonDocument = QJsonDocument::fromJson(fileRead.toUtf8()); + + if(jsonDocument["list"].isArray() == true) { + QJsonArray jsonArray = jsonDocument["list"].toArray(); + for(QJsonValueRef refJsonObject: jsonArray) { + QJsonObject jsonMainObject = refJsonObject.toObject(); + QString appName = jsonMainObject["Name"].toString(); + + // This needs to be here and not at the beggining of this function because it is an iterator + QDirIterator appsDir("/mnt/onboard/onboard/.apps", QDirIterator::NoIteratorFlags); + while (appsDir.hasNext()) { + QDir dir(appsDir.next()); + if(dir.exists() == true) { + if(dir.path().split("/").last().toLower().contains(appName.toLower()) == true) { + QJsonObject root; + root["app"] = refJsonObject.toObject();; + QJsonDocument newJsonDocument; + newJsonDocument.setObject(root); + + QFile newSmallJson = QFile{dir.path() + "/" + "app.json"}; + + newSmallJson.open(QIODevice::ReadWrite); + QTextStream stream(&newSmallJson); + stream << newJsonDocument.toJson() << Qt::endl; + newSmallJson.flush(); + newSmallJson.close(); + } + } + } + } + } +} int main(int argc, char *argv[]) { diff --git a/resources/edit.png b/resources/edit.png new file mode 100644 index 0000000000000000000000000000000000000000..b84e661c5e96ab39f49900b9f721703fa79ee306 GIT binary patch literal 2626 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 literal 0 HcmV?d00001 diff --git a/settings.cpp b/settings.cpp index 5104761..21cb8a9 100644 --- a/settings.cpp +++ b/settings.cpp @@ -822,6 +822,10 @@ void settings::on_showSystemInfoBtn_clicked() // Show a system info dialog log("Showing system info dialog", className); generalDialogWindow = new generalDialog(); + if(global::deviceID == "n306\n") { + generalDialogWindow->yIncrease = 2.6; + } + generalDialogWindow->increaseSize(); generalDialogWindow->setAttribute(Qt::WA_DeleteOnClose); } diff --git a/userapps.cpp b/userapps.cpp new file mode 100644 index 0000000..f9beb77 --- /dev/null +++ b/userapps.cpp @@ -0,0 +1,246 @@ +#include "userapps.h" +#include "ui_userapps.h" +#include "mainwindow.h" +#include "generaldialog.h" + +userapps::userapps(QWidget *parent) : + QWidget(parent), + ui(new Ui::userapps) +{ + ui->setupUi(this); + ui->launchBtn->setProperty("type", "borderless"); + ui->launchBtn->setStyleSheet("background: lightGrey; font-size: 9pt; padding: 8px"); + + ui->launchBtn->setProperty("type", "borderless"); + + ui->iconBtn->setProperty("type", "borderless"); + ui->infoBtn->setProperty("type", "borderless"); + ui->statusBtn->setProperty("type", "borderless"); + ui->statusBtn->setStyleSheet("background: lightGrey; font-size: 9pt; padding: 8px"); +} + +userapps::~userapps() +{ + delete ui; +} + +void userapps::provideInfo(QJsonObject jsonInfo) +{ + QString name = jsonInfo["Name"].toString(); + appName = name; // It is for searching for json entry while disabling / enabling + jsonObject = jsonInfo; + // Limit name size to avoid breaking the GUI + if(name.size() > 20) + { + // If someone wants to break the GUI, they will do it ¯\^-^/¯ + name.remove(16, 100); + } + ui->appNameLabel->setText(name); + + appDir.setPath("/mnt/onboard/onboard/.apps/" + name + "/" + name); + + QFile iconPath = QFile{appDir.path() + "/" + jsonInfo["IconPath"].toString()}; + if(iconPath.exists() == true) { + QIcon appIcon = QIcon(iconPath.fileName()); + ui->iconBtn->setIconSize(QSize(40, 40)); + ui->iconBtn->setIcon(appIcon); + } + else { + QString function = __func__; + QString message = ": Warning: Icon not found: "; + message.append(iconPath.fileName()); + log(function + message, className); + } + + execPath.setFileName("/" + jsonInfo["ExecPath"].toString()); + + userAppEnabled = jsonInfo["Enabled"].toBool(); + if(userAppEnabled == true) { + ui->statusBtn->setText("Disable"); + } + else { + ui->statusBtn->setText("Enable"); + } +} + +// This function is needed when we dont want to repaint all widgets, but only change the the page (when no changes to the main JSON file were applied) +void userapps::changePageEnabling(bool SecondPage) +{ + if(SecondPage == true){ + ui->stackedWidget->setCurrentIndex(1); + } + else { + ui->stackedWidget->setCurrentIndex(0); + } +} + +void userapps::on_infoBtn_clicked() +{ + log("Launching user application information dialog", className); + // https://stackoverflow.com/questions/28181627/how-to-convert-a-qjsonobject-to-qstring + QString jsonStringParsed = parseJsonShow(jsonObject); + global::text::textBrowserContents = jsonStringParsed; + + global::userApps::appInfoDialog = true; + generalDialogWindow = new generalDialog(); + generalDialogWindow->setAttribute(Qt::WA_DeleteOnClose); + generalDialogWindow->show(); +} + +void userapps::on_statusBtn_clicked() +{ + ui->statusBtn->setEnabled(false); + + // Here the text on this button is used as a bool. No need to create a new one + // Disable and Enable + if(userAppEnabled == false) { + userAppEnabled = true; + ui->statusBtn->setText("Disable"); + } + else { + userAppEnabled = false; + ui->statusBtn->setText("Enable"); + } + + QJsonObject jsonRootObject = jsonDocument.object(); + QJsonArray jsonArrayList = jsonRootObject["list"].toArray(); + + int arraySize = jsonArrayList.size(); + + for(int i = 0; i < arraySize; i++) + { + QJsonObject jsonObject = jsonArrayList.at(i).toObject(); + QString entryName = jsonObject["Name"].toString(); + + if(entryName == appName) + { + jsonObject.insert("Enabled", QJsonValue(userAppEnabled)); + + QJsonArray sonArrayListNew = jsonDocument.object()["list"].toArray(); + sonArrayListNew.replace(i, jsonObject); + + jsonRootObject["list"] = sonArrayListNew; + + jsonDocument.setObject(jsonRootObject); + emit updateJsonFileSignalUA(jsonDocument); + } + } + ui->statusBtn->setEnabled(true); +} + +void userapps::updateJsonFileSlotUA(QJsonDocument jsonDocumentProvided) +{ + jsonDocument = jsonDocumentProvided; +} + +void userapps::on_launchBtn_clicked() +{ + // Some command to execute script or binary at "ExecPath" + QString supportedDevices = jsonObject["SupportedDevices"].toString(); + QString message = "Supported devices for this app: "; + message.append(supportedDevices); + log(message, className); + + if(supportedDevices.contains("all") == false and supportedDevices.contains(global::deviceID.trimmed()) == false) { + log("Warning: User app does not support this device", className); + global::userApps::appCompatibilityDialog = true; + global::userApps::appCompatibilityText = "Your device is not compatible with this app.
Launch it anyway
?"; + generalDialogWindow = new generalDialog(); + generalDialogWindow->setAttribute(Qt::WA_DeleteOnClose); + + generalDialogWindow->exec(); + } + else { + global::userApps::launchApp = true; + } + + if(manageRequiredFeatures() == true) { + if(global::userApps::launchApp == true) { + global::userApps::launchApp = false; + QString message = "Launching user application at: "; + message.append(appDir.path() + execPath.fileName()); + log(message, className); + + QProcess process; + QStringList args; + args << appDir.path() << execPath.fileName(); + process.startDetached("launch_user_application.sh", args); + qApp->quit(); + } + } +} + +bool userapps::manageRequiredFeatures() +{ + QJsonArray jsonArray = jsonObject["RequiredFeatures"].toArray(); + for(QJsonValueRef refJsonObject: jsonArray) + { + bool launchDialog = false; + int featureId = refJsonObject.toInt(); + // Wi-Fi connection required + if(featureId == 0) { + global::userApps::appCompatibilityText = "This app needs Wi-Fi connection, launch anyway?"; + launchDialog = true; + } + // Rooted kernel required + if(featureId == 1) { + global::userApps::appCompatibilityText = "This app needs a rooted kernel, launch anyway?"; + launchDialog = true; + } + + if(launchDialog == true) + { + global::userApps::appCompatibilityDialog = true; + generalDialogWindow = new generalDialog(); + generalDialogWindow->setAttribute(Qt::WA_DeleteOnClose); + generalDialogWindow->exec(); + } + + if(global::userApps::appCompatibilityLastContinueStatus == false) + { + return false; + } + } + return true; +} + +QString userapps::parseJsonShow(QJsonObject json) +{ + QString mainString; + foreach(const QString& key, json.keys()) { + QJsonValue value = json.value(key); + + QString appendString; + + appendString.append(""); + appendString.append(key); + appendString.append(": "); + + if(value.isString()) { + appendString.append(value.toString()); + } + else if(value.isBool()) { + appendString.append(QVariant(value.toBool()).toString()); + } + else if(value.isArray()) { + QJsonArray array = value.toArray(); + for(QJsonValueRef ref: array) { + int id = ref.toInt(); + if(id == 0) + { + appendString.append("Wi-Fi connection"); + } else if(id == 1) + { + appendString.append("Rooted kernel"); + } + appendString.append(", "); + } + appendString.remove(appendString.size() - 2, 2); + } + + appendString.append("
"); + mainString.append(appendString); + } + + return mainString; +} diff --git a/userapps.h b/userapps.h new file mode 100644 index 0000000..3063844 --- /dev/null +++ b/userapps.h @@ -0,0 +1,58 @@ +#ifndef USERAPPS_H +#define USERAPPS_H + +#include "generaldialog.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Ui { + class userapps; +} + +class userapps : public QWidget +{ + Q_OBJECT + +public: + QString className = this->metaObject()->className(); + explicit userapps(QWidget * parent = nullptr); + ~userapps(); + QJsonDocument jsonDocument; // TODO: Optimize for RAM usage + QJsonObject jsonObject; // Needed for 'App info' button + QString jsonFilePath; + +signals: + // This is needed, because user application have their own JSON file. If the user changes it too fast it won't read it and overwrite previous changes + void updateJsonFileSignalUA(QJsonDocument jsonDocument); + + +public slots: + void provideInfo(QJsonObject jsonObject); + void changePageEnabling(bool goThere); + void updateJsonFileSlotUA(QJsonDocument jsonDocument); + bool manageRequiredFeatures(); + QString parseJsonShow(QJsonObject json); + +private slots: + void on_infoBtn_clicked(); + void on_statusBtn_clicked(); + void on_launchBtn_clicked(); + +private: + Ui::userapps * ui; + generalDialog * generalDialogWindow; + + QDir appDir; + QFile execPath; + bool userAppEnabled; + QString appName; +}; + +#endif // USERAPPS_H diff --git a/userapps.ui b/userapps.ui new file mode 100644 index 0000000..506029a --- /dev/null +++ b/userapps.ui @@ -0,0 +1,221 @@ + + + userapps + + + + 0 + 0 + 461 + 79 + + + + Form + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 0 + + + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + + 75 + true + + + + Launch + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + + + + + :/resources/info.png:/resources/info.png + + + + 34 + 34 + + + + + + + + + 0 + 0 + + + + + 75 + true + + + + Disable + + + + + + + + + + + + + + 0 + 0 + + + + + 40 + 40 + + + + + + + + + + + Qt::Horizontal + + + QSizePolicy::Preferred + + + + 5 + 0 + + + + + + + + + 0 + 0 + + + + + 75 + true + + + + 0 + + + AppName + + + + + + + Qt::Horizontal + + + QSizePolicy::Expanding + + + + 13 + 13 + + + + + + + + + + + + +