X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?p=elogind.git;a=blobdiff_plain;f=src%2Flibsystemd-terminal%2Fterm-screen.c;h=145dcdaee55ac1ae06544cc1e6c577fd43345569;hp=a19c684d2ad6c040dd610dd18d5d915ebc2d1943;hb=ce04e2335ab80eda5674de3399aa16b5aea2657f;hpb=e432f9e8f999fe75d79a2499035c8e84b04a8b1a diff --git a/src/libsystemd-terminal/term-screen.c b/src/libsystemd-terminal/term-screen.c index a19c684d2..145dcdaee 100644 --- a/src/libsystemd-terminal/term-screen.c +++ b/src/libsystemd-terminal/term-screen.c @@ -47,6 +47,7 @@ #include #include #include +#include #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+ 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+ */ + 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; +}