Cocoa Dock fires NSApplicationDidChangeScreenParametersNotification - cocoa

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.

Related

How to add watermark to Screen (NSScreen) in macOS

I am making an application that will add a watermark to the live screen on Mac. Which API should I choose? I tried NSScreen but I didn't find any method that could add a view to NSScreen.
Such app like Sakura in Mac Appstore.Please check it out,i have no idea which API should i use.
The easiest solution is to define a custom, transparent, window.
When you create the window, you specify the special BorderlessWindowMask. This creates a window that is a simple rectangular area on the screen with no titlebar, edges, etc.
Then you set up a bunch of properties so that
the window floats above the other windows
it doesn't respond to events
it doesn't have a shadow
its background is transparent
And so on
#implementation WatermarkOverlayWindow
- (id)initWithContentRect:(NSRect)contentRect
{
self = [super initWithContentRect:contentRect
styleMask:NSBorderlessWindowMask
backing:NSBackingStoreBuffered
defer:NO];
if (self!=nil)
{
self.level = NSFloatingWindowLevel;
self.ignoresMouseEvents = YES;
self.releasedWhenClosed = NO;
self.movableByWindowBackground = NO;
self.alphaValue = 1.0f;
self.backgroundColor = NSColor.clearColor;
self.opaque = NO;
self.hasShadow = NO;
}
return self;
}
...
Now you can add semi-transparent views to this window and those views will appear to float on the screen. Alternatively, you can place opaque views in this window and then change the overall alphaValue of the window to something less than 1.0.

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

NSOpenGLView toggle to fullscreen from within the view

