65.9K
CodeProject 正在变化。 阅读更多。
Home

Windows Mobile 远程控制器

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.92/5 (21投票s)

2008年3月17日

CPOL

4分钟阅读

viewsIcon

179726

downloadIcon

2846

从您的桌面控制您的 Windows Mobile 设备。

引言

本文介绍了 Windows Mobile 桌面远程控制器 的实现。通过此应用程序,您将能够使用鼠标和键盘远程控制您的 Windows Mobile 设备。

背景

本文中的代码基于我之前的文章:一个远程 Windows Mobile 屏幕抓取器。此应用程序没有阻塞 RAPI 调用,而是实现了一个流式 RAPI 服务器,允许桌面应用程序与设备建立永久连接。此外,在此代码中,我放弃了 GAPI 和 DirectDraw 屏幕抓取技术,转而使用更简单的基于 GDI 的屏幕抓取技术。为了提高通信性能,代码在两端都使用了 ZLIB 压缩库。

桌面代码

请参阅分发 Zip 文件中的 CeRemoteClient 目录以获取桌面项目。

桌面代码的大部分位于 CeRemoteClientView.h 文件中。这实际上是一个 WTL 8.0 框架子窗口,实现了所有桌面客户端功能。

设备连接通过公共 Connect()Disconnect() 方法执行。这些方法由框架窗口类实现(MainFrm.h)中的菜单和工具栏处理程序调用。当桌面成功连接到设备后,会创建一个 200 毫秒的计时器,用于轮询设备以获取压缩屏幕位图。

设备屏幕由私有 GetScreen() 方法检索。它首先停止计时器,并向设备服务器发送一条消息,请求当前屏幕。

KillTimer(SCREEN_TIMER);
hr = Write(RCM_GETSCREEN);

设备服务器返回一条包含相同消息代码的消息,显示压缩屏幕缓冲区的大小、其展开大小以及压缩字节流。读取前三个 DWORD 后,代码会确保压缩和展开缓冲区都有足够的空间,然后读取压缩字节流。

// Read the compressed buffer
hr = m_pStream->Read(m_pZipBuf, cbZipBuf, &ulRead);

如果一切正常,则解压缩压缩缓冲区,并查询所得 DIB 的位图尺寸。如果尺寸与上次不同,则很可能是设备屏幕发生了旋转,因此会使整个窗口失效以清除任何垃圾。

zr = uncompress(m_pScrBuf, &nDestLen, m_pZipBuf, cbZipBuf);
if(zr == Z_OK)
{
    DIBINFO* pDibInfo = (DIBINFO*)m_pScrBuf;
    BYTE*    pBmpData = (BYTE*)(m_pScrBuf + sizeof(DIBINFO));
    BOOL     bErase   = FALSE;

    if(m_xDevScr != pDibInfo->bmiHeader.biWidth || 
       m_yDevScr != pDibInfo->bmiHeader.biHeight)
    {
        m_xDevScr = pDibInfo->bmiHeader.biWidth;
        m_yDevScr = pDibInfo->bmiHeader.biHeight;

        SetScrollSize(m_xDevScr, m_yDevScr);

        bErase = TRUE;
    }

    m_dib.SetBitmap((BITMAPINFO*)pDibInfo, pBmpData);

    InvalidateRect(NULL, bErase);
    UpdateWindow();
}

强制窗口更新后,将重新启动计时器,以便我们获取下一个屏幕。

发送输入

向设备发送键盘和鼠标输入非常简单:处理相应的窗口消息,将其数据内容转换为 INPUT 结构,然后将它们发送给服务器进行处理。以下是 WM_KEYDOWN 处理程序:

LRESULT OnKeyDown(TCHAR vk, UINT cRepeat, UINT flags)
{
    HRESULT hr;
    INPUT   input;

    if(!m_bConnected)
        return 0;

    input.type           = INPUT_KEYBOARD;
    input.ki.wVk         = MapKey(vk);
    input.ki.wScan       = 0;
    input.ki.dwFlags     = 0;
    input.ki.time        = 0;
    input.ki.dwExtraInfo = 0;

    hr = Write(RCM_SETINPUT);
    if(SUCCEEDED(hr))
    {
        hr = Write(&input, sizeof(input));
        if(SUCCEEDED(hr))
            GetScreen();
    }

    return 0;
}

MapKey() 函数执行桌面和设备键盘之间的基本键映射。使用 F1 键作为左功能键,F2 作为右功能键。F3 和 F4 键自然映射到手机按键。

发送鼠标操作类似:

LRESULT OnLButtonDown(UINT Flags, CPoint pt)
{
    HRESULT    hr;
    INPUT    input;

    if(!m_bConnected)
        return 0;

    m_bLeftBtn           = true;
    input.type           = INPUT_MOUSE;
    input.mi.dwFlags     = MOUSEEVENTF_ABSOLUTE | 
                           MOUSEEVENTF_LEFTDOWN;
    input.mi.dx          = pt.x * 65536 / m_xDevScr;
    input.mi.dy          = pt.y * 65536 / m_yDevScr;
    input.mi.mouseData   = 0;
    input.mi.time        = 0;
    input.mi.dwExtraInfo = 0;

    hr = Write(RCM_SETINPUT);
    if(SUCCEEDED(hr))
    {
        hr = Write(&input, sizeof(input));
        if(SUCCEEDED(hr))
            hr = GetScreen();
    }

    return 0;
}

请注意,鼠标屏幕坐标是如何为设备屏幕进行标准化的。这是设备服务器上使用的 SendInput API 的要求。

既然提到了,让我们更仔细地看一下设备服务器代码。

设备代码

请参阅分发 Zip 文件中的 CeRemSrv 目录以获取设备项目。

设备代码的大部分是在 CRemoteControl 类中实现的。桌面客户端发送的所有消息都在 Run 方法中实现的执行循环中进行处理和分派。

设备屏幕通过 SendScreen 方法捕获,该方法与桌面对应项具有非常相似的结构。请注意设备屏幕是如何轻松捕获的。

hDC = GetWindowDC(NULL);

获取设备屏幕的 HDC 后,您可以非常轻松地将其复制到位图中并序列化到桌面。不再需要像我之前使用的花哨的 GAPI 或 DirectDraw 技术。

将设备屏幕复制到 DIB 后,整个内容将被压缩并发送回桌面客户端。

memcpy(m_pScrBuf + i, m_dib.GetBitmapInfo(), sizeof(DIBINFO));
i += sizeof(DIBINFO);

memcpy(m_pScrBuf + i, m_dib.GetDIBits(), m_dib.GetImageSize());
i += m_dib.GetImageSize();

ULONG len = m_cbZipBuf;
int   zr  = compress(m_pZipBuf, &len, m_pScrBuf, cbNew);

if(zr != Z_OK)
    len = 0;

hr = m_pStream->Write(&dwMsg,    sizeof(DWORD), &ulWritten);
hr = m_pStream->Write(&len,      sizeof(ULONG), &ulWritten);
hr = m_pStream->Write(&cbNew,    sizeof(DWORD), &ulWritten);
hr = m_pStream->Write(m_pZipBuf, len,           &ulWritten);

处理来自桌面的输入甚至更简单。

HRESULT CRemoteControl::GetInput()
{
    INPUT   input;
    HRESULT hr;
    DWORD   dwRead;

    hr = m_pStream->Read(&input, sizeof(input), &dwRead);
    if(FAILED(hr))
        return hr;

    if(dwRead != sizeof(input))
        return E_FAIL;

    SendInput(1, &input, sizeof(input));

    return S_OK;
}

确实是一个非常简单的实现。

关注点

有两个有趣的事情您可能想知道:我如何模拟双击鼠标事件,以及为什么此代码在 WM5 和 WM6 设备上无法直接使用。

双击必须通过向设备发送四条消息来模拟。这是因为桌面窗口管理器会将四个鼠标事件(按下 - 释放 - 按下 - 释放)合并为一个消息 - WM_LBUTTONDBLCLK。如果您查看我的代码,您将看到它是如何被撤销的……

在 WM5 和 WM6 设备上,您可能需要启用 RAPI 连接才能使设备服务器 DLL 响应客户端。我曾经编写了一个简单的设备工具来帮助您完成这项艰巨的任务。您可以在此处获取。将 EXE 复制到设备并执行它。

历史

  • 2008-03-28 - 修正了闪烁问题。
  • 2008-03-17 - 首次发布。
© . All rights reserved.