Initial check-in for hid_cookie

This commit is contained in:
Derek Jamison 2023-04-15 14:34:24 -04:00
parent 3a1a0c19ab
commit e6d61aa22c
23 changed files with 598 additions and 0 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

@ -0,0 +1,11 @@
App(
appid="CookieBT",
name="Cookie Clicker",
apptype=FlipperAppType.EXTERNAL,
entry_point="hid_cookie_ble_app",
stack_size=1 * 1024,
fap_category="Tools",
fap_icon="hid_cookie_ble_10px.png",
fap_icon_assets="assets",
fap_icon_assets_symbol="hid",
)

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 657 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

243
hid/hid_cookie/hid.c Normal file
View File

@ -0,0 +1,243 @@
#include "hid.h"
#include "views.h"
#include <notification/notification_messages.h>
#include <dolphin/dolphin.h>
#define TAG "CookieClickerApp"
enum HidDebugSubmenuIndex {
HidSubmenuIndexInstructions,
HidSubmenuIndexClicker,
HidSubmenuIndexCredits,
};
static void hid_submenu_callback(void* context, uint32_t index) {
furi_assert(context);
Hid* app = context;
if(index == HidSubmenuIndexInstructions) {
app->view_id = BtHidViewInstructions;
view_dispatcher_switch_to_view(app->view_dispatcher, app->view_id);
} else if(index == HidSubmenuIndexClicker) {
app->view_id = BtHidViewClicker;
view_dispatcher_switch_to_view(app->view_dispatcher, app->view_id);
} else if(index == HidSubmenuIndexCredits) {
app->view_id = BtHidViewCredits;
view_dispatcher_switch_to_view(app->view_dispatcher, app->view_id);
}
}
static void bt_hid_connection_status_changed_callback(BtStatus status, void* context) {
furi_assert(context);
Hid* hid = context;
bool connected = (status == BtStatusConnected);
if(connected) {
notification_internal_message(hid->notifications, &sequence_set_blue_255);
} else {
notification_internal_message(hid->notifications, &sequence_reset_blue);
}
hid_cc_set_connected_status(hid->hid_cc, connected);
}
static uint32_t hid_submenu_view(void* context) {
UNUSED(context);
return HidViewSubmenu;
}
static uint32_t hid_exit(void* context) {
UNUSED(context);
return VIEW_NONE;
}
Hid* hid_alloc() {
Hid* app = malloc(sizeof(Hid));
// 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);
// Submenu view
app->submenu = submenu_alloc();
submenu_set_header(app->submenu, "Cookie clicker");
submenu_add_item(
app->submenu, "Instructions", HidSubmenuIndexInstructions, hid_submenu_callback, app);
submenu_add_item(
app->submenu, "BT Phone Clicker", HidSubmenuIndexClicker, hid_submenu_callback, app);
submenu_add_item(app->submenu, "Credits", HidSubmenuIndexCredits, hid_submenu_callback, app);
view_set_previous_callback(submenu_get_view(app->submenu), hid_exit);
view_dispatcher_add_view(app->view_dispatcher, HidViewSubmenu, submenu_get_view(app->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;
// Instructions view
app->widget_instructions = widget_alloc();
widget_add_text_scroll_element(
app->widget_instructions,
0,
0,
128,
64,
"While this app is running, go to\n"
"your phone's bluetooth\n"
"settings and discover devices.\n"
"You should see 'Control\n"
"<flipper>' in the list. Click pair\n"
"on your phone. On the Flipper\n"
"Zero you should see a pin #.\n"
"If the pin matches, click OK on\n"
"your phone and the Flipper.\n"
"You should now be connected!\n"
"Launch the Cookie clicker app\n"
"on your phone. Tap your\n"
"phone's screen to click on the\n"
"first cookie, then on the\n"
"Flipper Zero, choose\n"
"'Phone clicker'. Click the\n"
"UP/DOWN buttons to set the\n"
"speed. Click the OK button on\n"
"the Flipper to enable/\n"
"disable the clicker.\n"
"Enjoy!\n");
view_set_previous_callback(widget_get_view(app->widget_instructions), hid_submenu_view);
view_dispatcher_add_view(
app->view_dispatcher, BtHidViewInstructions, widget_get_view(app->widget_instructions));
// Clicker view
app->hid_cc = hid_cc_alloc(app);
view_set_previous_callback(hid_cc_get_view(app->hid_cc), hid_submenu_view);
view_dispatcher_add_view(app->view_dispatcher, BtHidViewClicker, hid_cc_get_view(app->hid_cc));
// Credits view
app->widget_credits = widget_alloc();
widget_add_text_scroll_element(
app->widget_credits,
0,
0,
128,
64,
"Original app was from\n"
"external/hid_app. \n\n"
"Modified by CodeAllNight.\n"
"https://youtube.com/\n"
"@MrDerekJamison\n\n"
"Thanks to all the\n"
"contributors of the original\n"
"app!\n\n"
"Flip the World!\n");
view_set_previous_callback(widget_get_view(app->widget_credits), hid_submenu_view);
view_dispatcher_add_view(
app->view_dispatcher, BtHidViewCredits, widget_get_view(app->widget_credits));
return app;
}
void hid_free(Hid* app) {
furi_assert(app);
// Reset notification
notification_internal_message(app->notifications, &sequence_reset_blue);
// Free views
view_dispatcher_remove_view(app->view_dispatcher, HidViewSubmenu);
submenu_free(app->submenu);
view_dispatcher_remove_view(app->view_dispatcher, BtHidViewClicker);
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_mouse_move(Hid* instance, int8_t dx, int8_t dy) {
furi_assert(instance);
furi_hal_bt_hid_mouse_move(dx, dy);
}
void hid_hal_mouse_press(Hid* instance, uint16_t event) {
furi_assert(instance);
furi_hal_bt_hid_mouse_press(event);
}
void hid_hal_mouse_release(Hid* instance, uint16_t event) {
furi_assert(instance);
furi_hal_bt_hid_mouse_release(event);
}
void hid_hal_mouse_release_all(Hid* instance) {
furi_assert(instance);
furi_hal_bt_hid_mouse_release_all();
}
int32_t hid_cookie_ble_app(void* p) {
UNUSED(p);
Hid* app = hid_alloc();
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;
}

41
hid/hid_cookie/hid.h Normal file
View File

@ -0,0 +1,41 @@
#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 <gui/modules/widget.h>
#include "views/hid_cc.h"
#define HID_BT_KEYS_STORAGE_NAME ".bt_hid.keys"
typedef struct Hid Hid;
struct Hid {
Bt* bt;
Gui* gui;
NotificationApp* notifications;
ViewDispatcher* view_dispatcher;
Submenu* submenu;
Widget* widget_instructions;
Widget* widget_credits;
HidCC* hid_cc;
uint32_t view_id;
};
void hid_hal_mouse_move(Hid* instance, int8_t dx, int8_t dy);
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);

Binary file not shown.

After

Width:  |  Height:  |  Size: 151 B

41
hid/hid_cookie/readme.md Normal file
View File

@ -0,0 +1,41 @@
# Bluetooth Cookie Clicker
The goal of this application is use the Flipper Zero's bluetooth connectivity to send mouse clicks to your phone at a given frequency. If you are playing a game, such as Cookie Clicker 2, this will result in a 'screen tap' being simulated. Only connect to phones you own (unless your friends ask you to connect your Flipper Zero to their phone, so they can unlock more achievements in their games!)
## Easy Setup
The easiest way is to use [FlipC.org](https://flipc.org/build) to build the project and install on your Flipper. If the application isn't already in the [Tools](https://flipc.org/category/tools) section; you can go to [Build](https://flipc.org/build) and build it yourself.
```
git: jamisonderek/flipper-zero-tutorials
settings:
root: /hid/hid_cookie
```
Then click on "Let's FAP!"
Once it is built, you will be able to choose to Download the FAP or install directly on your Flipper Zero!
## Local Setup (Windows)
Setup your development machine with the following. You can watch my video at [YouTube](https://www.youtube.com/watch?v=gqovwRkn2xw)
- VSCode
- Git
- Recursive Clone of the Flipper Zero firmware
- Clone of this tutorial project : https://github.com/jamisonderek/flipper-zero-tutorials
Once everything is cloned, copy the hid_cookie project into your applications_user folder...
- Open the firmware project in Visual Studio Code
- Right click on "applications_user"
- Choose "Open in Integrated Terminal"
- Type:
```
New-Item -Path hid_cookie -ItemType Junction -Value ..\..\flipper-zero-tutorials\hid\hid_cookie
```
Once everything is copied, build and run the project...
- Open the build task window (Ctrl+Shift+B)
- Choose "Build update bundle"
- Open the "applications_user\hid_cookie\hid.c" file
- Open the build task window (Ctrl+Shift+B)
- Choose "Launch app on Flipper"
## Using the app
Once the application is launched, it will show a menu that includes directions for using the application. Press the OK button to launch the instructions. Press the DOWN button to read more instructions. Press the BACK button to return to the menu.

7
hid/hid_cookie/views.h Normal file
View File

@ -0,0 +1,7 @@
typedef enum {
HidViewSubmenu,
BtHidViewInstructions,
BtHidViewClicker,
BtHidViewCredits,
HidViewExitConfirm,
} HidView;

View File

@ -0,0 +1,241 @@
#include "hid_cc.h"
#include "../hid.h"
#include <gui/elements.h>
#include "hid_icons.h"
#define TAG "HidCC"
struct HidCC {
View* view;
Hid* hid;
FuriTimer* timer;
};
typedef struct {
bool left_pressed;
bool up_pressed;
bool right_pressed;
bool down_pressed;
bool ok_pressed;
bool connected;
bool is_cursor_set;
float timer_duration;
bool timer_click_enabled;
} HidCCModel;
static void hid_cc_draw_callback(Canvas* canvas, void* context) {
furi_assert(context);
HidCCModel* model = context;
// Header
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);
elements_multiline_text_aligned(canvas, 17, 3, AlignLeft, AlignTop, "Cookie\nClicker");
canvas_set_font(canvas, FontSecondary);
FuriString* buffer = furi_string_alloc(32);
furi_string_printf(buffer, "%0.1f ms\r\n", (double)model->timer_duration);
if(model->timer_click_enabled) {
furi_string_cat(buffer, "Clicking");
}
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);
// 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, "Back to menu");
}
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);
}
}
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 == 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 == 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) {
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_mouse_release_all(hid_cc->hid);
model->is_cursor_set = false;
consumed = false;
}
} else if(event->type == InputTypeLong) {
if(event->key == InputKeyBack) {
hid_hal_mouse_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->timer_duration = 22.0f;
model->timer_click_enabled = false;
},
true);
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);
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);