I'm trying to create a method that will toggle between fullscreen and a window. I'm trying to do this from within a class inherited from NSOpenGLView, essentially following this blogpost. That works once, going from windowed to fullscreen; trying to go back fails in various ways: the window screen doesn't get updated, or I don't even manage switch to the window but the fullscreen just blanks out. Trying to go back and forth a few times anyway (mapped it to the 'f' key), the program often locks up, and in a worst case, I have to restart my computer.
I've attached the code for the method below; for debugging purposes, I've set the full frame rectangle much smaller, so that if things freeze, the application is never at full screen.
The fullscreen example in the Apple developer examples suggest using a controller, and does not go fullscreen from within the inherited NSOpenGLView.
My questions:
should I use a controller instead, and from there switch between windowed and fullscreen (creating a separate fullscreen view each time)? Or should both methods work?
If both methods should work, which one is preferred?
If both methods can work, what am I doing wrong in the current way of implementing this?
or, is there a third, better, method?
Note that for both references, I'll have to assume that things haven't changed for 10.8 (both references seem to apply to 10.6).
Code follows:
#implementation MyOpenGLView
[...]
- (void)toggleFullscreen
{
mainWindow = [self window];
if (isFullscreen) {
[fullscreenWindow close];
[mainWindow setAcceptsMouseMovedEvents:YES];
[mainWindow setContentView: self];
[mainWindow makeKeyAndOrderFront: self];
[mainWindow makeFirstResponder: self];
isFullscreen = false;
} else {
[mainWindow setAcceptsMouseMovedEvents:NO];
//NSRect fullscreenFrame = [[NSScreen mainScreen] frame];
NSRect fullscreenFrame = { {300, 300}, {300, 300} };
fullscreenWindow = [[NSWindow alloc] initWithContentRect:fullscreenFrame
styleMask:NSBorderlessWindowMask
backing:NSBackingStoreBuffered
defer:NO];
if (fullscreenWindow) {
[fullscreenWindow setAcceptsMouseMovedEvents:YES];
[fullscreenWindow setTitle:#"Full screen"];
[fullscreenWindow setReleasedWhenClosed: YES];
[fullscreenWindow setContentView: self];
[fullscreenWindow makeKeyAndOrderFront: self];
//[fullscreenWindow setOpaque:YES];
//[fullscreenWindow setHidesOnDeactivate:YES];
// Set the window level to be just above the menu bar
//[fullScreenWindow setLevel:NSMainMenuWindowLevel+1];
// Set the window level to be just below the screen saver
[fullscreenWindow setLevel:NSScreenSaverWindowLevel-1];
[fullscreenWindow makeFirstResponder:self];
isFullscreen = true;
} else {
NSLog(#"Error: could not switch to full screen.");
}
}
}
[...]
#end
I now think this can't be done, and should not be done. When windowed, the rendering context is a window, which is a different beast than a screen, when rendering fullscreen.
Thus, when switching between, things have to re-setup everytime you switch.
It is possible to simply use the native fullscreen option that is in the newest OS X variants. This will (presumably) enlarge the containg window to full screen size while removing the frame, borders and buttons. Thus, you're still rendering to a window, though it looks fullscreen.
I'm not sure if this option makes things slower: there's a window layer in between, which could make it a slower than rendering directly to a screen.
For the curious, implementing the native fullscreen is ridiculously easy (at least in 10.8 and 10.9): In XCode, select the .xib file, select the (main) window in the editor's sidebar, then select the attributes selector on the right. You can find a "Full Screen" selection between Unsupported, Primary Window or Auxiliary Window. That will automatically add the full screen toggle to the window.
Even neater, now select the main menu -> view menu in the sidebar, find the "Full Screen Menu Item" in the inspector at the bottom (there's a search bar for it), drag it into the View menu in the editor, and voilà, it will have a shortcut and automatically connect to the full screen option for the window (select the new View menu item and look at the Connections inspector to it's already connected for you).
A nice way to test all this is to grab the full screen example I linked in my question, and edit it as suggested above. Using the default control-command F shortcut to toggle back and forth between fullscreen will show the opengl view and the frame with text below it in a full screen. Using the fullscreen option as coded in the example will toggle the openglview to use the fullscreen, without any extra (Cocoa) frames, buttons or text.
I'm curious about this too- specifically your first two bullet point questions.
This doesn't address those questions, but your third one about the bug, I think you can get away with just changing the properties of the same window (works for me):
- (void)toggleFullscreen
{
if (isFullscreen) {
NSRect windowFrame = [[NSScreen mainScreen] visibleFrame];
[mainWindow setStyleMask:NSTitledWindowMask | NSClosableWindowMask |
NSMiniaturizableWindowMask | NSResizableWindowMask ];
[mainWindow setFrame:windowFrame display:true];
[mainWindow setAcceptsMouseMovedEvents:YES];
[mainWindow setLevel:NSNormalWindowLevel];
[mainWindow setTitle:#"SimpleOculus"];
[mainWindow makeKeyAndOrderFront:self];
[mainWindow makeFirstResponder:self];
isFullscreen = false;
}
else {
NSRect fullscreenFrame = [[NSScreen mainScreen] frame];
[mainWindow setStyleMask:NSBorderlessWindowMask];
[mainWindow setFrame:fullscreenFrame display:true];
[mainWindow setAcceptsMouseMovedEvents:YES];
[mainWindow setLevel:NSScreenSaverWindowLevel-1];
[mainWindow makeKeyAndOrderFront:self];
[mainWindow makeFirstResponder:self];
isFullscreen = true;
}
}

Two Finger Drag with IKImageView and NSScrollView in Mountain Lion

I have a Mac App that's been in the app store for a year or so now. It was first published with target SDK 10.7, Lion. Upon the update to Mountain Lion it no longer works.
The application displays large images in an IKImageView which is embedded in an NSScrollView. The purpose of putting it into a scrollview was to get two finger dragging working, rather than the user having to click to drag. Using ScrollViewWorkaround by Nicholas Riley, I was able to use two finger scrolling to show the clipped content after the user had zoomed in. Just like you see in the Preview app.
Nicholas Riley's Solution:
IKImageView and scroll bars
Now in Mountain Lion this doesn't work. After zooming in, pinch or zoom button, the image is locked in the lower left portion of the image. It won't scroll.
So the question is, what's the appropriate way to display a large image in IKImageView and have two finger dragging of the zoomed image?
Thank you,
Stateful
Well, Nicholas Riley's Solution is an ugly hack in that it addresses the wrong class; the issue isn't with NSClipView (which he subclassed, but which works just fine as is), but with IKImageView.
The issue with IKImageView is actually quite simple (God knows why Apple hasn't fixed this in what? … 7 years ...): Its size does not adjust to the size of the image it displays. Now, when you embed an IKImageView in an NSScrollView, the scroll view obviously can only adjust its scroll bars relative to the size of the embedded IKImageView, not to the image it contains. And since the size of the IKImageView always stays the same, the scroll bars won't work as expected.
The following code subclasses IKImageView and fixes this behavior. Alas, it won't fix the fact that IKImageView is crash-prone in Mountain Lion as soon as you zoom …
///////////////////// HEADER FILE - FixedIKImageView.h
#import <Quartz/Quartz.h>
#interface FixedIKImageView : IKImageView
#end
///////////////////// IMPLEMENTATION FILE - FixedIKImageView.m
#import "FixedIKImageView.h"
#implementation FixedIKImageView
- (void)awakeFromNib
{
[self setTranslatesAutoresizingMaskIntoConstraints:NO]; // compatibility with Auto Layout; without this, there could be Auto Layout error messages when we are resized (delete this line if your app does not use Auto Layout)
}
// FixedIKImageView must *only* be used embedded within an NSScrollView. This means that setFrame: should never be called explicitly from outside the scroll view. Instead, this method is overwritten here to provide the correct behavior within a scroll view. The new implementation ignores the frameRect parameter.
- (void)setFrame:(NSRect)frameRect
{
NSSize imageSize = [self imageSize];
CGFloat zoomFactor = [self zoomFactor];
NSSize clipViewSize = [[self superview] frame].size;
// The content of our scroll view (which is ourselves) should stay at least as large as the scroll clip view, so we make ourselves as large as the clip view in case our (zoomed) image is smaller. However, if our image is larger than the clip view, we make ourselves as large as the image, to make the scrollbars appear and scale appropriately.
CGFloat newWidth = (imageSize.width * zoomFactor < clipViewSize.width)? clipViewSize.width : imageSize.width * zoomFactor;
CGFloat newHeight = (imageSize.height * zoomFactor < clipViewSize.height)? clipViewSize.height : imageSize.height * zoomFactor;
[super setFrame:NSMakeRect(0, 0, newWidth - 2, newHeight - 2)]; // actually, the clip view is 1 pixel larger than the content view on each side, so we must take that into account
}
//// We forward size affecting messages to our superclass, but add [self setFrame:NSZeroRect] to update the scroll bars. We also add [self setAutoresizes:NO]. Since IKImageView, instead of using [self setAutoresizes:NO], seems to set the autoresizes instance variable to NO directly, the scrollers would not be activated again without invoking [self setAutoresizes:NO] ourselves when these methods are invoked.
- (void)setZoomFactor:(CGFloat)zoomFactor
{
[super setZoomFactor:zoomFactor];
[self setFrame:NSZeroRect];
[self setAutoresizes:NO];
}
- (void)zoomImageToRect:(NSRect)rect
{
[super zoomImageToRect:rect];
[self setFrame:NSZeroRect];
[self setAutoresizes:NO];
}
- (void)zoomIn:(id)sender
{
[super zoomIn:self];
[self setFrame:NSZeroRect];
[self setAutoresizes:NO];
}
- (void)zoomOut:(id)sender
{
[super zoomOut:self];
[self setFrame:NSZeroRect];
[self setAutoresizes:NO];
}
- (void)zoomImageToActualSize:(id)sender
{
[super zoomImageToActualSize:sender];
[self setFrame:NSZeroRect];
[self setAutoresizes:NO];
}
- (void)zoomImageToFit:(id)sender
{
[self setAutoresizes:YES]; // instead of invoking super's zoomImageToFit: method, which has problems of its own, we invoke setAutoresizes:YES, which does the same thing, but also makes sure the image stays zoomed to fit even if the scroll view is resized, which is the most intuitive behavior, anyway. Since there are no scroll bars in autoresize mode, we need not add [self setFrame:NSZeroRect].
}
- (void)setAutoresizes:(BOOL)autoresizes // As long as we autoresize, make sure that no scrollers flicker up occasionally during live update.
{
[self setHasHorizontalScroller:!autoresizes];
[self setHasVerticalScroller:!autoresizes];
[super setAutoresizes:autoresizes];
}
#end

NSStatusItem (cocoa) location on screen

I am trying to get the on screen location of an NSStatusItem so that I can perform a click on that area via code like below. I am doing this so that my users can press a hotkey to see the menu.
event = CGEventCreateMouseEvent(NULL, kCGEventLeftMouseDown, newLocation, kCGMouseButtonLeft);
CGEventPost(kCGHIDEventTap, event);
CFRelease(event);
Does anyone know of a way to get the location?, I have been trying ideas and searching for days and have found several ideas but none of them seem to work in leopard/snow leopard
The NSStatusItem is using an NSMenu not a custom view.
Don't do this by simulating a mouse event, that is completely the wrong way to go.
You can use the -popUpStatusItemMenu: method of NSStatusItem to show the menu. You can call this method from a hot key by using a CGEventTap to capture global events that your NSStatusItem can respond to.
On 10.6 you can use the +addGlobalMonitorForEventsMatchingMask:handler: method of NSEvent to capture global events instead of using CGEventTap directly.
Thinking out loud you could create your own custom view for the NSStatusItem. Then for example from the AppController:
- (void)awakeFromNib {
float width = 30.0;
float height = [[NSStatusBar systemStatusBar] thickness];
NSRect viewFrame = NSMakeRect(0, 0, width, height);
statusItem = [[[NSStatusBar systemStatusBar] statusItemWithLength:width] retain];
[statusItem setView:[[[MyCustomView alloc] initWithFrame:viewFrame controller:self] autorelease]];
And to simulate the highlight you'd send a message to your custom view. Then when drawing MyCustomView:
// In MyCustomView.m
- (void)drawRect:(NSRect)rect {
if (clicked) {
[[NSColor selectedMenuItemColor] set];
NSRectFill(rect);
}
As well as using -popUpStatusItemMenu:.

Resources