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),标准控件一般都支持该接口。

看到有些人对通过枚举方式识别窗口的代码感兴趣 , 下面是我的代码:

class CSCWinFilter
{
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  ;  2N - 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;

这样使用, 在截图开始时保存所有桌面窗口层次:

CSCWinSpy<CSCWinFilter>::GetInstance()->SnapshotAllWinRect();

然后就可以这样查询某个位置的最上层窗口了:

CRect rtSelect = CSCWinSpy<CSCWinFilter>::GetInstance()->GetWinRectByPoint(pt, FALSE);

http://www.cppblog.com/weiym/archive/2012/05/06/173845.html

时间: 2024-08-07 04:14:51

QQ截图时窗口自动识别的原理(WindowFromPoint, ChildWindowFromPoint, ChildWindowFromPointEx,RealChildWindowFromPoint)的相关文章

C#软件开发实例.私人订制自己的屏幕截图工具(九)使用自定义光标,QQ截图时的光标

在使用QQ的截图功能的时候,是不是觉得它的光标很酷呢?今天就说一下怎么应用自定义光标,在我们的截图工具中使用QQ截图的光标. 打开资源: 切换到文件资源视图: 打开资源文件目录,将光标文件复制到此目录下: 所需光标文件下载:C#软件开发实例.私人订制自己的屏幕截图工具中使用的光标文件 选中Resources目录,刷新,显示出刚刚复制进来的光标文件: 选中光标文件,拖动到资源的文件视图中: 资源资源名称中的单词第一个字母改为大写. 光标预览: 在Form1类中添加私有变量: #region 自定义

WindowFromPoint &nbsp; ChildWindowFromPoint &nbsp; ChildWindowFromPointEx

HWND WindowFromPoint(     POINT Point ); 功能:返回包含点的窗口句柄,Point参数指屏幕坐标. 如果不存在窗口包含这个点,则返回NULL.如果窗口无效或者隐藏,则返回NULL. 备注:WindowFromPoint函数不获取隐藏或禁止的窗口句柄,即使点在该窗口内.应用程序应该使用ChildWindowFromPoint函数进行无限制查询,这样就可以获得静态文本控件的句柄. 实例: void CTestDlg::OnTest()  {     POINT 

win10中遇到qq视频时摄像头打不开没反应的解决方法

我们在win10系统电脑的使用中,有小伙伴在win10系统的使用中,QQ是我们最常使用到的软件,我们可以直接在上面做很多的操作,qq视频摄像头视频也是我们经常需要使用到的,今天小编就来跟大家分享到我们在win10中遇到qq视频时摄像头打不开没反应的解决方法. 具体的方法和详细的步骤请添加链接描述如下: 1.点击打开开始菜单 - 电脑设置,打开电脑设置窗口:win10中遇到qq视频时摄像头打不开没反应的解决方法-01 2.在打开的电脑设置窗口中,点击打开"隐私"项进入下一步操作: 3.在

微信QQ的二维码登录原理浅析

在很多地方就是都出现了使用二维码登录,二维码付款,二维码账户等应用(这里的二维码种马,诈骗就不说了),二维码验证,多终端辅助授权应用开始多起来,这里先说下啥是二维码,其实二维码就是存了二进制数据的黑白图片,当出现要求二维码登录的时候,服务器会生成一条临时的唯一的二维码信息,发送到客户端以二维码(图片)的形式写入到网页,然后你就会看到统一的四个方形的二维码,如果做的好这个二维码信息应该是有时效的,这里暂且不考虑这些,就简单的微信登录作为例子看看吧: 首先说下整个授权流程: 在客户端网页中会不断向服

Ubuntu 下 安装QQ 截图工具

1.由于ubuntu下是没有dll动态链接库的,所以需要安装一个软件wine,有这个东西之后,以后在ubuntu下就可以运行exe文件了.(wine是一款优秀的Linux系统平台下的模拟器软件,用来将Windows系统下的软件在Linux系统下稳定运行,本熊测试过小的windows应用软件一般都没问题,大的有点问题.应付截图小软件,绝对绰绰有余) 1.1.打开“ubuntu软件中心”,搜索wine1.7(现在这个是最新版的,好像170M左右),安装.如下图所示: 1.2.安装完成之后,对于exe

[Surface] 在win8.1上使用QQ截图放大问题(解决办法)

在使用每次截图的时候整个都被放大了,很让人郁闷,截不到完整的图,本着遇到问题解决问题的想法,这事早解决早好.   开工: 1. 度娘上搜索"win8 qq截图 放大",找到很多资料,整理了一下,分为两类 2. 一类,是修改win8的配置,直接上图,这种方法修改之后,屏幕字符太小(Surface更小),小屏幕不建议采用: 3. 另外一类,是修改qq的配置,Disable DPI设置(DPI是Dots Per Inch(每英寸所打印的点数)的缩写,是打印机.鼠标等设备分辨率的单位),如下图

Qt5.10实现QQ截图工具功能代码源码分析

为了做出一个QQ截图功能的模块,参考了网上某位网友的代码.这里我们分析一下主要代码.源码已经过测试运行,不懂的留言即可.源码地址:Qtjietu20191216.zip1.首先我们一定会想到要对鼠标事件进行改写,包括点击,移动,释放,双击取消选区等功能. ```//重写基类方法 void keyPressEvent(QKeyEvent *event); void paintEvent(QPaintEvent *event); void mousePressEvent(QMouseEvent *e

截图时如何显示鼠标指针,如何带鼠标指针截图,带鼠标指针截图的方法,怎么在截图时把鼠标指针一起截上

截图时如何显示鼠标指针,如何带鼠标指针截图,带鼠标指针截图的方法,怎么在截图时把鼠标指针一起截上 可以借助截图软件来实现, FSCapture(全称FastStone Capture),可是实现截图是带着鼠标指针 HprSnap.可是实现截图是带着鼠标指针 原文地址:https://www.cnblogs.com/GaoNa/p/12304366.html

matlab循环中显示figure时窗口跳动

在Matlab中,当在一个循环内部利用figure显示图片时,有时候会出现窗口跳动,尤其是两个显示器的时候, 具体就是每次循环中显示的figure的位置都出现在屏幕的不同位置,导致看起来灰常不爽 google了个解决方法: 利用imshow中的parent参数,将axis设置为与上一状态一致. code: figure(1);imshow(img,[],'parent',gca);