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 theNotes.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.