Prevent activating the application when clicking on NSWindow/NSView - macos

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

Related

Add an NSView around cursor?

Simple question, starting out with macOS stuff – I’d like to create a small radial menu around my cursor, top-most, above whatever application is currently active, whenever a specific mouse button is pressed.
I have the specific mouse button over all application down, but I’m wondering where I need to draw that NSView, i.e. “topmost”. I guess on iOS this would be at the UIWindow level, but would NSWindow be the wrong approach here?
this is purely an opinion-based question, but basically if you want to present any custom content on the desktop, that should use NSWindow, and you can customise the window's content for your wish.
NOTE: you can find more information about the NSWindow class in Apple's Class Reference Docs.

Resume NSProgressIndicator animation after window minimised and then restored

I'm having trouble with the animation of some subclassed indeterminate NSProgressIndicators. They start and stop animating without any issues. However, if I minimise the window while animating, stopAnimation: / StopAnimation(NSObject sender) is called, which makes sense to save resources if the window is not visible. I assume this is invoked from the cocoa framework itself looking at the stacktrace.
The problem then arises when the window is restored, the animation is not resumed.
I've seen you can use the NSCoding Protocol and can override encodeWithEncoder: / EncodeTo(NSCoder encoder) to save some state, and then use that saved state in initWithCoder: / AppProgressIndicatorBar(NSCoder coder) to resume. But the problem here was that my encodeWithEncoder: / EncodeTo(NSCoder encoder) was never called.
Looking at this SO question and answer, it should be handled automatically if the object needs to be serialized. So I'm not sure why it's not being called.
That same answer says you can do it explicitly with NSKeyedArchiver, but then I would need to listen with NSWindowDelegate to know when the window is minimizing / restoring. In which case, I could just use this, and not use the NSCoding Protocol...
This just feels dirty, and I would imagine this is a very common scenario. So how should / do you resume animation? I'm new to cocoa, coming from a mostly .NET background, and I think this problem is a symptom of my limited cocoa knowledge.
I'm using Xamarin Mac, and have tried to give the Objective-C and C# method signatures. I'll be happy for a solution in either, I'll be able to (hopefully!) convert it to the C# equivalent.
For completeness, here is my current Xamarin Mac subclass using the NSCoder Protocol where EncodeTo is not being called. I'm running OS X 10.11.3 and Xamarin Studio 5.10.2.
[Register("AppProgressIndicatorBar")]
public class AppProgressIndicatorBar : NSProgressIndicator, INSCoding
{
...
public AppProgressIndicatorBar(NSCoder coder) : base(coder)
{
...
}
...
public override void EncodeTo(NSCoder encoder)
{
base.EncodeTo(encoder);
...
}
...
}
You should be able to use the NSWindowWillMiniaturizeNotification, NSWindowDidMiniaturizeNotification and NSWindowDidDeminiaturizeNotification notifications or the windowWillMiniaturize:, windowDidMiniaturize: and windowDidDeminiaturize: Window delegate methods to track the state of your window and restore the state of your progress bar when the window deminiaturises (is that really a word?).
HTH

OSX - How to identify window under cursor in all spaces including fullscreen

