view dep/animia/src/win/win32.cc @ 152:8700806c2cc2

dep/animia: awesome new breaking changes! I'm so tired
author Paper <mrpapersonic@gmail.com>
date Wed, 15 Nov 2023 02:34:59 -0500
parents
children bd439dd6ffc5
line wrap: on
line source

#include "animia/win/win32.h"
#include "animia/util/win32.h"
#include "animia/win.h"
#include "animia.h"

#include <set>
#include <string>

#include <windows.h>

namespace animia::internal::win32 {

static std::wstring GetWindowClassName(HWND hwnd) {
	// The maximum size for lpszClassName, according to the documentation of
	// WNDCLASSEX structure
	constexpr int kMaxSize = 256;

	std::wstring buffer(kMaxSize, L'\0');
	const auto size = ::GetClassNameW(hwnd, &buffer.front(), buffer.length());
	return buffer;
}

static std::wstring GetWindowText(HWND hwnd) {
	const int size = ::GetWindowTextLengthW(hwnd);

	std::wstring buffer(size, L'\0');
	::GetWindowTextW(hwnd, &buffer.front(), buffer.length());
	return buffer;
}

static DWORD GetWindowProcessId(HWND hwnd) {
	DWORD process_id = 0;
	::GetWindowThreadProcessId(hwnd, &process_id);
	return process_id;
}

static std::wstring GetProcessPath(DWORD process_id) {
	// If we try to open a SYSTEM process, this function fails and the last error
	// code is ERROR_ACCESS_DENIED.
	//
	// Note that if we requested PROCESS_QUERY_INFORMATION access right instead
	// of PROCESS_QUERY_LIMITED_INFORMATION, this function would fail when used
	// to open an elevated process.
	Handle process_handle(::OpenProcess(
			PROCESS_QUERY_LIMITED_INFORMATION, FALSE, process_id));

	if (!process_handle)
		return std::wstring();

	std::wstring buffer(MAX_PATH, L'\0');
	DWORD buf_size = buffer.length();

	// Note that this function requires Windows Vista or above. You may use
	// GetProcessImageFileName or GetModuleFileNameEx on earlier versions.
	if (!::QueryFullProcessImageNameW(process_handle.get(), 0, &buffer.front(), &buf_size))
		return std::wstring();

	return buffer.substr(0, buf_size + 1);
}

////////////////////////////////////////////////////////////////////////////////

static bool VerifyWindowStyle(HWND hwnd) {
	const auto window_style = ::GetWindowLong(hwnd, GWL_STYLE);
	const auto window_ex_style = ::GetWindowLong(hwnd, GWL_EXSTYLE);

	auto has_style = [&window_style](DWORD style) {
		return (window_style & style) != 0;
	};
	auto has_ex_style = [&window_ex_style](DWORD ex_style) {
		return (window_ex_style & ex_style) != 0;
	};

	// Toolbars, tooltips and similar topmost windows
	if (has_style(WS_POPUP) && has_ex_style(WS_EX_TOOLWINDOW))
		return false;
	if (has_ex_style(WS_EX_TOPMOST) && has_ex_style(WS_EX_TOOLWINDOW))
		return false;

	return true;
}

static bool VerifyClassName(const std::wstring& name) {
	static const std::set<std::wstring> invalid_names = {
		// System classes
		L"#32770",         // Dialog box
		L"CabinetWClass",  // Windows Explorer
		L"ComboLBox",
		L"DDEMLEvent",
		L"DDEMLMom",
		L"DirectUIHWND",
		L"GDI+ Hook Window Class",
		L"IME",
		L"Internet Explorer_Hidden",
		L"MSCTFIME UI",
		L"tooltips_class32",
	};

	return !name.empty() && !invalid_names.count(name);
}

static bool VerifyProcessPath(const std::wstring& path) {
	return !path.empty() && !IsSystemDirectory(path);
}

static bool VerifyProcessFileName(const std::wstring& name) {
	static const std::set<std::wstring> invalid_names = {
		// System files
		L"explorer",    // Windows Explorer
		L"taskeng",     // Task Scheduler Engine
		L"taskhost",    // Host Process for Windows Tasks
		L"taskhostex",  // Host Process for Windows Tasks
		L"Taskmgr",     // Task Manager
	};

	return !name.empty() && !invalid_names.count(name);
}

////////////////////////////////////////////////////////////////////////////////

static BOOL CALLBACK EnumWindowsProc(HWND hwnd, LPARAM param) {
	if (!::IsWindowVisible(hwnd))
		return TRUE;

	if (!VerifyWindowStyle(hwnd))
		return TRUE;

	Window window;
	window.id = static_cast<unsigned int>(reinterpret_cast<ULONG_PTR>(hwnd));
	window.text = ToUtf8String(GetWindowText(hwnd));

	{
		std::wstring class_name = GetWindowClassName(hwnd);
		window.class_name = ToUtf8String(class_name);
		if (!VerifyClassName(class_name))
			return TRUE;
	}

	Process process;
	process.pid = GetWindowProcessId(hwnd);

	const auto path = GetProcessPath(process.pid);
	if (!VerifyProcessPath(path))
		return TRUE;

	{
		std::wstring name = GetFileNameWithoutExtension(GetFileNameFromPath(path));
		process.name = ToUtf8String(name);
		if (!VerifyProcessFileName(name))
			return TRUE;
	}

	auto& window_proc = *reinterpret_cast<window_proc_t*>(param);
	if (!window_proc(process, window))
		return FALSE;

	return TRUE;
}

bool Win32WinTools::EnumerateWindows(window_proc_t window_proc) {
	if (!window_proc)
		return false;

	const auto param = reinterpret_cast<LPARAM>(&window_proc);

	// Note that EnumWindows enumerates only top-level windows of desktop apps
	// (as opposed to UWP apps) on Windows 8 and above.
	return ::EnumWindows(EnumWindowsProc, param) != FALSE;
}

} // namespace animia::win::detail