Move Process list manager into a separate class

This commit is contained in:
Anton Kasyanov
2018-02-08 22:23:51 +02:00
parent 6bf73ad501
commit 7b5858287a
7 changed files with 200 additions and 73 deletions

View File

@@ -110,6 +110,10 @@
<Compile Include="Configuration\IAppConfig.cs" /> <Compile Include="Configuration\IAppConfig.cs" />
<Compile Include="Configuration\IThumbnailConfiguration.cs" /> <Compile Include="Configuration\IThumbnailConfiguration.cs" />
<Compile Include="Configuration\ZoomAnchor.cs" /> <Compile Include="Configuration\ZoomAnchor.cs" />
<Compile Include="Services\Implementation\ProcessInfo.cs" />
<Compile Include="Services\Implementation\ProcessMonitor.cs" />
<Compile Include="Services\Interface\IProcessInfo.cs" />
<Compile Include="Services\Interface\IProcessMonitor.cs" />
<Compile Include="WindowManager\Implementation\DwmThumbnail.cs" /> <Compile Include="WindowManager\Implementation\DwmThumbnail.cs" />
<Compile Include="WindowManager\Interface\IDwmThumbnail.cs" /> <Compile Include="WindowManager\Interface\IDwmThumbnail.cs" />
<Compile Include="WindowManager\Interface\InteropConstants.cs" /> <Compile Include="WindowManager\Interface\InteropConstants.cs" />

View File

@@ -1,24 +1,25 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing; using System.Drawing;
using System.Windows.Threading; using System.Windows.Threading;
using EveOPreview.Configuration; using EveOPreview.Configuration;
using EveOPreview.Mediator;
using EveOPreview.Services;
using EveOPreview.WindowManager; using EveOPreview.WindowManager;
namespace EveOPreview.UI namespace EveOPreview.UI
{ {
public class ThumbnailManager : IThumbnailManager class ThumbnailManager : IThumbnailManager
{ {
#region Private constants #region Private constants
private const int WindowPositionThreshold = -5000; private const int WindowPositionThreshold = -5000;
private const int WindowSizeThreshold = -5000; private const int WindowSizeThreshold = -5000;
private const string ClientProcessName = "ExeFile";
private const string DefaultClientTitle = "EVE"; private const string DefaultClientTitle = "EVE";
#endregion #endregion
#region Private fields #region Private fields
private readonly IProcessMonitor _processMonitor;
private readonly IWindowManager _windowManager; private readonly IWindowManager _windowManager;
private readonly IThumbnailConfiguration _configuration; private readonly IThumbnailConfiguration _configuration;
private readonly DispatcherTimer _thumbnailUpdateTimer; private readonly DispatcherTimer _thumbnailUpdateTimer;
@@ -32,8 +33,9 @@ namespace EveOPreview.UI
private bool _isHoverEffectActive; private bool _isHoverEffectActive;
#endregion #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._windowManager = windowManager;
this._configuration = configuration; this._configuration = configuration;
this._thumbnailViewFactory = factory; this._thumbnailViewFactory = factory;
@@ -189,15 +191,9 @@ namespace EveOPreview.UI
this._ignoreViewEvents = true; this._ignoreViewEvents = true;
} }
private static Process[] GetClientProcesses()
{
return Process.GetProcessesByName(ThumbnailManager.ClientProcessName);
}
private void UpdateThumbnailsList() private void UpdateThumbnailsList()
{ {
Process[] clientProcesses = ThumbnailManager.GetClientProcesses(); this._processMonitor.GetUpdatedProcesses(out ICollection<IProcessInfo> addedProcesses, out ICollection<IProcessInfo> updatedProcesses, out ICollection<IProcessInfo> removedProcesses);
List<IntPtr> processHandles = new List<IntPtr>(clientProcesses.Length);
IntPtr foregroundWindowHandle = this._windowManager.GetForegroundWindowHandle(); IntPtr foregroundWindowHandle = this._windowManager.GetForegroundWindowHandle();
@@ -205,78 +201,74 @@ namespace EveOPreview.UI
List<IThumbnailView> viewsUpdated = new List<IThumbnailView>(); List<IThumbnailView> viewsUpdated = new List<IThumbnailView>();
List<IThumbnailView> viewsRemoved = new List<IThumbnailView>(); List<IThumbnailView> viewsRemoved = new List<IThumbnailView>();
foreach (Process process in clientProcesses) foreach (IProcessInfo process in addedProcesses)
{ {
IntPtr processHandle = process.MainWindowHandle; IThumbnailView view = this._thumbnailViewFactory.Create(process.Handle, process.Title, this._configuration.ThumbnailSize);
string processTitle = process.MainWindowTitle; view.IsEnabled = true;
processHandles.Add(processHandle); 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; view.ThumbnailLocation = this.IsManageableThumbnail(view)
this._thumbnailViews.TryGetValue(processHandle, out 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); this._activeClientHandle = process.Handle;
view.IsEnabled = true; this._activeClientTitle = process.Title;
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);
} }
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; // Something went terribly wrong
view.RegisterHotkey(this._configuration.GetClientHotkey(processTitle)); 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); viewsUpdated.Add(view);
} }
if (process.MainWindowHandle == foregroundWindowHandle) if (process.Handle == foregroundWindowHandle)
{ {
this._activeClientHandle = process.MainWindowHandle; this._activeClientHandle = process.Handle;
this._activeClientTitle = process.MainWindowTitle; this._activeClientTitle = process.Title;
} }
} }
// Cleanup foreach (IProcessInfo process in removedProcesses)
IList<IntPtr> obsoleteThumbnails = new List<IntPtr>();
foreach (IntPtr processHandle in this._thumbnailViews.Keys)
{ {
if (!processHandles.Contains(processHandle)) IThumbnailView view = this._thumbnailViews[process.Handle];
{
obsoleteThumbnails.Add(processHandle);
}
}
foreach (IntPtr processHandle in obsoleteThumbnails) this._thumbnailViews.Remove(process.Handle);
{
IThumbnailView view = this._thumbnailViews[processHandle];
this._thumbnailViews.Remove(processHandle);
view.UnregisterHotkey(); view.UnregisterHotkey();
@@ -453,11 +445,11 @@ namespace EveOPreview.UI
private void UpdateClientLayouts() private void UpdateClientLayouts()
{ {
Process[] clientProcesses = ThumbnailManager.GetClientProcesses(); ICollection<IProcessInfo> 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 width = Math.Abs(right - left);
int height = Math.Abs(bottom - top); int height = Math.Abs(bottom - top);
@@ -467,7 +459,7 @@ namespace EveOPreview.UI
continue; continue;
} }
this._configuration.SetClientLayout(process.MainWindowTitle, new ClientLayout(left, top, width, height)); this._configuration.SetClientLayout(process.Title, new ClientLayout(left, top, width, height));
} }
} }

