Selaa lähdekoodia

Add new remote for quac

WillyJL 7 kuukautta sitten
vanhempi
commit
2fbce8679e
71 muutettua tiedostoa jossa 0 lisäystä ja 5179 poistoa
  1. 0 17
      quac/.clangd
  2. 0 1
      quac/.github/FUNDING.yml
  3. 0 41
      quac/.github/workflows/build.yml
  4. 0 7
      quac/.gitignore
  5. 0 1
      quac/.gitsubtree
  6. 0 70
      quac/CHANGELOG.md
  7. 0 148
      quac/README.md
  8. 0 52
      quac/README_flipperlab.md
  9. 0 70
      quac/actions/action.c
  10. 0 13
      quac/actions/action.h
  11. 0 13
      quac/actions/action_i.h
  12. 0 48
      quac/actions/action_ibutton.c
  13. 0 90
      quac/actions/action_ir.c
  14. 0 205
      quac/actions/action_ir_utils.c
  15. 0 40
      quac/actions/action_ir_utils.h
  16. 0 45
      quac/actions/action_nfc.c
  17. 0 184
      quac/actions/action_qpl.c
  18. 0 111
      quac/actions/action_rfid.c
  19. 0 223
      quac/actions/action_subghz.c
  20. 0 613
      quac/actions/helpers/subghz_txrx.c
  21. 0 336
      quac/actions/helpers/subghz_txrx.h
  22. 0 29
      quac/actions/helpers/subghz_txrx_i.h
  23. 0 91
      quac/actions/helpers/subghz_types.h
  24. 0 17
      quac/application.fam
  25. 0 0
      quac/images/.gitkeep
  26. BIN
      quac/images/ArrowDown_8x4.png
  27. BIN
      quac/images/ArrowUp_8x4.png
  28. BIN
      quac/images/Directory_10px.png
  29. BIN
      quac/images/IR_10px.png
  30. BIN
      quac/images/NFC_10px.png
  31. BIN
      quac/images/Playlist_10px.png
  32. BIN
      quac/images/RFID_10px.png
  33. BIN
      quac/images/Settings_10px.png
  34. BIN
      quac/images/SubGHz_10px.png
  35. BIN
      quac/images/Unknown_10px.png
  36. BIN
      quac/images/iButton_10px.png
  37. BIN
      quac/images/quac.png
  38. 0 191
      quac/item.c
  39. 0 75
      quac/item.h
  40. 0 112
      quac/quac.c
  41. 0 71
      quac/quac.h
  42. 0 179
      quac/quac_settings.c
  43. 0 14
      quac/quac_settings.h
  44. 0 6
      quac/scenes/.gitignore
  45. 0 40
      quac/scenes/scene_about.c
  46. 0 8
      quac/scenes/scene_about.h
  47. 0 91
      quac/scenes/scene_action_create_group.c
  48. 0 8
      quac/scenes/scene_action_create_group.h
  49. 0 148
      quac/scenes/scene_action_ir_list.c
  50. 0 8
      quac/scenes/scene_action_ir_list.h
  51. 0 110
      quac/scenes/scene_action_rename.c
  52. 0 8
      quac/scenes/scene_action_rename.h
  53. 0 383
      quac/scenes/scene_action_settings.c
  54. 0 8
      quac/scenes/scene_action_settings.h
  55. 0 187
      quac/scenes/scene_items.c
  56. 0 9
      quac/scenes/scene_items.h
  57. 0 201
      quac/scenes/scene_settings.c
  58. 0 10
      quac/scenes/scene_settings.h
  59. 0 55
      quac/scenes/scenes.c
  60. 0 35
      quac/scenes/scenes.h
  61. BIN
      quac/screenshots/screenshot_1.png
  62. BIN
      quac/screenshots/screenshot_1_orig.png
  63. BIN
      quac/screenshots/screenshot_2.png
  64. BIN
      quac/screenshots/screenshot_2_orig.png
  65. BIN
      quac/screenshots/screenshot_3.png
  66. BIN
      quac/screenshots/screenshot_3_orig.png
  67. BIN
      quac/screenshots/screenshot_4.png
  68. BIN
      quac/screenshots/screenshot_4_90.png
  69. BIN
      quac/screenshots/screenshot_4_orig.png
  70. 0 629
      quac/views/action_menu.c
  71. 0 128
      quac/views/action_menu.h

+ 0 - 17
quac/.clangd

@@ -1,17 +0,0 @@
-CompileFlags:
-    Add: 
-        - -Wno-unknown-warning-option
-        - -Wno-format
-    Remove: 
-        - -mword-relocations
-
-Diagnostics:
-    ClangTidy:
-        FastCheckFilter: None
-
----
-
-If:
-    PathMatch: .*\.h
-Diagnostics:
-    UnusedIncludes: None

+ 0 - 1
quac/.github/FUNDING.yml

@@ -1 +0,0 @@
-buy_me_a_coffee: rdefeo

+ 0 - 41
quac/.github/workflows/build.yml

@@ -1,41 +0,0 @@
-name: "FAP: Build for multiple SDK sources"
-# This will build your app for dev and release channels on GitHub. 
-# It will also build your app every day to make sure it's up to date with the latest SDK changes.
-# See https://github.com/marketplace/actions/build-flipper-application-package-fap for more information
-
-on:
-  push:
-    ## put your main branch name under "branches"
-    #branches: 
-    #  - master 
-  pull_request:
-  schedule: 
-    # do a build every day
-    - cron: "1 1 * * *"
-
-jobs:
-  ufbt-build:
-    runs-on: ubuntu-latest
-    strategy:
-      matrix:
-        include:
-          - name: dev channel
-            sdk-channel: dev
-          - name: release channel
-            sdk-channel: release
-          # You can add unofficial channels here. See ufbt action docs for more info.
-    name: 'ufbt: Build for ${{ matrix.name }}'
-    steps:
-      - name: Checkout
-        uses: actions/checkout@v4
-      - name: Build with ufbt
-        uses: flipperdevices/flipperzero-ufbt-action@v0.1
-        id: build-app
-        with:
-          sdk-channel: ${{ matrix.sdk-channel }}
-      - name: Upload app artifacts
-        uses: actions/upload-artifact@v4
-        with:
-          # See ufbt action docs for other output variables
-          name: ${{ github.event.repository.name }}-${{ steps.build-app.outputs.suffix }}
-          path: ${{ steps.build-app.outputs.fap-artifacts }}

+ 0 - 7
quac/.gitignore

@@ -1,7 +0,0 @@
-dist/*
-.vscode
-.clang-format
-.clangd
-.editorconfig
-.env
-.ufbt

+ 0 - 1
quac/.gitsubtree

@@ -1 +0,0 @@
-https://github.com/rdefeo/quac master /

+ 0 - 70
quac/CHANGELOG.md

@@ -1,70 +0,0 @@
-## 0.8.0
-
-- Complete refactor of SubGhz code
-- Supports dynamic SubGhz signals with rolling codes
-- Auto-detect External SubGhz Antenna and use/prefer if available
-- Removed SugGhz External Antenna setting due to new auto-detect feature
-- Removed SubGhz Repeat setting, no longer needed (was non-standard)
-
-## 0.7.2
-
-- Updated for firmware SDK 1.2
-
-## 0.7.1
-
-- Updated for firmware SDK 1.1.12
-
-## 0.7.0
-
-- Addded support for iButton
-- Added IR Import All
-- Updated for firmware SDK 1.0.1
-
-## 0.6.3
-
-- Updated for firmware SDK 0.105.0
-
-## 0.6.2
-
-- Fix IR Import bus fault on empty
-- Add hint text on Empty group
-
-## 0.6.1
-
-- Several crash fixes when Importing files and Creating groups
-
-## 0.6
-
-- Basic NFC support
-- Improved IR file import, prompts for specific IR command
-- NFC default duration in Settings
-
-## 0.5
-
-- Basic infrared support
-- Action settings: Rename, Delete, Import, Create Group
-- Support for Sub-GHz external antenna in Settings
-- About screen
-
-## 0.4
-
-- New UI with horizontal/vertical layout support
-- Added icons, better header font
-- Settings menu to control UI
-- RFID duration suppport added to playlists
-
-## 0.3
-
-- Updated to firmware SDK 0.99.1
-
-## 0.2
-
-- Playlist support
-- Hidden file/folder support
-
-## 0.1
-
-- First release!
-- Supports Sub-GHz and RFID files
-- Items can be sorted based on filename prefix
-- Item labels are pretty-printed

+ 0 - 148
quac/README.md

@@ -1,148 +0,0 @@
-# Quac! Remote
-
-## Quick Action Remote Control for Flipperzero
-
-Get the latest version:
-
-* [Flipper Lab](https://lab.flipper.net/apps/quac) - recommended
-* [Latest builds](https://github.com/rdefeo/quac/releases) - manual install
-* Or build yourself with [ufbt](README.md#building)
-
-![build status badge](https://github.com/rdefeo/quac/actions/workflows/build.yml/badge.svg)
-
-This app allows you to organize previously recorded signals, of any type*, so that you can quickly and easily play them back. No more needing to recall whether that door is Sub-GHz or RFID! Just navigate to that action in **Quac!** and press OK to send!
-
-The app does not provide any recording functionality - you must use the existing Flipperzero apps to create the saved files for your action/device. Quac! provides some basic functionality to manage your files. Or, you can manage the folder structure manually on your SD card.
-
-> Supported files include: Sub-Ghz (.sub), RFID (.rfid), Infrared (.ir), NFC (.nfc), and iButton (.ibtn)
-
-## Features
-
-* [Playback of rfid, sub-ghz, IR, NFC, iButton signals](README.md#signal-playback)
-* [Easy navigation](README.md#navigation--controls)
-* [Flexible signal organization](README.md#signal-organization) - utilizing the SDcard filesystem
-* [In-app file management](README.md#action-settings) - rename, delete, import, import link
-* [Playlist support](README.md#playlists)
-* [Flexible naming/sorting, hidden file/folder support](README.md#sorting-and-naming)
-* [Customizable UI](README.md#application-settings)
-
-## Screenshots
-
-![top-level view](screenshots/screenshot_1.png)
-![group view](screenshots/screenshot_3.png)
-![vertical layout](screenshots/screenshot_4_90.png)
-
-## Navigation / Controls
-
-* Pressing `OK` on a folder label will open/navigate to that folder and display it's contents
-* Pressing `OK` on a signal will transmit that signal / playlist
-* Pressing `Back` will take you up one folder
-* Pressing `Up` and `Down` will, you know, select things up and down...
-* Long pressing `Right` will open that item's settings: Rename, Delete, Import Here, Create Group
-
-## Signal playback
-
-The signal files are played back as recorded. During playback/transmit, the LED light will flash blue until the action is complete. For RFID, NFC, and iButton signals, they are continuously played back for their defined durations: RFID - 2.5 seconds, NFC - 1 second, iButton - 1 second. These defaults can be changed in [application Settings](README.md#application-settings).
-
-SubGhz signals that are dynamic (i.e. have rolling codes / counters) will be re-saved on playback, ensuring the persistence of the new counter values. If an External SubGhz antenna is available, it will be used.
-
-## Signal Organization
-
-The key to organizing your Quac! interface is to organize your `/ext/apps_data/quac` folder structure. The UI is derived directly from the filesystem structure. Every individual file/signal is given a label on screen. And every folder/directory is a logical group of more files/folders. Selecting a group in the UI will show you the contents of that folder. There is no limit on the number of actions or folders - nest as deep as you want!
-
-You can organize your files by device type, or by function. For example, you may have a folder of "TV" actions, which correspond to Channel Up, Channel Down, Volume Up, Volume Down, etc. Or you may have a "Work" folder, which contains files/actions that correspond to Parking Gate, Garage Door, Lobby Entrance.
-
-The files in a folder can be of mixed types. **This is Quac!'s main strength!** So continuing with the "Work Access" example, the Parking Gate can be Sub-Ghz and the Garage Door can be RFID.
-
-Here's an example file layout for the screenshots above:
-
-```text
-/ext/apps_data/quac
-  /00_Work
-    00_ParkingGate.sub
-    01_Garage_door.rfid
-    02_Office.rfid
-  01_Front_Door.rfid
-  /02_Jam_Lists
-     00_Gate_Spam.qpl
-     01_TVs_OFF.qpl
-     02_my_Playlist.qpl
-```
-
-## Action Settings
-
-Long pressing the `Right` button will launch a settings menu for the currently selected action. This provides the following options:
-
-* **Rename**: Allows you to rename the selected item. Useful for changing sorting order. The file extension is preserved on signal files. **Note: folder renaming is broken right now**
-* **Delete**: Deletes files and folders - folders must be empty
-* **Import Here**: Launches file browser to let you select a signal file from anywhere on the SDcard and then copies it to the current folder.
-  * When importing an IR file, you are prompted to select which IR command to import. This individual command is imported as it's own `.ir` file into the current location. You can also select `* IMPORT ALL *` to, well, import all commands.
-  * If an Import fails, the Flipperzero will flash red and buzz - this may be caused by a duplicate filename (i.e. that file/IR command already exists in the current folder) or the target file can not be read.
-* **Import Link Here**: Similar to Link Here, but instead of copying the file, it will generate a Quac Link file (`.ql`) which contains the path of the selected file.
-  * At this time, you can not create a link to an IR file, as this would need to also include a reference to the specific signal within the target file.
-  * You can not create a link to a Quac Link file.
-* **Create Group**: Prompts for the name of a new folder that will be created at that point in the folder structure.
-
-## Playlists
-
-You can chain multiple signal playback actions together by creating a playlist. Simply create a text file, with extension `.qpl`, which contains a list of paths to the signals you wish to transmit - they will be played sequentially. Playlist names show up as clickable action, like all other individual signals/actions. Playlist files feature the following:
-
-* Comments: lines that start with a `#` are ignored
-* `pause <ms>` on a line will pause the playback by the specified millisecond duration
-* Signal file names can be absolute (full path) or relative to the current directory
-* RFID and NFC files can have an optional duration specified. Simply add a space after the signal's file name, followed by a millisecond duration. This duration will override the Quac! Settings value, just for this one signal.
-
-Errors found in the playlist will halt playback and vibrate the Flipper. Blank lines are ignored.
-
-Here's an example playlist called `/ext/apps_data/quac/arrive_home.qpl`:
-
-```text
-# Home arrival playlist
-
-.exterior_light_on.sub
-.side_door_unlock.sub
-
-pause 2500
-
-/ext/apps_data/quac/03_Stereo/00_On.sub
-/ext/apps_data/quac/06_Lights/Disco_Ball.sub
-
-Lava_Lamp.rfid 4000
-Neon_Strobe.nfc
-```
-
-The first two `.sub` files live in the `/ext/apps_data/quac` folder, which is where `arrive_home.qpl` is located, and they will not show up in any UI screen since they are hidden (they start with a `.`). Next, we pause the playlist for 2.5 sec. The next two files live elsewhere, but can still be referenced by the playlist since they are specified via absolute path. For the last two signals: the RFID signal is transmitted for 4000ms, instead of the duration listed in Quac! Settings, followed by an NFC signal which is transmitted for the default duration.
-
-## Sorting and Naming
-
-The list view UI is based on the sorted file and folder order. This is enforced by sorting the actual filenames. When there are cases where you need to create a specific order, you can prepend the file and folder names with `XX_` where `X` is a digit between 0-9. This will let you place an action called `On` before `Off`, even though when sorted alphabeticaly, `Off` would come before `On`. Therefore, you would name your files `00_On.rfid` and `01_Off.rfid`. But that looks ugly! When the files and folders are rendered for display, any `XX_` prefix will be stripped. All underscores will be replaced with spaces. Extensions will be stripped. Casing is preserved. Additionally, all files and folders that begin with a `.` will be ignored when drawing the UI - these are "hidden" files. However, they can still be referenced in playlists. This keeps the UI uncluttered.
-
-## Application Settings
-
-![Settings menu](screenshots/screenshot_2.png)
-
-The settings menu will appear as the last item when you are viewing the "root" directory. Within the settings you can control:
-
-* Layout: Switch between Horizontal and Vertical layout
-* Show Icons: Toggles display of all icons
-* Show Headers: Toggles display of header/folder text at the top, giving you room for one more item on screen!
-* RFID Duration: Changes the length of time a RFID signal is transmitted. Within playlists, this can be overridden per `.rfid` file.
-* NFC Duration: Changes the length of time a NFC signal is transmitted. Within playlists, this can be overridden per `.nfc` file.
-* iButton Duration: Changes the length of time a iButton signal is transmitted. Within playlists, this can be overridden per `.ibtn` file.
-* IR Ext Ant: Whether to use the external device for IR signals. If enabled, but no external IR device is attached to TX, then the internal IR device will be used.
-* Show Hidden: Will display files and folders that start with a period (`.`)
-* About: Application info and version
-
-## Troubleshooting
-
-For some errors, Quac! will show an error message on screen. In other cases, it may simply flash the LED red and vibrate. To get more information on what may be happening, it is helpful to connect your Flipperzero to your computer and view the logs. Quac! will print useful information and error information to the log file as it performs its actions. The handy `ufbt cli` command, followed by `log` will show all log statements in real time, and can be very useful if you need to report an error. Check out the [ufbt](https://github.com/flipperdevices/flipperzero-ufbt) page for more information.
-
-## On deck
-
-* suggestions??
-
-## Building
-
-This app is currently built with [ufbt](https://github.com/flipperdevices/flipperzero-ufbt), intended for the official firmware. I have not tested this on other firmwares. The `.fap` file can be found in the Releases section on the right.
-
-<a href="https://www.buymeacoffee.com/rdefeo" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/v2/default-yellow.png" alt="Buy Me A Coffee" style="height: 60px !important;width: 217px !important;" ></a>

+ 0 - 52
quac/README_flipperlab.md

@@ -1,52 +0,0 @@
-# Quac! Remote
-
-## QUick ACtion Remote Control for Flipperzero
-
-This app allows you to organize previously recorded signals, of any type, so that you can quickly and easily play them back. No more needing to recall whether that door is Sub-GHz or RFID! Just navigate to that action in **Quac** and press OK to send!
-
-The app does not provide any recording functionality - you can use the existing Flipperzero apps to create the saved files or import from existing files. Quac! provides some basic functionality to manage your files. Or, you can manage the folder structure manually on your SD card on your PC.
-
-* Supported files include: Sub-Ghz (.sub), RFID (.rfid), Infrared (.ir), NFC (.nfc), and iButton (.ibtn)
-
-## Features
-
-* Playback of rfid, sub-ghz, IR, NFC, and iButton signals
-* Easy navigation
-* Flexible signal organization
-* In-app file management
-* Playlist support
-* Flexible naming/sorting, hidden file/folder support
-* Customizable UI
-
-## Signal playback
-
-The signal files are played back as recorded. During playback/transmit, the LED light will flash blue until the action is complete. For RFID, NFC, anbd iButton signals, they are continuously played back for the durations specified in the Settings.
-
-## Signal Organization
-
-The key to organizing your Quac interface is to organize your **/ext/apps_data/quac** folder structure. The UI is derived directly from the filesystem structure. Every individual file/signal is assigned a button. This is an "action". And every folder/directory is a logical group of more files/folders. Selecting a group in the UI will show you the contents of that folder. There is no limit on the number of actions or folders - nest as deep as you want!
-
-You can organize your files by device type, or by function. For example, you may have a folder of "TV" actions, which correspond to Channel Up, Channel Down, Volume Up, Volume Down, etc. Or you may have a "Work Access" folder, which contains files/actions that correspond to Parking Gate, Garage Door, Lobby Entrance.
-
-The files in a folder can be of mixed types. **This is Quac's main strength!** So continuing with the "Work Access" example, the Parking Gate can be Sub-Ghz and the Garage Door can be RFID.
-
-## Playlists
-
-You can chain multiple signal playback actions together by creating a playlist. Simply create a text file which contains a list of paths to the signals you wish to transmit - they will be played sequentially. Playlist names show up as clickable button, like all other individual signals/actions.
-
-Errors found in the playlist will halt playback and vibrate the Flipperzero. Blank lines are ignored.
-
-## Settings
-
-The settings menu will appear as the last item when you are viewing the "root" directory. Within the settings you can control:
-
-* Layout: Switch between Horizontal and Vertical layout
-* Show Icons: Toggles display of all icons
-* Show Headers: Toggles display of header/folder text at the top, giving you room for one more item on screen!
-* RFID Duration: Changes the length of time a RFID signal is transmitted. Can be overridden, per RFID file in a Playlist
-* NFC Duration: Changes the length of time a NFC signal is transmitted. Can be overridden, per NFC file in a Playlist
-* iButton Duration: Changes the length of time a iButton signal is transmitted. Can be overridden, per iButton file in a Playlist
-* IR Ext Ant: Enables / Disables use of external IR antenna
-* Show Hidden: Toggles display of files/folders that start with a period.
-
-**More information can be found in the Git repository**

+ 0 - 70
quac/actions/action.c

@@ -1,70 +0,0 @@
-
-#include "quac.h"
-#include "item.h"
-#include "action_i.h"
-
-#define MAX_FILE_LEN (size_t)256
-
-void action_ql_resolve(
-    void* context,
-    const FuriString* action_path,
-    FuriString* new_path,
-    FuriString* error) {
-    UNUSED(error);
-    App* app = (App*)context;
-    File* file_link = storage_file_alloc(app->storage);
-    do {
-        if(!storage_file_open(
-               file_link, furi_string_get_cstr(action_path), FSAM_READ, FSOM_OPEN_EXISTING)) {
-            ACTION_SET_ERROR(
-                "Quac Link: Failed to open file %s", furi_string_get_cstr(action_path));
-            break;
-        }
-        char buffer[MAX_FILE_LEN]; // long enough?
-        size_t bytes_read = storage_file_read(file_link, buffer, MAX_FILE_LEN);
-        if(bytes_read == 0) {
-            ACTION_SET_ERROR(
-                "Quac Link: Error reading link file %s", furi_string_get_cstr(action_path));
-            break;
-        }
-        if(!storage_file_exists(app->storage, buffer)) {
-            ACTION_SET_ERROR("Quac Link: Linked file does not exist! %s", buffer);
-            break;
-        }
-        furi_string_set_strn(new_path, buffer, bytes_read);
-    } while(false);
-    storage_file_close(file_link);
-    storage_file_free(file_link);
-}
-
-void action_tx(void* context, Item* item, FuriString* error) {
-    // FURI_LOG_I(TAG, "action_run: %s : %s", furi_string_get_cstr(item->name), item->ext);
-
-    FuriString* path = furi_string_alloc_set(item->path);
-    if(item->is_link) {
-        // This is a Quac link, open the file and pull the real filename
-        action_ql_resolve(context, item->path, path, error);
-        if(furi_string_size(error)) {
-            furi_string_free(path);
-            return;
-        }
-        FURI_LOG_I(TAG, "Resolved Quac link file to: %s", furi_string_get_cstr(path));
-    }
-
-    if(!strcmp(item->ext, ".sub")) {
-        action_subghz_tx(context, path, error);
-    } else if(!strcmp(item->ext, ".ir")) {
-        action_ir_tx(context, path, error);
-    } else if(!strcmp(item->ext, ".rfid")) {
-        action_rfid_tx(context, path, error);
-    } else if(!strcmp(item->ext, ".nfc")) {
-        action_nfc_tx(context, path, error);
-    } else if(!strcmp(item->ext, ".ibtn")) {
-        action_ibutton_tx(context, path, error);
-    } else if(!strcmp(item->ext, ".qpl")) {
-        action_qpl_tx(context, path, error);
-    } else {
-        FURI_LOG_E(TAG, "Unknown item file type! %s", item->ext);
-    }
-    furi_string_free(path);
-}

+ 0 - 13
quac/actions/action.h

@@ -1,13 +0,0 @@
-#pragma once
-
-#define EMPTY_ACTION_INDEX -1
-
-struct Item;
-
-/** Transmits the selected item
- * 
- * @param   context     The App
- * @param   item        Selected item to transmit
- * @param   error       Error message if unsuccessful
-*/
-void action_tx(void* context, Item* item, FuriString* error);

+ 0 - 13
quac/actions/action_i.h

@@ -1,13 +0,0 @@
-#pragma once
-
-#include <furi.h>
-#include <furi_hal.h>
-
-#define ACTION_SET_ERROR(_msg_fmt, ...) furi_string_printf(error, _msg_fmt, ##__VA_ARGS__)
-
-void action_subghz_tx(void* context, FuriString* action_path, FuriString* error);
-void action_rfid_tx(void* context, const FuriString* action_path, FuriString* error);
-void action_ir_tx(void* context, const FuriString* action_path, FuriString* error);
-void action_nfc_tx(void* context, const FuriString* action_path, FuriString* error);
-void action_ibutton_tx(void* context, const FuriString* action_path, FuriString* error);
-void action_qpl_tx(void* context, const FuriString* action_path, FuriString* error);

+ 0 - 48
quac/actions/action_ibutton.c

@@ -1,48 +0,0 @@
-// Methods for iButton transmission
-
-#include <furi.h>
-#include <furi_hal.h>
-
-#include <ibutton/ibutton_key.h>
-#include <ibutton/ibutton_worker.h>
-#include <ibutton/ibutton_protocols.h>
-
-#include "action_i.h"
-#include "quac.h"
-
-void action_ibutton_tx(void* context, const FuriString* action_path, FuriString* error) {
-    App* app = context;
-    const char* cpath = furi_string_get_cstr(action_path);
-
-    FURI_LOG_I(TAG, "iButton: Tx %s", cpath);
-
-    iButtonProtocols* protocols = ibutton_protocols_alloc();
-    iButtonKey* key = ibutton_key_alloc(ibutton_protocols_get_max_data_size(protocols));
-
-    const bool success = ibutton_protocols_load(protocols, key, cpath);
-    if(!success) {
-        FURI_LOG_E(TAG, "Error loading iButton file %s", cpath);
-        ACTION_SET_ERROR("Error loading %s", cpath);
-    } else {
-        FURI_LOG_I(TAG, "iButton: Starting...");
-        iButtonWorker* worker = ibutton_worker_alloc(protocols);
-        ibutton_worker_start_thread(worker);
-
-        ibutton_worker_emulate_start(worker, key);
-
-        int16_t time_ms = app->settings.ibutton_duration;
-        const int16_t interval_ms = 100;
-        while(time_ms > 0) {
-            furi_delay_ms(interval_ms);
-            time_ms -= interval_ms;
-        }
-
-        FURI_LOG_I(TAG, "iButton: Done");
-        ibutton_worker_stop(worker);
-        ibutton_worker_stop_thread(worker);
-        ibutton_worker_free(worker);
-    }
-
-    ibutton_key_free(key);
-    ibutton_protocols_free(protocols);
-}

