Merge branch 'dev' into leptoptilos

This commit is contained in:
MX 2024-01-05 22:41:57 +03:00
commit a640dfecf8
No known key found for this signature in database
GPG key ID: 7CCC66B7DBDD1C83
100 changed files with 4841 additions and 0 deletions

View file

@ -0,0 +1,24 @@
App(
appid="hid_usb",
name="USB Keyboard & Mouse",
apptype=FlipperAppType.EXTERNAL,
entry_point="hid_usb_app",
stack_size=1 * 1024,
fap_category="USB",
fap_icon="hid_usb_10px.png",
fap_icon_assets="assets",
fap_icon_assets_symbol="hid",
)
App(
appid="hid_ble",
name="Bluetooth Remote",
apptype=FlipperAppType.EXTERNAL,
entry_point="hid_ble_app",
stack_size=1 * 1024,
fap_category="Bluetooth",
fap_icon="hid_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: 340 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 362 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 362 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 362 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 362 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 172 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 173 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 180 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 177 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 179 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 178 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 177 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 178 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 177 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 176 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 176 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 179 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 156 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 404 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 470 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 356 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 162 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 163 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 161 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 165 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 164 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 163 B

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.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 356 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 341 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 329 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 339 B

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: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 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: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 959 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 320 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 323 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 162 B

View file

@ -0,0 +1,480 @@
#include "hid.h"
#include "views.h"
#include <notification/notification_messages.h>
#include <dolphin/dolphin.h>
#define TAG "HidApp"
enum HidDebugSubmenuIndex {
HidSubmenuIndexKeynote,
HidSubmenuIndexKeynoteVertical,
HidSubmenuIndexKeyboard,
HidSubmenuIndexNumpad,
HidSubmenuIndexMedia,
HidSubmenuIndexMovie,
HidSubmenuIndexTikShorts,
HidSubmenuIndexMouse,
HidSubmenuIndexMouseClicker,
HidSubmenuIndexMouseJiggler,
HidSubmenuIndexPushToTalk,
};
static void hid_submenu_callback(void* context, uint32_t index) {
furi_assert(context);
Hid* app = context;
if(index == HidSubmenuIndexKeynote) {
app->view_id = HidViewKeynote;
hid_keynote_set_orientation(app->hid_keynote, false);
view_dispatcher_switch_to_view(app->view_dispatcher, HidViewKeynote);
} else if(index == HidSubmenuIndexKeynoteVertical) {
app->view_id = HidViewKeynote;
hid_keynote_set_orientation(app->hid_keynote, true);
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 == HidSubmenuIndexNumpad) {
app->view_id = HidViewNumpad;
view_dispatcher_switch_to_view(app->view_dispatcher, HidViewNumpad);
} else if(index == HidSubmenuIndexMedia) {
app->view_id = HidViewMedia;
view_dispatcher_switch_to_view(app->view_dispatcher, HidViewMedia);
} else if(index == HidSubmenuIndexMovie) {
app->view_id = HidViewMovie;
view_dispatcher_switch_to_view(app->view_dispatcher, HidViewMovie);
} else if(index == HidSubmenuIndexMouse) {
app->view_id = HidViewMouse;
view_dispatcher_switch_to_view(app->view_dispatcher, HidViewMouse);
} else if(index == HidSubmenuIndexTikShorts) {
app->view_id = BtHidViewTikShorts;
view_dispatcher_switch_to_view(app->view_dispatcher, BtHidViewTikShorts);
} else if(index == HidSubmenuIndexMouseClicker) {
app->view_id = HidViewMouseClicker;
view_dispatcher_switch_to_view(app->view_dispatcher, HidViewMouseClicker);
} else if(index == HidSubmenuIndexMouseJiggler) {
app->view_id = HidViewMouseJiggler;
view_dispatcher_switch_to_view(app->view_dispatcher, HidViewMouseJiggler);
} else if(index == HidSubmenuIndexPushToTalk) {
app->view_id = HidViewPushToTalkMenu;
view_dispatcher_switch_to_view(app->view_dispatcher, HidViewPushToTalkMenu);
}
}
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_numpad_set_connected_status(hid->hid_numpad, connected);
hid_media_set_connected_status(hid->hid_media, connected);
hid_movie_set_connected_status(hid->hid_movie, connected);
hid_mouse_set_connected_status(hid->hid_mouse, connected);
hid_mouse_clicker_set_connected_status(hid->hid_mouse_clicker, connected);
hid_mouse_jiggler_set_connected_status(hid->hid_mouse_jiggler, connected);
hid_ptt_set_connected_status(hid->hid_ptt, connected);
hid_tikshorts_set_connected_status(hid->hid_tikshorts, connected);
}
static uint32_t hid_menu_view(void* context) {
UNUSED(context);
return HidViewSubmenu;
}
static uint32_t hid_exit(void* context) {
UNUSED(context);
return VIEW_NONE;
}
static uint32_t hid_ptt_menu_view(void* context) {
UNUSED(context);
return HidViewPushToTalkMenu;
}
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,
"Keynote Vertical",
HidSubmenuIndexKeynoteVertical,
hid_submenu_callback,
app);
submenu_add_item(
app->device_type_submenu, "Keyboard", HidSubmenuIndexKeyboard, hid_submenu_callback, app);
submenu_add_item(
app->device_type_submenu, "Numpad", HidSubmenuIndexNumpad, hid_submenu_callback, app);
submenu_add_item(
app->device_type_submenu, "Media", HidSubmenuIndexMedia, hid_submenu_callback, app);
submenu_add_item(
app->device_type_submenu, "Movie", HidSubmenuIndexMovie, 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 / YT Shorts",
HidSubmenuIndexTikShorts,
hid_submenu_callback,
app);
}
submenu_add_item(
app->device_type_submenu,
"Mouse Clicker",
HidSubmenuIndexMouseClicker,
hid_submenu_callback,
app);
submenu_add_item(
app->device_type_submenu,
"Mouse Jiggler",
HidSubmenuIndexMouseJiggler,
hid_submenu_callback,
app);
submenu_add_item(
app->device_type_submenu, "PushToTalk", HidSubmenuIndexPushToTalk, 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;
// Keynote view
app->hid_keynote = hid_keynote_alloc(app);
view_set_previous_callback(hid_keynote_get_view(app->hid_keynote), hid_menu_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_menu_view);
view_dispatcher_add_view(
app->view_dispatcher, HidViewKeyboard, hid_keyboard_get_view(app->hid_keyboard));
//Numpad keyboard view
app->hid_numpad = hid_numpad_alloc(app);
view_set_previous_callback(hid_numpad_get_view(app->hid_numpad), hid_menu_view);
view_dispatcher_add_view(
app->view_dispatcher, HidViewNumpad, hid_numpad_get_view(app->hid_numpad));
// Media view
app->hid_media = hid_media_alloc(app);
view_set_previous_callback(hid_media_get_view(app->hid_media), hid_menu_view);
view_dispatcher_add_view(
app->view_dispatcher, HidViewMedia, hid_media_get_view(app->hid_media));
// Movie view
app->hid_movie = hid_movie_alloc(app);
view_set_previous_callback(hid_movie_get_view(app->hid_movie), hid_menu_view);
view_dispatcher_add_view(
app->view_dispatcher, HidViewMovie, hid_movie_get_view(app->hid_movie));
// TikTok / YT Shorts view
app->hid_tikshorts = hid_tikshorts_alloc(app);
view_set_previous_callback(hid_tikshorts_get_view(app->hid_tikshorts), hid_menu_view);
view_dispatcher_add_view(
app->view_dispatcher, BtHidViewTikShorts, hid_tikshorts_get_view(app->hid_tikshorts));
// Mouse view
app->hid_mouse = hid_mouse_alloc(app);
view_set_previous_callback(hid_mouse_get_view(app->hid_mouse), hid_menu_view);
view_dispatcher_add_view(
app->view_dispatcher, HidViewMouse, hid_mouse_get_view(app->hid_mouse));
// Mouse clicker view
app->hid_mouse_clicker = hid_mouse_clicker_alloc(app);
view_set_previous_callback(
hid_mouse_clicker_get_view(app->hid_mouse_clicker), hid_menu_view);
view_dispatcher_add_view(
app->view_dispatcher,
HidViewMouseClicker,
hid_mouse_clicker_get_view(app->hid_mouse_clicker));
// 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_menu_view);
view_dispatcher_add_view(
app->view_dispatcher,
HidViewMouseJiggler,
hid_mouse_jiggler_get_view(app->hid_mouse_jiggler));
// PushToTalk view
app->hid_ptt_menu = hid_ptt_menu_alloc(app);
view_set_previous_callback(hid_ptt_menu_get_view(app->hid_ptt_menu), hid_menu_view);
view_dispatcher_add_view(
app->view_dispatcher, HidViewPushToTalkMenu, hid_ptt_menu_get_view(app->hid_ptt_menu));
app->hid_ptt = hid_ptt_alloc(app);
view_set_previous_callback(hid_ptt_get_view(app->hid_ptt), hid_ptt_menu_view);
view_dispatcher_add_view(
app->view_dispatcher, HidViewPushToTalk, hid_ptt_get_view(app->hid_ptt));
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, 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, HidViewNumpad);
hid_numpad_free(app->hid_numpad);
view_dispatcher_remove_view(app->view_dispatcher, HidViewMedia);
hid_media_free(app->hid_media);
view_dispatcher_remove_view(app->view_dispatcher, HidViewMovie);
hid_movie_free(app->hid_movie);
view_dispatcher_remove_view(app->view_dispatcher, HidViewMouse);
hid_mouse_free(app->hid_mouse);
view_dispatcher_remove_view(app->view_dispatcher, HidViewMouseClicker);
hid_mouse_clicker_free(app->hid_mouse_clicker);
view_dispatcher_remove_view(app->view_dispatcher, HidViewMouseJiggler);
hid_mouse_jiggler_free(app->hid_mouse_jiggler);
view_dispatcher_remove_view(app->view_dispatcher, HidViewPushToTalkMenu);
hid_ptt_menu_free(app->hid_ptt_menu);
view_dispatcher_remove_view(app->view_dispatcher, HidViewPushToTalk);
hid_ptt_free(app->hid_ptt);
view_dispatcher_remove_view(app->view_dispatcher, BtHidViewTikShorts);
hid_tikshorts_free(app->hid_tikshorts);
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();
}
}
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();
}
}
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();
}
}
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();
}
}
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();
}
}
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();
}
}
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();
}
}
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();
}
}
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();
}
}
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();
}
}
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();
}
}
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);
furi_check(bt_set_profile(app->bt, BtProfileHidKeyboard));
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);
furi_check(bt_set_profile(app->bt, BtProfileSerial));
hid_free(app);
return 0;
}

View file

@ -0,0 +1,75 @@
#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_numpad.h"
#include "views/hid_media.h"
#include "views/hid_movie.h"
#include "views/hid_mouse.h"
#include "views/hid_mouse_clicker.h"
#include "views/hid_mouse_jiggler.h"
#include "views/hid_tikshorts.h"
#include "views/hid_ptt.h"
#include "views/hid_ptt_menu.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;
HidNumpad* hid_numpad;
HidMedia* hid_media;
HidMovie* hid_movie;
HidMouse* hid_mouse;
HidMouseClicker* hid_mouse_clicker;
HidMouseJiggler* hid_mouse_jiggler;
HidTikShorts* hid_tikshorts;
HidPushToTalk* hid_ptt;
HidPushToTalkMenu* hid_ptt_menu;
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);

Binary file not shown.

After

Width:  |  Height:  |  Size: 151 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 174 B

View file

@ -0,0 +1,15 @@
typedef enum {
HidViewSubmenu,
HidViewKeynote,
HidViewKeyboard,
HidViewNumpad,
HidViewMedia,
HidViewMovie,
HidViewMouse,
HidViewMouseClicker,
HidViewMouseJiggler,
BtHidViewTikShorts,
HidViewPushToTalk,
HidViewPushToTalkMenu,
HidViewPushToTalkHelp,
} HidView;

View file

@ -0,0 +1,411 @@
#include "hid_keyboard.h"
#include <furi.h>
#include <gui/elements.h>
#include <gui/icon_i.h>
#include "../hid.h"
#include "hid_icons.h"
#define TAG "HidKeyboard"
struct HidKeyboard {
View* view;
Hid* hid;
};
typedef struct {
bool shift;
bool alt;
bool ctrl;
bool gui;
uint8_t x;
uint8_t y;
uint8_t last_key_code;
uint16_t modifier_code;
bool ok_pressed;
bool back_pressed;
bool connected;
char key_string[5];
HidTransport transport;
} HidKeyboardModel;
typedef struct {
uint8_t width;
char* key;
const Icon* icon;
char* shift_key;
uint8_t value;
} HidKeyboardKey;
typedef struct {
int8_t x;
int8_t y;
} HidKeyboardPoint;
// 4 BY 12
#define MARGIN_TOP 0
#define MARGIN_LEFT 4
#define KEY_WIDTH 9
#define KEY_HEIGHT 12
#define KEY_PADDING 1
#define ROW_COUNT 7
#define COLUMN_COUNT 12
// 0 width items are not drawn, but there value is used
const HidKeyboardKey hid_keyboard_keyset[ROW_COUNT][COLUMN_COUNT] = {
{
{.width = 1, .icon = &I_ButtonF1_5x8, .value = HID_KEYBOARD_F1},
{.width = 1, .icon = &I_ButtonF2_5x8, .value = HID_KEYBOARD_F2},
{.width = 1, .icon = &I_ButtonF3_5x8, .value = HID_KEYBOARD_F3},
{.width = 1, .icon = &I_ButtonF4_5x8, .value = HID_KEYBOARD_F4},
{.width = 1, .icon = &I_ButtonF5_5x8, .value = HID_KEYBOARD_F5},
{.width = 1, .icon = &I_ButtonF6_5x8, .value = HID_KEYBOARD_F6},
{.width = 1, .icon = &I_ButtonF7_5x8, .value = HID_KEYBOARD_F7},
{.width = 1, .icon = &I_ButtonF8_5x8, .value = HID_KEYBOARD_F8},
{.width = 1, .icon = &I_ButtonF9_5x8, .value = HID_KEYBOARD_F9},
{.width = 1, .icon = &I_ButtonF10_5x8, .value = HID_KEYBOARD_F10},
{.width = 1, .icon = &I_ButtonF11_5x8, .value = HID_KEYBOARD_F11},
{.width = 1, .icon = &I_ButtonF12_5x8, .value = HID_KEYBOARD_F12},
},
{
{.width = 1, .icon = NULL, .key = "1", .shift_key = "!", .value = HID_KEYBOARD_1},
{.width = 1, .icon = NULL, .key = "2", .shift_key = "@", .value = HID_KEYBOARD_2},
{.width = 1, .icon = NULL, .key = "3", .shift_key = "#", .value = HID_KEYBOARD_3},
{.width = 1, .icon = NULL, .key = "4", .shift_key = "$", .value = HID_KEYBOARD_4},
{.width = 1, .icon = NULL, .key = "5", .shift_key = "%", .value = HID_KEYBOARD_5},
{.width = 1, .icon = NULL, .key = "6", .shift_key = "^", .value = HID_KEYBOARD_6},
{.width = 1, .icon = NULL, .key = "7", .shift_key = "&", .value = HID_KEYBOARD_7},
{.width = 1, .icon = NULL, .key = "8", .shift_key = "*", .value = HID_KEYBOARD_8},
{.width = 1, .icon = NULL, .key = "9", .shift_key = "(", .value = HID_KEYBOARD_9},
{.width = 1, .icon = NULL, .key = "0", .shift_key = ")", .value = HID_KEYBOARD_0},
{.width = 2, .icon = &I_Pin_arrow_left_9x7, .value = HID_KEYBOARD_DELETE},
{.width = 0, .value = HID_KEYBOARD_DELETE},
},
{
{.width = 1, .icon = NULL, .key = "q", .shift_key = "Q", .value = HID_KEYBOARD_Q},
{.width = 1, .icon = NULL, .key = "w", .shift_key = "W", .value = HID_KEYBOARD_W},
{.width = 1, .icon = NULL, .key = "e", .shift_key = "E", .value = HID_KEYBOARD_E},
{.width = 1, .icon = NULL, .key = "r", .shift_key = "R", .value = HID_KEYBOARD_R},
{.width = 1, .icon = NULL, .key = "t", .shift_key = "T", .value = HID_KEYBOARD_T},
{.width = 1, .icon = NULL, .key = "y", .shift_key = "Y", .value = HID_KEYBOARD_Y},
{.width = 1, .icon = NULL, .key = "u", .shift_key = "U", .value = HID_KEYBOARD_U},
{.width = 1, .icon = NULL, .key = "i", .shift_key = "I", .value = HID_KEYBOARD_I},
{.width = 1, .icon = NULL, .key = "o", .shift_key = "O", .value = HID_KEYBOARD_O},
{.width = 1, .icon = NULL, .key = "p", .shift_key = "P", .value = HID_KEYBOARD_P},
{.width = 1, .icon = NULL, .key = "[", .shift_key = "{", .value = HID_KEYBOARD_OPEN_BRACKET},
{.width = 1,
.icon = NULL,
.key = "]",
.shift_key = "}",
.value = HID_KEYBOARD_CLOSE_BRACKET},
},
{
{.width = 1, .icon = NULL, .key = "a", .shift_key = "A", .value = HID_KEYBOARD_A},
{.width = 1, .icon = NULL, .key = "s", .shift_key = "S", .value = HID_KEYBOARD_S},
{.width = 1, .icon = NULL, .key = "d", .shift_key = "D", .value = HID_KEYBOARD_D},
{.width = 1, .icon = NULL, .key = "f", .shift_key = "F", .value = HID_KEYBOARD_F},
{.width = 1, .icon = NULL, .key = "g", .shift_key = "G", .value = HID_KEYBOARD_G},
{.width = 1, .icon = NULL, .key = "h", .shift_key = "H", .value = HID_KEYBOARD_H},
{.width = 1, .icon = NULL, .key = "j", .shift_key = "J", .value = HID_KEYBOARD_J},
{.width = 1, .icon = NULL, .key = "k", .shift_key = "K", .value = HID_KEYBOARD_K},
{.width = 1, .icon = NULL, .key = "l", .shift_key = "L", .value = HID_KEYBOARD_L},
{.width = 1, .icon = NULL, .key = ";", .shift_key = ":", .value = HID_KEYBOARD_SEMICOLON},
{.width = 2, .icon = &I_Pin_arrow_right_9x7, .value = HID_KEYBOARD_RETURN},
{.width = 0, .value = HID_KEYBOARD_RETURN},
},
{
{.width = 1, .icon = NULL, .key = "z", .shift_key = "Z", .value = HID_KEYBOARD_Z},
{.width = 1, .icon = NULL, .key = "x", .shift_key = "X", .value = HID_KEYBOARD_X},
{.width = 1, .icon = NULL, .key = "c", .shift_key = "C", .value = HID_KEYBOARD_C},
{.width = 1, .icon = NULL, .key = "v", .shift_key = "V", .value = HID_KEYBOARD_V},
{.width = 1, .icon = NULL, .key = "b", .shift_key = "B", .value = HID_KEYBOARD_B},
{.width = 1, .icon = NULL, .key = "n", .shift_key = "N", .value = HID_KEYBOARD_N},
{.width = 1, .icon = NULL, .key = "m", .shift_key = "M", .value = HID_KEYBOARD_M},
{.width = 1, .icon = NULL, .key = "/", .shift_key = "?", .value = HID_KEYBOARD_SLASH},
{.width = 1, .icon = NULL, .key = "\\", .shift_key = "|", .value = HID_KEYBOARD_BACKSLASH},
{.width = 1, .icon = NULL, .key = "`", .shift_key = "~", .value = HID_KEYBOARD_GRAVE_ACCENT},
{.width = 1, .icon = &I_ButtonUp_7x4, .value = HID_KEYBOARD_UP_ARROW},
{.width = 1, .icon = NULL, .key = "-", .shift_key = "_", .value = HID_KEYBOARD_MINUS},
},
{
{.width = 1, .icon = &I_Pin_arrow_up_7x9, .value = HID_KEYBOARD_L_SHIFT},
{.width = 1, .icon = NULL, .key = ",", .shift_key = "<", .value = HID_KEYBOARD_COMMA},
{.width = 1, .icon = NULL, .key = ".", .shift_key = ">", .value = HID_KEYBOARD_DOT},
{.width = 4, .icon = NULL, .key = " ", .value = HID_KEYBOARD_SPACEBAR},
{.width = 0, .value = HID_KEYBOARD_SPACEBAR},
{.width = 0, .value = HID_KEYBOARD_SPACEBAR},
{.width = 0, .value = HID_KEYBOARD_SPACEBAR},
{.width = 1, .icon = NULL, .key = "'", .shift_key = "\"", .value = HID_KEYBOARD_APOSTROPHE},
{.width = 1, .icon = NULL, .key = "=", .shift_key = "+", .value = HID_KEYBOARD_EQUAL_SIGN},
{.width = 1, .icon = &I_ButtonLeft_4x7, .value = HID_KEYBOARD_LEFT_ARROW},
{.width = 1, .icon = &I_ButtonDown_7x4, .value = HID_KEYBOARD_DOWN_ARROW},
{.width = 1, .icon = &I_ButtonRight_4x7, .value = HID_KEYBOARD_RIGHT_ARROW},
},
{
{.width = 2, .icon = &I_KB_key_Ctl_17x10, .value = HID_KEYBOARD_L_CTRL},
{.width = 0, .icon = NULL, .value = HID_KEYBOARD_L_CTRL},
{.width = 2, .icon = &I_KB_key_Alt_17x10, .value = HID_KEYBOARD_L_ALT},
{.width = 0, .icon = NULL, .value = HID_KEYBOARD_L_ALT},
{.width = 2, .icon = &I_KB_key_Cmd_17x10, .value = HID_KEYBOARD_L_GUI},
{.width = 0, .icon = NULL, .value = HID_KEYBOARD_L_GUI},
{.width = 2, .icon = &I_KB_key_Tab_17x10, .value = HID_KEYBOARD_TAB},
{.width = 0, .icon = NULL, .value = HID_KEYBOARD_TAB},
{.width = 2, .icon = &I_KB_key_Esc_17x10, .value = HID_KEYBOARD_ESCAPE},
{.width = 0, .icon = NULL, .value = HID_KEYBOARD_ESCAPE},
{.width = 2, .icon = &I_KB_key_Del_17x10, .value = HID_KEYBOARD_DELETE_FORWARD},
{.width = 0, .icon = NULL, .value = HID_KEYBOARD_DELETE_FORWARD},
},
};
static void hid_keyboard_to_upper(char* str) {
while(*str) {
*str = toupper((unsigned char)*str);
str++;
}
}
static void hid_keyboard_draw_key(
Canvas* canvas,
HidKeyboardModel* model,
uint8_t x,
uint8_t y,
HidKeyboardKey key,
bool selected) {
if(!key.width) return;
canvas_set_color(canvas, ColorBlack);
uint8_t keyWidth = KEY_WIDTH * key.width + KEY_PADDING * (key.width - 1);
if(selected) {
// Draw a filled box
elements_slightly_rounded_box(
canvas,
MARGIN_LEFT + x * (KEY_WIDTH + KEY_PADDING),
MARGIN_TOP + y * (KEY_HEIGHT + KEY_PADDING),
keyWidth,
KEY_HEIGHT);
canvas_set_color(canvas, ColorWhite);
} else {
// Draw a framed box
elements_slightly_rounded_frame(
canvas,
MARGIN_LEFT + x * (KEY_WIDTH + KEY_PADDING),
MARGIN_TOP + y * (KEY_HEIGHT + KEY_PADDING),
keyWidth,
KEY_HEIGHT);
}
if(key.icon != NULL) {
// Draw the icon centered on the button
canvas_draw_icon(
canvas,
MARGIN_LEFT + x * (KEY_WIDTH + KEY_PADDING) + keyWidth / 2 - key.icon->width / 2,
MARGIN_TOP + y * (KEY_HEIGHT + KEY_PADDING) + KEY_HEIGHT / 2 - key.icon->height / 2,
key.icon);
} else {
// If shift is toggled use the shift key when available
strcpy(model->key_string, (model->shift && key.shift_key != 0) ? key.shift_key : key.key);
// Upper case if ctrl or alt was toggled true
if((model->ctrl && key.value == HID_KEYBOARD_L_CTRL) ||
(model->alt && key.value == HID_KEYBOARD_L_ALT) ||
(model->gui && key.value == HID_KEYBOARD_L_GUI)) {
hid_keyboard_to_upper(model->key_string);
}
canvas_draw_str_aligned(
canvas,
MARGIN_LEFT + x * (KEY_WIDTH + KEY_PADDING) + keyWidth / 2 + 1,
MARGIN_TOP + y * (KEY_HEIGHT + KEY_PADDING) + KEY_HEIGHT / 2,
AlignCenter,
AlignCenter,
model->key_string);
}
}
static void hid_keyboard_draw_callback(Canvas* canvas, void* context) {
furi_assert(context);
HidKeyboardModel* model = context;
// Header
if((!model->connected) && (model->transport == HidTransportBle)) {
canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15);
canvas_set_font(canvas, FontPrimary);
elements_multiline_text_aligned(canvas, 17, 3, AlignLeft, AlignTop, "Keyboard");
canvas_draw_icon(canvas, 68, 3, &I_Pin_back_arrow_10x8);
canvas_set_font(canvas, FontSecondary);
elements_multiline_text_aligned(canvas, 127, 4, AlignRight, AlignTop, "Hold to exit");
elements_multiline_text_aligned(
canvas, 4, 60, AlignLeft, AlignBottom, "Waiting for Connection...");
return; // Dont render the keyboard if we are not yet connected
}
canvas_set_font(canvas, FontKeyboard);
// Start shifting the all keys up if on the next row (Scrolling)
uint8_t initY = model->y == 0 ? 0 : 1;
if(model->y > 5) {
initY = model->y - 4;
}
for(uint8_t y = initY; y < ROW_COUNT; y++) {
const HidKeyboardKey* keyboardKeyRow = hid_keyboard_keyset[y];
uint8_t x = 0;
for(uint8_t i = 0; i < COLUMN_COUNT; i++) {
HidKeyboardKey key = keyboardKeyRow[i];
// Select when the button is hovered
// Select if the button is hovered within its width
// Select if back is clicked and its the backspace key
// Deselect when the button clicked or not hovered
bool keySelected = (x <= model->x && model->x < (x + key.width)) && y == model->y;
bool backSelected = model->back_pressed && key.value == HID_KEYBOARD_DELETE;
hid_keyboard_draw_key(
canvas,
model,
x,
y - initY,
key,
(!model->ok_pressed && keySelected) || backSelected);
x += key.width;
}
}
}
static uint8_t hid_keyboard_get_selected_key(HidKeyboardModel* model) {
HidKeyboardKey key = hid_keyboard_keyset[model->y][model->x];
return key.value;
}
static void hid_keyboard_get_select_key(HidKeyboardModel* model, HidKeyboardPoint delta) {
// Keep going until a valid spot is found, this allows for nulls and zero width keys in the map
do {
const int delta_sum = model->y + delta.y;
model->y = delta_sum < 0 ? ROW_COUNT - 1 : delta_sum % ROW_COUNT;
} while(delta.y != 0 && hid_keyboard_keyset[model->y][model->x].value == 0);
do {
const int delta_sum = model->x + delta.x;
model->x = delta_sum < 0 ? COLUMN_COUNT - 1 : delta_sum % COLUMN_COUNT;
} while(delta.x != 0 && hid_keyboard_keyset[model->y][model->x].width ==
0); // Skip zero width keys, pretend they are one key
}
static void hid_keyboard_process(HidKeyboard* hid_keyboard, InputEvent* event) {
with_view_model(
hid_keyboard->view,
HidKeyboardModel * model,
{
if(event->key == InputKeyOk) {
if(event->type == InputTypePress) {
model->ok_pressed = true;
} else if(event->type == InputTypeLong || event->type == InputTypeShort) {
model->last_key_code = hid_keyboard_get_selected_key(model);
// Toggle the modifier key when clicked, and click the key
if(model->last_key_code == HID_KEYBOARD_L_SHIFT) {
model->shift = !model->shift;
if(model->shift)
model->modifier_code |= KEY_MOD_LEFT_SHIFT;
else
model->modifier_code &= ~KEY_MOD_LEFT_SHIFT;
} else if(model->last_key_code == HID_KEYBOARD_L_ALT) {
model->alt = !model->alt;
if(model->alt)
model->modifier_code |= KEY_MOD_LEFT_ALT;
else
model->modifier_code &= ~KEY_MOD_LEFT_ALT;
} else if(model->last_key_code == HID_KEYBOARD_L_CTRL) {
model->ctrl = !model->ctrl;
if(model->ctrl)
model->modifier_code |= KEY_MOD_LEFT_CTRL;
else
model->modifier_code &= ~KEY_MOD_LEFT_CTRL;
} else if(model->last_key_code == HID_KEYBOARD_L_GUI) {
model->gui = !model->gui;
if(model->gui)
model->modifier_code |= KEY_MOD_LEFT_GUI;
else
model->modifier_code &= ~KEY_MOD_LEFT_GUI;
}
hid_hal_keyboard_press(
hid_keyboard->hid, model->modifier_code | model->last_key_code);
} else if(event->type == InputTypeRelease) {
// Release happens after short and long presses
hid_hal_keyboard_release(
hid_keyboard->hid, model->modifier_code | model->last_key_code);
model->ok_pressed = false;
}
} else if(event->key == InputKeyBack) {
// If back is pressed for a short time, backspace
if(event->type == InputTypePress) {
model->back_pressed = true;
} else if(event->type == InputTypeShort) {
hid_hal_keyboard_press(hid_keyboard->hid, HID_KEYBOARD_DELETE);
hid_hal_keyboard_release(hid_keyboard->hid, HID_KEYBOARD_DELETE);
} else if(event->type == InputTypeRelease) {
model->back_pressed = false;
}
} else if(event->type == InputTypePress || event->type == InputTypeRepeat) {
// Cycle the selected keys
if(event->key == InputKeyUp) {
hid_keyboard_get_select_key(model, (HidKeyboardPoint){.x = 0, .y = -1});
} else if(event->key == InputKeyDown) {
hid_keyboard_get_select_key(model, (HidKeyboardPoint){.x = 0, .y = 1});
} else if(event->key == InputKeyLeft) {
hid_keyboard_get_select_key(model, (HidKeyboardPoint){.x = -1, .y = 0});
} else if(event->key == InputKeyRight) {
hid_keyboard_get_select_key(model, (HidKeyboardPoint){.x = 1, .y = 0});
}
}
},
true);
}
static bool hid_keyboard_input_callback(InputEvent* event, void* context) {
furi_assert(context);
HidKeyboard* hid_keyboard = context;
bool consumed = false;
if(event->type == InputTypeLong && event->key == InputKeyBack) {
hid_hal_keyboard_release_all(hid_keyboard->hid);
} else {
hid_keyboard_process(hid_keyboard, event);
consumed = true;
}
return consumed;
}
HidKeyboard* hid_keyboard_alloc(Hid* bt_hid) {
HidKeyboard* hid_keyboard = malloc(sizeof(HidKeyboard));
hid_keyboard->view = view_alloc();
hid_keyboard->hid = bt_hid;
view_set_context(hid_keyboard->view, hid_keyboard);
view_allocate_model(hid_keyboard->view, ViewModelTypeLocking, sizeof(HidKeyboardModel));
view_set_draw_callback(hid_keyboard->view, hid_keyboard_draw_callback);
view_set_input_callback(hid_keyboard->view, hid_keyboard_input_callback);
with_view_model(
hid_keyboard->view,
HidKeyboardModel * model,
{
model->transport = bt_hid->transport;
model->y = 1;
},
true);
return hid_keyboard;
}
void hid_keyboard_free(HidKeyboard* hid_keyboard) {
furi_assert(hid_keyboard);
view_free(hid_keyboard->view);
free(hid_keyboard);
}
View* hid_keyboard_get_view(HidKeyboard* hid_keyboard) {
furi_assert(hid_keyboard);
return hid_keyboard->view;
}
void hid_keyboard_set_connected_status(HidKeyboard* hid_keyboard, bool connected) {
furi_assert(hid_keyboard);
with_view_model(
hid_keyboard->view, HidKeyboardModel * model, { model->connected = connected; }, true);
}

