diff --git a/CHANGELOG.md b/CHANGELOG.md index 69489146..aa9ec3c6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,8 +5,18 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ## [Unreleased] +## [0.6.0] - 2018-12-03 + +### Added + +- Added bind mode for inputs + +### Changed + - Updated to IOWrapper v0.9.11 + ## [0.5.2] - 2018-10-28 ### Fixed + - Values other than 0 or 50 for DeadZone should now work properly - Maximum deflection should now be achievable again ## [0.5.1] - 2018-10-28 diff --git a/GitVersion.yml b/GitVersion.yml index f0d54013..deb4053d 100644 --- a/GitVersion.yml +++ b/GitVersion.yml @@ -5,7 +5,7 @@ branches: mode: ContinuousDelivery tag: alpha increment: none - dev(elop)?(ment)?$: + develop: mode: ContinuousDelivery tag: alpha-rc increment: Patch diff --git a/README.md b/README.md index 83f3edee..27b6e5d1 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # Universal Control Remapper -[![GitHub release](https://img.shields.io/badge/release-v0.5.2-blue.svg)](https://github.com/Snoothy/UCR/releases/tag/v0.5.2) [![IOWrapper version](https://img.shields.io/badge/IOWrapper-v0.8.3-blue.svg)](https://github.com/evilC/IOWrapper) [![license](https://img.shields.io/github/license/snoothy/ucr.svg)](https://github.com/Snoothy/UCR/blob/master/LICENSE) [![Github All Releases](https://img.shields.io/github/downloads/snoothy/ucr/total.svg)](https://github.com/Snoothy/UCR/releases) [![AppVeyor](https://ci.appveyor.com/api/projects/status/github/Snoothy/UCR?svg=true)](https://ci.appveyor.com/project/Snoothy/ucr) [![Sonarcloud Status](https://sonarcloud.io/api/project_badges/measure?project=Snoothy_UCR&metric=alert_status)](https://sonarcloud.io/dashboard?id=Snoothy_UCR) +[![GitHub release](https://img.shields.io/badge/release-v0.6.0-blue.svg)](https://github.com/Snoothy/UCR/releases/tag/v0.6.0) [![IOWrapper version](https://img.shields.io/badge/IOWrapper-v0.9.11-blue.svg)](https://github.com/evilC/IOWrapper) [![license](https://img.shields.io/github/license/snoothy/ucr.svg)](https://github.com/Snoothy/UCR/blob/master/LICENSE) [![Github All Releases](https://img.shields.io/github/downloads/snoothy/ucr/total.svg)](https://github.com/Snoothy/UCR/releases) [![AppVeyor](https://ci.appveyor.com/api/projects/status/github/Snoothy/UCR?svg=true)](https://ci.appveyor.com/project/Snoothy/ucr) [![Sonarcloud Status](https://sonarcloud.io/api/project_badges/measure?project=Snoothy_UCR&metric=alert_status)](https://sonarcloud.io/dashboard?id=Snoothy_UCR) Universal Control Remapper is a complete rewrite of the original [UCR](https://github.com/evilC/UCR), created in collaboration with [evilC](https://github.com/evilC/). diff --git a/UCR.Core/Context.cs b/UCR.Core/Context.cs index 3c574ba8..6ce58873 100644 --- a/UCR.Core/Context.cs +++ b/UCR.Core/Context.cs @@ -14,7 +14,7 @@ namespace HidWizards.UCR.Core { - public class Context : IDisposable + public sealed class Context : IDisposable { private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); private const string ContextName = "context.xml"; @@ -26,18 +26,13 @@ public class Context : IDisposable public List OutputGroups { get; set; } /* Runtime */ - [XmlIgnore] - public Profile ActiveProfile { get; set; } - [XmlIgnore] - public ProfilesManager ProfilesManager { get; set; } - [XmlIgnore] - public DevicesManager DevicesManager { get; set; } - [XmlIgnore] - public DeviceGroupsManager DeviceGroupsManager { get; set; } - [XmlIgnore] - public SubscriptionsManager SubscriptionsManager { get; set; } - [XmlIgnore] - public PluginsManager PluginManager { get; set; } + [XmlIgnore] public Profile ActiveProfile { get; set; } + [XmlIgnore] public ProfilesManager ProfilesManager { get; set; } + [XmlIgnore] public DevicesManager DevicesManager { get; set; } + [XmlIgnore] public DeviceGroupsManager DeviceGroupsManager { get; set; } + [XmlIgnore] public SubscriptionsManager SubscriptionsManager { get; set; } + [XmlIgnore] public PluginsManager PluginManager { get; set; } + [XmlIgnore] public BindingManager BindingManager { get; set; } public delegate void ActiveProfileChanged(Profile profile); public event ActiveProfileChanged ActiveProfileChangedEvent; @@ -65,6 +60,7 @@ private void Init() DeviceGroupsManager = new DeviceGroupsManager(this, InputGroups, OutputGroups); SubscriptionsManager = new SubscriptionsManager(this); PluginManager = new PluginsManager(PluginPath); + BindingManager = new BindingManager(this); } private void SetCommandLineOptions() diff --git a/UCR.Core/Managers/BindingManager.cs b/UCR.Core/Managers/BindingManager.cs new file mode 100644 index 00000000..e9b4835c --- /dev/null +++ b/UCR.Core/Managers/BindingManager.cs @@ -0,0 +1,162 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Runtime.CompilerServices; +using System.Windows.Threading; +using HidWizards.IOWrapper.DataTransferObjects; +using HidWizards.UCR.Core.Annotations; +using HidWizards.UCR.Core.Models; +using HidWizards.UCR.Core.Models.Binding; +using HidWizards.UCR.Core.Utilities; +using NLog; +using Logger = NLog.Logger; + +namespace HidWizards.UCR.Core.Managers +{ + public sealed class BindingManager : IDisposable, INotifyPropertyChanged + { + private double _bindModeProgress = 0; + + public double BindModeProgress + { + get { return _bindModeProgress / BindModeTime * 100.0; } + set + { + _bindModeProgress = value; + OnPropertyChanged(); + } + } + + private static readonly double BindModeTime = 5000.0; + private static readonly int BindModeTick = 10; + private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); + private readonly Context _context; + private List _deviceList; + private DeviceBinding _deviceBinding; + private DispatcherTimer BindingTimer; + private readonly object bindmodeLock = new object(); + private bool bindmodeActive; + + public delegate void EndBindModeDelegate(DeviceBinding deviceBinding); + public event EndBindModeDelegate EndBindModeHandler; + + public BindingManager(Context context) + { + _context = context; + _deviceList = new List(); + Logger.Debug($"Start bind mode"); + } + + public void BeginBindMode(DeviceBinding deviceBinding) + { + if (_deviceList.Count > 0) EndBindMode(); + _deviceBinding = deviceBinding; + foreach (var device in deviceBinding.Profile.GetDeviceList(deviceBinding)) + { + _context.IOController.SetDetectionMode(DetectionMode.Bind, GetProviderDescriptor(device), GetDeviceDescriptor(device), InputChanged); + _deviceList.Add(device); + } + + BindingTimer = new DispatcherTimer(); + BindingTimer.Tick += BindingTimerOnTick; + BindingTimer.Interval = TimeSpan.FromMilliseconds(BindModeTick); + BindModeProgress = BindModeTime; + BindingTimer.Start(); + bindmodeActive = true; + } + + private void BindingTimerOnTick(object sender, EventArgs e) + { + BindModeProgress = _bindModeProgress - BindModeTick; + if (BindModeProgress <= 0.0) EndBindMode(); + } + + private void EndBindMode() + { + lock (bindmodeLock) + { + Logger.Debug($"End bind mode"); + if (!bindmodeActive) return; + + EndBindModeHandler?.Invoke(_deviceBinding); + BindingTimer.Stop(); + + foreach (var device in _deviceList) + { + _context.IOController.SetDetectionMode(DetectionMode.Subscription, GetProviderDescriptor(device), + GetDeviceDescriptor(device)); + } + + _deviceList = new List(); + BindingTimer.Stop(); + bindmodeActive = false; + } + } + + private DeviceDescriptor GetDeviceDescriptor(Device device) + { + return new DeviceDescriptor() + { + DeviceHandle = device.DeviceHandle, + DeviceInstance = device.DeviceNumber + }; + } + + private ProviderDescriptor GetProviderDescriptor(Device device) + { + return new ProviderDescriptor() + { + ProviderName = device.ProviderName + }; + } + + private void InputChanged(ProviderDescriptor providerDescriptor, DeviceDescriptor deviceDescriptor, BindingReport bindingReport, int value) + { + if (!DeviceBinding.MapCategory(bindingReport.Category).Equals(_deviceBinding.DeviceBindingCategory)) return; + if (!IsInputValid(bindingReport.Category, value)) return; + + var device = FindDevice(providerDescriptor, deviceDescriptor); + _deviceBinding.SetDeviceGuid(device.Guid); + _deviceBinding.SetKeyTypeValue((int)bindingReport.BindingDescriptor.Type, bindingReport.BindingDescriptor.Index, bindingReport.BindingDescriptor.SubIndex); + EndBindMode(); + } + + private bool IsInputValid(BindingCategory bindingCategory, int value) + { + switch (DeviceBinding.MapCategory(bindingCategory)) + { + case DeviceBindingCategory.Delta: + case DeviceBindingCategory.Event: + return true; + case DeviceBindingCategory.Momentary: + return value != 0; + case DeviceBindingCategory.Range: + return Constants.AxisMaxValue * 0.4 < Math.Abs(value) + && Constants.AxisMaxValue * 0.6 > Math.Abs(value); + default: + return false; + } + } + + private Device FindDevice(ProviderDescriptor providerDescriptor, DeviceDescriptor deviceDescriptor) + { + return _deviceList.Find(d => d.ProviderName == providerDescriptor.ProviderName + && d.DeviceHandle == deviceDescriptor.DeviceHandle + && d.DeviceNumber == deviceDescriptor.DeviceInstance + ); + } + + public void Dispose() + { + EndBindMode(); + } + + public event PropertyChangedEventHandler PropertyChanged; + + [NotifyPropertyChangedInvocator] + private void OnPropertyChanged([CallerMemberName] string propertyName = null) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + } +} diff --git a/UCR.Core/Managers/SubscriptionsManager.cs b/UCR.Core/Managers/SubscriptionsManager.cs index f741066a..b53a3d12 100644 --- a/UCR.Core/Managers/SubscriptionsManager.cs +++ b/UCR.Core/Managers/SubscriptionsManager.cs @@ -1,6 +1,9 @@ using System; using System.Collections.Generic; +using System.ComponentModel; +using System.Runtime.CompilerServices; using HidWizards.IOWrapper.DataTransferObjects; +using HidWizards.UCR.Core.Annotations; using HidWizards.UCR.Core.Models; using HidWizards.UCR.Core.Models.Binding; using HidWizards.UCR.Core.Models.Subscription; @@ -9,10 +12,21 @@ namespace HidWizards.UCR.Core.Managers { - public class SubscriptionsManager : IDisposable + public sealed class SubscriptionsManager : IDisposable, INotifyPropertyChanged { private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); + private bool _profileActive; + public bool ProfileActive + { + get => _profileActive; + set + { + _profileActive = value; + OnPropertyChanged(); + } + } + internal SubscriptionState SubscriptionState { get; set; } private readonly Context _context; @@ -72,6 +86,7 @@ private void FinalizeNewState(Profile profile, SubscriptionState subscriptionSta } _context.OnActiveProfileChangedEvent(profile); + ProfileActive = true; } public bool DeactivateProfile() @@ -105,6 +120,7 @@ public bool DeactivateProfile() SubscriptionState = null; _context.ActiveProfile = null; _context.OnActiveProfileChangedEvent(null); + ProfileActive = false; return success; } @@ -283,5 +299,13 @@ public void Dispose() DeactivateProfile(); } } + + public event PropertyChangedEventHandler PropertyChanged; + + [NotifyPropertyChangedInvocator] + private void OnPropertyChanged([CallerMemberName] string propertyName = null) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } } } diff --git a/UCR.Core/Models/Binding/DeviceBinding.cs b/UCR.Core/Models/Binding/DeviceBinding.cs index b3cbe9fd..d703554e 100644 --- a/UCR.Core/Models/Binding/DeviceBinding.cs +++ b/UCR.Core/Models/Binding/DeviceBinding.cs @@ -18,7 +18,16 @@ public enum DeviceBindingCategory public class DeviceBinding : INotifyPropertyChanged { /* Persistence */ - public bool IsBound { get; set; } + private bool _isBound; + public bool IsBound + { + get => _isBound; + set + { + _isBound = value; + OnPropertyChanged(); + } + } // Index in its device list public Guid DeviceGuid { get; set; } // Subscription key @@ -36,6 +45,18 @@ public class DeviceBinding : INotifyPropertyChanged [XmlIgnore] public DeviceBindingCategory DeviceBindingCategory { get; set; } + private bool _isInBindMode = false; + [XmlIgnore] + public bool IsInBindMode + { + get => _isInBindMode; + private set + { + _isInBindMode = value; + OnPropertyChanged(); + } + } + public delegate void ValueChanged(long value); @@ -135,6 +156,20 @@ public void WriteOutput(long value) OutputSink?.Invoke(value); } + public void EnterBindMode() + { + Profile.Context.BindingManager.BeginBindMode(this); + Profile.Context.BindingManager.EndBindModeHandler += OnEndBindModeHandler; + IsInBindMode = true; + } + + private void OnEndBindModeHandler(DeviceBinding deviceBinding) + { + if (deviceBinding.Guid != Guid) return; + IsInBindMode = false; + Profile.Context.BindingManager.EndBindModeHandler -= OnEndBindModeHandler; + } + private void InputChanged(long value) { CurrentValue = value; diff --git a/UCR.Core/Models/Device.cs b/UCR.Core/Models/Device.cs index 293841ed..0ebf5041 100644 --- a/UCR.Core/Models/Device.cs +++ b/UCR.Core/Models/Device.cs @@ -107,7 +107,7 @@ private static List GetDeviceBindingMenu(List deviceBindingNodes) diff --git a/UCR.Core/UCR.Core.csproj b/UCR.Core/UCR.Core.csproj index 8cf3c767..f56b5ba6 100644 --- a/UCR.Core/UCR.Core.csproj +++ b/UCR.Core/UCR.Core.csproj @@ -60,6 +60,7 @@ ..\packages\Trinet.Core.IO.Ntfs.4.1.1\lib\net35\Trinet.Core.IO.Ntfs.dll True + @@ -67,6 +68,7 @@ + diff --git a/UCR/Utilities/ResourceLoader.cs b/UCR/Utilities/ResourceLoader.cs index 2dadccb9..018a42ac 100644 --- a/UCR/Utilities/ResourceLoader.cs +++ b/UCR/Utilities/ResourceLoader.cs @@ -22,7 +22,7 @@ public void Load() var folderName = path.Remove(0, path.LastIndexOf(Path.DirectorySeparatorChar) + 1); if (File.Exists(Path.Combine(path, folderName + ".dll"))) { - catalog.Catalogs.Add(new DirectoryCatalog(path)); + catalog.Catalogs.Add(new DirectoryCatalog(path, folderName + ".dll")); } } diff --git a/UCR/ViewModels/ProfileViewModels/DeviceBindingViewModel.cs b/UCR/ViewModels/ProfileViewModels/DeviceBindingViewModel.cs index 018e06ea..2db0aa92 100644 --- a/UCR/ViewModels/ProfileViewModels/DeviceBindingViewModel.cs +++ b/UCR/ViewModels/ProfileViewModels/DeviceBindingViewModel.cs @@ -1,7 +1,11 @@ using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; using System.ComponentModel; using System.Runtime.CompilerServices; +using System.Windows; using HidWizards.UCR.Core.Annotations; +using HidWizards.UCR.Core.Managers; using HidWizards.UCR.Core.Models.Binding; namespace HidWizards.UCR.ViewModels.ProfileViewModels @@ -10,6 +14,31 @@ public class DeviceBindingViewModel : INotifyPropertyChanged { public string DeviceBindingName { get; set; } public DeviceBindingCategory DeviceBindingCategory { get; set; } + public ObservableCollection Devices { get; set; } + public ComboBoxItemViewModel SelectedDevice { get; set; } + public Visibility ShowPreview => DeviceBinding.IsInBindMode ? Visibility.Hidden : Visibility.Visible; + public Visibility ShowBindMode => ShowPreview.Equals(Visibility.Visible) ? Visibility.Hidden : Visibility.Visible; + + private bool _bindingEnabled; + public bool BindingEnabled + { + get => _bindingEnabled; + set + { + _bindingEnabled = value; + OnPropertyChanged(); + } + } + + public string BindButtonText + { + get + { + if (DeviceBinding.IsInBindMode) return "Press input device"; + if (DeviceBinding.IsBound) return DeviceBinding.BoundName(); + return "Click to bind"; + } + } private DeviceBinding _deviceBinding; public DeviceBinding DeviceBinding @@ -34,10 +63,95 @@ public long CurrentValue } } + private long _bindModeProgress; + public long BindModeProgress + { + get => _bindModeProgress; + set + { + _bindModeProgress = value; + OnPropertyChanged(); + } + } + + public DeviceBindingViewModel(DeviceBinding deviceBinding) + { + DeviceBinding = deviceBinding; + deviceBinding.Profile.Context.BindingManager.PropertyChanged += BindingManagerOnPropertyChanged; + deviceBinding.Profile.Context.SubscriptionsManager.PropertyChanged += SubscriptionsManagerOnPropertyChanged; + BindingEnabled = !DeviceBinding.Profile.Context.SubscriptionsManager.ProfileActive; + LoadDeviceInputs(); + } + + public void LoadDeviceInputs() + { + var devicelist = DeviceBinding.Profile.GetDeviceList(DeviceBinding); + Devices = new ObservableCollection(); + foreach (var device in devicelist) + { + Devices.Add(new ComboBoxItemViewModel(device.Title, device.Guid)); + } + + SetSelectDevice(); + } + + private void SetSelectDevice() + { + ComboBoxItemViewModel selectedDevice = null; + + foreach (var comboBoxItem in Devices) + { + if (comboBoxItem.Value == DeviceBinding.DeviceGuid) + { + selectedDevice = comboBoxItem; + break; + } + } + + if (Devices.Count == 0) Devices.Add(new ComboBoxItemViewModel("No devices", Guid.Empty)); + if (selectedDevice == null) + { + selectedDevice = Devices[0]; + DeviceBinding.SetDeviceGuid(selectedDevice.Value); + } + + SelectedDevice = selectedDevice; + } + private void DeviceBindingOnPropertyChanged(object sender, PropertyChangedEventArgs propertyChangedEventArgs) { var deviceBinding = (DeviceBinding) sender; + if (!deviceBinding.Guid.Equals(DeviceBinding.Guid)) return; + CurrentValue = deviceBinding.CurrentValue; + + if (propertyChangedEventArgs.PropertyName.Equals("IsBound") + || propertyChangedEventArgs.PropertyName.Equals("IsInBindMode")) + { + OnPropertyChanged(nameof(BindButtonText)); + OnPropertyChanged(nameof(ShowPreview)); + OnPropertyChanged(nameof(ShowBindMode)); + } + + if (propertyChangedEventArgs.PropertyName.Equals("IsBound")) + { + SetSelectDevice(); + OnPropertyChanged(nameof(SelectedDevice)); + } + } + + private void BindingManagerOnPropertyChanged(object sender, PropertyChangedEventArgs e) + { + var bindingManger = sender as BindingManager; + BindModeProgress = (long)bindingManger.BindModeProgress; + } + + private void SubscriptionsManagerOnPropertyChanged(object sender, PropertyChangedEventArgs propertyChangedEventArgs) + { + if (propertyChangedEventArgs.PropertyName.Equals("ProfileActive")) + { + BindingEnabled = !DeviceBinding.Profile.Context.SubscriptionsManager.ProfileActive; + } } public event PropertyChangedEventHandler PropertyChanged; diff --git a/UCR/ViewModels/ProfileViewModels/MappingViewModel.cs b/UCR/ViewModels/ProfileViewModels/MappingViewModel.cs index 1950f4d3..16756cf8 100644 --- a/UCR/ViewModels/ProfileViewModels/MappingViewModel.cs +++ b/UCR/ViewModels/ProfileViewModels/MappingViewModel.cs @@ -42,9 +42,8 @@ private void PopulateDeviceBindingsViewModels() var plugin = Mapping.Plugins[0]; for (var i = 0; i < plugin.InputCategories.Count; i++) { - DeviceBindings.Add(new DeviceBindingViewModel() + DeviceBindings.Add(new DeviceBindingViewModel(Mapping.DeviceBindings[i]) { - DeviceBinding = Mapping.DeviceBindings[i], DeviceBindingName = plugin.InputCategories[i].Name, DeviceBindingCategory = plugin.InputCategories[i].Category }); diff --git a/UCR/ViewModels/ProfileViewModels/PluginViewModel.cs b/UCR/ViewModels/ProfileViewModels/PluginViewModel.cs index e08f7e4c..75597810 100644 --- a/UCR/ViewModels/ProfileViewModels/PluginViewModel.cs +++ b/UCR/ViewModels/ProfileViewModels/PluginViewModel.cs @@ -27,9 +27,8 @@ private void PopulateDeviceBindingsViewModels() DeviceBindings = new ObservableCollection(); for (var i = 0; i < Plugin.OutputCategories.Count; i++) { - DeviceBindings.Add(new DeviceBindingViewModel() + DeviceBindings.Add(new DeviceBindingViewModel(Plugin.Outputs[i]) { - DeviceBinding = Plugin.Outputs[i], DeviceBindingName = Plugin.OutputCategories[i].Name, DeviceBindingCategory = Plugin.OutputCategories[i].Category }); diff --git a/UCR/Views/Controls/DeviceBindingControl.xaml b/UCR/Views/Controls/DeviceBindingControl.xaml index 464e7929..40bf3688 100644 --- a/UCR/Views/Controls/DeviceBindingControl.xaml +++ b/UCR/Views/Controls/DeviceBindingControl.xaml @@ -4,6 +4,7 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:local="clr-namespace:HidWizards.UCR.Views.Controls" + d:DesignHeight="200" d:DesignWidth="400" mc:Ignorable="d"> @@ -13,22 +14,39 @@ - - - + + + + + + + +