How to get current Input Language?

Hi,

I would like to set up a MidiKeyboardComponent to catch my KeyPresses in such way that the ‘a’,‘s’,‘d’,‘f’,… row on the keyboard mimics the first white notes on the midi piano.
My problem is: how do I cope with the different input languages that my users might have? In French and German for instance, the keyboard layouts are quite different and the keycodes will be different.

How can I at least get the current input method so that I can change the keyboard mapping accordingly? (then I will have another problem of knowing how the other keyboards look like, but I guess resources can be found on the internet for that…).

I don’t think it is possible without hacking the juce code. You have to retrieve the key virtual scancode, which is independent of the local keyboard layout , see for example what is mentionned here:

http://www.rawmaterialsoftware.com/viewtopic.php?f=2&t=7754&p=43769#p43769

I have defined a global int variable “GLOBAL_keycode_of_last_event” that is written into each time a key is released or pressed . Its value can be checked in the keyPressed / keyStateChanged callback of your component.

--- modules.orig/juce_gui_basics/native/juce_linux_Windowing.cpp  2012-02-17 09:50:23.000000000 +0100
+++ modules/juce_gui_basics/native/juce_linux_Windowing.cpp       2012-02-17 09:50:27.000000000 +0100
@@ -736,6 +736,7 @@
     }
 }

+int GLOBAL_keycode_of_last_event; 

 //==============================================================================
 class LinuxComponentPeer  : public ComponentPeer
@@ -1254,6 +1255,7 @@
         bool keyDownChange = false;
         KeySym sym;

