diff --git a/.gitignore b/.gitignore index d9a98a5..896bfac 100644 --- a/.gitignore +++ b/.gitignore @@ -7,10 +7,12 @@ # or wget: # wget --no-check-certificate http://gist.githubusercontent.com/kmorcinek/2710267/raw/.gitignore +RawInput.Sharp/* + # User-specific files -*.suo -*.user -*.sln.docstates +.suo +.user +.sln.docstates # Build results [Dd]ebug/ diff --git a/App.xaml b/App.xaml new file mode 100755 index 0000000..7a61778 --- /dev/null +++ b/App.xaml @@ -0,0 +1,9 @@ + + + + + diff --git a/App.xaml.cs b/App.xaml.cs new file mode 100755 index 0000000..ca244cc --- /dev/null +++ b/App.xaml.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Configuration; +using System.Data; +using System.Linq; +using System.Threading.Tasks; +using System.Windows; + +namespace MacroBoard +{ + /// + /// Interaction logic for App.xaml + /// + public partial class App : Application + { + } +} diff --git a/AssemblyInfo.cs b/AssemblyInfo.cs new file mode 100755 index 0000000..74087a1 --- /dev/null +++ b/AssemblyInfo.cs @@ -0,0 +1,10 @@ +using System.Windows; + +[assembly: ThemeInfo( + ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located + //(used if a resource is not found in the page, + // or application resource dictionaries) + ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located + //(used if a resource is not found in the page, + // app, or any theme specific resource dictionaries) +)] diff --git a/Config.cs b/Config.cs new file mode 100755 index 0000000..5a63aeb --- /dev/null +++ b/Config.cs @@ -0,0 +1,107 @@ +using Linearstar.Windows.RawInput; +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Windows; +using System.Xml; +using System.Xml.Serialization; +using System.Linq; + +namespace MacroBoard +{ + class Config + { + public static readonly string ConfigDir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Configs"); + public static readonly string AppConfigDir = Path.Combine(ConfigDir, "Application"); + public static readonly string ModConfigDir = Path.Combine(ConfigDir, "Mods"); + + public static readonly string AppConfigDevicesPath = Path.Combine(AppConfigDir, "Devices.cfg"); + + /// + /// Saves and updates the device config if a new device is found + /// + /// The DeviceConfig object being stored + /// Path where the configuration file will be saved + public static void SaveDevices(DeviceConfig deviceConfig, Stream saveStream) + { + XmlSerializer xs = new XmlSerializer(typeof(DeviceConfig)); + + xs.Serialize(saveStream, deviceConfig); + } + + public static DeviceConfig ReadDevices(string savePath) + { + XmlSerializer xs = new XmlSerializer(typeof(DeviceConfig)); + + using (TextReader tw = new StreamReader(savePath)) + { + return (DeviceConfig)xs.Deserialize(tw); + } + } + + //??? + public static DeviceConfig GetDevices() + { + return new DeviceConfig(/*new bool(),*/ new List()); + } + + //??? + public static bool NewDevicesFound() + { + return new bool(); + } + + public static void /*DeviceConfig*/ HandleDeviceConfig(List keyboardList) + { + if (File.Exists(AppConfigDevicesPath)) + { + DeviceConfig dc = ReadDevices(AppConfigDevicesPath); + + //New Keyboard(s) Found + if (dc.Keyboards.Count < keyboardList.Count) + { + List AddedKeyboardsList = new List(); + + AddedKeyboardsList = keyboardList.Where(x => !dc.Keyboards.Any(y => y.KeyboardName == x.KeyboardName)).ToList(); + + + foreach (var keyboard in dc.Keyboards) + System.Diagnostics.Debug.WriteLine("dc.Keyboards: " + keyboard.KeyboardName); + + foreach (var keyboard in keyboardList) + { + System.Diagnostics.Debug.WriteLine("keyboardList: " + keyboard.KeyboardName); + } + + string addedKeyboards = ""; + foreach (var keyboard in AddedKeyboardsList) + addedKeyboards += $"\r\n{keyboard.KeyboardName}\r\n"; + + MessageBox.Show($"New devices have been found.\r\nPress \"Yes\" if you would like to configure these devices.\r\n\r\nAdded Devices:\r\n{addedKeyboards}", "New Devices Found", MessageBoxButton.YesNo, MessageBoxImage.Question); + } + //Old Keyboard(s) Removed + else if (dc.Keyboards.Count > keyboardList.Count) + { + List RemovedKeyboardsList = new List(); + + RemovedKeyboardsList = dc.Keyboards.Where(x => !keyboardList.Any(y => x.KeyboardName == y.KeyboardName)).ToList(); + + string removedKeyboards = ""; + foreach (var keyboard in RemovedKeyboardsList) + removedKeyboards += $"\r\n{keyboard.KeyboardName}\r\n"; + + MessageBox.Show($"Old devices have been removed.\r\nRemoved Devices:\r\n{removedKeyboards}", "Old Devices Removed", MessageBoxButton.YesNo, MessageBoxImage.Question); + } + } + else + { + using (var f = File.Create(AppConfigDevicesPath)) + { + DeviceConfig dc = new DeviceConfig(keyboardList); + SaveDevices(dc, f); + } + } + } + } +} diff --git a/ConfigClasses.cs b/ConfigClasses.cs new file mode 100755 index 0000000..d14f797 --- /dev/null +++ b/ConfigClasses.cs @@ -0,0 +1,43 @@ +using System; +using System.Collections.Generic; + +namespace MacroBoard +{ + public class DeviceConfig + { + public DeviceConfig(List keyboards) + { + Keyboards = keyboards; + } + + //Need a blank constructor to allow for Xml Deserialization + private DeviceConfig() { } + + + public List Keyboards { get; set; } + } + + public class KeyboardConfig + { + public KeyboardConfig(string keyboardAlias, bool isMacroBoard, bool hasAutoNumLock, string keyboardName, string keyboardPath, bool isDefaultMacroBoard) + { + KeyboardAlias = keyboardAlias; + IsMacroBoard = isMacroBoard; + HasAutoNumLock = hasAutoNumLock; + KeyboardName = keyboardName; + KeyboardPath = keyboardPath; + IsDefaultMacroBoard = isDefaultMacroBoard; + } + + //Need a blank constructor to allow for Xml Deserialization + private KeyboardConfig() { } + + public string KeyboardAlias { get; set; } + public bool IsMacroBoard { get; set; } + public bool HasAutoNumLock { get; set; } + public string KeyboardName { get; set; } + public string KeyboardPath { get; set; } + + public bool IsDefaultMacroBoard { get; set; } + } +} diff --git a/DllInject.cs b/DllInject.cs new file mode 100755 index 0000000..c446074 --- /dev/null +++ b/DllInject.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Text; + +namespace MacroBoard.Inject +{ +#warning Need to get proper unmangled names in the future. + class DllInject + { + [DllImport("KeyboardHook.dll")] + public static extern int InstallHook(IntPtr hwndParent); + + [DllImport("KeyboardHook.dll")] + public static extern int UninstallHook(); + } +} diff --git a/Handler.cs b/Handler.cs new file mode 100755 index 0000000..3162f69 --- /dev/null +++ b/Handler.cs @@ -0,0 +1,141 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Reflection; +using System.Text; + +namespace MacroBoard +{ + class Handler + { + //Contains list of ModKeyCombination objects accessible by the mod's Type + public static Dictionary> ModsKeyCombinations = new Dictionary>(); + + /// + /// This is a boolean mapping a ModType to a boolean saying whether or not it is allowed to set key combinations determined by the HandlerCommunications.HandleType + /// + private static Dictionary SupportedByHandler = new Dictionary(); + + public static void SetHandlerType(dynamic modType, bool b) + { + SupportedByHandler.Add(modType, b); + } + + public static void HandleCombination(List Keys) + { + + var modDataList = ModData.ModDataList; + + for (int i = 0; i < modDataList.Count; i++) + { + //Contains list of KeyCombinations under a specific mod + var modKeyCombinationsList = ModsKeyCombinations[modDataList[i].Type]; + + for (int j = 0; j < modKeyCombinationsList.Count; j++) + { + + int keysMatching = 0; + var ModComboKeys = modKeyCombinationsList[j].Keys; + + for (int k = 0; k < ModComboKeys.Count; k++) + { + + for (int l = 0; l < Keys.Count; l++) + { + + if (KeyData.CompareToModKeybind(ModComboKeys[k], Keys[l], MainWindow.DefaultKeyboardAlias)) + { + keysMatching++; + } + } + + } + + if (keysMatching == ModComboKeys.Count) + { + Debug.WriteLine("Calling Method: " + modKeyCombinationsList[j].CallbackMethod.Method.Name); + modKeyCombinationsList[j].CallbackMethod.Invoke(); + } + } + } + } + } + + public class ModKeyCombination + { + //Convert List<[Mod Name].ModKeyComination> to List Not sure how to cast different types, so copying each invidivual primitive at a time. + public static List Convert(dynamic data) + { + List ComboList = new List(); + Debug.WriteLine(data.Count as object); + for (int i = 0; i < data.Count; i++) + { + ModKeyCombination Combo = new ModKeyCombination(); + foreach (PropertyInfo prop in data[i].GetType().GetProperties()) + { + if (prop.Name == "Keys") + { + List keyListData = new List(); + var modKeys = prop.GetValue(data[i]); + for (int j = 0; j < modKeys.Count; j++) + { + KeyData keyData = new KeyData(); + + foreach (PropertyInfo propKey in modKeys[j].GetType().GetProperties()) + { + + typeof(KeyData) + .GetProperty(propKey.Name, BindingFlags.Instance | BindingFlags.Public) + .SetValue(keyData, propKey.GetValue(modKeys[j])); + } + + keyListData.Add(keyData); + } + + Combo.Keys = keyListData; + } + else + { + typeof(ModKeyCombination) + .GetProperty(prop.Name, BindingFlags.Instance | BindingFlags.Public) + .SetValue(Combo, prop.GetValue(data[i])); + + } + + + } + + ComboList.Add(Combo); + } + + return ComboList; + } + + public static void SetModCombinations(dynamic modType, List Combinations) + { + if (!Handler.ModsKeyCombinations.ContainsKey(modType)) + { + Handler.ModsKeyCombinations.Add(modType, Combinations); + } + else + { + Handler.ModsKeyCombinations[modType] = Combinations; + } + } + + public ModKeyCombination() + { + + } + + public ModKeyCombination(List keys, Action callbackMethod) + { + Keys = keys; + CallbackMethod = callbackMethod; + + } + + + public List Keys { get; set; } + public Action CallbackMethod { get; set; } + } diff --git a/HandlerCommunications.cs b/HandlerCommunications.cs new file mode 100755 index 0000000..6d1ed2a --- /dev/null +++ b/HandlerCommunications.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace MacroBoard +{ + class HandlerCommunications + { + /// + /// Tells the Mod Handler how keys should be handled. + /// + public enum HandleType + { + /// + /// Have the mod handler (Main Application) handle all of the keys. This means the mod won't recieve "Call" method calls. + /// + HandlerOnly, + + /// + /// Have the mod handle all of the keys. This means the mod handler (Main Application) won't accept any [List<KeyCombination>, delegate] dicts. + /// + ModOnly, + + /// + /// Have both the mod handler (Main Application) and mod handle the keys. This means the mod will recieve "Call" method calls, and can send [List<KeyCombination>, delegate] dicts to the mod handler. + /// + Both + } + } +} diff --git a/KeyData.cs b/KeyData.cs new file mode 100755 index 0000000..1f53b6e --- /dev/null +++ b/KeyData.cs @@ -0,0 +1,157 @@ +using Linearstar.Windows.RawInput; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text; +using static MacroBoard.Hook.KeyboardHook; + +namespace MacroBoard +{ + + + public class KeyData + { + public KeyData() { } + + private static List Keys = new List(); + + public static void ProcessKey(KeyData data, uint WindowMessage) + { + KeyState state = KeyState.Unknown; + + if ((KeyMessages)WindowMessage == KeyMessages.WM_KEYDOWN || (KeyMessages)WindowMessage == KeyMessages.WM_SYSKEYDOWN) + { + state = KeyState.Down; + } else if ((KeyMessages)WindowMessage == KeyMessages.WM_KEYUP || (KeyMessages)WindowMessage == KeyMessages.WM_SYSKEYUP) + { + state = KeyState.Up; + } + else + { + //state is Unknown, this shouldn't happen + throw new Exception("KeyState Unknown"); + } + + if (state == KeyState.Down) + { + //boolean determining if this key is already down + bool keyAlreadyExists = false; + for (int i = 0; i < Keys.Count; i++) + { + KeyData key = Keys[i]; + if (Compare(data, key)) + { + keyAlreadyExists = true; + } + + if (!keyAlreadyExists) + Keys.Add(data); + + + + } + + Debug.WriteLine("Keys count: " + Keys.Count); + + Keys.ForEach(kbd => Debug.WriteLine(kbd.KeyboardAlias + " : (" + (VKeys)kbd.VirtualKey + ") " + kbd.VirtualKey + " : (" + (RawKeyboardFlags)kbd.Flags + ") " + kbd.Flags + " : " + kbd.ScanCode)); + Debug.WriteLine("\r\n"); + + if (state == KeyState.Up) + { + for (int i = 0; i < Keys.Count; i++) + { + KeyData key = Keys[i]; + + if (Compare(data, key)) + { + Keys.Remove(key); + } + + } + } + else + { + //Check all Key Combinations + Handler.HandleCombination(Keys); + } + } + + public static KeyData Format(string KeyboardAlias, RawInputKeyboardData data) + { + + return new KeyData( + KeyboardAlias, + (int)data.Keyboard.Flags, + data.Keyboard.ScanCode, + data.Keyboard.VirutalKey + ); + } + + public static bool Compare(KeyData key1, KeyData key2) + { + RawKeyboardFlags key1Flags = (RawKeyboardFlags)key1.Flags; + RawKeyboardFlags key2Flags = (RawKeyboardFlags)key2.Flags; + + if (key1.KeyboardAlias == key2.KeyboardAlias && + key1Flags.HasFlag(RawKeyboardFlags.KeyE0) == key2Flags.HasFlag(RawKeyboardFlags.KeyE0) && //could technically just remove the None/KeyUp flag from both and it would solve the problem simpler. + key1Flags.HasFlag(RawKeyboardFlags.KeyE1) == key2Flags.HasFlag(RawKeyboardFlags.KeyE1) && + key1.ScanCode == key2.ScanCode && + key1.VirtualKey == key2.VirtualKey) + { + return true; + } + return false; + } + + //Same as Compare, but is used specifically for comparing Keys and ModKeyCombinations specifically for KeyAlias being [Default] + public static bool CompareToModKeybind(KeyData modData, KeyData keyData, string ReplaceDefault) + { + RawKeyboardFlags key1Flags = (RawKeyboardFlags)modData.Flags; + RawKeyboardFlags key2Flags = (RawKeyboardFlags)keyData.Flags; + + if (((modData.KeyboardAlias == keyData.KeyboardAlias) || (modData.KeyboardAlias == "[Default]" && ReplaceDefault == keyData.KeyboardAlias)) && + key1Flags.HasFlag(RawKeyboardFlags.KeyE0) == key2Flags.HasFlag(RawKeyboardFlags.KeyE0) && //could technically just remove the None/KeyUp flag from both and it would solve the problem simpler. + key1Flags.HasFlag(RawKeyboardFlags.KeyE1) == key2Flags.HasFlag(RawKeyboardFlags.KeyE1) && + modData.ScanCode == keyData.ScanCode && + modData.VirtualKey == keyData.VirtualKey) + { + return true; + } + return false; + } + + public int Flags { get; set; } + public int ScanCode { get; set; } + public int VirtualKey { get; set; } + + //Custom made data + + //The string passed when the key is pressed to tell the mod what keyboard it was sent from. + public /*readonly*/ string KeyboardAlias { get; set; } + + + public KeyData(string keyboardAlias, int flags, int scanCode, int virtualKey) + { + KeyboardAlias = keyboardAlias; + Flags = flags; + ScanCode = scanCode; + VirtualKey = virtualKey; + } + } + + public enum KeyState + { + Unknown, + Down, + Up, + } + + [Flags] + public enum RawKeyboardFlags : ushort + { + None = 0, + Up = 1 << 0, + KeyE0 = 1 << 1, + KeyE1 = 1 << 2, + } +} diff --git a/KeyHandling.cs b/KeyHandling.cs new file mode 100755 index 0000000..dd403d1 --- /dev/null +++ b/KeyHandling.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace MacroBoard +{ + public class KeyHandling + { + [Flags] + public enum KeyModifiers + { + None = 0, + LShift = 1 << 0, + RShift = 1 << 1, + LControl = 1 << 2, + RControl = 1 << 3, + LAlt = 1 << 4, + RAlt = 1 << 5, + LWin = 1 << 6, + RWin = 1 << 7 + } + + + } +} diff --git a/KeyboardData.cs b/KeyboardData.cs new file mode 100755 index 0000000..6cb0e16 --- /dev/null +++ b/KeyboardData.cs @@ -0,0 +1,119 @@ +using Linearstar.Windows.RawInput; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text; +using static MacroBoard.Hook.KeyboardHook; + +namespace MacroBoard +{ + + + class KeyboardData + { + private static List ActiveData = new List(); + + public static void ProcessKey(KeyboardData data) + { + KeyState state = KeyState.Unknown; + + if ((KeyMessages)data.WindowMessage == KeyMessages.WM_KEYDOWN || (KeyMessages)data.WindowMessage == KeyMessages.WM_SYSKEYDOWN) + { + state = KeyState.Down; + } else if ((KeyMessages)data.WindowMessage == KeyMessages.WM_KEYUP || (KeyMessages)data.WindowMessage == KeyMessages.WM_SYSKEYUP) + { + state = KeyState.Up; + } + else + { + //state is Unknown, this shouldn't happen + throw new Exception("KeyState Unknown"); + } + + if (state == KeyState.Down) + { + ActiveData.Add(data); + } + + ActiveData.ForEach(kbd => Debug.WriteLine(kbd.KeyboardAlias + " : " + (VKeys)kbd.VirtualKey + " : " + kbd.Flags + " : " + kbd.ScanCode)); + Debug.WriteLine("\r\n"); + + if (state == KeyState.Up) + { + for (int i = 0; i < ActiveData.Count; i++) + { + KeyboardData kbd = ActiveData[i]; + + + RawKeyboardFlags kbdFlags = (RawKeyboardFlags)kbd.Flags; + RawKeyboardFlags dataFlags = (RawKeyboardFlags)data.Flags; + + if (kbd.KeyboardAlias == data.KeyboardAlias && + kbdFlags.HasFlag(RawKeyboardFlags.KeyE0) == dataFlags.HasFlag(RawKeyboardFlags.KeyE0) && //could technically just remove the None/KeyUp flag from both and it would solve the problem simpler. + kbdFlags.HasFlag(RawKeyboardFlags.KeyE1) == dataFlags.HasFlag(RawKeyboardFlags.KeyE1) && + kbd.ScanCode == data.ScanCode && + kbd.VirtualKey == data.VirtualKey) + { + ActiveData.Remove(kbd); + } + } + } + } + + public static KeyboardData Format(string KeyboardAlias, RawInputKeyboardData data) + { + // Using WindowMessage to determine key state + return new KeyboardData( + KeyboardAlias, + data.Keyboard.ExtraInformation, + (int)data.Keyboard.Flags, + data.Keyboard.ScanCode, + data.Keyboard.VirutalKey, + data.Keyboard.WindowMessage + ); + } + + //Passed data from "RawInputKeyboardData" + + public readonly uint ExtraInformation; + public readonly /*RawKeyboardFlags*/ int Flags; + public readonly int ScanCode; + public readonly int VirtualKey; + public readonly uint WindowMessage; + + //Custom made data + + //The string passed when the key is pressed to tell the mod what keyboard it was sent from. + public readonly string KeyboardAlias; + + //Determine if any key modifiers are presed. + //public readonly int KeyModifiers; + + + public KeyboardData(string keyboardAlias, uint extraInformation, int flags, int scanCode, int virtualKey, uint windowMessage) + { + KeyboardAlias = keyboardAlias; + ExtraInformation = extraInformation; + Flags = flags; + ScanCode = scanCode; + VirtualKey = virtualKey; + WindowMessage = windowMessage; + } + } + + public enum KeyState + { + Unknown, + Down, + Up, + } + + [Flags] + public enum RawKeyboardFlags : ushort + { + None = 0, + Up = 1 << 0, + KeyE0 = 1 << 1, + KeyE1 = 1 << 2, + } +} diff --git a/KeyboardHook.cs b/KeyboardHook.cs new file mode 100755 index 0000000..ea5af97 --- /dev/null +++ b/KeyboardHook.cs @@ -0,0 +1,448 @@ +using MacroBoard.Native; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading; +using System.Windows; +using System.Windows.Input; +using System.Windows.Interop; + +namespace MacroBoard.Hook +{ + /// + /// Class for intercepting low level keyboard hooks + /// + public class KeyboardHook + { + /// + /// Virtual Keys + /// + public enum VKeys + { + // Losely based on http://www.pinvoke.net/default.aspx/Enums/VK.html + + LBUTTON = 0x01, // Left mouse button + RBUTTON = 0x02, // Right mouse button + CANCEL = 0x03, // Control-break processing + MBUTTON = 0x04, // Middle mouse button (three-button mouse) + XBUTTON1 = 0x05, // Windows 2000/XP: X1 mouse button + XBUTTON2 = 0x06, // Windows 2000/XP: X2 mouse button + // 0x07 // Undefined + BACK = 0x08, // BACKSPACE key + TAB = 0x09, // TAB key + // 0x0A-0x0B, // Reserved + CLEAR = 0x0C, // CLEAR key + RETURN = 0x0D, // ENTER key + // 0x0E-0x0F, // Undefined + SHIFT = 0x10, // SHIFT key + CONTROL = 0x11, // CTRL key + MENU = 0x12, // ALT key + PAUSE = 0x13, // PAUSE key + CAPITAL = 0x14, // CAPS LOCK key + KANA = 0x15, // Input Method Editor (IME) Kana mode + HANGUL = 0x15, // IME Hangul mode + // 0x16, // Undefined + JUNJA = 0x17, // IME Junja mode + FINAL = 0x18, // IME final mode + HANJA = 0x19, // IME Hanja mode + KANJI = 0x19, // IME Kanji mode + // 0x1A, // Undefined + ESCAPE = 0x1B, // ESC key + CONVERT = 0x1C, // IME convert + NONCONVERT = 0x1D, // IME nonconvert + ACCEPT = 0x1E, // IME accept + MODECHANGE = 0x1F, // IME mode change request + SPACE = 0x20, // SPACEBAR + PRIOR = 0x21, // PAGE UP key + NEXT = 0x22, // PAGE DOWN key + END = 0x23, // END key + HOME = 0x24, // HOME key + LEFT = 0x25, // LEFT ARROW key + UP = 0x26, // UP ARROW key + RIGHT = 0x27, // RIGHT ARROW key + DOWN = 0x28, // DOWN ARROW key + SELECT = 0x29, // SELECT key + PRINT = 0x2A, // PRINT key + EXECUTE = 0x2B, // EXECUTE key + SNAPSHOT = 0x2C, // PRINT SCREEN key + INSERT = 0x2D, // INS key + DELETE = 0x2E, // DEL key + HELP = 0x2F, // HELP key + KEY_0 = 0x30, // 0 key + KEY_1 = 0x31, // 1 key + KEY_2 = 0x32, // 2 key + KEY_3 = 0x33, // 3 key + KEY_4 = 0x34, // 4 key + KEY_5 = 0x35, // 5 key + KEY_6 = 0x36, // 6 key + KEY_7 = 0x37, // 7 key + KEY_8 = 0x38, // 8 key + KEY_9 = 0x39, // 9 key + // 0x3A-0x40, // Undefined + KEY_A = 0x41, // A key + KEY_B = 0x42, // B key + KEY_C = 0x43, // C key + KEY_D = 0x44, // D key + KEY_E = 0x45, // E key + KEY_F = 0x46, // F key + KEY_G = 0x47, // G key + KEY_H = 0x48, // H key + KEY_I = 0x49, // I key + KEY_J = 0x4A, // J key + KEY_K = 0x4B, // K key + KEY_L = 0x4C, // L key + KEY_M = 0x4D, // M key + KEY_N = 0x4E, // N key + KEY_O = 0x4F, // O key + KEY_P = 0x50, // P key + KEY_Q = 0x51, // Q key + KEY_R = 0x52, // R key + KEY_S = 0x53, // S key + KEY_T = 0x54, // T key + KEY_U = 0x55, // U key + KEY_V = 0x56, // V key + KEY_W = 0x57, // W key + KEY_X = 0x58, // X key + KEY_Y = 0x59, // Y key + KEY_Z = 0x5A, // Z key + LWIN = 0x5B, // Left Windows key (Microsoft Natural keyboard) + RWIN = 0x5C, // Right Windows key (Natural keyboard) + APPS = 0x5D, // Applications key (Natural keyboard) + // 0x5E, // Reserved + SLEEP = 0x5F, // Computer Sleep key + NUMPAD0 = 0x60, // Numeric keypad 0 key + NUMPAD1 = 0x61, // Numeric keypad 1 key + NUMPAD2 = 0x62, // Numeric keypad 2 key + NUMPAD3 = 0x63, // Numeric keypad 3 key + NUMPAD4 = 0x64, // Numeric keypad 4 key + NUMPAD5 = 0x65, // Numeric keypad 5 key + NUMPAD6 = 0x66, // Numeric keypad 6 key + NUMPAD7 = 0x67, // Numeric keypad 7 key + NUMPAD8 = 0x68, // Numeric keypad 8 key + NUMPAD9 = 0x69, // Numeric keypad 9 key + MULTIPLY = 0x6A, // Multiply key + ADD = 0x6B, // Add key + SEPARATOR = 0x6C, // Separator key + SUBTRACT = 0x6D, // Subtract key + DECIMAL = 0x6E, // Decimal key + DIVIDE = 0x6F, // Divide key + F1 = 0x70, // F1 key + F2 = 0x71, // F2 key + F3 = 0x72, // F3 key + F4 = 0x73, // F4 key + F5 = 0x74, // F5 key + F6 = 0x75, // F6 key + F7 = 0x76, // F7 key + F8 = 0x77, // F8 key + F9 = 0x78, // F9 key + F10 = 0x79, // F10 key + F11 = 0x7A, // F11 key + F12 = 0x7B, // F12 key + F13 = 0x7C, // F13 key + F14 = 0x7D, // F14 key + F15 = 0x7E, // F15 key + F16 = 0x7F, // F16 key + F17 = 0x80, // F17 key + F18 = 0x81, // F18 key + F19 = 0x82, // F19 key + F20 = 0x83, // F20 key + F21 = 0x84, // F21 key + F22 = 0x85, // F22 key, (PPC only) Key used to lock device. + F23 = 0x86, // F23 key + F24 = 0x87, // F24 key + // 0x88-0X8F, // Unassigned + NUMLOCK = 0x90, // NUM LOCK key + SCROLL = 0x91, // SCROLL LOCK key + // 0x92-0x96, // OEM specific + // 0x97-0x9F, // Unassigned + LSHIFT = 0xA0, // Left SHIFT key + RSHIFT = 0xA1, // Right SHIFT key + LCONTROL = 0xA2, // Left CONTROL key + RCONTROL = 0xA3, // Right CONTROL key + LMENU = 0xA4, // Left MENU key + RMENU = 0xA5, // Right MENU key + BROWSER_BACK = 0xA6, // Windows 2000/XP: Browser Back key + BROWSER_FORWARD = 0xA7, // Windows 2000/XP: Browser Forward key + BROWSER_REFRESH = 0xA8, // Windows 2000/XP: Browser Refresh key + BROWSER_STOP = 0xA9, // Windows 2000/XP: Browser Stop key + BROWSER_SEARCH = 0xAA, // Windows 2000/XP: Browser Search key + BROWSER_FAVORITES = 0xAB, // Windows 2000/XP: Browser Favorites key + BROWSER_HOME = 0xAC, // Windows 2000/XP: Browser Start and Home key + VOLUME_MUTE = 0xAD, // Windows 2000/XP: Volume Mute key + VOLUME_DOWN = 0xAE, // Windows 2000/XP: Volume Down key + VOLUME_UP = 0xAF, // Windows 2000/XP: Volume Up key + MEDIA_NEXT_TRACK = 0xB0,// Windows 2000/XP: Next Track key + MEDIA_PREV_TRACK = 0xB1,// Windows 2000/XP: Previous Track key + MEDIA_STOP = 0xB2, // Windows 2000/XP: Stop Media key + MEDIA_PLAY_PAUSE = 0xB3,// Windows 2000/XP: Play/Pause Media key + LAUNCH_MAIL = 0xB4, // Windows 2000/XP: Start Mail key + LAUNCH_MEDIA_SELECT = 0xB5, // Windows 2000/XP: Select Media key + LAUNCH_APP1 = 0xB6, // Windows 2000/XP: Start Application 1 key + LAUNCH_APP2 = 0xB7, // Windows 2000/XP: Start Application 2 key + // 0xB8-0xB9, // Reserved + OEM_1 = 0xBA, // Used for miscellaneous characters; it can vary by keyboard. + // Windows 2000/XP: For the US standard keyboard, the ';:' key + OEM_PLUS = 0xBB, // Windows 2000/XP: For any country/region, the '+' key + OEM_COMMA = 0xBC, // Windows 2000/XP: For any country/region, the ',' key + OEM_MINUS = 0xBD, // Windows 2000/XP: For any country/region, the '-' key + OEM_PERIOD = 0xBE, // Windows 2000/XP: For any country/region, the '.' key + OEM_2 = 0xBF, // Used for miscellaneous characters; it can vary by keyboard. + // Windows 2000/XP: For the US standard keyboard, the '/?' key + OEM_3 = 0xC0, // Used for miscellaneous characters; it can vary by keyboard. + // Windows 2000/XP: For the US standard keyboard, the '`~' key + // 0xC1-0xD7, // Reserved + // 0xD8-0xDA, // Unassigned + OEM_4 = 0xDB, // Used for miscellaneous characters; it can vary by keyboard. + // Windows 2000/XP: For the US standard keyboard, the '[{' key + OEM_5 = 0xDC, // Used for miscellaneous characters; it can vary by keyboard. + // Windows 2000/XP: For the US standard keyboard, the '\|' key + OEM_6 = 0xDD, // Used for miscellaneous characters; it can vary by keyboard. + // Windows 2000/XP: For the US standard keyboard, the ']}' key + OEM_7 = 0xDE, // Used for miscellaneous characters; it can vary by keyboard. + // Windows 2000/XP: For the US standard keyboard, the 'single-quote/double-quote' key + OEM_8 = 0xDF, // Used for miscellaneous characters; it can vary by keyboard. + // 0xE0, // Reserved + // 0xE1, // OEM specific + OEM_102 = 0xE2, // Windows 2000/XP: Either the angle bracket key or the backslash key on the RT 102-key keyboard + // 0xE3-E4, // OEM specific + PROCESSKEY = 0xE5, // Windows 95/98/Me, Windows NT 4.0, Windows 2000/XP: IME PROCESS key + // 0xE6, // OEM specific + PACKET = 0xE7, // Windows 2000/XP: 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. For more information, see Remark in KEYBDINPUT, SendInput, WM_KEYDOWN, and WM_KEYUP + // 0xE8, // Unassigned + // 0xE9-F5, // OEM specific + ATTN = 0xF6, // Attn key + CRSEL = 0xF7, // CrSel key + EXSEL = 0xF8, // ExSel key + EREOF = 0xF9, // Erase EOF key + PLAY = 0xFA, // Play key + ZOOM = 0xFB, // Zoom key + NONAME = 0xFC, // Reserved + PA1 = 0xFD, // PA1 key + OEM_CLEAR = 0xFE // Clear key + } + + public enum CustomMessages + { + WM_APP = 0x8000, //32768 + WM_HOOK = WM_APP + 1, //32769 + WM_INPUT_DEVICE_CHANGE = 0x00FE, //254 + } + + + + /// + /// Internal callback processing function + /// + private delegate IntPtr KeyboardHookHandler(int nCode, IntPtr wParam, IntPtr lParam); + private KeyboardHookHandler hookHandler; + + /// + /// Function that will be called when defined events occur + /// + /// VKeys + public delegate void KeyboardHookCallback(VKeys key); + + #region Events + public event KeyboardHookCallback KeyDown; + public event KeyboardHookCallback KeyUp; + #endregion + + /// + /// Hook ID + /// + private IntPtr hookID = IntPtr.Zero; + + /// + /// Window Handle to send message of incoming keys + /// + private IntPtr hWndServer; + + /// + /// Install low level keyboard hook + /// + public void Install(IntPtr hWndParent, Hooks hookType, params dynamic[] data) + { + switch (hookType) + { + case Hooks.WH_KEYBOARD: + IntPtr hInstance = data[0]; + if (hInstance != IntPtr.Zero) + { + var hProc = Libraries.GetProcAddress(hInstance, "InstallHook"); + Debug.WriteLine("HPROC: " + hProc); + hookID = SetWindowsHookEx((int)Hooks.WH_KEYBOARD, hProc, hInstance, 0); + } + break; + + case Hooks.WH_KEYBOARD_LL: + hookHandler = HookFunc; + hookID = SetHook_LL(hookHandler); + break; + } + + hWndServer = hWndParent; + + } + + /// + /// Remove low level keyboard hook + /// + public void Uninstall() + { + UnhookWindowsHookEx(hookID); + } + + /// + /// Registers hook with Windows API + /// + /// Callback function + /// Hook ID + //private IntPtr SetHook(Hooks hookType, ) + //{ + // using (ProcessModule module = Process.GetCurrentProcess().MainModule) + // return SetWindowsHookEx((int)Hooks.WH_KEYBOARD, proc, GetModuleHandle(module.ModuleName), 0); + + //} + + + /// + /// Registers LOW LEVEL hook with Windows API + /// + /// Callback function + /// Hook ID + private IntPtr SetHook_LL(KeyboardHookHandler proc) + { + using (ProcessModule module = Process.GetCurrentProcess().MainModule) + return SetWindowsHookEx((int)Hooks.WH_KEYBOARD_LL, proc, GetModuleHandle(module.ModuleName), 0); + + } + + + /// + /// Default hook call, which analyses pressed keys + /// + private IntPtr HookFunc(int nCode, IntPtr wParam, IntPtr lParam) + { + //new Thread(x => { MessageBox.Show("HOOK FUNC"); }).Start(); + if (nCode >= 0) + { + Debug.WriteLine("WITHOUT SENDMESSAGE"); + + //1 means handled, 0 means unhandled + bool handled = Messages.SendMessage(hWndServer, (int)CustomMessages.WM_HOOK, wParam, lParam) == (IntPtr)1 ? true : false; + Debug.WriteLine("SEND MESSAGE HANDLED? : " + handled); + + if (handled) + { + return (IntPtr)1; + } + + int iwParam = wParam.ToInt32(); + + if ((iwParam == (int)KeyMessages.WM_KEYDOWN || iwParam == (int)KeyMessages.WM_SYSKEYDOWN)) + if (KeyDown != null) + KeyDown((VKeys)Marshal.ReadInt32(lParam)); + if ((iwParam == (int)KeyMessages.WM_KEYUP || iwParam == (int)KeyMessages.WM_SYSKEYUP)) + if (KeyUp != null) + KeyUp((VKeys)Marshal.ReadInt32(lParam)); + } + + return CallNextHookEx(hookID, nCode, wParam, lParam); + } + + /// + /// Destructor. Unhook current hook + /// + ~KeyboardHook() + { + Uninstall(); + } + public enum Hooks + { + WH_KEYBOARD = 2, + WH_KEYBOARD_LL = 13 + } + + /// + /// Low-Level function declarations + /// + #region WinAPI + + + public enum KeyMessages + { + WM_KEYDOWN = 0x100, + WM_SYSKEYDOWN = 0x104, + WM_KEYUP = 0x101, + WM_SYSKEYUP = 0x105 + } + + [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] + private static extern IntPtr SetWindowsHookEx(int idHook, KeyboardHookHandler lpfn, IntPtr hMod, uint dwThreadId); + + [DllImport("user32.dll", SetLastError = true)] + static extern IntPtr SetWindowsHookEx(int hookType, IntPtr lpfn, IntPtr hMod, uint dwThreadId); + + [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool UnhookWindowsHookEx(IntPtr hhk); + + [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] + private static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam); + + [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] + private static extern IntPtr GetModuleHandle(string lpModuleName); + #endregion + } + + /// + /// Doesn't work (only works with WH_KEYBOARD_LL) + /// + [StructLayout(LayoutKind.Sequential)] + public class KBDLLHOOKSTRUCT + { + public uint vkCode; + public uint scanCode; + public KBDLLHOOKSTRUCTFlags flags; + public uint time; + public UIntPtr dwExtraInfo; + } + + /// + /// Doesn't work (only works with WH_KEYBOARD_LL) + /// + [Flags] + public enum KBDLLHOOKSTRUCTFlags : uint + { + LLKHF_EXTENDED = 0x01, + LLKHF_INJECTED = 0x10, + LLKHF_ALTDOWN = 0x20, + LLKHF_UP = 0x80, + } + + //[StructLayout(LayoutKind.Sequential)] + //public class keymsgflags + //{ + + // unsigned int repcnt : 16; + // unsigned int scancode : 8; + // unsigned int extkey : 1; + // unsigned int reserved : 4; + // unsigned int context : 1; + // unsigned int prevstate : 1; + // unsigned int transition : 1; + //}; + + [Flags] + public enum KBDHOOKSTRUCTFlags : uint + { + KHF_EXTENDED = 0x01000000, + KHF_INJECTED = 0x10000000, + //Not sure if the KHF_LOWER_IL_INJECTED is correct, might have to be 0x00000002? but the others were shown in that form but didn't need to be like that + KHF_LOWER_IL_INJECTED = 0x00200000, + KHF_ALTDOWN = 0x20000000, + KHF_UP = 0x80000000 + } +} diff --git a/MacroBoard.csproj b/MacroBoard.csproj new file mode 100755 index 0000000..5760379 --- /dev/null +++ b/MacroBoard.csproj @@ -0,0 +1,47 @@ + + + + WinExe + netcoreapp3.1 + true + app.manifest + + + + true + + + + true + full + true + + + + + + + + + + + + + + + + + + + + + + + + + + C:\Windows\Microsoft.NET\Framework\v2.0.50727\System.Windows.Forms.dll + + + + diff --git a/MacroBoard.csproj.user b/MacroBoard.csproj.user new file mode 100755 index 0000000..72081c1 --- /dev/null +++ b/MacroBoard.csproj.user @@ -0,0 +1,17 @@ + + + + true + + + + Designer + + + + + + Designer + + + \ No newline at end of file diff --git a/MacroBoard.sln b/MacroBoard.sln new file mode 100755 index 0000000..42815e6 --- /dev/null +++ b/MacroBoard.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.31112.23 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MacroBoard", "MacroBoard.csproj", "{685525DF-1CE7-4BFA-94DF-60476F201C1A}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {685525DF-1CE7-4BFA-94DF-60476F201C1A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {685525DF-1CE7-4BFA-94DF-60476F201C1A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {685525DF-1CE7-4BFA-94DF-60476F201C1A}.Debug|x86.ActiveCfg = Debug|Any CPU + {685525DF-1CE7-4BFA-94DF-60476F201C1A}.Debug|x86.Build.0 = Debug|Any CPU + {685525DF-1CE7-4BFA-94DF-60476F201C1A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {685525DF-1CE7-4BFA-94DF-60476F201C1A}.Release|Any CPU.Build.0 = Release|Any CPU + {685525DF-1CE7-4BFA-94DF-60476F201C1A}.Release|x86.ActiveCfg = Release|Any CPU + {685525DF-1CE7-4BFA-94DF-60476F201C1A}.Release|x86.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {30CBE9E0-E653-446E-821A-7D0FF24CF191} + EndGlobalSection +EndGlobal diff --git a/MainWindow.xaml b/MainWindow.xaml new file mode 100755 index 0000000..0b508f8 --- /dev/null +++ b/MainWindow.xaml @@ -0,0 +1,118 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +