[FL-2556] Update complete screen (#1332)
* Desktop: slideshow implementation * Updater: handling splashscreen installation; added format version field to slideshow binary * Desktop: added bidirectional slideshow navigation + instant cancel by "back" button; Updater: rebalanced update stages weights * Updater: fixed missing field init; fixed potential loop when baking slideshows * Assets: fixed "update complete" image to match original * Desktop: added check for slideshow file version * Scripts: slideshow.py cleanup * Desktop: removed first start intro sequence * Desktop: removed first start remnants
This commit is contained in:
		
							parent
							
								
									4b02a404ba
								
							
						
					
					
						commit
						eb31fed0e2
					
				
							
								
								
									
										4
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										4
									
								
								Makefile
									
									
									
									
									
								
							| @ -2,6 +2,7 @@ PROJECT_ROOT := $(abspath $(dir $(abspath $(firstword $(MAKEFILE_LIST))))) | ||||
| 
 | ||||
| include			$(PROJECT_ROOT)/make/git.mk | ||||
| include			$(PROJECT_ROOT)/assets/copro.mk | ||||
| include			$(PROJECT_ROOT)/assets/splash.mk | ||||
| 
 | ||||
| PROJECT_SOURCE_DIRECTORIES := \
 | ||||
| 	$(PROJECT_ROOT)/applications \
 | ||||
| @ -103,7 +104,8 @@ updater_package: firmware_all updater assets_manifest | ||||
| 	--radio $(COPRO_STACK_BIN_PATH) \
 | ||||
| 	--radiotype $(COPRO_STACK_TYPE) \
 | ||||
| 	$(COPRO_DISCLAIMER) \
 | ||||
| 	--obdata $(PROJECT_ROOT)/scripts/$(COPRO_OB_DATA) | ||||
| 	--obdata $(PROJECT_ROOT)/scripts/$(COPRO_OB_DATA) \
 | ||||
| 	--splash $(UPDATER_SPLASH_DIR) | ||||
| 
 | ||||
| .PHONY: assets_manifest | ||||
| assets_manifest: | ||||
|  | ||||
| @ -158,11 +158,11 @@ Desktop* desktop_alloc() { | ||||
| 
 | ||||
|     desktop->lock_menu = desktop_lock_menu_alloc(); | ||||
|     desktop->debug_view = desktop_debug_alloc(); | ||||
|     desktop->first_start_view = desktop_first_start_alloc(); | ||||
|     desktop->hw_mismatch_popup = popup_alloc(); | ||||
|     desktop->locked_view = desktop_view_locked_alloc(); | ||||
|     desktop->pin_input_view = desktop_view_pin_input_alloc(); | ||||
|     desktop->pin_timeout_view = desktop_view_pin_timeout_alloc(); | ||||
|     desktop->slideshow_view = desktop_view_slideshow_alloc(); | ||||
| 
 | ||||
|     desktop->main_view_stack = view_stack_alloc(); | ||||
|     desktop->main_view = desktop_main_alloc(); | ||||
| @ -193,10 +193,6 @@ Desktop* desktop_alloc() { | ||||
|         desktop_lock_menu_get_view(desktop->lock_menu)); | ||||
|     view_dispatcher_add_view( | ||||
|         desktop->view_dispatcher, DesktopViewIdDebug, desktop_debug_get_view(desktop->debug_view)); | ||||
|     view_dispatcher_add_view( | ||||
|         desktop->view_dispatcher, | ||||
|         DesktopViewIdFirstStart, | ||||
|         desktop_first_start_get_view(desktop->first_start_view)); | ||||
|     view_dispatcher_add_view( | ||||
|         desktop->view_dispatcher, | ||||
|         DesktopViewIdHwMismatch, | ||||
| @ -209,6 +205,10 @@ Desktop* desktop_alloc() { | ||||
|         desktop->view_dispatcher, | ||||
|         DesktopViewIdPinInput, | ||||
|         desktop_view_pin_input_get_view(desktop->pin_input_view)); | ||||
|     view_dispatcher_add_view( | ||||
|         desktop->view_dispatcher, | ||||
|         DesktopViewIdSlideshow, | ||||
|         desktop_view_slideshow_get_view(desktop->slideshow_view)); | ||||
| 
 | ||||
|     // Lock icon
 | ||||
|     desktop->lock_viewport = view_port_alloc(); | ||||
| @ -258,7 +258,6 @@ void desktop_free(Desktop* desktop) { | ||||
|     view_dispatcher_remove_view(desktop->view_dispatcher, DesktopViewIdLockMenu); | ||||
|     view_dispatcher_remove_view(desktop->view_dispatcher, DesktopViewIdLocked); | ||||
|     view_dispatcher_remove_view(desktop->view_dispatcher, DesktopViewIdDebug); | ||||
|     view_dispatcher_remove_view(desktop->view_dispatcher, DesktopViewIdFirstStart); | ||||
|     view_dispatcher_remove_view(desktop->view_dispatcher, DesktopViewIdHwMismatch); | ||||
|     view_dispatcher_remove_view(desktop->view_dispatcher, DesktopViewIdPinInput); | ||||
|     view_dispatcher_remove_view(desktop->view_dispatcher, DesktopViewIdPinTimeout); | ||||
| @ -274,7 +273,6 @@ void desktop_free(Desktop* desktop) { | ||||
|     desktop_lock_menu_free(desktop->lock_menu); | ||||
|     desktop_view_locked_free(desktop->locked_view); | ||||
|     desktop_debug_free(desktop->debug_view); | ||||
|     desktop_first_start_free(desktop->first_start_view); | ||||
|     popup_free(desktop->hw_mismatch_popup); | ||||
|     desktop_view_pin_timeout_free(desktop->pin_timeout_view); | ||||
| 
 | ||||
| @ -290,9 +288,9 @@ void desktop_free(Desktop* desktop) { | ||||
|     free(desktop); | ||||
| } | ||||
| 
 | ||||
| static bool desktop_is_first_start() { | ||||
| static bool desktop_check_file_flag(const char* flag_path) { | ||||
|     Storage* storage = furi_record_open("storage"); | ||||
|     bool exists = storage_common_stat(storage, "/int/first_start", NULL) == FSE_OK; | ||||
|     bool exists = storage_common_stat(storage, flag_path, NULL) == FSE_OK; | ||||
|     furi_record_close("storage"); | ||||
| 
 | ||||
|     return exists; | ||||
| @ -320,8 +318,8 @@ int32_t desktop_srv(void* p) { | ||||
|         desktop_lock(desktop); | ||||
|     } | ||||
| 
 | ||||
|     if(desktop_is_first_start()) { | ||||
|         scene_manager_next_scene(desktop->scene_manager, DesktopSceneFirstStart); | ||||
|     if(desktop_check_file_flag("/int/slideshow")) { | ||||
|         scene_manager_next_scene(desktop->scene_manager, DesktopSceneSlideshow); | ||||
|     } | ||||
| 
 | ||||
|     if(!furi_hal_version_do_i_belong_here()) { | ||||
|  | ||||
| @ -6,9 +6,9 @@ | ||||
| #include "views/desktop_view_pin_input.h" | ||||
| #include "views/desktop_view_locked.h" | ||||
| #include "views/desktop_view_main.h" | ||||
| #include "views/desktop_view_first_start.h" | ||||
| #include "views/desktop_view_lock_menu.h" | ||||
| #include "views/desktop_view_debug.h" | ||||
| #include "views/desktop_view_slideshow.h" | ||||
| #include "desktop/desktop_settings/desktop_settings.h" | ||||
| 
 | ||||
| #include <furi.h> | ||||
| @ -28,10 +28,10 @@ typedef enum { | ||||
|     DesktopViewIdLockMenu, | ||||
|     DesktopViewIdLocked, | ||||
|     DesktopViewIdDebug, | ||||
|     DesktopViewIdFirstStart, | ||||
|     DesktopViewIdHwMismatch, | ||||
|     DesktopViewIdPinInput, | ||||
|     DesktopViewIdPinTimeout, | ||||
|     DesktopViewIdSlideshow, | ||||
|     DesktopViewIdTotal, | ||||
| } DesktopViewId; | ||||
| 
 | ||||
| @ -43,13 +43,13 @@ struct Desktop { | ||||
|     ViewDispatcher* view_dispatcher; | ||||
|     SceneManager* scene_manager; | ||||
| 
 | ||||
|     DesktopFirstStartView* first_start_view; | ||||
|     Popup* hw_mismatch_popup; | ||||
|     DesktopLockMenuView* lock_menu; | ||||
|     DesktopDebugView* debug_view; | ||||
|     DesktopViewLocked* locked_view; | ||||
|     DesktopMainView* main_view; | ||||
|     DesktopViewPinTimeout* pin_timeout_view; | ||||
|     DesktopSlideshowView* slideshow_view; | ||||
| 
 | ||||
|     ViewStack* main_view_stack; | ||||
|     ViewStack* locked_view_stack; | ||||
|  | ||||
							
								
								
									
										115
									
								
								applications/desktop/helpers/slideshow.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										115
									
								
								applications/desktop/helpers/slideshow.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,115 @@ | ||||
| #include "slideshow.h" | ||||
| 
 | ||||
| #include <stddef.h> | ||||
| #include <storage/storage.h> | ||||
| #include <gui/icon.h> | ||||
| #include <gui/icon_i.h> | ||||
| #include <furi/dangerous_defines.h> | ||||
| 
 | ||||
| #define SLIDESHOW_MAGIC 0x72676468 | ||||
| #define SLIDESHOW_MAX_SUPPORTED_VERSION 1 | ||||
| 
 | ||||
| struct Slideshow { | ||||
|     Icon icon; | ||||
|     uint32_t current_frame; | ||||
| }; | ||||
| 
 | ||||
| #pragma pack(push, 1) | ||||
| 
 | ||||
| typedef struct { | ||||
|     uint32_t magic; | ||||
|     uint8_t version; | ||||
|     uint8_t width; | ||||
|     uint8_t height; | ||||
|     uint8_t frame_count; | ||||
| } SlideshowFileHeader; | ||||
| _Static_assert(sizeof(SlideshowFileHeader) == 8, "Incorrect SlideshowFileHeader size"); | ||||
| 
 | ||||
| typedef struct { | ||||
|     uint16_t size; | ||||
| } SlideshowFrameHeader; | ||||
| _Static_assert(sizeof(SlideshowFrameHeader) == 2, "Incorrect SlideshowFrameHeader size"); | ||||
| 
 | ||||
| #pragma pack(pop) | ||||
| 
 | ||||
| Slideshow* slideshow_alloc() { | ||||
|     Slideshow* ret = malloc(sizeof(Slideshow)); | ||||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| void slideshow_free(Slideshow* slideshow) { | ||||
|     Icon* icon = &slideshow->icon; | ||||
|     if(icon) { | ||||
|         for(int frame_idx = 0; frame_idx < icon->frame_count; ++frame_idx) { | ||||
|             uint8_t* frame_data = (uint8_t*)icon->frames[frame_idx]; | ||||
|             free(frame_data); | ||||
|         } | ||||
|         free((uint8_t**)icon->frames); | ||||
|     } | ||||
|     free(slideshow); | ||||
| } | ||||
| 
 | ||||
| bool slideshow_load(Slideshow* slideshow, const char* fspath) { | ||||
|     Storage* storage = furi_record_open("storage"); | ||||
|     File* slideshow_file = storage_file_alloc(storage); | ||||
|     bool load_success = false; | ||||
|     do { | ||||
|         if(!storage_file_open(slideshow_file, fspath, FSAM_READ, FSOM_OPEN_EXISTING)) { | ||||
|             break; | ||||
|         } | ||||
|         SlideshowFileHeader header; | ||||
|         if((storage_file_read(slideshow_file, &header, sizeof(header)) != sizeof(header)) || | ||||
|            (header.magic != SLIDESHOW_MAGIC) || | ||||
|            (header.version > SLIDESHOW_MAX_SUPPORTED_VERSION)) { | ||||
|             break; | ||||
|         } | ||||
|         Icon* icon = &slideshow->icon; | ||||
|         FURI_CONST_ASSIGN(icon->frame_count, header.frame_count); | ||||
|         FURI_CONST_ASSIGN(icon->width, header.width); | ||||
|         FURI_CONST_ASSIGN(icon->height, header.height); | ||||
|         icon->frames = malloc(header.frame_count * sizeof(uint8_t*)); | ||||
|         for(int frame_idx = 0; frame_idx < header.frame_count; ++frame_idx) { | ||||
|             SlideshowFrameHeader frame_header; | ||||
|             if(storage_file_read(slideshow_file, &frame_header, sizeof(frame_header)) != | ||||
|                sizeof(frame_header)) { | ||||
|                 break; | ||||
|             } | ||||
|             FURI_CONST_ASSIGN_PTR(icon->frames[frame_idx], malloc(frame_header.size)); | ||||
|             uint8_t* frame_data = (uint8_t*)icon->frames[frame_idx]; | ||||
|             if(storage_file_read(slideshow_file, frame_data, frame_header.size) != | ||||
|                frame_header.size) { | ||||
|                 break; | ||||
|             } | ||||
|             load_success = (frame_idx + 1) == header.frame_count; | ||||
|         } | ||||
|     } while(false); | ||||
|     storage_file_free(slideshow_file); | ||||
|     furi_record_close("storage"); | ||||
|     return load_success; | ||||
| } | ||||
| 
 | ||||
| bool slideshow_advance(Slideshow* slideshow) { | ||||
|     uint8_t next_frame = slideshow->current_frame + 1; | ||||
|     if(next_frame < slideshow->icon.frame_count) { | ||||
|         slideshow->current_frame = next_frame; | ||||
|         return true; | ||||
|     } | ||||
|     return false; | ||||
| } | ||||
| 
 | ||||
| void slideshow_goback(Slideshow* slideshow) { | ||||
|     if(slideshow->current_frame > 0) { | ||||
|         slideshow->current_frame--; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void slideshow_draw(Slideshow* slideshow, Canvas* canvas, uint8_t x, uint8_t y) { | ||||
|     furi_assert(slideshow->current_frame < slideshow->icon.frame_count); | ||||
|     canvas_draw_bitmap( | ||||
|         canvas, | ||||
|         x, | ||||
|         y, | ||||
|         slideshow->icon.width, | ||||
|         slideshow->icon.height, | ||||
|         slideshow->icon.frames[slideshow->current_frame]); | ||||
| } | ||||
							
								
								
									
										13
									
								
								applications/desktop/helpers/slideshow.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								applications/desktop/helpers/slideshow.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,13 @@ | ||||
| #pragma once | ||||
| 
 | ||||
| #include <gui/canvas.h> | ||||
| 
 | ||||
| typedef struct Slideshow Slideshow; | ||||
| 
 | ||||
| Slideshow* slideshow_alloc(); | ||||
| 
 | ||||
| void slideshow_free(Slideshow* slideshow); | ||||
| bool slideshow_load(Slideshow* slideshow, const char* fspath); | ||||
| void slideshow_goback(Slideshow* slideshow); | ||||
| bool slideshow_advance(Slideshow* slideshow); | ||||
| void slideshow_draw(Slideshow* slideshow, Canvas* canvas, uint8_t x, uint8_t y); | ||||
| @ -1,9 +1,9 @@ | ||||
| ADD_SCENE(desktop, main, Main) | ||||
| ADD_SCENE(desktop, lock_menu, LockMenu) | ||||
| ADD_SCENE(desktop, debug, Debug) | ||||
| ADD_SCENE(desktop, first_start, FirstStart) | ||||
| ADD_SCENE(desktop, hw_mismatch, HwMismatch) | ||||
| ADD_SCENE(desktop, fault, Fault) | ||||
| ADD_SCENE(desktop, locked, Locked) | ||||
| ADD_SCENE(desktop, pin_input, PinInput) | ||||
| ADD_SCENE(desktop, pin_timeout, PinTimeout) | ||||
| ADD_SCENE(desktop, slideshow, Slideshow) | ||||
|  | ||||
| @ -1,54 +0,0 @@ | ||||
| #include <power/power_service/power.h> | ||||
| #include <storage/storage.h> | ||||
| 
 | ||||
| #include "../desktop_i.h" | ||||
| #include "../views/desktop_view_first_start.h" | ||||
| #include "../views/desktop_events.h" | ||||
| 
 | ||||
| void desktop_scene_first_start_callback(DesktopEvent event, void* context) { | ||||
|     Desktop* desktop = (Desktop*)context; | ||||
|     view_dispatcher_send_custom_event(desktop->view_dispatcher, event); | ||||
| } | ||||
| 
 | ||||
| void desktop_scene_first_start_on_enter(void* context) { | ||||
|     Desktop* desktop = (Desktop*)context; | ||||
|     DesktopFirstStartView* first_start_view = desktop->first_start_view; | ||||
| 
 | ||||
|     desktop_first_start_set_callback( | ||||
|         first_start_view, desktop_scene_first_start_callback, desktop); | ||||
| 
 | ||||
|     view_dispatcher_switch_to_view(desktop->view_dispatcher, DesktopViewIdFirstStart); | ||||
| } | ||||
| 
 | ||||
| bool desktop_scene_first_start_on_event(void* context, SceneManagerEvent event) { | ||||
|     Desktop* desktop = (Desktop*)context; | ||||
|     bool consumed = false; | ||||
|     Storage* storage = NULL; | ||||
|     Power* power = NULL; | ||||
| 
 | ||||
|     if(event.type == SceneManagerEventTypeCustom) { | ||||
|         switch(event.event) { | ||||
|         case DesktopFirstStartCompleted: | ||||
|             storage = furi_record_open("storage"); | ||||
|             storage_common_remove(storage, "/int/first_start"); | ||||
|             furi_record_close("storage"); | ||||
|             scene_manager_previous_scene(desktop->scene_manager); | ||||
|             consumed = true; | ||||
|             break; | ||||
|         case DesktopFirstStartPoweroff: | ||||
|             power = furi_record_open("power"); | ||||
|             power_off(power); | ||||
|             furi_record_close("power"); | ||||
|             consumed = true; | ||||
|             break; | ||||
| 
 | ||||
|         default: | ||||
|             break; | ||||
|         } | ||||
|     } | ||||
|     return consumed; | ||||
| } | ||||
| 
 | ||||
| void desktop_scene_first_start_on_exit(void* context) { | ||||
|     UNUSED(context); | ||||
| } | ||||
							
								
								
									
										45
									
								
								applications/desktop/scenes/desktop_scene_slideshow.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								applications/desktop/scenes/desktop_scene_slideshow.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,45 @@ | ||||
| #include <storage/storage.h> | ||||
| 
 | ||||
| #include "../desktop_i.h" | ||||
| #include "../views/desktop_view_slideshow.h" | ||||
| #include "../views/desktop_events.h" | ||||
| 
 | ||||
| void desktop_scene_slideshow_callback(DesktopEvent event, void* context) { | ||||
|     Desktop* desktop = (Desktop*)context; | ||||
|     view_dispatcher_send_custom_event(desktop->view_dispatcher, event); | ||||
| } | ||||
| 
 | ||||
| void desktop_scene_slideshow_on_enter(void* context) { | ||||
|     Desktop* desktop = (Desktop*)context; | ||||
|     DesktopSlideshowView* slideshow_view = desktop->slideshow_view; | ||||
| 
 | ||||
|     desktop_view_slideshow_set_callback(slideshow_view, desktop_scene_slideshow_callback, desktop); | ||||
| 
 | ||||
|     view_dispatcher_switch_to_view(desktop->view_dispatcher, DesktopViewIdSlideshow); | ||||
| } | ||||
| 
 | ||||
| bool desktop_scene_slideshow_on_event(void* context, SceneManagerEvent event) { | ||||
|     Desktop* desktop = (Desktop*)context; | ||||
|     bool consumed = false; | ||||
|     Storage* storage = NULL; | ||||
| 
 | ||||
|     if(event.type == SceneManagerEventTypeCustom) { | ||||
|         switch(event.event) { | ||||
|         case DesktopSlideshowCompleted: | ||||
|             storage = furi_record_open("storage"); | ||||
|             storage_common_remove(storage, "/int/slideshow"); | ||||
|             furi_record_close("storage"); | ||||
|             scene_manager_previous_scene(desktop->scene_manager); | ||||
|             consumed = true; | ||||
|             break; | ||||
| 
 | ||||
|         default: | ||||
|             break; | ||||
|         } | ||||
|     } | ||||
|     return consumed; | ||||
| } | ||||
| 
 | ||||
| void desktop_scene_slideshow_on_exit(void* context) { | ||||
|     UNUSED(context); | ||||
| } | ||||
| @ -26,9 +26,6 @@ typedef enum { | ||||
|     DesktopDebugEventSaveState, | ||||
|     DesktopDebugEventExit, | ||||
| 
 | ||||
|     DesktopFirstStartCompleted, | ||||
|     DesktopFirstStartPoweroff, | ||||
| 
 | ||||
|     DesktopLockMenuEventLock, | ||||
|     DesktopLockMenuEventPinLock, | ||||
|     DesktopLockMenuEventExit, | ||||
| @ -37,6 +34,8 @@ typedef enum { | ||||
|     DesktopAnimationEventNewIdleAnimation, | ||||
|     DesktopAnimationEventInteractAnimation, | ||||
| 
 | ||||
|     DesktopSlideshowCompleted, | ||||
| 
 | ||||
|     // Global events
 | ||||
|     DesktopGlobalBeforeAppStarted, | ||||
|     DesktopGlobalAfterAppFinished, | ||||
|  | ||||
| @ -1,166 +0,0 @@ | ||||
| #include <furi.h> | ||||
| #include <furi_hal.h> | ||||
| #include <gui/elements.h> | ||||
| 
 | ||||
| #include "../desktop_i.h" | ||||
| #include "desktop_view_first_start.h" | ||||
| 
 | ||||
| #define DESKTOP_FIRST_START_POWEROFF_SHORT 5000 | ||||
| #define DESKTOP_FIRST_START_POWEROFF_LONG (60 * 60 * 1000) | ||||
| 
 | ||||
| struct DesktopFirstStartView { | ||||
|     View* view; | ||||
|     DesktopFirstStartViewCallback callback; | ||||
|     void* context; | ||||
|     osTimerId_t timer; | ||||
| }; | ||||
| 
 | ||||
| typedef struct { | ||||
|     uint8_t page; | ||||
| } DesktopFirstStartViewModel; | ||||
| 
 | ||||
| static void desktop_first_start_draw(Canvas* canvas, void* model) { | ||||
|     DesktopFirstStartViewModel* m = model; | ||||
| 
 | ||||
|     canvas_clear(canvas); | ||||
|     canvas_set_color(canvas, ColorBlack); | ||||
|     canvas_set_font(canvas, FontSecondary); | ||||
|     uint8_t width = canvas_width(canvas); | ||||
|     uint8_t height = canvas_height(canvas); | ||||
|     const char* my_name = furi_hal_version_get_name_ptr(); | ||||
|     if(m->page == 0) { | ||||
|         canvas_draw_icon(canvas, 0, height - 51, &I_DolphinFirstStart0_70x53); | ||||
|         elements_multiline_text_framed( | ||||
|             canvas, 75, 16 + STATUS_BAR_Y_SHIFT, "Hey m8,\npress > to\ncontinue"); | ||||
|     } else if(m->page == 1) { | ||||
|         canvas_draw_icon(canvas, 0, height - 51, &I_DolphinFirstStart1_59x53); | ||||
|         elements_multiline_text_framed( | ||||
|             canvas, 64, 16 + STATUS_BAR_Y_SHIFT, "First Of All,\n...      >"); | ||||
|     } else if(m->page == 2) { | ||||
|         canvas_draw_icon(canvas, 0, height - 51, &I_DolphinFirstStart2_59x51); | ||||
|         elements_multiline_text_framed( | ||||
|             canvas, 64, 16 + STATUS_BAR_Y_SHIFT, "Thank you\nfor your\nsupport! >"); | ||||
|     } else if(m->page == 3) { | ||||
|         canvas_draw_icon(canvas, width - 57, height - 45, &I_DolphinFirstStart3_57x48); | ||||
|         elements_multiline_text_framed( | ||||
|             canvas, 0, 16 + STATUS_BAR_Y_SHIFT, "Kickstarter\ncampaign\nwas INSANE! >"); | ||||
|     } else if(m->page == 4) { | ||||
|         canvas_draw_icon(canvas, width - 67, height - 51, &I_DolphinFirstStart4_67x53); | ||||
|         elements_multiline_text_framed( | ||||
|             canvas, 0, 13 + STATUS_BAR_Y_SHIFT, "Now\nallow me\nto introduce\nmyself >"); | ||||
|     } else if(m->page == 5) { | ||||
|         char buf[64]; | ||||
|         snprintf( | ||||
|             buf, | ||||
|             64, | ||||
|             "%s %s%s", | ||||
|             "I am", | ||||
|             my_name ? my_name : "Unknown", | ||||
|             ",\ncyberdolphin\nliving in your\npocket >"); | ||||
|         canvas_draw_icon(canvas, 0, height - 49, &I_DolphinFirstStart5_54x49); | ||||
|         elements_multiline_text_framed(canvas, 60, 13 + STATUS_BAR_Y_SHIFT, buf); | ||||
|     } else if(m->page == 6) { | ||||
|         canvas_draw_icon(canvas, 0, height - 51, &I_DolphinFirstStart6_58x54); | ||||
|         elements_multiline_text_framed( | ||||
|             canvas, | ||||
|             63, | ||||
|             13 + STATUS_BAR_Y_SHIFT, | ||||
|             "I can grow\nsmart'n'cool\nif you use me\noften >"); | ||||
|     } else if(m->page == 7) { | ||||
|         canvas_draw_icon(canvas, width - 61, height - 51, &I_DolphinFirstStart7_61x51); | ||||
|         elements_multiline_text_framed( | ||||
|             canvas, 0, 13 + STATUS_BAR_Y_SHIFT, "As long as\nyou read, write\nand emulate >"); | ||||
|     } else if(m->page == 8) { | ||||
|         canvas_draw_icon(canvas, width - 56, height - 51, &I_DolphinFirstStart8_56x51); | ||||
|         elements_multiline_text_framed( | ||||
|             canvas, | ||||
|             0, | ||||
|             13 + STATUS_BAR_Y_SHIFT, | ||||
|             "You can check\nmy level and\nmood in the\nPassport menu"); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| static bool desktop_first_start_input(InputEvent* event, void* context) { | ||||
|     furi_assert(event); | ||||
|     DesktopFirstStartView* instance = context; | ||||
| 
 | ||||
|     if(event->type == InputTypeShort) { | ||||
|         DesktopFirstStartViewModel* model = view_get_model(instance->view); | ||||
|         if(event->key == InputKeyLeft) { | ||||
|             if(model->page > 0) model->page--; | ||||
|         } else if(event->key == InputKeyRight) { | ||||
|             uint32_t page = ++model->page; | ||||
|             if(page > 8) { | ||||
|                 instance->callback(DesktopFirstStartCompleted, instance->context); | ||||
|             } | ||||
|         } | ||||
|         view_commit_model(instance->view, true); | ||||
|     } | ||||
| 
 | ||||
|     if(event->key == InputKeyOk) { | ||||
|         if(event->type == InputTypePress) { | ||||
|             osTimerStart(instance->timer, DESKTOP_FIRST_START_POWEROFF_SHORT); | ||||
|         } else if(event->type == InputTypeRelease) { | ||||
|             osTimerStop(instance->timer); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| static void desktop_first_start_timer_callback(void* context) { | ||||
|     DesktopFirstStartView* instance = context; | ||||
|     instance->callback(DesktopFirstStartPoweroff, instance->context); | ||||
| } | ||||
| 
 | ||||
| static void desktop_first_start_enter(void* context) { | ||||
|     DesktopFirstStartView* instance = context; | ||||
| 
 | ||||
|     furi_assert(instance->timer == NULL); | ||||
|     instance->timer = osTimerNew(desktop_first_start_timer_callback, osTimerOnce, instance, NULL); | ||||
| 
 | ||||
|     osTimerStart(instance->timer, DESKTOP_FIRST_START_POWEROFF_LONG); | ||||
| } | ||||
| 
 | ||||
| static void desktop_first_start_exit(void* context) { | ||||
|     DesktopFirstStartView* instance = context; | ||||
| 
 | ||||
|     osTimerStop(instance->timer); | ||||
|     osTimerDelete(instance->timer); | ||||
|     instance->timer = NULL; | ||||
| } | ||||
| 
 | ||||
| DesktopFirstStartView* desktop_first_start_alloc() { | ||||
|     DesktopFirstStartView* instance = malloc(sizeof(DesktopFirstStartView)); | ||||
|     instance->view = view_alloc(); | ||||
|     view_allocate_model(instance->view, ViewModelTypeLocking, sizeof(DesktopFirstStartViewModel)); | ||||
|     view_set_context(instance->view, instance); | ||||
|     view_set_draw_callback(instance->view, (ViewDrawCallback)desktop_first_start_draw); | ||||
|     view_set_input_callback(instance->view, desktop_first_start_input); | ||||
|     view_set_enter_callback(instance->view, desktop_first_start_enter); | ||||
|     view_set_exit_callback(instance->view, desktop_first_start_exit); | ||||
| 
 | ||||
|     return instance; | ||||
| } | ||||
| 
 | ||||
| void desktop_first_start_free(DesktopFirstStartView* instance) { | ||||
|     furi_assert(instance); | ||||
| 
 | ||||
|     view_free(instance->view); | ||||
|     free(instance); | ||||
| } | ||||
| 
 | ||||
| View* desktop_first_start_get_view(DesktopFirstStartView* instance) { | ||||
|     furi_assert(instance); | ||||
|     return instance->view; | ||||
| } | ||||
| 
 | ||||
| void desktop_first_start_set_callback( | ||||
|     DesktopFirstStartView* instance, | ||||
|     DesktopFirstStartViewCallback callback, | ||||
|     void* context) { | ||||
|     furi_assert(instance); | ||||
|     furi_assert(callback); | ||||
|     instance->callback = callback; | ||||
|     instance->context = context; | ||||
| } | ||||
| @ -1,20 +0,0 @@ | ||||
| #pragma once | ||||
| 
 | ||||
| #include <gui/view.h> | ||||
| 
 | ||||
| #include "desktop_events.h" | ||||
| 
 | ||||
| typedef struct DesktopFirstStartView DesktopFirstStartView; | ||||
| 
 | ||||
| typedef void (*DesktopFirstStartViewCallback)(DesktopEvent event, void* context); | ||||
| 
 | ||||
| DesktopFirstStartView* desktop_first_start_alloc(); | ||||
| 
 | ||||
| void desktop_first_start_free(DesktopFirstStartView* main_view); | ||||
| 
 | ||||
| View* desktop_first_start_get_view(DesktopFirstStartView* main_view); | ||||
| 
 | ||||
| void desktop_first_start_set_callback( | ||||
|     DesktopFirstStartView* main_view, | ||||
|     DesktopFirstStartViewCallback callback, | ||||
|     void* context); | ||||
							
								
								
									
										108
									
								
								applications/desktop/views/desktop_view_slideshow.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										108
									
								
								applications/desktop/views/desktop_view_slideshow.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,108 @@ | ||||
| #include <furi.h> | ||||
| #include <furi_hal.h> | ||||
| #include <gui/elements.h> | ||||
| 
 | ||||
| #include "../desktop_i.h" | ||||
| #include "desktop_view_slideshow.h" | ||||
| #include "../helpers/slideshow.h" | ||||
| 
 | ||||
| struct DesktopSlideshowView { | ||||
|     View* view; | ||||
|     DesktopSlideshowViewCallback callback; | ||||
|     void* context; | ||||
| }; | ||||
| 
 | ||||
| typedef struct { | ||||
|     uint8_t page; | ||||
|     Slideshow* slideshow; | ||||
| } DesktopSlideshowViewModel; | ||||
| 
 | ||||
| static void desktop_view_slideshow_draw(Canvas* canvas, void* model) { | ||||
|     DesktopSlideshowViewModel* m = model; | ||||
| 
 | ||||
|     canvas_clear(canvas); | ||||
|     slideshow_draw(m->slideshow, canvas, 0, 0); | ||||
| } | ||||
| 
 | ||||
| static bool desktop_view_slideshow_input(InputEvent* event, void* context) { | ||||
|     furi_assert(event); | ||||
|     DesktopSlideshowView* instance = context; | ||||
| 
 | ||||
|     if(event->type == InputTypeShort) { | ||||
|         DesktopSlideshowViewModel* model = view_get_model(instance->view); | ||||
|         bool end_slideshow = false; | ||||
|         switch(event->key) { | ||||
|         case InputKeyLeft: | ||||
|             slideshow_goback(model->slideshow); | ||||
|             break; | ||||
|         case InputKeyRight: | ||||
|         case InputKeyOk: | ||||
|             end_slideshow = !slideshow_advance(model->slideshow); | ||||
|             break; | ||||
|         case InputKeyBack: | ||||
|             end_slideshow = true; | ||||
|         default: | ||||
|             break; | ||||
|         } | ||||
|         if(end_slideshow) { | ||||
|             instance->callback(DesktopSlideshowCompleted, instance->context); | ||||
|         } | ||||
|         view_commit_model(instance->view, true); | ||||
|     } | ||||
| 
 | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| static void desktop_view_slideshow_enter(void* context) { | ||||
|     DesktopSlideshowView* instance = context; | ||||
| 
 | ||||
|     DesktopSlideshowViewModel* model = view_get_model(instance->view); | ||||
|     model->slideshow = slideshow_alloc(); | ||||
|     if(!slideshow_load(model->slideshow, "/int/slideshow")) { | ||||
|         instance->callback(DesktopSlideshowCompleted, instance->context); | ||||
|     } | ||||
|     view_commit_model(instance->view, false); | ||||
| } | ||||
| 
 | ||||
| static void desktop_view_slideshow_exit(void* context) { | ||||
|     DesktopSlideshowView* instance = context; | ||||
| 
 | ||||
|     DesktopSlideshowViewModel* model = view_get_model(instance->view); | ||||
|     slideshow_free(model->slideshow); | ||||
|     view_commit_model(instance->view, false); | ||||
| } | ||||
| 
 | ||||
| DesktopSlideshowView* desktop_view_slideshow_alloc() { | ||||
|     DesktopSlideshowView* instance = malloc(sizeof(DesktopSlideshowView)); | ||||
|     instance->view = view_alloc(); | ||||
|     view_allocate_model(instance->view, ViewModelTypeLocking, sizeof(DesktopSlideshowViewModel)); | ||||
|     view_set_context(instance->view, instance); | ||||
|     view_set_draw_callback(instance->view, (ViewDrawCallback)desktop_view_slideshow_draw); | ||||
|     view_set_input_callback(instance->view, desktop_view_slideshow_input); | ||||
|     view_set_enter_callback(instance->view, desktop_view_slideshow_enter); | ||||
|     view_set_exit_callback(instance->view, desktop_view_slideshow_exit); | ||||
| 
 | ||||
|     return instance; | ||||
| } | ||||
| 
 | ||||
| void desktop_view_slideshow_free(DesktopSlideshowView* instance) { | ||||
|     furi_assert(instance); | ||||
| 
 | ||||
|     view_free(instance->view); | ||||
|     free(instance); | ||||
| } | ||||
| 
 | ||||
| View* desktop_view_slideshow_get_view(DesktopSlideshowView* instance) { | ||||
|     furi_assert(instance); | ||||
|     return instance->view; | ||||
| } | ||||
| 
 | ||||
| void desktop_view_slideshow_set_callback( | ||||
|     DesktopSlideshowView* instance, | ||||
|     DesktopSlideshowViewCallback callback, | ||||
|     void* context) { | ||||
|     furi_assert(instance); | ||||
|     furi_assert(callback); | ||||
|     instance->callback = callback; | ||||
|     instance->context = context; | ||||
| } | ||||
							
								
								
									
										20
									
								
								applications/desktop/views/desktop_view_slideshow.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								applications/desktop/views/desktop_view_slideshow.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,20 @@ | ||||
| #pragma once | ||||
| 
 | ||||
| #include <gui/view.h> | ||||
| 
 | ||||
| #include "desktop_events.h" | ||||
| 
 | ||||
| typedef struct DesktopSlideshowView DesktopSlideshowView; | ||||
| 
 | ||||
| typedef void (*DesktopSlideshowViewCallback)(DesktopEvent event, void* context); | ||||
| 
 | ||||
| DesktopSlideshowView* desktop_view_slideshow_alloc(); | ||||
| 
 | ||||
| void desktop_view_slideshow_free(DesktopSlideshowView* main_view); | ||||
| 
 | ||||
| View* desktop_view_slideshow_get_view(DesktopSlideshowView* main_view); | ||||
| 
 | ||||
| void desktop_view_slideshow_set_callback( | ||||
|     DesktopSlideshowView* main_view, | ||||
|     DesktopSlideshowViewCallback callback, | ||||
|     void* context); | ||||
| @ -24,6 +24,7 @@ static const char* update_task_stage_descr[] = { | ||||
|     [UpdateTaskStageLfsBackup] = "Backing up LFS", | ||||
|     [UpdateTaskStageLfsRestore] = "Restoring LFS", | ||||
|     [UpdateTaskStageResourcesUpdate] = "Updating resources", | ||||
|     [UpdateTaskStageSplashscreenInstall] = "Installing splashscreen", | ||||
|     [UpdateTaskStageCompleted] = "Restarting...", | ||||
|     [UpdateTaskStageError] = "Error", | ||||
|     [UpdateTaskStageOBError] = "OB, report", | ||||
| @ -41,13 +42,13 @@ static const UpdateTaskStageGroupMap update_task_stage_progress[] = { | ||||
|     [UpdateTaskStageProgress] = STAGE_DEF(UpdateTaskStageGroupMisc, 0), | ||||
| 
 | ||||
|     [UpdateTaskStageReadManifest] = STAGE_DEF(UpdateTaskStageGroupPreUpdate, 5), | ||||
|     [UpdateTaskStageLfsBackup] = STAGE_DEF(UpdateTaskStageGroupPreUpdate, 30), | ||||
|     [UpdateTaskStageLfsBackup] = STAGE_DEF(UpdateTaskStageGroupPreUpdate, 15), | ||||
| 
 | ||||
|     [UpdateTaskStageRadioImageValidate] = STAGE_DEF(UpdateTaskStageGroupRadio, 30), | ||||
|     [UpdateTaskStageRadioImageValidate] = STAGE_DEF(UpdateTaskStageGroupRadio, 10), | ||||
|     [UpdateTaskStageRadioErase] = STAGE_DEF(UpdateTaskStageGroupRadio, 50), | ||||
|     [UpdateTaskStageRadioWrite] = STAGE_DEF(UpdateTaskStageGroupRadio, 100), | ||||
|     [UpdateTaskStageRadioInstall] = STAGE_DEF(UpdateTaskStageGroupRadio, 5), | ||||
|     [UpdateTaskStageRadioBusy] = STAGE_DEF(UpdateTaskStageGroupRadio, 70), | ||||
|     [UpdateTaskStageRadioWrite] = STAGE_DEF(UpdateTaskStageGroupRadio, 90), | ||||
|     [UpdateTaskStageRadioInstall] = STAGE_DEF(UpdateTaskStageGroupRadio, 15), | ||||
|     [UpdateTaskStageRadioBusy] = STAGE_DEF(UpdateTaskStageGroupRadio, 60), | ||||
| 
 | ||||
|     [UpdateTaskStageOBValidation] = STAGE_DEF(UpdateTaskStageGroupOptionBytes, 10), | ||||
| 
 | ||||
| @ -58,6 +59,7 @@ static const UpdateTaskStageGroupMap update_task_stage_progress[] = { | ||||
|     [UpdateTaskStageLfsRestore] = STAGE_DEF(UpdateTaskStageGroupPostUpdate, 30), | ||||
| 
 | ||||
|     [UpdateTaskStageResourcesUpdate] = STAGE_DEF(UpdateTaskStageGroupResources, 255), | ||||
|     [UpdateTaskStageSplashscreenInstall] = STAGE_DEF(UpdateTaskStageGroupSplashscreen, 5), | ||||
| 
 | ||||
|     [UpdateTaskStageCompleted] = STAGE_DEF(UpdateTaskStageGroupMisc, 1), | ||||
|     [UpdateTaskStageError] = STAGE_DEF(UpdateTaskStageGroupMisc, 1), | ||||
| @ -79,6 +81,9 @@ static UpdateTaskStageGroup update_task_get_task_groups(UpdateTask* update_task) | ||||
|     if(!string_empty_p(manifest->resource_bundle)) { | ||||
|         ret |= UpdateTaskStageGroupResources; | ||||
|     } | ||||
|     if(!string_empty_p(manifest->splash_file)) { | ||||
|         ret |= UpdateTaskStageGroupSplashscreen; | ||||
|     } | ||||
|     return ret; | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -33,6 +33,7 @@ typedef enum { | ||||
| 
 | ||||
|     UpdateTaskStageLfsRestore, | ||||
|     UpdateTaskStageResourcesUpdate, | ||||
|     UpdateTaskStageSplashscreenInstall, | ||||
| 
 | ||||
|     UpdateTaskStageCompleted, | ||||
|     UpdateTaskStageError, | ||||
| @ -52,6 +53,7 @@ typedef enum { | ||||
|     UpdateTaskStageGroupRadio = 1 << 4, | ||||
|     UpdateTaskStageGroupPostUpdate = 1 << 5, | ||||
|     UpdateTaskStageGroupResources = 1 << 6, | ||||
|     UpdateTaskStageGroupSplashscreen = 1 << 7, | ||||
| } UpdateTaskStageGroup; | ||||
| 
 | ||||
| typedef struct { | ||||
|  | ||||
| @ -93,6 +93,19 @@ static bool update_task_post_update(UpdateTask* update_task) { | ||||
|                 CHECK_RESULT(tar_archive_unpack_to(archive, EXT_PATH)); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         if(update_task->state.groups & UpdateTaskStageGroupSplashscreen) { | ||||
|             update_task_set_progress(update_task, UpdateTaskStageSplashscreenInstall, 0); | ||||
|             string_t tmp_path; | ||||
|             string_init_set(tmp_path, update_task->update_path); | ||||
|             path_append(tmp_path, string_get_cstr(update_task->manifest->splash_file)); | ||||
|             if(storage_common_copy( | ||||
|                    update_task->storage, string_get_cstr(tmp_path), "/int/slideshow") != FSE_OK) { | ||||
|                 // actually, not critical
 | ||||
|             } | ||||
|             string_clear(tmp_path); | ||||
|             update_task_set_progress(update_task, UpdateTaskStageSplashscreenInstall, 100); | ||||
|         } | ||||
|         success = true; | ||||
|     } while(false); | ||||
| 
 | ||||
|  | ||||
							
								
								
									
										
											BIN
										
									
								
								assets/slideshow/update_default/frame_00.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								assets/slideshow/update_default/frame_00.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 2.2 KiB | 
							
								
								
									
										3
									
								
								assets/splash.mk
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								assets/splash.mk
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,3 @@ | ||||
| ASSETS_DIR			?= $(PROJECT_ROOT)/assets | ||||
| UPDATER_SPLASH		?= update_default | ||||
| UPDATER_SPLASH_DIR	:= $(ASSETS_DIR)/slideshow/$(UPDATER_SPLASH) | ||||
| @ -17,6 +17,7 @@ | ||||
| #define MANIFEST_KEY_OB_REFERENCE "OB reference" | ||||
| #define MANIFEST_KEY_OB_MASK "OB mask" | ||||
| #define MANIFEST_KEY_OB_WRITE_MASK "OB write mask" | ||||
| #define MANIFEST_KEY_SPLASH_FILE "Splashscreen" | ||||
| 
 | ||||
| UpdateManifest* update_manifest_alloc() { | ||||
|     UpdateManifest* update_manifest = malloc(sizeof(UpdateManifest)); | ||||
| @ -25,6 +26,7 @@ UpdateManifest* update_manifest_alloc() { | ||||
|     string_init(update_manifest->radio_image); | ||||
|     string_init(update_manifest->staged_loader_file); | ||||
|     string_init(update_manifest->resource_bundle); | ||||
|     string_init(update_manifest->splash_file); | ||||
|     update_manifest->target = 0; | ||||
|     update_manifest->manifest_version = 0; | ||||
|     memset(update_manifest->ob_reference.bytes, 0, FURI_HAL_FLASH_OB_RAW_SIZE_BYTES); | ||||
| @ -41,6 +43,7 @@ void update_manifest_free(UpdateManifest* update_manifest) { | ||||
|     string_clear(update_manifest->radio_image); | ||||
|     string_clear(update_manifest->staged_loader_file); | ||||
|     string_clear(update_manifest->resource_bundle); | ||||
|     string_clear(update_manifest->splash_file); | ||||
|     free(update_manifest); | ||||
| } | ||||
| 
 | ||||
| @ -107,6 +110,9 @@ static bool | ||||
|             update_manifest->ob_write_mask.bytes, | ||||
|             FURI_HAL_FLASH_OB_RAW_SIZE_BYTES); | ||||
| 
 | ||||
|         flipper_format_read_string( | ||||
|             flipper_file, MANIFEST_KEY_SPLASH_FILE, update_manifest->splash_file); | ||||
| 
 | ||||
|         update_manifest->valid = | ||||
|             (!string_empty_p(update_manifest->firmware_dfu_image) || | ||||
|              !string_empty_p(update_manifest->radio_image) || | ||||
|  | ||||
| @ -42,6 +42,7 @@ typedef struct { | ||||
|     FuriHalFlashRawOptionByteData ob_reference; | ||||
|     FuriHalFlashRawOptionByteData ob_compare_mask; | ||||
|     FuriHalFlashRawOptionByteData ob_write_mask; | ||||
|     string_t splash_file; | ||||
|     bool valid; | ||||
| } UpdateManifest; | ||||
| 
 | ||||
|  | ||||
| @ -38,9 +38,9 @@ class Main(App): | ||||
|             return 1 | ||||
| 
 | ||||
|         with open(self.args.input, mode="rb") as file: | ||||
|             bin = file.read() | ||||
|             bindata = file.read() | ||||
| 
 | ||||
|         data = struct.pack("<II", self.args.address, len(bin)) + bin | ||||
|         data = struct.pack("<II", self.args.address, len(bindata)) + bindata | ||||
| 
 | ||||
|         # Target prefix | ||||
|         szTargetName = self.args.label.encode("ascii") | ||||
|  | ||||
							
								
								
									
										61
									
								
								scripts/slideshow.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								scripts/slideshow.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,61 @@ | ||||
| #!/usr/bin/env python3 | ||||
| 
 | ||||
| import os | ||||
| import struct | ||||
| 
 | ||||
| from flipper.app import App | ||||
| from flipper.assets.icon import file2image | ||||
| 
 | ||||
| 
 | ||||
| class Main(App): | ||||
|     MAGIC = 0x72676468 | ||||
|     VERSION = 1 | ||||
| 
 | ||||
|     def init(self): | ||||
|         self.parser.add_argument("-i", "--input", help="input folder", required=True) | ||||
|         self.parser.add_argument("-o", "--output", help="output file", required=True) | ||||
| 
 | ||||
|         self.parser.set_defaults(func=self.pack) | ||||
| 
 | ||||
|     def pack(self): | ||||
|         if not os.path.exists(self.args.input): | ||||
|             self.logger.error(f'"{self.args.input}" does not exist') | ||||
|             return 1 | ||||
| 
 | ||||
|         file_idx = 0 | ||||
|         images = [] | ||||
|         while True: | ||||
|             frame_filename = os.path.join(self.args.input, f"frame_{file_idx:02}.png") | ||||
|             if not os.path.exists(frame_filename): | ||||
|                 break | ||||
| 
 | ||||
|             self.logger.debug(f"working on {frame_filename}") | ||||
|             try: | ||||
|                 images.append(file2image(frame_filename)) | ||||
|                 self.logger.debug(f"Processed frame #{file_idx}") | ||||
|                 file_idx += 1 | ||||
|             except Exception as e: | ||||
|                 self.logger.error(e) | ||||
|                 break | ||||
| 
 | ||||
|         widths = set(img.width for img in images) | ||||
|         heights = set(img.height for img in images) | ||||
|         if len(widths) != 1 or len(heights) != 1: | ||||
|             self.logger.error("All images must have same dimensions!") | ||||
|             return 2 | ||||
| 
 | ||||
|         data = struct.pack( | ||||
|             "<IBBBB", self.MAGIC, self.VERSION, widths.pop(), heights.pop(), len(images) | ||||
|         ) | ||||
|         for image in images: | ||||
|             data += struct.pack("<H", len(image.data)) | ||||
|             data += image.data | ||||
| 
 | ||||
|         with open(self.args.output, mode="wb") as file: | ||||
|             file.write(data) | ||||
| 
 | ||||
|         return 0 | ||||
| 
 | ||||
| 
 | ||||
| if __name__ == "__main__": | ||||
|     Main()() | ||||
| @ -11,6 +11,8 @@ import zlib | ||||
| import tarfile | ||||
| import math | ||||
| 
 | ||||
| from slideshow import Main as SlideshowMain | ||||
| 
 | ||||
| 
 | ||||
| class Main(App): | ||||
|     UPDATE_MANIFEST_VERSION = 2 | ||||
| @ -31,6 +33,9 @@ class Main(App): | ||||
|     FLASH_BASE = 0x8000000 | ||||
|     MIN_LFS_PAGES = 6 | ||||
| 
 | ||||
|     # Post-update slideshow | ||||
|     SPLASH_BIN_NAME = "splash.bin" | ||||
| 
 | ||||
|     def init(self): | ||||
|         self.subparsers = self.parser.add_subparsers(help="sub-command help") | ||||
| 
 | ||||
| @ -63,6 +68,7 @@ class Main(App): | ||||
|         ) | ||||
| 
 | ||||
|         self.parser_generate.add_argument("--obdata", dest="obdata", required=False) | ||||
|         self.parser_generate.add_argument("--splash", dest="splash", required=False) | ||||
|         self.parser_generate.add_argument( | ||||
|             "--I-understand-what-I-am-doing", dest="disclaimer", required=False | ||||
|         ) | ||||
| @ -124,6 +130,19 @@ class Main(App): | ||||
|                 self.disclaimer() | ||||
|                 return 2 | ||||
| 
 | ||||
|         if self.args.splash: | ||||
|             splash_args = [ | ||||
|                 "-i", | ||||
|                 self.args.splash, | ||||
|                 "-o", | ||||
|                 join(self.args.directory, self.SPLASH_BIN_NAME), | ||||
|             ] | ||||
|             if splash_code := SlideshowMain(no_exit=True)(splash_args): | ||||
|                 self.logger.error( | ||||
|                     f"Failed to convert splash screen data: {splash_code}" | ||||
|                 ) | ||||
|                 return splash_code | ||||
| 
 | ||||
|         file = FlipperFormatFile() | ||||
|         file.setHeader( | ||||
|             "Flipper firmware upgrade configuration", self.UPDATE_MANIFEST_VERSION | ||||
| @ -152,6 +171,7 @@ class Main(App): | ||||
|         file.writeKey("OB reference", self.bytes2ffhex(obvalues.reference)) | ||||
|         file.writeKey("OB mask", self.bytes2ffhex(obvalues.compare_mask)) | ||||
|         file.writeKey("OB write mask", self.bytes2ffhex(obvalues.write_mask)) | ||||
|         file.writeKey("Splashscreen", self.SPLASH_BIN_NAME if self.args.splash else "") | ||||
|         file.save(join(self.args.directory, self.UPDATE_MANIFEST_NAME)) | ||||
| 
 | ||||
|         return 0 | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 hedger
						hedger