View file

@ -0,0 +1,14 @@
#pragma once
#include <gui/view.h>
typedef struct Hid Hid;
typedef struct HidKeyboard HidKeyboard;
HidKeyboard* hid_keyboard_alloc(Hid* bt_hid);
void hid_keyboard_free(HidKeyboard* hid_keyboard);
View* hid_keyboard_get_view(HidKeyboard* hid_keyboard);
void hid_keyboard_set_connected_status(HidKeyboard* hid_keyboard, bool connected);

View file

@ -0,0 +1,312 @@
#include "hid_keynote.h"
#include <gui/elements.h>
#include "../hid.h"
#include "hid_icons.h"
#define TAG "HidKeynote"
struct HidKeynote {
View* view;
Hid* hid;
};
typedef struct {
bool left_pressed;
bool up_pressed;
bool right_pressed;
bool down_pressed;
bool ok_pressed;
bool back_pressed;
bool connected;
HidTransport transport;
} HidKeynoteModel;
static void hid_keynote_draw_arrow(Canvas* canvas, uint8_t x, uint8_t y, CanvasDirection dir) {
canvas_draw_triangle(canvas, x, y, 5, 3, dir);
if(dir == CanvasDirectionBottomToTop) {
canvas_draw_line(canvas, x, y + 6, x, y - 1);
} else if(dir == CanvasDirectionTopToBottom) {
canvas_draw_line(canvas, x, y - 6, x, y + 1);
} else if(dir == CanvasDirectionRightToLeft) {
canvas_draw_line(canvas, x + 6, y, x - 1, y);
} else if(dir == CanvasDirectionLeftToRight) {
canvas_draw_line(canvas, x - 6, y, x + 1, y);
}
}
static void hid_keynote_draw_callback(Canvas* canvas, void* context) {
furi_assert(context);
HidKeynoteModel* 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);
elements_multiline_text_aligned(canvas, 17, 3, AlignLeft, AlignTop, "Keynote");
canvas_draw_icon(canvas, 68, 2, &I_Pin_back_arrow_10x8);
canvas_set_font(canvas, FontSecondary);
elements_multiline_text_aligned(canvas, 127, 3, AlignRight, AlignTop, "Hold to exit");
// Up
canvas_draw_icon(canvas, 21, 24, &I_Button_18x18);
if(model->up_pressed) {
elements_slightly_rounded_box(canvas, 24, 26, 13, 13);
canvas_set_color(canvas, ColorWhite);
}
hid_keynote_draw_arrow(canvas, 30, 30, CanvasDirectionBottomToTop);
canvas_set_color(canvas, ColorBlack);
// Down
canvas_draw_icon(canvas, 21, 45, &I_Button_18x18);
if(model->down_pressed) {
elements_slightly_rounded_box(canvas, 24, 47, 13, 13);
canvas_set_color(canvas, ColorWhite);
}
hid_keynote_draw_arrow(canvas, 30, 55, CanvasDirectionTopToBottom);
canvas_set_color(canvas, ColorBlack);
// Left
canvas_draw_icon(canvas, 0, 45, &I_Button_18x18);
if(model->left_pressed) {
elements_slightly_rounded_box(canvas, 3, 47, 13, 13);
canvas_set_color(canvas, ColorWhite);
}
hid_keynote_draw_arrow(canvas, 7, 53, CanvasDirectionRightToLeft);
canvas_set_color(canvas, ColorBlack);
// Right
canvas_draw_icon(canvas, 42, 45, &I_Button_18x18);
if(model->right_pressed) {
elements_slightly_rounded_box(canvas, 45, 47, 13, 13);
canvas_set_color(canvas, ColorWhite);
}
hid_keynote_draw_arrow(canvas, 53, 53, CanvasDirectionLeftToRight);
canvas_set_color(canvas, ColorBlack);
// Ok
canvas_draw_icon(canvas, 63, 25, &I_Space_65x18);
if(model->ok_pressed) {
elements_slightly_rounded_box(canvas, 66, 27, 60, 13);
canvas_set_color(canvas, ColorWhite);
}
canvas_draw_icon(canvas, 74, 29, &I_Ok_btn_9x9);
elements_multiline_text_aligned(canvas, 91, 36, AlignLeft, AlignBottom, "Space");
canvas_set_color(canvas, ColorBlack);
// Back
canvas_draw_icon(canvas, 63, 45, &I_Space_65x18);
if(model->back_pressed) {
elements_slightly_rounded_box(canvas, 66, 47, 60, 13);
canvas_set_color(canvas, ColorWhite);
}
canvas_draw_icon(canvas, 74, 49, &I_Pin_back_arrow_10x8);
elements_multiline_text_aligned(canvas, 91, 57, AlignLeft, AlignBottom, "Back");
}
static void hid_keynote_draw_vertical_callback(Canvas* canvas, void* context) {
furi_assert(context);
HidKeynoteModel* model = context;
// Header
canvas_set_font(canvas, FontPrimary);
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);
}
elements_multiline_text_aligned(canvas, 20, 3, AlignLeft, AlignTop, "Keynote");
} else {
elements_multiline_text_aligned(canvas, 12, 3, AlignLeft, AlignTop, "Keynote");
}
canvas_draw_icon(canvas, 2, 18, &I_Pin_back_arrow_10x8);
canvas_set_font(canvas, FontSecondary);
elements_multiline_text_aligned(canvas, 15, 19, AlignLeft, AlignTop, "Hold to exit");
const uint8_t x_2 = 23;
const uint8_t x_1 = 2;
const uint8_t x_3 = 44;
const uint8_t y_1 = 44;
const uint8_t y_2 = 65;
// Up
canvas_draw_icon(canvas, x_2, y_1, &I_Button_18x18);
if(model->up_pressed) {
elements_slightly_rounded_box(canvas, x_2 + 3, y_1 + 2, 13, 13);
canvas_set_color(canvas, ColorWhite);
}
hid_keynote_draw_arrow(canvas, x_2 + 9, y_1 + 6, CanvasDirectionBottomToTop);
canvas_set_color(canvas, ColorBlack);
// Down
canvas_draw_icon(canvas, x_2, y_2, &I_Button_18x18);
if(model->down_pressed) {
elements_slightly_rounded_box(canvas, x_2 + 3, y_2 + 2, 13, 13);
canvas_set_color(canvas, ColorWhite);
}
hid_keynote_draw_arrow(canvas, x_2 + 9, y_2 + 10, CanvasDirectionTopToBottom);
canvas_set_color(canvas, ColorBlack);
// Left
canvas_draw_icon(canvas, x_1, y_2, &I_Button_18x18);
if(model->left_pressed) {
elements_slightly_rounded_box(canvas, x_1 + 3, y_2 + 2, 13, 13);
canvas_set_color(canvas, ColorWhite);
}
hid_keynote_draw_arrow(canvas, x_1 + 7, y_2 + 8, CanvasDirectionRightToLeft);
canvas_set_color(canvas, ColorBlack);
// Right
canvas_draw_icon(canvas, x_3, y_2, &I_Button_18x18);
if(model->right_pressed) {
elements_slightly_rounded_box(canvas, x_3 + 3, y_2 + 2, 13, 13);
canvas_set_color(canvas, ColorWhite);
}
hid_keynote_draw_arrow(canvas, x_3 + 11, y_2 + 8, CanvasDirectionLeftToRight);
canvas_set_color(canvas, ColorBlack);
// Ok
canvas_draw_icon(canvas, 2, 86, &I_Space_60x18);
if(model->ok_pressed) {
elements_slightly_rounded_box(canvas, 5, 88, 55, 13);
canvas_set_color(canvas, ColorWhite);
}
canvas_draw_icon(canvas, 11, 90, &I_Ok_btn_9x9);
elements_multiline_text_aligned(canvas, 26, 98, AlignLeft, AlignBottom, "Space");
canvas_set_color(canvas, ColorBlack);
// Back
canvas_draw_icon(canvas, 2, 107, &I_Space_60x18);
if(model->back_pressed) {
elements_slightly_rounded_box(canvas, 5, 109, 55, 13);
canvas_set_color(canvas, ColorWhite);
}
canvas_draw_icon(canvas, 11, 111, &I_Pin_back_arrow_10x8);
elements_multiline_text_aligned(canvas, 26, 119, AlignLeft, AlignBottom, "Back");
}
static void hid_keynote_process(HidKeynote* hid_keynote, InputEvent* event) {
with_view_model(
hid_keynote->view,
HidKeynoteModel * model,
{
if(event->type == InputTypePress) {
if(event->key == InputKeyUp) {
model->up_pressed = true;
hid_hal_keyboard_press(hid_keynote->hid, HID_KEYBOARD_UP_ARROW);
} else if(event->key == InputKeyDown) {
model->down_pressed = true;
hid_hal_keyboard_press(hid_keynote->hid, HID_KEYBOARD_DOWN_ARROW);
} else if(event->key == InputKeyLeft) {
model->left_pressed = true;
hid_hal_keyboard_press(hid_keynote->hid, HID_KEYBOARD_LEFT_ARROW);
} else if(event->key == InputKeyRight) {
model->right_pressed = true;
hid_hal_keyboard_press(hid_keynote->hid, HID_KEYBOARD_RIGHT_ARROW);
} else if(event->key == InputKeyOk) {
model->ok_pressed = true;
hid_hal_keyboard_press(hid_keynote->hid, HID_KEYBOARD_SPACEBAR);
} else if(event->key == InputKeyBack) {
model->back_pressed = true;
}
} else if(event->type == InputTypeRelease) {
if(event->key == InputKeyUp) {
model->up_pressed = false;
hid_hal_keyboard_release(hid_keynote->hid, HID_KEYBOARD_UP_ARROW);
} else if(event->key == InputKeyDown) {
model->down_pressed = false;
hid_hal_keyboard_release(hid_keynote->hid, HID_KEYBOARD_DOWN_ARROW);
} else if(event->key == InputKeyLeft) {
model->left_pressed = false;
hid_hal_keyboard_release(hid_keynote->hid, HID_KEYBOARD_LEFT_ARROW);
} else if(event->key == InputKeyRight) {
model->right_pressed = false;
hid_hal_keyboard_release(hid_keynote->hid, HID_KEYBOARD_RIGHT_ARROW);
} else if(event->key == InputKeyOk) {
model->ok_pressed = false;
hid_hal_keyboard_release(hid_keynote->hid, HID_KEYBOARD_SPACEBAR);
} else if(event->key == InputKeyBack) {
model->back_pressed = false;
}
} else if(event->type == InputTypeShort) {
if(event->key == InputKeyBack) {
hid_hal_keyboard_press(hid_keynote->hid, HID_KEYBOARD_DELETE);
hid_hal_keyboard_release(hid_keynote->hid, HID_KEYBOARD_DELETE);
hid_hal_consumer_key_press(hid_keynote->hid, HID_CONSUMER_AC_BACK);
hid_hal_consumer_key_release(hid_keynote->hid, HID_CONSUMER_AC_BACK);
}
}
},
true);
}
static bool hid_keynote_input_callback(InputEvent* event, void* context) {
furi_assert(context);
HidKeynote* hid_keynote = context;
bool consumed = false;
if(event->type == InputTypeLong && event->key == InputKeyBack) {
hid_hal_keyboard_release_all(hid_keynote->hid);
} else {
hid_keynote_process(hid_keynote, event);
consumed = true;
}
return consumed;
}
HidKeynote* hid_keynote_alloc(Hid* hid) {
HidKeynote* hid_keynote = malloc(sizeof(HidKeynote));
hid_keynote->view = view_alloc();
hid_keynote->hid = hid;
view_set_context(hid_keynote->view, hid_keynote);
view_allocate_model(hid_keynote->view, ViewModelTypeLocking, sizeof(HidKeynoteModel));
view_set_draw_callback(hid_keynote->view, hid_keynote_draw_callback);
view_set_input_callback(hid_keynote->view, hid_keynote_input_callback);
with_view_model(
hid_keynote->view, HidKeynoteModel * model, { model->transport = hid->transport; }, true);
return hid_keynote;
}
void hid_keynote_free(HidKeynote* hid_keynote) {
furi_assert(hid_keynote);
view_free(hid_keynote->view);
free(hid_keynote);
}
View* hid_keynote_get_view(HidKeynote* hid_keynote) {
furi_assert(hid_keynote);
return hid_keynote->view;
}
void hid_keynote_set_connected_status(HidKeynote* hid_keynote, bool connected) {
furi_assert(hid_keynote);
with_view_model(
hid_keynote->view, HidKeynoteModel * model, { model->connected = connected; }, true);
}
void hid_keynote_set_orientation(HidKeynote* hid_keynote, bool vertical) {
furi_assert(hid_keynote);
if(vertical) {
view_set_draw_callback(hid_keynote->view, hid_keynote_draw_vertical_callback);
view_set_orientation(hid_keynote->view, ViewOrientationVerticalFlip);
} else {
view_set_draw_callback(hid_keynote->view, hid_keynote_draw_callback);
view_set_orientation(hid_keynote->view, ViewOrientationHorizontal);
}
}

View file

