Merge branch 'UFW_dev' into subrem_new_app

This commit is contained in:
gid9798
2023-05-21 23:55:38 +03:00
20 changed files with 561 additions and 314 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

View File

@@ -12,3 +12,4 @@ ADD_SCENE(wifi_marauder, script_confirm_delete, ScriptConfirmDelete)
ADD_SCENE(wifi_marauder, script_stage_edit, ScriptStageEdit) ADD_SCENE(wifi_marauder, script_stage_edit, ScriptStageEdit)
ADD_SCENE(wifi_marauder, script_stage_add, ScriptStageAdd) ADD_SCENE(wifi_marauder, script_stage_add, ScriptStageAdd)
ADD_SCENE(wifi_marauder, script_stage_edit_list, ScriptStageEditList) ADD_SCENE(wifi_marauder, script_stage_edit_list, ScriptStageEditList)
ADD_SCENE(wifi_marauder, sniffpmkid_options, SniffPmkidOptions)

View File

@@ -0,0 +1,117 @@
#include "../wifi_marauder_app_i.h"
enum SubmenuIndex {
SubmenuIndexPassive,
SubmenuIndexActive,
SubmenuIndexTargetedPassive,
SubmenuIndexTargetedActive,
SubmenuIndexChannelPassive,
SubmenuIndexChannelActive,
};
static void wifi_marauder_scene_sniffpmkid_options_callback(void* context, uint32_t index) {
WifiMarauderApp* app = context;
app->is_custom_tx_string = false; // this will be set if needed by text input
switch(index) {
case SubmenuIndexPassive:
app->selected_tx_string = "sniffpmkid";
scene_manager_set_scene_state(
app->scene_manager, WifiMarauderSceneSniffPmkidOptions, index);
scene_manager_next_scene(app->scene_manager, WifiMarauderSceneConsoleOutput);
break;
case SubmenuIndexActive:
app->selected_tx_string = "sniffpmkid -d";
scene_manager_set_scene_state(
app->scene_manager, WifiMarauderSceneSniffPmkidOptions, index);
scene_manager_next_scene(app->scene_manager, WifiMarauderSceneConsoleOutput);
break;
case SubmenuIndexTargetedPassive:
app->selected_tx_string = "sniffpmkid -l";
scene_manager_set_scene_state(
app->scene_manager, WifiMarauderSceneSniffPmkidOptions, index);
scene_manager_next_scene(app->scene_manager, WifiMarauderSceneConsoleOutput);
break;
case SubmenuIndexTargetedActive:
app->selected_tx_string = "sniffpmkid -d -l";
scene_manager_set_scene_state(
app->scene_manager, WifiMarauderSceneSniffPmkidOptions, index);
scene_manager_next_scene(app->scene_manager, WifiMarauderSceneConsoleOutput);
break;
case SubmenuIndexChannelPassive:
app->selected_tx_string = "sniffpmkid -c";
scene_manager_set_scene_state(
app->scene_manager, WifiMarauderSceneSniffPmkidOptions, index);
scene_manager_next_scene(app->scene_manager, WifiMarauderSceneTextInput);
break;
case SubmenuIndexChannelActive:
app->selected_tx_string = "sniffpmkid -d -c";
scene_manager_set_scene_state(
app->scene_manager, WifiMarauderSceneSniffPmkidOptions, index);
scene_manager_next_scene(app->scene_manager, WifiMarauderSceneTextInput);
break;
}
}
void wifi_marauder_scene_sniffpmkid_options_on_enter(void* context) {
WifiMarauderApp* app = context;
Submenu* submenu = app->submenu;
submenu_set_header(submenu, "Sniff PMKID");
submenu_add_item(
submenu,
"Passive",
SubmenuIndexPassive,
wifi_marauder_scene_sniffpmkid_options_callback,
app);
submenu_add_item(
submenu,
"Active (Force Deauth)",
SubmenuIndexActive,
wifi_marauder_scene_sniffpmkid_options_callback,
app);
submenu_add_item(
submenu,
"Targeted Passive (List)",
SubmenuIndexTargetedPassive,
wifi_marauder_scene_sniffpmkid_options_callback,
app);
submenu_add_item(
submenu,
"Targeted Active (List)",
SubmenuIndexTargetedActive,
wifi_marauder_scene_sniffpmkid_options_callback,
app);
submenu_add_item(
submenu,
"On Channel # - Passive",
SubmenuIndexChannelPassive,
wifi_marauder_scene_sniffpmkid_options_callback,
app);
submenu_add_item(
submenu,
"On Channel # - Active",
SubmenuIndexChannelActive,
wifi_marauder_scene_sniffpmkid_options_callback,
app);
submenu_set_selected_item(
submenu,
scene_manager_get_scene_state(app->scene_manager, WifiMarauderSceneSniffPmkidOptions));
view_dispatcher_switch_to_view(app->view_dispatcher, WifiMarauderAppViewSubmenu);
}
bool wifi_marauder_scene_sniffpmkid_options_on_event(void* context, SceneManagerEvent event) {
//WifiMarauderApp* app = context;
UNUSED(context);
UNUSED(event);
bool consumed = false;
return consumed;
}
void wifi_marauder_scene_sniffpmkid_options_on_exit(void* context) {
WifiMarauderApp* app = context;
submenu_reset(app->submenu);
}

View File

