// 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.Globalization; using System.Text; using System.Threading; using System.Threading.Tasks; using Wox.Plugin.Common.VirtualDesktop.Helper; using Wox.Plugin.Common.Win32; using Wox.Plugin.Logger; namespace Microsoft.Plugin.WindowWalker.Components { /// /// Represents a specific open window /// internal class Window { /// /// The handle to the window /// private readonly IntPtr hwnd; /// /// A static cache for the process data of all known windows /// that we don't have to query the data every time /// private static readonly Dictionary _handlesToProcessCache = new Dictionary(); /// /// An instance of that contains the process information for the window /// private readonly WindowProcess processInfo; /// /// An instance of that contains the desktop information for the window /// private readonly VDesktop desktopInfo; /// /// Gets the title of the window (the string displayed at the top of the window) /// internal 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 /// internal IntPtr Hwnd { get { return hwnd; } } /// /// Gets the object of with the process information of the window /// internal WindowProcess Process { get { return processInfo; } } /// /// Gets the object of with the desktop information of the window /// internal VDesktop Desktop { get { return desktopInfo; } } /// /// Gets the name of the class for the window represented /// internal string ClassName { get { return GetWindowClassName(Hwnd); } } /// /// Gets a value indicating whether the window is visible (might return false if it is a hidden IE tab) /// internal bool Visible { get { return NativeMethods.IsWindowVisible(Hwnd); } } /// /// Gets a value indicating whether the window is cloaked (true) or not (false). /// (A cloaked window is not visible to the user. But the window is still composed by DWM.) /// internal bool IsCloaked { get { return GetWindowCloakState() != WindowCloakState.None; } } /// /// Gets a value indicating whether the specified window handle identifies an existing window. /// internal bool IsWindow { get { return NativeMethods.IsWindow(Hwnd); } } /// /// Gets a value indicating whether the window is a toolwindow /// internal bool IsToolWindow { get { return (NativeMethods.GetWindowLong(Hwnd, Win32Constants.GWL_EXSTYLE) & (uint)ExtendedWindowStyles.WS_EX_TOOLWINDOW) == (uint)ExtendedWindowStyles.WS_EX_TOOLWINDOW; } } /// /// Gets a value indicating whether the window is an appwindow /// internal bool IsAppWindow { get { return (NativeMethods.GetWindowLong(Hwnd, Win32Constants.GWL_EXSTYLE) & (uint)ExtendedWindowStyles.WS_EX_APPWINDOW) == (uint)ExtendedWindowStyles.WS_EX_APPWINDOW; } } /// /// Gets a value indicating whether the window has ITaskList_Deleted property /// internal bool TaskListDeleted { get { return NativeMethods.GetProp(Hwnd, "ITaskList_Deleted") != IntPtr.Zero; } } /// /// Gets a value indicating whether the specified windows is the owner (i.e. doesn't have an owner) /// internal bool IsOwner { get { return NativeMethods.GetWindow(Hwnd, GetWindowCmd.GW_OWNER) == IntPtr.Zero; } } /// /// Gets a value indicating whether the window is minimized /// internal 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 internal Window(IntPtr hwnd) { // TODO: Add verification as to whether the window handle is valid this.hwnd = hwnd; processInfo = CreateWindowProcessInstance(hwnd); desktopInfo = Main.VirtualDesktopHelperInstance.GetWindowDesktop(hwnd); } /// /// Switches desktop focus to the window /// internal 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 (processInfo.Name.ToUpperInvariant().Equals("IEXPLORE.EXE", StringComparison.Ordinal) || !Minimized) { NativeMethods.SetForegroundWindow(Hwnd); } else { if (!NativeMethods.ShowWindow(Hwnd, ShowWindowCommand.Restore)) { // ShowWindow doesn't work if the process is running elevated: fall back to SendMessage _ = NativeMethods.SendMessage(Hwnd, Win32Constants.WM_SYSCOMMAND, Win32Constants.SC_RESTORE); } } NativeMethods.FlashWindow(Hwnd, true); } /// /// Helper function to close the window /// internal void CloseThisWindowHelper() { _ = NativeMethods.SendMessageTimeout(Hwnd, Win32Constants.WM_SYSCOMMAND, Win32Constants.SC_CLOSE, 0, 0x0000, 5000, out _); } /// /// Closes the window /// internal void CloseThisWindow() { Thread thread = new(new ThreadStart(CloseThisWindowHelper)); thread.Start(); } /// /// 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 + " (" + processInfo.Name.ToUpper(CultureInfo.CurrentCulture) + ")"; } /// /// Returns what the window size is /// /// The state (minimized, maximized, etc..) of the window internal WindowSizeState GetWindowSizeState() { NativeMethods.GetWindowPlacement(Hwnd, out WINDOWPLACEMENT placement); switch (placement.ShowCmd) { case ShowWindowCommand.Normal: return WindowSizeState.Normal; case ShowWindowCommand.Minimize: case ShowWindowCommand.ShowMinimized: return WindowSizeState.Minimized; case ShowWindowCommand.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 /// internal enum WindowSizeState { Normal, Minimized, Maximized, Unknown, } /// /// Returns the window cloak state from DWM /// (A cloaked window is not visible to the user. But the window is still composed by DWM.) /// /// The state (none, app, ...) of the window internal WindowCloakState GetWindowCloakState() { _ = NativeMethods.DwmGetWindowAttribute(Hwnd, (int)DwmWindowAttributes.Cloaked, out int isCloakedState, sizeof(uint)); switch (isCloakedState) { case (int)DwmWindowCloakStates.None: return WindowCloakState.None; case (int)DwmWindowCloakStates.CloakedApp: return WindowCloakState.App; case (int)DwmWindowCloakStates.CloakedShell: return Main.VirtualDesktopHelperInstance.IsWindowCloakedByVirtualDesktopManager(hwnd, Desktop.Id) ? WindowCloakState.OtherDesktop : WindowCloakState.Shell; case (int)DwmWindowCloakStates.CloakedInherited: return WindowCloakState.Inherited; default: return WindowCloakState.Unknown; } } /// /// Enum to simplify the cloak state of the window /// internal enum WindowCloakState { None, App, Shell, Inherited, OtherDesktop, Unknown, } /// /// Returns the class name of a window. /// /// Handle to the window. /// Class name private static string GetWindowClassName(IntPtr hwnd) { StringBuilder windowClassName = new StringBuilder(300); var numCharactersWritten = NativeMethods.GetClassName(hwnd, windowClassName, windowClassName.MaxCapacity); if (numCharactersWritten == 0) { return string.Empty; } return windowClassName.ToString(); } /// /// Gets an instance of form process cache or creates a new one. A new one will be added to the cache. /// /// The handle to the window /// A new Instance of type private static WindowProcess CreateWindowProcessInstance(IntPtr hWindow) { lock (_handlesToProcessCache) { if (_handlesToProcessCache.Count > 7000) { Debug.Print("Clearing Process Cache because its size is " + _handlesToProcessCache.Count); _handlesToProcessCache.Clear(); } // Add window's process to cache if missing if (!_handlesToProcessCache.ContainsKey(hWindow)) { // Get process ID and name var processId = WindowProcess.GetProcessIDFromWindowHandle(hWindow); var threadId = WindowProcess.GetThreadIDFromWindowHandle(hWindow); var processName = WindowProcess.GetProcessNameFromProcessID(processId); if (processName.Length != 0) { _handlesToProcessCache.Add(hWindow, new WindowProcess(processId, threadId, processName)); } else { // For the dwm process we cannot receive the name. This is no problem because the window isn't part of result list. Log.Debug($"Invalid process {processId} ({processName}) for window handle {hWindow}.", typeof(Window)); _handlesToProcessCache.Add(hWindow, new WindowProcess(0, 0, string.Empty)); } } // Correct the process data if the window belongs to a uwp app hosted by 'ApplicationFrameHost.exe' // (This only works if the window isn't minimized. For minimized windows the required child window isn't assigned.) if (string.Equals(_handlesToProcessCache[hWindow].Name, "ApplicationFrameHost.exe", StringComparison.OrdinalIgnoreCase)) { new Task(() => { EnumWindowsProc callbackptr = new EnumWindowsProc((IntPtr hwnd, IntPtr lParam) => { // Every uwp app main window has at least three child windows. Only the one we are interested in has a class starting with "Windows.UI.Core." and is assigned to the real app process. // (The other ones have a class name that begins with the string "ApplicationFrame".) if (GetWindowClassName(hwnd).StartsWith("Windows.UI.Core.", StringComparison.OrdinalIgnoreCase)) { var childProcessId = WindowProcess.GetProcessIDFromWindowHandle(hwnd); var childThreadId = WindowProcess.GetThreadIDFromWindowHandle(hwnd); var childProcessName = WindowProcess.GetProcessNameFromProcessID(childProcessId); // Update process info in cache _handlesToProcessCache[hWindow].UpdateProcessInfo(childProcessId, childThreadId, childProcessName); return false; } else { return true; } }); _ = NativeMethods.EnumChildWindows(hWindow, callbackptr, 0); }).Start(); } return _handlesToProcessCache[hWindow]; } } } }