Mac OS X -- receive notification when frontmost window changes - objective-c

I am wondering whether there is a way in Mac OS X to receive a notification when the frontmost window switches to a different window -- either an Objective-C solution, or Python, or AppleScript, or something else. I want to look at the whole system, not just within my application. My app is trying to keep track of what file the user is currently working on, and I have a polling solution that gets the frontmost app and frontmost window every so often by running an AppleScript, but it would simplify my life if I could run that check only when I knew that the frontmost window had changed.
I've also looked at NSDistributedNotificationCenter and global event monitors for NSEvents, which are both useful in different ways, but don't seem able to give me the specific front-window-change notification that I'm ideally looking for.
Any ideas on directions I should try, or whether this is even possible, would be greatly appreciated!

I don't know of a way to get a notification when a window changes, however in objective-c you can get a notification when things happen at the application level. That might help you.
You want to register for NSWorkspace notifications...
[[[NSWorkspace sharedWorkspace] notificationCenter] addObserver:self selector:#selector(nsworkspaceNotification:) name:nil object:nil];
Look at the bottom of the NSWorkspace class documentation for the notifications. Some that would help you are: NSWorkspaceDidLaunchApplicationNotification, NSWorkspaceDidActivateApplicationNotification, NSWorkspaceDidDeactivateApplicationNotification, NSWorkspaceDidHideApplicationNotification, NSWorkspaceDidUnhideApplicationNotification. There may be others.
Good luck.

I think you would capture NSWindowDidBecomeMainNotification. The notification object contains NSWindow.
best,

Related

NSUserNotificationCenter defaultNotificationcenter returns nil

I feel like I am going insane here.
I'm making a little command-line tool to let me know when a Twitch channel I like goes live. So far, everything works. There's only one, small issue: [NSUserNotificationCenter defaultUserNotificationCenter] is consistently returning nil, no matter where I call it in the program. This is a bit of a problem, since I need to be able to send notifications.
After a solid hour of googling, I can't figure out what's going on. Something like [[NSUserNotificationCenter defaultUserNotificationCenter] setDelegate:myDelegate] doesn't crash, but I'm pretty sure that's because you can send messages to nil in Obj-C. Similarly, I can do something like [[NSUserNotificationCenter defaultNotificationCenter] deliverNotification: mynote], but nothing is displayed onscreen (and this is with the delegate for [NSUserNotificationCenter defaultUserNotificationCenter] set to return YES for the userNotificationCenter:(NSUserNotificationCenter *)center shouldPresentNotification:(NSUserNotification *)method.)
Additionally, all notifications presented via deliverNotification do not have their delivered property set to YES.
The only thing I can think of is that the usage of NSUserNotificationCenter only works if you're running an NSApplication, but I cannot find that documented anywhere. I'm running a simple command-line tool, which is pretty much a main function, an #autoreleasepool, and some method calls.
Is there anything obvious that can cause [NSUserNotificationCenter defaultUserNotificationCenter] to return nil?
The only thing I can think of is that the usage of NSUserNotificationCenter only works if you're running an NSApplication, but I cannot find that documented anywhere.
I don't think it would be easy to find it explicitly documented somewhere, but if your app is a simple command line tool, i.e., it is not using a NSRunLoop, I doubt that features like notifications can work at all. They intrinsically require your program to have a run loop that sits there waiting for events (e.g., a notification coming in, a click, a touch) and dispatching them to whomever should handle them.
You might try starting and NSRunLoop from you main function, but it would cease to be a simple command-line tool, I guess.

NSWorkspaceDidActivateApplicationNotification called before application is ready to use (launched)

I'm using both NSWorkspaceDidActivateApplicationNotification and NSWorkspaceDidLaunchApplicationNotification notifications to know which app the user is interacting with.
The problem is that, if an application is just opened and still launching, I first receive a activate notification, and soon afterwards a launch notification.
Is there any way to know within the activate method that the app is still launching and not yet ready for use? (Still bouncing in the dock)
I see that the ichat sample project by apple does not use the above approach and instead only listens to launch notifications. It then uses kAXApplicationActivatedNotification to add an AXObserver to the app. Is this the preferred way? (And also NSRunningApplications to add an observer to all already loaded apps).
I wanted to keep using just plain simple NSNotifications because I think it may be less memory intensive. (No need to keep an observer around for each and every app loaded).
check the NSRunningApplication object passed in the userinfo of the NSWorkspaceDidActivateApplicationNotification
NSRunningApplication *app = [note.userInfo objectForKey:NSWorkspaceApplicationKey];
if(app.isFinishedLaunching)
NSLog(#"up");

Receive remote control events without audio

Here is some background information, otherwise skip ahead to the question in bold. I am building an app and I would like it to have access to the remote control/lock screen events. The tricky part is that this app does not play audio itself, it controls the audio of another device nearby. The communication between devices is not a problem when the app is in the foreground. As I just found out, an app does not assume control of the remote controls until it has played audio with a playback audio session, and was the last do so. This presents a problem because like I said, the app controls ANOTHER device's audio and has no need to play its own.
My first inclination is to have the app play a silent clip every time it is opened in order to assume control of the remote controls. The fact that I have to do this makes me wonder if I am even going to be allowed to do it by Apple or if there is another way to achieve this without fooling the system with fake audio clips.
QUESTION(S): Will Apple approve an app that plays a silent audio clip in order to assume control of the remote/lock screen controls for the purpose of controlling another device's audio? Is there any way of assuming control of the remote controls without an audio session?
P.S. I would prefer to have this functionality on iOS 4.0 and up.
P.P.S I have seen this similar question and it has gotten me brainstorming but the answer provided is not specific to what I need to know.
NOTE: As of iOS 7.1, you should be using MPRemoteCommandCenter instead of the answer below.
You create various system-provided subclasses of MPRemoteCommand and assign them to properties of the [MPRemoteCommandCenter sharedCommandCenter].
I'm keeping the rest of this around for historical reference, but the following is not guaranteed to work on recent iOS versions. In fact, it just might not.
You definitely do need an audio player but not necessarily an explicit session to take control of the remote control events. (AVAudioSession is implicit to any app that plays audio.) I spent a decent amount of time playing with this to confirm this.
I've seen a lot of confusion on the internet about where to set up the removeControlEventRecievedWithEvent: method and various approaches to the responder chain. I know this method works on iOS 6 and iOS 7. Other methods have not. Don't waste your time handling remote control events in the app delegate (where they used to work) or in a view controller which may go away during the lifecycle of your app.
I made a demo project to show how to do this.
Here's a quick rundown of what has to happen:
You need to create a subclass of UIApplication. When the documentation says UIResponder, it means UIApplication, since your application class is a subclass of UIResponder. In this subclass, you're going to implement the remoteControlReceivedWithEvent: and canBecomeFirstResponder methods. You want to return YES from canBecomeFirstResponder. In the remote control method, you'll probably want to notify your audio player that something's changed.
You need to tell iOS to use your custom class to run the app, instead of the default UIApplication. To do so, open main.m and change this:
return UIApplicationMain(argc, argv, nil, NSStringFromClass([RCAppDel`egate class]));
to look like this:
return UIApplicationMain(argc, argv, NSStringFromClass([RCApplication class]), NSStringFromClass([RCAppDelegate class]));
In my case RCApplication is the name of my custom class. Use the name of your subclass instead. Don't forget to #import the appropriate header.
OPTIONAL: You should configure an audio session. It's not required, but if you don't, audio won't play if the phone is muted. I do this in the demo app's delegate, but do so where appropriate.
Play something. Until you do, the remote controls will ignore your app. I just took an AVPlayer and gave it the URL of a streaming site that I expect to be up. If you find that it fails, put your own URL in there and play with it to your heart's content.
This example has a little bit more code in there to log out remote events, but it's not all that complicated. I just define and pass around some string constants.
I bet that a silent looping MP3 file would help work towards your goal.
Moshe's solution worked great for me! However one issue I noticed is when you paused the audio, the media controls would go away and you won't be able to play it again without going back into the app. If you set the Media Info on the lock screen when you play the audio then this won't happen:
NSDictionary *mediaInfo = #{MPMediaItemPropertyTitle: #"My Title",
MPMediaItemPropertyAlbumTitle: #"My Album Name",
MPMediaItemPropertyPlaybackDuration: [NSNumber numberWithFloat:0.30f]};
[[MPNowPlayingInfoCenter defaultCenter] setNowPlayingInfo:mediaInfo];

Launch Mac application when iTunes starts without background process (like last.fm)

I would like to start my OSX application when iTunes loads, without having a background process to monitor when iTunes launches. The last.fm client seems to do this; I can find no background process when iTunes is closed, but as soon as it starts the last.fm app opens right along with it. Perhaps it is using some kind of iTunes plugin that can start another process?
It seems to be fairly trivial to do this with a background process, but I'd like to do it without one so my program isn't using system resources.
One option with a background process is to use NSWorkspace's notification center, such as:
[[[NSWorkspace sharedWorkspace] notificationCenter] addObserver:self selector:#selector(appDidLaunch:) name:NSWorkspaceDidLaunchApplicationNotification object:nil]
However, this obviously requires a background process. Another option I found was to use ProcessNotif, something like this:
ProcessNotif *x = [[ProcessNotif new] autorelease];
[x setProcessName: #"iTunes"];
[x setTarget: self];
[x setAction: #selector(doStuff)];
[x start];
This is probably even less ideal than the NSWorkspace method, and it too requires a background process.
So, is there some way to launch from iTunes when it launches, no background process required?
Thanks!
The last.fm client achieves that by installing an iTunes plugin. This plugin gets loaded when iTunes starts and then has a chance to start the last.fm app. To create a plugin you need the iTunes PlugIn SDK available here.

Closing child windows in Cocoa when the main window is closed

I'm a Cocoa newbie so it is likely that my approach is wrong but ..
I have an app which opens several child windows (after the main/parent window has been loaded) using NSWindowController and initNibWIthName:. This works fine.
But when I close the parent window (using the red x) these remain open and prevent the app from closing until they are closed as well. This makes sense as I am not shutting them anywhere.
But how do I do this? There must be an event that is called at this point but I can't find what it is anywhere.
Notifications such as applicationWillTerminate (and so on) are only called when the application actually is terminating not when the close button has been pressed.
I guess I'm looking for something similar to the Windows WM_CLOSE type messages.
The closest equivalent you'll find is the NSWindowWillCloseNotification posted by the window prior to its closing. You can probably get the child windows to close themselves when the parent window closes using:
NSWindow *parentWindow;
NSArray *childWindows;
NSNotificationCenter *noteCenter = [NSNotificationCenter defaultCenter];
for (NSWindow *childWindow in childWindows) {
[noteCenter
addObserver:childWindow selector:#selector(close)
name:NSWindowWillCloseNotification object:parentWindow];
}
If the child window will be deallocated before its parent, be sure to unregister it for notifications before that happens.
The delegate method mentioned by Mark is a convenience method for the delegate that saves them the trouble of registering for a notification they'll likely want anyway. You don't need to create a window controller just to receive that message; simply sending the window [window setDelegate:myObject] will cause myObject to receive the -windowWillClose: message if it responds to the method.
By the way, what Cocoa calls "child windows" differs from what you're thinking of. They're not addressed in the Window Programming Guide, but if you look at the documentation for the related methods on NSWindow, you'll see that they basically track the movements of their parent window, so that they move with it.
If you're coming to Cocoa from Win32 programming, you might find Apple's Porting to Mac OS X from Windows Win32 API helpful to highlight conceptual differences between Win32 and Cocoa.
windowWillClose:
Apple developer docs NSWindowDelegate
Windows and applications are not the same thing in Mac OS X.
If you have a single-window interface, with a main window and no others except for About, Preferences, etc., then you should implement applicationShouldTerminateAfterLastWindowClosed: in your application delegate and return YES. This is the only way (aside from you doing it manually) that closing a window causes the application to quit.
If you have a multiple-window interface (as in a typical document-based application), then you should make all of those windows peers to one another. Windows such as Inspectors and tool palettes should be floating panels, not regular windows. And closing the last window should never, ever quit such an app.