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