WingMan – Blame information for rev 14

Subversion Repositories:
Rev:
Rev Author Line No. Line
10 office 1 using System;
14 office 2 using System.Collections.Specialized;
10 office 3 using System.IO;
4 using System.Linq;
5 using System.Threading;
6 using System.Threading.Tasks;
14 office 7 using System.Threading.Tasks.Dataflow;
10 office 8 using System.Windows.Forms;
9 using Gma.System.MouseKeyHook;
10 using WingMan.Communication;
14 office 11 using WingMan.Utilities;
10 office 12  
14 office 13 namespace WingMan.Bindings
10 office 14 {
15 public class KeyInterceptor : IDisposable
16 {
17 public delegate void MouseKeyBindingMatched(object sender, KeyBindingMatchedEventArgs args);
18  
14 office 19 private volatile bool ProcessPipe;
20  
10 office 21 public KeyInterceptor(RemoteKeyBindings remoteKeyBindings, MqttCommunication mqttCommunication,
22 TaskScheduler taskScheduler, CancellationToken cancellationToken)
23 {
14 office 24 DataFlowSemaphoreSlim = new SemaphoreSlim(1, 1);
25  
10 office 26 RemoteKeyBindings = remoteKeyBindings;
14 office 27 RemoteKeyBindings.Bindings.CollectionChanged += OnRemoteKeyBindingsChanged;
28  
10 office 29 MqttCommunication = mqttCommunication;
30 TaskScheduler = taskScheduler;
31 CancellationToken = cancellationToken;
32  
33 MouseKeyGloalHook = Hook.GlobalEvents();
34 MouseKeyGloalHook.KeyUp += MouseKeyGloalHookOnKeyUp;
35 MouseKeyGloalHook.KeyDown += MouseKeyGloalHookOnKeyDown;
36 }
37  
14 office 38 private BatchBlock<string> KeyComboBatchBlock { get; set; }
39  
40 private ActionBlock<string[]> KeyComboActionBlock { get; set; }
41  
42 private IDisposable KeyComboDataFlowLink { get; set; }
43  
44 private SemaphoreSlim DataFlowSemaphoreSlim { get; }
45  
10 office 46 private RemoteKeyBindings RemoteKeyBindings { get; }
47 private MqttCommunication MqttCommunication { get; }
48 private TaskScheduler TaskScheduler { get; }
49 private CancellationToken CancellationToken { get; }
50  
14 office 51 private IKeyboardMouseEvents MouseKeyGloalHook { get; set; }
10 office 52  
53 public void Dispose()
54 {
55 MouseKeyGloalHook.KeyUp -= MouseKeyGloalHookOnKeyUp;
56 MouseKeyGloalHook.KeyDown -= MouseKeyGloalHookOnKeyDown;
14 office 57 RemoteKeyBindings.Bindings.CollectionChanged -= OnRemoteKeyBindingsChanged;
10 office 58  
14 office 59 KeyComboDataFlowLink?.Dispose();
60 KeyComboDataFlowLink = null;
10 office 61  
14 office 62 MouseKeyGloalHook?.Dispose();
63 MouseKeyGloalHook = null;
10 office 64 }
65  
14 office 66 private async void OnRemoteKeyBindingsChanged(object sender, NotifyCollectionChangedEventArgs e)
10 office 67 {
14 office 68 await DataFlowSemaphoreSlim.WaitAsync(CancellationToken);
10 office 69  
14 office 70 try
71 {
72 // Break the link and dispose it.
73 KeyComboDataFlowLink?.Dispose();
74 KeyComboDataFlowLink = null;
10 office 75  
14 office 76 // Create a sliding window of size equal to the longest key combination.
77 var maxKeyComboLength = RemoteKeyBindings.Bindings.Max(binding => binding.Keys.Count);
10 office 78  
14 office 79 KeyComboBatchBlock =
80 new BatchBlock<string>(maxKeyComboLength);
81 KeyComboActionBlock = new ActionBlock<string[]>(ProcessKeyCombos,
82 new ExecutionDataflowBlockOptions {CancellationToken = CancellationToken});
83 KeyComboDataFlowLink = KeyComboBatchBlock.LinkTo(KeyComboActionBlock);
84 }
85 finally
86 {
87 DataFlowSemaphoreSlim.Release();
88 }
10 office 89 }
90  
14 office 91 private async Task ProcessKeyCombos(string[] keys)
10 office 92 {
14 office 93 await DataFlowSemaphoreSlim.WaitAsync(CancellationToken);
94  
95 try
10 office 96 {
14 office 97 if (!ProcessPipe)
98 return;
10 office 99  
14 office 100 foreach (var binding in RemoteKeyBindings.Bindings)
10 office 101 {
14 office 102 if (!keys.SubsetEquals(binding.Keys))
103 continue;
10 office 104  
14 office 105 // Raise the match event.
106 await Task.Delay(0, CancellationToken)
107 .ContinueWith(
108 _ => OnMouseKeyBindingMatched?.Invoke(this,
109 new KeyBindingMatchedEventArgs(binding.Nick, binding.Name, binding.Keys)),
110 CancellationToken,
111 TaskContinuationOptions.None, TaskScheduler);
10 office 112  
14 office 113 using (var memoryStream = new MemoryStream())
114 {
115 ExecuteKeyBinding.XmlSerializer.Serialize(memoryStream,
116 new ExecuteKeyBinding(binding.Nick, binding.Name));
117  
118 memoryStream.Position = 0L;
119  
120 await MqttCommunication.Broadcast("execute", memoryStream.ToArray());
121 }
10 office 122 }
123 }
14 office 124 finally
125 {
126 DataFlowSemaphoreSlim.Release();
127 }
10 office 128 }
14 office 129  
130 public event MouseKeyBindingMatched OnMouseKeyBindingMatched;
131  
132 private async void MouseKeyGloalHookOnKeyDown(object sender, KeyEventArgs e)
133 {
134 ProcessPipe = true;
135  
136 if (!KeyConversion.KeysToString.TryGetValue((byte) e.KeyCode, out var key))
137 return;
138  
139 await DataFlowSemaphoreSlim.WaitAsync(CancellationToken);
140 try
141 {
142 if (KeyComboBatchBlock != null) await KeyComboBatchBlock.SendAsync(key, CancellationToken);
143 }
144 finally
145 {
146 DataFlowSemaphoreSlim.Release();
147 }
148 }
149  
150 private void MouseKeyGloalHookOnKeyUp(object sender, KeyEventArgs e)
151 {
152 ProcessPipe = false;
153 }
10 office 154 }
155 }