2
votes

I would like to simulate a mouse click on a Cocoa application without actually clicking the mouse, and* not have to figure out which view should respond to the click, given the current mouse location.

I would like the Cocoa framework to handle figuring out which view should respond, so I don't think that a method call on an NSView object is what I'm looking for. That is, I think I need a method call that will end up calling this method.

I currently have this working by clicking the mouse at a particular global location, using CGEventCreateMouseEvent and CGEventPost. However, this technique actually clicks the mouse. So this works, but I'm not completely happy with the behavior. For example, if I hold down a key on the keyboard while the CGEventPost is called, that key is wrapped into the event. Also, if I move another process's window over the window that I'd like to simulate the click, then the CGEventPost method will click the mouse in that window. That is, it's acting globally, across processes. I'd like a technique that works on a single window. Something on the NSWindow object maybe?

I read that "Mouse events are dispatched by an NSWindow object to the NSView object over which the event occurred" in the Cocoa documentation.

OK. So I'd like to know the method that is called to do the dispatching. Call this method on the window, and then let the framework figure out which NSView to call, given the current mouse location.

Any help would be greatly appreciated. I'm just starting to learn the Cocoa framework, so I apologize if any of the terminology/verbage here isn't quite right.

3
-[NSApplication sendEvent:] is essentially the entry point for input events; from there it (generally) gets passed to a window. That said, the distinction you are trying to draw between "simulating" and "actually" clicking the mouse is not clear. What are the features of the CGEvent procedure that you're trying to avoid? (E.g., there's no servo in the physical mouse device that moves the button, which is what I would call "actually" clicking.)jscs
Edited. Thx for your comments.Clayton Stanley
@JoshCaswell This looks like a good approach, but I can't get it to work. I'd post the code, but it's written in Clozure using the Objective C bridge, so it might be a bit taxing to parse. I couldn't find any examples online of simulated mouse clicks using sendEvent. Can you, by chance?Clayton Stanley
You're not just looking for -[NSView hitTest:], are you?abarnert

3 Answers

4
votes

It's hard to know exactly how much fidelity you're looking for with what happens for an actual click. For example, do you want the click to activate the app? Do you want the click to bring a window to the top? To make it key or main? If the location is in the title bar, do you want it to potentially close, minimize, zoom, or move the window?

As John Caswell noted, if you pass an appropriately-constructed NSEvent to -[NSApplication sendEvent:] that will closely simulate the processing of a real event. In most cases, NSApplication will forward the event to the event's window and its -[NSWindow sendEvent:] method. If you want to avoid any chance of NSApplication doing something else, you could dispatch directly to the window's -sendEvent: method. But that may defeat some desirable behavior, depending on exactly what you desire.

What happens if the clicked window's or view's response is to run an internal event-tracking loop? It's going to be synchronous; that is, the code that calls -sendEvent: is not going to get control back until after that loop has completed and it might not complete if you aren't able to deliver subsequent events. In fact, such a loop is going to look for subsequent events via -[NSApplication nextEventMatchingMask:untilDate:inMode:dequeue:], so if your synthetic events are not in the queue, they won't be seen. Therefore, an even better simulation of the handling of real events would probably require that you post events (mouse-down, mouse drags, mouse-up) to the queue using -[NSApplication postEvent:atStart:].

I think your first task is to really think deeply about what you're trying to accomplish, all the potential pitfalls and corner cases, and decide how you want to handle those.

With respect to the CGEvent... stuff, you can post an event to a specific process using CGEventPostToPSN() and that won't click on other app's windows, even if they are in front of the target window. However, it may still click on a different window within the target app.

2
votes

OK. So I'd like to know the method that is called to do the dispatching. Call this method on the window, and then let the framework figure out which NSView to call, given the current mouse location.

NSView *target = [[theWindow contentView] hitTest:thePoint];

I'm not entirely clear on your problem so I don't know if all you want to do is then call mouseDown: on the target. But if you did, that would be almost exactly the same thing that happens for a real mouse click.

This is the message used in delivering live clicks. It walks the view hierarchy, automatically dealing with overlap, hidden messages, etc., and letting each step in the chain of views interfere if it wants. If a view wants to prevent child views from getting clicks, it does that by eating hitTest:, which means it'll affect your code the exact same way it affects a real click. If the click would be delivered, this method always tells you where it would be delivered.

However, it doesn't necessarily handle all the reasons a click might not be delivered (acceptsFirstMouse, modal dialogs, etc.). Also, you have to know the window, and the point (in the appropriate coordinate system), but it sounds like that's what you're starting with.

1
votes

You can simulate mouse click by calling mouseDown: like this:

[self mouseDown: nil];

And to get mouse location in screen:

-(void)mouseDown:(NSEvent *)theEvent {

    NSPoint mouseLocation = [NSEvent mouseLocation];

    NSLog(@"x:  %f", mouseLocation.x);
    NSLog(@"y:  %f", mouseLocation.y);
}