// 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; } }