diff --git a/reHackable-launcher.pro b/draft.pro similarity index 72% rename from reHackable-launcher.pro rename to draft.pro index 56dd60d..bd993d3 100644 --- a/reHackable-launcher.pro +++ b/draft.pro @@ -2,7 +2,7 @@ QT += quick CONFIG += c++11 LIBS += -lqsgepaper -TARGET = reHackable-launcher +TARGET = draft # The following define makes your compiler emit warnings if you use # any feature of Qt which as been marked deprecated (the exact warnings @@ -16,10 +16,14 @@ DEFINES += QT_DEPRECATED_WARNINGS #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 HEADERS += \ - mainview.h + mainview.h \ + options.h \ + handler.h SOURCES += main.cpp \ - mainview.cpp + mainview.cpp \ + options.cpp \ + handler.cpp DEPLOYMENT_PATH = /usr/share/$$TARGET DEFINES += DEPLOYMENT_PATH=\\\"$$DEPLOYMENT_PATH\\\" @@ -30,17 +34,30 @@ js.path == $$DEPLOYMENT_PATH/js INSTALLS += js qml.files = qml/Main.qml +qml.files+= qml/MenuItem.qml qml.path == $$DEPLOYMENT_PATH/qml INSTALLS += qml + +# Installs /etc/draft and /lib/systemd/system/draft.service. +configFiles.files = extra-files/draft +configFiles.path = /etc/ +INSTALLS += configFiles + +service.files = extra-files/draft.service +service.path=/lib/systemd/system/ +INSTALLS += service + # Additional import path used to resolve QML modules in Qt Creator's code model QML_IMPORT_PATH = + target.path = /usr/bin INSTALLS += target DISTFILES += \ - js/Main.js + js/Main.js \ + qml/MenuItem.qml diff --git a/extra-files/draft.service b/extra-files/draft.service new file mode 100644 index 0000000..fed1607 --- /dev/null +++ b/extra-files/draft.service @@ -0,0 +1,13 @@ +[Unit] +Description=draft launcher +#StartLimitIntervalSec=600 +#StartLimitBurst=4 +After=home.mount + +[Service] +ExecStart=/usr/bin/draft + +[Install] +WantedBy=multi-user.target + + diff --git a/extra-files/draft/.terminate b/extra-files/draft/.terminate new file mode 100644 index 0000000..397db75 --- /dev/null +++ b/extra-files/draft/.terminate @@ -0,0 +1 @@ +: diff --git a/extra-files/draft/01-xochitl b/extra-files/draft/01-xochitl new file mode 100644 index 0000000..200b31f --- /dev/null +++ b/extra-files/draft/01-xochitl @@ -0,0 +1,5 @@ +name=xochitl +desc=The standard environment for reMarkable. +call=/usr/bin/xochitl +term=killall xochitl +imgFile=xochitl diff --git a/extra-files/draft/02-fingerterm b/extra-files/draft/02-fingerterm new file mode 100644 index 0000000..94a39c8 --- /dev/null +++ b/extra-files/draft/02-fingerterm @@ -0,0 +1,5 @@ +name=fingerterm +desc=Touchscreen accessible terminal. +call=/usr/bin/fingerterm +term=: +imgFile=fingerterm diff --git a/extra-files/draft/99-shutdown b/extra-files/draft/99-shutdown new file mode 100644 index 0000000..9cdc6ee --- /dev/null +++ b/extra-files/draft/99-shutdown @@ -0,0 +1,5 @@ +name=shutdown +desc=Switch off the reMarkable. +call=shutdown now +term=: +imgFile=power diff --git a/extra-files/draft/icons/fingerterm.png b/extra-files/draft/icons/fingerterm.png new file mode 100644 index 0000000..7ffa028 Binary files /dev/null and b/extra-files/draft/icons/fingerterm.png differ diff --git a/extra-files/draft/icons/power.png b/extra-files/draft/icons/power.png new file mode 100644 index 0000000..cd26aa0 Binary files /dev/null and b/extra-files/draft/icons/power.png differ diff --git a/extra-files/draft/icons/xochitl.png b/extra-files/draft/icons/xochitl.png new file mode 100644 index 0000000..3eac010 Binary files /dev/null and b/extra-files/draft/icons/xochitl.png differ diff --git a/handler.cpp b/handler.cpp new file mode 100644 index 0000000..d7b3391 --- /dev/null +++ b/handler.cpp @@ -0,0 +1,69 @@ +#include "handler.h" +#include "mainview.h" +#include +#include +#include +#include +#include +#include +#include + +time_t lastReturn; +int qSize = 1404; + +Handler::Handler(std::string lT, std::string term, QGuiApplication* app, MainView* mainView, QObject* obj) + : link(lT), term(term), ef(obj), app(app), mainView(mainView){ + + time(&lastReturn); + +} + +bool Handler::eventFilter(QObject* obj, QEvent* event) +{ + if (event->type() == QEvent::MouseButtonPress) { + obj->parent()->setProperty("color", QVariant("#22000000")); + return true; + } + + else if (event->type() == QEvent::MouseButtonRelease) { + obj->parent()->setProperty("color", QVariant("white")); + time_t tim; + time(&tim); + // 1 second cooldown before opening again! + if(difftime(tim, lastReturn) > 1) { + handleEvent(); + } + return true; + } + else + return false; +} + +void Handler::handleEvent() { + + std::cout << "Setting termfile /etc/draft/.terminate" << std::endl; + std::ofstream termfile; + termfile.open("/etc/draft/.terminate"); + termfile << term << std::endl; + + std::cout << "Running command " << link << "..." << std::endl; + system((link).c_str()); + + // Don't exit any more - just head back to the launcher! + std::cout << "Running again" << std::endl; + + // Yes, this is an exceptionally hacky way of repainting the screen + qSize = 1-(qSize-1403)+1403; + + usleep(500000); + + mainView->rootObject()->setProperty("width",QVariant(qSize)); + + // Cooldown + time(&lastReturn); + +} + +Handler::~Handler() { + ef->removeEventFilter(this); +} diff --git a/handler.h b/handler.h new file mode 100644 index 0000000..6bde1fb --- /dev/null +++ b/handler.h @@ -0,0 +1,27 @@ +#pragma once + +#include +#include +#include +#include +#include "mainview.h" +#include + +extern time_t lastReturn; + +class Handler : public QObject +{ + Q_OBJECT +public: + Handler(std::string lT, std::string term, QGuiApplication* app, MainView* mainView, QObject* obj); + ~Handler(); + bool eventFilter(QObject* obj, QEvent* event); + void handleEvent(); + +private: + std::string link; + std::string term; + QObject* ef; + QGuiApplication* app; + MainView* mainView; +}; diff --git a/main.cpp b/main.cpp index 1fbfbb6..c5517dc 100644 --- a/main.cpp +++ b/main.cpp @@ -2,6 +2,8 @@ //#include //#include #include "mainview.h" +#include "options.h" +#include "handler.h" Q_IMPORT_PLUGIN(QsgEpaperPlugin) @@ -10,17 +12,20 @@ int main(int argc, char *argv[]) qputenv("QMLSCENE_DEVICE", "epaper"); qputenv("QT_QPA_PLATFORM", "epaper:enable_fonts"); qputenv("QT_QPA_EVDEV_TOUCHSCREEN_PARAMETERS", "rotate=180"); -// qputenv("QT_QPA_EVDEV_TOUCHSCREEN_PARAMETERS", "/dev/input/event1"); -// qputenv("QT_QPA_EGLFS_NO_LIBINPUT", "1"); + + system("/usr/bin/button-capture &"); QGuiApplication app(argc, argv); MainView view; + srand(time(NULL)); + view.rootContext()->setContextProperty("screenGeometry", app.primaryScreen()->geometry()); view.engine()->addImportPath(QStringLiteral(DEPLOYMENT_PATH)); view.setSource(QDir(DEPLOYMENT_PATH).filePath("qml/Main.qml")); view.show(); - qDebug() << "view shown"; + Options options(&view, &app); + return app.exec(); } diff --git a/mainview.h b/mainview.h index db20f10..d6fb80f 100644 --- a/mainview.h +++ b/mainview.h @@ -1,5 +1,4 @@ -#ifndef MAINVIEW_H -#define MAINVIEW_H +#pragma once #include #include @@ -17,5 +16,3 @@ public slots: void tabletEvent(QTabletEvent* te); void touchEvent(QTouchEvent* te); }; - -#endif // MAINVIEW_H diff --git a/options.cpp b/options.cpp new file mode 100644 index 0000000..924a060 --- /dev/null +++ b/options.cpp @@ -0,0 +1,120 @@ +#include "options.h" +#include "handler.h" +#include +#include +#include +#include +#include +#include + +const std::string configDir = "/etc/draft"; + +// Create options and add them to the screen. +Options::Options(MainView* mainView, QGuiApplication* app) : + mainView(mainView), + optionsView(mainView->rootObject()->findChild("optionsArea")), + app(app) +{ + + std::vector filenames; + + // If the config directory doesn't exist, + // then print an error and stop. + if(!Options::read_directory(configDir, filenames)) { + Options::error("Failed to read directory - it does not exist."); + return; + } + + std::sort(filenames.begin(), filenames.end()); + + for(std::string f : filenames) { + + std::cout << "parsing file " << f << std::endl; + QFile file((configDir + "/" + f).c_str()); + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { + Options::error("Couldn't find the file " + f + "."); + break; + } + + QTextStream in(&file); + + OptionItem opt; + + while (!in.atEnd()) { + std::string line = in.readLine().toStdString(); + if(line.length() > 0) { + size_t sep = line.find("="); + if(sep != line.npos) { + std::string lhs = line.substr(0,sep); + std::string rhs = line.substr(sep+1); + + if (lhs == "name") opt.name = rhs; + else if(lhs == "desc") opt.desc = rhs; + else if(lhs == "imgFile") opt.imgFile = rhs; + else if(lhs == "call") opt.call = rhs; + else if(lhs == "term") opt.term = rhs; + else std::cout << "ignoring unknown parameter \"" << line + << "\" in file \"" << f << "\"" << std::endl; + } + } + else { + std::cout << "ignoring malformed line \"" << line + << "\" in file \"" << f << "\"" << std::endl; + } + } + + if(opt.call == "" || opt.term == "") continue; + createOption(opt, optionList.size()); + optionList.push_back(opt); + + } + +} + +void Options::createOption(OptionItem &option, size_t index) { + + QQuickView* opt = new QQuickView(); + opt->setSource(QDir(DEPLOYMENT_PATH).filePath("qml/MenuItem.qml")); + opt->show(); + + QQuickItem* root = opt->rootObject(); + root->setProperty("itemNumber", QVariant(index)); + root->setParentItem(optionsView); + + root->setProperty("t_name",QVariant(option.name.c_str())); + root->setProperty("t_desc",QVariant(option.desc.c_str())); + root->setProperty("t_imgFile",QVariant(("file://"+configDir+"/icons/"+option.imgFile+".png").c_str())); + + QObject* mouseArea = root->children().at(0); + Handler* handler = new Handler(option.call, option.term, app, mainView, mouseArea); + root->children().at(0)->installEventFilter(handler); + + option.object = opt; + option.handler = handler; +} + +void Options::error(std::string text) { + std::cout << "!! Error: " << text << std::endl; +} + + +// Stolen shamelessly from Martin Broadhurst +bool Options::read_directory(const std::string name, + std::vector& filenames) +{ + DIR* dirp = opendir(name.c_str()); + if(dirp == nullptr) { + return false; + } + + struct dirent * dp; + while ((dp = readdir(dirp)) != NULL) { + std::string dn = dp->d_name; + + if(dn == "." || dn == ".."|| dn == "icons") continue; + + filenames.push_back(dn); + } + closedir(dirp); + return true; +} diff --git a/options.h b/options.h new file mode 100644 index 0000000..2e21372 --- /dev/null +++ b/options.h @@ -0,0 +1,32 @@ +#pragma once + +#include +#include +#include +#include +#include "mainview.h" + +struct OptionItem { + std::string name; + std::string desc; + std::string call; + std::string term; + std::string imgFile; + QObject* object; + QObject* handler; +}; + +class Options +{ +public: + Options(MainView* mainView, QGuiApplication* app); + bool read_directory(const std::string name, std::vector& files); +private: + void error(std::string text); + void createOption(OptionItem &option, size_t index); + + MainView* mainView; + QQuickItem* optionsView; + std::vector optionList; + QGuiApplication* app; +}; diff --git a/qml/Main.qml b/qml/Main.qml index a4b3a93..32e6864 100644 --- a/qml/Main.qml +++ b/qml/Main.qml @@ -3,85 +3,84 @@ import "../js/Main.js" as MainJS Rectangle { id: canvas - width: screenGeometry.width - height: screenGeometry.height + width: 1404 + height: 1872 - MouseArea { - id: ma - anchors.fill: parent - hoverEnabled: true - - onClicked: { - console.log("big: click detected"); + Behavior on y { + NumberAnimation { + property:"y" + to:0 + duration:1000 } } - Rectangle { - id: infoRect - function toggleInfo() { - infoRect.visible = !infoRect.visible; - console.log("info set to " + infoRect.visible); - // bad way to refresh the item - clock.visible = false - clock.visible = true - } - width: 800 - height: 500 - anchors.centerIn: parent - border.width: 10 - Text { - anchors { - fill: parent - margins: 5 - } - id: info - text: ( - "pos: (" - + ma.mouseX.toFixed(2) + "," + ma.mouseY.toFixed(2) + ")" - + "\n small Area is pressed: " + maSmall.pressed - + "\n containsPress: " + ma.containsPress - ) + Text { + id: heading + anchors.left:parent.left + anchors.right:parent.right + text: " draft launcher" + textFormat: Text.RichText + horizontalAlignment: Text.AlignHCenter + font.pixelSize: 80 + font.family:"Noto Serif" + font.italic:true + } + + Text { + id: credit + anchors.right:parent.right + anchors.bottom:parent.bottom + anchors.margins: {bottom:10} + text: qsTr("draft v0.2 created by @dixonary_") + font.pixelSize: 30 + font.family:"Noto Serif" + font.italic:true } + + Text { + id: optionsHeading + anchors.left:parent.left + anchors.right:parent.right + anchors.margins:50 + horizontalAlignment:Text.AlignLeft + font.pixelSize: 40 + font.family:"Noto Serif" + font.italic:true + text: "Available Options" + y: heading.y + heading.height + 50 } Rectangle { - id: clock - width: 200 - height: 50 - y: screenGeometry.height- clock.height - x: screenGeometry.width - clock.width - color: !infoRect.visible ? "white" : "black" - border.width: 2 - border.color: infoRect.visible ? "white" : "black" - + id:optionsArea + objectName: "optionsArea" + y:optionsHeading.y + optionsHeading.height + 10 + anchors.left:parent.left + anchors.right:parent.right + height:credit.y - y - 5 - MouseArea { - id: maSmall - anchors.fill: parent - hoverEnabled: true - onClicked: console.log("small: click detected") - onPressAndHold: { - infoRect.toggleInfo(); - console.log("small: HOLD"); - } + Rectangle { + id:topBar + color: "black" + anchors.left:parent.left + anchors.right:parent.right + anchors.margins: 50 + anchors.topMargin: 0 + height:5 } - - Text { - color: infoRect.visible ? "white" : "black" - id: time - anchors { - left: parent.left - leftMargin: 8 - } - text: "" + Rectangle { + id:bottomBar + color: "black" + anchors.left:parent.left + anchors.right:parent.right + anchors.bottom:parent.bottom + anchors.margins: 50 + anchors.bottomMargin:0 + height:5 } - Timer { - interval: 500 - running: true - repeat: true - onTriggered: { - time.text = MainJS.displayDateAsHHMMSS(); - } + MouseArea { + anchors.fill:parent } + } + } diff --git a/qml/MenuItem.qml b/qml/MenuItem.qml new file mode 100644 index 0000000..c3f0d7b --- /dev/null +++ b/qml/MenuItem.qml @@ -0,0 +1,58 @@ +import QtQuick 2.0 +import "../js/Main.js" as MainJS + +Rectangle { + property int itemNumber: 0 + + property alias t_name : tname.text + property alias t_desc : tdesc.text + property alias t_imgFile: timg.source + //property alias t_imgFile: imgLoader.src + + + y: itemNumber * 200 + 30 + anchors.left:parent.left + anchors.right:parent.right + height:180 + anchors.margins:70 + border.color:"#cccccc" + border.width: 3 + + MouseArea { + id:ma + anchors.fill:parent + } + + Text { + id:tname + anchors.top:parent.top + anchors.left:parent.left + anchors.right:parent.right + anchors.topMargin:10 + anchors.leftMargin:20 + font.family:"Noto Serif" + font.pixelSize:80 + font.italic: true + } + Text { + id:tdesc + anchors.bottom:parent.bottom + anchors.left:parent.left + anchors.right:parent.right + anchors.leftMargin:20 + anchors.bottomMargin:10 + font.family:"Noto Serif" + font.pixelSize:40 + font.italic: true + } + Image { + id:timg + fillMode: Image.PreserveAspectFit + width: height + anchors.top: parent.top + anchors.right: parent.right + anchors.bottom:parent.bottom + anchors.margins:15 + } + +} diff --git a/readme.md b/readme.md deleted file mode 100644 index 0461f6e..0000000 --- a/readme.md +++ /dev/null @@ -1,6 +0,0 @@ -## reHackable-HelloWorld -This is actually a very first version of 'reHackable-launcher', which has the objective to offer an interface to launch other utilities/bash scripts from an interface appart from Xochitl. - -Built with QtCreator. I followed [this guide for the RPi](http://www.jumpnowtek.com/rpi/Qt-Creator-Setup-for-RPi-cross-development.html) to setup the [toolchain offered by reMarkable](https://remarkable.engineering/deploy/sdk/) in Qt Creator. - -