From e9b37d5b50a16b24a652cc5235c6d3633ca06cfc Mon Sep 17 00:00:00 2001 From: Derek Jamison Date: Thu, 30 Mar 2023 16:03:11 -0400 Subject: [PATCH] Cookie Clicker over bluetooth! --- hid/hid_app/README.md | 466 ++++++++++++++++++ .../final_files/assets/Cookie_def_11x9.png | Bin 0 -> 3664 bytes .../assets/Cookie_pressed_17x17.png | Bin 0 -> 3689 bytes hid/hid_app/final_files/hid.c | 438 ++++++++++++++++ hid/hid_app/final_files/hid.h | 67 +++ hid/hid_app/final_files/views.h | 11 + hid/hid_app/final_files/views/hid_cc.c | 287 +++++++++++ hid/hid_app/final_files/views/hid_cc.h | 14 + 8 files changed, 1283 insertions(+) create mode 100644 hid/hid_app/README.md create mode 100644 hid/hid_app/final_files/assets/Cookie_def_11x9.png create mode 100644 hid/hid_app/final_files/assets/Cookie_pressed_17x17.png create mode 100644 hid/hid_app/final_files/hid.c create mode 100644 hid/hid_app/final_files/hid.h create mode 100644 hid/hid_app/final_files/views.h create mode 100644 hid/hid_app/final_files/views/hid_cc.c create mode 100644 hid/hid_app/final_files/views/hid_cc.h diff --git a/hid/hid_app/README.md b/hid/hid_app/README.md new file mode 100644 index 0000000..b6c95e9 --- /dev/null +++ b/hid/hid_app/README.md @@ -0,0 +1,466 @@ +# Cookie Clicker Tutorial + +This tutorial modifies the Flipper Zero firmware to add a Cookie Clicker controller option to the hid_app (known as "Remote" under Applications/Bluetooth). This Cookie clicker will automatically click the mouse and allow you to set the speed of the clicks. + +The "Remote" application is a Bluetooth HID (Human Interface Device) application. It allows you to connect to a Bluetooth HID device and send keystrokes and mouse clicks. The Flipper Zero firmware has a TikTok controller built in. This tutorial will show you how to modify the firmware to add a Cookie Clicker controller, starting with the code from the TikTok controller. + +Make sure you have a working Flipper Zero before you start this tutorial & that +you have enabled Bluetooth on the Flipper Zero (under Settings). You will need to pair the Flipper Zero with your phone before you can use the Cookie Clicker controller (the Flipper Zero device should display in your phone's Bluetooth settings while the "Remote" application is running). + +If you are unclear of a step, the [final_files](./final_files/) folder contains the modified files you should have after you have completed the tutorial. Please let me know which step was unclear and I will try to clarify it for future users. + +## Step 1. Install Git and VS Code. + +If you have not already installed Git and VS Code, you will need to do so. The following links will take you to the download pages for Git and VS Code. + +- [Git](https://git-scm.com/downloads) +- [VS Code](https://code.visualstudio.com/download) + +## Step 2. Clone the Flipper Zero firmware. + +Clone the Flipper Zero firmware from GitHub. The following command will clone the firmware into a folder named official-firmware. (The below commands may wrap on your screen. You can copy and paste the entire command; there should only be two lines.) + +```console +cd +git clone --recursive https://github.com/flipperdevices/flipperzero-firmware.git official-firmware +``` + +Replace _<your working directory>_ with the directory where you want to clone the firmware. + +## Step 3. Run FBT to build the firmware and configure VS Code. + +Run the following commands from the root of the firmware folder to build the firmware and configure VS Code. Replace _<your working directory>_ with the directory where you cloned the firmware. In some environments you do not need the "./" at the beginning of the command. + +```console +cd +cd official-firmware +./fbt vscode_dist +./fbt updater_package +``` + +\*\*\* **Please follow the steps at [FBT VSCode integration](https://github.com/flipperdevices/flipperzero-firmware/blob/dev/documentation/fbt.md#vscode-integration) before proceeding.** \*\*\* + +## Step 4. Open the applications\external\hid_app folder in VS Code. + +After following the previous step, navigate to the **applications \ external \ hid_app** folder in VS Code. + +## Step 5. Expand the assets folder. + +Expand the **assets** folder in VS Code. + +## Step 6. Create a Cookie_def_11x9.png. + +Copy the **Like_def_11x9.png** file to **Cookie_def_11x9.png**. +Edit the file to have a cookie image. + +![11x9 cookie](./final_files/assets/cookie_def_11x9.png) + +## Step 7. Create a Cookie_pressed_17x17.png. + +Copy the **Like_pressed_17x17.png** file to **Cookie_pressed_17x17.png**. +Edit the file to have a cookie image. + +![17x17 cookie](./final_files/assets/cookie_pressed_17x17.png) + +## Step 8. Expand the views folder. + +Expand the **views** folder in VS Code. + +## Step 9. Create a hid_cc.h file. + +In the views folder, copy the **hid_tiktok.h** file to **hid_cc.h**. + +## Step 10. Edit the hid_cc.h file. + +Edit the **hid_cc.h** file to have the following changes: + +- Case-sensitive! Replace all TikTok with CC +- Case-sensitive! Replace all \_tiktok with \_cc + +## Step 11. Create a hid_cc.c file. + +In the views folder, copy the **hid_tiktok.c** file to **hid_cc.c**. + +## Step 12. Edit the hid_cc.c file. + +Edit the **hid_cc.c** file to have the following changes: + +- Case-sensitive! Replace all TikTok with CC +- Case-sensitive! Replace all \_tiktok with \_cc +- Case-sensitive! Replace all I_LIKE\_ with I_COOKIE\_ + +## Step 13. Edit the views.h file. + +Edit the **views.h** file to have the following changes: + +- Below **BtHidViewTikTok,** add the following line: + +```c + BtHidViewCC, +``` + +## Step 14. Edit the hid.h file. + +Edit the **hid.h** file to have the following changes: + +- Below **#include "views/hid_tiktok.h"** add the following line: + +```c +#include "views/hid_cc.h" +``` + +- Below **HidTikTok\* hid_tiktok;** add the following line: + +```c +HidCC* hid_cc; +``` + +## Step 15. Edit the hid.c file. + +Edit the **hid.c** file to have the following changes: + +- Below **HidSubmenuIndexTikTok,** add the following line: + +```c + HidSubmenuIndexCC, +``` + +- Below **view_dispatcher_switch_to_view(app->view_dispatcher, BtHidViewTikTok);** add the following lines: + +```c + } else if(index == HidSubmenuIndexCC) { + app->view_id = BtHidViewCC; + view_dispatcher_switch_to_view(app->view_dispatcher, BtHidViewCC); +``` + +- Below **hid_tiktok_set_connected_status(hid->hid_tiktok, connected);** add the following line: + +```c +hid_cc_set_connected_status(hid->hid_cc, connected); +``` + +- Find the text "TikTok Controller" and then after the next close bracket (a few lines later), add the following commands: + +```c + if(app->transport == HidTransportBle) { + submenu_add_item( + app->device_type_submenu, + "CC Controller", + HidSubmenuIndexCC, + hid_submenu_callback, + app); + } +``` + +- Find the text "TikTok view" and then after the next blank line (a few lines later), add the following commands: + +```c + // CC view + app->hid_cc = hid_cc_alloc(app); + view_set_previous_callback(hid_cc_get_view(app->hid_cc), hid_exit_confirm_view); + view_dispatcher_add_view(app->view_dispatcher, BtHidViewCC, hid_cc_get_view(app->hid_cc)); +``` + +- After the line **hid_tiktok_free(hid->hid_tiktok);** add the following commands: + +```c + view_dispatcher_remove_view(app->view_dispatcher, BtHidViewCC); + hid_cc_free(app->hid_cc); +``` + +## Step 16. Compile and run the application + +You should now how copied the TikTok application to the Cookie Clicker application. Lets build and deploy the firmware to your Flipper Zero, +and confirm that "Cookie Clicker" is just a copy of TikTok. + +Run the following command from the root of the firmware folder to compile and run the application. Make sure you are not currently running the Flipper UI, +qFlipper or lab.flipper.net. In some environments you do not need the "./" at the beginning of the command. + +```console +cd +cd official-firmware +./fbt launch_app APPSRC=applications/external/hid_app +``` + +The application should run on the Flipper Zero and you should see the Cookie Clicker application. You can now edit the application to add your own +custom code. + +## Step 17. Decide what changes we need to make to our view (hid_cc.c) + +We need to make the following changes to our hid_cc view: + +- Add a timer that fires every so often. +- Keep track of the click speed and if the timer is enabled. +- Display the speed of the cookie clicker. +- When the timer fires, we need to click the mouse if enabled. +- When UP is pressed, increase the speed of the cookie clicker. +- When DOWN is pressed, decrease the speed of the cookie clicker. +- When OK is pressed, toggle the timer on/off. + +## Step 18. Add a "FuriTimer\* timer" to the HidCC: + +In the **hid_cc.c** file, find modify the **struct HidCC** to have the following changes: + +```c +struct HidCC { +View* view; +Hid* hid; +FuriTimer* timer; // Add this line +}; +``` + +## Step 19. Add a "float timer_duration" and "bool timer_click_enabled" to HidCCModel. + +In the **hid_cc.c** file, find modify the **HidCCModel** to have the following changes: + +```c +typedef struct { +bool left_pressed; +bool up_pressed; +bool right_pressed; +bool down_pressed; +bool ok_pressed; +bool connected; +bool is_cursor_set; +HidTransport transport; +float timer_duration; // Add this line (duration in ms) +bool timer_click_enabled; // Add this line (are we clicking) +} HidCCModel; +``` + +## Step 20. Rename title from "CC" to "Cookie\nClicker". + +Replace the title which should be double-quotes followed by CC followed by double-quotes. We use \n so the text will wrap 2 lines. Find the line **elements_multiline_text_aligned(canvas, 17, 3, AlignLeft, AlignTop, "CC");** which contains the title and change it to: + +```c +elements_multiline_text_aligned( +canvas, 17, 3, AlignLeft, AlignTop, "Cookie\nClicker"); +``` + +## Step 21. Add code to display the click speed, right after displaying the title. + +The **canvas_set_font(canvas, FontSecondary);** should already be the text immediately after the title. We will add the code to display the click speed +right after this line. Find the line **canvas_set_font(canvas, FontSecondary);** and add the following code: + +```c +canvas_set_font(canvas, FontSecondary); +FuriString\* buffer = furi_string_alloc(32); +furi_string_printf( +buffer, +"%0.1f ms\r\n", +(double)model->timer_duration); +elements_multiline_text_aligned(canvas, 17, 25, AlignLeft, AlignTop, furi_string_get_cstr(buffer)); +furi_string_free(buffer); +``` + +This code does the following: + +- Sets the font to FontSecondary. +- Allocates a buffer to hold the text. +- Formats the text (containing the duration) into the buffer. +- Displays the text on the screen. +- Frees the buffer. + +## Step 22. Add a new method to update the timer. + +-You can add this code immediate before the **static void hid_cc_reset_cursor(HidCC\* hid_cc)** code. Find the line **static void hid_cc_reset_cursor(HidCC\* hid_cc)** and add the following _above_ it: + +```c +static void hid_cc_update_timer(HidCC* hid_cc, HidCCModel* model) { + furi_assert(hid_cc); + furi_assert(model); + if(model->timer_click_enabled) { + furi_timer_start(hid_cc->timer, model->timer_duration); + } else { + furi_timer_stop(hid_cc->timer); + } +} +``` + +This code does the following: + +- Checks that the hid_cc and model are not null. +- If the timer is enabled, start the timer using the duration from the model. +- If the timer is disabled, stop the timer. + +## Step 23. Add a new method to handle when the timer fires. + +- You can add this code immediately after the code from the previous step. + +```c +static void hid_cc_tick_callback(void* context) { + furi_assert(context); + HidCC* hid_cc = context; + + with_view_model( + hid_cc->view, + HidCCModel * model, + { + if (model->timer_click_enabled) { + hid_hal_mouse_press(hid_cc->hid, HID_MOUSE_BTN_LEFT); + furi_delay_ms(10); + hid_hal_mouse_release(hid_cc->hid, HID_MOUSE_BTN_LEFT); + } + }, + true); +} +``` + +This code does the following: + +- Checks that the context is not null. +- Gets the model from the view. +- If the timer is enabled, click the mouse. + +## Step 24, change what the OK button does. + +We will update the hid_cc_input_callback handling of short press of the OK button. We want the code to toggle the timer_click_enabled value and then update_timer (so it starts/stops). + +REPLACE: + +```c +if(event->key == InputKeyOk) { +hid_hal_mouse_press(hid_tiktok->hid, HID_MOUSE_BTN_LEFT); +furi_delay_ms(50); +hid_hal_mouse_release(hid_tiktok->hid, HID_MOUSE_BTN_LEFT); +furi_delay_ms(50); +hid_hal_mouse_press(hid_tiktok->hid, HID_MOUSE_BTN_LEFT); +furi_delay_ms(50); +hid_hal_mouse_release(hid_tiktok->hid, HID_MOUSE_BTN_LEFT); +consumed = true; +} +``` + +WITH: + +```c +if(event->key == InputKeyOk) { + model->timer_click_enabled = !model->timer_click_enabled; + hid_cc_update_timer(hid_cc, model); + consumed = true; +} +``` + +## Step 25, change what the UP button does. + +We will update the hid_cc_input_callback handling of short press of the UP button. We want the code to reduce the duration of the timer to 95% of value + update_timer. + +REPLACE: +```c +else if(event->key == InputKeyUp) { +// Emulate up swipe +hid_hal_mouse_scroll(hid_tiktok->hid, -6); +hid_hal_mouse_scroll(hid_tiktok->hid, -12); +hid_hal_mouse_scroll(hid_tiktok->hid, -19); +hid_hal_mouse_scroll(hid_tiktok->hid, -12); +hid_hal_mouse_scroll(hid_tiktok->hid, -6); +consumed = true; +```` + +WITH: + +```c +} else if(event->key == InputKeyUp) { + // Reduce duration to 95% of value + update_timer. + if (model->timer_duration>0) { + model->timer_duration \*= 0.95f; + hid_cc_update_timer(hid_cc, model); + } + consumed = true; +``` + +## Step 26, change what the DOWN button does. + +We will update the hid_cc_input_callback handling of short press of the DOWN button. We want the code to increase the duration of the timer to 105% of value + update_timer. + +REPLACE: + +```c +} else if(event->key == InputKeyDown) { +// Emulate down swipe +hid_hal_mouse_scroll(hid_tiktok->hid, 6); +hid_hal_mouse_scroll(hid_tiktok->hid, 12); +hid_hal_mouse_scroll(hid_tiktok->hid, 19); +hid_hal_mouse_scroll(hid_tiktok->hid, 12); +hid_hal_mouse_scroll(hid_tiktok->hid, 6); +consumed = true; +``` + +WITH: + +```c +} else if(event->key == InputKeyDown) { + // Increase duration to 105% of value + update_timer + model->timer_duration \*= 1.05f; + hid_cc_update_timer(hid_cc, model); + consumed = true; +``` + +## Step 27, allocate the timer object. + +Update hid_cc_alloc to set timer values & allocate timer object. + +REPLACE: + +```c +with_view_model( +hid_cc->view, HidCCModel * model, { model->transport = bt*hid->transport; }, true); +``` + +WITH: + +```c +with_view_model( +hid_cc->view, +HidCCModel * model, +{ +model->transport = bt_hid->transport; +// Set default values for timer +model->timer_duration = 22.0f; +model->timer_click_enabled = false; +}, +true); +// Allocate timer +hid_cc->timer = furi_timer_alloc(hid_cc_tick_callback, FuriTimerTypePeriodic, hid_cc); +``` + +This code does the following: + +- Gets the model from the view. +- Sets the default values for the timer (22ms and not enabled). +- Allocates the timer object. + +## Step 28, free the timer object. + +Update hic_cc_free to free timer object. + +REPLACE: + +```c +view_free(hid_cc->view); +``` + +WITH: + +```c +furi_timer_free(hid_cc->timer); +view_free(hid_cc->view); +``` + +## Step 29. Compile and run the application + +You should now how copied the TikTok application to the Cookie Clicker application. Lets build and deploy the firmware to your Flipper Zero, +and confirm that "Cookie Clicker" is just a copy of TikTok. + +Run the following command from the root of the firmware folder to compile and run the application. Make sure you are not currently running the Flipper UI, +qFlipper or lab.flipper.net. In some environments you do not need the "./" at the beginning of the command. + +```console +cd +cd official-firmware +./fbt launch_app APPSRC=applications/external/hid_app +``` + +The application should run on the Flipper Zero and you should see the Cookie Clicker application. When you click the OK button, the timer should start and stop. When you click the UP button, the timer duration should decrease. When you click the DOWN button, the timer duration should increase. + +Congratulations! You have successfully modified the TikTok application to the Cookie Clicker application. \ No newline at end of file diff --git a/hid/hid_app/final_files/assets/Cookie_def_11x9.png b/hid/hid_app/final_files/assets/Cookie_def_11x9.png new file mode 100644 index 0000000000000000000000000000000000000000..8566e2e6530096cee0f36ac2e5553775d18029da GIT binary patch literal 3664 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 literal 0 HcmV?d00001 diff --git a/hid/hid_app/final_files/hid.c b/hid/hid_app/final_files/hid.c new file mode 100644 index 0000000..3332855 --- /dev/null +++ b/hid/hid_app/final_files/hid.c @@ -0,0 +1,438 @@ +#include "hid.h" +#include "views.h" +#include +#include + +#define TAG "HidApp" + +enum HidDebugSubmenuIndex { + HidSubmenuIndexKeynote, + HidSubmenuIndexKeyboard, + HidSubmenuIndexMedia, + HidSubmenuIndexTikTok, + HidSubmenuIndexCC, + HidSubmenuIndexMouse, + HidSubmenuIndexMouseJiggler, +}; + +static void hid_submenu_callback(void* context, uint32_t index) { + furi_assert(context); + Hid* app = context; + if(index == HidSubmenuIndexKeynote) { + app->view_id = HidViewKeynote; + view_dispatcher_switch_to_view(app->view_dispatcher, HidViewKeynote); + } else if(index == HidSubmenuIndexKeyboard) { + app->view_id = HidViewKeyboard; + view_dispatcher_switch_to_view(app->view_dispatcher, HidViewKeyboard); + } else if(index == HidSubmenuIndexMedia) { + app->view_id = HidViewMedia; + view_dispatcher_switch_to_view(app->view_dispatcher, HidViewMedia); + } else if(index == HidSubmenuIndexMouse) { + app->view_id = HidViewMouse; + view_dispatcher_switch_to_view(app->view_dispatcher, HidViewMouse); + } else if(index == HidSubmenuIndexTikTok) { + app->view_id = BtHidViewTikTok; + view_dispatcher_switch_to_view(app->view_dispatcher, BtHidViewTikTok); + } else if(index == HidSubmenuIndexCC) { + app->view_id = BtHidViewCC; + view_dispatcher_switch_to_view(app->view_dispatcher, BtHidViewCC); + } else if(index == HidSubmenuIndexMouseJiggler) { + app->view_id = HidViewMouseJiggler; + view_dispatcher_switch_to_view(app->view_dispatcher, HidViewMouseJiggler); + } +} + +static void bt_hid_connection_status_changed_callback(BtStatus status, void* context) { + furi_assert(context); + Hid* hid = context; + bool connected = (status == BtStatusConnected); + if(hid->transport == HidTransportBle) { + if(connected) { + notification_internal_message(hid->notifications, &sequence_set_blue_255); + } else { + notification_internal_message(hid->notifications, &sequence_reset_blue); + } + } + hid_keynote_set_connected_status(hid->hid_keynote, connected); + hid_keyboard_set_connected_status(hid->hid_keyboard, connected); + hid_media_set_connected_status(hid->hid_media, connected); + hid_mouse_set_connected_status(hid->hid_mouse, connected); + hid_mouse_jiggler_set_connected_status(hid->hid_mouse_jiggler, connected); + hid_tiktok_set_connected_status(hid->hid_tiktok, connected); + hid_cc_set_connected_status(hid->hid_cc, connected); +} + +static void hid_dialog_callback(DialogExResult result, void* context) { + furi_assert(context); + Hid* app = context; + if(result == DialogExResultLeft) { + view_dispatcher_stop(app->view_dispatcher); + } else if(result == DialogExResultRight) { + view_dispatcher_switch_to_view(app->view_dispatcher, app->view_id); // Show last view + } else if(result == DialogExResultCenter) { + view_dispatcher_switch_to_view(app->view_dispatcher, HidViewSubmenu); + } +} + +static uint32_t hid_exit_confirm_view(void* context) { + UNUSED(context); + return HidViewExitConfirm; +} + +static uint32_t hid_exit(void* context) { + UNUSED(context); + return VIEW_NONE; +} + +Hid* hid_alloc(HidTransport transport) { + Hid* app = malloc(sizeof(Hid)); + app->transport = transport; + + // Gui + app->gui = furi_record_open(RECORD_GUI); + + // Bt + app->bt = furi_record_open(RECORD_BT); + + // Notifications + app->notifications = furi_record_open(RECORD_NOTIFICATION); + + // View dispatcher + app->view_dispatcher = view_dispatcher_alloc(); + view_dispatcher_enable_queue(app->view_dispatcher); + view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen); + // Device Type Submenu view + app->device_type_submenu = submenu_alloc(); + submenu_add_item( + app->device_type_submenu, "Keynote", HidSubmenuIndexKeynote, hid_submenu_callback, app); + submenu_add_item( + app->device_type_submenu, "Keyboard", HidSubmenuIndexKeyboard, hid_submenu_callback, app); + submenu_add_item( + app->device_type_submenu, "Media", HidSubmenuIndexMedia, hid_submenu_callback, app); + submenu_add_item( + app->device_type_submenu, "Mouse", HidSubmenuIndexMouse, hid_submenu_callback, app); + if(app->transport == HidTransportBle) { + submenu_add_item( + app->device_type_submenu, + "TikTok Controller", + HidSubmenuIndexTikTok, + hid_submenu_callback, + app); + } + if(app->transport == HidTransportBle) { + submenu_add_item( + app->device_type_submenu, + "CC Controller", + HidSubmenuIndexCC, + hid_submenu_callback, + app); + } + submenu_add_item( + app->device_type_submenu, + "Mouse Jiggler", + HidSubmenuIndexMouseJiggler, + hid_submenu_callback, + app); + view_set_previous_callback(submenu_get_view(app->device_type_submenu), hid_exit); + view_dispatcher_add_view( + app->view_dispatcher, HidViewSubmenu, submenu_get_view(app->device_type_submenu)); + app->view_id = HidViewSubmenu; + view_dispatcher_switch_to_view(app->view_dispatcher, app->view_id); + return app; +} + +Hid* hid_app_alloc_view(void* context) { + furi_assert(context); + Hid* app = context; + // Dialog view + app->dialog = dialog_ex_alloc(); + dialog_ex_set_result_callback(app->dialog, hid_dialog_callback); + dialog_ex_set_context(app->dialog, app); + dialog_ex_set_left_button_text(app->dialog, "Exit"); + dialog_ex_set_right_button_text(app->dialog, "Stay"); + dialog_ex_set_center_button_text(app->dialog, "Menu"); + dialog_ex_set_header(app->dialog, "Close Current App?", 16, 12, AlignLeft, AlignTop); + view_dispatcher_add_view( + app->view_dispatcher, HidViewExitConfirm, dialog_ex_get_view(app->dialog)); + + // Keynote view + app->hid_keynote = hid_keynote_alloc(app); + view_set_previous_callback(hid_keynote_get_view(app->hid_keynote), hid_exit_confirm_view); + view_dispatcher_add_view( + app->view_dispatcher, HidViewKeynote, hid_keynote_get_view(app->hid_keynote)); + + // Keyboard view + app->hid_keyboard = hid_keyboard_alloc(app); + view_set_previous_callback(hid_keyboard_get_view(app->hid_keyboard), hid_exit_confirm_view); + view_dispatcher_add_view( + app->view_dispatcher, HidViewKeyboard, hid_keyboard_get_view(app->hid_keyboard)); + + // Media view + app->hid_media = hid_media_alloc(app); + view_set_previous_callback(hid_media_get_view(app->hid_media), hid_exit_confirm_view); + view_dispatcher_add_view( + app->view_dispatcher, HidViewMedia, hid_media_get_view(app->hid_media)); + + // TikTok view + app->hid_tiktok = hid_tiktok_alloc(app); + view_set_previous_callback(hid_tiktok_get_view(app->hid_tiktok), hid_exit_confirm_view); + view_dispatcher_add_view( + app->view_dispatcher, BtHidViewTikTok, hid_tiktok_get_view(app->hid_tiktok)); + + // CC view + app->hid_cc = hid_cc_alloc(app); + view_set_previous_callback(hid_cc_get_view(app->hid_cc), hid_exit_confirm_view); + view_dispatcher_add_view(app->view_dispatcher, BtHidViewCC, hid_cc_get_view(app->hid_cc)); + + // Mouse view + app->hid_mouse = hid_mouse_alloc(app); + view_set_previous_callback(hid_mouse_get_view(app->hid_mouse), hid_exit_confirm_view); + view_dispatcher_add_view( + app->view_dispatcher, HidViewMouse, hid_mouse_get_view(app->hid_mouse)); + + // Mouse jiggler view + app->hid_mouse_jiggler = hid_mouse_jiggler_alloc(app); + view_set_previous_callback( + hid_mouse_jiggler_get_view(app->hid_mouse_jiggler), hid_exit_confirm_view); + view_dispatcher_add_view( + app->view_dispatcher, + HidViewMouseJiggler, + hid_mouse_jiggler_get_view(app->hid_mouse_jiggler)); + + return app; +} + +void hid_free(Hid* app) { + furi_assert(app); + + // Reset notification + if(app->transport == HidTransportBle) { + notification_internal_message(app->notifications, &sequence_reset_blue); + } + + // Free views + view_dispatcher_remove_view(app->view_dispatcher, HidViewSubmenu); + submenu_free(app->device_type_submenu); + view_dispatcher_remove_view(app->view_dispatcher, HidViewExitConfirm); + dialog_ex_free(app->dialog); + view_dispatcher_remove_view(app->view_dispatcher, HidViewKeynote); + hid_keynote_free(app->hid_keynote); + view_dispatcher_remove_view(app->view_dispatcher, HidViewKeyboard); + hid_keyboard_free(app->hid_keyboard); + view_dispatcher_remove_view(app->view_dispatcher, HidViewMedia); + hid_media_free(app->hid_media); + view_dispatcher_remove_view(app->view_dispatcher, HidViewMouse); + hid_mouse_free(app->hid_mouse); + view_dispatcher_remove_view(app->view_dispatcher, HidViewMouseJiggler); + hid_mouse_jiggler_free(app->hid_mouse_jiggler); + view_dispatcher_remove_view(app->view_dispatcher, BtHidViewTikTok); + hid_tiktok_free(app->hid_tiktok); + view_dispatcher_remove_view(app->view_dispatcher, BtHidViewCC); + hid_cc_free(app->hid_cc); + view_dispatcher_free(app->view_dispatcher); + + // Close records + furi_record_close(RECORD_GUI); + app->gui = NULL; + furi_record_close(RECORD_NOTIFICATION); + app->notifications = NULL; + furi_record_close(RECORD_BT); + app->bt = NULL; + + // Free rest + free(app); +} + +void hid_hal_keyboard_press(Hid* instance, uint16_t event) { + furi_assert(instance); + if(instance->transport == HidTransportBle) { + furi_hal_bt_hid_kb_press(event); + } else if(instance->transport == HidTransportUsb) { + furi_hal_hid_kb_press(event); + } else { + furi_crash(NULL); + } +} + +void hid_hal_keyboard_release(Hid* instance, uint16_t event) { + furi_assert(instance); + if(instance->transport == HidTransportBle) { + furi_hal_bt_hid_kb_release(event); + } else if(instance->transport == HidTransportUsb) { + furi_hal_hid_kb_release(event); + } else { + furi_crash(NULL); + } +} + +void hid_hal_keyboard_release_all(Hid* instance) { + furi_assert(instance); + if(instance->transport == HidTransportBle) { + furi_hal_bt_hid_kb_release_all(); + } else if(instance->transport == HidTransportUsb) { + furi_hal_hid_kb_release_all(); + } else { + furi_crash(NULL); + } +} + +void hid_hal_consumer_key_press(Hid* instance, uint16_t event) { + furi_assert(instance); + if(instance->transport == HidTransportBle) { + furi_hal_bt_hid_consumer_key_press(event); + } else if(instance->transport == HidTransportUsb) { + furi_hal_hid_consumer_key_press(event); + } else { + furi_crash(NULL); + } +} + +void hid_hal_consumer_key_release(Hid* instance, uint16_t event) { + furi_assert(instance); + if(instance->transport == HidTransportBle) { + furi_hal_bt_hid_consumer_key_release(event); + } else if(instance->transport == HidTransportUsb) { + furi_hal_hid_consumer_key_release(event); + } else { + furi_crash(NULL); + } +} + +void hid_hal_consumer_key_release_all(Hid* instance) { + furi_assert(instance); + if(instance->transport == HidTransportBle) { + furi_hal_bt_hid_consumer_key_release_all(); + } else if(instance->transport == HidTransportUsb) { + furi_hal_hid_kb_release_all(); + } else { + furi_crash(NULL); + } +} + +void hid_hal_mouse_move(Hid* instance, int8_t dx, int8_t dy) { + furi_assert(instance); + if(instance->transport == HidTransportBle) { + furi_hal_bt_hid_mouse_move(dx, dy); + } else if(instance->transport == HidTransportUsb) { + furi_hal_hid_mouse_move(dx, dy); + } else { + furi_crash(NULL); + } +} + +void hid_hal_mouse_scroll(Hid* instance, int8_t delta) { + furi_assert(instance); + if(instance->transport == HidTransportBle) { + furi_hal_bt_hid_mouse_scroll(delta); + } else if(instance->transport == HidTransportUsb) { + furi_hal_hid_mouse_scroll(delta); + } else { + furi_crash(NULL); + } +} + +void hid_hal_mouse_press(Hid* instance, uint16_t event) { + furi_assert(instance); + if(instance->transport == HidTransportBle) { + furi_hal_bt_hid_mouse_press(event); + } else if(instance->transport == HidTransportUsb) { + furi_hal_hid_mouse_press(event); + } else { + furi_crash(NULL); + } +} + +void hid_hal_mouse_release(Hid* instance, uint16_t event) { + furi_assert(instance); + if(instance->transport == HidTransportBle) { + furi_hal_bt_hid_mouse_release(event); + } else if(instance->transport == HidTransportUsb) { + furi_hal_hid_mouse_release(event); + } else { + furi_crash(NULL); + } +} + +void hid_hal_mouse_release_all(Hid* instance) { + furi_assert(instance); + if(instance->transport == HidTransportBle) { + furi_hal_bt_hid_mouse_release_all(); + } else if(instance->transport == HidTransportUsb) { + furi_hal_hid_mouse_release(HID_MOUSE_BTN_LEFT); + furi_hal_hid_mouse_release(HID_MOUSE_BTN_RIGHT); + } else { + furi_crash(NULL); + } +} + +int32_t hid_usb_app(void* p) { + UNUSED(p); + Hid* app = hid_alloc(HidTransportUsb); + app = hid_app_alloc_view(app); + FuriHalUsbInterface* usb_mode_prev = furi_hal_usb_get_config(); + furi_hal_usb_unlock(); + furi_check(furi_hal_usb_set_config(&usb_hid, NULL) == true); + + bt_hid_connection_status_changed_callback(BtStatusConnected, app); + + DOLPHIN_DEED(DolphinDeedPluginStart); + + view_dispatcher_run(app->view_dispatcher); + + furi_hal_usb_set_config(usb_mode_prev, NULL); + + hid_free(app); + + return 0; +} + +int32_t hid_ble_app(void* p) { + UNUSED(p); + Hid* app = hid_alloc(HidTransportBle); + app = hid_app_alloc_view(app); + + bt_disconnect(app->bt); + + // Wait 2nd core to update nvm storage + furi_delay_ms(200); + + // Migrate data from old sd-card folder + Storage* storage = furi_record_open(RECORD_STORAGE); + + storage_common_migrate( + storage, + EXT_PATH("apps/Tools/" HID_BT_KEYS_STORAGE_NAME), + APP_DATA_PATH(HID_BT_KEYS_STORAGE_NAME)); + + bt_keys_storage_set_storage_path(app->bt, APP_DATA_PATH(HID_BT_KEYS_STORAGE_NAME)); + + furi_record_close(RECORD_STORAGE); + + if(!bt_set_profile(app->bt, BtProfileHidKeyboard)) { + FURI_LOG_E(TAG, "Failed to switch to HID profile"); + } + + furi_hal_bt_start_advertising(); + bt_set_status_changed_callback(app->bt, bt_hid_connection_status_changed_callback, app); + + DOLPHIN_DEED(DolphinDeedPluginStart); + + view_dispatcher_run(app->view_dispatcher); + + bt_set_status_changed_callback(app->bt, NULL, NULL); + + bt_disconnect(app->bt); + + // Wait 2nd core to update nvm storage + furi_delay_ms(200); + + bt_keys_storage_set_default_path(app->bt); + + if(!bt_set_profile(app->bt, BtProfileSerial)) { + FURI_LOG_E(TAG, "Failed to switch to Serial profile"); + } + + hid_free(app); + + return 0; +} diff --git a/hid/hid_app/final_files/hid.h b/hid/hid_app/final_files/hid.h new file mode 100644 index 0000000..178de8f --- /dev/null +++ b/hid/hid_app/final_files/hid.h @@ -0,0 +1,67 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include "views/hid_keynote.h" +#include "views/hid_keyboard.h" +#include "views/hid_media.h" +#include "views/hid_mouse.h" +#include "views/hid_mouse_jiggler.h" +#include "views/hid_tiktok.h" +#include "views/hid_cc.h" + +#define HID_BT_KEYS_STORAGE_NAME ".bt_hid.keys" + +typedef enum { + HidTransportUsb, + HidTransportBle, +} HidTransport; + +typedef struct Hid Hid; + +struct Hid { + Bt* bt; + Gui* gui; + NotificationApp* notifications; + ViewDispatcher* view_dispatcher; + Submenu* device_type_submenu; + DialogEx* dialog; + HidKeynote* hid_keynote; + HidKeyboard* hid_keyboard; + HidMedia* hid_media; + HidMouse* hid_mouse; + HidMouseJiggler* hid_mouse_jiggler; + HidTikTok* hid_tiktok; + HidCC* hid_cc; + + HidTransport transport; + uint32_t view_id; +}; + +void hid_hal_keyboard_press(Hid* instance, uint16_t event); +void hid_hal_keyboard_release(Hid* instance, uint16_t event); +void hid_hal_keyboard_release_all(Hid* instance); + +void hid_hal_consumer_key_press(Hid* instance, uint16_t event); +void hid_hal_consumer_key_release(Hid* instance, uint16_t event); +void hid_hal_consumer_key_release_all(Hid* instance); + +void hid_hal_mouse_move(Hid* instance, int8_t dx, int8_t dy); +void hid_hal_mouse_scroll(Hid* instance, int8_t delta); +void hid_hal_mouse_press(Hid* instance, uint16_t event); +void hid_hal_mouse_release(Hid* instance, uint16_t event); +void hid_hal_mouse_release_all(Hid* instance); \ No newline at end of file diff --git a/hid/hid_app/final_files/views.h b/hid/hid_app/final_files/views.h new file mode 100644 index 0000000..5ac483c --- /dev/null +++ b/hid/hid_app/final_files/views.h @@ -0,0 +1,11 @@ +typedef enum { + HidViewSubmenu, + HidViewKeynote, + HidViewKeyboard, + HidViewMedia, + HidViewMouse, + HidViewMouseJiggler, + BtHidViewTikTok, + BtHidViewCC, + HidViewExitConfirm, +} HidView; \ No newline at end of file diff --git a/hid/hid_app/final_files/views/hid_cc.c b/hid/hid_app/final_files/views/hid_cc.c new file mode 100644 index 0000000..53688b4 --- /dev/null +++ b/hid/hid_app/final_files/views/hid_cc.c @@ -0,0 +1,287 @@ +#include "hid_cc.h" +#include "../hid.h" +#include + +#include "hid_icons.h" + +#define TAG "HidCC" + +//Add a "FuriTimer* timer" to the HidCC: +struct HidCC { + View* view; + Hid* hid; + FuriTimer* timer; +}; + +// Add a "float timer_duration" and "bool timer_click_enabled" to HidCCModel. +typedef struct { + bool left_pressed; + bool up_pressed; + bool right_pressed; + bool down_pressed; + bool ok_pressed; + bool connected; + bool is_cursor_set; + HidTransport transport; + float timer_duration; // Add this line (duration in ms) + bool timer_click_enabled; // Add this line (are we clicking) +} HidCCModel; + +static void hid_cc_draw_callback(Canvas* canvas, void* context) { + furi_assert(context); + HidCCModel* model = context; + + // Header + if(model->transport == HidTransportBle) { + if(model->connected) { + canvas_draw_icon(canvas, 0, 0, &I_Ble_connected_15x15); + } else { + canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15); + } + } + + canvas_set_font(canvas, FontPrimary); + // Rename title from "CC" to "Cookie\nClicker". Use \n so it wraps 2 lines. + elements_multiline_text_aligned( + canvas, 17, 3, AlignLeft, AlignTop, "Cookie\nClicker"); // Update this line + canvas_set_font(canvas, FontSecondary); + // Add code to display the click speed, right after displaying the title. + canvas_set_font(canvas, FontSecondary); + FuriString* buffer = furi_string_alloc(32); + furi_string_printf(buffer, "%0.1f\r\n", (double)model->timer_duration); + elements_multiline_text_aligned( + canvas, 17, 25, AlignLeft, AlignTop, furi_string_get_cstr(buffer)); + furi_string_free(buffer); + + // Keypad circles + canvas_draw_icon(canvas, 76, 8, &I_Circles_47x47); + + // Up + if(model->up_pressed) { + canvas_set_bitmap_mode(canvas, 1); + canvas_draw_icon(canvas, 93, 9, &I_Pressed_Button_13x13); + canvas_set_bitmap_mode(canvas, 0); + canvas_set_color(canvas, ColorWhite); + } + canvas_draw_icon(canvas, 96, 11, &I_Arr_up_7x9); + canvas_set_color(canvas, ColorBlack); + + // Down + if(model->down_pressed) { + canvas_set_bitmap_mode(canvas, 1); + canvas_draw_icon(canvas, 93, 41, &I_Pressed_Button_13x13); + canvas_set_bitmap_mode(canvas, 0); + canvas_set_color(canvas, ColorWhite); + } + canvas_draw_icon(canvas, 96, 44, &I_Arr_dwn_7x9); + canvas_set_color(canvas, ColorBlack); + + // Left + if(model->left_pressed) { + canvas_set_bitmap_mode(canvas, 1); + canvas_draw_icon(canvas, 77, 25, &I_Pressed_Button_13x13); + canvas_set_bitmap_mode(canvas, 0); + canvas_set_color(canvas, ColorWhite); + } + canvas_draw_icon(canvas, 81, 29, &I_Voldwn_6x6); + canvas_set_color(canvas, ColorBlack); + + // Right + if(model->right_pressed) { + canvas_set_bitmap_mode(canvas, 1); + canvas_draw_icon(canvas, 109, 25, &I_Pressed_Button_13x13); + canvas_set_bitmap_mode(canvas, 0); + canvas_set_color(canvas, ColorWhite); + } + canvas_draw_icon(canvas, 111, 29, &I_Volup_8x6); + canvas_set_color(canvas, ColorBlack); + + // Ok + if(model->ok_pressed) { + canvas_draw_icon(canvas, 91, 23, &I_Cookie_pressed_17x17); + } else { + canvas_draw_icon(canvas, 94, 27, &I_Cookie_def_11x9); + } + // Exit + canvas_draw_icon(canvas, 0, 54, &I_Pin_back_arrow_10x8); + canvas_set_font(canvas, FontSecondary); + elements_multiline_text_aligned(canvas, 13, 62, AlignLeft, AlignBottom, "Hold to exit"); +} + +// Add a new method to update the timer. +static void hid_cc_update_timer(HidCC* hid_cc, HidCCModel* model) { + furi_assert(hid_cc); + furi_assert(model); + + if(model->timer_click_enabled) { + furi_timer_start(hid_cc->timer, model->timer_duration); + } else { + furi_timer_stop(hid_cc->timer); + } +} + +// Add new method on tick callback that clicks button if click_enabled... +static void hid_cc_tick_callback(void* context) { + furi_assert(context); + HidCC* hid_cc = context; + + with_view_model( + hid_cc->view, + HidCCModel * model, + { + if(model->timer_click_enabled) { + hid_hal_mouse_press(hid_cc->hid, HID_MOUSE_BTN_LEFT); + furi_delay_ms(10); + hid_hal_mouse_release(hid_cc->hid, HID_MOUSE_BTN_LEFT); + } + }, + true); +} + +static void hid_cc_reset_cursor(HidCC* hid_cc) { + // Set cursor to the phone's left up corner + // Delays to guarantee one packet per connection interval + for(size_t i = 0; i < 8; i++) { + hid_hal_mouse_move(hid_cc->hid, -127, -127); + furi_delay_ms(50); + } + // Move cursor from the corner + hid_hal_mouse_move(hid_cc->hid, 20, 120); + furi_delay_ms(50); +} + +static void hid_cc_process_press(HidCC* hid_cc, HidCCModel* model, InputEvent* event) { + if(event->key == InputKeyUp) { + model->up_pressed = true; + } else if(event->key == InputKeyDown) { + model->down_pressed = true; + } else if(event->key == InputKeyLeft) { + model->left_pressed = true; + hid_hal_consumer_key_press(hid_cc->hid, HID_CONSUMER_VOLUME_DECREMENT); + } else if(event->key == InputKeyRight) { + model->right_pressed = true; + hid_hal_consumer_key_press(hid_cc->hid, HID_CONSUMER_VOLUME_INCREMENT); + } else if(event->key == InputKeyOk) { + model->ok_pressed = true; + } +} + +static void hid_cc_process_release(HidCC* hid_cc, HidCCModel* model, InputEvent* event) { + if(event->key == InputKeyUp) { + model->up_pressed = false; + } else if(event->key == InputKeyDown) { + model->down_pressed = false; + } else if(event->key == InputKeyLeft) { + model->left_pressed = false; + hid_hal_consumer_key_release(hid_cc->hid, HID_CONSUMER_VOLUME_DECREMENT); + } else if(event->key == InputKeyRight) { + model->right_pressed = false; + hid_hal_consumer_key_release(hid_cc->hid, HID_CONSUMER_VOLUME_INCREMENT); + } else if(event->key == InputKeyOk) { + model->ok_pressed = false; + } +} + +static bool hid_cc_input_callback(InputEvent* event, void* context) { + furi_assert(context); + HidCC* hid_cc = context; + bool consumed = false; + + with_view_model( + hid_cc->view, + HidCCModel * model, + { + if(event->type == InputTypePress) { + hid_cc_process_press(hid_cc, model, event); + if(model->connected && !model->is_cursor_set) { + hid_cc_reset_cursor(hid_cc); + model->is_cursor_set = true; + } + consumed = true; + } else if(event->type == InputTypeRelease) { + hid_cc_process_release(hid_cc, model, event); + consumed = true; + } else if(event->type == InputTypeShort) { + if(event->key == InputKeyOk) { + // Toggle timer_click_enabled + update_timer + model->timer_click_enabled = !model->timer_click_enabled; + hid_cc_update_timer(hid_cc, model); + consumed = true; + } else if(event->key == InputKeyUp) { + // Reduce duration to 95% of value + update_timer. + if(model->timer_duration > 0) { + model->timer_duration *= 0.95f; + hid_cc_update_timer(hid_cc, model); + } + consumed = true; + } else if(event->key == InputKeyDown) { + // Increase duration to 105% of value + update_timer + model->timer_duration *= 1.05f; + hid_cc_update_timer(hid_cc, model); + consumed = true; + } else if(event->key == InputKeyBack) { + hid_hal_consumer_key_release_all(hid_cc->hid); + consumed = true; + } + } else if(event->type == InputTypeLong) { + if(event->key == InputKeyBack) { + hid_hal_consumer_key_release_all(hid_cc->hid); + model->is_cursor_set = false; + consumed = false; + } + } + }, + true); + + return consumed; +} + +HidCC* hid_cc_alloc(Hid* bt_hid) { + HidCC* hid_cc = malloc(sizeof(HidCC)); + hid_cc->hid = bt_hid; + hid_cc->view = view_alloc(); + view_set_context(hid_cc->view, hid_cc); + view_allocate_model(hid_cc->view, ViewModelTypeLocking, sizeof(HidCCModel)); + view_set_draw_callback(hid_cc->view, hid_cc_draw_callback); + view_set_input_callback(hid_cc->view, hid_cc_input_callback); + + with_view_model( + hid_cc->view, + HidCCModel * model, + { + model->transport = bt_hid->transport; + // Set default values for timer + model->timer_duration = 22.0f; + model->timer_click_enabled = false; + }, + true); + // Allocate timer + hid_cc->timer = furi_timer_alloc(hid_cc_tick_callback, FuriTimerTypePeriodic, hid_cc); + + return hid_cc; +} + +void hid_cc_free(HidCC* hid_cc) { + furi_assert(hid_cc); + // Free timer object. + furi_timer_free(hid_cc->timer); + view_free(hid_cc->view); + free(hid_cc); +} + +View* hid_cc_get_view(HidCC* hid_cc) { + furi_assert(hid_cc); + return hid_cc->view; +} + +void hid_cc_set_connected_status(HidCC* hid_cc, bool connected) { + furi_assert(hid_cc); + with_view_model( + hid_cc->view, + HidCCModel * model, + { + model->connected = connected; + model->is_cursor_set = false; + }, + true); +} diff --git a/hid/hid_app/final_files/views/hid_cc.h b/hid/hid_app/final_files/views/hid_cc.h new file mode 100644 index 0000000..caa864c --- /dev/null +++ b/hid/hid_app/final_files/views/hid_cc.h @@ -0,0 +1,14 @@ +#pragma once + +#include + +typedef struct Hid Hid; +typedef struct HidCC HidCC; + +HidCC* hid_cc_alloc(Hid* bt_hid); + +void hid_cc_free(HidCC* hid_cc); + +View* hid_cc_get_view(HidCC* hid_cc); + +void hid_cc_set_connected_status(HidCC* hid_cc, bool connected);