Articles‎ > ‎

Replacing Kicker: handling system events in Python using the SystemConfiguration framework

posted Dec 28, 2008, 7:01 AM by Philip Rinehart   [ updated Mar 13, 2009, 3:09 PM by Nigel Kersten ]
by Chris Adams

Since this post was originally written, I've been working on the PyMacAdmin project with Nigel Kersten.
The information below is still correct but the kicker-replacement script has gained the ability to handle filesystem events and workspace notifications and been renamed to
crankd.


In a perfect world software would gracefully network transitions. Unfortunately my users have encountered a fair number of things which don't always handle things like a laptop moving from ethernet to WiFi, a DHCP server taking awhile to respond, etc. While many programs have at least reached the point of eventually timing out and retrying it would be nice to automatically restart something as soon as the network configuration changes. This is unfortunately system-specific and frequently required some hackish approach involving tail -f or equivalent to watch a log file, which is slow and tends to break on upgrades.

OS X has a nice way to query the current system configuration and receive event notifications when things change. In 10.5 you can elegantly receive event notifications entirely within Python.

OS X has a nice way to query the current system configuration and receive event notifications when things change: the SystemConfiguration Framework(Technical Note TN1145: Living in a Dynamic TCP/IP Environment is also of interest). You can explore this using the scutil command-line tool - in the example below, I've looked at the list of available events and chosen to watch for power-state changes, receiving a notice when I unplugged the power cable from my laptop:

chris@Enceladus:~ $ scutil> list  subKey [0] = Plugin:IPConfiguration  subKey [1] = Plugin:InterfaceNamer  subKey [2] = Setup:  subKey [3] = Setup:/  subKey [4] = Setup:/Network/Global/IPv4  subKey [5] = Setup:/Network/HostNames…  subKey [21] = State:/IOKit/PowerManagement/CurrentSettings  subKey [22] = State:/IOKit/PowerSources/InternalBattery-0…> n.add State:/IOKit/PowerSources/InternalBattery-0> n.watch> notification callback (store address = 0x1036c0).  changed key [0] = State:/IOKit/PowerSources/InternalBattery-0notification callback (store address = 0x1036c0).  changed key [0] = State:/IOKit/PowerSources/InternalBattery-0

This is pretty cool stuff but I'd like to do something smarter than scripting a copy of scutil. I could write an Objective-C application but OS X 10.5 included the very handy PyObjC 2.0 which allows access to most of the native APIs directly from within Python. James Reynolds posted a message to the MacEnterprise mailing list which prompted me to stop procrastinating and actually write some code.

A little poking around later and I have a Python script which is ready for me to add whatever custom actions I want to take when the network state changes:

from Cocoa import *from SystemConfiguration import *def handleNetworkConfigChange(store, changedKeys, info):	print "Global network configuration changed: ", changedKeys	# Kick a change-intolerant service in the head herestore = SCDynamicStoreCreate(None, "global-network-watcher", handleNetworkConfigChange, None)SCDynamicStoreSetNotificationKeys(store, None, [ 'State:/Network/Global/IPv4', 'State:/Network/Global/IPv6' ])CFRunLoopAddSource(CFRunLoopGetCurrent(), SCDynamicStoreCreateRunLoopSource(None, store, 0), kCFRunLoopCommonModes)CFRunLoopRun()

After some discussion on the MacEnterprise mailing list and some additional work by Geoff Franks to make it more useful out of the box here's a replacement for Kicker, which disappeared in 10.5:

http://improbable.org/chris/Software/kicker-replacement 

Comments