/* * Greenshot - a free and open source screenshot tool * Copyright (C) 2007-2020 Thomas Braun, Jens Klingen, Robin Krom * * For more information see: http://getgreenshot.org/ * The Greenshot project is hosted on GitHub https://github.com/greenshot/greenshot * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 1 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ using Greenshot.IniFile; using Greenshot.Interop; using Greenshot.Plugin; using GreenshotPlugin.UnmanagedHelpers; using log4net; using System; using System.Collections.Generic; using System.Diagnostics; using System.Drawing; using System.Drawing.Imaging; using System.Runtime.InteropServices; using System.Text; using System.Text.RegularExpressions; using System.Windows.Forms; namespace GreenshotPlugin.Core { /// /// EnumWindows wrapper for .NET /// public class WindowsEnumerator { /// /// Returns the collection of windows returned by GetWindows /// public IList Items { get; private set; } /// /// Gets all top level windows on the system. /// public WindowsEnumerator GetWindows() { GetWindows(IntPtr.Zero, null); return this; } /// /// Gets all child windows of the specified window /// /// Window Handle to get children for public WindowsEnumerator GetWindows(WindowDetails parent) { GetWindows(parent?.Handle ?? IntPtr.Zero, null); return this; } /// /// Gets all child windows of the specified window /// /// Window Handle to get children for /// Window Classname to copy, use null to copy all public WindowsEnumerator GetWindows(IntPtr hWndParent, string classname) { Items = new List(); IList windows = new List(); User32.EnumChildWindows(hWndParent, WindowEnum, IntPtr.Zero); bool hasParent = !IntPtr.Zero.Equals(hWndParent); string parentText = null; if (hasParent) { var title = new StringBuilder(260, 260); User32.GetWindowText(hWndParent, title, title.Capacity); parentText = title.ToString(); } foreach (var window in Items) { if (hasParent) { window.Text = parentText; window.ParentHandle = hWndParent; } if (classname == null || window.ClassName.Equals(classname)) { windows.Add(window); } } Items = windows; return this; } /// /// The enum Windows callback. /// /// Window Handle /// Application defined value /// 1 to continue enumeration, 0 to stop private int WindowEnum(IntPtr hWnd, int lParam) { if (OnWindowEnum(hWnd)) { return 1; } return 0; } /// /// Called whenever a new window is about to be added /// by the Window enumeration called from GetWindows. /// If overriding this function, return true to continue /// enumeration or false to stop. If you do not call /// the base implementation the Items collection will /// be empty. /// /// Window handle to add /// True to continue enumeration, False to stop protected virtual bool OnWindowEnum(IntPtr hWnd) { if (!WindowDetails.IsIgnoreHandle(hWnd)) { Items.Add(new WindowDetails(hWnd)); } return true; } } /// /// Code for handling with "windows" /// Main code is taken from vbAccelerator, location: /// http://www.vbaccelerator.com/home/NET/Code/Libraries/Windows/Enumerating_Windows/article.asp /// but a LOT of changes/enhancements were made to adapt it for Greenshot. /// /// 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"; private static readonly IList IgnoreClasses = new List(new[] { "Progman", "Button", "Dwm" }); //"MS-SDIa" private static readonly ILog Log = LogManager.GetLogger(typeof(WindowDetails)); private static readonly CoreConfiguration Conf = IniConfig.GetIniSection(); private static readonly IList IgnoreHandles = new List(); private static readonly IList ExcludeProcessesFromFreeze = new List(); private static readonly IAppVisibility AppVisibility; static WindowDetails() { try { // Only try to instantiate when Windows 8 or later. if (Environment.OSVersion.Version.Major >= 6 && Environment.OSVersion.Version.Minor >= 2) { AppVisibility = COMWrapper.CreateInstance(); } } catch (Exception ex) { Log.WarnFormat("Couldn't create instance of IAppVisibility: {0}", ex.Message); } } public static void AddProcessToExcludeFromFreeze(string processname) { if (!ExcludeProcessesFromFreeze.Contains(processname)) { ExcludeProcessesFromFreeze.Add(processname); } } internal static bool IsIgnoreHandle(IntPtr handle) { return IgnoreHandles.Contains(handle); } private IList _childWindows; private IntPtr _parentHandle = IntPtr.Zero; private WindowDetails _parent; private bool _frozen; /// /// 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); /// /// This checks if the window is a Windows 10 App /// For Windows 10 apps are hosted inside "ApplicationFrameWindow" /// public bool IsWin10App => FramedAppClass.Equals(ClassName); /// /// Check if the window is the metro gutter (sizeable separator) /// public bool IsGutter => MetroGutterClass.Equals(ClassName); /// /// Test if this window is for the App-Launcher /// public bool IsAppLauncher => MetroApplauncherClass.Equals(ClassName); /// /// Check if this window is the window of a metro app /// public bool IsMetroApp => IsAppLauncher || IsApp; /// /// To allow items to be compared, the hash code /// is set to the Window handle, so two EnumWindowsItem /// objects for the same Window will be equal. /// /// The Window Handle for this window public override int GetHashCode() { return Handle.ToInt32(); } public override bool Equals(object right) { return Equals(right as WindowDetails); } /// /// Compare two windows details /// /// /// public bool Equals(WindowDetails other) { if (other is null) { return false; } if (ReferenceEquals(this, other)) { return true; } if (GetType() != other.GetType()){ return false; } return other.Handle == Handle; } /// /// Check if the window has children /// public bool HasChildren => (_childWindows != null) && (_childWindows.Count > 0); /// /// Freeze information updates /// public void FreezeDetails() { _frozen = true; } /// /// Make the information update again. /// public void UnfreezeDetails() { _frozen = false; } /// /// Get the file path to the exe for the process which owns this window /// public string ProcessPath { get { if (Handle == IntPtr.Zero) { // not a valid window handle return string.Empty; } // Get the process id User32.GetWindowThreadProcessId(Handle, out var processid); return Kernel32.GetProcessPath(processid); } } /// /// Get the icon belonging to the process /// public Image DisplayIcon { get { try { using var appIcon = GetAppIcon(Handle); if (appIcon != null) { return appIcon.ToBitmap(); } } catch (Exception ex) { Log.WarnFormat("Couldn't get icon for window {0} due to: {1}", Text, ex.Message); Log.Warn(ex); } if (IsMetroApp) { // No method yet to get the metro icon return null; } try { return PluginUtils.GetCachedExeIcon(ProcessPath, 0); } catch (Exception ex) { Log.WarnFormat("Couldn't get icon for window {0} due to: {1}", Text, ex.Message); Log.Warn(ex); } return null; } } /// /// Get the icon for a hWnd /// /// /// private static Icon GetAppIcon(IntPtr hwnd) { IntPtr iconSmall = IntPtr.Zero; IntPtr iconBig = new IntPtr(1); IntPtr iconSmall2 = new IntPtr(2); IntPtr iconHandle; if (Conf.UseLargeIcons) { iconHandle = User32.SendMessage(hwnd, (int)WindowsMessages.WM_GETICON, iconBig, IntPtr.Zero); if (iconHandle == IntPtr.Zero) { iconHandle = User32.GetClassLongWrapper(hwnd, (int)ClassLongIndex.GCL_HICON); } } else { iconHandle = User32.SendMessage(hwnd, (int)WindowsMessages.WM_GETICON, iconSmall2, IntPtr.Zero); } if (iconHandle == IntPtr.Zero) { iconHandle = User32.SendMessage(hwnd, (int)WindowsMessages.WM_GETICON, iconSmall, IntPtr.Zero); } if (iconHandle == IntPtr.Zero) { iconHandle = User32.GetClassLongWrapper(hwnd, (int)ClassLongIndex.GCL_HICONSM); } if (iconHandle == IntPtr.Zero) { iconHandle = User32.SendMessage(hwnd, (int)WindowsMessages.WM_GETICON, iconBig, IntPtr.Zero); } if (iconHandle == IntPtr.Zero) { iconHandle = User32.GetClassLongWrapper(hwnd, (int)ClassLongIndex.GCL_HICON); } if (iconHandle == IntPtr.Zero) { return null; } Icon icon = Icon.FromHandle(iconHandle); return icon; } /// /// Use this to make remove internal windows, like the mainform and the captureforms, invisible /// /// public static void RegisterIgnoreHandle(IntPtr ignoreHandle) { IgnoreHandles.Add(ignoreHandle); } /// /// Use this to remove the with RegisterIgnoreHandle registered handle /// /// public static void UnregisterIgnoreHandle(IntPtr ignoreHandle) { IgnoreHandles.Remove(ignoreHandle); } public IList Children { get { if (_childWindows == null) { GetChildren(); } return _childWindows; } } /// /// Retrieve all windows with a certain title or classname /// /// /// The regexp to look for in the title /// The regexp to look for in the classname /// List WindowDetails with all the found windows private static IEnumerable FindWindow(IList windows, string titlePattern, string classnamePattern) { Regex titleRegexp = null; Regex classnameRegexp = null; if (titlePattern != null && titlePattern.Trim().Length > 0) { titleRegexp = new Regex(titlePattern); } if (classnamePattern != null && classnamePattern.Trim().Length > 0) { classnameRegexp = new Regex(classnamePattern); } foreach(WindowDetails window in windows) { if (titleRegexp != null && titleRegexp.IsMatch(window.Text)) { yield return window; } else if (classnameRegexp != null && classnameRegexp.IsMatch(window.ClassName)) { yield return window; } } } /// /// Retrieve the child with matching classname /// public WindowDetails GetChild(string childClassname) { foreach(WindowDetails child in Children) { if (childClassname.Equals(child.ClassName)) { return child; } } return null; } /// /// Retrieve the children with matching classname /// public IEnumerable GetChilden(string childClassname) { foreach (WindowDetails child in Children) { if (childClassname.Equals(child.ClassName)) { yield return child; } } } public IntPtr ParentHandle { get { if (_parentHandle == IntPtr.Zero) { _parentHandle = User32.GetParent(Handle); _parent = null; } return _parentHandle; } set { if (_parentHandle != value) { _parentHandle = value; _parent = null; } } } /// /// Get the parent of the current window /// /// WindowDetails of the parent, or null if none public WindowDetails GetParent() { if (_parent == null) { if (_parentHandle == IntPtr.Zero) { _parentHandle = User32.GetParent(Handle); } if (_parentHandle != IntPtr.Zero) { _parent = new WindowDetails(_parentHandle); } } return _parent; } /// /// Retrieve all the children, this only stores the children internally. /// One should normally use the getter "Children" /// public IList GetChildren() { if (_childWindows == null) { return GetChildren(0); } return _childWindows; } /// /// Retrieve all the children, this only stores the children internally, use the "Children" property for the value /// /// Specify how many levels we go in public IList GetChildren(int levelsToGo) { if (_childWindows != null) { return _childWindows; } _childWindows = new WindowsEnumerator().GetWindows(Handle, null).Items; foreach(var childWindow in _childWindows) { if (levelsToGo > 0) { childWindow.GetChildren(levelsToGo-1); } } return _childWindows; } /// /// Retrieve children with a certain title or classname /// /// The regexp to look for in the title /// The regexp to look for in the classname /// List WindowDetails with all the found windows, or an empty list public IEnumerable FindChildren(string titlePattern, string classnamePattern) { return FindWindow(Children, titlePattern, classnamePattern); } /// /// Recursing helper method for the FindPath /// /// List string with classnames /// The index in the list to look for /// WindowDetails if a match was found private WindowDetails FindPath(IList classnames, int index) { if (index == classnames.Count - 1) { foreach (var foundWindow in FindChildren(null, classnames[index])) { return foundWindow; } } else { foreach(var foundWindow in FindChildren(null, classnames[index])) { var resultWindow = foundWindow.FindPath(classnames, index+1); if (resultWindow != null) { return resultWindow; } } } return null; } /// /// This method will find the child window according to a path of classnames. /// Usually used for finding a certain "content" window like for the IE Browser /// /// List of string with classname "path" /// true allows the search to skip a classname of the path /// WindowDetails if found public WindowDetails FindPath(IList classnames, bool allowSkip) { int index = 0; var resultWindow = FindPath(classnames, index++); if (resultWindow == null && allowSkip) { while(resultWindow == null && index < classnames.Count) { resultWindow = FindPath(classnames, index); } } return resultWindow; } /// /// Deep scan for a certain classname pattern /// /// Window to scan into /// Classname regexp pattern /// The first WindowDetails found public static WindowDetails DeepScan(WindowDetails windowDetails, Regex classnamePattern) { if (classnamePattern.IsMatch(windowDetails.ClassName)) { return windowDetails; } // First loop through this level foreach(var child in windowDetails.Children) { if (classnamePattern.IsMatch(child.ClassName)) { return child; } } // Go into all children foreach(var child in windowDetails.Children) { var deepWindow = DeepScan(child, classnamePattern); if (deepWindow != null) { return deepWindow; } } return null; } /// /// GetWindow /// /// The GetWindowCommand to use /// null if nothing found, otherwise the WindowDetails instance of the "child" public WindowDetails GetWindow(GetWindowCommand gwCommand) { var tmphWnd = User32.GetWindow(Handle, gwCommand); if (IntPtr.Zero == tmphWnd) { return null; } var windowDetails = new WindowDetails(tmphWnd) { _parent = this }; return windowDetails; } /// /// Gets the window's handle /// public IntPtr Handle { get; } private string _text; /// /// Gets the window's title (caption) /// public string Text { set { _text = value; } get { if (_text == null) { var title = new StringBuilder(260, 260); User32.GetWindowText(Handle, title, title.Capacity); _text = title.ToString(); } return _text; } } private string _className; /// /// Gets the window's class name. /// public string ClassName => _className ?? (_className = GetClassName(Handle)); /// /// Gets/Sets whether the window is iconic (mimimized) or not. /// public bool Iconic { get { if (IsMetroApp) { return !Visible; } return User32.IsIconic(Handle) || Location.X <= -32000; } set { if (value) { User32.SendMessage(Handle, (int)WindowsMessages.WM_SYSCOMMAND, (IntPtr)User32.SC_MINIMIZE, IntPtr.Zero); } else { User32.SendMessage(Handle, (int)WindowsMessages.WM_SYSCOMMAND, (IntPtr)User32.SC_RESTORE, IntPtr.Zero); } } } /// /// Gets/Sets whether the window is maximised or not. /// public bool Maximised { get { if (IsApp) { if (Visible) { Rectangle windowRectangle = WindowRectangle; foreach (var screen in Screen.AllScreens) { if (screen.Bounds.Contains(windowRectangle)) { if (windowRectangle.Equals(screen.Bounds)) { return true; } } } } return false; } return User32.IsZoomed(Handle); } set { if (value) { User32.SendMessage(Handle, (int)WindowsMessages.WM_SYSCOMMAND, (IntPtr)User32.SC_MAXIMIZE, IntPtr.Zero); } else { User32.SendMessage(Handle, (int)WindowsMessages.WM_SYSCOMMAND, (IntPtr)User32.SC_MINIMIZE, IntPtr.Zero); } } } /// /// This doesn't work as good as is should, but does move the App out of the way... /// public void HideApp() { User32.ShowWindow(Handle, ShowWindowCommand.Hide); } /// /// Gets whether the window is visible. /// public bool Visible { get { if (IsApp) { Rectangle windowRectangle = WindowRectangle; foreach (Screen screen in Screen.AllScreens) { if (screen.Bounds.Contains(windowRectangle)) { if (windowRectangle.Equals(screen.Bounds)) { // Fullscreen, it's "visible" when AppVisibilityOnMonitor says yes // Although it might be the other App, this is not "very" important RECT rect = new RECT(screen.Bounds); IntPtr monitor = User32.MonitorFromRect(ref rect, User32.MONITOR_DEFAULTTONULL); if (monitor != IntPtr.Zero) { MONITOR_APP_VISIBILITY? monitorAppVisibility = AppVisibility?.GetAppVisibilityOnMonitor(monitor); //LOG.DebugFormat("App {0} visible: {1} on {2}", Text, monitorAppVisibility, screen.Bounds); if (monitorAppVisibility == MONITOR_APP_VISIBILITY.MAV_APP_VISIBLE) { return true; } } } else { // Is only partly on the screen, when this happens the app is always visible! return true; } } } return false; } if (IsGutter) { // gutter is only made available when it's visible return true; } if (IsAppLauncher) { return IsAppLauncherVisible; } return User32.IsWindowVisible(Handle); } } public bool HasParent { get { GetParent(); return _parentHandle != IntPtr.Zero; } } public int ProcessId { get { User32.GetWindowThreadProcessId(Handle, out var processId); return processId; } } public Process Process { get { try { User32.GetWindowThreadProcessId(Handle, out var processId); return Process.GetProcessById(processId); } catch (Exception ex) { Log.Warn(ex); } return null; } } /// /// Make sure the next call of a cached value is guaranteed the real value /// public void Reset() { _previousWindowRectangle = Rectangle.Empty; } private Rectangle _previousWindowRectangle = Rectangle.Empty; private long _lastWindowRectangleRetrieveTime; private const long CacheTime = TimeSpan.TicksPerSecond * 2; /// /// Gets the bounding rectangle of the window /// public Rectangle WindowRectangle { get { // Try to return a cached value long now = DateTime.Now.Ticks; if (_previousWindowRectangle.IsEmpty || !_frozen) { if (_previousWindowRectangle.IsEmpty || now - _lastWindowRectangleRetrieveTime > CacheTime) { Rectangle windowRect = Rectangle.Empty; if (DWM.IsDwmEnabled()) { bool gotFrameBounds = GetExtendedFrameBounds(out windowRect); if (IsApp) { // Pre-Cache for Maximised call, this is only on Windows 8 apps (full screen) if (gotFrameBounds) { _previousWindowRectangle = windowRect; _lastWindowRectangleRetrieveTime = now; } } if (gotFrameBounds && WindowsVersion.IsWindows10OrLater && !Maximised) { // Somehow DWM doesn't calculate it corectly, there is a 1 pixel border around the capture // Remove this border, currently it's fixed but TODO: Make it depend on the OS? windowRect.Inflate(Conf.Win10BorderCrop); _previousWindowRectangle = windowRect; _lastWindowRectangleRetrieveTime = now; return windowRect; } } if (windowRect.IsEmpty) { if (!GetWindowRect(out windowRect)) { Win32Error error = Win32.GetLastErrorCode(); Log.WarnFormat("Couldn't retrieve the windows rectangle: {0}", Win32.GetMessage(error)); } } // Correction for maximized windows, only if it's not an app if (!HasParent && !IsApp && Maximised) { // Only if the border size can be retrieved if (GetBorderSize(out var size)) { windowRect = new Rectangle(windowRect.X + size.Width, windowRect.Y + size.Height, windowRect.Width - (2 * size.Width), windowRect.Height - (2 * size.Height)); } } _lastWindowRectangleRetrieveTime = now; // Try to return something valid, by getting returning the previous size if the window doesn't have a Rectangle anymore if (windowRect.IsEmpty) { return _previousWindowRectangle; } _previousWindowRectangle = windowRect; return windowRect; } } return _previousWindowRectangle; } } /// /// Gets the location of the window relative to the screen. /// public Point Location { get { Rectangle tmpRectangle = WindowRectangle; return new Point(tmpRectangle.Left, tmpRectangle.Top); } } /// /// Gets the size of the window. /// public Size Size { get { Rectangle tmpRectangle = WindowRectangle; return new Size(tmpRectangle.Right - tmpRectangle.Left, tmpRectangle.Bottom - tmpRectangle.Top); } } /// /// Get the client rectangle, this is the part of the window inside the borders (drawable area) /// public Rectangle ClientRectangle { get { if (!GetClientRect(out var clientRect)) { Win32Error error = Win32.GetLastErrorCode(); Log.WarnFormat("Couldn't retrieve the client rectangle for {0}, error: {1}", Text, Win32.GetMessage(error)); } return clientRect; } } /// /// Check if the supplied point lies in the window /// /// Point with the coordinates to check /// true if the point lies within public bool Contains(Point p) { return WindowRectangle.Contains(p); } /// /// Restores and Brings the window to the front, /// assuming it is a visible application window. /// public void Restore() { if (Iconic) { User32.SendMessage(Handle, (int)WindowsMessages.WM_SYSCOMMAND, (IntPtr)User32.SC_RESTORE, IntPtr.Zero); } User32.BringWindowToTop(Handle); User32.SetForegroundWindow(Handle); // Make sure windows has time to perform the action // TODO: this is BAD practice! while(Iconic) { Application.DoEvents(); } } /// /// Get / Set the WindowStyle /// public WindowStyleFlags WindowStyle { get { return (WindowStyleFlags)User32.GetWindowLongWrapper(Handle, (int)WindowLongIndex.GWL_STYLE); } set { User32.SetWindowLongWrapper(Handle, (int)WindowLongIndex.GWL_STYLE, new IntPtr((long)value)); } } /// /// Get/Set the WindowPlacement /// public WindowPlacement WindowPlacement { get { var placement = WindowPlacement.Default; User32.GetWindowPlacement(Handle, ref placement); return placement; } set { User32.SetWindowPlacement(Handle, ref value); } } /// /// Get/Set the Extended WindowStyle /// public ExtendedWindowStyleFlags ExtendedWindowStyle { get { return (ExtendedWindowStyleFlags)User32.GetWindowLongWrapper(Handle, (int)WindowLongIndex.GWL_EXSTYLE); } set { User32.SetWindowLongWrapper(Handle, (int)WindowLongIndex.GWL_EXSTYLE, new IntPtr((uint)value)); } } /// /// Capture Window with GDI+ /// /// The capture to fill /// ICapture public ICapture CaptureGdiWindow(ICapture capture) { Image capturedImage = PrintWindow(); if (capturedImage != null) { capture.Image = capturedImage; capture.Location = Location; return capture; } return null; } /// /// Capture DWM Window /// /// Capture to fill /// Wanted WindowCaptureMode /// True if auto modus is used /// ICapture with the capture public ICapture CaptureDwmWindow(ICapture capture, WindowCaptureMode windowCaptureMode, bool autoMode) { IntPtr thumbnailHandle = IntPtr.Zero; Form tempForm = null; bool tempFormShown = false; try { tempForm = new Form { ShowInTaskbar = false, FormBorderStyle = FormBorderStyle.None, TopMost = true }; // Register the Thumbnail DWM.DwmRegisterThumbnail(tempForm.Handle, Handle, out thumbnailHandle); // Get the original size DWM.DwmQueryThumbnailSourceSize(thumbnailHandle, out var sourceSize); if (sourceSize.Width <= 0 || sourceSize.Height <= 0) { return null; } // Calculate the location of the temp form Rectangle windowRectangle = WindowRectangle; Point formLocation = windowRectangle.Location; Size borderSize = new Size(); bool doesCaptureFit = false; if (!Maximised) { // Assume using it's own location formLocation = windowRectangle.Location; using Region workingArea = new Region(Screen.PrimaryScreen.Bounds); // Find the screen where the window is and check if it fits foreach (Screen screen in Screen.AllScreens) { if (!Equals(screen, Screen.PrimaryScreen)) { workingArea.Union(screen.Bounds); } } // If the formLocation is not inside the visible area if (!workingArea.AreRectangleCornersVisisble(windowRectangle)) { // If none found we find the biggest screen foreach (Screen screen in Screen.AllScreens) { Rectangle newWindowRectangle = new Rectangle(screen.WorkingArea.Location, windowRectangle.Size); if (workingArea.AreRectangleCornersVisisble(newWindowRectangle)) { formLocation = screen.Bounds.Location; doesCaptureFit = true; break; } } } else { doesCaptureFit = true; } } else if (!WindowsVersion.IsWindows8OrLater) { //GetClientRect(out windowRectangle); GetBorderSize(out borderSize); formLocation = new Point(windowRectangle.X - borderSize.Width, windowRectangle.Y - borderSize.Height); } tempForm.Location = formLocation; tempForm.Size = sourceSize.ToSize(); // Prepare rectangle to capture from the screen. Rectangle captureRectangle = new Rectangle(formLocation.X, formLocation.Y, sourceSize.Width, sourceSize.Height); if (Maximised) { // Correct capture size for maximized window by offsetting the X,Y with the border size // and subtracting the border from the size (2 times, as we move right/down for the capture without resizing) captureRectangle.Inflate(borderSize.Width, borderSize.Height); } else { // TODO: Also 8.x? if (WindowsVersion.IsWindows10OrLater) { captureRectangle.Inflate(Conf.Win10BorderCrop); } if (autoMode) { // check if the capture fits if (!doesCaptureFit) { // if GDI is allowed.. (a screenshot won't be better than we comes if we continue) using Process thisWindowProcess = Process; if (!IsMetroApp && WindowCapture.IsGdiAllowed(thisWindowProcess)) { // we return null which causes the capturing code to try another method. return null; } } } } // Prepare the displaying of the Thumbnail DWM_THUMBNAIL_PROPERTIES props = new DWM_THUMBNAIL_PROPERTIES { Opacity = 255, Visible = true, Destination = new RECT(0, 0, sourceSize.Width, sourceSize.Height) }; DWM.DwmUpdateThumbnailProperties(thumbnailHandle, ref props); tempForm.Show(); tempFormShown = true; // Intersect with screen captureRectangle.Intersect(capture.ScreenBounds); // Destination bitmap for the capture Bitmap capturedBitmap = null; bool frozen = false; try { // Check if we make a transparent capture if (windowCaptureMode == WindowCaptureMode.AeroTransparent) { frozen = FreezeWindow(); // Use white, later black to capture transparent tempForm.BackColor = Color.White; // Make sure everything is visible tempForm.Refresh(); Application.DoEvents(); try { using Bitmap whiteBitmap = WindowCapture.CaptureRectangle(captureRectangle); // Apply a white color tempForm.BackColor = Color.Black; // Make sure everything is visible tempForm.Refresh(); if (!IsMetroApp) { // Make sure the application window is active, so the colors & buttons are right ToForeground(); } // Make sure all changes are processed and visible Application.DoEvents(); using Bitmap blackBitmap = WindowCapture.CaptureRectangle(captureRectangle); capturedBitmap = ApplyTransparency(blackBitmap, whiteBitmap); } catch (Exception e) { Log.Debug("Exception: ", e); // Some problem occurred, cleanup and make a normal capture if (capturedBitmap != null) { capturedBitmap.Dispose(); capturedBitmap = null; } } } // If no capture up till now, create a normal capture. if (capturedBitmap == null) { // Remove transparency, this will break the capturing if (!autoMode) { tempForm.BackColor = Color.FromArgb(255, Conf.DWMBackgroundColor.R, Conf.DWMBackgroundColor.G, Conf.DWMBackgroundColor.B); } else { Color colorizationColor = DWM.ColorizationColor; // Modify by losing the transparency and increasing the intensity (as if the background color is white) colorizationColor = Color.FromArgb(255, (colorizationColor.R + 255) >> 1, (colorizationColor.G + 255) >> 1, (colorizationColor.B + 255) >> 1); tempForm.BackColor = colorizationColor; } // Make sure everything is visible tempForm.Refresh(); if (!IsMetroApp) { // Make sure the application window is active, so the colors & buttons are right ToForeground(); } // Make sure all changes are processed and visible Application.DoEvents(); // Capture from the screen capturedBitmap = WindowCapture.CaptureRectangle(captureRectangle); } if (capturedBitmap != null) { // Not needed for Windows 8 if (!WindowsVersion.IsWindows8OrLater) { // Only if the Inivalue is set, not maximized and it's not a tool window. if (Conf.WindowCaptureRemoveCorners && !Maximised && (ExtendedWindowStyle & ExtendedWindowStyleFlags.WS_EX_TOOLWINDOW) == 0) { // Remove corners if (!Image.IsAlphaPixelFormat(capturedBitmap.PixelFormat)) { Log.Debug("Changing pixelformat to Alpha for the RemoveCorners"); Bitmap tmpBitmap = ImageHelper.Clone(capturedBitmap, PixelFormat.Format32bppArgb); capturedBitmap.Dispose(); capturedBitmap = tmpBitmap; } RemoveCorners(capturedBitmap); } } } } finally { // Make sure to ALWAYS unfreeze!! if (frozen) { UnfreezeWindow(); } } capture.Image = capturedBitmap; // Make sure the capture location is the location of the window, not the copy capture.Location = Location; } finally { if (thumbnailHandle != IntPtr.Zero) { // Unregister (cleanup), as we are finished we don't need the form or the thumbnail anymore DWM.DwmUnregisterThumbnail(thumbnailHandle); } if (tempForm != null) { if (tempFormShown) { tempForm.Close(); } tempForm.Dispose(); tempForm = null; } } return capture; } /// /// Helper method to remove the corners from a DMW capture /// /// The bitmap to remove the corners from. private void RemoveCorners(Bitmap image) { using IFastBitmap fastBitmap = FastBitmap.Create(image); for (int y = 0; y < Conf.WindowCornerCutShape.Count; y++) { for (int x = 0; x < Conf.WindowCornerCutShape[y]; x++) { fastBitmap.SetColorAt(x, y, Color.Transparent); fastBitmap.SetColorAt(image.Width-1-x, y, Color.Transparent); fastBitmap.SetColorAt(image.Width-1-x, image.Height-1-y, Color.Transparent); fastBitmap.SetColorAt(x, image.Height-1-y, Color.Transparent); } } } /// /// Apply transparency by comparing a transparent capture with a black and white background /// A "Math.min" makes sure there is no overflow, but this could cause the picture to have shifted colors. /// The pictures should have been taken without differency, except for the colors. /// /// Bitmap with the black image /// Bitmap with the black image /// Bitmap with transparency private Bitmap ApplyTransparency(Bitmap blackBitmap, Bitmap whiteBitmap) { using IFastBitmap targetBuffer = FastBitmap.CreateEmpty(blackBitmap.Size, PixelFormat.Format32bppArgb, Color.Transparent); targetBuffer.SetResolution(blackBitmap.HorizontalResolution, blackBitmap.VerticalResolution); using (IFastBitmap blackBuffer = FastBitmap.Create(blackBitmap)) { using IFastBitmap whiteBuffer = FastBitmap.Create(whiteBitmap); for (int y = 0; y < blackBuffer.Height; y++) { for (int x = 0; x < blackBuffer.Width; x++) { Color c0 = blackBuffer.GetColorAt(x, y); Color c1 = whiteBuffer.GetColorAt(x, y); // Calculate alpha as double in range 0-1 int alpha = c0.R - c1.R + 255; if (alpha == 255) { // Alpha == 255 means no change! targetBuffer.SetColorAt(x, y, c0); } else if (alpha == 0) { // Complete transparency, use transparent pixel targetBuffer.SetColorAt(x, y, Color.Transparent); } else { // Calculate original color byte originalAlpha = (byte)Math.Min(255, alpha); var alphaFactor = alpha/255d; //LOG.DebugFormat("Alpha {0} & c0 {1} & c1 {2}", alpha, c0, c1); byte originalRed = (byte)Math.Min(255, c0.R / alphaFactor); byte originalGreen = (byte)Math.Min(255, c0.G / alphaFactor); byte originalBlue = (byte)Math.Min(255, c0.B / alphaFactor); Color originalColor = Color.FromArgb(originalAlpha, originalRed, originalGreen, originalBlue); //Color originalColor = Color.FromArgb(originalAlpha, originalRed, c0.G, c0.B); targetBuffer.SetColorAt(x, y, originalColor); } } } } return targetBuffer.UnlockAndReturnBitmap(); } /// /// Helper method to get the window size for DWM Windows /// /// out Rectangle /// bool true if it worked private bool GetExtendedFrameBounds(out Rectangle rectangle) { int result = DWM.DwmGetWindowAttribute(Handle, (int)DWMWINDOWATTRIBUTE.DWMWA_EXTENDED_FRAME_BOUNDS, out var rect, Marshal.SizeOf(typeof(RECT))); if (result >= 0) { rectangle = rect.ToRectangle(); return true; } rectangle = Rectangle.Empty; return false; } /// /// Helper method to get the window size for GDI Windows /// /// out Rectangle /// bool true if it worked private bool GetClientRect(out Rectangle rectangle) { var windowInfo = new WindowInfo(); // Get the Window Info for this window bool result = User32.GetWindowInfo(Handle, ref windowInfo); rectangle = result ? windowInfo.rcClient.ToRectangle() : Rectangle.Empty; return result; } /// /// Helper method to get the window size for GDI Windows /// /// out Rectangle /// bool true if it worked private bool GetWindowRect(out Rectangle rectangle) { var windowInfo = new WindowInfo(); // Get the Window Info for this window bool result = User32.GetWindowInfo(Handle, ref windowInfo); rectangle = result ? windowInfo.rcWindow.ToRectangle() : Rectangle.Empty; return result; } /// /// Helper method to get the Border size for GDI Windows /// /// out Size /// bool true if it worked private bool GetBorderSize(out Size size) { var windowInfo = new WindowInfo(); // Get the Window Info for this window bool result = User32.GetWindowInfo(Handle, ref windowInfo); size = result ? new Size((int)windowInfo.cxWindowBorders, (int)windowInfo.cyWindowBorders) : Size.Empty; return result; } /// /// Set the window as foreground window /// /// hWnd of the window to bring to the foreground /// bool with true to use a trick to really bring the window to the foreground public static void ToForeground(IntPtr handle, bool workaround = true) { var window = new WindowDetails(handle); // Nothing we can do if it's not visible! if (!window.Visible) { return; } if (window.Iconic) { window.Iconic = false; while (window.Iconic) { Application.DoEvents(); } } // See https://msdn.microsoft.com/en-us/library/windows/desktop/ms633539(v=vs.85).aspx if (workaround) { const byte alt = 0xA4; const int extendedkey = 0x1; const int keyup = 0x2; // Simulate an "ALT" key press. User32.keybd_event(alt, 0x45, extendedkey | 0, 0); // Simulate an "ALT" key release. User32.keybd_event(alt, 0x45, extendedkey | keyup, 0); } // Show window in forground. User32.BringWindowToTop(handle); User32.SetForegroundWindow(handle); } /// /// Set the window as foreground window /// /// true to use a workaround, otherwise the window might only flash public void ToForeground(bool workaround = true) { ToForeground(Handle, workaround); } /// /// Get the region for a window /// private Region GetRegion() { using (SafeRegionHandle region = GDI32.CreateRectRgn(0, 0, 0, 0)) { if (!region.IsInvalid) { RegionResult result = User32.GetWindowRgn(Handle, region); if (result != RegionResult.REGION_ERROR && result != RegionResult.REGION_NULLREGION) { return Region.FromHrgn(region.DangerousGetHandle()); } } } return null; } private bool CanFreezeOrUnfreeze(string titleOrProcessname) { if (string.IsNullOrEmpty(titleOrProcessname)) { return false; } if (titleOrProcessname.ToLower().Contains("greenshot")) { return false; } foreach (string excludeProcess in ExcludeProcessesFromFreeze) { if (titleOrProcessname.ToLower().Contains(excludeProcess)) { return false; } } return true; } /// /// Freezes the process belonging to the window /// Warning: Use only if no other way!! /// private bool FreezeWindow() { bool frozen = false; using (Process proc = Process.GetProcessById(ProcessId)) { string processName = proc.ProcessName; if (!CanFreezeOrUnfreeze(processName)) { Log.DebugFormat("Not freezing {0}", processName); return false; } if (!CanFreezeOrUnfreeze(Text)) { Log.DebugFormat("Not freezing {0}", processName); return false; } Log.DebugFormat("Freezing process: {0}", processName); foreach (ProcessThread pT in proc.Threads) { IntPtr pOpenThread = Kernel32.OpenThread(ThreadAccess.SUSPEND_RESUME, false, (uint)pT.Id); if (pOpenThread == IntPtr.Zero) { break; } frozen = true; Kernel32.SuspendThread(pOpenThread); pT.Dispose(); } } return frozen; } /// /// Unfreeze the process belonging to the window /// public void UnfreezeWindow() { using Process proc = Process.GetProcessById(ProcessId); string processName = proc.ProcessName; if (!CanFreezeOrUnfreeze(processName)) { Log.DebugFormat("Not unfreezing {0}", processName); return; } if (!CanFreezeOrUnfreeze(Text)) { Log.DebugFormat("Not unfreezing {0}", processName); return; } Log.DebugFormat("Unfreezing process: {0}", processName); foreach (ProcessThread pT in proc.Threads) { IntPtr pOpenThread = Kernel32.OpenThread(ThreadAccess.SUSPEND_RESUME, false, (uint)pT.Id); if (pOpenThread == IntPtr.Zero) { break; } Kernel32.ResumeThread(pOpenThread); } } /// /// Return an Image representing the Window! /// As GDI+ draws it, it will be without Aero borders! /// public Image PrintWindow() { Rectangle windowRect = WindowRectangle; // Start the capture Exception exceptionOccured = null; Image returnImage; using (Region region = GetRegion()) { PixelFormat pixelFormat = PixelFormat.Format24bppRgb; // Only use 32 bpp ARGB when the window has a region if (region != null) { pixelFormat = PixelFormat.Format32bppArgb; } returnImage = new Bitmap(windowRect.Width, windowRect.Height, pixelFormat); using Graphics graphics = Graphics.FromImage(returnImage); using (SafeDeviceContextHandle graphicsDc = graphics.GetSafeDeviceContext()) { bool printSucceeded = User32.PrintWindow(Handle, graphicsDc.DangerousGetHandle(), 0x0); if (!printSucceeded) { // something went wrong, most likely a "0x80004005" (Acess Denied) when using UAC exceptionOccured = User32.CreateWin32Exception("PrintWindow"); } } // Apply the region "transparency" if (region != null && !region.IsEmpty(graphics)) { graphics.ExcludeClip(region); graphics.Clear(Color.Transparent); } graphics.Flush(); } // Return null if error if (exceptionOccured != null) { Log.ErrorFormat("Error calling print window: {0}", exceptionOccured.Message); returnImage.Dispose(); return null; } if (!HasParent && Maximised) { Log.Debug("Correcting for maximalization"); GetBorderSize(out var borderSize); Rectangle borderRectangle = new Rectangle(borderSize.Width, borderSize.Height, windowRect.Width - (2 * borderSize.Width), windowRect.Height - (2 * borderSize.Height)); ImageHelper.Crop(ref returnImage, ref borderRectangle); } return returnImage; } /// /// Constructs a new instance of this class for /// the specified Window Handle. /// /// The Window Handle public WindowDetails(IntPtr hWnd) { Handle = hWnd; } /// /// Gets an instance of the current active foreground window /// /// WindowDetails of the current window public static WindowDetails GetActiveWindow() { IntPtr hWnd = User32.GetForegroundWindow(); if (hWnd != IntPtr.Zero) { if (IgnoreHandles.Contains(hWnd)) { return GetDesktopWindow(); } WindowDetails activeWindow = new WindowDetails(hWnd); // Invisible Windows should not be active if (!activeWindow.Visible) { return GetDesktopWindow(); } return activeWindow; } return null; } /// /// Check if this window is Greenshot /// public bool IsGreenshot { get { try { if (!IsMetroApp) { using Process thisWindowProcess = Process; return "Greenshot".Equals(thisWindowProcess.MainModule.FileVersionInfo.ProductName); } } catch (Exception ex) { Log.Warn(ex); } return false; } } /// /// Gets the Desktop window /// /// WindowDetails for the desktop window public static WindowDetails GetDesktopWindow() { return new WindowDetails(User32.GetDesktopWindow()); } /// /// Get all the top level windows /// /// List of WindowDetails with all the top level windows public static IList GetAllWindows() { return GetAllWindows(null); } /// /// Get all the top level windows, with matching classname /// /// List WindowDetails with all the top level windows public static IList GetAllWindows(string classname) { return new WindowsEnumerator().GetWindows(IntPtr.Zero, classname).Items; } /// /// Recursive "find children which" /// /// point to check for /// public WindowDetails FindChildUnderPoint(Point point) { if (!Contains(point)) { return null; } var rect = WindowRectangle; // If the mouse it at the edge, take the whole window if (rect.X == point.X || rect.Y == point.Y || rect.Right == point.X || rect.Bottom == point.Y) { return this; } // Look into the child windows foreach(var childWindow in Children) { if (childWindow.Contains(point)) { return childWindow.FindChildUnderPoint(point); } } return this; } /// /// Retrieves the classname for a hWnd /// /// IntPtr with the windows handle /// String with ClassName public static string GetClassName(IntPtr hWnd) { var classNameBuilder = new StringBuilder(260, 260); User32.GetClassName(hWnd, classNameBuilder, classNameBuilder.Capacity); return classNameBuilder.ToString(); } /// /// Helper method to decide if a top level window is visible /// /// /// /// private static bool IsVisible(WindowDetails window, Rectangle screenBounds) { // Ignore invisible if (!window.Visible) { return false; } // Ignore minizied if (window.Iconic) { return false; } if (IgnoreClasses.Contains(window.ClassName)) { return false; } // On windows which are visible on the screen var windowRect = window.WindowRectangle; windowRect.Intersect(screenBounds); if (windowRect.IsEmpty) { return false; } // Skip everything which is not rendered "normally", trying to fix BUG-2017 var exWindowStyle = window.ExtendedWindowStyle; if (!window.IsApp && !window.IsWin10App && (exWindowStyle & ExtendedWindowStyleFlags.WS_EX_NOREDIRECTIONBITMAP) != 0) { return false; } return true; } /// /// Get all the visible top level windows /// /// List WindowDetails with all the visible top level windows public static IEnumerable GetVisibleWindows() { Rectangle screenBounds = WindowCapture.GetScreenBounds(); foreach(var window in GetMetroApps()) { if (IsVisible(window, screenBounds)) { yield return window; } } foreach (var window in GetAllWindows()) { if (IsVisible(window, screenBounds)) { yield return window; } } } /// /// Get the WindowDetails for all Metro Apps /// These are all Windows with Classname "Windows.UI.Core.CoreWindow" /// /// List WindowDetails with visible metro apps public static IEnumerable GetMetroApps() { // 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); 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); if (gutterHandle != IntPtr.Zero) { yield return new WindowDetails(gutterHandle); } } nextHandle = User32.FindWindowEx(IntPtr.Zero, nextHandle, MetroWindowsClass, null); } } /// /// Check if the window is a top level /// /// WindowDetails /// bool private static bool IsTopLevel(WindowDetails window) { // 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.IsEmpty) { return false; } if (window.HasParent) { return false; } var exWindowStyle = window.ExtendedWindowStyle; if ((exWindowStyle & ExtendedWindowStyleFlags.WS_EX_TOOLWINDOW) != 0) { return false; } // Skip everything which is not rendered "normally", trying to fix BUG-2017 if (!window.IsApp && !window.IsWin10App && (exWindowStyle & ExtendedWindowStyleFlags.WS_EX_NOREDIRECTIONBITMAP) != 0) { return false; } // Skip preview windows, like the one from Firefox if ((window.WindowStyle & WindowStyleFlags.WS_VISIBLE) == 0) { return false; } return window.Visible || window.Iconic; } /// /// Get all the top level windows /// /// List WindowDetails with all the top level windows public static IEnumerable GetTopLevelWindows() { foreach (var possibleTopLevel in GetMetroApps()) { if (IsTopLevel(possibleTopLevel)) { yield return possibleTopLevel; } } foreach (var possibleTopLevel in GetAllWindows()) { if (IsTopLevel(possibleTopLevel)) { yield return possibleTopLevel; } } } /// /// Find a window belonging to the same process as the supplied window. /// /// /// public static WindowDetails GetLinkedWindow(WindowDetails windowToLinkTo) { int processIdSelectedWindow = windowToLinkTo.ProcessId; foreach(var window in GetAllWindows()) { // Ignore windows without title if (window.Text.Length == 0) { continue; } // Ignore invisible if (!window.Visible) { continue; } if (window.Handle == windowToLinkTo.Handle) { continue; } if (window.Iconic) { continue; } // Windows without size Size windowSize = window.WindowRectangle.Size; if (windowSize.Width == 0 || windowSize.Height == 0) { continue; } if (window.ProcessId == processIdSelectedWindow) { Log.InfoFormat("Found window {0} belonging to same process as the window {1}", window.Text, windowToLinkTo.Text); return window; } } return null; } /// /// Helper method to "active" all windows that are not in the supplied list. /// One should preferably call "GetVisibleWindows" for the oldWindows. /// /// List WindowDetails with old windows public static void ActiveNewerWindows(IEnumerable oldWindows) { var oldWindowsList = new List(oldWindows); foreach(var window in GetVisibleWindows()) { if (!oldWindowsList.Contains(window)) { window.ToForeground(); } } } /// /// Get the AppLauncher /// /// public static WindowDetails GetAppLauncher() { // Only if Windows 8 (or higher) if (AppVisibility == null) { return null; } IntPtr appLauncher = User32.FindWindow(MetroApplauncherClass, null); if (appLauncher != IntPtr.Zero) { return new WindowDetails (appLauncher); } return null; } /// /// Return true if the metro-app-launcher is visible /// /// public static bool IsAppLauncherVisible { get { if (AppVisibility != null) { return AppVisibility.IsLauncherVisible; } return false; } } } }