diff --git a/README.md b/README.md
index dee24b9..e0bc131 100644
--- a/README.md
+++ b/README.md
@@ -57,6 +57,8 @@ So after you quit the app, the ss-local maybe be still running.
Added a manual mode which won't configure the system proxy settings.
Then you could configure your apps to use socks5 proxy manual.
+Added global Keyboard shortcut ⌃ + ⌘ + P to switch between `global` mode and `auto` mode.
+
## Contributing
Contributions must be available on a separately named branch based on the latest version of the main branch develop.
diff --git a/ShadowsocksX-NG.xcodeproj/project.pbxproj b/ShadowsocksX-NG.xcodeproj/project.pbxproj
index 2a78063..163bbce 100755
--- a/ShadowsocksX-NG.xcodeproj/project.pbxproj
+++ b/ShadowsocksX-NG.xcodeproj/project.pbxproj
@@ -427,7 +427,7 @@
isa = PBXProject;
attributes = {
LastSwiftUpdateCheck = 0730;
- LastUpgradeCheck = 0730;
+ LastUpgradeCheck = 0810;
ORGANIZATIONNAME = qiuyuzhou;
TargetAttributes = {
9B0BFFE41D0460A70040E62B = {
@@ -764,6 +764,7 @@
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
+ CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
@@ -774,8 +775,10 @@
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_IDENTITY = "Mac Developer";
@@ -809,6 +812,7 @@
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
+ CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
@@ -819,8 +823,10 @@
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_IDENTITY = "Mac Developer";
@@ -839,6 +845,7 @@
MACOSX_DEPLOYMENT_TARGET = 10.11;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = macosx;
+ SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
};
name = Release;
};
@@ -846,6 +853,7 @@
isa = XCBuildConfiguration;
baseConfigurationReference = FE3237E9FB24D9B924A0E630 /* Pods-ShadowsocksX-NG.debug.xcconfig */;
buildSettings = {
+ ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
COMBINE_HIDPI_IMAGES = YES;
@@ -873,6 +881,7 @@
isa = XCBuildConfiguration;
baseConfigurationReference = E9E9FB3855DA55D0710EE7BD /* Pods-ShadowsocksX-NG.release.xcconfig */;
buildSettings = {
+ ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
COMBINE_HIDPI_IMAGES = YES;
diff --git a/ShadowsocksX-NG.xcodeproj/xcshareddata/xcschemes/ShadowsocksX-NG.xcscheme b/ShadowsocksX-NG.xcodeproj/xcshareddata/xcschemes/ShadowsocksX-NG.xcscheme
index 19af2fe..35ff62e 100644
--- a/ShadowsocksX-NG.xcodeproj/xcshareddata/xcschemes/ShadowsocksX-NG.xcscheme
+++ b/ShadowsocksX-NG.xcodeproj/xcshareddata/xcschemes/ShadowsocksX-NG.xcscheme
@@ -1,6 +1,6 @@
Void {
+ var gMyHotKeyID = EventHotKeyID()
+ gMyHotKeyID.signature = OSType(fourCharCodeFrom(string: "sxng"))
+ gMyHotKeyID.id = UInt32(keyCode)
+
+ var eventType = EventTypeSpec()
+ eventType.eventClass = OSType(kEventClassKeyboard)
+ eventType.eventKind = OSType(kEventHotKeyPressed)
+
+ // Void pointer to `self`:
+ let context = Unmanaged.passUnretained(self).toOpaque()
+
+ // Install handler.
+ InstallEventHandler(GetApplicationEventTarget(), {(nextHanlder, theEvent, userContext) -> OSStatus in
+ // Extract pointer to `self` from void pointer:
+ let mySelf = Unmanaged.fromOpaque(userContext!).takeUnretainedValue()
+
+ switch Globals.proxyType {
+ case .pac:
+ Globals.proxyType = .global
+ UserDefaults.standard.setValue("global", forKey: "ShadowsocksRunningMode")
+ mySelf.isNameTextField.stringValue = "Gobal Mode"
+ mySelf.updateRunningModeMenu()
+ mySelf.applyConfig()
+ case .global:
+ Globals.proxyType = .pac
+ UserDefaults.standard.setValue("auto", forKey: "ShadowsocksRunningMode")
+ mySelf.isNameTextField.stringValue = "Auto Mode"
+ mySelf.updateRunningModeMenu()
+ mySelf.applyConfig()
+ }
+
+ mySelf.fadeInHud()
+
+ return noErr
+ }, 1, &eventType, context, nil)
+
+ // Register hotkey.
+ RegisterEventHotKey(UInt32(keyCode),
+ UInt32(modifierKeys),
+ gMyHotKeyID,
+ GetApplicationEventTarget(),
+ 0,
+ &hotKeyRef)
+ }
+
+ func fourCharCodeFrom(string: String) -> FourCharCode {
+ assert(string.characters.count == 4, "String length must be 4")
+ var result: FourCharCode = 0
+ for char in string.utf16 {
+ result = (result << 8) + FourCharCode(char)
+ }
+ return result
+ }
+
+ // MARK: - UI Methods
@IBAction func toggleRunning(_ sender: NSMenuItem) {
let defaults = UserDefaults.standard
var isOn = defaults.bool(forKey: "ShadowsocksOn")
@@ -510,3 +591,93 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserNotificationCenterDele
}
}
+extension AppDelegate {
+ func fadeInHud() -> Void {
+ if timerToFadeOut != nil {
+ timerToFadeOut?.invalidate()
+ timerToFadeOut = nil
+ }
+
+ fadingOut = false
+
+ hudWindow.orderFrontRegardless()
+
+ CATransaction.begin()
+ CATransaction.setAnimationDuration(kHudFadeInDuration)
+ CATransaction.setCompletionBlock { self.didFadeIn() }
+ panelView.layer?.opacity = 1.0
+ CATransaction.commit()
+ }
+
+ func didFadeIn() -> Void {
+ timerToFadeOut = Timer.scheduledTimer(
+ timeInterval: kHudDisplayDuration,
+ target: self,
+ selector: #selector(fadeOutHud),
+ userInfo: nil,
+ repeats: false)
+ }
+
+ func fadeOutHud() -> Void {
+ fadingOut = true
+
+ CATransaction.begin()
+ CATransaction.setAnimationDuration(kHudFadeOutDuration)
+ CATransaction.setCompletionBlock { self.didFadeOut() }
+ panelView.layer?.opacity = 0.0
+ CATransaction.commit()
+ }
+
+ func didFadeOut() -> Void {
+ if fadingOut {
+ self.hudWindow.orderOut(nil)
+ }
+ fadingOut = false
+ }
+
+ func setupHud() -> Void {
+ isNameTextField.stringValue = "Global Mode"
+ isNameTextField.sizeToFit()
+
+ var labelFrame: CGRect = isNameTextField.frame
+ var hudWindowFrame: CGRect = hudWindow.frame
+ hudWindowFrame.size.width = labelFrame.size.width + kHudHorizontalMargin * 2
+ hudWindowFrame.size.height = kHudHeight
+
+ let screenRect: NSRect = NSScreen.screens()![0].visibleFrame
+ hudWindowFrame.origin.x = (screenRect.size.width - hudWindowFrame.size.width) / 2
+ hudWindowFrame.origin.y = (screenRect.size.height - hudWindowFrame.size.height) / 2
+ hudWindow.setFrame(hudWindowFrame, display: true)
+
+ var viewFrame: NSRect = hudWindowFrame;
+ viewFrame.origin.x = 0
+ viewFrame.origin.y = 0
+ panelView.frame = viewFrame
+
+ labelFrame.origin.x = kHudHorizontalMargin
+ labelFrame.origin.y = (hudWindowFrame.size.height - labelFrame.size.height) / 2
+ isNameTextField.frame = labelFrame
+ }
+
+ func initUIComponent() -> Void {
+ hudWindow.isOpaque = false
+ hudWindow.backgroundColor = .clear
+ hudWindow.level = Int(CGWindowLevelForKey(.utilityWindow)) + 1000
+ hudWindow.styleMask = .borderless
+ hudWindow.hidesOnDeactivate = false
+ hudWindow.collectionBehavior = .canJoinAllSpaces
+
+ let viewLayer: CALayer = CALayer()
+ viewLayer.backgroundColor = CGColor.init(red: 0.05, green: 0.05, blue: 0.05, alpha: kHudAlphaValue)
+ viewLayer.cornerRadius = kHudCornerRadius
+ panelView.wantsLayer = true
+ panelView.layer = viewLayer
+ panelView.layer?.opacity = 0.0
+
+ setupHud()
+ }
+
+ override func awakeFromNib() {
+ initUIComponent()
+ }
+}
diff --git a/ShadowsocksX-NG/Base.lproj/MainMenu.xib b/ShadowsocksX-NG/Base.lproj/MainMenu.xib
index be83a01..e16dd52 100755
--- a/ShadowsocksX-NG/Base.lproj/MainMenu.xib
+++ b/ShadowsocksX-NG/Base.lproj/MainMenu.xib
@@ -1,8 +1,9 @@
-
+
-
+
+
@@ -14,11 +15,13 @@
-
+
+
+
@@ -164,7 +167,7 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ShadowsocksX-NG/ServerProfileManager.swift b/ShadowsocksX-NG/ServerProfileManager.swift
index 61b62c7..8de313a 100644
--- a/ShadowsocksX-NG/ServerProfileManager.swift
+++ b/ShadowsocksX-NG/ServerProfileManager.swift
@@ -51,7 +51,7 @@ class ServerProfileManager: NSObject {
if activeProfileId != nil {
defaults.set(activeProfileId, forKey: "ActiveServerProfileId")
- writeSSLocalConfFile((getActiveProfile()?.toJsonConfig())!)
+ _ = writeSSLocalConfFile((getActiveProfile()?.toJsonConfig())!)
} else {
defaults.removeObject(forKey: "ActiveServerProfileId")
removeSSLocalConfFile()
diff --git a/ShadowsocksX-NG/Utils.swift b/ShadowsocksX-NG/Utils.swift
index 74c0f54..280504c 100644
--- a/ShadowsocksX-NG/Utils.swift
+++ b/ShadowsocksX-NG/Utils.swift
@@ -8,14 +8,12 @@
import Foundation
-
extension String {
var localized: String {
return NSLocalizedString(self, tableName: nil, bundle: Bundle.main, value: "", comment: "")
}
}
-
extension Data {
func sha1() -> String {
let data = self
@@ -25,3 +23,12 @@ extension Data {
return hexBytes.joined(separator: "")
}
}
+
+enum ProxyType {
+ case pac
+ case global
+}
+
+struct Globals {
+ static var proxyType = ProxyType.pac
+}
diff --git a/ShadowsocksX-NG/zh-Hans.lproj/Localizable.strings b/ShadowsocksX-NG/zh-Hans.lproj/Localizable.strings
index 10b0b4c..0332c8b 100755
--- a/ShadowsocksX-NG/zh-Hans.lproj/Localizable.strings
+++ b/ShadowsocksX-NG/zh-Hans.lproj/Localizable.strings
@@ -44,9 +44,9 @@
"Turn Shadowsocks On" = "打开 Shadowsocks";
-"Proxy - Auto By PAC" = "代理 - PAC自动";
+"Proxy - Auto By PAC" = "代理 - PAC自动(⌃⌘P)";
-"Proxy - Global" = "代理 - 全局";
+"Proxy - Global" = "代理 - 全局(⌃⌘P)";
"Proxy - Manual" = "代理 - 手动";