From d881fb0de14912fde7b605a2200cb9701a36bc98 Mon Sep 17 00:00:00 2001
From: TransposonY <ccfyzh@gmail.com>
Date: Thu, 12 Jan 2017 14:13:13 +0800
Subject: [PATCH] HotKey support

---
 .../Applications/ApplicationManager.cs        |  20 +-
 GestureSign.Common/Gestures/Gesture.cs        |  10 +
 GestureSign.Common/Plugins/PluginManager.cs   |  25 +-
 .../Dialogs/GestureDefinition.cs              |  47 +++-
 .../Dialogs/GestureDefinition.xaml            | 110 +++++---
 .../Languages/ControlPanel/en.xml             |   2 +
 .../Languages/ControlPanel/zh.xml             |   2 +
 GestureSign.Daemon/GestureSign.Daemon.csproj  |   5 +
 GestureSign.Daemon/Program.cs                 |   4 +-
 .../Triggers/GestureNameEventArgs.cs          |  15 +
 GestureSign.Daemon/Triggers/HotKeyManager.cs  |  79 ++++++
 GestureSign.Daemon/Triggers/Trigger.cs        |  20 ++
 GestureSign.Daemon/Triggers/TriggerManager.cs |  85 ++++++
 ManagedWinapi/Hotkey.cs                       | 264 ++++++++++++++++++
 ManagedWinapi/ManagedWinapi.csproj            |   6 +-
 .../Windows/EventDispatchingNativeWindow.cs   |  94 +++++++
 ManagedWinapi/{ => Windows}/PInvokeTypes.cs   |   0
 ManagedWinapi/{ => Windows}/SystemWindow.cs   |   0
 18 files changed, 710 insertions(+), 78 deletions(-)
 create mode 100644 GestureSign.Daemon/Triggers/GestureNameEventArgs.cs
 create mode 100644 GestureSign.Daemon/Triggers/HotKeyManager.cs
 create mode 100644 GestureSign.Daemon/Triggers/Trigger.cs
 create mode 100644 GestureSign.Daemon/Triggers/TriggerManager.cs
 create mode 100644 ManagedWinapi/Hotkey.cs
 create mode 100644 ManagedWinapi/Windows/EventDispatchingNativeWindow.cs
 rename ManagedWinapi/{ => Windows}/PInvokeTypes.cs (100%)
 rename ManagedWinapi/{ => Windows}/SystemWindow.cs (100%)

diff --git a/GestureSign.Common/Applications/ApplicationManager.cs b/GestureSign.Common/Applications/ApplicationManager.cs
index e7ea07d..8e10bb2 100644
--- a/GestureSign.Common/Applications/ApplicationManager.cs
+++ b/GestureSign.Common/Applications/ApplicationManager.cs
@@ -12,7 +12,6 @@
 using GestureSign.Common.Input;
 using GestureSign.Common.InterProcessCommunication;
 using ManagedWinapi.Windows;
-using Action = GestureSign.Applications.Action;
 
 namespace GestureSign.Common.Applications
 {
@@ -119,12 +118,14 @@ protected void TouchCapture_CaptureStarted(object sender, PointsCapturedEventArg
                     else
                         limitNumberFlag |= e.Points.Count < userApplication.LimitNumberOfFingers;
                 }
-
-                IgnoredApplication ignoredApplication = app as IgnoredApplication;
-                if (ignoredApplication != null && ignoredApplication.IsEnabled)
+                else
                 {
-                    e.Cancel = true;
-                    return;
+                    IgnoredApplication ignoredApplication = app as IgnoredApplication;
+                    if (ignoredApplication != null && ignoredApplication.IsEnabled)
+                    {
+                        e.Cancel = true;
+                        return;
+                    }
                 }
             }
             e.Cancel = limitNumberFlag ?? e.Points.Count == 1;
@@ -363,6 +364,13 @@ public IApplication[] FindMatchApplications<TApplication>(MatchUsing matchUsing,
                         excludedApplication != a.Name).ToArray();
         }
 
+        public SystemWindow GetForegroundApplications()
+        {
+            CaptureWindow = SystemWindow.ForegroundWindow;
+            RecognizedApplication = GetApplicationFromWindow(CaptureWindow);
+            return CaptureWindow;
+        }
+
         #endregion
 
         #region Private Methods
diff --git a/GestureSign.Common/Gestures/Gesture.cs b/GestureSign.Common/Gestures/Gesture.cs
index f78b260..052068f 100644
--- a/GestureSign.Common/Gestures/Gesture.cs
+++ b/GestureSign.Common/Gestures/Gesture.cs
@@ -1,5 +1,6 @@
 using System;
 using System.Runtime.Serialization;
+using ManagedWinapi;
 
 namespace GestureSign.Common.Gestures
 {
@@ -27,6 +28,15 @@ public Gesture(string name, PointPattern[] pointPatterns)
         [DataMember]
         public PointPattern[] PointPatterns { get; set; }
 
+        [DataMember]
+        public Hotkey Hotkey { get; set; }
+
+        public bool Equals(Gesture other)
+        {
+            if (other == null) return false;
+            return Name != null && Name.Equals(other.Name) && Hotkey != null && Hotkey.Equals(other.Hotkey);
+        }
+
         #endregion
     }
 }
