]> granicus.if.org Git - esp-idf/commitdiff
console: add support for terminal probing and dumb terminal mode
authorIvan Grokhotkov <ivan@espressif.com>
Wed, 16 Aug 2017 06:59:28 +0000 (14:59 +0800)
committerIvan Grokhotkov <ivan@espressif.com>
Mon, 21 Aug 2017 07:58:28 +0000 (15:58 +0800)
components/console/linenoise/linenoise.c
components/console/linenoise/linenoise.h
examples/system/console/main/console_example_main.c

index f68b6ec54cf60a311ea31046ae71b26767f31f25..5e39866ba736fdc78e31bc39adfb09eba084f68e 100644 (file)
 #include <ctype.h>
 #include <sys/stat.h>
 #include <sys/types.h>
+#include <sys/fcntl.h>
 #include <unistd.h>
 #include "linenoise.h"
 
@@ -123,6 +124,7 @@ static linenoiseHintsCallback *hintsCallback = NULL;
 static linenoiseFreeHintsCallback *freeHintsCallback = NULL;
 
 static int mlmode = 0;  /* Multi line mode. Default is single line. */
+static int dumbmode = 0; /* Dumb mode where line editing is disabled. Off by default */
 static int history_max_len = LINENOISE_DEFAULT_HISTORY_MAX_LEN;
 static int history_len = 0;
 static char **history = NULL;
@@ -194,6 +196,11 @@ void linenoiseSetMultiLine(int ml) {
     mlmode = ml;
 }
 
+/* Set if terminal does not recognize escape sequences */
+void linenoiseSetDumbMode(int set) {
+    dumbmode = set;
+}
+
 /* Use the ESC [6n escape sequence to query the horizontal cursor position
  * and return it. On error -1 is returned, on success the position of the
  * cursor. */
