Windows Mobile 远程控制器






4.92/5 (21投票s)
从您的桌面控制您的 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 - 首次发布。