This commit is contained in:
clipboard1
2023-05-02 22:25:33 +05:00
31 changed files with 1003 additions and 51 deletions
+22
View File
@@ -0,0 +1,22 @@
MIT License
Copyright (c) 2023 лень
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+2
View File
@@ -0,0 +1,2 @@
# flipperzero-bomberduck
Bomberman clone on flipper zero!
+14
View File
@@ -0,0 +1,14 @@
App(
appid="bomberduck",
name="Bomberduck",
apptype=FlipperAppType.EXTERNAL,
entry_point="bomberduck_app",
requires=[
"gui",
],
stack_size=1 * 1024,
order=90,
fap_icon="bomb.png",
fap_category="Games",
fap_icon_assets="assets",
)
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

+648
View File
@@ -0,0 +1,648 @@
#include <stdio.h>
#include <furi.h>
#include <gui/gui.h>
#include <input/input.h>
#include <notification/notification.h>
#include <notification/notification_messages.h>
#include <gui/canvas_i.h>
#include "bomberduck_icons.h"
#include <dolphin/dolphin.h>
int max(int a, int b) {
return (a > b) ? a : b;
}
int min(int a, int b) {
return (a < b) ? a : b;
}
#define WorldSizeX 12
#define WorldSizeY 6
#define BombRange 1
typedef struct {
FuriMutex* mutex;
} BomberState;
typedef struct {
int row;
int col;
} Cell;
typedef struct {
Cell cells[WorldSizeY * WorldSizeX];
int front;
int rear;
} Queue;
void enqueue(Queue* q, Cell c) {
q->cells[q->rear] = c;
q->rear++;
}
Cell dequeue(Queue* q) {
Cell c = q->cells[q->front];
q->front++;
return c;
}
bool is_empty(Queue* q) {
return q->front == q->rear;
}
typedef struct {
int x;
int y;
int planted;
} Bomb;
typedef struct {
int x;
int y;
bool side;
} Player;
typedef struct {
int x;
int y;
int last;
bool side;
int level;
} Enemy;
typedef struct {
int matrix[WorldSizeY][WorldSizeX];
Player* player;
bool running;
int level;
Enemy enemies[10];
int enemies_count;
Bomb bombs[100];
int bombs_count;
int endx;
int endy;
} World;
Player player = {0, 0, 1};
World world = {{{0}}, &player, 1, 0, {}, 0, {}, 0, 0, 0};
bool vibration = false;
void init() {
player.x = 1;
player.y = 1;
world.endx = 4 + rand() % 8;
world.endy = rand() % 6;
for(int i = 0; i < WorldSizeY; i++) {
for(int j = 0; j < WorldSizeX; j++) {
world.matrix[i][j] = rand() % 3;
}
}
world.running = 1;
world.bombs_count = 0;
vibration = false;
for(int j = max(0, player.y - BombRange); j < min(WorldSizeY, player.y + BombRange + 1); j++) {
world.matrix[j][player.x] = 0;
}
for(int j = max(0, player.x - BombRange); j < min(WorldSizeX, player.x + BombRange + 1); j++) {
world.matrix[player.y][j] = 0;
}
world.enemies_count = 0;
for(int j = 0; j < rand() % 4 + world.level / 5; j++) {
Enemy enemy;
enemy.x = 4 + rand() % 7;
enemy.y = rand() % 6;
enemy.last = 0;
enemy.side = 1;
enemy.level = 0;
world.enemies[j] = enemy;
world.enemies_count++;
for(int m = max(0, world.enemies[j].y - BombRange);
m < min(WorldSizeY, world.enemies[j].y + BombRange + 1);
m++) {
world.matrix[m][world.enemies[j].x] = 0;
}
for(int m = max(0, world.enemies[j].x - BombRange);
m < min(WorldSizeX, world.enemies[j].x + BombRange + 1);
m++) {
world.matrix[world.enemies[j].y][m] = 0;
}
}
world.matrix[world.endy][world.endx] = 1;
}
const NotificationSequence end = {
&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,
};
static const NotificationSequence bomb2 = {
&message_vibro_on,
&message_delay_25,
&message_vibro_off,
NULL,
};
static const NotificationSequence bomb_explore = {
&message_vibro_on,
&message_delay_50,
&message_vibro_off,
NULL,
};
static const NotificationSequence vibr1 = {
&message_vibro_on,
&message_delay_10,
&message_vibro_off,
&message_delay_10,
&message_vibro_on,
&message_delay_10,
&message_vibro_off,
&message_delay_10,
NULL,
};
void intToStr(int num, char* str) {
int i = 0, sign = 0;
if(num < 0) {
num = -num;
sign = 1;
}
do {
str[i++] = num % 10 + '0';
num /= 10;
} while(num > 0);
if(sign) {
str[i++] = '-';
}
str[i] = '\0';
// Reverse the string
int j, len = i;
char temp;
for(j = 0; j < len / 2; j++) {
temp = str[j];
str[j] = str[len - j - 1];
str[len - j - 1] = temp;
}
}
bool BFS() {
// Initialize visited array and queue
int visited[WorldSizeY][WorldSizeX] = {0};
Queue q = {.front = 0, .rear = 0};
// Mark the starting cell as visited and enqueue it
visited[world.player->y][world.player->x] = 1;
Cell startCell = {.row = world.player->y, .col = world.player->x};
enqueue(&q, startCell);
// Traverse the field
while(!is_empty(&q)) {
// Dequeue a cell from the queue
Cell currentCell = dequeue(&q);
// Check if the current cell is the destination cell
if(currentCell.row == world.endy && currentCell.col == world.endx) {
return true;
}
// Check the neighboring cells
for(int rowOffset = -1; rowOffset <= 1; rowOffset++) {
for(int colOffset = -1; colOffset <= 1; colOffset++) {
// Skip diagonals and the current cell
if(rowOffset == 0 && colOffset == 0) {
continue;
}
if(rowOffset != 0 && colOffset != 0) {
continue;
}
// Calculate the row and column of the neighboring cell
int neighborRow = currentCell.row + rowOffset;
int neighborCol = currentCell.col + colOffset;
// Skip out-of-bounds cells and already visited cells
if(neighborRow < 0 || neighborRow >= WorldSizeY || neighborCol < 0 ||
neighborCol >= WorldSizeX) {
continue;
}
if(visited[neighborRow][neighborCol]) {
continue;
}
// Mark the neighboring cell as visited and enqueue it
if(world.matrix[neighborRow][neighborCol] != 2) {
visited[neighborRow][neighborCol] = 1;
Cell neighborCell = {.row = neighborRow, .col = neighborCol};
enqueue(&q, neighborCell);
}
}
}
}
return false;
}
static void draw_callback(Canvas* canvas, void* ctx) {
furi_assert(ctx);
const BomberState* bomber_state = ctx;
furi_mutex_acquire(bomber_state->mutex, FuriWaitForever);
if(!BFS()) {
init();
}
canvas_clear(canvas);
canvas_draw_icon(canvas, world.endx * 10 + 4, world.endy * 10 + 2, &I_end);
if(world.running) {
for(size_t i = 0; i < WorldSizeY; i++) {
for(size_t j = 0; j < WorldSizeX; j++) {
switch(world.matrix[i][j]) {
case 0:
break;
case 1:
canvas_draw_icon(canvas, j * 10 + 4, i * 10 + 2, &I_box);
break;
case 2:
canvas_draw_icon(canvas, j * 10 + 4, i * 10 + 2, &I_unbreakbox);
break;
case 3:
canvas_draw_icon(canvas, j * 10 + 4, i * 10 + 2, &I_bomb0);
break;
case 4:
canvas_draw_icon(canvas, j * 10 + 4, i * 10 + 2, &I_bomb1);
break;
case 5:
canvas_draw_icon(canvas, j * 10 + 4, i * 10 + 2, &I_bomb2);
break;
case 6:
canvas_draw_icon(canvas, j * 10 + 4, i * 10 + 2, &I_explore);
world.matrix[i][j] = 0;
break;
}
}
}
if(world.player->side) {
canvas_draw_icon(
canvas, world.player->x * 10 + 4, world.player->y * 10 + 2, &I_playerright);
} else {
canvas_draw_icon(
canvas, world.player->x * 10 + 4, world.player->y * 10 + 2, &I_playerleft);
}
for(int i = 0; i < world.enemies_count; i++) {
if(world.enemies[i].level > 0) {
canvas_draw_icon(
canvas, world.enemies[i].x * 10 + 4, world.enemies[i].y * 10 + 2, &I_enemy1);
} else {
if(world.enemies[i].side) {
canvas_draw_icon(
canvas,
world.enemies[i].x * 10 + 4,
world.enemies[i].y * 10 + 2,
&I_enemyright);
} else {
canvas_draw_icon(
canvas,
world.enemies[i].x * 10 + 4,
world.enemies[i].y * 10 + 2,
&I_enemyleft);
}
}
}
} else {
canvas_set_font(canvas, FontPrimary);
if(world.player->x == world.endx && world.player->y == world.endy) {
if(world.level == 20) {
canvas_draw_str(canvas, 30, 35, "You win!");
} else {
canvas_draw_str(canvas, 30, 35, "Next level!");
char str[20];
intToStr(world.level, str);
canvas_draw_str(canvas, 90, 35, str);
}
} else {
canvas_draw_str(canvas, 30, 35, "You died :(");
}
}
furi_mutex_release(bomber_state->mutex);
}
static void input_callback(InputEvent* input_event, void* ctx) {
// Проверяем, что контекст не нулевой
furi_assert(ctx);
FuriMessageQueue* event_queue = ctx;
furi_message_queue_put(event_queue, input_event, FuriWaitForever);
}
int32_t bomberduck_app(void* p) {
UNUSED(p);
// Текущее событие типа InputEvent
InputEvent event;
// Очередь событий на 8 элементов размера InputEvent
FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(InputEvent));
BomberState* bomber_state = malloc(sizeof(BomberState));
bomber_state->mutex = furi_mutex_alloc(FuriMutexTypeNormal); // Alloc Mutex
if(!bomber_state->mutex) {
FURI_LOG_E("BomberDuck", "cannot create mutex\r\n");
furi_message_queue_free(event_queue);
free(bomber_state);
return 255;
}
DOLPHIN_DEED(DolphinDeedPluginGameStart);
// Создаем новый view port
ViewPort* view_port = view_port_alloc();
// Создаем callback отрисовки, без контекста
view_port_draw_callback_set(view_port, draw_callback, bomber_state);
// Создаем callback нажатий на клавиши, в качестве контекста передаем
// нашу очередь сообщений, чтоб запихивать в неё эти события
view_port_input_callback_set(view_port, input_callback, event_queue);
// Создаем GUI приложения
Gui* gui = furi_record_open(RECORD_GUI);
// Подключаем view port к 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);
init();
// Бесконечный цикл обработки очереди событий
while(1) {
if(furi_message_queue_get(event_queue, &event, 100) == FuriStatusOk) {
furi_mutex_acquire(bomber_state->mutex, FuriWaitForever);
// Если нажата кнопка "назад", то выходим из цикла, а следовательно и из приложения
if(event.type == InputTypePress) {
if(event.key == InputKeyOk) {
if(world.running) {
if(world.matrix[world.player->y][world.player->x] == 0 &&
world.bombs_count < 2) {
notification_message(notification, &bomb2);
world.matrix[world.player->y][world.player->x] = 3;
Bomb bomb = {world.player->x, world.player->y, furi_get_tick()};
world.bombs[world.bombs_count] = bomb;
world.bombs_count++;
}
} else {
init();
}
}
if(world.running) {
if(event.key == InputKeyUp) {
if(world.player->y > 0 &&
world.matrix[world.player->y - 1][world.player->x] == 0)
world.player->y--;
}
if(event.key == InputKeyDown) {
if(world.player->y < WorldSizeY - 1 &&
world.matrix[world.player->y + 1][world.player->x] == 0)
world.player->y++;
}
if(event.key == InputKeyLeft) {
world.player->side = 0;
if(world.player->x > 0 &&
world.matrix[world.player->y][world.player->x - 1] == 0)
world.player->x--;
}
if(event.key == InputKeyRight) {
world.player->side = 1;
if(world.player->x < WorldSizeX - 1 &&
world.matrix[world.player->y][world.player->x + 1] == 0)
world.player->x++;
}
}
} else if(event.type == InputTypeLong) {
if(event.key == InputKeyBack) {
break;
}
}
}
if(world.running) {
if(world.player->x == world.endx && world.player->y == world.endy) {
notification_message(notification, &end);
world.running = 0;
world.level += 1;
if(world.level % 5 == 0) {
DOLPHIN_DEED(DolphinDeedPluginGameWin);
}
}
for(int i = 0; i < world.bombs_count; i++) {
if(furi_get_tick() - world.bombs[i].planted >
(unsigned long)max((3000 - world.level * 150), 1000)) {
vibration = false;
world.matrix[world.bombs[i].y][world.bombs[i].x] = 6;
notification_message(notification, &bomb_explore);
for(int j = max(0, world.bombs[i].y - BombRange);
j < min(WorldSizeY, world.bombs[i].y + BombRange + 1);
j++) {
if(world.matrix[j][world.bombs[i].x] != 2) {
world.matrix[j][world.bombs[i].x] = 6;
if(j == world.player->y && world.bombs[i].x == world.player->x) {
notification_message(notification, &end);
world.running = 0;
}
for(int e = 0; e < world.enemies_count; e++) {
if(j == world.enemies[e].y &&
world.bombs[i].x == world.enemies[e].x) {
if(world.enemies[e].level > 0) {
world.enemies[e].level--;
} else {
for(int l = e; l < world.enemies_count - 1; l++) {
world.enemies[l] = world.enemies[l + 1];
}
world.enemies_count--;
}
}
}
}
}
for(int j = max(0, world.bombs[i].x - BombRange);
j < min(WorldSizeX, world.bombs[i].x + BombRange + 1);
j++) {
if(world.matrix[world.bombs[i].y][j] != 2) {
world.matrix[world.bombs[i].y][j] = 6;
if(world.bombs[i].y == world.player->y && j == world.player->x) {
notification_message(notification, &end);
world.running = 0;
}
for(int e = 0; e < world.enemies_count; e++) {
if(world.bombs[i].y == world.enemies[e].y &&
j == world.enemies[e].x) {
if(world.enemies[e].level > 0) {
world.enemies[e].level--;
} else {
for(int l = e; l < world.enemies_count - 1; l++) {
world.enemies[l] = world.enemies[l + 1];
}
world.enemies_count--;
}
}
}
}
}
for(int j = i; j < world.bombs_count - 1; j++) {
world.bombs[j] = world.bombs[j + 1];
}
world.bombs_count--;
} else if(
furi_get_tick() - world.bombs[i].planted >
(unsigned long)max((3000 - world.level * 150) * 2 / 3, 666) &&
world.matrix[world.bombs[i].y][world.bombs[i].x] != 5) {
world.matrix[world.bombs[i].y][world.bombs[i].x] = 5;
vibration = true;
} else if(
furi_get_tick() - world.bombs[i].planted >
(unsigned long)max((3000 - world.level * 150) / 3, 333) &&
world.matrix[world.bombs[i].y][world.bombs[i].x] != 4) {
world.matrix[world.bombs[i].y][world.bombs[i].x] = 4;
}
}
for(int e = 0; e < world.enemies_count; e++) {
if(world.player->y == world.enemies[e].y &&
world.player->x == world.enemies[e].x) {
notification_message(notification, &end);
world.running = 0;
}
}
for(int e = 0; e < world.enemies_count; e++) {
if(world.enemies[e].level > 0) {
if(furi_get_tick() - world.enemies[e].last >
(unsigned long)max((2000 - world.level * 100), 1000)) {
world.enemies[e].last = furi_get_tick();
int move = rand() % 4;
switch(move) {
case 0:
if(world.enemies[e].y > 0 &&
world.matrix[world.enemies[e].y - 1][world.enemies[e].x] != 2)
world.enemies[e].y--;
break;
case 1:
if(world.enemies[e].y < WorldSizeY - 1 &&
world.matrix[world.enemies[e].y + 1][world.enemies[e].x] != 2)
world.enemies[e].y++;
break;
case 2:
world.enemies[e].side = 0;
if(world.enemies[e].x > 0 &&
world.matrix[world.enemies[e].y][world.enemies[e].x - 1] != 2)
world.enemies[e].x--;
break;
case 3:
world.enemies[e].side = 1;
if(world.enemies[e].x < WorldSizeX - 1 &&
world.matrix[world.enemies[e].y][world.enemies[e].x + 1] != 2)
world.enemies[e].x++;
default:
break;
}
}
} else {
if(furi_get_tick() - world.enemies[e].last >
(unsigned long)max((1000 - world.level * 50), 500)) {
world.enemies[e].last = furi_get_tick();
int move = rand() % 4;
switch(move) {
case 0:
if(world.enemies[e].y > 0 &&
world.matrix[world.enemies[e].y - 1][world.enemies[e].x] == 0)
world.enemies[e].y--;
break;
case 1:
if(world.enemies[e].y < WorldSizeY - 1 &&
world.matrix[world.enemies[e].y + 1][world.enemies[e].x] == 0)
world.enemies[e].y++;
break;
case 2:
world.enemies[e].side = 0;
if(world.enemies[e].x > 0 &&
world.matrix[world.enemies[e].y][world.enemies[e].x - 1] == 0)
world.enemies[e].x--;
break;
case 3:
world.enemies[e].side = 1;
if(world.enemies[e].x < WorldSizeX - 1 &&
world.matrix[world.enemies[e].y][world.enemies[e].x + 1] == 0)
world.enemies[e].x++;
default:
break;
}
}
}
}
for(int e = 0; e < world.enemies_count; e++) {
for(int h = e + 1; h < world.enemies_count; h++) {
if(world.enemies[e].y == world.enemies[h].y &&
world.enemies[e].x == world.enemies[h].x) {
world.enemies[h].level++;
for(int l = e; l < world.enemies_count - 1; l++) {
world.enemies[l] = world.enemies[l + 1];
}
world.enemies_count--;
}
}
}
if(vibration) {
notification_message(notification, &vibr1);
}
}
view_port_update(view_port);
furi_mutex_release(bomber_state->mutex);
}
// Return to normal backlight settings
notification_message_block(notification, &sequence_display_backlight_enforce_auto);
furi_record_close(RECORD_NOTIFICATION);
// Специальная очистка памяти, занимаемой очередью
furi_message_queue_free(event_queue);
// Чистим созданные объекты, связанные с интерфейсом
gui_remove_view_port(gui, view_port);
view_port_free(view_port);
furi_mutex_free(bomber_state->mutex);
furi_record_close(RECORD_GUI);
free(bomber_state);
return 0;
}
+21
View File
@@ -14,6 +14,7 @@ enum HidDebugSubmenuIndex {
HidSubmenuIndexTikTok,
HidSubmenuIndexYTShorts,
HidSubmenuIndexMouse,
HidSubmenuIndexMouseClicker,
HidSubmenuIndexMouseJiggler,
};
@@ -44,6 +45,9 @@ static void hid_submenu_callback(void* context, uint32_t index) {
} else if(index == HidSubmenuIndexYTShorts) {
app->view_id = BtHidViewYTShorts;
view_dispatcher_switch_to_view(app->view_dispatcher, BtHidViewYTShorts);
} 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);
@@ -67,6 +71,7 @@ static void bt_hid_connection_status_changed_callback(BtStatus status, void* con
hid_numpad_set_connected_status(hid->hid_numpad, connected);
hid_media_set_connected_status(hid->hid_media, 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_tiktok_set_connected_status(hid->hid_tiktok, connected);
hid_ytshorts_set_connected_status(hid->hid_ytshorts, connected);
@@ -143,6 +148,12 @@ Hid* hid_alloc(HidTransport transport) {
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",
@@ -222,6 +233,14 @@ Hid* hid_app_alloc_view(void* context) {
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_exit_confirm_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(
@@ -259,6 +278,8 @@ void hid_free(Hid* app) {
hid_media_free(app->hid_media);
view_dispatcher_remove_view(app->view_dispatcher, HidViewMouse);
hid_mouse_free(app->hid_mouse);
view_dispatcher_remove_view(app->view_dispatcher, 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, BtHidViewTikTok);
+2
View File
@@ -25,6 +25,7 @@
#include "views/hid_mouse_jiggler.h"
#include "views/hid_tiktok.h"
#include "views/hid_ytshorts.h"
#include "views/hid_mouse_clicker.h"
#define HID_BT_KEYS_STORAGE_NAME ".bt_hid.keys"
@@ -48,6 +49,7 @@ struct Hid {
HidNumpad* hid_numpad;
HidMedia* hid_media;
HidMouse* hid_mouse;
HidMouseClicker* hid_mouse_clicker;
HidMouseJiggler* hid_mouse_jiggler;
HidTikTok* hid_tiktok;
HidYTShorts* hid_ytshorts;
+1
View File
@@ -6,6 +6,7 @@ typedef enum {
HidViewNumpad,
HidViewMedia,
HidViewMouse,
HidViewMouseClicker,
HidViewMouseJiggler,
BtHidViewTikTok,
BtHidViewYTShorts,
+214
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 != InputTypeRelease) {
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);
}
+14
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);
-1
View File
@@ -109,7 +109,6 @@ static void hid_ytshorts_draw_callback(Canvas* canvas, void* context) {
elements_multiline_text_aligned(canvas, 13, 62, AlignLeft, AlignBottom, "Hold to exit");
}
static void hid_ytshorts_reset_cursor(HidYTShorts* hid_ytshorts) {
// Set cursor to the phone's left up corner
// Delays to guarantee one packet per connection interval
+5
View File
@@ -3166,6 +3166,11 @@ int32_t swd_probe_app_main(void* p) {
furi_message_queue_free(app->event_queue);
furi_mutex_free(app->gui_mutex);
furi_mutex_free(app->swd_mutex);
// Reset GPIO pins to default state
for(int io = 0; io < 8; io++) {
furi_hal_gpio_init(gpios[io], GpioModeAnalog, GpioPullNo, GpioSpeedLow);
}
free(app);
furi_record_close(RECORD_GUI);
+6 -7
View File
@@ -30,7 +30,7 @@ static uint32_t get_keypress_delay(TokenAutomationFeature features) {
}
static void totp_type_code_worker_press_key(
uint8_t key,
uint16_t key,
TOTP_AUTOMATION_KEY_HANDLER key_press_fn,
TOTP_AUTOMATION_KEY_HANDLER key_release_fn,
TokenAutomationFeature features) {
@@ -47,8 +47,6 @@ void totp_type_code_worker_execute_automation(
TokenAutomationFeature features) {
furi_delay_ms(500);
uint8_t i = 0;
totp_type_code_worker_press_key(
HID_KEYBOARD_CAPS_LOCK, key_press_fn, key_release_fn, features);
while(i < code_buffer_size && code_buffer[i] != 0) {
uint8_t char_index = CONVERT_CHAR_TO_DIGIT(code_buffer[i]);
@@ -58,7 +56,11 @@ void totp_type_code_worker_execute_automation(
if(char_index > 35) break;
uint8_t hid_kb_key = hid_number_keys[char_index];
uint16_t hid_kb_key = hid_number_keys[char_index];
if(char_index > 9) {
hid_kb_key |= KEY_MOD_LEFT_SHIFT;
}
totp_type_code_worker_press_key(hid_kb_key, key_press_fn, key_release_fn, features);
furi_delay_ms(get_keystroke_delay(features));
i++;
@@ -74,7 +76,4 @@ void totp_type_code_worker_execute_automation(
furi_delay_ms(get_keystroke_delay(features));
totp_type_code_worker_press_key(HID_KEYBOARD_TAB, key_press_fn, key_release_fn, features);
}
totp_type_code_worker_press_key(
HID_KEYBOARD_CAPS_LOCK, key_press_fn, key_release_fn, features);
}