7
votes

I've been using the following code to issue clicks programmatically on a Mac

void PostMouseEvent(CGMouseButton button, CGEventType type, const CGPoint point) 
{
    CGEventRef theEvent = CGEventCreateMouseEvent(NULL, type, point, button);
    CGEventSetType(theEvent, type);
    CGEventPost(kCGHIDEventTap, theEvent);
    CFRelease(theEvent);
}

void Click(const CGPoint point) 
{
    PostMouseEvent(kCGMouseButtonLeft, kCGEventMouseMoved, point);
    NSLog(@"Click!");
    PostMouseEvent(kCGMouseButtonLeft, kCGEventLeftMouseDown, point);
    PostMouseEvent(kCGMouseButtonLeft, kCGEventLeftMouseUp, point);
}

Now, I'm trying to click down to be able to drag objects, like a scroll bar or an application's window. I'm using the following:

PostMouseEvent(kCGMouseButtonLeft, kCGEventMouseMoved, point);
NSLog(@"Click Down!");
PostMouseEvent(kCGMouseButtonLeft, kCGEventLeftMouseDown, point);

When i ran the code above something interesting will happen, when the left mouse down is issue nothing seem to happen, I move my mouse and the window doesn't move, however when I added a mouse up event then the window jumped to the location where I supposedly dragged it. this is sort of OK, however, how do I can make the mouse click down and drag an object?

Note: I do have a whole method to see when the mouse stopped moving so I can click up.

please post code. Thanks

4

4 Answers

4
votes

The only way to do what you want is setting a active CGEventTap to get a editable stream of kCGEventMouseMoved events and change them into kCGEventLeftMouseDragged events in the callback of the CGEventTap (obviously you need to synthesize the mouse down event first)

You would think that this is done automatically by sending a click event with no release and moving the mouse but that is not the case.

3
votes

To drag, you must put the mouse button down first, then move the mouse.

Programmatically, you should probably post kCGEventLeftMouseDragged, not kCGEventMouseMoved, between the LeftMouseDown and LeftMouseUp events. You can install a custom event tap and log real mouse events to confirm or correct this.

2
votes

Although in PyObjC, this does work as advertised. But you can reverse engineer it back to Cocoa if you'd like.

#!/usr/bin/python
import sys, time
from Quartz.CoreGraphics import *

def mouseEvent(type, posx, posy):
    theEvent = CGEventCreateMouseEvent(None, type, (posx,posy), kCGMouseButtonLeft)
    CGEventPost(kCGHIDEventTap, theEvent)

def mousemove(posx,posy):
    mouseEvent(kCGEventMouseMoved, posx,posy)

def mouseclickdn(posx,posy):
    mouseEvent(kCGEventLeftMouseDown, posx,posy)

def mouseclickup(posx,posy):
    mouseEvent(kCGEventLeftMouseUp, posx,posy)

def mousedrag(posx,posy):
    mouseEvent(kCGEventLeftMouseDragged, posx,posy)

ourEvent = CGEventCreate(None)
currentpos = CGEventGetLocation(ourEvent)   # Save current mouse position

mouseclickdn(60, 100)
mousedrag(60, 300)
mouseclickup(60, 300)

time.sleep(1)
mousemove(int(currentpos.x), int(currentpos.y)) # Restore mouse position
1
votes

I have reimplemented it this way in Cocoa in C:

    void mouse_left_click_down_at(float x, float y) {

            /**
             * create a new Quartz mouse event.
             * params:
             * @source : CGEventSourceRef
             * @mouseType : CGEventType
             * @mouseCursorPosition : CGPoint
             * @mouseButton : CGMouseButton
         */
        CGEventRef  left_click_down_event = CGEventCreateMouseEvent(NULL, kCGEventLeftMouseDown, CGPointMake(x, y), 0);

        /**
         * post a Quartz event into the event stream at a specified location.
         * params:
         * @tap : CGEventTapLocation
         * @event : CGEventRef
         */
        CGEventPost(kCGHIDEventTap, left_click_down_event);

        /**
         * release a Quartz event
         */
        CFRelease(left_click_down_event);
    }
    void mouse_left_click_up_at(float x, float y) {

        /**
         * create a new Quartz mouse event.
         * params:
         * @source : CGEventSourceRef
         * @mouseType : CGEventType
         * @mouseCursorPosition : CGPoint
         * @mouseButton : CGMouseButton
         */
        CGEventRef left_click_up_event = CGEventCreateMouseEvent(NULL, kCGEventLeftMouseUp, CGPointMake(x, y), 0);

        /**
         * post a Quartz event into the event stream at a specified location.
         * params:
         * @tap : CGEventTapLocation
         * @event : CGEventRef
         */
        CGEventPost(kCGHIDEventTap, left_click_up_event);

        /**
         * release a Quartz event
         */
        CFRelease(left_click_up_event);
    }

    void mouse_left_drag_to(float x, float y) {

        /**
         * create a new Quartz mouse event.
         * params:
         * @source : CGEventSourceRef
         * @mouseType : CGEventType
         * @mouseCursorPosition : CGPoint
         * @mouseButton : CGMouseButton
         */
        CGEventRef left_drag_event = CGEventCreateMouseEvent(NULL, kCGEventLeftMouseDragged, CGPointMake(x, y), 0);

        /**
         * post a Quartz event into the event stream at a specified location.
         * params:
         * @tap : CGEventTapLocation
         * @event : CGEventRef
         */
        CGEventPost(kCGHIDEventTap, left_drag_event);

        /**
        * release a Quartz event
        */
        CFRelease(left_drag_event);
    }

But I encounter some problem with executing it one by one. I works only when I add some significant delay between invocation of mouse down, mouse drag and mouse up.

 mouse_left_click_at(400.0, 200.0);          // get focus
    usleep(10000);

    mouse_left_click_down_at(400.0, 200.0);     // start dragging
    usleep(10000);
    mouse_left_drag_to(500.0, 400.0);           // continue dragging
    usleep(10000);
    mouse_left_drag_to(600.0, 500.0);
    usleep(10000);
    mouse_left_drag_to(700.0, 600.0);
    usleep(10000);
    mouse_left_drag_to(800.0, 700.0);
    usleep(10000);
    mouse_left_click_up_at(800.0, 700.0);       // stop dragging