From 09bcf9ac8a05347ff01734580545faa23b842a9b Mon Sep 17 00:00:00 2001 From: Hoggormino <125792648+Hoggormino@users.noreply.github.com> Date: Wed, 16 Sep 2026 16:50:00 +0200 Subject: [PATCH 1/2] Guard targets with unknown profiles and a full target list A tag whose type code is not in the profile table gets a zeroed profile: kind Unknown, 0x0, Mono. NFC scan, targets.txt and web_job.txt (and the unreachable phone-sync code) all create such targets, and because only Segment was excluded they were offered Set Text, Set Image and WiFi Plugins. Set Text on a 0x0 job reaches malloc(0), which the firmware treats as a fatal error. - Offer Set Text, Set Image and WiFi Plugins only for dot-matrix profiles found in the table; the app has no display size for others. - Show Tag Info says the type is not in the profile table and which actions are hidden, instead of reporting "Size: 0x0" and "Color: Mono". - Refuse a 0x0 text job before either sized text path allocates. - Manual barcode entry with all 16 slots used now shows "Target List Full". It used to open Target Actions with selected_target -1, where Show Tag Info and LED Test index targets[-1]. - Widget buttons call back on press, short press and release, so the Back button went back twice. Act on the short press only. Co-Authored-By: Claude Opus 5 --- scenes/tagtinker_scene_barcode_input.c | 27 +++++++++++++++++++++---- scenes/tagtinker_scene_target_actions.c | 21 +++++++++++++++++++ scenes/tagtinker_scene_transmit.c | 5 +++++ tagtinker_app.c | 5 ++++- 4 files changed, 53 insertions(+), 5 deletions(-) diff --git a/scenes/tagtinker_scene_barcode_input.c b/scenes/tagtinker_scene_barcode_input.c index 46ef8bc..ea508e7 100644 --- a/scenes/tagtinker_scene_barcode_input.c +++ b/scenes/tagtinker_scene_barcode_input.c @@ -6,7 +6,8 @@ static void unsupported_tag_back_cb(GuiButtonType type, InputType input_type, void* ctx) { UNUSED(type); - UNUSED(input_type); + /* Widget buttons report press, short and release alike; act once. */ + if(input_type != InputTypeShort) return; TagTinkerApp* app = ctx; scene_manager_previous_scene(app->scene_manager); } @@ -60,12 +61,30 @@ bool tagtinker_scene_barcode_input_on_event(void* ctx, SceneManagerEvent event) - app->selected_target = tagtinker_ensure_target(app, app->barcode); + int8_t idx = tagtinker_ensure_target(app, app->barcode); - if(app->selected_target >= 0) { - tagtinker_select_target(app, (uint8_t)app->selected_target); + if(idx < 0) { + /* The barcode already passed validation, so the only way left to fail + * is a full target list. Do not open the target actions with index -1. */ + widget_reset(app->widget); + widget_add_string_element( + app->widget, 64, 10, AlignCenter, AlignTop, FontPrimary, "Target List Full"); + widget_add_string_multiline_element( + app->widget, + 64, + 30, + AlignCenter, + AlignTop, + FontSecondary, + "Delete a saved tag\nto add a new one."); + widget_add_button_element( + app->widget, GuiButtonTypeLeft, "Back", unsupported_tag_back_cb, app); + view_dispatcher_switch_to_view(app->view_dispatcher, TagTinkerViewWidget); + return true; } + tagtinker_select_target(app, (uint8_t)idx); + uint32_t target_scene = scene_manager_get_scene_state( app->scene_manager, TagTinkerSceneBarcodeInput); scene_manager_next_scene(app->scene_manager, target_scene); diff --git a/scenes/tagtinker_scene_target_actions.c b/scenes/tagtinker_scene_target_actions.c index 979802b..e39e022 100644 --- a/scenes/tagtinker_scene_target_actions.c +++ b/scenes/tagtinker_scene_target_actions.c @@ -29,6 +29,27 @@ static void show_target_details(TagTinkerApp* app, const TagTinkerTarget* target text_box_set_focus(app->text_box, TextBoxFocusStart); static char details_buf[256]; + + if(!target->profile.known) { + /* No table entry: size and colour are unknown, not 0x0 and Mono. */ + snprintf( + details_buf, + sizeof(details_buf), + "--- Tag Info ---\n" + "Model: Unknown\n" + "Type: %u\n" + "Not in the profile table,\n" + "so Set Text, Set Image\n" + "and WiFi Plugins are\n" + "hidden for this tag.\n" + "Barcode:\n%s", + target->profile.type_code, + target->barcode); + text_box_set_text(app->text_box, details_buf); + scene_manager_next_scene(app->scene_manager, TagTinkerSceneTextBox); + return; + } + uint16_t size_w = target->profile.width; uint16_t size_h = target->profile.height; tagtinker_profile_glass_size(&target->profile, &size_w, &size_h); diff --git a/scenes/tagtinker_scene_transmit.c b/scenes/tagtinker_scene_transmit.c index f5ab8fd..bf87eb3 100644 --- a/scenes/tagtinker_scene_transmit.c +++ b/scenes/tagtinker_scene_transmit.c @@ -457,6 +457,11 @@ static bool tx_send_color26_text(TagTinkerApp* app) { } static bool tx_stream_text_image(TagTinkerApp* app) { + /* A target without a table profile has no dimensions. Refuse here: both + * text paths below would otherwise call malloc(0), which the firmware + * treats as a fatal error. */ + if(app->image_tx_job.width == 0U || app->image_tx_job.height == 0U) return false; + if(tx_is_color26(app)) { return tx_send_color26_text(app); } diff --git a/tagtinker_app.c b/tagtinker_app.c index f6c5292..bb4fbb5 100644 --- a/tagtinker_app.c +++ b/tagtinker_app.c @@ -289,7 +289,10 @@ bool tagtinker_delete_target(TagTinkerApp* app, uint8_t index) { bool tagtinker_target_supports_graphics(const TagTinkerTarget* target) { if(!target) return false; - return target->profile.kind != TagTinkerTagKindSegment; + /* Only profile-table entries have real dimensions. A type code missing + * from the table leaves a zeroed profile (0x0, kind Unknown), so the app + * has no known display size to render text or images for. */ + return target->profile.known && target->profile.kind == TagTinkerTagKindDotMatrix; } bool tagtinker_target_supports_accent(const TagTinkerTarget* target) { From 0447f121644e44ca35b92add8ab57b0bc422f456 Mon Sep 17 00:00:00 2001 From: Hoggormino <125792648+Hoggormino@users.noreply.github.com> Date: Wed, 16 Sep 2026 21:58:55 +0200 Subject: [PATCH 2/2] Add a custom-size option for unrecognized infrared tags A tag whose type code is not in the profile table has no dimensions, so after the unknown-profile guard its Set Text / Set Image / WiFi Plugins actions are hidden. This adds a "Set Custom Size" action for those tags: enter width, height and colour, and the graphics actions become usable. The IR framing is identical to a table profile; only the screen geometry differs, so no protocol is guessed. Custom size is applied only when the barcode's type is not in the table, so a real profile is never overridden. - New scene (VariableItemList): Width and Height in 8 px steps from 8 to 800, Colour mono/red/yellow, and Save. - tagtinker_target_refresh_profile() synthesizes a "Custom" profile from the stored size for unknown-type targets, so Show Tag Info, Set Text and Set Image all see real dimensions. - targets.txt gains three optional fields (barcode|name|cw|ch|ccolor); older two-field lines still load, with no custom size. - "Set Custom Size" appears in Target Actions only for unknown-type targets (tagtinker_type_is_known()). Builds clean on release SDK 1.4.3 (API 87.1) and dev SDK; no new clang-format drift. Co-Authored-By: Claude Opus 4.8 --- application.fam | 1 + protocol/tagtinker_proto.c | 4 + protocol/tagtinker_proto.h | 8 ++ scenes/tagtinker_scene.c | 3 + scenes/tagtinker_scene.h | 5 + scenes/tagtinker_scene_custom_size.c | 130 ++++++++++++++++++++++++ scenes/tagtinker_scene_target_actions.c | 12 +++ tagtinker_app.c | 47 +++++++-- tagtinker_app.h | 12 +++ 9 files changed, 212 insertions(+), 10 deletions(-) create mode 100644 scenes/tagtinker_scene_custom_size.c diff --git a/application.fam b/application.fam index 36b5d05..3e8ade1 100644 --- a/application.fam +++ b/application.fam @@ -34,6 +34,7 @@ App( "views/numlock_input.c", "nfc/tagtinker_nfc.c", "scenes/tagtinker_scene_nfc_scan.c", + "scenes/tagtinker_scene_custom_size.c", "wifi/tagtinker_wifi.c", "wifi/tagtinker_wifi_bmp.c", "scenes/tagtinker_scene_wifi_plugins.c", diff --git a/protocol/tagtinker_proto.c b/protocol/tagtinker_proto.c index 066f23f..c4391ef 100644 --- a/protocol/tagtinker_proto.c +++ b/protocol/tagtinker_proto.c @@ -119,6 +119,10 @@ bool tagtinker_barcode_to_plid(const char* barcode, uint8_t plid[4]) { return true; } +bool tagtinker_type_is_known(uint16_t type_code) { + return find_profile_entry(type_code) != NULL; +} + bool tagtinker_barcode_to_type(const char* barcode, uint16_t* type_code) { if(!barcode || strlen(barcode) != 17 || !type_code) return false; uint16_t type = 0; diff --git a/protocol/tagtinker_proto.h b/protocol/tagtinker_proto.h index 1861cb7..4ddbeb8 100644 --- a/protocol/tagtinker_proto.h +++ b/protocol/tagtinker_proto.h @@ -105,6 +105,14 @@ static inline void tagtinker_profile_glass_size( bool tagtinker_is_barcode_valid(const char* barcode); bool tagtinker_barcode_to_plid(const char* barcode, uint8_t plid[4]); bool tagtinker_barcode_to_type(const char* barcode, uint16_t* type_code); + +/* True when this type code has an entry in the built-in profile table. */ +bool tagtinker_type_is_known(uint16_t type_code); + +/* Bounds for a user-entered custom tag size (pixels). Width is byte-aligned. */ +#define TAGTINKER_CUSTOM_SIZE_MIN 8U +#define TAGTINKER_CUSTOM_SIZE_MAX 800U +#define TAGTINKER_CUSTOM_SIZE_STEP 8U bool tagtinker_barcode_to_profile(const char* barcode, TagTinkerTagProfile* profile); typedef struct { diff --git a/scenes/tagtinker_scene.c b/scenes/tagtinker_scene.c index d8a102d..4a71b38 100644 --- a/scenes/tagtinker_scene.c +++ b/scenes/tagtinker_scene.c @@ -25,6 +25,7 @@ void(*const tagtinker_scene_on_enter_handlers[])(void*) = { tagtinker_scene_wifi_plugins_on_enter, tagtinker_scene_wifi_setup_on_enter, tagtinker_scene_wifi_run_on_enter, + tagtinker_scene_custom_size_on_enter, }; bool(*const tagtinker_scene_on_event_handlers[])(void*, SceneManagerEvent) = { @@ -48,6 +49,7 @@ bool(*const tagtinker_scene_on_event_handlers[])(void*, SceneManagerEvent) = { tagtinker_scene_wifi_plugins_on_event, tagtinker_scene_wifi_setup_on_event, tagtinker_scene_wifi_run_on_event, + tagtinker_scene_custom_size_on_event, }; void(*const tagtinker_scene_on_exit_handlers[])(void*) = { @@ -71,6 +73,7 @@ void(*const tagtinker_scene_on_exit_handlers[])(void*) = { tagtinker_scene_wifi_plugins_on_exit, tagtinker_scene_wifi_setup_on_exit, tagtinker_scene_wifi_run_on_exit, + tagtinker_scene_custom_size_on_exit, }; const SceneManagerHandlers tagtinker_scene_handlers = { diff --git a/scenes/tagtinker_scene.h b/scenes/tagtinker_scene.h index 434931f..e7c103b 100644 --- a/scenes/tagtinker_scene.h +++ b/scenes/tagtinker_scene.h @@ -27,6 +27,7 @@ typedef enum { TagTinkerSceneWifiPlugins, TagTinkerSceneWifiSetup, TagTinkerSceneWifiRun, + TagTinkerSceneCustomSize, TagTinkerSceneCount, } TagTinkerScene; @@ -109,3 +110,7 @@ void tagtinker_scene_wifi_setup_on_exit(void* ctx); void tagtinker_scene_wifi_run_on_enter(void* ctx); bool tagtinker_scene_wifi_run_on_event(void* ctx, SceneManagerEvent event); void tagtinker_scene_wifi_run_on_exit(void* ctx); + +void tagtinker_scene_custom_size_on_enter(void* ctx); +bool tagtinker_scene_custom_size_on_event(void* ctx, SceneManagerEvent event); +void tagtinker_scene_custom_size_on_exit(void* ctx); diff --git a/scenes/tagtinker_scene_custom_size.c b/scenes/tagtinker_scene_custom_size.c new file mode 100644 index 0000000..4e6886b --- /dev/null +++ b/scenes/tagtinker_scene_custom_size.c @@ -0,0 +1,130 @@ +/* + * Custom size scene. + * + * Lets the user give a width, height and colour to a target whose type code is + * not in the profile table, so Set Text and Set Image work for it. The IR + * framing is identical to a table profile; only the screen geometry differs. + * This does not guess any protocol and only applies to unknown-type targets. + */ + +#include "../tagtinker_app.h" +#include "../protocol/tagtinker_proto.h" + +enum { + CustomSizeWidth, + CustomSizeHeight, + CustomSizeColor, + CustomSizeSave, +}; + +/* index 0 -> MIN, then MIN + i*STEP, up to MAX. */ +static uint32_t size_value_count(void) { + return (TAGTINKER_CUSTOM_SIZE_MAX - TAGTINKER_CUSTOM_SIZE_MIN) / TAGTINKER_CUSTOM_SIZE_STEP + + 1U; +} + +static uint16_t size_index_to_px(uint8_t index) { + return (uint16_t)(TAGTINKER_CUSTOM_SIZE_MIN + (uint32_t)index * TAGTINKER_CUSTOM_SIZE_STEP); +} + +static uint8_t size_px_to_index(uint16_t px) { + if(px < TAGTINKER_CUSTOM_SIZE_MIN) return 0U; + uint16_t clamped = px > TAGTINKER_CUSTOM_SIZE_MAX ? TAGTINKER_CUSTOM_SIZE_MAX : px; + return (uint8_t)((clamped - TAGTINKER_CUSTOM_SIZE_MIN) / TAGTINKER_CUSTOM_SIZE_STEP); +} + +static void width_changed(VariableItem* item) { + TagTinkerApp* app = variable_item_get_context(item); + app->custom_size_w = size_index_to_px(variable_item_get_current_value_index(item)); + char buf[8]; + snprintf(buf, sizeof(buf), "%u", app->custom_size_w); + variable_item_set_current_value_text(item, buf); +} + +static void height_changed(VariableItem* item) { + TagTinkerApp* app = variable_item_get_context(item); + app->custom_size_h = size_index_to_px(variable_item_get_current_value_index(item)); + char buf[8]; + snprintf(buf, sizeof(buf), "%u", app->custom_size_h); + variable_item_set_current_value_text(item, buf); +} + +static void color_changed(VariableItem* item) { + TagTinkerApp* app = variable_item_get_context(item); + app->custom_size_color = (uint8_t)variable_item_get_current_value_index(item); + variable_item_set_current_value_text( + item, tagtinker_profile_color_label((TagTinkerTagColor)app->custom_size_color)); +} + +static void custom_size_cb(void* ctx, uint32_t index) { + TagTinkerApp* app = ctx; + if(index != CustomSizeSave) return; + if(app->selected_target < 0 || app->selected_target >= app->target_count) return; + + TagTinkerTarget* target = &app->targets[app->selected_target]; + target->custom_width = app->custom_size_w; + target->custom_height = app->custom_size_h; + target->custom_color = app->custom_size_color; + tagtinker_target_refresh_profile(target); + tagtinker_targets_save(app); + + /* Keep the app's working size in step with the target we just sized. */ + app->esl_width = target->profile.width; + app->esl_height = target->profile.height; + + scene_manager_previous_scene(app->scene_manager); +} + +void tagtinker_scene_custom_size_on_enter(void* ctx) { + TagTinkerApp* app = ctx; + + uint16_t w = TAGTINKER_CUSTOM_SIZE_MIN; + uint16_t h = TAGTINKER_CUSTOM_SIZE_MIN; + uint8_t color = 0U; + if(app->selected_target >= 0 && app->selected_target < app->target_count) { + const TagTinkerTarget* t = &app->targets[app->selected_target]; + if(t->custom_width > 0) w = t->custom_width; + if(t->custom_height > 0) h = t->custom_height; + color = t->custom_color; + } + app->custom_size_w = w; + app->custom_size_h = h; + app->custom_size_color = color; + + variable_item_list_reset(app->var_item_list); + + char buf[8]; + VariableItem* item_w = variable_item_list_add( + app->var_item_list, "Width", (uint8_t)size_value_count(), width_changed, app); + variable_item_set_current_value_index(item_w, size_px_to_index(w)); + snprintf(buf, sizeof(buf), "%u", w); + variable_item_set_current_value_text(item_w, buf); + + VariableItem* item_h = variable_item_list_add( + app->var_item_list, "Height", (uint8_t)size_value_count(), height_changed, app); + variable_item_set_current_value_index(item_h, size_px_to_index(h)); + snprintf(buf, sizeof(buf), "%u", h); + variable_item_set_current_value_text(item_h, buf); + + VariableItem* item_c = + variable_item_list_add(app->var_item_list, "Color", 3, color_changed, app); + variable_item_set_current_value_index(item_c, color); + variable_item_set_current_value_text( + item_c, tagtinker_profile_color_label((TagTinkerTagColor)color)); + + variable_item_list_add(app->var_item_list, ">> Save <<", 0, NULL, app); + variable_item_list_set_enter_callback(app->var_item_list, custom_size_cb, app); + + view_dispatcher_switch_to_view(app->view_dispatcher, TagTinkerViewVarItemList); +} + +bool tagtinker_scene_custom_size_on_event(void* ctx, SceneManagerEvent event) { + UNUSED(ctx); + UNUSED(event); + return false; +} + +void tagtinker_scene_custom_size_on_exit(void* ctx) { + TagTinkerApp* app = ctx; + variable_item_list_reset(app->var_item_list); +} diff --git a/scenes/tagtinker_scene_target_actions.c b/scenes/tagtinker_scene_target_actions.c index e39e022..7bb37e7 100644 --- a/scenes/tagtinker_scene_target_actions.c +++ b/scenes/tagtinker_scene_target_actions.c @@ -3,6 +3,7 @@ */ #include "../tagtinker_app.h" +#include "../protocol/tagtinker_proto.h" static void target_actions_cb(void* ctx, uint32_t index) { TagTinkerApp* app = ctx; @@ -98,6 +99,14 @@ void tagtinker_scene_target_actions_on_enter(void* ctx) { submenu_add_item(app->submenu, "WiFi Plugins", TagTinkerTargetWifiPlugins, target_actions_cb, app); } + /* An unknown type has no table profile; let the user give it a size so the + * graphics actions above become usable. Shown whether or not a size is set, + * so it can be changed later. */ + if(target && !tagtinker_type_is_known(target->profile.type_code)) { + submenu_add_item( + app->submenu, "Set Custom Size", TagTinkerTargetCustomSize, target_actions_cb, app); + } + submenu_add_item(app->submenu, "LED Test", TagTinkerTargetPingFlash, target_actions_cb, app); submenu_add_item(app->submenu, "Delete Tag", TagTinkerTargetDeleteTag, target_actions_cb, app); @@ -129,6 +138,9 @@ bool tagtinker_scene_target_actions_on_event(void* ctx, SceneManagerEvent event) if(!tagtinker_target_supports_graphics(&app->targets[app->selected_target])) return true; scene_manager_next_scene(app->scene_manager, TagTinkerSceneWifiPlugins); return true; + case TagTinkerTargetCustomSize: + scene_manager_next_scene(app->scene_manager, TagTinkerSceneCustomSize); + return true; case TagTinkerTargetPingFlash: { TagTinkerTarget* target = &app->targets[app->selected_target]; diff --git a/tagtinker_app.c b/tagtinker_app.c index bb4fbb5..fa1822f 100644 --- a/tagtinker_app.c +++ b/tagtinker_app.c @@ -59,6 +59,17 @@ void tagtinker_target_refresh_profile(TagTinkerTarget* target) { memset(&target->profile, 0, sizeof(target->profile)); tagtinker_barcode_to_profile(target->barcode, &target->profile); + + /* If the barcode's type is not in the table but the user gave a size, build + * a profile from it. A real table profile is left untouched. */ + if(!target->profile.known && target->custom_width > 0 && target->custom_height > 0) { + target->profile.width = target->custom_width; + target->profile.height = target->custom_height; + target->profile.color = (TagTinkerTagColor)target->custom_color; + target->profile.kind = TagTinkerTagKindDotMatrix; + target->profile.model_name = "Custom"; + target->profile.known = true; + } } void tagtinker_target_set_default_name(TagTinkerApp* app, TagTinkerTarget* target) { @@ -512,22 +523,35 @@ void tagtinker_targets_load(TagTinkerApp* app) { if(nl) *nl = '\0'; if(*line) { - char* sep = strchr(line, '|'); - if(sep) *sep = '\0'; + /* Split into up to 5 fields: barcode|name|cw|ch|ccolor. + * Older files have only barcode|name; the rest default to 0. */ + char* fields[5] = {line, NULL, NULL, NULL, NULL}; + char* p = line; + for(uint8_t f = 1; f < 5; f++) { + char* sep = strchr(p, '|'); + if(!sep) break; + *sep = '\0'; + fields[f] = sep + 1; + p = sep + 1; + } - if(tagtinker_barcode_to_plid(line, app->targets[app->target_count].plid)) { + if(tagtinker_barcode_to_plid(fields[0], app->targets[app->target_count].plid)) { TagTinkerTarget* target = &app->targets[app->target_count]; - strncpy(target->barcode, line, TAGTINKER_BC_LEN); + memset(target, 0, sizeof(*target)); + strncpy(target->barcode, fields[0], TAGTINKER_BC_LEN); target->barcode[TAGTINKER_BC_LEN] = '\0'; - memset(target->name, 0, sizeof(target->name)); - if(sep && *(sep + 1)) { - strncpy(target->name, sep + 1, TAGTINKER_TARGET_NAME_LEN); + if(fields[1] && *fields[1]) { + strncpy(target->name, fields[1], TAGTINKER_TARGET_NAME_LEN); target->name[TAGTINKER_TARGET_NAME_LEN] = '\0'; } else { tagtinker_target_set_default_name(app, target); } + if(fields[2]) target->custom_width = (uint16_t)atoi(fields[2]); + if(fields[3]) target->custom_height = (uint16_t)atoi(fields[3]); + if(fields[4]) target->custom_color = (uint8_t)atoi(fields[4]); + tagtinker_target_refresh_profile(target); app->target_count++; } @@ -551,13 +575,16 @@ bool tagtinker_targets_save(const TagTinkerApp* app) { if(storage_file_open(file, APP_DATA_PATH("targets.txt"), FSAM_WRITE, FSOM_CREATE_ALWAYS)) { ok = true; for(uint8_t i = 0; i < app->target_count; i++) { - char line[64]; + char line[96]; int len = snprintf( line, sizeof(line), - "%s|%s\n", + "%s|%s|%u|%u|%u\n", app->targets[i].barcode, - app->targets[i].name); + app->targets[i].name, + app->targets[i].custom_width, + app->targets[i].custom_height, + app->targets[i].custom_color); if(len <= 0 || !storage_file_write(file, line, (uint16_t)len)) { ok = false; diff --git a/tagtinker_app.h b/tagtinker_app.h index d6a010b..c3bc57c 100644 --- a/tagtinker_app.h +++ b/tagtinker_app.h @@ -105,6 +105,12 @@ typedef struct { char barcode[TAGTINKER_BC_LEN + 1]; uint8_t plid[4]; TagTinkerTagProfile profile; + /* User-entered geometry for a tag whose type code is not in the profile + * table. 0x0 means unset. Applied by tagtinker_target_refresh_profile only + * when the barcode's type is unknown, so a real profile is never overridden. */ + uint16_t custom_width; + uint16_t custom_height; + uint8_t custom_color; /* TagTinkerTagColor */ } TagTinkerTarget; struct TagTinkerApp { @@ -156,6 +162,11 @@ struct TagTinkerApp { /* Saved targets */ TagTinkerTarget targets[TAGTINKER_MAX_TARGETS]; + + /* Custom-size scene working values. */ + uint16_t custom_size_w; + uint16_t custom_size_h; + uint8_t custom_size_color; uint8_t target_count; /* Text to push */ @@ -294,6 +305,7 @@ typedef enum { TagTinkerTargetDeleteSyncedImages, TagTinkerTargetPingFlash, TagTinkerTargetDeleteTag, + TagTinkerTargetCustomSize, } TagTinkerTargetActionItem; void tagtinker_target_refresh_profile(TagTinkerTarget* target);