View File

@@ -3,6 +3,7 @@ using System.Threading;
using System.Windows.Forms; using System.Windows.Forms;
using EveOPreview.Configuration; using EveOPreview.Configuration;
using EveOPreview.Mediator; using EveOPreview.Mediator;
using EveOPreview.Services;
using EveOPreview.UI; using EveOPreview.UI;
using EveOPreview.WindowManager; using EveOPreview.WindowManager;
@@ -41,13 +42,13 @@ namespace EveOPreview
{ {
// The code might look overcomplicated here for a single Mutex operation // The code might look overcomplicated here for a single Mutex operation
// Yet we had already experienced a Windows-level issue // 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 // a failed Mutex operation. That did lead to weird OutOfMemory
// exceptions later // exceptions later
try try
{ {
Mutex mutex = Mutex.OpenExisting(Program.MutexName); Mutex.OpenExisting(Program.MutexName);
// if that didn't fail then anotherinstance is already running // if that didn't fail then another instance is already running
return null; return null;
} }
catch (UnauthorizedAccessException) catch (UnauthorizedAccessException)
@@ -75,6 +76,7 @@ namespace EveOPreview
// Low-level services // Low-level services
container.Register<IMediator>(); container.Register<IMediator>();
container.Register<IWindowManager>(); container.Register<IWindowManager>();
container.Register<IProcessMonitor>();
// Configuration services // Configuration services
container.Register<IConfigurationStorage>(); container.Register<IConfigurationStorage>();

View File

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

View File

@@ -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<IntPtr, string> _processCache;
#endregion
public ProcessMonitor()
{
this._processCache = new Dictionary<IntPtr, string>(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<IProcessInfo> addedProcesses, out ICollection<IProcessInfo> updatedProcesses, out ICollection<IProcessInfo> removedProcesses)
{
addedProcesses = new List<IProcessInfo>(16);
updatedProcesses = new List<IProcessInfo>(16);
removedProcesses = new List<IProcessInfo>(16);
IList<IntPtr> knownProcesses = new List<IntPtr>(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<IProcessInfo> GetAllProcesses()
{
ICollection<IProcessInfo> result = new List<IProcessInfo>(this._processCache.Count);
// TODO Lock list here just in case
foreach (KeyValuePair<IntPtr, string> entry in this._processCache)
{
result.Add(new ProcessInfo(entry.Key, entry.Value));
}
return result;
}
}
}

View File

@@ -0,0 +1,10 @@
using System;
namespace EveOPreview.Services
{
public interface IProcessInfo
{
IntPtr Handle { get; }
string Title { get; }
}
}

View File

@@ -0,0 +1,10 @@
using System.Collections.Generic;
namespace EveOPreview.Services
{
public interface IProcessMonitor
{
ICollection<IProcessInfo> GetAllProcesses();
void GetUpdatedProcesses(out ICollection<IProcessInfo> addedProcesses, out ICollection<IProcessInfo> updatedProcesses, out ICollection<IProcessInfo> removedProcesses);
}
}