I am programatically generating mouse clicks when a user clicks a certain keyboard key (CapsLock).
So I do a left mouse down when CapsLock is switched on, then a left mouse up when CapsLock is switched off.
This behaves correctly in that if I for example place the mouse over a window title bar, click CapsLock, then move the mouse, then click CapsLock, the window correctly moves. i.e. I correctly 'drag' the window as if I had held the left mouse button down whilst moving the mouse.
However there is one problem - the window does not move whilst I am moving the mouse, it only moves to the final position after I have clicked CapsLock a second time. i.e. after I have 'released' the mouse button.
What do I need to do to ensure the screen is refreshed during the mouse move?
Interestingly, I also hooked to
[NSEvent addGlobalMonitorForEventsMatchingMask:NSLeftMouseDraggedMask
and found that my NSLog statement only output after I released the left mouse button (the real left mouse button)
Mouse click code is below, I can post all the code if necessary, there isn't much of it..
// simulate mouse down
// get current mouse pos
CGEventRef ourEvent = CGEventCreate(NULL);
CGPoint point = CGEventGetLocation(ourEvent);
NSLog(#"Location? x= %f, y = %f", (float)point.x, (float)point.y);
CGEventSourceRef source = CGEventSourceCreate(kCGEventSourceStateCombinedSessionState);
CGEventRef theEvent = CGEventCreateMouseEvent(source, kCGEventLeftMouseDown, point, kCGMouseButtonLeft);
CGEventSetType(theEvent, kCGEventLeftMouseDown);
CGEventPost(kCGHIDEventTap, theEvent);
CFRelease(theEvent);
// simulate mouse up
// get current mouse pos
CGEventRef ourEvent = CGEventCreate(NULL);
CGPoint point = CGEventGetLocation(ourEvent);
NSLog(#"Location? x= %f, y = %f", (float)point.x, (float)point.y);
CGEventSourceRef source = CGEventSourceCreate(kCGEventSourceStateCombinedSessionState);
CGEventRef theEvent = CGEventCreateMouseEvent(source, kCGEventLeftMouseUp, point, kCGMouseButtonLeft);
CGEventSetType(theEvent, kCGEventLeftMouseUp);
CGEventPost(kCGHIDEventTap, theEvent);
CFRelease(theEvent);
If you want to be able to drag windows, the problem is that you also need to post a LeftMouseDragged event.
Simply call beginEventMonitoring to start listening for caps lock key events and mouse move events. The event handlers will simulate a left mouse press and movement just as you wanted. Here is a link to my blog where you can download a full working example for Xcode 4: http://www.jakepetroules.com/2011/06/25/simulating-mouse-events-in-cocoa
The example is in the public domain, do whatever you like with it. :)
According to Apple (NSEvent documentation), "Enable access for assistive devices" needs to be checked in System Preferences > Universal Access for this to work, but I have it unchecked and it wasn't an issue. Just a heads up.
Please let me know if you have any further issues and I will try my best to help.
// Begin listening for caps lock key presses and mouse movements
- (void)beginEventMonitoring
{
// Determines whether the caps lock key was initially down before we started listening for events
wasCapsLockDown = CGEventSourceKeyState(kCGEventSourceStateHIDSystemState, kVK_CapsLock);
capsLockEventMonitor = [NSEvent addGlobalMonitorForEventsMatchingMask:(NSFlagsChangedMask) handler: ^(NSEvent *event)
{
// Determines whether the caps lock key was pressed and posts a mouse down or mouse up event depending on its state
bool isCapsLockDown = [event modifierFlags] & NSAlphaShiftKeyMask;
if (isCapsLockDown && !wasCapsLockDown)
{
[self simulateMouseEvent: kCGEventLeftMouseDown];
wasCapsLockDown = true;
}
else if (wasCapsLockDown)
{
[self simulateMouseEvent: kCGEventLeftMouseUp];
wasCapsLockDown = false;
}
}];
mouseMovementEventMonitor = [NSEvent addGlobalMonitorForEventsMatchingMask:(NSMouseMovedMask) handler:^(NSEvent *event)
{
[self simulateMouseEvent: kCGEventLeftMouseDragged];
}];
}
// Cease listening for caps lock key presses and mouse movements
- (void)endEventMonitoring
{
if (capsLockEventMonitor)
{
[NSEvent removeMonitor: capsLockEventMonitor];
capsLockEventMonitor = nil;
}
if (mouseMovementEventMonitor)
{
[NSEvent removeMonitor: mouseMovementEventMonitor];
mouseMovementEventMonitor = nil;
}
}
-(void)simulateMouseEvent:(CGEventType)eventType
{
// Get the current mouse position
CGEventRef ourEvent = CGEventCreate(NULL);
CGPoint mouseLocation = CGEventGetLocation(ourEvent);
// Create and post the event
CGEventRef event = CGEventCreateMouseEvent(CGEventSourceCreate(kCGEventSourceStateHIDSystemState), eventType, mouseLocation, kCGMouseButtonLeft);
CGEventPost(kCGHIDEventTap, event);
CFRelease(event);
}
Related
As you all might be knowing about the new Quick Type bar on the keyboard.
In my application , I have put a custom TextView bar on the keyboard. But because of QuickType Bar , my textview gets hidden.
I want to know , Is there any property or method to know whether the QuickType Bar is open or not ?
There is nothing which can tell you whether the QuickType bar is active or not but you can register for the UIKeyboardWillChangeFrameNotification notification with this code and with that you can get info about the height of the keyboard.
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(keyboardWillChangeFrame:) name:UIKeyboardDidChangeFrameNotification object:nil];
Use the UIKeyboardFrameBeginUserInfoKey and UIKeyboardFrameEndUserInfoKey values in the passed userInfo dictionary to retrieve the current and future frames of the keyboard. You can use the following code as a reference.
- (void)keyboardWillChangeFrame:(NSNotification*)notification
{
NSDictionary* keyboardInfo = [notification userInfo];
NSValue* keyboardFrameBegin = [keyboardInfo valueForKey:UIKeyboardFrameBeginUserInfoKey];
CGRect keyboardFrameBeginRect = [keyboardFrameBegin CGRectValue];
// Manage your other frame changes
}
Hope this helps. It will get called every time the keyboard changes its frame.
As the other answers have suggested, the UIKeyboardWillChangeFrameNotification will fire every time the keyboard gets a new frame. This includes when the keyboard will show and hide, and when the QuickType bar will show and hide.
The thing is, this is exactly the same notification as the UIKeyboardWillShowNotification, just with a different name. Therefore, if you're already implementing a method for the UIKeyboardWillShowNotification, you're good to go.
With one exception, though. When you get the keyboard frame in your method for handling the UIKeyboardWillShowNotification, you must make sure you access it via the UIKeyboardFrameEndUserInfoKey, not the UIKeyboardFrameBeginUserInfoKey. Otherwise, it will get the correct frame when the keyboard is shown/hidden, but not when the QuickType bar is.
So, the code in your method for handling the UIKeyboardWillShowNotification should look something like this (in Swift):
func keyboardWillShow(notification: NSNotification) {
let info = notification.userInfo!
keyboardRect = (info[UIKeyboardFrameEndUserInfoKey] as! NSValue).CGRectValue()
keyboardRect = yourView.convertRect(keyboardRect, fromView: nil)
// Handling the keyboard rect by changing frames, content offsets, constraints, or whatever
}
- (void)keyboardFrameChanged:(NSNotification*)aNotification
{
NSDictionary* info = [aNotification userInfo];
CGPoint from = [[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].origin;
CGPoint to = [[info objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue].origin;
float height = 0.0f;
if (UIInterfaceOrientationIsLandscape([UIApplication sharedApplication].statusBarOrientation)) {
height = to.x - from.x;
} else {
height = to.y - from.y;
}
[self setContentSize:CGSizeMake(self.frame.size.width, self.frame.size.height + height)];
}
I have a full-screen transparent window that displays above the main menu of my app. It has ignoresMouseEvents set to NO. To receive mouse clicks nonetheless, I added this code :
[NSEvent addLocalMonitorForEventsMatchingMask:NSLeftMouseDownMask handler:^(NSEvent *event) {
[self click:event];
return event;
}];
Every time the user clicks while my app is active, a method click is thus called :
- (BOOL)click:(NSEvent *)event {
NSPoint coordinate = [event locationInWindow];
float ycoord = coordinate.y;
float menuheight = [[NSApp mainMenu] menuBarHeight];
float windowheight = [[NSApp mainWindow] frame].size.height;
if (ycoord >= windowheight - menuheight && ![[NSApp mainWindow] ignoresMouseEvents]) {
[[NSApp mainWindow] setIgnoresMouseEvents:YES];
[NSApp sendEvent:event];
NSLog(#"click");
[[NSApp mainWindow] setIgnoresMouseEvents:NO];
return YES;
}
return NO;
}
As you can see, it changes the ignoresMouseEvents property of the main window to YES if the click was on the main menu bar - after which it calls sendEvent: in NSApplication. Finally it changes the ignoresMouseEvents property of the main window back to NO.
However, even though the log does say 'click' when the main menu bar is clicked, the click has no effect. If I click a menu item (eg. the 'File' menu item), for example, it will not open the corresponding menu (in this case the file menu).
What am I doing wrong?
The window that an event is targeted toward is decided by the window server before your app even receives it. It is not decided at the time of the call to -sendEvent:. The primary effect of -setIgnoresMouseEvents: is to inform the window server, not Cocoa's internals, how to dispatch mouse events.
Except for something like event taps, once you've received an event, it's too late to re-target it.
Note, for example, that the NSEvent already has an associated -window before your call to -sendEvent:. -sendEvent: is just going to use that to dispatch it.
If you want to allow clicks in the menu bar, you should either size your window so it doesn't overlap the menu bar or you should set its window level to be behind the menu bar.
With Cocoa, how do I check if the mouse is inside a specified window of mine? I have the following code that detects if it's within the bounds of the window, but it incorrectly prints that it's inside if the window is closed/hidden but the mouse is still in that rectangle. It will also incorrectly say it's inside if another window is on top of it, but the mouse is within the region of the window I'm testing below it.
NSPoint mouse = [NSEvent mouseLocation];
BOOL mouseInside = NSPointInRect(mouse, self.window.frame);
if (!mouseInside) {
NSLog(#"mouse isn't inside");
} else {
NSLog(#"mouse is inside");
}
I've tried something like this:
while ((screen = [screenEnum nextObject]) && !NSMouseInRect(mouse, [screen frame], NO));
if (screen != self.window.screen && mouseInside) {
NSLog(#"mouse is inside.");
}
but it would always print "mouse is inside".
Any ideas? Or is setting up a tracking area the only way?
mikeash on Freenode pointed me to NSWindow's windowNumberAtPoint:
The following code appears to work as needed:
if ([NSWindow windowNumberAtPoint:mouse belowWindowWithWindowNumber:0] != self.window.windowNumber) {
NSLog(#"mouse outside");
}
When changing the dock position Cocoa is firing a NSApplicationDidChangeScreenParametersNotification:
The problem is that as for Apple Docs, it should be raised only when
Posted when the configuration of the displays attached to the computer
is changed. The configuration change can be made either
programmatically or when the user changes settings in the Displays
control panel. The notification object is sharedApplication. This
notification does not contain a userInfo dictionary.
So if you want to update your application windows when attach a new display (e.g. changing/moving the frame of some HUD window/etc), you will have a fake notification coming the dock.
Also there's no userInfo dictionary attached to this notification, so I had no chance to check whenever was the dock or a new display controller.
So how to handle this?
A possible solution is to check the [NSScreen mainScreen] size when the notification si fired.
If this NSSize changes, that notification comes from a new display attached, not from the dock:
static NSSize mainScreenSize;
-(void)handleApplicationDidChangeScreenParameters:(NSNotification *)notification {
NSSize screenSize = [[NSScreen mainScreen] frame].size;
if( screenSize.width != mainScreenSize.width || screenSize.height != mainScreenSize.height ) { // screen size changed
mainScreenSize = [[NSScreen mainScreen] frame].size;
[myWindowController updateContent];
[[myWindow contentView] setNeedsDisplay:YES]; // update custom window
}
The notification is fired because the main screen's visibleFrame (which excludes the space occupied by the Dock) depends on the position of the Dock.
So if the visibleFrame of the main screen changes, you can be sure that the notification is the result of the Dock being moved.
I am developing an Desktop application in which I should be able to take mouse events on transparent window. But, transparent NSWindow does not take mouse events. So, I have set setIgnoreMouseEvents to NO which allows the transparent window to take mouse events.
I have the problem in the following scenario:
There is dynamically created rectangular shape on this window. The transparent window should not take mouse events in this region; it should be delegated to the window (of some other app) that is present behind this shape.
For this purpose, if the mouseDown event is inside the shape I am setting setIgnoreMouseEvents to YES. Now, if the user performs mouse events in the area outside the shape the transparent window should take the event. Since, setIgnoreMouseEvents is set to YES, window does not take mouse events.
There is no way to identify that mouseDown event has occurred so that I can set setIgnoreMouseEvents to NO.
Could someone suggest me some best method to handle mouse events on transparent window?
Deepa
I've just come across Quartz Event Taps, which basically let you capture the mouse event and execute your own callback.
Haven't tried it out myself, but it seems like you should be able to check where the mouse click fell and execute conditionally on the values
Here's an example:
//---------------------------------------------------------------------------
CGEventRef MouseTapCallback( CGEventTapProxy aProxy, CGEventType aType, CGEventRef aEvent, void* aRefcon )
//---------------------------------------------------------------------------
{
if( aType == kCGEventRightMouseDown ) NSLog( #"down" );
else if( aType == kCGEventRightMouseUp ) NSLog( #"up" );
else NSLog( #"other" );
CGPoint theLocation = CGEventGetLocation(aEvent);
NSLog( #"location x: %d y:%d", theLocation.x, theLocation.y );
return aEvent;
}
//---------------------------------------------------------------------------
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
//---------------------------------------------------------------------------
{
CGEventMask theEventMask = CGEventMaskBit(kCGEventRightMouseDown) |
CGEventMaskBit(kCGEventRightMouseUp);
CFMachPortRef theEventTap = CGEventTapCreate( kCGSessionEventTap,
kCGHeadInsertEventTap,
0,
theEventMask,
MouseTapCallback,
NULL );
if( !theEventTap )
{
NSLog( #"Failed to create event tap!" );
}
CFRunLoopSourceRef theRunLoopSource =
CFMachPortCreateRunLoopSource( kCFAllocatorDefault, theEventTap, 0);
CFRunLoopAddSource( CFRunLoopGetCurrent(),
theRunLoopSource,
kCFRunLoopCommonModes);
CGEventTapEnable(theEventTap, true);
}