1 // SPDX-License-Identifier: GPL-2.0+
2 /*
3  * Implementation of a menu in a scene
4  *
5  * Copyright 2022 Google LLC
6  * Written by Simon Glass <sjg@chromium.org>
7  */
8 
9 #define LOG_CATEGORY	LOGC_BOOT
10 
11 #include <common.h>
12 #include <dm.h>
13 #include <expo.h>
14 #include <malloc.h>
15 #include <mapmem.h>
16 #include <menu.h>
17 #include <video.h>
18 #include <video_console.h>
19 #include <linux/input.h>
20 #include "scene_internal.h"
21 
scene_menuitem_destroy(struct scene_menitem * item)22 static void scene_menuitem_destroy(struct scene_menitem *item)
23 {
24 	free(item->name);
25 	free(item);
26 }
27 
scene_menu_destroy(struct scene_obj_menu * menu)28 void scene_menu_destroy(struct scene_obj_menu *menu)
29 {
30 	struct scene_menitem *item, *next;
31 
32 	list_for_each_entry_safe(item, next, &menu->item_head, sibling)
33 		scene_menuitem_destroy(item);
34 }
35 
36 /**
37  * menu_point_to_item() - Point to a particular menu item
38  *
39  * Sets the currently pointed-to / highlighted menu item
40  */
menu_point_to_item(struct scene_obj_menu * menu,uint item_id)41 static void menu_point_to_item(struct scene_obj_menu *menu, uint item_id)
42 {
43 	menu->cur_item_id = item_id;
44 }
45 
scene_menu_arrange(struct scene * scn,struct scene_obj_menu * menu)46 int scene_menu_arrange(struct scene *scn, struct scene_obj_menu *menu)
47 {
48 	struct scene_menitem *item;
49 	int y, cur_y;
50 	int ret;
51 
52 	y = menu->obj.y;
53 	if (menu->title_id) {
54 		ret = scene_obj_set_pos(scn, menu->title_id, menu->obj.x, y);
55 		if (ret < 0)
56 			return log_msg_ret("tit", ret);
57 
58 		ret = scene_obj_get_hw(scn, menu->title_id, NULL);
59 		if (ret < 0)
60 			return log_msg_ret("hei", ret);
61 
62 		y += ret * 2;
63 	}
64 
65 	/*
66 	 * Currently everything is hard-coded to particular columns so this
67 	 * won't work on small displays and looks strange if the font size is
68 	 * small. This can be updated once text measuring is supported in
69 	 * vidconsole
70 	 */
71 	cur_y = -1;
72 	list_for_each_entry(item, &menu->item_head, sibling) {
73 		int height;
74 
75 		ret = scene_obj_get_hw(scn, item->desc_id, NULL);
76 		if (ret < 0)
77 			return log_msg_ret("get", ret);
78 		height = ret;
79 
80 		if (item->flags & SCENEMIF_GAP_BEFORE)
81 			y += height;
82 
83 		/* select an item if not done already */
84 		if (!menu->cur_item_id)
85 			menu_point_to_item(menu, item->id);
86 
87 		/*
88 		 * Put the label on the left, then leave a space for the
89 		 * pointer, then the key and the description
90 		 */
91 		if (item->label_id) {
92 			ret = scene_obj_set_pos(scn, item->label_id, menu->obj.x,
93 						y);
94 			if (ret < 0)
95 				return log_msg_ret("nam", ret);
96 		}
97 
98 		ret = scene_obj_set_pos(scn, item->key_id, menu->obj.x + 230,
99 					y);
100 		if (ret < 0)
101 			return log_msg_ret("key", ret);
102 
103 		ret = scene_obj_set_pos(scn, item->desc_id, menu->obj.x + 280,
104 					y);
105 		if (ret < 0)
106 			return log_msg_ret("des", ret);
107 
108 		if (menu->cur_item_id == item->id)
109 			cur_y = y;
110 
111 		if (item->preview_id) {
112 			bool hide;
113 
114 			/*
115 			 * put all previews on top of each other, on the right
116 			 * size of the display
117 			 */
118 			ret = scene_obj_set_pos(scn, item->preview_id, -4, y);
119 			if (ret < 0)
120 				return log_msg_ret("prev", ret);
121 
122 			hide = menu->cur_item_id != item->id;
123 			ret = scene_obj_set_hide(scn, item->preview_id, hide);
124 			if (ret < 0)
125 				return log_msg_ret("hid", ret);
126 		}
127 
128 		y += height;
129 	}
130 
131 	if (menu->pointer_id && cur_y != -1) {
132 		/*
133 		 * put the pointer to the right of and level with the item it
134 		 * points to
135 		 */
136 		ret = scene_obj_set_pos(scn, menu->pointer_id,
137 					menu->obj.x + 200, cur_y);
138 		if (ret < 0)
139 			return log_msg_ret("ptr", ret);
140 	}
141 
142 	return 0;
143 }
144 
scene_menu(struct scene * scn,const char * name,uint id,struct scene_obj_menu ** menup)145 int scene_menu(struct scene *scn, const char *name, uint id,
146 	       struct scene_obj_menu **menup)
147 {
148 	struct scene_obj_menu *menu;
149 	int ret;
150 
151 	ret = scene_obj_add(scn, name, id, SCENEOBJT_MENU,
152 			    sizeof(struct scene_obj_menu),
153 			    (struct scene_obj **)&menu);
154 	if (ret < 0)
155 		return log_msg_ret("obj", -ENOMEM);
156 
157 	if (menup)
158 		*menup = menu;
159 	INIT_LIST_HEAD(&menu->item_head);
160 
161 	ret = scene_menu_arrange(scn, menu);
162 	if (ret)
163 		return log_msg_ret("pos", ret);
164 
165 	return menu->obj.id;
166 }
167 
scene_menu_find_key(struct scene * scn,struct scene_obj_menu * menu,int key)168 static struct scene_menitem *scene_menu_find_key(struct scene *scn,
169 						  struct scene_obj_menu *menu,
170 						  int key)
171 {
172 	struct scene_menitem *item;
173 
174 	list_for_each_entry(item, &menu->item_head, sibling) {
175 		if (item->key_id) {
176 			struct scene_obj_txt *txt;
177 			const char *str;
178 
179 			txt = scene_obj_find(scn, item->key_id, SCENEOBJT_TEXT);
180 			if (txt) {
181 				str = expo_get_str(scn->expo, txt->str_id);
182 				if (str && *str == key)
183 					return item;
184 			}
185 		}
186 	}
187 
188 	return NULL;
189 }
190 
scene_menu_send_key(struct scene * scn,struct scene_obj_menu * menu,int key,struct expo_action * event)191 int scene_menu_send_key(struct scene *scn, struct scene_obj_menu *menu, int key,
192 			struct expo_action *event)
193 {
194 	struct scene_menitem *item, *cur, *key_item;
195 
196 	cur = NULL;
197 	key_item = NULL;
198 
199 	if (!list_empty(&menu->item_head)) {
200 		list_for_each_entry(item, &menu->item_head, sibling) {
201 			/* select an item if not done already */
202 			if (menu->cur_item_id == item->id) {
203 				cur = item;
204 				break;
205 			}
206 		}
207 	}
208 
209 	if (!cur)
210 		return -ENOTTY;
211 
212 	switch (key) {
213 	case BKEY_UP:
214 		if (item != list_first_entry(&menu->item_head,
215 					     struct scene_menitem, sibling)) {
216 			item = list_entry(item->sibling.prev,
217 					  struct scene_menitem, sibling);
218 			event->type = EXPOACT_POINT;
219 			event->select.id = item->id;
220 			log_debug("up to item %d\n", event->select.id);
221 		}
222 		break;
223 	case BKEY_DOWN:
224 		if (!list_is_last(&item->sibling, &menu->item_head)) {
225 			item = list_entry(item->sibling.next,
226 					  struct scene_menitem, sibling);
227 			event->type = EXPOACT_POINT;
228 			event->select.id = item->id;
229 			log_debug("down to item %d\n", event->select.id);
230 		}
231 		break;
232 	case BKEY_SELECT:
233 		event->type = EXPOACT_SELECT;
234 		event->select.id = item->id;
235 		log_debug("select item %d\n", event->select.id);
236 		break;
237 	case BKEY_QUIT:
238 		event->type = EXPOACT_QUIT;
239 		log_debug("quit\n");
240 		break;
241 	case '0'...'9':
242 		key_item = scene_menu_find_key(scn, menu, key);
243 		if (key_item) {
244 			event->type = EXPOACT_SELECT;
245 			event->select.id = key_item->id;
246 		}
247 		break;
248 	}
249 
250 	menu_point_to_item(menu, item->id);
251 
252 	return 0;
253 }
254 
scene_menuitem(struct scene * scn,uint menu_id,const char * name,uint id,uint key_id,uint label_id,uint desc_id,uint preview_id,uint flags,struct scene_menitem ** itemp)255 int scene_menuitem(struct scene *scn, uint menu_id, const char *name, uint id,
256 		   uint key_id, uint label_id, uint desc_id, uint preview_id,
257 		   uint flags, struct scene_menitem **itemp)
258 {
259 	struct scene_obj_menu *menu;
260 	struct scene_menitem *item;
261 	int ret;
262 
263 	menu = scene_obj_find(scn, menu_id, SCENEOBJT_MENU);
264 	if (!menu)
265 		return log_msg_ret("find", -ENOENT);
266 
267 	/* Check that the text ID is valid */
268 	if (!scene_obj_find(scn, desc_id, SCENEOBJT_TEXT))
269 		return log_msg_ret("txt", -EINVAL);
270 
271 	item = calloc(1, sizeof(struct scene_obj_menu));
272 	if (!item)
273 		return log_msg_ret("item", -ENOMEM);
274 	item->name = strdup(name);
275 	if (!item->name) {
276 		free(item);
277 		return log_msg_ret("name", -ENOMEM);
278 	}
279 
280 	item->id = resolve_id(scn->expo, id);
281 	item->key_id = key_id;
282 	item->label_id = label_id;
283 	item->desc_id = desc_id;
284 	item->preview_id = preview_id;
285 	item->flags = flags;
286 	list_add_tail(&item->sibling, &menu->item_head);
287 
288 	ret = scene_menu_arrange(scn, menu);
289 	if (ret)
290 		return log_msg_ret("pos", ret);
291 
292 	if (itemp)
293 		*itemp = item;
294 
295 	return item->id;
296 }
297 
scene_menu_set_title(struct scene * scn,uint id,uint title_id)298 int scene_menu_set_title(struct scene *scn, uint id, uint title_id)
299 {
300 	struct scene_obj_menu *menu;
301 	struct scene_obj_txt *txt;
302 
303 	menu = scene_obj_find(scn, id, SCENEOBJT_MENU);
304 	if (!menu)
305 		return log_msg_ret("menu", -ENOENT);
306 
307 	/* Check that the ID is valid */
308 	if (title_id) {
309 		txt = scene_obj_find(scn, title_id, SCENEOBJT_TEXT);
310 		if (!txt)
311 			return log_msg_ret("txt", -EINVAL);
312 	}
313 
314 	menu->title_id = title_id;
315 
316 	return 0;
317 }
318 
scene_menu_set_pointer(struct scene * scn,uint id,uint pointer_id)319 int scene_menu_set_pointer(struct scene *scn, uint id, uint pointer_id)
320 {
321 	struct scene_obj_menu *menu;
322 	struct scene_obj *obj;
323 
324 	menu = scene_obj_find(scn, id, SCENEOBJT_MENU);
325 	if (!menu)
326 		return log_msg_ret("menu", -ENOENT);
327 
328 	/* Check that the ID is valid */
329 	if (pointer_id) {
330 		obj = scene_obj_find(scn, pointer_id, SCENEOBJT_NONE);
331 		if (!obj)
332 			return log_msg_ret("obj", -EINVAL);
333 	}
334 
335 	menu->pointer_id = pointer_id;
336 
337 	return 0;
338 }
339 
scene_menu_display(struct scene_obj_menu * menu)340 int scene_menu_display(struct scene_obj_menu *menu)
341 {
342 	struct scene *scn = menu->obj.scene;
343 	struct scene_obj_txt *pointer;
344 	struct expo *exp = scn->expo;
345 	struct scene_menitem *item;
346 	const char *pstr;
347 
348 	printf("U-Boot    :    Boot Menu\n\n");
349 	if (menu->title_id) {
350 		struct scene_obj_txt *txt;
351 		const char *str;
352 
353 		txt = scene_obj_find(scn, menu->title_id, SCENEOBJT_TEXT);
354 		if (!txt)
355 			return log_msg_ret("txt", -EINVAL);
356 
357 		str = expo_get_str(exp, txt->str_id);
358 		printf("%s\n\n", str);
359 	}
360 
361 	if (list_empty(&menu->item_head))
362 		return 0;
363 
364 	pointer = scene_obj_find(scn, menu->pointer_id, SCENEOBJT_TEXT);
365 	pstr = expo_get_str(scn->expo, pointer->str_id);
366 
367 	list_for_each_entry(item, &menu->item_head, sibling) {
368 		struct scene_obj_txt *key = NULL, *label = NULL;
369 		struct scene_obj_txt *desc = NULL;
370 		const char *kstr = NULL, *lstr = NULL, *dstr = NULL;
371 
372 		key = scene_obj_find(scn, item->key_id, SCENEOBJT_TEXT);
373 		if (key)
374 			kstr = expo_get_str(exp, key->str_id);
375 
376 		label = scene_obj_find(scn, item->label_id, SCENEOBJT_TEXT);
377 		if (label)
378 			lstr = expo_get_str(exp, label->str_id);
379 
380 		desc = scene_obj_find(scn, item->desc_id, SCENEOBJT_TEXT);
381 		if (desc)
382 			dstr = expo_get_str(exp, desc->str_id);
383 
384 		printf("%3s  %3s  %-10s  %s\n",
385 		       pointer && menu->cur_item_id == item->id ? pstr : "",
386 		       kstr, lstr, dstr);
387 	}
388 
389 	return -ENOTSUPP;
390 }
391