From 739b04065e3f235e2e14603d1583cadabc62c2e0 Mon Sep 17 00:00:00 2001 From: yangziy Date: Mon, 17 Sep 2018 13:15:48 +0800 Subject: [PATCH] Add HTTP API & AppleScript support. Refactor in class AppDelegate. --- ShadowsocksX-NG.xcodeproj/project.pbxproj | 12 +++ ShadowsocksX-NG/AppDelegate.swift | 85 ++++++++++++--------- ShadowsocksX-NG/AppleScriptDefinition.sdef | 51 +++++++++++++ ShadowsocksX-NG/AppleScriptUserProxy.swift | 72 ++++++++++++++++++ ShadowsocksX-NG/HTTPUserProxy.swift | 88 ++++++++++++++++++++++ ShadowsocksX-NG/Info.plist | 4 + 6 files changed, 278 insertions(+), 34 deletions(-) create mode 100644 ShadowsocksX-NG/AppleScriptDefinition.sdef create mode 100644 ShadowsocksX-NG/AppleScriptUserProxy.swift create mode 100644 ShadowsocksX-NG/HTTPUserProxy.swift diff --git a/ShadowsocksX-NG.xcodeproj/project.pbxproj b/ShadowsocksX-NG.xcodeproj/project.pbxproj index d3f372cf..46884f19 100755 --- a/ShadowsocksX-NG.xcodeproj/project.pbxproj +++ b/ShadowsocksX-NG.xcodeproj/project.pbxproj @@ -11,6 +11,9 @@ 1C82DBA81FA96C7500B32551 /* obfs-local in Resources */ = {isa = PBXBuildFile; fileRef = 1C82DBA51FA96C7400B32551 /* obfs-local */; }; 1C82DBAA1FA96FB600B32551 /* install_simple_obfs.sh in Resources */ = {isa = PBXBuildFile; fileRef = 1C82DBA91FA96F0300B32551 /* install_simple_obfs.sh */; }; 258E511BA910B0521B24DAB8 /* Pods_ShadowsocksX_NG.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 283ED1A8E9B711AC65670031 /* Pods_ShadowsocksX_NG.framework */; }; + 8EE2EDD8214F7CEC00FB4562 /* AppleScriptUserProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8EE2EDD4214F7CEC00FB4562 /* AppleScriptUserProxy.swift */; }; + 8EE2EDD9214F7CEC00FB4562 /* HTTPUserProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8EE2EDD6214F7CEC00FB4562 /* HTTPUserProxy.swift */; }; + 8EE2EDDA214F7CEC00FB4562 /* AppleScriptDefinition.sdef in Resources */ = {isa = PBXBuildFile; fileRef = 8EE2EDD7214F7CEC00FB4562 /* AppleScriptDefinition.sdef */; }; 9B07EFA71D048BBB0052D9DF /* ss-local in Resources */ = {isa = PBXBuildFile; fileRef = 9B07EFA61D048BBB0052D9DF /* ss-local */; }; 9B07EFAC1D048E880052D9DF /* menu_icon@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 9B07EFA81D048E880052D9DF /* menu_icon@2x.png */; }; 9B07EFAD1D048E880052D9DF /* menu_icon.png in Resources */ = {isa = PBXBuildFile; fileRef = 9B07EFA91D048E880052D9DF /* menu_icon.png */; }; @@ -145,6 +148,9 @@ 50D54926AA21B0D4D8DD9C4F /* Pods-ShadowsocksX-NGUITests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ShadowsocksX-NGUITests.release.xcconfig"; path = "Pods/Target Support Files/Pods-ShadowsocksX-NGUITests/Pods-ShadowsocksX-NGUITests.release.xcconfig"; sourceTree = ""; }; 58907E7F50405104B42CB189 /* Pods-ShadowsocksX-NGUITests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ShadowsocksX-NGUITests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-ShadowsocksX-NGUITests/Pods-ShadowsocksX-NGUITests.debug.xcconfig"; sourceTree = ""; }; 5B6203C1228FCD3D365814AC /* Pods-ShadowsocksX-NGTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ShadowsocksX-NGTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-ShadowsocksX-NGTests/Pods-ShadowsocksX-NGTests.debug.xcconfig"; sourceTree = ""; }; + 8EE2EDD4214F7CEC00FB4562 /* AppleScriptUserProxy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppleScriptUserProxy.swift; sourceTree = ""; }; + 8EE2EDD6214F7CEC00FB4562 /* HTTPUserProxy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HTTPUserProxy.swift; sourceTree = ""; }; + 8EE2EDD7214F7CEC00FB4562 /* AppleScriptDefinition.sdef */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = AppleScriptDefinition.sdef; sourceTree = ""; }; 9B07EFA61D048BBB0052D9DF /* ss-local */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.executable"; path = "ss-local"; sourceTree = ""; }; 9B07EFA81D048E880052D9DF /* menu_icon@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "menu_icon@2x.png"; sourceTree = ""; }; 9B07EFA91D048E880052D9DF /* menu_icon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = menu_icon.png; sourceTree = ""; }; @@ -359,6 +365,9 @@ isa = PBXGroup; children = ( 9BB706A51D1B982300551F0E /* SWBApplication.m */, + 8EE2EDD7214F7CEC00FB4562 /* AppleScriptDefinition.sdef */, + 8EE2EDD4214F7CEC00FB4562 /* AppleScriptUserProxy.swift */, + 8EE2EDD6214F7CEC00FB4562 /* HTTPUserProxy.swift */, 9BB706A61D1B982300551F0E /* SWBApplication.h */, 9B3FFF511D09DBA20019A709 /* ShadowsocksX-NG-Bridging-Header.h */, 9B3FFF151D072FDE0019A709 /* LaunchAtLoginController.h */, @@ -635,6 +644,7 @@ 9B3FFF341D08CEF70019A709 /* SWBQRCodeWindowController.xib in Resources */, 9B3FFF231D088E8D0019A709 /* abp.js in Resources */, 9B07EFAD1D048E880052D9DF /* menu_icon.png in Resources */, + 8EE2EDDA214F7CEC00FB4562 /* AppleScriptDefinition.sdef in Resources */, 9BAFE2E21E83ED7F00F71CCE /* PreferencesWinController.xib in Resources */, 9B0BFFEB1D0460A70040E62B /* Assets.xcassets in Resources */, 08FCA0FF1E24BE1A0070984F /* example-gui-config.json in Resources */, @@ -823,10 +833,12 @@ 9BB706A71D1B982300551F0E /* SWBApplication.m in Sources */, 9B3FFF1E1D0732660019A709 /* Utils.m in Sources */, 9B7297EA214D7C6B00FD24AA /* ShareServerProfilesWindowController.swift in Sources */, + 8EE2EDD9214F7CEC00FB4562 /* HTTPUserProxy.swift in Sources */, 9B3FFF321D08CEE40019A709 /* SWBQRCodeWindowController.m in Sources */, 9B3FFF211D08826E0019A709 /* PACUtils.swift in Sources */, 9B3FFF141D0705810019A709 /* Notifications.swift in Sources */, 9BEEF0701D04DDB100FC52B3 /* ServerProfileManager.swift in Sources */, + 8EE2EDD8214F7CEC00FB4562 /* AppleScriptUserProxy.swift in Sources */, 9BEEF06E1D04DCE400FC52B3 /* ServerProfile.swift in Sources */, 9B3FFF0D1D05FEB30019A709 /* Utils.swift in Sources */, 9BEEF0751D04EF3E00FC52B3 /* PreferencesWindowController.swift in Sources */, diff --git a/ShadowsocksX-NG/AppDelegate.swift b/ShadowsocksX-NG/AppDelegate.swift index 57096620..3d89131c 100755 --- a/ShadowsocksX-NG/AppDelegate.swift +++ b/ShadowsocksX-NG/AppDelegate.swift @@ -40,12 +40,13 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserNotificationCenterDele @IBOutlet weak var lanchAtLoginMenuItem: NSMenuItem! + @IBOutlet weak var hudWindow: NSPanel! @IBOutlet weak var panelView: NSView! @IBOutlet weak var isNameTextField: NSTextField! - + let kProfileMenuItemIndexBase = 100 - + var statusItem: NSStatusItem! static let StatusItemIconWidth: CGFloat = NSStatusItem.variableLength @@ -133,7 +134,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserNotificationCenterDele self.updateServersMenu() self.updateRunningModeMenu() SyncSSLocal() - } + } ) _ = notifyCenter.rx.notification(NOTIFY_TOGGLE_RUNNING_SHORTCUT) .subscribe(onNext: { noti in @@ -180,9 +181,12 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserNotificationCenterDele ProxyConfHelper.install() ProxyConfHelper.startMonitorPAC() applyConfig() - + // Register global hotkey ShortcutsController.bindShortcuts() + + // Start API Server + HTTPUserProxy.shard.start() } func applicationWillTerminate(_ aNotification: Notification) { @@ -191,7 +195,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserNotificationCenterDele StopPrivoxy() ProxyConfHelper.disableProxy() } - + func applyConfig() { SyncSSLocal() @@ -211,7 +215,21 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserNotificationCenterDele ProxyConfHelper.disableProxy() } } - + + func changeMode(mode:String!) { + let defaults = UserDefaults.standard + + switch mode{ + case "auto":defaults.setValue("auto", forKey: "ShadowsocksRunningMode") + case "global":defaults.setValue("global", forKey: "ShadowsocksRunningMode") + case "manual":defaults.setValue("manual", forKey: "ShadowsocksRunningMode") + default: fatalError() + } + + updateRunningModeMenu() + applyConfig() + } + // MARK: - UI Methods @IBAction func toggleRunning(_ sender: NSMenuItem) { self.doToggleRunning(showToast: false) @@ -324,26 +342,17 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserNotificationCenterDele ]) } } - + @IBAction func selectPACMode(_ sender: NSMenuItem) { - let defaults = UserDefaults.standard - defaults.setValue("auto", forKey: "ShadowsocksRunningMode") - updateRunningModeMenu() - applyConfig() + changeMode(mode: "auto") } @IBAction func selectGlobalMode(_ sender: NSMenuItem) { - let defaults = UserDefaults.standard - defaults.setValue("global", forKey: "ShadowsocksRunningMode") - updateRunningModeMenu() - applyConfig() + changeMode(mode: "global") } @IBAction func selectManualMode(_ sender: NSMenuItem) { - let defaults = UserDefaults.standard - defaults.setValue("manual", forKey: "ShadowsocksRunningMode") - updateRunningModeMenu() - applyConfig() + changeMode(mode: "manual") } @IBAction func editServerPreferences(_ sender: NSMenuItem) { @@ -369,19 +378,27 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserNotificationCenterDele allInOnePreferencesWinCtrl.window?.makeKeyAndOrderFront(self) } - @IBAction func selectServer(_ sender: NSMenuItem) { - let index = sender.tag - kProfileMenuItemIndexBase + func changeServer(@objc uuid: String) { let spMgr = ServerProfileManager.instance - let newProfile = spMgr.profiles[index] - if newProfile.uuid != spMgr.activeProfileId { - spMgr.setActiveProfiledId(newProfile.uuid) + + if uuid != spMgr.activeProfileId { + spMgr.setActiveProfiledId(uuid) updateServersMenu() SyncSSLocal() applyConfig() } + updateRunningModeMenu() } + @IBAction func selectServer(_ sender: NSMenuItem) { + let index = sender.tag - kProfileMenuItemIndexBase + let spMgr = ServerProfileManager.instance + let newProfileId = spMgr.profiles[index].uuid + + changeServer(uuid:newProfileId) + } + @IBAction func copyExportCommand(_ sender: NSMenuItem) { // Get the Http proxy config. let defaults = UserDefaults.standard @@ -427,7 +444,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserNotificationCenterDele func updateRunningModeMenu() { let defaults = UserDefaults.standard - let mode = defaults.string(forKey: "ShadowsocksRunningMode") + let mode = defaults.string(forKey: "ShadowsocksRunningMosde") var serverMenuText = "Servers".localized @@ -468,12 +485,12 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserNotificationCenterDele if isOn { if let m = mode { switch m { - case "auto": - statusItem.image = NSImage(named: NSImage.Name(rawValue: "menu_p_icon")) - case "global": - statusItem.image = NSImage(named: NSImage.Name(rawValue: "menu_g_icon")) - case "manual": - statusItem.image = NSImage(named: NSImage.Name(rawValue: "menu_m_icon")) + case "auto": + statusItem.image = NSImage(named: NSImage.Name(rawValue: "menu_p_icon")) + case "global": + statusItem.image = NSImage(named: NSImage.Name(rawValue: "menu_g_icon")) + case "manual": + statusItem.image = NSImage(named: NSImage.Name(rawValue: "menu_m_icon")) default: break } statusItem.image?.isTemplate = true @@ -512,9 +529,9 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserNotificationCenterDele func updateServersMenu() { guard let menu = serversMenuItem.submenu else { return } + let mgr = ServerProfileManager.instance let profiles = mgr.profiles - // Remove all profile menu items let beginIndex = menu.index(of: serverProfilesBeginSeparatorMenuItem) + 1 let endIndex = menu.index(of: serverProfilesEndSeparatorMenuItem) @@ -522,7 +539,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserNotificationCenterDele for index in (beginIndex.. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +  + + diff --git a/ShadowsocksX-NG/AppleScriptUserProxy.swift b/ShadowsocksX-NG/AppleScriptUserProxy.swift new file mode 100644 index 00000000..0890a1ac --- /dev/null +++ b/ShadowsocksX-NG/AppleScriptUserProxy.swift @@ -0,0 +1,72 @@ +// +// AppleScriptCommand.swift +// ShadowsocksX-NG +// +// Created by melonEater on 2018/9/6. +// Copyright © 2018 qiuyuzhou. All rights reserved. +// + +import Cocoa + + +class AppleScriptUserProxy: NSScriptCommand { + let appdeleget = NSApplication.shared.delegate as! AppDelegate + let SerMgr = ServerProfileManager.instance + + override func performDefaultImplementation() -> Any? { + switch(self.commandDescription.commandName) { + case "isRunning": + return isRunning() + case "toggle": + toggle() + case "mode": + return getMode() + case "change mode": + changeMode(mode: self.directParameter as! String) + case "servers": + return getServerList(); + case "change server": + setServer(remark: self.directParameter as! String) + default: + return nil; + } + return nil + } + + func toggle() { + self.appdeleget.doToggleRunning(showToast: false) + } + + func isRunning() -> Bool { + let isOn = UserDefaults.standard.bool(forKey: "ShadowsocksOn") + return isOn + } + + func getMode() -> String { + return UserDefaults.standard.string(forKey: "ShadowsocksRunningMode") as! String + } + + func changeMode(mode:String) { + appdeleget.changeMode(mode: mode) + } + + func getServerList() -> [String] { + var data = [String]() + + for each in self.SerMgr.profiles{ + data.append(each.remark) + } + + return data + } + + func setServer(remark: String) { + for each in self.SerMgr.profiles{ + if (each.remark == remark) { + self.appdeleget.changeServer(uuid: each.uuid) + return + } + } + } +} + diff --git a/ShadowsocksX-NG/HTTPUserProxy.swift b/ShadowsocksX-NG/HTTPUserProxy.swift new file mode 100644 index 00000000..5945a995 --- /dev/null +++ b/ShadowsocksX-NG/HTTPUserProxy.swift @@ -0,0 +1,88 @@ +// +// ApiServer.swift +// ShadowsocksX-R +// +// Created by CYC on 2016/10/9. +// Copyright © 2016年 qiuyuzhou. All rights reserved. +// + +import Foundation +import GCDWebServer + + + +class HTTPUserProxy{ + static let shard = HTTPUserProxy() + + let apiserver = GCDWebServer() + let SerMgr = ServerProfileManager.instance + let defaults = UserDefaults.standard + let appdeleget = NSApplication.shared.delegate as! AppDelegate + let api_port:UInt = 9528 + + func start(){ + setRouter() + do{ + try apiserver.start(options: [GCDWebServerOption_Port:api_port,"BindToLocalhost":true]) + }catch{ + NSLog("Error:ApiServ start fail") + } + } + + func setRouter(){ + apiserver.addHandler(forMethod: "GET", path: "/status", request: GCDWebServerRequest.self, processBlock: {request in + let isOn = self.defaults.bool(forKey: "ShadowsocksOn") + return GCDWebServerDataResponse(jsonObject: ["enable":isOn], contentType: "json") + }) + + apiserver.addHandler(forMethod: "POST", path: "/toggle", request: GCDWebServerRequest.self, processBlock: {request in + self.appdeleget.doToggleRunning(showToast: false) + return GCDWebServerDataResponse(jsonObject: ["status":1], contentType: "json") + }) + + apiserver.addHandler(forMethod: "GET", path: "/servers", request: GCDWebServerRequest.self, processBlock: {request in + + var data = [[String:Any]]() + + for each in self.SerMgr.profiles{ + data.append(["id":each.uuid,"remark":each.remark, + "active":self.SerMgr.activeProfileId == each.uuid ? 1 : 0]) + } + + return GCDWebServerDataResponse(jsonObject: data, contentType: "json") + }) + + apiserver.addHandler(forMethod: "POST", path: "/servers", request: GCDWebServerURLEncodedFormRequest.self, processBlock: {request in + + let uuid = ((request as! GCDWebServerURLEncodedFormRequest).arguments["id"])as? String + for each in self.SerMgr.profiles{ + if (each.uuid == uuid) { + self.appdeleget.changeServer(uuid: uuid!) + return GCDWebServerDataResponse(jsonObject: ["status":1], contentType: "json") + + } + } + return GCDWebServerDataResponse(jsonObject: ["status":0], contentType: "json") + }) + + + apiserver.addHandler(forMethod: "GET", path: "/mode", request: GCDWebServerRequest.self, processBlock: {request in + if let current = self.defaults.string(forKey: "ShadowsocksRunningMode"){ + return GCDWebServerDataResponse(jsonObject: ["mode":current], contentType: "json") + } + return GCDWebServerDataResponse(jsonObject: ["mode":"unknow"], contentType: "json") + }) + + apiserver.addHandler(forMethod: "POST", path: "/mode", request: GCDWebServerURLEncodedFormRequest.self, processBlock: {request in + let arg = ((request as! GCDWebServerURLEncodedFormRequest).arguments["mode"])as? String + + if (arg != "auto" && arg != "global" && arg != "manual") { + return GCDWebServerDataResponse(jsonObject: ["status":0], contentType: "json") + } + + self.appdeleget.changeMode(mode: arg!) + + return GCDWebServerDataResponse(jsonObject: ["status":1], contentType: "json") + }) + } +} diff --git a/ShadowsocksX-NG/Info.plist b/ShadowsocksX-NG/Info.plist index c48f1b80..05adbfc0 100644 --- a/ShadowsocksX-NG/Info.plist +++ b/ShadowsocksX-NG/Info.plist @@ -2,6 +2,10 @@ + OSAScriptingDefinition + AppleScriptDefinition.sdef + NSAppleScriptEnabled + CFBundleDevelopmentRegion en CFBundleExecutable