How to send correctly keypress to BG app with no difference between swift script and compiled swiftc binary

297 views Asked by At

Have a small swift script called skey.swift which sending a keypress into processes even when they're not active, (e.g. they're in the background) by their PID (process ID).

import Foundation
if CommandLine.argc < 2 {
        print("Error", CommandLine.arguments[0], " No arguments are passed.")
        exit(1)
}

let src = CGEventSource(stateID: CGEventSourceStateID.hidSystemState)

let key_d = CGEvent(keyboardEventSource: src, virtualKey: 0x12, keyDown: true)  // key "1" press
let key_u = CGEvent(keyboardEventSource: src, virtualKey: 0x12, keyDown: false) // key "1" release

for i in 1 ..< Int(CommandLine.argc) {
        if let pid = pid_t(CommandLine.arguments[i]) {
                print("arg:", pid)
                key_d?.postToPid( pid )
                key_u?.postToPid( pid )
        }
}

Test case:

  • Let say run the TextEdit.app and the Notes.app
  • write down their PIDs from the ps axu | grep -E 'MacOS/(TextEdit|Notes)' - example
me              87756   3,3  0,3  4716112  97228   ??  S    11:54     0:28.01 /Applications/Notes.app/Contents/MacOS/Notes
me              83077   0,0  0,1  4609916  49312   ??  S     8:00     0:04.58 /Applications/TextEdit.app/Contents/MacOS/TextEdit
  • remember the PID's: 87756 83077
  • Run from the Terminal the swift skey.swift 87756 83077
  • in the BOTH app-windows (TextEdit and Notes) appears the "1", even if they're in the background (the Terminal is in the active app).
  • so, the script works as expected !!

The problem

  • compile the script with swiftc skey.swift
  • run the compiled binary with the same arguments, e.g. ./skey 87756 83077
  • ONLY the first PID gets the "1" (in this case, only in the Notes)

Why such a difference between running a script:

  • swift skey.swift 87756 83077
  • or ./skey 87756 83077

EDIT

Just found in this source-code a comment

// Maybe this is a dumb way to solve this, but let's sleep for just a moment so they events don’t arrive out of order.

so, started experimenting with the usleep myself. 50µs won't help, but using 2x1000µs HELPED and finally the compiled and interpreted versions work exactly in the same way.

So, this works.

for i in 1 ..< Int(CommandLine.argc) {
        if let pid = pid_t(CommandLine.arguments[i]) {
                print("arg:", pid)
                key_d?.postToPid( pid )
                usleep(1000)
                key_u?.postToPid( pid )
                usleep(1000)
        }
}

But, as the comment talking about the "dumb way" - would be nice to know the CORRECT WAY because the code should work in any macOS...

EDIT 2

Based on @willeke's comment I tried the following

for i in 1 ..< Int(CommandLine.argc) {
        if let pid = pid_t(CommandLine.arguments[i]) {
                print("arg:", pid)
                key_d?.postToPid( pid )
                key_u?.postToPid( pid )
        }
}
sleep(1) // give enough time to send events before exit

and it works as expected. Looks like the event sending process must exist, otherwise, all his events are discarded.

0

There are 0 answers