SDL_TEXTINPUT support for EVDEV
authorGabriel Jacobo <gabomdq@gmail.com>
Thu, 03 Oct 2013 10:28:10 -0300
changeset 7778 a571a9947869
parent 7777 d5489cec371b
child 7779 d46f8afbcb17
SDL_TEXTINPUT support for EVDEV
README-raspberrypi.txt
configure
configure.in
include/SDL_config.h.in
src/events/SDL_keyboard.c
src/events/SDL_keyboard_c.h
src/input/evdev/SDL_evdev.c
src/input/evdev/SDL_evdev.h
--- a/README-raspberrypi.txt	Thu Oct 03 03:31:05 2013 -0700
+++ b/README-raspberrypi.txt	Thu Oct 03 10:28:10 2013 -0300
@@ -20,7 +20,7 @@
  Raspbian Build Dependencies
 ================================================================================
 
-sudo apt-get install libudev-dev libasound2-dev
+sudo apt-get install libudev-dev libasound2-dev libdbus-1-dev
 
 You also need the VideoCore binary stuff that ships in /opt/vc for EGL and 
 OpenGL ES 2.x, it usually comes pre installed, but in any case:
@@ -28,6 +28,14 @@
 sudo apt-get install libraspberrypi0 libraspberrypi-bin libraspberrypi-dev
 
 ================================================================================
+ No input
+================================================================================
+
+Make sure you belong to the "input" group.
+
+    sudo usermod -aG input `whoami`
+
+================================================================================
  No HDMI Audio
 ================================================================================
 
@@ -40,9 +48,40 @@
 Reference: http://www.raspberrypi.org/phpBB3/viewtopic.php?t=5062
 
 ================================================================================
+ Text Input API support
+================================================================================
+
+The Text Input API is supported, with translation of scan codes done via the
+kernel symbol tables. For this to work, SDL needs access to a valid console.
+If you notice there's no SDL_TEXTINPUT message being emmited, double check that
+your app has read access to one of the following:
+    
+* /proc/self/fd/0
+* /dev/tty
+* /dev/tty[0...6]
+* /dev/vc/0
+* /dev/console
+
+This is usually not a problem if you run from the physical terminal (as opposed
+to running from a pseudo terminal, such as via SSH). If running from a PTS, a 
+quick workaround is to run your app as root or add yourself to the tty group,
+then re login to the system.
+
+   sudo usermod -aG tty `whoami`
+    
+The keyboard layout used by SDL is the same as the one the kernel uses.
+To configure the layout on Raspbian:
+    
+    sudo dpkg-reconfigure keyboard-configuration
+    
+To configure the locale, which controls which keys are interpreted as letters,
+this determining the CAPS LOCK behavior:
+
+    sudo dpkg-reconfigure locales
+
+================================================================================
  Notes
 ================================================================================
 
 * Building has only been tested natively (i.e. not cross compiled). Cross
   compilation might work though, feedback is welcome!
-* No Text Input yet.
\ No newline at end of file
--- a/configure	Thu Oct 03 03:31:05 2013 -0700
+++ b/configure	Thu Oct 03 10:28:10 2013 -0300
@@ -20754,6 +20754,45 @@
         fi
 }
 
