How do I get the selected text from the focused window using native Win32 API?

12,342

Solution 1

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!

Solution 2

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

Share:
12,342
legends2k
Author by

legends2k

I roam around hunting for bits in mathematics, graphics, C++, C, Lua, Python and design-related questions; have learnt a lot here. I like giving elaborate answers with examples and citations. Here are some I enjoyed answering and asking. Answers: Rotating a Vector in 3D space Calculating a perpendicular offset from a diagonal line Deriving OpenGL's utility function gluLookAt How can a program be OS-independent? Efficiently calculating rotation matrix from 2 vectors In OpenGL why give gluPerspective before gluLookAt? How are textures referenced in shader? How many pixels is a meter? Rendering the HSV wheel How to interpolate hue values in HSV colour space? Shift operator in C Passing 2D array to a function Lua multiple assignment with tables Difference between move and forward Taking std::function with templatized parameter as a parameter Linkage of symbols within anonymous namespace within a regular namespace Confusion on strings in C Questions: Purpose of Unions Why is (void)0 a no-op in C and C++? When is a conditional variable needed, isn't a mutex enough? Mixing extern and const I can type 77 words per minute :)

Updated on June 05, 2022

Comments

  • legends2k
    legends2k about 2 years

    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?

  • legends2k
    legends2k about 14 years
    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.
  • kurige
    kurige about 14 years
    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!
  • legends2k
    legends2k about 14 years
    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
    legends2k about 14 years
    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
    legends2k about 14 years
    This in combination with kurige's answer made it to work! Thanks :)
  • Oliver Weichhold
    Oliver Weichhold over 12 years
    What I don't understand. Why do you obtain the target_window HWND if it's not going to be used later on. Unless I've missed something.
  • kurige
    kurige over 12 years
    @OliverWeichhold - I believe (it was a long time ago) I was simply explaining that "sending WM_COPY" won't work unless you A) use attachThreadInput, and b) get the window with keyboard focus. Then I go on to explain the unrelated SendInput method that doesn't use any HWND ids. The two ideas are not as cleanly separated as they should be.
  • Jorge González Lorenzo
    Jorge González Lorenzo over 12 years
    Yeah, for example Chrome was unable to put the selected text in the clipboard before my process reading it.
  • Jorge González Lorenzo
    Jorge González Lorenzo over 12 years
    But maybe it's better if you register your window in the clipboard chain, and add the WM_DRAWCLIPBOARD to your app WndProc. That way you will receive the message after the window that receives the input modifies the clipboard.
  • tmighty
    tmighty over 2 years
    You should have written the AttachThread code, too.
  • kurige
    kurige about 2 years
    @tmighty Why? Was my answer from 12 years ago too short for you?
  • tmighty
    tmighty about 2 years
    Not too short. You wrote everything else down so nicely that the fact that you didn't write the AttachThread code looked intentional, like it wasn't necessary.