Reputation: 128
LRESULT CALLBACK ProcessMsgs(HWND hwnd, UINT msg, WPARAM param, LPARAM lparam) {
switch (msg) {
case WM_HOTKEY: {
WORD AUXKEY = LOWORD(lparam);
WORD MKEY = HIWORD(lparam);
INPUT ip;
ip.type = INPUT_KEYBOARD;
ip.ki.time = 0;
ip.ki.dwExtraInfo = 0;
ip.ki.dwFlags = KEYEVENTF_UNICODE;
ip.ki.wVk = 0; // 0 because of unicode
if (AUXKEY == MOD_ALT) {
switch (MKEY) { // Lowercase
case 0x41: // A
ip.ki.wScan = 0xE1; // lowercase a with accent
break;
case 0x4E: // N
ip.ki.wScan = 0xF1; // lowercase n with tilde accent
break;
case 0x4F: // O
ip.ki.wScan = 0xF3; // lowercase o with accent
break;
case 0x55: // U
ip.ki.wScan = 0xFA; // lowercase u with accent
break;
case 0x49: // I
ip.ki.wScan = 0xED; // lowercase i with accent
break;
case 0xBD: // DASH
ip.ki.wScan = 0x2014; // em dash
break;
}
SendInput(1, &ip, sizeof(INPUT)); // breakpoint here
}
if (AUXKEY == MOD_ALT + MOD_SHIFT) {
switch (MKEY) { // Uppercase
case 0x41: // A
ip.ki.wScan = 0xC1; // uppercase a with accent
break;
case 0x4E: // N
ip.ki.wScan = 0xF1; // uppercase n with tilde accent
break;
case 0x4F: // O
ip.ki.wScan = 0xD2; // uppercase o with accent
break;
case 0x55: // U
ip.ki.wScan = 0xDA; // uppercase u with accent
break;
case 0x49: // I
ip.ki.wScan = 0xCD; // uppercase i with accent
break;
}
SendInput(1, &ip, sizeof(INPUT));
}
ip.ki.dwFlags = KEYEVENTF_KEYUP + KEYEVENTF_UNICODE; // KEYEVENTF_KEYUP for key release
SendInput(1, &ip, sizeof(INPUT));
return 0;
}
case WM_DESTROY:
PostQuitMessage(0);
return 0;
default:
return DefWindowProc(hwnd, msg, param, lparam);
}
}
This code types a vowel with an accent on it when combined with the ALT key. It recieves the message WM_HOTKEY
from registered hotkeys like this:
RegisterHotKey(
TheWindow,
NULL,
MOD_ALT,
0x41 // A
);
There are hotkeys for all vowels, the letter N, and the dash character. The problem i'm having is the fact that the code works fine when I use a breakpoint on the line of the initial SendInput, but when I run it normally, no letter with accent is typed. All those breaks are there because the switch would always use the last case, I have no idea why.
The thing is when I run this very alpha and primitive version of the code I developed prior to the aforementioned code, it works fine without needing breakpoints (this version used CTRL as the indicator, I changed it to ALT because there are so many functions of programs that use CTRL plus a letter for user functions... the RegisterHotKey function uses MOD_CONTROL
instead of MOD_ALT
accordingly):
switch (msg) {
case WM_HOTKEY:
INPUT ip;
ip.type = INPUT_KEYBOARD;
ip.ki.wScan = 0xE1; // lowercase a with accent
ip.ki.time = 0;
ip.ki.dwExtraInfo = 0;
ip.ki.dwFlags = KEYEVENTF_UNICODE;
ip.ki.wVk = 0; // 0 because of unicode
SendInput(1, &ip, sizeof(INPUT));
ip.ki.dwFlags = KEYEVENTF_KEYUP | KEYEVENTF_UNICODE; // KEYEVENTF_KEYUP for key release
SendInput(1, &ip, sizeof(INPUT));
return 0;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
default:
return DefWindowProc(hwnd, msg, param, lparam);
}
I know that I can use identifiers to simplify some of this, but regardless... that code functions, just with breakpoints... So odd.
Upvotes: 2
Views: 611
Reputation: 10965
I unfortunately don't have a Windows System on hand to test, but i think i can guess what your problem is - but nice heisenbug, took me quite some time to figure out :D
Keep in mind that even if you have a registered hotkey the target window will still get the keypresses as well.
In addition to that SendInput
will not reset the keyboard state:
This function does not reset the keyboard's current state. Any keys that are already pressed when the function is called might interfere with the events that this function generates. To avoid this problem, check the keyboard's state with the GetAsyncKeyState function and correct as necessary.
So from the view of the target window you would see the following key events:
ALT
downA
downá
downá
upA
upALT
upWhereas if you're debugging, you'll probably release the keys once you hit the breakpoint, so the sequence would look like this:
ALT
downA
downA
upALT
upá
downá
upSo that's the most likely reason why it works when you're debugging - you're releasing the keys once the breakpoint is hit.
The ALT-Key is a bit evil in itself - mainly because as long as you're holding down the ALT-Key the window will not get WM_KEYDOWN
messages but only WM_SYSKEYDOWN
messages: Key-Down and Key-Up Messages
The WM_SYSKEYDOWN message indicates a system key, which is a key stroke that invokes a system command. There are two types of system key:
- ALT + any key
- F10
All other key strokes are considered nonsystem keys and produce the WM_KEYDOWN message. This includes the function keys other than F10.
So basically the target app will ignore your input entirely because it only receives it as a SYSKEY message.
You can fix this by checking if the ALT key (or any other of the modifier keys) is still down when your hotkey is called, e.g. via GetAsyncKeyState()
and then call SendInput()
with a release event for that modifier key, then send your special character, and then revert the state of the modifier key back.
Also i would recommend batching up all the sent key events into a single SendInput()
to make sure no user-input (or other programmatic input) is interleaved between your input events.
e.g.:
std::vector<INPUT> inputEvents;
short altKeyState = GetAsyncKeyState(VK_MENU);
// if we can't get the current input state
// we most likely aren't allowed to inject input
if(altKeyState == 0) return 0;
bool isAltDown = altKeyState < 0;
if(isAltDown) {
INPUT ip;
ip.type = INPUT_KEYBOARD;
ip.ki.time = 0;
ip.ki.dwExtraInfo = 0;
ip.ki.dwFlags = KEYEVENTF_SCANCODE | KEYEVENTF_KEYUP;
ip.ki.wVk = VK_MENU;
inputEvents.push_back(ip);
}
// TODO: Add other modifier keys that might interfere with input
// Generate your char input
INPUT ips;
ips.type = INPUT_KEYBOARD;
ips.ki.wScan = 0xE1;
ips.ki.time = 0;
ips.ki.dwExtraInfo = 0;
ips.ki.dwFlags = KEYEVENTF_UNICODE;
ips.ki.wVk = 0;
inputEvents.push_back(ips);
if(isAltDown) {
INPUT ip;
ip.type = INPUT_KEYBOARD;
ip.ki.time = 0;
ip.ki.dwExtraInfo = 0;
ip.ki.dwFlags = KEYEVENTF_SCANCODE;
ip.ki.wVk = VK_MENU;
inputEvents.push_back(ip);
}
// TODO: Add other modifier keys that might interfere with input
// Submit all inputs to the queue
UINT result = SendInput(inputEvents.size(), &inputEvents[0], sizeof(INPUT));
if(result == inputEvents.size()) {
// SUCCESS! Injected all inputs
} else if (result > 0) {
// Partial success, something failed, not really much you can do here other than call GetLastError()
} else {
// blocked by other thread
}
Note that your program is still subject to UIPI (User Interface Privilege Isolation), so you can't use SendInput()
to inject symbols into programs running with a higher integrity level.
If you have focus on a window of a program with a higher integrity level it won't get your SendInput()
messages at all - but you also won't be able to detect the failure at all.
(Unless it used ChangeWindowMessageFilter(Ex)
to allow keyboard input from lower-priority processes)
Upvotes: 2