// SPDX-License-Identifier: GPL-2.0+ /* * Implementation of a expo, a collection of scenes providing menu options * * Copyright 2022 Google LLC * Written by Simon Glass */ #define LOG_CATEGORY LOGC_EXPO #include #include #include #include #include #include #include #include #include #include #include #include #include "scene_internal.h" int expo_new(const char *name, void *priv, struct expo **expp) { struct expo *exp; int ret; exp = calloc(1, sizeof(struct expo)); if (!exp) return log_msg_ret("expo", -ENOMEM); exp->name = strdup(name); if (!exp->name) { free(exp); return log_msg_ret("name", -ENOMEM); } ret = expo_test_init(exp); if (ret) { free(exp->name); free(exp); return log_msg_ret("tst", ret); } exp->priv = priv; INIT_LIST_HEAD(&exp->scene_head); INIT_LIST_HEAD(&exp->str_head); exp->next_id = EXPOID_BASE_ID; cli_ch_init(&exp->cch); exp->last_key_ms = get_timer(0); *expp = exp; return 0; } static void estr_destroy(struct expo_string *estr) { free(estr); } void expo_destroy(struct expo *exp) { struct scene *scn, *next; struct expo_string *estr, *enext; expo_test_uninit(exp); list_for_each_entry_safe(scn, next, &exp->scene_head, sibling) scene_destroy(scn); list_for_each_entry_safe(estr, enext, &exp->str_head, sibling) estr_destroy(estr); free(exp->name); free(exp); } uint resolve_id(struct expo *exp, uint id) { log_debug("resolve id %d\n", id); if (!id) id = exp->next_id++; else if (id >= exp->next_id) exp->next_id = id + 1; return id; } void expo_set_dynamic_start(struct expo *exp, uint dyn_start) { exp->next_id = dyn_start; } int expo_str(struct expo *exp, const char *name, uint id, const char *str) { struct expo_string *estr; estr = calloc(1, sizeof(struct expo_string)); if (!estr) return log_msg_ret("obj", -ENOMEM); estr->id = resolve_id(exp, id); abuf_init_const(&estr->buf, str, strlen(str) + 1); list_add_tail(&estr->sibling, &exp->str_head); return estr->id; } const char *expo_get_str(struct expo *exp, uint id) { struct expo_string *estr; list_for_each_entry(estr, &exp->str_head, sibling) { if (estr->id == id) return estr->buf.data; } return NULL; } int expo_edit_str(struct expo *exp, uint id, struct abuf *orig, struct abuf **copyp) { struct expo_string *estr; struct abuf old; list_for_each_entry(estr, &exp->str_head, sibling) { if (estr->id == id) { old = estr->buf; if (!abuf_copy(&old, &estr->buf)) return -ENOMEM; *copyp = &estr->buf; if (orig) *orig = old; return 0; } } return -ENOENT; } int expo_set_display(struct expo *exp, struct udevice *dev) { struct udevice *cons; int ret; ret = device_find_first_child_by_uclass(dev, UCLASS_VIDEO_CONSOLE, &cons); if (ret) return log_msg_ret("con", ret); exp->display = dev; exp->cons = cons; if (IS_ENABLED(CONFIG_MOUSE) && exp->mouse_enabled) { /* * Tell the mouse driver about the video device for coordinate * scaling */ ret = mouse_set_video(exp->mouse, exp->display); if (ret) return log_msg_ret("msv", ret); mouse_get_pos(exp->mouse, &exp->mouse_pos); } return 0; } int expo_calc_dims(struct expo *exp) { struct scene *scn; int ret; if (!exp->cons) return log_msg_ret("dim", -ENOTSUPP); list_for_each_entry(scn, &exp->scene_head, sibling) { ret = scene_calc_dims(scn); if (ret) return log_msg_ret("scn", ret); } return 0; } void expo_set_text_mode(struct expo *exp, bool text_mode) { exp->text_mode = text_mode; } int expo_set_mouse_enable(struct expo *exp, bool enable) { int ret; if (!IS_ENABLED(CONFIG_MOUSE) || !enable) { exp->mouse_enabled = false; return 0; } ret = uclass_first_device_err(UCLASS_MOUSE, &exp->mouse); if (ret) return log_msg_ret("sme", ret); /* Get mouse pointer image and dimensions */ exp->mouse_ptr = video_image_getptr(riscos_arrow); if (exp->mouse_ptr) { ulong width, height; uint bpix; video_bmp_get_info(exp->mouse_ptr, &width, &height, &bpix); exp->mouse_size.w = width; exp->mouse_size.h = height; } exp->mouse_enabled = true; return 0; } struct scene *expo_lookup_scene_id(struct expo *exp, uint scene_id) { struct scene *scn; list_for_each_entry(scn, &exp->scene_head, sibling) { if (scn->id == scene_id) return scn; } return NULL; } int expo_set_scene_id(struct expo *exp, uint scene_id) { struct scene *scn; scn = expo_lookup_scene_id(exp, scene_id); if (!scn) return log_msg_ret("id", -ENOENT); exp->scene_id = scene_id; return 0; } int expo_first_scene_id(struct expo *exp) { struct scene *scn; if (list_empty(&exp->scene_head)) return -ENOENT; scn = list_first_entry(&exp->scene_head, struct scene, sibling); return scn->id; } int expo_arrange(struct expo *exp) { struct scene *scn; int ret; if (!exp->scene_id) return 0; scn = expo_lookup_scene_id(exp, exp->scene_id); if (!scn) return log_msg_ret("scn", -ENOENT); ret = scene_arrange(scn); if (ret) return log_msg_ret("ear", ret); return 0; } static int update_mouse_position(struct expo *exp) { struct mouse_event event; int ret; if (!IS_ENABLED(CONFIG_MOUSE) || !exp->mouse_enabled) return 0; /* Process all available mouse events to get latest position */ while (1) { ret = mouse_get_event(exp->mouse, &event); if (ret) break; /* No more events available */ if (event.type == MOUSE_EV_MOTION) { exp->mouse_pos.x = event.motion.x; exp->mouse_pos.y = event.motion.y; } else if (event.type == MOUSE_EV_BUTTON) { exp->mouse_pos.x = event.button.x; exp->mouse_pos.y = event.button.y; } } return 0; } /** * render_mouse_pointer() - Render the mouse pointer if enabled and visible * * @exp: Expo containing mouse state * Return: 0 if OK, -ve on error */ static int render_mouse_pointer(struct expo *exp) { struct udevice *dev = exp->display; int ret; if (!IS_ENABLED(CONFIG_MOUSE) || !exp->mouse_enabled || !exp->mouse_ptr) return 0; /* Use white (0xffffff) as transparent color */ ret = video_bmp_displaya(dev, map_to_sysmem(exp->mouse_ptr), exp->mouse_pos.x, exp->mouse_pos.y, false, true, 0xffffff); if (ret) log_debug("Failed to display mouse pointer: %d\n", ret); return 0; } static int expo_render_(struct expo *exp, bool dirty_only) { struct udevice *dev = exp->display; struct video_priv *vid_priv = dev_get_uclass_priv(dev); struct scene *scn = NULL; enum colour_idx back; u32 colour; int ret; expo_test_mark(exp); expo_test_update(exp); back = vid_priv->white_on_black ? VID_BLACK : VID_WHITE; colour = video_index_to_colour(vid_priv, back); ret = video_fill(dev, colour); if (ret) return log_msg_ret("fill", ret); if (exp->scene_id) { scn = expo_lookup_scene_id(exp, exp->scene_id); if (!scn) return log_msg_ret("scn", -ENOENT); ret = scene_render(scn, dirty_only); if (ret) return log_msg_ret("ren", ret); } /* Render mouse pointer if mouse is enabled */ ret = render_mouse_pointer(exp); if (ret) return log_msg_ret("mou", ret); /* Render test-mode info if enabled */ ret = expo_test_render(exp); if (ret) return log_msg_ret("tst", ret); video_manual_sync(dev, VIDSYNC_COPY | VIDSYNC_FLUSH); expo_test_sync(exp); return scn ? 0 : -ECHILD; } int expo_render(struct expo *exp) { return expo_render_(exp, false); } int expo_render_dirty(struct expo *exp) { return expo_render_(exp, true); } int expo_send_key(struct expo *exp, int key) { struct scene *scn = NULL; if (exp->scene_id) { int ret; scn = expo_lookup_scene_id(exp, exp->scene_id); if (!scn) return log_msg_ret("scn", -ENOENT); ret = scene_send_key(scn, key, &exp->action); if (ret) return log_msg_ret("key", ret); /* arrange it to get any changes */ ret = scene_arrange(scn); if (ret) return log_msg_ret("arr", ret); } return scn ? 0 : -ECHILD; } int expo_send_click(struct expo *exp, int x, int y) { struct scene *scn = NULL; if (exp->scene_id) { int ret; scn = expo_lookup_scene_id(exp, exp->scene_id); if (!scn) return log_msg_ret("scn", -ENOENT); ret = scene_send_click(scn, x, y, &exp->action); if (ret) return log_msg_ret("click", ret); /* arrange it to get any changes */ ret = scene_arrange(scn); if (ret) return log_msg_ret("arr", ret); } return scn ? 0 : -ECHILD; } int expo_action_get(struct expo *exp, struct expo_action *act) { *act = exp->action; exp->action.type = EXPOACT_NONE; return act->type == EXPOACT_NONE ? -EAGAIN : 0; } int expo_apply_theme(struct expo *exp, bool do_objs) { struct expo_theme *theme = &exp->theme; struct scene *scn; if (exp->display) video_set_white_on_black(exp->display, theme->white_on_black); if (do_objs) { list_for_each_entry(scn, &exp->scene_head, sibling) { int ret; ret = scene_apply_theme(scn, theme); if (ret) return log_msg_ret("asn", ret); } } return 0; } int expo_setup_theme(struct expo *exp, ofnode node) { struct expo_theme *theme = &exp->theme; int ret; log_debug("Applying theme %s\n", ofnode_get_name(node)); memset(theme, '\0', sizeof(struct expo_theme)); ofnode_read_u32(node, "font-size", &theme->font_size); ofnode_read_u32(node, "menu-inset", &theme->menu_inset); ofnode_read_u32(node, "menuitem-gap-y", &theme->menuitem_gap_y); ofnode_read_u32(node, "menu-title-margin-x", &theme->menu_title_margin_x); ofnode_read_u32(node, "textline-label-margin-x", &theme->textline_label_margin_x); theme->white_on_black = ofnode_read_bool(node, "white-on-black"); ret = expo_apply_theme(exp, true); if (ret) return log_msg_ret("asn", ret); return 0; } int expo_iter_scene_objs(struct expo *exp, expo_scene_obj_iterator iter, void *priv) { struct scene *scn; int ret; list_for_each_entry(scn, &exp->scene_head, sibling) { ret = scene_iter_objs(scn, iter, priv); if (ret) return log_msg_ret("wr", ret); } return 0; } static int poll_keys(struct expo *exp) { int ch = 0, ichar, key; ichar = cli_ch_process(&exp->cch, 0); if (!ichar) { /* Check once for available input */ if (tstc()) { ch = getchar(); ichar = cli_ch_process(&exp->cch, ch); } if (!ch && get_timer(exp->last_key_ms) >= 10) ichar = cli_ch_process(&exp->cch, -ETIMEDOUT); } key = 0; if (ichar) { exp->last_key_ms = get_timer(0); key = bootmenu_conv_key(ichar); if (key == BKEY_NONE || key >= BKEY_FIRST_EXTRA) key = ichar; } return key ? key : -EAGAIN; } static int poll_mouse(struct expo *exp, struct vid_pos *pos) { int ret; if (!exp->mouse_enabled) return -EAGAIN; /* First check if we have a click available */ ret = mouse_get_click(exp->mouse, pos); if (ret) return log_msg_ret("epm", ret); return 0; /* Click available */ } int expo_poll(struct expo *exp, struct expo_action *act) { int key, ret = -EAGAIN; schedule(); expo_test_mark(exp); /* update mouse position if mouse is enabled */ update_mouse_position(exp); key = poll_keys(exp); if (key != -EAGAIN) { ret = expo_send_key(exp, key); } else if (IS_ENABLED(CONFIG_MOUSE)) { struct vid_pos pos; ret = poll_mouse(exp, &pos); if (!ret) ret = expo_send_click(exp, pos.x, pos.y); } if (ret) { expo_test_poll(exp); return log_msg_ret("epk", ret); } /* get the action (either a key or a click) */ ret = expo_action_get(exp, act); expo_test_poll(exp); if (ret) return log_msg_ret("eag", ret); return 0; } void expo_req_size(struct expo *exp, int width, int height) { exp->req_width = width; exp->req_height = height; } void expo_enter_mode(struct expo *exp) { video_set_manual_sync(true); if (IS_ENABLED(CONFIG_MOUSE) && exp->mouse_enabled) mouse_set_ptr_visible(exp->mouse, false); expo_test_checkenv(exp); } void expo_exit_mode(struct expo *exp) { video_set_manual_sync(false); if (IS_ENABLED(CONFIG_MOUSE) && exp->mouse_enabled) mouse_set_ptr_visible(exp->mouse, true); } void expo_damage_reset(struct expo *exp) { exp->damage.x0 = 0; exp->damage.y0 = 0; exp->damage.x1 = 0; exp->damage.y1 = 0; } void expo_damage_add(struct expo *exp, const struct vid_bbox *bbox) { /* If bbox is invalid (empty), do nothing */ if (bbox->x1 <= bbox->x0 || bbox->y1 <= bbox->y0) return; /* If current damage is empty, set it to the new bbox */ if (exp->damage.x1 <= exp->damage.x0 || exp->damage.y1 <= exp->damage.y0) { exp->damage = *bbox; } else { /* Expand damage area to include new bbox */ if (bbox->x0 < exp->damage.x0) exp->damage.x0 = bbox->x0; if (bbox->y0 < exp->damage.y0) exp->damage.y0 = bbox->y0; if (bbox->x1 > exp->damage.x1) exp->damage.x1 = bbox->x1; if (bbox->y1 > exp->damage.y1) exp->damage.y1 = bbox->y1; } }