fix: should request location permission on macOS14

This commit is contained in:
yicheng 2023-10-14 19:33:05 +08:00
parent ec117e5055
commit 6a2b7274de
12 changed files with 190 additions and 54 deletions

View File

@ -100,6 +100,7 @@
49D767742A6195C800830333 /* ConnectionsReq.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49D767732A6195C800830333 /* ConnectionsReq.swift */; };
49D767762A6195E600830333 /* StructedLogReq.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49D767752A6195E600830333 /* StructedLogReq.swift */; };
49D84AD32A56E9760074CCDB /* ConnectionStatusIconCellView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49D84AD22A56E9760074CCDB /* ConnectionStatusIconCellView.swift */; };
49FEC6692AD9369C00BAD9F5 /* Command.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49FEC6682AD9369C00BAD9F5 /* Command.swift */; };
8A2BBEA727A03ACB0081EBEF /* ProxySetting.sdef in Resources */ = {isa = PBXBuildFile; fileRef = 8A2BBEA627A03ACB0081EBEF /* ProxySetting.sdef */; };
8ACD21BB27A04C7800BC4632 /* ProxySettingCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8ACD21BA27A04C7800BC4632 /* ProxySettingCommand.swift */; };
8ACD21BD27A04ED500BC4632 /* ProxyModeChangeCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8ACD21BC27A04ED500BC4632 /* ProxyModeChangeCommand.swift */; };
@ -269,6 +270,7 @@
49D8276627E9B01700159D93 /* LoginKitWrapper.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = LoginKitWrapper.h; sourceTree = "<group>"; };
49D8276727E9B01700159D93 /* LoginKitWrapper.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = LoginKitWrapper.m; sourceTree = "<group>"; };
49D84AD22A56E9760074CCDB /* ConnectionStatusIconCellView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectionStatusIconCellView.swift; sourceTree = "<group>"; };
49FEC6682AD9369C00BAD9F5 /* Command.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Command.swift; sourceTree = "<group>"; };
5217C006C5A22A1CEA24BFC1 /* Pods-ClashX.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ClashX.debug.xcconfig"; path = "Pods/Target Support Files/Pods-ClashX/Pods-ClashX.debug.xcconfig"; sourceTree = "<group>"; };
8A2BBEA627A03ACB0081EBEF /* ProxySetting.sdef */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = ProxySetting.sdef; sourceTree = "<group>"; };
8ACD21BA27A04C7800BC4632 /* ProxySettingCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProxySettingCommand.swift; sourceTree = "<group>"; };
@ -361,6 +363,7 @@
49D176A62355FE680093DD7B /* NetworkChangeNotifier.swift */,
49B445152457CDF000B27E3E /* ClashStatusTool.swift */,
49D223382A1DA5F10002FFCB /* SSIDSuspendTool.swift */,
49FEC6682AD9369C00BAD9F5 /* Command.swift */,
);
path = Utils;
sourceTree = "<group>";
@ -962,6 +965,7 @@
49722FF0211F338B00650A41 /* EventStream.swift in Sources */,
499A486522EEA3FD00F6C675 /* Array+Safe.swift in Sources */,
F92D0B24236BC12000575E15 /* SavedProxyModel.swift in Sources */,
49FEC6692AD9369C00BAD9F5 /* Command.swift in Sources */,
F92D0B2A236C759100575E15 /* NSTextField+Vibrancy.swift in Sources */,
49CCDA302A54FC3300FF1E13 /* ConnectionLeftPannelView.swift in Sources */,
49D223392A1DA5F10002FFCB /* SSIDSuspendTool.swift in Sources */,

View File

