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