+CheckInputKD()
+{
+
+    { $as_echo "$as_me:${as_lineno-$LINENO}: checking for Linux kd.h" >&5
+$as_echo_n "checking for Linux kd.h... " >&6; }
+    use_input_kd=no
+    cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+      #include <linux/kd.h>
+      #include <linux/keyboard.h>
+
+int
+main ()
+{
+
+        struct kbentry kbe;
+        kbe.kb_table = KG_CTRL;
+        ioctl(0, KDGKBENT, &kbe);
+
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+
+    use_input_kd=yes
+
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+    { $as_echo "$as_me:${as_lineno-$LINENO}: result: $use_input_kd" >&5
+$as_echo "$use_input_kd" >&6; }
+    if test x$use_input_kd = xyes; then
+
+$as_echo "#define SDL_INPUT_LINUXKD 1" >>confdefs.h
+
+    fi
+}
+
 CheckLibUDev()
 {
     # Check whether --enable-libudev was given.
@@ -22080,6 +22119,7 @@
         CheckLibUDev
         CheckDBus
         CheckInputEvents
+        CheckInputKD
         CheckTslib
         CheckUSBHID
         CheckPTHREAD
--- a/configure.in	Thu Oct 03 03:31:05 2013 -0700
+++ b/configure.in	Thu Oct 03 10:28:10 2013 -0300
@@ -1791,6 +1791,28 @@
         fi
 }
 
+dnl See if we can use the kernel kd.h header
+CheckInputKD()
+{
+
+    AC_MSG_CHECKING(for Linux kd.h)
+    use_input_kd=no
+    AC_TRY_COMPILE([
+      #include <linux/kd.h>
+      #include <linux/keyboard.h>
+    ],[
+        struct kbentry kbe;
+        kbe.kb_table = KG_CTRL;
+        ioctl(0, KDGKBENT, &kbe);
+    ],[
+    use_input_kd=yes
+    ])
+    AC_MSG_RESULT($use_input_kd)
+    if test x$use_input_kd = xyes; then
+        AC_DEFINE(SDL_INPUT_LINUXKD, 1, [ ])
+    fi
+}
+
 dnl See if the platform offers libudev for device enumeration and hotplugging.
 CheckLibUDev()
 {
@@ -2395,6 +2417,7 @@
         CheckLibUDev
         CheckDBus
         CheckInputEvents
+        CheckInputKD
         CheckTslib
         CheckUSBHID
         CheckPTHREAD
--- a/include/SDL_config.h.in	Thu Oct 03 03:31:05 2013 -0700
+++ b/include/SDL_config.h.in	Thu Oct 03 10:28:10 2013 -0300
@@ -217,6 +217,7 @@
 
 /* Enable various input drivers */
 #undef SDL_INPUT_LINUXEV
+#undef SDL_INPUT_LINUXKD
 #undef SDL_INPUT_TSLIB
 #undef SDL_JOYSTICK_BEOS
 #undef SDL_JOYSTICK_DINPUT
--- a/src/events/SDL_keyboard.c	Thu Oct 03 03:31:05 2013 -0700
+++ b/src/events/SDL_keyboard.c	Thu Oct 03 10:28:10 2013 -0300
@@ -507,7 +507,7 @@
 };
 
 /* Taken from SDL_iconv() */
-static char *
+char *
 SDL_UCS4ToUTF8(Uint32 ch, char *dst)
 {
     Uint8 *p = (Uint8 *) dst;
--- a/src/events/SDL_keyboard_c.h	Thu Oct 03 03:31:05 2013 -0700
+++ b/src/events/SDL_keyboard_c.h	Thu Oct 03 10:28:10 2013 -0300
@@ -59,6 +59,9 @@
 /* Shutdown the keyboard subsystem */
 extern void SDL_KeyboardQuit(void);
 
+/* Convert to UTF-8 */
+extern char *SDL_UCS4ToUTF8(Uint32 ch, char *dst);
+
 #endif /* _SDL_keyboard_c_h */
 
 /* vi: set ts=4 sw=4 expandtab: */
--- a/src/input/evdev/SDL_evdev.c	Thu Oct 03 03:31:05 2013 -0700
+++ b/src/input/evdev/SDL_evdev.c	Thu Oct 03 10:28:10 2013 -0300
@@ -39,7 +39,11 @@
 #include <fcntl.h>
 #include <sys/ioctl.h>
 #include <limits.h>             /* For the definition of PATH_MAX */
-
+#include <linux/input.h>
+#ifdef SDL_INPUT_LINUXKD
+#include <linux/kd.h>
+#include <linux/keyboard.h>
+#endif
 
 #include "SDL.h"
 #include "SDL_assert.h"
@@ -325,12 +329,54 @@
     SDL_BUTTON_X2 + 3           /*  BTN_TASK        0x117 */
 };
 
+static char* EVDEV_consoles[] = {
+    "/proc/self/fd/0",
+    "/dev/tty",
+    "/dev/tty0",
+    "/dev/tty1",
+    "/dev/tty2",
+    "/dev/tty3",
+    "/dev/tty4",
+    "/dev/tty5",
+    "/dev/tty6",
+    "/dev/vc/0",
+    "/dev/console"
+};
+
+#define IS_CONSOLE(fd) isatty (fd) && ioctl(fd, KDGKBTYPE, &arg) == 0 && ((arg == KB_101) || (arg == KB_84))
+
+static int SDL_EVDEV_get_console_fd(void)
+{
+    int fd, i;
+    char arg = 0;
+    
+    /* Try a few consoles to see which one we have read access to */
+    
+    for( i = 0; i < SDL_arraysize(EVDEV_consoles); i++) {
+        fd = open(EVDEV_consoles[i], O_RDONLY);
+        if (fd >= 0) {
+            if (IS_CONSOLE(fd)) return fd;
+            close(fd);
+        }
+    }
+    
+    /* Try stdin, stdout, stderr */
+    
+    for( fd = 0; fd < 3; fd++) {
+        if (IS_CONSOLE(fd)) return fd;
+    }
+    
+    /* We won't be able to send SDL_TEXTINPUT events */
+    return -1;
+}
+
 int
 SDL_EVDEV_Init(void)
 {
     int retval = 0;
     
     if (_this == NULL) {
+        
         _this = (SDL_EVDEV_PrivateData *) SDL_calloc(1, sizeof(*_this));
         if(_this == NULL) {
             return SDL_OutOfMemory();
@@ -354,6 +400,9 @@
 #else
         /* TODO: Scan the devices manually, like a caveman */
 #endif /* SDL_USE_LIBUDEV */
+        
+        /* We need a physical terminal (not PTS) to be able to translate key code to symbols via the kernel tables */
+        _this->console_fd = SDL_EVDEV_get_console_fd();
 
     }
     
@@ -378,6 +427,9 @@
         SDL_UDEV_Quit();
 #endif /* SDL_USE_LIBUDEV */
        
+        if (_this->console_fd >= 0) {
+            close(_this->console_fd);
+        }
         /* Remove existing devices */
         while(_this->first != NULL) {
             SDL_EVDEV_device_removed(_this->first->path);
@@ -443,11 +495,18 @@
     SDL_Scancode scan_code;
     int mouse_button;
     SDL_Mouse *mouse;
-    
+#ifdef SDL_INPUT_LINUXKD
+    Uint16 modstate;
+    struct kbentry kbe;
+    static char keysym[8];
+    char *end;
+    Uint32 kval;
+#endif
+
 #if SDL_USE_LIBUDEV
     SDL_UDEV_Poll();
 #endif
-    
+
     for (item = _this->first; item != NULL; item = item->next) {
         while ((len = read(item->fd, events, (sizeof events))) > 0) {
             len /= sizeof(events[0]);
@@ -455,20 +514,57 @@
                 switch(item->devclass) {
                     case SDL_EVDEV_DEVICE_KEYBOARD:
                         switch (events[i].type) {
-                            case EV_KEY:
-                                scan_code = SDL_EVDEV_translate_keycode(events[i].code);
-                                if (scan_code != SDL_SCANCODE_UNKNOWN) {
-                                    if (events[i].value == 0) {
-                                        SDL_SendKeyboardKey(SDL_RELEASED, scan_code);
+                        case EV_KEY:
+                            scan_code = SDL_EVDEV_translate_keycode(events[i].code);
+                            if (scan_code != SDL_SCANCODE_UNKNOWN) {
+                                if (events[i].value == 0) {
+                                    SDL_SendKeyboardKey(SDL_RELEASED, scan_code);
+                                }
+                                else if (events[i].value == 1 || events[i].value == 2 /* Key repeated */ ) {
+                                    SDL_SendKeyboardKey(SDL_PRESSED, scan_code);
+#ifdef SDL_INPUT_LINUXKD
+                                    if (_this->console_fd >= 0) {
+                                        kbe.kb_index = events[i].code;
+                                        /* Convert the key to an UTF-8 char */
+                                        /* Ref: http://www.linuxjournal.com/article/2783 */
+                                        modstate = SDL_GetModState();
+                                        kbe.kb_table = 0;
+                                        
+                                        /* Ref: http://graphics.stanford.edu/~seander/bithacks.html#ConditionalSetOrClearBitsWithoutBranching */
+                                        kbe.kb_table |= -( (modstate & KMOD_LCTRL) != 0) & (1 << KG_CTRLL | 1 << KG_CTRL);
+                                        kbe.kb_table |= -( (modstate & KMOD_RCTRL) != 0) & (1 << KG_CTRLR | 1 << KG_CTRL);
+                                        kbe.kb_table |= -( (modstate & KMOD_LSHIFT) != 0) & (1 << KG_SHIFTL | 1 << KG_SHIFT);
+                                        kbe.kb_table |= -( (modstate & KMOD_RSHIFT) != 0) & (1 << KG_SHIFTR | 1 << KG_SHIFT);
+                                        kbe.kb_table |= -( (modstate & KMOD_LALT) != 0) & (1 << KG_ALT);
+                                        kbe.kb_table |= -( (modstate & KMOD_RALT) != 0) & (1 << KG_ALTGR);
+
+                                        if(ioctl(_this->console_fd, KDGKBENT, (unsigned long)&kbe) == 0 && 
+                                            ( (KTYP(kbe.kb_value) == KT_LATIN) || (KTYP(kbe.kb_value) == KT_ASCII) || (KTYP(kbe.kb_value) == KT_LETTER) )) 
+                                        {
+                                            kval = KVAL(kbe.kb_value);
+                                            
+                                            /* While there's a KG_CAPSSHIFT symbol, it's not useful to build the table index with it
+                                             * because 1 << KG_CAPSSHIFT overflows the 8 bits of kb_table 
+                                             * So, we do the CAPS LOCK logic here. Note that isalpha depends on the locale!
+                                             */
+                                            if ( modstate & KMOD_CAPS && isalpha(kval) ) {
+                                                if ( isupper(kval) ) {
+                                                    kval = tolower(kval);
+                                                }
+                                                else {
+                                                    kval = toupper(kval);
+                                                }
+                                            }
+                                             
+                                            /* Convert to UTF-8 and send */
+                                            end = SDL_UCS4ToUTF8( kval, keysym);
+                                            *end = '\0';
+                                            SDL_SendKeyboardText(keysym);
+                                        }
                                     }
-                                    else if (events[i].value == 1) {
-                                        SDL_SendKeyboardKey(SDL_PRESSED, scan_code);
-                                    }
-                                    else if (events[i].value == 2) {
-                                        /* Key repeated */
-                                        SDL_SendKeyboardKey(SDL_PRESSED, scan_code);
-                                    }
+#endif    
                                 }
+                            }
                                 break;
 
                             default:
@@ -651,3 +747,4 @@
 #endif /* SDL_INPUT_LINUXEV */
 
 /* vi: set ts=4 sw=4 expandtab: */
+
--- a/src/input/evdev/SDL_evdev.h	Thu Oct 03 03:31:05 2013 -0700
+++ b/src/input/evdev/SDL_evdev.h	Thu Oct 03 10:28:10 2013 -0300
@@ -19,8 +19,6 @@
   3. This notice may not be removed or altered from any source distribution.
 */
 
-#include <linux/input.h>
-
 #include "SDL_config.h"
 
 #ifndef _SDL_evdev_h
@@ -42,7 +40,7 @@
     char *path;
     int fd;
     SDL_EVDEV_deviceclass devclass;
-    struct SDL_evdevlist_item *next;   
+    struct SDL_evdevlist_item *next;
 } SDL_evdevlist_item;
 
 typedef struct SDL_EVDEV_PrivateData
@@ -51,6 +49,7 @@
     SDL_evdevlist_item *last;
     int numdevices;
     int ref_count;
+    int console_fd;
 } SDL_EVDEV_PrivateData;
 
 extern int SDL_EVDEV_Init(void);