Apple Silicon Support

This commit is contained in:
David Wernhart 2021-02-14 22:01:54 +01:00
commit 77119a1a13
30 changed files with 3178 additions and 0 deletions

View 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 */;
}

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:">
</FileRef>
</Workspace>

View File

@ -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>

View File

@ -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
}

View File

@ -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>

View File

@ -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
View 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)
}
}
}
}

View 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
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

View File

@ -0,0 +1,6 @@
{
"info" : {
"version" : 1,
"author" : "xcode"
}
}

View 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
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

View 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
View 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
View 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
View 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>

View 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")
}
}

View File

@ -0,0 +1,6 @@
{
"info" : {
"version" : 1,
"author" : "xcode"
}
}

View 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
View 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" cant be opened because Apple cant 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
View 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)

View 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>

View 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>

View 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()
}
}

View 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)
}
}

View 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()