WingMan – Blame information for rev 36

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