Getting the currently selected text from Pocket Internet Explorer

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.

3 Responses to “Getting the currently selected text from Pocket Internet Explorer”

  1. happypuppy says:

    #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;
    }

  2. Rayman Zhang says:

    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.

  3. Rayman Zhang says:

    Sorry, Looks like the DTM_COPYSELECTIONTONEWISTREAM works under WM6, it’s some other bugs cause the my program out of work.