Index: trunk/LocationDo/English.lproj/MainMenu.nib/classes.nib
===================================================================
--- trunk/LocationDo/English.lproj/MainMenu.nib/classes.nib (revision 196)
+++ trunk/LocationDo/English.lproj/MainMenu.nib/classes.nib (revision 196)
@@ -0,0 +1,4 @@
+{
+ IBClasses = ({CLASS = FirstResponder; LANGUAGE = ObjC; SUPERCLASS = NSObject; });
+ IBVersion = 1;
+}
Index: trunk/LocationDo/English.lproj/MainMenu.nib/info.nib
===================================================================
--- trunk/LocationDo/English.lproj/MainMenu.nib/info.nib (revision 196)
+++ trunk/LocationDo/English.lproj/MainMenu.nib/info.nib (revision 196)
@@ -0,0 +1,12 @@
+
+
+
+
+ IBDocumentLocation
+ 69 10 356 240 0 0 1024 746
+ IBFramework Version
+ 439.0
+ IBSystem Version
+ 8B15
+
+
Index: trunk/LocationDo/LocationDo.py
===================================================================
--- trunk/LocationDo/LocationDo.py (revision 196)
+++ trunk/LocationDo/LocationDo.py (revision 196)
@@ -0,0 +1,201 @@
+#!/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])
+
+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()
+
+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 '4111 SC' in location:
+ action.openInBackground('~/Documents/MSSP/MSSP progress.oo3')
+ action.setDisplayBrightnessPercent(0.9)
+ action.setAdiumStatus('Working in 4111 SC')
+ if 'ACM office' in location:
+ action.setDisplayBrightnessPercent(0.2)
+ action.setAdiumStatus('At ACM')
+ if 'Champaign' in location:
+ action.openInBackground('~/Documents/To do.oo3')
+ action.setDisplayBrightnessPercent(0.2)
+ action.setVolumePercent(0.5)
+ action.setAdiumStatus('At home')
+ if '1320 DCL' in location:
+ action.setDisplayBrightnessPercent(0.2)
+ action.setVolumePercent(0)
+ action.setAdiumStatus('In 1320 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
+
+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: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('1320 DCL')
+ 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 == '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')
+ 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()
Index: trunk/LocationDo/SCNetworkReachabilitymodule.c
===================================================================
--- trunk/LocationDo/SCNetworkReachabilitymodule.c (revision 196)
+++ trunk/LocationDo/SCNetworkReachabilitymodule.c (revision 196)
@@ -0,0 +1,83 @@
+#include "Python.h"
+
+#include
+
+#define SCNR_REACHABLE "SCNetworkIsReachable"
+static CFStringRef SCNR_Reachable = CFSTR(SCNR_REACHABLE);
+static SCNetworkReachabilityContext SCNR_Context = {0, NULL, NULL, NULL, NULL};
+
+static void
+SCNR_callback(SCNetworkReachabilityRef target,
+ SCNetworkConnectionFlags flags, void *tagString);
+
+static Boolean
+SCNR_reachable(SCNetworkConnectionFlags flags) {
+ /* not reachable if it requires a non-automatic connection */
+ return ((flags & kSCNetworkFlagsReachable) &&
+ (!(flags & kSCNetworkFlagsConnectionRequired) ||
+ (flags & kSCNetworkFlagsConnectionAutomatic)));
+}
+
+static void
+SCNR_schedule(CFStringRef tagString,
+ SCNetworkReachabilityRef target) {
+ SCNR_Context.info = (void *)tagString;
+ SCNetworkReachabilitySetCallback(target, SCNR_callback, &SCNR_Context);
+ SCNetworkReachabilityScheduleWithRunLoop(target,
+ CFRunLoopGetCurrent(),
+ kCFRunLoopDefaultMode);
+}
+
+static void
+SCNR_callback(SCNetworkReachabilityRef target,
+ SCNetworkConnectionFlags flags, void *tagString) {
+ if (SCNR_reachable(flags)) {
+ CFNotificationCenterPostNotification(CFNotificationCenterGetLocalCenter(),
+ SCNR_Reachable, tagString, NULL, false);
+ CFRelease((CFStringRef)tagString);
+ } else {
+ SCNR_schedule(tagString, target);
+ }
+}
+
+static PyObject *
+SCNR_notify_when_reachable(PyObject *self, PyObject *args) {
+ const char *hostname;
+ const char *tag;
+ if (!PyArg_ParseTuple(args, "ss", &hostname, &tag))
+ return NULL;
+
+ SCNetworkConnectionFlags flags;
+ if (SCNetworkCheckReachabilityByName(hostname, &flags) &&
+ SCNR_reachable(flags))
+ return PyBool_FromLong(0);
+
+ SCNetworkReachabilityRef target =
+ SCNetworkReachabilityCreateWithName(NULL, hostname);
+ CFStringRef tagString =
+ CFStringCreateWithCString(NULL, tag, kCFStringEncodingUTF8);
+
+ SCNR_schedule(tagString, target);
+
+ return PyBool_FromLong(1);
+}
+
+
+static PyMethodDef SCNetworkReachabilitymodule_methods[] = {
+ {"notify_when_reachable", SCNR_notify_when_reachable, METH_VARARGS,
+ "notify_when_reachable(hostname, tag) -> bool\n\n"
+ "Deliver the SCNetworkIsReachable runloop notification with object tag\n"
+ "when the host becomes reachable, or return false if it's currently\n"
+ "reachable."},
+ {NULL, NULL, 0, NULL}
+};
+
+PyMODINIT_FUNC
+initSCNetworkReachability(void) {
+ PyObject *module = Py_InitModule("SCNetworkReachability",
+ SCNetworkReachabilitymodule_methods);
+ PyObject *dict = PyModule_GetDict(module);
+ PyObject *reachable = PyString_FromString(SCNR_REACHABLE);
+ PyDict_SetItemString(dict, SCNR_REACHABLE, reachable);
+ Py_DECREF(reachable);
+}
Index: trunk/LocationDo/action.py
===================================================================
--- trunk/LocationDo/action.py (revision 196)
+++ trunk/LocationDo/action.py (revision 196)
@@ -0,0 +1,83 @@
+from Authorization import Authorization, kAuthorizationFlagDestroyRights
+from Foundation import NSBundle, NSNotificationCenter, NSObject, NSURL
+from AppKit import NSWorkspace
+from appscript import app, k, its
+import os, osax, subprocess, tempfile, SCNetworkReachability
+
+appBundle = NSBundle.mainBundle()
+
+def ensureKerberosPrincipalsValid(principals):
+ kerberosApp = app(id='edu.mit.Kerberos.KerberosApp')
+ validPrincipals = kerberosApp.caches.filter(its.time_remaining != 'Expired').principal.get()
+ for principal in principals:
+ if principal not in validPrincipals:
+ # XXX make these async
+ kerberosApp.renew_tickets(for_principal=principal)
+ # kerberosApp.get_tickets(for_principal=principal)
+
+def setVolumePercent(percent):
+ # XXX should use CoreAudio: see Pester/Source/NJRSoundManager.m
+ osax.setvolume(int(7 * percent))
+
+def _openURL(url):
+ ws = NSWorkspace.sharedWorkspace()
+ url = NSURL.URLWithString_(url)
+ return ws.openURL_(url)
+
+class _LDURLWatcher(NSObject):
+ def URLIsReachable_(self, notification):
+ _openURL(notification.object())
+
+_urlWatcher = _LDURLWatcher.alloc().init()
+NSNotificationCenter.defaultCenter().addObserver_selector_name_object_(
+ _urlWatcher, 'URLIsReachable:', SCNetworkReachability.SCNetworkIsReachable,
+ None)
+
+def openURL(url):
+ hostname = NSURL.URLWithString_(url).host()
+ if not SCNetworkReachability.notify_when_reachable(hostname, url):
+ _openURL(url)
+
+def openInBackground(path):
+ ws = NSWorkspace.sharedWorkspace()
+ path = os.path.expanduser(path)
+ return ws.openFile_withApplication_andDeactivate_(path, None, False)
+
+def setDisplayBrightnessPercent(percent):
+ # XXX create brightness module
+ pathToBrightness = appBundle.pathForResource_ofType_('brightness', None)
+ subprocess.call([pathToBrightness, str(percent)], stderr=subprocess.STDOUT,
+ stdout=subprocess.PIPE)
+
+def setAdiumStatus(status):
+ # XXX doesn't match preset status if available
+ adiumApp = app(id='com.adiumX.adiumX')
+ if adiumApp.my_status_type() == k.offline:
+ adiumApp.my_status.status_message_string.set(status)
+ else:
+ adiumApp.my_status_message.set(status)
+ adiumApp.my_status_type.set(k.available)
+
+def terminalDo(command):
+ terminalApp = app(id='com.apple.Terminal')
+ terminalApp.do_script(command + '; exit')
+
+_auth = None
+
+def authorizationDo(command, *args):
+ global _auth
+ if not _auth:
+ _auth = Authorization(destroyflags=(kAuthorizationFlagDestroyRights,))
+ return _auth.executeWithPrivileges(command, *args)
+
+def stopVPNC():
+ authorizationDo('/usr/bin/killall', 'vpnc')
+
+def startVPNC(vpncProfile=None):
+ stopVPNC()
+ args = ['--kernel-ipsec']
+ if vpncProfile:
+ args.append('/etc/vpnc/%s.conf' % vpncProfile)
+ # XXX get password from keychain, then use:
+ # authorizationDo('/usr/local/sbin/vpnc', *args)
+ terminalDo('sudo /usr/local/sbin/vpnc %s' % ' '.join(args))
Index: trunk/LocationDo/brightness.m
===================================================================
--- trunk/LocationDo/brightness.m (revision 196)
+++ trunk/LocationDo/brightness.m (revision 196)
@@ -0,0 +1,65 @@
+/* gcc -o brightness -framework Cocoa -framework DisplayServices -F/System/Library/PrivateFrameworks brightness.m */
+
+#import
+
+@interface O3Manager : NSObject
++ (void)initialize;
++ (id)engineOfClass:(NSString *)cls forDisplayID:(CGDirectDisplayID)fp12;
+@end
+
+@protocol O3EngineWireProtocol
+@end
+
+@protocol BrightnessEngineWireProtocol
+- (float)brightness;
+- (BOOL)setBrightness:(float)fp8;
+- (void)bumpBrightnessUp;
+- (void)bumpBrightnessDown;
+@end
+
+const int kMaxDisplays = 16;
+
+int main(int argc, const char *argv[])
+{
+ CGDirectDisplayID display[kMaxDisplays];
+ CGDisplayCount numDisplays;
+ CGDisplayCount i;
+ CGDisplayErr err;
+
+ [[NSAutoreleasePool alloc] init];
+ [O3Manager initialize];
+
+ err = CGGetActiveDisplayList(kMaxDisplays, display, &numDisplays);
+ if (err != CGDisplayNoErr) {
+ NSLog(@"Cannot get displays (%d)", err);
+ exit(1);
+ }
+ printf("%d displays found", (int)numDisplays);
+ for ( i = 0; i < numDisplays; ++i ) {
+ CGDirectDisplayID dspy = display[i];
+ CFDictionaryRef originalMode;
+
+ originalMode = CGDisplayCurrentMode(dspy);
+ if (originalMode == NULL)
+ continue;
+
+ NSLog(@"Display 0x%x: %@", (unsigned int)dspy, originalMode);
+
+ if ([[(NSDictionary *)originalMode objectForKey: @"RefreshRate"] intValue] == 0) {
+ id engine =
+ [O3Manager engineOfClass: @"BrightnessEngine" forDisplayID: dspy];
+ NSLog(@"Engine: %@", engine);
+ NSLog(@"Brightness was %f", [engine brightness]);
+ if (argc == 2) {
+ float newBrightness = [[NSString stringWithCString: argv[1]] floatValue];
+ if (newBrightness < 0. || newBrightness > 1.) {
+ NSLog(@"Brightness should be between 0 and 1");
+ exit(1);
+ }
+ [engine setBrightness: newBrightness];
+ NSLog(@"Brightness is now %f", [engine brightness]);
+ }
+ }
+ }
+ exit(0);
+}
Index: trunk/LocationDo/setup.py
===================================================================
--- trunk/LocationDo/setup.py (revision 196)
+++ trunk/LocationDo/setup.py (revision 196)
@@ -0,0 +1,26 @@
+"""
+Script for building LocationDo.
+
+Usage:
+ python setup.py py2app
+"""
+from distutils.core import setup, Extension
+import py2app
+
+plist = dict(
+ CFBundleIdentifier='net.sabi.LocationDo',
+ CFBundleName='LocationDo',
+ NSPrincipalClass='LocationDo',
+ LSUIElement=1,
+)
+
+setup(
+ app=["LocationDo.py"],
+ ext_modules=[Extension('SCNetworkReachability',
+ sources=['SCNetworkReachabilitymodule.c'],
+ extra_link_args=['-framework', 'SystemConfiguration',
+ '-framework', 'CoreFoundation'])],
+ data_files=["English.lproj"],
+ options=dict(py2app=dict(plist=plist,
+ resources=['brightness'])),
+)