blob: 7dbcac78c2287403fe831c18abeb5aa5fadce9a2 [file] [log] [blame]
Simon Glassd8adbe92023-01-06 08:52:36 -06001// 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
Simon Glassfe4c1e22023-06-01 10:22:43 -06009#define LOG_CATEGORY LOGC_EXPO
10
Simon Glassd8adbe92023-01-06 08:52:36 -060011#include <dm.h>
12#include <expo.h>
Simon Glass137b4422025-05-02 08:46:16 -060013#include <log.h>
Simon Glassd8adbe92023-01-06 08:52:36 -060014#include <malloc.h>
Simon Glass137b4422025-05-02 08:46:16 -060015#include <menu.h>
Simon Glassd8adbe92023-01-06 08:52:36 -060016#include <video.h>
Simon Glass137b4422025-05-02 08:46:16 -060017#include <watchdog.h>
18#include <linux/delay.h>
Simon Glassd8adbe92023-01-06 08:52:36 -060019#include "scene_internal.h"
20
21int 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);
Simon Glass53a0a2f2024-10-14 16:31:57 -060036 exp->next_id = EXPOID_BASE_ID;
Simon Glass683d8832025-05-02 08:46:15 -060037 cli_ch_init(&exp->cch);
Simon Glassd8adbe92023-01-06 08:52:36 -060038
39 *expp = exp;
40
41 return 0;
42}
43
44static void estr_destroy(struct expo_string *estr)
45{
46 free(estr);
47}
48
49void 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
Simon Glass6e9e4152023-06-01 10:22:47 -060064uint resolve_id(struct expo *exp, uint id)
65{
Simon Glass61300722023-06-01 10:23:01 -060066 log_debug("resolve id %d\n", id);
Simon Glass6e9e4152023-06-01 10:22:47 -060067 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
75void expo_set_dynamic_start(struct expo *exp, uint dyn_start)
76{
77 exp->next_id = dyn_start;
78}
79
Simon Glassd8adbe92023-01-06 08:52:36 -060080int 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);
Simon Glassda337752025-05-02 08:46:32 -060089 abuf_init_const(&estr->buf, str, strlen(str) + 1);
Simon Glassd8adbe92023-01-06 08:52:36 -060090 list_add_tail(&estr->sibling, &exp->str_head);
91
92 return estr->id;
93}
94
95const 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)
Simon Glassda337752025-05-02 08:46:32 -0600101 return estr->buf.data;
Simon Glassd8adbe92023-01-06 08:52:36 -0600102 }
103
104 return NULL;
105}
106
Simon Glassc5ae5b12025-05-02 08:46:40 -0600107int 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
Simon Glassd8adbe92023-01-06 08:52:36 -0600128int expo_set_display(struct expo *exp, struct udevice *dev)
129{
Simon Glass67e2af12023-06-01 10:22:34 -0600130 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
Simon Glassd8adbe92023-01-06 08:52:36 -0600138 exp->display = dev;
Simon Glass67e2af12023-06-01 10:22:34 -0600139 exp->cons = cons;
Simon Glassd8adbe92023-01-06 08:52:36 -0600140
141 return 0;
142}
143
Simon Glass7a960052023-06-01 10:22:52 -0600144int 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
Simon Glassb2c40342023-06-01 10:22:37 -0600168void expo_set_text_mode(struct expo *exp, bool text_mode)
Simon Glassd8adbe92023-01-06 08:52:36 -0600169{
170 exp->text_mode = text_mode;
171}
172
173struct 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
185int expo_set_scene_id(struct expo *exp, uint scene_id)
186{
Simon Glassd7e32a82023-06-01 10:22:35 -0600187 struct scene *scn;
188 int ret;
189
190 scn = expo_lookup_scene_id(exp, scene_id);
191 if (!scn)
Simon Glassd8adbe92023-01-06 08:52:36 -0600192 return log_msg_ret("id", -ENOENT);
Simon Glassd7e32a82023-06-01 10:22:35 -0600193 ret = scene_arrange(scn);
194 if (ret)
195 return log_msg_ret("arr", ret);
196
Simon Glassd8adbe92023-01-06 08:52:36 -0600197 exp->scene_id = scene_id;
198
199 return 0;
200}
201
Simon Glass12f57732023-06-01 10:22:58 -0600202int 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
Simon Glassd8adbe92023-01-06 08:52:36 -0600214int 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;
Simon Glass9ac53fb2023-10-01 19:14:41 -0600219 enum colour_idx back;
Simon Glassd8adbe92023-01-06 08:52:36 -0600220 u32 colour;
221 int ret;
222
Simon Glass21320da2025-04-02 06:29:33 +1300223 back = vid_priv->white_on_black ? VID_BLACK : VID_WHITE;
Simon Glass9ac53fb2023-10-01 19:14:41 -0600224 colour = video_index_to_colour(vid_priv, back);
Simon Glassd8adbe92023-01-06 08:52:36 -0600225 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
244int 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);
Simon Glassd7e32a82023-06-01 10:22:35 -0600258
259 /* arrange it to get any changes */
260 ret = scene_arrange(scn);
261 if (ret)
262 return log_msg_ret("arr", ret);
Simon Glassd8adbe92023-01-06 08:52:36 -0600263 }
264
265 return scn ? 0 : -ECHILD;
266}
267
268int 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}
Simon Glassc999e172023-06-01 10:22:53 -0600275
276int expo_apply_theme(struct expo *exp, ofnode node)
277{
278 struct scene *scn;
279 struct expo_theme *theme = &exp->theme;
Simon Glass61c91f02025-05-02 08:46:43 -0600280 bool white_on_black;
Simon Glassc999e172023-06-01 10:22:53 -0600281 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);
Simon Glass86f1ac52023-06-01 10:23:00 -0600287 ofnode_read_u32(node, "menu-inset", &theme->menu_inset);
288 ofnode_read_u32(node, "menuitem-gap-y", &theme->menuitem_gap_y);
Simon Glass377f18e2024-10-14 16:31:55 -0600289 ofnode_read_u32(node, "menu-title-margin-x",
290 &theme->menu_title_margin_x);
Simon Glass61c91f02025-05-02 08:46:43 -0600291 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);
Simon Glassc999e172023-06-01 10:22:53 -0600294
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}
Simon Glasse90acd82023-08-14 16:40:23 -0600303
304int 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}
Simon Glass137b4422025-05-02 08:46:16 -0600318
319int expo_poll(struct expo *exp, struct expo_action *act)
320{
321 int ichar, key, ret;
322
323 ret = expo_render(exp);
324 if (ret)
325 return log_msg_ret("ere", ret);
326
327 ichar = cli_ch_process(&exp->cch, 0);
328 if (!ichar) {
329 while (!ichar && !tstc()) {
330 schedule();
331 mdelay(2);
332 ichar = cli_ch_process(&exp->cch, -ETIMEDOUT);
333 }
334 if (!ichar) {
335 ichar = getchar();
336 ichar = cli_ch_process(&exp->cch, ichar);
337 }
338 }
339
340 key = 0;
341 if (ichar) {
342 key = bootmenu_conv_key(ichar);
343 if (key == BKEY_NONE || key >= BKEY_FIRST_EXTRA)
344 key = ichar;
345 }
346 if (!key)
347 return -EAGAIN;
348
349 ret = expo_send_key(exp, key);
350 if (ret)
351 return log_msg_ret("epk", ret);
352 ret = expo_action_get(exp, act);
353 if (ret)
354 return log_msg_ret("eag", ret);
355
356 return 0;
357}