检测窗口是否最大化兼容 Win10/11

检测窗口是否最大化(窗口覆盖或独占全屏)兼容 Win10/11

问题描述

在 Win10/11 上有很多 UWP 进程,检测窗口是否最大化将迎来新的挑战。这些窗口以其不能够使用 Win32 的 IsWindowVisible 获取窗口可见性为特征。此时,必须使用 DWM API 来判断窗口的可见性状态。

代码实现

下面的代码实现了一个工具类检测当前桌面是否被覆盖,以及覆盖窗口的信息。(此代码参考 CustomDesktop 开源项目,并做了预览桌面时的逃逸规则)

“Singleton.h” :

#pragma once

namespace cd
{
	template<class T>
	class Singleton
	{
	protected:
		Singleton() = default;
		virtual ~Singleton() = default;

	public:
		static T& GetInstance()
		{
			static T s_instance;
			return s_instance;
		}
	};

#define DECL_SINGLETON(T) friend class Singleton<T>
#define DECL_SINGLETON_DEFAULT(T) \
	DECL_SINGLETON(T); \
	private: \
	T() = default; \
	~T() = default
}

“CheckCovered.h”:

#pragma once
#include "Singleton.h"
#include <thread>
#include <memory>


namespace cd
{
	// 检测桌面是否被遮挡了
	class CheckCovered final : public Singleton<CheckCovered>
	{
		DECL_SINGLETON(CheckCovered);
	public:
		bool IsReady() { return m_runThreadFlag; }

		bool Init();
		bool Uninit();

	private:
		CheckCovered();
		~CheckCovered();


		std::unique_ptr<std::thread> m_thread;
		bool m_runThreadFlag = true;
		bool m_isCovered = false;
		HWND m_coveredByHwnd = NULL;


		void CheckCoveredThread();
		bool IsDesktopCovered();
	};
}

“CheckCovered.cpp” :

#include "CheckCovered.h"

#ifdef _WIN64
#include <Dwmapi.h>
#endif


namespace cd
{
	CheckCovered::CheckCovered()
	{
		Init();
	}

	CheckCovered::~CheckCovered()
	{
		Uninit();
	}
	
	bool CheckCovered::Init()
	{
		m_runThreadFlag = true;
		/* 在主线程中执行函数,可以用来做 dllmain 中不能完成的初始化,通过自定义消息实现
	       // MYDLL_API void WINAPI ExecInMainThread(std::function<void()> function);
	    
	       CD_API void WINAPI ExecInMainThread(std::function<void()> function)
	       {
		       PostMessage(g_global.m_fileListWnd, WM_EXEC_FUNCTION, reinterpret_cast<WPARAM>(
			     new decltype(function)(std::move(function))), NULL);
	       }
	    */
		ExecInMainThread([this]{ m_thread = std::make_unique<std::thread>(&CheckCovered::CheckCoveredThread, this); });
		return true;
	}

	bool CheckCovered::Uninit()
	{
		m_runThreadFlag = false;
		if (m_thread != nullptr && m_thread->joinable())
			m_thread->join();
		m_thread = nullptr;
		return true;
	}


	void CheckCovered::CheckCoveredThread()
	{
		while (m_runThreadFlag)
		{
			if (IsDesktopCovered())
			{
				if (!m_isCovered)
				{
					m_isCovered = true;
					//ExecInMainThread([]{ g_desktopCoveredEvent(); });

#ifdef _DEBUG
					WCHAR windowName[100], className[100];
					GetWindowTextW(m_coveredByHwnd, windowName, _countof(windowName));
					GetClassNameW(m_coveredByHwnd, className, _countof(className));
					_RPTFW2(_CRT_WARN, L"桌面被 %s (%s) 遮挡\n", windowName, className);
#endif
				}
			}
			else
			{
				if (m_isCovered)
				{
					m_isCovered = false;
					//ExecInMainThread([]{ g_desktopUncoveredEvent(); });

					_RPTF0(_CRT_WARN, "桌面从被遮挡恢复\n");
				}
			}

			for (int i = 0; i < 10; i++)
			{
				if (!m_runThreadFlag)
					break;
				Sleep(100);
			}
		}
	}

	bool CheckCovered::IsDesktopCovered()
	{
		m_coveredByHwnd = NULL;

		// 对于 D3D 独占全屏的程序,不能用 IsZoomed 判断全屏
		// TODO:兼容多屏幕
		int screenWidth = GetSystemMetrics(SM_CXSCREEN);
		int screenHeight = GetSystemMetrics(SM_CYSCREEN);
		HWND hwnd = GetForegroundWindow();
		if (hwnd != GLOBAL_YOUR_WINDOW) // GLOBAL_YOUR_WINDOW 是你要检测是否被全屏幕覆盖的窗口
		{
			RECT rect;
			GetWindowRect(hwnd, &rect);
			if (rect.left == 0 && rect.top == 0
				&& rect.right == screenWidth && rect.bottom == screenHeight)
			{
				WCHAR wsClassName[MAX_PATH] = { 0 };
				GetClassNameW(hwnd, wsClassName, MAX_PATH);
				if (wcsstr(wsClassName, L"LivePreview") == NULL) {  // 预览桌面窗口出现时,恢复动画播放
					m_coveredByHwnd = hwnd;
					return true;
				}
				else {
					m_coveredByHwnd = nullptr;
					return false;
				}
				
			}
		}

		EnumWindows([](HWND hwnd, LPARAM pCoveredByHwnd)->BOOL {
#ifdef _WIN64
			// 对于 win10 app,不能用 IsWindowVisible 判断是否可见
			DWORD cloaked = 0;
			DwmGetWindowAttribute(hwnd, DWMWA_CLOAKED, &cloaked, sizeof(cloaked));
			if (cloaked != 0)
				return TRUE;
#endif

			// 有最大化的窗口而且可见则被遮挡(最小化也是不可见)
			if (IsZoomed(hwnd) && IsWindowVisible(hwnd))
			{
				*(HWND*)pCoveredByHwnd = hwnd;
				return FALSE;
			}
			return TRUE;
		}, (LPARAM)&m_coveredByHwnd);

		if (m_coveredByHwnd != nullptr) {
			WCHAR wsClassName[MAX_PATH] = { 0 };
			GetClassNameW(m_coveredByHwnd, wsClassName, MAX_PATH);
			if (wcsstr(wsClassName, L"LivePreview") == NULL) {  // 预览桌面窗口出现时,恢复动画播放
				return true;
			}
			else {
				m_coveredByHwnd = nullptr;
				return false;
			}
		}
		
		return false;
	}
}

这里有两个点要说一下,一是此代码需要完善多桌面的情况,二是此代码考虑了预览桌面时候会产生一个窗口覆盖全屏的情况(LivePreview),为了避免检测失效,应该排除此时的覆盖情况(代码中也已经初步实现了)。


本文发布于:2024.06.10.

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

涟幽516

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值