From b546d98db0ebaeba75d6fb935b1181d86ccd3178 Mon Sep 17 00:00:00 2001 From: SpoinkyNL Date: Mon, 13 Jun 2016 20:17:31 +0200 Subject: [PATCH] All device enable methods are now async. This fixes #104 --- .../Corsair/CorsairHeadsets.cs | 20 ++-- .../DeviceProviders/Corsair/CorsairMice.cs | 19 ++-- .../DeviceProviders/Corsair/CorsairRGB.cs | 34 ++++++- .../Artemis/DeviceProviders/DeviceProvider.cs | 12 ++- .../DeviceProviders/KeyboardProvider.cs | 16 ++- .../InjectionModules/ArtemisModules.cs | 2 +- Artemis/Artemis/Managers/DeviceManager.cs | 97 +++++++++---------- Artemis/Artemis/Managers/EffectManager.cs | 2 +- Artemis/Artemis/Managers/LoopManager.cs | 9 +- Artemis/Artemis/Managers/MainManager.cs | 3 +- Artemis/Artemis/Managers/ProfileManager.cs | 2 +- .../VolumeDisplay/VolumeDisplayModel.cs | 3 + .../Artemis/Services/MetroDialogService.cs | 11 ++- .../Flyouts/FlyoutSettingsViewModel.cs | 2 +- 14 files changed, 153 insertions(+), 79 deletions(-) diff --git a/Artemis/Artemis/DeviceProviders/Corsair/CorsairHeadsets.cs b/Artemis/Artemis/DeviceProviders/Corsair/CorsairHeadsets.cs index 8090d1dcb..fdedd3c49 100644 --- a/Artemis/Artemis/DeviceProviders/Corsair/CorsairHeadsets.cs +++ b/Artemis/Artemis/DeviceProviders/Corsair/CorsairHeadsets.cs @@ -46,18 +46,20 @@ namespace Artemis.DeviceProviders.Corsair var visual = new DrawingVisual(); using (var c = visual.RenderOpen()) c.DrawRectangle(brush, null, rect); - var img = ImageUtilities.DrawinVisualToBitmap(visual, rect); - var ledIndex = 0; - // Color each LED according to one of the pixels - foreach (var corsairLed in CueSDK.HeadsetSDK.Leds) + using (var img = ImageUtilities.DrawinVisualToBitmap(visual, rect)) { - corsairLed.Color = ledIndex == 0 - ? img.GetPixel(0, 0) - : img.GetPixel((ledIndex + 1)*20 - 1, (ledIndex + 1)*20 - 1); - ledIndex++; - } + var ledIndex = 0; + // Color each LED according to one of the pixels + foreach (var corsairLed in CueSDK.HeadsetSDK.Leds) + { + corsairLed.Color = ledIndex == 0 + ? img.GetPixel(0, 0) + : img.GetPixel((ledIndex + 1)*20 - 1, (ledIndex + 1)*20 - 1); + ledIndex++; + } + } // Flush is required for headset to work reliably on CUE2 for some reason CueSDK.HeadsetSDK.Update(true); } diff --git a/Artemis/Artemis/DeviceProviders/Corsair/CorsairMice.cs b/Artemis/Artemis/DeviceProviders/Corsair/CorsairMice.cs index 9d4763851..3e08f941c 100644 --- a/Artemis/Artemis/DeviceProviders/Corsair/CorsairMice.cs +++ b/Artemis/Artemis/DeviceProviders/Corsair/CorsairMice.cs @@ -46,18 +46,19 @@ namespace Artemis.DeviceProviders.Corsair var visual = new DrawingVisual(); using (var c = visual.RenderOpen()) c.DrawRectangle(brush, null, rect); - var img = ImageUtilities.DrawinVisualToBitmap(visual, rect); - var ledIndex = 0; - // Color each LED according to one of the pixels - foreach (var corsairLed in CueSDK.MouseSDK.Leds) + using (var img = ImageUtilities.DrawinVisualToBitmap(visual, rect)) { - corsairLed.Color = ledIndex == 0 - ? img.GetPixel(0, 0) - : img.GetPixel((ledIndex + 1)*20 - 1, (ledIndex + 1)*20 - 1); - ledIndex++; + var ledIndex = 0; + // Color each LED according to one of the pixels + foreach (var corsairLed in CueSDK.MouseSDK.Leds) + { + corsairLed.Color = ledIndex == 0 + ? img.GetPixel(0, 0) + : img.GetPixel((ledIndex + 1)*20 - 1, (ledIndex + 1)*20 - 1); + ledIndex++; + } } - CueSDK.MouseSDK.Update(); } diff --git a/Artemis/Artemis/DeviceProviders/Corsair/CorsairRGB.cs b/Artemis/Artemis/DeviceProviders/Corsair/CorsairRGB.cs index ddc637a53..ba375a6d7 100644 --- a/Artemis/Artemis/DeviceProviders/Corsair/CorsairRGB.cs +++ b/Artemis/Artemis/DeviceProviders/Corsair/CorsairRGB.cs @@ -1,5 +1,6 @@ using System.Drawing; using System.Threading; +using System.Threading.Tasks; using System.Windows; using Artemis.Properties; using Artemis.Utilities; @@ -7,6 +8,7 @@ using CUE.NET; using CUE.NET.Brushes; using CUE.NET.Devices.Generic.Enums; using CUE.NET.Devices.Keyboard; +using MahApps.Metro.Controls.Dialogs; using Ninject.Extensions.Logging; using Point = System.Drawing.Point; @@ -14,7 +16,6 @@ namespace Artemis.DeviceProviders.Corsair { public class CorsairRGB : KeyboardProvider { - public ILogger Logger { get; set; } private CorsairKeyboard _keyboard; private ImageBrush _keyboardBrush; @@ -28,6 +29,37 @@ namespace Artemis.DeviceProviders.Corsair "If needed, you can select a different keyboard in Artemis under settings."; } + public ILogger Logger { get; set; } + + public sealed override Task CanEnableAsync(ProgressDialogController dialog) + { + return Task.Run(() => + { + // This will skip the check-loop if the SDK is initialized + if (CueSDK.IsInitialized) + return CueSDK.IsSDKAvailable(CorsairDeviceType.Keyboard); + for (var tries = 0; tries < 9; tries++) + { + // Stop trying if cancelled by user + if (dialog != null && dialog.IsCanceled) + { + dialog.SetIndeterminate(); + return false; + } + dialog?.SetProgress(0.1*(tries + 1)); + + if (CueSDK.IsSDKAvailable(CorsairDeviceType.Keyboard)) + { + dialog?.SetIndeterminate(); + return true; + } + Thread.Sleep(2000); + } + dialog?.SetIndeterminate(); + return false; + }); + } + public override bool CanEnable() { // This will skip the check-loop if the SDK is initialized diff --git a/Artemis/Artemis/DeviceProviders/DeviceProvider.cs b/Artemis/Artemis/DeviceProviders/DeviceProvider.cs index f86ee2e64..c244eb73a 100644 --- a/Artemis/Artemis/DeviceProviders/DeviceProvider.cs +++ b/Artemis/Artemis/DeviceProviders/DeviceProvider.cs @@ -1,4 +1,5 @@ -using System.Windows.Media; +using System.Threading.Tasks; +using System.Windows.Media; namespace Artemis.DeviceProviders { @@ -29,6 +30,15 @@ namespace Artemis.DeviceProviders /// Disables the device /// public abstract void Disable(); + + /// + /// Tries to enable the device and updates CanUse accordingly asynchronously + /// + /// + public Task TryEnableAsync() + { + return Task.Run(() => TryEnable()); + } } public enum DeviceType diff --git a/Artemis/Artemis/DeviceProviders/KeyboardProvider.cs b/Artemis/Artemis/DeviceProviders/KeyboardProvider.cs index 54a157b60..7498c6279 100644 --- a/Artemis/Artemis/DeviceProviders/KeyboardProvider.cs +++ b/Artemis/Artemis/DeviceProviders/KeyboardProvider.cs @@ -1,6 +1,8 @@ using System; using System.Drawing; +using System.Threading.Tasks; using System.Windows; +using MahApps.Metro.Controls.Dialogs; using Brush = System.Windows.Media.Brush; using Size = System.Windows.Size; @@ -18,7 +20,6 @@ namespace Artemis.DeviceProviders public int Height { get; set; } public int Width { get; set; } public string CantEnableText { get; set; } - public PreviewSettings PreviewSettings { get; set; } public abstract bool CanEnable(); @@ -39,6 +40,16 @@ namespace Artemis.DeviceProviders public Rect KeyboardRectangle(int scale) => new Rect(new Size(Width*scale, Height*scale)); + public virtual Task CanEnableAsync(ProgressDialogController dialog) + { + return Task.Run(() => CanEnable()); + } + + public virtual Task EnableAsync(ProgressDialogController dialog) + { + return Task.Run(() => Enable()); + } + public override void UpdateDevice(Brush brush) { throw new NotImplementedException("KeyboardProvider doesn't implement UpdateDevice, use DrawBitmap instead."); @@ -46,7 +57,8 @@ namespace Artemis.DeviceProviders public override bool TryEnable() { - throw new NotImplementedException("KeyboardProvider doesn't implement TryEnable, use CanEnable instead."); + throw new NotImplementedException( + "KeyboardProvider doesn't implement TryEnable, use CanEnableAsync instead."); } } diff --git a/Artemis/Artemis/InjectionModules/ArtemisModules.cs b/Artemis/Artemis/InjectionModules/ArtemisModules.cs index 531797855..2ec4602f1 100644 --- a/Artemis/Artemis/InjectionModules/ArtemisModules.cs +++ b/Artemis/Artemis/InjectionModules/ArtemisModules.cs @@ -27,7 +27,7 @@ namespace Artemis.InjectionModules // Effects Bind().To().InSingletonScope(); Bind().To().InSingletonScope(); - //Bind().To().InSingletonScope(); TODO: Performance + Bind().To().InSingletonScope(); Bind().To().InSingletonScope(); // Games diff --git a/Artemis/Artemis/Managers/DeviceManager.cs b/Artemis/Artemis/Managers/DeviceManager.cs index 64c922764..a805481a9 100644 --- a/Artemis/Artemis/Managers/DeviceManager.cs +++ b/Artemis/Artemis/Managers/DeviceManager.cs @@ -2,11 +2,13 @@ using System.Collections.Generic; using System.Linq; using System.Threading; +using System.Threading.Tasks; using Artemis.DeviceProviders; using Artemis.Events; using Artemis.Services; using Artemis.Settings; using Caliburn.Micro; +using MahApps.Metro.Controls.Dialogs; using Ninject; using Ninject.Extensions.Logging; @@ -65,8 +67,14 @@ namespace Artemis.Managers /// Enables the given keyboard /// /// - public void EnableKeyboard(KeyboardProvider keyboardProvider) + public async void EnableKeyboard(KeyboardProvider keyboardProvider) { + if (ChangingKeyboard) + return; + + // Store the old keyboard so it can be used in the event we're raising later + var oldKeyboard = ActiveKeyboard; + lock (this) { ChangingKeyboard = true; @@ -80,9 +88,6 @@ namespace Artemis.Managers return; } - // Store the old keyboard so it can be used in the event we're raising later - var oldKeyboard = ActiveKeyboard; - var wasNull = false; if (ActiveKeyboard == null) { @@ -90,69 +95,63 @@ namespace Artemis.Managers ActiveKeyboard = keyboardProvider; } - _logger.Debug("Enabling keyboard: {0}", keyboardProvider.Name); - if (!wasNull) ReleaseActiveKeyboard(); - var asynchEnable = false; - if (keyboardProvider.CanEnable()) - { - FinishEnableKeyboard(keyboardProvider, oldKeyboard); - } - else - { - for (var i = 1; i <= 10; i++) - { - var thread = new Thread(() => - { - Thread.Sleep(500*i); - asynchEnable = keyboardProvider.CanEnable(); - }); - - _logger.Warn("Failed enabling keyboard: {0}, re-attempt {1} of 10", keyboardProvider.Name, i); - - thread.Start(); - if (asynchEnable) - break; - } - - if (!asynchEnable) - { - // Disable everything if there's no active keyboard found - DialogService.ShowErrorMessageBox(keyboardProvider.CantEnableText); - ActiveKeyboard = null; - General.Default.LastKeyboard = null; - General.Default.Save(); - _logger.Warn("Failed enabling keyboard: {0}", keyboardProvider.Name); - ChangingKeyboard = false; - return; - } - FinishEnableKeyboard(keyboardProvider, oldKeyboard); - } } - } - private void FinishEnableKeyboard(KeyboardProvider keyboardProvider, KeyboardProvider oldKeyboard) - { + // TODO: LoopManager shouldn't be running at this point + _logger.Debug("Enabling keyboard: {0}", keyboardProvider.Name); + + // Create a dialog to let the user know Artemis hasn't frozen + ProgressDialogController dialog = null; + if (DialogService.GetActiveWindow() != null) + { + dialog = await DialogService.ShowProgressDialog("Enabling keyboard", + $"Checking if keyboard '{keyboardProvider.Name}' can be enabled...", true); + + // May seem a bit cheesy, but it's tidier to have the animation finish + await Task.Delay(500); + } + dialog?.SetIndeterminate(); + + var canEnable = await keyboardProvider.CanEnableAsync(dialog); + if (!canEnable) + { + if (dialog != null) + await dialog.CloseAsync(); + + DialogService.ShowErrorMessageBox(keyboardProvider.CantEnableText); + ActiveKeyboard = null; + General.Default.LastKeyboard = null; + General.Default.Save(); + _logger.Warn("Failed enabling keyboard: {0}", keyboardProvider.Name); + ChangingKeyboard = false; + return; + } + + dialog?.SetMessage($"Enabling keyboard: {keyboardProvider.Name}..."); ActiveKeyboard = keyboardProvider; - ActiveKeyboard.Enable(); + await ActiveKeyboard.EnableAsync(dialog); General.Default.LastKeyboard = ActiveKeyboard.Name; General.Default.Save(); EnableUsableDevices(); - - ChangingKeyboard = false; _events.PublishOnUIThread(new ActiveKeyboardChanged(oldKeyboard, ActiveKeyboard)); _logger.Debug("Enabled keyboard: {0}", keyboardProvider.Name); + + if (dialog != null) + await dialog.CloseAsync(); + + ChangingKeyboard = false; } private void EnableUsableDevices() { foreach (var mouseProvider in MiceProviders) - mouseProvider.TryEnable(); + mouseProvider.TryEnableAsync(); foreach (var headsetProvider in HeadsetProviders) - headsetProvider.TryEnable(); + headsetProvider.TryEnableAsync(); } /// diff --git a/Artemis/Artemis/Managers/EffectManager.cs b/Artemis/Artemis/Managers/EffectManager.cs index a7143e039..0605ac372 100644 --- a/Artemis/Artemis/Managers/EffectManager.cs +++ b/Artemis/Artemis/Managers/EffectManager.cs @@ -134,7 +134,7 @@ namespace Artemis.Managers if (loopManager != null && !loopManager.Running) { _logger.Debug("Starting LoopManager for effect change"); - loopManager.Start(); + loopManager.StartAsync(); } _logger.Debug("Changed active effect to: {0}", effectModel.Name); diff --git a/Artemis/Artemis/Managers/LoopManager.cs b/Artemis/Artemis/Managers/LoopManager.cs index 6cb6d12c3..ef358b3aa 100644 --- a/Artemis/Artemis/Managers/LoopManager.cs +++ b/Artemis/Artemis/Managers/LoopManager.cs @@ -1,6 +1,7 @@ using System; using System.Drawing; using System.Linq; +using System.Threading.Tasks; using System.Timers; using Ninject.Extensions.Logging; using Brush = System.Windows.Media.Brush; @@ -40,7 +41,12 @@ namespace Artemis.Managers _loopTimer.Dispose(); } - public void Start() + public Task StartAsync() + { + return Task.Run(() => Start()); + } + + private void Start() { if (Running) return; @@ -157,6 +163,7 @@ namespace Artemis.Managers // Update the keyboard _deviceManager.ActiveKeyboard?.DrawBitmap(bitmap); + bitmap.Dispose(); } } } diff --git a/Artemis/Artemis/Managers/MainManager.cs b/Artemis/Artemis/Managers/MainManager.cs index c2c8ca63d..4a25ea4af 100644 --- a/Artemis/Artemis/Managers/MainManager.cs +++ b/Artemis/Artemis/Managers/MainManager.cs @@ -46,7 +46,6 @@ namespace Artemis.Managers ProgramEnabled = false; Running = false; - // TODO: Dependency inject utilities? KeyboardHook = new KeyboardHook(); @@ -94,7 +93,7 @@ namespace Artemis.Managers { _logger.Debug("Enabling program"); ProgramEnabled = true; - LoopManager.Start(); + LoopManager.StartAsync(); _events.PublishOnUIThread(new ToggleEnabled(ProgramEnabled)); } diff --git a/Artemis/Artemis/Managers/ProfileManager.cs b/Artemis/Artemis/Managers/ProfileManager.cs index ed30fc18e..637743b75 100644 --- a/Artemis/Artemis/Managers/ProfileManager.cs +++ b/Artemis/Artemis/Managers/ProfileManager.cs @@ -68,7 +68,7 @@ namespace Artemis.Managers } // LoopManager might be running, this method won't do any harm in that case. - _loopManager.Start(); + _loopManager.StartAsync(); if (!ReferenceEquals(ProfilePreviewModel.Profile, activePreview.ProfileEditor.SelectedProfile)) ProfilePreviewModel.Profile = activePreview.ProfileEditor.SelectedProfile; diff --git a/Artemis/Artemis/Modules/Overlays/VolumeDisplay/VolumeDisplayModel.cs b/Artemis/Artemis/Modules/Overlays/VolumeDisplay/VolumeDisplayModel.cs index e6dda0fd2..9bad9ac89 100644 --- a/Artemis/Artemis/Modules/Overlays/VolumeDisplay/VolumeDisplayModel.cs +++ b/Artemis/Artemis/Modules/Overlays/VolumeDisplay/VolumeDisplayModel.cs @@ -94,6 +94,9 @@ namespace Artemis.Modules.Overlays.VolumeDisplay public override void RenderOverlay(ref Bitmap keyboard, ref Brush mouse, ref Brush headset, bool renderMice, bool renderHeadsets) { + if (MainManager.DeviceManager.ActiveKeyboard == null) + return; + keyboard = GenerateBitmap(keyboard ?? MainManager.DeviceManager.ActiveKeyboard.KeyboardBitmap(4)); } } diff --git a/Artemis/Artemis/Services/MetroDialogService.cs b/Artemis/Artemis/Services/MetroDialogService.cs index 10954f6bc..7bdb4ab62 100644 --- a/Artemis/Artemis/Services/MetroDialogService.cs +++ b/Artemis/Artemis/Services/MetroDialogService.cs @@ -32,7 +32,7 @@ namespace Artemis.Services { public class MetroDialogService : DialogService { - private MetroWindow GetActiveWindow() + public MetroWindow GetActiveWindow() { MetroWindow window = null; @@ -128,5 +128,14 @@ namespace Artemis.Services return res.Value; } + + public Task ShowProgressDialog(string title, string message, bool isCancelable = false, + MetroDialogSettings settings = null) + { + return GetActiveWindow() == null + ? null + : GetActiveWindow().Dispatcher.Invoke(() => + GetActiveWindow()?.ShowProgressAsync(title, message, isCancelable, settings)); + } } } \ No newline at end of file diff --git a/Artemis/Artemis/ViewModels/Flyouts/FlyoutSettingsViewModel.cs b/Artemis/Artemis/ViewModels/Flyouts/FlyoutSettingsViewModel.cs index 30178eb03..e4a5f8a85 100644 --- a/Artemis/Artemis/ViewModels/Flyouts/FlyoutSettingsViewModel.cs +++ b/Artemis/Artemis/ViewModels/Flyouts/FlyoutSettingsViewModel.cs @@ -167,7 +167,7 @@ namespace Artemis.ViewModels.Flyouts if (keyboard != null) { MainManager.DeviceManager.EnableKeyboard(keyboard); - MainManager.LoopManager.Start(); + MainManager.LoopManager.StartAsync(); } else MainManager.DeviceManager.ReleaseActiveKeyboard(true);