+struct nc_scr *cui_set_current(struct cui *cui, struct nc_scr *scr)
+{
+ struct nc_scr *old;
+
+ DBGS("%p -> %p\n", cui->current, scr);
+
+ assert(cui->current != scr);
+
+ old = cui->current;
+ nc_scr_unpost(old);
+
+ cui->current = scr;
+
+ nc_scr_post(cui->current);
+
+ return old;
+}
+
+static bool set_temp_autoboot_opt(struct cui *cui, struct autoboot_option *opt)
+{
+ cui->autoboot_opt = opt;
+ if (cui->client)
+ discover_client_send_temp_autoboot(cui->client, opt);
+
+ return true;
+}
+
+static bool key_cancels_boot(int key)
+{
+ unsigned int i;
+
+ if (key == 0xc)
+ return false;
+
+ for (i = 0; i < ARRAY_SIZE(autoboot_override_keys); i++)
+ if (key == autoboot_override_keys[i].key)
+ return false;
+
+ return true;
+}
+
+static bool process_global_keys(struct cui *cui, int key)
+{
+ unsigned int i;
+
+ switch (key) {
+ case 0xc:
+ if (cui->current && cui->current->main_ncw)
+ wrefresh(curscr);
+ return true;
+ }
+
+ /* check for autoboot override keys */
+ for (i = 0; i < ARRAY_SIZE(autoboot_override_keys); i++) {
+ if (key != autoboot_override_keys[i].key)
+ continue;
+
+ pb_log("Sending temporary autoboot override\n");
+ set_temp_autoboot_opt(cui, &autoboot_override_keys[i].opt);
+ return true;
+ }
+
+ return false;
+}
+
+/**
+ * cui_process_key - Process input on stdin.
+ */
+
+static int cui_process_key(void *arg)
+{
+ struct cui *cui = cui_from_arg(arg);
+ unsigned int i;
+ char *sequence;
+ int grab;
+
+ assert(cui->current);
+
+ for (;;) {
+ int c = getch();
+
+ pb_debug("%s: got key %d\n", __func__, c);
+
+ if (c == ERR)
+ break;
+
+ if (c == 27) {
+ /*
+ * If this is a console code sequence try to parse it
+ * and don't treat this as a key press.
+ */
+ grab = getch();
+ if (grab != ERR && grab != 27) {
+ ungetch(grab);
+ pb_debug("%s: Caught unhandled command sequence\n",
+ __func__);
+ sequence = handle_control_sequence(cui, c);
+ pb_debug("Caught sequence ");
+ if (sequence) {
+ pb_debug("(%zu): ", strlen(sequence));
+ for (i = 0; i < strlen(sequence); i++)
+ pb_debug("0%o ", sequence[i]);
+ pb_debug("\n");
+ } else
+ pb_debug("(0): (none)\n");
+ continue;
+ }
+ }
+
+ if (cui->preboot_mode) {
+ /* Turn curses options back on if the user interacts */
+ cui->preboot_mode = false;
+ cui_set_curses_options(true);
+ }
+
+ if (!cui->has_input && key_cancels_boot(c)) {
+ cui->has_input = true;
+ if (cui->client) {
+ pb_log("UI input received (key = %d), aborting "
+ "default boot\n", c);
+ discover_client_cancel_default(cui->client);
+ } else {
+ pb_log("UI input received (key = %d), aborting "
+ "once server connects\n", c);
+ }
+ }
+
+ if (process_global_keys(cui, c))
+ continue;
+
+ cui->current->process_key(cui->current, c);
+ }
+
+ return 0;
+}
+
+/**
+ * cui_process_js - Process joystick events.
+ */
+
+static int cui_process_js(void *arg)
+{
+ struct cui *cui = cui_from_arg(arg);
+ int c;
+
+ c = pjs_process_event(cui->pjs);
+
+ if (c) {
+ ungetch(c);
+ cui_process_key(arg);
+ }
+
+ return 0;
+}
+
+/**
+ * cui_handle_resize - Handle the term resize.
+ */
+
+static void cui_handle_resize(struct cui *cui)
+{
+ struct winsize ws;
+
+ if (ioctl(1, TIOCGWINSZ, &ws) == -1) {
+ pb_log_fn("ioctl failed: %s\n", strerror(errno));
+ return;
+ }
+
+ pb_debug("%s: {%u,%u}\n", __func__, ws.ws_row, ws.ws_col);
+
+ wclear(cui->current->main_ncw);
+ resize_term(ws.ws_row, ws.ws_col);
+ cui->current->resize(cui->current);
+
+ /* For some reason this makes ncurses redraw the screen */
+ getch();
+ redrawwin(cui->current->main_ncw);
+ wrefresh(cui->current->main_ncw);
+}
+
+/**
+ * cui_device_add - Client device_add callback.
+ *
+ * Creates menu_items for all the device boot_options and inserts those
+ * menu_items into the main menu. Redraws the main menu if it is active.
+ * If a 'plugin' type boot_option appears the plugin menu is updated instead.
+ */
+
+static int cui_boot_option_add(struct device *dev, struct boot_option *opt,
+ void *arg)
+{
+ struct pmenu_item *i, *dev_hdr = NULL;
+ struct cui *cui = cui_from_arg(arg);
+ struct cui_opt_data *cod;
+ const char *tab = " ";
+ unsigned int insert_pt;
+ int result, rows, cols;
+ struct pmenu *menu;
+ bool plugin_option;
+ ITEM *selected;
+ char *name;
+
+ plugin_option = opt->type == DISCOVER_PLUGIN_OPTION;
+ menu = plugin_option ? cui->plugin_menu : cui->main;
+
+ pb_debug("%s: %p %s\n", __func__, opt, opt->id);
+
+ selected = current_item(menu->ncm);
+ menu_format(menu->ncm, &rows, &cols);
+
+ if (cui->current == &cui->main->scr)
+ nc_scr_unpost(cui->current);
+ if (plugin_option && cui->current == &cui->plugin_menu->scr)
+ nc_scr_unpost(cui->current);
+
+ /* Check if the boot device is new */
+ dev_hdr = pmenu_find_device(menu, dev, opt);
+
+ /* All actual boot entries are 'tabbed' across */
+ name = talloc_asprintf(menu, "%s%s%s",
+ tab, opt->is_autoboot_default ? "(*) " : "",
+ opt->name ? : "Unknown Name");
+
+ /* Save the item in opt->ui_info for cui_device_remove() */
+ opt->ui_info = i = pmenu_item_create(menu, name);
+ talloc_free(name);
+ if (!i)
+ return -1;
+
+ if (plugin_option) {
+ i->on_edit = NULL;
+ i->on_execute = cui_plugin_install_check;
+ } else {
+ i->on_edit = cui_item_edit;
+ i->on_execute = cui_boot_check;
+ }
+
+ i->data = cod = talloc(i, struct cui_opt_data);
+ cod->opt = opt;
+ cod->dev = dev;
+ cod->opt_hash = pb_opt_hash(dev, opt);
+ cod->name = opt->name;
+
+ if (plugin_option) {
+ cod->pd = talloc(i, struct pb_plugin_data);
+ cod->pd->plugin_file = talloc_strdup(cod,
+ opt->boot_image_file);
+ } else {
+ cod->bd = talloc(i, struct pb_boot_data);
+ cod->bd->image = talloc_strdup(cod->bd, opt->boot_image_file);
+ cod->bd->initrd = talloc_strdup(cod->bd, opt->initrd_file);
+ cod->bd->dtb = talloc_strdup(cod->bd, opt->dtb_file);
+ cod->bd->args = talloc_strdup(cod->bd, opt->boot_args);
+ cod->bd->args_sig_file = talloc_strdup(cod->bd, opt->args_sig_file);
+ }
+
+ /* This disconnects items array from menu. */
+ result = set_menu_items(menu->ncm, NULL);
+
+ if (result)
+ pb_log_fn("set_menu_items failed: %d\n", result);
+
+ /* Insert new items at insert_pt. */
+ if (dev_hdr) {
+ insert_pt = pmenu_grow(menu, 2);
+ pmenu_item_insert(menu, dev_hdr, insert_pt);
+ pb_log("%s: adding new device hierarchy %s\n",
+ __func__, opt->device_id);
+ pmenu_item_insert(menu, i, insert_pt+1);
+ } else {
+ insert_pt = pmenu_grow(menu, 1);
+ pmenu_item_add(menu, i, insert_pt);
+ }
+
+ if (plugin_option) {
+ pb_log_fn("adding plugin '%s'\n", cod->name);
+ pb_log(" file '%s'\n", cod->pd->plugin_file);
+ } else {
+ pb_log_fn("adding opt '%s'\n", cod->name);
+ pb_log(" image '%s'\n", cod->bd->image);
+ pb_log(" initrd '%s'\n", cod->bd->initrd);
+ pb_log(" args '%s'\n", cod->bd->args);
+ pb_log(" argsig '%s'\n", cod->bd->args_sig_file);
+ }
+
+ /* Update the plugin menu label if needed */
+ if (plugin_option) {
+ struct pmenu_item *item;
+ unsigned int j;
+ result = set_menu_items(cui->main->ncm, NULL);
+ for (j = 0 ; j < cui->main->item_count; j++) {
+ item = item_userptr(cui->main->items[j]);
+ if (item->on_execute != menu_plugin_execute)
+ continue;
+ cui->n_plugins++;
+ char *label = talloc_asprintf(item, _("Plugins (%u)"),
+ cui->n_plugins);
+ pmenu_item_update(item, label);
+ talloc_free(label);
+ break;
+ }
+ result = set_menu_items(cui->main->ncm, cui->main->items);
+ if (result)
+ pb_log_fn("set_menu_items failed: %d\n", result);
+ }
+
+ /* Update the default option */
+ if (opt->is_autoboot_default) {
+ struct cui_opt_data *tmp;
+ struct pmenu_item *item;
+ unsigned int j;
+ if (cui->default_item) {
+ for (j = 0; j < cui->main->item_count; j++) {
+ item = item_userptr(cui->main->items[j]);
+ tmp = cod_from_item(item);
+ if (tmp->opt_hash == cui->default_item) {
+ char *label = talloc_asprintf(menu, "%s%s",
+ tab, tmp->name ? : "Unknown Name");
+ pmenu_item_update(item, label);
+ talloc_free(label);
+ break;
+ }
+ }
+ }
+ cui->default_item = cod->opt_hash;
+ }
+
+ /* Re-attach the items array. */
+ result = set_menu_items(menu->ncm, menu->items);
+
+ if (result)
+ pb_log_fn("set_menu_items failed: %d\n", result);
+
+ if (0) {
+ pb_log("%s\n", __func__);
+ pmenu_dump_items(menu->items,
+ item_count(menu->ncm) + 1);
+ }
+
+ if (!item_visible(selected)) {
+ int idx, top;
+
+ top = top_row(menu->ncm);
+ idx = item_index(selected);
+
+ /* If our index is above the current top row, align
+ * us to the new top. Otherwise, align us to the new
+ * bottom */
+ top = top < idx ? idx - rows + 1 : idx;
+
+ set_top_row(menu->ncm, top);
+ set_current_item(menu->ncm, selected);
+ }
+
+ if (cui->current == &menu->scr)
+ nc_scr_post(cui->current);
+ if (plugin_option && cui->current == &cui->main->scr)
+ nc_scr_post(cui->current);
+
+ return 0;
+}
+
+/**
+ * cui_device_remove - Client device remove callback.
+ *
+ * Removes all the menu_items for the device from the main menu and redraws the
+ * main menu if it is active.
+ */
+static void cui_device_remove(struct device *dev, void *arg)