@ -0,0 +1,16 @@
#pragma once
#include <gui/view.h>
typedef struct Hid Hid;
typedef struct HidKeynote HidKeynote;
HidKeynote* hid_keynote_alloc(Hid* bt_hid);
void hid_keynote_free(HidKeynote* hid_keynote);
View* hid_keynote_get_view(HidKeynote* hid_keynote);
void hid_keynote_set_connected_status(HidKeynote* hid_keynote, bool connected);
void hid_keynote_set_orientation(HidKeynote* hid_keynote, bool vertical);

View file

@ -0,0 +1,234 @@
#include "hid_media.h"
#include <furi.h>
#include <furi_hal_bt_hid.h>
#include <furi_hal_usb_hid.h>
#include <gui/elements.h>
#include "../hid.h"
#include "hid_icons.h"
#define TAG "HidMedia"
struct HidMedia {
View* view;
Hid* hid;
};
typedef struct {
bool left_pressed;
bool up_pressed;
bool right_pressed;
bool down_pressed;
bool ok_pressed;
bool connected;
bool back_pressed;
HidTransport transport;
} HidMediaModel;
static void hid_media_draw_arrow(Canvas* canvas, uint8_t x, uint8_t y, CanvasDirection dir) {
canvas_draw_triangle(canvas, x, y, 5, 3, dir);
if(dir == CanvasDirectionBottomToTop) {
canvas_draw_dot(canvas, x, y - 1);
} else if(dir == CanvasDirectionTopToBottom) {
canvas_draw_dot(canvas, x, y + 1);
} else if(dir == CanvasDirectionRightToLeft) {
canvas_draw_dot(canvas, x - 1, y);
} else if(dir == CanvasDirectionLeftToRight) {
canvas_draw_dot(canvas, x + 1, y);
}
}
static void hid_media_draw_callback(Canvas* canvas, void* context) {
furi_assert(context);
HidMediaModel* 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);
elements_multiline_text_aligned(canvas, 17, 3, AlignLeft, AlignTop, "Media");
canvas_set_font(canvas, FontSecondary);
// Keypad circles
canvas_draw_icon(canvas, 58, 3, &I_OutCircles_70x51);
// Up
if(model->up_pressed) {
canvas_set_bitmap_mode(canvas, 1);
canvas_draw_icon(canvas, 68, 6, &I_S_UP_31x15);
canvas_set_bitmap_mode(canvas, 0);
canvas_set_color(canvas, ColorWhite);
}
canvas_draw_icon(canvas, 79, 9, &I_Volup_8x6);
canvas_set_color(canvas, ColorBlack);
// Down
if(model->down_pressed) {
canvas_set_bitmap_mode(canvas, 1);
canvas_draw_icon(canvas, 68, 36, &I_S_DOWN_31x15);
canvas_set_bitmap_mode(canvas, 0);
canvas_set_color(canvas, ColorWhite);
}
canvas_draw_icon(canvas, 80, 41, &I_Voldwn_6x6);
canvas_set_color(canvas, ColorBlack);
// Left
if(model->left_pressed) {
canvas_set_bitmap_mode(canvas, 1);
canvas_draw_icon(canvas, 61, 13, &I_S_LEFT_15x31);
canvas_set_bitmap_mode(canvas, 0);
canvas_set_color(canvas, ColorWhite);
}
hid_media_draw_arrow(canvas, 67, 28, CanvasDirectionRightToLeft);
hid_media_draw_arrow(canvas, 70, 28, CanvasDirectionRightToLeft);
canvas_draw_line(canvas, 64, 26, 64, 30);
canvas_set_color(canvas, ColorBlack);
// Right
if(model->right_pressed) {
canvas_set_bitmap_mode(canvas, 1);
canvas_draw_icon(canvas, 91, 13, &I_S_RIGHT_15x31);
canvas_set_bitmap_mode(canvas, 0);
canvas_set_color(canvas, ColorWhite);
}
hid_media_draw_arrow(canvas, 96, 28, CanvasDirectionLeftToRight);
hid_media_draw_arrow(canvas, 99, 28, CanvasDirectionLeftToRight);
canvas_draw_line(canvas, 102, 26, 102, 30);
canvas_set_color(canvas, ColorBlack);
// Ok
if(model->ok_pressed) {
canvas_set_bitmap_mode(canvas, 1);
canvas_draw_icon(canvas, 74, 19, &I_Pressed_Button_19x19);
canvas_set_bitmap_mode(canvas, 0);
canvas_set_color(canvas, ColorWhite);
}
hid_media_draw_arrow(canvas, 80, 28, CanvasDirectionLeftToRight);
canvas_draw_line(canvas, 84, 26, 84, 30);
canvas_draw_line(canvas, 86, 26, 86, 30);
canvas_set_color(canvas, ColorBlack);
// Exit
if(model->back_pressed) {
canvas_set_bitmap_mode(canvas, 1);
canvas_draw_icon(canvas, 107, 33, &I_Pressed_Button_19x19);
canvas_set_bitmap_mode(canvas, 0);
canvas_set_color(canvas, ColorWhite);
}
canvas_draw_icon(canvas, 111, 38, &I_Pin_back_arrow_10x10);
canvas_set_color(canvas, ColorBlack);
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");
}
static void hid_media_process_press(HidMedia* hid_media, InputEvent* event) {
with_view_model(
hid_media->view,
HidMediaModel * model,
{
if(event->key == InputKeyUp) {
model->up_pressed = true;
hid_hal_consumer_key_press(hid_media->hid, HID_CONSUMER_VOLUME_INCREMENT);
} else if(event->key == InputKeyDown) {
model->down_pressed = true;
hid_hal_consumer_key_press(hid_media->hid, HID_CONSUMER_VOLUME_DECREMENT);
} else if(event->key == InputKeyLeft) {
model->left_pressed = true;
hid_hal_consumer_key_press(hid_media->hid, HID_CONSUMER_SCAN_PREVIOUS_TRACK);
} else if(event->key == InputKeyRight) {
model->right_pressed = true;
hid_hal_consumer_key_press(hid_media->hid, HID_CONSUMER_SCAN_NEXT_TRACK);
} else if(event->key == InputKeyOk) {
model->ok_pressed = true;
hid_hal_consumer_key_press(hid_media->hid, HID_CONSUMER_PLAY_PAUSE);
} else if(event->key == InputKeyBack) {
model->back_pressed = true;
}
},
true);
}
static void hid_media_process_release(HidMedia* hid_media, InputEvent* event) {
with_view_model(
hid_media->view,
HidMediaModel * model,
{
if(event->key == InputKeyUp) {
model->up_pressed = false;
hid_hal_consumer_key_release(hid_media->hid, HID_CONSUMER_VOLUME_INCREMENT);
} else if(event->key == InputKeyDown) {
model->down_pressed = false;
hid_hal_consumer_key_release(hid_media->hid, HID_CONSUMER_VOLUME_DECREMENT);
} else if(event->key == InputKeyLeft) {
model->left_pressed = false;
hid_hal_consumer_key_release(hid_media->hid, HID_CONSUMER_SCAN_PREVIOUS_TRACK);
} else if(event->key == InputKeyRight) {
model->right_pressed = false;
hid_hal_consumer_key_release(hid_media->hid, HID_CONSUMER_SCAN_NEXT_TRACK);
} else if(event->key == InputKeyOk) {
model->ok_pressed = false;
hid_hal_consumer_key_release(hid_media->hid, HID_CONSUMER_PLAY_PAUSE);
} else if(event->key == InputKeyBack) {
model->back_pressed = false;
}
},
true);
}
static bool hid_media_input_callback(InputEvent* event, void* context) {
furi_assert(context);
HidMedia* hid_media = context;
bool consumed = false;
if(event->type == InputTypeLong && event->key == InputKeyBack) {
hid_hal_keyboard_release_all(hid_media->hid);
} else {
consumed = true;
if(event->type == InputTypePress) {
hid_media_process_press(hid_media, event);
} else if(event->type == InputTypeRelease) {
hid_media_process_release(hid_media, event);
}
}
return consumed;
}
HidMedia* hid_media_alloc(Hid* hid) {
HidMedia* hid_media = malloc(sizeof(HidMedia));
hid_media->view = view_alloc();
hid_media->hid = hid;
view_set_context(hid_media->view, hid_media);
view_allocate_model(hid_media->view, ViewModelTypeLocking, sizeof(HidMediaModel));
view_set_draw_callback(hid_media->view, hid_media_draw_callback);
view_set_input_callback(hid_media->view, hid_media_input_callback);
with_view_model(
hid_media->view, HidMediaModel * model, { model->transport = hid->transport; }, true);
return hid_media;
}
void hid_media_free(HidMedia* hid_media) {
furi_assert(hid_media);
view_free(hid_media->view);
free(hid_media);
}
View* hid_media_get_view(HidMedia* hid_media) {
furi_assert(hid_media);
return hid_media->view;
}
void hid_media_set_connected_status(HidMedia* hid_media, bool connected) {
furi_assert(hid_media);
with_view_model(
hid_media->view, HidMediaModel * model, { model->connected = connected; }, true);
}

View file

@ -0,0 +1,13 @@
#pragma once
#include <gui/view.h>
typedef struct HidMedia HidMedia;
HidMedia* hid_media_alloc();
void hid_media_free(HidMedia* hid_media);
View* hid_media_get_view(HidMedia* hid_media);
void hid_media_set_connected_status(HidMedia* hid_media, bool connected);

View file

@ -0,0 +1,243 @@
#include "hid_mouse.h"
#include <gui/elements.h>
#include "../hid.h"
#include "hid_icons.h"
#define TAG "HidMouse"
struct HidMouse {
View* view;
Hid* hid;
};
typedef struct {
bool left_pressed;
bool up_pressed;
bool right_pressed;
bool down_pressed;
bool left_mouse_pressed;
bool left_mouse_held;
bool right_mouse_pressed;
bool connected;
uint8_t acceleration;
HidTransport transport;
} HidMouseModel;
static void hid_mouse_draw_callback(Canvas* canvas, void* context) {
furi_assert(context);
HidMouseModel* 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);
elements_multiline_text_aligned(canvas, 17, 3, AlignLeft, AlignTop, "Mouse");
canvas_set_font(canvas, FontSecondary);
if(model->left_mouse_held == true) {
elements_multiline_text_aligned(canvas, 0, 62, AlignLeft, AlignBottom, "Selecting...");
} else {
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");
}
// Keypad circles
canvas_draw_icon(canvas, 58, 3, &I_OutCircles_70x51);
// Up
if(model->up_pressed) {
canvas_set_bitmap_mode(canvas, 1);
canvas_draw_icon(canvas, 68, 6, &I_S_UP_31x15);
canvas_set_bitmap_mode(canvas, 0);
canvas_set_color(canvas, ColorWhite);
}
canvas_draw_icon(canvas, 80, 8, &I_Pin_arrow_up_7x9);
canvas_set_color(canvas, ColorBlack);
// Down
if(model->down_pressed) {
canvas_set_bitmap_mode(canvas, 1);
canvas_draw_icon(canvas, 68, 36, &I_S_DOWN_31x15);
canvas_set_bitmap_mode(canvas, 0);
canvas_set_color(canvas, ColorWhite);
}
canvas_draw_icon(canvas, 80, 40, &I_Pin_arrow_down_7x9);
canvas_set_color(canvas, ColorBlack);
// Left
if(model->left_pressed) {
canvas_set_bitmap_mode(canvas, 1);
canvas_draw_icon(canvas, 61, 13, &I_S_LEFT_15x31);
canvas_set_bitmap_mode(canvas, 0);
canvas_set_color(canvas, ColorWhite);
}
canvas_draw_icon(canvas, 63, 25, &I_Pin_arrow_left_9x7);
canvas_set_color(canvas, ColorBlack);
// Right
if(model->right_pressed) {
canvas_set_bitmap_mode(canvas, 1);
canvas_draw_icon(canvas, 91, 13, &I_S_RIGHT_15x31);
canvas_set_bitmap_mode(canvas, 0);
canvas_set_color(canvas, ColorWhite);
}
canvas_draw_icon(canvas, 95, 25, &I_Pin_arrow_right_9x7);
canvas_set_color(canvas, ColorBlack);
// Ok
if(model->left_mouse_pressed) {
canvas_set_bitmap_mode(canvas, 1);
canvas_draw_icon(canvas, 74, 19, &I_Pressed_Button_19x19);
canvas_set_bitmap_mode(canvas, 0);
canvas_set_color(canvas, ColorWhite);
}
canvas_draw_icon(canvas, 79, 24, &I_Left_mouse_icon_9x9);
canvas_set_color(canvas, ColorBlack);
// Back
if(model->right_mouse_pressed) {
canvas_set_bitmap_mode(canvas, 1);
canvas_draw_icon(canvas, 107, 33, &I_Pressed_Button_19x19);
canvas_set_bitmap_mode(canvas, 0);
canvas_set_color(canvas, ColorWhite);
}
canvas_draw_icon(canvas, 112, 38, &I_Right_mouse_icon_9x9);
canvas_set_color(canvas, ColorBlack);
}
static void hid_mouse_process(HidMouse* hid_mouse, InputEvent* event) {
with_view_model(
hid_mouse->view,
HidMouseModel * model,
{
model->acceleration = (event->type == InputTypePress) ? 1 :
(event->type == InputTypeRelease) ? 0 :
(model->acceleration >= 20) ? 20 :
model->acceleration + 1;
if(event->key == InputKeyBack) {
if(event->type == InputTypeShort) {
hid_hal_mouse_press(hid_mouse->hid, HID_MOUSE_BTN_RIGHT);
hid_hal_mouse_release(hid_mouse->hid, HID_MOUSE_BTN_RIGHT);
} else if(event->type == InputTypePress) {
model->right_mouse_pressed = true;
} else if(event->type == InputTypeRelease) {
model->right_mouse_pressed = false;
}
} else if(event->key == InputKeyOk) {
if(event->type == InputTypeShort) {
// Just release if it was being held before
if(!model->left_mouse_held)
hid_hal_mouse_press(hid_mouse->hid, HID_MOUSE_BTN_LEFT);
hid_hal_mouse_release(hid_mouse->hid, HID_MOUSE_BTN_LEFT);
model->left_mouse_held = false;
} else if(event->type == InputTypeLong) {
hid_hal_mouse_press(hid_mouse->hid, HID_MOUSE_BTN_LEFT);
model->left_mouse_held = true;
model->left_mouse_pressed = true;
} else if(event->type == InputTypePress) {
model->left_mouse_pressed = true;
} else if(event->type == InputTypeRelease) {
// Only release if it wasn't a long press
if(!model->left_mouse_held) model->left_mouse_pressed = false;
}
} else if(event->key == InputKeyRight) {
if(event->type == InputTypePress) {
model->right_pressed = true;
hid_hal_mouse_move(hid_mouse->hid, MOUSE_MOVE_SHORT, 0);
} else if(event->type == InputTypeRepeat) {
for(uint8_t i = model->acceleration; i > 1; i -= 2)
hid_hal_mouse_move(hid_mouse->hid, MOUSE_MOVE_LONG, 0);
} else if(event->type == InputTypeRelease) {
model->right_pressed = false;
}
} else if(event->key == InputKeyLeft) {
if(event->type == InputTypePress) {
model->left_pressed = true;
hid_hal_mouse_move(hid_mouse->hid, -MOUSE_MOVE_SHORT, 0);
} else if(event->type == InputTypeRepeat) {
for(uint8_t i = model->acceleration; i > 1; i -= 2)
hid_hal_mouse_move(hid_mouse->hid, -MOUSE_MOVE_LONG, 0);
} else if(event->type == InputTypeRelease) {
model->left_pressed = false;
}
} else if(event->key == InputKeyDown) {
if(event->type == InputTypePress) {
model->down_pressed = true;
hid_hal_mouse_move(hid_mouse->hid, 0, MOUSE_MOVE_SHORT);
} else if(event->type == InputTypeRepeat) {
for(uint8_t i = model->acceleration; i > 1; i -= 2)
hid_hal_mouse_move(hid_mouse->hid, 0, MOUSE_MOVE_LONG);
} else if(event->type == InputTypeRelease) {
model->down_pressed = false;
}
} else if(event->key == InputKeyUp) {
if(event->type == InputTypePress) {
model->up_pressed = true;
hid_hal_mouse_move(hid_mouse->hid, 0, -MOUSE_MOVE_SHORT);
} else if(event->type == InputTypeRepeat) {
for(uint8_t i = model->acceleration; i > 1; i -= 2)
hid_hal_mouse_move(hid_mouse->hid, 0, -MOUSE_MOVE_LONG);
} else if(event->type == InputTypeRelease) {
model->up_pressed = false;
}
}
},
true);
}
static bool hid_mouse_input_callback(InputEvent* event, void* context) {
furi_assert(context);
HidMouse* hid_mouse = context;
bool consumed = false;
if(event->type == InputTypeLong && event->key == InputKeyBack) {
hid_hal_mouse_release_all(hid_mouse->hid);
} else {
hid_mouse_process(hid_mouse, event);
consumed = true;
}
return consumed;
}
HidMouse* hid_mouse_alloc(Hid* hid) {
HidMouse* hid_mouse = malloc(sizeof(HidMouse));
hid_mouse->view = view_alloc();
hid_mouse->hid = hid;
view_set_context(hid_mouse->view, hid_mouse);
view_allocate_model(hid_mouse->view, ViewModelTypeLocking, sizeof(HidMouseModel));
view_set_draw_callback(hid_mouse->view, hid_mouse_draw_callback);
view_set_input_callback(hid_mouse->view, hid_mouse_input_callback);
with_view_model(
hid_mouse->view, HidMouseModel * model, { model->transport = hid->transport; }, true);
return hid_mouse;
}
void hid_mouse_free(HidMouse* hid_mouse) {
furi_assert(hid_mouse);
view_free(hid_mouse->view);
free(hid_mouse);
}
View* hid_mouse_get_view(HidMouse* hid_mouse) {
furi_assert(hid_mouse);
return hid_mouse->view;
}
void hid_mouse_set_connected_status(HidMouse* hid_mouse, bool connected) {
furi_assert(hid_mouse);
with_view_model(
hid_mouse->view, HidMouseModel * model, { model->connected = connected; }, true);
}

View file

@ -0,0 +1,17 @@
#pragma once
#include <gui/view.h>
#define MOUSE_MOVE_SHORT 5
#define MOUSE_MOVE_LONG 20
typedef struct Hid Hid;
typedef struct HidMouse HidMouse;
HidMouse* hid_mouse_alloc(Hid* bt_hid);
void hid_mouse_free(HidMouse* hid_mouse);
View* hid_mouse_get_view(HidMouse* hid_mouse);
void hid_mouse_set_connected_status(HidMouse* hid_mouse, bool connected);

View file

@ -0,0 +1,214 @@
#include "hid_mouse_clicker.h"
#include <gui/elements.h>
#include "../hid.h"
#include "hid_icons.h"
#define TAG "HidMouseClicker"
#define DEFAULT_CLICK_RATE 1
#define MAXIMUM_CLICK_RATE 60
struct HidMouseClicker {
View* view;
Hid* hid;
FuriTimer* timer;
};
typedef struct {
bool connected;
bool running;
int rate;
HidTransport transport;
} HidMouseClickerModel;
static void hid_mouse_clicker_start_or_restart_timer(void* context) {
furi_assert(context);
HidMouseClicker* hid_mouse_clicker = context;
if(furi_timer_is_running(hid_mouse_clicker->timer)) {
furi_timer_stop(hid_mouse_clicker->timer);
}
with_view_model(
hid_mouse_clicker->view,
HidMouseClickerModel * model,
{
furi_timer_start(
hid_mouse_clicker->timer, furi_kernel_get_tick_frequency() / model->rate);
},
true);
}
static void hid_mouse_clicker_draw_callback(Canvas* canvas, void* context) {
furi_assert(context);
HidMouseClickerModel* 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);
elements_multiline_text_aligned(canvas, 17, 3, AlignLeft, AlignTop, "Mouse Clicker");
// Ok
canvas_draw_icon(canvas, 63, 25, &I_Space_65x18);
if(model->running) {
canvas_set_font(canvas, FontPrimary);
FuriString* rate_label = furi_string_alloc();
furi_string_printf(rate_label, "%d clicks/s\n\nUp / Down", model->rate);
elements_multiline_text(canvas, AlignLeft, 35, furi_string_get_cstr(rate_label));
canvas_set_font(canvas, FontSecondary);
furi_string_free(rate_label);
elements_slightly_rounded_box(canvas, 66, 27, 60, 13);
canvas_set_color(canvas, ColorWhite);
} else {
canvas_set_font(canvas, FontPrimary);
elements_multiline_text(canvas, AlignLeft, 35, "Press Start\nto start\nclicking");
canvas_set_font(canvas, FontSecondary);
}
canvas_draw_icon(canvas, 74, 29, &I_Ok_btn_9x9);
if(model->running) {
elements_multiline_text_aligned(canvas, 91, 36, AlignLeft, AlignBottom, "Stop");
} else {
elements_multiline_text_aligned(canvas, 91, 36, AlignLeft, AlignBottom, "Start");
}
canvas_set_color(canvas, ColorBlack);
// Back
canvas_draw_icon(canvas, 74, 49, &I_Pin_back_arrow_10x8);
elements_multiline_text_aligned(canvas, 91, 57, AlignLeft, AlignBottom, "Quit");
}
static void hid_mouse_clicker_timer_callback(void* context) {
furi_assert(context);
HidMouseClicker* hid_mouse_clicker = context;
with_view_model(
hid_mouse_clicker->view,
HidMouseClickerModel * model,
{
if(model->running) {
hid_hal_mouse_press(hid_mouse_clicker->hid, HID_MOUSE_BTN_LEFT);
hid_hal_mouse_release(hid_mouse_clicker->hid, HID_MOUSE_BTN_LEFT);
}
},
false);
}
static void hid_mouse_clicker_enter_callback(void* context) {
hid_mouse_clicker_start_or_restart_timer(context);
}
static void hid_mouse_clicker_exit_callback(void* context) {
furi_assert(context);
HidMouseClicker* hid_mouse_clicker = context;
furi_timer_stop(hid_mouse_clicker->timer);
}
static bool hid_mouse_clicker_input_callback(InputEvent* event, void* context) {
furi_assert(context);
HidMouseClicker* hid_mouse_clicker = context;
bool consumed = false;
bool rate_changed = false;
if(event->type != InputTypeShort && event->type != InputTypeRepeat) {
return false;
}
with_view_model(
hid_mouse_clicker->view,
HidMouseClickerModel * model,
{
switch(event->key) {
case InputKeyOk:
model->running = !model->running;
consumed = true;
break;
case InputKeyUp:
if(model->rate < MAXIMUM_CLICK_RATE) {
model->rate++;
}
rate_changed = true;
consumed = true;
break;
case InputKeyDown:
if(model->rate > 1) {
model->rate--;
}
rate_changed = true;
consumed = true;
break;
default:
consumed = true;
break;
}
},
true);
if(rate_changed) {
hid_mouse_clicker_start_or_restart_timer(context);
}
return consumed;
}
HidMouseClicker* hid_mouse_clicker_alloc(Hid* hid) {
HidMouseClicker* hid_mouse_clicker = malloc(sizeof(HidMouseClicker));
hid_mouse_clicker->view = view_alloc();
view_set_context(hid_mouse_clicker->view, hid_mouse_clicker);
view_allocate_model(
hid_mouse_clicker->view, ViewModelTypeLocking, sizeof(HidMouseClickerModel));
view_set_draw_callback(hid_mouse_clicker->view, hid_mouse_clicker_draw_callback);
view_set_input_callback(hid_mouse_clicker->view, hid_mouse_clicker_input_callback);
view_set_enter_callback(hid_mouse_clicker->view, hid_mouse_clicker_enter_callback);
view_set_exit_callback(hid_mouse_clicker->view, hid_mouse_clicker_exit_callback);
hid_mouse_clicker->hid = hid;
hid_mouse_clicker->timer = furi_timer_alloc(
hid_mouse_clicker_timer_callback, FuriTimerTypePeriodic, hid_mouse_clicker);
with_view_model(
hid_mouse_clicker->view,
HidMouseClickerModel * model,
{
model->transport = hid->transport;
model->rate = DEFAULT_CLICK_RATE;
},
true);
return hid_mouse_clicker;
}
void hid_mouse_clicker_free(HidMouseClicker* hid_mouse_clicker) {
furi_assert(hid_mouse_clicker);
furi_timer_stop(hid_mouse_clicker->timer);
furi_timer_free(hid_mouse_clicker->timer);
view_free(hid_mouse_clicker->view);
free(hid_mouse_clicker);
}
View* hid_mouse_clicker_get_view(HidMouseClicker* hid_mouse_clicker) {
furi_assert(hid_mouse_clicker);
return hid_mouse_clicker->view;
}
void hid_mouse_clicker_set_connected_status(HidMouseClicker* hid_mouse_clicker, bool connected) {
furi_assert(hid_mouse_clicker);
with_view_model(
hid_mouse_clicker->view,
HidMouseClickerModel * model,
{ model->connected = connected; },
true);
}

View file

@ -0,0 +1,14 @@
#pragma once
#include <gui/view.h>
typedef struct Hid Hid;
typedef struct HidMouseClicker HidMouseClicker;
HidMouseClicker* hid_mouse_clicker_alloc(Hid* bt_hid);
void hid_mouse_clicker_free(HidMouseClicker* hid_mouse_clicker);
View* hid_mouse_clicker_get_view(HidMouseClicker* hid_mouse_clicker);
void hid_mouse_clicker_set_connected_status(HidMouseClicker* hid_mouse_clicker, bool connected);

View file

@ -0,0 +1,183 @@
#include "hid_mouse_jiggler.h"
#include <gui/elements.h>
#include "../hid.h"
#include "hid_icons.h"
#define TAG "HidMouseJiggler"
struct HidMouseJiggler {
View* view;
Hid* hid;
FuriTimer* timer;
};
typedef struct {
bool connected;
bool running;
int interval_idx;
uint8_t counter;
HidTransport transport;
} HidMouseJigglerModel;
const int intervals[6] = {500, 2000, 5000, 10000, 30000, 60000};
static void hid_mouse_jiggler_draw_callback(Canvas* canvas, void* context) {
furi_assert(context);
HidMouseJigglerModel* 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);
elements_multiline_text_aligned(canvas, 17, 2, AlignLeft, AlignTop, "Mouse Jiggler");
// Timeout
elements_multiline_text(canvas, AlignLeft, 26, "Interval (ms):");
canvas_set_font(canvas, FontSecondary);
if(model->interval_idx != 0) canvas_draw_icon(canvas, 74, 19, &I_ButtonLeft_4x7);
if(model->interval_idx != (int)COUNT_OF(intervals) - 1)
canvas_draw_icon(canvas, 80, 19, &I_ButtonRight_4x7);
FuriString* interval_str = furi_string_alloc_printf("%d", intervals[model->interval_idx]);
elements_multiline_text(canvas, 91, 26, furi_string_get_cstr(interval_str));
furi_string_free(interval_str);
canvas_set_font(canvas, FontPrimary);
elements_multiline_text(canvas, AlignLeft, 40, "Press Start\nto jiggle");
canvas_set_font(canvas, FontSecondary);
// Ok
canvas_draw_icon(canvas, 63, 30, &I_Space_65x18);
if(model->running) {
elements_slightly_rounded_box(canvas, 66, 32, 60, 13);
canvas_set_color(canvas, ColorWhite);
}
canvas_draw_icon(canvas, 74, 34, &I_Ok_btn_9x9);
if(model->running) {
elements_multiline_text_aligned(canvas, 91, 41, AlignLeft, AlignBottom, "Stop");
} else {
elements_multiline_text_aligned(canvas, 91, 41, AlignLeft, AlignBottom, "Start");
}
canvas_set_color(canvas, ColorBlack);
// Back
canvas_draw_icon(canvas, 74, 54, &I_Pin_back_arrow_10x8);
elements_multiline_text_aligned(canvas, 91, 62, AlignLeft, AlignBottom, "Quit");
}
static void hid_mouse_jiggler_timer_callback(void* context) {
furi_assert(context);
HidMouseJiggler* hid_mouse_jiggler = context;
with_view_model(
hid_mouse_jiggler->view,
HidMouseJigglerModel * model,
{
if(model->running) {
model->counter++;
hid_hal_mouse_move(
hid_mouse_jiggler->hid,
(model->counter % 2 == 0) ? MOUSE_MOVE_SHORT : -MOUSE_MOVE_SHORT,
0);
}
},
false);
}
static void hid_mouse_jiggler_exit_callback(void* context) {
furi_assert(context);
HidMouseJiggler* hid_mouse_jiggler = context;
furi_timer_stop(hid_mouse_jiggler->timer);
}
static bool hid_mouse_jiggler_input_callback(InputEvent* event, void* context) {
furi_assert(context);
HidMouseJiggler* hid_mouse_jiggler = context;
bool consumed = false;
with_view_model(
hid_mouse_jiggler->view,
HidMouseJigglerModel * model,
{
if(event->type == InputTypePress && event->key == InputKeyOk) {
model->running = !model->running;
if(model->running) {
furi_timer_stop(hid_mouse_jiggler->timer);
furi_timer_start(hid_mouse_jiggler->timer, intervals[model->interval_idx]);
};
consumed = true;
}
if(event->type == InputTypePress && event->key == InputKeyRight && !model->running &&
model->interval_idx < (int)COUNT_OF(intervals) - 1) {
model->interval_idx++;
consumed = true;
}
if(event->type == InputTypePress && event->key == InputKeyLeft && !model->running &&
model->interval_idx > 0) {
model->interval_idx--;
consumed = true;
}
},
true);
return consumed;
}
HidMouseJiggler* hid_mouse_jiggler_alloc(Hid* hid) {
HidMouseJiggler* hid_mouse_jiggler = malloc(sizeof(HidMouseJiggler));
hid_mouse_jiggler->view = view_alloc();
view_set_context(hid_mouse_jiggler->view, hid_mouse_jiggler);
view_allocate_model(
hid_mouse_jiggler->view, ViewModelTypeLocking, sizeof(HidMouseJigglerModel));
view_set_draw_callback(hid_mouse_jiggler->view, hid_mouse_jiggler_draw_callback);
view_set_input_callback(hid_mouse_jiggler->view, hid_mouse_jiggler_input_callback);
view_set_exit_callback(hid_mouse_jiggler->view, hid_mouse_jiggler_exit_callback);
hid_mouse_jiggler->hid = hid;
hid_mouse_jiggler->timer = furi_timer_alloc(
hid_mouse_jiggler_timer_callback, FuriTimerTypePeriodic, hid_mouse_jiggler);
with_view_model(
hid_mouse_jiggler->view,
HidMouseJigglerModel * model,
{
model->transport = hid->transport;
model->interval_idx = 2;
},
true);
return hid_mouse_jiggler;
}
void hid_mouse_jiggler_free(HidMouseJiggler* hid_mouse_jiggler) {
furi_assert(hid_mouse_jiggler);
furi_timer_stop(hid_mouse_jiggler->timer);
furi_timer_free(hid_mouse_jiggler->timer);
view_free(hid_mouse_jiggler->view);
free(hid_mouse_jiggler);
}
View* hid_mouse_jiggler_get_view(HidMouseJiggler* hid_mouse_jiggler) {
furi_assert(hid_mouse_jiggler);
return hid_mouse_jiggler->view;
}
void hid_mouse_jiggler_set_connected_status(HidMouseJiggler* hid_mouse_jiggler, bool connected) {
furi_assert(hid_mouse_jiggler);
with_view_model(
hid_mouse_jiggler->view,
HidMouseJigglerModel * model,
{ model->connected = connected; },
true);
}

View file

@ -0,0 +1,16 @@
#pragma once
#include <gui/view.h>
#define MOUSE_MOVE_SHORT 5
typedef struct Hid Hid;
typedef struct HidMouseJiggler HidMouseJiggler;
HidMouseJiggler* hid_mouse_jiggler_alloc(Hid* bt_hid);
void hid_mouse_jiggler_free(HidMouseJiggler* hid_mouse_jiggler);
View* hid_mouse_jiggler_get_view(HidMouseJiggler* hid_mouse_jiggler);
void hid_mouse_jiggler_set_connected_status(HidMouseJiggler* hid_mouse_jiggler, bool connected);

View file

@ -0,0 +1,235 @@
#include "hid_movie.h"
#include <furi.h>
#include <furi_hal_bt_hid.h>
#include <furi_hal_usb_hid.h>
#include <gui/elements.h>
#include "../hid.h"
#include "hid_icons.h"
#define TAG "HidMovie"
struct HidMovie {
View* view;
Hid* hid;
};
typedef struct {
bool left_pressed;
bool up_pressed;
bool right_pressed;
bool down_pressed;
bool ok_pressed;
bool connected;
bool back_pressed;
HidTransport transport;
} HidMovieModel;
static void hid_movie_draw_arrow(Canvas* canvas, uint8_t x, uint8_t y, CanvasDirection dir) {
canvas_draw_triangle(canvas, x, y, 5, 3, dir);
if(dir == CanvasDirectionBottomToTop) {
canvas_draw_dot(canvas, x, y - 1);
} else if(dir == CanvasDirectionTopToBottom) {
canvas_draw_dot(canvas, x, y + 1);
} else if(dir == CanvasDirectionRightToLeft) {
canvas_draw_dot(canvas, x - 1, y);
} else if(dir == CanvasDirectionLeftToRight) {
canvas_draw_dot(canvas, x + 1, y);
}
}
static void hid_movie_draw_callback(Canvas* canvas, void* context) {
furi_assert(context);
HidMovieModel* 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);
elements_multiline_text_aligned(canvas, 17, 3, AlignLeft, AlignTop, "Movie");
canvas_set_font(canvas, FontSecondary);
// Keypad circles
canvas_draw_icon(canvas, 58, 3, &I_OutCircles_70x51);
// Up
if(model->up_pressed) {
canvas_set_bitmap_mode(canvas, 1);
canvas_draw_icon(canvas, 68, 6, &I_S_UP_31x15);
canvas_set_bitmap_mode(canvas, 0);
canvas_set_color(canvas, ColorWhite);
}
canvas_draw_icon(canvas, 79, 9, &I_Volup_8x6);
canvas_set_color(canvas, ColorBlack);
// Down
if(model->down_pressed) {
canvas_set_bitmap_mode(canvas, 1);
canvas_draw_icon(canvas, 68, 36, &I_S_DOWN_31x15);
canvas_set_bitmap_mode(canvas, 0);
canvas_set_color(canvas, ColorWhite);
}
canvas_draw_icon(canvas, 80, 41, &I_Voldwn_6x6);
canvas_set_color(canvas, ColorBlack);
// Left
if(model->left_pressed) {
canvas_set_bitmap_mode(canvas, 1);
canvas_draw_icon(canvas, 61, 13, &I_S_LEFT_15x31);
canvas_set_bitmap_mode(canvas, 0);
canvas_set_color(canvas, ColorWhite);
}
hid_movie_draw_arrow(canvas, 65, 28, CanvasDirectionRightToLeft);
hid_movie_draw_arrow(canvas, 70, 28, CanvasDirectionRightToLeft);
canvas_set_color(canvas, ColorBlack);
// Right
if(model->right_pressed) {
canvas_set_bitmap_mode(canvas, 1);
canvas_draw_icon(canvas, 91, 13, &I_S_RIGHT_15x31);
canvas_set_bitmap_mode(canvas, 0);
canvas_set_color(canvas, ColorWhite);
}
hid_movie_draw_arrow(canvas, 96, 28, CanvasDirectionLeftToRight);
hid_movie_draw_arrow(canvas, 101, 28, CanvasDirectionLeftToRight);
canvas_set_color(canvas, ColorBlack);
// Ok
if(model->ok_pressed) {
canvas_set_bitmap_mode(canvas, 1);
canvas_draw_icon(canvas, 74, 19, &I_Pressed_Button_19x19);
canvas_set_bitmap_mode(canvas, 0);
canvas_set_color(canvas, ColorWhite);
}
hid_movie_draw_arrow(canvas, 80, 28, CanvasDirectionLeftToRight);
canvas_draw_line(canvas, 84, 26, 84, 30);
canvas_draw_line(canvas, 86, 26, 86, 30);
canvas_set_color(canvas, ColorBlack);
// Exit
if(model->back_pressed) {
canvas_set_bitmap_mode(canvas, 1);
canvas_draw_icon(canvas, 107, 33, &I_Pressed_Button_19x19);
canvas_set_bitmap_mode(canvas, 0);
canvas_set_color(canvas, ColorWhite);
}
canvas_draw_icon(canvas, 111, 38, &I_Pin_back_arrow_10x10);
canvas_set_color(canvas, ColorBlack);
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");
}
static void hid_movie_process_press(HidMovie* hid_movie, InputEvent* event) {
with_view_model(
hid_movie->view,
HidMovieModel * model,
{
if(event->key == InputKeyUp) {
model->up_pressed = true;
hid_hal_consumer_key_press(hid_movie->hid, HID_CONSUMER_VOLUME_INCREMENT);
} else if(event->key == InputKeyDown) {
model->down_pressed = true;
hid_hal_consumer_key_press(hid_movie->hid, HID_CONSUMER_VOLUME_DECREMENT);
} else if(event->key == InputKeyLeft) {
model->left_pressed = true;
hid_hal_keyboard_press(hid_movie->hid, HID_KEYBOARD_LEFT_ARROW);
} else if(event->key == InputKeyRight) {
model->right_pressed = true;
hid_hal_keyboard_press(hid_movie->hid, HID_KEYBOARD_RIGHT_ARROW);
} else if(event->key == InputKeyOk) {
model->ok_pressed = true;
hid_hal_consumer_key_press(hid_movie->hid, HID_CONSUMER_PLAY_PAUSE);
} else if(event->key == InputKeyBack) {
model->back_pressed = true;
}
},
true);
}
static void hid_movie_process_release(HidMovie* hid_movie, InputEvent* event) {
with_view_model(
hid_movie->view,
HidMovieModel * model,
{
if(event->key == InputKeyUp) {
model->up_pressed = false;
hid_hal_consumer_key_release(hid_movie->hid, HID_CONSUMER_VOLUME_INCREMENT);
} else if(event->key == InputKeyDown) {
model->down_pressed = false;
hid_hal_consumer_key_release(hid_movie->hid, HID_CONSUMER_VOLUME_DECREMENT);
} else if(event->key == InputKeyLeft) {
model->left_pressed = false;
hid_hal_keyboard_release(hid_movie->hid, HID_KEYBOARD_LEFT_ARROW);
} else if(event->key == InputKeyRight) {
model->right_pressed = false;
hid_hal_keyboard_release(hid_movie->hid, HID_KEYBOARD_RIGHT_ARROW);
} else if(event->key == InputKeyOk) {
model->ok_pressed = false;
hid_hal_consumer_key_release(hid_movie->hid, HID_CONSUMER_PLAY_PAUSE);
} else if(event->key == InputKeyBack) {
model->back_pressed = false;
}
},
true);
}
static bool hid_movie_input_callback(InputEvent* event, void* context) {
furi_assert(context);
HidMovie* hid_movie = context;
bool consumed = false;
if(event->type == InputTypeLong && event->key == InputKeyBack) {
hid_hal_keyboard_release_all(hid_movie->hid);
} else {
consumed = true;
if(event->type == InputTypePress) {
hid_movie_process_press(hid_movie, event);
consumed = true;
} else if(event->type == InputTypeRelease) {
hid_movie_process_release(hid_movie, event);
consumed = true;
}
}
return consumed;
}
HidMovie* hid_movie_alloc(Hid* hid) {
HidMovie* hid_movie = malloc(sizeof(HidMovie));
hid_movie->view = view_alloc();
hid_movie->hid = hid;
view_set_context(hid_movie->view, hid_movie);
view_allocate_model(hid_movie->view, ViewModelTypeLocking, sizeof(HidMovieModel));
view_set_draw_callback(hid_movie->view, hid_movie_draw_callback);
view_set_input_callback(hid_movie->view, hid_movie_input_callback);
with_view_model(
hid_movie->view, HidMovieModel * model, { model->transport = hid->transport; }, true);
return hid_movie;
}
void hid_movie_free(HidMovie* hid_movie) {
furi_assert(hid_movie);
view_free(hid_movie->view);
free(hid_movie);
}
View* hid_movie_get_view(HidMovie* hid_movie) {
furi_assert(hid_movie);
return hid_movie->view;
}
void hid_movie_set_connected_status(HidMovie* hid_movie, bool connected) {
furi_assert(hid_movie);
with_view_model(
hid_movie->view, HidMovieModel * model, { model->connected = connected; }, true);
}

View file

@ -0,0 +1,14 @@
#pragma once
#include <gui/view.h>
typedef struct Hid Hid;
typedef struct HidMovie HidMovie;
HidMovie* hid_movie_alloc(Hid* bt_hid);
void hid_movie_free(HidMovie* hid_movie);
View* hid_movie_get_view(HidMovie* hid_movie);
void hid_movie_set_connected_status(HidMovie* hid_movie, bool connected);

View file

