Emulate double leftclick on MacOS

I've built an "interactive laserpointer" iPhone app that positions the cursor at the point of aim of the iPhone, called Ultimatepointer - or Upoint for short. For this, I need a UpointDriver on the Mac to receive the cursor coordinates and any "mouse-click" the iPhone app sends over, and execute them on the Mac. In an earlier thread (https://developer.apple.com/forums/thread/685618?answerId=682733022#682733022) the problems arising from the introduction of Big Sur 11.5 were discussed. It turns out you have to de-check and re-check the macOS app's permissions in Preferences - Security and Privacy - Accessibility every time you recompile. While that problem was solved, I'm still left with the problem of how to effect a double-click. For the Windows-equivalent of the Driver, it's easy: just effect two clicks within a short timeframe. For Mac, the solution eludes me. It's not as simple as effecting two mouseDown-mouseUp sequences, or two mouseDown and then a mouseUp.... I'm stumped. Any suggestions? The code I use is below. Note that I'm using a Dispatch because the function is sometimes called by the app from a different thread, and I don't want to run into trouble with the UI thread. If you're certain the function only gets called from the main thread, Dispatch is not needed.

func leftClick() {
    DispatchQueue.main.async {
        let source = CGEventSource.init(stateID: .hidSystemState)
        let position = self.correctedCursorPosition // an internal variable holding the intended position of the click
        let eventDown = CGEvent(mouseEventSource: source, mouseType: .leftMouseDown, mouseCursorPosition: position , mouseButton: .left)
        let eventUp = CGEvent(mouseEventSource: source, mouseType: .leftMouseUp, mouseCursorPosition: position , mouseButton: .left)
        eventDown?.post(tap: .cghidEventTap)
        eventUp?.post(tap: .cghidEventTap)
    }
}

Did you try posting events twice ? 

       eventDown?.post(tap: .cghidEventTap)
       eventUp?.post(tap: .cghidEventTap)
       eventDown?.post(tap: .cghidEventTap)
       eventUp?.post(tap: .cghidEventTap)

yes, no luck there, I'm afraid... Also tried two Down events and one Up event. Didn't work. Also tried sleeping 0.1 secs in-between. Also there: no dice....

Could you test how many events were received, using:

class func counterForEventType(_ stateID: CGEventSourceStateID, eventType: CGEventType) -> UInt32

Sounds like a plan. Here's the code I used:

func leftClick() {

    DispatchQueue.main.async {

        print("-----------------------")

        let sleepTime = 0.05
        let inBetweenClickSleepTime = 0.1
     
        let source = CGEventSource.init(stateID: .hidSystemState)
        let position = self.correctedCursorPosition // holds the intended click position
        let eventDown = CGEvent(mouseEventSource: source, mouseType: .leftMouseDown, mouseCursorPosition: position , mouseButton: .left)
        let eventUp = CGEvent(mouseEventSource: source, mouseType: .leftMouseUp, mouseCursorPosition: position , mouseButton: .left)

        print("before D & before U post,              leftMouseDown count: \(CGEventSource.counterForEventType(.hidSystemState, eventType: .leftMouseDown)), leftMouseUp count: \(CGEventSource.counterForEventType(.hidSystemState, eventType: .leftMouseUp))")
        eventDown?.post(tap: .cghidEventTap)
        Thread.sleep(forTimeInterval: sleepTime)
        print("after D post and \(sleepTime) sec sleep,       leftMouseDown count: \(CGEventSource.counterForEventType(.hidSystemState, eventType: .leftMouseDown)), leftMouseUp count: \(CGEventSource.counterForEventType(.hidSystemState, eventType: .leftMouseUp))")
        eventUp?.post(tap: .cghidEventTap)
        Thread.sleep(forTimeInterval: sleepTime)
        print("after D and U post and \(sleepTime) sec sleep, leftMouseDown count: \(CGEventSource.counterForEventType(.hidSystemState, eventType: .leftMouseDown)), leftMouseUp count: \(CGEventSource.counterForEventType(.hidSystemState, eventType: .leftMouseUp))")

        print("\nSleeping for \(inBetweenClickSleepTime) secs, then trying another click\n")

        Thread.sleep(forTimeInterval: inBetweenClickSleepTime)

        print("before D & before U post,              leftMouseDown count: \(CGEventSource.counterForEventType(.hidSystemState, eventType: .leftMouseDown)), leftMouseUp count: \(CGEventSource.counterForEventType(.hidSystemState, eventType: .leftMouseUp))")
        eventDown?.post(tap: .cghidEventTap)
        Thread.sleep(forTimeInterval: sleepTime)
        print("after D post and \(sleepTime) sec sleep,       leftMouseDown count: \(CGEventSource.counterForEventType(.hidSystemState, eventType: .leftMouseDown)), leftMouseUp count: \(CGEventSource.counterForEventType(.hidSystemState, eventType: .leftMouseUp))")
        eventUp?.post(tap: .cghidEventTap)
        Thread.sleep(forTimeInterval: sleepTime)
        print("after D and U post and \(sleepTime) sec sleep, leftMouseDown count: \(CGEventSource.counterForEventType(.hidSystemState, eventType: .leftMouseDown)), leftMouseUp count: \(CGEventSource.counterForEventType(.hidSystemState, eventType: .leftMouseUp))")
    }
}

