diff --git a/Greenshot/Forms/MainForm.cs b/Greenshot/Forms/MainForm.cs index ccb6b9a4f..fb35500b6 100644 --- a/Greenshot/Forms/MainForm.cs +++ b/Greenshot/Forms/MainForm.cs @@ -918,7 +918,7 @@ namespace Greenshot.Forms { private void ShowThumbnailOnEnter(object sender, EventArgs e) { if (sender is not ToolStripMenuItem captureWindowItem) return; - WindowDetails window = captureWindowItem.Tag as WindowDetails; + var window = captureWindowItem.Tag as WindowDetails; if (_thumbnailForm == null) { _thumbnailForm = new ThumbnailForm(); } @@ -939,29 +939,39 @@ namespace Greenshot.Forms { _thumbnailForm = null; } + /// + /// Create the "capture window from list" list + /// + /// ToolStripMenuItem + /// EventHandler public void AddCaptureWindowMenuItems(ToolStripMenuItem menuItem, EventHandler eventHandler) { menuItem.DropDownItems.Clear(); // check if thumbnailPreview is enabled and DWM is enabled bool thumbnailPreview = _conf.ThumnailPreview && DWM.IsDwmEnabled; - foreach(WindowDetails window in WindowDetails.GetTopLevelWindows()) { - + foreach(var window in WindowDetails.GetTopLevelWindows()) { + if (LOG.IsDebugEnabled) + { + LOG.Debug(window.ToString()); + } string title = window.Text; - if (title != null) { - if (title.Length > _conf.MaxMenuItemLength) { - title = title.Substring(0, Math.Min(title.Length, _conf.MaxMenuItemLength)); - } - ToolStripItem captureWindowItem = menuItem.DropDownItems.Add(title); - captureWindowItem.Tag = window; - captureWindowItem.Image = window.DisplayIcon; - captureWindowItem.Click += eventHandler; - // Only show preview when enabled - if (thumbnailPreview) { - captureWindowItem.MouseEnter += ShowThumbnailOnEnter; - captureWindowItem.MouseLeave += HideThumbnailOnLeave; - } - } - } + if (string.IsNullOrEmpty(title)) + { + continue; + } + if (title.Length > _conf.MaxMenuItemLength) { + title = title.Substring(0, Math.Min(title.Length, _conf.MaxMenuItemLength)); + } + ToolStripItem captureWindowItem = menuItem.DropDownItems.Add(title); + captureWindowItem.Tag = window; + captureWindowItem.Image = window.DisplayIcon; + captureWindowItem.Click += eventHandler; + // Only show preview when enabled + if (thumbnailPreview) { + captureWindowItem.MouseEnter += ShowThumbnailOnEnter; + captureWindowItem.MouseLeave += HideThumbnailOnLeave; + } + } } private void CaptureAreaToolStripMenuItemClick(object sender, EventArgs e) { diff --git a/Greenshot/Helpers/CaptureHelper.cs b/Greenshot/Helpers/CaptureHelper.cs index 19acda012..813a18b4f 100644 --- a/Greenshot/Helpers/CaptureHelper.cs +++ b/Greenshot/Helpers/CaptureHelper.cs @@ -970,7 +970,7 @@ namespace Greenshot.Helpers { // The following, to be precise the HideApp, causes the app to close as described in BUG-1620 // Added check for metro (Modern UI) apps, which might be maximized and cover the screen. - //foreach(WindowDetails app in WindowDetails.GetMetroApps()) { + //foreach(WindowDetails app in WindowDetails.GetAppWindows()) { // if (app.Maximised) { // app.HideApp(); // } diff --git a/GreenshotPlugin/Controls/ThumbnailForm.cs b/GreenshotPlugin/Controls/ThumbnailForm.cs index 3578b63d3..aae16b46c 100644 --- a/GreenshotPlugin/Controls/ThumbnailForm.cs +++ b/GreenshotPlugin/Controls/ThumbnailForm.cs @@ -22,6 +22,7 @@ using System; using System.Windows.Forms; using GreenshotPlugin.Core; using System.Drawing; +using GreenshotPlugin.Core.Enums; using GreenshotPlugin.IniFile; using GreenshotPlugin.UnmanagedHelpers; using GreenshotPlugin.UnmanagedHelpers.Enums; @@ -32,7 +33,7 @@ namespace GreenshotPlugin.Controls { /// This form allows us to show a Thumbnail preview of a window near the context menu when selecting a window to capture. /// Didn't make it completely "generic" yet, but at least most logic is in here so we don't have it in the mainform. /// - public class ThumbnailForm : FormWithoutActivation { + public sealed class ThumbnailForm : FormWithoutActivation { private static readonly CoreConfiguration conf = IniConfig.GetIniSection(); private IntPtr _thumbnailHandle = IntPtr.Zero; @@ -59,12 +60,13 @@ namespace GreenshotPlugin.Controls { base.Hide(); } - private void UnregisterThumbnail() { - if (_thumbnailHandle != IntPtr.Zero) { - DWM.DwmUnregisterThumbnail(_thumbnailHandle); - _thumbnailHandle = IntPtr.Zero; - } - } + private void UnregisterThumbnail() + { + if (_thumbnailHandle == IntPtr.Zero) return; + + DWM.DwmUnregisterThumbnail(_thumbnailHandle); + _thumbnailHandle = IntPtr.Zero; + } /// /// Show the thumbnail of the supplied window above (or under) the parent Control @@ -75,41 +77,60 @@ namespace GreenshotPlugin.Controls { UnregisterThumbnail(); DWM.DwmRegisterThumbnail(Handle, window.Handle, out _thumbnailHandle); - if (_thumbnailHandle != IntPtr.Zero) { - DWM.DwmQueryThumbnailSourceSize(_thumbnailHandle, out var sourceSize); - int thumbnailHeight = 200; - int thumbnailWidth = (int)(thumbnailHeight * (sourceSize.Width / (float)sourceSize.Height)); - if (parentControl != null && thumbnailWidth > parentControl.Width) { - thumbnailWidth = parentControl.Width; - thumbnailHeight = (int)(thumbnailWidth * (sourceSize.Height / (float)sourceSize.Width)); - } - Width = thumbnailWidth; - Height = thumbnailHeight; - // Prepare the displaying of the Thumbnail - DWM_THUMBNAIL_PROPERTIES props = new DWM_THUMBNAIL_PROPERTIES - { - Opacity = 255, - Visible = true, - SourceClientAreaOnly = false, - Destination = new RECT(0, 0, thumbnailWidth, thumbnailHeight) - }; - DWM.DwmUpdateThumbnailProperties(_thumbnailHandle, ref props); - if (parentControl != null) { - AlignToControl(parentControl); - } + if (_thumbnailHandle == IntPtr.Zero) return; - if (!Visible) { - Show(); - } - // Make sure it's on "top"! - if (parentControl != null) { - User32.SetWindowPos(Handle, parentControl.Handle, 0, 0, 0, 0, WindowPos.SWP_NOMOVE | WindowPos.SWP_NOSIZE | WindowPos.SWP_NOACTIVATE); - } - } - } + var result = DWM.DwmQueryThumbnailSourceSize(_thumbnailHandle, out var sourceSize); + if (result.Failed()) + { + DWM.DwmUnregisterThumbnail(_thumbnailHandle); + return; + } + + if (sourceSize.IsEmpty) + { + DWM.DwmUnregisterThumbnail(_thumbnailHandle); + return; + } + + int thumbnailHeight = 200; + int thumbnailWidth = (int)(thumbnailHeight * (sourceSize.Width / (float)sourceSize.Height)); + if (parentControl != null && thumbnailWidth > parentControl.Width) + { + thumbnailWidth = parentControl.Width; + thumbnailHeight = (int)(thumbnailWidth * (sourceSize.Height / (float)sourceSize.Width)); + } + Width = thumbnailWidth; + Height = thumbnailHeight; + // Prepare the displaying of the Thumbnail + var dwmThumbnailProperties = new DWM_THUMBNAIL_PROPERTIES + { + Opacity = 255, + Visible = true, + SourceClientAreaOnly = false, + Destination = new RECT(0, 0, thumbnailWidth, thumbnailHeight) + }; + result = DWM.DwmUpdateThumbnailProperties(_thumbnailHandle, ref dwmThumbnailProperties); + if (result.Failed()) + { + DWM.DwmUnregisterThumbnail(_thumbnailHandle); + return; + } + + if (parentControl != null) { + AlignToControl(parentControl); + } + + if (!Visible) { + Show(); + } + // Make sure it's on "top"! + if (parentControl != null) { + User32.SetWindowPos(Handle, parentControl.Handle, 0, 0, 0, 0, WindowPos.SWP_NOMOVE | WindowPos.SWP_NOSIZE | WindowPos.SWP_NOACTIVATE); + } + } public void AlignToControl(Control alignTo) { - Rectangle screenBounds = WindowCapture.GetScreenBounds(); + var screenBounds = WindowCapture.GetScreenBounds(); if (screenBounds.Contains(alignTo.Left, alignTo.Top - Height)) { Location = new Point(alignTo.Left + (alignTo.Width / 2) - (Width / 2), alignTo.Top - Height); } else { diff --git a/GreenshotPlugin/Core/WindowDetails.cs b/GreenshotPlugin/Core/WindowDetails.cs index a2d3d9a63..903483c0c 100644 --- a/GreenshotPlugin/Core/WindowDetails.cs +++ b/GreenshotPlugin/Core/WindowDetails.cs @@ -2,7 +2,9 @@ using System.Collections.Generic; using System.Diagnostics; using System.Drawing; +using System.Drawing.Drawing2D; using System.Drawing.Imaging; +using System.Linq; using System.Runtime.InteropServices; using System.Text; using System.Text.RegularExpressions; @@ -25,11 +27,11 @@ namespace GreenshotPlugin.Core /// /// Provides details about a Window returned by the enumeration /// - public class WindowDetails : IEquatable{ - private const string MetroWindowsClass = "Windows.UI.Core.CoreWindow"; //Used for Windows 8(.1) - private const string FramedAppClass = "ApplicationFrameWindow"; // Windows 10 uses ApplicationFrameWindow - private const string MetroApplauncherClass = "ImmersiveLauncher"; - private const string MetroGutterClass = "ImmersiveGutter"; + public class WindowDetails : IEquatable { + private const string AppWindowClass = "Windows.UI.Core.CoreWindow"; //Used for Windows 8(.1) + private const string AppFrameWindowClass = "ApplicationFrameWindow"; // Windows 10 uses ApplicationFrameWindow + private const string ApplauncherClass = "ImmersiveLauncher"; + private const string GutterClass = "ImmersiveGutter"; private static readonly IList IgnoreClasses = new List(new[] { "Progman", "Button", "Dwm" }); //"MS-SDIa" private static readonly ILog Log = LogManager.GetLogger(typeof(WindowDetails)); @@ -72,23 +74,28 @@ namespace GreenshotPlugin.Core /// This checks if the window is a Windows 8 App /// For Windows 10 most normal code works, as it's hosted inside "ApplicationFrameWindow" /// - public bool IsApp => MetroWindowsClass.Equals(ClassName); + public bool IsApp => AppWindowClass.Equals(ClassName); /// /// This checks if the window is a Windows 10 App /// For Windows 10 apps are hosted inside "ApplicationFrameWindow" /// - public bool IsWin10App => FramedAppClass.Equals(ClassName); + public bool IsWin10App => AppFrameWindowClass.Equals(ClassName); + /// + /// Check if this window belongs to a background app + /// + public bool IsBackgroundWin10App => WindowsVersion.IsWindows10OrLater && AppFrameWindowClass.Equals(ClassName) && !Children.Any(window => string.Equals(window.ClassName, AppWindowClass)); + /// /// Check if the window is the metro gutter (sizeable separator) /// - public bool IsGutter => MetroGutterClass.Equals(ClassName); + public bool IsGutter => GutterClass.Equals(ClassName); /// /// Test if this window is for the App-Launcher /// - public bool IsAppLauncher => MetroApplauncherClass.Equals(ClassName); + public bool IsAppLauncher => ApplauncherClass.Equals(ClassName); /// /// Check if this window is the window of a metro app @@ -447,7 +454,7 @@ namespace GreenshotPlugin.Core /// public bool Visible { get { - // Tip from Raymond Chen + // Tip from Raymond Chen https://devblogs.microsoft.com/oldnewthing/20200302-00/?p=103507 if (IsCloaked) { return false; @@ -1337,7 +1344,7 @@ namespace GreenshotPlugin.Core /// List WindowDetails with all the visible top level windows public static IEnumerable GetVisibleWindows() { Rectangle screenBounds = WindowCapture.GetScreenBounds(); - foreach(var window in GetMetroApps()) { + foreach(var window in GetAppWindows()) { if (IsVisible(window, screenBounds)) { yield return window; @@ -1357,37 +1364,24 @@ namespace GreenshotPlugin.Core /// These are all Windows with Classname "Windows.UI.Core.CoreWindow" /// /// List WindowDetails with visible metro apps - public static IEnumerable GetMetroApps() { + public static IEnumerable GetAppWindows() { // if the appVisibility != null we have Windows 8. if (AppVisibility == null) { yield break; } - //string[] wcs = {"ImmersiveGutter", "Snapped Desktop", "ImmersiveBackgroundWindow","ImmersiveLauncher","Windows.UI.Core.CoreWindow","ApplicationManager_ImmersiveShellWindow","SearchPane","MetroGhostWindow","EdgeUiInputWndClass", "NativeHWNDHost", "Shell_CharmWindow"}; - //List specials = new List(); - //foreach(string wc in wcs) { - // IntPtr wcHandle = User32.FindWindow(null, null); - // while (wcHandle != IntPtr.Zero) { - // WindowDetails special = new WindowDetails(wcHandle); - // if (special.WindowRectangle.Left >= 1920 && special.WindowRectangle.Size != Size.Empty) { - // specials.Add(special); - // LOG.DebugFormat("Found special {0} : {1} at {2} visible: {3} {4} {5}", special.ClassName, special.Text, special.WindowRectangle, special.Visible, special.ExtendedWindowStyle, special.WindowStyle); - // } - // wcHandle = User32.FindWindowEx(IntPtr.Zero, wcHandle, null, null); - // }; - //} - IntPtr nextHandle = User32.FindWindow(MetroWindowsClass, null); + var nextHandle = User32.FindWindow(AppWindowClass, null); while (nextHandle != IntPtr.Zero) { var metroApp = new WindowDetails(nextHandle); yield return metroApp; // Check if we have a gutter! if (metroApp.Visible && !metroApp.Maximised) { - var gutterHandle = User32.FindWindow(MetroGutterClass, null); + var gutterHandle = User32.FindWindow(GutterClass, null); if (gutterHandle != IntPtr.Zero) { yield return new WindowDetails(gutterHandle); } } - nextHandle = User32.FindWindowEx(IntPtr.Zero, nextHandle, MetroWindowsClass, null); + nextHandle = User32.FindWindowEx(IntPtr.Zero, nextHandle, AppWindowClass, null); } } @@ -1398,21 +1392,10 @@ namespace GreenshotPlugin.Core /// bool private static bool IsTopLevel(WindowDetails window) { - // Window is not on this desktop if (window.IsCloaked) { return false; } - - // Ignore windows without title - if (window.Text.Length == 0) - { - return false; - } - if (IgnoreClasses.Contains(window.ClassName)) - { - return false; - } // Windows without size if (window.WindowRectangle.Size.Width * window.WindowRectangle.Size.Height == 0) { @@ -1437,7 +1420,21 @@ namespace GreenshotPlugin.Core { return false; } - return window.Visible || window.Iconic; + // Ignore windows without title + if (window.Text.Length == 0) + { + return false; + } + if (IgnoreClasses.Contains(window.ClassName)) + { + return false; + } + if (!(window.Visible || window.Iconic)) + { + return false; + } + + return !window.IsBackgroundWin10App; } /// @@ -1445,7 +1442,7 @@ namespace GreenshotPlugin.Core /// /// List WindowDetails with all the top level windows public static IEnumerable GetTopLevelWindows() { - foreach (var possibleTopLevel in GetMetroApps()) + foreach (var possibleTopLevel in GetAppWindows()) { if (IsTopLevel(possibleTopLevel)) { @@ -1523,7 +1520,7 @@ namespace GreenshotPlugin.Core if (AppVisibility == null) { return null; } - IntPtr appLauncher = User32.FindWindow(MetroApplauncherClass, null); + IntPtr appLauncher = User32.FindWindow(ApplauncherClass, null); if (appLauncher != IntPtr.Zero) { return new WindowDetails (appLauncher); } @@ -1542,5 +1539,32 @@ namespace GreenshotPlugin.Core return false; } } + + /// + /// Make a string representation of the window details + /// + /// string + public override string ToString() + { + var result = new StringBuilder(); + result.AppendLine($"Text: {Text}"); + result.AppendLine($"ClassName: {ClassName}"); + result.AppendLine($"ExtendedWindowStyle: {ExtendedWindowStyle}"); + result.AppendLine($"WindowStyle: {WindowStyle}"); + result.AppendLine($"Size: {WindowRectangle.Size}"); + result.AppendLine($"HasParent: {HasParent}"); + result.AppendLine($"IsWin10App: {IsWin10App}"); + result.AppendLine($"IsApp: {IsApp}"); + result.AppendLine($"Visible: {Visible}"); + result.AppendLine($"IsWindowVisible: {User32.IsWindowVisible(Handle)}"); + result.AppendLine($"IsCloaked: {IsCloaked}"); + result.AppendLine($"Iconic: {Iconic}"); + result.AppendLine($"IsBackgroundWin10App: {IsBackgroundWin10App}"); + if (HasChildren) + { + result.AppendLine($"Children classes: {string.Join(",", Children.Select(c => c.ClassName))}"); + } + return result.ToString(); + } } } \ No newline at end of file diff --git a/GreenshotPlugin/UnmanagedHelpers/DWM.cs b/GreenshotPlugin/UnmanagedHelpers/DWM.cs index b95a766ba..eed7c71e5 100644 --- a/GreenshotPlugin/UnmanagedHelpers/DWM.cs +++ b/GreenshotPlugin/UnmanagedHelpers/DWM.cs @@ -23,6 +23,7 @@ using System; using System.Drawing; using System.Runtime.InteropServices; using GreenshotPlugin.Core; +using GreenshotPlugin.Core.Enums; using GreenshotPlugin.UnmanagedHelpers.Enums; using GreenshotPlugin.UnmanagedHelpers.Structs; using Microsoft.Win32; @@ -40,9 +41,9 @@ namespace GreenshotPlugin.UnmanagedHelpers { [DllImport("dwmapi", SetLastError = true)] public static extern int DwmUnregisterThumbnail(IntPtr thumb); [DllImport("dwmapi", SetLastError = true)] - public static extern int DwmQueryThumbnailSourceSize(IntPtr thumb, out SIZE size); + public static extern HResult DwmQueryThumbnailSourceSize(IntPtr thumb, out SIZE size); [DllImport("dwmapi", SetLastError = true)] - public static extern int DwmUpdateThumbnailProperties(IntPtr hThumb, ref DWM_THUMBNAIL_PROPERTIES props); + public static extern HResult DwmUpdateThumbnailProperties(IntPtr hThumb, ref DWM_THUMBNAIL_PROPERTIES props); // Deprecated as of Windows 8 Release Preview [DllImport("dwmapi", SetLastError = true)] diff --git a/GreenshotPlugin/UnmanagedHelpers/Enums/DWMWINDOWATTRIBUTE.cs b/GreenshotPlugin/UnmanagedHelpers/Enums/DWMWINDOWATTRIBUTE.cs index 04490cd17..e58bde613 100644 --- a/GreenshotPlugin/UnmanagedHelpers/Enums/DWMWINDOWATTRIBUTE.cs +++ b/GreenshotPlugin/UnmanagedHelpers/Enums/DWMWINDOWATTRIBUTE.cs @@ -26,7 +26,21 @@ namespace GreenshotPlugin.UnmanagedHelpers.Enums [SuppressMessage("ReSharper", "InconsistentNaming")] public enum DWMWINDOWATTRIBUTE : uint { + DWMWA_NCRENDERING_ENABLED = 1, + DWMWA_NCRENDERING_POLICY, + DWMWA_TRANSITIONS_FORCEDISABLED, + DWMWA_ALLOW_NCPAINT, + DWMWA_CAPTION_BUTTON_BOUNDS, + DWMWA_NONCLIENT_RTL_LAYOUT, + DWMWA_FORCE_ICONIC_REPRESENTATION, + DWMWA_FLIP3D_POLICY, DWMWA_EXTENDED_FRAME_BOUNDS, // This is the one we need for retrieving the Window size since Windows Vista + DWMWA_HAS_ICONIC_BITMAP, // Since Windows 7 + DWMWA_DISALLOW_PEEK, // Since Windows 7 + DWMWA_EXCLUDED_FROM_PEEK, // Since Windows 7 + DWMWA_CLOAK, // Since Windows 8 DWMWA_CLOAKED, // Since Windows 8 + DWMWA_FREEZE_REPRESENTATION, // Since Windows 8 + DWMWA_LAST } } \ No newline at end of file diff --git a/GreenshotPlugin/UnmanagedHelpers/Structs/SIZE.cs b/GreenshotPlugin/UnmanagedHelpers/Structs/SIZE.cs index be5d00bbc..bd1b78699 100644 --- a/GreenshotPlugin/UnmanagedHelpers/Structs/SIZE.cs +++ b/GreenshotPlugin/UnmanagedHelpers/Structs/SIZE.cs @@ -40,5 +40,7 @@ namespace GreenshotPlugin.UnmanagedHelpers.Structs { public Size ToSize() { return new Size(Width, Height); } + + public bool IsEmpty => Width * Height == 0; } }