From 0299ad4a6438141db08fc55c161e1b2812c34f19 Mon Sep 17 00:00:00 2001 From: joekolodz Date: Sun, 14 Aug 2022 22:47:01 -0500 Subject: [PATCH] Add functionality to repeat a button press a number of times; added text box and label; new repeat count property on button; new events for repeat; display icon in Activity list; --- src/Controls/MacroTransport.xaml | 7 +- src/Models/HOTASButton.cs | 1 + src/Models/HOTASCollection.cs | 40 ++++++-- src/Models/HOTASDevice.cs | 16 +++ src/Models/HOTASQueue.cs | 108 ++++++++++++++++++--- src/Models/IHOTASCollection.cs | 2 + src/Models/IHOTASDevice.cs | 2 + src/Models/IHOTASQueue.cs | 2 + src/Models/RecordStartedEventArgs.cs | 16 +++ src/Models/RepeatCancelledEventArgs.cs | 16 +++ src/SierraHOTAS.csproj | 2 + src/ViewModels/ButtonMapViewModel.cs | 11 +++ src/ViewModels/HOTASCollectionViewModel.cs | 19 +++- src/Win32/Keyboard.cs | 4 +- src/Win32/Win32Structures.cs | 4 +- 15 files changed, 223 insertions(+), 27 deletions(-) create mode 100644 src/Models/RecordStartedEventArgs.cs create mode 100644 src/Models/RepeatCancelledEventArgs.cs diff --git a/src/Controls/MacroTransport.xaml b/src/Controls/MacroTransport.xaml index 31e66ef..a644406 100644 --- a/src/Controls/MacroTransport.xaml +++ b/src/Controls/MacroTransport.xaml @@ -65,6 +65,11 @@ Margin="0,2,0,0" HorizontalAlignment="Left" VerticalAlignment="Center"/> - One Shot + + + One Shot + diff --git a/src/Models/HOTASButton.cs b/src/Models/HOTASButton.cs index 2af2ee5..4ceb449 100644 --- a/src/Models/HOTASButton.cs +++ b/src/Models/HOTASButton.cs @@ -21,6 +21,7 @@ public enum ButtonType public int ShiftModePage { get; set; } public bool IsShift { get; set; } public bool IsOneShot { get; set; } + public int RepeatCount{ get; set; } public Guid ActionId { diff --git a/src/Models/HOTASCollection.cs b/src/Models/HOTASCollection.cs index 05c847c..ae0dfbf 100644 --- a/src/Models/HOTASCollection.cs +++ b/src/Models/HOTASCollection.cs @@ -25,6 +25,8 @@ public class HOTASCollection : IHOTASCollection public virtual event EventHandler KeystrokeUpSent; public virtual event EventHandler MacroStarted; public virtual event EventHandler MacroCancelled; + public virtual event EventHandler RepeatStarted; + public virtual event EventHandler RepeatCancelled; public virtual event EventHandler ButtonPressed; public virtual event EventHandler AxisChanged; public virtual event EventHandler ModeChanged; @@ -120,15 +122,7 @@ private void StopDevice(IHOTASDevice device) { if (device == null) return; - device.ButtonPressed -= Device_ButtonPressed; - device.AxisChanged -= Device_AxisChanged; - device.KeystrokeDownSent -= Device_KeystrokeDownSent; - device.KeystrokeUpSent -= Device_KeystrokeUpSent; - device.MacroStarted -= Device_MacroStarted; - device.MacroCancelled -= Device_MacroCancelled; - device.ModeSelected -= device_modeSelected; - device.ShiftReleased -= Device_ShiftReleased; - device.LostConnectionToDevice -= Device_LostConnectionToDevice; + RemoveHandlers(device); device.Stop(); } @@ -163,12 +157,16 @@ public void ListenToAllDevices() public void ListenToDevice(IHOTASDevice device) { + RemoveHandlers(device); + device.ButtonPressed += Device_ButtonPressed; device.AxisChanged += Device_AxisChanged; device.KeystrokeDownSent += Device_KeystrokeDownSent; device.KeystrokeUpSent += Device_KeystrokeUpSent; device.MacroStarted += Device_MacroStarted; device.MacroCancelled += Device_MacroCancelled; + device.RepeatStarted += Device_RepeatStarted; + device.RepeatCancelled += Device_RepeatCancelled; device.ModeSelected += device_modeSelected; device.ShiftReleased += Device_ShiftReleased; device.LostConnectionToDevice += Device_LostConnectionToDevice; @@ -177,6 +175,21 @@ public void ListenToDevice(IHOTASDevice device) device.ListenAsync(); } + private void RemoveHandlers(IHOTASDevice device) + { + device.ButtonPressed -= Device_ButtonPressed; + device.AxisChanged -= Device_AxisChanged; + device.KeystrokeDownSent -= Device_KeystrokeDownSent; + device.KeystrokeUpSent -= Device_KeystrokeUpSent; + device.MacroStarted -= Device_MacroStarted; + device.MacroCancelled -= Device_MacroCancelled; + device.RepeatStarted -= Device_RepeatStarted; + device.RepeatCancelled -= Device_RepeatCancelled; + device.ModeSelected -= device_modeSelected; + device.ShiftReleased -= Device_ShiftReleased; + device.LostConnectionToDevice -= Device_LostConnectionToDevice; + } + private void Device_LostConnectionToDevice(object sender, LostConnectionToDeviceEventArgs e) { LostConnectionToDevice?.Invoke(sender, e); @@ -252,6 +265,15 @@ private void Device_MacroCancelled(object sender, MacroCancelledEventArgs e) { MacroCancelled?.Invoke(sender, e); } + private void Device_RepeatStarted(object sender, RepeatStartedEventArgs e) + { + Logging.Log.Debug("HOTASCollection - repeat started event"); + RepeatStarted?.Invoke(sender, e); + } + private void Device_RepeatCancelled(object sender, RepeatCancelledEventArgs e) + { + RepeatCancelled?.Invoke(sender, e); + } public void ForceButtonPress(IHOTASDevice device, JoystickOffset offset, bool isDown) { diff --git a/src/Models/HOTASDevice.cs b/src/Models/HOTASDevice.cs index 17bf75d..4d6377d 100644 --- a/src/Models/HOTASDevice.cs +++ b/src/Models/HOTASDevice.cs @@ -17,6 +17,8 @@ public class HOTASDevice : IHOTASDevice public event EventHandler KeystrokeUpSent; public event EventHandler MacroStarted; public event EventHandler MacroCancelled; + public event EventHandler RepeatStarted; + public event EventHandler RepeatCancelled; public event EventHandler ButtonPressed; public event EventHandler ModeSelected; public event EventHandler ShiftReleased; @@ -228,6 +230,8 @@ private void AddQueueHandlers() _hotasQueue.KeystrokeUpSent += OnKeystrokeUpSent; _hotasQueue.MacroStarted += OnMacroStarted; _hotasQueue.MacroCancelled += OnMacroCancelled; + _hotasQueue.RepeatStarted += OnRepeatStarted; + _hotasQueue.RepeatCancelled += OnRepeatCancelled; _hotasQueue.ButtonPressed += OnButtonPress; _hotasQueue.AxisChanged += OnAxisChanged; _hotasQueue.ModeSelected += onModeSelected; @@ -241,6 +245,8 @@ private void RemoveQueueHandlers() _hotasQueue.KeystrokeUpSent -= OnKeystrokeUpSent; _hotasQueue.MacroStarted -= OnMacroStarted; _hotasQueue.MacroCancelled -= OnMacroCancelled; + _hotasQueue.RepeatStarted -= OnRepeatStarted; + _hotasQueue.RepeatCancelled -= OnRepeatCancelled; _hotasQueue.ButtonPressed -= OnButtonPress; _hotasQueue.AxisChanged -= OnAxisChanged; _hotasQueue.ModeSelected -= onModeSelected; @@ -473,6 +479,16 @@ private void OnMacroCancelled(object sender, MacroCancelledEventArgs e) MacroCancelled?.Invoke(sender, e); } + private void OnRepeatStarted(object sender, RepeatStartedEventArgs e) + { + Logging.Log.Debug("HOTASDevice - repeat started event"); + RepeatStarted?.Invoke(sender, e); + } + private void OnRepeatCancelled(object sender, RepeatCancelledEventArgs e) + { + RepeatCancelled?.Invoke(sender, e); + } + private void OnButtonPress(object sender, ButtonPressedEventArgs e) { ButtonPressed?.Invoke(this, new ButtonPressedEventArgs() { ButtonId = e.ButtonId, Device = this }); diff --git a/src/Models/HOTASQueue.cs b/src/Models/HOTASQueue.cs index 1240c09..e2c3111 100644 --- a/src/Models/HOTASQueue.cs +++ b/src/Models/HOTASQueue.cs @@ -18,6 +18,8 @@ public class HOTASQueue : IHOTASQueue public event EventHandler KeystrokeDownSent; public event EventHandler MacroStarted; public event EventHandler MacroCancelled; + public event EventHandler RepeatStarted; + public event EventHandler RepeatCancelled; public event EventHandler ButtonPressed; public event EventHandler ButtonReleased; public event EventHandler AxisChanged; @@ -82,7 +84,7 @@ public static int TranslatePointOfViewOffset(JoystickOffset offset, int value) return translatedOffset; } - private void ListenLoop() + private async Task ListenLoop() { while (!_isStopRequested) { @@ -110,7 +112,7 @@ private void ListenLoop() if (offset >= JoystickOffset.Button1 && offset <= JoystickOffset.Button128) { Logging.Log.Debug($"Offset:{offset}({state.RawOffset}), Seq:{state.Sequence}, Value:{state.Value}"); - HandleStandardButton((int)offset, state.Value); + await HandleStandardButton((int)offset, state.Value); continue; } @@ -120,7 +122,7 @@ private void ListenLoop() offset == JoystickOffset.POV4) { Logging.Log.Debug($"Offset:{offset}({state.RawOffset}), POV:{TranslatePointOfViewOffset(offset, state.Value)}, Seq:{state.Sequence}, Value:{state.Value}"); - HandlePovButton((JoystickOffset)state.Offset, state.Value); + await HandlePovButton((JoystickOffset)state.Offset, state.Value); continue; } @@ -141,7 +143,7 @@ private void ListenLoop() } if (_jitterDetectionDictionary[state.RawOffset].IsJitter(state.Value)) continue; - HandleAxis(state); + await HandleAxis(state); OnAxisChanged(state); continue; } @@ -196,7 +198,7 @@ private bool IsButtonDown(int value) } private static readonly ConcurrentDictionary _lastPovButton = new ConcurrentDictionary(); - private void HandlePovButton(JoystickOffset offset, int value) + private async Task HandlePovButton(JoystickOffset offset, int value) { if (_lastPovButton.ContainsKey(offset) || value == (int)JoystickOffsetValues.PointOfViewPositionValues.Released) { @@ -218,14 +220,15 @@ private void HandlePovButton(JoystickOffset offset, int value) if (!(GetMap(translatedOffset) is HOTASButton map)) return; - HandleButtonPressed(map, translatedOffset); + await HandleButtonPressed(map, translatedOffset); OnButtonPress(translatedOffset); } } + private static readonly ConcurrentDictionary _activeRepeat = new ConcurrentDictionary(); private static readonly ConcurrentDictionary _activeMacros = new ConcurrentDictionary(); private static readonly ConcurrentDictionary _activeButtons = new ConcurrentDictionary(); - private void HandleStandardButton(int offset, int value) + private async Task HandleStandardButton(int offset, int value) { var map = GetMap(offset) as HOTASButton; if (IsButtonDown(value)) @@ -233,7 +236,7 @@ private void HandleStandardButton(int offset, int value) if (map != null && (map.ActionCatalogItem.Actions.Count > 0 || map.ShiftModePage > 0)) { //if action list has a timer in it, then it is a macro and executes on another thread independently. does not interrupt other buttons - HandleButtonPressed(map, offset); + await HandleButtonPressed(map, offset); } else { @@ -243,7 +246,7 @@ private void HandleStandardButton(int offset, int value) map = GetMapFromParentMode(_modeActivationButtons[_mode].InheritFromMode, offset) as HOTASButton; if (map != null) { - HandleButtonPressed(map, offset); + await HandleButtonPressed(map, offset); } } } @@ -257,10 +260,17 @@ private void HandleStandardButton(int offset, int value) } } - private void HandleButtonPressed(HOTASButton button, int offset) + private async Task HandleButtonPressed(HOTASButton button, int offset) { if (button == null) return; + //macros and oneshots can be repeated so check repeats first + if (button.RepeatCount != 0) + { + HandleRepeat(button, offset); + return; + } + if (button.IsOneShot) { HandleOneShot(button, offset); @@ -282,6 +292,80 @@ private void HandleButtonPressed(HOTASButton button, int offset) _actionJobs.Add(new ActionJobItem() { Offset = offset, MapId = button.MapId, Actions = button.ActionCatalogItem.Actions }); } + private void HandleRepeat(HOTASButton button, int offset) + { + if (_activeRepeat.TryGetValue(offset, out _)) + { + //cancel a repeat already in progress + _activeRepeat.TryRemove(offset, out _); + RepeatCancelled?.Invoke(this, new RepeatCancelledEventArgs(offset, (int)Win32Structures.ScanCodeShort.REPEAT_CANCELLED)); + return; + } + + _activeRepeat.TryAdd(offset, true); + + Task.Run(() => PlayRepeat(button, offset)); + } + + private async Task PlayRepeat(HOTASButton button, int offset) + { + Logging.Log.Debug("HOTASQueue - repeat started event"); + RepeatStarted?.Invoke(this, new RepeatStartedEventArgs(offset, (int)Win32Structures.ScanCodeShort.REPEAT_STARTED)); + + var repeatedButton = new HOTASButton() + { + RepeatCount = 0, + ActionCatalogItem = button.ActionCatalogItem, + ActionId = button.ActionId, + ActionName = button.ActionName, + IsOneShot = button.IsOneShot, + IsShift = button.IsShift, + MapId = button.MapId, + MapName = button.MapName, + ShiftModePage = button.ShiftModePage, + Type = button.Type + }; + + + var repeatsLeft = button.RepeatCount; + + if (repeatsLeft == -1) + { + while (true) + { + if (await BaseRepeat(button, offset, repeatedButton)) break; + } + } + else + { + while (repeatsLeft-- > 0) + { + if (await BaseRepeat(button, offset, repeatedButton)) break; + } + } + + _activeRepeat.TryRemove(offset, out _); + } + + private async Task BaseRepeat(HOTASButton button, int offset, HOTASButton repeatedButton) + { + await HandleButtonPressed(repeatedButton, offset); + + if (_activeRepeat.ContainsKey(offset) == false) return true; + + if (button.IsMacro) + { + while (_activeMacros.ContainsKey(offset)) + { + await Task.Delay(500); + if (_activeRepeat.ContainsKey(offset) == false) break; + } + } + + return false; + } + + private void HandleMacro(HOTASButton button, int offset) { if (_activeMacros.TryGetValue(offset, out _)) @@ -382,7 +466,7 @@ private void HandleButtonReleased(HOTASButton button, int offset) _actionJobs.Add(new ActionJobItem() { Offset = offset, MapId = mapId, Actions = null }); } - private void HandleAxis(JoystickUpdate state) + private async Task HandleAxis(JoystickUpdate state) { var offset = (int)state.Offset; @@ -393,7 +477,7 @@ private void HandleAxis(JoystickUpdate state) if (!axis.IsSegmentChanged) return; var map = axis.GetButtonMapFromRawValue(state.Value); - HandleButtonPressed(map, offset); + await HandleButtonPressed(map, offset); HandleButtonReleased(map, offset); } diff --git a/src/Models/IHOTASCollection.cs b/src/Models/IHOTASCollection.cs index 2d86cc6..2a2170a 100644 --- a/src/Models/IHOTASCollection.cs +++ b/src/Models/IHOTASCollection.cs @@ -10,6 +10,8 @@ public interface IHOTASCollection event EventHandler KeystrokeUpSent; event EventHandler MacroStarted; event EventHandler MacroCancelled; + event EventHandler RepeatStarted; + event EventHandler RepeatCancelled; event EventHandler ButtonPressed; event EventHandler AxisChanged; event EventHandler ModeChanged; diff --git a/src/Models/IHOTASDevice.cs b/src/Models/IHOTASDevice.cs index 56c5dd5..9886965 100644 --- a/src/Models/IHOTASDevice.cs +++ b/src/Models/IHOTASDevice.cs @@ -11,6 +11,8 @@ public interface IHOTASDevice event EventHandler KeystrokeUpSent; event EventHandler MacroStarted; event EventHandler MacroCancelled; + event EventHandler RepeatStarted; + event EventHandler RepeatCancelled; event EventHandler ButtonPressed; event EventHandler ModeSelected; event EventHandler ShiftReleased; diff --git a/src/Models/IHOTASQueue.cs b/src/Models/IHOTASQueue.cs index 80c3ee2..156a29a 100644 --- a/src/Models/IHOTASQueue.cs +++ b/src/Models/IHOTASQueue.cs @@ -10,6 +10,8 @@ public interface IHOTASQueue event EventHandler KeystrokeDownSent; event EventHandler MacroCancelled; event EventHandler MacroStarted; + event EventHandler RepeatStarted; + event EventHandler RepeatCancelled; event EventHandler ButtonPressed; event EventHandler ButtonReleased; event EventHandler AxisChanged; diff --git a/src/Models/RecordStartedEventArgs.cs b/src/Models/RecordStartedEventArgs.cs new file mode 100644 index 0000000..2d4e55e --- /dev/null +++ b/src/Models/RecordStartedEventArgs.cs @@ -0,0 +1,16 @@ +using System; + +namespace SierraHOTAS.Models +{ + public class RepeatStartedEventArgs : EventArgs + { + public int Offset { get; set; } + public int Code { get; set; } + + public RepeatStartedEventArgs(int offset, int code) + { + Offset = offset; + Code = code; + } + } +} diff --git a/src/Models/RepeatCancelledEventArgs.cs b/src/Models/RepeatCancelledEventArgs.cs new file mode 100644 index 0000000..d2ce8b0 --- /dev/null +++ b/src/Models/RepeatCancelledEventArgs.cs @@ -0,0 +1,16 @@ +using System; + +namespace SierraHOTAS.Models +{ + public class RepeatCancelledEventArgs : EventArgs + { + public int Offset { get; set; } + public int Code { get; set; } + + public RepeatCancelledEventArgs(int offset, int code) + { + Offset = offset; + Code = code; + } + } +} diff --git a/src/SierraHOTAS.csproj b/src/SierraHOTAS.csproj index 9b5c0a5..9f9d9a8 100644 --- a/src/SierraHOTAS.csproj +++ b/src/SierraHOTAS.csproj @@ -166,6 +166,8 @@ + + diff --git a/src/ViewModels/ButtonMapViewModel.cs b/src/ViewModels/ButtonMapViewModel.cs index 0735f55..c8fdf59 100644 --- a/src/ViewModels/ButtonMapViewModel.cs +++ b/src/ViewModels/ButtonMapViewModel.cs @@ -59,6 +59,17 @@ public bool IsOneShot } } + public int RepeatCount + { + get => _hotasButton.RepeatCount; + set + { + if (_hotasButton.RepeatCount == value) return; + _hotasButton.RepeatCount = value; + OnPropertyChanged(nameof(RepeatCount)); + } + } + public string ActionName { get => ActionItem.ActionName; diff --git a/src/ViewModels/HOTASCollectionViewModel.cs b/src/ViewModels/HOTASCollectionViewModel.cs index 3433180..64f82f9 100644 --- a/src/ViewModels/HOTASCollectionViewModel.cs +++ b/src/ViewModels/HOTASCollectionViewModel.cs @@ -276,6 +276,8 @@ public void Initialize() _deviceList.KeystrokeDownSent += DeviceList_KeystrokeDownSent; _deviceList.MacroStarted += DeviceList_MacroStarted; _deviceList.MacroCancelled += DeviceList_MacroCancelled; + _deviceList.RepeatStarted += DeviceList_RepeatStarted; + _deviceList.RepeatCancelled += DeviceList_RepeatCancelled; _deviceList.KeystrokeUpSent += DeviceList_KeystrokeUpSent; _deviceList.ModeChanged += OnModeChanged; _deviceList.LostConnectionToDevice += DeviceList_LostConnectionToDevice; @@ -353,15 +355,26 @@ private void AddActivity(IHotasBaseMap map, KeystrokeSentEventArgs e) private void DeviceList_MacroStarted(object sender, MacroStartedEventArgs e) { - AddMacroActivity(sender, e.Offset, e.Code, false, "Macro Started"); + AddCustomActivity(sender, e.Offset, e.Code, false, "Macro Started"); } private void DeviceList_MacroCancelled(object sender, MacroCancelledEventArgs e) { - AddMacroActivity(sender, e.Offset, e.Code, true, "Macro Cancelled"); + AddCustomActivity(sender, e.Offset, e.Code, true, "Macro Cancelled"); } - private void AddMacroActivity(object sender, int offset, int scanCode, bool isKeyUp, string message) + private void DeviceList_RepeatStarted(object sender, RepeatStartedEventArgs e) + { + Logging.Log.Debug("HOTASCollectionVM - repeat started event"); + AddCustomActivity(sender, e.Offset, e.Code, false, "Repeat Started"); + } + + private void DeviceList_RepeatCancelled(object sender, RepeatCancelledEventArgs e) + { + AddCustomActivity(sender, e.Offset, e.Code, true, "Repeat Cancelled"); + } + + private void AddCustomActivity(object sender, int offset, int scanCode, bool isKeyUp, string message) { var map = (sender as HOTASQueue)?.GetMap(offset); if (map == null) return; diff --git a/src/Win32/Keyboard.cs b/src/Win32/Keyboard.cs index 4729243..82d59f7 100644 --- a/src/Win32/Keyboard.cs +++ b/src/Win32/Keyboard.cs @@ -97,7 +97,9 @@ private static void PopulateKeyDisplayNamesExtendedDictionary() {Win32Structures.ScanCodeShort.LEFT, "LEFT"}, {Win32Structures.ScanCodeShort.RETURN, "NUM ENTR"}, {Win32Structures.ScanCodeShort.MACRO_STARTED, "MACRO"}, - {Win32Structures.ScanCodeShort.MACRO_CANCELLED, "MACRO"} + {Win32Structures.ScanCodeShort.MACRO_CANCELLED, "MACRO"}, + {Win32Structures.ScanCodeShort.REPEAT_STARTED, "REPEAT"}, + {Win32Structures.ScanCodeShort.REPEAT_CANCELLED, "REPEAT"} }; } diff --git a/src/Win32/Win32Structures.cs b/src/Win32/Win32Structures.cs index 0e5fcce..9b54d37 100644 --- a/src/Win32/Win32Structures.cs +++ b/src/Win32/Win32Structures.cs @@ -883,7 +883,9 @@ public enum ScanCodeShort : short //PA1 = 0, //OEM_CLEAR = 0, MACRO_STARTED = -1, //custom addition for sierraHOTAS - MACRO_CANCELLED = -2 //custom addition for sierraHOTAS + MACRO_CANCELLED = -2, //custom addition for sierraHOTAS + REPEAT_STARTED = -3, //custom addition for sierraHOTAS + REPEAT_CANCELLED = -4 //custom addition for sierraHOTAS } [StructLayout(LayoutKind.Sequential)]