I'm using Window Services' CGWindowListCreate and CGWindowListCreateDescriptionFromArray to get window information. When getting kCGWindowBounds in a regular Space everything works fine (I'm drawing borders around the frontmost window on the 0th level). However, when I use the same method while on a fullscreen application's Space, I get nonsense bounds: (0, 855, 480, 1).
I wouldn't care much about this if there was an easy way to tell if I'm currently at a fullscreen app's Space, because then I'd just draw a border around the screen (well... it would depend if the menu bar is showing...).
Is this a bug, or is there a reason for this behavior?
EDIT:
Figured out my problem. It's a bigger issue than I would have liked. The thing is the API goes through ALL NSWindows, even the ones that aren't, well, normal windows. Chrome's loading bar on the bottom is a window by itself, for example, and Mail also has some window on the top of the app. This is a problem because I have no way to differentiate the window that looks to be frontmost.
For my app, I would like to capture a specific window to intercept mouse events in it. I would have liked to be able to have the user press a hotkey and then click on the desired window to select, but there is no API to get the window under the cursor. I have no clue how to proceed.
Edit 2:
To better help people find a useful answer, changed title from: "Quartz Window Services returning wrong window bounds for fullscreen apps"
Have you got these methods defined for the window delegate?
- (NSSize)window:(NSWindow *)window willUseFullScreenContentSize:(NSSize)proposedSize
{
NSRect mainDisplayRect = [[NSScreen mainScreen] frame];
CGSize cgScreenSize = CGSizeMake(mainDisplayRect.size.width, mainDisplayRect.size.height);
return cgScreenSize;
}
- (void)windowWillEnterFullScreen:(NSNotification *)notification
{
}
- (void)windowDidEnterFullScreen:(NSNotification *)notification
{
}
- (void)windowWillExitFullScreen:(NSNotification *)notification
{
}
I proceeded by going through the description dictionaries and checking if the current cursor position was inside the bounds of the windows. The first window to satisfy this would be the window right under the cursor, which is exactly what I needed.
Separately, to find the current top-most window, I used the iChat Apple example of the Accessibility API to register ApplicationActivatedNotification and MainWindowDidChangeNotifications. Both notifications combined would let me keep track of the main window of the active app (top-most). To get the bounds in this case, I just got the main window's position and size using the Accessibility API.

System-wide recognition of scroll events on Mac OSX and setting focus to a different window

I'm registering for global mouse wheel events in my cocoa application. My goal is to have some kind of background application to be able to focus a window of another application when the user scrolls in it's window. If possible with Objective-C and Cocoa, what route would I need to go if I wanted to do this?
My code for the event registering looks like this:
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
[NSEvent addGlobalMonitorForEventsMatchingMask:
NSScrollWheelMask handler:^(NSEvent * ev) {
NSLog(#"%#", ev.description);
}];
}
This works, but the data captured in the event (like the window or the windowid) I don't seem to be able to manipulate - and the window id doesn't even seem to be the correct one, as I can get a list of windows and get a different id in there - just the screen position seems to be accurate. So three questions to solve this riddle:
How can I get a window or window id at a certain location on the
screen?
If I can only get a window id, how can I find the appropriate
application or window object to manipulate?
I guess I would need the accessibility API for manipulating the
window and giving it focus. How does that work?
Maybe these are simple tasks, but I've not ever written a Mac-Cocoa application before. Before suggesting documentations to read, you should know that I already scanned all the documentation, and that I better learn by example than by reading books :-)
EDIT: I just found out that I might use the ProcessManager to bring the application to the front. If you think this is a possible solution, ho can I get the process id for the window on a certain point on the screen?
EDIT2: I don't want to use Carbon APIs.

Global Mouse Moved Events in Cocoa

Is there a way to register for global mouse moved events in Cocoa? I was able to register for the events using Carbon's InstallEventHandler(), but would prefer a Cocoa equivalent. I have looked for NSNotificationCenter events, but there doesn't seem to be any public event names (are there private ones?)
Alternatively, is there a way to use NSTrackingArea for views with a clearColor background?
The app is Snow Leopard only.
In SnowLeopard there is a new class method on NSEvent which does exactly what you want: + (id)addGlobalMonitorForEventsMatchingMask:(NSEventMask)mask handler:(void (^)(NSEvent*))block. You’ll want mask = NSMouseMovedMask.
A similar question was already asked on StackOverflow:
How to make a transparent NSView subclass handle mouse events?
To summarize, the tansparent view method didn't work. Quartz Event Taps seem to be the best answer.
Here are some hints on working with taps:
Create the tap with CGEventTapCreate.
a) For the location (first) parameter you'll probably want to use kCGSessionEventTap.
b) For the placement (second) parameter you'll probably want kCGHeadInsertEventTap.
c) For the event mask parameter, try (1 << kCGEventMouseMoved).
Create a run loop source with CFMachPortCreateRunLoopSource, passing the event tap as the second parameter.
Add the run loop source to your run loop. Assuming you want it added to the main run loop, do:
CFRunLoopAddSource(CFRunLoopGetMain(), sourceFromStep2, kCFRunLoopDefaultMode);
Enable the event tap with CGEventTapEnable
If you want to track the mouse no matter where it is, you want a CGEventTap. There is no Cocoa equivalent. If you just want to track it in your application then you should explain why you're finding yourself unable to do so a little more thoroughly.

Resources