+        GLOBAL_keycode_of_last_event = keyEvent->keycode; 
         {
             ScopedXLock xlock;
             updateKeyStates (keyEvent->keycode, true);
@@ -1365,6 +1367,7 @@
     {
         if (! isKeyReleasePartOfAutoRepeat (keyEvent))
         {
+            GLOBAL_keycode_of_last_event = keyEvent->keycode; 
             updateKeyStates (keyEvent->keycode, false);
             KeySym sym;

@@ -2715,6 +2718,11 @@
     }
 }

--- modules.orig/juce_gui_basics/native/juce_mac_NSViewComponentPeer.mm   2012-02-14 23:41:24.000000000 +0100
+++ modules/juce_gui_basics/native/juce_mac_NSViewComponentPeer.mm        2012-02-17 09:50:27.000000000 +0100
@@ -1378,6 +1378,8 @@
 {
 }

+int GLOBAL_keycode_of_last_event; // horrible hack to provide a low-level keycode info.
+
 bool NSViewComponentPeer::handleKeyEvent (NSEvent* ev, bool isKeyDown)
 {
     String unicode (nsStringToJuce ([ev characters]));
@@ -1387,6 +1389,8 @@
     //DBG ("unicode: " + unicode + " " + String::toHexString ((int) unicode[0]));
     //DBG ("unmodified: " + unmodified + " " + String::toHexString ((int) unmodified[0]));

+    GLOBAL_keycode_of_last_event = [ev keyCode];
+
     if (unicode.isNotEmpty() || keyCode != 0)
     {
         if (isKeyDown)
@@ -1412,6 +1416,10 @@
             if (handleKeyUpOrDown (false))
                 return true;
         }
+    } else {
+        if (handleKeyUpOrDown (isKeyDown))
+            return true;
     }

     return false;
@@ -1446,6 +1454,8 @@

 void NSViewComponentPeer::redirectModKeyChange (NSEvent* ev)
 {
+    GLOBAL_keycode_of_last_event = [ev keyCode];
+
     keysCurrentlyDown.clear();
     handleKeyUpOrDown (true);

--- modules.orig/juce_gui_basics/native/juce_win32_Windowing.cpp  2012-02-14 23:41:25.000000000 +0100
+++ modules/juce_gui_basics/native/juce_win32_Windowing.cpp       2012-02-14 23:41:32.000000000 +0100
@@ -192,6 +192,7 @@
 const int KeyPress::fastForwardKey          = 0x30002;
 const int KeyPress::rewindKey               = 0x30003;

+int GLOBAL_keycode_of_last_event; // horrible hack to provide a low-level keycode info.

 //==============================================================================
 class WindowsBitmapImage  : public ImagePixelData
@@ -2223,6 +2224,7 @@
             //==============================================================================
             case WM_KEYDOWN:
             case WM_SYSKEYDOWN:
+                GLOBAL_keycode_of_last_event = ((lParam & 0xFF0000) >> 16); 
                 if (doKeyDown (wParam))
                     return 0;

@@ -2231,6 +2233,7 @@

             case WM_KEYUP:
             case WM_SYSKEYUP:
+                GLOBAL_keycode_of_last_event = ((lParam & 0xFF0000) >> 16); 
                 if (doKeyUp (wParam))
                     return 0;

Definitively not elegant but it does work very well. The mapping keycode <-> physical key is OS-dependant, though

1 Like

Since there is some renewed interest in the topic, here is the code that maps the virtual scancodes to physical keys for the 3 os:

class QwertyCode {
public:
  /* some keys are not really handled (shift, alt, ctrl, capslock etc) */
  typedef enum {
    INVALID,
    NUMSQUARE, NUM1, NUM2, NUM3, NUM4, NUM5, NUM6, NUM7, NUM8, NUM9, NUM0, NUMMINUS, NUMEQUAL, BACKSPACE,
    TAB, Q, W, E, R, T, Y, U, I, O, P, LBRACKET, RBRACKET,
    CAPSLOCK, A, S, D, F, G, H, J, K, L, SEMICOLON, APOSTROPHE, ANTISLASH,
    LSHIFT, Z, X, C, V, B, N, M, COMMA, DOT, SLASH, RSHIFT,
    LCONTROL, LWIN, LALT, SPACEBAR, RALT, RWIN, RCONTROL,
    ESCAPE, RETURN
  } Code;
  Code code;
  QwertyCode() : code(INVALID) {}
  QwertyCode(int i) : code((Code)i) {}
  operator int() const { return code; }
  bool isValid() const { return code > INVALID && code <= RETURN; }
  static  QwertyCode fromScanCode(int scan_code) {
#ifdef TARGET_DARWIN
    int q2s[RETURN+1] =
      {-1,
       10, 18, 19, 20, 21, 23, 22, 26, 28, 25, 29, 27, 24, 51,
       48, 12, 13, 14, 15, 17, 16, 32, 34, 31, 35, 33, 30,
       57, 0, 1, 2, 3, 5, 4, 38, 40, 37, 41, 39, 42,
       56, 6, 7, 8, 9, 11, 45, 46, 43, 47, 44, 60,
       59, 58, 55, 49, 54, 61, -1,
       53, 36
      };
    xassert(q2s[RETURN] == 36);
#elif defined(TARGET_WIN32)
    int q2s[RETURN+1] =
      {-1,
       1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14,
       15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27,
       -1, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 43,
       42, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54,
       29, 91, 56, 57, 56, 92, 29,
       1, 28 /* return */};
    xassert(q2s[RETURN] == 28);
#elif defined(TARGET_LINUX) || defined(TARGET_ANDROID)
    int q2s[RETURN+1] =
      {-1,
       49, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22,
       23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35,
       -1, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 51,
       50, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62,
       37,115, 64, 65,113,116, -1,
       9, 36};
    xassert(q2s[RETURN] == 36);
#else
#error todo..
#endif
    for (int i=0; i < RETURN+1; ++i) {
      if (scan_code == q2s[i]) return (QwertyCode)i;
    }
    return INVALID;
  }

  static QwertyCode fromAscii(char asc) {
    // the star '*' marks invalid entries
    const char *lst = "*^1234567890-=\b\tqwertyuiop[]*asdfghjkl;'\\*zxcvbnm,./**** ***\033\015";
    xassert(strlen(lst) == RETURN+1);
    const char *p = strchr(lst, asciiToLower(asc));
    if (p) return QwertyCode((Code)(p - lst));
    else return QwertyCode();
  }

  // range of values: x=[0..15], y=[0..5]
  void keyBounds(float &x, float &y, float &w, float &h) {
    x = y = 0; w = 1; h = 1;
    if (code == INVALID) {
      w = 0;
    } else if (code >= NUMSQUARE && code <= BACKSPACE) {
      y = 0;
      x = (code - NUMSQUARE);
      if (code == BACKSPACE) w = 2;
    } else if (code >= TAB && code <= RBRACKET) {
      y = 1;
      if (code == TAB) {
        w = 1.5;
      } else x = 1.5 + (code - TAB - 1);
    } else if (code >= CAPSLOCK && code <= ANTISLASH) {
      y = 2;
      if (code == CAPSLOCK) {
        w = 1.8;
      } else x = 1.8 + (code - A);
    } else if (code >= LSHIFT && code <= RSHIFT) {
      y = 3;
      if (code == LSHIFT) {
        w = 2.4;
      } else x = 2.4 + (code - LSHIFT - 1);
      if (code == RSHIFT) w = 5 - 2.4;
    } else if (code >= LCONTROL && code <= RCONTROL) {
      y = 4;
      float xx[] = { 0, 1.5, 3, 4.5, 10.5, 12, 13.5, 15 };
      xassert(C_ARRAY_LENGTH(xx) == (RCONTROL - LCONTROL + 2));
      int i = code - LCONTROL;
      x = xx[i]; w = xx[i+1] - xx[i];
    } else if (code == RETURN) {
      y = 1; x = 13.8; w = 1.2; h = 2;
    }
  }

  std::string toString() const {
    const char *str[RETURN+1] =
      {"INVALID",
       "NUMSQUARE", "NUM1", "NUM2", "NUM3", "NUM4", "NUM5", "NUM6", "NUM7", "NUM8", "NUM9", "NUM0", "NUMMINUS", "NUMEQUAL", "BACKSPACE",
       "TAB", "Q", "W", "E", "R", "T", "Y", "U", "I", "O", "P", "LBRACKET", "RBRACKET",
       "CAPSLOCK", "A", "S", "D", "F", "G", "H", "J", "K", "L", "SEMICOLON", "APOSTROPHE", "ANTISLASH",
       "LSHIFT", "Z", "X", "C", "V", "B", "N", "M", "COMMA", "DOT", "SLASH", "RSHIFT",
       "LCONTROL", "LWIN", "LALT", "SPACEBAR", "RALT", "RWIN", "RCONTROL",
       "ESCAPE", "RETURN" };
    xassert(str[RETURN] && strcmp(str[RETURN], "RETURN") == 0);
    if (code >= 0 && code <= RETURN) return str[code]; else return "???";
  }

};

1 Like

I’m working on a Linux version of my project. Do you have an updated Linux version of your keyboard example as the old one doesn’t seem to be compatible with the new versions of JUCE?

The patch here should apply cleanly:

https://bitbucket.org/jpommier/juce-stuff/src/master/virtual_scancode.patch

(I don’t think there was much changes, only a file rename for the linux one).

I finally got to trying to apply the patch and it seems not to work. Here’s the error I got when applying it:

error: patch failed: modules/juce_gui_basics/juce_gui_basics.cpp:99
error: modules/juce_gui_basics/juce_gui_basics.cpp: patch does not apply
error: modules/juce_gui_basics/native/juce_mac_NSViewComponentPeer.mm: No such file or directory
error: modules/juce_gui_basics/native/juce_win32_Windowing.cpp: No such file or directory
error: modules/juce_gui_basics/native/x11/juce_linux_XWindowSystem.cpp: No such file or directory

Yes, it looks like many files were renamed by the juce team last week…

For example /juce_mac_NSViewComponentPeer.mm is now juce_NSViewComponentPeer_mac.mm

I tried applying the patch to a JUCE version (development branch) from one month ago and got the same errors. The incompatible changes might be much older then.