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) * Added Task bar notifications. (https://github.com/thecoderok/vsdingextension/issues/1)
It can be disabled in Tools->Options->Ding->Show tray notifications It can be disabled in Tools->Options->Ding->Show tray notifications

View File

@ -159,6 +159,7 @@
<Compile Include="OptionsDialog.cs"> <Compile Include="OptionsDialog.cs">
<SubType>Component</SubType> <SubType>Component</SubType>
</Compile> </Compile>
<Compile Include="Players.cs" />
<Compile Include="Resources.Designer.cs"> <Compile Include="Resources.Designer.cs">
<AutoGen>True</AutoGen> <AutoGen>True</AutoGen>
<DesignTime>True</DesignTime> <DesignTime>True</DesignTime>
@ -167,6 +168,7 @@
<Compile Include="GlobalSuppressions.cs" /> <Compile Include="GlobalSuppressions.cs" />
<Compile Include="VsDingExtensionProjectPackage.cs" /> <Compile Include="VsDingExtensionProjectPackage.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="WinApiHelper.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<EmbeddedResource Include="Resources.resx"> <EmbeddedResource Include="Resources.resx">

View File

@ -27,19 +27,11 @@
public sealed class VsDingExtensionProjectPackage : Package, IDisposable public sealed class VsDingExtensionProjectPackage : Package, IDisposable
{ {
private DTE2 applicationObject; private DTE2 applicationObject;
private AddIn addInInstance;
private BuildEvents buildEvents; private BuildEvents buildEvents;
private DebuggerEvents debugEvents; private DebuggerEvents debugEvents;
private SoundPlayer buildCompleteSoundPlayer;
private SoundPlayer debugSoundPlayer;
private SoundPlayer testCompleteSoundPlayer;
private OptionsDialog _options = null; 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() public VsDingExtensionProjectPackage()
{ {
Debug.WriteLine(string.Format(CultureInfo.CurrentCulture, "Entering constructor for: {0}", ToString())); 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())); Debug.WriteLine(string.Format(CultureInfo.CurrentCulture, "Entering Initialize() of: {0}", ToString()));
base.Initialize(); base.Initialize();
buildCompleteSoundPlayer = new SoundPlayer(Resources.build);
debugSoundPlayer = new SoundPlayer(Resources.debug);
testCompleteSoundPlayer = new SoundPlayer(Resources.ding);
applicationObject = (DTE2)GetService(typeof(DTE)); applicationObject = (DTE2)GetService(typeof(DTE));
buildEvents = applicationObject.Events.BuildEvents; buildEvents = applicationObject.Events.BuildEvents;
debugEvents = applicationObject.Events.DebuggerEvents; debugEvents = applicationObject.Events.DebuggerEvents;
SetupEventHandlers();
}
private void SetupEventHandlers()
{
buildEvents.OnBuildDone += (scope, action) => buildEvents.OnBuildDone += (scope, action) =>
{ {
if (Options.IsBeepOnBuildComplete) 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) 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()) if (!ShouldPerformNotificationAction())
{ {
return; return;
} }
PlaySoundSafe(soundPlayer); players.PlaySoundSafe(eventType);
ShowNotifyMessage(messageText, icon); 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() private bool ShouldPerformNotificationAction()
{ {
if (!Options.IsBeepOnlyWhenVisualStudioIsInBackground) if (!Options.IsBeepOnlyWhenVisualStudioIsInBackground)
{ {
return true; return true;
} }
return Options.IsBeepOnlyWhenVisualStudioIsInBackground && !ApplicationIsActivated(); return Options.IsBeepOnlyWhenVisualStudioIsInBackground && !WinApiHelper.ApplicationIsActivated();
} }
private void OperationStateOnStateChanged(object sender, OperationStateChangedEventArgs operationStateChangedEventArgs) private void OperationStateOnStateChanged(object sender, OperationStateChangedEventArgs operationStateChangedEventArgs)
{ {
if (Options.IsBeepOnTestComplete && operationStateChangedEventArgs.State.HasFlag(TestOperationStates.TestExecutionFinished)) 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); HandleEventSafe(eventType, "Test execution failed!", ToolTipIcon.Error);
if (testOperation.DominantTestState == TestState.Failed)
{
HandleEventSafe(testCompleteSoundPlayer, "Test execution failed!", ToolTipIcon.Error);
}
} }
else else
{ {
HandleEventSafe(testCompleteSoundPlayer, "Test execution has been completed."); HandleEventSafe(eventType, "Test execution has been completed.");
} }
} }
} }
#endregion #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() public void Dispose()
{ {
SafeDispose(this.debugSoundPlayer); players.Dispose();
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);
}
} }
} }
} }

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"?> <?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"> <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> <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> <DisplayName>Visual Studio Ding extension</DisplayName>
<Description xml:space="preserve">This small extension will play notification sounds when following events occur: <Description xml:space="preserve">This small extension will play notification sounds when following events occur:
- Build Complete - Build Complete