terminal: Special keys
authorCallum Lowcay <callum@callumscode.com>
Fri, 7 Jan 2011 19:47:02 +0000 (19:47 +0000)
committerKristian Høgsberg <krh@bitplanet.net>
Sun, 9 Jan 2011 01:35:15 +0000 (20:35 -0500)
Implements support for function, cursor, and editing keys, with
modifiers.  Partially implements application keypad mode.  Expands
control key support.

Signed-off-by: Callum Lowcay <callum@callumscode.com>
clients/terminal.c

index a56f29b..04dcc43 100644 (file)
@@ -63,6 +63,8 @@ static int option_fullscreen;
 #define MODE_AUTOREPEAT                0x00000008
 #define MODE_LF_NEWLINE                0x00000010
 #define MODE_IRM               0x00000020
+#define MODE_DELETE_SENDS_DEL  0x00000040
+#define MODE_ALT_SENDS_ESC     0x00000080
 
 union utf8_char {
        unsigned char byte[4];
@@ -229,6 +231,87 @@ apply_char_set(character_set cs, union utf8_char *utf8)
        }
 }
 
+struct key_map {
+       int sym;
+       int num;
+       char escape;
+       char code;
+};
+/* Set last key_sub sym to NULL */
+typedef struct key_map *keyboard_mode;
+
+static struct key_map KM_NORMAL[] = {
+       {XK_Left,  1, '[', 'D'},
+       {XK_Right, 1, '[', 'C'},
+       {XK_Up,    1, '[', 'A'},
+       {XK_Down,  1, '[', 'B'},
+       {XK_Home,  1, '[', 'H'},
+       {XK_End,   1, '[', 'F'},
+       {0, 0, 0, 0}
+};
+static struct key_map KM_APPLICATION[] = {
+       {XK_Left,          1, 'O', 'D'},
+       {XK_Right,         1, 'O', 'C'},
+       {XK_Up,            1, 'O', 'A'},
+       {XK_Down,          1, 'O', 'B'},
+       {XK_Home,          1, 'O', 'H'},
+       {XK_End,           1, 'O', 'F'},
+       {XK_KP_Enter,      1, 'O', 'M'},
+       {XK_KP_Multiply,   1, 'O', 'j'},
+       {XK_KP_Add,        1, 'O', 'k'},
+       {XK_KP_Separator,  1, 'O', 'l'},
+       {XK_KP_Subtract,   1, 'O', 'm'},
+       {XK_KP_Divide,     1, 'O', 'o'},
+       {0, 0, 0, 0}
+};
+
+static int
+function_key_response(char escape, int num, uint32_t modifiers,
+                     char code, char *response)
+{
+       int mod_num = 0;
+       int len;
+
+       if (modifiers & WINDOW_MODIFIER_SHIFT) mod_num   |= 1;
+       if (modifiers & WINDOW_MODIFIER_ALT) mod_num     |= 2;
+       if (modifiers & WINDOW_MODIFIER_CONTROL) mod_num |= 4;
+
+       if (mod_num != 0)
+               len = snprintf(response, MAX_RESPONSE, "\e[%d;%d%c",
+                              num, mod_num + 1, code);
+       else if (code != '~')
+               len = snprintf(response, MAX_RESPONSE, "\e%c%c",
+                              escape, code);
+       else
+               len = snprintf(response, MAX_RESPONSE, "\e%c%d%c",
+                              escape, num, code);
+
+       if (len >= MAX_RESPONSE)        return MAX_RESPONSE - 1;
+       else                            return len;
+}
+
+/* returns the number of bytes written into response,
+ * which must have room for MAX_RESPONSE bytes */
+static int
+apply_key_map(keyboard_mode mode, int sym, uint32_t modifiers, char *response)
+{
+       struct key_map map;
+       int len = 0;
+       int i = 0;
+       
+       while (mode[i].sym) {
+               map = mode[i++];
+               if (sym == map.sym) {
+                       len = function_key_response(map.escape, map.num,
+                                                   modifiers, map.code,
+                                                   response);
+                       break;
+               }
+       }
+       
+       return len;
+}
+
 struct terminal_color { double r, g, b, a; };
 struct attr {
        unsigned char fg, bg;
@@ -267,6 +350,7 @@ struct terminal {
        int margin_top, margin_bottom;
        character_set cs, g0, g1;
        character_set saved_cs, saved_g0, saved_g1;
+       keyboard_mode key_mode;
        int data_pitch, attr_pitch;  /* The width in bytes of a line */
        int width, height, start, row, column;
        int saved_row, saved_column;
@@ -309,6 +393,7 @@ terminal_init(struct terminal *terminal)
        terminal->origin_mode = 0;
        terminal->mode = MODE_SHOW_CURSOR |
                         MODE_AUTOREPEAT |
+                        MODE_ALT_SENDS_ESC |
                         MODE_AUTOWRAP;
 
        terminal->row = 0;
@@ -317,6 +402,7 @@ terminal_init(struct terminal *terminal)
        terminal->g0 = CS_US;
        terminal->g1 = CS_US;
        terminal->cs = terminal->g0;
+       terminal->key_mode = KM_NORMAL;
 
        terminal->saved_g0 = terminal->g0;
        terminal->saved_g1 = terminal->g1;
@@ -787,6 +873,10 @@ handle_term_parameter(struct terminal *terminal, int code, int sr)
 
        if (terminal->qmark_flag) {
                switch(code) {
+               case 1:  /* DECCKM */
+                       if (sr) terminal->key_mode = KM_APPLICATION;
+                       else    terminal->key_mode = KM_NORMAL;
+                       break;
                case 2:  /* DECANM */
                        /* No VT52 support yet */
                        terminal->g0 = CS_US;
@@ -832,6 +922,14 @@ handle_term_parameter(struct terminal *terminal, int code, int sr)
                        if (sr) terminal->mode |=  MODE_SHOW_CURSOR;
                        else    terminal->mode &= ~MODE_SHOW_CURSOR;
                        break;
+               case 1037:   /* deleteSendsDel */
+                       if (sr) terminal->mode |=  MODE_DELETE_SENDS_DEL;
+                       else    terminal->mode &= ~MODE_DELETE_SENDS_DEL;
+                       break;
+               case 1039:   /* altSendsEscape */
+                       if (sr) terminal->mode |=  MODE_ALT_SENDS_ESC;
+                       else    terminal->mode &= ~MODE_ALT_SENDS_ESC;
+                       break;
                default:
                        fprintf(stderr, "Unknown parameter: ?%d\n", code);
                        break;
@@ -1239,6 +1337,12 @@ handle_non_csi_escape(struct terminal *terminal, char code)
                terminal->g0 = terminal->saved_g0;
                terminal->g1 = terminal->saved_g1;
                break;
+       case '=':    /* DECPAM */
+               terminal->key_mode = KM_APPLICATION;
+               break;
+       case '>':    /* DECPNM */
+               terminal->key_mode = KM_NORMAL;
+               break;
        default:
                fprintf(stderr, "Unknown escape code: %c\n", code);
                break;
@@ -1583,8 +1687,6 @@ key_handler(struct window *window, uint32_t key, uint32_t sym,
                window_schedule_redraw(terminal->window);
                break;
 
-       case XK_Delete:
-               sym = 0x04;
        case XK_BackSpace:
        case XK_Tab:
        case XK_Linefeed:
@@ -1613,11 +1715,78 @@ key_handler(struct window *window, uint32_t key, uint32_t sym,
        case XK_Alt_R:
                break;
 
+       case XK_Insert:
+               len = function_key_response('[', 2, modifiers, '~', ch);
+               break;
+       case XK_Delete:
+               if (terminal->mode & MODE_DELETE_SENDS_DEL) {
+                       ch[len++] = '\x04';
+               } else {
+                       len = function_key_response('[', 3, modifiers, '~', ch);
+               }
+               break;
+       case XK_Page_Up:
+               len = function_key_response('[', 5, modifiers, '~', ch);
+               break;
+       case XK_Page_Down:
+               len = function_key_response('[', 6, modifiers, '~', ch);
+               break;
+       case XK_F1:
+               len = function_key_response('O', 1, modifiers, 'P', ch);
+               break;
+       case XK_F2:
+               len = function_key_response('O', 1, modifiers, 'Q', ch);
+               break;
+       case XK_F3:
+               len = function_key_response('O', 1, modifiers, 'R', ch);
+               break;
+       case XK_F4:
+               len = function_key_response('O', 1, modifiers, 'S', ch);
+               break;
+       case XK_F5:
+               len = function_key_response('[', 15, modifiers, '~', ch);
+               break;
+       case XK_F6:
+               len = function_key_response('[', 17, modifiers, '~', ch);
+               break;
+       case XK_F7:
+               len = function_key_response('[', 18, modifiers, '~', ch);
+               break;
+       case XK_F8:
+               len = function_key_response('[', 19, modifiers, '~', ch);
+               break;
+       case XK_F9:
+               len = function_key_response('[', 20, modifiers, '~', ch);
+               break;
+       case XK_F10:
+               len = function_key_response('[', 21, modifiers, '~', ch);
+               break;
+       case XK_F12:
+               len = function_key_response('[', 24, modifiers, '~', ch);
+               break;
        default:
-               if (modifiers & WINDOW_MODIFIER_CONTROL)
-                       sym = sym & 0x1f;
-               else if (modifiers & WINDOW_MODIFIER_ALT)
+               /* Handle special keys with alternate mappings */
+               len = apply_key_map(terminal->key_mode, sym, modifiers, ch);
+               if (len != 0) break;
+               
+               if (modifiers & WINDOW_MODIFIER_CONTROL) {
+                       if (sym >= '3' && sym <= '7')
+                               sym = (sym & 0x1f) + 8;
+
+                       if (!((sym >= '!' && sym <= '/') ||
+                               (sym >= '8' && sym <= '?') ||
+                               (sym >= '0' && sym <= '2'))) sym = sym & 0x1f;
+                       else if (sym == '2') sym = 0x00;
+                       else if (sym == '/') sym = 0x1F;
+                       else if (sym == '8' || sym == '?') sym = 0x7F;
+               } else if ((terminal->mode & MODE_ALT_SENDS_ESC) && 
+                       (modifiers & WINDOW_MODIFIER_ALT))
+               {
                        ch[len++] = 0x1b;
+               } else if (modifiers & WINDOW_MODIFIER_ALT) {
+                       sym = sym | 0x80;
+               }
+
                if (sym < 256)
                        ch[len++] = sym;
                break;