WingMan – Blame information for rev 21

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 private ActionBlock<string[]> KeyComboActionBlock { get; set; }
40 private IDisposable KeyComboDataFlowLink { get; set; }
41 private SemaphoreSlim DataFlowSemaphoreSlim { get; }
10 office 42 private RemoteKeyBindings RemoteKeyBindings { get; }
43 private MqttCommunication MqttCommunication { get; }
44 private TaskScheduler TaskScheduler { get; }
45 private CancellationToken CancellationToken { get; }
14 office 46 private IKeyboardMouseEvents MouseKeyGloalHook { get; set; }
10 office 47  
48 public void Dispose()
49 {
50 MouseKeyGloalHook.KeyUp -= MouseKeyGloalHookOnKeyUp;
51 MouseKeyGloalHook.KeyDown -= MouseKeyGloalHookOnKeyDown;
14 office 52 RemoteKeyBindings.Bindings.CollectionChanged -= OnRemoteKeyBindingsChanged;
10 office 53  
14 office 54 KeyComboDataFlowLink?.Dispose();
55 KeyComboDataFlowLink = null;
10 office 56  
14 office 57 MouseKeyGloalHook?.Dispose();
58 MouseKeyGloalHook = null;
10 office 59 }
60  
14 office 61 private async void OnRemoteKeyBindingsChanged(object sender, NotifyCollectionChangedEventArgs e)
10 office 62 {
14 office 63 await DataFlowSemaphoreSlim.WaitAsync(CancellationToken);
10 office 64  
14 office 65 try
66 {
67 // Break the link and dispose it.
68 KeyComboDataFlowLink?.Dispose();
69 KeyComboDataFlowLink = null;
10 office 70  
14 office 71 // Create a sliding window of size equal to the longest key combination.
72 var maxKeyComboLength = RemoteKeyBindings.Bindings.Max(binding => binding.Keys.Count);
21 office 73 if (maxKeyComboLength <= 0)
74 return;
10 office 75  
14 office 76 KeyComboBatchBlock =
77 new BatchBlock<string>(maxKeyComboLength);
78 KeyComboActionBlock = new ActionBlock<string[]>(ProcessKeyCombos,
79 new ExecutionDataflowBlockOptions {CancellationToken = CancellationToken});
80 KeyComboDataFlowLink = KeyComboBatchBlock.LinkTo(KeyComboActionBlock);
81 }
82 finally
83 {
84 DataFlowSemaphoreSlim.Release();
85 }
10 office 86 }
87  
14 office 88 private async Task ProcessKeyCombos(string[] keys)
10 office 89 {
14 office 90 await DataFlowSemaphoreSlim.WaitAsync(CancellationToken);
91  
92 try
10 office 93 {
14 office 94 if (!ProcessPipe)
95 return;
10 office 96  
14 office 97 foreach (var binding in RemoteKeyBindings.Bindings)
10 office 98 {
14 office 99 if (!keys.SubsetEquals(binding.Keys))
100 continue;
10 office 101  
14 office 102 // Raise the match event.
103 await Task.Delay(0, CancellationToken)
104 .ContinueWith(
105 _ => OnMouseKeyBindingMatched?.Invoke(this,
106 new KeyBindingMatchedEventArgs(binding.Nick, binding.Name, binding.Keys)),
107 CancellationToken,
108 TaskContinuationOptions.None, TaskScheduler);
10 office 109  
14 office 110 using (var memoryStream = new MemoryStream())
111 {
112 ExecuteKeyBinding.XmlSerializer.Serialize(memoryStream,
113 new ExecuteKeyBinding(binding.Nick, binding.Name));
114  
115 memoryStream.Position = 0L;
116  
117 await MqttCommunication.Broadcast("execute", memoryStream.ToArray());
118 }
10 office 119 }
120 }
14 office 121 finally
122 {
123 DataFlowSemaphoreSlim.Release();
124 }
10 office 125 }
14 office 126  
127 public event MouseKeyBindingMatched OnMouseKeyBindingMatched;
128  
129 private async void MouseKeyGloalHookOnKeyDown(object sender, KeyEventArgs e)
130 {
131 ProcessPipe = true;
132  
133 if (!KeyConversion.KeysToString.TryGetValue((byte) e.KeyCode, out var key))
134 return;
135  
136 await DataFlowSemaphoreSlim.WaitAsync(CancellationToken);
137 try
138 {
21 office 139 if (KeyComboBatchBlock != null)
140 await KeyComboBatchBlock.SendAsync(key, CancellationToken);
14 office 141 }
142 finally
143 {
144 DataFlowSemaphoreSlim.Release();
145 }
146 }
147  
148 private void MouseKeyGloalHookOnKeyUp(object sender, KeyEventArgs e)
149 {
150 ProcessPipe = false;
151 }
10 office 152 }
153 }