inside my hook proc, how can i determine whether a used is pressing SHIFT (without releasing it) and then a char key (like A) ?
for example, if i press Shift+A, i want to know that it will be an uppercase a instead of getting it like Shift, a
so it will be A, if a user presses and releases Shift, it will capture Shift only.
the instaleld hook is WH_KEYBOARD (Global)
function KeyHookProc(Code: Integer; wVirtKey: WPARAM; lKeyStroke: LPARAM)
: LRESULT; stdcall;
type
TTransitionState = (tsPressed, tsReleased);
PKeystrokeData = ^TKeystrokeData;
TKeystrokeData = record
VirtualKey: WPARAM;
KeyStroke: LPARAM;
KeyState: TKeyboardState;
end;
var
Transition: TTransitionState;
KeystrokeDataPtr: PKeystrokeData;
begin
Result := CallNextHookEx(hKeyHook, Code, wVirtKey, lKeyStroke);
Transition := TTransitionState((lKeyStroke shr 31) and 1);
if (Code = HC_ACTION) and (Transition = tsPressed) then
begin
New(KeystrokeDataPtr);
try
KeystrokeDataPtr^.VirtualKey := wVirtKey;
KeystrokeDataPtr^.KeyStroke := lKeyStroke;
GetKeyboardState(KeystrokeDataPtr^.KeyState);
SendMessage(hConsole, WM_NULL, 0, LPARAM(KeystrokeDataPtr));
finally
Dispose(KeystrokeDataPtr);
end;
end;
end;
Here's the code we use in normal day-to-day use to detect the shift key. I've never used it in a hooked context, so I don't know if it would work there, or if something is different in that context that would prevent it.
function ShiftIsDown : Boolean;
var
State: TKeyboardState;
begin
WINDOWS.GetKeyboardState(State);
Result := ((State[vk_Shift] and 128) <> 0);
end;
You detect the pressing of the SHIFT key when your hook proc is called with wParam equal to VK_SHIFT.
When your hook proc is called corresponding to the A key being pressed, the wParam and lParam values are identical whether or not the SHIFT key is down. Since you are not calling TranslateMessage and DispatchMessage as would happen in a normal message loop, you are going to have to translate the raw key down/up events into actual key presses.
It's probably going to be easiest for you to use GetKeyState(VK_SHIFT)<0 to detect whether or not the SHIFT key is down. That depends on exactly what you are trying to do. It looks a little like you are making a fully functioned keylogger. In which case ad-hoc calls to GetKeyState(VK_SHIFT)<0 may not meet your needs, and proper processing of the individual key down/up messages would be better.
Some other comments:
Why are you heap allocating your TKeystrokeData record? You can perfectly well use a stack allocated record.
I hope that hConsole is a window in the same process as your hook. If not it won't receive any useful information because the pointer you send it is only meaningful in the process that defines it. If you want to send information cross-process them WM_COPYDATA is your guy.
Related
In Delphi 10.1.2, inside a TActionList I have created a TAction with these properties and assigned a shortcut Ctrl+F12 to it:
At runtime, when I keep the shortcut keys Ctrl+F12 pressed, the action is executed repeatedly (with speed depending on the system keyboard repeating speed).
So how can I make the action execute only ONCE (until these keys are lifted up), even if the user keeps the keys pressed down or if the user's system has a high keyboard repeat speed setting?
You can retrieve system keyboard settings by using SystemParametersInfo. SPI_GETKEYBOARDDELAY gives the repeat delay; the time between the first and second generated events. SPI_GETKEYBOARDSPEED gives keyboard repeat rate; the duration between events after the initial delay. The documentation has approximate values of the slowest and fastest settings which may help you decide on an acting point.
Suppose you decided to act on. Since shortcuts expose no direct relation to the event that generated them, they have no property or anything that could reveal information about if it is an initial, delayed, or repeated execution.
Then, one option is to disable a shortcut's execution as soon as it is entered and re-enable after the appropriate key has been released. You have to set KeyPreview of the form to true to implement this solution as any of the controls on the form might be the one with the focus when a shortcut is generated.
A less cumbersome solution I would find is to prevent generation of the shortcut when it's not generated by an initial key-down. You have to watch for key down events this time.
One possible implementation can be to install a local keyboard hook.
var
KeybHook: HHOOK;
procedure TForm1.FormCreate(Sender: TObject);
begin
KeybHook := SetWindowsHookEx(WH_KEYBOARD, KeyboardProc, 0, GetCurrentThreadId);
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
UnhookWindowsHookEx(KeybHook);
end;
It's probably tempting to test for the repeat count of the interested key in the callback, however, as mentioned in the documentation for WM_KEYDOWN, the repeat count is not cumulative. What that practically means is that the OS does not supply this information. The previous key state information is provided though. That would be bit 30 of the "lParam" of the callback. You can prevent any keyboard message when your shortcut keys are down, and the primary shortcut has been previously down, from reaching the window procedure of the focused control.
function KeyboardProc(code: Integer; wParam: WPARAM; lParam: LPARAM): LRESULT;
stdcall;
begin
if code > 0 then begin
if (wParam = vk_f12) and (GetKeyState(VK_CONTROL) < 0) and
((lParam and $40000000) > 0) then begin
Result := 1;
Exit;
end;
end;
Result := CallNextHookEx(KeybHook, code, wParam, lParam);
end;
Lastly, don't disregard the probability that if a user's system has been set up with a fast keyboard repeat, it's the user's choice rather than not.
Adding a new ShortCut to the Delphi IDE is not too difficult because the Open Tools API provides a service for this. I am trying something apparently more complex: Add a Wordstar like additional ShortCut:
I want something to happen when the user presses
Shift+Ctrl+H followed by the single key X
where X should work regardless of the state of the Shift key.
This is my code:
procedure TGxKeyboardBinding.BindKeyboard(const BindingServices: IOTAKeyBindingServices);
const
DefaultKeyBindingsFlag = kfImplicitShift + kfImplicitModifier + kfImplicitKeypad;
var
GExpertsShortcut: Byte;
ShiftState: TShiftState;
FirstShortCut: TShortCut;
SecondShortCut: TShortCut;
begin
GExpertsShortcut := Ord('H');
ShiftState := [ssShift, ssCtrl];
FirstShortCut := ShortCut(GExpertsShortcut, ShiftState);
SecondShortCut := ShortCut(Ord('X'), []);
BindingServices.AddKeyBinding([FirstShortCut, SecondShortCut],
TwoKeyBindingHandler, nil,
DefaultKeyBindingsFlag, '', '');
end;
So, if I set ShiftState := [ssCtrl] pressing
Ctrl+H X
calls my TwoKeyBindingHandler method.
But with ShiftState := [ssShift, ssCtrl] pressing
Shift+Ctrl+H X
does nothing.
Oddly enough, when specifying ShiftState := [ssShift, ssCtrl] (which should only affect the first key) pressing
Shift+Ctrl+H Shift+X
calls my TwoKeyBindingHandler method, even though the second ShortCut is added without a modifier key.
Any idea? Is this maybe a known limitation/bug of the Delphi IDE/Open Tools API? Is there a known workaround?
I tried it in Delphi 2007 and Delphi 10 Seattle, no difference.
You should be able to do it using the GetKeyState function.
The program has two operations - Think of it as opening a drop down menu item. When ctr-shift-h is pressed your programme will need to flag that the 'Menu' is now open and that subsequent keypresses will either activate an option or close the 'menu' if an invalid key is presses.
function IsKeyDown(const VK: integer): boolean;
begin
IsKeyDown := GetKeyState(VK) and $8000 <> 0;
end;
procedure Form1.OnkeyDown(...)
begin
if Not H_MenuOpen then
if IsKeyDown(vk_Control) and IskeyDown(vk_Shift) and IsKeyDown(vk_H) then
begin
//Some Boolean in the form
H_MenuOpen:=True;
//Will probably need to invalidate some parameters here so that
//no control tries to process the key
exit;
end;
if H_MenuOpen then
begin
if key=vk_X then
begin
//x has been pressed
*Your code here*
//possibly invalidate some of the params again
exit;
end;
//Nothing valid
H_MenuOpen:=False;
end;
end;
OK, since apparently nobody has found an answer, here is what I ended up doing:
I had already planned to show a hint window listing all possible characters for the second key (actually that code was already working fine, using the approach suggested by Helen Fairgrieve in her answer to this question). Instead, I now register only a one-key shortcut:
BindingServices.AddKeyBinding([FirstShortCut],
TwoKeyBindingHandler, nil,
DefaultKeyBindingsFlag, '', '');
And in the TwoKeyBindingHandler method, I show a popup menu which contains those characters as the shortcuts. The IDE/VCL/Windows then handles the rest for me.
This is what it looks like:
It's not an answer to the actual question but it solves my problem. Sorry if you got here expecting something more.
I have a Delphi 2007 Win32 executable which sends keystrokes to other applications. My app is invoked from within these target applications by a hotkey like F11 or Shift+F11.
I want users to be able to hold down a key to abort the keystroke sending (say, if they realize they invoked my app in the wrong location). I had thought Shift, Ctrl, and Alt were good candidates because, alone, those key presses aren't likely to disrupt anything in the target application. (Escape, for instance, is a bad choice, as it might cause the target application to close one or more windows.)
I wrote the function farther below and call it periodically as follows, while sending keystrokes with the intent of detecting keys held down.
if wsAnyKeysDownInWindow( TgtWindow, [VK_Escape, VK_Menu{Alt}, VK_Control, VK_Shift] ) then
Abort;
Problem is, my app sends keystroke combinations like Shift+Tab and Ctrl+Home, which (I think) makes this approach fail--it always detects a down state for Shift and/or Ctrl. (I also tried a similar function which called SetKeyboardState just prior to beginning to send keystrokes, to set the key states' high-order (down) bit but that didn't help.)
Anyone think of a workable approach, short of hooking the keyboard?
function wsAnyKeysDownInWindow(Handle: HWnd; VKeys: array of byte): boolean;
{ Checks whether each of the VKeys set of virtual keys is down in Handle,
a window created by another process. }
var
OtherThreadID : integer;
State: TKeyboardState;
AKey: byte;
begin
Result := False;
if not IsWindow(Handle) then
exit;
OtherThreadID := GetWindowThreadProcessID( Handle, nil);
if AttachThreadInput( GetCurrentThreadID, OtherThreadID, True ) then try
GetKeyboardState(State);
for AKey in VKeys do
if (State[AKey] and 128) <> 0 then begin //If high-order bit is set, key is down
Result := True;
exit;
end;
finally
AttachThreadInput( GetCurrentThreadID, OtherThreadID, False );
end;
end;
Consider using Scroll Lock for this. It is rarely used (only of Excel comes to my mind), and you will have even visual indicator if keys are being sent or not.
BTW, Alt is not a good choice for another reason - it invokes the main menu in an application (if there is one, of course).
I have a 3d object in a delphi program and I am trying to program to control his movement in 3d space with arrow keys.
First problem: For single arrow keys the movement works fine - the object turns around or goes forward according to the key pressed, but it doesn't seem to work with multiple key press. In case of the UP key and Left or Right key press, it rotates but does not go forward.
Second problem: The UP key movement is somehow delayed. Only 0.5 seconds after the UP key is pressed, the object starts moving forward. Why is that?
Here's the code:
function KeyPressed(nKey: Integer): Boolean;
begin
Result := (GetAsyncKeyState(nKey) and $8000) <> 0;
end;
procedure TForm1.FormKeyDown(Sender: TObject; var Key: Word;
Shift: TShiftState);
begin
if keypressed(VK_LEFT) then
if heroMove.angle < 360 then inc(heroMove.angle,5) else heroMove.angle:=0
else if keypressed(VK_RIGHT) then
if heroMove.angle < 360 then inc(heroMove.angle,-5) else heroMove.angle:=0 else
if keypressed(VK_UP) then
begin
heroMove.x:=heroMove.x+0.2*Sin(heroMove.angle*3.1415/180);
heroMove.y:=heroMove.y+0.2*Cos(heroMove.angle*3.1415/180);
pose:=1;
end;
if keypressed(VK_LEFT) and keypressed(VK_UP) then
begin
if heroMove.angle < 360 then inc(heroMove.angle,5) else heroMove.angle:=0;
heroMove.x:=heroMove.x+0.2*Sin(heroMove.angle*3.1415/180);
heroMove.y:=heroMove.y+0.2*Cos(heroMove.angle*3.1415/180);
pose:=1;
end else
if keypressed(VK_RIGHT) and keypressed(VK_UP) then
begin
if heroMove.angle < 360 then inc(heroMove.angle,-5) else heroMove.angle:=0;
heroMove.x:=heroMove.x+0.2*Sin(heroMove.angle*3.1415/180);
heroMove.y:=heroMove.y+0.2*Cos(heroMove.angle*3.1415/180);
pose:=1;
end;
end;
procedure TForm1.FormKeyUp(Sender: TObject; var Key: Word; Shift: TShiftState);
begin
if Key=VK_UP then
pose:=0;
end;
P.S. The parameter "pose" just controls the animation type of the object.
What you need here is to stop using event driven programming methods. You need to switch to a more tradition game programming style.
You want to run a game loop that provides a steady pulse to your program. Each iteration of this loop you check the keyboard state. Use GetAsyncKeyState for that. Check the state of each key that you are interested in. In your game loop you will also be driving any updates to the OpenGL canvas.
I can see from your update that you are already using GetAsyncKeyState. But that really does not make a lot of sense when used in response to an OnKeyDown event. Either you use event driven synchronous I/O, or polling asynchronous I/O. I cannot understand why you are trying to mix the two paradigms.
The way you call GetAsyncKeyState looks wrong. You should write it like this:
function KeyPressed(nKey: Integer): Boolean;
begin
Result := GetAsyncKeyState(nKey) < 0;
end;
I also suggest that in each iteration of your main loop you call GetAsyncKeyState once and once only for each key. Save the returned value to a local variable. That avoids the scenario where a key is deemed to be down at the start of the iteration, but then the same key is up at some later point in the iteration. That's an issue you need to be alive to with asynchronous I/O.
I am trying to write international program and need to send some text to "other text edit programs" like word or notepad or a browser. On the other hand I am not sure that I can find an international way(because of the different keyboard layouts)
it would be nice to use a code like below
SendMessage(FindActiveWindowsHWND,WM_SETTEXT,0,Integer(PChar('My String')));
and I dont have function like FindActiveWindowsHWND
Edit: The code I am tried but not satisfied so far;
procedure FindActiveWindowsHWND();
var
ThreadInfo: TGUIThreadInfo;
activewindowsHwnd: HWND;
begin
GetGUIThreadInfo(0,ThreadInfo);
activewindowsHwnd:= ThreadInfo.hwndActive; (or ThreadInfo.hwndFocus);
end;
also I used Sendinput function like this
procedure SendKey(vKey: SmallInt; booDown: boolean);
var
GInput: array[0..0] of tagINPUT; //GENERALINPUT;
// doesn't have to be array :)
begin
GInput[0].Itype := INPUT_KEYBOARD;
GInput[0].ki.wVk := vKey;
GInput[0].ki.wScan := 0;
GInput[0].ki.time := 0;
GInput[0].ki.dwExtraInfo := 0;
if not booDown then
GInput[0].ki.dwFlags := KEYEVENTF_KEYUP
else
GInput[0].ki.dwFlags := 0;
SendInput(1, GInput[0], SizeOf(GInput));
end;
then
SendKey(65,true); //to send an "A" for example
but instead it sent an "a" and when I try to send an "a" using SendKey(97,true) it sent "1".
it is really interesting that I have to send shift key down to write uppercase letters
You can use GetGUIThreadInfo() to get the HWND of the currently focused window in another process. Not all window types accept WM_SETTEXT, though. You could use SendInput() to put Unicode characters into the keyboard queue, though. Or use the Automation API, like David said, though not all window types implement that.