Skip to content

Commit aaece46

Browse files
committed
wip unlock animation component and workflow
1 parent 424d1ff commit aaece46

File tree

9 files changed

+289
-0
lines changed

9 files changed

+289
-0
lines changed

src/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ set(DBB-FIRMWARE-UI-SOURCES
8787
${CMAKE_SOURCE_DIR}/src/ui/components/orientation_arrows.c
8888
${CMAKE_SOURCE_DIR}/src/ui/components/info_centered.c
8989
${CMAKE_SOURCE_DIR}/src/ui/components/lockscreen.c
90+
${CMAKE_SOURCE_DIR}/src/ui/components/unlock_animation.c
9091
${CMAKE_SOURCE_DIR}/src/ui/components/menu.c
9192
${CMAKE_SOURCE_DIR}/src/ui/components/status.c
9293
${CMAKE_SOURCE_DIR}/src/ui/components/image.c

src/rust/bitbox02-rust/src/workflow.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ pub mod transaction;
2727
pub mod trinary_choice;
2828
pub mod trinary_input_string;
2929
pub mod unlock;
30+
pub mod unlock_animation;
3031
pub mod verify_message;
3132

3233
use alloc::string::String;

src/rust/bitbox02-rust/src/workflow/unlock.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,8 @@ pub async fn unlock_keystore(
8080
title: &str,
8181
can_cancel: password::CanCancel,
8282
) -> Result<(), UnlockError> {
83+
super::unlock_animation::animate().await;
84+
8385
let password = password::enter(
8486
hal,
8587
title,
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// Copyright 2025 Shift Crypto AG
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
use crate::bb02_async::option_no_screensaver;
16+
use core::cell::RefCell;
17+
18+
pub async fn animate() {
19+
let result = RefCell::new(None as Option<()>);
20+
let mut component = bitbox02::ui::unlock_animation_create(|| {
21+
*result.borrow_mut() = Some(());
22+
});
23+
component.screen_stack_push();
24+
option_no_screensaver(&result).await
25+
}

src/rust/bitbox02-sys/build.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ const ALLOWLIST_FNS: &[&str] = &[
6767
"delay_ms",
6868
"delay_us",
6969
"empty_create",
70+
"unlock_animation_create",
7071
"keystore_bip39_mnemonic_to_seed",
7172
"keystore_copy_seed",
7273
"keystore_copy_bip39_seed",

src/rust/bitbox02-sys/wrapper.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
#include <ui/components/status.h>
4040
#include <ui/components/trinary_choice.h>
4141
#include <ui/components/trinary_input_string.h>
42+
#include <ui/components/unlock_animation.h>
4243
#include <ui/fonts/font_a_11X10.h>
4344
#include <ui/fonts/font_a_9X9.h>
4445
#include <ui/fonts/monogram_5X9.h>

src/rust/bitbox02/src/ui/ui.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -476,3 +476,31 @@ pub fn empty_create<'a>() -> Component<'a> {
476476
_p: PhantomData,
477477
}
478478
}
479+
480+
pub fn unlock_animation_create<'a, F>(on_done: F) -> Component<'a>
481+
where
482+
// Callback must outlive component.
483+
F: FnMut() + 'a,
484+
{
485+
unsafe extern "C" fn c_on_done<F2>(param: *mut c_void)
486+
where
487+
F2: FnMut(),
488+
{
489+
// The callback is dropped afterwards. This is safe because
490+
// this C callback is guaranteed to be called only once.
491+
let mut on_done = unsafe { Box::from_raw(param as *mut F2) };
492+
on_done();
493+
}
494+
let component = unsafe {
495+
bitbox02_sys::unlock_animation_create(
496+
Some(c_on_done::<F>),
497+
Box::into_raw(Box::new(on_done)) as *mut _, // passed to c_on_done as `param`.
498+
)
499+
};
500+
Component {
501+
component,
502+
is_pushed: false,
503+
on_drop: None,
504+
_p: PhantomData,
505+
}
506+
}
Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
// Copyright 2025 Shift Crypto AG
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#include "unlock_animation.h"
16+
17+
#include <hardfault.h>
18+
#include <screen.h>
19+
#include <ui/component.h>
20+
#include <ui/ui_util.h>
21+
22+
#include <stdint.h>
23+
#include <string.h>
24+
25+
// This many iterations times the slowdown factor to render the whole animation.
26+
#define LOCK_ANIMATION_N_FRAMES (38)
27+
28+
#define SLOWDOWN_FACTOR (10)
29+
30+
#define LOCK_ANIMATION_FRAME_WIDTH (28)
31+
#define LOCK_ANIMATION_FRAME_HEIGHT (25)
32+
#define LOCK_ANIMATION_FRAME_SIZE \
33+
((LOCK_ANIMATION_FRAME_WIDTH * LOCK_ANIMATION_FRAME_HEIGHT + 7) / 8)
34+
35+
typedef struct {
36+
int frame;
37+
void (*on_done)(void*);
38+
void* on_done_param;
39+
} data_t;
40+
41+
#define LOCK_ANIMATION_ACTUAL_N_FRAMES (10)
42+
/**
43+
* Use less frames than ticks for the animation.
44+
*
45+
* Split the remaining animation time into long frame times
46+
* for the closed lock, open lock, and turned lock.
47+
*/
48+
#define LOCK_ANIMATION_FRAMES_STOP_TIME \
49+
((LOCK_ANIMATION_N_FRAMES - LOCK_ANIMATION_ACTUAL_N_FRAMES) / 3)
50+
51+
/**
52+
* At frame 4, the animation will pause for a second.
53+
*/
54+
#define LOCK_ANIMATION_PAUSE_FRAME (2)
55+
56+
static const uint8_t LOCK_ANIMATION[LOCK_ANIMATION_ACTUAL_N_FRAMES][LOCK_ANIMATION_FRAME_SIZE] = {
57+
// Frame 0 - Closed lock
58+
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
59+
0x00, 0x00, 0x00, 0x00, 0x03, 0xe0, 0x00, 0x00, 0xff, 0x80, 0x00, 0x1c, 0x1c, 0x00, 0x03,
60+
0x80, 0xe0, 0x00, 0x30, 0x06, 0x00, 0x06, 0x00, 0x30, 0x00, 0x60, 0x03, 0x00, 0x06, 0x00,
61+
0x30, 0x00, 0x7f, 0xff, 0x00, 0x07, 0xff, 0xf0, 0x00, 0x7f, 0xff, 0x00, 0x07, 0xff, 0xf0,
62+
0x00, 0x7f, 0xff, 0x00, 0x07, 0xff, 0xf0, 0x00, 0x7f, 0xff, 0x00, 0x07, 0xff, 0xf0, 0x00,
63+
0x7f, 0xff, 0x00, 0x07, 0xff, 0xf0, 0x00, 0x7f, 0xff, 0x00, 0x03, 0xff, 0xe0},
64+
// Frame 1
65+
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xe0, 0x00,
66+
0x00, 0xff, 0x80, 0x00, 0x1c, 0x1c, 0x00, 0x03, 0x80, 0xe0, 0x00, 0x30, 0x06, 0x00, 0x06,
67+
0x00, 0x30, 0x00, 0x60, 0x03, 0x00, 0x06, 0x00, 0x30, 0x00, 0x60, 0x00, 0x00, 0x06, 0x00,
68+
0x00, 0x00, 0x7f, 0xff, 0x00, 0x07, 0xff, 0xf0, 0x00, 0x7f, 0xff, 0x00, 0x07, 0xff, 0xf0,
69+
0x00, 0x7f, 0xff, 0x00, 0x07, 0xff, 0xf0, 0x00, 0x7f, 0xff, 0x00, 0x07, 0xff, 0xf0, 0x00,
70+
0x7f, 0xff, 0x00, 0x07, 0xff, 0xf0, 0x00, 0x7f, 0xff, 0x00, 0x03, 0xff, 0xe0},
71+
// Frame 2 - Open lock
72+
{0x00, 0x00, 0x3e, 0x00, 0x00, 0x0f, 0xf8, 0x00, 0x01, 0xc1, 0xc0, 0x00, 0x38, 0x0e, 0x00,
73+
0x03, 0x00, 0x60, 0x00, 0x60, 0x03, 0x00, 0x06, 0x00, 0x30, 0x00, 0x60, 0x03, 0x00, 0x06,
74+
0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x06, 0x00,
75+
0x00, 0x00, 0x7f, 0xff, 0x00, 0x07, 0xff, 0xf0, 0x00, 0x7f, 0xff, 0x00, 0x07, 0xff, 0xf0,
76+
0x00, 0x7f, 0xff, 0x00, 0x07, 0xff, 0xf0, 0x00, 0x7f, 0xff, 0x00, 0x07, 0xff, 0xf0, 0x00,
77+
0x7f, 0xff, 0x00, 0x07, 0xff, 0xf0, 0x00, 0x7f, 0xff, 0x00, 0x03, 0xff, 0xe0},
78+
// Frame 3
79+
{0x00, 0x00, 0x38, 0x00, 0x00, 0x0f, 0xe0, 0x00, 0x01, 0xc7, 0x00, 0x00, 0x38, 0x38, 0x00,
80+
0x03, 0x01, 0x80, 0x00, 0x60, 0x0c, 0x00, 0x06, 0x00, 0xc0, 0x00, 0x60, 0x0c, 0x00, 0x06,
81+
0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x06, 0x00,
82+
0x00, 0x00, 0x7f, 0xff, 0x00, 0x07, 0xff, 0xf0, 0x00, 0x7f, 0xff, 0x00, 0x07, 0xff, 0xf0,
83+
0x00, 0x7f, 0xff, 0x00, 0x07, 0xff, 0xf0, 0x00, 0x7f, 0xff, 0x00, 0x07, 0xff, 0xf0, 0x00,
84+
0x7f, 0xff, 0x00, 0x07, 0xff, 0xf0, 0x00, 0x7f, 0xff, 0x00, 0x03, 0xff, 0xe0},
85+
// Frame 4
86+
{0x00, 0x00, 0xe0, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x01, 0xb0, 0x00, 0x00, 0x31, 0x80, 0x00,
87+
0x03, 0x18, 0x00, 0x00, 0x61, 0x80, 0x00, 0x06, 0x18, 0x00, 0x00, 0x61, 0x80, 0x00, 0x06,
88+
0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x06, 0x00,
89+
0x00, 0x00, 0x7f, 0xff, 0x00, 0x07, 0xff, 0xf0, 0x00, 0x7f, 0xff, 0x00, 0x07, 0xff, 0xf0,
90+
0x00, 0x7f, 0xff, 0x00, 0x07, 0xff, 0xf0, 0x00, 0x7f, 0xff, 0x00, 0x07, 0xff, 0xf0, 0x00,
91+
0x7f, 0xff, 0x00, 0x07, 0xff, 0xf0, 0x00, 0x7f, 0xff, 0x00, 0x03, 0xff, 0xe0},
92+
// Frame 5
93+
{0x00, 0x01, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x03, 0x80, 0x00, 0x00, 0x38, 0x00, 0x00,
94+
0x03, 0x80, 0x00, 0x00, 0x68, 0x00, 0x00, 0x06, 0x80, 0x00, 0x00, 0x68, 0x00, 0x00, 0x06,
95+
0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x06, 0x00,
96+
0x00, 0x00, 0x7f, 0xff, 0x00, 0x07, 0xff, 0xf0, 0x00, 0x7f, 0xff, 0x00, 0x07, 0xff, 0xf0,
97+
0x00, 0x7f, 0xff, 0x00, 0x07, 0xff, 0xf0, 0x00, 0x7f, 0xff, 0x00, 0x07, 0xff, 0xf0, 0x00,
98+
0x7f, 0xff, 0x00, 0x07, 0xff, 0xf0, 0x00, 0x7f, 0xff, 0x00, 0x03, 0xff, 0xe0},
99+
// Frame 6
100+
{0x00, 0x08, 0x00, 0x00, 0x01, 0xc0, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x01, 0xc0, 0x00, 0x00,
101+
0x1c, 0x00, 0x00, 0x01, 0x60, 0x00, 0x00, 0x16, 0x00, 0x00, 0x01, 0x60, 0x00, 0x00, 0x06,
102+
0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x06, 0x00,
103+
0x00, 0x00, 0x7f, 0xff, 0x00, 0x07, 0xff, 0xf0, 0x00, 0x7f, 0xff, 0x00, 0x07, 0xff, 0xf0,
104+
0x00, 0x7f, 0xff, 0x00, 0x07, 0xff, 0xf0, 0x00, 0x7f, 0xff, 0x00, 0x07, 0xff, 0xf0, 0x00,
105+
0x7f, 0xff, 0x00, 0x07, 0xff, 0xf0, 0x00, 0x7f, 0xff, 0x00, 0x03, 0xff, 0xe0},
106+
// Frame 7
107+
{0x00, 0x70, 0x00, 0x00, 0x0d, 0x80, 0x00, 0x00, 0xd8, 0x00, 0x00, 0x18, 0xc0, 0x00, 0x01,
108+
0x8c, 0x00, 0x00, 0x18, 0x60, 0x00, 0x01, 0x86, 0x00, 0x00, 0x18, 0x60, 0x00, 0x00, 0x06,
109+
0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x06, 0x00,
110+
0x00, 0x00, 0x7f, 0xff, 0x00, 0x07, 0xff, 0xf0, 0x00, 0x7f, 0xff, 0x00, 0x07, 0xff, 0xf0,
111+
0x00, 0x7f, 0xff, 0x00, 0x07, 0xff, 0xf0, 0x00, 0x7f, 0xff, 0x00, 0x07, 0xff, 0xf0, 0x00,
112+
0x7f, 0xff, 0x00, 0x07, 0xff, 0xf0, 0x00, 0x7f, 0xff, 0x00, 0x03, 0xff, 0xe0},
113+
// Frame 8
114+
{0x01, 0xc0, 0x00, 0x00, 0x7f, 0x00, 0x00, 0x0e, 0x38, 0x00, 0x01, 0xc1, 0xc0, 0x00, 0x18,
115+
0x0c, 0x00, 0x03, 0x00, 0x60, 0x00, 0x30, 0x06, 0x00, 0x03, 0x00, 0x60, 0x00, 0x00, 0x06,
116+
0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x06, 0x00,
117+
0x00, 0x00, 0x7f, 0xff, 0x00, 0x07, 0xff, 0xf0, 0x00, 0x7f, 0xff, 0x00, 0x07, 0xff, 0xf0,
118+
0x00, 0x7f, 0xff, 0x00, 0x07, 0xff, 0xf0, 0x00, 0x7f, 0xff, 0x00, 0x07, 0xff, 0xf0, 0x00,
119+
0x7f, 0xff, 0x00, 0x07, 0xff, 0xf0, 0x00, 0x7f, 0xff, 0x00, 0x03, 0xff, 0xe0},
120+
// Frame 9 - Turned lock
121+
{0x07, 0xc0, 0x00, 0x01, 0xff, 0x00, 0x00, 0x38, 0x38, 0x00, 0x07, 0x01, 0xc0, 0x00, 0x60,
122+
0x0c, 0x00, 0x0c, 0x00, 0x60, 0x00, 0xc0, 0x06, 0x00, 0x0c, 0x00, 0x60, 0x00, 0x00, 0x06,
123+
0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x06, 0x00,
124+
0x00, 0x00, 0x7f, 0xff, 0x00, 0x07, 0xff, 0xf0, 0x00, 0x7f, 0xff, 0x00, 0x07, 0xff, 0xf0,
125+
0x00, 0x7f, 0xff, 0x00, 0x07, 0xff, 0xf0, 0x00, 0x7f, 0xff, 0x00, 0x07, 0xff, 0xf0, 0x00,
126+
0x7f, 0xff, 0x00, 0x07, 0xff, 0xf0, 0x00, 0x7f, 0xff, 0x00, 0x03, 0xff, 0xe0}};
127+
128+
/**
129+
* Gets a frame of the lock animation.
130+
*/
131+
static const uint8_t* _get_frame(int frame_idx)
132+
{
133+
if (frame_idx >= LOCK_ANIMATION_N_FRAMES) {
134+
Abort("Invalid lock animation frame requested.");
135+
}
136+
/* First part of the animation: Closed lock for LOCK_ANIMATION_FRAMES_STOP_TIME frames. */
137+
if (frame_idx < LOCK_ANIMATION_FRAMES_STOP_TIME) {
138+
return LOCK_ANIMATION[0];
139+
}
140+
/* Second part: show the lock opening */
141+
int actual_frame_idx = frame_idx - LOCK_ANIMATION_FRAMES_STOP_TIME;
142+
if (actual_frame_idx < LOCK_ANIMATION_PAUSE_FRAME) {
143+
return LOCK_ANIMATION[actual_frame_idx];
144+
}
145+
/* Third part: keep the lock open for a while. */
146+
if (actual_frame_idx < LOCK_ANIMATION_FRAMES_STOP_TIME + LOCK_ANIMATION_PAUSE_FRAME) {
147+
return LOCK_ANIMATION[LOCK_ANIMATION_PAUSE_FRAME];
148+
}
149+
/* Fourth/fifth part: Spin the lock, then keep it open for a while. */
150+
actual_frame_idx -= LOCK_ANIMATION_FRAMES_STOP_TIME;
151+
if (actual_frame_idx >= LOCK_ANIMATION_ACTUAL_N_FRAMES) {
152+
return LOCK_ANIMATION[LOCK_ANIMATION_ACTUAL_N_FRAMES - 1];
153+
}
154+
return LOCK_ANIMATION[actual_frame_idx];
155+
}
156+
157+
static void _render(component_t* component)
158+
{
159+
data_t* data = (data_t*)component->data;
160+
int frame = data->frame / SLOWDOWN_FACTOR;
161+
162+
if (frame == LOCK_ANIMATION_N_FRAMES) {
163+
/* End of the animation */
164+
if (data->on_done) {
165+
data->on_done(data->on_done_param);
166+
data->on_done = NULL;
167+
}
168+
return;
169+
}
170+
171+
/* Draw the frame. */
172+
position_t pos = {
173+
.left = (SCREEN_WIDTH - LOCK_ANIMATION_FRAME_WIDTH) / 2,
174+
.top = (SCREEN_HEIGHT - LOCK_ANIMATION_FRAME_HEIGHT) / 2};
175+
dimension_t dim = {.width = LOCK_ANIMATION_FRAME_WIDTH, .height = LOCK_ANIMATION_FRAME_HEIGHT};
176+
in_buffer_t image = {.data = _get_frame(frame), .len = LOCK_ANIMATION_FRAME_SIZE};
177+
graphics_draw_image(&pos, &dim, &image);
178+
data->frame++;
179+
}
180+
181+
static const component_functions_t _component_functions = {
182+
.cleanup = ui_util_component_cleanup,
183+
.render = _render,
184+
.on_event = ui_util_on_event_noop,
185+
};
186+
187+
component_t* unlock_animation_create(void (*on_done)(void*), void* on_done_param)
188+
{
189+
data_t* data = malloc(sizeof(data_t));
190+
if (!data) {
191+
Abort("Error: malloc unlock_animation data");
192+
}
193+
memset(data, 0, sizeof(data_t));
194+
data->on_done = on_done;
195+
data->on_done_param = on_done_param;
196+
197+
component_t* component = malloc(sizeof(component_t));
198+
if (!component) {
199+
Abort("Error: malloc unlock_animation_data");
200+
}
201+
memset(component, 0, sizeof(component_t));
202+
203+
component->f = &_component_functions;
204+
component->data = data;
205+
component->dimension.width = SCREEN_WIDTH;
206+
component->dimension.height = SCREEN_HEIGHT;
207+
return component;
208+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// Copyright 2025 Shift Crypto AG
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#ifndef _UNLOCK_ANIMATION_H_
16+
#define _UNLOCK_ANIMATION_H_
17+
18+
#include <ui/component.h>
19+
20+
component_t* unlock_animation_create(void (*on_done)(void*), void* on_done_param);
21+
22+
#endif

0 commit comments

Comments
 (0)