+ 0 - 90
quac/actions/action_ir.c

@@ -1,90 +0,0 @@
-// Methods for IR transmission
-
-#include "quac.h"
-#include "action_i.h"
-#include "action_ir_utils.h"
-
-void action_ir_tx(void* context, const FuriString* action_path, FuriString* error) {
-    UNUSED(error);
-    App* app = context;
-    const char* file_name = furi_string_get_cstr(action_path);
-    InfraredSignal* signal = infrared_utils_signal_alloc();
-
-    FlipperFormat* fff_data_file = flipper_format_file_alloc(app->storage);
-    FuriString* temp_str;
-    temp_str = furi_string_alloc();
-    // uint32_t temp_data32;
-
-    // https://developer.flipper.net/flipperzero/doxygen/infrared_file_format.html
-    // TODO: Right now we only read the first signal found in the file. Add support
-    // for reading any signal by 'name'?
-    do {
-        if(!flipper_format_file_open_existing(fff_data_file, file_name)) {
-            ACTION_SET_ERROR("IR: Error opening %s", file_name);
-            break;
-        }
-        uint32_t index = 0;
-        if(!infrared_utils_read_signal_at_index(fff_data_file, index, signal, temp_str)) {
-            ACTION_SET_ERROR("IR: Failed to read from file");
-            break;
-        }
-
-        if(app->settings.ir_use_ext_module) action_ir_power_otg(true);
-
-        if(signal->is_raw) {
-            // raw
-            FURI_LOG_I(
-                TAG,
-                "IR: Sending (%s) type=raw => %d timings, %lu Hz, %f",
-                file_name,
-                signal->payload.raw.timings_size,
-                signal->payload.raw.frequency,
-                (double)signal->payload.raw.duty_cycle);
-
-            infrared_send_raw_ext(
-                signal->payload.raw.timings,
-                signal->payload.raw.timings_size,
-                true,
-                signal->payload.raw.frequency,
-                signal->payload.raw.duty_cycle);
-
-            FURI_LOG_I(TAG, "IR: Send complete");
-        } else {
-            //parsed
-            FURI_LOG_I(
-                TAG,
-                "IR: Sending (%s) type=parsed => %s %lu %lu",
-                file_name,
-                infrared_get_protocol_name(signal->payload.message.protocol),
-                signal->payload.message.address,
-                signal->payload.message.command);
-
-            infrared_send(&signal->payload.message, 1);
-
-            FURI_LOG_I(TAG, "IR: Send complete");
-        }
-
-        if(app->settings.ir_use_ext_module) action_ir_power_otg(false);
-    } while(false);
-
-    furi_string_free(temp_str);
-    flipper_format_free(fff_data_file);
-    infrared_utils_signal_free(signal);
-}
-
-void action_ir_power_otg(bool enable) {
-    FuriHalInfraredTxPin tx_pin_detected = furi_hal_infrared_detect_tx_output();
-    furi_hal_infrared_set_tx_output(tx_pin_detected);
-
-    if(tx_pin_detected == FuriHalInfraredTxPinInternal) return;
-
-    if(enable) {
-        if(!furi_hal_power_is_otg_enabled()) {
-            furi_hal_power_enable_otg();
-        }
-    } else {
-        if(furi_hal_power_is_otg_enabled()) {
-            furi_hal_power_disable_otg();
-        }
-    }
-}

+ 0 - 205
quac/actions/action_ir_utils.c

@@ -1,205 +0,0 @@
-// Utility methods for IR transmission
-#include <furi.h>
-#include "../quac.h"
-
-#include <flipper_format/flipper_format.h>
-
-#include "action_ir_utils.h"
-
-InfraredSignal* infrared_utils_signal_alloc() {
-    InfraredSignal* signal = malloc(sizeof(InfraredSignal));
-    signal->is_raw = false;
-    signal->payload.message.protocol = InfraredProtocolUnknown;
-    return signal;
-}
-
-void infrared_utils_signal_free(InfraredSignal* signal) {
-    if(signal->is_raw) {
-        free(signal->payload.raw.timings);
-        signal->payload.raw.timings = NULL;
-    }
-    free(signal);
-}
-
-bool infrared_utils_read_signal_at_index(
-    FlipperFormat* fff_data_file,
-    uint32_t index,
-    InfraredSignal* signal,
-    FuriString* name) {
-    //
-    FuriString* temp_str;
-    temp_str = furi_string_alloc();
-    uint32_t temp_data32;
-    bool success = false;
-
-    do {
-        if(!flipper_format_read_header(fff_data_file, temp_str, &temp_data32)) {
-            FURI_LOG_E(TAG, "IR: Missing or incorrect header");
-            break;
-        }
-
-        if(!furi_string_cmp_str(temp_str, INFRARED_FILE_TYPE) &&
-           temp_data32 == INFRARED_FILE_VERSION) {
-        } else {
-            // ACTION_SET_ERROR("IR: File type or version mismatch");
-            FURI_LOG_E(TAG, "IR: File type or version mismatch");
-            break;
-        }
-
-        // Read the file until we find the signal we want
-        uint32_t i = 0;
-        bool found = false;
-        while(flipper_format_read_string(fff_data_file, "name", name)) {
-            if(i == index) {
-                found = true;
-                break;
-            }
-            ++i;
-        }
-        if(!found) {
-            // ACTION_SET_ERROR("IR: Could not find command %lu!", index);
-            FURI_LOG_E(TAG, "Requested IR command %lu not found", index);
-            furi_string_reset(name);
-            break;
-        }
-
-        FURI_LOG_I(TAG, "Reading signal %s", furi_string_get_cstr(temp_str));
-        if(!flipper_format_read_string(fff_data_file, "type", temp_str)) {
-            // ACTION_SET_ERROR("IR: Type missing");
-            break;
-        }
-        if(furi_string_equal(temp_str, "parsed")) {
-            signal->is_raw = false;
-
-            if(!flipper_format_read_string(fff_data_file, "protocol", temp_str)) {
-                // ACTION_SET_ERROR("IR: Invalid or missing protocol");
-                break;
-            }
-            signal->payload.message.protocol =
-                infrared_get_protocol_by_name(furi_string_get_cstr(temp_str));
-            if(!infrared_is_protocol_valid(signal->payload.message.protocol)) {
-                // ACTION_SET_ERROR("IR: Invalid or unknown protocol");
-                break;
-            }
-
-            if(!flipper_format_read_hex(
-                   fff_data_file, "address", (uint8_t*)&signal->payload.message.address, 4)) {
-                // ACTION_SET_ERROR("IR: Failed to read address");
-                break;
-            }
-            if(!flipper_format_read_hex(
-                   fff_data_file, "command", (uint8_t*)&signal->payload.message.command, 4)) {
-                // ACTION_SET_ERROR("IR: Failed to read command");
-                break;
-            }
-            success = true;
-        } else if(furi_string_equal(temp_str, "raw")) {
-            signal->is_raw = true;
-
-            if(!flipper_format_read_uint32(
-                   fff_data_file, "frequency", &signal->payload.raw.frequency, 1)) {
-                // ACTION_SET_ERROR("IR: Failed to read frequency");
-                break;
-            }
-            if(!flipper_format_read_float(
-                   fff_data_file, "duty_cycle", &signal->payload.raw.duty_cycle, 1)) {
-                // ACTION_SET_ERROR("IR: Failed to read duty cycle");
-                break;
-            }
-            if(!flipper_format_get_value_count(fff_data_file, "data", &temp_data32)) {
-                // ACTION_SET_ERROR("IR: Failed to get size of data");
-                break;
-            }
-            if(temp_data32 > MAX_TIMINGS_AMOUNT) {
-                // ACTION_SET_ERROR("IR: Data size exceeds limit");
-                break;
-            }
-            signal->payload.raw.timings_size = temp_data32;
-
-            signal->payload.raw.timings =
-                malloc(sizeof(uint32_t) * signal->payload.raw.timings_size);
-            if(!flipper_format_read_uint32(
-                   fff_data_file, "data", signal->payload.raw.timings, temp_data32)) {
-                // ACTION_SET_ERROR("IR: Failed to read data");
-                free(signal->payload.raw.timings);
-                break;
-            }
-            success = true;
-        }
-    } while(false);
-
-    return success;
-}
-
-bool infrared_utils_write_signal(
-    FlipperFormat* fff_data_file,
-    InfraredSignal* signal,
-    FuriString* name) {
-    //
-    bool success = false;
-
-    do {
-        if(!flipper_format_write_header_cstr(
-               fff_data_file, INFRARED_FILE_TYPE, INFRARED_FILE_VERSION)) {
-            FURI_LOG_E(TAG, "Error writing header");
-            break;
-        }
-        if(!flipper_format_write_comment_cstr(fff_data_file, "")) {
-            FURI_LOG_E(TAG, "Error writing blank comment");
-            break;
-        }
-        if(!flipper_format_write_string(fff_data_file, "name", name)) {
-            FURI_LOG_E(TAG, "Error writing name");
-            break;
-        }
-        if(!flipper_format_write_string_cstr(
-               fff_data_file, "type", signal->is_raw ? "raw" : "parsed")) {
-            FURI_LOG_E(TAG, "Error writing type");
-            break;
-        }
-        if(signal->is_raw) {
-            // raw
-            if(!flipper_format_write_uint32(
-                   fff_data_file, "frequency", &signal->payload.raw.frequency, 1)) {
-                FURI_LOG_E(TAG, "Error writing frequency");
-                break;
-            }
-            if(!flipper_format_write_float(
-                   fff_data_file, "duty_cycle", &signal->payload.raw.duty_cycle, 1)) {
-                FURI_LOG_E(TAG, "Error writing duty_cycle");
-                break;
-            }
-            if(!flipper_format_write_uint32(
-                   fff_data_file,
-                   "data",
-                   signal->payload.raw.timings,
-                   signal->payload.raw.timings_size)) {
-                FURI_LOG_E(TAG, "Error writing data");
-                break;
-            }
-            success = true;
-        } else {
-            // parsed
-            if(!flipper_format_write_string_cstr(
-                   fff_data_file,
-                   "protocol",
-                   infrared_get_protocol_name(signal->payload.message.protocol))) {
-                FURI_LOG_E(TAG, "Error writing protocol");
-                break;
-            }
-            if(!flipper_format_write_hex(
-                   fff_data_file, "address", (uint8_t*)&signal->payload.message.address, 4)) {
-                FURI_LOG_E(TAG, "Error writing address");
-                break;
-            }
-            if(!flipper_format_write_hex(
-                   fff_data_file, "command", (uint8_t*)&signal->payload.message.command, 4)) {
-                FURI_LOG_E(TAG, "Error writing command");
-                break;
-            }
-            success = true;
-        }
-    } while(false);
-
-    return success;
-}

+ 0 - 40
quac/actions/action_ir_utils.h

@@ -1,40 +0,0 @@
-#include <furi.h>
-// infrared
-#include <infrared.h>
-#include <infrared/encoder_decoder/infrared.h>
-#include <infrared/worker/infrared_transmit.h>
-#include <infrared/worker/infrared_worker.h>
-
-#include <flipper_format/flipper_format.h>
-
-#define INFRARED_FILE_TYPE    "IR signals file"
-#define INFRARED_FILE_VERSION 1
-
-typedef struct {
-    size_t timings_size; /**< Number of elements in the timings array. */
-    uint32_t* timings; /**< Pointer to an array of timings describing the signal. */
-    uint32_t frequency; /**< Carrier frequency of the signal. */
-    float duty_cycle; /**< Duty cycle of the signal. */
-} InfraredRawSignal;
-
-typedef struct InfraredSignal {
-    bool is_raw;
-    union {
-        InfraredMessage message; // protocol, address, command, repeat
-        InfraredRawSignal raw;
-    } payload;
-} InfraredSignal;
-
-InfraredSignal* infrared_utils_signal_alloc();
-
-void infrared_utils_signal_free(InfraredSignal* signal);
-
-bool infrared_utils_read_signal_at_index(
-    FlipperFormat* fffile,
-    uint32_t index,
-    InfraredSignal* signal,
-    FuriString* name);
-
-bool infrared_utils_write_signal(FlipperFormat* fffile, InfraredSignal* signal, FuriString* name);
-
-void action_ir_power_otg(bool enable);

+ 0 - 45
quac/actions/action_nfc.c

@@ -1,45 +0,0 @@
-// Methods for NFC transmission
-
-// nfc
-#include <furi.h>
-#include <furi_hal.h>
-#include <nfc/nfc.h>
-#include <nfc/nfc_device.h>
-#include <nfc/nfc_listener.h>
-
-#include "action_i.h"
-#include "quac.h"
-
-void action_nfc_tx(void* context, const FuriString* action_path, FuriString* error) {
-    App* app = context;
-
-    FURI_LOG_I(TAG, "NFC: Tx %s", furi_string_get_cstr(action_path));
-    Nfc* nfc = nfc_alloc();
-    NfcDevice* device = nfc_device_alloc();
-
-    if(nfc_device_load(device, furi_string_get_cstr(action_path))) {
-        NfcProtocol protocol = nfc_device_get_protocol(device);
-        FURI_LOG_I(TAG, "NFC: Protocol %s", nfc_device_get_protocol_name(protocol));
-        NfcListener* listener =
-            nfc_listener_alloc(nfc, protocol, nfc_device_get_data(device, protocol));
-        FURI_LOG_I(TAG, "NFC: Starting...");
-        nfc_listener_start(listener, NULL, NULL);
-
-        int16_t time_ms = app->settings.nfc_duration;
-        const int16_t interval_ms = 100;
-        while(time_ms > 0) {
-            furi_delay_ms(interval_ms);
-            time_ms -= interval_ms;
-        }
-
-        FURI_LOG_I(TAG, "NFC: Done");
-        nfc_listener_stop(listener);
-        nfc_listener_free(listener);
-    } else {
-        FURI_LOG_E(TAG, "NFC: Failed to load %s", furi_string_get_cstr(action_path));
-        ACTION_SET_ERROR("Failed to load %s", furi_string_get_cstr(action_path));
-    }
-    nfc_device_clear(device); // probably not needed?
-    nfc_free(nfc);
-    nfc_device_free(device);
-}

+ 0 - 184
quac/actions/action_qpl.c

@@ -1,184 +0,0 @@
-// Methods for Quac Playlist transmission
-
-#include <toolbox/stream/stream.h>
-#include <toolbox/stream/file_stream.h>
-#include <toolbox/path.h>
-#include <toolbox/args.h>
-
-#include <notification/notification_messages.h>
-
-#include "action_i.h"
-#include "quac.h"
-
-/** Open the Playlist file and then transmit each action
- * 
- * Each line of the playlist file is one of:
- *   <file_path>
- *      Full SD card path, or relative path to action to be transmitted. Must be
- *      one of the supported filetypes (.sub, .rfid, [.ir coming soon])
- * 
- *      If an .rfid file has a space followed by a number, that will be the
- *      duration for that RFID transmission. All other .rfid files will use
- *      the value specified in the Settings
- * 
- *   pause <ms>
- *      Pauses the playback for 'ms' milliseconds.
- * 
- * Blank lines, and comments (start with '#') are ignored. Whitespace is trimmed.
- * 
-*/
-void action_qpl_tx(void* context, const FuriString* action_path, FuriString* error) {
-    App* app = context;
-
-    // Save the current durations, in case the are changed during playback
-    uint32_t orig_rfid_duration = app->settings.rfid_duration;
-    uint32_t orig_nfc_duration = app->settings.nfc_duration;
-    uint32_t orig_ibutton_duration = app->settings.ibutton_duration;
-
-    FuriString* buffer;
-    buffer = furi_string_alloc();
-
-    Stream* file = file_stream_alloc(app->storage);
-    if(file_stream_open(file, furi_string_get_cstr(action_path), FSAM_READ, FSOM_OPEN_EXISTING)) {
-        while(stream_read_line(file, buffer)) {
-            furi_string_trim(buffer); // remove '\n\r' line endings, cleanup spaces
-            // FURI_LOG_I(TAG, "line: %s", furi_string_get_cstr(buffer));
-
-            // Skip blank lines
-            if(furi_string_size(buffer) == 0) {
-                continue;
-            }
-
-            // Skip comments
-            char first_char = furi_string_get_char(buffer, 0);
-            if(first_char == '#') {
-                continue;
-            }
-
-            // Check if buffer is a "command", and not just a filename
-            // Commands will contain spaces
-            bool processed_special_command = false;
-            FuriString* args_tmp;
-            args_tmp = furi_string_alloc();
-            do {
-                if(!args_read_string_and_trim(buffer, args_tmp)) {
-                    // No spaces found, buffer and args_tmp are now have same contents
-                    break;
-                }
-
-                // FURI_LOG_I(
-                //     TAG,
-                //     "args_temp: '%s', buffer: '%s'",
-                //     furi_string_get_cstr(args_tmp),
-                //     furi_string_get_cstr(buffer));
-
-                // OK, there's a space, and args_tmp is the first token, buffer is the rest
-                if(furi_string_cmpi_str(args_tmp, "pause") == 0) {
-                    processed_special_command = true;
-                    uint32_t pause_length = 0;
-                    if(sscanf(furi_string_get_cstr(buffer), "%lu", &pause_length) == 1) {
-                        FURI_LOG_I(TAG, "Pausing playlist for %lu ms", pause_length);
-                        furi_delay_ms(pause_length);
-                    } else {
-                        ACTION_SET_ERROR("Playlist: Invalid or missing pause time");
-                    }
-                    break;
-                }
-
-                // First token wasn't "pause", so maybe args_tmp is a filename followed
-                // by a transmit duration in ms in buffer
-                // Note: Not using path_extract_extension since it expects to find slashes in the
-                // path, and thus won't work if we have a relative path file
-                char ext[MAX_EXT_LEN + 1] = "";
-                size_t dot = furi_string_search_rchar(args_tmp, '.');
-                if(dot != FURI_STRING_FAILURE && furi_string_size(args_tmp) - dot <= MAX_EXT_LEN) {
-                    strlcpy(ext, &(furi_string_get_cstr(args_tmp))[dot], MAX_EXT_LEN);
-                }
-
-                // FURI_LOG_I(TAG, " - Found extension of %s", ext);
-
-                if(!strcmp(ext, ".rfid")) {
-                    uint32_t rfid_duration = 0;
-                    // FURI_LOG_I(TAG, "RFID file with duration");
-                    if(sscanf(furi_string_get_cstr(buffer), "%lu", &rfid_duration) == 1) {
-                        FURI_LOG_I(TAG, "RFID duration = %lu", rfid_duration);
-                        app->settings.rfid_duration = rfid_duration;
-                    }
-                } else if(!strcmp(ext, ".nfc")) {
-                    uint32_t nfc_duration = 0;
-                    if(sscanf(furi_string_get_cstr(buffer), "%lu", &nfc_duration) == 1) {
-                        FURI_LOG_I(TAG, "NFC duration = %lu", nfc_duration);
-                        app->settings.nfc_duration = nfc_duration;
-                    }
-                } else if(!strcmp(ext, ".ibtn")) {
-                    uint32_t ibutton_duration = 0;
-                    if(sscanf(furi_string_get_cstr(buffer), "%lu", &ibutton_duration) == 1) {
-                        FURI_LOG_I(TAG, "iButton duration = %lu", ibutton_duration);
-                        app->settings.ibutton_duration = ibutton_duration;
-                    }
-                }
-
-            } while(false);
-
-            furi_string_swap(buffer, args_tmp);
-            furi_string_free(args_tmp);
-
-            if(processed_special_command) {
-                continue;
-            }
-
-            first_char = furi_string_get_char(buffer, 0);
-            // Using relative paths? Prepend path of our playlist file
-            if(first_char != '/') {
-                FuriString* dirname;
-                dirname = furi_string_alloc();
-                path_extract_dirname(furi_string_get_cstr(action_path), dirname);
-                furi_string_cat_printf(dirname, "/%s", furi_string_get_cstr(buffer));
-                furi_string_swap(dirname, buffer);
-                furi_string_free(dirname);
-            }
-
-            char ext[MAX_EXT_LEN + 1] = "";
-            path_extract_extension(buffer, ext, MAX_EXT_LEN);
-            if(!strcmp(ext, ".sub")) {
-                action_subghz_tx(context, buffer, error);
-            } else if(!strcmp(ext, ".rfid")) {
-                action_rfid_tx(context, buffer, error);
-                // Reset our default duration back - in case it was changed during playback
-                app->settings.rfid_duration = orig_rfid_duration;
-            } else if(!strcmp(ext, ".ir")) {
-                action_ir_tx(context, buffer, error);
-            } else if(!strcmp(ext, ".nfc")) {
-                action_nfc_tx(context, buffer, error);
-                // Reset our default duration back - in case it was changed during playback
-                app->settings.nfc_duration = orig_nfc_duration;
-            } else if(!strcmp(ext, ".ibtn")) {
-                action_ibutton_tx(context, buffer, error);
-                // Reset our default duration back - in case it was changed during playback
-                app->settings.ibutton_duration = orig_ibutton_duration;
-            } else if(!strcmp(ext, ".qpl")) {
-                ACTION_SET_ERROR("Playlist: Can't call playlist from playlist");
-            } else {
-                ACTION_SET_ERROR(
-                    "Playlist: Unknown file/command! %s", furi_string_get_cstr(buffer));
-            }
-
-            if(furi_string_size(error)) {
-                // Abort playing the playlist - one of our actions failed
-                break;
-            }
-
-            // Playlist action complete!
-            // TODO: Do we need a small delay (say 25ms) between actions?
-            // TODO: Should we blip the light a diff color to indicate that
-            //       we're done with one command and moving to the next?
-            // furi_delay_ms(25);
-        }
-    } else {
-        ACTION_SET_ERROR("Could not open playlist");
-    }
-
-    furi_string_free(buffer);
-    file_stream_close(file);
-    stream_free(file);
-}

+ 0 - 111
quac/actions/action_rfid.c

@@ -1,111 +0,0 @@
-// Methods for RFID transmission
-
-// lfrid
-#include <lib/lfrfid/lfrfid_worker.h>
-#include <toolbox/protocols/protocol_dict.h>
-#include <lfrfid/protocols/lfrfid_protocols.h>
-#include <lfrfid/lfrfid_raw_file.h>
-#include <lib/toolbox/args.h>
-
-#include <flipper_format/flipper_format.h>
-
-#include "action_i.h"
-#include "quac.h"
-
-#define RFID_FILE_TYPE "Flipper RFID key"
-#define RFID_FILE_VERSION 1
-
-// lifted from flipperzero-firmware/applications/main/lfrfid/lfrfid_cli.c
-void action_rfid_tx(void* context, const FuriString* action_path, FuriString* error) {
-    UNUSED(error);
-
-    App* app = context;
-    const char* file_name = furi_string_get_cstr(action_path);
-
-    FlipperFormat* fff_data_file = flipper_format_file_alloc(app->storage);
-    FuriString* temp_str;
-    temp_str = furi_string_alloc();
-    uint32_t temp_data32;
-
-    ProtocolDict* dict = protocol_dict_alloc(lfrfid_protocols, LFRFIDProtocolMax);
-    ProtocolId protocol;
-    size_t data_size = protocol_dict_get_max_data_size(dict);
-    uint8_t* data = malloc(data_size);
-
-    // FURI_LOG_I(TAG, "Max dict data size is %d", data_size);
-    bool successful_read = false;
-    do {
-        if(!flipper_format_file_open_existing(fff_data_file, file_name)) {
-            ACTION_SET_ERROR("RFID: Error opening %s", file_name);
-            break;
-        }
-        if(!flipper_format_read_header(fff_data_file, temp_str, &temp_data32)) {
-            ACTION_SET_ERROR("RFID: Missing or incorrect header");
-            break;
-        }
-        if(!strcmp(furi_string_get_cstr(temp_str), RFID_FILE_TYPE) &&
-           temp_data32 == RFID_FILE_VERSION) {
-        } else {
-            ACTION_SET_ERROR("RFID: Type or version mismatch");
-            break;
-        }
-        // read and check the protocol field
-        if(!flipper_format_read_string(fff_data_file, "Key type", temp_str)) {
-            ACTION_SET_ERROR("RFID: Error reading protocol");
-            break;
-        }
-        protocol = protocol_dict_get_protocol_by_name(dict, furi_string_get_cstr(temp_str));
-        if(protocol == PROTOCOL_NO) {
-            ACTION_SET_ERROR("RFID: Unknown protocol: %s", furi_string_get_cstr(temp_str));
-            break;
-        }
-
-        // read and check data field
-        size_t required_size = protocol_dict_get_data_size(dict, protocol);
-        // FURI_LOG_I(TAG, "Protocol req data size is %d", required_size);
-        if(!flipper_format_read_hex(fff_data_file, "Data", data, required_size)) {
-            FURI_LOG_E(TAG, "RFID: Error reading data");
-            ACTION_SET_ERROR("RFID: Error reading data");
-            break;
-        }
-        // FURI_LOG_I(TAG, "Data: %s", furi_string_get_cstr(data_text));
-
-        // if(data_size != required_size) {
-        //     FURI_LOG_E(
-        //         TAG,
-        //         "%s data needs to be %zu bytes long",
-        //         protocol_dict_get_name(dict, protocol),
-        //         required_size);
-        //     break;
-        // }
-
-        protocol_dict_set_data(dict, protocol, data, data_size);
-        successful_read = true;
-        // FURI_LOG_I(TAG, "protocol dict setup complete!");
-    } while(false);
-
-    if(successful_read) {
-        LFRFIDWorker* worker = lfrfid_worker_alloc(dict);
-
-        lfrfid_worker_start_thread(worker);
-        lfrfid_worker_emulate_start(worker, protocol);
-
-        int16_t time_ms = app->settings.rfid_duration;
-        FURI_LOG_I(TAG, "RFID: Emulating (%s) for %d ms", file_name, time_ms);
-        int16_t interval_ms = 100;
-        while(time_ms > 0) {
-            furi_delay_ms(interval_ms);
-            time_ms -= interval_ms;
-        }
-        FURI_LOG_I(TAG, "RFID: Emulation stopped");
-
-        lfrfid_worker_stop(worker);
-        lfrfid_worker_stop_thread(worker);
-        lfrfid_worker_free(worker);
-    }
-
-    furi_string_free(temp_str);
-    free(data);
-    protocol_dict_free(dict);
-    flipper_format_free(fff_data_file);
-}

