QQ截图时窗口自动识别的原理(WindowFromPoint, ChildWindowFromPoint, ChildWindowFromPointEx,RealChildWindowFromPoint)
新版的QQ在截图时加入了窗口自动识别的功能,能根据鼠标的位置自动画出下面窗口的轮廓。今天有人在论坛上问起这个问题,下面我们来探讨这个功能的实现原理。
首先我们要明白截图软件的基本原理,截图时实际上是新建了一个全屏窗口,然后将当前桌面的截图画在上面,大部分截图软件,包括QQ都是这么做的。根据鼠标位置获取下层窗口,有好几个类似的API可以用(WindowFromPoint, ChildWindowFromPoint, ChildWindowFromPointEx,RealChildWindowFromPoint)。
这里我们重点关注ChildWindowFromPointEx,因为我们知道截图时有个全屏窗口覆盖在上面,通过鼠标位置去取得窗口,肯定首先取到的是这个全屏窗口,所以我们要把这个窗口过滤掉,而只有ChildWindowFromPointEx这个API有窗口过滤功能。
HWND ChildWindowFromPointEx( HWND hwndParent, POINT pt, UINT uFlags );
Parameters
- hwndParent
- [in] Handle to the parent window.
- pt
- [in] Specifies a POINT structure that defines the client coordinates (relative to hwndParent) of the point to be checked.
- uFlags
- [in] Specifies which child windows to skip. This parameter can be one or more of the following values.
- CWP_ALL
- Does not skip any child windows
- CWP_SKIPINVISIBLE
- Skips invisible child windows
- CWP_SKIPDISABLED
- Skips disabled child windows
- CWP_SKIPTRANSPARENT
- Skips transparent child windows
所以我们有理由相信QQ的全屏窗口用了WS_EX_LAYERED属性,然后QQ通过调用ChildWindowFromPointEx(hWndDesktop,ptCursor, CWP_SKIPINVISIBLE|CWP_SKIPTRANSPARENT), 这样就可以过滤掉不可见的和Layered窗口,然后通过递归调用该API,就可以获取里面的子窗口了。
为了验证我们猜想,怎么可以自己建立一个Layered Window,然后用QQ截图,可以看到QQ是无法识别该窗口的。
另外我们可以在启动QQ截图后,通过Windows键激活任务栏,然后改变通过任务栏最小化或是关闭某个包含在截图内的窗口,再继续截图就会发现QQ没法识别了。这也说明了QQ截图是实时通过ChildWindowFromPointEx来获取下层窗口的。这也算是QQ截图的一个Bug。
很多截图软件却没有上述问题,我想他们应该是在开始截图时保存了桌面上所有窗口的层次关系和所在区域,后面用的都是当时保存的信息来识别的,这样即使后面下面的窗口变化了,识别也不会受到影响。
另外,有些截图软件能够识别到比窗口粒度更小的元素,比如Toolbar控件上的每个Item,他们用的应该是MSAA(Microsoft Active Accessibility),标准控件一般都支持该接口。
看到有些人对通过枚举方式识别窗口的代码感兴趣 , 下面是我的代码:
{
public:
static BOOL IsFilterWindow(HWND hWnd)
{
_ASSERTE(hWnd != NULL);
DWORD dwProcessID = GetCurrentProcessId();
if(hWnd != NULL && IsWindow(hWnd))
{
DWORD dwWinProcessId(0);
GetWindowThreadProcessId(hWnd, &dwWinProcessId);
if(dwProcessID == dwWinProcessId)
{
return TRUE;
}
}
return FALSE;
}
static DWORD GetIncludeStyle()
{
return WS_VISIBLE;
}
static DWORD GetExcludeStyleEx()
{
return WS_EX_TRANSPARENT;
}
static BOOL IsTargetPopupWindow()
{
return FALSE;
}
};
class CSCWinInfo
{
public:
HWND m_hWnd;
CRect m_rtWin; //window rect
INT m_nLevel; // 1 - pop up window ; 2
N - child window
};
//pop up win 1 (level 1)
.. first Z order
// child11 (level 2)
// child12 (level 2)
// chilld121 (level 3)
// chilld122 (level 3)
// 
// child3 (level 2)
//pop up win2
// child21 (level 2)
// child21 (level 2)
//
.
//
.
//pop up winN 




