* Adium is the legal property of its developers, whose names are listed in the copyright file included * with this source distribution. * This program is free software; you can redistribute it and/or modify it under the terms of the GNU * General Public License as published by the Free Software Foundation; either version 2 of the License, * or (at your option) any later version. * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General * Public License for more details. * You should have received a copy of the GNU General Public License along with this program; if not, * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #import "AIAutomaticStatus.h" #import <Adium/AIAccountControllerProtocol.h> #import <Adium/AIStatusControllerProtocol.h> #import <Adium/AIInterfaceControllerProtocol.h> #import <Adium/AIChatControllerProtocol.h> #import <Adium/ESTextAndButtonsWindowController.h> #import <Adium/AIAccount.h> #import <Adium/AIStatus.h> #import <Adium/AIStatusGroup.h> AIAwayScreenSaved = (1 << 2), AIAwayScreenLocked = (1 << 3), AIAwayFastUserSwitched = (1 << 4) @interface AIAutomaticStatus () - (void)notificationHandler:(NSNotification *)notification; - (void)triggerAutoAwayWithStatusID:(NSNumber *)statusID; - (void)returnFromAutoAway; * @class AIAutomaticStatus * Automatically set accounts to certain statuses when events occur. Currently this handles: * - Screen(saver|lock) activation @implementation AIAutomaticStatus * @brief Initialize the automatic status system // Ensure no idle time is set as we load [adium.preferenceController setPreference:nil group:GROUP_ACCOUNT_STATUS]; // Initialize our state information accountsToReconnect = [[NSMutableSet alloc] init]; previousStatus = [[NSMutableDictionary alloc] init]; // Register our notifications NSNotificationCenter *notificationCenter; // FUS events are on the sharedWorkspace's notificationCenter notificationCenter = [[NSWorkspace sharedWorkspace] notificationCenter]; [notificationCenter addObserver:self selector:@selector(notificationHandler:) name:NSWorkspaceSessionDidBecomeActiveNotification [notificationCenter addObserver:self selector:@selector(notificationHandler:) name:NSWorkspaceSessionDidResignActiveNotification // Screensaver events are distributed notification events notificationCenter = [NSDistributedNotificationCenter defaultCenter]; [notificationCenter addObserver:self selector:@selector(notificationHandler:) name:AIScreensaverDidStartNotification [notificationCenter addObserver:self selector:@selector(notificationHandler:) name:AIScreensaverDidStopNotification [notificationCenter addObserver:self selector:@selector(notificationHandler:) name:AIScreenLockDidStartNotification [notificationCenter addObserver:self selector:@selector(notificationHandler:) name:AIScreenLockDidStopNotification // Idle events are in the Adium notification center, posted by the AdiumIdleManager notificationCenter = [NSNotificationCenter defaultCenter]; [notificationCenter addObserver:self selector:@selector(notificationHandler:) name:AIMachineIdleUpdateNotification [notificationCenter addObserver:self selector:@selector(notificationHandler:) name:AIMachineIsActiveNotification // Register for status preference updates [adium.preferenceController registerPreferenceObserver:self forGroup:PREF_GROUP_STATUS_PREFERENCES]; * @brief Uninstall plugin * When the plugin is uninstalled, we revert to whatever status we were previously set to. // Unregister our notifications [[[NSWorkspace sharedWorkspace] notificationCenter] removeObserver:self]; [[NSDistributedNotificationCenter defaultCenter] removeObserver:self]; [[NSNotificationCenter defaultCenter] removeObserver:self]; // Unregister our preference observations [adium.preferenceController unregisterPreferenceObserver:self]; // Revert to our stored statuses if (automaticStatusBitMap != 0) { [self returnFromAutoAway]; [accountsToReconnect release]; [previousStatus release]; [fastUserSwitchID release]; * @brief Preferences changed * Note the status IDs, interval information, and enabled information for our preferences - (void)preferencesChangedForGroup:(NSString *)group object:(AIListObject *)object preferenceDict:(NSDictionary *)prefDict firstTime:(BOOL)firstTime reportIdleEnabled = [[prefDict objectForKey:KEY_STATUS_REPORT_IDLE] boolValue]; idleReportInterval = [[prefDict objectForKey:KEY_STATUS_REPORT_IDLE_INTERVAL] doubleValue]; idleStatusID = [[prefDict objectForKey:KEY_STATUS_AUTO_AWAY_STATUS_STATE_ID] retain]; idleStatusEnabled = [[prefDict objectForKey:KEY_STATUS_AUTO_AWAY] boolValue]; idleStatusInterval = [[prefDict objectForKey:KEY_STATUS_AUTO_AWAY_INTERVAL] doubleValue]; [fastUserSwitchID release]; fastUserSwitchID = [[prefDict objectForKey:KEY_STATUS_FUS_STATUS_STATE_ID] retain]; fastUserSwitchEnabled = [[prefDict objectForKey:KEY_STATUS_FUS] boolValue]; screenSaverID = [[prefDict objectForKey:KEY_STATUS_SS_STATUS_STATE_ID] retain]; screenSaverEnabled = [[prefDict objectForKey:KEY_STATUS_SS] boolValue]; * @brief Handle a notification * @param notification The notification to process * When a notification comes in, this checks if it's a start or end event * If this event changes one of the automatic statusses, the user is set again * to the highest priority automatic away status, or if none, returns from autoaway. * Priorities: Fast User Switch'ed > Screen(saver|lock) > Idle. - (void)notificationHandler:(NSNotification *)notification NSString *notificationName = [notification name]; unsigned oldBitMap = automaticStatusBitMap; if ([notificationName isEqualToString:NSWorkspaceSessionDidResignActiveNotification]) { AILogWithSignature(@"Fast user switch (start) detected"); if (fastUserSwitchEnabled) automaticStatusBitMap |= AIAwayFastUserSwitched; } else if ([notificationName isEqualToString:AIScreensaverDidStartNotification]) { AILogWithSignature(@"Screensaver (start) detected."); if (screenSaverEnabled) automaticStatusBitMap |= AIAwayScreenSaved; } else if ([notificationName isEqualToString:AIMachineIdleUpdateNotification]) { double duration = [[[notification userInfo] objectForKey:@"Duration"] doubleValue]; if (reportIdleEnabled && duration >= idleReportInterval) { NSDate *idleSince = [[notification userInfo] objectForKey:@"idleSince"]; if ((NSInteger)[[adium.preferenceController preferenceForKey:@"idleSince" group:GROUP_ACCOUNT_STATUS] timeIntervalSince1970] != (NSInteger)[idleSince timeIntervalSince1970]) { AILogWithSignature(@"Idle (start) detected. %@ -> %@", [adium.preferenceController preferenceForKey:@"idleSince" group:GROUP_ACCOUNT_STATUS], idleSince); [adium.preferenceController setPreference:[[notification userInfo] objectForKey:@"idleSince"] group:GROUP_ACCOUNT_STATUS]; if (idleStatusEnabled && duration >= idleStatusInterval && !(automaticStatusBitMap & AIAwayIdle)) { AILogWithSignature(@"Auto-away (start) detected."); automaticStatusBitMap |= AIAwayIdle; } if ([notificationName isEqualToString:AIScreenLockDidStartNotification]) { AILogWithSignature(@"Screenlock (start) detected."); if (screenSaverEnabled) automaticStatusBitMap |= AIAwayScreenLocked; if ([notificationName isEqualToString:NSWorkspaceSessionDidBecomeActiveNotification]) { AILogWithSignature(@"Fast user switch (end) detected."); automaticStatusBitMap &= ~AIAwayFastUserSwitched; } else if ([notificationName isEqualToString:AIScreensaverDidStopNotification]) { AILogWithSignature(@"Screensaver (end) detected."); automaticStatusBitMap &= ~AIAwayScreenSaved; } else if ([notificationName isEqualToString:AIMachineIsActiveNotification]) { if (automaticStatusBitMap & AIAwayIdle) { AILogWithSignature(@"Auto-away (end) detected."); automaticStatusBitMap &= ~AIAwayIdle; AILogWithSignature(@"Idle (end) detected."); [adium.preferenceController setPreference:nil group:GROUP_ACCOUNT_STATUS]; } else if ([notificationName isEqualToString:AIScreenLockDidStopNotification]) { AILogWithSignature(@"Screenlock (end) detected."); automaticStatusBitMap &= ~AIAwayScreenLocked; // Check if a change in status is required: if so, look for the one with the highest priority if (oldBitMap != automaticStatusBitMap) { NSNumber *statusID = nil; if (automaticStatusBitMap & AIAwayFastUserSwitched) statusID = fastUserSwitchID; else if ((automaticStatusBitMap & AIAwayScreenLocked) || (automaticStatusBitMap & AIAwayScreenSaved)) statusID = screenSaverID; else if (automaticStatusBitMap & AIAwayIdle) [self returnFromAutoAway]; [self triggerAutoAwayWithStatusID:statusID]; * @brief Automatically set an account as away * @param statusID The status ID to change account status to * Sets all available accounts to the status type statusID, while storing the old one if necessary - (void)triggerAutoAwayWithStatusID:(NSNumber *)statusID AIStatusItem *targetStatusState = [adium.statusController statusStateWithUniqueStatusID:statusID]; // Grab any group member if possible if ([targetStatusState isKindOfClass:[AIStatusGroup class]]) { targetStatusState = [(AIStatusGroup *)targetStatusState anyContainedStatus]; // If we weren't given a valid and new state, fail. if (!targetStatusState || [oldStatusID isEqualToNumber:statusID]) { for (AIAccount *account in adium.accountController.accounts) { AIStatus *currentStatusState = account.statusState; // Store the state of the account if there is no previous one saved if (![previousStatus objectForKey:[account internalObjectID]]) { // Don't modify or store the status of (originally!) non-available accounts if (currentStatusState.statusType != AIAvailableStatusType) { [previousStatus setObject:currentStatusState forKey:[account internalObjectID]]; AILogWithSignature(@"Setting %@ to status %@", account, targetStatusState); // Set the account's status to our new value [account setStatusState:(AIStatus *)targetStatusState]; // If this status brought the account offline, add it to the list to reconnect. if (targetStatusState.statusType == AIOfflineStatusType) { [accountsToReconnect addObject:account]; [account setStatusStateAndRemainOffline:(AIStatus *)targetStatusState]; oldStatusID = [statusID retain]; * @brief Return from automatic away * Returns all accounts with stored status information to their previous status. - (void)returnFromAutoAway for (AIAccount *account in adium.accountController.accounts) { AIStatus *previousStatusState = [previousStatus objectForKey:[account internalObjectID]]; // Skip accounts without stored information. if (!previousStatusState) { AILogWithSignature(@"Returning %@ to status %@", account, previousStatusState); if (account.online || [accountsToReconnect containsObject:account]) { //If online or needs to be reconnected, set the previous state, going online if necessary [account setStatusState:previousStatusState]; //If offline, set the state without coming online [account setStatusStateAndRemainOffline:previousStatusState]; [accountsToReconnect removeAllObjects]; [previousStatus removeAllObjects]; automaticStatusBitMap = 0;