Recent

Author Topic: Sending keystrokes to the previously focused app similar to how Autohotkey does.  (Read 3645 times)

Rave

  • Full Member
  • ***
  • Posts: 163
So I am developing an on-screen shortcut app to help me with drawing and such (will be releasing it as GPL on Github), but I am stumped by the actual process of sending the keystrokes to the windows.

The closest I got was by using FindWindow thing, however it requires to specify window class or at least its name, whereas I need the key strokes to be passed to the window that was in focus just before the user clicked shortcut button on my form, without any knowledge of its name or class.

The shortcut window would be always on top and semi-transparent, but I need to make it so the thing can actually be used.

The software will be Windows-only for the time being, so the portability is not the issue right now.

GetMem

  • Hero Member
  • *****
  • Posts: 3752
So I am developing an on-screen shortcut app to help me with drawing and such (will be releasing it as GPL on Github), but I am stumped by the actual process of sending the keystrokes to the windows.

The closest I got was by using FindWindow thing, however it requires to specify window class or at least its name, whereas I need the key strokes to be passed to the window that was in focus just before the user clicked shortcut button on my form, without any knowledge of its name or class.

The shortcut window would be always on top and semi-transparent, but I need to make it so the thing can actually be used.

The software will be Windows-only for the time being, so the portability is not the issue right now.

1. Download, build then run attached project
2. Start notepad
3. Switch back to attached project, then click "Send Keys" button

Rave

  • Full Member
  • ***
  • Posts: 163
That's a step in a good direction, however I can't use it as it depends on MouseAndKeyInput which doesn't support pressing alt (which would be an awful limitation of my program to have). What I need is an ability to send specific keystrokes (like Ctrl+N or Alt+F4 to the previously focused application, not random text.

Also, notice what happens when the text to be sent is "Ctrl+A" (without quotes). No, it doesn't press Ctrl+A in the notepad, neither it does make it write Ctrl+A as a text.
« Last Edit: January 05, 2022, 10:11:25 am by Rave »

Thaddy

  • Hero Member
  • *****
  • Posts: 11637
There is a unit sendkeys.pas that compiles in mode delph with my minor changes.
 Tested win32/win64