Some interesting stuff happens: if I put sleepTime and inBetweenClickSleepTime to 0, counters are not increased. The output becomes:

  • before D & before U post,        leftMouseDown count: 4190, leftMouseUp count: 4190
  • after D post and 0.0 sec sleep,     leftMouseDown count: 4190, leftMouseUp count: 4190
  • after D and U post and 0.0 sec sleep, leftMouseDown count: 4190, leftMouseUp count: 4190
  • Sleeping for 0.0 secs, then trying another click
  • before D & before U post,        leftMouseDown count: 4190, leftMouseUp count: 4190
  • after D post and 0.0 sec sleep,     leftMouseDown count: 4190, leftMouseUp count: 4190
  • after D and U post and 0.0 sec sleep, leftMouseDown count: 4190, leftMouseUp count: 4190

Even so, a single click is registered in the target window (provided I de- and re-checked the app's privileges after compile). If I put sleepTime and inBetweenClickSleepTime to 0.05 and 0.1 but forget to do the privileges trick, counters are not increased and the target window doesn't receive the event. If I do reset privileges, the output becomes:

  • before D & before U post,        leftMouseDown count: 4036, leftMouseUp count: 4036
  • after D post and 0.05 sec sleep,     leftMouseDown count: 4037, leftMouseUp count: 4036
  • after D and U post and 0.05 sec sleep, leftMouseDown count: 4037, leftMouseUp count: 4037
  • Sleeping for 0.1 secs, then trying another click
  • before D & before U post,        leftMouseDown count: 4037, leftMouseUp count: 4037
  • after D post and 0.05 sec sleep,     leftMouseDown count: 4038, leftMouseUp count: 4037
  • after D and U post and 0.05 sec sleep, leftMouseDown count: 4038, leftMouseUp count: 4038

That is, both Down events and both Up events are registered by the counters. Still, the target window seems to receive only 1 click. For example, if the target window is Mail and the click is on a message, it doesn't open up - it just gets selected. Things don't change if I set inBetweenClickSleepTime to 0 but keep sleepTime at 0.05. Removing the Dispatch also doesn't help....

Could you try to move the mouse by a few pixels, so that it is not the exact same event but still in the limits for double click ?

It's already connected to my iPhone, so every test I reported was by actually moving the cursor using my iPhone to the place I wanted to click. That means the clicks were never in the exact same place. Every time, though (provided I did the privileges trick, and had the sleepTimes at reasonable values), the item in the targeted Mail window got activated - just not opened up (as a double click does)

My point is that you post the same event:

        eventDown?.post(tap: .cghidEventTap)

I propose to create 2 events: eventDown1 and eventDown2.

ah, sorry, my mistake. I tried it, but no luck there either, I'm afraid: creating two Up and two Down events, and then positing them as Up1, Down1, Up2, Down2, with sleep in-between, still only produces a single click, even though the counterForEventType registers two Up and 2 Down events.

try this.

let position = self.correctedCursorPosition // an internal variable holding the intended position of the click

let eventDown = CGEvent(mouseEventSource: source, mouseType: .leftMouseDown, mouseCursorPosition: position , mouseButton: .left)
eventDown?.setIntegerValueField(.mouseEventClickState, value: 2)

let eventUp = CGEvent(mouseEventSource: source, mouseType: .leftMouseUp, mouseCursorPosition: position , mouseButton: .left)
eventUp?.setIntegerValueField(.mouseEventClickState, value: 2)

eventDown?.post(tap: .cghidEventTap)
eventUp?.post(tap: .cghidEventTap)
Emulate double leftclick on MacOS
 
 
Q