| /**************************************************************************** |
| * drivers/video/vnc/vnc_keymap.c |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| * |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. The |
| * ASF licenses this file to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance with the |
| * License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| * License for the specific language governing permissions and limitations |
| * under the License. |
| * |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Included Files |
| ****************************************************************************/ |
| |
| #include <nuttx/config.h> |
| |
| #include <stdint.h> |
| #include <assert.h> |
| #include <errno.h> |
| |
| #include <nuttx/ascii.h> |
| #include <nuttx/video/vnc.h> |
| #include <nuttx/input/x11_keysym.h> |
| #include <nuttx/input/kbd_codec.h> |
| |
| #include "vnc_server.h" |
| |
| /**************************************************************************** |
| * Pre-processor Definitions |
| ****************************************************************************/ |
| |
| #define FIRST_PRTCHAR ASCII_SPACE |
| #define LAST_PRTCHAR ASCII_TILDE |
| #define NPRTCHARS (ASCII_TILDE + ASCII_SPACE - 1) |
| |
| #define ISPRINTABLE(c) ((c) >= FIRST_PRTCHAR && (c) <= LAST_PRTCHAR) |
| #define ISLOWERCASE(c) ((c) >= ASCII_a && (c) <= ASCII_z) |
| #define ISUPPERCASE(c) ((c) >= ASCII_A && (c) <= ASCII_Z) |
| #define ISALPHABETIC(c) (ISLOWERCASE(c) || ISUPPERCASE(c)) |
| |
| /**************************************************************************** |
| * Private types |
| ****************************************************************************/ |
| |
| enum vnc_modifier_e |
| { |
| MOD_SHIFT = 0, /* Left or right shift key */ |
| MOD_CONTROL, /* Left or right control key */ |
| MOD_ALT, /* Alt key */ |
| MOD_CAPSLOCK, /* Caps lock */ |
| MOD_SHIFTLOCK, /* Shift lock */ |
| #ifdef CONFIG_VNCSERVER_KBDENCODE |
| MOD_SCROLLLOCK, /* Scroll lock */ |
| MOD_NUMLOCK, /* Num lock */ |
| #endif |
| NMODIFIERS |
| }; |
| |
| struct vnc_keymap_s |
| { |
| uint16_t nxcode; |
| uint16_t x11code; |
| }; |
| |
| /**************************************************************************** |
| * Private Data |
| ****************************************************************************/ |
| |
| /* Special key modifiers */ |
| |
| static const struct vnc_keymap_s g_modifiers[] = |
| { |
| {MOD_SHIFT, XK_Shift_L}, |
| {MOD_SHIFT, XK_Shift_R}, |
| {MOD_CONTROL, XK_Control_L}, |
| {MOD_CONTROL, XK_Control_R}, |
| {MOD_ALT, XK_Alt_L}, |
| {MOD_ALT, XK_Alt_R}, |
| {MOD_CAPSLOCK, XK_Caps_Lock}, |
| {MOD_SHIFTLOCK, XK_Shift_Lock}, |
| #ifdef CONFIG_VNCSERVER_KBDENCODE |
| {MOD_SCROLLLOCK, XK_Scroll_Lock}, |
| {MOD_NUMLOCK, XK_Num_Lock}, |
| #endif |
| }; |
| |
| #define G_MODIFIERS_NELEM (sizeof(g_modifiers) / sizeof(struct vnc_keymap_s)) |
| |
| /* Map special mappings for X11 codes to ASCII characters */ |
| |
| static const struct vnc_keymap_s g_asciimap[] = |
| { |
| /* Control characters */ |
| |
| #ifdef CONFIG_VNCSERVER_KBDENCODE |
| {ASCII_BS, XK_BackSpace}, |
| #endif |
| {ASCII_TAB, XK_Tab}, |
| {ASCII_LF, XK_Linefeed}, |
| {ASCII_CR, XK_Return}, |
| {ASCII_ESC, XK_Escape}, |
| #ifdef CONFIG_VNCSERVER_KBDENCODE |
| {ASCII_DEL, XK_Delete}, |
| #endif |
| |
| /* Alternative encodings */ |
| |
| {'`', XK_dead_grave}, |
| {'\xb4', XK_dead_acute}, |
| {ASCII_TILDE, XK_dead_tilde}, |
| {ASCII_CARET, XK_dead_circumflex}, |
| |
| /* Keypad aliases */ |
| |
| {ASCII_0, XK_KP_0}, |
| {ASCII_1, XK_KP_1}, |
| {ASCII_2, XK_KP_2}, |
| {ASCII_3, XK_KP_3}, |
| {ASCII_4, XK_KP_4}, |
| {ASCII_5, XK_KP_5}, |
| {ASCII_6, XK_KP_6}, |
| {ASCII_7, XK_KP_7}, |
| {ASCII_8, XK_KP_8}, |
| {ASCII_9, XK_KP_9}, |
| {ASCII_ASTERISK, XK_KP_Multiply}, |
| {ASCII_PLUS, XK_KP_Add}, |
| {ASCII_COMMA, XK_KP_Separator}, |
| {ASCII_HYPHEN, XK_KP_Subtract}, |
| {ASCII_PERIOD, XK_KP_Decimal}, |
| {ASCII_DIVIDE, XK_KP_Divide}, |
| {ASCII_SPACE, XK_KP_Space}, |
| {ASCII_TAB, XK_KP_Tab}, |
| {ASCII_CR, XK_KP_Enter}, |
| #ifdef CONFIG_VNCSERVER_KBDENCODE |
| {ASCII_DEL, XK_KP_Delete}, |
| #endif |
| }; |
| |
| #define G_ASCIIMAP_NELEM (sizeof(g_asciimap) / sizeof(struct vnc_keymap_s)) |
| |
| #ifdef CONFIG_VNCSERVER_KBDENCODE |
| static const struct vnc_keymap_s g_cursor[] = |
| { |
| {KEYCODE_BACKDEL, XK_BackSpace}, |
| {KEYCODE_FWDDEL, XK_Delete}, |
| {KEYCODE_FWDDEL, XK_KP_Delete}, |
| {KEYCODE_HOME, XK_Home}, |
| {KEYCODE_HOME, XK_KP_Home}, |
| {KEYCODE_END, XK_End}, |
| {KEYCODE_END, XK_KP_End}, |
| {KEYCODE_LEFT, XK_Left}, |
| {KEYCODE_LEFT, XK_KP_Left}, |
| {KEYCODE_RIGHT, XK_Right}, |
| {KEYCODE_RIGHT, XK_KP_Right}, |
| {KEYCODE_UP, XK_Up}, |
| {KEYCODE_UP, XK_KP_Up}, |
| {KEYCODE_DOWN, XK_Down}, |
| {KEYCODE_DOWN, XK_KP_Down}, |
| {KEYCODE_PAGEUP, XK_Page_Up}, |
| {KEYCODE_PAGEUP, XK_KP_Prior}, |
| {KEYCODE_PAGEUP, XK_KP_Page_Up}, |
| {KEYCODE_PAGEDOWN, XK_Page_Down}, |
| {KEYCODE_PAGEDOWN, XK_KP_Next}, |
| {KEYCODE_PAGEDOWN, XK_KP_Page_Down}, |
| {KEYCODE_INSERT, XK_Insert}, |
| {KEYCODE_INSERT, XK_KP_Insert}, |
| {KEYCODE_SELECT, XK_Select}, |
| {KEYCODE_EXECUTE, XK_Execute}, |
| {KEYCODE_HELP, XK_Help}, |
| {KEYCODE_MENU, XK_Alt_L}, |
| {KEYCODE_MENU, XK_Alt_R}, |
| {KEYCODE_PAUSE, XK_Pause}, |
| {KEYCODE_PRTSCRN, XK_Print}, |
| {KEYCODE_CLEAR, XK_Clear}, |
| {MOD_SCROLLLOCK, XK_Scroll_Lock}, |
| {MOD_NUMLOCK, XK_Num_Lock}, |
| {KEYCODE_F1, XK_KP_F1}, |
| {KEYCODE_F1, XK_F1}, |
| {KEYCODE_F2, XK_KP_F2}, |
| {KEYCODE_F2, XK_F2}, |
| {KEYCODE_F3, XK_KP_F3}, |
| {KEYCODE_F3, XK_F3}, |
| {KEYCODE_F4, XK_KP_F4}, |
| {KEYCODE_F4, XK_F4}, |
| {KEYCODE_F5, XK_F5}, |
| {KEYCODE_F6, XK_F6}, |
| {KEYCODE_F7, XK_F7}, |
| {KEYCODE_F8, XK_F8}, |
| {KEYCODE_F9, XK_F9}, |
| {KEYCODE_F10, XK_F10}, |
| {KEYCODE_F11, XK_F11}, |
| {KEYCODE_F12, XK_F12}, |
| {KEYCODE_F13, XK_F13}, |
| {KEYCODE_F14, XK_F14}, |
| {KEYCODE_F15, XK_F15}, |
| {KEYCODE_F16, XK_F16}, |
| {KEYCODE_F17, XK_F17}, |
| {KEYCODE_F18, XK_F18}, |
| {KEYCODE_F19, XK_F19}, |
| {KEYCODE_F20, XK_F20}, |
| {KEYCODE_F21, XK_F21}, |
| {KEYCODE_F22, XK_F22}, |
| {KEYCODE_F23, XK_F23}, |
| {KEYCODE_F24, XK_F24}, |
| }; |
| #endif |
| |
| /* Changes the case of a character. Based on US keyboard layout */ |
| |
| static const uint8_t g_caseswap[NPRTCHARS] = |
| { |
| ASCII_SPACE, ASCII_1, ASCII_RSQUOTE, ASCII_3, /* ! " # */ |
| ASCII_4, ASCII_5, ASCII_7, ASCII_QUOTE, /* $ % & ' */ |
| ASCII_9, ASCII_0, ASCII_8, ASCII_EQUAL, /* ( ) * + */ |
| ASCII_LT, ASCII_UNDERSCORE, ASCII_GT, ASCII_QUESTION, /* , - . / */ |
| ASCII_RPAREN, ASCII_EXCLAM, ASCII_AT, ASCII_NUMBER, /* 0 1 2 3 */ |
| ASCII_DOLLAR, ASCII_PERCENT, ASCII_CIRCUMFLEX, ASCII_AMPERSAND, /* 4 5 6 7 */ |
| ASCII_ASTERISK, ASCII_LPAREN, ASCII_SEMICOLON, ASCII_COLON, /* 8 9 : ; */ |
| ASCII_COMMA, ASCII_PLUS, ASCII_PERIOD, ASCII_SLASH, /* < = > ? */ |
| ASCII_2, ASCII_a, ASCII_b, ASCII_c, /* @ A B C */ |
| ASCII_d, ASCII_e, ASCII_f, ASCII_g, /* D E F G */ |
| ASCII_h, ASCII_i, ASCII_j, ASCII_k, /* H I J K */ |
| ASCII_l, ASCII_m, ASCII_n, ASCII_o, /* L M N O */ |
| ASCII_p, ASCII_q, ASCII_r, ASCII_s, /* P Q R S */ |
| ASCII_t, ASCII_u, ASCII_v, ASCII_v, /* T U V W */ |
| ASCII_x, ASCII_y, ASCII_z, ASCII_LBRACE, /* X Y Z [ */ |
| ASCII_VERTBAR, ASCII_RBRACE, ASCII_6, ASCII_HYPHEN, /* \ ] ^ _ */ |
| ASCII_TILDE, ASCII_A, ASCII_B, ASCII_C, /* ' a b c */ |
| ASCII_D, ASCII_E, ASCII_F, ASCII_G, /* c e f g */ |
| ASCII_H, ASCII_I, ASCII_J, ASCII_K, /* h i j k */ |
| ASCII_L, ASCII_M, ASCII_N, ASCII_O, /* l m n o */ |
| ASCII_P, ASCII_Q, ASCII_R, ASCII_S, /* p q r s */ |
| ASCII_T, ASCII_U, ASCII_V, ASCII_W, /* t u v w */ |
| ASCII_X, ASCII_Y, ASCII_Z, ASCII_LBRACKET, /* x y z { */ |
| ASCII_BACKSLASH, ASCII_RBRACKET, ASCII_RSQUOTE, /* | } ~ */ |
| }; |
| |
| /* State of each modifier */ |
| |
| static bool g_modstate[NMODIFIERS]; |
| |
| /**************************************************************************** |
| * Private Functions |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Name: vnc_kbd_encode |
| * |
| * Description: |
| * Encode one escape sequence command into the proivded buffer. |
| * |
| * Input Parameters: |
| * buffer - The location to write the sequence |
| * keycode - The command to be added to the output stream. |
| * terminator - Escape sequence terminating character. |
| * |
| * Returned Value: |
| * Number of bytes written |
| * |
| ****************************************************************************/ |
| |
| #ifdef CONFIG_VNCSERVER_KBDENCODE |
| static inline int vnc_kbd_encode(FAR uint8_t *buffer, uint8_t keycode, |
| uint8_t terminator) |
| { |
| *buffer++ = ASCII_ESC; |
| *buffer++ = ASCII_LBRACKET; |
| *buffer++ = keycode; |
| *buffer = terminator; |
| return 4; |
| } |
| #endif |
| |
| /**************************************************************************** |
| * Name: vnc_kbd_press |
| * |
| * Description: |
| * Indicates a normal key press event. Put one byte of normal keyboard |
| * data into the user provided buffer. |
| * |
| * Input Parameters: |
| * buffer - The location to write the sequence |
| * ch - The character to be added to the output stream. |
| * |
| * Returned Value: |
| * Number of bytes written |
| * |
| ****************************************************************************/ |
| |
| #ifdef CONFIG_VNCSERVER_KBDENCODE |
| static inline void vnc_kbd_press(FAR uint8_t *buffer, uint8_t ch) |
| { |
| *buffer = ch; |
| return 1; |
| } |
| #endif |
| |
| /**************************************************************************** |
| * Name: vnc_kbd_release |
| * |
| * Description: |
| * Encode the release of a normal key. |
| * |
| * Input Parameters: |
| * buffer - The location to write the sequence |
| * ch - The character associated with the key that was releared. |
| * |
| * Returned Value: |
| * Number of bytes written |
| * |
| ****************************************************************************/ |
| |
| #ifdef CONFIG_VNCSERVER_KBDENCODE |
| static inline void vnc_kbd_release(FAR uint8_t *buffer, uint8_t ch) |
| { |
| return vnc_kbd_encode(buffer, ch, ('a' + KBD_RELEASE)); |
| } |
| #endif |
| |
| /**************************************************************************** |
| * Name: vnc_kbd_specpress |
| * |
| * Description: |
| * Denotes a special key press event. Put one special keyboard command |
| * into the user provided buffer. |
| * |
| * Input Parameters: |
| * buffer - The location to write the sequence |
| * keycode - The command to be added to the output stream. |
| * |
| * Returned Value: |
| * Number of bytes written |
| * |
| ****************************************************************************/ |
| |
| #ifdef CONFIG_VNCSERVER_KBDENCODE |
| static inline void vnc_kbd_specpress(FAR uint8_t *buffer, uint8_t keycode) |
| { |
| return vnc_kbd_encode(buffer, keycode, stream, ('a' + KBD_SPECPRESS)); |
| } |
| #endif |
| |
| /**************************************************************************** |
| * Name: vnc_kbd_specrel |
| * |
| * Description: |
| * Denotes a special key release event. Put one special keyboard |
| * command into the user provided buffer. |
| * |
| * Input Parameters: |
| * buffer - The location to write the sequence |
| * keycode - The command to be added to the output stream. |
| * |
| * Returned Value: |
| * Number of bytes written |
| * |
| ****************************************************************************/ |
| |
| #ifdef CONFIG_VNCSERVER_KBDENCODE |
| static inline void vnc_kbd_specrel(FAR uint8_t *buffer, uint8_t keycode) |
| { |
| return vnc_kbd_encode(buffer, keycode, stream, ('a' + KBD_SPECREL)); |
| } |
| #endif |
| |
| /**************************************************************************** |
| * Name: vnc_kbd_lookup |
| * |
| * Description: |
| * Attempt to map the X11 keycode by searching in a lookup table. |
| * |
| ****************************************************************************/ |
| |
| static int vnc_kbd_lookup(FAR const struct vnc_keymap_s *table, |
| unsigned int nelem, uint16_t keysym) |
| { |
| int i; |
| |
| /* First just try to map the virtual keycode using our lookup-table */ |
| |
| for (i = 0; i < nelem; i++) |
| { |
| if (table[i].x11code == keysym) |
| { |
| /* We have a match */ |
| |
| return (int)table[i].nxcode; |
| } |
| } |
| |
| /* No match */ |
| |
| return -EINVAL; |
| } |
| |
| /**************************************************************************** |
| * Name: vnc_kbd_ascii |
| * |
| * Description: |
| * Attempt to map the X11 keycode into the corresponding ASCII code. |
| * |
| ****************************************************************************/ |
| |
| static int vnc_kbd_ascii(uint16_t keysym) |
| { |
| /* ISO/IEC 8859-1 Latin1 matches C ASCII in this range: */ |
| |
| #ifdef CONFIG_VNCSERVER_KBDENCODE |
| if (keysym >= ASCII_SPACE && keysym < ASCII_DEL) |
| #else |
| if (keysym >= ASCII_SPACE && keysym <= ASCII_DEL) |
| #endif |
| { |
| return (int)keysym; |
| } |
| |
| /* Perform a lookup to handler some special cases */ |
| |
| return vnc_kbd_lookup(g_asciimap, G_ASCIIMAP_NELEM, keysym); |
| } |
| |
| /**************************************************************************** |
| * Public Functions |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Name: vnc_key_map |
| * |
| * Description: |
| * Map the receive X11 keysym into something understood by NuttX and route |
| * that through NX to the appropriate window. |
| * |
| * Input Parameters: |
| * session - An instance of the session structure allocated by |
| * vnc_create_session(). |
| * keysym - The X11 keysym value (see include/nuttx/inputx11_keysymdef) |
| * keydown - True: Key pressed; False: Key released |
| * |
| * Returned Value: |
| * None |
| * |
| ****************************************************************************/ |
| |
| void vnc_key_map(FAR struct vnc_session_s *session, uint16_t keysym, |
| bool keydown) |
| { |
| #ifdef CONFIG_VNCSERVER_KBDENCODE |
| uint8_t buffer[4] |
| int nch; |
| #else |
| uint8_t buffer; |
| #endif |
| int16_t keych; |
| |
| /* Check for modifier keys */ |
| |
| keych = vnc_kbd_lookup(g_modifiers, G_MODIFIERS_NELEM, keysym); |
| if (keych >= 0) |
| { |
| g_modstate[keych] = keydown; |
| return; |
| } |
| |
| #ifndef CONFIG_VNCSERVER_KBDENCODE |
| /* If we are not encoding key presses, then we have to ignore key release |
| * events. |
| */ |
| |
| if (!keydown) |
| { |
| return; |
| } |
| #endif |
| |
| /* If no external keyboard input handler has been provided, |
| * then we have to drop the keyboard input. |
| */ |
| |
| if (session->kbdout == NULL) |
| { |
| return; |
| } |
| |
| /* Try to convert the keycode to an ASCII value */ |
| |
| keych = vnc_kbd_ascii((char)(keysym & 255)); |
| if (keych >= 0) |
| { |
| /* It is a simple ASCII-mappable LATIN1 character. Now we need |
| * to apply any modifiers. |
| */ |
| |
| if (g_modstate[MOD_CONTROL]) |
| { |
| /* Make into a control character */ |
| |
| keych &= 0x1f; |
| } |
| |
| /* Other modifiers apply only to printable characters */ |
| |
| else if (ISPRINTABLE(keych)) |
| { |
| /* If Shift Lock is selected, then the case of all printable |
| * characters should be reversed (unless the Shift key is also |
| * pressed) |
| */ |
| |
| if (g_modstate[MOD_SHIFTLOCK]) |
| { |
| if (g_modstate[MOD_SHIFT]) |
| { |
| /* Swap case */ |
| |
| keych = g_caseswap[keych]; |
| } |
| } |
| |
| /* If Caps Lock is selected, then the case of alphabetic |
| * characters should be reversed (unless the Shift key is also |
| * pressed) |
| */ |
| |
| else if (g_modstate[MOD_CAPSLOCK] && ISALPHABETIC(keych)) |
| { |
| if (g_modstate[MOD_SHIFT]) |
| { |
| /* Swap case */ |
| |
| keych = g_caseswap[keych]; |
| } |
| } |
| |
| /* If (1) only the Shift Key is pressed or (2) the Shift key is |
| * pressed with Caps Lock, but the character is not alphabetic, |
| * then the case of all printable characters should be reversed. |
| */ |
| |
| else if (g_modstate[MOD_SHIFT]) |
| { |
| keych = g_caseswap[keych]; |
| } |
| } |
| |
| #ifdef CONFIG_VNCSERVER_KBDENCODE |
| /* Encode the normal character */ |
| |
| if (keydown) |
| { |
| nch = vnc_kbd_press(buffer, keych); |
| } |
| else |
| { |
| nch = vnc_kbd_release(buffer, keych); |
| } |
| |
| /* Inject the normal character sequence into NX */ |
| |
| session->kbdout(session->arg, nch, buffer); |
| #else |
| /* Inject the single key press into NX */ |
| |
| buffer = (uint8_t)keych; |
| session->kbdout(session->arg, 1, &buffer); |
| #endif |
| } |
| |
| /* Not mappable to an ASCII LATIN1 character */ |
| |
| #ifdef CONFIG_VNCSERVER_KBDENCODE |
| else |
| { |
| /* Lookup cursor movement/screen control keysyms */ |
| |
| keych = vnc_kbd_lookup(g_modifiers, G_MODIFIERS_NELEM, keysym); |
| if (keych >= 0) |
| { |
| /* Encode the special character */ |
| |
| if (keydown) |
| { |
| nch = vnc_kbd_specpress(buffer, keych); |
| } |
| else |
| { |
| nch = vnc_kbd_specrel(buffer, keych); |
| } |
| |
| /* Inject the special character sequence into NX */ |
| |
| session->kbdout(session->arg, nch, buffer); |
| } |
| } |
| #endif |
| } |