+ 0 - 223
quac/actions/action_subghz.c

@@ -1,223 +0,0 @@
-// Methods for Sub-GHz transmission
-
-#include <flipper_format/flipper_format_i.h>
-#include <path.h>
-#include <string.h>
-
-#include "helpers/subghz_txrx.h"
-
-#include "action_i.h"
-#include "quac.h"
-
-#define SUBGHZ_DIR_PATH EXT_PATH("subghz/")
-
-typedef struct SubGhzNeedSaveContext {
-    App* app;
-    SubGhzTxRx* txrx;
-    FuriString* file_path;
-} SubGhzNeedSaveContext;
-
-void action_subghz_need_save_callback(void* context) {
-    FURI_LOG_I(TAG, "Saving udpated subghz signal");
-    SubGhzNeedSaveContext* savectx = (SubGhzNeedSaveContext*)context;
-    FlipperFormat* ff = subghz_txrx_get_fff_data(savectx->txrx);
-
-    Stream* ff_stream = flipper_format_get_raw_stream(ff);
-    flipper_format_delete_key(ff, "Repeat");
-    flipper_format_delete_key(ff, "Manufacture");
-
-    do {
-        if(!storage_simply_remove(
-               savectx->app->storage, furi_string_get_cstr(savectx->file_path))) {
-            FURI_LOG_E(TAG, "Failed to delete subghz file before re-save");
-            break;
-        }
-        stream_seek(ff_stream, 0, StreamOffsetFromStart);
-        stream_save_to_file(
-            ff_stream,
-            savectx->app->storage,
-            furi_string_get_cstr(savectx->file_path),
-            FSOM_CREATE_ALWAYS);
-        if(storage_common_stat(
-               savectx->app->storage, furi_string_get_cstr(savectx->file_path), NULL) != FSE_OK) {
-            FURI_LOG_E(TAG, "Error verifying new subghz file after re-save");
-            break;
-        }
-    } while(0);
-
-    // Update original .sub file.
-    //In case when rolling code was used in Quac we must update original .sub file with actual rolling code counter
-
-    // Take file name from quac_app path
-    FuriString* quac_filename = furi_string_alloc();
-    furi_string_reset(quac_filename);
-    path_extract_filename(savectx->file_path, quac_filename, false);
-    FURI_LOG_I(TAG, "Extracted quac filename: %s", furi_string_get_cstr(quac_filename));
-
-    //create new char string with full path (dir+filename) to original subghz folder
-    char* full_subghz_file_name =
-        malloc(1 + strlen(SUBGHZ_DIR_PATH) + strlen(furi_string_get_cstr(quac_filename)));
-    strcpy(full_subghz_file_name, SUBGHZ_DIR_PATH);
-    strcat(full_subghz_file_name, furi_string_get_cstr(quac_filename));
-    FURI_LOG_I(TAG, "Full path to safe file: %s", full_subghz_file_name);
-
-    //Save subghz file to original subghz location
-    do {
-        if(!storage_simply_remove(savectx->app->storage, full_subghz_file_name)) {
-            FURI_LOG_E(
-                TAG, "Failed to delete subghz file before re-save in original SUBGHZ location");
-            break;
-        }
-        stream_seek(ff_stream, 0, StreamOffsetFromStart);
-        stream_save_to_file(
-            ff_stream, savectx->app->storage, full_subghz_file_name, FSOM_CREATE_ALWAYS);
-        if(storage_common_stat(savectx->app->storage, full_subghz_file_name, NULL) != FSE_OK) {
-            FURI_LOG_E(
-                TAG, "Error verifying new subghz file after re-save in original SUBGHZ location");
-            break;
-        }
-    } while(0);
-
-    free(full_subghz_file_name);
-    furi_string_free(quac_filename);
-}
-
-void action_subghz_tx(void* context, FuriString* action_path, FuriString* error) {
-    App* app = context;
-    const char* file_name = furi_string_get_cstr(action_path);
-
-    FlipperFormat* fff_data_file = flipper_format_file_alloc(app->storage);
-
-    SubGhzTxRx* txrx = subghz_txrx_alloc();
-
-    SubGhzNeedSaveContext save_context = {app, txrx, action_path};
-    subghz_txrx_set_need_save_callback(txrx, action_subghz_need_save_callback, &save_context);
-
-    Stream* fff_data_stream = flipper_format_get_raw_stream(subghz_txrx_get_fff_data(txrx));
-    stream_clean(fff_data_stream);
-
-    FuriString* preset_name = furi_string_alloc();
-    FuriString* protocol_name = furi_string_alloc();
-
-    FuriString* temp_str;
-    temp_str = furi_string_alloc();
-    uint32_t temp_data32;
-
-    uint32_t frequency = 0;
-
-    FURI_LOG_I(TAG, "SUBGHZ: Action starting...");
-
-    do {
-        if(!flipper_format_file_open_existing(fff_data_file, file_name)) {
-            FURI_LOG_E(TAG, "Error opening %s", file_name);
-            ACTION_SET_ERROR("SUBGHZ: Error opening %s", file_name);
-            break;
-        }
-
-        if(!flipper_format_read_header(fff_data_file, temp_str, &temp_data32)) {
-            FURI_LOG_E(TAG, "Missing or incorrect header");
-            ACTION_SET_ERROR("SUBGHZ: Missing or incorrect header");
-            break;
-        }
-
-        if(((!strcmp(furi_string_get_cstr(temp_str), SUBGHZ_KEY_FILE_TYPE)) ||
-            (!strcmp(furi_string_get_cstr(temp_str), SUBGHZ_RAW_FILE_TYPE))) &&
-           temp_data32 == SUBGHZ_KEY_FILE_VERSION) {
-        } else {
-            FURI_LOG_E(TAG, "Type or version mismatch");
-            ACTION_SET_ERROR("SUBGHZ: Type or version mismatch");
-            break;
-        }
-
-        SubGhzSetting* setting = subghz_txrx_get_setting(txrx);
-        if(!flipper_format_read_uint32(fff_data_file, "Frequency", &frequency, 1)) {
-            FURI_LOG_W(TAG, "Missing Frequency. Setting default frequency");
-            frequency = subghz_setting_get_default_frequency(setting);
-        } else if(!subghz_txrx_radio_device_is_frequecy_valid(txrx, frequency)) {
-            FURI_LOG_E(TAG, "Frequency not supported on the chosen radio module");
-            ACTION_SET_ERROR("SUBGHZ: Frequency not supported");
-            break;
-        }
-
-        if(!flipper_format_read_string(fff_data_file, "Preset", temp_str)) {
-            FURI_LOG_E(TAG, "Missing Preset");
-            ACTION_SET_ERROR("SUBGHZ: Missing preset");
-            break;
-        }
-
-        furi_string_set_str(
-            temp_str, subghz_txrx_get_preset_name(txrx, furi_string_get_cstr(temp_str)));
-        if(!strcmp(furi_string_get_cstr(temp_str), "")) {
-            FURI_LOG_E(TAG, "Unknown preset");
-            ACTION_SET_ERROR("SUBGHZ: Unknown preset");
-            break;
-        }
-
-        if(!strcmp(furi_string_get_cstr(temp_str), "CUSTOM")) {
-            subghz_setting_delete_custom_preset(setting, furi_string_get_cstr(temp_str));
-            if(!subghz_setting_load_custom_preset(
-                   setting, furi_string_get_cstr(temp_str), fff_data_file)) {
-                FURI_LOG_E(TAG, "Missing Custom preset");
-                ACTION_SET_ERROR("SUBGHZ: Missing Custom preset");
-                break;
-            }
-        }
-        furi_string_set(preset_name, temp_str);
-        size_t preset_index =
-            subghz_setting_get_inx_preset_by_name(setting, furi_string_get_cstr(preset_name));
-        subghz_txrx_set_preset(
-            txrx,
-            furi_string_get_cstr(preset_name),
-            frequency,
-            subghz_setting_get_preset_data(setting, preset_index),
-            subghz_setting_get_preset_data_size(setting, preset_index));
-
-        // Load Protocol
-        if(!flipper_format_read_string(fff_data_file, "Protocol", protocol_name)) {
-            FURI_LOG_E(TAG, "Missing protocol");
-            ACTION_SET_ERROR("SUBGHZ: Missing protocol");
-            break;
-        }
-
-        FlipperFormat* fff_data = subghz_txrx_get_fff_data(txrx);
-        if(!strcmp(furi_string_get_cstr(protocol_name), "RAW")) {
-            subghz_protocol_raw_gen_fff_data(
-                fff_data, file_name, subghz_txrx_radio_device_get_name(txrx));
-        } else {
-            stream_copy_full(
-                flipper_format_get_raw_stream(fff_data_file),
-                flipper_format_get_raw_stream(fff_data));
-        }
-
-        if(subghz_txrx_load_decoder_by_name_protocol(txrx, furi_string_get_cstr(protocol_name))) {
-            SubGhzProtocolStatus status =
-                subghz_protocol_decoder_base_deserialize(subghz_txrx_get_decoder(txrx), fff_data);
-            if(status != SubGhzProtocolStatusOk) {
-                break;
-            }
-        } else {
-            FURI_LOG_E(TAG, "Protocol not found: %s", furi_string_get_cstr(protocol_name));
-            break;
-        }
-    } while(false);
-
-    flipper_format_file_close(fff_data_file);
-    flipper_format_free(fff_data_file);
-
-    if(subghz_txrx_tx_start(txrx, subghz_txrx_get_fff_data(txrx)) != SubGhzTxRxStartTxStateOk) {
-        FURI_LOG_E(TAG, "Failed to start TX");
-    }
-
-    // TODO: Should this be based on a Setting?
-    furi_delay_ms(100);
-
-    FURI_LOG_I(TAG, "SUBGHZ: Action complete.");
-
-    // This will call need_save_callback, if necessary
-    subghz_txrx_stop(txrx);
-
-    subghz_txrx_free(txrx);
-    furi_string_free(preset_name);
-    furi_string_free(protocol_name);
-    furi_string_free(temp_str);
-}

+ 0 - 613
quac/actions/helpers/subghz_txrx.c

@@ -1,613 +0,0 @@
-#include "subghz_txrx_i.h" // IWYU pragma: keep
-
-#include <lib/subghz/subghz_protocol_registry.h>
-#include <applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h>
-#include <lib/subghz/devices/cc1101_int/cc1101_int_interconnect.h>
-
-#define TAG "SubGhz"
-
-static void subghz_txrx_radio_device_power_on(SubGhzTxRx* instance) {
-    UNUSED(instance);
-    uint8_t attempts = 5;
-    while(--attempts > 0) {
-        if(furi_hal_power_enable_otg()) break;
-    }
-    if(attempts == 0) {
-        if(furi_hal_power_get_usb_voltage() < 4.5f) {
-            FURI_LOG_E(
-                TAG,
-                "Error power otg enable. BQ2589 check otg fault = %d",
-                furi_hal_power_check_otg_fault() ? 1 : 0);
-        }
-    }
-}
-
-static void subghz_txrx_radio_device_power_off(SubGhzTxRx* instance) {
-    UNUSED(instance);
-    if(furi_hal_power_is_otg_enabled()) furi_hal_power_disable_otg();
-}
-
-SubGhzTxRx* subghz_txrx_alloc(void) {
-    SubGhzTxRx* instance = malloc(sizeof(SubGhzTxRx));
-    instance->setting = subghz_setting_alloc();
-    subghz_setting_load(instance->setting, EXT_PATH("subghz/assets/setting_user"));
-
-    instance->preset = malloc(sizeof(SubGhzRadioPreset));
-    instance->preset->name = furi_string_alloc();
-    subghz_txrx_set_preset(
-        instance, "AM650", subghz_setting_get_default_frequency(instance->setting), NULL, 0);
-
-    instance->txrx_state = SubGhzTxRxStateSleep;
-
-    subghz_txrx_hopper_set_state(instance, SubGhzHopperStateOFF);
-    subghz_txrx_speaker_set_state(instance, SubGhzSpeakerStateDisable);
-
-    instance->worker = subghz_worker_alloc();
-    instance->fff_data = flipper_format_string_alloc();
-
-    instance->environment = subghz_environment_alloc();
-    instance->is_database_loaded =
-        subghz_environment_load_keystore(instance->environment, SUBGHZ_KEYSTORE_DIR_NAME);
-    subghz_environment_load_keystore(instance->environment, SUBGHZ_KEYSTORE_DIR_USER_NAME);
-    subghz_environment_set_came_atomo_rainbow_table_file_name(
-        instance->environment, SUBGHZ_CAME_ATOMO_DIR_NAME);
-    subghz_environment_set_alutech_at_4n_rainbow_table_file_name(
-        instance->environment, SUBGHZ_ALUTECH_AT_4N_DIR_NAME);
-    subghz_environment_set_nice_flor_s_rainbow_table_file_name(
-        instance->environment, SUBGHZ_NICE_FLOR_S_DIR_NAME);
-    subghz_environment_set_protocol_registry(
-        instance->environment, (void*)&subghz_protocol_registry);
-    instance->receiver = subghz_receiver_alloc_init(instance->environment);
-
-    subghz_worker_set_overrun_callback(
-        instance->worker, (SubGhzWorkerOverrunCallback)subghz_receiver_reset);
-    subghz_worker_set_pair_callback(
-        instance->worker, (SubGhzWorkerPairCallback)subghz_receiver_decode);
-    subghz_worker_set_context(instance->worker, instance->receiver);
-
-    //set default device External
-    subghz_devices_init();
-    instance->radio_device_type = SubGhzRadioDeviceTypeInternal;
-    instance->radio_device_type =
-        subghz_txrx_radio_device_set(instance, SubGhzRadioDeviceTypeExternalCC1101);
-    FURI_LOG_I(
-        TAG,
-        "radio device type = %s",
-        instance->radio_device_type == SubGhzRadioDeviceTypeInternal ? "Internal" : "External");
-
-    return instance;
-}
-
-void subghz_txrx_free(SubGhzTxRx* instance) {
-    furi_assert(instance);
-
-    if(instance->radio_device_type != SubGhzRadioDeviceTypeInternal) {
-        subghz_txrx_radio_device_power_off(instance);
-        subghz_devices_end(instance->radio_device);
-    }
-
-    subghz_devices_deinit();
-
-    subghz_worker_free(instance->worker);
-    subghz_receiver_free(instance->receiver);
-    subghz_environment_free(instance->environment);
-    flipper_format_free(instance->fff_data);
-    furi_string_free(instance->preset->name);
-    subghz_setting_free(instance->setting);
-
-    free(instance->preset);
-    free(instance);
-}
-
-bool subghz_txrx_is_database_loaded(SubGhzTxRx* instance) {
-    furi_assert(instance);
-    return instance->is_database_loaded;
-}
-
-void subghz_txrx_set_preset(
-    SubGhzTxRx* instance,
-    const char* preset_name,
-    uint32_t frequency,
-    uint8_t* preset_data,
-    size_t preset_data_size) {
-    furi_assert(instance);
-    furi_string_set(instance->preset->name, preset_name);
-    SubGhzRadioPreset* preset = instance->preset;
-    preset->frequency = frequency;
-    preset->data = preset_data;
-    preset->data_size = preset_data_size;
-}
-
-const char* subghz_txrx_get_preset_name(SubGhzTxRx* instance, const char* preset) {
-    UNUSED(instance);
-    const char* preset_name = "";
-    if(!strcmp(preset, "FuriHalSubGhzPresetOok270Async")) {
-        preset_name = "AM270";
-    } else if(!strcmp(preset, "FuriHalSubGhzPresetOok650Async")) {
-        preset_name = "AM650";
-    } else if(!strcmp(preset, "FuriHalSubGhzPreset2FSKDev238Async")) {
-        preset_name = "FM238";
-    } else if(!strcmp(preset, "FuriHalSubGhzPreset2FSKDev476Async")) {
-        preset_name = "FM476";
-    } else if(!strcmp(preset, "FuriHalSubGhzPresetCustom")) {
-        preset_name = "CUSTOM";
-    } else {
-        FURI_LOG_E(TAG, "Unknown preset");
-    }
-    return preset_name;
-}
-
-SubGhzRadioPreset subghz_txrx_get_preset(SubGhzTxRx* instance) {
-    furi_assert(instance);
-    return *instance->preset;
-}
-
-void subghz_txrx_get_frequency_and_modulation(
-    SubGhzTxRx* instance,
-    FuriString* frequency,
-    FuriString* modulation) {
-    furi_assert(instance);
-    SubGhzRadioPreset* preset = instance->preset;
-    if(frequency != NULL) {
-        furi_string_printf(
-            frequency,
-            "%03ld.%02ld",
-            preset->frequency / 1000000 % 1000,
-            preset->frequency / 10000 % 100);
-    }
-    if(modulation != NULL) {
-        furi_string_printf(modulation, "%.2s", furi_string_get_cstr(preset->name));
-    }
-}
-
-static void subghz_txrx_begin(SubGhzTxRx* instance, uint8_t* preset_data) {
-    furi_assert(instance);
-    subghz_devices_reset(instance->radio_device);
-    subghz_devices_idle(instance->radio_device);
-    subghz_devices_load_preset(instance->radio_device, FuriHalSubGhzPresetCustom, preset_data);
-    instance->txrx_state = SubGhzTxRxStateIDLE;
-}
-
-static uint32_t subghz_txrx_rx(SubGhzTxRx* instance, uint32_t frequency) {
-    furi_assert(instance);
-
-    furi_assert(
-        instance->txrx_state != SubGhzTxRxStateRx && instance->txrx_state != SubGhzTxRxStateSleep);
-
-    subghz_devices_idle(instance->radio_device);
-
-    uint32_t value = subghz_devices_set_frequency(instance->radio_device, frequency);
-    subghz_devices_flush_rx(instance->radio_device);
-    subghz_txrx_speaker_on(instance);
-
-    subghz_devices_start_async_rx(
-        instance->radio_device, subghz_worker_rx_callback, instance->worker);
-    subghz_worker_start(instance->worker);
-    instance->txrx_state = SubGhzTxRxStateRx;
-    return value;
-}
-
-static void subghz_txrx_idle(SubGhzTxRx* instance) {
-    furi_assert(instance);
-    if(instance->txrx_state != SubGhzTxRxStateSleep) {
-        subghz_devices_idle(instance->radio_device);
-        subghz_txrx_speaker_off(instance);
-        instance->txrx_state = SubGhzTxRxStateIDLE;
-    }
-}
-
-static void subghz_txrx_rx_end(SubGhzTxRx* instance) {
-    furi_assert(instance);
-    furi_assert(instance->txrx_state == SubGhzTxRxStateRx);
-
-    if(subghz_worker_is_running(instance->worker)) {
-        subghz_worker_stop(instance->worker);
-        subghz_devices_stop_async_rx(instance->radio_device);
-    }
-    subghz_devices_idle(instance->radio_device);
-    subghz_txrx_speaker_off(instance);
-    instance->txrx_state = SubGhzTxRxStateIDLE;
-}
-
-void subghz_txrx_sleep(SubGhzTxRx* instance) {
-    furi_assert(instance);
-    subghz_devices_sleep(instance->radio_device);
-    instance->txrx_state = SubGhzTxRxStateSleep;
-}
-
-static bool subghz_txrx_tx(SubGhzTxRx* instance, uint32_t frequency) {
-    furi_assert(instance);
-    furi_assert(instance->txrx_state != SubGhzTxRxStateSleep);
-    subghz_devices_idle(instance->radio_device);
-    subghz_devices_set_frequency(instance->radio_device, frequency);
-
-    bool ret = subghz_devices_set_tx(instance->radio_device);
-    if(ret) {
-        subghz_txrx_speaker_on(instance);
-        instance->txrx_state = SubGhzTxRxStateTx;
-    }
-
-    return ret;
-}
-
-SubGhzTxRxStartTxState subghz_txrx_tx_start(SubGhzTxRx* instance, FlipperFormat* flipper_format) {
-    furi_assert(instance);
-    furi_assert(flipper_format);
-
-    subghz_txrx_stop(instance);
-
-    SubGhzTxRxStartTxState ret = SubGhzTxRxStartTxStateErrorParserOthers;
-    FuriString* temp_str = furi_string_alloc();
-    uint32_t repeat = 200;
-    do {
-        if(!flipper_format_rewind(flipper_format)) {
-            FURI_LOG_E(TAG, "Rewind error");
-            break;
-        }
-        if(!flipper_format_read_string(flipper_format, "Protocol", temp_str)) {
-            FURI_LOG_E(TAG, "Missing Protocol");
-            break;
-        }
-        if(!flipper_format_insert_or_update_uint32(flipper_format, "Repeat", &repeat, 1)) {
-            FURI_LOG_E(TAG, "Unable Repeat");
-            break;
-        }
-        ret = SubGhzTxRxStartTxStateOk;
-
-        SubGhzRadioPreset* preset = instance->preset;
-        instance->transmitter =
-            subghz_transmitter_alloc_init(instance->environment, furi_string_get_cstr(temp_str));
-
-        if(instance->transmitter) {
-            if(subghz_transmitter_deserialize(instance->transmitter, flipper_format) ==
-               SubGhzProtocolStatusOk) {
-                if(strcmp(furi_string_get_cstr(preset->name), "") != 0) {
-                    subghz_txrx_begin(
-                        instance,
-                        subghz_setting_get_preset_data_by_name(
-                            instance->setting, furi_string_get_cstr(preset->name)));
-                    if(preset->frequency) {
-                        if(!subghz_txrx_tx(instance, preset->frequency)) {
-                            FURI_LOG_E(TAG, "Only Rx");
-                            ret = SubGhzTxRxStartTxStateErrorOnlyRx;
-                        }
-                    } else {
-                        ret = SubGhzTxRxStartTxStateErrorParserOthers;
-                    }
-
-                } else {
-                    FURI_LOG_E(
-                        TAG, "Unknown name preset \" %s \"", furi_string_get_cstr(preset->name));
-                    ret = SubGhzTxRxStartTxStateErrorParserOthers;
-                }
-
-                if(ret == SubGhzTxRxStartTxStateOk) {
-                    //Start TX
-                    subghz_devices_start_async_tx(
-                        instance->radio_device, subghz_transmitter_yield, instance->transmitter);
-                }
-            } else {
-                ret = SubGhzTxRxStartTxStateErrorParserOthers;
-            }
-        } else {
-            ret = SubGhzTxRxStartTxStateErrorParserOthers;
-        }
-        if(ret != SubGhzTxRxStartTxStateOk) {
-            subghz_transmitter_free(instance->transmitter);
-            if(instance->txrx_state != SubGhzTxRxStateIDLE) {
-                subghz_txrx_idle(instance);
-            }
-        }
-
-    } while(false);
-    furi_string_free(temp_str);
-    return ret;
-}
-
-void subghz_txrx_rx_start(SubGhzTxRx* instance) {
-    furi_assert(instance);
-    subghz_txrx_stop(instance);
-    subghz_txrx_begin(
-        instance,
-        subghz_setting_get_preset_data_by_name(
-            subghz_txrx_get_setting(instance), furi_string_get_cstr(instance->preset->name)));
-    subghz_txrx_rx(instance, instance->preset->frequency);
-}
-
-void subghz_txrx_set_need_save_callback(
-    SubGhzTxRx* instance,
-    SubGhzTxRxNeedSaveCallback callback,
-    void* context) {
-    furi_assert(instance);
-    instance->need_save_callback = callback;
-    instance->need_save_context = context;
-}
-
-static void subghz_txrx_tx_stop(SubGhzTxRx* instance) {
-    furi_assert(instance);
-    furi_assert(instance->txrx_state == SubGhzTxRxStateTx);
-    //Stop TX
-    subghz_devices_stop_async_tx(instance->radio_device);
-    subghz_transmitter_stop(instance->transmitter);
-    subghz_transmitter_free(instance->transmitter);
-
-    //if protocol dynamic then we save the last upload
-    if(instance->decoder_result->protocol->type == SubGhzProtocolTypeDynamic) {
-        if(instance->need_save_callback) {
-            instance->need_save_callback(instance->need_save_context);
-        }
-    }
-    subghz_txrx_idle(instance);
-    subghz_txrx_speaker_off(instance);
-}
-
-FlipperFormat* subghz_txrx_get_fff_data(SubGhzTxRx* instance) {
-    furi_assert(instance);
-    return instance->fff_data;
-}
-
-SubGhzSetting* subghz_txrx_get_setting(SubGhzTxRx* instance) {
-    furi_assert(instance);
-    return instance->setting;
-}
-
-void subghz_txrx_stop(SubGhzTxRx* instance) {
-    furi_assert(instance);
-
-    switch(instance->txrx_state) {
-    case SubGhzTxRxStateTx:
-        subghz_txrx_tx_stop(instance);
-        subghz_txrx_speaker_unmute(instance);
-        break;
-    case SubGhzTxRxStateRx:
-        subghz_txrx_rx_end(instance);
-        subghz_txrx_speaker_mute(instance);
-        break;
-
-    default:
-        break;
-    }
-}
-
-void subghz_txrx_hopper_update(SubGhzTxRx* instance) {
-    furi_assert(instance);
-
-    switch(instance->hopper_state) {
-    case SubGhzHopperStateOFF:
-    case SubGhzHopperStatePause:
-        return;
-    case SubGhzHopperStateRSSITimeOut:
-        if(instance->hopper_timeout != 0) {
-            instance->hopper_timeout--;
-            return;
-        }
-        break;
-    default:
-        break;
-    }
-    float rssi = -127.0f;
-    if(instance->hopper_state != SubGhzHopperStateRSSITimeOut) {
-        // See RSSI Calculation timings in CC1101 17.3 RSSI
-        rssi = subghz_devices_get_rssi(instance->radio_device);
-
-        // Stay if RSSI is high enough
-        if(rssi > -90.0f) {
-            instance->hopper_timeout = 10;
-            instance->hopper_state = SubGhzHopperStateRSSITimeOut;
-            return;
-        }
-    } else {
-        instance->hopper_state = SubGhzHopperStateRunnig;
-    }
-    // Select next frequency
-    if(instance->hopper_idx_frequency <
-       subghz_setting_get_hopper_frequency_count(instance->setting) - 1) {
-        instance->hopper_idx_frequency++;
-    } else {
-        instance->hopper_idx_frequency = 0;
-    }
-
-    if(instance->txrx_state == SubGhzTxRxStateRx) {
-        subghz_txrx_rx_end(instance);
-    };
-    if(instance->txrx_state == SubGhzTxRxStateIDLE) {
-        subghz_receiver_reset(instance->receiver);
-        instance->preset->frequency =
-            subghz_setting_get_hopper_frequency(instance->setting, instance->hopper_idx_frequency);
-        subghz_txrx_rx(instance, instance->preset->frequency);
-    }
-}
-
-SubGhzHopperState subghz_txrx_hopper_get_state(SubGhzTxRx* instance) {
-    furi_assert(instance);
-    return instance->hopper_state;
-}
-
-void subghz_txrx_hopper_set_state(SubGhzTxRx* instance, SubGhzHopperState state) {
-    furi_assert(instance);
-    instance->hopper_state = state;
-}
-
-void subghz_txrx_hopper_unpause(SubGhzTxRx* instance) {
-    furi_assert(instance);
-    if(instance->hopper_state == SubGhzHopperStatePause) {
-        instance->hopper_state = SubGhzHopperStateRunnig;
-    }
-}
-
-void subghz_txrx_hopper_pause(SubGhzTxRx* instance) {
-    furi_assert(instance);
-    if(instance->hopper_state == SubGhzHopperStateRunnig) {
-        instance->hopper_state = SubGhzHopperStatePause;
-    }
-}
-
-void subghz_txrx_speaker_on(SubGhzTxRx* instance) {
-    furi_assert(instance);
-    if(instance->speaker_state == SubGhzSpeakerStateEnable) {
-        if(furi_hal_speaker_acquire(30)) {
-            subghz_devices_set_async_mirror_pin(instance->radio_device, &gpio_speaker);
-        } else {
-            instance->speaker_state = SubGhzSpeakerStateDisable;
-        }
-    }
-}
-
-void subghz_txrx_speaker_off(SubGhzTxRx* instance) {
-    furi_assert(instance);
-    if(instance->speaker_state != SubGhzSpeakerStateDisable) {
-        if(furi_hal_speaker_is_mine()) {
-            subghz_devices_set_async_mirror_pin(instance->radio_device, NULL);
-            furi_hal_speaker_release();
-            if(instance->speaker_state == SubGhzSpeakerStateShutdown)
-                instance->speaker_state = SubGhzSpeakerStateDisable;
-        }
-    }
-}
-
-void subghz_txrx_speaker_mute(SubGhzTxRx* instance) {
-    furi_assert(instance);
-    if(instance->speaker_state == SubGhzSpeakerStateEnable) {
-        if(furi_hal_speaker_is_mine()) {
-            subghz_devices_set_async_mirror_pin(instance->radio_device, NULL);
-        }
-    }
-}
-
-void subghz_txrx_speaker_unmute(SubGhzTxRx* instance) {
-    furi_assert(instance);
-    if(instance->speaker_state == SubGhzSpeakerStateEnable) {
-        if(furi_hal_speaker_is_mine()) {
-            subghz_devices_set_async_mirror_pin(instance->radio_device, &gpio_speaker);
-        }
-    }
-}
-
-void subghz_txrx_speaker_set_state(SubGhzTxRx* instance, SubGhzSpeakerState state) {
-    furi_assert(instance);
-    instance->speaker_state = state;
-}
-
-SubGhzSpeakerState subghz_txrx_speaker_get_state(SubGhzTxRx* instance) {
-    furi_assert(instance);
-    return instance->speaker_state;
-}
-
-bool subghz_txrx_load_decoder_by_name_protocol(SubGhzTxRx* instance, const char* name_protocol) {
-    furi_assert(instance);
-    furi_assert(name_protocol);
-    bool res = false;
-    instance->decoder_result =
-        subghz_receiver_search_decoder_base_by_name(instance->receiver, name_protocol);
-    if(instance->decoder_result) {
-        res = true;
-    }
-    return res;
-}
-
-SubGhzProtocolDecoderBase* subghz_txrx_get_decoder(SubGhzTxRx* instance) {
-    furi_assert(instance);
-    return instance->decoder_result;
-}
-
-bool subghz_txrx_protocol_is_serializable(SubGhzTxRx* instance) {
-    furi_assert(instance);
-    return (instance->decoder_result->protocol->flag & SubGhzProtocolFlag_Save) ==
-           SubGhzProtocolFlag_Save;
-}
-
-bool subghz_txrx_protocol_is_transmittable(SubGhzTxRx* instance, bool check_type) {
-    furi_assert(instance);
-    const SubGhzProtocol* protocol = instance->decoder_result->protocol;
-    if(check_type) {
-        return ((protocol->flag & SubGhzProtocolFlag_Send) == SubGhzProtocolFlag_Send) &&
-               protocol->encoder->deserialize && protocol->type == SubGhzProtocolTypeStatic;
-    }
-    return ((protocol->flag & SubGhzProtocolFlag_Send) == SubGhzProtocolFlag_Send) &&
-           protocol->encoder->deserialize;
-}
-
-void subghz_txrx_receiver_set_filter(SubGhzTxRx* instance, SubGhzProtocolFlag filter) {
-    furi_assert(instance);
-    subghz_receiver_set_filter(instance->receiver, filter);
-}
-
-void subghz_txrx_set_rx_calback(
-    SubGhzTxRx* instance,
-    SubGhzReceiverCallback callback,
-    void* context) {
-    subghz_receiver_set_rx_callback(instance->receiver, callback, context);
-}
-
-void subghz_txrx_set_raw_file_encoder_worker_callback_end(
-    SubGhzTxRx* instance,
-    SubGhzProtocolEncoderRAWCallbackEnd callback,
-    void* context) {
-    subghz_protocol_raw_file_encoder_worker_set_callback_end(
-        (SubGhzProtocolEncoderRAW*)subghz_transmitter_get_protocol_instance(instance->transmitter),
-        callback,
-        context);
-}
-
-bool subghz_txrx_radio_device_is_external_connected(SubGhzTxRx* instance, const char* name) {
-    furi_assert(instance);
-
-    bool is_connect = false;
-    bool is_otg_enabled = furi_hal_power_is_otg_enabled();
-
-    if(!is_otg_enabled) {
-        subghz_txrx_radio_device_power_on(instance);
-    }
-
-    const SubGhzDevice* device = subghz_devices_get_by_name(name);
-    if(device) {
-        is_connect = subghz_devices_is_connect(device);
-    }
-
-    if(!is_otg_enabled) {
-        subghz_txrx_radio_device_power_off(instance);
-    }
-    return is_connect;
-}
-
-SubGhzRadioDeviceType
-    subghz_txrx_radio_device_set(SubGhzTxRx* instance, SubGhzRadioDeviceType radio_device_type) {
-    furi_assert(instance);
-
-    if(radio_device_type == SubGhzRadioDeviceTypeExternalCC1101 &&
-       subghz_txrx_radio_device_is_external_connected(instance, SUBGHZ_DEVICE_CC1101_EXT_NAME)) {
-        subghz_txrx_radio_device_power_on(instance);
-        instance->radio_device = subghz_devices_get_by_name(SUBGHZ_DEVICE_CC1101_EXT_NAME);
-        subghz_devices_begin(instance->radio_device);
-        instance->radio_device_type = SubGhzRadioDeviceTypeExternalCC1101;
-    } else {
-        subghz_txrx_radio_device_power_off(instance);
-        if(instance->radio_device_type != SubGhzRadioDeviceTypeInternal) {
-            subghz_devices_end(instance->radio_device);
-        }
-        instance->radio_device = subghz_devices_get_by_name(SUBGHZ_DEVICE_CC1101_INT_NAME);
-        instance->radio_device_type = SubGhzRadioDeviceTypeInternal;
-    }
-
-    return instance->radio_device_type;
-}
-
-SubGhzRadioDeviceType subghz_txrx_radio_device_get(SubGhzTxRx* instance) {
-    furi_assert(instance);
-    return instance->radio_device_type;
-}
-
-float subghz_txrx_radio_device_get_rssi(SubGhzTxRx* instance) {
-    furi_assert(instance);
-    return subghz_devices_get_rssi(instance->radio_device);
-}
-
-const char* subghz_txrx_radio_device_get_name(SubGhzTxRx* instance) {
-    furi_assert(instance);
-    return subghz_devices_get_name(instance->radio_device);
-}
-
-bool subghz_txrx_radio_device_is_frequecy_valid(SubGhzTxRx* instance, uint32_t frequency) {
-    furi_assert(instance);
-    return subghz_devices_is_frequency_valid(instance->radio_device, frequency);
-}

