refactored the code, prepared for adding new players

This commit is contained in:
Vitalii Ganzha 2015-12-28 19:52:44 -08:00
parent 638bf96bea
commit 1c2e5170c5
6 changed files with 139 additions and 70 deletions

View 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
}
}

View File

@ -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

View File

@ -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">

View File

@ -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();
}
}
}

View 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;
}
}
}

View File

@ -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