@ -0,0 +1,318 @@
#include "hid_numpad.h"
#include <furi.h>
#include <gui/elements.h>
#include <gui/icon_i.h>
#include "../hid.h"
#include "hid_icons.h"
#define TAG "HidNumpad"
struct HidNumpad {
View* view;
Hid* hid;
};
typedef struct {
uint8_t last_x;
uint8_t last_y;
uint8_t x;
uint8_t y;
uint8_t last_key_code;
uint16_t modifier_code;
bool ok_pressed;
bool back_pressed;
bool connected;
char key_string[5];
HidTransport transport;
} HidNumpadModel;
typedef struct {
uint8_t width;
char* key;
uint8_t height;
const Icon* icon;
uint8_t value;
} HidNumpadKey;
typedef struct {
int8_t x;
int8_t y;
} HidNumpadPoint;
#define MARGIN_TOP 32
#define MARGIN_LEFT 1
#define KEY_WIDTH 20
#define KEY_HEIGHT 15
#define KEY_PADDING 1
#define ROW_COUNT 6
#define COLUMN_COUNT 3
const HidNumpadKey hid_numpad_keyset[ROW_COUNT][COLUMN_COUNT] = {
{
{.width = 1, .height = 1, .icon = NULL, .key = "NL", .value = HID_KEYPAD_NUMLOCK},
{.width = 1, .height = 1, .icon = NULL, .key = "/", .value = HID_KEYPAD_SLASH},
{.width = 1, .height = 1, .icon = NULL, .key = "*", .value = HID_KEYPAD_ASTERISK},
// {.width = 1, .height = 1, .icon = NULL, .key = "-", .value = HID_KEYPAD_MINUS},
},
{
{.width = 1, .height = 1, .icon = NULL, .key = "7", .value = HID_KEYPAD_7},
{.width = 1, .height = 1, .icon = NULL, .key = "8", .value = HID_KEYBOARD_8},
{.width = 1, .height = 1, .icon = NULL, .key = "9", .value = HID_KEYBOARD_9},
// {.width = 1, .height = 2, .icon = NULL, .key = "+", .value = HID_KEYPAD_PLUS},
},
{
{.width = 1, .height = 1, .icon = NULL, .key = "4", .value = HID_KEYPAD_4},
{.width = 1, .height = 1, .icon = NULL, .key = "5", .value = HID_KEYPAD_5},
{.width = 1, .height = 1, .icon = NULL, .key = "6", .value = HID_KEYPAD_6},
},
{
{.width = 1, .height = 1, .icon = NULL, .key = "1", .value = HID_KEYPAD_1},
{.width = 1, .height = 1, .icon = NULL, .key = "2", .value = HID_KEYPAD_2},
{.width = 1, .height = 1, .icon = NULL, .key = "3", .value = HID_KEYPAD_3},
// {.width = 1, .height = 2, .icon = NULL, .key = "En", .value = HID_KEYPAD_ENTER},
},
{
{.width = 2, .height = 1, .icon = NULL, .key = "0", .value = HID_KEYBOARD_0},
{.width = 0, .height = 0, .icon = NULL, .key = "0", .value = HID_KEYBOARD_0},
{.width = 1, .height = 1, .icon = NULL, .key = ".", .value = HID_KEYPAD_DOT},
},
{
{.width = 1, .height = 1, .icon = NULL, .key = "En", .value = HID_KEYPAD_ENTER},
{.width = 1, .height = 1, .icon = NULL, .key = "-", .value = HID_KEYPAD_MINUS},
{.width = 1, .height = 1, .icon = NULL, .key = "+", .value = HID_KEYPAD_PLUS},
},
};
static void hid_numpad_draw_key(
Canvas* canvas,
HidNumpadModel* model,
uint8_t x,
uint8_t y,
HidNumpadKey key,
bool selected) {
if(!key.width || !key.height) return;
canvas_set_color(canvas, ColorBlack);
uint8_t keyWidth = KEY_WIDTH * key.width + KEY_PADDING * (key.width - 1);
uint8_t keyHeight = KEY_HEIGHT * key.height + KEY_PADDING * (key.height - 1);
if(selected) {
elements_slightly_rounded_box(
canvas,
MARGIN_LEFT + x * (KEY_WIDTH + KEY_PADDING),
MARGIN_TOP + y * (KEY_HEIGHT + KEY_PADDING),
keyWidth,
keyHeight);
canvas_set_color(canvas, ColorWhite);
} else {
elements_slightly_rounded_frame(
canvas,
MARGIN_LEFT + x * (KEY_WIDTH + KEY_PADDING),
MARGIN_TOP + y * (KEY_HEIGHT + KEY_PADDING),
keyWidth,
keyHeight);
}
if(key.icon != NULL) {
canvas_draw_icon(
canvas,
MARGIN_LEFT + x * (KEY_WIDTH + KEY_PADDING) + keyWidth / 2 - key.icon->width / 2,
MARGIN_TOP + y * (KEY_HEIGHT + KEY_PADDING) + keyHeight / 2 - key.icon->height / 2,
key.icon);
} else {
strcpy(model->key_string, key.key);
canvas_draw_str_aligned(
canvas,
MARGIN_LEFT + x * (KEY_WIDTH + KEY_PADDING) + keyWidth / 2 + 1,
MARGIN_TOP + y * (KEY_HEIGHT + KEY_PADDING) + keyHeight / 2 + 1,
AlignCenter,
AlignCenter,
model->key_string);
}
}
static void hid_numpad_draw_callback(Canvas* canvas, void* context) {
furi_assert(context);
HidNumpadModel* model = context;
// Header
canvas_set_font(canvas, FontPrimary);
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);
elements_multiline_text_aligned(
canvas, 7, 60, AlignLeft, AlignBottom, "Waiting for\nConnection...");
}
elements_multiline_text_aligned(canvas, 20, 3, AlignLeft, AlignTop, "Numpad");
} else {
elements_multiline_text_aligned(canvas, 12, 3, AlignLeft, AlignTop, "Numpad");
}
canvas_draw_icon(canvas, 3, 18, &I_Pin_back_arrow_10x8);
canvas_set_font(canvas, FontSecondary);
elements_multiline_text_aligned(canvas, 15, 19, AlignLeft, AlignTop, "Hold to exit");
if(!model->connected && (model->transport == HidTransportBle)) {
return;
}
canvas_set_font(canvas, FontKeyboard);
uint8_t initY = 0; // = model->y == 0 ? 0 : 1;
// if(model->y > ROW_COUNT) {
// initY = model->y - (ROW_COUNT - 1);
// }
for(uint8_t y = initY; y < ROW_COUNT; y++) {
const HidNumpadKey* numpadKeyRow = hid_numpad_keyset[y];
uint8_t x = 0;
for(uint8_t i = 0; i < COLUMN_COUNT; i++) {
HidNumpadKey key = numpadKeyRow[i];
bool keySelected = (x <= model->x && model->x < (x + key.width)) && y == model->y;
bool backSelected = model->back_pressed && key.value == HID_KEYBOARD_DELETE;
hid_numpad_draw_key(
canvas,
model,
x,
y - initY,
key,
(!model->ok_pressed && keySelected) || backSelected);
x += key.width;
}
}
}
static uint8_t hid_numpad_get_selected_key(HidNumpadModel* model) {
HidNumpadKey key = hid_numpad_keyset[model->y][model->x];
return key.value;
}
static void hid_numpad_get_select_key(HidNumpadModel* model, HidNumpadPoint delta) {
do {
const int delta_sum = model->y + delta.y;
model->y = delta_sum < 0 ? ROW_COUNT - 1 : delta_sum % ROW_COUNT;
} while(delta.y != 0 && hid_numpad_keyset[model->y][model->x].value == 0);
do {
const int delta_sum = model->x + delta.x;
model->x = delta_sum < 0 ? COLUMN_COUNT - 1 : delta_sum % COLUMN_COUNT;
} while(delta.x != 0 && hid_numpad_keyset[model->y][model->x].width == 0);
}
static void hid_numpad_process(HidNumpad* hid_numpad, InputEvent* event) {
with_view_model(
hid_numpad->view,
HidNumpadModel * model,
{
if(event->key == InputKeyOk) {
if(event->type == InputTypePress) {
model->ok_pressed = true;
} else if(event->type == InputTypeLong || event->type == InputTypeShort) {
model->last_key_code = hid_numpad_get_selected_key(model);
hid_hal_keyboard_press(
hid_numpad->hid, model->modifier_code | model->last_key_code);
} else if(event->type == InputTypeRelease) {
hid_hal_keyboard_release(
hid_numpad->hid, model->modifier_code | model->last_key_code);
model->ok_pressed = false;
}
} else if(event->key == InputKeyBack) {
if(event->type == InputTypePress) {
model->back_pressed = true;
} else if(event->type == InputTypeShort) {
hid_hal_keyboard_press(hid_numpad->hid, HID_KEYBOARD_DELETE);
hid_hal_keyboard_release(hid_numpad->hid, HID_KEYBOARD_DELETE);
} else if(event->type == InputTypeRelease) {
model->back_pressed = false;
}
} else if(event->type == InputTypePress || event->type == InputTypeRepeat) {
if(event->key == InputKeyUp) {
hid_numpad_get_select_key(model, (HidNumpadPoint){.x = 0, .y = -1});
} else if(event->key == InputKeyDown) {
hid_numpad_get_select_key(model, (HidNumpadPoint){.x = 0, .y = 1});
} else if(event->key == InputKeyLeft) {
if(model->last_x == 2 && model->last_y == 2 && model->y == 1 &&
model->x == 3) {
model->x = model->last_x;
model->y = model->last_y;
} else if(
model->last_x == 2 && model->last_y == 4 && model->y == 3 &&
model->x == 3) {
model->x = model->last_x;
model->y = model->last_y;
} else
hid_numpad_get_select_key(model, (HidNumpadPoint){.x = -1, .y = 0});
model->last_x = 0;
model->last_y = 0;
} else if(event->key == InputKeyRight) {
if(model->x == 2 && model->y == 2) {
model->last_x = model->x;
model->last_y = model->y;
hid_numpad_get_select_key(model, (HidNumpadPoint){.x = 1, .y = -1});
} else if(model->x == 2 && model->y == 4) {
model->last_x = model->x;
model->last_y = model->y;
hid_numpad_get_select_key(model, (HidNumpadPoint){.x = 1, .y = -1});
} else {
hid_numpad_get_select_key(model, (HidNumpadPoint){.x = 1, .y = 0});
}
}
}
},
true);
}
static bool hid_numpad_input_callback(InputEvent* event, void* context) {
furi_assert(context);
HidNumpad* hid_numpad = context;
bool consumed = false;
if(event->type == InputTypeLong && event->key == InputKeyBack) {
hid_hal_keyboard_release_all(hid_numpad->hid);
} else {
hid_numpad_process(hid_numpad, event);
consumed = true;
}
return consumed;
}
HidNumpad* hid_numpad_alloc(Hid* bt_hid) {
HidNumpad* hid_numpad = malloc(sizeof(HidNumpad));
hid_numpad->view = view_alloc();
hid_numpad->hid = bt_hid;
view_set_context(hid_numpad->view, hid_numpad);
view_allocate_model(hid_numpad->view, ViewModelTypeLocking, sizeof(HidNumpadModel));
view_set_orientation(hid_numpad->view, ViewOrientationVertical);
view_set_draw_callback(hid_numpad->view, hid_numpad_draw_callback);
view_set_input_callback(hid_numpad->view, hid_numpad_input_callback);
with_view_model(
hid_numpad->view,
HidNumpadModel * model,
{
model->transport = bt_hid->transport;
model->y = 0;
},
true);
return hid_numpad;
}
void hid_numpad_free(HidNumpad* hid_numpad) {
furi_assert(hid_numpad);
view_free(hid_numpad->view);
free(hid_numpad);
}
View* hid_numpad_get_view(HidNumpad* hid_numpad) {
furi_assert(hid_numpad);
return hid_numpad->view;
}
void hid_numpad_set_connected_status(HidNumpad* hid_numpad, bool connected) {
furi_assert(hid_numpad);
with_view_model(
hid_numpad->view, HidNumpadModel * model, { model->connected = connected; }, true);
}

View file

@ -0,0 +1,14 @@
#pragma once
#include <gui/view.h>
typedef struct Hid Hid;
typedef struct HidNumpad HidNumpad;
HidNumpad* hid_numpad_alloc(Hid* bt_hid);
void hid_numpad_free(HidNumpad* hid_numpad);
View* hid_numpad_get_view(HidNumpad* hid_numpad);
void hid_numpad_set_connected_status(HidNumpad* hid_numpad, bool connected);

View file

