OSX assign left mouse click to a keyboard key - cocoa

I would like to be able to assign a key on my keyboard to be equivalent to a left mouse click.
Ideally it needs to act such that holding the key down is also equivalent to holding the left mouse button down.
I'd like this capability as a user, additionally a programmatic solution (cocoa/applescript etc) would be great too.

Not exactly what you want, but in the System preferences -> Universal access you can turn on mouse keys - and with them you can move (and click) mouse by keyboard. docs here:
http://docs.info.apple.com/article.html?path=Mac/10.6/en/cdb_moskys.html
http://docs.info.apple.com/article.html?path=Mac/10.6/en/8565.html
http://docs.info.apple.com/article.html?artnum=61472
Or,
With the "ControllerMate.app" is possible to do this, but it is commercial app.

This can be done by writing some code:
Write a global handler to receive the type of event you want to watch
[NSEvent addGlobalMonitorForEventsMatchingMask:NSKeyDownMask
handler:^(NSEvent *event){
NSLog(#"%i", [event keyCode]);
//todo invoke mouse clicking code;
}];
Then write the mouse click code:
// get current mouse pos
CGEventRef ourEvent = CGEventCreate(NULL);
CGPoint point = CGEventGetLocation(ourEvent);
NSLog(#"Location? x= %f, y = %f", (float)point.x, (float)point.y);
// perform a click
CGEventSourceRef source = CGEventSourceCreate(kCGEventSourceStateCombinedSessionState);
CGEventRef theEvent = CGEventCreateMouseEvent(source, kCGEventLeftMouseDown, point, kCGMouseButtonLeft);
CGEventSetType(theEvent, kCGEventLeftMouseDown);
CGEventPost(kCGHIDEventTap, theEvent);
CFRelease(theEvent);

Related

How to get NSScrollView to scroll horizontally with the scroll wheel without the Shift key?

I'm using an NSScrollView to implement a tab bar which scrolls horizontally when there are too many tabs to fit in the window. I can move the scrollview left and right using the trackpad just fine. I'd like to enable mouse users to scroll left and right using the scroll wheel. Right now, this already works if the user holds the Shift key while turning the scroll wheel - the tabs will scroll horizontally. However, I'd like for it to work without the Shift key. This is how Safari's tabs work, for example.
How do I implement horizontal scrolling using the scroll wheel without the Shift key?
Please note that my tab bar does not display a horizontal scroller. Since macOS 10.9's introduction of responsive scrolling, I don't believe we can override -scrollWheel: anymore (see the Update at the bottom of this post: https://stackoverflow.com/a/31201614/111418)
I used an NSEvent monitor to accomplish this.
_scrollWheelEventMonitor = [NSEvent addLocalMonitorForEventsMatchingMask:NSEventMaskScrollWheel handler:^NSEvent * _Nullable(NSEvent * _Nonnull event) {
NSPoint location = [_scrollView convertPoint:event.locationInWindow fromView:nil];
// We want events:
// where the mouse is over the _scrollView
// and where the user is not modifying it with the SHIFT key
// and initiated by the scroll wheel and not the trackpad
if ([_scrollView mouse:location inRect:_scrollView.bounds]
&& !event.modifierFlags
&& !event.hasPreciseScrollingDeltas)
{
// Create a new scroll wheel event based on the original,
// but set the new deltaX to the original's deltaY.
// stackoverflow.com/a/38991946/111418
CGEventRef cgEvent = CGEventCreateCopy(event.CGEvent);
CGEventSetIntegerValueField(cgEvent, kCGScrollWheelEventDeltaAxis2, event.scrollingDeltaY);
NSEvent *newEvent = [NSEvent eventWithCGEvent:cgEvent];
CFRelease(cgEvent);
return newEvent;
}
return event;
}];

How to make a text Field on user's click

I want my application to make a text box where ever a user clicks. So how do i synthesize a text box on demand. Kind of like in OneNote.
So how do i synthesize a text box on demand.
You create a text view or text field, and then you add it to your view wherever you want it. Assuming that you have the click event in event and you know the width and height of the text view you want to create, do this:
NSPoint *p = [myContainerView convertPoint:[event locationInWindow] fromView:nil];
NSRect frame = NSMakeRect(p.x, p.y, width, height);
NSTextView *tv = [[NSTextView alloc] initWithFrame:frame];
[myContainerView addSubview:tv];
For detecting mouse event
NSUInteger pmb = [NSEvent pressedMouseButtons];
/*
A return value of 1 << 0 corresponds to the left mouse button, 1 << 1 corresponds to the right mouse button, 1<< n, n >=2 correspond to other mouse buttons.
*/
NSPoint mouseLocation = [NSEvent mouseLocation];
/*
Reports the current mouse position in screen coordinates.
*/

How to accept a mouse click for one portion and let click-though the rest of an NSWindow

I have the code below for a subclassed NSWindow, there is an animated view which can be scaled and I want to accept click when it is clicked at the right spot and reject (click through) if it is outside.
The code below works nice except the window does not let clicks through.
- (void)mouseDragged:(NSEvent *)theEvent {
if (allowDrag) {
NSRect screenVisibleFrame = [[NSScreen mainScreen] visibleFrame];
NSRect windowFrame = [self frame];
NSPoint newOrigin = windowFrame.origin;
// Get the mouse location in window coordinates.
NSPoint currentLocation = [theEvent locationInWindow];
// Update the origin with the difference between the new mouse location and the old mouse location.
newOrigin.x += (currentLocation.x - initialMouseLocation.x);
newOrigin.y += (currentLocation.y - initialMouseLocation.y);
if ((newOrigin.y + windowFrame.size.height) > (screenVisibleFrame.origin.y + screenVisibleFrame.size.height)) {
newOrigin.y = screenVisibleFrame.origin.y + (screenVisibleFrame.size.height - windowFrame.size.height);
}
// Move the window to the new location
[self setFrameOrigin:newOrigin];
}
}
- (void)mouseDown:(NSEvent *)theEvent
{
screenResolution = [[NSScreen mainScreen] frame];
initialMouseLocation = [theEvent locationInWindow];
float scale = [[NSUserDefaults standardUserDefaults] floatForKey:#"widgetScale"]/100;
float pX = initialMouseLocation.x;
float pY = initialMouseLocation.y;
float fX = self.frame.size.width;
float fY = self.frame.size.height;
if (pX>(fX-fX*scale)/2 && pX<(fX+fX*scale)/2 && pY>(fY+fY*scale)/2) {
allowDrag = YES;
} else {
allowDrag = NO;
}
}
In Cocoa, you have two basic choices: 1) you can make a whole window pass clicks through with [window setIgnoresMouseEvents:YES], or 2) you can make parts of your window transparent and clicks will pass through by default.
The limitation is that the window server makes the decision about which app to deliver the event to once. After it has delivered the event to your app, there is no way to make it take the event back and deliver it to another app.
One possible solution might be to use Quartz Event Taps. The idea is that you make your window ignore mouse events, but set up an event tap that will see all events for the login session. If you want to make an event that's going through your window actually stop at your window, you process it manually and then discard it. You don't let the event continue on to the app it would otherwise reach. I expect that this would be very tricky to do right. For example, you don't want to intercept events for another app's window that is in front of yours.
If at all possible, I recommend that you use the Cocoa-supported techniques. I would think you would only want clicks to go through your window where it's transparent anyway, since otherwise how would the user know what they are clicking on?
Please invoke an transparent overlay CHILD WINDOW for accepting control and make the main window -setIgnoresMouseEvents:YES as Ken directed.
I used this tricky on my app named "Overlay".

CGEventCreateKeyboardEvent and CGEventTapLocation

I'm having a hard time dealing with custom keyboard events. I want to be able to send any key with / without kCGEventFlagMasks like command, alt, ctrl, shift...
e.g. I want to send the current frontmost app, let's assume it's TextEdit, the key combo cmd+t, which should show the font window.
But currently only t is added to the current document of TextEdit. I tried posting the event with custom set CGEventFlags and alternatively generating keyDown and keyUp events for cmd around the t event. The free app Key Codes shows me when I use the CGEventFlags set, that cmd+t has been pressed, but this key combo isn't passed to TextEdit, only the single t...
Here's my code:
...
flags = (flags | kCGEventFlagMaskCommand);
[self writeString:#"" withFlags:flags];
...
(void)writeString:(NSString *)valueToSet withFlags:(int)flags
{
UniChar buffer;
CGEventRef keyEventDown = CGEventCreateKeyboardEvent(NULL, 1, true);
CGEventRef keyEventUp = CGEventCreateKeyboardEvent(NULL, 1, false);
CGEventSetFlags(keyEventDown,0);
CGEventSetFlags(keyEventUp,0);
for (int i = 0; i < [valueToSet length]; i++) {
[valueToSet getCharacters:&buffer range:NSMakeRange(i, 1)];
CGEventKeyboardSetUnicodeString(keyEventDown, 1, &buffer);
CGEventSetFlags(keyEventDown,flags);
CGEventPost(kCGSessionEventTap, keyEventDown);
CGEventKeyboardSetUnicodeString(keyEventUp, 1, &buffer);
CGEventSetFlags(keyEventUp,flags);
CGEventPost(kCGSessionEventTap, keyEventUp);
}
CFRelease(keyEventUp);
CFRelease(keyEventDown);
}
I'm also having a hard time understanding the meaning of a CGEventTapLocation. I don't know which event tap would be the most suitable for my task. The description in the Quartz Event Reference doesn't enlighten me:
kCGHIDEventTap
Specifies that an event tap is placed at the point where HID system events enter the window server.
kCGSessionEventTap
Specifies that an event tap is placed at the point where HID system and remote control events enter a login session.
kCGAnnotatedSessionEventTap
Specifies that an event tap is placed at the point where session events have been annotated to flow to an application.
I've seen many posts about that topic, but none of 'em helped me solve my problem...
Thanks for your help!
That's not the answer to the question but my current workaround via AppleScript:
appleScriptString = [NSString stringWithFormat:#"tell application \"System Events\" to key code %d using %#",keyCode, modifierDownString];
which I feed to that function:
- (void)runScript:(NSString*)scriptText
{
NSDictionary *error = nil;
NSAppleEventDescriptor *appleEventDescriptor;
NSAppleScript *appleScript;
appleScript = [[NSAppleScript alloc] initWithSource:scriptText];
appleEventDescriptor = [appleScript executeAndReturnError:&error];
[appleScript release];
}

Getting "global" mouse position in Mac OS X

How can I get in Mac OS X "global" mouse position - I mean how can I in cocoa/cf/whatever find out cursor position even if it's outside the window, and even if my window is inactive?
I know it's somehow possible (even without admin permissions), because I've seen something like that in Java - but I want to write it in ObjC
Sorry for my English - I hope you'll understand what I mean ;)
NSPoint mouseLoc;
mouseLoc = [NSEvent mouseLocation]; //get current mouse position
NSLog(#"Mouse location: %f %f", mouseLoc.x, mouseLoc.y);
If you want it to continuously get the coordinates then make sure you have an NSTimer or something similar
Matt S. is correct that if you can use the NSEvent API to get the mouse location. However, you don't have to poll in order to continuously get coordinates. You can use a CGEventTap instead:
- (void) startEventTap {
//eventTap is an ivar on this class of type CFMachPortRef
eventTap = CGEventTapCreate(kCGHIDEventTap, kCGHeadInsertEventTap, kCGEventTapOptionListenOnly, kCGEventMaskForAllEvents, myCGEventCallback, NULL);
CGEventTapEnable(eventTap, true);
}
CGEventRef myCGEventCallback(CGEventTapProxy proxy, CGEventType type, CGEventRef event, void *refcon) {
if (type == kCGEventMouseMoved) {
NSLog(#"%#", NSStringFromPoint([NSEvent mouseLocation]));
}
return event;
}
This way, your function myCGEventCallback will fire every time the mouse moves (regardless of whether your app is frontmost or not), without you having to poll for the information. Don't forget to CGEventTapEnable(eventTap, false) and CFRelease(eventTap) when you're done.
If you're not in cocoa land and an event tap is not appropriate for the situation,
CGEventRef event = CGEventCreate(nil);
CGPoint loc = CGEventGetLocation(event);
CFRelease(event);
works.
For a continuous update on the global cursor position, here is what the code looks like (Swift 4.0.3):
override func viewDidLoad()
{
super.viewDidLoad()
NSEvent.addGlobalMonitorForEvents(matching: NSEvent.EventTypeMask.mouseMoved, handler: {(mouseEvent:NSEvent) in
let position = mouseEvent.locationInWindow
})
}
Note, the variable 'position' is an NSPoint object with x and y coordinates (among many other attributes). This will not return the coordinates if your application is in focus or when you are clicking and dragging. There are separate events for all of those: simply change the NSEvent.EventTypeMask.mouseMoved to a different EventTypeMask
Matt is correct, but in order to continuously get the coordinates, I believe the other option is to use event monitor provided by NSEvent. Try addGlobalMonitorForEventsMatchingMask:handler: and addLocalMonitorForEventsMatchingMask:handler:. Check NSEvent Class Reference for more detail.

Resources