Feature: Use apple prefer way to run privileged task
This commit is contained in:
parent
a6e09a625e
commit
b42ba7123f
|
@ -5,3 +5,4 @@ ClashX.h
|
|||
ClashX/Resources/dashboard
|
||||
ClashX.app
|
||||
*.dmg
|
||||
**/xcuserdata/
|
||||
|
|
|
@ -44,20 +44,23 @@
|
|||
499A485C22ED793C00F6C675 /* NSView+Nib.swift in Sources */ = {isa = PBXBuildFile; fileRef = 499A485B22ED793C00F6C675 /* NSView+Nib.swift */; };
|
||||
499A485E22ED9B7C00F6C675 /* NSTableView+Reload.swift in Sources */ = {isa = PBXBuildFile; fileRef = 499A485D22ED9B7C00F6C675 /* NSTableView+Reload.swift */; };
|
||||
499A486522EEA3FD00F6C675 /* Array+Safe.swift in Sources */ = {isa = PBXBuildFile; fileRef = 499A486422EEA3FC00F6C675 /* Array+Safe.swift */; };
|
||||
49AF9104225B2B03004CC077 /* ProxyConfig in CopyFiles */ = {isa = PBXBuildFile; fileRef = 49AF9103225B2A96004CC077 /* ProxyConfig */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
|
||||
49B1086A216A356D0064FFCE /* String+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49B10869216A356D0064FFCE /* String+Extension.swift */; };
|
||||
49BC061C212931F4005A0FE7 /* AboutViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49BC061B212931F4005A0FE7 /* AboutViewController.swift */; };
|
||||
49C9EF64223E78F5005D8B6A /* ClashProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49C9EF63223E78F5005D8B6A /* ClashProxy.swift */; };
|
||||
49CF3B2120CD7463001EBF94 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49CF3B2020CD7463001EBF94 /* AppDelegate.swift */; };
|
||||
49CF3B2520CD7465001EBF94 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 49CF3B2420CD7465001EBF94 /* Assets.xcassets */; };
|
||||
49CF3B2820CD7465001EBF94 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 49CF3B2620CD7465001EBF94 /* Main.storyboard */; };
|
||||
49CF3B5C20CE8068001EBF94 /* ProxyConfigHelperManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49CF3B5B20CE8068001EBF94 /* ProxyConfigHelperManager.swift */; };
|
||||
49CF3B5F20CE80D2001EBF94 /* install_proxy_helper.sh in Resources */ = {isa = PBXBuildFile; fileRef = 49CF3B5E20CE80D2001EBF94 /* install_proxy_helper.sh */; };
|
||||
49CF3B6320CED9CF001EBF94 /* check_proxy_helper.sh in Resources */ = {isa = PBXBuildFile; fileRef = 49CF3B6220CED934001EBF94 /* check_proxy_helper.sh */; };
|
||||
49CF3B5C20CE8068001EBF94 /* ClashResourceManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49CF3B5B20CE8068001EBF94 /* ClashResourceManager.swift */; };
|
||||
49CF3B6520CEE06C001EBF94 /* ConfigManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49CF3B6420CEE06C001EBF94 /* ConfigManager.swift */; };
|
||||
49E07A8C20D501A000A088A3 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 49E07A8920D501A000A088A3 /* Main.storyboard */; };
|
||||
49EC8D1922F13F330079B8F4 /* Sparkle.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 49EC8D0F22F13DEE0079B8F4 /* Sparkle.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
49EC8D1A22F13FFC0079B8F4 /* Sparkle.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 49EC8D0F22F13DEE0079B8F4 /* Sparkle.framework */; };
|
||||
F935B2F02307C52E009E4D33 /* com.west2online.ClashX.ProxyConfigHelper in Copy Files */ = {isa = PBXBuildFile; fileRef = F9A7C0692306E874007163C7 /* com.west2online.ClashX.ProxyConfigHelper */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
|
||||
F935B2F42307CD32009E4D33 /* ProxyConfigHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = F935B2F32307CD32009E4D33 /* ProxyConfigHelper.m */; };
|
||||
F935B2FA23083EE6009E4D33 /* ProxySettingTool.m in Sources */ = {isa = PBXBuildFile; fileRef = F935B2F923083EE6009E4D33 /* ProxySettingTool.m */; };
|
||||
F935B2FC23085515009E4D33 /* SystemProxyManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = F935B2FB23085515009E4D33 /* SystemProxyManager.swift */; };
|
||||
F94245092306DABA0005B196 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 49E07A8A20D501A000A088A3 /* Main.storyboard */; };
|
||||
F9A7C06C2306E874007163C7 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = F9A7C06B2306E874007163C7 /* main.m */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
|
@ -68,12 +71,12 @@
|
|||
remoteGlobalIDString = 495A44BB20D2660A00888A0A;
|
||||
remoteInfo = ClashXLaunchHelper;
|
||||
};
|
||||
49AF9102225B2A96004CC077 /* PBXContainerItemProxy */ = {
|
||||
F935B2EB2307B7CD009E4D33 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 49AF90FE225B2A95004CC077 /* ProxyConfig.xcodeproj */;
|
||||
proxyType = 2;
|
||||
remoteGlobalIDString = 49AF90F3225B17D2004CC077;
|
||||
remoteInfo = ProxyConfig;
|
||||
containerPortal = 49CF3B1520CD7463001EBF94 /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = F9A7C0682306E874007163C7;
|
||||
remoteInfo = ProxyConfigHelper;
|
||||
};
|
||||
/* End PBXContainerItemProxy section */
|
||||
|
||||
|
@ -98,16 +101,27 @@
|
|||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
663E4677213FCDC4006F11BB /* CopyFiles */ = {
|
||||
663E4677213FCDC4006F11BB /* Copy Files */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
dstPath = "";
|
||||
dstSubfolderSpec = 7;
|
||||
dstPath = Contents/Library/LaunchServices;
|
||||
dstSubfolderSpec = 1;
|
||||
files = (
|
||||
49AF9104225B2B03004CC077 /* ProxyConfig in CopyFiles */,
|
||||
F935B2F02307C52E009E4D33 /* com.west2online.ClashX.ProxyConfigHelper in Copy Files */,
|
||||
);
|
||||
name = "Copy Files";
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
F9A7C0672306E874007163C7 /* Copy Files */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
dstPath = /usr/share/man/man1/;
|
||||
dstSubfolderSpec = 0;
|
||||
files = (
|
||||
);
|
||||
name = "Copy Files";
|
||||
runOnlyForDeploymentPostprocessing = 1;
|
||||
};
|
||||
/* End PBXCopyFilesBuildPhase section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
|
@ -157,7 +171,6 @@
|
|||
499A485B22ED793C00F6C675 /* NSView+Nib.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSView+Nib.swift"; sourceTree = "<group>"; };
|
||||
499A485D22ED9B7C00F6C675 /* NSTableView+Reload.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSTableView+Reload.swift"; sourceTree = "<group>"; };
|
||||
499A486422EEA3FC00F6C675 /* Array+Safe.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Array+Safe.swift"; sourceTree = "<group>"; };
|
||||
49AF90FE225B2A95004CC077 /* ProxyConfig.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = ProxyConfig.xcodeproj; path = ProxyConfig/ProxyConfig.xcodeproj; sourceTree = "<group>"; };
|
||||
49B10869216A356D0064FFCE /* String+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Extension.swift"; sourceTree = "<group>"; };
|
||||
49BC061B212931F4005A0FE7 /* AboutViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutViewController.swift; sourceTree = "<group>"; };
|
||||
49C9EF63223E78F5005D8B6A /* ClashProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClashProxy.swift; sourceTree = "<group>"; };
|
||||
|
@ -168,14 +181,22 @@
|
|||
49CF3B2920CD7465001EBF94 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
49CF3B2A20CD7465001EBF94 /* ClashX.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = ClashX.entitlements; sourceTree = "<group>"; };
|
||||
49CF3B3520CD75DF001EBF94 /* ClashX-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "ClashX-Bridging-Header.h"; sourceTree = "<group>"; };
|
||||
49CF3B5B20CE8068001EBF94 /* ProxyConfigHelperManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProxyConfigHelperManager.swift; sourceTree = "<group>"; };
|
||||
49CF3B5E20CE80D2001EBF94 /* install_proxy_helper.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; path = install_proxy_helper.sh; sourceTree = "<group>"; };
|
||||
49CF3B6220CED934001EBF94 /* check_proxy_helper.sh */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = check_proxy_helper.sh; sourceTree = "<group>"; };
|
||||
49CF3B5B20CE8068001EBF94 /* ClashResourceManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClashResourceManager.swift; sourceTree = "<group>"; };
|
||||
49CF3B6420CEE06C001EBF94 /* ConfigManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigManager.swift; sourceTree = "<group>"; };
|
||||
49E07A8A20D501A000A088A3 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Main.storyboard; sourceTree = "<group>"; };
|
||||
49E07A8A20D501A000A088A3 /* Main.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Main.storyboard; sourceTree = "<group>"; };
|
||||
49EC8D0F22F13DEE0079B8F4 /* Sparkle.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = Sparkle.framework; 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>"; };
|
||||
A1485BCE642059532D01B8BA /* Pods-ClashX.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ClashX.release.xcconfig"; path = "Pods/Target Support Files/Pods-ClashX/Pods-ClashX.release.xcconfig"; sourceTree = "<group>"; };
|
||||
F935B2EA2307B6BA009E4D33 /* Helper-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "Helper-Info.plist"; sourceTree = "<group>"; };
|
||||
F935B2F12307C802009E4D33 /* Helper-Launchd.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Helper-Launchd.plist"; sourceTree = "<group>"; };
|
||||
F935B2F22307CD32009E4D33 /* ProxyConfigHelper.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ProxyConfigHelper.h; sourceTree = "<group>"; };
|
||||
F935B2F32307CD32009E4D33 /* ProxyConfigHelper.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ProxyConfigHelper.m; sourceTree = "<group>"; };
|
||||
F935B2F52307D00D009E4D33 /* ProxyConfigRemoteProcessProtocol.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ProxyConfigRemoteProcessProtocol.h; sourceTree = "<group>"; };
|
||||
F935B2F823083EE6009E4D33 /* ProxySettingTool.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ProxySettingTool.h; sourceTree = "<group>"; };
|
||||
F935B2F923083EE6009E4D33 /* ProxySettingTool.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ProxySettingTool.m; sourceTree = "<group>"; };
|
||||
F935B2FB23085515009E4D33 /* SystemProxyManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SystemProxyManager.swift; sourceTree = "<group>"; };
|
||||
F9A7C0692306E874007163C7 /* com.west2online.ClashX.ProxyConfigHelper */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = com.west2online.ClashX.ProxyConfigHelper; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
F9A7C06B2306E874007163C7 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
|
@ -196,6 +217,13 @@
|
|||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
F9A7C0662306E874007163C7 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
|
@ -232,10 +260,11 @@
|
|||
isa = PBXGroup;
|
||||
children = (
|
||||
4952C3CF2117027C004A4FA8 /* ConfigFileManager.swift */,
|
||||
49CF3B5B20CE8068001EBF94 /* ProxyConfigHelperManager.swift */,
|
||||
49CF3B5B20CE8068001EBF94 /* ClashResourceManager.swift */,
|
||||
49CF3B6420CEE06C001EBF94 /* ConfigManager.swift */,
|
||||
495BFB8721919B9800C8779D /* RemoteConfigManager.swift */,
|
||||
4952C3BE2115C7CA004A4FA8 /* MenuItemFactory.swift */,
|
||||
F935B2FB23085515009E4D33 /* SystemProxyManager.swift */,
|
||||
);
|
||||
path = Managers;
|
||||
sourceTree = "<group>";
|
||||
|
@ -275,6 +304,7 @@
|
|||
495A44BD20D2660A00888A0A /* ClashXLaunchHelper */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
49E07A8A20D501A000A088A3 /* Main.storyboard */,
|
||||
49E07A8920D501A000A088A3 /* Main.storyboard */,
|
||||
495A44BE20D2660A00888A0A /* AppDelegate.swift */,
|
||||
495A44C720D2660B00888A0A /* Info.plist */,
|
||||
|
@ -306,7 +336,6 @@
|
|||
isa = PBXGroup;
|
||||
children = (
|
||||
49761DA621C9497000AE13EF /* dashboard */,
|
||||
49CF3B5D20CE80D2001EBF94 /* script */,
|
||||
);
|
||||
path = Resources;
|
||||
sourceTree = "<group>";
|
||||
|
@ -334,19 +363,12 @@
|
|||
path = Basic;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
49AF90FF225B2A95004CC077 /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
49AF9103225B2A96004CC077 /* ProxyConfig */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
49CF3B1420CD7463001EBF94 = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
49CF3B1F20CD7463001EBF94 /* ClashX */,
|
||||
495A44BD20D2660A00888A0A /* ClashXLaunchHelper */,
|
||||
F9A7C06A2306E874007163C7 /* ProxyConfigHelper */,
|
||||
49CF3B1E20CD7463001EBF94 /* Products */,
|
||||
76229F122B00E935D126742A /* Pods */,
|
||||
CF1AC9FACC36FCE7663C5583 /* Frameworks */,
|
||||
|
@ -359,6 +381,7 @@
|
|||
children = (
|
||||
49CF3B1D20CD7463001EBF94 /* ClashX.app */,
|
||||
495A44BC20D2660A00888A0A /* ClashXLaunchHelper.app */,
|
||||
F9A7C0692306E874007163C7 /* com.west2online.ClashX.ProxyConfigHelper */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
|
@ -376,7 +399,6 @@
|
|||
4989F98520D0AA300001E564 /* ViewControllers */,
|
||||
49761DA521C9490400AE13EF /* Resources */,
|
||||
49CF3B3A20CD783A001EBF94 /* Support Files */,
|
||||
49AF90FE225B2A95004CC077 /* ProxyConfig.xcodeproj */,
|
||||
49CF3B2020CD7463001EBF94 /* AppDelegate.swift */,
|
||||
49CF3B2420CD7465001EBF94 /* Assets.xcassets */,
|
||||
497F0DF220DE2FE50077AD41 /* Icon.icns */,
|
||||
|
@ -401,15 +423,6 @@
|
|||
path = "Support Files";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
49CF3B5D20CE80D2001EBF94 /* script */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
49CF3B5E20CE80D2001EBF94 /* install_proxy_helper.sh */,
|
||||
49CF3B6220CED934001EBF94 /* check_proxy_helper.sh */,
|
||||
);
|
||||
path = script;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
76229F122B00E935D126742A /* Pods */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
@ -427,6 +440,21 @@
|
|||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
F9A7C06A2306E874007163C7 /* ProxyConfigHelper */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
F9A7C06B2306E874007163C7 /* main.m */,
|
||||
F935B2F12307C802009E4D33 /* Helper-Launchd.plist */,
|
||||
F935B2EA2307B6BA009E4D33 /* Helper-Info.plist */,
|
||||
F935B2F22307CD32009E4D33 /* ProxyConfigHelper.h */,
|
||||
F935B2F32307CD32009E4D33 /* ProxyConfigHelper.m */,
|
||||
F935B2F52307D00D009E4D33 /* ProxyConfigRemoteProcessProtocol.h */,
|
||||
F935B2F823083EE6009E4D33 /* ProxySettingTool.h */,
|
||||
F935B2F923083EE6009E4D33 /* ProxySettingTool.m */,
|
||||
);
|
||||
path = ProxyConfigHelper;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
|
@ -457,13 +485,14 @@
|
|||
49CF3B1B20CD7463001EBF94 /* Resources */,
|
||||
495A44CC20D266B000888A0A /* CopyFiles */,
|
||||
A741C26F5755233F0D7CEC6F /* [CP] Embed Pods Frameworks */,
|
||||
663E4677213FCDC4006F11BB /* CopyFiles */,
|
||||
663E4677213FCDC4006F11BB /* Copy Files */,
|
||||
49EC8D1822F13F100079B8F4 /* CopyFiles */,
|
||||
4929F1E621BC170D0059D8B4 /* ShellScript */,
|
||||
4929F1E621BC170D0059D8B4 /* Run Script - Fabric */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
F935B2EC2307B7CD009E4D33 /* PBXTargetDependency */,
|
||||
495A44CF20D2671F00888A0A /* PBXTargetDependency */,
|
||||
);
|
||||
name = ClashX;
|
||||
|
@ -471,13 +500,30 @@
|
|||
productReference = 49CF3B1D20CD7463001EBF94 /* ClashX.app */;
|
||||
productType = "com.apple.product-type.application";
|
||||
};
|
||||
F9A7C0682306E874007163C7 /* com.west2online.ClashX.ProxyConfigHelper */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = F9A7C06D2306E874007163C7 /* Build configuration list for PBXNativeTarget "com.west2online.ClashX.ProxyConfigHelper" */;
|
||||
buildPhases = (
|
||||
F9A7C0652306E874007163C7 /* Sources */,
|
||||
F9A7C0662306E874007163C7 /* Frameworks */,
|
||||
F9A7C0672306E874007163C7 /* Copy Files */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = com.west2online.ClashX.ProxyConfigHelper;
|
||||
productName = ProxyConfigHelper;
|
||||
productReference = F9A7C0692306E874007163C7 /* com.west2online.ClashX.ProxyConfigHelper */;
|
||||
productType = "com.apple.product-type.tool";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
|
||||
/* Begin PBXProject section */
|
||||
49CF3B1520CD7463001EBF94 /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastSwiftUpdateCheck = 0940;
|
||||
LastSwiftUpdateCheck = 1030;
|
||||
LastUpgradeCheck = 1020;
|
||||
ORGANIZATIONNAME = west2online;
|
||||
TargetAttributes = {
|
||||
|
@ -502,6 +548,9 @@
|
|||
};
|
||||
};
|
||||
};
|
||||
F9A7C0682306E874007163C7 = {
|
||||
CreatedOnToolsVersion = 10.3;
|
||||
};
|
||||
};
|
||||
};
|
||||
buildConfigurationList = 49CF3B1820CD7463001EBF94 /* Build configuration list for PBXProject "ClashX" */;
|
||||
|
@ -512,40 +561,25 @@
|
|||
en,
|
||||
Base,
|
||||
"zh-Hans",
|
||||
global,
|
||||
);
|
||||
mainGroup = 49CF3B1420CD7463001EBF94;
|
||||
productRefGroup = 49CF3B1E20CD7463001EBF94 /* Products */;
|
||||
projectDirPath = "";
|
||||
projectReferences = (
|
||||
{
|
||||
ProductGroup = 49AF90FF225B2A95004CC077 /* Products */;
|
||||
ProjectRef = 49AF90FE225B2A95004CC077 /* ProxyConfig.xcodeproj */;
|
||||
},
|
||||
);
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
49CF3B1C20CD7463001EBF94 /* ClashX */,
|
||||
495A44BB20D2660A00888A0A /* ClashXLaunchHelper */,
|
||||
F9A7C0682306E874007163C7 /* com.west2online.ClashX.ProxyConfigHelper */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
|
||||
/* Begin PBXReferenceProxy section */
|
||||
49AF9103225B2A96004CC077 /* ProxyConfig */ = {
|
||||
isa = PBXReferenceProxy;
|
||||
fileType = "compiled.mach-o.executable";
|
||||
path = ProxyConfig;
|
||||
remoteRef = 49AF9102225B2A96004CC077 /* PBXContainerItemProxy */;
|
||||
sourceTree = BUILT_PRODUCTS_DIR;
|
||||
};
|
||||
/* End PBXReferenceProxy section */
|
||||
|
||||
/* Begin PBXResourcesBuildPhase section */
|
||||
495A44BA20D2660A00888A0A /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
F94245092306DABA0005B196 /* Main.storyboard in Resources */,
|
||||
49E07A8C20D501A000A088A3 /* Main.storyboard in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
|
@ -559,12 +593,10 @@
|
|||
4981C88B216BAE4A008CC14A /* Localizable.strings in Resources */,
|
||||
495340B020DE5F7200B0D3FF /* StatusItemView.xib in Resources */,
|
||||
49CF3B2820CD7465001EBF94 /* Main.storyboard in Resources */,
|
||||
49CF3B5F20CE80D2001EBF94 /* install_proxy_helper.sh in Resources */,
|
||||
499A485A22ED781100F6C675 /* RemoteConfigAddView.xib in Resources */,
|
||||
4989F98420D02D200001E564 /* Country.mmdb in Resources */,
|
||||
497F0DF320DE2FE50077AD41 /* Icon.icns in Resources */,
|
||||
4989F98E20D0AE990001E564 /* sampleConfig.yaml in Resources */,
|
||||
49CF3B6320CED9CF001EBF94 /* check_proxy_helper.sh in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
@ -589,7 +621,7 @@
|
|||
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
4929F1E621BC170D0059D8B4 /* ShellScript */ = {
|
||||
4929F1E621BC170D0059D8B4 /* Run Script - Fabric */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
|
@ -597,6 +629,7 @@
|
|||
inputPaths = (
|
||||
"$(BUILT_PRODUCTS_DIR)/$(INFOPLIST_PATH)",
|
||||
);
|
||||
name = "Run Script - Fabric";
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
|
@ -636,12 +669,13 @@
|
|||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
499A485522ED707300F6C675 /* RemoteConfigViewController.swift in Sources */,
|
||||
49CF3B5C20CE8068001EBF94 /* ProxyConfigHelperManager.swift in Sources */,
|
||||
49CF3B5C20CE8068001EBF94 /* ClashResourceManager.swift in Sources */,
|
||||
4952C3D02117027C004A4FA8 /* ConfigFileManager.swift in Sources */,
|
||||
4997732520D251A60009B136 /* SWBApplication.m in Sources */,
|
||||
49BC061C212931F4005A0FE7 /* AboutViewController.swift in Sources */,
|
||||
4949D154213242F600EF85E6 /* Paths.swift in Sources */,
|
||||
495340B320DE68C300B0D3FF /* StatusItemView.swift in Sources */,
|
||||
F935B2FC23085515009E4D33 /* SystemProxyManager.swift in Sources */,
|
||||
495A44D320D267D000888A0A /* LaunchAtLogin.swift in Sources */,
|
||||
493AEAE3221AE3420016FE98 /* AppVersionUtil.swift in Sources */,
|
||||
49CF3B2120CD7463001EBF94 /* AppDelegate.swift in Sources */,
|
||||
|
@ -670,6 +704,16 @@
|
|||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
F9A7C0652306E874007163C7 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
F935B2F42307CD32009E4D33 /* ProxyConfigHelper.m in Sources */,
|
||||
F9A7C06C2306E874007163C7 /* main.m in Sources */,
|
||||
F935B2FA23083EE6009E4D33 /* ProxySettingTool.m in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXTargetDependency section */
|
||||
|
@ -678,6 +722,11 @@
|
|||
target = 495A44BB20D2660A00888A0A /* ClashXLaunchHelper */;
|
||||
targetProxy = 495A44CE20D2671F00888A0A /* PBXContainerItemProxy */;
|
||||
};
|
||||
F935B2EC2307B7CD009E4D33 /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = F9A7C0682306E874007163C7 /* com.west2online.ClashX.ProxyConfigHelper */;
|
||||
targetProxy = F935B2EB2307B7CD009E4D33 /* PBXContainerItemProxy */;
|
||||
};
|
||||
/* End PBXTargetDependency section */
|
||||
|
||||
/* Begin PBXVariantGroup section */
|
||||
|
@ -702,7 +751,6 @@
|
|||
49E07A8920D501A000A088A3 /* Main.storyboard */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
49E07A8A20D501A000A088A3 /* Base */,
|
||||
4981C888216BAB8A008CC14A /* zh-Hans */,
|
||||
);
|
||||
name = Main.storyboard;
|
||||
|
@ -716,6 +764,7 @@
|
|||
buildSettings = {
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = ClashXLaunchHelper/ClashXLaunchHelper.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Developer ID Application";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
DEVELOPMENT_TEAM = MEWHFZ92DY;
|
||||
|
@ -879,7 +928,7 @@
|
|||
baseConfigurationReference = 5217C006C5A22A1CEA24BFC1 /* Pods-ClashX.debug.xcconfig */;
|
||||
buildSettings = {
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_IDENTITY = "Mac Developer";
|
||||
CODE_SIGN_IDENTITY = "Developer ID Application";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
DEVELOPMENT_TEAM = MEWHFZ92DY;
|
||||
|
@ -950,6 +999,57 @@
|
|||
};
|
||||
name = Release;
|
||||
};
|
||||
F9A7C06E2306E874007163C7 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CODE_SIGN_IDENTITY = "Developer ID Application";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
DEVELOPMENT_TEAM = MEWHFZ92DY;
|
||||
INFOPLIST_FILE = "$(SRCROOT)/ProxyConfigHelper/Helper-Info.plist";
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.10;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
OTHER_LDFLAGS = (
|
||||
"-sectcreate",
|
||||
__TEXT,
|
||||
__info_plist,
|
||||
"\"$(SRCROOT)/ProxyConfigHelper/Helper-Info.plist\"",
|
||||
"-sectcreate",
|
||||
__TEXT,
|
||||
__launchd_plist,
|
||||
"\"$(SRCROOT)/ProxyConfigHelper/Helper-Launchd.plist\"",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.west2online.ClashX.ProxyConfigHelper;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
F9A7C06F2306E874007163C7 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CODE_SIGN_IDENTITY = "Developer ID Application";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
DEVELOPMENT_TEAM = MEWHFZ92DY;
|
||||
INFOPLIST_FILE = "$(SRCROOT)/ProxyConfigHelper/Helper-Info.plist";
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.10;
|
||||
MTL_FAST_MATH = YES;
|
||||
OTHER_LDFLAGS = (
|
||||
"-sectcreate",
|
||||
__TEXT,
|
||||
__info_plist,
|
||||
"\"$(SRCROOT)/ProxyConfigHelper/Helper-Info.plist\"",
|
||||
"-sectcreate",
|
||||
__TEXT,
|
||||
__launchd_plist,
|
||||
"\"$(SRCROOT)/ProxyConfigHelper/Helper-Launchd.plist\"",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.west2online.ClashX.ProxyConfigHelper;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
|
@ -980,6 +1080,15 @@
|
|||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
F9A7C06D2306E874007163C7 /* Build configuration list for PBXNativeTarget "com.west2online.ClashX.ProxyConfigHelper" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
F9A7C06E2306E874007163C7 /* Debug */,
|
||||
F9A7C06F2306E874007163C7 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
};
|
||||
rootObject = 49CF3B1520CD7463001EBF94 /* Project object */;
|
||||
|
|
|
@ -21,7 +21,7 @@ private let statusItemLengthWithSpeed:CGFloat = 70
|
|||
@NSApplicationMain
|
||||
class AppDelegate: NSObject, NSApplicationDelegate {
|
||||
var statusItem: NSStatusItem!
|
||||
|
||||
|
||||
@IBOutlet weak var statusMenu: NSMenu!
|
||||
@IBOutlet weak var proxySettingMenuItem: NSMenuItem!
|
||||
@IBOutlet weak var autoStartMenuItem: NSMenuItem!
|
||||
|
@ -53,6 +53,8 @@ class AppDelegate: NSObject, NSApplicationDelegate {
|
|||
func applicationDidFinishLaunching(_ notification: Notification) {
|
||||
signal(SIGPIPE, SIG_IGN)
|
||||
|
||||
|
||||
|
||||
// setup menu item first
|
||||
statusItem = NSStatusBar.system.statusItem(withLength:statusItemLengthWithSpeed)
|
||||
statusItem.menu = statusMenu
|
||||
|
@ -60,13 +62,13 @@ class AppDelegate: NSObject, NSApplicationDelegate {
|
|||
statusItemView = StatusItemView.create(statusItem: statusItem)
|
||||
statusItemView.frame = CGRect(x: 0, y: 0, width: statusItemLengthWithSpeed, height: 22)
|
||||
statusMenu.delegate = self
|
||||
|
||||
// crash recorder
|
||||
failLaunchProtect()
|
||||
registCrashLogger()
|
||||
|
||||
// install proxy helper
|
||||
_ = ProxyConfigHelperManager.install()
|
||||
_ = ClashResourceManager.check()
|
||||
SystemProxyManager.shared.checkInstall()
|
||||
ConfigFileManager.copySampleConfigIfNeed()
|
||||
ConfigManager.shared.refreshApiInfo()
|
||||
|
||||
|
@ -92,7 +94,7 @@ class AppDelegate: NSObject, NSApplicationDelegate {
|
|||
|
||||
func applicationWillTerminate(_ aNotification: Notification) {
|
||||
if ConfigManager.shared.proxyPortAutoSet {
|
||||
_ = ProxyConfigHelperManager.setUpSystemProxy(port: nil,socksPort: nil)
|
||||
SystemProxyManager.shared.disableProxy()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -181,7 +183,7 @@ class AppDelegate: NSObject, NSApplicationDelegate {
|
|||
self.updateProxyList()
|
||||
|
||||
if (old?.port != config.port && ConfigManager.shared.proxyPortAutoSet) {
|
||||
_ = ProxyConfigHelperManager.setUpSystemProxy(port: config.port,socksPort: config.socketPort)
|
||||
SystemProxyManager.shared.enableProxy(port: config.port, socksPort: config.socketPort)
|
||||
}
|
||||
|
||||
self.httpPortMenuItem.title = "Http Port:\(config.port)"
|
||||
|
@ -361,9 +363,9 @@ extension AppDelegate {
|
|||
if ConfigManager.shared.proxyPortAutoSet {
|
||||
let port = ConfigManager.shared.currentConfig?.port ?? 0
|
||||
let socketPort = ConfigManager.shared.currentConfig?.socketPort ?? 0
|
||||
_ = ProxyConfigHelperManager.setUpSystemProxy(port: port,socksPort:socketPort)
|
||||
SystemProxyManager.shared.enableProxy(port: port, socksPort: socketPort)
|
||||
} else {
|
||||
_ = ProxyConfigHelperManager.setUpSystemProxy(port: nil,socksPort: nil)
|
||||
SystemProxyManager.shared.disableProxy()
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -2,3 +2,4 @@
|
|||
// Use this file to import your target's public headers that you would like to expose to Swift.
|
||||
//
|
||||
#import "ClashX.h"
|
||||
#import "ProxyConfigRemoteProcessProtocol.h"
|
||||
|
|
|
@ -0,0 +1,95 @@
|
|||
|
||||
import Foundation
|
||||
import AppKit
|
||||
|
||||
class ClashResourceManager {
|
||||
static let kProxyConfigFolder = (NSHomeDirectory() as NSString).appendingPathComponent("/.config/clash")
|
||||
|
||||
static func check() -> Bool {
|
||||
checkConfigDir()
|
||||
checkMMDB()
|
||||
upgardeYmlExtensionName()
|
||||
checkAndRemoveOldErrorConfig()
|
||||
return true
|
||||
}
|
||||
|
||||
static func checkConfigDir() {
|
||||
var isDir : ObjCBool = true
|
||||
|
||||
if !FileManager.default.fileExists(atPath: kProxyConfigFolder, isDirectory:&isDir) {
|
||||
do {
|
||||
try FileManager.default.createDirectory(atPath: kProxyConfigFolder, withIntermediateDirectories: true, attributes: nil)
|
||||
} catch {
|
||||
showCreateConfigDirFailAlert()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static func checkMMDB() {
|
||||
let fileManage = FileManager.default
|
||||
let destMMDBPath = "\(kProxyConfigFolder)/Country.mmdb"
|
||||
|
||||
// Remove old mmdb file after version update.
|
||||
if fileManage.fileExists(atPath: destMMDBPath) {
|
||||
if AppVersionUtil.hasVersionChanged || AppVersionUtil.isFirstLaunch {
|
||||
try? fileManage.removeItem(atPath: destMMDBPath)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if !fileManage.fileExists(atPath: destMMDBPath) {
|
||||
if let mmdbPath = Bundle.main.path(forResource: "Country", ofType: "mmdb") {
|
||||
try? fileManage.copyItem(at: URL(fileURLWithPath: mmdbPath), to: URL(fileURLWithPath: destMMDBPath))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static func checkAndRemoveOldErrorConfig() {
|
||||
if FileManager.default.fileExists(atPath: kDefaultConfigFilePath) {
|
||||
do {
|
||||
let defaultConfigData = try Data(contentsOf: URL(fileURLWithPath: kDefaultConfigFilePath))
|
||||
var checkSum: UInt8 = 0
|
||||
for byte in defaultConfigData {
|
||||
checkSum &+= byte
|
||||
}
|
||||
|
||||
if checkSum == 101 {
|
||||
// old error config
|
||||
Logger.log(msg: "removing old config.yaml")
|
||||
try FileManager.default.removeItem(atPath: kDefaultConfigFilePath)
|
||||
}
|
||||
} catch let err {
|
||||
Logger.log(msg: "removing old config.yaml fail: \(err.localizedDescription)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static func upgardeYmlExtensionName() {
|
||||
do {
|
||||
let fileURLs = try FileManager.default.contentsOfDirectory(at: URL(fileURLWithPath: kConfigFolderPath, isDirectory: true), includingPropertiesForKeys: nil, options: [.skipsSubdirectoryDescendants])
|
||||
|
||||
for upgradeUrl in fileURLs.filter({$0.pathExtension == "yml" }) {
|
||||
let dest = upgradeUrl.deletingPathExtension().appendingPathExtension("yaml")
|
||||
try FileManager.default.moveItem(at: upgradeUrl, to: dest)
|
||||
}
|
||||
|
||||
} catch let err {
|
||||
Logger.log(msg: err.localizedDescription)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static func showCreateConfigDirFailAlert() {
|
||||
let alert = NSAlert()
|
||||
alert.messageText = NSLocalizedString("ClashX fail to create ~/.config/clash folder. Please check privileges or manually create folder and restart ClashX.", comment: "")
|
||||
alert.alertStyle = .warning
|
||||
alert.addButton(withTitle: NSLocalizedString("Quit", comment: ""))
|
||||
alert.runModal()
|
||||
NSApplication.shared.terminate(nil)
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -1,175 +0,0 @@
|
|||
|
||||
import Foundation
|
||||
import AppKit
|
||||
|
||||
class ProxyConfigHelperManager {
|
||||
static let kProxyConfigFolder = (NSHomeDirectory() as NSString).appendingPathComponent("/.config/clash")
|
||||
static let kVersion = "0.1.3"
|
||||
|
||||
|
||||
static func vaildHelper() -> Bool {
|
||||
let scriptPath = "\(Bundle.main.resourcePath!)/check_proxy_helper.sh"
|
||||
let appleScriptStr = "do shell script \"bash \\\"\(scriptPath)\\\" \(kProxyConfigFolder) \(kVersion) \" "
|
||||
let appleScript = NSAppleScript(source: appleScriptStr)
|
||||
var dict: NSDictionary?
|
||||
if let res = appleScript?.executeAndReturnError(&dict) {
|
||||
if (res.stringValue?.contains("success")) ?? false {
|
||||
return true
|
||||
}
|
||||
} else {
|
||||
Logger.log(msg: "\(String(describing: dict))",level: .error)
|
||||
}
|
||||
return false
|
||||
|
||||
}
|
||||
|
||||
static func install() -> Bool {
|
||||
checkConfigDir()
|
||||
checkMMDB()
|
||||
upgardeYmlExtensionName()
|
||||
checkAndRemoveOldErrorConfig()
|
||||
|
||||
let proxyHelperPath = Bundle.main.path(forResource: "ProxyConfig", ofType: nil)
|
||||
let targetPath = "\(kProxyConfigFolder)/ProxyConfig"
|
||||
|
||||
|
||||
if !vaildHelper() {
|
||||
if (!showInstallHelperAlert()) {
|
||||
NSApplication.shared.terminate(nil)
|
||||
}
|
||||
|
||||
if (FileManager.default.fileExists(atPath: targetPath)) {
|
||||
try? FileManager.default.removeItem(atPath: targetPath)
|
||||
}
|
||||
try? FileManager.default.copyItem(at: URL(fileURLWithPath: proxyHelperPath!), to: URL(fileURLWithPath: targetPath))
|
||||
|
||||
let scriptPath = "\(Bundle.main.resourcePath!)/install_proxy_helper.sh"
|
||||
let appleScriptStr = "do shell script \"bash \(scriptPath) \(kProxyConfigFolder) \" with administrator privileges"
|
||||
let appleScript = NSAppleScript(source: appleScriptStr)
|
||||
|
||||
var dict: NSDictionary?
|
||||
if let _ = appleScript?.executeAndReturnError(&dict) {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
static func checkConfigDir() {
|
||||
var isDir : ObjCBool = true
|
||||
|
||||
if !FileManager.default.fileExists(atPath: kProxyConfigFolder, isDirectory:&isDir) {
|
||||
do {
|
||||
try FileManager.default.createDirectory(atPath: kProxyConfigFolder, withIntermediateDirectories: true, attributes: nil)
|
||||
} catch {
|
||||
showCreateConfigDirFailAlert()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static func checkMMDB() {
|
||||
let fileManage = FileManager.default
|
||||
let destMMDBPath = "\(kProxyConfigFolder)/Country.mmdb"
|
||||
|
||||
// Remove old mmdb file after version update.
|
||||
if fileManage.fileExists(atPath: destMMDBPath) {
|
||||
if AppVersionUtil.hasVersionChanged || AppVersionUtil.isFirstLaunch {
|
||||
try? fileManage.removeItem(atPath: destMMDBPath)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if !fileManage.fileExists(atPath: destMMDBPath) {
|
||||
if let mmdbPath = Bundle.main.path(forResource: "Country", ofType: "mmdb") {
|
||||
try? fileManage.copyItem(at: URL(fileURLWithPath: mmdbPath), to: URL(fileURLWithPath: destMMDBPath))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static func checkAndRemoveOldErrorConfig() {
|
||||
if FileManager.default.fileExists(atPath: kDefaultConfigFilePath) {
|
||||
do {
|
||||
let defaultConfigData = try Data(contentsOf: URL(fileURLWithPath: kDefaultConfigFilePath))
|
||||
var checkSum: UInt8 = 0
|
||||
for byte in defaultConfigData {
|
||||
checkSum &+= byte
|
||||
}
|
||||
|
||||
if checkSum == 101 {
|
||||
// old error config
|
||||
Logger.log(msg: "removing old config.yaml")
|
||||
try FileManager.default.removeItem(atPath: kDefaultConfigFilePath)
|
||||
}
|
||||
} catch let err {
|
||||
Logger.log(msg: "removing old config.yaml fail: \(err.localizedDescription)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static func upgardeYmlExtensionName() {
|
||||
do {
|
||||
let fileURLs = try FileManager.default.contentsOfDirectory(at: URL(fileURLWithPath: kConfigFolderPath, isDirectory: true), includingPropertiesForKeys: nil, options: [.skipsSubdirectoryDescendants])
|
||||
|
||||
for upgradeUrl in fileURLs.filter({$0.pathExtension == "yml" }) {
|
||||
let dest = upgradeUrl.deletingPathExtension().appendingPathExtension("yaml")
|
||||
try FileManager.default.moveItem(at: upgradeUrl, to: dest)
|
||||
}
|
||||
|
||||
} catch let err {
|
||||
Logger.log(msg: err.localizedDescription)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static func setUpSystemProxy(port: Int?,socksPort: Int?) -> Bool {
|
||||
let task = Process()
|
||||
task.launchPath = "\(kProxyConfigFolder)/ProxyConfig"
|
||||
let hookTask:String?
|
||||
if let port = port,let socksPort = socksPort {
|
||||
hookTask = UserDefaults.standard.string(forKey: "kProxyEnableHook")
|
||||
task.arguments = [String(port),String(socksPort), "enable"]
|
||||
} else {
|
||||
hookTask = UserDefaults.standard.string(forKey: "kProxyDisableHook")
|
||||
task.arguments = ["0", "0", "disable"]
|
||||
}
|
||||
|
||||
task.launch()
|
||||
|
||||
task.waitUntilExit()
|
||||
|
||||
if task.terminationStatus != 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
DispatchQueue.global().async {
|
||||
if let command = hookTask {
|
||||
let appleScriptStr = "do shell script \"\(command)\""
|
||||
let appleScript = NSAppleScript(source: appleScriptStr)
|
||||
_ = appleScript?.executeAndReturnError(nil)
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
static func showInstallHelperAlert() -> Bool{
|
||||
let alert = NSAlert()
|
||||
alert.messageText = NSLocalizedString("ClashX needs to install a small tool to ~/.config/clash with administrator privileges to set system proxy quickly.\n\nOtherwise you need to type in the administrator password every time you change system proxy through ClashX.", comment: "")
|
||||
alert.alertStyle = .warning
|
||||
alert.addButton(withTitle: NSLocalizedString("Install", comment: ""))
|
||||
alert.addButton(withTitle: NSLocalizedString("Quit", comment: ""))
|
||||
return alert.runModal() == .alertFirstButtonReturn
|
||||
}
|
||||
|
||||
static func showCreateConfigDirFailAlert() {
|
||||
let alert = NSAlert()
|
||||
alert.messageText = NSLocalizedString("ClashX fail to create ~/.config/clash folder. Please check privileges or manually create folder and restart ClashX.", comment: "")
|
||||
alert.alertStyle = .warning
|
||||
alert.addButton(withTitle: NSLocalizedString("Quit", comment: ""))
|
||||
alert.runModal()
|
||||
NSApplication.shared.terminate(nil)
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,228 @@
|
|||
//
|
||||
// SystemProxyManager.swift
|
||||
// ClashX
|
||||
//
|
||||
// Created by yichengchen on 2019/8/17.
|
||||
// Copyright © 2019 west2online. All rights reserved.
|
||||
//
|
||||
|
||||
import ServiceManagement
|
||||
import AppKit
|
||||
|
||||
class SystemProxyManager: NSObject {
|
||||
|
||||
static let shared = SystemProxyManager()
|
||||
|
||||
private static let machServiceName = "com.west2online.ClashX.ProxyConfigHelper"
|
||||
private var authRef: AuthorizationRef?
|
||||
private var connection: NSXPCConnection?
|
||||
private var _helper: ProxyConfigRemoteProcessProtocol?
|
||||
|
||||
// MARK: - LifeCycle
|
||||
|
||||
override init() {
|
||||
super.init()
|
||||
initAuthorizationRef()
|
||||
}
|
||||
|
||||
// MARK: - Public
|
||||
|
||||
func checkInstall() {
|
||||
helperStatus { [weak self] installed in
|
||||
if installed {return}
|
||||
if Thread.isMainThread {
|
||||
self?.notifyInstall()
|
||||
} else {
|
||||
DispatchQueue.main.async {
|
||||
self?.notifyInstall()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func enableProxy(port: Int,socksPort: Int) {
|
||||
Logger.log(msg: "enableProxy", level: .debug)
|
||||
helper()?.enableProxy(withPort: Int32(port), socksPort: Int32(socksPort), authData: authData(), error: { error in
|
||||
if let error = error{
|
||||
Logger.log(msg: "enableProxy \(error)", level: .error)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func disableProxy() {
|
||||
Logger.log(msg: "disableProxy", level: .debug)
|
||||
helper()?.disableProxy(withAuthData: authData(), error: { error in
|
||||
if let error = error{
|
||||
Logger.log(msg: "disableProxy \(error)", level: .error)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private func initAuthorizationRef() {
|
||||
// Create an empty AuthorizationRef
|
||||
let status = AuthorizationCreate(nil, nil, AuthorizationFlags(), &authRef)
|
||||
if (status != OSStatus(errAuthorizationSuccess)) {
|
||||
Logger.log(msg:"initAuthorizationRef AuthorizationCreate failed",level: .error)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Install new helper daemon
|
||||
private func installHelperDaemon() {
|
||||
|
||||
// Create authorization reference for the user
|
||||
var authRef: AuthorizationRef?
|
||||
var authStatus = AuthorizationCreate(nil, nil, [], &authRef)
|
||||
|
||||
// Check if the reference is valid
|
||||
guard authStatus == errAuthorizationSuccess else {
|
||||
Logger.log(msg: "Authorization failed: \(authStatus)", level: .error)
|
||||
return
|
||||
}
|
||||
|
||||
// Ask user for the admin privileges to install the
|
||||
var authItem = AuthorizationItem(name: kSMRightBlessPrivilegedHelper, valueLength: 0, value: nil, flags: 0)
|
||||
var authRights = AuthorizationRights(count: 1, items: &authItem)
|
||||
let flags: AuthorizationFlags = [[], .interactionAllowed, .extendRights, .preAuthorize]
|
||||
authStatus = AuthorizationCreate(&authRights, nil, flags, &authRef)
|
||||
defer {
|
||||
if let ref = authRef {
|
||||
AuthorizationFree(ref, [])
|
||||
}
|
||||
}
|
||||
// Check if the authorization went succesfully
|
||||
guard authStatus == errAuthorizationSuccess else {
|
||||
Logger.log(msg: "Couldn't obtain admin privileges: \(authStatus)", level: .error)
|
||||
return
|
||||
}
|
||||
|
||||
// Launch the privileged helper using SMJobBless tool
|
||||
var error: Unmanaged<CFError>? = nil
|
||||
|
||||
if(SMJobBless(kSMDomainSystemLaunchd, SystemProxyManager.machServiceName as CFString, authRef, &error) == false) {
|
||||
let blessError = error!.takeRetainedValue() as Error
|
||||
Logger.log(msg: "Bless Error: \(blessError)", level: .error)
|
||||
} else {
|
||||
Logger.log(msg: "\(SystemProxyManager.machServiceName) installed successfully", level: .info)
|
||||
}
|
||||
|
||||
connection?.invalidate()
|
||||
connection = nil
|
||||
_helper = nil
|
||||
}
|
||||
|
||||
private func authData() -> Data? {
|
||||
guard let authRef = authRef else {return nil}
|
||||
var authRefExtForm = AuthorizationExternalForm()
|
||||
|
||||
// Make an external form of the AuthorizationRef
|
||||
var status = AuthorizationMakeExternalForm(authRef, &authRefExtForm)
|
||||
if (status != OSStatus(errAuthorizationSuccess)) {
|
||||
Logger.log(msg: "AppviewController: AuthorizationMakeExternalForm failed", level: .error)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Add all or update required authorization right definition to the authorization database
|
||||
var currentRight:CFDictionary?
|
||||
|
||||
// Try to get the authorization right definition from the database
|
||||
status = AuthorizationRightGet(AppAuthorizationRights.rightName.utf8String!, ¤tRight)
|
||||
|
||||
if (status == errAuthorizationDenied) {
|
||||
|
||||
let defaultRules = AppAuthorizationRights.rightDefaultRule
|
||||
status = AuthorizationRightSet(authRef,
|
||||
AppAuthorizationRights.rightName.utf8String!,
|
||||
defaultRules as CFDictionary,
|
||||
AppAuthorizationRights.rightDescription,
|
||||
nil, "Common" as CFString)
|
||||
}
|
||||
|
||||
// We need to put the AuthorizationRef to a form that can be passed through inter process call
|
||||
let authData = NSData(bytes: &authRefExtForm, length:kAuthorizationExternalFormLength)
|
||||
return authData as Data
|
||||
}
|
||||
|
||||
private func helperConnection() -> NSXPCConnection? {
|
||||
// Check that the connection is valid before trying to do an inter process call to helper
|
||||
if(connection == nil) {
|
||||
connection = NSXPCConnection(machServiceName: SystemProxyManager.machServiceName, options: NSXPCConnection.Options.privileged)
|
||||
connection?.remoteObjectInterface = NSXPCInterface(with: ProxyConfigRemoteProcessProtocol.self)
|
||||
connection?.invalidationHandler = {
|
||||
self.connection?.invalidationHandler = nil
|
||||
OperationQueue.main.addOperation() {
|
||||
self.connection = nil
|
||||
Logger.log(msg: "XPC Connection Invalidated")
|
||||
}
|
||||
}
|
||||
connection?.resume()
|
||||
}
|
||||
return connection
|
||||
}
|
||||
|
||||
|
||||
private func helper(failture: (() -> Void)? = nil) -> ProxyConfigRemoteProcessProtocol? {
|
||||
if _helper == nil {
|
||||
guard let newHelper = self.helperConnection()?.remoteObjectProxyWithErrorHandler({ error in
|
||||
Logger.log(msg: "Helper connection was closed with error: \(error)")
|
||||
failture?()
|
||||
}) as? ProxyConfigRemoteProcessProtocol else { return nil }
|
||||
_helper = newHelper
|
||||
}
|
||||
return _helper
|
||||
}
|
||||
|
||||
private func helperStatus(completion: @escaping (_ installed: Bool) -> Void) {
|
||||
let helperURL = Bundle.main.bundleURL.appendingPathComponent("Contents/Library/LaunchServices/" + SystemProxyManager.machServiceName)
|
||||
var callback:((Bool)->Void)? = completion
|
||||
|
||||
guard
|
||||
let helperBundleInfo = CFBundleCopyInfoDictionaryForURL(helperURL as CFURL) as? [String: Any],
|
||||
let helperVersion = helperBundleInfo["CFBundleShortVersionString"] as? String,
|
||||
let helper = self.helper() else {
|
||||
callback?(false)
|
||||
callback = nil
|
||||
return
|
||||
}
|
||||
var installed = false
|
||||
let semaphore = DispatchSemaphore(value: 0)
|
||||
helper.getVersion { installedHelperVersion in
|
||||
installed = installedHelperVersion == helperVersion
|
||||
}
|
||||
_ = semaphore.wait(timeout: DispatchTime.now()+1)
|
||||
callback?(installed)
|
||||
callback = nil
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
extension SystemProxyManager {
|
||||
private func notifyInstall() {
|
||||
guard showInstallHelperAlert() else {exit(0)}
|
||||
self.installHelperDaemon()
|
||||
}
|
||||
|
||||
private func showInstallHelperAlert() -> Bool{
|
||||
let alert = NSAlert()
|
||||
alert.messageText = NSLocalizedString("ClashX needs to install a small tool to ~/.config/clash with administrator privileges to set system proxy quickly.\n\nOtherwise you need to type in the administrator password every time you change system proxy through ClashX.", comment: "")
|
||||
alert.alertStyle = .warning
|
||||
alert.addButton(withTitle: NSLocalizedString("Install", comment: ""))
|
||||
alert.addButton(withTitle: NSLocalizedString("Quit", comment: ""))
|
||||
return alert.runModal() == .alertFirstButtonReturn
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fileprivate struct AppAuthorizationRights {
|
||||
static let rightName: NSString = "com.west2online.ClashX.ProxyConfigHelper.config"
|
||||
static let rightDefaultRule: Dictionary = adminRightsRule
|
||||
static let rightDescription: CFString = "ProxyConfigHelper wants to configure your proxy setting'" as CFString
|
||||
static var adminRightsRule: [String:Any] = ["class" : "user",
|
||||
"group" : "admin",
|
||||
"timeout" : 0,
|
||||
"version" : 1]
|
||||
}
|
|
@ -48,13 +48,12 @@ class JsBridgeUtil {
|
|||
if let enable = anydata as? Bool {
|
||||
ConfigManager.shared.proxyPortAutoSet = enable
|
||||
if let config = ConfigManager.shared.currentConfig {
|
||||
let success:Bool
|
||||
if enable{
|
||||
success = ProxyConfigHelperManager.setUpSystemProxy(port: config.port,socksPort: config.socketPort)
|
||||
SystemProxyManager.shared.enableProxy(port: config.port, socksPort: config.socketPort)
|
||||
} else {
|
||||
success = ProxyConfigHelperManager.setUpSystemProxy(port: nil,socksPort: nil)
|
||||
SystemProxyManager.shared.disableProxy()
|
||||
}
|
||||
responseCallback?(success)
|
||||
responseCallback?(true)
|
||||
} else {
|
||||
responseCallback?(false)
|
||||
}
|
||||
|
|
|
@ -55,7 +55,8 @@
|
|||
<array>
|
||||
<dict>
|
||||
<key>KitInfo</key>
|
||||
<dict/>
|
||||
<dict>
|
||||
</dict>
|
||||
<key>KitName</key>
|
||||
<string>Crashlytics</string>
|
||||
</dict>
|
||||
|
@ -78,6 +79,15 @@
|
|||
<string>Main</string>
|
||||
<key>NSPrincipalClass</key>
|
||||
<string>SWBApplication</string>
|
||||
<key>SMAuthorizedClients</key>
|
||||
<array>
|
||||
<string>identifier "com.west2online.ClashX" and anchor apple generic and certificate leaf[subject.CN] = "Mac Developer: chen yicheng (96U846XGYH)" and certificate 1[field.1.2.840.113635.100.6.2.1] /* exists */</string>
|
||||
</array>
|
||||
<key>SMPrivilegedExecutables</key>
|
||||
<dict>
|
||||
<key>com.west2online.ClashX.ProxyConfigHelper</key>
|
||||
<string>anchor apple generic and identifier "com.west2online.ClashX.ProxyConfigHelper" and (certificate leaf[field.1.2.840.113635.100.6.1.9] /* exists */ or certificate 1[field.1.2.840.113635.100.6.2.6] /* exists */ and certificate leaf[field.1.2.840.113635.100.6.1.13] /* exists */ and certificate leaf[subject.OU] = MEWHFZ92DY)</string>
|
||||
</dict>
|
||||
<key>SUEnableAutomaticChecks</key>
|
||||
<true/>
|
||||
<key>SUFeedURL</key>
|
||||
|
|
|
@ -1,287 +0,0 @@
|
|||
// !$*UTF8*$!
|
||||
{
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 50;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
49AF90F7225B17D2004CC077 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 49AF90F6225B17D2004CC077 /* main.m */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXCopyFilesBuildPhase section */
|
||||
49AF90F1225B17D2004CC077 /* CopyFiles */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
dstPath = /usr/share/man/man1/;
|
||||
dstSubfolderSpec = 0;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 1;
|
||||
};
|
||||
/* End PBXCopyFilesBuildPhase section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
49AF90F3225B17D2004CC077 /* ProxyConfig */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = ProxyConfig; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
49AF90F6225B17D2004CC077 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
49AF90F0225B17D2004CC077 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
49AF90EA225B17D2004CC077 = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
49AF90F5225B17D2004CC077 /* ProxyConfig */,
|
||||
49AF90F4225B17D2004CC077 /* Products */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
49AF90F4225B17D2004CC077 /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
49AF90F3225B17D2004CC077 /* ProxyConfig */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
49AF90F5225B17D2004CC077 /* ProxyConfig */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
49AF90F6225B17D2004CC077 /* main.m */,
|
||||
);
|
||||
path = ProxyConfig;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
49AF90F2225B17D2004CC077 /* ProxyConfig */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 49AF90FA225B17D2004CC077 /* Build configuration list for PBXNativeTarget "ProxyConfig" */;
|
||||
buildPhases = (
|
||||
49AF90EF225B17D2004CC077 /* Sources */,
|
||||
49AF90F0225B17D2004CC077 /* Frameworks */,
|
||||
49AF90F1225B17D2004CC077 /* CopyFiles */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = ProxyConfig;
|
||||
productName = ProxyConfig;
|
||||
productReference = 49AF90F3225B17D2004CC077 /* ProxyConfig */;
|
||||
productType = "com.apple.product-type.tool";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
|
||||
/* Begin PBXProject section */
|
||||
49AF90EB225B17D2004CC077 /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastUpgradeCheck = 1020;
|
||||
ORGANIZATIONNAME = west2online;
|
||||
TargetAttributes = {
|
||||
49AF90F2225B17D2004CC077 = {
|
||||
CreatedOnToolsVersion = 10.2;
|
||||
};
|
||||
};
|
||||
};
|
||||
buildConfigurationList = 49AF90EE225B17D2004CC077 /* Build configuration list for PBXProject "ProxyConfig" */;
|
||||
compatibilityVersion = "Xcode 9.3";
|
||||
developmentRegion = en;
|
||||
hasScannedForEncodings = 0;
|
||||
knownRegions = (
|
||||
en,
|
||||
);
|
||||
mainGroup = 49AF90EA225B17D2004CC077;
|
||||
productRefGroup = 49AF90F4225B17D2004CC077 /* Products */;
|
||||
projectDirPath = "";
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
49AF90F2225B17D2004CC077 /* ProxyConfig */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
49AF90EF225B17D2004CC077 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
49AF90F7225B17D2004CC077 /* main.m in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
49AF90F8225B17D2004CC077 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
CODE_SIGN_IDENTITY = "Mac Developer";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_TESTABILITY = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
GCC_DYNAMIC_NO_PIC = NO;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_OPTIMIZATION_LEVEL = 0;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"DEBUG=1",
|
||||
"$(inherited)",
|
||||
);
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.14;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
SDKROOT = macosx;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
49AF90F9225B17D2004CC077 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
CODE_SIGN_IDENTITY = "Mac Developer";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.14;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
MTL_FAST_MATH = YES;
|
||||
SDKROOT = macosx;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
49AF90FB225B17D2004CC077 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CODE_SIGN_IDENTITY = "-";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.10;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
49AF90FC225B17D2004CC077 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CODE_SIGN_IDENTITY = "-";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.10;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
49AF90EE225B17D2004CC077 /* Build configuration list for PBXProject "ProxyConfig" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
49AF90F8225B17D2004CC077 /* Debug */,
|
||||
49AF90F9225B17D2004CC077 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
49AF90FA225B17D2004CC077 /* Build configuration list for PBXNativeTarget "ProxyConfig" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
49AF90FB225B17D2004CC077 /* Debug */,
|
||||
49AF90FC225B17D2004CC077 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
};
|
||||
rootObject = 49AF90EB225B17D2004CC077 /* Project object */;
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "self:ProxyConfig.xcodeproj">
|
||||
</FileRef>
|
||||
</Workspace>
|
|
@ -1,91 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1020"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "49AF90F2225B17D2004CC077"
|
||||
BuildableName = "ProxyConfig"
|
||||
BlueprintName = "ProxyConfig"
|
||||
ReferencedContainer = "container:ProxyConfig.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
</Testables>
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "49AF90F2225B17D2004CC077"
|
||||
BuildableName = "ProxyConfig"
|
||||
BlueprintName = "ProxyConfig"
|
||||
ReferencedContainer = "container:ProxyConfig.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "49AF90F2225B17D2004CC077"
|
||||
BuildableName = "ProxyConfig"
|
||||
BlueprintName = "ProxyConfig"
|
||||
ReferencedContainer = "container:ProxyConfig.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "49AF90F2225B17D2004CC077"
|
||||
BuildableName = "ProxyConfig"
|
||||
BlueprintName = "ProxyConfig"
|
||||
ReferencedContainer = "container:ProxyConfig.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
|
@ -1,13 +0,0 @@
|
|||
#!/bin/sh
|
||||
|
||||
cd "$1";
|
||||
CURRENT=$(./ProxyConfig version);
|
||||
TARGET=$2;
|
||||
if [[ `ls -l ProxyConfig` = *"root"* ]]; then
|
||||
|
||||
if [ "$CURRENT" == "$TARGET" ];then
|
||||
echo "success"
|
||||
exit
|
||||
fi
|
||||
fi
|
||||
echo "false"
|
|
@ -1,4 +0,0 @@
|
|||
cd "$1";
|
||||
sudo chown root:admin "ProxyConfig"
|
||||
sudo chmod +s "ProxyConfig"
|
||||
echo done
|
|
@ -30,5 +30,10 @@
|
|||
<string>Main</string>
|
||||
<key>NSPrincipalClass</key>
|
||||
<string>NSApplication</string>
|
||||
<key>SMPrivilegedExecutables</key>
|
||||
<dict>
|
||||
<key>com.github.erikberglund.SwiftPrivilegedHelper</key>
|
||||
<string>identifier "com.github.erikberglund.SwiftPrivilegedHelper" and anchor apple generic and certificate leaf[subject.CN] = "Mac Developer: chen yicheng (96U846XGYH)" and certificate 1[field.1.2.840.113635.100.6.2.1] /* exists */</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>com.west2online.ClashX.ProxyConfigHelper</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>com.west2online.ClashX.ProxyConfigHelper</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.1</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
<key>SMAuthorizedClients</key>
|
||||
<array>
|
||||
<string>anchor apple generic and identifier "com.west2online.ClashX" and (certificate leaf[field.1.2.840.113635.100.6.1.9] /* exists */ or certificate 1[field.1.2.840.113635.100.6.2.6] /* exists */ and certificate leaf[field.1.2.840.113635.100.6.1.13] /* exists */ and certificate leaf[subject.OU] = MEWHFZ92DY)</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
|
@ -2,7 +2,12 @@
|
|||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>IDEDidComputeMac32BitWarning</key>
|
||||
<true/>
|
||||
<key>Label</key>
|
||||
<string>com.west2online.ClashX.ProxyConfigHelper</string>
|
||||
<key>MachServices</key>
|
||||
<dict>
|
||||
<key>com.west2online.ClashX.ProxyConfigHelper</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
|
@ -0,0 +1,17 @@
|
|||
//
|
||||
// ProxyConfigHelper.h
|
||||
// com.west2online.ClashX.ProxyConfigHelper
|
||||
//
|
||||
// Created by yichengchen on 2019/8/17.
|
||||
// Copyright © 2019 west2online. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface ProxyConfigHelper : NSObject
|
||||
- (void)run;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
|
@ -0,0 +1,116 @@
|
|||
//
|
||||
// ProxyConfigHelper.m
|
||||
// com.west2online.ClashX.ProxyConfigHelper
|
||||
//
|
||||
// Created by yichengchen on 2019/8/17.
|
||||
// Copyright © 2019 west2online. All rights reserved.
|
||||
//
|
||||
|
||||
#import "ProxyConfigHelper.h"
|
||||
#import <AppKit/AppKit.h>
|
||||
#import "ProxyConfigRemoteProcessProtocol.h"
|
||||
#import "ProxySettingTool.h"
|
||||
|
||||
@interface ProxyConfigHelper()
|
||||
<
|
||||
NSXPCListenerDelegate,
|
||||
ProxyConfigRemoteProcessProtocol
|
||||
>
|
||||
|
||||
@property (nonatomic, strong) NSXPCListener *listener;
|
||||
@property (nonatomic, strong) NSMutableSet<NSXPCConnection *> *connections;
|
||||
@property (nonatomic, strong) NSTimer *checkTimer;
|
||||
@property (nonatomic, assign) BOOL shouldQuit;
|
||||
|
||||
@end
|
||||
|
||||
@implementation ProxyConfigHelper
|
||||
- (instancetype)init {
|
||||
|
||||
if (self = [super init]) {
|
||||
self.connections = [NSMutableSet new];
|
||||
self.shouldQuit = NO;
|
||||
self.listener = [[NSXPCListener alloc] initWithMachServiceName:@"com.west2online.ClashX.ProxyConfigHelper"];
|
||||
self.listener.delegate = self;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)run {
|
||||
[self.listener resume];
|
||||
self.checkTimer =
|
||||
[NSTimer timerWithTimeInterval:5.f target:self selector:@selector(connectionCheckOnLaunch) userInfo:nil repeats:NO];
|
||||
[[NSRunLoop currentRunLoop] addTimer:self.checkTimer forMode:NSDefaultRunLoopMode];
|
||||
while (!self.shouldQuit) {
|
||||
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:2.0]];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)connectionCheckOnLaunch {
|
||||
if (self.connections.count == 0) {
|
||||
self.shouldQuit = YES;
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)connectionIsVaild: (NSXPCConnection *)connection {
|
||||
NSRunningApplication *remoteApp =
|
||||
[NSRunningApplication runningApplicationWithProcessIdentifier:connection.processIdentifier];
|
||||
return remoteApp != nil;
|
||||
}
|
||||
|
||||
// MARK: - NSXPCListenerDelegate
|
||||
|
||||
- (BOOL)listener:(NSXPCListener *)listener shouldAcceptNewConnection:(NSXPCConnection *)newConnection {
|
||||
if (![self connectionIsVaild:newConnection]) {
|
||||
return NO;
|
||||
}
|
||||
newConnection.exportedInterface = [NSXPCInterface interfaceWithProtocol:@protocol(ProxyConfigRemoteProcessProtocol)];
|
||||
newConnection.exportedObject = self;
|
||||
__weak NSXPCConnection *weakConnection = newConnection;
|
||||
__weak ProxyConfigHelper *weakSelf = self;
|
||||
newConnection.invalidationHandler = ^{
|
||||
[weakSelf.connections removeObject:weakConnection];
|
||||
if (weakSelf.connections.count == 0) {
|
||||
weakSelf.shouldQuit = YES;
|
||||
}
|
||||
};
|
||||
[self.connections addObject:newConnection];
|
||||
[newConnection resume];
|
||||
return YES;
|
||||
}
|
||||
|
||||
// MARK: - ProxyConfigRemoteProcessProtocol
|
||||
- (void)getVersion:(stringReplyBlock)reply {
|
||||
NSString *version = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"];
|
||||
if (version == nil) {
|
||||
version = @"unknown";
|
||||
}
|
||||
reply(version);
|
||||
}
|
||||
|
||||
- (void)enableProxyWithPort:(int)port
|
||||
socksPort:(int)socksPort
|
||||
authData:(NSData *)authData
|
||||
error:(stringReplyBlock)reply {
|
||||
ProxySettingTool *tool = [ProxySettingTool new];
|
||||
NSString *err = [tool setupAuth:authData];
|
||||
if (err != nil) {
|
||||
reply(err);
|
||||
return;
|
||||
}
|
||||
[tool enableProxyWithport:port socksPort:socksPort];
|
||||
reply(nil);
|
||||
}
|
||||
|
||||
- (void)disableProxyWithAuthData:(NSData *)authData error:(stringReplyBlock)reply {
|
||||
ProxySettingTool *tool = [ProxySettingTool new];
|
||||
NSString *err = [tool setupAuth:authData];
|
||||
if (err != nil) {
|
||||
reply(err);
|
||||
return;
|
||||
}
|
||||
[tool disableProxy];
|
||||
reply(nil);
|
||||
}
|
||||
|
||||
@end
|
|
@ -0,0 +1,26 @@
|
|||
//
|
||||
// ProxyConfigRemoteProcessProtocol.h
|
||||
// com.west2online.ClashX.ProxyConfigHelper
|
||||
//
|
||||
// Created by yichengchen on 2019/8/17.
|
||||
// Copyright © 2019 west2online. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
typedef void(^stringReplyBlock)(NSString *);
|
||||
typedef void(^boolReplyBlock)(BOOL);
|
||||
|
||||
@protocol ProxyConfigRemoteProcessProtocol <NSObject>
|
||||
@required
|
||||
|
||||
- (void)getVersion:(stringReplyBlock)reply;
|
||||
|
||||
|
||||
- (void)enableProxyWithPort:(int)port
|
||||
socksPort:(int)socksPort
|
||||
authData:(NSData *)authData
|
||||
error:(stringReplyBlock)reply;
|
||||
|
||||
- (void)disableProxyWithAuthData:(NSData *)authData error:(stringReplyBlock)reply;
|
||||
@end
|
|
@ -0,0 +1,20 @@
|
|||
//
|
||||
// ProxySettingTool.h
|
||||
// com.west2online.ClashX.ProxyConfigHelper
|
||||
//
|
||||
// Created by yichengchen on 2019/8/17.
|
||||
// Copyright © 2019 west2online. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface ProxySettingTool : NSObject
|
||||
|
||||
- (NSString *)setupAuth:(NSData *)authData;
|
||||
- (void)enableProxyWithport:(int)port socksPort:(int)socksPort;
|
||||
- (void)disableProxy;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
|
@ -0,0 +1,199 @@
|
|||
//
|
||||
// ProxySettingTool.m
|
||||
// com.west2online.ClashX.ProxyConfigHelper
|
||||
//
|
||||
// Created by yichengchen on 2019/8/17.
|
||||
// Copyright © 2019 west2online. All rights reserved.
|
||||
//
|
||||
|
||||
#import "ProxySettingTool.h"
|
||||
#import <SystemConfiguration/SystemConfiguration.h>
|
||||
#import <AppKit/AppKit.h>
|
||||
|
||||
@interface ProxySettingTool()
|
||||
@property (nonatomic, assign) AuthorizationRef authRef;
|
||||
|
||||
@end
|
||||
|
||||
@implementation ProxySettingTool
|
||||
|
||||
// MARK: - Public
|
||||
|
||||
- (void)enableProxyWithport:(int)port socksPort:(int)socksPort {
|
||||
[self applySCNetworkSettingWithRef:^(SCPreferencesRef ref) {
|
||||
[self getDiviceListWithPrefRef:ref devices:^(NSString *key) {
|
||||
[self enableProxySettings:ref interface:key port:port socksPort:socksPort];
|
||||
}];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)disableProxy {
|
||||
[self applySCNetworkSettingWithRef:^(SCPreferencesRef ref) {
|
||||
[self getDiviceListWithPrefRef:ref devices:^(NSString *key) {
|
||||
[self disableProxySetting:ref interface:key];
|
||||
}];
|
||||
}];
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
- (void)dealloc {
|
||||
[self freeAuth];
|
||||
}
|
||||
|
||||
- (NSArray<NSString *> *)getIgnoreList {
|
||||
NSString *configPath = [NSHomeDirectory() stringByAppendingString:@"/.config/clash/proxyIgnoreList.plist"];
|
||||
if ([NSFileManager.defaultManager fileExistsAtPath:configPath]) {
|
||||
NSArray *arr = [[NSArray alloc] initWithContentsOfFile:configPath];
|
||||
if (arr != nil && arr.count > 0 && [arr containsObject:@"127.0.0.1"]) {
|
||||
return arr;
|
||||
}
|
||||
}
|
||||
NSArray *ignoreList = @[
|
||||
@"192.168.0.0/16",
|
||||
@"10.0.0.0/8",
|
||||
@"172.16.0.0/12",
|
||||
@"127.0.0.1",
|
||||
@"localhost",
|
||||
@"*.local",
|
||||
@"*.crashlytics.com"
|
||||
];
|
||||
return ignoreList;
|
||||
}
|
||||
|
||||
- (NSDictionary *)getProxySetting:(BOOL)enable port:(int) port socksPort: (int)socksPort {
|
||||
NSMutableDictionary *proxySettings = [NSMutableDictionary dictionary];
|
||||
|
||||
NSString *ip = enable ? @"127.0.0.1" : @"";
|
||||
NSInteger enableInt = enable ? 1 : 0;
|
||||
|
||||
proxySettings[(NSString *)kCFNetworkProxiesHTTPProxy] = ip;
|
||||
proxySettings[(NSString *)kCFNetworkProxiesHTTPEnable] = @(enableInt);
|
||||
proxySettings[(NSString *)kCFNetworkProxiesHTTPSProxy] = ip;
|
||||
proxySettings[(NSString *)kCFNetworkProxiesHTTPSEnable] = @(enableInt);
|
||||
|
||||
proxySettings[(NSString *)kCFNetworkProxiesSOCKSProxy] = ip;
|
||||
proxySettings[(NSString *)kCFNetworkProxiesSOCKSEnable] = @(enableInt);
|
||||
|
||||
if (enable) {
|
||||
proxySettings[(NSString *)kCFNetworkProxiesHTTPPort] = @(port);
|
||||
proxySettings[(NSString *)kCFNetworkProxiesHTTPSPort] = @(port);
|
||||
proxySettings[(NSString *)kCFNetworkProxiesSOCKSPort] = @(socksPort);
|
||||
} else {
|
||||
proxySettings[(NSString *)kCFNetworkProxiesHTTPPort] = nil;
|
||||
proxySettings[(NSString *)kCFNetworkProxiesHTTPSPort] = nil;
|
||||
proxySettings[(NSString *)kCFNetworkProxiesSOCKSPort] = nil;
|
||||
}
|
||||
|
||||
|
||||
proxySettings[(NSString *)kCFNetworkProxiesExceptionsList] = [self getIgnoreList];
|
||||
|
||||
return proxySettings;
|
||||
}
|
||||
|
||||
- (NSString *)proxySettingPathWithInterface:(NSString *)interfaceKey {
|
||||
return [NSString stringWithFormat:@"/%@/%@/%@",
|
||||
(NSString *)kSCPrefNetworkServices,
|
||||
interfaceKey,
|
||||
(NSString *)kSCEntNetProxies];
|
||||
}
|
||||
|
||||
- (void)enableProxySettings:(SCPreferencesRef)prefs
|
||||
interface:(NSString *)interfaceKey
|
||||
port:(int) port
|
||||
socksPort:(int) socksPort {
|
||||
|
||||
NSDictionary *proxySettings = [self getProxySetting:YES port:port socksPort:socksPort];
|
||||
NSString *path = [self proxySettingPathWithInterface:interfaceKey];
|
||||
SCPreferencesPathSetValue(prefs,
|
||||
(__bridge CFStringRef)path,
|
||||
(__bridge CFDictionaryRef)proxySettings);
|
||||
}
|
||||
|
||||
- (void)disableProxySetting:(SCPreferencesRef)prefs
|
||||
interface:(NSString *)interfaceKey {
|
||||
|
||||
NSDictionary *proxySettings = [self getProxySetting:NO port:0 socksPort:0];
|
||||
NSString *path = [self proxySettingPathWithInterface:interfaceKey];
|
||||
SCPreferencesPathSetValue(prefs,
|
||||
(__bridge CFStringRef)path,
|
||||
(__bridge CFDictionaryRef)proxySettings);
|
||||
}
|
||||
|
||||
- (void)getDiviceListWithPrefRef:(SCPreferencesRef)ref devices:(void(^)(NSString *))callback {
|
||||
NSDictionary *sets = (__bridge NSDictionary *)SCPreferencesGetValue(ref, kSCPrefNetworkServices);
|
||||
for (NSString *key in [sets allKeys]) {
|
||||
NSMutableDictionary *dict = [sets objectForKey:key];
|
||||
NSString *hardware = [dict valueForKeyPath:@"Interface.Hardware"];
|
||||
if ([hardware isEqualToString:@"AirPort"]
|
||||
|| [hardware isEqualToString:@"Wi-Fi"]
|
||||
|| [hardware isEqualToString:@"Ethernet"]) {
|
||||
callback(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)applySCNetworkSettingWithRef:(void(^)(SCPreferencesRef))callback {
|
||||
SCPreferencesRef ref = SCPreferencesCreateWithAuthorization(nil, CFSTR("com.west2online.ClashX.ProxyConfigHelper.config"), nil, self.authRef);
|
||||
if (!ref) {
|
||||
return;
|
||||
}
|
||||
callback(ref);
|
||||
|
||||
SCPreferencesCommitChanges(ref);
|
||||
SCPreferencesApplyChanges(ref);
|
||||
SCPreferencesSynchronize(ref);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
- (AuthorizationFlags)authFlags {
|
||||
AuthorizationFlags authFlags = kAuthorizationFlagDefaults
|
||||
| kAuthorizationFlagExtendRights
|
||||
| kAuthorizationFlagInteractionAllowed
|
||||
| kAuthorizationFlagPreAuthorize;
|
||||
return authFlags;
|
||||
}
|
||||
|
||||
- (NSString *)setupAuth:(NSData *)authData {
|
||||
if (authData.length == 0 || authData.length != kAuthorizationExternalFormLength) {
|
||||
return @"PrivilegedTaskRunnerHelper: Authorization data is malformed";
|
||||
}
|
||||
AuthorizationRef authRef;
|
||||
|
||||
OSStatus status = AuthorizationCreateFromExternalForm([authData bytes],&authRef);
|
||||
if (status != errAuthorizationSuccess) {
|
||||
return @"AuthorizationCreateFromExternalForm fail";
|
||||
}
|
||||
|
||||
NSString *authName = @"com.west2online.ClashX.ProxyConfigHelper.config";
|
||||
AuthorizationItem authItem = {authName.UTF8String, 0, NULL, 0};
|
||||
AuthorizationRights authRight = {1, &authItem};
|
||||
|
||||
AuthorizationFlags authFlags = [self authFlags];
|
||||
|
||||
status = AuthorizationCopyRights(authRef, &authRight, nil, authFlags, nil);
|
||||
if (status != errAuthorizationSuccess) {
|
||||
AuthorizationFree(authRef, authFlags);
|
||||
return @"AuthorizationCopyRights fail";
|
||||
}
|
||||
|
||||
OSStatus authErr = AuthorizationCreate(nil, kAuthorizationEmptyEnvironment, authFlags, &authRef);
|
||||
|
||||
if (authErr != noErr) {
|
||||
AuthorizationFree(authRef, authFlags);
|
||||
return @"AuthorizationCreate fail";
|
||||
}
|
||||
self.authRef = authRef;
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (void)freeAuth {
|
||||
if (self.authRef) {
|
||||
AuthorizationFree(self.authRef, [self authFlags]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@end
|
|
@ -0,0 +1,17 @@
|
|||
//
|
||||
// main.m
|
||||
// ProxyConfigHelper
|
||||
//
|
||||
// Created by yichengchen on 2019/8/16.
|
||||
// Copyright © 2019 west2online. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "ProxyConfigHelper.h"
|
||||
int main(int argc, const char * argv[]) {
|
||||
@autoreleasepool {
|
||||
[[ProxyConfigHelper new] run];
|
||||
NSLog(@"ProxyConfigHelper exit");
|
||||
}
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,441 @@
|
|||
#! /usr/bin/python
|
||||
#
|
||||
# File: SMJobBlessUtil.py
|
||||
#
|
||||
# Contains: Tool for checking and correcting apps that use SMJobBless.
|
||||
#
|
||||
# Written by: DTS
|
||||
#
|
||||
# Copyright: Copyright (c) 2012 Apple Inc. All Rights Reserved.
|
||||
#
|
||||
# Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple Inc.
|
||||
# ("Apple") in consideration of your agreement to the following
|
||||
# terms, and your use, installation, modification or
|
||||
# redistribution of this Apple software constitutes acceptance of
|
||||
# these terms. If you do not agree with these terms, please do
|
||||
# not use, install, modify or redistribute this Apple software.
|
||||
#
|
||||
# In consideration of your agreement to abide by the following
|
||||
# terms, and subject to these terms, Apple grants you a personal,
|
||||
# non-exclusive license, under Apple's copyrights in this
|
||||
# original Apple software (the "Apple Software"), to use,
|
||||
# reproduce, modify and redistribute the Apple Software, with or
|
||||
# without modifications, in source and/or binary forms; provided
|
||||
# that if you redistribute the Apple Software in its entirety and
|
||||
# without modifications, you must retain this notice and the
|
||||
# following text and disclaimers in all such redistributions of
|
||||
# the Apple Software. Neither the name, trademarks, service marks
|
||||
# or logos of Apple Inc. may be used to endorse or promote
|
||||
# products derived from the Apple Software without specific prior
|
||||
# written permission from Apple. Except as expressly stated in
|
||||
# this notice, no other rights or licenses, express or implied,
|
||||
# are granted by Apple herein, including but not limited to any
|
||||
# patent rights that may be infringed by your derivative works or
|
||||
# by other works in which the Apple Software may be incorporated.
|
||||
#
|
||||
# The Apple Software is provided by Apple on an "AS IS" basis.
|
||||
# APPLE MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING
|
||||
# WITHOUT LIMITATION THE IMPLIED WARRANTIES OF NON-INFRINGEMENT,
|
||||
# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, REGARDING
|
||||
# THE APPLE SOFTWARE OR ITS USE AND OPERATION ALONE OR IN
|
||||
# COMBINATION WITH YOUR PRODUCTS.
|
||||
#
|
||||
# IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT,
|
||||
# INCIDENTAL OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
|
||||
# TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) ARISING IN ANY WAY
|
||||
# OUT OF THE USE, REPRODUCTION, MODIFICATION AND/OR DISTRIBUTION
|
||||
# OF THE APPLE SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY
|
||||
# OF CONTRACT, TORT (INCLUDING NEGLIGENCE), STRICT LIABILITY OR
|
||||
# OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
# SUCH DAMAGE.
|
||||
#
|
||||
|
||||
import sys
|
||||
import os
|
||||
import getopt
|
||||
import subprocess
|
||||
import plistlib
|
||||
import operator
|
||||
|
||||
class UsageException (Exception):
|
||||
"""
|
||||
Raised when the progam detects a usage issue; the top-level code catches this
|
||||
and prints a usage message.
|
||||
"""
|
||||
pass
|
||||
|
||||
class CheckException (Exception):
|
||||
"""
|
||||
Raised when the "check" subcommand detects a problem; the top-level code catches
|
||||
this and prints a nice error message.
|
||||
"""
|
||||
def __init__(self, message, path=None):
|
||||
self.message = message
|
||||
self.path = path
|
||||
|
||||
def checkCodeSignature(programPath, programType):
|
||||
"""Checks the code signature of the referenced program."""
|
||||
|
||||
# Use the codesign tool to check the signature. The second "-v" is required to enable
|
||||
# verbose mode, which causes codesign to do more checking. By default it does the minimum
|
||||
# amount of checking ("Is the program properly signed?"). If you enabled verbose mode it
|
||||
# does other sanity checks, which we definitely want. The specific thing I'd like to
|
||||
# detect is "Does the code satisfy its own designated requirement?" and I need to enable
|
||||
# verbose mode to get that.
|
||||
|
||||
args = [
|
||||
# "false",
|
||||
"codesign",
|
||||
"-v",
|
||||
"-v",
|
||||
programPath
|
||||
]
|
||||
try:
|
||||
subprocess.check_call(args, stderr=open("/dev/null"))
|
||||
except subprocess.CalledProcessError, e:
|
||||
raise CheckException("%s code signature invalid" % programType, programPath)
|
||||
|
||||
def readDesignatedRequirement(programPath, programType):
|
||||
"""Returns the designated requirement of the program as a string."""
|
||||
args = [
|
||||
# "false",
|
||||
"codesign",
|
||||
"-d",
|
||||
"-r",
|
||||
"-",
|
||||
programPath
|
||||
]
|
||||
try:
|
||||
req = subprocess.check_output(args, stderr=open("/dev/null"))
|
||||
except subprocess.CalledProcessError, e:
|
||||
raise CheckException("%s designated requirement unreadable" % programType, programPath)
|
||||
|
||||
reqLines = req.splitlines()
|
||||
if len(reqLines) != 1 or not req.startswith("designated => "):
|
||||
raise CheckException("%s designated requirement malformed" % programType, programPath)
|
||||
return reqLines[0][len("designated => "):]
|
||||
|
||||
def readInfoPlistFromPath(infoPath):
|
||||
"""Reads an "Info.plist" file from the specified path."""
|
||||
try:
|
||||
info = plistlib.readPlist(infoPath)
|
||||
except:
|
||||
raise CheckException("'Info.plist' not readable", infoPath)
|
||||
if not isinstance(info, dict):
|
||||
raise CheckException("'Info.plist' root must be a dictionary", infoPath)
|
||||
return info
|
||||
|
||||
def readPlistFromToolSection(toolPath, segmentName, sectionName):
|
||||
"""Reads a dictionary property list from the specified section within the specified executable."""
|
||||
|
||||
# Run otool -s to get a hex dump of the section.
|
||||
|
||||
args = [
|
||||
# "false",
|
||||
"otool",
|
||||
"-s",
|
||||
segmentName,
|
||||
sectionName,
|
||||
toolPath
|
||||
]
|
||||
try:
|
||||
plistDump = subprocess.check_output(args)
|
||||
except subprocess.CalledProcessError, e:
|
||||
raise CheckException("tool %s / %s section unreadable" % (segmentName, sectionName), toolPath)
|
||||
|
||||
# Convert that hex dump to an property list.
|
||||
|
||||
plistLines = plistDump.splitlines()
|
||||
if len(plistLines) < 3 or plistLines[1] != ("Contents of (%s,%s) section" % (segmentName, sectionName)):
|
||||
raise CheckException("tool %s / %s section dump malformed (1)" % (segmentName, sectionName), toolPath)
|
||||
del plistLines[0:2]
|
||||
|
||||
try:
|
||||
bytes = []
|
||||
for line in plistLines:
|
||||
# line looks like this:
|
||||
#
|
||||
# '0000000100000b80\t3c 3f 78 6d 6c 20 76 65 72 73 69 6f 6e 3d 22 31 '
|
||||
columns = line.split("\t")
|
||||
assert len(columns) == 2
|
||||
for hexStr in columns[1].split():
|
||||
bytes.append(int(hexStr, 16))
|
||||
plist = plistlib.readPlistFromString(bytearray(bytes))
|
||||
except:
|
||||
raise CheckException("tool %s / %s section dump malformed (2)" % (segmentName, sectionName), toolPath)
|
||||
|
||||
# Check the root of the property list.
|
||||
|
||||
if not isinstance(plist, dict):
|
||||
raise CheckException("tool %s / %s property list root must be a dictionary" % (segmentName, sectionName), toolPath)
|
||||
|
||||
return plist
|
||||
|
||||
def checkStep1(appPath):
|
||||
"""Checks that the app and the tool are both correctly code signed."""
|
||||
|
||||
if not os.path.isdir(appPath):
|
||||
raise CheckException("app not found", appPath)
|
||||
|
||||
# Check the app's code signature.
|
||||
|
||||
checkCodeSignature(appPath, "app")
|
||||
|
||||
# Check the tool directory.
|
||||
|
||||
toolDirPath = os.path.join(appPath, "Contents", "Library", "LaunchServices")
|
||||
if not os.path.isdir(toolDirPath):
|
||||
raise CheckException("tool directory not found", toolDirPath)
|
||||
|
||||
# Check each tool's code signature.
|
||||
|
||||
toolPathList = []
|
||||
for toolName in os.listdir(toolDirPath):
|
||||
if toolName != ".DS_Store":
|
||||
toolPath = os.path.join(toolDirPath, toolName)
|
||||
if not os.path.isfile(toolPath):
|
||||
raise CheckException("tool directory contains a directory", toolPath)
|
||||
checkCodeSignature(toolPath, "tool")
|
||||
toolPathList.append(toolPath)
|
||||
|
||||
# Check that we have at least one tool.
|
||||
|
||||
if len(toolPathList) == 0:
|
||||
raise CheckException("no tools found", toolDirPath)
|
||||
|
||||
return toolPathList
|
||||
|
||||
def checkStep2(appPath, toolPathList):
|
||||
"""Checks the SMPrivilegedExecutables entry in the app's "Info.plist"."""
|
||||
|
||||
# Create a map from the tool name (not path) to its designated requirement.
|
||||
|
||||
toolNameToReqMap = dict()
|
||||
for toolPath in toolPathList:
|
||||
req = readDesignatedRequirement(toolPath, "tool")
|
||||
toolNameToReqMap[os.path.basename(toolPath)] = req
|
||||
|
||||
# Read the Info.plist for the app and extract the SMPrivilegedExecutables value.
|
||||
|
||||
infoPath = os.path.join(appPath, "Contents", "Info.plist")
|
||||
info = readInfoPlistFromPath(infoPath)
|
||||
if not info.has_key("SMPrivilegedExecutables"):
|
||||
raise CheckException("'SMPrivilegedExecutables' not found", infoPath)
|
||||
infoToolDict = info["SMPrivilegedExecutables"]
|
||||
if not isinstance(infoToolDict, dict):
|
||||
raise CheckException("'SMPrivilegedExecutables' must be a dictionary", infoPath)
|
||||
|
||||
# Check that the list of tools matches the list of SMPrivilegedExecutables entries.
|
||||
|
||||
if sorted(infoToolDict.keys()) != sorted(toolNameToReqMap.keys()):
|
||||
raise CheckException("'SMPrivilegedExecutables' and tools in 'Contents/Library/LaunchServices' don't match")
|
||||
|
||||
# Check that all the requirements match.
|
||||
|
||||
# This is an interesting policy choice. Technically the tool just needs to match
|
||||
# the requirement listed in SMPrivilegedExecutables, and we can check that by
|
||||
# putting the requirement into tmp.req and then running
|
||||
#
|
||||
# $ codesign -v -R tmp.req /path/to/tool
|
||||
#
|
||||
# However, for a Developer ID signed tool we really want to have the SMPrivilegedExecutables
|
||||
# entry contain the tool's designated requirement because Xcode has built a
|
||||
# more complex DR that does lots of useful and important checks. So, as a matter
|
||||
# of policy we require that the value in SMPrivilegedExecutables match the tool's DR.
|
||||
|
||||
for toolName in infoToolDict:
|
||||
if infoToolDict[toolName] != toolNameToReqMap[toolName]:
|
||||
raise CheckException("tool designated requirement (%s) doesn't match entry in 'SMPrivilegedExecutables' (%s)" % (toolNameToReqMap[toolName], infoToolDict[toolName]))
|
||||
|
||||
def checkStep3(appPath, toolPathList):
|
||||
"""Checks the "Info.plist" embedded in each helper tool."""
|
||||
|
||||
# First get the app's designated requirement.
|
||||
|
||||
appReq = readDesignatedRequirement(appPath, "app")
|
||||
|
||||
# Then check that the tool's SMAuthorizedClients value matches it.
|
||||
|
||||
for toolPath in toolPathList:
|
||||
info = readPlistFromToolSection(toolPath, "__TEXT", "__info_plist")
|
||||
|
||||
if not info.has_key("CFBundleInfoDictionaryVersion") or info["CFBundleInfoDictionaryVersion"] != "6.0":
|
||||
raise CheckException("'CFBundleInfoDictionaryVersion' in tool __TEXT / __info_plist section must be '6.0'", toolPath)
|
||||
|
||||
if not info.has_key("CFBundleIdentifier") or info["CFBundleIdentifier"] != os.path.basename(toolPath):
|
||||
raise CheckException("'CFBundleIdentifier' in tool __TEXT / __info_plist section must match tool name", toolPath)
|
||||
|
||||
if not info.has_key("SMAuthorizedClients"):
|
||||
raise CheckException("'SMAuthorizedClients' in tool __TEXT / __info_plist section not found", toolPath)
|
||||
infoClientList = info["SMAuthorizedClients"]
|
||||
if not isinstance(infoClientList, list):
|
||||
raise CheckException("'SMAuthorizedClients' in tool __TEXT / __info_plist section must be an array", toolPath)
|
||||
if len(infoClientList) != 1:
|
||||
raise CheckException("'SMAuthorizedClients' in tool __TEXT / __info_plist section must have one entry", toolPath)
|
||||
|
||||
# Again, as a matter of policy we require that the SMAuthorizedClients entry must
|
||||
# match exactly the designated requirement of the app.
|
||||
|
||||
if infoClientList[0] != appReq:
|
||||
raise CheckException("app designated requirement (%s) doesn't match entry in 'SMAuthorizedClients' (%s)" % (appReq, infoClientList[0]), toolPath)
|
||||
|
||||
def checkStep4(appPath, toolPathList):
|
||||
"""Checks the "launchd.plist" embedded in each helper tool."""
|
||||
|
||||
for toolPath in toolPathList:
|
||||
launchd = readPlistFromToolSection(toolPath, "__TEXT", "__launchd_plist")
|
||||
|
||||
if not launchd.has_key("Label") or launchd["Label"] != os.path.basename(toolPath):
|
||||
raise CheckException("'Label' in tool __TEXT / __launchd_plist section must match tool name", toolPath)
|
||||
|
||||
# We don't need to check that the label matches the bundle identifier because
|
||||
# we know it matches the tool name and step 4 checks that the tool name matches
|
||||
# the bundle identifier.
|
||||
|
||||
def checkStep5(appPath):
|
||||
"""There's nothing to do here; we effectively checked for this is steps 1 and 2."""
|
||||
pass
|
||||
|
||||
def check(appPath):
|
||||
"""Checks the SMJobBless setup of the specified app."""
|
||||
|
||||
# Each of the following steps matches a bullet point in the SMJobBless header doc.
|
||||
|
||||
toolPathList = checkStep1(appPath)
|
||||
|
||||
checkStep2(appPath, toolPathList)
|
||||
|
||||
checkStep3(appPath, toolPathList)
|
||||
|
||||
checkStep4(appPath, toolPathList)
|
||||
|
||||
checkStep5(appPath)
|
||||
|
||||
def setreq(appPath, appInfoPlistPath, toolInfoPlistPaths):
|
||||
"""
|
||||
Reads information from the built app and uses it to set the SMJobBless setup
|
||||
in the specified app and tool Info.plist source files.
|
||||
"""
|
||||
|
||||
if not os.path.isdir(appPath):
|
||||
raise CheckException("app not found", appPath)
|
||||
|
||||
if not os.path.isfile(appInfoPlistPath):
|
||||
raise CheckException("app 'Info.plist' not found", appInfoPlistPath)
|
||||
for toolInfoPlistPath in toolInfoPlistPaths:
|
||||
if not os.path.isfile(toolInfoPlistPath):
|
||||
raise CheckException("app 'Info.plist' not found", toolInfoPlistPath)
|
||||
|
||||
# Get the designated requirement for the app and each of the tools.
|
||||
|
||||
appReq = readDesignatedRequirement(appPath, "app")
|
||||
|
||||
toolDirPath = os.path.join(appPath, "Contents", "Library", "LaunchServices")
|
||||
if not os.path.isdir(toolDirPath):
|
||||
raise CheckException("tool directory not found", toolDirPath)
|
||||
|
||||
toolNameToReqMap = {}
|
||||
print os.listdir(toolDirPath)
|
||||
for toolName in os.listdir(toolDirPath):
|
||||
req = readDesignatedRequirement(os.path.join(toolDirPath, toolName), "tool")
|
||||
print '-----'
|
||||
print toolName
|
||||
print '-----'
|
||||
toolNameToReqMap[toolName] = req
|
||||
|
||||
if len(toolNameToReqMap) > len(toolInfoPlistPaths):
|
||||
raise CheckException("tool directory has more tools (%d) than you've supplied tool 'Info.plist' paths (%d)" % (len(toolNameToReqMap), len(toolInfoPlistPaths)), toolDirPath)
|
||||
if len(toolNameToReqMap) < len(toolInfoPlistPaths):
|
||||
raise CheckException("tool directory has fewer tools (%d) than you've supplied tool 'Info.plist' paths (%d)" % (len(toolNameToReqMap), len(toolInfoPlistPaths)), toolDirPath)
|
||||
|
||||
# Build the new value for SMPrivilegedExecutables.
|
||||
|
||||
appToolDict = {}
|
||||
toolInfoPlistPathToToolInfoMap = {}
|
||||
for toolInfoPlistPath in toolInfoPlistPaths:
|
||||
toolInfo = readInfoPlistFromPath(toolInfoPlistPath)
|
||||
toolInfoPlistPathToToolInfoMap[toolInfoPlistPath] = toolInfo
|
||||
if not toolInfo.has_key("CFBundleIdentifier"):
|
||||
raise CheckException("'CFBundleIdentifier' not found", toolInfoPlistPath)
|
||||
bundleID = toolInfo["CFBundleIdentifier"]
|
||||
if not isinstance(bundleID, basestring):
|
||||
raise CheckException("'CFBundleIdentifier' must be a string", toolInfoPlistPath)
|
||||
appToolDict[bundleID] = toolNameToReqMap[bundleID]
|
||||
|
||||
# Set the SMPrivilegedExecutables value in the app "Info.plist".
|
||||
|
||||
appInfo = readInfoPlistFromPath(appInfoPlistPath)
|
||||
needsUpdate = not appInfo.has_key("SMPrivilegedExecutables")
|
||||
if not needsUpdate:
|
||||
oldAppToolDict = appInfo["SMPrivilegedExecutables"]
|
||||
if not isinstance(oldAppToolDict, dict):
|
||||
raise CheckException("'SMPrivilegedExecutables' must be a dictionary", appInfoPlistPath)
|
||||
appToolDictSorted = sorted(appToolDict.iteritems(), key=operator.itemgetter(0))
|
||||
oldAppToolDictSorted = sorted(oldAppToolDict.iteritems(), key=operator.itemgetter(0))
|
||||
needsUpdate = (appToolDictSorted != oldAppToolDictSorted)
|
||||
|
||||
if needsUpdate:
|
||||
appInfo["SMPrivilegedExecutables"] = appToolDict
|
||||
plistlib.writePlist(appInfo, appInfoPlistPath)
|
||||
print >> sys.stdout, "%s: updated" % appInfoPlistPath
|
||||
|
||||
# Set the SMAuthorizedClients value in each tool's "Info.plist".
|
||||
|
||||
toolAppListSorted = [ appReq ] # only one element, so obviously sorted (-:
|
||||
for toolInfoPlistPath in toolInfoPlistPaths:
|
||||
toolInfo = toolInfoPlistPathToToolInfoMap[toolInfoPlistPath]
|
||||
|
||||
needsUpdate = not toolInfo.has_key("SMAuthorizedClients")
|
||||
if not needsUpdate:
|
||||
oldToolAppList = toolInfo["SMAuthorizedClients"]
|
||||
if not isinstance(oldToolAppList, list):
|
||||
raise CheckException("'SMAuthorizedClients' must be an array", toolInfoPlistPath)
|
||||
oldToolAppListSorted = sorted(oldToolAppList)
|
||||
needsUpdate = (toolAppListSorted != oldToolAppListSorted)
|
||||
|
||||
if needsUpdate:
|
||||
toolInfo["SMAuthorizedClients"] = toolAppListSorted
|
||||
plistlib.writePlist(toolInfo, toolInfoPlistPath)
|
||||
print >> sys.stdout, "%s: updated" % toolInfoPlistPath
|
||||
|
||||
def main():
|
||||
options, appArgs = getopt.getopt(sys.argv[1:], "d")
|
||||
|
||||
debug = False
|
||||
for opt, val in options:
|
||||
if opt == "-d":
|
||||
debug = True
|
||||
else:
|
||||
raise UsageException()
|
||||
|
||||
if len(appArgs) == 0:
|
||||
raise UsageException()
|
||||
command = appArgs[0]
|
||||
if command == "check":
|
||||
if len(appArgs) != 2:
|
||||
raise UsageException()
|
||||
check(appArgs[1])
|
||||
elif command == "setreq":
|
||||
if len(appArgs) < 4:
|
||||
raise UsageException()
|
||||
setreq(appArgs[1], appArgs[2], appArgs[3:])
|
||||
else:
|
||||
raise UsageException()
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
main()
|
||||
except CheckException, e:
|
||||
if e.path is None:
|
||||
print >> sys.stderr, "%s: %s" % (os.path.basename(sys.argv[0]), e.message)
|
||||
else:
|
||||
path = e.path
|
||||
if path.endswith("/"):
|
||||
path = path[:-1]
|
||||
print >> sys.stderr, "%s: %s" % (path, e.message)
|
||||
sys.exit(1)
|
||||
except UsageException, e:
|
||||
print >> sys.stderr, "usage: %s check /path/to/app" % os.path.basename(sys.argv[0])
|
||||
print >> sys.stderr, " %s setreq /path/to/app /path/to/app/Info.plist /path/to/tool/Info.plist..." % os.path.basename(sys.argv[0])
|
||||
sys.exit(1)
|
Loading…
Reference in New Issue