@ -0,0 +1,815 @@
#include "hid_ptt.h"
#include "hid_ptt_menu.h"
#include <gui/elements.h>
#include <notification/notification_messages.h>
#include <gui/modules/widget.h>
#include "../hid.h"
#include "../views.h"
#include "hid_icons.h"
#define TAG "HidPushToTalk"
struct HidPushToTalk {
View* view;
Hid* hid;
Widget* help;
};
typedef void (*PushToTalkActionCallback)(HidPushToTalk* hid_ptt);
typedef struct {
bool left_pressed;
bool up_pressed;
bool right_pressed;
bool down_pressed;
bool muted;
bool ptt_pressed;
bool mic_pressed;
bool connected;
FuriString *os;
FuriString *app;
size_t osIndex;
size_t appIndex;
size_t window_position;
HidTransport transport;
PushToTalkActionCallback callback_trigger_mute;
PushToTalkActionCallback callback_trigger_camera;
PushToTalkActionCallback callback_trigger_hand;
PushToTalkActionCallback callback_start_ptt;
PushToTalkActionCallback callback_stop_ptt;
} HidPushToTalkModel;
enum HidPushToTalkAppIndex {
HidPushToTalkAppIndexDiscord,
HidPushToTalkAppIndexFaceTime,
HidPushToTalkAppIndexGoogleMeet,
HidPushToTalkAppIndexGoogleHangouts,
HidPushToTalkAppIndexJamulus,
HidPushToTalkAppIndexSignal,
HidPushToTalkAppIndexSkype,
HidPushToTalkAppIndexSlackCall,
HidPushToTalkAppIndexSlackHubble,
HidPushToTalkAppIndexTeams,
HidPushToTalkAppIndexTeamSpeak,
HidPushToTalkAppIndexWebex,
HidPushToTalkAppIndexZoom,
HidPushToTalkAppIndexSize,
};
// meet, zoom
static void hid_ptt_start_ptt_meet_zoom(HidPushToTalk* hid_ptt) {
hid_hal_keyboard_press(hid_ptt->hid, HID_KEYBOARD_SPACEBAR);
}
static void hid_ptt_stop_ptt_meet_zoom(HidPushToTalk* hid_ptt) {
hid_hal_keyboard_release(hid_ptt->hid, HID_KEYBOARD_SPACEBAR);
}
static void hid_ptt_trigger_mute_macos_meet(HidPushToTalk* hid_ptt) {
hid_hal_keyboard_press( hid_ptt->hid, KEY_MOD_LEFT_GUI | HID_KEYBOARD_D);
hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_GUI | HID_KEYBOARD_D);
}
static void hid_ptt_trigger_mute_linux_meet(HidPushToTalk* hid_ptt) {
hid_hal_keyboard_press( hid_ptt->hid, KEY_MOD_LEFT_CTRL | HID_KEYBOARD_D);
hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_CTRL | HID_KEYBOARD_D);
}
static void hid_ptt_trigger_camera_macos_meet(HidPushToTalk* hid_ptt) {
hid_hal_keyboard_press( hid_ptt->hid, KEY_MOD_LEFT_GUI | HID_KEYBOARD_E);
hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_GUI | HID_KEYBOARD_E);
}
static void hid_ptt_trigger_camera_linux_meet(HidPushToTalk* hid_ptt) {
hid_hal_keyboard_press( hid_ptt->hid, KEY_MOD_LEFT_CTRL | HID_KEYBOARD_E);
hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_CTRL | HID_KEYBOARD_E );
}
static void hid_ptt_trigger_hand_macos_meet(HidPushToTalk* hid_ptt) {
hid_hal_keyboard_press( hid_ptt->hid, KEY_MOD_LEFT_GUI | KEY_MOD_LEFT_CTRL |HID_KEYBOARD_H);
hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_GUI | KEY_MOD_LEFT_CTRL |HID_KEYBOARD_H);
}
static void hid_ptt_trigger_hand_linux_meet(HidPushToTalk* hid_ptt) {
hid_hal_keyboard_press( hid_ptt->hid, KEY_MOD_LEFT_CTRL | KEY_MOD_LEFT_ALT |HID_KEYBOARD_H);
hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_CTRL | KEY_MOD_LEFT_ALT |HID_KEYBOARD_H);
}
static void hid_ptt_trigger_mute_macos_zoom(HidPushToTalk* hid_ptt) {
hid_hal_keyboard_press( hid_ptt->hid, KEY_MOD_LEFT_GUI| KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_A);
hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_GUI| KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_A);
}
static void hid_ptt_trigger_mute_linux_zoom(HidPushToTalk* hid_ptt) {
hid_hal_keyboard_press( hid_ptt->hid, KEY_MOD_LEFT_ALT | HID_KEYBOARD_A);
hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_ALT | HID_KEYBOARD_A);
}
static void hid_ptt_trigger_camera_macos_zoom(HidPushToTalk* hid_ptt) {
hid_hal_keyboard_press( hid_ptt->hid, KEY_MOD_LEFT_GUI| KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_V);
hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_GUI| KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_V);
}
static void hid_ptt_trigger_camera_linux_zoom(HidPushToTalk* hid_ptt) {
hid_hal_keyboard_press( hid_ptt->hid, KEY_MOD_LEFT_ALT | HID_KEYBOARD_V);
hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_ALT | HID_KEYBOARD_V);
}
static void hid_ptt_trigger_hand_zoom(HidPushToTalk* hid_ptt) {
hid_hal_keyboard_press( hid_ptt->hid, KEY_MOD_LEFT_ALT | HID_KEYBOARD_Y);
hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_ALT | HID_KEYBOARD_Y);
}
// this one is widely used across different apps
static void hid_ptt_trigger_cmd_shift_m(HidPushToTalk* hid_ptt) {
hid_hal_keyboard_press( hid_ptt->hid, KEY_MOD_LEFT_GUI| KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_M);
hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_GUI| KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_M);
}
// Hangouts HidPushToTalkAppIndexGoogleHangouts
static void hid_ptt_trigger_mute_macos_hangouts(HidPushToTalk* hid_ptt) {
hid_hal_keyboard_press( hid_ptt->hid, KEY_MOD_LEFT_GUI | HID_KEYBOARD_D);
hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_GUI | HID_KEYBOARD_D);
}
static void hid_ptt_trigger_mute_linux_hangouts(HidPushToTalk* hid_ptt) {
hid_hal_keyboard_press( hid_ptt->hid, KEY_MOD_LEFT_CTRL | HID_KEYBOARD_D);
hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_CTRL | HID_KEYBOARD_D);
}
static void hid_ptt_trigger_camera_macos_hangouts(HidPushToTalk* hid_ptt) { // and hand in teams
hid_hal_keyboard_press( hid_ptt->hid, KEY_MOD_LEFT_GUI | HID_KEYBOARD_E);
hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_GUI | HID_KEYBOARD_E);
}
static void hid_ptt_trigger_camera_linux_hangouts(HidPushToTalk* hid_ptt) { // and hand in teams
hid_hal_keyboard_press( hid_ptt->hid, KEY_MOD_LEFT_CTRL | HID_KEYBOARD_E);
hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_CTRL | HID_KEYBOARD_E);
}
// Signal
static void hid_ptt_trigger_mute_signal(HidPushToTalk* hid_ptt) {
hid_hal_keyboard_press( hid_ptt->hid, KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_M);
hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_M);
}
static void hid_ptt_trigger_camera_signal(HidPushToTalk* hid_ptt) {
hid_hal_keyboard_press( hid_ptt->hid, KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_V);
hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_V);
}
// skype
static void hid_ptt_trigger_mute_linux_skype(HidPushToTalk* hid_ptt) { // and webex
hid_hal_keyboard_press( hid_ptt->hid, KEY_MOD_LEFT_CTRL | HID_KEYBOARD_M);
hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_CTRL | HID_KEYBOARD_M);
}
static void hid_ptt_trigger_camera_macos_skype(HidPushToTalk* hid_ptt) { // and hand in teams
hid_hal_keyboard_press( hid_ptt->hid, KEY_MOD_LEFT_GUI| KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_K);
hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_GUI| KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_K);
}
static void hid_ptt_trigger_camera_linux_skype(HidPushToTalk* hid_ptt) { // and hand in teams
hid_hal_keyboard_press( hid_ptt->hid, KEY_MOD_LEFT_CTRL| KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_K);
hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_CTRL| KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_K);
}
// slack call
static void hid_ptt_trigger_mute_slack_call(HidPushToTalk* hid_ptt) {
hid_hal_keyboard_press( hid_ptt->hid, HID_KEYBOARD_M);
hid_hal_keyboard_release(hid_ptt->hid, HID_KEYBOARD_M);
}
static void hid_ptt_trigger_camera_slack_call(HidPushToTalk* hid_ptt) {
hid_hal_keyboard_press( hid_ptt->hid, HID_KEYBOARD_V);
hid_hal_keyboard_release(hid_ptt->hid, HID_KEYBOARD_V);
}
// slack hubble
static void hid_ptt_trigger_mute_macos_slack_hubble(HidPushToTalk* hid_ptt) {
hid_hal_keyboard_press( hid_ptt->hid, KEY_MOD_LEFT_GUI | KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_SPACEBAR);
hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_GUI | KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_SPACEBAR);
}
static void hid_ptt_trigger_mute_linux_slack_hubble(HidPushToTalk* hid_ptt) {
hid_hal_keyboard_press( hid_ptt->hid, KEY_MOD_LEFT_CTRL | KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_SPACEBAR);
hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_CTRL | KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_SPACEBAR);
}
// discord
static void hid_ptt_trigger_mute_macos_discord(HidPushToTalk* hid_ptt) {
hid_hal_keyboard_press( hid_ptt->hid, KEY_MOD_LEFT_GUI | KEY_MOD_RIGHT_GUI | KEY_MOD_RIGHT_ALT | KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_M);
hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_GUI | KEY_MOD_RIGHT_GUI | KEY_MOD_RIGHT_ALT | KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_M);
}
static void hid_ptt_start_ptt_macos_discord(HidPushToTalk* hid_ptt) { // and TeamSpeak
hid_hal_keyboard_press( hid_ptt->hid, KEY_MOD_LEFT_GUI | KEY_MOD_RIGHT_GUI | KEY_MOD_RIGHT_ALT | KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_P);
}
static void hid_ptt_stop_ptt_macos_discord(HidPushToTalk* hid_ptt) { // and TeamSpeak
hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_GUI | KEY_MOD_RIGHT_GUI | KEY_MOD_RIGHT_ALT | KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_P);
}
static void hid_ptt_trigger_mute_linux_discord(HidPushToTalk* hid_ptt) {
hid_hal_keyboard_press( hid_ptt->hid, KEY_MOD_LEFT_CTRL | KEY_MOD_RIGHT_CTRL | KEY_MOD_RIGHT_ALT | KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_M);
hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_CTRL | KEY_MOD_RIGHT_CTRL | KEY_MOD_RIGHT_ALT | KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_M);
}
static void hid_ptt_start_ptt_linux_discord(HidPushToTalk* hid_ptt) { // and TeamSpeak
hid_hal_keyboard_press( hid_ptt->hid, KEY_MOD_LEFT_CTRL | KEY_MOD_RIGHT_CTRL | KEY_MOD_RIGHT_ALT | KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_P);
}
static void hid_ptt_stop_ptt_linux_discord(HidPushToTalk* hid_ptt) { // and TeamSpeak
hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_CTRL | KEY_MOD_RIGHT_CTRL | KEY_MOD_RIGHT_ALT | KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_P);
}
// teamspeak
static void hid_ptt_trigger_mute_macos_teamspeak(HidPushToTalk* hid_ptt) {
hid_hal_keyboard_press( hid_ptt->hid, KEY_MOD_LEFT_GUI | KEY_MOD_RIGHT_GUI | KEY_MOD_RIGHT_ALT | KEY_MOD_RIGHT_SHIFT | HID_KEYBOARD_M);
hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_GUI | KEY_MOD_RIGHT_GUI | KEY_MOD_RIGHT_ALT | KEY_MOD_RIGHT_SHIFT | HID_KEYBOARD_M);
}
static void hid_ptt_start_ptt_macos_teamspeak(HidPushToTalk* hid_ptt) {
hid_hal_keyboard_press( hid_ptt->hid, KEY_MOD_LEFT_GUI | KEY_MOD_RIGHT_GUI | KEY_MOD_RIGHT_ALT | KEY_MOD_RIGHT_SHIFT | HID_KEYBOARD_P);
}
static void hid_ptt_stop_ptt_macos_teamspeak(HidPushToTalk* hid_ptt) {
hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_GUI | KEY_MOD_RIGHT_GUI | KEY_MOD_RIGHT_ALT | KEY_MOD_RIGHT_SHIFT | HID_KEYBOARD_P);
}
static void hid_ptt_trigger_mute_linux_teamspeak(HidPushToTalk* hid_ptt) {
hid_hal_keyboard_press( hid_ptt->hid, KEY_MOD_LEFT_CTRL | KEY_MOD_RIGHT_CTRL | KEY_MOD_RIGHT_ALT | KEY_MOD_RIGHT_SHIFT | HID_KEYBOARD_M);
hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_CTRL | KEY_MOD_RIGHT_CTRL | KEY_MOD_RIGHT_ALT | KEY_MOD_RIGHT_SHIFT | HID_KEYBOARD_M);
}
static void hid_ptt_start_ptt_linux_teamspeak(HidPushToTalk* hid_ptt) {
hid_hal_keyboard_press( hid_ptt->hid, KEY_MOD_LEFT_CTRL | KEY_MOD_RIGHT_CTRL | KEY_MOD_RIGHT_ALT | KEY_MOD_RIGHT_SHIFT | HID_KEYBOARD_P);
}
static void hid_ptt_stop_ptt_linux_teamspeak(HidPushToTalk* hid_ptt) {
hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_CTRL | KEY_MOD_RIGHT_CTRL | KEY_MOD_RIGHT_ALT | KEY_MOD_RIGHT_SHIFT | HID_KEYBOARD_P);
}
// teams
static void hid_ptt_start_ptt_macos_teams(HidPushToTalk* hid_ptt) {
hid_hal_keyboard_press(hid_ptt->hid, KEY_MOD_LEFT_GUI|HID_KEYBOARD_SPACEBAR);
}
static void hid_ptt_start_ptt_linux_teams(HidPushToTalk* hid_ptt) {
hid_hal_keyboard_press(hid_ptt->hid, KEY_MOD_LEFT_CTRL|HID_KEYBOARD_SPACEBAR);
}
static void hid_ptt_stop_ptt_macos_teams(HidPushToTalk* hid_ptt) {
hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_GUI|HID_KEYBOARD_SPACEBAR);
}
static void hid_ptt_stop_ptt_linux_teams(HidPushToTalk* hid_ptt) {
hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_CTRL|HID_KEYBOARD_SPACEBAR);
}
static void hid_ptt_trigger_mute_linux_teams(HidPushToTalk* hid_ptt) {
hid_hal_keyboard_press( hid_ptt->hid, KEY_MOD_LEFT_CTRL| KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_M);
hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_CTRL| KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_M);
}
static void hid_ptt_trigger_camera_macos_teams(HidPushToTalk* hid_ptt) {
hid_hal_keyboard_press( hid_ptt->hid, KEY_MOD_LEFT_GUI | KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_O);
hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_GUI | KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_O);
}
static void hid_ptt_trigger_camera_linux_teams(HidPushToTalk* hid_ptt) {
hid_hal_keyboard_press( hid_ptt->hid, KEY_MOD_LEFT_CTRL | KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_O);
hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_CTRL | KEY_MOD_LEFT_SHIFT |HID_KEYBOARD_O);
}
// Jamulus
static void hid_ptt_trigger_mute_jamulus(HidPushToTalk* hid_ptt) {
hid_hal_keyboard_press( hid_ptt->hid, KEY_MOD_LEFT_ALT | HID_KEYBOARD_M);
hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_ALT | HID_KEYBOARD_M);
}
// webex
static void hid_ptt_trigger_camera_webex(HidPushToTalk* hid_ptt) {
hid_hal_keyboard_press( hid_ptt->hid, KEY_MOD_LEFT_CTRL| KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_V);
hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_CTRL| KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_V);
}
static void hid_ptt_trigger_hand_macos_webex(HidPushToTalk* hid_ptt) {
hid_hal_keyboard_press( hid_ptt->hid, KEY_MOD_LEFT_GUI | KEY_MOD_RIGHT_SHIFT | HID_KEYBOARD_R);
hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_GUI | KEY_MOD_RIGHT_SHIFT | HID_KEYBOARD_R);
}
static void hid_ptt_trigger_hand_linux_webex(HidPushToTalk* hid_ptt) {
hid_hal_keyboard_press( hid_ptt->hid, KEY_MOD_LEFT_CTRL | KEY_MOD_RIGHT_SHIFT | HID_KEYBOARD_R);
hid_hal_keyboard_release(hid_ptt->hid, KEY_MOD_LEFT_CTRL | KEY_MOD_RIGHT_SHIFT | HID_KEYBOARD_R);
}
static void hid_ptt_menu_callback(void* context, uint32_t osIndex, FuriString* osLabel, uint32_t appIndex, FuriString* appLabel) {
furi_assert(context);
HidPushToTalk* hid_ptt = context;
with_view_model(
hid_ptt->view, HidPushToTalkModel * model, {
furi_string_set(model->os, osLabel);
furi_string_set(model->app, appLabel);
model->osIndex = osIndex;
model->appIndex = appIndex;
model->callback_trigger_mute = NULL;
model->callback_trigger_camera = NULL;
model->callback_trigger_hand = NULL;
model->callback_start_ptt = NULL;
model->callback_stop_ptt = NULL;
FURI_LOG_E(TAG, "appIndex: %lu", appIndex);
if(osIndex == HidPushToTalkMacOS) {
switch(appIndex) {
case HidPushToTalkAppIndexDiscord:
model->callback_trigger_mute = hid_ptt_trigger_mute_macos_discord;
model->callback_start_ptt = hid_ptt_start_ptt_macos_discord;
model->callback_stop_ptt = hid_ptt_stop_ptt_macos_discord;
break;
case HidPushToTalkAppIndexFaceTime:
model->callback_trigger_mute = hid_ptt_trigger_cmd_shift_m;
model->callback_start_ptt = hid_ptt_trigger_cmd_shift_m;
model->callback_stop_ptt = hid_ptt_trigger_cmd_shift_m;
break;
case HidPushToTalkAppIndexGoogleHangouts:
model->callback_trigger_mute = hid_ptt_trigger_mute_macos_hangouts;
model->callback_trigger_camera = hid_ptt_trigger_camera_macos_hangouts;
model->callback_start_ptt = hid_ptt_trigger_mute_macos_hangouts;
model->callback_stop_ptt = hid_ptt_trigger_mute_macos_hangouts;
break;
case HidPushToTalkAppIndexGoogleMeet:
model->callback_trigger_mute = hid_ptt_trigger_mute_macos_meet;
model->callback_trigger_camera = hid_ptt_trigger_camera_macos_meet;
model->callback_trigger_hand = hid_ptt_trigger_hand_macos_meet;
model->callback_start_ptt = hid_ptt_start_ptt_meet_zoom;
model->callback_stop_ptt = hid_ptt_stop_ptt_meet_zoom;
break;
case HidPushToTalkAppIndexJamulus:
model->callback_trigger_mute = hid_ptt_trigger_mute_jamulus;
model->callback_start_ptt = hid_ptt_trigger_mute_jamulus;
model->callback_stop_ptt = hid_ptt_trigger_mute_jamulus;
break;
case HidPushToTalkAppIndexTeams:
model->callback_trigger_mute = hid_ptt_trigger_cmd_shift_m;
model->callback_trigger_camera = hid_ptt_trigger_camera_macos_teams;
model->callback_trigger_hand = hid_ptt_trigger_camera_macos_skype;
model->callback_start_ptt = hid_ptt_start_ptt_macos_teams;
model->callback_stop_ptt = hid_ptt_stop_ptt_macos_teams;
break;
case HidPushToTalkAppIndexTeamSpeak:
model->callback_trigger_mute = hid_ptt_trigger_mute_macos_teamspeak;
model->callback_start_ptt = hid_ptt_start_ptt_macos_teamspeak;
model->callback_stop_ptt = hid_ptt_stop_ptt_macos_teamspeak;
break;
case HidPushToTalkAppIndexSignal:
model->callback_trigger_mute = hid_ptt_trigger_mute_signal;
model->callback_trigger_camera = hid_ptt_trigger_camera_signal;
model->callback_start_ptt = hid_ptt_trigger_mute_signal;
model->callback_stop_ptt = hid_ptt_trigger_mute_signal;
break;
case HidPushToTalkAppIndexSkype:
model->callback_trigger_mute = hid_ptt_trigger_cmd_shift_m;
model->callback_trigger_camera = hid_ptt_trigger_camera_macos_skype;
model->callback_start_ptt = hid_ptt_trigger_cmd_shift_m;
model->callback_stop_ptt = hid_ptt_trigger_cmd_shift_m;
break;
case HidPushToTalkAppIndexSlackCall:
model->callback_trigger_mute = hid_ptt_trigger_mute_slack_call;
model->callback_trigger_camera = hid_ptt_trigger_camera_slack_call;
model->callback_start_ptt = hid_ptt_trigger_mute_slack_call;
model->callback_stop_ptt = hid_ptt_trigger_mute_slack_call;
break;
case HidPushToTalkAppIndexSlackHubble:
model->callback_trigger_mute = hid_ptt_trigger_mute_macos_slack_hubble;
model->callback_start_ptt = hid_ptt_trigger_mute_macos_slack_hubble;
model->callback_stop_ptt = hid_ptt_trigger_mute_macos_slack_hubble;
break;
case HidPushToTalkAppIndexWebex:
model->callback_trigger_mute = hid_ptt_trigger_cmd_shift_m;
model->callback_trigger_camera = hid_ptt_trigger_camera_webex;
model->callback_trigger_hand = hid_ptt_trigger_hand_macos_webex;
model->callback_start_ptt = hid_ptt_trigger_cmd_shift_m;
model->callback_stop_ptt = hid_ptt_trigger_cmd_shift_m;
break;
case HidPushToTalkAppIndexZoom:
model->callback_trigger_mute = hid_ptt_trigger_mute_macos_zoom;
model->callback_trigger_camera = hid_ptt_trigger_camera_macos_zoom;
model->callback_trigger_hand = hid_ptt_trigger_hand_zoom;
model->callback_start_ptt = hid_ptt_start_ptt_meet_zoom;
model->callback_stop_ptt = hid_ptt_stop_ptt_meet_zoom;
break;
}
} else if (osIndex == HidPushToTalkLinux) {
switch(appIndex) {
case HidPushToTalkAppIndexDiscord:
model->callback_trigger_mute = hid_ptt_trigger_mute_linux_discord;
model->callback_start_ptt = hid_ptt_start_ptt_linux_discord;
model->callback_stop_ptt = hid_ptt_stop_ptt_linux_discord;
break;
case HidPushToTalkAppIndexGoogleHangouts:
model->callback_trigger_mute = hid_ptt_trigger_mute_linux_hangouts;
model->callback_trigger_camera = hid_ptt_trigger_camera_linux_hangouts;
model->callback_start_ptt = hid_ptt_trigger_mute_linux_hangouts;
model->callback_stop_ptt = hid_ptt_trigger_mute_linux_hangouts;
break;
case HidPushToTalkAppIndexGoogleMeet:
model->callback_trigger_mute = hid_ptt_trigger_mute_linux_meet;
model->callback_trigger_camera = hid_ptt_trigger_camera_linux_meet;
model->callback_trigger_hand = hid_ptt_trigger_hand_linux_meet;
model->callback_start_ptt = hid_ptt_start_ptt_meet_zoom;
model->callback_stop_ptt = hid_ptt_stop_ptt_meet_zoom;
break;
case HidPushToTalkAppIndexJamulus:
model->callback_trigger_mute = hid_ptt_trigger_mute_jamulus;
model->callback_start_ptt = hid_ptt_trigger_mute_jamulus;
model->callback_stop_ptt = hid_ptt_trigger_mute_jamulus;
break;
case HidPushToTalkAppIndexTeams:
model->callback_trigger_mute = hid_ptt_trigger_mute_linux_teams;
model->callback_trigger_camera = hid_ptt_trigger_camera_linux_teams;
model->callback_trigger_hand = hid_ptt_trigger_camera_linux_skype;
model->callback_start_ptt = hid_ptt_start_ptt_linux_teams;
model->callback_stop_ptt = hid_ptt_stop_ptt_linux_teams;
break;
case HidPushToTalkAppIndexTeamSpeak:
model->callback_trigger_mute = hid_ptt_trigger_mute_linux_teamspeak;
model->callback_start_ptt = hid_ptt_start_ptt_linux_teamspeak;
model->callback_stop_ptt = hid_ptt_stop_ptt_linux_teamspeak;
break;
case HidPushToTalkAppIndexSignal:
model->callback_trigger_mute = hid_ptt_trigger_mute_signal;
model->callback_trigger_camera = hid_ptt_trigger_camera_signal;
model->callback_start_ptt = hid_ptt_trigger_mute_signal;
model->callback_stop_ptt = hid_ptt_trigger_mute_signal;
break;
case HidPushToTalkAppIndexSkype:
model->callback_trigger_mute = hid_ptt_trigger_mute_linux_skype;
model->callback_trigger_camera = hid_ptt_trigger_camera_linux_skype;
model->callback_start_ptt = hid_ptt_trigger_mute_linux_skype;
model->callback_stop_ptt = hid_ptt_trigger_mute_linux_skype;
break;
case HidPushToTalkAppIndexSlackCall:
model->callback_trigger_mute = hid_ptt_trigger_mute_slack_call;
model->callback_trigger_camera = hid_ptt_trigger_camera_slack_call;
model->callback_start_ptt = hid_ptt_trigger_mute_slack_call;
model->callback_stop_ptt = hid_ptt_trigger_mute_slack_call;
break;
case HidPushToTalkAppIndexSlackHubble:
model->callback_trigger_mute = hid_ptt_trigger_mute_linux_slack_hubble;
model->callback_start_ptt = hid_ptt_trigger_mute_linux_slack_hubble;
model->callback_stop_ptt = hid_ptt_trigger_mute_linux_slack_hubble;
break;
case HidPushToTalkAppIndexZoom:
model->callback_trigger_mute = hid_ptt_trigger_mute_linux_zoom;
model->callback_trigger_camera = hid_ptt_trigger_camera_linux_zoom;
model->callback_trigger_hand = hid_ptt_trigger_hand_zoom;
model->callback_start_ptt = hid_ptt_start_ptt_meet_zoom;
model->callback_stop_ptt = hid_ptt_stop_ptt_meet_zoom;
break;
case HidPushToTalkAppIndexWebex:
model->callback_trigger_mute = hid_ptt_trigger_mute_linux_skype;
model->callback_trigger_camera = hid_ptt_trigger_camera_webex;
model->callback_trigger_hand = hid_ptt_trigger_hand_linux_webex;
model->callback_start_ptt = hid_ptt_trigger_mute_linux_skype;
model->callback_stop_ptt = hid_ptt_trigger_mute_linux_skype;
break;
}
}
char *app_specific_help = "";
switch(appIndex) {
case HidPushToTalkAppIndexGoogleMeet:
app_specific_help =
"Google Meet:\n"
"This feature is off by default in your audio settings "
"and may not work for Windows users who use their screen "
"reader. In this situation, the spacebar performs a different action.\n\n"
;
break;
case HidPushToTalkAppIndexDiscord:
app_specific_help =
"Discord:\n"
"1. Under App Settings, click Voice & Video. Under Input Mode, "
"check the box next to Push to Talk.\n"
"2. Scroll down to SHORTCUT, click Record Keybinder.\n"
"3. Press PTT in the app to bind it."
"4. Go to Keybinds and assign mute button.\n\n"
;
break;
case HidPushToTalkAppIndexTeamSpeak:
app_specific_help =
"TeamSpeak:\n"
"To make keys working bind them in TeamSpeak settings.\n\n"
;
break;
case HidPushToTalkAppIndexTeams:
app_specific_help =
"Teams:\n"
"Go to Settings > Privacy. Make sure Keyboard shortcut to unmute is toggled on.\n\n"
;
break;
}
FuriString *msg = furi_string_alloc();
furi_string_cat_printf(msg,
"%sGeneral:\n"
"To operate properly flipper microphone "
"status must be in sync with your computer.\n"
"Hold > to change mic status.\n"
"Hold < to open this help.\n"
"Press BACK to switch mic on/off.\n"
"Hold 'o' for PTT mode (mic will be off once you release 'o')\n"
"Hold BACK to exit.", app_specific_help);
widget_add_text_scroll_element(hid_ptt->help, 0, 0, 128, 64, furi_string_get_cstr(msg));
furi_string_free(msg);
}, true);
view_dispatcher_switch_to_view(hid_ptt->hid->view_dispatcher, HidViewPushToTalk);
}
static void hid_ptt_draw_camera(Canvas* canvas, uint8_t x, uint8_t y) {
canvas_draw_icon(canvas, x + 7, y, &I_ButtonLeft_4x7);
canvas_draw_box(canvas, x, y, 7, 7);
}
static void hid_ptt_draw_text_centered(Canvas* canvas, uint8_t y, FuriString* str) {
FuriString* disp_str;
disp_str = furi_string_alloc_set(str);
elements_string_fit_width(canvas, disp_str, canvas_width(canvas));
uint8_t x_pos = (canvas_width(canvas) - canvas_string_width(canvas,furi_string_get_cstr(disp_str))) / 2;
canvas_draw_str(canvas,x_pos,y,furi_string_get_cstr(disp_str));
furi_string_free(disp_str);
}
static void hid_ptt_draw_callback(Canvas* canvas, void* context) {
furi_assert(context);
HidPushToTalkModel* model = context;
// Header
canvas_set_font(canvas, FontPrimary);
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);
}
}
// OS and App labels
canvas_set_font(canvas, FontSecondary);
hid_ptt_draw_text_centered(canvas, 73, model->app);
hid_ptt_draw_text_centered(canvas, 84, model->os);
// Help label
canvas_draw_icon(canvas, 0, 88, &I_Help_top_64x17);
canvas_draw_line(canvas, 4, 105, 4, 114);
canvas_draw_line(canvas, 63, 105, 63, 114);
canvas_draw_icon(canvas, 7, 107, &I_Hold_15x5);
canvas_draw_icon(canvas, 24, 105, &I_BtnLeft_9x9);
canvas_draw_icon(canvas, 34, 108, &I_for_help_27x5);
canvas_draw_icon(canvas, 0, 115, &I_Help_exit_64x9);
canvas_draw_icon(canvas, 24, 115, &I_BtnBackV_9x9);
const uint8_t x_1 = 0;
const uint8_t x_2 = x_1 + 19 + 4;
const uint8_t x_3 = x_1 + 19 * 2 + 8;
const uint8_t y_1 = 3;
const uint8_t y_2 = y_1 + 19;
const uint8_t y_3 = y_2 + 19;
// Up
canvas_draw_icon(canvas, x_2, y_1, &I_Button_18x18);
if(model->up_pressed) {
elements_slightly_rounded_box(canvas, x_2 + 3, y_1 + 2, 13, 13);
canvas_set_color(canvas, ColorWhite);
}
canvas_draw_icon(canvas, x_2 + 5, y_1 + 5, &I_Volup_8x6);
canvas_set_color(canvas, ColorBlack);
// Down
canvas_draw_icon(canvas, x_2, y_3, &I_Button_18x18);
if(model->down_pressed) {
elements_slightly_rounded_box(canvas, x_2 + 3, y_3 + 2, 13, 13);
canvas_set_color(canvas, ColorWhite);
}
canvas_draw_icon(canvas, x_2 + 6, y_3 + 5, &I_Voldwn_6x6);
canvas_set_color(canvas, ColorBlack);
// Left / Help
canvas_draw_icon(canvas, x_1, y_2, &I_Button_18x18);
if(model->left_pressed) {
elements_slightly_rounded_box(canvas, x_1 + 3, y_2 + 2, 13, 13);
canvas_set_color(canvas, ColorWhite);
}
if (model->callback_trigger_hand) {
canvas_draw_icon(canvas, x_1 + 4, y_2 + 3, &I_Hand_8x10);
} else {
canvas_draw_icon(canvas, x_1 + 2, y_2 + 1, &I_BrokenButton_15x15);
}
canvas_set_color(canvas, ColorBlack);
// Right / Camera
canvas_draw_icon(canvas, x_3, y_2, &I_Button_18x18);
if(model->right_pressed) {
elements_slightly_rounded_box(canvas, x_3 + 3, y_2 + 2, 13, 13);
canvas_set_color(canvas, ColorWhite);
}
if (model->callback_trigger_camera) {
hid_ptt_draw_camera(canvas, x_3 + 4, y_2 + 5);
} else {
canvas_draw_icon(canvas, x_3 + 2, y_2 + 1, &I_BrokenButton_15x15);
}
canvas_set_color(canvas, ColorBlack);
// Back / Mic
const uint8_t x_mic = x_3;
canvas_draw_icon(canvas, x_mic, 0, &I_RoundButtonUnpressed_16x16);
if (!(!model->muted || (model->ptt_pressed))) {
// show muted
if(model->mic_pressed) {
// canvas_draw_icon(canvas, x_mic + 1, 0, &I_MicrophonePressedCrossed_15x15);
canvas_draw_icon(canvas, x_mic, 0, &I_MicrophonePressedCrossedBtn_16x16);
} else {
canvas_draw_icon(canvas, x_mic, 0, &I_MicrophoneCrossed_16x16);
}
} else {
// show unmuted
if(model->mic_pressed) {
// canvas_draw_icon(canvas, x_mic + 1, 0, &I_MicrophonePressed_15x15);
canvas_draw_icon(canvas, x_mic, 0, &I_MicrophonePressedBtn_16x16);
} else {
canvas_draw_icon(canvas, x_mic + 5, 2, &I_Mic_7x11);
}
}
// Ok / PTT
const uint8_t x_ptt_margin = 4;
const uint8_t x_ptt_width = 17;
const uint8_t x_ptt = x_1 + 19;
canvas_draw_icon(canvas, x_ptt , y_2 , &I_BtnFrameLeft_3x18);
canvas_draw_icon(canvas, x_ptt + x_ptt_width + 3 + x_ptt_margin, y_2 , &I_BtnFrameRight_2x18);
canvas_draw_line(canvas, x_ptt + 3 , y_2 , x_ptt + x_ptt_width + 2 + x_ptt_margin, y_2);
canvas_draw_line(canvas, x_ptt + 3 , y_2 + 16, x_ptt + x_ptt_width + 2 + x_ptt_margin, y_2 + 16);
canvas_draw_line(canvas, x_ptt + 3 , y_2 + 17, x_ptt + x_ptt_width + 2 + x_ptt_margin, y_2 + 17);
if (model->ptt_pressed) {
elements_slightly_rounded_box(canvas, x_ptt + 3, y_2 + 2, x_ptt_width + x_ptt_margin, 13);
canvas_set_color(canvas, ColorWhite);
}
canvas_set_font(canvas, FontPrimary);
elements_multiline_text_aligned(canvas, x_ptt + 2 + x_ptt_margin / 2, y_2 + 13, AlignLeft, AlignBottom, "PTT");
canvas_set_font(canvas, FontSecondary);
canvas_set_color(canvas, ColorBlack);
}
static void hid_ptt_process(HidPushToTalk* hid_ptt, InputEvent* event) {
with_view_model(
hid_ptt->view,
HidPushToTalkModel * model,
{
if(event->type == InputTypePress && !model->ptt_pressed) {
if(event->key == InputKeyUp) {
model->up_pressed = true;
hid_hal_consumer_key_press(hid_ptt->hid, HID_CONSUMER_VOLUME_INCREMENT);
} else if(event->key == InputKeyDown) {
model->down_pressed = true;
hid_hal_consumer_key_press(hid_ptt->hid, HID_CONSUMER_VOLUME_DECREMENT);
} else if(event->key == InputKeyLeft) {
model->left_pressed = true;
} else if(event->key == InputKeyRight) {
model->right_pressed = true;
} else if(event->key == InputKeyOk) {
model->ptt_pressed = true;
if (!model->mic_pressed && model->muted){
model->callback_start_ptt ? model->callback_start_ptt(hid_ptt):0;
}
} else if(event->key == InputKeyBack) {
model->mic_pressed = true;
}
} else if(event->type == InputTypeRelease) {
if(event->key == InputKeyUp) {
model->up_pressed = false;
if (!model->ptt_pressed){
hid_hal_consumer_key_release(hid_ptt->hid, HID_CONSUMER_VOLUME_INCREMENT);
}
} else if(event->key == InputKeyDown) {
model->down_pressed = false;
if (!model->ptt_pressed){
hid_hal_consumer_key_release(hid_ptt->hid, HID_CONSUMER_VOLUME_DECREMENT);
}
} else if(event->key == InputKeyLeft) {
model->left_pressed = false;
} else if(event->key == InputKeyRight) {
model->right_pressed = false;
} else if(event->key == InputKeyOk) {
model->ptt_pressed = false;
if(!model->mic_pressed) {
if (model->muted) {
model->callback_stop_ptt ? model->callback_stop_ptt(hid_ptt):0;
} else {
model->callback_trigger_mute ? model->callback_trigger_mute(hid_ptt):0;
model->muted = true;
}
}
} else if(event->key == InputKeyBack) {
model->mic_pressed = false;
}
} else if(event->type == InputTypeShort && !model->ptt_pressed) {
if(event->key == InputKeyBack ) { // no changes if PTT is pressed
model->muted = !model->muted;
model->callback_trigger_mute ? model->callback_trigger_mute(hid_ptt):0;
} else if(event->key == InputKeyRight) {
model->callback_trigger_camera ? model->callback_trigger_camera(hid_ptt):0;
} else if(event->key == InputKeyLeft) {
model->callback_trigger_hand ? model->callback_trigger_hand(hid_ptt):0;
}
} else if(event->type == InputTypeLong && event->key == InputKeyRight) {
model->muted = !model->muted;
notification_message(hid_ptt->hid->notifications, &sequence_single_vibro);
} else if(event->type == InputTypeLong && event->key == InputKeyLeft) {
notification_message(hid_ptt->hid->notifications, &sequence_single_vibro);
model->left_pressed = false;
view_dispatcher_switch_to_view(hid_ptt->hid->view_dispatcher, HidViewPushToTalkHelp);
}
//LED
if (!model->muted || (model->ptt_pressed)) {
notification_message(hid_ptt->hid->notifications, &sequence_set_red_255);
} else {
notification_message(hid_ptt->hid->notifications, &sequence_reset_red);
}
},
true);
}
static bool hid_ptt_input_callback(InputEvent* event, void* context) {
furi_assert(context);
HidPushToTalk* hid_ptt = context;
bool consumed = false;
if(event->type == InputTypeLong && event->key == InputKeyBack) {
hid_hal_keyboard_release_all(hid_ptt->hid);
notification_message(hid_ptt->hid->notifications, &sequence_double_vibro);
widget_reset(hid_ptt->help);
} else {
consumed = true;
hid_ptt_process(hid_ptt, event);
}
return consumed;
}
View* hid_ptt_get_view(HidPushToTalk* hid_ptt) {
furi_assert(hid_ptt);
return hid_ptt->view;
}
static uint32_t hid_ptt_view(void* context) {
UNUSED(context);
return HidViewPushToTalk;
}
HidPushToTalk* hid_ptt_alloc(Hid* hid) {
HidPushToTalk* hid_ptt = malloc(sizeof(HidPushToTalk));
hid_ptt->hid = hid;
hid_ptt->view = view_alloc();
view_set_context(hid_ptt->view, hid_ptt);
view_allocate_model(hid_ptt->view, ViewModelTypeLocking, sizeof(HidPushToTalkModel));
view_set_draw_callback(hid_ptt->view, hid_ptt_draw_callback);
view_set_input_callback(hid_ptt->view, hid_ptt_input_callback);
view_set_orientation(hid_ptt->view, ViewOrientationVerticalFlip);
with_view_model(
hid_ptt->view, HidPushToTalkModel * model, {
model->transport = hid->transport;
model->muted = true; // assume we're muted
model->os = furi_string_alloc();
model->app = furi_string_alloc();
}, true);
FURI_LOG_I(TAG, "Calling adding list");
ptt_menu_add_list(hid->hid_ptt_menu, "macOS", HidPushToTalkMacOS);
ptt_menu_add_list(hid->hid_ptt_menu, "Win/Linux", HidPushToTalkLinux);
ptt_menu_add_item_to_list(hid->hid_ptt_menu, HidPushToTalkMacOS, "Google Meet", HidPushToTalkAppIndexGoogleMeet, hid_ptt_menu_callback, hid_ptt);
ptt_menu_add_item_to_list(hid->hid_ptt_menu, HidPushToTalkLinux, "Google Meet", HidPushToTalkAppIndexGoogleMeet, hid_ptt_menu_callback, hid_ptt);
ptt_menu_add_item_to_list(hid->hid_ptt_menu, HidPushToTalkMacOS, "Google Hangouts", HidPushToTalkAppIndexGoogleHangouts, hid_ptt_menu_callback, hid_ptt);
ptt_menu_add_item_to_list(hid->hid_ptt_menu, HidPushToTalkLinux, "Google Hangouts", HidPushToTalkAppIndexGoogleHangouts, hid_ptt_menu_callback, hid_ptt);
ptt_menu_add_item_to_list(hid->hid_ptt_menu, HidPushToTalkMacOS, "Discord", HidPushToTalkAppIndexDiscord, hid_ptt_menu_callback, hid_ptt);
ptt_menu_add_item_to_list(hid->hid_ptt_menu, HidPushToTalkLinux, "Discord", HidPushToTalkAppIndexDiscord, hid_ptt_menu_callback, hid_ptt);
ptt_menu_add_item_to_list(hid->hid_ptt_menu, HidPushToTalkMacOS, "FaceTime", HidPushToTalkAppIndexFaceTime, hid_ptt_menu_callback, hid_ptt);
ptt_menu_add_item_to_list(hid->hid_ptt_menu, HidPushToTalkMacOS, "Jamulus", HidPushToTalkAppIndexJamulus, hid_ptt_menu_callback, hid_ptt);
ptt_menu_add_item_to_list(hid->hid_ptt_menu, HidPushToTalkLinux, "Jamulus", HidPushToTalkAppIndexJamulus, hid_ptt_menu_callback, hid_ptt);
ptt_menu_add_item_to_list(hid->hid_ptt_menu, HidPushToTalkMacOS, "Signal", HidPushToTalkAppIndexSignal, hid_ptt_menu_callback, hid_ptt);
ptt_menu_add_item_to_list(hid->hid_ptt_menu, HidPushToTalkLinux, "Signal", HidPushToTalkAppIndexSignal, hid_ptt_menu_callback, hid_ptt);
ptt_menu_add_item_to_list(hid->hid_ptt_menu, HidPushToTalkMacOS, "Skype", HidPushToTalkAppIndexSkype, hid_ptt_menu_callback, hid_ptt);
ptt_menu_add_item_to_list(hid->hid_ptt_menu, HidPushToTalkLinux, "Skype", HidPushToTalkAppIndexSkype, hid_ptt_menu_callback, hid_ptt);
ptt_menu_add_item_to_list(hid->hid_ptt_menu, HidPushToTalkMacOS, "Slack Call", HidPushToTalkAppIndexSlackCall, hid_ptt_menu_callback, hid_ptt);
ptt_menu_add_item_to_list(hid->hid_ptt_menu, HidPushToTalkLinux, "Slack Call", HidPushToTalkAppIndexSlackCall, hid_ptt_menu_callback, hid_ptt);
ptt_menu_add_item_to_list(hid->hid_ptt_menu, HidPushToTalkMacOS, "Slack Hubble", HidPushToTalkAppIndexSlackHubble, hid_ptt_menu_callback, hid_ptt);
ptt_menu_add_item_to_list(hid->hid_ptt_menu, HidPushToTalkLinux, "Slack Hubble", HidPushToTalkAppIndexSlackHubble, hid_ptt_menu_callback, hid_ptt);
ptt_menu_add_item_to_list(hid->hid_ptt_menu, HidPushToTalkMacOS, "TeamSpeak", HidPushToTalkAppIndexTeamSpeak, hid_ptt_menu_callback, hid_ptt);
ptt_menu_add_item_to_list(hid->hid_ptt_menu, HidPushToTalkLinux, "TeamSpeak", HidPushToTalkAppIndexTeamSpeak, hid_ptt_menu_callback, hid_ptt);
ptt_menu_add_item_to_list(hid->hid_ptt_menu, HidPushToTalkMacOS, "Teams", HidPushToTalkAppIndexTeams, hid_ptt_menu_callback, hid_ptt);
ptt_menu_add_item_to_list(hid->hid_ptt_menu, HidPushToTalkLinux, "Teams", HidPushToTalkAppIndexTeams, hid_ptt_menu_callback, hid_ptt);
ptt_menu_add_item_to_list(hid->hid_ptt_menu, HidPushToTalkMacOS, "Zoom", HidPushToTalkAppIndexZoom, hid_ptt_menu_callback, hid_ptt);
ptt_menu_add_item_to_list(hid->hid_ptt_menu, HidPushToTalkLinux, "Zoom", HidPushToTalkAppIndexZoom, hid_ptt_menu_callback, hid_ptt);
ptt_menu_add_item_to_list(hid->hid_ptt_menu, HidPushToTalkMacOS, "Webex", HidPushToTalkAppIndexWebex, hid_ptt_menu_callback, hid_ptt);
ptt_menu_add_item_to_list(hid->hid_ptt_menu, HidPushToTalkLinux, "Webex", HidPushToTalkAppIndexWebex, hid_ptt_menu_callback, hid_ptt);
hid_ptt->help = widget_alloc();
view_set_previous_callback(widget_get_view(hid_ptt->help), hid_ptt_view);
view_dispatcher_add_view(hid->view_dispatcher, HidViewPushToTalkHelp, widget_get_view(hid_ptt->help));
return hid_ptt;
}
void hid_ptt_free(HidPushToTalk* hid_ptt) {
furi_assert(hid_ptt);
notification_message(hid_ptt->hid->notifications, &sequence_reset_red);
with_view_model(
hid_ptt->view, HidPushToTalkModel * model, {
furi_string_free(model->os);
furi_string_free(model->app);
}, true);
view_dispatcher_remove_view(hid_ptt->hid->view_dispatcher, HidViewPushToTalkHelp);
widget_free(hid_ptt->help);
view_free(hid_ptt->view);
free(hid_ptt);
}
void hid_ptt_set_connected_status(HidPushToTalk* hid_ptt, bool connected) {
furi_assert(hid_ptt);
with_view_model(
hid_ptt->view, HidPushToTalkModel * model, {
if (!connected && model->connected) {
notification_message(hid_ptt->hid->notifications, &sequence_single_vibro);
}
model->connected = connected;
}, true);
}

