chiark / gitweb /
terminal/screen: adjust screen age only on update
[elogind.git] / src / libsystemd-terminal / term-screen.c
index a19c684..145dcda 100644 (file)
@@ -47,6 +47,7 @@
 #include <stdbool.h>
 #include <stdint.h>
 #include <stdlib.h>
+#include <xkbcommon/xkbcommon-keysyms.h>
 #include "macro.h"
 #include "term-internal.h"
 #include "util.h"
@@ -418,6 +419,7 @@ static void screen_mode_change(term_screen *screen, unsigned int mode, bool dec,
                          * TODO
                          */
                         set_reset(screen, TERM_FLAG_HIDE_CURSOR, !set);
+                        screen_age_cursor(screen);
                 }
 
                 break;
@@ -455,7 +457,7 @@ static uint32_t screen_map(term_screen *screen, uint32_t val) {
 
 /*
  * Command Handlers
- * This is the inofficial documentation of all the TERM_CMD_* definitions. Each
+ * This is the unofficial documentation of all the TERM_CMD_* definitions. Each
  * handled command has a separate function with an extensive comment on the
  * semantics of the command.
  * Note that many semantics are unknown and need to be verified. This is mostly
@@ -1040,7 +1042,7 @@ static int screen_DECELR(term_screen *screen, const term_seq *seq) {
         /*
          * DECELR - enable-locator-reporting
          * This changes the locator-reporting mode. @args[0] specifies the mode
-         * to set, 0 disables locator-reporting, 1 enables it continously, 2
+         * to set, 0 disables locator-reporting, 1 enables it continuously, 2
          * enables it for a single report. @args[1] specifies the
          * precision-mode. 0 and 2 set the reporting to cell-precision, 1 sets
          * pixel-precision.
@@ -2942,31 +2944,9 @@ static int screen_SGR(term_screen *screen, const term_seq *seq) {
                                 if (i >= seq->n_args || seq->args[i] < 0)
                                         break;
 
+                                dst->ccode = TERM_CCODE_256;
                                 code = seq->args[i];
-                                if (code < 16) {
-                                        dst->ccode = code;
-                                } else if (code < 232) {
-                                        static const uint8_t bval[] = {
-                                                0x00, 0x5f, 0x87,
-                                                0xaf, 0xd7, 0xff,
-                                        };
-
-                                        dst->ccode = TERM_CCODE_256;
-                                        dst->c256 = code;
-                                        code -= 16;
-                                        dst->blue = bval[code % 6];
-                                        code /= 6;
-                                        dst->green = bval[code % 6];
-                                        code /= 6;
-                                        dst->red = bval[code % 6];
-                                } else if (code < 256) {
-                                        dst->ccode = TERM_CCODE_256;
-                                        dst->c256 = code;
-                                        code = (code - 232) * 10 + 8;
-                                        dst->red = code;
-                                        dst->green = code;
-                                        dst->blue = code;
-                                }
+                                dst->c256 = code < 256 ? code : 0;
 
                                 break;
                         }
@@ -3743,20 +3723,40 @@ static int screen_feed_cmd(term_screen *screen, const term_seq *seq) {
         return 0;
 }
 
+unsigned int term_screen_get_width(term_screen *screen) {
+        assert_return(screen, -EINVAL);
+
+        return screen->page->width;
+}
+
+unsigned int term_screen_get_height(term_screen *screen) {
+        assert_return(screen, -EINVAL);
+
+        return screen->page->height;
+}
+
+uint64_t term_screen_get_age(term_screen *screen) {
+        assert_return(screen, 0);
+
+        return screen->age;
+}
+
 int term_screen_feed_text(term_screen *screen, const uint8_t *in, size_t size) {
-        const uint32_t *ucs4_str;
+        uint32_t *ucs4_str;
         size_t i, j, ucs4_len;
         const term_seq *seq;
         int r;
 
         assert_return(screen, -EINVAL);
 
+        ++screen->age;
+
         /* Feed bytes into utf8 decoder and handle parsed ucs4 chars. We always
          * treat data as UTF-8, but the parser makes sure to fall back to raw
          * 8bit mode if the stream is not valid UTF-8. This should be more than
          * enough to support old 7bit/8bit modes. */
         for (i = 0; i < size; ++i) {
-                ucs4_str = term_utf8_decode(&screen->utf8, &ucs4_len, in[i]);
+                ucs4_len = term_utf8_decode(&screen->utf8, &ucs4_str, in[i]);
                 for (j = 0; j < ucs4_len; ++j) {
                         r = term_parser_feed(screen->parser, &seq, ucs4_str[j]);
                         if (r < 0) {
@@ -3772,12 +3772,329 @@ int term_screen_feed_text(term_screen *screen, const uint8_t *in, size_t size) {
         return 0;
 }
 
-int term_screen_feed_keyboard(term_screen *screen, uint32_t keysym, uint32_t ascii, uint32_t ucs4, unsigned int mods) {
+static char *screen_map_key(term_screen *screen,
+                            char *p,
+                            const uint32_t *keysyms,
+                            size_t n_syms,
+                            uint32_t ascii,
+                            const uint32_t *ucs4,
+                            unsigned int mods) {
+        char ch, ch2, ch_mods;
+        uint32_t v;
+        size_t i;
+
+        /* TODO: All these key-mappings need to be verified. Public information
+         * on those mappings is pretty scarce and every emulator seems to do it
+         * slightly differently.
+         * A lot of mappings are also missing. */
+
+        if (n_syms < 1)
+                return p;
+
+        if (n_syms == 1)
+                v = keysyms[0];
+        else
+                v = XKB_KEY_NoSymbol;
+
+        /* In some mappings, the modifiers are encoded as CSI parameters. The
+         * encoding is rather arbitrary, but seems to work. */
+        ch_mods = 0;
+        switch (mods & (TERM_KBDMOD_SHIFT | TERM_KBDMOD_ALT | TERM_KBDMOD_CTRL)) {
+        case TERM_KBDMOD_SHIFT:
+                ch_mods = '2';
+                break;
+        case TERM_KBDMOD_ALT:
+                ch_mods = '3';
+                break;
+        case TERM_KBDMOD_SHIFT | TERM_KBDMOD_ALT:
+                ch_mods = '4';
+                break;
+        case TERM_KBDMOD_CTRL:
+                ch_mods = '5';
+                break;
+        case TERM_KBDMOD_CTRL | TERM_KBDMOD_SHIFT:
+                ch_mods = '6';
+                break;
+        case TERM_KBDMOD_CTRL | TERM_KBDMOD_ALT:
+                ch_mods = '7';
+                break;
+        case TERM_KBDMOD_CTRL | TERM_KBDMOD_SHIFT | TERM_KBDMOD_ALT:
+                ch_mods = '8';
+                break;
+        }
+
+        /* A user might actually use multiple layouts for keyboard
+         * input. @keysyms[0] contains the actual keysym that the user
+         * used. But if this keysym is not in the ascii range, the
+         * input handler does check all other layouts that the user
+         * specified whether one of them maps the key to some ASCII
+         * keysym and provides this via @ascii. We always use the real
+         * keysym except when handling CTRL+<XY> shortcuts we use the
+         * ascii keysym. This is for compatibility to xterm et. al. so
+         * ctrl+c always works regardless of the currently active
+         * keyboard layout. But if no ascii-sym is found, we still use
+         * the real keysym. */
+        if (ascii == XKB_KEY_NoSymbol)
+                ascii = v;
+
+        /* map CTRL+<ascii> */
+        if (mods & TERM_KBDMOD_CTRL) {
+                switch (ascii) {
+                case 0x60 ... 0x7e:
+                        /* Right hand side is mapped to the left and then
+                         * treated equally. Fall through to left-hand side.. */
+                        ascii -= 0x20;
+                case 0x20 ... 0x5f:
+                        /* Printable ASCII is mapped 1-1 in XKB and in
+                         * combination with CTRL bit 7 is flipped. This
+                         * is equivalent to the caret-notation. */
+                        *p++ = ascii ^ 0x40;
+                        return p;
+                }
+        }
+
+        /* map cursor keys */
+        ch = 0;
+        switch (v) {
+        case XKB_KEY_Up:
+                ch = 'A';
+                break;
+        case XKB_KEY_Down:
+                ch = 'B';
+                break;
+        case XKB_KEY_Right:
+                ch = 'C';
+                break;
+        case XKB_KEY_Left:
+                ch = 'D';
+                break;
+        case XKB_KEY_Home:
+                ch = 'H';
+                break;
+        case XKB_KEY_End:
+                ch = 'F';
+                break;
+        }
+        if (ch) {
+                *p++ = 0x1b;
+                if (screen->flags & TERM_FLAG_CURSOR_KEYS)
+                        *p++ = 'O';
+                else
+                        *p++ = '[';
+                if (ch_mods) {
+                        *p++ = '1';
+                        *p++ = ';';
+                        *p++ = ch_mods;
+                }
+                *p++ = ch;
+                return p;
+        }
+
+        /* map action keys */
+        ch = 0;
+        switch (v) {
+        case XKB_KEY_Find:
+                ch = '1';
+                break;
+        case XKB_KEY_Insert:
+                ch = '2';
+                break;
+        case XKB_KEY_Delete:
+                ch = '3';
+                break;
+        case XKB_KEY_Select:
+                ch = '4';
+                break;
+        case XKB_KEY_Page_Up:
+                ch = '5';
+                break;
+        case XKB_KEY_Page_Down:
+                ch = '6';
+                break;
+        }
+        if (ch) {
+                *p++ = 0x1b;
+                *p++ = '[';
+                *p++ = ch;
+                if (ch_mods) {
+                        *p++ = ';';
+                        *p++ = ch_mods;
+                }
+                *p++ = '~';
+                return p;
+        }
+
+        /* map lower function keys */
+        ch = 0;
+        switch (v) {
+        case XKB_KEY_F1:
+                ch = 'P';
+                break;
+        case XKB_KEY_F2:
+                ch = 'Q';
+                break;
+        case XKB_KEY_F3:
+                ch = 'R';
+                break;
+        case XKB_KEY_F4:
+                ch = 'S';
+                break;
+        }
+        if (ch) {
+                if (ch_mods) {
+                        *p++ = 0x1b;
+                        *p++ = '[';
+                        *p++ = '1';
+                        *p++ = ';';
+                        *p++ = ch_mods;
+                        *p++ = ch;
+                } else {
+                        *p++ = 0x1b;
+                        *p++ = 'O';
+                        *p++ = ch;
+                }
+
+                return p;
+        }
+
+        /* map upper function keys */
+        ch = 0;
+        ch2 = 0;
+        switch (v) {
+        case XKB_KEY_F5:
+                ch = '1';
+                ch2 = '5';
+                break;
+        case XKB_KEY_F6:
+                ch = '1';
+                ch2 = '7';
+                break;
+        case XKB_KEY_F7:
+                ch = '1';
+                ch2 = '8';
+                break;
+        case XKB_KEY_F8:
+                ch = '1';
+                ch2 = '9';
+                break;
+        case XKB_KEY_F9:
+                ch = '2';
+                ch2 = '0';
+                break;
+        case XKB_KEY_F10:
+                ch = '2';
+                ch2 = '1';
+                break;
+        case XKB_KEY_F11:
+                ch = '2';
+                ch2 = '2';
+                break;
+        case XKB_KEY_F12:
+                ch = '2';
+                ch2 = '3';
+                break;
+        }
+        if (ch) {
+                *p++ = 0x1b;
+                *p++ = '[';
+                *p++ = ch;
+                if (ch2)
+                        *p++ = ch2;
+                if (ch_mods) {
+                        *p++ = ';';
+                        *p++ = ch_mods;
+                }
+                *p++ = '~';
+                return p;
+        }
+
+        /* map special keys */
+        switch (v) {
+        case 0xff08: /* XKB_KEY_BackSpace */
+        case 0xff09: /* XKB_KEY_Tab */
+        case 0xff0a: /* XKB_KEY_Linefeed */
+        case 0xff0b: /* XKB_KEY_Clear */
+        case 0xff15: /* XKB_KEY_Sys_Req */
+        case 0xff1b: /* XKB_KEY_Escape */
+        case 0xffff: /* XKB_KEY_Delete */
+                *p++ = v - 0xff00;
+                return p;
+        case 0xff13: /* XKB_KEY_Pause */
+                /* TODO: What should we do with this key?
+                 * Sending XOFF is awful as there is no simple
+                 * way on modern keyboards to send XON again.
+                 * If someone wants this, we can re-eanble
+                 * optionally. */
+                return p;
+        case 0xff14: /* XKB_KEY_Scroll_Lock */
+                /* TODO: What should we do on scroll-lock?
+                 * Sending 0x14 is what the specs say but it is
+                 * not used today the way most users would
+                 * expect so we disable it. If someone wants
+                 * this, we can re-enable it (optionally). */
+                return p;
+        case XKB_KEY_Return:
+                *p++ = 0x0d;
+                if (screen->flags & TERM_FLAG_NEWLINE_MODE)
+                        *p++ = 0x0a;
+                return p;
+        case XKB_KEY_ISO_Left_Tab:
+                *p++ = 0x09;
+                return p;
+        }
+
+        /* map unicode keys */
+        for (i = 0; i < n_syms; ++i)
+                p += term_utf8_encode(p, ucs4[i]);
+
+        return p;
+}
+
+int term_screen_feed_keyboard(term_screen *screen,
+                              const uint32_t *keysyms,
+                              size_t n_syms,
+                              uint32_t ascii,
+                              const uint32_t *ucs4,
+                              unsigned int mods) {
+        _cleanup_free_ char *dyn = NULL;
+        static const size_t padding = 1;
+        char buf[128], *start, *p = buf;
+
         assert_return(screen, -EINVAL);
 
-        /* TODO */
+        /* allocate buffer if too small */
+        start = buf;
+        if (4 * n_syms + padding > sizeof(buf)) {
+                dyn = malloc(4 * n_syms + padding);
+                if (!dyn)
+                        return -ENOMEM;
+
+                start = dyn;
+        }
 
-        return 0;
+        /* reserve prefix space */
+        start += padding;
+        p = start;
+
+        p = screen_map_key(screen, p, keysyms, n_syms, ascii, ucs4, mods);
+        if (!p || p - start < 1)
+                return 0;
+
+        /* The ALT modifier causes ESC to be prepended to any key-stroke. We
+         * already accounted for that buffer space above, so simply prepend it
+         * here.
+         * TODO: is altSendsEscape a suitable default? What are the semantics
+         * exactly? Is it used in C0/C1 conversion? Is it prepended if there
+         * already is an escape character? */
+        if (mods & TERM_KBDMOD_ALT && *start != 0x1b)
+                *--start = 0x1b;
+
+        /* turn C0 into C1 */
+        if (!(screen->flags & TERM_FLAG_7BIT_MODE) && p - start >= 2)
+                if (start[0] == 0x1b && start[1] >= 0x40 && start[1] <= 0x5f)
+                        *++start ^= 0x40;
+
+        return screen_write(screen, start, p - start);
 }
 
 int term_screen_resize(term_screen *screen, unsigned int x, unsigned int y) {
@@ -3880,3 +4197,76 @@ int term_screen_set_answerback(term_screen *screen, const char *answerback) {
 
         return 0;
 }
+
+int term_screen_draw(term_screen *screen,
+                     int (*draw_fn) (term_screen *screen,
+                                     void *userdata,
+                                     unsigned int x,
+                                     unsigned int y,
+                                     const term_attr *attr,
+                                     const uint32_t *ch,
+                                     size_t n_ch,
+                                     unsigned int ch_width),
+                     void *userdata,
+                     uint64_t *fb_age) {
+        uint64_t cell_age, line_age, age = 0;
+        term_charbuf_t ch_buf;
+        const uint32_t *ch_str;
+        unsigned int i, j, cw;
+        term_page *page;
+        term_line *line;
+        term_cell *cell;
+        size_t ch_n;
+        int r;
+
+        assert(screen);
+        assert(draw_fn);
+
+        if (fb_age)
+                age = *fb_age;
+
+        page = screen->page;
+
+        for (j = 0; j < page->height; ++j) {
+                line = page->lines[j];
+                line_age = MAX(line->age, page->age);
+
+                for (i = 0; i < page->width; ++i) {
+                        term_attr attr;
+
+                        cell = &line->cells[i];
+                        cell_age = MAX(cell->age, line_age);
+
+                        if (age != 0 && cell_age <= age)
+                                continue;
+
+                        ch_str = term_char_resolve(cell->ch, &ch_n, &ch_buf);
+
+                        /* Character-width of 0 is used for cleared cells.
+                         * Always treat this as single-cell character, so
+                         * renderers can assume ch_width is set properpy. */
+                        cw = MAX(cell->cwidth, 1U);
+
+                        attr = cell->attr;
+                        if (i == screen->cursor_x && j == screen->cursor_y &&
+                            !(screen->flags & TERM_FLAG_HIDE_CURSOR))
+                                attr.inverse ^= 1;
+
+                        r = draw_fn(screen,
+                                    userdata,
+                                    i,
+                                    j,
+                                    &attr,
+                                    ch_str,
+                                    ch_n,
+                                    cw);
+                        if (r != 0)
+                                return r;
+                }
+        }
+
+        if (fb_age)
+                *fb_age = screen->age;
+
+        return 0;
+}