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