WingMan – Blame information for rev 14
?pathlinks?
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 | } |