[FL-2119] BT HID App (#888)
* view_dispatcher: add default back processing for Long events * assets: add ble connected and disconnected assets * bt keyboard: introduce new application * bt keyboard: add logic to keyboard mode * bt: remove debug ble hid application * bt hid: introduce media controller * gui canvas: rename CanvasFontDirection -> CanvasDirection * gui canvas: add arrow element * assets: update finilized assets * bt hid: finalise keynote GUI * bt hid: finalise media player GUI * bt: add media key buttons support * bt: add exit confirm view * bt: change Clicker -> Remote * bt: support f6 target * bt: hopefully final bt hid design * bt hid: add blue led notification when device is connected * bt: leave only bt clicker for now * bt: add display notification on pin code show event
This commit is contained in:
169
applications/bt/bt_hid_app/bt_hid.c
Executable file
169
applications/bt/bt_hid_app/bt_hid.c
Executable file
@@ -0,0 +1,169 @@
|
||||
#include "bt_hid.h"
|
||||
#include <furi-hal-bt.h>
|
||||
#include <applications/notification/notification-messages.h>
|
||||
|
||||
#define TAG "BtHidApp"
|
||||
|
||||
enum BtDebugSubmenuIndex {
|
||||
BtHidSubmenuIndexKeynote,
|
||||
BtHidSubmenuIndexMedia,
|
||||
};
|
||||
|
||||
void bt_hid_submenu_callback(void* context, uint32_t index) {
|
||||
furi_assert(context);
|
||||
BtHid* app = context;
|
||||
if(index == BtHidSubmenuIndexKeynote) {
|
||||
app->view_id = BtHidViewKeynote;
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, BtHidViewKeynote);
|
||||
} else if(index == BtHidSubmenuIndexMedia) {
|
||||
app->view_id = BtHidViewMedia;
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, BtHidViewMedia);
|
||||
}
|
||||
}
|
||||
|
||||
void bt_hid_dialog_callback(DialogExResult result, void* context) {
|
||||
furi_assert(context);
|
||||
BtHid* app = context;
|
||||
if(result == DialogExResultLeft) {
|
||||
// TODO switch to Submenu after Media is done
|
||||
view_dispatcher_stop(app->view_dispatcher);
|
||||
} else if(result == DialogExResultRight) {
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, app->view_id);
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t bt_hid_exit_confirm_view(void* context) {
|
||||
return BtHidViewExitConfirm;
|
||||
}
|
||||
|
||||
uint32_t bt_hid_exit(void* context) {
|
||||
return VIEW_NONE;
|
||||
}
|
||||
|
||||
void bt_hid_connection_status_changed_callback(BtStatus status, void* context) {
|
||||
furi_assert(context);
|
||||
BtHid* bt_hid = context;
|
||||
bool connected = (status == BtStatusConnected);
|
||||
if(connected) {
|
||||
notification_internal_message(bt_hid->notifications, &sequence_set_blue_255);
|
||||
} else {
|
||||
notification_internal_message(bt_hid->notifications, &sequence_reset_blue);
|
||||
}
|
||||
bt_hid_keynote_set_connected_status(bt_hid->bt_hid_keynote, connected);
|
||||
bt_hid_media_set_connected_status(bt_hid->bt_hid_media, connected);
|
||||
}
|
||||
|
||||
BtHid* bt_hid_app_alloc() {
|
||||
BtHid* app = furi_alloc(sizeof(BtHid));
|
||||
|
||||
// Load Bluetooth settings
|
||||
bt_settings_load(&app->bt_settings);
|
||||
|
||||
// Gui
|
||||
app->gui = furi_record_open("gui");
|
||||
|
||||
// Bt
|
||||
app->bt = furi_record_open("bt");
|
||||
|
||||
// Notifications
|
||||
app->notifications = furi_record_open("notification");
|
||||
|
||||
// View dispatcher
|
||||
app->view_dispatcher = view_dispatcher_alloc();
|
||||
view_dispatcher_enable_queue(app->view_dispatcher);
|
||||
view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen);
|
||||
|
||||
// Submenu view
|
||||
app->submenu = submenu_alloc();
|
||||
submenu_add_item(
|
||||
app->submenu, "Keynote", BtHidSubmenuIndexKeynote, bt_hid_submenu_callback, app);
|
||||
submenu_add_item(
|
||||
app->submenu, "Media player", BtHidSubmenuIndexMedia, bt_hid_submenu_callback, app);
|
||||
view_set_previous_callback(submenu_get_view(app->submenu), bt_hid_exit);
|
||||
view_dispatcher_add_view(
|
||||
app->view_dispatcher, BtHidViewSubmenu, submenu_get_view(app->submenu));
|
||||
|
||||
// Dialog view
|
||||
app->dialog = dialog_ex_alloc();
|
||||
dialog_ex_set_result_callback(app->dialog, bt_hid_dialog_callback);
|
||||
dialog_ex_set_context(app->dialog, app);
|
||||
dialog_ex_set_left_button_text(app->dialog, "Exit");
|
||||
dialog_ex_set_right_button_text(app->dialog, "Stay");
|
||||
dialog_ex_set_header(app->dialog, "Close current app?", 16, 12, AlignLeft, AlignTop);
|
||||
view_dispatcher_add_view(
|
||||
app->view_dispatcher, BtHidViewExitConfirm, dialog_ex_get_view(app->dialog));
|
||||
|
||||
// Keynote view
|
||||
app->bt_hid_keynote = bt_hid_keynote_alloc();
|
||||
view_set_previous_callback(
|
||||
bt_hid_keynote_get_view(app->bt_hid_keynote), bt_hid_exit_confirm_view);
|
||||
view_dispatcher_add_view(
|
||||
app->view_dispatcher, BtHidViewKeynote, bt_hid_keynote_get_view(app->bt_hid_keynote));
|
||||
|
||||
// Media view
|
||||
app->bt_hid_media = bt_hid_media_alloc();
|
||||
view_set_previous_callback(bt_hid_media_get_view(app->bt_hid_media), bt_hid_exit_confirm_view);
|
||||
view_dispatcher_add_view(
|
||||
app->view_dispatcher, BtHidViewMedia, bt_hid_media_get_view(app->bt_hid_media));
|
||||
|
||||
// TODO switch to menu after Media is done
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, BtHidViewKeynote);
|
||||
|
||||
return app;
|
||||
}
|
||||
|
||||
void bt_hid_app_free(BtHid* app) {
|
||||
furi_assert(app);
|
||||
|
||||
// Reset notification
|
||||
notification_internal_message(app->notifications, &sequence_reset_blue);
|
||||
|
||||
// Free views
|
||||
view_dispatcher_remove_view(app->view_dispatcher, BtHidViewSubmenu);
|
||||
submenu_free(app->submenu);
|
||||
view_dispatcher_remove_view(app->view_dispatcher, BtHidViewExitConfirm);
|
||||
dialog_ex_free(app->dialog);
|
||||
view_dispatcher_remove_view(app->view_dispatcher, BtHidViewKeynote);
|
||||
bt_hid_keynote_free(app->bt_hid_keynote);
|
||||
view_dispatcher_remove_view(app->view_dispatcher, BtHidViewMedia);
|
||||
bt_hid_media_free(app->bt_hid_media);
|
||||
view_dispatcher_free(app->view_dispatcher);
|
||||
|
||||
// Close records
|
||||
furi_record_close("gui");
|
||||
app->gui = NULL;
|
||||
furi_record_close("notification");
|
||||
app->notifications = NULL;
|
||||
furi_record_close("bt");
|
||||
app->bt = NULL;
|
||||
|
||||
// Free rest
|
||||
free(app);
|
||||
}
|
||||
|
||||
int32_t bt_hid_app(void* p) {
|
||||
// Switch profile to Hid
|
||||
BtHid* app = bt_hid_app_alloc();
|
||||
bt_set_status_changed_callback(app->bt, bt_hid_connection_status_changed_callback, app);
|
||||
// Change profile
|
||||
if(!bt_set_profile(app->bt, BtProfileHidKeyboard)) {
|
||||
FURI_LOG_E(TAG, "Failed to switch profile");
|
||||
bt_hid_app_free(app);
|
||||
return -1;
|
||||
}
|
||||
furi_hal_bt_start_advertising();
|
||||
|
||||
view_dispatcher_run(app->view_dispatcher);
|
||||
|
||||
bt_set_status_changed_callback(app->bt, NULL, NULL);
|
||||
// Stop advertising if bt was off
|
||||
if(app->bt_settings.enabled) {
|
||||
furi_hal_bt_stop_advertising();
|
||||
}
|
||||
// Change back profile to Serial
|
||||
bt_set_profile(app->bt, BtProfileSerial);
|
||||
|
||||
bt_hid_app_free(app);
|
||||
|
||||
return 0;
|
||||
}
|
34
applications/bt/bt_hid_app/bt_hid.h
Normal file
34
applications/bt/bt_hid_app/bt_hid.h
Normal file
@@ -0,0 +1,34 @@
|
||||
#pragma once
|
||||
|
||||
#include <furi.h>
|
||||
#include <bt/bt_service/bt.h>
|
||||
#include <gui/gui.h>
|
||||
#include <gui/view.h>
|
||||
#include <gui/view_dispatcher.h>
|
||||
#include <applications/notification/notification.h>
|
||||
#include <applications/bt/bt_settings.h>
|
||||
|
||||
#include <gui/modules/submenu.h>
|
||||
#include <gui/modules/dialog_ex.h>
|
||||
#include "views/bt_hid_keynote.h"
|
||||
#include "views/bt_hid_media.h"
|
||||
|
||||
typedef struct {
|
||||
BtSettings bt_settings;
|
||||
Bt* bt;
|
||||
Gui* gui;
|
||||
NotificationApp* notifications;
|
||||
ViewDispatcher* view_dispatcher;
|
||||
Submenu* submenu;
|
||||
DialogEx* dialog;
|
||||
BtHidKeynote* bt_hid_keynote;
|
||||
BtHidMedia* bt_hid_media;
|
||||
uint32_t view_id;
|
||||
} BtHid;
|
||||
|
||||
typedef enum {
|
||||
BtHidViewSubmenu,
|
||||
BtHidViewKeynote,
|
||||
BtHidViewMedia,
|
||||
BtHidViewExitConfirm,
|
||||
} BtHidView;
|
192
applications/bt/bt_hid_app/views/bt_hid_keynote.c
Executable file
192
applications/bt/bt_hid_app/views/bt_hid_keynote.c
Executable file
@@ -0,0 +1,192 @@
|
||||
#include "bt_hid_keynote.h"
|
||||
#include <furi.h>
|
||||
#include <furi-hal-bt-hid.h>
|
||||
#include <furi-hal-usb-hid.h>
|
||||
#include <gui/elements.h>
|
||||
|
||||
struct BtHidKeynote {
|
||||
View* view;
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
bool left_pressed;
|
||||
bool up_pressed;
|
||||
bool right_pressed;
|
||||
bool down_pressed;
|
||||
bool ok_pressed;
|
||||
bool connected;
|
||||
} BtHidKeynoteModel;
|
||||
|
||||
static void bt_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 bt_hid_keynote_draw_callback(Canvas* canvas, void* context) {
|
||||
furi_assert(context);
|
||||
BtHidKeynoteModel* model = context;
|
||||
|
||||
// Header
|
||||
canvas_set_font(canvas, FontPrimary);
|
||||
elements_multiline_text_aligned(canvas, 9, 3, AlignLeft, AlignTop, "Keynote");
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
|
||||
// Connected status
|
||||
if(model->connected) {
|
||||
canvas_draw_icon(canvas, 18, 18, &I_Ble_connected_38x34);
|
||||
elements_multiline_text_aligned(canvas, 9, 60, AlignLeft, AlignBottom, "Connected");
|
||||
} else {
|
||||
canvas_draw_icon(canvas, 18, 18, &I_Ble_disconnected_24x34);
|
||||
elements_multiline_text_aligned(canvas, 3, 60, AlignLeft, AlignBottom, "Disconnected");
|
||||
}
|
||||
|
||||
// Up
|
||||
canvas_draw_icon(canvas, 86, 4, &I_Button_18x18);
|
||||
if(model->up_pressed) {
|
||||
elements_slightly_rounded_box(canvas, 89, 6, 13, 13);
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
}
|
||||
bt_hid_keynote_draw_arrow(canvas, 95, 10, CanvasDirectionBottomToTop);
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
|
||||
// Down
|
||||
canvas_draw_icon(canvas, 86, 25, &I_Button_18x18);
|
||||
if(model->down_pressed) {
|
||||
elements_slightly_rounded_box(canvas, 89, 27, 13, 13);
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
}
|
||||
bt_hid_keynote_draw_arrow(canvas, 95, 35, CanvasDirectionTopToBottom);
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
|
||||
// Left
|
||||
canvas_draw_icon(canvas, 65, 25, &I_Button_18x18);
|
||||
if(model->left_pressed) {
|
||||
elements_slightly_rounded_box(canvas, 68, 27, 13, 13);
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
}
|
||||
bt_hid_keynote_draw_arrow(canvas, 72, 33, CanvasDirectionRightToLeft);
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
|
||||
// Right
|
||||
canvas_draw_icon(canvas, 107, 25, &I_Button_18x18);
|
||||
if(model->right_pressed) {
|
||||
elements_slightly_rounded_box(canvas, 110, 27, 13, 13);
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
}
|
||||
bt_hid_keynote_draw_arrow(canvas, 118, 33, CanvasDirectionLeftToRight);
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
|
||||
// Ok
|
||||
canvas_draw_icon(canvas, 63, 45, &I_Space_65x18);
|
||||
if(model->ok_pressed) {
|
||||
elements_slightly_rounded_box(canvas, 66, 47, 60, 13);
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
}
|
||||
canvas_draw_icon(canvas, 74, 49, &I_Ok_btn_9x9);
|
||||
elements_multiline_text_aligned(canvas, 91, 56, AlignLeft, AlignBottom, "Space");
|
||||
}
|
||||
|
||||
static void bt_hid_keynote_process_press(BtHidKeynote* bt_hid_keynote, InputEvent* event) {
|
||||
with_view_model(
|
||||
bt_hid_keynote->view, (BtHidKeynoteModel * model) {
|
||||
if(event->key == InputKeyUp) {
|
||||
model->up_pressed = true;
|
||||
furi_hal_bt_hid_kb_press(KEY_UP_ARROW);
|
||||
} else if(event->key == InputKeyDown) {
|
||||
model->down_pressed = true;
|
||||
furi_hal_bt_hid_kb_press(KEY_DOWN_ARROW);
|
||||
} else if(event->key == InputKeyLeft) {
|
||||
model->left_pressed = true;
|
||||
furi_hal_bt_hid_kb_press(KEY_LEFT_ARROW);
|
||||
} else if(event->key == InputKeyRight) {
|
||||
model->right_pressed = true;
|
||||
furi_hal_bt_hid_kb_press(KEY_RIGHT_ARROW);
|
||||
} else if(event->key == InputKeyOk) {
|
||||
model->ok_pressed = true;
|
||||
furi_hal_bt_hid_kb_press(KEY_SPACE);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
static void bt_hid_keynote_process_release(BtHidKeynote* bt_hid_keynote, InputEvent* event) {
|
||||
with_view_model(
|
||||
bt_hid_keynote->view, (BtHidKeynoteModel * model) {
|
||||
if(event->key == InputKeyUp) {
|
||||
model->up_pressed = false;
|
||||
furi_hal_bt_hid_kb_release(KEY_UP_ARROW);
|
||||
} else if(event->key == InputKeyDown) {
|
||||
model->down_pressed = false;
|
||||
furi_hal_bt_hid_kb_release(KEY_DOWN_ARROW);
|
||||
} else if(event->key == InputKeyLeft) {
|
||||
model->left_pressed = false;
|
||||
furi_hal_bt_hid_kb_release(KEY_LEFT_ARROW);
|
||||
} else if(event->key == InputKeyRight) {
|
||||
model->right_pressed = false;
|
||||
furi_hal_bt_hid_kb_release(KEY_RIGHT_ARROW);
|
||||
} else if(event->key == InputKeyOk) {
|
||||
model->ok_pressed = false;
|
||||
furi_hal_bt_hid_kb_release(KEY_SPACE);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
static bool bt_hid_keynote_input_callback(InputEvent* event, void* context) {
|
||||
furi_assert(context);
|
||||
BtHidKeynote* bt_hid_keynote = context;
|
||||
bool consumed = false;
|
||||
|
||||
if(event->type == InputTypePress) {
|
||||
bt_hid_keynote_process_press(bt_hid_keynote, event);
|
||||
consumed = true;
|
||||
} else if(event->type == InputTypeRelease) {
|
||||
bt_hid_keynote_process_release(bt_hid_keynote, event);
|
||||
consumed = true;
|
||||
} else if(event->type == InputTypeShort) {
|
||||
if(event->key == InputKeyBack) {
|
||||
furi_hal_hid_kb_release_all();
|
||||
}
|
||||
}
|
||||
|
||||
return consumed;
|
||||
}
|
||||
|
||||
BtHidKeynote* bt_hid_keynote_alloc() {
|
||||
BtHidKeynote* bt_hid_keynote = furi_alloc(sizeof(BtHidKeynote));
|
||||
bt_hid_keynote->view = view_alloc();
|
||||
view_set_context(bt_hid_keynote->view, bt_hid_keynote);
|
||||
view_allocate_model(bt_hid_keynote->view, ViewModelTypeLocking, sizeof(BtHidKeynoteModel));
|
||||
view_set_draw_callback(bt_hid_keynote->view, bt_hid_keynote_draw_callback);
|
||||
view_set_input_callback(bt_hid_keynote->view, bt_hid_keynote_input_callback);
|
||||
|
||||
return bt_hid_keynote;
|
||||
}
|
||||
|
||||
void bt_hid_keynote_free(BtHidKeynote* bt_hid_keynote) {
|
||||
furi_assert(bt_hid_keynote);
|
||||
view_free(bt_hid_keynote->view);
|
||||
free(bt_hid_keynote);
|
||||
}
|
||||
|
||||
View* bt_hid_keynote_get_view(BtHidKeynote* bt_hid_keynote) {
|
||||
furi_assert(bt_hid_keynote);
|
||||
return bt_hid_keynote->view;
|
||||
}
|
||||
|
||||
void bt_hid_keynote_set_connected_status(BtHidKeynote* bt_hid_keynote, bool connected) {
|
||||
furi_assert(bt_hid_keynote);
|
||||
with_view_model(
|
||||
bt_hid_keynote->view, (BtHidKeynoteModel * model) {
|
||||
model->connected = connected;
|
||||
return true;
|
||||
});
|
||||
}
|
13
applications/bt/bt_hid_app/views/bt_hid_keynote.h
Normal file
13
applications/bt/bt_hid_app/views/bt_hid_keynote.h
Normal file
@@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#include <gui/view.h>
|
||||
|
||||
typedef struct BtHidKeynote BtHidKeynote;
|
||||
|
||||
BtHidKeynote* bt_hid_keynote_alloc();
|
||||
|
||||
void bt_hid_keynote_free(BtHidKeynote* bt_hid_keynote);
|
||||
|
||||
View* bt_hid_keynote_get_view(BtHidKeynote* bt_hid_keynote);
|
||||
|
||||
void bt_hid_keynote_set_connected_status(BtHidKeynote* bt_hid_keynote, bool connected);
|
193
applications/bt/bt_hid_app/views/bt_hid_media.c
Executable file
193
applications/bt/bt_hid_app/views/bt_hid_media.c
Executable file
@@ -0,0 +1,193 @@
|
||||
#include "bt_hid_media.h"
|
||||
#include <furi.h>
|
||||
#include <furi-hal-bt-hid.h>
|
||||
#include <furi-hal-usb-hid.h>
|
||||
#include <gui/elements.h>
|
||||
|
||||
struct BtHidMedia {
|
||||
View* view;
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
bool left_pressed;
|
||||
bool up_pressed;
|
||||
bool right_pressed;
|
||||
bool down_pressed;
|
||||
bool ok_pressed;
|
||||
bool connected;
|
||||
} BtHidMediaModel;
|
||||
|
||||
static void bt_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 bt_hid_media_draw_callback(Canvas* canvas, void* context) {
|
||||
furi_assert(context);
|
||||
BtHidMediaModel* model = context;
|
||||
|
||||
// Header
|
||||
canvas_set_font(canvas, FontPrimary);
|
||||
elements_multiline_text_aligned(canvas, 9, 3, AlignLeft, AlignTop, "Media player");
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
|
||||
// Connected status
|
||||
if(model->connected) {
|
||||
canvas_draw_icon(canvas, 23, 17, &I_Ble_connected_38x34);
|
||||
elements_multiline_text_aligned(canvas, 35, 61, AlignCenter, AlignBottom, "Connected");
|
||||
} else {
|
||||
canvas_draw_icon(canvas, 23, 17, &I_Ble_disconnected_24x34);
|
||||
elements_multiline_text_aligned(canvas, 35, 61, AlignCenter, AlignBottom, "Disconnected");
|
||||
}
|
||||
|
||||
// Keypad circles
|
||||
canvas_draw_icon(canvas, 76, 8, &I_Circles_47x47);
|
||||
|
||||
// Up
|
||||
if(model->up_pressed) {
|
||||
canvas_draw_icon(canvas, 93, 9, &I_Pressed_Button_13x13);
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
}
|
||||
canvas_draw_icon(canvas, 96, 12, &I_Volup_8x6);
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
|
||||
// Down
|
||||
if(model->down_pressed) {
|
||||
canvas_draw_icon(canvas, 93, 41, &I_Pressed_Button_13x13);
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
}
|
||||
canvas_draw_icon(canvas, 96, 45, &I_Voldwn_6x6);
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
|
||||
// Left
|
||||
if(model->left_pressed) {
|
||||
canvas_draw_icon(canvas, 77, 25, &I_Pressed_Button_13x13);
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
}
|
||||
bt_hid_media_draw_arrow(canvas, 82, 31, CanvasDirectionRightToLeft);
|
||||
bt_hid_media_draw_arrow(canvas, 86, 31, CanvasDirectionRightToLeft);
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
|
||||
// Right
|
||||
if(model->right_pressed) {
|
||||
canvas_draw_icon(canvas, 109, 25, &I_Pressed_Button_13x13);
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
}
|
||||
bt_hid_media_draw_arrow(canvas, 112, 31, CanvasDirectionLeftToRight);
|
||||
bt_hid_media_draw_arrow(canvas, 116, 31, CanvasDirectionLeftToRight);
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
|
||||
// Ok
|
||||
if(model->ok_pressed) {
|
||||
canvas_draw_icon(canvas, 93, 25, &I_Pressed_Button_13x13);
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
}
|
||||
bt_hid_media_draw_arrow(canvas, 96, 31, CanvasDirectionLeftToRight);
|
||||
canvas_draw_line(canvas, 100, 29, 100, 33);
|
||||
canvas_draw_line(canvas, 102, 29, 102, 33);
|
||||
}
|
||||
|
||||
static void bt_hid_media_process_press(BtHidMedia* bt_hid_media, InputEvent* event) {
|
||||
with_view_model(
|
||||
bt_hid_media->view, (BtHidMediaModel * model) {
|
||||
if(event->key == InputKeyUp) {
|
||||
model->up_pressed = true;
|
||||
furi_hal_bt_hid_media_press(FuriHalBtHidMediaVolumeUp);
|
||||
} else if(event->key == InputKeyDown) {
|
||||
model->down_pressed = true;
|
||||
furi_hal_bt_hid_media_press(FuriHalBtHidMediaVolumeDown);
|
||||
} else if(event->key == InputKeyLeft) {
|
||||
model->left_pressed = true;
|
||||
furi_hal_bt_hid_media_press(FuriHalBtHidMediaScanPrevious);
|
||||
} else if(event->key == InputKeyRight) {
|
||||
model->right_pressed = true;
|
||||
furi_hal_bt_hid_media_press(FuriHalBtHidMediaScanNext);
|
||||
} else if(event->key == InputKeyOk) {
|
||||
model->ok_pressed = true;
|
||||
furi_hal_bt_hid_media_press(FuriHalBtHidMediaPlayPause);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
static void bt_hid_media_process_release(BtHidMedia* bt_hid_media, InputEvent* event) {
|
||||
with_view_model(
|
||||
bt_hid_media->view, (BtHidMediaModel * model) {
|
||||
if(event->key == InputKeyUp) {
|
||||
model->up_pressed = false;
|
||||
furi_hal_bt_hid_media_release(FuriHalBtHidMediaVolumeUp);
|
||||
} else if(event->key == InputKeyDown) {
|
||||
model->down_pressed = false;
|
||||
furi_hal_bt_hid_media_release(FuriHalBtHidMediaVolumeDown);
|
||||
} else if(event->key == InputKeyLeft) {
|
||||
model->left_pressed = false;
|
||||
furi_hal_bt_hid_media_release(FuriHalBtHidMediaScanPrevious);
|
||||
} else if(event->key == InputKeyRight) {
|
||||
model->right_pressed = false;
|
||||
furi_hal_bt_hid_media_release(FuriHalBtHidMediaScanNext);
|
||||
} else if(event->key == InputKeyOk) {
|
||||
model->ok_pressed = false;
|
||||
furi_hal_bt_hid_media_release(FuriHalBtHidMediaPlayPause);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
static bool bt_hid_media_input_callback(InputEvent* event, void* context) {
|
||||
furi_assert(context);
|
||||
BtHidMedia* bt_hid_media = context;
|
||||
bool consumed = false;
|
||||
|
||||
if(event->type == InputTypePress) {
|
||||
bt_hid_media_process_press(bt_hid_media, event);
|
||||
consumed = true;
|
||||
} else if(event->type == InputTypeRelease) {
|
||||
bt_hid_media_process_release(bt_hid_media, event);
|
||||
consumed = true;
|
||||
} else if(event->type == InputTypeShort) {
|
||||
if(event->key == InputKeyBack) {
|
||||
furi_hal_bt_hid_media_release_all();
|
||||
}
|
||||
}
|
||||
|
||||
return consumed;
|
||||
}
|
||||
|
||||
BtHidMedia* bt_hid_media_alloc() {
|
||||
BtHidMedia* bt_hid_media = furi_alloc(sizeof(BtHidMedia));
|
||||
bt_hid_media->view = view_alloc();
|
||||
view_set_context(bt_hid_media->view, bt_hid_media);
|
||||
view_allocate_model(bt_hid_media->view, ViewModelTypeLocking, sizeof(BtHidMediaModel));
|
||||
view_set_draw_callback(bt_hid_media->view, bt_hid_media_draw_callback);
|
||||
view_set_input_callback(bt_hid_media->view, bt_hid_media_input_callback);
|
||||
|
||||
return bt_hid_media;
|
||||
}
|
||||
|
||||
void bt_hid_media_free(BtHidMedia* bt_hid_media) {
|
||||
furi_assert(bt_hid_media);
|
||||
view_free(bt_hid_media->view);
|
||||
free(bt_hid_media);
|
||||
}
|
||||
|
||||
View* bt_hid_media_get_view(BtHidMedia* bt_hid_media) {
|
||||
furi_assert(bt_hid_media);
|
||||
return bt_hid_media->view;
|
||||
}
|
||||
|
||||
void bt_hid_media_set_connected_status(BtHidMedia* bt_hid_media, bool connected) {
|
||||
furi_assert(bt_hid_media);
|
||||
with_view_model(
|
||||
bt_hid_media->view, (BtHidMediaModel * model) {
|
||||
model->connected = connected;
|
||||
return true;
|
||||
});
|
||||
}
|
13
applications/bt/bt_hid_app/views/bt_hid_media.h
Normal file
13
applications/bt/bt_hid_app/views/bt_hid_media.h
Normal file
@@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#include <gui/view.h>
|
||||
|
||||
typedef struct BtHidMedia BtHidMedia;
|
||||
|
||||
BtHidMedia* bt_hid_media_alloc();
|
||||
|
||||
void bt_hid_media_free(BtHidMedia* bt_hid_media);
|
||||
|
||||
View* bt_hid_media_get_view(BtHidMedia* bt_hid_media);
|
||||
|
||||
void bt_hid_media_set_connected_status(BtHidMedia* bt_hid_media, bool connected);
|
@@ -2,6 +2,8 @@
|
||||
#include "battery_service.h"
|
||||
#include "bt_keys_storage.h"
|
||||
|
||||
#include <applications/notification/notification-messages.h>
|
||||
|
||||
#define TAG "BtSrv"
|
||||
|
||||
#define BT_RPC_EVENT_BUFF_SENT (1UL << 0)
|
||||
@@ -29,6 +31,7 @@ static ViewPort* bt_statusbar_view_port_alloc(Bt* bt) {
|
||||
|
||||
static void bt_pin_code_show_event_handler(Bt* bt, uint32_t pin) {
|
||||
furi_assert(bt);
|
||||
notification_message(bt->notification, &sequence_display_on);
|
||||
string_t pin_str;
|
||||
dialog_message_set_icon(bt->dialog_message, &I_BLE_Pairing_128x64, 0, 0);
|
||||
string_init_printf(pin_str, "Pairing code\n%06d", pin);
|
||||
@@ -41,6 +44,7 @@ static void bt_pin_code_show_event_handler(Bt* bt, uint32_t pin) {
|
||||
|
||||
static bool bt_pin_code_verify_event_handler(Bt* bt, uint32_t pin) {
|
||||
furi_assert(bt);
|
||||
notification_message(bt->notification, &sequence_display_on);
|
||||
string_t pin_str;
|
||||
dialog_message_set_icon(bt->dialog_message, &I_BLE_Pairing_128x64, 0, 0);
|
||||
string_init_printf(pin_str, "Verify code\n%06d", pin);
|
||||
@@ -80,6 +84,8 @@ Bt* bt_alloc() {
|
||||
|
||||
// Setup statusbar view port
|
||||
bt->statusbar_view_port = bt_statusbar_view_port_alloc(bt);
|
||||
// Notification
|
||||
bt->notification = furi_record_open("notification");
|
||||
// Gui
|
||||
bt->gui = furi_record_open("gui");
|
||||
gui_add_view_port(bt->gui, bt->statusbar_view_port, GuiLayerStatusBarLeft);
|
||||
@@ -288,6 +294,9 @@ int32_t bt_srv() {
|
||||
if(message.type == BtMessageTypeUpdateStatusbar) {
|
||||
// Update statusbar
|
||||
bt_statusbar_update(bt);
|
||||
if(bt->status_changed_cb) {
|
||||
bt->status_changed_cb(bt->status, bt->status_changed_ctx);
|
||||
}
|
||||
} else if(message.type == BtMessageTypeUpdateBatteryLevel) {
|
||||
// Update battery level
|
||||
furi_hal_bt_update_battery_level(message.data.battery_level);
|
||||
|
@@ -9,13 +9,20 @@ extern "C" {
|
||||
|
||||
typedef struct Bt Bt;
|
||||
|
||||
typedef enum {
|
||||
BtStatusOff,
|
||||
BtStatusAdvertising,
|
||||
BtStatusConnected,
|
||||
} BtStatus;
|
||||
|
||||
typedef enum {
|
||||
BtProfileSerial,
|
||||
BtProfileHidKeyboard,
|
||||
} BtProfile;
|
||||
|
||||
/**
|
||||
* Change BLE Profile
|
||||
typedef void (*BtStatusChangedCallback)(BtStatus status, void* context);
|
||||
|
||||
/** Change BLE Profile
|
||||
* @note Call of this function leads to 2nd core restart
|
||||
*
|
||||
* @param bt Bt instance
|
||||
@@ -25,6 +32,14 @@ typedef enum {
|
||||
*/
|
||||
bool bt_set_profile(Bt* bt, BtProfile profile);
|
||||
|
||||
/** Set callback for Bluetooth status change notification
|
||||
*
|
||||
* @param bt Bt instance
|
||||
* @param callback BtStatusChangedCallback instance
|
||||
* @param context pointer to context
|
||||
*/
|
||||
void bt_set_status_changed_callback(Bt* bt, BtStatusChangedCallback callback, void* context);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
@@ -13,3 +13,10 @@ bool bt_set_profile(Bt* bt, BtProfile profile) {
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void bt_set_status_changed_callback(Bt* bt, BtStatusChangedCallback callback, void* context) {
|
||||
furi_assert(bt);
|
||||
|
||||
bt->status_changed_cb = callback;
|
||||
bt->status_changed_ctx = context;
|
||||
}
|
||||
|
@@ -12,17 +12,12 @@
|
||||
#include <dialogs/dialogs.h>
|
||||
#include <power/power_service/power.h>
|
||||
#include <applications/rpc/rpc.h>
|
||||
#include <applications/notification/notification.h>
|
||||
|
||||
#include "../bt_settings.h"
|
||||
|
||||
#define BT_API_UNLOCK_EVENT (1UL << 0)
|
||||
|
||||
typedef enum {
|
||||
BtStatusOff,
|
||||
BtStatusAdvertising,
|
||||
BtStatusConnected,
|
||||
} BtStatus;
|
||||
|
||||
typedef enum {
|
||||
BtMessageTypeUpdateStatusbar,
|
||||
BtMessageTypeUpdateBatteryLevel,
|
||||
@@ -51,6 +46,7 @@ struct Bt {
|
||||
BtStatus status;
|
||||
BtProfile profile;
|
||||
osMessageQueueId_t message_queue;
|
||||
NotificationApp* notification;
|
||||
Gui* gui;
|
||||
ViewPort* statusbar_view_port;
|
||||
DialogsApp* dialogs;
|
||||
@@ -60,4 +56,6 @@ struct Bt {
|
||||
RpcSession* rpc_session;
|
||||
osEventFlagsId_t rpc_event;
|
||||
osEventFlagsId_t api_event;
|
||||
BtStatusChangedCallback status_changed_cb;
|
||||
void* status_changed_ctx;
|
||||
};
|
||||
|
Reference in New Issue
Block a user