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.

185 lines
7.3 KiB

  1. #!/usr/bin/env python
  2. from Foundation import *
  3. from AppKit import *
  4. from SystemConfiguration import *
  5. # from pprint import pprint
  6. import commands, re
  7. class ToggleProxy(NSObject):
  8. proxies = {
  9. 'ftp' : { 'pref': kSCPropNetProxiesFTPEnable, 'title': 'FTP Proxy', 'action': 'toggleFtpProxy:', 'keyEquivalent': "", 'menuitem': None },
  10. 'http' : { 'pref': kSCPropNetProxiesHTTPEnable, 'title': 'HTTP Proxy', 'action': 'toggleHttpProxy:', 'keyEquivalent': "", 'menuitem': None },
  11. 'https': { 'pref': kSCPropNetProxiesHTTPSEnable, 'title': 'HTTPS Proxy', 'action': 'toggleHttpsProxy:', 'keyEquivalent': "", 'menuitem': None },
  12. 'rtsp': { 'pref': kSCPropNetProxiesRTSPEnable, 'title': 'RTSP Proxy', 'action': 'toggleRtspProxy:', 'keyEquivalent': "", 'menuitem': None },
  13. 'socks': { 'pref': kSCPropNetProxiesSOCKSEnable, 'title': 'SOCKS Proxy', 'action': 'toggleSocksProxy:', 'keyEquivalent': "", 'menuitem': None },
  14. }
  15. def applicationDidFinishLaunching_(self, notification):
  16. # load icon files
  17. self.icons = {
  18. '0-0-0' : NSImage.imageNamed_("icon-0-0-0"),
  19. '1-0-0' : NSImage.imageNamed_("icon-1-0-0"),
  20. '0-1-0' : NSImage.imageNamed_("icon-0-1-0"),
  21. '0-0-1' : NSImage.imageNamed_("icon-0-0-1"),
  22. '1-1-0' : NSImage.imageNamed_("icon-1-1-0"),
  23. '1-0-1' : NSImage.imageNamed_("icon-1-0-1"),
  24. '1-1-1' : NSImage.imageNamed_("icon-1-1-1")
  25. }
  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. self.statusitem.setImage_(self.icons['0-0-0'])
  32. # insert a menu into the status bar item
  33. self.menu = NSMenu.alloc().init()
  34. self.statusitem.setMenu_(self.menu)
  35. # open connection to the dynamic (configuration) store
  36. self.store = SCDynamicStoreCreate(None, "name.klep.toggleproxy", self.dynamicStoreCallback, None)
  37. service = self.service
  38. proxyRef = SCNetworkServiceCopyProtocol(service, kSCNetworkProtocolTypeProxies)
  39. prefDict = SCNetworkProtocolGetConfiguration(proxyRef)
  40. # pprint(prefDict)
  41. separatorRequired = False
  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. # NSLog("%s does not appear to be configured" % proxy['title'])
  53. proxy['menuitem'] = None
  54. # pprint(self.proxies)
  55. if separatorRequired:
  56. self.menu.addItem_(NSMenuItem.separatorItem())
  57. self.menu.addItemWithTitle_action_keyEquivalent_(
  58. "Quit",
  59. "quitApp:",
  60. "")
  61. # start working
  62. self.loadNetworkServices()
  63. self.watchForProxyChanges()
  64. self.updateProxyStatus()
  65. @property
  66. def interface(self):
  67. # get primary interface
  68. return SCDynamicStoreCopyValue(self.store, 'State:/Network/Global/IPv4')['PrimaryInterface']
  69. @property
  70. def service(self):
  71. """ Returns the service relating to self.interface """
  72. prefs = SCPreferencesCreate(kCFAllocatorDefault, 'PRG', None)
  73. # Fetch the list of services
  74. for serviceRef in SCNetworkServiceCopyAll(prefs):
  75. interface = SCNetworkServiceGetInterface(serviceRef)
  76. bsdname = SCNetworkInterfaceGetBSDName(interface)
  77. if self.interface == bsdname:
  78. return serviceRef
  79. return None
  80. def loadNetworkServices(self):
  81. """ Load and store a list of network services indexed by their BSDName """
  82. self.services = {}
  83. # Create a dummy preference reference
  84. prefs = SCPreferencesCreate(kCFAllocatorDefault, 'PRG', None)
  85. # Fetch the list of services
  86. for service in SCNetworkServiceCopyAll(prefs):
  87. # This is what we're after, the user-defined service name e.g., "Built-in Ethernet"
  88. name = SCNetworkServiceGetName(service)
  89. # Interface reference
  90. interface = SCNetworkServiceGetInterface(service)
  91. # The BSDName of the interface, E.g., "en1", this will be the index of our list
  92. bsdname = SCNetworkInterfaceGetBSDName(interface)
  93. self.services[bsdname] = name
  94. def watchForProxyChanges(self):
  95. """ install a watcher for proxy changes """
  96. SCDynamicStoreSetNotificationKeys(self.store, None, [ 'State:/Network/Global/Proxies' ])
  97. source = SCDynamicStoreCreateRunLoopSource(None, self.store, 0)
  98. loop = NSRunLoop.currentRunLoop().getCFRunLoop()
  99. CFRunLoopAddSource(loop, source, kCFRunLoopCommonModes)
  100. def dynamicStoreCallback(self, store, keys, info):
  101. """ callback for watcher """
  102. self.updateProxyStatus()
  103. def updateProxyStatus(self):
  104. """ update proxy status """
  105. # load proxy dictionary
  106. proxydict = SCDynamicStoreCopyProxies(None)
  107. # get status for primary interface
  108. status = proxydict['__SCOPED__'][self.interface]
  109. # update menu items according to their related proxy state
  110. for proxy in self.proxies.values():
  111. if proxy['menuitem']:
  112. proxy['menuitem'].setState_( status.get(proxy['pref'], False) and NSOnState or NSOffState)
  113. # update icon
  114. self.statusitem.setImage_(
  115. self.icons['%d-%d-%d' % (
  116. status.get(self.proxies['http']['pref'], False) and 1 or 0,
  117. status.get(self.proxies['https']['pref'], False) and 1 or 0,
  118. status.get(self.proxies['socks']['pref'], False) and 1 or 0
  119. )]
  120. )
  121. def quitApp_(self, sender):
  122. NSApp.terminate_(self)
  123. def toggleFtpProxy_(self, sender):
  124. self.toggleProxy(self.proxies['ftp']['menuitem'], 'ftpproxy')
  125. def toggleHttpProxy_(self, sender):
  126. self.toggleProxy(self.proxies['http']['menuitem'], 'webproxy')
  127. def toggleHttpsProxy_(self, sender):
  128. self.toggleProxy(self.proxies['https']['menuitem'], 'securewebproxy')
  129. def toggleRtspProxy_(self, sender):
  130. self.toggleProxy(self.proxies['socks']['menuitem'], 'streamingproxy')
  131. def toggleSocksProxy_(self, sender):
  132. self.toggleProxy(self.proxies['socks']['menuitem'], 'socksfirewallproxy')
  133. def toggleProxy(self, item, target):
  134. """ callback for clicks on menu item """
  135. servicename = self.services.get(self.interface)
  136. if not servicename:
  137. NSLog("interface '%s' not found in services?" % self.interface)
  138. return
  139. newstate = item.state() == NSOffState and 'on' or 'off'
  140. commands.getoutput("/usr/sbin/networksetup -set%sstate '%s' %s" % (
  141. target,
  142. servicename,
  143. newstate
  144. ))
  145. self.updateProxyStatus()
  146. if __name__ == '__main__':
  147. sharedapp = NSApplication.sharedApplication()
  148. toggler = ToggleProxy.alloc().init()
  149. sharedapp.setDelegate_(toggler)
  150. sharedapp.run()