Best way to send keystrokes to a 3rd party application?

I need a way to send keystrokes to a particular third party application on both Windows and Mac (the same third party app on both platforms).

I don’t know if an app can specifically be targeted for keystrokes or if I would need to somehow programatically bring it to the foreground, ensure it has focus, and then send the keystrokes.

Does anyone know if there is an existing accepted way to this in Xojo already, or is it declare digging time?

For Windows you can check Windows Functionality Suite for the SendKeys module. For macOS, I think @Christian_Schmitz has something.

1 Like

Thanks @Anthony_G_Cyphers, SendKeys requires the app to have focus, correct?

You call AppActivate (or whatever it’s called) on the window, then call the specific SendKeys function(s).

1 Like

Yes, we have a RemoteControlMBS module. And WinBringWindowToTop may help with the focus.

1 Like

Thanks, @Christian_Schmitz, is there a Mac equivalent for WinBringWindowToTop? Playing around with RemoteControlMBS and MacProcessVisible = True makes the application window visible but does not give it the focus.

You may just try to do that with an Apple Script.
Or check NSRunningApplicationMBS class, where we have a activateWithOptions method.

1 Like

Thanks Christian!

If anyone else needs a Mac version of AppActivate, here’s a quick and dirty method (requires MBS):

Public Function MacAppActivate(bundleID as String) As Boolean
#If TargetMacOS Then
  var n() as NSRunningApplicationMBS = NSRunningApplicationMBS.runningApplicationsWithBundleIdentifier(bundleID)
  if n.count = 0 then
    //App is not running
    Return false
  end if
  
  var u as integer, b as boolean = n(0).activateWithOptions(u)
  Return b
  #EndIf
End Function

Sending keys on Mac via RemoteControlMBS.MacPressKey worked the first time I ran the app in the IDE (when I was prompted to toggle the switch in Accessibility), but fails on each subsequent debug compile. I think perhaps because it no longer has system permissions to send keys, despite the switch still being toggled on in the Privacy & Security preference pane.

Is there a workaround for this?

I don’t know if this is any help to you, but I use third-party apps because of problems like this. Under Windows AutoHotkey and on the Mac Keyboard Maestro.

1 Like

Thanks for this, does this require the user of your app to have Keyboard Maestro?

Sadly, yes. AutoHokey creates selfcontained EXE’s, but Keyboard Maestro needs an installed and licensed Keyboard Maestro Engine on each Machine.

Ahhh, that’s too bad. Won’t be able to swing that for my app, but I appreciate the suggestion. It looks like it’d be a great solution for apps that don’t need wide distribution.

There is definitely something funky going on. The RemoteControlMBS.MacPressKey methods have just stopped working, even in the MBS Example Project “Write Something” where it was certainly working just an hour ago with no code changes. If anyone has any insight into what might have changed between runs, I’d appreciate it.

Edit: Okay so it seems that EVERY TIME YOU DEBUG YOUR APP, you have to open System Settings … Privacy & Security… Accessibility, and remove the old entry for your app’s permission. Then you have to let your app re-try its key sending operation, fail, allow the permission for the new instance of your app in Accessibility, and then try again.

This is a huge waste of time. I’m often running dozens of debugs an hour. Does anyone know a way to circumvent this?

I’ve been wrestling with this for days and I haven’t found a good workaround yet that doesn’t involve some serious screwing around with crazy AppleScripts, etc. This is a huge blind spot on Apple’s part for developers… I appreciate that they are focused on security but there ought to be a way to temporarily disable all the checks.

1 Like

It also makes me wonder if those permissions will break whenever you push an update for your app.

@Eric_Williams Here’s a little something I put together after spending too much time on this issue, lol. Call this method when your app loads and it will remove the old permissions. You will still be prompted to allow it the next time you use accessibility features, but at least it saves you from having to remove it each time.

Public Sub ResetAccessibility()
  #If DebugBuild And TargetMacOS Then
    Declare Function mainBundle Lib "AppKit" selector "mainBundle"(NSBundleClass As Ptr) As Ptr
    Declare Function NSClassFromString Lib "AppKit"(className As CFStringRef) As Ptr
    Declare Function getValue Lib "AppKit" selector "bundleIdentifier"(NSBundleRef As Ptr) As CFStringRef
    var bundleID As String = getValue(mainBundle(NSClassFromString("NSBundle")))
    var s as new Shell
    s.Execute("tccutil reset All " + bundleID)
  #EndIf
End Sub

3 Likes

For Full Disk Access and Automation I need a built app so that I can do debugging.

A million thanks for this; you just saved me hours of frustration.

1 Like

Here. Here. Great stuff @Christian_Wheel