+ 0 - 336
quac/actions/helpers/subghz_txrx.h

@@ -1,336 +0,0 @@
-#pragma once
-
-#include "subghz_types.h"
-
-#include <lib/subghz/subghz_worker.h>
-#include <lib/subghz/subghz_setting.h>
-#include <lib/subghz/receiver.h>
-#include <lib/subghz/transmitter.h>
-#include <lib/subghz/protocols/raw.h>
-#include <lib/subghz/devices/devices.h>
-
-typedef struct SubGhzTxRx SubGhzTxRx;
-
-typedef void (*SubGhzTxRxNeedSaveCallback)(void* context);
-
-typedef enum {
-    SubGhzTxRxStartTxStateOk,
-    SubGhzTxRxStartTxStateErrorOnlyRx,
-    SubGhzTxRxStartTxStateErrorParserOthers,
-} SubGhzTxRxStartTxState;
-
-/**
- * Allocate SubGhzTxRx
- * 
- * @return SubGhzTxRx* pointer to SubGhzTxRx
- */
-SubGhzTxRx* subghz_txrx_alloc(void);
-
-/**
- * Free SubGhzTxRx
- * 
- * @param instance Pointer to a SubGhzTxRx
- */
-void subghz_txrx_free(SubGhzTxRx* instance);
-
-/**
- * Check if the database is loaded
- * 
- * @param instance Pointer to a SubGhzTxRx
- * @return bool True if the database is loaded
- */
-bool subghz_txrx_is_database_loaded(SubGhzTxRx* instance);
-
-/**
- * Set preset 
- * 
- * @param instance Pointer to a SubGhzTxRx
- * @param preset_name Name of preset
- * @param frequency Frequency in Hz
- * @param preset_data Data of preset
- * @param preset_data_size Size of preset data
- */
-void subghz_txrx_set_preset(
-    SubGhzTxRx* instance,
-    const char* preset_name,
-    uint32_t frequency,
-    uint8_t* preset_data,
-    size_t preset_data_size);
-
-/**
- * Get name of preset
- * 
- * @param instance Pointer to a SubGhzTxRx
- * @param preset String of preset 
- * @return const char*  Name of preset
- */
-const char* subghz_txrx_get_preset_name(SubGhzTxRx* instance, const char* preset);
-
-/**
- * Get of preset
- * 
- * @param instance Pointer to a SubGhzTxRx
- * @return SubGhzRadioPreset Preset
- */
-SubGhzRadioPreset subghz_txrx_get_preset(SubGhzTxRx* instance);
-
-/**
- * Get string frequency and modulation
- * 
- * @param instance Pointer to a SubGhzTxRx
- * @param frequency Pointer to a string frequency
- * @param modulation Pointer to a string modulation
- */
-void subghz_txrx_get_frequency_and_modulation(
-    SubGhzTxRx* instance,
-    FuriString* frequency,
-    FuriString* modulation);
-
-/**
- * Start TX CC1101
- * 
- * @param instance Pointer to a SubGhzTxRx
- * @param flipper_format Pointer to a FlipperFormat
- * @return SubGhzTxRxStartTxState 
- */
-SubGhzTxRxStartTxState subghz_txrx_tx_start(SubGhzTxRx* instance, FlipperFormat* flipper_format);
-
-/**
- * Start RX CC1101
- * 
- * @param instance Pointer to a SubGhzTxRx
- */
-void subghz_txrx_rx_start(SubGhzTxRx* instance);
-
-/**
- * Stop TX/RX CC1101
- * 
- * @param instance Pointer to a SubGhzTxRx
- */
-void subghz_txrx_stop(SubGhzTxRx* instance);
-
-/**
- * Set sleep mode CC1101
- * 
- * @param instance Pointer to a SubGhzTxRx
- */
-void subghz_txrx_sleep(SubGhzTxRx* instance);
-
-/**
- * Update frequency CC1101 in automatic mode (hopper)
- * 
- * @param instance Pointer to a SubGhzTxRx
- */
-void subghz_txrx_hopper_update(SubGhzTxRx* instance);
-
-/**
- * Get state hopper
- * 
- * @param instance Pointer to a SubGhzTxRx
- * @return SubGhzHopperState 
- */
-SubGhzHopperState subghz_txrx_hopper_get_state(SubGhzTxRx* instance);
-
-/**
- * Set state hopper
- * 
- * @param instance Pointer to a SubGhzTxRx
- * @param state State hopper
- */
-void subghz_txrx_hopper_set_state(SubGhzTxRx* instance, SubGhzHopperState state);
-
-/**
- * Unpause hopper
- * 
- * @param instance Pointer to a SubGhzTxRx
- */
-void subghz_txrx_hopper_unpause(SubGhzTxRx* instance);
-
-/**
- * Set pause hopper
- * 
- * @param instance Pointer to a SubGhzTxRx
- */
-void subghz_txrx_hopper_pause(SubGhzTxRx* instance);
-
-/**
- * Speaker on
- * 
- * @param instance Pointer to a SubGhzTxRx 
- */
-void subghz_txrx_speaker_on(SubGhzTxRx* instance);
-
-/**
- * Speaker off
- * 
- * @param instance Pointer to a SubGhzTxRx 
- */
-void subghz_txrx_speaker_off(SubGhzTxRx* instance);
-
-/**
- * Speaker mute
- * 
- * @param instance Pointer to a SubGhzTxRx 
- */
-void subghz_txrx_speaker_mute(SubGhzTxRx* instance);
-
-/**
- * Speaker unmute
- * 
- * @param instance Pointer to a SubGhzTxRx 
- */
-void subghz_txrx_speaker_unmute(SubGhzTxRx* instance);
-
-/**
- * Set state speaker
- * 
- * @param instance Pointer to a SubGhzTxRx 
- * @param state State speaker
- */
-void subghz_txrx_speaker_set_state(SubGhzTxRx* instance, SubGhzSpeakerState state);
-
-/**
- * Get state speaker
- * 
- * @param instance Pointer to a SubGhzTxRx 
- * @return SubGhzSpeakerState 
- */
-SubGhzSpeakerState subghz_txrx_speaker_get_state(SubGhzTxRx* instance);
-
-/**
- * load decoder by name protocol
- * 
- * @param instance Pointer to a SubGhzTxRx
- * @param name_protocol Name protocol
- * @return bool True if the decoder is loaded 
- */
-bool subghz_txrx_load_decoder_by_name_protocol(SubGhzTxRx* instance, const char* name_protocol);
-
-/**
- * Get decoder
- * 
- * @param instance Pointer to a SubGhzTxRx
- * @return SubGhzProtocolDecoderBase* Pointer to a SubGhzProtocolDecoderBase
- */
-SubGhzProtocolDecoderBase* subghz_txrx_get_decoder(SubGhzTxRx* instance);
-
-/**
- * Set callback for save data
- * 
- * @param instance Pointer to a SubGhzTxRx
- * @param callback Callback for save data
- * @param context Context for callback
- */
-void subghz_txrx_set_need_save_callback(
-    SubGhzTxRx* instance,
-    SubGhzTxRxNeedSaveCallback callback,
-    void* context);
-
-/**
- * Get pointer to a load data key
- * 
- * @param instance Pointer to a SubGhzTxRx
- * @return FlipperFormat* 
- */
-FlipperFormat* subghz_txrx_get_fff_data(SubGhzTxRx* instance);
-
-/**
- * Get pointer to a SugGhzSetting
- * 
- * @param instance Pointer to a SubGhzTxRx
- * @return SubGhzSetting* 
- */
-SubGhzSetting* subghz_txrx_get_setting(SubGhzTxRx* instance);
-
-/**
- * Is it possible to save this protocol
- * 
- * @param instance Pointer to a SubGhzTxRx 
- * @return bool True if it is possible to save this protocol
- */
-bool subghz_txrx_protocol_is_serializable(SubGhzTxRx* instance);
-
-/**
- * Is it possible to send this protocol
- * 
- * @param instance Pointer to a SubGhzTxRx 
- * @return bool True if it is possible to send this protocol
- */
-bool subghz_txrx_protocol_is_transmittable(SubGhzTxRx* instance, bool check_type);
-
-/**
- * Set filter, what types of decoder to use 
- * 
- * @param instance Pointer to a SubGhzTxRx
- * @param filter Filter
- */
-void subghz_txrx_receiver_set_filter(SubGhzTxRx* instance, SubGhzProtocolFlag filter);
-
-/**
- * Set callback for receive data
- * 
- * @param instance Pointer to a SubGhzTxRx
- * @param callback Callback for receive data
- * @param context Context for callback
- */
-void subghz_txrx_set_rx_calback(
-    SubGhzTxRx* instance,
-    SubGhzReceiverCallback callback,
-    void* context);
-
-/**
- * Set callback for Raw decoder, end of data transfer  
- * 
- * @param instance Pointer to a SubGhzTxRx
- * @param callback Callback for Raw decoder, end of data transfer 
- * @param context Context for callback
- */
-void subghz_txrx_set_raw_file_encoder_worker_callback_end(
-    SubGhzTxRx* instance,
-    SubGhzProtocolEncoderRAWCallbackEnd callback,
-    void* context);
-
-/* Checking if an external radio device is connected
-* 
-* @param instance Pointer to a SubGhzTxRx
-* @param name Name of external radio device
-* @return bool True if is connected to the external radio device
-*/
-bool subghz_txrx_radio_device_is_external_connected(SubGhzTxRx* instance, const char* name);
-
-/* Set the selected radio device to use
-*
-* @param instance Pointer to a SubGhzTxRx
-* @param radio_device_type Radio device type
-* @return SubGhzRadioDeviceType Type of installed radio device
-*/
-SubGhzRadioDeviceType
-    subghz_txrx_radio_device_set(SubGhzTxRx* instance, SubGhzRadioDeviceType radio_device_type);
-
-/* Get the selected radio device to use
-*
-* @param instance Pointer to a SubGhzTxRx
-* @return SubGhzRadioDeviceType Type of installed radio device
-*/
-SubGhzRadioDeviceType subghz_txrx_radio_device_get(SubGhzTxRx* instance);
-
-/* Get RSSI the selected radio device to use
-*
-* @param instance Pointer to a SubGhzTxRx
-* @return float RSSI
-*/
-float subghz_txrx_radio_device_get_rssi(SubGhzTxRx* instance);
-
-/* Get name the selected radio device to use
-*
-* @param instance Pointer to a SubGhzTxRx
-* @return const char* Name of installed radio device
-*/
-const char* subghz_txrx_radio_device_get_name(SubGhzTxRx* instance);
-
-/* Get get intelligence whether frequency the selected radio device to use
-*
-* @param instance Pointer to a SubGhzTxRx
-* @return bool True if the frequency is valid
-*/
-bool subghz_txrx_radio_device_is_frequecy_valid(SubGhzTxRx* instance, uint32_t frequency);

+ 0 - 29
quac/actions/helpers/subghz_txrx_i.h

@@ -1,29 +0,0 @@
-#pragma once
-
-#include "subghz_txrx.h"
-
-struct SubGhzTxRx {
-    SubGhzWorker* worker;
-
-    SubGhzEnvironment* environment;
-    SubGhzReceiver* receiver;
-    SubGhzTransmitter* transmitter;
-    SubGhzProtocolDecoderBase* decoder_result;
-    FlipperFormat* fff_data;
-
-    SubGhzRadioPreset* preset;
-    SubGhzSetting* setting;
-
-    uint8_t hopper_timeout;
-    uint8_t hopper_idx_frequency;
-    bool is_database_loaded;
-    SubGhzHopperState hopper_state;
-
-    SubGhzTxRxState txrx_state;
-    SubGhzSpeakerState speaker_state;
-    const SubGhzDevice* radio_device;
-    SubGhzRadioDeviceType radio_device_type;
-
-    SubGhzTxRxNeedSaveCallback need_save_callback;
-    void* need_save_context;
-};

+ 0 - 91
quac/actions/helpers/subghz_types.h

@@ -1,91 +0,0 @@
-#pragma once
-
-#include <furi.h>
-#include <furi_hal.h>
-
-/** SubGhzNotification state */
-typedef enum {
-    SubGhzNotificationStateStarting,
-    SubGhzNotificationStateIDLE,
-    SubGhzNotificationStateTx,
-    SubGhzNotificationStateRx,
-    SubGhzNotificationStateRxDone,
-} SubGhzNotificationState;
-
-/** SubGhzTxRx state */
-typedef enum {
-    SubGhzTxRxStateIDLE,
-    SubGhzTxRxStateRx,
-    SubGhzTxRxStateTx,
-    SubGhzTxRxStateSleep,
-} SubGhzTxRxState;
-
-/** SubGhzHopperState state */
-typedef enum {
-    SubGhzHopperStateOFF,
-    SubGhzHopperStateRunnig,
-    SubGhzHopperStatePause,
-    SubGhzHopperStateRSSITimeOut,
-} SubGhzHopperState;
-
-/** SubGhzSpeakerState state */
-typedef enum {
-    SubGhzSpeakerStateDisable,
-    SubGhzSpeakerStateShutdown,
-    SubGhzSpeakerStateEnable,
-} SubGhzSpeakerState;
-
-/** SubGhzRadioDeviceType */
-typedef enum {
-    SubGhzRadioDeviceTypeAuto,
-    SubGhzRadioDeviceTypeInternal,
-    SubGhzRadioDeviceTypeExternalCC1101,
-} SubGhzRadioDeviceType;
-
-/** SubGhzRxKeyState state */
-typedef enum {
-    SubGhzRxKeyStateIDLE,
-    SubGhzRxKeyStateNoSave,
-    SubGhzRxKeyStateNeedSave,
-    SubGhzRxKeyStateBack,
-    SubGhzRxKeyStateStart,
-    SubGhzRxKeyStateAddKey,
-    SubGhzRxKeyStateExit,
-    SubGhzRxKeyStateRAWLoad,
-    SubGhzRxKeyStateRAWMore,
-    SubGhzRxKeyStateRAWSave,
-} SubGhzRxKeyState;
-
-/** SubGhzLoadKeyState state */
-typedef enum {
-    SubGhzLoadKeyStateUnknown,
-    SubGhzLoadKeyStateOK,
-    SubGhzLoadKeyStateParseErr,
-    SubGhzLoadKeyStateProtocolDescriptionErr,
-} SubGhzLoadKeyState;
-
-/** SubGhzLock */
-typedef enum {
-    SubGhzLockOff,
-    SubGhzLockOn,
-} SubGhzLock;
-
-typedef enum {
-    SubGhzViewIdMenu,
-    SubGhzViewIdReceiver,
-    SubGhzViewIdPopup,
-    SubGhzViewIdTextInput,
-    SubGhzViewIdWidget,
-    SubGhzViewIdTransmitter,
-    SubGhzViewIdVariableItemList,
-    SubGhzViewIdFrequencyAnalyzer,
-    SubGhzViewIdReadRAW,
-
-} SubGhzViewId;
-
-/** SubGhz load type file */
-typedef enum {
-    SubGhzLoadTypeFileNoLoad,
-    SubGhzLoadTypeFileKey,
-    SubGhzLoadTypeFileRaw,
-} SubGhzLoadTypeFile;

+ 0 - 17
quac/application.fam

@@ -1,17 +0,0 @@
-# For details & more options, see documentation/AppManifests.md in firmware repo
-
-App(
-    appid="quac",  # Must be unique
-    name="Quac!",  # Displayed in menus
-    apptype=FlipperAppType.EXTERNAL,
-    entry_point="quac_app",
-    stack_size=2 * 1024,
-    fap_category="Tools",
-    # Optional values
-    fap_version="0.8.0",
-    fap_icon="images/quac.png",  # 10x10 1-bit PNG
-    fap_description="Quick Action remote control app",
-    fap_author="Roberto De Feo",
-    fap_weburl="https://github.com/rdefeo/quac",
-    fap_icon_assets="images",  # Image assets to compile for this application
-)

+ 0 - 0
quac/images/.gitkeep


BIN
quac/images/ArrowDown_8x4.png


BIN
quac/images/ArrowUp_8x4.png


BIN
quac/images/Directory_10px.png


BIN
quac/images/IR_10px.png


BIN
quac/images/NFC_10px.png


BIN
quac/images/Playlist_10px.png


BIN
quac/images/RFID_10px.png


BIN
quac/images/Settings_10px.png


BIN
quac/images/SubGHz_10px.png


BIN
quac/images/Unknown_10px.png


BIN
quac/images/iButton_10px.png


BIN
quac/images/quac.png


+ 0 - 191
quac/item.c

