diff --git a/GoToWindow.Api.Tests/WindowEntryFactoryTests.cs b/GoToWindow.Api.Tests/WindowEntryFactoryTests.cs index 5c048fe..49d06c7 100644 --- a/GoToWindow.Api.Tests/WindowEntryFactoryTests.cs +++ b/GoToWindow.Api.Tests/WindowEntryFactoryTests.cs @@ -14,12 +14,11 @@ public void GetGetWindowEntry_FromTestWindow() var expectedWindowHandle = app.Process.MainWindowHandle; var window = WindowEntryFactory.Create(expectedWindowHandle); - Assert.AreEqual(expectedWindowHandle, window.HWnd); Assert.AreEqual((uint)app.Process.Id, window.ProcessId); - Assert.AreEqual(app.Process.ProcessName, window.ProcessName); Assert.AreEqual(app.ExpectedWindow.Title, window.Title); Assert.AreNotEqual(IntPtr.Zero, window.IconHandle); + Assert.IsNull(window.ProcessName); } } } diff --git a/GoToWindow.Api/IWindowEntry.cs b/GoToWindow.Api/IWindowEntry.cs index 0fe0546..1e3e094 100644 --- a/GoToWindow.Api/IWindowEntry.cs +++ b/GoToWindow.Api/IWindowEntry.cs @@ -7,9 +7,11 @@ public interface IWindowEntry IntPtr HWnd { get; set; } uint ProcessId { get; set; } string ProcessName { get; set; } + //TODO: Windows 10 App Icons + //string ProcessFileName { get; set; } string Title { get; set; } IntPtr IconHandle { get; set; } - bool IsVisible { get; set; } + bool IsVisible { get; set; } bool Focus(); bool IsForeground(); diff --git a/GoToWindow.Api/WindowEntryFactory.cs b/GoToWindow.Api/WindowEntryFactory.cs index e172272..ce72d71 100644 --- a/GoToWindow.Api/WindowEntryFactory.cs +++ b/GoToWindow.Api/WindowEntryFactory.cs @@ -21,11 +21,16 @@ public static class WindowEntryFactory public static WindowEntry Create(IntPtr hWnd) { - var windowTitle = GetWindowTitle(hWnd); - uint processId; GetWindowThreadProcessId(hWnd, out processId); + return Create(hWnd, processId); + } + + public static WindowEntry Create(IntPtr hWnd, uint processId) + { + var windowTitle = GetWindowTitle(hWnd); + var iconHandle = WindowIcon.GetAppIcon(hWnd); var isVisible = !IsIconic(hWnd); diff --git a/GoToWindow.Api/WindowsListFactory.cs b/GoToWindow.Api/WindowsListFactory.cs index 52f0153..2420484 100644 --- a/GoToWindow.Api/WindowsListFactory.cs +++ b/GoToWindow.Api/WindowsListFactory.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Linq; using System.Runtime.InteropServices; using System.Text; using log4net; @@ -9,13 +10,15 @@ namespace GoToWindow.Api { /// /// Thanks to Tommy Carlier for how to get the list of windows: http://blog.tcx.be/2006/05/getting-list-of-all-open-windows.html + /// Thanks to taby for window eligibility: http://stackoverflow.com/questions/210504/enumerate-windows-like-alt-tab-does + /// Thanks to Hans Passant & Tim Beaudet for Windows 10 apps process name: http://stackoverflow.com/a/32513438/154480 /// public static class WindowsListFactory { private static readonly ILog Log = LogManager.GetLogger(typeof(WindowsListFactory).Assembly, "GoToWindow"); private const int MaxLastActivePopupIterations = 50; - delegate bool EnumWindowsProc(IntPtr hWnd, int lParam); + delegate bool EnumWindowsProc(IntPtr hWnd, int lParam); public enum GetAncestorFlags { @@ -42,43 +45,131 @@ public enum GetAncestorFlags [DllImport("user32.dll")] static extern IntPtr GetLastActivePopup(IntPtr hWnd); + [DllImport("user32.dll")] + [return: MarshalAs(UnmanagedType.Bool)] + static extern bool EnumChildWindows(IntPtr hwndParent, EnumWindowsProc lpEnumFunc, IntPtr lParam); + + [DllImport("user32.dll", SetLastError = true)] + static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId); + public static WindowsList Load() { var lShellWindow = GetShellWindow(); var windows = new List(); var currentProcessId = Process.GetCurrentProcess().Id; - EnumWindows((hWnd, lParam) => - { - if (!HWndEligibleForActivation(hWnd, lShellWindow)) - return true; + EnumWindows((hWnd, lParam) => + { + InspectPotentialWindow(hWnd, lShellWindow, currentProcessId, windows); + return true; + }, 0); - var className = GetClassName(hWnd); + return new WindowsList(windows); + } - if (!ClassEligibleForActivation(className)) - return true; + private static void InspectPotentialWindow(IntPtr hWnd, IntPtr lShellWindow, int currentProcessId, ICollection windows) + { + if (!HWndEligibleForActivation(hWnd, lShellWindow)) + return; - var window = WindowEntryFactory.Create(hWnd); + var className = GetClassName(hWnd); - if (window == null || window.ProcessId == currentProcessId || window.Title == null) - return true; + if (className == "ApplicationFrameWindow") + InspectWindows10AppWindow(hWnd, windows, className); + else + InspectNormalWindow(hWnd, currentProcessId, windows, className); + } - window.ProcessName = GetProcessName(window); + private static void InspectNormalWindow(IntPtr hWnd, int currentProcessId, ICollection windows, string className) + { + if (!ClassEligibleForActivation(className)) + return; -#if(DEBUG) - Log.DebugFormat("Found Window: {0} {1} Class: '{2}' Title: '{3}'", window.ProcessId, window.ProcessName, className, window.Title); -#endif + var window = WindowEntryFactory.Create(hWnd); - if (IsKnownException(window)) - return true; + if (IsKnownException(window)) + return; - windows.Add(window); + if (window.ProcessId == currentProcessId || window.Title == null) + return; - return true; - }, 0); + UpdateProcessName(window); + windows.Add(window); + LogDebugWindow("- Found normal window: ", window, className); + } - return new WindowsList(windows); - } + private static void InspectWindows10AppWindow(IntPtr hWnd, ICollection windows, string className) + { + Log.Debug("- Found Window 10 App"); + + var foundChildren = false; + uint processId; + GetWindowThreadProcessId(hWnd, out processId); + + EnumChildWindows(hWnd, (childHWnd, lparam) => + { + uint childProcessId; + GetWindowThreadProcessId(childHWnd, out childProcessId); + Log.Debug(" - Checking process: " + childProcessId); + if (processId != childProcessId) + { + var childClassName = GetClassName(hWnd); + + var window = WindowEntryFactory.Create(childHWnd, childProcessId); + if (window.Title != null) + { + UpdateProcessName(window); + + if (IsKnownWindows10Exception(window)) return true; + + //TODO: Windows 10 App Icons + // 1. Get the window.ProcessFileName + // 2. Look in the folder for AppManifest.xml + // 3. Look for Package/Properties/Logo + // 4. Load that file (should be a PNG) + + windows.Add(window); + foundChildren = true; + LogDebugWindow(" - Found window: ", window, childClassName); + } + } + return true; + }, IntPtr.Zero); + + if (!foundChildren) + { + var window = WindowEntryFactory.Create(hWnd, processId); + if (window.Title != null) + { + window.ProcessName = "Windows 10 App"; + windows.Add(window); + LogDebugWindow(" - No windows found: ", window, className); + } + } + } + + private static bool IsKnownWindows10Exception(WindowEntry window) + { + if (window.ProcessName == "MicrosoftEdge") + return true; + + if (window.ProcessName == "MicrosoftEdgeCP") + { + if (window.Title == "CoreInput") + return true; + + if (window.Title == "about:tabs") + return true; + } + + return false; + } + + [Conditional("DEBUG")] + private static void LogDebugWindow(string message, WindowEntry window, string className) + { + Log.Debug(message + window + ", Class: " + className); + } private static bool ClassEligibleForActivation(string className) { @@ -98,25 +189,26 @@ private static string GetClassName(IntPtr hWnd) return length == 0 ? null : classNameStringBuilder.ToString(); } - private static string GetProcessName(IWindowEntry window) + private static void UpdateProcessName(IWindowEntry window) { - var processName = WmiProcessWatcher.GetProcessName(window.ProcessId, () => window.ProcessName); - - if (processName == null) + window.ProcessName = WmiProcessWatcher.GetProcessName(window.ProcessId, () => { using (var process = Process.GetProcessById((int) window.ProcessId)) { - processName = process.ProcessName; + //TODO: Windows 10 App Icons + /* + try + { + window.ProcessFileName = process.MainModule.FileName; + } + catch (Exception ex) + { + Log.Warn("Could not get the executable name of the process " + window, ex); + } + * */ + return process.ProcessName; } - } - - if ("WWAHost".Equals(processName, StringComparison.OrdinalIgnoreCase)) - return "Windows Store App"; - - if ("ApplicationFrameHost".Equals(processName, StringComparison.OrdinalIgnoreCase)) - return "Windows Store App"; - - return processName; + }); } private static bool IsKnownException(IWindowEntry window) @@ -134,14 +226,13 @@ private static bool IsKnownException(IWindowEntry window) "MsgrIMEWindowClass", // Messenger "SysShadow", // Messenger "Button", // UI component, e.g. Start Menu button - "Windows.UI.Core.CoreWindow", // Windows 10 Store Apps when minimized + "Windows.UI.Core.CoreWindow", // Windows 10 Store Apps + "Frame Alternate Owner", // Edge "MultitaskingViewFrame", // The original Win + Tab view }; private static bool HWndEligibleForActivation(IntPtr hWnd, IntPtr lShellWindow) { - // http://stackoverflow.com/questions/210504/enumerate-windows-like-alt-tab-does - if (hWnd == lShellWindow) return false; diff --git a/GoToWindow/GoToWindow.csproj b/GoToWindow/GoToWindow.csproj index 48e1243..03b32df 100644 --- a/GoToWindow/GoToWindow.csproj +++ b/GoToWindow/GoToWindow.csproj @@ -25,6 +25,7 @@ DEBUG;TRACE prompt 4 + false AnyCPU