@ -41,8 +41,8 @@ class Logger {
}
}
static func log(_ msg: String, level: ClashLogLevel = .info, function: String = #function) {
shared.logToFile(msg: "[\(level.rawValue)] \(function) \(msg)", level: level)
static func log(_ msg: String, level: ClashLogLevel = .info, file: String = #file, function: String = #function) {
shared.logToFile(msg: "[\(level.rawValue)] \(file) \(function) \(msg)", level: level)
}
func logFilePath() -> String {

View File

@ -0,0 +1,34 @@
//
// Command.swift
// ClashX
//
// Created by yicheng on 2023/10/13.
// Copyright © 2023 west2online. All rights reserved.
//
import Foundation
struct Command {
let cmd: String
let args: [String]
func run() -> String {
var output = ""
let task = Process()
task.launchPath = cmd
task.arguments = args
let outpipe = Pipe()
task.standardOutput = outpipe
task.launch()
task.waitUntilExit()
let outdata = outpipe.fileHandleForReading.readDataToEndOfFile()
if var string = String(data: outdata, encoding: .utf8) {
output = string.trimmingCharacters(in: .newlines)
}
return output
}
}

View File

@ -198,8 +198,4 @@ class NetworkChangeNotifier {
}
return allowIPV6 ? ipv6 : nil
}
static func getCurrentSSID() -> String? {
return CWWiFiClient.shared().interface()?.ssid()
}
}

View File