@@ -1,191 +0,0 @@
-
-#include <furi.h>
-#include <storage/storage.h>
-#include <toolbox/dir_walk.h>
-#include <toolbox/path.h>
-
-#include "quac.h"
-#include "item.h"
-#include <m-array.h>
-
-ARRAY_DEF(FileArray, FuriString*, FURI_STRING_OPLIST);
-
-ItemsView* item_get_items_view_from_path(void* context, const FuriString* input_path) {
-    App* app = context;
-
-    // Handle the app start condition
-    FuriString* in_path;
-    if(input_path == NULL) {
-        in_path = furi_string_alloc_set_str(APP_DATA_PATH(""));
-    } else {
-        in_path = furi_string_alloc_set(input_path);
-    }
-    if(furi_string_get_char(in_path, furi_string_size(in_path) - 1) == '/') {
-        furi_string_left(in_path, furi_string_size(in_path) - 1);
-    }
-    const char* cpath = furi_string_get_cstr(in_path);
-
-    FURI_LOG_I(TAG, "Reading items from path: %s", cpath);
-    ItemsView* iview = malloc(sizeof(ItemsView));
-    iview->path = furi_string_alloc_set(in_path);
-
-    iview->name = furi_string_alloc();
-    if(app->depth == 0) {
-        furi_string_set_str(iview->name, QUAC_NAME);
-    } else {
-        path_extract_basename(cpath, iview->name);
-        item_prettify_name(iview->name);
-    }
-
-    DirWalk* dir_walk = dir_walk_alloc(app->storage);
-    dir_walk_set_recursive(dir_walk, false);
-
-    FuriString* path = furi_string_alloc();
-    FileArray_t flist;
-    FileArray_init(flist);
-
-    FuriString* filename_tmp;
-    filename_tmp = furi_string_alloc();
-
-    // Walk the directory and store all file names in sorted order
-    if(dir_walk_open(dir_walk, cpath)) {
-        while(dir_walk_read(dir_walk, path, NULL) == DirWalkOK) {
-            // FURI_LOG_I(TAG, "> dir_walk: %s", furi_string_get_cstr(path));
-            const char* cpath = furi_string_get_cstr(path);
-
-            path_extract_filename(path, filename_tmp, false);
-            // Always skip our .quac.conf file!
-            if(!furi_string_cmp_str(filename_tmp, QUAC_SETTINGS_FILENAME)) {
-                continue;
-            }
-
-            // Skip "hidden" files
-            char first_char = furi_string_get_char(filename_tmp, 0);
-            if(first_char == '.' && !app->settings.show_hidden) {
-                // FURI_LOG_I(TAG, ">> skipping hidden file: %s", furi_string_get_cstr(filename_tmp));
-                continue;
-            }
-
-            // Insert the new file path in sorted order to flist
-            uint32_t i = 0;
-            FileArray_it_t it;
-            for(FileArray_it(it, flist); !FileArray_end_p(it); FileArray_next(it), ++i) {
-                if(strcmp(cpath, furi_string_get_cstr(*FileArray_ref(it))) > 0) {
-                    continue;
-                }
-                // FURI_LOG_I(TAG, ">> Inserting at %lu", i);
-                FileArray_push_at(flist, i, path);
-                break;
-            }
-            if(i == FileArray_size(flist)) {
-                // FURI_LOG_I(TAG, "Couldn't insert, so adding at the end!");
-                FileArray_push_back(flist, path);
-            }
-        }
-    }
-
-    furi_string_free(filename_tmp);
-    furi_string_free(path);
-
-    // Generate our Item list
-    FileArray_it_t iter;
-    ItemArray_init(iview->items);
-    for(FileArray_it(iter, flist); !FileArray_end_p(iter); FileArray_next(iter)) {
-        path = *FileArray_ref(iter);
-
-        Item* item = ItemArray_push_new(iview->items);
-        item->is_link = false;
-        item->name = furi_string_alloc();
-
-        FileInfo fileinfo;
-        if(storage_common_stat(app->storage, furi_string_get_cstr(path), &fileinfo) == FSE_OK &&
-           file_info_is_dir(&fileinfo)) {
-            item->type = Item_Group;
-            path_extract_filename(path, item->name, false);
-        } else {
-            // Action files have extensions, which determine their type
-            item->ext[0] = 0;
-            item_path_extract_filename(path, item->name, &(item->ext), &(item->is_link));
-            item->type = item_get_item_type_from_extension(item->ext);
-        }
-
-        // FURI_LOG_I(TAG, "Basename: %s", furi_string_get_cstr(item->name));
-        item_prettify_name(item->name);
-
-        item->path = furi_string_alloc_set(path);
-        // FURI_LOG_I(TAG, "Path: %s", furi_string_get_cstr(item->path));
-    }
-
-    furi_string_free(in_path);
-    FileArray_clear(flist);
-    dir_walk_free(dir_walk);
-
-    return iview;
-}
-
-void item_items_view_free(ItemsView* items_view) {
-    furi_string_free(items_view->name);
-    furi_string_free(items_view->path);
-    ItemArray_it_t iter;
-    for(ItemArray_it(iter, items_view->items); !ItemArray_end_p(iter); ItemArray_next(iter)) {
-        furi_string_free(ItemArray_ref(iter)->name);
-        furi_string_free(ItemArray_ref(iter)->path);
-    }
-    ItemArray_clear(items_view->items);
-    free(items_view);
-}
-
-void item_prettify_name(FuriString* name) {
-    // FURI_LOG_I(TAG, "Converting %s to...", furi_string_get_cstr(name));
-    if(furi_string_size(name) > 3) {
-        char c = furi_string_get_char(name, 2);
-        if(c == '_') {
-            char a = furi_string_get_char(name, 0);
-            char b = furi_string_get_char(name, 1);
-            if(a >= '0' && a <= '9' && b >= '0' && b <= '9') {
-                furi_string_right(name, 3);
-            }
-        }
-    }
-    furi_string_replace_all_str(name, "_", " ");
-    // FURI_LOG_I(TAG, "... %s", furi_string_get_cstr(name));
-}
-
-ItemType item_get_item_type_from_extension(const char* ext) {
-    ItemType type = Item_Unknown;
-
-    if(!strcmp(ext, ".sub")) {
-        type = Item_SubGhz;
-    } else if(!strcmp(ext, ".rfid")) {
-        type = Item_RFID;
-    } else if(!strcmp(ext, ".ir")) {
-        type = Item_IR;
-    } else if(!strcmp(ext, ".nfc")) {
-        type = Item_NFC;
-    } else if(!strcmp(ext, ".ibtn")) {
-        type = Item_iButton;
-    } else if(!strcmp(ext, ".qpl")) {
-        type = Item_Playlist;
-    }
-    return type;
-}
-
-void item_path_extract_filename(
-    FuriString* path,
-    FuriString* name,
-    char (*ext)[MAX_EXT_LEN],
-    bool* is_link) {
-    furi_check(ext);
-    furi_check(is_link);
-
-    FuriString* temp = furi_string_alloc_set(path);
-    *is_link = furi_string_end_withi_str(temp, ".ql");
-    if(*is_link) {
-        furi_string_left(temp, furi_string_size(temp) - 3);
-    }
-
-    path_extract_extension(temp, *ext, MAX_EXT_LEN);
-    path_extract_filename(temp, name, true);
-
-    furi_string_free(temp);
-}

+ 0 - 75
quac/item.h

@@ -1,75 +0,0 @@
-#pragma once
-
-#include <m-array.h>
-
-// Max length of a filename, final path element only
-#define MAX_NAME_LEN (size_t)64
-#define MAX_EXT_LEN  (size_t)6
-
-/** Defines an individual item action or item group. Each object contains
- * the relevant file and type information needed to both render correctly
- * on-screen as well as to perform that action.
-*/
-
-typedef enum {
-    Item_SubGhz,
-    Item_RFID,
-    Item_IR,
-    Item_NFC,
-    Item_iButton,
-    Item_Playlist,
-    Item_Group,
-    Item_Settings,
-    Item_Unknown,
-    Item_count
-} ItemType;
-
-typedef struct Item {
-    ItemType type;
-    FuriString* name;
-    FuriString* path;
-    char ext[MAX_EXT_LEN];
-    bool is_link;
-} Item;
-
-ARRAY_DEF(ItemArray, Item, M_POD_OPLIST);
-
-typedef struct ItemsView {
-    FuriString* name;
-    FuriString* path;
-    ItemArray_t items;
-} ItemsView;
-
-/** Allocates and returns an ItemsView* which contains the list of
- * items to display for the given path. Contains everything needed
- * to render a scene_items.
- * 
- * @param   context App*
- * @param   path    FuriString*
- * @return  ItemsView*
-*/
-ItemsView* item_get_items_view_from_path(void* context, const FuriString* path);
-
-/** Free ItemsView
- * @param   items_view
-*/
-void item_items_view_free(ItemsView* items_view);
-
-/** Prettify the name by removing a leading XX_, only if both X are digits,
- * as well as replace all '_' with ' '.
- * @param   name    FuriString*
-*/
-void item_prettify_name(FuriString* name);
-
-/** Return the ItemType enum for the given extension
- * @param   ext     File extension
-*/
-ItemType item_get_item_type_from_extension(const char* ext);
-
-/** Extract filename and extension from path. Check if path is a link file
-*/
-void item_path_extract_filename(
-    FuriString* path,
-    FuriString* name,
-    char (*ext)[MAX_EXT_LEN],
-    bool* is_link);

+ 0 - 112
quac/quac.c

@@ -1,112 +0,0 @@
-#include <furi.h>
-
-#include "quac.h"
-#include "quac_settings.h"
-
-#include "item.h"
-#include "scenes/scenes.h"
-
-/* generated by fbt from .png files in images folder */
-#include <quac_icons.h>
-
-App* app_alloc() {
-    App* app = malloc(sizeof(App));
-    app->scene_manager = scene_manager_alloc(&app_scene_handlers, app);
-    app->view_dispatcher = view_dispatcher_alloc();
-    view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
-    view_dispatcher_set_custom_event_callback(app->view_dispatcher, app_scene_custom_callback);
-    view_dispatcher_set_navigation_event_callback(app->view_dispatcher, app_back_event_callback);
-
-    // Create our UI elements
-    // Main interface
-    app->action_menu = action_menu_alloc();
-    view_dispatcher_add_view(
-        app->view_dispatcher, QView_ActionMenu, action_menu_get_view(app->action_menu));
-
-    // App settings
-    app->vil_settings = variable_item_list_alloc();
-    view_dispatcher_add_view(
-        app->view_dispatcher, QView_Settings, variable_item_list_get_view(app->vil_settings));
-
-    // Misc interfaces
-    app->sub_menu = submenu_alloc();
-    view_dispatcher_add_view(app->view_dispatcher, QView_SubMenu, submenu_get_view(app->sub_menu));
-
-    app->text_input = text_input_alloc();
-    view_dispatcher_add_view(
-        app->view_dispatcher, QView_TextInput, text_input_get_view(app->text_input));
-
-    app->popup = popup_alloc();
-    view_dispatcher_add_view(app->view_dispatcher, QView_Popup, popup_get_view(app->popup));
-
-    // Storage
-    app->storage = furi_record_open(RECORD_STORAGE);
-
-    // Notifications - for LED light access
-    app->notifications = furi_record_open(RECORD_NOTIFICATION);
-
-    app->dialog = furi_record_open(RECORD_DIALOGS);
-
-    // data member initialize
-    app->depth = 0;
-    app->selected_item = -1;
-
-    app->temp_str = furi_string_alloc();
-
-    return app;
-}
-
-void app_free(App* app) {
-    furi_assert(app);
-
-    item_items_view_free(app->items_view);
-
-    view_dispatcher_remove_view(app->view_dispatcher, QView_ActionMenu);
-    view_dispatcher_remove_view(app->view_dispatcher, QView_Settings);
-    view_dispatcher_remove_view(app->view_dispatcher, QView_SubMenu);
-    view_dispatcher_remove_view(app->view_dispatcher, QView_TextInput);
-    view_dispatcher_remove_view(app->view_dispatcher, QView_Popup);
-
-    action_menu_free(app->action_menu);
-    variable_item_list_free(app->vil_settings);
-    submenu_free(app->sub_menu);
-    text_input_free(app->text_input);
-
-    scene_manager_free(app->scene_manager);
-    view_dispatcher_free(app->view_dispatcher);
-
-    furi_string_free(app->temp_str);
-
-    furi_record_close(RECORD_STORAGE);
-    furi_record_close(RECORD_NOTIFICATION);
-    furi_record_close(RECORD_DIALOGS);
-
-    free(app);
-}
-
-// FAP Entry Point
-int32_t quac_app(void* p) {
-    UNUSED(p);
-    FURI_LOG_I(TAG, "QUAC! QUAC!");
-
-    size_t free_start = memmgr_get_free_heap();
-
-    App* app = app_alloc();
-    quac_load_settings(app);
-
-    // Read items at our root
-    app->items_view = item_get_items_view_from_path(app, NULL);
-
-    Gui* gui = furi_record_open(RECORD_GUI);
-    view_dispatcher_attach_to_gui(app->view_dispatcher, gui, ViewDispatcherTypeFullscreen);
-    scene_manager_next_scene(app->scene_manager, QScene_Items);
-    view_dispatcher_run(app->view_dispatcher);
-
-    furi_record_close(RECORD_GUI);
-    app_free(app);
-
-    size_t free_end = memmgr_get_free_heap();
-    FURI_LOG_W(TAG, "Heap: Start = %d, End = %d", free_start, free_end);
-
-    return 0;
-}

+ 0 - 71
quac/quac.h

@@ -1,71 +0,0 @@
-#pragma once
-
-#include <gui/gui.h>
-#include <gui/scene_manager.h>
-#include <gui/view_dispatcher.h>
-#include <gui/scene_manager.h>
-#include <gui/modules/variable_item_list.h>
-#include <gui/modules/submenu.h>
-#include <gui/modules/text_input.h>
-#include <gui/modules/popup.h>
-#include <dialogs/dialogs.h>
-
-#include <storage/storage.h>
-#include <notification/notification_messages.h>
-
-#include "views/action_menu.h"
-#include "item.h"
-
-#define QUAC_NAME    "Quac!"
-#define QUAC_VERSION "v0.8.0"
-#define QUAC_ABOUT                                    \
-    "Quick Action remote control\n" QUAC_VERSION "\n" \
-    "github.com/rdefeo/quac"
-#define TAG "Quac" // log statement id
-
-// Location of our actions and folders
-#define QUAC_SETTINGS_FILENAME ".quac.conf"
-#define QUAC_SETTINGS_PATH     APP_DATA_PATH(QUAC_SETTINGS_FILENAME)
-
-typedef enum {
-    QUAC_APP_PORTRAIT,
-    QUAC_APP_LANDSCAPE
-} QuacAppLayout;
-
-typedef struct App {
-    SceneManager* scene_manager;
-    ViewDispatcher* view_dispatcher;
-
-    ActionMenu* action_menu;
-    VariableItemList* vil_settings;
-    DialogsApp* dialog;
-    Submenu* sub_menu;
-    TextInput* text_input;
-    Popup* popup;
-
-    Storage* storage;
-    NotificationApp* notifications;
-
-    ItemsView* items_view;
-    int depth;
-    int selected_item;
-
-    FuriString* temp_str; // used for renames/etc
-    char temp_cstr[MAX_NAME_LEN]; // used for renames/etc
-    uint32_t temp_u32;
-
-    struct {
-        QuacAppLayout layout; // Defaults to Portrait
-        bool show_icons; // Defaults to True
-        bool show_headers; // Defaults to True
-        uint32_t rfid_duration; // Defaults to 2500 ms
-        uint32_t nfc_duration; // Defaults to 1000 ms
-        uint32_t ibutton_duration; // Defaults to 1000 ms
-        bool ir_use_ext_module; // Defaults to False
-        bool show_hidden; // Defaults to False
-    } settings;
-
-} App;
-
-App* app_alloc();
-void app_free(App* app);

+ 0 - 179
quac/quac_settings.c

@@ -1,179 +0,0 @@
-#include "quac_settings.h"
-
-#include <flipper_format/flipper_format.h>
-
-// Quac Settings File Info
-#define QUAC_SETTINGS_FILE_TYPE    "Quac Settings File"
-#define QUAC_SETTINGS_FILE_VERSION 1
-
-void quac_set_default_settings(App* app) {
-    app->settings.layout = QUAC_APP_LANDSCAPE;
-    app->settings.show_icons = true;
-    app->settings.show_headers = true;
-    app->settings.rfid_duration = 2500;
-    app->settings.nfc_duration = 1000;
-    app->settings.ibutton_duration = 1000;
-    app->settings.ir_use_ext_module = false;
-    app->settings.show_hidden = false;
-}
-
-void quac_load_settings(App* app) {
-    FlipperFormat* fff_settings = flipper_format_file_alloc(app->storage);
-    FuriString* temp_str;
-    temp_str = furi_string_alloc();
-    uint32_t temp_data32 = 0;
-
-    // Initialize settings to the defaults
-    quac_set_default_settings(app);
-
-    FURI_LOG_I(TAG, "SETTINGS: Reading: %s", QUAC_SETTINGS_PATH);
-    do {
-        if(!flipper_format_file_open_existing(fff_settings, QUAC_SETTINGS_PATH)) {
-            FURI_LOG_I(TAG, "SETTINGS: File not found, using defaults");
-            break;
-        }
-
-        if(!flipper_format_read_header(fff_settings, temp_str, &temp_data32)) {
-            FURI_LOG_E(TAG, "SETTINGS: Missing or incorrect header");
-            break;
-        }
-
-        if((!strcmp(furi_string_get_cstr(temp_str), QUAC_SETTINGS_FILE_TYPE)) &&
-           (temp_data32 == QUAC_SETTINGS_FILE_VERSION)) {
-        } else {
-            FURI_LOG_E(TAG, "SETTINGS: Type or version mismatch");
-            break;
-        }
-
-        // Now read actual values we care about
-        if(!flipper_format_read_string(fff_settings, "Layout", temp_str)) {
-            FURI_LOG_W(TAG, "SETTINGS: Missing Layout");
-        } else {
-            if(!strcmp(furi_string_get_cstr(temp_str), "Landscape")) {
-                app->settings.layout = QUAC_APP_LANDSCAPE;
-            } else if(!strcmp(furi_string_get_cstr(temp_str), "Portrait")) {
-                app->settings.layout = QUAC_APP_PORTRAIT;
-            } else {
-                FURI_LOG_E(TAG, "SETTINGS: Invalid Layout");
-            }
-        }
-
-        if(!flipper_format_read_uint32(fff_settings, "Show Icons", &temp_data32, 1)) {
-            FURI_LOG_W(TAG, "SETTINGS: Missing 'Show Icons'");
-        } else {
-            app->settings.show_icons = (temp_data32 == 0) ? false : true;
-        }
-
-        if(!flipper_format_read_uint32(fff_settings, "Show Headers", &temp_data32, 1)) {
-            FURI_LOG_W(TAG, "SETTINGS: Missing 'Show Headers'");
-        } else {
-            app->settings.show_headers = (temp_data32 == 1) ? true : false;
-        }
-
-        if(!flipper_format_read_uint32(fff_settings, "RFID Duration", &temp_data32, 1)) {
-            FURI_LOG_W(TAG, "SETTINGS: Missing 'RFID Duration'");
-        } else {
-            app->settings.rfid_duration = temp_data32;
-        }
-
-        if(!flipper_format_read_uint32(fff_settings, "NFC Duration", &temp_data32, 1)) {
-            FURI_LOG_W(TAG, "SETTINGS: Missing 'NFC Duration'");
-        } else {
-            app->settings.nfc_duration = temp_data32;
-        }
-
-        if(!flipper_format_read_uint32(fff_settings, "iButton Duration", &temp_data32, 1)) {
-            FURI_LOG_W(TAG, "SETTINGS: Missing 'iButton Duration'");
-        } else {
-            app->settings.ibutton_duration = temp_data32;
-        }
-
-        if(!flipper_format_read_uint32(fff_settings, "IR Ext Module", &temp_data32, 1)) {
-            FURI_LOG_W(TAG, "SETTINGS: Missing 'IR Ext Module'");
-        } else {
-            app->settings.ir_use_ext_module = temp_data32 == 1;
-        }
-
-        if(!flipper_format_read_uint32(fff_settings, "Show Hidden", &temp_data32, 1)) {
-            FURI_LOG_W(TAG, "SETTINGS: Missing 'Show Hidden'");
-        } else {
-            app->settings.show_hidden = (temp_data32 == 1) ? true : false;
-        }
-    } while(false);
-
-    furi_string_free(temp_str);
-    flipper_format_free(fff_settings);
-}
-
-void quac_save_settings(App* app) {
-    FlipperFormat* fff_settings = flipper_format_file_alloc(app->storage);
-    uint32_t temp_data32;
-
-    FURI_LOG_I(TAG, "SETTINGS: Saving");
-    bool successful = false;
-    do {
-        if(!flipper_format_file_open_always(fff_settings, QUAC_SETTINGS_PATH)) {
-            FURI_LOG_E(TAG, "SETTINGS: Unable to open file for save!!");
-            break;
-        }
-
-        if(!flipper_format_write_header_cstr(
-               fff_settings, QUAC_SETTINGS_FILE_TYPE, QUAC_SETTINGS_FILE_VERSION)) {
-            FURI_LOG_E(TAG, "SETTINGS: Failed writing file type and version");
-            break;
-        }
-        // layout, icons, headers, duration
-        if(!flipper_format_write_string_cstr(
-               fff_settings,
-               "Layout",
-               app->settings.layout == QUAC_APP_LANDSCAPE ? "Landscape" : "Portrait")) {
-            FURI_LOG_E(TAG, "SETTINGS: Failed to write Layout");
-            break;
-        }
-
-        temp_data32 = app->settings.show_icons ? 1 : 0;
-        if(!flipper_format_write_uint32(fff_settings, "Show Icons", &temp_data32, 1)) {
-            FURI_LOG_E(TAG, "SETTINGS: Failed to write 'Show Icons'");
-            break;
-        }
-        temp_data32 = app->settings.show_headers ? 1 : 0;
-        if(!flipper_format_write_uint32(fff_settings, "Show Headers", &temp_data32, 1)) {
-            FURI_LOG_E(TAG, "SETTINGS: Failed to write 'Show Headers'");
-            break;
-        }
-        if(!flipper_format_write_uint32(
-               fff_settings, "RFID Duration", &app->settings.rfid_duration, 1)) {
-            FURI_LOG_E(TAG, "SETTINGS: Failed to write 'RFID Duration'");
-            break;
-        }
-        if(!flipper_format_write_uint32(
-               fff_settings, "NFC Duration", &app->settings.nfc_duration, 1)) {
-            FURI_LOG_E(TAG, "SETTINGS: Failed to write 'NFC Duration'");
-            break;
-        }
-        if(!flipper_format_write_uint32(
-               fff_settings, "iButton Duration", &app->settings.ibutton_duration, 1)) {
-            FURI_LOG_E(TAG, "SETTINGS: Failed to write 'iButton Duration'");
-            break;
-        }
-        temp_data32 = app->settings.ir_use_ext_module ? 1 : 0;
-        if(!flipper_format_write_uint32(fff_settings, "IR Ext Module", &temp_data32, 1)) {
-            FURI_LOG_E(TAG, "SETTINGS: Failed to write 'IR Ext Module'");
-            break;
-        }
-        temp_data32 = app->settings.show_hidden ? 1 : 0;
-        if(!flipper_format_write_uint32(fff_settings, "Show Hidden", &temp_data32, 1)) {
-            FURI_LOG_E(TAG, "SETTINGS: Failed to write 'Show Hidden'");
-            break;
-        }
-
-        successful = true;
-    } while(false);
-
-    if(!successful) {
-        FURI_LOG_E(TAG, "SETTINGS: Failed to save settings!!");
-    }
-
-    flipper_format_file_close(fff_settings);
-    flipper_format_free(fff_settings);
-}

+ 0 - 14
quac/quac_settings.h

@@ -1,14 +0,0 @@
-#pragma once
-
-#include "quac.h"
-
-/** Set the default Settings for Quac */
-void quac_set_default_settings(App* app);
-
-/** Load the Settings from the .quac.conf file. If not found,
- * then load the defaults.
-*/
-void quac_load_settings(App* app);
-
-/** Save the current settings to the .quac.conf file */
-void quac_save_settings(App* app);

+ 0 - 6
quac/scenes/.gitignore

