简体   繁体   中英

Prevent activating the application when clicking on NSWindow/NSView

I'm working on a screenshot Mac app. I'm trying to rebuilt what happens when you press Cmd-Ctrl-Shift-4: the cross hair cursor and the selection rectangle for the screenshot.

I'm using a custom borderless NSWindow on top of all other windows. I disabled the cursor to draw my own along with the selection rectangle.

My problem is that as soon as I click & drag to capture a screenshot, my app gets activated (because the click is intercepted by my shielding window).

Is there a way how I can receive the click in my custom view/window without having my app get activated?

I tried using an NSPanel with the NSNonactivatingPanelMask flag, but in this case, I have a problem with the cursor: I can't draw my own when another app is active, because I can't hide the cursor for other apps...

Actually, I have a new, better answer to this question involving more undocumented goodies. Here it is for future posterity:

There is an undocumented method on NSWindow that does exactly what you want:

@interface NSWindow (Private)
- (void )_setPreventsActivation:(bool)preventsActivation;
@end

[myWindow _setPreventsActivation:true];

This stops the window from activating both itself and its application when the user clicks on it.

The standard warnings about using undocumented APIs of course apply: Apple may change this at some point (although it's been around for many OS X versions so there's a good chance they won't) and using this may get your app rejected from the Mac app store.

For what it's worth, there's another way to make the cursor invisible globally other than creating a giant window. It involves some undocumented APIs if that's something you can use:

extern "C" {
    typedef int CGSConnection;
    void CGSSetConnectionProperty(int, int, const void *, const void *);
    int CGSMainConnectionID();
}

void allowHidingCursorForBackgroundOnlyApp()
{
    CFStringRef propertyString = CFStringCreateCopy(NULL, CFSTR("SetsCursorInBackground"));
    CGSSetConnectionProperty(CGSMainConnectionID(), CGSMainConnectionID(), propertyString, kCFBooleanTrue);
    CFRelease((CFTypeRef)propertyString);
}

Combine that with judicious use of event taps to capture and filter out mouse clicks, and you can create the same effect as the built-in screen shot feature.

I pray that there is a better way to do this now, but when I had to do something similar I ended up letting my window/view ignore all mouse input, then I used a CGEventTap (see Quarts Event Services documentation ) to capture mouse events globally(without removing them from the event queue). I them mapped them manually to my window, created a custom copy NSEvent and manually dispatched it to my window.

The huge downside here (aside from complexity) is that I recall needing to run as root to be able to install the event tap. However, I think there is a way to get permission though universal access.

I'm completely unsure if dispatching a custom NSEvent directly to the window will have the same side effect of activating your application; especially since many things have changed since 10.6... I would suggest a simple test to see if this is feasible before pursuing it.

One more idea, you can override - (BOOL)_isNonactivatingPanel private method in NSWindow subclass:

@implementation MyWindow

- (BOOL)_isNonactivatingPanel
{
    return YES;
}

@end

Voila, you got behaviour similar to NSPanel :)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM