mirror of
https://github.com/davidwernhart/AlDente-Charge-Limiter.git
synced 2024-08-16 16:20:33 +03:00
Apple Silicon Support
This commit is contained in:
commit
77119a1a13
567
AlDente.xcodeproj/project.pbxproj
Normal file
567
AlDente.xcodeproj/project.pbxproj
Normal file
@ -0,0 +1,567 @@
|
|||||||
|
// !$*UTF8*$!
|
||||||
|
{
|
||||||
|
archiveVersion = 1;
|
||||||
|
classes = {
|
||||||
|
};
|
||||||
|
objectVersion = 54;
|
||||||
|
objects = {
|
||||||
|
|
||||||
|
/* Begin PBXBuildFile section */
|
||||||
|
505E96C92586211600DAA405 /* LaunchAtLogin in Frameworks */ = {isa = PBXBuildFile; productRef = 505E96C82586211600DAA405 /* LaunchAtLogin */; };
|
||||||
|
9224A08723F1F70600961AC4 /* com.davidwernhart.Helper in CopyFiles */ = {isa = PBXBuildFile; fileRef = 9283741823F1F34400B8AE7A /* com.davidwernhart.Helper */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
|
||||||
|
927DBFC923F5478E00F8BF0D /* HelperToolProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 927DBFC823F5478E00F8BF0D /* HelperToolProtocol.swift */; };
|
||||||
|
927DBFCA23F5478E00F8BF0D /* HelperToolProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 927DBFC823F5478E00F8BF0D /* HelperToolProtocol.swift */; };
|
||||||
|
9283741B23F1F34400B8AE7A /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9283741A23F1F34400B8AE7A /* main.swift */; };
|
||||||
|
928674C025D07BA300EC79C1 /* PersistanceManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 928674BF25D07BA300EC79C1 /* PersistanceManager.swift */; };
|
||||||
|
92981EDB23F08D9B00C05424 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 92981EDA23F08D9B00C05424 /* AppDelegate.swift */; };
|
||||||
|
92981EDD23F08D9B00C05424 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 92981EDC23F08D9B00C05424 /* ContentView.swift */; };
|
||||||
|
92981EDF23F08D9C00C05424 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 92981EDE23F08D9C00C05424 /* Assets.xcassets */; };
|
||||||
|
92981EE223F08D9C00C05424 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 92981EE123F08D9C00C05424 /* Preview Assets.xcassets */; };
|
||||||
|
92981EE523F08D9C00C05424 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 92981EE323F08D9C00C05424 /* Main.storyboard */; };
|
||||||
|
92ACA12B23F5F822003512DC /* HelperTool.swift in Sources */ = {isa = PBXBuildFile; fileRef = 927DBFCB23F54C2C00F8BF0D /* HelperTool.swift */; };
|
||||||
|
92ACA12E23F5F861003512DC /* SMC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 92ACA12D23F5F861003512DC /* SMC.swift */; };
|
||||||
|
92E24B4123F6DC0D00BE41ED /* Helper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 92E24B4023F6DC0D00BE41ED /* Helper.swift */; };
|
||||||
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
|
/* Begin PBXCopyFilesBuildPhase section */
|
||||||
|
9224A08623F1F6E300961AC4 /* CopyFiles */ = {
|
||||||
|
isa = PBXCopyFilesBuildPhase;
|
||||||
|
buildActionMask = 12;
|
||||||
|
dstPath = Contents/Library/LaunchServices;
|
||||||
|
dstSubfolderSpec = 1;
|
||||||
|
files = (
|
||||||
|
9224A08723F1F70600961AC4 /* com.davidwernhart.Helper in CopyFiles */,
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
/* End PBXCopyFilesBuildPhase section */
|
||||||
|
|
||||||
|
/* Begin PBXFileReference section */
|
||||||
|
927DBFC623F533FB00F8BF0D /* Helper-Launchd.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Helper-Launchd.plist"; sourceTree = "<group>"; };
|
||||||
|
927DBFC823F5478E00F8BF0D /* HelperToolProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HelperToolProtocol.swift; sourceTree = "<group>"; };
|
||||||
|
927DBFCB23F54C2C00F8BF0D /* HelperTool.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HelperTool.swift; sourceTree = "<group>"; };
|
||||||
|
9283741823F1F34400B8AE7A /* com.davidwernhart.Helper */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = com.davidwernhart.Helper; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
|
9283741A23F1F34400B8AE7A /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = "<group>"; };
|
||||||
|
9283741F23F1F38100B8AE7A /* Helper-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Helper-Info.plist"; sourceTree = "<group>"; };
|
||||||
|
928674BF25D07BA300EC79C1 /* PersistanceManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PersistanceManager.swift; sourceTree = "<group>"; };
|
||||||
|
92981ED723F08D9B00C05424 /* AlDente.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = AlDente.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
|
92981EDA23F08D9B00C05424 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||||
|
92981EDC23F08D9B00C05424 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
|
||||||
|
92981EDE23F08D9C00C05424 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||||
|
92981EE123F08D9C00C05424 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; };
|
||||||
|
92981EE423F08D9C00C05424 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
|
||||||
|
92981EE623F08D9C00C05424 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||||
|
92ACA12D23F5F861003512DC /* SMC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SMC.swift; sourceTree = "<group>"; };
|
||||||
|
92E24B4023F6DC0D00BE41ED /* Helper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Helper.swift; sourceTree = "<group>"; };
|
||||||
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
|
/* Begin PBXFrameworksBuildPhase section */
|
||||||
|
92981ED423F08D9B00C05424 /* Frameworks */ = {
|
||||||
|
isa = PBXFrameworksBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
505E96C92586211600DAA405 /* LaunchAtLogin in Frameworks */,
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
/* End PBXFrameworksBuildPhase section */
|
||||||
|
|
||||||
|
/* Begin PBXGroup section */
|
||||||
|
927DBFC723F545AF00F8BF0D /* Common */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
927DBFC823F5478E00F8BF0D /* HelperToolProtocol.swift */,
|
||||||
|
);
|
||||||
|
path = Common;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
9283741923F1F34400B8AE7A /* com.davidwernhart.Helper */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
9283741A23F1F34400B8AE7A /* main.swift */,
|
||||||
|
9283741F23F1F38100B8AE7A /* Helper-Info.plist */,
|
||||||
|
927DBFC623F533FB00F8BF0D /* Helper-Launchd.plist */,
|
||||||
|
927DBFCB23F54C2C00F8BF0D /* HelperTool.swift */,
|
||||||
|
92ACA12D23F5F861003512DC /* SMC.swift */,
|
||||||
|
);
|
||||||
|
path = com.davidwernhart.Helper;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
92981ECE23F08D9B00C05424 = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
927DBFC723F545AF00F8BF0D /* Common */,
|
||||||
|
92981ED923F08D9B00C05424 /* AlDente */,
|
||||||
|
9283741923F1F34400B8AE7A /* com.davidwernhart.Helper */,
|
||||||
|
92981ED823F08D9B00C05424 /* Products */,
|
||||||
|
);
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
92981ED823F08D9B00C05424 /* Products */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
92981ED723F08D9B00C05424 /* AlDente.app */,
|
||||||
|
9283741823F1F34400B8AE7A /* com.davidwernhart.Helper */,
|
||||||
|
);
|
||||||
|
name = Products;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
92981ED923F08D9B00C05424 /* AlDente */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
92E24B4023F6DC0D00BE41ED /* Helper.swift */,
|
||||||
|
92981EDA23F08D9B00C05424 /* AppDelegate.swift */,
|
||||||
|
92981EDC23F08D9B00C05424 /* ContentView.swift */,
|
||||||
|
92981EDE23F08D9C00C05424 /* Assets.xcassets */,
|
||||||
|
92981EE323F08D9C00C05424 /* Main.storyboard */,
|
||||||
|
92981EE623F08D9C00C05424 /* Info.plist */,
|
||||||
|
92981EE023F08D9C00C05424 /* Preview Content */,
|
||||||
|
928674BF25D07BA300EC79C1 /* PersistanceManager.swift */,
|
||||||
|
);
|
||||||
|
path = AlDente;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
92981EE023F08D9C00C05424 /* Preview Content */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
92981EE123F08D9C00C05424 /* Preview Assets.xcassets */,
|
||||||
|
);
|
||||||
|
path = "Preview Content";
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
/* End PBXGroup section */
|
||||||
|
|
||||||
|
/* Begin PBXNativeTarget section */
|
||||||
|
9283741723F1F34400B8AE7A /* com.davidwernhart.Helper */ = {
|
||||||
|
isa = PBXNativeTarget;
|
||||||
|
buildConfigurationList = 9283741C23F1F34400B8AE7A /* Build configuration list for PBXNativeTarget "com.davidwernhart.Helper" */;
|
||||||
|
buildPhases = (
|
||||||
|
9283741423F1F34400B8AE7A /* Sources */,
|
||||||
|
);
|
||||||
|
buildRules = (
|
||||||
|
);
|
||||||
|
dependencies = (
|
||||||
|
);
|
||||||
|
name = com.davidwernhart.Helper;
|
||||||
|
productName = com.davidwernhart.Helper;
|
||||||
|
productReference = 9283741823F1F34400B8AE7A /* com.davidwernhart.Helper */;
|
||||||
|
productType = "com.apple.product-type.tool";
|
||||||
|
};
|
||||||
|
92981ED623F08D9B00C05424 /* AlDente */ = {
|
||||||
|
isa = PBXNativeTarget;
|
||||||
|
buildConfigurationList = 92981EEA23F08D9C00C05424 /* Build configuration list for PBXNativeTarget "AlDente" */;
|
||||||
|
buildPhases = (
|
||||||
|
92981ED323F08D9B00C05424 /* Sources */,
|
||||||
|
92981ED423F08D9B00C05424 /* Frameworks */,
|
||||||
|
92981ED523F08D9B00C05424 /* Resources */,
|
||||||
|
9224A08623F1F6E300961AC4 /* CopyFiles */,
|
||||||
|
92E24B4423F847B500BE41ED /* LaunchAtLogin */,
|
||||||
|
);
|
||||||
|
buildRules = (
|
||||||
|
);
|
||||||
|
dependencies = (
|
||||||
|
);
|
||||||
|
name = AlDente;
|
||||||
|
packageProductDependencies = (
|
||||||
|
505E96C82586211600DAA405 /* LaunchAtLogin */,
|
||||||
|
);
|
||||||
|
productName = AlDente;
|
||||||
|
productReference = 92981ED723F08D9B00C05424 /* AlDente.app */;
|
||||||
|
productType = "com.apple.product-type.application";
|
||||||
|
};
|
||||||
|
/* End PBXNativeTarget section */
|
||||||
|
|
||||||
|
/* Begin PBXProject section */
|
||||||
|
92981ECF23F08D9B00C05424 /* Project object */ = {
|
||||||
|
isa = PBXProject;
|
||||||
|
attributes = {
|
||||||
|
LastSwiftUpdateCheck = 1130;
|
||||||
|
LastUpgradeCheck = 1220;
|
||||||
|
ORGANIZATIONNAME = "David Wernhart";
|
||||||
|
TargetAttributes = {
|
||||||
|
9283741723F1F34400B8AE7A = {
|
||||||
|
CreatedOnToolsVersion = 11.3.1;
|
||||||
|
};
|
||||||
|
92981ED623F08D9B00C05424 = {
|
||||||
|
CreatedOnToolsVersion = 11.3.1;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
buildConfigurationList = 92981ED223F08D9B00C05424 /* Build configuration list for PBXProject "AlDente" */;
|
||||||
|
compatibilityVersion = "Xcode 12.0";
|
||||||
|
developmentRegion = en;
|
||||||
|
hasScannedForEncodings = 0;
|
||||||
|
knownRegions = (
|
||||||
|
en,
|
||||||
|
Base,
|
||||||
|
);
|
||||||
|
mainGroup = 92981ECE23F08D9B00C05424;
|
||||||
|
packageReferences = (
|
||||||
|
505E96C72586211600DAA405 /* XCRemoteSwiftPackageReference "LaunchAtLogin" */,
|
||||||
|
);
|
||||||
|
productRefGroup = 92981ED823F08D9B00C05424 /* Products */;
|
||||||
|
projectDirPath = "";
|
||||||
|
projectRoot = "";
|
||||||
|
targets = (
|
||||||
|
92981ED623F08D9B00C05424 /* AlDente */,
|
||||||
|
9283741723F1F34400B8AE7A /* com.davidwernhart.Helper */,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
/* End PBXProject section */
|
||||||
|
|
||||||
|
/* Begin PBXResourcesBuildPhase section */
|
||||||
|
92981ED523F08D9B00C05424 /* Resources */ = {
|
||||||
|
isa = PBXResourcesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
92981EE523F08D9C00C05424 /* Main.storyboard in Resources */,
|
||||||
|
92981EE223F08D9C00C05424 /* Preview Assets.xcassets in Resources */,
|
||||||
|
92981EDF23F08D9C00C05424 /* Assets.xcassets in Resources */,
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
/* End PBXResourcesBuildPhase section */
|
||||||
|
|
||||||
|
/* Begin PBXShellScriptBuildPhase section */
|
||||||
|
92E24B4423F847B500BE41ED /* LaunchAtLogin */ = {
|
||||||
|
isa = PBXShellScriptBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
inputFileListPaths = (
|
||||||
|
);
|
||||||
|
inputPaths = (
|
||||||
|
);
|
||||||
|
name = LaunchAtLogin;
|
||||||
|
outputFileListPaths = (
|
||||||
|
);
|
||||||
|
outputPaths = (
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
shellPath = /bin/sh;
|
||||||
|
shellScript = "\"${BUILT_PRODUCTS_DIR}/LaunchAtLogin_LaunchAtLogin.bundle/Contents/Resources/copy-helper-swiftpm.sh\"\n";
|
||||||
|
};
|
||||||
|
/* End PBXShellScriptBuildPhase section */
|
||||||
|
|
||||||
|
/* Begin PBXSourcesBuildPhase section */
|
||||||
|
9283741423F1F34400B8AE7A /* Sources */ = {
|
||||||
|
isa = PBXSourcesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
9283741B23F1F34400B8AE7A /* main.swift in Sources */,
|
||||||
|
92ACA12B23F5F822003512DC /* HelperTool.swift in Sources */,
|
||||||
|
927DBFCA23F5478E00F8BF0D /* HelperToolProtocol.swift in Sources */,
|
||||||
|
92ACA12E23F5F861003512DC /* SMC.swift in Sources */,
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
92981ED323F08D9B00C05424 /* Sources */ = {
|
||||||
|
isa = PBXSourcesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
928674C025D07BA300EC79C1 /* PersistanceManager.swift in Sources */,
|
||||||
|
927DBFC923F5478E00F8BF0D /* HelperToolProtocol.swift in Sources */,
|
||||||
|
92981EDD23F08D9B00C05424 /* ContentView.swift in Sources */,
|
||||||
|
92981EDB23F08D9B00C05424 /* AppDelegate.swift in Sources */,
|
||||||
|
92E24B4123F6DC0D00BE41ED /* Helper.swift in Sources */,
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
/* End PBXSourcesBuildPhase section */
|
||||||
|
|
||||||
|
/* Begin PBXVariantGroup section */
|
||||||
|
92981EE323F08D9C00C05424 /* Main.storyboard */ = {
|
||||||
|
isa = PBXVariantGroup;
|
||||||
|
children = (
|
||||||
|
92981EE423F08D9C00C05424 /* Base */,
|
||||||
|
);
|
||||||
|
name = Main.storyboard;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
/* End PBXVariantGroup section */
|
||||||
|
|
||||||
|
/* Begin XCBuildConfiguration section */
|
||||||
|
9283741D23F1F34400B8AE7A /* Debug */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
ARCHS = "$(ARCHS_STANDARD)";
|
||||||
|
CODE_SIGN_IDENTITY = "-";
|
||||||
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
CURRENT_PROJECT_VERSION = 10;
|
||||||
|
DEVELOPMENT_TEAM = 56C2L92EKW;
|
||||||
|
ENABLE_HARDENED_RUNTIME = YES;
|
||||||
|
EXCLUDED_ARCHS = "";
|
||||||
|
"EXCLUDED_ARCHS[sdk=*]" = "";
|
||||||
|
INFOPLIST_FILE = "com.davidwernhart.Helper/Helper-Info.plist";
|
||||||
|
MACOSX_DEPLOYMENT_TARGET = 11.1;
|
||||||
|
MARKETING_VERSION = 4.3;
|
||||||
|
OTHER_LDFLAGS = (
|
||||||
|
"-sectcreate",
|
||||||
|
__TEXT,
|
||||||
|
__info_plist,
|
||||||
|
"$(SRCROOT)/com.davidwernhart.Helper/Helper-Info.plist",
|
||||||
|
"-sectcreate",
|
||||||
|
__TEXT,
|
||||||
|
__launchd_plist,
|
||||||
|
"$(SRCROOT)/com.davidwernhart.Helper/Helper-Launchd.plist",
|
||||||
|
);
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = com.davidwernhart.Helper;
|
||||||
|
PRODUCT_MODULE_NAME = AlDenteHelper;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||||
|
SKIP_INSTALL = YES;
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
};
|
||||||
|
name = Debug;
|
||||||
|
};
|
||||||
|
9283741E23F1F34400B8AE7A /* Release */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
ARCHS = "$(ARCHS_STANDARD)";
|
||||||
|
CODE_SIGN_IDENTITY = "-";
|
||||||
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
CURRENT_PROJECT_VERSION = 10;
|
||||||
|
DEVELOPMENT_TEAM = 56C2L92EKW;
|
||||||
|
ENABLE_HARDENED_RUNTIME = YES;
|
||||||
|
EXCLUDED_ARCHS = "";
|
||||||
|
INFOPLIST_FILE = "com.davidwernhart.Helper/Helper-Info.plist";
|
||||||
|
MACOSX_DEPLOYMENT_TARGET = 11.1;
|
||||||
|
MARKETING_VERSION = 4.3;
|
||||||
|
OTHER_LDFLAGS = (
|
||||||
|
"-sectcreate",
|
||||||
|
__TEXT,
|
||||||
|
__info_plist,
|
||||||
|
"$(SRCROOT)/com.davidwernhart.Helper/Helper-Info.plist",
|
||||||
|
"-sectcreate",
|
||||||
|
__TEXT,
|
||||||
|
__launchd_plist,
|
||||||
|
"$(SRCROOT)/com.davidwernhart.Helper/Helper-Launchd.plist",
|
||||||
|
);
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = com.davidwernhart.Helper;
|
||||||
|
PRODUCT_MODULE_NAME = AlDenteHelper;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||||
|
SKIP_INSTALL = YES;
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
};
|
||||||
|
name = Release;
|
||||||
|
};
|
||||||
|
92981EE823F08D9C00C05424 /* 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_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||||
|
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;
|
||||||
|
COPY_PHASE_STRIP = NO;
|
||||||
|
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||||
|
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||||
|
ENABLE_TESTABILITY = YES;
|
||||||
|
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||||
|
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 = 11.1;
|
||||||
|
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||||
|
MTL_FAST_MATH = YES;
|
||||||
|
ONLY_ACTIVE_ARCH = YES;
|
||||||
|
SDKROOT = macosx;
|
||||||
|
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
||||||
|
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||||
|
};
|
||||||
|
name = Debug;
|
||||||
|
};
|
||||||
|
92981EE923F08D9C00C05424 /* 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_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||||
|
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;
|
||||||
|
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 = 11.1;
|
||||||
|
MTL_ENABLE_DEBUG_INFO = NO;
|
||||||
|
MTL_FAST_MATH = YES;
|
||||||
|
SDKROOT = macosx;
|
||||||
|
SWIFT_COMPILATION_MODE = wholemodule;
|
||||||
|
SWIFT_OPTIMIZATION_LEVEL = "-O";
|
||||||
|
};
|
||||||
|
name = Release;
|
||||||
|
};
|
||||||
|
92981EEB23F08D9C00C05424 /* Debug */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
|
CODE_SIGN_IDENTITY = "-";
|
||||||
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
COMBINE_HIDPI_IMAGES = YES;
|
||||||
|
CURRENT_PROJECT_VERSION = 6;
|
||||||
|
DEVELOPMENT_ASSET_PATHS = "\"AlDente/Preview Content\"";
|
||||||
|
DEVELOPMENT_TEAM = 56C2L92EKW;
|
||||||
|
ENABLE_HARDENED_RUNTIME = YES;
|
||||||
|
ENABLE_PREVIEWS = YES;
|
||||||
|
INFOPLIST_FILE = AlDente/Info.plist;
|
||||||
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"@executable_path/../Frameworks",
|
||||||
|
);
|
||||||
|
MACOSX_DEPLOYMENT_TARGET = 11.1;
|
||||||
|
MARKETING_VERSION = "2.0 Alpha";
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = com.davidwernhart.AlDente;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
};
|
||||||
|
name = Debug;
|
||||||
|
};
|
||||||
|
92981EEC23F08D9C00C05424 /* Release */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
|
CODE_SIGN_IDENTITY = "-";
|
||||||
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
COMBINE_HIDPI_IMAGES = YES;
|
||||||
|
CURRENT_PROJECT_VERSION = 6;
|
||||||
|
DEVELOPMENT_ASSET_PATHS = "\"AlDente/Preview Content\"";
|
||||||
|
DEVELOPMENT_TEAM = 56C2L92EKW;
|
||||||
|
ENABLE_HARDENED_RUNTIME = YES;
|
||||||
|
ENABLE_PREVIEWS = YES;
|
||||||
|
INFOPLIST_FILE = AlDente/Info.plist;
|
||||||
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"@executable_path/../Frameworks",
|
||||||
|
);
|
||||||
|
MACOSX_DEPLOYMENT_TARGET = 11.1;
|
||||||
|
MARKETING_VERSION = "2.0 Alpha";
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = com.davidwernhart.AlDente;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
};
|
||||||
|
name = Release;
|
||||||
|
};
|
||||||
|
/* End XCBuildConfiguration section */
|
||||||
|
|
||||||
|
/* Begin XCConfigurationList section */
|
||||||
|
9283741C23F1F34400B8AE7A /* Build configuration list for PBXNativeTarget "com.davidwernhart.Helper" */ = {
|
||||||
|
isa = XCConfigurationList;
|
||||||
|
buildConfigurations = (
|
||||||
|
9283741D23F1F34400B8AE7A /* Debug */,
|
||||||
|
9283741E23F1F34400B8AE7A /* Release */,
|
||||||
|
);
|
||||||
|
defaultConfigurationIsVisible = 0;
|
||||||
|
defaultConfigurationName = Release;
|
||||||
|
};
|
||||||
|
92981ED223F08D9B00C05424 /* Build configuration list for PBXProject "AlDente" */ = {
|
||||||
|
isa = XCConfigurationList;
|
||||||
|
buildConfigurations = (
|
||||||
|
92981EE823F08D9C00C05424 /* Debug */,
|
||||||
|
92981EE923F08D9C00C05424 /* Release */,
|
||||||
|
);
|
||||||
|
defaultConfigurationIsVisible = 0;
|
||||||
|
defaultConfigurationName = Release;
|
||||||
|
};
|
||||||
|
92981EEA23F08D9C00C05424 /* Build configuration list for PBXNativeTarget "AlDente" */ = {
|
||||||
|
isa = XCConfigurationList;
|
||||||
|
buildConfigurations = (
|
||||||
|
92981EEB23F08D9C00C05424 /* Debug */,
|
||||||
|
92981EEC23F08D9C00C05424 /* Release */,
|
||||||
|
);
|
||||||
|
defaultConfigurationIsVisible = 0;
|
||||||
|
defaultConfigurationName = Release;
|
||||||
|
};
|
||||||
|
/* End XCConfigurationList section */
|
||||||
|
|
||||||
|
/* Begin XCRemoteSwiftPackageReference section */
|
||||||
|
505E96C72586211600DAA405 /* XCRemoteSwiftPackageReference "LaunchAtLogin" */ = {
|
||||||
|
isa = XCRemoteSwiftPackageReference;
|
||||||
|
repositoryURL = "https://github.com/sindresorhus/LaunchAtLogin";
|
||||||
|
requirement = {
|
||||||
|
kind = upToNextMajorVersion;
|
||||||
|
minimumVersion = 4.0.0;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
/* End XCRemoteSwiftPackageReference section */
|
||||||
|
|
||||||
|
/* Begin XCSwiftPackageProductDependency section */
|
||||||
|
505E96C82586211600DAA405 /* LaunchAtLogin */ = {
|
||||||
|
isa = XCSwiftPackageProductDependency;
|
||||||
|
package = 505E96C72586211600DAA405 /* XCRemoteSwiftPackageReference "LaunchAtLogin" */;
|
||||||
|
productName = LaunchAtLogin;
|
||||||
|
};
|
||||||
|
/* End XCSwiftPackageProductDependency section */
|
||||||
|
};
|
||||||
|
rootObject = 92981ECF23F08D9B00C05424 /* Project object */;
|
||||||
|
}
|
7
AlDente.xcodeproj/project.xcworkspace/contents.xcworkspacedata
generated
Normal file
7
AlDente.xcodeproj/project.xcworkspace/contents.xcworkspacedata
generated
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Workspace
|
||||||
|
version = "1.0">
|
||||||
|
<FileRef
|
||||||
|
location = "self:">
|
||||||
|
</FileRef>
|
||||||
|
</Workspace>
|
@ -0,0 +1,8 @@
|
|||||||
|
<?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>IDEDidComputeMac32BitWarning</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"object": {
|
||||||
|
"pins": [
|
||||||
|
{
|
||||||
|
"package": "LaunchAtLogin",
|
||||||
|
"repositoryURL": "https://github.com/sindresorhus/LaunchAtLogin",
|
||||||
|
"state": {
|
||||||
|
"branch": null,
|
||||||
|
"revision": "0f39982b9d6993eef253b81219d3c39ba1e680f3",
|
||||||
|
"version": "4.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"version": 1
|
||||||
|
}
|
Binary file not shown.
@ -0,0 +1,24 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Bucket
|
||||||
|
uuid = "0B65810B-47FA-44B5-B39C-4D764B29033A"
|
||||||
|
type = "1"
|
||||||
|
version = "2.0">
|
||||||
|
<Breakpoints>
|
||||||
|
<BreakpointProxy
|
||||||
|
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
|
||||||
|
<BreakpointContent
|
||||||
|
uuid = "15EAC10E-2093-4C1F-817D-D17853B691FE"
|
||||||
|
shouldBeEnabled = "No"
|
||||||
|
ignoreCount = "0"
|
||||||
|
continueAfterRunningActions = "No"
|
||||||
|
filePath = "AlDente/ContentView.swift"
|
||||||
|
startingColumnNumber = "9223372036854775807"
|
||||||
|
endingColumnNumber = "9223372036854775807"
|
||||||
|
startingLineNumber = "37"
|
||||||
|
endingLineNumber = "37"
|
||||||
|
landmarkName = "Settings"
|
||||||
|
landmarkType = "14">
|
||||||
|
</BreakpointContent>
|
||||||
|
</BreakpointProxy>
|
||||||
|
</Breakpoints>
|
||||||
|
</Bucket>
|
@ -0,0 +1,39 @@
|
|||||||
|
<?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>SchemeUserState</key>
|
||||||
|
<dict>
|
||||||
|
<key>AlDente Pro.xcscheme_^#shared#^_</key>
|
||||||
|
<dict>
|
||||||
|
<key>orderHint</key>
|
||||||
|
<integer>2</integer>
|
||||||
|
</dict>
|
||||||
|
<key>AlDente copy.xcscheme_^#shared#^_</key>
|
||||||
|
<dict>
|
||||||
|
<key>orderHint</key>
|
||||||
|
<integer>2</integer>
|
||||||
|
</dict>
|
||||||
|
<key>AlDente.xcscheme_^#shared#^_</key>
|
||||||
|
<dict>
|
||||||
|
<key>orderHint</key>
|
||||||
|
<integer>1</integer>
|
||||||
|
</dict>
|
||||||
|
<key>Hall.xcscheme_^#shared#^_</key>
|
||||||
|
<dict>
|
||||||
|
<key>orderHint</key>
|
||||||
|
<integer>0</integer>
|
||||||
|
</dict>
|
||||||
|
<key>Helper.xcscheme_^#shared#^_</key>
|
||||||
|
<dict>
|
||||||
|
<key>orderHint</key>
|
||||||
|
<integer>2</integer>
|
||||||
|
</dict>
|
||||||
|
<key>com.davidwernhart.Helper.xcscheme_^#shared#^_</key>
|
||||||
|
<dict>
|
||||||
|
<key>orderHint</key>
|
||||||
|
<integer>0</integer>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
133
AlDente/AppDelegate.swift
Normal file
133
AlDente/AppDelegate.swift
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
//
|
||||||
|
// AppDelegate.swift
|
||||||
|
// AlDente
|
||||||
|
//
|
||||||
|
// Created by David Wernhart on 09.02.20.
|
||||||
|
// Copyright © 2020 David Wernhart. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import AppKit
|
||||||
|
import SwiftUI
|
||||||
|
import LaunchAtLogin
|
||||||
|
import Foundation
|
||||||
|
import IOKit.ps
|
||||||
|
import IOKit.pwr_mgt
|
||||||
|
|
||||||
|
extension ProcessInfo {
|
||||||
|
/// Returns a `String` representing the machine hardware name or nil if there was an error invoking `uname(_:)` or decoding the response.
|
||||||
|
///
|
||||||
|
/// Return value is the equivalent to running `$ uname -m` in shell.
|
||||||
|
var machineHardwareName: String? {
|
||||||
|
var sysinfo = utsname()
|
||||||
|
let result = uname(&sysinfo)
|
||||||
|
guard result == EXIT_SUCCESS else { return nil }
|
||||||
|
let data = Data(bytes: &sysinfo.machine, count: Int(_SYS_NAMELEN))
|
||||||
|
guard let identifier = String(bytes: data, encoding: .ascii) else { return nil }
|
||||||
|
return identifier.trimmingCharacters(in: .controlCharacters)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@NSApplicationMain
|
||||||
|
final class AppDelegate: NSObject, NSApplicationDelegate {
|
||||||
|
|
||||||
|
//var window: NSWindow!
|
||||||
|
var statusBarItem: NSStatusItem!
|
||||||
|
var popover: NSPopover!
|
||||||
|
|
||||||
|
func applicationWillTerminate(_ aNotification: Notification) {
|
||||||
|
Helper.instance.enableSleep()
|
||||||
|
Helper.instance.enableCharging()
|
||||||
|
}
|
||||||
|
|
||||||
|
func applicationDidFinishLaunching(_ aNotification: Notification) {
|
||||||
|
|
||||||
|
let contentView = ContentView()
|
||||||
|
|
||||||
|
// Create the popover
|
||||||
|
let popover = NSPopover()
|
||||||
|
popover.contentSize = NSSize(width: 400, height: 600)
|
||||||
|
popover.behavior = .transient
|
||||||
|
popover.contentViewController = NSHostingController(rootView: contentView)
|
||||||
|
self.popover = popover
|
||||||
|
|
||||||
|
let statusBar = NSStatusBar.system
|
||||||
|
statusBarItem = statusBar.statusItem(withLength: NSStatusItem.squareLength)
|
||||||
|
statusBarItem.button?.image = NSImage(named: "menubaricon")!
|
||||||
|
statusBarItem.button?.action = #selector(togglePopover(_:))
|
||||||
|
|
||||||
|
Helper.instance.setPlatformKey()
|
||||||
|
|
||||||
|
Helper.instance.checkHelperVersion{(foundHelper) in
|
||||||
|
if(foundHelper){
|
||||||
|
print("helper found!")
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
Helper.instance.installHelper()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LaunchAtLogin.isEnabled = true
|
||||||
|
SMCPresenter.shared.loadValue()
|
||||||
|
|
||||||
|
Helper.instance.checkCharging()
|
||||||
|
|
||||||
|
var actionMsg:String?
|
||||||
|
|
||||||
|
Timer.scheduledTimer(withTimeInterval: 5, repeats: true) { timer in
|
||||||
|
if(Helper.instance.isInitialized){
|
||||||
|
Helper.instance.getChargingInfo { (Name, Capacity, IsCharging, MaxCapacity) in
|
||||||
|
|
||||||
|
|
||||||
|
if(!PersistanceManager.instance.oldKey){
|
||||||
|
if(Capacity < SMCPresenter.shared.value){
|
||||||
|
actionMsg = "NEED TO CHARGE"
|
||||||
|
if(Helper.instance.chargeInhibited){
|
||||||
|
Helper.instance.enableCharging()
|
||||||
|
}
|
||||||
|
Helper.instance.disableSleep()
|
||||||
|
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
actionMsg = "IS PERFECT"
|
||||||
|
if(!Helper.instance.chargeInhibited){
|
||||||
|
Helper.instance.disableCharging()
|
||||||
|
}
|
||||||
|
Helper.instance.enableSleep()
|
||||||
|
|
||||||
|
}
|
||||||
|
print("TARGET: ",SMCPresenter.shared.value,
|
||||||
|
" CURRENT: ",String(Capacity),
|
||||||
|
" ISCHARGING: ",String(IsCharging),
|
||||||
|
" CHARGE INHIBITED: ",String(Helper.instance.chargeInhibited),
|
||||||
|
" ACTION: ",actionMsg!)
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
print("BCLM MODE ENABLED")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
Helper.instance.setStatusString()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func togglePopover(_ sender: AnyObject?) {
|
||||||
|
popover.contentViewController?.view.window?.becomeKey()
|
||||||
|
|
||||||
|
Helper.instance.setStatusString()
|
||||||
|
if let button = self.statusBarItem.button {
|
||||||
|
if popover.isShown {
|
||||||
|
popover.performClose(sender)
|
||||||
|
} else {
|
||||||
|
popover.show(relativeTo: button.bounds, of: button, preferredEdge: .minY)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
59
AlDente/Assets.xcassets/AppIcon.appiconset/Contents.json
Normal file
59
AlDente/Assets.xcassets/AppIcon.appiconset/Contents.json
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "mac",
|
||||||
|
"scale" : "1x",
|
||||||
|
"size" : "16x16"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "mac",
|
||||||
|
"scale" : "2x",
|
||||||
|
"size" : "16x16"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "mac",
|
||||||
|
"scale" : "1x",
|
||||||
|
"size" : "32x32"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "mac",
|
||||||
|
"scale" : "2x",
|
||||||
|
"size" : "32x32"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "mac",
|
||||||
|
"scale" : "1x",
|
||||||
|
"size" : "128x128"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "mac",
|
||||||
|
"scale" : "2x",
|
||||||
|
"size" : "128x128"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "mac",
|
||||||
|
"scale" : "1x",
|
||||||
|
"size" : "256x256"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "mac",
|
||||||
|
"scale" : "2x",
|
||||||
|
"size" : "256x256"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "spaghetti.png",
|
||||||
|
"idiom" : "mac",
|
||||||
|
"scale" : "1x",
|
||||||
|
"size" : "512x512"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "mac",
|
||||||
|
"scale" : "2x",
|
||||||
|
"size" : "512x512"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
BIN
AlDente/Assets.xcassets/AppIcon.appiconset/spaghetti.png
Normal file
BIN
AlDente/Assets.xcassets/AppIcon.appiconset/spaghetti.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 73 KiB |
6
AlDente/Assets.xcassets/Contents.json
Normal file
6
AlDente/Assets.xcassets/Contents.json
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"info" : {
|
||||||
|
"version" : 1,
|
||||||
|
"author" : "xcode"
|
||||||
|
}
|
||||||
|
}
|
54
AlDente/Assets.xcassets/menubaricon.imageset/Contents.json
vendored
Normal file
54
AlDente/Assets.xcassets/menubaricon.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "aldentelighticon1x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"appearances" : [
|
||||||
|
{
|
||||||
|
"appearance" : "luminosity",
|
||||||
|
"value" : "dark"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"filename" : "aldentedarkicon1x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "aldentelighticon2x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"appearances" : [
|
||||||
|
{
|
||||||
|
"appearance" : "luminosity",
|
||||||
|
"value" : "dark"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"filename" : "aldentedarkicon2x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"appearances" : [
|
||||||
|
{
|
||||||
|
"appearance" : "luminosity",
|
||||||
|
"value" : "dark"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
BIN
AlDente/Assets.xcassets/menubaricon.imageset/aldentedarkicon1x.png
vendored
Normal file
BIN
AlDente/Assets.xcassets/menubaricon.imageset/aldentedarkicon1x.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.2 KiB |
BIN
AlDente/Assets.xcassets/menubaricon.imageset/aldentedarkicon2x.png
vendored
Normal file
BIN
AlDente/Assets.xcassets/menubaricon.imageset/aldentedarkicon2x.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.6 KiB |
BIN
AlDente/Assets.xcassets/menubaricon.imageset/aldentelighticon1x.png
vendored
Normal file
BIN
AlDente/Assets.xcassets/menubaricon.imageset/aldentelighticon1x.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.0 KiB |
BIN
AlDente/Assets.xcassets/menubaricon.imageset/aldentelighticon2x.png
vendored
Normal file
BIN
AlDente/Assets.xcassets/menubaricon.imageset/aldentelighticon2x.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.3 KiB |
88
AlDente/Base.lproj/Main.storyboard
Normal file
88
AlDente/Base.lproj/Main.storyboard
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="15705" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
|
||||||
|
<dependencies>
|
||||||
|
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="15705"/>
|
||||||
|
</dependencies>
|
||||||
|
<scenes>
|
||||||
|
<!--Application-->
|
||||||
|
<scene sceneID="JPo-4y-FX3">
|
||||||
|
<objects>
|
||||||
|
<application id="hnw-xV-0zn" sceneMemberID="viewController">
|
||||||
|
<menu key="mainMenu" title="Main Menu" systemMenu="main" id="AYu-sK-qS6">
|
||||||
|
<items>
|
||||||
|
<menuItem title="AlDente" id="1Xt-HY-uBw">
|
||||||
|
<modifierMask key="keyEquivalentModifierMask"/>
|
||||||
|
<menu key="submenu" title="AlDente" systemMenu="apple" id="uQy-DD-JDr">
|
||||||
|
<items>
|
||||||
|
<menuItem title="About AlDente" id="5kV-Vb-QxS">
|
||||||
|
<modifierMask key="keyEquivalentModifierMask"/>
|
||||||
|
<connections>
|
||||||
|
<action selector="orderFrontStandardAboutPanel:" target="Ady-hI-5gd" id="Exp-CZ-Vem"/>
|
||||||
|
</connections>
|
||||||
|
</menuItem>
|
||||||
|
<menuItem isSeparatorItem="YES" id="VOq-y0-SEH"/>
|
||||||
|
<menuItem title="Preferences…" keyEquivalent="," id="BOF-NM-1cW"/>
|
||||||
|
<menuItem isSeparatorItem="YES" id="wFC-TO-SCJ"/>
|
||||||
|
<menuItem title="Services" id="NMo-om-nkz">
|
||||||
|
<modifierMask key="keyEquivalentModifierMask"/>
|
||||||
|
<menu key="submenu" title="Services" systemMenu="services" id="hz9-B4-Xy5"/>
|
||||||
|
</menuItem>
|
||||||
|
<menuItem isSeparatorItem="YES" id="4je-JR-u6R"/>
|
||||||
|
<menuItem title="Hide AlDente" keyEquivalent="h" id="Olw-nP-bQN">
|
||||||
|
<connections>
|
||||||
|
<action selector="hide:" target="Ady-hI-5gd" id="PnN-Uc-m68"/>
|
||||||
|
</connections>
|
||||||
|
</menuItem>
|
||||||
|
<menuItem title="Hide Others" keyEquivalent="h" id="Vdr-fp-XzO">
|
||||||
|
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
|
||||||
|
<connections>
|
||||||
|
<action selector="hideOtherApplications:" target="Ady-hI-5gd" id="VT4-aY-XCT"/>
|
||||||
|
</connections>
|
||||||
|
</menuItem>
|
||||||
|
<menuItem title="Show All" id="Kd2-mp-pUS">
|
||||||
|
<modifierMask key="keyEquivalentModifierMask"/>
|
||||||
|
<connections>
|
||||||
|
<action selector="unhideAllApplications:" target="Ady-hI-5gd" id="Dhg-Le-xox"/>
|
||||||
|
</connections>
|
||||||
|
</menuItem>
|
||||||
|
<menuItem isSeparatorItem="YES" id="kCx-OE-vgT"/>
|
||||||
|
<menuItem title="Quit AlDente" keyEquivalent="q" id="4sb-4s-VLi">
|
||||||
|
<connections>
|
||||||
|
<action selector="terminate:" target="Ady-hI-5gd" id="Te7-pn-YzF"/>
|
||||||
|
</connections>
|
||||||
|
</menuItem>
|
||||||
|
</items>
|
||||||
|
</menu>
|
||||||
|
</menuItem>
|
||||||
|
<menuItem title="File" id="dMs-cI-mzQ">
|
||||||
|
<modifierMask key="keyEquivalentModifierMask"/>
|
||||||
|
</menuItem>
|
||||||
|
<menuItem title="Edit" id="5QF-Oa-p0T">
|
||||||
|
<modifierMask key="keyEquivalentModifierMask"/>
|
||||||
|
</menuItem>
|
||||||
|
<menuItem title="Format" id="jxT-CU-nIS">
|
||||||
|
<modifierMask key="keyEquivalentModifierMask"/>
|
||||||
|
</menuItem>
|
||||||
|
<menuItem title="View" id="H8h-7b-M4v">
|
||||||
|
<modifierMask key="keyEquivalentModifierMask"/>
|
||||||
|
</menuItem>
|
||||||
|
<menuItem title="Window" id="aUF-d1-5bR">
|
||||||
|
<modifierMask key="keyEquivalentModifierMask"/>
|
||||||
|
</menuItem>
|
||||||
|
<menuItem title="Help" id="wpr-3q-Mcd">
|
||||||
|
<modifierMask key="keyEquivalentModifierMask"/>
|
||||||
|
</menuItem>
|
||||||
|
</items>
|
||||||
|
</menu>
|
||||||
|
<connections>
|
||||||
|
<outlet property="delegate" destination="Voe-Tx-rLC" id="PrD-fu-P6m"/>
|
||||||
|
</connections>
|
||||||
|
</application>
|
||||||
|
<customObject id="Voe-Tx-rLC" customClass="AppDelegate" customModule="AlDente" customModuleProvider="target"/>
|
||||||
|
<customObject id="YLy-65-1bz" customClass="NSFontManager"/>
|
||||||
|
<customObject id="Ady-hI-5gd" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
|
||||||
|
</objects>
|
||||||
|
<point key="canvasLocation" x="75" y="0.0"/>
|
||||||
|
</scene>
|
||||||
|
</scenes>
|
||||||
|
</document>
|
259
AlDente/ContentView.swift
Normal file
259
AlDente/ContentView.swift
Normal file
@ -0,0 +1,259 @@
|
|||||||
|
//
|
||||||
|
// ContentView.swift
|
||||||
|
// AlDente
|
||||||
|
//
|
||||||
|
// Created by David Wernhart on 09.02.20.
|
||||||
|
// Copyright © 2020 David Wernhart. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import LaunchAtLogin
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
private struct BlueButtonStyle: ButtonStyle {
|
||||||
|
func makeBody(configuration: Self.Configuration) -> some View {
|
||||||
|
configuration.label
|
||||||
|
.foregroundColor(configuration.isPressed ? Color.blue : Color.white)
|
||||||
|
.background(configuration.isPressed ? Color.white : Color.blue)
|
||||||
|
.cornerRadius(6.0)
|
||||||
|
.padding()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private struct RedButtonStyle: ButtonStyle {
|
||||||
|
func makeBody(configuration: Self.Configuration) -> some View {
|
||||||
|
configuration.label
|
||||||
|
.foregroundColor(configuration.isPressed ? Color.red : Color.white)
|
||||||
|
.background(configuration.isPressed ? Color.white : Color.red)
|
||||||
|
.cornerRadius(6.0)
|
||||||
|
.padding()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var allowDischarge = true
|
||||||
|
|
||||||
|
private struct Settings: View {
|
||||||
|
@State private var launchAtLogin = LaunchAtLogin.isEnabled
|
||||||
|
@State private var oldKey = PersistanceManager.instance.oldKey
|
||||||
|
@ObservedObject private var presenter = SMCPresenter.shared
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
VStack {
|
||||||
|
Spacer().frame(height: 15)
|
||||||
|
Text(presenter.status)
|
||||||
|
HStack {
|
||||||
|
VStack(alignment: .leading) {
|
||||||
|
Toggle(isOn: Binding(
|
||||||
|
get: { launchAtLogin },
|
||||||
|
|
||||||
|
set: { newValue in
|
||||||
|
launchAtLogin = newValue
|
||||||
|
print("Launch at login turned \(newValue ? "on" : "off")!")
|
||||||
|
LaunchAtLogin.isEnabled = newValue
|
||||||
|
}
|
||||||
|
)) {
|
||||||
|
Text("Launch at login")
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!Helper.instance.appleSilicon!){
|
||||||
|
Toggle(isOn: Binding(
|
||||||
|
get: { oldKey },
|
||||||
|
|
||||||
|
set: { newValue in
|
||||||
|
oldKey = newValue
|
||||||
|
PersistanceManager.instance.oldKey = oldKey
|
||||||
|
PersistanceManager.instance.save()
|
||||||
|
Helper.instance.setStatusString()
|
||||||
|
if(newValue){
|
||||||
|
Helper.instance.enableCharging()
|
||||||
|
Helper.instance.enableSleep()
|
||||||
|
//presenter.writeValue()
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
presenter.setValue(value: 100)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)) {
|
||||||
|
Text("Use Classic SMC Key (Intel)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.padding()
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
|
||||||
|
Button(action: {
|
||||||
|
Helper.instance.installHelper()
|
||||||
|
}) {
|
||||||
|
Text("Reinstall Helper")
|
||||||
|
.frame(maxWidth: 120, maxHeight: 30)
|
||||||
|
}.buttonStyle(BlueButtonStyle())
|
||||||
|
}
|
||||||
|
|
||||||
|
HStack {
|
||||||
|
Spacer().frame(width: 15)
|
||||||
|
VStack(alignment: .leading) {
|
||||||
|
let version = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String
|
||||||
|
Text("AlDente \(version ?? "") 🍝").font(.headline)
|
||||||
|
|
||||||
|
let address = "github.com/davidwernhart/AlDente"
|
||||||
|
Button(action: {
|
||||||
|
openURL("https://" + address)
|
||||||
|
}) {
|
||||||
|
Text(address).foregroundColor(Color(.linkColor))
|
||||||
|
}.buttonStyle(PlainButtonStyle())
|
||||||
|
|
||||||
|
Text("Cooked up in 2021 by AppHouseKitchen")
|
||||||
|
// Text("AlDente 🍝").font(.title)
|
||||||
|
// Text("Keep your battery just right").font(.subheadline)
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
|
||||||
|
Button(action: {
|
||||||
|
openURL("https://apphousekitchen.com/aldente/")
|
||||||
|
}) {
|
||||||
|
Text("Get Pro 🍜")
|
||||||
|
.frame(maxWidth: 100, maxHeight: 50)
|
||||||
|
}
|
||||||
|
.buttonStyle(BlueButtonStyle())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.background(Color(.unemphasizedSelectedContentBackgroundColor))
|
||||||
|
.cornerRadius(5)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func openURL(_ string: String) {
|
||||||
|
let url = URL(string: string)!
|
||||||
|
if NSWorkspace.shared.open(url) {
|
||||||
|
print("default browser was successfully opened")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ContentView: View {
|
||||||
|
@State private var adaptableHeight = CGFloat(100)
|
||||||
|
@State private var showSettings = false
|
||||||
|
|
||||||
|
@ObservedObject private var presenter = SMCPresenter.shared
|
||||||
|
|
||||||
|
init() {
|
||||||
|
Helper.instance.delegate = presenter
|
||||||
|
}
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
VStack(alignment: .leading) {
|
||||||
|
|
||||||
|
HStack {
|
||||||
|
Text("Max. Battery Charge:").padding(.leading)
|
||||||
|
TextField("Number", value: Binding(
|
||||||
|
get: {
|
||||||
|
Float(presenter.value)
|
||||||
|
},
|
||||||
|
set: { newValue in
|
||||||
|
if newValue >= 20 && newValue <= 100 {
|
||||||
|
presenter.setValue(value: newValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
), formatter: NumberFormatter())
|
||||||
|
.multilineTextAlignment(.center)
|
||||||
|
.frame(maxWidth: 50)
|
||||||
|
.textFieldStyle(RoundedBorderTextFieldStyle())
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
|
||||||
|
Button(action: {
|
||||||
|
showSettings.toggle()
|
||||||
|
adaptableHeight = showSettings ? 275 : 100
|
||||||
|
}) {
|
||||||
|
Text("Settings")
|
||||||
|
.frame(maxWidth: 70, maxHeight: 30)
|
||||||
|
}.buttonStyle(BlueButtonStyle()).padding(.leading, -30)
|
||||||
|
|
||||||
|
Button(action: {
|
||||||
|
NSApplication.shared.terminate(self)
|
||||||
|
}) {
|
||||||
|
Text("Quit")
|
||||||
|
.frame(maxWidth: 50, maxHeight: 30)
|
||||||
|
}.buttonStyle(RedButtonStyle()).padding(.leading, -30)
|
||||||
|
}
|
||||||
|
|
||||||
|
Slider(value: Binding(
|
||||||
|
get: {
|
||||||
|
Float(presenter.value)
|
||||||
|
},
|
||||||
|
set: { newValue in
|
||||||
|
if newValue >= 20 && newValue <= 100 {
|
||||||
|
presenter.setValue(value: newValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
), in: 20...100).padding(.horizontal).padding(.top, -20)
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
|
||||||
|
if showSettings {
|
||||||
|
Settings()
|
||||||
|
}
|
||||||
|
|
||||||
|
}.frame(width: 400, height: adaptableHeight)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class SMCPresenter: ObservableObject, HelperDelegate {
|
||||||
|
|
||||||
|
static let shared = SMCPresenter()
|
||||||
|
|
||||||
|
@Published var value: UInt8 = 0
|
||||||
|
@Published var status: String = ""
|
||||||
|
private var timer: Timer?
|
||||||
|
private var accuracyTimer: Timer?
|
||||||
|
|
||||||
|
func OnMaxBatRead(value: UInt8) {
|
||||||
|
if(PersistanceManager.instance.oldKey){
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self.value = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateStatus(status:String){
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self.status = status
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func loadValue(){
|
||||||
|
PersistanceManager.instance.load()
|
||||||
|
self.value = UInt8(PersistanceManager.instance.chargeVal!)
|
||||||
|
if(self.value == 0){
|
||||||
|
self.value = 50
|
||||||
|
}
|
||||||
|
print("loaded max charge val: ",self.value," old key:",PersistanceManager.instance.oldKey)
|
||||||
|
// if(!Helper.instance.appleSilicon!){
|
||||||
|
// Helper.instance.getSMCCharge(withReply: { (smcval) in
|
||||||
|
// self.value = UInt8(smcval)
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
if(PersistanceManager.instance.oldKey){
|
||||||
|
writeValue()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func setValue(value: Float) {
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self.value = UInt8(value)
|
||||||
|
PersistanceManager.instance.chargeVal = Int(value)
|
||||||
|
PersistanceManager.instance.save()
|
||||||
|
self.writeValue()
|
||||||
|
}
|
||||||
|
timer?.invalidate()
|
||||||
|
accuracyTimer?.invalidate()
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeValue(){
|
||||||
|
if(PersistanceManager.instance.oldKey){
|
||||||
|
print("should write bclm value: ", self.value)
|
||||||
|
Helper.instance.writeMaxBatteryCharge(setVal: self.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
293
AlDente/Helper.swift
Normal file
293
AlDente/Helper.swift
Normal file
@ -0,0 +1,293 @@
|
|||||||
|
//
|
||||||
|
// Helper.swift
|
||||||
|
// AlDente
|
||||||
|
//
|
||||||
|
// Created by David Wernhart on 14.02.20.
|
||||||
|
// Copyright © 2020 David Wernhart. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import ServiceManagement
|
||||||
|
import IOKit.pwr_mgt
|
||||||
|
|
||||||
|
protocol HelperDelegate {
|
||||||
|
func OnMaxBatRead(value: UInt8)
|
||||||
|
func updateStatus(status:String)
|
||||||
|
}
|
||||||
|
|
||||||
|
final class Helper {
|
||||||
|
|
||||||
|
static let instance = Helper()
|
||||||
|
|
||||||
|
public var delegate: HelperDelegate?
|
||||||
|
|
||||||
|
private var key: String?
|
||||||
|
|
||||||
|
private var preventSleepID: IOPMAssertionID?
|
||||||
|
|
||||||
|
public var appleSilicon:Bool?
|
||||||
|
public var chargeInhibited: Bool = false
|
||||||
|
public var isInitialized:Bool = false
|
||||||
|
|
||||||
|
public var statusString:String = ""
|
||||||
|
|
||||||
|
|
||||||
|
lazy var helperToolConnection: NSXPCConnection = {
|
||||||
|
let connection = NSXPCConnection(machServiceName: "com.davidwernhart.Helper.mach", options: .privileged)
|
||||||
|
connection.remoteObjectInterface = NSXPCInterface(with: HelperToolProtocol.self)
|
||||||
|
|
||||||
|
connection.resume()
|
||||||
|
return connection
|
||||||
|
}()
|
||||||
|
|
||||||
|
func setPlatformKey() {
|
||||||
|
let s:String! = ProcessInfo.init().machineHardwareName
|
||||||
|
if(s != nil){
|
||||||
|
if(s.elementsEqual("x86_64")){
|
||||||
|
print("intel cpu!")
|
||||||
|
appleSilicon = false;
|
||||||
|
}
|
||||||
|
else if(s.elementsEqual("arm64")){
|
||||||
|
print("arm cpu!")
|
||||||
|
appleSilicon = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func setStatusString(){
|
||||||
|
checkCharging()
|
||||||
|
var sleepDisabled:Bool = !(preventSleepID == nil)
|
||||||
|
statusString = ""
|
||||||
|
if(PersistanceManager.instance.oldKey){
|
||||||
|
statusString = "BCLM Key Mode. Final charge value can differ by up to 5%"
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
statusString = "Charge Inhibit: "+String(chargeInhibited)+" | Prevent Sleep: "+String(sleepDisabled)+" | Helper v"+String(helperVersion)+": \(self.isInitialized ? "found" : "not found")"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
self.delegate?.updateStatus(status: statusString)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func enableSleep(){
|
||||||
|
if(self.preventSleepID != nil){
|
||||||
|
print("RELEASING PREVENT SLEEP ASSERTION WITH ID: ",preventSleepID!)
|
||||||
|
releaseAssertion(assertionId: self.preventSleepID!)
|
||||||
|
self.preventSleepID = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func disableSleep(){
|
||||||
|
createAssertion(assertion: kIOPMAssertionTypePreventSystemSleep){ id in
|
||||||
|
if(self.preventSleepID == nil){
|
||||||
|
print("PREVENT SLEEP ASSERTION CREATED! ID: ",id)
|
||||||
|
self.preventSleepID = id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func enableCharging(){
|
||||||
|
if(appleSilicon!){
|
||||||
|
SMCWriteByte(key: "CH0B", value: 00)
|
||||||
|
}
|
||||||
|
SMCWriteByte(key: "CH0B", value: 00)
|
||||||
|
self.chargeInhibited = false
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func disableCharging(){
|
||||||
|
if(appleSilicon!){
|
||||||
|
SMCWriteByte(key: "CH0B", value: 02)
|
||||||
|
}
|
||||||
|
SMCWriteByte(key: "CH0B", value: 02)
|
||||||
|
self.chargeInhibited = true
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkCharging(){
|
||||||
|
Helper.instance.SMCReadUInt32(key: "CH0B") { value in
|
||||||
|
self.chargeInhibited = !(value == 00)
|
||||||
|
print("CHARGE INHIBITED: "+String(self.chargeInhibited))
|
||||||
|
}
|
||||||
|
if(PersistanceManager.instance.oldKey){
|
||||||
|
Helper.instance.readMaxBatteryCharge()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func getChargingInfo(withReply reply: (String,Int,Bool,Int) -> Void){
|
||||||
|
let snapshot = IOPSCopyPowerSourcesInfo().takeRetainedValue()
|
||||||
|
let sources = IOPSCopyPowerSourcesList(snapshot).takeRetainedValue() as Array
|
||||||
|
let info = IOPSGetPowerSourceDescription(snapshot, sources[0]).takeUnretainedValue() as! [String: AnyObject]
|
||||||
|
|
||||||
|
if let name = info[kIOPSNameKey] as? String,
|
||||||
|
let capacity = info[kIOPSCurrentCapacityKey] as? Int,
|
||||||
|
let isCharging = info[kIOPSIsChargingKey] as? Bool,
|
||||||
|
let max = info[kIOPSMaxCapacityKey] as? Int {
|
||||||
|
reply(name,capacity,isCharging,max)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getSMCCharge(withReply reply: @escaping (Float)->Void){
|
||||||
|
Helper.instance.SMCReadUInt32(key: "BRSC") { value in
|
||||||
|
let smcval = Float(value >> 16)
|
||||||
|
reply(smcval)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func createAssertion(assertion: String, withReply reply: @escaping (IOPMAssertionID) -> Void){
|
||||||
|
let helper = helperToolConnection.remoteObjectProxyWithErrorHandler {
|
||||||
|
let e = $0 as NSError
|
||||||
|
print("Remote proxy error \(e.code): \(e.localizedDescription) \(e.localizedRecoverySuggestion ?? "---")")
|
||||||
|
} as? HelperToolProtocol
|
||||||
|
|
||||||
|
helper?.createAssertion(assertion: assertion, withReply: { id in
|
||||||
|
reply(id)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func releaseAssertion(assertionId: IOPMAssertionID){
|
||||||
|
let helper = helperToolConnection.remoteObjectProxyWithErrorHandler {
|
||||||
|
let e = $0 as NSError
|
||||||
|
print("Remote proxy error \(e.code): \(e.localizedDescription) \(e.localizedRecoverySuggestion ?? "---")")
|
||||||
|
} as? HelperToolProtocol
|
||||||
|
|
||||||
|
helper?.releaseAssertion(assertionID: assertionId)
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func installHelper() {
|
||||||
|
print("trying to install helper!")
|
||||||
|
var status = noErr
|
||||||
|
let helperID = "com.davidwernhart.Helper" as CFString // Prefs.helperID as CFString
|
||||||
|
|
||||||
|
var authItem = kSMRightBlessPrivilegedHelper.withCString {
|
||||||
|
AuthorizationItem(name: $0, valueLength: 0, value: nil, flags: 0)
|
||||||
|
}
|
||||||
|
var authRights = withUnsafeMutablePointer(to: &authItem) {
|
||||||
|
AuthorizationRights(count: 1, items: $0)
|
||||||
|
}
|
||||||
|
let authFlags: AuthorizationFlags = [.interactionAllowed, .preAuthorize, .extendRights]
|
||||||
|
var authRef: AuthorizationRef?
|
||||||
|
status = AuthorizationCreate(&authRights, nil, authFlags, &authRef)
|
||||||
|
if status != errAuthorizationSuccess {
|
||||||
|
print(SecCopyErrorMessageString(status, nil) ?? "")
|
||||||
|
print("Error: \(status)")
|
||||||
|
}
|
||||||
|
|
||||||
|
var error: Unmanaged<CFError>?
|
||||||
|
SMJobBless(kSMDomainSystemLaunchd, helperID, authRef, &error)
|
||||||
|
if let e = error?.takeRetainedValue() {
|
||||||
|
print("Domain: ", CFErrorGetDomain(e) ?? "")
|
||||||
|
print("Code: ", CFErrorGetCode(e))
|
||||||
|
print("UserInfo: ", CFErrorCopyUserInfo(e) ?? "")
|
||||||
|
print("Description: ", CFErrorCopyDescription(e) ?? "")
|
||||||
|
print("Reason: ", CFErrorCopyFailureReason(e) ?? "")
|
||||||
|
print("Suggestion: ", CFErrorCopyRecoverySuggestion(e) ?? "")
|
||||||
|
}
|
||||||
|
|
||||||
|
if(error == nil){
|
||||||
|
print("helper installed successfully!")
|
||||||
|
restart()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func restart(){
|
||||||
|
let url = URL(fileURLWithPath: Bundle.main.resourcePath!)
|
||||||
|
let path = url.deletingLastPathComponent().deletingLastPathComponent().absoluteString
|
||||||
|
let task = Process()
|
||||||
|
task.launchPath = "/usr/bin/open"
|
||||||
|
task.arguments = [path]
|
||||||
|
task.launch()
|
||||||
|
exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func setResetValues(){
|
||||||
|
let helper = helperToolConnection.remoteObjectProxyWithErrorHandler {
|
||||||
|
let e = $0 as NSError
|
||||||
|
print("Remote proxy error \(e.code): \(e.localizedDescription) \(e.localizedRecoverySuggestion ?? "---")")
|
||||||
|
|
||||||
|
} as? HelperToolProtocol
|
||||||
|
|
||||||
|
helper?.setResetVal(key: "CH0B", value: 00)
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func writeMaxBatteryCharge(setVal: UInt8) {
|
||||||
|
SMCWriteByte(key: "BCLM", value: setVal)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func readMaxBatteryCharge() {
|
||||||
|
SMCReadByte(key: "BCLM") { value in
|
||||||
|
print("OLD KEY MAX CHARGE: "+String(value))
|
||||||
|
self.delegate?.OnMaxBatRead(value: value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func enableCharging(enabled: Bool) {
|
||||||
|
if(enabled){
|
||||||
|
SMCWriteByte(key: "CH0B", value: 00)
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
SMCWriteByte(key: "CH0B", value: 02)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func checkHelperVersion(withReply reply: @escaping (Bool) -> Void) {
|
||||||
|
print("checking helper version")
|
||||||
|
let helper = helperToolConnection.remoteObjectProxyWithErrorHandler {
|
||||||
|
let e = $0 as NSError
|
||||||
|
print("Remote proxy error \(e.code): \(e.localizedDescription) \(e.localizedRecoverySuggestion ?? "---")")
|
||||||
|
reply(false)
|
||||||
|
return()
|
||||||
|
|
||||||
|
} as? HelperToolProtocol
|
||||||
|
|
||||||
|
helper?.getVersion { version in
|
||||||
|
print("helperVersion:", helperVersion, " version from helper:", version)
|
||||||
|
if !helperVersion.elementsEqual(version) {
|
||||||
|
reply(false)
|
||||||
|
return() }
|
||||||
|
else{
|
||||||
|
self.isInitialized = true
|
||||||
|
reply(true)
|
||||||
|
return()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func SMCReadByte(key: String, withReply reply: @escaping (UInt8) -> Void) {
|
||||||
|
let helper = helperToolConnection.remoteObjectProxyWithErrorHandler {
|
||||||
|
let e = $0 as NSError
|
||||||
|
print("Remote proxy error \(e.code): \(e.localizedDescription) \(e.localizedRecoverySuggestion ?? "---")")
|
||||||
|
|
||||||
|
} as? HelperToolProtocol
|
||||||
|
|
||||||
|
helper?.readSMCByte(key: key) {
|
||||||
|
reply($0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func SMCReadUInt32(key: String, withReply reply: @escaping (UInt32) -> Void) {
|
||||||
|
let helper = helperToolConnection.remoteObjectProxyWithErrorHandler {
|
||||||
|
let e = $0 as NSError
|
||||||
|
print("Remote proxy error \(e.code): \(e.localizedDescription) \(e.localizedRecoverySuggestion ?? "---")")
|
||||||
|
|
||||||
|
} as? HelperToolProtocol
|
||||||
|
|
||||||
|
helper?.readSMCUInt32(key: key) {
|
||||||
|
reply($0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func SMCWriteByte(key: String, value: UInt8) {
|
||||||
|
let helper = helperToolConnection.remoteObjectProxyWithErrorHandler {
|
||||||
|
let e = $0 as NSError
|
||||||
|
print("Remote proxy error \(e.code): \(e.localizedDescription) \(e.localizedRecoverySuggestion ?? "---")")
|
||||||
|
|
||||||
|
} as? HelperToolProtocol
|
||||||
|
|
||||||
|
helper?.setSMCByte(key: key, value: value)
|
||||||
|
}
|
||||||
|
}
|
45
AlDente/Info.plist
Normal file
45
AlDente/Info.plist
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
<?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>CFBundleDevelopmentRegion</key>
|
||||||
|
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||||
|
<key>CFBundleExecutable</key>
|
||||||
|
<string>$(EXECUTABLE_NAME)</string>
|
||||||
|
<key>CFBundleIconFile</key>
|
||||||
|
<string></string>
|
||||||
|
<key>CFBundleIdentifier</key>
|
||||||
|
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||||
|
<key>CFBundleInfoDictionaryVersion</key>
|
||||||
|
<string>6.0</string>
|
||||||
|
<key>CFBundleName</key>
|
||||||
|
<string>$(PRODUCT_NAME)</string>
|
||||||
|
<key>CFBundlePackageType</key>
|
||||||
|
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
|
||||||
|
<key>CFBundleShortVersionString</key>
|
||||||
|
<string>$(MARKETING_VERSION)</string>
|
||||||
|
<key>CFBundleVersion</key>
|
||||||
|
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||||
|
<key>LSApplicationCategoryType</key>
|
||||||
|
<string>public.app-category.developer-tools</string>
|
||||||
|
<key>LSMinimumSystemVersion</key>
|
||||||
|
<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
|
||||||
|
<key>LSUIElement</key>
|
||||||
|
<true/>
|
||||||
|
<key>NSHumanReadableCopyright</key>
|
||||||
|
<string>Copyright © 2020 David Wernhart. All rights reserved.</string>
|
||||||
|
<key>NSMainStoryboardFile</key>
|
||||||
|
<string>Main</string>
|
||||||
|
<key>NSPrincipalClass</key>
|
||||||
|
<string>NSApplication</string>
|
||||||
|
<key>NSSupportsAutomaticTermination</key>
|
||||||
|
<false/>
|
||||||
|
<key>NSSupportsSuddenTermination</key>
|
||||||
|
<false/>
|
||||||
|
<key>SMPrivilegedExecutables</key>
|
||||||
|
<dict>
|
||||||
|
<key>com.davidwernhart.Helper</key>
|
||||||
|
<string>identifier "com.davidwernhart.Helper" and anchor apple generic and certificate leaf[subject.CN] = "Apple Development: david.wernhart96@gmail.com (GSDX9BQ584)" and certificate 1[field.1.2.840.113635.100.6.2.1] /* exists */</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
29
AlDente/PersistanceManager.swift
Normal file
29
AlDente/PersistanceManager.swift
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
//
|
||||||
|
// PersistanceManager.swift
|
||||||
|
// AlDente
|
||||||
|
//
|
||||||
|
// Created by David Wernhart on 07.02.21.
|
||||||
|
// Copyright © 2021 David Wernhart. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
class PersistanceManager{
|
||||||
|
static let instance = PersistanceManager()
|
||||||
|
|
||||||
|
public var launchOnLogin: Bool?
|
||||||
|
public var chargeVal: Int?
|
||||||
|
public var oldKey: Bool = false
|
||||||
|
|
||||||
|
public func load(){
|
||||||
|
launchOnLogin = UserDefaults.standard.bool(forKey: "launchOnLogin")
|
||||||
|
oldKey = UserDefaults.standard.bool(forKey: "oldKey")
|
||||||
|
chargeVal = UserDefaults.standard.integer(forKey: "chargeVal")
|
||||||
|
}
|
||||||
|
|
||||||
|
public func save(){
|
||||||
|
UserDefaults.standard.set(launchOnLogin, forKey: "launchOnLogin")
|
||||||
|
UserDefaults.standard.set(chargeVal, forKey: "chargeVal")
|
||||||
|
UserDefaults.standard.set(oldKey, forKey: "oldKey")
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"info" : {
|
||||||
|
"version" : 1,
|
||||||
|
"author" : "xcode"
|
||||||
|
}
|
||||||
|
}
|
27
Common/HelperToolProtocol.swift
Normal file
27
Common/HelperToolProtocol.swift
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
//
|
||||||
|
// HelperToolProtocol.swift
|
||||||
|
// AlDente
|
||||||
|
//
|
||||||
|
// Created by David Wernhart on 13.02.20.
|
||||||
|
// Copyright © 2020 David Wernhart. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import IOKit.pwr_mgt
|
||||||
|
|
||||||
|
let helperVersion: String = "10" //for some reason the integrated version check does not work, so I use this one
|
||||||
|
|
||||||
|
@objc(HelperToolProtocol) protocol HelperToolProtocol {
|
||||||
|
//protocol HelperToolProtocol {
|
||||||
|
func getVersion(withReply reply: @escaping (String) -> Void)
|
||||||
|
|
||||||
|
//TODO: more functions for other data types, altough this is sufficient for battery max charge level
|
||||||
|
func setSMCByte(key: String, value: UInt8)
|
||||||
|
func readSMCByte(key: String, withReply reply: @escaping (UInt8) -> Void)
|
||||||
|
func readSMCUInt32(key: String, withReply reply: @escaping (UInt32) -> Void)
|
||||||
|
|
||||||
|
func createAssertion(assertion:String, withReply reply: @escaping (IOPMAssertionID) -> Void)
|
||||||
|
func releaseAssertion(assertionID:IOPMAssertionID)
|
||||||
|
func setResetVal(key:String, value: UInt8)
|
||||||
|
|
||||||
|
}
|
85
README.md
Normal file
85
README.md
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
# AlDente 🍝
|
||||||
|
_macOS menu bar tool to limit maximum charging percentage_
|
||||||
|
|
||||||
|
#### Don't overcook your battery! Keep it fresh and chewy with AlDente.
|
||||||
|
|
||||||
|
## AlDente for Apple Silicon is coming soon!
|
||||||
|
|
||||||
|
## Why do I need this?
|
||||||
|
Li-ion and polymer batteries (like the one in your MacBook) last the longest when operating between 30 and 80 percent. Keeping your battery at 100% at all times can shorten the lifespan of your notebook significantly.
|
||||||
|
More information can be found here:
|
||||||
|
<https://batteryuniversity.com/learn/article/how_to_charge_when_to_charge_table>
|
||||||
|
|
||||||
|
## How does it work?
|
||||||
|
The tool writes the desired value to your MacBooks SMC (System Management Controller), which handles the rest.
|
||||||
|
For everyone that is curious, the modified SMC key is called "BCLM" (Presumably "Battery Charge Level Max")
|
||||||
|
|
||||||
|
## AlDente is tested and working on pretty much every Intel MacBook from 2011-2020, including:
|
||||||
|
* 2020 MacBook Pro 13" with 4 TB3 ports
|
||||||
|
* 2020 Macbook Air 13" (Intel)
|
||||||
|
* 2019 MacBook Pro 16"
|
||||||
|
* 2019 MacBook Pro 13"
|
||||||
|
* 2017 Macbook Pro 13" without TouchBar
|
||||||
|
* 2017 MacBook Air 13"
|
||||||
|
* 2017 MacBook 12"
|
||||||
|
* 2016 MacBook Pro 15"
|
||||||
|
* 2016 MacBook 12"
|
||||||
|
* 2015 MacBook 12"
|
||||||
|
* 2015 MacBook Pro 13"
|
||||||
|
* 2014 MacBook Air 13"
|
||||||
|
* 2014 MacBook Peo 13"
|
||||||
|
* 2013 MacBook Pro 13"
|
||||||
|
* 2010 MacBook
|
||||||
|
|
||||||
|
## Download:
|
||||||
|
AlDente is available as a homebrew cask. Install via
|
||||||
|
```
|
||||||
|
brew install aldente
|
||||||
|
```
|
||||||
|
|
||||||
|
Alternatively, you can download the app from GitHub: <https://github.com/davidwernhart/AlDente/releases>
|
||||||
|
|
||||||
|
## How to use:
|
||||||
|
Simply extract the latest .zip release and drag the App to your Applications Folder.
|
||||||
|
|
||||||
|
If you get the annoying `"AlDente cannot be opened because the developer cannot be verified"` or `"AlDente.app" can’t be opened because Apple can’t check it for malicious software.` messages on Catalina, simply navigate to your Applications folder using Finder, right click on AlDente and select `Open`. Then you can proceed to start the tool normally.
|
||||||
|
|
||||||
|
On the first start, the application is going to ask you to allow installing a helper tool. This is necessary, since writing SMC Keys requires root privileges.
|
||||||
|
Once finished, enter your desired max. charging percentage by clicking on the 🍝 icon on your
|
||||||
|
menu bar.
|
||||||
|
|
||||||
|
For some reason, macOS will always try to squeeze in a few more percent than specified by the SMC. For example, if you set yours to 80% it will stop charging at around 83%, so be patient. Strangely, this is not the case using Windows with bootcamp, therefore I have chosen not to correct this inaccuracy in code for now.
|
||||||
|
|
||||||
|
Usually, the operating system will take a minute or two registering the changes, so be patient. You can check if it's working by setting the max. percentage to e.g.: 70%. After a while, clicking on your battery icon will report "Battery is not charging" if you have more than ≈73% left, even tough your charger is connected. Notice that in this state, your MacBook is still powered by the charger, but the battery is not charging anymore.
|
||||||
|
|
||||||
|
## FAQ
|
||||||
|
* IT DOES NOT WORK???
|
||||||
|
|
||||||
|
AlDente does only prevent your Mac from charging more than you specify, it does NOT automatically drain your battery to the specified percentage.
|
||||||
|
* STILL NOT WORKING :(
|
||||||
|
|
||||||
|
Simply reboot your MacBook! This seems to do the trick for most people.
|
||||||
|
* IS MACOS BIG SUR SUPPORTED?
|
||||||
|
|
||||||
|
Allthough AlDente was written and compiled in Catalina, it works just fine on MacOS Big Sur.
|
||||||
|
* HOW TO UNINSTALL?
|
||||||
|
|
||||||
|
Make Sure to set your max. charging percentage back to 100 before moving AlDente to the Trash. If you really want to make sure, you can also reset your Mac's SMC. If you want to also remove the helper tool, take a look at this issue: <https://github.com/davidwernhart/AlDente/issues/19>.
|
||||||
|
* DO I HAVE TO UNCHECK THE BUILT IN BATTERY HEALTH CHECKBOX?
|
||||||
|
|
||||||
|
Since this checkbox does not seem to do much for most users, many reported that it does not matter, although I would reccomend to uncheck it just to make sure.
|
||||||
|
|
||||||
|
## Other tools I used in this project:
|
||||||
|
* <https://github.com/beltex/SMCKit>
|
||||||
|
* <https://github.com/sindresorhus/LaunchAtLogin>
|
||||||
|
* <https://github.com/andreyvit/create-dmg>
|
||||||
|
|
||||||
|
## Disclaimer:
|
||||||
|
I do not take any responsibility for any sort of damage in result of using this tool! Alltough this had no negative side effects for me, AlDente still taps in some very low level system functions that are not ment to be tampered with. Use it at your own risk!
|
||||||
|
|
||||||
|
Copyright(c) 2020 David Wernhart
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
437
SMJobBlessUtil.py
Executable file
437
SMJobBlessUtil.py
Executable file
@ -0,0 +1,437 @@
|
|||||||
|
#! /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 = {}
|
||||||
|
for toolName in os.listdir(toolDirPath):
|
||||||
|
req = readDesignatedRequirement(os.path.join(toolDirPath, toolName), "tool")
|
||||||
|
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)
|
18
com.davidwernhart.Helper/Helper-Info.plist
Normal file
18
com.davidwernhart.Helper/Helper-Info.plist
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<?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.davidwernhart.Helper</string>
|
||||||
|
<key>CFBundleInfoDictionaryVersion</key>
|
||||||
|
<string>6.0</string>
|
||||||
|
<key>CFBundleName</key>
|
||||||
|
<string>com.davidwernhart.Helper</string>
|
||||||
|
<key>CFBundleVersion</key>
|
||||||
|
<string>10</string>
|
||||||
|
<key>SMAuthorizedClients</key>
|
||||||
|
<array>
|
||||||
|
<string>identifier "com.davidwernhart.AlDente" and anchor apple generic and certificate leaf[subject.CN] = "Apple Development: david.wernhart96@gmail.com (GSDX9BQ584)" and certificate 1[field.1.2.840.113635.100.6.2.1] /* exists */</string>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
13
com.davidwernhart.Helper/Helper-Launchd.plist
Normal file
13
com.davidwernhart.Helper/Helper-Launchd.plist
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<?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>Label</key>
|
||||||
|
<string>com.davidwernhart.Helper</string>
|
||||||
|
<key>MachServices</key>
|
||||||
|
<dict>
|
||||||
|
<key>com.davidwernhart.Helper.mach</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
126
com.davidwernhart.Helper/HelperTool.swift
Normal file
126
com.davidwernhart.Helper/HelperTool.swift
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
//
|
||||||
|
// HelperTool.swift
|
||||||
|
// com.davidwernhart.Helper
|
||||||
|
//
|
||||||
|
// Created by David Wernhart on 13.02.20.
|
||||||
|
// Copyright © 2020 David Wernhart. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import IOKit.pwr_mgt
|
||||||
|
|
||||||
|
final class HelperTool: NSObject, HelperToolProtocol {
|
||||||
|
|
||||||
|
static let instance = HelperTool()
|
||||||
|
|
||||||
|
var modifiedKeys: [String: UInt8] = [:]
|
||||||
|
var openAssertions: [IOPMAssertionID] = []
|
||||||
|
|
||||||
|
func getVersion(withReply reply: (String) -> Void) {
|
||||||
|
// let version = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString" as String) as? String ?? "(unknown version)"
|
||||||
|
// let build = Bundle.main.object(forInfoDictionaryKey: kCFBundleVersionKey as String) as? String ?? "(unknown build)"
|
||||||
|
// reply("v\(version) (\(build))")
|
||||||
|
reply(helperVersion)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func setSMCByte(key: String, value: UInt8) {
|
||||||
|
do {
|
||||||
|
try SMCKit.open()
|
||||||
|
} catch {
|
||||||
|
print(error)
|
||||||
|
exit(EX_UNAVAILABLE)
|
||||||
|
}
|
||||||
|
let smcKey = SMCKit.getKey(key, type: DataTypes.UInt8)
|
||||||
|
let bytes: SMCBytes = (value, UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0),
|
||||||
|
UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0),
|
||||||
|
UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0),
|
||||||
|
UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0),
|
||||||
|
UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0),
|
||||||
|
UInt8(0), UInt8(0))
|
||||||
|
|
||||||
|
if(self.modifiedKeys[key] == nil){
|
||||||
|
readSMCByte(key: key) { (originalValue) in
|
||||||
|
self.modifiedKeys[key] = originalValue
|
||||||
|
_ = try? SMCKit.writeData(smcKey, data: bytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
_ = try? SMCKit.writeData(smcKey, data: bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func readSMCByte(key: String, withReply reply: @escaping (UInt8) -> Void) {
|
||||||
|
do {
|
||||||
|
try SMCKit.open()
|
||||||
|
} catch {
|
||||||
|
print(error)
|
||||||
|
exit(EX_UNAVAILABLE)
|
||||||
|
}
|
||||||
|
|
||||||
|
let smcKey = SMCKit.getKey(key, type: DataTypes.UInt8)
|
||||||
|
do {
|
||||||
|
let status = try SMCKit.readData(smcKey).0
|
||||||
|
reply(status)
|
||||||
|
} catch {
|
||||||
|
reply(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func readSMCUInt32(key: String, withReply reply: @escaping (UInt32) -> Void) {
|
||||||
|
do {
|
||||||
|
try SMCKit.open()
|
||||||
|
} catch {
|
||||||
|
print(error)
|
||||||
|
exit(EX_UNAVAILABLE)
|
||||||
|
}
|
||||||
|
|
||||||
|
let smcKey = SMCKit.getKey(key, type: DataTypes.UInt32)
|
||||||
|
do {
|
||||||
|
let data = try SMCKit.readData(smcKey)
|
||||||
|
reply(UInt32(fromBytes: (data.0, data.1, data.2, data.3)))
|
||||||
|
} catch {
|
||||||
|
reply(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func createAssertion(assertion:String, withReply reply: @escaping (IOPMAssertionID) -> Void){
|
||||||
|
var assertionID : IOPMAssertionID = IOPMAssertionID(0)
|
||||||
|
let reason:CFString = "AlDente" as NSString
|
||||||
|
let cfAssertion:CFString = assertion as NSString
|
||||||
|
let success = IOPMAssertionCreateWithName(cfAssertion,
|
||||||
|
IOPMAssertionLevel(kIOPMAssertionLevelOn),
|
||||||
|
reason,
|
||||||
|
&assertionID)
|
||||||
|
if success == kIOReturnSuccess {
|
||||||
|
openAssertions.append(assertionID)
|
||||||
|
reply(assertionID)
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
reply (UInt32(kCFNumberNaN))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func releaseAssertion(assertionID:IOPMAssertionID){
|
||||||
|
IOPMAssertionRelease(assertionID)
|
||||||
|
openAssertions.remove(at: openAssertions.firstIndex(of: assertionID)!)
|
||||||
|
}
|
||||||
|
|
||||||
|
func setResetVal(key:String, value: UInt8){
|
||||||
|
modifiedKeys[key]=value
|
||||||
|
}
|
||||||
|
|
||||||
|
func reset(){
|
||||||
|
for (key, value) in modifiedKeys{
|
||||||
|
setSMCByte(key: key, value: value)
|
||||||
|
}
|
||||||
|
|
||||||
|
for assertionID in openAssertions{
|
||||||
|
releaseAssertion(assertionID: assertionID)
|
||||||
|
}
|
||||||
|
modifiedKeys.removeAll()
|
||||||
|
openAssertions.removeAll()
|
||||||
|
}
|
||||||
|
}
|
795
com.davidwernhart.Helper/SMC.swift
Normal file
795
com.davidwernhart.Helper/SMC.swift
Normal file
@ -0,0 +1,795 @@
|
|||||||
|
//
|
||||||
|
// SMC.swift
|
||||||
|
// SMCKit
|
||||||
|
//
|
||||||
|
// The MIT License
|
||||||
|
//
|
||||||
|
// Copyright (C) 2014-2017 beltex <https://beltex.github.io>
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
import IOKit
|
||||||
|
import Foundation
|
||||||
|
import os
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// MARK: Type Aliases
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// http://stackoverflow.com/a/22383661
|
||||||
|
|
||||||
|
/// Floating point, unsigned, 14 bits exponent, 2 bits fraction
|
||||||
|
public typealias FPE2 = (UInt8, UInt8)
|
||||||
|
|
||||||
|
/// Floating point, signed, 7 bits exponent, 8 bits fraction
|
||||||
|
public typealias SP78 = (UInt8, UInt8)
|
||||||
|
|
||||||
|
public typealias SMCBytes = (UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8,
|
||||||
|
UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8,
|
||||||
|
UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8,
|
||||||
|
UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8,
|
||||||
|
UInt8, UInt8, UInt8, UInt8)
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// MARK: Standard Library Extensions
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
extension UInt32 {
|
||||||
|
|
||||||
|
init(fromBytes bytes: (UInt8, UInt8, UInt8, UInt8)) {
|
||||||
|
// TODO: Broken up due to "Expression was too complex" error as of
|
||||||
|
// Swift 4.
|
||||||
|
|
||||||
|
let byte0 = UInt32(bytes.0) << 24
|
||||||
|
let byte1 = UInt32(bytes.1) << 16
|
||||||
|
let byte2 = UInt32(bytes.2) << 8
|
||||||
|
let byte3 = UInt32(bytes.3)
|
||||||
|
|
||||||
|
self = byte0 | byte1 | byte2 | byte3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Bool {
|
||||||
|
|
||||||
|
init(fromByte byte: UInt8) {
|
||||||
|
self = byte == 1 ? true : false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public extension Int {
|
||||||
|
|
||||||
|
init(fromFPE2 bytes: FPE2) {
|
||||||
|
self = (Int(bytes.0) << 6) + (Int(bytes.1) >> 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
func toFPE2() -> FPE2 {
|
||||||
|
return (UInt8(self >> 6), UInt8((self << 2) ^ ((self >> 6) << 8)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Double {
|
||||||
|
|
||||||
|
init(fromSP78 bytes: SP78) {
|
||||||
|
// FIXME: Handle second byte
|
||||||
|
let sign = bytes.0 & 0x80 == 0 ? 1.0 : -1.0
|
||||||
|
self = sign * Double(bytes.0 & 0x7F) // AND to mask sign bit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Thanks to Airspeed Velocity for the great idea!
|
||||||
|
// http://airspeedvelocity.net/2015/05/22/my-talk-at-swift-summit/
|
||||||
|
public extension FourCharCode {
|
||||||
|
|
||||||
|
init(fromString str: String) {
|
||||||
|
precondition(str.count == 4)
|
||||||
|
|
||||||
|
self = str.utf8.reduce(0) { sum, character in
|
||||||
|
return sum << 8 | UInt32(character)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
init(fromStaticString str: StaticString) {
|
||||||
|
precondition(str.utf8CodeUnitCount == 4)
|
||||||
|
|
||||||
|
self = str.withUTF8Buffer { buffer in
|
||||||
|
// TODO: Broken up due to "Expression was too complex" error as of
|
||||||
|
// Swift 4.
|
||||||
|
|
||||||
|
let byte0 = UInt32(buffer[0]) << 24
|
||||||
|
let byte1 = UInt32(buffer[1]) << 16
|
||||||
|
let byte2 = UInt32(buffer[2]) << 8
|
||||||
|
let byte3 = UInt32(buffer[3])
|
||||||
|
|
||||||
|
return byte0 | byte1 | byte2 | byte3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func toString() -> String {
|
||||||
|
return String(describing: UnicodeScalar(self >> 24 & 0xff)!) +
|
||||||
|
String(describing: UnicodeScalar(self >> 16 & 0xff)!) +
|
||||||
|
String(describing: UnicodeScalar(self >> 8 & 0xff)!) +
|
||||||
|
String(describing: UnicodeScalar(self & 0xff)!)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// MARK: Defined by AppleSMC.kext
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/// Defined by AppleSMC.kext
|
||||||
|
///
|
||||||
|
/// This is the predefined struct that must be passed to communicate with the
|
||||||
|
/// AppleSMC driver. While the driver is closed source, the definition of this
|
||||||
|
/// struct happened to appear in the Apple PowerManagement project at around
|
||||||
|
/// version 211, and soon after disappeared. It can be seen in the PrivateLib.c
|
||||||
|
/// file under pmconfigd. Given that it is C code, this is the closest
|
||||||
|
/// translation to Swift from a type perspective.
|
||||||
|
///
|
||||||
|
/// ### Issues
|
||||||
|
///
|
||||||
|
/// * Padding for struct alignment when passed over to C side
|
||||||
|
/// * Size of struct must be 80 bytes
|
||||||
|
/// * C array's are bridged as tuples
|
||||||
|
///
|
||||||
|
/// http://www.opensource.apple.com/source/PowerManagement/PowerManagement-211/
|
||||||
|
public struct SMCParamStruct {
|
||||||
|
|
||||||
|
/// I/O Kit function selector
|
||||||
|
public enum Selector: UInt8 {
|
||||||
|
case kSMCHandleYPCEvent = 2
|
||||||
|
case kSMCReadKey = 5
|
||||||
|
case kSMCWriteKey = 6
|
||||||
|
case kSMCGetKeyFromIndex = 8
|
||||||
|
case kSMCGetKeyInfo = 9
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return codes for SMCParamStruct.result property
|
||||||
|
public enum Result: UInt8 {
|
||||||
|
case kSMCSuccess = 0
|
||||||
|
case kSMCError = 1
|
||||||
|
case kSMCKeyNotFound = 132
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct SMCVersion {
|
||||||
|
var major: CUnsignedChar = 0
|
||||||
|
var minor: CUnsignedChar = 0
|
||||||
|
var build: CUnsignedChar = 0
|
||||||
|
var reserved: CUnsignedChar = 0
|
||||||
|
var release: CUnsignedShort = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct SMCPLimitData {
|
||||||
|
var version: UInt16 = 0
|
||||||
|
var length: UInt16 = 0
|
||||||
|
var cpuPLimit: UInt32 = 0
|
||||||
|
var gpuPLimit: UInt32 = 0
|
||||||
|
var memPLimit: UInt32 = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct SMCKeyInfoData {
|
||||||
|
/// How many bytes written to SMCParamStruct.bytes
|
||||||
|
//var dataSize: IOByteCount = 0
|
||||||
|
var dataSize:UInt32 = 0
|
||||||
|
|
||||||
|
/// Type of data written to SMCParamStruct.bytes. This lets us know how
|
||||||
|
/// to interpret it (translate it to human readable)
|
||||||
|
var dataType: UInt32 = 0
|
||||||
|
|
||||||
|
var dataAttributes: UInt8 = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
/// FourCharCode telling the SMC what we want
|
||||||
|
var key: UInt32 = 0
|
||||||
|
|
||||||
|
var vers = SMCVersion()
|
||||||
|
|
||||||
|
var pLimitData = SMCPLimitData()
|
||||||
|
|
||||||
|
var keyInfo = SMCKeyInfoData()
|
||||||
|
|
||||||
|
/// Padding for struct alignment when passed over to C side
|
||||||
|
var padding: UInt16 = 0
|
||||||
|
|
||||||
|
/// Result of an operation
|
||||||
|
var result: UInt8 = 0
|
||||||
|
|
||||||
|
var status: UInt8 = 0
|
||||||
|
|
||||||
|
/// Method selector
|
||||||
|
var data8: UInt8 = 0
|
||||||
|
|
||||||
|
var data32: UInt32 = 0
|
||||||
|
|
||||||
|
/// Data returned from the SMC
|
||||||
|
var bytes: SMCBytes = (UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0),
|
||||||
|
UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0),
|
||||||
|
UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0),
|
||||||
|
UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0),
|
||||||
|
UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0),
|
||||||
|
UInt8(0), UInt8(0))
|
||||||
|
}
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// MARK: SMC Client
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/// SMC data type information
|
||||||
|
public struct DataTypes {
|
||||||
|
|
||||||
|
/// Fan information struct
|
||||||
|
public static let FDS =
|
||||||
|
DataType(type: FourCharCode(fromStaticString: "{fds"), size: 16)
|
||||||
|
public static let Flag =
|
||||||
|
DataType(type: FourCharCode(fromStaticString: "flag"), size: 1)
|
||||||
|
/// See type aliases
|
||||||
|
public static let FPE2 =
|
||||||
|
DataType(type: FourCharCode(fromStaticString: "fpe2"), size: 2)
|
||||||
|
/// See type aliases
|
||||||
|
public static let SP78 =
|
||||||
|
DataType(type: FourCharCode(fromStaticString: "sp78"), size: 2)
|
||||||
|
public static let UInt8 =
|
||||||
|
DataType(type: FourCharCode(fromStaticString: "ui8 "), size: 1)
|
||||||
|
public static let UInt32 =
|
||||||
|
DataType(type: FourCharCode(fromStaticString: "ui32"), size: 4)
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct SMCKey {
|
||||||
|
let code: FourCharCode
|
||||||
|
let info: DataType
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct DataType: Equatable {
|
||||||
|
let type: FourCharCode
|
||||||
|
let size: IOByteCount
|
||||||
|
}
|
||||||
|
|
||||||
|
public func ==(lhs: DataType, rhs: DataType) -> Bool {
|
||||||
|
return lhs.type == rhs.type && lhs.size == rhs.size
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Apple System Management Controller (SMC) user-space client for Intel-based
|
||||||
|
/// Macs. Works by talking to the AppleSMC.kext (kernel extension), the closed
|
||||||
|
/// source driver for the SMC.
|
||||||
|
public struct SMCKit {
|
||||||
|
|
||||||
|
public enum SMCError: Error {
|
||||||
|
|
||||||
|
/// AppleSMC driver not found
|
||||||
|
case driverNotFound
|
||||||
|
|
||||||
|
/// Failed to open a connection to the AppleSMC driver
|
||||||
|
case failedToOpen
|
||||||
|
|
||||||
|
/// This SMC key is not valid on this machine
|
||||||
|
case keyNotFound(code: String)
|
||||||
|
|
||||||
|
/// Requires root privileges
|
||||||
|
case notPrivileged
|
||||||
|
|
||||||
|
/// Fan speed must be > 0 && <= fanMaxSpeed
|
||||||
|
case unsafeFanSpeed
|
||||||
|
|
||||||
|
/// https://developer.apple.com/library/mac/qa/qa1075/_index.html
|
||||||
|
///
|
||||||
|
/// - parameter kIOReturn: I/O Kit error code
|
||||||
|
/// - parameter SMCResult: SMC specific return code
|
||||||
|
case unknown(kIOReturn: kern_return_t, SMCResult: UInt8)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Connection to the SMC driver
|
||||||
|
fileprivate static var connection: io_connect_t = 0
|
||||||
|
|
||||||
|
/// Open connection to the SMC driver. This must be done first before any
|
||||||
|
/// other calls
|
||||||
|
public static func open() throws {
|
||||||
|
let service = IOServiceGetMatchingService(kIOMasterPortDefault,
|
||||||
|
IOServiceMatching("AppleSMC"))
|
||||||
|
|
||||||
|
if service == 0 { throw SMCError.driverNotFound }
|
||||||
|
|
||||||
|
let result = IOServiceOpen(service, mach_task_self_, 0,
|
||||||
|
&SMCKit.connection)
|
||||||
|
IOObjectRelease(service)
|
||||||
|
|
||||||
|
if result != kIOReturnSuccess { throw SMCError.failedToOpen }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Close connection to the SMC driver
|
||||||
|
@discardableResult
|
||||||
|
public static func close() -> Bool {
|
||||||
|
let result = IOServiceClose(SMCKit.connection)
|
||||||
|
return result == kIOReturnSuccess ? true : false
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get information about a key
|
||||||
|
public static func keyInformation(_ key: FourCharCode) throws -> DataType {
|
||||||
|
var inputStruct = SMCParamStruct()
|
||||||
|
|
||||||
|
inputStruct.key = key
|
||||||
|
inputStruct.data8 = SMCParamStruct.Selector.kSMCGetKeyInfo.rawValue
|
||||||
|
|
||||||
|
let outputStruct = try callDriver(&inputStruct)
|
||||||
|
|
||||||
|
return DataType(type: outputStruct.keyInfo.dataType,
|
||||||
|
size: IOByteCount(outputStruct.keyInfo.dataSize))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get information about the key at index
|
||||||
|
public static func keyInformationAtIndex(_ index: Int) throws ->
|
||||||
|
FourCharCode {
|
||||||
|
var inputStruct = SMCParamStruct()
|
||||||
|
|
||||||
|
inputStruct.data8 = SMCParamStruct.Selector.kSMCGetKeyFromIndex.rawValue
|
||||||
|
inputStruct.data32 = UInt32(index)
|
||||||
|
|
||||||
|
let outputStruct = try callDriver(&inputStruct)
|
||||||
|
|
||||||
|
return outputStruct.key
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func getKey(_ code: String, type: DataType) -> SMCKey {
|
||||||
|
let key = SMCKey(code: FourCharCode(fromString: code), info: type)
|
||||||
|
return key
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Read data of a key
|
||||||
|
public static func readData(_ key: SMCKey) throws -> SMCBytes {
|
||||||
|
var inputStruct = SMCParamStruct()
|
||||||
|
|
||||||
|
inputStruct.key = key.code
|
||||||
|
inputStruct.keyInfo.dataSize = UInt32(IOByteCount(key.info.size))
|
||||||
|
inputStruct.data8 = SMCParamStruct.Selector.kSMCReadKey.rawValue
|
||||||
|
|
||||||
|
let outputStruct = try callDriver(&inputStruct)
|
||||||
|
|
||||||
|
return outputStruct.bytes
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Write data for a key
|
||||||
|
public static func writeData(_ key: SMCKey, data: SMCBytes) throws {
|
||||||
|
var inputStruct = SMCParamStruct()
|
||||||
|
|
||||||
|
inputStruct.key = key.code
|
||||||
|
inputStruct.bytes = data
|
||||||
|
inputStruct.keyInfo.dataSize = UInt32(IOByteCount(key.info.size))
|
||||||
|
inputStruct.data8 = SMCParamStruct.Selector.kSMCWriteKey.rawValue
|
||||||
|
|
||||||
|
_ = try callDriver(&inputStruct)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Make an actual call to the SMC driver
|
||||||
|
public static func callDriver(_ inputStruct: inout SMCParamStruct,
|
||||||
|
selector: SMCParamStruct.Selector = .kSMCHandleYPCEvent)
|
||||||
|
throws -> SMCParamStruct {
|
||||||
|
os_log("SMCPARAMSTRUCT SIZE: %d", MemoryLayout<SMCParamStruct>.stride)
|
||||||
|
assert(MemoryLayout<SMCParamStruct>.stride == 80, "SMCParamStruct size is != 80")
|
||||||
|
|
||||||
|
var outputStruct = SMCParamStruct()
|
||||||
|
let inputStructSize = MemoryLayout<SMCParamStruct>.stride
|
||||||
|
var outputStructSize = MemoryLayout<SMCParamStruct>.stride
|
||||||
|
|
||||||
|
let result = IOConnectCallStructMethod(SMCKit.connection,
|
||||||
|
UInt32(selector.rawValue),
|
||||||
|
&inputStruct,
|
||||||
|
inputStructSize,
|
||||||
|
&outputStruct,
|
||||||
|
&outputStructSize)
|
||||||
|
|
||||||
|
switch (result, outputStruct.result) {
|
||||||
|
case (kIOReturnSuccess, SMCParamStruct.Result.kSMCSuccess.rawValue):
|
||||||
|
return outputStruct
|
||||||
|
case (kIOReturnSuccess, SMCParamStruct.Result.kSMCKeyNotFound.rawValue):
|
||||||
|
throw SMCError.keyNotFound(code: inputStruct.key.toString())
|
||||||
|
case (kIOReturnNotPrivileged, _):
|
||||||
|
throw SMCError.notPrivileged
|
||||||
|
default:
|
||||||
|
throw SMCError.unknown(kIOReturn: result,
|
||||||
|
SMCResult: outputStruct.result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// MARK: General
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
extension SMCKit {
|
||||||
|
|
||||||
|
/// Get all valid SMC keys for this machine
|
||||||
|
public static func allKeys() throws -> [SMCKey] {
|
||||||
|
let count = try keyCount()
|
||||||
|
var keys = [SMCKey]()
|
||||||
|
|
||||||
|
for i in 0 ..< count {
|
||||||
|
let key = try keyInformationAtIndex(i)
|
||||||
|
let info = try keyInformation(key)
|
||||||
|
keys.append(SMCKey(code: key, info: info))
|
||||||
|
}
|
||||||
|
|
||||||
|
return keys
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the number of valid SMC keys for this machine
|
||||||
|
public static func keyCount() throws -> Int {
|
||||||
|
let key = SMCKey(code: FourCharCode(fromStaticString: "#KEY"),
|
||||||
|
info: DataTypes.UInt32)
|
||||||
|
|
||||||
|
let data = try readData(key)
|
||||||
|
return Int(UInt32(fromBytes: (data.0, data.1, data.2, data.3)))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Is this key valid on this machine?
|
||||||
|
public static func isKeyFound(_ code: FourCharCode) throws -> Bool {
|
||||||
|
do {
|
||||||
|
_ = try keyInformation(code)
|
||||||
|
} catch SMCError.keyNotFound { return false }
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// MARK: Temperature
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/// The list is NOT exhaustive. In addition, the names of the sensors may not be
|
||||||
|
/// mapped to the correct hardware component.
|
||||||
|
///
|
||||||
|
/// ### Sources
|
||||||
|
///
|
||||||
|
/// * powermetrics(1)
|
||||||
|
/// * https://www.apple.com/downloads/dashboard/status/istatpro.html
|
||||||
|
/// * https://github.com/hholtmann/smcFanControl
|
||||||
|
/// * https://github.com/jedda/OSX-Monitoring-Tools
|
||||||
|
/// * http://www.opensource.apple.com/source/net_snmp/
|
||||||
|
/// * http://www.parhelia.ch/blog/statics/k3_keys.html
|
||||||
|
public struct TemperatureSensors {
|
||||||
|
|
||||||
|
public static let AMBIENT_AIR_0 = TemperatureSensor(name: "AMBIENT_AIR_0",
|
||||||
|
code: FourCharCode(fromStaticString: "TA0P"))
|
||||||
|
public static let AMBIENT_AIR_1 = TemperatureSensor(name: "AMBIENT_AIR_1",
|
||||||
|
code: FourCharCode(fromStaticString: "TA1P"))
|
||||||
|
// Via powermetrics(1)
|
||||||
|
public static let CPU_0_DIE = TemperatureSensor(name: "CPU_0_DIE",
|
||||||
|
code: FourCharCode(fromStaticString: "TC0F"))
|
||||||
|
public static let CPU_0_DIODE = TemperatureSensor(name: "CPU_0_DIODE",
|
||||||
|
code: FourCharCode(fromStaticString: "TC0D"))
|
||||||
|
public static let CPU_0_HEATSINK = TemperatureSensor(name: "CPU_0_HEATSINK",
|
||||||
|
code: FourCharCode(fromStaticString: "TC0H"))
|
||||||
|
public static let CPU_0_PROXIMITY =
|
||||||
|
TemperatureSensor(name: "CPU_0_PROXIMITY",
|
||||||
|
code: FourCharCode(fromStaticString: "TC0P"))
|
||||||
|
public static let ENCLOSURE_BASE_0 =
|
||||||
|
TemperatureSensor(name: "ENCLOSURE_BASE_0",
|
||||||
|
code: FourCharCode(fromStaticString: "TB0T"))
|
||||||
|
public static let ENCLOSURE_BASE_1 =
|
||||||
|
TemperatureSensor(name: "ENCLOSURE_BASE_1",
|
||||||
|
code: FourCharCode(fromStaticString: "TB1T"))
|
||||||
|
public static let ENCLOSURE_BASE_2 =
|
||||||
|
TemperatureSensor(name: "ENCLOSURE_BASE_2",
|
||||||
|
code: FourCharCode(fromStaticString: "TB2T"))
|
||||||
|
public static let ENCLOSURE_BASE_3 =
|
||||||
|
TemperatureSensor(name: "ENCLOSURE_BASE_3",
|
||||||
|
code: FourCharCode(fromStaticString: "TB3T"))
|
||||||
|
public static let GPU_0_DIODE = TemperatureSensor(name: "GPU_0_DIODE",
|
||||||
|
code: FourCharCode(fromStaticString: "TG0D"))
|
||||||
|
public static let GPU_0_HEATSINK = TemperatureSensor(name: "GPU_0_HEATSINK",
|
||||||
|
code: FourCharCode(fromStaticString: "TG0H"))
|
||||||
|
public static let GPU_0_PROXIMITY =
|
||||||
|
TemperatureSensor(name: "GPU_0_PROXIMITY",
|
||||||
|
code: FourCharCode(fromStaticString: "TG0P"))
|
||||||
|
public static let HDD_PROXIMITY = TemperatureSensor(name: "HDD_PROXIMITY",
|
||||||
|
code: FourCharCode(fromStaticString: "TH0P"))
|
||||||
|
public static let HEATSINK_0 = TemperatureSensor(name: "HEATSINK_0",
|
||||||
|
code: FourCharCode(fromStaticString: "Th0H"))
|
||||||
|
public static let HEATSINK_1 = TemperatureSensor(name: "HEATSINK_1",
|
||||||
|
code: FourCharCode(fromStaticString: "Th1H"))
|
||||||
|
public static let HEATSINK_2 = TemperatureSensor(name: "HEATSINK_2",
|
||||||
|
code: FourCharCode(fromStaticString: "Th2H"))
|
||||||
|
public static let LCD_PROXIMITY = TemperatureSensor(name: "LCD_PROXIMITY",
|
||||||
|
code: FourCharCode(fromStaticString: "TL0P"))
|
||||||
|
public static let MEM_SLOT_0 = TemperatureSensor(name: "MEM_SLOT_0",
|
||||||
|
code: FourCharCode(fromStaticString: "TM0S"))
|
||||||
|
public static let MEM_SLOTS_PROXIMITY =
|
||||||
|
TemperatureSensor(name: "MEM_SLOTS_PROXIMITY",
|
||||||
|
code: FourCharCode(fromStaticString: "TM0P"))
|
||||||
|
public static let MISC_PROXIMITY = TemperatureSensor(name: "MISC_PROXIMITY",
|
||||||
|
code: FourCharCode(fromStaticString: "Tm0P"))
|
||||||
|
public static let NORTHBRIDGE = TemperatureSensor(name: "NORTHBRIDGE",
|
||||||
|
code: FourCharCode(fromStaticString: "TN0H"))
|
||||||
|
public static let NORTHBRIDGE_DIODE =
|
||||||
|
TemperatureSensor(name: "NORTHBRIDGE_DIODE",
|
||||||
|
code: FourCharCode(fromStaticString: "TN0D"))
|
||||||
|
public static let NORTHBRIDGE_PROXIMITY =
|
||||||
|
TemperatureSensor(name: "NORTHBRIDGE_PROXIMITY",
|
||||||
|
code: FourCharCode(fromStaticString: "TN0P"))
|
||||||
|
public static let ODD_PROXIMITY = TemperatureSensor(name: "ODD_PROXIMITY",
|
||||||
|
code: FourCharCode(fromStaticString: "TO0P"))
|
||||||
|
public static let PALM_REST = TemperatureSensor(name: "PALM_REST",
|
||||||
|
code: FourCharCode(fromStaticString: "Ts0P"))
|
||||||
|
public static let PWR_SUPPLY_PROXIMITY =
|
||||||
|
TemperatureSensor(name: "PWR_SUPPLY_PROXIMITY",
|
||||||
|
code: FourCharCode(fromStaticString: "Tp0P"))
|
||||||
|
public static let THUNDERBOLT_0 = TemperatureSensor(name: "THUNDERBOLT_0",
|
||||||
|
code: FourCharCode(fromStaticString: "TI0P"))
|
||||||
|
public static let THUNDERBOLT_1 = TemperatureSensor(name: "THUNDERBOLT_1",
|
||||||
|
code: FourCharCode(fromStaticString: "TI1P"))
|
||||||
|
|
||||||
|
public static let all = [AMBIENT_AIR_0.code: AMBIENT_AIR_0,
|
||||||
|
AMBIENT_AIR_1.code: AMBIENT_AIR_1,
|
||||||
|
CPU_0_DIE.code: CPU_0_DIE,
|
||||||
|
CPU_0_DIODE.code: CPU_0_DIODE,
|
||||||
|
CPU_0_HEATSINK.code: CPU_0_HEATSINK,
|
||||||
|
CPU_0_PROXIMITY.code: CPU_0_PROXIMITY,
|
||||||
|
ENCLOSURE_BASE_0.code: ENCLOSURE_BASE_0,
|
||||||
|
ENCLOSURE_BASE_1.code: ENCLOSURE_BASE_1,
|
||||||
|
ENCLOSURE_BASE_2.code: ENCLOSURE_BASE_2,
|
||||||
|
ENCLOSURE_BASE_3.code: ENCLOSURE_BASE_3,
|
||||||
|
GPU_0_DIODE.code: GPU_0_DIODE,
|
||||||
|
GPU_0_HEATSINK.code: GPU_0_HEATSINK,
|
||||||
|
GPU_0_PROXIMITY.code: GPU_0_PROXIMITY,
|
||||||
|
HDD_PROXIMITY.code: HDD_PROXIMITY,
|
||||||
|
HEATSINK_0.code: HEATSINK_0,
|
||||||
|
HEATSINK_1.code: HEATSINK_1,
|
||||||
|
HEATSINK_2.code: HEATSINK_2,
|
||||||
|
MEM_SLOT_0.code: MEM_SLOT_0,
|
||||||
|
MEM_SLOTS_PROXIMITY.code: MEM_SLOTS_PROXIMITY,
|
||||||
|
PALM_REST.code: PALM_REST,
|
||||||
|
LCD_PROXIMITY.code: LCD_PROXIMITY,
|
||||||
|
MISC_PROXIMITY.code: MISC_PROXIMITY,
|
||||||
|
NORTHBRIDGE.code: NORTHBRIDGE,
|
||||||
|
NORTHBRIDGE_DIODE.code: NORTHBRIDGE_DIODE,
|
||||||
|
NORTHBRIDGE_PROXIMITY.code: NORTHBRIDGE_PROXIMITY,
|
||||||
|
ODD_PROXIMITY.code: ODD_PROXIMITY,
|
||||||
|
PWR_SUPPLY_PROXIMITY.code: PWR_SUPPLY_PROXIMITY,
|
||||||
|
THUNDERBOLT_0.code: THUNDERBOLT_0,
|
||||||
|
THUNDERBOLT_1.code: THUNDERBOLT_1]
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct TemperatureSensor {
|
||||||
|
public let name: String
|
||||||
|
public let code: FourCharCode
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum TemperatureUnit {
|
||||||
|
case celius
|
||||||
|
case fahrenheit
|
||||||
|
case kelvin
|
||||||
|
|
||||||
|
public static func toFahrenheit(_ celius: Double) -> Double {
|
||||||
|
// https://en.wikipedia.org/wiki/Fahrenheit#Definition_and_conversions
|
||||||
|
return (celius * 1.8) + 32
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func toKelvin(_ celius: Double) -> Double {
|
||||||
|
// https://en.wikipedia.org/wiki/Kelvin
|
||||||
|
return celius + 273.15
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension SMCKit {
|
||||||
|
|
||||||
|
public static func allKnownTemperatureSensors() throws ->
|
||||||
|
[TemperatureSensor] {
|
||||||
|
var sensors = [TemperatureSensor]()
|
||||||
|
|
||||||
|
for sensor in TemperatureSensors.all.values {
|
||||||
|
if try isKeyFound(sensor.code) { sensors.append(sensor) }
|
||||||
|
}
|
||||||
|
|
||||||
|
return sensors
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func allUnknownTemperatureSensors() throws -> [TemperatureSensor] {
|
||||||
|
let keys = try allKeys()
|
||||||
|
|
||||||
|
return keys.filter { $0.code.toString().hasPrefix("T") &&
|
||||||
|
$0.info == DataTypes.SP78 &&
|
||||||
|
TemperatureSensors.all[$0.code] == nil }
|
||||||
|
.map { TemperatureSensor(name: "Unknown", code: $0.code) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get current temperature of a sensor
|
||||||
|
public static func temperature(_ sensorCode: FourCharCode,
|
||||||
|
unit: TemperatureUnit = .celius) throws -> Double {
|
||||||
|
let data = try readData(SMCKey(code: sensorCode, info: DataTypes.SP78))
|
||||||
|
|
||||||
|
let temperatureInCelius = Double(fromSP78: (data.0, data.1))
|
||||||
|
|
||||||
|
switch unit {
|
||||||
|
case .celius:
|
||||||
|
return temperatureInCelius
|
||||||
|
case .fahrenheit:
|
||||||
|
return TemperatureUnit.toFahrenheit(temperatureInCelius)
|
||||||
|
case .kelvin:
|
||||||
|
return TemperatureUnit.toKelvin(temperatureInCelius)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// MARK: Fan
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
public struct Fan {
|
||||||
|
// TODO: Should we start the fan id from 1 instead of 0?
|
||||||
|
public let id: Int
|
||||||
|
public let name: String
|
||||||
|
public let minSpeed: Int
|
||||||
|
public let maxSpeed: Int
|
||||||
|
}
|
||||||
|
|
||||||
|
extension SMCKit {
|
||||||
|
|
||||||
|
public static func allFans() throws -> [Fan] {
|
||||||
|
let count = try fanCount()
|
||||||
|
var fans = [Fan]()
|
||||||
|
|
||||||
|
for i in 0 ..< count {
|
||||||
|
fans.append(try SMCKit.fan(i))
|
||||||
|
}
|
||||||
|
|
||||||
|
return fans
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func fan(_ id: Int) throws -> Fan {
|
||||||
|
let name = try fanName(id)
|
||||||
|
let minSpeed = try fanMinSpeed(id)
|
||||||
|
let maxSpeed = try fanMaxSpeed(id)
|
||||||
|
return Fan(id: id, name: name, minSpeed: minSpeed, maxSpeed: maxSpeed)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Number of fans this machine has. All Intel based Macs, except for the
|
||||||
|
/// 2015 MacBook (8,1), have at least 1
|
||||||
|
public static func fanCount() throws -> Int {
|
||||||
|
let key = SMCKey(code: FourCharCode(fromStaticString: "FNum"),
|
||||||
|
info: DataTypes.UInt8)
|
||||||
|
|
||||||
|
let data = try readData(key)
|
||||||
|
return Int(data.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func fanName(_ id: Int) throws -> String {
|
||||||
|
let key = SMCKey(code: FourCharCode(fromString: "F\(id)ID"),
|
||||||
|
info: DataTypes.FDS)
|
||||||
|
let data = try readData(key)
|
||||||
|
|
||||||
|
// The last 12 bytes of '{fds' data type, a custom struct defined by the
|
||||||
|
// AppleSMC.kext that is 16 bytes, contains the fan name
|
||||||
|
let c1 = String(UnicodeScalar(data.4))
|
||||||
|
let c2 = String(UnicodeScalar(data.5))
|
||||||
|
let c3 = String(UnicodeScalar(data.6))
|
||||||
|
let c4 = String(UnicodeScalar(data.7))
|
||||||
|
let c5 = String(UnicodeScalar(data.8))
|
||||||
|
let c6 = String(UnicodeScalar(data.9))
|
||||||
|
let c7 = String(UnicodeScalar(data.10))
|
||||||
|
let c8 = String(UnicodeScalar(data.11))
|
||||||
|
let c9 = String(UnicodeScalar(data.12))
|
||||||
|
let c10 = String(UnicodeScalar(data.13))
|
||||||
|
let c11 = String(UnicodeScalar(data.14))
|
||||||
|
let c12 = String(UnicodeScalar(data.15))
|
||||||
|
|
||||||
|
let name = c1 + c2 + c3 + c4 + c5 + c6 + c7 + c8 + c9 + c10 + c11 + c12
|
||||||
|
|
||||||
|
let characterSet = CharacterSet.whitespaces
|
||||||
|
return name.trimmingCharacters(in: characterSet)
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func fanCurrentSpeed(_ id: Int) throws -> Int {
|
||||||
|
let key = SMCKey(code: FourCharCode(fromString: "F\(id)Ac"),
|
||||||
|
info: DataTypes.FPE2)
|
||||||
|
|
||||||
|
let data = try readData(key)
|
||||||
|
return Int(fromFPE2: (data.0, data.1))
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func fanMinSpeed(_ id: Int) throws -> Int {
|
||||||
|
let key = SMCKey(code: FourCharCode(fromString: "F\(id)Mn"),
|
||||||
|
info: DataTypes.FPE2)
|
||||||
|
|
||||||
|
let data = try readData(key)
|
||||||
|
return Int(fromFPE2: (data.0, data.1))
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func fanMaxSpeed(_ id: Int) throws -> Int {
|
||||||
|
let key = SMCKey(code: FourCharCode(fromString: "F\(id)Mx"),
|
||||||
|
info: DataTypes.FPE2)
|
||||||
|
|
||||||
|
let data = try readData(key)
|
||||||
|
return Int(fromFPE2: (data.0, data.1))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Requires root privileges. By minimum we mean that OS X can interject and
|
||||||
|
/// raise the fan speed if needed, however it will not go below this.
|
||||||
|
///
|
||||||
|
/// WARNING: You are playing with hardware here, BE CAREFUL.
|
||||||
|
///
|
||||||
|
/// - Throws: Of note, `SMCKit.SMCError`'s `UnsafeFanSpeed` and `NotPrivileged`
|
||||||
|
public static func fanSetMinSpeed(_ id: Int, speed: Int) throws {
|
||||||
|
let maxSpeed = try fanMaxSpeed(id)
|
||||||
|
if speed <= 0 || speed > maxSpeed { throw SMCError.unsafeFanSpeed }
|
||||||
|
|
||||||
|
let data = speed.toFPE2()
|
||||||
|
let bytes: SMCBytes = (data.0, data.1, UInt8(0), UInt8(0), UInt8(0), UInt8(0),
|
||||||
|
UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0),
|
||||||
|
UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0),
|
||||||
|
UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0),
|
||||||
|
UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0),
|
||||||
|
UInt8(0), UInt8(0))
|
||||||
|
|
||||||
|
let key = SMCKey(code: FourCharCode(fromString: "F\(id)Mn"),
|
||||||
|
info: DataTypes.FPE2)
|
||||||
|
|
||||||
|
try writeData(key, data: bytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// MARK: Miscellaneous
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
public struct batteryInfo {
|
||||||
|
public let batteryCount: Int
|
||||||
|
public let isACPresent: Bool
|
||||||
|
public let isBatteryPowered: Bool
|
||||||
|
public let isBatteryOk: Bool
|
||||||
|
public let isCharging: Bool
|
||||||
|
}
|
||||||
|
|
||||||
|
extension SMCKit {
|
||||||
|
|
||||||
|
public static func isOpticalDiskDriveFull() throws -> Bool {
|
||||||
|
// TODO: Should we catch key not found? That just means the machine
|
||||||
|
// doesn't have an ODD. Returning false though is not fully correct.
|
||||||
|
// Maybe we could throw a no ODD error instead?
|
||||||
|
let key = SMCKey(code: FourCharCode(fromStaticString: "MSDI"),
|
||||||
|
info: DataTypes.Flag)
|
||||||
|
|
||||||
|
let data = try readData(key)
|
||||||
|
return Bool(fromByte: data.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func batteryInformation() throws -> batteryInfo {
|
||||||
|
let batteryCountKey =
|
||||||
|
SMCKey(code: FourCharCode(fromStaticString: "BNum"),
|
||||||
|
info: DataTypes.UInt8)
|
||||||
|
let batteryPoweredKey =
|
||||||
|
SMCKey(code: FourCharCode(fromStaticString: "BATP"),
|
||||||
|
info: DataTypes.Flag)
|
||||||
|
let batteryInfoKey =
|
||||||
|
SMCKey(code: FourCharCode(fromStaticString: "BSIn"),
|
||||||
|
info: DataTypes.UInt8)
|
||||||
|
|
||||||
|
let batteryCountData = try readData(batteryCountKey)
|
||||||
|
let batteryCount = Int(batteryCountData.0)
|
||||||
|
|
||||||
|
let isBatteryPoweredData = try readData(batteryPoweredKey)
|
||||||
|
let isBatteryPowered = Bool(fromByte: isBatteryPoweredData.0)
|
||||||
|
|
||||||
|
let batteryInfoData = try readData(batteryInfoKey)
|
||||||
|
let isCharging = batteryInfoData.0 & 1 == 1 ? true : false
|
||||||
|
let isACPresent = (batteryInfoData.0 >> 1) & 1 == 1 ? true : false
|
||||||
|
let isBatteryOk = (batteryInfoData.0 >> 6) & 1 == 1 ? true : false
|
||||||
|
|
||||||
|
return batteryInfo(batteryCount: batteryCount, isACPresent: isACPresent,
|
||||||
|
isBatteryPowered: isBatteryPowered,
|
||||||
|
isBatteryOk: isBatteryOk,
|
||||||
|
isCharging: isCharging)
|
||||||
|
}
|
||||||
|
}
|
44
com.davidwernhart.Helper/main.swift
Normal file
44
com.davidwernhart.Helper/main.swift
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
//
|
||||||
|
// main.swift
|
||||||
|
// com.davidwernhart.Helper
|
||||||
|
//
|
||||||
|
// Created by David Wernhart on 10.02.20.
|
||||||
|
// Copyright © 2020 David Wernhart. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import AppKit
|
||||||
|
|
||||||
|
final class HelperDelegate: NSObject, NSXPCListenerDelegate {
|
||||||
|
func listener(_ listener: NSXPCListener, shouldAcceptNewConnection newConnection: NSXPCConnection) -> Bool {
|
||||||
|
newConnection.exportedInterface = NSXPCInterface(with: HelperToolProtocol.self)
|
||||||
|
newConnection.exportedObject = HelperTool.instance
|
||||||
|
newConnection.resume()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let delegate = HelperDelegate()
|
||||||
|
let listener = NSXPCListener(machServiceName: "com.davidwernhart.Helper.mach")
|
||||||
|
listener.delegate = delegate
|
||||||
|
listener.resume()
|
||||||
|
var hasChecked = false
|
||||||
|
Timer.scheduledTimer(withTimeInterval: 10, repeats: true) { timer in
|
||||||
|
let workspace = NSWorkspace.shared
|
||||||
|
let applications = workspace.runningApplications
|
||||||
|
var foundApp = false
|
||||||
|
for app in applications {
|
||||||
|
if(app.bundleIdentifier?.elementsEqual("com.davidwernhart.AlDente") == true){
|
||||||
|
foundApp = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(foundApp && hasChecked){
|
||||||
|
hasChecked = false
|
||||||
|
}
|
||||||
|
else if(!foundApp && !hasChecked){
|
||||||
|
hasChecked = true
|
||||||
|
HelperTool.instance.reset()
|
||||||
|
exit(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RunLoop.current.run()
|
Loading…
Reference in New Issue
Block a user