@@ -1,6 +0,0 @@
-dist/*
-.vscode
-.clang-format
-.editorconfig
-.env
-.ufbt

+ 0 - 40
quac/scenes/scene_about.c

@@ -1,40 +0,0 @@
-#include <furi.h>
-
-#include <gui/view_dispatcher.h>
-#include <gui/scene_manager.h>
-#include <flipper_application/flipper_application.h>
-
-#include "quac.h"
-#include "scenes.h"
-#include "scene_about.h"
-#include "quac_icons.h"
-
-enum {
-    SceneActionAboutEvent,
-};
-
-void scene_about_callback(void* context) {
-    App* app = context;
-    view_dispatcher_send_custom_event(app->view_dispatcher, SceneActionAboutEvent);
-}
-
-void scene_about_on_enter(void* context) {
-    App* app = context;
-
-    Popup* popup = app->popup;
-    popup_set_header(popup, QUAC_NAME, 68, 1, AlignCenter, AlignTop);
-    popup_set_text(popup, QUAC_ABOUT, 0, 15, AlignLeft, AlignTop);
-    popup_set_icon(popup, 38, 0, &I_quac);
-    view_dispatcher_switch_to_view(app->view_dispatcher, QView_Popup);
-}
-
-bool scene_about_on_event(void* context, SceneManagerEvent event) {
-    UNUSED(context);
-    UNUSED(event);
-    return false;
-}
-
-void scene_about_on_exit(void* context) {
-    App* app = context;
-    popup_reset(app->popup);
-}

+ 0 - 8
quac/scenes/scene_about.h

@@ -1,8 +0,0 @@
-#pragma once
-
-#include <gui/scene_manager.h>
-
-// For each scene, implement handler callbacks
-void scene_about_on_enter(void* context);
-bool scene_about_on_event(void* context, SceneManagerEvent event);
-void scene_about_on_exit(void* context);

+ 0 - 91
quac/scenes/scene_action_create_group.c

@@ -1,91 +0,0 @@
-#include <furi.h>
-
-#include <gui/view_dispatcher.h>
-#include <gui/scene_manager.h>
-#include <gui/modules/text_input.h>
-
-#include "quac.h"
-#include "scenes.h"
-#include "scene_action_create_group.h"
-#include "../actions/action.h"
-
-#include <lib/toolbox/path.h>
-
-enum {
-    SceneActionCreateGroupEvent,
-};
-
-void scene_action_create_group_callback(void* context) {
-    App* app = context;
-    view_dispatcher_send_custom_event(app->view_dispatcher, SceneActionCreateGroupEvent);
-}
-
-void scene_action_create_group_on_enter(void* context) {
-    App* app = context;
-    TextInput* text = app->text_input;
-
-    text_input_set_header_text(text, "Enter new group name:");
-
-    app->temp_cstr[0] = 0;
-    text_input_set_result_callback(
-        text, scene_action_create_group_callback, app, app->temp_cstr, MAX_NAME_LEN, false);
-
-    // TextInputValidatorCallback
-    // text_input_set_validator(text, validator_callback, context)
-
-    view_dispatcher_switch_to_view(app->view_dispatcher, QView_TextInput);
-}
-
-bool scene_action_create_group_on_event(void* context, SceneManagerEvent event) {
-    App* app = context;
-    bool consumed = false;
-    if(event.type == SceneManagerEventTypeCustom) {
-        if(event.event == SceneActionCreateGroupEvent) {
-            // FURI_LOG_I(TAG, "Attempting to create group %s", app->temp_cstr);
-            if(!strcmp(app->temp_cstr, "")) {
-                return false;
-            }
-
-            FuriString* current_path = furi_string_alloc();
-            if(app->selected_item != EMPTY_ACTION_INDEX) {
-                Item* item = ItemArray_get(app->items_view->items, app->selected_item);
-                path_extract_dirname(furi_string_get_cstr(item->path), current_path);
-            } else {
-                furi_string_set(current_path, app->items_view->path);
-            }
-
-            FuriString* new_group_path = furi_string_alloc();
-            furi_string_printf(
-                new_group_path, "%s/%s", furi_string_get_cstr(current_path), app->temp_cstr);
-            // FURI_LOG_I(TAG, "Full new path: %s", furi_string_get_cstr(new_group_path));
-
-            FS_Error fs_result =
-                storage_common_mkdir(app->storage, furi_string_get_cstr(new_group_path));
-            if(fs_result == FSE_OK) {
-                ItemsView* new_items = item_get_items_view_from_path(app, current_path);
-                item_items_view_free(app->items_view);
-                app->items_view = new_items;
-            } else {
-                FURI_LOG_E(
-                    TAG, "Create Group failed! %s", filesystem_api_error_get_desc(fs_result));
-                FuriString* error_msg = furi_string_alloc_printf(
-                    "Create Group failed!\nError: %s", filesystem_api_error_get_desc(fs_result));
-                dialog_message_show_storage_error(app->dialog, furi_string_get_cstr(error_msg));
-                furi_string_free(error_msg);
-            }
-
-            furi_string_free(current_path);
-            furi_string_free(new_group_path);
-
-            scene_manager_search_and_switch_to_previous_scene(app->scene_manager, QScene_Items);
-            consumed = true;
-        }
-    }
-
-    return consumed;
-}
-
-void scene_action_create_group_on_exit(void* context) {
-    App* app = context;
-    text_input_reset(app->text_input);
-}

+ 0 - 8
quac/scenes/scene_action_create_group.h

@@ -1,8 +0,0 @@
-#pragma once
-
-#include <gui/scene_manager.h>
-
-// For each scene, implement handler callbacks
-void scene_action_create_group_on_enter(void* context);
-bool scene_action_create_group_on_event(void* context, SceneManagerEvent event);
-void scene_action_create_group_on_exit(void* context);

+ 0 - 148
quac/scenes/scene_action_ir_list.c

@@ -1,148 +0,0 @@
-#include <furi.h>
-
-#include <gui/view_dispatcher.h>
-#include <gui/scene_manager.h>
-#include <gui/modules/submenu.h>
-#include <lib/toolbox/path.h>
-
-#include "quac.h"
-#include "scenes.h"
-#include "scene_action_ir_list.h"
-#include "../actions/action.h"
-#include "../actions/action_ir_utils.h"
-
-#include <flipper_format/flipper_format.h>
-
-void scene_action_ir_list_callback(void* context, uint32_t index) {
-    App* app = context;
-    view_dispatcher_send_custom_event(app->view_dispatcher, index);
-}
-
-void scene_action_ir_list_on_enter(void* context) {
-    App* app = context;
-
-    Submenu* menu = app->sub_menu;
-    submenu_reset(menu);
-
-    // Our selected IR File is app->temp_str
-    submenu_set_header(menu, "Select IR Command");
-
-    uint32_t index = 0;
-
-    // Add an entry for IMPORT ALL
-    submenu_add_item(menu, "* IMPORT ALL *", index++, scene_action_ir_list_callback, app);
-
-    // read the IR file and load the names of all of the commands
-    FuriString* name = furi_string_alloc();
-
-    FlipperFormat* fff_data_file = flipper_format_file_alloc(app->storage);
-    if(flipper_format_file_open_existing(fff_data_file, furi_string_get_cstr(app->temp_str))) {
-        while(flipper_format_read_string(fff_data_file, "name", name)) {
-            submenu_add_item(
-                menu, furi_string_get_cstr(name), index, scene_action_ir_list_callback, app);
-            index++;
-        }
-    }
-
-    // Number of IR Commands in file
-    app->temp_u32 = index - 1;
-    if(app->temp_u32 == 0) {
-        FURI_LOG_E(TAG, "Failed to get ANY commands from %s", furi_string_get_cstr(app->temp_str));
-        submenu_change_item_label(menu, 0, "No IR cmds!");
-    }
-
-    flipper_format_file_close(fff_data_file);
-    flipper_format_free(fff_data_file);
-    furi_string_free(name);
-
-    view_dispatcher_switch_to_view(app->view_dispatcher, QView_SubMenu);
-}
-
-bool scene_action_ir_list_on_event(void* context, SceneManagerEvent event) {
-    App* app = context;
-    bool consumed = false;
-    if(event.type == SceneManagerEventTypeCustom) {
-        consumed = true;
-        uint32_t index = event.event;
-        InfraredSignal* signal = infrared_utils_signal_alloc();
-
-        // extract that item as it's own file and place it "here", as defined by
-        // the currently selected_item
-        FuriString* name = furi_string_alloc(); // IR command name
-        FlipperFormat* fff_data_file = flipper_format_file_alloc(app->storage);
-        FuriString* file_name = furi_string_alloc(); // new IR file name
-
-        do {
-            uint32_t num_imported = 0;
-            uint32_t start = index - 1;
-            uint32_t end = index;
-            if(index == 0) {
-                start = 0;
-                end = app->temp_u32; // Number of IR Commands in file
-            }
-            for(uint32_t ir_index = start; ir_index < end; ir_index++) {
-                if(!flipper_format_file_open_existing(
-                       fff_data_file, furi_string_get_cstr(app->temp_str))) {
-                    FURI_LOG_E(TAG, "Failed to open %s", furi_string_get_cstr(app->temp_str));
-                    break;
-                }
-
-                if(!infrared_utils_read_signal_at_index(fff_data_file, ir_index, signal, name)) {
-                    FURI_LOG_E(TAG, "Failed to read signal at %lu", index);
-                    break;
-                }
-                FURI_LOG_I(TAG, "Read IR signal: %s", furi_string_get_cstr(name));
-                flipper_format_file_close(fff_data_file);
-
-                // generate the new path, based on current item's dir and new command name
-                if(app->selected_item != EMPTY_ACTION_INDEX) {
-                    Item* item = ItemArray_get(app->items_view->items, app->selected_item);
-                    path_extract_dirname(furi_string_get_cstr(item->path), file_name);
-                } else {
-                    furi_string_set(file_name, app->items_view->path);
-                }
-                furi_string_cat_printf(file_name, "/%s.ir", furi_string_get_cstr(name));
-
-                FURI_LOG_I(TAG, "Writing new IR file: %s", furi_string_get_cstr(file_name));
-                if(!flipper_format_file_open_new(fff_data_file, furi_string_get_cstr(file_name))) {
-                    FURI_LOG_E(
-                        TAG, "Error creating new file: %s", furi_string_get_cstr(file_name));
-                    break;
-                }
-                if(!infrared_utils_write_signal(fff_data_file, signal, name)) {
-                    FURI_LOG_E(TAG, "Failed to write signal!");
-                    break;
-                }
-                flipper_format_file_close(fff_data_file);
-                FURI_LOG_I(TAG, "Imported %s", furi_string_get_cstr(name));
-                num_imported++;
-            }
-
-            if(num_imported == (end - start)) {
-                // Import successful!
-                notification_message(app->notifications, &sequence_success);
-            } else {
-                FURI_LOG_E(
-                    TAG,
-                    "Error importing IR command(s) from %s",
-                    furi_string_get_cstr(app->temp_str));
-                notification_message(app->notifications, &sequence_error);
-            }
-            // Leave the user on this scene, in case they want to import
-            // more commands from this IR file
-        } while(false);
-
-        // cleanup
-        flipper_format_file_close(fff_data_file);
-        flipper_format_free(fff_data_file);
-        furi_string_free(name);
-        furi_string_free(file_name);
-        infrared_utils_signal_free(signal);
-    }
-    return consumed;
-}
-
-void scene_action_ir_list_on_exit(void* context) {
-    App* app = context;
-    submenu_reset(app->sub_menu);
-}

+ 0 - 8
quac/scenes/scene_action_ir_list.h

@@ -1,8 +0,0 @@
-#pragma once
-
-#include <gui/scene_manager.h>
-
-// For each scene, implement handler callbacks
-void scene_action_ir_list_on_enter(void* context);
-bool scene_action_ir_list_on_event(void* context, SceneManagerEvent event);
-void scene_action_ir_list_on_exit(void* context);

+ 0 - 110
quac/scenes/scene_action_rename.c

@@ -1,110 +0,0 @@
-#include <furi.h>
-
-#include <gui/view_dispatcher.h>
-#include <gui/scene_manager.h>
-#include <gui/modules/text_input.h>
-
-#include "quac.h"
-#include "scenes.h"
-#include "scene_action_rename.h"
-
-#include <lib/toolbox/path.h>
-
-enum {
-    SceneActionRenameEvent,
-};
-
-void scene_action_rename_callback(void* context) {
-    App* app = context;
-    view_dispatcher_send_custom_event(app->view_dispatcher, SceneActionRenameEvent);
-}
-
-void scene_action_rename_on_enter(void* context) {
-    App* app = context;
-    TextInput* text = app->text_input;
-
-    Item* item = ItemArray_get(app->items_view->items, app->selected_item);
-
-    text_input_set_header_text(text, "Enter new name:");
-
-    FuriString* file_name = furi_string_alloc();
-    char ext[MAX_EXT_LEN] = {0};
-    bool is_link;
-    item_path_extract_filename(item->path, file_name, &ext, &is_link);
-    strncpy(app->temp_cstr, furi_string_get_cstr(file_name), MAX_NAME_LEN);
-
-    text_input_set_result_callback(
-        text, scene_action_rename_callback, app, app->temp_cstr, MAX_NAME_LEN, false);
-
-    furi_string_free(file_name);
-    view_dispatcher_switch_to_view(app->view_dispatcher, QView_TextInput);
-}
-
-bool scene_action_rename_on_event(void* context, SceneManagerEvent event) {
-    App* app = context;
-    bool consumed = false;
-    if(event.type == SceneManagerEventTypeCustom) {
-        if(event.event == SceneActionRenameEvent) {
-            // FURI_LOG_I(TAG, "Attempting rename to %s", app->temp_cstr);
-            if(!strcmp(app->temp_cstr, "") || !path_contains_only_ascii(app->temp_cstr)) {
-                return false;
-            }
-            Item* item = ItemArray_get(app->items_view->items, app->selected_item);
-            const char* old_path = furi_string_get_cstr(item->path);
-
-            FuriString* file_name = furi_string_alloc();
-            char ext[MAX_EXT_LEN] = {0};
-            bool is_link;
-            item_path_extract_filename(item->path, file_name, &ext, &is_link);
-            // FURI_LOG_I(TAG, "Original name is %s", furi_string_get_cstr(file_name));
-            if(!furi_string_cmp_str(file_name, app->temp_cstr)) {
-                FURI_LOG_W(TAG, "Rename: File names are the same!");
-                furi_string_free(file_name);
-                return false;
-            }
-
-            // build the new name full path, with extension
-            FuriString* dir_name = furi_string_alloc();
-            path_extract_dirname(old_path, dir_name);
-            FuriString* new_path = furi_string_alloc_printf(
-                "%s/%s%s", furi_string_get_cstr(dir_name), app->temp_cstr, item->ext);
-            if(is_link) {
-                furi_string_cat_str(new_path, ".ql");
-            }
-
-            FURI_LOG_I(TAG, "Rename: %s to %s", old_path, furi_string_get_cstr(new_path));
-            FS_Error fs_result =
-                storage_common_rename(app->storage, old_path, furi_string_get_cstr(new_path));
-            if(fs_result == FSE_OK) {
-                ItemsView* new_items = item_get_items_view_from_path(app, dir_name);
-                item_items_view_free(app->items_view);
-                app->items_view = new_items;
-                // furi_string_swap(item->path, new_path);
-                // furi_string_set_str(item->name, app->temp_cstr);
-                // item_prettify_name(item->name);
-            } else {
-                FURI_LOG_E(
-                    TAG, "Rename file failed! %s", filesystem_api_error_get_desc(fs_result));
-                FuriString* error_msg = furi_string_alloc_printf(
-                    "Rename failed!\nError: %s", filesystem_api_error_get_desc(fs_result));
-                dialog_message_show_storage_error(app->dialog, furi_string_get_cstr(error_msg));
-                furi_string_free(error_msg);
-            }
-
-            scene_manager_search_and_switch_to_previous_scene(app->scene_manager, QScene_Items);
-
-            furi_string_free(dir_name);
-            furi_string_free(file_name);
-            furi_string_free(new_path);
-
-            consumed = true;
-        }
-    }
-
-    return consumed;
-}
-
-void scene_action_rename_on_exit(void* context) {
-    App* app = context;
-    text_input_reset(app->text_input);
-}

+ 0 - 8
quac/scenes/scene_action_rename.h

@@ -1,8 +0,0 @@
-#pragma once
-
-#include <gui/scene_manager.h>
-
-// For each scene, implement handler callbacks
-void scene_action_rename_on_enter(void* context);
-bool scene_action_rename_on_event(void* context, SceneManagerEvent event);
-void scene_action_rename_on_exit(void* context);

+ 0 - 383
quac/scenes/scene_action_settings.c

@@ -1,383 +0,0 @@
-#include <furi.h>
-
-#include <gui/view_dispatcher.h>
-#include <gui/scene_manager.h>
-#include <gui/modules/submenu.h>
-#include <lib/toolbox/path.h>
-
-#include "quac.h"
-#include "scenes.h"
-#include "scene_action_settings.h"
-#include "../actions/action.h"
-#include "quac_icons.h"
-
-// Define different settings per Action
-typedef enum {
-    ActionSettingsRename, // Rename file or folder
-    ActionSettingsDelete, // Delete file or folder on SDcard
-    ActionSettingsImport, // Copy a remote file into "current" folder
-    ActionSettingsImportLink, // Create a link to a remote file into "current" folder
-    ActionSettingsCreateGroup, // Create new empty folder in "current" folder
-    ActionSettingsCreatePlaylist, // Turn this folder into a playlist
-    ActionSettingsAddToPlaylist, // Append a remote file to this playlist
-} ActionSettingsIndex;
-
-// Delete the file of the currently selected item
-// Update items_view list before returning so that UI is updated and correct
-bool scene_action_settings_delete(App* app) {
-    bool success = false;
-    Item* item = ItemArray_get(app->items_view->items, app->selected_item);
-
-    DialogMessage* dialog = dialog_message_alloc();
-    dialog_message_set_header(dialog, "Delete?", 64, 0, AlignCenter, AlignTop);
-    FuriString* text = furi_string_alloc();
-    furi_string_printf(text, "%s\n\n%s", furi_string_get_cstr(item->name), "Are you sure?");
-    dialog_message_set_text(dialog, furi_string_get_cstr(text), 64, 18, AlignCenter, AlignTop);
-    dialog_message_set_buttons(dialog, "Cancel", NULL, "OK");
-    DialogMessageButton button = dialog_message_show(app->dialog, dialog);
-
-    if(button == DialogMessageButtonRight) {
-        FuriString* current_path = furi_string_alloc();
-        path_extract_dirname(furi_string_get_cstr(item->path), current_path);
-
-        FS_Error fs_result = storage_common_remove(app->storage, furi_string_get_cstr(item->path));
-        if(fs_result == FSE_OK) {
-            success = true;
-            FURI_LOG_I(TAG, "Deleted file: %s", furi_string_get_cstr(item->path));
-            // ItemsView* new_items = item_get_items_view_from_path(app, current_path);
-            // item_items_view_free(app->items_view);
-            // app->items_view = new_items;
-        } else {
-            FURI_LOG_E(
-                TAG, "Error deleting file! Error=%s", filesystem_api_error_get_desc(fs_result));
-            FuriString* error_msg = furi_string_alloc();
-            furi_string_printf(
-                error_msg, "Delete failed!\nError: %s", filesystem_api_error_get_desc(fs_result));
-            dialog_message_show_storage_error(app->dialog, furi_string_get_cstr(error_msg));
-            furi_string_free(error_msg);
-        }
-
-        furi_string_free(current_path);
-    } else {
-        // FURI_LOG_I(TAG, "Used cancelled Delete");
-    }
-
-    furi_string_free(text);
-    dialog_message_free(dialog);
-    return success;
-}
-
-static bool scene_action_settings_import_file_browser_callback(
-    FuriString* path,
-    void* context,
-    uint8_t** icon,
-    FuriString* item_name) {
-    UNUSED(context);
-    UNUSED(item_name);
-    char ext[MAX_EXT_LEN];
-    path_extract_extension(path, ext, MAX_EXT_LEN);
-    if(!strcmp(ext, ".sub")) {
-        memcpy(*icon, icon_get_frame_data(&I_SubGHz_10px, 0), 32); // TODO: find the right size!
-    } else if(!strcmp(ext, ".rfid")) {
-        memcpy(*icon, icon_get_frame_data(&I_RFID_10px, 0), 32);
-    } else if(!strcmp(ext, ".ir")) {
-        memcpy(*icon, icon_get_frame_data(&I_IR_10px, 0), 32);
-    } else if(!strcmp(ext, ".nfc")) {
-        memcpy(*icon, icon_get_frame_data(&I_NFC_10px, 0), 32);
-    } else if(!strcmp(ext, ".ibtn")) {
-        memcpy(*icon, icon_get_frame_data(&I_iButton_10px, 0), 32);
-    } else if(!strcmp(ext, ".qpl")) {
-        memcpy(*icon, icon_get_frame_data(&I_Playlist_10px, 0), 32);
-    } else {
-        return false;
-    }
-    return true;
-}
-
-// Ask user for file to import from elsewhere on the SD card
-FuriString* scene_action_get_file_to_import_alloc(App* app) {
-    // Setup our file browser options
-    DialogsFileBrowserOptions fb_options;
-    dialog_file_browser_set_basic_options(&fb_options, "", NULL);
-    fb_options.base_path = STORAGE_EXT_PATH_PREFIX;
-    fb_options.skip_assets = true;
-    furi_string_set_str(app->temp_str, fb_options.base_path);
-    fb_options.item_loader_callback = scene_action_settings_import_file_browser_callback;
-    fb_options.item_loader_context = app;
-
-    FuriString* full_path = NULL;
-    if(dialog_file_browser_show(app->dialog, app->temp_str, app->temp_str, &fb_options)) {
-        full_path = furi_string_alloc_set(app->temp_str);
-    }
-    return full_path;
-}
-
-// Import a file from elsewhere on the SD card
-// Update items_view list before returning so that UI is updated and correct
-bool scene_action_settings_import(App* app) {
-    bool success = false;
-    FuriString* current_path = furi_string_alloc();
-    if(app->selected_item != EMPTY_ACTION_INDEX) {
-        Item* item = ItemArray_get(app->items_view->items, app->selected_item);
-        path_extract_dirname(furi_string_get_cstr(item->path), current_path);
-    } else {
-        furi_string_set(current_path, app->items_view->path);
-    }
-
-    // Setup our file browser options
-    DialogsFileBrowserOptions fb_options;
-    dialog_file_browser_set_basic_options(&fb_options, "", NULL);
-    fb_options.base_path = furi_string_get_cstr(current_path);
-    fb_options.skip_assets = true;
-    furi_string_set_str(app->temp_str, fb_options.base_path);
-    fb_options.item_loader_callback = scene_action_settings_import_file_browser_callback;
-    fb_options.item_loader_context = app;
-
-    if(dialog_file_browser_show(app->dialog, app->temp_str, app->temp_str, &fb_options)) {
-        // FURI_LOG_I(TAG, "Selected file is %s", furi_string_get_cstr(app->temp_str));
-        FuriString* file_name = furi_string_alloc();
-        path_extract_filename(app->temp_str, file_name, false);
-        // FURI_LOG_I(TAG, "Importing file %s", furi_string_get_cstr(file_name));
-        FuriString* full_path;
-        full_path = furi_string_alloc_printf(
-            "%s/%s", furi_string_get_cstr(current_path), furi_string_get_cstr(file_name));
-        // FURI_LOG_I(TAG, "New path is %s", furi_string_get_cstr(full_path));
-
-        FS_Error fs_result = storage_common_copy(
-            app->storage, furi_string_get_cstr(app->temp_str), furi_string_get_cstr(full_path));
-        if(fs_result == FSE_OK) {
-            success = true;
-            // FURI_LOG_I(TAG, "File copied / updating items view list");
-            // ItemsView* new_items = item_get_items_view_from_path(app, current_path);
-            // item_items_view_free(app->items_view);
-            // app->items_view = new_items;
-        } else {
-            FURI_LOG_E(TAG, "File copy failed! %s", filesystem_api_error_get_desc(fs_result));
-            FuriString* error_msg = furi_string_alloc_printf(
-                "File copy failed!\nError: %s", filesystem_api_error_get_desc(fs_result));
-            dialog_message_show_storage_error(app->dialog, furi_string_get_cstr(error_msg));
-            furi_string_free(error_msg);
-        }
-        furi_string_free(file_name);
-        furi_string_free(full_path);
-    } else {
-        // FURI_LOG_I(TAG, "User cancelled");
-    }
-
-    furi_string_free(current_path);
-    return success;
-}
-
-// Prompt user for the name of the new Group
-// Update items_view list before returning so that UI is updated and correct
-bool scene_action_settings_create_group(App* app) {
-    UNUSED(app);
-    return false;
-}
-
-void scene_action_settings_callback(void* context, uint32_t index) {
-    App* app = context;
-    view_dispatcher_send_custom_event(app->view_dispatcher, index);
-}
-
-void scene_action_settings_on_enter(void* context) {
-    App* app = context;
-
-    Submenu* menu = app->sub_menu;
-    submenu_reset(menu);
-
-    if(app->selected_item >= 0) {
-        Item* item = ItemArray_get(app->items_view->items, app->selected_item);
-        submenu_set_header(menu, furi_string_get_cstr(item->name));
-
-        submenu_add_item(
-            menu, "Rename", ActionSettingsRename, scene_action_settings_callback, app);
-        submenu_add_item(
-            menu, "Delete", ActionSettingsDelete, scene_action_settings_callback, app);
-    } else {
-        submenu_set_header(menu, furi_string_get_cstr(app->items_view->name));
-    }
-
-    submenu_add_item(
-        menu, "Import Here", ActionSettingsImport, scene_action_settings_callback, app);
-    submenu_add_item(
-        menu, "Import Link Here", ActionSettingsImportLink, scene_action_settings_callback, app);
-    submenu_add_item(
-        menu, "Create Group", ActionSettingsCreateGroup, scene_action_settings_callback, app);
-
-    view_dispatcher_switch_to_view(app->view_dispatcher, QView_SubMenu);
-}
-
-bool scene_action_settings_on_event(void* context, SceneManagerEvent event) {
-    App* app = context;
-    bool consumed = false;
-    if(event.type == SceneManagerEventTypeCustom) {
-        switch(event.event) {
-        case ActionSettingsRename:
-            consumed = true;
-            scene_manager_next_scene(app->scene_manager, QScene_ActionRename);
-            break;
-        case ActionSettingsDelete:
-            consumed = true;
-            if(scene_action_settings_delete(app)) {
-                scene_manager_previous_scene(app->scene_manager);
-            }
-            break;
-        case ActionSettingsImport: {
-            consumed = true;
-            // get the filename to import
-            FuriString* import_file = scene_action_get_file_to_import_alloc(app);
-            if(import_file) {
-                FURI_LOG_I(TAG, "Importing %s", furi_string_get_cstr(import_file));
-                // if it's a .ir file, switch to a scene that lets user pick the command from the file
-                // only if there's more than one command in the file. then copy that relevant chunk
-                // to the local directory
-                char ext[MAX_EXT_LEN] = {0};
-
-                path_extract_extension(import_file, ext, MAX_EXT_LEN);
-                if(!strcmp(ext, ".ir")) {
-                    FURI_LOG_I(TAG, "Loading ir file %s", furi_string_get_cstr(app->temp_str));
-                    // load scene that takes filename and lists all commands
-                    // the scene should write the new file, eh?
-                    scene_manager_next_scene(app->scene_manager, QScene_ActionIRList);
-                } else {
-                    // just copy the file here
-                    FuriString* current_path = furi_string_alloc();
-                    if(app->selected_item != EMPTY_ACTION_INDEX) {
-                        Item* item = ItemArray_get(app->items_view->items, app->selected_item);
-                        path_extract_dirname(furi_string_get_cstr(item->path), current_path);
-                    } else {
-                        furi_string_set(current_path, app->items_view->path);
-                    }
-                    // TODO: this should be a method
-                    FuriString* file_name = furi_string_alloc();
-                    path_extract_filename(import_file, file_name, false);
-                    // FURI_LOG_I(TAG, "Importing file %s", furi_string_get_cstr(file_name));
-                    FuriString* full_path;
-                    full_path = furi_string_alloc_printf(
-                        "%s/%s",
-                        furi_string_get_cstr(current_path),
-                        furi_string_get_cstr(file_name));
-                    // FURI_LOG_I(TAG, "New path is %s", furi_string_get_cstr(full_path));
-
-                    FURI_LOG_I(
-                        TAG,
-                        "Copy: %s to %s",
-                        furi_string_get_cstr(import_file),
-                        furi_string_get_cstr(full_path));
-                    FS_Error fs_result = storage_common_copy(
-                        app->storage,
-                        furi_string_get_cstr(import_file),
-                        furi_string_get_cstr(full_path));
-                    if(fs_result != FSE_OK) {
-                        FURI_LOG_E(
-                            TAG, "Copy file failed! %s", filesystem_api_error_get_desc(fs_result));
-                        FuriString* error_msg = furi_string_alloc_printf(
-                            "Copy failed!\nError: %s", filesystem_api_error_get_desc(fs_result));
-                        dialog_message_show_storage_error(
-                            app->dialog, furi_string_get_cstr(error_msg));
-                        furi_string_free(error_msg);
-                    }
-                    furi_string_free(file_name);
-                    furi_string_free(full_path);
-                }
-                furi_string_free(import_file);
-            } else {
-                scene_manager_previous_scene(app->scene_manager);
-            }
-
-            // if(scene_action_settings_import(app)) {
-            //     scene_manager_previous_scene(app->scene_manager);
-            // }
-        } break;
-        case ActionSettingsImportLink: {
-            consumed = true;
-            // get the filename to import as a link
-            FuriString* import_file = scene_action_get_file_to_import_alloc(app);
-            if(import_file) {
-                FURI_LOG_I(TAG, "Importing as link %s", furi_string_get_cstr(import_file));
-                char ext[MAX_EXT_LEN] = {0};
-
-                path_extract_extension(import_file, ext, MAX_EXT_LEN);
-                if(!strcmp(ext, ".ir")) {
-                    dialog_message_show_storage_error(
-                        app->dialog, "Can't import IR file as link at this time");
-                } else if(!strcmp(ext, ".ql")) {
-                    FURI_LOG_E(TAG, "Can't import link file as a link!");
-                    dialog_message_show_storage_error(
-                        app->dialog, "Can't import link file as a link!");
-                } else {
-                    FuriString* current_path = furi_string_alloc();
-                    if(app->selected_item != EMPTY_ACTION_INDEX) {
-                        Item* item = ItemArray_get(app->items_view->items, app->selected_item);
-                        path_extract_dirname(furi_string_get_cstr(item->path), current_path);
-                    } else {
-                        furi_string_set(current_path, app->items_view->path);
-                    }
-                    FuriString* file_name = furi_string_alloc();
-                    path_extract_filename(import_file, file_name, false);
-
-                    FuriString* full_path;
-                    full_path = furi_string_alloc_printf(
-                        "%s/%s.ql", // path/filename.ext.ql
-                        furi_string_get_cstr(current_path),
-                        furi_string_get_cstr(file_name));
-                    FURI_LOG_I(
-                        TAG,
-                        "Copy as link: %s to %s",
-                        furi_string_get_cstr(import_file),
-                        furi_string_get_cstr(full_path));
-
-                    File* file_link = storage_file_alloc(app->storage);
-                    if(storage_file_open(
-                           file_link,
-                           furi_string_get_cstr(full_path),
-                           FSAM_WRITE,
-                           FSOM_CREATE_NEW)) {
-                        const char* cimport_file = furi_string_get_cstr(import_file);
-                        size_t bytes_written =
-                            storage_file_write(file_link, cimport_file, strlen(cimport_file));
-                        if(bytes_written != strlen(cimport_file)) {
-                            FURI_LOG_E(
-                                TAG,
-                                "Copy as link failure: incorrect bytes written. Expected %d, wrote %d",
-                                strlen(cimport_file),
-                                bytes_written);
-                        }
-                    } else {
-                        dialog_message_show_storage_error(app->dialog, "Error writing link file!");
-                        FURI_LOG_E(
-                            TAG,
-                            "Copy file as link failed! File %s already exists",
-                            furi_string_get_cstr(full_path));
-                    }
-                    storage_file_close(file_link);
-                    storage_file_free(file_link);
-
-                    furi_string_free(file_name);
-                    furi_string_free(full_path);
-                }
-                furi_string_free(import_file);
-            } else {
-                scene_manager_previous_scene(app->scene_manager);
-            }
-        } break;
-        case ActionSettingsCreateGroup:
-            consumed = true;
-            scene_manager_next_scene(app->scene_manager, QScene_ActionCreateGroup);
-            break;
-        }
-    }
-
-    return consumed;
-}
-
-void scene_action_settings_on_exit(void* context) {
-    App* app = context;
-    submenu_reset(app->sub_menu);
-
-    // Rebuild our list on exit, to pick up any renames
-    ItemsView* new_items = item_get_items_view_from_path(app, app->items_view->path);
-    item_items_view_free(app->items_view);
-    app->items_view = new_items;
-}

+ 0 - 8
quac/scenes/scene_action_settings.h

@@ -1,8 +0,0 @@
-#pragma once
-
-#include <gui/scene_manager.h>
-
-// For each scene, implement handler callbacks
-void scene_action_settings_on_enter(void* context);
-bool scene_action_settings_on_event(void* context, SceneManagerEvent event);
-void scene_action_settings_on_exit(void* context);

+ 0 - 187
quac/scenes/scene_items.c

@@ -1,187 +0,0 @@
-#include <furi.h>
-
-#include <gui/view_dispatcher.h>
-#include <gui/scene_manager.h>
-#include <gui/modules/dialog_ex.h>
-
-#include <notification/notification_messages.h>
-
-#include "quac.h"
-#include "scenes.h"
-#include "scene_items.h"
-#include "../actions/action.h"
-#include "../views/action_menu.h"
-
-#include <lib/toolbox/path.h>
-
-static const ActionMenuItemType ItemToMenuItem[] = {
-    [Item_SubGhz] = ActionMenuItemTypeSubGHz,
-    [Item_RFID] = ActionMenuItemTypeRFID,
-    [Item_IR] = ActionMenuItemTypeIR,
-    [Item_NFC] = ActionMenuItemTypeNFC,
-    [Item_iButton] = ActionMenuItemTypeiButton,
-    [Item_Playlist] = ActionMenuItemTypePlaylist,
-    [Item_Group] = ActionMenuItemTypeGroup,
-    [Item_Settings] = ActionMenuItemTypeSettings,
-    [Item_Unknown] = ActionMenuItemTypeUnknown,
-};
-
-void scene_items_item_callback(void* context, int32_t index, InputType type) {
-    App* app = context;
-
-    // FURI_LOG_I(TAG, "scene_items callback, type == %s", input_get_type_name(type));
-
-    if(type == InputTypeShort) {
-        app->selected_item = index;
-        view_dispatcher_send_custom_event(app->view_dispatcher, Event_ButtonPressed);
-    } else if(type == InputTypeLong) {
-        app->selected_item = index;
-        view_dispatcher_send_custom_event(app->view_dispatcher, Event_ButtonPressedLong);
-    } else {
-        // do nothing
-    }
-}
-
-// For each scene, implement handler callbacks
-void scene_items_on_enter(void* context) {
-    App* app = context;
-
-    ActionMenu* menu = app->action_menu;
-    action_menu_reset(menu);
-    if(app->settings.layout == QUAC_APP_LANDSCAPE)
-        action_menu_set_layout(menu, ActionMenuLayoutLandscape);
-    else
-        action_menu_set_layout(menu, ActionMenuLayoutPortrait);
-    action_menu_set_show_icons(menu, app->settings.show_icons);
-    action_menu_set_show_headers(menu, app->settings.show_headers);
-
-    ItemsView* items_view = app->items_view;
-    FURI_LOG_I(
-        TAG, "Generating scene: [depth=%d] %s", app->depth, furi_string_get_cstr(items_view->path));
-
-    action_menu_set_header(menu, furi_string_get_cstr(items_view->name));
-
-    size_t item_view_size = ItemArray_size(items_view->items);
-    if(item_view_size > 0) {
-        ItemArray_it_t iter;
-        int32_t index = 0;
-        for(ItemArray_it(iter, items_view->items); !ItemArray_end_p(iter);
-            ItemArray_next(iter), ++index) {
-            const Item* item = ItemArray_cref(iter);
-            const char* label = furi_string_get_cstr(item->name);
-            ActionMenuItemType type = ItemToMenuItem[item->type];
-            ActionMenuItem* menu_item =
-                action_menu_add_item(menu, label, index, scene_items_item_callback, type, app);
-            action_menu_item_set_link(menu_item, item->is_link);
-        }
-    } else {
-        FURI_LOG_W(TAG, "No items for: %s", furi_string_get_cstr(items_view->path));
-        // Add a bogus item - this lets the user still access the Action menu to import, etc
-        action_menu_add_item(
-            menu,
-            "<Empty, Hold Right>",
-            EMPTY_ACTION_INDEX,
-            scene_items_item_callback,
-            ActionMenuItemTypeGroup,
-            app);
-    }
-
-    // Always add the "Settings" item at the end of our list - but only at top level!
-    if(app->depth == 0) {
-        action_menu_add_item(
-            menu,
-            "Settings",
-            item_view_size, // last item!
-            scene_items_item_callback,
-            ActionMenuItemTypeSettings,
-            app);
-    }
-
-    view_dispatcher_switch_to_view(app->view_dispatcher, QView_ActionMenu);
-}
-bool scene_items_on_event(void* context, SceneManagerEvent event) {
-    App* app = context;
-    bool consumed = false;
-
-    switch(event.type) {
-    case SceneManagerEventTypeCustom:
-        if(event.event == Event_ButtonPressed && app->selected_item != EMPTY_ACTION_INDEX) {
-            consumed = true;
-            // FURI_LOG_I(TAG, "button pressed is %d", app->selected_item);
-            if(app->selected_item < (int)ItemArray_size(app->items_view->items)) {
-                Item* item = ItemArray_get(app->items_view->items, app->selected_item);
-                if(item->type == Item_Group) {
-                    app->depth++;
-                    ItemsView* new_items = item_get_items_view_from_path(app, item->path);
-                    item_items_view_free(app->items_view);
-                    app->items_view = new_items;
-                    scene_manager_next_scene(app->scene_manager, QScene_Items);
-                } else {
-                    FURI_LOG_I(
-                        TAG, "Initiating item action: %s", furi_string_get_cstr(item->name));
-
-                    // LED goes blinky blinky
-                    App* app = context;
-                    notification_message(app->notifications, &sequence_blink_start_blue);
-
-                    // Prepare error string for action calls
-                    FuriString* error;
-                    error = furi_string_alloc();
-
-                    action_tx(app, item, error);
-
-                    if(furi_string_size(error)) {
-                        FURI_LOG_E(TAG, furi_string_get_cstr(error));
-                        // Fire up the LED and vibrate!
-                        notification_message(app->notifications, &sequence_error);
-                    }
-
-                    furi_string_free(error);
-
-                    // Turn off LED light
-                    notification_message(app->notifications, &sequence_blink_stop);
-                }
-            } else {
-                // FURI_LOG_I(TAG, "Selected Settings!");
-                // TODO: Do we need to free this current items_view??
-                scene_manager_next_scene(app->scene_manager, QScene_Settings);
-            }
-        } else if(event.event == Event_ButtonPressedLong) {
-            if(app->selected_item < (int)ItemArray_size(app->items_view->items)) {
-                consumed = true;
-                scene_manager_next_scene(app->scene_manager, QScene_ActionSettings);
-            }
-        }
-        break;
-    case SceneManagerEventTypeBack:
-        // FURI_LOG_I(TAG, "Back button pressed!");
-        consumed = false; // Ensure Back event continues to propagate
-        if(app->depth > 0) {
-            // take our current ItemsView path, and go back up a level
-            FuriString* parent_path;
-            parent_path = furi_string_alloc();
-            path_extract_dirname(furi_string_get_cstr(app->items_view->path), parent_path);
-
-            app->depth--;
-            ItemsView* new_items = item_get_items_view_from_path(app, parent_path);
-            item_items_view_free(app->items_view);
-            app->items_view = new_items;
-
-            furi_string_free(parent_path);
-        } else {
-            // FURI_LOG_I(TAG, "At the root level!");
-        }
-        break;
-    default:
-        FURI_LOG_I(TAG, "Custom event not handled");
-        break;
-    }
-    // FURI_LOG_I(TAG, "Generic event not handled");
-    return consumed;
-}
-
-void scene_items_on_exit(void* context) {
-    App* app = context;
-    ActionMenu* menu = app->action_menu;
-    action_menu_reset(menu);
-}

+ 0 - 9
quac/scenes/scene_items.h

@@ -1,9 +0,0 @@
-#pragma once
-
-#include <gui/scene_manager.h>
-
-void scene_items_item_callback(void* context, int32_t index, InputType type);
-// For each scene, implement handler callbacks
-void scene_items_on_enter(void* context);
-bool scene_items_on_event(void* context, SceneManagerEvent event);
-void scene_items_on_exit(void* context);

+ 0 - 201
quac/scenes/scene_settings.c

@@ -1,201 +0,0 @@
-#include <furi.h>
-
-#include <gui/view_dispatcher.h>
-#include <gui/scene_manager.h>
-#include <gui/modules/variable_item_list.h>
-#include <toolbox/value_index.h>
-
-#include "quac.h"
-#include "scenes.h"
-#include "scene_settings.h"
-#include "../quac_settings.h"
-
-#include <lib/toolbox/path.h>
-
-// Unfortunately, the VariableItemList does not provide a method to query the length
-// of the list. Since we intend to place "About" last, it would be convenient to
-// dynamically know it's list index for our on_event method. However, we'll need to
-// hardcode the value..
-// TODO: Figure out a better way to do this
-#define SCENE_SETTINGS_ABOUT 8 // 9 items in our Settings list, so last index is 8
-
-static const char* const layout_text[2] = {"Vert", "Horiz"};
-static const uint32_t layout_value[2] = {QUAC_APP_PORTRAIT, QUAC_APP_LANDSCAPE};
-
-static const char* const show_offon_text[2] = {"OFF", "ON"};
-static const uint32_t show_offon_value[2] = {false, true};
-
-#define V_DURATION_COUNT 8
-static const char* const duration_text[V_DURATION_COUNT] = {
-    "500 ms",
-    "1 sec",
-    "1.5 sec",
-    "2 sec",
-    "2.5 sec",
-    "3 sec",
-    "5 sec",
-    "10 sec",
-};
-static const uint32_t duration_value[V_DURATION_COUNT] = {
-    500,
-    1000,
-    1500,
-    2000,
-    2500,
-    3000,
-    5000,
-    10000,
-};
-
-static const char* const disabled_enabled_text[2] = {"Disabled", "Enabled"};
-static const uint32_t disabled_enabled_value[2] = {false, true};
-
-static void scene_settings_layout_changed(VariableItem* item) {
-    App* app = variable_item_get_context(item);
-    uint8_t index = variable_item_get_current_value_index(item);
-    variable_item_set_current_value_text(item, layout_text[index]);
-    app->settings.layout = layout_value[index];
-}
-
-static void scene_settings_show_icons_changed(VariableItem* item) {
-    App* app = variable_item_get_context(item);
-    uint8_t index = variable_item_get_current_value_index(item);
-    variable_item_set_current_value_text(item, show_offon_text[index]);
-    app->settings.show_icons = show_offon_value[index];
-}
-
-static void scene_settings_show_headers_changed(VariableItem* item) {
-    App* app = variable_item_get_context(item);
-    uint8_t index = variable_item_get_current_value_index(item);
-    variable_item_set_current_value_text(item, show_offon_text[index]);
-    app->settings.show_headers = show_offon_value[index];
-}
-
-static void scene_settings_rfid_duration_changed(VariableItem* item) {
-    App* app = variable_item_get_context(item);
-    uint8_t index = variable_item_get_current_value_index(item);
-    variable_item_set_current_value_text(item, duration_text[index]);
-    app->settings.rfid_duration = duration_value[index];
-}
-
-static void scene_settings_nfc_duration_changed(VariableItem* item) {
-    App* app = variable_item_get_context(item);
-    uint8_t index = variable_item_get_current_value_index(item);
-    variable_item_set_current_value_text(item, duration_text[index]);
-    app->settings.nfc_duration = duration_value[index];
-}
-
-static void scene_settings_ibutton_duration_changed(VariableItem* item) {
-    App* app = variable_item_get_context(item);
-    uint8_t index = variable_item_get_current_value_index(item);
-    variable_item_set_current_value_text(item, duration_text[index]);
-    app->settings.ibutton_duration = duration_value[index];
-}
-
-static void scene_settings_ir_ext_changed(VariableItem* item) {
-    App* app = variable_item_get_context(item);
-    uint8_t index = variable_item_get_current_value_index(item);
-    variable_item_set_current_value_text(item, disabled_enabled_text[index]);
-    app->settings.ir_use_ext_module = disabled_enabled_value[index];
-}
-
-static void scene_settings_show_hidden_changed(VariableItem* item) {
-    App* app = variable_item_get_context(item);
-    uint8_t index = variable_item_get_current_value_index(item);
-    variable_item_set_current_value_text(item, show_offon_text[index]);
-    app->settings.show_hidden = show_offon_value[index];
-}
-
-static void scene_settings_enter_callback(void* context, uint32_t index) {
-    App* app = context;
-    view_dispatcher_send_custom_event(app->view_dispatcher, index);
-}
-
-// For each scene, implement handler callbacks
-void scene_settings_on_enter(void* context) {
-    App* app = context;
-
-    VariableItemList* vil = app->vil_settings;
-    variable_item_list_reset(vil);
-
-    VariableItem* item;
-    uint8_t value_index;
-
-    item = variable_item_list_add(vil, "Layout", 2, scene_settings_layout_changed, app);
-    value_index = value_index_uint32(app->settings.layout, layout_value, 2);
-    variable_item_set_current_value_index(item, value_index);
-    variable_item_set_current_value_text(item, layout_text[value_index]);
-
-    item = variable_item_list_add(vil, "Show Icons", 2, scene_settings_show_icons_changed, app);
-    value_index = value_index_uint32(app->settings.show_icons, show_offon_value, 2);
-    variable_item_set_current_value_index(item, value_index);
-    variable_item_set_current_value_text(item, show_offon_text[value_index]);
-
-    item =
-        variable_item_list_add(vil, "Show Headers", 2, scene_settings_show_headers_changed, app);
-    value_index = value_index_uint32(app->settings.show_headers, show_offon_value, 2);
-    variable_item_set_current_value_index(item, value_index);
-    variable_item_set_current_value_text(item, show_offon_text[value_index]);
-
-    item = variable_item_list_add(
-        vil, "RFID Duration", V_DURATION_COUNT, scene_settings_rfid_duration_changed, app);
-    value_index =
-        value_index_uint32(app->settings.rfid_duration, duration_value, V_DURATION_COUNT);
-    variable_item_set_current_value_index(item, value_index);
-    variable_item_set_current_value_text(item, duration_text[value_index]);
-
-    item = variable_item_list_add(
-        vil, "NFC Duration", V_DURATION_COUNT, scene_settings_nfc_duration_changed, app);
-    value_index = value_index_uint32(app->settings.nfc_duration, duration_value, V_DURATION_COUNT);
-    variable_item_set_current_value_index(item, value_index);
-    variable_item_set_current_value_text(item, duration_text[value_index]);
-
-    item = variable_item_list_add(
-        vil, "iButton Duration", V_DURATION_COUNT, scene_settings_ibutton_duration_changed, app);
-    value_index =
-        value_index_uint32(app->settings.ibutton_duration, duration_value, V_DURATION_COUNT);
-    variable_item_set_current_value_index(item, value_index);
-    variable_item_set_current_value_text(item, duration_text[value_index]);
-
-    item = variable_item_list_add(vil, "IR Ext Module", 2, scene_settings_ir_ext_changed, app);
-    value_index = value_index_uint32(app->settings.ir_use_ext_module, disabled_enabled_value, 2);
-    variable_item_set_current_value_index(item, value_index);
-    variable_item_set_current_value_text(item, disabled_enabled_text[value_index]);
-
-    item = variable_item_list_add(vil, "Show Hidden", 2, scene_settings_show_hidden_changed, app);
-    value_index = value_index_uint32(app->settings.show_hidden, show_offon_value, 2);
-    variable_item_set_current_value_index(item, value_index);
-    variable_item_set_current_value_text(item, show_offon_text[value_index]);
-
-    // Last item is always "About"
-    item = variable_item_list_add(vil, "About", 1, NULL, NULL);
-    variable_item_list_set_enter_callback(vil, scene_settings_enter_callback, app);
-
-    view_dispatcher_switch_to_view(app->view_dispatcher, QView_Settings);
-}
-
-bool scene_settings_on_event(void* context, SceneManagerEvent event) {
-    App* app = context;
-    bool consumed = false;
-
-    if(event.type == SceneManagerEventTypeCustom) {
-        switch(event.event) {
-        case SCENE_SETTINGS_ABOUT:
-            consumed = true;
-            scene_manager_next_scene(app->scene_manager, QScene_About);
-            break;
-        default:
-            break;
-        }
-    }
-
-    return consumed;
-}
-
-void scene_settings_on_exit(void* context) {
-    App* app = context;
-    VariableItemList* vil = app->vil_settings;
-    variable_item_list_reset(vil);
-
-    quac_save_settings(app);
-}

+ 0 - 10
quac/scenes/scene_settings.h

@@ -1,10 +0,0 @@
-#pragma once
-
-#include <gui/scene_manager.h>
-
-// void scene_settings_item_callback(void* context, int32_t index, InputType type);
-
-// For each scene, implement handler callbacks
-void scene_settings_on_enter(void* context);
-bool scene_settings_on_event(void* context, SceneManagerEvent event);
-void scene_settings_on_exit(void* context);

+ 0 - 55
quac/scenes/scenes.c

@@ -1,55 +0,0 @@
-
-
-#include "quac.h"
-#include "scenes.h"
-#include "scene_items.h"
-#include "scene_settings.h"
-#include "scene_action_settings.h"
-#include "scene_action_rename.h"
-#include "scene_action_create_group.h"
-#include "scene_action_ir_list.h"
-#include "scene_about.h"
-
-// define handler callbacks - order must match appScenes enum!
-void (*const app_on_enter_handlers[])(void* context) = {
-    scene_items_on_enter,
-    scene_settings_on_enter,
-    scene_action_settings_on_enter,
-    scene_action_rename_on_enter,
-    scene_action_create_group_on_enter,
-    scene_action_ir_list_on_enter,
-    scene_about_on_enter,
-};
-bool (*const app_on_event_handlers[])(void* context, SceneManagerEvent event) = {
-    scene_items_on_event,
-    scene_settings_on_event,
-    scene_action_settings_on_event,
-    scene_action_rename_on_event,
-    scene_action_create_group_on_event,
-    scene_action_ir_list_on_event,
-    scene_about_on_event,
-};
-void (*const app_on_exit_handlers[])(void* context) = {
-    scene_items_on_exit,
-    scene_settings_on_exit,
-    scene_action_settings_on_exit,
-    scene_action_rename_on_exit,
-    scene_action_create_group_on_exit,
-    scene_action_ir_list_on_exit,
-    scene_about_on_exit,
-};
-
-const SceneManagerHandlers app_scene_handlers = {
-    .on_enter_handlers = app_on_enter_handlers,
-    .on_event_handlers = app_on_event_handlers,
-    .on_exit_handlers = app_on_exit_handlers,
-    .scene_num = QScene_count};
-
-bool app_scene_custom_callback(void* context, uint32_t custom_event_id) {
-    App* app = context;
-    return scene_manager_handle_custom_event(app->scene_manager, custom_event_id);
-}
-bool app_back_event_callback(void* context) {
-    App* app = context;
-    return scene_manager_handle_back_event(app->scene_manager);
-}

+ 0 - 35
quac/scenes/scenes.h

@@ -1,35 +0,0 @@
-#pragma once
-
-#include <gui/scene_manager.h>
-typedef enum {
-    QScene_Items,
-    QScene_Settings,
-    QScene_ActionSettings,
-    QScene_ActionRename,
-    QScene_ActionCreateGroup,
-    QScene_ActionIRList,
-    QScene_About,
-    QScene_count
-} appScenes;
-
-typedef enum {
-    QView_ActionMenu, // main UI
-    QView_Settings, // Variable Item List for App Settings
-    QView_SubMenu, // Action: Rename, Delete, Import, IR List
-    QView_TextInput, // Action: Rename, Create Group
-    QView_Popup, // About screen
-} appView;
-
-typedef enum {
-    Event_DeviceSelected,
-    Event_ButtonPressed,
-    Event_ButtonPressedLong
-} AppCustomEvents;
-
-extern void (*const app_on_enter_handlers[])(void*);
-extern bool (*const app_on_event_handlers[])(void*, SceneManagerEvent);
-extern void (*const app_on_exit_handlers[])(void*);
-extern const SceneManagerHandlers app_scene_handlers;
-
-extern bool app_scene_custom_callback(void* context, uint32_t custom_event_id);
-extern bool app_back_event_callback(void* context);

BIN
quac/screenshots/screenshot_1.png


BIN
quac/screenshots/screenshot_1_orig.png


BIN
quac/screenshots/screenshot_2.png


BIN
quac/screenshots/screenshot_2_orig.png


BIN
quac/screenshots/screenshot_3.png


BIN
quac/screenshots/screenshot_3_orig.png


BIN
quac/screenshots/screenshot_4.png


BIN
quac/screenshots/screenshot_4_90.png


BIN
quac/screenshots/screenshot_4_orig.png


+ 0 - 629
quac/views/action_menu.c

@@ -1,629 +0,0 @@
-#include "action_menu.h"
-
-#include <gui/canvas.h>
-#include <gui/elements.h>
-#include <input/input.h>
-
-#include <furi.h>
-
-#include <stdint.h>
-#include <m-array.h>
-
-#include "quac_icons.h"
-
-#define ITEM_FIRST_OFFSET 17
-#define ITEM_NEXT_OFFSET 4
-#define ITEM_HEIGHT 14
-#define ITEM_WIDTH 64
-#define BUTTONS_PER_SCREEN 6
-
-#define ITEMS_PER_SCREEN_LANDSCAPE 3
-#define ITEMS_PER_SCREEN_PORTRAIT 6
-
-#define SCROLL_INTERVAL 333
-#define SCROLL_DELAY 2
-
-static const Icon* ActionMenuIcons[] = {
-    [ActionMenuItemTypeSubGHz] = &I_SubGHz_10px,
-    [ActionMenuItemTypeRFID] = &I_RFID_10px,
-    [ActionMenuItemTypeIR] = &I_IR_10px,
-    [ActionMenuItemTypeNFC] = &I_NFC_10px,
-    [ActionMenuItemTypeiButton] = &I_iButton_10px,
-    [ActionMenuItemTypePlaylist] = &I_Playlist_10px,
-    [ActionMenuItemTypeGroup] = &I_Directory_10px,
-    [ActionMenuItemTypeSettings] = &I_Settings_10px,
-    [ActionMenuItemTypeUnknown] = &I_Unknown_10px,
-};
-
-struct ActionMenuItem {
-    const char* label;
-    uint32_t index;
-    ActionMenuItemCallback callback;
-    ActionMenuItemType type;
-    void* callback_context;
-    bool is_link;
-};
-
-ARRAY_DEF(ActionMenuItemArray, ActionMenuItem, M_POD_OPLIST);
-#define M_OPL_ActionMenuItemArray_t() ARRAY_OPLIST(ActionMenuItemArray, M_POD_OPLIST)
-
-struct ActionMenu {
-    View* view;
-    FuriTimer* scroll_timer;
-};
-
-typedef struct {
-    ActionMenuItemArray_t items;
-    size_t position;
-    size_t window_position;
-    FuriString* header;
-    ActionMenuLayout layout;
-    bool show_icons;
-    bool show_headers;
-    size_t scroll_counter;
-} ActionMenuModel;
-
-// Returns the adjusted scroll counter, accounting for the initial pause/delay
-// when an item is first selected
-size_t get_adjusted_scroll_counter(bool selected, size_t scroll_counter) {
-    if(selected) {
-        if(scroll_counter < SCROLL_DELAY) {
-            return 0;
-        }
-        return scroll_counter - SCROLL_DELAY;
-    }
-    return 0;
-}
-
-static void action_menu_scroll_timer_callback(void* context) {
-    furi_check(context);
-    ActionMenu* menu = context;
-    with_view_model(menu->view, ActionMenuModel * model, { model->scroll_counter++; }, true);
-}
-
-static void action_menu_view_enter_callback(void* context) {
-    furi_check(context);
-    ActionMenu* menu = context;
-    with_view_model(menu->view, ActionMenuModel * model, { model->scroll_counter = 0; }, true);
-    furi_timer_start(menu->scroll_timer, SCROLL_INTERVAL);
-}
-
-static void action_menu_view_exit_callback(void* context) {
-    furi_check(context);
-    ActionMenu* menu = context;
-    furi_timer_stop(menu->scroll_timer);
-}
-
-static void action_menu_draw_landscape(Canvas* canvas, ActionMenuModel* model) {
-    const uint8_t item_height = 16;
-    uint8_t item_width = canvas_width(canvas) - 5; // space for scrollbar
-
-    const bool have_header = furi_string_size(model->header) && model->show_headers;
-
-    canvas_clear(canvas);
-    if(have_header) {
-        canvas_set_font(canvas, FontPrimary);
-        canvas_draw_str(canvas, 4, 11, furi_string_get_cstr(model->header));
-    }
-    canvas_set_font(canvas, FontSecondary);
-
-    size_t position = 0;
-    const size_t items_on_screen = ITEMS_PER_SCREEN_LANDSCAPE + (have_header ? 0 : 1);
-    uint8_t y_offset = have_header ? 16 : 0;
-    const size_t x_txt_start = model->show_icons ? 18 : 4;
-
-    ActionMenuItemArray_it_t it;
-    for(ActionMenuItemArray_it(it, model->items); !ActionMenuItemArray_end_p(it);
-        ActionMenuItemArray_next(it)) {
-        const size_t item_position = position - model->window_position;
-        bool selected = position == model->position;
-
-        if(item_position < items_on_screen) {
-            if(selected) {
-                canvas_set_color(canvas, ColorBlack);
-                elements_slightly_rounded_box(
-                    canvas,
-                    0,
-                    y_offset + (item_position * item_height) + 1,
-                    item_width,
-                    item_height - 2);
-                canvas_set_color(canvas, ColorWhite);
-            } else {
-                canvas_set_color(canvas, ColorBlack);
-            }
-
-            const ActionMenuItem* item = ActionMenuItemArray_cref(it);
-            if(model->show_icons) {
-                canvas_draw_icon(
-                    canvas,
-                    4,
-                    y_offset + (item_position * item_height) + 3,
-                    ActionMenuIcons[item->type]);
-            }
-
-            size_t scroll_counter = get_adjusted_scroll_counter(selected, model->scroll_counter);
-
-            FuriString* disp_str;
-            disp_str = furi_string_alloc_set(item->label);
-            size_t width = item_width - x_txt_start - 2;
-            width -= item->is_link ? 2 : 0;
-            elements_scrollable_text_line(
-                canvas,
-                x_txt_start,
-                y_offset + (item_position * item_height) + item_height - 4,
-                width,
-                disp_str,
-                scroll_counter,
-                false);
-            furi_string_free(disp_str);
-
-            // draw a link indicator/glyph/icon thing
-            if(item->is_link) {
-                // canvas_draw_line(
-                //     canvas,
-                //     item_width - 3,
-                //     y_offset + (item_position * item_height) + 3,
-                //     item_width - 3,
-                //     y_offset + (item_position * item_height) + item_height - 4);
-
-                canvas_draw_dot(
-                    canvas, item_width - 3, y_offset + (item_position * item_height) + 6);
-                canvas_draw_dot(
-                    canvas, item_width - 3, y_offset + (item_position * item_height) + 8);
-                canvas_draw_dot(
-                    canvas, item_width - 3, y_offset + (item_position * item_height) + 10);
-            }
-        }
-        position++;
-    }
-
-    elements_scrollbar(canvas, model->position, ActionMenuItemArray_size(model->items));
-}
-
-static void action_menu_draw_portrait(Canvas* canvas, ActionMenuModel* model) {
-    const bool have_header = furi_string_size(model->header) && model->show_headers;
-    const size_t items_per_screen = have_header ? ITEMS_PER_SCREEN_PORTRAIT :
-                                                  ITEMS_PER_SCREEN_PORTRAIT + 1;
-    const size_t active_screen = model->position / items_per_screen;
-    const size_t items_size = ActionMenuItemArray_size(model->items);
-    const size_t max_screen = items_size ? (items_size - 1) / items_per_screen : 0;
-
-    canvas_clear(canvas);
-
-    // Draw up/down arrows, as needed
-    if(active_screen > 0) {
-        canvas_draw_icon(canvas, 28, 1, &I_ArrowUp_8x4);
-    }
-    if(max_screen > active_screen) {
-        canvas_draw_icon(canvas, 28, 123, &I_ArrowDown_8x4);
-    }
-
-    if(have_header) {
-        // Center the header if it fits, otherwise left-justify
-        canvas_set_font(canvas, FontPrimary);
-        uint16_t width = canvas_string_width(canvas, furi_string_get_cstr(model->header));
-        if(width <= ITEM_WIDTH) {
-            canvas_draw_str_aligned(
-                canvas, 32, 10, AlignCenter, AlignCenter, furi_string_get_cstr(model->header));
-        } else {
-            canvas_draw_str_aligned(
-                canvas, 0, 10, AlignLeft, AlignCenter, furi_string_get_cstr(model->header));
-        }
-    }
-    canvas_set_font(canvas, FontSecondary);
-
-    size_t item_position = 0;
-    const size_t x_txt_start = model->show_icons ? 16 : 4;
-    const size_t y_offset = have_header ? ITEM_FIRST_OFFSET : 6;
-    const size_t item_next_offset = have_header ? ITEM_NEXT_OFFSET : ITEM_NEXT_OFFSET - 1;
-
-    ActionMenuItemArray_it_t it;
-    for(ActionMenuItemArray_it(it, model->items); !ActionMenuItemArray_end_p(it);
-        ActionMenuItemArray_next(it), ++item_position) {
-        if(active_screen == (item_position / items_per_screen)) {
-            uint8_t position_offset = item_position % items_per_screen;
-            bool selected = item_position == model->position;
-
-            // draw the item
-            uint8_t item_y = y_offset + (position_offset * (ITEM_HEIGHT + item_next_offset));
-
-            canvas_set_color(canvas, ColorBlack);
-
-            if(selected) {
-                // Same as elements_slightly_rounded_box with radius of 5
-                canvas_draw_rbox(canvas, 0, item_y, ITEM_WIDTH, ITEM_HEIGHT, 1);
-                canvas_set_color(canvas, ColorWhite);
-            } else {
-                canvas_draw_rframe(canvas, 0, item_y, ITEM_WIDTH, ITEM_HEIGHT, 1);
-            }
-
-            const ActionMenuItem* item = ActionMenuItemArray_cref(it);
-            if(model->show_icons) {
-                canvas_draw_icon(canvas, 3, item_y + 2, ActionMenuIcons[item->type]);
-            }
-
-            size_t scroll_counter = get_adjusted_scroll_counter(selected, model->scroll_counter);
-
-            FuriString* disp_str;
-            disp_str = furi_string_alloc_set(item->label);
-            size_t width = ITEM_WIDTH - x_txt_start - 1;
-            width -= item->is_link ? 2 : 0;
-            elements_scrollable_text_line(
-                canvas,
-                x_txt_start,
-                item_y + (ITEM_HEIGHT / 2) + 3,
-                width,
-                disp_str,
-                scroll_counter,
-                false);
-            furi_string_free(disp_str);
-
-            // draw a link indicator/glyph/icon thing
-            if(item->is_link) {
-                // top-line, 2 rows
-                // canvas_draw_line(canvas, ITEM_WIDTH - 6, item_y, ITEM_WIDTH - 1, item_y);
-                // canvas_draw_line(canvas, ITEM_WIDTH - 6, item_y + 1, ITEM_WIDTH - 1, item_y + 1);
-
-                // single vertical line
-                // canvas_draw_line(
-                //     canvas, ITEM_WIDTH - 3, item_y + 2, ITEM_WIDTH - 3, item_y + ITEM_HEIGHT - 3);
-
-                // 3 vertical dots
-                canvas_draw_dot(canvas, ITEM_WIDTH - 3, item_y + 4);
-                canvas_draw_dot(canvas, ITEM_WIDTH - 3, item_y + 6);
-                canvas_draw_dot(canvas, ITEM_WIDTH - 3, item_y + 8);
-            }
-        }
-    }
-}
-
-static void action_menu_view_draw_callback(Canvas* canvas, void* context) {
-    furi_assert(canvas);
-    ActionMenuModel* model = (ActionMenuModel*)context;
-
-    if(model->layout == ActionMenuLayoutLandscape) {
-        action_menu_draw_landscape(canvas, model);
-    } else {
-        action_menu_draw_portrait(canvas, model);
-    }
-}
-
-static void action_menu_process_up(ActionMenu* action_menu) {
-    furi_assert(action_menu);
-
-    with_view_model(
-        action_menu->view,
-        ActionMenuModel * model,
-        {
-            const size_t items_size = ActionMenuItemArray_size(model->items);
-            if(model->layout == ActionMenuLayoutPortrait) {
-                if(model->position > 0) {
-                    model->position--;
-                } else {
-                    model->position = items_size - 1;
-                }
-            } else {
-                const size_t items_on_screen = furi_string_empty(model->header) ? 4 : 3;
-                if(model->position > 0) {
-                    model->position--;
-                    if((model->position == model->window_position) &&
-                       (model->window_position > 0)) {
-                        model->window_position--;
-                    }
-                } else {
-                    model->position = items_size - 1;
-                    if(model->position > items_on_screen - 1) {
-                        model->window_position = model->position - (items_on_screen - 1);
-                    }
-                }
-            }
-            model->scroll_counter = 0;
-        },
-        true);
-}
-
-static void action_menu_process_down(ActionMenu* action_menu) {
-    furi_assert(action_menu);
-
-    with_view_model(
-        action_menu->view,
-        ActionMenuModel * model,
-        {
-            const size_t items_size = ActionMenuItemArray_size(model->items);
-            if(model->layout == ActionMenuLayoutPortrait) {
-                if(model->position < items_size - 1) {
-                    model->position++;
-                } else {
-                    model->position = 0;
-                }
-            } else {
-                const size_t items_on_screen = furi_string_empty(model->header) ? 4 : 3;
-                if(model->position < items_size - 1) {
-                    model->position++;
-                    if((model->position - model->window_position > items_on_screen - 2) &&
-                       (model->window_position < items_size - items_on_screen)) {
-                        model->window_position++;
-                    }
-                } else {
-                    model->position = 0;
-                    model->window_position = 0;
-                }
-            }
-            model->scroll_counter = 0;
-        },
-        true);
-}
-
-// Used for both the Short and Long presses of OK
-static void action_menu_process_ok(ActionMenu* action_menu, InputType type) {
-    furi_assert(action_menu);
-
-    // FURI_LOG_I("AM", "OK pressed! %d: %s", type, input_get_type_name(type));
-    ActionMenuItem* item = NULL;
-
-    with_view_model(
-        action_menu->view,
-        ActionMenuModel * model,
-        {
-            if(model->position < (ActionMenuItemArray_size(model->items))) {
-                item = ActionMenuItemArray_get(model->items, model->position);
-                if(item->callback) {
-                    item->callback(item->callback_context, item->index, type);
-                }
-            }
-        },
-        false);
-}
-
-static bool action_menu_view_input_callback(InputEvent* event, void* context) {
-    furi_assert(event);
-
-    ActionMenu* action_menu = context;
-    bool consumed = false;
-
-    if(event->type == InputTypeShort) {
-        switch(event->key) {
-        case InputKeyOk:
-            consumed = true;
-            action_menu_process_ok(action_menu, event->type);
-            break;
-        case InputKeyUp:
-            consumed = true;
-            action_menu_process_up(action_menu);
-            break;
-        case InputKeyDown:
-            consumed = true;
-            action_menu_process_down(action_menu);
-            break;
-        case InputKeyLeft:
-            break;
-        case InputKeyRight:
-            break;
-        default:
-            // FURI_LOG_E("AM", "Unknown key!");
-            break;
-        }
-    } else if(event->type == InputTypeLong) {
-        if(event->key == InputKeyRight) {
-            consumed = true;
-            action_menu_process_ok(action_menu, event->type);
-        }
-    }
-
-    return consumed;
-}
-
-View* action_menu_get_view(ActionMenu* action_menu) {
-    furi_assert(action_menu);
-    return action_menu->view;
-}
-
-void action_menu_reset(ActionMenu* action_menu) {
-    furi_assert(action_menu);
-
-    with_view_model(
-        action_menu->view,
-        ActionMenuModel * model,
-        {
-            // for
-            //     M_EACH(item, model->items, ActionMenuItemArray_t) {
-            //         icon_animation_stop(item->icon);
-            //         icon_animation_free(item->icon);
-            //     }
-            ActionMenuItemArray_reset(model->items);
-            model->position = 0;
-            model->window_position = 0;
-            furi_string_reset(model->header);
-        },
-        true);
-}
-
-void action_menu_set_layout(ActionMenu* action_menu, ActionMenuLayout layout) {
-    furi_assert(action_menu);
-
-    with_view_model(
-        action_menu->view,
-        ActionMenuModel * model,
-        {
-            model->layout = layout;
-            if(model->layout == ActionMenuLayoutLandscape) {
-                view_set_orientation(action_menu->view, ViewOrientationHorizontal);
-            } else {
-                view_set_orientation(action_menu->view, ViewOrientationVertical);
-            }
-        },
-        true);
-}
-
-void action_menu_set_header(ActionMenu* action_menu, const char* header) {
-    furi_assert(action_menu);
-    with_view_model(
-        action_menu->view,
-        ActionMenuModel * model,
-        {
-            if(header == NULL) {
-                furi_string_reset(model->header);
-            } else {
-                furi_string_set_str(model->header, header);
-            }
-        },
-        true);
-}
-
-void action_menu_set_show_icons(ActionMenu* action_menu, bool show_icons) {
-    with_view_model(
-        action_menu->view, ActionMenuModel * model, { model->show_icons = show_icons; }, true);
-}
-
-void action_menu_set_show_headers(ActionMenu* action_menu, bool show_headers) {
-    with_view_model(
-        action_menu->view, ActionMenuModel * model, { model->show_headers = show_headers; }, true);
-}
-
-ActionMenuItem* action_menu_add_item(
-    ActionMenu* action_menu,
-    const char* label,
-    int32_t index,
-    ActionMenuItemCallback callback,
-    ActionMenuItemType type,
-    void* callback_context) {
-    ActionMenuItem* item = NULL;
-    furi_assert(label);
-    furi_assert(action_menu);
-
-    with_view_model(
-        action_menu->view,
-        ActionMenuModel * model,
-        {
-            item = ActionMenuItemArray_push_new(model->items);
-            item->label = label;
-            // item->icon = icon ? icon_animation_alloc(icon) : NULL; // or default icon?
-            // view_tie_icon_animation(action_menu->view, item->icon);
-            item->index = index;
-            item->type = type;
-            item->callback = callback;
-            item->callback_context = callback_context;
-        },
-        true);
-
-    return item;
-}
-
-ActionMenu* action_menu_alloc(void) {
-    ActionMenu* action_menu = malloc(sizeof(ActionMenu));
-    action_menu->view = view_alloc();
-    view_set_orientation(action_menu->view, ViewOrientationHorizontal);
-    view_set_context(action_menu->view, action_menu);
-    view_allocate_model(action_menu->view, ViewModelTypeLocking, sizeof(ActionMenuModel));
-    view_set_draw_callback(action_menu->view, action_menu_view_draw_callback);
-    view_set_input_callback(action_menu->view, action_menu_view_input_callback);
-    view_set_enter_callback(action_menu->view, action_menu_view_enter_callback);
-    view_set_exit_callback(action_menu->view, action_menu_view_exit_callback);
-
-    action_menu->scroll_timer =
-        furi_timer_alloc(action_menu_scroll_timer_callback, FuriTimerTypePeriodic, action_menu);
-
-    with_view_model(
-        action_menu->view,
-        ActionMenuModel * model,
-        {
-            ActionMenuItemArray_init(model->items);
-            model->position = 0;
-            model->window_position = 0;
-            model->header = furi_string_alloc();
-            model->layout = ActionMenuLayoutLandscape; // TODO: ehhhhhhhhhhhhhhhhhhh
-            model->show_icons = true;
-            model->show_headers = true;
-        },
-        true);
-
-    return action_menu;
-}
-
-void action_menu_free(ActionMenu* action_menu) {
-    furi_assert(action_menu);
-
-    with_view_model(
-        action_menu->view,
-        ActionMenuModel * model,
-        {
-            // for
-            //     M_EACH(item, model->items, ActionMenuItemArray_t) {
-            //         icon_animation_stop(item->icon);
-            //         icon_animation_free(item->icon);
-            //     }
-            ActionMenuItemArray_clear(model->items);
-            furi_string_free(model->header);
-        },
-        true);
-    view_free(action_menu->view);
-    furi_timer_free(action_menu->scroll_timer);
-    free(action_menu);
-}
-
-void action_menu_set_selected_item(ActionMenu* action_menu, uint32_t index) {
-    furi_assert(action_menu);
-
-    ActionMenuModel* m = view_get_model(action_menu->view);
-    if(m->layout == ActionMenuLayoutPortrait) {
-        with_view_model(
-            action_menu->view,
-            ActionMenuModel * model,
-            {
-                size_t item_position = 0;
-                ActionMenuItemArray_it_t it;
-                for(ActionMenuItemArray_it(it, model->items); !ActionMenuItemArray_end_p(it);
-                    ActionMenuItemArray_next(it), ++item_position) {
-                    if((uint32_t)ActionMenuItemArray_cref(it)->index == index) {
-                        model->position = item_position;
-                        break;
-                    }
-                }
-            },
-            true);
-    } else {
-        with_view_model(
-            action_menu->view,
-            ActionMenuModel * model,
-            {
-                size_t position = 0;
-                ActionMenuItemArray_it_t it;
-                for(ActionMenuItemArray_it(it, model->items); !ActionMenuItemArray_end_p(it);
-                    ActionMenuItemArray_next(it)) {
-                    if(index == ActionMenuItemArray_cref(it)->index) {
-                        break;
-                    }
-                    position++;
-                }
-                const size_t items_size = ActionMenuItemArray_size(model->items);
-
-                if(position >= items_size) {
-                    position = 0;
-                }
-
-                model->position = position;
-                model->window_position = position;
-
-                if(model->window_position > 0) {
-                    model->window_position -= 1;
-                }
-
-                const size_t items_on_screen = furi_string_empty(model->header) ? 4 : 3;
-
-                if(items_size <= items_on_screen) {
-                    model->window_position = 0;
-                } else {
-                    const size_t pos = items_size - items_on_screen;
-                    if(model->window_position > pos) {
-                        model->window_position = pos;
-                    }
-                }
-            },
-            true);
-    }
-}
-
-void action_menu_item_set_link(ActionMenuItem* action_item, bool is_link) {
-    furi_assert(action_item);
-    action_item->is_link = is_link;
-}

+ 0 - 128
quac/views/action_menu.h

@@ -1,128 +0,0 @@
-#pragma once
-
-#include <stdint.h>
-#include <gui/view.h>
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-/** ActionMenu anonymous structure */
-typedef struct ActionMenu ActionMenu;
-
-/** ActionMenuItem anonymous structure */
-typedef struct ActionMenuItem ActionMenuItem;
-
-/** Callback for any button menu actions */
-typedef void (*ActionMenuItemCallback)(void* context, int32_t index, InputType type);
-
-/** Type of UI element */
-typedef enum {
-    ActionMenuItemTypeSubGHz,
-    ActionMenuItemTypeRFID,
-    ActionMenuItemTypeIR,
-    ActionMenuItemTypeNFC,
-    ActionMenuItemTypeiButton,
-    ActionMenuItemTypePlaylist,
-    ActionMenuItemTypeGroup,
-    ActionMenuItemTypeSettings,
-    ActionMenuItemTypeUnknown,
-    ActionMenuItemType_count
-} ActionMenuItemType;
-
-typedef enum {
-    ActionMenuLayoutPortrait,
-    ActionMenuLayoutLandscape,
-} ActionMenuLayout;
-
-/** Get button menu view
- *
- * @param      action_menu  ActionMenu instance
- *
- * @return     View instance that can be used for embedding
- */
-View* action_menu_get_view(ActionMenu* action_menu);
-
-/** Clean button menu
- *
- * @param      action_menu  ActionMenu instance
- */
-void action_menu_reset(ActionMenu* action_menu);
-
-/** Set the layout
- * 
- * @param      layout       Portrait or Landscape
-*/
-void action_menu_set_layout(ActionMenu* menu, ActionMenuLayout layout);
-
-/** Show/Hide icons in UI
- * 
- * @param      show_icons   Show or Hide icons
-*/
-void action_menu_set_show_icons(ActionMenu* menu, bool show_icons);
-
-/** Show/Hide header labels in UI
- * 
- * @param      show_headers   Show or Hide header labels
-*/
-void action_menu_set_show_headers(ActionMenu* menu, bool show_headers);
-
-/** Add item to button menu instance
- *
- * @param      action_menu       ActionMenu instance
- * @param      label             text inside new button
- * @param      icon              IconAnimation instance
- * @param      index             value to distinct between buttons inside
- *                               ActionMenuItemCallback
- * @param      callback          The callback
- * @param      type              type of button to create. Differ by button
- *                               drawing. Control buttons have no frames, and
- *                               have more squared borders.
- * @param      callback_context  The callback context
- *
- * @return     pointer to just-created item
- */
-ActionMenuItem* action_menu_add_item(
-    ActionMenu* action_menu,
-    const char* label,
-    int32_t index,
-    ActionMenuItemCallback callback,
-    ActionMenuItemType type,
-    void* callback_context);
-
-/** Allocate and initialize new instance of ActionMenu model
- *
- * @return     just-created ActionMenu model
- */
-ActionMenu* action_menu_alloc(void);
-
-/** Free ActionMenu element
- *
- * @param      action_menu  ActionMenu instance
- */
-void action_menu_free(ActionMenu* action_menu);
-
-/** Set ActionMenu header on top of canvas
- *
- * @param      action_menu  ActionMenu instance
- * @param      header       header on the top of button menu
- */
-void action_menu_set_header(ActionMenu* action_menu, const char* header);
-
-/** Set selected item
- *
- * @param      action_menu  ActionMenu instance
- * @param      index        index of ActionMenu to be selected
- */
-void action_menu_set_selected_item(ActionMenu* action_menu, uint32_t index);
-
-/** Set whether this item is a link
- *
- * @param      action_item  ActionMenuItem pointer
- * @param      is_link      bool
- */
-void action_menu_item_set_link(ActionMenuItem* action_item, bool is_link);
-
-#ifdef __cplusplus
-}
-#endif