@ -6,24 +6,47 @@
// Copyright © 2023 west2online. All rights reserved.
//
import CoreLocation
import CoreWLAN
import Foundation
import RxCocoa
import RxSwift
class SSIDSuspendTool {
class SSIDSuspendTool: NSObject {
static let shared = SSIDSuspendTool()
var disposeBag = DisposeBag()
func setup() {
NotificationCenter
.default
.rx
.notification(.systemNetworkStatusDidChange)
.observe(on: MainScheduler.instance)
.delay(.seconds(2), scheduler: MainScheduler.instance)
.bind { [weak self] _ in
self?.update()
}.disposed(by: disposeBag)
private var ssidChangePublisher = PublishSubject<String>()
private var disposeBag = DisposeBag()
private lazy var locationManager = CLLocationManager()
var showNoticeOnNotPermission = false
func setup() {
if AppVersionUtil.hasVersionChanged {
showNoticeOnNotPermission = true
}
requestPermissionIfNeed()
do {
try CWWiFiClient.shared().startMonitoringEvent(with: .ssidDidChange)
CWWiFiClient.shared().delegate = self
ssidChangePublisher
.observe(on: MainScheduler.instance)
.debounce(.seconds(1), scheduler: MainScheduler.instance)
.delay(.seconds(1), scheduler: MainScheduler.instance)
.bind { [weak self] _ in
self?.update()
}.disposed(by: disposeBag)
} catch let err {
Logger.log(String(describing: err), level: .warning)
NotificationCenter
.default
.rx
.notification(.systemNetworkStatusDidChange)
.observe(on: MainScheduler.instance)
.delay(.seconds(2), scheduler: MainScheduler.instance)
.bind { [weak self] _ in
self?.update()
}.disposed(by: disposeBag)
}
ConfigManager.shared
.proxyShouldPaused
.asObservable()
@ -40,6 +63,27 @@ class SSIDSuspendTool {
update()
}
func requestPermissionIfNeed() {
defer {
showNoticeOnNotPermission = false
}
if #available(macOS 14, *) {
if Settings.disableSSIDList.isEmpty { return }
if locationManager.authorizationStatus == .notDetermined {
Logger.log("request location permission")
locationManager.desiredAccuracy = kCLLocationAccuracyReduced
locationManager.delegate = self
locationManager.requestAlwaysAuthorization()
} else if locationManager.authorizationStatus != .authorized {
if showNoticeOnNotPermission {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
self.openLocationSettings()
}
}
}
}
}
func update() {
if shouldSuspend() {
ConfigManager.shared.proxyShouldPaused.accept(true)
@ -49,10 +93,52 @@ class SSIDSuspendTool {
}
func shouldSuspend() -> Bool {
if let currentSSID = NetworkChangeNotifier.getCurrentSSID() {
if let currentSSID = getCurrentSSID() {
return Settings.disableSSIDList.contains(currentSSID)
} else {
return false
}
}
private func getCurrentSSID() -> String? {
if #available(macOS 14, *) {
if locationManager.authorizationStatus != .authorized {
let info = Command(cmd: "/System/Library/PrivateFrameworks/Apple80211.framework/Resources/airport", args: ["-I"]).run()
let ssid = info.components(separatedBy: "\n")
.lazy
.map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }
.first { $0.starts(with: "SSID:") }?
.components(separatedBy: ":")
.last?.trimmingCharacters(in: .whitespacesAndNewlines)
return ssid
}
}
return CWWiFiClient.shared().interface()?.ssid()
}
private func openLocationSettings() {
NSWorkspace.shared.open(URL(string: "x-apple.systempreferences:com.apple.preference.security?Privacy_Location")!)
NSApp.activate(ignoringOtherApps: true)
NSAlert.alert(with: NSLocalizedString("Please enable the location service for ClashX to detect your current WiFi network's SSID name and provide the auto-suspend services.", comment: ""))
}
}
extension SSIDSuspendTool: CLLocationManagerDelegate {
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
Logger.log("Location status: \(status.rawValue)")
if status != .authorized, showNoticeOnNotPermission {
openLocationSettings()
}
showNoticeOnNotPermission = false
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {}
}
extension SSIDSuspendTool: CWEventDelegate {
func ssidDidChangeForWiFiInterface(withName interfaceName: String) {
ssidChangePublisher.onNext(interfaceName)
}
}

View File

@ -2,6 +2,10 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>ClashX use location info to detect your current WiFi network SSID name and provide the auto suspend services.</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>ClashX use location info to detect your current WiFi network SSID name and provide the auto suspend services.</string>
<key>BETA</key>
<true/>
<key>CFBundleDevelopmentRegion</key>

View File

@ -172,6 +172,9 @@
/* No comment provided by engineer. */
"Open System Login Item Setting" = "Open System Login Item Setting";
/* No comment provided by engineer. */
"Please enable the location service for ClashX to detect your current WiFi network's SSID name and provide the auto-suspend services." = "Please enable the location service for ClashX to detect your current WiFi network's SSID name and provide the auto-suspend services.";
/* No comment provided by engineer. */
"Ports Open Fail, Please try to restart ClashX" = "Ports Open Fail, Please try to restart ClashX";

View File

@ -172,6 +172,9 @@
/* No comment provided by engineer. */
"Open System Login Item Setting" = "打开系统登录项设置";
/* No comment provided by engineer. */
"Please enable the location service for ClashX to detect your current WiFi network's SSID name and provide the auto-suspend services." = "请允许ClashX使用定位服务来获取当前所连接的WiFi名称从而提供按需暂停服务。";
/* No comment provided by engineer. */
"Ports Open Fail, Please try to restart ClashX" = "端口打开失败请尝试重启ClashX";

View File

@ -172,6 +172,9 @@
/* No comment provided by engineer. */
"Open System Login Item Setting" = "打開系統登錄項設定";
/* No comment provided by engineer. */
"Please enable the location service for ClashX to detect your current WiFi network's SSID name and provide the auto-suspend services." = "請允許 ClashX 使用定位服務,以獲取您目前連接的 WiFi 名稱,並提供按需暫停服務。";
/* No comment provided by engineer. */
"Ports Open Fail, Please try to restart ClashX" = "端口打開失敗請嘗試重啟ClashX";

View File

@ -127,6 +127,9 @@ class GeneralSettingViewController: NSViewController {
if url.isUrlVaild() || url.isEmpty {
Settings.benchMarkUrl = url
}
SSIDSuspendTool.shared.showNoticeOnNotPermission = true
SSIDSuspendTool.shared.requestPermissionIfNeed()
SSIDSuspendTool.shared.update()
}
@IBAction func actionResetIgnoreList(_ sender: Any) {

View File

@ -6,6 +6,6 @@ source "https://rubygems.org"
gem 'fastlane'
gem 'cocoapods'
gem "activesupport", "= 7.0.8"
plugins_path = File.join(File.dirname(__FILE__), 'fastlane', 'Pluginfile')
eval_gemfile(plugins_path) if File.exist?(plugins_path)

View File

@ -3,12 +3,12 @@ GEM
specs:
CFPropertyList (3.0.6)
rexml
activesupport (7.0.5)
activesupport (7.0.8)
concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 1.6, < 2)
minitest (>= 5.1)
tzinfo (~> 2.0)
addressable (2.8.4)
addressable (2.8.5)
public_suffix (>= 2.0.2, < 6.0)
algoliasearch (1.27.5)
httpclient (~> 2.8, >= 2.8.3)
@ -16,27 +16,27 @@ GEM
artifactory (3.0.15)
atomos (0.1.3)
aws-eventstream (1.2.0)
aws-partitions (1.779.0)
aws-sdk-core (3.174.0)
aws-partitions (1.835.0)
aws-sdk-core (3.185.1)
aws-eventstream (~> 1, >= 1.0.2)
aws-partitions (~> 1, >= 1.651.0)
aws-sigv4 (~> 1.5)
jmespath (~> 1, >= 1.6.1)
aws-sdk-kms (1.66.0)
aws-sdk-core (~> 3, >= 3.174.0)
aws-sdk-kms (1.72.0)
aws-sdk-core (~> 3, >= 3.184.0)
aws-sigv4 (~> 1.1)
aws-sdk-s3 (1.124.0)
aws-sdk-core (~> 3, >= 3.174.0)
aws-sdk-s3 (1.136.0)
aws-sdk-core (~> 3, >= 3.181.0)
aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.4)
aws-sigv4 (1.5.2)
aws-sigv4 (~> 1.6)
aws-sigv4 (1.6.0)
aws-eventstream (~> 1, >= 1.0.2)
babosa (1.0.4)
claide (1.1.0)
cocoapods (1.12.1)
cocoapods (1.13.0)
addressable (~> 2.8)
claide (>= 1.0.2, < 2.0)
cocoapods-core (= 1.12.1)
cocoapods-core (= 1.13.0)
cocoapods-deintegrate (>= 1.0.3, < 2.0)
cocoapods-downloader (>= 1.6.0, < 2.0)
cocoapods-plugins (>= 1.0.0, < 2.0)
@ -50,8 +50,8 @@ GEM
molinillo (~> 0.8.0)
nap (~> 1.0)
ruby-macho (>= 2.3.0, < 3.0)
xcodeproj (>= 1.21.0, < 2.0)
cocoapods-core (1.12.1)
xcodeproj (>= 1.23.0, < 2.0)
cocoapods-core (1.13.0)
activesupport (>= 5.0, < 8)
addressable (~> 2.8)
algoliasearch (~> 1.0)
@ -76,7 +76,7 @@ GEM
highline (~> 2.0.0)
concurrent-ruby (1.2.2)
declarative (0.0.20)
digest-crc (0.6.4)
digest-crc (0.6.5)
rake (>= 12.0.0, < 14.0.0)
domain_name (0.5.20190701)
unf (>= 0.0.5, < 1.0.0)
@ -85,7 +85,7 @@ GEM
escape (0.0.4)
ethon (0.16.0)
ffi (>= 1.15.0)
excon (0.100.0)
excon (0.104.0)
faraday (1.10.3)
faraday-em_http (~> 1.0)
faraday-em_synchrony (~> 1.0)
@ -115,7 +115,7 @@ GEM
faraday_middleware (1.2.0)
faraday (~> 1.0)
fastimage (2.2.7)
fastlane (2.213.0)
fastlane (2.216.0)
CFPropertyList (>= 2.3, < 4.0.0)
addressable (>= 2.8, < 3.0.0)
artifactory (~> 3.0)
@ -136,6 +136,7 @@ GEM
google-apis-playcustomapp_v1 (~> 0.1)
google-cloud-storage (~> 1.31)
highline (~> 2.0)
http-cookie (~> 1.0.5)
json (< 3.0.0)
jwt (>= 2.1.0, < 3)
mini_magick (>= 4.9.4, < 5.0.0)
@ -147,23 +148,23 @@ GEM
security (= 0.1.3)
simctl (~> 1.6.3)
terminal-notifier (>= 2.0.0, < 3.0.0)
terminal-table (>= 1.4.5, < 2.0.0)
terminal-table (~> 3)
tty-screen (>= 0.6.3, < 1.0.0)
tty-spinner (>= 0.8.0, < 1.0.0)
word_wrap (~> 1.0.0)
xcodeproj (>= 1.13.0, < 2.0.0)
xcpretty (~> 0.3.0)
xcpretty-travis-formatter (>= 0.0.3)
fastlane-plugin-appcenter (2.1.0)
fastlane-plugin-appcenter (2.1.1)
fastlane-plugin-update_xcodeproj (1.0.1)
fastlane-plugin-versioning (0.5.1)
ffi (1.15.5)
fastlane-plugin-versioning (0.5.2)
ffi (1.16.3)
fourflusher (2.3.1)
fuzzy_match (2.0.4)
gh_inspector (1.1.3)
google-apis-androidpublisher_v3 (0.43.0)
google-apis-androidpublisher_v3 (0.50.0)
google-apis-core (>= 0.11.0, < 2.a)
google-apis-core (0.11.0)
google-apis-core (0.11.1)
addressable (~> 2.5, >= 2.5.1)
googleauth (>= 0.16.2, < 2.a)
httpclient (>= 2.8.1, < 3.a)
@ -192,10 +193,9 @@ GEM
google-cloud-core (~> 1.6)
googleauth (>= 0.16.2, < 2.a)
mini_mime (~> 1.0)
googleauth (1.5.2)
googleauth (1.8.1)
faraday (>= 0.17.3, < 3.a)
jwt (>= 1.4, < 3.0)
memoist (~> 0.16)
multi_json (~> 1.11)
os (>= 0.9, < 2.0)
signet (>= 0.16, < 2.a)
@ -208,10 +208,9 @@ GEM
jmespath (1.6.2)
json (2.6.3)
jwt (2.7.1)
memoist (0.16.2)
mini_magick (4.12.0)
mini_mime (1.1.2)
minitest (5.18.0)
mini_mime (1.1.5)
minitest (5.20.0)
molinillo (0.8.0)
multi_json (1.15.0)
multipart-post (2.3.0)
@ -229,13 +228,13 @@ GEM
trailblazer-option (>= 0.1.1, < 0.2.0)
uber (< 0.2.0)
retriable (3.1.2)
rexml (3.2.5)
rexml (3.2.6)
rouge (2.0.7)
ruby-macho (2.5.1)
ruby2_keywords (0.0.5)
rubyzip (2.3.2)
security (0.1.3)
signet (0.17.0)
signet (0.18.0)
addressable (~> 2.8)
faraday (>= 0.17.5, < 3.a)
jwt (>= 1.5, < 3.0)
@ -244,8 +243,8 @@ GEM
CFPropertyList
naturally
terminal-notifier (2.0.0)
terminal-table (1.8.0)
unicode-display_width (~> 1.1, >= 1.1.1)
terminal-table (3.0.2)
unicode-display_width (>= 1.1.1, < 3)
trailblazer-option (0.1.2)
tty-cursor (0.7.1)
tty-screen (0.8.1)
@ -259,10 +258,10 @@ GEM
unf (0.1.4)
unf_ext
unf_ext (0.0.8.2)
unicode-display_width (1.8.0)
unicode-display_width (2.5.0)
webrick (1.8.1)
word_wrap (1.0.0)
xcodeproj (1.22.0)
xcodeproj (1.23.0)
CFPropertyList (>= 2.3.3, < 4.0)
atomos (~> 0.1.3)
claide (>= 1.0.2, < 2.0)
@ -278,6 +277,7 @@ PLATFORMS
ruby
DEPENDENCIES
activesupport (= 7.0.8)
cocoapods
fastlane
fastlane-plugin-appcenter