// 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.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 list of icons from process so that we don't have to keep
/// loading them from disk
///
private static readonly Dictionary _processIdsToIconsCache = 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 = InteropAndHelpers.GetWindowTextLength(hwnd);
if (sizeOfTitle++ > 0)
{
StringBuilder titleBuffer = new StringBuilder(sizeOfTitle);
InteropAndHelpers.GetWindowText(hwnd, titleBuffer, sizeOfTitle);
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].ToLower() == "applicationframehost.exe")
{
new Task(() =>
{
InteropAndHelpers.CallBackPtr callbackptr = new InteropAndHelpers.CallBackPtr((IntPtr hwnd, IntPtr lParam) =>
{
var childProcessId = GetProcessIDFromWindowHandle(hwnd);
if (childProcessId != this.ProcessID)
{
_handlesToProcessCache[Hwnd] = GetProcessNameFromWindowHandle(hwnd);
return false;
}
else
{
return true;
}
});
InteropAndHelpers.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);
InteropAndHelpers.GetClassName(Hwnd, windowClassName, windowClassName.MaxCapacity);
return windowClassName.ToString();
}
}
///
/// Gets represents the Window Icon for the specified window
///
public ImageSource WindowIcon
{
get
{
lock (_processIdsToIconsCache)
{
InteropAndHelpers.GetWindowThreadProcessId(Hwnd, out uint processId);
if (!_processIdsToIconsCache.ContainsKey(processId))
{
try
{
Process process = Process.GetProcessById((int)processId);
Icon tempIcon = Icon.ExtractAssociatedIcon(process.Modules[0].FileName);
_processIdsToIconsCache.Add(processId, Imaging.CreateBitmapSourceFromHIcon(
tempIcon.Handle,
Int32Rect.Empty,
BitmapSizeOptions.FromEmptyOptions()));
}
catch
{
BitmapImage failedImage = new BitmapImage(new Uri(@"Images\failedIcon.jpg", UriKind.Relative));
_processIdsToIconsCache.Add(processId, failedImage);
}
}
return _processIdsToIconsCache[processId];
}
}
}
///
/// Gets a value indicating whether is the window visible (might return false if it is a hidden IE tab)
///
public bool Visible
{
get
{
return InteropAndHelpers.IsWindowVisible(Hwnd);
}
}
///
/// Determines whether the specified window handle identifies an existing window.
///
public bool IsWindow
{
get
{
return InteropAndHelpers.IsWindow(Hwnd);
}
}
///
/// Get a value indicating whether is the window GWL_EX_STYLE is a toolwindow
///
public bool IsToolWindow
{
get
{
return (InteropAndHelpers.GetWindowLong(Hwnd, InteropAndHelpers.GWL_EXSTYLE) &
(uint)InteropAndHelpers.ExtendedWindowStyles.WS_EX_TOOLWINDOW) ==
(uint)InteropAndHelpers.ExtendedWindowStyles.WS_EX_TOOLWINDOW;
}
}
///
/// Get a value indicating whether the window GWL_EX_STYLE is an appwindow
///
public bool IsAppWindow
{
get
{
return (InteropAndHelpers.GetWindowLong(Hwnd, InteropAndHelpers.GWL_EXSTYLE) &
(uint)InteropAndHelpers.ExtendedWindowStyles.WS_EX_APPWINDOW) ==
(uint)InteropAndHelpers.ExtendedWindowStyles.WS_EX_APPWINDOW;
}
}
///
/// Get a value indicating whether the window has ITaskList_Deleted property
///
public bool TaskListDeleted
{
get
{
return InteropAndHelpers.GetProp(Hwnd, "ITaskList_Deleted") != IntPtr.Zero;
}
}
///
/// Get a value indicating whether the app is a cloaked UWP app
///
public bool IsUWPCloaked
{
get
{
return (this.IsWindowCloaked() && this.ClassName == "ApplicationFrameWindow");
}
}
///
/// Determines whether the specified windows is the owner
///
public bool IsOwner
{
get
{
return InteropAndHelpers.GetWindow(Hwnd, InteropAndHelpers.GetWindowCmd.GW_OWNER) != null;
}
}
///
/// Gets a value indicating whether is the window cloaked. To detect UWP apps in background or win32 apps running in another virtual desktop
///
public bool IsWindowCloaked()
{
int isCloaked = 0;
const int DWMWA_CLOAKED = 14;
InteropAndHelpers.DwmGetWindowAttribute(this.hwnd, DWMWA_CLOAKED, out isCloaked, sizeof(int));
return isCloaked != 0;
}
///
/// 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;
}
///
/// Highlights a window to help the user identify the window that has been selected
///
public void HighlightWindow()
{
throw new NotImplementedException();
}
///
/// Switches dekstop 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
if (ProcessName.ToLower().Equals("iexplore.exe") || !Minimized)
{
InteropAndHelpers.SetForegroundWindow(Hwnd);
}
else
{
InteropAndHelpers.ShowWindow(Hwnd, InteropAndHelpers.ShowWindowCommands.Restore);
}
InteropAndHelpers.FlashWindow(Hwnd, true);
}
///
/// Converts the window name to string along with the process name
///
/// The title of the window
public override string ToString()
{
return Title + " (" + ProcessName.ToUpper() + ")";
}
///
/// Returns what the window size is
///
/// The state (minimized, maximized, etc..) of the window
public WindowSizeState GetWindowSizeState()
{
InteropAndHelpers.GetWindowPlacement(Hwnd, out InteropAndHelpers.WINDOWPLACEMENT placement);
switch (placement.ShowCmd)
{
case InteropAndHelpers.ShowWindowCommands.Normal:
return WindowSizeState.Normal;
case InteropAndHelpers.ShowWindowCommands.Minimize:
case InteropAndHelpers.ShowWindowCommands.ShowMinimized:
return WindowSizeState.Minimized;
case InteropAndHelpers.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 = InteropAndHelpers.OpenProcess(InteropAndHelpers.ProcessAccessFlags.AllAccess, true, (int)processId);
StringBuilder processName = new StringBuilder(MaximumFileNameLength);
if (InteropAndHelpers.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 uint GetProcessIDFromWindowHandle(IntPtr hwnd)
{
InteropAndHelpers.GetWindowThreadProcessId(hwnd, out uint processId);
return processId;
}
}
}