diff --git a/Eve-O-Preview/Eve-O-Preview.csproj b/Eve-O-Preview/Eve-O-Preview.csproj
index 1532924..ecad15d 100644
--- a/Eve-O-Preview/Eve-O-Preview.csproj
+++ b/Eve-O-Preview/Eve-O-Preview.csproj
@@ -110,6 +110,10 @@
+
+
+
+
diff --git a/Eve-O-Preview/Presentation/ThumbnailManager.cs b/Eve-O-Preview/Presentation/ThumbnailManager.cs
index 0f8cb9a..ca47111 100644
--- a/Eve-O-Preview/Presentation/ThumbnailManager.cs
+++ b/Eve-O-Preview/Presentation/ThumbnailManager.cs
@@ -1,24 +1,25 @@
using System;
using System.Collections.Generic;
-using System.Diagnostics;
using System.Drawing;
using System.Windows.Threading;
using EveOPreview.Configuration;
+using EveOPreview.Mediator;
+using EveOPreview.Services;
using EveOPreview.WindowManager;
namespace EveOPreview.UI
{
- public class ThumbnailManager : IThumbnailManager
+ class ThumbnailManager : IThumbnailManager
{
#region Private constants
private const int WindowPositionThreshold = -5000;
private const int WindowSizeThreshold = -5000;
- private const string ClientProcessName = "ExeFile";
private const string DefaultClientTitle = "EVE";
#endregion
#region Private fields
+ private readonly IProcessMonitor _processMonitor;
private readonly IWindowManager _windowManager;
private readonly IThumbnailConfiguration _configuration;
private readonly DispatcherTimer _thumbnailUpdateTimer;
@@ -32,8 +33,9 @@ namespace EveOPreview.UI
private bool _isHoverEffectActive;
#endregion
- public ThumbnailManager(IWindowManager windowManager, IThumbnailConfiguration configuration, IThumbnailViewFactory factory)
+ public ThumbnailManager(IMediator mediator, IThumbnailConfiguration configuration, IProcessMonitor processMonitor, IWindowManager windowManager, IThumbnailViewFactory factory)
{
+ this._processMonitor = processMonitor;
this._windowManager = windowManager;
this._configuration = configuration;
this._thumbnailViewFactory = factory;
@@ -189,15 +191,9 @@ namespace EveOPreview.UI
this._ignoreViewEvents = true;
}
- private static Process[] GetClientProcesses()
- {
- return Process.GetProcessesByName(ThumbnailManager.ClientProcessName);
- }
-
private void UpdateThumbnailsList()
{
- Process[] clientProcesses = ThumbnailManager.GetClientProcesses();
- List processHandles = new List(clientProcesses.Length);
+ this._processMonitor.GetUpdatedProcesses(out ICollection addedProcesses, out ICollection updatedProcesses, out ICollection removedProcesses);
IntPtr foregroundWindowHandle = this._windowManager.GetForegroundWindowHandle();
@@ -205,78 +201,74 @@ namespace EveOPreview.UI
List viewsUpdated = new List();
List viewsRemoved = new List();
- foreach (Process process in clientProcesses)
+ foreach (IProcessInfo process in addedProcesses)
{
- IntPtr processHandle = process.MainWindowHandle;
- string processTitle = process.MainWindowTitle;
- processHandles.Add(processHandle);
+ IThumbnailView view = this._thumbnailViewFactory.Create(process.Handle, process.Title, this._configuration.ThumbnailSize);
+ view.IsEnabled = true;
+ view.IsOverlayEnabled = this._configuration.ShowThumbnailOverlays;
+ view.SetFrames(this._configuration.ShowThumbnailFrames);
+ // Max/Min size limitations should be set AFTER the frames are disabled
+ // Otherwise thumbnail window will be unnecessary resized
+ view.SetSizeLimitations(this._configuration.ThumbnailMinimumSize, this._configuration.ThumbnailMaximumSize);
+ view.SetTopMost(this._configuration.ShowThumbnailsAlwaysOnTop);
- IThumbnailView view;
- this._thumbnailViews.TryGetValue(processHandle, out view);
+ view.ThumbnailLocation = this.IsManageableThumbnail(view)
+ ? this._configuration.GetThumbnailLocation(process.Title, this._activeClientTitle, view.ThumbnailLocation)
+ : this._configuration.GetDefaultThumbnailLocation();
- if ((view == null) && (processTitle != ""))
+ this._thumbnailViews.Add(process.Handle, view);
+
+ view.ThumbnailResized = this.ThumbnailViewResized;
+ view.ThumbnailMoved = this.ThumbnailViewMoved;
+ view.ThumbnailFocused = this.ThumbnailViewFocused;
+ view.ThumbnailLostFocus = this.ThumbnailViewLostFocus;
+ view.ThumbnailActivated = this.ThumbnailActivated;
+ view.ThumbnailDeactivated = this.ThumbnailDeactivated;
+
+ view.RegisterHotkey(this._configuration.GetClientHotkey(process.Title));
+
+ this.ApplyClientLayout(process.Handle, process.Title);
+
+ viewsAdded.Add(view);
+
+ if (process.Handle == foregroundWindowHandle)
{
- view = this._thumbnailViewFactory.Create(processHandle, processTitle, this._configuration.ThumbnailSize);
- view.IsEnabled = true;
- view.IsOverlayEnabled = this._configuration.ShowThumbnailOverlays;
- view.SetFrames(this._configuration.ShowThumbnailFrames);
- // Max/Min size limitations should be set AFTER the frames are disabled
- // Otherwise thumbnail window will be unnecessary resized
- view.SetSizeLimitations(this._configuration.ThumbnailMinimumSize, this._configuration.ThumbnailMaximumSize);
- view.SetTopMost(this._configuration.ShowThumbnailsAlwaysOnTop);
-
- view.ThumbnailLocation = this.IsManageableThumbnail(view)
- ? this._configuration.GetThumbnailLocation(processTitle, this._activeClientTitle, view.ThumbnailLocation)
- : this._configuration.GetDefaultThumbnailLocation();
-
- this._thumbnailViews.Add(processHandle, view);
-
- view.ThumbnailResized = this.ThumbnailViewResized;
- view.ThumbnailMoved = this.ThumbnailViewMoved;
- view.ThumbnailFocused = this.ThumbnailViewFocused;
- view.ThumbnailLostFocus = this.ThumbnailViewLostFocus;
- view.ThumbnailActivated = this.ThumbnailActivated;
- view.ThumbnailDeactivated = this.ThumbnailDeactivated;
-
- view.RegisterHotkey(this._configuration.GetClientHotkey(processTitle));
-
- this.ApplyClientLayout(processHandle, processTitle);
-
- viewsAdded.Add(view);
+ this._activeClientHandle = process.Handle;
+ this._activeClientTitle = process.Title;
}
- else if ((view != null) && (processTitle != view.Title)) // update thumbnail title
+ }
+
+ foreach (IProcessInfo process in updatedProcesses)
+ {
+ this._thumbnailViews.TryGetValue(process.Handle, out IThumbnailView view);
+
+ if (view == null)
{
- view.Title = processTitle;
- view.RegisterHotkey(this._configuration.GetClientHotkey(processTitle));
+ // Something went terribly wrong
+ continue;
+ }
- this.ApplyClientLayout(processHandle, processTitle);
+ if (process.Title != view.Title) // update thumbnail title
+ {
+ view.Title = process.Title;
+ view.RegisterHotkey(this._configuration.GetClientHotkey(process.Title));
+ this.ApplyClientLayout(process.Handle, process.Title);
viewsUpdated.Add(view);
}
- if (process.MainWindowHandle == foregroundWindowHandle)
+ if (process.Handle == foregroundWindowHandle)
{
- this._activeClientHandle = process.MainWindowHandle;
- this._activeClientTitle = process.MainWindowTitle;
+ this._activeClientHandle = process.Handle;
+ this._activeClientTitle = process.Title;
}
}
- // Cleanup
- IList obsoleteThumbnails = new List();
-
- foreach (IntPtr processHandle in this._thumbnailViews.Keys)
+ foreach (IProcessInfo process in removedProcesses)
{
- if (!processHandles.Contains(processHandle))
- {
- obsoleteThumbnails.Add(processHandle);
- }
- }
+ IThumbnailView view = this._thumbnailViews[process.Handle];
- foreach (IntPtr processHandle in obsoleteThumbnails)
- {
- IThumbnailView view = this._thumbnailViews[processHandle];
-
- this._thumbnailViews.Remove(processHandle);
+ this._thumbnailViews.Remove(process.Handle);
view.UnregisterHotkey();
@@ -453,11 +445,11 @@ namespace EveOPreview.UI
private void UpdateClientLayouts()
{
- Process[] clientProcesses = ThumbnailManager.GetClientProcesses();
+ ICollection processes = this._processMonitor.GetAllProcesses();
- foreach (Process process in clientProcesses)
+ foreach (IProcessInfo process in processes)
{
- this._windowManager.GetWindowCoordinates(process.MainWindowHandle, out int left, out int top, out int right, out int bottom);
+ this._windowManager.GetWindowCoordinates(process.Handle, out int left, out int top, out int right, out int bottom);
int width = Math.Abs(right - left);
int height = Math.Abs(bottom - top);
@@ -467,7 +459,7 @@ namespace EveOPreview.UI
continue;
}
- this._configuration.SetClientLayout(process.MainWindowTitle, new ClientLayout(left, top, width, height));
+ this._configuration.SetClientLayout(process.Title, new ClientLayout(left, top, width, height));
}
}
diff --git a/Eve-O-Preview/Program.cs b/Eve-O-Preview/Program.cs
index 1ab2408..a439198 100644
--- a/Eve-O-Preview/Program.cs
+++ b/Eve-O-Preview/Program.cs
@@ -3,6 +3,7 @@ using System.Threading;
using System.Windows.Forms;
using EveOPreview.Configuration;
using EveOPreview.Mediator;
+using EveOPreview.Services;
using EveOPreview.UI;
using EveOPreview.WindowManager;
@@ -41,13 +42,13 @@ namespace EveOPreview
{
// The code might look overcomplicated here for a single Mutex operation
// Yet we had already experienced a Windows-level issue
- // where .NET finalizer theread was literally paralyzed by
+ // where .NET finalizer thread was literally paralyzed by
// a failed Mutex operation. That did lead to weird OutOfMemory
// exceptions later
try
{
- Mutex mutex = Mutex.OpenExisting(Program.MutexName);
- // if that didn't fail then anotherinstance is already running
+ Mutex.OpenExisting(Program.MutexName);
+ // if that didn't fail then another instance is already running
return null;
}
catch (UnauthorizedAccessException)
@@ -75,6 +76,7 @@ namespace EveOPreview
// Low-level services
container.Register();
container.Register();
+ container.Register();
// Configuration services
container.Register();
diff --git a/Eve-O-Preview/Services/Implementation/ProcessInfo.cs b/Eve-O-Preview/Services/Implementation/ProcessInfo.cs
new file mode 100644
index 0000000..7021c3f
--- /dev/null
+++ b/Eve-O-Preview/Services/Implementation/ProcessInfo.cs
@@ -0,0 +1,16 @@
+using System;
+
+namespace EveOPreview.Services.Implementation
+{
+ sealed class ProcessInfo : IProcessInfo
+ {
+ public ProcessInfo(IntPtr handle, string title)
+ {
+ this.Handle = handle;
+ this.Title = title;
+ }
+
+ public IntPtr Handle { get; }
+ public string Title { get; }
+ }
+}
\ No newline at end of file
diff --git a/Eve-O-Preview/Services/Implementation/ProcessMonitor.cs b/Eve-O-Preview/Services/Implementation/ProcessMonitor.cs
new file mode 100644
index 0000000..3f53e87
--- /dev/null
+++ b/Eve-O-Preview/Services/Implementation/ProcessMonitor.cs
@@ -0,0 +1,93 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+
+namespace EveOPreview.Services.Implementation
+{
+ sealed class ProcessMonitor : IProcessMonitor
+ {
+ #region Private constants
+ private const string DefaultProcessName = "ExeFile";
+ #endregion
+
+ #region Private fields
+ private readonly IDictionary _processCache;
+ #endregion
+
+ public ProcessMonitor()
+ {
+ this._processCache = new Dictionary(512);
+ }
+
+ private bool IsMonitoredProcess(string processName)
+ {
+ // This is a possible extension point
+ return String.Equals(processName, ProcessMonitor.DefaultProcessName, StringComparison.OrdinalIgnoreCase);
+ }
+
+ public void GetUpdatedProcesses(out ICollection addedProcesses, out ICollection updatedProcesses, out ICollection removedProcesses)
+ {
+ addedProcesses = new List(16);
+ updatedProcesses = new List(16);
+ removedProcesses = new List(16);
+
+ IList knownProcesses = new List(this._processCache.Keys);
+ foreach (Process process in Process.GetProcesses())
+ {
+ string processName = process.ProcessName;
+
+ if (!this.IsMonitoredProcess(processName))
+ {
+ continue;
+ }
+
+ IntPtr mainWindowHandle = process.MainWindowHandle;
+ if (mainWindowHandle == IntPtr.Zero)
+ {
+ continue; // No need to monitor non-visual processes
+ }
+
+ string mainWindowTitle = process.MainWindowTitle;
+ this._processCache.TryGetValue(mainWindowHandle, out string cachedTitle);
+
+ if (cachedTitle == null)
+ {
+ // This is a new process in the list
+ this._processCache.Add(mainWindowHandle, mainWindowTitle);
+ addedProcesses.Add(new ProcessInfo(mainWindowHandle, mainWindowTitle));
+ }
+ else
+ {
+ // This is an already known process
+ if (cachedTitle != mainWindowTitle)
+ {
+ this._processCache[mainWindowHandle] = mainWindowTitle;
+ updatedProcesses.Add((IProcessInfo)new ProcessInfo(mainWindowHandle, mainWindowTitle));
+ }
+
+ knownProcesses.Remove(mainWindowHandle);
+ }
+ }
+
+ foreach (IntPtr index in knownProcesses)
+ {
+ string title = this._processCache[index];
+ removedProcesses.Add(new ProcessInfo(index, title));
+ this._processCache.Remove(index);
+ }
+ }
+
+ public ICollection GetAllProcesses()
+ {
+ ICollection result = new List(this._processCache.Count);
+
+ // TODO Lock list here just in case
+ foreach (KeyValuePair entry in this._processCache)
+ {
+ result.Add(new ProcessInfo(entry.Key, entry.Value));
+ }
+
+ return result;
+ }
+ }
+}
diff --git a/Eve-O-Preview/Services/Interface/IProcessInfo.cs b/Eve-O-Preview/Services/Interface/IProcessInfo.cs
new file mode 100644
index 0000000..d86e9ac
--- /dev/null
+++ b/Eve-O-Preview/Services/Interface/IProcessInfo.cs
@@ -0,0 +1,10 @@
+using System;
+
+namespace EveOPreview.Services
+{
+ public interface IProcessInfo
+ {
+ IntPtr Handle { get; }
+ string Title { get; }
+ }
+}
\ No newline at end of file
diff --git a/Eve-O-Preview/Services/Interface/IProcessMonitor.cs b/Eve-O-Preview/Services/Interface/IProcessMonitor.cs
new file mode 100644
index 0000000..1e123bb
--- /dev/null
+++ b/Eve-O-Preview/Services/Interface/IProcessMonitor.cs
@@ -0,0 +1,10 @@
+using System.Collections.Generic;
+
+namespace EveOPreview.Services
+{
+ public interface IProcessMonitor
+ {
+ ICollection GetAllProcesses();
+ void GetUpdatedProcesses(out ICollection addedProcesses, out ICollection updatedProcesses, out ICollection removedProcesses);
+ }
+}
\ No newline at end of file