@@ -97,13 +97,6 @@ const WifiMarauderItem items[NUM_MENU_ITEMS] = {
NO_ARGS, NO_ARGS,
FOCUS_CONSOLE_END, FOCUS_CONSOLE_END,
SHOW_STOPSCAN_TIP}, SHOW_STOPSCAN_TIP},
{"Sniff PMKID",
{"ap", "channel"},
2,
{"sniffpmkid -d -l", "sniffpmkid -c"},
TOGGLE_ARGS,
FOCUS_CONSOLE_END,
SHOW_STOPSCAN_TIP},
{"Channel", {"Channel",
{"get", "set"}, {"get", "set"},
2, 2,
@@ -161,6 +154,14 @@ static void wifi_marauder_scene_start_var_list_enter_callback(void* context, uin
return; return;
} }
if(app->selected_tx_string &&
strncmp("sniffpmkid", app->selected_tx_string, strlen("sniffpmkid")) == 0) {
// sniffpmkid submenu
view_dispatcher_send_custom_event(
app->view_dispatcher, WifiMarauderEventStartSniffPmkidOptions);
return;
}
// Select automation script // Select automation script
if(index == NUM_MENU_ITEMS - 2) { if(index == NUM_MENU_ITEMS - 2) {
view_dispatcher_send_custom_event( view_dispatcher_send_custom_event(
@@ -254,6 +255,10 @@ bool wifi_marauder_scene_start_on_event(void* context, SceneManagerEvent event)
scene_manager_set_scene_state( scene_manager_set_scene_state(
app->scene_manager, WifiMarauderSceneStart, app->selected_menu_index); app->scene_manager, WifiMarauderSceneStart, app->selected_menu_index);
scene_manager_next_scene(app->scene_manager, WifiMarauderSceneScriptSelect); scene_manager_next_scene(app->scene_manager, WifiMarauderSceneScriptSelect);
} else if(event.event == WifiMarauderEventStartSniffPmkidOptions) {
scene_manager_set_scene_state(
app->scene_manager, WifiMarauderSceneStart, app->selected_menu_index);
scene_manager_next_scene(app->scene_manager, WifiMarauderSceneSniffPmkidOptions);
} }
consumed = true; consumed = true;
} else if(event.type == SceneManagerEventTypeTick) { } else if(event.type == SceneManagerEventTypeTick) {

View File

@@ -4,7 +4,7 @@
extern "C" { extern "C" {
#endif #endif
#define WIFI_MARAUDER_APP_VERSION "v0.3.5" #define WIFI_MARAUDER_APP_VERSION "v0.3.6"
typedef struct WifiMarauderApp WifiMarauderApp; typedef struct WifiMarauderApp WifiMarauderApp;

View File

@@ -26,7 +26,7 @@
#include <lib/toolbox/path.h> #include <lib/toolbox/path.h>
#include <dialogs/dialogs.h> #include <dialogs/dialogs.h>
#define NUM_MENU_ITEMS (18) #define NUM_MENU_ITEMS (17)
#define WIFI_MARAUDER_TEXT_BOX_STORE_SIZE (4096) #define WIFI_MARAUDER_TEXT_BOX_STORE_SIZE (4096)
#define WIFI_MARAUDER_TEXT_INPUT_STORE_SIZE (512) #define WIFI_MARAUDER_TEXT_INPUT_STORE_SIZE (512)

View File

@@ -8,5 +8,6 @@ typedef enum {
WifiMarauderEventSaveDestinationMac, WifiMarauderEventSaveDestinationMac,
WifiMarauderEventStartSettingsInit, WifiMarauderEventStartSettingsInit,
WifiMarauderEventStartLogViewer, WifiMarauderEventStartLogViewer,
WifiMarauderEventStartScriptSelect WifiMarauderEventStartScriptSelect,
WifiMarauderEventStartSniffPmkidOptions
} WifiMarauderCustomEvent; } WifiMarauderCustomEvent;

View File

@@ -15,149 +15,215 @@ typedef struct {
const uint8_t y; const uint8_t y;
} WIFI_TextInputKey; } WIFI_TextInputKey;
typedef struct {
const WIFI_TextInputKey* rows[3];
const uint8_t keyboard_index;
} Keyboard;
typedef struct { typedef struct {
const char* header; const char* header;
char* text_buffer; char* text_buffer;
size_t text_buffer_size; size_t text_buffer_size;
size_t minimum_length;
bool clear_default_text; bool clear_default_text;
bool cursor_select;
size_t cursor_pos;
WIFI_TextInputCallback callback; WIFI_TextInputCallback callback;
void* callback_context; void* callback_context;
uint8_t selected_row; uint8_t selected_row;
uint8_t selected_column; uint8_t selected_column;
uint8_t selected_keyboard;
WIFI_TextInputValidatorCallback validator_callback; WIFI_TextInputValidatorCallback validator_callback;
void* validator_callback_context; void* validator_callback_context;
FuriString* validator_text; FuriString* validator_text;
bool valadator_message_visible; bool validator_message_visible;
} WIFI_TextInputModel; } WIFI_TextInputModel;
static const uint8_t keyboard_origin_x = 1; static const uint8_t keyboard_origin_x = 1;
static const uint8_t keyboard_origin_y = 29; static const uint8_t keyboard_origin_y = 29;
static const uint8_t keyboard_row_count = 4; static const uint8_t keyboard_row_count = 3;
static const uint8_t keyboard_count = 2;
#define ENTER_KEY '\r' #define ENTER_KEY '\r'
#define BACKSPACE_KEY '\b' #define BACKSPACE_KEY '\b'
#define SWITCH_KEYBOARD_KEY 0xfe
static const WIFI_TextInputKey keyboard_keys_row_1[] = { static const WIFI_TextInputKey keyboard_keys_row_1[] = {
{'{', 1, 0}, {'q', 1, 8},
{'(', 9, 0}, {'w', 10, 8},
{'[', 17, 0}, {'e', 19, 8},
{'|', 25, 0}, {'r', 28, 8},
{'@', 33, 0}, {'t', 37, 8},
{'&', 41, 0}, {'y', 46, 8},
{'#', 49, 0}, {'u', 55, 8},
{';', 57, 0}, {'i', 64, 8},
{'^', 65, 0}, {'o', 73, 8},
{'*', 73, 0}, {'p', 82, 8},
{'`', 81, 0}, {'0', 91, 8},
{'"', 89, 0}, {'1', 100, 8},
{'~', 97, 0}, {'2', 110, 8},
{'\'', 105, 0}, {'3', 120, 8},
{'.', 113, 0},
{'/', 120, 0},
}; };
static const WIFI_TextInputKey keyboard_keys_row_2[] = { static const WIFI_TextInputKey keyboard_keys_row_2[] = {
{'q', 1, 10}, {'a', 1, 20},
{'w', 9, 10}, {'s', 10, 20},
{'e', 17, 10}, {'d', 19, 20},
{'r', 25, 10}, {'f', 28, 20},
{'t', 33, 10}, {'g', 37, 20},
{'y', 41, 10}, {'h', 46, 20},
{'u', 49, 10}, {'j', 55, 20},
{'i', 57, 10}, {'k', 64, 20},
{'o', 65, 10}, {'l', 73, 20},
{'p', 73, 10}, {BACKSPACE_KEY, 82, 12},
{'0', 81, 10}, {'4', 100, 20},
{'1', 89, 10}, {'5', 110, 20},
{'2', 97, 10}, {'6', 120, 20},
{'3', 105, 10},
{'=', 113, 10},
{'-', 120, 10},
}; };
static const WIFI_TextInputKey keyboard_keys_row_3[] = { static const WIFI_TextInputKey keyboard_keys_row_3[] = {
{'a', 1, 21}, {SWITCH_KEYBOARD_KEY, 1, 23},
{'s', 9, 21}, {'z', 13, 32},
{'d', 18, 21}, {'x', 21, 32},
{'f', 25, 21}, {'c', 28, 32},
{'g', 33, 21}, {'v', 36, 32},
{'h', 41, 21}, {'b', 44, 32},
{'j', 49, 21}, {'n', 52, 32},
{'k', 57, 21}, {'m', 59, 32},
{'l', 65, 21}, {'_', 67, 32},
{BACKSPACE_KEY, 72, 13}, {ENTER_KEY, 74, 23},
{'4', 89, 21}, {'7', 100, 32},
{'5', 97, 21}, {'8', 110, 32},
{'6', 105, 21}, {'9', 120, 32},
{'$', 113, 21},
{'%', 120, 21},
}; };
static const WIFI_TextInputKey keyboard_keys_row_4[] = { static const WIFI_TextInputKey symbol_keyboard_keys_row_1[] = {
{'z', 1, 33}, {'!', 2, 8},
{'x', 9, 33}, {'@', 12, 8},
{'c', 18, 33}, {'#', 22, 8},
{'v', 25, 33}, {'$', 32, 8},
{'b', 33, 33}, {'%', 42, 8},
{'n', 41, 33}, {'^', 52, 8},
{'m', 49, 33}, {'&', 62, 8},
{'_', 57, 33}, {'(', 71, 8},
{ENTER_KEY, 64, 24}, {')', 81, 8},
{'7', 89, 33}, {'0', 91, 8},
{'8', 97, 33}, {'1', 100, 8},
{'9', 105, 33}, {'2', 110, 8},
{'!', 113, 33}, {'3', 120, 8},
{'+', 120, 33},
}; };
static uint8_t get_row_size(uint8_t row_index) { static const WIFI_TextInputKey symbol_keyboard_keys_row_2[] = {
{'~', 2, 20},
{'+', 12, 20},
{'-', 22, 20},
{'=', 32, 20},
{'[', 42, 20},
{']', 52, 20},
{'{', 62, 20},
{'}', 72, 20},
{BACKSPACE_KEY, 82, 12},
{'4', 100, 20},
{'5', 110, 20},
{'6', 120, 20},
};
static const WIFI_TextInputKey symbol_keyboard_keys_row_3[] = {
{SWITCH_KEYBOARD_KEY, 1, 23},
{'.', 15, 32},
{',', 29, 32},
{';', 41, 32},
{'`', 53, 32},
{'\'', 65, 32},
{ENTER_KEY, 74, 23},
{'7', 100, 32},
{'8', 110, 32},
{'9', 120, 32},
};
static const Keyboard keyboard = {
.rows =
{
keyboard_keys_row_1,
keyboard_keys_row_2,
keyboard_keys_row_3,
},
.keyboard_index = 0,
};
static const Keyboard symbol_keyboard = {
.rows =
{
symbol_keyboard_keys_row_1,
symbol_keyboard_keys_row_2,
symbol_keyboard_keys_row_3,
},
.keyboard_index = 1,
};
static const Keyboard* keyboards[] = {
&keyboard,
&symbol_keyboard,
};
static void switch_keyboard(WIFI_TextInputModel* model) {
model->selected_keyboard = (model->selected_keyboard + 1) % keyboard_count;
}
static uint8_t get_row_size(const Keyboard* keyboard, uint8_t row_index) {
uint8_t row_size = 0; uint8_t row_size = 0;
if(keyboard == &symbol_keyboard) {
switch(row_index + 1) { switch(row_index + 1) {
case 1: case 1:
row_size = sizeof(keyboard_keys_row_1) / sizeof(WIFI_TextInputKey); row_size = COUNT_OF(symbol_keyboard_keys_row_1);
break; break;
case 2: case 2:
row_size = sizeof(keyboard_keys_row_2) / sizeof(WIFI_TextInputKey); row_size = COUNT_OF(symbol_keyboard_keys_row_2);
break; break;
case 3: case 3:
row_size = sizeof(keyboard_keys_row_3) / sizeof(WIFI_TextInputKey); row_size = COUNT_OF(symbol_keyboard_keys_row_3);
break; break;
case 4: default:
row_size = sizeof(keyboard_keys_row_4) / sizeof(WIFI_TextInputKey); furi_crash(NULL);
break; }
} else {
switch(row_index + 1) {
case 1:
row_size = COUNT_OF(keyboard_keys_row_1);
break;
case 2:
row_size = COUNT_OF(keyboard_keys_row_2);
break;
case 3:
row_size = COUNT_OF(keyboard_keys_row_3);
break;
default:
furi_crash(NULL);
}
} }
return row_size; return row_size;
} }
static const WIFI_TextInputKey* get_row(uint8_t row_index) { static const WIFI_TextInputKey* get_row(const Keyboard* keyboard, uint8_t row_index) {
const WIFI_TextInputKey* row = NULL; const WIFI_TextInputKey* row = NULL;
if(row_index < 3) {
switch(row_index + 1) { row = keyboard->rows[row_index];
case 1: } else {
row = keyboard_keys_row_1; furi_crash(NULL);
break;
case 2:
row = keyboard_keys_row_2;
break;
case 3:
row = keyboard_keys_row_3;
break;
case 4:
row = keyboard_keys_row_4;
break;
} }
return row; return row;
} }
static char get_selected_char(WIFI_TextInputModel* model) { static char get_selected_char(WIFI_TextInputModel* model) {
return get_row(model->selected_row)[model->selected_column].text; return get_row(
keyboards[model->selected_keyboard], model->selected_row)[model->selected_column]
.text;
} }
static bool char_is_lowercase(char letter) { static bool char_is_lowercase(char letter) {
@@ -165,36 +231,9 @@ static bool char_is_lowercase(char letter) {
} }
static char char_to_uppercase(const char letter) { static char char_to_uppercase(const char letter) {
switch(letter) { if(letter == '_') {
case '_':
return 0x20; return 0x20;
break; } else if(char_is_lowercase(letter)) {
case '(':
return 0x29;
break;
case '{':
return 0x7d;
break;
case '[':
return 0x5d;
break;
case '/':
return 0x5c;
break;
case ';':
return 0x3a;
break;
case '.':
return 0x2c;
break;
case '!':
return 0x3f;
break;
case '<':
return 0x3e;
break;
}
if(char_is_lowercase(letter)) {
return (letter - 0x20); return (letter - 0x20);
} else { } else {
return letter; return letter;
@@ -202,86 +241,96 @@ static char char_to_uppercase(const char letter) {
} }
static void wifi_text_input_backspace_cb(WIFI_TextInputModel* model) { static void wifi_text_input_backspace_cb(WIFI_TextInputModel* model) {
uint8_t text_length = model->clear_default_text ? 1 : strlen(model->text_buffer); if(model->clear_default_text) {
if(text_length > 0) { model->text_buffer[0] = 0;
model->text_buffer[text_length - 1] = 0; model->cursor_pos = 0;
} else if(model->cursor_pos > 0) {
char* move = model->text_buffer + model->cursor_pos;
memmove(move - 1, move, strlen(move) + 1);
model->cursor_pos--;
} }
} }
static void wifi_text_input_view_draw_callback(Canvas* canvas, void* _model) { static void wifi_text_input_view_draw_callback(Canvas* canvas, void* _model) {
WIFI_TextInputModel* model = _model; WIFI_TextInputModel* model = _model;
//uint8_t text_length = model->text_buffer ? strlen(model->text_buffer) : 0; uint8_t text_length = model->text_buffer ? strlen(model->text_buffer) : 0;
uint8_t needed_string_width = canvas_width(canvas) - 8; uint8_t needed_string_width = canvas_width(canvas) - 8;
uint8_t start_pos = 4; uint8_t start_pos = 4;
const char* text = model->text_buffer; model->cursor_pos = model->cursor_pos > text_length ? text_length : model->cursor_pos;
size_t cursor_pos = model->cursor_pos;
canvas_clear(canvas); canvas_clear(canvas);
canvas_set_color(canvas, ColorBlack); canvas_set_color(canvas, ColorBlack);
canvas_draw_str(canvas, 2, 7, model->header); canvas_draw_str(canvas, 2, 8, model->header);
elements_slightly_rounded_frame(canvas, 1, 8, 126, 12); elements_slightly_rounded_frame(canvas, 1, 12, 126, 15);
if(canvas_string_width(canvas, text) > needed_string_width) { char buf[model->text_buffer_size + 1];
canvas_draw_str(canvas, start_pos, 17, "..."); if(model->text_buffer) {
start_pos += 6; strlcpy(buf, model->text_buffer, sizeof(buf));
needed_string_width -= 8;
}
while(text != 0 && canvas_string_width(canvas, text) > needed_string_width) {
text++;
} }
char* str = buf;
if(model->clear_default_text) { if(model->clear_default_text) {
elements_slightly_rounded_box( elements_slightly_rounded_box(
canvas, start_pos - 1, 14, canvas_string_width(canvas, text) + 2, 10); canvas, start_pos - 1, 14, canvas_string_width(canvas, str) + 2, 10);
canvas_set_color(canvas, ColorWhite); canvas_set_color(canvas, ColorWhite);
} else { } else {
canvas_draw_str(canvas, start_pos + canvas_string_width(canvas, text) + 1, 18, "|"); char* move = str + cursor_pos;
canvas_draw_str(canvas, start_pos + canvas_string_width(canvas, text) + 2, 18, "|"); memmove(move + 1, move, strlen(move) + 1);
str[cursor_pos] = '|';
} }
canvas_draw_str(canvas, start_pos, 17, text);
if(cursor_pos > 0 && canvas_string_width(canvas, str) > needed_string_width) {
canvas_draw_str(canvas, start_pos, 22, "...");
start_pos += 6;
needed_string_width -= 8;
for(uint32_t off = 0;
strlen(str) && canvas_string_width(canvas, str) > needed_string_width &&
off < cursor_pos;
off++) {
str++;
}
}
if(canvas_string_width(canvas, str) > needed_string_width) {
needed_string_width -= 4;
size_t len = strlen(str);
while(len && canvas_string_width(canvas, str) > needed_string_width) {
str[len--] = '\0';
}
strcat(str, "...");
}
canvas_draw_str(canvas, start_pos, 22, str);
canvas_set_font(canvas, FontKeyboard); canvas_set_font(canvas, FontKeyboard);
for(uint8_t row = 0; row <= keyboard_row_count; row++) { for(uint8_t row = 0; row < keyboard_row_count; row++) {
const uint8_t column_count = get_row_size(row); const uint8_t column_count = get_row_size(keyboards[model->selected_keyboard], row);
const WIFI_TextInputKey* keys = get_row(row); const WIFI_TextInputKey* keys = get_row(keyboards[model->selected_keyboard], row);
for(size_t column = 0; column < column_count; column++) { for(size_t column = 0; column < column_count; column++) {
bool selected = !model->cursor_select && model->selected_row == row &&
model->selected_column == column;
const Icon* icon = NULL;
if(keys[column].text == ENTER_KEY) { if(keys[column].text == ENTER_KEY) {
canvas_set_color(canvas, ColorBlack); icon = selected ? &I_KeySaveSelected_24x11 : &I_KeySave_24x11;
if(model->selected_row == row && model->selected_column == column) { } else if(keys[column].text == SWITCH_KEYBOARD_KEY) {
canvas_draw_icon( icon = selected ? &I_KeyKeyboardSelected_10x11 : &I_KeyKeyboard_10x11;
canvas,
keyboard_origin_x + keys[column].x,
keyboard_origin_y + keys[column].y,
&I_KeySaveSelected_24x11);
} else {
canvas_draw_icon(
canvas,
keyboard_origin_x + keys[column].x,
keyboard_origin_y + keys[column].y,
&I_KeySave_24x11);
}
} else if(keys[column].text == BACKSPACE_KEY) { } else if(keys[column].text == BACKSPACE_KEY) {
canvas_set_color(canvas, ColorBlack); icon = selected ? &I_KeyBackspaceSelected_16x9 : &I_KeyBackspace_16x9;
if(model->selected_row == row && model->selected_column == column) { }
canvas_draw_icon( canvas_set_color(canvas, ColorBlack);
canvas, if(icon != NULL) {
keyboard_origin_x + keys[column].x, canvas_draw_icon(
keyboard_origin_y + keys[column].y, canvas,
&I_KeyBackspaceSelected_16x9); keyboard_origin_x + keys[column].x,
} else { keyboard_origin_y + keys[column].y,
canvas_draw_icon( icon);
canvas,
keyboard_origin_x + keys[column].x,
keyboard_origin_y + keys[column].y,
&I_KeyBackspace_16x9);
}
} else { } else {
if(model->selected_row == row && model->selected_column == column) { if(selected) {
canvas_set_color(canvas, ColorBlack);
canvas_draw_box( canvas_draw_box(
canvas, canvas,
keyboard_origin_x + keys[column].x - 1, keyboard_origin_x + keys[column].x - 1,
@@ -289,19 +338,25 @@ static void wifi_text_input_view_draw_callback(Canvas* canvas, void* _model) {
7, 7,
10); 10);
canvas_set_color(canvas, ColorWhite); canvas_set_color(canvas, ColorWhite);
} else {
canvas_set_color(canvas, ColorBlack);
} }
canvas_draw_glyph( if(model->clear_default_text || text_length == 0) {
canvas, canvas_draw_glyph(
keyboard_origin_x + keys[column].x, canvas,
keyboard_origin_y + keys[column].y, keyboard_origin_x + keys[column].x,
keys[column].text); keyboard_origin_y + keys[column].y,
char_to_uppercase(keys[column].text));
} else {
canvas_draw_glyph(
canvas,
keyboard_origin_x + keys[column].x,
keyboard_origin_y + keys[column].y,
keys[column].text);
}
} }
} }
} }
if(model->valadator_message_visible) { if(model->validator_message_visible) {
canvas_set_font(canvas, FontSecondary); canvas_set_font(canvas, FontSecondary);
canvas_set_color(canvas, ColorWhite); canvas_set_color(canvas, ColorWhite);
canvas_draw_box(canvas, 8, 10, 110, 48); canvas_draw_box(canvas, 8, 10, 110, 48);
@@ -319,37 +374,69 @@ static void
UNUSED(wifi_text_input); UNUSED(wifi_text_input);
if(model->selected_row > 0) { if(model->selected_row > 0) {
model->selected_row--; model->selected_row--;
if(model->selected_column > get_row_size(model->selected_row) - 6) { if(model->selected_row == 0 &&
model->selected_column >
get_row_size(keyboards[model->selected_keyboard], model->selected_row) - 6) {
model->selected_column = model->selected_column + 1; model->selected_column = model->selected_column + 1;
} }
if(model->selected_row == 1 &&
model->selected_keyboard == symbol_keyboard.keyboard_index) {
if(model->selected_column > 5)
model->selected_column += 2;
else if(model->selected_column > 1)
model->selected_column += 1;
}
} else {
model->cursor_select = true;
model->clear_default_text = false;
} }
} }
static void static void
wifi_text_input_handle_down(WIFI_TextInput* wifi_text_input, WIFI_TextInputModel* model) { wifi_text_input_handle_down(WIFI_TextInput* wifi_text_input, WIFI_TextInputModel* model) {
UNUSED(wifi_text_input); UNUSED(wifi_text_input);
if(model->selected_row < keyboard_row_count - 1) { if(model->cursor_select) {
model->cursor_select = false;
} else if(model->selected_row < keyboard_row_count - 1) {
model->selected_row++; model->selected_row++;
if(model->selected_column > get_row_size(model->selected_row) - 4) { if(model->selected_row == 1 &&
model->selected_column >
get_row_size(keyboards[model->selected_keyboard], model->selected_row) - 4) {
model->selected_column = model->selected_column - 1; model->selected_column = model->selected_column - 1;
} }
if(model->selected_row == 2 &&
model->selected_keyboard == symbol_keyboard.keyboard_index) {
if(model->selected_column > 7)
model->selected_column -= 2;
else if(model->selected_column > 1)
model->selected_column -= 1;
}
} }
} }
static void static void
wifi_text_input_handle_left(WIFI_TextInput* wifi_text_input, WIFI_TextInputModel* model) { wifi_text_input_handle_left(WIFI_TextInput* wifi_text_input, WIFI_TextInputModel* model) {
UNUSED(wifi_text_input); UNUSED(wifi_text_input);
if(model->selected_column > 0) { if(model->cursor_select) {
if(model->cursor_pos > 0) {
model->cursor_pos = CLAMP(model->cursor_pos - 1, strlen(model->text_buffer), 0u);
}
} else if(model->selected_column > 0) {
model->selected_column--; model->selected_column--;
} else { } else {
model->selected_column = get_row_size(model->selected_row) - 1; model->selected_column =
get_row_size(keyboards[model->selected_keyboard], model->selected_row) - 1;
} }
} }
static void static void
wifi_text_input_handle_right(WIFI_TextInput* wifi_text_input, WIFI_TextInputModel* model) { wifi_text_input_handle_right(WIFI_TextInput* wifi_text_input, WIFI_TextInputModel* model) {
UNUSED(wifi_text_input); UNUSED(wifi_text_input);
if(model->selected_column < get_row_size(model->selected_row) - 1) { if(model->cursor_select) {
model->cursor_pos = CLAMP(model->cursor_pos + 1, strlen(model->text_buffer), 0u);
} else if(
model->selected_column <
get_row_size(keyboards[model->selected_keyboard], model->selected_row) - 1) {
model->selected_column++; model->selected_column++;
} else { } else {
model->selected_column = 0; model->selected_column = 0;
@@ -359,35 +446,49 @@ static void
static void wifi_text_input_handle_ok( static void wifi_text_input_handle_ok(
WIFI_TextInput* wifi_text_input, WIFI_TextInput* wifi_text_input,
WIFI_TextInputModel* model, WIFI_TextInputModel* model,
bool shift) { InputType type) {
if(model->cursor_select) return;
bool shift = type == InputTypeLong;
bool repeat = type == InputTypeRepeat;
char selected = get_selected_char(model); char selected = get_selected_char(model);
uint8_t text_length = strlen(model->text_buffer); size_t text_length = strlen(model->text_buffer);
if(shift) {
selected = char_to_uppercase(selected);
}
if(selected == ENTER_KEY) { if(selected == ENTER_KEY) {
if(model->validator_callback && if(model->validator_callback &&
(!model->validator_callback( (!model->validator_callback(
model->text_buffer, model->validator_text, model->validator_callback_context))) { model->text_buffer, model->validator_text, model->validator_callback_context))) {
model->valadator_message_visible = true; model->validator_message_visible = true;
furi_timer_start(wifi_text_input->timer, furi_kernel_get_tick_frequency() * 4); furi_timer_start(wifi_text_input->timer, furi_kernel_get_tick_frequency() * 4);
} else if(model->callback != 0 && text_length > 0) { } else if(model->callback != 0 && text_length >= model->minimum_length) {
model->callback(model->callback_context); model->callback(model->callback_context);
} }
} else if(selected == BACKSPACE_KEY) { } else if(selected == SWITCH_KEYBOARD_KEY) {
wifi_text_input_backspace_cb(model); switch_keyboard(model);
} else { } else {
if(model->clear_default_text) { if(selected == BACKSPACE_KEY) {
text_length = 0; wifi_text_input_backspace_cb(model);
} } else if(!repeat) {
if(text_length < (model->text_buffer_size - 1)) { if(model->clear_default_text) {
model->text_buffer[text_length] = selected; text_length = 0;
model->text_buffer[text_length + 1] = 0; }
if(text_length < (model->text_buffer_size - 1)) {
if(shift != (text_length == 0)) {
selected = char_to_uppercase(selected);
}
if(model->clear_default_text) {
model->text_buffer[0] = selected;
model->text_buffer[1] = '\0';
model->cursor_pos = 1;
} else {
char* move = model->text_buffer + model->cursor_pos;
memmove(move + 1, move, strlen(move) + 1);
model->text_buffer[model->cursor_pos] = selected;
model->cursor_pos++;
}
}
} }
model->clear_default_text = false;
} }
model->clear_default_text = false;
} }
static bool wifi_text_input_view_input_callback(InputEvent* event, void* context) { static bool wifi_text_input_view_input_callback(InputEvent* event, void* context) {
@@ -400,8 +501,8 @@ static bool wifi_text_input_view_input_callback(InputEvent* event, void* context
WIFI_TextInputModel* model = view_get_model(wifi_text_input->view); WIFI_TextInputModel* model = view_get_model(wifi_text_input->view);
if((!(event->type == InputTypePress) && !(event->type == InputTypeRelease)) && if((!(event->type == InputTypePress) && !(event->type == InputTypeRelease)) &&
model->valadator_message_visible) { model->validator_message_visible) {
model->valadator_message_visible = false; model->validator_message_visible = false;
consumed = true; consumed = true;
} else if(event->type == InputTypeShort) { } else if(event->type == InputTypeShort) {
consumed = true; consumed = true;
@@ -419,7 +520,7 @@ static bool wifi_text_input_view_input_callback(InputEvent* event, void* context
wifi_text_input_handle_right(wifi_text_input, model); wifi_text_input_handle_right(wifi_text_input, model);
break; break;
case InputKeyOk: case InputKeyOk:
wifi_text_input_handle_ok(wifi_text_input, model, false); wifi_text_input_handle_ok(wifi_text_input, model, event->type);
break; break;
default: default:
consumed = false; consumed = false;
@@ -441,7 +542,7 @@ static bool wifi_text_input_view_input_callback(InputEvent* event, void* context
wifi_text_input_handle_right(wifi_text_input, model); wifi_text_input_handle_right(wifi_text_input, model);
break; break;
case InputKeyOk: case InputKeyOk:
wifi_text_input_handle_ok(wifi_text_input, model, true); wifi_text_input_handle_ok(wifi_text_input, model, event->type);
break; break;
case InputKeyBack: case InputKeyBack:
wifi_text_input_backspace_cb(model); wifi_text_input_backspace_cb(model);
@@ -465,6 +566,9 @@ static bool wifi_text_input_view_input_callback(InputEvent* event, void* context
case InputKeyRight: case InputKeyRight:
wifi_text_input_handle_right(wifi_text_input, model); wifi_text_input_handle_right(wifi_text_input, model);
break; break;
case InputKeyOk:
wifi_text_input_handle_ok(wifi_text_input, model, event->type);
break;
case InputKeyBack: case InputKeyBack:
wifi_text_input_backspace_cb(model); wifi_text_input_backspace_cb(model);
break; break;
@@ -487,7 +591,7 @@ void wifi_text_input_timer_callback(void* context) {
with_view_model( with_view_model(
wifi_text_input->view, wifi_text_input->view,
WIFI_TextInputModel * model, WIFI_TextInputModel * model,
{ model->valadator_message_visible = false; }, { model->validator_message_visible = false; },
true); true);
} }
@@ -505,7 +609,12 @@ WIFI_TextInput* wifi_text_input_alloc() {
with_view_model( with_view_model(
wifi_text_input->view, wifi_text_input->view,
WIFI_TextInputModel * model, WIFI_TextInputModel * model,
{ model->validator_text = furi_string_alloc(); }, {
model->validator_text = furi_string_alloc();
model->minimum_length = 1;
model->cursor_pos = 0;
model->cursor_select = false;
},
false); false);
wifi_text_input_reset(wifi_text_input); wifi_text_input_reset(wifi_text_input);
@@ -537,11 +646,14 @@ void wifi_text_input_reset(WIFI_TextInput* wifi_text_input) {
wifi_text_input->view, wifi_text_input->view,
WIFI_TextInputModel * model, WIFI_TextInputModel * model,
{ {
model->text_buffer_size = 0;
model->header = ""; model->header = "";
model->selected_row = 0; model->selected_row = 0;
model->selected_column = 0; model->selected_column = 0;
model->selected_keyboard = 0;
model->minimum_length = 1;
model->clear_default_text = false; model->clear_default_text = false;
model->cursor_pos = 0;
model->cursor_select = false;
model->text_buffer = NULL; model->text_buffer = NULL;
model->text_buffer_size = 0; model->text_buffer_size = 0;
model->callback = NULL; model->callback = NULL;
@@ -549,7 +661,7 @@ void wifi_text_input_reset(WIFI_TextInput* wifi_text_input) {
model->validator_callback = NULL; model->validator_callback = NULL;
model->validator_callback_context = NULL; model->validator_callback_context = NULL;
furi_string_reset(model->validator_text); furi_string_reset(model->validator_text);
model->valadator_message_visible = false; model->validator_message_visible = false;
}, },
true); true);
} }
@@ -575,15 +687,28 @@ void wifi_text_input_set_result_callback(
model->text_buffer = text_buffer; model->text_buffer = text_buffer;
model->text_buffer_size = text_buffer_size; model->text_buffer_size = text_buffer_size;
model->clear_default_text = clear_default_text; model->clear_default_text = clear_default_text;
model->cursor_select = false;
if(text_buffer && text_buffer[0] != '\0') { if(text_buffer && text_buffer[0] != '\0') {
model->cursor_pos = strlen(text_buffer);
// Set focus on Save // Set focus on Save
model->selected_row = 2; model->selected_row = 2;
model->selected_column = 8; model->selected_column = 9;
model->selected_keyboard = 0;
} else {
model->cursor_pos = 0;
} }
}, },
true); true);
} }
void wifi_text_input_set_minimum_length(WIFI_TextInput* wifi_text_input, size_t minimum_length) {
with_view_model(
wifi_text_input->view,
WIFI_TextInputModel * model,
{ model->minimum_length = minimum_length; },
true);
}
void wifi_text_input_set_validator( void wifi_text_input_set_validator(
WIFI_TextInput* wifi_text_input, WIFI_TextInput* wifi_text_input,
WIFI_TextInputValidatorCallback callback, WIFI_TextInputValidatorCallback callback,

View File

@@ -22,27 +22,27 @@ WIFI_TextInput* wifi_text_input_alloc();
/** Deinitialize and free text input /** Deinitialize and free text input
* *
* @param wifi_text_input WIFI_TextInput instance * @param text_input WIFI_TextInput instance
*/ */
void wifi_text_input_free(WIFI_TextInput* wifi_text_input); void wifi_text_input_free(WIFI_TextInput* text_input);
/** Clean text input view Note: this function does not free memory /** Clean text input view Note: this function does not free memory
* *
* @param wifi_text_input Text input instance * @param text_input Text input instance
*/ */
void wifi_text_input_reset(WIFI_TextInput* wifi_text_input); void wifi_text_input_reset(WIFI_TextInput* text_input);
/** Get text input view /** Get text input view
* *
* @param wifi_text_input WIFI_TextInput instance * @param text_input WIFI_TextInput instance
* *
* @return View instance that can be used for embedding * @return View instance that can be used for embedding
*/ */
View* wifi_text_input_get_view(WIFI_TextInput* wifi_text_input); View* wifi_text_input_get_view(WIFI_TextInput* text_input);
/** Set text input result callback /** Set text input result callback
* *
* @param wifi_text_input WIFI_TextInput instance * @param text_input WIFI_TextInput instance
* @param callback callback fn * @param callback callback fn
* @param callback_context callback context * @param callback_context callback context
* @param text_buffer pointer to YOUR text buffer, that we going * @param text_buffer pointer to YOUR text buffer, that we going
@@ -53,7 +53,7 @@ View* wifi_text_input_get_view(WIFI_TextInput* wifi_text_input);
* event * event
*/ */
void wifi_text_input_set_result_callback( void wifi_text_input_set_result_callback(
WIFI_TextInput* wifi_text_input, WIFI_TextInput* text_input,
WIFI_TextInputCallback callback, WIFI_TextInputCallback callback,
void* callback_context, void* callback_context,
char* text_buffer, char* text_buffer,
@@ -61,21 +61,22 @@ void wifi_text_input_set_result_callback(
bool clear_default_text); bool clear_default_text);
void wifi_text_input_set_validator( void wifi_text_input_set_validator(
WIFI_TextInput* wifi_text_input, WIFI_TextInput* text_input,
WIFI_TextInputValidatorCallback callback, WIFI_TextInputValidatorCallback callback,
void* callback_context); void* callback_context);
WIFI_TextInputValidatorCallback void wifi_text_input_set_minimum_length(WIFI_TextInput* text_input, size_t minimum_length);
wifi_text_input_get_validator_callback(WIFI_TextInput* wifi_text_input);
void* wifi_text_input_get_validator_callback_context(WIFI_TextInput* wifi_text_input); WIFI_TextInputValidatorCallback wifi_text_input_get_validator_callback(WIFI_TextInput* text_input);
void* wifi_text_input_get_validator_callback_context(WIFI_TextInput* text_input);
/** Set text input header text /** Set text input header text
* *
* @param wifi_text_input WIFI_TextInput instance * @param text_input WIFI_TextInput instance
* @param text text to be shown * @param text text to be shown
*/ */
void wifi_text_input_set_header_text(WIFI_TextInput* wifi_text_input, const char* text); void wifi_text_input_set_header_text(WIFI_TextInput* text_input, const char* text);
#ifdef __cplusplus #ifdef __cplusplus
} }

View File

@@ -23,6 +23,14 @@ static void subghz_remote_app_tick_event_callback(void* context) {
SubGhzRemoteApp* subghz_remote_app_alloc() { SubGhzRemoteApp* subghz_remote_app_alloc() {
SubGhzRemoteApp* app = malloc(sizeof(SubGhzRemoteApp)); SubGhzRemoteApp* app = malloc(sizeof(SubGhzRemoteApp));
Storage* storage = furi_record_open(RECORD_STORAGE);
storage_common_migrate(storage, EXT_PATH("unirf"), SUBREM_APP_FOLDER);
if(!storage_simply_mkdir(storage, SUBREM_APP_FOLDER)) {
//FURI_LOG_E(TAG, "Could not create folder %s", SUBREM_APP_FOLDER);
}
furi_record_close(RECORD_STORAGE);
// Enable power for External CC1101 if it is connected // Enable power for External CC1101 if it is connected
furi_hal_subghz_enable_ext_power(); furi_hal_subghz_enable_ext_power();
// Auto switch to internal radio if external radio is not available // Auto switch to internal radio if external radio is not available

View File

@@ -9,6 +9,6 @@ App(
"desktop", "desktop",
"loader", "loader",
"power", "power",
"namechange_srv", "namechanger_srv",
], ],
) )

View File

@@ -1,7 +1,7 @@
App( App(
appid="namechange_srv", appid="namechanger_srv",
apptype=FlipperAppType.STARTUP, apptype=FlipperAppType.STARTUP,
entry_point="namechange_on_system_start", entry_point="namechanger_on_system_start",
requires=["storage", "cli", "bt"], requires=["storage", "cli", "bt"],
conflicts=["updater"], conflicts=["updater"],
order=600, order=600,

View File

@@ -1,46 +0,0 @@
#include <furi_hal.h>
#include <cli/cli.h>
#include <cli/cli_vcp.h>
#include <storage/storage.h>
#include <toolbox/namechanger.h>
#include <bt/bt_service/bt.h>
int32_t namechange_on_system_start(void* p) {
UNUSED(p);
if(furi_hal_rtc_get_boot_mode() != FuriHalRtcBootModeNormal) {
return 0;
}
// Wait for all required services to start and create their records
uint8_t timeout = 0;
while(!furi_record_exists(RECORD_CLI) || !furi_record_exists(RECORD_BT) ||
!furi_record_exists(RECORD_STORAGE)) {
timeout++;
if(timeout > 250) {
return 0;
}
furi_delay_ms(5);
}
// Hehe bad code now here, bad bad bad, very bad, bad example, dont take it, make it better
if(NameChanger_Init()) {
Cli* cli = furi_record_open(RECORD_CLI);
cli_session_close(cli);
furi_delay_ms(2); // why i added delays here
cli_session_open(cli, &cli_vcp);
furi_record_close(RECORD_CLI);
furi_delay_ms(3);
Bt* bt = furi_record_open(RECORD_BT);
if(!bt_set_profile(bt, BtProfileSerial)) {
//FURI_LOG_D(TAG, "Failed to touch bluetooth to name change");
}
furi_record_close(RECORD_BT);
bt = NULL;
furi_delay_ms(3);
}
return 0;
}

View File

@@ -1,10 +1,15 @@
#include "namechanger.h" #include "namechanger.h"
#include <furi_hal.h>
#include <furi_hal_version.h> #include <furi_hal_version.h>
#include <cli/cli.h>
#include <cli/cli_vcp.h>
#include <bt/bt_service/bt.h>
#include <storage/storage.h>
#include <flipper_format/flipper_format.h> #include <flipper_format/flipper_format.h>
#define TAG "NameChanger" #define TAG "NameChanger"
bool NameChanger_Init() { static bool namechanger_init() {
Storage* storage = furi_record_open(RECORD_STORAGE); Storage* storage = furi_record_open(RECORD_STORAGE);
// Kostil + velosiped = top ficha // Kostil + velosiped = top ficha
@@ -65,3 +70,42 @@ bool NameChanger_Init() {
return res; return res;
} }
int32_t namechanger_on_system_start(void* p) {
UNUSED(p);
if(furi_hal_rtc_get_boot_mode() != FuriHalRtcBootModeNormal) {
return 0;
}
// Wait for all required services to start and create their records
uint8_t timeout = 0;
while(!furi_record_exists(RECORD_CLI) || !furi_record_exists(RECORD_BT) ||
!furi_record_exists(RECORD_STORAGE)) {
timeout++;
if(timeout > 250) {
return 0;
}
furi_delay_ms(5);
}
// Hehe bad code now here, bad bad bad, very bad, bad example, dont take it, make it better
if(namechanger_init()) {
Cli* cli = furi_record_open(RECORD_CLI);
cli_session_close(cli);
furi_delay_ms(2); // why i added delays here
cli_session_open(cli, &cli_vcp);
furi_record_close(RECORD_CLI);
furi_delay_ms(3);
Bt* bt = furi_record_open(RECORD_BT);
if(!bt_set_profile(bt, BtProfileSerial)) {
//FURI_LOG_D(TAG, "Failed to touch bluetooth to name change");
}
furi_record_close(RECORD_BT);
bt = NULL;
furi_delay_ms(3);
}
return 0;
}

View File

@@ -0,0 +1,5 @@
#pragma once
#define NAMECHANGER_HEADER "Flipper Name File"
#define NAMECHANGER_VERSION 1
#define NAMECHANGER_PATH EXT_PATH("dolphin/name.settings")

View File

@@ -1,7 +1,7 @@
#include <furi.h> #include <furi.h>
#include <gui/modules/popup.h> #include <gui/modules/popup.h>
#include <gui/scene_manager.h> #include <gui/scene_manager.h>
#include <toolbox/namechanger.h> #include <namechanger/namechanger.h>
#include <flipper_format/flipper_format.h> #include <flipper_format/flipper_format.h>
#include <power/power_service/power.h> #include <power/power_service/power.h>

View File

@@ -436,7 +436,11 @@ static bool hid_send_report(uint8_t report_id) {
if((hid_semaphore == NULL) || (hid_connected == false)) return false; if((hid_semaphore == NULL) || (hid_connected == false)) return false;
if((boot_protocol == true) && (report_id != ReportIdKeyboard)) return false; if((boot_protocol == true) && (report_id != ReportIdKeyboard)) return false;
furi_check(furi_semaphore_acquire(hid_semaphore, FuriWaitForever) == FuriStatusOk); FuriStatus status = furi_semaphore_acquire(hid_semaphore, HID_INTERVAL * 2);
if(status == FuriStatusErrorTimeout) {
return false;
}
furi_check(status == FuriStatusOk);
if(hid_connected == false) { if(hid_connected == false) {
return false; return false;
} }

View File

@@ -1,18 +0,0 @@
#pragma once
#define NAMECHANGER_HEADER "Flipper Name File"
#define NAMECHANGER_VERSION 1
#define NAMECHANGER_PATH EXT_PATH("dolphin/name.settings")
#include "stdbool.h"
#ifdef __cplusplus
extern "C" {
#endif
// Initializes the name changer. (Load name file, apply changes)
bool NameChanger_Init();
#ifdef __cplusplus
}
#endif

View File

@@ -1,7 +1,7 @@
import os import os
from dataclasses import dataclass, field from dataclasses import dataclass, field
from enum import Enum from enum import Enum
from typing import Callable, List, Optional, Tuple from typing import Callable, List, Optional, Tuple, Union
class FlipperManifestException(Exception): class FlipperManifestException(Exception):
@@ -56,7 +56,7 @@ class FlipperApplication:
# .fap-specific # .fap-specific
sources: List[str] = field(default_factory=lambda: ["*.c*"]) sources: List[str] = field(default_factory=lambda: ["*.c*"])
fap_version: str | Tuple[int] = "0.1" fap_version: Union[str, Tuple[int]] = "0.1"
fap_icon: Optional[str] = None fap_icon: Optional[str] = None
fap_libs: List[str] = field(default_factory=list) fap_libs: List[str] = field(default_factory=list)
fap_category: str = "" fap_category: str = ""