From 23967eca20d0b5dae937a5ba368391768a3d4468 Mon Sep 17 00:00:00 2001 From: Vicent Tsai Date: Sun, 11 Dec 2016 15:39:56 +0800 Subject: [PATCH] Added global Keyboard shortcut So we could switch between different proxy modes with global shortcut. --- README.md | 2 + ShadowsocksX-NG.xcodeproj/project.pbxproj | 11 +- .../xcschemes/ShadowsocksX-NG.xcscheme | 2 +- .../xcschemes/ShadowsocksX-NGTests.xcscheme | 2 +- .../xcschemes/proxy_conf_helper.xcscheme | 2 +- ShadowsocksX-NG/AppDelegate.swift | 185 +++++++++++++++++- ShadowsocksX-NG/Base.lproj/MainMenu.xib | 35 +++- ShadowsocksX-NG/ServerProfileManager.swift | 2 +- ShadowsocksX-NG/Utils.swift | 11 +- .../zh-Hans.lproj/Localizable.strings | 4 +- 10 files changed, 236 insertions(+), 20 deletions(-) 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 @@ - + @@ -178,6 +181,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + 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" = "代理 - 手动";