Mac OS X menu item for quickly enabling/disabling HTTP proxy
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

164 lines
6.6 KiB

  1. #!/usr/bin/env python
  2. from Foundation import *
  3. from AppKit import *
  4. from SystemConfiguration import *
  5. import commands, re
  6. class ToggleProxy(NSObject):
  7. # This is a dictionary of the proxy-types we support, each with a
  8. # dictionary of some unique attributes for each, namely:
  9. #
  10. # 'pref' : This is a constant defining which preference itemmarks if this proxy is enabled
  11. # 'title' : This is what will appear in the menu
  12. # 'action' : This is the method that will be called if the user toggles this proxies menuitem
  13. # 'keyEquivalent' : Self-explanatory, but unused
  14. # 'menuitem' : This will store the menu item for this proxy once it is created
  15. proxies = {
  16. 'ftp' : { 'pref': kSCPropNetProxiesFTPEnable, 'title': 'FTP Proxy', 'action': 'toggleFtpProxy:', 'keyEquivalent': "", 'menuitem': None },
  17. 'http' : { 'pref': kSCPropNetProxiesHTTPEnable, 'title': 'HTTP Proxy', 'action': 'toggleHttpProxy:', 'keyEquivalent': "", 'menuitem': None },
  18. 'https': { 'pref': kSCPropNetProxiesHTTPSEnable, 'title': 'HTTPS Proxy', 'action': 'toggleHttpsProxy:', 'keyEquivalent': "", 'menuitem': None },
  19. 'rtsp' : { 'pref': kSCPropNetProxiesRTSPEnable, 'title': 'RTSP Proxy', 'action': 'toggleRtspProxy:', 'keyEquivalent': "", 'menuitem': None },
  20. 'socks': { 'pref': kSCPropNetProxiesSOCKSEnable, 'title': 'SOCKS Proxy', 'action': 'toggleSocksProxy:', 'keyEquivalent': "", 'menuitem': None },
  21. }
  22. def applicationDidFinishLaunching_(self, notification):
  23. # load icon files
  24. self.active_image = NSImage.imageNamed_("active")
  25. self.inactive_image = NSImage.imageNamed_("inactive")
  26. # make status bar item
  27. self.statusitem = NSStatusBar.systemStatusBar().statusItemWithLength_(NSVariableStatusItemLength)
  28. self.statusitem.retain()
  29. self.statusitem.setHighlightMode_(False)
  30. self.statusitem.setEnabled_(True)
  31. # insert a menu into the status bar item
  32. self.menu = NSMenu.alloc().init()
  33. self.statusitem.setMenu_(self.menu)
  34. # open connection to the dynamic (configuration) store
  35. self.store = SCDynamicStoreCreate(None, "name.klep.toggleproxy", self.dynamicStoreCallback, None)
  36. proxyRef = SCNetworkServiceCopyProtocol(self.service, kSCNetworkProtocolTypeProxies)
  37. prefDict = SCNetworkProtocolGetConfiguration(proxyRef)
  38. separatorRequired = False
  39. # For each of the proxies we are concerned with, check to see if any
  40. # are configured. If so (even if not enabled), create a menuitem for
  41. # that proxy type.
  42. for proxy in self.proxies.values():
  43. enabled = CFDictionaryGetValue(prefDict, proxy['pref'])
  44. if enabled is not None:
  45. proxy['menuitem'] = self.menu.addItemWithTitle_action_keyEquivalent_(
  46. proxy['title'],
  47. proxy['action'],
  48. proxy['keyEquivalent']
  49. )
  50. separatorRequired = True
  51. else:
  52. proxy['menuitem'] = None
  53. if separatorRequired:
  54. self.menu.addItem_(NSMenuItem.separatorItem())
  55. # Need a way to quit
  56. self.menu.addItemWithTitle_action_keyEquivalent_("Quit", "quitApp:", "")
  57. # Start working
  58. # self.loadNetworkServices()
  59. self.watchForProxyChanges()
  60. self.updateProxyStatus()
  61. @property
  62. def interface(self):
  63. # get primary interface
  64. return SCDynamicStoreCopyValue(self.store, 'State:/Network/Global/IPv4')['PrimaryInterface']
  65. @property
  66. def service(self):
  67. """ Returns the service relating to self.interface """
  68. prefs = SCPreferencesCreate(kCFAllocatorDefault, 'PRG', None)
  69. # Fetch the list of services
  70. for serviceRef in SCNetworkServiceCopyAll(prefs):
  71. interface = SCNetworkServiceGetInterface(serviceRef)
  72. if self.interface == SCNetworkInterfaceGetBSDName(interface):
  73. return serviceRef
  74. return None
  75. def watchForProxyChanges(self):
  76. """ install a watcher for proxy changes """
  77. SCDynamicStoreSetNotificationKeys(self.store, None, [ 'State:/Network/Global/Proxies' ])
  78. source = SCDynamicStoreCreateRunLoopSource(None, self.store, 0)
  79. loop = NSRunLoop.currentRunLoop().getCFRunLoop()
  80. CFRunLoopAddSource(loop, source, kCFRunLoopCommonModes)
  81. def dynamicStoreCallback(self, store, keys, info):
  82. """ callback for watcher """
  83. self.updateProxyStatus()
  84. def updateProxyStatus(self):
  85. """ update proxy status """
  86. # load proxy dictionary
  87. proxydict = SCDynamicStoreCopyProxies(None)
  88. # get status for primary interface
  89. status = proxydict['__SCOPED__'][self.interface]
  90. # Are any proxies active now?
  91. anyProxyEnabled = False
  92. # update menu items according to their related proxy state
  93. for proxy in self.proxies.values():
  94. if proxy['menuitem']:
  95. proxy['menuitem'].setState_(status.get(proxy['pref'], False) and NSOnState or NSOffState)
  96. if status.get(proxy['pref'], False):
  97. anyProxyEnabled = True
  98. # set image
  99. self.statusitem.setImage_(anyProxyEnabled and self.active_image or self.inactive_image)
  100. def quitApp_(self, sender):
  101. NSApp.terminate_(self)
  102. def toggleFtpProxy_(self, sender):
  103. self.toggleProxy(self.proxies['ftp']['menuitem'], 'ftpproxy')
  104. def toggleHttpProxy_(self, sender):
  105. self.toggleProxy(self.proxies['http']['menuitem'], 'webproxy')
  106. def toggleHttpsProxy_(self, sender):
  107. self.toggleProxy(self.proxies['https']['menuitem'], 'securewebproxy')
  108. def toggleRtspProxy_(self, sender):
  109. self.toggleProxy(self.proxies['socks']['menuitem'], 'streamingproxy')
  110. def toggleSocksProxy_(self, sender):
  111. self.toggleProxy(self.proxies['socks']['menuitem'], 'socksfirewallproxy')
  112. def toggleProxy(self, item, target):
  113. """ callback for clicks on menu item """
  114. servicename = SCNetworkServiceGetName(self.service)
  115. if not servicename:
  116. NSLog("interface '%s' not found in services?" % self.interface)
  117. return
  118. newstate = item.state() == NSOffState and 'on' or 'off'
  119. commands.getoutput("/usr/sbin/networksetup -set%sstate '%s' %s" % (
  120. target,
  121. servicename,
  122. newstate
  123. ))
  124. self.updateProxyStatus()
  125. if __name__ == '__main__':
  126. sharedapp = NSApplication.sharedApplication()
  127. toggler = ToggleProxy.alloc().init()
  128. sharedapp.setDelegate_(toggler)
  129. sharedapp.run()