Cookie Clicker over bluetooth!

This commit is contained in:
Derek Jamison 2023-03-30 16:03:11 -04:00
parent 10fee7301b
commit e9b37d5b50
8 changed files with 1283 additions and 0 deletions

466
hid/hid_app/README.md Normal file
View File

@ -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 <your working directory>
git clone --recursive https://github.com/flipperdevices/flipperzero-firmware.git official-firmware
```
Replace _&lt;your working directory&gt;_ 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 _&lt;your working directory&gt;_ with the directory where you cloned the firmware. In some environments you do not need the "./" at the beginning of the command.
```console
cd <your working directory>
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 <your working directory>
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 <your working directory>
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.

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

View File

@ -0,0 +1,438 @@
#include "hid.h"
#include "views.h"
#include <notification/notification_messages.h>
#include <dolphin/dolphin.h>
#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;
}

View File

@ -0,0 +1,67 @@
#pragma once
#include <furi.h>
#include <furi_hal_bt.h>
#include <furi_hal_bt_hid.h>
#include <furi_hal_usb.h>
#include <furi_hal_usb_hid.h>
#include <bt/bt_service/bt.h>
#include <gui/gui.h>
#include <gui/view.h>
#include <gui/view_dispatcher.h>
#include <notification/notification.h>
#include <storage/storage.h>
#include <gui/modules/submenu.h>
#include <gui/modules/dialog_ex.h>
#include <gui/modules/popup.h>
#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);

View File

@ -0,0 +1,11 @@
typedef enum {
HidViewSubmenu,
HidViewKeynote,
HidViewKeyboard,
HidViewMedia,
HidViewMouse,
HidViewMouseJiggler,
BtHidViewTikTok,
BtHidViewCC,
HidViewExitConfirm,
} HidView;

View File

@ -0,0 +1,287 @@
#include "hid_cc.h"
#include "../hid.h"
#include <gui/elements.h>
#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);
}

View File

@ -0,0 +1,14 @@
#pragma once
#include <gui/view.h>
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);