Handling mouse events on transparent window conditionally - macos

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);
}

Related

Custom NSButton not working from CoreMIDI callback function

I'm creating a piano app (for OSX) that has an onscreen keyboard that displays what the user is playing on their synth keyboard. Everything is connected using the CoreMIDI framework. I have created some customs buttons by subclassing NSButton in a class called PianoKey. The class can be seen below:
#import "PianoKey.h"
#implementation PianoKey
//updates the core animation layer whenever it needs to be changed
- (BOOL)wantsUpdateLayer {
return YES;
}
- (void)updateLayer {
//tells you whether or not the button is pressed
if ([self.cell isHighlighted]) {
NSLog(#"BUTTON PRESSED");
self.layer.contents = [NSImage imageNamed:#"buttonpressed.png"];
}
else {
NSLog(#"BUTTON NOT PRESSED");
self.layer.contents = [NSImage imageNamed:#"button.png"];
}
}
#end
The "PianoKeys" are created programmatically. When unpressed, the piano keys are blue and when they are pressed they are pink (just temp colours). The colour switch works fine if I click the buttons on screen, but they are not changing when I try to play through my MIDI keyboard.
Some things to note:
1) The MIDI keyboard works
2) I am successfully getting MIDI data from the keyboard.
a) This MIDI data is passed to a callback function called midiInputCallback as seen below:
[from class AppController.m]
void midiInputCallback(const MIDIPacketList *list, void *procRef, void *srcRef) {
PianoKey *button = (__bridge PianoKey*)procRef;
UInt16 nBytes;
const MIDIPacket *packet = &list->packet[0]; //gets first packet in list
for(unsigned int i = 0; i < list->numPackets; i++) {
nBytes = packet->length; //number of bytes in a packet
handleMIDIStatus(packet, button);
packet = MIDIPacketNext(packet);
}
}
The object button is a reference to the button that I've created programatically as defined in AppController.h as:
#property PianoKey *button; //yes, this is synthesized in AppController.m
b) The callback function calls a bunch of functions that handle the MIDI data. If a note has been detected as being played, this is where I set my custom button to be highlighted...this can be seen in the function below:
void updateKeyboardButtonAfterKeyPressed(PianoKey *button, int key, bool keyOn) {
if(keyOn)
[button highlight:YES];
else
[button highlight:NO];
[button updateLayer];
}
[button updateLayer] calls my updateLayer() method from PianoKey, but it doesn't change the image. Is there something I'm missing? It seems as if the view or window is not being updated, even though my button says that it is "highlighted". Please help! Thanks in advance :)
Just a guess (but a good one...;-) Is the callback function being called on the main thread? If not, that could be the problem - at leas that is the way it works in iOS: the UI (screen drawing) is only updated in the main thread.
Put a log statement or breakpoint in the callback to see if it is getting called. If it is, then the problem is due to this thread issue.
Update:
So tell the button to update itself - but on the main thread (I think I have the syntaxt right - at least for iOS - OSX may vary)
[button performSelectorOnMainThread:#selector(updateLayer)
withObject:nil
waitUntilDone:FALSE];

-SendEvent does not behave as expected

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.

Is it possible to detect if Magic Mouse/Trackpad is being touched?

Just wondering if there is a way to receive a callback in Cocoa if Magic Mouse or Trackpad is being touched by the user?
I looked into Quartz Events, but it seems I can only get callbacks if the mouse is moving or clicked etc.
Note that I want to receive a callback even if my app is not active. It's a background utility app. Also, it can't use private frameworks as it's going to be a Mac App Store app.
You could use this code to trap the events: (create a new Cocoa application and put this in the application delegate)
NSEventMask eventMask = NSEventMaskGesture | NSEventMaskMagnify | NSEventMaskSwipe | NSEventMaskRotate | NSEventMaskBeginGesture | NSEventMaskEndGesture;
CGEventRef eventTapCallback(CGEventTapProxy proxy, CGEventType type, CGEventRef eventRef, void *userInfo) {
NSEvent *event = [NSEvent eventWithCGEvent:eventRef];
// only act for events which do match the mask
if (eventMask & NSEventMaskFromType([event type])) {
NSLog(#"eventTapCallback: [event type] = %ld", [event type]);
}
return [event CGEvent];
}
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
CFMachPortRef eventTap = CGEventTapCreate(kCGSessionEventTap, kCGHeadInsertEventTap, kCGEventTapOptionListenOnly, kCGEventMaskForAllEvents, eventTapCallback, nil);
CFRunLoopAddSource(CFRunLoopGetCurrent(), CFMachPortCreateRunLoopSource(kCFAllocatorDefault, eventTap, 0), kCFRunLoopCommonModes);
CGEventTapEnable(eventTap, true);
}
but.. sandboxing will probably prevent you from using CGEventTapCreate because by nature, it allows an application to listen to the whole event system, which is not very secure. If not using the sandboxing is acceptable to you, then eventTapCallback is called when a new touch is made on the touchpad.

cocoa - screen not refreshing after CGEventPost mouse click

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);
}

CGEventTap blocks application input

I'm trying to use CGCreateEventTap to monitor global mouse clicks, however when I do this it seems to block interaction with my own app. Mouse clicks in other running apps work fine, but my own app (that is the DemoAppDelegate app) does not respond completely. I can drag the main window for the app, but the red/yellow/green window buttons are greyed out. And the DemoApp's menu is unclickable as well.
This seems really strange to me, and I've been unable to figure it out. Examples of using event taps are few and far between, so any advice is greatly appreciated.
#import "DemoAppDelegate.h"
CGEventRef myCGEventCallback(CGEventTapProxy proxy, CGEventType type, CGEventRef event, void *refcon) {
CGPoint location = CGEventGetLocation(event);
NSLog(#"location: (%f, %f) - %#\n", location.x, location.y, (NSString*)refcon);
return event;
}
#implementation DemoAppDelegate
#synthesize window;
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
CFMachPortRef eventTap;
CGEventMask eventMask;
CFRunLoopSourceRef runLoopSource;
eventMask = 1 << kCGEventLeftMouseDown;
eventTap = CGEventTapCreate(kCGSessionEventTap, kCGHeadInsertEventTap,
1, eventMask, myCGEventCallback, #"mydata");
runLoopSource = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, eventTap, 0);
CFRunLoopAddSource(CFRunLoopGetCurrent(), runLoopSource,
kCFRunLoopCommonModes);
CGEventTapEnable(eventTap, true);
CFRunLoopRun();
}
#end
When you create a Cocoa application, -[NSApplication run] is responsible for running the event loop — it runs the run loop, and dispatches events. This means that you should remove that
CFRunLoopRun();
call at the bottom of your -applicationDidFinishLaunching: method implementation, since it prevents -applicationDidFinishLaunching: from returning and also prevents NSApplication from dispatching events.

Resources