ISFAController.m
/* |
File: ISFAController.m |
Abstract: Implements the main menu's controller. |
Opens a chat room in iChat when users click the "Joins Chat Room" button. |
Watches for application switch events and updates iChat status using the Scripting Bridge framework. |
Version: 1.2 |
Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple |
Inc. ("Apple") in consideration of your agreement to the following |
terms, and your use, installation, modification or redistribution of |
this Apple software constitutes acceptance of these terms. If you do |
not agree with these terms, please do not use, install, modify or |
redistribute this Apple software. |
In consideration of your agreement to abide by the following terms, and |
subject to these terms, Apple grants you a personal, non-exclusive |
license, under Apple's copyrights in this original Apple software (the |
"Apple Software"), to use, reproduce, modify and redistribute the Apple |
Software, with or without modifications, in source and/or binary forms; |
provided that if you redistribute the Apple Software in its entirety and |
without modifications, you must retain this notice and the following |
text and disclaimers in all such redistributions of the Apple Software. |
Neither the name, trademarks, service marks or logos of Apple Inc. may |
be used to endorse or promote products derived from the Apple Software |
without specific prior written permission from Apple. Except as |
expressly stated in this notice, no other rights or licenses, express or |
implied, are granted by Apple herein, including but not limited to any |
patent rights that may be infringed by your derivative works or by other |
works in which the Apple Software may be incorporated. |
The Apple Software is provided by Apple on an "AS IS" basis. APPLE |
MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION |
THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS |
FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND |
OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. |
IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL |
OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, |
MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED |
AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), |
STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE |
POSSIBILITY OF SUCH DAMAGE. |
Copyright (C) 2011 Apple Inc. All Rights Reserved. |
*/ |
#import "ISFAController.h" |
@interface ISFAController (PRIVATE) |
- (void)applicationSwitched; |
- (void)applicationLaunched:(NSNotification *)notification; |
- (void)applicationTerminated:(NSNotification *)notification; |
- (void)registerForAppSwitchNotificationFor:(NSDictionary *)application; |
@end |
@implementation ISFAController |
#pragma mark Watches for Application Switches |
/* |
Updates the current status and icon of users in iChat and "iChatStatus" window. |
Sets the user's status and icon to the fronmost application's name and icon. |
*/ |
- (void)applicationSwitched |
{ |
/* Get information about the current active application */ |
NSDictionary *applicationInfo = [[NSWorkspace sharedWorkspace] activeApplication]; |
/* Get the application's process id */ |
pid_t switchedPid = (pid_t)[[applicationInfo valueForKey:@"NSApplicationProcessIdentifier"] integerValue]; |
/* Do not do anything if we do not have new application in the front or if are in the front ourselves */ |
if(switchedPid != _currentPid && switchedPid != getpid()) { |
/* Only update iChat's status if it is running and the current status is set to available */ |
if([_iChatApp isRunning]) { |
if ([_iChatApp status] == iChatMyStatusAvailable) { |
/* Grab the icon of the running application as an NSImage */ |
NSImage *iconImage = [[NSWorkspace sharedWorkspace] iconForFile:[applicationInfo valueForKey:@"NSApplicationPath"]]; |
/* Set the buddy picture in iChat to the icon (using the bridged iChat application object) */ |
[_iChatApp setImage:iconImage]; |
/* Set the application's icon view in the "iChatStatus" window to the icon image */ |
[_appIconView setImage:iconImage]; |
NSString *statusString = [NSString stringWithFormat:@"Using %@", [applicationInfo objectForKey:@"NSApplicationName"]]; |
/* Set the status message in iChat to the running application (using the bridged iChat application object) */ |
[_iChatApp setStatusMessage:statusString]; |
/* Set the status message in the "iChatStatus" window */ |
[_appLabelField setStringValue:statusString]; |
} else { |
/* Current status is not set to available */ |
[_appIconView setImage:nil]; |
[_appLabelField setStringValue:@"Status is not set to available"]; |
} |
} else { |
/* iChat is not running */ |
[_appIconView setImage:nil]; |
[_appLabelField setStringValue:@"iChat is not running"]; |
} |
/* Store this application's process id so we can compare it with the process id of the next frontmost application */ |
_currentPid = switchedPid; |
} |
} |
/* |
Called when a new application was launched. Registers for its notifications when the |
application is activated. |
*/ |
- (void)applicationLaunched:(NSNotification *)notification |
{ |
/* A new application has launched. Make sure we get notifications when it activates. */ |
[self registerForAppSwitchNotificationFor:[notification userInfo]]; |
[self applicationSwitched]; |
} |
/* |
Called when an application was terminated. Stops watching for this application switch events. |
*/ |
- (void)applicationTerminated:(NSNotification *)notification |
{ |
/* Get the application's process id */ |
NSNumber *pidNumber = [[notification userInfo] valueForKey:@"NSApplicationProcessIdentifier"]; |
/* Get the observer associated to this application */ |
AXObserverRef observer = (AXObserverRef)[_observers objectForKey:pidNumber]; |
/* Check whether this observer is valid. If observer is valid, unregister for accessibility notifications |
and display a descriptive message otherwise. */ |
if(observer) { |
/* Stop listening to the accessibility notifications for the dead application */ |
CFRunLoopRemoveSource(CFRunLoopGetCurrent(), |
AXObserverGetRunLoopSource(observer), |
kCFRunLoopDefaultMode); |
[_observers removeObjectForKey:pidNumber]; |
} else { |
NSLog(@"Application \"%@\" that we didn't know about quit!", [[notification userInfo] valueForKey:@"NSApplicationName"]); |
} |
} |
/* |
Calls the applicationSwitched method. |
*/ |
static void applicationSwitched(AXObserverRef observer, AXUIElementRef element, CFStringRef notification, void *self) |
{ |
[(id)self applicationSwitched]; |
} |
/* |
Creates an accessibility observer to watch an application switch events. |
*/ |
- (void)registerForAppSwitchNotificationFor:(NSDictionary *)application |
{ |
NSNumber *pidNumber = [application valueForKey:@"NSApplicationProcessIdentifier"]; |
/* Don't sign up for our own switch events (that will fail). */ |
if([pidNumber intValue] != getpid()) { |
/* Check whether we are not already watching for this application's switch events */ |
if(![_observers objectForKey:pidNumber]) { |
pid_t pid = (pid_t)[pidNumber integerValue]; |
/* Create an Accessibility observer for the application */ |
AXObserverRef observer; |
if(AXObserverCreate(pid, applicationSwitched, &observer) == kAXErrorSuccess) { |
/* Register for the application activated notification */ |
CFRunLoopAddSource(CFRunLoopGetCurrent(), |
AXObserverGetRunLoopSource(observer), |
kCFRunLoopDefaultMode); |
AXUIElementRef element = AXUIElementCreateApplication(pid); |
if(AXObserverAddNotification(observer, element, kAXApplicationActivatedNotification, self) != kAXErrorSuccess) { |
NSLog(@"Failed to create observer for application \"%@\".", [application valueForKey:@"NSApplicationName"]); |
} else { |
/* Remember the observer so that we can unregister later */ |
[_observers setObject:(id)observer forKey:pidNumber]; |
} |
/* The observers dictionary wil hold on to the observer for us */ |
CFRelease(observer); |
/* We do not need the element any more */ |
CFRelease(element); |
} else { |
/* We could not create an observer to watch this application's switch events */ |
NSLog(@"Failed to create observer for application \"%@\".", [application valueForKey:@"NSApplicationName"]); |
} |
} else { |
/* We are already observing this application */ |
NSLog(@"Attempted to observe application \"%@\" twice.", [application valueForKey:@"NSApplicationName"]); |
} |
} |
} |
#pragma mark iChat |
/* |
Attempts to retrieve an instance of the iChat application and returns it |
if successful. |
*/ |
- (void)getiChatApp |
{ |
/* This will use the Scripting Bridge to get a reference to the main application class |
for iChat, as defined in the iChat.h header (see ReadMe.txt for more information). */ |
_iChatApp = (iChatApplication *)[[SBApplication applicationWithBundleIdentifier:@"com.apple.iChat"] retain]; |
if (!_iChatApp) |
NSLog(@"Unable to create an instance of SBApplication."); |
} |
/* |
Launches a chat room in iChat when users click the "Join Chat Room" button in the "iChat Status" window. |
Uses bridged objects to make iChat join (or create) an AIM chat room named @"ichatStatus". |
*/ |
- (IBAction)joinChatRoom:(id)sender |
{ |
/* Get the list of all active services in iChat */ |
SBElementArray *services = [_iChatApp services]; |
/* We're looking for any AIM service */ |
iChatService *aimService = nil; |
/* Iterate through the available services */ |
for (iChatService * svc in services) { |
/* Use the first connected AIM service we find */ |
if (svc.serviceType == iChatServiceTypeAIM && svc.status == iChatConnectionStatusConnected) { |
aimService = svc; |
break; |
} |
} |
/* Join the chat room if we found an aim service */ |
if (aimService) { |
/* Go to the chat room named "ichatStatus" on the AIM service we found */ |
NSDictionary *chatProperties = [NSDictionary dictionaryWithObjectsAndKeys:aimService, @"service", |
@"ichatStatus", @"name", nil]; |
/* Get the class for the text chat AppleScript class */ |
Class textChatClass = [_iChatApp classForScriptingClass:@"text chat"]; |
/* Create the text chat object */ |
iChatTextChat *myChat = [[textChatClass alloc] initWithProperties:chatProperties]; |
/* Add the text chat to the app's list of open chats */ |
[[_iChatApp textChats] addObject:myChat]; |
/* iChat will now send out the request to create chat room (not synchronous) */ |
/* Realeasing iChatTextChat here is not necessary and will result in a crash. |
This is because it was not actually allocated until it was used (by adding it |
to the SBElementArray) and was autoreleased afterwards. */ |
} |
} |
#pragma mark Initialization |
- (void)awakeFromNib |
{ |
/* Check if 'Enable access for assistive devices' is enabled. */ |
if(!AXAPIEnabled()) { |
/* |
'Enable access for assistive devices' is not enabled, so we will alert the user, |
then quit because we can't update the users status on app switch as we are meant to |
(because we can't get notifications of application switches). |
*/ |
NSRunCriticalAlertPanel(@"'Enable access for assistive devices' is not enabled.", @"iChatStatusFromApplication requires that 'Enable access for assistive devices' in the 'Universal Access' preferences panel be enabled in order to monitor application switching.", @"Quit", nil, nil); |
[NSApp terminate:self]; |
} |
[self getiChatApp]; |
NSWorkspace *workspace = [NSWorkspace sharedWorkspace]; |
_observers = [[NSMutableDictionary alloc] init]; |
/* Register for application launch notifications */ |
[[workspace notificationCenter] addObserver:self |
selector:@selector(applicationLaunched:) |
name:NSWorkspaceDidLaunchApplicationNotification |
object:workspace]; |
/* Register for application termination notifications */ |
[[workspace notificationCenter] addObserver:self |
selector:@selector(applicationTerminated:) |
name:NSWorkspaceDidTerminateApplicationNotification |
object:workspace]; |
/* Register for activation notifications for all currently running applications */ |
for(NSDictionary *application in [workspace launchedApplications]) { |
[self registerForAppSwitchNotificationFor:application]; |
} |
[self applicationSwitched]; |
} |
#pragma mark Deallocate Resources |
- (void)dealloc |
{ |
/* Stop listening to all the notifications */ |
[[[NSWorkspace sharedWorkspace] notificationCenter] removeObserver:self]; |
for(NSNumber *pidNumber in _observers) { |
AXObserverRef observer = (AXObserverRef)[_observers objectForKey:pidNumber]; |
CFRunLoopRemoveSource(CFRunLoopGetCurrent(), AXObserverGetRunLoopSource(observer),kCFRunLoopDefaultMode); |
} |
/* This will also release the observers in the dictionary */ |
[_observers release]; |
[super dealloc]; |
} |
@end |
Copyright © 2011 Apple Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2011-08-25