├── .gitignore
├── .gitmodules
├── 3rdparty
└── CMakeLists.txt
├── CMakeLists.txt
├── CODE OF CONDUCT.md
├── HACKING.md
├── LICENSE
├── Messages.sh
├── MiTail-Firmware
├── README.md
├── changelog.txt
├── data
├── AndroidManifest.xml
├── ic_launcher-web.png
├── res
│ ├── drawable-hdpi
│ │ └── ic_stat_notification_icon.png
│ ├── drawable-mdpi
│ │ └── ic_stat_notification_icon.png
│ ├── drawable-xhdpi
│ │ └── ic_stat_notification_icon.png
│ ├── drawable-xxhdpi
│ │ └── ic_stat_notification_icon.png
│ ├── drawable-xxxhdpi
│ │ └── ic_stat_notification_icon.png
│ ├── drawable
│ │ ├── digitail.png
│ │ ├── digitail_small.png
│ │ └── splash.xml
│ ├── mipmap-anydpi-v26
│ │ ├── ic_launcher.xml
│ │ └── ic_launcher_round.xml
│ ├── mipmap-hdpi
│ │ ├── ic_launcher.png
│ │ ├── ic_launcher_foreground.png
│ │ └── ic_launcher_round.png
│ ├── mipmap-mdpi
│ │ ├── ic_launcher.png
│ │ ├── ic_launcher_foreground.png
│ │ └── ic_launcher_round.png
│ ├── mipmap-xhdpi
│ │ ├── ic_launcher.png
│ │ ├── ic_launcher_foreground.png
│ │ └── ic_launcher_round.png
│ ├── mipmap-xxhdpi
│ │ ├── ic_launcher.png
│ │ ├── ic_launcher_foreground.png
│ │ └── ic_launcher_round.png
│ ├── mipmap-xxxhdpi
│ │ ├── ic_launcher.png
│ │ ├── ic_launcher_foreground.png
│ │ └── ic_launcher_round.png
│ ├── raw
│ │ └── splash.mp4
│ └── values
│ │ ├── ic_launcher_background.xml
│ │ └── strings.xml
└── src
│ └── org
│ └── thetailcompany
│ └── digitail
│ ├── SplashActivity.java
│ ├── TailActivity.java
│ └── TailService.java
├── digitail.svg
├── servo drawing 1.jpg
├── servo drawing 2.jpg
├── src
├── Alarm.cpp
├── Alarm.h
├── AlarmList.cpp
├── AlarmList.h
├── AppSettings.cpp
├── AppSettings.h
├── AppSettingsProxy.rep
├── BTConnectionManager.cpp
├── BTConnectionManager.h
├── BTConnectionManagerProxy.rep
├── CMakeLists.txt
├── CommandInfo.cpp
├── CommandInfo.h
├── CommandModel.cpp
├── CommandModel.h
├── CommandPersistence.cpp
├── CommandPersistence.h
├── CommandQueue.cpp
├── CommandQueue.h
├── CommandQueueProxy.rep
├── DeviceModel.cpp
├── DeviceModel.h
├── FilterProxyModel.cpp
├── FilterProxyModel.h
├── GearBase.cpp
├── GearBase.h
├── GearCommandModel.cpp
├── GearCommandModel.h
├── GestureController.cpp
├── GestureController.h
├── GestureControllerProxy.rep
├── GestureDetectorModel.cpp
├── GestureDetectorModel.h
├── GestureSensor.cpp
├── GestureSensor.h
├── IdleMode.cpp
├── IdleMode.h
├── PermissionsManager.cpp
├── PermissionsManager.h
├── Utilities.cpp
├── Utilities.h
├── WalkingSensorGestureReconizer.cpp
├── WalkingSensorGestureReconizer.h
├── audio
│ └── Sparkle-sound-effect.mp3
├── commands
│ ├── digitail-builtin.crumpet
│ ├── eargear-base.crumpet
│ ├── eargear2-base.crumpet
│ ├── mitail-builtin.crumpet
│ ├── mitail-lights-builtin.crumpet
│ ├── mitailmini-builtin.crumpet
│ └── mitailmini-lights-builtin.crumpet
├── convert_translations.sh
├── gearimplementations
│ ├── GearDigitail.cpp
│ ├── GearDigitail.h
│ ├── GearEars.cpp
│ ├── GearEars.h
│ ├── GearFake.cpp
│ ├── GearFake.h
│ ├── GearFlutterWings.cpp
│ ├── GearFlutterWings.h
│ ├── GearMitail.cpp
│ ├── GearMitail.h
│ ├── GearMitailMini.cpp
│ └── GearMitailMini.h
├── icontheme-index.theme
├── images
│ ├── alarm.svg
│ ├── banner_image.png
│ ├── casualmode.svg
│ ├── crumpet-head.svg
│ ├── ear-sequence.svg
│ ├── eargear.svg
│ ├── earposes.svg
│ ├── flutter.svg
│ ├── glowtip.svg
│ ├── listeningmode.svg
│ ├── logo.svg
│ ├── mitailmini.svg
│ ├── movelist.svg
│ ├── moves.svg
│ ├── tail.svg
│ ├── tail_lights.svg
│ ├── tail_moves.svg
│ ├── taildevice.svg
│ └── tiltmode.svg
├── kirigami-icons.qrc
├── main.cpp
├── po
│ ├── cs_CZ
│ │ └── digitail.po
│ ├── da_DK
│ │ └── digitail.po
│ ├── de_CH
│ │ └── digitail.po
│ ├── de_DE
│ │ └── digitail.po
│ ├── digitail.pot
│ ├── en@pirate
│ │ └── digitail.po
│ ├── en_FY
│ │ └── digitail.po
│ ├── es_ES
│ │ └── digitail.po
│ ├── fr
│ │ └── digitail.po
│ ├── it
│ │ └── digitail.po
│ ├── ja_JP
│ │ └── digitail.po
│ ├── lt
│ │ └── digitail.po
│ ├── nl_NL
│ │ └── digitail.po
│ └── ru
│ │ └── digitail.po
├── qml
│ ├── AboutPage.qml
│ ├── AlarmEditor.qml
│ ├── AlarmList.qml
│ ├── BaseCommandListEditor.qml
│ ├── BaseMovesComponent.qml
│ ├── BasicListItem.qml
│ ├── CommandPausePicker.qml
│ ├── ConnectToTail.qml
│ ├── DatePicker.qml
│ ├── DeveloperModePage.qml
│ ├── DisconnectOptions.qml
│ ├── EarPoses.qml
│ ├── FlatButton.qml
│ ├── GearGestures.qml
│ ├── IdleModePage.qml
│ ├── IdlePauseRangePicker.qml
│ ├── InfoCard.qml
│ ├── ListenSettings.qml
│ ├── MessageBox.qml
│ ├── MoveListEditor.qml
│ ├── MoveLists.qml
│ ├── NamePicker.qml
│ ├── NotConnectedCard.qml
│ ├── PickACommandSheet.qml
│ ├── SettingsCard.qml
│ ├── SettingsCrumpetPicker.qml
│ ├── SettingsPage.qml
│ ├── TailBattery.qml
│ ├── TailLights.qml
│ ├── TailMoves.qml
│ ├── TiltSettings.qml
│ ├── TimePicker.qml
│ ├── WelcomePage.qml
│ └── main.qml
├── qtquickcontrols2.conf
├── resources.qrc
└── video
│ ├── readme
│ └── squarelo.mpeg
└── wiki
├── LEDUS1.jpg
├── Serial Bluetooth Terminal.jpg
├── USERMOVE graphical example.jpg
├── VER.jpg
├── connect failed.jpg
├── connected.jpg
├── devices.jpg
├── dt-VER.jpg
├── dt-connected.jpg
├── dt-devices.jpg
├── profile.jpg
└── terminal.jpg
/.gitignore:
--------------------------------------------------------------------------------
1 | # C++ objects and libs
2 | *.slo
3 | *.lo
4 | *.o
5 | *.a
6 | *.la
7 | *.lai
8 | *.so
9 | *.dll
10 | *.dylib
11 |
12 | # Qt-es
13 | object_script.*.Release
14 | object_script.*.Debug
15 | *_plugin_import.cpp
16 | /.qmake.cache
17 | /.qmake.stash
18 | *.pro.user
19 | *.pro.user.*
20 | *.qbs.user
21 | *.qbs.user.*
22 | *.moc
23 | moc_*.cpp
24 | moc_*.h
25 | qrc_*.cpp
26 | ui_*.h
27 | *.qmlc
28 | *.jsc
29 | Makefile*
30 | *build-*
31 |
32 | # Qt unit tests
33 | target_wrapper.*
34 |
35 | # QtCreator
36 | *.autosave
37 |
38 | # QtCreator Qml
39 | *.qmlproject.user
40 | *.qmlproject.user.*
41 |
42 | # QtCreator CMake
43 | CMakeLists.txt.user*
44 |
45 | build/*
46 | export/*
47 |
48 | # A small variety if things that don't want to go into our repository
49 | *.apk
50 | *.idsig
51 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "3rdparty/breeze-icons"]
2 | path = 3rdparty/breeze-icons
3 | url = https://invent.kde.org/frameworks/breeze-icons.git
4 | branch = master
5 |
--------------------------------------------------------------------------------
/3rdparty/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | option(BUILD_SHARED_LIBS "Build a shared module" OFF)
2 | option(DISABLE_DBUS "Build without D-Bus support" ON)
3 | option(BUILD_TESTING "Build tests for ki18n" OFF)
4 |
5 | add_subdirectory(kirigami)
6 | add_subdirectory(ki18n)
7 |
--------------------------------------------------------------------------------
/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | cmake_minimum_required(VERSION 3.16)
2 | project(digitail VERSION 0.1)
3 | if (POLICY CMP0063)
4 | cmake_policy(SET CMP0063 NEW)
5 | endif()
6 |
7 | set(CMAKE_CXX_STANDARD 17)
8 | set(CMAKE_CXX_EXTENSIONS OFF)
9 |
10 | set(BREEZEICONS_DIR ${CMAKE_SOURCE_DIR}/3rdparty/breeze-icons/)
11 | set(DISABLE_DBUS true)
12 |
13 | set(KF_MIN_VERSION "5.240.0")
14 | set(QT_MIN_VERSION "6.5")
15 |
16 | include(FeatureSummary)
17 | find_package(ECM ${KF_MIN_VERSION} REQUIRED NO_MODULE)
18 | feature_summary(WHAT REQUIRED_PACKAGES_NOT_FOUND FATAL_ON_MISSING_REQUIRED_PACKAGES)
19 | set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH})
20 |
21 | include(ECMSetupVersion)
22 | include(KDEInstallDirs)
23 | include(KDECMakeSettings)
24 | include(KDECompilerSettings NO_POLICY_SCOPE)
25 | if (ANDROID)
26 | include(ECMAddAndroidApk)
27 | endif ()
28 |
29 | # Android has no DBus, so we can't use that. Sniff things and tell the code
30 | if(CMAKE_SYSTEM_NAME STREQUAL Android)
31 | message("Building for Android - this means no dbus, and other small details. Work with that")
32 | add_definitions(-DANDROID)
33 | find_package(Qt6 ${Qt_MIN_VERSION} NO_MODULE REQUIRED Core Gui Quick Multimedia Sensors Test Widgets QuickControls2 Svg Bluetooth RemoteObjects)
34 | find_package(OpenSSL REQUIRED)
35 | elseif(WIN32)
36 | message("Building for Windows - this means no dbus, and other small details. Work with that")
37 | add_definitions(-DWINDOWS)
38 | find_package(Qt6 ${Qt_MIN_VERSION} NO_MODULE REQUIRED Core Gui Quick Multimedia Sensors Test Widgets QuickControls2 Svg Bluetooth RemoteObjects)
39 | else()
40 | find_package(Qt6 ${Qt_MIN_VERSION} NO_MODULE REQUIRED Core Gui Quick Multimedia Sensors Test Widgets QuickControls2 Svg Bluetooth RemoteObjects)
41 | endif()
42 |
43 | find_package(KF6 ${KF_MIN_VERSION} REQUIRED Kirigami I18n)
44 | set_package_properties(KF6Kirigami PROPERTIES
45 | DESCRIPTION "KDE's lightweight user interface framework for mobile and convergent applications"
46 | URL "https://techbase.kde.org/Kirigami"
47 | PURPOSE "Required for the main UI of the app"
48 | TYPE RUNTIME)
49 |
50 | add_subdirectory(src)
51 |
52 | feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES)
53 |
--------------------------------------------------------------------------------
/CODE OF CONDUCT.md:
--------------------------------------------------------------------------------
1 | Tail Company
2 | Code of Conduct
3 | Crumpet, in both iOS and Android versions, are open source community projects about working, as a group, to empower ourselves and others to have control over our own animatronic wearable gear. Just as we hope to increase technological diversity on the Internet through decentralization, we also believe that diverse viewpoints and voices among our community members foster innovation and creative solutions to the challenges we face. Its also more fun!
4 | We are committed to providing a safe, welcoming, and harrassment-free space for collaboration, for everyone, without regard to age, disability, economic situation, ethnicity, gender identity and expression, language fluency, level of knowledge or experience, nationality, personal appearance, race, religion, sexual identity and orientation, or any other attribute.
5 |
6 | The maintainers of the Crumpet apps share the dual responsibility of leading by example and enforcing these policies as necessary to maintain an open and welcoming environment. All community members should be excellent to each other.
7 |
8 | Scope
9 | This Code of Conduct applies to all places where the Crumpet app community activity is ocurring, including on GitHub, in discussion forums like Telegram, on social media, and in real life. The Code of Conduct applies not only on websites/at events run by the Crumpet app community (e.g. our GitHub organization) but also at any other location where the Crumpet app community is present.
10 |
11 | Our Standards
12 | Examples of behavior that contributes to creating a positive environment include:
13 |
14 | Using welcoming and inclusive language
15 | Being respectful of differing viewpoints and experiences
16 | Gracefully accepting constructive criticism
17 | Showing empathy towards other community members
18 | Making room for new and quieter voices
19 |
20 | Examples of unacceptable behavior by participants include:
21 | The use of sexualized language or imagery and unwelcome sexual attention or advances
22 | Trolling, insulting/derogatory/unwelcome comments, and personal or political attacks
23 | Public or private harassment
24 | Publishing others' private information, such as a physical or electronic address, without explicit permission
25 | Aggressive and micro-aggressive behavior, such as unconstructive criticism, providing corrections that do not improve the conversation (sometimes referred to as "well actually"s), repeatedly interrupting or talking over someone else, feigning surprise at someone's lack of knowledge or awareness about a topic.
26 | Other conduct which could reasonably be considered inappropriate in a professional setting
27 | Retaliating against anyone who reports a violation of this code.
28 | We will not tolerate harassment. Harassment is any unwelcome or hostile behavior towards another person for any reason. This includes, but is not limited to, offensive verbal comments related to personal characteristics or choices, sexual images or comments, deliberate intimidation, bullying, stalking, following, harassing photography or recording, sustained disruption of discussion or events, nonconsensual publication of private comments, inappropriate physical contact, or unwelcome sexual attention. Conduct need not be intentional to be harassment.
29 |
30 | Enforcement
31 | We will remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not consistent with this Code of Conduct. We may ban, temporarily or permanently, any contributor for violating this code, when appropriate.
32 |
33 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project lead, Andrew Shoben (@MasterTailer). All reports will be treated confidentially, impartially, consistently, and swiftly.
34 |
35 | Because the need for confidentiality for all parties involved in an enforcement action outweighs the goals of openness, limited information will be shared with the Crumpet community regarding enforcement actions that have taken place.
36 |
37 | Attribution
38 | This Code of Conduct is adapted from the Contributor Covenant, version 1.4 and the code of conduct of Code for DC.
--------------------------------------------------------------------------------
/HACKING.md:
--------------------------------------------------------------------------------
1 | # Working on the DIGITAiL App
2 |
3 | ## Coding style
4 |
5 | The coding style employed in the DIGITAiL codebase is
6 | [the KDELibs codestyle](https://community.kde.org/Policies/Kdelibs_Coding_Style)
7 |
8 | ## Code Layout
9 |
10 | Note that the following does not include code function documentation as such,
11 | rather it focuses on overview. See the header files for each specific class
12 | for the documentation for each specific class
13 |
14 | ### Basic filesystem layout
15 |
16 | - **(root)** The root folder, containing the base build information, and the main CMakeLists.txt project file
17 | - **3rdparty** Contains external code (in particular the Kirigami Qt Quick UI framework)
18 | - **data** The Android specific parts of the codebase, and Android gradle build instructions
19 | - **src** The sourcecode for both the service and the frontend
20 |
21 | ### Code Concepts
22 |
23 | The code is split in two main parts:
24 |
25 | - Frontend application (based on the Kirigami Qt Quick UI framework)
26 | - Backend service (QCoreApplication on everything but Android, where it is a QAndroidService)
27 |
28 | The interaction between the backend and frontend is done through objects replicated
29 | through Qt's RemoteObjects module.
30 |
31 | ### Backend/Service
32 |
33 | The service is launched with the command `digitail -service`
34 |
35 | Upon launching the service, a number of objects are created and exposed through a local
36 | RemoteObjects node. The exposed object classes, in hierarchical layout, are:
37 |
38 | - **AppSettings** Contains basic settings for the application, such as advanced mode, and the various idle mode settings (and future settings such as tail name and the like)
39 | - **BTConnectionManager** Central interface for connecting to tails, exposing both connection functions, and the three further convenience objects listed below:
40 | - **BTDeviceModel** A QAbstractListModel containing bluetooth devices for display if more than one tail is found
41 | - **TailCommandModel** Once BTConnectionManager has connected to a tail, this model is automatically filled with commands, according to the version of the tail
42 | - **CommandQueue** The main interface for sending commands to the tail. This supports sending any number of commands in order, and further supports list manipulation options to adjust the queue.
43 |
44 | It further includes the IdleMode class, which handles queueing up random
45 | commands and pauses, if the mode is turned on in AppSettings. This class is not
46 | exported, as it is controlled indirectly and operates without user interaction.
47 |
48 | ### Frontend
49 |
50 | The frontend is launched with the command `digitail`
51 |
52 | Upon launching the application, it will first create the QML application engine,
53 | and register a few types on it (primarily the Kirigami types, and some
54 | convenience types). It will then attempt to connect to the RemoteObjects node,
55 | and acquire the replication objects. These objects are then registered as
56 | context property objects on the engine's root context. Finally, the engine is
57 | asked to load the application main qml file, which in turn will show the main
58 | window. All communication between the frontend and backend is done through these
59 | objects.
60 |
61 | The base layout of the frontend's UI is:
62 |
63 | - **main.qml** Contains the root Kirigami ApplicationWindow, which holds a number of pages, as well as the logic of the global context drawer (which allows for switching directly between WelcomePage, TailMoves, TailLights, IdleModePage and AboutPage).
64 | - **qml/AboutPage.qml** A simple informational page which shows support, version and licensing information about the application, as well as contributor details.
65 | - **qml/BaseMovesPage.qml** A page containing a list of cards
66 | - **qml/ConnectToTail.qml** A sheet which can be shown to the user, listing all found tails. This should only be shown if more than one tail has been discovered.
67 | - **qml/IdleModePage.qml** Settings for idle mode: The range of duration of the pause between random commands, and the categories to include in the selection.
68 | - **qml/NotConnectedCard.qml** A component which gives the user sensible options to perform when not connected to a tail.
69 | - **qml/TailLights.qml** A specific instance of BaseMovesPage which shows the single lighting category of tail commands.
70 | - **qml/TailMoves.qml** A specific instance of BaseMovesPage which shows the three movement categories of tail commands.
71 | - **qml/WelcomePage.qml** The first page shown to the user upon launching the application. This shows a grid of functional options (alarm, tail poses editor, moves playlist, tail lights, and tail moves). If there is no connected tail, an instance of the NotConnectedCard is shown at the top. If the user is connected to a tail, a tickbox shown in a card underneath the function grid allows the user to turn on idle mode, and when turned on allows the user to switch to the idle mode settings page.
72 |
--------------------------------------------------------------------------------
/Messages.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | BASEDIR="./src/" # root of translatable sources
3 | PROJECT="digitail" # project name
4 | BUGADDR="https://github.com/MasterTailer/CRUMPET/issues" # MSGID-Bugs
5 | WDIR=`pwd` # working dir
6 |
7 | echo "Extracting messages"
8 | cd ${BASEDIR}
9 | # see above on sorting
10 | find . -name '*.cpp' -o -name '*.h' -o -name '*.c' -o -name '*.qml' | sort > ${WDIR}/infiles.list
11 | cd ${WDIR}
12 | xgettext --from-code=UTF-8 -C -kde -ci18n -ki18n:1 -ki18nc:1c,2 -ki18np:1,2 -ki18ncp:1c,2,3 -ktr2i18n:1 \
13 | -kI18N_NOOP:1 -kI18N_NOOP2:1c,2 -kaliasLocale -kki18n:1 -kki18nc:1c,2 -kki18np:1,2 -kki18ncp:1c,2,3 \
14 | --msgid-bugs-address="${BUGADDR}" \
15 | --files-from=infiles.list -D ${BASEDIR} -D ${WDIR} -o ${PROJECT}.pot || { echo "error while calling xgettext. aborting."; exit 1; }
16 | echo "Done extracting messages"
17 |
18 |
19 | echo "Merging translations"
20 | catalogs=`find . -name '*.po' -not -path "./craft-*"`
21 | for cat in $catalogs; do
22 | echo $cat
23 | msgmerge -o $cat.new $cat ${PROJECT}.pot
24 | mv $cat.new $cat
25 | done
26 | echo "Done merging translations"
27 |
28 |
29 | echo "Cleaning up"
30 | cd ${WDIR}
31 | rm infiles.list
32 | echo "Done"
33 |
--------------------------------------------------------------------------------
/MiTail-Firmware:
--------------------------------------------------------------------------------
1 | # Latest MiTail firmware version
2 |
3 | Changelog
4 |
5 | 4.0.2 Red LED activity improved, and NFM Blue led changed
6 | 4.0.1 MiTail cannot be switched off during charging
7 | 4.0.0 New moves, smoothing, cleaned up code, NFM, and more.
8 | 3.2.8 - First release. Small aesthetic change to TAILER, to account for shorter tails
9 |
10 | OTA updates taking approximately 3 minutes to flash new partition.
11 |
--------------------------------------------------------------------------------
/data/ic_launcher-web.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OpenTails/CRUMPET-Android/f540d6f35e62b208f8949cec2c3d21f10d94b60a/data/ic_launcher-web.png
--------------------------------------------------------------------------------
/data/res/drawable-hdpi/ic_stat_notification_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OpenTails/CRUMPET-Android/f540d6f35e62b208f8949cec2c3d21f10d94b60a/data/res/drawable-hdpi/ic_stat_notification_icon.png
--------------------------------------------------------------------------------
/data/res/drawable-mdpi/ic_stat_notification_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OpenTails/CRUMPET-Android/f540d6f35e62b208f8949cec2c3d21f10d94b60a/data/res/drawable-mdpi/ic_stat_notification_icon.png
--------------------------------------------------------------------------------
/data/res/drawable-xhdpi/ic_stat_notification_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OpenTails/CRUMPET-Android/f540d6f35e62b208f8949cec2c3d21f10d94b60a/data/res/drawable-xhdpi/ic_stat_notification_icon.png
--------------------------------------------------------------------------------
/data/res/drawable-xxhdpi/ic_stat_notification_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OpenTails/CRUMPET-Android/f540d6f35e62b208f8949cec2c3d21f10d94b60a/data/res/drawable-xxhdpi/ic_stat_notification_icon.png
--------------------------------------------------------------------------------
/data/res/drawable-xxxhdpi/ic_stat_notification_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OpenTails/CRUMPET-Android/f540d6f35e62b208f8949cec2c3d21f10d94b60a/data/res/drawable-xxxhdpi/ic_stat_notification_icon.png
--------------------------------------------------------------------------------
/data/res/drawable/digitail.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OpenTails/CRUMPET-Android/f540d6f35e62b208f8949cec2c3d21f10d94b60a/data/res/drawable/digitail.png
--------------------------------------------------------------------------------
/data/res/drawable/digitail_small.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OpenTails/CRUMPET-Android/f540d6f35e62b208f8949cec2c3d21f10d94b60a/data/res/drawable/digitail_small.png
--------------------------------------------------------------------------------
/data/res/drawable/splash.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | -
4 |
5 |
6 |
7 |
8 | -
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/data/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/data/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/data/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OpenTails/CRUMPET-Android/f540d6f35e62b208f8949cec2c3d21f10d94b60a/data/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/data/res/mipmap-hdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OpenTails/CRUMPET-Android/f540d6f35e62b208f8949cec2c3d21f10d94b60a/data/res/mipmap-hdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/data/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OpenTails/CRUMPET-Android/f540d6f35e62b208f8949cec2c3d21f10d94b60a/data/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/data/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OpenTails/CRUMPET-Android/f540d6f35e62b208f8949cec2c3d21f10d94b60a/data/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/data/res/mipmap-mdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OpenTails/CRUMPET-Android/f540d6f35e62b208f8949cec2c3d21f10d94b60a/data/res/mipmap-mdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/data/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OpenTails/CRUMPET-Android/f540d6f35e62b208f8949cec2c3d21f10d94b60a/data/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/data/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OpenTails/CRUMPET-Android/f540d6f35e62b208f8949cec2c3d21f10d94b60a/data/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/data/res/mipmap-xhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OpenTails/CRUMPET-Android/f540d6f35e62b208f8949cec2c3d21f10d94b60a/data/res/mipmap-xhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/data/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OpenTails/CRUMPET-Android/f540d6f35e62b208f8949cec2c3d21f10d94b60a/data/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/data/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OpenTails/CRUMPET-Android/f540d6f35e62b208f8949cec2c3d21f10d94b60a/data/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/data/res/mipmap-xxhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OpenTails/CRUMPET-Android/f540d6f35e62b208f8949cec2c3d21f10d94b60a/data/res/mipmap-xxhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/data/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OpenTails/CRUMPET-Android/f540d6f35e62b208f8949cec2c3d21f10d94b60a/data/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/data/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OpenTails/CRUMPET-Android/f540d6f35e62b208f8949cec2c3d21f10d94b60a/data/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/data/res/mipmap-xxxhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OpenTails/CRUMPET-Android/f540d6f35e62b208f8949cec2c3d21f10d94b60a/data/res/mipmap-xxxhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/data/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OpenTails/CRUMPET-Android/f540d6f35e62b208f8949cec2c3d21f10d94b60a/data/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/data/res/raw/splash.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OpenTails/CRUMPET-Android/f540d6f35e62b208f8949cec2c3d21f10d94b60a/data/res/raw/splash.mp4
--------------------------------------------------------------------------------
/data/res/values/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #FFFFFF
4 |
--------------------------------------------------------------------------------
/data/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Crumpet
4 | Connected to your gear
5 | The controller is connected to your gear
6 |
7 |
--------------------------------------------------------------------------------
/data/src/org/thetailcompany/digitail/SplashActivity.java:
--------------------------------------------------------------------------------
1 | package org.thetailcompany.digitail;
2 |
3 | import android.media.MediaPlayer;
4 | import android.media.MediaPlayer.OnCompletionListener;
5 | import android.net.Uri;
6 | import android.os.Bundle;
7 | import android.app.Activity;
8 | import android.content.Intent;
9 | import android.widget.VideoView;
10 | import android.widget.RelativeLayout;
11 | import android.view.MotionEvent;
12 | import android.graphics.Color;
13 | import android.preference.PreferenceManager;
14 | import android.content.SharedPreferences;
15 |
16 | public class SplashActivity extends Activity
17 | {
18 | @Override
19 | protected void onCreate(Bundle savedInstanceState)
20 | {
21 | super.onCreate(savedInstanceState);
22 |
23 | try
24 | {
25 | SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
26 | boolean isPlaySplashVideo = preferences.getBoolean("isPlaySplashVideo", true);
27 | if (!isPlaySplashVideo)
28 | {
29 | jump();
30 | return;
31 | }
32 |
33 | SharedPreferences.Editor editor = preferences.edit();
34 | editor.putBoolean("isPlaySplashVideo", false);
35 | editor.commit();
36 |
37 | // Create parent RelativeLayout
38 | RelativeLayout parentLayout = new RelativeLayout(this);
39 | RelativeLayout.LayoutParams parentLayoutParam = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.MATCH_PARENT);
40 | parentLayout.setLayoutParams(parentLayoutParam);
41 |
42 | // Create child VideoView
43 | VideoView videoHolder = new VideoView(this);
44 | RelativeLayout.LayoutParams videoHolderParams = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
45 | videoHolder.setLayoutParams(videoHolderParams);
46 | videoHolderParams.addRule(RelativeLayout.CENTER_IN_PARENT);
47 |
48 | // Add child VideoView to parent RelativeLayout
49 | parentLayout.addView(videoHolder);
50 |
51 | // Set parent RelativeLayout to your screen
52 | setContentView(parentLayout, parentLayoutParam);
53 | getWindow().getDecorView().setBackgroundColor(Color.WHITE);
54 |
55 | Uri video = Uri.parse("android.resource://" + getPackageName() + "/" + R.raw.splash);
56 | videoHolder.setVideoURI(video);
57 |
58 | videoHolder.setOnCompletionListener(new MediaPlayer.OnCompletionListener()
59 | {
60 | public void onCompletion(MediaPlayer mp)
61 | {
62 | jump();
63 | }
64 | });
65 |
66 | videoHolder.start();
67 | }
68 | catch (Exception ex)
69 | {
70 | jump();
71 | }
72 | }
73 |
74 | @Override
75 | public boolean onTouchEvent(MotionEvent event)
76 | {
77 | jump();
78 | return true;
79 | }
80 |
81 | private void jump()
82 | {
83 | if (isFinishing())
84 | {
85 | return;
86 | }
87 |
88 | startActivity(new Intent(this, TailActivity.class));
89 | finish();
90 | }
91 | }
--------------------------------------------------------------------------------
/data/src/org/thetailcompany/digitail/TailActivity.java:
--------------------------------------------------------------------------------
1 | package org.thetailcompany.digitail;
2 |
3 | import java.lang.String;
4 | import android.os.Bundle;
5 | import android.util.Log;
6 | import android.content.Intent;
7 | import android.app.Activity;
8 |
9 | import org.qtproject.qt.android.bindings.QtActivity;
10 |
11 | public class TailActivity extends org.qtproject.qt.android.bindings.QtActivity
12 | {
13 | private static final String TAG = "TailActivity";
14 | // No longer needed as of Qt6 i guess - and looks like it interferes with bringing the app back up from sleep as well, so probably not great anyway
15 | // Leaving in place just for now, can be removed later if it does turn out to truly not to be a problem any longer
16 | // @Override
17 | // protected void onPause()
18 | // {
19 | // super.onPause();
20 | // Log.v(TAG, "onPause - entered");
21 | // }
22 | }
23 |
--------------------------------------------------------------------------------
/data/src/org/thetailcompany/digitail/TailService.java:
--------------------------------------------------------------------------------
1 | // java file goes in android/src/org/thetailcompany/digitail/TailService.java
2 | package org.thetailcompany.digitail;
3 |
4 | import android.content.Context;
5 | import android.content.Intent;
6 | import android.os.Build;
7 | import android.os.PowerManager;
8 | import android.os.PowerManager.WakeLock;
9 | import android.app.ActivityManager;
10 | import android.app.ActivityManager.RunningServiceInfo;
11 | import android.app.Notification;
12 | import android.app.NotificationManager;
13 | import android.app.NotificationChannel;
14 | import android.app.PendingIntent;
15 | import android.app.Service;
16 | import org.qtproject.qt.android.bindings.QtService;
17 |
18 | public class TailService extends QtService
19 | {
20 | private static WakeLock mWakeLock;
21 | private static final int ONGOING_NOTIFICATION_ID = 1;
22 | private static final String NOTIFICATION_CHANNEL_ID = "org.thetailcompany.digitail";
23 |
24 | private synchronized String createChannel() {
25 | NotificationManager mNotificationManager = (NotificationManager) this.getSystemService(Context.NOTIFICATION_SERVICE);
26 |
27 | String channelName = "TailService";
28 | int importance = NotificationManager.IMPORTANCE_LOW;
29 |
30 | NotificationChannel mChannel = new NotificationChannel(NOTIFICATION_CHANNEL_ID, channelName, importance);
31 |
32 | if (mNotificationManager != null) {
33 | mNotificationManager.createNotificationChannel(mChannel);
34 | } else {
35 | stopSelf();
36 | }
37 | return NOTIFICATION_CHANNEL_ID;
38 | }
39 |
40 | public void acquireWakeLock() {
41 | PowerManager powerManager = (PowerManager) getSystemService(POWER_SERVICE);
42 | mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "DIGITAiL::TailWakeLockTag");
43 | mWakeLock.acquire();
44 |
45 | // Start foregrounding the service and put up a notification saying we're alive
46 | Intent notificationIntent = new Intent(this, SplashActivity.class);
47 | // notificationIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
48 | PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
49 |
50 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
51 | String channel = createChannel();
52 | Notification notification = new Notification.Builder(this, channel)
53 | .setContentTitle(getText(R.string.notification_title))
54 | .setContentText(getText(R.string.notification_message))
55 | .setSmallIcon(R.drawable.ic_stat_notification_icon)
56 | .setContentIntent(pendingIntent)
57 | .setTicker(getText(R.string.ticker_text))
58 | .build();
59 | startForeground(ONGOING_NOTIFICATION_ID, notification);
60 | } else {
61 | Notification notification = new Notification.Builder(this)
62 | .setContentTitle(getText(R.string.notification_title))
63 | .setContentText(getText(R.string.notification_message))
64 | .setSmallIcon(R.drawable.ic_stat_notification_icon)
65 | .setContentIntent(pendingIntent)
66 | .setTicker(getText(R.string.ticker_text))
67 | .build();
68 | startForeground(ONGOING_NOTIFICATION_ID, notification);
69 | }
70 | }
71 |
72 | public void releaseWakeLock() {
73 | // Stop forcing foregrounding, and remove the notification
74 | stopForeground(true);
75 | if(mWakeLock != null) {
76 | mWakeLock.release();
77 | mWakeLock = null;
78 | }
79 | }
80 |
81 | private static boolean isMyServiceRunning(Class> serviceClass, Context ctx) {
82 | ActivityManager manager = (ActivityManager) ctx.getSystemService(Context.ACTIVITY_SERVICE);
83 | for (RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) {
84 | if (serviceClass.getName().equals(service.service.getClassName())) {
85 | return true;
86 | }
87 | }
88 | return false;
89 | }
90 | public static void startTailService(Context ctx) {
91 | if(!isMyServiceRunning(TailService.class, ctx)) {
92 | ctx.startService(new Intent(ctx, TailService.class));
93 | }
94 | }
95 | public static void stopTailService(Context ctx) {
96 | ctx.stopService(new Intent(ctx, TailService.class));
97 | }
98 |
99 | @Override
100 | public int onStartCommand(Intent intent, int flags, int startId) {
101 | super.onStartCommand(intent, flags, startId);
102 | return START_STICKY;
103 | }
104 |
105 | }
106 |
--------------------------------------------------------------------------------
/servo drawing 1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OpenTails/CRUMPET-Android/f540d6f35e62b208f8949cec2c3d21f10d94b60a/servo drawing 1.jpg
--------------------------------------------------------------------------------
/servo drawing 2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OpenTails/CRUMPET-Android/f540d6f35e62b208f8949cec2c3d21f10d94b60a/servo drawing 2.jpg
--------------------------------------------------------------------------------
/src/Alarm.cpp:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Ildar Gilmanov
3 | *
4 | * This program is free software; you can redistribute it and/or modify
5 | * it under the terms of the GNU Library General Public License as
6 | * published by the Free Software Foundation; either version 3, or
7 | * (at your option) any later version.
8 | *
9 | * This program is distributed in the hope that it will be useful,
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | * GNU Library General Public License for more details
13 | *
14 | * You should have received a copy of the GNU Library General Public License
15 | * along with this program; if not, see
16 | */
17 |
18 | #include "Alarm.h"
19 |
20 | #include
21 |
22 | class Alarm::Private
23 | {
24 | public:
25 | Private()
26 | {}
27 |
28 | Private(const QString& name)
29 | : name(name),
30 | time(QDateTime::currentDateTime())
31 | {}
32 |
33 | Private(const QString& name, const QDateTime& time, const QStringList& commands)
34 | : name(name),
35 | time(time),
36 | commands(commands)
37 | {
38 | if (!this->time.isValid()) {
39 | this->time = QDateTime::currentDateTime();
40 | }
41 | }
42 |
43 | QString name;
44 | QDateTime time;
45 |
46 | //TODO: It would be great to represent commands as objects, not strings
47 | QStringList commands;
48 | };
49 |
50 | Alarm::Alarm(QObject* parent)
51 | : QObject(parent),
52 | d(new Private())
53 | {
54 | }
55 |
56 | Alarm::Alarm(const QString& name, QObject* parent)
57 | : QObject(parent),
58 | d(new Private(name))
59 | {
60 | }
61 |
62 | Alarm::Alarm(const QString& name,
63 | const QDateTime& time,
64 | const QStringList& commands,
65 | QObject* parent)
66 | : QObject(parent),
67 | d(new Private(name, time, commands))
68 | {
69 | }
70 |
71 | Alarm::~Alarm()
72 | {
73 | delete d;
74 | }
75 |
76 | QString Alarm::name() const
77 | {
78 | return d->name;
79 | }
80 |
81 | void Alarm::setName(const QString& name)
82 | {
83 | if (d->name != name) {
84 | d->name = name;
85 | Q_EMIT nameChanged();
86 | Q_EMIT alarmChanged();
87 | }
88 | }
89 |
90 | QDateTime Alarm::time() const
91 | {
92 | return d->time;
93 | }
94 |
95 | void Alarm::setTime(const QDateTime& time)
96 | {
97 | if (d->time != time) {
98 | d->time = time;
99 | Q_EMIT timeChanged();
100 | Q_EMIT alarmChanged();
101 | }
102 | }
103 |
104 | QStringList Alarm::commands() const
105 | {
106 | return d->commands;
107 | }
108 |
109 | void Alarm::setCommands(const QStringList& commands)
110 | {
111 | d->commands = commands;
112 | Q_EMIT commandsChanged();
113 | Q_EMIT alarmChanged();
114 | }
115 |
116 | void Alarm::addCommand(int index, const QString& command)
117 | {
118 | d->commands.insert(index, command);
119 | Q_EMIT commandsChanged();
120 | Q_EMIT alarmChanged();
121 | }
122 |
123 | void Alarm::removeCommand(int index)
124 | {
125 | if (index < 0 || index >= d->commands.size()) {
126 | qWarning() << QString::fromUtf8("Unable to remvoe command from alarm '%1'. Index (%2) is out of the bounds.")
127 | .arg(name())
128 | .arg(index);
129 | return;
130 | }
131 |
132 | d->commands.removeAt(index);
133 | Q_EMIT commandsChanged();
134 | Q_EMIT alarmChanged();
135 | }
136 |
137 |
138 | QVariantMap Alarm::toVariantMap() const
139 | {
140 | QVariantMap result;
141 |
142 | result[QLatin1String{"name"}] = name();
143 | result[QLatin1String{"time"}] = time();
144 | result[QLatin1String{"commands"}] = commands();
145 |
146 | return result;
147 | }
148 |
--------------------------------------------------------------------------------
/src/Alarm.h:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Ildar Gilmanov
3 | *
4 | * This program is free software; you can redistribute it and/or modify
5 | * it under the terms of the GNU Library General Public License as
6 | * published by the Free Software Foundation; either version 3, or
7 | * (at your option) any later version.
8 | *
9 | * This program is distributed in the hope that it will be useful,
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | * GNU Library General Public License for more details
13 | *
14 | * You should have received a copy of the GNU Library General Public License
15 | * along with this program; if not, see
16 | */
17 |
18 | #ifndef ALARM_H
19 | #define ALARM_H
20 |
21 | #include
22 | #include
23 |
24 | class AlarmList;
25 |
26 | /**
27 | * @brief The Alarm class represents alarm moves.
28 | * See also AlarmList class.
29 | */
30 | class Alarm : public QObject
31 | {
32 | Q_OBJECT
33 |
34 | //TODO: It seems we do not need Q_PROPERTIES here because in qml we use QVariantList and QVariantMap
35 | Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged)
36 | Q_PROPERTY(QDateTime time READ time WRITE setTime NOTIFY timeChanged)
37 | Q_PROPERTY(QStringList commands READ commands WRITE setCommands NOTIFY commandsChanged)
38 |
39 | public:
40 | explicit Alarm(QObject *parent = nullptr);
41 | explicit Alarm(const QString& name, QObject* parent = nullptr);
42 |
43 | explicit Alarm(const QString& name,
44 | const QDateTime& time,
45 | const QStringList& commands,
46 | QObject* parent);
47 | ~Alarm() override;
48 |
49 | QString name() const;
50 |
51 | QDateTime time() const;
52 | void setTime(const QDateTime& time);
53 |
54 | QStringList commands() const;
55 | void setCommands(const QStringList& commands);
56 |
57 | void addCommand(int index, const QString& command);
58 | void removeCommand(int index);
59 |
60 | QVariantMap toVariantMap() const;
61 |
62 | public Q_SLOTS:
63 |
64 | Q_SIGNALS:
65 | void nameChanged();
66 | void timeChanged();
67 | void commandsChanged();
68 |
69 | void alarmChanged();
70 |
71 | private:
72 | //TODO: There is no any reason to use PIMPL idiom at these classes
73 | class Private;
74 | Private* d;
75 |
76 | void setName(const QString& name);
77 |
78 | /// We can change name only in AlarmList class
79 | friend class AlarmList;
80 | };
81 |
82 | #endif // ALARM_H
83 |
--------------------------------------------------------------------------------
/src/AlarmList.h:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Ildar Gilmanov
3 | *
4 | * This program is free software; you can redistribute it and/or modify
5 | * it under the terms of the GNU Library General Public License as
6 | * published by the Free Software Foundation; either version 3, or
7 | * (at your option) any later version.
8 | *
9 | * This program is distributed in the hope that it will be useful,
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | * GNU Library General Public License for more details
13 | *
14 | * You should have received a copy of the GNU Library General Public License
15 | * along with this program; if not, see
16 | */
17 |
18 | #ifndef ALARMLIST_H
19 | #define ALARMLIST_H
20 |
21 | #include
22 |
23 | class Alarm;
24 | class CommandQueue;
25 |
26 | /**
27 | * @brief The AlarmList class represents collection of all alarm moves.
28 | * See also Alarm class.
29 | *
30 | * Please note that now alarm names should be unique,
31 | * if we try to rename an alarm or add new alarm with the existed name
32 | * alarmExisted() signal will be emitted.
33 | *
34 | * Now we use the AlarmList class via AppSettings,
35 | * but later we may want to use it as a separated model via QAbstractItemModelReplica.
36 | */
37 | class AlarmList : public QAbstractListModel
38 | {
39 | Q_OBJECT
40 |
41 | //TODO: It seems we do not need Q_PROPERTIES here because in qml we use QVariantList and QVariantMap
42 | Q_PROPERTY(int size READ size NOTIFY listChanged)
43 |
44 | public:
45 | enum Roles {
46 | AlarmRole = Qt::UserRole + 1,
47 | };
48 |
49 | explicit AlarmList(QObject *parent = nullptr);
50 | ~AlarmList() override;
51 |
52 | int size() const;
53 | Alarm* at(int index) const;
54 |
55 | QHash roleNames() const override;
56 | int rowCount(const QModelIndex& parent = QModelIndex()) const override;
57 | QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
58 |
59 | bool exists(const QString& name) const;
60 | Alarm* alarm(const QString& name) const;
61 | int alarmIndex(const QString& name) const;
62 |
63 | void addAlarm(const QString& name, const QDateTime& time, const QStringList& commands);
64 |
65 | /**
66 | * Add a new alarm to the model.
67 | * The new alarm is added at the start of the unsorted model
68 | * The model takes ownership of the alarm, and deletion should not
69 | * be done manually.
70 | * @param alarm The new alarm
71 | */
72 | void addAlarm(Alarm* alarm);
73 | void addAlarm(const QString& alarmName);
74 |
75 | void removeAlarm(Alarm* alarm);
76 | void removeAlarm(const QString& alarmName);
77 | void removeAlarmByIndex(int index);
78 |
79 | void changeAlarmName(const QString& oldName, const QString& newName);
80 | void setAlarmTime(const QString& alarmName, const QDateTime& time);
81 | void setAlarmCommands(const QString& alarmName, const QStringList& commands);
82 | void addAlarmCommand(const QString& alarmName, int index, const QString& command, QStringList devices);
83 | void removeAlarmCommand(const QString& alarmName, int index);
84 |
85 | QVariantList toVariantList() const;
86 | QVariantMap getAlarmVariantMap(const QString& alarmName);
87 |
88 | void setCommandQueue(CommandQueue* commandQueue);
89 | public Q_SLOTS:
90 |
91 | Q_SIGNALS:
92 | void listChanged();
93 |
94 | /// If alarm with the same name exists we emit alarmExisted() signal
95 | void alarmExisted(const QString& name);
96 |
97 | /// If we can not find an alarm with the name we emit alarmNotExisted() signal
98 | void alarmNotExisted(const QString& name);
99 |
100 | private:
101 | //TODO: There is no any reason to use PIMPL idiom at these classes
102 | class Private;
103 | Private* d;
104 | };
105 |
106 | #endif // ALARMLIST_H
107 |
--------------------------------------------------------------------------------
/src/AppSettingsProxy.rep:
--------------------------------------------------------------------------------
1 | // Copyright 2019 Dan Leinir Turthra Jensen
2 | //
3 | // This program is free software; you can redistribute it and/or modify
4 | // it under the terms of the GNU Library General Public License as
5 | // published by the Free Software Foundation; either version 3, or
6 | // (at your option) any later version.
7 | //
8 | // This program is distributed in the hope that it will be useful,
9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | // GNU Library General Public License for more details
12 | //
13 | // You should have received a copy of the GNU Library General Public License
14 | // along with this program; if not, see
15 |
16 | #include
17 |
18 | class AppSettingsProxy {
19 | PROP(int androidApiLevel READONLY)
20 | PROP(bool advancedMode READWRITE)
21 | PROP(bool developerMode READWRITE)
22 | PROP(bool idleMode READWRITE)
23 | PROP(bool autoReconnect READWRITE)
24 | PROP(bool alwaysSendToAll READWRITE)
25 | PROP(QStringList idleCategories)
26 | SLOT(void addIdleCategory(const QString& category))
27 | SLOT(void removeIdleCategory(const QString& category))
28 | PROP(int idleMinPause READWRITE)
29 | PROP(int idleMaxPause READWRITE)
30 | PROP(bool fakeTailMode READWRITE)
31 |
32 | PROP(QStringList availableLanguages READONLY)
33 | PROP(QString languageOverride READWRITE)
34 |
35 | PROP(QStringList moveLists READONLY)
36 | SLOT(void addMoveList(const QString& moveListName))
37 | SLOT(void removeMoveList(const QString& moveListName))
38 | PROP(QStringList moveList READONLY)
39 | SLOT(void setActiveMoveList(const QString& moveListName))
40 | SLOT(void addMoveListEntry(int index, const QString& entry, QStringList devices))
41 | SLOT(void removeMoveListEntry(int index))
42 |
43 | PROP(QVariantList alarmList READONLY)
44 | SLOT(void addAlarm(const QString& alarmName))
45 | SLOT(void removeAlarm(const QString& alarmName))
46 | PROP(QVariantMap activeAlarm READONLY)
47 | SLOT(void setActiveAlarmName(const QString& alarmName))
48 | SLOT(void changeAlarmName(const QString& newName))
49 | SLOT(void setAlarmTime(const QDateTime& time))
50 | SLOT(void setAlarmCommands(const QStringList& commands))
51 | SLOT(void addAlarmCommand(int index, const QString& command, QStringList devices))
52 | SLOT(void removeAlarmCommand(int index))
53 | SIGNAL(alarmExisted(const QString& name))
54 | SIGNAL(alarmNotExisted(const QString& name))
55 | SIGNAL(idleModeTimeout())
56 |
57 | PROP(QVariantMap commandFiles READONLY)
58 | SLOT(void addCommandFile(const QString& filename, const QString& content))
59 | SLOT(void removeCommandFile(const QString& filename))
60 | SLOT(void setCommandFileContents(const QString& filename, const QString& content))
61 | SLOT(void renameCommandFile(const QString& filename, const QString& newFilename))
62 |
63 | SLOT(void shutDownService())
64 | };
65 |
--------------------------------------------------------------------------------
/src/BTConnectionManagerProxy.rep:
--------------------------------------------------------------------------------
1 | // Copyright 2019 Dan Leinir Turthra Jensen
2 | //
3 | // This program is free software; you can redistribute it and/or modify
4 | // it under the terms of the GNU Library General Public License as
5 | // published by the Free Software Foundation; either version 3, or
6 | // (at your option) any later version.
7 | //
8 | // This program is distributed in the hope that it will be useful,
9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | // GNU Library General Public License for more details
12 | //
13 | // You should have received a copy of the GNU Library General Public License
14 | // along with this program; if not, see
15 |
16 | class BTConnectionManagerProxy {
17 | PROP(bool isConnected READONLY)
18 | PROP(bool discoveryRunning READONLY)
19 | PROP(int deviceCount READONLY)
20 | PROP(int commandQueueCount READONLY)
21 | PROP(int bluetoothState READONLY)
22 |
23 | SLOT(void runCommand(const QString& command))
24 | SLOT(void startDiscovery())
25 | SLOT(void stopDiscovery())
26 | SLOT(void sendMessage(const QString& message, const QStringList& deviceIDs))
27 | SLOT(void connectToDevice(const QString& deviceID))
28 | SLOT(void disconnectDevice(const QString& deviceID))
29 | SLOT(void setDeviceName(const QString& deviceID, const QString& deviceName))
30 | SLOT(void setDeviceChecked(const QString& deviceID, bool checked))
31 | SLOT(void setDeviceListeningState(const QString& deviceID, int listeningMode))
32 | SLOT(void setDeviceTiltState(const QString& deviceID, bool tiltState))
33 |
34 | // Use this to call a function on a specific device without digging too deeply (for example, call the checkOTA function)
35 | SLOT(void callDeviceFunction(const QString& deviceID, const QString& functionName))
36 | SLOT(void callDeviceFunctionWithParameter(const QString& deviceID, const QString& functionName, const QVariant ¶meter))
37 | // Use this to set a property on a specific device without digging too deeply (changes get tracked through the device model)
38 | SLOT(void setDeviceProperty(const QString& deviceID, const QString& property, const QVariant& value))
39 |
40 | // Use this to disconnect from and forget everything about a specific piece of gear
41 | SLOT(void forgetGear(const QString& deviceID))
42 |
43 | // Designed to fill up a map with information, based on the command found in the QVariantMap key "command"'s value
44 | PROP(QVariantMap command)
45 | SLOT(QVariantMap getCommand(const QString& command))
46 |
47 | SLOT(void setDeviceCommandsFileEnabled(const QString& deviceID, const QString& filename, bool enabled))
48 | SLOT(void setDeviceGestureEventCommand(const QString& deviceID, const int &gestureEvent, const QStringList &targetDeviceIDs, const QString &command))
49 |
50 | SIGNAL(messageReceived(const QString &sender, const QString &message))
51 | SIGNAL(message(const QString& message))
52 | SIGNAL(blockingMessage(const QString& title, const QString& message))
53 | SIGNAL(deviceConnected(const QString& deviceID))
54 | };
55 |
--------------------------------------------------------------------------------
/src/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | set(CMAKE_AUTORCC ON)
2 | include_directories(${CMAKE_BINARY_DIR}/src)
3 |
4 | # Make sure we can register the types from the static library
5 | add_definitions(-DKIRIGAMI_BUILD_TYPE_STATIC)
6 |
7 | add_executable(digitail)
8 |
9 | target_sources(digitail
10 | PRIVATE
11 | main.cpp
12 | BTConnectionManager.cpp
13 | GearCommandModel.cpp
14 | GearBase.cpp
15 | CommandInfo.cpp
16 | CommandModel.cpp
17 | CommandPersistence.cpp
18 | CommandQueue.cpp
19 | DeviceModel.cpp
20 | FilterProxyModel.cpp
21 | GestureController.cpp
22 | GestureDetectorModel.cpp
23 | GestureSensor.cpp
24 | IdleMode.cpp
25 | AppSettings.cpp
26 | Utilities.cpp
27 | Alarm.cpp
28 | AlarmList.cpp
29 | PermissionsManager.cpp
30 | WalkingSensorGestureReconizer.cpp
31 |
32 | gearimplementations/GearEars.cpp
33 | gearimplementations/GearFake.cpp
34 | gearimplementations/GearFlutterWings.cpp
35 | gearimplementations/GearMitail.cpp
36 | gearimplementations/GearMitailMini.cpp
37 | gearimplementations/GearDigitail.cpp
38 |
39 | kirigami-icons.qrc
40 | resources.qrc
41 | )
42 |
43 | qt_add_repc_sources(digitail
44 | AppSettingsProxy.rep
45 | BTConnectionManagerProxy.rep
46 | CommandQueueProxy.rep
47 | GestureControllerProxy.rep
48 | )
49 | qt_add_repc_replicas(digitail
50 | AppSettingsProxy.rep
51 | BTConnectionManagerProxy.rep
52 | CommandQueueProxy.rep
53 | GestureControllerProxy.rep
54 | )
55 |
56 | if (CMAKE_SYSTEM_NAME STREQUAL "Android")
57 | set(digitail_EXTRA_LIBS
58 | OpenSSL::SSL
59 | Qt6::CorePrivate # for QAndroidService
60 | Qt6::Gui
61 | #FIXME: we shouldn't have to link to these but otherwise their libs won't be packaged on Android
62 | Qt6::QuickControls2
63 | Qt6::Svg
64 | KF6::Kirigami
65 | )
66 | # else ()
67 | #qstyle-based qqc2 style needs a QApplication
68 | # set(digitail_EXTRA_LIBS )
69 | endif()
70 |
71 | target_link_libraries(digitail
72 | Qt6::Core
73 | Qt6::RemoteObjects
74 | Qt6::Widgets
75 | Qt6::Qml
76 | Qt6::Quick
77 | Qt6::QuickControls2
78 | Qt6::Bluetooth
79 | Qt6::Sensors
80 | KF6::I18n
81 | ${digitail_EXTRA_LIBS}
82 | )
83 |
84 | # Translations
85 | #add_custom_target(convert_translations_for_embedding
86 | #${CMAKE_CURRENT_SOURCE_DIR}/convert_translations.sh ${CMAKE_BINARY_DIR}
87 | #WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
88 | #)
89 | #add_dependencies(digitail convert_translations_for_embedding)
90 |
91 | install(TARGETS digitail ${INSTALL_TARGETS_DEFAULT_ARGS})
92 | if (ANDROID)
93 | ecm_add_android_apk(digitail ANDROID_DIR ${CMAKE_SOURCE_DIR}/data)
94 | endif()
95 |
96 | ki18n_install(po)
97 |
98 | # kirigami_package_breeze_icons(ICONS application-menu document-decrypt folder-sync go-next go-previous go-up handle-left handle-right view-list-icons applications-graphics media-record-symbolic)
99 |
--------------------------------------------------------------------------------
/src/CommandInfo.cpp:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Dan Leinir Turthra Jensen
3 | *
4 | * This program is free software; you can redistribute it and/or modify
5 | * it under the terms of the GNU Library General Public License as
6 | * published by the Free Software Foundation; either version 3, or
7 | * (at your option) any later version.
8 | *
9 | * This program is distributed in the hope that it will be useful,
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | * GNU Library General Public License for more details
13 | *
14 | * You should have received a copy of the GNU Library General Public License
15 | * along with this program; if not, see
16 | */
17 |
18 | #include "CommandInfo.h"
19 |
20 | CommandInfo::CommandInfo()
21 | { }
22 |
23 | CommandInfo::CommandInfo(const CommandInfo& other)
24 | : name(other.name)
25 | , command(other.command)
26 | , category(other.category)
27 | , duration(other.duration)
28 | , minimumCooldown(other.minimumCooldown)
29 | , group(other.group)
30 | , isRunning(other.isRunning)
31 | , isAvailable(other.isAvailable)
32 | { }
33 |
34 | CommandInfo::CommandInfo(CommandInfo && other) noexcept
35 | : name(std::move(other.name))
36 | , command(std::move(other.command))
37 | , category(std::move(other.category))
38 | , duration(std::move(other.duration))
39 | , minimumCooldown(std::move(other.minimumCooldown))
40 | , group(std::move(other.group))
41 | , isRunning(std::move(other.isRunning))
42 | , isAvailable(std::move(other.isAvailable))
43 | {
44 | }
45 |
46 | CommandInfo& CommandInfo::operator=(CommandInfo && other) noexcept
47 | {
48 | name = std::move(other.name);
49 | command = std::move(other.command);
50 | category = std::move(other.category);
51 | duration = std::move(other.duration);
52 | minimumCooldown = std::move(other.minimumCooldown);
53 | group = std::move(other.group);
54 | isRunning = std::move(other.isRunning);
55 | isAvailable = std::move(other.isAvailable);
56 | return *this;
57 | }
58 |
59 | CommandInfo& CommandInfo::operator=(const CommandInfo& other)
60 | {
61 | name = other.name;
62 | command = other.command;
63 | category = other.category;
64 | duration = other.duration;
65 | minimumCooldown = other.minimumCooldown;
66 | group = other.group;
67 | isRunning = other.isRunning;
68 | isAvailable = other.isAvailable;
69 | return *this;
70 | }
71 |
72 | CommandInfo::~CommandInfo()
73 | { }
74 |
75 | void CommandInfo::clear()
76 | {
77 | name.clear();
78 | command.clear();
79 | category.clear();
80 | duration = 0;
81 | minimumCooldown = 0;
82 | group = 0;
83 | isRunning = false;
84 | isAvailable = true;
85 | }
86 |
87 | bool CommandInfo::compare(const CommandInfo& other) const
88 | {
89 | // Not comparing isRunning and isAvailable, as that isn't necessarily quite as true...
90 | return (
91 | name == other.name &&
92 | command == other.command &&
93 | category == other.category &&
94 | duration == other.duration &&
95 | minimumCooldown == other.minimumCooldown &&
96 | group == other.group
97 | );
98 | }
99 |
100 | bool CommandInfo::equivalent(const CommandInfo &other) const
101 | {
102 | // Only checking the name, command, category, and group, as duration and cooldown is different per-device. Used for grouping purposes
103 | return (
104 | name == other.name &&
105 | command == other.command &&
106 | category == other.category &&
107 | group == other.group
108 | );
109 | }
110 |
111 | bool CommandInfo::isValid() const
112 | {
113 | if (name.isEmpty() || command.isEmpty() || category.isEmpty() || duration < 1) {
114 | return false;
115 | }
116 | return true;
117 | }
118 |
--------------------------------------------------------------------------------
/src/CommandInfo.h:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Dan Leinir Turthra Jensen
3 | *
4 | * This program is free software; you can redistribute it and/or modify
5 | * it under the terms of the GNU Library General Public License as
6 | * published by the Free Software Foundation; either version 3, or
7 | * (at your option) any later version.
8 | *
9 | * This program is distributed in the hope that it will be useful,
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | * GNU Library General Public License for more details
13 | *
14 | * You should have received a copy of the GNU Library General Public License
15 | * along with this program; if not, see
16 | */
17 |
18 | #ifndef COMMANDINFO_H
19 | #define COMMANDINFO_H
20 |
21 | #include
22 |
23 | class CommandInfo {
24 | public:
25 | CommandInfo();
26 | CommandInfo(const CommandInfo& other);
27 | CommandInfo(CommandInfo&& other) noexcept;
28 | CommandInfo& operator=(const CommandInfo& other);
29 | CommandInfo& operator=(CommandInfo&& other) noexcept;
30 | ~CommandInfo();
31 |
32 | QString name;
33 | QString command;
34 | QString category;
35 | int duration{0}; // milliseconds
36 | int minimumCooldown{0}; // milliseconds
37 | int group{0}; // A super-category grouping (no two commands should both be available, if they have the same group and belong to the same device)
38 |
39 | bool isRunning{false};
40 | bool isAvailable{true};
41 |
42 | void clear();
43 | bool compare(const CommandInfo& other) const;
44 | bool equivalent(const CommandInfo& other) const;
45 | /**
46 | * Returns true if name, command, category, and duration are all set, otherwise false
47 | */
48 | bool isValid() const;
49 | };
50 | typedef QList CommandInfoList;
51 |
52 | #endif//COMMANDINFO_H
53 |
--------------------------------------------------------------------------------
/src/CommandModel.h:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Dan Leinir Turthra Jensen
3 | *
4 | * This program is free software; you can redistribute it and/or modify
5 | * it under the terms of the GNU Library General Public License as
6 | * published by the Free Software Foundation; either version 3, or
7 | * (at your option) any later version.
8 | *
9 | * This program is distributed in the hope that it will be useful,
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | * GNU Library General Public License for more details
13 | *
14 | * You should have received a copy of the GNU Library General Public License
15 | * along with this program; if not, see
16 | */
17 |
18 | #ifndef COMMANDMODEL_H
19 | #define COMMANDMODEL_H
20 |
21 | #include
22 | #include "GearCommandModel.h"
23 |
24 | class DeviceModel;
25 | /**
26 | * An agregation model, which agregates all the command models for all connected
27 | * devices and presents all the available commands only once (duplicates are allowed
28 | * if they are in different categories).
29 | */
30 | class CommandModel : public QAbstractListModel
31 | {
32 | Q_OBJECT
33 | public:
34 | explicit CommandModel (QObject* parent = nullptr);
35 | ~CommandModel() override;
36 |
37 | enum Roles {
38 | Name = Qt::UserRole + 1,
39 | Command,
40 | IsRunning,
41 | Category,
42 | Duration,
43 | MinimumCooldown,
44 | CommandIndex,
45 | DeviceIDs,
46 | IsAvailable
47 | };
48 | Q_ENUM(Roles)
49 |
50 | QHash< int, QByteArray > roleNames() const override;
51 | QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
52 | int rowCount(const QModelIndex& parent = QModelIndex()) const override;
53 |
54 | /**
55 | * Set the DeviceModel which contains the devies this model should present commands from
56 | */
57 | void setDeviceModel(DeviceModel * deviceModel);
58 |
59 | /**
60 | * Get the command at a specified index
61 | *
62 | * @param index The index of the command to fetch
63 | * @return A command if the index was valid, or null if not
64 | */
65 | Q_INVOKABLE CommandInfo getCommand(int index) const;
66 | /**
67 | * Get the command with the specified actual command
68 | *
69 | * @param command The command to fetch information for
70 | * @return The command info instance for the specified command, or null if none was found
71 | */
72 | Q_INVOKABLE CommandInfo getCommand(QString command) const;
73 | /**
74 | * Get a random command, picked from the currently available commands, limited
75 | * to commands with the category listed in includedCategories. If the list is
76 | * empty, any command will be listed.
77 | *
78 | * @param includedCategories A list of strings matching the categories
79 | * @return A random command matching one of the requested categories
80 | */
81 | Q_INVOKABLE CommandInfo getRandomCommand(QStringList includedCategories) const;
82 | private:
83 | class Private;
84 | Private* d;
85 | };
86 |
87 | #endif//COMMANDMODEL_H
88 |
--------------------------------------------------------------------------------
/src/CommandQueueProxy.rep:
--------------------------------------------------------------------------------
1 | // Copyright 2019 Dan Leinir Turthra Jensen
2 | //
3 | // This program is free software; you can redistribute it and/or modify
4 | // it under the terms of the GNU Library General Public License as
5 | // published by the Free Software Foundation; either version 3, or
6 | // (at your option) any later version.
7 | //
8 | // This program is distributed in the hope that it will be useful,
9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | // GNU Library General Public License for more details
12 | //
13 | // You should have received a copy of the GNU Library General Public License
14 | // along with this program; if not, see
15 |
16 | class CommandQueueProxy {
17 | PROP(int currentCommandRemainingMSeconds READONLY)
18 | PROP(int currentCommandTotalDuration READONLY)
19 | PROP(int count READONLY)
20 | SLOT(void clear(const QString& deviceID))
21 | SLOT(void pushPause(int durationMilliseconds, QStringList deviceIDs))
22 | SLOT(void pushCommand(QString tailCommand, QStringList deviceIDs))
23 | SLOT(void pushCommands(QStringList commands, QStringList deviceIDs))
24 | SLOT(void removeEntry(int index))
25 | SLOT(void swapEntries(int swapThis, int withThis))
26 | SLOT(void moveEntryUp(int index))
27 | SLOT(void moveEntryDown(int index))
28 | };
29 |
--------------------------------------------------------------------------------
/src/FilterProxyModel.cpp:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Dan Leinir Turthra Jensen
3 | *
4 | * This program is free software; you can redistribute it and/or modify
5 | * it under the terms of the GNU Library General Public License as
6 | * published by the Free Software Foundation; either version 3, or
7 | * (at your option) any later version.
8 | *
9 | * This program is distributed in the hope that it will be useful,
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | * GNU Library General Public License for more details
13 | *
14 | * You should have received a copy of the GNU Library General Public License
15 | * along with this program; if not, see
16 | */
17 |
18 | #include "FilterProxyModel.h"
19 |
20 | #include
21 |
22 | class FilterProxyModel::Private {
23 | public:
24 | Private() {
25 | updateTimer.setInterval(1);
26 | updateTimer.setSingleShot(true);
27 | }
28 | bool filterBoolean{false};
29 | QTimer updateTimer;
30 | };
31 |
32 | FilterProxyModel::FilterProxyModel(QObject *parent)
33 | : QSortFilterProxyModel(parent)
34 | , d(new Private)
35 | {
36 | connect(&d->updateTimer, &QTimer::timeout, this, [this](){ QTimer::singleShot(1, this, [this](){ Q_EMIT countChanged(); }); } );
37 | connect(this, &QAbstractItemModel::rowsInserted, this, [this](){ d->updateTimer.start(); });
38 | connect(this, &QAbstractItemModel::rowsRemoved, this, [this](){ d->updateTimer.start(); });
39 | connect(this, &QAbstractItemModel::dataChanged, this, [this](){ d->updateTimer.start(); });
40 | connect(this, &QAbstractItemModel::modelReset, this, [this](){ d->updateTimer.start(); });
41 | connect(this, &QSortFilterProxyModel::sourceModelChanged, this, [this](){ d->updateTimer.start(); });
42 | }
43 |
44 | FilterProxyModel::~FilterProxyModel()
45 | {
46 | delete d;
47 | }
48 |
49 | void FilterProxyModel::setFilterString(const QString &string)
50 | {
51 | this->setFilterFixedString(string);
52 | this->setFilterCaseSensitivity(Qt::CaseInsensitive);
53 | Q_EMIT filterStringChanged();
54 | }
55 |
56 | QString FilterProxyModel::filterString() const
57 | {
58 | return filterRegularExpression().pattern();
59 | }
60 |
61 | void FilterProxyModel::setFilterBoolean(const bool& value)
62 | {
63 | d->filterBoolean = value;
64 | Q_EMIT filterBooleanChanged();
65 | }
66 |
67 | bool FilterProxyModel::filterBoolean() const
68 | {
69 | return d->filterBoolean;
70 | }
71 |
72 | bool FilterProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
73 | {
74 | QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent);
75 | if (d->filterBoolean) {
76 | return sourceModel()->data(index, filterRole()).toBool();
77 | } else {
78 | return sourceModel()->data(index, filterRole()).toString().contains(filterRegularExpression());
79 | }
80 | }
81 |
82 | int FilterProxyModel::count() const
83 | {
84 | return rowCount();
85 | }
86 |
87 | int FilterProxyModel::sourceIndex(const int& filterIndex)
88 | {
89 | int mappedIndex{-1};
90 | QModelIndex ourIndex = index(filterIndex, 0);
91 | if (ourIndex.isValid() && sourceModel()) {
92 | mappedIndex = mapToSource(ourIndex).row();
93 | }
94 | return mappedIndex;
95 | }
96 |
--------------------------------------------------------------------------------
/src/FilterProxyModel.h:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Dan Leinir Turthra Jensen
3 | *
4 | * This program is free software; you can redistribute it and/or modify
5 | * it under the terms of the GNU Library General Public License as
6 | * published by the Free Software Foundation; either version 3, or
7 | * (at your option) any later version.
8 | *
9 | * This program is distributed in the hope that it will be useful,
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | * GNU Library General Public License for more details
13 | *
14 | * You should have received a copy of the GNU Library General Public License
15 | * along with this program; if not, see
16 | */
17 |
18 | #ifndef FILTERPROXYMODEL
19 | #define FILTERPROXYMODEL
20 |
21 | #include
22 |
23 | class FilterProxyModel : public QSortFilterProxyModel
24 | {
25 | Q_OBJECT
26 | Q_PROPERTY(QString filterString READ filterString WRITE setFilterString NOTIFY filterStringChanged)
27 | Q_PROPERTY(bool filterBoolean READ filterBoolean WRITE setFilterBoolean NOTIFY filterBooleanChanged)
28 | Q_PROPERTY(int count READ count NOTIFY countChanged)
29 | public:
30 | explicit FilterProxyModel(QObject* parent = nullptr);
31 | ~FilterProxyModel() override;
32 |
33 | void setFilterString(const QString &string);
34 | QString filterString() const;
35 | Q_SIGNAL void filterStringChanged();
36 |
37 | void setFilterBoolean(const bool &value);
38 | bool filterBoolean() const;
39 | Q_SIGNAL void filterBooleanChanged();
40 |
41 | bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override;
42 |
43 | int count() const;
44 | Q_SIGNAL void countChanged();
45 |
46 | Q_INVOKABLE int sourceIndex( const int& filterIndex );
47 | private:
48 | class Private;
49 | Private* d;
50 | };
51 |
52 | #endif//FILTERPROXYMODEL
53 |
--------------------------------------------------------------------------------
/src/GearCommandModel.h:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2018 Dan Leinir Turthra Jensen
3 | *
4 | * This program is free software; you can redistribute it and/or modify
5 | * it under the terms of the GNU Library General Public License as
6 | * published by the Free Software Foundation; either version 3, or
7 | * (at your option) any later version.
8 | *
9 | * This program is distributed in the hope that it will be useful,
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | * GNU Library General Public License for more details
13 | *
14 | * You should have received a copy of the GNU Library General Public License
15 | * along with this program; if not, see
16 | */
17 |
18 | #ifndef TAILCOMMANDMODEL_H
19 | #define TAILCOMMANDMODEL_H
20 |
21 | #include
22 |
23 | #include "CommandInfo.h"
24 |
25 | class GearCommandModel : public QAbstractListModel
26 | {
27 | Q_OBJECT
28 | public:
29 | explicit GearCommandModel(QObject* parent = nullptr);
30 | ~GearCommandModel() override;
31 |
32 | enum Roles {
33 | Name = Qt::UserRole + 1,
34 | Command,
35 | IsRunning,
36 | Category,
37 | Duration,
38 | MinimumCooldown,
39 | CommandIndex,
40 | IsAvailable
41 | };
42 |
43 | QHash< int, QByteArray > roleNames() const override;
44 | QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
45 | int rowCount(const QModelIndex& parent = QModelIndex()) const override;
46 |
47 | /**
48 | * Clear the model of all known commands
49 | */
50 | void clear();
51 |
52 | /**
53 | * Add a new command to the model.
54 | * The new command is added at the start of the unsorted model
55 | * The model takes ownership of the command, and deletion should not
56 | * be done manually.
57 | * @param command The new command to show in the model
58 | */
59 | void addCommand(const CommandInfo& command);
60 | Q_SIGNAL void commandAdded(const CommandInfo& command);
61 | /**
62 | * Remove a command from the model.
63 | * The entry will be deleted by this function, and you should not attempt to
64 | * use the instance afterwards.
65 | * If the command is not maintained by this model, it will still be deleted!
66 | */
67 | void removeCommand(const CommandInfo& command);
68 | Q_SIGNAL void commandRemoved(const CommandInfo& command);
69 |
70 | /**
71 | * Automatically fill the model with known commands for the specified version
72 | * of digitail.
73 | * @param version The version for the tail we've been connected to
74 | */
75 | void autofill(const QString& version);
76 | void setRunning(const QString& command, bool isRunning);
77 |
78 | /**
79 | * Get all the commands in this model
80 | *
81 | * @return A list of all commands currently managed by this model
82 | */
83 | const CommandInfoList& allCommands() const;
84 |
85 | /**
86 | * Whether the equivalent command to cmd in this model is marked as running
87 | *
88 | * This might seem odd, but a CommandInfo is equivalent whether or not it is
89 | * marked as running, so this is a convenient (and cheaper) way to inspect a
90 | * model's commands, without having to pass the list of commands around.
91 | *
92 | * @param cmd The command to check in this model
93 | * @return Whether the equivalent command in this model is marked as running
94 | */
95 | bool isRunning(const CommandInfo& cmd) const;
96 |
97 | /**
98 | * Whether the equivalent command to cmd in this model is marked as being available
99 | *
100 | * This is similar logic to isRunning, but also takes into account the command's
101 | * group (only one command will be available in each group).
102 | *
103 | * @note To ensure a specific command is always available, ensure that it has a unique group
104 | *
105 | * @param cmd The command to check in this model
106 | * @return Whether the equivalent command in this model is marked as being available
107 | * @see bool isRunning(const CommandInfo& cmd) const
108 | */
109 | bool isAvailable(const CommandInfo& cmd) const;
110 | private:
111 | class Private;
112 | Private* d;
113 | };
114 |
115 | #endif//TAILCOMMANDMODEL_H
116 |
--------------------------------------------------------------------------------
/src/GestureController.cpp:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2021 Dan Leinir Turthra Jensen
3 | *
4 | * This program is free software; you can redistribute it and/or modify
5 | * it under the terms of the GNU Library General Public License as
6 | * published by the Free Software Foundation; either version 3, or
7 | * (at your option) any later version.
8 | *
9 | * This program is distributed in the hope that it will be useful,
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | * GNU Library General Public License for more details
13 | *
14 | * You should have received a copy of the GNU Library General Public License
15 | * along with this program; if not, see
16 | */
17 |
18 | #include "GestureController.h"
19 | #include "BTConnectionManager.h"
20 | #include "CommandModel.h"
21 | #include "DeviceModel.h"
22 | #include "GearBase.h"
23 | #include "GestureDetectorModel.h"
24 | #include "GestureSensor.h"
25 | #include "WalkingSensorGestureReconizer.h"
26 |
27 | // #include
28 | #include
29 | #include
30 | #include
31 |
32 | class GestureController::Private {
33 | public:
34 | Private(GestureController* qq)
35 | : q(qq)
36 | , connectionManager(nullptr)
37 | {
38 | model = new GestureDetectorModel(qq);
39 |
40 | QList gestureSensors;
41 | gestureSensors << new WalkingSensor(q);
42 |
43 | for (GestureSensor *sensor : gestureSensors) {
44 | QObject::connect(sensor, &GestureSensor::detected, qq, &GestureController::gestureDetected);
45 | for (const QString& gestureName : sensor->recognizerSignals()) {
46 | GestureDetails* gesture = new GestureDetails(gestureName, sensor, q);
47 | model->addGesture(gesture);
48 | }
49 | }
50 | }
51 | ~Private() { }
52 | GestureController* q{nullptr};
53 | GestureDetectorModel* model{nullptr};
54 | BTConnectionManager* connectionManager{nullptr};
55 |
56 | void gestureDetected(const QString& gestureId) {
57 | qDebug() << gestureId << "detected";
58 | GestureDetails* gesture = model->gesture(gestureId);
59 | if (gesture && !gesture->command().isEmpty() && gesture->sensorEnabled()) {
60 | qDebug() << "We have a gesture with a command set, send that to our required devices, which are (empty means all):" << gesture->devices();
61 | DeviceModel * deviceModel = qobject_cast(connectionManager->deviceModel());
62 | // First get the command from the core model...
63 | CommandModel * commandModel = qobject_cast(connectionManager->commandModel());
64 | CommandInfo cmd = commandModel->getCommand(gesture->command());
65 | for (int i = 0 ; i < deviceModel->count() ; ++i) {
66 | GearBase* device = deviceModel->getDeviceById(i);
67 | qDebug() << device->deviceID() << "of class type" << device->metaObject()->className() << "is connected?" << device->isConnected() << "is the command available?" << device->commandModel->isAvailable(cmd) << "with the command being" << cmd.command << "and is supposed to be a recipient of this command?" << (gesture->devices().count() == 0 || gesture->devices().contains(device->deviceID()));
68 | // Now check if the device is connected, the device model says that command is available,
69 | // and that it's supposed to be a recipient
70 | if (device->isConnected() && device->commandModel->isAvailable(cmd)
71 | && (gesture->devices().count() == 0 || gesture->devices().contains(device->deviceID()))) {
72 | device->sendMessage(gesture->command());
73 | }
74 | }
75 | }
76 | }
77 | };
78 |
79 | GestureController::GestureController(QObject* parent)
80 | : GestureControllerProxySource(parent)
81 | , d(new Private(this))
82 | {
83 | }
84 |
85 | GestureController::~GestureController()
86 | {
87 | delete d;
88 | }
89 |
90 | void GestureController::setConnectionManager(BTConnectionManager* connectionManager)
91 | {
92 | if(d->connectionManager) {
93 | d->connectionManager->disconnect(this);
94 | }
95 | d->connectionManager = connectionManager;
96 | }
97 |
98 | BTConnectionManager *GestureController::connectionManager() const
99 | {
100 | return d->connectionManager;
101 | }
102 |
103 | void GestureController::gestureDetected(const QString& gestureId)
104 | {
105 | d->gestureDetected(gestureId);
106 | }
107 |
108 | void GestureController::setGestureDetails(int index, QString command, QStringList devices)
109 | {
110 | d->model->setGestureDetails(index, command, devices);
111 | }
112 |
113 | void GestureController::setGestureSensorEnabled(int index, bool enabled)
114 | {
115 | d->model->setGestureSensorEnabled(index, enabled);
116 | }
117 |
118 | void GestureController::setGestureSensorPinned(int index, bool pinned)
119 | {
120 | d->model->setGestureSensorPinned(index, pinned);
121 | }
122 |
123 | GestureDetectorModel * GestureController::model() const
124 | {
125 | return d->model;
126 | }
127 |
--------------------------------------------------------------------------------
/src/GestureController.h:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2021 Dan Leinir Turthra Jensen
3 | *
4 | * This program is free software; you can redistribute it and/or modify
5 | * it under the terms of the GNU Library General Public License as
6 | * published by the Free Software Foundation; either version 3, or
7 | * (at your option) any later version.
8 | *
9 | * This program is distributed in the hope that it will be useful,
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | * GNU Library General Public License for more details
13 | *
14 | * You should have received a copy of the GNU Library General Public License
15 | * along with this program; if not, see
16 | */
17 |
18 | #ifndef GESTURECONTROLLER_H
19 | #define GESTURECONTROLLER_H
20 |
21 | #include "rep_GestureControllerProxy_source.h"
22 |
23 | #include "AppSettings.h"
24 |
25 | class BTConnectionManager;
26 | class GestureDetectorModel;
27 |
28 | /**
29 | * When enabled, the Gesture Controller will listen to gestures, and when one is identified
30 | * the associated command will be sent to the devices enabled for that gesture.
31 | * @see AppSettings
32 | */
33 | class GestureController : public GestureControllerProxySource
34 | {
35 | Q_OBJECT
36 | public:
37 | explicit GestureController(QObject* parent = nullptr);
38 | ~GestureController() override;
39 |
40 | void setConnectionManager(BTConnectionManager* connectionManager);
41 | BTConnectionManager* connectionManager() const;
42 |
43 | Q_SLOT void setGestureDetails(int index, QString command, QStringList devices) override;
44 | // Index is the index of a gesture, but the state is set for all gestures with the same sensor
45 | Q_SLOT void setGestureSensorPinned(int index, bool pinned) override;
46 | // Index is the index of a gesture, but the state is set for all gestures with the same sensor
47 | Q_SLOT void setGestureSensorEnabled(int index, bool enabled) override;
48 |
49 | GestureDetectorModel* model() const;
50 | protected:
51 | // This is here because QSensorGesture does not support modern connect statements
52 | Q_SLOT void gestureDetected(const QString& gestureId);
53 |
54 | private:
55 | class Private;
56 | Private* d;
57 | };
58 |
59 | #endif//GESTURECONTROLLER_H
60 |
--------------------------------------------------------------------------------
/src/GestureControllerProxy.rep:
--------------------------------------------------------------------------------
1 | // Copyright 2019 Dan Leinir Turthra Jensen
2 | //
3 | // This program is free software; you can redistribute it and/or modify
4 | // it under the terms of the GNU Library General Public License as
5 | // published by the Free Software Foundation; either version 3, or
6 | // (at your option) any later version.
7 | //
8 | // This program is distributed in the hope that it will be useful,
9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | // GNU Library General Public License for more details
12 | //
13 | // You should have received a copy of the GNU Library General Public License
14 | // along with this program; if not, see
15 |
16 | class GestureControllerProxy {
17 | SLOT(void setGestureDetails(int index, QString command, QStringList devices))
18 | SLOT(void setGestureSensorPinned(int index, bool pinned))
19 | SLOT(void setGestureSensorEnabled(int index, bool enabled))
20 | };
21 |
--------------------------------------------------------------------------------
/src/GestureDetectorModel.h:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 Dan Leinir Turthra Jensen
3 | *
4 | * This program is free software; you can redistribute it and/or modify
5 | * it under the terms of the GNU Library General Public License as
6 | * published by the Free Software Foundation; either version 3, or
7 | * (at your option) any later version.
8 | *
9 | * This program is distributed in the hope that it will be useful,
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | * GNU Library General Public License for more details
13 | *
14 | * You should have received a copy of the GNU Library General Public License
15 | * along with this program; if not, see
16 | */
17 |
18 | #ifndef GESTUREDETECTORMODEL_H
19 | #define GESTUREDETECTORMODEL_H
20 |
21 | #include "GestureController.h"
22 | #include
23 |
24 | struct GestureDetails;
25 | class GestureDetectorModel : public QAbstractListModel
26 | {
27 | Q_OBJECT
28 | public:
29 | explicit GestureDetectorModel(GestureController* parent = nullptr);
30 | ~GestureDetectorModel() override;
31 |
32 | enum Roles {
33 | NameRole = Qt::UserRole + 1,
34 | IdRole,
35 | SensorIdRole, ///< The ID of the sensor for this gesture
36 | SensorNameRole, ///< Name of the sensor to be shown to the user
37 | SensorEnabledRole, ///< Whether or not the sensor is enabled
38 | SensorPinnedRole, ///< Whether or not the sensor should be shown on the front page
39 | CommandRole,
40 | DefaultCommandRole,
41 | DevicesModel,
42 | FirstInSensorRole, ///< True if the row has a different sensor ID to the previous row
43 | VisibleRole, ///< Whether or not this should be shown
44 | };
45 | Q_ENUM(Roles)
46 |
47 | QHash< int, QByteArray > roleNames() const override;
48 | QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
49 | int rowCount(const QModelIndex& parent = QModelIndex()) const override;
50 |
51 | void setAppSettings(AppSettings* settings);
52 |
53 | void addGesture(GestureDetails* gesture);
54 | GestureDetails* gesture(const QString& gestureId) const;
55 |
56 | void gestureDetailsChanged(GestureDetails* gesture);
57 | void setGestureDetails(int index, QString command, QStringList devices);
58 | // Index is the index of a gesture, but the state is set for all gestures with the same sensor
59 | void setGestureSensorPinned(int index, bool pinned);
60 | // Index is the index of a gesture, but the state is set for all gestures with the same sensor
61 | void setGestureSensorEnabled(int index, bool enabled);
62 |
63 | void setGestureSensorEnabled(GestureDetails* gesture, bool enabled);
64 |
65 | private:
66 | class Private;
67 | Private* d;
68 | };
69 |
70 | class GestureSensor;
71 | class GestureController;
72 | struct GestureDetails {
73 | public:
74 | GestureDetails(QString gestureId, GestureSensor* sensor, GestureController* q);
75 | ~GestureDetails();
76 |
77 | void load();
78 | void save();
79 | GestureSensor* sensor() const;
80 | QString sensorName() const;
81 | QString gestureId() const;
82 | QString humanName() const;
83 | void setCommand(const QString& value);
84 | QString command() const;
85 | QString defaultCommand() const;
86 | void setDevices(const QStringList& value);
87 | QStringList devices() const;
88 | bool visible() const;
89 |
90 | void startDetection();
91 | void stopDetection();
92 | bool sensorEnabled() const;
93 | bool sensorPinned() const;
94 |
95 | private:
96 | friend class GestureDetectorModel;
97 | class Private;
98 | Private* d;
99 | };
100 |
101 | #endif//GESTUREDETECTORMODEL_H
102 |
--------------------------------------------------------------------------------
/src/GestureSensor.cpp:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2024 Dan Leinir Turthra Jensen
3 | *
4 | * This program is free software; you can redistribute it and/or modify
5 | * it under the terms of the GNU Library General Public License as
6 | * published by the Free Software Foundation; either version 3, or
7 | * (at your option) any later version.
8 | *
9 | * This program is distributed in the hope that it will be useful,
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | * GNU Library General Public License for more details
13 | *
14 | * You should have received a copy of the GNU Library General Public License
15 | * along with this program; if not, see
16 | */
17 |
18 | #include "GestureSensor.h"
19 |
20 | #include
21 |
22 | GestureSensor::GestureSensor(QObject* parent)
23 | : QObject(parent)
24 | {
25 | }
26 |
27 | GestureSensor::~GestureSensor()
28 | {
29 | }
30 |
--------------------------------------------------------------------------------
/src/GestureSensor.h:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2024 Dan Leinir Turthra Jensen
3 | *
4 | * This program is free software; you can redistribute it and/or modify
5 | * it under the terms of the GNU Library General Public License as
6 | * published by the Free Software Foundation; either version 3, or
7 | * (at your option) any later version.
8 | *
9 | * This program is distributed in the hope that it will be useful,
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | * GNU Library General Public License for more details
13 | *
14 | * You should have received a copy of the GNU Library General Public License
15 | * along with this program; if not, see
16 | */
17 |
18 | #ifndef GESTURESENSOR_H
19 | #define GESTURESENSOR_H
20 |
21 | #include
22 |
23 | class GestureSensorPrivate;
24 | class GestureSensor : public QObject {
25 | Q_OBJECT
26 | public:
27 | explicit GestureSensor(QObject* parent = nullptr);
28 | ~GestureSensor() override;
29 |
30 | virtual QStringList recognizerSignals() const = 0;
31 | virtual QString sensorId() const = 0;
32 | virtual QString humanName() const = 0;
33 |
34 | virtual void startDetection() = 0;
35 | virtual void stopDetection() = 0;
36 |
37 | Q_SIGNAL void detected(const QString &signature);
38 | };
39 |
40 | #endif//GESTURESENSOR_H
41 |
--------------------------------------------------------------------------------
/src/IdleMode.h:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Dan Leinir Turthra Jensen
3 | * This file based on sample code from Kirigami
4 | *
5 | * This program is free software; you can redistribute it and/or modify
6 | * it under the terms of the GNU Library General Public License as
7 | * published by the Free Software Foundation; either version 3, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU Library General Public License for more details
14 | *
15 | * You should have received a copy of the GNU Library General Public License
16 | * along with this program; if not, see
17 | */
18 |
19 | #ifndef IDLEMODE_H
20 | #define IDLEMODE_H
21 |
22 | #include
23 |
24 | #include "AppSettings.h"
25 |
26 | class BTConnectionManager;
27 |
28 | /**
29 | * When enabled, Idle Mode will pick a random command from the chosen categories
30 | * and if there are no more commands in the queue, that command will be added,
31 | * followed by a pause with a random duration between the minimum and maximum
32 | * pause durations.
33 | * @see AppSettings
34 | */
35 | class IdleMode : public QObject
36 | {
37 | Q_OBJECT
38 | public:
39 | explicit IdleMode(QObject* parent = nullptr);
40 | ~IdleMode() override;
41 |
42 | void setAppSettings(AppSettings* settings);
43 | void setConnectionManager(BTConnectionManager* connectionManager);
44 | private:
45 | class Private;
46 | Private* d;
47 | };
48 |
49 | #endif//IDLEMODE_H
50 |
--------------------------------------------------------------------------------
/src/PermissionsManager.h:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Dan Leinir Turthra Jensen
3 | *
4 | * This program is free software; you can redistribute it and/or modify
5 | * it under the terms of the GNU Library General Public License as
6 | * published by the Free Software Foundation; either version 3, or
7 | * (at your option) any later version.
8 | *
9 | * This program is distributed in the hope that it will be useful,
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | * GNU Library General Public License for more details
13 | *
14 | * You should have received a copy of the GNU Library General Public License
15 | * along with this program; if not, see
16 | */
17 |
18 | #ifndef PERMISSIONSMANAGER_H
19 | #define PERMISSIONSMANAGER_H
20 |
21 | #include
22 |
23 | class PermissionsManager : public QObject
24 | {
25 | Q_OBJECT
26 | Q_PROPERTY(bool hasBluetoothPermissions READ hasBluetoothPermissions NOTIFY permissionsChanged)
27 | Q_PROPERTY(bool hasNotificationPermissions READ hasNotificationPermissions NOTIFY permissionsChanged)
28 | public:
29 | explicit PermissionsManager(QObject* parent = nullptr);
30 | ~PermissionsManager() override;
31 |
32 | Q_INVOKABLE void requestPermission(const QString& permission);
33 | Q_INVOKABLE void requestPermissionNow(const QString& permission);
34 | Q_INVOKABLE bool hasPermission(const QString& permission) const;
35 | Q_SIGNAL void permissionsChanged();
36 |
37 | bool hasBluetoothPermissions() const;
38 | Q_INVOKABLE void requestBluetoothPermissions();
39 | bool hasNotificationPermissions() const;
40 | Q_INVOKABLE void requestNotificationPermissions();
41 | private:
42 | class Private;
43 | Private *d;
44 | };
45 |
46 | #endif//PERMISSIONSMANAGER_H
47 |
48 |
--------------------------------------------------------------------------------
/src/Utilities.h:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Dan Leinir Turthra Jensen
3 | *
4 | * This program is free software; you can redistribute it and/or modify
5 | * it under the terms of the GNU Library General Public License as
6 | * published by the Free Software Foundation; either version 3, or
7 | * (at your option) any later version.
8 | *
9 | * This program is distributed in the hope that it will be useful,
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | * GNU Library General Public License for more details
13 | *
14 | * You should have received a copy of the GNU Library General Public License
15 | * along with this program; if not, see
16 | */
17 |
18 | #ifndef UTILITIES_H
19 | #define UTILITIES_H
20 |
21 | #include
22 | #include
23 |
24 | #include "rep_BTConnectionManagerProxy_replica.h"
25 |
26 | class Utilities : public QObject
27 | {
28 | Q_OBJECT
29 | public:
30 | ~Utilities() override;
31 |
32 | static Utilities* getInstance() {
33 | static Utilities* instance = nullptr;
34 | if(!instance) {
35 | instance = new Utilities();
36 | }
37 | return instance;
38 | }
39 |
40 | //TODO: It would be great to represent commands as objects, not strings or QVariantMap
41 | /**
42 | * An awkward way to get a command from the service via the replicated objects
43 | * Request a command by calling getCommand, and then listen to the commandGotten
44 | * signal, which will be emitted for all responses from the service (that is, likely
45 | * more than once per request)
46 | */
47 | Q_INVOKABLE void getCommand(QString command);
48 | Q_SIGNAL void commandGotten(QVariantMap command);
49 |
50 | void setConnectionManager(BTConnectionManagerProxyReplica* connectionManagerProxy);
51 |
52 | /**
53 | * \brief Get the translated version of several strings used in our data files
54 | * @note Adding strings to this function should be the last resort solution (basically, should only be used for things in .crumpet files)
55 | * @param stringToTranslate The string that you wish to get a human-readable translated version of
56 | * @return The translated version of the string, or the string itself if it was not recognised
57 | */
58 | Q_INVOKABLE QString translateStaticString(const QString &stringToTranslate) const;
59 |
60 | private:
61 | explicit Utilities(QObject* parent = nullptr);
62 | class Private;
63 | Private* d;
64 | };
65 |
66 | #endif//UTILITIES_H
67 |
--------------------------------------------------------------------------------
/src/WalkingSensorGestureReconizer.h:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 Dan Leinir Turthra Jensen
3 | *
4 | * This program is free software; you can redistribute it and/or modify
5 | * it under the terms of the GNU Library General Public License as
6 | * published by the Free Software Foundation; either version 3, or
7 | * (at your option) any later version.
8 | *
9 | * This program is distributed in the hope that it will be useful,
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | * GNU Library General Public License for more details
13 | *
14 | * You should have received a copy of the GNU Library General Public License
15 | * along with this program; if not, see
16 | */
17 |
18 | #ifndef WALKINGSENSORGESTURERECONIZER_H
19 | #define WALKINGSENSORGESTURERECONIZER_H
20 |
21 | #include "GestureSensor.h"
22 |
23 | #include
24 |
25 | class WalkingSensor;
26 | class WalkingSensorSignaller : public QObject
27 | {
28 | Q_OBJECT
29 | public:
30 | explicit WalkingSensorSignaller(WalkingSensor *parent);
31 | ~WalkingSensorSignaller() override;
32 | Q_SIGNALS:
33 | void walkingStarted();
34 | void walkingStopped();
35 | void stepDetected();
36 | void evenStepDetected();
37 | void oddStepDetected();
38 | private:
39 | Q_DISABLE_COPY(WalkingSensorSignaller)
40 | };
41 |
42 | class WalkingSensorPrivate;
43 | class WalkingSensor : public GestureSensor {
44 | Q_OBJECT
45 | public:
46 | explicit WalkingSensor(QObject *parent);
47 | QStringList recognizerSignals() const override;
48 | QString sensorId() const override;
49 | QString humanName() const override;
50 | void startDetection() override;
51 | void stopDetection() override;
52 | private:
53 | WalkingSensorPrivate* d{nullptr};
54 | };
55 |
56 |
57 | #endif // WALKINGSENSORGESTURERECONIZER_H
58 |
--------------------------------------------------------------------------------
/src/audio/Sparkle-sound-effect.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OpenTails/CRUMPET-Android/f540d6f35e62b208f8949cec2c3d21f10d94b60a/src/audio/Sparkle-sound-effect.mp3
--------------------------------------------------------------------------------
/src/commands/digitail-builtin.crumpet:
--------------------------------------------------------------------------------
1 | {
2 | "Title": "DIGITAiL Built-ins",
3 | "Description": "The basic set of commands defined in the DIGITAiL firmware",
4 | "Commands": [
5 | {
6 | "Category": "",
7 | "Command": "TAILHM",
8 | "Duration": 200,
9 | "Group": 0,
10 | "MinimumCooldown": 1000,
11 | "Name": "Tail Home Position"
12 | },
13 | {
14 | "Category": "relaxed",
15 | "Command": "TAILS1",
16 | "Duration": 11530,
17 | "Group": 0,
18 | "MinimumCooldown": 1000,
19 | "Name": "One"
20 | },
21 | {
22 | "Category": "relaxed",
23 | "Command": "TAILS2",
24 | "Duration": 7100,
25 | "Group": 0,
26 | "MinimumCooldown": 1000,
27 | "Name": "Two"
28 | },
29 | {
30 | "Category": "relaxed",
31 | "Command": "TAILS3",
32 | "Duration": 8500,
33 | "Group": 0,
34 | "MinimumCooldown": 1000,
35 | "Name": "Three"
36 | },
37 | {
38 | "Category": "excited",
39 | "Command": "TAILFA",
40 | "Duration": 9960,
41 | "Group": 0,
42 | "MinimumCooldown": 1000,
43 | "Name": "One"
44 | },
45 | {
46 | "Category": "excited",
47 | "Command": "TAILSH",
48 | "Duration": 7460,
49 | "Group": 0,
50 | "MinimumCooldown": 1000,
51 | "Name": "Two"
52 | },
53 | {
54 | "Category": "excited",
55 | "Command": "TAILHA",
56 | "Duration": 8860,
57 | "Group": 0,
58 | "MinimumCooldown": 1000,
59 | "Name": "Three"
60 | },
61 | {
62 | "Category": "excited",
63 | "Command": "TAILER",
64 | "Duration": 5800,
65 | "Group": 0,
66 | "MinimumCooldown": 1000,
67 | "Name": "Four"
68 | },
69 | {
70 | "Category": "tense",
71 | "Command": "TAILT1",
72 | "Duration": 4060,
73 | "Group": 0,
74 | "MinimumCooldown": 1000,
75 | "Name": "One"
76 | },
77 | {
78 | "Category": "tense",
79 | "Command": "TAILT2",
80 | "Duration": 5550,
81 | "Group": 0,
82 | "MinimumCooldown": 1000,
83 | "Name": "Two"
84 | },
85 | {
86 | "Category": "tense",
87 | "Command": "TAILET",
88 | "Duration": 4730,
89 | "Group": 0,
90 | "MinimumCooldown": 1000,
91 | "Name": "Three"
92 | },
93 | {
94 | "Category": "tense",
95 | "Command": "TAILEP",
96 | "Duration": 9760,
97 | "Group": 0,
98 | "MinimumCooldown": 1000,
99 | "Name": "Four"
100 | },
101 | {
102 | "Category": "lights",
103 | "Command": "LEDREC",
104 | "Duration": 10000,
105 | "Group": 1,
106 | "MinimumCooldown": 0,
107 | "Name": "BLINK"
108 | },
109 | {
110 | "Category": "lights",
111 | "Command": "LEDTRI",
112 | "Duration": 10000,
113 | "Group": 1,
114 | "MinimumCooldown": 0,
115 | "Name": "TRIANGLE"
116 | },
117 | {
118 | "Category": "lights",
119 | "Command": "LEDSAW",
120 | "Duration": 10000,
121 | "Group": 1,
122 | "MinimumCooldown": 0,
123 | "Name": "SAWTOOTH"
124 | },
125 | {
126 | "Category": "lights",
127 | "Command": "LEDSOS",
128 | "Duration": 10000,
129 | "Group": 1,
130 | "MinimumCooldown": 0,
131 | "Name": "MORSE"
132 | },
133 | {
134 | "Category": "lights",
135 | "Command": "LEDBEA",
136 | "Duration": 10000,
137 | "Group": 1,
138 | "MinimumCooldown": 0,
139 | "Name": "BEACON"
140 | },
141 | {
142 | "Category": "lights",
143 | "Command": "LEDFLA",
144 | "Duration": 10000,
145 | "Group": 1,
146 | "MinimumCooldown": 0,
147 | "Name": "FLAME"
148 | },
149 | {
150 | "Category": "lights",
151 | "Command": "LEDSTR",
152 | "Duration": 10000,
153 | "Group": 1,
154 | "MinimumCooldown": 0,
155 | "Name": "STROBE"
156 | },
157 | {
158 | "Category": "",
159 | "Command": "LEDOFF",
160 | "Duration": 300,
161 | "Group": 1,
162 | "MinimumCooldown": 0,
163 | "Name": "Off"
164 | }
165 | ]
166 | }
167 |
--------------------------------------------------------------------------------
/src/commands/mitail-builtin.crumpet:
--------------------------------------------------------------------------------
1 | {
2 | "Title": "MiTail Built-ins",
3 | "Description": "The basic set of commands defined in the MiTail firmware",
4 | "Commands": [
5 | {
6 | "Category": "",
7 | "Command": "TAILHM",
8 | "Duration": 200,
9 | "Group": 0,
10 | "MinimumCooldown": 1000,
11 | "Name": "Tail Home Position"
12 | },
13 | {
14 | "Category": "relaxed",
15 | "Command": "TAILS1",
16 | "Duration": 11150,
17 | "Group": 0,
18 | "MinimumCooldown": 1000,
19 | "Name": "One"
20 | },
21 | {
22 | "Category": "relaxed",
23 | "Command": "TAILS2",
24 | "Duration": 7245,
25 | "Group": 0,
26 | "MinimumCooldown": 1000,
27 | "Name": "Two"
28 | },
29 | {
30 | "Category": "relaxed",
31 | "Command": "TAILS3",
32 | "Duration": 8840,
33 | "Group": 0,
34 | "MinimumCooldown": 1000,
35 | "Name": "Three"
36 | },
37 | {
38 | "Category": "excited",
39 | "Command": "TAILFA",
40 | "Duration": 16040,
41 | "Group": 0,
42 | "MinimumCooldown": 1000,
43 | "Name": "One"
44 | },
45 | {
46 | "Category": "excited",
47 | "Command": "TAILSH",
48 | "Duration": 14840,
49 | "Group": 0,
50 | "MinimumCooldown": 1000,
51 | "Name": "Two"
52 | },
53 | {
54 | "Category": "excited",
55 | "Command": "TAILHA",
56 | "Duration": 14045,
57 | "Group": 0,
58 | "MinimumCooldown": 1000,
59 | "Name": "Three"
60 | },
61 | {
62 | "Category": "excited",
63 | "Command": "TAILER",
64 | "Duration": 5270,
65 | "Group": 0,
66 | "MinimumCooldown": 1000,
67 | "Name": "Four"
68 | },
69 | {
70 | "Category": "tense",
71 | "Command": "TAILT1",
72 | "Duration": 8045,
73 | "Group": 0,
74 | "MinimumCooldown": 1000,
75 | "Name": "One"
76 | },
77 | {
78 | "Category": "tense",
79 | "Command": "TAILT2",
80 | "Duration": 11645,
81 | "Group": 0,
82 | "MinimumCooldown": 1000,
83 | "Name": "Two"
84 | },
85 | {
86 | "Category": "tense",
87 | "Command": "TAILET",
88 | "Duration": 11640,
89 | "Group": 0,
90 | "MinimumCooldown": 1000,
91 | "Name": "Three"
92 | },
93 | {
94 | "Category": "tense",
95 | "Command": "TAILEP",
96 | "Duration": 12040,
97 | "Group": 0,
98 | "MinimumCooldown": 1000,
99 | "Name": "Four"
100 | }
101 | ]
102 | }
103 |
--------------------------------------------------------------------------------
/src/commands/mitail-lights-builtin.crumpet:
--------------------------------------------------------------------------------
1 | {
2 | "Title": "MiTail Built-ins",
3 | "Description": "The basic set of commands defined in the MiTail firmware",
4 | "Commands": [
5 | {
6 | "Category": "",
7 | "Command": "TAILHM",
8 | "Duration": 200,
9 | "Group": 0,
10 | "MinimumCooldown": 1000,
11 | "Name": "Tail Home Position"
12 | },
13 | {
14 | "Category": "relaxed",
15 | "Command": "TAILS1",
16 | "Duration": 11150,
17 | "Group": 0,
18 | "MinimumCooldown": 1000,
19 | "Name": "One"
20 | },
21 | {
22 | "Category": "relaxed",
23 | "Command": "TAILS2",
24 | "Duration": 7245,
25 | "Group": 0,
26 | "MinimumCooldown": 1000,
27 | "Name": "Two"
28 | },
29 | {
30 | "Category": "relaxed",
31 | "Command": "TAILS3",
32 | "Duration": 8840,
33 | "Group": 0,
34 | "MinimumCooldown": 1000,
35 | "Name": "Three"
36 | },
37 | {
38 | "Category": "excited",
39 | "Command": "TAILFA",
40 | "Duration": 16040,
41 | "Group": 0,
42 | "MinimumCooldown": 1000,
43 | "Name": "One"
44 | },
45 | {
46 | "Category": "excited",
47 | "Command": "TAILSH",
48 | "Duration": 14840,
49 | "Group": 0,
50 | "MinimumCooldown": 1000,
51 | "Name": "Two"
52 | },
53 | {
54 | "Category": "excited",
55 | "Command": "TAILHA",
56 | "Duration": 14045,
57 | "Group": 0,
58 | "MinimumCooldown": 1000,
59 | "Name": "Three"
60 | },
61 | {
62 | "Category": "excited",
63 | "Command": "TAILER",
64 | "Duration": 5270,
65 | "Group": 0,
66 | "MinimumCooldown": 1000,
67 | "Name": "Four"
68 | },
69 | {
70 | "Category": "tense",
71 | "Command": "TAILT1",
72 | "Duration": 8045,
73 | "Group": 0,
74 | "MinimumCooldown": 1000,
75 | "Name": "One"
76 | },
77 | {
78 | "Category": "tense",
79 | "Command": "TAILT2",
80 | "Duration": 11645,
81 | "Group": 0,
82 | "MinimumCooldown": 1000,
83 | "Name": "Two"
84 | },
85 | {
86 | "Category": "tense",
87 | "Command": "TAILET",
88 | "Duration": 11640,
89 | "Group": 0,
90 | "MinimumCooldown": 1000,
91 | "Name": "Three"
92 | },
93 | {
94 | "Category": "tense",
95 | "Command": "TAILEP",
96 | "Duration": 12040,
97 | "Group": 0,
98 | "MinimumCooldown": 1000,
99 | "Name": "Four"
100 | },
101 | {
102 | "Category": "lights",
103 | "Command": "LEDREC",
104 | "Duration": 30300,
105 | "Group": 1,
106 | "MinimumCooldown": 0,
107 | "Name": "BLINK"
108 | },
109 | {
110 | "Category": "lights",
111 | "Command": "LEDTRI",
112 | "Duration": 30300,
113 | "Group": 1,
114 | "MinimumCooldown": 0,
115 | "Name": "TRIANGLE"
116 | },
117 | {
118 | "Category": "lights",
119 | "Command": "LEDSAW",
120 | "Duration": 30600,
121 | "Group": 1,
122 | "MinimumCooldown": 0,
123 | "Name": "SAWTOOTH"
124 | },
125 | {
126 | "Category": "lights",
127 | "Command": "LEDSOS",
128 | "Duration": 32450,
129 | "Group": 1,
130 | "MinimumCooldown": 0,
131 | "Name": "MORSE"
132 | },
133 | {
134 | "Category": "lights",
135 | "Command": "LEDBEA",
136 | "Duration": 30300,
137 | "Group": 1,
138 | "MinimumCooldown": 0,
139 | "Name": "BEACON"
140 | },
141 | {
142 | "Category": "lights",
143 | "Command": "LEDFLA",
144 | "Duration": 32800,
145 | "Group": 1,
146 | "MinimumCooldown": 0,
147 | "Name": "FLAME"
148 | },
149 | {
150 | "Category": "lights",
151 | "Command": "LEDSTR",
152 | "Duration": 35000,
153 | "Group": 1,
154 | "MinimumCooldown": 0,
155 | "Name": "STROBE"
156 | },
157 | {
158 | "Category": "",
159 | "Command": "LEDON",
160 | "Duration": 20200,
161 | "Group": 1,
162 | "MinimumCooldown": 0,
163 | "Name": "On"
164 | },
165 | {
166 | "Category": "",
167 | "Command": "LEDOFF",
168 | "Duration": 40,
169 | "Group": 1,
170 | "MinimumCooldown": 0,
171 | "Name": "Off"
172 | }
173 | ]
174 | }
175 |
--------------------------------------------------------------------------------
/src/commands/mitailmini-builtin.crumpet:
--------------------------------------------------------------------------------
1 | {
2 | "Title": "MiTail Mini Built-ins",
3 | "Description": "The basic set of commands defined in the MiTail Mini firmware",
4 | "Commands": [
5 | {
6 | "Category": "",
7 | "Command": "TAILHM",
8 | "Duration": 200,
9 | "Group": 0,
10 | "MinimumCooldown": 1000,
11 | "Name": "Tail Home Position"
12 | },
13 | {
14 | "Category": "relaxed",
15 | "Command": "TAILS1",
16 | "Duration": 11150,
17 | "Group": 0,
18 | "MinimumCooldown": 1000,
19 | "Name": "One"
20 | },
21 | {
22 | "Category": "relaxed",
23 | "Command": "TAILS2",
24 | "Duration": 7245,
25 | "Group": 0,
26 | "MinimumCooldown": 1000,
27 | "Name": "Two"
28 | },
29 | {
30 | "Category": "relaxed",
31 | "Command": "TAILS3",
32 | "Duration": 8840,
33 | "Group": 0,
34 | "MinimumCooldown": 1000,
35 | "Name": "Three"
36 | },
37 | {
38 | "Category": "excited",
39 | "Command": "TAILFA",
40 | "Duration": 16040,
41 | "Group": 0,
42 | "MinimumCooldown": 1000,
43 | "Name": "One"
44 | }
45 | ]
46 | }
47 |
--------------------------------------------------------------------------------
/src/commands/mitailmini-lights-builtin.crumpet:
--------------------------------------------------------------------------------
1 | {
2 | "Title": "MiTail Mini Glowtip Built-ins",
3 | "Description": "The basic set of commands defined in the MiTail Mini firmware for tails with a glowtip",
4 | "Commands": [
5 | {
6 | "Category": "",
7 | "Command": "TAILHM",
8 | "Duration": 200,
9 | "Group": 0,
10 | "MinimumCooldown": 1000,
11 | "Name": "Tail Home Position"
12 | },
13 | {
14 | "Category": "relaxed",
15 | "Command": "TAILS1",
16 | "Duration": 11150,
17 | "Group": 0,
18 | "MinimumCooldown": 1000,
19 | "Name": "One"
20 | },
21 | {
22 | "Category": "relaxed",
23 | "Command": "TAILS2",
24 | "Duration": 7245,
25 | "Group": 0,
26 | "MinimumCooldown": 1000,
27 | "Name": "Two"
28 | },
29 | {
30 | "Category": "relaxed",
31 | "Command": "TAILS3",
32 | "Duration": 8840,
33 | "Group": 0,
34 | "MinimumCooldown": 1000,
35 | "Name": "Three"
36 | },
37 | {
38 | "Category": "excited",
39 | "Command": "TAILFA",
40 | "Duration": 16040,
41 | "Group": 0,
42 | "MinimumCooldown": 1000,
43 | "Name": "One"
44 | },
45 | {
46 | "Category": "lights",
47 | "Command": "LEDREC",
48 | "Duration": 30300,
49 | "Group": 1,
50 | "MinimumCooldown": 0,
51 | "Name": "BLINK"
52 | },
53 | {
54 | "Category": "lights",
55 | "Command": "LEDTRI",
56 | "Duration": 30300,
57 | "Group": 1,
58 | "MinimumCooldown": 0,
59 | "Name": "TRIANGLE"
60 | },
61 | {
62 | "Category": "lights",
63 | "Command": "LEDSAW",
64 | "Duration": 30600,
65 | "Group": 1,
66 | "MinimumCooldown": 0,
67 | "Name": "SAWTOOTH"
68 | },
69 | {
70 | "Category": "lights",
71 | "Command": "LEDSOS",
72 | "Duration": 32450,
73 | "Group": 1,
74 | "MinimumCooldown": 0,
75 | "Name": "MORSE"
76 | },
77 | {
78 | "Category": "lights",
79 | "Command": "LEDBEA",
80 | "Duration": 30300,
81 | "Group": 1,
82 | "MinimumCooldown": 0,
83 | "Name": "BEACON"
84 | },
85 | {
86 | "Category": "lights",
87 | "Command": "LEDFLA",
88 | "Duration": 32800,
89 | "Group": 1,
90 | "MinimumCooldown": 0,
91 | "Name": "FLAME"
92 | },
93 | {
94 | "Category": "lights",
95 | "Command": "LEDSTR",
96 | "Duration": 35000,
97 | "Group": 1,
98 | "MinimumCooldown": 0,
99 | "Name": "STROBE"
100 | },
101 | {
102 | "Category": "",
103 | "Command": "LEDON",
104 | "Duration": 20200,
105 | "Group": 1,
106 | "MinimumCooldown": 0,
107 | "Name": "On"
108 | },
109 | {
110 | "Category": "",
111 | "Command": "LEDOFF",
112 | "Duration": 40,
113 | "Group": 1,
114 | "MinimumCooldown": 0,
115 | "Name": "Off"
116 | }
117 | ]
118 | }
119 |
--------------------------------------------------------------------------------
/src/convert_translations.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #originally a file from KStars
3 |
4 | if [ $# -lt 1 ] ; then
5 | echo Usage: convert_translations.sh po_dir_location
6 | echo
7 | echo The script finds the downloaded translation files \(\*.po\) from the po subdirectory and it converts to
8 | echo mo format what can be loaded on Android directly.
9 | echo
10 | exit 1
11 | fi
12 |
13 | mkdir -p $1/locale
14 | DIRS=$(ls -1 po)
15 |
16 | echo Convert translations...
17 | for dir in $DIRS; do
18 | # Don't convert the source pot file, let's just skip that one
19 | if [[ "$dir" == "digitail.pot" ]]; then
20 | continue
21 | fi
22 | NEW_DIR=$1/locale/$dir/LC_MESSAGES
23 | mkdir -p $NEW_DIR
24 | echo Convert po/$dir/digitail.po to $NEW_DIR/digitail.mo
25 | msgfmt po/$dir/digitail.po -o $NEW_DIR/digitail.mo
26 | done
27 |
--------------------------------------------------------------------------------
/src/gearimplementations/GearDigitail.h:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Dan Leinir Turthra Jensen
3 | *
4 | * This program is free software; you can redistribute it and/or modify
5 | * it under the terms of the GNU Library General Public License as
6 | * published by the Free Software Foundation; either version 3, or
7 | * (at your option) any later version.
8 | *
9 | * This program is distributed in the hope that it will be useful,
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | * GNU Library General Public License for more details
13 | *
14 | * You should have received a copy of the GNU Library General Public License
15 | * along with this program; if not, see
16 | */
17 |
18 | #ifndef BTDEVICETAIL_H
19 | #define BTDEVICETAIL_H
20 |
21 | #include "GearBase.h"
22 |
23 | class GearDigitail : public GearBase
24 | {
25 | Q_OBJECT
26 | public:
27 | explicit GearDigitail(const QBluetoothDeviceInfo& info, DeviceModel * parent = nullptr);
28 | ~GearDigitail() override;
29 |
30 | bool isConnected() const override;
31 | QString version() const override;
32 | QString currentCall() const override;
33 | int batteryLevel() const override;
34 |
35 | void connectDevice() override;
36 | void disconnectDevice() override;
37 |
38 | void sendMessage(const QString &message) override;
39 | private:
40 | class Private;
41 | Private* d;
42 | };
43 |
44 | #endif//BTDEVICETAIL_H
45 |
--------------------------------------------------------------------------------
/src/gearimplementations/GearEars.h:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 Dan Leinir Turthra Jensen
3 | *
4 | * This program is free software; you can redistribute it and/or modify
5 | * it under the terms of the GNU Library General Public License as
6 | * published by the Free Software Foundation; either version 3, or
7 | * (at your option) any later version.
8 | *
9 | * This program is distributed in the hope that it will be useful,
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | * GNU Library General Public License for more details
13 | *
14 | * You should have received a copy of the GNU Library General Public License
15 | * along with this program; if not, see
16 | */
17 |
18 | #ifndef BTDEVICEEARS_H
19 | #define BTDEVICEEARS_H
20 |
21 | #include "GearBase.h"
22 |
23 | class GearEars : public GearBase
24 | {
25 | Q_OBJECT
26 | Q_PROPERTY(ListenMode listenMode READ listenMode WRITE setListenMode NOTIFY listenModeChanged)
27 | Q_PROPERTY(bool micsSwapped READ micsSwapped NOTIFY micsSwappedChanged)
28 | Q_PROPERTY(bool hasTilt READ hasTilt NOTIFY hasTiltChanged)
29 | Q_PROPERTY(bool canBalanceListening READ canBalanceListening NOTIFY canBalanceListeningChanged)
30 | Q_PROPERTY(bool tiltEnabled READ tiltEnabled NOTIFY tiltEnabledChanged)
31 | public:
32 | explicit GearEars(const QBluetoothDeviceInfo& info, DeviceModel * parent = nullptr);
33 | ~GearEars() override;
34 |
35 | enum ListenMode {
36 | ListenModeOff, ///< Microphones off
37 | ListenModeOn, ///< The iOS-friendly on-board listen mode
38 | ListenModeFull ///< Full listening support, including feedback for all values
39 | };
40 | Q_ENUMS(ListenMode)
41 |
42 | bool isConnected() const override;
43 | QString version() const override;
44 | QString currentCall() const override;
45 | int batteryLevel() const override;
46 |
47 | void connectDevice() override;
48 | void disconnectDevice() override;
49 |
50 | QStringList defaultCommandFiles() const override;
51 |
52 | ListenMode listenMode() const;
53 | void setListenMode(const ListenMode& listenMode);
54 | Q_SIGNAL void listenModeChanged();
55 |
56 | bool micsSwapped() const;
57 | Q_SIGNAL void micsSwappedChanged();
58 |
59 | bool hasTilt() const;
60 | Q_SIGNAL void hasTiltChanged();
61 |
62 | bool canBalanceListening() const;
63 | Q_SIGNAL void canBalanceListeningChanged();
64 |
65 | bool tiltEnabled() const;
66 | Q_SIGNAL void tiltEnabledChanged();
67 | void setTiltMode(bool tiltState);
68 |
69 | QVariantList supportedTiltEvents() override;
70 | QVariantList supportedSoundEvents() override;
71 |
72 | void sendMessage(const QString &message) override;
73 |
74 | Q_INVOKABLE void checkOTA() override;
75 | bool hasAvailableOTA() override;
76 | void setOtaVersion(const QString & version) override;
77 | QString otaVersion() override;
78 | Q_INVOKABLE void downloadOTAData() override;
79 | Q_INVOKABLE void setOTAData ( const QString& md5sum, const QByteArray& firmware ) override;
80 | bool hasOTAData() override;
81 | Q_INVOKABLE void startOTA() override;
82 | private:
83 | class Private;
84 | Private* d;
85 | };
86 |
87 | #endif//BTDEVICEEARS_H
88 |
--------------------------------------------------------------------------------
/src/gearimplementations/GearFake.cpp:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Dan Leinir Turthra Jensen
3 | *
4 | * This program is free software; you can redistribute it and/or modify
5 | * it under the terms of the GNU Library General Public License as
6 | * published by the Free Software Foundation; either version 3, or
7 | * (at your option) any later version.
8 | *
9 | * This program is distributed in the hope that it will be useful,
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | * GNU Library General Public License for more details
13 | *
14 | * You should have received a copy of the GNU Library General Public License
15 | * along with this program; if not, see
16 | */
17 |
18 | #include "GearFake.h"
19 | #include "CommandPersistence.h"
20 |
21 | #include
22 |
23 | #include
24 |
25 | class GearFake::Private {
26 | public:
27 | Private() {}
28 | bool isConnected{false};
29 | int batteryLevel{-1};
30 | bool isCharging{false};
31 | QString currentCall;
32 | QLatin1String version{"Fake V2"};
33 |
34 | QTimer batteryTimer;
35 | };
36 |
37 | GearFake::GearFake(const QBluetoothDeviceInfo& info, DeviceModel * parent)
38 | : GearBase(info, parent)
39 | , d(new Private)
40 | {
41 | d->batteryTimer.setInterval(1000);
42 | setSupportsOTA(true);
43 | setHasLights(true);
44 | setHasShutdown(true);
45 | setHasNoPhoneMode(true);
46 | setNoPhoneModeGroups({
47 | i18nc("Name of the calm and relaxed group as used for no phone group selection", "Calm and Relaxed"),
48 | i18nc("Name of the fast and excited group as used for no phone group selection", "Fast and Excited"),
49 | i18nc("Name of the frustrated and tense group as used for no phone group selection", "Frustrated and Tense"),
50 | });
51 | connect(&d->batteryTimer, &QTimer::timeout, this, [this](){
52 | if (d->batteryLevel > 3) {
53 | if (d->isCharging) {
54 | d->isCharging = false;
55 | setChargingState(0);
56 | } else {
57 | d->isCharging = true;
58 | setChargingState(1);
59 | }
60 | d->batteryLevel = -1;
61 | } else {
62 | d->batteryLevel++;
63 | }
64 | setBatteryLevelPercent(d->batteryLevel * 25);
65 | Q_EMIT batteryLevelChanged(d->batteryLevel);
66 | });
67 | }
68 |
69 | GearFake::~GearFake()
70 | {
71 | delete d;
72 | }
73 |
74 | bool GearFake::isConnected() const
75 | {
76 | return d->isConnected;
77 | }
78 |
79 | int GearFake::batteryLevel() const
80 | {
81 | return d->batteryLevel;
82 | }
83 |
84 | QString GearFake::currentCall() const
85 | {
86 | return d->currentCall;
87 | }
88 |
89 | QString GearFake::version() const
90 | {
91 | return d->version;
92 | }
93 |
94 | void GearFake::connectDevice()
95 | {
96 | setIsConnecting(true);
97 | QTimer::singleShot(1000, this, [this](){
98 | d->isConnected = true;
99 | Q_EMIT isConnectedChanged(isConnected());
100 | setIsConnecting(false);
101 | reloadCommands();
102 | setKnownFirmwareMessage(i18nc("An example message to show people what the firmware message will look like for a real device", "This is a message that's supposed to inform people that there is something important going on with their firmware"));
103 | d->batteryTimer.start();
104 | });
105 | }
106 |
107 | void GearFake::disconnectDevice()
108 | {
109 | d->batteryTimer.stop();
110 | d->batteryLevel = -1;
111 | Q_EMIT batteryLevelChanged(d->batteryLevel);
112 | commandModel->clear();
113 | commandShorthands.clear();
114 | d->isConnected = false;
115 | Q_EMIT isConnectedChanged(d->isConnected);
116 | setIsConnecting(false);
117 | }
118 |
119 | void GearFake::sendMessage(const QString& message)
120 | {
121 | qDebug() << "Fakery for" << message;
122 | CommandInfo commandInfo;
123 | const CommandInfoList& commands = commandModel->allCommands();
124 | for (const CommandInfo& command : commands) {
125 | if (command.command == message) {
126 | commandInfo = command;
127 | break;
128 | }
129 | }
130 | if(commandInfo.isValid()) {
131 | commandModel->setRunning(message, true);
132 | d->currentCall = message;
133 | Q_EMIT currentCallChanged(message);
134 | QTimer::singleShot(commandInfo.duration, this, [this, message](){
135 | d->currentCall.clear();
136 | commandModel->setRunning(message, false);
137 | Q_EMIT currentCallChanged(currentCall());
138 | });
139 | }
140 | }
141 |
142 | QStringList GearFake::defaultCommandFiles() const
143 | {
144 | return QStringList{QLatin1String{":/commands/mitail-lights-builtin.crumpet"}};
145 | }
146 |
--------------------------------------------------------------------------------
/src/gearimplementations/GearFake.h:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Dan Leinir Turthra Jensen
3 | *
4 | * This program is free software; you can redistribute it and/or modify
5 | * it under the terms of the GNU Library General Public License as
6 | * published by the Free Software Foundation; either version 3, or
7 | * (at your option) any later version.
8 | *
9 | * This program is distributed in the hope that it will be useful,
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | * GNU Library General Public License for more details
13 | *
14 | * You should have received a copy of the GNU Library General Public License
15 | * along with this program; if not, see
16 | */
17 |
18 | #ifndef BTDEVICEFAKE_H
19 | #define BTDEVICEFAKE_H
20 |
21 | #include "GearBase.h"
22 |
23 | class GearFake : public GearBase
24 | {
25 | Q_OBJECT
26 | public:
27 | explicit GearFake(const QBluetoothDeviceInfo& info, DeviceModel * parent = nullptr);
28 | ~GearFake() override;
29 |
30 | bool isConnected() const override;
31 | QString version() const override;
32 | QString currentCall() const override;
33 | int batteryLevel() const override;
34 |
35 | void connectDevice() override;
36 | void disconnectDevice() override;
37 |
38 | void sendMessage(const QString &message) override;
39 | QStringList defaultCommandFiles() const override;
40 | private:
41 | class Private;
42 | Private* d;
43 | };
44 |
45 | #endif//BTDEVICEFAKE_H
46 |
--------------------------------------------------------------------------------
/src/gearimplementations/GearFlutterWings.h:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2021 Dan Leinir Turthra Jensen
3 | *
4 | * This program is free software; you can redistribute it and/or modify
5 | * it under the terms of the GNU Library General Public License as
6 | * published by the Free Software Foundation; either version 3, or
7 | * (at your option) any later version.
8 | *
9 | * This program is distributed in the hope that it will be useful,
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | * GNU Library General Public License for more details
13 | *
14 | * You should have received a copy of the GNU Library General Public License
15 | * along with this program; if not, see
16 | */
17 |
18 | #ifndef BTDEVICEFLUTTERWINGS_H
19 | #define BTDEVICEFLUTTERWINGS_H
20 |
21 | #include "GearBase.h"
22 |
23 | class GearFlutterWings : public GearBase
24 | {
25 | Q_OBJECT
26 | public:
27 | explicit GearFlutterWings(const QBluetoothDeviceInfo& info, DeviceModel * parent = nullptr);
28 | ~GearFlutterWings() override;
29 |
30 | bool isConnected() const override;
31 | QString version() const override;
32 | QString currentCall() const override;
33 | int batteryLevel() const override;
34 |
35 | void connectDevice() override;
36 | void disconnectDevice() override;
37 |
38 | QStringList defaultCommandFiles() const override;
39 |
40 | void sendMessage(const QString &message) override;
41 |
42 | Q_INVOKABLE void checkOTA() override;
43 | bool hasAvailableOTA() override;
44 | void setOtaVersion(const QString & version) override;
45 | QString otaVersion() override;
46 | Q_INVOKABLE void downloadOTAData() override;
47 | Q_INVOKABLE void setOTAData ( const QString& md5sum, const QByteArray& firmware ) override;
48 | bool hasOTAData() override;
49 | Q_INVOKABLE void startOTA() override;
50 | private:
51 | class Private;
52 | Private* d;
53 | };
54 |
55 | #endif//BTDEVICEFLUTTERWINGS_H
56 |
--------------------------------------------------------------------------------
/src/gearimplementations/GearMitail.h:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2021 Dan Leinir Turthra Jensen
3 | *
4 | * This program is free software; you can redistribute it and/or modify
5 | * it under the terms of the GNU Library General Public License as
6 | * published by the Free Software Foundation; either version 3, or
7 | * (at your option) any later version.
8 | *
9 | * This program is distributed in the hope that it will be useful,
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | * GNU Library General Public License for more details
13 | *
14 | * You should have received a copy of the GNU Library General Public License
15 | * along with this program; if not, see
16 | */
17 |
18 | #ifndef BTDEVICEMITAIL_H
19 | #define BTDEVICEMITAIL_H
20 |
21 | #include "GearBase.h"
22 |
23 | class GearMitail : public GearBase
24 | {
25 | Q_OBJECT
26 | public:
27 | explicit GearMitail(const QBluetoothDeviceInfo& info, DeviceModel * parent = nullptr);
28 | ~GearMitail() override;
29 |
30 | bool isConnected() const override;
31 | QString version() const override;
32 | QString currentCall() const override;
33 | int batteryLevel() const override;
34 |
35 | void connectDevice() override;
36 | void disconnectDevice() override;
37 |
38 | QStringList defaultCommandFiles() const override;
39 |
40 | void sendMessage(const QString &message) override;
41 |
42 | Q_INVOKABLE void checkOTA() override;
43 | bool hasAvailableOTA() override;
44 | void setOtaVersion(const QString & version) override;
45 | QString otaVersion() override;
46 | Q_INVOKABLE void downloadOTAData() override;
47 | Q_INVOKABLE void setOTAData ( const QString& md5sum, const QByteArray& firmware ) override;
48 | bool hasOTAData() override;
49 | Q_INVOKABLE void startOTA() override;
50 | private:
51 | class Private;
52 | Private* d;
53 | };
54 |
55 | #endif//BTDEVICEMITAIL_H
56 |
--------------------------------------------------------------------------------
/src/gearimplementations/GearMitailMini.h:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2021 Dan Leinir Turthra Jensen
3 | *
4 | * This program is free software; you can redistribute it and/or modify
5 | * it under the terms of the GNU Library General Public License as
6 | * published by the Free Software Foundation; either version 3, or
7 | * (at your option) any later version.
8 | *
9 | * This program is distributed in the hope that it will be useful,
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | * GNU Library General Public License for more details
13 | *
14 | * You should have received a copy of the GNU Library General Public License
15 | * along with this program; if not, see
16 | */
17 |
18 | #ifndef BTDEVICEMITAILMINI_H
19 | #define BTDEVICEMITAILMINI_H
20 |
21 | #include "GearBase.h"
22 |
23 | class GearMitailMini : public GearBase
24 | {
25 | Q_OBJECT
26 | public:
27 | explicit GearMitailMini(const QBluetoothDeviceInfo& info, DeviceModel * parent = nullptr);
28 | ~GearMitailMini() override;
29 |
30 | bool isConnected() const override;
31 | QString version() const override;
32 | QString currentCall() const override;
33 | int batteryLevel() const override;
34 |
35 | void connectDevice() override;
36 | void disconnectDevice() override;
37 |
38 | QStringList defaultCommandFiles() const override;
39 |
40 | void sendMessage(const QString &message) override;
41 |
42 | Q_INVOKABLE void checkOTA() override;
43 | bool hasAvailableOTA() override;
44 | void setOtaVersion(const QString & version) override;
45 | QString otaVersion() override;
46 | Q_INVOKABLE void downloadOTAData() override;
47 | Q_INVOKABLE void setOTAData ( const QString& md5sum, const QByteArray& firmware ) override;
48 | bool hasOTAData() override;
49 | Q_INVOKABLE void startOTA() override;
50 | private:
51 | class Private;
52 | Private* d;
53 | };
54 |
55 | #endif//BTDEVICEMITAILMINI_H
56 |
--------------------------------------------------------------------------------
/src/icontheme-index.theme:
--------------------------------------------------------------------------------
1 | [Icon Theme]
2 | Name=Breeze
3 |
4 | Comment=Default Plasma 5 Theme
5 | DisplayDepth=32
6 |
7 | Inherits=hicolor
8 |
9 | Example=folder
10 |
11 | FollowsColorScheme=true
12 |
13 | DesktopDefault=22
14 | DesktopSizes=22
15 | ToolbarDefault=22
16 | ToolbarSizes=22
17 | MainToolbarDefault=22
18 | MainToolbarSizes=22
19 | SmallDefault=22
20 | SmallSizes=22
21 | PanelDefault=22
22 | PanelSizes=22
23 | DialogDefault=22
24 | DialogSizes=22
25 |
26 | KDE-Extensions=.svg
27 |
28 | Directories=actions/16,actions/22,actions/32,apps/22,devices/22,emblems/22,emotes/22,mimetypes/22,places/22,status/22,actions/symbolic,devices/symbolic,emblems/symbolic,places/symbolic,status/symbolic
29 |
30 | [actions/16]
31 | Size=16
32 | Context=Actions
33 | Type=Scalable
34 |
35 | [actions/22]
36 | Size=22
37 | Context=Actions
38 | Type=Scalable
39 |
40 | [actions/32]
41 | Size=32
42 | Context=Actions
43 | Type=Scalable
44 |
45 | [apps/22]
46 | Size=22
47 | Context=Applications
48 | Type=Scalable
49 |
50 | [categories/32]
51 | Size=32
52 | Context=Categories
53 | Type=Scalable
54 |
55 | [devices/22]
56 | Size=22
57 | Context=Devices
58 | Type=Scalable
59 |
60 | [emblems/22]
61 | Size=22
62 | Context=Emblems
63 | Type=Scalable
64 |
65 | [emotes/22]
66 | Size=22
67 | Context=Emotes
68 | Type=Scalable
69 |
70 | [mimetypes/22]
71 | Size=22
72 | Context=MimeTypes
73 | Type=Scalable
74 |
75 | [places/22]
76 | Size=22
77 | Context=Places
78 | Type=Fixed
79 |
80 | [status/22]
81 | Size=22
82 | Context=Status
83 | Type=Scalable
84 |
--------------------------------------------------------------------------------
/src/images/alarm.svg:
--------------------------------------------------------------------------------
1 |
2 |
66 |
--------------------------------------------------------------------------------
/src/images/banner_image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OpenTails/CRUMPET-Android/f540d6f35e62b208f8949cec2c3d21f10d94b60a/src/images/banner_image.png
--------------------------------------------------------------------------------
/src/images/casualmode.svg:
--------------------------------------------------------------------------------
1 |
2 |
60 |
--------------------------------------------------------------------------------
/src/images/crumpet-head.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
77 |
--------------------------------------------------------------------------------
/src/images/eargear.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/images/flutter.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/images/glowtip.svg:
--------------------------------------------------------------------------------
1 |
2 |
81 |
--------------------------------------------------------------------------------
/src/images/listeningmode.svg:
--------------------------------------------------------------------------------
1 |
2 |
67 |
--------------------------------------------------------------------------------
/src/images/mitailmini.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/images/moves.svg:
--------------------------------------------------------------------------------
1 |
2 |
61 |
--------------------------------------------------------------------------------
/src/images/tail.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
72 |
--------------------------------------------------------------------------------
/src/images/tail_lights.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
107 |
--------------------------------------------------------------------------------
/src/images/tail_moves.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
89 |
--------------------------------------------------------------------------------
/src/images/taildevice.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/qml/AboutPage.qml:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Dan Leinir Turthra Jensen
3 | * This file based on sample code from Kirigami
4 | *
5 | * This program is free software; you can redistribute it and/or modify
6 | * it under the terms of the GNU Library General Public License as
7 | * published by the Free Software Foundation; either version 3, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU Library General Public License for more details
14 | *
15 | * You should have received a copy of the GNU Library General Public License
16 | * along with this program; if not, see
17 | */
18 |
19 | import QtQuick
20 | import QtQuick.Controls
21 | import QtQuick.Layouts
22 | import org.kde.kirigami as Kirigami
23 | import org.thetailcompany.digitail as Digitail
24 |
25 | Kirigami.AboutPage {
26 | Digitail.FilterProxyModel {
27 | id: deviceFilterProxy;
28 | sourceModel: Digitail.DeviceModel;
29 | filterRole: Digitail.DeviceModelTypes.IsConnected;
30 | filterBoolean: true;
31 | function handyStringMakerThing() {
32 | if (count === 1) {
33 | return i18nc("A label which describes the hardware revision of the connected gear, when only one item is connected", "The connected gear is version %1", data(index(0, 0), Digitail.DeviceModelTypes.DeviceVersion))
34 | } else {
35 | var constructedString = "";
36 | var newLine = "";
37 | for (var i = 0; i < count; ++i) {
38 | constructedString += newLine + i18nc("A label which describes the hardware revision of a piece of connected gear, when more than one item is connected", "The gear named %1 is version %2", data(index(i, 0), Digitail.DeviceModelTypes.Name), data(index(i, 0), Digitail.DeviceModelTypes.DeviceVersion));
39 | newLine = "\n";
40 | }
41 | return constructedString;
42 | }
43 | }
44 | }
45 | objectName: "aboutPage";
46 | getInvolvedUrl: "https://github.com/OpenTails/CRUMPET-Android"
47 | aboutData: {
48 | "displayName" : "Crumpet",
49 | "productName" : "digitail/controller",
50 | "programLogo" : "qrc:/icon-round.png",
51 | "componentName" : "digitail",
52 | "shortDescription" : "Crumpet, MiTail, DIGITAiL, and EarGear designed and created by The Tail Company",
53 | "homepage" : "https://thetailcompany.com/",
54 | "bugAddress" : "contact@thetailcompany.com",
55 | "version" : AppVersion,
56 | "otherText" : deviceFilterProxy.count > 0 ? deviceFilterProxy.handyStringMakerThing() : "",
57 | "authors" : [
58 | {
59 | "name" : "MT at The Tail Company",
60 | // TODO Reenable this when the about page wraps the person delegate properly
61 | //"task" : i18nc("Task description for mt", ""),
62 | "emailAddress" : "contact@thetailcompany.com",
63 | "webAddress" : "https://thetailcompany.com/",
64 | },
65 | {
66 | "name" : "Dan Leinir Turthra Jensen",
67 | //"task" : i18nc("Task description for leinir", "Lead Developer"),
68 | "emailAddress" : "admin@leinir.dk",
69 | "webAddress" : "https://leinir.dk/",
70 | "ocsUsername" : "leinir"
71 | },
72 | {
73 | "name" : "Ildar Gilmanov",
74 | //"task" : i18nc("Task description for Ildar", "Qt/QML Developer"),
75 | "emailAddress" : "gil.ildar@gmail.com",
76 | "webAddress": ""
77 | }
78 | ],
79 | "credits" : [],
80 | "translators" : [],
81 | "licenses" : [
82 | {
83 | "name" : "GPL v3",
84 | "text" : GPLv3LicenseText,
85 | "spdx" : "GPL-3.0"
86 | }
87 | ],
88 | "copyrightStatement" : "© 2018-2024 The Tail Company Community",
89 | "desktopFileName" : "org.thetailcompany.digitail"
90 | }
91 | }
92 |
93 |
--------------------------------------------------------------------------------
/src/qml/AlarmEditor.qml:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Ildar Gilmanov
3 | *
4 | * This program is free software; you can redistribute it and/or modify
5 | * it under the terms of the GNU Library General Public License as
6 | * published by the Free Software Foundation; either version 3, or
7 | * (at your option) any later version.
8 | *
9 | * This program is distributed in the hope that it will be useful,
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | * GNU Library General Public License for more details
13 | *
14 | * You should have received a copy of the GNU Library General Public License
15 | * along with this program; if not, see
16 | */
17 |
18 | import QtQuick
19 | import QtQuick.Controls as QQC2
20 | import org.kde.kirigami as Kirigami
21 | import org.thetailcompany.digitail as Digitail
22 |
23 | BaseCommandListEditor {
24 | property var alarm: null;
25 | readonly property string alarmName: alarm ? alarm["name"] : "";
26 |
27 | objectName: "alarmEditor";
28 | title: alarmName;
29 | model: Digitail.AppSettings.activeAlarm["commands"];
30 |
31 | onAlarmNameChanged: {
32 | Digitail.AppSettings.setActiveAlarmName(alarmName);
33 | }
34 |
35 | onInsertCommand: function(insertAt, command, destinations) {
36 | if (!alarm) {
37 | return;
38 | }
39 |
40 | Digitail.AppSettings.addAlarmCommand(insertAt, command, destinations);
41 | }
42 |
43 | onRemoveCommand: function(index) {
44 | if (!alarm) {
45 | return;
46 | }
47 |
48 | Digitail.AppSettings.removeAlarmCommand(index);
49 | }
50 |
51 | Component.onCompleted: {
52 | Digitail.AppSettings.setActiveAlarmName(alarmName);
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/qml/BasicListItem.qml:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2024 Dan Leinir Turthra Jensen
3 | * This file based on sample code from Kirigami
4 | *
5 | * This program is free software; you can redistribute it and/or modify
6 | * it under the terms of the GNU Library General Public License as
7 | * published by the Free Software Foundation; either version 3, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU Library General Public License for more details
14 | *
15 | * You should have received a copy of the GNU Library General Public License
16 | * along with this program; if not, see
17 | */
18 |
19 | import QtQuick
20 | import QtQuick.Controls
21 | import QtQuick.Layouts
22 | import org.kde.kirigami as Kirigami
23 |
24 | ItemDelegate {
25 | id: component
26 | property alias bold: textLabel.font.bold
27 | contentItem: RowLayout {
28 | spacing: Kirigami.Units.largeSpacing
29 | Kirigami.Icon {
30 | implicitHeight: Kirigami.Units.iconSizes.medium
31 | implicitWidth: Kirigami.Units.iconSizes.medium
32 | source: component.icon.source
33 | }
34 | Label {
35 | id: textLabel
36 | text: component.text
37 | Layout.fillWidth: true
38 | horizontalAlignment: Text.AlignLeft
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/qml/CommandPausePicker.qml:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Dan Leinir Turthra Jensen
3 | * Copyright 2019 Ildar Gilmanov
4 | * This file based on sample code from Kirigami
5 | *
6 | * This program is free software; you can redistribute it and/or modify
7 | * it under the terms of the GNU Library General Public License as
8 | * published by the Free Software Foundation; either version 3, or
9 | * (at your option) any later version.
10 | *
11 | * This program is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU Library General Public License for more details
15 | *
16 | * You should have received a copy of the GNU Library General Public License
17 | * along with this program; if not, see
18 | */
19 |
20 | import QtQuick
21 | import QtQuick.Controls as QQC2
22 | import org.kde.kirigami as Kirigami
23 |
24 | Kirigami.OverlaySheet {
25 | id: control;
26 |
27 | property int insertAt;
28 |
29 | signal durationPicked(int duration);
30 | signal insertCommand(int insertAt, string command);
31 |
32 | function pickDuration() {
33 | durationSlider.value = 10;
34 | open();
35 | }
36 |
37 | onDurationPicked: function(duration) {
38 | control.insertCommand(insertAt, "pause:" + duration);
39 | }
40 |
41 | parent: QQC2.Overlay.overlay
42 | showCloseButton: true
43 | title: i18nc("Heading for an overlay for picking the duration of a pause in the command list", "Pick the duration of your pause");
44 |
45 | Column {
46 | QQC2.Slider {
47 | id: durationSlider;
48 |
49 | width: control.width - Kirigami.Units.largeSpacing * 4;
50 | from: 1;
51 | to: 300;
52 | stepSize: 1;
53 | snapMode: QQC2.Slider.SnapAlways;
54 | }
55 |
56 | QQC2.Label {
57 | width: control.width - Kirigami.Units.largeSpacing * 4;
58 | text: i18nc("A label which describes the slider for the duration of a pause, in the overlay for picking the duration of a pause, in a command list", "%1 seconds", durationSlider.value);
59 | }
60 |
61 | QQC2.Button {
62 | text: i18nc("Button for an overlay for picking the duration of a pause in the command list", "Add this pause");
63 |
64 | width: control.width - Kirigami.Units.largeSpacing * 4;
65 | onClicked: {
66 | control.durationPicked(durationSlider.value);
67 | }
68 | }
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/src/qml/DeveloperModePage.qml:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Ildar Gilmanov
3 | * This file based on sample code from Kirigami
4 | *
5 | * This program is free software; you can redistribute it and/or modify
6 | * it under the terms of the GNU Library General Public License as
7 | * published by the Free Software Foundation; either version 3, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU Library General Public License for more details
14 | *
15 | * You should have received a copy of the GNU Library General Public License
16 | * along with this program; if not, see
17 | */
18 |
19 | import QtQuick
20 | import QtQuick.Controls as QQC2
21 | import org.kde.kirigami as Kirigami
22 | import QtQuick.Layouts
23 | import QtMultimedia
24 |
25 | Kirigami.ScrollablePage {
26 | objectName: "developerModePage";
27 | title: i18nc("Header for the Developer Mode page", "Developer Mode");
28 |
29 | Column {
30 | width: root.width - Kirigami.Units.largeSpacing * 4;
31 | spacing: Kirigami.Units.largeSpacing;
32 |
33 | QQC2.Button {
34 | text: i18nc("Button with a sound effect for the Developer Mode Page", "MAX GLASH");
35 |
36 | Layout.fillWidth: true;
37 |
38 | MediaPlayer {
39 | id: playSound
40 | source: "qrc:/audio/Sparkle-sound-effect.mp3"
41 | }
42 |
43 | onClicked: {
44 | playSound.play()
45 | }
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/qml/EarPoses.qml:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 Dan Leinir Turthra Jensen
3 | * This file based on sample code from Kirigami
4 | *
5 | * This program is free software; you can redistribute it and/or modify
6 | * it under the terms of the GNU Library General Public License as
7 | * published by the Free Software Foundation; either version 3, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU Library General Public License for more details
14 | *
15 | * You should have received a copy of the GNU Library General Public License
16 | * along with this program; if not, see
17 | */
18 |
19 | import QtQuick
20 | import QtQuick.Layouts
21 | import QtQuick.Controls as QQC2
22 | import org.kde.kirigami as Kirigami
23 | import org.thetailcompany.digitail as Digitail
24 |
25 | Kirigami.ScrollablePage {
26 | objectName: "earPoses";
27 | title: i18nc("Title for the page for selecting a pose for the EarGear", "Ear Poses");
28 | actions: [
29 | Kirigami.Action {
30 | text: i18nc("Button for returning the EarGear to the home position, on the page for selecting a pose for the EarGear", "Home Position");
31 | icon.name: "go-home";
32 | displayHint: Kirigami.DisplayHint.KeepVisible;
33 | onTriggered: {
34 | Digitail.BTConnectionManager.sendMessage("TAILHM", []);
35 | }
36 | }
37 | ]
38 | BaseMovesComponent {
39 | infoText: i18nc("Description for the page for selecting a pose for the EarGear", "The list below shows all the poses available to your gear. Tap any of them to send them off to any of your connected devices! If you have more than one connected, the little coloured dots show which you can send that pose to.");
40 | infoFooter: RowLayout {
41 | QQC2.Button {
42 | text: i18nc("Label for the button for setting EarGear moves to run more slowly", "Be Calm")
43 | Layout.fillWidth: true
44 | Layout.preferredWidth: Kirigami.Units.gridUnit * 10
45 | onClicked: {
46 | Digitail.BTConnectionManager.sendMessage("SPEED SLOW", []);
47 | }
48 | }
49 | QQC2.Button {
50 | text: i18nc("Label for the button for setting EarGear moves to run faster", "Be Excited")
51 | Layout.fillWidth: true
52 | Layout.preferredWidth: Kirigami.Units.gridUnit * 10
53 | onClicked: {
54 | Digitail.BTConnectionManager.sendMessage("SPEED FAST", []);
55 | }
56 | }
57 | }
58 | onCommandActivated: function(command, destinations) {
59 | Digitail.CommandQueue.clear("");
60 | Digitail.CommandQueue.pushCommand(command, destinations);
61 | }
62 | categoriesModel: [
63 | {
64 | name: i18nc("Heading for the list of poses, on the page for selecting a pose for the EarGear", "Poses"),
65 | category: "eargearposes",
66 | color: "#93cee9",
67 | }
68 | ]
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/src/qml/FlatButton.qml:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Ildar Gilmanov
3 | *
4 | * This program is free software; you can redistribute it and/or modify
5 | * it under the terms of the GNU Library General Public License as
6 | * published by the Free Software Foundation; either version 3, or
7 | * (at your option) any later version.
8 | *
9 | * This program is distributed in the hope that it will be useful,
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | * GNU Library General Public License for more details
13 | *
14 | * You should have received a copy of the GNU Library General Public License
15 | * along with this program; if not, see
16 | */
17 |
18 | import QtQuick
19 | import QtQuick.Controls
20 | import QtQuick.Layouts
21 | import QtQuick.Controls.Material
22 |
23 | Button {
24 | id: control
25 |
26 | property alias textColor: controlText.color
27 | property alias textAlignment: controlText.horizontalAlignment
28 |
29 | focusPolicy: Qt.NoFocus
30 |
31 | leftPadding: 6
32 | rightPadding: 6
33 |
34 | Layout.fillWidth: true
35 | Layout.preferredWidth: 1
36 |
37 | contentItem: Text {
38 | id: controlText
39 |
40 | text: control.text
41 | color: Material.foreground
42 | opacity: enabled ? 1.0 : 0.3
43 | horizontalAlignment: Text.AlignHCenter
44 | verticalAlignment: Text.AlignVCenter
45 | elide: Text.ElideRight
46 | font.capitalization: Font.AllUppercase
47 | font.weight: Font.Medium
48 | }
49 |
50 | background: Rectangle {
51 | color: control.pressed ? controlText.color : "transparent"
52 | opacity: control.pressed ? 0.12 : 1.0
53 | radius: 2
54 | implicitHeight: 48
55 | Layout.minimumWidth: 88
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/qml/IdleModePage.qml:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Dan Leinir Turthra Jensen
3 | * This file based on sample code from Kirigami
4 | *
5 | * This program is free software; you can redistribute it and/or modify
6 | * it under the terms of the GNU Library General Public License as
7 | * published by the Free Software Foundation; either version 3, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU Library General Public License for more details
14 | *
15 | * You should have received a copy of the GNU Library General Public License
16 | * along with this program; if not, see
17 | */
18 |
19 | import QtQuick
20 | import QtQuick.Controls
21 | import org.kde.kirigami as Kirigami
22 | import org.thetailcompany.digitail as Digitail
23 |
24 | Kirigami.ScrollablePage {
25 | objectName: "idleMode";
26 | title: i18nc("Title for the Casual Mode settings page", "Casual Mode Categories");
27 |
28 | Component {
29 | id: idleCategoryDelegate;
30 | BasicListItem {
31 | icon.source: Digitail.AppSettings.idleCategories.indexOf(modelData["category"]) >= 0 ? "qrc:/icons/breeze-internal/emblems/16/checkbox-checked" : "qrc:/icons/breeze-internal/emblems/16/checkbox-unchecked";
32 | text: modelData["name"];
33 | onClicked: {
34 | var idx = Digitail.AppSettings.idleCategories.indexOf(modelData["category"]);
35 | if(idx >= 0) {
36 | Digitail.AppSettings.removeIdleCategory(modelData["category"]);
37 | }
38 | else {
39 | Digitail.AppSettings.addIdleCategory(modelData["category"]);
40 | }
41 | }
42 | }
43 | }
44 | property var availableCategories: [
45 | {
46 | name: i18nc("Description for the category for the Relaxed Moveset, on the Casual Mode settings page", "Calm and Relaxed"),
47 | category: "relaxed",
48 | color: "#1cdc9a",
49 | },
50 | {
51 | name: i18nc("Description for the category for the Excited Moveset, on the Casual Mode settings page", "Fast and Excited"),
52 | category: "excited",
53 | color: "#c9ce3b",
54 | },
55 | {
56 | name: i18nc("Description for the category for the Tense Moveset, on the Casual Mode settings page", "Frustrated and Tense"),
57 | category: "tense",
58 | color: "#f67400",
59 | },
60 | {
61 | name: i18nc("Description for the category for the LED Patterns, on the Casual Mode settings page", "LED Patterns"),
62 | category: "lights",
63 | color: "#93cee9",
64 | }
65 | ]
66 |
67 | ListView {
68 | model: availableCategories;
69 | delegate: idleCategoryDelegate;
70 |
71 | header: InfoCard {
72 | text: i18nc("Infocard for selecting the pauses in between moves, on the Casual Mode settings page", "This is where you set the pauses in between moves, as well as the families of moves that can be called upon.");
73 | footer: BasicListItem {
74 | text: i18nc("Label for the button for enabling the Casual Mode, on the Casual Mode settings page", "Enable Casual Mode");
75 | icon.source: (Digitail.AppSettings !== null && Digitail.AppSettings.idleMode) ? "qrc:/icons/breeze-internal/emblems/16/checkbox-checked" : "qrc:/icons/breeze-internal/emblems/16/checkbox-unchecked";
76 | onClicked: { Digitail.AppSettings.idleMode = !Digitail.AppSettings.idleMode; }
77 | }
78 | }
79 | footer: Kirigami.AbstractCard {
80 | contentItem: IdlePauseRangePicker {
81 | }
82 |
83 | anchors {
84 | left: parent.left;
85 | right: parent.right;
86 | }
87 | }
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/src/qml/IdlePauseRangePicker.qml:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2018 Dan Leinir Turthra Jensen
3 | * Copyright 2019 Ildar Gilmanov
4 | * This file based on sample code from Kirigami
5 | *
6 | * This program is free software; you can redistribute it and/or modify
7 | * it under the terms of the GNU Library General Public License as
8 | * published by the Free Software Foundation; either version 3, or
9 | * (at your option) any later version.
10 | *
11 | * This program is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU Library General Public License for more details
15 | *
16 | * You should have received a copy of the GNU Library General Public License
17 | * along with this program; if not, see
18 | */
19 |
20 | import QtQuick
21 | import QtQuick.Controls
22 | import QtQuick.Layouts
23 |
24 | import org.kde.kirigami as Kirigami
25 | import org.thetailcompany.digitail as Digitail
26 |
27 | ColumnLayout {
28 | Layout.fillWidth: true;
29 | Label {
30 | text: i18nc("Label for selecting the range of pauses in between moves in Casual Mode", "Range of pause between moves in seconds");
31 | wrapMode: Text.Wrap;
32 | Layout.fillWidth: true;
33 | }
34 |
35 | RangeSlider {
36 | id: pauseRangeSlider;
37 |
38 | first.value: Digitail.AppSettings.idleMinPause;
39 | second.value: Digitail.AppSettings.idleMaxPause;
40 | from: 15;
41 | to: 600;
42 | stepSize: 1.0;
43 | Layout.leftMargin: Kirigami.Units.largeSpacing;
44 | Layout.fillWidth: true;
45 |
46 | first.onMoved: {
47 | Digitail.AppSettings.idleMinPause = first.value;
48 | }
49 | second.onMoved: {
50 | if (second.value < 20) {
51 | second.value = 20;
52 | }
53 | Digitail.AppSettings.idleMaxPause = second.value;
54 | }
55 |
56 | function handlePressed() {
57 | if (first.pressed || second.pressed) {
58 | applicationWindow().pageStack.interactive = false;
59 | } else {
60 | applicationWindow().pageStack.interactive = true;
61 | }
62 | }
63 | first.onPressedChanged: {
64 | handlePressed();
65 | }
66 | second.onPressedChanged: {
67 | handlePressed();
68 | }
69 |
70 | Component.onCompleted: {
71 | pauseRangeSlider.setValues(Digitail.AppSettings.idleMinPause, Digitail.AppSettings.idleMaxPause);
72 | }
73 | }
74 |
75 | Item {
76 | height: Kirigami.Units.iconSizes.medium
77 | Layout.leftMargin: Kirigami.Units.largeSpacing;
78 | Layout.fillWidth: true;
79 |
80 | Label {
81 | text: Math.floor(pauseRangeSlider.first.value);
82 |
83 | anchors {
84 | left: parent.left;
85 | right: parent.horizontalCentre;
86 | }
87 | }
88 |
89 | Label {
90 | text: Math.floor(pauseRangeSlider.second.value);
91 |
92 | anchors {
93 | left: parent.horizontalCentre;
94 | right: parent.right;
95 | }
96 | }
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/src/qml/InfoCard.qml:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Dan Leinir Turthra Jensen
3 | *
4 | * This program is free software; you can redistribute it and/or modify
5 | * it under the terms of the GNU Library General Public License as
6 | * published by the Free Software Foundation; either version 3, or
7 | * (at your option) any later version.
8 | *
9 | * This program is distributed in the hope that it will be useful,
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | * GNU Library General Public License for more details
13 | *
14 | * You should have received a copy of the GNU Library General Public License
15 | * along with this program; if not, see
16 | */
17 |
18 | import QtQuick
19 | import QtQuick.Controls as QQC2
20 | import QtQuick.Layouts
21 | import org.kde.kirigami as Kirigami
22 |
23 | ColumnLayout {
24 | id: root;
25 | property alias text: infoCardText.text;
26 | property alias footer: card.footer;
27 | width: parent.width;
28 | height: card.height + Kirigami.Units.largeSpacing * 3;
29 | Kirigami.Card {
30 | id: card
31 | Layout.fillWidth: true;
32 | Layout.margins: Kirigami.Units.largeSpacing;
33 | contentItem: QQC2.Label {
34 | id: infoCardText;
35 | padding: Kirigami.Units.smallSpacing;
36 | wrapMode: Text.Wrap;
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/qml/MessageBox.qml:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Ildar Gilmanov
3 | * This file based on sample code from Kirigami
4 | *
5 | * This program is free software; you can redistribute it and/or modify
6 | * it under the terms of the GNU Library General Public License as
7 | * published by the Free Software Foundation; either version 3, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU Library General Public License for more details
14 | *
15 | * You should have received a copy of the GNU Library General Public License
16 | * along with this program; if not, see
17 | */
18 |
19 | import QtQuick
20 | import QtQuick.Controls as QQC2
21 | import QtQuick.Layouts
22 | import org.kde.kirigami as Kirigami
23 |
24 | Kirigami.OverlaySheet {
25 | id: control;
26 |
27 | property alias headerText: textHeader.text;
28 | property alias text: label.text;
29 | property var okHandler: null;
30 |
31 | function showMessageBox(header, text, okHandler) {
32 | control.headerText = header;
33 | control.text = text;
34 | control.okHandler = okHandler;
35 | buttonCancel.visible = !!okHandler;
36 | open();
37 | }
38 |
39 | parent: QQC2.Overlay.overlay
40 | header: Kirigami.Heading {
41 | id: textHeader;
42 | wrapMode: Text.Wrap;
43 | }
44 |
45 | footer: RowLayout {
46 | Layout.fillWidth: true
47 |
48 | QQC2.Button {
49 | text: i18nc("Confirmation button for a generic message", "Ok");
50 | highlighted: true;
51 | Layout.fillWidth: true
52 | Layout.preferredWidth: applicationWindow().width
53 |
54 | onClicked: {
55 | if (okHandler) {
56 | var handler = okHandler;
57 | okHandler = null;
58 | close();
59 | handler();
60 | } else {
61 | close();
62 | }
63 | }
64 | }
65 |
66 | QQC2.Button {
67 | id: buttonCancel
68 | text: i18nc("Cancel button for a generic message", "Cancel");
69 | Layout.fillWidth: true
70 | Layout.preferredWidth: applicationWindow().width
71 |
72 | onClicked: {
73 | close();
74 | }
75 | }
76 | }
77 |
78 | QQC2.Label {
79 | id: label;
80 | Layout.fillWidth: true;
81 | wrapMode: Text.Wrap;
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/src/qml/MoveListEditor.qml:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Dan Leinir Turthra Jensen
3 | * Copyright 2019 Ildar Gilmanov
4 | *
5 | * This program is free software; you can redistribute it and/or modify
6 | * it under the terms of the GNU Library General Public License as
7 | * published by the Free Software Foundation; either version 3, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU Library General Public License for more details
14 | *
15 | * You should have received a copy of the GNU Library General Public License
16 | * along with this program; if not, see
17 | */
18 |
19 | import QtQuick
20 | import QtQuick.Layouts
21 | import QtQuick.Controls as QQC2
22 | import org.kde.kirigami as Kirigami
23 | import org.thetailcompany.digitail as Digitail
24 |
25 | BaseCommandListEditor {
26 | property string moveListName;
27 |
28 | objectName: "moveListEditor";
29 | title: moveListName;
30 | model: Digitail.AppSettings.moveList
31 | infoCardFooter: QQC2.Button {
32 | text: i18nc("Label for the button for running a Move List, on the Move List Editor page", "Run Move List")
33 | Layout.fillWidth: true
34 | onClicked: {
35 | showMessageBox(i18nc("Heading for the confirmation prompt for running a Move List, on the Move List Editor page", "Run this list?"),
36 | i18nc("Message for the confirmation prompt for running a Move List, on the Move List Editor page", "Do you want to run this list?"),
37 | function() {
38 | Digitail.CommandQueue.pushCommands(Digitail.AppSettings.moveList, []);
39 | });
40 | }
41 | }
42 |
43 | onMoveListNameChanged: function() {
44 | Digitail.AppSettings.setActiveMoveList(moveListName);
45 | }
46 |
47 | onInsertCommand: function(insertAt, command, destinations) {
48 | Digitail.AppSettings.addMoveListEntry(insertAt, command, destinations);
49 | }
50 | onRemoveCommand: function(index) {
51 | showMessageBox(i18nc("Header for the confirmation prompt for removing a Move List Entry, on the Move List Editor page", "Remove Move List Entry?"),
52 | i18nc("Message for the confirmation prompt for removing a Move List Entry, on the Move List Editor page", "Are you sure that you want to remove this entry from your move list?"),
53 | function () {
54 | Digitail.AppSettings.removeMoveListEntry(index);
55 | });
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/qml/NamePicker.qml:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Dan Leinir Turthra Jensen
3 | * Copyright 2019 Ildar Gilmanov
4 | * This file based on sample code from Kirigami
5 | *
6 | * This program is free software; you can redistribute it and/or modify
7 | * it under the terms of the GNU Library General Public License as
8 | * published by the Free Software Foundation; either version 3, or
9 | * (at your option) any later version.
10 | *
11 | * This program is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU Library General Public License for more details
15 | *
16 | * You should have received a copy of the GNU Library General Public License
17 | * along with this program; if not, see
18 | */
19 |
20 | import QtQuick
21 | import QtQuick.Controls as QQC2
22 | import QtQuick.Layouts
23 | import org.kde.kirigami as Kirigami
24 |
25 | Kirigami.OverlaySheet {
26 | id: control;
27 |
28 | property alias headerText: control.title
29 | property alias description: textDescription.text;
30 | property alias placeholderText: enteredName.placeholderText;
31 | property alias buttonOkText: buttonOk.text;
32 |
33 | signal namePicked(string name);
34 |
35 | function pickName() {
36 | enteredName.text = "";
37 | open();
38 | }
39 |
40 | parent: QQC2.Overlay.overlay
41 | showCloseButton: true
42 | title: i18nc("Text header for picking a name", "Pick a name");
43 |
44 | footer: QQC2.Button {
45 | id: buttonOk;
46 |
47 | // The check below is done to ensure we do not have to wait for the text to be accepted
48 | // before enabling the button. The text will still be entered, but both need to be checked
49 | // to ensure the expected behaviour (as the actual text does not get entered until the
50 | // text box loses focus or the input is accepted).
51 | enabled: (enteredName.preeditText + enteredName.text).length;
52 |
53 | onClicked: {
54 | control.namePicked(enteredName.text);
55 | }
56 | }
57 |
58 | ColumnLayout {
59 | Layout.fillWidth: true
60 | spacing: Kirigami.Units.smallSpacing;
61 | QQC2.Label {
62 | id: textDescription;
63 | Layout.fillWidth: true
64 | wrapMode: Text.Wrap;
65 | }
66 | QQC2.TextField {
67 | id: enteredName;
68 | Layout.fillWidth: true
69 | }
70 | Item {
71 | Layout.fillWidth: true
72 | Layout.minimumHeight: Kirigami.Units.smallSpacing
73 | }
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/src/qml/PickACommandSheet.qml:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Dan Leinir Turthra Jensen
3 | * This file based on sample code from Kirigami
4 | *
5 | * This program is free software; you can redistribute it and/or modify
6 | * it under the terms of the GNU Library General Public License as
7 | * published by the Free Software Foundation; either version 3, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU Library General Public License for more details
14 | *
15 | * You should have received a copy of the GNU Library General Public License
16 | * along with this program; if not, see
17 | */
18 |
19 | import QtQuick
20 | import QtQuick.Controls
21 | import org.kde.kirigami as Kirigami
22 | import org.thetailcompany.digitail as Digitail
23 |
24 | Kirigami.OverlaySheet {
25 | id: root;
26 |
27 | function pickCommand() {
28 | open();
29 | }
30 | signal commandPicked(string command, variant destinations);
31 | showCloseButton: true
32 | title: i18nc("Heading for the overlay for picking a command, for configuring a move list", "Pick a command");
33 | parent: Overlay.overlay
34 | BaseMovesComponent {
35 | infoText: i18nc("Description for the overlay for picking a command, for configuring a move list", "The list below shows all the moves and light patterns available to your gear. Tap any of them to pick that command.");
36 | width: root.width;
37 | ignoreAvailability: true;
38 | onCommandActivated: function(command, destinations) {
39 | pickACommand.commandPicked(command, destinations);
40 | }
41 | categoriesModel: connectedWithLightsModel.count > 0
42 | ? [
43 | {
44 | name: i18nc("Description for the category for the Ear Gear poses, on the overlay for picking a command, for configuring a move list", "Poses"),
45 | category: "eargearposes",
46 | color: "#93cee9",
47 | },
48 | {
49 | name: i18nc("Description for the category for the Relaxed Moveset, on the overlay for picking a command, for configuring a move list", "Calm and Relaxed"),
50 | category: "relaxed",
51 | color: "#1cdc9a",
52 | },
53 | {
54 | name: i18nc("Description for the category for the Excited Moveset, on the overlay for picking a command, for configuring a move list", "Fast and Excited"),
55 | category: "excited",
56 | color: "#c9ce3b",
57 | },
58 | {
59 | name: i18nc("Description for the category for the Tense Moveset, on the overlay for picking a command, for configuring a move list", "Frustrated and Tense"),
60 | category: "tense",
61 | color: "#f67400",
62 | },
63 | {
64 | name: i18nc("Description for the category for the LED Patterns, on the overlay for picking a command, for configuring a move list", "LED Patterns"),
65 | category: "lights",
66 | color: "#93cee9",
67 | }
68 | ]
69 | : [
70 | {
71 | name: i18nc("Description for the category for the Ear Gear poses, on the overlay for picking a command, for configuring a move list", "Poses"),
72 | category: "eargearposes",
73 | color: "#93cee9",
74 | },
75 | {
76 | name: i18nc("Description for the category for the Relaxed Moveset, on the overlay for picking a command, for configuring a move list", "Calm and Relaxed"),
77 | category: "relaxed",
78 | color: "#1cdc9a",
79 | },
80 | {
81 | name: i18nc("Description for the category for the Excited Moveset, on the overlay for picking a command, for configuring a move list", "Fast and Excited"),
82 | category: "excited",
83 | color: "#c9ce3b",
84 | },
85 | {
86 | name: i18nc("Description for the category for the Tense Moveset, on the overlay for picking a command, for configuring a move list", "Frustrated and Tense"),
87 | category: "tense",
88 | color: "#f67400",
89 | }
90 | ]
91 | Digitail.FilterProxyModel {
92 | id: connectedWithLightsModel;
93 | sourceModel: connectedDevicesModel;
94 | filterRole: Digitail.DeviceModelTypes.HasLights;
95 | filterBoolean: true;
96 | }
97 | Digitail.FilterProxyModel {
98 | id: connectedDevicesModel
99 | sourceModel: Digitail.DeviceModel;
100 | filterRole: Digitail.DeviceModelTypes.IsConnected;
101 | filterBoolean: true;
102 | }
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/src/qml/SettingsCard.qml:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Dan Leinir Turthra Jensen
3 | *
4 | * This program is free software; you can redistribute it and/or modify
5 | * it under the terms of the GNU Library General Public License as
6 | * published by the Free Software Foundation; either version 3, or
7 | * (at your option) any later version.
8 | *
9 | * This program is distributed in the hope that it will be useful,
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | * GNU Library General Public License for more details
13 | *
14 | * You should have received a copy of the GNU Library General Public License
15 | * along with this program; if not, see
16 | */
17 |
18 | import QtQuick
19 | import QtQuick.Controls as QQC2
20 | import QtQuick.Layouts
21 | import org.kde.kirigami as Kirigami
22 |
23 | Kirigami.AbstractCard {
24 | property alias headerText: headerLabel.text;
25 | property alias descriptionText: descriptionLabel.text;
26 |
27 | header: Kirigami.Heading {
28 | id: headerLabel;
29 | level: 2
30 | padding: Kirigami.Units.smallSpacing;
31 | wrapMode: Text.Wrap;
32 | }
33 | contentItem: QQC2.Label {
34 | id: descriptionLabel;
35 | padding: Kirigami.Units.smallSpacing;
36 | wrapMode: Text.Wrap;
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/qml/TailLights.qml:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Dan Leinir Turthra Jensen
3 | * This file based on sample code from Kirigami
4 | *
5 | * This program is free software; you can redistribute it and/or modify
6 | * it under the terms of the GNU Library General Public License as
7 | * published by the Free Software Foundation; either version 3, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU Library General Public License for more details
14 | *
15 | * You should have received a copy of the GNU Library General Public License
16 | * along with this program; if not, see
17 | */
18 |
19 | import QtQuick
20 | import org.kde.kirigami as Kirigami
21 | import org.thetailcompany.digitail as Digitail
22 |
23 | Kirigami.ScrollablePage {
24 | objectName: "tailLights";
25 | title: i18nc("Header for the page for the Glow Tips", "Glow Tips");
26 | actions: [
27 | Kirigami.Action {
28 | text: i18nc("Button for stopping the light patterns, on the page for the Glow Tips", "Stop Lights");
29 | icon.name: "flashlight-off";
30 | displayHint: Kirigami.DisplayHint.KeepVisible;
31 | onTriggered: {
32 | Digitail.BTConnectionManager.sendMessage("LEDOFF", []);
33 | }
34 | }
35 | ]
36 | BaseMovesComponent {
37 | width: parent.width;
38 | infoText: i18nc("Description for the list of light patterns, on the page for the Glow Tips", "The list below shows all the light patterns available to your gear. Tap any of them to send them off to any of your connected devices! If you have more than one connected, the little coloured dots show which you can send that light pattern to.");
39 | onCommandActivated: function(command, destinations) {
40 | Digitail.CommandQueue.clear("");
41 | Digitail.CommandQueue.pushCommand(command, destinations);
42 | }
43 | categoriesModel: [
44 | {
45 | name: i18nc("List element for the light patterns, on the page for the Glow Tips", "LED Patterns"),
46 | category: "lights",
47 | color: "#93cee9",
48 | }
49 | ]
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/qml/TailMoves.qml:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Dan Leinir Turthra Jensen
3 | * This file based on sample code from Kirigami
4 | *
5 | * This program is free software; you can redistribute it and/or modify
6 | * it under the terms of the GNU Library General Public License as
7 | * published by the Free Software Foundation; either version 3, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU Library General Public License for more details
14 | *
15 | * You should have received a copy of the GNU Library General Public License
16 | * along with this program; if not, see
17 | */
18 |
19 | import QtQuick
20 | import org.kde.kirigami as Kirigami
21 | import org.thetailcompany.digitail as Digitail
22 |
23 | Kirigami.ScrollablePage {
24 | objectName: "tailMoves";
25 | title: i18nc("Heading for the Moves page", "Moves");
26 | actions: [
27 | Kirigami.Action {
28 | text: i18nc("Button for returning the tail to its home position, on the Moves page", "Home Position");
29 | icon.name: "dialog-cancel";
30 | displayHint: Kirigami.DisplayHint.KeepVisible;
31 | onTriggered: {
32 | Digitail.BTConnectionManager.sendMessage("TAILHM", []);
33 | }
34 | }
35 | ]
36 | BaseMovesComponent {
37 | infoText: i18nc("Description for the list of moves, on the Moves page", "The list below shows all the moves available to your gear. Tap any of them to send them off to any of your connected devices! If you have more than one connected, the little coloured dots show which you can send that move to.");
38 | onCommandActivated: function(command, destinations) {
39 | Digitail.CommandQueue.clear("");
40 | Digitail.CommandQueue.pushCommand(command, destinations);
41 | }
42 | categoriesModel: [
43 | {
44 | name: i18nc("Description for the category for the Relaxed Moveset, on the Moves page", "Calm and Relaxed"),
45 | category: "relaxed",
46 | color: "#1cdc9a",
47 | },
48 | {
49 | name: i18nc("Description for the category for the Excited Moveset, on the Moves page", "Fast and Excited"),
50 | category: "excited",
51 | color: "#c9ce3b",
52 | },
53 | {
54 | name: i18nc("Description for the category for the Tense Moveset, on the Moves page", "Frustrated and Tense"),
55 | category: "tense",
56 | color: "#f67400",
57 | }
58 | ]
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/src/qtquickcontrols2.conf:
--------------------------------------------------------------------------------
1 | ; This file can be edited to change the style of the application
2 | ; See Styling Qt Quick Controls 2 in the documentation for details:
3 | ; http://doc.qt.io/qt-5/qtquickcontrols2-styles.html
4 |
5 | [Controls]
6 | Style=Material
7 |
8 | [Universal]
9 | Theme=Light
10 | ;Accent=Steel
11 |
12 | [Material]
13 | Theme=Light
14 | Accent=BlueGrey
15 | Primary=BlueGrey
16 |
--------------------------------------------------------------------------------
/src/resources.qrc:
--------------------------------------------------------------------------------
1 |
2 |
3 | ../LICENSE
4 | qml/main.qml
5 | qml/AboutPage.qml
6 | qml/BaseMovesComponent.qml
7 | qml/ConnectToTail.qml
8 | qml/IdleModePage.qml
9 | qml/NotConnectedCard.qml
10 | qml/MoveListEditor.qml
11 | qml/MoveLists.qml
12 | qml/PickACommandSheet.qml
13 | qml/TailLights.qml
14 | qml/TailMoves.qml
15 | qml/WelcomePage.qml
16 | qml/TailBattery.qml
17 | qml/AlarmList.qml
18 | qtquickcontrols2.conf
19 | images/logo.svg
20 | images/banner_image.png
21 | ../data/res/mipmap-xxxhdpi/ic_launcher_round.png
22 | qml/NamePicker.qml
23 | qml/MessageBox.qml
24 | qml/AlarmEditor.qml
25 | qml/BaseCommandListEditor.qml
26 | qml/CommandPausePicker.qml
27 | qml/DatePicker.qml
28 | qml/TimePicker.qml
29 | qml/FlatButton.qml
30 | qml/IdlePauseRangePicker.qml
31 | qml/InfoCard.qml
32 | qml/SettingsPage.qml
33 | qml/SettingsCard.qml
34 | qml/SettingsCrumpetPicker.qml
35 | qml/DeveloperModePage.qml
36 | qml/EarPoses.qml
37 | qml/GearGestures.qml
38 | qml/DisconnectOptions.qml
39 | qml/TiltSettings.qml
40 | qml/ListenSettings.qml
41 | qml/BasicListItem.qml
42 | audio/Sparkle-sound-effect.mp3
43 | commands/digitail-builtin.crumpet
44 | commands/mitail-builtin.crumpet
45 | commands/mitail-lights-builtin.crumpet
46 | commands/mitailmini-builtin.crumpet
47 | commands/mitailmini-lights-builtin.crumpet
48 | commands/eargear-base.crumpet
49 | commands/eargear2-base.crumpet
50 | images/alarm.svg
51 | images/crumpet-head.svg
52 | images/casualmode.svg
53 | images/earposes.svg
54 | images/glowtip.svg
55 | images/listeningmode.svg
56 | images/movelist.svg
57 | images/moves.svg
58 | images/tiltmode.svg
59 | images/tail.svg
60 | images/taildevice.svg
61 | images/eargear.svg
62 | images/flutter.svg
63 | images/mitailmini.svg
64 |
65 |
66 |
--------------------------------------------------------------------------------
/src/video/readme:
--------------------------------------------------------------------------------
1 | A square 1024 x 1024 pixel intro video, featuring Crumpet the Fox
2 |
--------------------------------------------------------------------------------
/src/video/squarelo.mpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OpenTails/CRUMPET-Android/f540d6f35e62b208f8949cec2c3d21f10d94b60a/src/video/squarelo.mpeg
--------------------------------------------------------------------------------
/wiki/LEDUS1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OpenTails/CRUMPET-Android/f540d6f35e62b208f8949cec2c3d21f10d94b60a/wiki/LEDUS1.jpg
--------------------------------------------------------------------------------
/wiki/Serial Bluetooth Terminal.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OpenTails/CRUMPET-Android/f540d6f35e62b208f8949cec2c3d21f10d94b60a/wiki/Serial Bluetooth Terminal.jpg
--------------------------------------------------------------------------------
/wiki/USERMOVE graphical example.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OpenTails/CRUMPET-Android/f540d6f35e62b208f8949cec2c3d21f10d94b60a/wiki/USERMOVE graphical example.jpg
--------------------------------------------------------------------------------
/wiki/VER.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OpenTails/CRUMPET-Android/f540d6f35e62b208f8949cec2c3d21f10d94b60a/wiki/VER.jpg
--------------------------------------------------------------------------------
/wiki/connect failed.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OpenTails/CRUMPET-Android/f540d6f35e62b208f8949cec2c3d21f10d94b60a/wiki/connect failed.jpg
--------------------------------------------------------------------------------
/wiki/connected.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OpenTails/CRUMPET-Android/f540d6f35e62b208f8949cec2c3d21f10d94b60a/wiki/connected.jpg
--------------------------------------------------------------------------------
/wiki/devices.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OpenTails/CRUMPET-Android/f540d6f35e62b208f8949cec2c3d21f10d94b60a/wiki/devices.jpg
--------------------------------------------------------------------------------
/wiki/dt-VER.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OpenTails/CRUMPET-Android/f540d6f35e62b208f8949cec2c3d21f10d94b60a/wiki/dt-VER.jpg
--------------------------------------------------------------------------------
/wiki/dt-connected.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OpenTails/CRUMPET-Android/f540d6f35e62b208f8949cec2c3d21f10d94b60a/wiki/dt-connected.jpg
--------------------------------------------------------------------------------
/wiki/dt-devices.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OpenTails/CRUMPET-Android/f540d6f35e62b208f8949cec2c3d21f10d94b60a/wiki/dt-devices.jpg
--------------------------------------------------------------------------------
/wiki/profile.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OpenTails/CRUMPET-Android/f540d6f35e62b208f8949cec2c3d21f10d94b60a/wiki/profile.jpg
--------------------------------------------------------------------------------
/wiki/terminal.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OpenTails/CRUMPET-Android/f540d6f35e62b208f8949cec2c3d21f10d94b60a/wiki/terminal.jpg
--------------------------------------------------------------------------------