diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index bd51e0b04..cfdef26cb 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -46,6 +46,7 @@ jobs: cd "$GITHUB_WORKSPACE"/toltec/package/oxide tar -czvf ./oxide.tar.gz "$GITHUB_WORKSPACE"/result/* cp "$GITHUB_WORKSPACE"/package . + sed -i "s/~VERSION~/$(cat version.txt)/" ./package cat ./package cd "$GITHUB_WORKSPACE"/toltec make oxide diff --git a/Makefile b/Makefile index e26c11a81..7cf5decfa 100644 --- a/Makefile +++ b/Makefile @@ -51,11 +51,12 @@ release: clean INSTALL_ROOT=../../release $(MAKE) -C .build/system-service install INSTALL_ROOT=../../release $(MAKE) -C .build/settings-manager install INSTALL_ROOT=../../release $(MAKE) -C .build/screenshot-tool install + INSTALL_ROOT=../../release $(MAKE) -C .build/screenshot-viewer install INSTALL_ROOT=../../release $(MAKE) -C .build/launcher install INSTALL_ROOT=../../release $(MAKE) -C .build/lockscreen install INSTALL_ROOT=../../release $(MAKE) -C .build/task-switcher install -build: tarnish erode rot oxide decay corrupt fret +build: tarnish erode rot oxide decay corrupt fret anxiety erode: mkdir -p .build/process-manager @@ -98,3 +99,9 @@ corrupt: tarnish cp -r applications/task-switcher/* .build/task-switcher cd .build/task-switcher && qmake corrupt.pro $(MAKE) -C .build/task-switcher all + +anxiety: tarnish + mkdir -p .build/screenshot-viewer + cp -r applications/screenshot-viewer/* .build/screenshot-viewer + cd .build/screenshot-viewer && qmake anxiety.pro + $(MAKE) -C .build/screenshot-viewer all diff --git a/applications/launcher/widgets/AppItem.qml b/applications/launcher/widgets/AppItem.qml index ab9a6026f..1317753af 100644 --- a/applications/launcher/widgets/AppItem.qml +++ b/applications/launcher/widgets/AppItem.qml @@ -27,6 +27,8 @@ Item { height: root.height * 0.70 width: root.width * 0.90 source: root.source + sourceSize.width: width + sourceSize.height: height } Text { text: root.text diff --git a/applications/lockscreen/controller.h b/applications/lockscreen/controller.h index 42a910d27..5602d00ab 100644 --- a/applications/lockscreen/controller.h +++ b/applications/lockscreen/controller.h @@ -185,6 +185,7 @@ class Controller : public QObject { if(state == "loaded" && pin == storedPin()){ qDebug() << "PIN matches!"; QTimer::singleShot(200, [this]{ + onLogin(); if(getPinEntryUI()){ pinEntryUI->setProperty("message", ""); pinEntryUI->setProperty("pin", ""); @@ -200,6 +201,7 @@ class Controller : public QObject { }else if(state == "loaded"){ qDebug() << "PIN doesn't match!"; + onFailedLogin(); return false; } if(state == "prompt"){ @@ -393,6 +395,36 @@ private slots: void chargerWarning(){ // TODO handle charger } + void onLogin(){ + if(!settings.contains("onLogin")){ + return; + } + auto path = settings.value("onLogin").toString(); + if(!QFile::exists(path)){ + qWarning() << "onLogin script does not exist" << path; + return; + } + if(!QFileInfo(path).isExecutable()){ + qWarning() << "onLogin script is not executable" << path; + return; + } + QProcess::execute(path); + } + void onFailedLogin(){ + if(!settings.contains("onFailedLogin")){ + return; + } + auto path = settings.value("onFailedLogin").toString(); + if(!QFile::exists(path)){ + qWarning() << "onFailedLogin script does not exist" << path; + return; + } + if(!QFileInfo(path).isExecutable()){ + qWarning() << "onFailedLogin script is not executable" << path; + return; + } + QProcess::execute(path); + } private: QString confirmPin; diff --git a/applications/process-manager/erode.pro b/applications/process-manager/erode.pro index b269bd89d..36290b22f 100755 --- a/applications/process-manager/erode.pro +++ b/applications/process-manager/erode.pro @@ -30,6 +30,10 @@ QML_DESIGNER_IMPORT_PATH = target.path = /opt/bin !isEmpty(target.path): INSTALLS += target +icons.files = ../../assets/etc/draft/icons/erode.svg +icons.path = /opt/etc/draft/icons +INSTALLS += icons + HEADERS += \ controller.h \ taskitem.h \ diff --git a/applications/screenshot-viewer/.gitignore b/applications/screenshot-viewer/.gitignore new file mode 100644 index 000000000..fab7372d7 --- /dev/null +++ b/applications/screenshot-viewer/.gitignore @@ -0,0 +1,73 @@ +# This file is used to ignore files which are generated +# ---------------------------------------------------------------------------- + +*~ +*.autosave +*.a +*.core +*.moc +*.o +*.obj +*.orig +*.rej +*.so +*.so.* +*_pch.h.cpp +*_resource.rc +*.qm +.#* +*.*# +core +!core/ +tags +.DS_Store +.directory +*.debug +Makefile* +*.prl +*.app +moc_*.cpp +ui_*.h +qrc_*.cpp +Thumbs.db +*.res +*.rc +/.qmake.cache +/.qmake.stash + +# qtcreator generated files +*.pro.user* + +# xemacs temporary files +*.flc + +# Vim temporary files +.*.swp + +# Visual Studio generated files +*.ib_pdb_index +*.idb +*.ilk +*.pdb +*.sln +*.suo +*.vcproj +*vcproj.*.*.user +*.ncb +*.sdf +*.opensdf +*.vcxproj +*vcxproj.* + +# MinGW generated files +*.Debug +*.Release + +# Python byte code +*.pyc + +# Binaries +# -------- +*.dll +*.exe + diff --git a/applications/screenshot-viewer/anxiety.pro b/applications/screenshot-viewer/anxiety.pro new file mode 100644 index 000000000..34b437879 --- /dev/null +++ b/applications/screenshot-viewer/anxiety.pro @@ -0,0 +1,48 @@ +QT += gui +QT += quick +QT += dbus + +CONFIG += c++11 +CONFIG += qml_debug + +# The following define makes your compiler emit warnings if you use +# any feature of Qt which as been marked deprecated (the exact warnings +# depend on your compiler). Please consult the documentation of the +# deprecated API in order to know how to port your code away from it. +DEFINES += QT_DEPRECATED_WARNINGS + +# You can make your code fail to compile if it uses deprecated APIs. +# In order to do so, uncomment the following line. +#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 + +linux-oe-g++ { + LIBS += -lqsgepaper +} + +SOURCES += \ + main.cpp \ + ../../shared/devicesettings.cpp \ + ../../shared/eventfilter.cpp + +# Default rules for deployment. +target.path = /opt/bin +!isEmpty(target.path): INSTALLS += target + +icons.files = ../../assets/etc/draft/icons/image.svg +icons.path = /opt/etc/draft/icons +INSTALLS += icons + +DBUS_INTERFACES += ../../interfaces/dbusservice.xml +DBUS_INTERFACES += ../../interfaces/screenapi.xml +DBUS_INTERFACES += ../../interfaces/screenshot.xml + +INCLUDEPATH += ../../shared +HEADERS += \ + ../../shared/dbussettings.h \ + ../../shared/devicesettings.h \ + ../../shared/eventfilter.h \ + controller.h \ + screenshotlist.h + +RESOURCES += \ + qml.qrc diff --git a/applications/screenshot-viewer/anxiety.user b/applications/screenshot-viewer/anxiety.user new file mode 100644 index 000000000..5de333197 --- /dev/null +++ b/applications/screenshot-viewer/anxiety.user @@ -0,0 +1,367 @@ + + + + + + EnvironmentId + {d6e5e884-ff5d-4653-8683-d295d8e7161a} + + + ProjectExplorer.Project.ActiveTarget + 0 + + + ProjectExplorer.Project.EditorSettings + + true + false + true + + Cpp + + CppGlobal + + + + QmlJS + + QmlJSGlobal + + + 2 + UTF-8 + false + 4 + false + 80 + true + true + 1 + true + false + 0 + true + true + 0 + 8 + true + 1 + true + true + true + *.md, *.MD, Makefile + false + true + + + + ProjectExplorer.Project.PluginSettings + + + true + true + true + true + true + + + 0 + true + + true + Builtin.Questionable + + false + true + Builtin.DefaultTidyAndClazy + 12 + + + + true + + + + + ProjectExplorer.Project.Target.0 + + GenericLinuxOsType + reMarkable + reMarkable + {208a71d7-d0a7-4e78-8ae0-0613f895950a} + 0 + 0 + 0 + + 1 + /home/eeems/git/Github/Eeems/oxide/.build/build-decay-reMarkable-Debug + /home/eeems/git/Github/Eeems/oxide/.build/build-decay-reMarkable-Debug + + + true + QtProjectManager.QMakeBuildStep + + false + + + + true + Qt4ProjectManager.MakeStep + + 2 + Build + Build + ProjectExplorer.BuildSteps.Build + + + + true + Qt4ProjectManager.MakeStep + clean + + 1 + Clean + Clean + ProjectExplorer.BuildSteps.Clean + + 2 + false + + + Debug + Qt4ProjectManager.Qt4BuildConfiguration + 2 + + + /home/eeems/git/Github/Eeems/oxide/.build/build-decay-reMarkable-Release + /home/eeems/git/Github/Eeems/oxide/.build/build-decay-reMarkable-Release + + + true + QtProjectManager.QMakeBuildStep + + false + + + + true + Qt4ProjectManager.MakeStep + + 2 + Build + Build + ProjectExplorer.BuildSteps.Build + + + + true + Qt4ProjectManager.MakeStep + clean + + 1 + Clean + Clean + ProjectExplorer.BuildSteps.Clean + + 2 + false + + + Release + Qt4ProjectManager.Qt4BuildConfiguration + 0 + 0 + + + 0 + /home/eeems/git/Github/Eeems/oxide/.build/build-decay-reMarkable-Profile + /home/eeems/git/Github/Eeems/oxide/.build/build-decay-reMarkable-Profile + + + true + QtProjectManager.QMakeBuildStep + + false + + + + true + Qt4ProjectManager.MakeStep + + 2 + Build + Build + ProjectExplorer.BuildSteps.Build + + + + true + Qt4ProjectManager.MakeStep + clean + + 1 + Clean + Clean + ProjectExplorer.BuildSteps.Clean + + 2 + false + + + Profile + Qt4ProjectManager.Qt4BuildConfiguration + 0 + 0 + 0 + + 3 + + + + true + RemoteLinux.CheckForFreeDiskSpaceStep + + + + + / + 5242880 + + + + + true + RemoteLinux.KillAppStep + + + + + + + + + true + RemoteLinux.DirectUploadStep + + /home/eeems/git/Github/Eeems/oxide/.build/build-screenshot-reMarkable-Release/fret + /home/eeems/git/Github/Eeems/oxide/.build/build-decay-reMarkable-Debug/decay + + + remarkable + remarkable + + + /opt/bin + /opt/bin + + + /opt/poky/1.8/sysroots/cortexa9hf-neon-oe-linux-gnueabi + /opt/poky/1.8/sysroots/cortexa9hf-neon-oe-linux-gnueabi + + + 2020-12-31T22:31:26.275 + 2021-01-16T22:24:25.237 + + + 2020-12-31T22:31:27.000 + 2021-01-16T22:24:30.000 + + + 3 + Deploy + Deploy + ProjectExplorer.BuildSteps.Deploy + + 1 + + false + DeployToGenericLinux + + 1 + + dwarf + + cpu-cycles + + + 250 + + -e + cpu-cycles + --call-graph + dwarf,4096 + -F + 250 + + -F + true + 4096 + false + false + 1000 + + true + + false + false + false + false + true + 0.01 + 10 + true + kcachegrind + 1 + 25 + + 1 + true + false + true + valgrind + + 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 12 + 13 + 14 + + + 1 + + decay (on reMarkable)2 + RemoteLinuxRunConfiguration:/home/eeems/git/Github/Eeems/oxide/applications/lockscreen/decay.pro + /home/eeems/git/Github/Eeems/oxide/applications/lockscreen/decay.pro + 1 + false + true + false + true + :1 + + 1 + + + + ProjectExplorer.Project.TargetCount + 1 + + + ProjectExplorer.Project.Updater.FileVersion + 22 + + + Version + 22 + + diff --git a/applications/screenshot-viewer/controller.h b/applications/screenshot-viewer/controller.h new file mode 100644 index 000000000..37e154427 --- /dev/null +++ b/applications/screenshot-viewer/controller.h @@ -0,0 +1,137 @@ +#ifndef CONTROLLER_H +#define CONTROLLER_H + +#include +#include +#include + +#include + +#include "dbussettings.h" + +#include "dbusservice_interface.h" +#include "screenapi_interface.h" +#include "screenshot_interface.h" + +#include "screenshotlist.h" + +using namespace codes::eeems::oxide1; + +#define CORRUPT_SETTINGS_VERSION 1 + +enum State { Normal, PowerSaving }; +enum BatteryState { BatteryUnknown, BatteryCharging, BatteryDischarging, BatteryNotPresent }; +enum ChargerState { ChargerUnknown, ChargerConnected, ChargerNotConnected, ChargerNotPresent }; +enum WifiState { WifiUnknown, WifiOff, WifiDisconnected, WifiOffline, WifiOnline}; + +class Controller : public QObject { + Q_OBJECT + Q_PROPERTY(ScreenshotList* screenshots MEMBER screenshots READ getScreenshots NOTIFY screenshotsChanged) + Q_PROPERTY(int columns READ columns WRITE setColumns NOTIFY columnsChanged) +public: + Controller(QObject* parent) + : QObject(parent), settings(this), applications() { + screenshots = new ScreenshotList(); + auto bus = QDBusConnection::systemBus(); + qDebug() << "Waiting for tarnish to start up..."; + while(!bus.interface()->registeredServiceNames().value().contains(OXIDE_SERVICE)){ + struct timespec args{ + .tv_sec = 1, + .tv_nsec = 0, + }, res; + nanosleep(&args, &res); + } + api = new General(OXIDE_SERVICE, OXIDE_SERVICE_PATH, bus, this); + + qDebug() << "Requesting screen API..."; + QDBusObjectPath path = api->requestAPI("screen"); + if(path.path() == "/"){ + qDebug() << "Unable to get screen API"; + throw ""; + } + screenApi = new Screen(OXIDE_SERVICE, path.path(), bus, this); + connect(screenApi, &Screen::screenshotAdded, this, &Controller::screenshotAdded); + connect(screenApi, &Screen::screenshotModified, this, &Controller::screenshotModified); + connect(screenApi, &Screen::screenshotRemoved, this, &Controller::screenshotRemoved); + + for(auto path : screenApi->screenshots()){ + screenshots->append(new Screenshot(OXIDE_SERVICE, path.path(), bus, this)); + } + + settings.sync(); + auto version = settings.value("version", 0).toInt(); + if(version < CORRUPT_SETTINGS_VERSION){ + migrate(&settings, version); + } + } + ~Controller(){} + + Q_INVOKABLE void startup(){ + qDebug() << "Running controller startup"; + QTimer::singleShot(10, [this]{ + setState("loaded"); + }); + } + QString state() { + if(!getStateControllerUI()){ + return "loading"; + } + return stateControllerUI->property("state").toString(); + } + void setState(QString state){ + if(!getStateControllerUI()){ + throw "Unable to find state controller"; + } + stateControllerUI->setProperty("state", state); + } + int columns() { return settings.value("columns", 3).toInt(); } + void setColumns(int columns){ + settings.setValue("columns", columns); + emit columnsChanged(columns); + } + ScreenshotList* getScreenshots(){ return screenshots; } + + void setRoot(QObject* root){ this->root = root; } + +signals: + void screenshotsChanged(ScreenshotList*); + void columnsChanged(int); + +private slots: + void screenshotAdded(const QDBusObjectPath& path){ + screenshots->append(new Screenshot(OXIDE_SERVICE, path.path(), QDBusConnection::systemBus(), this)); + emit screenshotsChanged(screenshots); + } + void screenshotModified(const QDBusObjectPath& path){ + Q_UNUSED(path); + emit screenshotsChanged(screenshots); + } + void screenshotRemoved(const QDBusObjectPath& path){ + screenshots->remove(path); + emit screenshotsChanged(screenshots); + } + +private: + QSettings settings; + ScreenshotList* screenshots; + General* api; + Screen* screenApi; + QObject* root = nullptr; + QObject* stateControllerUI = nullptr; + QList applications; + + QObject* getStateControllerUI(){ + stateControllerUI = root->findChild("stateController"); + return stateControllerUI; + } + static void migrate(QSettings* settings, int fromVersion){ + if(fromVersion != 0){ + throw "Unknown settings version"; + } + // In the future migrate changes to settings between versions + settings->setValue("version", CORRUPT_SETTINGS_VERSION); + settings->sync(); + } +}; + +#endif // CONTROLLER_H diff --git a/applications/screenshot-viewer/font/icomoon.ttf b/applications/screenshot-viewer/font/icomoon.ttf new file mode 100644 index 000000000..90ea4d8d8 Binary files /dev/null and b/applications/screenshot-viewer/font/icomoon.ttf differ diff --git a/applications/screenshot-viewer/main.cpp b/applications/screenshot-viewer/main.cpp new file mode 100644 index 000000000..c22b7bc33 --- /dev/null +++ b/applications/screenshot-viewer/main.cpp @@ -0,0 +1,68 @@ +#include +#include +#include +#include + +#include +#include + +#include "dbussettings.h" +#include "devicesettings.h" +#include "controller.h" +#include "eventfilter.h" + +#ifdef __arm__ +Q_IMPORT_PLUGIN(QsgEpaperPlugin) +#endif + +#include "dbusservice_interface.h" + +using namespace std; + +const char* qt_version = qVersion(); + +void sigHandler(int signal){ + ::signal(signal, SIG_DFL); + qApp->quit(); +} + +int main(int argc, char *argv[]){ + if (strcmp(qt_version, QT_VERSION_STR) != 0){ + qDebug() << "Version mismatch, Runtime: " << qt_version << ", Build: " << QT_VERSION_STR; + } +#ifdef __arm__ + // Setup epaper + qputenv("QMLSCENE_DEVICE", "epaper"); + qputenv("QT_QPA_PLATFORM", "epaper:enable_fonts"); + qputenv("QT_QPA_EVDEV_TOUCHSCREEN_PARAMETERS", deviceSettings.getTouchEnvSetting()); + qputenv("QT_QPA_GENERIC_PLUGINS", "evdevtablet"); +// qputenv("QT_DEBUG_BACKINGSTORE", "1"); +#endif + QGuiApplication app(argc, argv); + auto filter = new EventFilter(&app); + app.installEventFilter(filter); + app.setOrganizationName("Eeems"); + app.setOrganizationDomain(OXIDE_SERVICE); + app.setApplicationName("anxiety"); + app.setApplicationDisplayName("Screenshots"); + app.setApplicationVersion(OXIDE_INTERFACE_VERSION); + Controller controller(&app); + QQmlApplicationEngine engine; + QQmlContext* context = engine.rootContext(); + context->setContextProperty("screenGeometry", app.primaryScreen()->geometry()); + context->setContextProperty("controller", &controller); + engine.load(QUrl(QStringLiteral("qrc:/main.qml"))); + if (engine.rootObjects().isEmpty()){ + qDebug() << "Nothing to display"; + return -1; + } + auto root = engine.rootObjects().first(); + filter->root = (QQuickItem*)root; + controller.setRoot(root); + + signal(SIGINT, sigHandler); + signal(SIGSEGV, sigHandler); + signal(SIGTERM, sigHandler); + + return app.exec(); +} diff --git a/applications/screenshot-viewer/main.qml b/applications/screenshot-viewer/main.qml new file mode 100644 index 000000000..f6b51c90b --- /dev/null +++ b/applications/screenshot-viewer/main.qml @@ -0,0 +1,247 @@ +import QtQuick 2.10 +import QtQuick.Window 2.3 +import QtQuick.Controls 2.4 +import QtQuick.Layouts 1.0 +import "./widgets" + +ApplicationWindow { + id: window + objectName: "window" + visible: stateController.state !== "loading" + width: screenGeometry.width + height: screenGeometry.height + title: { + if(stateController.state !== "viewing" || !viewer.model){ + return Qt.application.displayName; + } + return viewer.model.display.path; + } + FontLoader { id: iconFont; source: "/font/icomoon.ttf" } + Component.onCompleted: { + controller.startup(); + } + header: Rectangle { + color: "black" + height: menu.height + RowLayout { + id: menu + anchors.left: parent.left + anchors.right: parent.right + Label { + text: "⬅️" + color: "white" + topPadding: 5 + bottomPadding: 5 + leftPadding: 10 + rightPadding: 10 + MouseArea { + anchors.fill: parent + onClicked: { + if(stateController.state !== "viewing"){ + Qt.quit(); + return; + } + viewer.model = undefined; + stateController.state = "loaded"; + } + } + } + Item { Layout.fillWidth: true } + Label { + color: "white" + text: window.title + } + Item { Layout.fillWidth: true } + Label { + text: "Delete" + color: "white" + visible: stateController.state === "viewing" + topPadding: 5 + bottomPadding: 5 + leftPadding: 10 + rightPadding: 10 + MouseArea { + anchors.fill: parent + onClicked: { + controller.screenshots.remove(viewer.model.display.path); + viewer.model = undefined; + stateController.state = "loading"; + } + } + } + CustomMenu { + visible: stateController.state === "loaded" + BetterMenu { + title: "" + font: iconFont.name + width: 310 + Action { + text: controller.columns == 4 ? "* Small" : "Small" + onTriggered: controller.columns = 4 + } + Action { + text: controller.columns == 3 ? "* Medium" : "Medium" + onTriggered: controller.columns = 3 + } + Action { + text: controller.columns == 2 ? "* Large" : "Large" + onTriggered: controller.columns = 2 + } + } + } + } + } + contentData: [ + Rectangle { + anchors.fill: parent + color: "white" + }, + ColumnLayout { + anchors.fill: parent + enabled: stateController.state == "loaded" + visible: enabled + BetterButton { + text: "▲" + visible: !screenshots.atYBeginning + Layout.fillWidth: true + onClicked: { + console.log("Scroll up"); + screenshots.currentIndex = screenshots.currentIndex - screenshots.pageSize(); + if(screenshots.currentIndex < 0){ + screenshots.currentIndex = 0; + screenshots.positionViewAtBeginning(); + }else{ + screenshots.positionViewAtIndex(screenshots.currentIndex, ListView.Beginning); + } + } + } + GridView { + id: screenshots + model: controller.screenshots + Layout.fillHeight: true + Layout.fillWidth: true + clip: true + cellWidth: parent.width / controller.columns + cellHeight: cellWidth + delegate: AppItem { + enabled: screenshots.enabled + text: model.display.name + source: 'file:' + model.display.path + width: screenshots.cellWidth + height: screenshots.cellHeight + onClicked: { + if(!screenshots.flicking){ + console.log("Opening " + model.display.path); + viewer.model = model; + stateController.state = "viewing"; + } + } + onLongPress: !screenshots.flicking && controller.screenshots.remove(model.display.path) + } + interactive: false + boundsBehavior: Flickable.StopAtBounds + ScrollBar.vertical: ScrollBar { + id: scrollbar + snapMode: ScrollBar.SnapAlways + policy: ScrollBar.AlwaysOn + contentItem: Rectangle { + color: "white" + implicitWidth: 6 + implicitHeight: 100 + radius: width / 2 + } + background: Rectangle { + color: "black" + implicitWidth: 6 + implicitHeight: 100 + radius: width / 2 + } + } + function pageWidth(){ + return controller.columns; + } + function pageHeight(){ + var item = itemAt(0, 0), + itemHeight = item ? item.height : height; + return height / itemHeight; + } + function pageSize(){ + return Math.floor(pageHeight() * pageWidth()); + } + } + BetterButton{ + text: "▼" + Layout.fillWidth: true + visible: !screenshots.atYEnd + onClicked: { + console.log("Scroll down"); + screenshots.currentIndex = screenshots.currentIndex + screenshots.pageSize(); + if(screenshots.currentIndex > screenshots.count){ + screenshots.currentIndex = screenshots.count; + screenshots.positionViewAtEnd(); + }else{ + screenshots.positionViewAtIndex(screenshots.currentIndex, ListView.Beginning); + } + } + } + }, + Item { + id: viewer + anchors.fill: parent + visible: stateController.state == "viewing" + property var model + Label { + visible: viewerImage.status == Image.Loading + anchors.centerIn: parent + text: "Loading..." + } + Image { + id: viewerImage + asynchronous: true + visible: status != Image.Loading + anchors.fill: parent + fillMode: Image.PreserveAspectFit + source: parent.model ? 'file:' + parent.model.display.path : '' + sourceSize.width: width + sourceSize.height: height + } + } + ] + StateGroup { + id: stateController + objectName: "stateController" + state: "loading" + states: [ + State { name: "loaded" }, + State { name: "loading" }, + State { name: "viewing" } + ] + transitions: [ + Transition { + from: "*"; to: "loaded" + SequentialAnimation { + ScriptAction { script: { + console.log("Display loaded"); + } } + } + }, + Transition { + from: "*"; to: "loading" + SequentialAnimation { + ScriptAction { script: { + console.log("Loading display"); + controller.startup(); + } } + } + }, + Transition { + from: "*"; to: "viewing" + SequentialAnimation { + ScriptAction { script: { + console.log("Viewing Image"); + } } + } + } + ] + } +} diff --git a/applications/screenshot-viewer/qml.qrc b/applications/screenshot-viewer/qml.qrc new file mode 100644 index 000000000..61cb58a03 --- /dev/null +++ b/applications/screenshot-viewer/qml.qrc @@ -0,0 +1,12 @@ + + + main.qml + widgets/AppItem.qml + widgets/CustomMenu.qml + widgets/BetterMenu.qml + font/icomoon.ttf + widgets/StatusIcon.qml + widgets/BetterButton.qml + widgets/SwipeArea.qml + + diff --git a/applications/screenshot-viewer/screenshotlist.h b/applications/screenshot-viewer/screenshotlist.h new file mode 100644 index 000000000..03e896a2f --- /dev/null +++ b/applications/screenshot-viewer/screenshotlist.h @@ -0,0 +1,160 @@ +#ifndef SCREENSHOTLIST_H +#define SCREENSHOTLIST_H + +#include + +#include "screenshot_interface.h" + +using namespace codes::eeems::oxide1; + +class ScreenshotItem : public QObject { + Q_OBJECT + Q_PROPERTY(QString path READ path NOTIFY pathChanged) + Q_PROPERTY(QString name READ name NOTIFY nameChanged) +public: + ScreenshotItem(Screenshot* screenshot, QObject* parent) : QObject(parent) { + m_screenshot = screenshot; + connect(screenshot, &Screenshot::modified, this, &ScreenshotItem::modified); + } + ~ScreenshotItem() { + if(m_screenshot != nullptr){ + delete m_screenshot; + } + } + QString path() { + if(m_screenshot == nullptr){ + return ""; + } + return m_screenshot->path(); + } + QString name() { return QFileInfo(path()).baseName(); } + bool is(Screenshot* screenshot) { return screenshot == m_screenshot; } + Screenshot* screenshot() { return m_screenshot; } + bool remove() { + auto reply = m_screenshot->remove(); + reply.waitForFinished(); + if(reply.isError() || !reply.isValid()){ + qDebug() << reply.error(); + return false; + } + return true; + } + QString qPath() { return m_screenshot->QDBusAbstractInterface::path(); } +signals: + void pathChanged(QString); + void nameChanged(QString); + +public slots: + void modified(){ + emit pathChanged(path()); + emit nameChanged(name()); + } + +private: + Screenshot* m_screenshot; +}; + + +class ScreenshotList : public QAbstractListModel +{ + Q_OBJECT +public: + ScreenshotList() : QAbstractListModel(nullptr) {} + + QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override { + Q_UNUSED(section) + Q_UNUSED(orientation) + Q_UNUSED(role) + return QVariant(); + } + int rowCount(const QModelIndex& parent = QModelIndex()) const override{ + if(parent.isValid()){ + return 0; + } + return screenshots.length(); + } + QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override{ + if(!index.isValid()){ + return QVariant(); + } + if(index.column() > 0){ + return QVariant(); + } + if(index.row() >= screenshots.length()){ + return QVariant(); + } + if(role != Qt::DisplayRole){ + return QVariant(); + } + return QVariant::fromValue(screenshots[index.row()]); + } + void append(Screenshot* screenshot){ + beginInsertRows(QModelIndex(), screenshots.length(), screenshots.length()); + screenshots.append(new ScreenshotItem(screenshot, this)); + endInsertRows(); + emit updated(); + } + ScreenshotItem* get(const QDBusObjectPath& path){ + auto pathString = path.path(); + for(auto screenshot : screenshots){ + if(pathString == screenshot->qPath()){ + return screenshot; + } + } + return nullptr; + } + Q_INVOKABLE void clear(){ + beginRemoveRows(QModelIndex(), 0, screenshots.length()); + for(auto screenshot : screenshots){ + screenshot->remove(); + delete screenshot; + } + screenshots.clear(); + endRemoveRows(); + emit updated(); + } + Q_INVOKABLE void remove(QString path){ + QMutableListIterator i(screenshots); + while(i.hasNext()){ + auto screenshot = i.next(); + if(screenshot->path() == path){ + beginRemoveRows(QModelIndex(), screenshots.indexOf(screenshot), screenshots.indexOf(screenshot)); + i.remove(); + screenshot->remove(); + delete screenshot; + endRemoveRows(); + } + } + emit updated(); + } + void remove(const QDBusObjectPath& path){ + auto screenshot = get(path); + if(screenshot != nullptr){ + remove(screenshot->path()); + } + } + int removeAll(Screenshot* screenshot) { + QMutableListIterator i(screenshots); + int count = 0; + while(i.hasNext()){ + auto item = i.next(); + if(item->is(screenshot) && item->remove()){ + beginRemoveRows(QModelIndex(), screenshots.indexOf(item), screenshots.indexOf(item)); + i.remove(); + delete item; + endRemoveRows(); + count++; + } + } + emit updated(); + return count; + } + int length() { return screenshots.length(); } + bool empty() { return screenshots.empty(); } +signals: + void updated(); +private: + QList screenshots; +}; + +#endif // SCREENSHOTLIST_H diff --git a/applications/screenshot-viewer/widgets/AppItem.qml b/applications/screenshot-viewer/widgets/AppItem.qml new file mode 100644 index 000000000..fd4e01858 --- /dev/null +++ b/applications/screenshot-viewer/widgets/AppItem.qml @@ -0,0 +1,97 @@ +import QtQuick 2.10 +import QtQuick.Window 2.3 +import QtQuick.Controls 2.4 +import QtQuick.Layouts 1.0 + +Item { + id: root + clip: true + width: height + enabled: visible + state: "released" + signal clicked; + signal longPress; + property string source: "qrc:/img/icon.png" + property string text: "" + property bool bold: false + Item { + id: spacer + height: root.height * 0.05 + width: root.width * 0.05 + } + Rectangle { + id: icon + anchors.top: spacer.bottom + anchors.left: spacer.right + height: root.height * 0.70 + width: root.width * 0.90 + clip: false + border.width: 1 + border.color: "black" + color: "white" + Item { + id: iconSpacer + width: 1 + height: 1 + } + Image { + fillMode: Image.PreserveAspectFit + anchors.left: iconSpacer.right + anchors.top: iconSpacer.bottom + source: root.source + width: icon.width - 2 + height: icon.height - 2 + sourceSize.width: width + sourceSize.height: height + asynchronous: true + } + } + Text { + text: root.text + width: root.width * 0.90 + height: root.height * 0.20 + font.pointSize: 100 + font.bold: root.bold + font.family: "Noto Serif" + font.italic: true + minimumPointSize: 1 + anchors.top: icon.bottom + anchors.left: icon.left + fontSizeMode: Text.Fit + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignHCenter + } + MouseArea { + anchors.fill: root + enabled: root.enabled + propagateComposedEvents: true + onClicked: { + root.state = "released"; + root.clicked(); + } + onPressAndHold: root.longPress() + onPressed: root.state = "pressed" + onReleased: root.state = "released" + onCanceled: root.state = "released" + } + states: [ + State { name: "released" }, + State { name: "pressed" } + ] + transitions: [ + Transition { + from: "pressed"; to: "released" + ParallelAnimation { + PropertyAction { target: icon; property: "width"; value: root.width * 0.90 } + PropertyAction { target: icon; property: "height"; value: root.height * 0.70 } + } + }, + Transition { + from: "released"; to: "pressed" + ParallelAnimation { + PropertyAction { target: icon; property: "width"; value: icon.width - 10} + PropertyAction { target: icon; property: "height"; value: icon.height - 10 } + } + } + ] +} diff --git a/applications/screenshot-viewer/widgets/BetterButton.qml b/applications/screenshot-viewer/widgets/BetterButton.qml new file mode 100644 index 000000000..cd18271e9 --- /dev/null +++ b/applications/screenshot-viewer/widgets/BetterButton.qml @@ -0,0 +1,26 @@ +import QtQuick 2.0 +import QtQuick.Controls 2.4 + +Button { + id: control + flat: true + contentItem: Text { + text: control.text + font: control.font + opacity: enabled ? 1.0 : 0.3 + color: control.down ? "gray" : "black" + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + elide: Text.ElideRight + } + background: Rectangle { + anchors.fill: control + implicitWidth: 100 + implicitHeight: 40 + opacity: enabled ? 1 : 0.3 + color: "white" + border.color: "black" + border.width: 1 + radius: 2 + } +} diff --git a/applications/screenshot-viewer/widgets/BetterMenu.qml b/applications/screenshot-viewer/widgets/BetterMenu.qml new file mode 100644 index 000000000..8425cff3a --- /dev/null +++ b/applications/screenshot-viewer/widgets/BetterMenu.qml @@ -0,0 +1,62 @@ +import QtQuick 2.10 +import QtQuick.Controls 2.4 + +Menu { + delegate: MenuItem { + id: menuItem + implicitWidth: 250 + implicitHeight: 60 + arrow: Canvas { + x: parent.width - width + implicitWidth: 40 + implicitHeight: 40 + visible: menuItem.subMenu + onPaint: { + var ctx = getContext("2d") + ctx.fillStyle = "black" + ctx.moveTo(15, 15) + ctx.lineTo(width - 15, height / 2) + ctx.lineTo(15, height - 15) + ctx.closePath() + ctx.fill() + } + } + indicator: Item { + implicitWidth: 40 + implicitHeight: 40 + Rectangle { + width: 26 + height: 26 + anchors.centerIn: parent + visible: menuItem.checkable + border.color: "black" + radius: 3 + Rectangle { + width: 14 + height: 14 + anchors.centerIn: parent + visible: menuItem.checked + color: "black" + radius: 2 + } + } + } + contentItem: Text { + leftPadding: menuItem.checkable ? menuItem.indicator.width : 0 + rightPadding: menuItem.subMenu ? menuItem.arrow.width : 0 + text: menuItem.text + font: menuItem.font + opacity: enabled ? 1.0 : 0.3 + color: "black" + horizontalAlignment: Text.AlignLeft + verticalAlignment: Text.AlignVCenter + elide: Text.ElideRight + } + background: Rectangle { + implicitWidth: menuItem.implicitWidth + implicitHeight: menuItem.implicitHeight + opacity: enabled ? 1 : 0.3 + color: "transparent" + } + } +} diff --git a/applications/screenshot-viewer/widgets/CustomMenu.qml b/applications/screenshot-viewer/widgets/CustomMenu.qml new file mode 100644 index 000000000..0654a7930 --- /dev/null +++ b/applications/screenshot-viewer/widgets/CustomMenu.qml @@ -0,0 +1,28 @@ +import QtQuick 2.6 +import QtQuick.Controls 2.4 + +MenuBar { + delegate: MenuBarItem { + id: menuBarItem + contentItem: Text { + text: menuBarItem.text + font: menuBarItem.font + opacity: enabled ? 1.0 : 0.3 + color: "white" + horizontalAlignment: Text.AlignLeft + verticalAlignment: Text.AlignVCenter + elide: Text.ElideRight + } + background: Rectangle { + implicitWidth: 40 + implicitHeight: 40 + opacity: enabled ? 1 : 0.3 + color: "black" + } + } + background: Rectangle { + implicitWidth: 40 + implicitHeight: 40 + color: "black" + } +} diff --git a/applications/screenshot-viewer/widgets/StatusIcon.qml b/applications/screenshot-viewer/widgets/StatusIcon.qml new file mode 100644 index 000000000..82ebc2242 --- /dev/null +++ b/applications/screenshot-viewer/widgets/StatusIcon.qml @@ -0,0 +1,15 @@ +import QtQuick 2.10 +import QtQuick.Controls 2.4 + +Label { + property string source + color: "white" + leftPadding: icon.width + 5 + Image { + id: icon + source: parent.source + height: parent.height - 10 + width: height + fillMode: Image.PreserveAspectFit + } +} diff --git a/applications/screenshot-viewer/widgets/SwipeArea.qml b/applications/screenshot-viewer/widgets/SwipeArea.qml new file mode 100644 index 000000000..bedf05deb --- /dev/null +++ b/applications/screenshot-viewer/widgets/SwipeArea.qml @@ -0,0 +1,67 @@ +/* This code was written by Sergejs Kovrovs and has been placed in the public domain. */ +// https://gist.github.com/kovrov/1742405#file-swipearea-qml + +import QtQuick 2.0 + + +MouseArea { + property point origin + property bool ready: false + property bool swiping: false + signal move(int x, int y) + signal swipe(string direction) + + propagateComposedEvents: true + onPressed: { + drag.axis = Drag.XAndYAxis; + origin = Qt.point(mouse.x, mouse.y); + mouse.accepted = false; + swiping = true; + } + + onPositionChanged: { + switch (drag.axis) { + case Drag.XAndYAxis: + if (Math.abs(mouse.x - origin.x) > 16) { + drag.axis = Drag.XAxis; + } else if (Math.abs(mouse.y - origin.y) > 16) { + drag.axis = Drag.YAxis; + } + mouse.accepted = true; + swiping = containsPress; + break; + case Drag.XAxis: + move(mouse.x - origin.x, 0); + mouse.accepted = true; + swiping = containsPress; + break; + case Drag.YAxis: + move(0, mouse.y - origin.y); + mouse.accepted = true; + swiping = containsPress; + break; + default: + mouse.accepted = false; + } + } + + onReleased: { + switch (drag.axis) { + case Drag.XAndYAxis: + canceled(mouse); + mouse.accepted = true; + break; + case Drag.XAxis: + swipe(mouse.x - origin.x < 0 ? "right" : "left"); + mouse.accepted = true; + break; + case Drag.YAxis: + swipe(mouse.y - origin.y < 0 ? "up" : "down"); + mouse.accepted = true; + break; + default: + mouse.accepted = false; + } + swiping = false; + } +} diff --git a/applications/system-service/application.cpp b/applications/system-service/application.cpp index 47b1746a5..b6a395b60 100644 --- a/applications/system-service/application.cpp +++ b/applications/system-service/application.cpp @@ -90,7 +90,9 @@ void Application::interruptApplication(){ timer.restart(); delayUpTo(1000); appsAPI->disconnectSignals(this, 2); - if(timer.isValid()){ + if(stateNoSecurityCheck() == Inactive){ + qDebug() << "Application crashed while pausing"; + }else if(timer.isValid()){ qDebug() << "Application took too long to background" << name(); kill(-m_process->processId(), SIGSTOP); waitForPause(); diff --git a/applications/system-service/appsapi.cpp b/applications/system-service/appsapi.cpp index 22911fbf1..95e7f5e80 100644 --- a/applications/system-service/appsapi.cpp +++ b/applications/system-service/appsapi.cpp @@ -4,6 +4,7 @@ AppsAPI::AppsAPI(QObject* parent) : APIBase(parent), m_stopping(false), + m_starting(true), m_enabled(false), applications(), previousApplications(), @@ -68,19 +69,26 @@ AppsAPI::AppsAPI(QObject* parent) void AppsAPI::startup(){ for(auto app : applications){ if(app->autoStart()){ + qDebug() << "Auto starting" << app->name(); app->launchNoSecurityCheck(); if(app->type() == Backgroundable){ + qDebug() << " Puasing auto started app" << app->name(); app->pauseNoSecurityCheck(); } } } auto app = getApplication(m_lockscreenApplication); if(app == nullptr){ + qDebug() << "Could not find lockscreen application"; app = getApplication(m_startupApplication); } - if(app != nullptr){ - app->launchNoSecurityCheck(); + if(app == nullptr){ + qDebug() << "could not find startup application"; + return; } + qDebug() << "Starting initial application" << app->name(); + app->launchNoSecurityCheck(); + m_starting = false; } bool AppsAPI::locked(){ return notificationAPI->locked(); } diff --git a/applications/system-service/appsapi.h b/applications/system-service/appsapi.h index 957edcd9e..974913f3b 100644 --- a/applications/system-service/appsapi.h +++ b/applications/system-service/appsapi.h @@ -260,7 +260,7 @@ class AppsAPI : public APIBase { } } void resumeIfNone(){ - if(m_stopping){ + if(m_stopping || m_starting){ return; } for(auto app : applications){ @@ -503,6 +503,7 @@ public slots: private: bool m_stopping; + bool m_starting; bool m_enabled; QMap applications; QStringList previousApplications; @@ -566,6 +567,9 @@ public slots: auto type = settings.value("type", Foreground).toInt(); auto bin = settings.value("bin").toString(); if(type < Foreground || type > Backgroundable || name.isEmpty() || bin.isEmpty()){ +#ifdef DEBUG + qDebug() << "Invalid configuration " << name; +#endif continue; } QVariantMap properties { @@ -591,9 +595,16 @@ public slots: properties.insert("group", settings.value("group", "").toString()); } if(applications.contains(name)){ +#ifdef DEBUG + qDebug() << "Updating " << name; + qDebug() << properties; +#endif applications[name]->setConfig(properties); }else{ qDebug() << name; +#ifdef DEBUG + qDebug() << properties; +#endif registerApplicationNoSecurityCheck(properties); } } @@ -609,6 +620,10 @@ public slots: } auto data = file.readAll(); auto app = QJsonDocument::fromJson(data).object(); + if(app.isEmpty()){ + qDebug() << "Invalid file " << entry.filePath(); + continue; + } auto name = entry.completeBaseName(); app["name"] = name; apps.insert(name, app); @@ -634,8 +649,11 @@ public slots: qDebug() << "Invalid type string:" << typeString; } auto bin = app["bin"].toString(); - if(!QFile::exists(bin)){ - qDebug() << "Can't find application binary:" << bin; + if(bin.isEmpty() || !QFile::exists(bin)){ + qDebug() << name << "Can't find application binary:" << bin; +#ifdef DEBUG + qDebug() << app; +#endif continue; } auto flags = QStringList() << "system"; @@ -706,9 +724,16 @@ public slots: properties.insert("environment", envMap); } if(applications.contains(name)){ +#ifdef DEBUG + qDebug() << "Updating " << name; + qDebug() << properties; +#endif applications[name]->setConfig(properties); }else{ qDebug() << "New system app" << name; +#ifdef DEBUG + qDebug() << properties; +#endif registerApplicationNoSecurityCheck(properties); } } diff --git a/applications/system-service/dbusservice.h b/applications/system-service/dbusservice.h index b22b3baaa..a0d8d6abd 100644 --- a/applications/system-service/dbusservice.h +++ b/applications/system-service/dbusservice.h @@ -72,7 +72,6 @@ class DBusService : public APIBase { connect(bus.interface(), SIGNAL(serviceOwnerChanged(QString,QString,QString)), instance, SLOT(serviceOwnerChanged(QString,QString,QString))); qDebug() << "Registered"; - instance->startup(); } return instance; } diff --git a/applications/system-service/main.cpp b/applications/system-service/main.cpp index 18a1afbc8..e359dae06 100755 --- a/applications/system-service/main.cpp +++ b/applications/system-service/main.cpp @@ -37,11 +37,19 @@ int main(int argc, char *argv[]){ parser.process(app); const QStringList args = parser.positionalArguments(); - dbusService; signal(SIGINT, sigHandler); signal(SIGSEGV, sigHandler); signal(SIGTERM, sigHandler); + dbusService; + QTimer::singleShot(0, []{ + dbusService->startup(); + }); + system("mkdir -p /run/oxide"); + system(("echo " + to_string(app.applicationPid()) + " > /run/oxide/oxide.pid").c_str()); + QObject::connect(&app, &QGuiApplication::aboutToQuit, []{ + remove("/run/oxide/oxide.pid"); + }); return app.exec(); } diff --git a/applications/system-service/notification.cpp b/applications/system-service/notification.cpp index 272220c11..7ec3a0249 100644 --- a/applications/system-service/notification.cpp +++ b/applications/system-service/notification.cpp @@ -9,29 +9,21 @@ void Notification::display(){ if(!hasPermission("notification")){ return; } - notificationAPI->lock(); - qDebug() << "Displaying notification" << identifier(); - auto path = appsAPI->currentApplicationNoSecurityCheck(); - Application* resumeApp = nullptr; - if(path.path() != "/"){ - resumeApp = appsAPI->getApplication(path); - resumeApp->interruptApplication(); + if(notificationAPI->locked()){ + qDebug() << "Queueing notification display"; + notificationAPI->notificationDisplayQueue.append(this); + return; } - auto frameBuffer = EPFrameBuffer::framebuffer(); + notificationAPI->lock(); dispatchToMainThread([=]{ - auto backup = frameBuffer->copy(); - const QRect rect = paintNotification(); - emit displayed(); - QTimer::singleShot(2000, [=]{ - QPainter painter(frameBuffer); - painter.drawImage(rect, backup, rect); - EPFrameBuffer::sendUpdate(rect, EPFrameBuffer::Mono, EPFrameBuffer::FullUpdate, true); - if(resumeApp != nullptr){ - resumeApp->uninterruptApplication(); - } - qDebug() << "Finished displaying notification" << identifier(); - notificationAPI->unlock(); - }); + qDebug() << "Displaying notification" << identifier(); + auto path = appsAPI->currentApplicationNoSecurityCheck(); + Application* resumeApp = nullptr; + if(path.path() != "/"){ + resumeApp = appsAPI->getApplication(path); + resumeApp->interruptApplication(); + } + paintNotification(resumeApp); }); } @@ -59,13 +51,14 @@ void Notification::dispatchToMainThread(std::function callback){ }); QMetaObject::invokeMethod(timer, "start", Qt::BlockingQueuedConnection, Q_ARG(int, 0)); } -const QRect Notification::paintNotification(){ - qDebug() << "Painting notification" << identifier(); +void Notification::paintNotification(Application* resumeApp){ auto frameBuffer = EPFrameBuffer::framebuffer(); qDebug() << "Waiting for other painting to finish..."; while(frameBuffer->paintingActive()){ - this->thread()->yieldCurrentThread(); + EPFrameBuffer::waitForLastUpdate(); } + qDebug() << "Painting notification" << identifier(); + screenBackup = frameBuffer->copy(); qDebug() << "Painting to framebuffer..."; QPainter painter(frameBuffer); auto size = frameBuffer->size(); @@ -76,19 +69,36 @@ const QRect Notification::paintNotification(){ auto height = fm.height() + (padding * 2); auto left = size.width() - width; auto top = size.height() - height; - QRect rect(left, top, width, height); - painter.fillRect(rect, Qt::black); + updateRect = QRect(left, top, width, height); + painter.fillRect(updateRect, Qt::black); painter.setPen(Qt::black); - painter.drawRoundedRect(rect, radius, radius); + painter.drawRoundedRect(updateRect, radius, radius); painter.setPen(Qt::white); - painter.drawText(rect, Qt::AlignCenter, text()); + painter.drawText(updateRect, Qt::AlignCenter, text()); painter.end(); - - qDebug() << "Updating screen " << rect << "..."; - EPFrameBuffer::sendUpdate(rect, EPFrameBuffer::Mono, EPFrameBuffer::PartialUpdate, true); + qDebug() << "Updating screen " << updateRect << "..."; + EPFrameBuffer::sendUpdate(updateRect, EPFrameBuffer::Mono, EPFrameBuffer::PartialUpdate, true); EPFrameBuffer::waitForLastUpdate(); qDebug() << "Painted notification" << identifier(); - return rect; + emit displayed(); + QTimer::singleShot(2000, [this, resumeApp]{ + QPainter painter(EPFrameBuffer::framebuffer()); + painter.drawImage(updateRect, screenBackup, updateRect); + painter.end(); + EPFrameBuffer::sendUpdate(updateRect, EPFrameBuffer::Mono, EPFrameBuffer::FullUpdate, true); + qDebug() << "Finished displaying notification" << identifier(); + EPFrameBuffer::waitForLastUpdate(); + if(!notificationAPI->notificationDisplayQueue.isEmpty()){ + dispatchToMainThread([resumeApp] { + notificationAPI->notificationDisplayQueue.takeFirst()->paintNotification(resumeApp); + }); + return; + } + if(resumeApp != nullptr){ + resumeApp->uninterruptApplication(); + } + notificationAPI->unlock(); + }); } bool Notification::hasPermission(QString permission, const char* sender){ return notificationAPI->hasPermission(permission, sender); } diff --git a/applications/system-service/notification.h b/applications/system-service/notification.h index 5388be30b..e031e9fc2 100644 --- a/applications/system-service/notification.h +++ b/applications/system-service/notification.h @@ -2,8 +2,10 @@ #define NOTIFICATION_H #include +#include #include +#include "application.h" #include "dbussettings.h" class Notification : public QObject{ @@ -121,6 +123,7 @@ class Notification : public QObject{ } emit clicked(); } + void paintNotification(Application* resumeApp); signals: void changed(QVariantMap); @@ -135,9 +138,10 @@ class Notification : public QObject{ QString m_application; QString m_text; QString m_icon; + QImage screenBackup; + QRect updateRect; void dispatchToMainThread(std::function callback); - const QRect paintNotification(); bool hasPermission(QString permission, const char* sender = __builtin_FUNCTION()); }; diff --git a/applications/system-service/notificationapi.h b/applications/system-service/notificationapi.h index 813c3c912..64e721ce3 100644 --- a/applications/system-service/notificationapi.h +++ b/applications/system-service/notificationapi.h @@ -25,7 +25,7 @@ class NotificationAPI : public APIBase { } return instance; } - NotificationAPI(QObject* parent) : APIBase(parent), m_enabled(false), m_notifications(), m_lock() { + NotificationAPI(QObject* parent) : APIBase(parent), notificationDisplayQueue(), m_enabled(false), m_notifications(), m_lock() { singleton(this); } ~NotificationAPI(){} @@ -75,6 +75,7 @@ class NotificationAPI : public APIBase { } return result; } + QList notificationDisplayQueue; public slots: QDBusObjectPath add(QString identifier, QString application, QString text, QString icon, QDBusMessage message){ @@ -136,7 +137,7 @@ public slots: m_lock.unlock(); return false; } - void lock() { m_lock.lock(); } + void lock() { m_lock.tryLock(1); } void unlock() { m_lock.unlock(); } signals: diff --git a/applications/system-service/screenshot.h b/applications/system-service/screenshot.h index 489d57289..93a6bcdc2 100644 --- a/applications/system-service/screenshot.h +++ b/applications/system-service/screenshot.h @@ -52,6 +52,10 @@ class Screenshot : public QObject{ if(!hasPermission("screen")){ return QByteArray(); } + if(!m_file->exists() && !m_file->isOpen()){ + emit removed(); + return QByteArray(); + } mutex.lock(); if(!m_file->isOpen() && !m_file->open(QIODevice::ReadWrite)){ qDebug() << "Unable to open screenshot file" << m_file->fileName(); @@ -84,6 +88,10 @@ class Screenshot : public QObject{ if(!hasPermission("screen")){ return ""; } + if(!m_file->exists()){ + emit removed(); + return ""; + } return m_file->fileName(); } @@ -97,9 +105,16 @@ public slots: return; } mutex.lock(); - m_file->remove(); - m_file->close(); + if(m_file->exists() && !m_file->remove()){ + qDebug() << "Failed to remove screenshot" << path(); + mutex.unlock(); + return; + } + if(m_file->isOpen()){ + m_file->close(); + } mutex.unlock(); + qDebug() << "Removed screenshot" << path(); emit removed(); } diff --git a/applications/system-service/tarnish.pro b/applications/system-service/tarnish.pro index aaf937097..ca5ba3e0b 100644 --- a/applications/system-service/tarnish.pro +++ b/applications/system-service/tarnish.pro @@ -66,14 +66,14 @@ HEADERS += \ powerapi.h \ screenapi.h \ screenshot.h \ - signalhandler.h \ supplicant.h \ sysobject.h \ systemapi.h \ wifiapi.h \ wlan.h \ wpa_supplicant.h \ - ../../shared/devicesettings.h + ../../shared/devicesettings.h \ + ../../shared/signalhandler.h linux-oe-g++ { LIBS += -lqsgepaper @@ -85,6 +85,7 @@ linux-oe-g++ { QMAKE_POST_LINK += sh $$_PRO_FILE_PWD_/generate_xml.sh DISTFILES += \ + ../../assets/opt/usr/share/applications/codes.eeems.anxiety.oxide \ ../../assets/opt/usr/share/applications/codes.eeems.corrupt.oxide \ fi.w1.wpa_supplicant1.xml \ generate_xml.sh \ diff --git a/applications/system-service/wlan.h b/applications/system-service/wlan.h index fa84977cc..f033f5289 100644 --- a/applications/system-service/wlan.h +++ b/applications/system-service/wlan.h @@ -41,7 +41,7 @@ class Wlan : public QObject, public SysObject { return ip != "" && (pingIP(ip, "53") || pingIP(ip, "80")); } int link(){ - auto out = exec("cat /proc/net/wireless | grep " + iface() + " | awk '{print $3}'"); + auto out = exec("grep " + iface() + " /proc/net/wireless | awk '{print $3}'"); try { return std::stoi(out); } diff --git a/applications/task-switcher/controller.h b/applications/task-switcher/controller.h index 53e2f4a46..4ccb02fe0 100644 --- a/applications/task-switcher/controller.h +++ b/applications/task-switcher/controller.h @@ -31,7 +31,7 @@ class Controller : public QObject { Q_OBJECT public: Controller(QObject* parent, ScreenProvider* screenProvider) - : QObject(parent), confirmPin(), settings(this), applications() { + : QObject(parent), settings(this), applications() { this->screenProvider = screenProvider; auto bus = QDBusConnection::systemBus(); qDebug() << "Waiting for tarnish to start up..."; @@ -232,7 +232,6 @@ private slots: } private: - QString confirmPin; QSettings settings; General* api; Screen* screenApi; diff --git a/applications/task-switcher/corrupt.pro b/applications/task-switcher/corrupt.pro index 00d249dce..8d3a604df 100644 --- a/applications/task-switcher/corrupt.pro +++ b/applications/task-switcher/corrupt.pro @@ -39,10 +39,10 @@ HEADERS += \ ../../shared/dbussettings.h \ ../../shared/devicesettings.h \ ../../shared/eventfilter.h \ + ../../shared/signalhandler.h \ appitem.h \ controller.h \ - screenprovider.h \ - signalhandler.h + screenprovider.h RESOURCES += \ qml.qrc diff --git a/applications/task-switcher/widgets/AppItem.qml b/applications/task-switcher/widgets/AppItem.qml index 59c9f4715..0697e1036 100644 --- a/applications/task-switcher/widgets/AppItem.qml +++ b/applications/task-switcher/widgets/AppItem.qml @@ -29,6 +29,8 @@ Item { height: root.height * (root.text ? 0.70 : 0.90) width: root.width source: root.source + sourceSize.width: width + sourceSize.height: height } Text { text: root.text diff --git a/assets/etc/dbus-1/system.d/codes.eeems.oxide.conf b/assets/etc/dbus-1/system.d/codes.eeems.oxide.conf index d82bdb08d..e7bf7e5bd 100644 --- a/assets/etc/dbus-1/system.d/codes.eeems.oxide.conf +++ b/assets/etc/dbus-1/system.d/codes.eeems.oxide.conf @@ -26,6 +26,8 @@ + + diff --git a/assets/etc/draft/icons/erode.svg b/assets/etc/draft/icons/erode.svg new file mode 100644 index 000000000..e0f0ffc11 --- /dev/null +++ b/assets/etc/draft/icons/erode.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/etc/draft/icons/image.svg b/assets/etc/draft/icons/image.svg new file mode 100644 index 000000000..410aa8d43 --- /dev/null +++ b/assets/etc/draft/icons/image.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/etc/draft/xochitl.draft b/assets/etc/draft/xochitl.draft deleted file mode 100644 index 200b31f5a..000000000 --- a/assets/etc/draft/xochitl.draft +++ /dev/null @@ -1,5 +0,0 @@ -name=xochitl -desc=The standard environment for reMarkable. -call=/usr/bin/xochitl -term=killall xochitl -imgFile=xochitl diff --git a/assets/opt/usr/share/applications/codes.eeems.anxiety.oxide b/assets/opt/usr/share/applications/codes.eeems.anxiety.oxide new file mode 100644 index 000000000..d718c4ef0 --- /dev/null +++ b/assets/opt/usr/share/applications/codes.eeems.anxiety.oxide @@ -0,0 +1,9 @@ +{ + "displayName": "Screenshots", + "description": "View and manage screenshots", + "bin": "/opt/bin/anxiety", + "icon": "/opt/etc/draft/icons/image.svg", + "flags": [], + "type": "foreground", + "permissions": ["screen"] +} diff --git a/assets/opt/usr/share/applications/codes.eeems.erode.oxide b/assets/opt/usr/share/applications/codes.eeems.erode.oxide index f78f573c3..48ad9ecff 100644 --- a/assets/opt/usr/share/applications/codes.eeems.erode.oxide +++ b/assets/opt/usr/share/applications/codes.eeems.erode.oxide @@ -1,5 +1,6 @@ { "displayName": "Process Manager", "description": "List and kill running processes", + "icon": "/opt/etc/draft/icons/erode.svg", "bin": "/opt/bin/erode" } diff --git a/assets/opt/usr/share/applications/codes.eeems.fret.oxide b/assets/opt/usr/share/applications/codes.eeems.fret.oxide index 4eadedf55..c7123ee4c 100644 --- a/assets/opt/usr/share/applications/codes.eeems.fret.oxide +++ b/assets/opt/usr/share/applications/codes.eeems.fret.oxide @@ -1,8 +1,8 @@ { - "displayName": "Screenshot daemon", - "description": "Handles taking screenshots", + "displayName": "Screenshot daemon", + "description": "Takes screenshots and displays notifications", "bin": "/opt/bin/fret", - "flags": ["hidden", "autoStart"], + "flags": ["autoStart", "hidden"], "type": "background", "permissions": ["notification", "screen", "system"] } diff --git a/package b/package index 58a27dbc0..613003a38 100644 --- a/package +++ b/package @@ -2,8 +2,8 @@ # Copyright (c) 2020 The Toltec Contributors # SPDX-License-Identifier: MIT -pkgnames=(erode fret oxide rot tarnish decay corrupt) -pkgver="2.2-$(cat package/oxide/version.txt)" +pkgnames=(erode fret oxide rot tarnish decay corrupt anxiety) +pkgver="2.2-~VERSION~" timestamp="$(date -u +%Y-%m-%dT%H:%MZ)" maintainer="Eeems " license=MIT @@ -117,3 +117,16 @@ corrupt() { install -D -m 644 -t "$pkgdir"/opt/usr/share/applications "$srcdir"/opt/usr/share/applications/codes.eeems.corrupt.oxide } } + +anxiety() { + pkgdesc="Screenshot viewer for Oxide" + url=https://github.com/Eeems/oxide/tree/master/applications/screenshot-viewer + section=utils + depends=("tarnish (= $pkgver)") + + package() { + install -D -m 755 -t "$pkgdir"/opt/bin "$srcdir"/opt/bin/anxiety + install -D -m 644 -t "$pkgdir"/opt/usr/share/applications "$srcdir"/opt/usr/share/applications/codes.eeems.anxiety.oxide + install -D -m 644 -t "$pkgdir"/opt/etc/draft/icons "$srcdir"/opt/etc/draft/icons/image.svg + } +} \ No newline at end of file diff --git a/applications/system-service/signalhandler.h b/shared/signalhandler.h similarity index 100% rename from applications/system-service/signalhandler.h rename to shared/signalhandler.h