// Copyright (c) Microsoft Corporation // The Microsoft Corporation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. // Code forked from Betsegaw Tadele's https://github.com/betsegaw/windowwalker/ using System; using System.Collections.Generic; using System.Diagnostics; using System.Drawing; using System.Globalization; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows; using System.Windows.Interop; using System.Windows.Media; using System.Windows.Media.Imaging; namespace Microsoft.Plugin.WindowWalker.Components { /// /// Represents a specific open window /// public class Window { /// /// Maximum size of a file name /// private const int MaximumFileNameLength = 1000; /// /// The list of owners of a window so that we don't have to /// constantly query for the process owning a specific window /// private static readonly Dictionary _handlesToProcessCache = new Dictionary(); /// /// The handle to the window /// private readonly IntPtr hwnd; /// /// Gets the title of the window (the string displayed at the top of the window) /// public string Title { get { int sizeOfTitle = NativeMethods.GetWindowTextLength(hwnd); if (sizeOfTitle++ > 0) { StringBuilder titleBuffer = new StringBuilder(sizeOfTitle); var numCharactersWritten = NativeMethods.GetWindowText(hwnd, titleBuffer, sizeOfTitle); if (numCharactersWritten == 0) { return string.Empty; } return titleBuffer.ToString(); } else { return string.Empty; } } } /// /// Gets the handle to the window /// public IntPtr Hwnd { get { return hwnd; } } public uint ProcessID { get; set; } /// /// Gets returns the name of the process /// public string ProcessName { get { lock (_handlesToProcessCache) { if (_handlesToProcessCache.Count > 7000) { Debug.Print("Clearing Process Cache because it's size is " + _handlesToProcessCache.Count); _handlesToProcessCache.Clear(); } if (!_handlesToProcessCache.ContainsKey(Hwnd)) { var processName = GetProcessNameFromWindowHandle(Hwnd); if (processName.Length != 0) { _handlesToProcessCache.Add( Hwnd, processName.ToString().Split('\\').Reverse().ToArray()[0]); } else { _handlesToProcessCache.Add(Hwnd, string.Empty); } } if (_handlesToProcessCache[hwnd].ToUpperInvariant() == "APPLICATIONFRAMEHOST.EXE") { new Task(() => { NativeMethods.CallBackPtr callbackptr = new NativeMethods.CallBackPtr((IntPtr hwnd, IntPtr lParam) => { var childProcessId = GetProcessIDFromWindowHandle(hwnd); if (childProcessId != ProcessID) { _handlesToProcessCache[Hwnd] = GetProcessNameFromWindowHandle(hwnd); return false; } else { return true; } }); _ = NativeMethods.EnumChildWindows(Hwnd, callbackptr, 0); }).Start(); } return _handlesToProcessCache[hwnd]; } } } /// /// Gets returns the name of the class for the window represented /// public string ClassName { get { StringBuilder windowClassName = new StringBuilder(300); var numCharactersWritten = NativeMethods.GetClassName(Hwnd, windowClassName, windowClassName.MaxCapacity); if (numCharactersWritten == 0) { return string.Empty; } return windowClassName.ToString(); } } /// /// Gets a value indicating whether is the window visible (might return false if it is a hidden IE tab) /// public bool Visible { get { return NativeMethods.IsWindowVisible(Hwnd); } } /// /// Gets a value indicating whether determines whether the specified window handle identifies an existing window. /// public bool IsWindow { get { return NativeMethods.IsWindow(Hwnd); } } /// /// Gets a value indicating whether get a value indicating whether is the window GWL_EX_STYLE is a toolwindow /// public bool IsToolWindow { get { return (NativeMethods.GetWindowLong(Hwnd, NativeMethods.GWL_EXSTYLE) & (uint)NativeMethods.ExtendedWindowStyles.WS_EX_TOOLWINDOW) == (uint)NativeMethods.ExtendedWindowStyles.WS_EX_TOOLWINDOW; } } /// /// Gets a value indicating whether get a value indicating whether the window GWL_EX_STYLE is an appwindow /// public bool IsAppWindow { get { return (NativeMethods.GetWindowLong(Hwnd, NativeMethods.GWL_EXSTYLE) & (uint)NativeMethods.ExtendedWindowStyles.WS_EX_APPWINDOW) == (uint)NativeMethods.ExtendedWindowStyles.WS_EX_APPWINDOW; } } /// /// Gets a value indicating whether get a value indicating whether the window has ITaskList_Deleted property /// public bool TaskListDeleted { get { return NativeMethods.GetProp(Hwnd, "ITaskList_Deleted") != IntPtr.Zero; } } /// /// Gets a value indicating whether returns true if the window is minimized /// public bool Minimized { get { return GetWindowSizeState() == WindowSizeState.Minimized; } } /// /// Initializes a new instance of the class. /// Initializes a new Window representation /// /// the handle to the window we are representing public Window(IntPtr hwnd) { // TODO: Add verification as to whether the window handle is valid this.hwnd = hwnd; } /// /// Switches desktop focus to the window /// public void SwitchToWindow() { // The following block is necessary because // 1) There is a weird flashing behavior when trying // to use ShowWindow for switching tabs in IE // 2) SetForegroundWindow fails on minimized windows // Using Ordinal since this is internal if (ProcessName.ToUpperInvariant().Equals("IEXPLORE.EXE", StringComparison.Ordinal) || !Minimized) { NativeMethods.SetForegroundWindow(Hwnd); } else { NativeMethods.ShowWindow(Hwnd, NativeMethods.ShowWindowCommands.Restore); } NativeMethods.FlashWindow(Hwnd, true); } /// /// Converts the window name to string along with the process name /// /// The title of the window public override string ToString() { // Using CurrentCulture since this is user facing return Title + " (" + ProcessName.ToUpper(CultureInfo.CurrentCulture) + ")"; } /// /// Returns what the window size is /// /// The state (minimized, maximized, etc..) of the window public WindowSizeState GetWindowSizeState() { NativeMethods.GetWindowPlacement(Hwnd, out NativeMethods.WINDOWPLACEMENT placement); switch (placement.ShowCmd) { case NativeMethods.ShowWindowCommands.Normal: return WindowSizeState.Normal; case NativeMethods.ShowWindowCommands.Minimize: case NativeMethods.ShowWindowCommands.ShowMinimized: return WindowSizeState.Minimized; case NativeMethods.ShowWindowCommands.Maximize: // No need for ShowMaximized here since its also of value 3 return WindowSizeState.Maximized; default: // throw new Exception("Don't know how to handle window state = " + placement.ShowCmd); return WindowSizeState.Unknown; } } /// /// Enum to simplify the state of the window /// public enum WindowSizeState { Normal, Minimized, Maximized, Unknown, } /// /// Gets the name of the process using the window handle /// /// The handle to the window /// A string representing the process name or an empty string if the function fails private string GetProcessNameFromWindowHandle(IntPtr hwnd) { uint processId = GetProcessIDFromWindowHandle(hwnd); ProcessID = processId; IntPtr processHandle = NativeMethods.OpenProcess(NativeMethods.ProcessAccessFlags.AllAccess, true, (int)processId); StringBuilder processName = new StringBuilder(MaximumFileNameLength); if (NativeMethods.GetProcessImageFileName(processHandle, processName, MaximumFileNameLength) != 0) { return processName.ToString().Split('\\').Reverse().ToArray()[0]; } else { return string.Empty; } } /// /// Gets the process ID for the Window handle /// /// The handle to the window /// The process ID private static uint GetProcessIDFromWindowHandle(IntPtr hwnd) { _ = NativeMethods.GetWindowThreadProcessId(hwnd, out uint processId); return processId; } } }