ucode-mod-uline: add support for querying window size from terminal if ioctl fails master
authorFelix Fietkau <nbd@nbd.name>
Thu, 27 Feb 2025 10:21:22 +0000 (11:21 +0100)
committerFelix Fietkau <nbd@nbd.name>
Thu, 27 Feb 2025 10:31:26 +0000 (11:31 +0100)
This is useful for running the cli on a serial console

Signed-off-by: Felix Fietkau <nbd@nbd.name>
package/utils/ucode-mod-uline/src/private.h
package/utils/ucode-mod-uline/src/uline.c
package/utils/ucode-mod-uline/src/uline.h
package/utils/ucode-mod-uline/src/vt100.c

index fa38d067370a2a3044982a58afc0fc9f3f9856d8..e53ec22e0333ed270207b54b77f56a3b355cc70c 100644 (file)
@@ -52,6 +52,7 @@ enum vt100_escape {
        VT100_CURSOR_WORD_LEFT,
        VT100_CURSOR_RIGHT,
        VT100_CURSOR_WORD_RIGHT,
+       VT100_CURSOR_POS,
        VT100_HOME,
        VT100_END,
        VT100_INSERT,
@@ -63,7 +64,7 @@ enum vt100_escape {
 };
 
 ssize_t utf8_nsyms(const char *str, size_t len);
-enum vt100_escape vt100_esc_decode(const char *str);
+enum vt100_escape vt100_esc_decode(const char *str, uint32_t *data);
 
 // helpers:
 void __vt100_csi_num(FILE *out, int num, char code);
@@ -191,4 +192,15 @@ static inline void vt100_ding(FILE *out)
        fflush(out);
 }
 
+static inline void vt100_request_window_size(FILE *out)
+{
+       fputs(
+             "\e7"             /* save cursor position */
+             "\e[r"            /* reset margins */
+             "\e[999;999H"     /* move cursor to bottom right */
+             "\e[6n"           /* report cursor position */
+             "\e8",            /* restore cursor position */
+             out);
+}
+
 #endif
index 26cea6fa246fd418164b23690032d62369e5628c..54febe92d5fcc0ff9ca86fa6b6122b187bcff00d 100644 (file)
@@ -101,13 +101,17 @@ update_window_size(struct uline_state *s, bool init)
 #ifdef TIOCGWINSZ
        struct winsize ws = {};
 
-       if (!ioctl(fileno(s->output), TIOCGWINSZ, &ws)) {
+       if (s->ioctl_winsize &&
+           !ioctl(fileno(s->output), TIOCGWINSZ, &ws)) {
                if (ws.ws_col)
                        cols = ws.ws_col;
                if (ws.ws_row)
                        rows = ws.ws_row;
-       }
+       } else
 #endif
+       {
+               s->ioctl_winsize = false;
+       }
 
        s->sigwinch_count = sigwinch_count;
        if (s->cols == cols && s->rows == rows)
@@ -534,7 +538,7 @@ move_word_right(struct uline_state *s, struct linebuf *line)
 }
 
 static bool
