[196] | 1 | from Authorization import Authorization, kAuthorizationFlagDestroyRights
|
---|
| 2 | from Foundation import NSBundle, NSNotificationCenter, NSObject, NSURL
|
---|
| 3 | from AppKit import NSWorkspace
|
---|
| 4 | from appscript import app, k, its
|
---|
[212] | 5 | from appscript.specifier import CommandError
|
---|
[196] | 6 | import os, osax, subprocess, tempfile, SCNetworkReachability
|
---|
| 7 |
|
---|
| 8 | appBundle = NSBundle.mainBundle()
|
---|
| 9 |
|
---|
| 10 | def ensureKerberosPrincipalsValid(principals):
|
---|
| 11 | kerberosApp = app(id='edu.mit.Kerberos.KerberosApp')
|
---|
[213] | 12 | validPrincipals = kerberosApp.caches.filter( \
|
---|
| 13 | (its.time_remaining.startswith('Expired').NOT) \
|
---|
| 14 | .AND(its.time_remaining.startswith('Not Valid').NOT)) \
|
---|
| 15 | .principal.get()
|
---|
[196] | 16 | for principal in principals:
|
---|
| 17 | if principal not in validPrincipals:
|
---|
| 18 | # XXX make these async
|
---|
| 19 | kerberosApp.renew_tickets(for_principal=principal)
|
---|
| 20 | # kerberosApp.get_tickets(for_principal=principal)
|
---|
| 21 |
|
---|
| 22 | def setVolumePercent(percent):
|
---|
| 23 | # XXX should use CoreAudio: see Pester/Source/NJRSoundManager.m
|
---|
| 24 | osax.setvolume(int(7 * percent))
|
---|
| 25 |
|
---|
| 26 | def _openURL(url):
|
---|
| 27 | ws = NSWorkspace.sharedWorkspace()
|
---|
| 28 | url = NSURL.URLWithString_(url)
|
---|
| 29 | return ws.openURL_(url)
|
---|
| 30 |
|
---|
| 31 | class _LDURLWatcher(NSObject):
|
---|
| 32 | def URLIsReachable_(self, notification):
|
---|
| 33 | _openURL(notification.object())
|
---|
| 34 |
|
---|
| 35 | _urlWatcher = _LDURLWatcher.alloc().init()
|
---|
| 36 | NSNotificationCenter.defaultCenter().addObserver_selector_name_object_(
|
---|
| 37 | _urlWatcher, 'URLIsReachable:', SCNetworkReachability.SCNetworkIsReachable,
|
---|
| 38 | None)
|
---|
| 39 |
|
---|
| 40 | def openURL(url):
|
---|
| 41 | hostname = NSURL.URLWithString_(url).host()
|
---|
| 42 | if not SCNetworkReachability.notify_when_reachable(hostname, url):
|
---|
| 43 | _openURL(url)
|
---|
| 44 |
|
---|
| 45 | def openInBackground(path):
|
---|
| 46 | ws = NSWorkspace.sharedWorkspace()
|
---|
| 47 | path = os.path.expanduser(path)
|
---|
| 48 | return ws.openFile_withApplication_andDeactivate_(path, None, False)
|
---|
| 49 |
|
---|
[212] | 50 | def openApplicationWithName(name):
|
---|
| 51 | ws = NSWorkspace.sharedWorkspace()
|
---|
| 52 | return ws.launchApplication_(name)
|
---|
| 53 |
|
---|
[196] | 54 | def setDisplayBrightnessPercent(percent):
|
---|
| 55 | # XXX create brightness module
|
---|
| 56 | pathToBrightness = appBundle.pathForResource_ofType_('brightness', None)
|
---|
| 57 | subprocess.call([pathToBrightness, str(percent)], stderr=subprocess.STDOUT,
|
---|
| 58 | stdout=subprocess.PIPE)
|
---|
| 59 |
|
---|
| 60 | def setAdiumStatus(status):
|
---|
| 61 | # XXX doesn't match preset status if available
|
---|
| 62 | adiumApp = app(id='com.adiumX.adiumX')
|
---|
| 63 | if adiumApp.my_status_type() == k.offline:
|
---|
| 64 | adiumApp.my_status.status_message_string.set(status)
|
---|
| 65 | else:
|
---|
| 66 | adiumApp.my_status_message.set(status)
|
---|
| 67 | adiumApp.my_status_type.set(k.available)
|
---|
| 68 |
|
---|
[212] | 69 | def moveWindow(appName, windowName, position, ifSizeMatches=None):
|
---|
| 70 | # XXX support locations relative to screen corners
|
---|
| 71 | systemEventsApp = app(id='com.apple.systemevents')
|
---|
| 72 | try:
|
---|
| 73 | if ifSizeMatches:
|
---|
| 74 | size = tuple(systemEventsApp.processes[appName].windows[windowName].size())
|
---|
| 75 | ifSizeMatches = tuple(ifSizeMatches)
|
---|
| 76 | if size != ifSizeMatches:
|
---|
| 77 | print "Window size didn't match: %s in %s" % (windowName, appName)
|
---|
| 78 | print "- Wanted %s, got %s" % (ifSizeMatches, size)
|
---|
| 79 | return False
|
---|
| 80 | systemEventsApp.processes[appName].windows[windowName].position.set(position)
|
---|
| 81 | except CommandError:
|
---|
| 82 | print "CommandError: %s in %s" % (windowName, appName)
|
---|
| 83 | return False
|
---|
| 84 | return True
|
---|
| 85 |
|
---|
[196] | 86 | def terminalDo(command):
|
---|
[205] | 87 | # XXX if this launches Terminal, it inherits our PYTHONPATH
|
---|
[196] | 88 | terminalApp = app(id='com.apple.Terminal')
|
---|
[205] | 89 | # XXX this does not create a new Terminal window; fix
|
---|
[196] | 90 | terminalApp.do_script(command + '; exit')
|
---|
| 91 |
|
---|
| 92 | _auth = None
|
---|
| 93 |
|
---|
| 94 | def authorizationDo(command, *args):
|
---|
| 95 | global _auth
|
---|
| 96 | if not _auth:
|
---|
| 97 | _auth = Authorization(destroyflags=(kAuthorizationFlagDestroyRights,))
|
---|
| 98 | return _auth.executeWithPrivileges(command, *args)
|
---|
| 99 |
|
---|
| 100 | def stopVPNC():
|
---|
[198] | 101 | # killall uses your uid, not your euid to determine which user's
|
---|
| 102 | # processes to kill; without '-u root', this fails
|
---|
| 103 | authorizationDo('/usr/bin/killall', '-u', 'root', 'vpnc')
|
---|
[196] | 104 |
|
---|
| 105 | def startVPNC(vpncProfile=None):
|
---|
| 106 | stopVPNC()
|
---|
| 107 | args = ['--kernel-ipsec']
|
---|
| 108 | if vpncProfile:
|
---|
| 109 | args.append('/etc/vpnc/%s.conf' % vpncProfile)
|
---|
| 110 | # XXX get password from keychain, then use:
|
---|
| 111 | # authorizationDo('/usr/local/sbin/vpnc', *args)
|
---|
| 112 | terminalDo('sudo /usr/local/sbin/vpnc %s' % ' '.join(args))
|
---|