view dep/animia/src/win/win32.cc @ 187:9613d72b097e

*: multiple performance improvements like marking `static const` when it makes sense... date: change old stupid heap-based method to a structure which should make copying the thing actually make a copy. also many performance-based changes, like removing the std::tie dependency and forward-declaring nlohmann json *: replace every instance of QString::fromUtf8 to Strings::ToQString. the main difference is that our function will always convert exactly what is in the string, while some other times it would only convert up to the nearest NUL byte
author Paper <mrpapersonic@gmail.com>
date Wed, 06 Dec 2023 13:43:54 -0500
parents cdf79282d647
children 0fc126d52de4
line wrap: on
line source

#include "animia/win/win32.h"
#include "animia.h"
#include "animia/util/win32.h"
#include "animia/win.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());
	/* for some reason GetClassName returns the actual size of the buffer *with* the
	   terminating NULL byte */
	buffer.resize(size);
	return buffer;
}

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

	std::wstring buffer(estimated_size + 1, L'\0');
	const auto size = ::GetWindowTextW(hwnd, &buffer.front(), buffer.length());
	/* GetWindowTextLength docs:
	   "Under certain conditions, the GetWindowTextLength function may return a value
	    that is larger than the actual length of the text." */
	buffer.resize(size);
	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();

	buffer.resize(buf_size);
	return buffer;
}

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

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::internal::win32