Code: Pascal  [Select][+][-]
  1. unit sendkeys;
  2. {$mode delphi}
  3. { originally written for delphi 1 by Ken Henderson. Freeware.
  4.   modified for Freepascal by Thaddy de Koning }
  5. interface
  6.  
  7. Uses SysUtils, Windows, Messages;
  8.  
  9. Function fpSendKeys(SendKeysString : PChar; Wait : Boolean) : Boolean;
  10. function AppActivate(WindowName : PChar) : boolean;
  11.  
  12. {Buffer for working with PChar's}
  13.  
  14. const
  15.   WorkBufLen = 40;
  16. var
  17.   WorkBuf : array[0..WorkBufLen] of Char;
  18.  
  19. implementation
  20. type
  21.   THKeys = array[0..pred(MaxLongInt)] of byte;
  22. var
  23.   AllocationSize : integer;
  24.  
  25. (*
  26. Converts a string of characters and key names to keyboard events and
  27. passes them to Windows.
  28.  
  29. Example syntax:
  30.  
  31. SendKeys('abc123{left}{left}{left}def{end}456{left 6}ghi{end}789', True);
  32. *)
  33.  
  34. Function fpSendKeys(SendKeysString : PChar; Wait : Boolean) : Boolean;
  35. type
  36.   WBytes = array[0..pred(SizeOf(Word))] of Byte;
  37.  
  38.   TSendKey = record
  39.     Name : ShortString;
  40.     VKey : Byte;
  41.   end;
  42.  
  43. const
  44.   {Array of keys that SendKeys recognizes.
  45.   If you add to this list, you must be sure to keep it sorted alphabetically
  46.   by Name because a binary search routine is used to scan it.}
  47.  
  48.   MaxSendKeyRecs = 41;
  49.   SendKeyRecs : array[1..MaxSendKeyRecs] of TSendKey =
  50.   (
  51.    (Name:'BACKSPACE';       VKey:VK_BACK),
  52.    (Name:'BKSP';            VKey:VK_BACK),
  53.    (Name:'BREAK';           VKey:VK_CANCEL),
  54.    (Name:'BS';              VKey:VK_BACK),
  55.    (Name:'CAPSLOCK';        VKey:VK_CAPITAL),
  56.    (Name:'CLEAR';           VKey:VK_CLEAR),
  57.    (Name:'DEL';             VKey:VK_DELETE),
  58.    (Name:'DELETE';          VKey:VK_DELETE),
  59.    (Name:'DOWN';            VKey:VK_DOWN),
  60.    (Name:'END';             VKey:VK_END),
  61.    (Name:'ENTER';           VKey:VK_RETURN),
  62.    (Name:'ESC';             VKey:VK_ESCAPE),
  63.    (Name:'ESCAPE';          VKey:VK_ESCAPE),
  64.    (Name:'F1';              VKey:VK_F1),
  65.    (Name:'F10';             VKey:VK_F10),
  66.    (Name:'F11';             VKey:VK_F11),
  67.    (Name:'F12';             VKey:VK_F12),
  68.    (Name:'F13';             VKey:VK_F13),
  69.    (Name:'F14';             VKey:VK_F14),
  70.    (Name:'F15';             VKey:VK_F15),
  71.    (Name:'F16';             VKey:VK_F16),
  72.    (Name:'F2';              VKey:VK_F2),
  73.    (Name:'F3';              VKey:VK_F3),
  74.    (Name:'F4';              VKey:VK_F4),
  75.    (Name:'F5';              VKey:VK_F5),
  76.    (Name:'F6';              VKey:VK_F6),
  77.    (Name:'F7';              VKey:VK_F7),
  78.    (Name:'F8';              VKey:VK_F8),
  79.    (Name:'F9';              VKey:VK_F9),
  80.    (Name:'HELP';            VKey:VK_HELP),
  81.    (Name:'HOME';            VKey:VK_HOME),
  82.    (Name:'INS';             VKey:VK_INSERT),
  83.    (Name:'LEFT';            VKey:VK_LEFT),
  84.    (Name:'NUMLOCK';         VKey:VK_NUMLOCK),
  85.    (Name:'PGDN';            VKey:VK_NEXT),
  86.    (Name:'PGUP';            VKey:VK_PRIOR),
  87.    (Name:'PRTSC';           VKey:VK_PRINT),
  88.    (Name:'RIGHT';           VKey:VK_RIGHT),
  89.    (Name:'SCROLLLOCK';      VKey:VK_SCROLL),
  90.    (Name:'TAB';             VKey:VK_TAB),
  91.    (Name:'UP';              VKey:VK_UP)
  92.   );
  93.  
  94.   {Extra VK constants missing from Delphi's Windows API interface}
  95.   VK_NULL=0;
  96.   VK_SemiColon=186;
  97.   VK_Equal=187;
  98.   VK_Comma=188;
  99.   VK_Minus=189;
  100.   VK_Period=190;
  101.   VK_Slash=191;
  102.   VK_BackQuote=192;
  103.   VK_LeftBracket=219;
  104.   VK_BackSlash=220;
  105.   VK_RightBracket=221;
  106.   VK_Quote=222;
  107.   VK_Last=VK_Quote;
  108.  
  109.   ExtendedVKeys : set of byte =
  110.   [VK_Up,
  111.    VK_Down,
  112.    VK_Left,
  113.    VK_Right,
  114.    VK_Home,
  115.    VK_End,
  116.    VK_Prior,  {PgUp}
  117.    VK_Next,   {PgDn}
  118.    VK_Insert,
  119.    VK_Delete];
  120.  
  121. const
  122.   INVALIDKEY = $FFFF {Unsigned -1};
  123.   VKKEYSCANSHIFTON = $01;
  124.   VKKEYSCANCTRLON = $02;
  125.   VKKEYSCANALTON = $04;
  126.   UNITNAME = 'SendKeys';
  127. var
  128.   UsingParens, ShiftDown, ControlDown, AltDown, FoundClose : Boolean;
  129.   PosSpace : Byte;
  130.   I, L : Integer;
  131.   NumTimes, MKey : Word;
  132.   KeyString : String[20];
  133.  
  134. procedure DisplayMessage(Message : PChar);
  135. begin
  136.   MessageBox(0,Message,UNITNAME,0);
  137. end;
  138.  
  139. function BitSet(BitTable, BitMask : Byte) : Boolean;
  140. begin
  141.   Result:=ByteBool(BitTable and BitMask);
  142. end;
  143.  
  144. procedure SetBit(var BitTable : Byte; BitMask : Byte);
  145. begin
  146.   BitTable:=BitTable or Bitmask;
  147. end;
  148.  
  149. Procedure KeyboardEvent(VKey, ScanCode : Byte; Flags : Longint);
  150. var
  151.   KeyboardMsg : TMsg;
  152. begin
  153.   keybd_event(VKey, ScanCode, Flags,0);
  154.   If (Wait) then
  155.     While (PeekMessage(KeyboardMsg,0,WM_KEYFIRST, WM_KEYLAST, PM_REMOVE)) do
  156.     begin
  157.       TranslateMessage(KeyboardMsg);
  158.       DispatchMessage(KeyboardMsg);
  159.     end;
  160. end;
  161.  
  162. Procedure SendKeyDown(VKey: Byte; NumTimes : Word; GenUpMsg : Boolean);
  163. var
  164.   Cnt : Word;
  165.   ScanCode : Byte;
  166.   NumState : Boolean;
  167.   KeyBoardState : TKeyboardState;
  168. begin
  169.   if (VKey=VK_NUMLOCK) then
  170.   begin
  171.     NumState:=ByteBool(GetKeyState(VK_NUMLOCK) and 1);
  172.     GetKeyBoardState(KeyBoardState);
  173.     If NumState then
  174.       KeyBoardState[VK_NUMLOCK]:=(KeyBoardState[VK_NUMLOCK] and not 1)
  175.     else
  176.       KeyBoardState[VK_NUMLOCK]:=(KeyBoardState[VK_NUMLOCK] or 1);
  177.     SetKeyBoardState(KeyBoardState);
  178.     Exit;
  179.   end;
  180.  
  181.   ScanCode:=Lo(Byte(MapVirtualKey(VKey,0)));
  182.   For Cnt:=1 to NumTimes do
  183.     If (VKey in ExtendedVKeys) then
  184.     begin
  185.       KeyboardEvent(VKey, ScanCode, KEYEVENTF_EXTENDEDKEY);
  186.       If (GenUpMsg) then
  187.         KeyboardEvent(VKey, ScanCode, KEYEVENTF_EXTENDEDKEY or KEYEVENTF_KEYUP)
  188.     end
  189.     else
  190.     begin
  191.       KeyboardEvent(VKey, ScanCode, 0);
  192.       If (GenUpMsg) then KeyboardEvent(VKey, ScanCode, KEYEVENTF_KEYUP);
  193.     end;
  194. end;
  195.  
  196. Procedure SendKeyUp(VKey: Byte);
  197. var
  198.   ScanCode : Byte;
  199. begin
  200.   ScanCode:=Lo(Byte(MapVirtualKey(VKey,0)));
  201.   If (VKey in ExtendedVKeys)then
  202.     KeyboardEvent(VKey, ScanCode, KEYEVENTF_EXTENDEDKEY and KEYEVENTF_KEYUP)
  203.   else
  204.     KeyboardEvent(VKey, ScanCode, KEYEVENTF_KEYUP);
  205. end;
  206.  
  207. Procedure SendKey(MKey: Word; NumTimes : Word; GenDownMsg : Boolean);
  208. begin
  209.   If (BitSet(Hi(MKey),VKKEYSCANSHIFTON)) then
  210.     SendKeyDown(VK_SHIFT,1,False);
  211.   If (BitSet(Hi(MKey),VKKEYSCANCTRLON)) then
  212.     SendKeyDown(VK_CONTROL,1,False);
  213.   If (BitSet(Hi(MKey),VKKEYSCANALTON)) then
  214.     SendKeyDown(VK_MENU,1,False);
  215.   SendKeyDown(Lo(MKey), NumTimes, GenDownMsg);
  216.   If (BitSet(Hi(MKey),VKKEYSCANSHIFTON)) then
  217.     SendKeyUp(VK_SHIFT);
  218.   If (BitSet(Hi(MKey),VKKEYSCANCTRLON)) then
  219.     SendKeyUp(VK_CONTROL);
  220.   If (BitSet(Hi(MKey),VKKEYSCANALTON)) then
  221.     SendKeyUp(VK_MENU);
  222. end;
  223.  
  224. {Implements a simple binary search to locate special key name strings}
  225.  
  226. Function StringToVKey(KeyString : ShortString) : Word;
  227. var
  228.   Found, Collided : Boolean;
  229.   Bottom, Top, Middle : Byte;
  230. begin
  231.   Result:=INVALIDKEY;
  232.   Bottom:=1;
  233.   Top:=MaxSendKeyRecs;
  234.   Found:=false;
  235.   Middle:=(Bottom+Top) div 2;
  236.   Repeat
  237.     Collided:=((Bottom=Middle) or (Top=Middle));
  238.     If (KeyString=SendKeyRecs[Middle].Name) then
  239.     begin
  240.        Found:=True;
  241.        Result:=SendKeyRecs[Middle].VKey;
  242.     end
  243.     else
  244.     begin
  245.        If (KeyString>SendKeyRecs[Middle].Name) then
  246.         Bottom:=Middle
  247.        else
  248.         Top:=Middle;
  249.        Middle:=(Succ(Bottom+Top)) div 2;
  250.     end;
  251.   Until (Found or Collided);
  252.   If (Result=INVALIDKEY) then
  253.     DisplayMessage('Invalid Key Name');
  254. end;
  255.  
  256. procedure PopUpShiftKeys;
  257. begin
  258.   If (not UsingParens) then
  259.   begin
  260.     If ShiftDown then SendKeyUp(VK_SHIFT);
  261.     If ControlDown then SendKeyUp(VK_CONTROL);
  262.     If AltDown then SendKeyUp(VK_MENU);
  263.     ShiftDown:=false;
  264.     ControlDown:=false;
  265.     AltDown:=false;
  266.   end;
  267. end;
  268.  
  269. begin
  270.   AllocationSize:=MaxInt;
  271.   Result:=false;
  272.   UsingParens:=false;
  273.   ShiftDown:=false;
  274.   ControlDown:=false;
  275.   AltDown:=false;
  276.   I:=0;
  277.   L:=StrLen(SendKeysString);
  278.   If (L>AllocationSize) then L:=AllocationSize;
  279.   If (L=0) then Exit;
  280.  
  281.   While (I<L) do
  282.   begin
  283.     case SendKeysString[I] of
  284.     '(' : begin
  285.             UsingParens:=True;
  286.             Inc(I);
  287.           end;
  288.     ')' : begin
  289.             UsingParens:=False;
  290.             PopUpShiftKeys;
  291.             Inc(I);
  292.           end;
  293.     '%' : begin
  294.              AltDown:=True;
  295.              SendKeyDown(VK_MENU,1,False);
  296.              Inc(I);
  297.           end;
  298.     '+' :  begin
  299.              ShiftDown:=True;
  300.              SendKeyDown(VK_SHIFT,1,False);
  301.              Inc(I);
  302.            end;
  303.     '^' :  begin
  304.              ControlDown:=True;
  305.              SendKeyDown(VK_CONTROL,1,False);
  306.              Inc(I);
  307.            end;
  308.     '{' : begin
  309.             NumTimes:=1;
  310.             If (SendKeysString[Succ(I)]='{') then
  311.             begin
  312.               MKey:=VK_LEFTBRACKET;
  313.               SetBit(Wbytes(MKey)[1],VKKEYSCANSHIFTON);
  314.               SendKey(MKey,1,True);
  315.               PopUpShiftKeys;
  316.               Inc(I,3);
  317.               Continue;
  318.             end;
  319.             KeyString:='';
  320.             FoundClose:=False;
  321.             While (I<=L) do
  322.             begin
  323.               Inc(I);
  324.               If (SendKeysString[I]='}') then
  325.               begin
  326.                 FoundClose:=True;
  327.                 Inc(I);
  328.                 Break;
  329.               end;
  330.               KeyString:=KeyString+Upcase(SendKeysString[I]);
  331.             end;
  332.             If (Not FoundClose) then
  333.             begin
  334.               DisplayMessage('No Close');
  335.               Exit;
  336.             end;
  337.             If (SendKeysString[I]='}') then
  338.             begin
  339.               MKey:=VK_RIGHTBRACKET;
  340.               SetBit(Wbytes(MKey)[1],VKKEYSCANSHIFTON);
  341.               SendKey(MKey,1,True);
  342.               PopUpShiftKeys;
  343.               Inc(I);
  344.               Continue;
  345.             end;
  346.             PosSpace:=Pos(' ',KeyString);
  347.             If (PosSpace<>0) then
  348.             begin
  349.               NumTimes := StrToInt(
  350.                 Copy(KeyString,Succ(PosSpace),Length(KeyString)-PosSpace)
  351.               );
  352.               KeyString:=Copy(KeyString,1,Pred(PosSpace));
  353.             end;
  354.             If (Length(KeyString)=1) then
  355.               MKey:=vkKeyScan(KeyString[1])
  356.             else
  357.               MKey:=StringToVKey(KeyString);
  358.             If (MKey<>INVALIDKEY) then
  359.             begin
  360.               SendKey(MKey,NumTimes,True);
  361.               PopUpShiftKeys;
  362.               Continue;
  363.             end;
  364.           end;
  365.     '~' : begin
  366.             SendKeyDown(VK_RETURN,1,True);
  367.             PopUpShiftKeys;
  368.             Inc(I);
  369.           end;
  370.     else
  371.       begin
  372.         MKey:=vkKeyScan(SendKeysString[I]);
  373.         if (MKey<>INVALIDKEY) then
  374.         begin
  375.           SendKey(MKey,1,True);
  376.           PopUpShiftKeys;
  377.         end
  378.         else
  379.           DisplayMessage('Invalid KeyName');
  380.         Inc(I);
  381.       end;
  382.     end;
  383.   end;
  384.   Result:=true;
  385.   PopUpShiftKeys;
  386. end;
  387.  
  388. {AppActivate
  389.  
  390. This is used to set the current input focus to a given window using its
  391. name.  This is especially useful for ensuring a window is active before
  392. sending it input messages using the SendKeys function.  You can specify
  393. a window's name in its entirety, or only portion of it, beginning from
  394. the left.}
  395.  
  396. var
  397.   WindowHandle : HWND;
  398.  
  399. function EnumWindowsProc(WHandle: HWND; lParam: LPARAM): BOOL; export; stdcall;
  400. type
  401.   TWindowName = array[0..max_path] of char;
  402. var
  403.   WindowName : TWindowName;
  404. begin
  405.   WindowName :=default(TWindowName);
  406.   {Can't test GetWindowText's return value since some windows don't have a
  407.   title}
  408.   GetWindowText(WHandle,WindowName,MAX_PATH);
  409.   Result := (StrLIComp(WindowName,PChar(lParam), StrLen(PChar(lParam))) <> 0);
  410.   If (not Result) then
  411.     WindowHandle:=WHandle;
  412. end;
  413.  
  414. function AppActivate(WindowName : PChar) : boolean;
  415. begin
  416.   try
  417.     Result:=true;
  418.     WindowHandle:=FindWindow(nil,WindowName);
  419.     If (WindowHandle=0) then
  420.       EnumWindows(@EnumWindowsProc,PtrInt(PChar(WindowName)));
  421.     If (WindowHandle<>0) then
  422.     begin
  423.       SendMessage(WindowHandle, WM_SYSCOMMAND, SC_HOTKEY, WindowHandle);
  424.       SendMessage(WindowHandle, WM_SYSCOMMAND, SC_RESTORE, WindowHandle);
  425.       SetForegroundWindow(WindowHandle);
  426.     end
  427.     else
  428.       Result:=false;
  429.   except
  430.     on Exception do Result:=false;
  431.   end;
  432. end;
  433.  
  434. end.

Use: startup notepad and minimize. (In my case it is named kladblok, because I am on a dutch win64. Then run this program:
Code: Pascal  [Select][+][-]
  1. program testkeys;
  2. {$mode delphi}
  3. uses windows,sendkeys;
  4. begin
  5.   AppActivate('kladblok');
  6.   fpSendKeys('abcdefg',false);
  7. end.

Result:
« Last Edit: January 05, 2022, 11:29:51 am by Thaddy »
Black themes should be banned.

Rave

  • Full Member
  • ***
  • Posts: 163
Please re-read the OP. I can't know the previous window's name or class, because what I am developing is a macro program, like this thing only free and open-source. So anything involving knowing what the previous window class and or name is is a no go for me, as I need to send the keys to an arbitrary app (usually the one that was in the focus before user clicked one of the buttons in the app).

GetMem's solution was better in that regard, but still not something I can really use.
« Last Edit: January 05, 2022, 11:32:33 am by Rave »

GetMem

  • Hero Member
  • *****
  • Posts: 3752
@Rave
Quote
That's a step in a good direction, however I can't use it as it depends on MouseAndKeyInput which doesn't support pressing alt (which would be an awful limitation of my program to have).
I removed MouseAndKeyInput.

Quote
What I need is an ability to send specific keystrokes (like Ctrl+N or Alt+F4 to the previously focused application, not random text.
You can send Alt + F4, but you have to change the code a little bit. See attached project.

PS: When testing make sure the last focused window was notepad, otherwise you will send key combination to different random windows.

 

TinyPortal © 2005-2018