#!/usr/bin/pythonw # -*- coding: utf-8 -*- from AppKit import NSApplication, NSBeep, NSSystemDefined, NSURL, NSWorkspace from Foundation import NSDistributedNotificationCenter, NSLog from PyObjCTools import AppHelper from Carbon.CarbonEvt import RegisterEventHotKey, GetApplicationEventTarget from SystemConfiguration import * from appscript import app import re import sets import action GROWL_APP_NAME = 'LocationDo' NOTIFICATION_LOCATION = 'Location' NOTIFICATION_ARRIVE = 'Arrive' NOTIFICATION_DEPART = 'Depart' NOTIFICATION_CURRENT = 'Current location' NOTIFICATION_UNKNOWN = 'Unknown location' NOTIFICATION_READY = 'Ready' NOTIFICATIONS_ALL = [NOTIFICATION_LOCATION, NOTIFICATION_ARRIVE, NOTIFICATION_DEPART, NOTIFICATION_CURRENT, NOTIFICATION_UNKNOWN, NOTIFICATION_READY] DEBUG = True kEventHotKeyPressedSubtype = 6 kEventHotKeyReleasedSubtype = 9 RE_AIRPORT_STATUS = '/'.join([kSCDynamicStoreDomainState, kSCCompNetwork, kSCCompInterface, kSCCompAnyRegex, kSCEntNetAirPort]) # XXX switch to Cocoa interface for Growl growl = app('GrowlHelperApp') growl.register( as_application=GROWL_APP_NAME, all_notifications=NOTIFICATIONS_ALL, default_notifications=NOTIFICATIONS_ALL) def growlNotify(name, title, description, **kw): params = dict(with_name=name, title=title, description=unicode(description), application_name=GROWL_APP_NAME, image_from_location='/System/Library/CoreServices/CoreTypes.bundle/Contents/Resources/GenericNetworkIcon.icns') params.update(kw) if not params.get('sticky', False): params['identifier'] = name if DEBUG: NSLog("%s: %s" % (title, description)) growl.notify(**params) class Location(sets.Set): def __str__(self): if not self: return '(unknown)' return ', '.join(self) currentLocation = Location() # XXX attach actions to location tag; something like: # locations['Siebel'].onArrive.ensureKerberosPrincipalsValid(...) def arriveAtLocation(location): growlNotify(NOTIFICATION_ARRIVE, 'Arriving at location', location) if 'Siebel' in location: action.ensureKerberosPrincipalsValid(['njriley@ACM.UIUC.EDU', 'njriley@AD.UIUC.EDU']) action.setVolumePercent(0.15) if '1115 SC' in location: action.openInBackground('~/Documents/CS 423/CS 423 to do.oo3') action.setDisplayBrightnessPercent(0.2) action.setAdiumStatus('Working in 1115 SC') if '1404 SC' in location: action.setDisplayBrightnessPercent(0.4) action.setVolumePercent(0) action.setAdiumStatus('In 1404 SC') if '4111 SC' in location: action.openInBackground('~/Documents/Research/Research progress.oo3') # XXX complain that NX has no creator nor bundle ID action.openApplicationWithName('NX Client for OSX') # XXX make these changes dependent on screen resolution action.moveWindow('iTunes', 'iTunes', (2123, 1534), ifSizeMatches=(129, 65)) action.moveWindow('Adium', 'Contacts', (1280, 1553)) action.moveWindow('Adium', 'AOL System Msg', (1448, 1320)) # XXX need better way to do this action.moveWindow('OmniOutliner Pro', 'Research progress', (1634, 847)) action.moveWindow('OmniOutliner Pro', '◇ Research progress', (1634, 847)) # XXX wait for yt to be accessible and Kerberos, then open the terminal window # action.terminalDo('kswitch -p njriley@ACM.UIUC.EDU;(sleep 10;kswitch -p njriley@AD.UIUC.EDU)&! ;clear;ssh yt') action.setDisplayBrightnessPercent(0.9) action.setAdiumStatus('Working in 4111 SC') if 'ACM office' in location: action.setDisplayBrightnessPercent(0.2) action.setVolumePercent(0.5) action.setAdiumStatus('At ACM') if 'Champaign' in location: action.ensureKerberosPrincipalsValid(['njriley@ACM.UIUC.EDU', 'njriley@AD.UIUC.EDU']) action.openInBackground('~/Documents/To do.oo3') action.setDisplayBrightnessPercent(0.2) action.setVolumePercent(0.5) action.setAdiumStatus('At home') if '13[12]0 DCL' in location: action.setDisplayBrightnessPercent(0.2) action.setVolumePercent(0) action.setAdiumStatus('In 13[12]0 DCL') if 'UIUCnet' in location: action.startVPNC('UIUCnet-oncampus') if 'CSL' in location: action.setDisplayBrightnessPercent(0.1) action.setVolumePercent(0) action.setAdiumStatus('At CSL') action.openURL('http://wireless.csl.uiuc.edu/authenticate.html') growlNotify(NOTIFICATION_ARRIVE, 'Arrived at location', location) def departLocation(location): growlNotify(NOTIFICATION_DEPART, 'Departing location', location) if 'UIUCnet' in location: # XXX should handle this more symmetrically action.stopVPNC() growlNotify(NOTIFICATION_DEPART, 'Departed location', location) def locationChanged(location): if not location: return # unknown global currentLocation oldLocation = currentLocation - location newLocation = location - currentLocation if DEBUG: NSLog("new %s | was %s | departing %s | arriving %s" % \ (location, currentLocation, oldLocation, newLocation)) if oldLocation: departLocation(oldLocation) currentLocation = location if newLocation: currentLocation = location arriveAtLocation(newLocation) def formatMACAddress(data): return ':'.join(re.findall('..', data.bytes()[:].encode('hex'))) # XXX watch battery status, too, for display brightness # XXX look at display configuration to tell difference between 4111 and 4124 # XXX need concept of ambiguity, ask user which location they're in (ACM/1115) airPortStatus = {'BSSID': None, 'SSID': None} class SCWatcher(NSObject): def keysChanged_inDynamicStore_(self, changed, store): location = Location() for key in changed: if re.match(RE_AIRPORT_STATUS, key): st = store.valueForKey_(key) bssid = formatMACAddress(st['BSSID']) if airPortStatus['BSSID'] != bssid: airPortStatus['BSSID'] = bssid if bssid in ['00:0e:83:05:77:22', '00:0e:83:05:77:20', '00:0e:83:05:75:d2']: location.add('4111 SC') elif bssid in ['00:0e:83:05:75:a2', '00:0e:83:05:75:a0']: location.add('1404 SC') elif bssid in ['00:0e:83:05:76:82', '00:0e:83:05:76:80']: location.add('ACM office') elif bssid == '00:11:24:0f:23:69': location.add('Champaign') elif bssid == '00:13:c4:ce:66:a0': location.add('13[12]0 DCL') elif bssid in ['00:0e:83:05:78:02', '00:0e:83:05:77:42']: location.add('1304 SC') elif bssid == '00:0e:83:05:78:a2': location.add('3124 SC') elif bssid == '00:0e:83:05:78:92': location.add('2405 SC') elif bssid == '00:0e:83:05:76:b2': location.add('Siebel 2nd floor atrium') elif bssid in ['00:14:1c:ad:5c:80', '00:14:1c:ad:64:50']: location.add('MEB') elif bssid == '00:02:2d:2c:cb:34': location.add('B02 CSL') elif bssid == '00:11:5c:fe:31:00': location.add('CAB') elif bssid == '00:13:c4:78:e1:a0': location.add('Engineering Hall') elif bssid in ['00:0c:30:d4:b7:2d', '00:0c:30:d4:b6:70']: location.add('Illini Union') elif bssid == '00:0d:93:ed:e0:bc': location.add('340 Marlborough St.') elif bssid == '00:40:96:5a:5e:0f': location.add('BMI baggage claim') elif bssid == '00:12:0e:10:1f:83': location.add('Waterville Valley') elif bssid == '44:44:44:44:44:44': location.add('No AirPort network') else: growlNotify(NOTIFICATION_UNKNOWN, 'Unknown BSSID', bssid, sticky=True) NSLog('Unknown BSSID %s' % bssid) ssid = st['SSID'] if airPortStatus['SSID'] != ssid: airPortStatus['SSID'] = ssid if ssid == '': pass elif ssid == 'dcs-wpa': location.add('Siebel') elif ssid == 'DCSnet': location.add('Siebel') location.add('UIUCnet') elif ssid == 'UIUCnet': location.add('UIUCnet') elif ssid == 'ACME Acres': location.add('Champaign') elif ssid == 'CSL Wireless': location.add('CSL') elif ssid == 'Wackyland': location.add('340 Marlborough St.') elif ssid == '05B408706826': location.add('Waterville Valley') elif ssid == 'CIRA': location.add('BMI') else: growlNotify(NOTIFICATION_UNKNOWN, 'Unknown SSID', ssid, sticky=True) locationChanged(location) class LocationDo(NSApplication): hotKeyRef = None def displayLocation(self): growlNotify(NOTIFICATION_CURRENT, 'Current location', currentLocation) def finishLaunching(self): super(LocationDo, self).finishLaunching() hotKeyRef = RegisterEventHotKey(98, 0, (0, 0), GetApplicationEventTarget(), 0) # F7 growlNotify(NOTIFICATION_READY, 'LocationDo ready', '') def sendEvent_(self, theEvent): if theEvent.type() == NSSystemDefined and \ theEvent.subtype() == kEventHotKeyPressedSubtype: self.displayLocation() super(LocationDo, self).sendEvent_(theEvent) store = None if __name__ == "__main__": store = UNSystemConfigurationDynamicStore.alloc().initWithName_('LocationDo') watcher = SCWatcher.alloc().init() store.setDelegate_(watcher) store.notifyValuesForKeys_matchingPatterns_(None, [RE_AIRPORT_STATUS]) store.addToCurrentRunLoop() AppHelper.runEventLoop()