-process_esc(struct uline_state *s, enum vt100_escape esc)
+process_esc(struct uline_state *s, enum vt100_escape esc, uint32_t data)
 {
        struct linebuf *line = &s->line;
 
@@ -552,6 +556,15 @@ process_esc(struct uline_state *s, enum vt100_escape esc)
                return move_right(s, line);
        case VT100_CURSOR_WORD_RIGHT:
                return move_word_right(s, line);
+       case VT100_CURSOR_POS:
+               if (s->rows == (data & 0xffff) &&
+                   s->cols == data >> 16)
+                       return false;
+               s->rows = data & 0xffff;
+               s->cols = data >> 16;
+           s->full_update = true;
+               s->cb->event(s, EDITLINE_EV_WINDOW_CHANGED);
+               return true;
        case VT100_HOME:
                line->pos = 0;
                return true;
@@ -682,9 +695,9 @@ process_ctrl(struct uline_state *s, char c)
                linebuf_reset(line);
                return true;
        case KEY_SOH:
-               return process_esc(s, VT100_HOME);
+               return process_esc(s, VT100_HOME, 0);
        case KEY_ENQ:
-               return process_esc(s, VT100_END);
+               return process_esc(s, VT100_END, 0);
        case KEY_VT:
                // TODO: kill
                return false;
@@ -718,18 +731,19 @@ static void
 process_char(struct uline_state *s, char c)
 {
        enum vt100_escape esc;
+       uint32_t data = 0;
 
        check_key_repeat(s, c);
        if (s->esc_idx >= 0) {
                s->esc_seq[s->esc_idx++] = c;
                s->esc_seq[s->esc_idx] = 0;
-               esc = vt100_esc_decode(s->esc_seq);
+               esc = vt100_esc_decode(s->esc_seq, &data);
                if (esc == VT100_INCOMPLETE &&
                    s->esc_idx < (int)sizeof(s->esc_seq) - 1)
                        return;
 
                s->esc_idx = -1;
-               if (!process_esc(s, esc))
+               if (!process_esc(s, esc, data))
                        return;
        } else if (s->cb->key_input &&
                   !check_utf8(s, (unsigned char )c) &&
@@ -901,7 +915,7 @@ void uline_init(struct uline_state *s, const struct uline_cb *cb,
        s->utf8 = utf8;
        s->input = in_fd;
        s->output = out_stream;
-       update_window_size(s, true);
+       s->ioctl_winsize = true;
        reset_input_state(s);
 
 #ifdef USE_SYSTEM_WCHAR
@@ -916,6 +930,12 @@ void uline_init(struct uline_state *s, const struct uline_cb *cb,
                s->has_termios = true;
                termios_set_native_mode(s);
        }
+
+       update_window_size(s, true);
+       if (!s->ioctl_winsize) {
+               vt100_request_window_size(s->output);
+               fflush(s->output);
+       }
 }
 
 void uline_free(struct uline_state *s)
index 6f7b75542f050d917840ea481a93e09772574520..514675e799dcacfc6718021d891bc18c023850ee 100644 (file)
@@ -82,12 +82,13 @@ struct uline_state {
        unsigned int rows, cols;
        struct pos cursor_pos;
        struct pos end_pos;
+       bool ioctl_winsize;
        bool full_update;
        bool stop;
 
        bool utf8;
 
-       char esc_seq[8];
+       char esc_seq[32];
        int8_t esc_idx;
        uint8_t utf8_cont;
 };
index b13e6a67228d1567f2c71a8f9fb1ed74a541f9c9..f81b11d3adc549001dfb2b81a515669c431c804f 100644 (file)
@@ -7,10 +7,10 @@
 #include "uline.h"
 #include "private.h"
 
-enum vt100_escape vt100_esc_decode(const char *str)
+enum vt100_escape vt100_esc_decode(const char *str, uint32_t *data)
 {
-       unsigned long code;
-       size_t idx;
+       unsigned long code, code2;
+       char *err;
 
        switch (*(str++)) {
        case 0:
@@ -45,23 +45,36 @@ enum vt100_escape vt100_esc_decode(const char *str)
                case '0' ... '4':
                case '6' ... '9':
                        str--;
-                       idx = strspn(str, "0123456789");
-                       if (!str[idx])
+                       code = strtoul(str, &err, 10);
+                       switch (*err) {
+                       case 0:
                                return VT100_INCOMPLETE;
-                       if (str[idx] != '~')
-                               return VT100_UNKNOWN;
-                       code = strtoul(str, NULL, 10);
-                       switch (code) {
-                       case 1:
-                               return VT100_HOME;
-                       case 3:
-                               return VT100_DELETE;
-                       case 4:
-                               return VT100_END;
-                       case 200:
-                       case 201:
-                               // paste start/end
-                               return VT100_IGNORE;
+                       case '~':
+                               switch (code) {
+                               case 1:
+                                       return VT100_HOME;
+                               case 3:
+                                       return VT100_DELETE;
+                               case 4:
+                                       return VT100_END;
+                               case 200:
+                               case 201:
+                                       // paste start/end
+                                       return VT100_IGNORE;
+                               default:
+                                       return VT100_UNKNOWN;
+                               }
+                       case ';':
+                               code2 = strtoul(err + 1, &err, 10);
+                               switch (*err) {
+                               case 0:
+                                       return VT100_INCOMPLETE;
+                               case 'R':
+                                       *data = (code2 << 16) | (code & 0xffff);
+                                       return VT100_CURSOR_POS;
+                               default:
+                                       return VT100_UNKNOWN;
+                               }
                        default:
                                return VT100_UNKNOWN;
                        }