diff --git a/.github/actions/sync-repository/action.yml b/.github/actions/sync-repository/action.yml index bb10a3983..8fc4f4b4d 100644 --- a/.github/actions/sync-repository/action.yml +++ b/.github/actions/sync-repository/action.yml @@ -22,7 +22,7 @@ runs: sudo apt-get update -yq echo "syncAptVersion=sshfs-$(apt-cache policy sshfs | grep -oP '(?<=Candidate:\s)(.+)')" >> $GITHUB_ENV - name: Cache Apt packages - uses: actions/cache@v2 + uses: actions/cache@v3 id: cache-apt with: path: ~/.aptcache diff --git a/.github/actions/web/action.yaml b/.github/actions/web/action.yaml index bec596222..03f226ea9 100644 --- a/.github/actions/web/action.yaml +++ b/.github/actions/web/action.yaml @@ -4,38 +4,26 @@ runs: using: composite steps: - name: Setup Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v3 with: python-version: '3.8' - name: Cache Python environment - uses: actions/cache@v2 + uses: actions/cache@v3 id: cache-python with: path: web/.venv key: web/.venv-${{ hashFiles('web/requirements.txt') }} - - name: Check for Apt updates - shell: bash - run: | - sudo apt-get update -yq - echo "webAptVersion=doxygen-$(apt-cache policy doxygen | grep -oP '(?<=Candidate:\s)(.+)')-graphviz-$(apt-cache policy graphviz | grep -oP '(?<=Candidate:\s)(.+)')-libgraphviz-dev-$(apt-cache policy libgraphviz-dev | grep -oP '(?<=Candidate:\s)(.+)')" >> $GITHUB_ENV - - name: Cache Apt packages - uses: actions/cache@v2 + - name: Install base Apt packages id: cache-apt + uses: awalsh128/cache-apt-pkgs-action@latest with: - path: ~/.aptcache - key: ${{ env.webAptVersion }} - - name: Install or restore Apt packages - shell: bash - env: - CACHE_HIT: ${{ steps.cache-apt.outputs.cache-hit }} - run: | - if [[ "$CACHE_HIT" != 'true' ]]; then - sudo apt-get install -yq doxygen graphviz libgraphviz-dev - mkdir -p ~/.aptcache - sudo dpkg -L doxygen graphviz libgraphviz-dev | while IFS= read -r f; do if test -f $f; then echo $f; fi; done | xargs cp --parents --target-directory ~/.aptcache/ - else - sudo cp --verbose --force --recursive ~/.aptcache/* / - fi + execute_install_scripts: true + packages: doxygen graphviz libgraphviz-dev librsvg2-bin pdf2svg + version: 1.0 + - name: Apt-Cache-Action + uses: Eeems-Org/apt-cache-action@v1 + with: + packages: texlive-base texlive-latex-extra - name: Build website shell: bash run: cd web && make prod diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c0fc23086..196d7cdda 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -2,11 +2,15 @@ name: Build oxide on: push: + branches: + - master paths: - 'applications/**' - 'shared/**' - 'assets/**' - 'interfaces/**' + - 'qmake/**' + - 'oxide.pro' - 'Makefile' - 'package' pull_request: @@ -15,6 +19,8 @@ on: - 'shared/**' - 'assets/**' - 'interfaces/**' + - 'qmake/**' + - 'oxide.pro' - 'Makefile' - 'package' jobs: @@ -22,41 +28,18 @@ jobs: name: Build and package runs-on: ubuntu-latest steps: - - name: Checkout toltec Git repository - uses: actions/checkout@v2 - with: - repository: toltec-dev/toltec.git - ref: testing - - name: Cleanup - run: | - rm -rf package/oxide/* - mkdir package/oxide/src - - uses: actions/checkout@v2.3.1 - with: - path: package/oxide/src + - uses: actions/checkout@v3 - name: Setup Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: - python-version: '3.8' - - name: Setup Toltec dependencies - uses: ./.github/actions/setup - - name: Prepare build files - run: | - cd package/oxide/src - echo "r$(git rev-list --count HEAD).$(git rev-parse --short HEAD)" > ../version.txt - sed -i "s/~VERSION~/$(cat ../version.txt)/" ./package - mv ./package .. - tar --exclude='./.git' -czvf ../oxide.tar.gz . + python-version: 3.9 + - name: Install toltecmk + run: pip install toltecmk - name: Build packages - run: | - make FLAGS=--verbose oxide - mkdir output - find . -name '*.ipk' | while read -r file; do - cp "$file" output/"$(basename $file)" - done + run: make package timeout-minutes: 15 - name: Save packages - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3 with: name: packages - path: output + path: release diff --git a/.github/workflows/nix.yml b/.github/workflows/nix.yml new file mode 100644 index 000000000..69344f6df --- /dev/null +++ b/.github/workflows/nix.yml @@ -0,0 +1,48 @@ +name: Build with Nix + +on: + push: + branches: + - master + paths: + - 'applications/**' + - 'shared/**' + - 'assets/**' + - 'interfaces/**' + - 'qmake/**' + - 'oxide.pro' + - 'Makefile' + - '*.nix' + pull_request: + paths: + - 'applications/**' + - 'shared/**' + - 'assets/**' + - 'interfaces/**' + - 'qmake/**' + - 'oxide.pro' + - 'Makefile' + - '*.nix' +jobs: + nix-build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: cachix/install-nix-action@v18 + with: + nix_path: nixpkgs=channel:nixos-unstable + - uses: cachix/cachix-action@v12 + with: + name: nix-remarkable + authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}' + - name: Build + run: nix-build --argstr system 'x86_64-linux' + timeout-minutes: 15 + - run: | + mkdir output + cp -a result/. output/ + - name: Save Artifact + uses: actions/upload-artifact@v3 + with: + name: output + path: output diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 298f10bd5..fcdb384b1 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -15,7 +15,7 @@ jobs: runs-on: ubuntu-20.04 steps: - name: Checkout the Git repository - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: submodules: true - name: Build web diff --git a/.github/workflows/web.yaml b/.github/workflows/web.yaml index 5b48702fb..9f7d37ef4 100644 --- a/.github/workflows/web.yaml +++ b/.github/workflows/web.yaml @@ -22,13 +22,13 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout the Git repository - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: submodules: true - name: Build web uses: ./.github/actions/web - name: Save web - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3 with: name: web path: web/dist diff --git a/.gitignore b/.gitignore index 447d2adb8..65e6f2195 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,7 @@ release/ applications/*/Makefile result *.orig +oxide.tar.gz +build/ +dist/ +version.txt diff --git a/Makefile b/Makefile index c1ee56421..65eb2c40b 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: all clean release build liboxide erode tarnish rot fret oxide decay corrupt anxiety sentry +.PHONY: all clean release build sentry package all: release @@ -6,106 +6,75 @@ all: release # FEATURES += sentry +DIST=$(CURDIR)/release +BUILD=$(CURDIR)/.build + + ifneq ($(filter sentry,$(FEATURES)),) OBJ += sentry -RELOBJ += release/opt/lib/libsentry.so +RELOBJ += $(DIST)/opt/lib/libsentry.so DEFINES += 'DEFINES+="SENTRY"' endif +OBJ += $(BUILD)/oxide/Makefile + clean: - rm -rf .build - rm -rf release + rm -rf $(DIST) $(BUILD) release: clean build $(RELOBJ) - mkdir -p release - INSTALL_ROOT=../../release $(MAKE) -j`nproc` -C .build/liboxide install - INSTALL_ROOT=../../release $(MAKE) -j`nproc` -C .build/process-manager install - INSTALL_ROOT=../../release $(MAKE) -j`nproc` -C .build/system-service install - INSTALL_ROOT=../../release $(MAKE) -j`nproc` -C .build/settings-manager install - INSTALL_ROOT=../../release $(MAKE) -j`nproc` -C .build/screenshot-tool install - INSTALL_ROOT=../../release $(MAKE) -j`nproc` -C .build/screenshot-viewer install - INSTALL_ROOT=../../release $(MAKE) -j`nproc` -C .build/launcher install - INSTALL_ROOT=../../release $(MAKE) -j`nproc` -C .build/lockscreen install - INSTALL_ROOT=../../release $(MAKE) -j`nproc` -C .build/task-switcher install - -build: liboxide tarnish erode rot oxide decay corrupt fret anxiety - -liboxide: $(OBJ) .build/liboxide/libliboxide.so - -.build/liboxide/libliboxide.so: - mkdir -p .build/liboxide - cp -r shared/liboxide/* .build/liboxide - cd .build/liboxide && qmake $(DEFINES) liboxide.pro - $(MAKE) -j`nproc` -C .build/liboxide all - -erode: liboxide .build/process-manager/erode - -.build/process-manager/erode: - mkdir -p .build/process-manager - cp -r applications/process-manager/* .build/process-manager - cd .build/process-manager && qmake $(DEFINES) erode.pro - $(MAKE) -j`nproc` -C .build/process-manager all - -tarnish: liboxide .build/system-service/tarnish - -.build/system-service/tarnish: - mkdir -p .build/system-service - cp -r applications/system-service/* .build/system-service - cd .build/system-service && qmake $(DEFINES) tarnish.pro - $(MAKE) -j`nproc` -C .build/system-service all - -rot: tarnish liboxide .build/settings-manager/rot - -.build/settings-manager/rot: - mkdir -p .build/settings-manager - cp -r applications/settings-manager/* .build/settings-manager - cd .build/settings-manager && qmake $(DEFINES) rot.pro - $(MAKE) -j`nproc` -C .build/settings-manager all - -fret: tarnish liboxide .build/screenshot-tool/fret - -.build/screenshot-tool/fret: - mkdir -p .build/screenshot-tool - cp -r applications/screenshot-tool/* .build/screenshot-tool - cd .build/screenshot-tool && qmake $(DEFINES) fret.pro - $(MAKE) -j`nproc` -C .build/screenshot-tool all - -oxide: tarnish liboxide .build/launcher/oxide - -.build/launcher/oxide: - mkdir -p .build/launcher - cp -r applications/launcher/* .build/launcher - cd .build/launcher && qmake $(DEFINES) oxide.pro - $(MAKE) -j`nproc` -C .build/launcher all - -decay: tarnish liboxide .build/lockscreen/decay - -.build/lockscreen/decay: - mkdir -p .build/lockscreen - cp -r applications/lockscreen/* .build/lockscreen - cd .build/lockscreen && qmake $(DEFINES) decay.pro - $(MAKE) -j`nproc` -C .build/lockscreen all - -corrupt: tarnish liboxide .build/task-switcher/corrupt - -.build/task-switcher/corrupt: - mkdir -p .build/task-switcher - cp -r applications/task-switcher/* .build/task-switcher - cd .build/task-switcher && qmake $(DEFINES) corrupt.pro - $(MAKE) -j`nproc` -C .build/task-switcher all - -anxiety: tarnish liboxide .build/screenshot-viewer/anxiety - -.build/screenshot-viewer/anxiety: - mkdir -p .build/screenshot-viewer - cp -r applications/screenshot-viewer/* .build/screenshot-viewer - cd .build/screenshot-viewer && qmake $(DEFINES) anxiety.pro - $(MAKE) -j`nproc` -C .build/screenshot-viewer all - -sentry: .build/sentry/libsentry.so - -.build/sentry/libsentry.so: - cd shared/sentry && cmake -B ../../.build/sentry \ + mkdir -p $(DIST) + INSTALL_ROOT=$(DIST) $(MAKE) -C $(BUILD)/oxide install + +build: $(BUILD) $(OBJ) + $(MAKE) -C $(BUILD)/oxide all + +package: REV="~r$(shell git rev-list --count HEAD).$(shell git rev-parse --short HEAD)" +package: + if [ -d .git ];then \ + echo $(REV) > version.txt; \ + else \ + echo "~manual" > version.txt; \ + fi; + mkdir -p $(BUILD)/package + rm -rf $(BUILD)/package/build + sed "s/~VERSION~/`cat version.txt`/" ./package > $(BUILD)/package/package + tar \ + --exclude='$(CURDIR)/.git' \ + --exclude='$(BUILD)' \ + --exclude='$(CURDIR)/dist' \ + --exclude='$(DIST)' \ + -czvf $(BUILD)/package/oxide.tar.gz \ + applications \ + assets \ + interfaces \ + qmake \ + shared \ + oxide.pro \ + Makefile + toltecmk \ + --verbose \ + -w $(BUILD)/package/build \ + -d $(BUILD)/package/dist \ + $(BUILD)/package + mkdir -p $(DIST) + cp -a $(BUILD)/package/dist/rmall/*.ipk $(DIST) + +sentry: $(BUILD)/sentry/libsentry.so + +$(BUILD): + mkdir -p $(BUILD) + +$(BUILD)/.nobackup: $(BUILD) + touch $(BUILD)/.nobackup + +$(BUILD)/oxide: $(BUILD)/.nobackup + mkdir -p $(BUILD)/oxide + +$(BUILD)/oxide/Makefile: $(BUILD)/oxide + cd $(BUILD)/oxide && qmake -r $(DEFINES) $(CURDIR)/oxide.pro + +$(BUILD)/sentry/libsentry.so: $(BUILD)/.nobackup + cd shared/sentry && cmake -B $(BUILD)/sentry/src \ -DBUILD_SHARED_LIBS=ON \ -DSENTRY_INTEGRATION_QT=ON \ -DCMAKE_BUILD_TYPE=RelWithDebInfo \ @@ -114,9 +83,9 @@ sentry: .build/sentry/libsentry.so -DSENTRY_BREAKPAD_SYSTEM=OFF \ -DSENTRY_EXPORT_SYMBOLS=ON \ -DSENTRY_PTHREAD=ON - cd shared/sentry && cmake --build ../../.build/sentry --parallel - cd shared/sentry && cmake --install ../../.build/sentry --prefix ../../.build/sentry --config RelWithDebInfo + cd shared/sentry && cmake --build $(BUILD)/sentry/src --parallel + cd shared/sentry && cmake --install $(BUILD)/sentry/src --prefix $(BUILD)/sentry --config RelWithDebInfo -release/opt/lib/libsentry.so: sentry - mkdir -p release/opt/lib - cp .build/sentry/libsentry.so release/opt/lib/ +$(DIST)/opt/lib/libsentry.so: sentry + mkdir -p $(DIST)/opt/lib + cp -a $(BUILD)/sentry/lib/libsentry.so $(DIST)/opt/lib/ diff --git a/README.md b/README.md index 7a5394bbc..c4c428e05 100644 --- a/README.md +++ b/README.md @@ -7,21 +7,31 @@ [![Discord](https://img.shields.io/discord/385916768696139794.svg?label=reMarkable&logo=discord&logoColor=ffffff&color=7389D8&labelColor=6A7EC2)](https://discord.gg/ATqQGfu) # Oxide + A launcher application for the [reMarkable tablet](https://remarkable.com/). Head over to the [releases](https://github.com/Eeems/oxide/releases) page for more information on the latest release. You can also see some (likely outdated) [screenshots here](https://github.com/Eeems/oxide/wiki/Screenshots). -Here is an outdated video of it in action: +Here is an outdated video of it in action: [![Oxide v2.0-beta](https://i.imgur.com/1Q9A4NF.png)](https://youtu.be/rIRKgqy21L0 "Oxide v2.0-beta") ## Building -Install the reMarkable toolchain and then run `make release`. It will produce a folder named `release` containing all the output. +### Binaries + + 1. Install the [reMarkable toolchain](https://remarkablewiki.com/devel/toolchain) + 2. Run `make release` or `make FEATURES=sentry release` + 3. The built files can be found in the `release/` folder + +### Package files + + 1. Install [toltecmk](https://pypi.org/project/toltecmk/) + 2. Run `make package` + 3. The ipk files can be found in the `dist/` folder ### Nix -Works on x86_64-linux or macOS with -[linuxkit-nix](https://github.com/nix-community/linuxkit-nix). +Works on x86_64-linux or macOS via [nix-docker](https://github.com/LnL7/nix-docker). ```ShellSession -$ nix build +$ nix-build --argstr system 'x86_64-linux' ``` diff --git a/applications/applications.pro b/applications/applications.pro new file mode 100644 index 000000000..30aa36b31 --- /dev/null +++ b/applications/applications.pro @@ -0,0 +1,43 @@ +TEMPLATE = subdirs + +SUBDIRS = \ + desktop-file-edit \ + desktop-file-install \ + desktop-file-validate \ + gio \ + launcher \ + lockscreen \ + notify-send \ + process-manager \ + screenshot-tool \ + screenshot-viewer \ + settings-manager \ + system-service \ + task-switcher \ + update-desktop-database \ + xdg-desktop-icon \ + xdg-desktop-menu \ + xdg-icon-resource \ + xdg-open \ + xdg-settings + +launcher.depends = system-service update-desktop-database +lockscreen.depends = system-service +notify-send.depends = system-service +process-manager.depends = +screenshot-tool.depends = system-service +screenshot-viewer.depends = system-service +settings-manager.depends = system-service +system-service.depends = +task-switcher.depends = system-service +update-desktop-database.depends = system-service +xdg-desktop-icon.depends = system-service +xdg-desktop-menu.depends = system-service +xdg-open.depends = system-service +gio.depends = system-service +xdg-settings.depends = system-service +xdg-icon-resource.depends = system-service +desktop-file-edit.depends = desktop-file-edit +desktop-file-install.depends = + +INSTALLS += $$SUBDIRS diff --git a/applications/desktop-file-edit/.gitignore b/applications/desktop-file-edit/.gitignore new file mode 100644 index 000000000..fab7372d7 --- /dev/null +++ b/applications/desktop-file-edit/.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/desktop-file-edit/desktop-file-edit.pro b/applications/desktop-file-edit/desktop-file-edit.pro new file mode 100644 index 000000000..e7b171de0 --- /dev/null +++ b/applications/desktop-file-edit/desktop-file-edit.pro @@ -0,0 +1,23 @@ +QT -= gui +QT += dbus + +CONFIG += c++11 console +CONFIG -= app_bundle + +DEFINES += QT_DEPRECATED_WARNINGS +#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 + +SOURCES += \ + edit.cpp \ + main.cpp + +HEADERS += \ + edit.h + +TARGET = desktop-file-edit +include(../../qmake/common.pri) +target.path = /opt/bin +INSTALLS += target + +include(../../qmake/liboxide.pri) +include(../../qmake/sentry.pri) diff --git a/applications/desktop-file-edit/edit.cpp b/applications/desktop-file-edit/edit.cpp new file mode 100644 index 000000000..0c309b5c7 --- /dev/null +++ b/applications/desktop-file-edit/edit.cpp @@ -0,0 +1,165 @@ +#include "edit.h" + +QCommandLineOption setKeyOption( + "set-key", + "Set the KEY key to the value passed to the next --set-value option. A matching --set-value option is mandatory.", + "KEY" +); +QCommandLineOption setValueOption( + "set-value", + "Set the key specified with the previous --set-key option to VALUE. A matching --set-key option is mandatory.", + "VALUE" +); +QCommandLineOption setNameOption( + "set-name", + "NOT IMPLEMENTED", + "NAME" +); +QCommandLineOption copyNameToGenericNameOption( + "copy-name-to-generic-name", + "Copy the value of the Name key to the GenericName key. Note that a desktop file requires a Name key to be valid, so this option will always have an effect." +); +QCommandLineOption setGenericNameOption( + "set-generic-name", + "Set the generic name (key DisplayName) to GENERIC-NAME. If a generic name was already set, it will be overridden. Localizations of the old generic name will be removed.", + "GENERIC-NAME" +); +QCommandLineOption copyGenericNameToNameOption( + "copy-generic-name-to-name", + "NOT IMPLEMENTED" +); +QCommandLineOption setCommentOption( + "set-comment", + "NOT IMPLEMENTED", + "COMMENT" +); +QCommandLineOption setIconOption( + "set-icon", + "Set the icon (key Icon) to ICON. If an icon was already set, it will be overridden. Localizations of the old icon will be removed.", + "ICON" +); +QCommandLineOption addCategoryOption( + "add-category", + "NOT IMPLEMENTED", + "CATEGORY" +); +QCommandLineOption removeCategoryOption( + "remove-category", + "NOT IMPLEMENTED", + "CATEGORY" +); +QCommandLineOption addMimeTypeOption( + "add-mime-type", + "NOT IMPLEMENTED", + "MIME-TYPE" +); +QCommandLineOption removeMimeTypeOption( + "remove-mime-type", + "NOT IMPLEMENTED", + "MIME-TYPE" +); +QCommandLineOption addOnlyShowInOption( + "add-only-show-in", + "NOT IMPLEMENTED", + "ENVIRONMENT" +); +QCommandLineOption removeOnlyShowInOption( + "remove-only-show-in", + "NOT IMPLEMENTED", + "ENVIRONMENT" +); +QCommandLineOption addNotShowInOption( + "add-not-show-in", + "NOT IMPLEMENTED", + "ENVIRONMENT" +); +QCommandLineOption removeNotShowInOption( + "remove-not-show-in", + "NOT IMPLEMENTED", + "ENVIRONMENT" +); +QCommandLineOption removeKeyOption( + "remove-key", + "Remove the KEY key from the desktop files, if present.", + "KEY" +); + +void addEditOptions(QCommandLineParser& parser){ + parser.addOption(setKeyOption); + parser.addOption(setValueOption); + parser.addOption(setNameOption); + parser.addOption(copyNameToGenericNameOption); + parser.addOption(setGenericNameOption); + parser.addOption(copyGenericNameToNameOption); + parser.addOption(setCommentOption); + parser.addOption(setIconOption); + parser.addOption(addCategoryOption); + parser.addOption(removeCategoryOption); + parser.addOption(addMimeTypeOption); + parser.addOption(removeMimeTypeOption); + parser.addOption(addOnlyShowInOption); + parser.addOption(removeOnlyShowInOption); + parser.addOption(addNotShowInOption); + parser.addOption(removeNotShowInOption); + parser.addOption(removeKeyOption); +} + +bool validateSetKeyValueOptions(QCommandLineParser& parser){ + auto options = parser.optionNames(); + if(parser.isSet(setKeyOption) || parser.isSet(setValueOption)){ + for(int i=0; i< options.length(); i++){ + auto option = options[i]; + if(setValueOption.names().contains(option)){ + qDebug() << "Option \"--set-value\" used without a prior \"--set-key\" option"; + qDebug() << "Run 'desktop-file-edit --help' to see a full list of available command line options."; + return false; + } + if(setKeyOption.names().contains(option)){ + option = options[++i]; + if(!setValueOption.names().contains(option)){ + qDebug() << "Option \"--set-key\" used without a following \"--set-value\" option"; + qDebug() << "Run 'desktop-file-edit --help' to see a full list of available command line options."; + return false; + } + } + } + } + return true; +} + +void applyChanges(QCommandLineParser& parser, QJsonObject& reg, const QString& name){ + int removeIndex = 0; + int keyIndex = 0; + int genericNameIndex = 0; + int iconIndex = 0; + QString key; + auto options = parser.optionNames(); + for(int i=0; i< options.length(); i++){ + auto option = options[i]; + if(copyNameToGenericNameOption.names().contains(option)){ + reg["displayName"] = name; + continue; + } + if(removeKeyOption.names().contains(option)){ + reg.remove(parser.values(removeKeyOption)[removeIndex]); + removeIndex++; + continue; + } + if(setGenericNameOption.names().contains(option)){ + reg["displayname"] = parser.values(setGenericNameOption)[genericNameIndex]; + genericNameIndex++; + continue; + } + if(setIconOption.names().contains(option)){ + reg["icon"] = parser.values(setIconOption)[iconIndex]; + iconIndex++; + continue; + } + if(setKeyOption.names().contains(option)){ + key = parser.values(setKeyOption)[keyIndex]; + reg[key] = parser.values(setValueOption)[keyIndex]; + i++; + keyIndex++; + } + } +} diff --git a/applications/desktop-file-edit/edit.h b/applications/desktop-file-edit/edit.h new file mode 100644 index 000000000..b1623e6e3 --- /dev/null +++ b/applications/desktop-file-edit/edit.h @@ -0,0 +1,12 @@ +#pragma once +#ifndef EDIT_H +#define EDIT_H + +#include +#include + +void addEditOptions(QCommandLineParser& parser); +bool validateSetKeyValueOptions(QCommandLineParser& parser); +void applyChanges(QCommandLineParser& parser, QJsonObject& reg, const QString& name); + +#endif // EDIT_H diff --git a/applications/desktop-file-edit/main.cpp b/applications/desktop-file-edit/main.cpp new file mode 100644 index 000000000..5f62980e8 --- /dev/null +++ b/applications/desktop-file-edit/main.cpp @@ -0,0 +1,78 @@ +#include +#include +#include + +#include +#include +#include + +#include "edit.h" + +using namespace Oxide::Sentry; +using namespace Oxide::JSON; +using namespace Oxide::Applications; + +QTextStream& qStdOut(){ + static QTextStream ts( stdout ); + return ts; +} + +int main(int argc, char *argv[]){ + QCoreApplication app(argc, argv); + sentry_init("desktop-file-edit", argv); + app.setOrganizationName("Eeems"); + app.setOrganizationDomain(OXIDE_SERVICE); + app.setApplicationName("desktop-file-edit"); + app.setApplicationVersion(APP_VERSION); + QCommandLineParser parser; + parser.setApplicationDescription("Edit application registration files"); + parser.applicationDescription(); + parser.addHelpOption(); + parser.addVersionOption(); + addEditOptions(parser); + parser.addPositionalArgument("FILE", "Application registration to edit"); + parser.process(app); + auto args = parser.positionalArguments(); + if(args.empty() || args.length() > 1){ + parser.showHelp(EXIT_FAILURE); + } + if(!validateSetKeyValueOptions(parser)){ + return EXIT_FAILURE; + } + auto path = args.first(); + QFile file(path); + if(!file.exists()){ + qDebug() << "Error on file" << path << ": No such file or directory"; + return EXIT_FAILURE; + } + auto reg = getRegistration(&file); + file.close(); + if(!file.open(QFile::WriteOnly | QFile::Truncate)){ + qDebug() << "Error on file" << path << ": Cannot write to file"; + return EXIT_FAILURE; + } + if(reg.isEmpty()){ + qDebug() << "Error on file" << path << ": is not valid"; + return EXIT_FAILURE; + } + + QFileInfo info(file); + auto name = info.baseName(); + applyChanges(parser, reg, name); + auto json = toJson(reg, QJsonDocument::Indented); + file.write(json.toUtf8()); + file.close(); + bool success = true; + for(auto error : validateRegistration(name, reg)){ + qStdOut() << path << ": " << error << Qt::endl; + auto level = error.level; + if(level == ErrorLevel::Error || level == ErrorLevel::Critical){ + success = false; + } + } + if(info.suffix() != "oxide"){ + qDebug() << path.toStdString().c_str() << ": error: filename does not have a .oxide extension"; + success = false; + } + return success ? EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/applications/desktop-file-install/.gitignore b/applications/desktop-file-install/.gitignore new file mode 100644 index 000000000..fab7372d7 --- /dev/null +++ b/applications/desktop-file-install/.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/desktop-file-install/desktop-file-install.pro b/applications/desktop-file-install/desktop-file-install.pro new file mode 100644 index 000000000..d41b248a1 --- /dev/null +++ b/applications/desktop-file-install/desktop-file-install.pro @@ -0,0 +1,23 @@ +QT -= gui +QT += dbus + +CONFIG += c++11 console +CONFIG -= app_bundle + +DEFINES += QT_DEPRECATED_WARNINGS +#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 + +SOURCES += \ + ../desktop-file-edit/edit.cpp \ + main.cpp + +HEADERS += \ + ../desktop-file-edit/edit.h + +TARGET = desktop-file-install +include(../../qmake/common.pri) +target.path = /opt/bin +INSTALLS += target + +include(../../qmake/liboxide.pri) +include(../../qmake/sentry.pri) diff --git a/applications/desktop-file-install/main.cpp b/applications/desktop-file-install/main.cpp new file mode 100644 index 000000000..1f36a9c19 --- /dev/null +++ b/applications/desktop-file-install/main.cpp @@ -0,0 +1,121 @@ +#include +#include +#include + +#include +#include +#include + +#include "../desktop-file-edit/edit.h" + +using namespace Oxide::Sentry; +using namespace Oxide::JSON; +using namespace Oxide::Applications; + +QTextStream& qStdOut(){ + static QTextStream ts( stdout ); + return ts; +} + +int main(int argc, char *argv[]){ + QCoreApplication app(argc, argv); + sentry_init("desktop-file-install", argv); + app.setOrganizationName("Eeems"); + app.setOrganizationDomain(OXIDE_SERVICE); + app.setApplicationName("desktop-file-install"); + app.setApplicationVersion(APP_VERSION); + QCommandLineParser parser; + parser.setApplicationDescription("Install application registration files"); + parser.applicationDescription(); + parser.addHelpOption(); + parser.addVersionOption(); + QCommandLineOption dirOption( + "dir", + "Install desktop files to the DIR directory.", + "DIR", + OXIDE_APPLICATION_REGISTRATIONS_DIRECTORY + ); + parser.addOption(dirOption); + QCommandLineOption modeOption( + {"m", "mode"}, + "NOT IMPLEMENTED", + "MODE" + ); + parser.addOption(modeOption); + QCommandLineOption vendorOption( + "vendor", + "NOT IMPLEMENTED", + "VENDOR" + ); + parser.addOption(vendorOption); + QCommandLineOption deleteOriginalOption( + "delete-original", + "Delete the source registration files, leaving only the target files. Effectively \"renames\" the registration files." + ); + parser.addOption(deleteOriginalOption); + QCommandLineOption rebuildMimeInfoCacheOption( + "rebuild-mime-info-cache", + "NOT IMPLEMENTED" + ); + parser.addOption(rebuildMimeInfoCacheOption); + addEditOptions(parser); + parser.addPositionalArgument("FILE", "Application registration(s) to install", "FILE..."); + parser.process(app); + auto args = parser.positionalArguments(); + if(args.empty()){ + parser.showHelp(EXIT_FAILURE); + } + if(!validateSetKeyValueOptions(parser)){ + return EXIT_FAILURE; + } + bool success = true; + for(auto path : args){ + QFile file(path); + if(!file.exists()){ + qDebug() << "Error on file" << path << ": No such file or directory"; + success = false; + continue; + } + auto reg = getRegistration(&file); + file.close(); + QFileInfo info(file); + QFile toFile(QDir::cleanPath(parser.value(dirOption) + QDir::separator() + info.baseName() + ".oxide")); + if(!toFile.open(QFile::WriteOnly | QFile::Truncate)){ + qDebug() << "Error on file" << path << ": Cannot write to file"; + success = false; + continue; + } + if(info.suffix() != "oxide"){ + qDebug() << path.toStdString().c_str() << ": error: filename does not have a .oxide extension"; + success = false; + continue; + } + if(reg.isEmpty()){ + qDebug() << "Error on file" << path << ": is not valid"; + success = false; + continue; + } + + auto name = info.baseName(); + applyChanges(parser, reg, name); + bool hadError = false; + for(auto error : validateRegistration(name, reg)){ + qStdOut() << path << ": " << error << Qt::endl; + auto level = error.level; + if(level == ErrorLevel::Error || level == ErrorLevel::Critical){ + hadError = true; + } + } + if(hadError){ + success = false; + continue; + } + auto json = toJson(reg, QJsonDocument::Indented); + toFile.write(json.toUtf8()); + toFile.close(); + if(parser.isSet(deleteOriginalOption)){ + file.remove(); + } + } + return success ? EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/applications/desktop-file-validate/.gitignore b/applications/desktop-file-validate/.gitignore new file mode 100644 index 000000000..fab7372d7 --- /dev/null +++ b/applications/desktop-file-validate/.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/desktop-file-validate/desktop-file-validate.pro b/applications/desktop-file-validate/desktop-file-validate.pro new file mode 100644 index 000000000..bae7481a5 --- /dev/null +++ b/applications/desktop-file-validate/desktop-file-validate.pro @@ -0,0 +1,21 @@ +QT -= gui +QT += dbus + +CONFIG += c++11 console +CONFIG -= app_bundle + +DEFINES += QT_DEPRECATED_WARNINGS +#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 + +SOURCES += \ + main.cpp + +HEADERS += + +TARGET = desktop-file-validate +include(../../qmake/common.pri) +target.path = /opt/bin +INSTALLS += target + +include(../../qmake/liboxide.pri) +include(../../qmake/sentry.pri) diff --git a/applications/desktop-file-validate/main.cpp b/applications/desktop-file-validate/main.cpp new file mode 100644 index 000000000..92fa172ea --- /dev/null +++ b/applications/desktop-file-validate/main.cpp @@ -0,0 +1,65 @@ +#include + +#include +#include +#include + +using namespace Oxide::Sentry; +using namespace Oxide::Applications; + +int qExit(int ret){ + QTimer::singleShot(0, [ret](){ + qApp->exit(ret); + }); + return qApp->exec(); +} +QTextStream& qStdOut(){ + static QTextStream ts( stdout ); + return ts; +} + +int main(int argc, char *argv[]){ + QCoreApplication app(argc, argv); + sentry_init("desktop-file-validate", argv); + app.setOrganizationName("Eeems"); + app.setOrganizationDomain(OXIDE_SERVICE); + app.setApplicationName("desktop-file-validate"); + app.setApplicationVersion(APP_VERSION); + QCommandLineParser parser; + parser.setApplicationDescription("Validates application registration files"); + parser.applicationDescription(); + parser.addHelpOption(); + parser.addVersionOption(); + QCommandLineOption noHintsOption( + "no-hints", + "Do not output hints about things that might be improved in the application registration." + ); + parser.addOption(noHintsOption); + QCommandLineOption noWarnDeprecatedOption( + "no-warn-deprecated", + "Do not warn about usage of deprecated items that were defined in the previous version of the specification." + ); + parser.addOption(noWarnDeprecatedOption); + QCommandLineOption warnKDEOption("warn-kde", "NOT IMPLEMENTED"); + parser.addOption(warnKDEOption); + parser.addPositionalArgument("file", "Application registration(s) to validate", "FILE..."); + parser.process(app); + auto args = parser.positionalArguments(); + if(args.empty()){ + parser.showHelp(EXIT_FAILURE); + } + bool skipHint = parser.isSet(noHintsOption); + bool skipDeprecations = parser.isSet(noWarnDeprecatedOption); + for(QString path : args){ + for(auto error : validateRegistration(path)){ + if(skipHint && error.level == ErrorLevel::Hint){ + continue; + } + if(skipDeprecations && error.level == ErrorLevel::Deprecation){ + continue; + } + qStdOut() << path << ": " << error << Qt::endl; + } + } + return qExit(EXIT_SUCCESS); +} diff --git a/applications/gio/.gitignore b/applications/gio/.gitignore new file mode 100644 index 000000000..fab7372d7 --- /dev/null +++ b/applications/gio/.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/gio/cat.h b/applications/gio/cat.h new file mode 100644 index 000000000..e88d28886 --- /dev/null +++ b/applications/gio/cat.h @@ -0,0 +1,37 @@ +#pragma once + +#include "common.h" + +#include +#include + +class CatCommand : ICommand{ + O_COMMAND(CatCommand, "cat", "Concatenates the given files and prints them to the standard output.") + int arguments() override{ + parser->addPositionalArgument("location", "Locations to concatenate", "LOCATION..."); + return EXIT_SUCCESS; + } + int command(const QStringList& args) override{ + for(const auto& path : args){ + auto url = urlFromPath(path); + if(!url.isLocalFile()){ + GIO_ERROR(url, path, "Error while parsing path", "Path is not a local file"); + continue; + } + QFile file(path); + if(!file.exists()){ + GIO_ERROR(url, path, "Error when getting information for file", "No such file or directory"); + continue; + } + if(!file.open(QFile::ReadOnly)){ + GIO_ERROR(url, path, "Error opening file", "Permission denied"); + continue; + } + while(!file.atEnd()){ + qStdOut() << file.read(1024); + } + file.close(); + } + return EXIT_SUCCESS; + } +}; diff --git a/applications/gio/common.cpp b/applications/gio/common.cpp new file mode 100644 index 000000000..3f7d169d9 --- /dev/null +++ b/applications/gio/common.cpp @@ -0,0 +1,112 @@ +#include "common.h" + +#include +#include + +QTextStream& qStdOut(){ + static QTextStream ts( stdout ); + return ts; +} + +QMap* ICommand::commands = new QMap; +QCommandLineParser* ICommand::parser = nullptr; + +QCommandLineOption ICommand::versionOption(){ + static QCommandLineOption value( + {"v", "version"}, + "Displays version information." + ); + return value; +} + +#define FULL_SIZE 66 + +QString ICommand::commandsHelp(){ + QString value; + const QList& keys = commands->keys(); + int leftSize = (*std::max_element(keys.constBegin(), keys.constEnd(), [](const QString& v1, const QString& v2){ + return v1.length() < v2.length(); + })).length() + 2; + int rightSize = FULL_SIZE - leftSize - 1; + for(const auto& name : keys){ + const auto& item = commands->value(name); + value += "\n" + name.leftJustified(leftSize, ' '); + if(item.help > rightSize){ + QStringList words = item.help.split(' '); + QString padding = QString().leftJustified(leftSize + 1, ' '); + bool first = true; + while(!words.isEmpty()){ + QString strPart; + while(!words.isEmpty()){ + auto word = words.first(); + // If the first word is larger than a single line + if(!strPart.length() && word.length() > rightSize){ + // Fill the line with what you can + strPart = word.left(rightSize); + words.replace(0, word.mid(rightSize)); + // Exit since we have what we need to display + break; + } + // Exit if the next word would be too long + if(strPart.length() + 1 + word.length() > rightSize){ + break; + } + // Add the word to the results + strPart += word + " "; + words.removeFirst(); + } + // Don't padd the first line, it doesn't need it + if(first){ + first = false; + }else{ + value += "\n"; + value += padding; + } + value += strPart; + } + }else{ + value += item.help; + } + } + return value; +} +int ICommand::exec(QCommandLineParser& _parser){ + parser = &_parser; + QStringList args = _parser.positionalArguments(); + if (args.isEmpty()) { + _parser.showHelp(EXIT_FAILURE); + } + if(_parser.isSet(ICommand::versionOption())){ + _parser.showVersion(); + } + auto name = args.first(); + if(!commands->contains(name)){ + _parser.showHelp(EXIT_FAILURE); + } + auto command = commands->value(name).command; + parser->clearPositionalArguments(); + parser->addPositionalArgument("", "", name); + auto res = command->arguments(); + if(res){ + return res; + } + _parser.process(*qApp); + if(_parser.isSet(versionOption())){ + _parser.showHelp(EXIT_FAILURE); + } + args = _parser.positionalArguments(); + args.removeFirst(); + if(!command->allowEmpty && args.isEmpty()){ + _parser.showHelp(EXIT_FAILURE); + } + return command->command(args); +} + +ICommand::ICommand(bool allowEmpty) : allowEmpty(allowEmpty){} +QUrl ICommand::urlFromPath(const QString& path){ + auto url = QUrl::fromUserInput(path, QDir::currentPath(), QUrl::AssumeLocalFile); + if(url.scheme().isEmpty()){ + url.setScheme("file"); + } + return url; +} diff --git a/applications/gio/common.h b/applications/gio/common.h new file mode 100644 index 000000000..ecf06c61e --- /dev/null +++ b/applications/gio/common.h @@ -0,0 +1,82 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +QTextStream& qStdOut(); + +#define GIO_ERROR(url, path, message, error) \ + qDebug() \ + << "gio:" \ + << url.toString().toStdString().c_str() \ + << ": " \ + << message \ + << QFileInfo(path).absoluteFilePath().toStdString().c_str() \ + << ": " \ + << error; + +#define O_COMMAND_BODY(_class, _name, _help, _allowEmpty) \ +public: \ + _class() : ICommand(_allowEmpty) { \ + if(commands->contains(_name)){ \ + throw "ICommand " _name " already exists"; \ + } \ + commands->insert(_name, Command{ \ + .help = _help, \ + .command = this, \ + }); \ + } +#define O_COMMAND_X_1(_class, _name, _help) O_COMMAND_BODY(_class, _name, _help, false) +#define O_COMMAND_X_2(_class, _name, _help, _allowEmpty) O_COMMAND_BODY(_class, _name, _help, _allowEmpty) +#define O_COMMAND_X_get_func(arg1, arg2, arg3, arg4, arg5, ...) arg5 +#define O_COMMAND_X(...) \ + O_COMMAND_X_get_func(__VA_ARGS__, \ + O_COMMAND_X_2, \ + O_COMMAND_X_1, \ + ) + +#define O_COMMAND(...) O_COMMAND_X(__VA_ARGS__)(__VA_ARGS__) +#define STATIC_INSTANCE(_class) Q_UNUSED(new _class()); +#define O_COMMAND_STUB(_name) \ + class _name ## Command : ICommand{ \ + O_COMMAND(_name ## Command, #_name, "NOT IMPLEMENTED") \ + int arguments() override{ \ + qDebug() << "This has not been implemented."; \ + return EXIT_FAILURE; \ + } \ + int command(const QStringList& args) override{ \ + Q_UNUSED(args) \ + qDebug() << "This has not been implemented."; \ + return EXIT_FAILURE; \ + } \ + }; \ + STATIC_INSTANCE(_name ## Command); + +class ICommand; + +struct Command { + QString help; + ICommand* command; +}; + +class ICommand { +public: + static QMap* commands; + static QCommandLineParser* parser; + + static QCommandLineOption versionOption(); + static QString commandsHelp(); + static int exec(QCommandLineParser& _parser); + + ICommand(bool allowEmpty); + virtual int arguments(){ return EXIT_FAILURE; } + virtual int command(const QStringList& args){ return EXIT_FAILURE; } + +protected: + bool allowEmpty = false; + QUrl urlFromPath(const QString& path); +}; diff --git a/applications/gio/copy.h b/applications/gio/copy.h new file mode 100644 index 000000000..b3b00e860 --- /dev/null +++ b/applications/gio/copy.h @@ -0,0 +1,130 @@ +#pragma once + +#include "common.h" + +#include +#include +#include + +// [OPTION...] SOURCE... DESTINATION +class CopyCommand : ICommand{ + O_COMMAND( + CopyCommand, "copy", + "Copies one or more files from SOURCE to DESTINATION. If more than one source is specified, the destination must be a directory. " + ) + int arguments() override{ + parser->addOption(noTargetDirectoryOption); + parser->addOption(progressOption); + parser->addOption(interactiveOption); + parser->addOption(preserveOption); + parser->addOption(backupOption); + parser->addOption(noDereferenceOption); + parser->addOption(defaultPermissionsOption); + parser->addPositionalArgument("SOURCE", "The source file(s) to copy to the destination", "SOURCE..."); + parser->addPositionalArgument("DESTINATION", "The destination to copy to"); + return EXIT_SUCCESS; + } + int command(const QStringList& args) override{ + if(args.length() < 2){ + parser->showHelp(EXIT_FAILURE); + } + auto toPath = args.last(); + auto toUrl = urlFromPath(toPath); + if(!toUrl.isLocalFile()){ + GIO_ERROR(toUrl, toPath, "Error while parsing path", "Path is not a local file"); + return EXIT_FAILURE; + } + if(!QFile::exists(toPath)){ + GIO_ERROR(toUrl, toPath, "Error when getting information for file", "No such file or directory"); + return EXIT_FAILURE; + } + QFileInfo toInfo(toPath); + if(toInfo.isFile() && args.length() > 2){ + qDebug() << "gio: Destination" << toPath << "is not a directory"; + parser->showHelp(EXIT_FAILURE); + } + bool failed = false; + for(const auto& path : args.mid(0, args.length() - 1)){ + auto url = urlFromPath(path); + if(!url.isLocalFile()){ + GIO_ERROR(url, path, "Error while parsing path", "Path is not a local file"); + failed = true; + continue; + } + QFileInfo info(path); + if(!info.exists()){ + GIO_ERROR(url, path, "Error when getting information for file", "No such file or directory"); + failed = true; + continue; + } + if(info.isDir()){ + qDebug() << "gio:" << url << ": Can’t recursively copy directory"; + failed = true; + continue; + } + if(!info.isReadable()){ + GIO_ERROR(url, path, "Error opening file", "Permission denied"); + failed = true; + continue; + } + } + if(failed){ + return EXIT_FAILURE; + } + auto cpArgs = QStringList() << "cp"; + if(parser->isSet(noTargetDirectoryOption)){ + cpArgs.append("-T"); + } + if(parser->isSet(interactiveOption)){ + cpArgs.append("-i"); + } + if(parser->isSet(preserveOption) && !parser->isSet(defaultPermissionsOption)){ + cpArgs.append("-p"); + } + if(parser->isSet(noDereferenceOption)){ + cpArgs.append("-P"); + }else{ + cpArgs.append("-L"); + } + auto* p = new QProcess(); + p->setInputChannelMode(QProcess::ForwardedInputChannel); + p->setProcessChannelMode(QProcess::ForwardedChannels); + + p->start("busybox", cpArgs + args); + qApp->processEvents(); + p->waitForFinished(); + auto res = p->exitCode(); + p->deleteLater(); + return res; + } + +private: + QCommandLineOption noTargetDirectoryOption = QCommandLineOption( + {"T", "no-target-directory"}, + "Don’t copy into DESTINATION even if it is a directory." + ); + QCommandLineOption progressOption = QCommandLineOption( + {"p", "progress"}, + "NOT IMPLEMENTED" + ); + QCommandLineOption interactiveOption = QCommandLineOption( + {"i", "interactive"}, + "Prompt for confirmation before overwriting files." + ); + QCommandLineOption preserveOption = QCommandLineOption( + "preserve", + "Preserve all attributes of copied files." + ); + QCommandLineOption backupOption = QCommandLineOption( + {"b", "backup"}, + "NOT IMPLEMENTED" + ); + QCommandLineOption noDereferenceOption = QCommandLineOption( + {"P", "no-dereference"}, + "Never follow symbolic links." + ); + QCommandLineOption defaultPermissionsOption = QCommandLineOption( + "default-permissions", + "Use the default permissions of the current process for the destination file, rather than copying the permissions of the source file." + ); +}; diff --git a/applications/gio/gio.pro b/applications/gio/gio.pro new file mode 100644 index 000000000..e86bcaf03 --- /dev/null +++ b/applications/gio/gio.pro @@ -0,0 +1,32 @@ +QT -= gui +QT += dbus + +CONFIG += c++11 console +CONFIG -= app_bundle + +DEFINES += QT_DEPRECATED_WARNINGS +#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 + +SOURCES += \ + common.cpp \ + main.cpp + +HEADERS += \ + cat.h \ + common.h \ + copy.h \ + help.h \ + launch.h \ + mkdir.h \ + open.h \ + remove.h \ + rename.h \ + version.h + +TARGET = gio +include(../../qmake/common.pri) +target.path = /opt/bin +INSTALLS += target + +include(../../qmake/liboxide.pri) +include(../../qmake/sentry.pri) diff --git a/applications/gio/help.h b/applications/gio/help.h new file mode 100644 index 000000000..d9099445e --- /dev/null +++ b/applications/gio/help.h @@ -0,0 +1,30 @@ +#pragma once + +#include "common.h" + +#include +#include + +class HelpCommand : ICommand{ + O_COMMAND(HelpCommand, "help", "Print help", true) + int arguments() override{ + parser->addPositionalArgument("Commands:", commandsHelp(), "[COMMAND]"); + return EXIT_SUCCESS; + } + int command(const QStringList& args) override{ + if(args.isEmpty()){ + parser->showHelp(EXIT_SUCCESS); + } + auto command = args.first(); + auto tempArgs = QStringList() << command; + if(!commands->contains(command)){ + parser->showHelp(EXIT_FAILURE); + } + parser->clearPositionalArguments(); + parser->addPositionalArgument("", "", command); + auto res = commands->value(command).command->arguments(); + parser->process(tempArgs); + parser->showHelp(res); + return res; + } +}; diff --git a/applications/gio/launch.h b/applications/gio/launch.h new file mode 100644 index 000000000..2ac176ecb --- /dev/null +++ b/applications/gio/launch.h @@ -0,0 +1,90 @@ +#pragma once + +#include "common.h" + +#include +#include + +#include +#include +#include +#include + +using namespace codes::eeems::oxide1; +using namespace Oxide::Applications; + +class LaunchCommand : ICommand{ + O_COMMAND(LaunchCommand, "launch", "Launch an application from an application registration file") + int arguments() override{ + parser->addPositionalArgument("DESKTOP-FILE", "Application registration to launch"); + parser->addPositionalArgument("FILE-ARG", "NOT IMPLEMENTED", "[FILE-ARG...]"); + return EXIT_SUCCESS; + } + int command(const QStringList& args) override{ + const auto& path = args.first(); + auto url = urlFromPath(path); + if(!url.isLocalFile()){ + GIO_ERROR(url, path, "Error while parsing path", "Path is not a local file"); + return EXIT_FAILURE; + } + auto suffix = QFileInfo(url.path()).suffix(); + if(suffix != "oxide"){ + GIO_ERROR(url, path, "Unhandled file extension", suffix); + return EXIT_FAILURE; + } + auto reg = getRegistration(path); + if(reg.isEmpty()){ + GIO_ERROR(url, path, "Invalid application registration", "Invalid JSON"); + return EXIT_FAILURE; + } + if(!reg.contains("flags")){ + reg["flags"] = QJsonArray(); + } + if(!reg["flags"].isArray()){ + GIO_ERROR(url, path, "Invalid application registration", "Key \"flags\" must be an array"); + return EXIT_FAILURE; + } + auto flags = reg["flags"].toArray(); + flags.prepend("transient"); + reg["flags"] = flags; + if(!reg.contains("displayName")){ + QFileInfo info(path); + // Workaround because basename will sometimes return the suffix instead + reg["displayName"] = info.fileName().left(info.fileName().length() - 6); + } + auto name = QUuid::createUuid().toString(); + auto errors = validateRegistration(name, reg); + if(std::any_of(errors.constBegin(), errors.constEnd(), [](const ValidationError& error){ + auto level = error.level; + return level == ErrorLevel::Error || level == ErrorLevel::Critical; + })){ + for(const auto& error : errors){ + if(error.level == ErrorLevel::Error || error.level == ErrorLevel::Critical){ + GIO_ERROR(url, path, "Invalid application registration", error) + } + } + return EXIT_FAILURE; + } + auto bus = QDBusConnection::systemBus(); + General api(OXIDE_SERVICE, OXIDE_SERVICE_PATH, bus); + QDBusObjectPath qpath = api.requestAPI("apps"); + if(qpath.path() == "/"){ + GIO_ERROR(url, path, "Error registering transient application", "Unable to get apps API"); + return EXIT_FAILURE; + } + Apps apps(OXIDE_SERVICE, qpath.path(), bus); + auto properties = registrationToMap(reg, name); + if(properties.isEmpty()){ + GIO_ERROR(url, path, "Error registering transient application", "Unable to convert application registration to QVariantMap"); + return EXIT_FAILURE; + } + qpath = apps.registerApplication(properties); + if(qpath.path() == "/"){ + GIO_ERROR(url, path, "Error registering transient application", "Failed to register" << name.toStdString().c_str()); + return EXIT_FAILURE; + } + Application app(OXIDE_SERVICE, qpath.path(), bus); + app.launch().waitForFinished(); + return EXIT_SUCCESS; + } +}; diff --git a/applications/gio/main.cpp b/applications/gio/main.cpp new file mode 100644 index 000000000..0aceb17f2 --- /dev/null +++ b/applications/gio/main.cpp @@ -0,0 +1,60 @@ +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace Oxide::Sentry; +using namespace Oxide::Applications; + +int main(int argc, char *argv[]){ + QCoreApplication app(argc, argv); + sentry_init("gio", argv); + app.setOrganizationName("Eeems"); + app.setOrganizationDomain(OXIDE_SERVICE); + app.setApplicationName("gio"); + app.setApplicationVersion(APP_VERSION); + + STATIC_INSTANCE(HelpCommand); + STATIC_INSTANCE(VersionCommand); + STATIC_INSTANCE(CatCommand); + STATIC_INSTANCE(CopyCommand); + O_COMMAND_STUB(info); + STATIC_INSTANCE(LaunchCommand); + O_COMMAND_STUB(list); + O_COMMAND_STUB(mime); + STATIC_INSTANCE(MkdirCommand); + O_COMMAND_STUB(monitor); + O_COMMAND_STUB(mount); + O_COMMAND_STUB(move); + STATIC_INSTANCE(OpenCommand); + STATIC_INSTANCE(RenameCommand); + STATIC_INSTANCE(RemoveCommand); + O_COMMAND_STUB(save); + O_COMMAND_STUB(set); + O_COMMAND_STUB(trash); + O_COMMAND_STUB(tree); + + QCommandLineParser parser; + parser.setApplicationDescription("GIO command line tool"); + parser.applicationDescription(); + parser.addHelpOption(); + parser.addOption(ICommand::versionOption()); + parser.addPositionalArgument("Commands:", ICommand::commandsHelp(), "COMMAND"); + parser.setOptionsAfterPositionalArgumentsMode(QCommandLineParser::ParseAsPositionalArguments); + parser.parse(app.arguments()); + parser.setOptionsAfterPositionalArgumentsMode(QCommandLineParser::ParseAsOptions); + return ICommand::exec(parser); +} diff --git a/applications/gio/mkdir.h b/applications/gio/mkdir.h new file mode 100644 index 000000000..3353fae91 --- /dev/null +++ b/applications/gio/mkdir.h @@ -0,0 +1,59 @@ +#pragma once + +#include "common.h" + +#include +#include +#include + +// [OPTION...] SOURCE... DESTINATION +class MkdirCommand : ICommand{ + O_COMMAND( + MkdirCommand, "mkdir", + "Creates directories." + ) + int arguments() override{ + parser->addOption(parentOption); + parser->addPositionalArgument("LOCATION", "The directories to create", "LOCATION..."); + return EXIT_SUCCESS; + } + int command(const QStringList& args) override{ + auto mkdirArgs = QStringList(); + if(parser->isSet(parentOption)){ + mkdirArgs.append("-p"); + } + auto* p = new QProcess(); + p->setInputChannelMode(QProcess::ForwardedInputChannel); + p->setProcessChannelMode(QProcess::ForwardedChannels); + bool failed = false; + for(const auto& path : args){ + auto url = urlFromPath(path); + if(!url.isLocalFile()){ + GIO_ERROR(url, path, "Error while parsing path", "Path is not a local file"); + failed = true; + continue; + } + QFileInfo info(path); + if(info.exists()){ + GIO_ERROR(url, path, "Error creating directory", "File exists"); + failed = true; + continue; + } + + p->start("mkdir", QStringList() << mkdirArgs << path); + qApp->processEvents(); + p->waitForFinished(); + if(p->exitCode()){ + failed = true; + } + } + p->deleteLater(); + return failed ? EXIT_FAILURE : EXIT_SUCCESS; + } + +private: + QCommandLineOption parentOption = QCommandLineOption( + {"p", "parent"}, + "Create parent directories when necessary." + ); +}; diff --git a/applications/gio/open.h b/applications/gio/open.h new file mode 100644 index 000000000..f6f1db2dc --- /dev/null +++ b/applications/gio/open.h @@ -0,0 +1,28 @@ +#pragma once + +#include "common.h" + +#include +#include + +class OpenCommand : ICommand{ + O_COMMAND(OpenCommand, "open", "Open file(s) with xdg-open") + int arguments() override{ + parser->addPositionalArgument("location", "Locations to open", "LOCATION..."); + return EXIT_SUCCESS; + } + int command(const QStringList& args) override{ + for(const auto& path : args){ + auto url = QUrl::fromUserInput(path, QDir::currentPath(), QUrl::AssumeLocalFile); + if(url.scheme().isEmpty()){ + url.setScheme("file"); + } + if(!url.isLocalFile()){ + GIO_ERROR(url, path, "Error while parsing path", "Path is not a local file"); + continue; + } + QProcess::execute("xdg-open", QStringList() << url.toString()); + } + return EXIT_SUCCESS; + } +}; diff --git a/applications/gio/remove.h b/applications/gio/remove.h new file mode 100644 index 000000000..4c26fe3e4 --- /dev/null +++ b/applications/gio/remove.h @@ -0,0 +1,61 @@ +#pragma once + +#include "common.h" + +#include +#include +#include + +// [OPTION...] SOURCE... DESTINATION +class RemoveCommand : ICommand{ + O_COMMAND( + RemoveCommand, "remove", + "Deletes each given file." + ) + int arguments() override{ + parser->addOption(forceOption); + parser->addPositionalArgument("LOCATION", "The locations to remove", "LOCATION..."); + return EXIT_SUCCESS; + } + int command(const QStringList& args) override{ + auto* p = new QProcess(); + p->setInputChannelMode(QProcess::ForwardedInputChannel); + p->setProcessChannelMode(QProcess::ForwardedChannels); + bool failed = false; + for(const auto& path : args){ + auto url = urlFromPath(path); + if(!url.isLocalFile()){ + GIO_ERROR(url, path, "Error while parsing path", "Path is not a local file"); + failed = true; + continue; + } + QFileInfo info(path); + if(!info.exists()){ + if(!parser->isSet(forceOption)){ + GIO_ERROR(url, path, "Error removing file", "No such file or directory"); + failed = true; + } + continue; + } + QStringList rmArgs; + if(info.isDir()){ + rmArgs.append("rmdir"); + }else{ + rmArgs.append("rm"); + rmArgs.append("-f"); + } + p->start("busybox", QStringList() << rmArgs << path); + qApp->processEvents(); + p->waitForFinished(); + failed = failed || p->exitCode(); + } + p->deleteLater(); + return failed ? EXIT_FAILURE : EXIT_SUCCESS; + } + +private: + QCommandLineOption forceOption = QCommandLineOption( + {"f", "force"}, + "Ignore non-existent and non-deletable files." + ); +}; diff --git a/applications/gio/rename.h b/applications/gio/rename.h new file mode 100644 index 000000000..8fd796fb0 --- /dev/null +++ b/applications/gio/rename.h @@ -0,0 +1,48 @@ +#pragma once + +#include "common.h" + +#include +#include +#include + +// [OPTION...] SOURCE... DESTINATION +class RenameCommand : ICommand{ + O_COMMAND( + RenameCommand, "rename", + "Renames a file." + ) + int arguments() override{ + parser->addPositionalArgument("LOCATION", "The location to rename."); + parser->addPositionalArgument("NAME", "The new name."); + return EXIT_SUCCESS; + } + int command(const QStringList& args) override{ + if(args.length() < 2){ + parser->showHelp(EXIT_FAILURE); + } + auto name = args.last(); + if(name.contains("/")){ + qDebug() << "gio: File names cannot contain \"/\""; + return EXIT_FAILURE; + } + auto path = args.first(); + auto url = urlFromPath(path); + if(!url.isLocalFile()){ + GIO_ERROR(url, path, "Error while parsing path", "Path is not a local file"); + return EXIT_FAILURE; + } + QFileInfo info(path); + if(!info.exists()){ + GIO_ERROR(url, path, "Error renaming file", "No such file or directory"); + return EXIT_FAILURE; + } + auto toPath = info.absoluteDir().path() + "/" + name; + if(!QFile::rename(path, toPath)){ + GIO_ERROR(url, path, "Error renaming file", "Rename failed."); + return EXIT_FAILURE; + } + qDebug() << "Rename successful. New uri:" << urlFromPath(toPath).toString().toStdString().c_str(); + return EXIT_SUCCESS; + } +}; diff --git a/applications/gio/version.h b/applications/gio/version.h new file mode 100644 index 000000000..5352cdbfc --- /dev/null +++ b/applications/gio/version.h @@ -0,0 +1,18 @@ +#pragma once + +#include "common.h" + +#include +#include + +class VersionCommand : ICommand{ + O_COMMAND(VersionCommand, "version", "Print the version of Oxide that gio belongs to", true) + int arguments() override{ return EXIT_SUCCESS; } + int command(const QStringList& args) override{ + if(!args.isEmpty()){ + parser->showHelp(EXIT_FAILURE); + } + parser->showVersion(); + return EXIT_SUCCESS; + } +}; diff --git a/applications/launcher/appitem.cpp b/applications/launcher/appitem.cpp index f0a95011b..3f238f93f 100755 --- a/applications/launcher/appitem.cpp +++ b/applications/launcher/appitem.cpp @@ -5,9 +5,9 @@ #include #include +#include + #include "appitem.h" -#include "dbusservice_interface.h" -#include "appsapi_interface.h" #include "mxcfb.h" #include "controller.h" @@ -15,7 +15,7 @@ bool AppItem::ok(){ return getApp() != nullptr; } void AppItem::execute(){ if(!getApp() || !app->isValid()){ - qWarning() << "Application instance is not valid"; + O_WARNING("Application instance is not valid"); return; } qDebug() << "Running application " << app->path(); @@ -28,7 +28,7 @@ void AppItem::execute(){ } void AppItem::stop(){ if(!getApp() || !app->isValid()){ - qWarning() << "Application instance is not valid"; + O_WARNING("Application instance is not valid"); return; } app->stop(); diff --git a/applications/launcher/appitem.h b/applications/launcher/appitem.h index 67f3380e1..b2b2c8927 100644 --- a/applications/launcher/appitem.h +++ b/applications/launcher/appitem.h @@ -1,18 +1,13 @@ #ifndef APP_H #define APP_H #include - -#include "application_interface.h" - -#ifndef OXIDE_SERVICE -#define OXIDE_SERVICE "codes.eeems.oxide1" -#define OXIDE_SERVICE_PATH "/codes/eeems/oxide1" -#endif +#include using namespace codes::eeems::oxide1; class AppItem : public QObject { Q_OBJECT + public: AppItem(QObject* parent) : QObject(parent){} @@ -59,7 +54,7 @@ private slots: } void onIconChanged(QString path){ _imgFile = path; - emit onIconChanged(path); + emit imgFileChanged(path); } private: diff --git a/applications/launcher/controller.cpp b/applications/launcher/controller.cpp index 9d3156103..583b7b0f4 100644 --- a/applications/launcher/controller.cpp +++ b/applications/launcher/controller.cpp @@ -10,10 +10,9 @@ #include #include #include -#include "sysobject.h" +#include #include "controller.h" -#include "dbusservice_interface.h" QSet settings = { "columns", "autoStartApplication" }; QSet booleanSettings {"showWifiDb", "showBatteryPercent", "showBatteryTemperature", "showDate" }; @@ -52,7 +51,7 @@ void Controller::loadSettings(){ if(!line.startsWith("#") && !line.isEmpty()){ QStringList parts = line.split("="); if(parts.length() != 2){ - qWarning() << " Wrong format on " << line; + O_WARNING(" Wrong format on " << line); continue; } QString lhs = parts.at(0).trimmed(); @@ -207,7 +206,7 @@ QList Controller::getApps(){ for(auto item : appsApi->applications()){ auto path = item.value().path(); Application app(OXIDE_SERVICE, path, bus, this); - if(app.hidden()){ + if(app.hidden() || app.transient()){ continue; } auto name = app.name(); @@ -256,80 +255,7 @@ AppItem* Controller::getApplication(QString name){ return nullptr; } -void Controller::importDraftApps(){ - qDebug() << "Importing Draft Applications"; - auto bus = QDBusConnection::systemBus(); - for(auto configDirectoryPath : configDirectoryPaths){ - QDir configDirectory(configDirectoryPath); - configDirectory.setFilter( QDir::Files | QDir::NoSymLinks | QDir::NoDot | QDir::NoDotDot); - auto images = configDirectory.entryInfoList(QDir::NoFilter,QDir::SortFlag::Name); - for(QFileInfo fi : images){ - if(fi.fileName() != "conf"){ - auto f = fi.absoluteFilePath(); - qDebug() << "parsing file " << f; - QFile file(fi.absoluteFilePath()); - if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { - qCritical() << "Couldn't find the file " << f; - continue; - } - QTextStream in(&file); - QVariantMap properties; - while (!in.atEnd()) { - QString line = in.readLine(); - if(line.startsWith("#") || line.trimmed().isEmpty()){ - continue; - } - QStringList parts = line.split("="); - if(parts.length() != 2){ - qWarning() << "wrong format on " << line; - continue; - } - QString lhs = parts.at(0); - QString rhs = parts.at(1); - if(rhs != ":" && rhs != ""){ - if(lhs == "name"){ - properties.insert("name", rhs); - }else if(lhs == "desc"){ - properties.insert("description", rhs); - }else if(lhs == "imgFile"){ - auto icon = configDirectoryPath + "/icons/" + rhs + ".png"; - if(icon.startsWith("qrc:")){ - icon = ""; - } - properties.insert("icon", icon); - }else if(lhs == "call"){ - properties.insert("bin", rhs); - }else if(lhs == "term"){ - properties.insert("onStop", rhs.trimmed()); - } - } - } - file.close(); - auto name = properties["name"].toString(); - QDBusObjectPath path = appsApi->getApplicationPath(name); - if(path.path() != "/"){ - qDebug() << "Already exists" << name; - auto icon = properties["icon"].toString(); - if(icon.isEmpty()){ - continue; - } - Application app(OXIDE_SERVICE, path.path(), bus, this); - if(app.icon().isEmpty()){ - app.setIcon(icon); - } - continue; - } - qDebug() << "Not found, creating..."; - properties.insert("displayName", name); - path = appsApi->registerApplication(properties); - if(path.path() == "/"){ - qDebug() << "Failed to import" << name; - } - } - } - } - qDebug() << "Finished Importing Draft Applications"; -} +void Controller::importDraftApps(){ QProcess::execute("update-desktop-database", QStringList() << "--verbose"); } void Controller::powerOff(){ qDebug() << "Powering off..."; systemApi->powerOff(); diff --git a/applications/launcher/controller.h b/applications/launcher/controller.h index 2c4e129a5..c5cbe6ec5 100644 --- a/applications/launcher/controller.h +++ b/applications/launcher/controller.h @@ -12,15 +12,6 @@ #include "appitem.h" #include "wifinetworklist.h" #include "notificationlist.h" -#include "dbusservice_interface.h" -#include "powerapi_interface.h" -#include "wifiapi_interface.h" -#include "network_interface.h" -#include "bss_interface.h" -#include "appsapi_interface.h" -#include "systemapi_interface.h" -#include "notification_interface.h" -#include "notificationapi_interface.h" #define OXIDE_SERVICE "codes.eeems.oxide1" #define OXIDE_SERVICE_PATH "/codes/eeems/oxide1" diff --git a/applications/launcher/launcher.pro b/applications/launcher/launcher.pro new file mode 100644 index 000000000..18a06de04 --- /dev/null +++ b/applications/launcher/launcher.pro @@ -0,0 +1,54 @@ +QT += gui +QT += quick +QT += dbus + +CONFIG += c++11 +CONFIG += qml_debug +CONFIG += qtquickcompiler +CONFIG += precompile_header + +DEFINES += QT_DEPRECATED_WARNINGS +DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 + +SOURCES += \ + main.cpp \ + controller.cpp \ + appitem.cpp + +RESOURCES += qml.qrc + +TARGET = oxide +include(../../qmake/common.pri) +target.path = /opt/bin +INSTALLS += target + +applications.files = ../../assets/opt/usr/share/applications/codes.eeems.oxide.oxide +applications.path = /opt/usr/share/applications/ +INSTALLS += applications + +icons.files = ../../assets/opt/usr/share/icons/oxide/702x702/splash/oxide.png +icons.path = /opt/usr/share/icons/oxide/702x702/splash/ +INSTALLS += icons + +configFile.files = ../../assets/etc/oxide.conf +configFile.path = /opt/etc/ +INSTALLS += configFile + +DISTFILES += \ + ../../assets/etc/dbus-1/system.d/org.freedesktop.login1.conf \ + ../../assets/etc/oxide.conf + +HEADERS += \ + controller.h \ + appitem.h \ + mxcfb.h \ + notificationlist.h \ + oxide_stable.h \ + wifinetworklist.h + +PRECOMPILED_HEADER = \ + oxide_stable.h + +include(../../qmake/epaper.pri) +include(../../qmake/liboxide.pri) +include(../../qmake/sentry.pri) diff --git a/applications/launcher/main.cpp b/applications/launcher/main.cpp index c60679912..f9a9d6836 100644 --- a/applications/launcher/main.cpp +++ b/applications/launcher/main.cpp @@ -5,7 +5,6 @@ #include #include #include -#include #include #include #include @@ -17,7 +16,6 @@ #include #include #include -#include #include "controller.h" diff --git a/applications/launcher/notificationlist.h b/applications/launcher/notificationlist.h index 558c2cdb7..015eaf9fd 100644 --- a/applications/launcher/notificationlist.h +++ b/applications/launcher/notificationlist.h @@ -3,8 +3,6 @@ #include -#include "notification_interface.h" - using namespace codes::eeems::oxide1; class NotificationItem : public QObject { @@ -13,6 +11,7 @@ class NotificationItem : public QObject { Q_PROPERTY(QString text READ text NOTIFY textChanged) Q_PROPERTY(QString icon READ icon NOTIFY iconChanged) Q_PROPERTY(int created READ created NOTIFY createdChanged) + public: NotificationItem(Notification* notification, QObject* parent) : QObject(parent) { m_identifier = notification->identifier(); @@ -71,6 +70,7 @@ public slots: class NotificationList : public QAbstractListModel { Q_OBJECT + public: NotificationList() : QAbstractListModel(nullptr) {} diff --git a/applications/launcher/oxide.pro b/applications/launcher/oxide.pro deleted file mode 100644 index 84238ac64..000000000 --- a/applications/launcher/oxide.pro +++ /dev/null @@ -1,75 +0,0 @@ -QT += gui -QT += quick -QT += dbus -CONFIG += c++11 -CONFIG += qml_debug - -DEFINES += QT_DEPRECATED_WARNINGS -DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 - -SOURCES += \ - main.cpp \ - controller.cpp \ - appitem.cpp - - -RESOURCES += qml.qrc - -# Default rules for deployment. -target.path = /opt/bin -!isEmpty(target.path): INSTALLS += target - -INCLUDEPATH += ../../shared - -DBUS_INTERFACES += ../../interfaces/dbusservice.xml -DBUS_INTERFACES += ../../interfaces/powerapi.xml -DBUS_INTERFACES += ../../interfaces/wifiapi.xml -DBUS_INTERFACES += ../../interfaces/network.xml -DBUS_INTERFACES += ../../interfaces/bss.xml -DBUS_INTERFACES += ../../interfaces/appsapi.xml -DBUS_INTERFACES += ../../interfaces/application.xml -DBUS_INTERFACES += ../../interfaces/systemapi.xml -DBUS_INTERFACES += ../../interfaces/notificationapi.xml -DBUS_INTERFACES += ../../interfaces/notification.xml - -icons.files = ../../assets/etc/draft/icons/oxide-splash.png -icons.path = /opt/etc/draft/icons -INSTALLS += icons - -configFile.files = ../../assets/etc/oxide.conf -configFile.path = /opt/etc/ -INSTALLS += configFile - -DISTFILES += \ - ../../assets/etc/dbus-1/system.d/org.freedesktop.login1.conf \ - ../../assets/etc/oxide.conf - -HEADERS += \ - controller.h \ - appitem.h \ - mxcfb.h \ - notificationlist.h \ - wifinetworklist.h - - -LIBS += -L$$PWD/../../shared/ -lqsgepaper -INCLUDEPATH += $$PWD/../../shared -DEPENDPATH += $$PWD/../../shared - -exists($$PWD/../../.build/sentry) { - LIBS += -L$$PWD/../../.build/sentry/lib -lsentry -ldl -lcurl -lbreakpad_client - INCLUDEPATH += $$PWD/../../.build/sentry/include - DEPENDPATH += $$PWD/../../.build/sentry/lib - - library.files = ../../.build/sentry/libsentry.so - library.path = /opt/lib - INSTALLS += library -} - -LIBS += -L$$PWD/../../.build/liboxide -lliboxide -INCLUDEPATH += $$PWD/../../shared/liboxide -DEPENDPATH += $$PWD/../../shared/liboxide - -QMAKE_RPATHDIR += /lib /usr/lib /opt/lib /opt/usr/lib - -VERSION = 2.5 diff --git a/applications/launcher/oxide_stable.h b/applications/launcher/oxide_stable.h new file mode 100644 index 000000000..1c024ce1f --- /dev/null +++ b/applications/launcher/oxide_stable.h @@ -0,0 +1,38 @@ +#if defined __cplusplus +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "controller.h" +#include "mxcfb.h" +#endif diff --git a/applications/launcher/wifinetworklist.h b/applications/launcher/wifinetworklist.h index 039c78d9f..5eb3fbb5d 100644 --- a/applications/launcher/wifinetworklist.h +++ b/applications/launcher/wifinetworklist.h @@ -2,14 +2,7 @@ #define WIFINETWORK_H #include - -#include "wifiapi_interface.h" -#include "network_interface.h" -#include "bss_interface.h" - -#ifndef OXIDE_SERVICE -#define OXIDE_SERVICE "codes.eeems.oxide1" -#endif +#include using namespace codes::eeems::oxide1; @@ -20,6 +13,7 @@ class WifiNetwork : public QObject { Q_PROPERTY(bool connected READ connected NOTIFY connectedChanged) Q_PROPERTY(bool available READ available NOTIFY availableChanged) Q_PROPERTY(bool known READ known NOTIFY knownChanged) + public: WifiNetwork(Network* network, Wifi* api, QObject* parent) : QObject(parent), @@ -137,6 +131,7 @@ class WifiNetworkList : public QAbstractListModel { Q_OBJECT Q_PROPERTY(bool scanning READ scanning NOTIFY scanningChanged); + public: explicit WifiNetworkList() : QAbstractListModel(nullptr), networks() {} diff --git a/applications/lockscreen/controller.h b/applications/lockscreen/controller.h index cbea2b000..17c6ff94d 100644 --- a/applications/lockscreen/controller.h +++ b/applications/lockscreen/controller.h @@ -5,18 +5,9 @@ #include #include -#include "dbusservice_interface.h" -#include "systemapi_interface.h" -#include "powerapi_interface.h" -#include "wifiapi_interface.h" -#include "appsapi_interface.h" -#include "application_interface.h" - using namespace codes::eeems::oxide1; using namespace Oxide::Sentry; -#define DECAY_SETTINGS_VERSION 1 - enum State { Normal, PowerSaving }; enum BatteryState { BatteryUnknown, BatteryCharging, BatteryDischarging, BatteryNotPresent }; enum ChargerState { ChargerUnknown, ChargerConnected, ChargerNotConnected, ChargerNotPresent }; @@ -30,9 +21,10 @@ class Controller : public QObject { Q_PROPERTY(bool telemetry READ telemetry WRITE setTelemetry NOTIFY telemetryChanged) Q_PROPERTY(bool applicationUsage READ applicationUsage WRITE setApplicationUsage NOTIFY applicationUsageChanged) Q_PROPERTY(bool crashReport READ crashReport WRITE setCrashReport NOTIFY crashReportChanged) + public: Controller(QObject* parent) - : QObject(parent), confirmPin(), settings(this) { + : QObject(parent), confirmPin() { clockTimer = new QTimer(root); auto bus = QDBusConnection::systemBus(); qDebug() << "Waiting for tarnish to start up..."; @@ -94,10 +86,26 @@ class Controller : public QObject { } appsApi = new Apps(OXIDE_SERVICE, path.path(), bus, this); - settings.sync(); - auto version = settings.value("version", 0).toInt(); - if(version < DECAY_SETTINGS_VERSION){ - migrate(&settings, version); + QSettings settings; + if(QFile::exists(settings.fileName())){ + qDebug() << "Importing old settings"; + settings.sync(); + if(settings.contains("pin")){ + qDebug() << "Importing old pin"; + sharedSettings.set_pin(settings.value("pin").toString()); + } + if(settings.contains("onLogin")){ + qDebug() << "Importing old onLogin"; + sharedSettings.set_onLogin(settings.value("onLogin").toString()); + } + if(settings.contains("onFailedLogin")){ + qDebug() << "Importing old onFailedLogin"; + sharedSettings.set_onFailedLogin(settings.value("onFailedLogin").toString()); + } + settings.clear(); + settings.sync(); + QFile::remove(settings.fileName()); + sharedSettings.sync(); } connect(&sharedSettings, &Oxide::SharedSettings::firstLaunchChanged, this, &Controller::firstLaunchChanged); @@ -148,7 +156,7 @@ class Controller : public QObject { return; } // There is no PIN configuration - if(!settings.contains("pin")){ + if(!sharedSettings.has_pin()){ qDebug() << "No Pin"; QTimer::singleShot(100, [this]{ stateControllerUI->setProperty("state", xochitlPin().isEmpty() ? "pinPrompt" : "import"); @@ -173,7 +181,6 @@ class Controller : public QObject { setState("loading"); }); }); - } void launchStartupApp(){ QDBusObjectPath path = appsApi->startupApplication(); @@ -181,13 +188,13 @@ class Controller : public QObject { path = appsApi->getApplicationPath("codes.eeems.oxide"); } if(path.path() == "/"){ - qWarning() << "Unable to find startup application to launch."; + O_WARNING("Unable to find startup application to launch."); return; } Application app(OXIDE_SERVICE, path.path(), QDBusConnection::systemBus()); app.launch(); } - bool hasPin(){ return settings.contains("pin") && storedPin().length(); } + bool hasPin(){ return sharedSettings.has_pin() && storedPin().length(); } void previousApplication(){ if(!appsApi->previousApplication()){ launchStartupApp(); @@ -230,9 +237,9 @@ class Controller : public QObject { }); }); return true; - }else if(state == "loaded"){ qDebug() << "PIN doesn't match!"; + O_DEBUG(pin << "!=" << storedPin()); onFailedLogin(); return false; } @@ -297,10 +304,16 @@ class Controller : public QObject { } stateControllerUI->setProperty("state", state); } - QString storedPin() { return settings.value("pin", "").toString(); } + QString storedPin() { + if(!sharedSettings.has_pin()){ + O_DEBUG("Does not have pin and storedPin was called"); + return ""; + } + return sharedSettings.pin(); + } void setStoredPin(QString pin) { - settings.setValue("pin", pin); - settings.sync(); + sharedSettings.set_pin(pin); + sharedSettings.sync(); } void setRoot(QObject* root){ this->root = root; } @@ -443,31 +456,31 @@ private slots: // TODO handle charger } void onLogin(){ - if(!settings.contains("onLogin")){ + if(!sharedSettings.has_onLogin()){ return; } - auto path = settings.value("onLogin").toString(); + auto path = sharedSettings.onLogin(); if(!QFile::exists(path)){ - qWarning() << "onLogin script does not exist" << path; + O_WARNING("onLogin script does not exist" << path); return; } if(!QFileInfo(path).isExecutable()){ - qWarning() << "onLogin script is not executable" << path; + O_WARNING("onLogin script is not executable" << path); return; } QProcess::execute(path, QStringList()); } void onFailedLogin(){ - if(!settings.contains("onFailedLogin")){ + if(!sharedSettings.has_onFailedLogin()){ return; } - auto path = settings.value("onFailedLogin").toString(); + auto path = sharedSettings.onFailedLogin(); if(!QFile::exists(path)){ - qWarning() << "onFailedLogin script does not exist" << path; + O_WARNING("onFailedLogin script does not exist" << path); return; } if(!QFileInfo(path).isExecutable()){ - qWarning() << "onFailedLogin script is not executable" << path; + O_WARNING("onFailedLogin script is not executable" << path); return; } QProcess::execute(path, QStringList()); @@ -475,7 +488,6 @@ private slots: private: QString confirmPin; - QSettings settings; General* api; System* systemApi; codes::eeems::oxide1::Power* powerApi; @@ -511,15 +523,6 @@ private slots: return pinEntryUI; } - 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", DECAY_SETTINGS_VERSION); - settings->sync(); - } - static QString xochitlPin(){ return xochitlSettings.passcode(); } static void removeXochitlPin(){ xochitlSettings.remove("Passcode"); diff --git a/applications/lockscreen/decay.pro b/applications/lockscreen/decay.pro deleted file mode 100644 index b65fd6990..000000000 --- a/applications/lockscreen/decay.pro +++ /dev/null @@ -1,52 +0,0 @@ -QT += gui -QT += quick -QT += dbus - -CONFIG += c++11 -CONFIG += qml_debug - -DEFINES += QT_DEPRECATED_WARNINGS -DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 - -SOURCES += \ - main.cpp - -# Default rules for deployment. -target.path = /opt/bin -!isEmpty(target.path): INSTALLS += target - -DBUS_INTERFACES += ../../interfaces/dbusservice.xml -DBUS_INTERFACES += ../../interfaces/systemapi.xml -DBUS_INTERFACES += ../../interfaces/powerapi.xml -DBUS_INTERFACES += ../../interfaces/wifiapi.xml -DBUS_INTERFACES += ../../interfaces/appsapi.xml -DBUS_INTERFACES += ../../interfaces/application.xml - -INCLUDEPATH += ../../shared -HEADERS += \ - controller.h - -RESOURCES += \ - qml.qrc - -LIBS += -L$$PWD/../../shared/ -lqsgepaper -INCLUDEPATH += $$PWD/../../shared -DEPENDPATH += $$PWD/../../shared - -exists($$PWD/../../.build/sentry) { - LIBS += -L$$PWD/../../.build/sentry/lib -lsentry -ldl -lcurl -lbreakpad_client - INCLUDEPATH += $$PWD/../../.build/sentry/include - DEPENDPATH += $$PWD/../../.build/sentry/lib - - library.files = ../../.build/sentry/libsentry.so - library.path = /opt/lib - INSTALLS += library -} - -LIBS += -L$$PWD/../../.build/liboxide -lliboxide -INCLUDEPATH += $$PWD/../../shared/liboxide -DEPENDPATH += $$PWD/../../shared/liboxide - -QMAKE_RPATHDIR += /lib /usr/lib /opt/lib /opt/usr/lib - -VERSION = 2.5 diff --git a/applications/lockscreen/lockscreen.pro b/applications/lockscreen/lockscreen.pro new file mode 100644 index 000000000..4018bfef9 --- /dev/null +++ b/applications/lockscreen/lockscreen.pro @@ -0,0 +1,32 @@ +QT += gui +QT += quick +QT += dbus + +CONFIG += c++11 +CONFIG += qml_debug +CONFIG += qtquickcompiler + +DEFINES += QT_DEPRECATED_WARNINGS +DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 + +SOURCES += \ + main.cpp + +TARGET = decay +include(../../qmake/common.pri) +target.path = /opt/bin +INSTALLS += target + +applications.files = ../../assets/opt/usr/share/applications/codes.eeems.decay.oxide +applications.path = /opt/usr/share/applications/ +INSTALLS += applications + +HEADERS += \ + controller.h + +RESOURCES += \ + qml.qrc + +include(../../qmake/epaper.pri) +include(../../qmake/liboxide.pri) +include(../../qmake/sentry.pri) diff --git a/applications/lockscreen/main.cpp b/applications/lockscreen/main.cpp index 0135c5767..946ad7d06 100644 --- a/applications/lockscreen/main.cpp +++ b/applications/lockscreen/main.cpp @@ -5,6 +5,7 @@ #include #include +#include #include "controller.h" @@ -12,8 +13,6 @@ Q_IMPORT_PLUGIN(QsgEpaperPlugin) #endif -#include "dbusservice_interface.h" - using namespace codes::eeems::oxide1; using namespace Oxide; using namespace Oxide::Sentry; @@ -39,7 +38,7 @@ int main(int argc, char *argv[]){ app.setOrganizationName("Eeems"); app.setOrganizationDomain(OXIDE_SERVICE); app.setApplicationName("decay"); - app.setApplicationVersion(OXIDE_INTERFACE_VERSION); + app.setApplicationVersion(APP_VERSION); Controller controller(&app); QQmlApplicationEngine engine; QQmlContext* context = engine.rootContext(); diff --git a/applications/notify-send/.gitignore b/applications/notify-send/.gitignore new file mode 100644 index 000000000..fab7372d7 --- /dev/null +++ b/applications/notify-send/.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/notify-send/main.cpp b/applications/notify-send/main.cpp new file mode 100644 index 000000000..428edd7ff --- /dev/null +++ b/applications/notify-send/main.cpp @@ -0,0 +1,189 @@ +#include +#include +#include +#include +#include + +#include + +using namespace codes::eeems::oxide1; +using namespace Oxide::Sentry; +using namespace Oxide::JSON; + +int qExit(int ret){ + QTimer::singleShot(0, [ret](){ + qApp->exit(ret); + }); + return qApp->exec(); +} + +int main(int argc, char *argv[]){ + QCoreApplication app(argc, argv); + sentry_init("notify-send", argv); + app.setOrganizationName("Eeems"); + app.setOrganizationDomain(OXIDE_SERVICE); + app.setApplicationName("notify-send"); + app.setApplicationVersion(APP_VERSION); + QCommandLineParser parser; + parser.setApplicationDescription("a program to send desktop notifications"); + parser.applicationDescription(); + parser.addVersionOption(); + parser.addPositionalArgument("summary", "Summary of the notification", "{SUMMARY}"); + parser.addPositionalArgument("body", "Body of the notification", "[BODY]"); + QCommandLineOption appNameOption( + {"a", "app-name"}, + "Specifies the app name for the notification.", + "APP_NAME" + ); + parser.addOption(appNameOption); + QCommandLineOption iconOption( + {"i", "icon"}, + "Specifies an icon filename or stock icon to display.", + "ICON" + ); + parser.addOption(iconOption); + QCommandLineOption expireOption( + {"t", "expire-time"}, + "The duration, in milliseconds to wait for the notification. Does nothing without --wait", + "TIME" + ); + parser.addOption(expireOption); + QCommandLineOption printOption( + {"p", "print-id"}, + "Print the notification identifier." + ); + parser.addOption(printOption); + QCommandLineOption replaceOption( + {"r", "replace-id"}, + "The identifier of the notification to replace.", + "REPLACE_ID" + ); + parser.addOption(replaceOption); + QCommandLineOption waitOption( + {"w", "wait"}, + "Wait for the notification to be closed before exiting. If the expire-time is set, it will be used as the maximum waiting time." + ); + parser.addOption(waitOption); + QCommandLineOption transientOption( + {"e", "transient"}, + "Show a transient notification. This notification will be removed immediatly after being shown on the screen" + ); + parser.addOption(transientOption); + QCommandLineOption urgencyOption( + {"u", "urgency"}, + "NOT IMPLEMENTED", + "LEVEL" + ); + parser.addOption(urgencyOption); + QCommandLineOption appOption( + {"A", "action"}, + "NOT IMPLEMENTED", + "[NAME=]TEXT" + ); + parser.addOption(appOption); + QCommandLineOption categoryOption( + {"c", "category"}, + "NOT IMPLEMENTED", + "TYPE[,TYPE]" + ); + parser.addOption(categoryOption); + QCommandLineOption hintOption( + {"h", "hint"}, + "NOT IMPLEMENTED", + "TYPE:NAME:VALUE" + ); + parser.addOption(hintOption); + QCommandLineOption helpOption( + {"?", "help"}, + "Show help and exit" + ); + parser.addOption(helpOption); + parser.process(app); + if(parser.isSet(helpOption)){ + parser.showHelp(); + } + QStringList args = parser.positionalArguments(); + if(args.isEmpty()){ +#ifdef SENTRY + sentry_breadcrumb("error", "No arguments"); +#endif + parser.showHelp(EXIT_FAILURE); + } + if(args.count() > 2){ +#ifdef SENTRY + sentry_breadcrumb("error", "Too many arguments"); +#endif + parser.showHelp(EXIT_FAILURE); + } + auto bus = QDBusConnection::systemBus(); + General api(OXIDE_SERVICE, OXIDE_SERVICE_PATH, bus); + qDebug() << "Requesting notification API..."; + QDBusObjectPath path = api.requestAPI("notification"); + if(path.path() == "/"){ + qDebug() << "Unable to get notification API"; + return qExit(EXIT_FAILURE); + } + Notifications notifications(OXIDE_SERVICE, path.path(), bus); + QString guid; + auto appName = parser.isSet(appNameOption) ? parser.value(appNameOption) : "codes.eeems.notify-send"; + auto iconPath = parser.isSet(iconOption) ? parser.value(iconOption) : ""; + auto text = args.join("\n"); + if(parser.isSet(replaceOption)){ + guid = parser.value(replaceOption); + path = notifications.get(guid); + if(path.path() == "/" || !notifications.take(guid)){ + guid = QUuid::createUuid().toString(); + path = notifications.add(guid, appName, text, iconPath); + } + if(path.path() == "/"){ + qDebug() << "Failed to add notification"; + return qExit(EXIT_FAILURE); + } + Notification notification(OXIDE_SERVICE, path.path(), bus); + notification.setApplication(appName); + notification.setIcon(iconPath); + notification.setText(text); + }else{ + guid = QUuid::createUuid().toString(); + path = notifications.add(guid, appName, text, iconPath); + if(path.path() == "/"){ + qDebug() << "Failed to add notification"; + return qExit(EXIT_FAILURE); + } + } + Notification notification(OXIDE_SERVICE, path.path(), bus); + if(parser.isSet(printOption)){ + QTextStream qStdOut(stdout, QIODevice::WriteOnly); + qStdOut << guid << Qt::endl; + } + qDebug() << "Displaying notification" << guid; + notification.display().waitForFinished(); + if(parser.isSet(waitOption)){ + qDebug() << "Waiting for notification to be closed"; + if(!Oxide::DBusConnect( + ¬ification, + "removed", + [](QVariantList args){ + Q_UNUSED(args); + qApp->exit(EXIT_SUCCESS); + }, + true + )){ + qDebug() << "Failed to wait for notification to exit"; + return qExit(EXIT_FAILURE); + } + if(parser.isSet(expireOption)){ + auto timeout = parser.value(expireOption); + qDebug() << ("Timeout set to " + timeout + "ms").toStdString().c_str(); + QTimer::singleShot(timeout.toInt(), [¬ification]{ + qDebug() << "Notification wait timed out"; + notification.remove().waitForFinished(); + qApp->exit(EXIT_FAILURE); + }); + } + return app.exec(); + }else if(parser.isSet(transientOption)){ + notification.remove(); + } + return qExit(EXIT_SUCCESS); +} diff --git a/applications/notify-send/notify-send.pro b/applications/notify-send/notify-send.pro new file mode 100644 index 000000000..49c8d89c5 --- /dev/null +++ b/applications/notify-send/notify-send.pro @@ -0,0 +1,21 @@ +QT -= gui +QT += dbus + +CONFIG += c++11 console +CONFIG -= app_bundle + +DEFINES += QT_DEPRECATED_WARNINGS +#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 + +SOURCES += \ + main.cpp + +HEADERS += + +TARGET = notify-send +include(../../qmake/common.pri) +target.path = /opt/bin +INSTALLS += target + +include(../../qmake/liboxide.pri) +include(../../qmake/sentry.pri) diff --git a/applications/process-manager/erode.pro b/applications/process-manager/erode.pro deleted file mode 100755 index a48d1175d..000000000 --- a/applications/process-manager/erode.pro +++ /dev/null @@ -1,52 +0,0 @@ -QT += quick -CONFIG += c++11 - -DEFINES += QT_DEPRECATED_WARNINGS -DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 - -SOURCES += main.cpp - -RESOURCES += qml.qrc - -# Default rules for deployment. -target.path = /opt/bin -!isEmpty(target.path): INSTALLS += target - -icons.files += \ - ../../assets/etc/draft/icons/erode.svg \ - ../../assets/etc/draft/icons/erode-splash.png -icons.path = /opt/etc/draft/icons -INSTALLS += icons - -HEADERS += \ - controller.h \ - taskitem.h \ - tasklist.h - -INCLUDEPATH += $$PWD/../../shared -INCLUDEPATH += ../../shared -DEPENDPATH += $$PWD/../../shared - -LIBS += -lsystemd - -LIBS += -L$$PWD/../../shared/ -lqsgepaper -INCLUDEPATH += $$PWD/../../shared -DEPENDPATH += $$PWD/../../shared - -exists($$PWD/../../.build/sentry) { - LIBS += -L$$PWD/../../.build/sentry/lib -lsentry -ldl -lcurl -lbreakpad_client - INCLUDEPATH += $$PWD/../../.build/sentry/include - DEPENDPATH += $$PWD/../../.build/sentry/lib - - library.files = ../../.build/sentry/libsentry.so - library.path = /opt/lib - INSTALLS += library -} - -LIBS += -L$$PWD/../../.build/liboxide -lliboxide -INCLUDEPATH += $$PWD/../../shared/liboxide -DEPENDPATH += $$PWD/../../shared/liboxide - -QMAKE_RPATHDIR += /lib /usr/lib /opt/lib /opt/usr/lib - -VERSION = 2.5 diff --git a/applications/process-manager/erode_stable.h b/applications/process-manager/erode_stable.h new file mode 100644 index 000000000..1d011d70c --- /dev/null +++ b/applications/process-manager/erode_stable.h @@ -0,0 +1,25 @@ +#if defined __cplusplus +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "controller.h" +#endif diff --git a/applications/process-manager/main.cpp b/applications/process-manager/main.cpp index 3da8741ab..ad47fb2a5 100755 --- a/applications/process-manager/main.cpp +++ b/applications/process-manager/main.cpp @@ -41,7 +41,7 @@ int main(int argc, char *argv[]){ app.setOrganizationDomain(OXIDE_SERVICE); app.setApplicationName("tarnish"); app.setApplicationDisplayName("Process Monitor"); - app.setApplicationVersion(OXIDE_INTERFACE_VERSION); + app.setApplicationVersion(APP_VERSION); EventFilter filter; app.installEventFilter(&filter); QQmlApplicationEngine engine; diff --git a/applications/process-manager/process-manager.pro b/applications/process-manager/process-manager.pro new file mode 100755 index 000000000..2d3552b4b --- /dev/null +++ b/applications/process-manager/process-manager.pro @@ -0,0 +1,45 @@ +QT += quick +QT += dbus + +CONFIG += c++11 +CONFIG += qtquickcompiler +CONFIG += precompile_header + +DEFINES += QT_DEPRECATED_WARNINGS +DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 + +SOURCES += main.cpp + +RESOURCES += qml.qrc + +TARGET = erode +include(../../qmake/common.pri) +target.path = /opt/bin +INSTALLS += target + +applications.files = ../../assets/opt/usr/share/applications/codes.eeems.erode.oxide +applications.path = /opt/usr/share/applications/ +INSTALLS += applications + +icons.path = /opt/usr/share/icons/oxide/48x48/apps/ +icons.files += ../../assets/opt/usr/share/icons/oxide/48x48/apps/erode.png +INSTALLS += icons + +splash.path = /opt/usr/share/icons/oxide/702x702/splash/ +splash.files += ../../assets/opt/usr/share/icons/oxide/702x702/splash/erode.png +INSTALLS += splash + + +HEADERS += \ + controller.h \ + taskitem.h \ + tasklist.h + +PRECOMPILED_HEADER = \ + erode_stable.h + +LIBS += -lsystemd + +include(../../qmake/epaper.pri) +include(../../qmake/liboxide.pri) +include(../../qmake/sentry.pri) diff --git a/applications/process-manager/taskitem.h b/applications/process-manager/taskitem.h index 0d54887c1..b3d8cfeb6 100755 --- a/applications/process-manager/taskitem.h +++ b/applications/process-manager/taskitem.h @@ -21,6 +21,7 @@ class TaskItem : public QObject { Q_PROPERTY(bool killable MEMBER _killable READ killable WRITE setKillable NOTIFY killableChanged); Q_PROPERTY(uint cpu MEMBER _cpu READ cpu WRITE setCpu NOTIFY cpuChanged); Q_PROPERTY(QString mem MEMBER _mem READ mem WRITE setMem NOTIFY memChanged); + public: TaskItem(int pid, QObject* parent) : QObject(parent), diff --git a/applications/process-manager/tasklist.h b/applications/process-manager/tasklist.h index 3c2e7de7c..9b2e184c1 100644 --- a/applications/process-manager/tasklist.h +++ b/applications/process-manager/tasklist.h @@ -179,8 +179,7 @@ class TaskList : public QAbstractListModel qCritical() << "Unable to access /proc"; return; } - directory.setFilter( QDir::Dirs | QDir::NoDot | QDir::NoDotDot); - auto processes = directory.entryInfoList(QDir::NoFilter, QDir::SortFlag::Name); + auto processes = directory.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot | QDir::Readable); // Get all pids we care about for(QFileInfo fi : processes){ std::string pid = fi.baseName().toStdString(); diff --git a/applications/screenshot-tool/fret.pro b/applications/screenshot-tool/fret.pro deleted file mode 100644 index 4c9a25dbc..000000000 --- a/applications/screenshot-tool/fret.pro +++ /dev/null @@ -1,46 +0,0 @@ -QT -= gui -QT += dbus - -CONFIG += c++11 console -CONFIG -= app_bundle - -DEFINES += QT_DEPRECATED_WARNINGS -DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 - -SOURCES += \ - main.cpp - -# Default rules for deployment. -target.path = /opt/bin -!isEmpty(target.path): INSTALLS += target - -DBUS_INTERFACES += ../../interfaces/dbusservice.xml -DBUS_INTERFACES += ../../interfaces/systemapi.xml -DBUS_INTERFACES += ../../interfaces/screenapi.xml -DBUS_INTERFACES += ../../interfaces/screenshot.xml -DBUS_INTERFACES += ../../interfaces/notificationapi.xml -DBUS_INTERFACES += ../../interfaces/notification.xml - -INCLUDEPATH += ../../shared - -LIBS += -L$$PWD/../../shared/ -lqsgepaper -INCLUDEPATH += $$PWD/../../shared -DEPENDPATH += $$PWD/../../shared - -exists($$PWD/../../.build/sentry) { - LIBS += -L$$PWD/../../.build/sentry/lib -lsentry -ldl -lcurl -lbreakpad_client - INCLUDEPATH += $$PWD/../../.build/sentry/include - DEPENDPATH += $$PWD/../../.build/sentry/lib - - library.files = ../../.build/sentry/libsentry.so - library.path = /opt/lib - INSTALLS += library -} - -LIBS += -L$$PWD/../../.build/liboxide -lliboxide -INCLUDEPATH += $$PWD/../../shared/liboxide -DEPENDPATH += $$PWD/../../shared/liboxide - -QMAKE_RPATHDIR += /lib /usr/lib /opt/lib /opt/usr/lib - -VERSION = 2.5 diff --git a/applications/screenshot-tool/main.cpp b/applications/screenshot-tool/main.cpp index c0c093500..afc9b9fdb 100644 --- a/applications/screenshot-tool/main.cpp +++ b/applications/screenshot-tool/main.cpp @@ -6,13 +6,6 @@ #include #include -#include "dbusservice_interface.h" -#include "systemapi_interface.h" -#include "screenapi_interface.h" -#include "screenshot_interface.h" -#include "notificationapi_interface.h" -#include "notification_interface.h" - using namespace codes::eeems::oxide1; using namespace Oxide::Sentry; @@ -53,7 +46,7 @@ int main(int argc, char *argv[]){ app.setOrganizationName("Eeems"); app.setOrganizationDomain(OXIDE_SERVICE); app.setApplicationName("fret"); - app.setApplicationVersion(OXIDE_INTERFACE_VERSION); + app.setApplicationVersion(APP_VERSION); auto bus = QDBusConnection::systemBus(); qDebug() << "Waiting for tarnish to start up..."; while(!bus.interface()->registeredServiceNames().value().contains(OXIDE_SERVICE)){ diff --git a/applications/screenshot-tool/screenshot-tool.pro b/applications/screenshot-tool/screenshot-tool.pro new file mode 100644 index 000000000..4342a9b2f --- /dev/null +++ b/applications/screenshot-tool/screenshot-tool.pro @@ -0,0 +1,24 @@ +QT -= gui +QT += dbus + +CONFIG += c++11 console +CONFIG -= app_bundle + +DEFINES += QT_DEPRECATED_WARNINGS +DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 + +SOURCES += \ + main.cpp + +TARGET = fret +include(../../qmake/common.pri) +target.path = /opt/bin +INSTALLS += target + +applications.files = ../../assets/opt/usr/share/applications/codes.eeems.fret.oxide +applications.path = /opt/usr/share/applications/ +INSTALLS += applications + +include(../../qmake/epaper.pri) +include(../../qmake/liboxide.pri) +include(../../qmake/sentry.pri) diff --git a/applications/screenshot-viewer/anxiety.pro b/applications/screenshot-viewer/anxiety.pro deleted file mode 100644 index 1e3fce3f0..000000000 --- a/applications/screenshot-viewer/anxiety.pro +++ /dev/null @@ -1,58 +0,0 @@ -QT += gui -QT += quick -QT += dbus - -CONFIG += c++11 -CONFIG += qml_debug - -DEFINES += QT_DEPRECATED_WARNINGS -DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 - -SOURCES += \ - main.cpp - -# Default rules for deployment. -target.path = /opt/bin -!isEmpty(target.path): INSTALLS += target - -icons.files += \ - ../../assets/etc/draft/icons/image.svg \ - ../../assets/etc/draft/icons/anxiety-splash.png - -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/epframebuffer.h \ - controller.h \ - screenshotlist.h - -RESOURCES += \ - qml.qrc - -LIBS += -L$$PWD/../../shared/ -lqsgepaper -INCLUDEPATH += $$PWD/../../shared -DEPENDPATH += $$PWD/../../shared - -exists($$PWD/../../.build/sentry) { - LIBS += -L$$PWD/../../.build/sentry/lib -lsentry -ldl -lcurl -lbreakpad_client - INCLUDEPATH += $$PWD/../../.build/sentry/include - DEPENDPATH += $$PWD/../../.build/sentry/lib - - library.files = ../../.build/sentry/libsentry.so - library.path = /opt/lib - INSTALLS += library -} - -LIBS += -L$$PWD/../../.build/liboxide -lliboxide -INCLUDEPATH += $$PWD/../../shared/liboxide -DEPENDPATH += $$PWD/../../shared/liboxide - -QMAKE_RPATHDIR += /lib /usr/lib /opt/lib /opt/usr/lib - -VERSION = 2.5 diff --git a/applications/screenshot-viewer/anxiety_stable.h b/applications/screenshot-viewer/anxiety_stable.h new file mode 100644 index 000000000..50019ece3 --- /dev/null +++ b/applications/screenshot-viewer/anxiety_stable.h @@ -0,0 +1,13 @@ +#if defined __cplusplus +#include +#include +#include +#include +#include +#include +#include +#include + +#include "controller.h" +#include "screenshotlist.h" +#endif diff --git a/applications/screenshot-viewer/controller.h b/applications/screenshot-viewer/controller.h index ebae93f2e..01eeafec6 100644 --- a/applications/screenshot-viewer/controller.h +++ b/applications/screenshot-viewer/controller.h @@ -8,10 +8,6 @@ #include "epframebuffer.h" -#include "dbusservice_interface.h" -#include "screenapi_interface.h" -#include "screenshot_interface.h" - #include "screenshotlist.h" using namespace codes::eeems::oxide1; @@ -28,6 +24,7 @@ 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() { diff --git a/applications/screenshot-viewer/main.cpp b/applications/screenshot-viewer/main.cpp index d1e77af14..aedc4c3ef 100644 --- a/applications/screenshot-viewer/main.cpp +++ b/applications/screenshot-viewer/main.cpp @@ -6,7 +6,6 @@ #include #include #include -#include #include "controller.h" @@ -14,8 +13,6 @@ Q_IMPORT_PLUGIN(QsgEpaperPlugin) #endif -#include "dbusservice_interface.h" - using namespace std; using namespace Oxide; using namespace Oxide::Sentry; @@ -47,7 +44,7 @@ int main(int argc, char *argv[]){ app.setOrganizationDomain(OXIDE_SERVICE); app.setApplicationName("anxiety"); app.setApplicationDisplayName("Screenshots"); - app.setApplicationVersion(OXIDE_INTERFACE_VERSION); + app.setApplicationVersion(APP_VERSION); Controller controller(&app); QQmlApplicationEngine engine; QQmlContext* context = engine.rootContext(); diff --git a/applications/screenshot-viewer/screenshot-viewer.pro b/applications/screenshot-viewer/screenshot-viewer.pro new file mode 100644 index 000000000..789474b8a --- /dev/null +++ b/applications/screenshot-viewer/screenshot-viewer.pro @@ -0,0 +1,45 @@ +QT += gui +QT += quick +QT += dbus + +CONFIG += c++11 +CONFIG += qml_debug +CONFIG += qtquickcompiler +CONFIG += precompile_header + +DEFINES += QT_DEPRECATED_WARNINGS +DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 + +SOURCES += \ + main.cpp + +TARGET = anxiety +include(../../qmake/common.pri) +target.path = /opt/bin +INSTALLS += target + +applications.files = ../../assets/opt/usr/share/applications/codes.eeems.anxiety.oxide +applications.path = /opt/usr/share/applications/ +INSTALLS += applications + +icons.files += ../../assets/opt/usr/share/icons/oxide/48x48/apps/image.png +icons.path = /opt/usr/share/icons/oxide/48x48/apps +INSTALLS += icons + +splash.files += ../../assets/opt/usr/share/icons/oxide/702x702/splash/anxiety.png +splash.path = /opt/usr/share/icons/oxide/702x702/splash +INSTALLS += splash + +HEADERS += \ + controller.h \ + screenshotlist.h + +RESOURCES += \ + qml.qrc + +PRECOMPILED_HEADER = \ + anxiety_stable.h + +include(../../qmake/epaper.pri) +include(../../qmake/liboxide.pri) +include(../../qmake/sentry.pri) diff --git a/applications/screenshot-viewer/screenshotlist.h b/applications/screenshot-viewer/screenshotlist.h index 03e896a2f..af9643ccb 100644 --- a/applications/screenshot-viewer/screenshotlist.h +++ b/applications/screenshot-viewer/screenshotlist.h @@ -2,8 +2,7 @@ #define SCREENSHOTLIST_H #include - -#include "screenshot_interface.h" +#include using namespace codes::eeems::oxide1; @@ -11,6 +10,7 @@ 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; @@ -40,6 +40,7 @@ class ScreenshotItem : public QObject { return true; } QString qPath() { return m_screenshot->QDBusAbstractInterface::path(); } + signals: void pathChanged(QString); void nameChanged(QString); @@ -58,6 +59,7 @@ public slots: class ScreenshotList : public QAbstractListModel { Q_OBJECT + public: ScreenshotList() : QAbstractListModel(nullptr) {} @@ -151,8 +153,10 @@ class ScreenshotList : public QAbstractListModel } int length() { return screenshots.length(); } bool empty() { return screenshots.empty(); } + signals: void updated(); + private: QList screenshots; }; diff --git a/applications/settings-manager/json.h b/applications/settings-manager/json.h deleted file mode 100644 index 6e74ca809..000000000 --- a/applications/settings-manager/json.h +++ /dev/null @@ -1,130 +0,0 @@ -#ifndef JSON_H -#define JSON_H -#include -#include -#include -#include -#include -#include - -QVariant sanitizeForJson(QVariant value); -QVariant decodeDBusArgument(const QDBusArgument& arg){ - auto type = arg.currentType(); - if(type == QDBusArgument::BasicType || type == QDBusArgument::VariantType){ - return sanitizeForJson(arg.asVariant()); - } - if(type == QDBusArgument::ArrayType){ - QVariantList list; - arg.beginArray(); - while(!arg.atEnd()){ - list.append(decodeDBusArgument(arg)); - } - arg.endArray(); - return sanitizeForJson(list); - } - if(type == QDBusArgument::MapType){ - qDebug() << "Map Type"; - QMap map; - arg.beginMap(); - while(!arg.atEnd()){ - arg.beginMapEntry(); - auto key = decodeDBusArgument(arg); - auto value = decodeDBusArgument(arg); - arg.endMapEntry(); - map.insert(sanitizeForJson(key), sanitizeForJson(value)); - } - arg.endMap(); - return sanitizeForJson(QVariant::fromValue(map)); - } - qDebug() << "Unable to sanitize QDBusArgument as it is an unknown type"; - return QVariant(); -} -QVariant sanitizeForJson(QVariant value){ - auto userType = value.userType(); - if(userType == QMetaType::type("QDBusObjectPath")){ - return value.value().path(); - } - if(userType == QMetaType::type("QDBusSignature")){ - return value.value().signature(); - } - if(userType == QMetaType::type("QDBusVariant")){ - return value.value().variant(); - } - if(userType == QMetaType::type("QDBusArgument")){ - return decodeDBusArgument(value.value()); - } - if(userType == QMetaType::type("QList")){ - QVariantList list; - for(auto value : value.value>()){ - list.append(sanitizeForJson(value.variant())); - } - return list; - } - if(userType == QMetaType::type("QList")){ - QStringList list; - for(auto value : value.value>()){ - list.append(value.signature()); - } - return list; - } - if(userType == QMetaType::type("QList")){ - QStringList list; - for(auto value : value.value>()){ - list.append(value.path()); - } - return list; - } - if(userType == QMetaType::QByteArray){ - auto byteArray = value.toByteArray(); - QVariantList list; - for(auto byte : byteArray){ - list.append(byte); - } - return list; - } - if(userType == QMetaType::QVariantMap){ - QVariantMap map; - auto input = value.toMap(); - for(auto key : input.keys()){ - map.insert(key, sanitizeForJson(input[key])); - } - return map; - } - if(userType == QMetaType::QVariantList){ - QVariantList list = value.toList(); - QMutableListIterator i(list); - while(i.hasNext()){ - i.setValue(sanitizeForJson(i.next())); - } - return list; - } - return value; -} -QString toJson(QVariant value){ - if(value.isNull()){ - return "null"; - } - auto jsonVariant = QJsonValue::fromVariant(sanitizeForJson(value)); - if(jsonVariant.isNull()){ - return "null"; - } - if(jsonVariant.isUndefined()){ - return "undefined"; - } - QJsonArray jsonArray; - jsonArray.append(jsonVariant); - QJsonDocument doc(jsonArray); - auto json = doc.toJson(QJsonDocument::Compact); - return json.mid(1, json.length() - 2); -} -QVariant fromJson(QByteArray json){ - QJsonParseError error; - QJsonDocument doc = QJsonDocument::fromJson("[" + json + "]", &error); - if(error.error != QJsonParseError::NoError){ - qDebug() << "Unable to read json value" << error.errorString(); - qDebug() << "Value to parse" << json; - } - return doc.array().first().toVariant(); -} - -#endif // JSON_H diff --git a/applications/settings-manager/main.cpp b/applications/settings-manager/main.cpp index c64f50ca4..c77461d45 100644 --- a/applications/settings-manager/main.cpp +++ b/applications/settings-manager/main.cpp @@ -1,29 +1,12 @@ #include -#include -#include #include -#include +#include #include -#include "dbusservice_interface.h" -#include "powerapi_interface.h" -#include "wifiapi_interface.h" -#include "network_interface.h" -#include "bss_interface.h" -#include "appsapi_interface.h" -#include "application_interface.h" -#include "systemapi_interface.h" -#include "screenapi_interface.h" -#include "screenshot_interface.h" -#include "notificationapi_interface.h" -#include "notification_interface.h" - -#include "json.h" -#include "slothandler.h" - using namespace codes::eeems::oxide1; using namespace Oxide::Sentry; +using namespace Oxide::JSON; int qExit(int ret){ QTimer::singleShot(0, [ret](){ @@ -38,7 +21,7 @@ int main(int argc, char *argv[]){ app.setOrganizationName("Eeems"); app.setOrganizationDomain(OXIDE_SERVICE); app.setApplicationName("rot"); - app.setApplicationVersion(OXIDE_INTERFACE_VERSION); + app.setApplicationVersion(APP_VERSION); QCommandLineParser parser; parser.setApplicationDescription("Oxide settings tool"); parser.addHelpOption(); @@ -318,6 +301,7 @@ int main(int argc, char *argv[]){ return qExit(EXIT_FAILURE); } QDBusAbstractInterface* iapi = qobject_cast(api); + QTextStream qStdOut(stdout, QIODevice::WriteOnly); if(action == "get"){ auto value = api->property(args.at(2).toStdString().c_str()); if(iapi != nullptr){ @@ -330,7 +314,7 @@ int main(int argc, char *argv[]){ return qExit(EXIT_FAILURE); } } - QTextStream(stdout, QIODevice::WriteOnly) << toJson(value).toStdString().c_str() << Qt::endl; + qStdOut << toJson(value).toStdString().c_str() << Qt::endl; }else if(action == "set"){ auto property = args.at(2).toStdString(); if(!api->setProperty(property.c_str(), args.at(3).toStdString().c_str())){ @@ -344,29 +328,23 @@ int main(int argc, char *argv[]){ return qExit(EXIT_FAILURE); } }else if(action == "listen"){ - auto metaObject = api->metaObject(); - auto name = QString(args.at(2).toStdString().c_str()); - for(int methodId = 0; methodId < metaObject->methodCount(); methodId++){ - auto method = metaObject->method(methodId); - if(method.methodType() == QMetaMethod::Signal && method.name() == name){ - QByteArray slotName = method.name().prepend("on").append("("); - QStringList parameters; - for(int i = 0, j = method.parameterCount(); i < j; ++i){ - parameters << QMetaType::typeName(method.parameterType(i)); + if(Oxide::DBusConnect( + (QDBusAbstractInterface*)api, + QString(args.at(2).toStdString().c_str()), + [](QVariantList args){ + QTextStream qStdOut(stdout, QIODevice::WriteOnly); + if(args.size() > 1){ + qStdOut << toJson(args).toStdString().c_str() << Qt::endl; + }else if(args.size() == 1 && !args.first().isNull()){ + qStdOut << toJson(args.first()).toStdString().c_str() << Qt::endl; + }else{ + qStdOut << "undefined" << Qt::endl; } - slotName.append(parameters.join(",").toUtf8()).append(")"); - QByteArray theSignal = QMetaObject::normalizedSignature(method.methodSignature().constData()); - QByteArray theSlot = QMetaObject::normalizedSignature(slotName); - if(!QMetaObject::checkConnectArgs(theSignal, theSlot)){ - continue; - } - auto slotHandler = new SlotHandler(OXIDE_SERVICE, parameters, parser.isSet("once"), [=](){ - qApp->exit(EXIT_SUCCESS); - }); - if(slotHandler->connect(api, methodId)){ - return app.exec(); - } - } + }, + [](){ qApp->exit(EXIT_SUCCESS); }, + parser.isSet("once") + )){ + return app.exec(); } qDebug() << "Unable to listen to signal"; if(iapi != nullptr){ @@ -431,7 +409,6 @@ int main(int argc, char *argv[]){ } QDBusMessage reply = iapi->callWithArgumentList(QDBus::Block, method, arguments); auto result = reply.arguments(); - QTextStream qStdOut(stdout, QIODevice::WriteOnly); if(result.size() > 1){ qStdOut << toJson(result).toStdString().c_str() << Qt::endl; }else if(!result.first().isNull()){ diff --git a/applications/settings-manager/rot.pro b/applications/settings-manager/rot.pro deleted file mode 100644 index f3cf50dbe..000000000 --- a/applications/settings-manager/rot.pro +++ /dev/null @@ -1,52 +0,0 @@ -QT -= gui -QT += dbus - -CONFIG += c++11 console -CONFIG -= app_bundle - -DEFINES += QT_DEPRECATED_WARNINGS -#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 - -SOURCES += \ - main.cpp - -INCLUDEPATH += ../../shared - -DBUS_INTERFACES += ../../interfaces/dbusservice.xml -DBUS_INTERFACES += ../../interfaces/powerapi.xml -DBUS_INTERFACES += ../../interfaces/wifiapi.xml -DBUS_INTERFACES += ../../interfaces/network.xml -DBUS_INTERFACES += ../../interfaces/bss.xml -DBUS_INTERFACES += ../../interfaces/appsapi.xml -DBUS_INTERFACES += ../../interfaces/application.xml -DBUS_INTERFACES += ../../interfaces/systemapi.xml -DBUS_INTERFACES += ../../interfaces/screenapi.xml -DBUS_INTERFACES += ../../interfaces/screenshot.xml -DBUS_INTERFACES += ../../interfaces/notificationapi.xml -DBUS_INTERFACES += ../../interfaces/notification.xml - -# Default rules for deployment. -target.path = /opt/bin -!isEmpty(target.path): INSTALLS += target - -exists($$PWD/../../.build/sentry) { - LIBS += -L$$PWD/../../.build/sentry/lib -lsentry -ldl -lcurl -lbreakpad_client - INCLUDEPATH += $$PWD/../../.build/sentry/include - DEPENDPATH += $$PWD/../../.build/sentry/lib - - library.files = ../../.build/sentry/libsentry.so - library.path = /opt/lib - INSTALLS += library -} - -LIBS += -L$$PWD/../../.build/liboxide -lliboxide -INCLUDEPATH += $$PWD/../../shared/liboxide -DEPENDPATH += $$PWD/../../shared/liboxide - -QMAKE_RPATHDIR += /lib /usr/lib /opt/lib /opt/usr/lib - -HEADERS += \ - json.h \ - slothandler.h - -VERSION = 2.5 diff --git a/applications/settings-manager/settings-manager.pro b/applications/settings-manager/settings-manager.pro new file mode 100644 index 000000000..e7046ca41 --- /dev/null +++ b/applications/settings-manager/settings-manager.pro @@ -0,0 +1,21 @@ +QT -= gui +QT += dbus + +CONFIG += c++11 console +CONFIG -= app_bundle + +DEFINES += QT_DEPRECATED_WARNINGS +#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 + +SOURCES += \ + main.cpp + +HEADERS += + +TARGET = rot +include(../../qmake/common.pri) +target.path = /opt/bin +INSTALLS += target + +include(../../qmake/liboxide.pri) +include(../../qmake/sentry.pri) diff --git a/applications/settings-manager/slothandler.h b/applications/settings-manager/slothandler.h deleted file mode 100644 index 9dafff1ae..000000000 --- a/applications/settings-manager/slothandler.h +++ /dev/null @@ -1,80 +0,0 @@ -#ifndef SLOTHANDLER_H -#define SLOTHANDLER_H -#include -#include -#include -#include -#include - -#include "json.h" - -class SlotHandler : public QObject { -public: - SlotHandler(const QString& serviceName, QStringList parameters, bool once, std::function callback) - : QObject(0), - serviceName(serviceName), - parameters(parameters), - once(once), - m_disconnected(false), - qStdOut(stdout, QIODevice::WriteOnly), - callback(callback) - { - watcher = new QDBusServiceWatcher(serviceName, QDBusConnection::systemBus(), QDBusServiceWatcher::WatchForUnregistration, this); - QObject::connect(watcher, &QDBusServiceWatcher::serviceUnregistered, this, [=](const QString& name){ - Q_UNUSED(name); - if(!m_disconnected){ - qDebug() << QDBusError(QDBusError::ServiceUnknown, "The name " + serviceName + " is no longer registered"); - m_disconnected = true; - callback(); - } - }); - } - ~SlotHandler() {}; - int qt_metacall(QMetaObject::Call call, int id, void **arguments){ - id = QObject::qt_metacall(call, id, arguments); - if (id < 0 || call != QMetaObject::InvokeMetaMethod){ - return id; - } - Q_ASSERT(id < 1); - if(!m_disconnected){ - handleSlot(sender(), arguments); - } - return -1; - } - bool connect(QObject* sender, int methodId){ - return QMetaObject::connect(sender, methodId, this, this->metaObject()->methodCount()); - } - -private: - QString serviceName; - QStringList parameters; - bool once; - bool m_disconnected; - QDBusServiceWatcher* watcher; - QTextStream qStdOut; - std::function callback; - - void handleSlot(QObject* api, void** arguments){ - Q_UNUSED(api); - QVariantList args; - for(int i = 0; i < parameters.length(); i++){ - auto typeId = QMetaType::type(parameters[i].toStdString().c_str()); - QMetaType type(typeId); - void* ptr = reinterpret_cast(arguments[i + 1]); - args << QVariant(typeId, ptr); - } - if(args.size() > 1){ - qStdOut << toJson(args).toStdString().c_str() << Qt::endl; - }else if(args.size() == 1 && !args.first().isNull()){ - qStdOut << toJson(args.first()).toStdString().c_str() << Qt::endl; - }else{ - qStdOut << "undefined" << Qt::endl; - } - if(once){ - m_disconnected = true; - callback(); - } - } -}; - -#endif // SLOTHANDLER_H diff --git a/applications/system-service/apibase.h b/applications/system-service/apibase.h index 1f74e0e51..b29b8a385 100644 --- a/applications/system-service/apibase.h +++ b/applications/system-service/apibase.h @@ -7,9 +7,9 @@ #include #include +#include #include -#include "../../shared/liboxide/liboxide.h" class APIBase : public QObject, protected QDBusContext { Q_OBJECT diff --git a/applications/system-service/application.cpp b/applications/system-service/application.cpp index d34d49199..c80707f1f 100644 --- a/applications/system-service/application.cpp +++ b/applications/system-service/application.cpp @@ -10,6 +10,8 @@ #include "buttonhandler.h" #include "digitizerhandler.h" +using namespace Oxide::Applications; + const event_device touchScreen(deviceSettings.getTouchDevicePath(), O_WRONLY); void Application::launch(){ @@ -60,9 +62,41 @@ void Application::launchNoSecurityCheck(){ } m_process->setUser(user()); m_process->setGroup(group()); + if(p_stdout == nullptr){ + p_stdout_fd = sd_journal_stream_fd(name().toStdString().c_str(), LOG_INFO, 1); + if (p_stdout_fd < 0) { + errno = -p_stdout_fd; + qDebug() << "Failed to create stdout fd:" << -p_stdout_fd; + }else{ + FILE* log = fdopen(p_stdout_fd, "w"); + if(!log){ + qDebug() << "Failed to create stdout FILE:" << errno; + close(p_stdout_fd); + }else{ + p_stdout = new QTextStream(log); + qDebug() << "Opened stdout for " << name(); + } + } + } + if(p_stderr == nullptr){ + p_stderr_fd = sd_journal_stream_fd(name().toStdString().c_str(), LOG_ERR, 1); + if (p_stderr_fd < 0) { + errno = -p_stderr_fd; + qDebug() << "Failed to create sterr fd:" << -p_stderr_fd; + }else{ + FILE* log = fdopen(p_stderr_fd, "w"); + if(!log){ + qDebug() << "Failed to create stderr FILE:" << errno; + close(p_stderr_fd); + }else{ + p_stderr = new QTextStream(log); + qDebug() << "Opened stderr for " << name(); + } + } + } m_process->start(); m_process->waitForStarted(); - if(type() == AppsAPI::Background){ + if(type() == Background){ startSpan("background", "Application is in the background"); }else{ startSpan("foreground", "Application is in the foreground"); @@ -80,7 +114,7 @@ void Application::pauseNoSecurityCheck(bool startIfNone){ if( !m_process->processId() || stateNoSecurityCheck() == Paused - || type() == AppsAPI::Background + || type() == Background ){ return; } @@ -109,7 +143,7 @@ void Application::interruptApplication(){ if( !m_process->processId() || stateNoSecurityCheck() == Paused - || type() == AppsAPI::Background + || type() == Background ){ return; } @@ -128,11 +162,11 @@ void Application::interruptApplication(){ } Oxide::Sentry::sentry_span(t, "background", "Background application", [this](){ switch(type()){ - case AppsAPI::Background: + case Background: // Already in the background. How did we get here? startSpan("background", "Application is in the background"); return; - case AppsAPI::Backgroundable: + case Backgroundable: qDebug() << "Waiting for SIGUSR2 ack"; appsAPI->connectSignals(this, 2); kill(-m_process->processId(), SIGUSR2); @@ -152,7 +186,7 @@ void Application::interruptApplication(){ startSpan("background", "Application is in the background"); } break; - case AppsAPI::Foreground: + case Foreground: default: kill(-m_process->processId(), SIGSTOP); waitForPause(); @@ -185,7 +219,7 @@ void Application::resumeNoSecurityCheck(){ if( !m_process->processId() || stateNoSecurityCheck() == InForeground - || (type() == AppsAPI::Background && stateNoSecurityCheck() == InBackground) + || (type() == Background && stateNoSecurityCheck() == InBackground) ){ qDebug() << "Can't Resume" << path() << "Already running!"; return; @@ -201,7 +235,7 @@ void Application::resumeNoSecurityCheck(){ appsAPI->recordPreviousApplication(); qDebug() << "Resuming " << path(); appsAPI->pauseAll(); - if(!flags().contains("nosavescreen") && (type() != AppsAPI::Backgroundable || stateNoSecurityCheck() == Paused)){ + if(!flags().contains("nosavescreen") && (type() != Backgroundable || stateNoSecurityCheck() == Paused)){ recallScreen(); } uninterruptApplication(); @@ -215,7 +249,7 @@ void Application::uninterruptApplication(){ if( !m_process->processId() || stateNoSecurityCheck() == InForeground - || (type() == AppsAPI::Background && stateNoSecurityCheck() == InBackground) + || (type() == Background && stateNoSecurityCheck() == InBackground) ){ return; } @@ -234,8 +268,8 @@ void Application::uninterruptApplication(){ } Oxide::Sentry::sentry_span(t, "foreground", "Foreground application", [this](){ switch(type()){ - case AppsAPI::Background: - case AppsAPI::Backgroundable: + case Background: + case Backgroundable: if(stateNoSecurityCheck() == Paused){ touchHandler->clear_buffer(); kill(-m_process->processId(), SIGCONT); @@ -254,7 +288,7 @@ void Application::uninterruptApplication(){ m_backgrounded = false; startSpan("background", "Application is in the background"); break; - case AppsAPI::Foreground: + case Foreground: default: touchHandler->clear_buffer(); kill(-m_process->processId(), SIGCONT); @@ -321,6 +355,14 @@ void Application::stopNoSecurityCheck(){ } }); appsAPI->removeFromPreviousApplications(name()); + if(p_stdout != nullptr){ + delete p_stdout; + p_stdout = nullptr; + } + if(p_stderr != nullptr){ + delete p_stderr; + p_stderr = nullptr; + } }); } void Application::signal(int signal){ @@ -351,7 +393,7 @@ int Application::stateNoSecurityCheck(){ return Paused; } } - if(type() == AppsAPI::Background || (type() == AppsAPI::Backgroundable && m_backgrounded)){ + if(type() == Background || (type() == Backgroundable && m_backgrounded)){ return InBackground; } return InForeground; @@ -364,7 +406,7 @@ int Application::stateNoSecurityCheck(){ void Application::setConfig(const QVariantMap& config){ auto oldBin = bin(); m_config = config; - if(type() == AppsAPI::Foreground){ + if(type() == Foreground){ setAutoStart(false); } if(oldBin == bin()){ @@ -384,6 +426,9 @@ void Application::finished(int exitCode){ appsAPI->resumeIfNone(); emit appsAPI->applicationExited(qPath(), exitCode); umountAll(); + if(transient()){ + unregister(); + } } void Application::errorOccurred(QProcess::ProcessError error){ switch(error){ @@ -391,6 +436,9 @@ void Application::errorOccurred(QProcess::ProcessError error){ qDebug() << "Application" << name() << "failed to start."; emit exited(-1); emit appsAPI->applicationExited(qPath(), -1); + if(transient()){ + unregister(); + } break; case QProcess::Crashed: qDebug() << "Application" << name() << "crashed."; @@ -477,13 +525,13 @@ void Application::showSplashScreen(){ void Application::powerStateDataRecieved(FifoHandler* handler, const QString& data){ Q_UNUSED(handler); if(!permissions().contains("power")){ - qWarning() << "Denied powerState request"; + O_WARNING("Denied powerState request"); return; } if((QStringList() << "mem" << "freeze" << "standby").contains(data)){ systemAPI->suspend(); }else{ - qWarning() << "Unknown power state call: " << data; + O_WARNING("Unknown power state call: " << data); } } void Application::startSpan(std::string operation, std::string description){ diff --git a/applications/system-service/application.h b/applications/system-service/application.h index 6788e19a6..243645166 100644 --- a/applications/system-service/application.h +++ b/applications/system-service/application.h @@ -23,8 +23,6 @@ #include #include #include -#include -#include #include #include #include @@ -33,23 +31,26 @@ #include #include -#include "../../shared/liboxide/liboxide.h" #include "mxcfb.h" #include "screenapi.h" #include "fifohandler.h" #include "buttonhandler.h" +// Must be included so that generate_xml.sh will work +#include "../../shared/liboxide/meta.h" + #define DEFAULT_PATH "/opt/bin:/opt/sbin:/opt/usr/bin:/usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/usr/sbin:/sbin" class SandBoxProcess : public QProcess{ Q_OBJECT + public: SandBoxProcess(QObject* parent = nullptr) : QProcess(parent), m_gid(0), m_uid(0), m_chroot(""), m_mask(0) {} bool setUser(const QString& name){ try{ - m_uid = getUID(name); + m_uid = Oxide::getUID(name); return true; } catch(const std::runtime_error&){ @@ -58,7 +59,7 @@ class SandBoxProcess : public QProcess{ } bool setGroup(const QString& name){ try{ - m_gid = getGID(name); + m_gid = Oxide::getGID(name); return true; } catch(const std::runtime_error&){ @@ -80,6 +81,7 @@ class SandBoxProcess : public QProcess{ void setMask(mode_t mask){ m_mask = mask; } + protected: void setupChildProcess() override { // Drop all privileges in the child process @@ -95,26 +97,12 @@ class SandBoxProcess : public QProcess{ setsid(); prctl(PR_SET_PDEATHSIG, SIGTERM); } + private: gid_t m_gid; uid_t m_uid; QString m_chroot; mode_t m_mask; - - uid_t getUID(const QString& name){ - auto user = getpwnam(name.toStdString().c_str()); - if(user == NULL){ - throw std::runtime_error("Invalid user name: " + name.toStdString()); - } - return user->pw_uid; - } - gid_t getGID(const QString& name){ - auto group = getgrnam(name.toStdString().c_str()); - if(group == NULL){ - throw std::runtime_error("Invalid group name: " + name.toStdString()); - } - return group->gr_gid; - } }; class Application : public QObject{ @@ -135,6 +123,7 @@ class Application : public QObject{ Q_PROPERTY(int state READ state) Q_PROPERTY(bool systemApp READ systemApp) Q_PROPERTY(bool hidden READ hidden) + Q_PROPERTY(bool transient READ transient) Q_PROPERTY(QString icon READ icon WRITE setIcon NOTIFY iconChanged) Q_PROPERTY(QString splash READ splash WRITE setSplash NOTIFY splashChanged) Q_PROPERTY(QVariantMap environment READ environment NOTIFY environmentChanged) @@ -144,6 +133,7 @@ class Application : public QObject{ Q_PROPERTY(QString group READ group) Q_PROPERTY(QStringList directories READ directories WRITE setDirectories NOTIFY directoriesChanged) Q_PROPERTY(QByteArray screenCapture READ screenCapture) + public: Application(QDBusObjectPath path, QObject* parent) : Application(path.path(), parent) {} Application(QString path, QObject* parent) : QObject(parent), m_path(path), m_backgrounded(false), fifos() { @@ -159,11 +149,28 @@ class Application : public QObject{ connect(m_process, &SandBoxProcess::errorOccurred, this, &Application::errorOccurred); } ~Application() { + stopNoSecurityCheck(); unregisterPath(); if(m_screenCapture != nullptr){ delete m_screenCapture; } umountAll(); + if(p_stdout != nullptr){ + p_stdout->flush(); + delete p_stdout; + } + if(p_stdout_fd > 0){ + close(p_stdout_fd); + p_stdout_fd = -1; + } + if(p_stderr != nullptr){ + p_stderr->flush(); + delete p_stderr; + } + if(p_stderr_fd > 0){ + close(p_stderr_fd); + p_stderr_fd = -1; + } } QString path() { return m_path; } @@ -257,6 +264,7 @@ class Application : public QObject{ } } bool systemApp() { return flags().contains("system"); } + bool transient() { return flags().contains("transient"); } bool hidden() { return flags().contains("hidden"); } int type() { return (int)value("type", 0).toInt(); } int state(){ @@ -266,15 +274,35 @@ class Application : public QObject{ return stateNoSecurityCheck(); } int stateNoSecurityCheck(); - QString icon() { return value("icon", "").toString(); } + QString icon(){ + auto _icon = value("icon", "").toString(); + if(_icon.isEmpty() || !_icon.contains("-") || QFile::exists(_icon)){ + return _icon; + } + auto path = Oxide::Applications::iconPath(_icon); + if(path.isEmpty()){ + return _icon; + } + return path; + } void setIcon(QString icon){ if(!hasPermission("permissions")){ return; } setValue("icon", icon); - emit iconChanged(icon); + emit iconChanged(this->icon()); + } + QString splash(){ + auto _splash = value("splash", "").toString(); + if(_splash.isEmpty() || !_splash.contains("-") || QFile::exists(_splash)){ + return _splash; + } + auto path = Oxide::Applications::iconPath(_splash); + if(path.isEmpty()){ + return _splash; + } + return path; } - QString splash() { return value("splash", "").toString(); } void setSplash(QString splash){ if(!hasPermission("permissions")){ return; @@ -342,7 +370,7 @@ class Application : public QObject{ QBuffer buffer(&bytes); buffer.open(QIODevice::WriteOnly); if(!EPFrameBuffer::framebuffer()->save(&buffer, "JPG", 100)){ - qWarning() << "Failed to save buffer"; + O_WARNING("Failed to save buffer"); } }); qDebug() << "Compressing data..."; @@ -396,6 +424,7 @@ class Application : public QObject{ void uninterruptApplication(); void waitForPause(); void waitForResume(); + signals: void launched(); void paused(); @@ -427,20 +456,30 @@ private slots: void started(); void finished(int exitCode); void readyReadStandardError(){ - const char* prefix = ("[" + name() + " " + QString::number(m_process->processId()) + "]").toUtf8(); QString error = m_process->readAllStandardError(); - for(QString line : error.split(QRegularExpression("[\r\n]"), Qt::SkipEmptyParts)){ - if(!line.isEmpty()){ - sd_journal_print(LOG_ERR, "%s %s", prefix, (const char*)line.toUtf8()); + if(p_stderr != nullptr){ + *p_stderr << error.toStdString().c_str(); + p_stderr->flush(); + }else{ + const char* prefix = ("[" + name() + " " + QString::number(m_process->processId()) + "]").toUtf8(); + for(QString line : error.split(QRegularExpression("[\r\n]"), Qt::SkipEmptyParts)){ + if(!line.isEmpty()){ + sd_journal_print(LOG_ERR, "%s %s", prefix, (const char*)line.toUtf8()); + } } } } void readyReadStandardOutput(){ - const char* prefix = ("[" + name() + " " + QString::number(m_process->processId()) + "]").toUtf8(); QString output = m_process->readAllStandardOutput(); - for(QString line : output.split(QRegularExpression("[\r\n]"), Qt::SkipEmptyParts)){ - if(!line.isEmpty()){ - sd_journal_print(LOG_INFO, "%s %s", prefix, (const char*)line.toUtf8()); + if(p_stdout != nullptr){ + *p_stdout << output.toStdString().c_str(); + p_stdout->flush(); + }else{ + const char* prefix = ("[" + name() + " " + QString::number(m_process->processId()) + "]").toUtf8(); + for(QString line : output.split(QRegularExpression("[\r\n]"), Qt::SkipEmptyParts)){ + if(!line.isEmpty()){ + sd_journal_print(LOG_INFO, "%s %s", prefix, (const char*)line.toUtf8()); + } } } } @@ -473,6 +512,7 @@ private slots: } void errorOccurred(QProcess::ProcessError error); void powerStateDataRecieved(FifoHandler* handler, const QString& data); + private: QVariantMap m_config; QString m_path; @@ -483,6 +523,10 @@ private slots: QMap fifos; Oxide::Sentry::Transaction* transaction = nullptr; Oxide::Sentry::Span* span = nullptr; + int p_stdout_fd = -1; + QTextStream* p_stdout = nullptr; + int p_stderr_fd = -1; + QTextStream* p_stderr = nullptr; bool hasPermission(QString permission, const char* sender = __builtin_FUNCTION()); void delayUpTo(int milliseconds){ @@ -531,14 +575,14 @@ private slots: auto csource = source.toStdString(); qDebug() << "mount" << source << target; if(mount(csource.c_str(), ctarget.c_str(), NULL, MS_BIND, NULL)){ - qWarning() << "Failed to create bindmount: " << ::strerror(errno); + O_WARNING("Failed to create bindmount: " << ::strerror(errno)); return; } if(!readOnly){ return; } if(mount(csource.c_str(), ctarget.c_str(), NULL, MS_REMOUNT | MS_BIND | MS_RDONLY, NULL)){ - qWarning() << "Failed to remount bindmount read only: " << ::strerror(errno); + O_WARNING("Failed to remount bindmount read only: " << ::strerror(errno)); } qDebug() << "mount ro" << source << target; } @@ -547,7 +591,7 @@ private slots: umount(path); qDebug() << "sysfs" << path; if(mount("none", path.toStdString().c_str(), "sysfs", 0, "")){ - qWarning() << "Failed to mount sysfs: " << ::strerror(errno); + O_WARNING("Failed to mount sysfs: " << ::strerror(errno)); } } void ramdisk(const QString& path){ @@ -555,7 +599,7 @@ private slots: umount(path); qDebug() << "ramdisk" << path; if(mount("tmpfs", path.toStdString().c_str(), "tmpfs", 0, "size=249m,mode=755")){ - qWarning() << "Failed to create ramdisk: " << ::strerror(errno); + O_WARNING("Failed to create ramdisk: " << ::strerror(errno)); } } void umount(const QString& path){ @@ -563,8 +607,8 @@ private slots: return; } auto cpath = path.toStdString(); - ::umount(cpath.c_str()); - if(isMounted(path)){ + auto ret = ::umount2(cpath.c_str(), MNT_DETACH); + if((ret && ret != EINVAL && ret != ENOENT) || isMounted(path)){ qDebug() << "umount failed" << path; return; } @@ -576,17 +620,17 @@ private slots: } FifoHandler* mkfifo(const QString& name, const QString& target){ if(isMounted(target)){ - qWarning() << target << "Already mounted"; + O_WARNING(target << "Already mounted"); return fifos.contains(name) ? fifos[name] : nullptr; } auto source = resourcePath() + "/" + name; if(!QFile::exists(source)){ if(::mkfifo(source.toStdString().c_str(), 0644)){ - qWarning() << "Failed to create " << name << " fifo: " << ::strerror(errno); + O_WARNING("Failed to create " << name << " fifo: " << ::strerror(errno)); } } if(!QFile::exists(source)){ - qWarning() << "No fifo for " << name; + O_WARNING("No fifo for " << name); return fifos.contains(name) ? fifos[name] : nullptr; } bind(source, target); @@ -612,7 +656,7 @@ private slots: } qDebug() << "symlink" << source << target; if(::symlink(target.toStdString().c_str(), source.toStdString().c_str())){ - qWarning() << "Failed to create symlink: " << ::strerror(errno); + O_WARNING("Failed to create symlink: " << ::strerror(errno)); return; } } @@ -733,7 +777,6 @@ private slots: if(mount.startsWith("/")){ activeMounts.append(mount); } - } mounts.close(); activeMounts.sort(Qt::CaseSensitive); diff --git a/applications/system-service/appsapi.h b/applications/system-service/appsapi.h index a04bd09de..80c8b3b93 100644 --- a/applications/system-service/appsapi.h +++ b/applications/system-service/appsapi.h @@ -22,6 +22,7 @@ #define appsAPI AppsAPI::singleton() using namespace Oxide; +using namespace Oxide::Applications; class AppsAPI : public APIBase { Q_OBJECT @@ -36,6 +37,7 @@ class AppsAPI : public APIBase { Q_PROPERTY(QDBusObjectPath currentApplication READ currentApplication) Q_PROPERTY(QVariantMap runningApplications READ runningApplications) Q_PROPERTY(QVariantMap pausedApplications READ pausedApplications) + public: static AppsAPI* singleton(AppsAPI* self = nullptr){ static AppsAPI* instance; @@ -102,9 +104,6 @@ class AppsAPI : public APIBase { void startup(); int state() { return 0; } // Ignore this, it's a kludge to get the xml to generate - enum ApplicationType { Foreground, Background, Backgroundable}; - Q_ENUM(ApplicationType) - void setEnabled(bool enabled){ qDebug() << "Apps API" << enabled; for(auto app : applications){ @@ -125,8 +124,8 @@ class AppsAPI : public APIBase { QDBusObjectPath registerApplicationNoSecurityCheck(QVariantMap properties){ QString name = properties.value("name", "").toString(); QString bin = properties.value("bin", "").toString(); - int type = properties.value("type", Foreground).toInt(); - if(type < Foreground || type > Backgroundable){ + int type = properties.value("type", ApplicationType::Foreground).toInt(); + if(type < ApplicationType::Foreground || type > ApplicationType::Backgroundable){ qDebug() << "Invalid configuration: Invalid type" << type; return QDBusObjectPath("/"); } @@ -415,7 +414,7 @@ class AppsAPI : public APIBase { void forceRecordPreviousApplication(){ auto currentApplication = getApplication(this->currentApplicationNoSecurityCheck()); if(currentApplication == nullptr){ - qWarning() << "Unable to find current application"; + O_WARNING("Unable to find current application"); return; } auto name = currentApplication->name(); @@ -426,7 +425,7 @@ class AppsAPI : public APIBase { void recordPreviousApplication(){ auto currentApplication = getApplication(this->currentApplicationNoSecurityCheck()); if(currentApplication == nullptr){ - qWarning() << "Unable to find current application"; + O_WARNING("Unable to find current application"); return; } if(currentApplication->qPath() == lockscreenApplication()){ @@ -612,7 +611,7 @@ public slots: settings.endArray(); for(auto name : applications.keys()){ auto app = applications[name]; - if(!names.contains(name) && !app->systemApp()){ + if(!names.contains(name) && !app->systemApp() && !app->transient()){ app->unregisterNoSecurityCheck(); } } @@ -670,16 +669,11 @@ public slots: } settings.endArray(); // Load system applications from disk - QDir dir("/opt/usr/share/applications/"); + QDir dir(OXIDE_APPLICATION_REGISTRATIONS_DIRECTORY); dir.setNameFilters(QStringList() << "*.oxide"); QMap apps; for(auto entry : dir.entryInfoList()){ - QFile file(entry.filePath()); - if(!file.open(QIODevice::ReadOnly)){ - continue; - } - auto data = file.readAll(); - auto app = QJsonDocument::fromJson(data).object(); + auto app = getRegistration(entry.filePath()); if(app.isEmpty()){ qDebug() << "Invalid file " << entry.filePath(); continue; @@ -699,15 +693,6 @@ public slots: // Register/Update any system application. for(auto app : apps){ auto name = app["name"].toString(); - int type = Foreground; - QString typeString = app.contains("type") ? app["type"].toString().toLower() : ""; - if(typeString == "background"){ - type = Background; - }else if(typeString == "backgroundable"){ - type = Backgroundable; - }else if(!typeString.isEmpty() && typeString != "foreground"){ - qDebug() << "Invalid type string:" << typeString; - } auto bin = app["bin"].toString(); if(bin.isEmpty() || !QFile::exists(bin)){ qDebug() << name << "Can't find application binary:" << bin; @@ -716,76 +701,13 @@ public slots: #endif continue; } - auto flags = QStringList() << "system"; - if(app.contains("flags")){ - for(auto flag : app["flags"].toArray()){ - auto value = flag.toString(); - if(!value.isEmpty() && value != "system"){ - flags << value; - } - } - } - QVariantMap properties { - {"name", name}, - {"bin", bin}, - {"type", type}, - {"flags", flags}, - }; - if(app.contains("displayName")){ - properties.insert("displayName", app["displayName"].toString()); - } - if(app.contains("description")){ - properties.insert("description", app["description"].toString()); - } - if(app.contains("icon")){ - properties.insert("icon", app["icon"].toString()); - } - if(app.contains("user")){ - properties.insert("user", app["user"].toString()); - } - if(app.contains("group")){ - properties.insert("group", app["group"].toString()); - } - if(app.contains("workingDirectory")){ - properties.insert("workingDirectory", app["workingDirectory"].toString()); - } - if(app.contains("directories")){ - QStringList directories; - for(auto directory : app["directories"].toArray()){ - directories.append(directory.toString()); - } - properties.insert("directories", directories); - } - if(app.contains("permissions")){ - QStringList permissions; - for(auto permission : app["permissions"].toArray()){ - permissions.append(permission.toString()); - } - properties.insert("permissions", permissions); - } - if(app.contains("events")){ - auto events = app["events"].toObject(); - for(auto event : events.keys()){ - if(event == "stop"){ - properties.insert("onStop", events[event].toString()); - }else if(event == "pause"){ - properties.insert("onPause", events[event].toString()); - }else if(event == "resume"){ - properties.insert("onResume", events[event].toString()); - } - } - } - if(app.contains("environment")){ - QVariantMap envMap; - auto environment = app["environment"].toObject(); - for(auto key : environment.keys()){ - envMap.insert(key, environment[key].toString()); - } - properties.insert("environment", envMap); - } - if(app.contains("splash")){ - properties.insert("splash", app["splash"].toString()); + if(!app.contains("flags") || !app["flags"].isArray()){ + app["flags"] = QJsonArray(); } + auto flags = app["flags"].toArray(); + flags.prepend("system"); + app["flags"] = flags; + auto properties = registrationToMap(app); if(applications.contains(name)){ #ifdef DEBUG qDebug() << "Updating " << name; diff --git a/applications/system-service/bss.h b/applications/system-service/bss.h index 072c12aee..9888c082e 100644 --- a/applications/system-service/bss.h +++ b/applications/system-service/bss.h @@ -4,10 +4,14 @@ #include #include -#include "../../shared/liboxide/liboxide.h" +#include + #include "supplicant.h" #include "network.h" +// Must be included so that generate_xml.sh will work +#include "../../shared/liboxide/meta.h" + class BSS : public QObject{ Q_OBJECT Q_CLASSINFO("Version", OXIDE_INTERFACE_VERSION) @@ -19,6 +23,7 @@ class BSS : public QObject{ Q_PROPERTY(ushort signal READ signal) Q_PROPERTY(QDBusObjectPath network READ network) Q_PROPERTY(QStringList key_mgmt READ key_mgmt) + public: BSS(QString path, QString bssid, QString ssid, QObject* parent); BSS(QString path, IBSS* bss, QObject* parent) : BSS(path, bss->bSSID(), bss->sSID(), parent) {} diff --git a/applications/system-service/buttonhandler.cpp b/applications/system-service/buttonhandler.cpp index ee58e4d50..da174b856 100644 --- a/applications/system-service/buttonhandler.cpp +++ b/applications/system-service/buttonhandler.cpp @@ -1,6 +1,8 @@ #include "buttonhandler.h" + +#include + #include "dbusservice.h" -#include "liboxide.h" void button_exit_handler(){ // Release lock diff --git a/applications/system-service/dbusservice.h b/applications/system-service/dbusservice.h index 4c711f290..4ae7a5254 100644 --- a/applications/system-service/dbusservice.h +++ b/applications/system-service/dbusservice.h @@ -10,8 +10,8 @@ #include #include #include +#include -#include "../../shared/liboxide/liboxide.h" #include "powerapi.h" #include "wifiapi.h" #include "appsapi.h" @@ -21,6 +21,9 @@ #include "buttonhandler.h" #include "digitizerhandler.h" +// Must be included so that generate_xml.sh will work +#include "../../shared/liboxide/meta.h" + #define dbusService DBusService::singleton() using namespace std; @@ -36,6 +39,7 @@ class DBusService : public APIBase { Q_OBJECT Q_CLASSINFO("D-Bus Interface", OXIDE_GENERAL_INTERFACE) Q_PROPERTY(int tarnishPid READ tarnishPid) + public: static DBusService* singleton(){ static DBusService* instance; diff --git a/applications/system-service/digitizerhandler.h b/applications/system-service/digitizerhandler.h index 6377e9664..be4c93f0e 100644 --- a/applications/system-service/digitizerhandler.h +++ b/applications/system-service/digitizerhandler.h @@ -21,6 +21,7 @@ using namespace std; class DigitizerHandler : public QThread { Q_OBJECT + public: static DigitizerHandler* singleton_touchScreen(){ static DigitizerHandler* instance; diff --git a/applications/system-service/fifohandler.h b/applications/system-service/fifohandler.h index 176b08ca6..799856aed 100644 --- a/applications/system-service/fifohandler.h +++ b/applications/system-service/fifohandler.h @@ -10,6 +10,7 @@ class FifoHandler : public QObject { Q_OBJECT + public: FifoHandler(QString name, QString path, QObject* host) : QObject(), @@ -25,7 +26,7 @@ class FifoHandler : public QObject { emit started(); in.open(this->path.toStdString().c_str(), std::ifstream::in); if(!in.good()){ - qWarning() << "Unable to open fifi (in)" << ::strerror(errno); + O_WARNING("Unable to open fifi (in)" << ::strerror(errno)); } timer.start(10); }); @@ -37,7 +38,7 @@ class FifoHandler : public QObject { QThread::create([this]{ out.open(this->path.toStdString().c_str(), std::ifstream::out); if(!out.good()){ - qWarning() << "Unable to open fifi (out)" << ::strerror(errno); + O_WARNING("Unable to open fifi (out)" << ::strerror(errno)); } })->start(); moveToThread(&_thread); @@ -59,10 +60,12 @@ class FifoHandler : public QObject { } } const QString& name() { return _name; } + signals: void started(); void finished(); void dataRecieved(FifoHandler* handler, const QString& data); + protected: void run() { if(!in.is_open()){ @@ -81,6 +84,7 @@ class FifoHandler : public QObject { } thread()->yieldCurrentThread(); } + private: QObject* host; QThread _thread; @@ -90,7 +94,6 @@ class FifoHandler : public QObject { std::ifstream in; std::ofstream out; bool getline_async(std::istream& is, std::string& str, char delim = '\n') { - static std::string lineSoFar; char inChar; int charsRead = 0; diff --git a/applications/system-service/generate_xml.sh b/applications/system-service/generate_xml.sh index be7e63609..deb516166 100644 --- a/applications/system-service/generate_xml.sh +++ b/applications/system-service/generate_xml.sh @@ -1,12 +1,17 @@ #!/bin/sh cd "$(dirname "$0")" mkdir -p ../../interfaces -qdbuscpp2xml -A dbusservice.h -o ../../interfaces/dbusservice.xml -qdbuscpp2xml -A network.h -o ../../interfaces/network.xml -qdbuscpp2xml -A bss.h -o ../../interfaces/bss.xml -qdbuscpp2xml -A application.h -o ../../interfaces/application.xml -qdbuscpp2xml -A screenshot.h -o ../../interfaces/screenshot.xml -qdbuscpp2xml -A notification.h -o ../../interfaces/notification.xml +p(){ + echo "qdbuscpp2xml $1" + qdbuscpp2xml -A "$1" -o ../../interfaces/"$(basename "$1" .h)".xml +} + +p dbusservice.h +p network.h +p bss.h +p application.h +p screenshot.h +p notification.h \ls ./*api.h | while read file; do - qdbuscpp2xml -A "$file" -o ../../interfaces/"$(basename "$file" .h)".xml + p "$file" done diff --git a/applications/system-service/main.cpp b/applications/system-service/main.cpp index 54f703d2b..066b65cf4 100755 --- a/applications/system-service/main.cpp +++ b/applications/system-service/main.cpp @@ -1,7 +1,9 @@ #include #include +#include #include +#include #include #include "dbusservice.h" @@ -9,20 +11,42 @@ using namespace std; using namespace Oxide::Sentry; -const char *qt_version = qVersion(); +const char* qt_version = qVersion(); +const std::string runPath = "/run/oxide"; +const char* pidPath = "/run/oxide/oxide.pid"; +const char* lockPath = "/run/oxide/oxide.lock"; void sigHandler(int signal){ ::signal(signal, SIG_DFL); qApp->quit(); } +bool stopProcess(pid_t pid){ + if(pid <= 1){ + return false; + } + qDebug() << "Waiting for other instance to stop..."; + kill(pid, SIGTERM); + int tries = 0; + while(0 == kill(pid, 0)){ + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + if(++tries == 50){ + qDebug() << "Instance is taking too long, killing..."; + kill(pid, SIGKILL); + }else if(tries == 60){ + qDebug() << "Unable to kill process"; + return false; + } + } + return true; +} -int main(int argc, char *argv[]){ +int main(int argc, char* argv[]){ if(deviceSettings.getDeviceType() == Oxide::DeviceSettings::RM2 && getenv("RM2FB_ACTIVE") == nullptr){ - qWarning() << "rm2fb not detected. Running xochitl instead!"; + O_WARNING("rm2fb not detected. Running xochitl instead!"); return QProcess::execute("/usr/bin/xochitl", QStringList()); } if (strcmp(qt_version, QT_VERSION_STR) != 0){ - qDebug() << "Version mismatch, Runtime: " << qt_version << ", Build: " << QT_VERSION_STR; + O_WARNING("Version mismatch, Runtime: " << qt_version << ", Build: " << QT_VERSION_STR); } #ifdef __arm__ // Setup epaper @@ -40,9 +64,60 @@ int main(int argc, char *argv[]){ parser.addHelpOption(); parser.applicationDescription(); parser.addVersionOption(); + QCommandLineOption breakLockOption( + {"f", "break-lock"}, + "Break existing locks and force startup if another version of tarnish is already running" + ); + parser.addOption(breakLockOption); parser.process(app); - const QStringList args = parser.positionalArguments(); + if(!args.isEmpty()){ + parser.showHelp(EXIT_FAILURE); + } + auto actualPid = QString::number(app.applicationPid()); + QString pid = Oxide::execute( + "systemctl", + QStringList() << "--no-pager" << "show" << "--property" << "MainPID" << "--value" << "tarnish" + ).trimmed(); + if(pid != "0" && pid != actualPid){ + if(!parser.isSet(breakLockOption)){ + qDebug() << "tarnish.service is already running"; + return EXIT_FAILURE; + } + if(QProcess::execute("systemctl", QStringList() << "stop" << "tarnish")){ + qDebug() << "tarnish.service is already running"; + qDebug() << "Unable to stop service"; + return EXIT_FAILURE; + } + } + qDebug() << "Creating lock file" << lockPath; + if(!QFile::exists(QString::fromStdString(runPath)) && !std::filesystem::create_directories(runPath)){ + qDebug() << "Failed to create" << runPath.c_str(); + return EXIT_FAILURE; + } + int lock = Oxide::tryGetLock(lockPath); + if(lock < 0){ + qDebug() << "Unable to establish lock on" << lockPath << strerror(errno); + if(!parser.isSet(breakLockOption)){ + return EXIT_FAILURE; + } + qDebug() << "Attempting to stop all other instances of tarnish" << lockPath; + for(auto lockingPid : Oxide::lsof(lockPath)){ + if(Oxide::processExists(lockingPid)){ + stopProcess(lockingPid); + } + } + lock = Oxide::tryGetLock(lockPath); + if(lock < 0){ + qDebug() << "Unable to establish lock on" << lockPath << strerror(errno); + return EXIT_FAILURE; + } + } + + QObject::connect(&app, &QGuiApplication::aboutToQuit, [lock]{ + qDebug() << "Releasing lock " << lockPath; + Oxide::releaseLock(lock, lockPath); + }); signal(SIGINT, sigHandler); signal(SIGSEGV, sigHandler); @@ -52,10 +127,16 @@ int main(int argc, char *argv[]){ QTimer::singleShot(0, []{ dbusService->startup(); }); - system("mkdir -p /run/oxide"); - system(("echo " + to_string(app.applicationPid()) + " > /run/oxide/oxide.pid").c_str()); + QFile pidFile(pidPath); + if(!pidFile.open(QFile::ReadWrite)){ + qWarning() << "Unable to create " << pidPath; + return app.exec(); + } + pidFile.seek(0); + pidFile.write(actualPid.toUtf8()); + pidFile.close(); QObject::connect(&app, &QGuiApplication::aboutToQuit, []{ - remove("/run/oxide/oxide.pid"); + remove(pidPath); }); return app.exec(); } diff --git a/applications/system-service/network.cpp b/applications/system-service/network.cpp index ba15c95d9..86dc0da4c 100644 --- a/applications/system-service/network.cpp +++ b/applications/system-service/network.cpp @@ -3,8 +3,6 @@ #include "network.h" #include "wifiapi.h" -//#define DEBUG - QSet none{ "NONE", "WPA-NONE" diff --git a/applications/system-service/network.h b/applications/system-service/network.h index 4bbb6bad8..55c442b5c 100644 --- a/applications/system-service/network.h +++ b/applications/system-service/network.h @@ -5,9 +5,13 @@ #include #include -#include "../../shared/liboxide/liboxide.h" +#include + #include "supplicant.h" +// Must be included so that generate_xml.sh will work +#include "../../shared/liboxide/meta.h" + class Network : public QObject { Q_OBJECT Q_CLASSINFO("Version", OXIDE_INTERFACE_VERSION) @@ -18,6 +22,7 @@ class Network : public QObject { Q_PROPERTY(QString password READ getNull WRITE setPassword) Q_PROPERTY(QString protocol READ protocol WRITE setProtocol) Q_PROPERTY(QVariantMap properties READ properties WRITE setProperties NOTIFY propertiesChanged) + public: Network(QString path, QString ssid, QVariantMap properties, QObject* parent); Network(QString path, QVariantMap properties, QObject* parent) diff --git a/applications/system-service/notification.cpp b/applications/system-service/notification.cpp index d2e794627..08d091956 100644 --- a/applications/system-service/notification.cpp +++ b/applications/system-service/notification.cpp @@ -32,7 +32,7 @@ void Notification::display(){ return; } notificationAPI->lock(); - dispatchToMainThread([=]{ + Oxide::dispatchToMainThread([=]{ qDebug() << "Displaying notification" << identifier(); auto path = appsAPI->currentApplicationNoSecurityCheck(); Application* resumeApp = nullptr; @@ -52,61 +52,10 @@ void Notification::remove(){ emit removed(); } -void Notification::dispatchToMainThread(std::function callback){ - if(this->thread() == qApp->thread()){ - callback(); - return; - } - // any thread - QTimer* timer = new QTimer(); - timer->moveToThread(qApp->thread()); - timer->setSingleShot(true); - QObject::connect(timer, &QTimer::timeout, [=](){ - // main thread - callback(); - timer->deleteLater(); - }); - QMetaObject::invokeMethod(timer, "start", Qt::BlockingQueuedConnection, Q_ARG(int, 0)); -} void Notification::paintNotification(Application* resumeApp){ - auto frameBuffer = EPFrameBuffer::framebuffer(); - qDebug() << "Waiting for other painting to finish..."; - while(frameBuffer->paintingActive()){ - EPFrameBuffer::waitForLastUpdate(); - } qDebug() << "Painting notification" << identifier(); - screenBackup = frameBuffer->copy(); - qDebug() << "Painting to framebuffer..."; - QPainter painter(frameBuffer); - auto size = frameBuffer->size(); - auto fm = painter.fontMetrics(); - auto padding = 10; - auto radius = 10; - QImage icon(m_icon); - auto iconSize = icon.isNull() ? 0 : 50; - auto width = fm.horizontalAdvance(text()) + iconSize + (padding * 3); - auto height = max(fm.height(), iconSize) + (padding * 2); - auto left = size.width() - width; - auto top = size.height() - height; - updateRect = QRect(left, top, width, height); - painter.fillRect(updateRect, Qt::black); - painter.setPen(Qt::black); - painter.drawRoundedRect(updateRect, radius, radius); - painter.setPen(Qt::white); - QRect textRect(left + padding, top + padding, width - iconSize - (padding * 2), height - padding); - painter.drawText(textRect, Qt::AlignCenter, text()); - painter.end(); - qDebug() << "Updating screen " << updateRect << "..."; - EPFrameBuffer::sendUpdate(updateRect, EPFrameBuffer::Mono, EPFrameBuffer::PartialUpdate, true); - if(!icon.isNull()){ - QPainter painter2(frameBuffer); - QRect iconRect(size.width() - iconSize - padding, top + padding, iconSize, iconSize); - painter2.fillRect(iconRect, Qt::white); - painter2.drawImage(iconRect, icon); - painter2.end(); - EPFrameBuffer::sendUpdate(iconRect, EPFrameBuffer::Mono, EPFrameBuffer::PartialUpdate, true); - } - EPFrameBuffer::waitForLastUpdate(); + screenBackup = screenAPI->copy(); + updateRect = notificationAPI->paintNotification(text(), m_icon); qDebug() << "Painted notification" << identifier(); emit displayed(); QTimer::singleShot(2000, [this, resumeApp]{ @@ -117,7 +66,7 @@ void Notification::paintNotification(Application* resumeApp){ qDebug() << "Finished displaying notification" << identifier(); EPFrameBuffer::waitForLastUpdate(); if(!notificationAPI->notificationDisplayQueue.isEmpty()){ - dispatchToMainThread([resumeApp] { + Oxide::dispatchToMainThread([resumeApp] { notificationAPI->notificationDisplayQueue.takeFirst()->paintNotification(resumeApp); }); return; diff --git a/applications/system-service/notification.h b/applications/system-service/notification.h index 01b468d2d..a4aa9d5e3 100644 --- a/applications/system-service/notification.h +++ b/applications/system-service/notification.h @@ -5,9 +5,13 @@ #include #include -#include "../../shared/liboxide/liboxide.h" +#include + #include "application.h" +// Must be included so that generate_xml.sh will work +#include "../../shared/liboxide/meta.h" + class Notification : public QObject{ Q_OBJECT Q_CLASSINFO("Version", OXIDE_INTERFACE_VERSION) @@ -17,6 +21,7 @@ class Notification : public QObject{ Q_PROPERTY(QString application READ application WRITE setApplication) Q_PROPERTY(QString text READ text WRITE setText) Q_PROPERTY(QString icon READ icon WRITE setIcon) + public: Notification(const QString& path, const QString& identifier, const QString& owner, const QString& application, const QString& text, const QString& icon, QObject* parent); ~Notification(){ @@ -129,7 +134,6 @@ class Notification : public QObject{ QImage screenBackup; QRect updateRect; - void dispatchToMainThread(std::function callback); bool hasPermission(QString permission, const char* sender = __builtin_FUNCTION()); }; diff --git a/applications/system-service/notificationapi.h b/applications/system-service/notificationapi.h index 0ea578d12..f1ec8002d 100644 --- a/applications/system-service/notificationapi.h +++ b/applications/system-service/notificationapi.h @@ -18,6 +18,7 @@ class NotificationAPI : public APIBase { Q_PROPERTY(bool enabled READ enabled) Q_PROPERTY(QList allNotifications READ getAllNotifications) Q_PROPERTY(QList unownedNotifications READ getUnownedNotifications) + public: static NotificationAPI* singleton(NotificationAPI* self = nullptr){ static NotificationAPI* instance; @@ -105,6 +106,41 @@ class NotificationAPI : public APIBase { } return m_notifications.value(identifier); } + QRect paintNotification(const QString& text, const QString& iconPath){ + qDebug() << "Painting to framebuffer..."; + auto frameBuffer = EPFrameBuffer::framebuffer(); + QPainter painter(frameBuffer); + auto size = frameBuffer->size(); + auto fm = painter.fontMetrics(); + auto padding = 10; + auto radius = 10; + QImage icon(iconPath); + auto iconSize = icon.isNull() ? 0 : 50; + auto width = fm.horizontalAdvance(text) + iconSize + (padding * 3); + auto height = max(fm.height(), iconSize) + (padding * 2); + auto left = size.width() - width; + auto top = size.height() - height; + QRect updateRect(left, top, width, height); + painter.fillRect(updateRect, Qt::black); + painter.setPen(Qt::black); + painter.drawRoundedRect(updateRect, radius, radius); + painter.setPen(Qt::white); + QRect textRect(left + padding, top + padding, width - iconSize - (padding * 2), height - padding); + painter.drawText(textRect, Qt::AlignCenter, text); + painter.end(); + qDebug() << "Updating screen " << updateRect << "..."; + EPFrameBuffer::sendUpdate(updateRect, EPFrameBuffer::Mono, EPFrameBuffer::PartialUpdate, true); + if(!icon.isNull()){ + QPainter painter2(frameBuffer); + QRect iconRect(size.width() - iconSize - padding, top + padding, iconSize, iconSize); + painter2.fillRect(iconRect, Qt::white); + painter2.drawImage(iconRect, icon); + painter2.end(); + EPFrameBuffer::sendUpdate(iconRect, EPFrameBuffer::Mono, EPFrameBuffer::PartialUpdate, true); + } + EPFrameBuffer::waitForLastUpdate(); + return updateRect; + } public slots: QDBusObjectPath add(const QString& identifier, const QString& application, const QString& text, const QString& icon, QDBusMessage message){ diff --git a/applications/system-service/powerapi.h b/applications/system-service/powerapi.h index e267a1f3d..c3eabdf67 100644 --- a/applications/system-service/powerapi.h +++ b/applications/system-service/powerapi.h @@ -1,7 +1,7 @@ #ifndef BATTERYAPI_H #define BATTERYAPI_H -#include +#include #include #include @@ -24,6 +24,7 @@ class PowerAPI : public APIBase { Q_PROPERTY(int batteryLevel READ batteryLevel NOTIFY batteryLevelChanged) Q_PROPERTY(int batteryTemperature READ batteryTemperature NOTIFY batteryTemperatureChanged) Q_PROPERTY(int chargerState READ chargerState NOTIFY chargerStateChanged) + public: static PowerAPI* singleton(PowerAPI* self = nullptr){ static PowerAPI* instance; @@ -38,7 +39,7 @@ class PowerAPI : public APIBase { Oxide::Sentry::sentry_span(t, "singleton", "Setup singleton", [this]{ singleton(this); }); - Oxide::Sentry::sentry_span(t, "sysfs", "Determine power devices from sysfs", [this](Oxide::Sentry::Span* s){ + Oxide::Sentry::sentry_span(t, "sysfs", "Determine power devices from sysfs", [this](){ Oxide::Power::batteries(); Oxide::Power::chargers(); }); @@ -167,7 +168,7 @@ class PowerAPI : public APIBase { setBatteryState(BatteryUnknown); } if(!m_batteryWarning){ - qWarning() << "Can't find battery information"; + O_WARNING("Can't find battery information"); m_batteryWarning = true; emit batteryWarning(); } @@ -175,11 +176,11 @@ class PowerAPI : public APIBase { } if(!Oxide::Power::batteryPresent()){ if(m_batteryState != BatteryNotPresent){ - qWarning() << "Battery is somehow not in the device?"; + O_WARNING("Battery is somehow not in the device?"); setBatteryState(BatteryNotPresent); } if(!m_batteryWarning){ - qWarning() << "Battery is somehow not in the device?"; + O_WARNING("Battery is somehow not in the device?"); m_batteryWarning = true; emit batteryWarning(); } @@ -220,7 +221,7 @@ class PowerAPI : public APIBase { setChargerState(ChargerUnknown); } if(!m_chargerWarning){ - qWarning() << "Can't find charger information"; + O_WARNING("Can't find charger information"); m_chargerWarning = true; emit chargerWarning(); } diff --git a/applications/system-service/screenapi.cpp b/applications/system-service/screenapi.cpp new file mode 100644 index 000000000..6d3d7de39 --- /dev/null +++ b/applications/system-service/screenapi.cpp @@ -0,0 +1,27 @@ +#include "screenapi.h" +#include "notificationapi.h" + +QDBusObjectPath ScreenAPI::screenshot(){ + if(!hasPermission("screen")){ + return QDBusObjectPath("/"); + } + qDebug() << "Taking screenshot"; + auto filePath = getNextPath(); +#ifdef DEBUG + qDebug() << "Using path" << filePath; +#endif + QImage screen = copy(); + QRect rect = notificationAPI->paintNotification("Taking Screenshot...", ""); + EPFrameBuffer::sendUpdate(rect, EPFrameBuffer::Mono, EPFrameBuffer::PartialUpdate, true); + QDBusObjectPath path("/"); + if(!screen.save(filePath)){ + qDebug() << "Failed to take screenshot"; + }else{ + path = addScreenshot(filePath)->qPath(); + } + QPainter painter(EPFrameBuffer::framebuffer()); + painter.drawImage(rect, screen, rect); + painter.end(); + EPFrameBuffer::sendUpdate(rect, EPFrameBuffer::HighQualityGrayscale, EPFrameBuffer::PartialUpdate, true); + return path; +} diff --git a/applications/system-service/screenapi.h b/applications/system-service/screenapi.h index fad816fe4..430a3e7ff 100644 --- a/applications/system-service/screenapi.h +++ b/applications/system-service/screenapi.h @@ -32,6 +32,7 @@ class ScreenAPI : public APIBase { Q_CLASSINFO("D-Bus Interface", OXIDE_SCREEN_INTERFACE) Q_PROPERTY(bool enabled READ enabled) Q_PROPERTY(QList screenshots READ screenshots) + public: static ScreenAPI* singleton(ScreenAPI* self = nullptr){ static ScreenAPI* instance; @@ -102,8 +103,7 @@ class ScreenAPI : public APIBase { } Oxide::Sentry::sentry_transaction("screen", "drawFullscrenImage", [img, path](Oxide::Sentry::Transaction* t){ Q_UNUSED(t); - auto size = EPFrameBuffer::framebuffer()->size(); - QRect rect(0, 0, size.width(), size.height()); + QRect rect = EPFrameBuffer::framebuffer()->rect(); QPainter painter(EPFrameBuffer::framebuffer()); painter.drawImage(rect, img); painter.end(); @@ -113,20 +113,14 @@ class ScreenAPI : public APIBase { return true; } - Q_INVOKABLE QDBusObjectPath screenshot(){ - if(!hasPermission("screen")){ - return QDBusObjectPath("/"); - } - qDebug() << "Taking screenshot"; - auto filePath = getNextPath(); -#ifdef DEBUG - qDebug() << "Using path" << filePath; -#endif - if(!EPFrameBuffer::framebuffer()->save(filePath)){ - qDebug() << "Failed to take screenshot"; - return QDBusObjectPath("/"); + Q_INVOKABLE QDBusObjectPath screenshot(); + QImage copy(){ + auto frameBuffer = EPFrameBuffer::framebuffer(); + qDebug() << "Waiting for other painting to finish..."; + while(frameBuffer->paintingActive()){ + EPFrameBuffer::waitForLastUpdate(); } - return addScreenshot(filePath)->qPath(); + return frameBuffer->copy(); } public slots: diff --git a/applications/system-service/screenshot.h b/applications/system-service/screenshot.h index 30934a2cf..a855911ab 100644 --- a/applications/system-service/screenshot.h +++ b/applications/system-service/screenshot.h @@ -6,8 +6,10 @@ #include #include +#include -#include "../../shared/liboxide/liboxide.h" +// Must be included so that generate_xml.sh will work +#include "../../shared/liboxide/meta.h" class Screenshot : public QObject{ Q_OBJECT @@ -15,6 +17,7 @@ class Screenshot : public QObject{ Q_CLASSINFO("D-Bus Interface", OXIDE_SCREENSHOT_INTERFACE) Q_PROPERTY(QByteArray blob READ blob WRITE setBlob) Q_PROPERTY(QString path READ getPath) + public: Screenshot(QString path, QString filePath, QObject* parent) : QObject(parent), m_path(path), mutex() { m_file = new QFile(filePath); diff --git a/applications/system-service/tarnish.pro b/applications/system-service/system-service.pro similarity index 61% rename from applications/system-service/tarnish.pro rename to applications/system-service/system-service.pro index 233205486..ed26c2a78 100644 --- a/applications/system-service/tarnish.pro +++ b/applications/system-service/system-service.pro @@ -3,7 +3,7 @@ QT += dbus CONFIG += c++17 CONFIG += console CONFIG -= app_bundle -CONFIG += precompile_header_c +CONFIG += precompile_header DEFINES += QT_DEPRECATED_WARNINGS DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 @@ -19,18 +19,19 @@ SOURCES += \ event_device.cpp \ network.cpp \ notification.cpp \ + screenapi.cpp \ screenshot.cpp \ systemapi.cpp \ wlan.cpp \ wpa_supplicant.cpp \ main.cpp -TARGET=tarnish - +TARGET = tarnish +include(../../qmake/common.pri) target.path = /opt/bin INSTALLS += target -configFile.files = ../../assets/etc/dbus-1/system.d/* +configFile.files = ../../assets/etc/dbus-1/system.d/codes.eeems.oxide.conf configFile.path = /etc/dbus-1/system.d/ INSTALLS += configFile @@ -38,16 +39,19 @@ service.files = ../../assets/etc/systemd/system/tarnish.service service.path = /etc/systemd/system/ INSTALLS += service -applications.files = ../../assets/opt/usr/share/applications/* +applications.files = ../../assets/opt/usr/share/applications/xochitl.oxide applications.path = /opt/usr/share/applications/ INSTALLS += applications +icons.files += ../../assets/opt/usr/share/icons/oxide/48x48/apps/xochitl.png +icons.path = /opt/usr/share/icons/oxide/48x48/apps +INSTALLS += icons + system(qdbusxml2cpp -N -p wpa_supplicant.h:wpa_supplicant.cpp fi.w1.wpa_supplicant1.xml) DBUS_INTERFACES += org.freedesktop.login1.xml HEADERS += \ - ../../shared/liboxide/liboxide.h \ apibase.h \ application.h \ appsapi.h \ @@ -66,40 +70,25 @@ HEADERS += \ screenshot.h \ supplicant.h \ systemapi.h \ + tarnish_stable.h \ wifiapi.h \ wlan.h \ wpa_supplicant.h +PRECOMPILED_HEADER = \ + tarnish_stable.h + LIBS += -lpng16 LIBS += -lsystemd LIBS += -lz -LIBS += -L$$PWD/../../shared/ -lqsgepaper -INCLUDEPATH += $$PWD/../../shared -DEPENDPATH += $$PWD/../../shared -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 \ org.freedesktop.login1.xml -exists($$PWD/../../.build/sentry) { - LIBS += -L$$PWD/../../.build/sentry/lib -lsentry -ldl -lcurl -lbreakpad_client - INCLUDEPATH += $$PWD/../../.build/sentry/include - DEPENDPATH += $$PWD/../../.build/sentry/lib +include(../../qmake/epaper.pri) +include(../../qmake/liboxide.pri) +include(../../qmake/sentry.pri) - library.files = ../../.build/sentry/libsentry.so - library.path = /opt/lib - INSTALLS += library -} - -LIBS += -L$$PWD/../../.build/liboxide -lliboxide -INCLUDEPATH += $$PWD/../../shared/liboxide -DEPENDPATH += $$PWD/../../shared/liboxide - -QMAKE_RPATHDIR += /lib /usr/lib /opt/lib /opt/usr/lib - -VERSION = 2.5 +QMAKE_POST_LINK += sh $$_PRO_FILE_PWD_/generate_xml.sh diff --git a/applications/system-service/systemapi.cpp b/applications/system-service/systemapi.cpp index 5588f169a..cac8229f3 100644 --- a/applications/system-service/systemapi.cpp +++ b/applications/system-service/systemapi.cpp @@ -83,9 +83,9 @@ void SystemAPI::PrepareForSleep(bool suspending){ Oxide::Sentry::sentry_span(t, "enable", "Enable various services", [this, device]{ buttonHandler->setEnabled(true); emit deviceResuming(); - if(m_autoSleep && powerAPI->chargerState() != PowerAPI::ChargerConnected){ + if(autoSleep() && powerAPI->chargerState() != PowerAPI::ChargerConnected){ qDebug() << "Suspend timer re-enabled due to resume"; - suspendTimer.start(m_autoSleep * 60 * 1000); + suspendTimer.start(autoSleep() * 60 * 1000); } if(device == Oxide::DeviceSettings::DeviceType::RM2){ system("modprobe brcmfmac"); @@ -98,20 +98,19 @@ void SystemAPI::PrepareForSleep(bool suspending){ }); } } -void SystemAPI::setAutoSleep(int autoSleep){ - if(autoSleep < 0 || autoSleep > 360){ +void SystemAPI::setAutoSleep(int _autoSleep){ + if(_autoSleep < 0 || _autoSleep > 360){ return; } - qDebug() << "Auto Sleep" << autoSleep; - m_autoSleep = autoSleep; - if(m_autoSleep && powerAPI->chargerState() != PowerAPI::ChargerConnected){ - suspendTimer.setInterval(m_autoSleep * 60 * 1000); - }else if(!m_autoSleep){ + qDebug() << "Auto Sleep" << _autoSleep; + sharedSettings.set_autoSleep(_autoSleep); + if(_autoSleep && powerAPI->chargerState() != PowerAPI::ChargerConnected){ + suspendTimer.setInterval(_autoSleep * 60 * 1000); + }else if(!_autoSleep){ suspendTimer.stop(); } - settings.setValue("autoSleep", autoSleep); - settings.sync(); - emit autoSleepChanged(autoSleep); + sharedSettings.sync(); + emit autoSleepChanged(_autoSleep); } void SystemAPI::uninhibitAll(QString name){ if(powerOffInhibited()){ @@ -126,25 +125,25 @@ void SystemAPI::uninhibitAll(QString name){ emit sleepInhibitedChanged(false); } } - if(!sleepInhibited() && m_autoSleep && powerAPI->chargerState() != PowerAPI::ChargerConnected && !suspendTimer.isActive()){ + if(!sleepInhibited() && autoSleep() && powerAPI->chargerState() != PowerAPI::ChargerConnected && !suspendTimer.isActive()){ qDebug() << "Suspend timer re-enabled due to uninhibit" << name; - suspendTimer.start(m_autoSleep * 60 * 1000); + suspendTimer.start(autoSleep() * 60 * 1000); } } void SystemAPI::startSuspendTimer(){ - if(m_autoSleep && powerAPI->chargerState() != PowerAPI::ChargerConnected && !suspendTimer.isActive()){ + if(autoSleep() && powerAPI->chargerState() != PowerAPI::ChargerConnected && !suspendTimer.isActive()){ qDebug() << "Suspend timer re-enabled due to start Suspend timer"; - suspendTimer.start(m_autoSleep * 60 * 1000); + suspendTimer.start(autoSleep() * 60 * 1000); } } void SystemAPI::activity(){ auto active = suspendTimer.isActive(); suspendTimer.stop(); - if(m_autoSleep && powerAPI->chargerState() != PowerAPI::ChargerConnected){ + if(autoSleep() && powerAPI->chargerState() != PowerAPI::ChargerConnected){ if(!active){ qDebug() << "Suspend timer re-enabled due to activity"; } - suspendTimer.start(m_autoSleep * 60 * 1000); + suspendTimer.start(autoSleep() * 60 * 1000); }else if(active){ qDebug() << "Suspend timer disabled"; } @@ -155,10 +154,10 @@ void SystemAPI::uninhibitSleep(QDBusMessage message){ return; } sleepInhibitors.removeAll(message.service()); - if(!sleepInhibited() && m_autoSleep && powerAPI->chargerState() != PowerAPI::ChargerConnected){ + if(!sleepInhibited() && autoSleep() && powerAPI->chargerState() != PowerAPI::ChargerConnected){ if(!suspendTimer.isActive()){ qDebug() << "Suspend timer re-enabled due to uninhibit sleep" << message.service(); - suspendTimer.start(m_autoSleep * 60 * 1000); + suspendTimer.start(autoSleep() * 60 * 1000); } releaseSleepInhibitors(true); } @@ -167,7 +166,7 @@ void SystemAPI::uninhibitSleep(QDBusMessage message){ } } void SystemAPI::timeout(){ - if(m_autoSleep && powerAPI->chargerState() != PowerAPI::ChargerConnected){ + if(autoSleep() && powerAPI->chargerState() != PowerAPI::ChargerConnected){ qDebug() << "Automatic suspend due to inactivity..."; suspend(); } diff --git a/applications/system-service/systemapi.h b/applications/system-service/systemapi.h index f5936288d..ef6f71e6c 100644 --- a/applications/system-service/systemapi.h +++ b/applications/system-service/systemapi.h @@ -67,6 +67,7 @@ class SystemAPI : public APIBase { Q_PROPERTY(int autoSleep READ autoSleep WRITE setAutoSleep NOTIFY autoSleepChanged) Q_PROPERTY(bool sleepInhibited READ sleepInhibited NOTIFY sleepInhibitedChanged) Q_PROPERTY(bool powerOffInhibited READ powerOffInhibited NOTIFY powerOffInhibitedChanged) + public: enum SwipeDirection { None, Right, Left, Up, Down }; Q_ENUM(SwipeDirection) @@ -113,39 +114,67 @@ class SystemAPI : public APIBase { }); }); Oxide::Sentry::sentry_span(t, "autoSleep", "Setup automatic sleep", [this](Oxide::Sentry::Span* s){ - auto autoSleep = settings.value("autoSleep", 1).toInt(); - m_autoSleep = autoSleep; - if(autoSleep < 0) { - m_autoSleep = 0; - - }else if(autoSleep > 10){ - m_autoSleep = 10; + QSettings settings; + if(QFile::exists(settings.fileName())){ + qDebug() << "Importing old settings"; + settings.sync(); + if(settings.contains("autoSleep")){ + qDebug() << "Importing old autoSleep"; + sharedSettings.set_autoSleep(settings.value("autoSleep").toInt()); + } + int size = settings.beginReadArray("swipes"); + if(size){ + sharedSettings.beginWriteArray("swipes"); + for(short i = Right; i <= Down && i < size; i++){ + settings.setArrayIndex(i); + sharedSettings.setArrayIndex(i); + qDebug() << QString("Importing old swipe[%1]").arg(i); + sharedSettings.setValue("enabled", settings.value("enabled", true)); + sharedSettings.setValue("length", settings.value("length", 30)); + } + sharedSettings.endArray(); + } + settings.endArray(); + settings.remove("swipes"); + settings.sync(); + sharedSettings.sync(); } - if(autoSleep != m_autoSleep){ - Oxide::Sentry::sentry_span(s, "update", "Update value", [this, autoSleep]{ - m_autoSleep = autoSleep; - settings.setValue("autoSleep", autoSleep); - settings.sync(); - emit autoSleepChanged(autoSleep); - }); + if(autoSleep() < 0) { + sharedSettings.set_autoSleep(0); + }else if(autoSleep() > 10){ + sharedSettings.set_autoSleep(10); } - qDebug() << "Auto Sleep" << autoSleep; + qDebug() << "Auto Sleep" << autoSleep(); Oxide::Sentry::sentry_span(s, "timer", "Setup timer", [this]{ - if(m_autoSleep){ - suspendTimer.start(m_autoSleep * 60 * 1000); - }else if(!m_autoSleep){ + if(autoSleep()){ + suspendTimer.start(autoSleep() * 60 * 1000); + }else if(!autoSleep()){ suspendTimer.stop(); } }); + connect(&sharedSettings, &Oxide::SharedSettings::autoSleepChanged, [=](int _autoSleep){ + emit autoSleepChanged(_autoSleep); + }); + connect(&sharedSettings, &Oxide::SharedSettings::changed, [=](){ + sharedSettings.beginReadArray("swipes"); + for(short i = Right; i <= Down; i++){ + sharedSettings.setArrayIndex(i); + swipeStates[(SwipeDirection)i] = sharedSettings.value("enabled", true).toBool(); + int length = sharedSettings.value("length", 30).toInt(); + swipeLengths[(SwipeDirection)i] = length; + emit swipeLengthChanged(i, length); + } + sharedSettings.endArray(); + }); }); - Oxide::Sentry::sentry_span(t, "swipes", "Load swipe settings", [this]{ - settings.beginReadArray("swipes"); + Oxide::Sentry::sentry_span(t, "swipes", "Load swipe settings", [=](){ + sharedSettings.beginReadArray("swipes"); for(short i = Right; i <= Down; i++){ - settings.setArrayIndex(i); - swipeStates[(SwipeDirection)i] = settings.value("enabled", true).toBool(); - swipeLengths[(SwipeDirection)i] = settings.value("length", 30).toInt(); + sharedSettings.setArrayIndex(i); + swipeStates[(SwipeDirection)i] = sharedSettings.value("enabled", true).toBool(); + swipeLengths[(SwipeDirection)i] = sharedSettings.value("length", 30).toInt(); } - settings.endArray(); + sharedSettings.endArray(); }); // Ask Systemd to tell us nicely when we are about to suspend or resume Oxide::Sentry::sentry_span(t, "inhibit", "Inhibit sleep and power off", [this](Oxide::Sentry::Span* s){ @@ -180,8 +209,8 @@ class SystemAPI : public APIBase { void setEnabled(bool enabled){ qDebug() << "System API" << enabled; } - int autoSleep(){return m_autoSleep; } - void setAutoSleep(int autoSleep); + int autoSleep(){return sharedSettings.autoSleep(); } + void setAutoSleep(int _autoSleep); bool sleepInhibited(){ return sleepInhibitors.length(); } bool powerOffInhibited(){ return powerOffInhibitors.length(); } void uninhibitAll(QString name); @@ -223,14 +252,14 @@ class SystemAPI : public APIBase { return; } swipeStates[direction] = enabled; - settings.beginWriteArray("swipes"); + sharedSettings.beginWriteArray("swipes"); for(short i = Right; i <= Down; i++){ - settings.setArrayIndex(i); - settings.setValue("enabled", swipeStates[(SwipeDirection)i]); - settings.setValue("length", swipeLengths[(SwipeDirection)i]); + sharedSettings.setArrayIndex(i); + sharedSettings.setValue("enabled", swipeStates[(SwipeDirection)i]); + sharedSettings.setValue("length", swipeLengths[(SwipeDirection)i]); } - settings.endArray(); - settings.sync(); + sharedSettings.endArray(); + sharedSettings.sync(); } Q_INVOKABLE bool getSwipeEnabled(int direction){ if(!hasPermission("system")){ @@ -295,14 +324,14 @@ class SystemAPI : public APIBase { return; } swipeLengths[direction] = length; - settings.beginWriteArray("swipes"); + sharedSettings.beginWriteArray("swipes"); for(short i = Right; i <= Down; i++){ - settings.setArrayIndex(i); - settings.setValue("enabled", swipeStates[(SwipeDirection)i]); - settings.setValue("length", swipeLengths[(SwipeDirection)i]); + sharedSettings.setArrayIndex(i); + sharedSettings.setValue("enabled", swipeStates[(SwipeDirection)i]); + sharedSettings.setValue("length", swipeLengths[(SwipeDirection)i]); } - settings.endArray(); - settings.sync(); + sharedSettings.endArray(); + sharedSettings.sync(); emit swipeLengthChanged(direction, length); } Q_INVOKABLE int getSwipeLength(int direction){ @@ -515,7 +544,6 @@ private slots: QMutex mutex; QMap touches; int currentSlot = 0; - int m_autoSleep; bool wifiWasOn = false; bool penActive = false; int swipeDirection = None; @@ -687,7 +715,6 @@ private slots: } if(swipeDirection == Left){ emit rightAction(); - }else{ emit leftAction(); } diff --git a/applications/system-service/tarnish_stable.h b/applications/system-service/tarnish_stable.h new file mode 100644 index 000000000..872a6bc80 --- /dev/null +++ b/applications/system-service/tarnish_stable.h @@ -0,0 +1,76 @@ +#if defined __cplusplus +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "apibase.h" +#include "mxcfb.h" +#include "supplicant.h" +#endif diff --git a/applications/system-service/wifiapi.h b/applications/system-service/wifiapi.h index 227df0f82..f74d8cf43 100644 --- a/applications/system-service/wifiapi.h +++ b/applications/system-service/wifiapi.h @@ -8,6 +8,7 @@ #include #include +#include #include "apibase.h" #include "wlan.h" @@ -27,6 +28,7 @@ class WifiAPI : public APIBase { Q_PROPERTY(QDBusObjectPath network READ network) Q_PROPERTY(QList networks READ getNetworkPaths) Q_PROPERTY(bool scanning READ scanning NOTIFY scanningChanged) + public: static WifiAPI* singleton(WifiAPI* self = nullptr){ static WifiAPI* instance; @@ -78,7 +80,7 @@ class WifiAPI : public APIBase { Oxide::Sentry::Span* span = Oxide::Sentry::start_span(s, "connect", "Connect to DBus interface"); QDBusConnection bus = QDBusConnection::systemBus(); if(!bus.isConnected()){ - qWarning("Failed to connect to system bus."); + O_WARNING("Failed to connect to system bus."); throw QException(); } validateSupplicant(); @@ -656,7 +658,6 @@ class WifiAPI : public APIBase { void bssRemoved(QDBusObjectPath); void scanningChanged(bool); - private slots: // wpa_supplicant signals void InterfaceAdded(const QDBusObjectPath &path, const QVariantMap &properties){ @@ -686,6 +687,7 @@ private slots: void PropertiesChanged(const QVariantMap &properties){ Q_UNUSED(properties); } + private: bool m_enabled; QTimer* timer; @@ -806,7 +808,6 @@ private slots: m_rssi = crssi; emit rssiChanged(crssi); } - } State getCurrentState(){ State state = Off; diff --git a/applications/system-service/wlan.h b/applications/system-service/wlan.h index af99170ca..049480b4a 100644 --- a/applications/system-service/wlan.h +++ b/applications/system-service/wlan.h @@ -4,15 +4,17 @@ #include #include -#include -#include "sysobject.h" #include "supplicant.h" +// Must be included so that generate_xml.sh will work +#include "../../shared/liboxide/sysobject.h" + using Oxide::SysObject; class Wlan : public QObject, public SysObject { Q_OBJECT + public: Wlan(QString path, QObject* parent) : QObject(parent), SysObject(path), m_blobs(), m_iface(){ m_iface = QFileInfo(path).fileName(); @@ -69,7 +71,7 @@ class Wlan : public QObject, public SysObject { signed int rssi(){ QDBusMessage message = m_interface->call("SignalPoll"); if (message.type() == QDBusMessage::ErrorMessage) { - qWarning() << "SignalPoll error: " << message.errorMessage(); + O_WARNING("SignalPoll error: " << message.errorMessage()); return -100; } auto props = qdbus_cast(message.arguments().at(0).value().variant().value()); diff --git a/applications/task-switcher/appitem.cpp b/applications/task-switcher/appitem.cpp index df8f68e56..9f860158f 100755 --- a/applications/task-switcher/appitem.cpp +++ b/applications/task-switcher/appitem.cpp @@ -5,16 +5,16 @@ #include #include +#include + #include "appitem.h" -#include "dbusservice_interface.h" -#include "appsapi_interface.h" #include "controller.h" bool AppItem::ok(){ return getApp() != nullptr; } void AppItem::execute(){ if(!getApp() || !app->isValid()){ - qWarning() << "Application instance is not valid"; + O_WARNING("Application instance is not valid"); return; } qDebug() << "Running application " << property("name").toString(); @@ -28,7 +28,7 @@ void AppItem::execute(){ } void AppItem::stop(){ if(!getApp() || !app->isValid()){ - qWarning() << "Application instance is not valid"; + O_WARNING("Application instance is not valid"); return; } QDBusPendingReply reply = app->stop(); diff --git a/applications/task-switcher/appitem.h b/applications/task-switcher/appitem.h index 52bdc5a03..b2b2c8927 100644 --- a/applications/task-switcher/appitem.h +++ b/applications/task-switcher/appitem.h @@ -1,18 +1,13 @@ #ifndef APP_H #define APP_H #include - -#include "application_interface.h" - -#ifndef OXIDE_SERVICE -#define OXIDE_SERVICE "codes.eeems.oxide1" -#define OXIDE_SERVICE_PATH "/codes/eeems/oxide1" -#endif +#include using namespace codes::eeems::oxide1; class AppItem : public QObject { Q_OBJECT + public: AppItem(QObject* parent) : QObject(parent){} @@ -34,6 +29,7 @@ class AppItem : public QObject { Q_INVOKABLE void execute(); Q_INVOKABLE void stop(); + signals: void nameChanged(QString); void displayNameChanged(QString); @@ -58,7 +54,7 @@ private slots: } void onIconChanged(QString path){ _imgFile = path; - emit onIconChanged(path); + emit imgFileChanged(path); } private: diff --git a/applications/task-switcher/controller.h b/applications/task-switcher/controller.h index cbc5f85ea..41c995a4f 100644 --- a/applications/task-switcher/controller.h +++ b/applications/task-switcher/controller.h @@ -11,11 +11,6 @@ #include #include -#include "dbusservice_interface.h" -#include "screenapi_interface.h" -#include "appsapi_interface.h" -#include "application_interface.h" - #include "screenprovider.h" #include "appitem.h" @@ -32,9 +27,10 @@ enum WifiState { WifiUnknown, WifiOff, WifiDisconnected, WifiOffline, WifiOnline class Controller : public QObject { Q_OBJECT + public: Controller(QObject* parent, ScreenProvider* screenProvider) - : QObject(parent), settings(this), applications() { + : QObject(parent),applications() { blankImage = new QImage(qApp->primaryScreen()->geometry().size(), QImage::Format_Mono); this->screenProvider = screenProvider; auto bus = QDBusConnection::systemBus(); @@ -72,11 +68,6 @@ class Controller : public QObject { connect(appsApi, &Apps::applicationLaunched, this, &Controller::reload); connect(appsApi, &Apps::applicationExited, this, &Controller::reload); - settings.sync(); - auto version = settings.value("version", 0).toInt(); - if(version < CORRUPT_SETTINGS_VERSION){ - migrate(&settings, version); - } updateImage(); } ~Controller(){} @@ -124,7 +115,6 @@ class Controller : public QObject { for(auto item : runningApplications){ auto path = item.value().path(); Application app(OXIDE_SERVICE, path, bus, this); - qDebug() << app.name() << app.hidden(); if(app.hidden()){ continue; } @@ -212,7 +202,7 @@ class Controller : public QObject { Oxide::Sentry::sentry_span(s, name.toStdString(), "Load image from application", [this, &img, previousApplications, name]{ auto path = ((QDBusObjectPath)appsApi->getApplicationPath(name)).path(); if(path == "/"){ - qWarning() << "Unable to get save screen for" << name; + O_WARNING("Unable to get save screen for" << name); return; } auto bus = QDBusConnection::systemBus(); @@ -220,7 +210,7 @@ class Controller : public QObject { auto data = app.screenCapture(); auto image = QImage::fromData(data, "JPG"); if(image.isNull()){ - qWarning() << "Image for " << name << " is corrupt, trying next application"; + O_WARNING("Image for " << name << " is corrupt, trying next application"); return; } img = new QImage(image); @@ -278,7 +268,6 @@ private slots: } private: - QSettings settings; General* api; Screen* screenApi; Apps* appsApi; @@ -293,15 +282,6 @@ private slots: 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/task-switcher/corrupt.pro b/applications/task-switcher/corrupt.pro deleted file mode 100644 index 31764e482..000000000 --- a/applications/task-switcher/corrupt.pro +++ /dev/null @@ -1,53 +0,0 @@ -QT += gui -QT += quick -QT += dbus - -CONFIG += c++11 -CONFIG += qml_debug - -DEFINES += QT_DEPRECATED_WARNINGS -DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 - -SOURCES += \ - appitem.cpp \ - main.cpp - -# Default rules for deployment. -target.path = /opt/bin -!isEmpty(target.path): INSTALLS += target - -DBUS_INTERFACES += ../../interfaces/dbusservice.xml -DBUS_INTERFACES += ../../interfaces/screenapi.xml -DBUS_INTERFACES += ../../interfaces/appsapi.xml -DBUS_INTERFACES += ../../interfaces/application.xml - -INCLUDEPATH += ../../shared -HEADERS += \ - appitem.h \ - controller.h \ - screenprovider.h - -RESOURCES += \ - qml.qrc - -LIBS += -L$$PWD/../../shared/ -lqsgepaper -INCLUDEPATH += $$PWD/../../shared -DEPENDPATH += $$PWD/../../shared - -exists($$PWD/../../.build/sentry) { - LIBS += -L$$PWD/../../.build/sentry/lib -lsentry -ldl -lcurl -lbreakpad_client - INCLUDEPATH += $$PWD/../../.build/sentry/include - DEPENDPATH += $$PWD/../../.build/sentry/lib - - library.files = ../../.build/sentry/libsentry.so - library.path = /opt/lib - INSTALLS += library -} - -LIBS += -L$$PWD/../../.build/liboxide -lliboxide -INCLUDEPATH += $$PWD/../../shared/liboxide -DEPENDPATH += $$PWD/../../shared/liboxide - -QMAKE_RPATHDIR += /lib /usr/lib /opt/lib /opt/usr/lib - -VERSION = 2.5 diff --git a/applications/task-switcher/corrupt_stable.h b/applications/task-switcher/corrupt_stable.h new file mode 100644 index 000000000..a22fb4ffd --- /dev/null +++ b/applications/task-switcher/corrupt_stable.h @@ -0,0 +1,25 @@ +#if defined __cplusplus +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "controller.h" +#include "screenprovider.h" +#endif diff --git a/applications/task-switcher/main.cpp b/applications/task-switcher/main.cpp index a01d65c12..c56934d0f 100644 --- a/applications/task-switcher/main.cpp +++ b/applications/task-switcher/main.cpp @@ -16,8 +16,6 @@ Q_IMPORT_PLUGIN(QsgEpaperPlugin) #endif -#include "dbusservice_interface.h" - using namespace std; using namespace Oxide; using namespace Oxide::Sentry; @@ -48,7 +46,7 @@ int main(int argc, char *argv[]){ app.setOrganizationName("Eeems"); app.setOrganizationDomain(OXIDE_SERVICE); app.setApplicationName("corrupt"); - app.setApplicationVersion(OXIDE_INTERFACE_VERSION); + app.setApplicationVersion(APP_VERSION); auto screenProvider = new ScreenProvider(&app); Controller controller(&app, screenProvider); QQmlApplicationEngine engine; diff --git a/applications/task-switcher/task-switcher.pro b/applications/task-switcher/task-switcher.pro new file mode 100644 index 000000000..5f7faa0f2 --- /dev/null +++ b/applications/task-switcher/task-switcher.pro @@ -0,0 +1,40 @@ +QT += gui +QT += quick +QT += dbus + +CONFIG += c++11 +CONFIG += qml_debug +CONFIG += qtquickcompiler +CONFIG += precompile_header + +DEFINES += QT_DEPRECATED_WARNINGS +DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 + +SOURCES += \ + appitem.cpp \ + main.cpp + +TARGET = corrupt +include(../../qmake/common.pri) +target.path = /opt/bin +INSTALLS += target + +applications.files = ../../assets/opt/usr/share/applications/codes.eeems.corrupt.oxide +applications.path = /opt/usr/share/applications/ +INSTALLS += applications + +INCLUDEPATH += ../../shared +HEADERS += \ + appitem.h \ + controller.h \ + screenprovider.h + +RESOURCES += \ + qml.qrc + +PRECOMPILED_HEADER = \ + corrupt_stable.h + +include(../../qmake/epaper.pri) +include(../../qmake/liboxide.pri) +include(../../qmake/sentry.pri) diff --git a/applications/update-desktop-database/.gitignore b/applications/update-desktop-database/.gitignore new file mode 100644 index 000000000..fab7372d7 --- /dev/null +++ b/applications/update-desktop-database/.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/update-desktop-database/main.cpp b/applications/update-desktop-database/main.cpp new file mode 100644 index 000000000..45ad8b893 --- /dev/null +++ b/applications/update-desktop-database/main.cpp @@ -0,0 +1,157 @@ +#include + +#include + +using namespace codes::eeems::oxide1; +using namespace Oxide::Sentry; +using namespace Oxide::JSON; +using namespace Oxide::Applications; + +#define LOG_VERBOSE(msg) if(!parser.isSet(quietOption) && parser.isSet(verboseOption)){qDebug() << msg;} +#define LOG(msg) if(!parser.isSet(quietOption)){qDebug() << msg;} + +int qExit(int ret){ + QTimer::singleShot(0, [ret](){ + qApp->exit(ret); + }); + return qApp->exec(); +} + +QList configDirectoryPaths = { "/opt/etc/draft", "/etc/draft", "/home/root/.config/draft" }; + +int main(int argc, char *argv[]){ + QCoreApplication app(argc, argv); + sentry_init("update-desktop-database", argv); + app.setOrganizationName("Eeems"); + app.setOrganizationDomain(OXIDE_SERVICE); + app.setApplicationName("update-desktop-database"); + app.setApplicationVersion(APP_VERSION); + QCommandLineParser parser; + parser.setApplicationDescription("Reload the application registration cache for Oxide"); + parser.applicationDescription(); + parser.addHelpOption(); + parser.addPositionalArgument("directory", "NOT IMPLEMENTED", "[DIRECTORY...]"); + // TODO handle using $XDG_DATA_DIRS/applications if directory is not set + QCommandLineOption versionOption("version", "Display the version and exit"); + parser.addOption(versionOption); + QCommandLineOption quietOption( + {"q", "quiet"}, + "Do not display any information about processing and updating progress." + ); + parser.addOption(quietOption); + QCommandLineOption verboseOption( + {"v", "verbose"}, + "Display more information about processing and upating progress" + ); + parser.addOption(verboseOption); + parser.process(app); + if(parser.isSet(versionOption)){ + parser.showVersion(); + } + auto bus = QDBusConnection::systemBus(); + General api(OXIDE_SERVICE, OXIDE_SERVICE_PATH, bus); + LOG_VERBOSE("Requesting apps API"); + QDBusObjectPath path = api.requestAPI("apps"); + if(path.path() == "/"){ + LOG("Unable to get apps API"); + return qExit(EXIT_FAILURE); + } + Apps apps(OXIDE_SERVICE, path.path(), bus); + LOG("Loading applications from disk"); + QDir dir(OXIDE_APPLICATION_REGISTRATIONS_DIRECTORY); + dir.setNameFilters(QStringList() << "*.oxide"); + for(auto entry : dir.entryInfoList()){ + auto path = entry.filePath(); + auto reg = getRegistration(path); + auto name = entry.completeBaseName(); + auto errors = validateRegistration(name, reg); + bool cache = true; + for(auto error : errors){ + if(error.level == ErrorLevel::Error || error.level == ErrorLevel::Critical){ + LOG_VERBOSE(" " << path.toStdString().c_str() << ": " << error); + cache = false; + } + } + if(cache && !addToTarnishCache(name, reg)){ + LOG(" " << path << ": Failed to cache") + } + } + LOG_VERBOSE("Finished reloading applications"); + LOG("Importing Draft Applications"); + for(auto configDirectoryPath : configDirectoryPaths){ + QDir configDirectory(configDirectoryPath); + configDirectory.setFilter( QDir::Files | QDir::NoSymLinks | QDir::NoDot | QDir::NoDotDot); + auto images = configDirectory.entryInfoList(QDir::NoFilter,QDir::SortFlag::Name); + for(QFileInfo fi : images){ + if(fi.fileName() != "conf"){ + auto f = fi.absoluteFilePath(); + LOG_VERBOSE("parsing file " << f); + QFile file(fi.absoluteFilePath()); + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { + if(!parser.isSet(quietOption)){ + qCritical() << "Couldn't find the file " << f; + } + continue; + } + QTextStream in(&file); + QVariantMap properties; + while (!in.atEnd()) { + QString line = in.readLine(); + if(line.startsWith("#") || line.trimmed().isEmpty()){ + continue; + } + QStringList parts = line.split("="); + if(parts.length() != 2){ + if(!parser.isSet(quietOption)){ + O_WARNING("wrong format on " << line); + } + continue; + } + QString lhs = parts.at(0); + QString rhs = parts.at(1); + if(rhs != ":" && rhs != ""){ + if(lhs == "name"){ + properties.insert("name", rhs); + }else if(lhs == "desc"){ + properties.insert("description", rhs); + }else if(lhs == "imgFile"){ + auto icon = configDirectoryPath + "/icons/" + rhs + ".png"; + if(icon.startsWith("qrc:")){ + icon = ""; + } + properties.insert("icon", icon); + }else if(lhs == "call"){ + properties.insert("bin", rhs); + }else if(lhs == "term"){ + properties.insert("onStop", rhs.trimmed()); + } + } + } + file.close(); + auto name = properties["name"].toString(); + path = apps.getApplicationPath(name); + if(path.path() != "/"){ + LOG_VERBOSE("Already exists" << name); + auto icon = properties["icon"].toString(); + if(icon.isEmpty()){ + continue; + } + Application application(OXIDE_SERVICE, path.path(), bus); + if(application.icon().isEmpty()){ + application.setIcon(icon); + } + continue; + } + LOG_VERBOSE("Not found, creating..."); + properties.insert("displayName", name); + path = apps.registerApplication(properties); + if(path.path() == "/"){ + LOG("Failed to import" << name); + } + } + } + } + LOG_VERBOSE("Finished Importing Draft Applications"); + apps.reload().waitForFinished(); + return qExit(EXIT_SUCCESS); +} diff --git a/applications/update-desktop-database/update-desktop-database.pro b/applications/update-desktop-database/update-desktop-database.pro new file mode 100644 index 000000000..9ea92d5b4 --- /dev/null +++ b/applications/update-desktop-database/update-desktop-database.pro @@ -0,0 +1,25 @@ +QT -= gui +QT += dbus + +CONFIG += c++11 console +CONFIG -= app_bundle + +DEFINES += QT_DEPRECATED_WARNINGS +#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 + +SOURCES += \ + main.cpp + +HEADERS += + +DBUS_INTERFACES += ../../interfaces/dbusservice.xml +DBUS_INTERFACES += ../../interfaces/appsapi.xml +DBUS_INTERFACES += ../../interfaces/application.xml + +TARGET = update-desktop-database +include(../../qmake/common.pri) +target.path = /opt/bin +INSTALLS += target + +include(../../qmake/liboxide.pri) +include(../../qmake/sentry.pri) diff --git a/applications/xdg-desktop-icon/.gitignore b/applications/xdg-desktop-icon/.gitignore new file mode 100644 index 000000000..fab7372d7 --- /dev/null +++ b/applications/xdg-desktop-icon/.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/xdg-desktop-icon/main.cpp b/applications/xdg-desktop-icon/main.cpp new file mode 100644 index 000000000..0b3057932 --- /dev/null +++ b/applications/xdg-desktop-icon/main.cpp @@ -0,0 +1,132 @@ +#include + +#include +#include +#include +#include + +using namespace Oxide::Sentry; +using namespace Oxide::Applications; + +#define APPS_DIR OXIDE_APPLICATION_REGISTRATIONS_DIRECTORY + +QCommandLineOption versionOption( + {"v", "version"}, + "Displays version information." +); + +QStringList positionArguments(QCommandLineParser& parser){ + parser.process(*qApp); + if(parser.isSet(versionOption)){ + parser.showHelp(EXIT_FAILURE); + } + auto args = parser.positionalArguments(); + args.removeFirst(); + if(args.isEmpty() || args.count() > 1){ + parser.showHelp(EXIT_FAILURE); + } + return args; +} + +int install(QCommandLineParser& parser){ + parser.addPositionalArgument("install", "Install one or more application registration.", "install [options]"); + QCommandLineOption novendorOption( + "novendor", + "NOT IMPLEMENETED" + ); + parser.addOption(novendorOption); + parser.addPositionalArgument("file", "Application registration to install.", "FILE"); + + QString path = positionArguments(parser).first(); + if(!QFile::exists(path)){ + qDebug() << "error:" << path.toStdString().c_str() << "does not exist"; + return EXIT_FAILURE; + } + if(!QFile::exists(APPS_DIR) && mkdir(APPS_DIR, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)){ + qDebug() << "error: failed to created " APPS_DIR; + return EXIT_FAILURE; + } + QFileInfo info(path); + if(info.absoluteDir().path() == APPS_DIR){ + qDebug() << "error: " << path.toStdString().c_str() << "is already installed"; + return EXIT_FAILURE; + } + auto errors = validateRegistration(path); + if(std::any_of(errors.constBegin(), errors.constEnd(), [](const ValidationError& e){ + return e.level == ErrorLevel::Error || e.level == ErrorLevel::Critical; + })){ + qDebug() << "error: " << path.toStdString().c_str() << "is not valid"; + return EXIT_FAILURE; + } + auto toPath = APPS_DIR "/" + info.fileName(); + if(QFile::exists(toPath) && !QFile::remove(toPath)){ + qDebug() << "error: " << toPath.toStdString().c_str() << " already exists and can't be removed"; + return EXIT_FAILURE; + } + if(!QFile::copy(path, toPath)){ + qDebug() << "error: Failed to created" << toPath.toStdString().c_str(); + return EXIT_FAILURE; + } + qDebug() << "success: Installed" << path.toStdString().c_str(); + return QProcess::execute("update-desktop-database", QStringList("--quiet")); +} +int uninstall(QCommandLineParser& parser){ + parser.addPositionalArgument("uninstall", "Uninstall one or more application registration.", "uninstall"); + parser.addPositionalArgument("file", "Application registration to uninstall.", "FILE"); + + QString path = positionArguments(parser).first(); + if(!QFile::exists(path)){ + qDebug() << "error:" << path.toStdString().c_str() << "does not exist"; + return EXIT_FAILURE; + } + auto toPath = APPS_DIR "/" + QFileInfo(path).fileName(); + if(!QFile::exists(toPath)){ + qDebug() << "success:" << toPath.toStdString().c_str() << "doesn't exist"; + }else if(!QFile::remove(toPath)){ + qDebug() << "error: Could not remove" << toPath.toStdString().c_str(); + return EXIT_FAILURE; + }else{ + qDebug() << "success: Uninstalled" << toPath.toStdString().c_str(); + } + return QProcess::execute("update-desktop-database", QStringList("--quiet")); +} + +int main(int argc, char *argv[]){ + QCoreApplication app(argc, argv); + sentry_init("xdg-desktop-icon", argv); + app.setOrganizationName("Eeems"); + app.setOrganizationDomain(OXIDE_SERVICE); + app.setApplicationName("xdg-desktop-icon"); + app.setApplicationVersion(APP_VERSION); + QCommandLineParser parser; + parser.setApplicationDescription("A program to (un)install application registrations"); + parser.applicationDescription(); + parser.addHelpOption(); + parser.addOption(versionOption); + parser.addPositionalArgument("command", + "install Install one or more application registration.\n" + "uninstall Uninstall one or more application registration.\n" + ); + + parser.setOptionsAfterPositionalArgumentsMode(QCommandLineParser::ParseAsPositionalArguments); + parser.parse(app.arguments()); + parser.setOptionsAfterPositionalArgumentsMode(QCommandLineParser::ParseAsOptions); + QStringList args = parser.positionalArguments(); + if (args.isEmpty()) { + parser.showHelp(EXIT_FAILURE); + } + if(parser.isSet(versionOption)){ + parser.showVersion(); + } + + auto command = args.first(); + if(command == "install"){ + parser.clearPositionalArguments(); + return install(parser); + } + if(command == "uninstall"){ + parser.clearPositionalArguments(); + return uninstall(parser); + } + parser.showHelp(EXIT_FAILURE); +} diff --git a/applications/xdg-desktop-icon/xdg-desktop-icon.pro b/applications/xdg-desktop-icon/xdg-desktop-icon.pro new file mode 100644 index 000000000..c3ef13f85 --- /dev/null +++ b/applications/xdg-desktop-icon/xdg-desktop-icon.pro @@ -0,0 +1,21 @@ +QT -= gui +QT += dbus + +CONFIG += c++11 console +CONFIG -= app_bundle + +DEFINES += QT_DEPRECATED_WARNINGS +#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 + +SOURCES += \ + main.cpp + +HEADERS += + +TARGET = xdg-desktop-icon +include(../../qmake/common.pri) +target.path = /opt/bin +INSTALLS += target + +include(../../qmake/liboxide.pri) +include(../../qmake/sentry.pri) diff --git a/applications/xdg-desktop-menu/.gitignore b/applications/xdg-desktop-menu/.gitignore new file mode 100644 index 000000000..fab7372d7 --- /dev/null +++ b/applications/xdg-desktop-menu/.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/xdg-desktop-menu/main.cpp b/applications/xdg-desktop-menu/main.cpp new file mode 100644 index 000000000..aa39268be --- /dev/null +++ b/applications/xdg-desktop-menu/main.cpp @@ -0,0 +1,203 @@ +#include + +#include +#include +#include +#include + +using namespace Oxide::Sentry; +using namespace Oxide::Applications; + +#define APPS_DIR OXIDE_APPLICATION_REGISTRATIONS_DIRECTORY + +QCommandLineOption versionOption( + {"v", "version"}, + "Displays version information." +); + +QStringList positionArguments(QCommandLineParser& parser, bool allowEmpty = false){ + parser.process(*qApp); + if(parser.isSet(versionOption)){ + parser.showHelp(EXIT_FAILURE); + } + auto args = parser.positionalArguments(); + args.removeFirst(); + if(!allowEmpty && args.isEmpty()){ + parser.showHelp(EXIT_FAILURE); + } + return args; +} + +int install(QCommandLineParser& parser){ + parser.addPositionalArgument("install", "Install one or more application registration.", "install [options]"); + QCommandLineOption noupdateOption( + "noupdate", + "Do not update the application cache." + ); + parser.addOption(noupdateOption); + QCommandLineOption novendorOption( + "novendor", + "NOT IMPLEMENETED" + ); + parser.addOption(novendorOption); + QCommandLineOption modeOption( + "mode", + "NOT IMPLEMENETED", + "mode" + ); + parser.addOption(modeOption); + parser.addPositionalArgument("files", "Application registration(s) to install.", "directory-file(s) desktop-file(s)"); + + auto args = positionArguments(parser); + if(!QFile::exists(APPS_DIR) && mkdir(APPS_DIR, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)){ + qDebug() << "error: failed to created " APPS_DIR; + return EXIT_FAILURE; + } + bool failure = false; + bool updated = false; + for(QString path : args){ + if(!QFile::exists(path)){ + qDebug() << "error:" << path.toStdString().c_str() << "does not exist"; + failure = true; + continue; + } + QFileInfo info(path); + if(info.absoluteDir().path() == APPS_DIR){ + qDebug() << "error: " << path.toStdString().c_str() << "is already installed"; + failure = true; + continue; + } + auto errors = validateRegistration(path); + if(std::any_of(errors.constBegin(), errors.constEnd(), [](const ValidationError& e){ + return e.level == ErrorLevel::Error || e.level == ErrorLevel::Critical; + })){ + qDebug() << "error: " << path.toStdString().c_str() << "is not valid"; + failure = true; + continue; + } + auto toPath = APPS_DIR "/" + info.fileName(); + if(QFile::exists(toPath) && !QFile::remove(toPath)){ + failure = true; + updated = true; + qDebug() << "error: " << toPath.toStdString().c_str() << " already exists and can't be removed"; + continue; + } + if(!QFile::copy(path, toPath)){ + qDebug() << "error: Failed to created" << toPath.toStdString().c_str(); + failure = true; + continue; + } + qDebug() << "success: Installed" << path.toStdString().c_str(); + updated = true; + } + + if(!updated || parser.isSet(noupdateOption)){ + return failure ? EXIT_FAILURE : EXIT_SUCCESS; + } + int res = QProcess::execute("update-desktop-database", QStringList("--quiet")); + return failure ? EXIT_FAILURE : res; +} +int uninstall(QCommandLineParser& parser){ + parser.addPositionalArgument("uninstall", "Uninstall one or more application registration.", "uninstall [options]"); + QCommandLineOption noupdateOption( + "noupdate", + "Do not update the application cache." + ); + parser.addOption(noupdateOption); + QCommandLineOption modeOption( + "mode", + "NOT IMPLEMENETED", + "mode" + ); + parser.addOption(modeOption); + parser.addPositionalArgument("files", "Application registration(s) to install.", "directory-file(s) desktop-file(s)"); + + auto args = positionArguments(parser); + bool failure = false; + bool updated = false; + for(QString path : args){ + if(!QFile::exists(path)){ + qDebug() << "error:" << path.toStdString().c_str() << "does not exist"; + failure = true; + continue; + } + auto toPath = APPS_DIR "/" + QFileInfo(path).fileName(); + if(!QFile::exists(toPath)){ + qDebug() << "success:" << toPath.toStdString().c_str() << "doesn't exist"; + continue; + } + if(!QFile::remove(toPath)){ + failure = true; + qDebug() << "error: Could not remove" << toPath.toStdString().c_str(); + continue; + } + qDebug() << "success: Uninstalled" << toPath.toStdString().c_str(); + updated = true; + } + if(!updated || parser.isSet(noupdateOption)){ + return failure ? EXIT_FAILURE : EXIT_SUCCESS; + } + int res = QProcess::execute("update-desktop-database", QStringList("--quiet")); + return failure ? EXIT_FAILURE : res; +} + +int forceupdate(QCommandLineParser& parser){ + parser.addPositionalArgument("forceupdate", "Force an update of the application registration cache.", "forceupdate [options]"); + QCommandLineOption modeOption( + "mode", + "NOT IMPLEMENETED", + "mode" + ); + parser.addOption(modeOption); + + if(!positionArguments(parser, true).isEmpty()){ + parser.showHelp(); + } + return QProcess::execute("update-desktop-database", QStringList()); +} + +int main(int argc, char *argv[]){ + QCoreApplication app(argc, argv); + sentry_init("xdg-desktop-menu", argv); + app.setOrganizationName("Eeems"); + app.setOrganizationDomain(OXIDE_SERVICE); + app.setApplicationName("xdg-desktop-menu"); + app.setApplicationVersion(APP_VERSION); + QCommandLineParser parser; + parser.setApplicationDescription("A program to (un)install application registrations"); + parser.applicationDescription(); + parser.addHelpOption(); + parser.addOption(versionOption); + parser.addPositionalArgument("command", + "install Install one or more application registration.\n" + "uninstall Uninstall one or more application registration.\n" + "forceupdate Force an update of the application registration \n" + " cache.\n" + ); + + parser.setOptionsAfterPositionalArgumentsMode(QCommandLineParser::ParseAsPositionalArguments); + parser.parse(app.arguments()); + parser.setOptionsAfterPositionalArgumentsMode(QCommandLineParser::ParseAsOptions); + QStringList args = parser.positionalArguments(); + if (args.isEmpty()) { + parser.showHelp(EXIT_FAILURE); + } + if(parser.isSet(versionOption)){ + parser.showVersion(); + } + + auto command = args.first(); + if(command == "install"){ + parser.clearPositionalArguments(); + return install(parser); + } + if(command == "uninstall"){ + parser.clearPositionalArguments(); + return uninstall(parser); + } + if(command == "forceupdate"){ + parser.clearPositionalArguments(); + return forceupdate(parser); + } + parser.showHelp(EXIT_FAILURE); +} diff --git a/applications/xdg-desktop-menu/xdg-desktop-menu.pro b/applications/xdg-desktop-menu/xdg-desktop-menu.pro new file mode 100644 index 000000000..34aecfd97 --- /dev/null +++ b/applications/xdg-desktop-menu/xdg-desktop-menu.pro @@ -0,0 +1,21 @@ +QT -= gui +QT += dbus + +CONFIG += c++11 console +CONFIG -= app_bundle + +DEFINES += QT_DEPRECATED_WARNINGS +#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 + +SOURCES += \ + main.cpp + +HEADERS += + +TARGET = xdg-desktop-menu +include(../../qmake/common.pri) +target.path = /opt/bin +INSTALLS += target + +include(../../qmake/liboxide.pri) +include(../../qmake/sentry.pri) diff --git a/applications/xdg-icon-resource/.gitignore b/applications/xdg-icon-resource/.gitignore new file mode 100644 index 000000000..fab7372d7 --- /dev/null +++ b/applications/xdg-icon-resource/.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/xdg-icon-resource/main.cpp b/applications/xdg-icon-resource/main.cpp new file mode 100644 index 000000000..f4bb59728 --- /dev/null +++ b/applications/xdg-icon-resource/main.cpp @@ -0,0 +1,203 @@ +#include + +#include +#include +#include +#include + +using namespace Oxide::Sentry; +using namespace Oxide::Applications; + +#define ICON_DIR OXIDE_ICONS_DIRECTORY + +QCommandLineOption versionOption( + {"v", "version"}, + "Displays version information." +); + +QCommandLineOption themeOption( + "theme", + "Installs or removes the icon file as part of theme. If no theme is specified the icons will be installed as part of the default hicolor theme. Applications may install icons under multiple themes but should at least install icons for the default hicolor theme.", + "theme", + "hicolor" +); + +QCommandLineOption modeOption( + "mode", + "NOT IMPLEMENETED", + "mode" +); +QCommandLineOption noupdateOption( + "noupdate", + "Do not update the application cache." +); +QCommandLineOption sizeOption( + "size", + "Specifies the size of the icon. All icons must be square. Common sizes for icons in the apps context are: 16, 22, 32, 48, 64 and 128. Common sizes for icons in the mimetypes context are: 16, 22, 32, 48, 64 and 128", + "size" +); +QCommandLineOption contextOption( + "context", + "Specifies the context for the icon. Icons to be used in the application menu and as desktop icon should use apps as context which is the default context. Icons to be used as file icons should use mimetypes as context. Other common contexts are actions, devices, emblems, filesystems and stock.", + "context", + "apps" +); +QCommandLineOption novendorOption( + "novendor", + "NOT IMPLEMENETED" +); + +QStringList positionArguments(QCommandLineParser& parser, bool allowEmpty = false){ + parser.process(*qApp); + if(parser.isSet(versionOption)){ + parser.showHelp(EXIT_FAILURE); + } + auto args = parser.positionalArguments(); + args.removeFirst(); + if(!allowEmpty && args.isEmpty()){ + parser.showHelp(EXIT_FAILURE); + } + return args; +} +QString iconDir(QCommandLineParser& parser){ return Oxide::Applications::iconDirPath( + parser.value(sizeOption).toUInt(), + parser.value(themeOption), + parser.value(contextOption) +); } +QString iconFile(QCommandLineParser& parser, const QString& name){ return Oxide::Applications::iconPath( + name, + parser.value(sizeOption).toUInt(), + parser.value(themeOption), + parser.value(contextOption) +); } + +int install(QCommandLineParser& parser){ + parser.addPositionalArgument("", "", "install [options]"); + parser.addOption(noupdateOption); + parser.addOption(novendorOption); + parser.addOption(themeOption); + parser.addOption(modeOption); + parser.addOption(contextOption); + parser.addOption(sizeOption); + parser.addPositionalArgument("icon-file", "Icon file to install."); + parser.addPositionalArgument("icon-name", "Name to use when installing.", "[icon-name]"); + + auto args = positionArguments(parser); + if(args.length() > 2 || !parser.isSet(sizeOption)){ + parser.showHelp(EXIT_FAILURE); + } + + auto fromPath = args.first(); + if(!QFile::exists(fromPath)){ + qDebug() << "error: file does not exist" << fromPath; + return EXIT_FAILURE; + } + + auto dirPath = iconDir(parser); + QDir dir(dirPath); + if(!dir.exists() && !dir.mkpath(".")){ + qDebug() << "error: failed to created directory" << dirPath; + return EXIT_FAILURE; + } + + QString name = args.length() == 2 ? args.last() : QFileInfo(fromPath).baseName(); + auto toPath = iconFile(parser, name); + if(!QFile::copy(fromPath, toPath)){ + qDebug() << "error: failed to copy file to" << toPath; + return EXIT_FAILURE; + } + + if(parser.isSet(noupdateOption)){ + return EXIT_SUCCESS; + } + return QProcess::execute("update-desktop-database", QStringList("--quiet")); +} +int uninstall(QCommandLineParser& parser){ + parser.addPositionalArgument("", "", "uninstall [options]"); + parser.addOption(noupdateOption); + parser.addOption(themeOption); + parser.addOption(modeOption); + parser.addOption(contextOption); + parser.addOption(sizeOption); + parser.addPositionalArgument("icon-name", "Name to use when installing.", "[icon-name]"); + + auto args = positionArguments(parser); + if(args.length() > 1 || !parser.isSet(sizeOption)){ + parser.showHelp(EXIT_FAILURE); + } + + auto path = iconFile(parser, args.last()); + if(QFile::exists(path) && !QFile::remove(path)){ + qDebug() << "error: failed to delete" << path; + return EXIT_FAILURE; + } + + if(parser.isSet(noupdateOption)){ + return EXIT_SUCCESS; + } + return QProcess::execute("update-desktop-database", QStringList("--quiet")); +} + +int forceupdate(QCommandLineParser& parser){ + parser.addPositionalArgument("", "", "forceupdate [options]"); + parser.addOption(themeOption); + parser.addOption(modeOption); + + if(!positionArguments(parser, true).isEmpty()){ + parser.showHelp(); + } + return QProcess::execute("update-desktop-database", QStringList()); +} + +int main(int argc, char *argv[]){ + QCoreApplication app(argc, argv); + sentry_init("xdg-icon-resource", argv); + app.setOrganizationName("Eeems"); + app.setOrganizationDomain(OXIDE_SERVICE); + app.setApplicationName("xdg-icon-resource"); + app.setApplicationVersion(APP_VERSION); + QCommandLineParser parser; + parser.setApplicationDescription("Command line tool for (un)installing icon resources."); + parser.applicationDescription(); + parser.addHelpOption(); + parser.addOption(versionOption); + parser.addPositionalArgument("command", + "install Installs the icon file indicated by icon-file to\n" + " the desktop icon system under the name icon-name.\n" + " Icon names do not have an extension. If icon-name\n" + " is not provided the name is derived from\n" + " icon-file.\n" + "uninstall Removes the icon indicated by icon-name from the\n" + " desktop icon system. Note that icon names do not\n" + " have an extension.\n" + "forceupdate Force an update of the desktop icon system. This\n" + " is only useful if the last call to\n" + " xdg-icon-resource included the --noupdate option.\n" + ); + + parser.setOptionsAfterPositionalArgumentsMode(QCommandLineParser::ParseAsPositionalArguments); + parser.parse(app.arguments()); + parser.setOptionsAfterPositionalArgumentsMode(QCommandLineParser::ParseAsOptions); + QStringList args = parser.positionalArguments(); + if (args.isEmpty()) { + parser.showHelp(EXIT_FAILURE); + } + if(parser.isSet(versionOption)){ + parser.showVersion(); + } + + auto command = args.first(); + if(command == "install"){ + parser.clearPositionalArguments(); + return install(parser); + } + if(command == "uninstall"){ + parser.clearPositionalArguments(); + return uninstall(parser); + } + if(command == "forceupdate"){ + parser.clearPositionalArguments(); + return forceupdate(parser); + } + parser.showHelp(EXIT_FAILURE); +} diff --git a/applications/xdg-icon-resource/xdg-icon-resource.pro b/applications/xdg-icon-resource/xdg-icon-resource.pro new file mode 100644 index 000000000..b8eb63f8e --- /dev/null +++ b/applications/xdg-icon-resource/xdg-icon-resource.pro @@ -0,0 +1,21 @@ +QT -= gui +QT += dbus + +CONFIG += c++11 console +CONFIG -= app_bundle + +DEFINES += QT_DEPRECATED_WARNINGS +#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 + +SOURCES += \ + main.cpp + +HEADERS += + +TARGET = xdg-icon-resource +include(../../qmake/common.pri) +target.path = /opt/bin +INSTALLS += target + +include(../../qmake/liboxide.pri) +include(../../qmake/sentry.pri) diff --git a/applications/xdg-open/.gitignore b/applications/xdg-open/.gitignore new file mode 100644 index 000000000..fab7372d7 --- /dev/null +++ b/applications/xdg-open/.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/xdg-open/main.cpp b/applications/xdg-open/main.cpp new file mode 100644 index 000000000..4da520f29 --- /dev/null +++ b/applications/xdg-open/main.cpp @@ -0,0 +1,76 @@ +#include + +#include +#include +#include +#include + +using namespace Oxide::Sentry; +using namespace Oxide::Applications; +using namespace codes::eeems::oxide1; + +int launchOxideApp(const QString& name){ + auto bus = QDBusConnection::systemBus(); + General api(OXIDE_SERVICE, OXIDE_SERVICE_PATH, bus); + QDBusObjectPath path = api.requestAPI("apps"); + if(path.path() == "/"){ + qDebug() << "Unable to get apps API"; + return EXIT_FAILURE; + } + Apps apps(OXIDE_SERVICE, path.path(), bus); + path = apps.getApplicationPath(name); + if(path.path() == "/"){ + qDebug() << "Application does not exist:" << name.toStdString().c_str(); + return EXIT_FAILURE; + } + Application app(OXIDE_SERVICE, path.path(), bus); + app.launch().waitForFinished(); + return EXIT_SUCCESS; +} + +int main(int argc, char *argv[]){ + QCoreApplication app(argc, argv); + sentry_init("xdg-open", argv); + app.setOrganizationName("Eeems"); + app.setOrganizationDomain(OXIDE_SERVICE); + app.setApplicationName("xdg-open"); + app.setApplicationVersion(APP_VERSION); + QCommandLineParser parser; + parser.setApplicationDescription("Open a file or URL."); + parser.applicationDescription(); + parser.addHelpOption(); + parser.addVersionOption(); + parser.process(app); + QStringList args = parser.positionalArguments(); + if (args.isEmpty() || args.length() > 1) { + parser.showHelp(EXIT_FAILURE); + } + auto path = args.first(); + auto url = QUrl::fromUserInput(path, QDir::currentPath(), QUrl::AssumeLocalFile); + if(url.scheme().isEmpty()){ + url.setScheme("file"); + } + if(url.isLocalFile()){ + QFileInfo info(url.path()); + if(info.suffix() != "oxide"){ + qDebug() << "The extension is not supported:" << path.toStdString().c_str(); + return EXIT_FAILURE; + } + if(info.absoluteDir().path() != OXIDE_APPLICATION_REGISTRATIONS_DIRECTORY){ + qDebug() << "The registration file must be in " OXIDE_APPLICATION_REGISTRATIONS_DIRECTORY ":" << path.toStdString().c_str(); + return EXIT_FAILURE; + } + // Workaround because basename will sometimes return the suffix instead + auto name = info.fileName().left(info.fileName().length() - 6); + return launchOxideApp(name); + }else if(url.scheme() == "oxide"){ + if(url.hasFragment() || url.hasQuery() || !url.userInfo().isEmpty() || !url.authority().isEmpty()){ + qDebug() << "Url must bein the format oxide://{appname} :" << path.toStdString().c_str(); + return EXIT_FAILURE; + } + auto name = url.path(); + return launchOxideApp(name); + } + qDebug() << "Operation not supported:" << path.toStdString().c_str(); + return EXIT_FAILURE; +} diff --git a/applications/xdg-open/xdg-open.pro b/applications/xdg-open/xdg-open.pro new file mode 100644 index 000000000..fdcccb524 --- /dev/null +++ b/applications/xdg-open/xdg-open.pro @@ -0,0 +1,21 @@ +QT -= gui +QT += dbus + +CONFIG += c++11 console +CONFIG -= app_bundle + +DEFINES += QT_DEPRECATED_WARNINGS +#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 + +SOURCES += \ + main.cpp + +HEADERS += + +TARGET = xdg-open +include(../../qmake/common.pri) +target.path = /opt/bin +INSTALLS += target + +include(../../qmake/liboxide.pri) +include(../../qmake/sentry.pri) diff --git a/applications/xdg-settings/.gitignore b/applications/xdg-settings/.gitignore new file mode 100644 index 000000000..fab7372d7 --- /dev/null +++ b/applications/xdg-settings/.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/xdg-settings/main.cpp b/applications/xdg-settings/main.cpp new file mode 100644 index 000000000..7755558f6 --- /dev/null +++ b/applications/xdg-settings/main.cpp @@ -0,0 +1,285 @@ +#include + +#include +#include +#include +#include + +using namespace codes::eeems::oxide1; +using namespace Oxide::Sentry; +using namespace Oxide::JSON; + +const QSet ApiNames = QSet{ + "general", + "power", + "wifi", + "apps", + "system", + "screen", + "notification", +}; + +QTextStream& qStdOut(){ + static QTextStream ts( stdout ); + return ts; +} + +QObject* getApi(const QString& name){ + if(!ApiNames.contains(name)){ + return nullptr; + } + auto bus = QDBusConnection::systemBus(); + if(!bus.isConnected()){ + qDebug() << "xdg-settings: Failed to connect to DBus"; + return nullptr; + } + if(name == "general"){ + return new General(OXIDE_SERVICE, OXIDE_SERVICE_PATH, bus); + } + General generalApi(OXIDE_SERVICE, OXIDE_SERVICE_PATH, bus); + auto reply = generalApi.requestAPI(name); + reply.waitForFinished(); + if(reply.isError()){ + qDebug() << "xdg-settings: Failed to connect to general API"; + return nullptr; + } + auto path = ((QDBusObjectPath)reply).path(); + if(path == "/"){ + qDebug() << "xdg-settings: Failed to get API path from tarnish"; + return nullptr; + } + if(name == "power"){ + return new Power(OXIDE_SERVICE, path, bus); + } + if(name == "wifi"){ + return new Wifi(OXIDE_SERVICE, path, bus); + } + if(name == "apps"){ + return new Apps(OXIDE_SERVICE, path, bus); + } + if(name == "system"){ + return new System(OXIDE_SERVICE, path, bus); + } + if(name == "screen"){ + return new Screen(OXIDE_SERVICE, path, bus); + } + if(name == "notification"){ + return new Notifications(OXIDE_SERVICE, path, bus); + } + qDebug() << "xdg-settings: Unknown API" << name; + return nullptr; +} + +bool isValidProperty(QObject* obj, const QString& name){ + auto meta = obj->metaObject(); + for(int i = meta->propertyOffset(); i < meta->propertyCount(); ++i){ + auto property = meta->property(i); + if(property.name() == name){ + return property.isReadable() && property.isWritable(); + } + } + return false; +} + +QList getProperties(QObject* obj){ + QList res; + auto meta = obj->metaObject(); + for(int i = meta->propertyOffset(); i < meta->propertyCount(); ++i){ + auto property = meta->property(i); + auto name = property.name(); + if(!QString(name).startsWith("__META_GROUP_") && property.isReadable() && property.isWritable()){ + res.append(name); + } + } + return res; +} + +QStringList positionArguments(QCommandLineParser& parser, int mandatoryCount, int maxCount){ + parser.process(*qApp); + auto args = parser.positionalArguments(); + args.removeFirst(); + auto len = args.length(); + parser.parse(args); + if(len < mandatoryCount || len > maxCount){ + parser.showHelp(EXIT_FAILURE); + } + return args; +} + +QObject* getObj(QStringList* args, int isGet = false){ + switch(args->length()){ + case 2: + if(!isGet){ + return &sharedSettings; + } + break; + case 1: + return &sharedSettings; + case 3: + default: + break; + } + auto name = args->first(); + QObject* obj = getApi(name); + args->removeFirst(); + return obj; +} + +int get(QCommandLineParser& parser){ + parser.clearPositionalArguments(); + parser.addPositionalArgument("", "", "get"); + parser.addPositionalArgument("property", "Property to get", "{property}"); + parser.addPositionalArgument("subproperty", "Subproperty to get", "[subproperty]"); + auto args = positionArguments(parser, 1, 2); + + QObject* obj = getObj(&args, true); + if(obj == nullptr){ + parser.showHelp(EXIT_FAILURE); + } + auto name = args.first(); + if(!isValidProperty(obj, name)){ + qDebug() << "xdg-settings: Unknown property" << name; + qDebug() << args; + parser.showHelp(EXIT_FAILURE); + } + QVariant value = obj->property(name.toStdString().c_str()); + if(!value.isValid()){ + qDebug() << "xdg-settings: Failed to get property"; + parser.showHelp(EXIT_FAILURE); + } + qStdOut() << toJson(value).toStdString().c_str() << Qt::endl; + return EXIT_SUCCESS; +} + +int check(QCommandLineParser& parser){ + parser.clearPositionalArguments(); + parser.addPositionalArgument("", "", "check"); + parser.addPositionalArgument("property", "Property to check", "{property}"); + parser.addPositionalArgument("subproperty", "Subproperty to check", "[subproperty]"); + parser.addPositionalArgument("value", "Value to check if the property/subproperty is", "value"); + auto args = positionArguments(parser, 2, 3); + + QObject* obj = getObj(&args); + if(obj == nullptr){ + parser.showHelp(EXIT_FAILURE); + } + auto name = args.first(); + if(!isValidProperty(obj, name)){ + qDebug() << "xdg-settings: Unknown property" << name; + parser.showHelp(EXIT_FAILURE); + } + QVariant value = obj->property(name.toStdString().c_str()); + if(!value.isValid()){ + qDebug() << "xdg-settings: Failed to get property"; + parser.showHelp(EXIT_FAILURE); + } + qStdOut() << (args.last() == value ? "yes" : "no") << Qt::endl; + return EXIT_SUCCESS; +} + +int set(QCommandLineParser& parser){ + parser.clearPositionalArguments(); + parser.addPositionalArgument("", "", "set"); + parser.addPositionalArgument("property", "Property to set", "{property}"); + parser.addPositionalArgument("subproperty", "Subproperty to set", "[subproperty]"); + parser.addPositionalArgument("value", "Value to set the property/subproperty to", "value"); + auto args = positionArguments(parser, 2, 3); + + QObject* obj = getObj(&args); + if(obj == nullptr){ + parser.showHelp(EXIT_FAILURE); + } + auto name = args.first(); + if(!isValidProperty(obj, name)){ + qDebug() << "xdg-settings: Unknown property" << name; + parser.showHelp(EXIT_FAILURE); + } + QVariant value = obj->property(name.toStdString().c_str()); + if(!value.isValid()){ + qDebug() << "xdg-settings: Failed to get property"; + parser.showHelp(EXIT_FAILURE); + } + if(obj->setProperty(name.toStdString().c_str(), args.last())){ + return EXIT_SUCCESS; + } + QDBusAbstractInterface* api = qobject_cast(obj); + if(api == nullptr){ + qDebug() << "xdg-settings: failed to set property"; + }else{ + qDebug() << "xdg-settings:" << api->lastError(); + } + return EXIT_FAILURE; +} + +int list(QCommandLineParser& parser){ + if(!parser.positionalArguments().isEmpty()){ + parser.showHelp(EXIT_FAILURE); + } + qStdOut() << "Known properties:" << Qt::endl; + for(auto name : getProperties(&sharedSettings)){ + qStdOut() << " " << name << Qt::endl; + } + for(auto name : ApiNames){ + auto api = getApi(name); + if(api == nullptr){ + return EXIT_FAILURE; + } + auto props = getProperties(api); + if(!props.isEmpty()){ + qStdOut() << " " << name << Qt::endl; + for(auto prop : props){ + qStdOut() << " " << prop << Qt::endl; + } + } + delete api; + } + return EXIT_SUCCESS; +} + +int main(int argc, char *argv[]){ + QCoreApplication app(argc, argv); + sentry_init("xdg-settings", argv); + app.setOrganizationName("Eeems"); + app.setOrganizationDomain(OXIDE_SERVICE); + app.setApplicationName("xdg-settings"); + app.setApplicationVersion(APP_VERSION); + QCommandLineParser parser; + parser.setApplicationDescription("Get various settings from the desktop environment"); + parser.applicationDescription(); + parser.addHelpOption(); + parser.addVersionOption(); + QCommandLineOption listOption("list", "List all properties xdg-settings knows about."); + parser.addOption(listOption); + parser.addPositionalArgument("Commands:", + "get Get the value of a setting.\n" + "check Check to see if a setting is a specific value.\n" + "set Set the value of a setting.\n" + ); + + parser.setOptionsAfterPositionalArgumentsMode(QCommandLineParser::ParseAsPositionalArguments); + parser.parse(app.arguments()); + parser.setOptionsAfterPositionalArgumentsMode(QCommandLineParser::ParseAsOptions); + QStringList args = parser.positionalArguments(); + if(parser.isSet(listOption)){ + return list(parser); + } + if (args.isEmpty()) { + parser.showHelp(EXIT_FAILURE); + } + + + auto command = args.first(); + if(command == "get"){ + parser.clearPositionalArguments(); + return get(parser); + } + if(command == "check"){ + parser.clearPositionalArguments(); + return check(parser); + } + if(command == "set"){ + parser.clearPositionalArguments(); + return set(parser); + } + parser.showHelp(EXIT_FAILURE); +} diff --git a/applications/xdg-settings/xdg-settings.pro b/applications/xdg-settings/xdg-settings.pro new file mode 100644 index 000000000..9d0f2c1bc --- /dev/null +++ b/applications/xdg-settings/xdg-settings.pro @@ -0,0 +1,21 @@ +QT -= gui +QT += dbus + +CONFIG += c++11 console +CONFIG -= app_bundle + +DEFINES += QT_DEPRECATED_WARNINGS +#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 + +SOURCES += \ + main.cpp + +HEADERS += + +TARGET = xdg-settings +include(../../qmake/common.pri) +target.path = /opt/bin +INSTALLS += target + +include(../../qmake/liboxide.pri) +include(../../qmake/sentry.pri) diff --git a/assets/etc/draft/icons/anxiety-splash.png b/assets/etc/draft/icons/anxiety-splash.png deleted file mode 100644 index 2b729896a..000000000 Binary files a/assets/etc/draft/icons/anxiety-splash.png and /dev/null differ diff --git a/assets/etc/draft/icons/erode-splash.png b/assets/etc/draft/icons/erode-splash.png deleted file mode 100644 index 302cfe10e..000000000 Binary files a/assets/etc/draft/icons/erode-splash.png and /dev/null differ diff --git a/assets/etc/draft/icons/erode.svg b/assets/etc/draft/icons/erode.svg deleted file mode 100644 index e0f0ffc11..000000000 --- a/assets/etc/draft/icons/erode.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/assets/etc/draft/icons/image.svg b/assets/etc/draft/icons/image.svg deleted file mode 100644 index 410aa8d43..000000000 --- a/assets/etc/draft/icons/image.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/assets/etc/draft/icons/oxide-splash.png b/assets/etc/draft/icons/oxide-splash.png deleted file mode 100644 index 931bc7045..000000000 Binary files a/assets/etc/draft/icons/oxide-splash.png and /dev/null differ diff --git a/assets/etc/systemd/system/tarnish.service b/assets/etc/systemd/system/tarnish.service index 78d72cb85..2889afeb2 100644 --- a/assets/etc/systemd/system/tarnish.service +++ b/assets/etc/systemd/system/tarnish.service @@ -11,7 +11,7 @@ Conflicts=sync.service [Service] Type=dbus BusName=codes.eeems.oxide1 -ExecStart=/opt/bin/tarnish +ExecStart=/opt/bin/tarnish --break-lock Restart=on-failure RestartSec=5 Environment="HOME=/home/root" diff --git a/assets/opt/usr/share/applications/codes.eeems.anxiety.oxide b/assets/opt/usr/share/applications/codes.eeems.anxiety.oxide index 4a253abdd..050aad4d8 100644 --- a/assets/opt/usr/share/applications/codes.eeems.anxiety.oxide +++ b/assets/opt/usr/share/applications/codes.eeems.anxiety.oxide @@ -2,8 +2,8 @@ "displayName": "Screenshots", "description": "View and manage screenshots", "bin": "/opt/bin/anxiety", - "icon": "/opt/etc/draft/icons/image.svg", - "splash": "/opt/etc/draft/icons/anxiety-splash.png", + "icon": "oxide:image-48", + "splash": "oxide:splash:anxiety-702", "flags": [], "type": "foreground", "permissions": ["screen"] diff --git a/assets/opt/usr/share/applications/codes.eeems.decay.oxide b/assets/opt/usr/share/applications/codes.eeems.decay.oxide index 5eecd80df..38ccdffb1 100644 --- a/assets/opt/usr/share/applications/codes.eeems.decay.oxide +++ b/assets/opt/usr/share/applications/codes.eeems.decay.oxide @@ -4,6 +4,6 @@ "bin": "/opt/bin/decay", "flags": ["hidden"], "type": "foreground", - "splash": "/opt/etc/draft/icons/oxide-splash.png", + "splash": "oxide:splash:oxide-702", "permissions": ["apps", "power", "system", "wifi"] } diff --git a/assets/opt/usr/share/applications/codes.eeems.erode.oxide b/assets/opt/usr/share/applications/codes.eeems.erode.oxide index 408b250c3..25b459b2a 100644 --- a/assets/opt/usr/share/applications/codes.eeems.erode.oxide +++ b/assets/opt/usr/share/applications/codes.eeems.erode.oxide @@ -1,7 +1,7 @@ { "displayName": "Process Manager", "description": "List and kill running processes", - "icon": "/opt/etc/draft/icons/erode.svg", - "splash": "/opt/etc/draft/icons/erode-splash.png", + "icon": "oxide:erode-48", + "splash": "oxide:splash:erode-702", "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 66df10e10..0d8710f2c 100644 --- a/assets/opt/usr/share/applications/codes.eeems.fret.oxide +++ b/assets/opt/usr/share/applications/codes.eeems.fret.oxide @@ -4,6 +4,6 @@ "bin": "/opt/bin/fret", "flags": ["autoStart", "hidden"], "type": "background", - "splash": "/opt/etc/draft/icons/oxide-splash.png", + "splash": "oxide:splash:oxide-702", "permissions": ["notification", "screen", "system"] } diff --git a/assets/opt/usr/share/applications/codes.eeems.oxide.oxide b/assets/opt/usr/share/applications/codes.eeems.oxide.oxide index c2ea43023..46b761778 100644 --- a/assets/opt/usr/share/applications/codes.eeems.oxide.oxide +++ b/assets/opt/usr/share/applications/codes.eeems.oxide.oxide @@ -4,6 +4,6 @@ "bin": "/opt/bin/oxide", "type": "foreground", "flags": ["hidden"], - "splash": "/opt/etc/draft/icons/oxide-splash.png", + "splash": "oxide:splash:oxide-702", "permissions": ["apps", "wifi", "power", "system", "notification"] } diff --git a/assets/opt/usr/share/applications/xochitl.oxide b/assets/opt/usr/share/applications/xochitl.oxide index a397f1c8e..046354577 100644 --- a/assets/opt/usr/share/applications/xochitl.oxide +++ b/assets/opt/usr/share/applications/xochitl.oxide @@ -2,7 +2,7 @@ "displayName": "Xochitl", "description": "Read documents and take notes", "bin": "/opt/bin/xochitl", - "icon": "/opt/etc/draft/icons/xochitl.png", + "icon": "oxide:xochitl-48", "flags": ["nosplash", "chroot"], "permissions": ["power"], "directories": [ diff --git a/assets/opt/usr/share/icons/oxide/48x48/apps/erode.png b/assets/opt/usr/share/icons/oxide/48x48/apps/erode.png new file mode 100644 index 000000000..f1b4bd60d Binary files /dev/null and b/assets/opt/usr/share/icons/oxide/48x48/apps/erode.png differ diff --git a/assets/opt/usr/share/icons/oxide/48x48/apps/image.png b/assets/opt/usr/share/icons/oxide/48x48/apps/image.png new file mode 100644 index 000000000..f14817739 Binary files /dev/null and b/assets/opt/usr/share/icons/oxide/48x48/apps/image.png differ diff --git a/assets/etc/draft/icons/xochitl.png b/assets/opt/usr/share/icons/oxide/48x48/apps/xochitl.png similarity index 100% rename from assets/etc/draft/icons/xochitl.png rename to assets/opt/usr/share/icons/oxide/48x48/apps/xochitl.png diff --git a/assets/opt/usr/share/icons/oxide/702x702/splash/anxiety.png b/assets/opt/usr/share/icons/oxide/702x702/splash/anxiety.png new file mode 100644 index 000000000..fb2ba07ef Binary files /dev/null and b/assets/opt/usr/share/icons/oxide/702x702/splash/anxiety.png differ diff --git a/assets/opt/usr/share/icons/oxide/702x702/splash/erode.png b/assets/opt/usr/share/icons/oxide/702x702/splash/erode.png new file mode 100644 index 000000000..966906e2c Binary files /dev/null and b/assets/opt/usr/share/icons/oxide/702x702/splash/erode.png differ diff --git a/assets/opt/usr/share/icons/oxide/702x702/splash/oxide.png b/assets/opt/usr/share/icons/oxide/702x702/splash/oxide.png new file mode 100644 index 000000000..990321f19 Binary files /dev/null and b/assets/opt/usr/share/icons/oxide/702x702/splash/oxide.png differ diff --git a/interfaces/application.xml b/interfaces/application.xml index bebe68885..1192d0145 100644 --- a/interfaces/application.xml +++ b/interfaces/application.xml @@ -15,6 +15,7 @@ + diff --git a/nix/sources.json b/nix/sources.json index 084bdeb6c..be881ad27 100644 --- a/nix/sources.json +++ b/nix/sources.json @@ -5,10 +5,10 @@ "homepage": "https://github.com/nmattia/niv", "owner": "nmattia", "repo": "niv", - "rev": "ba57d5a29b4e0f2085917010380ef3ddc3cf380f", - "sha256": "1kpsvc53x821cmjg1khvp1nz7906gczq8mp83664cr15h94sh8i4", + "rev": "5830a4dd348d77e39a0f3c4c762ff2663b602d4c", + "sha256": "1d3lsrqvci4qz2hwjrcnd8h5vfkg8aypq3sjd4g3izbc8frwz5sm", "type": "tarball", - "url": "https://github.com/nmattia/niv/archive/ba57d5a29b4e0f2085917010380ef3ddc3cf380f.tar.gz", + "url": "https://github.com/nmattia/niv/archive/5830a4dd348d77e39a0f3c4c762ff2663b602d4c.tar.gz", "url_template": "https://github.com///archive/.tar.gz" }, "nix-inclusive": { @@ -29,10 +29,10 @@ "homepage": "", "owner": "NixOS", "repo": "nixpkgs", - "rev": "db103e0f98d461f3e66cd68702492afca0810db5", - "sha256": "19gz926nqv7ggq281mv2qi1ah6a5slg2vvhsvc5jdnkp28m8f55k", + "rev": "77fda7f672726e1a95c8cd200f27bccfc86c870b", + "sha256": "07qj1d45pkqsmkahbhh7hilwwbvg8vlz1wg497hzjrlx1a57v4y5", "type": "tarball", - "url": "https://github.com/NixOS/nixpkgs/archive/db103e0f98d461f3e66cd68702492afca0810db5.tar.gz", + "url": "https://github.com/NixOS/nixpkgs/archive/77fda7f672726e1a95c8cd200f27bccfc86c870b.tar.gz", "url_template": "https://github.com///archive/.tar.gz" } } diff --git a/oxide.nix b/oxide.nix index 3c952ebc3..7e92756b5 100644 --- a/oxide.nix +++ b/oxide.nix @@ -1,4 +1,4 @@ -{ stdenv, fetchFromGitHub, inclusive, qt512, remarkable-toolchain }: +{ stdenv, lib, fetchFromGitHub, inclusive, qt512, remarkable-toolchain }: stdenv.mkDerivation { name = "oxide"; @@ -11,20 +11,22 @@ stdenv.mkDerivation { ./assets ./interfaces ./shared + ./oxide.pro + ./qmake ]; preBuild = '' - source ${remarkable-toolchain}/environment-setup-cortexa9hf-neon-oe-linux-gnueabi + source ${remarkable-toolchain}/environment-setup-cortexa9hf-neon-remarkable-linux-gnueabi ''; enableParallelBuilding = true; makeFlags = [ "release" ]; installPhase = '' - cp -r release/. $out + cp -a release/. $out ''; - meta = with stdenv.lib; { + meta = with lib; { description = "A launcher application for the reMarkable tablet"; platform = [ "x86_64-linux" ]; maintainers = [ maintainers.siraben ]; diff --git a/oxide.pro b/oxide.pro new file mode 100644 index 000000000..d471787cb --- /dev/null +++ b/oxide.pro @@ -0,0 +1,9 @@ +TEMPLATE = subdirs + +SUBDIRS = \ + shared \ + applications + +applications.depends = shared + +INSTALLS += $$SUBDIRS diff --git a/package b/package index 7da5f3c96..b8ec3faa0 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 anxiety liboxide libsentry) -pkgver="2.5~~VERSION~" +pkgnames=(erode fret oxide rot tarnish decay corrupt anxiety liboxide libsentry oxide-utils) +pkgver="2.6~VERSION~" timestamp="$(date -u +%Y-%m-%dT%H:%MZ)" maintainer="Eeems " url=https://oxide.eeems.codes @@ -17,50 +17,61 @@ build() { find . -name "*.pro" -type f -print0 \ | xargs -r -0 sed -i 's/linux-oe-g++/linux-arm-remarkable-g++/g' CMAKE_TOOLCHAIN_FILE="/usr/share/cmake/$CHOST.cmake" make FEATURES=sentry release + # Cleanup build files + rm -r .build } erode() { pkgdesc="Task manager" section=utils - installdepends=("tarnish=$pkgver" "liboxide=$pkgver" "libsentry=$_sentry") + installdepends=("tarnish=$pkgver" "oxide-utils=$pkgver" "liboxide=$pkgver" "libsentry=$_sentry") package() { install -D -m 755 -t "$pkgdir"/opt/bin "$srcdir"/release/opt/bin/erode - install -D -m 644 -t "$pkgdir"/opt/etc/draft/icons "$srcdir"/release/opt/etc/draft/icons/erode.svg - install -D -m 644 -t "$pkgdir"/opt/etc/draft/icons "$srcdir"/release/opt/etc/draft/icons/erode-splash.png + install -D -m 644 -t "$pkgdir"/opt/usr/share/icons/oxide/48x48/apps "$srcdir"/release/opt/usr/share/icons/oxide/48x48/apps/erode.png + install -D -m 644 -t "$pkgdir"/opt/usr/share/icons/oxide/702x702/splash "$srcdir"/release/opt/usr/share/icons/oxide/702x702/splash/erode.png install -D -m 644 -t "$pkgdir"/opt/usr/share/applications "$srcdir"/release/opt/usr/share/applications/codes.eeems.erode.oxide } + + configure() { + if is-active "tarnish.service"; then + update-desktop-database + fi + } } fret() { pkgdesc="Take screenshots" section=utils - installdepends=("tarnish=$pkgver" "liboxide=$pkgver" "libsentry=$_sentry") + installdepends=("tarnish=$pkgver" "oxide-utils=$pkgver" "liboxide=$pkgver" "libsentry=$_sentry") package() { install -D -m 755 -t "$pkgdir"/opt/bin "$srcdir"/release/opt/bin/fret install -D -m 644 -t "$pkgdir"/opt/usr/share/applications "$srcdir"/release/opt/usr/share/applications/codes.eeems.fret.oxide } + + configure() { + if is-active "tarnish.service"; then + update-desktop-database + fi + } } oxide() { pkgdesc="Launcher application" section=launchers - installdepends=("erode=$pkgver" "fret=$pkgver" "tarnish=$pkgver" "rot=$pkgver" "decay=$pkgver" "corrupt=$pkgver" "liboxide=$pkgver" display "libsentry=$_sentry") + installdepends=("erode=$pkgver" "fret=$pkgver" "tarnish=$pkgver" "rot=$pkgver" "decay=$pkgver" "corrupt=$pkgver" "oxide-utils=$pkgver" "liboxide=$pkgver" display "libsentry=$_sentry") package() { install -D -m 755 -t "$pkgdir"/opt/bin "$srcdir"/release/opt/bin/oxide install -D -m 644 -t "$pkgdir"/opt/etc "$srcdir"/release/opt/etc/oxide.conf install -D -m 644 -t "$pkgdir"/opt/usr/share/applications "$srcdir"/release/opt/usr/share/applications/codes.eeems.oxide.oxide - install -D -m 644 -t "$pkgdir"/opt/etc/draft/icons "$srcdir"/release/opt/etc/draft/icons/oxide-splash.png + install -D -m 644 -t "$pkgdir"/opt/usr/share/icons/oxide/702x702/splash "$srcdir"/release/opt/usr/share/icons/oxide/702x702/splash/oxide.png } configure() { - if ! is-enabled "tarnish.service"; then - echo "" - echo "Run the following command(s) to use $pkgname as your launcher" - how-to-enable "tarnish.service" - echo "" + if is-active "tarnish.service"; then + update-desktop-database fi } } @@ -88,6 +99,12 @@ tarnish() { configure() { systemctl daemon-reload + if ! is-enabled "tarnish.service"; then + echo "" + echo "Run the following command(s) to use $pkgname as your launcher" + how-to-enable "tarnish.service" + echo "" + fi } preremove() { @@ -105,35 +122,71 @@ tarnish() { decay() { pkgdesc="Lockscreen application" section=utils - installdepends=("tarnish=$pkgver" "liboxide=$pkgver" "libsentry=$_sentry") + installdepends=("tarnish=$pkgver" "oxide-utils=$pkgver" "liboxide=$pkgver" "libsentry=$_sentry") package() { install -D -m 755 -t "$pkgdir"/opt/bin "$srcdir"/release/opt/bin/decay install -D -m 644 -t "$pkgdir"/opt/usr/share/applications "$srcdir"/release/opt/usr/share/applications/codes.eeems.decay.oxide } + + configure() { + if is-active "tarnish.service"; then + update-desktop-database + fi + } } corrupt() { pkgdesc="Task Switcher for Oxide" section=utils - installdepends=("tarnish=$pkgver" "liboxide=$pkgver" "libsentry=$_sentry") + installdepends=("tarnish=$pkgver" "oxide-utils=$pkgver" "liboxide=$pkgver" "libsentry=$_sentry") package() { install -D -m 755 -t "$pkgdir"/opt/bin "$srcdir"/release/opt/bin/corrupt install -D -m 644 -t "$pkgdir"/opt/usr/share/applications "$srcdir"/release/opt/usr/share/applications/codes.eeems.corrupt.oxide } + + configure() { + if is-active "tarnish.service"; then + update-desktop-database + fi + } } anxiety() { pkgdesc="Screenshot viewer for Oxide" section=utils - installdepends=("tarnish=$pkgver" "liboxide=$pkgver" "libsentry=$_sentry" "imagemagick-png") + installdepends=("tarnish=$pkgver" "oxide-utils=$pkgver" "liboxide=$pkgver" "libsentry=$_sentry" "imagemagick-png") package() { install -D -m 755 -t "$pkgdir"/opt/bin "$srcdir"/release/opt/bin/anxiety install -D -m 644 -t "$pkgdir"/opt/usr/share/applications "$srcdir"/release/opt/usr/share/applications/codes.eeems.anxiety.oxide - install -D -m 644 -t "$pkgdir"/opt/etc/draft/icons "$srcdir"/release/opt/etc/draft/icons/image.svg - install -D -m 644 -t "$pkgdir"/opt/etc/draft/icons "$srcdir"/release/opt/etc/draft/icons/anxiety-splash.png + install -D -m 644 -t "$pkgdir"/opt/usr/share/icons/oxide/48x48/apps "$srcdir"/release/opt/usr/share/icons/oxide/48x48/apps/image.png + install -D -m 644 -t "$pkgdir"/opt/usr/share/icons/oxide/702x702/splash "$srcdir"/release/opt/usr/share/icons/oxide/702x702/splash/anxiety.png + } + + configure() { + if is-active "tarnish.service"; then + update-desktop-database + fi + } +} + +oxide-utils() { + pkgdesc="Command line tools for Oxide" + section=utils + installdepends=("tarnish=$pkgver" "liboxide=$pkgver" "libsentry=$_sentry") + replaces=(notify-send update-desktop-database desktop-file-validate) + conflicts=(notify-send update-desktop-database desktop-file-validate) + + package() { + install -D -m 755 -t "$pkgdir"/opt/bin "$srcdir"/release/opt/bin/notify-send + install -D -m 755 -t "$pkgdir"/opt/bin "$srcdir"/release/opt/bin/update-desktop-database + install -D -m 755 -t "$pkgdir"/opt/bin "$srcdir"/release/opt/bin/desktop-file-validate + install -D -m 755 -t "$pkgdir"/opt/bin "$srcdir"/release/opt/bin/xdg-desktop-menu + install -D -m 755 -t "$pkgdir"/opt/bin "$srcdir"/release/opt/bin/xdg-desktop-icon + install -D -m 755 -t "$pkgdir"/opt/bin "$srcdir"/release/opt/bin/xdg-open + install -D -m 755 -t "$pkgdir"/opt/bin "$srcdir"/release/opt/bin/gio } } @@ -142,7 +195,7 @@ liboxide() { section=devel package() { - install -D -m 755 -t "$pkgdir"/opt/usr/lib "$srcdir"/release/opt/usr/lib/libliboxide.so* + install -D -m 755 -t "$pkgdir"/opt/lib "$srcdir"/release/opt/lib/libliboxide.so* } } diff --git a/qmake/common.pri b/qmake/common.pri new file mode 100644 index 000000000..c8b3ac4fa --- /dev/null +++ b/qmake/common.pri @@ -0,0 +1,3 @@ +QMAKE_RPATHDIR += /lib /usr/lib /opt/lib /opt/usr/lib +VERSION = 2.6 +DEFINES += APP_VERSION=\\\"$$VERSION\\\" diff --git a/qmake/epaper.pri b/qmake/epaper.pri new file mode 100644 index 000000000..f74112c42 --- /dev/null +++ b/qmake/epaper.pri @@ -0,0 +1,2 @@ +LIBS += -L$$PWD/../shared/epaper -lqsgepaper +INCLUDEPATH += $$PWD/../shared/epaper diff --git a/qmake/liboxide.pri b/qmake/liboxide.pri new file mode 100644 index 000000000..fbd942875 --- /dev/null +++ b/qmake/liboxide.pri @@ -0,0 +1,2 @@ +LIBS += -L$$OUT_PWD/../../shared/liboxide -lliboxide +INCLUDEPATH += $$OUT_PWD/../../shared/liboxide/include diff --git a/qmake/sentry.pri b/qmake/sentry.pri new file mode 100644 index 000000000..e107a4de0 --- /dev/null +++ b/qmake/sentry.pri @@ -0,0 +1,16 @@ +contains(DEFINES, SENTRY){ + LIBSENTRY_ROOT = $$PWD/../.build/sentry + LIBSENTRY_LIB = $$LIBSENTRY_ROOT/lib + LIBSENTRY_SO = $$LIBSENTRY_LIB/libsentry.so + !exists($$LIBSENTRY_SO){ + error(Missing $$LIBSENTRY_SO) + } + LIBSENTRY_INC = $$LIBSENTRY_ROOT/include + LIBSENTRY_H = $$LIBSENTRY_INC/sentry.h + !exists($$LIBSENTRY_H){ + error(Missing $$LIBSENTRY_H) + } + LIBS_PRIVATE += -L$$LIBSENTRY_LIB -lsentry -ldl -lcurl -lbreakpad_client + INCLUDEPATH += $$LIBSENTRY_INC + DEPENDPATH += $$LIBSENTRY_LIB +} diff --git a/shared/dbussettings.h b/shared/dbussettings.h deleted file mode 120000 index bb714c76b..000000000 --- a/shared/dbussettings.h +++ /dev/null @@ -1 +0,0 @@ -../applications/system-service/dbussettings.h \ No newline at end of file diff --git a/shared/epframebuffer.h b/shared/epaper/epframebuffer.h similarity index 100% rename from shared/epframebuffer.h rename to shared/epaper/epframebuffer.h diff --git a/shared/libepaper.so b/shared/epaper/libepaper.so similarity index 100% rename from shared/libepaper.so rename to shared/epaper/libepaper.so diff --git a/shared/libqsgepaper.a b/shared/epaper/libqsgepaper.a similarity index 100% rename from shared/libqsgepaper.a rename to shared/epaper/libqsgepaper.a diff --git a/shared/liboxide/applications.cpp b/shared/liboxide/applications.cpp new file mode 100644 index 000000000..a8ff8a5cc --- /dev/null +++ b/shared/liboxide/applications.cpp @@ -0,0 +1,523 @@ +#include "applications.h" +#include "json.h" +#include "liboxide.h" + +#include +#include +#include +#include + +using namespace codes::eeems::oxide1; + +const QList SystemFlags { + "system", + "transient", +}; +const QList Flags { + "autoStart", + "chroot", + "hidden", + "nosplash", + "nosavescreen", + "system", +}; + +namespace Oxide::Applications{ + QList _validateRegistration(const QString& name, const QJsonObject& app, bool exitEarly){ + QList errors; +#define addError(_level, _msg) errors.append(ValidationError { .level = _level, .msg = _msg }); + auto contains = [app, &errors](const QString& name) -> bool{ + if(app.contains(name)){ + return true; + } + addError(ErrorLevel::Critical, QString( + "Key \"%1\" is missing" + ).arg(name)); + return false; + }; + auto isString = [app, &errors, contains](const QString& name, bool required) -> bool{ + if(required && !contains(name)){ + return false; + }else if(!required && !app.contains(name)){ + return false; + } + if(app[name].isString()){ + return true; + } + addError(required ? ErrorLevel::Error : ErrorLevel::Warning, QString( + "Key \"%1\" must contain a string" + ).arg(name)); + return false; + }; + auto isArray = [app, &errors, contains](const QString& name, const ErrorLevel& level, bool required) -> bool{ + if(required && !contains(name)){ + return false; + }else if(!required && !app.contains(name)){ + return false; + } + if(app[name].isArray()){ + return true; + } + addError(level, QString( + "Key \"%1\" must contain an array" + ).arg(name)); + return false; + }; + auto isFile = [app, &errors, isString](const QString& name, const ErrorLevel& level, bool required) -> bool{ + if(!isString(name, required)){ + return false; + } + if(!app.contains(name)){ + return false; + } + auto value = app[name]; + auto str = value.toString(); + if(str.isEmpty()){ + return false; + } + if(QFile::exists(str)){ + return true; + } + addError(level, QString( + "Value \"%1\" for key \"%2\" is a path that does not exist" + ).arg(str, name)); + return false; + }; + auto isIcon = [app, &errors, isString](const QString& name, const ErrorLevel& level, bool required) -> bool{ + if(!isString(name, required)){ + return false; + } + if(!app.contains(name)){ + return false; + } + auto value = app[name].toString(); + auto path = value; + if(QFile::exists(path)){ + QImage image; + if(image.load(path) && !image.isNull()){ + return true; + } + addError(level, QString( + "Value \"%1\" for key \"%2\" is a path to a file that is not a valid image" + ).arg(value, name)); + return false; + } + path = iconPath(value); + if(path.isEmpty()){ + addError(level, QString( + "Value \"%1\" for key \"%2\" is not a valid icon spec" + ).arg(value, name)); + return false; + } + if(!QFile::exists(path)){ + addError(level, QString( + "Value \"%1\" for key \"%2\" is a path that does not exist" + ).arg(value, name)); + return false; + } + QImage image; + if(image.load(path) && !image.isNull()){ + return true; + } + addError(level, QString( + "Value \"%1\" for key \"%2\" is an icon spec for an icon that does not exist" + ).arg(value, name)); + return false; + }; +#define isError(level) (level == ErrorLevel::Error || level == ErrorLevel::Critical) +#define addError(_level, _msg) errors.append(ValidationError { .level = _level, .msg = _msg }); if(exitEarly && isError(_level)){ return errors; } +#define shouldExit if(exitEarly && std::any_of(errors.constBegin(), errors.constEnd(), [](const ValidationError& error){ return isError(error.level); })){ return errors; } + QString type = app.contains("type") ? app["type"].toString().toLower() : ""; + if(type == "background"){ + // TODO validate any extra settings required by background apps + }else if(type == "backgroundable"){ + // TODO validate any extra settings required by backgroundable apps + }else if(type == "foreground" || type.isEmpty()){ + // TODO validate any extra settings required by foreground apps + }else{ + addError(ErrorLevel::Warning, QString( + "Value \"%1\" for key \"type\" is not valid and will default to foreground" + ).arg(type)); + } + if(isFile("bin", ErrorLevel::Error, true)){ + auto bin = app["bin"].toString(); + QFileInfo info(bin); + if(!info.isFile()){ + addError(ErrorLevel::Error, QString( + "Value \"%1\" for key \"bin\" is not a path to a file" + ).arg(bin)); + }else if(!info.isExecutable()){ + addError(ErrorLevel::Error, QString( + "Value \"%1\" for key \"bin\" is a path to a file that is not executable" + ).arg(bin)); + } + } else shouldExit + QStringList flags; + if(isArray("flags", ErrorLevel::Critical, false)){ + auto flagsArray = app["flags"].toArray(); + auto flagsJson = Oxide::JSON::toJson(flagsArray); + for(auto flag : flagsArray){ + if(!flag.isString()){ + addError(ErrorLevel::Error, QString( + "Value \"%1\" for key \"flags\" contains an entry that is not a string \"%2\"" + ).arg(flagsJson, Oxide::JSON::toJson(flag.toVariant()))); + continue; + } + auto value = flag.toString().trimmed(); + if(value.isEmpty()){ + addError(ErrorLevel::Error, QString( + "Value \"%1\" for key \"flags\" contains an entry that is empty" + ).arg(flagsJson)); + continue; + } + if(SystemFlags.contains(value)){ + addError(ErrorLevel::Warning, QString( + "Value \"%1\" for key \"flags\" contains an entry that should only be used by the system \"%2\"" + ).arg(flagsJson, value)); + }else if(!Flags.contains(value)){ + addError(ErrorLevel::Warning, QString( + "Value \"%1\" for key \"flags\" contains an entry that is not known \"%2\"" + ).arg(flagsJson, value)); + } + flags << value; + } + if(type == "background" && flags.contains("nosavescreen")){ + addError(ErrorLevel::Hint, "Key \"flags\" contains \"nosavescreen\" while \"type\" has value \"background\""); + } + } else shouldExit + if(isArray("directories", ErrorLevel::Critical, false)){ + auto directories = app["directories"].toArray(); + for(int i = 0; i < directories.count(); i++){ + QJsonValue entry = directories[i]; + if(!entry.isString()){ + addError(ErrorLevel::Error, QString( + "Value \"%1\" for key \"directories[%2]\" contains an entry that is not a string \"%3\"" + ).arg(Oxide::JSON::toJson(directories), QString::number(i), Oxide::JSON::toJson(entry))); + continue; + } + auto directory = entry.toString(); + if(!QFile::exists(directory)){ + addError(ErrorLevel::Error, QString( + "Value \"%1\" for key \"directories[%2]\" contains a path that does not exist \"%3\"" + ).arg(Oxide::JSON::toJson(directories), QString::number(i), directory)); + } + } + } else shouldExit else if(flags.contains("chroot")){ + addError(ErrorLevel::Hint, "Key \"flags\" contains \"chroot\" while \"directories\" is missing"); + } + if(isArray("permissions", ErrorLevel::Critical, false)){ + auto permissions = app["permissions"].toArray(); + for(int i = 0; i < permissions.count(); i++){ + QJsonValue entry = permissions[i]; + if(!entry.isString()){ + addError(ErrorLevel::Error, QString( + "Value \"%1\" for key \"permissions[%2]\" contains a value that is not a string \"%3\"" + ).arg(Oxide::JSON::toJson(permissions), QString::number(i), Oxide::JSON::toJson(entry))); + } + } + } else shouldExit + isFile("workingDirectory", ErrorLevel::Error, false); shouldExit + isString("displayName", false); shouldExit + isString("description", false); shouldExit + if(isString("user", false)){ + auto user = app["user"].toString(); + try{ + Oxide::getUID(user); + }catch(const std::exception& e){ + addError(ErrorLevel::Error, QString( + "Value \"%1\" for key \"user\" is not a valid user: \"%2\"" + ).arg(user, e.what())); + } + } else shouldExit + if(isString("group", false)){ + auto group = app["group"].toString(); + try{ + Oxide::getGID(group); + }catch(const std::exception& e){ + addError(ErrorLevel::Error, QString( + "Value \"%1\" for key \"group\" is not a valid group: \"%2\"" + ).arg(group, e.what())); + } + } else shouldExit + isIcon("icon", ErrorLevel::Warning, false); + if(isIcon("splash", ErrorLevel::Warning, false) && flags.contains("nosplash")){ + addError(ErrorLevel::Hint, "Key \"splash\" provided while \"flags\" contains \"nosplash\" value"); + } else shouldExit + if(registrationToMap(app, name).isEmpty()){ + addError(ErrorLevel::Critical, "Unable to convert registration to QVariantMap"); + } + return errors; +#undef isError +#undef addError +#undef shouldExit +} + QTextStream& operator<<(QTextStream& s, const ErrorLevel& l){ + switch (l) { + case ErrorLevel::Hint: + s << "hint"; + break; + case ErrorLevel::Deprecation: + s << "deprecation"; + break; + case ErrorLevel::Warning: + s << "warning"; + break; + case ErrorLevel::Error: + s << "error"; + break; + case ErrorLevel::Critical: + s << "critical"; + break; + default: + s << "unknown"; + } + return s; + } + QTextStream& operator<<(QTextStream& s, const ValidationError& e){ + s << e.level << ": " << e.msg; + return s; + } + QDebug& operator<<(QDebug& s, const ErrorLevel& l){ + switch (l) { + case ErrorLevel::Hint: + s << "hint"; + break; + case ErrorLevel::Deprecation: + s << "deprecation"; + break; + case ErrorLevel::Warning: + s << "warning"; + break; + case ErrorLevel::Error: + s << "error"; + break; + case ErrorLevel::Critical: + s << "critical"; + break; + default: + s << "unknown"; + } + return s; + } + QDebug& operator<<(QDebug& s, const ValidationError& e){ + s << e.level << ": " << e.msg; + return s; + } + bool operator==(const ValidationError& v1, const ValidationError& v2){ return v1.level == v2.level && v1.msg == v2.msg; } + QVariantMap registrationToMap(const QJsonObject& app, const QString& name){ + auto _name = name; + if(name.isEmpty()){ + if(!app.contains("name") || !app["name"].isString() || app["name"].toString().isEmpty()){ + return QVariantMap(); + } + _name = app["name"].toString(); + } + int type = Foreground; + QString typeString = app.contains("type") ? app["type"].toString().toLower() : ""; + if(typeString == "background"){ + type = Background; + }else if(typeString == "backgroundable"){ + type = Backgroundable; + } + // Anything else defaults to Foreground + auto flags = QStringList(); + if(app.contains("flags")){ + for(auto flag : app["flags"].toArray()){ + if(!flag.isString()){ + continue; + } + auto value = flag.toString(); + if(!value.isEmpty()){ + flags << value; + } + } + } + QVariantMap properties { + {"name", _name}, + {"bin", app["bin"].toString()}, + {"type", type}, + {"flags", flags}, + }; + if(app.contains("displayName")){ + properties.insert("displayName", app["displayName"].toString()); + } + if(app.contains("description")){ + properties.insert("description", app["description"].toString()); + } + if(app.contains("icon")){ + properties.insert("icon", app["icon"].toString()); + } + if(app.contains("user")){ + properties.insert("user", app["user"].toString()); + } + if(app.contains("group")){ + properties.insert("group", app["group"].toString()); + } + if(app.contains("workingDirectory")){ + properties.insert("workingDirectory", app["workingDirectory"].toString()); + } + if(app.contains("directories")){ + QStringList directories; + for(auto directory : app["directories"].toArray()){ + directories.append(directory.toString()); + } + properties.insert("directories", directories); + } + if(app.contains("permissions")){ + QStringList permissions; + for(auto permission : app["permissions"].toArray()){ + permissions.append(permission.toString()); + } + properties.insert("permissions", permissions); + } + if(app.contains("events")){ + auto events = app["events"].toObject(); + for(auto event : events.keys()){ + if(event == "stop"){ + properties.insert("onStop", events[event].toString()); + }else if(event == "pause"){ + properties.insert("onPause", events[event].toString()); + }else if(event == "resume"){ + properties.insert("onResume", events[event].toString()); + } + } + } + if(app.contains("environment")){ + QVariantMap envMap; + auto environment = app["environment"].toObject(); + for(auto key : environment.keys()){ + envMap.insert(key, environment[key].toString()); + } + properties.insert("environment", envMap); + } + if(app.contains("splash")){ + properties.insert("splash", app["splash"].toString()); + } + return properties; + } + QJsonObject getRegistration(const char* path){ return getRegistration(QString(path)); } + QJsonObject getRegistration(const std::string& path){ return getRegistration(QString(path.c_str())); } + QJsonObject getRegistration(const QString& path){ + QFile file(path); + auto res = getRegistration(&file); + if(file.isOpen()){ + file.close(); + } + return res; + } + QJsonObject getRegistration(QFile* file){ + if(!file->isOpen() && !file->open(QFile::ReadOnly)){ + return QJsonObject(); + } + auto data = file->readAll(); + auto app = QJsonDocument::fromJson(data).object(); + return app; + } + QList validateRegistration(const char* path){ return validateRegistration(QString(path)); } + QList validateRegistration(const std::string& path){ return validateRegistration(QString(path.c_str())); } + QList validateRegistration(const QString& path){ + QFile file(path); + auto res = validateRegistration(&file); + if(file.isOpen()){ + file.close(); + } + return res; + } + QList validateRegistration(QFile* file){ + if(!file->isOpen() && !file->open(QFile::ReadOnly)){ + QList errors; + errors.append(ValidationError{ + .level = ErrorLevel::Critical, + .msg = "Could not open file" + }); + return errors; + } + auto data = file->readAll(); + auto app = QJsonDocument::fromJson(data).object(); + if(!app.isEmpty()){ + return validateRegistration(QFileInfo(file->fileName()).completeBaseName(), app); + } + QList errors; + errors.append(ValidationError{ + .level = ErrorLevel::Critical, + .msg = "File is not valid JSON or is empty" + }); + return errors; + } + QList validateRegistration(const QString& name, const QJsonObject& app){ return _validateRegistration(name, app, false); } + bool addToTarnishCache(const char* path){ return addToTarnishCache(QString(path)); } + bool addToTarnishCache(const std::string& path){ return addToTarnishCache(QString(path.c_str())); } + bool addToTarnishCache(const QString& path){ + QFile file(path); + auto res = addToTarnishCache(&file); + if(file.isOpen()){ + file.close(); + } + return res; + } + bool addToTarnishCache(QFile* file){ + auto app = getRegistration(file); + auto name = QFileInfo(file->fileName()).completeBaseName(); + return addToTarnishCache(name, app); + } + bool addToTarnishCache(const QString& name, const QJsonObject& app){ + if(app.isEmpty()){ + return false; + } + if(!_validateRegistration(name, app, true).isEmpty()){ + return false; + } + auto bus = QDBusConnection::systemBus(); + General api(OXIDE_SERVICE, OXIDE_SERVICE_PATH, bus); + QDBusObjectPath path = api.requestAPI("apps"); + if(path.path() == "/"){ + return false; + } + Apps apps(OXIDE_SERVICE, path.path(), bus); + auto properties = registrationToMap(app, name); + if(properties.isEmpty()){ + return false; + } + path = apps.registerApplication(properties); + return path.path() != "/"; + } + QString iconDirPath(int size, const QString& theme, const QString& context){ + return QString(OXIDE_ICONS_DIRECTORY "/%1/%2x%2/%3").arg( + theme, + QString::number(size), + context + ); + } + QString iconPath(const QString& name, int size, const QString& theme, const QString& context){ + return QString(OXIDE_ICONS_DIRECTORY "/%1/%2x%2/%3/%4.png").arg( + theme, + QString::number(size), + context, + name + ); + } + QString iconPath(const QString& spec){ + if(spec.isEmpty() || !spec.contains("-")){ + return ""; + } + auto parts = spec.split('-'); + auto size = parts.last().toUInt(); + if(size == 0){ + return ""; + } + parts.removeLast(); + auto name = parts.join('-'); + if(!name.contains(":")){ + return iconPath(name, size); + } + parts = name.split(':'); + auto len = parts.length(); + if(len == 1 || len > 3){ + return ""; + } + if(len == 2){ + return iconPath(parts.last(), size, parts.first()); + } + return iconPath(parts.last(), size, parts.first(), parts.at(1)); + } +} diff --git a/shared/liboxide/applications.h b/shared/liboxide/applications.h new file mode 100644 index 000000000..007b81a9d --- /dev/null +++ b/shared/liboxide/applications.h @@ -0,0 +1,212 @@ +/*! + * \addtogroup Applications + * \brief The Applications module + * @{ + * \file + */ +#pragma once + +#include "liboxide_global.h" + +#include +#include +#include + +#include + +/*! + * \def OXIDE_APPLICATION_REGISTRATIONS_DIRECTORY + * \brief Application directory location + */ +#define OXIDE_APPLICATION_REGISTRATIONS_DIRECTORY "/opt/usr/share/applications" +#define OXIDE_ICONS_DIRECTORY "/opt/usr/share/icons" + +/*! + * \brief The applications namespace + */ +namespace Oxide::Applications{ + /*! + * \typedef ApplicationType + * \brief Application registration type + */ + typedef enum { + Foreground, /*!< Only runs in the foreground */ + Background, /*!< Only runs in the background */ + Backgroundable /*!< Runs in either the foreground or background */ + } ApplicationType; + /*! + * \typedef ErrorLevel + * \brief ValidationError error levels + */ + typedef enum{ + Hint, /*!< A hint */ + Deprecation, /*!< A deprecation warning */ + Warning, /*!< A warning */ + Error, /*!< An error */ + Critical /*!< A critical error*/ + } ErrorLevel; + /*! + * \struct ValidationError + * \brief Errors returned by validateRegistration + */ + typedef struct{ + ErrorLevel level; /*!< Error level */ + QString msg; /*!< Error message */ + } ValidationError; + /*! + * \brief Convert an application registration to a QVariantMap + * \param Application registration to convert + * \param Name of the application + * \return QVariantMap of the cached application + */ + LIBOXIDE_EXPORT QVariantMap registrationToMap(const QJsonObject& app, const QString& name = ""); + /*! + * \brief Convert an ErrorLevel to a human readable string when piping to a text stream + * \param Text stream to write to + * \param Error level + * \return Resulting text stream + * \sa ErrorLevel + */ + LIBOXIDE_EXPORT QTextStream& operator<<(QTextStream& s, const ErrorLevel& l); + /*! + * \brief Convert an ValidationError to a human readable string when piping to a text stream + * \param Text stream to write to + * \param Validation error to convert + * \return Resulting text stream + * \sa ValidationError + */ + LIBOXIDE_EXPORT QTextStream& operator<<(QTextStream& s, const ValidationError& t); + /*! + * \brief Convert an ErrorLevel to a human readable string when piping to a text stream + * \param Text stream to write to + * \param Error level + * \return Resulting text stream + * \sa ErrorLevel + */ + LIBOXIDE_EXPORT QDebug& operator<<(QDebug& s, const ErrorLevel& l); + /*! + * \brief Convert an ValidationError to a human readable string when piping to a text stream + * \param Text stream to write to + * \param Validation error to convert + * \return Resulting text stream + * \sa ValidationError + */ + LIBOXIDE_EXPORT QDebug& operator<<(QDebug& s, const ValidationError& t); + /*! + * \brief Compare two ValidationError instances + * \param First instance to comprae + * \param Second instance to compare + * \return If they are the same + * \sa ValidationError + */ + LIBOXIDE_EXPORT bool operator==(const ValidationError& v1, const ValidationError& v2); + /*! + * \brief Get an application registration. + * \param Path to application registration + * \return Application registration + */ + LIBOXIDE_EXPORT QJsonObject getRegistration(const char* path); + /*! + * \brief Get an application registration. + * \param Path to application registration + * \return Application registration + */ + LIBOXIDE_EXPORT QJsonObject getRegistration(const std::string& path); + /*! + * \brief Get an application registration. + * \param Path to application registration + * \return Application registration + */ + LIBOXIDE_EXPORT QJsonObject getRegistration(const QString& path); + /*! + * \brief Get an application registration. + * \param Application registration file + * \return Application registration + */ + LIBOXIDE_EXPORT QJsonObject getRegistration(QFile* file); + /*! + * \brief Validate a application registration file and return any errors found + * \param Path to the file to validate + * \return List of validation errors + */ + LIBOXIDE_EXPORT QList validateRegistration(const char* path); + /*! + * \brief Validate a application registration file and return any errors found + * \param Path to the file to validate + * \return List of validation errors + */ + LIBOXIDE_EXPORT QList validateRegistration(const std::string& path); + /*! + * \brief Validate a application registration file and return any errors found + * \param Path to the file to validate + * \return List of validation errors + */ + LIBOXIDE_EXPORT QList validateRegistration(const QString& path); + /*! + * \brief Validate a application registration file and return any errors found + * \param The file to validate + * \return List of validation errors + */ + LIBOXIDE_EXPORT QList validateRegistration(QFile* file); + /*! + * \brief Validate a application registration file and return any errors found + * \param The file to validate + * \return List of validation errors + */ + LIBOXIDE_EXPORT QList validateRegistration(const QString& name, const QJsonObject& app); + /*! + * \brief Add an application to the tarnish application cache + * \param Path to the application registration file + * \return If the application was successfully added + */ + LIBOXIDE_EXPORT bool addToTarnishCache(const char* path); + /*! + * \brief Add an application to the tarnish application cache + * \param Path to the application registration file + * \return If the application was successfully added + */ + LIBOXIDE_EXPORT bool addToTarnishCache(const std::string& path); + /*! + * \brief Add an application to the tarnish application cache + * \param Path to the application registration file + * \return If the application was successfully added + */ + LIBOXIDE_EXPORT bool addToTarnishCache(const QString& path); + /*! + * \brief Add an application to the tarnish application cache + * \param The application registration file + * \return If the application was successfully added + */ + LIBOXIDE_EXPORT bool addToTarnishCache(QFile* file); + /*! + * \brief Add an application to the tarnish application cache + * \param The name of the application + * \param The application registration file + * \return If the application was successfully added + */ + LIBOXIDE_EXPORT bool addToTarnishCache(const QString& name, const QJsonObject& app); + /*! + * \brief Get the path to the directory that an icon would be stored in. + * \param Size of the icon. + * \param Icon theme. Default is hicolor. + * \param Icon context. Default is apps. + * \return Path to the directory that would contain the icon. + */ + LIBOXIDE_EXPORT QString iconDirPath(int size, const QString& theme = "hicolor", const QString& context = "apps"); + /*! + * \brief Get the path to an icon. + * \param Name of the icon. + * \param Size of the icon. + * \param Icon theme. Default is hicolor. + * \param Icon context. Default is apps. + * \return Path to the icon. + */ + LIBOXIDE_EXPORT QString iconPath(const QString& name, int size, const QString& theme = "hicolor", const QString& context = "apps"); + /*! + * \brief Get the path to an icon from an icon name spec. + * \param Icon name spec using the following format: [theme:][context:]{name}-{size}. e.g. oxide:splash:xochitl-702 + * \return Path to the icon. An empty string if it failed to parse the spec. + */ + LIBOXIDE_EXPORT QString iconPath(const QString& spec); +} +/*! @} */ diff --git a/shared/liboxide/dbus.h b/shared/liboxide/dbus.h new file mode 100644 index 000000000..4a504b2e6 --- /dev/null +++ b/shared/liboxide/dbus.h @@ -0,0 +1,26 @@ +/*! + * \addtogroup DBus + * \brief The DBus module + * @{ + * \file + */ +#pragma once +// This must be here to make precompiled headers happy +#ifndef LIBOXIDE_DBUS_H +#define LIBOXIDE_DBUS_H + +#include "dbusservice_interface.h" +#include "powerapi_interface.h" +#include "wifiapi_interface.h" +#include "network_interface.h" +#include "bss_interface.h" +#include "appsapi_interface.h" +#include "application_interface.h" +#include "systemapi_interface.h" +#include "screenapi_interface.h" +#include "screenshot_interface.h" +#include "notificationapi_interface.h" +#include "notification_interface.h" + +#endif // LIBOXIDE_DBUS_H +/*! @} */ diff --git a/shared/liboxide/debug.cpp b/shared/liboxide/debug.cpp new file mode 100644 index 000000000..f885ced16 --- /dev/null +++ b/shared/liboxide/debug.cpp @@ -0,0 +1,11 @@ +#include "debug.h" + +namespace Oxide { + bool debugEnabled(){ + if(getenv("DEBUG") == NULL){ + return false; + } + QString env = qgetenv("DEBUG"); + return !(QStringList() << "0" << "n" << "no" << "false").contains(env.toLower()); + } +} diff --git a/shared/liboxide/debug.h b/shared/liboxide/debug.h new file mode 100644 index 000000000..a99080145 --- /dev/null +++ b/shared/liboxide/debug.h @@ -0,0 +1,36 @@ +/*! + * \addtogroup Oxide + * @{ + * \file + */ +#pragma once + +#include "liboxide_global.h" + +#include +/*! + * \def O_DEBUG(msg) + * \brief Log a debug message if compiled with DEBUG mode, and debugging is enabled + * \param Debug message to log + */ +#ifdef DEBUG +#define O_DEBUG(msg) if(Oxide::debugEnabled()){ qDebug() << msg; } +#else +#define O_DEBUG(msg) +#endif +/*! + * \def O_WARNING(msg) + * \brief Log a warning message if debugging is enabled + * \param Warning message to log + */ +#define O_WARNING(msg) if(Oxide::debugEnabled()){ qWarning() << msg; } + +namespace Oxide { + /*! + * \brief Return the state of debugging + * \return Debugging state + * \snippet examples/oxide.cpp debugEnabled + */ + LIBOXIDE_EXPORT bool debugEnabled(); +} +/*! @} */ diff --git a/shared/liboxide/eventfilter.cpp b/shared/liboxide/eventfilter.cpp index 5912c1b08..dd9b523bf 100644 --- a/shared/liboxide/eventfilter.cpp +++ b/shared/liboxide/eventfilter.cpp @@ -1,6 +1,7 @@ #include "eventfilter.h" +#include "debug.h" + #include -#include #include #include #include @@ -11,6 +12,11 @@ #define WACOM_X_SCALAR (float(DISPLAYWIDTH) / float(DISPLAYHEIGHT)) #define WACOM_Y_SCALAR (float(DISPLAYHEIGHT) / float(DISPLAYWIDTH)) //#define DEBUG_EVENTS +#ifdef DEBUG_EVENTS +#define O_DEBUG_EVENT(msg) O_DEBUG(msg) +#else +#define O_DEBUG_EVENT(msg) +#endif namespace Oxide{ EventFilter::EventFilter(QObject *parent) : QObject(parent), root(nullptr){} @@ -86,9 +92,7 @@ namespace Oxide{ auto pos = mouseEvent->globalPos(); for(auto postWidget : widgetsAt(root, pos)){ if(parentCount((QQuickItem*)postWidget)){ -#ifdef DEBUG_EVENTS - qDebug() << "postWidget: " << postWidget; -#endif + O_DEBUG_EVENT("postWidget: " << postWidget); auto event = new QMouseEvent( mouseEvent->type(), mouseEvent->localPos(), mouseEvent->windowPos(), mouseEvent->screenPos(), mouseEvent->button(), mouseEvent->buttons(), @@ -110,19 +114,13 @@ namespace Oxide{ bool filtered = QObject::eventFilter(obj, ev); if(!filtered){ if(type == QEvent::TabletPress){ -#ifdef DEBUG_EVENTS - qDebug() << ev; -#endif + O_DEBUG_EVENT(ev); postEvent(QMouseEvent::MouseButtonPress, ev, root); }else if(type == QEvent::TabletRelease){ -#ifdef DEBUG_EVENTS - qDebug() << ev; -#endif + O_DEBUG_EVENT(ev); postEvent(QMouseEvent::MouseButtonRelease, ev, root); }else if(type == QEvent::TabletMove){ -#ifdef DEBUG_EVENTS - qDebug() << ev; -#endif + O_DEBUG_EVENT(ev); postEvent(QMouseEvent::MouseMove, ev, root); } #ifdef DEBUG_EVENTS @@ -133,11 +131,11 @@ namespace Oxide{ ){ for(auto widget : widgetsAt(root, ((QMouseEvent*)ev)->globalPos())){ if(parentCount((QQuickItem*)widget)){ - qDebug() << "postWidget: " << widget; + O_DEBUG("postWidget: " << widget); } } - qDebug() << obj; - qDebug() << ev; + O_DEBUG(obj); + O_DEBUG(ev); } #endif } diff --git a/shared/liboxide/eventfilter.h b/shared/liboxide/eventfilter.h index 07fa721a0..8faa1c62a 100644 --- a/shared/liboxide/eventfilter.h +++ b/shared/liboxide/eventfilter.h @@ -1,13 +1,13 @@ /*! - * \file eventfilter.h + * \addtogroup Oxide + * @{ + * \file */ -#ifndef EVENTFILTER_H -#define EVENTFILTER_H +#pragma once #include #include #include - namespace Oxide{ /*! * \brief An event filter that maps pen events to Qt touch events @@ -31,5 +31,4 @@ namespace Oxide{ bool eventFilter(QObject* obj, QEvent* ev); }; } - -#endif // EVENTFILTER_H +/*! @} */ diff --git a/shared/liboxide/examples/oxide.cpp b/shared/liboxide/examples/oxide.cpp index 191fc7946..4fa0c3b94 100644 --- a/shared/liboxide/examples/oxide.cpp +++ b/shared/liboxide/examples/oxide.cpp @@ -1,11 +1,11 @@ /*! - * \file examples/oxide.cpp + * \file */ //! [debugEnabled] if(Oxide::debugEnabled()){ qDebug() << "Debugging is enabled"; } -//! [debugEnabled] +//! [debugEnabled] //! [dispatchToMainThread] Oxide::dispatchToMainThread([=]{ qDebug() << "This is running on the main thread"; @@ -18,3 +18,19 @@ connect(signalHandler, &SignalHandler::sigUsr1, [=]{ qDebug() << "SIGUSR1 recieved"; }); //! [SignalHandler] +//! [getGID] +try{ + auto gid = Oxide::getGID("admin"); + qDebug() << "admin GID is " << gid; +}catch(const std::exception& e){ + qDebug() << "Failed to get group: " << e.what(); +} +//! [getGID] +//! [getUID] +try{ + auto gid = Oxide::getUID("root"); + qDebug() << "root UID is " << gid; +}catch(const std::exception& e){ + qDebug() << "Failed to get user: " << e.what(); +} +//! [getUID] diff --git a/shared/liboxide/json.cpp b/shared/liboxide/json.cpp new file mode 100644 index 000000000..f71340be4 --- /dev/null +++ b/shared/liboxide/json.cpp @@ -0,0 +1,219 @@ +#include "json.h" +#include "debug.h" + +#include +#include + +static bool qIsNumericType(uint tp){ + static const qulonglong numericTypeBits = + Q_UINT64_C(1) << QMetaType::Bool | + Q_UINT64_C(1) << QMetaType::Double | + Q_UINT64_C(1) << QMetaType::Float | + Q_UINT64_C(1) << QMetaType::Char | + Q_UINT64_C(1) << QMetaType::SChar | + Q_UINT64_C(1) << QMetaType::UChar | + Q_UINT64_C(1) << QMetaType::Short | + Q_UINT64_C(1) << QMetaType::UShort | + Q_UINT64_C(1) << QMetaType::Int | + Q_UINT64_C(1) << QMetaType::UInt | + Q_UINT64_C(1) << QMetaType::Long | + Q_UINT64_C(1) << QMetaType::ULong | + Q_UINT64_C(1) << QMetaType::LongLong | + Q_UINT64_C(1) << QMetaType::ULongLong; + return tp < (CHAR_BIT * sizeof numericTypeBits) ? numericTypeBits & (Q_UINT64_C(1) << tp) : false; +} + +int compareAsString(const QVariant* v1, const QVariant* v2){ + int r = v1->toString().compare(v2->toString(), Qt::CaseInsensitive); + if (r == 0) { + return (v1->type() < v2->type()) ? -1 : 1; + } + return r; +} +int compare(const QVariant* v1, const QVariant* v2){ + if (qIsNumericType(v1->type()) && qIsNumericType(v2->type())){ + if(v1 == v2){ + return 0; + } + if(v1 < v2){ + return -1; + } + return 1; + } + if ((int)v1->type() >= (int)QMetaType::User) { + int result; + const void* v1d = v1->constData(); + const void* v2d = v2->constData(); + if(QMetaType::compare(v1d, v2d, v1->type(), &result)){ + return result; + } + } + switch (v1->type()){ + case QVariant::Date: + return v1->toDate() < v2->toDate() ? -1 : 1; + case QVariant::Time: + return v1->toTime() < v2->toTime() ? -1 : 1; + case QVariant::DateTime: + return v1->toDateTime() < v2->toDateTime() ? -1 : 1; + case QVariant::StringList: + return v1->toStringList() < v2->toStringList() ? -1 : 1; + default: + return compareAsString(v1, v2); + } +} + +bool operator<(const QVariant& lhs, const QVariant& rhs){ + const QVariant* v1 = &lhs; + const QVariant* v2 = &rhs; + if(lhs.type() != rhs.type()){ + if (v2->canConvert(v1->type())) { + QVariant converted2 = *v2; + if (converted2.convert(v1->type())){ + v2 = &converted2; + } + } + if (v1->type() != v2->type() && v1->canConvert(v2->type())) { + QVariant converted1 = *v1; + if (converted1.convert(v2->type())){ + v1 = &converted1; + } + } + if (v1->type() != v2->type()) { + return compareAsString(v1, v2) < 0; + } + } + return compare(v1, v2) < 0; +} + +namespace Oxide::JSON { + QVariant decodeDBusArgument(const QDBusArgument& arg){ + auto type = arg.currentType(); + if(type == QDBusArgument::BasicType || type == QDBusArgument::VariantType){ + return sanitizeForJson(arg.asVariant()); + } + if(type == QDBusArgument::ArrayType){ + QVariantList list; + arg.beginArray(); + while(!arg.atEnd()){ + list.append(decodeDBusArgument(arg)); + } + arg.endArray(); + return sanitizeForJson(list); + } + if(type == QDBusArgument::MapType){ + QMap map; + arg.beginMap(); + while(!arg.atEnd()){ + arg.beginMapEntry(); + auto key = decodeDBusArgument(arg); + auto value = decodeDBusArgument(arg); + arg.endMapEntry(); + map.insert(sanitizeForJson(key), sanitizeForJson(value)); + } + arg.endMap(); + return sanitizeForJson(QVariant::fromValue(map)); + } + O_WARNING("Unable to sanitize QDBusArgument as it is an unknown type"); + return QVariant(); + } + QVariant sanitizeForJson(QVariant value){ + auto userType = value.userType(); + if(userType == QMetaType::type("QDBusObjectPath")){ + return value.value().path(); + } + if(userType == QMetaType::type("QDBusSignature")){ + return value.value().signature(); + } + if(userType == QMetaType::type("QDBusVariant")){ + return value.value().variant(); + } + if(userType == QMetaType::type("QDBusArgument")){ + return decodeDBusArgument(value.value()); + } + if(userType == QMetaType::type("QList")){ + QVariantList list; + for(auto value : value.value>()){ + list.append(sanitizeForJson(value.variant())); + } + return list; + } + if(userType == QMetaType::type("QList")){ + QStringList list; + for(auto value : value.value>()){ + list.append(value.signature()); + } + return list; + } + if(userType == QMetaType::type("QList")){ + QStringList list; + for(auto value : value.value>()){ + list.append(value.path()); + } + return list; + } + if(userType == QMetaType::QByteArray){ + auto byteArray = value.toByteArray(); + QVariantList list; + for(auto byte : byteArray){ + list.append(byte); + } + return list; + } + if(userType == QMetaType::QVariantMap){ + QVariantMap map; + auto input = value.toMap(); + for(auto key : input.keys()){ + map.insert(key, sanitizeForJson(input[key])); + } + return map; + } + if(userType == QMetaType::QVariantList){ + QVariantList list = value.toList(); + QMutableListIterator i(list); + while(i.hasNext()){ + i.setValue(sanitizeForJson(i.next())); + } + return list; + } + return value; + } + QString toJson(QVariant value, QJsonDocument::JsonFormat format){ + if(value.isNull()){ + return "null"; + } + auto jsonVariant = QJsonValue::fromVariant(sanitizeForJson(value)); + if(jsonVariant.isNull()){ + return "null"; + } + if(jsonVariant.isUndefined()){ + return "undefined"; + } + if(jsonVariant.isArray()){ + QJsonDocument doc(jsonVariant.toArray()); + return doc.toJson(format); + } + if(jsonVariant.isObject()){ + QJsonDocument doc(jsonVariant.toObject()); + return doc.toJson(format); + } + if(jsonVariant.isBool()){ + return jsonVariant.toBool() ? "true" : "false"; + } + // Number, string or other unknown type + QJsonArray jsonArray; + jsonArray.append(jsonVariant); + QJsonDocument doc(jsonArray); + auto json = doc.toJson(QJsonDocument::Compact); + return json.mid(1, json.length() - 2); + } + QVariant fromJson(QByteArray json){ + QJsonParseError error; + QJsonDocument doc = QJsonDocument::fromJson("[" + json + "]", &error); + if(error.error != QJsonParseError::NoError){ + O_WARNING("Unable to read json value" << error.errorString()); + O_WARNING("Value to parse" << json); + } + return doc.array().first().toVariant(); + } + QVariant fromJson(QFile* file){ return fromJson(file->readAll()); } +} diff --git a/shared/liboxide/json.h b/shared/liboxide/json.h new file mode 100644 index 000000000..da9ecf8cd --- /dev/null +++ b/shared/liboxide/json.h @@ -0,0 +1,50 @@ +/*! + * \addtogroup JSON + * \brief The JSON module + * @{ + * \file + */ +#pragma once + +#include "liboxide_global.h" + +#include +#include +#include +/*! + * \brief The JSON namespace + */ +namespace Oxide::JSON { + /*! + * \brief Decode a DBus Argument into a QVariant + * \param DBus Argument to decode + * \return QVariant + */ + LIBOXIDE_EXPORT QVariant decodeDBusArgument(const QDBusArgument& arg); + /*! + * \brief Sanitize a QVariant into a value that can be converted to JSON + * \param QVariant to sanitize + * \return Sanitized value + */ + LIBOXIDE_EXPORT QVariant sanitizeForJson(QVariant value); + /*! + * \brief Convert a QVariant to a JSON string + * \param QVariant to convert + * \param Format to use + * \return JSON string + */ + LIBOXIDE_EXPORT QString toJson(QVariant value, QJsonDocument::JsonFormat format = QJsonDocument::Compact); + /*! + * \brief Convert a JSON string into a QVariant + * \param JSON string to convert + * \return The converted QVaraint + */ + LIBOXIDE_EXPORT QVariant fromJson(QByteArray json); + /*! + * \brief Convert a JSON file into a QVariant + * \param JSON fle to convert + * \return The converted QVaraint + */ + LIBOXIDE_EXPORT QVariant fromJson(QFile* file); +} +/*! @} */ diff --git a/shared/liboxide/liboxide.cpp b/shared/liboxide/liboxide.cpp index 032510beb..fa839d2a5 100644 --- a/shared/liboxide/liboxide.cpp +++ b/shared/liboxide/liboxide.cpp @@ -1,59 +1,17 @@ #include "liboxide.h" -#include #include #include #include #include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include #include -#include - -// String: 5aa5ca39ee0b4f48927529ca17519524 -// UUID: 5aa5ca39-ee0b-4f48-9275-29ca17519524 -#define OXIDE_UID SD_ID128_MAKE(5a,a5,ca,39,ee,0b,4f,48,92,75,29,ca,17,51,95,24) +#include +#include +#include -#ifdef SENTRY -#define SAMPLE_RATE 1.0 -std::string readFile(const std::string& path){ - std::ifstream t(path); - std::stringstream buffer; - buffer << t.rdbuf(); - return buffer.str(); -} -#endif -void sentry_setup_user(){ -#ifdef SENTRY - if(!sharedSettings.telemetry()){ - return; - } - sentry_value_t user = sentry_value_new_object(); - sentry_value_set_by_key(user, "id", sentry_value_new_string(readFile("/etc/machine-id").c_str())); - sentry_set_user(user); -#endif -} -void sentry_setup_context(){ -#ifdef SENTRY - if(!sharedSettings.telemetry()){ - return; - } - std::string version = readFile("/etc/version"); - sentry_set_tag("os.version", version.c_str()); - sentry_value_t device = sentry_value_new_object(); - sentry_value_set_by_key(device, "machine-id", sentry_value_new_string(readFile("/etc/machine-id").c_str())); - sentry_value_set_by_key(device, "version", sentry_value_new_string(readFile("/etc/version").c_str())); - sentry_set_context("device", device); -#endif -} #define BITS_PER_LONG (sizeof(long) * 8) #define NBITS(x) ((((x)-1)/BITS_PER_LONG)+1) @@ -61,353 +19,124 @@ void sentry_setup_context(){ #define LONG(x) ((x)/BITS_PER_LONG) #define test_bit(bit, array) ((array[LONG(bit)] >> OFF(bit)) & 1) -static void *invalid_mem = (void *)1; - -void logMachineIdError(int error, QString name, QString path){ - if(error == -ENOENT){ - qWarning() << "/etc/machine-id is missing"; - }else if(error == -ENOMEDIUM){ - qWarning() << path + " is empty or all zeros"; - }else if(error == -EIO){ - qWarning() << path + " has the incorrect format"; - }else if(error == -EPERM){ - qWarning() << path + " access denied"; - } if(error == -EINVAL){ - qWarning() << "Error while reading " + name + ": Buffer invalid"; - }else if(error == -ENXIO){ - qWarning() << "Error while reading " + name + ": No invocation ID is set"; - }else if(error == -EOPNOTSUPP){ - qWarning() << "Error while reading " + name + ": Operation not supported"; - }else{ - qWarning() << "Unexpected error code reading " + name + ":" << strerror(error); - } -} -std::string getAppSpecific(sd_id128_t base){ - QCryptographicHash hash(QCryptographicHash::Sha256); - char buf[SD_ID128_STRING_MAX]; - hash.addData(sd_id128_to_string(base, buf)); - hash.addData(sd_id128_to_string(OXIDE_UID, buf)); - auto r = hash.result(); - r[6] = (r.at(6) & 0x0F) | 0x40; - r[8] = (r.at(8) & 0x3F) | 0x80; - QUuid uid(r.at(0), r.at(1), r.at(2), r.at(3), r.at(4), r.at(5), r.at(6), r.at(7), r.at(8), r.at(9), r.at(10)); - return uid.toString((QUuid::Id128)).toStdString(); -} - namespace Oxide { - void dispatchToMainThread(std::function callback){ - if(QThread::currentThread() == qApp->thread()){ - callback(); - return; - } - // any thread - QTimer* timer = new QTimer(); - timer->moveToThread(qApp->thread()); - timer->setSingleShot(true); - QObject::connect(timer, &QTimer::timeout, [=](){ - // main thread - callback(); - timer->deleteLater(); + QString execute(const QString& program, const QStringList& args){ + QString output; + QProcess p; + p.setProgram(program); + p.setArguments(args); + p.setProcessChannelMode(QProcess::MergedChannels); + p.connect(&p, &QProcess::readyReadStandardOutput, [&p, &output]{ + output += (QString)p.readAllStandardOutput(); }); - QMetaObject::invokeMethod(timer, "start", Qt::BlockingQueuedConnection, Q_ARG(int, 0)); + p.start(); + p.waitForFinished(); + return output; } - namespace Sentry{ -#ifdef SENTRY - static bool initialized = false; - Transaction::Transaction(sentry_transaction_t* t){ - inner = t; - } - Span::Span(sentry_span_t* s){ - inner = s; - } -#else - Transaction::Transaction(void* t){ - Q_UNUSED(t); - inner = nullptr; + // https://stackoverflow.com/a/1643134 + int tryGetLock(char const* lockName){ + mode_t m = umask(0); + int fd = open(lockName, O_RDWR | O_CREAT, 0666); + umask(m); + if(fd < 0){ + return -1; } - Span::Span(void* s){ - Q_UNUSED(s); - inner = nullptr; + if(!flock(fd, LOCK_EX | LOCK_NB)){ + return fd; } -#endif - const char* bootId(){ - static std::string bootId(""); - if(!bootId.empty()){ - return bootId.c_str(); - } - sd_id128_t id; - int ret = sd_id128_get_boot(&id); - // TODO - eventually replace with the following when supported by the - // reMarkable kernel - // int ret = sd_id128_get_boot_app_specific(OXIDE_UID, &id); - if(ret == EXIT_SUCCESS){ - bootId = getAppSpecific(id); - // TODO - eventually replace with the following when supported by the - // reMarkable kernel - //char buf[SD_ID128_STRING_MAX]; - //bootId = sd_id128_to_string(id, buf); - return bootId.c_str(); - } - logMachineIdError(ret, "boot_id", "/proc/sys/kernel/random/boot_id"); - return ""; + close(fd); + return -1; + } + void releaseLock(int fd, char const* lockName){ + if(fd < 0){ + return; } - const char* machineId(){ - static std::string machineId(""); - if(!machineId.empty()){ - return machineId.c_str(); - } - sd_id128_t id; - int ret = sd_id128_get_machine(&id); - // TODO - eventually replace with the following when supported by the - // reMarkable kernel - // int ret = sd_id128_get_machine_app_specific(OXIDE_UID, &id); - if(ret == EXIT_SUCCESS){ - machineId = getAppSpecific(id); - // TODO - eventually replace with the following when supported by the - // reMarkable kernel - //char buf[SD_ID128_STRING_MAX]; - //machineId = sd_id128_to_string(id, buf); - return machineId.c_str(); - } - logMachineIdError(ret, "machine-id", "/etc/machine-id"); - return ""; + if(!flock(fd, F_ULOCK | LOCK_NB)){ + remove(lockName); } - bool enabled(){ - return sharedSettings.crashReport() || sharedSettings.telemetry(); + close(fd); + } + bool processExists(pid_t pid){ return QFile::exists(QString("/proc/%1").arg(pid)); } + QList lsof(const QString& path){ + QList pids; + QDir directory("/proc"); + if (!directory.exists() || directory.isEmpty()){ + qCritical() << "Unable to access /proc"; + return pids; } -#ifdef SENTRY - sentry_options_t* options = sentry_options_new(); -#endif - void sentry_init(const char* name, char* argv[], bool autoSessionTracking){ -#ifdef SENTRY - if(sharedSettings.crashReport()){ - sentry_options_set_sample_rate(options, SAMPLE_RATE); - }else{ - sentry_options_set_sample_rate(options, 0.0); - } - if(sharedSettings.telemetry()){ - sentry_options_set_traces_sample_rate(options, SAMPLE_RATE); - sentry_options_set_max_spans(options, 1000); - }else{ - sentry_options_set_traces_sample_rate(options, 0.0); + QString qpath(QFileInfo(path).canonicalFilePath()); + auto processes = directory.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot | QDir::Readable, QDir::Name); + // Get all pids we care about + for(QFileInfo fi : processes){ + auto pid = fi.baseName().toUInt(); + if(!pid || !processExists(pid)){ + continue; } - if(!sharedSettings.telemetry() && !sharedSettings.crashReport()){ - sentry_user_consent_revoke(); - }else{ - sentry_user_consent_give(); + QFile statm(QString("/proc/%1/statm").arg(pid)); + QTextStream stream(&statm); + if(!statm.open(QIODevice::ReadOnly | QIODevice::Text)){ + continue; } - sentry_options_set_auto_session_tracking(options, autoSessionTracking && sharedSettings.telemetry()); - if(initialized){ - return; + QString content = stream.readAll().trimmed(); + statm.close(); + // Ignore kernel processes + if(content == "0 0 0 0 0 0 0"){ + continue; } - initialized = true; - // Setup options - sentry_options_set_dsn(options, "https://a0136a12d63048c5a353c4a1c2d38914@sentry.eeems.codes/2"); - sentry_options_set_symbolize_stacktraces(options, true); - if(QLibraryInfo::isDebugBuild()){ - sentry_options_set_environment(options, "debug"); - }else{ - sentry_options_set_environment(options, "release"); + QDir fd_directory(QString("/proc/%1/fd").arg(pid)); + if(!fd_directory.exists() || fd_directory.isEmpty()){ + continue; } - sentry_options_set_debug(options, debugEnabled()); - sentry_options_set_database_path(options, "/home/root/.cache/Eeems/sentry"); - sentry_options_set_release(options, (std::string(name) + "@2.4").c_str()); - sentry_init(options); - - // Setup user - sentry_value_t user = sentry_value_new_object(); - sentry_value_set_by_key(user, "id", sentry_value_new_string(machineId())); - sentry_set_user(user); - // Setup context - std::string version = readFile("/etc/version"); - sentry_set_tag("os.version", version.c_str()); - sentry_set_tag("name", name); - sentry_value_t device = sentry_value_new_object(); - sentry_value_set_by_key(device, "machine-id", sentry_value_new_string(machineId())); - sentry_value_set_by_key(device, "version", sentry_value_new_string(readFile("/etc/version").c_str())); - sentry_value_set_by_key(device, "model", sentry_value_new_string(deviceSettings.getDeviceName())); - sentry_set_context("device", device); - // Setup transaction - sentry_set_transaction(name); - // Add close guard - QObject::connect(qApp, &QCoreApplication::aboutToQuit, []() { - sentry_close(); - }); - // Handle settings changing - QObject::connect(&sharedSettings, &SharedSettings::telemetryChanged, [name, argv, autoSessionTracking](bool telemetry){ - Q_UNUSED(telemetry) - if(debugEnabled()){ - qDebug() << "Telemetry changed to" << telemetry; - } - sentry_init(name, argv, autoSessionTracking); - }); - QObject::connect(&sharedSettings, &SharedSettings::crashReportChanged, [name, argv, autoSessionTracking](bool crashReport){ - Q_UNUSED(crashReport) - if(debugEnabled()){ - qDebug() << "CrashReport changed to" << crashReport; + auto fds = fd_directory.entryInfoList(QDir::Files | QDir::NoDot | QDir::NoDotDot); + for(QFileInfo fd : fds){ + if(fd.canonicalFilePath() == qpath){ + pids.append(pid); } - sentry_init(name, argv, autoSessionTracking); - }); -#else - Q_UNUSED(name); - Q_UNUSED(argv); - Q_UNUSED(autoSessionTracking); -#endif - } - void sentry_breadcrumb(const char* category, const char* message, const char* type, const char* level){ -#ifdef SENTRY - if(!sharedSettings.telemetry()){ - return; - } - sentry_value_t crumb = sentry_value_new_breadcrumb(type, message); - sentry_value_set_by_key(crumb, "category", sentry_value_new_string(category)); - sentry_value_set_by_key(crumb, "level", sentry_value_new_string(level)); - sentry_add_breadcrumb(crumb); -#else - Q_UNUSED(category); - Q_UNUSED(message); - Q_UNUSED(type); - Q_UNUSED(level); -#endif - } - Transaction* start_transaction(const std::string& name, const std::string& action){ -#ifdef SENTRY - sentry_transaction_context_t* context = sentry_transaction_context_new(name.c_str(), action.c_str()); - // Hack to force transactions to be reported even though SAMPLE_RATE is 100% - sentry_transaction_context_set_sampled(context, 1); - sentry_transaction_t* transaction = sentry_transaction_start(context, sentry_value_new_null()); - return new Transaction(transaction); -#else - Q_UNUSED(name); - Q_UNUSED(action); - return nullptr; -#endif - } - void stop_transaction(Transaction* transaction){ -#ifdef SENTRY - if(transaction != nullptr && transaction->inner != nullptr){ - sentry_transaction_finish(transaction->inner); } -#else - Q_UNUSED(transaction); -#endif } - void sentry_transaction(const std::string& name, const std::string& action, std::function callback){ -#ifdef SENTRY - if(!sharedSettings.telemetry()){ - callback(nullptr); - return; - } - Transaction* transaction = start_transaction(name, action); - auto scopeGuard = qScopeGuard([transaction] { - stop_transaction(transaction); - }); - callback(transaction); -#else - Q_UNUSED(name); - Q_UNUSED(action); - callback(nullptr); -#endif - } - Span* start_span(Transaction* transaction, const std::string& operation, const std::string& description){ -#ifdef SENTRY - if(transaction == nullptr){ - return nullptr; - } - return new Span(sentry_transaction_start_child(transaction->inner, (char*)operation.c_str(), (char*)description.c_str())); -#else - Q_UNUSED(transaction); - Q_UNUSED(operation); - Q_UNUSED(description); - return nullptr; -#endif - } - Span* start_span(Span* parent, const std::string& operation, const std::string& description){ -#ifdef SENTRY - if(parent == nullptr){ - return nullptr; - } - return new Span(sentry_span_start_child(parent->inner, (char*)operation.c_str(), (char*)description.c_str())); -#else - Q_UNUSED(parent); - Q_UNUSED(operation); - Q_UNUSED(description); - return nullptr; -#endif - } - void stop_span(Span* span){ -#ifdef SENTRY - if(span != nullptr && span->inner != nullptr){ - sentry_span_finish(span->inner); - } -#else - Q_UNUSED(span); -#endif + return pids; + } + void dispatchToMainThread(std::function callback){ + if(QThread::currentThread() == qApp->thread()){ + callback(); + return; } - void sentry_span(Transaction* transaction, const std::string& operation, const std::string& description, std::function callback){ -#ifdef SENTRY - sentry_span(transaction, operation, description, [callback](Span* s){ - Q_UNUSED(s); - callback(); - }); -#else - Q_UNUSED(transaction); - Q_UNUSED(operation); - Q_UNUSED(description); + // any thread + QTimer* timer = new QTimer(); + timer->moveToThread(qApp->thread()); + timer->setSingleShot(true); + QObject::connect(timer, &QTimer::timeout, [=](){ + // main thread callback(); -#endif + timer->deleteLater(); + }); + QMetaObject::invokeMethod(timer, "start", Qt::BlockingQueuedConnection, Q_ARG(int, 0)); + } + uid_t getUID(const QString& name){ + char buffer[1024]; + struct passwd user; + struct passwd* result; + auto status = getpwnam_r(name.toStdString().c_str(), &user, buffer, sizeof(buffer), &result); + if(status != 0){ + throw std::runtime_error("Failed to get user" + status); } - void sentry_span(Transaction* transaction, const std::string& operation, const std::string& description, std::function callback){ -#ifdef SENTRY - if(!sharedSettings.telemetry() || transaction == nullptr || transaction->inner == nullptr){ - callback(nullptr); - return; - } - Span* span = start_span(transaction, operation, description); - auto scopeGuard = qScopeGuard([span] { - stop_span(span); - }); - callback(span); -#else - Q_UNUSED(transaction); - Q_UNUSED(operation); - Q_UNUSED(description); - callback(nullptr); -#endif + if(result == NULL){ + throw std::runtime_error("Invalid user name: " + name.toStdString()); } - void sentry_span(Span* parent, const std::string& operation, const std::string& description, std::function callback){ -#ifdef SENTRY - sentry_span(parent, operation, description, [callback](Span* s){ - Q_UNUSED(s); - callback(); - }); -#else - Q_UNUSED(parent); - Q_UNUSED(operation); - Q_UNUSED(description); - callback(); -#endif + return result->pw_uid; + } + gid_t getGID(const QString& name){ + char buffer[1024]; + struct group grp; + struct group* result; + auto status = getgrnam_r(name.toStdString().c_str(), &grp, buffer, sizeof(buffer), &result); + if(status != 0){ + throw std::runtime_error("Failed to get group" + status); } - - void sentry_span(Span* parent, const std::string& operation, const std::string& description, std::function callback){ -#ifdef SENTRY - if(!sharedSettings.telemetry() || parent == nullptr || parent->inner == nullptr){ - callback(nullptr); - return; - } - Span* span = start_span(parent, operation, description); - auto scopeGuard = qScopeGuard([span] { - stop_span(span); - }); - callback(span); -#else - Q_UNUSED(parent); - Q_UNUSED(operation); - Q_UNUSED(description); - callback(nullptr); -#endif + if(result == NULL){ + throw std::runtime_error("Invalid group name: " + name.toStdString()); } - void trigger_crash(){ memset((char *)invalid_mem, 1, 100); } + return result->gr_gid; } DeviceSettings& DeviceSettings::instance() { static DeviceSettings INSTANCE; @@ -416,50 +145,51 @@ namespace Oxide { DeviceSettings::DeviceSettings(): _deviceType(DeviceType::RM1) { readDeviceType(); + O_DEBUG("Looking for input devices..."); QDir dir("/dev/input"); - qDebug() << "Looking for input devices..."; for(auto path : dir.entryList(QDir::Files | QDir::NoSymLinks | QDir::System)){ - qDebug() << (" Checking " + path + "...").toStdString().c_str(); + O_DEBUG((" Checking " + path + "...").toStdString().c_str()); QString fullPath(dir.path() + "/" + path); QFile device(fullPath); device.open(QIODevice::ReadOnly); int fd = device.handle(); int version; if (ioctl(fd, EVIOCGVERSION, &version)){ - qDebug() << " Invalid"; + O_DEBUG(" Invalid"); continue; } unsigned long bit[EV_MAX]; ioctl(fd, EVIOCGBIT(0, EV_MAX), bit); if (test_bit(EV_KEY, bit)) { if (checkBitSet(fd, EV_KEY, BTN_STYLUS) && test_bit(EV_ABS, bit)) { - qDebug() << " Wacom input device detected"; + O_DEBUG(" Wacom input device detected"); wacomPath = fullPath.toStdString(); continue; } if (checkBitSet(fd, EV_KEY, KEY_POWER)) { - qDebug() << " Buttons input device detected"; + O_DEBUG(" Buttons input device detected"); buttonsPath = fullPath.toStdString(); continue; } } if (checkBitSet(fd, EV_ABS, ABS_MT_SLOT)) { - qDebug() << " Touch input device detected"; + O_DEBUG(" Touch input device detected"); touchPath = fullPath.toStdString(); continue; } - qDebug() << " Invalid"; + O_DEBUG(" Invalid"); } if (wacomPath.empty()) { - qWarning() << "Wacom input device not found"; + O_WARNING("Wacom input device not found"); } if (touchPath.empty()) { - qWarning() << "Touch input device not found"; + O_WARNING("Touch input device not found"); } if (buttonsPath.empty()){ - qWarning() << "Buttons input device not found"; + O_WARNING("Buttons input device not found"); } } + DeviceSettings::~DeviceSettings(){} bool DeviceSettings::checkBitSet(int fd, int type, int i) { unsigned long bit[NBITS(KEY_MAX)]; ioctl(fd, EVIOCGBIT(type, KEY_MAX), bit); @@ -469,18 +199,18 @@ namespace Oxide { void DeviceSettings::readDeviceType() { QFile file("/sys/devices/soc0/machine"); if(!file.exists() || !file.open(QIODevice::ReadOnly | QIODevice::Text)){ - qDebug() << "Couldn't open " << file.fileName(); + O_DEBUG("Couldn't open " << file.fileName()); _deviceType = DeviceType::Unknown; return; } QTextStream in(&file); QString modelName = in.readLine(); if (modelName.startsWith("reMarkable 2")) { - qDebug() << "RM2 detected..."; + O_DEBUG("RM2 detected..."); _deviceType = DeviceType::RM2; return; } - qDebug() << "RM1 detected..."; + O_DEBUG("RM1 detected..."); _deviceType = DeviceType::RM1; } @@ -573,4 +303,8 @@ namespace Oxide { O_SETTINGS_PROPERTY_BODY(SharedSettings, bool, General, telemetry, false) O_SETTINGS_PROPERTY_BODY(SharedSettings, bool, General, applicationUsage, false) O_SETTINGS_PROPERTY_BODY(SharedSettings, bool, General, crashReport, true) + O_SETTINGS_PROPERTY_BODY(SharedSettings, int, General, autoSleep, 1) + O_SETTINGS_PROPERTY_BODY(SharedSettings, QString, Lockscreen, pin) + O_SETTINGS_PROPERTY_BODY(SharedSettings, QString, Lockscreen, onLogin) + O_SETTINGS_PROPERTY_BODY(SharedSettings, QString, Lockscreen, onFailedLogin) } diff --git a/shared/liboxide/liboxide.h b/shared/liboxide/liboxide.h index 6f679e0e6..a29823742 100644 --- a/shared/liboxide/liboxide.h +++ b/shared/liboxide/liboxide.h @@ -1,14 +1,24 @@ /*! - * \file liboxide.h + * \addtogroup Oxide + * \brief The main Oxide module + * @{ + * \file */ -#ifndef LIBOXIDE_H -#define LIBOXIDE_H +#pragma once #include "liboxide_global.h" +#include "meta.h" +#include "dbus.h" +#include "applications.h" #include "settingsfile.h" #include "power.h" +#include "json.h" #include "signalhandler.h" +#include "slothandler.h" +#include "sysobject.h" +#include "debug.h" +#include "oxide_sentry.h" #include #include @@ -16,26 +26,7 @@ #include #include -#define WPA_SUPPLICANT_SERVICE "fi.w1.wpa_supplicant1" -#define WPA_SUPPLICANT_SERVICE_PATH "/fi/w1/wpa_supplicant1" - -#define OXIDE_SERVICE "codes.eeems.oxide1" -#define OXIDE_SERVICE_PATH "/codes/eeems/oxide1" -#define OXIDE_INTERFACE_VERSION "1.0.0" - -#define OXIDE_GENERAL_INTERFACE OXIDE_SERVICE ".General" -#define OXIDE_POWER_INTERFACE OXIDE_SERVICE ".Power" -#define OXIDE_WIFI_INTERFACE OXIDE_SERVICE ".Wifi" -#define OXIDE_NETWORK_INTERFACE OXIDE_SERVICE ".Network" -#define OXIDE_BSS_INTERFACE OXIDE_SERVICE ".BSS" -#define OXIDE_APPS_INTERFACE OXIDE_SERVICE ".Apps" -#define OXIDE_APPLICATION_INTERFACE OXIDE_SERVICE ".Application" -#define OXIDE_SYSTEM_INTERFACE OXIDE_SERVICE ".System" -#define OXIDE_SCREEN_INTERFACE OXIDE_SERVICE ".Screen" -#define OXIDE_NOTIFICATIONS_INTERFACE OXIDE_SERVICE ".Notifications" -#define OXIDE_NOTIFICATION_INTERFACE OXIDE_SERVICE ".Notification" -#define OXIDE_SCREENSHOT_INTERFACE OXIDE_SERVICE ".Screenshot" - +#include /*! * \def deviceSettings() * \brief Get the Oxide::DeviceSettings instance @@ -52,25 +43,46 @@ */ #define sharedSettings Oxide::SharedSettings::instance() -#ifdef SENTRY -#include -#include -#endif /*! * \brief Wifi Network definition */ typedef QMap WifiNetworks; Q_DECLARE_METATYPE(WifiNetworks); -/*! - * \addtogroup Oxide - * \brief The main Oxide namespace - * @{ - */ /*! * \brief The main Oxide namespace */ namespace Oxide { + /*! + * \brief Execute a program and return it's output + * \param Program to run + * \param Arguments to pass to the program + * \return Output if it ran. Otherwise NULL. + */ + LIBOXIDE_EXPORT QString execute(const QString& program, const QStringList& args); + /*! + * \brief Try to get a lock + * \param Path to the lock file + * \return File descriptor of the lock file if a positive number or -1 if it errored + */ + LIBOXIDE_EXPORT int tryGetLock(char const *lockName); + /*! + * \brief Release a lock file + * \param File descriptor of the lock file + * \param Path to the lock file + */ + LIBOXIDE_EXPORT void releaseLock(int fd, char const* lockName); + /*! + * \brief Checks to see if a process exists + * \return If the process exists + */ + LIBOXIDE_EXPORT bool processExists(pid_t pid); + /*! + * \brief Get list of pids that have a file open + * \param File to check + * \return list of pids that have the file open + */ + LIBOXIDE_EXPORT QList lsof(const QString& path); /*! * \brief Run code on the main Qt thread * \param callback The code to run on the main thread @@ -78,48 +90,22 @@ namespace Oxide { * \snippet examples/oxide.cpp dispatchToMainThread */ LIBOXIDE_EXPORT void dispatchToMainThread(std::function callback); - namespace Sentry{ - struct Transaction { -#ifdef SENTRY - /*! - * \brief The sentry_transaction_t instance - */ - sentry_transaction_t* inner; - explicit Transaction(sentry_transaction_t* t); -#else - void* inner; - explicit Transaction(void* t); -#endif - }; - struct Span { -#ifdef SENTRY - /*! - * \brief The sentry_span_t instance - */ - sentry_span_t* inner; - explicit Span(sentry_span_t* s); -#else - void* inner; - explicit Span(void* s); -#endif - }; - - LIBOXIDE_EXPORT const char* bootId(); - LIBOXIDE_EXPORT const char* machineId(); - LIBOXIDE_EXPORT void sentry_init(const char* name, char* argv[], bool autoSessionTracking = true); - LIBOXIDE_EXPORT void sentry_breadcrumb(const char* category, const char* message, const char* type = "default", const char* level = "info"); - LIBOXIDE_EXPORT Transaction* start_transaction(const std::string& name, const std::string& action); - LIBOXIDE_EXPORT void stop_transaction(Transaction* transaction); - LIBOXIDE_EXPORT void sentry_transaction(const std::string& name, const std::string& action, std::function callback); - LIBOXIDE_EXPORT Span* start_span(Transaction* transaction, const std::string& operation, const std::string& description); - LIBOXIDE_EXPORT Span* start_span(Span* parent, const std::string& operation, const std::string& description); - LIBOXIDE_EXPORT void stop_span(Span* span); - LIBOXIDE_EXPORT void sentry_span(Transaction* transaction, const std::string& operation, const std::string& description, std::function callback); - LIBOXIDE_EXPORT void sentry_span(Transaction* transaction, const std::string& operation, const std::string& description, std::function callback); - LIBOXIDE_EXPORT void sentry_span(Span* parent, const std::string& operation, const std::string& description, std::function callback); - LIBOXIDE_EXPORT void sentry_span(Span* parent, const std::string& operation, const std::string& description, std::function callback); - LIBOXIDE_EXPORT void trigger_crash(); - } + /*! + * \brief Get the UID for a username + * \param Username to search for + * \throws std::runtime_error Failed to get the UID for the username + * \return The UID for the username + * \snippet examples/oxide.cpp getUID + */ + LIBOXIDE_EXPORT uid_t getUID(const QString& name); + /*! + * \brief Get the GID for a groupname + * \param Groupname to search for + * \throws std::runtime_error Failed to get the GID for the groupname + * \return The GID for the groupname + * \snippet examples/oxide.cpp getGID + */ + LIBOXIDE_EXPORT gid_t getGID(const QString& name); /*! * \brief Device specific values */ @@ -184,7 +170,7 @@ namespace Oxide { DeviceType _deviceType; DeviceSettings(); - ~DeviceSettings() {}; + ~DeviceSettings(); void readDeviceType(); bool checkBitSet(int fd, int type, int i); std::string buttonsPath = ""; @@ -241,6 +227,7 @@ namespace Oxide { * \sa setWifinetworks, wifinetworksChanged */ Q_PROPERTY(WifiNetworks wifinetworks MEMBER m_wifinetworks READ wifinetworks WRITE setWifinetworks RESET resetWifinetworks NOTIFY wifinetworksChanged) + public: WifiNetworks wifinetworks(); /*! @@ -261,11 +248,13 @@ namespace Oxide { */ void setWifiNetwork(const QString& name, QVariantMap properties); void resetWifinetworks(); + signals: /*! * \brief The contents of the wifi network list has changed */ void wifinetworksChanged(WifiNetworks); + private: ~XochitlSettings(); WifiNetworks m_wifinetworks; @@ -283,7 +272,30 @@ namespace Oxide { */ // cppcheck-suppress uninitMemberVarPrivate O_SETTINGS(SharedSettings, "/home/root/.config/Eeems/shared.conf") + /*! + * \property version + * \brief Current version of the settings file + * \sa set_version, versionChanged + */ + /*! + * \fn versionChanged + * \brief If the version number has changed + */ O_SETTINGS_PROPERTY(int, General, version) + /*! + * \property firstLaunch + * \brief If this is the first time that things have been run + * \sa set_firstLaunch, firstLaunchChanged + */ + /*! + * \fn set_firstLaunch + * \param _arg_firstLaunch + * \brief Change the state of firstLaunch + */ + /*! + * \fn firstLaunchChanged + * \brief If firstLaunch has changed + */ O_SETTINGS_PROPERTY(bool, General, firstLaunch, true) /*! * \property telemetry @@ -323,16 +335,91 @@ namespace Oxide { /*! * \fn set_crashReport * \param _arg_crashReport - * \brief Enable or disable crash reporting + * \brief Enable or disable crash reporting */ /*! * \fn crashReportChanged * \brief If crash reporting has been enabled or disabled */ O_SETTINGS_PROPERTY(bool, General, crashReport, true) + /*! + * \property autoSleep + * \brief How long without activity before the device should suspend + * \sa set_autoSleep, autoSleepChanged + */ + /*! + * \fn set_autoSleep + * \param _arg_autoSleep + * \brief Change autoSleep + */ + /*! + * \fn autoSleepChanged + * \brief If autoSleep has been changed + */ + O_SETTINGS_PROPERTY(int, General, autoSleep, 1) + /*! + * \property pin + * \brief The lockscreen pin + * \sa set_pin, pinChanged + */ + /*! + * \fn set_pin + * \param _arg_pin + * \brief Change lockscreen pin + */ + /*! + * \fn has_pin + * \brief Change lockscreen pin + * \return If the lockscreen pin is set + */ + /*! + * \fn pinChanged + * \brief If the lockscreen pin has been changed + */ + O_SETTINGS_PROPERTY(QString, Lockscreen, pin) + /*! + * \property onLogin + * \brief The lockscreen onLogin + * \sa set_onLogin, onLoginChanged + */ + /*! + * \fn set_onLogin + * \param _arg_onLogin + * \brief Change lockscreen onLogin + */ + /*! + * \fn has_onLogin + * \brief If lockscreen onLogin has been set + * \return If the lockscreen onLogin is set + */ + /*! + * \fn onLoginChanged + * \brief If the lockscreen onLogin has been changed + */ + O_SETTINGS_PROPERTY(QString, Lockscreen, onLogin) + /*! + * \property onFailedLogin + * \brief The lockscreen onFailedLogin + * \sa set_onFailedLogin, onFailedLoginChanged + */ + /*! + * \fn set_onFailedLogin + * \param _arg_onFailedLogin + * \brief Change lockscreen onFailedLogin + */ + /*! + * \fn has_onFailedLogin + * \brief If lockscreen onFailedLogin has been set + * \return If the lockscreen onFailedLogin is set + */ + /*! + * \fn onFailedLoginChanged + * \brief If the lockscreen onFailedLogin has been changed + */ + O_SETTINGS_PROPERTY(QString, Lockscreen, onFailedLogin) + private: ~SharedSettings(); }; } /*! @} */ -#endif // LIBOXIDE_H diff --git a/shared/liboxide/liboxide.pro b/shared/liboxide/liboxide.pro index 8d73bad01..e262925b0 100644 --- a/shared/liboxide/liboxide.pro +++ b/shared/liboxide/liboxide.pro @@ -1,48 +1,87 @@ QT -= gui QT += quick +QT += dbus TEMPLATE = lib DEFINES += LIBOXIDE_LIBRARY CONFIG += c++11 +CONFIG += warn_on +CONFIG += precompile_header DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 SOURCES += \ + applications.cpp \ + debug.cpp \ eventfilter.cpp \ + json.cpp \ liboxide.cpp \ + oxide_sentry.cpp \ power.cpp \ settingsfile.cpp \ + slothandler.cpp \ sysobject.cpp \ signalhandler.cpp HEADERS += \ + applications.h \ + dbus.h \ + debug.h \ eventfilter.h \ liboxide_global.h \ liboxide.h \ + meta.h \ + oxide_sentry.h \ power.h \ + json.h \ settingsfile.h \ + slothandler.h \ sysobject.h \ signalhandler.h +PRECOMPILED_HEADER = \ + liboxide_stable.h + +DBUS_INTERFACES += \ + ../../interfaces/dbusservice.xml \ + ../../interfaces/powerapi.xml \ + ../../interfaces/wifiapi.xml \ + ../../interfaces/network.xml \ + ../../interfaces/bss.xml \ + ../../interfaces/appsapi.xml \ + ../../interfaces/application.xml \ + ../../interfaces/systemapi.xml \ + ../../interfaces/screenapi.xml \ + ../../interfaces/screenshot.xml \ + ../../interfaces/notificationapi.xml \ + ../../interfaces/notification.xml + LIBS += -lsystemd -INCLUDEPATH += ../../shared -LIBS += -L$$PWD/../../shared/ -lqsgepaper -INCLUDEPATH += $$PWD/../../shared -DEPENDPATH += $$PWD/../../shared +liboxide_liboxide_h.target = include/liboxide/liboxide.h +liboxide_liboxide_h.commands = \ + mkdir -p include/liboxide && \ + echo $$HEADERS | xargs -rn1 | xargs -rI {} cp $$PWD/{} include/liboxide/ && \ + echo $$DBUS_INTERFACES | xargs -rn1 | xargs -rI {} basename \"{}\" .xml | xargs -rI {} cp $$OUT_PWD/\"{}\"_interface.h include/liboxide/ + +liboxide_h.target = include/liboxide.h +liboxide_h.depends += liboxide_liboxide_h +liboxide_h.commands = \ + echo \\$$LITERAL_HASH"pragma once" > include/liboxide.h && \ + echo \"$$LITERAL_HASH"include \\\"liboxide/liboxide.h\\\"\"" >> include/liboxide.h -exists($$PWD/../../.build/sentry) { - LIBS += -L$$PWD/../../.build/sentry/lib -lsentry -ldl -lcurl -lbreakpad_client - INCLUDEPATH += $$PWD/../../.build/sentry/include - DEPENDPATH += $$PWD/../../.build/sentry/lib +clean_headers.target = include/.clean-target +clean_headers.commands = rm -rf include - library.files = ../../.build/sentry/libsentry.so - library.path = /opt/lib - INSTALLS += library -} +QMAKE_EXTRA_TARGETS += liboxide_liboxide_h liboxide_h clean_headers +PRE_TARGETDEPS += $$clean_headers.target +POST_TARGETDEPS += $$liboxide_liboxide_h.target $$liboxide_h.target +QMAKE_CLEAN += $$liboxide_h.target include/liboxide/*.h -target.path = /opt/usr/lib -!isEmpty(target.path): INSTALLS += target +include(../../qmake/common.pri) +target.path = /opt/lib +INSTALLS += target -VERSION = 2.5 +include(../../qmake/epaper.pri) +include(../../qmake/sentry.pri) diff --git a/shared/liboxide/liboxide_global.h b/shared/liboxide/liboxide_global.h index 45e6be73f..719e84775 100644 --- a/shared/liboxide/liboxide_global.h +++ b/shared/liboxide/liboxide_global.h @@ -1,9 +1,9 @@ /*! - * \file liboxide_global.h + * \addtogroup Oxide + * @{ + * \file */ -#ifndef LIBOXIDE_GLOBAL_H -#define LIBOXIDE_GLOBAL_H - +#pragma once #include @@ -15,7 +15,7 @@ # endif #else # define SENTRY +# define DEBUG # define LIBOXIDE_EXPORT #endif - -#endif // LIBOXIDE_GLOBAL_H +/*! @} */ diff --git a/shared/liboxide/liboxide_stable.h b/shared/liboxide/liboxide_stable.h new file mode 100644 index 000000000..aff608cf5 --- /dev/null +++ b/shared/liboxide/liboxide_stable.h @@ -0,0 +1,67 @@ +#if defined __cplusplus +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "application_interface.h" +#include "appsapi_interface.h" +#include "bss_interface.h" +#include "dbusservice_interface.h" +#include "network_interface.h" +#include "notification_interface.h" +#include "notificationapi_interface.h" +#include "powerapi_interface.h" +#include "systemapi_interface.h" +#include "wifiapi_interface.h" + +#include "liboxide_global.h" +#include "meta.h" +#endif diff --git a/shared/liboxide/meta.h b/shared/liboxide/meta.h new file mode 100644 index 000000000..2a7a8150a --- /dev/null +++ b/shared/liboxide/meta.h @@ -0,0 +1,94 @@ +/*! + * \addtogroup Oxide + * @{ + * \file + */ + +#pragma once +/*! + * \def WPA_SUPPLICANT_SERVICE + * \brief wpa_supplicant DBus service name + */ +#define WPA_SUPPLICANT_SERVICE "fi.w1.wpa_supplicant1" +/*! + * \def WPA_SUPPLICANT_SERVICE_PATH + * \brief wpa_supplicant DBus service path + */ +#define WPA_SUPPLICANT_SERVICE_PATH "/fi/w1/wpa_supplicant1" + +/*! + * \def OXIDE_SERVICE + * \brief DBus service for tarnish + */ +#define OXIDE_SERVICE "codes.eeems.oxide1" +/*! + * \def OXIDE_SERVICE_PATH + * \brief DBus path for tarnish + */ +#define OXIDE_SERVICE_PATH "/codes/eeems/oxide1" +/*! + * \def OXIDE_INTERFACE_VERSION + * \brief Version of Tarnish and liboxide + */ +#define OXIDE_INTERFACE_VERSION "2.5.0" +/*! + * \def OXIDE_GENERAL_INTERFACE + * \brief DBus service for the general API + */ +#define OXIDE_GENERAL_INTERFACE OXIDE_SERVICE ".General" +/*! + * \def OXIDE_APPS_INTERFACE + * \brief DBus service for the apps API + */ +#define OXIDE_APPS_INTERFACE OXIDE_SERVICE ".Apps" +/*! + * \def OXIDE_NOTIFICATIONS_INTERFACE + * \brief DBus service for the notifications API + */ +#define OXIDE_NOTIFICATIONS_INTERFACE OXIDE_SERVICE ".Notifications" +/*! + * \def OXIDE_POWER_INTERFACE + * \brief DBus service for the power API + */ +#define OXIDE_POWER_INTERFACE OXIDE_SERVICE ".Power" +/*! + * \def OXIDE_SCREEN_INTERFACE + * \brief DBus service for the screen API + */ +#define OXIDE_SCREEN_INTERFACE OXIDE_SERVICE ".Screen" +/*! + * \def OXIDE_SYSTEM_INTERFACE + * \brief DBus service for the system API + */ +#define OXIDE_SYSTEM_INTERFACE OXIDE_SERVICE ".System" +/*! + * \def OXIDE_WIFI_INTERFACE + * \brief DBus service for the wifi API + */ +#define OXIDE_WIFI_INTERFACE OXIDE_SERVICE ".Wifi" +/*! + * \def OXIDE_APPLICATION_INTERFACE + * \brief DBus service for an application object + */ +#define OXIDE_APPLICATION_INTERFACE OXIDE_SERVICE ".Application" +/*! + * \def OXIDE_BSS_INTERFACE + * \brief DBus service for a bss object + */ +#define OXIDE_BSS_INTERFACE OXIDE_SERVICE ".BSS" +/*! + * \def OXIDE_NETWORK_INTERFACE + * \brief DBus service for a network object + */ +#define OXIDE_NETWORK_INTERFACE OXIDE_SERVICE ".Network" +/*! + * \def OXIDE_NOTIFICATION_INTERFACE + * \brief DBus service for a notification object + */ +#define OXIDE_NOTIFICATION_INTERFACE OXIDE_SERVICE ".Notification" +/*! + * \def OXIDE_SCREENSHOT_INTERFACE + * \brief DBus service for a screenshot object + */ +#define OXIDE_SCREENSHOT_INTERFACE OXIDE_SERVICE ".Screenshot" +/*! @} */ diff --git a/shared/liboxide/oxide_sentry.cpp b/shared/liboxide/oxide_sentry.cpp new file mode 100644 index 000000000..0fd2b4268 --- /dev/null +++ b/shared/liboxide/oxide_sentry.cpp @@ -0,0 +1,354 @@ +#include "oxide_sentry.h" +#include "liboxide.h" + +#include +#include +#include +#include +#include + +#include +#include +#include + +// String: 5aa5ca39ee0b4f48927529ca17519524 +// UUID: 5aa5ca39-ee0b-4f48-9275-29ca17519524 +#define OXIDE_UID SD_ID128_MAKE(5a,a5,ca,39,ee,0b,4f,48,92,75,29,ca,17,51,95,24) + +#ifdef SENTRY +#define SAMPLE_RATE 1.0 +std::string readFile(const std::string& path){ + std::ifstream t(path); + std::stringstream buffer; + buffer << t.rdbuf(); + return buffer.str(); +} +#endif + +static void* invalid_mem = (void *)1; + +void logMachineIdError(int error, QString name, QString path){ + if(error == -ENOENT){ + O_WARNING("/etc/machine-id is missing"); + }else if(error == -ENOMEDIUM){ + O_WARNING(path + " is empty or all zeros"); + }else if(error == -EIO){ + O_WARNING(path + " has the incorrect format"); + }else if(error == -EPERM){ + O_WARNING(path + " access denied"); + } if(error == -EINVAL){ + O_WARNING("Error while reading " + name + ": Buffer invalid"); + }else if(error == -ENXIO){ + O_WARNING("Error while reading " + name + ": No invocation ID is set"); + }else if(error == -EOPNOTSUPP){ + O_WARNING("Error while reading " + name + ": Operation not supported"); + }else{ + O_WARNING("Unexpected error code reading " + name + ":" << strerror(error)); + } +} +std::string getAppSpecific(sd_id128_t base){ + QCryptographicHash hash(QCryptographicHash::Sha256); + char buf[SD_ID128_STRING_MAX]; + hash.addData(sd_id128_to_string(base, buf)); + hash.addData(sd_id128_to_string(OXIDE_UID, buf)); + auto r = hash.result(); + r[6] = (r.at(6) & 0x0F) | 0x40; + r[8] = (r.at(8) & 0x3F) | 0x80; + QUuid uid(r.at(0), r.at(1), r.at(2), r.at(3), r.at(4), r.at(5), r.at(6), r.at(7), r.at(8), r.at(9), r.at(10)); + return uid.toString((QUuid::Id128)).toStdString(); +} + +namespace Oxide::Sentry{ +#ifdef SENTRY + static bool initialized = false; + Transaction::Transaction(sentry_transaction_t* t){ + inner = t; + } + Span::Span(sentry_span_t* s){ + inner = s; + } +#else + Transaction::Transaction(void* t){ + Q_UNUSED(t); + inner = nullptr; + } + Span::Span(void* s){ + Q_UNUSED(s); + inner = nullptr; + } +#endif + const char* bootId(){ + static std::string bootId(""); + if(!bootId.empty()){ + return bootId.c_str(); + } + sd_id128_t id; + int ret = sd_id128_get_boot(&id); + // TODO - eventually replace with the following when supported by the + // reMarkable kernel + // int ret = sd_id128_get_boot_app_specific(OXIDE_UID, &id); + if(ret == EXIT_SUCCESS){ + bootId = getAppSpecific(id); + // TODO - eventually replace with the following when supported by the + // reMarkable kernel + //char buf[SD_ID128_STRING_MAX]; + //bootId = sd_id128_to_string(id, buf); + return bootId.c_str(); + } + logMachineIdError(ret, "boot_id", "/proc/sys/kernel/random/boot_id"); + return ""; + } + const char* machineId(){ + static std::string machineId(""); + if(!machineId.empty()){ + return machineId.c_str(); + } + sd_id128_t id; + int ret = sd_id128_get_machine(&id); + // TODO - eventually replace with the following when supported by the + // reMarkable kernel + // int ret = sd_id128_get_machine_app_specific(OXIDE_UID, &id); + if(ret == EXIT_SUCCESS){ + machineId = getAppSpecific(id); + // TODO - eventually replace with the following when supported by the + // reMarkable kernel + //char buf[SD_ID128_STRING_MAX]; + //machineId = sd_id128_to_string(id, buf); + return machineId.c_str(); + } + logMachineIdError(ret, "machine-id", "/etc/machine-id"); + return ""; + } + bool enabled(){ + return sharedSettings.crashReport() || sharedSettings.telemetry(); + } +#ifdef SENTRY + sentry_options_t* options = sentry_options_new(); +#endif + void sentry_init(const char* name, char* argv[], bool autoSessionTracking){ +#ifdef SENTRY + if(sharedSettings.crashReport()){ + sentry_options_set_sample_rate(options, SAMPLE_RATE); + }else{ + sentry_options_set_sample_rate(options, 0.0); + } + if(sharedSettings.telemetry()){ + sentry_options_set_traces_sample_rate(options, SAMPLE_RATE); + sentry_options_set_max_spans(options, 1000); + }else{ + sentry_options_set_traces_sample_rate(options, 0.0); + } + if(!sharedSettings.telemetry() && !sharedSettings.crashReport()){ + sentry_user_consent_revoke(); + }else{ + sentry_user_consent_give(); + } + sentry_options_set_auto_session_tracking(options, autoSessionTracking && sharedSettings.telemetry()); + if(initialized){ + return; + } + initialized = true; + // Setup options + sentry_options_set_dsn(options, "https://a0136a12d63048c5a353c4a1c2d38914@sentry.eeems.codes/2"); + sentry_options_set_symbolize_stacktraces(options, true); + if(QLibraryInfo::isDebugBuild()){ + sentry_options_set_environment(options, "debug"); + }else{ + sentry_options_set_environment(options, "release"); + } + sentry_options_set_debug(options, debugEnabled()); + sentry_options_set_database_path(options, "/home/root/.cache/Eeems/sentry"); + sentry_options_set_release(options, (std::string(name) + "@2.4").c_str()); + sentry_init(options); + + // Setup user + sentry_value_t user = sentry_value_new_object(); + sentry_value_set_by_key(user, "id", sentry_value_new_string(machineId())); + sentry_set_user(user); + // Setup context + std::string version = readFile("/etc/version"); + sentry_set_tag("os.version", version.c_str()); + sentry_set_tag("name", name); + sentry_value_t device = sentry_value_new_object(); + sentry_value_set_by_key(device, "machine-id", sentry_value_new_string(machineId())); + sentry_value_set_by_key(device, "version", sentry_value_new_string(readFile("/etc/version").c_str())); + sentry_value_set_by_key(device, "model", sentry_value_new_string(deviceSettings.getDeviceName())); + sentry_set_context("device", device); + // Setup transaction + sentry_set_transaction(name); + // Add close guard + QObject::connect(qApp, &QCoreApplication::aboutToQuit, []() { + sentry_close(); + }); + // Handle settings changing + QObject::connect(&sharedSettings, &SharedSettings::telemetryChanged, [name, argv, autoSessionTracking](bool telemetry){ + Q_UNUSED(telemetry) + O_DEBUG("Telemetry changed to" << telemetry); + sentry_init(name, argv, autoSessionTracking); + }); + QObject::connect(&sharedSettings, &SharedSettings::crashReportChanged, [name, argv, autoSessionTracking](bool crashReport){ + Q_UNUSED(crashReport) + O_DEBUG("CrashReport changed to" << crashReport); + sentry_init(name, argv, autoSessionTracking); + }); +#else + Q_UNUSED(name); + Q_UNUSED(argv); + Q_UNUSED(autoSessionTracking); +#endif + } + void sentry_breadcrumb(const char* category, const char* message, const char* type, const char* level){ +#ifdef SENTRY + if(!sharedSettings.telemetry()){ + return; + } + sentry_value_t crumb = sentry_value_new_breadcrumb(type, message); + sentry_value_set_by_key(crumb, "category", sentry_value_new_string(category)); + sentry_value_set_by_key(crumb, "level", sentry_value_new_string(level)); + sentry_add_breadcrumb(crumb); +#else + Q_UNUSED(category); + Q_UNUSED(message); + Q_UNUSED(type); + Q_UNUSED(level); +#endif + } + Transaction* start_transaction(const std::string& name, const std::string& action){ +#ifdef SENTRY + sentry_transaction_context_t* context = sentry_transaction_context_new(name.c_str(), action.c_str()); + // Hack to force transactions to be reported even though SAMPLE_RATE is 100% + sentry_transaction_context_set_sampled(context, 1); + sentry_transaction_t* transaction = sentry_transaction_start(context, sentry_value_new_null()); + return new Transaction(transaction); +#else + Q_UNUSED(name); + Q_UNUSED(action); + return nullptr; +#endif + } + void stop_transaction(Transaction* transaction){ +#ifdef SENTRY + if(transaction != nullptr && transaction->inner != nullptr){ + sentry_transaction_finish(transaction->inner); + } +#else + Q_UNUSED(transaction); +#endif + } + void sentry_transaction(const std::string& name, const std::string& action, std::function callback){ +#ifdef SENTRY + if(!sharedSettings.telemetry()){ + callback(nullptr); + return; + } + Transaction* transaction = start_transaction(name, action); + auto scopeGuard = qScopeGuard([transaction] { + stop_transaction(transaction); + }); + callback(transaction); +#else + Q_UNUSED(name); + Q_UNUSED(action); + callback(nullptr); +#endif + } + Span* start_span(Transaction* transaction, const std::string& operation, const std::string& description){ +#ifdef SENTRY + if(transaction == nullptr){ + return nullptr; + } + return new Span(sentry_transaction_start_child(transaction->inner, (char*)operation.c_str(), (char*)description.c_str())); +#else + Q_UNUSED(transaction); + Q_UNUSED(operation); + Q_UNUSED(description); + return nullptr; +#endif + } + Span* start_span(Span* parent, const std::string& operation, const std::string& description){ +#ifdef SENTRY + if(parent == nullptr){ + return nullptr; + } + return new Span(sentry_span_start_child(parent->inner, (char*)operation.c_str(), (char*)description.c_str())); +#else + Q_UNUSED(parent); + Q_UNUSED(operation); + Q_UNUSED(description); + return nullptr; +#endif + } + void stop_span(Span* span){ +#ifdef SENTRY + if(span != nullptr && span->inner != nullptr){ + sentry_span_finish(span->inner); + } +#else + Q_UNUSED(span); +#endif + } + void sentry_span(Transaction* transaction, const std::string& operation, const std::string& description, std::function callback){ +#ifdef SENTRY + sentry_span(transaction, operation, description, [callback](Span* s){ + Q_UNUSED(s); + callback(); + }); +#else + Q_UNUSED(transaction); + Q_UNUSED(operation); + Q_UNUSED(description); + callback(); +#endif + } + void sentry_span(Transaction* transaction, const std::string& operation, const std::string& description, std::function callback){ +#ifdef SENTRY + if(!sharedSettings.telemetry() || transaction == nullptr || transaction->inner == nullptr){ + callback(nullptr); + return; + } + Span* span = start_span(transaction, operation, description); + auto scopeGuard = qScopeGuard([span] { + stop_span(span); + }); + callback(span); +#else + Q_UNUSED(transaction); + Q_UNUSED(operation); + Q_UNUSED(description); + callback(nullptr); +#endif + } + void sentry_span(Span* parent, const std::string& operation, const std::string& description, std::function callback){ +#ifdef SENTRY + sentry_span(parent, operation, description, [callback](Span* s){ + Q_UNUSED(s); + callback(); + }); +#else + Q_UNUSED(parent); + Q_UNUSED(operation); + Q_UNUSED(description); + callback(); +#endif + } + + void sentry_span(Span* parent, const std::string& operation, const std::string& description, std::function callback){ +#ifdef SENTRY + if(!sharedSettings.telemetry() || parent == nullptr || parent->inner == nullptr){ + callback(nullptr); + return; + } + Span* span = start_span(parent, operation, description); + auto scopeGuard = qScopeGuard([span] { + stop_span(span); + }); + callback(span); +#else + Q_UNUSED(parent); + Q_UNUSED(operation); + Q_UNUSED(description); + callback(nullptr); +#endif + } + void trigger_crash(){ memset((char *)invalid_mem, 1, 100); } +} diff --git a/shared/liboxide/oxide_sentry.h b/shared/liboxide/oxide_sentry.h new file mode 100644 index 000000000..cd30a415d --- /dev/null +++ b/shared/liboxide/oxide_sentry.h @@ -0,0 +1,163 @@ +/*! + * \addtogroup Sentry + * \brief The Sentry module + * @{ + * \file + */ +#pragma once + +#include "liboxide_global.h" + +#include +#include +#include +#include + +#ifdef SENTRY +#include +#endif +/*! + *\brief The Sentry namespace + */ +namespace Oxide::Sentry{ + /*! + * \brief A sentry_transaction_t wrapper + */ + struct Transaction { +#ifdef SENTRY + /*! + * \brief The sentry_transaction_t instance + */ + sentry_transaction_t* inner; + /*! + * \brief Create a sentry_transaction_t wrapper + * \param sentry_transaction_t instance to wrap + */ + explicit Transaction(sentry_transaction_t* t); +#else + void* inner; + explicit Transaction(void* t); +#endif + }; + /*! + * \brief A sentry_span_t wrapper + */ + struct Span { +#ifdef SENTRY + /*! + * \brief The sentry_span_t instance + */ + sentry_span_t* inner; + /*! + * \brief Create a sentry_span_t wrapper + * \param The sentry_span_t instance to wrap + */ + explicit Span(sentry_span_t* s); +#else + void* inner; + explicit Span(void* s); +#endif + }; + /*! + * \brief Get the boot identifier of the device using sd_id128_get_boot + * \return The boot identifier + */ + LIBOXIDE_EXPORT const char* bootId(); + /*! + * \brief Get the machine identifier of the device using sd_id128_get_machine + * \return The machine identifier + */ + LIBOXIDE_EXPORT const char* machineId(); + /*! + * \brief Initialize sentry tracking + * \param Name of the application + * \param Arguments passed to the application + * \param If automatic session tracking should be enabled + */ + LIBOXIDE_EXPORT void sentry_init(const char* name, char* argv[], bool autoSessionTracking = true); + /*! + * \brief Create a breadcrumb in the current sentry transaction + * \param Category of the breadcrumb + * \param Message of the breadcrumb + * \param Type of breadcrumb + * \param Logging level of the breadcrumb + */ + LIBOXIDE_EXPORT void sentry_breadcrumb(const char* category, const char* message, const char* type = "default", const char* level = "info"); + /*! + * \brief Start a transaction + * \param Name of the transaction + * \param Action being performed + * \return The transaction wrapper + */ + LIBOXIDE_EXPORT Transaction* start_transaction(const std::string& name, const std::string& action); + /*! + * \brief Stop a sentry transaction + * \param The transaction wrapper to stop + */ + LIBOXIDE_EXPORT void stop_transaction(Transaction* transaction); + /*! + * \brief Record a sentry trancation + * \param Name of the transaction + * \param Action being performed + * \param Code to run inside the transaction + */ + LIBOXIDE_EXPORT void sentry_transaction(const std::string& name, const std::string& action, std::function callback); + /*! + * \brief Start a span inside a sentry transaction + * \param Transaction wrapper to attach the span to + * \param Operation being performed + * \param Description of the span + * \return The span wrapper + */ + LIBOXIDE_EXPORT Span* start_span(Transaction* transaction, const std::string& operation, const std::string& description); + /*! + * \brief Start a span inside another sentry span + * \param The parent sentry span wrapper + * \param Operation being performed + * \param Description of the span + * \return The span wrapper + */ + LIBOXIDE_EXPORT Span* start_span(Span* parent, const std::string& operation, const std::string& description); + /*! + * \brief Stop a sentry span + * \param The span wrapper to stop + */ + LIBOXIDE_EXPORT void stop_span(Span* span); + /*! + * \brief Record a sentry span + * \param The transaction wrapper to attach this span to + * \param Operation being performed + * \param Description of the span + * \param Code to run inside the transaction + */ + LIBOXIDE_EXPORT void sentry_span(Transaction* transaction, const std::string& operation, const std::string& description, std::function callback); + /*! + * \brief Record a sentry span + * \param The transaction wrapper to attach this span to + * \param Operation being performed + * \param Description of the span + * \param Code to run inside the transaction + */ + LIBOXIDE_EXPORT void sentry_span(Transaction* transaction, const std::string& operation, const std::string& description, std::function callback); + /*! + * \brief Record a sentry span + * \param The span wrapper to attach this span to + * \param Operation being performed + * \param Description of the span + * \param Code to run inside the transaction + */ + LIBOXIDE_EXPORT void sentry_span(Span* parent, const std::string& operation, const std::string& description, std::function callback); + /*! + * \brief Record a sentry span + * \param The span wrapper to attach this span to + * \param Operation being performed + * \param Description of the span + * \param Code to run inside the transaction + */ + LIBOXIDE_EXPORT void sentry_span(Span* parent, const std::string& operation, const std::string& description, std::function callback); + /*! + * \brief Trigger a crash. Useful to test that sentry integration is working + */ + LIBOXIDE_EXPORT void trigger_crash(); +} +/*! @} */ diff --git a/shared/liboxide/power.cpp b/shared/liboxide/power.cpp index be7f30cb0..64dfa6697 100644 --- a/shared/liboxide/power.cpp +++ b/shared/liboxide/power.cpp @@ -1,8 +1,8 @@ #include "power.h" +#include "debug.h" #include #include -#include using Oxide::SysObject; @@ -25,27 +25,27 @@ void _setup(){ _chargers = new QList(); } QDir dir("/sys/class/power_supply"); - qDebug() << "Looking for batteries and chargers..."; + O_DEBUG("Looking for batteries and chargers..."); for(auto& path : dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot | QDir::Readable)){ - qDebug() << (" Checking " + path + "...").toStdString().c_str(); + O_DEBUG((" Checking " + path + "...").toStdString().c_str()); SysObject item(dir.path() + "/" + path); if(!item.hasProperty("type")){ - qDebug() << " Missing type property"; + O_DEBUG(" Missing type property"); continue; } if(item.hasProperty("present") && !item.intProperty("present")){ - qDebug() << " Either missing present property, or battery is not present"; + O_DEBUG(" Either missing present property, or battery is not present"); continue; } auto type = item.strProperty("type"); if(type == "Battery"){ - qDebug() << " Found Battery!"; + O_DEBUG(" Found Battery!"); _batteries->append(item); }else if(type == "USB" || type == "USB_CDP"){ - qDebug() << " Found Charger!"; + O_DEBUG(" Found Charger!"); _chargers->append(item); }else{ - qDebug() << " Unknown type"; + O_DEBUG(" Unknown type"); } } if(_chargers->empty()){ @@ -156,5 +156,5 @@ namespace Oxide::Power { } bool batteryHasWarning(){ return batteryWarning().length(); } bool batteryHasAlert(){ return batteryAlert().length(); } - bool chargerConnected(){ return _chargerInt("online") || batteryCharging(); } + bool chargerConnected(){ return batteryCharging() || _chargerInt("online"); } } diff --git a/shared/liboxide/power.h b/shared/liboxide/power.h index 80ece4189..c4b849b71 100644 --- a/shared/liboxide/power.h +++ b/shared/liboxide/power.h @@ -1,11 +1,12 @@ /*! - * \file power.h + * \addtogroup Power + * \brief The Power module + * @{ + * \file */ -#ifndef POWER_H -#define POWER_H +#pragma once #include "liboxide_global.h" -#include #include #include @@ -72,5 +73,4 @@ namespace Oxide::Power { */ LIBOXIDE_EXPORT bool chargerConnected(); } - -#endif // POWER_H +/*! @} */ diff --git a/shared/liboxide/settingsfile.cpp b/shared/liboxide/settingsfile.cpp index eb6753be0..e8223351e 100644 --- a/shared/liboxide/settingsfile.cpp +++ b/shared/liboxide/settingsfile.cpp @@ -1,69 +1,69 @@ #include -#include #include "settingsfile.h" +#include "debug.h" namespace Oxide { - bool debugEnabled(){ - if(getenv("DEBUG") == NULL){ - return false; - } - QString env = qgetenv("DEBUG"); - return !(QStringList() << "0" << "n" << "no" << "false").contains(env.toLower()); - } SettingsFile::SettingsFile(QString path) : QSettings(path, QSettings::IniFormat), - fileWatcher(QStringList() << path) - { - - } - SettingsFile::~SettingsFile(){ - - } + reloadSemaphore(1), + fileWatcher(QStringList() << path) { } + SettingsFile::~SettingsFile(){ } void SettingsFile::fileChanged(){ if(!fileWatcher.files().contains(fileName()) && !fileWatcher.addPath(fileName())){ - qWarning() << "Unable to watch " << fileName(); - } - if(debugEnabled()){ - qDebug() << "Settings file" << fileName() << "changed!"; + O_WARNING("Unable to watch " << fileName()); } + O_DEBUG("Settings file" << fileName() << "changed!"); // Load new values sync(); auto metaObj = metaObject(); for (int i = metaObj->propertyOffset(); i < metaObj->propertyCount(); ++i) { - auto property = metaObj->property(i); - if(property.hasNotifySignal()){ - auto value = property.read(this); - auto value2 = this->value(property.name()); - if(value != value2){ - if(debugEnabled()){ - qDebug() << "Property" << property.name() << "changed"; - } - property.write(this, value2); - property.notifySignal().invoke(this, Qt::QueuedConnection, QGenericArgument(value2.typeName(), value2.data())); + auto prop = metaObj->property(i); + if(QString(prop.name()).startsWith("__META_GROUP_") || !prop.isWritable() || !prop.hasNotifySignal()){ + continue; + } + auto value = prop.read(this); + auto groupName = this->groupName(prop.name()); + if(groupName.isNull()){ + continue; + } + beginGroup(groupName != "General" ? groupName : ""); + bool exists = contains(prop.name()); + auto value2 = this->value(prop.name()); + endGroup(); + if(!exists){ + reloadSemaphore.acquire(); + if(prop.isResettable()){ + prop.reset(this); + }else if(!prop.read(this).isNull()){ + prop.write(this, QVariant()); } + reloadSemaphore.release(); + continue; } + if(!value2.isValid()){ + O_DEBUG("Property" << prop.name() << "new value invalid") + continue; + } + if(value == value2){ + continue; + } + O_DEBUG("Property" << prop.name() << "changed" << value2) + reloadSemaphore.acquire(); + prop.write(this, value2); + reloadSemaphore.release(); } - if(debugEnabled()){ - qDebug() << "Settings file" << fileName() << "changes loaded"; - } + O_DEBUG("Settings file" << fileName() << "changes loaded"); + emit changed(); } void SettingsFile::reloadProperty(const QString& name){ - auto metaObj = metaObject(); - - auto propertyName = "__META_GROUP_" + name; - auto idx = metaObj->indexOfProperty(propertyName.toStdString().c_str()); - if(idx == -1){ - O_SETTINGS_DEBUG("Group for " + name + " not found") + auto groupName = this->groupName(name); + if(groupName.isNull()){ return; } - auto groupName = property(propertyName.toStdString().c_str()).toString(); O_SETTINGS_DEBUG((fileName() + " Reloading " + groupName + "." + name).toStdString().c_str()) - if(groupName != "General"){ - beginGroup(groupName); - }else{ - beginGroup(""); - } + reloadSemaphore.acquire(); + beginGroup(groupName != "General" ? groupName : ""); if(contains(name)){ O_SETTINGS_DEBUG(" Value exists") auto value = this->value(name); @@ -75,8 +75,16 @@ namespace Oxide { } }else{ O_SETTINGS_DEBUG(" No Value") + auto metaObj = metaObject(); + auto prop = metaObj->property(metaObj->indexOfProperty(name.toStdString().c_str())); + if(prop.isResettable()){ + resetProperty(name); + }else if(!prop.read(this).isNull()){ + prop.write(this, QVariant()); + } } endGroup(); + reloadSemaphore.release(); O_SETTINGS_DEBUG(" Done") } void SettingsFile::resetProperty(const QString& name){ @@ -104,7 +112,7 @@ namespace Oxide { sync(); reloadProperties(); if(!fileWatcher.files().contains(fileName()) && !fileWatcher.addPath(fileName())){ - qWarning() << "Unable to watch " << fileName(); + O_WARNING("Unable to watch " << fileName()); } connect(&fileWatcher, &QFileSystemWatcher::fileChanged, this, &SettingsFile::fileChanged); } @@ -128,4 +136,14 @@ namespace Oxide { resetProperty(property.name()); } } + QString SettingsFile::groupName(const QString& name){ + auto metaObj = metaObject(); + auto propertyName = "__META_GROUP_" + name; + auto idx = metaObj->indexOfProperty(propertyName.toStdString().c_str()); + if(idx == -1){ + O_SETTINGS_DEBUG("Group for " + name + " not found") + return QString(); + } + return property(propertyName.toStdString().c_str()).toString(); + } } diff --git a/shared/liboxide/settingsfile.h b/shared/liboxide/settingsfile.h index 515f41cdc..530e05699 100644 --- a/shared/liboxide/settingsfile.h +++ b/shared/liboxide/settingsfile.h @@ -1,31 +1,29 @@ /*! - * \file settingsfile.h + * \addtogroup Oxide + * @{ + * \file */ -#ifndef SETTINGSFILE_H -#define SETTINGSFILE_H +#pragma once #include "liboxide_global.h" #include #include -#include #include #include #include +#include #include -#ifdef DEBUG -#define O_SETTINGS_DEBUG(msg) if(debugEnabled()){ qDebug() << msg; } -#else -#define O_SETTINGS_DEBUG(msg) -#endif +#define O_SETTINGS_DEBUG(msg) O_DEBUG(msg) #define O_SETTINGS_PROPERTY_0(_type, member, _group) \ Q_PROPERTY(QString __META_GROUP_##member READ __META_GROUP_##member CONSTANT FINAL) \ public: \ void set_##member(_type _arg_##member); \ _type member() const; \ + bool has_##member(); \ void reload_##member(); \ Q_SIGNALS: \ void member##Changed(const _type&); \ @@ -37,33 +35,42 @@ #define O_SETTINGS_PROPERTY_1(_type, group, member) \ Q_PROPERTY(_type member MEMBER m_##member READ member WRITE set_##member NOTIFY member##Changed FINAL) \ O_SETTINGS_PROPERTY_0(_type, member, group) - #define O_SETTINGS_PROPERTY_2(_type, group, member, _default) \ Q_PROPERTY(_type member MEMBER m_##member READ member WRITE set_##member NOTIFY member##Changed RESET reset_##member) \ O_SETTINGS_PROPERTY_0(_type, member, group) \ public: \ void reset_##member(); - #define O_SETTINGS_PROPERTY_BODY_0(_class, _type, member, _group) \ void _class::set_##member(_type _arg_##member) { \ + if(m_##member == _arg_##member){ \ + O_SETTINGS_DEBUG(fileName() + " No Change " + #_group + "." + #member) \ + return; \ + } \ O_SETTINGS_DEBUG(fileName() + " Setting " + #_group + "." + #member) \ m_##member = _arg_##member; \ - if(std::strcmp("General", #_group) != 0){ \ - beginGroup(#_group); \ + if(reloadSemaphore.tryAcquire()){ \ + beginGroup(std::strcmp("General", #_group) != 0 ? #_group : ""); \ + setValue(#member, QVariant::fromValue<_type>(_arg_##member)); \ + endGroup(); \ + O_SETTINGS_DEBUG(fileName() + " Saving " + #_group + "." + #member) \ + sync(); \ + reloadSemaphore.release(); \ }else{ \ - beginGroup(""); \ + O_SETTINGS_DEBUG(fileName() + " Not Saving " + #_group + "." + #member) \ } \ - setValue(#member, QVariant::fromValue<_type>(_arg_##member)); \ - endGroup(); \ - sync(); \ + emit member##Changed(m_##member);\ } \ _type _class::member() const { return m_##member; } \ + bool _class::has_##member() { \ + beginGroup(std::strcmp("General", #_group) != 0 ? #_group : ""); \ + bool res = contains(#member); \ + endGroup(); \ + return res; \ + } \ void _class::reload_##member() { reloadProperty(#member); } \ QString _class::__META_GROUP_##member() const { return #_group; } - #define O_SETTINGS_PROPERTY_BODY_1(_class, _type, group, member) \ O_SETTINGS_PROPERTY_BODY_0(_class, _type, member, group) - #define O_SETTINGS_PROPERTY_BODY_2(_class, _type, group, member, _default) \ O_SETTINGS_PROPERTY_BODY_0(_class, _type, member, group) \ void _class::reset_##member() { \ @@ -72,25 +79,36 @@ setProperty(#member, _default); \ O_SETTINGS_DEBUG(" Done") \ } - #define O_SETTINGS_PROPERTY_X_get_func(arg1, arg2, arg3, arg4, arg5, ...) arg5 #define O_SETTINGS_PROPERTY_X(...) \ O_SETTINGS_PROPERTY_X_get_func(__VA_ARGS__, \ O_SETTINGS_PROPERTY_2, \ O_SETTINGS_PROPERTY_1, \ ) - #define O_SETTINGS_PROPERTY_BODY_X_get_func(arg1, arg2, arg3, arg4, arg5, arg6, ...) arg6 #define O_SETTINGS_PROPERTY_BODY_X(...) \ O_SETTINGS_PROPERTY_BODY_X_get_func(__VA_ARGS__, \ O_SETTINGS_PROPERTY_BODY_2, \ O_SETTINGS_PROPERTY_BODY_1, \ ) - +/*! + * \def O_SETTINGS_PROPERTY + * \brief Add a property to a SettingsFile derived class + * \sa O_SETTINGS, O_SETTINGS_PROPERTY_BODY, Oxide::SettingsFile + */ #define O_SETTINGS_PROPERTY(...) O_SETTINGS_PROPERTY_X(__VA_ARGS__)(__VA_ARGS__) +/*! + * \def O_SETTINGS_PROPERTY_BODY + * \brief Add the body for a property on a SettingsFile derived class + * \sa O_SETTINGS, O_SETTINGS_PROPERTY, Oxide::SettingsFile + */ #define O_SETTINGS_PROPERTY_BODY(...) O_SETTINGS_PROPERTY_BODY_X(__VA_ARGS__)(__VA_ARGS__) - +/*! + * \def O_SETTINGS + * \brief Define the instance() and constructor methods for a SettingsFile derived class + * \sa O_SETTINGS_PROPERTY, O_SETTINGS_PROPERTY_BODY, Oxide::SettingsFile + */ #define O_SETTINGS(_type, path) \ public: \ static _type& instance(){ \ @@ -103,24 +121,22 @@ namespace Oxide { - /*! - * \brief Return the state of debugging - * \return Debugging state - * \snippet examples/oxide.cpp debugEnabled - */ - LIBOXIDE_EXPORT bool debugEnabled(); /*! * \brief A better version of [QSettings](https://doc.qt.io/qt-5/qsettings.html) * * This base class adds dynamic updates of changes to a settings file from disk. * It also implements a static instance method that will return the singleton for this class. * - * \sa sharedSettings, xochitlSettings + * \sa sharedSettings, xochitlSettings, O_SETTINGS, O_SETTINGS_PROPERTY, O_SETTINGS_PROPERTY_BODY */ class LIBOXIDE_EXPORT SettingsFile : public QSettings { Q_OBJECT + signals: + void changed(); + private slots: void fileChanged(); + protected: SettingsFile(QString path); ~SettingsFile(); @@ -129,9 +145,12 @@ namespace Oxide { void init(); void reloadProperties(); void resetProperties(); + QString groupName(const QString& name); + QSemaphore reloadSemaphore; + private: QFileSystemWatcher fileWatcher; bool initalized = false; }; } -#endif // SETTINGSFILE_H +/*! @} */ diff --git a/shared/liboxide/signalhandler.h b/shared/liboxide/signalhandler.h index b9f899c57..ac45f3d6a 100644 --- a/shared/liboxide/signalhandler.h +++ b/shared/liboxide/signalhandler.h @@ -1,14 +1,14 @@ /*! - * \file signalhandler.h + * \addtogroup Oxide + * @{ + * \file */ -#ifndef SIGNALHANDLER_H -#define SIGNALHANDLER_H +#pragma once #include "liboxide_global.h" #include #include - /*! * \brief signalHandler() */ @@ -68,4 +68,4 @@ namespace Oxide { QSocketNotifier* snUsr2; }; } -#endif // SIGNALHANDLER_H +/*! @} */ diff --git a/shared/liboxide/slothandler.cpp b/shared/liboxide/slothandler.cpp new file mode 100644 index 000000000..cc47280fc --- /dev/null +++ b/shared/liboxide/slothandler.cpp @@ -0,0 +1,86 @@ +#include "slothandler.h" +#include "meta.h" +#include "debug.h" + +#include +#include +#include +#include + + +namespace Oxide{ + bool DBusConnect(QDBusAbstractInterface* interface, const QString& slotName, std::function onMessage, const bool& once=false){ + return DBusConnect(interface, slotName, onMessage, []{}, once); + } + bool DBusConnect(QDBusAbstractInterface* interface, const QString& slotName, std::function onMessage, std::function callback, const bool& once){ + auto metaObject = interface->metaObject(); + for(int methodId = 0; methodId < metaObject->methodCount(); methodId++){ + auto method = metaObject->method(methodId); + if(method.methodType() == QMetaMethod::Signal && method.name() == slotName){ + QByteArray slotName = method.name().prepend("on").append("("); + QStringList parameters; + for(int i = 0, j = method.parameterCount(); i < j; ++i){ + parameters << QMetaType::typeName(method.parameterType(i)); + } + slotName.append(parameters.join(",").toUtf8()).append(")"); + QByteArray theSignal = QMetaObject::normalizedSignature(method.methodSignature().constData()); + QByteArray theSlot = QMetaObject::normalizedSignature(slotName); + if(!QMetaObject::checkConnectArgs(theSignal, theSlot)){ + continue; + } + auto slotHandler = new Oxide::SlotHandler(OXIDE_SERVICE, parameters, once, onMessage, callback); + return slotHandler->connect(interface, methodId); + } + } + return false; + } + SlotHandler::SlotHandler(const QString& serviceName, QStringList parameters, bool once, std::function onMessage, std::function callback) + : QObject(0), + serviceName(serviceName), + parameters(parameters), + once(once), + m_disconnected(false), + onMessage(onMessage), + callback(callback) + { + watcher = new QDBusServiceWatcher(serviceName, QDBusConnection::systemBus(), QDBusServiceWatcher::WatchForUnregistration, this); + QObject::connect(watcher, &QDBusServiceWatcher::serviceUnregistered, this, [=](const QString& name){ + Q_UNUSED(name); + if(!m_disconnected){ + O_DEBUG(QDBusError(QDBusError::ServiceUnknown, "The name " + serviceName + " is no longer registered")); + m_disconnected = true; + callback(); + } + }); + } + SlotHandler::~SlotHandler() {} + int SlotHandler::qt_metacall(QMetaObject::Call call, int id, void **arguments){ + id = QObject::qt_metacall(call, id, arguments); + if (id < 0 || call != QMetaObject::InvokeMetaMethod){ + return id; + } + Q_ASSERT(id < 1); + if(!m_disconnected){ + handleSlot(sender(), arguments); + } + return -1; + } + bool SlotHandler::connect(QObject* sender, int methodId){ + return QMetaObject::connect(sender, methodId, this, this->metaObject()->methodCount()); + } + void SlotHandler::handleSlot(QObject* api, void** arguments){ + Q_UNUSED(api); + QVariantList args; + for(int i = 0; i < parameters.length(); i++){ + auto typeId = QMetaType::type(parameters[i].toStdString().c_str()); + QMetaType type(typeId); + void* ptr = reinterpret_cast(arguments[i + 1]); + args << QVariant(typeId, ptr); + } + onMessage(args); + if(once){ + m_disconnected = true; + callback(); + } + } +} diff --git a/shared/liboxide/slothandler.h b/shared/liboxide/slothandler.h new file mode 100644 index 000000000..a42e06557 --- /dev/null +++ b/shared/liboxide/slothandler.h @@ -0,0 +1,63 @@ +/*! + * \addtogroup Oxide + * @{ + * \file + */ +#pragma once + +#include "liboxide_global.h" + +#include +#include +#include + +namespace Oxide{ + /*! + * \brief Connect to a slot on a DBus interface + * \param Interface to connect to + * \param Slot to connect to + * \param Method to run when an event is recieved on the slot + * \return If the connect succeeded + */ + LIBOXIDE_EXPORT bool DBusConnect(QDBusAbstractInterface* interface, const QString& slotName, std::function onMessage); + /*! + * \brief Connect to a slot on a DBus interface + * \param Interface to connect to + * \param Slot to connect to + * \param Method to run when an event is recieved on the slot + * \param If this should disconnect after the first event + * \return If the connect succeeded + */ + LIBOXIDE_EXPORT bool DBusConnect(QDBusAbstractInterface* interface, const QString& slotName, std::function onMessage, const bool& once); + /*! + * \brief Connect to a slot on a DBus interface + * \param Interface to connect to + * \param Slot to connect to + * \param Method to run when an event is recieved on the slot + * \param Method to run when disconnecting + * \param If this should disconnect after the first event + * \return If the connect succeeded + */ + LIBOXIDE_EXPORT bool DBusConnect(QDBusAbstractInterface* interface, const QString& slotName, std::function onMessage, std::function callback, const bool& once=false); + /*! + * \brief A class for handling DBus slots + */ + class LIBOXIDE_EXPORT SlotHandler : public QObject { + public: + SlotHandler(const QString& serviceName, QStringList parameters, bool once, std::function onMessage, std::function callback); + ~SlotHandler(); + int qt_metacall(QMetaObject::Call call, int id, void **arguments); + bool connect(QObject* sender, int methodId); + + private: + QString serviceName; + QStringList parameters; + bool once; + bool m_disconnected; + QDBusServiceWatcher* watcher; + std::function onMessage; + std::function callback; + void handleSlot(QObject* api, void** arguments); + }; +} +/*! @} */ diff --git a/shared/liboxide/sysobject.cpp b/shared/liboxide/sysobject.cpp index 09db213e3..8b52c59ec 100644 --- a/shared/liboxide/sysobject.cpp +++ b/shared/liboxide/sysobject.cpp @@ -1,13 +1,12 @@ #include "sysobject.h" +#include "debug.h" + #include #include #include -#include #include #include -#include - namespace Oxide{ std::string SysObject::propertyPath(const std::string& name){ return m_path + "/" + name; @@ -32,9 +31,7 @@ namespace Oxide{ auto path = propertyPath(name); QFile file(path.c_str()); if(!file.open(QIODevice::ReadOnly | QIODevice::Text)){ - if(Oxide::debugEnabled()){ - qDebug() << "Couldn't find the file " << path.c_str(); - } + O_DEBUG("Couldn't find the file " << path.c_str()); return "0"; } QTextStream in(&file); diff --git a/shared/liboxide/sysobject.h b/shared/liboxide/sysobject.h index 6839450ec..04efaeecd 100644 --- a/shared/liboxide/sysobject.h +++ b/shared/liboxide/sysobject.h @@ -1,8 +1,12 @@ /*! - * \file sysobject.h + * \addtogroup Oxide + * @{ + * \file */ -#ifndef SYSOBJECT_H -#define SYSOBJECT_H +#pragma once +// This is required to allow generate_xml.sh to work +#ifndef LIBOXIDE_SYSOBJECT_H +#define LIBOXIDE_SYSOBJECT_H #include "liboxide_global.h" @@ -13,8 +17,7 @@ namespace Oxide { /*! * \brief A class to make interacting with sysfs easier */ - class LIBOXIDE_EXPORT SysObject - { + class LIBOXIDE_EXPORT SysObject { public: explicit SysObject(QString path) : m_path(path.toStdString()){} /*! @@ -62,4 +65,5 @@ namespace Oxide { std::string m_path; }; } -#endif // SYSOBJECT_H +#endif // LIBOXIDE_SYSOBJECT_H +/*! @} */ diff --git a/shared/sentry/external/breakpad/docs/breakpad.png b/shared/sentry/external/breakpad/docs/breakpad.png index 20bc7566c..8d1a498d0 100644 Binary files a/shared/sentry/external/breakpad/docs/breakpad.png and b/shared/sentry/external/breakpad/docs/breakpad.png differ diff --git a/shared/sentry/external/crashpad/build/ios/Default.png b/shared/sentry/external/crashpad/build/ios/Default.png index 8c9089d5c..ca857795b 100644 Binary files a/shared/sentry/external/crashpad/build/ios/Default.png and b/shared/sentry/external/crashpad/build/ios/Default.png differ diff --git a/shared/sentry/external/crashpad/doc/layering.png b/shared/sentry/external/crashpad/doc/layering.png index 791bea538..8de5ac441 100644 Binary files a/shared/sentry/external/crashpad/doc/layering.png and b/shared/sentry/external/crashpad/doc/layering.png differ diff --git a/shared/sentry/external/crashpad/doc/overview.png b/shared/sentry/external/crashpad/doc/overview.png index 78f849eff..5e04d38a1 100644 Binary files a/shared/sentry/external/crashpad/doc/overview.png and b/shared/sentry/external/crashpad/doc/overview.png differ diff --git a/shared/shared.pro b/shared/shared.pro new file mode 100644 index 000000000..183ce12ea --- /dev/null +++ b/shared/shared.pro @@ -0,0 +1,6 @@ +TEMPLATE = subdirs + +SUBDIRS = \ + liboxide + +INSTALLS += $$SUBDIRS diff --git a/web/Makefile b/web/Makefile index 07294830b..cffcb2485 100644 --- a/web/Makefile +++ b/web/Makefile @@ -1,6 +1,15 @@ -DIST=dist -SRC=src -VENV=.venv +DIST=$(CURDIR)/dist +SRC=$(CURDIR)/src +IMAGES=$(CURDIR)/images +VENV=$(CURDIR)/.venv +BUILD=$(CURDIR)/.build + +texSvgFiles := $(wildcard $(IMAGES)/*.svg.tex) +svgFiles := $(texSvgFiles:$(IMAGES)/%.svg.tex=$(SRC)/_static/images/%.svg) +texPngFiles := $(wildcard $(IMAGES)/*.png.tex) +pngFiles := $(texPngFiles:$(IMAGES)/%.png.tex=$(SRC)/_static/images/%.png) + +.PHONY: all prod images dev dev-images clean all: prod @@ -10,7 +19,38 @@ $(VENV)/bin/activate: . .venv/bin/activate; \ python -m pip install -r requirements.txt -prod: $(VENV)/bin/activate +$(SRC)/_static/images/%.svg: $(IMAGES)/%.svg.tex + mkdir -p $(SRC)/_static/images + mkdir -p $(BUILD)/images + cd $(IMAGES) && pdflatex \ + -shell-escape \ + -halt-on-error \ + -file-line-error \ + -interaction nonstopmode \ + -output-directory=$(BUILD)/images \ + $*.svg.tex + pdf2svg $(BUILD)/images/$*.svg.pdf $(SRC)/_static/images/$*.svg + +$(SRC)/_static/images/%.png: $(IMAGES)/%.png.tex + mkdir -p $(SRC)/_static/images + mkdir -p $(BUILD)/images + cd $(IMAGES) && pdflatex \ + -shell-escape \ + -halt-on-error \ + -file-line-error \ + -interaction nonstopmode \ + -output-directory=$(BUILD)/images \ + $*.png.tex + cd $(BUILD)/images && \ + pdf2svg $*.png.pdf $*.svg && \ + rsvg-convert $*.svg -o $(SRC)/_static/images/$*.png + +$(SRC)/_static/images/favicon.png: $(SRC)/_static/images/favicon.svg + rsvg-convert -h 180 $(SRC)/_static/images/favicon.svg -o $(SRC)/_static/images/favicon.png + +images: $(svgFiles) $(pngFiles) $(SRC)/_static/images/favicon.png + +$(DIST): $(VENV)/bin/activate images . $(VENV)/bin/activate; \ sphinx-build -a -n -E -b html $(SRC) $(DIST) # Clean unused files inherited from default theme @@ -35,12 +75,18 @@ prod: $(VENV)/bin/activate $(DIST)/_static/underscore-1.13.1.js \ $(DIST)/_static/underscore.js \ +prod: $(DIST) + dev: $(VENV)/bin/activate . $(VENV)/bin/activate; \ - sphinx-autobuild -a $(SRC) $(DIST) + sphinx-autobuild -a $(SRC) $(DIST) --port=0 --open-browser -clean: - rm -r $(DIST) - rm -r $(VENV) +dev-images: + while inotifywait -e close_write,create $(IMAGES) $(IMAGES)/*.tex;do \ + rm -f $(svgFiles) $(pngFiles); \ + $(MAKE) images; \ + done -.PHONY: all prod dev clean +clean: + rm -rf $(DIST) $(VENV) $(BUILD) + rm -f $(SRC)/_static/images/*.png $(SRC)/_static/images/*.svg diff --git a/web/images/favicon.svg.tex b/web/images/favicon.svg.tex new file mode 100644 index 000000000..ee98e3859 --- /dev/null +++ b/web/images/favicon.svg.tex @@ -0,0 +1,23 @@ +% Copyright (c) 2021 The Toltec Contributors +% SPDX-License-Identifier: MIT +% +% Example document for the Toltec shapes library +\documentclass[crop, tikz, border=.1cm]{standalone} + +\usepackage{tikz} +\usetikzlibrary{arrows.meta} +\input{remarkable} +\usepackage[T1]{fontenc} +\usepackage{lmodern} +\usepackage{graphicx} + +\begin{document} + \begin{tikzpicture} + \pic (rM-pen) at (6.75, -3) {rM1}; + \node[font=\fontsize{1}{3.5}\selectfont] at ($(rM-pen-screen-top-right)+(-0.21,-1.9)$) {Starting launcher...}; + \node[inner sep=0pt] (icon) at ($(rM-pen-screen-center)+(0,0)$){ + \includegraphics[scale=0.3]{../../assets/opt/usr/share/icons/oxide/702x702/splash/oxide.png} + }; + \pic[rotate=-25] at ($(rM-pen-screen-center)+(-.45,-.5)$) {rM pen}; + \end{tikzpicture} +\end{document} diff --git a/web/images/gestures.svg.tex b/web/images/gestures.svg.tex new file mode 100644 index 000000000..c15471984 --- /dev/null +++ b/web/images/gestures.svg.tex @@ -0,0 +1,99 @@ +% Copyright (c) 2021 The Toltec Contributors +% SPDX-License-Identifier: MIT +% +% Example document for the Toltec shapes library +\documentclass[crop, tikz, border=.1cm]{standalone} + +\usepackage{tikz} +\usetikzlibrary{arrows.meta} +\input{remarkable} +\usepackage[T1]{fontenc} +\usepackage{lmodern} +\usepackage{graphicx} + +\begin{document} + \begin{tikzpicture} + \pic (rM-scroll-left) at (0, 0) {rM1}; + \pic (swipe-left) at ($(rM-scroll-left-screen-center)+(.2, 0)$) {scroll left}; + \node[font=\fontsize{2}{3.5}\selectfont] + at ($(rM-scroll-left-screen-center)+(.2, -.22)$) + {Swipe left from edge}; + \node[rectangle,draw,fill=white,font=\fontsize{3}{3.5}\selectfont] + at ($(rM-scroll-left-screen-center)+(0, -1.6)$) + {Take a screenshot}; + + \pic (rM-scroll-right) at (2.25, 0) {rM1}; + \pic at ($(rM-scroll-right-screen-center)+(-.2, 0)$) {scroll right}; + \node[font=\fontsize{2}{3.5}\selectfont] + at ($(rM-scroll-right-screen-center)+(-.2, -.22)$) + {Swipe right from edge}; + \node[rectangle,draw,fill=white,font=\fontsize{3}{3.5}\selectfont] + at ($(rM-scroll-right-screen-center)+(0, -1.6)$) + {Open previous application}; + + \pic (rM-scroll-up) at (4.5, 0) {rM1}; + \pic at ($(rM-scroll-up-screen-center)+(0, -.35)$) {scroll up}; + \node[font=\fontsize{2}{3.5}\selectfont] + at ($(rM-scroll-up-screen-center)+(0, .25)$) + {Swipe up from edge}; + \node[rectangle,draw,fill=white,font=\fontsize{3}{3.5}\selectfont] + at ($(rM-scroll-up-screen-center)+(0, -1.6)$) + {Open task switcher}; + + \pic (rM-scroll-down) at (6.75, 0) {rM1}; + \pic at ($(rM-scroll-down-screen-center)+(0, .35)$) {scroll down}; + \node[font=\fontsize{2}{3.5}\selectfont] + at ($(rM-scroll-down-screen-center)+(0, -.25)$) + {Swipe down from edge}; + \node[rectangle,draw,fill=white,font=\fontsize{3}{3.5}\selectfont] + at ($(rM-scroll-down-screen-center)+(0, -1.6)$) + {Toggle touch gestures}; + + \pic (rM-power) at (0, -3) {rM1}; + \draw[rM annotation, ultra thick, {Latex[length=2.2mm]}-] + ($(rM-power-key-power.south)+(0, -.05)$) + to ++(0, -.6); + \node[font=\fontsize{2}{3.5}\selectfont] + at ($(rM-power-key-power.south)+(0, -.7)$) + {Press button}; + \node[rectangle,draw,fill=white,font=\fontsize{3}{3.5}\selectfont] + at ($(rM-power-screen-center)+(0, -1.6)$) + {Suspend device}; + + \pic[rM1 left fill=rM border] + (rM-left) at (2.25, -3) {rM1}; + \draw[rM annotation, ultra thick, {Latex[length=2.2mm]}-] + ($(rM-left-key-left.north)+(0, .05)$) + to [out=85, in=-130] ++(.2, .6); + \node[font=\fontsize{2}{3.5}\selectfont] + at ($(rM-left-key-left.north)+(.5, .7)$) + {Hold button}; + \node[rectangle,draw,fill=white,font=\fontsize{3}{3.5}\selectfont] + at ($(rM-left-screen-center)+(0, -1.6)$) + {Open launcher}; + + \pic[rM1 home fill=rM border] + (rM-home) at (4.5, -3) {rM1}; + \draw[rM annotation, ultra thick, {Latex[length=2.2mm]}-] + ($(rM-home-key-home.north)+(0, .05)$) + to ++(0, .6); + \node[font=\fontsize{2}{3.5}\selectfont] + at ($(rM-home-key-home.north)+(0, .7)$) + {Hold button}; + \node[rectangle,draw,fill=white,font=\fontsize{3}{3.5}\selectfont] + at ($(rM-home-screen-center)+(0, -1.6)$) + {Open process manager}; + + \pic[rM1 right fill=rM border] + (rM-right) at (6.75, -3) {rM1}; + \draw[rM annotation, ultra thick, {Latex[length=2.2mm]}-] + ($(rM-right-key-right.north)+(0, .05)$) + to [out=95, in=-50] ++(-.2, .6); + \node[font=\fontsize{2}{3.5}\selectfont] + at ($(rM-right-key-right.north)+(-.5, .7)$) + {Hold button}; + \node[rectangle,draw,fill=white,font=\fontsize{3}{3.5}\selectfont] + at ($(rM-right-screen-center)+(0, -1.6)$) + {Take a screenshot}; + \end{tikzpicture} +\end{document} diff --git a/web/images/logo.png.tex b/web/images/logo.png.tex new file mode 100644 index 000000000..b82bfc95f --- /dev/null +++ b/web/images/logo.png.tex @@ -0,0 +1,22 @@ +% Copyright (c) 2021 The Toltec Contributors +% SPDX-License-Identifier: MIT +% +% Example document for the Toltec shapes library +\documentclass[crop, tikz, border=.1cm]{standalone} + +\usepackage{tikz} +\usetikzlibrary{arrows.meta} +\input{remarkable} +\usepackage[T1]{fontenc} +\usepackage{lmodern} +\usepackage{graphicx} + +\begin{document} + \begin{tikzpicture} + \pic[scale=10, every node/.style={scale=10}] (tablet) at (6.75, -3) {rM1}; + \node[scale=10, every node/.style={scale=10}, font=\fontsize{1}{3.5}\selectfont] at ($(tablet-screen-top-right)+(-2.2,-19)$) {Starting launcher...}; + \node[scale=10, every node/.style={scale=10}, inner sep=0pt] (icon) at ($(tablet-screen-center)+(0,0)$){ + \includegraphics[scale=0.3]{../../assets/opt/usr/share/icons/oxide/702x702/splash/oxide.png} + }; + \end{tikzpicture} +\end{document} diff --git a/web/images/remarkable.tex b/web/images/remarkable.tex new file mode 100644 index 000000000..4b1384675 --- /dev/null +++ b/web/images/remarkable.tex @@ -0,0 +1,172 @@ +% Copyright (c) 2021 The Toltec Contributors +% SPDX-License-Identifier: MIT +% +% Library of reMarkable-related TikZ shapes for illustrating the documentation +% of community projects +\usetikzlibrary{calc} + +\colorlet{rM border}{black!60} +\colorlet{rM screen}{black!6} +\colorlet{rM overscreen}{black!10} +\colorlet{rM annotation}{black!70} + +\tikzset{ + % Shape of a reMarkable 1 tablet + % Arguments: by border + rM1 left fill/.initial={white}, + rM1 home fill/.initial={white}, + rM1 right fill/.initial={white}, + pics/rM1/.style args={#1 by #2 border #3}{ + code={ + \node[ + fill=rM border, + rounded corners=0.25, + minimum width={2 * #3 cm}, + minimum height=2pt, + yshift=-.25pt, + inner sep=0pt, + ] at (.5 * #1, #2) (-key-power) {}; + + \draw[ + draw=rM border, fill=white, + rounded corners=1, + ] (0, 0) rectangle (#1, #2); + + \fill[rM screen] + (#3, 4 * #3) + coordinate (-screen-bottom-left) + rectangle (#1 - #3, #2 - 2 * #3) + coordinate (-screen-top-right); + + \coordinate (-screen-center) + at ($(-screen-bottom-left)!.5!(-screen-top-right)$); + + \tikzset{ + lower key/.style={ + draw=rM border, rectangle, + line width=.3, + rounded corners=0.25, + minimum width={2 * #3 cm}, + minimum height={2 * #3 cm}, + inner sep=0pt, + }, + } + + \node[ + lower key, + fill=\pgfkeysvalueof{/tikz/rM1 left fill}, + ] at (2 * #3, 2 * #3) (-key-left) {}; + \node[ + lower key, + fill=\pgfkeysvalueof{/tikz/rM1 home fill}, + ] at (.5 * #1, 2 * #3) (-key-home) {}; + \node[ + lower key, + fill=\pgfkeysvalueof{/tikz/rM1 right fill}, + ] at (#1 - 2 * #3, 2 * #3) (-key-right) {}; + } + }, + % + % Shape of a reMarkable pen + % Arguments: by + pics/rM pen/.style args={#1 by #2}{ + code={ + \draw[rM annotation, fill=white] (0, 0) + to [out=135, in=-90] ++(-.5 * #1, #1) + to ++(0, #2) to ++(#1, 0) + to ++(0, -#2) + to [out=-90, in=45] ++(-.5 * #1, -#1); + } + }, + pics/rM pen/.default={.12 by .8}, + pics/rM1/.default={1.75 by 2.55 border .1}, + % + % Symbol representing a tap on a touch screen + % Arguments: // + pics/tap/.style args={#1/#2/#3}{ + code={ + \draw[rM annotation, semithick] (0, 0) circle (#1); + + \draw[rM annotation] (#2, 0) + arc[radius=#2, start angle=0, delta angle=#3] + (#2, 0) + arc[radius=#2, start angle=0, delta angle=-#3]; + + \draw[rM annotation] (-#2, 0) + arc[radius=#2, start angle=180, delta angle=#3] + (-#2, 0) + arc[radius=#2, start angle=180, delta angle=-#3]; + } + }, + pics/tap/.default={.15/.22/50}, + % + % Symbol representing a horizontal swipe in any direction + % Arguments: none + pics/scroll x/.style={ + code={ + \draw[rM annotation, semithick] (0, 0) circle (#1); + \draw[rM annotation, ultra thick, -{Latex[length=2.2mm]}] + (-#1, 0) -- ++(-.4, 0); + \draw[rM annotation, ultra thick, -{Latex[length=2.2mm]}] + (#1, 0) -- ++(.4, 0); + } + }, + pics/scroll x/.default={.15}, + % + % Symbol representing a horizontal swipe to the left + % Arguments: none + pics/scroll left/.style={ + code={ + \draw[rM annotation, semithick] (.4, 0) circle (#1); + \draw[rM annotation, ultra thick, -{Latex[length=2.2mm]}] + (#1+.1, 0) -- ++(-.8, 0); + } + }, + pics/scroll left/.default={.15}, + % + % Symbol representing a horizontal swipe to the right + % Arguments: none + pics/scroll right/.style={ + code={ + \draw[rM annotation, semithick] (-.4, 0) circle (#1); + \draw[rM annotation, ultra thick, -{Latex[length=2.2mm]}] + (-#1-.1, 0) -- ++(.8, 0); + } + }, + pics/scroll right/.default={.15}, + % + % Symbol representing a vertical swipe in any direction + % Arguments: none + pics/scroll y/.style={ + code={ + \draw[rM annotation, semithick] (0, 0) circle (#1); + \draw[rM annotation, ultra thick, -{Latex[length=2.2mm]}] + (#1, -#1) -- ++(0, -.4); + \draw[rM annotation, ultra thick, -{Latex[length=2.2mm]}] + (#1, #1) -- ++(0, .4); + } + }, + pics/scroll y/.default={.15}, + % + % Symbol representing a vertical swipe up + % Arguments: none + pics/scroll up/.style={ + code={ + \draw[rM annotation, semithick] (0, -.4) circle (#1); + \draw[rM annotation, ultra thick, -{Latex[length=2.2mm]}] + (0, -#1-.1) -- ++(0, .8); + } + }, + pics/scroll up/.default={.15}, + % + % Symbol representing a vertical swipe down + % Arguments: none + pics/scroll down/.style={ + code={ + \draw[rM annotation, semithick] (0, .4) circle (#1); + \draw[rM annotation, ultra thick, -{Latex[length=2.2mm]}] + (0, #1+.1) -- ++(0, -.8); + } + }, + pics/scroll down/.default={.15}, +} diff --git a/web/src/_static/images/.gitignore b/web/src/_static/images/.gitignore new file mode 100644 index 000000000..664db108e --- /dev/null +++ b/web/src/_static/images/.gitignore @@ -0,0 +1,2 @@ +*.png +*.svg diff --git a/web/src/_themes/oxide/static/oxide.css b/web/src/_themes/oxide/static/oxide.css index e94751d64..c2ce3ccac 100644 --- a/web/src/_themes/oxide/static/oxide.css +++ b/web/src/_themes/oxide/static/oxide.css @@ -91,12 +91,12 @@ pre { } div.warning { - padding: var(--text-height); - overflow: auto; border-radius: var(--border-radius); border-color: var(--bg-color-inv); border-style: dashed; font-size: 0.888rem; + overflow: auto; + padding: var(--text-height); } a { @@ -294,7 +294,7 @@ main > section:first-of-type > h1:first-of-type { .logo { display: block; - width: calc(var(--scaling) * 256px); + max-width: calc(var(--scaling) * 256px); margin: calc(var(--scaling) * -32px) auto; } diff --git a/web/src/documentation/01_usage.rst b/web/src/documentation/01_usage.rst index b74a26107..40b4df13f 100644 --- a/web/src/documentation/01_usage.rst +++ b/web/src/documentation/01_usage.rst @@ -2,6 +2,11 @@ Usage ===== +.. raw:: html + + gestures +
+ Lockscreen (decay) ================== diff --git a/web/src/documentation/02_oxide-utils.rst b/web/src/documentation/02_oxide-utils.rst new file mode 100644 index 000000000..aecea5a97 --- /dev/null +++ b/web/src/documentation/02_oxide-utils.rst @@ -0,0 +1,41 @@ +=========== +Oxide-Utils +=========== + +As of version 2.6, Oxide ships with several new command line utilities meant to mimic common +existing linux command line tools meant for dealing with desktop environments. + +desktop-file-validate +===================== + +https://man.archlinux.org/man/desktop-file-validate.1.en + +update-desktop-database +======================= + +https://man.archlinux.org/man/update-desktop-database.1.en + +xdg-desktop-menu +================ + +https://man.archlinux.org/man/xdg-desktop-menu.1 + +xdg-desktop-icon +================ + +https://man.archlinux.org/man/xdg-desktop-icon.1 + +xdg-open +======== + +https://man.archlinux.org/man/xdg-open.1 + +xdg-settings +============ + +https://man.archlinux.org/man/xdg-settings.1.en + +gio +=== + +https://man.archlinux.org/man/gio.1 diff --git a/web/src/documentation/02_application_registration_format.rst b/web/src/documentation/03_application_registration_format.rst similarity index 94% rename from web/src/documentation/02_application_registration_format.rst rename to web/src/documentation/03_application_registration_format.rst index 4131531ac..cbefeabfa 100644 --- a/web/src/documentation/02_application_registration_format.rst +++ b/web/src/documentation/03_application_registration_format.rst @@ -133,7 +133,14 @@ Properties | icon | string | No | Path to an image | | | | | file to use as the | | | | | icon for this | -| | | | application. | +| | | | application. Or an | +| | | | icon spec. | ++------------------+--------------+----------+-----------------------+ +| splash | string | No | Path to an image | +| | | | file to use as the | +| | | | splash screen for | +| | | | this application. Or | +| | | | an icon spec. | +------------------+--------------+----------+-----------------------+ | user | string | No | User to run this | | | | | application as. | @@ -226,3 +233,17 @@ Properties | | | | stopped | +------------------+--------------+----------+-----------------------+ +Icon Spec +========= + +Icon specifications can be in the following format: ``[theme:][context:]{name}-{size}`` + +Some examples: + +- ``oxide:splash:xochitl-702`` +- ``oxide:apps:xochitl-48`` +- ``oxide:xochitl-48`` +- ``xochitl-48`` + +You can find available icons in ``/opt/usr/share/icons``. The default theme is +hicolor, and the default context is apps. diff --git a/web/src/documentation/03_api.rst b/web/src/documentation/04_api.rst similarity index 100% rename from web/src/documentation/03_api.rst rename to web/src/documentation/04_api.rst diff --git a/web/src/documentation/api/01_general.rst b/web/src/documentation/api/01_general.rst index 9a004d0d7..4a17ef417 100644 --- a/web/src/documentation/api/01_general.rst +++ b/web/src/documentation/api/01_general.rst @@ -64,8 +64,8 @@ usage of the API away. .. _example-usage-1: -Example Usage: -~~~~~~~~~~~~~~ +Example Usage +~~~~~~~~~~~~~ .. code:: cpp diff --git a/web/src/documentation/api/03_notification.rst b/web/src/documentation/api/02_notification.rst similarity index 65% rename from web/src/documentation/api/03_notification.rst rename to web/src/documentation/api/02_notification.rst index 45acc0c12..fbe0d7fd9 100644 --- a/web/src/documentation/api/03_notification.rst +++ b/web/src/documentation/api/02_notification.rst @@ -212,111 +212,3 @@ Example Usage rot --object Notification:$path notification call display rot --object Notification:$path notification call remove -Power API ---------- - -+----------------------+----------------------+----------------------+ -| Name | Specification | Description | -+======================+======================+======================+ -| state | ``INT32`` property | Currently requested | -| | (read/write) | power state. | -| | | Possible values: | -| | | - ``0`` Normal | -| | | - ``1`` Power Saving | -+----------------------+----------------------+----------------------+ -| batteryState | ``INT32`` property | Current battery | -| | (read) | state. | -| | | - ``0`` Unknown | -| | | - ``1`` Charging | -| | | - ``2`` Discharging | -| | | - ``3`` Not Present | -+----------------------+----------------------+----------------------+ -| batteryLevel | ``INT32`` property | Current battery | -| | (read) | percentage. | -+----------------------+----------------------+----------------------+ -| batteryTemperature | ``INT32`` property | Current battery | -| | (read) | temperature in | -| | | Celsius. | -+----------------------+----------------------+----------------------+ -| chargerState | ``INT32`` property | Current charger | -| | (read) | state. | -| | | - ``0`` Unknown | -| | | - ``1`` Connected | -| | | - ``2`` Not | -| | | Connected | -+----------------------+----------------------+----------------------+ -| stateChanged | signal | Signal sent when the | -| | - (out) ``INT32`` | requested power | -| | | state has changed. | -+----------------------+----------------------+----------------------+ -| batteryStateChanged | signal | Signal sent when the | -| | - (out) ``INT32`` | battery state has | -| | | changed. | -+----------------------+----------------------+----------------------+ -| batteryLevelChanged | signal | Signal sent when the | -| | - (out) ``INT32`` | battery level has | -| | | changed. | -+----------------------+----------------------+----------------------+ -| batte | signal | Signal sent when the | -| ryTemperatureChanged | - (out) ``INT32`` | battery temperature | -| | | has changed. | -+----------------------+----------------------+----------------------+ -| chargerStateChanged | signal | Signal sent when the | -| | - (out) ``INT32`` | charger state has | -| | | changed. | -+----------------------+----------------------+----------------------+ -| batteryWarning | signal | Signal sent when a | -| | | battery warning has | -| | | been detected. | -+----------------------+----------------------+----------------------+ -| batteryAlert | signal | Signal sent when a | -| | | battery alert has | -| | | been detected. | -+----------------------+----------------------+----------------------+ -| chargerWarning | signal | Signal sent when a | -| | | charger warning has | -| | | been detected. | -+----------------------+----------------------+----------------------+ - -.. _example-usage-6: - -Example Usage -~~~~~~~~~~~~~ - -.. code:: cpp - - #include - #include "dbusservice_interface.h" - #include "powerapi_interface.h" - - using namespace codes::eeems::oxide1; - - int main(int argc, char* argv[]){ - QCoreApplication app(argc, argv); - - auto bus = QDBusConnection::systemBus(); - General api(OXIDE_SERVICE, OXIDE_SERVICE_PATH, bus); - qDebug() << "Requesting power API..."; - QDBusObjectPath path = api.requestAPI("power"); - if(path.path() == "/"){ - qDebug() << "Unable to get power API"; - return EXIT_FAILURE; - } - qDebug() << "Got the power API!"; - - Power power(OXIDE_SERVICE, path.path(), bus); - qDebug() << "Logging battery level:"; - qDebug() << power.batteryLevel(); - QObject::connect(&power, &Power::batteryLevelChanged, [](int batteryLevel){ - qDebug() << batteryLevel; - }); - return app.exec(); - } - -.. code:: shell - - #!/bin/bash - echo "Logging battery level:" - rot power get batteryLevel - rot power listen batteryLevelChanged - diff --git a/web/src/documentation/api/02_power.rst b/web/src/documentation/api/02_power.rst new file mode 100644 index 000000000..5ce80023e --- /dev/null +++ b/web/src/documentation/api/02_power.rst @@ -0,0 +1,108 @@ +========= +Power API +========= + ++----------------------+----------------------+----------------------+ +| Name | Specification | Description | ++======================+======================+======================+ +| state | ``INT32`` property | Currently requested | +| | (read/write) | power state. | +| | | Possible values: | +| | | - ``0`` Normal | +| | | - ``1`` Power Saving | ++----------------------+----------------------+----------------------+ +| batteryState | ``INT32`` property | Current battery | +| | (read) | state. | +| | | - ``0`` Unknown | +| | | - ``1`` Charging | +| | | - ``2`` Discharging | +| | | - ``3`` Not Present | ++----------------------+----------------------+----------------------+ +| batteryLevel | ``INT32`` property | Current battery | +| | (read) | percentage. | ++----------------------+----------------------+----------------------+ +| batteryTemperature | ``INT32`` property | Current battery | +| | (read) | temperature in | +| | | Celsius. | ++----------------------+----------------------+----------------------+ +| chargerState | ``INT32`` property | Current charger | +| | (read) | state. | +| | | - ``0`` Unknown | +| | | - ``1`` Connected | +| | | - ``2`` Not | +| | | Connected | ++----------------------+----------------------+----------------------+ +| stateChanged | signal | Signal sent when the | +| | - (out) ``INT32`` | requested power | +| | | state has changed. | ++----------------------+----------------------+----------------------+ +| batteryStateChanged | signal | Signal sent when the | +| | - (out) ``INT32`` | battery state has | +| | | changed. | ++----------------------+----------------------+----------------------+ +| batteryLevelChanged | signal | Signal sent when the | +| | - (out) ``INT32`` | battery level has | +| | | changed. | ++----------------------+----------------------+----------------------+ +| batte | signal | Signal sent when the | +| ryTemperatureChanged | - (out) ``INT32`` | battery temperature | +| | | has changed. | ++----------------------+----------------------+----------------------+ +| chargerStateChanged | signal | Signal sent when the | +| | - (out) ``INT32`` | charger state has | +| | | changed. | ++----------------------+----------------------+----------------------+ +| batteryWarning | signal | Signal sent when a | +| | | battery warning has | +| | | been detected. | ++----------------------+----------------------+----------------------+ +| batteryAlert | signal | Signal sent when a | +| | | battery alert has | +| | | been detected. | ++----------------------+----------------------+----------------------+ +| chargerWarning | signal | Signal sent when a | +| | | charger warning has | +| | | been detected. | ++----------------------+----------------------+----------------------+ + +.. _example-usage-6: + +Example Usage +~~~~~~~~~~~~~ + +.. code:: cpp + + #include + #include "dbusservice_interface.h" + #include "powerapi_interface.h" + + using namespace codes::eeems::oxide1; + + int main(int argc, char* argv[]){ + QCoreApplication app(argc, argv); + + auto bus = QDBusConnection::systemBus(); + General api(OXIDE_SERVICE, OXIDE_SERVICE_PATH, bus); + qDebug() << "Requesting power API..."; + QDBusObjectPath path = api.requestAPI("power"); + if(path.path() == "/"){ + qDebug() << "Unable to get power API"; + return EXIT_FAILURE; + } + qDebug() << "Got the power API!"; + + Power power(OXIDE_SERVICE, path.path(), bus); + qDebug() << "Logging battery level:"; + qDebug() << power.batteryLevel(); + QObject::connect(&power, &Power::batteryLevelChanged, [](int batteryLevel){ + qDebug() << batteryLevel; + }); + return app.exec(); + } + +.. code:: shell + + #!/bin/bash + echo "Logging battery level:" + rot power get batteryLevel + rot power listen batteryLevelChanged diff --git a/web/src/documentation/api/02_screenshot.rst b/web/src/documentation/api/02_screenshot.rst new file mode 100644 index 000000000..c231f914b --- /dev/null +++ b/web/src/documentation/api/02_screenshot.rst @@ -0,0 +1,153 @@ +========== +Screen API +========== + ++---------------------+----------------------+----------------------+ +| Name | Specification | Description | ++=====================+======================+======================+ +| screenshots | ` | Get the list of | +| | `ARRAY OBJECT_PATH`` | screenshots on the | +| | property (read) | device. | ++---------------------+----------------------+----------------------+ +| screenshotAdded | signal | Signal sent when a | +| | - (out) | screenshot is added. | +| | ``OBJECT_PATH`` | | ++---------------------+----------------------+----------------------+ +| screenshotRemoved | signal | Signal sent when a | +| | - (out) | screenshot is | +| | ``OBJECT_PATH`` | removed. | ++---------------------+----------------------+----------------------+ +| screenshotModified | signal | Signal sent when a | +| | - (out) | screenshot is | +| | ``OBJECT_PATH`` | modified. | ++---------------------+----------------------+----------------------+ +| addScreenshot | method | Add a screenshot | +| | - (in) blob | taken by an | +| | ``ARRAY BYTE`` | application. | +| | - (out) | | +| | ``OBJECT_PATH`` | | ++---------------------+----------------------+----------------------+ +| drawFullScreenImage | method | Draw an image to the | +| | - (in) path | screen. | +| | ``STRING`` | | +| | - (out) ``BOOLEAN`` | | ++---------------------+----------------------+----------------------+ +| screenshot | method | Take a screenshot. | +| | - (out) | | +| | ``OBJECT_PATH`` | | ++---------------------+----------------------+----------------------+ + +.. _example-usage-7: + +Example Usage +~~~~~~~~~~~~~ + +.. code:: cpp + + #include + #include "dbusservice_interface.h" + #include "screenapi_interface.h" + + using namespace codes::eeems::oxide1; + + int main(int argc, char* argv[]){ + Q_UNUSED(argc); + Q_UNUSED(argv); + + auto bus = QDBusConnection::systemBus(); + General api(OXIDE_SERVICE, OXIDE_SERVICE_PATH, bus); + qDebug() << "Requesting screen API..."; + QDBusObjectPath path = api.requestAPI("screen"); + if(path.path() == "/"){ + qDebug() << "Unable to get screen API"; + return EXIT_FAILURE; + } + qDebug() << "Got the screen API!"; + + Screen screen(OXIDE_SERVICE, path.path(), bus); + path = screen.screenshot(); + if(path.path() == "/"){ + qDebug() << "Screenshot failed"; + }else{ + qDebug() << "Screenshot taken"; + } + return EXIT_SUCCESS; + } + +.. code:: shell + + #!/bin/bash + echo -n "Screenshot " + if [ $(rot screen call screenshot | jq -cr) = "/" ]; then + echo "failed" + else + echo "taken" + fi + +Screenshot +~~~~~~~~~~ + ++----------+----------------------------+----------------------------+ +| Name | Specification | Description | ++==========+============================+============================+ +| blob | ``ARRAY BYTE`` property | The blob data of the | +| | (read/write) | screenshot. | ++----------+----------------------------+----------------------------+ +| path | ``STRING`` property (read) | The path to the screenshot | +| | | on disk. | ++----------+----------------------------+----------------------------+ +| modified | signal | Signal sent when the | +| | | screenshot is modified. | ++----------+----------------------------+----------------------------+ +| removed | signal | Signal sent when the | +| | | screenshot is removed. | ++----------+----------------------------+----------------------------+ +| remove | method | Remove the screenshot from | +| | | the device. | ++----------+----------------------------+----------------------------+ + +.. _example-usage-8: + +Example Usage +^^^^^^^^^^^^^ + +.. code:: cpp + + #include + #include "dbusservice_interface.h" + #include "screenapi_interface.h" + #include "screenshot_interface.h" + + using namespace codes::eeems::oxide1; + + int main(int argc, char* argv[]){ + Q_UNUSED(argc); + Q_UNUSED(argv); + + auto bus = QDBusConnection::systemBus(); + General api(OXIDE_SERVICE, OXIDE_SERVICE_PATH, bus); + qDebug() << "Requesting screen API..."; + QDBusObjectPath path = api.requestAPI("screen"); + if(path.path() == "/"){ + qDebug() << "Unable to get screen API"; + return EXIT_FAILURE; + } + qDebug() << "Got the screen API!"; + + Screen screen(OXIDE_SERVICE, path.path(), bus); + for(auto path : screen.screenshots()){ + Screenshot(OXIDE_SERVICE, path.path(), bus).remove().waitForFinished(); + } + qDebug() << "Screenshots removed"; + return EXIT_SUCCESS; + } + +.. code:: shell + + #!/bin/bash + rot screen get screenshots \ + | jq -cr 'values | join("\n")' \ + | sed 's|/codes/eeems/oxide1/||' \ + | xargs -rI {} rot --object Screenshot:{} screen call remove + echo "Screenshots removed" + diff --git a/web/src/documentation/api/04_screenshot.rst b/web/src/documentation/api/02_system.rst similarity index 57% rename from web/src/documentation/api/04_screenshot.rst rename to web/src/documentation/api/02_system.rst index b993c00f5..0acbc0e60 100644 --- a/web/src/documentation/api/04_screenshot.rst +++ b/web/src/documentation/api/02_system.rst @@ -1,158 +1,6 @@ ========== -Screen API -========== - -+---------------------+----------------------+----------------------+ -| Name | Specification | Description | -+=====================+======================+======================+ -| screenshots | ` | Get the list of | -| | `ARRAY OBJECT_PATH`` | screenshots on the | -| | property (read) | device. | -+---------------------+----------------------+----------------------+ -| screenshotAdded | signal | Signal sent when a | -| | - (out) | screenshot is added. | -| | ``OBJECT_PATH`` | | -+---------------------+----------------------+----------------------+ -| screenshotRemoved | signal | Signal sent when a | -| | - (out) | screenshot is | -| | ``OBJECT_PATH`` | removed. | -+---------------------+----------------------+----------------------+ -| screenshotModified | signal | Signal sent when a | -| | - (out) | screenshot is | -| | ``OBJECT_PATH`` | modified. | -+---------------------+----------------------+----------------------+ -| addScreenshot | method | Add a screenshot | -| | - (in) blob | taken by an | -| | ``ARRAY BYTE`` | application. | -| | - (out) | | -| | ``OBJECT_PATH`` | | -+---------------------+----------------------+----------------------+ -| drawFullScreenImage | method | Draw an image to the | -| | - (in) path | screen. | -| | ``STRING`` | | -| | - (out) ``BOOLEAN`` | | -+---------------------+----------------------+----------------------+ -| screenshot | method | Take a screenshot. | -| | - (out) | | -| | ``OBJECT_PATH`` | | -+---------------------+----------------------+----------------------+ - -.. _example-usage-7: - -Example Usage -~~~~~~~~~~~~~ - -.. code:: cpp - - #include - #include "dbusservice_interface.h" - #include "screenapi_interface.h" - - using namespace codes::eeems::oxide1; - - int main(int argc, char* argv[]){ - Q_UNUSED(argc); - Q_UNUSED(argv); - - auto bus = QDBusConnection::systemBus(); - General api(OXIDE_SERVICE, OXIDE_SERVICE_PATH, bus); - qDebug() << "Requesting screen API..."; - QDBusObjectPath path = api.requestAPI("screen"); - if(path.path() == "/"){ - qDebug() << "Unable to get screen API"; - return EXIT_FAILURE; - } - qDebug() << "Got the screen API!"; - - Screen screen(OXIDE_SERVICE, path.path(), bus); - path = screen.screenshot(); - if(path.path() == "/"){ - qDebug() << "Screenshot failed"; - }else{ - qDebug() << "Screenshot taken"; - } - return EXIT_SUCCESS; - } - -.. code:: shell - - #!/bin/bash - echo -n "Screenshot " - if [ $(rot screen call screenshot | jq -cr) = "/" ]; then - echo "failed" - else - echo "taken" - fi - -Screenshot -~~~~~~~~~~ - -+----------+----------------------------+----------------------------+ -| Name | Specification | Description | -+==========+============================+============================+ -| blob | ``ARRAY BYTE`` property | The blob data of the | -| | (read/write) | screenshot. | -+----------+----------------------------+----------------------------+ -| path | ``STRING`` property (read) | The path to the screenshot | -| | | on disk. | -+----------+----------------------------+----------------------------+ -| modified | signal | Signal sent when the | -| | | screenshot is modified. | -+----------+----------------------------+----------------------------+ -| removed | signal | Signal sent when the | -| | | screenshot is removed. | -+----------+----------------------------+----------------------------+ -| remove | method | Remove the screenshot from | -| | | the device. | -+----------+----------------------------+----------------------------+ - -.. _example-usage-8: - -Example Usage -^^^^^^^^^^^^^ - -.. code:: cpp - - #include - #include "dbusservice_interface.h" - #include "screenapi_interface.h" - #include "screenshot_interface.h" - - using namespace codes::eeems::oxide1; - - int main(int argc, char* argv[]){ - Q_UNUSED(argc); - Q_UNUSED(argv); - - auto bus = QDBusConnection::systemBus(); - General api(OXIDE_SERVICE, OXIDE_SERVICE_PATH, bus); - qDebug() << "Requesting screen API..."; - QDBusObjectPath path = api.requestAPI("screen"); - if(path.path() == "/"){ - qDebug() << "Unable to get screen API"; - return EXIT_FAILURE; - } - qDebug() << "Got the screen API!"; - - Screen screen(OXIDE_SERVICE, path.path(), bus); - for(auto path : screen.screenshots()){ - Screenshot(OXIDE_SERVICE, path.path(), bus).remove().waitForFinished(); - } - qDebug() << "Screenshots removed"; - return EXIT_SUCCESS; - } - -.. code:: shell - - #!/bin/bash - rot screen get screenshots \ - | jq -cr 'values | join("\n")' \ - | sed 's|/codes/eeems/oxide1/||' \ - | xargs -rI {} rot --object Screenshot:{} screen call remove - echo "Screenshots removed" - System API ----------- +========== +----------------------+----------------------+----------------------+ | Name | Specification | Description | diff --git a/web/src/documentation/api/05_wifi.rst b/web/src/documentation/api/02_wifi.rst similarity index 100% rename from web/src/documentation/api/05_wifi.rst rename to web/src/documentation/api/02_wifi.rst diff --git a/web/src/faq.rst b/web/src/faq.rst index b46c52e5b..3241a3055 100644 --- a/web/src/faq.rst +++ b/web/src/faq.rst @@ -34,9 +34,9 @@ How can I disable the telemetry? .. code:: bash - rot settings set telemetry false - rot settings set crashReport false - rot settings set applicationUsage false + xdg-settings set telemetry false + xdg-settings set crashReport false + xdg-settings set applicationUsage false Or you can compile the applications manually without the ``sentry`` feature enabled. @@ -63,14 +63,17 @@ Oxide (and most other applications) on the reMarkable 2 requires How do I change my pin after I've set it? ========================================= -There is no way to currently trigger a pin change, but you can wipe your current pin, and trigger -the pin setting dialog by doing the following: +As of 2.6 you can change your pin to any 4 numbers with the following command: .. code:: bash - systemctl stop tarnish - rm /home/root/.config/Eeems/decay.conf - systemctl start tarnish + xdg-settings set pin + +As of 2.6 you can clear your pin to skip the lock screen with the following command: + +.. code:: bash + + xdg-settings set pin '' Not all of my applications are listed? @@ -81,6 +84,12 @@ top left of the launcher. If your application is still not listed, you may need logs to determine why it's failing to load. If an application is configured in draft to pass arguments in the ``call=`` line, it will fail to import as this is not supported by Oxide. +You can check for errors with your application registration files with the following command: + +.. code:: bash + + desktop-file-validate /opt/usr/share/applications/*.oxide + How do I review my device logs? =============================== @@ -92,6 +101,13 @@ for Oxide's programs, and any application you run through Oxide, you can run the journalctl -eau tarnish +As of Oxide 2.5, you can now get logs for specific applications with the following, where +``codes.eeems.oxide`` is the name of the application as it's been registered. + +.. code:: bash + + journalctl -eat codes.eeems.oxide + Where are the configuration files? ================================== diff --git a/web/src/index.rst b/web/src/index.rst index d837f5f8e..14df8f7d8 100644 --- a/web/src/index.rst +++ b/web/src/index.rst @@ -2,6 +2,11 @@ Home ==== +.. raw:: html + + +
+ Oxide is a `desktop environment `_ for the `reMarkable tablet `_. Features