// The following code was taken from here: http://www.gamedev.net/community/forums/topic.asp?topic_id=457783
// and modified for use within Chernobyl. This code is used to generate buffered
// input on Windows when using XNA since XNA has no buffered input.
// Note that, this code may not work correctly in 64 bit.
// TODO: test in 64 bit
using System;
using System.Runtime.InteropServices;
using Chernobyl.Collections.Generic.Event;
using Chernobyl.Dependency;
using Chernobyl.Event;
using Chernobyl.Plugin;
using Microsoft.Xna.Framework;
namespace Chernobyl.Input.Xna.Controls.Keyboard
{
///
/// The class responsible for inject in Keyboard input from Windows.
///
public class WindowsKeyboardInput : IPlugin
{
///
/// Initializes a new instance of the class.
///
/// The
/// instance that gives and takes services.
public WindowsKeyboardInput(IEventCollection services)
{
services.Inject(this);
}
///
/// The XNA that describes the window.
///
[Inject]
public GameWindow Window
{
set
{
_hookProcDelegate = new WndProc(HookProc);
_prevWndProc = (IntPtr)SetWindowLong(value.Handle, GWL_WNDPROC,
(int)Marshal.GetFunctionPointerForDelegate(_hookProcDelegate));
_hImc = ImmGetContext(value.Handle);
}
}
///
/// The services provided by this plug-in or null if no services are
/// provided.
///
public IEventCollection Services { get; private set; }
///
/// An event that is raised when a character has been entered.
///
public event EventHandler CharEntered;
///
/// A delegate that defines the Window's procedure used to received all
/// Input directed at a window.
///
delegate IntPtr WndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam);
///
/// This function retrieves the input context associated with the
/// specified window. See
/// http://msdn.microsoft.com/en-us/library/aa913345.aspx for more
/// information.
///
/// Handle to the window to retrieve the input
/// context for.
/// The handle to the input context indicates success.
[DllImport("Imm32.dll")]
static extern IntPtr ImmGetContext(IntPtr hWnd);
///
/// Associates the specified input context with the specified window.
/// By default, the operating system associates the default input context
/// with each window as it is created. See
/// http://msdn.microsoft.com/en-us/library/dd318171%28v=vs.85%29.aspx
/// for more information.
///
/// Handle to the window to associate with the input
/// context.
/// Handle to the input context. If hIMC is NULL, the
/// function removes any association the window has with an input context.
/// Thus IME cannot be used in the window.
/// Returns the handle to the input context previously
/// associated with the window.
[DllImport("Imm32.dll")]
static extern IntPtr ImmAssociateContext(IntPtr hWnd, IntPtr hImc);
///
/// Passes message information to the specified window procedure. See
/// http://msdn.microsoft.com/en-us/library/ms633571%28v=vs.85%29.aspx
/// for more information.
///
/// The previous window procedure. If this
/// value is obtained by calling the GetWindowLong function with the
/// nIndex parameter set to GWL_WNDPROC or DWL_DLGPROC, it is actually
/// either the address of a window or dialog box procedure, or a special
/// internal value meaningful only to
/// .
/// A handle to the window procedure to receive the
/// message.
/// The message.
/// Additional message-specific information. The
/// contents of this parameter depend on the value of the Msg parameter.
/// Additional message-specific information. The
/// contents of this parameter depend on the value of the Msg parameter.
///
[DllImport("user32.dll")]
static extern IntPtr CallWindowProc(IntPtr lpPrevWndFunc, IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam);
///
/// Changes an attribute of the specified window. The function also sets
/// the 32-bit (long) value at the specified offset into the extra window
/// memory. Fore more information, see
/// http://msdn.microsoft.com/en-us/library/ms633591%28v=vs.85%29.aspx
///
/// A handle to the window and, indirectly, the class
/// to which the window belongs.
/// The zero-based offset to the value to be set.
/// Valid values are in the range zero through the number of bytes of
/// extra window memory, minus the size of an integer. To set any other
/// value, specify one of the following values.
/// The replacement value.
/// If the function succeeds, the return value is the previous
/// value of the specified 32-bit integer. If the function fails, the
/// return value is zero. To get extended error information, call
/// GetLastError. If the previous value of the specified 32-bit integer
/// is zero, and the function succeeds, the return value is zero, but
/// the function does not clear the last error information. This makes
/// it difficult to determine success or failure. To deal with this, you
/// should clear the last error information by calling SetLastError with
/// 0 before calling SetWindowLong. Then, function failure will be
/// indicated by a return value of zero and a GetLastError result that
/// is nonzero.
[DllImport("user32.dll")]
static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);
///
/// The Window's hook procedure that handles Window's messages. See
/// http://msdn.microsoft.com/en-us/library/ms632589%28v=vs.85%29.aspx
/// for more information.
///
/// The handle to the window the message is for.
/// The message.
/// Additional message-specific information. The
/// contents of this parameter depend on the value of the Msg parameter.
/// Additional message-specific information. The
/// contents of this parameter depend on the value of the Msg parameter.
///
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_CHAR:
char character = (char)wParam;
if (character != VK_ESCAPE && character != VK_BACK)
{
if (CharEntered != null)
CharEntered(null, new CharacterEventArgs(character, lParam.ToInt32()));
}
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;
}
///
/// The previous windows messaging procedure before we set our custom
/// window procedure, .
///
IntPtr _prevWndProc;
///
/// Our custom windows procedure.
///
WndProc _hookProcDelegate;
///
/// The input context for our window.
///
IntPtr _hImc;
// ReSharper disable InconsistentNaming
///
/// For more information, see
/// http://msdn.microsoft.com/en-us/library/ms633591%28v=vs.85%29.aspx
///
const int GWL_WNDPROC = -4;
///
/// For more information,
/// see http://msdn.microsoft.com/en-us/library/ms646276%28v=vs.85%29.aspx
///
const int WM_CHAR = 0x102;
///
/// For more information,
/// see http://msdn.microsoft.com/en-us/library/aa915455.aspx
///
const int WM_IME_SETCONTEXT = 0x0281;
///
/// For more information,
/// see http://msdn.microsoft.com/en-us/library/ms632629%28v=vs.85%29.aspx
///
const int WM_INPUTLANGCHANGE = 0x51;
///
/// For more information,
/// see http://msdn.microsoft.com/en-us/library/ms645425%28v=vs.85%29.aspx
///
const int WM_GETDLGCODE = 0x87;
///
/// For more information,
/// see http://msdn.microsoft.com/en-us/library/dd374133%28v=vs.85%29.aspx
///
const int WM_IME_COMPOSITION = 0x10f;
///
/// For more information,
/// see http://support.microsoft.com/kb/83302
///
const int DLGC_WANTALLKEYS = 4;
///
/// The virtual-key code for the escape key.
///
const int VK_ESCAPE = 0x1B;
///
/// The virtual-key code for the backspace key.
///
const int VK_BACK = 0x08;
// ReSharper restore InconsistentNaming
}
///
/// An class for containing event data on input
/// related functionality from the Windows API.
///
public class CharacterEventArgs : ItemsEventArgs
{
///
/// Initializes a new instance of the
/// class.
///
/// The keyboard character that was pressed.
/// Extra information on the keyboard character
/// that was pressed.
public CharacterEventArgs(char character, int lParam)
: base(character)
{
_character = character;
_lParam = lParam;
}
///
/// The keyboard character that was pressed.
///
public char Character
{
get { return _character; }
}
///
/// Extra information on the keyboard character that was pressed.
///
public int Param
{
get { return _lParam; }
}
///
/// The repeat count for the current message. The value is the number of
/// times the keystroke is auto-repeated as a result of the user holding
/// down the key. If the keystroke is held long enough, multiple messages
/// are sent. However, the repeat count is not cumulative.
///
public int RepeatCount
{
get { return _lParam & 0xffff; }
}
///
/// Indicates whether the key is an extended key, such as the right-hand
/// ALT and CTRL keys that appear on an enhanced 101- or 102-key keyboard.
/// The value is true if it is an extended key; otherwise, it is false.
///
public bool ExtendedKey
{
get { return (_lParam & (1 << 24)) > 0; }
}
///
/// The context code. The value is true if the ALT key is held down while
/// the key is pressed; otherwise, the value is false.
///
public bool AltPressed
{
get { return (_lParam & (1 << 29)) > 0; }
}
///
/// The previous key state. The value is true if the key is down before
/// the message is sent, or it is false if the key is up.
///
public bool PreviousState
{
get { return (_lParam & (1 << 30)) > 0; }
}
///
/// The transition state. The value is true if the key is being released,
/// or it is false if the key is being pressed.
///
public bool TransitionState
{
get { return (_lParam & (1 << 31)) > 0; }
}
///
/// The keyboard character that was pressed.
///
readonly char _character;
///
/// Extra information on the keyboard character that was pressed.
///
readonly int _lParam;
}
}