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