-1

I need to detect Ctrl+A keystrokes whenever user presses them. Here is what I did so far

procedure TForm1.Timer1Timer(Sender: TObject);
begin
  if (GetKeyState(65)>0) then
  begin
    caption:='CTRL+A Pressed!';
  end else
  begin
    caption:='Not pressed';
  end;
end;

How to know if CTRL key is pressed?

Xel Naga
  • 826
  • 11
  • 28
  • If your window doesn't have input focus, then you aren't ever going to see anything calling `GetKeyState`. At least that's what I take from you saying "in other applications". – David Heffernan Jan 05 '21 at 16:40
  • 3
    This is an [XY Problem](http://xyproblem.info). The solution to your *real* problem is [WinEvents](https://learn.microsoft.com/en-us/windows/win32/winauto/what-are-winevents). – IInspectable Jan 05 '21 at 16:42
  • @DavidHeffernan How can many applications like Camtasia detect keystrokes then? – Xel Naga Jan 05 '21 at 16:42
  • That's a different question. But they don't do it using the code in your question. As @IInspectable says, we have the XY problem here, and you need to reformulate the question bearing that in mind. – David Heffernan Jan 05 '21 at 17:02
  • Maybe you'll find the answer there: https://stackoverflow.com/questions/54171074/what-is-keyboard-hook – fpiette Jan 05 '21 at 17:10
  • I posted an answer that works for me. How can it work now? – Xel Naga Jan 05 '21 at 17:20
  • Why are you trying to do this? Definitely seems like an XY problem... – J... Jan 05 '21 at 18:11
  • 1
    @DavidHeffernan: Just out of curiosity. "If your window doesn't have input focus, then you aren't ever going to see anything calling GetKeyState" Have you actually tried it? (I'm asking because I have a few times the last few years, and it has always worked. It might be undocumented behaviour, though.) – Andreas Rejbrand Jan 05 '21 at 18:57
  • 1
    @AndreasRejbrand I just now tested it. On XP, `GetKeyState()` DID see key state changes while my test app DID NOT have input focus. That *directly contradicts* [what Raymond Chen said](https://devblogs.microsoft.com/oldnewthing/20041130-00/?p=37173) on this matter: "*If the user has switched to another program, then **the GetKeyState function will not see the input that the user typed into that other program**, since that input was not sent to your input queue.*" I ran the same test app on Win7 and `GetKeyState()` DID NOT see key state changes without the app having input focus, as expected. – Remy Lebeau Jan 05 '21 at 19:21
  • Windows10 works too. – Xel Naga Jan 05 '21 at 19:28
  • And it works on my Windows 7 system. But we can all agree that using `GetKeyState` isn't the right solution for this task, for the obvious reasons (polling issues) and because it seems not to work on every system. – Andreas Rejbrand Jan 05 '21 at 19:36
  • @andre Polling isn't the obvious reason. `GetKeyState` doesn't require polling. The state reported only changes when calling message retrieval functions. `GetMessageW` is a blocking function, so there's your event based solution. The obvious reason is that monitoring CTRL+A in other applications is meaningless. Any application can decide at any point in time how to handle CTRL+A. Observing an event whose consequences are unknown carries little value. – IInspectable Jan 05 '21 at 20:09
  • @IInspectable: You are right. I was thinking about the OP's polling (which does cause even more issues) but only wrote `GetKeyState`. Although I do not know the internals of the `GetKeyState` function (other than what I can infer from MSDN and Raymond Chen), it certainly isn't meant to be used here (even if it "often" seems to work). – Andreas Rejbrand Jan 05 '21 at 20:34
  • Consider using [LowLevelKeyboard hook](https://learn.microsoft.com/en-us/previous-versions/windows/desktop/legacy/ms644985(v=vs.85))/[Raw Input](https://learn.microsoft.com/en-us/windows/win32/inputdev/raw-input) to get key down/up events and record ctrl and A keys at the same time. – Drake Wu Jan 06 '21 at 06:21

3 Answers3

1

I need to detect Ctrl+A keystrokes whenever user presses them.

If you really mean that and nothing else then consider RegisterHotkey().

  • Advantage: always notifies you. No polling needed, no performance wasted.
  • Disadvantage: this also means you "steal" that input for every other window - it'd be up to you to synthesize this key combination for the focused window so the reality is restored. This function is not intended for interception.

Example: you program/window is not focused, maybe even minimized. In Notepad you're pressing CTRL+A to select all the text. Your program is notified and Notepad never received such an input. You could use SendInput() to generate CTRL+A for Notepad, which then finally selects all the text.

AmigoJack
  • 5,234
  • 1
  • 15
  • 31
1

You could use Raw Input/LowLevelKeyboard hook to get key down/up events instead of polling.

For Raw Input, you could refer to this answer,

  • Use RegisterRawInputDevices to register for your window;
  • Then your window will get the WM_INPUT message when there is any raw input;
  • Identify the key;
  • Record the status of ctrl;
  • When key 'A' is pressed, check the ctrl status.

Sample in C++:

LRESULT CALLBACK WindProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam)
{
    static BOOL ctrl_state = FALSE; //up
    if (Msg == WM_INPUT)
    {
        HRAWINPUT hRawInput = (HRAWINPUT)lParam;
        RAWINPUT input = { 0 };
        UINT size = sizeof(input);
        GetRawInputData(hRawInput, RID_INPUT, &input, &size, sizeof(RAWINPUTHEADER));
        switch (input.data.keyboard.VKey)
        {
        case VK_CONTROL:
            if(input.data.keyboard.Flags & RI_KEY_BREAK)
                ctrl_state = TRUE;
            else
                ctrl_state = FALSE;
            break;
        case 0x41:
            if (input.data.keyboard.Flags & RI_KEY_BREAK)
                OutputDebugString(L"Ctrl + A pressed");
            break;
        default:
            break;
        }

    }
    return DefWindowProc(hWnd, Msg, wParam, lParam);
}
Drake Wu
  • 6,927
  • 1
  • 7
  • 30
-1

It worked for me.

procedure TForm1.Timer1Timer(Sender: TObject);
begin
  if (GetKeyState(VK_CONTROL)<0) and (GetKeyState(65)<0) then
  begin
    caption:='Pressed!';
  end else
  begin
    caption:='';
  end;
end;
Xel Naga
  • 826
  • 11
  • 28
  • 1
    What is the `Interval` of the timer? What if the user presses Ctrl+A really quickly, between two invocations of this code? What happens if Ctrl+A is pressed long enough for this to trigger an action several times in a row? How to distinguish that from several actual distinct key presses? – Andreas Rejbrand Jan 05 '21 at 17:36
  • @AndreasRejbrand I appreciate your point. I use 100ms for now, it works OK. I will send the keystrokes into a queue, it requires some other code of course. – Xel Naga Jan 05 '21 at 17:40
  • 3
    @Andrzej "*No idea why it is downvoted*" - likely because this approach doesn't actually address the question you asked. This approach only works within your own app, you cannot detect keystrokes in other applications this way. And using a timer to poll key states will miss events at times. You really need a keyboard hook via `SetWindowsHookEx()` or `RegisterRawInputDevices()` instead. Or, like @IInspectable said, use WinEvents instead to act on the state change that the keystroke causes (ie, such as the `EVENT_OBJECT_SELECTION...` events), rather than focusing on the keystroke itself. – Remy Lebeau Jan 05 '21 at 18:00
  • 2
    @RemyLebeau: "you cannot detect keystrokes in other applications this way" Are you sure? – Andreas Rejbrand Jan 05 '21 at 18:17
  • @RemyLebeau I can detect in other applications as well – Xel Naga Jan 05 '21 at 18:18
  • 2
    @Andrzej Not with `GetKeyState()`, you can't. It polls the keyboard state only for the *calling thread*, and that state is updated only when the calling thread has input focus and performs message retrieval. To detect the key state from other apps, you would have to use `GetAsyncKeyState()` instead. But in any case, polling key states is not the best solution for this situation, as stated earlier. – Remy Lebeau Jan 05 '21 at 18:46
  • 1
    @RemyLebeau: The only objection I have is that I tried it and it does work. When I press Ctrl+A in Firefox, my Delphi test app with the OP's code above does display "Pressed" while the keys are actually down. I also recall seeing it work in the past. But of course I agree with all the other points you make. – Andreas Rejbrand Jan 05 '21 at 18:51
  • @AndreasRejbrand, with Timer, I would bet, that the Application does freeze, and give no Feedback anymore, because they take "Message" all the Time. So, that the Application does crash ... – Jens Aug 26 '23 at 09:19