From 1c2e5170c5a1ded8d619cefc62e14f7becf0a317 Mon Sep 17 00:00:00 2001 From: Vitalii Ganzha Date: Mon, 28 Dec 2015 19:52:44 -0800 Subject: [PATCH] refactored the code, prepared for adding new players --- VsDingExtensionProject/Players.cs | 74 +++++++++++++++ VsDingExtensionProject/ReleaseNotes.txt | 10 ++- .../VsDingExtensionProject.csproj | 2 + .../VsDingExtensionProjectPackage.cs | 89 +++++-------------- VsDingExtensionProject/WinApiHelper.cs | 32 +++++++ .../source.extension.vsixmanifest | 2 +- 6 files changed, 139 insertions(+), 70 deletions(-) create mode 100644 VsDingExtensionProject/Players.cs create mode 100644 VsDingExtensionProject/WinApiHelper.cs diff --git a/VsDingExtensionProject/Players.cs b/VsDingExtensionProject/Players.cs new file mode 100644 index 0000000..083a65e --- /dev/null +++ b/VsDingExtensionProject/Players.cs @@ -0,0 +1,74 @@ +using Microsoft.VisualStudio.Shell; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Media; +using System.Text; +using System.Threading.Tasks; + +namespace VitaliiGanzha.VsDingExtension +{ + public class Players : IDisposable + { + private readonly Dictionary> eventTypeToSoundPlayerMapping; + + public Players() + { + eventTypeToSoundPlayerMapping = new Dictionary>(); + eventTypeToSoundPlayerMapping[EventType.BuildCompleted] = new List() { new SoundPlayer(Resources.build) }; + eventTypeToSoundPlayerMapping[EventType.BreakpointHit] = new List() { new SoundPlayer(Resources.debug) }; + eventTypeToSoundPlayerMapping[EventType.TestsCompletedSuccess] = new List() { new SoundPlayer(Resources.ding) }; + eventTypeToSoundPlayerMapping[EventType.TestsCompletedFailure] = new List() { new SoundPlayer(Resources.ding) }; // TODO: different sound for failed tests + } + + public void PlaySoundSafe(EventType eventType) + { + foreach (var soundPlayer in this.eventTypeToSoundPlayerMapping[eventType]) + { + try + { + soundPlayer.Play(); + + // When sound played, break. + // Attempt to play using another player only in case of failure (file gets deleted, etc.) + break; + } + catch (Exception ex) + { + ActivityLog.LogError(GetType().FullName, ex.Message); + } + } + } + + private void SafeDispose(SoundPlayer soundPlayer) + { + try + { + soundPlayer.Dispose(); + } + catch (Exception ex) + { + ActivityLog.LogError(this.GetType().FullName, "Error when disposing player: " + ex.Message); + } + } + + public void Dispose() + { + foreach (var players in this.eventTypeToSoundPlayerMapping.Values) + { + foreach (var player in players) + { + this.SafeDispose(player); + } + } + } + } + + public enum EventType + { + BuildCompleted, + BreakpointHit, + TestsCompletedSuccess, + TestsCompletedFailure + } +} diff --git a/VsDingExtensionProject/ReleaseNotes.txt b/VsDingExtensionProject/ReleaseNotes.txt index dc32b66..3557a86 100644 --- a/VsDingExtensionProject/ReleaseNotes.txt +++ b/VsDingExtensionProject/ReleaseNotes.txt @@ -1,4 +1,12 @@ -Version 1.5: +Version 1.8: +* Custom sound for ding https://github.com/thecoderok/vsdingextension/issues/5 +* Use different sound when tests failed https://github.com/thecoderok/vsdingextension/issues/7 +* Add option to only notify on failed tests (thanks to https://github.com/sboulema for contribution!) + +Version 1.6: +* Fixed compatibility issues with Visual Studio 2012 + +Version 1.5: * Added Task bar notifications. (https://github.com/thecoderok/vsdingextension/issues/1) It can be disabled in Tools->Options->Ding->Show tray notifications diff --git a/VsDingExtensionProject/VsDingExtensionProject.csproj b/VsDingExtensionProject/VsDingExtensionProject.csproj index 7517e8d..c4eb3c7 100644 --- a/VsDingExtensionProject/VsDingExtensionProject.csproj +++ b/VsDingExtensionProject/VsDingExtensionProject.csproj @@ -159,6 +159,7 @@ Component + True True @@ -167,6 +168,7 @@ + diff --git a/VsDingExtensionProject/VsDingExtensionProjectPackage.cs b/VsDingExtensionProject/VsDingExtensionProjectPackage.cs index c643a60..2daf3e7 100644 --- a/VsDingExtensionProject/VsDingExtensionProjectPackage.cs +++ b/VsDingExtensionProject/VsDingExtensionProjectPackage.cs @@ -27,19 +27,11 @@ public sealed class VsDingExtensionProjectPackage : Package, IDisposable { private DTE2 applicationObject; - private AddIn addInInstance; private BuildEvents buildEvents; private DebuggerEvents debugEvents; - private SoundPlayer buildCompleteSoundPlayer; - private SoundPlayer debugSoundPlayer; - private SoundPlayer testCompleteSoundPlayer; private OptionsDialog _options = null; + private Players players = new Players(); - [DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)] - private static extern IntPtr GetForegroundWindow(); - - [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] - private static extern int GetWindowThreadProcessId(IntPtr handle, out int processId); public VsDingExtensionProjectPackage() { Debug.WriteLine(string.Format(CultureInfo.CurrentCulture, "Entering constructor for: {0}", ToString())); @@ -52,27 +44,28 @@ Debug.WriteLine(string.Format(CultureInfo.CurrentCulture, "Entering Initialize() of: {0}", ToString())); base.Initialize(); - buildCompleteSoundPlayer = new SoundPlayer(Resources.build); - debugSoundPlayer = new SoundPlayer(Resources.debug); - testCompleteSoundPlayer = new SoundPlayer(Resources.ding); - applicationObject = (DTE2)GetService(typeof(DTE)); buildEvents = applicationObject.Events.BuildEvents; debugEvents = applicationObject.Events.DebuggerEvents; + SetupEventHandlers(); + } + + private void SetupEventHandlers() + { buildEvents.OnBuildDone += (scope, action) => { if (Options.IsBeepOnBuildComplete) { - HandleEventSafe(buildCompleteSoundPlayer, "Build has been completed."); + HandleEventSafe(EventType.BuildCompleted, "Build has been completed."); } }; - debugEvents.OnEnterBreakMode += delegate (dbgEventReason reason, ref dbgExecutionAction action) + debugEvents.OnEnterBreakMode += delegate(dbgEventReason reason, ref dbgExecutionAction action) { if (reason != dbgEventReason.dbgEventReasonStep && Options.IsBeepOnBreakpointHit) { - HandleEventSafe(debugSoundPlayer, "Breakpoint was hit."); + HandleEventSafe(EventType.BreakpointHit, "Breakpoint was hit."); } }; @@ -101,19 +94,19 @@ } } - private void HandleEventSafe(SoundPlayer soundPlayer, string messageText) + private void HandleEventSafe(EventType eventType, string messageText) { - HandleEventSafe(soundPlayer, messageText, ToolTipIcon.Info); + HandleEventSafe(eventType, messageText, ToolTipIcon.Info); } - private void HandleEventSafe(SoundPlayer soundPlayer, string messageText, ToolTipIcon icon) + private void HandleEventSafe(EventType eventType, string messageText, ToolTipIcon icon) { if (!ShouldPerformNotificationAction()) { return; } - PlaySoundSafe(soundPlayer); + players.PlaySoundSafe(eventType); ShowNotifyMessage(messageText, icon); } @@ -154,77 +147,37 @@ }); } - private void PlaySoundSafe(SoundPlayer soundPlayer) - { - try - { - soundPlayer.Play(); - } - catch (Exception ex) - { - ActivityLog.LogError(GetType().FullName, ex.Message); - } - } - private bool ShouldPerformNotificationAction() { if (!Options.IsBeepOnlyWhenVisualStudioIsInBackground) { return true; } - return Options.IsBeepOnlyWhenVisualStudioIsInBackground && !ApplicationIsActivated(); + return Options.IsBeepOnlyWhenVisualStudioIsInBackground && !WinApiHelper.ApplicationIsActivated(); } private void OperationStateOnStateChanged(object sender, OperationStateChangedEventArgs operationStateChangedEventArgs) { if (Options.IsBeepOnTestComplete && operationStateChangedEventArgs.State.HasFlag(TestOperationStates.TestExecutionFinished)) { - if (Options.IsBeepOnTestFailed) + var testOperation = ((TestRunRequest)operationStateChangedEventArgs.Operation); + var isTestsFailed = testOperation.DominantTestState == TestState.Failed; + var eventType = isTestsFailed? EventType.TestsCompletedFailure : EventType.TestsCompletedSuccess; + if (Options.IsBeepOnTestFailed && isTestsFailed) { - var testOperation = ((TestRunRequest)operationStateChangedEventArgs.Operation); - if (testOperation.DominantTestState == TestState.Failed) - { - HandleEventSafe(testCompleteSoundPlayer, "Test execution failed!", ToolTipIcon.Error); - } + HandleEventSafe(eventType, "Test execution failed!", ToolTipIcon.Error); } else { - HandleEventSafe(testCompleteSoundPlayer, "Test execution has been completed."); + HandleEventSafe(eventType, "Test execution has been completed."); } } } #endregion - private bool ApplicationIsActivated() - { - var activatedHandle = GetForegroundWindow(); - if (activatedHandle == IntPtr.Zero) - { - return false; // No window is currently activated - } - var procId = Process.GetCurrentProcess().Id; - int activeProcId; - GetWindowThreadProcessId(activatedHandle, out activeProcId); - return activeProcId == procId; - } - public void Dispose() { - SafeDispose(this.debugSoundPlayer); - SafeDispose(this.buildCompleteSoundPlayer); - SafeDispose(this.testCompleteSoundPlayer); - } - - private void SafeDispose(SoundPlayer soundPlayer) - { - try - { - soundPlayer.Dispose(); - } - catch (Exception ex) - { - ActivityLog.LogError(this.GetType().FullName, "Error when disposing player: " + ex.Message); - } + players.Dispose(); } } } diff --git a/VsDingExtensionProject/WinApiHelper.cs b/VsDingExtensionProject/WinApiHelper.cs new file mode 100644 index 0000000..dd96f75 --- /dev/null +++ b/VsDingExtensionProject/WinApiHelper.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; + +namespace VitaliiGanzha.VsDingExtension +{ + public static class WinApiHelper + { + [DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)] + private static extern IntPtr GetForegroundWindow(); + + [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] + private static extern int GetWindowThreadProcessId(IntPtr handle, out int processId); + + public static bool ApplicationIsActivated() + { + var activatedHandle = GetForegroundWindow(); + if (activatedHandle == IntPtr.Zero) + { + return false; // No window is currently activated + } + var procId = Process.GetCurrentProcess().Id; + int activeProcId; + GetWindowThreadProcessId(activatedHandle, out activeProcId); + return activeProcId == procId; + } + } +} diff --git a/VsDingExtensionProject/source.extension.vsixmanifest b/VsDingExtensionProject/source.extension.vsixmanifest index b6868af..5c1538c 100644 --- a/VsDingExtensionProject/source.extension.vsixmanifest +++ b/VsDingExtensionProject/source.extension.vsixmanifest @@ -1,7 +1,7 @@  - + Visual Studio Ding extension This small extension will play notification sounds when following events occur: - Build Complete