diff --git a/tools/FancyZones_zonable_tester/FancyZones_zonable_tester.sln b/tools/FancyZones_zonable_tester/FancyZones_zonable_tester.sln new file mode 100644 index 000000000..0f80f6a7c --- /dev/null +++ b/tools/FancyZones_zonable_tester/FancyZones_zonable_tester.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.29519.87 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "FancyZones_zonable_tester", "FancyZones_zonable_tester.vcxproj", "{784E01FE-25D9-4E7B-9F2D-30B71B66FB05}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {784E01FE-25D9-4E7B-9F2D-30B71B66FB05}.Debug|x64.ActiveCfg = Debug|x64 + {784E01FE-25D9-4E7B-9F2D-30B71B66FB05}.Debug|x64.Build.0 = Debug|x64 + {784E01FE-25D9-4E7B-9F2D-30B71B66FB05}.Debug|x86.ActiveCfg = Debug|Win32 + {784E01FE-25D9-4E7B-9F2D-30B71B66FB05}.Debug|x86.Build.0 = Debug|Win32 + {784E01FE-25D9-4E7B-9F2D-30B71B66FB05}.Release|x64.ActiveCfg = Release|x64 + {784E01FE-25D9-4E7B-9F2D-30B71B66FB05}.Release|x64.Build.0 = Release|x64 + {784E01FE-25D9-4E7B-9F2D-30B71B66FB05}.Release|x86.ActiveCfg = Release|Win32 + {784E01FE-25D9-4E7B-9F2D-30B71B66FB05}.Release|x86.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {FB4BCA3C-13A3-4CFD-AF4C-7A6AF4D73895} + EndGlobalSection +EndGlobal diff --git a/tools/FancyZones_zonable_tester/FancyZones_zonable_tester.vcxproj b/tools/FancyZones_zonable_tester/FancyZones_zonable_tester.vcxproj new file mode 100644 index 000000000..239b95ce9 --- /dev/null +++ b/tools/FancyZones_zonable_tester/FancyZones_zonable_tester.vcxproj @@ -0,0 +1,135 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 16.0 + {784E01FE-25D9-4E7B-9F2D-30B71B66FB05} + FancyZoneszonabletester + 10.0 + + + + Application + true + v142 + MultiByte + + + Application + false + v142 + true + MultiByte + + + Application + true + v142 + MultiByte + + + Application + false + v142 + true + MultiByte + + + + + + + + + + + + + + + + + + + + + + + Level3 + Disabled + true + true + stdcpplatest + + + Console + + + + + Level3 + Disabled + true + true + stdcpplatest + + + Console + + + + + Level3 + MaxSpeed + true + true + true + true + stdcpplatest + + + Console + true + true + + + + + Level3 + MaxSpeed + true + true + true + true + stdcpplatest + + + Console + true + true + + + + + + + + + \ No newline at end of file diff --git a/tools/FancyZones_zonable_tester/FancyZones_zonable_tester.vcxproj.filters b/tools/FancyZones_zonable_tester/FancyZones_zonable_tester.vcxproj.filters new file mode 100644 index 000000000..4327830c9 --- /dev/null +++ b/tools/FancyZones_zonable_tester/FancyZones_zonable_tester.vcxproj.filters @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/tools/FancyZones_zonable_tester/main.cpp b/tools/FancyZones_zonable_tester/main.cpp new file mode 100644 index 000000000..36084612e --- /dev/null +++ b/tools/FancyZones_zonable_tester/main.cpp @@ -0,0 +1,262 @@ +#include +#include +#include +#include + +std::wstring get_process_path(DWORD pid) noexcept +{ + auto process = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, TRUE, pid); + std::wstring name; + if (process != INVALID_HANDLE_VALUE) + { + name.resize(MAX_PATH); + DWORD name_length = static_cast(name.length()); + if (QueryFullProcessImageNameW(process, 0, (LPWSTR)name.data(), &name_length) == 0) + { + name_length = 0; + } + name.resize(name_length); + CloseHandle(process); + } + return name; +} + +std::wstring get_process_path(HWND window) noexcept +{ + const static std::wstring app_frame_host = L"ApplicationFrameHost.exe"; + DWORD pid{}; + GetWindowThreadProcessId(window, &pid); + auto name = get_process_path(pid); + if (name.length() >= app_frame_host.length() && + name.compare(name.length() - app_frame_host.length(), app_frame_host.length(), app_frame_host) == 0) + { + // It is a UWP app. We will enumarate the windows and look for one created + // by something with a different PID + DWORD new_pid = pid; + EnumChildWindows(window, [](HWND hwnd, LPARAM param) -> BOOL { + auto new_pid_ptr = reinterpret_cast(param); + DWORD pid; + GetWindowThreadProcessId(hwnd, &pid); + if (pid != *new_pid_ptr) + { + *new_pid_ptr = pid; + return FALSE; + } + else + { + return TRUE; + } + }, reinterpret_cast(&new_pid)); + // If we have a new pid, get the new name. + if (new_pid != pid) + { + return get_process_path(new_pid); + } + } + return name; +} + +std::string window_styles(LONG style) +{ + std::string result; + if (style == 0) + result = "WS_OVERLAPPED "; +#define TEST_STYLE(x) if ((style & x) == x) result += #x " "; + TEST_STYLE(WS_POPUP); + TEST_STYLE(WS_CHILD); + TEST_STYLE(WS_MINIMIZE); + TEST_STYLE(WS_VISIBLE); + TEST_STYLE(WS_DISABLED); + TEST_STYLE(WS_CLIPSIBLINGS); + TEST_STYLE(WS_CLIPCHILDREN); + TEST_STYLE(WS_MAXIMIZE); + TEST_STYLE(WS_CAPTION); + TEST_STYLE(WS_BORDER); + TEST_STYLE(WS_DLGFRAME); + TEST_STYLE(WS_VSCROLL); + TEST_STYLE(WS_HSCROLL); + TEST_STYLE(WS_SYSMENU); + TEST_STYLE(WS_THICKFRAME); + TEST_STYLE(WS_GROUP); + TEST_STYLE(WS_TABSTOP); + TEST_STYLE(WS_MINIMIZEBOX); + TEST_STYLE(WS_MAXIMIZEBOX); + TEST_STYLE(WS_ICONIC); + TEST_STYLE(WS_SIZEBOX); + TEST_STYLE(WS_TILEDWINDOW); + TEST_STYLE(WS_OVERLAPPEDWINDOW); + TEST_STYLE(WS_POPUPWINDOW); + TEST_STYLE(WS_CHILDWINDOW); +#undef TEST_STYLE + if (result.size() > 0) + result.pop_back(); + return result; +} + +std::string window_exstyles(LONG style) +{ + std::string result; +#define TEST_STYLE(x) if ((style & x) == x) result += #x " "; + TEST_STYLE(WS_EX_DLGMODALFRAME); + TEST_STYLE(WS_EX_NOPARENTNOTIFY); + TEST_STYLE(WS_EX_TOPMOST); + TEST_STYLE(WS_EX_ACCEPTFILES); + TEST_STYLE(WS_EX_TRANSPARENT); + TEST_STYLE(WS_EX_MDICHILD); + TEST_STYLE(WS_EX_TOOLWINDOW); + TEST_STYLE(WS_EX_WINDOWEDGE); + TEST_STYLE(WS_EX_CLIENTEDGE); + TEST_STYLE(WS_EX_CONTEXTHELP); + TEST_STYLE(WS_EX_RIGHT); + TEST_STYLE(WS_EX_LEFT); + TEST_STYLE(WS_EX_RTLREADING); + TEST_STYLE(WS_EX_LTRREADING); + TEST_STYLE(WS_EX_LEFTSCROLLBAR); + TEST_STYLE(WS_EX_RIGHTSCROLLBAR); + TEST_STYLE(WS_EX_CONTROLPARENT); + TEST_STYLE(WS_EX_STATICEDGE); + TEST_STYLE(WS_EX_APPWINDOW); + TEST_STYLE(WS_EX_OVERLAPPEDWINDOW); + TEST_STYLE(WS_EX_PALETTEWINDOW); + TEST_STYLE(WS_EX_LAYERED); + TEST_STYLE(WS_EX_NOINHERITLAYOUT); + TEST_STYLE(WS_EX_NOREDIRECTIONBITMAP); + TEST_STYLE(WS_EX_LAYOUTRTL); + TEST_STYLE(WS_EX_COMPOSITED); +#undef TEST_STYLE + if (result.size() > 0) + result.pop_back(); + return result; +} + + +bool is_system_window(HWND hwnd, const char* class_name) +{ + static auto system_classes = { "SysListView32", "WorkerW", "Shell_TrayWnd", "Shell_SecondaryTrayWnd", "Progman" }; + static auto system_hwnds = { GetDesktopWindow(), GetShellWindow() }; + for (auto system_hwnd : system_hwnds) + { + if (hwnd == system_hwnd) + { + return true; + } + } + for (const auto& system_class : system_classes) + { + if (strcmp(system_class, class_name) == 0) + { + return true; + } + } + return false; +} + +static bool no_visible_owner(HWND window) noexcept +{ + auto owner = GetWindow(window, GW_OWNER); + if (owner == nullptr) + { + return true; // There is no owner at all + } + if (!IsWindowVisible(owner)) + { + return true; // Owner is invisible + } + RECT rect; + if (!GetWindowRect(owner, &rect)) + { + return false; // Could not get the rect, return true (and filter out the window) just in case + } + // Return false (and allow the window to be zonable) if the owner window size is zero + // It is enough that the window is zero-sized in one dimension only. + return rect.top == rect.bottom || rect.left == rect.right; +} + +#define TEST_IF(condition) std::cout<<"\t" << #condition <<": " <<((condition) ? "true, window not zonable" : "false")<<"\n"; if (condition) rv = false; + +bool test_window(HWND window) +{ + std::cout << "\n"; + std::cout << "HWND: 0x" << window << "\n"; + DWORD pid; + GetWindowThreadProcessId(window, &pid); + std::cout << "PID: 0x" << std::hex << pid << "\n"; + std::cout << "FOREGROUND: 0x" << GetForegroundWindow() << "\n"; + + auto style = GetWindowLongPtr(window, GWL_STYLE); + auto exStyle = GetWindowLongPtr(window, GWL_EXSTYLE); + std::cout << "style: 0x" << std::hex << style << ": " << window_styles(style) << "\n"; + std::cout << "exStyle: 0x" << std::hex << exStyle << ": " << window_exstyles(exStyle) << " \n"; + std::array class_name; + GetClassNameA(window, class_name.data(), static_cast(class_name.size())); + std::cout << "Window class: '" << class_name.data() << "' equals:\n"; + auto process_path = get_process_path(window); + std::wcout<< L"Process path: " << process_path << L"\n"; + bool rv = true; + std::cout << "Testing if the window is zonable:\n"; + TEST_IF(GetAncestor(window, GA_ROOT) != window); + TEST_IF(!IsWindowVisible(window)); + if ((style & WS_POPUP) == WS_POPUP && + (style & WS_THICKFRAME) == 0 && + (style & WS_MINIMIZEBOX) == 0 && + (style & WS_MAXIMIZEBOX) == 0) + { + std::cout << "\t(style & WS_POPUP) && no frame nor max/min buttons: true, window not zonable\n"; + } + else + { + std::cout << "\t(style & WS_POPUP) && no frame nor max/min buttons: false\n"; + } + TEST_IF((style & WS_CHILD) == WS_CHILD); + TEST_IF((style & WS_DISABLED) == WS_DISABLED); + TEST_IF((exStyle & WS_EX_TOOLWINDOW) == WS_EX_TOOLWINDOW); + TEST_IF((exStyle & WS_EX_NOACTIVATE) == WS_EX_NOACTIVATE); + TEST_IF(is_system_window(window, class_name.data())); + if (strcmp(class_name.data(), "Windows.UI.Core.CoreWindow") == 0 && + process_path.ends_with(L"SearchUI.exe")) + { + std::cout << "\tapp is Cortana: true, window not zonable\n"; + } + else + { + std::cout << "\tapp is Cortana: false\n"; + } + TEST_IF(!no_visible_owner(window)); + return rv; +} + +LRESULT CALLBACK LowLevelMouseProc(int nCode, WPARAM wParam, LPARAM lParam) +{ + static HWND hwnd = nullptr; + if (nCode == HC_ACTION) + { + POINT point; + GetCursorPos(&point); + auto new_hwnd = WindowFromPoint(point); + if (hwnd != new_hwnd) { + hwnd = new_hwnd; + if (test_window(hwnd)) + { + std::cout << "Window is zonable\n"; + } + else + { + std::cout << "Window is NOT zonable\n"; + } + } + } + return CallNextHookEx(NULL, nCode, wParam, lParam); +} + +int main() +{ + HHOOK hhkLowLevelKybd = SetWindowsHookEx(WH_MOUSE_LL, LowLevelMouseProc, 0, 0); + MSG msg; + while (!GetMessage(&msg, NULL, NULL, NULL)) + { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + UnhookWindowsHookEx(hhkLowLevelKybd); + return(0); +}