views:

1105

answers:

2

I'm using the Windows API SendInput() call to simulate keyboard events. The following snippet (irrelevant details omitted) works perfectly for sending a sequence of characters:

wchar_t txt = ...;
INPUT *input = ...;
size_t nInput = 0;

for (unsigned int j = 0; j < length; j++) {
    input[nInput].ki.wVk = 0;
    input[nInput].ki.wScan = txt[j];
    input[nInput].ki.dwFlags = KEYEVENTF_UNICODE;
    nInput++;
    input[nInput].ki.wVk = 0;
    input[nInput].ki.wScan = txt[j];
    input[nInput].ki.dwFlags = KEYEVENTF_UNICODE | KEYEVENTF_KEYUP;
    nInput++;
}
SendInput(nInput, input, sizeoF(INPUT));

Now I'm trying to send single keypresses, with modifiers. I tried the following code:

bool control, alt shift;
wchar_t chr;

if (control) {
    input[nInput].ki.wVk = VK_CONTROL;
    input[nInput].ki.dwFlags = 0;
    nInput++;
}
if (alt) {
    input[nInput].ki.wVk = VK_MENU;
    input[nInput].ki.dwFlags = 0;
    nInput++;
}
if (shift) {
    input[nInput].ki.wVk = VK_SHIFT;
    input[nInput].ki.dwFlags = 0;
    nInput++;
}

input[nInput].ki.wVk = 0;
input[nInput].ki.wScan = chr;
input[nInput].ki.dwFlags = KEYEVENTF_UNICODE;
nInput++;
input[nInput].ki.wVk = 0;
input[nInput].ki.wScan = chr;
input[nInput].ki.dwFlags = KEYEVENTF_UNICODE | KEYEVENTF_KEYUP;
nInput++;

if (shift) {
    input[nInput].ki.wVk = VK_SHIFT;
    input[nInput].ki.dwFlags = KEYEVENTF_KEYUP;
    nInput++;
}
if (alt) {
    input[nInput].ki.wVk = VK_MENU;
    input[nInput].ki.dwFlags = KEYEVENTF_KEYUP;
    nInput++;
}
if (control) {
    input[nInput].ki.wVk = VK_CONTROL;
    input[nInput].ki.dwFlags = KEYEVENTF_KEYUP;
    nInput++;
}
SendInput(nInput, input, sizeof(INPUT));

However, modifiers don't seem to get through, i.e., even though, say control is set to true, the event sequence is received as a plain keypress.

+1  A: 

The only piece of code I've found which may indicate your problem sets the scan code in the ki structure as well.

Try changing your modifier sections to be:

if (control) {
    input[nInput].ki.wVk = VK_CONTROL;
    input[nInput].ki.dwFlags = 0;
    input[nInput].ki.wScan = MapVirtualKey(VK_CONTROL, 0); 
    nInput++;
}
if (alt) {
    input[nInput].ki.wVk = VK_MENU;
    input[nInput].ki.dwFlags = 0;
    input[nInput].ki.wScan = MapVirtualKey(VK_MENU, 0); 
    nInput++;
}
if (shift) {
    input[nInput].ki.wVk = VK_SHIFT;
    input[nInput].ki.dwFlags = 0;
    input[nInput].ki.wScan = MapVirtualKey(VK_SHIFT, 0); 
    nInput++;
}

and see if that helps.

If that still doesn't work, try setting the dwFlags to KEYEVENTF_SCANCODE instead of 0 (only for the modifier keys).

Also do it for the key-up events as well and I'm assuming that you're setting input[nInput].type to INPUT_KEYBOARD, yes? Your code doesn't indicate this.

For reference, it was found on this page.

paxdiablo
"you're setting input[nInput].type to INPUT_KEYBOARD" - yes, of course.
David Hanak
Not "of course", I never assume anything that's not plainly stated (and sometimes I even doubt those :-). Anyhow, did you try the scan codes?
paxdiablo
Fair enough :-) Anyway, the scan codes did not help, but the page you referenced lead me to the solution. I'll post it as an answer. +1, and thanks!
David Hanak
No probs, glad I could help, if only a little. I'm off to bed, it's late here in Oz.
paxdiablo
+1  A: 

Pax's answer did not work out, but it lead me to the correct solution. In short: do not use KEYEVENTF_UNICODE for individual keypresses, rather, convert the character to a virtual key code, and send it that way. Here's the relevant code:

SHORT virtKey = VkKeyScan((TCHAR) chr);
input[nInput].ki.wVk = LOBYTE(virtKey);
input[nInput].ki.dwFlags = 0;
nInput++;
input[nInput].ki.wVk = LOBYTE(virtKey);
input[nInput].ki.dwFlags = KEYEVENTF_KEYUP;
nInput++;

I have not tested how this works for non-ASCII unicode characters, that are mapped to non-English keyboard layouts, and what happens when the layout set for my app is different from the layout set for the application in the front.

David Hanak
@David, you should probably accept this answer otherwise SO will continue to bother you with "have you thought of accepting an answer to this question".
paxdiablo
@Pax: sure, I'll do that, but first I wanted to give others a chance to improve my answer or to point out my mistakes.
David Hanak