-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathToggleProxy.py
253 lines (215 loc) · 11.9 KB
/
ToggleProxy.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
#!/usr/bin/env python
from Foundation import NSLog, kCFRunLoopCommonModes, kCFAllocatorDefault, CFDictionaryGetValue, CFRunLoopAddSource, NSUserDefaults
from AppKit import NSObject, NSImage, NSStatusBar, NSVariableStatusItemLength, NSMenu, NSMenuItem, NSRunLoop, NSOnState, NSApp, NSLog, NSOffState, NSApplication
from SystemConfiguration import kSCNetworkProtocolTypeProxies, kSCPropNetProxiesFTPEnable, kSCPropNetProxiesHTTPEnable, kSCPropNetProxiesHTTPSEnable, kSCPropNetProxiesRTSPEnable, kSCPropNetProxiesSOCKSEnable, kSCPropNetProxiesFTPProxy, kSCPropNetProxiesHTTPProxy, kSCPropNetProxiesHTTPSProxy, kSCPropNetProxiesRTSPProxy, kSCPropNetProxiesSOCKSProxy, kSCPropNetProxiesFTPPort, \
kSCPropNetProxiesHTTPPort, kSCPropNetProxiesHTTPSPort, kSCPropNetProxiesRTSPPort, kSCPropNetProxiesSOCKSPort, kSCNetworkProtocolTypeProxies
from SystemConfiguration import SCDynamicStoreCreate, SCNetworkServiceCopyProtocol, SCNetworkProtocolGetConfiguration, SCDynamicStoreCopyValue, SCPreferencesCreate, SCNetworkServiceCopyAll, SCNetworkServiceGetInterface, SCNetworkInterfaceGetBSDName, SCDynamicStoreSetNotificationKeys, SCDynamicStoreCreateRunLoopSource, SCDynamicStoreCopyProxies, SCNetworkServiceGetName
import commands, re
class ToggleProxy(NSObject):
# This is a dictionary of the proxy-types we support, each with a
# dictionary of some unique attributes for each, namely:
#
# 'prefEnable' : This is a constant defining which preference item marks if this proxy is enabled
# 'prefProxy' : This is a constant defining which preference item holds the proxy host
# 'prefPort' : This is a constant defining which preference item holds the proxy port
# 'title' : This is what will appear in the menu
# 'action' : This is the method that will be called if the user toggles this proxies menuitem
# 'keyEquivalent' : Self-explanatory
# 'menuitem' : This will store the menu item for this proxy once it is created
# 'envVariable' : Environment variable to set with system proxy settings, if needed
proxyTypes = {
'http': {'prefEnable': kSCPropNetProxiesHTTPEnable,
'prefProxy': kSCPropNetProxiesHTTPProxy,
'prefPort': kSCPropNetProxiesHTTPPort,
'title': 'HTTP Proxy',
'action': 'toggleHttpProxy:',
'keyEquivalent': "",
'menuitem': None,
'envVariable': {"http_proxy", "HTTP_PROXY"}},
'https': {'prefEnable': kSCPropNetProxiesHTTPSEnable,
'prefProxy': kSCPropNetProxiesHTTPSProxy,
'prefPort': kSCPropNetProxiesHTTPSPort,
'title': 'HTTPS Proxy', 'action': 'toggleHttpsProxy:',
'keyEquivalent': "",
'menuitem': None,
'envVariable': {"https_proxy", "HTTPS_PROXY"}},
'ftp': {'prefEnable': kSCPropNetProxiesFTPEnable,
'prefProxy': kSCPropNetProxiesFTPProxy,
'prefPort': kSCPropNetProxiesFTPPort,
'title': 'FTP Proxy',
'action': 'toggleFtpProxy:',
'keyEquivalent': "",
'menuitem': None,
'envVariable': {"ftp_proxy", "FTP_PROXY"}},
'rtsp': {'prefEnable': kSCPropNetProxiesRTSPEnable,
'prefProxy': kSCPropNetProxiesRTSPProxy,
'prefPort': kSCPropNetProxiesRTSPPort,
'title': 'RTSP Proxy',
'action': 'toggleRtspProxy:',
'keyEquivalent': "",
'menuitem': None,
'envVariable': None},
'socks': {'prefEnable': kSCPropNetProxiesSOCKSEnable,
'prefProxy': kSCPropNetProxiesSOCKSProxy,
'prefPort': kSCPropNetProxiesSOCKSPort,
'title': 'SOCKS Proxy',
'action': 'toggleSocksProxy:',
'keyEquivalent': "",
'menuitem': None,
'envVariable': None},
}
def log(self, content):
# Toggle logging from Terminal:
# $ defaults write name.klep.toggleproxy logging -bool YES/NO
if NSUserDefaults.standardUserDefaults().boolForKey_("logging"):
NSLog(content)
def applicationDidFinishLaunching_(self, notification):
# load icon files
self.active_image = NSImage.imageNamed_("StatusBarImage")
self.inactive_image = NSImage.imageNamed_("StatusBarImage-inactive")
self.no_network_image = NSImage.imageNamed_("StatusBarImage-noNetwork")
self.active_image.setTemplate_(True)
self.inactive_image.setTemplate_(True)
self.no_network_image.setTemplate_(True)
# make status bar item
self.statusitem = NSStatusBar.systemStatusBar().statusItemWithLength_(NSVariableStatusItemLength)
self.statusitem.retain()
self.statusitem.setHighlightMode_(False)
self.statusitem.setEnabled_(True)
# insert a menu into the status bar item
self.menu = NSMenu.alloc().init()
self.statusitem.setMenu_(self.menu)
# open connection to the dynamic (configuration) store
self.store = SCDynamicStoreCreate(None, "name.klep.toggleproxy", self.dynamicStoreCallback, None)
self.prefDict = SCNetworkProtocolGetConfiguration(SCNetworkServiceCopyProtocol(self.service, kSCNetworkProtocolTypeProxies))
self.constructMenu()
self.watchForProxyOrIpChanges()
self.updateUI()
self.setEnvVariables()
@property
def is_ip_assigned(self):
return SCDynamicStoreCopyValue(self.store, 'State:/Network/Global/IPv4') is not None
@property
def interface(self):
# get primary interface
return SCDynamicStoreCopyValue(self.store, 'State:/Network/Global/IPv4')['PrimaryInterface']
@property
def service(self):
""" Returns the service relating to self.interface """
prefs = SCPreferencesCreate(kCFAllocatorDefault, 'PRG', None)
# Fetch the list of services
for serviceRef in SCNetworkServiceCopyAll(prefs):
interface = SCNetworkServiceGetInterface(serviceRef)
if self.interface == SCNetworkInterfaceGetBSDName(interface):
return serviceRef
return None
def constructMenu(self):
self.menu.removeAllItems()
separator_required = False
if self.is_ip_assigned:
# For each of the proxyTypes we are concerned with, check to see if any
# are configured. If so (even if not enabled), create a menuitem for
# that proxy type.
for proxy in self.proxyTypes.values():
enabled = CFDictionaryGetValue(self.prefDict, proxy['prefEnable'])
if enabled is not None:
proxy['menuitem'] = self.menu.addItemWithTitle_action_keyEquivalent_(
proxy['title'],
proxy['action'],
proxy['keyEquivalent']
)
separator_required = True
else:
proxy['menuitem'] = None
else:
self.menu.addItemWithTitle_action_keyEquivalent_("No connection - Please connect to any network before using this tool", None, "")
if separator_required:
self.menu.addItem_(NSMenuItem.separatorItem())
# Need a way to quit
self.menu.addItemWithTitle_action_keyEquivalent_("Quit", "quitApp:", "q")
def watchForProxyOrIpChanges(self):
""" install a watcher for proxy and Ip changes """
SCDynamicStoreSetNotificationKeys(self.store, None, ['State:/Network/Global/Proxies', 'State:/Network/Global/IPv4'])
source = SCDynamicStoreCreateRunLoopSource(None, self.store, 0)
loop = NSRunLoop.currentRunLoop().getCFRunLoop()
CFRunLoopAddSource(loop, source, kCFRunLoopCommonModes)
def dynamicStoreCallback(self, store, keys, info):
""" callback for watcher """
self.log("Proxy or IP change detected")
# could be an interface change, we have to rebuild menu from scratch in case the proxy configuration is different
self.constructMenu()
self.updateUI()
self.setEnvVariables()
def updateUI(self):
if self.is_ip_assigned:
self.log("Update proxy status on menu items")
# load proxy dictionary
proxydict = SCDynamicStoreCopyProxies(None)
# get status for primary interface
status = proxydict['__SCOPED__'][self.interface]
# Are any proxies active now?
anyProxyEnabled = False
# update menu items according to their related proxy state
for proxy in self.proxyTypes.values():
if proxy['menuitem']:
proxy['menuitem'].setState_(status.get(proxy['prefEnable'], False) and NSOnState or NSOffState)
if status.get(proxy['prefEnable'], False):
anyProxyEnabled = True
# set image
self.statusitem.setImage_(anyProxyEnabled and self.active_image or self.inactive_image)
else:
self.statusitem.setImage_(self.no_network_image)
def setEnvVariables(self):
if self.is_ip_assigned:
self.log("Setting env var according to system settings")
# load proxy dictionary
proxydict = SCDynamicStoreCopyProxies(None)
# get status for primary interface
status = proxydict['__SCOPED__'][self.interface]
# update menu items according to their related proxy state
for proxy in self.proxyTypes.values():
if proxy['menuitem'] and proxy['envVariable']:
if status.get(proxy['prefEnable'], False):
for envvar in proxy['envVariable']:
self.executeCommand("launchctl setenv %s '%s'" % (envvar, "http://" + CFDictionaryGetValue(self.prefDict, proxy['prefProxy']) + ":" + str(CFDictionaryGetValue(self.prefDict, proxy['prefPort']))))
else:
for envvar in proxy['envVariable']:
self.executeCommand("launchctl unsetenv %s" % envvar)
def quitApp_(self, sender):
NSApp.terminate_(self)
def toggleFtpProxy_(self, sender):
self.toggleProxy(self.proxyTypes['ftp']['menuitem'], 'ftpproxy')
def toggleHttpProxy_(self, sender):
self.toggleProxy(self.proxyTypes['http']['menuitem'], 'webproxy')
def toggleHttpsProxy_(self, sender):
self.toggleProxy(self.proxyTypes['https']['menuitem'], 'securewebproxy')
def toggleRtspProxy_(self, sender):
self.toggleProxy(self.proxyTypes['rtsp']['menuitem'], 'streamingproxy')
def toggleSocksProxy_(self, sender):
self.toggleProxy(self.proxyTypes['socks']['menuitem'], 'socksfirewallproxy')
def executeCommand(self, command):
self.log("[Exec Command] %s" % command)
commands.getoutput(command)
def toggleProxy(self, item, target):
""" callback for clicks on menu item """
servicename = SCNetworkServiceGetName(self.service)
if not servicename:
self.log("interface '%s' not found in services?" % self.interface)
return
newstate = item.state() == NSOffState and 'on' or 'off'
# Sometimes the UI will be updated too fast if we don't wait a little
# (resulting in wrongly enabled proxies in the menu), so after changing
# the interface state we wait a bit (this is easier than doing a sleep
# in code, as that has to be scheduled on the run loop)
self.executeCommand("/usr/sbin/networksetup -set%sstate '%s' %s; sleep 1" % (
target,
servicename,
newstate
))
self.updateUI()
self.setEnvVariables()
if __name__ == '__main__':
sharedapp = NSApplication.sharedApplication()
toggler = ToggleProxy.alloc().init()
sharedapp.setDelegate_(toggler)
sharedapp.run()