You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

366 lines
18 KiB

// This code is distributed under MIT license.
// Copyright (c) 2015 George Mamaladze
// See license.txt or http://opensource.org/licenses/mit-license.php
using System;
using System.Runtime.InteropServices;
using System.Text;
using System.Windows.Forms;
using Gma.System.MouseKeyHook.Implementation;
namespace Gma.System.MouseKeyHook.WinApi
{
internal static class KeyboardNativeMethods
{
//values from Winuser.h in Microsoft SDK.
public const byte VK_SHIFT = 0x10;
public const byte VK_CAPITAL = 0x14;
public const byte VK_NUMLOCK = 0x90;
public const byte VK_LSHIFT = 0xA0;
public const byte VK_RSHIFT = 0xA1;
public const byte VK_LCONTROL = 0xA2;
public const byte VK_RCONTROL = 0xA3;
public const byte VK_LMENU = 0xA4;
public const byte VK_RMENU = 0xA5;
public const byte VK_LWIN = 0x5B;
public const byte VK_RWIN = 0x5C;
public const byte VK_SCROLL = 0x91;
public const byte VK_INSERT = 0x2D;
//may be possible to use these aggregates instead of L and R separately (untested)
public const byte VK_CONTROL = 0x11;
public const byte VK_MENU = 0x12;
public const byte VK_PACKET = 0xE7;
//Used to pass Unicode characters as if they were keystrokes. The VK_PACKET key is the low word of a 32-bit Virtual Key value used for non-keyboard input methods
private static int lastVirtualKeyCode = 0;
private static int lastScanCode = 0;
private static byte[] lastKeyState = new byte[255];
private static bool lastIsDead = false;
/// <summary>
/// Translates a virtual key to its character equivalent using the current keyboard layout without knowing the
/// scancode in advance.
/// </summary>
/// <param name="virtualKeyCode"></param>
/// <param name="fuState"></param>
/// <param name="chars"></param>
/// <returns></returns>
internal static void TryGetCharFromKeyboardState(int virtualKeyCode, int fuState, out char[] chars)
{
var dwhkl = GetActiveKeyboard();
int scanCode = MapVirtualKeyEx(virtualKeyCode, (int) MapType.MAPVK_VK_TO_VSC, dwhkl);
TryGetCharFromKeyboardState(virtualKeyCode, scanCode, fuState, dwhkl, out chars);
}
/// <summary>
/// Translates a virtual key to its character equivalent using the current keyboard layout
/// </summary>
/// <param name="virtualKeyCode"></param>
/// <param name="scanCode"></param>
/// <param name="fuState"></param>
/// <param name="chars"></param>
/// <returns></returns>
internal static void TryGetCharFromKeyboardState(int virtualKeyCode, int scanCode, int fuState, out char[] chars)
{
var dwhkl = GetActiveKeyboard(); //get the active keyboard layout
TryGetCharFromKeyboardState(virtualKeyCode, scanCode, fuState, dwhkl, out chars);
}
/// <summary>
/// Translates a virtual key to its character equivalent using a specified keyboard layout
/// </summary>
/// <param name="virtualKeyCode"></param>
/// <param name="scanCode"></param>
/// <param name="fuState"></param>
/// <param name="dwhkl"></param>
/// <param name="chars"></param>
/// <returns></returns>
internal static void TryGetCharFromKeyboardState(int virtualKeyCode, int scanCode, int fuState, IntPtr dwhkl, out char[] chars)
{
StringBuilder pwszBuff = new StringBuilder(64);
KeyboardState keyboardState = KeyboardState.GetCurrent();
byte[] currentKeyboardState = keyboardState.GetNativeState();
bool isDead = false;
if (keyboardState.IsDown(Keys.ShiftKey))
currentKeyboardState[(byte) Keys.ShiftKey] = 0x80;
if (keyboardState.IsToggled(Keys.CapsLock))
currentKeyboardState[(byte) Keys.CapsLock] = 0x01;
var relevantChars = ToUnicodeEx(virtualKeyCode, scanCode, currentKeyboardState, pwszBuff, pwszBuff.Capacity, fuState, dwhkl);
switch (relevantChars)
{
case -1:
isDead = true;
ClearKeyboardBuffer(virtualKeyCode, scanCode, dwhkl);
chars = null;
break;
case 0:
chars = null;
break;
case 1:
if (pwszBuff.Length > 0) chars = new[] { pwszBuff[0] };
else chars = null;
break;
// Two or more (only two of them is relevant)
default:
if (pwszBuff.Length > 1) chars = new[] { pwszBuff[0], pwszBuff[1] };
else chars = new[] { pwszBuff[0] };
break;
}
if (lastVirtualKeyCode != 0 && lastIsDead)
{
if (chars != null)
{
StringBuilder sbTemp = new StringBuilder(5);
ToUnicodeEx(lastVirtualKeyCode, lastScanCode, lastKeyState, sbTemp, sbTemp.Capacity, 0, dwhkl);
lastIsDead = false;
lastVirtualKeyCode = 0;
}
return;
}
lastScanCode = scanCode;
lastVirtualKeyCode = virtualKeyCode;
lastIsDead = isDead;
lastKeyState = (byte[]) currentKeyboardState.Clone();
}
private static void ClearKeyboardBuffer(int vk, int sc, IntPtr hkl)
{
var sb = new StringBuilder(10);
int rc;
do
{
byte[] lpKeyStateNull = new Byte[255];
rc = ToUnicodeEx(vk, sc, lpKeyStateNull, sb, sb.Capacity, 0, hkl);
} while (rc < 0);
}
/// <summary>
/// Gets the input locale identifier for the active application's thread. Using this combined with the ToUnicodeEx and
/// MapVirtualKeyEx enables Windows to properly translate keys based on the keyboard layout designated for the
/// application.
/// </summary>
/// <returns>HKL</returns>
private static IntPtr GetActiveKeyboard()
{
IntPtr hActiveWnd = ThreadNativeMethods.GetForegroundWindow(); //handle to focused window
int dwProcessId;
int hCurrentWnd = ThreadNativeMethods.GetWindowThreadProcessId(hActiveWnd, out dwProcessId);
//thread of focused window
return GetKeyboardLayout(hCurrentWnd); //get the layout identifier for the thread whose window is focused
}
/// <summary>
/// The ToAscii function translates the specified virtual-key code and keyboard
/// state to the corresponding character or characters. The function translates the code
/// using the input language and physical keyboard layout identified by the keyboard layout handle.
/// </summary>
/// <param name="uVirtKey">
/// [in] Specifies the virtual-key code to be translated.
/// </param>
/// <param name="uScanCode">
/// [in] Specifies the hardware scan code of the key to be translated.
/// The high-order bit of this value is set if the key is up (not pressed).
/// </param>
/// <param name="lpbKeyState">
/// [in] Pointer to a 256-byte array that contains the current keyboard state.
/// Each element (byte) in the array contains the state of one key.
/// If the high-order bit of a byte is set, the key is down (pressed).
/// The low bit, if set, indicates that the key is toggled on. In this function,
/// only the toggle bit of the CAPS LOCK key is relevant. The toggle state
/// of the NUM LOCK and SCROLL LOCK keys is ignored.
/// </param>
/// <param name="lpwTransKey">
/// [out] Pointer to the buffer that receives the translated character or characters.
/// </param>
/// <param name="fuState">
/// [in] Specifies whether a menu is active. This parameter must be 1 if a menu is active, or 0 otherwise.
/// </param>
/// <returns>
/// If the specified key is a dead key, the return value is negative. Otherwise, it is one of the following values.
/// Value Meaning
/// 0 The specified virtual key has no translation for the current state of the keyboard.
/// 1 One character was copied to the buffer.
/// 2 Two characters were copied to the buffer. This usually happens when a dead-key character
/// (accent or diacritic) stored in the keyboard layout cannot be composed with the specified
/// virtual key to form a single character.
/// </returns>
/// <remarks>
/// http://msdn.microsoft.com/library/default.asp?url=/library/en-us/winui/winui/windowsuserinterface/userinput/keyboardinput/keyboardinputreference/keyboardinputfunctions/toascii.asp
/// </remarks>
[Obsolete("Use ToUnicodeEx instead")]
[DllImport("user32.dll")]
public static extern int ToAscii(
int uVirtKey,
int uScanCode,
byte[] lpbKeyState,
byte[] lpwTransKey,
int fuState);
/// <summary>
/// Translates the specified virtual-key code and keyboard state to the corresponding Unicode character or characters.
/// </summary>
/// <param name="wVirtKey">[in] The virtual-key code to be translated.</param>
/// <param name="wScanCode">
/// [in] The hardware scan code of the key to be translated. The high-order bit of this value is
/// set if the key is up.
/// </param>
/// <param name="lpKeyState">
/// [in, optional] A pointer to a 256-byte array that contains the current keyboard state. Each
/// element (byte) in the array contains the state of one key. If the high-order bit of a byte is set, the key is down.
/// </param>
/// <param name="pwszBuff">
/// [out] The buffer that receives the translated Unicode character or characters. However, this
/// buffer may be returned without being null-terminated even though the variable name suggests that it is
/// null-terminated.
/// </param>
/// <param name="cchBuff">[in] The size, in characters, of the buffer pointed to by the pwszBuff parameter.</param>
/// <param name="wFlags">
/// [in] The behavior of the function. If bit 0 is set, a menu is active. Bits 1 through 31 are
/// reserved.
/// </param>
/// <param name="dwhkl">The input locale identifier used to translate the specified code.</param>
/// <returns>
/// -1 &lt;= return &lt;= n
/// <list type="bullet">
/// <item>
/// -1 = The specified virtual key is a dead-key character (accent or diacritic). This value is returned
/// regardless of the keyboard layout, even if several characters have been typed and are stored in the
/// keyboard state. If possible, even with Unicode keyboard layouts, the function has written a spacing version
/// of the dead-key character to the buffer specified by pwszBuff. For example, the function writes the
/// character SPACING ACUTE (0x00B4), rather than the character NON_SPACING ACUTE (0x0301).
/// </item>
/// <item>
/// 0 = The specified virtual key has no translation for the current state of the keyboard. Nothing was
/// written to the buffer specified by pwszBuff.
/// </item>
/// <item> 1 = One character was written to the buffer specified by pwszBuff.</item>
/// <item>
/// n = Two or more characters were written to the buffer specified by pwszBuff. The most common cause
/// for this is that a dead-key character (accent or diacritic) stored in the keyboard layout could not be
/// combined with the specified virtual key to form a single character. However, the buffer may contain more
/// characters than the return value specifies. When this happens, any extra characters are invalid and should
/// be ignored.
/// </item>
/// </list>
/// </returns>
[DllImport("user32.dll")]
public static extern int ToUnicodeEx(int wVirtKey,
int wScanCode,
byte[] lpKeyState,
[Out, MarshalAs(UnmanagedType.LPWStr, SizeConst = 64)] StringBuilder pwszBuff,
int cchBuff,
int wFlags,
IntPtr dwhkl);
/// <summary>
/// The GetKeyboardState function copies the status of the 256 virtual keys to the
/// specified buffer.
/// </summary>
/// <param name="pbKeyState">
/// [in] Pointer to a 256-byte array that contains keyboard key states.
/// </param>
/// <returns>
/// If the function succeeds, the return value is nonzero.
/// If the function fails, the return value is zero. To get extended error information, call GetLastError.
/// </returns>
/// <remarks>
/// http://msdn.microsoft.com/library/default.asp?url=/library/en-us/winui/winui/windowsuserinterface/userinput/keyboardinput/keyboardinputreference/keyboardinputfunctions/toascii.asp
/// </remarks>
[DllImport("user32.dll")]
public static extern int GetKeyboardState(byte[] pbKeyState);
/// <summary>
/// The GetKeyState function retrieves the status of the specified virtual key. The status specifies whether the key is
/// up, down, or toggled
/// (on, off—alternating each time the key is pressed).
/// </summary>
/// <param name="vKey">
/// [in] Specifies a virtual key. If the desired virtual key is a letter or digit (A through Z, a through z, or 0
/// through 9), nVirtKey must be set to the ASCII value of that character. For other keys, it must be a virtual-key
/// code.
/// </param>
/// <returns>
/// The return value specifies the status of the specified virtual key, as follows:
/// If the high-order bit is 1, the key is down; otherwise, it is up.
/// If the low-order bit is 1, the key is toggled. A key, such as the CAPS LOCK key, is toggled if it is turned on. The
/// key is off and untoggled if the low-order bit is 0. A toggle key's indicator light (if any) on the keyboard will be
/// on when the key is toggled, and off when the key is untoggled.
/// </returns>
/// <remarks>http://msdn.microsoft.com/en-us/library/ms646301.aspx</remarks>
[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
public static extern short GetKeyState(int vKey);
/// <summary>
/// Translates (maps) a virtual-key code into a scan code or character value, or translates a scan code into a
/// virtual-key code.
/// </summary>
/// <param name="uCode">
/// [in] The virtual key code or scan code for a key. How this value is interpreted depends on the
/// value of the uMapType parameter.
/// </param>
/// <param name="uMapType">
/// [in] The translation to be performed. The value of this parameter depends on the value of the
/// uCode parameter.
/// </param>
/// <param name="dwhkl">[in] The input locale identifier used to translate the specified code.</param>
/// <returns></returns>
[DllImport("user32.dll", CharSet = CharSet.Auto)]
internal static extern int MapVirtualKeyEx(int uCode, int uMapType, IntPtr dwhkl);
/// <summary>
/// Retrieves the active input locale identifier (formerly called the keyboard layout) for the specified thread.
/// If the idThread parameter is zero, the input locale identifier for the active thread is returned.
/// </summary>
/// <param name="dwLayout">[in] The identifier of the thread to query, or 0 for the current thread. </param>
/// <returns>
/// The return value is the input locale identifier for the thread. The low word contains a Language Identifier for the
/// input
/// language and the high word contains a device handle to the physical layout of the keyboard.
/// </returns>
[DllImport("user32.dll", CharSet = CharSet.Auto)]
internal static extern IntPtr GetKeyboardLayout(int dwLayout);
/// <summary>
/// MapVirtualKeys uMapType
/// </summary>
internal enum MapType
{
/// <summary>
/// uCode is a virtual-key code and is translated into an unshifted character value in the low-order word of the return
/// value. Dead keys (diacritics) are indicated by setting the top bit of the return value. If there is no translation,
/// the function returns 0.
/// </summary>
MAPVK_VK_TO_VSC,
/// <summary>
/// uCode is a virtual-key code and is translated into a scan code. If it is a virtual-key code that does not
/// distinguish between left- and right-hand keys, the left-hand scan code is returned. If there is no translation, the
/// function returns 0.
/// </summary>
MAPVK_VSC_TO_VK,
/// <summary>
/// uCode is a scan code and is translated into a virtual-key code that does not distinguish between left- and
/// right-hand keys. If there is no translation, the function returns 0.
/// </summary>
MAPVK_VK_TO_CHAR,
/// <summary>
/// uCode is a scan code and is translated into a virtual-key code that distinguishes between left- and right-hand
/// keys. If there is no translation, the function returns 0.
/// </summary>
MAPVK_VSC_TO_VK_EX
}
}
}