@@ -204,6 +211,7 @@ static int getCursorPosition() {
 
     /* Report cursor location */
     fprintf(stdout, "\x1b[6n");
+
     /* Read the response: ESC [ rows ; cols R */
     while (i < sizeof(buf)-1) {
         if (fread(buf+i, 1, 1, stdin) != 1) break;
@@ -875,6 +883,38 @@ static int linenoiseEdit(char *buf, size_t buflen, const char *prompt)
     return l.len;
 }
 
+int linenoiseProbe() {
+    /* Switch to non-blocking mode */
+    int flags = fcntl(STDIN_FILENO, F_GETFL);
+    flags |= O_NONBLOCK;
+    int res = fcntl(STDIN_FILENO, F_SETFL, flags);
+    if (res != 0) {
+        return -1;
+    }
+    /* Device status request */
+    fprintf(stdout, "\x1b[5n");
+
+    /* Try to read response */
+    int timeout_ms = 200;
+    size_t read_bytes = 0;
+    while (timeout_ms > 0 && read_bytes < 4) { // response is ESC[0n or ESC[3n
+        usleep(1000);
+        char c;
+        int cb = fread(&c, 1, 1, stdin);
+        read_bytes += cb;
+        timeout_ms--;
+    }
+    /* Restore old mode */
+    flags &= ~O_NONBLOCK;
+    res = fcntl(STDIN_FILENO, F_SETFL, flags);
+    if (res != 0) {
+        return -1;
+    }
+    if (read_bytes < 4) {
+        return -2;
+    }
+    return 0;
+}
 
 static int linenoiseRaw(char *buf, size_t buflen, const char *prompt) {
     int count;
@@ -885,18 +925,65 @@ static int linenoiseRaw(char *buf, size_t buflen, const char *prompt) {
     }
 
     count = linenoiseEdit(buf, buflen, prompt);
-    printf("\n");
+    fputc('\n', stdout);
     return count;
 }
 
+static int linenoiseDumb(char* buf, size_t buflen, const char* prompt) {
+    /* dumb terminal, fall back to fgets */
+    fputs(prompt, stdout);
+    int count = 0;
+    while (count < buflen) {
+        int c = fgetc(stdin);
+        if (c == '\n') {
+            break;
+        } else if (c >= 0x1c && c <= 0x1f){
+            continue; /* consume arrow keys */
+        } else if (c == BACKSPACE || c == 0x8) {
+            if (count > 0) {
+                buf[count - 1] = 0;
+                count --;
+            }
+            fputs("\x08 ", stdout); /* Windows CMD: erase symbol under cursor */
+        } else {
+            buf[count] = c;
+            ++count;
+        }
+        fputc(c, stdout); /* echo */
+    }
+    fputc('\n', stdout);
+    return count;
+}
+
+static void sanitize(char* src) {
+    char* dst = src;
+    for (int c = *src; c != 0; src++, c = *src) {
+        if (isprint(c)) {
+            *dst = c;
+            ++dst;
+        }
+    }
+    *dst = 0;
+}
+
 /* The high level function that is the main API of the linenoise library. */
 char *linenoise(const char *prompt) {
-    char buf[LINENOISE_MAX_LINE];
-    int count;
-
-    count = linenoiseRaw(buf,LINENOISE_MAX_LINE,prompt);
-    if (count == -1) return NULL;
-    return strdup(buf);
+    char *buf = calloc(1, LINENOISE_MAX_LINE);
+    int count = 0;
+    if (!dumbmode) {
+        count = linenoiseRaw(buf, LINENOISE_MAX_LINE, prompt);
+    } else {
+        count = linenoiseDumb(buf, LINENOISE_MAX_LINE, prompt);
+    }
+    if (count > 0) {
+        sanitize(buf);
+        count = strlen(buf);
+    }
+    if (count <= 0) {
+        free(buf);
+        return NULL;
+    }
+    return buf;
 }
 
 /* This is just a wrapper the user may want to call in order to make sure
index 31a7d0fb9ea74cfa0726fb301c5e7480ef586cd9..a82701f835b87492da3b4ca6d2b72953b00d1e79 100644 (file)
@@ -56,6 +56,7 @@ void linenoiseSetHintsCallback(linenoiseHintsCallback *);
 void linenoiseSetFreeHintsCallback(linenoiseFreeHintsCallback *);
 void linenoiseAddCompletion(linenoiseCompletions *, const char *);
 
+int linenoiseProbe(void);
 char *linenoise(const char *prompt);
 void linenoiseFree(void *ptr);
 int linenoiseHistoryAdd(const char *line);
@@ -65,6 +66,7 @@ int linenoiseHistoryLoad(const char *filename);
 void linenoiseHistoryFree();
 void linenoiseClearScreen(void);
 void linenoiseSetMultiLine(int ml);
+void linenoiseSetDumbMode(int set);
 void linenoisePrintKeyCodes(void);
 
 #ifdef __cplusplus
index 90ccdcb674f0ab32e939adc28ab6ac448c355ca1..c849d54948339ff2f41bac7d1a5ceeca5a935e22 100644 (file)
@@ -105,41 +105,58 @@ void app_main()
     register_system();
     register_wifi();
 
+    /* Prompt to be printed before each line.
+     * This can be customized, made dynamic, etc.
+     */
+    const char* prompt = LOG_COLOR_I "esp32> " LOG_RESET_COLOR;
+
     printf("\n"
            "This is an example of ESP-IDF console component.\n"
            "Type 'help' to get the list of commands.\n"
            "Use UP/DOWN arrows to navigate through command history.\n"
            "Press TAB when typing command name to auto-complete.\n");
 
-    /* Prompt to be printed before each line.
-     * This can be customized, made dynamic, etc.
-     */
-    const char* prompt = LOG_COLOR_I "[esp32]> " LOG_RESET_COLOR;
+    /* Figure out if the terminal supports escape sequences */
+    int probe_status = linenoiseProbe();
+    if (probe_status) { /* zero indicates success */
+        printf("\n"
+               "Your terminal application does not support escape sequences.\n"
+               "Line editing and history features are disabled.\n"
+               "On Windows, try using Putty instead.\n");
+        linenoiseSetDumbMode(1);
+#if CONFIG_LOG_COLORS
+        /* Since the terminal doesn't support escape sequences,
+         * don't use color codes in the prompt.
+         */
+        prompt = "esp32> ";
+#endif //CONFIG_LOG_COLORS
+    }
 
     /* Main loop */
-    char *line;
-    /* Get a line using linenoise.
-     * The line is returned when ENTER is pressed.
-     */
-    while((line = linenoise(prompt)) != NULL) {
-        if (strlen(line) > 0) {  /* Ignore empty lines */
-            /* Add the command to the history */
-            linenoiseHistoryAdd(line);
+    while(true) {
+        /* Get a line using linenoise.
+         * The line is returned when ENTER is pressed.
+         */
+        char* line = linenoise(prompt);
+        if (line == NULL) { /* Ignore empty lines */
+            continue;
+        }
+        /* Add the command to the history */
+        linenoiseHistoryAdd(line);
 #if CONFIG_STORE_HISTORY
-            /* Save command history to filesystem */
-            linenoiseHistorySave(HISTORY_PATH);
+        /* Save command history to filesystem */
+        linenoiseHistorySave(HISTORY_PATH);
 #endif
 
-            /* Try to run the command */
-            int ret;
-            esp_err_t err = esp_console_run(line, &ret);
-            if (err == ESP_ERR_NOT_FOUND) {
-                printf("Unrecognized command\n");
-            } else if (err == ESP_OK && ret != ESP_OK) {
-                printf("Command returned non-zero error code: 0x%x\n", ret);
-            } else if (err != ESP_OK) {
-                printf("Internal error: 0x%x\n", err);
-            }
+        /* Try to run the command */
+        int ret;
+        esp_err_t err = esp_console_run(line, &ret);
+        if (err == ESP_ERR_NOT_FOUND) {
+            printf("Unrecognized command\n");
+        } else if (err == ESP_OK && ret != ESP_OK) {
+            printf("Command returned non-zero error code: 0x%x\n", ret);
+        } else if (err != ESP_OK) {
+            printf("Internal error: 0x%x\n", err);
         }
         /* linenoise allocates line buffer on the heap, so need to free it */
         linenoiseFree(line);