View file

@ -0,0 +1,19 @@
#pragma once
#include <gui/view.h>
typedef struct Hid Hid;
typedef struct HidPushToTalk HidPushToTalk;
HidPushToTalk* hid_ptt_alloc(Hid* bt_hid);
void hid_ptt_free(HidPushToTalk* hid_ptt);
View* hid_ptt_get_view(HidPushToTalk* hid_ptt);
void hid_ptt_set_connected_status(HidPushToTalk* hid_ptt, bool connected);
enum HidPushToTalkOSes {
HidPushToTalkMacOS,
HidPushToTalkLinux,
};

View file

@ -0,0 +1,413 @@
#include "hid_ptt_menu.h"
#include "hid_ptt.h"
#include <gui/elements.h>
#include <m-array.h>
#include "../hid.h"
#include "../views.h"
#define TAG "HidPushToTalkMenu"
struct HidPushToTalkMenu {
View* view;
Hid* hid;
};
typedef struct {
FuriString* label;
uint32_t index;
PushToTalkMenuItemCallback callback;
void* callback_context;
} PushToTalkMenuItem;
// Menu item
static void PushToTalkMenuItem_init(PushToTalkMenuItem* item) {
item->label = furi_string_alloc();
item->index = 0;
}
static void PushToTalkMenuItem_init_set(PushToTalkMenuItem* item, const PushToTalkMenuItem* src) {
item->label = furi_string_alloc_set(src->label);
item->index = src->index;
}
static void PushToTalkMenuItem_set(PushToTalkMenuItem* item, const PushToTalkMenuItem* src) {
furi_string_set(item->label, src->label);
item->index = src->index;
}
static void PushToTalkMenuItem_clear(PushToTalkMenuItem* item) {
furi_string_free(item->label);
}
ARRAY_DEF(
PushToTalkMenuItemArray,
PushToTalkMenuItem,
(INIT(API_2(PushToTalkMenuItem_init)),
SET(API_6(PushToTalkMenuItem_set)),
INIT_SET(API_6(PushToTalkMenuItem_init_set)),
CLEAR(API_2(PushToTalkMenuItem_clear))))
// Menu list (horisontal, 2d array)
typedef struct {
FuriString* label;
uint32_t index;
PushToTalkMenuItemArray_t items;
} PushToTalkMenuList;
typedef struct {
size_t list_position;
size_t position;
size_t window_position;
PushToTalkMenuList *lists;
int lists_count;
} HidPushToTalkMenuModel;
static void hid_ptt_menu_draw_list(Canvas* canvas, void* context, const PushToTalkMenuItemArray_t items) {
furi_assert(context);
HidPushToTalkMenuModel* model = context;
const uint8_t item_height = 16;
uint8_t item_width = canvas_width(canvas) - 5;
canvas_set_font(canvas, FontSecondary);
size_t position = 0;
PushToTalkMenuItemArray_it_t it;
for(PushToTalkMenuItemArray_it(it, items); !PushToTalkMenuItemArray_end_p(it); PushToTalkMenuItemArray_next(it)) {
const size_t item_position = position - model->window_position;
const size_t items_on_screen = 3;
uint8_t y_offset = 16;
if(item_position < items_on_screen) {
if(position == model->position) {
canvas_set_color(canvas, ColorBlack);
elements_slightly_rounded_box(
canvas,
0,
y_offset + (item_position * item_height) + 1,
item_width,
item_height - 2);
canvas_set_color(canvas, ColorWhite);
} else {
canvas_set_color(canvas, ColorBlack);
}
FuriString* disp_str;
disp_str = furi_string_alloc_set(PushToTalkMenuItemArray_cref(it)->label);
elements_string_fit_width(canvas, disp_str, item_width - (6 * 2));
canvas_draw_str(
canvas,
6,
y_offset + (item_position * item_height) + item_height - 4,
furi_string_get_cstr(disp_str));
furi_string_free(disp_str);
}
position++;
}
elements_scrollbar_pos(canvas, 128 , 17, 46, model->position, PushToTalkMenuItemArray_size(items));
}
PushToTalkMenuList * hid_ptt_menu_get_list_at_index(
void* context,
uint32_t index) {
furi_assert(context);
HidPushToTalkMenuModel* model = context;
for (int i = 0; i < model->lists_count; i++) {
PushToTalkMenuList* list = &model->lists[i];
if(index == list->index) {
return list;
}
}
return NULL;
}
static void hid_ptt_menu_draw_callback(Canvas* canvas, void* context) {
furi_assert(context);
HidPushToTalkMenuModel* model = context;
if (model->lists_count == 0){
return;
}
uint8_t item_width = canvas_width(canvas) - 5;
canvas_clear(canvas);
canvas_set_font(canvas, FontPrimary);
canvas_draw_str(canvas, 4, 11, "<");
canvas_draw_str(canvas, 121, 11, ">");
PushToTalkMenuList* list = &model->lists[model->list_position];
FuriString* disp_str;
disp_str = furi_string_alloc_set(list->label);
elements_string_fit_width(canvas, disp_str, item_width - (6 * 2));
uint8_t x_pos = (canvas_width(canvas) - canvas_string_width(canvas,furi_string_get_cstr(disp_str))) / 2;
canvas_draw_str(canvas,x_pos,11,furi_string_get_cstr(disp_str));
furi_string_free(disp_str);
canvas_set_font(canvas, FontSecondary);
hid_ptt_menu_draw_list(
canvas,
context,
list->items
);
}
void ptt_menu_add_list(
HidPushToTalkMenu* hid_ptt_menu,
const char* label,
uint32_t index) {
furi_assert(label);
furi_assert(hid_ptt_menu);
with_view_model(
hid_ptt_menu->view, HidPushToTalkMenuModel * model,
{
if (model->lists_count == 0) {
model->lists = (PushToTalkMenuList *)malloc(sizeof(PushToTalkMenuList));
} else {
model->lists = (PushToTalkMenuList *)realloc(model->lists, (model->lists_count + 1) * sizeof(PushToTalkMenuList));
}
if (model->lists == NULL) {
FURI_LOG_E(TAG, "Memory reallocation failed (%i)", model->lists_count);
return;
}
PushToTalkMenuList* list = &model->lists[model->lists_count];
PushToTalkMenuItemArray_init(list->items);
list->label = furi_string_alloc_set(label);
list->index = index;
model->lists_count += 1;
},
true);
}
void ptt_menu_add_item_to_list(
HidPushToTalkMenu* hid_ptt_menu,
uint32_t list_index,
const char* label,
uint32_t index,
PushToTalkMenuItemCallback callback,
void* callback_context) {
PushToTalkMenuItem* item = NULL;
furi_assert(label);
furi_assert(hid_ptt_menu);
UNUSED(list_index);
with_view_model(
hid_ptt_menu->view, HidPushToTalkMenuModel * model,
{
PushToTalkMenuList* list = hid_ptt_menu_get_list_at_index(model, list_index);
if (list == NULL){
FURI_LOG_E(TAG, "Adding item %s to unknown index %li", label, list_index);
return;
}
item = PushToTalkMenuItemArray_push_new(list->items);
furi_string_set_str(item->label, label);
item->index = index;
item->callback = callback;
item->callback_context = callback_context;
},
true);
}
void ptt_menu_shift_list(HidPushToTalkMenu* hid_ptt_menu, int shift){
size_t new_position = 0;
uint32_t index = 0;
with_view_model(
hid_ptt_menu->view, HidPushToTalkMenuModel * model,
{
int new_list_position = (short) model->list_position + shift;
if (new_list_position >= model->lists_count) {
new_list_position = 0;
} else if (new_list_position < 0) {
new_list_position = model->lists_count - 1;
}
PushToTalkMenuList* list = &model->lists[model->list_position];
PushToTalkMenuList* new_list = &model->lists[new_list_position];
size_t new_window_position = model->window_position;
const size_t items_size = PushToTalkMenuItemArray_size(new_list->items);
size_t position = 0;
// Find item index from current list
PushToTalkMenuItemArray_it_t it;
for(PushToTalkMenuItemArray_it(it, list->items); !PushToTalkMenuItemArray_end_p(it); PushToTalkMenuItemArray_next(it)) {
if (position == model->position){
index = PushToTalkMenuItemArray_cref(it)->index;
break;
}
position++;
}
// Try to find item with the same index in a new list
position = 0;
bool item_exists_in_new_list = false;
for(PushToTalkMenuItemArray_it(it, new_list->items); !PushToTalkMenuItemArray_end_p(it); PushToTalkMenuItemArray_next(it)) {
if (PushToTalkMenuItemArray_cref(it)->index == index) {
item_exists_in_new_list = true;
new_position = position;
break;
}
position++;
}
// This list item is not presented in a new list, let's try to keep position as is.
// If it's out of range for the new list set it to the end
if (!item_exists_in_new_list) {
new_position = items_size - 1 < model->position ? items_size - 1 : model->position;
}
// Tune window position. As we have 3 items on screen, keep focus centered
const size_t items_on_screen = 3;
if (new_position >= items_size - 1) {
if (items_size < items_on_screen + 1) {
new_window_position = 0;
} else {
new_window_position = items_size - items_on_screen;
}
} else if (new_position < items_on_screen - 1) {
new_window_position = 0;
} else {
new_window_position = new_position - 1;
}
model->list_position = new_list_position;
model->position = new_position;
model->window_position = new_window_position;
},
true);
}
void ptt_menu_process_up(HidPushToTalkMenu* hid_ptt_menu) {
with_view_model(
hid_ptt_menu->view, HidPushToTalkMenuModel * model,
{
PushToTalkMenuList* list = &model->lists[model->list_position];
const size_t items_on_screen = 3;
const size_t items_size = PushToTalkMenuItemArray_size(list->items);
if(model->position > 0) {
model->position--;
if((model->position == model->window_position) && (model->window_position > 0)) {
model->window_position--;
}
} else {
model->position = items_size - 1;
if(model->position > items_on_screen - 1) {
model->window_position = model->position - (items_on_screen - 1);
}
}
},
true);
}
void ptt_menu_process_down(HidPushToTalkMenu* hid_ptt_menu) {
with_view_model(
hid_ptt_menu->view, HidPushToTalkMenuModel * model,
{
PushToTalkMenuList* list = &model->lists[model->list_position];
const size_t items_on_screen = 3;
const size_t items_size = PushToTalkMenuItemArray_size(list->items);
if(model->position < items_size - 1) {
model->position++;
if((model->position - model->window_position > items_on_screen - 2) &&
(model->window_position < items_size - items_on_screen)) {
model->window_position++;
}
} else {
model->position = 0;
model->window_position = 0;
}
},
true);
}
void ptt_menu_process_ok(HidPushToTalkMenu* hid_ptt_menu) {
PushToTalkMenuList* list = NULL;
PushToTalkMenuItem* item = NULL;
with_view_model(
hid_ptt_menu->view, HidPushToTalkMenuModel * model,
{
list = &model->lists[model->list_position];
const size_t items_size = PushToTalkMenuItemArray_size(list->items);
if(model->position < items_size) {
item = PushToTalkMenuItemArray_get(list->items, model->position);
}
},
true);
if(item && list && item->callback) {
item->callback(item->callback_context, list->index, list->label, item->index, item->label);
}
}
static bool hid_ptt_menu_input_callback(InputEvent* event, void* context) {
furi_assert(context);
HidPushToTalkMenu* hid_ptt_menu = context;
bool consumed = false;
if(event->type == InputTypeShort) {
switch(event->key) {
case InputKeyUp:
consumed = true;
ptt_menu_process_up(hid_ptt_menu);
break;
case InputKeyDown:
consumed = true;
ptt_menu_process_down(hid_ptt_menu);
break;
case InputKeyLeft:
consumed = true;
ptt_menu_shift_list(hid_ptt_menu, -1);
break;
case InputKeyRight:
consumed = true;
ptt_menu_shift_list(hid_ptt_menu, +1);
break;
case InputKeyOk:
consumed = true;
ptt_menu_process_ok(hid_ptt_menu);
break;
default:
break;
}
} else if(event->type == InputTypeRepeat) {
if(event->key == InputKeyUp) {
consumed = true;
ptt_menu_process_up(hid_ptt_menu);
} else if(event->key == InputKeyDown) {
consumed = true;
ptt_menu_process_down(hid_ptt_menu);
}
}
return consumed;
}
View* hid_ptt_menu_get_view(HidPushToTalkMenu* hid_ptt_menu) {
furi_assert(hid_ptt_menu);
return hid_ptt_menu->view;
}
HidPushToTalkMenu* hid_ptt_menu_alloc(Hid* hid) {
HidPushToTalkMenu* hid_ptt_menu = malloc(sizeof(HidPushToTalkMenu));
hid_ptt_menu->hid = hid;
hid_ptt_menu->view = view_alloc();
view_set_context(hid_ptt_menu->view, hid_ptt_menu);
view_allocate_model(hid_ptt_menu->view, ViewModelTypeLocking, sizeof(HidPushToTalkMenuModel));
view_set_draw_callback(hid_ptt_menu->view, hid_ptt_menu_draw_callback);
view_set_input_callback(hid_ptt_menu->view, hid_ptt_menu_input_callback);
with_view_model(
hid_ptt_menu->view, HidPushToTalkMenuModel * model, {
model->lists_count = 0;
model->position = 0;
model->window_position = 0;
}, true);
return hid_ptt_menu;
}
void hid_ptt_menu_free(HidPushToTalkMenu* hid_ptt_menu) {
furi_assert(hid_ptt_menu);
with_view_model(
hid_ptt_menu->view, HidPushToTalkMenuModel * model, {
for (int i = 0; i < model->lists_count; i++) {
PushToTalkMenuItemArray_clear(model->lists[i].items);
furi_string_free(model->lists[i].label);
}
free(model->lists);
}, true);
view_free(hid_ptt_menu->view);
free(hid_ptt_menu);
}

View file

@ -0,0 +1,27 @@
#pragma once
#include <gui/view.h>
typedef struct Hid Hid;
typedef struct HidPushToTalkMenu HidPushToTalkMenu;
typedef void (*PushToTalkMenuItemCallback)(void* context, uint32_t listIndex, FuriString* listLabel, uint32_t itemIndex, FuriString* itemLabel );
HidPushToTalkMenu* hid_ptt_menu_alloc(Hid* bt_hid);
void hid_ptt_menu_free(HidPushToTalkMenu* hid_ptt_menu);
View* hid_ptt_menu_get_view(HidPushToTalkMenu* hid_ptt_menu);
void ptt_menu_add_item_to_list(
HidPushToTalkMenu* hid_ptt_menu,
uint32_t list_index,
const char* label,
uint32_t index,
PushToTalkMenuItemCallback callback,
void* callback_context);
void ptt_menu_add_list(
HidPushToTalkMenu* hid_ptt_menu,
const char* label,
uint32_t index);

View file

@ -0,0 +1,271 @@
#include "hid_tikshorts.h"
#include "../hid.h"
#include <gui/elements.h>
#include "hid_icons.h"
#define TAG "HidTikShorts"
struct HidTikShorts {
View* view;
Hid* hid;
};
typedef struct {
bool left_pressed;
bool up_pressed;
bool right_pressed;
bool down_pressed;
bool ok_pressed;
bool connected;
bool is_cursor_set;
bool back_mouse_pressed;
HidTransport transport;
} HidTikShortsModel;
static void hid_tikshorts_draw_callback(Canvas* canvas, void* context) {
furi_assert(context);
HidTikShortsModel* 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);
elements_multiline_text_aligned(canvas, 17, 3, AlignLeft, AlignTop, "TikTok /");
elements_multiline_text_aligned(canvas, 3, 18, AlignLeft, AlignTop, "YT Shorts");
canvas_set_font(canvas, FontSecondary);
// Keypad circles
canvas_draw_icon(canvas, 58, 3, &I_OutCircles_70x51);
// Pause
if(model->back_mouse_pressed) {
canvas_set_bitmap_mode(canvas, 1);
canvas_draw_icon(canvas, 107, 33, &I_Pressed_Button_19x19);
canvas_set_bitmap_mode(canvas, 0);
canvas_set_color(canvas, ColorWhite);
}
canvas_draw_icon(canvas, 113, 37, &I_Pause_icon_9x9);
canvas_set_color(canvas, ColorBlack);
// Up
if(model->up_pressed) {
canvas_set_bitmap_mode(canvas, 1);
canvas_draw_icon(canvas, 68, 6, &I_S_UP_31x15);
canvas_set_bitmap_mode(canvas, 0);
canvas_set_color(canvas, ColorWhite);
}
canvas_draw_icon(canvas, 80, 8, &I_Arr_up_7x9);
canvas_set_color(canvas, ColorBlack);
// Down
if(model->down_pressed) {
canvas_set_bitmap_mode(canvas, 1);
canvas_draw_icon(canvas, 68, 36, &I_S_DOWN_31x15);
canvas_set_bitmap_mode(canvas, 0);
canvas_set_color(canvas, ColorWhite);
}
canvas_draw_icon(canvas, 80, 40, &I_Arr_dwn_7x9);
canvas_set_color(canvas, ColorBlack);
// Left
if(model->left_pressed) {
canvas_set_bitmap_mode(canvas, 1);
canvas_draw_icon(canvas, 61, 13, &I_S_LEFT_15x31);
canvas_set_bitmap_mode(canvas, 0);
canvas_set_color(canvas, ColorWhite);
}
canvas_draw_icon(canvas, 64, 25, &I_Voldwn_6x6);
canvas_set_color(canvas, ColorBlack);
// Right
if(model->right_pressed) {
canvas_set_bitmap_mode(canvas, 1);
canvas_draw_icon(canvas, 91, 13, &I_S_RIGHT_15x31);
canvas_set_bitmap_mode(canvas, 0);
canvas_set_color(canvas, ColorWhite);
}
canvas_draw_icon(canvas, 95, 25, &I_Volup_8x6);
canvas_set_color(canvas, ColorBlack);
// Ok
if(model->ok_pressed) {
canvas_set_bitmap_mode(canvas, 1);
canvas_draw_icon(canvas, 74, 19, &I_Pressed_Button_19x19);
canvas_set_bitmap_mode(canvas, 0);
canvas_set_color(canvas, ColorWhite);
}
canvas_draw_icon(canvas, 78, 25, &I_Like_def_11x9);
canvas_set_color(canvas, ColorBlack);
// 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");
}
static void hid_tikshorts_reset_cursor(HidTikShorts* hid_tikshorts) {
// 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_tikshorts->hid, -127, -127);
furi_delay_ms(50);
}
// Move cursor from the corner
hid_hal_mouse_move(hid_tikshorts->hid, 20, 120);
furi_delay_ms(50);
}
static void hid_tikshorts_process_press(
HidTikShorts* hid_tikshorts,
HidTikShortsModel* 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_tikshorts->hid, HID_CONSUMER_VOLUME_DECREMENT);
} else if(event->key == InputKeyRight) {
model->right_pressed = true;
hid_hal_consumer_key_press(hid_tikshorts->hid, HID_CONSUMER_VOLUME_INCREMENT);
} else if(event->key == InputKeyOk) {
model->ok_pressed = true;
} else if(event->key == InputKeyBack) {
model->back_mouse_pressed = true;
}
}
static void hid_tikshorts_process_release(
HidTikShorts* hid_tikshorts,
HidTikShortsModel* 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_tikshorts->hid, HID_CONSUMER_VOLUME_DECREMENT);
} else if(event->key == InputKeyRight) {
model->right_pressed = false;
hid_hal_consumer_key_release(hid_tikshorts->hid, HID_CONSUMER_VOLUME_INCREMENT);
} else if(event->key == InputKeyOk) {
model->ok_pressed = false;
} else if(event->key == InputKeyBack) {
model->back_mouse_pressed = false;
}
}
static bool hid_tikshorts_input_callback(InputEvent* event, void* context) {
furi_assert(context);
HidTikShorts* hid_tikshorts = context;
bool consumed = false;
with_view_model(
hid_tikshorts->view,
HidTikShortsModel * model,
{
if(event->type == InputTypePress) {
hid_tikshorts_process_press(hid_tikshorts, model, event);
if(model->connected && !model->is_cursor_set) {
hid_tikshorts_reset_cursor(hid_tikshorts);
model->is_cursor_set = true;
}
consumed = true;
} else if(event->type == InputTypeRelease) {
hid_tikshorts_process_release(hid_tikshorts, model, event);
consumed = true;
} else if(event->type == InputTypeShort) {
if(event->key == InputKeyOk) {
hid_hal_mouse_press(hid_tikshorts->hid, HID_MOUSE_BTN_LEFT);
furi_delay_ms(25);
hid_hal_mouse_release(hid_tikshorts->hid, HID_MOUSE_BTN_LEFT);
furi_delay_ms(100);
hid_hal_mouse_press(hid_tikshorts->hid, HID_MOUSE_BTN_LEFT);
furi_delay_ms(25);
hid_hal_mouse_release(hid_tikshorts->hid, HID_MOUSE_BTN_LEFT);
consumed = true;
} else if(event->key == InputKeyDown) {
// Swipe to next video
hid_hal_mouse_scroll(hid_tikshorts->hid, 6);
hid_hal_mouse_scroll(hid_tikshorts->hid, 8);
hid_hal_mouse_scroll(hid_tikshorts->hid, 10);
hid_hal_mouse_scroll(hid_tikshorts->hid, 8);
hid_hal_mouse_scroll(hid_tikshorts->hid, 6);
consumed = true;
} else if(event->key == InputKeyUp) {
// Swipe to previous video
hid_hal_mouse_scroll(hid_tikshorts->hid, -6);
hid_hal_mouse_scroll(hid_tikshorts->hid, -8);
hid_hal_mouse_scroll(hid_tikshorts->hid, -10);
hid_hal_mouse_scroll(hid_tikshorts->hid, -8);
hid_hal_mouse_scroll(hid_tikshorts->hid, -6);
consumed = true;
} else if(event->key == InputKeyBack) {
// Pause
hid_hal_mouse_press(hid_tikshorts->hid, HID_MOUSE_BTN_LEFT);
furi_delay_ms(50);
hid_hal_mouse_release(hid_tikshorts->hid, HID_MOUSE_BTN_LEFT);
consumed = true;
}
} else if(event->type == InputTypeLong) {
if(event->key == InputKeyBack) {
hid_hal_consumer_key_release_all(hid_tikshorts->hid);
model->is_cursor_set = false;
consumed = false;
}
}
},
true);
return consumed;
}
HidTikShorts* hid_tikshorts_alloc(Hid* bt_hid) {
HidTikShorts* hid_tikshorts = malloc(sizeof(HidTikShorts));
hid_tikshorts->hid = bt_hid;
hid_tikshorts->view = view_alloc();
view_set_context(hid_tikshorts->view, hid_tikshorts);
view_allocate_model(hid_tikshorts->view, ViewModelTypeLocking, sizeof(HidTikShortsModel));
view_set_draw_callback(hid_tikshorts->view, hid_tikshorts_draw_callback);
view_set_input_callback(hid_tikshorts->view, hid_tikshorts_input_callback);
with_view_model(
hid_tikshorts->view,
HidTikShortsModel * model,
{ model->transport = bt_hid->transport; },
true);
return hid_tikshorts;
}
void hid_tikshorts_free(HidTikShorts* hid_tikshorts) {
furi_assert(hid_tikshorts);
view_free(hid_tikshorts->view);
free(hid_tikshorts);
}
View* hid_tikshorts_get_view(HidTikShorts* hid_tikshorts) {
furi_assert(hid_tikshorts);
return hid_tikshorts->view;
}
void hid_tikshorts_set_connected_status(HidTikShorts* hid_tikshorts, bool connected) {
furi_assert(hid_tikshorts);
with_view_model(
hid_tikshorts->view,
HidTikShortsModel * 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 HidTikShorts HidTikShorts;
HidTikShorts* hid_tikshorts_alloc(Hid* bt_hid);
void hid_tikshorts_free(HidTikShorts* hid_tikshorts);
View* hid_tikshorts_get_view(HidTikShorts* hid_tikshorts);
void hid_tikshorts_set_connected_status(HidTikShorts* hid_tikshorts, bool connected);

View file

@ -0,0 +1,11 @@
App(
appid="snake",
name="Snake Game",
apptype=FlipperAppType.EXTERNAL,
entry_point="snake_game_app",
requires=["gui"],
stack_size=1 * 1024,
order=30,
fap_icon="snake_10px.png",
fap_category="Games",
)

Binary file not shown.

After

Width:  |  Height:  |  Size: 158 B

View file

@ -0,0 +1,409 @@
#include <furi.h>
#include <gui/gui.h>
#include <input/input.h>
#include <stdlib.h>
#include <dolphin/dolphin.h>
#include <notification/notification.h>
#include <notification/notification_messages.h>
typedef struct {
// +-----x
// |
// |
// y
uint8_t x;
uint8_t y;
} Point;
typedef enum {
GameStateLife,
// https://melmagazine.com/en-us/story/snake-nokia-6110-oral-history-taneli-armanto
// Armanto: While testing the early versions of the game, I noticed it was hard
// to control the snake upon getting close to and edge but not crashing — especially
// in the highest speed levels. I wanted the highest level to be as fast as I could
// possibly make the device "run," but on the other hand, I wanted to be friendly
// and help the player manage that level. Otherwise it might not be fun to play. So
// I implemented a little delay. A few milliseconds of extra time right before
// the player crashes, during which she can still change the directions. And if
// she does, the game continues.
GameStateLastChance,
GameStateGameOver,
} GameState;
// Note: do not change without purpose. Current values are used in smart
// orthogonality calculation in `snake_game_get_turn_snake`.
typedef enum {
DirectionUp,
DirectionRight,
DirectionDown,
DirectionLeft,
} Direction;
#define MAX_SNAKE_LEN 128 * 64 / 4
typedef struct {
Point points[MAX_SNAKE_LEN];
uint16_t len;
Direction currentMovement;
Direction nextMovement; // if backward of currentMovement, ignore
Point fruit;
GameState state;
FuriMutex* mutex;
} SnakeState;
typedef enum {
EventTypeTick,
EventTypeKey,
} EventType;
typedef struct {
EventType type;
InputEvent input;
} SnakeEvent;
const NotificationSequence sequence_fail = {
&message_vibro_on,
&message_note_ds4,
&message_delay_10,
&message_sound_off,
&message_delay_10,
&message_note_ds4,
&message_delay_10,
&message_sound_off,
&message_delay_10,
&message_note_ds4,
&message_delay_10,
&message_sound_off,
&message_delay_10,
&message_vibro_off,
NULL,
};
const NotificationSequence sequence_eat = {
&message_note_c7,
&message_delay_50,
&message_sound_off,
NULL,
};
static void snake_game_render_callback(Canvas* const canvas, void* ctx) {
furi_assert(ctx);
const SnakeState* snake_state = ctx;
furi_mutex_acquire(snake_state->mutex, FuriWaitForever);
// Frame
canvas_draw_frame(canvas, 0, 0, 128, 64);
// Fruit
Point f = snake_state->fruit;
f.x = f.x * 4 + 1;
f.y = f.y * 4 + 1;
canvas_draw_rframe(canvas, f.x, f.y, 6, 6, 2);
// Snake
for(uint16_t i = 0; i < snake_state->len; i++) {
Point p = snake_state->points[i];
p.x = p.x * 4 + 2;
p.y = p.y * 4 + 2;
canvas_draw_box(canvas, p.x, p.y, 4, 4);
}
// Game Over banner
if(snake_state->state == GameStateGameOver) {
// Screen is 128x64 px
canvas_set_color(canvas, ColorWhite);
canvas_draw_box(canvas, 34, 20, 62, 24);
canvas_set_color(canvas, ColorBlack);
canvas_draw_frame(canvas, 34, 20, 62, 24);
canvas_set_font(canvas, FontPrimary);
canvas_draw_str(canvas, 37, 31, "Game Over");
canvas_set_font(canvas, FontSecondary);
char buffer[12];
snprintf(buffer, sizeof(buffer), "Score: %u", snake_state->len - 7U);
canvas_draw_str_aligned(canvas, 64, 41, AlignCenter, AlignBottom, buffer);
}
furi_mutex_release(snake_state->mutex);
}
static void snake_game_input_callback(InputEvent* input_event, FuriMessageQueue* event_queue) {
furi_assert(event_queue);
SnakeEvent event = {.type = EventTypeKey, .input = *input_event};
furi_message_queue_put(event_queue, &event, FuriWaitForever);
}
static void snake_game_update_timer_callback(FuriMessageQueue* event_queue) {
furi_assert(event_queue);
SnakeEvent event = {.type = EventTypeTick};
furi_message_queue_put(event_queue, &event, 0);
}
static void snake_game_init_game(SnakeState* const snake_state) {
Point p[] = {{8, 6}, {7, 6}, {6, 6}, {5, 6}, {4, 6}, {3, 6}, {2, 6}};
memcpy(snake_state->points, p, sizeof(p)); //-V1086
snake_state->len = 7;
snake_state->currentMovement = DirectionRight;
snake_state->nextMovement = DirectionRight;
Point f = {18, 6};
snake_state->fruit = f;
snake_state->state = GameStateLife;
}
static Point snake_game_get_new_fruit(SnakeState const* const snake_state) {
// 1 bit for each point on the playing field where the snake can turn
// and where the fruit can appear
uint16_t buffer[8];
memset(buffer, 0, sizeof(buffer));
uint8_t empty = 8 * 16;
for(uint16_t i = 0; i < snake_state->len; i++) {
Point p = snake_state->points[i];
if(p.x % 2 != 0 || p.y % 2 != 0) {
continue;
}
p.x /= 2;
p.y /= 2;
buffer[p.y] |= 1 << p.x;
empty--;
}
// Bit set if snake use that playing field
uint16_t newFruit = rand() % empty;
// Skip random number of _empty_ fields
for(uint8_t y = 0; y < 8; y++) {
for(uint16_t x = 0, mask = 1; x < 16; x += 1, mask <<= 1) {
if((buffer[y] & mask) == 0) {
if(newFruit == 0) {
Point p = {
.x = x * 2,
.y = y * 2,
};
return p;
}
newFruit--;
}
}
}
// We will never be here
Point p = {0, 0};
return p;
}
static bool snake_game_collision_with_frame(Point const next_step) {
// if x == 0 && currentMovement == left then x - 1 == 255 ,
// so check only x > right border
return next_step.x > 30 || next_step.y > 14;
}
static bool
snake_game_collision_with_tail(SnakeState const* const snake_state, Point const next_step) {
for(uint16_t i = 0; i < snake_state->len; i++) {
Point p = snake_state->points[i];
if(p.x == next_step.x && p.y == next_step.y) {
return true;
}
}
return false;
}
static Direction snake_game_get_turn_snake(SnakeState const* const snake_state) {
// Sum of two `Direction` lies between 0 and 6, odd values indicate orthogonality.
bool is_orthogonal = (snake_state->currentMovement + snake_state->nextMovement) % 2 == 1;
return is_orthogonal ? snake_state->nextMovement : snake_state->currentMovement;
}
static Point snake_game_get_next_step(SnakeState const* const snake_state) {
Point next_step = snake_state->points[0];
switch(snake_state->currentMovement) {
// +-----x
// |
// |
// y
case DirectionUp:
next_step.y--;
break;
case DirectionRight:
next_step.x++;
break;
case DirectionDown:
next_step.y++;
break;
case DirectionLeft:
next_step.x--;
break;
}
return next_step;
}
static void snake_game_move_snake(SnakeState* const snake_state, Point const next_step) {
memmove(snake_state->points + 1, snake_state->points, snake_state->len * sizeof(Point));
snake_state->points[0] = next_step;
}
static void
snake_game_process_game_step(SnakeState* const snake_state, NotificationApp* notification) {
if(snake_state->state == GameStateGameOver) {
return;
}
snake_state->currentMovement = snake_game_get_turn_snake(snake_state);
Point next_step = snake_game_get_next_step(snake_state);
bool crush = snake_game_collision_with_frame(next_step);
if(crush) {
if(snake_state->state == GameStateLife) {
snake_state->state = GameStateLastChance;
return;
} else if(snake_state->state == GameStateLastChance) {
snake_state->state = GameStateGameOver;
notification_message_block(notification, &sequence_fail);
return;
}
} else {
if(snake_state->state == GameStateLastChance) {
snake_state->state = GameStateLife;
}
}
crush = snake_game_collision_with_tail(snake_state, next_step);
if(crush) {
snake_state->state = GameStateGameOver;
notification_message_block(notification, &sequence_fail);
return;
}
bool eatFruit = (next_step.x == snake_state->fruit.x) && (next_step.y == snake_state->fruit.y);
if(eatFruit) {
snake_state->len++;
if(snake_state->len >= MAX_SNAKE_LEN) {
snake_state->state = GameStateGameOver;
notification_message_block(notification, &sequence_fail);
return;
}
}
snake_game_move_snake(snake_state, next_step);
if(eatFruit) {
snake_state->fruit = snake_game_get_new_fruit(snake_state);
notification_message(notification, &sequence_eat);
}
}
int32_t snake_game_app(void* p) {
UNUSED(p);
FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(SnakeEvent));
SnakeState* snake_state = malloc(sizeof(SnakeState));
snake_game_init_game(snake_state);
snake_state->mutex = furi_mutex_alloc(FuriMutexTypeNormal);
if(!snake_state->mutex) {
FURI_LOG_E("SnakeGame", "cannot create mutex\r\n");
furi_message_queue_free(event_queue);
free(snake_state);
return 255;
}
ViewPort* view_port = view_port_alloc();
view_port_draw_callback_set(view_port, snake_game_render_callback, snake_state);
view_port_input_callback_set(view_port, snake_game_input_callback, event_queue);
FuriTimer* timer =
furi_timer_alloc(snake_game_update_timer_callback, FuriTimerTypePeriodic, event_queue);
furi_timer_start(timer, furi_kernel_get_tick_frequency() / 4);
// Open GUI and register view_port
Gui* gui = furi_record_open(RECORD_GUI);
gui_add_view_port(gui, view_port, GuiLayerFullscreen);
NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION);
notification_message_block(notification, &sequence_display_backlight_enforce_on);
dolphin_deed(DolphinDeedPluginGameStart);
SnakeEvent event;
for(bool processing = true; processing;) {
FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100);
furi_mutex_acquire(snake_state->mutex, FuriWaitForever);
if(event_status == FuriStatusOk) {
// press events
if(event.type == EventTypeKey) {
if(event.input.type == InputTypePress) {
switch(event.input.key) {
case InputKeyUp:
snake_state->nextMovement = DirectionUp;
break;
case InputKeyDown:
snake_state->nextMovement = DirectionDown;
break;
case InputKeyRight:
snake_state->nextMovement = DirectionRight;
break;
case InputKeyLeft:
snake_state->nextMovement = DirectionLeft;
break;
case InputKeyOk:
if(snake_state->state == GameStateGameOver) {
snake_game_init_game(snake_state);
}
break;
case InputKeyBack:
processing = false;
break;
default:
break;
}
}
} else if(event.type == EventTypeTick) {
snake_game_process_game_step(snake_state, notification);
}
} else {
// event timeout
}
furi_mutex_release(snake_state->mutex);
view_port_update(view_port);
}
// Return backlight to normal state
notification_message(notification, &sequence_display_backlight_enforce_auto);
furi_timer_free(timer);
view_port_enabled_set(view_port, false);
gui_remove_view_port(gui, view_port);
furi_record_close(RECORD_GUI);
furi_record_close(RECORD_NOTIFICATION);
view_port_free(view_port);
furi_message_queue_free(event_queue);
furi_mutex_free(snake_state->mutex);
free(snake_state);
return 0;
}