7
votes

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...

4
I dont know exactly what you are trying to acomplish, but maybe instead of re-implementing the screenshot mechanism yourself you can get by with one of these solutions: stackoverflow.com/questions/4516852/…Brad Allred
@BradAllred I'm trying to build the same user experience as Cmd-Shift-3, but without the user having to save the file first. Seems like that's what the answers suggest in the question you referenced.Mark
So, FYI, you can hold the ctrl key to take a screenshot to the clipboard... dont know if that is all you are trying to do. otherwise, yes, one of the suggestions in the linked thread may be the best (or my answer below)Brad Allred
Setting NSApplicationActivationPolicy.Prohibited is another approach... depending on the app's needs, of course.Milos

4 Answers

3
votes

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.

1
votes

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.

0
votes

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.

0
votes

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 :)