diff --git a/Pilz.Input/KeyboardListener.cs b/Pilz.Input/KeyboardListener.cs deleted file mode 100644 index 456dcfc..0000000 --- a/Pilz.Input/KeyboardListener.cs +++ /dev/null @@ -1,431 +0,0 @@ -using System; -using System.Diagnostics; -using System.Runtime.InteropServices; -using System.Runtime.CompilerServices; -using System.Windows.Input; -using System.Windows.Threading; -using System.Collections.Generic; - -namespace Pilz.Input -{ - /// - /// Listens keyboard globally. - /// - /// Uses WH_KEYBOARD_LL. - /// - public class KeyboardListener : IDisposable - { - /// - /// Creates global keyboard listener. - /// - public KeyboardListener() - { - // Dispatcher thread handling the KeyDown/KeyUp events. - this.dispatcher = Dispatcher.CurrentDispatcher; - - // We have to store the LowLevelKeyboardProc, so that it is not garbage collected runtime - hookedLowLevelKeyboardProc = (InterceptKeys.LowLevelKeyboardProc)LowLevelKeyboardProc; - - // Set the hook - hookId = InterceptKeys.SetHook(hookedLowLevelKeyboardProc); - - // Assign the asynchronous callback event - hookedKeyboardCallbackAsync = new KeyboardCallbackAsync(KeyboardListener_KeyboardCallbackAsync); - } - - private Dispatcher dispatcher; - - /// - /// Destroys global keyboard listener. - /// - ~KeyboardListener() - { - Dispose(); - } - - /// - /// Fired when any of the keys is pressed down. - /// - public event RawKeyEventHandler KeyDown; - - /// - /// Fired when any of the keys is released. - /// - public event RawKeyEventHandler KeyUp; - - #region Inner workings - - /// - /// Hook ID - /// - private IntPtr hookId = IntPtr.Zero; - - /// - /// Asynchronous callback hook. - /// - /// Character - /// Keyboard event - /// VKCode - private delegate void KeyboardCallbackAsync(InterceptKeys.KeyEvent keyEvent, int vkCode, string character); - - /// - /// Actual callback hook. - /// - /// Calls asynchronously the asyncCallback. - /// - /// - /// - /// - /// - [MethodImpl(MethodImplOptions.NoInlining)] - private IntPtr LowLevelKeyboardProc(int nCode, UIntPtr wParam, IntPtr lParam) - { - string chars = ""; - - if (nCode >= 0) - if (wParam.ToUInt32() == (int)InterceptKeys.KeyEvent.WM_KEYDOWN || - wParam.ToUInt32() == (int)InterceptKeys.KeyEvent.WM_KEYUP || - wParam.ToUInt32() == (int)InterceptKeys.KeyEvent.WM_SYSKEYDOWN || - wParam.ToUInt32() == (int)InterceptKeys.KeyEvent.WM_SYSKEYUP) - { - // Captures the character(s) pressed only on WM_KEYDOWN - chars = InterceptKeys.VKCodeToString((uint)Marshal.ReadInt32(lParam), - (wParam.ToUInt32() == (int)InterceptKeys.KeyEvent.WM_KEYDOWN || - wParam.ToUInt32() == (int)InterceptKeys.KeyEvent.WM_SYSKEYDOWN)); - - hookedKeyboardCallbackAsync.BeginInvoke((InterceptKeys.KeyEvent)wParam.ToUInt32(), Marshal.ReadInt32(lParam), chars, null, null); - } - - return InterceptKeys.CallNextHookEx(hookId, nCode, wParam, lParam); - } - - /// - /// Event to be invoked asynchronously (BeginInvoke) each time key is pressed. - /// - private KeyboardCallbackAsync hookedKeyboardCallbackAsync; - - /// - /// Contains the hooked callback in runtime. - /// - private InterceptKeys.LowLevelKeyboardProc hookedLowLevelKeyboardProc; - - /// - /// HookCallbackAsync procedure that calls accordingly the KeyDown or KeyUp events. - /// - /// Keyboard event - /// VKCode - /// Character as string. - void KeyboardListener_KeyboardCallbackAsync(InterceptKeys.KeyEvent keyEvent, int vkCode, string character) - { - switch (keyEvent) - { - // KeyDown events - case InterceptKeys.KeyEvent.WM_KEYDOWN: - if (KeyDown != null) - dispatcher.BeginInvoke(new RawKeyEventHandler(KeyDown), this, new RawKeyEventArgs(vkCode, false, character)); - break; - case InterceptKeys.KeyEvent.WM_SYSKEYDOWN: - if (KeyDown != null) - dispatcher.BeginInvoke(new RawKeyEventHandler(KeyDown), this, new RawKeyEventArgs(vkCode, true, character)); - break; - - // KeyUp events - case InterceptKeys.KeyEvent.WM_KEYUP: - if (KeyUp != null) - dispatcher.BeginInvoke(new RawKeyEventHandler(KeyUp), this, new RawKeyEventArgs(vkCode, false, character)); - break; - case InterceptKeys.KeyEvent.WM_SYSKEYUP: - if (KeyUp != null) - dispatcher.BeginInvoke(new RawKeyEventHandler(KeyUp), this, new RawKeyEventArgs(vkCode, true, character)); - break; - - default: - break; - } - } - - #endregion - - #region IDisposable Members - - /// - /// Disposes the hook. - /// This call is required as it calls the UnhookWindowsHookEx. - /// - public void Dispose() - { - InterceptKeys.UnhookWindowsHookEx(hookId); - } - - #endregion - } - - /// - /// Raw KeyEvent arguments. - /// - public class RawKeyEventArgs : EventArgs - { - /// - /// VKCode of the key. - /// - public int VKCode; - - /// - /// WPF Key of the key. - /// - public Key Key; - - /// - /// Is the hitted key system key. - /// - public bool IsSysKey; - - /// - /// Convert to string. - /// - /// Returns string representation of this key, if not possible empty string is returned. - public override string ToString() - { - return Character; - } - - /// - /// Unicode character of key pressed. - /// - public string Character; - - /// - /// Create raw keyevent arguments. - /// - /// - /// - /// Character - public RawKeyEventArgs(int VKCode, bool isSysKey, string Character) - { - this.VKCode = VKCode; - this.IsSysKey = isSysKey; - this.Character = Character; - this.Key = System.Windows.Input.KeyInterop.KeyFromVirtualKey(VKCode); - } - - } - - /// - /// Raw keyevent handler. - /// - /// sender - /// raw keyevent arguments - public delegate void RawKeyEventHandler(object sender, RawKeyEventArgs args); - - #region WINAPI Helper class - /// - /// Winapi Key interception helper class. - /// - internal static class InterceptKeys - { - public delegate IntPtr LowLevelKeyboardProc(int nCode, UIntPtr wParam, IntPtr lParam); - public static int WH_KEYBOARD_LL = 13; - - /// - /// Key event - /// - public enum KeyEvent : int - { - /// - /// Key down - /// - WM_KEYDOWN = 256, - - /// - /// Key up - /// - WM_KEYUP = 257, - - /// - /// System key up - /// - WM_SYSKEYUP = 261, - - /// - /// System key down - /// - WM_SYSKEYDOWN = 260 - } - - public static IntPtr SetHook(LowLevelKeyboardProc proc) - { - using (Process curProcess = Process.GetCurrentProcess()) - using (ProcessModule curModule = curProcess.MainModule) - { - return SetWindowsHookEx(WH_KEYBOARD_LL, proc, GetModuleHandle(curModule.ModuleName), 0); - } - } - - [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] - public static extern IntPtr SetWindowsHookEx(int idHook, LowLevelKeyboardProc lpfn, IntPtr hMod, uint dwThreadId); - - [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] - [return: MarshalAs(UnmanagedType.Bool)] - public static extern bool UnhookWindowsHookEx(IntPtr hhk); - - [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] - public static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, UIntPtr wParam, IntPtr lParam); - - [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] - public static extern IntPtr GetModuleHandle(string lpModuleName); - - #region Convert VKCode to string - // Note: Sometimes single VKCode represents multiple chars, thus string. - // E.g. typing "^1" (notice that when pressing 1 the both characters appear, - // because of this behavior, "^" is called dead key) - - [DllImport("user32.dll")] - private static extern int ToUnicodeEx(uint wVirtKey, uint wScanCode, byte[] lpKeyState, [Out, MarshalAs(UnmanagedType.LPWStr)] System.Text.StringBuilder pwszBuff, int cchBuff, uint wFlags, IntPtr dwhkl); - - [DllImport("user32.dll")] - private static extern bool GetKeyboardState(byte[] lpKeyState); - - [DllImport("user32.dll")] - private static extern uint MapVirtualKeyEx(uint uCode, uint uMapType, IntPtr dwhkl); - - [DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)] - private static extern IntPtr GetKeyboardLayout(uint dwLayout); - - [DllImport("User32.dll")] - private static extern IntPtr GetForegroundWindow(); - - [DllImport("User32.dll")] - private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId); - - [DllImport("user32.dll")] - private static extern bool AttachThreadInput(uint idAttach, uint idAttachTo, bool fAttach); - - [DllImport("kernel32.dll")] - private static extern uint GetCurrentThreadId(); - - private static uint lastVKCode = 0; - private static uint lastScanCode = 0; - private static byte[] lastKeyState = new byte[255]; - private static bool lastIsDead = false; - - /// - /// Convert VKCode to Unicode. - /// isKeyDown is required for because of keyboard state inconsistencies! - /// - /// VKCode - /// Is the key down event? - /// String representing single unicode character. - public static string VKCodeToString(uint VKCode, bool isKeyDown) - { - // ToUnicodeEx needs StringBuilder, it populates that during execution. - System.Text.StringBuilder sbString = new System.Text.StringBuilder(5); - - byte[] bKeyState = new byte[255]; - bool bKeyStateStatus; - bool isDead = false; - - // Gets the current windows window handle, threadID, processID - IntPtr currentHWnd = GetForegroundWindow(); - uint currentProcessID; - uint currentWindowThreadID = GetWindowThreadProcessId(currentHWnd, out currentProcessID); - - // This programs Thread ID - uint thisProgramThreadId = GetCurrentThreadId(); - - // Attach to active thread so we can get that keyboard state - if (AttachThreadInput(thisProgramThreadId, currentWindowThreadID, true)) - { - // Current state of the modifiers in keyboard - bKeyStateStatus = GetKeyboardState(bKeyState); - - // Detach - AttachThreadInput(thisProgramThreadId, currentWindowThreadID, false); - } - else - { - // Could not attach, perhaps it is this process? - bKeyStateStatus = GetKeyboardState(bKeyState); - } - - // On failure we return empty string. - if (!bKeyStateStatus) - return ""; - - // Gets the layout of keyboard - IntPtr HKL = GetKeyboardLayout(currentWindowThreadID); - - // Maps the virtual keycode - uint lScanCode = MapVirtualKeyEx(VKCode, 0, HKL); - - // Keyboard state goes inconsistent if this is not in place. In other words, we need to call above commands in UP events also. - if (!isKeyDown) - return ""; - - // Converts the VKCode to unicode - int relevantKeyCountInBuffer = ToUnicodeEx(VKCode, lScanCode, bKeyState, sbString, sbString.Capacity, (uint)0, HKL); - - string ret = ""; - - switch (relevantKeyCountInBuffer) - { - // Dead keys (^,`...) - case -1: - isDead = true; - - // We must clear the buffer because ToUnicodeEx messed it up, see below. - ClearKeyboardBuffer(VKCode, lScanCode, HKL); - break; - - case 0: - break; - - // Single character in buffer - case 1: - ret = sbString[0].ToString(); - break; - - // Two or more (only two of them is relevant) - case 2: - default: - ret = sbString.ToString().Substring(0, 2); - break; - } - - // We inject the last dead key back, since ToUnicodeEx removed it. - // More about this peculiar behavior see e.g: - // http://www.experts-exchange.com/Programming/System/Windows__Programming/Q_23453780.html - // http://blogs.msdn.com/michkap/archive/2005/01/19/355870.aspx - // http://blogs.msdn.com/michkap/archive/2007/10/27/5717859.aspx - if (lastVKCode != 0 && lastIsDead) - { - System.Text.StringBuilder sbTemp = new System.Text.StringBuilder(5); - ToUnicodeEx(lastVKCode, lastScanCode, lastKeyState, sbTemp, sbTemp.Capacity, (uint)0, HKL); - lastVKCode = 0; - - return ret; - } - - // Save these - lastScanCode = lScanCode; - lastVKCode = VKCode; - lastIsDead = isDead; - lastKeyState = (byte[])bKeyState.Clone(); - - return ret; - } - - private static void ClearKeyboardBuffer(uint vk, uint sc, IntPtr hkl) - { - System.Text.StringBuilder sb = new System.Text.StringBuilder(10); - - int rc; - do - { - byte[] lpKeyStateNull = new Byte[255]; - rc = ToUnicodeEx(vk, sc, lpKeyStateNull, sb, sb.Capacity, 0, hkl); - } while (rc < 0); - } - #endregion - } - #endregion -} \ No newline at end of file diff --git a/Pilz.Input/Pilz.Input.csproj b/Pilz.Input/Pilz.Input.csproj deleted file mode 100644 index f476fc7..0000000 --- a/Pilz.Input/Pilz.Input.csproj +++ /dev/null @@ -1,8 +0,0 @@ - - - - net5.0-windows7.0 - true - - - diff --git a/Pilz.sln b/Pilz.sln index 746f885..548cbb6 100644 --- a/Pilz.sln +++ b/Pilz.sln @@ -31,8 +31,6 @@ Project("{778DAE3C-4631-46EA-AA77-85C1314464D9}") = "Pilz.Networking", "Pilz.Net EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Pilz.Cryptography", "Pilz.Cryptography\Pilz.Cryptography.csproj", "{3F5988E6-439E-4A9D-B2C6-47EFFB161AC6}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Pilz.Input", "Pilz.Input\Pilz.Input.csproj", "{6F52431D-5D7D-4A6F-AF88-29575F4196B3}" -EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -153,14 +151,6 @@ Global {3F5988E6-439E-4A9D-B2C6-47EFFB161AC6}.Release|Any CPU.Build.0 = Release|Any CPU {3F5988E6-439E-4A9D-B2C6-47EFFB161AC6}.Release|x86.ActiveCfg = Release|Any CPU {3F5988E6-439E-4A9D-B2C6-47EFFB161AC6}.Release|x86.Build.0 = Release|Any CPU - {6F52431D-5D7D-4A6F-AF88-29575F4196B3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {6F52431D-5D7D-4A6F-AF88-29575F4196B3}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6F52431D-5D7D-4A6F-AF88-29575F4196B3}.Debug|x86.ActiveCfg = Debug|Any CPU - {6F52431D-5D7D-4A6F-AF88-29575F4196B3}.Debug|x86.Build.0 = Debug|Any CPU - {6F52431D-5D7D-4A6F-AF88-29575F4196B3}.Release|Any CPU.ActiveCfg = Release|Any CPU - {6F52431D-5D7D-4A6F-AF88-29575F4196B3}.Release|Any CPU.Build.0 = Release|Any CPU - {6F52431D-5D7D-4A6F-AF88-29575F4196B3}.Release|x86.ActiveCfg = Release|Any CPU - {6F52431D-5D7D-4A6F-AF88-29575F4196B3}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE