/* * Copyright (c) 2018 Nordic Semiconductor ASA * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include "shell_utils.h" #include "shell_help.h" #include "shell_ops.h" #include "shell_vt100.h" #define SHELL_MSG_CMD_NOT_SUPPORTED "Command not supported.\n" #define SHELL_HELP_COMMENT "Ignore lines beginning with 'rem '" #define SHELL_HELP_RETVAL "Print return value of most recent command" #define SHELL_HELP_CLEAR "Clear screen." #define SHELL_HELP_BACKENDS "List active shell backends.\n" #define SHELL_HELP_BACKSPACE_MODE "Toggle backspace key mode.\n" \ "Some terminals are not sending separate escape code for " \ "backspace and delete button. This command forces shell to interpret" \ " delete key as backspace." #define SHELL_HELP_BACKSPACE_MODE_BACKSPACE "Set different escape" \ " code for backspace and delete key." #define SHELL_HELP_BACKSPACE_MODE_DELETE "Set the same escape" \ " code for backspace and delete key." #define SHELL_HELP_COLORS "Toggle colored syntax." #define SHELL_HELP_COLORS_OFF "Disable colored syntax." #define SHELL_HELP_COLORS_ON "Enable colored syntax." #define SHELL_HELP_VT100 "Toggle vt100 commands." #define SHELL_HELP_VT100_OFF "Disable vt100 commands." #define SHELL_HELP_VT100_ON "Enable vt100 commands." #define SHELL_HELP_PROMPT "Toggle prompt." #define SHELL_HELP_PROMPT_OFF "Disable prompt." #define SHELL_HELP_PROMPT_ON "Enable prompt." #define SHELL_HELP_STATISTICS "Shell statistics." #define SHELL_HELP_STATISTICS_SHOW \ "Get shell statistics for the Logger module." #define SHELL_HELP_STATISTICS_RESET \ "Reset shell statistics for the Logger module." #define SHELL_HELP_RESIZE \ "Console gets terminal screen size or assumes default in case" \ " the readout fails. It must be executed after each terminal" \ " width change to ensure correct text display." #define SHELL_HELP_RESIZE_DEFAULT \ "Assume 80 chars screen width and send this setting " \ "to the terminal." #define SHELL_HELP_HISTORY "Command history." #define SHELL_HELP_ECHO "Toggle shell echo." #define SHELL_HELP_ECHO_ON "Enable shell echo." #define SHELL_HELP_ECHO_OFF \ "Disable shell echo. Editing keys and meta-keys are not handled" #define SHELL_HELP_SELECT "Selects new root command. In order for the " \ "command to be selected, it must meet the criteria:\n" \ " - it is a static command\n" \ " - it is not preceded by a dynamic command\n" \ " - it accepts arguments\n" \ "Return to the main command tree is done by pressing alt+r." #define SHELL_HELP_SHELL "Useful, not Unix-like shell commands." #define SHELL_HELP_HELP "Prints help message." #define SHELL_MSG_UNKNOWN_PARAMETER " unknown parameter: " #define SHELL_MAX_TERMINAL_SIZE (250u) /* 10 == {esc, [, 2, 5, 0, ;, 2, 5, 0, '\0'} */ #define SHELL_CURSOR_POSITION_BUFFER (10u) #define SHELL_DEFAULT_TERMINAL_WIDTH 80 #define SHELL_DEFAULT_TERMINAL_HEIGHT 24 /* Function reads cursor position from terminal. */ static int cursor_position_get(const struct shell *sh, uint16_t *x, uint16_t *y) { uint16_t buff_idx = 0U; size_t cnt; char c = 0; *x = 0U; *y = 0U; memset(sh->ctx->temp_buff, 0, sizeof(sh->ctx->temp_buff)); /* escape code asking terminal about its size */ static char const cmd_get_terminal_size[] = "\033[6n"; z_shell_raw_fprintf(sh->fprintf_ctx, cmd_get_terminal_size); /* fprintf buffer needs to be flushed to start sending prepared * escape code to the terminal. */ z_transport_buffer_flush(sh); /* timeout for terminal response = ~1s */ for (uint16_t i = 0; i < 1000; i++) { do { (void)sh->iface->api->read(sh->iface, &c, sizeof(c), &cnt); if (cnt == 0) { k_busy_wait(1000); break; } if ((c != SHELL_VT100_ASCII_ESC) && (sh->ctx->temp_buff[0] != SHELL_VT100_ASCII_ESC)) { continue; } if (c == 'R') { /* End of response from the terminal. */ sh->ctx->temp_buff[buff_idx] = '\0'; if (sh->ctx->temp_buff[1] != '[') { sh->ctx->temp_buff[0] = 0; return -EIO; } /* Index start position in the buffer where 'y' * is stored. */ buff_idx = 2U; while (sh->ctx->temp_buff[buff_idx] != ';') { *y = *y * 10U + (sh->ctx->temp_buff[buff_idx++] - '0'); if (buff_idx >= CONFIG_SHELL_CMD_BUFF_SIZE) { return -EMSGSIZE; } } if (++buff_idx >= CONFIG_SHELL_CMD_BUFF_SIZE) { return -EIO; } while (sh->ctx->temp_buff[buff_idx] != '\0') { *x = *x * 10U + (sh->ctx->temp_buff[buff_idx++] - '0'); if (buff_idx >= CONFIG_SHELL_CMD_BUFF_SIZE) { return -EMSGSIZE; } } /* horizontal cursor position */ if (*x > SHELL_MAX_TERMINAL_SIZE) { *x = SHELL_MAX_TERMINAL_SIZE; } /* vertical cursor position */ if (*y > SHELL_MAX_TERMINAL_SIZE) { *y = SHELL_MAX_TERMINAL_SIZE; } sh->ctx->temp_buff[0] = 0; return 0; } sh->ctx->temp_buff[buff_idx] = c; if (++buff_idx > SHELL_CURSOR_POSITION_BUFFER - 1) { sh->ctx->temp_buff[0] = 0; /* data_buf[SHELL_CURSOR_POSITION_BUFFER - 1] * is reserved for '\0' */ return -ENOMEM; } } while (cnt > 0); } return -ETIMEDOUT; } /* Function gets terminal width and height. */ static int terminal_size_get(const struct shell *sh) { uint16_t x; /* horizontal position */ uint16_t y; /* vertical position */ int ret_val = 0; z_cursor_save(sh); /* Assumption: terminal width and height < 999. */ /* Move to last column. */ z_shell_op_cursor_vert_move(sh, -SHELL_MAX_TERMINAL_SIZE); /* Move to last row. */ z_shell_op_cursor_horiz_move(sh, SHELL_MAX_TERMINAL_SIZE); if (cursor_position_get(sh, &x, &y) == 0) { sh->ctx->vt100_ctx.cons.terminal_wid = x; sh->ctx->vt100_ctx.cons.terminal_hei = y; } else { ret_val = -ENOTSUP; } z_cursor_restore(sh); return ret_val; } static int cmd_comment(const struct shell *sh, size_t argc, char **argv) { ARG_UNUSED(sh); ARG_UNUSED(argc); ARG_UNUSED(argv); return 0; } static int cmd_clear(const struct shell *sh, size_t argc, char **argv) { ARG_UNUSED(argv); Z_SHELL_VT100_CMD(sh, SHELL_VT100_CURSORHOME); Z_SHELL_VT100_CMD(sh, SHELL_VT100_CLEARSCREEN); return 0; } static int cmd_backends(const struct shell *sh, size_t argc, char **argv) { ARG_UNUSED(argc); ARG_UNUSED(argv); uint16_t cnt = 0; shell_print(sh, "Active shell backends:"); STRUCT_SECTION_FOREACH(shell, obj) { shell_print(sh, " %2d. :%s (%s)", cnt++, obj->ctx->prompt, obj->name); } return 0; } static int cmd_bacskpace_mode_backspace(const struct shell *sh, size_t argc, char **argv) { ARG_UNUSED(argc); ARG_UNUSED(argv); z_flag_mode_delete_set(sh, false); return 0; } static int cmd_bacskpace_mode_delete(const struct shell *sh, size_t argc, char **argv) { ARG_UNUSED(argc); ARG_UNUSED(argv); z_flag_mode_delete_set(sh, true); return 0; } static int cmd_colors_off(const struct shell *sh, size_t argc, char **argv) { ARG_UNUSED(argc); ARG_UNUSED(argv); z_flag_use_colors_set(sh, false); return 0; } static int cmd_colors_on(const struct shell *sh, size_t argc, char **argv) { ARG_UNUSED(argv); ARG_UNUSED(argv); z_flag_use_colors_set(sh, true); return 0; } static int cmd_vt100_off(const struct shell *sh, size_t argc, char **argv) { ARG_UNUSED(argc); ARG_UNUSED(argv); z_flag_use_vt100_set(sh, false); return 0; } static int cmd_vt100_on(const struct shell *sh, size_t argc, char **argv) { ARG_UNUSED(argv); ARG_UNUSED(argv); z_flag_use_vt100_set(sh, true); return 0; } static int cmd_prompt_off(const struct shell *sh, size_t argc, char **argv) { ARG_UNUSED(argc); ARG_UNUSED(argv); shell_prompt_change(sh, ""); return 0; } static int cmd_prompt_on(const struct shell *sh, size_t argc, char **argv) { ARG_UNUSED(argv); ARG_UNUSED(argv); shell_prompt_change(sh, sh->default_prompt); return 0; } static int cmd_echo_off(const struct shell *sh, size_t argc, char **argv) { ARG_UNUSED(argc); ARG_UNUSED(argv); z_flag_echo_set(sh, false); return 0; } static int cmd_echo_on(const struct shell *sh, size_t argc, char **argv) { ARG_UNUSED(argc); ARG_UNUSED(argv); z_flag_echo_set(sh, true); return 0; } static int cmd_echo(const struct shell *sh, size_t argc, char **argv) { if (argc == 2) { shell_error(sh, "%s:%s%s", argv[0], SHELL_MSG_UNKNOWN_PARAMETER, argv[1]); return -EINVAL; } shell_print(sh, "Echo status: %s", z_flag_echo_get(sh) ? "on" : "off"); return 0; } static int cmd_history(const struct shell *sh, size_t argc, char **argv) { ARG_UNUSED(argc); ARG_UNUSED(argv); size_t i = 0; uint16_t len; while (1) { z_shell_history_get(sh->history, true, sh->ctx->temp_buff, &len); if (len) { shell_print(sh, "[%3d] %s", (int)i++, sh->ctx->temp_buff); } else { break; } } sh->ctx->temp_buff[0] = '\0'; return 0; } static int cmd_shell_stats_show(const struct shell *sh, size_t argc, char **argv) { ARG_UNUSED(argc); ARG_UNUSED(argv); shell_print(sh, "Lost logs: %lu", sh->stats->log_lost_cnt); return 0; } static int cmd_shell_stats_reset(const struct shell *sh, size_t argc, char **argv) { ARG_UNUSED(argc); ARG_UNUSED(argv); sh->stats->log_lost_cnt = 0; return 0; } static int cmd_resize_default(const struct shell *sh, size_t argc, char **argv) { ARG_UNUSED(argc); ARG_UNUSED(argv); Z_SHELL_VT100_CMD(sh, SHELL_VT100_SETCOL_80); sh->ctx->vt100_ctx.cons.terminal_wid = SHELL_DEFAULT_TERMINAL_WIDTH; sh->ctx->vt100_ctx.cons.terminal_hei = SHELL_DEFAULT_TERMINAL_HEIGHT; return 0; } static int cmd_resize(const struct shell *sh, size_t argc, char **argv) { int err; if (argc != 1) { shell_error(sh, "%s:%s%s", argv[0], SHELL_MSG_UNKNOWN_PARAMETER, argv[1]); return -EINVAL; } err = terminal_size_get(sh); if (err != 0) { sh->ctx->vt100_ctx.cons.terminal_wid = CONFIG_SHELL_DEFAULT_TERMINAL_WIDTH; sh->ctx->vt100_ctx.cons.terminal_hei = CONFIG_SHELL_DEFAULT_TERMINAL_HEIGHT; shell_warn(sh, "No response from the terminal, assumed 80x24" " screen size"); return -ENOEXEC; } return 0; } static int cmd_get_retval(const struct shell *sh, size_t argc, char **argv) { ARG_UNUSED(argc); ARG_UNUSED(argv); shell_print(sh, "%d", shell_get_return_value(sh)); return 0; } static bool no_args(const struct shell_static_entry *entry) { return (entry->args.mandatory == 1) && (entry->args.optional == 0); } static int cmd_select(const struct shell *sh, size_t argc, char **argv) { const struct shell_static_entry *candidate = NULL; struct shell_static_entry entry; size_t matching_argc; argc--; argv = argv + 1; candidate = z_shell_get_last_command(sh->ctx->selected_cmd, argc, (const char **)argv, &matching_argc, &entry, true); if ((candidate != NULL) && !no_args(candidate) && (argc == matching_argc)) { sh->ctx->selected_cmd = candidate; return 0; } shell_error(sh, "Cannot select command"); return -EINVAL; } SHELL_STATIC_SUBCMD_SET_CREATE(m_sub_colors, SHELL_COND_CMD_ARG(CONFIG_SHELL_VT100_COMMANDS, off, NULL, SHELL_HELP_COLORS_OFF, cmd_colors_off, 1, 0), SHELL_COND_CMD_ARG(CONFIG_SHELL_VT100_COMMANDS, on, NULL, SHELL_HELP_COLORS_ON, cmd_colors_on, 1, 0), SHELL_SUBCMD_SET_END ); SHELL_STATIC_SUBCMD_SET_CREATE(m_sub_vt100, SHELL_COND_CMD_ARG(CONFIG_SHELL_VT100_COMMANDS, off, NULL, SHELL_HELP_VT100_OFF, cmd_vt100_off, 1, 0), SHELL_COND_CMD_ARG(CONFIG_SHELL_VT100_COMMANDS, on, NULL, SHELL_HELP_VT100_ON, cmd_vt100_on, 1, 0), SHELL_SUBCMD_SET_END ); SHELL_STATIC_SUBCMD_SET_CREATE(m_sub_prompt, SHELL_CMD_ARG(off, NULL, SHELL_HELP_PROMPT_OFF, cmd_prompt_off, 1, 0), SHELL_CMD_ARG(on, NULL, SHELL_HELP_PROMPT_ON, cmd_prompt_on, 1, 0), SHELL_SUBCMD_SET_END ); SHELL_STATIC_SUBCMD_SET_CREATE(m_sub_echo, SHELL_CMD_ARG(off, NULL, SHELL_HELP_ECHO_OFF, cmd_echo_off, 1, 0), SHELL_CMD_ARG(on, NULL, SHELL_HELP_ECHO_ON, cmd_echo_on, 1, 0), SHELL_SUBCMD_SET_END ); SHELL_STATIC_SUBCMD_SET_CREATE(m_sub_shell_stats, SHELL_CMD_ARG(reset, NULL, SHELL_HELP_STATISTICS_RESET, cmd_shell_stats_reset, 1, 0), SHELL_CMD_ARG(show, NULL, SHELL_HELP_STATISTICS_SHOW, cmd_shell_stats_show, 1, 0), SHELL_SUBCMD_SET_END ); SHELL_STATIC_SUBCMD_SET_CREATE(m_sub_backspace_mode, SHELL_CMD_ARG(backspace, NULL, SHELL_HELP_BACKSPACE_MODE_BACKSPACE, cmd_bacskpace_mode_backspace, 1, 0), SHELL_CMD_ARG(delete, NULL, SHELL_HELP_BACKSPACE_MODE_DELETE, cmd_bacskpace_mode_delete, 1, 0), SHELL_SUBCMD_SET_END ); SHELL_STATIC_SUBCMD_SET_CREATE(m_sub_shell, SHELL_CMD_ARG(backends, NULL, SHELL_HELP_BACKENDS, cmd_backends, 1, 0), SHELL_CMD(backspace_mode, &m_sub_backspace_mode, SHELL_HELP_BACKSPACE_MODE, NULL), SHELL_COND_CMD(CONFIG_SHELL_VT100_COMMANDS, colors, &m_sub_colors, SHELL_HELP_COLORS, NULL), SHELL_COND_CMD(CONFIG_SHELL_VT100_COMMANDS, vt100, &m_sub_vt100, SHELL_HELP_VT100, NULL), SHELL_CMD(prompt, &m_sub_prompt, SHELL_HELP_PROMPT, NULL), SHELL_CMD_ARG(echo, &m_sub_echo, SHELL_HELP_ECHO, cmd_echo, 1, 1), SHELL_COND_CMD(CONFIG_SHELL_STATS, stats, &m_sub_shell_stats, SHELL_HELP_STATISTICS, NULL), SHELL_SUBCMD_SET_END ); SHELL_STATIC_SUBCMD_SET_CREATE(m_sub_resize, SHELL_CMD_ARG(default, NULL, SHELL_HELP_RESIZE_DEFAULT, cmd_resize_default, 1, 0), SHELL_SUBCMD_SET_END ); SHELL_COND_CMD_REGISTER(CONFIG_SHELL_VT100_COMMANDS, rem, NULL, SHELL_HELP_COMMENT, cmd_comment); SHELL_COND_CMD_ARG_REGISTER(CONFIG_SHELL_VT100_COMMANDS, clear, NULL, SHELL_HELP_CLEAR, cmd_clear, 1, 0); SHELL_CMD_REGISTER(shell, &m_sub_shell, SHELL_HELP_SHELL, NULL); SHELL_COND_CMD_ARG_REGISTER(CONFIG_SHELL_HISTORY, history, NULL, SHELL_HELP_HISTORY, cmd_history, 1, 0); SHELL_COND_CMD_ARG_REGISTER(CONFIG_SHELL_CMDS_RESIZE, resize, &m_sub_resize, SHELL_HELP_RESIZE, cmd_resize, 1, 1); SHELL_COND_CMD_ARG_REGISTER(CONFIG_SHELL_CMDS_SELECT, select, NULL, SHELL_HELP_SELECT, cmd_select, 2, SHELL_OPT_ARG_CHECK_SKIP); SHELL_COND_CMD_ARG_REGISTER(CONFIG_SHELL_CMDS_RETURN_VALUE, retval, NULL, SHELL_HELP_RETVAL, cmd_get_retval, 1, 0);