views:

456

answers:

3

My app. will be running on the system try monitoring for a hotkey; when the user selects some text in any window and presses a hotkey, how do I obtain the selected text, when I get the WM_HOTKEY message?

To capture the text on to the clipboard, I tried sending Ctrl + C using keybd_event() and SendInput() to the active window (GetActiveWindow()) and forground window (GetForegroundWindow()); tried combinations amongst these; all in vain. Can I get the selected text of the focused window in Windows with plain Win32 system APIs?

A: 

Try SendMessage(WM_COPY, etc. ).

Roel
I tried that too :(
legends2k
+1  A: 

Try adding a Sleep() after each SendInput(). Some apps just aren't that fast in catching keyboard input.

Stefan
This in combination with kurige's answer made it to work! Thanks :)
legends2k
+2  A: 

TL;DR: Yes, there is a way to do this using plain win32 system APIs, but it's difficult to implement correctly.

WM_COPY and WM_GETTEXT may work, but not in all cases. They depend on the receiving window handling the request correctly - and in many cases it will not. Let me run through one possible way of doing this. It may not be as simple as you were hoping, but what is in the adventure filled world of win32 programming? Ready? Ok. Let's go.

First we need to get the HWND id of the target window. There are many ways of doing this. One such approach is the one you mentioned above: get the foreground window and then the window with focus, etc. However, there is one huge gotcha that many people forget. After you get the foreground window you must AttachThreadInput to get the window with focus. Otherwise GetFocus() will simply return NULL.

There is a much easier way. Simply (miss)use the GUITREADINFO functions. It's much safer, as it avoids all the hidden dangers associated with attaching your input thread with another program.

LPGUITHREADINFO lpgui = NULL;
HWND target_window = NULL;

if( GetGUIThreadInfo( NULL, lpgui ) )
    target_window = lpgui->hwndFocus;
else
{
    // You can get more information on why the function failed by calling
    // the win32 function, GetLastError().
}

Sending the keystrokes to copy the text is a bit more involved...

We're going to use SendInput instead of keybd_event because it's faster, and, most importantly, cannot be messed up by concurrent user input, or other programs simulating keystrokes.

This does mean that the program will be required to run on Windows XP or later, though, so, sorry if your running 98!

// We're sending two keys CONTROL and 'V'. Since keydown and keyup are two
// seperate messages, we multiply that number by two.
int key_count = 4;

INPUT* input = new INPUT[key_count];
for( int i = 0; i < key_count; i++ )
{
    input[i].dwFlags = 0;
    input[i].type = INPUT_KEYBOARD;
}

input[0].wVK = VK_CONTROL;
input[0].wScan = MapVirtualKey( VK_CONTROL, MAPVK_VK_TO_VSC );
input[1].wVK = 0x56 // Virtual key code for 'v'
input[1].wScan = MapVirtualKey( 0x56, MAPVK_VK_TO_VSC );
input[2].dwFlags = KEYEVENTF_KEYUP;
input[2].wVK = input[0].wVK;
input[2].wScan = input[0].wScan;
input[3].dwFlags = KEYEVENTF_KEYUP;
input[3].wVK = input[1].wVK;
input[3].wScan = input[1].wScan;

if( !SendInput( key_count, (LPINPUT)input, sizeof(INPUT) ) )
{
    // You can get more information on why this function failed by calling
    // the win32 function, GetLastError().
}

There. That wasn't so bad, was it?

Now we just have to take a peek at what's in the clipboard. This isn't as simple as you would first think. The "clipboard" can actually hold multiple representations of the same thing. The application that is active when you copy to the clipboard has control over what exactly to place in the clipboard.

When you copy text from Microsoft Office, for example, it places RTF data into the clipboard, alongside a plain-text representation of the same text. That way you can paste it into wordpad and notepad. Wordpad would use the rich-text format, while notepad would use the plain-text format.

For this simple example, though, let's assume we're only interested in plaintext.

if( OpenClipboard(NULL) )
{
    // Optionally you may want to change CF_TEXT below to CF_UNICODE.
    // Play around with it, and check out all the standard formats at:
    // http://msdn.microsoft.com/en-us/library/ms649013(VS.85).aspx
    HGLOBAL hglb = GetClipboardData( CF_TEXT );
    LPSTR lpstr = GlobalLock(hglb);

    // Copy lpstr, then do whatever you want with the copy.

    GlobalUnlock(hglb);
    CloseClipboard();
}
else
{
    // You know the drill by now. Check GetLastError() to find out what
    // went wrong. :)
}

And there you have it! Just make sure you copy lpstr to some variable you want to use, don't use lpstr directly, since we have to cede control of the contents of the clipboard before we close it.

Win32 programming can be quite daunting at first, but after a while... it's still daunting.

Cheers!

kurige
First of all, thanks a lot for taking time to give such a detailed answer, explaining the nuances! I'll try it and get back to you.
legends2k
This is all stuff I've had to go through before in previous projects involving low-level keyboard hooks. Mostly just copy/pasted from those projects and added some descriptive text. Hope it helps!
kurige
It worked! Oh thanks, after a long time, it finally worked! Earlier when I tried using SendInput I was doing the same, but the problem was the hotkey I registered for. I registered for a `Ctrl + Alt + S` and in the _WM_HOTKEY_, I called SendInput with `Ctrl + C`. But when the user presses `Ctrl + Alt + S`, `Alt` will still be down when I virtually pass `Ctrl + C`; when I changed the hotkey to `Windows + S` it worked perfectly. In a hotkey with `Alt` in the combination, when I virtually unpress `Alt` (KEYEVENTF_KEYUP), it works too.
legends2k
Is there a way to virtually *un-press* all the pressed keys, so that only `Ctrl + C` (copy) alone is passed to the OS? Reason I want it is, the copy operation is hindered by keys like `Shift` or `Alt` when they're pressed, when I pass `Ctrl + C`.
legends2k