Embedded SDK
Embedded SDK
ux_layout_paging.c
Go to the documentation of this file.
1 
2 /*******************************************************************************
3  * Ledger Nano S - Secure firmware
4  * (c) 2022 Ledger
5  *
6  * Licensed under the Apache License, Version 2.0 (the "License");
7  * you may not use this file except in compliance with the License.
8  * You may obtain a copy of the License at
9  *
10  * http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing, software
13  * distributed under the License is distributed on an "AS IS" BASIS,
14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  * See the License for the specific language governing permissions and
16  * limitations under the License.
17  ********************************************************************************/
18 
19 #include "os_helpers.h"
20 #include "os_math.h"
21 #include "os_pic.h"
22 #include "os_print.h"
23 #include "os_utils.h"
24 #include "ux.h"
25 #include <string.h>
26 #include "os.h"
27 
28 #ifdef HAVE_UX_FLOW
29 
30 #include "ux_layout_common.h"
31 
32 #ifdef HAVE_BAGL
33 
34 /*********************************************************************************
35  * 4 text lines
36  */
37 #define LINE_FONT BAGL_FONT_OPEN_SANS_REGULAR_11px
38 
39 #if (BAGL_WIDTH == 128 && BAGL_HEIGHT == 64)
40 // clang-format off
41 static const bagl_element_t ux_layout_paging_elements[] = {
42  // erase
43  {{BAGL_RECTANGLE , 0x00, 0, 0, 128, 64, 0, 0, BAGL_FILL, 0x000000, 0xFFFFFF, 0, 0}, .text=NULL},
44 
45  {{BAGL_ICON , 0x01, 2, 28, 4, 7, 0, 0, 0 , 0xFFFFFF, 0x000000, 0, 0 }, .text=(const char*)&C_icon_left},
46  {{BAGL_ICON , 0x02, 122, 28, 4, 7, 0, 0, 0 , 0xFFFFFF, 0x000000, 0, 0 }, .text=(const char*)&C_icon_right},
47 
48  {{BAGL_LABELINE , 0x10, 0, 15, 128, 12, 0, 0, 0 , 0xFFFFFF, 0x000000, BAGL_FONT_OPEN_SANS_EXTRABOLD_11px|BAGL_FONT_ALIGNMENT_CENTER, 0 }, .text=NULL},
49  {{BAGL_LABELINE , 0x11, (128-PIXEL_PER_LINE)/2, 29, PIXEL_PER_LINE, 12, 0, 0, 0 , 0xFFFFFF, 0x000000, LINE_FONT|BAGL_FONT_ALIGNMENT_CENTER, 0 }, .text=NULL},
50  {{BAGL_LABELINE , 0x12, (128-PIXEL_PER_LINE)/2, 43, PIXEL_PER_LINE, 12, 0, 0, 0 , 0xFFFFFF, 0x000000, LINE_FONT|BAGL_FONT_ALIGNMENT_CENTER, 0 }, .text=NULL},
51  {{BAGL_LABELINE , 0x13, (128-PIXEL_PER_LINE)/2, 57, PIXEL_PER_LINE, 12, 0, 0, 0 , 0xFFFFFF, 0x000000, LINE_FONT|BAGL_FONT_ALIGNMENT_CENTER, 0 }, .text=NULL},
52 };
53 // clang-format on
54 #endif // (BAGL_WIDTH==128 && BAGL_HEIGHT==64)
55 
56 const bagl_element_t *ux_layout_paging_prepro_common(const bagl_element_t *element,
57  const char *title,
58  const char *text)
59 {
60  // copy element before any mod
61  memmove(&G_ux.tmp_element, element, sizeof(bagl_element_t));
62 
63  switch (element->component.userid) {
64  case 0x01:
65  // no step before AND no pages before
66  if (ux_flow_is_first() && G_ux.layout_paging.current == 0) {
67  return NULL;
68  }
69  break;
70 
71  case 0x02:
72  if (ux_flow_is_last() && G_ux.layout_paging.current == G_ux.layout_paging.count - 1) {
73  return NULL;
74  }
75  break;
76 
77  case 0x10:
78  // We set the boldness of the text.
79  // display
80  if (title) {
81  SPRINTF(G_ux.string_buffer,
82  (G_ux.layout_paging.count > 1) ? "%s (%d/%d)" : "%s",
83  STRPIC(title),
84  G_ux.layout_paging.current + 1,
85  G_ux.layout_paging.count);
86  }
87  else {
88  SPRINTF(G_ux.string_buffer,
89  "%d/%d",
90  G_ux.layout_paging.current + 1,
91  G_ux.layout_paging.count);
92  }
93 
94  G_ux.tmp_element.component.font_id
95  = ((G_ux.layout_paging.format & PAGING_FORMAT_BN) == PAGING_FORMAT_BN)
96  ? (BAGL_FONT_OPEN_SANS_EXTRABOLD_11px | BAGL_FONT_ALIGNMENT_CENTER)
97  : (BAGL_FONT_OPEN_SANS_REGULAR_11px | BAGL_FONT_ALIGNMENT_CENTER);
98  G_ux.tmp_element.text = G_ux.string_buffer;
99  break;
100 
101  case 0x11:
102  case 0x12:
103  case 0x13: {
104  unsigned int lineidx = (element->component.userid & 0xF) - 1;
105  if (lineidx < UX_LAYOUT_PAGING_LINE_COUNT && G_ux.layout_paging.lengths[lineidx]) {
106  SPRINTF(G_ux.string_buffer,
107  "%.*s",
108  // avoid overflow
109  MIN(sizeof(G_ux.string_buffer) - 1, G_ux.layout_paging.lengths[lineidx]),
110  (text ? STRPIC(text) : G_ux.externalText)
111  + G_ux.layout_paging.offsets[lineidx]);
112  G_ux.tmp_element.text = G_ux.string_buffer;
113 
114  G_ux.tmp_element.component.font_id
115  = ((G_ux.layout_paging.format & PAGING_FORMAT_NB) == PAGING_FORMAT_NB)
116  ? (BAGL_FONT_OPEN_SANS_EXTRABOLD_11px | BAGL_FONT_ALIGNMENT_CENTER)
117  : (BAGL_FONT_OPEN_SANS_REGULAR_11px | BAGL_FONT_ALIGNMENT_CENTER);
118  }
119  break;
120  }
121  }
122  return &G_ux.tmp_element;
123 }
124 
125 static const bagl_element_t *ux_layout_paging_prepro_by_addr(const bagl_element_t *element)
126 {
127  // don't display if null
128  const void *params = ux_stack_get_current_step_params();
129  if (NULL == params) {
130  return NULL;
131  }
132  const char *title;
133  const char *text;
134 
135 #if defined(HAVE_INDEXED_STRINGS)
136  UX_LOC_STRINGS_INDEX index = ((const ux_loc_layout_params_t *) params)->index;
137  title = get_ux_loc_string(index);
138  text = get_ux_loc_string(index + 1);
139 #else // defined(HAVE_INDEXED_STRINGS)
140  title = ((const ux_layout_paging_params_t *) params)->title;
141  text = ((const ux_layout_paging_params_t *) params)->text;
142 #endif // defined(HAVE_INDEXED_STRINGS)
143  return ux_layout_paging_prepro_common(element, title, text);
144 }
145 
146 // redisplay current page
147 void ux_layout_paging_redisplay_common(unsigned int stack_slot,
148  const char *text,
149  button_push_callback_t button_callback,
151 {
152  bagl_font_id_e font_id;
153  ux_stack_slot_t *slot = &G_ux.stack[stack_slot];
154 #if (BAGL_WIDTH == 128 && BAGL_HEIGHT == 64)
155  slot->element_arrays[0].element_array = ux_layout_paging_elements;
156  slot->element_arrays[0].element_array_count = ARRAYLEN(ux_layout_paging_elements);
157  slot->element_arrays_count = 1;
158 #else
159  ux_layout_bb_init_common(stack_slot);
160 #endif // (BAGL_WIDTH==128 && BAGL_HEIGHT==64)
161 
162  // Use the correct font, to be able to compute correctly text width:
163  if (G_ux.layout_paging.format & PAGING_FORMAT_NB) {
164  font_id = BAGL_FONT_OPEN_SANS_EXTRABOLD_11px;
165  }
166  else {
167  font_id = BAGL_FONT_OPEN_SANS_REGULAR_11px;
168  }
169  // request offsets and lengths of lines for the current page
170  ux_layout_paging_compute(text, G_ux.layout_paging.current, &G_ux.layout_paging, font_id);
171 
173  slot->button_push_callback = button_callback;
174  ux_stack_display(stack_slot);
175 }
176 
177 STATIC_IF_NOT_INDEXED unsigned int ux_layout_paging_button_callback_by_addr(
178  unsigned int button_mask,
179  unsigned int button_mask_counter);
180 
181 void ux_layout_paging_redisplay_by_addr(unsigned int stack_slot)
182 {
183  const char *text;
184  const void *params = ux_stack_get_current_step_params();
185  if (NULL == params) {
186  return;
187  }
188 #if defined(HAVE_INDEXED_STRINGS)
189  text = get_ux_loc_string(((const ux_loc_layout_params_t *) params)->index + 1);
190 #else // defined(HAVE_INDEXED_STRINGS)
191  text = ((const ux_layout_paging_params_t *) params)->text;
192 #endif // defined(HAVE_INDEXED_STRINGS)
193  ux_layout_paging_redisplay_common(stack_slot,
194  text,
195  ux_layout_paging_button_callback_by_addr,
196  ux_layout_paging_prepro_by_addr);
197 }
198 
199 static void ux_layout_paging_next(ux_layout_paging_redisplay_t redisplay)
200 {
201  if (G_ux.layout_paging.current == G_ux.layout_paging.count - 1) {
202  ux_flow_next();
203  }
204  else {
205  // display next page, count the number of char to fit in the next page
206  G_ux.layout_paging.current++;
207  redisplay(G_ux.stack_count - 1);
208  }
209 }
210 
211 static void ux_layout_paging_prev(ux_layout_paging_redisplay_t redisplay)
212 {
213  if (G_ux.layout_paging.current == 0) {
214  ux_flow_prev();
215  }
216  else {
217  // display previous page, count the number of char to fit in the previous page
218  G_ux.layout_paging.current--;
219  redisplay(G_ux.stack_count - 1);
220  }
221 }
222 
223 STATIC_IF_NOT_INDEXED unsigned int ux_layout_paging_button_callback_common(
224  unsigned int button_mask,
225  unsigned int button_mask_counter,
226  ux_layout_paging_redisplay_t redisplay)
227 {
228  UNUSED(button_mask_counter);
229  switch (button_mask) {
231  ux_layout_paging_prev(redisplay);
232  break;
234  ux_layout_paging_next(redisplay);
235  break;
237  if (G_ux.layout_paging.count == 0
238  || G_ux.layout_paging.count - 1 == G_ux.layout_paging.current) {
240  }
241  break;
242  }
243  return 0;
244 }
245 
246 STATIC_IF_NOT_INDEXED unsigned int ux_layout_paging_button_callback_by_addr(
247  unsigned int button_mask,
248  unsigned int button_mask_counter)
249 {
250  return ux_layout_paging_button_callback_common(
251  button_mask, button_mask_counter, ux_layout_paging_redisplay_by_addr);
252 }
253 
254 void ux_layout_paging_init_common(unsigned int stack_slot,
255  const char *text,
256  ux_layout_paging_redisplay_t redisplay)
257 {
258  bagl_font_id_e font_id;
259 
260  // At this very moment, we don't want to get rid of the format, but keep
261  // the one which has just been set (in case of direction backward or forward).
262  unsigned int backup_format = G_ux.layout_paging.format;
263 
264  // depending flow browsing direction, select the correct page to display
265  switch (ux_flow_direction()) {
267  ux_layout_paging_reset();
268  // ask the paging to start at the last page.
269  // This step must be performed after the 'ux_layout_paging_reset' call,
270  // thus we cannot mutualize the call with the one in the 'forward' case.
271  G_ux.layout_paging.current = -1UL;
272  break;
274  // open the first page
275  ux_layout_paging_reset();
276  break;
278  // shall already be at the first page
279  break;
280  }
281 
282  G_ux.layout_paging.format = backup_format;
283 
284  // store params
285  ux_stack_init(stack_slot);
286 
287  // compute number of chars to display from the params complete string
288  if ((text == NULL) && (G_ux.externalText == NULL)) {
289  text = ""; // empty string to avoid disrupting the ux flow.
290  }
291 
292  // Use the correct font, to be able to compute correctly text width:
293  if (G_ux.layout_paging.format & PAGING_FORMAT_NB) {
294  font_id = BAGL_FONT_OPEN_SANS_EXTRABOLD_11px;
295  }
296  else {
297  font_id = BAGL_FONT_OPEN_SANS_REGULAR_11px;
298  }
299 
300  // count total number of pages
301  G_ux.layout_paging.count
302  = ux_layout_paging_compute(text, -1UL, &G_ux.layout_paging, font_id); // at least one page
303 
304  if (G_ux.layout_paging.count == 0) {
305  ux_layout_paging_reset();
306  }
307 
308  // if (start != end) {
309  // ux_layout_paging_reset();
310  // }
311 
312  // perform displaying the last page as requested (-1UL in prevstep hook does this)
313  if (G_ux.layout_paging.count && G_ux.layout_paging.current > G_ux.layout_paging.count - 1UL) {
314  G_ux.layout_paging.current = G_ux.layout_paging.count - 1;
315  }
316 
317  redisplay(stack_slot);
318 }
319 
320 void ux_layout_paging_init(unsigned int stack_slot)
321 {
322  const ux_layout_paging_params_t *params
323  = (const ux_layout_paging_params_t *) ux_stack_get_step_params(stack_slot);
324  ux_layout_paging_init_common(stack_slot, params->text, ux_layout_paging_redisplay_by_addr);
325 }
326 
327 void ux_layout_xx_paging_init(unsigned int stack_slot, unsigned int format)
328 {
329  G_ux.layout_paging.format = format;
330  ux_layout_paging_init(stack_slot);
331 }
332 
333 void ux_layout_nn_paging_init(unsigned int stack_slot)
334 {
335  ux_layout_xx_paging_init(stack_slot, PAGING_FORMAT_NN);
336 }
337 
338 void ux_layout_nb_paging_init(unsigned int stack_slot)
339 {
340  ux_layout_xx_paging_init(stack_slot, PAGING_FORMAT_NB);
341 }
342 
343 void ux_layout_bn_paging_init(unsigned int stack_slot)
344 {
345  ux_layout_xx_paging_init(stack_slot, PAGING_FORMAT_BN);
346 }
347 
348 void ux_layout_bb_paging_init(unsigned int stack_slot)
349 {
350  ux_layout_xx_paging_init(stack_slot, PAGING_FORMAT_BB);
351 }
352 
353 // function callable externally which reset the paging (to be called before init when willing to
354 // redisplay the first page)
355 void ux_layout_paging_reset(void)
356 {
357  memset(&G_ux.layout_paging, 0, sizeof(G_ux.layout_paging));
358 }
359 
360 #endif // HAVE_BAGL
361 
362 #endif // HAVE_UX_FLOW
const char * get_ux_loc_string(uint32_t index)
#define MIN(x, y)
Definition: nbgl_types.h:79
const char * text
Definition: ux_bagl.h:68
bagl_component_t component
Definition: ux_bagl.h:58
unsigned char element_arrays_count
Definition: ux_bagl.h:279
button_push_callback_t button_push_callback
Definition: ux_bagl.h:292
struct ux_stack_slot_s::@45 element_arrays[UX_STACK_SLOT_ARRAY_COUNT]
bagl_element_callback_t screen_before_element_display_callback
Definition: ux_bagl.h:291
const bagl_element_t * element_array
Definition: ux_bagl.h:283
unsigned char element_array_count
Definition: ux_bagl.h:284
#define STATIC_IF_NOT_INDEXED
Definition: ux_bagl.h:74
#define BUTTON_RIGHT
Definition: ux_bagl.h:145
#define BUTTON_EVT_RELEASED
Definition: ux_bagl.h:148
const bagl_element_t *(* bagl_element_callback_t)(const bagl_element_t *element)
Definition: ux_bagl.h:54
#define G_ux
Definition: ux_bagl.h:345
void ux_stack_display(unsigned int stack_slot)
Definition: ux_stack.c:331
void ux_stack_init(unsigned int stack_slot)
Definition: ux_stack.c:171
unsigned int(* button_push_callback_t)(unsigned int button_mask, unsigned int button_mask_counter)
Definition: ux_bagl.h:142
#define BUTTON_LEFT
Definition: ux_bagl.h:144
@ FLOW_DIRECTION_BACKWARD
@ FLOW_DIRECTION_FORWARD
@ FLOW_DIRECTION_START
ux_flow_direction_t ux_flow_direction(void)
void ux_flow_next(void)
void ux_flow_prev(void)
unsigned int ux_flow_is_first(void)
const void * ux_stack_get_step_params(unsigned int stack_slot)
const void * ux_stack_get_current_step_params(void)
unsigned int ux_flow_is_last(void)
#define STRPIC(x)
void ux_flow_validate(void)
BOLOS_UX_LOC_STRINGS UX_LOC_STRINGS_INDEX