1 // SPDX-License-Identifier: GPL-2.0+
2 /*
3 * Implementation of a expo, a collection of scenes providing menu options
4 *
5 * Copyright 2022 Google LLC
6 * Written by Simon Glass <sjg@chromium.org>
7 */
8
9 #define LOG_CATEGORY LOGC_EXPO
10
11 #include <dm.h>
12 #include <expo.h>
13 #include <log.h>
14 #include <malloc.h>
15 #include <menu.h>
16 #include <video.h>
17 #include <watchdog.h>
18 #include <linux/delay.h>
19 #include "scene_internal.h"
20
expo_new(const char * name,void * priv,struct expo ** expp)21 int expo_new(const char *name, void *priv, struct expo **expp)
22 {
23 struct expo *exp;
24
25 exp = calloc(1, sizeof(struct expo));
26 if (!exp)
27 return log_msg_ret("expo", -ENOMEM);
28 exp->name = strdup(name);
29 if (!exp->name) {
30 free(exp);
31 return log_msg_ret("name", -ENOMEM);
32 }
33 exp->priv = priv;
34 INIT_LIST_HEAD(&exp->scene_head);
35 INIT_LIST_HEAD(&exp->str_head);
36 exp->next_id = EXPOID_BASE_ID;
37 cli_ch_init(&exp->cch);
38
39 *expp = exp;
40
41 return 0;
42 }
43
estr_destroy(struct expo_string * estr)44 static void estr_destroy(struct expo_string *estr)
45 {
46 free(estr);
47 }
48
expo_destroy(struct expo * exp)49 void expo_destroy(struct expo *exp)
50 {
51 struct scene *scn, *next;
52 struct expo_string *estr, *enext;
53
54 list_for_each_entry_safe(scn, next, &exp->scene_head, sibling)
55 scene_destroy(scn);
56
57 list_for_each_entry_safe(estr, enext, &exp->str_head, sibling)
58 estr_destroy(estr);
59
60 free(exp->name);
61 free(exp);
62 }
63
resolve_id(struct expo * exp,uint id)64 uint resolve_id(struct expo *exp, uint id)
65 {
66 log_debug("resolve id %d\n", id);
67 if (!id)
68 id = exp->next_id++;
69 else if (id >= exp->next_id)
70 exp->next_id = id + 1;
71
72 return id;
73 }
74
expo_set_dynamic_start(struct expo * exp,uint dyn_start)75 void expo_set_dynamic_start(struct expo *exp, uint dyn_start)
76 {
77 exp->next_id = dyn_start;
78 }
79
expo_str(struct expo * exp,const char * name,uint id,const char * str)80 int expo_str(struct expo *exp, const char *name, uint id, const char *str)
81 {
82 struct expo_string *estr;
83
84 estr = calloc(1, sizeof(struct expo_string));
85 if (!estr)
86 return log_msg_ret("obj", -ENOMEM);
87
88 estr->id = resolve_id(exp, id);
89 abuf_init_const(&estr->buf, str, strlen(str) + 1);
90 list_add_tail(&estr->sibling, &exp->str_head);
91
92 return estr->id;
93 }
94
expo_get_str(struct expo * exp,uint id)95 const char *expo_get_str(struct expo *exp, uint id)
96 {
97 struct expo_string *estr;
98
99 list_for_each_entry(estr, &exp->str_head, sibling) {
100 if (estr->id == id)
101 return estr->buf.data;
102 }
103
104 return NULL;
105 }
106
expo_edit_str(struct expo * exp,uint id,struct abuf * orig,struct abuf ** copyp)107 int expo_edit_str(struct expo *exp, uint id, struct abuf *orig,
108 struct abuf **copyp)
109 {
110 struct expo_string *estr;
111 struct abuf old;
112
113 list_for_each_entry(estr, &exp->str_head, sibling) {
114 if (estr->id == id) {
115 old = estr->buf;
116 if (!abuf_copy(&old, &estr->buf))
117 return -ENOMEM;
118 *copyp = &estr->buf;
119 if (orig)
120 *orig = old;
121 return 0;
122 }
123 }
124
125 return -ENOENT;
126 }
127
expo_set_display(struct expo * exp,struct udevice * dev)128 int expo_set_display(struct expo *exp, struct udevice *dev)
129 {
130 struct udevice *cons;
131 int ret;
132
133 ret = device_find_first_child_by_uclass(dev, UCLASS_VIDEO_CONSOLE,
134 &cons);
135 if (ret)
136 return log_msg_ret("con", ret);
137
138 exp->display = dev;
139 exp->cons = cons;
140
141 return 0;
142 }
143
expo_calc_dims(struct expo * exp)144 int expo_calc_dims(struct expo *exp)
145 {
146 struct scene *scn;
147 int ret;
148
149 if (!exp->cons)
150 return log_msg_ret("dim", -ENOTSUPP);
151
152 list_for_each_entry(scn, &exp->scene_head, sibling) {
153 /*
154 * Do the menus last so that all the menus' text objects
155 * are dimensioned
156 */
157 ret = scene_calc_dims(scn, false);
158 if (ret)
159 return log_msg_ret("scn", ret);
160 ret = scene_calc_dims(scn, true);
161 if (ret)
162 return log_msg_ret("scn", ret);
163 }
164
165 return 0;
166 }
167
expo_set_text_mode(struct expo * exp,bool text_mode)168 void expo_set_text_mode(struct expo *exp, bool text_mode)
169 {
170 exp->text_mode = text_mode;
171 }
172
expo_lookup_scene_id(struct expo * exp,uint scene_id)173 struct scene *expo_lookup_scene_id(struct expo *exp, uint scene_id)
174 {
175 struct scene *scn;
176
177 list_for_each_entry(scn, &exp->scene_head, sibling) {
178 if (scn->id == scene_id)
179 return scn;
180 }
181
182 return NULL;
183 }
184
expo_set_scene_id(struct expo * exp,uint scene_id)185 int expo_set_scene_id(struct expo *exp, uint scene_id)
186 {
187 struct scene *scn;
188 int ret;
189
190 scn = expo_lookup_scene_id(exp, scene_id);
191 if (!scn)
192 return log_msg_ret("id", -ENOENT);
193 ret = scene_arrange(scn);
194 if (ret)
195 return log_msg_ret("arr", ret);
196
197 exp->scene_id = scene_id;
198
199 return 0;
200 }
201
expo_first_scene_id(struct expo * exp)202 int expo_first_scene_id(struct expo *exp)
203 {
204 struct scene *scn;
205
206 if (list_empty(&exp->scene_head))
207 return -ENOENT;
208
209 scn = list_first_entry(&exp->scene_head, struct scene, sibling);
210
211 return scn->id;
212 }
213
expo_render(struct expo * exp)214 int expo_render(struct expo *exp)
215 {
216 struct udevice *dev = exp->display;
217 struct video_priv *vid_priv = dev_get_uclass_priv(dev);
218 struct scene *scn = NULL;
219 enum colour_idx back;
220 u32 colour;
221 int ret;
222
223 back = vid_priv->white_on_black ? VID_BLACK : VID_WHITE;
224 colour = video_index_to_colour(vid_priv, back);
225 ret = video_fill(dev, colour);
226 if (ret)
227 return log_msg_ret("fill", ret);
228
229 if (exp->scene_id) {
230 scn = expo_lookup_scene_id(exp, exp->scene_id);
231 if (!scn)
232 return log_msg_ret("scn", -ENOENT);
233
234 ret = scene_render(scn);
235 if (ret)
236 return log_msg_ret("ren", ret);
237 }
238
239 video_sync(dev, true);
240
241 return scn ? 0 : -ECHILD;
242 }
243
expo_send_key(struct expo * exp,int key)244 int expo_send_key(struct expo *exp, int key)
245 {
246 struct scene *scn = NULL;
247
248 if (exp->scene_id) {
249 int ret;
250
251 scn = expo_lookup_scene_id(exp, exp->scene_id);
252 if (!scn)
253 return log_msg_ret("scn", -ENOENT);
254
255 ret = scene_send_key(scn, key, &exp->action);
256 if (ret)
257 return log_msg_ret("key", ret);
258
259 /* arrange it to get any changes */
260 ret = scene_arrange(scn);
261 if (ret)
262 return log_msg_ret("arr", ret);
263 }
264
265 return scn ? 0 : -ECHILD;
266 }
267
expo_action_get(struct expo * exp,struct expo_action * act)268 int expo_action_get(struct expo *exp, struct expo_action *act)
269 {
270 *act = exp->action;
271 exp->action.type = EXPOACT_NONE;
272
273 return act->type == EXPOACT_NONE ? -EAGAIN : 0;
274 }
275
expo_apply_theme(struct expo * exp,ofnode node)276 int expo_apply_theme(struct expo *exp, ofnode node)
277 {
278 struct scene *scn;
279 struct expo_theme *theme = &exp->theme;
280 bool white_on_black;
281 int ret;
282
283 log_debug("Applying theme %s\n", ofnode_get_name(node));
284
285 memset(theme, '\0', sizeof(struct expo_theme));
286 ofnode_read_u32(node, "font-size", &theme->font_size);
287 ofnode_read_u32(node, "menu-inset", &theme->menu_inset);
288 ofnode_read_u32(node, "menuitem-gap-y", &theme->menuitem_gap_y);
289 ofnode_read_u32(node, "menu-title-margin-x",
290 &theme->menu_title_margin_x);
291 white_on_black = ofnode_read_bool(node, "white-on-black");
292 if (exp->display)
293 video_set_white_on_black(exp->display, white_on_black);
294
295 list_for_each_entry(scn, &exp->scene_head, sibling) {
296 ret = scene_apply_theme(scn, theme);
297 if (ret)
298 return log_msg_ret("app", ret);
299 }
300
301 return 0;
302 }
303
expo_iter_scene_objs(struct expo * exp,expo_scene_obj_iterator iter,void * priv)304 int expo_iter_scene_objs(struct expo *exp, expo_scene_obj_iterator iter,
305 void *priv)
306 {
307 struct scene *scn;
308 int ret;
309
310 list_for_each_entry(scn, &exp->scene_head, sibling) {
311 ret = scene_iter_objs(scn, iter, priv);
312 if (ret)
313 return log_msg_ret("wr", ret);
314 }
315
316 return 0;
317 }
318
expo_poll(struct expo * exp,struct expo_action * act)319 int expo_poll(struct expo *exp, struct expo_action *act)
320 {
321 int ichar, key, ret;
322
323 ichar = cli_ch_process(&exp->cch, 0);
324 if (!ichar) {
325 int i;
326
327 for (i = 0; i < 10 && !ichar && !tstc(); i++) {
328 schedule();
329 mdelay(2);
330 ichar = cli_ch_process(&exp->cch, -ETIMEDOUT);
331 }
332 while (!ichar && tstc()) {
333 ichar = getchar();
334 ichar = cli_ch_process(&exp->cch, ichar);
335 }
336 }
337
338 key = 0;
339 if (ichar) {
340 key = bootmenu_conv_key(ichar);
341 if (key == BKEY_NONE || key >= BKEY_FIRST_EXTRA)
342 key = ichar;
343 }
344 if (!key)
345 return -EAGAIN;
346
347 ret = expo_send_key(exp, key);
348 if (ret)
349 return log_msg_ret("epk", ret);
350 ret = expo_action_get(exp, act);
351 if (ret)
352 return log_msg_ret("eag", ret);
353
354 return 0;
355 }
356