WingMan – Blame information for rev 32

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.
24 office 72 if (!RemoteKeyBindings.Bindings.Any())
73 return;
74  
75 var remoteKeyBindings = RemoteKeyBindings.Bindings.Where(binding => binding.Keys.Any()).ToList();
76 if (!remoteKeyBindings.Any())
77 return;
78  
79 var maxKeyComboLength = remoteKeyBindings.Max(binding => binding.Keys.Count);
21 office 80 if (maxKeyComboLength <= 0)
81 return;
10 office 82  
14 office 83 KeyComboBatchBlock =
84 new BatchBlock<string>(maxKeyComboLength);
85 KeyComboActionBlock = new ActionBlock<string[]>(ProcessKeyCombos,
86 new ExecutionDataflowBlockOptions {CancellationToken = CancellationToken});
87 KeyComboDataFlowLink = KeyComboBatchBlock.LinkTo(KeyComboActionBlock);
88 }
89 finally
90 {
91 DataFlowSemaphoreSlim.Release();
92 }
10 office 93 }
94  
14 office 95 private async Task ProcessKeyCombos(string[] keys)
10 office 96 {
14 office 97 await DataFlowSemaphoreSlim.WaitAsync(CancellationToken);
98  
99 try
10 office 100 {
14 office 101 if (!ProcessPipe)
102 return;
10 office 103  
14 office 104 foreach (var binding in RemoteKeyBindings.Bindings)
10 office 105 {
14 office 106 if (!keys.SubsetEquals(binding.Keys))
107 continue;
10 office 108  
14 office 109 // Raise the match event.
110 await Task.Delay(0, CancellationToken)
111 .ContinueWith(
112 _ => OnMouseKeyBindingMatched?.Invoke(this,
113 new KeyBindingMatchedEventArgs(binding.Nick, binding.Name, binding.Keys)),
114 CancellationToken,
115 TaskContinuationOptions.None, TaskScheduler);
10 office 116  
14 office 117 using (var memoryStream = new MemoryStream())
118 {
119 ExecuteKeyBinding.XmlSerializer.Serialize(memoryStream,
120 new ExecuteKeyBinding(binding.Nick, binding.Name));
121  
122 memoryStream.Position = 0L;
123  
124 await MqttCommunication.Broadcast("execute", memoryStream.ToArray());
125 }
10 office 126 }
127 }
14 office 128 finally
129 {
130 DataFlowSemaphoreSlim.Release();
131 }
10 office 132 }
14 office 133  
134 public event MouseKeyBindingMatched OnMouseKeyBindingMatched;
135  
136 private async void MouseKeyGloalHookOnKeyDown(object sender, KeyEventArgs e)
137 {
138 ProcessPipe = true;
139  
140 if (!KeyConversion.KeysToString.TryGetValue((byte) e.KeyCode, out var key))
141 return;
142  
143 await DataFlowSemaphoreSlim.WaitAsync(CancellationToken);
144 try
145 {
21 office 146 if (KeyComboBatchBlock != null)
147 await KeyComboBatchBlock.SendAsync(key, CancellationToken);
14 office 148 }
149 finally
150 {
151 DataFlowSemaphoreSlim.Release();
152 }
153 }
154  
155 private void MouseKeyGloalHookOnKeyUp(object sender, KeyEventArgs e)
156 {
157 ProcessPipe = false;
158 }
10 office 159 }
32 office 160 }