1 | #!/usr/bin/pythonw |
---|
2 | # -*- coding: utf-8 -*- |
---|
3 | |
---|
4 | from AppKit import NSApplication, NSBeep, NSSystemDefined, NSURL, NSWorkspace |
---|
5 | from Foundation import NSDistributedNotificationCenter, NSLog |
---|
6 | from PyObjCTools import AppHelper |
---|
7 | from Carbon.CarbonEvt import RegisterEventHotKey, GetApplicationEventTarget |
---|
8 | from SystemConfiguration import * |
---|
9 | from appscript import app |
---|
10 | import re |
---|
11 | import sets |
---|
12 | |
---|
13 | import action |
---|
14 | |
---|
15 | GROWL_APP_NAME = 'LocationDo' |
---|
16 | NOTIFICATION_LOCATION = 'Location' |
---|
17 | NOTIFICATION_ARRIVE = 'Arrive' |
---|
18 | NOTIFICATION_DEPART = 'Depart' |
---|
19 | NOTIFICATION_CURRENT = 'Current location' |
---|
20 | NOTIFICATION_UNKNOWN = 'Unknown location' |
---|
21 | NOTIFICATION_READY = 'Ready' |
---|
22 | NOTIFICATIONS_ALL = [NOTIFICATION_LOCATION, NOTIFICATION_ARRIVE, |
---|
23 | NOTIFICATION_DEPART, NOTIFICATION_CURRENT, |
---|
24 | NOTIFICATION_UNKNOWN, NOTIFICATION_READY] |
---|
25 | |
---|
26 | DEBUG = True |
---|
27 | |
---|
28 | kEventHotKeyPressedSubtype = 6 |
---|
29 | kEventHotKeyReleasedSubtype = 9 |
---|
30 | |
---|
31 | RE_AIRPORT_STATUS = '/'.join([kSCDynamicStoreDomainState, kSCCompNetwork, kSCCompInterface, kSCCompAnyRegex, kSCEntNetAirPort]) |
---|
32 | |
---|
33 | # XXX switch to Cocoa interface for Growl |
---|
34 | growl = app('GrowlHelperApp') |
---|
35 | |
---|
36 | growl.register( |
---|
37 | as_application=GROWL_APP_NAME, |
---|
38 | all_notifications=NOTIFICATIONS_ALL, |
---|
39 | default_notifications=NOTIFICATIONS_ALL) |
---|
40 | |
---|
41 | def growlNotify(name, title, description, **kw): |
---|
42 | params = dict(with_name=name, |
---|
43 | title=title, |
---|
44 | description=unicode(description), |
---|
45 | application_name=GROWL_APP_NAME, |
---|
46 | image_from_location='/System/Library/CoreServices/CoreTypes.bundle/Contents/Resources/GenericNetworkIcon.icns') |
---|
47 | params.update(kw) |
---|
48 | if not params.get('sticky', False): |
---|
49 | params['identifier'] = name |
---|
50 | if DEBUG: |
---|
51 | NSLog("%s: %s" % (title, description)) |
---|
52 | growl.notify(**params) |
---|
53 | |
---|
54 | class Location(sets.Set): |
---|
55 | def __str__(self): |
---|
56 | if not self: return '(unknown)' |
---|
57 | return ', '.join(self) |
---|
58 | |
---|
59 | currentLocation = Location() |
---|
60 | |
---|
61 | # XXX attach actions to location tag; something like: |
---|
62 | # locations['Siebel'].onArrive.ensureKerberosPrincipalsValid(...) |
---|
63 | |
---|
64 | def arriveAtLocation(location): |
---|
65 | growlNotify(NOTIFICATION_ARRIVE, 'Arriving at location', location) |
---|
66 | if 'Siebel' in location: |
---|
67 | action.ensureKerberosPrincipalsValid(['njriley@ACM.UIUC.EDU', |
---|
68 | 'njriley@AD.UIUC.EDU']) |
---|
69 | action.setVolumePercent(0.15) |
---|
70 | if '1115 SC' in location: |
---|
71 | action.openInBackground('~/Documents/CS 423/CS 423 to do.oo3') |
---|
72 | action.setDisplayBrightnessPercent(0.2) |
---|
73 | action.setAdiumStatus('Working in 1115 SC') |
---|
74 | if '1404 SC' in location: |
---|
75 | action.setDisplayBrightnessPercent(0.4) |
---|
76 | action.setVolumePercent(0) |
---|
77 | action.setAdiumStatus('In 1404 SC') |
---|
78 | if '4111 SC' in location: |
---|
79 | action.openInBackground('~/Documents/Research/Research progress.oo3') |
---|
80 | # XXX wait for yt to be accessible |
---|
81 | # XXX start NX |
---|
82 | # XXX move windows |
---|
83 | action.terminalDo('kswitch -p njriley@ACM.UIUC.EDU;(sleep 10;kswitch -p njriley@AD.UIUC.EDU)&! ;clear;ssh yt') |
---|
84 | action.setDisplayBrightnessPercent(0.9) |
---|
85 | action.setAdiumStatus('Working in 4111 SC') |
---|
86 | if 'ACM office' in location: |
---|
87 | action.setDisplayBrightnessPercent(0.2) |
---|
88 | action.setVolumePercent(0.5) |
---|
89 | action.setAdiumStatus('At ACM') |
---|
90 | if 'Champaign' in location: |
---|
91 | action.openInBackground('~/Documents/To do.oo3') |
---|
92 | action.setDisplayBrightnessPercent(0.2) |
---|
93 | action.setVolumePercent(0.5) |
---|
94 | action.setAdiumStatus('At home') |
---|
95 | if '13[12]0 DCL' in location: |
---|
96 | action.setDisplayBrightnessPercent(0.2) |
---|
97 | action.setVolumePercent(0) |
---|
98 | action.setAdiumStatus('In 13[12]0 DCL') |
---|
99 | if 'UIUCnet' in location: |
---|
100 | action.startVPNC('UIUCnet-oncampus') |
---|
101 | if 'CSL' in location: |
---|
102 | action.setDisplayBrightnessPercent(0.1) |
---|
103 | action.setVolumePercent(0) |
---|
104 | action.setAdiumStatus('At CSL') |
---|
105 | action.openURL('http://wireless.csl.uiuc.edu/authenticate.html') |
---|
106 | growlNotify(NOTIFICATION_ARRIVE, 'Arrived at location', |
---|
107 | location) |
---|
108 | |
---|
109 | def departLocation(location): |
---|
110 | growlNotify(NOTIFICATION_DEPART, 'Departing location', location) |
---|
111 | if 'UIUCnet' in location: |
---|
112 | # XXX should handle this more symmetrically |
---|
113 | action.stopVPNC() |
---|
114 | growlNotify(NOTIFICATION_DEPART, 'Departed location', location) |
---|
115 | |
---|
116 | def locationChanged(location): |
---|
117 | if not location: return # unknown |
---|
118 | global currentLocation |
---|
119 | oldLocation = currentLocation - location |
---|
120 | newLocation = location - currentLocation |
---|
121 | if DEBUG: |
---|
122 | NSLog("new %s | was %s | departing %s | arriving %s" % \ |
---|
123 | (location, currentLocation, oldLocation, newLocation)) |
---|
124 | if oldLocation: |
---|
125 | departLocation(oldLocation) |
---|
126 | currentLocation = location |
---|
127 | if newLocation: |
---|
128 | currentLocation = location |
---|
129 | arriveAtLocation(newLocation) |
---|
130 | |
---|
131 | def formatMACAddress(data): |
---|
132 | return ':'.join(re.findall('..', data.bytes()[:].encode('hex'))) |
---|
133 | |
---|
134 | # XXX watch battery status, too, for display brightness |
---|
135 | # XXX look at display configuration to tell difference between 4111 and 4124 |
---|
136 | # XXX need concept of ambiguity, ask user which location they're in (ACM/1115) |
---|
137 | |
---|
138 | airPortStatus = {'BSSID': None, 'SSID': None} |
---|
139 | |
---|
140 | class SCWatcher(NSObject): |
---|
141 | def keysChanged_inDynamicStore_(self, changed, store): |
---|
142 | location = Location() |
---|
143 | for key in changed: |
---|
144 | if re.match(RE_AIRPORT_STATUS, key): |
---|
145 | st = store.valueForKey_(key) |
---|
146 | bssid = formatMACAddress(st['BSSID']) |
---|
147 | if airPortStatus['BSSID'] != bssid: |
---|
148 | airPortStatus['BSSID'] = bssid |
---|
149 | if bssid in ['00:0e:83:05:77:22', '00:0e:83:05:77:20', |
---|
150 | '00:0e:83:05:75:d2']: |
---|
151 | location.add('4111 SC') |
---|
152 | elif bssid in ['00:0e:83:05:75:a2', '00:0e:83:05:75:a0']: |
---|
153 | location.add('1404 SC') |
---|
154 | elif bssid in ['00:0e:83:05:76:82', '00:0e:83:05:76:80']: |
---|
155 | location.add('ACM office') |
---|
156 | elif bssid == '00:11:24:0f:23:69': |
---|
157 | location.add('Champaign') |
---|
158 | elif bssid == '00:13:c4:ce:66:a0': |
---|
159 | location.add('13[12]0 DCL') |
---|
160 | elif bssid in ['00:14:1c:ad:5c:80', '00:14:1c:ad:64:50']: |
---|
161 | location.add('MEB') |
---|
162 | elif bssid == '00:02:2d:2c:cb:34': |
---|
163 | location.add('B02 CSL') |
---|
164 | elif bssid == '00:13:c4:78:e1:a0': |
---|
165 | location.add('Engineering Hall') |
---|
166 | elif bssid == '00:0d:93:ed:e0:bc': |
---|
167 | location.add('340 Marlborough St.') |
---|
168 | elif bssid == '00:40:96:5a:5e:0f': |
---|
169 | location.add('BMI baggage claim') |
---|
170 | elif bssid == '44:44:44:44:44:44': |
---|
171 | location.add('No AirPort network') |
---|
172 | else: |
---|
173 | growlNotify(NOTIFICATION_UNKNOWN, 'Unknown BSSID', bssid, |
---|
174 | sticky=True) |
---|
175 | NSLog('Unknown BSSID %s' % bssid) |
---|
176 | ssid = st['SSID'] |
---|
177 | if airPortStatus['SSID'] != ssid: |
---|
178 | airPortStatus['SSID'] = ssid |
---|
179 | if ssid == '': |
---|
180 | pass |
---|
181 | elif ssid == 'dcs-wpa': |
---|
182 | location.add('Siebel') |
---|
183 | elif ssid == 'DCSnet': |
---|
184 | location.add('Siebel') |
---|
185 | location.add('UIUCnet') |
---|
186 | elif ssid == 'UIUCnet': |
---|
187 | location.add('UIUCnet') |
---|
188 | elif ssid == 'ACME Acres': |
---|
189 | location.add('Champaign') |
---|
190 | elif ssid == 'CSL Wireless': |
---|
191 | location.add('CSL') |
---|
192 | elif ssid == 'Wackyland': |
---|
193 | location.add('340 Marlborough St.') |
---|
194 | elif ssid == 'CIRA': |
---|
195 | location.add('BMI') |
---|
196 | else: |
---|
197 | growlNotify(NOTIFICATION_UNKNOWN, 'Unknown SSID', ssid, |
---|
198 | sticky=True) |
---|
199 | locationChanged(location) |
---|
200 | |
---|
201 | class LocationDo(NSApplication): |
---|
202 | |
---|
203 | hotKeyRef = None |
---|
204 | |
---|
205 | def displayLocation(self): |
---|
206 | growlNotify(NOTIFICATION_CURRENT, 'Current location', currentLocation) |
---|
207 | |
---|
208 | def finishLaunching(self): |
---|
209 | super(LocationDo, self).finishLaunching() |
---|
210 | hotKeyRef = RegisterEventHotKey(98, 0, (0, 0), |
---|
211 | GetApplicationEventTarget(), 0) # F7 |
---|
212 | growlNotify(NOTIFICATION_READY, 'LocationDo ready', '') |
---|
213 | |
---|
214 | def sendEvent_(self, theEvent): |
---|
215 | if theEvent.type() == NSSystemDefined and \ |
---|
216 | theEvent.subtype() == kEventHotKeyPressedSubtype: |
---|
217 | self.displayLocation() |
---|
218 | super(LocationDo, self).sendEvent_(theEvent) |
---|
219 | |
---|
220 | store = None |
---|
221 | |
---|
222 | if __name__ == "__main__": |
---|
223 | store = UNSystemConfigurationDynamicStore.alloc().initWithName_('LocationDo') |
---|
224 | watcher = SCWatcher.alloc().init() |
---|
225 | store.setDelegate_(watcher) |
---|
226 | store.notifyValuesForKeys_matchingPatterns_(None, [RE_AIRPORT_STATUS]) |
---|
227 | store.addToCurrentRunLoop() |
---|
228 | AppHelper.runEventLoop() |
---|