SendKey ?

Mac OSX specific forum
Eidos
New User
New User
Posts: 3
Joined: Thu Feb 16, 2012 10:28 am

SendKey ?

Post by Eidos »

Hi All,

This my first post :) --
Can you please tell, whether a SendKey analog exist in MAC OS?
Any OSX Api function to simulate keybd_event like in Windows?
Is it possible to do it in PureBasic for Mac?

Many thanks!
WilliamL
Addict
Addict
Posts: 1214
Joined: Mon Aug 04, 2008 10:56 pm
Location: Seattle, USA

Re: SendKey ?

Post by WilliamL »

Last edited by WilliamL on Thu Feb 16, 2012 8:50 pm, edited 1 time in total.
MacBook Pro-M1 (2021), Sonoma 14.3.1 (CLT 15.3), PB 6.10b7 M1
User avatar
Shardik
Addict
Addict
Posts: 1989
Joined: Thu Apr 21, 2005 2:38 pm
Location: Germany

Re: SendKey ?

Post by Shardik »

At first: welcome to the PureBasic Forum, Eidos... :wink:

What do you want to achieve?

If you want to simulate a key press into a StringGadget this can be
done very easily and cross-platform:

Code: Select all

EnableExplicit

OpenWindow(0, 270, 100, 270, 70, "Simulate key press", #PB_Window_SystemMenu | #PB_Window_SizeGadget)
StringGadget(0, 10, 10, WindowWidth(0) - 20, 20, "")
ButtonGadget(1, 30, 40, WindowWidth(0) - 60, 20, "Simulate pressing of <K> key")

Repeat
  Select WaitWindowEvent()
    Case #PB_Event_CloseWindow
      Break
    Case #PB_Event_Gadget
      If EventGadget() = 1
        If EventType() = #PB_EventType_LeftClick
          SetGadgetText(0, GetGadgetText(0) + "K")
        EndIf
      EndIf
  EndSelect
ForEver
If you want to simulate a key press by generating an internal event,
things are becoming more complicated:

Code: Select all

EnableExplicit

ImportC ""
  CreateEvent(MemoryAllocatorRef.L, EventClass.L, EventKind.L, EventTime.D, EventAttributes.L, *EventRef)
  SendEventToEventTargetWithOptions(EventRef.L, EventTargetRef.L, OptionBits.L)
EndImport

#kEventAttributeNone = 0
#kEventClassControl = 'cntl'
#kEventParamTextInputSendKeyboardEvent = 'tske'

Structure EventTypeSpec
  EventClass.L
  EventKind.L
EndStructure

ProcedureC ControlHandler(*NextEventHandler, EventRef.L, UserData.L)
  Protected Result.L

  If GetEventKind_(EventRef) = #kEventParamTextInputSendKeyboardEvent
    SetGadgetText(0, GetGadgetText(0) + "K")
  EndIf
EndProcedure

Dim EventTypes.EventTypeSpec(0)

Define EventRef.L
Define Result.L

OpenWindow(0, 270, 100, 270, 70, "Simulate key press", #PB_Window_SystemMenu | #PB_Window_SizeGadget)
StringGadget(0, 10, 10, WindowWidth(0) - 20, 20, "")
ButtonGadget(1, 30, 40, WindowWidth(0) - 60, 20, "Simulate pressing of <K> key")

EventTypes(0)\EventClass = #kEventClassControl
EventTypes(0)\EventKind = #kEventParamTextInputSendKeyboardEvent
InstallEventHandler_(GetControlEventTarget_(GadgetID(0)), @ControlHandler(), 1, @EventTypes(), 0, 0)

If CreateEvent(0, #kEventClassControl, #kEventParamTextInputSendKeyboardEvent, 0, #kEventAttributeNone, @EventRef)
  Debug "CreateEvent() failed!"
  End
EndIf

Repeat
  Select WaitWindowEvent()
    Case #PB_Event_CloseWindow
      Break
    Case #PB_Event_Gadget
      If EventGadget() = 1
        If EventType() = #PB_EventType_LeftClick
          SendEventToEventTarget_(EventRef, GetControlEventTarget_(GadgetID(0)))
        EndIf
      EndIf
  EndSelect
ForEver
If you want to know how to detect the pressing of keys (and clicks onto
different mouse buttons) in a window (not as input into a StringGadget)
and display these actions in a TextGadget, then you might take a look
into this thread.
Eidos
New User
New User
Posts: 3
Joined: Thu Feb 16, 2012 10:28 am

Re: SendKey ?

Post by Eidos »

Thank you for reply!

Actually, I'm writing a simple plugin for QuarkXpress and want to send key/word to edit window of QXP.
I've tried apple script, but that works too slowly and sometimes hanging the whole Qxp.
The solution was to make a simple application that stays on top and sending keystrokes to Quark on demand (pressing button)

The text is compiling inside the plugin and copied into the clipboard. Then switching to Qxp and simulating the APPLE + V (paste) operation. All wanted to do, is to simulate the Apple+V command.

here is the code where I'm trying to achieve this:

Code: Select all

  Repeat
    evt = WaitWindowEvent()
  Select evt
    Case #PB_Event_CloseWindow
      quit = 1
    Case #PB_Event_Gadget
      eg = EventGadget()
      If eg >=0 And eg <=11 
        SetClipboardText (GetGadgetText(eg)) ; put text into clipboard ...
        DisableWindow (0, 1) ; remove sticky option for dialog
        ; <<---- Here, simulate Apple+V keystroke
        DisableWindow (0, 0) ; back sticky option
      EndIf 
   EndSelect    
  Until quit = 1  
I hope that your second solution will fit my requirements.
Many thanks!
WilliamL
Addict
Addict
Posts: 1214
Joined: Mon Aug 04, 2008 10:56 pm
Location: Seattle, USA

Re: SendKey ?

Post by WilliamL »

Well, how about that!?

Anyway, we got two routines from shardik for sending and getting keystrokes.

Thanks again shardik!
MacBook Pro-M1 (2021), Sonoma 14.3.1 (CLT 15.3), PB 6.10b7 M1
User avatar
Shardik
Addict
Addict
Posts: 1989
Joined: Thu Apr 21, 2005 2:38 pm
Location: Germany

Re: SendKey ?

Post by Shardik »

Eidos,

thank you for the description of your intended project. Unfortunately I
have no QuarkXpress to code a customized solution, but I have tried to
find a similar solution which starts TextEdit and sends keystrokes to be
displayed in TextEdit (or each other application which currently has the
keyboard focus). You have to send the virtual key codes and the modifier
key mask (for example if you have the need to press the Shift key
simultaneously). Perhaps you should modify the SendKey() procedure to
accept a normal ANSI character which will then of course have to be
translated to the corresponding virtual key code. Since you can call
SendKey() multiple times, there is no need to use the clipboard anymore... :wink:

Code: Select all

EnableExplicit

#kCGHIDEventTap= 0

; ----- Virtual keycodes (defined in Events.h)

#kVK_ANSI_E = $0E
#kVK_ANSI_H = $04
#kVK_ANSI_L = $25
#kVK_ANSI_O = $1F
#kVK_Return = $24

; ----- Modifier key masks (defined in IOLLEvent.h)

#kCGEventFlagMaskShift     = $020000 ; = NX_SHIFTMASK
#kCGEventFlagMaskControl   = $040000 ; = NX_CONTROLMASK
#kCGEventFlagMaskAlternate = $080000 ; = NX_ALTERNATEMASK
#kCGEventFlagMaskCommand   = $100000 ; = NX_COMMANDMASK

ImportC ""
  CFRelease(CFTypeRef.L)
  CGEventCreateKeyboardEvent(CGEventSourceRef.L, CGVirtualKeyCode.U, KeyDown.L)
  CGEventPost(CGEventTapLocation.L, CGEventRef.L)
  CGEventSetFlags(CGEventRef.L, CGEventFlags.L)
EndImport

Procedure.L SendKey(VirtualKey.U, ModifierKey.L = 0)
  Protected KeyEvent.L

  KeyEvent = CGEventCreateKeyboardEvent(0, VirtualKey, #True)

  If KeyEvent
    ; ----- Press key
    CGEventSetFlags(KeyEvent, ModifierKey)
    CGEventPost(#kCGHIDEventTap, KeyEvent)
    CFRelease(KeyEvent)

    KeyEvent = CGEventCreateKeyboardEvent(0, VirtualKey, #False)
    CGEventSetFlags(KeyEvent, ModifierKey)

    If KeyEvent
      ; ----- Release key
      CGEventPost(#kCGHIDEventTap, KeyEvent)
      CFRelease(KeyEvent)
    EndIf
  EndIf

  ProcedureReturn KeyEvent
EndProcedure

Define Msg.S = "Hello!"
Define ProgramID.L

ProgramID = RunProgram("Open", "/Applications/TextEdit.app", "", #PB_Program_Open)

If ProgramID
  ; ----- Wait until TextEdit is running (increase delay if necessary)
  Delay(100)
  ; ----- Send Keys to TextEdit
  SendKey(#kVK_ANSI_H, #kCGEventFlagMaskShift)
  SendKey(#kVK_ANSI_E)
  SendKey(#kVK_ANSI_L)
  SendKey(#kVK_ANSI_L)
  SendKey(#kVK_ANSI_O)
  SendKey(#kVK_Return)
  CloseProgram(ProgramID)
EndIf
Eidos
New User
New User
Posts: 3
Joined: Thu Feb 16, 2012 10:28 am

Re: SendKey ?

Post by Eidos »

Hi Shardik,

Many thanks for code! That's really useful piece of code.
I came from Windows world and just started with Mac using PB.

I have faced with strange clipboard behavior in PB before implementing your senkey solution in mac.
It seems that SetClipboardText doesn't work properly on my Mac OSX.
The application can put into clipboard text and then read it back. SetClipboardText and GetClipboatdText works fine within the application, BUT SHOW Clipboard operation inside the FINDER doesn't show anything...

Simple text editor, safari and mac Word are showing my entries via APPLE+V (paste from clipboard).

Also, I've tried to copy the text from PB IDE and paste somewhere else: that worked! even showed up in SHOW Clipboard and QuarkXPress, but no luck when using Set/Get clipboard operations.

Can some try to run simple CLIPBOARD example on Mac OS (use only SetClipboardText) and check whether text visible or not in Show Clipboard window.

That's really strange and makes my development efforts useless....

Here is my mac configuration:
Powermac G4, MacOS X 10.4.9. (8p 135), Darwin 8.9.0
QuarkXpress v6.1 PPC
WilliamL
Addict
Addict
Posts: 1214
Joined: Mon Aug 04, 2008 10:56 pm
Location: Seattle, USA

Re: SendKey ?

Post by WilliamL »

Works ok for me. I've been using the clipboard copy/paste in a couple of apps and it has been working for me. When I look in 'Show clipboard', from the finder, it's there... ran example and cmd+V when in TextEdit and it pasted correctly. (only tried the text - canceled the pict load)

Code: Select all

; ------------------------------------------------------------
;
;   PureBasic - Clipboard example file
;
;    (c) Fantaisie Software
;
; ------------------------------------------------------------
;

; Paste text to the clipboard..
SetClipboardText("PureBasic pasted text example.")

; Display the contents of the clipboard...
MessageRequester("Info", "Text in the clipboard"+#LF$+"'"+GetClipboardText()+"'", 0)

; Now paste an image...

If LoadImage(0, OpenFileRequester("Load a picture","","Bitmap (*.bmp)|*.bmp",0))
  If SetClipboardImage(0)

    MessageRequester("Info", "Image correctly pasted to the clipboard."+Chr(10)+"Open 'Paint' to test it !", 0)
  EndIf
EndIf
MacBook Pro-M1 (2021), Sonoma 14.3.1 (CLT 15.3), PB 6.10b7 M1
jvzuck
User
User
Posts: 14
Joined: Sat Aug 15, 2020 6:06 pm

Re: SendKey ?

Post by jvzuck »

Shardik,
Do you believe this code should still work under Catalina? I'm getting an "invalid memory access." That make sense?
Jonathan
Shardik wrote:Eidos,

thank you for the description of your intended project. Unfortunately I
have no QuarkXpress to code a customized solution, but I have tried to
find a similar solution which starts TextEdit and sends keystrokes to be
displayed in TextEdit (or each other application which currently has the
keyboard focus). You have to send the virtual key codes and the modifier
key mask (for example if you have the need to press the Shift key
simultaneously). Perhaps you should modify the SendKey() procedure to
accept a normal ANSI character which will then of course have to be
translated to the corresponding virtual key code. Since you can call
SendKey() multiple times, there is no need to use the clipboard anymore... :wink:

Code: Select all

EnableExplicit

#kCGHIDEventTap= 0

; ----- Virtual keycodes (defined in Events.h)

#kVK_ANSI_E = $0E
#kVK_ANSI_H = $04
#kVK_ANSI_L = $25
#kVK_ANSI_O = $1F
#kVK_Return = $24

; ----- Modifier key masks (defined in IOLLEvent.h)

#kCGEventFlagMaskShift     = $020000 ; = NX_SHIFTMASK
#kCGEventFlagMaskControl   = $040000 ; = NX_CONTROLMASK
#kCGEventFlagMaskAlternate = $080000 ; = NX_ALTERNATEMASK
#kCGEventFlagMaskCommand   = $100000 ; = NX_COMMANDMASK

ImportC ""
  CFRelease(CFTypeRef.L)
  CGEventCreateKeyboardEvent(CGEventSourceRef.L, CGVirtualKeyCode.U, KeyDown.L)
  CGEventPost(CGEventTapLocation.L, CGEventRef.L)
  CGEventSetFlags(CGEventRef.L, CGEventFlags.L)
EndImport

Procedure.L SendKey(VirtualKey.U, ModifierKey.L = 0)
  Protected KeyEvent.L

  KeyEvent = CGEventCreateKeyboardEvent(0, VirtualKey, #True)

  If KeyEvent
    ; ----- Press key
    CGEventSetFlags(KeyEvent, ModifierKey)
    CGEventPost(#kCGHIDEventTap, KeyEvent)
    CFRelease(KeyEvent)

    KeyEvent = CGEventCreateKeyboardEvent(0, VirtualKey, #False)
    CGEventSetFlags(KeyEvent, ModifierKey)

    If KeyEvent
      ; ----- Release key
      CGEventPost(#kCGHIDEventTap, KeyEvent)
      CFRelease(KeyEvent)
    EndIf
  EndIf

  ProcedureReturn KeyEvent
EndProcedure

Define Msg.S = "Hello!"
Define ProgramID.L

ProgramID = RunProgram("Open", "/Applications/TextEdit.app", "", #PB_Program_Open)

If ProgramID
  ; ----- Wait until TextEdit is running (increase delay if necessary)
  Delay(100)
  ; ----- Send Keys to TextEdit
  SendKey(#kVK_ANSI_H, #kCGEventFlagMaskShift)
  SendKey(#kVK_ANSI_E)
  SendKey(#kVK_ANSI_L)
  SendKey(#kVK_ANSI_L)
  SendKey(#kVK_ANSI_O)
  SendKey(#kVK_Return)
  CloseProgram(ProgramID)
EndIf
User avatar
mk-soft
Always Here
Always Here
Posts: 5335
Joined: Fri May 12, 2006 6:51 pm
Location: Germany

Re: SendKey ?

Post by mk-soft »

Code is write for x86.

You mast change the var type long to integer ...

Code: Select all

EnableExplicit

#kCGHIDEventTap= 0

; ----- Virtual keycodes (defined in Events.h)

#kVK_ANSI_E = $0E
#kVK_ANSI_H = $04
#kVK_ANSI_L = $25
#kVK_ANSI_O = $1F
#kVK_Return = $24

; ----- Modifier key masks (defined in IOLLEvent.h)

#kCGEventFlagMaskShift     = $020000 ; = NX_SHIFTMASK
#kCGEventFlagMaskControl   = $040000 ; = NX_CONTROLMASK
#kCGEventFlagMaskAlternate = $080000 ; = NX_ALTERNATEMASK
#kCGEventFlagMaskCommand   = $100000 ; = NX_COMMANDMASK

ImportC ""
  CFRelease(CFTypeRef.i)
  CGEventCreateKeyboardEvent(CGEventSourceRef.i, CGVirtualKeyCode.U, KeyDown.i)
  CGEventPost(CGEventTapLocation.i, CGEventRef.i)
  CGEventSetFlags(CGEventRef.i, CGEventFlags.i)
EndImport

Procedure.i SendKey(VirtualKey.U, ModifierKey.i = 0)
  Protected KeyEvent.i

  KeyEvent = CGEventCreateKeyboardEvent(0, VirtualKey, #True)

  If KeyEvent
    ; ----- Press key
    CGEventSetFlags(KeyEvent, ModifierKey)
    CGEventPost(#kCGHIDEventTap, KeyEvent)
    CFRelease(KeyEvent)

    KeyEvent = CGEventCreateKeyboardEvent(0, VirtualKey, #False)
    CGEventSetFlags(KeyEvent, ModifierKey)

    If KeyEvent
      ; ----- Release key
      CGEventPost(#kCGHIDEventTap, KeyEvent)
      CFRelease(KeyEvent)
    EndIf
  EndIf

  ProcedureReturn KeyEvent
EndProcedure

Define Msg.S = "Hello!"
Define ProgramID.i

ProgramID = RunProgram("Open", "/Applications/TextEdit.app", "", #PB_Program_Open)

If ProgramID
  ; ----- Wait until TextEdit is running (increase delay if necessary)
  Delay(100)
  ; ----- Send Keys to TextEdit
  SendKey(#kVK_ANSI_H, #kCGEventFlagMaskShift)
  SendKey(#kVK_ANSI_E)
  SendKey(#kVK_ANSI_L)
  SendKey(#kVK_ANSI_L)
  SendKey(#kVK_ANSI_O)
  SendKey(#kVK_Return)
  CloseProgram(ProgramID)
EndIf
My Projects ThreadToGUI / OOP-BaseClass / EventDesigner V3
PB v3.30 / v5.75 - OS Mac Mini OSX 10.xx - VM Window Pro / Linux Ubuntu
Downloads on my Webspace / OneDrive
User avatar
Shardik
Addict
Addict
Posts: 1989
Joined: Thu Apr 21, 2005 2:38 pm
Location: Germany

Re: SendKey ?

Post by Shardik »

jvzuck wrote:Shardik,
Do you believe this code should still work under Catalina? I'm getting an "invalid memory access."
Unfortunately my old code from 2012 doesn't work anymore on MacOS Mojave and Catalina. It still works up to High Sierra (for x64 compilation you have to make the changes proposed by mk-soft).

In Catalina it seems not possible anymore to start TextEdit with RunProgram(). But it is still possible to start an application like TextEdit with launchApplication:. Unfortunately launchApplication: is deprecated in Big Sur but still working in Catalina. Therefore I have modified my old example from 2012 to also work in Catalina with PB 5.72 x64:

Code: Select all

EnableExplicit

#kCGHIDEventTap= 0

; ----- Virtual keycodes (defined in Events.h)

#kVK_ANSI_E = $0E
#kVK_ANSI_H = $04
#kVK_ANSI_L = $25
#kVK_ANSI_O = $1F
#kVK_Return = $24

; ----- Modifier key masks (defined in IOLLEvent.h)

#kCGEventFlagMaskShift     = $020000 ; = NX_SHIFTMASK
#kCGEventFlagMaskControl   = $040000 ; = NX_CONTROLMASK
#kCGEventFlagMaskAlternate = $080000 ; = NX_ALTERNATEMASK
#kCGEventFlagMaskCommand   = $100000 ; = NX_COMMANDMASK

ImportC ""
  CFRelease(CFTypeRef.I)
  CGEventCreateKeyboardEvent(CGEventSourceRef.I, CGVirtualKeyCode.U, KeyDown.L)
  CGEventPost(CGEventTapLocation.I, CGEventRef.I)
  CGEventSetFlags(CGEventRef.I, CGEventFlags.L)
EndImport

Procedure.L SendKey(VirtualKey.U, ModifierKey.L = 0)
  Protected KeyEvent.I

  KeyEvent = CGEventCreateKeyboardEvent(0, VirtualKey, #True)

  If KeyEvent
    ; ----- Press key
    CGEventSetFlags(KeyEvent, ModifierKey)
    CGEventPost(#kCGHIDEventTap, KeyEvent)
    CFRelease(KeyEvent)

    KeyEvent = CGEventCreateKeyboardEvent(0, VirtualKey, #False)
    CGEventSetFlags(KeyEvent, ModifierKey)

    If KeyEvent
      ; ----- Release key
      CGEventPost(#kCGHIDEventTap, KeyEvent)
      CFRelease(KeyEvent)
    EndIf
  EndIf

  ProcedureReturn KeyEvent
EndProcedure

Define Msg.S = "Hello!"
Define ProgramID.I

If CocoaMessage(0, CocoaMessage(0, 0, "NSWorkspace sharedWorkspace"),
  "launchApplication:$", @"TextEdit")
  ; ----- Wait until TextEdit is running (increase delay if necessary)
  Delay(500)
  ; ----- Send Keys to TextEdit
  SendKey(#kVK_ANSI_H, #kCGEventFlagMaskShift)
  SendKey(#kVK_ANSI_E)
  SendKey(#kVK_ANSI_L)
  SendKey(#kVK_ANSI_L)
  SendKey(#kVK_ANSI_O)
  SendKey(#kVK_Return)
EndIf
jvzuck
User
User
Posts: 14
Joined: Sat Aug 15, 2020 6:06 pm

Re: SendKey ?

Post by jvzuck »

So great! Thanks!
Where are the include files of which you speak: events.h and IOLLEvent.h

Presumably, if I wanted to do Command+Shit, I would just AND them, right?
Jonathan
jvzuck
User
User
Posts: 14
Joined: Sat Aug 15, 2020 6:06 pm

Re: SendKey ?

Post by jvzuck »

I found the include files online but it's strange I cannot find them on my Mac, having installed XCode and all.

Final question, your code bring the target application to the foreground. Is there a way to send keys to an application in the background. Let's say you're running a Keynote presentation and you want to log into TextEdit the timecodes each time you change slides. Ideally, you wouldn't bring TextEdit to the foreground to do it. That make sense?
Jonathan
User avatar
Shardik
Addict
Addict
Posts: 1989
Joined: Thu Apr 21, 2005 2:38 pm
Location: Germany

Re: SendKey ?

Post by Shardik »

jvzuck wrote:Where are the include files of which you speak: events.h and IOLLEvent.h
jvzuck wrote:I found the include files online but it's strange I cannot find them on my Mac, having installed XCode and all.
The Carbon framework (Events.h is a part of it) has been declared deprecated by Apple already for many years. Carbon was officially discontinued and removed entirely with the release of MacOS 10.15 Catalina. Nevertheless you still find the header files in the Xcode app:
- Events.h:
/Applications/Xcode.app/Contents/Developer/Platform/MacOSX.platform/Developer/SDKs/MacOSX.sdk/System/Library/Frameworks/Carbon.framework/Versions/A/Frameworks/HIToolbox.framework/Versions/A/Headers/Events.h
- IOLLEvent.h:
/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/System/Library/Frameworks/Kernel.framework/Versions/A/Headers/IOKit/hidsystem/IOLLEvent.h

Alternatively you find them on opensource.apple.com
- IOLLEvent.h
and GitHub:
- Events.h
jvzuck wrote:Presumably, if I wanted to do Command+Shit, I would just AND them, right?
You may combine 2 modifier keys in the second parameter of SendKey() by using the OR operator "|", not the AND operator "&" which would clear each flag! Just try

Code: Select all

Debug 1 & 2 ; Displays 0
Debug 1 | 2 ; Displays 3
jvzuck
User
User
Posts: 14
Joined: Sat Aug 15, 2020 6:06 pm

Re: SendKey ?

Post by jvzuck »

Yes, of course. Thanks. My programming skills are VERY rusty!
Post Reply