add for_each_window and for_each_thread_window helpers (#403)

* fix RegistryTests (warning as errors breaking on signed/unsigned comparison)

* add for_each_window and for_each_thread_window helpers

* .

* simplify wrappers, add child window, can work w/mutable lambdas

* format

* only expose throwing versions when  WIL_ENABLE_EXCEPTIONS is set

* 'format'

* .

* .

* unused result_t

* Update include/wil/windowing.h

Co-authored-by: Duncan Horn <40036384+dunhor@users.noreply.github.com>

* Update include/wil/windowing.h

Co-authored-by: Duncan Horn <40036384+dunhor@users.noreply.github.com>

* pr fb

---------

Co-authored-by: Duncan Horn <40036384+dunhor@users.noreply.github.com>
This commit is contained in:
Alexander Sklar 2024-01-04 13:58:08 -08:00 коммит произвёл GitHub
Родитель ce27eed3a9
Коммит e2a0e70632
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
3 изменённых файлов: 396 добавлений и 0 удалений

164
include/wil/windowing.h Normal file
Просмотреть файл

@ -0,0 +1,164 @@
//*********************************************************
//
// Copyright (c) Microsoft. All rights reserved.
// This code is licensed under the MIT License.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
// ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
// TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
// PARTICULAR PURPOSE AND NONINFRINGEMENT.
//
//*********************************************************
#ifndef __WIL_WINDOWING_INCLUDED
#define __WIL_WINDOWING_INCLUDED
#include <WinUser.h>
#include <exception>
#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)
namespace wil
{
namespace details
{
template <typename T>
struct always_false : wistd::false_type
{
};
template <typename TEnumApi, typename TCallback>
void DoEnumWindowsNoThrow(TEnumApi&& enumApi, TCallback&& callback) noexcept
{
auto enumproc = [](HWND hwnd, LPARAM lParam) -> BOOL {
auto pCallback = reinterpret_cast<TCallback*>(lParam);
#ifdef __cpp_if_constexpr
using result_t = decltype((*pCallback)(hwnd));
if constexpr (wistd::is_void_v<result_t>)
{
(*pCallback)(hwnd);
return TRUE;
}
else if constexpr (wistd::is_same_v<result_t, HRESULT>)
{
// NB: this works for both HRESULT and NTSTATUS as both S_OK and ERROR_SUCCESS are 0
return (S_OK == (*pCallback)(hwnd)) ? TRUE : FALSE;
}
else if constexpr (std::is_same_v<result_t, bool>)
{
return (*pCallback)(hwnd) ? TRUE : FALSE;
}
else
{
static_assert(details::always_false<TCallback>::value, "Callback must return void, bool, or HRESULT");
}
#else
return (*pCallback)(hwnd);
#endif
};
enumApi(enumproc, reinterpret_cast<LPARAM>(&callback));
}
#ifdef WIL_ENABLE_EXCEPTIONS
template <typename TEnumApi, typename TCallback>
void DoEnumWindows(TEnumApi&& enumApi, TCallback&& callback)
{
struct
{
std::exception_ptr exception;
TCallback* pCallback;
} callbackData = {nullptr, &callback};
auto enumproc = [](HWND hwnd, LPARAM lParam) -> BOOL {
auto pCallbackData = reinterpret_cast<decltype(&callbackData)>(lParam);
try
{
auto pCallback = pCallbackData->pCallback;
#ifdef __cpp_if_constexpr
using result_t = decltype((*pCallback)(hwnd));
if constexpr (std::is_void_v<result_t>)
{
(*pCallback)(hwnd);
return TRUE;
}
else if constexpr (std::is_same_v<result_t, HRESULT>)
{
// NB: this works for both HRESULT and NTSTATUS as both S_OK and ERROR_SUCCESS are 0
return (S_OK == (*pCallback)(hwnd)) ? TRUE : FALSE;
}
else if constexpr (std::is_same_v<result_t, bool>)
{
return (*pCallback)(hwnd) ? TRUE : FALSE;
}
else
{
static_assert(details::always_false<TCallback>::value, "Callback must return void, bool, or HRESULT");
}
#else
return (*pCallback)(hwnd);
#endif
}
catch (...)
{
pCallbackData->exception = std::current_exception();
return FALSE;
}
};
enumApi(enumproc, reinterpret_cast<LPARAM>(&callbackData));
if (callbackData.exception)
{
std::rethrow_exception(callbackData.exception);
}
}
#endif
} // namespace details
template <typename TCallback>
void for_each_window_nothrow(TCallback&& callback) noexcept
{
details::DoEnumWindowsNoThrow(&EnumWindows, wistd::forward<TCallback>(callback));
}
template <typename TCallback>
void for_each_thread_window_nothrow(_In_ DWORD threadId, TCallback&& callback) noexcept
{
auto boundEnumThreadWindows = [threadId](WNDENUMPROC enumproc, LPARAM lParam) noexcept -> BOOL {
return EnumThreadWindows(threadId, enumproc, lParam);
};
details::DoEnumWindowsNoThrow(boundEnumThreadWindows, wistd::forward<TCallback>(callback));
}
template <typename TCallback>
void for_each_child_window_nothrow(_In_ HWND hwndParent, TCallback&& callback) noexcept
{
auto boundEnumChildWindows = [hwndParent](WNDENUMPROC enumproc, LPARAM lParam) noexcept -> BOOL {
return EnumChildWindows(hwndParent, enumproc, lParam);
};
details::DoEnumWindowsNoThrow(boundEnumChildWindows, wistd::forward<TCallback>(callback));
}
#ifdef WIL_ENABLE_EXCEPTIONS
template <typename TCallback>
void for_each_window(TCallback&& callback)
{
details::DoEnumWindows(&EnumWindows, wistd::forward<TCallback>(callback));
}
template <typename TCallback>
void for_each_thread_window(_In_ DWORD threadId, TCallback&& callback)
{
auto boundEnumThreadWindows = [threadId](WNDENUMPROC enumproc, LPARAM lParam) noexcept -> BOOL {
return EnumThreadWindows(threadId, enumproc, lParam);
};
details::DoEnumWindows(boundEnumThreadWindows, wistd::forward<TCallback>(callback));
}
template <typename TCallback>
void for_each_child_window(_In_ HWND hwndParent, TCallback&& callback)
{
auto boundEnumChildWindows = [hwndParent](WNDENUMPROC enumproc, LPARAM lParam) noexcept -> BOOL {
return EnumChildWindows(hwndParent, enumproc, lParam);
};
details::DoEnumWindows(boundEnumChildWindows, wistd::forward<TCallback>(callback));
}
#endif
} // namespace wil
#endif // WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)
#endif // __WIL_WINDOWING_INCLUDED

Просмотреть файл

@ -63,6 +63,7 @@ set(COMMON_SOURCES
${CMAKE_CURRENT_SOURCE_DIR}/SafeCastTests.cpp
${CMAKE_CURRENT_SOURCE_DIR}/TraceLoggingTests.cpp
${CMAKE_CURRENT_SOURCE_DIR}/TraceLoggingTests_PartB.cpp
${CMAKE_CURRENT_SOURCE_DIR}/WindowingTests.cpp
${CMAKE_CURRENT_SOURCE_DIR}/WistdTests.cpp
${CMAKE_CURRENT_SOURCE_DIR}/wiTest.cpp
${CMAKE_CURRENT_SOURCE_DIR}/../natvis/wil.natvis

231
tests/WindowingTests.cpp Normal file
Просмотреть файл

@ -0,0 +1,231 @@
#include "common.h"
#include <wil/windowing.h>
#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) && WIL_HAS_CXX_17
TEST_CASE("EnumWindows", "[windowing]")
{
// lambda can return a bool
wil::for_each_window_nothrow([](HWND hwnd) {
REQUIRE(IsWindow(hwnd));
return true;
});
// or not return anything (we'll stop if we get an exception)
wil::for_each_window_nothrow([](HWND hwnd) {
REQUIRE(IsWindow(hwnd));
});
// or return an HRESULT and we'll stop if it's not S_OK
wil::for_each_window_nothrow([](HWND hwnd) {
REQUIRE(IsWindow(hwnd));
return S_FALSE;
});
// mutable lambda
std::vector<HWND> windows;
wil::for_each_window_nothrow([&windows](HWND hwnd) {
windows.push_back(hwnd);
});
wil::for_each_window_nothrow([windows = std::vector<HWND>{}](HWND hwnd) mutable {
windows.push_back(hwnd);
});
// With captures
const auto pid = GetCurrentProcessId();
wil::for_each_window_nothrow([pid](HWND hwnd) {
if (pid == GetWindowThreadProcessId(hwnd, nullptr))
{
REQUIRE(IsWindow(hwnd));
};
return true;
});
#ifdef WIL_ENABLE_EXCEPTIONS
// throwing version
wil::for_each_window([](HWND hwnd) {
REQUIRE(IsWindow(hwnd));
return true;
});
wil::for_each_window([](HWND hwnd) {
REQUIRE(IsWindow(hwnd));
});
wil::for_each_window([](HWND hwnd) {
REQUIRE(IsWindow(hwnd));
return S_FALSE;
});
windows.clear();
wil::for_each_window([&windows](HWND hwnd) {
windows.push_back(hwnd);
});
wil::for_each_window([windows = std::vector<HWND>{}](HWND hwnd) mutable {
windows.push_back(hwnd);
});
REQUIRE_THROWS_AS(
wil::for_each_window([](HWND) {
throw std::exception();
}),
std::exception);
#endif
}
TEST_CASE("EnumThreadWindows", "[windowing]")
{
// find any window
DWORD thread_id{};
wil::for_each_window_nothrow([&thread_id](HWND hwnd) {
if (IsWindow(hwnd) && IsWindowVisible(hwnd))
{
thread_id = GetWindowThreadProcessId(hwnd, nullptr);
return false;
}
return true;
});
// nothrow version
{
wil::for_each_thread_window_nothrow(thread_id, [](HWND hwnd) {
REQUIRE(IsWindow(hwnd));
return true;
});
wil::for_each_thread_window_nothrow(thread_id, [](HWND hwnd) {
REQUIRE(IsWindow(hwnd));
});
// lambda can also return an HRESULT and we'll stop if it's not S_OK
wil::for_each_thread_window_nothrow(thread_id, [](HWND hwnd) {
REQUIRE(IsWindow(hwnd));
return S_FALSE;
});
// mutable lambda
std::vector<HWND> windows;
wil::for_each_thread_window_nothrow(thread_id, [&windows, thread_id](HWND hwnd) {
REQUIRE(GetWindowThreadProcessId(hwnd, nullptr) == thread_id);
windows.push_back(hwnd);
});
wil::for_each_thread_window_nothrow(thread_id, [windows = std::vector<HWND>{}](HWND hwnd) mutable {
windows.push_back(hwnd);
});
}
#ifdef WIL_ENABLE_EXCEPTIONS
// throwing version
{
wil::for_each_thread_window(thread_id, [](HWND hwnd) {
REQUIRE(IsWindow(hwnd));
return true;
});
wil::for_each_thread_window(thread_id, [](HWND hwnd) {
REQUIRE(IsWindow(hwnd));
});
// lambda can also return an HRESULT and we'll stop if it's not S_OK
wil::for_each_thread_window(thread_id, [](HWND hwnd) {
REQUIRE(IsWindow(hwnd));
return S_FALSE;
});
// mutable lambda
std::vector<HWND> windows;
wil::for_each_thread_window(thread_id, [&windows, thread_id](HWND hwnd) {
REQUIRE(GetWindowThreadProcessId(hwnd, nullptr) == thread_id);
windows.push_back(hwnd);
});
wil::for_each_thread_window(thread_id, [windows = std::vector<HWND>{}](HWND hwnd) mutable {
windows.push_back(hwnd);
});
// with exceptions
REQUIRE_THROWS_AS(
wil::for_each_thread_window(
thread_id,
[](HWND) {
throw std::exception();
}),
std::exception);
}
#endif
}
TEST_CASE("EnumChildWindows", "[windowing]")
{
// find any window
HWND parent{};
wil::for_each_window_nothrow([&parent](HWND hwnd) {
if (IsWindow(hwnd) && IsWindowVisible(hwnd))
{
parent = hwnd;
return false;
}
return true;
});
// nothrow version
{
wil::for_each_child_window_nothrow(parent, [](HWND hwnd) {
REQUIRE(IsWindow(hwnd));
return true;
});
wil::for_each_child_window_nothrow(parent, [](HWND hwnd) {
REQUIRE(IsWindow(hwnd));
});
// lambda can also return an HRESULT and we'll stop if it's not S_OK
wil::for_each_child_window_nothrow(parent, [](HWND hwnd) {
REQUIRE(IsWindow(hwnd));
return S_FALSE;
});
// mutable lambda
std::vector<HWND> windows;
wil::for_each_child_window_nothrow(parent, [&windows](HWND hwnd) {
windows.push_back(hwnd);
});
wil::for_each_child_window_nothrow(parent, [windows = std::vector<HWND>{}](HWND hwnd) mutable {
windows.push_back(hwnd);
});
}
#ifdef WIL_ENABLE_EXCEPTIONS
// throwing version
{
wil::for_each_child_window(parent, [](HWND hwnd) {
REQUIRE(IsWindow(hwnd));
return true;
});
wil::for_each_child_window(parent, [](HWND hwnd) {
REQUIRE(IsWindow(hwnd));
});
// lambda can also return an HRESULT and we'll stop if it's not S_OK
wil::for_each_child_window(parent, [](HWND hwnd) {
REQUIRE(IsWindow(hwnd));
return S_FALSE;
});
// mutable lambda
std::vector<HWND> windows;
wil::for_each_child_window(parent, [&windows](HWND hwnd) {
windows.push_back(hwnd);
});
wil::for_each_child_window(parent, [windows = std::vector<HWND>{}](HWND hwnd) mutable {
windows.push_back(hwnd);
});
// with exceptions
REQUIRE_THROWS_AS(
wil::for_each_child_window(
parent,
[](HWND) {
throw std::exception();
}),
std::exception);
}
#endif
}
#endif