As part of my hack for mobileCampLondon, I wanted to get the current selection from the web browser on my Windows Mobile phone.
The plan was to add a new menu item to Pocket Internet Explorer that lets you search for the text you’ve selected in Google. So I needed to get the currently selected text in order to include it in a Google search page URL. I thought it’d be simple, but it turned out to be more of a hassle than I expected.
My final approach is more of a hack than I’d have liked, so in this post, I’ll outline my unsuccessful approaches before showing what I finally ended up doing.
My handle to Pocket Internet Explorer (PIE) is represented by pWebBrowser – a IWebBrowser2 object.
Attempt 1 – ExecWB
The most obvious approach was to use ExecWB to execute a copy command, and get the currently selected text from the clipboard.
HRESULT hr = pWebBrowser->ExecWB(OLECMDID_COPY, OLECMDEXECOPT_DONTPROMPTUSER, NULL, NULL);
This didn’t work.
hr
ends up containing -2147221248
(“Trying to revoke a drop target that has not been registered”).
A look at the MSDN documentation for ExecWB (I do look at documentation occasionally… honest!) says that:
This method is not supported for Windows Mobile Version 5.0 and later.
This doesn’t mean it’s not implemented (some commands seem to work – a little experimentation shows that using ExecWB to send OLECMDID_SELECTALL
, for example, does select everything on the page as you’d expect). But it does apparently mean that I can’t moan that it won’t work. And -2147417848 seems to mean that the OLECMDID value you’ve tried to use is not defined.
It was time to think of another way…
Attempt 2 – IHTMLDocument2
The next plan was to get a handle to the HTML document being displayed by the web browser, and use the selection
property to get the currently selected text.
Easy? Erm… not so much.
After much searching through the Windows Mobile SDK, I couldn’t find IHTMLDocument2 anywhere. But, I did find a mention of IPIEHTMLDocument2
in an idl
file in the SDK Include directory. (Ugh. I don’t get the point of idl files… why do I have to build my own libraries? 🙁 )
There were a few steps between me and getting a handle to a IPIEHTMLDocument2 object…
C:\\Program Files\\Windows Mobile\\Windows Mobile 5.0 Pocket PC SDK\\Include\\Armv4i>midl /D:UNDER_CE /I . webvw.idl Microsoft (R) 32b/64b MIDL Compiler Version 6.00.0366 Copyright (c) Microsoft Corporation 1991-2002. All rights reserved. Processing .\\webvw.idl webvw.idl Processing .\\oaidl.idl oaidl.idl Processing .\\objidl.idl objidl.idl Processing .\\unknwn.idl unknwn.idl Processing .\\wtypes.idl wtypes.idl Processing .\\basetsd.h basetsd.h Processing .\\ocidl.idl ocidl.idl Processing .\\oleidl.idl oleidl.idl Processing .\\dispex.idl dispex.idl Processing .\\servprov.idl servprov.idl Processing .\\urlmon.idl urlmon.idl Processing .\\msxml2.idl msxml2.idl Processing C:\\Program Files\\IDEs\\Visual Studio 2005\\VC\\PlatformSDK\\include\\oaidl.acf oaidl.acf Processing C:\\Program Files\\IDEs\\Visual Studio 2005\\VC\\PlatformSDK\\include\\ocidl.acf ocidl.acf
This produces a webvw.tlb
file, which I imported into my code using:
#import "C:\\\\Program Files\\\\Windows Mobile\\\\Windows Mobile 5.0 Pocket PC SDK\\\\Include\\\\Armv4i\\\\webvw.tlb"
Then I added wvuuid.lib
to the project dependencies in Visual Studio.
On the plus side, this let me get a IPIEHTMLDocument2 object:
IDispatch* pHtmlDocDispatch; IOleCommandTarget* pOleCommandTarget; WEBVIEWLib::IPIEHTMLDocument2* pHTMLDocument2; WEBVIEWLib::IPIEHTMLWindow2* pHTMLWindow; IPIEHTMLElementCollection* pHTMLElementCollection; hr = pWebBrowser->get_Document(&pHtmlDocDispatch); CHR(hr); if (pHtmlDocDispatch != NULL) { hr = pHtmlDocDispatch->QueryInterface(IID_IPIEHTMLDocument2, (void**)&pHTMLDocument2); CHR(hr); hr = pHTMLDocument2->get_parentWindow(&pHTMLWindow); CHR(hr); pHTMLDocument2->selection ... oh. bugger. }
On the down side… dammit. IPIEHTMLDocument2
doesn’t have the selection
property. So this was all for nothing. Damn.
Attempt 3 – DTM_COPYSELECTIONTONEWISTREAM
At this point, I was running out of ideas and turned to Google for inspiration. I came across the idea of using SendMessage to send DTM_COPYSELECTIONTONEWISTREAM
to the web browser’s window.
The code looks something like this:
ULONG ulNumChars = 0; LPWSTR pSelectedText = NULL; LPSTREAM pStream = 0; DWORD rsd = 0; HGLOBAL hglbCopy; LPTSTR lptstrCopy; HWND hWndHTML; IOleWindow *pOleWindow; hr = pDataObj->QueryInterface(IID_IOleWindow, (void**)&pOleWindow); CHR(hr); hr = pOleWindow->GetWindow(&hWndHTML); CHR(hr); hr = SendMessage(hWndHTML, DTM_COPYSELECTIONTONEWISTREAM, (WPARAM)&rsd, (LPARAM)&pStream); CHR(hr); if(pStream) { STATSTG stat = { 0 }; hr = pStream->Stat(&stat, STATFLAG_NONAME); CHR(hr); pSelectedText = (LPWSTR)LocalAlloc(LPTR,(ULONG)stat.cbSize.QuadPart + 4); if (pSelectedText) { hr = pStream->Read(pSelectedText, (ULONG)stat.cbSize.QuadPart, &ulNumChars); CHR(hr); EmptyClipboard(); hglbCopy = GlobalAlloc(GMEM_MOVEABLE, ulNumChars+2); if(hglbCopy != NULL) { lptstrCopy = (LPTSTR) GlobalLock(hglbCopy); memcpy(lptstrCopy, pSelectedText, ulNumChars); GlobalUnlock(hglbCopy); SetClipboardData(CF_UNICODETEXT, hglbCopy); } LocalFree(pSelectedText); } pStream->Release (); } CloseClipboard();
Sounds good, and certainly looks the business.
Except, it didn’t work. Or at least, in a weekend where I spent more time meeting people and joining in discussions than coding, I didn’t get the bottom of what is wrong with it. Whatever I did, pStream
stayed empty. 🙁
Attempt 4 – Cheating
Okay, so at this point it was getting late on Sunday and I was still no nearer to solving what was supposed to a small aspect of the hacks I wanted to show people. So… I kinda cheated. 🙂
keybd_event (VK_CONTROL, 0, 0, 0); keybd_event (0x43, 0, 0, 0); Sleep(1000); keybd_event (0x43, 0, KEYEVENTF_KEYUP, 0); keybd_event (VK_CONTROL, 0, KEYEVENTF_KEYUP, 0);
I used keybd_event to fake a Ctrl-C keystroke. I press Ctrl and C, wait for a bit, then release them, copying the selection to the clipboard.
Urgh, Very hacky, but I guess that is in-keeping with the spirit of a hack challenge!
So there we go… if anyone knows a better way of doing this, or if anyone knows why I couldn’t get DTM_COPYSELECTIONTONEWISTREAM
to work, I’d love to hear from you.
#define DTM_COPYSELECTIONTONEWISTREAM 0x0477
BOOL xxxx::GetHTMLSelection(CString& csText)
{
BOOL bRetVal = FALSE;
if (OpenClipboard())
{
if( EmptyClipboard() )
{
CloseClipboard();
HWND hWndInternal = ::GetWindow(m_hHTMLWnd, GW_CHILD);
::SendMessage(hWndInternal,WM_COMMAND,0x139E,0);
::SendMessage(hWndInternal,WM_COMMAND,0x56BA,0);
::SendMessage(hWndInternal,WM_COMMAND,0x1608,0);
//DTM_ISSELECTION
if (OpenClipboard())
{
HANDLE hData = ::GetClipboardData(CF_UNICODETEXT);
if( NULL != hData )
{
csText = (wchar_t*)hData;
EmptyClipboard();
csText.TrimLeft();
csText.TrimRight();
bRetVal = !csText.IsEmpty();
}
if( !bRetVal )
{
LPSTREAM pStream = NULL;
DWORD rsd = 0;
::SendMessage(m_hHTMLWnd, DTM_COPYSELECTIONTONEWISTREAM, (WPARAM)&rsd, (LPARAM)&pStream);
if( pStream )
{
STATSTG stat = { 0 };
if (SUCCEEDED(pStream->Stat(&stat, STATFLAG_NONAME)))
{
ULONG ulNumChars = 0;
LPWSTR pSelectedText = (LPWSTR)LocalAlloc(LPTR,(ULONG)stat.cbSize.QuadPart + 8);
if (pSelectedText)
{
HRESULT hr = pStream->Read(pSelectedText,(ULONG)stat.cbSize.QuadPart, &ulNumChars);
if( SUCCEEDED(hr) )
{
csText = pSelectedText;
csText.TrimLeft();
csText.TrimRight();
bRetVal = !csText.IsEmpty();
}
LocalFree(pSelectedText);
}
}
pStream->Release ();
}
}
CloseClipboard();
}
}
}
return bRetVal;
}
Hi,
DTM_COPYSELECTIONTONEWISTREAM does work on WM5. It should be sent to the window handle which created by “Create( DISPLAYCLASS, ….)”. But unforturnately, it dosen’t work under WM6 for unknow reason.
Sorry, Looks like the DTM_COPYSELECTIONTONEWISTREAM works under WM6, it’s some other bugs cause the my program out of work.