|
|||||||||||||||||||||||
|
|||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
IntroductionThis article describes the implementation of a Windows Mobile desktop remote controller. With this application, you will be able to remotely control your Windows Mobile device by using the mouse and keyboard. BackgroundThe code in this article builds on my previous article: A Remote Windows Mobile Screen Grabber. Instead of blocking RAPI calls, this application implements a streamed RAPI server that allows the desktop application to have a permanent connection to the device. Also, in this code, I have dropped the GAPI and DirectDraw screen grabbing techniques, and used a simpler GDI based screen grabbing technique. To improve communications performance, the code uses the ZLIB compression library on both ends. Desktop codePlease refer to the CeRemoteClient directory on the distribution Zip file for the desktop project. The bulk of the desktop code is on the CeRemoteClientView.h file. This is in fact a WTL 8.0 frame child window that implements all the desktop client features. Device connection is performed via the public The device screen is retrieved by the private KillTimer(SCREEN_TIMER); hr = Write(RCM_GETSCREEN); The device server returns a message containing the same message code, the compressed size of the screen buffer, its expanded size, and the compressed byte stream. After reading the three first // Read the compressed buffer hr = m_pStream->Read(m_pZipBuf, cbZipBuf, &ulRead); If all is well, the compressed buffer is decompressed and the resulting DIB is queried for the bitmap dimensions. If the dimensions are different than the last time, then it is very likely that the device screen was rotated, so the whole window is invalidated to erase any garbage: 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(); } After forcing the window to update, the timer is restarted so we can get the next screen. Sending inputSending keyboard and mouse input to the device is pretty simple: handle the corresponding window messages, convert their data content to 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;
}
The Sending mouse actions is similar: 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;
}
Note how the mouse screen coordinates are normalized for the device screen. This is a requirement of the Now that I mentioned it, let's take a closer look at the device server code. Device codePlease refer to the CeRemSrv directory on the distribution Zip file for the device project. The bulk of the device code is implemented in the The device screen is captured by the hDC = GetWindowDC(NULL); After getting the HDC of the device screen, you can very easily copy it into a bitmap and serialize it to the desktop. There's no need for fancy GAPI or DirectDraw techniques like I used before. After getting the device screen copied into a DIB, the whole thing is compressed and sent back to the desktop client: 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); Handling input from the desktop is even simpler: 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;
}
A very simple implementation indeed. Points of interestThere are two interesting things you may like to know: how I simulated the double-click mouse event, and why this code will not work out of the box on WM5 and WM6 devices. Double-clicks had to be simulated by sending four messages to the device. This happens because the desktop window manager will merge the four mouse events (down - up - down - up) into a single message - On WM5 and WM6 devices, you may have to enable RAPI connectivity in order for the device server DLL to respond to the client. I once wrote a simple device tool to help you with this nasty chore. You can get it here. Copy the EXE to the device and execute it. History
|
||||||||||||||||||||||