├── .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 | 17 | 19 | 20 | 22 | image/svg+xml 23 | 25 | 26 | 27 | 28 | 30 | 50 | 53 | 59 | 64 | 65 | 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 | 17 | 19 | 20 | 22 | image/svg+xml 23 | 25 | 26 | 27 | 28 | 30 | 50 | 53 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /src/images/crumpet-head.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 22 | 24 | 43 | 45 | 46 | 48 | image/svg+xml 49 | 51 | 52 | 53 | 54 | 55 | 60 | 64 | 67 | 73 | 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /src/images/eargear.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | EG 12 | -------------------------------------------------------------------------------- /src/images/flutter.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | FW 12 | -------------------------------------------------------------------------------- /src/images/glowtip.svg: -------------------------------------------------------------------------------- 1 | 2 | 17 | 19 | 20 | 22 | image/svg+xml 23 | 25 | 26 | 27 | 28 | 30 | 50 | 53 | 59 | 64 | 69 | 74 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /src/images/listeningmode.svg: -------------------------------------------------------------------------------- 1 | 2 | 17 | 19 | 20 | 22 | image/svg+xml 23 | 25 | 26 | 27 | 28 | 30 | 50 | 53 | 59 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /src/images/mitailmini.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | MM 12 | -------------------------------------------------------------------------------- /src/images/moves.svg: -------------------------------------------------------------------------------- 1 | 2 | 17 | 19 | 20 | 22 | image/svg+xml 23 | 25 | 26 | 27 | 28 | 30 | 50 | 53 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /src/images/tail.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 22 | 24 | 43 | 45 | 46 | 48 | image/svg+xml 49 | 51 | 52 | 53 | 54 | 55 | 60 | 63 | 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /src/images/tail_lights.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 22 | 24 | 48 | 50 | 51 | 53 | image/svg+xml 54 | 56 | 57 | 58 | 59 | 60 | 65 | 68 | 74 | 80 | 86 | 92 | 98 | 104 | 105 | 106 | 107 | -------------------------------------------------------------------------------- /src/images/tail_moves.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 22 | 24 | 43 | 45 | 46 | 48 | image/svg+xml 49 | 51 | 52 | 53 | 54 | 55 | 60 | 63 | 68 | 74 | 80 | 86 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /src/images/taildevice.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 14 | 15 | MT 16 | -------------------------------------------------------------------------------- /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 --------------------------------------------------------------------------------