diff --git a/BetterGenshinImpact.sln b/BetterGenshinImpact.sln
index 4d1709c8..d7c9e3fb 100644
--- a/BetterGenshinImpact.sln
+++ b/BetterGenshinImpact.sln
@@ -19,6 +19,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BetterGenshinImpact.Test",
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Fischless.WindowsInput", "Fischless.WindowsInput\Fischless.WindowsInput.csproj", "{9D00BC7A-9280-4AC9-8951-4502EDB71B76}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MouseKeyHook", "Common\MouseKeyHook\MouseKeyHook.csproj", "{F52AA97E-180A-40ED-8F2B-09080171D6C7}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -83,6 +85,14 @@ Global
{9D00BC7A-9280-4AC9-8951-4502EDB71B76}.Release|Any CPU.Build.0 = Release|x64
{9D00BC7A-9280-4AC9-8951-4502EDB71B76}.Release|x64.ActiveCfg = Release|x64
{9D00BC7A-9280-4AC9-8951-4502EDB71B76}.Release|x64.Build.0 = Release|x64
+ {F52AA97E-180A-40ED-8F2B-09080171D6C7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {F52AA97E-180A-40ED-8F2B-09080171D6C7}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {F52AA97E-180A-40ED-8F2B-09080171D6C7}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {F52AA97E-180A-40ED-8F2B-09080171D6C7}.Debug|x64.Build.0 = Debug|Any CPU
+ {F52AA97E-180A-40ED-8F2B-09080171D6C7}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {F52AA97E-180A-40ED-8F2B-09080171D6C7}.Release|Any CPU.Build.0 = Release|Any CPU
+ {F52AA97E-180A-40ED-8F2B-09080171D6C7}.Release|x64.ActiveCfg = Release|Any CPU
+ {F52AA97E-180A-40ED-8F2B-09080171D6C7}.Release|x64.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/BetterGenshinImpact/BetterGenshinImpact.csproj b/BetterGenshinImpact/BetterGenshinImpact.csproj
index 60fbdb73..08c335f2 100644
--- a/BetterGenshinImpact/BetterGenshinImpact.csproj
+++ b/BetterGenshinImpact/BetterGenshinImpact.csproj
@@ -56,7 +56,7 @@
-
+
@@ -85,6 +85,7 @@
+
diff --git a/BetterGenshinImpact/Core/Config/RecordConfig.cs b/BetterGenshinImpact/Core/Config/RecordConfig.cs
index d32547d7..fda77744 100644
--- a/BetterGenshinImpact/Core/Config/RecordConfig.cs
+++ b/BetterGenshinImpact/Core/Config/RecordConfig.cs
@@ -29,5 +29,5 @@ public partial class RecordConfig : ObservableObject
/// 通过派蒙判断是否在主界面
///
[ObservableProperty]
- private bool _paimonSwitchEnabled = true;
+ private bool _paimonSwitchEnabled = false;
}
diff --git a/Common/MouseKeyHook/Combination.cs b/Common/MouseKeyHook/Combination.cs
new file mode 100644
index 00000000..c33ab489
--- /dev/null
+++ b/Common/MouseKeyHook/Combination.cs
@@ -0,0 +1,146 @@
+// This code is distributed under MIT license.
+// Copyright (c) 2010-2018 George Mamaladze
+// See license.txt or https://mit-license.org/
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Windows.Forms;
+using Gma.System.MouseKeyHook.Implementation;
+
+namespace Gma.System.MouseKeyHook
+{
+ ///
+ /// Used to represent a key combination as frequently used in application as shortcuts.
+ /// e.g. Alt+Shift+R. This combination is triggered when 'R' is pressed after 'Alt' and 'Shift' are already down.
+ ///
+ public class Combination
+ {
+ private readonly Chord _chord;
+
+ private Combination(Keys triggerKey, IEnumerable chordKeys)
+ : this(triggerKey, new Chord(chordKeys))
+ {
+ }
+
+ private Combination(Keys triggerKey, Chord chord)
+ {
+ TriggerKey = triggerKey.Normalize();
+ _chord = chord;
+ }
+
+ ///
+ /// Last key which triggers the combination.
+ ///
+ public Keys TriggerKey { get; }
+
+ ///
+ /// Keys which all must be alredy down when trigger key is pressed.
+ ///
+ public IEnumerable Chord
+ {
+ get { return _chord; }
+ }
+
+ ///
+ /// Number of chord (modifier) keys which must be already down when the trigger key is pressed.
+ ///
+ public int ChordLength
+ {
+ get { return _chord.Count; }
+ }
+
+ ///
+ /// A chainable builder method to simplify chord creation. Used along with ,
+ /// , , , .
+ ///
+ ///
+ public static Combination TriggeredBy(Keys key)
+ {
+ return new Combination(key, (IEnumerable) new Chord(Enumerable.Empty()));
+ }
+
+ ///
+ /// A chainable builder method to simplify chord creation. Used along with ,
+ /// , , , .
+ ///
+ ///
+ public Combination With(Keys key)
+ {
+ return new Combination(TriggerKey, Chord.Concat(Enumerable.Repeat(key, 1)));
+ }
+
+ ///
+ /// A chainable builder method to simplify chord creation. Used along with ,
+ /// , , , .
+ ///
+ public Combination Control()
+ {
+ return With(Keys.Control);
+ }
+
+ ///
+ /// A chainable builder method to simplify chord creation. Used along with ,
+ /// , , , .
+ ///
+ public Combination Alt()
+ {
+ return With(Keys.Alt);
+ }
+
+ ///
+ /// A chainable builder method to simplify chord creation. Used along with ,
+ /// , , , .
+ ///
+ public Combination Shift()
+ {
+ return With(Keys.Shift);
+ }
+
+
+ ///
+ public override string ToString()
+ {
+ return string.Join("+", Chord.Concat(Enumerable.Repeat(TriggerKey, 1)));
+ }
+
+ ///
+ /// TriggeredBy a chord from any string like this 'Alt+Shift+R'.
+ /// Nothe that the trigger key must be the last one.
+ ///
+ public static Combination FromString(string trigger)
+ {
+ var parts = trigger
+ .Split('+')
+ .Select(p => Enum.Parse(typeof(Keys), p))
+ .Cast();
+ var stack = new Stack(parts);
+ var triggerKey = stack.Pop();
+ return new Combination(triggerKey, stack);
+ }
+
+ ///
+ protected bool Equals(Combination other)
+ {
+ return
+ TriggerKey == other.TriggerKey
+ && Chord.Equals(other.Chord);
+ }
+
+ ///
+ public override bool Equals(object obj)
+ {
+ if (ReferenceEquals(null, obj)) return false;
+ if (ReferenceEquals(this, obj)) return true;
+ if (obj.GetType() != GetType()) return false;
+ return Equals((Combination) obj);
+ }
+
+ ///
+ public override int GetHashCode()
+ {
+ return Chord.GetHashCode() ^
+ (int) TriggerKey;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Common/MouseKeyHook/Hook.cs b/Common/MouseKeyHook/Hook.cs
new file mode 100644
index 00000000..b7280877
--- /dev/null
+++ b/Common/MouseKeyHook/Hook.cs
@@ -0,0 +1,38 @@
+// This code is distributed under MIT license.
+// Copyright (c) 2015 George Mamaladze
+// See license.txt or https://mit-license.org/
+
+using Gma.System.MouseKeyHook.Implementation;
+
+namespace Gma.System.MouseKeyHook
+{
+ ///
+ /// This is the class to start with.
+ ///
+ public static class Hook
+ {
+ ///
+ /// Here you find all application wide events. Both mouse and keyboard.
+ ///
+ ///
+ /// Returned instance is used for event subscriptions.
+ /// You can refetch it (you will get the same instance anyway).
+ ///
+ public static IKeyboardMouseEvents AppEvents()
+ {
+ return new AppEventFacade();
+ }
+
+ ///
+ /// Here you find all application wide events. Both mouse and keyboard.
+ ///
+ ///
+ /// Returned instance is used for event subscriptions.
+ /// You can refetch it (you will get the same instance anyway).
+ ///
+ public static IKeyboardMouseEvents GlobalEvents()
+ {
+ return new GlobalEventFacade();
+ }
+ }
+}
\ No newline at end of file
diff --git a/Common/MouseKeyHook/HotKeys/HotKeyArgs.cs b/Common/MouseKeyHook/HotKeys/HotKeyArgs.cs
new file mode 100644
index 00000000..1e847c83
--- /dev/null
+++ b/Common/MouseKeyHook/HotKeys/HotKeyArgs.cs
@@ -0,0 +1,28 @@
+// This code is distributed under MIT license.
+// Copyright (c) 2015 George Mamaladze
+// See license.txt or https://mit-license.org/
+
+using System;
+
+namespace Gma.System.MouseKeyHook.HotKeys
+{
+ ///
+ /// The event arguments passed when a HotKeySet's OnHotKeysDownHold event is triggered.
+ ///
+ public sealed class HotKeyArgs : EventArgs
+ {
+ ///
+ /// Creates an instance of the HotKeyArgs.
+ /// Time when the event was triggered
+ ///
+ public HotKeyArgs(DateTime triggeredAt)
+ {
+ Time = triggeredAt;
+ }
+
+ ///
+ /// Time when the event was triggered
+ ///
+ public DateTime Time { get; }
+ }
+}
\ No newline at end of file
diff --git a/Common/MouseKeyHook/HotKeys/HotKeySet.cs b/Common/MouseKeyHook/HotKeys/HotKeySet.cs
new file mode 100644
index 00000000..4fb2b3ae
--- /dev/null
+++ b/Common/MouseKeyHook/HotKeys/HotKeySet.cs
@@ -0,0 +1,284 @@
+// This code is distributed under MIT license.
+// Copyright (c) 2015 George Mamaladze
+// See license.txt or https://mit-license.org/
+
+using System;
+using System.Collections.Generic;
+using System.Windows.Forms;
+using Gma.System.MouseKeyHook.Implementation;
+
+namespace Gma.System.MouseKeyHook.HotKeys
+{
+ ///
+ /// An immutable set of Hot Keys that provides an event for when the set is activated.
+ ///
+ public class HotKeySet
+ {
+ ///
+ /// A delegate representing the signature for the OnHotKeysDownHold event
+ ///
+ ///
+ ///
+ public delegate void HotKeyHandler(object sender, HotKeyArgs e);
+
+ private readonly Dictionary m_hotkeystate; //Keeps track of the status of the set of Keys
+
+ /*
+ * Example of m_remapping:
+ * a single key from the set of Keys requested is chosen to be the reference key (aka primary key)
+ *
+ * m_remapping[ Keys.LShiftKey ] = Keys.LShiftKey
+ * m_remapping[ Keys.RShiftKey ] = Keys.LShiftKey
+ *
+ * This allows the m_hotkeystate to use a single key (primary key) from the set that will act on behalf of all the keys in the set,
+ * which in turn reduces to this:
+ *
+ * Keys k = Keys.RShiftKey
+ * Keys primaryKey = PrimaryKeyOf( k ) = Keys.LShiftKey
+ * m_hotkeystate[ primaryKey ] = true/false
+ */
+ private readonly Dictionary m_remapping; //Used for mapping multiple keys to a single key
+
+ private bool m_enabled = true; //enabled by default
+
+ //These provide the actual status of whether a set is truly activated or not.
+ private int m_hotkeydowncount; //number of hot keys down
+
+ private int m_remappingCount;
+ //the number of remappings, i.e., a set of mappings, not the individual count in m_remapping
+
+ ///
+ /// Creates an instance of the HotKeySet class. Once created, the keys cannot be changed.
+ ///
+ /// Set of Hot Keys
+ public HotKeySet(IEnumerable hotkeys)
+ {
+ m_hotkeystate = new Dictionary();
+ m_remapping = new Dictionary();
+ HotKeys = hotkeys;
+ InitializeKeys();
+ }
+
+ ///
+ /// Enables the ability to name the set
+ ///
+ public string Name { get; set; }
+
+ ///
+ /// Enables the ability to describe what the set is used for or supposed to do
+ ///
+ public string Description { get; set; }
+
+ ///
+ /// Gets the set of hotkeys that this class handles.
+ ///
+ public IEnumerable HotKeys { get; }
+
+ ///
+ /// Returns whether the set of Keys is activated
+ ///
+ public bool HotKeysActivated
+ {
+ get { return m_hotkeydowncount == m_hotkeystate.Count - m_remappingCount; }
+ }
+
+ ///
+ /// Gets or sets the enabled state of the HotKey set.
+ ///
+ public bool Enabled
+ {
+ get { return m_enabled; }
+ set
+ {
+ if (value)
+ InitializeKeys(); //must get the actual current state of each key to update
+
+ m_enabled = value;
+ }
+ }
+
+ ///
+ /// Called as the user holds down the keys in the set. It is NOT triggered the first time the keys are set.
+ ///
+ ///
+ public event HotKeyHandler OnHotKeysDownHold;
+
+ ///
+ /// Called whenever the hot key set is no longer active. This is essentially a KeyPress event, indicating that a full
+ /// key cycle has occurred, only for HotKeys because a single key removed from the set constitutes an incomplete set.
+ ///
+ public event HotKeyHandler OnHotKeysUp;
+
+ ///
+ /// Called the first time the down keys are set. It does not get called throughout the duration the user holds it but
+ /// only the
+ /// first time it's activated.
+ ///
+ public event HotKeyHandler OnHotKeysDownOnce;
+
+ ///
+ /// General invocation handler
+ ///
+ ///
+ private void InvokeHotKeyHandler(HotKeyHandler hotKeyDelegate)
+ {
+ if (hotKeyDelegate != null)
+ hotKeyDelegate(this, new HotKeyArgs(DateTime.Now));
+ }
+
+ ///
+ /// Adds the keys into the dictionary tracking the keys and gets the real-time status of the Keys
+ /// from the OS
+ ///
+ private void InitializeKeys()
+ {
+ foreach (var k in HotKeys)
+ {
+ if (m_hotkeystate.ContainsKey(k))
+ m_hotkeystate.Add(k, false);
+
+ //assign using the current state of the keyboard
+ m_hotkeystate[k] = KeyboardState.GetCurrent().IsDown(k);
+ }
+ }
+
+ ///
+ /// Unregisters a previously set exclusive or based on the primary key.
+ ///
+ /// Any key used in the Registration method used to create an exclusive or set
+ ///
+ /// True if successful. False doesn't indicate a failure to unregister, it indicates that the Key is not
+ /// registered as an Exclusive Or key or it's not the Primary Key.
+ ///
+ public bool UnregisterExclusiveOrKey(Keys anyKeyInTheExclusiveOrSet)
+ {
+ var primaryKey = GetExclusiveOrPrimaryKey(anyKeyInTheExclusiveOrSet);
+
+ if (primaryKey == Keys.None || !m_remapping.ContainsValue(primaryKey))
+ return false;
+
+ var keystoremove = new List();
+
+ foreach (var pair in m_remapping)
+ if (pair.Value == primaryKey)
+ keystoremove.Add(pair.Key);
+
+ foreach (var k in keystoremove)
+ m_remapping.Remove(k);
+
+ --m_remappingCount;
+
+ return true;
+ }
+
+ ///
+ /// Registers a group of Keys that are already part of the HotKeySet in order to provide better flexibility among keys.
+ ///
+ ///
+ /// HotKeySet hks = new HotKeySet( new [] { Keys.T, Keys.LShiftKey, Keys.RShiftKey } );
+ /// RegisterExclusiveOrKey( new [] { Keys.LShiftKey, Keys.RShiftKey } );
+ ///
+ /// allows either Keys.LShiftKey or Keys.RShiftKey to be combined with Keys.T.
+ ///
+ ///
+ ///
+ /// Primary key used for mapping or Keys.None on error
+ public Keys RegisterExclusiveOrKey(IEnumerable orKeySet)
+ {
+ //Verification first, so as to not leave the m_remapping with a partial set.
+ foreach (var k in orKeySet)
+ if (!m_hotkeystate.ContainsKey(k))
+ return Keys.None;
+
+ var i = 0;
+ var primaryKey = Keys.None;
+
+ //Commit after verification
+ foreach (var k in orKeySet)
+ {
+ if (i == 0)
+ primaryKey = k;
+
+ m_remapping[k] = primaryKey;
+
+ ++i;
+ }
+
+ //Must increase to keep a true count of how many keys are necessary for the activation to be true
+ ++m_remappingCount;
+
+ return primaryKey;
+ }
+
+ ///
+ /// Gets the primary key
+ ///
+ ///
+ /// The primary key if it exists, otherwise Keys.None
+ private Keys GetExclusiveOrPrimaryKey(Keys k)
+ {
+ return m_remapping.ContainsKey(k) ? m_remapping[k] : Keys.None;
+ }
+
+ ///
+ /// Resolves obtaining the key used for state checking.
+ ///
+ ///
+ /// The primary key if it exists, otherwise the key entered
+ private Keys GetPrimaryKey(Keys k)
+ {
+ //If the key is remapped then get the primary keys
+ return m_remapping.ContainsKey(k) ? m_remapping[k] : k;
+ }
+
+ ///
+ ///
+ ///
+ internal void OnKey(KeyEventArgsExt kex)
+ {
+ if (!Enabled)
+ return;
+
+ //Gets the primary key if mapped to a single key or gets the key itself
+ var primaryKey = GetPrimaryKey(kex.KeyCode);
+
+ if (kex.IsKeyDown)
+ OnKeyDown(primaryKey);
+ else //reset
+ OnKeyUp(primaryKey);
+ }
+
+ private void OnKeyDown(Keys k)
+ {
+ //If the keys are activated still then keep invoking the event
+ if (HotKeysActivated)
+ {
+ InvokeHotKeyHandler(OnHotKeysDownHold); //Call the duration event
+ }
+
+ //indicates the key's state is current false but the key is now down
+ else if (m_hotkeystate.ContainsKey(k) && !m_hotkeystate[k])
+ {
+ m_hotkeystate[k] = true; //key's state is down
+ ++m_hotkeydowncount; //increase the number of keys down in this set
+
+ if (HotKeysActivated) //because of the increase, check whether the set is activated
+ InvokeHotKeyHandler(OnHotKeysDownOnce); //Call the initial event
+ }
+ }
+
+ private void OnKeyUp(Keys k)
+ {
+ if (m_hotkeystate.ContainsKey(k) && m_hotkeystate[k]) //indicates the key's state was down but now it's up
+ {
+ var wasActive = HotKeysActivated;
+
+ m_hotkeystate[k] = false; //key's state is up
+ --m_hotkeydowncount; //this set is no longer ready
+
+ if (wasActive)
+ InvokeHotKeyHandler(OnHotKeysUp); //call the KeyUp event because the set is no longer active
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Common/MouseKeyHook/HotKeys/HotKeySetCollection.cs b/Common/MouseKeyHook/HotKeys/HotKeySetCollection.cs
new file mode 100644
index 00000000..479c7cd9
--- /dev/null
+++ b/Common/MouseKeyHook/HotKeys/HotKeySetCollection.cs
@@ -0,0 +1,48 @@
+// This code is distributed under MIT license.
+// Copyright (c) 2015 George Mamaladze
+// See license.txt or https://mit-license.org/
+
+using System.Collections.Generic;
+
+namespace Gma.System.MouseKeyHook.HotKeys
+{
+ ///
+ /// A collection of HotKeySets
+ ///
+ public sealed class HotKeySetCollection : List
+ {
+ private KeyChainHandler m_keyChain;
+
+ ///
+ /// Adds a HotKeySet to the collection.
+ ///
+ ///
+ public new void Add(HotKeySet hks)
+ {
+ m_keyChain += hks.OnKey;
+ base.Add(hks);
+ }
+
+ ///
+ /// Removes the HotKeySet from the collection.
+ ///
+ ///
+ public new void Remove(HotKeySet hks)
+ {
+ m_keyChain -= hks.OnKey;
+ base.Remove(hks);
+ }
+
+ ///
+ /// Uses a multi-case delegate to invoke individual HotKeySets if the Key is in use by any HotKeySets.
+ ///
+ ///
+ internal void OnKey(KeyEventArgsExt e)
+ {
+ if (m_keyChain != null)
+ m_keyChain(e);
+ }
+
+ private delegate void KeyChainHandler(KeyEventArgsExt kex);
+ }
+}
\ No newline at end of file
diff --git a/Common/MouseKeyHook/HotKeys/HotKeySetsListener.cs b/Common/MouseKeyHook/HotKeys/HotKeySetsListener.cs
new file mode 100644
index 00000000..4c4ca9b5
--- /dev/null
+++ b/Common/MouseKeyHook/HotKeys/HotKeySetsListener.cs
@@ -0,0 +1,4 @@
+// This code is distributed under MIT license.
+// Copyright (c) 2015 George Mamaladze
+// See license.txt or https://mit-license.org/
+
diff --git a/Common/MouseKeyHook/HotKeys/ReadMe.txt b/Common/MouseKeyHook/HotKeys/ReadMe.txt
new file mode 100644
index 00000000..12a3f832
--- /dev/null
+++ b/Common/MouseKeyHook/HotKeys/ReadMe.txt
@@ -0,0 +1,85 @@
+Until a separate, full-featured test version is ready, here's a quick update that can be made to the TestFormHookListeners:
+
+ //HotKeySetsListener inherits KeyboardHookListener
+ private readonly HotKeySetsListener m_KeyboardHookManager;
+ private readonly MouseHookListener m_MouseHookManager;
+
+ public TestFormHookListeners()
+ {
+ InitializeComponent();
+ //m_KeyboardHookManager = new KeyboardHookListener(new GlobalHooker());
+ //m_KeyboardHookManager.Enabled = true;
+
+ m_MouseHookManager = new MouseHookListener( new GlobalHooker() ) { Enabled = true };
+
+ HotKeySetCollection hkscoll = new HotKeySetCollection();
+ m_KeyboardHookManager = new HotKeySetsListener( hkscoll, new GlobalHooker() ) { Enabled = true };
+
+ BuildHotKeyTests( hkscoll );
+ }
+
+ private void BuildHotKeyTests( HotKeySetCollection hkscoll )
+ {
+ //Hot Keys are enabled by default. Use the Enabled property to adjust.
+ hkscoll.Add( BindHotKeySet( new[] { Keys.T, Keys.LShiftKey }, null, OnHotKeyDownOnce1, OnHotKeyDownHold1, OnHotKeyUp1, "test1" ) );
+ hkscoll.Add( BindHotKeySet( new[] { Keys.T, Keys.LControlKey, Keys.RControlKey }, new[] { Keys.LControlKey, Keys.RControlKey }, OnHotKeyDownGeneral2, OnHotKeyDownGeneral2, OnHotKeyUp1, "test2" ) );
+ }
+
+ private static HotKeySet BindHotKeySet( IEnumerable ks,
+ IEnumerable xorKeys,
+ HotKeySet.HotKeyHandler onEventDownOnce,
+ HotKeySet.HotKeyHandler onEventDownHold,
+ HotKeySet.HotKeyHandler onEventUp,
+ string name )
+ {
+
+ //Declare ALL Keys that will be available in this set, including any keys you want to register as an either/or subset
+ HotKeySet hks = new HotKeySet( ks );
+
+ //Indicates that the keys in this array will be treated as an OR rather than AND: LShiftKey or RShiftKey
+ //The keys MUST be a subset of the ks Keys array.
+ if ( hks.RegisterExclusiveOrKey( xorKeys ) == Keys.None ) //Keys.None indicates an error
+ {
+ MessageBox.Show( null, @"Unable to register subset: " + String.Join( ", ", xorKeys ),
+ @"Subset registration error", MessageBoxButtons.OK, MessageBoxIcon.Error );
+ }
+
+ hks.OnHotKeysDownOnce += onEventDownOnce; //The first time the key is down
+ hks.OnHotKeysDownHold += onEventDownHold; //Fired as long as the user holds the hot keys down but is not fired the first time.
+ hks.OnHotKeysUp += onEventUp; //Whenever a key from the set is no longer being held down
+
+ hks.Name = ( name ?? String.Empty );
+
+ return hks;
+
+ }
+
+ private void GeneralHotKeyEvent( object sender, DateTime timeTriggered, string eventType )
+ {
+ HotKeySet hks = sender as HotKeySet;
+ string kstring = String.Join( ", ", hks.HotKeys );
+ Log( String.Format( "{0}: {2} {1} - {3}\r\n", timeTriggered.TimeOfDay, eventType, hks.Name, kstring ) );
+ }
+
+ private void OnHotKeyDownGeneral2( object sender, HotKeyArgs e )
+ {
+ GeneralHotKeyEvent( sender, e.Time, "ONCE/HOLD" );
+ }
+
+ private void OnHotKeyDownOnce1( object sender, HotKeyArgs e )
+ {
+ GeneralHotKeyEvent( sender, e.Time, "ONCE" );
+ }
+
+ private void OnHotKeyDownHold1( object sender, HotKeyArgs e )
+ {
+ GeneralHotKeyEvent( sender, e.Time, "HOLD" );
+ }
+
+ private void OnHotKeyUp1( object sender, HotKeyArgs e )
+ {
+ GeneralHotKeyEvent( sender, e.Time, "UP" );
+ }
+
+
+
diff --git a/Common/MouseKeyHook/IKeyboardEvents.cs b/Common/MouseKeyHook/IKeyboardEvents.cs
new file mode 100644
index 00000000..f2d11b17
--- /dev/null
+++ b/Common/MouseKeyHook/IKeyboardEvents.cs
@@ -0,0 +1,54 @@
+// This code is distributed under MIT license.
+// Copyright (c) 2015 George Mamaladze
+// See license.txt or https://mit-license.org/
+
+using System;
+using System.Windows.Forms;
+
+namespace Gma.System.MouseKeyHook
+{
+ ///
+ /// Provides keyboard events
+ ///
+ public interface IKeyboardEvents
+ {
+ ///
+ /// Occurs when a key is pressed.
+ ///
+ event KeyEventHandler KeyDown;
+
+ ///
+ /// Occurs when a key is pressed.
+ ///
+ ///
+ /// Key events occur in the following order:
+ ///
+ /// - KeyDown
+ /// - KeyPress
+ /// - KeyDownTxt
+ /// - KeyUp
+ ///
+ /// The KeyPress event is not raised by non-character keys; however, the non-character keys do raise the KeyDown and
+ /// KeyUp events.
+ /// Use the KeyChar property to sample keystrokes at run time and to consume or modify a subset of common keystrokes.
+ /// To handle keyboard events only in your application and not enable other applications to receive keyboard events,
+ /// set the property in your form's KeyPress event-handling method to
+ /// true.
+ ///
+ event KeyPressEventHandler KeyPress;
+
+ ///
+ /// Occurs when a key is pressed, includes the keystroke characters if any
+ ///
+ event EventHandler KeyDownTxt;
+
+ ///
+ /// Occurs when a key is released.
+ ///
+ event KeyEventHandler KeyUp;
+
+ event EventHandler KeyDownExt;
+
+ event EventHandler KeyUpExt;
+ }
+}
\ No newline at end of file
diff --git a/Common/MouseKeyHook/IKeyboardMouseEvents.cs b/Common/MouseKeyHook/IKeyboardMouseEvents.cs
new file mode 100644
index 00000000..be5b230f
--- /dev/null
+++ b/Common/MouseKeyHook/IKeyboardMouseEvents.cs
@@ -0,0 +1,15 @@
+// This code is distributed under MIT license.
+// Copyright (c) 2015 George Mamaladze
+// See license.txt or https://mit-license.org/
+
+using System;
+
+namespace Gma.System.MouseKeyHook
+{
+ ///
+ /// Provides keyboard and mouse events.
+ ///
+ public interface IKeyboardMouseEvents : IKeyboardEvents, IMouseEvents, IDisposable
+ {
+ }
+}
\ No newline at end of file
diff --git a/Common/MouseKeyHook/IMouseEvents.cs b/Common/MouseKeyHook/IMouseEvents.cs
new file mode 100644
index 00000000..232112e6
--- /dev/null
+++ b/Common/MouseKeyHook/IMouseEvents.cs
@@ -0,0 +1,124 @@
+// This code is distributed under MIT license.
+// Copyright (c) 2015 George Mamaladze
+// See license.txt or https://mit-license.org/
+
+using System;
+using System.Windows.Forms;
+
+namespace Gma.System.MouseKeyHook
+{
+ ///
+ /// Provides all mouse events.
+ ///
+ public interface IMouseEvents
+ {
+ ///
+ /// Occurs when the mouse pointer is moved.
+ ///
+ event MouseEventHandler MouseMove;
+
+ ///
+ /// Occurs when the mouse pointer is moved.
+ ///
+ ///
+ /// This event provides extended arguments of type enabling you to
+ /// suppress further processing of mouse movement in other applications.
+ ///
+ event EventHandler MouseMoveExt;
+
+ ///
+ /// Occurs when a click was performed by the mouse.
+ ///
+ event MouseEventHandler MouseClick;
+
+ ///
+ /// Occurs when the mouse a mouse button is pressed.
+ ///
+ event MouseEventHandler MouseDown;
+
+ ///
+ /// Occurs when the mouse a mouse button is pressed.
+ ///
+ ///
+ /// This event provides extended arguments of type enabling you to
+ /// suppress further processing of mouse click in other applications.
+ ///
+ event EventHandler MouseDownExt;
+
+ ///
+ /// Occurs when a mouse button is released.
+ ///
+ event MouseEventHandler MouseUp;
+
+ ///
+ /// Occurs when a mouse button is released.
+ ///
+ ///
+ /// This event provides extended arguments of type enabling you to
+ /// suppress further processing of mouse click in other applications.
+ ///
+ event EventHandler MouseUpExt;
+
+
+ ///
+ /// Occurs when the mouse wheel moves.
+ ///
+ event MouseEventHandler MouseWheel;
+
+ ///
+ /// Occurs when the mouse wheel moves horizontally.
+ ///
+ event MouseEventHandler MouseHWheel;
+
+ ///
+ /// Occurs when the mouse wheel moves.
+ ///
+ ///
+ /// This event provides extended arguments of type enabling you to
+ /// suppress further processing of mouse wheel moves in other applications.
+ ///
+ event EventHandler MouseWheelExt;
+
+ ///
+ /// Occurs when the mouse wheel moves.
+ ///
+ ///
+ /// This event provides extended arguments of type enabling you to
+ /// suppress further processing of horizontal mouse wheel moves in other applications.
+ ///
+ event EventHandler MouseHWheelExt;
+
+ ///
+ /// Occurs when a mouse button is double-clicked.
+ ///
+ event MouseEventHandler MouseDoubleClick;
+
+ ///
+ /// Occurs when a drag event has started (left button held down whilst moving more than the system drag threshold).
+ ///
+ event MouseEventHandler MouseDragStarted;
+
+ ///
+ /// Occurs when a drag event has started (left button held down whilst moving more than the system drag threshold).
+ ///
+ ///
+ /// This event provides extended arguments of type enabling you to
+ /// suppress further processing of mouse movement in other applications.
+ ///
+ event EventHandler MouseDragStartedExt;
+
+ ///
+ /// Occurs when a drag event has completed.
+ ///
+ event MouseEventHandler MouseDragFinished;
+
+ ///
+ /// Occurs when a drag event has completed.
+ ///
+ ///
+ /// This event provides extended arguments of type enabling you to
+ /// suppress further processing of mouse movement in other applications.
+ ///
+ event EventHandler MouseDragFinishedExt;
+ }
+}
\ No newline at end of file
diff --git a/Common/MouseKeyHook/Implementation/AppEventFacade.cs b/Common/MouseKeyHook/Implementation/AppEventFacade.cs
new file mode 100644
index 00000000..3b2ac51a
--- /dev/null
+++ b/Common/MouseKeyHook/Implementation/AppEventFacade.cs
@@ -0,0 +1,19 @@
+// This code is distributed under MIT license.
+// Copyright (c) 2015 George Mamaladze
+// See license.txt or https://mit-license.org/
+
+namespace Gma.System.MouseKeyHook.Implementation
+{
+ internal class AppEventFacade : EventFacade
+ {
+ protected override MouseListener CreateMouseListener()
+ {
+ return new AppMouseListener();
+ }
+
+ protected override KeyListener CreateKeyListener()
+ {
+ return new AppKeyListener();
+ }
+ }
+}
\ No newline at end of file
diff --git a/Common/MouseKeyHook/Implementation/AppKeyListener.cs b/Common/MouseKeyHook/Implementation/AppKeyListener.cs
new file mode 100644
index 00000000..0eccf331
--- /dev/null
+++ b/Common/MouseKeyHook/Implementation/AppKeyListener.cs
@@ -0,0 +1,27 @@
+// This code is distributed under MIT license.
+// Copyright (c) 2015 George Mamaladze
+// See license.txt or https://mit-license.org/
+
+using System.Collections.Generic;
+using Gma.System.MouseKeyHook.WinApi;
+
+namespace Gma.System.MouseKeyHook.Implementation
+{
+ internal class AppKeyListener : KeyListener
+ {
+ public AppKeyListener()
+ : base(HookHelper.HookAppKeyboard)
+ {
+ }
+
+ protected override IEnumerable GetPressEventArgs(CallbackData data)
+ {
+ return KeyPressEventArgsExt.FromRawDataApp(data);
+ }
+
+ protected override KeyEventArgsExt GetDownUpEventArgs(CallbackData data)
+ {
+ return KeyEventArgsExt.FromRawDataApp(data);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Common/MouseKeyHook/Implementation/AppMouseListener.cs b/Common/MouseKeyHook/Implementation/AppMouseListener.cs
new file mode 100644
index 00000000..3588560d
--- /dev/null
+++ b/Common/MouseKeyHook/Implementation/AppMouseListener.cs
@@ -0,0 +1,21 @@
+// This code is distributed under MIT license.
+// Copyright (c) 2015 George Mamaladze
+// See license.txt or https://mit-license.org/
+
+using Gma.System.MouseKeyHook.WinApi;
+
+namespace Gma.System.MouseKeyHook.Implementation
+{
+ internal class AppMouseListener : MouseListener
+ {
+ public AppMouseListener()
+ : base(HookHelper.HookAppMouse)
+ {
+ }
+
+ protected override MouseEventExtArgs GetEventArgs(CallbackData data)
+ {
+ return MouseEventExtArgs.FromRawDataApp(data);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Common/MouseKeyHook/Implementation/BaseListener.cs b/Common/MouseKeyHook/Implementation/BaseListener.cs
new file mode 100644
index 00000000..338df57c
--- /dev/null
+++ b/Common/MouseKeyHook/Implementation/BaseListener.cs
@@ -0,0 +1,26 @@
+// This code is distributed under MIT license.
+// Copyright (c) 2015 George Mamaladze
+// See license.txt or https://mit-license.org/
+
+using System;
+using Gma.System.MouseKeyHook.WinApi;
+
+namespace Gma.System.MouseKeyHook.Implementation
+{
+ internal abstract class BaseListener : IDisposable
+ {
+ protected BaseListener(Subscribe subscribe)
+ {
+ Handle = subscribe(Callback);
+ }
+
+ protected HookResult Handle { get; set; }
+
+ public void Dispose()
+ {
+ Handle.Dispose();
+ }
+
+ protected abstract bool Callback(CallbackData data);
+ }
+}
\ No newline at end of file
diff --git a/Common/MouseKeyHook/Implementation/ButtonSet.cs b/Common/MouseKeyHook/Implementation/ButtonSet.cs
new file mode 100644
index 00000000..53425629
--- /dev/null
+++ b/Common/MouseKeyHook/Implementation/ButtonSet.cs
@@ -0,0 +1,33 @@
+// This code is distributed under MIT license.
+// Copyright (c) 2015 George Mamaladze
+// See license.txt or https://mit-license.org/
+
+using System.Windows.Forms;
+
+namespace Gma.System.MouseKeyHook.Implementation
+{
+ internal class ButtonSet
+ {
+ private MouseButtons m_Set;
+
+ public ButtonSet()
+ {
+ m_Set = MouseButtons.None;
+ }
+
+ public void Add(MouseButtons element)
+ {
+ m_Set |= element;
+ }
+
+ public void Remove(MouseButtons element)
+ {
+ m_Set &= ~element;
+ }
+
+ public bool Contains(MouseButtons element)
+ {
+ return (m_Set & element) != MouseButtons.None;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Common/MouseKeyHook/Implementation/Callback.cs b/Common/MouseKeyHook/Implementation/Callback.cs
new file mode 100644
index 00000000..685cf55a
--- /dev/null
+++ b/Common/MouseKeyHook/Implementation/Callback.cs
@@ -0,0 +1,10 @@
+// This code is distributed under MIT license.
+// Copyright (c) 2015 George Mamaladze
+// See license.txt or https://mit-license.org/
+
+using Gma.System.MouseKeyHook.WinApi;
+
+namespace Gma.System.MouseKeyHook.Implementation
+{
+ internal delegate bool Callback(CallbackData data);
+}
\ No newline at end of file
diff --git a/Common/MouseKeyHook/Implementation/Chord.cs b/Common/MouseKeyHook/Implementation/Chord.cs
new file mode 100644
index 00000000..0edc936a
--- /dev/null
+++ b/Common/MouseKeyHook/Implementation/Chord.cs
@@ -0,0 +1,75 @@
+// This code is distributed under MIT license.
+// Copyright (c) 2010-2018 George Mamaladze
+// See license.txt or https://mit-license.org/
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+using System.Windows.Forms;
+
+namespace Gma.System.MouseKeyHook.Implementation
+{
+ internal class Chord : IEnumerable
+ {
+ private readonly Keys[] _keys;
+
+ internal Chord(IEnumerable additionalKeys)
+ {
+ _keys = additionalKeys.Select(k => k.Normalize()).OrderBy(k => k).ToArray();
+ }
+
+ public int Count
+ {
+ get { return _keys.Length; }
+ }
+
+ public IEnumerator GetEnumerator()
+ {
+ return _keys.Cast().GetEnumerator();
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return GetEnumerator();
+ }
+
+ public override string ToString()
+ {
+ return string.Join("+", _keys);
+ }
+
+ public static Chord FromString(string chord)
+ {
+ var parts = chord
+ .Split('+')
+ .Select(p => Enum.Parse(typeof(Keys), p))
+ .Cast();
+ var stack = new Stack(parts);
+ return new Chord(stack);
+ }
+
+ protected bool Equals(Chord other)
+ {
+ if (_keys.Length != other._keys.Length) return false;
+ return _keys.SequenceEqual(other._keys);
+ }
+
+ public override bool Equals(object obj)
+ {
+ if (ReferenceEquals(null, obj)) return false;
+ if (ReferenceEquals(this, obj)) return true;
+ if (obj.GetType() != GetType()) return false;
+ return Equals((Chord) obj);
+ }
+
+ public override int GetHashCode()
+ {
+ unchecked
+ {
+ return (_keys.Length + 13) ^
+ ((_keys.Length != 0 ? (int) _keys[0] ^ (int) _keys[_keys.Length - 1] : 0) * 397);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Common/MouseKeyHook/Implementation/EventFacade.cs b/Common/MouseKeyHook/Implementation/EventFacade.cs
new file mode 100644
index 00000000..52cc0e1a
--- /dev/null
+++ b/Common/MouseKeyHook/Implementation/EventFacade.cs
@@ -0,0 +1,174 @@
+// This code is distributed under MIT license.
+// Copyright (c) 2015 George Mamaladze
+// See license.txt or https://mit-license.org/
+
+using System;
+using System.Windows.Forms;
+
+namespace Gma.System.MouseKeyHook.Implementation
+{
+ internal abstract class EventFacade : IKeyboardMouseEvents
+ {
+ private KeyListener m_KeyListenerCache;
+ private MouseListener m_MouseListenerCache;
+
+ public event KeyEventHandler KeyDown
+ {
+ add { GetKeyListener().KeyDown += value; }
+ remove { GetKeyListener().KeyDown -= value; }
+ }
+
+ public event KeyPressEventHandler KeyPress
+ {
+ add { GetKeyListener().KeyPress += value; }
+ remove { GetKeyListener().KeyPress -= value; }
+ }
+
+ public event EventHandler KeyDownTxt
+ {
+ add { GetKeyListener().KeyDownTxt += value; }
+ remove { GetKeyListener().KeyDownTxt -= value; }
+ }
+
+ public event KeyEventHandler KeyUp
+ {
+ add { GetKeyListener().KeyUp += value; }
+ remove { GetKeyListener().KeyUp -= value; }
+ }
+
+ public event EventHandler KeyDownExt
+ {
+ add { GetKeyListener().KeyDownExt += value; }
+ remove { GetKeyListener().KeyDownExt -= value; }
+ }
+
+ public event EventHandler KeyUpExt
+ {
+ add { GetKeyListener().KeyUpExt += value; }
+ remove { GetKeyListener().KeyUpExt -= value; }
+ }
+
+ public event MouseEventHandler MouseMove
+ {
+ add { GetMouseListener().MouseMove += value; }
+ remove { GetMouseListener().MouseMove -= value; }
+ }
+
+ public event EventHandler MouseMoveExt
+ {
+ add { GetMouseListener().MouseMoveExt += value; }
+ remove { GetMouseListener().MouseMoveExt -= value; }
+ }
+
+ public event MouseEventHandler MouseClick
+ {
+ add { GetMouseListener().MouseClick += value; }
+ remove { GetMouseListener().MouseClick -= value; }
+ }
+
+ public event MouseEventHandler MouseDown
+ {
+ add { GetMouseListener().MouseDown += value; }
+ remove { GetMouseListener().MouseDown -= value; }
+ }
+
+ public event EventHandler MouseDownExt
+ {
+ add { GetMouseListener().MouseDownExt += value; }
+ remove { GetMouseListener().MouseDownExt -= value; }
+ }
+
+ public event MouseEventHandler MouseUp
+ {
+ add { GetMouseListener().MouseUp += value; }
+ remove { GetMouseListener().MouseUp -= value; }
+ }
+
+ public event EventHandler MouseUpExt
+ {
+ add { GetMouseListener().MouseUpExt += value; }
+ remove { GetMouseListener().MouseUpExt -= value; }
+ }
+
+ public event MouseEventHandler MouseWheel
+ {
+ add { GetMouseListener().MouseWheel += value; }
+ remove { GetMouseListener().MouseWheel -= value; }
+ }
+
+ public event EventHandler MouseWheelExt
+ {
+ add { GetMouseListener().MouseWheelExt += value; }
+ remove { GetMouseListener().MouseWheelExt -= value; }
+ }
+
+ public event MouseEventHandler MouseHWheel
+ {
+ add { GetMouseListener().MouseHWheel += value; }
+ remove { GetMouseListener().MouseHWheel -= value; }
+ }
+
+ public event EventHandler MouseHWheelExt
+ {
+ add { GetMouseListener().MouseHWheelExt += value; }
+ remove { GetMouseListener().MouseHWheelExt -= value; }
+ }
+
+ public event MouseEventHandler MouseDoubleClick
+ {
+ add { GetMouseListener().MouseDoubleClick += value; }
+ remove { GetMouseListener().MouseDoubleClick -= value; }
+ }
+
+ public event MouseEventHandler MouseDragStarted
+ {
+ add { GetMouseListener().MouseDragStarted += value; }
+ remove { GetMouseListener().MouseDragStarted -= value; }
+ }
+
+ public event EventHandler MouseDragStartedExt
+ {
+ add { GetMouseListener().MouseDragStartedExt += value; }
+ remove { GetMouseListener().MouseDragStartedExt -= value; }
+ }
+
+ public event MouseEventHandler MouseDragFinished
+ {
+ add { GetMouseListener().MouseDragFinished += value; }
+ remove { GetMouseListener().MouseDragFinished -= value; }
+ }
+
+ public event EventHandler MouseDragFinishedExt
+ {
+ add { GetMouseListener().MouseDragFinishedExt += value; }
+ remove { GetMouseListener().MouseDragFinishedExt -= value; }
+ }
+
+ public void Dispose()
+ {
+ if (m_MouseListenerCache != null) m_MouseListenerCache.Dispose();
+ if (m_KeyListenerCache != null) m_KeyListenerCache.Dispose();
+ }
+
+ private KeyListener GetKeyListener()
+ {
+ var target = m_KeyListenerCache;
+ if (target != null) return target;
+ target = CreateKeyListener();
+ m_KeyListenerCache = target;
+ return target;
+ }
+
+ private MouseListener GetMouseListener()
+ {
+ var target = m_MouseListenerCache;
+ if (target != null) return target;
+ target = CreateMouseListener();
+ m_MouseListenerCache = target;
+ return target;
+ }
+
+ protected abstract MouseListener CreateMouseListener();
+ protected abstract KeyListener CreateKeyListener();
+ }
+}
\ No newline at end of file
diff --git a/Common/MouseKeyHook/Implementation/GlobalEventFacade.cs b/Common/MouseKeyHook/Implementation/GlobalEventFacade.cs
new file mode 100644
index 00000000..a7a772b5
--- /dev/null
+++ b/Common/MouseKeyHook/Implementation/GlobalEventFacade.cs
@@ -0,0 +1,19 @@
+// This code is distributed under MIT license.
+// Copyright (c) 2015 George Mamaladze
+// See license.txt or https://mit-license.org/
+
+namespace Gma.System.MouseKeyHook.Implementation
+{
+ internal class GlobalEventFacade : EventFacade
+ {
+ protected override MouseListener CreateMouseListener()
+ {
+ return new GlobalMouseListener();
+ }
+
+ protected override KeyListener CreateKeyListener()
+ {
+ return new GlobalKeyListener();
+ }
+ }
+}
\ No newline at end of file
diff --git a/Common/MouseKeyHook/Implementation/GlobalKeyListener.cs b/Common/MouseKeyHook/Implementation/GlobalKeyListener.cs
new file mode 100644
index 00000000..9f783458
--- /dev/null
+++ b/Common/MouseKeyHook/Implementation/GlobalKeyListener.cs
@@ -0,0 +1,27 @@
+// This code is distributed under MIT license.
+// Copyright (c) 2015 George Mamaladze
+// See license.txt or https://mit-license.org/
+
+using System.Collections.Generic;
+using Gma.System.MouseKeyHook.WinApi;
+
+namespace Gma.System.MouseKeyHook.Implementation
+{
+ internal class GlobalKeyListener : KeyListener
+ {
+ public GlobalKeyListener()
+ : base(HookHelper.HookGlobalKeyboard)
+ {
+ }
+
+ protected override IEnumerable GetPressEventArgs(CallbackData data)
+ {
+ return KeyPressEventArgsExt.FromRawDataGlobal(data);
+ }
+
+ protected override KeyEventArgsExt GetDownUpEventArgs(CallbackData data)
+ {
+ return KeyEventArgsExt.FromRawDataGlobal(data);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Common/MouseKeyHook/Implementation/GlobalMouseListener.cs b/Common/MouseKeyHook/Implementation/GlobalMouseListener.cs
new file mode 100644
index 00000000..178102d0
--- /dev/null
+++ b/Common/MouseKeyHook/Implementation/GlobalMouseListener.cs
@@ -0,0 +1,75 @@
+// This code is distributed under MIT license.
+// Copyright (c) 2015 George Mamaladze
+// See license.txt or https://mit-license.org/
+
+using System;
+using System.Windows.Forms;
+using Gma.System.MouseKeyHook.WinApi;
+
+namespace Gma.System.MouseKeyHook.Implementation
+{
+ internal class GlobalMouseListener : MouseListener
+ {
+ private readonly int m_SystemDoubleClickTime;
+ private readonly int m_xDoubleClickThreshold;
+ private readonly int m_yDoubleClickThreshold;
+ private MouseButtons m_PreviousClicked;
+ private Point m_PreviousClickedPosition;
+ private int m_PreviousClickedTime;
+
+ public GlobalMouseListener()
+ : base(HookHelper.HookGlobalMouse)
+ {
+ m_SystemDoubleClickTime = MouseNativeMethods.GetDoubleClickTime();
+ m_xDoubleClickThreshold = NativeMethods.GetXDoubleClickThreshold();
+ m_yDoubleClickThreshold = NativeMethods.GetYDoubleClickThreshold();
+ }
+
+ protected override void ProcessDown(ref MouseEventExtArgs e)
+ {
+ if (IsDoubleClick(e))
+ e = e.ToDoubleClickEventArgs();
+ else
+ StartDoubleClickWaiting(e);
+ base.ProcessDown(ref e);
+ }
+
+ protected override void ProcessUp(ref MouseEventExtArgs e)
+ {
+ base.ProcessUp(ref e);
+ if (e.Clicks == 2)
+ StopDoubleClickWaiting();
+ }
+
+ private void StartDoubleClickWaiting(MouseEventExtArgs e)
+ {
+ m_PreviousClicked = e.Button;
+ m_PreviousClickedTime = e.Timestamp;
+ m_PreviousClickedPosition = e.Point;
+ }
+
+ private void StopDoubleClickWaiting()
+ {
+ m_PreviousClicked = MouseButtons.None;
+ m_PreviousClickedTime = 0;
+ m_PreviousClickedPosition = m_UninitialisedPoint;
+ }
+
+ private bool IsDoubleClick(MouseEventExtArgs e)
+ {
+ var isXMoving = Math.Abs(e.Point.X - m_PreviousClickedPosition.X) > m_xDoubleClickThreshold;
+ var isYMoving = Math.Abs(e.Point.Y - m_PreviousClickedPosition.Y) > m_yDoubleClickThreshold;
+
+ return
+ e.Button == m_PreviousClicked &&
+ !isXMoving &&
+ !isYMoving &&
+ e.Timestamp - m_PreviousClickedTime <= m_SystemDoubleClickTime;
+ }
+
+ protected override MouseEventExtArgs GetEventArgs(CallbackData data)
+ {
+ return MouseEventExtArgs.FromRawDataGlobal(data);
+ }
+ }
+}
diff --git a/Common/MouseKeyHook/Implementation/KeyListener.cs b/Common/MouseKeyHook/Implementation/KeyListener.cs
new file mode 100644
index 00000000..417a7d10
--- /dev/null
+++ b/Common/MouseKeyHook/Implementation/KeyListener.cs
@@ -0,0 +1,110 @@
+// This code is distributed under MIT license.
+// Copyright (c) 2015 George Mamaladze
+// See license.txt or https://mit-license.org/
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Windows.Forms;
+using Gma.System.MouseKeyHook.WinApi;
+
+namespace Gma.System.MouseKeyHook.Implementation
+{
+ internal abstract class KeyListener : BaseListener, IKeyboardEvents
+ {
+ protected KeyListener(Subscribe subscribe)
+ : base(subscribe)
+ {
+ }
+
+ public event KeyEventHandler KeyDown;
+ public event KeyPressEventHandler KeyPress;
+ public event EventHandler KeyDownTxt;
+ public event KeyEventHandler KeyUp;
+
+ public event EventHandler KeyDownExt;
+ public event EventHandler KeyUpExt;
+
+ public void InvokeKeyDown(KeyEventArgsExt e)
+ {
+ var handler = KeyDown;
+ if (handler == null || e.Handled || !e.IsKeyDown)
+ return;
+ handler(this, e);
+
+ var handlerExt = KeyDownExt;
+ if (handlerExt == null || e.Handled || !e.IsKeyDown)
+ return;
+ handlerExt(this, e);
+ }
+
+ public void InvokeKeyPress(KeyPressEventArgsExt e)
+ {
+ var handler = KeyPress;
+ if (handler == null || e.Handled || e.IsNonChar)
+ return;
+ handler(this, e);
+ }
+
+ public void InvokeKeyDownTxt(KeyDownTxtEventArgs e)
+ {
+ var handler = KeyDownTxt;
+ if (handler == null || e.KeyEvent.Handled || !e.KeyEvent.IsKeyDown)
+ return;
+ handler(this, e);
+ }
+
+ public void InvokeKeyUp(KeyEventArgsExt e)
+ {
+ var handler = KeyUp;
+ if (handler == null || e.Handled || !e.IsKeyUp)
+ return;
+ handler(this, e);
+
+ var handlerExt = KeyUpExt;
+ if (handlerExt == null || e.Handled || !e.IsKeyDown)
+ return;
+ handlerExt(this, e);
+ }
+
+ protected override bool Callback(CallbackData data)
+ {
+ var eDownUp = GetDownUpEventArgs(data);
+
+ InvokeKeyDown(eDownUp);
+
+ if (KeyPress != null || KeyDownTxt != null)
+ {
+ var pressEventArgs = GetPressEventArgs(data).ToList();
+
+ foreach (var pressEventArg in pressEventArgs)
+ InvokeKeyPress(pressEventArg);
+
+ var downTxtEventArgs = GetDownTxtEventArgs(eDownUp, pressEventArgs);
+ InvokeKeyDownTxt(downTxtEventArgs);
+ }
+
+ InvokeKeyUp(eDownUp);
+
+ return !eDownUp.Handled;
+ }
+
+ private KeyDownTxtEventArgs GetDownTxtEventArgs(KeyEventArgsExt eDownUp, IEnumerable pressEventArgs)
+ {
+ var charsCollection = pressEventArgs.Where(e => !e.IsNonChar).Select(e => e.KeyChar);
+ var chars = string.Join(string.Empty, charsCollection);
+ return new KeyDownTxtEventArgs(eDownUp, chars);
+ }
+
+ protected abstract IEnumerable GetPressEventArgs(CallbackData data);
+ protected abstract KeyEventArgsExt GetDownUpEventArgs(CallbackData data);
+
+ private void OnKeyUpExt(KeyEventArgsExt e)
+ {
+ var handler = KeyUpExt;
+ if (handler == null || e.Handled || !e.IsKeyUp)
+ return;
+ handler(this, e);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Common/MouseKeyHook/Implementation/KeyboardState.cs b/Common/MouseKeyHook/Implementation/KeyboardState.cs
new file mode 100644
index 00000000..9c7837f0
--- /dev/null
+++ b/Common/MouseKeyHook/Implementation/KeyboardState.cs
@@ -0,0 +1,112 @@
+// This code is distributed under MIT license.
+// Copyright (c) 2015 George Mamaladze
+// See license.txt or https://mit-license.org/
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Windows.Forms;
+using Gma.System.MouseKeyHook.WinApi;
+
+namespace Gma.System.MouseKeyHook.Implementation
+{
+ ///
+ /// Contains a snapshot of a keyboard state at certain moment and provides methods
+ /// of querying whether specific keys are pressed or locked.
+ ///
+ ///
+ /// This class is basically a managed wrapper of GetKeyboardState API function
+ /// http://msdn.microsoft.com/en-us/library/ms646299
+ ///
+ public class KeyboardState
+ {
+ private readonly byte[] m_KeyboardStateNative;
+
+ private KeyboardState(byte[] keyboardStateNative)
+ {
+ m_KeyboardStateNative = keyboardStateNative;
+ }
+
+ ///
+ /// Makes a snapshot of a keyboard state to the moment of call and returns an
+ /// instance of class.
+ ///
+ /// An instance of class representing a snapshot of keyboard state at certain moment.
+ public static KeyboardState GetCurrent()
+ {
+ var keyboardStateNative = new byte[256];
+ KeyboardNativeMethods.GetKeyboardState(keyboardStateNative);
+ return new KeyboardState(keyboardStateNative);
+ }
+
+ internal byte[] GetNativeState()
+ {
+ return m_KeyboardStateNative;
+ }
+
+ ///
+ /// Indicates whether specified key was down at the moment when snapshot was created or not.
+ ///
+ /// Key (corresponds to the virtual code of the key)
+ /// true if key was down, false - if key was up.
+ public bool IsDown(Keys key)
+ {
+ if ((int)key < 256) return IsDownRaw(key);
+ if (key == Keys.Alt) return IsDownRaw(Keys.LMenu) || IsDownRaw(Keys.RMenu);
+ if (key == Keys.Shift) return IsDownRaw(Keys.LShiftKey) || IsDownRaw(Keys.RShiftKey);
+ if (key == Keys.Control) return IsDownRaw(Keys.LControlKey) || IsDownRaw(Keys.RControlKey);
+ return false;
+ }
+
+ private bool IsDownRaw(Keys key)
+ {
+ var keyState = GetKeyState(key);
+ var isDown = GetHighBit(keyState);
+ return isDown;
+ }
+
+ ///
+ /// Indicate weather specified key was toggled at the moment when snapshot was created or not.
+ ///
+ /// Key (corresponds to the virtual code of the key)
+ ///
+ /// true if toggle key like (CapsLock, NumLocke, etc.) was on. false if it was off.
+ /// Ordinal (non toggle) keys return always false.
+ ///
+ public bool IsToggled(Keys key)
+ {
+ var keyState = GetKeyState(key);
+ var isToggled = GetLowBit(keyState);
+ return isToggled;
+ }
+
+ ///
+ /// Indicates weather every of specified keys were down at the moment when snapshot was created.
+ /// The method returns false if even one of them was up.
+ ///
+ /// Keys to verify whether they were down or not.
+ /// true - all were down. false - at least one was up.
+ public bool AreAllDown(IEnumerable keys)
+ {
+ return keys.All(IsDown);
+ }
+
+ private byte GetKeyState(Keys key)
+ {
+ var virtualKeyCode = (int) key;
+ if (virtualKeyCode < 0 || virtualKeyCode > 255)
+ throw new ArgumentOutOfRangeException("key", key, "The value must be between 0 and 255.");
+ return m_KeyboardStateNative[virtualKeyCode];
+ }
+
+ private static bool GetHighBit(byte value)
+ {
+ return value >> 7 != 0;
+ }
+
+ private static bool GetLowBit(byte value)
+ {
+ return (value & 1) != 0;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Common/MouseKeyHook/Implementation/KeysExtensions.cs b/Common/MouseKeyHook/Implementation/KeysExtensions.cs
new file mode 100644
index 00000000..07e40689
--- /dev/null
+++ b/Common/MouseKeyHook/Implementation/KeysExtensions.cs
@@ -0,0 +1,22 @@
+// This code is distributed under MIT license.
+// Copyright (c) 2010-2018 George Mamaladze
+// See license.txt or https://mit-license.org/
+
+using System.Windows.Forms;
+
+namespace Gma.System.MouseKeyHook.Implementation
+{
+ internal static class KeysExtensions
+ {
+ public static Keys Normalize(this Keys key)
+ {
+ if ((key & Keys.LControlKey) == Keys.LControlKey ||
+ (key & Keys.RControlKey) == Keys.RControlKey) return Keys.Control;
+ if ((key & Keys.LShiftKey) == Keys.LShiftKey ||
+ (key & Keys.RShiftKey) == Keys.RShiftKey) return Keys.Shift;
+ if ((key & Keys.LMenu) == Keys.LMenu ||
+ (key & Keys.RMenu) == Keys.RMenu) return Keys.Alt;
+ return key;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Common/MouseKeyHook/Implementation/MouseListener.cs b/Common/MouseKeyHook/Implementation/MouseListener.cs
new file mode 100644
index 00000000..271c10d1
--- /dev/null
+++ b/Common/MouseKeyHook/Implementation/MouseListener.cs
@@ -0,0 +1,328 @@
+// This code is distributed under MIT license.
+// Copyright (c) 2015 George Mamaladze
+// See license.txt or https://mit-license.org/
+
+using System;
+using System.Runtime.InteropServices;
+using System.Windows.Forms;
+using Gma.System.MouseKeyHook.WinApi;
+
+namespace Gma.System.MouseKeyHook.Implementation
+{
+ // Because it is a P/Invoke method, 'GetSystemMetrics(int)'
+ // should be defined in a class named NativeMethods, SafeNativeMethods,
+ // or UnsafeNativeMethods.
+ // https://msdn.microsoft.com/en-us/library/windows/desktop/ms724385(v=vs.85).aspx
+ internal static class NativeMethods
+ {
+ private const int SM_SWAPBUTTON = 23;
+ private const int SM_CXDRAG = 68;
+ private const int SM_CYDRAG = 69;
+ private const int SM_CXDOUBLECLK = 36;
+ private const int SM_CYDOUBLECLK = 37;
+
+ [DllImport("user32.dll")]
+ private static extern int GetSystemMetrics(int index);
+
+ public static int GetSwapButtonThreshold()
+ {
+ return GetSystemMetrics(SM_SWAPBUTTON);
+ }
+
+ public static int GetXDragThreshold()
+ {
+ return GetSystemMetrics(SM_CXDRAG) / 2 + 1;
+ }
+
+ public static int GetYDragThreshold()
+ {
+ return GetSystemMetrics(SM_CYDRAG) / 2 + 1;
+ }
+
+ public static int GetXDoubleClickThreshold()
+ {
+ return GetSystemMetrics(SM_CXDOUBLECLK) / 2 + 1;
+ }
+
+ public static int GetYDoubleClickThreshold()
+ {
+ return GetSystemMetrics(SM_CYDOUBLECLK) / 2 + 1;
+ }
+ }
+
+ internal abstract class MouseListener : BaseListener, IMouseEvents
+ {
+ private readonly ButtonSet m_DoubleDown;
+ private readonly ButtonSet m_SingleDown;
+ protected readonly Point m_UninitialisedPoint = new Point(-99999, -99999);
+ private readonly int m_SwapButtonThreshold;
+ private readonly int m_xDragThreshold;
+ private readonly int m_yDragThreshold;
+ private Point m_DragStartPosition;
+
+ private bool m_IsDragging;
+
+ private Point m_PreviousPosition;
+
+ protected MouseListener(Subscribe subscribe)
+ : base(subscribe)
+ {
+ m_SwapButtonThreshold = NativeMethods.GetSwapButtonThreshold();
+ m_xDragThreshold = NativeMethods.GetXDragThreshold();
+ m_yDragThreshold = NativeMethods.GetYDragThreshold();
+ m_IsDragging = false;
+
+ m_PreviousPosition = m_UninitialisedPoint;
+ m_DragStartPosition = m_UninitialisedPoint;
+
+ m_DoubleDown = new ButtonSet();
+ m_SingleDown = new ButtonSet();
+ }
+
+ public event MouseEventHandler MouseMove;
+ public event EventHandler MouseMoveExt;
+ public event MouseEventHandler MouseClick;
+ public event MouseEventHandler MouseDown;
+ public event EventHandler MouseDownExt;
+ public event MouseEventHandler MouseUp;
+ public event EventHandler MouseUpExt;
+ public event MouseEventHandler MouseWheel;
+ public event EventHandler MouseWheelExt;
+ public event MouseEventHandler MouseHWheel;
+ public event EventHandler MouseHWheelExt;
+ public event MouseEventHandler MouseDoubleClick;
+ public event MouseEventHandler MouseDragStarted;
+ public event EventHandler MouseDragStartedExt;
+ public event MouseEventHandler MouseDragFinished;
+ public event EventHandler MouseDragFinishedExt;
+
+ protected override bool Callback(CallbackData data)
+ {
+ data.MSwapButton = m_SwapButtonThreshold;
+
+ var e = GetEventArgs(data);
+
+ if (e.IsMouseButtonDown)
+ ProcessDown(ref e);
+
+ if (e.IsMouseButtonUp)
+ ProcessUp(ref e);
+
+ if (e.WheelScrolled)
+ {
+ if (e.IsHorizontalWheel)
+ ProcessHWheel(ref e);
+ else
+ ProcessWheel(ref e);
+ }
+
+ if (HasMoved(e.Point))
+ ProcessMove(ref e);
+
+ ProcessDrag(ref e);
+
+ return !e.Handled;
+ }
+
+ protected abstract MouseEventExtArgs GetEventArgs(CallbackData data);
+
+ protected virtual void ProcessWheel(ref MouseEventExtArgs e)
+ {
+ OnWheel(e);
+ OnWheelExt(e);
+ }
+
+ protected virtual void ProcessHWheel(ref MouseEventExtArgs e)
+ {
+ OnHWheel(e);
+ OnHWheelExt(e);
+ }
+
+ protected virtual void ProcessDown(ref MouseEventExtArgs e)
+ {
+ OnDown(e);
+ OnDownExt(e);
+ if (e.Handled)
+ return;
+
+ if (e.Clicks == 2)
+ m_DoubleDown.Add(e.Button);
+
+ if (e.Clicks == 1)
+ m_SingleDown.Add(e.Button);
+ }
+
+ protected virtual void ProcessUp(ref MouseEventExtArgs e)
+ {
+ OnUp(e);
+ OnUpExt(e);
+ if (e.Handled)
+ return;
+
+ if (m_SingleDown.Contains(e.Button))
+ {
+ OnClick(e);
+ m_SingleDown.Remove(e.Button);
+ }
+
+ if (m_DoubleDown.Contains(e.Button))
+ {
+ e = e.ToDoubleClickEventArgs();
+ OnDoubleClick(e);
+ m_DoubleDown.Remove(e.Button);
+ }
+ }
+
+ private void ProcessMove(ref MouseEventExtArgs e)
+ {
+ m_PreviousPosition = e.Point;
+
+ OnMove(e);
+ OnMoveExt(e);
+ }
+
+ private void ProcessDrag(ref MouseEventExtArgs e)
+ {
+ if (m_SingleDown.Contains(MouseButtons.Left))
+ {
+ if (m_DragStartPosition.Equals(m_UninitialisedPoint))
+ m_DragStartPosition = e.Point;
+
+ ProcessDragStarted(ref e);
+ }
+ else
+ {
+ m_DragStartPosition = m_UninitialisedPoint;
+ ProcessDragFinished(ref e);
+ }
+ }
+
+ private void ProcessDragStarted(ref MouseEventExtArgs e)
+ {
+ if (!m_IsDragging)
+ {
+ var isXDragging = Math.Abs(e.Point.X - m_DragStartPosition.X) > m_xDragThreshold;
+ var isYDragging = Math.Abs(e.Point.Y - m_DragStartPosition.Y) > m_yDragThreshold;
+ m_IsDragging = isXDragging || isYDragging;
+
+ if (m_IsDragging)
+ {
+ var dragArgs = new MouseEventExtArgs(e.Button, e.Clicks, m_DragStartPosition, e.Delta, e.Timestamp, e.IsMouseButtonDown, e.IsMouseButtonUp, e.IsHorizontalWheel);
+ OnDragStarted(dragArgs);
+ OnDragStartedExt(dragArgs);
+ }
+ }
+ }
+
+ private void ProcessDragFinished(ref MouseEventExtArgs e)
+ {
+ if (m_IsDragging)
+ {
+ OnDragFinished(e);
+ OnDragFinishedExt(e);
+ m_IsDragging = false;
+ }
+ }
+
+ private bool HasMoved(Point actualPoint)
+ {
+ return m_PreviousPosition != actualPoint;
+ }
+
+ protected virtual void OnMove(MouseEventArgs e)
+ {
+ var handler = MouseMove;
+ if (handler != null) handler(this, e);
+ }
+
+ protected virtual void OnMoveExt(MouseEventExtArgs e)
+ {
+ var handler = MouseMoveExt;
+ if (handler != null) handler(this, e);
+ }
+
+ protected virtual void OnClick(MouseEventArgs e)
+ {
+ var handler = MouseClick;
+ if (handler != null) handler(this, e);
+ }
+
+ protected virtual void OnDown(MouseEventArgs e)
+ {
+ var handler = MouseDown;
+ if (handler != null) handler(this, e);
+ }
+
+ protected virtual void OnDownExt(MouseEventExtArgs e)
+ {
+ var handler = MouseDownExt;
+ if (handler != null) handler(this, e);
+ }
+
+ protected virtual void OnUp(MouseEventArgs e)
+ {
+ var handler = MouseUp;
+ if (handler != null) handler(this, e);
+ }
+
+ protected virtual void OnUpExt(MouseEventExtArgs e)
+ {
+ var handler = MouseUpExt;
+ if (handler != null) handler(this, e);
+ }
+
+ protected virtual void OnWheel(MouseEventArgs e)
+ {
+ var handler = MouseWheel;
+ if (handler != null) handler(this, e);
+ }
+
+ protected virtual void OnWheelExt(MouseEventExtArgs e)
+ {
+ var handler = MouseWheelExt;
+ if (handler != null) handler(this, e);
+ }
+
+ protected virtual void OnHWheel(MouseEventArgs e)
+ {
+ var handler = MouseHWheel;
+ if (handler != null) handler(this, e);
+ }
+
+ protected virtual void OnHWheelExt(MouseEventExtArgs e)
+ {
+ var handler = MouseHWheelExt;
+ if (handler != null) handler(this, e);
+ }
+
+ protected virtual void OnDoubleClick(MouseEventArgs e)
+ {
+ var handler = MouseDoubleClick;
+ if (handler != null) handler(this, e);
+ }
+
+ protected virtual void OnDragStarted(MouseEventArgs e)
+ {
+ var handler = MouseDragStarted;
+ if (handler != null) handler(this, e);
+ }
+
+ protected virtual void OnDragStartedExt(MouseEventExtArgs e)
+ {
+ var handler = MouseDragStartedExt;
+ if (handler != null) handler(this, e);
+ }
+
+ protected virtual void OnDragFinished(MouseEventArgs e)
+ {
+ var handler = MouseDragFinished;
+ if (handler != null) handler(this, e);
+ }
+
+ protected virtual void OnDragFinishedExt(MouseEventExtArgs e)
+ {
+ var handler = MouseDragFinishedExt;
+ if (handler != null) handler(this, e);
+ }
+ }
+}
diff --git a/Common/MouseKeyHook/Implementation/Subscribe.cs b/Common/MouseKeyHook/Implementation/Subscribe.cs
new file mode 100644
index 00000000..1dc4ee92
--- /dev/null
+++ b/Common/MouseKeyHook/Implementation/Subscribe.cs
@@ -0,0 +1,10 @@
+// This code is distributed under MIT license.
+// Copyright (c) 2015 George Mamaladze
+// See license.txt or https://mit-license.org/
+
+using Gma.System.MouseKeyHook.WinApi;
+
+namespace Gma.System.MouseKeyHook.Implementation
+{
+ internal delegate HookResult Subscribe(Callback callbck);
+}
\ No newline at end of file
diff --git a/Common/MouseKeyHook/KeyCombinationExtensions.cs b/Common/MouseKeyHook/KeyCombinationExtensions.cs
new file mode 100644
index 00000000..32bae7bf
--- /dev/null
+++ b/Common/MouseKeyHook/KeyCombinationExtensions.cs
@@ -0,0 +1,108 @@
+// This code is distributed under MIT license.
+// Copyright (c) 2010-2018 George Mamaladze
+// See license.txt or https://mit-license.org/
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Gma.System.MouseKeyHook.Implementation;
+
+namespace Gma.System.MouseKeyHook
+{
+ ///
+ /// Extension methods to detect key combinations
+ ///
+ public static class KeyCombinationExtensions
+ {
+ ///
+ /// Detects a key or key combination and triggers the corresponding action.
+ ///
+ ///
+ /// An instance of Global or Application hook. Use or to
+ /// create it.
+ ///
+ ///
+ /// This map contains the list of key combinations mapped to corresponding actions. You can use a dictionary initilizer
+ /// to easily create it.
+ /// Whenever a listed combination will be detected a corresponding action will be triggered.
+ ///
+ ///
+ /// This optional action will be executed when some key was pressed but it was not part of any wanted combinations.
+ ///
+ public static void OnCombination(this IKeyboardEvents source,
+ IEnumerable> map, Action reset = null)
+ {
+ var watchlists = map.GroupBy(k => k.Key.TriggerKey)
+ .ToDictionary(g => g.Key, g => g.ToArray());
+ source.KeyDown += (sender, e) =>
+ {
+ KeyValuePair[] element;
+ var found = watchlists.TryGetValue(e.KeyCode.Normalize(), out element);
+ if (!found)
+ {
+ reset?.Invoke();
+ return;
+ }
+ var state = KeyboardState.GetCurrent();
+ var action = reset;
+ var maxLength = 0;
+ foreach (var current in element)
+ {
+ var matches = current.Key.Chord.All(state.IsDown);
+ if (!matches) continue;
+ if (maxLength > current.Key.ChordLength) continue;
+ maxLength = current.Key.ChordLength;
+ action = current.Value;
+ }
+ action?.Invoke();
+ };
+ }
+
+
+ ///
+ /// Detects a key or key combination sequence and triggers the corresponding action.
+ ///
+ ///
+ /// An instance of Global or Application hook. Use or
+ /// to create it.
+ ///
+ ///
+ /// This map contains the list of sequences mapped to corresponding actions. You can use a dictionary initilizer to
+ /// easily create it.
+ /// Whenever a listed sequnce will be detected a corresponding action will be triggered. If two or more sequences match
+ /// the longest one will be used.
+ /// Example: sequences may A,B,C and B,C might be detected simultanously if user pressed first A then B then C. In this
+ /// case only action corresponding
+ /// to 'A,B,C' will be triggered.
+ ///
+ public static void OnSequence(this IKeyboardEvents source, IEnumerable> map)
+ {
+ var actBySeq = map.ToArray();
+ var endsWith = new Func, Sequence, bool>((chords, sequence) =>
+ {
+ var skipCount = chords.Count - sequence.Length;
+ return skipCount >= 0 && chords.Skip(skipCount).SequenceEqual(sequence);
+ });
+
+ var max = actBySeq.Select(p => p.Key).Max(c => c.Length);
+ var min = actBySeq.Select(p => p.Key).Min(c => c.Length);
+ var buffer = new Queue(max);
+
+ var wrapMap = actBySeq.SelectMany(p => p.Key).Select(c => new KeyValuePair(c, () =>
+ {
+ buffer.Enqueue(c);
+ if (buffer.Count > max) buffer.Dequeue();
+ if (buffer.Count < min) return;
+ //Invoke action corresponding to the longest matching sequence
+ actBySeq
+ .Where(pair => endsWith(buffer, pair.Key))
+ .OrderBy(pair => pair.Key.Length)
+ .Select(pair => pair.Value)
+ .LastOrDefault()
+ ?.Invoke();
+ }));
+
+ OnCombination(source, wrapMap, buffer.Clear);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Common/MouseKeyHook/KeyDownTxtEventArgs.cs b/Common/MouseKeyHook/KeyDownTxtEventArgs.cs
new file mode 100644
index 00000000..fec69d82
--- /dev/null
+++ b/Common/MouseKeyHook/KeyDownTxtEventArgs.cs
@@ -0,0 +1,16 @@
+using System;
+
+namespace Gma.System.MouseKeyHook
+{
+ public class KeyDownTxtEventArgs : EventArgs
+ {
+ public KeyEventArgsExt KeyEvent { get; }
+ public string Chars { get; }
+
+ public KeyDownTxtEventArgs(KeyEventArgsExt keyEvent, string chars)
+ {
+ KeyEvent = keyEvent;
+ Chars = chars ?? string.Empty;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Common/MouseKeyHook/KeyEventArgsExt.cs b/Common/MouseKeyHook/KeyEventArgsExt.cs
new file mode 100644
index 00000000..3d4d505a
--- /dev/null
+++ b/Common/MouseKeyHook/KeyEventArgsExt.cs
@@ -0,0 +1,153 @@
+// This code is distributed under MIT license.
+// Copyright (c) 2015 George Mamaladze
+// See license.txt or https://mit-license.org/
+
+using System;
+using System.Runtime.InteropServices;
+using System.Windows.Forms;
+using Gma.System.MouseKeyHook.Implementation;
+using Gma.System.MouseKeyHook.WinApi;
+
+namespace Gma.System.MouseKeyHook
+{
+ ///
+ /// Provides extended argument data for the or
+ /// event.
+ ///
+ public class KeyEventArgsExt : KeyEventArgs
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///
+ public KeyEventArgsExt(Keys keyData)
+ : base(keyData)
+ {
+ }
+
+ internal KeyEventArgsExt(Keys keyData, int scanCode, int timestamp, bool isKeyDown, bool isKeyUp,
+ bool isExtendedKey)
+ : this(keyData)
+ {
+ ScanCode = scanCode;
+ Timestamp = timestamp;
+ IsKeyDown = isKeyDown;
+ IsKeyUp = isKeyUp;
+ IsExtendedKey = isExtendedKey;
+ }
+
+ ///
+ /// The hardware scan code.
+ ///
+ public int ScanCode { get; }
+
+ ///
+ /// The system tick count of when the event occurred.
+ ///
+ public int Timestamp { get; }
+
+ ///
+ /// True if event signals key down..
+ ///
+ public bool IsKeyDown { get; }
+
+ ///
+ /// True if event signals key up.
+ ///
+ public bool IsKeyUp { get; }
+
+ ///
+ /// True if event signals, that the key is an extended key
+ ///
+ public bool IsExtendedKey { get; }
+
+ internal static KeyEventArgsExt FromRawDataApp(CallbackData data)
+ {
+ var wParam = data.WParam;
+ var lParam = data.LParam;
+
+ //http://msdn.microsoft.com/en-us/library/ms644984(v=VS.85).aspx
+
+ const uint maskKeydown = 0x40000000; // for bit 30
+ const uint maskKeyup = 0x80000000; // for bit 31
+ const uint maskExtendedKey = 0x1000000; // for bit 24
+
+ var timestamp = Environment.TickCount;
+
+ var flags = (uint) lParam.ToInt64();
+
+ //bit 30 Specifies the previous key state. The value is 1 if the key is down before the message is sent; it is 0 if the key is up.
+ var wasKeyDown = (flags & maskKeydown) > 0;
+ //bit 31 Specifies the transition state. The value is 0 if the key is being pressed and 1 if it is being released.
+ var isKeyReleased = (flags & maskKeyup) > 0;
+ //bit 24 Specifies the extended key state. The value is 1 if the key is an extended key, otherwise the value is 0.
+ var isExtendedKey = (flags & maskExtendedKey) > 0;
+
+
+ var keyData = AppendModifierStates((Keys) wParam);
+ var scanCode = (int) (((flags & 0x10000) | (flags & 0x20000) | (flags & 0x40000) | (flags & 0x80000) |
+ (flags & 0x100000) | (flags & 0x200000) | (flags & 0x400000) | (flags & 0x800000)) >>
+ 16);
+
+ var isKeyDown = !isKeyReleased;
+ var isKeyUp = wasKeyDown && isKeyReleased;
+
+ return new KeyEventArgsExt(keyData, scanCode, timestamp, isKeyDown, isKeyUp, isExtendedKey);
+ }
+
+ internal static KeyEventArgsExt FromRawDataGlobal(CallbackData data)
+ {
+ var wParam = data.WParam;
+ var lParam = data.LParam;
+ var keyboardHookStruct =
+ (KeyboardHookStruct) Marshal.PtrToStructure(lParam, typeof(KeyboardHookStruct));
+
+ var keyData = AppendModifierStates((Keys) keyboardHookStruct.VirtualKeyCode);
+
+ var keyCode = (int) wParam;
+ var isKeyDown = keyCode == Messages.WM_KEYDOWN || keyCode == Messages.WM_SYSKEYDOWN;
+ var isKeyUp = keyCode == Messages.WM_KEYUP || keyCode == Messages.WM_SYSKEYUP;
+
+ const uint maskExtendedKey = 0x1;
+ var isExtendedKey = (keyboardHookStruct.Flags & maskExtendedKey) > 0;
+
+ return new KeyEventArgsExt(keyData, keyboardHookStruct.ScanCode, keyboardHookStruct.Time, isKeyDown,
+ isKeyUp, isExtendedKey);
+ }
+
+ // # It is not possible to distinguish Keys.LControlKey and Keys.RControlKey when they are modifiers
+ // Check for Keys.Control instead
+ // Same for Shift and Alt(Menu)
+ // See more at http://www.tech-archive.net/Archive/DotNet/microsoft.public.dotnet.framework.windowsforms/2008-04/msg00127.html #
+
+ // A shortcut to make life easier
+ private static bool CheckModifier(int vKey)
+ {
+ return (KeyboardNativeMethods.GetKeyState(vKey) & 0x8000) > 0;
+ }
+
+ private static Keys AppendModifierStates(Keys keyData)
+ {
+ // Is Control being held down?
+ var control = CheckModifier(KeyboardNativeMethods.VK_CONTROL);
+ // Is Shift being held down?
+ var shift = CheckModifier(KeyboardNativeMethods.VK_SHIFT);
+ // Is Alt being held down?
+ var alt = CheckModifier(KeyboardNativeMethods.VK_MENU);
+
+ // Windows keys
+ // # combine LWin and RWin key with other keys will potentially corrupt the data
+ // notable F5 | Keys.LWin == F12, see https://globalmousekeyhook.codeplex.com/workitem/1188
+ // and the KeyEventArgs.KeyData don't recognize combined data either
+
+ // Function (Fn) key
+ // # CANNOT determine state due to conversion inside keyboard
+ // See http://en.wikipedia.org/wiki/Fn_key#Technical_details #
+
+ return keyData |
+ (control ? Keys.Control : Keys.None) |
+ (shift ? Keys.Shift : Keys.None) |
+ (alt ? Keys.Alt : Keys.None);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Common/MouseKeyHook/KeyPressEventArgsExt.cs b/Common/MouseKeyHook/KeyPressEventArgsExt.cs
new file mode 100644
index 00000000..2e543901
--- /dev/null
+++ b/Common/MouseKeyHook/KeyPressEventArgsExt.cs
@@ -0,0 +1,111 @@
+// This code is distributed under MIT license.
+// Copyright (c) 2015 George Mamaladze
+// See license.txt or https://mit-license.org/
+
+using System;
+using System.Collections.Generic;
+using System.Runtime.InteropServices;
+using System.Windows.Forms;
+using Gma.System.MouseKeyHook.Implementation;
+using Gma.System.MouseKeyHook.WinApi;
+
+namespace Gma.System.MouseKeyHook
+{
+ ///
+ /// Provides extended data for the event.
+ ///
+ public class KeyPressEventArgsExt : KeyPressEventArgs
+ {
+ internal KeyPressEventArgsExt(char keyChar, int timestamp)
+ : base(keyChar)
+ {
+ IsNonChar = keyChar == (char) 0x0;
+ Timestamp = timestamp;
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///
+ /// Character corresponding to the key pressed. 0 char if represents a system or functional non char
+ /// key.
+ ///
+ public KeyPressEventArgsExt(char keyChar)
+ : this(keyChar, Environment.TickCount)
+ {
+ }
+
+ ///
+ /// True if represents a system or functional non char key.
+ ///
+ public bool IsNonChar { get; }
+
+ ///
+ /// The system tick count of when the event occurred.
+ ///
+ public int Timestamp { get; }
+
+ internal static IEnumerable FromRawDataApp(CallbackData data)
+ {
+ var wParam = data.WParam;
+ var lParam = data.LParam;
+
+ //http://msdn.microsoft.com/en-us/library/ms644984(v=VS.85).aspx
+
+ const uint maskKeydown = 0x40000000; // for bit 30
+ const uint maskKeyup = 0x80000000; // for bit 31
+ const uint maskScanCode = 0xff0000; // for bit 23-16
+
+ var flags = (uint) lParam.ToInt64();
+
+ //bit 30 Specifies the previous key state. The value is 1 if the key is down before the message is sent; it is 0 if the key is up.
+ var wasKeyDown = (flags & maskKeydown) > 0;
+ //bit 31 Specifies the transition state. The value is 0 if the key is being pressed and 1 if it is being released.
+ var isKeyReleased = (flags & maskKeyup) > 0;
+
+ if (!wasKeyDown && !isKeyReleased)
+ yield break;
+
+ var virtualKeyCode = (int) wParam;
+ var scanCode = checked((int) (flags & maskScanCode));
+ const int fuState = 0;
+
+ char[] chars;
+
+ KeyboardNativeMethods.TryGetCharFromKeyboardState(virtualKeyCode, scanCode, fuState, out chars);
+ if (chars == null) yield break;
+ foreach (var ch in chars)
+ yield return new KeyPressEventArgsExt(ch);
+ }
+
+ internal static IEnumerable FromRawDataGlobal(CallbackData data)
+ {
+ var wParam = data.WParam;
+ var lParam = data.LParam;
+
+ if ((int) wParam != Messages.WM_KEYDOWN && (int) wParam != Messages.WM_SYSKEYDOWN)
+ yield break;
+
+ var keyboardHookStruct =
+ (KeyboardHookStruct) Marshal.PtrToStructure(lParam, typeof(KeyboardHookStruct));
+
+ var virtualKeyCode = keyboardHookStruct.VirtualKeyCode;
+ var scanCode = keyboardHookStruct.ScanCode;
+ var fuState = keyboardHookStruct.Flags;
+
+ if (virtualKeyCode == KeyboardNativeMethods.VK_PACKET)
+ {
+ var ch = (char) scanCode;
+ yield return new KeyPressEventArgsExt(ch, keyboardHookStruct.Time);
+ }
+ else
+ {
+ char[] chars;
+ KeyboardNativeMethods.TryGetCharFromKeyboardState(virtualKeyCode, scanCode, fuState, out chars);
+ if (chars == null) yield break;
+ foreach (var current in chars)
+ yield return new KeyPressEventArgsExt(current, keyboardHookStruct.Time);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Common/MouseKeyHook/MouseEventExtArgs.cs b/Common/MouseKeyHook/MouseEventExtArgs.cs
new file mode 100644
index 00000000..8704bcda
--- /dev/null
+++ b/Common/MouseKeyHook/MouseEventExtArgs.cs
@@ -0,0 +1,226 @@
+// This code is distributed under MIT license.
+// Copyright (c) 2015 George Mamaladze
+// See license.txt or https://mit-license.org/
+
+using System;
+using System.Runtime.InteropServices;
+using System.Windows.Forms;
+using Gma.System.MouseKeyHook.WinApi;
+
+namespace Gma.System.MouseKeyHook
+{
+ ///
+ /// Provides extended data for the MouseClickExt and MouseMoveExt events.
+ ///
+ public class MouseEventExtArgs : MouseEventArgs
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// One of the MouseButtons values indicating which mouse button was pressed.
+ /// The number of times a mouse button was pressed.
+ /// The x and y coordinate of a mouse click, in pixels.
+ /// A signed count of the number of detents the wheel has rotated.
+ /// The system tick count when the event occurred.
+ /// True if event signals mouse button down.
+ /// True if event signals mouse button up.
+ /// True if event signals horizontal wheel action.
+ internal MouseEventExtArgs(MouseButtons buttons, int clicks, Point point, int delta, int timestamp,
+ bool isMouseButtonDown, bool isMouseButtonUp, bool isHorizontalWheel)
+ : base(buttons, clicks, point.X, point.Y, delta)
+ {
+ IsMouseButtonDown = isMouseButtonDown;
+ IsMouseButtonUp = isMouseButtonUp;
+ IsHorizontalWheel = isHorizontalWheel;
+ Timestamp = timestamp;
+ }
+
+ ///
+ /// Set this property to true inside your event handler to prevent further processing of the event in other
+ /// applications.
+ ///
+ public bool Handled { get; set; }
+
+ ///
+ /// True if event contains information about wheel scroll.
+ ///
+ public bool WheelScrolled
+ {
+ get { return Delta != 0; }
+ }
+
+ ///
+ /// True if event signals a click. False if it was only a move or wheel scroll.
+ ///
+ public bool Clicked
+ {
+ get { return Clicks > 0; }
+ }
+
+ ///
+ /// True if event signals mouse button down.
+ ///
+ public bool IsMouseButtonDown { get; }
+
+ ///
+ /// True if event signals mouse button up.
+ ///
+ public bool IsMouseButtonUp { get; }
+
+ ///
+ /// True if event signals horizontal wheel action.
+ ///
+ public bool IsHorizontalWheel { get; }
+
+ ///
+ /// The system tick count of when the event occurred.
+ ///
+ public int Timestamp { get; }
+
+ ///
+ ///
+ internal Point Point
+ {
+ get { return new Point(X, Y); }
+ }
+
+ internal static MouseEventExtArgs FromRawDataApp(CallbackData data)
+ {
+ var wParam = data.WParam;
+ var lParam = data.LParam;
+ var mSwapButton = data.MSwapButton;
+
+ var marshalledMouseStruct =
+ (AppMouseStruct) Marshal.PtrToStructure(lParam, typeof(AppMouseStruct));
+ return FromRawDataUniversal(wParam, marshalledMouseStruct.ToMouseStruct(), mSwapButton);
+ }
+
+ internal static MouseEventExtArgs FromRawDataGlobal(CallbackData data)
+ {
+ var wParam = data.WParam;
+ var lParam = data.LParam;
+ var mSwapButton = data.MSwapButton;
+
+ var marshalledMouseStruct = (MouseStruct) Marshal.PtrToStructure(lParam, typeof(MouseStruct));
+ return FromRawDataUniversal(wParam, marshalledMouseStruct, mSwapButton);
+ }
+
+ ///
+ /// Creates from relevant mouse data.
+ ///
+ /// First Windows Message parameter.
+ /// A MouseStruct containing information from which to construct MouseEventExtArgs.
+ /// A new MouseEventExtArgs object.
+ private static MouseEventExtArgs FromRawDataUniversal(IntPtr wParam, MouseStruct mouseInfo, int mSwapButton)
+ {
+ var button = MouseButtons.None;
+ short mouseDelta = 0;
+ var clickCount = 0;
+
+ var isMouseButtonDown = false;
+ var isMouseButtonUp = false;
+ var isHorizontalWheel = false;
+
+
+ switch ((long) wParam)
+ {
+ case Messages.WM_LBUTTONDOWN:
+ isMouseButtonDown = true;
+ button = MouseButtons.Left;
+ clickCount = 1;
+ break;
+ case Messages.WM_LBUTTONUP:
+ isMouseButtonUp = true;
+ button = MouseButtons.Left;
+ clickCount = 1;
+ break;
+ case Messages.WM_LBUTTONDBLCLK:
+ isMouseButtonDown = true;
+ button = MouseButtons.Left;
+ clickCount = 2;
+ break;
+ case Messages.WM_RBUTTONDOWN:
+ isMouseButtonDown = true;
+ button = MouseButtons.Right;
+ clickCount = 1;
+ break;
+ case Messages.WM_RBUTTONUP:
+ isMouseButtonUp = true;
+ button = MouseButtons.Right;
+ clickCount = 1;
+ break;
+ case Messages.WM_RBUTTONDBLCLK:
+ isMouseButtonDown = true;
+ button = MouseButtons.Right;
+ clickCount = 2;
+ break;
+ case Messages.WM_MBUTTONDOWN:
+ isMouseButtonDown = true;
+ button = MouseButtons.Middle;
+ clickCount = 1;
+ break;
+ case Messages.WM_MBUTTONUP:
+ isMouseButtonUp = true;
+ button = MouseButtons.Middle;
+ clickCount = 1;
+ break;
+ case Messages.WM_MBUTTONDBLCLK:
+ isMouseButtonDown = true;
+ button = MouseButtons.Middle;
+ clickCount = 2;
+ break;
+ case Messages.WM_MOUSEWHEEL:
+ isHorizontalWheel = false;
+ mouseDelta = mouseInfo.MouseData;
+ break;
+ case Messages.WM_MOUSEHWHEEL:
+ isHorizontalWheel = true;
+ mouseDelta = mouseInfo.MouseData;
+ break;
+ case Messages.WM_XBUTTONDOWN:
+ button = mouseInfo.MouseData == 1
+ ? MouseButtons.XButton1
+ : MouseButtons.XButton2;
+ isMouseButtonDown = true;
+ clickCount = 1;
+ break;
+ case Messages.WM_XBUTTONUP:
+ button = mouseInfo.MouseData == 1
+ ? MouseButtons.XButton1
+ : MouseButtons.XButton2;
+ isMouseButtonUp = true;
+ clickCount = 1;
+ break;
+ case Messages.WM_XBUTTONDBLCLK:
+ isMouseButtonDown = true;
+ button = mouseInfo.MouseData == 1
+ ? MouseButtons.XButton1
+ : MouseButtons.XButton2;
+ clickCount = 2;
+ break;
+ }
+
+ if (mSwapButton > 0)
+ {
+ button = button == MouseButtons.Left ? MouseButtons.Right : button == MouseButtons.Right ? MouseButtons.Left : button;
+ }
+
+ var e = new MouseEventExtArgs(
+ button,
+ clickCount,
+ mouseInfo.Point,
+ mouseDelta,
+ mouseInfo.Timestamp,
+ isMouseButtonDown,
+ isMouseButtonUp,
+ isHorizontalWheel);
+
+ return e;
+ }
+
+ internal MouseEventExtArgs ToDoubleClickEventArgs()
+ {
+ return new MouseEventExtArgs(Button, 2, Point, Delta, Timestamp, IsMouseButtonDown, IsMouseButtonUp, IsHorizontalWheel);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Common/MouseKeyHook/MouseKeyHook.csproj b/Common/MouseKeyHook/MouseKeyHook.csproj
new file mode 100644
index 00000000..88e838c6
--- /dev/null
+++ b/Common/MouseKeyHook/MouseKeyHook.csproj
@@ -0,0 +1,70 @@
+
+
+ Local
+ 9.0.21022
+ {F52AA97E-180A-40ED-8F2B-09080171D6C7}
+ Debug
+ AnyCPU
+ MouseKeyHook
+ Gma.System.MouseKeyHook
+ 5.7.1
+ false
+ Library
+ net8.0-windows10.0.22621.0
+ true
+ Gma.System.MouseKeyHook
+ true
+ George Mamaladze
+
+ This library attaches to windows global hooks, tracks keyboard and mouse clicks and movement and raises common .NET events with KeyEventArgs and MouseEventArgs, so you can easily retrieve any information you need:
+
+ * Mouse coordinates
+ * Mouse buttons clicked
+ * Mouse wheel scrolls
+ * Key presses and releases
+ * Special key states
+ * [NEW] Key combinations and sequences
+
+ Additionally, there is a possibility to supress certain keyboard or mouse clicks, or detect special key combinations.
+
+ (c) George Mamaladze 2000-2023
+ MouseKeyHook
+
+ https://github.com/gmamaladze/globalmousekeyhook
+ keyboard mouse hook event global spy
+ MIT
+ https://github.com/gmamaladze/globalmousekeyhook
+ git
+
+
+
+ false
+ 285212672
+ false
+ DEBUG;TRACE
+ true
+ 4096
+ false
+ false
+ false
+ false
+ false
+ 4
+ full
+
+
+ false
+ 285212672
+ false
+ TRACE
+ false
+ 4096
+ false
+ true
+ false
+ false
+ false
+ 4
+ none
+
+
\ No newline at end of file
diff --git a/Common/MouseKeyHook/Properties/AssemblyInfo.cs b/Common/MouseKeyHook/Properties/AssemblyInfo.cs
new file mode 100644
index 00000000..61274ec3
--- /dev/null
+++ b/Common/MouseKeyHook/Properties/AssemblyInfo.cs
@@ -0,0 +1,7 @@
+// This code is distributed under MIT license.
+// Copyright (c) 2015 George Mamaladze
+// See license.txt or https://mit-license.org/
+
+using System.Runtime.InteropServices;
+
+[assembly: ComVisible(false)]
\ No newline at end of file
diff --git a/Common/MouseKeyHook/Sequence.cs b/Common/MouseKeyHook/Sequence.cs
new file mode 100644
index 00000000..5dc9527f
--- /dev/null
+++ b/Common/MouseKeyHook/Sequence.cs
@@ -0,0 +1,42 @@
+// This code is distributed under MIT license.
+// Copyright (c) 2010-2018 George Mamaladze
+// See license.txt or https://mit-license.org/
+
+using System.Linq;
+
+namespace Gma.System.MouseKeyHook
+{
+ ///
+ /// Describes key or key combination sequences. e.g. Control+Z,Z
+ ///
+ public class Sequence : SequenceBase
+ {
+ private Sequence(Combination[] combinations) : base(combinations)
+ {
+ }
+
+ ///
+ /// Creates an instance of sequence object from parameters representing keys or key combinations.
+ ///
+ ///
+ ///
+ public static Sequence Of(params Combination[] combinations)
+ {
+ return new Sequence(combinations);
+ }
+
+ ///
+ /// Creates an instance of sequnce object from string.
+ /// The string must contain comma ',' delimited list of strings describing keys or key combinations.
+ /// Examples: 'A,B,C' 'Alt+R,S', 'Shift+R,Alt+K'
+ ///
+ ///
+ ///
+ public static Sequence FromString(string text)
+ {
+ var parts = text.Split(',');
+ var combinations = parts.Select(Combination.FromString).ToArray();
+ return new Sequence(combinations);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Common/MouseKeyHook/SequenceBase.cs b/Common/MouseKeyHook/SequenceBase.cs
new file mode 100644
index 00000000..a3de2561
--- /dev/null
+++ b/Common/MouseKeyHook/SequenceBase.cs
@@ -0,0 +1,82 @@
+// This code is distributed under MIT license.
+// Copyright (c) 2010-2018 George Mamaladze
+// See license.txt or https://mit-license.org/
+
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Gma.System.MouseKeyHook
+{
+ ///
+ /// Describes a sequence of generic objects.
+ ///
+ ///
+ public abstract class SequenceBase : IEnumerable
+ {
+ private readonly T[] _elements;
+
+ ///
+ /// Creates an instance of sequnce from sequnce elements.
+ ///
+ ///
+ protected SequenceBase(params T[] elements)
+ {
+ _elements = elements;
+ }
+
+ ///
+ /// Number of elements in the sequnce.
+ ///
+ public int Length
+ {
+ get { return _elements.Length; }
+ }
+
+ ///
+ public IEnumerator GetEnumerator()
+ {
+ return _elements.Cast().GetEnumerator();
+ }
+
+ ///
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return GetEnumerator();
+ }
+
+ ///
+ public override string ToString()
+ {
+ return string.Join(",", _elements);
+ }
+
+ ///
+ protected bool Equals(SequenceBase other)
+ {
+ if (_elements.Length != other._elements.Length) return false;
+ return _elements.SequenceEqual(other._elements);
+ }
+
+ ///
+ public override bool Equals(object obj)
+ {
+ if (ReferenceEquals(null, obj)) return false;
+ if (ReferenceEquals(this, obj)) return true;
+ if (obj.GetType() != GetType()) return false;
+ return Equals((SequenceBase) obj);
+ }
+
+ ///
+ public override int GetHashCode()
+ {
+ unchecked
+ {
+ return (_elements.Length + 13) ^
+ ((_elements.Length != 0
+ ? _elements[0].GetHashCode() ^ _elements[_elements.Length - 1].GetHashCode()
+ : 0) * 397);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Common/MouseKeyHook/WinApi/AppMouseStruct.cs b/Common/MouseKeyHook/WinApi/AppMouseStruct.cs
new file mode 100644
index 00000000..37dc8b17
--- /dev/null
+++ b/Common/MouseKeyHook/WinApi/AppMouseStruct.cs
@@ -0,0 +1,67 @@
+// This code is distributed under MIT license.
+// Copyright (c) 2015 George Mamaladze
+// See license.txt or https://mit-license.org/
+
+using System;
+using System.Runtime.InteropServices;
+
+namespace Gma.System.MouseKeyHook.WinApi
+{
+ ///
+ /// The AppMouseStruct structure contains information about a application-level mouse input event.
+ ///
+ ///
+ /// See full documentation at http://globalmousekeyhook.codeplex.com/wikipage?title=MouseStruct
+ ///
+ [StructLayout(LayoutKind.Explicit)]
+ internal struct AppMouseStruct
+ {
+ ///
+ /// Specifies a Point structure that contains the X- and Y-coordinates of the cursor, in screen coordinates.
+ ///
+ [FieldOffset(0x00)] public Point Point;
+
+ ///
+ /// Specifies information associated with the message.
+ ///
+ ///
+ /// The possible values are:
+ ///
+ /// -
+ /// 0 - No Information
+ ///
+ /// -
+ /// 1 - X-Button1 Click
+ ///
+ /// -
+ /// 2 - X-Button2 Click
+ ///
+ /// -
+ /// 120 - Mouse Scroll Away from User
+ ///
+ /// -
+ /// -120 - Mouse Scroll Toward User
+ ///
+ ///
+ ///
+ [FieldOffset(0x16)] public short MouseData_x86;
+
+ [FieldOffset(0x22)] public short MouseData_x64;
+
+ ///
+ /// Converts the current into a .
+ ///
+ ///
+ ///
+ /// The AppMouseStruct does not have a timestamp, thus one is generated at the time of this call.
+ ///
+ public MouseStruct ToMouseStruct()
+ {
+ var tmp = new MouseStruct();
+ tmp.Point = Point;
+ tmp.MouseData = IntPtr.Size == 4 ? MouseData_x86 : MouseData_x64;
+ tmp.Timestamp = Environment.TickCount;
+ return tmp;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Common/MouseKeyHook/WinApi/CallbackData.cs b/Common/MouseKeyHook/WinApi/CallbackData.cs
new file mode 100644
index 00000000..f90ed9d8
--- /dev/null
+++ b/Common/MouseKeyHook/WinApi/CallbackData.cs
@@ -0,0 +1,24 @@
+// This code is distributed under MIT license.
+// Copyright (c) 2015 George Mamaladze
+// See license.txt or https://mit-license.org/
+
+using System;
+
+namespace Gma.System.MouseKeyHook.WinApi
+{
+ internal struct CallbackData
+ {
+ public CallbackData(IntPtr wParam, IntPtr lParam, int mSwapButton = 0)
+ {
+ WParam = wParam;
+ LParam = lParam;
+ MSwapButton = mSwapButton;
+ }
+
+ public IntPtr WParam { get; }
+
+ public IntPtr LParam { get; }
+
+ public int MSwapButton{ get; set; }
+ }
+}
\ No newline at end of file
diff --git a/Common/MouseKeyHook/WinApi/HookHelper.cs b/Common/MouseKeyHook/WinApi/HookHelper.cs
new file mode 100644
index 00000000..a225c24f
--- /dev/null
+++ b/Common/MouseKeyHook/WinApi/HookHelper.cs
@@ -0,0 +1,96 @@
+// This code is distributed under MIT license.
+// Copyright (c) 2015 George Mamaladze
+// See license.txt or https://mit-license.org/
+
+using System;
+using System.ComponentModel;
+using System.Diagnostics;
+using System.Runtime.InteropServices;
+using Gma.System.MouseKeyHook.Implementation;
+
+namespace Gma.System.MouseKeyHook.WinApi
+{
+ internal static class HookHelper
+ {
+ private static HookProcedure _appHookProc;
+ private static HookProcedure _globalHookProc;
+
+ public static HookResult HookAppMouse(Callback callback)
+ {
+ return HookApp(HookIds.WH_MOUSE, callback);
+ }
+
+ public static HookResult HookAppKeyboard(Callback callback)
+ {
+ return HookApp(HookIds.WH_KEYBOARD, callback);
+ }
+
+ public static HookResult HookGlobalMouse(Callback callback)
+ {
+ return HookGlobal(HookIds.WH_MOUSE_LL, callback);
+ }
+
+ public static HookResult HookGlobalKeyboard(Callback callback)
+ {
+ return HookGlobal(HookIds.WH_KEYBOARD_LL, callback);
+ }
+
+ private static HookResult HookApp(int hookId, Callback callback)
+ {
+ _appHookProc = (code, param, lParam) => HookProcedure(code, param, lParam, callback);
+
+ var hookHandle = HookNativeMethods.SetWindowsHookEx(
+ hookId,
+ _appHookProc,
+ IntPtr.Zero,
+ ThreadNativeMethods.GetCurrentThreadId());
+
+ if (hookHandle.IsInvalid)
+ ThrowLastUnmanagedErrorAsException();
+
+ return new HookResult(hookHandle, _appHookProc);
+ }
+
+ private static HookResult HookGlobal(int hookId, Callback callback)
+ {
+ _globalHookProc = (code, param, lParam) => HookProcedure(code, param, lParam, callback);
+
+ var hookHandle = HookNativeMethods.SetWindowsHookEx(
+ hookId,
+ _globalHookProc,
+ Process.GetCurrentProcess().MainModule.BaseAddress,
+ 0);
+
+ if (hookHandle.IsInvalid)
+ ThrowLastUnmanagedErrorAsException();
+
+ return new HookResult(hookHandle, _globalHookProc);
+ }
+
+ private static IntPtr HookProcedure(int nCode, IntPtr wParam, IntPtr lParam, Callback callback)
+ {
+ var passThrough = nCode != 0;
+ if (passThrough)
+ return CallNextHookEx(nCode, wParam, lParam);
+
+ var callbackData = new CallbackData(wParam, lParam);
+ var continueProcessing = callback(callbackData);
+
+ if (!continueProcessing)
+ return new IntPtr(-1);
+
+ return CallNextHookEx(nCode, wParam, lParam);
+ }
+
+ private static IntPtr CallNextHookEx(int nCode, IntPtr wParam, IntPtr lParam)
+ {
+ return HookNativeMethods.CallNextHookEx(IntPtr.Zero, nCode, wParam, lParam);
+ }
+
+ private static void ThrowLastUnmanagedErrorAsException()
+ {
+ var errorCode = Marshal.GetLastWin32Error();
+ throw new Win32Exception(errorCode);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Common/MouseKeyHook/WinApi/HookIds.cs b/Common/MouseKeyHook/WinApi/HookIds.cs
new file mode 100644
index 00000000..adda76f4
--- /dev/null
+++ b/Common/MouseKeyHook/WinApi/HookIds.cs
@@ -0,0 +1,30 @@
+// This code is distributed under MIT license.
+// Copyright (c) 2015 George Mamaladze
+// See license.txt or https://mit-license.org/
+
+namespace Gma.System.MouseKeyHook.WinApi
+{
+ internal static class HookIds
+ {
+ ///
+ /// Installs a hook procedure that monitors mouse messages. For more information, see the MouseProc hook procedure.
+ ///
+ internal const int WH_MOUSE = 7;
+
+ ///
+ /// Installs a hook procedure that monitors keystroke messages. For more information, see the KeyboardProc hook
+ /// procedure.
+ ///
+ internal const int WH_KEYBOARD = 2;
+
+ ///
+ /// Windows NT/2000/XP/Vista/7: Installs a hook procedure that monitors low-level mouse input events.
+ ///
+ internal const int WH_MOUSE_LL = 14;
+
+ ///
+ /// Windows NT/2000/XP/Vista/7: Installs a hook procedure that monitors low-level keyboard input events.
+ ///
+ internal const int WH_KEYBOARD_LL = 13;
+ }
+}
\ No newline at end of file
diff --git a/Common/MouseKeyHook/WinApi/HookNativeMethods.cs b/Common/MouseKeyHook/WinApi/HookNativeMethods.cs
new file mode 100644
index 00000000..01b6eedc
--- /dev/null
+++ b/Common/MouseKeyHook/WinApi/HookNativeMethods.cs
@@ -0,0 +1,89 @@
+// This code is distributed under MIT license.
+// Copyright (c) 2015 George Mamaladze
+// See license.txt or https://mit-license.org/
+
+using System;
+using System.Runtime.InteropServices;
+
+namespace Gma.System.MouseKeyHook.WinApi
+{
+ internal static class HookNativeMethods
+ {
+ ///
+ /// The CallNextHookEx function passes the hook information to the next hook procedure in the current hook chain.
+ /// A hook procedure can call this function either before or after processing the hook information.
+ ///
+ /// This parameter is ignored.
+ /// [in] Specifies the hook code passed to the current hook procedure.
+ /// [in] Specifies the wParam value passed to the current hook procedure.
+ /// [in] Specifies the lParam value passed to the current hook procedure.
+ /// This value is returned by the next hook procedure in the chain.
+ ///
+ /// http://msdn.microsoft.com/library/default.asp?url=/library/en-us/winui/winui/windowsuserinterface/windowing/hooks/hookreference/hookfunctions/setwindowshookex.asp
+ ///
+ [DllImport("user32.dll", CharSet = CharSet.Auto,
+ CallingConvention = CallingConvention.StdCall)]
+ internal static extern IntPtr CallNextHookEx(
+ IntPtr idHook,
+ int nCode,
+ IntPtr wParam,
+ IntPtr lParam);
+
+ ///
+ /// The SetWindowsHookEx function installs an application-defined hook procedure into a hook chain.
+ /// You would install a hook procedure to monitor the system for certain types of events. These events
+ /// are associated either with a specific thread or with all threads in the same desktop as the calling thread.
+ ///
+ ///
+ /// [in] Specifies the type of hook procedure to be installed. This parameter can be one of the following values.
+ ///
+ ///
+ /// [in] Pointer to the hook procedure. If the dwThreadId parameter is zero or specifies the identifier of a
+ /// thread created by a different process, the lpfn parameter must point to a hook procedure in a dynamic-link
+ /// library (DLL). Otherwise, lpfn can point to a hook procedure in the code associated with the current process.
+ ///
+ ///
+ /// [in] Handle to the DLL containing the hook procedure pointed to by the lpfn parameter.
+ /// The hMod parameter must be set to NULL if the dwThreadId parameter specifies a thread created by
+ /// the current process and if the hook procedure is within the code associated with the current process.
+ ///
+ ///
+ /// [in] Specifies the identifier of the thread with which the hook procedure is to be associated.
+ /// If this parameter is zero, the hook procedure is associated with all existing threads running in the
+ /// same desktop as the calling thread.
+ ///
+ ///
+ /// If the function succeeds, the return value is the handle to the hook procedure.
+ /// If the function fails, the return value is NULL. To get extended error information, call GetLastError.
+ ///
+ ///
+ /// http://msdn.microsoft.com/library/default.asp?url=/library/en-us/winui/winui/windowsuserinterface/windowing/hooks/hookreference/hookfunctions/setwindowshookex.asp
+ ///
+ [DllImport("user32.dll", CharSet = CharSet.Auto,
+ CallingConvention = CallingConvention.StdCall, SetLastError = true)]
+ internal static extern HookProcedureHandle SetWindowsHookEx(
+ int idHook,
+ HookProcedure lpfn,
+ IntPtr hMod,
+ int dwThreadId);
+
+ ///
+ /// The UnhookWindowsHookEx function removes a hook procedure installed in a hook chain by the SetWindowsHookEx
+ /// function.
+ ///
+ ///
+ /// [in] Handle to the hook to be removed. This parameter is a hook handle obtained by a previous call to
+ /// SetWindowsHookEx.
+ ///
+ ///
+ /// 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.
+ ///
+ ///
+ /// http://msdn.microsoft.com/library/default.asp?url=/library/en-us/winui/winui/windowsuserinterface/windowing/hooks/hookreference/hookfunctions/setwindowshookex.asp
+ ///
+ [DllImport("user32.dll", CharSet = CharSet.Auto,
+ CallingConvention = CallingConvention.StdCall, SetLastError = true)]
+ internal static extern int UnhookWindowsHookEx(IntPtr idHook);
+ }
+}
\ No newline at end of file
diff --git a/Common/MouseKeyHook/WinApi/HookProcedure.cs b/Common/MouseKeyHook/WinApi/HookProcedure.cs
new file mode 100644
index 00000000..5644f3ab
--- /dev/null
+++ b/Common/MouseKeyHook/WinApi/HookProcedure.cs
@@ -0,0 +1,40 @@
+// This code is distributed under MIT license.
+// Copyright (c) 2015 George Mamaladze
+// See license.txt or https://mit-license.org/
+
+using System;
+
+namespace Gma.System.MouseKeyHook.WinApi
+{
+ ///
+ /// The CallWndProc hook procedure is an application-defined or library-defined callback
+ /// function used with the SetWindowsHookEx function. The HOOKPROC type defines a pointer
+ /// to this callback function. CallWndProc is a placeholder for the application-defined
+ /// or library-defined function name.
+ ///
+ ///
+ /// [in] Specifies whether the hook procedure must process the message.
+ /// If nCode is HC_ACTION, the hook procedure must process the message.
+ /// If nCode is less than zero, the hook procedure must pass the message to the
+ /// CallNextHookEx function without further processing and must return the
+ /// value returned by CallNextHookEx.
+ ///
+ ///
+ /// [in] Specifies whether the message was sent by the current thread.
+ /// If the message was sent by the current thread, it is nonzero; otherwise, it is zero.
+ ///
+ ///
+ /// [in] Pointer to a CWPSTRUCT structure that contains details about the message.
+ ///
+ ///
+ /// If nCode is less than zero, the hook procedure must return the value returned by CallNextHookEx.
+ /// If nCode is greater than or equal to zero, it is highly recommended that you call CallNextHookEx
+ /// and return the value it returns; otherwise, other applications that have installed WH_CALLWNDPROC
+ /// hooks will not receive hook notifications and may behave incorrectly as a result. If the hook
+ /// procedure does not call CallNextHookEx, the return value should be zero.
+ ///
+ ///
+ /// http://msdn.microsoft.com/library/default.asp?url=/library/en-us/winui/winui/windowsuserinterface/windowing/hooks/hookreference/hookfunctions/callwndproc.asp
+ ///
+ public delegate IntPtr HookProcedure(int nCode, IntPtr wParam, IntPtr lParam);
+}
\ No newline at end of file
diff --git a/Common/MouseKeyHook/WinApi/HookProcedureHandle.cs b/Common/MouseKeyHook/WinApi/HookProcedureHandle.cs
new file mode 100644
index 00000000..dacdd05b
--- /dev/null
+++ b/Common/MouseKeyHook/WinApi/HookProcedureHandle.cs
@@ -0,0 +1,37 @@
+// This code is distributed under MIT license.
+// Copyright (c) 2015 George Mamaladze
+// See license.txt or https://mit-license.org/
+
+using System.Windows.Forms;
+using Microsoft.Win32.SafeHandles;
+
+namespace Gma.System.MouseKeyHook.WinApi
+{
+ internal class HookProcedureHandle : SafeHandleZeroOrMinusOneIsInvalid
+ {
+ //private static bool _closing;
+
+ static HookProcedureHandle()
+ {
+ //Application.ApplicationExit += (sender, e) => { HookProcedureHandle._closing = true; };
+ }
+
+ public HookProcedureHandle()
+ : base(true)
+ {
+ }
+
+ protected override bool ReleaseHandle()
+ {
+ //NOTE Calling Unhook during processexit causes deley
+ var ret = HookNativeMethods.UnhookWindowsHookEx(handle);
+ if (ret != 0)
+ {
+ base.Dispose();
+ return true;
+ }
+ else
+ return true;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Common/MouseKeyHook/WinApi/HookResult.cs b/Common/MouseKeyHook/WinApi/HookResult.cs
new file mode 100644
index 00000000..32211f94
--- /dev/null
+++ b/Common/MouseKeyHook/WinApi/HookResult.cs
@@ -0,0 +1,26 @@
+// This code is distributed under MIT license.
+// Copyright (c) 2015 George Mamaladze
+// See license.txt or https://mit-license.org/
+
+using System;
+
+namespace Gma.System.MouseKeyHook.WinApi
+{
+ internal class HookResult : IDisposable
+ {
+ public HookResult(HookProcedureHandle handle, HookProcedure procedure)
+ {
+ Handle = handle;
+ Procedure = procedure;
+ }
+
+ public HookProcedureHandle Handle { get; }
+
+ public HookProcedure Procedure { get; }
+
+ public void Dispose()
+ {
+ Handle.Dispose();
+ }
+ }
+}
\ No newline at end of file
diff --git a/Common/MouseKeyHook/WinApi/HotkeysNativeMethods.cs b/Common/MouseKeyHook/WinApi/HotkeysNativeMethods.cs
new file mode 100644
index 00000000..2c32ee4e
--- /dev/null
+++ b/Common/MouseKeyHook/WinApi/HotkeysNativeMethods.cs
@@ -0,0 +1,51 @@
+using System;
+using System.Runtime.InteropServices;
+
+namespace Gma.System.MouseKeyHook.WinApi
+{
+ internal static class HotkeysNativeMethods
+ {
+ ///
+ /// Defines a system-wide hot key.
+ ///
+ ///
+ /// A handle to the window that will receive WM_HOTKEY messages generated by the hot key. If this parameter is NULL,
+ /// WM_HOTKEY messages are posted to the message queue of the calling thread and must be processed in the message loop.
+ ///
+ ///
+ /// The identifier of the hot key. If the hWnd parameter is NULL, then the hot key is associated with the current
+ /// thread rather than with a particular window. If a hot key already exists with the same hWnd and id parameters, see
+ /// Remarks for the action taken.
+ ///
+ ///
+ /// The keys that must be pressed in combination with the key specified by the uVirtKey parameter in order to generate
+ /// the WM_HOTKEY message. The fsModifiers parameter can be a combination of the following values.
+ ///
+ ///
+ /// The virtual-key code of the hot key. See Virtual Key Codes.
+ ///
+ ///
+ /// 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.
+ ///
+ [DllImport("user32.dll")]
+ public static extern int RegisterHotKey(IntPtr hwnd, int id, int fsModifiers, int vk);
+
+ ///
+ /// Frees a hot key previously registered by the calling thread.
+ ///
+ ///
+ /// A handle to the window associated with the hot key to be freed. This parameter should be NULL if the hot key is not
+ /// associated with a window.
+ ///
+ ///
+ /// The identifier of the hot key to be freed.
+ ///
+ ///
+ /// 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.
+ ///
+ [DllImport("user32.dll")]
+ public static extern bool UnregisterHotKey(IntPtr hwnd, int id);
+ }
+}
\ No newline at end of file
diff --git a/Common/MouseKeyHook/WinApi/KeyboardHookStruct.cs b/Common/MouseKeyHook/WinApi/KeyboardHookStruct.cs
new file mode 100644
index 00000000..dd4da19f
--- /dev/null
+++ b/Common/MouseKeyHook/WinApi/KeyboardHookStruct.cs
@@ -0,0 +1,43 @@
+// This code is distributed under MIT license.
+// Copyright (c) 2015 George Mamaladze
+// See license.txt or https://mit-license.org/
+
+using System.Runtime.InteropServices;
+
+namespace Gma.System.MouseKeyHook.WinApi
+{
+ ///
+ /// The KeyboardHookStruct structure contains information about a low-level keyboard input event.
+ ///
+ ///
+ /// http://msdn.microsoft.com/library/default.asp?url=/library/en-us/winui/winui/windowsuserinterface/windowing/hooks/hookreference/hookstructures/cwpstruct.asp
+ ///
+ [StructLayout(LayoutKind.Sequential)]
+ internal struct KeyboardHookStruct
+ {
+ ///
+ /// Specifies a virtual-key code. The code must be a value in the range 1 to 254.
+ ///
+ public int VirtualKeyCode;
+
+ ///
+ /// Specifies a hardware scan code for the key.
+ ///
+ public int ScanCode;
+
+ ///
+ /// Specifies the extended-key flag, event-injected flag, context code, and transition-state flag.
+ ///
+ public int Flags;
+
+ ///
+ /// Specifies the Time stamp for this message.
+ ///
+ public int Time;
+
+ ///
+ /// Specifies extra information associated with the message.
+ ///
+ public int ExtraInfo;
+ }
+}
\ No newline at end of file
diff --git a/Common/MouseKeyHook/WinApi/KeyboardNativeMethods.cs b/Common/MouseKeyHook/WinApi/KeyboardNativeMethods.cs
new file mode 100644
index 00000000..fd038c80
--- /dev/null
+++ b/Common/MouseKeyHook/WinApi/KeyboardNativeMethods.cs
@@ -0,0 +1,376 @@
+// This code is distributed under MIT license.
+// Copyright (c) 2015 George Mamaladze
+// See license.txt or https://mit-license.org/
+
+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;
+
+ private static int lastScanCode;
+ private static byte[] lastKeyState = new byte[255];
+ private static bool lastIsDead;
+
+ ///
+ /// Translates a virtual key to its character equivalent using the current keyboard layout without knowing the
+ /// scancode in advance.
+ ///
+ ///
+ ///
+ ///
+ ///
+ internal static void TryGetCharFromKeyboardState(int virtualKeyCode, int fuState, out char[] chars)
+ {
+ var dwhkl = GetActiveKeyboard();
+ var scanCode = MapVirtualKeyEx(virtualKeyCode, (int) MapType.MAPVK_VK_TO_VSC, dwhkl);
+ TryGetCharFromKeyboardState(virtualKeyCode, scanCode, fuState, dwhkl, out chars);
+ }
+
+ ///
+ /// Translates a virtual key to its character equivalent using the current keyboard layout
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ 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);
+ }
+
+ ///
+ /// Translates a virtual key to its character equivalent using a specified keyboard layout
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ internal static void TryGetCharFromKeyboardState(int virtualKeyCode, int scanCode, int fuState, IntPtr dwhkl,
+ out char[] chars)
+ {
+ var pwszBuff = new StringBuilder(64);
+ var keyboardState = KeyboardState.GetCurrent();
+ var currentKeyboardState = keyboardState.GetNativeState();
+ var 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)
+ {
+ var 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
+ {
+ var lpKeyStateNull = new byte[255];
+ rc = ToUnicodeEx(vk, sc, lpKeyStateNull, sb, sb.Capacity, 0, hkl);
+ } while (rc < 0);
+ }
+
+ ///
+ /// 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.
+ ///
+ /// HKL
+ private static IntPtr GetActiveKeyboard()
+ {
+ var hActiveWnd = ThreadNativeMethods.GetForegroundWindow(); //handle to focused window
+ int dwProcessId;
+ var hCurrentWnd = ThreadNativeMethods.GetWindowThreadProcessId(hActiveWnd, out dwProcessId);
+ //thread of focused window
+ return GetKeyboardLayout(hCurrentWnd); //get the layout identifier for the thread whose window is focused
+ }
+
+ ///
+ /// 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.
+ ///
+ ///
+ /// [in] Specifies the virtual-key code to be translated.
+ ///
+ ///
+ /// [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).
+ ///
+ ///
+ /// [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.
+ ///
+ ///
+ /// [out] Pointer to the buffer that receives the translated character or characters.
+ ///
+ ///
+ /// [in] Specifies whether a menu is active. This parameter must be 1 if a menu is active, or 0 otherwise.
+ ///
+ ///
+ /// 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.
+ ///
+ ///
+ /// http://msdn.microsoft.com/library/default.asp?url=/library/en-us/winui/winui/windowsuserinterface/userinput/keyboardinput/keyboardinputreference/keyboardinputfunctions/toascii.asp
+ ///
+ [Obsolete("Use ToUnicodeEx instead")]
+ [DllImport("user32.dll")]
+ public static extern int ToAscii(
+ int uVirtKey,
+ int uScanCode,
+ byte[] lpbKeyState,
+ byte[] lpwTransKey,
+ int fuState);
+
+ ///
+ /// Translates the specified virtual-key code and keyboard state to the corresponding Unicode character or characters.
+ ///
+ /// [in] The virtual-key code to be translated.
+ ///
+ /// [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.
+ ///
+ ///
+ /// [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.
+ ///
+ ///
+ /// [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.
+ ///
+ /// [in] The size, in characters, of the buffer pointed to by the pwszBuff parameter.
+ ///
+ /// [in] The behavior of the function. If bit 0 is set, a menu is active. Bits 1 through 31 are
+ /// reserved.
+ ///
+ /// The input locale identifier used to translate the specified code.
+ ///
+ /// -1 <= return <= n
+ ///
+ /// -
+ /// -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).
+ ///
+ /// -
+ /// 0 = The specified virtual key has no translation for the current state of the keyboard. Nothing was
+ /// written to the buffer specified by pwszBuff.
+ ///
+ /// - 1 = One character was written to the buffer specified by pwszBuff.
+ /// -
+ /// 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.
+ ///
+ ///
+ ///
+ [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);
+
+ ///
+ /// The GetKeyboardState function copies the status of the 256 virtual keys to the
+ /// specified buffer.
+ ///
+ ///
+ /// [in] Pointer to a 256-byte array that contains keyboard key states.
+ ///
+ ///
+ /// 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.
+ ///
+ ///
+ /// http://msdn.microsoft.com/library/default.asp?url=/library/en-us/winui/winui/windowsuserinterface/userinput/keyboardinput/keyboardinputreference/keyboardinputfunctions/toascii.asp
+ ///
+ [DllImport("user32.dll")]
+ public static extern int GetKeyboardState(byte[] pbKeyState);
+
+ ///
+ /// 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).
+ ///
+ ///
+ /// [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.
+ ///
+ ///
+ /// 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.
+ ///
+ /// http://msdn.microsoft.com/en-us/library/ms646301.aspx
+ [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
+ public static extern short GetKeyState(int vKey);
+
+ ///
+ /// Translates (maps) a virtual-key code into a scan code or character value, or translates a scan code into a
+ /// virtual-key code.
+ ///
+ ///
+ /// [in] The virtual key code or scan code for a key. How this value is interpreted depends on the
+ /// value of the uMapType parameter.
+ ///
+ ///
+ /// [in] The translation to be performed. The value of this parameter depends on the value of the
+ /// uCode parameter.
+ ///
+ /// [in] The input locale identifier used to translate the specified code.
+ ///
+ [DllImport("user32.dll", CharSet = CharSet.Auto)]
+ internal static extern int MapVirtualKeyEx(int uCode, int uMapType, IntPtr dwhkl);
+
+ ///
+ /// 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.
+ ///
+ /// [in] The identifier of the thread to query, or 0 for the current thread.
+ ///
+ /// 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.
+ ///
+ [DllImport("user32.dll", CharSet = CharSet.Auto)]
+ internal static extern IntPtr GetKeyboardLayout(int dwLayout);
+
+ ///
+ /// MapVirtualKeys uMapType
+ ///
+ internal enum MapType
+ {
+ ///
+ /// 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.
+ ///
+ MAPVK_VK_TO_VSC,
+
+ ///
+ /// 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.
+ ///
+ MAPVK_VSC_TO_VK,
+
+ ///
+ /// 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.
+ ///
+ MAPVK_VK_TO_CHAR,
+
+ ///
+ /// 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.
+ ///
+ MAPVK_VSC_TO_VK_EX
+ }
+ }
+}
\ No newline at end of file
diff --git a/Common/MouseKeyHook/WinApi/Messages.cs b/Common/MouseKeyHook/WinApi/Messages.cs
new file mode 100644
index 00000000..82557fdd
--- /dev/null
+++ b/Common/MouseKeyHook/WinApi/Messages.cs
@@ -0,0 +1,123 @@
+// This code is distributed under MIT license.
+// Copyright (c) 2015 George Mamaladze
+// See license.txt or https://mit-license.org/
+
+namespace Gma.System.MouseKeyHook.WinApi
+{
+ internal static class Messages
+ {
+ //values from Winuser.h in Microsoft SDK.
+
+ ///
+ /// The WM_MOUSEMOVE message is posted to a window when the cursor moves.
+ ///
+ public const int WM_MOUSEMOVE = 0x200;
+
+ ///
+ /// The WM_LBUTTONDOWN message is posted when the user presses the left mouse button
+ ///
+ public const int WM_LBUTTONDOWN = 0x201;
+
+ ///
+ /// The WM_RBUTTONDOWN message is posted when the user presses the right mouse button
+ ///
+ public const int WM_RBUTTONDOWN = 0x204;
+
+ ///
+ /// The WM_MBUTTONDOWN message is posted when the user presses the middle mouse button
+ ///
+ public const int WM_MBUTTONDOWN = 0x207;
+
+ ///
+ /// The WM_LBUTTONUP message is posted when the user releases the left mouse button
+ ///
+ public const int WM_LBUTTONUP = 0x202;
+
+ ///
+ /// The WM_RBUTTONUP message is posted when the user releases the right mouse button
+ ///
+ public const int WM_RBUTTONUP = 0x205;
+
+ ///
+ /// The WM_MBUTTONUP message is posted when the user releases the middle mouse button
+ ///
+ public const int WM_MBUTTONUP = 0x208;
+
+ ///
+ /// The WM_LBUTTONDBLCLK message is posted when the user double-clicks the left mouse button
+ ///
+ public const int WM_LBUTTONDBLCLK = 0x203;
+
+ ///
+ /// The WM_RBUTTONDBLCLK message is posted when the user double-clicks the right mouse button
+ ///
+ public const int WM_RBUTTONDBLCLK = 0x206;
+
+ ///
+ /// The WM_RBUTTONDOWN message is posted when the user presses the right mouse button
+ ///
+ public const int WM_MBUTTONDBLCLK = 0x209;
+
+ ///
+ /// The WM_MOUSEWHEEL message is posted when the user presses the mouse wheel.
+ ///
+ public const int WM_MOUSEWHEEL = 0x020A;
+
+ ///
+ /// The WM_XBUTTONDOWN message is posted when the user presses the first or second X mouse
+ /// button.
+ ///
+ public const int WM_XBUTTONDOWN = 0x20B;
+
+ ///
+ /// The WM_XBUTTONUP message is posted when the user releases the first or second X mouse
+ /// button.
+ ///
+ public const int WM_XBUTTONUP = 0x20C;
+
+ ///
+ /// The WM_XBUTTONDBLCLK message is posted when the user double-clicks the first or second
+ /// X mouse button.
+ ///
+ /// Only windows that have the CS_DBLCLKS style can receive WM_XBUTTONDBLCLK messages.
+ public const int WM_XBUTTONDBLCLK = 0x20D;
+
+ ///
+ /// The WM_MOUSEHWHEEL message Sent to the active window when the mouse's horizontal scroll
+ /// wheel is tilted or rotated.
+ ///
+ public const int WM_MOUSEHWHEEL = 0x20E;
+
+ ///
+ /// The WM_KEYDOWN message is posted to the window with the keyboard focus when a non-system
+ /// key is pressed. A non-system key is a key that is pressed when the ALT key is not pressed.
+ ///
+ public const int WM_KEYDOWN = 0x100;
+
+ ///
+ /// The WM_KEYUP message is posted to the window with the keyboard focus when a non-system
+ /// key is released. A non-system key is a key that is pressed when the ALT key is not pressed,
+ /// or a keyboard key that is pressed when a window has the keyboard focus.
+ ///
+ public const int WM_KEYUP = 0x101;
+
+ ///
+ /// The WM_SYSKEYDOWN message is posted to the window with the keyboard focus when the user
+ /// presses the F10 key (which activates the menu bar) or holds down the ALT key and then
+ /// presses another key. It also occurs when no window currently has the keyboard focus;
+ /// in this case, the WM_SYSKEYDOWN message is sent to the active window. The window that
+ /// receives the message can distinguish between these two contexts by checking the context
+ /// code in the lParam parameter.
+ ///
+ public const int WM_SYSKEYDOWN = 0x104;
+
+ ///
+ /// The WM_SYSKEYUP message is posted to the window with the keyboard focus when the user
+ /// releases a key that was pressed while the ALT key was held down. It also occurs when no
+ /// window currently has the keyboard focus; in this case, the WM_SYSKEYUP message is sent
+ /// to the active window. The window that receives the message can distinguish between
+ /// these two contexts by checking the context code in the lParam parameter.
+ ///
+ public const int WM_SYSKEYUP = 0x105;
+ }
+}
\ No newline at end of file
diff --git a/Common/MouseKeyHook/WinApi/MouseNativeMethods.cs b/Common/MouseKeyHook/WinApi/MouseNativeMethods.cs
new file mode 100644
index 00000000..eddc33d7
--- /dev/null
+++ b/Common/MouseKeyHook/WinApi/MouseNativeMethods.cs
@@ -0,0 +1,27 @@
+// This code is distributed under MIT license.
+// Copyright (c) 2015 George Mamaladze
+// See license.txt or https://mit-license.org/
+
+using System.Runtime.InteropServices;
+
+namespace Gma.System.MouseKeyHook.WinApi
+{
+ internal static class MouseNativeMethods
+ {
+ ///
+ /// The GetDoubleClickTime function retrieves the current double-click time for the mouse. A double-click is a series
+ /// of two clicks of the
+ /// mouse button, the second occurring within a specified time after the first. The double-click time is the maximum
+ /// number of
+ /// milliseconds that may occur between the first and second click of a double-click.
+ ///
+ ///
+ /// The return value specifies the current double-click time, in milliseconds.
+ ///
+ ///
+ /// http://msdn.microsoft.com/en-us/library/ms646258(VS.85).aspx
+ ///
+ [DllImport("user32")]
+ internal static extern int GetDoubleClickTime();
+ }
+}
\ No newline at end of file
diff --git a/Common/MouseKeyHook/WinApi/MouseStruct.cs b/Common/MouseKeyHook/WinApi/MouseStruct.cs
new file mode 100644
index 00000000..05c2c7e6
--- /dev/null
+++ b/Common/MouseKeyHook/WinApi/MouseStruct.cs
@@ -0,0 +1,53 @@
+// This code is distributed under MIT license.
+// Copyright (c) 2015 George Mamaladze
+// See license.txt or https://mit-license.org/
+
+using System.Runtime.InteropServices;
+
+namespace Gma.System.MouseKeyHook.WinApi
+{
+ ///
+ /// The structure contains information about a mouse input event.
+ ///
+ ///
+ /// See full documentation at http://globalmousekeyhook.codeplex.com/wikipage?title=MouseStruct
+ ///
+ [StructLayout(LayoutKind.Explicit)]
+ internal struct MouseStruct
+ {
+ ///
+ /// Specifies a Point structure that contains the X- and Y-coordinates of the cursor, in screen coordinates.
+ ///
+ [FieldOffset(0x00)] public Point Point;
+
+ ///
+ /// Specifies information associated with the message.
+ ///
+ ///
+ /// The possible values are:
+ ///
+ /// -
+ /// 0 - No Information
+ ///
+ /// -
+ /// 1 - X-Button1 Click
+ ///
+ /// -
+ /// 2 - X-Button2 Click
+ ///
+ /// -
+ /// 120 - Mouse Scroll Away from User
+ ///
+ /// -
+ /// -120 - Mouse Scroll Toward User
+ ///
+ ///
+ ///
+ [FieldOffset(0x0A)] public short MouseData;
+
+ ///
+ /// Returns a Timestamp associated with the input, in System Ticks.
+ ///
+ [FieldOffset(0x10)] public int Timestamp;
+ }
+}
\ No newline at end of file
diff --git a/Common/MouseKeyHook/WinApi/Point.cs b/Common/MouseKeyHook/WinApi/Point.cs
new file mode 100644
index 00000000..15a03cba
--- /dev/null
+++ b/Common/MouseKeyHook/WinApi/Point.cs
@@ -0,0 +1,64 @@
+// This code is distributed under MIT license.
+// Copyright (c) 2015 George Mamaladze
+// See license.txt or https://mit-license.org/
+
+using System.Runtime.InteropServices;
+
+namespace Gma.System.MouseKeyHook.WinApi
+{
+ ///
+ /// The Point structure defines the X- and Y- coordinates of a point.
+ ///
+ ///
+ /// http://msdn.microsoft.com/library/default.asp?url=/library/en-us/gdi/rectangl_0tiq.asp
+ ///
+ [StructLayout(LayoutKind.Sequential)]
+ internal struct Point
+ {
+ ///
+ /// Specifies the X-coordinate of the point.
+ ///
+ public int X;
+
+ ///
+ /// Specifies the Y-coordinate of the point.
+ ///
+ public int Y;
+
+ public Point(int x, int y)
+ {
+ X = x;
+ Y = y;
+ }
+
+ public static bool operator ==(Point a, Point b)
+ {
+ return a.X == b.X && a.Y == b.Y;
+ }
+
+ public static bool operator !=(Point a, Point b)
+ {
+ return !(a == b);
+ }
+
+ public bool Equals(Point other)
+ {
+ return other.X == X && other.Y == Y;
+ }
+
+ public override bool Equals(object obj)
+ {
+ if (ReferenceEquals(null, obj)) return false;
+ if (obj.GetType() != typeof(Point)) return false;
+ return Equals((Point) obj);
+ }
+
+ public override int GetHashCode()
+ {
+ unchecked
+ {
+ return (X * 397) ^ Y;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Common/MouseKeyHook/WinApi/ThreadNativeMethods.cs b/Common/MouseKeyHook/WinApi/ThreadNativeMethods.cs
new file mode 100644
index 00000000..b10420da
--- /dev/null
+++ b/Common/MouseKeyHook/WinApi/ThreadNativeMethods.cs
@@ -0,0 +1,42 @@
+// This code is distributed under MIT license.
+// Copyright (c) 2015 George Mamaladze
+// See license.txt or https://mit-license.org/
+
+using System;
+using System.Runtime.InteropServices;
+
+namespace Gma.System.MouseKeyHook.WinApi
+{
+ internal static class ThreadNativeMethods
+ {
+ ///
+ /// Retrieves the unmanaged thread identifier of the calling thread.
+ ///
+ ///
+ [DllImport("kernel32")]
+ internal static extern int GetCurrentThreadId();
+
+ ///
+ /// Retrieves a handle to the foreground window (the window with which the user is currently working).
+ /// The system assigns a slightly higher priority to the thread that creates the foreground window than it does to
+ /// other threads.
+ ///
+ ///
+ [DllImport("user32.dll", CharSet = CharSet.Auto)]
+ internal static extern IntPtr GetForegroundWindow();
+
+ ///
+ /// Retrieves the identifier of the thread that created the specified window and, optionally, the identifier of the
+ /// process that
+ /// created the window.
+ ///
+ /// A handle to the window.
+ ///
+ /// A pointer to a variable that receives the process identifier. If this parameter is not NULL,
+ /// GetWindowThreadProcessId copies the identifier of the process to the variable; otherwise, it does not.
+ ///
+ /// The return value is the identifier of the thread that created the window.
+ [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
+ internal static extern int GetWindowThreadProcessId(IntPtr handle, out int processId);
+ }
+}
\ No newline at end of file