diff --git a/applications/plugins/airmouse/views/bt_mouse.c b/applications/plugins/airmouse/views/bt_mouse.c new file mode 100644 index 000000000..e6e0ae45a --- /dev/null +++ b/applications/plugins/airmouse/views/bt_mouse.c @@ -0,0 +1,303 @@ +#include "bt_mouse.h" +#include "../tracking/main_loop.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +typedef struct ButtonEvent { + int8_t button; + bool state; +} ButtonEvent; + +#define BTN_EVT_QUEUE_SIZE 32 + +struct BtMouse { + View* view; + ViewDispatcher* view_dispatcher; + Bt* bt; + NotificationApp* notifications; + FuriMutex* mutex; + FuriThread* thread; + bool connected; + + // Current mouse state + uint8_t btn; + int dx; + int dy; + int wheel; + + // Circular buffer; + // (qhead == qtail) means either empty or overflow. + // We'll ignore overflow and treat it as empty. + int qhead; + int qtail; + ButtonEvent queue[BTN_EVT_QUEUE_SIZE]; +}; + +#define BT_MOUSE_FLAG_INPUT_EVENT (1UL << 0) +#define BT_MOUSE_FLAG_KILL_THREAD (1UL << 1) +#define BT_MOUSE_FLAG_ALL (BT_MOUSE_FLAG_INPUT_EVENT | BT_MOUSE_FLAG_KILL_THREAD) + +#define MOUSE_MOVE_SHORT 5 +#define MOUSE_MOVE_LONG 20 + +static void bt_mouse_notify_event(BtMouse* bt_mouse) { + FuriThreadId thread_id = furi_thread_get_id(bt_mouse->thread); + furi_assert(thread_id); + furi_thread_flags_set(thread_id, BT_MOUSE_FLAG_INPUT_EVENT); +} + +static void bt_mouse_draw_callback(Canvas* canvas, void* context) { + UNUSED(context); + canvas_clear(canvas); + canvas_set_font(canvas, FontPrimary); + canvas_draw_str(canvas, 0, 10, "Bluetooth Mouse mode"); + canvas_set_font(canvas, FontSecondary); + canvas_draw_str(canvas, 0, 63, "Hold [back] to exit"); +} + +static void bt_mouse_button_state(BtMouse* bt_mouse, int8_t button, bool state) { + ButtonEvent event; + event.button = button; + event.state = state; + + if(bt_mouse->connected) { + furi_mutex_acquire(bt_mouse->mutex, FuriWaitForever); + bt_mouse->queue[bt_mouse->qtail++] = event; + bt_mouse->qtail %= BTN_EVT_QUEUE_SIZE; + furi_mutex_release(bt_mouse->mutex); + bt_mouse_notify_event(bt_mouse); + } +} + +static void bt_mouse_process(BtMouse* bt_mouse, InputEvent* event) { + with_view_model( + bt_mouse->view, + void* model, + { + UNUSED(model); + if(event->key == InputKeyUp) { + if(event->type == InputTypePress) { + bt_mouse_button_state(bt_mouse, HID_MOUSE_BTN_LEFT, true); + } else if(event->type == InputTypeRelease) { + bt_mouse_button_state(bt_mouse, HID_MOUSE_BTN_LEFT, false); + } + } else if(event->key == InputKeyDown) { + if(event->type == InputTypePress) { + bt_mouse_button_state(bt_mouse, HID_MOUSE_BTN_RIGHT, true); + } else if(event->type == InputTypeRelease) { + bt_mouse_button_state(bt_mouse, HID_MOUSE_BTN_RIGHT, false); + } + } else if(event->key == InputKeyOk) { + if(event->type == InputTypePress) { + bt_mouse_button_state(bt_mouse, HID_MOUSE_BTN_WHEEL, true); + } else if(event->type == InputTypeRelease) { + bt_mouse_button_state(bt_mouse, HID_MOUSE_BTN_WHEEL, false); + } + } + }, + true); +} + +static bool bt_mouse_input_callback(InputEvent* event, void* context) { + furi_assert(context); + BtMouse* bt_mouse = context; + bool consumed = false; + + if(event->type == InputTypeLong && event->key == InputKeyBack) { + furi_hal_bt_hid_mouse_release_all(); + } else { + bt_mouse_process(bt_mouse, event); + consumed = true; + } + + return consumed; +} + +void bt_mouse_connection_status_changed_callback(BtStatus status, void* context) { + furi_assert(context); + BtMouse* bt_mouse = context; + + bt_mouse->connected = (status == BtStatusConnected); + if(bt_mouse->connected) { + notification_internal_message(bt_mouse->notifications, &sequence_set_blue_255); + tracking_begin(); + view_dispatcher_send_custom_event(bt_mouse->view_dispatcher, 0); + } else { + tracking_end(); + notification_internal_message(bt_mouse->notifications, &sequence_reset_blue); + } + + //with_view_model( + // bt_mouse->view, void * model, { model->connected = connected; }, true); +} + +bool bt_mouse_move(int8_t dx, int8_t dy, void* context) { + furi_assert(context); + BtMouse* bt_mouse = context; + + if(bt_mouse->connected) { + furi_mutex_acquire(bt_mouse->mutex, FuriWaitForever); + bt_mouse->dx += dx; + bt_mouse->dy += dy; + furi_mutex_release(bt_mouse->mutex); + bt_mouse_notify_event(bt_mouse); + } + + return true; +} + +void bt_mouse_enter_callback(void* context) { + furi_assert(context); + BtMouse* bt_mouse = context; + + bt_mouse->bt = furi_record_open(RECORD_BT); + bt_mouse->notifications = furi_record_open(RECORD_NOTIFICATION); + bt_set_status_changed_callback( + bt_mouse->bt, bt_mouse_connection_status_changed_callback, bt_mouse); + furi_assert(bt_set_profile(bt_mouse->bt, BtProfileHidKeyboard)); + furi_hal_bt_start_advertising(); +} + +bool bt_mouse_custom_callback(uint32_t event, void* context) { + UNUSED(event); + furi_assert(context); + BtMouse* bt_mouse = context; + + tracking_step(bt_mouse_move, context); + furi_delay_ms(3); // Magic! Removing this will break the buttons + + view_dispatcher_send_custom_event(bt_mouse->view_dispatcher, 0); + return true; +} + +void bt_mouse_exit_callback(void* context) { + furi_assert(context); + BtMouse* bt_mouse = context; + + tracking_end(); + notification_internal_message(bt_mouse->notifications, &sequence_reset_blue); + + furi_hal_bt_stop_advertising(); + bt_set_profile(bt_mouse->bt, BtProfileSerial); + + furi_record_close(RECORD_NOTIFICATION); + bt_mouse->notifications = NULL; + furi_record_close(RECORD_BT); + bt_mouse->bt = NULL; +} + +static int8_t clamp(int t) { + if(t < -128) { + return -128; + } else if(t > 127) { + return 127; + } + return t; +} + +static int32_t bt_mouse_thread_callback(void* context) { + furi_assert(context); + BtMouse* bt_mouse = (BtMouse*)context; + + while(1) { + uint32_t flags = + furi_thread_flags_wait(BT_MOUSE_FLAG_ALL, FuriFlagWaitAny, FuriWaitForever); + if(flags & BT_MOUSE_FLAG_KILL_THREAD) { + break; + } + if(flags & BT_MOUSE_FLAG_INPUT_EVENT) { + furi_mutex_acquire(bt_mouse->mutex, FuriWaitForever); + + ButtonEvent event; + bool send_buttons = false; + if(bt_mouse->qhead != bt_mouse->qtail) { + event = bt_mouse->queue[bt_mouse->qhead++]; + bt_mouse->qhead %= BTN_EVT_QUEUE_SIZE; + send_buttons = true; + } + + int8_t dx = clamp(bt_mouse->dx); + bt_mouse->dx -= dx; + int8_t dy = clamp(bt_mouse->dy); + bt_mouse->dy -= dy; + int8_t wheel = clamp(bt_mouse->wheel); + bt_mouse->wheel -= wheel; + + furi_mutex_release(bt_mouse->mutex); + + if(bt_mouse->connected && send_buttons) { + if(event.state) { + furi_hal_bt_hid_mouse_press(event.button); + } else { + furi_hal_bt_hid_mouse_release(event.button); + } + } + + if(bt_mouse->connected && (dx != 0 || dy != 0)) { + furi_hal_bt_hid_mouse_move(dx, dy); + } + + if(bt_mouse->connected && wheel != 0) { + furi_hal_bt_hid_mouse_scroll(wheel); + } + } + } + + return 0; +} + +void bt_mouse_thread_start(BtMouse* bt_mouse) { + furi_assert(bt_mouse); + bt_mouse->mutex = furi_mutex_alloc(FuriMutexTypeNormal); + bt_mouse->thread = furi_thread_alloc(); + furi_thread_set_name(bt_mouse->thread, "BtSender"); + furi_thread_set_stack_size(bt_mouse->thread, 1024); + furi_thread_set_context(bt_mouse->thread, bt_mouse); + furi_thread_set_callback(bt_mouse->thread, bt_mouse_thread_callback); + furi_thread_start(bt_mouse->thread); +} + +void bt_mouse_thread_stop(BtMouse* bt_mouse) { + furi_assert(bt_mouse); + FuriThreadId thread_id = furi_thread_get_id(bt_mouse->thread); + furi_assert(thread_id); + furi_thread_flags_set(thread_id, BT_MOUSE_FLAG_KILL_THREAD); + furi_thread_join(bt_mouse->thread); + furi_thread_free(bt_mouse->thread); + furi_mutex_free(bt_mouse->mutex); +} + +BtMouse* bt_mouse_alloc(ViewDispatcher* view_dispatcher) { + BtMouse* bt_mouse = malloc(sizeof(BtMouse)); + memset(bt_mouse, 0, sizeof(BtMouse)); + + bt_mouse->view = view_alloc(); + bt_mouse->view_dispatcher = view_dispatcher; + view_set_context(bt_mouse->view, bt_mouse); + view_set_draw_callback(bt_mouse->view, bt_mouse_draw_callback); + view_set_input_callback(bt_mouse->view, bt_mouse_input_callback); + view_set_enter_callback(bt_mouse->view, bt_mouse_enter_callback); + view_set_custom_callback(bt_mouse->view, bt_mouse_custom_callback); + view_set_exit_callback(bt_mouse->view, bt_mouse_exit_callback); + bt_mouse_thread_start(bt_mouse); + return bt_mouse; +} + +void bt_mouse_free(BtMouse* bt_mouse) { + furi_assert(bt_mouse); + bt_mouse_thread_stop(bt_mouse); + view_free(bt_mouse->view); + free(bt_mouse); +} + +View* bt_mouse_get_view(BtMouse* bt_mouse) { + furi_assert(bt_mouse); + return bt_mouse->view; +} diff --git a/applications/plugins/counter/counter.c b/applications/plugins/counter/counter.c index 370f9e65a..886ad3398 100644 --- a/applications/plugins/counter/counter.c +++ b/applications/plugins/counter/counter.c @@ -90,7 +90,7 @@ int32_t counterapp(void) { furi_mutex_release(c->mutex); state_free(c); return 0; - } else if(input.key == InputKeyUp && c->count < MAX_COUNT) { + } else if((input.key == InputKeyUp || input.key == InputKeyOk) && c->count < MAX_COUNT) { c->pressed = true; c->boxtimer = BOXTIME; c->count++; diff --git a/applications/plugins/flashlight/flashlight.c b/applications/plugins/flashlight/flashlight.c index 9c5f600f7..534d48fdb 100644 --- a/applications/plugins/flashlight/flashlight.c +++ b/applications/plugins/flashlight/flashlight.c @@ -127,4 +127,4 @@ int32_t flashlight_app() { delete_mutex(&state_mutex); return 0; -} \ No newline at end of file +} diff --git a/applications/plugins/music_beeper/music_beeper_worker.c b/applications/plugins/music_beeper/music_beeper_worker.c index d31135e1d..4523b806e 100644 --- a/applications/plugins/music_beeper/music_beeper_worker.c +++ b/applications/plugins/music_beeper/music_beeper_worker.c @@ -79,7 +79,7 @@ static int32_t music_beeper_worker_thread_callback(void* context) { furi_hal_speaker_stop(); furi_hal_speaker_start(frequency, volume); while(instance->should_work && furi_get_tick() < next_tick) { - volume *= 0.9945679; + volume *= 1; furi_hal_speaker_set_volume(volume); furi_delay_ms(2); } diff --git a/applications/plugins/musictracker/tracker_engine/speaker_hal.c b/applications/plugins/musictracker/tracker_engine/speaker_hal.c index aa79e5395..14a9c4f85 100644 --- a/applications/plugins/musictracker/tracker_engine/speaker_hal.c +++ b/applications/plugins/musictracker/tracker_engine/speaker_hal.c @@ -40,10 +40,10 @@ void tracker_speaker_stop() { } void tracker_speaker_init() { - if(furi_hal_speaker_acquire(30)) { + if(furi_hal_speaker_is_mine() || furi_hal_speaker_acquire(30)) { furi_hal_speaker_start(200.0f, 0.01f); + tracker_speaker_stop(); } - tracker_speaker_stop(); } void tracker_speaker_deinit() { diff --git a/applications/plugins/ocarina/ocarina.c b/applications/plugins/ocarina/ocarina.c index 2ca281c8e..80dc1fddc 100644 --- a/applications/plugins/ocarina/ocarina.c +++ b/applications/plugins/ocarina/ocarina.c @@ -88,27 +88,27 @@ int32_t ocarina_app(void* p) { if(event.type == InputTypePress) { switch(event.key) { case InputKeyUp: - if(furi_hal_speaker_acquire(30)) { + if(furi_hal_speaker_is_mine() || furi_hal_speaker_acquire(30)) { furi_hal_speaker_start(NOTE_UP, volume); } break; case InputKeyDown: - if(furi_hal_speaker_acquire(30)) { + if(furi_hal_speaker_is_mine() || furi_hal_speaker_acquire(30)) { furi_hal_speaker_start(NOTE_DOWN, volume); } break; case InputKeyLeft: - if(furi_hal_speaker_acquire(30)) { + if(furi_hal_speaker_is_mine() || furi_hal_speaker_acquire(30)) { furi_hal_speaker_start(NOTE_LEFT, volume); } break; case InputKeyRight: - if(furi_hal_speaker_acquire(30)) { + if(furi_hal_speaker_is_mine() || furi_hal_speaker_acquire(30)) { furi_hal_speaker_start(NOTE_RIGHT, volume); } break; case InputKeyOk: - if(furi_hal_speaker_acquire(30)) { + if(furi_hal_speaker_is_mine() || furi_hal_speaker_acquire(30)) { furi_hal_speaker_start(NOTE_OK, volume); } break; diff --git a/applications/plugins/tama_p1/hal.c b/applications/plugins/tama_p1/hal.c index 8e601f8b0..b4638a84a 100644 --- a/applications/plugins/tama_p1/hal.c +++ b/applications/plugins/tama_p1/hal.c @@ -103,7 +103,7 @@ static void tama_p1_hal_set_lcd_icon(u8_t icon, bool_t val) { static void tama_p1_hal_play_frequency(bool_t en) { if(en) { - if(furi_hal_speaker_acquire(30)) { + if(furi_hal_speaker_is_mine() || furi_hal_speaker_acquire(30)) { furi_hal_speaker_start(g_ctx->frequency, 0.5f); } } else { @@ -112,6 +112,7 @@ static void tama_p1_hal_play_frequency(bool_t en) { furi_hal_speaker_release(); } } + g_ctx->buzzer_on = en; } diff --git a/applications/plugins/tuning_fork/tuning_fork.c b/applications/plugins/tuning_fork/tuning_fork.c index 78fe269a1..69a76029f 100644 --- a/applications/plugins/tuning_fork/tuning_fork.c +++ b/applications/plugins/tuning_fork/tuning_fork.c @@ -114,7 +114,7 @@ static void decrease_volume(TuningForkState* tuning_fork_state) { } static void play(TuningForkState* tuning_fork_state) { - if(furi_hal_speaker_acquire(30)) { + if(furi_hal_speaker_is_mine() || furi_hal_speaker_acquire(30)) { furi_hal_speaker_start( current_tuning_note_freq(tuning_fork_state), tuning_fork_state->volume); } diff --git a/applications/plugins/usb_midi/usb_midi.c b/applications/plugins/usb_midi/usb_midi.c index b226bb58a..d82fb74d7 100644 --- a/applications/plugins/usb_midi/usb_midi.c +++ b/applications/plugins/usb_midi/usb_midi.c @@ -57,14 +57,14 @@ int32_t usb_midi_app(void* p) { if(event->type == NoteOn) { NoteOnEvent note_on = AsNoteOn(event); current_note = note_on.note; - if(furi_hal_speaker_acquire(30)) { + if(furi_hal_speaker_is_mine() || furi_hal_speaker_acquire(30)) { furi_hal_speaker_start( note_to_frequency(note_on.note), note_on.velocity / 127.0f); } } else if(event->type == NoteOff) { NoteOffEvent note_off = AsNoteOff(event); - if(furi_hal_speaker_is_mine() && note_off.note == current_note) { + if(note_off.note == current_note && furi_hal_speaker_is_mine()) { furi_hal_speaker_stop(); furi_hal_speaker_release(); }