Last active
May 18, 2020 15:32
-
-
Save zakes-it/bedbaa2a782ffb9bd97454b281b7262d to your computer and use it in GitHub Desktop.
Use with crankd turn off AirPort when on an Ethernet connection and turn AirPort back on when Ethernet disconnects
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#! /usr/bin/python | |
import syslog | |
import objc | |
from Foundation import NSAppleScript, CFPreferencesCopyAppValue,\ | |
CFPreferencesSetAppValue, CFPreferencesAppSynchronize | |
from SystemConfiguration import SCDynamicStoreCreate, SCDynamicStoreCopyValue | |
BUNDLE_ID = 'AirportKiller' | |
bundle_path = '/System/Library/Frameworks/CoreWLAN.framework' | |
objc.loadBundle('CoreWLAN', bundle_path=bundle_path, module_globals=globals()) | |
iface = CWInterface.interface() | |
""" | |
Turn off wifi: | |
iface.setPower_error_(False, None) | |
Turn on wifi: | |
iface.setPower_error_(True, None) | |
""" | |
class LogHelper(object): | |
def __init__(self, consoleOn=False, guiOn=False): | |
self.console = consoleOn | |
self.gui = guiOn | |
self.msg = "AirportKiller: Network change detected" | |
def add(self, txt): | |
self.msg = self.msg + "\n" + txt | |
def submit(self): | |
if self.gui: | |
self.aScriptNotify() | |
if self.console: | |
self.consoleLog() | |
def consoleLog(self): | |
syslog.syslog(syslog.LOG_ALERT, self.msg) | |
def aScriptNotify(self): | |
script = '''tell application "System Events" | |
activate | |
display dialog "{}" with title "AirPort Killer" buttons {{"OK"}} | |
end tell | |
'''.format(self.msg) | |
s = NSAppleScript.alloc().initWithSource_(script) | |
_ = s.executeAndReturnError_(None) | |
def dumpState(state): | |
for key, val in state.items(): | |
CFPreferencesSetAppValue(key, val, BUNDLE_ID) | |
CFPreferencesAppSynchronize(BUNDLE_ID) | |
def loadState(): | |
state = {} | |
for key in ('AirportPower', 'AirportActive', 'EthernetActive'): | |
state[key] = CFPreferencesCopyAppValue(key, BUNDLE_ID) | |
return state | |
def getState(): | |
net_config = SCDynamicStoreCreate(None, "net", None, None) | |
services = {} | |
key = "Setup:/Network/Global/IPv4" | |
service_uuids = SCDynamicStoreCopyValue(net_config, key)["ServiceOrder"] | |
for uuid in service_uuids: | |
key = "Setup:/Network/Service/{}/Interface".format(uuid) | |
service = SCDynamicStoreCopyValue(net_config, key) | |
if 'DeviceName' in service: | |
services[service['DeviceName']] = service | |
key = "State:/Network/Interface" | |
interfaces = SCDynamicStoreCopyValue(net_config, key)['Interfaces'] | |
ether = {} | |
for i in interfaces: | |
# ignore devices not listed in network services | |
if i not in services: | |
continue | |
# ignore bluetooth interfaces | |
if i.startswith('awdl') or i.startswith('llw'): | |
continue | |
# ignore hidden interfaces like iPhone/iPad USB | |
if services[i].get('HiddenConfiguration', False): | |
continue | |
key = "State:/Network/Interface/{}/Link".format(i) | |
state = SCDynamicStoreCopyValue(net_config, key) | |
key = "State:/Network/Interface/{}/AirPort".format(i) | |
airport = SCDynamicStoreCopyValue(net_config, key) | |
if airport: | |
currentState = {'AirportPower': airport['Power Status'], | |
'AirportActive': state['Active']} | |
continue | |
if state: | |
ether[i] = state['Active'] | |
currentState['EthernetActive'] = any(ether.values()) | |
return currentState | |
syslog.openlog(ident="AirportKiller", facility=syslog.LOG_DAEMON) | |
log = LogHelper(consoleOn=True, guiOn=False) | |
currentState = getState() | |
log.add("Current network state: {}".format(currentState)) | |
try: | |
previousState = loadState() | |
except: | |
previousState = None | |
if previousState: | |
log.add("Previous network state: {}".format(previousState)) | |
else: | |
log.add("Previous network state unknown.") | |
if currentState['EthernetActive'] and currentState['AirportActive']: | |
log.add("System docked, previous network state unknown. Killing" | |
" Airport...") | |
iface.setPower_error_(False, None) | |
dumpState(getState()) | |
log.submit() | |
quit() | |
if (previousState['EthernetActive'] and not currentState['EthernetActive'] and | |
not currentState['AirportActive']): | |
log.add("System undocked with AirPort off. Resurrecting AirPort...") | |
iface.setPower_error_(True, None) | |
elif (not previousState['EthernetActive'] and currentState['EthernetActive'] | |
and currentState['AirportActive']): | |
log.add("System docked with AirPort on. Killing AirPort...") | |
iface.setPower_error_(False, None) | |
else: | |
log.add("No actionable network changes.") | |
dumpState(getState()) | |
log.submit() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?xml version="1.0" encoding="UTF-8"?> | |
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> | |
<plist version="1.0"> | |
<dict> | |
<key>SystemConfiguration</key> | |
<dict> | |
<key>State:/Network/Global/IPv4</key> | |
<dict> | |
<key>command</key> | |
<string>/usr/bin/python '/usr/local/myorg/AirportKiller.py'</string> | |
</dict> | |
</dict> | |
</dict> | |
</plist> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment