refactored the code, prepared for adding new players
This commit is contained in:
		
							parent
							
								
									638bf96bea
								
							
						
					
					
						commit
						1c2e5170c5
					
				
					 6 changed files with 139 additions and 70 deletions
				
			
		
							
								
								
									
										74
									
								
								VsDingExtensionProject/Players.cs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										74
									
								
								VsDingExtensionProject/Players.cs
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -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<EventType, IList<SoundPlayer>> eventTypeToSoundPlayerMapping;
 | 
			
		||||
 | 
			
		||||
        public Players()
 | 
			
		||||
        {
 | 
			
		||||
            eventTypeToSoundPlayerMapping = new Dictionary<EventType, IList<SoundPlayer>>();
 | 
			
		||||
            eventTypeToSoundPlayerMapping[EventType.BuildCompleted] = new List<SoundPlayer>() { new SoundPlayer(Resources.build) };
 | 
			
		||||
            eventTypeToSoundPlayerMapping[EventType.BreakpointHit] = new List<SoundPlayer>() { new SoundPlayer(Resources.debug) };
 | 
			
		||||
            eventTypeToSoundPlayerMapping[EventType.TestsCompletedSuccess] = new List<SoundPlayer>() { new SoundPlayer(Resources.ding) };
 | 
			
		||||
            eventTypeToSoundPlayerMapping[EventType.TestsCompletedFailure] = new List<SoundPlayer>() { 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
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -159,6 +159,7 @@
 | 
			
		|||
    <Compile Include="OptionsDialog.cs">
 | 
			
		||||
      <SubType>Component</SubType>
 | 
			
		||||
    </Compile>
 | 
			
		||||
    <Compile Include="Players.cs" />
 | 
			
		||||
    <Compile Include="Resources.Designer.cs">
 | 
			
		||||
      <AutoGen>True</AutoGen>
 | 
			
		||||
      <DesignTime>True</DesignTime>
 | 
			
		||||
| 
						 | 
				
			
			@ -167,6 +168,7 @@
 | 
			
		|||
    <Compile Include="GlobalSuppressions.cs" />
 | 
			
		||||
    <Compile Include="VsDingExtensionProjectPackage.cs" />
 | 
			
		||||
    <Compile Include="Properties\AssemblyInfo.cs" />
 | 
			
		||||
    <Compile Include="WinApiHelper.cs" />
 | 
			
		||||
  </ItemGroup>
 | 
			
		||||
  <ItemGroup>
 | 
			
		||||
    <EmbeddedResource Include="Resources.resx">
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										32
									
								
								VsDingExtensionProject/WinApiHelper.cs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								VsDingExtensionProject/WinApiHelper.cs
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -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;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,7 +1,7 @@
 | 
			
		|||
<?xml version="1.0" encoding="utf-8"?>
 | 
			
		||||
<PackageManifest Version="2.0.0" xmlns="http://schemas.microsoft.com/developer/vsx-schema/2011" xmlns:d="http://schemas.microsoft.com/developer/vsx-schema-design/2011">
 | 
			
		||||
  <Metadata>
 | 
			
		||||
    <Identity Id="26ba08d0-0d25-4479-8684-3054dd122876" Version="1.6" Language="en-US" Publisher="Vitalii Ganzha" />
 | 
			
		||||
    <Identity Id="26ba08d0-0d25-4479-8684-3054dd122876" Version="1.8" Language="en-US" Publisher="Vitalii Ganzha" />
 | 
			
		||||
    <DisplayName>Visual Studio Ding extension</DisplayName>
 | 
			
		||||
    <Description xml:space="preserve">This small extension will play notification sounds when following events occur:
 | 
			
		||||
- Build Complete
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue