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