I want to add a possibility to type Cyrillic letters in my XNA-game. I can't use default Input.KeyboardState because Input.Keys enum contains only English letters. So, I used keyboard-hook to handle Windows input. But it returns keycode, which can be used for English letters (because ASCII-code matches key-code), but don't work properly with Cyrillic. For "a-я" can be used 848 offset, but for more specific letters, like "і" or "ї" there are different values.
Is there efficient way to convert key-code into Cyrillic letter in ASCII? Or maybe there is solution without nasty keyboard-hook?
BTW, here keyboard-hook class listing:
#region EventArgs
public class CharacterEventArgs : EventArgs
{
private readonly char character;
private readonly int lParam;
private readonly int keyLayout;
public CharacterEventArgs(char character, int lParam, int keyLayout)
{
this.character = character;
this.lParam = lParam;
this.keyLayout = keyLayout;
}
#region Properties
public int KeyLayout
{
get { return keyLayout; }
}
public char Character
{
get { return character; }
}
public int Param
{
get { return lParam; }
}
public int RepeatCount
{
get { return lParam & 0xffff; }
}
public bool ExtendedKey
{
get { return (lParam & (1 << 24)) > 0; }
}
public bool AltPressed
{
get { return (lParam & (1 << 29)) > 0; }
}
public bool PreviousState
{
get { return (lParam & (1 << 30)) > 0; }
}
public bool TransitionState
{
get { return (lParam & (1 << 31)) > 0; }
}
#endregion
}
public class KeyEventArgs : EventArgs
{
private Keys keyCode;
public KeyEventArgs(Keys keyCode)
{
this.keyCode = keyCode;
}
public Keys KeyCode
{
get { return keyCode; }
}
}
#endregion
#region delegats
public delegate void CharEnteredHandler(object sender, CharacterEventArgs e);
public delegate void KeyEventHandler(object sender, KeyEventArgs e);
#endregion
public static class EventInput
{
#region events
/// <summary>
/// Event raised when a character has been entered.
/// </summary>
public static event CharEnteredHandler CharEntered;
/// <summary>
/// Event raised when a key has been pressed down. May fire multiple times due to keyboard repeat.
/// </summary>
public static event KeyEventHandler KeyDown;
/// <summary>
/// Event raised when a key has been released.
/// </summary>
public static event KeyEventHandler KeyUp;
#endregion
delegate IntPtr WndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam);
static bool initialized;
static IntPtr prevWndProc;
static WndProc hookProcDelegate;
static IntPtr hIMC;
static GameWindow gameWindow;
#region constants
const int GWL_WNDPROC = -4;
const int WM_KEYDOWN = 0x100;
const int WM_KEYUP = 0x101;
const int WM_CHAR = 0x102;
const int WM_IME_SETCONTEXT = 0x0281;
const int WM_INPUTLANGCHANGE = 0x51;
const int WM_GETDLGCODE = 0x87;
const int WM_IME_COMPOSITION = 0x10f;
const int DLGC_WANTALLKEYS = 4;
#endregion
#region Win32-functions
//to handle input
[DllImport("Imm32.dll")]
static extern IntPtr ImmGetContext(IntPtr hWnd);
[DllImport("Imm32.dll")]
static extern IntPtr ImmAssociateContext(IntPtr hWnd, IntPtr hIMC);
[DllImport("user32.dll")]
static extern IntPtr CallWindowProc(IntPtr lpPrevWndFunc, IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);
[DllImport("user32.dll")]
static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);
//to get keyboard layout
[DllImport("user32.dll", SetLastError = true)]
static extern int GetWindowThreadProcessId(
[In] IntPtr hWnd,
[Out, Optional] IntPtr lpdwProcessId
);
[DllImport("user32.dll", SetLastError = true)]
static extern ushort GetKeyboardLayout(
[In] int idThread
);
#endregion
static ushort GetKeyboardLayout()
{
return GetKeyboardLayout(GetWindowThreadProcessId(gameWindow.Handle, IntPtr.Zero));
}
#region Initialize
/// <summary>
/// Initialize the TextInput with the given GameWindow.
/// </summary>
/// <param name="window">The XNA window to which text input should be linked.</param>
public static void Initialize(GameWindow window)
{
if (initialized)
throw new InvalidOperationException("TextInput.Initialize can only be called once!");
gameWindow = window;
hookProcDelegate = new WndProc(HookProc);
prevWndProc = (IntPtr)SetWindowLong(window.Handle, GWL_WNDPROC,
(int)Marshal.GetFunctionPointerForDelegate(hookProcDelegate));
hIMC = ImmGetContext(window.Handle);
initialized = true;
}
#endregion
static IntPtr HookProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam)
{
IntPtr returnCode = CallWindowProc(prevWndProc, hWnd, msg, wParam, lParam);
switch (msg)
{
case WM_GETDLGCODE:
returnCode = (IntPtr)(returnCode.ToInt32() | DLGC_WANTALLKEYS);
break;
case WM_KEYDOWN:
if (KeyDown != null)
KeyDown(null, new KeyEventArgs((Keys)wParam));
break;
case WM_KEYUP:
if (KeyUp != null)
KeyUp(null, new KeyEventArgs((Keys)wParam));
break;
case WM_CHAR:
if (CharEntered != null)
{
CharEntered(null, new CharacterEventArgs((char) wParam, lParam.ToInt32(), GetKeyboardLayout()));
}
break;
case WM_IME_SETCONTEXT:
if (wParam.ToInt32() == 1)
ImmAssociateContext(hWnd, hIMC);
break;
case WM_INPUTLANGCHANGE:
ImmAssociateContext(hWnd, hIMC);
returnCode = (IntPtr)1;
break;
}
return returnCode;
}
}