Spring – Blame information for rev 1

Subversion Repositories:
Rev:
Rev Author Line No. Line
1 office 1 using System;
2 using System.Collections.Generic;
3 using System.ComponentModel;
4 using System.Linq;
5 using System.Threading;
6 using System.Threading.Tasks;
7 using System.Windows.Forms;
8 using WindowsInput;
9 using WindowsInput.Native;
10 using Spring.Charging;
11 using Spring.Main;
12 using Spring.MouseKeyboard;
13 using Spring.Utilities;
14 using Spring.Utilities.Collections;
15 using SpringCombos;
16 using Debug = System.Diagnostics.Debug;
17  
18 namespace Spring.Discharging
19 {
20 public class Discharge : IDisposable
21 {
22 #region Public Events & Delegates
23  
24 public event EventHandler<DischargeProgressEventArgs> SpringDischargeProgress;
25  
26 public event EventHandler<DischargeStartEventArgs> SpringDischargeStart;
27  
28 public event EventHandler<DischargeStopEventArgs> SpringDischargeStop;
29  
30 #endregion
31  
32 #region Public Enums, Properties and Fields
33  
34 public Key Key { get; set; }
35  
36 public int Fuzz { get; set; }
37  
38 #endregion
39  
40 #region Private Delegates, Events, Enums, Properties, Indexers and Fields
41  
42 private int SpeedMultiplier { get; set; }
43  
44 private CancellationTokenSource PauseCancellationTokenSource { get; set; }
45  
46 private InputSimulator InputSimulator { get; }
47  
48 private KeyWatch WatchKey { get; }
49  
50 private MainForm Form { get; }
51  
52 private Charge Charge { get; }
53  
54 private CancellationTokenSource DischargeCancellationTokenSource { get; set; }
55  
56 private Configuration.Configuration Configuration { get; }
57  
58 private SemaphoreSlim DischargeSemaphore { get; }
59  
60 private Combos ExecutingSpringCombos { get; set; }
61  
62 private Combos OriginalSpringCombos { get; set; }
63  
64 private Task DischargeTask { get; set; }
65  
66 private static Random Random { get; set; }
67  
68 private bool _disposing;
69  
70 private volatile bool _isDischarging;
71  
72 #endregion
73  
74 #region Constructors, Destructors and Finalizers
75  
76 public Discharge()
77 {
78 InputSimulator = new InputSimulator();
79 DischargeSemaphore = new SemaphoreSlim(1, 1);
80 Random = new Random();
81 }
82  
83 public Discharge(MainForm form,
84 Configuration.Configuration springConfiguration,
85 Key key,
86 KeyWatch springWatchKey,
87 Charge springSpringCharge,
88 int speedBarValue) :
89 this()
90 {
91 Form = form;
92 Configuration = springConfiguration;
93 Key = key;
94 WatchKey = springWatchKey;
95 Charge = springSpringCharge;
96 SpeedMultiplier = speedBarValue;
97  
98 Configuration.Spring.Charge.PropertyChanged += Loading_PropertyChanged;
99 Configuration.Spring.PropertyChanged += Spring_PropertyChanged;
100  
101 Form.SpeedbarValueChanged += Form_SpeedbarValueChanged;
102  
103 WatchKey.KeyUp += SpringWatchKey_SpringKeyUp;
104  
105 Charge.ChargeChanged += SpringChange_ChargeChanged;
106  
107 Charge.ChargeLoaded += SpringCharge_ChargeLoaded;
108  
109 Key.KeyComboConfigured += Key_SpringKeyComboConfigured;
110  
111 if (Charge != null)
112 {
113 OriginalSpringCombos = Charge.Combos;
114 }
115 }
116  
117 public void Dispose()
118 {
119 if (_disposing)
120 {
121 return;
122 }
123  
124 _disposing = true;
125  
126 Configuration.Spring.Charge.PropertyChanged -= Loading_PropertyChanged;
127 Configuration.Spring.PropertyChanged -= Spring_PropertyChanged;
128  
129 Form.SpeedbarValueChanged -= Form_SpeedbarValueChanged;
130  
131 WatchKey.KeyUp -= SpringWatchKey_SpringKeyUp;
132  
133 Charge.ChargeChanged -= SpringChange_ChargeChanged;
134  
135 Charge.ChargeLoaded -= SpringCharge_ChargeLoaded;
136  
137 Key.KeyComboConfigured -= Key_SpringKeyComboConfigured;
138  
139 StopDischarge()
140 .Wait();
141 }
142  
143 #endregion
144  
145 #region Event Handlers
146  
147 private void Spring_PropertyChanged(object sender, PropertyChangedEventArgs e)
148 {
149 Fuzz = ((Configuration.Spring) sender).Fuzz;
150 }
151  
152 private async void Key_SpringKeyComboConfigured(object sender, KeyComboConfiguredEventArgs e)
153 {
154 await StopDischarge();
155 }
156  
157 private void Loading_PropertyChanged(object sender, PropertyChangedEventArgs e)
158 {
159 Configuration.Spring.Charge = (Configuration.Charge) sender;
160 }
161  
162 private async void Form_SpeedbarValueChanged(object sender, SpeedbarValueChangedEventArgs e)
163 {
164 if (ExecutingSpringCombos == null || OriginalSpringCombos == null)
165 {
166 return;
167 }
168  
169 SpeedMultiplier = e.Value;
170  
171 try
172 {
173 await AdjustComboSpeed(e.Value);
174 }
175 catch (Exception ex) when (ex is OperationCanceledException || ex is ObjectDisposedException)
176 {
177 }
178 }
179  
180 private async void SpringWatchKey_SpringKeyUp(object sender, KeyUpEventArgs e)
181 {
182 if (e.TimePressed > TimeSpan.FromSeconds(Configuration.Spring.Charge.ChargeSeconds))
183 {
184 return;
185 }
186  
187 if (_isDischarging)
188 {
189 await StopDischarge();
190  
191 return;
192 }
193  
194 if (OriginalSpringCombos == null)
195 {
196 return;
197 }
198  
199 StartDischarge();
200 }
201  
202 private void SpringCharge_ChargeLoaded(object sender, ChargeLoadedEventArgs e)
203 {
204 OriginalSpringCombos = e.Combos;
205 }
206  
207 private void SpringChange_ChargeChanged(object sender, ChargeChangedEventArgs e)
208 {
209 OriginalSpringCombos = e.Combos;
210 }
211  
212 #endregion
213  
214 #region Private Methods
215  
216 private async Task StopDischarge()
217 {
218 DischargeCancellationTokenSource?.Cancel();
219  
220 if (DischargeTask != null)
221 {
222 await DischargeTask;
223 DischargeTask = null;
224 }
225  
226 DischargeCancellationTokenSource = null;
227  
228 _isDischarging = false;
229 }
230  
231 private void StartDischarge()
232 {
233 #pragma warning disable 4014
234 DischargeTask = Task.Run(() => StartDischarge(OriginalSpringCombos));
235 #pragma warning restore 4014
236 _isDischarging = true;
237 }
238  
239 private async Task AdjustComboSpeed(int speedMultiplier)
240 {
241 Debug.Assert(ExecutingSpringCombos.Combo.Count == OriginalSpringCombos.Combo.Count,
242 "Executing combo count and original combo count are not the same.");
243  
244 if (DischargeCancellationTokenSource != null)
245 {
246 var cancellationToken = DischargeCancellationTokenSource.Token;
247  
248 await DischargeSemaphore.WaitAsync(cancellationToken);
249 }
250  
251 try
252 {
253 foreach (var originalCombo in OriginalSpringCombos.Combo)
254 {
255 if (!(originalCombo is PauseCombo originalPauseCombo))
256 {
257 continue;
258 }
259  
260 var executingCombo = ExecutingSpringCombos.Combo.ElementAtOrDefault(originalCombo.Index);
261  
262 if (executingCombo is PauseCombo executingPauseCombo)
263 {
264 executingPauseCombo.TimeSpan =
265 GetAdjustedPauseCombo(originalPauseCombo, speedMultiplier);
266  
267 continue;
268 }
269  
270 Debug.Assert(executingCombo is PauseCombo,
271 "Different combo found instead of pause combo when adjusting speed.");
272 }
273  
274 PauseCancellationTokenSource?.Dispose();
275 PauseCancellationTokenSource = null;
276 }
277 finally
278 {
279 if (DischargeCancellationTokenSource != null)
280 {
281 DischargeSemaphore.Release();
282 }
283 }
284 }
285  
286 private async Task StartDischarge(Combos args)
287 {
288 var adjustedCombos = AdjustCombos(args);
289  
290 ExecutingSpringCombos = new Combos(adjustedCombos)
291 { ComboRepeat = args.ComboRepeat };
292  
293 Debug.Assert(ExecutingSpringCombos.Combo.Count == OriginalSpringCombos.Combo.Count,
294 "Executing combo count and original combo count are not the same.");
295  
296 var ring = new Ring<Combo>(ExecutingSpringCombos.Combo);
297  
298 try
299 {
300 using (DischargeCancellationTokenSource = new CancellationTokenSource())
301 {
302 var dischargeCancellationToken = DischargeCancellationTokenSource.Token;
303  
304 SpringDischargeStart?.Invoke(this,
305 new DischargeStartEventArgs(ExecutingSpringCombos.ComboRepeat));
306  
307 var loop = ExecutingSpringCombos.ComboRepeat * ring.Count;
308  
309 do
310 {
311 await DischargeSemaphore.WaitAsync(dischargeCancellationToken);
312  
313 try
314 {
315 // Circular queue. . .
316 ring.Get(out var @event);
317  
318 await SimulateEvent(ring, @event, dischargeCancellationToken);
319  
320 SpringDischargeProgress?.Invoke(this,
321 new DischargeProgressEventArgs(loop,
322 ring.Count,
323 ExecutingSpringCombos.ComboRepeat));
324 }
325 catch (Exception ex) when (ex is OperationCanceledException || ex is ObjectDisposedException)
326 {
327 }
328 finally
329 {
330 DischargeSemaphore.Release();
331 }
332 } while (!dischargeCancellationToken.IsCancellationRequested && (loop == 0 || --loop > 0));
333 }
334 }
335 catch (Exception ex) when (ex is OperationCanceledException || ex is ObjectDisposedException)
336 {
337 }
338 finally
339 {
340 DischargeCancellationTokenSource = null;
341  
342 await StopAllActions(ring, ExecutingSpringCombos, CancellationToken.None);
343  
344 _isDischarging = false;
345  
346 SpringDischargeStop?.Invoke(this,
347 new DischargeStopEventArgs(ExecutingSpringCombos.ComboRepeat));
348 }
349 }
350  
351 private async Task StopAllActions(Ring<Combo> combos, Combos springCombos, CancellationToken cancellationToken)
352 {
353 foreach (var combo in springCombos.Combo)
354 {
355 switch (combo)
356 {
357 case KeyboardCombo keyboardCombo:
358 if (keyboardCombo.ComboAction == ComboAction.Down)
359 {
360 await SimulateEvent(new KeyboardCombo
361 { ComboAction = ComboAction.Up, Keys = keyboardCombo.Keys },
362 cancellationToken);
363 }
364  
365 break;
366  
367 case MouseCombo mouseCombo:
368 if (mouseCombo.ComboAction == ComboAction.Down)
369 {
370 await SimulateEvent(combos,
371 new MouseCombo
372 { ComboAction = ComboAction.Up, Button = mouseCombo.Button },
373 cancellationToken);
374 }
375  
376 break;
377 }
378 }
379 }
380  
381 private IEnumerable<Combo> AdjustCombos(Combos springCombos)
382 {
383 foreach (var combo in springCombos.Combo)
384 {
385 switch (combo)
386 {
387 case PauseCombo pauseCombo:
388 var adjustedPauseCombo = GetAdjustedPauseCombo(pauseCombo, SpeedMultiplier);
389  
390 yield return new PauseCombo(adjustedPauseCombo)
391 { Index = combo.Index, Fuzz = Fuzz };
392  
393 break;
394  
395 default:
396 yield return combo;
397  
398 break;
399 }
400 }
401 }
402  
403 private static TimeSpan GetAdjustedPauseCombo(PauseCombo pauseCombo, int multiplier)
404 {
405 var timeSpan = pauseCombo.TimeSpan;
406  
407 switch (Math.Sign(multiplier))
408 {
409 case -1:
410 timeSpan =
411 TimeSpan.FromTicks(
412 pauseCombo.TimeSpan.Ticks * Math.Abs(multiplier));
413  
414 break;
415  
416 case 0:
417  
418 break;
419  
420 case 1:
421 timeSpan =
422 TimeSpan.FromTicks(
423 pauseCombo.TimeSpan.Ticks / Math.Abs(multiplier));
424  
425 break;
426 }
427  
428 return timeSpan;
429 }
430  
431 private async Task SimulateEvent(PauseCombo pauseCombo, CancellationToken cancellationToken)
432 {
433 try
434 {
435 using (PauseCancellationTokenSource = new CancellationTokenSource())
436 {
437 var localCancellationToken = PauseCancellationTokenSource.Token;
438  
439 using (var combinedCancellationTokenSource =
440 CancellationTokenSource.CreateLinkedTokenSource(
441 new[] { cancellationToken, localCancellationToken }))
442 {
443 var combinedCancellationToken = combinedCancellationTokenSource.Token;
444  
445 TimeSpan fuzzedTimeSpan;
446  
447 var ms = pauseCombo.Fuzz == 0 && Fuzz != 0 ? Fuzz : pauseCombo.Fuzz;
448  
449 switch (Random.Next(0, 2))
450 {
451 case 1:
452 fuzzedTimeSpan = pauseCombo.TimeSpan + TimeSpan.FromMilliseconds(ms);
453  
454 break;
455  
456 default:
457 fuzzedTimeSpan =
458 pauseCombo.TimeSpan - TimeSpan.FromMilliseconds(ms);
459  
460 break;
461 }
462  
463 $"Combo timespan: {pauseCombo.TimeSpan} vs. Fuzzed timespan: {fuzzedTimeSpan}".ToDebugConsole();
464  
465 await Task.Delay(fuzzedTimeSpan, combinedCancellationToken);
466 }
467 }
468 }
469 catch (Exception ex) when (ex is OperationCanceledException || ex is ObjectDisposedException)
470 {
471 }
472 }
473  
474 private async Task SimulateEvent(Ring<Combo> combos, MouseCombo mouseCombo, CancellationToken cancellationToken)
475 {
476 Task simulate = null;
477  
478 try
479 {
480 switch (mouseCombo.ComboAction)
481 {
482 case ComboAction.Up:
483 case ComboAction.Down:
484 switch (mouseCombo.Button)
485 {
486 case MouseButtons.Left:
487 switch (mouseCombo.ComboAction)
488 {
489 case ComboAction.Down:
490 simulate = Task.Run(() => { InputSimulator.Mouse.LeftButtonDown(); },
491 cancellationToken);
492  
493 break;
494  
495 case ComboAction.Up:
496 simulate = Task.Run(() => { InputSimulator.Mouse.LeftButtonUp(); },
497 cancellationToken);
498  
499 break;
500 }
501  
502 break;
503  
504 case MouseButtons.Middle:
505 switch (mouseCombo.ComboAction)
506 {
507 case ComboAction.Down:
508 simulate = Task.Run(() => { InputSimulator.Mouse.MiddleButtonDown(); },
509 cancellationToken);
510  
511 break;
512  
513 case ComboAction.Up:
514 simulate = Task.Run(() => { InputSimulator.Mouse.MiddleButtonUp(); },
515 cancellationToken);
516  
517 break;
518 }
519  
520 break;
521  
522 case MouseButtons.Right:
523 switch (mouseCombo.ComboAction)
524 {
525 case ComboAction.Down:
526 simulate = Task.Run(() => { InputSimulator.Mouse.RightButtonDown(); },
527 cancellationToken);
528  
529 break;
530  
531 case ComboAction.Up:
532 simulate = Task.Run(() => { InputSimulator.Mouse.RightButtonUp(); },
533 cancellationToken);
534  
535 break;
536 }
537  
538 break;
539 }
540  
541 break;
542  
543 case ComboAction.Move:
544 simulate = Task.Run(() =>
545 {
546 switch (Configuration.Spring.Discharge.UseRelativeMouseMovement)
547 {
548 case true:
549 var lastCombo = (MouseCombo) combos
550 .SeekReverseUntil(combo =>
551 combo is MouseCombo lastMouseCombo &&
552 lastMouseCombo.ComboAction == ComboAction.Move);
553  
554 if (lastCombo == default(MouseCombo))
555 {
556 return;
557 }
558  
559 var deltaX = mouseCombo.X - lastCombo.X;
560 var deltaY = mouseCombo.Y - lastCombo.Y;
561  
562 // Relative mouse movements are subject to modification by the system-wide mouse speed settings
563 // such that they need to be stored and restored before and after performing the mouse move.
564 var mouseSpeed = MouseOptions.GetMouseSpeed();
565 var mouseEnhancedPointerPrecision =
566 MouseOptions.GetMouseEnhancedPrecision();
567  
568 try
569 {
570 MouseOptions.SetDefaultMouseSpeed();
571 MouseOptions.SetMouseEnhancedPrecision(false);
572  
573 InputSimulator.Mouse.MoveMouseBy((int) deltaX, (int) deltaY);
574 }
575 finally
576 {
577 MouseOptions.SetMouseSpeed(mouseSpeed);
578 MouseOptions.SetMouseEnhancedPrecision(mouseEnhancedPointerPrecision);
579 }
580  
581 break;
582 default:
583 InputSimulator.Mouse.MoveMouseToPositionOnVirtualDesktop(
584 mouseCombo.X.MapValueToRange(0d,
585 SystemInformation.VirtualScreen.Width,
586 0d,
587 65535d),
588 mouseCombo.Y.MapValueToRange(0d,
589 SystemInformation.VirtualScreen.Height,
590 0d,
591 65535d));
592  
593 break;
594 }
595 },
596 cancellationToken);
597  
598 break;
599 }
600  
601 if (simulate != null)
602 {
603 await simulate;
604 }
605 }
606 catch (Exception ex) when (ex is OperationCanceledException || ex is ObjectDisposedException)
607 {
608 }
609 }
610  
611 private async Task SimulateEvent(KeyboardCombo keyboardCombo, CancellationToken cancellationToken)
612 {
613 Task simulate = null;
614  
615 try
616 {
617 switch (keyboardCombo.ComboAction)
618 {
619 case ComboAction.Up:
620 simulate = Task.Run(() =>
621 {
622 InputSimulator.Keyboard.KeyUp((VirtualKeyCode) keyboardCombo.Keys);
623 },
624 cancellationToken);
625  
626 break;
627  
628 case ComboAction.Down:
629 simulate = Task.Run(() =>
630 {
631 InputSimulator.Keyboard.KeyDown((VirtualKeyCode) keyboardCombo.Keys);
632 },
633 cancellationToken);
634  
635 break;
636 }
637  
638 Debug.Assert(simulate != null, "Unknown key event found in key combo discharge simulate.");
639  
640 await simulate;
641 }
642 catch (Exception ex) when (ex is OperationCanceledException || ex is ObjectDisposedException)
643 {
644 }
645 }
646  
647 private async Task SimulateEvent<T>(Ring<Combo> combos, T eventArgs, CancellationToken cancellationToken)
648 {
649 switch (eventArgs)
650 {
651 case MouseCombo springMouseEventArgs:
652 await SimulateEvent(combos, springMouseEventArgs, cancellationToken);
653  
654 break;
655  
656 case KeyboardCombo springKeyEventArgs:
657 await SimulateEvent(springKeyEventArgs, cancellationToken);
658  
659 break;
660  
661 case PauseCombo pauseEventArgs:
662 await SimulateEvent(pauseEventArgs, cancellationToken);
663  
664 break;
665 }
666 }
667  
668 #endregion
669 }
670 }