diff --git a/Eve-O-Preview/Configuration/Implementation/ThumbnailConfiguration.cs b/Eve-O-Preview/Configuration/Implementation/ThumbnailConfiguration.cs index 9ec33b0..779f509 100644 --- a/Eve-O-Preview/Configuration/Implementation/ThumbnailConfiguration.cs +++ b/Eve-O-Preview/Configuration/Implementation/ThumbnailConfiguration.cs @@ -24,6 +24,8 @@ namespace EveOPreview.Configuration.Implementation this.MinimizeToTray = false; this.ThumbnailRefreshPeriod = 500; + this.EnableCompatibilityMode = false; + this.ThumbnailOpacity = 0.5; this.EnableClientLayoutTracking = false; @@ -54,6 +56,9 @@ namespace EveOPreview.Configuration.Implementation public bool MinimizeToTray { get; set; } public int ThumbnailRefreshPeriod { get; set; } + [JsonProperty("CompatibilityMode")] + public bool EnableCompatibilityMode { get; set; } + [JsonProperty("ThumbnailsOpacity")] public double ThumbnailOpacity { get; set; } @@ -145,7 +150,8 @@ namespace EveOPreview.Configuration.Implementation // If there is no layout too then use the default one if (this.EnablePerClientThumbnailLayouts && !string.IsNullOrEmpty(activeClient)) { - if (this.PerClientLayout.TryGetValue(activeClient, out Dictionary layoutSource) && layoutSource.TryGetValue(currentClient, out location)) + Dictionary layoutSource; + if (this.PerClientLayout.TryGetValue(activeClient, out layoutSource) && layoutSource.TryGetValue(currentClient, out location)) { return location; } @@ -181,7 +187,8 @@ namespace EveOPreview.Configuration.Implementation public ClientLayout GetClientLayout(string currentClient) { - this.ClientLayout.TryGetValue(currentClient, out ClientLayout layout); + ClientLayout layout; + this.ClientLayout.TryGetValue(currentClient, out layout); return layout; } @@ -193,7 +200,8 @@ namespace EveOPreview.Configuration.Implementation public Keys GetClientHotkey(string currentClient) { - if (this.ClientHotkey.TryGetValue(currentClient, out string hotkey)) + string hotkey; + if (this.ClientHotkey.TryGetValue(currentClient, out hotkey)) { // Protect from incorrect values object rawValue = (new KeysConverter()).ConvertFromInvariantString(hotkey); diff --git a/Eve-O-Preview/Configuration/Interface/IThumbnailConfiguration.cs b/Eve-O-Preview/Configuration/Interface/IThumbnailConfiguration.cs index 565e395..3cb1158 100644 --- a/Eve-O-Preview/Configuration/Interface/IThumbnailConfiguration.cs +++ b/Eve-O-Preview/Configuration/Interface/IThumbnailConfiguration.cs @@ -8,6 +8,8 @@ namespace EveOPreview.Configuration bool MinimizeToTray { get; set; } int ThumbnailRefreshPeriod { get; set; } + bool EnableCompatibilityMode { get; set; } + double ThumbnailOpacity { get; set; } bool EnableClientLayoutTracking { get; set; } diff --git a/Eve-O-Preview/Eve-O-Preview.csproj b/Eve-O-Preview/Eve-O-Preview.csproj index ef5efdc..375b7a6 100644 --- a/Eve-O-Preview/Eve-O-Preview.csproj +++ b/Eve-O-Preview/Eve-O-Preview.csproj @@ -147,6 +147,7 @@ + @@ -163,6 +164,9 @@ Component + + Form + diff --git a/Eve-O-Preview/Program.cs b/Eve-O-Preview/Program.cs index 0206b9b..dd29e98 100644 --- a/Eve-O-Preview/Program.cs +++ b/Eve-O-Preview/Program.cs @@ -99,6 +99,7 @@ namespace EveOPreview IApplicationController controller = new ApplicationController(container); // UI classes + controller.RegisterView(); controller.RegisterView(); controller.RegisterView(); diff --git a/Eve-O-Preview/Services/Implementation/WindowManager.cs b/Eve-O-Preview/Services/Implementation/WindowManager.cs index 0782f80..80ff23c 100644 --- a/Eve-O-Preview/Services/Implementation/WindowManager.cs +++ b/Eve-O-Preview/Services/Implementation/WindowManager.cs @@ -14,7 +14,7 @@ namespace EveOPreview.Services.Implementation public WindowManager() { // Composition is always enabled for Windows 8+ - this.IsCompositionEnabled = + this.IsCompositionEnabled = ((Environment.OSVersion.Version.Major == 6) && (Environment.OSVersion.Version.Minor >= 2)) // Win 8 and Win 8.1 || (Environment.OSVersion.Version.Major >= 10) // Win 10 || DwmNativeMethods.DwmIsCompositionEnabled(); // In case of Win 7 an API call is requiredWin 7 @@ -89,5 +89,35 @@ namespace EveOPreview.Services.Implementation return thumbnail; } + + public Image GetStaticThumbnail(IntPtr source) + { + var sourceContext = User32NativeMethods.GetDC(source); + + User32NativeMethods.GetClientRect(source, out RECT windowRect); + + var width = windowRect.Right - windowRect.Left; + var height = windowRect.Bottom - windowRect.Top; + + // Check if there is anything to make thumbnail of + if ((width < WINDOW_SIZE_THRESHOLD) || (height < WINDOW_SIZE_THRESHOLD)) + { + return null; + } + + var destContext = Gdi32NativeMethods.CreateCompatibleDC(sourceContext); + var bitmap = Gdi32NativeMethods.CreateCompatibleBitmap(sourceContext, width, height); + + var oldBitmap = Gdi32NativeMethods.SelectObject(destContext, bitmap); + Gdi32NativeMethods.BitBlt(destContext, 0, 0, width, height, sourceContext, 0, 0, Gdi32NativeMethods.SRCCOPY); + Gdi32NativeMethods.SelectObject(destContext, oldBitmap); + Gdi32NativeMethods.DeleteDC(destContext); + User32NativeMethods.ReleaseDC(source, sourceContext); + + Image image = Image.FromHbitmap(bitmap); + Gdi32NativeMethods.DeleteObject(bitmap); + + return image; + } } } \ No newline at end of file diff --git a/Eve-O-Preview/Services/Interface/IWindowManager.cs b/Eve-O-Preview/Services/Interface/IWindowManager.cs index f9f7e06..c1ce08d 100644 --- a/Eve-O-Preview/Services/Interface/IWindowManager.cs +++ b/Eve-O-Preview/Services/Interface/IWindowManager.cs @@ -1,4 +1,5 @@ using System; +using System.Drawing; namespace EveOPreview.Services { @@ -15,5 +16,6 @@ namespace EveOPreview.Services bool IsWindowMaximized(IntPtr handle); bool IsWindowMinimized(IntPtr handle); IDwmThumbnail GetLiveThumbnail(IntPtr destination, IntPtr source); + Image GetStaticThumbnail(IntPtr source); } } \ No newline at end of file diff --git a/Eve-O-Preview/Services/Interop/DwmNativeMethods.cs b/Eve-O-Preview/Services/Interop/DwmNativeMethods.cs index 696668b..5cb7325 100644 --- a/Eve-O-Preview/Services/Interop/DwmNativeMethods.cs +++ b/Eve-O-Preview/Services/Interop/DwmNativeMethods.cs @@ -1,12 +1,28 @@ using System; using System.Runtime.InteropServices; +using System.Drawing; namespace EveOPreview.Services.Interop { static class DwmNativeMethods { + [DllImport("dwmapi.dll", PreserveSig = false)] + public static extern void DwmEnableBlurBehindWindow(IntPtr hWnd, DWM_BLURBEHIND pBlurBehind); + + [DllImport("dwmapi.dll", PreserveSig = false)] + public static extern void DwmExtendFrameIntoClientArea(IntPtr hWnd, MARGINS pMargins); + [DllImport("dwmapi.dll", PreserveSig = false)] public static extern bool DwmIsCompositionEnabled(); + + [DllImport("dwmapi.dll", PreserveSig = false)] + public static extern void DwmGetColorizationColor( + out int pcrColorization, + [MarshalAs(UnmanagedType.Bool)]out bool pfOpaqueBlend); + + [DllImport("dwmapi.dll", PreserveSig = false)] + public static extern void DwmEnableComposition(bool bEnable); + [DllImport("dwmapi.dll", PreserveSig = false)] public static extern IntPtr DwmRegisterThumbnail(IntPtr dest, IntPtr source); @@ -15,5 +31,8 @@ namespace EveOPreview.Services.Interop [DllImport("dwmapi.dll", PreserveSig = false)] public static extern void DwmUpdateThumbnailProperties(IntPtr hThumbnail, DWM_THUMBNAIL_PROPERTIES props); + + [DllImport("dwmapi.dll", PreserveSig = false)] + public static extern void DwmQueryThumbnailSourceSize(IntPtr hThumbnail, out Size size); } } \ No newline at end of file diff --git a/Eve-O-Preview/Services/Interop/Gdi32NativeMethods.cs b/Eve-O-Preview/Services/Interop/Gdi32NativeMethods.cs new file mode 100644 index 0000000..f966511 --- /dev/null +++ b/Eve-O-Preview/Services/Interop/Gdi32NativeMethods.cs @@ -0,0 +1,28 @@ +using System; +using System.Runtime.InteropServices; + +namespace EveOPreview.Services.Interop +{ + static class Gdi32NativeMethods + { + public const int SRCCOPY = 13369376; + + [DllImport("gdi32.dll")] + public static extern IntPtr CreateCompatibleDC(IntPtr hdc); + + [DllImport("gdi32.dll")] + public static extern bool DeleteDC(IntPtr hdc); + + [DllImport("gdi32.dll")] + public static extern IntPtr CreateCompatibleBitmap(IntPtr hdc, int width, int height); + + [DllImport("gdi32.dll")] + public static extern IntPtr SelectObject(IntPtr hdc, IntPtr hObject); + + [DllImport("gdi32.dll")] + public static extern bool DeleteObject(IntPtr hObject); + + [DllImport("gdi32.dll")] + public static extern bool BitBlt(IntPtr hObject, int nXDest, int nYDest, int nWidth, int nHeight, IntPtr hObjectSource, int nXSrc, int nYSrc, int dwRop); + } +} diff --git a/Eve-O-Preview/Services/Interop/User32NativeMethods.cs b/Eve-O-Preview/Services/Interop/User32NativeMethods.cs index 05ef2bc..6adc97a 100644 --- a/Eve-O-Preview/Services/Interop/User32NativeMethods.cs +++ b/Eve-O-Preview/Services/Interop/User32NativeMethods.cs @@ -14,6 +14,9 @@ namespace EveOPreview.Services.Interop [DllImport("user32.dll")] public static extern bool ShowWindowAsync(IntPtr hWnd, int nCmdShow); + [DllImport("User32.dll")] + public static extern bool ReleaseCapture(); + [DllImport("User32.dll")] public static extern int SendMessage(IntPtr hWnd, int Msg, int wParam, int lParam); @@ -23,6 +26,9 @@ namespace EveOPreview.Services.Interop [DllImport("user32.dll")] public static extern int GetWindowRect(IntPtr hWnd, out RECT rect); + [DllImport("user32.dll")] + public static extern bool GetClientRect(IntPtr hWnd, out RECT rect); + [DllImport("user32.dll", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool GetWindowPlacement(IntPtr hWnd, ref WINDOWPLACEMENT lpwndpl); @@ -39,5 +45,14 @@ namespace EveOPreview.Services.Interop [DllImport("user32.dll")] public static extern bool IsZoomed(IntPtr hWnd); + + [DllImport("user32.dll")] + public static extern IntPtr GetWindowDC(IntPtr hWnd); + + [DllImport("user32.dll")] + public static extern IntPtr GetDC(IntPtr hWnd); + + [DllImport("user32.dll")] + public static extern IntPtr ReleaseDC(IntPtr hWnd, IntPtr hdc); } } \ No newline at end of file diff --git a/Eve-O-Preview/View/Implementation/StaticThumbnailView.cs b/Eve-O-Preview/View/Implementation/StaticThumbnailView.cs new file mode 100644 index 0000000..c01ebc3 --- /dev/null +++ b/Eve-O-Preview/View/Implementation/StaticThumbnailView.cs @@ -0,0 +1,70 @@ +using System; +using System.Drawing; +using System.Windows.Forms; +using EveOPreview.Services; + +namespace EveOPreview.View +{ + sealed class StaticThumbnailView : ThumbnailView + { + #region Private fields + private readonly PictureBox _thumbnail; + #endregion + + public StaticThumbnailView(IWindowManager windowManager) + : base(windowManager) + { + this._thumbnail = new StaticThumbnailImage + { + TabStop = false, + SizeMode = PictureBoxSizeMode.StretchImage, + Location = new Point(0, 0), + Size = new Size(this.ClientSize.Width, this.ClientSize.Height) + }; + this.Controls.Add(this._thumbnail); + } + + protected override void RefreshThumbnail(bool forceRefresh) + { + if (!forceRefresh) + { + return; + } + + var thumbnail = this.WindowManager.GetStaticThumbnail(this.Id); + if (thumbnail != null) + { + var oldImage = this._thumbnail.Image; + this._thumbnail.Image = thumbnail; + oldImage?.Dispose(); + } + } + + protected override void ResizeThumbnail(int baseWidth, int baseHeight, int highlightWidthTop, int highlightWidthRight, int highlightWidthBottom, int highlightWidthLeft) + { + var left = 0 + highlightWidthLeft; + var top = 0 + highlightWidthTop; + if (this.IsLocationUpdateRequired(this._thumbnail.Location, left, top)) + { + this._thumbnail.Location = new Point(left, top); + } + + var width = baseWidth - highlightWidthLeft - highlightWidthRight; + var height = baseHeight - highlightWidthTop - highlightWidthBottom; + if (this.IsSizeUpdateRequired(this._thumbnail.Size, width, height)) + { + this._thumbnail.Size = new Size(width, height); + } + } + + private bool IsLocationUpdateRequired(Point currentLocation, int left, int top) + { + return (currentLocation.X != left) || (currentLocation.Y != top); + } + + private bool IsSizeUpdateRequired(Size currentSize, int width, int height) + { + return (currentSize.Width != width) || (currentSize.Height != height); + } + } +} \ No newline at end of file diff --git a/Eve-O-Preview/View/Implementation/ThumbnailViewFactory.cs b/Eve-O-Preview/View/Implementation/ThumbnailViewFactory.cs index 285adef..83d271e 100644 --- a/Eve-O-Preview/View/Implementation/ThumbnailViewFactory.cs +++ b/Eve-O-Preview/View/Implementation/ThumbnailViewFactory.cs @@ -7,15 +7,19 @@ namespace EveOPreview.View sealed class ThumbnailViewFactory : IThumbnailViewFactory { private readonly IApplicationController _controller; + private readonly bool _isCompatibilityModeEnabled; public ThumbnailViewFactory(IApplicationController controller, IThumbnailConfiguration configuration) { this._controller = controller; + this._isCompatibilityModeEnabled = configuration.EnableCompatibilityMode; } public IThumbnailView Create(IntPtr id, string title, Size size) { - IThumbnailView view = this._controller.Create(); + IThumbnailView view = this._isCompatibilityModeEnabled + ? (IThumbnailView)this._controller.Create() + : (IThumbnailView)this._controller.Create(); view.Id = id; view.Title = title; diff --git a/README.md b/README.md index 7b377bc..03004b0 100644 --- a/README.md +++ b/README.md @@ -91,6 +91,7 @@ Some of the application options are not exposed in the GUI. They can be adjusted | Option | Description | | --- | --- | | **ActiveClientHighlightThickness** | Thickness of the border used to highlight the active client's thumbnail.
Allowed values are **1**...**6**.
The default value is **3**
For example: **"ActiveClientHighlightThickness": 3** | +| **CompatibilityMode** | Enables the alternative render mode (see below)
The default value is **false**
For example: **"CompatibilityMode": true** | | **EnableThumbnailSnap** | Allows to disable thumbnails snap feature by setting its value to **false**
The default value is **true**
For example: **"EnableThumbnailSnap": true** | | **PriorityClients** | Allows to set a list of clients that are not auto-minimized on inactivity even if the **Minimize inactive EVE clients** option is enabled. Listed clients still can be minimized using Windows hotkeys or via _Ctrl+Click_ on the corresponding thumbnail
The default value is empty list **[]**
For example: **"PriorityClients": [ "EVE - Phrynohyas Tig-Rah", "EVE - Ondatra Patrouette" ]** | | **ThumbnailMinimumSize** | Minimum thumbnail size that can be set either via GUI or by resizing a thumbnail window. Value is written in the form "width, height"
The default value is **"100, 80"**.
For example: **"ThumbnailMinimumSize": "100, 80"** | @@ -124,6 +125,14 @@ The following hotkey is described as `modifier+key` where `modifier` can be **Co **Note:** Do not set hotkeys to use the key combinations already used by EVE. It won't work as "_I set hotkey for my DPS char to F1 and when I'll press F1 it will automatically open the DPS char's window and activate guns_". Key combination will be swallowed by EVE-O Preview and NOT retranslated to EVE window. So it will be only "_it will automatically open the DPS char's window_". +## Compatibility Mode + +This setting allows to enable an alternate thumbnail render. This render doesn't use advanced DWM API to create live previews. Instead it is a screenshot-based render with the following pros and cons: +* `+` Should work even in remote desktop environments +* `-` Consumes significantly more memory. In the testing environment EVE-O Preview did consume around 180 MB to manage 3 thumbnails using this render. At the same time the primary render did consume around 50 MB when run in the same environment. +* `-` Thumbnail images are refreshed at 1 FPS rate +* `-` Possible short mouse cursor freezes + --- # Credits