. last Z order
template<typename CWinFilterTraits = CSCWinFilter>
class CSCWinSpy: public CHYSingleton<CSCWinSpy>
{
public:
BOOL SnapshotAllWinRect()
{
ClearData();
// cache current window Z order when call this function
EnumWindows(EnumWindowsSnapshotProc, 1);
return TRUE;
}
//get from current Z order of desktop
HWND GetHWNDByPoint(CPoint pt)
{
m_hWndTarget = NULL;
EnumWindows(EnumWindowsRealTimeProc, MAKELPARAM(pt.x, pt.y));
return m_hWndTarget;
}
CRect GetWinRectByPoint(CPoint ptHit, BOOL bGetInRealTime = FALSE)
{
CRect rtRect(0, 0, 0, 0);
if(bGetInRealTime) //get from current Z order
{
HWND hWndTarget = GetHWNDByPoint(ptHit);
if(hWndTarget != NULL )
{
GetWindowRect(hWndTarget, &rtRect);
}
}
else //get from snapshot cache
{
GetRectByPointFromSnapshot(ptHit, rtRect);
}
return rtRect;
}
protected:
static BOOL CALLBACK EnumWindowsRealTimeProc(HWND hwnd, LPARAM lParam)
{
if(!PtInWinRect(hwnd, CPoint(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)))) return TRUE;
if(ShouldWinBeFiltered(hwnd)) return TRUE;
m_hWndTarget = hwnd;
if(CWinFilterTraits::IsTargetPopupWindow()) return FALSE; //this is the target window, exit search
EnumChildWindows(hwnd, EnumChildRealTimeProc, lParam);
return FALSE;
}
static BOOL CALLBACK EnumChildRealTimeProc(HWND hwnd, LPARAM lParam)
{
if(!PtInWinRect(hwnd, CPoint(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)))) return TRUE;
if(ShouldWinBeFiltered(hwnd)) return TRUE;
m_hWndTarget = hwnd;
EnumChildWindows(hwnd, EnumChildRealTimeProc, lParam);
return FALSE;
}
protected:
static BOOL CALLBACK EnumWindowsSnapshotProc(HWND hwnd, LPARAM lParam)
{
INT nLevel = lParam;
if(ShouldWinBeFiltered(hwnd)) return TRUE;
SaveSnapshotWindow(hwnd, nLevel);
if(!CWinFilterTraits::IsTargetPopupWindow())
{
++nLevel;
EnumChildWindows(hwnd, EnumChildSnapshotProc, nLevel);
}
return TRUE;
}
static BOOL CALLBACK EnumChildSnapshotProc(HWND hwnd, LPARAM lParam)
{
INT nLevel = lParam;
if(ShouldWinBeFiltered(hwnd)) return TRUE;
SaveSnapshotWindow(hwnd, nLevel);
++nLevel;
EnumChildWindows(hwnd, EnumChildSnapshotProc, nLevel);
return TRUE;
}
protected:
static BOOL PtInWinRect(HWND hWnd, CPoint pt)
{
CRect rtWin(0, 0, 0, 0);
GetWindowRect(hWnd, &rtWin);
return PtInRect(&rtWin, pt);
}
static BOOL ShouldWinBeFiltered(HWND hWnd)
{
if(CWinFilterTraits::IsFilterWindow(hWnd)) return TRUE;
DWORD dwStyle = GetWindowLong(hWnd, GWL_STYLE);
DWORD dwStyleMust = CWinFilterTraits::GetIncludeStyle();
if((dwStyle & dwStyleMust) != dwStyleMust) return TRUE;
DWORD dwStyleEx = GetWindowLong(hWnd, GWL_EXSTYLE);
DWORD dwStyleMustNot = CWinFilterTraits::GetExcludeStyleEx();
if((dwStyleMustNot & dwStyleEx) != 0) return TRUE;
return FALSE;
}
//find the first window that level is biggest
static BOOL GetRectByPointFromSnapshot(CPoint ptHit, CRect& rtRet)
{
int nCount = m_arSnapshot.size();
_ASSERTE(nCount > 0);
CSCWinInfo* pInfo = NULL;
CSCWinInfo* pTarget = NULL;
for(int i=0; i<nCount; ++i)
{
pInfo = m_arSnapshot[i];
_ASSERTE(pInfo != NULL);
//target window is found
//and level is not increasing,
//that is checking its sibling or parent window, exit search
if(pTarget != NULL
&& pInfo->m_nLevel <= pTarget->m_nLevel)
{
break;
}
if(PtInRect(&pInfo->m_rtWin, ptHit))
{
if(pTarget == NULL)
{
pTarget = pInfo;
}
else
{
if( pInfo->m_nLevel > pTarget->m_nLevel)
{
pTarget = pInfo;
}
}
}
}
if(pTarget != NULL)
{
#ifdef _DEBUG
if(pTarget != NULL)
{
HWND hWnd = pTarget->m_hWnd;
TCHAR szText[128] = {0};
_sntprintf(szText, 127, _T("GetRectByPointFromSnapshot: pt(%d, %d), hWnd(%x)"),
ptHit.x, ptHit.y, (UINT)(pInfo->m_hWnd));
OutputDebugString(szText);
}
#endif
rtRet.CopyRect(&pTarget->m_rtWin);
return TRUE;
}
return FALSE;
}
static VOID SaveSnapshotWindow(HWND hWnd, INT nLevel)
{
_ASSERTE(hWnd != NULL && IsWindow(hWnd));
CRect rtWin(0, 0, 0, 0);
GetWindowRect(hWnd, &rtWin);
if(rtWin.IsRectEmpty()) return;
CSCWinInfo* pInfo = new CSCWinInfo;
if(pInfo == NULL) return;
pInfo->m_hWnd = hWnd;
pInfo->m_nLevel = nLevel;
pInfo->m_rtWin = rtWin;
m_arSnapshot.push_back(pInfo);
}
static VOID ClearData()
{
int nCount = m_arSnapshot.size();
for(int i=0; i<nCount; ++i)
{
delete m_arSnapshot[i];
}
m_arSnapshot.clear();
}
protected:
friend class CHYSingleton<CSCWinSpy>;
CSCWinSpy() { NULL; }
~CSCWinSpy() { ClearData(); }
static HWND m_hWndTarget;
static std::vector<CSCWinInfo*> m_arSnapshot;
};
template<typename T> HWND CSCWinSpy<T>::m_hWndTarget = NULL;
template<typename T> std::vector<CSCWinInfo*> CSCWinSpy<T>::m_arSnapshot;
这样使用, 在截图开始时保存所有桌面窗口层次:
然后就可以这样查询某个位置的最上层窗口了:
http://www.cppblog.com/weiym/archive/2012/05/06/173845.html
QQ截图时窗口自动识别的原理(WindowFromPoint, ChildWindowFromPoint, ChildWindowFromPointEx,RealChildWindowFromPoint)的更多相关文章
- C#软件开发实例.私人订制自己的屏幕截图工具(九)使用自己定义光标,QQ截图时的光标
版权声明:本文为 testcs_dn(微wx笑) 原创文章,非商用自由转载-保持署名-注明出处,谢谢. https://blog.csdn.net/testcs_dn/article/details/ ...
- qq截图原理
屏幕截图实现的大体思想是:发起截图时,将当前窗口的图像保存到内存中,然后弹出一个置顶的全屏窗口,将保存的桌面图片绘制到这个全屏窗口上:初始时绘制的是灰化的桌面图像,选择截图区域后,则将选中的区域绘制成 ...
- 【转】Android仿QQ截图应用测试
使用过QQ的同学应该都用过QQ截图,Ctrl+Alt+A进入截图操作,通过拉伸,移动高亮区域的框体可以快速截取我们需要的图片.在android应用中,我们也经常需要截图操作,以下实现了一个类似QQ截图 ...
- WPF C#截图功能 仿qq截图
原文:WPF C#截图功能 仿qq截图 先上效果图 源码下载地址:http://download.csdn.net/detail/candyvoice/9788099 描述:启动程序,点击窗口butt ...
- Qt 模仿QQ截图 动态吸附直线
最近在学Qt.学东西怎么能不动手. 就写了些小程序.看QQ截图能够动态吸附直线的功能挺有意思,所以就模仿了一个. 先上效果图 界面很简单..呵呵 移动鼠标,会把鼠标所在最小矩形选中.把没有选中的地方给 ...
- cnblog可以直接黏贴qq截图,但最好不要偷懒
秋秋(qq)基本上算是ytkah本人开机后要开的软件了,平时也习惯用Ctrl+Alt+A快捷键来进行截图.很早以前ytkah一个无意间发现cnblog可以直接黏贴qq截图,当时截图后可能是忘记黏贴到其 ...
- "QQ尾巴病毒"核心技术的实现原理分析
声明:本文旨在探讨技术,请读者不要使用文章中的方法进行任何破坏. 2003这一年里,QQ尾巴病毒可以算是风光了一阵子.它利用IE的邮件头漏洞在QQ上疯狂传播.中毒者在给别人发信息时,病毒会自动在信息文 ...
- 【转】Mac QQ截图保存在哪里?
原文网址:http://www.pc6.com/edu/67677.html QQ Mac版的截屏图片保存在哪儿呢?可不可以像Windows版本一样设定保存路径呢?当然是可定的.Mac QQ截图保存你 ...
- Android窗口管理服务WindowManagerService显示窗口动画的原理分析
文章转载至CSDN社区罗升阳的安卓之旅,原文地址:http://blog.csdn.net/luoshengyang/article/details/8611754 在前一文中,我们分析了Activi ...
随机推荐
- POJ(2784)Buy or Build
Buy or Build Time Limit: 2000MS Memory Limit: 65536K Total Submissions: 1369 Accepted: 542 Descr ...
- Java里的接口
Java里面由于不允许多重继承,所以如果要实现多个类的功能,则可以通过实现多个接口来实现. Java接口和Java抽象类代表的就是抽象类型,就是我们需要提出的抽象层的具体表现.OOP面向对象的编程,如 ...
- acid数据库事务正确执行的四个基本要素的缩写编辑本义项
ACID,指数据库事务正确执行的四个基本要素的缩写.包含:原子性(Atomicity).一致性(Consistency).隔离性(Isolation).持久性(Durability).一个支持事务(T ...
- Android中AsyncTask的简单用法 .
在开发Android应用时必须遵守单线程模型的原则: Android UI操作并不是线程安全的并且这些操作必须在UI线程中执行.在单线程模型中始终要记住两条法则: 1. 不要阻塞UI线程 2. 确保只 ...
- freemarker字符串转换成日期和时间
freemarker字符串转换成日期和时间 1.日期时间转换总结 (1)date用来转换为日期 (2)time用来转换为时间 (3)datetime用来转换为日期和时间 2.展示演示样例 <#- ...
- C程序设计的抽象思维-算法分析-大多数元素
[问题] 请编写下面函数 int MajorityElement(int array[],int n); 该函数返回数组array中的多数元素.多数元素是指在占绝对多数(至少51%)的一个值.假设多数 ...
- Java基础知识强化56:经典排序之快速排序(QuickSort)
1. 快速排序的原理: 快速排序(Quicksort)是对冒泡排序的一种改进. 快速排序由C. A. R. Hoare在1962年提出.它的基本思想是:通过一趟排序将要排序的数据分割成独立的两部分,其 ...
- passwd的使用
名称:passwd 使用权限:所有使用者 使用方式:passwd [-k] [-l] [-u [-f]] [-d] [-S] [username] 说明:用来更改使用者的密码 参数: -k keep ...
- Java初转型-Tomcat安装和配置
1.http://www.cnblogs.com/diegodu/p/5915358.html tomcat直接解压,启动然后测试是否正常. 2.http://www.cnblogs.com/mq00 ...
- Linux命令之文件与用户权限
1.文件管理 在Linux里,任何软件和I/O设备都被视为文件.Linux中的文件名最大支持256个字符,分别可以用A-Z.a-z.0-9等字符来命名. 和Windows不同,Linux中文件是区分大 ...