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\IThumbnailConfiguration.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\Interface\IDwmThumbnail.cs" />
<Compile Include="WindowManager\Interface\InteropConstants.cs" />

View File

@@ -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<IntPtr> processHandles = new List<IntPtr>(clientProcesses.Length);
this._processMonitor.GetUpdatedProcesses(out ICollection<IProcessInfo> addedProcesses, out ICollection<IProcessInfo> updatedProcesses, out ICollection<IProcessInfo> removedProcesses);
IntPtr foregroundWindowHandle = this._windowManager.GetForegroundWindowHandle();
@@ -205,78 +201,74 @@ namespace EveOPreview.UI
List<IThumbnailView> viewsUpdated = new List<IThumbnailView>();
List<IThumbnailView> viewsRemoved = new List<IThumbnailView>();
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<IntPtr> obsoleteThumbnails = new List<IntPtr>();
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<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 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));
}
}

View File

@@ -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<IMediator>();
container.Register<IWindowManager>();
container.Register<IProcessMonitor>();
// Configuration services
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);
}
}