diff --git a/GestureSign.Common/Plugins/PluginManager.cs b/GestureSign.Common/Plugins/PluginManager.cs
index 374558e..73ce881 100644
--- a/GestureSign.Common/Plugins/PluginManager.cs
+++ b/GestureSign.Common/Plugins/PluginManager.cs
@@ -46,19 +46,27 @@ protected PluginManager()
         protected void TouchCapture_GestureRecognized(object sender, RecognitionEventArgs e)
         {
             var touchCapture = (ITouchCapture)sender;
+            ExecuteAction(touchCapture.Mode, e.GestureName, e.ContactIdentifiers, e.FirstCapturedPoints, e.Points);
+        }
+
+        #endregion
+
+        #region Public Methods
+
+        public void ExecuteAction(CaptureMode mode, string gestureName, List<int> contactIdentifiers, List<Point> firstCapturedPoints, List<List<Point>> points)
+        {
             // Exit if we're teaching
-            if (touchCapture.Mode == CaptureMode.Training)
+            if (mode == CaptureMode.Training)
                 return;
-
             // Get action to be executed
-            IEnumerable<IAction> executableActions = ApplicationManager.Instance.GetRecognizedDefinedAction(e.GestureName);
+            IEnumerable<IAction> executableActions = ApplicationManager.Instance.GetRecognizedDefinedAction(gestureName);
             foreach (IAction executableAction in executableActions)
             {
                 // Exit if there is no action configured
                 if (executableAction == null || !executableAction.IsEnabled ||
-                    (touchCapture.Mode == CaptureMode.UserDisabled &&
+                    (mode == CaptureMode.UserDisabled &&
                     !"GestureSign.CorePlugins.ToggleDisableGestures".Equals(executableAction.PluginClass)) ||
-                   !Compute(executableAction.Condition, e.Points, e.ContactIdentifiers))
+                   !Compute(executableAction.Condition, points, contactIdentifiers))
                     continue;
 
                 // Locate the plugin associated with this action
@@ -71,13 +79,10 @@ protected void TouchCapture_GestureRecognized(object sender, RecognitionEventArg
                 // Load action settings into plugin
                 pluginInfo.Plugin.Deserialize(executableAction.ActionSettings);
                 // Execute plugin process
-                pluginInfo.Plugin.Gestured(new PointInfo(e.FirstCapturedPoints, e.Points));
+                pluginInfo.Plugin.Gestured(new PointInfo(firstCapturedPoints, points));
             }
-        }
-
-        #endregion
 
-        #region Public Methods
+        }
 
         public bool LoadPlugins(IHostControl host)
         {
diff --git a/GestureSign.ControlPanel/Dialogs/GestureDefinition.cs b/GestureSign.ControlPanel/Dialogs/GestureDefinition.cs
index 0ff89c2..a727df4 100644
--- a/GestureSign.ControlPanel/Dialogs/GestureDefinition.cs
+++ b/GestureSign.ControlPanel/Dialogs/GestureDefinition.cs
@@ -8,7 +8,9 @@
 using System.Threading.Tasks;
 using System.Windows;
 using System.Windows.Controls;
+using System.Windows.Input;
 using System.Windows.Media;
+using ManagedWinapi;
 
 namespace GestureSign.ControlPanel.Dialogs
 {
@@ -26,11 +28,12 @@ public GestureDefinition()
         public GestureDefinition(IGesture gesture)
             : this()
         {
+            _oldGesture = (Gesture)gesture;
             _currentPointPatterns = gesture.PointPatterns;
-            GestureManager.Instance.GestureName = _oldGestureName = gesture.Name;
+            GestureManager.Instance.GestureName = gesture.Name;
         }
 
-        private string _oldGestureName;
+        private Gesture _oldGesture;
         private string _similarGestureName;
         private PointPattern[] _currentPointPatterns;
         private Color _color;
@@ -70,7 +73,7 @@ private void Window_Loaded(object sender, RoutedEventArgs e)
             if (_currentPointPatterns != null)
                 imgGestureThumbnail.Source = GestureImage.CreateImage(_currentPointPatterns, new Size(65, 65), _color);
 
-            if (_oldGestureName == null)
+            if (_oldGesture == null)
             {
                 NamedPipe.SendMessageAsync("StartTeaching", "GestureSignDaemon");
                 Title = LocalizationProvider.Instance.GetTextValue("GestureDefinition.Title");
@@ -78,8 +81,10 @@ private void Window_Loaded(object sender, RoutedEventArgs e)
             else
             {
                 Title = LocalizationProvider.Instance.GetTextValue("GestureDefinition.Rename");
-                txtGestureName.Text = _oldGestureName; //this.txtGestureName.Text
-
+                txtGestureName.Text = _oldGesture.Name; //this.txtGestureName.Text
+                var hotkey = ((Gesture)_oldGesture).Hotkey;
+                if (hotkey != null)
+                    HotKeyTextBox.HotKey = new HotKey(KeyInterop.KeyFromVirtualKey(hotkey.KeyCode), (ModifierKeys)hotkey.ModifierKeys);
                 DrawGestureTextBlock.Visibility = ResetButton.Visibility = StackUpGestureButton.Visibility = Visibility.Collapsed;
 
                 txtGestureName.Focus();
@@ -91,7 +96,7 @@ private void Window_Loaded(object sender, RoutedEventArgs e)
         private async void Window_Closing(object sender, CancelEventArgs e)
         {
             //Non-renaming mode
-            if (_oldGestureName == null)
+            if (_oldGesture == null)
                 await NamedPipe.SendMessageAsync("StopTraining", "GestureSignDaemon");
         }
 
@@ -114,7 +119,7 @@ private void cmdCancel_Click(object sender, RoutedEventArgs e)
         private void txtGestureName_TextChanged(object sender, TextChangedEventArgs e)
         {
             string newGestureName = txtGestureName.Text.Trim();
-            if (_oldGestureName == newGestureName || SimilarGestureName == newGestureName) return;
+            if (_oldGesture?.Name == newGestureName || SimilarGestureName == newGestureName) return;
 
             txtGestureName.GetBindingExpression(TextBox.TextProperty).UpdateSource();
         }
@@ -178,16 +183,30 @@ private bool SaveGesture()
             if (_currentPointPatterns == null || _currentPointPatterns.Length == 0)
                 return false;
 
-            //Rename mode
-            if (_oldGestureName != null)
+            var newGesture = new Gesture()
+            {
+                Name = newGestureName,
+                PointPatterns = _currentPointPatterns,
+                Hotkey = HotKeyTextBox.HotKey != null ?
+                  new Hotkey()
+                  {
+                      KeyCode = KeyInterop.VirtualKeyFromKey(HotKeyTextBox.HotKey.Key),
+                      ModifierKeys = (int)HotKeyTextBox.HotKey.ModifierKeys
+                  } : null
+            };
+
+            if (_oldGesture != null)
             {
-                if (_oldGestureName.Equals(newGestureName)) return true;
-                if (GestureManager.Instance.GestureExists(newGestureName))
+                //Edit gesture
+                if (_oldGesture.Equals(newGesture)) return true;
+                if (!_oldGesture.Name.Equals(newGestureName) && GestureManager.Instance.GestureExists(newGestureName))
                 {
                     txtGestureName.GetBindingExpression(TextBox.TextProperty).UpdateSource();
                     return false;
                 }
-                GestureManager.Instance.RenameGesture(_oldGestureName, newGestureName);
+                GestureManager.Instance.RenameGesture(_oldGesture.Name, newGestureName);
+                GestureManager.Instance.DeleteGesture(newGestureName);
+                GestureManager.Instance.AddGesture(newGesture);
             }
             else
             {
@@ -199,7 +218,7 @@ private bool SaveGesture()
                         return false;
                     }
                     // Add new gesture to gesture manager
-                    GestureManager.Instance.AddGesture(new Gesture(newGestureName, _currentPointPatterns));
+                    GestureManager.Instance.AddGesture(newGesture);
                 }
                 else
                 {
@@ -214,7 +233,7 @@ private bool SaveGesture()
 
                     GestureManager.Instance.DeleteGesture(newGestureName);
                     // Add new gesture to gesture manager
-                    GestureManager.Instance.AddGesture(new Gesture(newGestureName, _currentPointPatterns));
+                    GestureManager.Instance.AddGesture(newGesture);
                 }
             }
             GestureManager.Instance.GestureName = newGestureName;
diff --git a/GestureSign.ControlPanel/Dialogs/GestureDefinition.xaml b/GestureSign.ControlPanel/Dialogs/GestureDefinition.xaml
index 0c5eb48..e8602a7 100644
--- a/GestureSign.ControlPanel/Dialogs/GestureDefinition.xaml
+++ b/GestureSign.ControlPanel/Dialogs/GestureDefinition.xaml
@@ -21,51 +21,71 @@
     <controls:MetroWindow.Resources>
     </controls:MetroWindow.Resources>
     <StackPanel>
-        <TextBlock x:Name="DrawGestureTextBlock"
-                   FontSize="14"
-                   FontWeight="Bold"
-                   Margin="12,10,12,0"
-                   Text="{localization:LocalisedText GestureDefinition.DrawGesture}"
-                   HorizontalAlignment="Center" />
-        <Image x:Name="imgGestureThumbnail"
-               Height="65"
-               Margin="0,10,0,0"
-               StretchDirection="DownOnly" />
-        <Button x:Name="ResetButton"
-                Content="{localization:LocalisedText GestureDefinition.Reset}"
-                Style="{DynamicResource SquareButtonStyle}"
-                Width="100"
-                HorizontalAlignment="Left"
-                BorderThickness="1"
-                BorderBrush="{StaticResource HighlightBrush}"
-                Margin="20,5,0,0"
-                Click="ResetButton_Click"
-                controls:ControlsHelper.ContentCharacterCasing="Normal"
-                IsEnabled="False"
-                Focusable="False" />
-        <Button x:Name="StackUpGestureButton"
-                Content="{localization:LocalisedText GestureDefinition.StackUpGesture}"
-                Style="{DynamicResource SquareButtonStyle}"
-                Width="100"
-                HorizontalAlignment="Right"
-                BorderThickness="1"
-                BorderBrush="{StaticResource HighlightBrush}"
-                Margin="0,-28,20,0"
-                Click="StackUpGestureButton_Click"
-                controls:ControlsHelper.ContentCharacterCasing="Normal"
-                IsEnabled="False"
-                Focusable="False" />
-        <TextBlock x:Name="ExistingTextBlock"
-                   TextWrapping="Wrap"
-                   FontSize="14"
-                   Margin="12,0"
-                   Text="{localization:LocalisedText GestureDefinition.ExistingGesture}"
-                   Visibility="Collapsed" />
-        <Image x:Name="ExistingGestureImage"
-               Height="65"
-               StretchDirection="DownOnly"
-               Margin="5"
-               Visibility="Collapsed" />
+        <TabControl>
+            <TabItem controls:ControlsHelper.HeaderFontSize="18"
+                     Header="{localization:LocalisedText Gesture.Header}">
+                <StackPanel>
+                    <TextBlock x:Name="DrawGestureTextBlock"
+                               FontSize="14"
+                               FontWeight="Bold"
+                               Margin="12,10,12,0"
+                               Text="{localization:LocalisedText GestureDefinition.DrawGesture}"
+                               HorizontalAlignment="Center" />
+                    <Image x:Name="imgGestureThumbnail"
+                           Height="65"
+                           Margin="0,10,0,0"
+                           StretchDirection="DownOnly" />
+                    <Button x:Name="ResetButton"
+                            Content="{localization:LocalisedText GestureDefinition.Reset}"
+                            Style="{DynamicResource SquareButtonStyle}"
+                            Width="100"
+                            HorizontalAlignment="Left"
+                            BorderThickness="1"
+                            BorderBrush="{StaticResource HighlightBrush}"
+                            Margin="20,5,0,0"
+                            Click="ResetButton_Click"
+                            controls:ControlsHelper.ContentCharacterCasing="Normal"
+                            IsEnabled="False"
+                            Focusable="False" />
+                    <Button x:Name="StackUpGestureButton"
+                            Content="{localization:LocalisedText GestureDefinition.StackUpGesture}"
+                            Style="{DynamicResource SquareButtonStyle}"
+                            Width="100"
+                            HorizontalAlignment="Right"
+                            BorderThickness="1"
+                            BorderBrush="{StaticResource HighlightBrush}"
+                            Margin="0,-28,20,0"
+                            Click="StackUpGestureButton_Click"
+                            controls:ControlsHelper.ContentCharacterCasing="Normal"
+                            IsEnabled="False"
+                            Focusable="False" />
+                    <TextBlock x:Name="ExistingTextBlock"
+                               TextWrapping="Wrap"
+                               FontSize="14"
+                               Margin="12,0"
+                               Text="{localization:LocalisedText GestureDefinition.ExistingGesture}"
+                               Visibility="Collapsed" />
+                    <Image x:Name="ExistingGestureImage"
+                           Height="65"
+                           StretchDirection="DownOnly"
+                           Margin="5"
+                           Visibility="Collapsed" />
+                </StackPanel>
+            </TabItem>
+            <TabItem Header="{localization:LocalisedText GestureDefinition.HotKey}"
+                     controls:ControlsHelper.HeaderFontSize="18">
+                <Canvas Height="50">
+                    <controls:HotKeyBox x:Name="HotKeyTextBox"
+                                        Width="313"
+                                        AreModifierKeysRequired="True"
+                                        Watermark="{localization:LocalisedText GestureDefinition.HotKeyWatermark}"
+                                        Canvas.Left="18"
+                                        Canvas.Top="15"
+                                        FontSize="14">
+                    </controls:HotKeyBox>
+                </Canvas>
+            </TabItem>
+        </TabControl>
         <TextBox x:Name="txtGestureName"
                  controls:TextBoxHelper.Watermark="{localization:LocalisedText GestureDefinition.GestureNameWatermark}"
                  TextWrapping="Wrap"
diff --git a/GestureSign.ControlPanel/Languages/ControlPanel/en.xml b/GestureSign.ControlPanel/Languages/ControlPanel/en.xml
index cd9536a..3a6eb83 100644
--- a/GestureSign.ControlPanel/Languages/ControlPanel/en.xml
+++ b/GestureSign.ControlPanel/Languages/ControlPanel/en.xml
@@ -161,6 +161,8 @@ Or click the button below to select the running applications.</GetMatchStringTip
     <StackUpGesture>Stack Up</StackUpGesture>
     <DrawGesture>Please draw a gesture</DrawGesture>
     <Reset>Reset</Reset>
+    <HotKey>HotKey</HotKey>
+    <HotKeyWatermark>Enter hot key</HotKeyWatermark>
     <Messages>
       <GestureExists>The Gesture Name "{0}" already exists, please provide a different Gesture Name</GestureExists>
     </Messages>
diff --git a/GestureSign.ControlPanel/Languages/ControlPanel/zh.xml b/GestureSign.ControlPanel/Languages/ControlPanel/zh.xml
index e75c3a6..583dd87 100644
--- a/GestureSign.ControlPanel/Languages/ControlPanel/zh.xml
+++ b/GestureSign.ControlPanel/Languages/ControlPanel/zh.xml
@@ -167,6 +167,8 @@ http://transposony.coding.me/GestureSign/
     <StackUpGesture>叠加手势</StackUpGesture>
     <DrawGesture>请画出手势</DrawGesture>
     <Reset>重置</Reset>
+    <HotKey>快捷键</HotKey>
+    <HotKeyWatermark>可为该手势指定一个快捷键</HotKeyWatermark>
     <Messages>
       <GestureExists>输入的手势名称{0}已存在,请重新输入一个手势名称</GestureExists>
     </Messages>
diff --git a/GestureSign.Daemon/GestureSign.Daemon.csproj b/GestureSign.Daemon/GestureSign.Daemon.csproj
index e48bda3..6ee8a48 100644
--- a/GestureSign.Daemon/GestureSign.Daemon.csproj
+++ b/GestureSign.Daemon/GestureSign.Daemon.csproj
@@ -99,6 +99,10 @@
       <SubType>Form</SubType>
     </Compile>
     <Compile Include="TrayManager.cs" />
+    <Compile Include="Triggers\GestureNameEventArgs.cs" />
+    <Compile Include="Triggers\HotKeyManager.cs" />
+    <Compile Include="Triggers\Trigger.cs" />
+    <Compile Include="Triggers\TriggerManager.cs" />
     <EmbeddedResource Include="Properties\Resources.resx">
       <Generator>ResXFileCodeGenerator</Generator>
       <SubType>Designer</SubType>
@@ -162,6 +166,7 @@
     <None Include="Resources\add.ico" />
     <None Include="Resources\stop.ico" />
   </ItemGroup>
+  <ItemGroup />
   <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
   <PropertyGroup>
     <PostBuildEvent>
diff --git a/GestureSign.Daemon/Program.cs b/GestureSign.Daemon/Program.cs
index 2d40f96..c8e4d32 100644
--- a/GestureSign.Daemon/Program.cs
+++ b/GestureSign.Daemon/Program.cs
@@ -13,6 +13,7 @@
 using GestureSign.Common.Plugins;
 using GestureSign.Daemon.Input;
 using GestureSign.Daemon.Surface;
+using GestureSign.Daemon.Triggers;
 
 namespace GestureSign.Daemon
 {
@@ -46,6 +47,8 @@ static void Main()
 
                         TouchCapture.Instance.Load();
                         _surfaceForm = new SurfaceForm();
+                        SynchronizationContext uiContext = SynchronizationContext.Current;
+                        TriggerManager.Instance.Load(uiContext);
 
                         if (!StartTouchInputProvider()) return;
 
@@ -63,7 +66,6 @@ static void Main()
                         PluginManager.Instance.Load(hostControl);
                         TrayManager.Instance.Load();
 
-                        SynchronizationContext uiContext = SynchronizationContext.Current;
                         NamedPipe.Instance.RunNamedPipeServer("GestureSignDaemon", new MessageProcessor(uiContext));
 
                         //if (TouchCapture.Instance.MessageWindow.NumberOfTouchscreens == 0)
diff --git a/GestureSign.Daemon/Triggers/GestureNameEventArgs.cs b/GestureSign.Daemon/Triggers/GestureNameEventArgs.cs
new file mode 100644
index 0000000..c28d9bc
--- /dev/null
+++ b/GestureSign.Daemon/Triggers/GestureNameEventArgs.cs
@@ -0,0 +1,15 @@
+using System;
+using System.Collections.Generic;
+
+namespace GestureSign.Daemon.Triggers
+{
+    public class GestureNameEventArgs : EventArgs
+    {
+        public GestureNameEventArgs(List<string> gestureName)
+        {
+            GestureName = gestureName;
+        }
+
+        public List<string> GestureName { get; }
+    }
+}
diff --git a/GestureSign.Daemon/Triggers/HotKeyManager.cs b/GestureSign.Daemon/Triggers/HotKeyManager.cs
new file mode 100644
index 0000000..cef5a83
--- /dev/null
+++ b/GestureSign.Daemon/Triggers/HotKeyManager.cs
@@ -0,0 +1,79 @@
+using System;
+using System.Collections.Generic;
+using GestureSign.Common.Gestures;
+using ManagedWinapi;
+
+namespace GestureSign.Daemon.Triggers
+{
+    class HotKeyManager : Trigger
+    {
+        private Dictionary<Hotkey, List<string>> _hotKeyMap = new Dictionary<Hotkey, List<string>>();
+
+        public override bool LoadConfiguration(IGesture[] gestures)
+        {
+            UnloadHotKeys();
+            return LoadHotKeys(gestures);
+        }
+
+        private bool LoadHotKeys(IGesture[] gestures)
+        {
+            _hotKeyMap = new Dictionary<Hotkey, List<string>>();
+
+            if (gestures == null || gestures.Length == 0) return false;
+
+            foreach (var g in gestures)
+            {
+                var h = ((Gesture)g).Hotkey;
+
+                if (h != null && h.ModifierKeys != 0 && h.KeyCode != 0)
+                {
+                    var hotKey = new Hotkey() { KeyCode = h.KeyCode, ModifierKeys = h.ModifierKeys };
+                    if (_hotKeyMap.ContainsKey(hotKey))
+                    {
+                        var gestureNameList = _hotKeyMap[hotKey] ?? new List<string>();
+                        if (!gestureNameList.Contains(g.Name))
+                            gestureNameList.Add(g.Name);
+                    }
+                    else
+                    {
+                        InitializeHotKey(hotKey);
+                        _hotKeyMap.Add(hotKey, new List<string>(new[] { g.Name }));
+                    }
+                }
+            }
+            return true;
+        }
+
+        private void InitializeHotKey(Hotkey hotkey)
+        {
+            try
+            {
+                hotkey.HotkeyPressed += Hotkey_HotkeyPressed;
+                hotkey.Register();
+            }
+            catch (HotkeyAlreadyInUseException)
+            {
+                hotkey.Unregister();
+            }
+        }
+
+        private void UnloadHotKeys()
+        {
+            foreach (var hotKeyPair in _hotKeyMap)
+            {
+                hotKeyPair.Key.HotkeyPressed -= Hotkey_HotkeyPressed;
+                hotKeyPair.Key.Dispose();
+            }
+            _hotKeyMap = null;
+        }
+
+        private void Hotkey_HotkeyPressed(object sender, EventArgs e)
+        {
+            Hotkey hotkey = (Hotkey)sender;
+            if (_hotKeyMap.ContainsKey(hotkey))
+            {
+                OnTriggerFired(new GestureNameEventArgs(_hotKeyMap[hotkey]));
+            }
+        }
+    }
+}
diff --git a/GestureSign.Daemon/Triggers/Trigger.cs b/GestureSign.Daemon/Triggers/Trigger.cs
new file mode 100644
index 0000000..cc5f98b
--- /dev/null
+++ b/GestureSign.Daemon/Triggers/Trigger.cs
@@ -0,0 +1,20 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using GestureSign.Common.Gestures;
+
+namespace GestureSign.Daemon.Triggers
+{
+    public abstract class Trigger
+    {
+        public event EventHandler<GestureNameEventArgs> TriggerFired;
+        public abstract bool LoadConfiguration(IGesture[] gestures);
+
+        protected virtual void OnTriggerFired(GestureNameEventArgs e)
+        {
+            TriggerFired?.Invoke(this, e);
+        }
+    }
+}
diff --git a/GestureSign.Daemon/Triggers/TriggerManager.cs b/GestureSign.Daemon/Triggers/TriggerManager.cs
new file mode 100644
index 0000000..fcae873
--- /dev/null
+++ b/GestureSign.Daemon/Triggers/TriggerManager.cs
@@ -0,0 +1,85 @@
+using System;
+using System.Collections.Generic;
+using System.Drawing;
+using System.Threading;
+using GestureSign.Common.Applications;
+using GestureSign.Common.Gestures;
+using GestureSign.Common.Plugins;
+using GestureSign.Daemon.Input;
+
+namespace GestureSign.Daemon.Triggers
+{
+    class TriggerManager
+    {
+        #region Private Variables
+
+        private List<Trigger> _triggerList = new List<Trigger>();
+        private SynchronizationContext _synchronizationContext;
+
+        #endregion
+
+        #region Constructors
+
+        static TriggerManager()
+        {
+            Instance = new TriggerManager();
+        }
+
+        #endregion
+
+        #region Public Instance Properties
+
+        public static TriggerManager Instance { get; }
+
+        #endregion
+
+        #region Public Methods
+
+        public void Load(SynchronizationContext synchronizationContext)
+        {
+            _synchronizationContext = synchronizationContext;
+            AddTrigger(new HotKeyManager());
+            GestureManager.OnLoadGesturesCompleted += GestureManager_OnLoadGesturesCompleted;
+        }
+
+        #endregion
+
+
+        #region Private Methods
+
+        private void GestureManager_OnLoadGesturesCompleted(object sender, EventArgs e)
+        {
+            _synchronizationContext.Send(state => { LoadConfig(((GestureManager)sender).Gestures); }, null);
+        }
+
+        private void LoadConfig(IGesture[] gestures)
+        {
+            foreach (var trigger in _triggerList)
+            {
+                trigger.LoadConfiguration(gestures);
+            }
+        }
+
+        private void AddTrigger(Trigger newTrigger)
+        {
+            newTrigger.TriggerFired += Trigger_TriggerFired;
+            _triggerList.Add(newTrigger);
+        }
+
+        private void Trigger_TriggerFired(object sender, GestureNameEventArgs e)
+        {
+            var window = ApplicationManager.Instance.GetForegroundApplications();
+            var point = new List<Point>(new[] { window.Rectangle.Location });
+            foreach (var name in e.GestureName)
+            {
+                PluginManager.Instance.ExecuteAction(TouchCapture.Instance.Mode,
+                    name,
+                    new List<int>(new[] { 1 }),
+                    point,
+                    new List<List<Point>>(new[] { point }));
+            }
+        }
+
+        #endregion
+    }
+}
diff --git a/ManagedWinapi/Hotkey.cs b/ManagedWinapi/Hotkey.cs
new file mode 100644
index 0000000..121b1d2
--- /dev/null
+++ b/ManagedWinapi/Hotkey.cs
@@ -0,0 +1,264 @@
+/*
+ * ManagedWinapi - A collection of .NET components that wrap PInvoke calls to 
+ * access native API by managed code. http://mwinapi.sourceforge.net/
+ * Copyright (C) 2006 Michael Schierl
+ * 
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; see the file COPYING. if not, visit
+ * http://www.gnu.org/licenses/lgpl.html or write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.ComponentModel;
+using System.Windows.Forms;
+using System.Runtime.InteropServices;
+using ManagedWinapi.Windows;
+
+namespace ManagedWinapi
+{
+
+    /// <summary>
+    /// Specifies a component that creates a global keyboard hotkey.
+    /// </summary>
+    [DefaultEvent("HotkeyPressed")]
+    public class Hotkey : IDisposable//: Component
+    {
+
+        /// <summary>
+        /// Occurs when the hotkey is pressed.
+        /// </summary>
+        public event EventHandler HotkeyPressed;
+
+        private static Object _myStaticLock = new Object();
+        private static int _hotkeyCounter = 0xA000;
+
+        private readonly int _hotkeyIndex;
+        private bool _isDisposed = false, _isEnabled = false, _isRegistered = false;
+        private int _keyCode;
+        //private bool _ctrl, _alt, _shift, _windows;
+        private int _modifierKeys;
+        private readonly IntPtr hWnd;
+        private readonly EventDispatchingNativeWindow nativeWindow;
+
+        ///// <summary>
+        ///// Initializes a new instance of this class with the specified container.
+        ///// </summary>
+        ///// <param name="container">The container to add it to.</param>
+        //public Hotkey(IContainer container) : this()
+        //{
+        //    container.Add(this);
+        //}
+
+        /// <summary>
+        /// Initializes a new instance of this class.
+        /// </summary>
+        public Hotkey()
+        {
+            nativeWindow = EventDispatchingNativeWindow.Instance;
+            nativeWindow.EventHandler += nw_EventHandler;
+            lock (_myStaticLock)
+            {
+                _hotkeyIndex = ++_hotkeyCounter;
+            }
+            hWnd = nativeWindow.Handle;
+        }
+
+        /// <summary>
+        /// Enables the hotkey. When the hotkey is enabled, pressing it causes a
+        /// <c>HotkeyPressed</c> event instead of being handled by the active 
+        /// application.
+        /// </summary>
+        private bool Enabled
+        {
+            get
+            {
+                return _isEnabled;
+            }
+            set
+            {
+                _isEnabled = value;
+                updateHotkey(false);
+            }
+        }
+
+        /// <summary>
+        /// The key code of the hotkey.
+        /// </summary>
+        public int KeyCode
+        {
+            get
+            {
+                return _keyCode;
+            }
+
+            set
+            {
+                _keyCode = value;
+                updateHotkey(true);
+            }
+        }
+
+        public int ModifierKeys
+        {
+            get { return _modifierKeys; }
+            set
+            {
+                _modifierKeys = value;
+                updateHotkey(true);
+            }
+        }
+
+        ///// <summary>
+        ///// Whether the shortcut includes the Control modifier.
+        ///// </summary>
+        //public bool Ctrl {
+        //    get { return _ctrl; }
+        //    set {_ctrl = value; updateHotkey(true);}
+        //}
+
+        ///// <summary>
+        ///// Whether this shortcut includes the Alt modifier.
+        ///// </summary>
+        //public bool Alt {
+        //    get { return _alt; }
+        //    set {_alt = value; updateHotkey(true);}
+        //}     
+
+        ///// <summary>
+        ///// Whether this shortcut includes the shift modifier.
+        ///// </summary>
+        //public bool Shift {
+        //    get { return _shift; }
+        //    set {_shift = value; updateHotkey(true);}
+        //}
+
+        ///// <summary>
+        ///// Whether this shortcut includes the Windows key modifier. The windows key
+        ///// is an addition by Microsoft to the keyboard layout. It is located between
+        ///// Control and Alt and depicts a Windows flag.
+        ///// </summary>
+        //public bool WindowsKey {
+        //    get { return _windows; }
+        //    set {_windows = value; updateHotkey(true);}
+        //}
+
+        public void Register()
+        {
+            Enabled = true;
+        }
+
+        public void Unregister()
+        {
+            Enabled = false;
+        }
+
+        public bool Equals(Hotkey other)
+        {
+            if (object.ReferenceEquals(null, other))
+                return false;
+            if (object.ReferenceEquals(this, other))
+                return true;
+            return Equals(other.KeyCode, this.KeyCode) && Equals(other.ModifierKeys, this.ModifierKeys);
+        }
+
+        public override bool Equals(object obj)
+        {
+            if (object.ReferenceEquals(null, obj))
+                return false;
+            if (object.ReferenceEquals(this, obj))
+                return true;
+            return obj.GetType() == typeof(Hotkey) && this.Equals((Hotkey)obj);
+        }
+
+        public override int GetHashCode()
+        {
+            return this.KeyCode.GetHashCode() * 397 ^ this.ModifierKeys.GetHashCode();
+        }
+
+        public void Dispose()
+        {
+            Dispose(true);
+            GC.SuppressFinalize(this);
+        }
+
+        void nw_EventHandler(ref Message m, ref bool handled)
+        {
+            if (handled) return;
+            if (m.Msg == WM_HOTKEY && m.WParam.ToInt32() == _hotkeyIndex)
+            {
+                if (HotkeyPressed != null)
+                    HotkeyPressed(this, EventArgs.Empty);
+                handled = true;
+            }
+        }
+
+        /// <summary>
+        /// Releases all resources used by the System.ComponentModel.Component.
+        /// </summary>
+        /// <param name="disposing">Whether to dispose managed resources.</param>
+        protected void Dispose(bool disposing)
+        {
+            if (!_isDisposed)
+            {
+                if (disposing)
+                {
+                    // Release managed resources
+                }
+                _isDisposed = true;
+                updateHotkey(false);
+                nativeWindow.EventHandler -= nw_EventHandler;
+            }
+        }
+
+        private void updateHotkey(bool reregister)
+        {
+            bool shouldBeRegistered = _isEnabled && !_isDisposed;
+            if (_isRegistered && (!shouldBeRegistered || reregister))
+            {
+                // unregister hotkey
+                UnregisterHotKey(hWnd, _hotkeyIndex);
+                _isRegistered = false;
+            }
+            if (!_isRegistered && shouldBeRegistered)
+            {
+                // register hotkey
+                bool success = RegisterHotKey(hWnd, _hotkeyIndex, _modifierKeys, _keyCode);
+                if (!success) throw new HotkeyAlreadyInUseException();
+                _isRegistered = true;
+            }
+        }
+
+        ~Hotkey()
+        {
+            Dispose(false);
+        }
+
+        #region PInvoke Declarations
+
+        [DllImport("user32.dll", SetLastError = true)]
+        private static extern bool RegisterHotKey(IntPtr hWnd, int id, int fsModifiers, int vlc);
+        [DllImport("user32.dll", SetLastError = true)]
+        private static extern bool UnregisterHotKey(IntPtr hWnd, int id);
+
+        private static readonly int WM_HOTKEY = 0x0312;
+
+        #endregion
+    }
+
+    /// <summary>
+    /// The exception is thrown when a hotkey should be registered that
+    /// has already been registered by another application.
+    /// </summary>
+    public class HotkeyAlreadyInUseException : Exception { }
+}
diff --git a/ManagedWinapi/ManagedWinapi.csproj b/ManagedWinapi/ManagedWinapi.csproj
index 1db933e..d8d9ee9 100644
--- a/ManagedWinapi/ManagedWinapi.csproj
+++ b/ManagedWinapi/ManagedWinapi.csproj
@@ -64,11 +64,13 @@
   </ItemGroup>
   <ItemGroup>
     <Compile Include="ApiHelper.cs" />
+    <Compile Include="Hotkey.cs" />
     <Compile Include="KeyboardKey.cs" />
-    <Compile Include="PInvokeTypes.cs" />
+    <Compile Include="Windows\EventDispatchingNativeWindow.cs" />
+    <Compile Include="Windows\PInvokeTypes.cs" />
     <Compile Include="SendKeysEscaper.cs" />
     <Compile Include="Properties\AssemblyInfo.cs" />
-    <Compile Include="SystemWindow.cs" />
+    <Compile Include="Windows\SystemWindow.cs" />
     <Service Include="{94E38DFF-614B-4cbd-B67C-F211BB35CE8B}" />
   </ItemGroup>
   <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
diff --git a/ManagedWinapi/Windows/EventDispatchingNativeWindow.cs b/ManagedWinapi/Windows/EventDispatchingNativeWindow.cs
new file mode 100644
index 0000000..528cf6e
--- /dev/null
+++ b/ManagedWinapi/Windows/EventDispatchingNativeWindow.cs
@@ -0,0 +1,94 @@
+/*
+ * ManagedWinapi - A collection of .NET components that wrap PInvoke calls to 
+ * access native API by managed code. http://mwinapi.sourceforge.net/
+ * Copyright (C) 2006 Michael Schierl
+ * 
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; see the file COPYING. if not, visit
+ * http://www.gnu.org/licenses/lgpl.html or write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Windows.Forms;
+
+namespace ManagedWinapi.Windows
+{
+
+    /// <summary>
+    /// Called by an EventDispatchingNativeWindow when a window message is received
+    /// </summary>
+    /// <param name="m">The message to handle.</param>
+    /// <param name="handled">Whether the event has already been handled. If this value is true, the handler
+    /// should return immediately. It may set the value to true to indicate that no others 
+    /// should handle it. If the event is not handled by any handler, it is passed to the
+    /// default WindowProc.</param>
+    public delegate void WndProcEventHandler(ref Message m, ref bool handled);
+
+    /// <summary>
+    /// A Win32 native window that delegates window messages to handlers. So several
+    /// components can use the same native window to save "USER resources". This class
+    /// is useful when writing your own components.
+    /// </summary>
+    public class EventDispatchingNativeWindow : NativeWindow
+    {
+
+        private static Object myLock = new Object();
+        [ThreadStatic]
+        private static EventDispatchingNativeWindow _instance;
+
+        /// <summary>
+        /// A global instance which can be used by components that do not need
+        /// their own window.
+        /// </summary>
+        public static EventDispatchingNativeWindow Instance
+        {
+            get
+            {
+                lock (myLock)
+                {
+                    if (_instance == null)
+                        _instance = new EventDispatchingNativeWindow();
+                    return _instance;
+                }
+            }
+        }
+
+        /// <summary>
+        /// Attach your event handlers here.
+        /// </summary>
+        public event WndProcEventHandler EventHandler;
+
+        /// <summary>
+        /// Create your own event dispatching window.
+        /// </summary>
+        public EventDispatchingNativeWindow()
+        {
+            CreateHandle(new CreateParams());
+        }
+
+        /// <summary>
+        /// Parse messages passed to this window and send them to the event handlers.
+        /// </summary>
+        /// <param name="m">A System.Windows.Forms.Message that is associated with the 
+        /// current Windows message.</param>
+        protected override void WndProc(ref Message m)
+        {
+            bool handled = false;
+            if (EventHandler != null)
+                EventHandler(ref m, ref handled);
+            if (!handled)
+                base.WndProc(ref m);
+        }
+    }
+}
diff --git a/ManagedWinapi/PInvokeTypes.cs b/ManagedWinapi/Windows/PInvokeTypes.cs
similarity index 100%
rename from ManagedWinapi/PInvokeTypes.cs
rename to ManagedWinapi/Windows/PInvokeTypes.cs
diff --git a/ManagedWinapi/SystemWindow.cs b/ManagedWinapi/Windows/SystemWindow.cs
similarity index 100%
rename from ManagedWinapi/SystemWindow.cs
rename to ManagedWinapi/Windows/SystemWindow.cs