3 Commits
20 changed files with 1881 additions and 0 deletions
+27
View File
@@ -0,0 +1,27 @@
# macOS
.DS_Store
.AppleDouble
.LSOverride
Icon
# Xcode & DerivedData
build/
DerivedData/
*.moved-builder
*.hmap
*.xccheckout
*.xcscmblueprint
# Xcode user state
xcuserdata/
*.xcuserdatad
*.xcuserstate
# Swift Package Manager
.build/
.swiftpm/
# Build outputs & Archives
*.dmg
*.zip
*.app
+10
View File
@@ -0,0 +1,10 @@
<?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>com.apple.security.app-sandbox</key>
<false/>
<key>com.apple.security.get-task-allow</key>
<true/>
</dict>
</plist>
+523
View File
@@ -0,0 +1,523 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 77;
objects = {
/* Begin PBXBuildFile section */
1879A3DCF6E305D7F2CD472E /* CoreFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2345555FF9780B02A90CC7A9 /* CoreFoundation.framework */; };
1B3F1C8ABF0ADFAA290FF6F6 /* MMSMCParser.m in Sources */ = {isa = PBXBuildFile; fileRef = 375AA781A2AA0A20B119C7B7 /* MMSMCParser.m */; };
3C0AC14F803C813287D8A663 /* IOKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 007F48A5A599AB4E8D216D16 /* IOKit.framework */; };
88387CDAE2AC1CC74EF89B57 /* MacMonitorApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = ADEA6BD38BA8D887B1DB64A9 /* MacMonitorApp.swift */; };
9BCE74C5454F81EF80492E75 /* MacMonitorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2EC3EDFCD5C50CB352FCED87 /* MacMonitorTests.swift */; };
A7609CE56D49EC3419987468 /* SystemTelemetryStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4B4E94FB2886C60D5548A2A /* SystemTelemetryStore.swift */; };
A9FCAEC0BF27A419DB148C4A /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E142AE61F9B87757997870DD /* ContentView.swift */; };
CD91FC663B19DA1FEA3D49D7 /* MMAppleSMCClient.m in Sources */ = {isa = PBXBuildFile; fileRef = 5555436CD3CB8A73F5D6504F /* MMAppleSMCClient.m */; };
EAFAF5A10502D4ACC64F4720 /* MMTelemetryCoordinator.m in Sources */ = {isa = PBXBuildFile; fileRef = F9F9AD1268F906761032AF4D /* MMTelemetryCoordinator.m */; };
EC9FA3E4A119277B89E73F8D /* MMSMCTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 940D608A1AF80F75584E744E /* MMSMCTests.swift */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
09422FEC7F2490CC38ED2F77 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 50A0A2115D830ED94B0DCD89 /* Project object */;
proxyType = 1;
remoteGlobalIDString = 8DA8F83F9F811331FC56346B;
remoteInfo = MacMonitor;
};
/* End PBXContainerItemProxy section */
/* Begin PBXFileReference section */
007F48A5A599AB4E8D216D16 /* IOKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = IOKit.framework; path = System/Library/Frameworks/IOKit.framework; sourceTree = SDKROOT; };
1AD9EEE571CD904BBCD1C96B /* MMSMCParser.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MMSMCParser.h; sourceTree = "<group>"; };
2345555FF9780B02A90CC7A9 /* CoreFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreFoundation.framework; path = System/Library/Frameworks/CoreFoundation.framework; sourceTree = SDKROOT; };
2EC3EDFCD5C50CB352FCED87 /* MacMonitorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MacMonitorTests.swift; sourceTree = "<group>"; };
32340166F6100A08105308D3 /* MMTelemetryCoordinator.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MMTelemetryCoordinator.h; sourceTree = "<group>"; };
375AA781A2AA0A20B119C7B7 /* MMSMCParser.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MMSMCParser.m; sourceTree = "<group>"; };
5555436CD3CB8A73F5D6504F /* MMAppleSMCClient.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MMAppleSMCClient.m; sourceTree = "<group>"; };
5635101F862DE680856BDE6E /* MMTelemetryDomain.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MMTelemetryDomain.h; sourceTree = "<group>"; };
56E285A0A1746AE659981F22 /* MacMonitorTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MacMonitorTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
6D0E5CAF25DFA9A1D2A698A0 /* MMTelemetryProvider.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MMTelemetryProvider.h; sourceTree = "<group>"; };
726005CB5A6C86E7D80D3CA0 /* MMAppleSMCClient.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MMAppleSMCClient.h; sourceTree = "<group>"; };
8FBB436F3D9472194D8C6EDD /* MMSMCDefines.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MMSMCDefines.h; sourceTree = "<group>"; };
940D608A1AF80F75584E744E /* MMSMCTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MMSMCTests.swift; sourceTree = "<group>"; };
ADEA6BD38BA8D887B1DB64A9 /* MacMonitorApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MacMonitorApp.swift; sourceTree = "<group>"; };
E142AE61F9B87757997870DD /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
E4B4E94FB2886C60D5548A2A /* SystemTelemetryStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SystemTelemetryStore.swift; sourceTree = "<group>"; };
F5EC59DCDA5FABDA409CDBCB /* MacMonitor.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MacMonitor.app; sourceTree = BUILT_PRODUCTS_DIR; };
F9F9AD1268F906761032AF4D /* MMTelemetryCoordinator.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MMTelemetryCoordinator.m; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
232654D97C29A6EB7C0150DA /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
3C0AC14F803C813287D8A663 /* IOKit.framework in Frameworks */,
1879A3DCF6E305D7F2CD472E /* CoreFoundation.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
1E4B6EF5F92A76F77E44AEAD = {
isa = PBXGroup;
children = (
2D4E3C74114E2D44711BCFAD /* Sources */,
C9A24E2CE4CBD45E8F5175DD /* Tests */,
E56B76815ED75B28A13D6814 /* Frameworks */,
C80C3EEE8F912F4B214102E3 /* Products */,
);
sourceTree = "<group>";
};
2D4E3C74114E2D44711BCFAD /* Sources */ = {
isa = PBXGroup;
children = (
4D8D8B40632C525CBC1D0E43 /* App */,
54787E6270EA7B9A1BE11359 /* Core */,
9844EC526E3527F6A582179F /* Hardware */,
3E4A0B53C583BD0B7A68EACE /* UI */,
);
path = Sources;
sourceTree = "<group>";
};
3E4A0B53C583BD0B7A68EACE /* UI */ = {
isa = PBXGroup;
children = (
E142AE61F9B87757997870DD /* ContentView.swift */,
);
path = UI;
sourceTree = "<group>";
};
4D8D8B40632C525CBC1D0E43 /* App */ = {
isa = PBXGroup;
children = (
ADEA6BD38BA8D887B1DB64A9 /* MacMonitorApp.swift */,
E4B4E94FB2886C60D5548A2A /* SystemTelemetryStore.swift */,
);
path = App;
sourceTree = "<group>";
};
54787E6270EA7B9A1BE11359 /* Core */ = {
isa = PBXGroup;
children = (
32340166F6100A08105308D3 /* MMTelemetryCoordinator.h */,
F9F9AD1268F906761032AF4D /* MMTelemetryCoordinator.m */,
5635101F862DE680856BDE6E /* MMTelemetryDomain.h */,
6D0E5CAF25DFA9A1D2A698A0 /* MMTelemetryProvider.h */,
);
path = Core;
sourceTree = "<group>";
};
9844EC526E3527F6A582179F /* Hardware */ = {
isa = PBXGroup;
children = (
E012FBB26EF9E35A2FAF87E2 /* AppleSMC */,
);
path = Hardware;
sourceTree = "<group>";
};
C80C3EEE8F912F4B214102E3 /* Products */ = {
isa = PBXGroup;
children = (
F5EC59DCDA5FABDA409CDBCB /* MacMonitor.app */,
56E285A0A1746AE659981F22 /* MacMonitorTests.xctest */,
);
name = Products;
sourceTree = "<group>";
};
C9A24E2CE4CBD45E8F5175DD /* Tests */ = {
isa = PBXGroup;
children = (
2EC3EDFCD5C50CB352FCED87 /* MacMonitorTests.swift */,
940D608A1AF80F75584E744E /* MMSMCTests.swift */,
);
path = Tests;
sourceTree = "<group>";
};
E012FBB26EF9E35A2FAF87E2 /* AppleSMC */ = {
isa = PBXGroup;
children = (
726005CB5A6C86E7D80D3CA0 /* MMAppleSMCClient.h */,
5555436CD3CB8A73F5D6504F /* MMAppleSMCClient.m */,
8FBB436F3D9472194D8C6EDD /* MMSMCDefines.h */,
1AD9EEE571CD904BBCD1C96B /* MMSMCParser.h */,
375AA781A2AA0A20B119C7B7 /* MMSMCParser.m */,
);
path = AppleSMC;
sourceTree = "<group>";
};
E56B76815ED75B28A13D6814 /* Frameworks */ = {
isa = PBXGroup;
children = (
2345555FF9780B02A90CC7A9 /* CoreFoundation.framework */,
007F48A5A599AB4E8D216D16 /* IOKit.framework */,
);
name = Frameworks;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
8DA8F83F9F811331FC56346B /* MacMonitor */ = {
isa = PBXNativeTarget;
buildConfigurationList = F8BFF00A996663DD56D3BBC8 /* Build configuration list for PBXNativeTarget "MacMonitor" */;
buildPhases = (
9F478799591119C7B9BC9B18 /* Sources */,
232654D97C29A6EB7C0150DA /* Frameworks */,
);
buildRules = (
);
dependencies = (
);
name = MacMonitor;
packageProductDependencies = (
);
productName = MacMonitor;
productReference = F5EC59DCDA5FABDA409CDBCB /* MacMonitor.app */;
productType = "com.apple.product-type.application";
};
AC0AE508FCA91494F76BAB20 /* MacMonitorTests */ = {
isa = PBXNativeTarget;
buildConfigurationList = 7116650F4E57A5D713138F34 /* Build configuration list for PBXNativeTarget "MacMonitorTests" */;
buildPhases = (
28403A95C96F704B96652C2E /* Sources */,
);
buildRules = (
);
dependencies = (
E940019BD253D9933F8340A5 /* PBXTargetDependency */,
);
name = MacMonitorTests;
packageProductDependencies = (
);
productName = MacMonitorTests;
productReference = 56E285A0A1746AE659981F22 /* MacMonitorTests.xctest */;
productType = "com.apple.product-type.bundle.unit-test";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
50A0A2115D830ED94B0DCD89 /* Project object */ = {
isa = PBXProject;
attributes = {
BuildIndependentTargetsInParallel = YES;
LastUpgradeCheck = 1600;
TargetAttributes = {
};
};
buildConfigurationList = 00AAEAE8F34AB900B7EE6E43 /* Build configuration list for PBXProject "MacMonitor" */;
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
Base,
en,
);
mainGroup = 1E4B6EF5F92A76F77E44AEAD;
minimizedProjectReferenceProxies = 1;
preferredProjectObjectVersion = 77;
productRefGroup = C80C3EEE8F912F4B214102E3 /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
8DA8F83F9F811331FC56346B /* MacMonitor */,
AC0AE508FCA91494F76BAB20 /* MacMonitorTests */,
);
};
/* End PBXProject section */
/* Begin PBXSourcesBuildPhase section */
28403A95C96F704B96652C2E /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
EC9FA3E4A119277B89E73F8D /* MMSMCTests.swift in Sources */,
9BCE74C5454F81EF80492E75 /* MacMonitorTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
9F478799591119C7B9BC9B18 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
A9FCAEC0BF27A419DB148C4A /* ContentView.swift in Sources */,
CD91FC663B19DA1FEA3D49D7 /* MMAppleSMCClient.m in Sources */,
1B3F1C8ABF0ADFAA290FF6F6 /* MMSMCParser.m in Sources */,
EAFAF5A10502D4ACC64F4720 /* MMTelemetryCoordinator.m in Sources */,
88387CDAE2AC1CC74EF89B57 /* MacMonitorApp.swift in Sources */,
A7609CE56D49EC3419987468 /* SystemTelemetryStore.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
E940019BD253D9933F8340A5 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 8DA8F83F9F811331FC56346B /* MacMonitor */;
targetProxy = 09422FEC7F2490CC38ED2F77 /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
/* Begin XCBuildConfiguration section */
3BE0BD5FC635F9E80F31CE13 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_ENTITLEMENTS = MacMonitor.entitlements;
COMBINE_HIDPI_IMAGES = YES;
HEADER_SEARCH_PATHS = (
"$(SRCROOT)/Sources/Core",
"$(SRCROOT)/Sources/Bridging",
"$(SRCROOT)/Sources/Hardware/**",
);
INFOPLIST_KEY_CFBundleDisplayName = MacMonitor;
INFOPLIST_KEY_NSPrincipalClass = NSApplication;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 14.0;
PRODUCT_BUNDLE_IDENTIFIER = com.i3omb.MacMonitor;
SDKROOT = macosx;
SWIFT_OBJC_BRIDGING_HEADER = "Sources/Bridging/MacMonitor-Bridging-Header.h";
};
name = Debug;
};
59F6FC23B6D4F0B126319453 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
COMBINE_HIDPI_IMAGES = YES;
HEADER_SEARCH_PATHS = (
"$(SRCROOT)/Sources/Core",
"$(SRCROOT)/Sources/Bridging",
"$(SRCROOT)/Sources/Hardware/**",
);
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
"@loader_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 14.0;
PRODUCT_BUNDLE_IDENTIFIER = com.i3omb.MacMonitorTests;
SDKROOT = macosx;
SWIFT_OBJC_BRIDGING_HEADER = "Sources/Bridging/MacMonitor-Bridging-Header.h";
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/MacMonitor.app/Contents/MacOS/MacMonitor";
};
name = Release;
};
5B1AD1F77021853BC57757D7 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ARCHS = x86_64;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
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;
CODE_SIGNING_ALLOWED = NO;
CODE_SIGN_IDENTITY = "";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
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;
GENERATE_INFOPLIST_FILE = YES;
MACOSX_DEPLOYMENT_TARGET = 14.0;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = NO;
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = macosx;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
SWIFT_VERSION = 5.0;
VALID_ARCHS = x86_64;
};
name = Release;
};
5D8000DCD693316A897E9E66 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
COMBINE_HIDPI_IMAGES = YES;
HEADER_SEARCH_PATHS = (
"$(SRCROOT)/Sources/Core",
"$(SRCROOT)/Sources/Bridging",
"$(SRCROOT)/Sources/Hardware/**",
);
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
"@loader_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 14.0;
PRODUCT_BUNDLE_IDENTIFIER = com.i3omb.MacMonitorTests;
SDKROOT = macosx;
SWIFT_OBJC_BRIDGING_HEADER = "Sources/Bridging/MacMonitor-Bridging-Header.h";
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/MacMonitor.app/Contents/MacOS/MacMonitor";
};
name = Debug;
};
67DE32FBD89D242E194436AA /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ARCHS = x86_64;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
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;
CODE_SIGNING_ALLOWED = NO;
CODE_SIGN_IDENTITY = "";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"$(inherited)",
"DEBUG=1",
);
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;
GENERATE_INFOPLIST_FILE = YES;
MACOSX_DEPLOYMENT_TARGET = 14.0;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = NO;
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = macosx;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
VALID_ARCHS = x86_64;
};
name = Debug;
};
7FA38ECBD02E9FC203FFF0EA /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_ENTITLEMENTS = MacMonitor.entitlements;
COMBINE_HIDPI_IMAGES = YES;
HEADER_SEARCH_PATHS = (
"$(SRCROOT)/Sources/Core",
"$(SRCROOT)/Sources/Bridging",
"$(SRCROOT)/Sources/Hardware/**",
);
INFOPLIST_KEY_CFBundleDisplayName = MacMonitor;
INFOPLIST_KEY_NSPrincipalClass = NSApplication;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 14.0;
PRODUCT_BUNDLE_IDENTIFIER = com.i3omb.MacMonitor;
SDKROOT = macosx;
SWIFT_OBJC_BRIDGING_HEADER = "Sources/Bridging/MacMonitor-Bridging-Header.h";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
00AAEAE8F34AB900B7EE6E43 /* Build configuration list for PBXProject "MacMonitor" */ = {
isa = XCConfigurationList;
buildConfigurations = (
67DE32FBD89D242E194436AA /* Debug */,
5B1AD1F77021853BC57757D7 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Debug;
};
7116650F4E57A5D713138F34 /* Build configuration list for PBXNativeTarget "MacMonitorTests" */ = {
isa = XCConfigurationList;
buildConfigurations = (
5D8000DCD693316A897E9E66 /* Debug */,
59F6FC23B6D4F0B126319453 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Debug;
};
F8BFF00A996663DD56D3BBC8 /* Build configuration list for PBXNativeTarget "MacMonitor" */ = {
isa = XCConfigurationList;
buildConfigurations = (
3BE0BD5FC635F9E80F31CE13 /* Debug */,
7FA38ECBD02E9FC203FFF0EA /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Debug;
};
/* End XCConfigurationList section */
};
rootObject = 50A0A2115D830ED94B0DCD89 /* Project object */;
}
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:">
</FileRef>
</Workspace>
+15
View File
@@ -0,0 +1,15 @@
import SwiftUI
@main
struct MacMonitorApp: App {
@State private var store = SystemTelemetryStore.shared
var body: some Scene {
WindowGroup {
ContentView()
}
.windowStyle(.titleBar)
.windowToolbarStyle(.unified)
.defaultSize(width: 960, height: 640)
}
}
+83
View File
@@ -0,0 +1,83 @@
import Foundation
import SwiftUI
import Observation
@Observable
@MainActor
public final class SystemTelemetryStore {
public static let shared = SystemTelemetryStore()
public private(set) var isRunning: Bool = false
public private(set) var lastUpdateTimestamp: Date = .now
public private(set) var sampleInterval: TimeInterval = 1.0
/// Raw snapshots organized by provider identifier
public private(set) var latestSnapshot: [String: [String: Any]] = [:]
/// System identification
public let hostModel: String
public let osVersion: String
public let kernelVersion: String
public let physicalCpuCount: Int
public let logicalCpuCount: Int
private let coordinator = MMTelemetryCoordinator.shared
private init() {
self.hostModel = SystemTelemetryStore.readSysctlString("hw.model") ?? "Intel Mac"
self.osVersion = ProcessInfo.processInfo.operatingSystemVersionString
self.kernelVersion = SystemTelemetryStore.readSysctlString("kern.osrelease") ?? "Unknown Kernel"
self.physicalCpuCount = SystemTelemetryStore.readSysctlInt("hw.physicalcpu") ?? 1
self.logicalCpuCount = SystemTelemetryStore.readSysctlInt("hw.logicalcpu") ?? 1
setupCoordinator()
}
private func setupCoordinator() {
coordinator.sampleInterval = self.sampleInterval
coordinator.snapshotHandler = { [weak self] snapshot in
guard let self else { return }
self.latestSnapshot = snapshot
self.lastUpdateTimestamp = Date()
}
}
public func start() {
coordinator.start()
isRunning = coordinator.isRunning
}
public func stop() {
coordinator.stop()
isRunning = coordinator.isRunning
}
public func setSampleInterval(_ interval: TimeInterval) {
self.sampleInterval = interval
coordinator.sampleInterval = interval
}
public func triggerManualSample() {
coordinator.sampleImmediately()
}
public var registeredProviderCount: Int {
coordinator.registeredProviders().count
}
private static func readSysctlString(_ name: String) -> String? {
var size: size_t = 0
sysctlbyname(name, nil, &size, nil, 0)
guard size > 0 else { return nil }
var buffer = [CChar](repeating: 0, count: size)
sysctlbyname(name, &buffer, &size, nil, 0)
return String(cString: buffer)
}
private static func readSysctlInt(_ name: String) -> Int? {
var value: Int32 = 0
var size = MemoryLayout<Int32>.size
let result = sysctlbyname(name, &value, &size, nil, 0)
return result == 0 ? Int(value) : nil
}
}
@@ -0,0 +1,14 @@
#ifndef MacMonitor_Bridging_Header_h
#define MacMonitor_Bridging_Header_h
// Core Architecture & Telemetry Contracts
#import "MMTelemetryDomain.h"
#import "MMTelemetryProvider.h"
#import "MMTelemetryCoordinator.h"
// Hardware Abstraction - AppleSMC
#import "MMSMCDefines.h"
#import "MMSMCParser.h"
#import "MMAppleSMCClient.h"
#endif /* MacMonitor_Bridging_Header_h */
+69
View File
@@ -0,0 +1,69 @@
#ifndef MMTelemetryCoordinator_h
#define MMTelemetryCoordinator_h
#import <Foundation/Foundation.h>
#import "MMTelemetryProvider.h"
NS_ASSUME_NONNULL_BEGIN
typedef void (^MMTelemetrySnapshotHandler)(NSDictionary<NSString *, NSDictionary<NSString *, id> *> *snapshot);
/**
* MMTelemetryCoordinator
* Central dispatcher orchestrating telemetry sampling loops across all registered providers
* on a dedicated low-priority background queue (QOS_CLASS_UTILITY).
*/
@interface MMTelemetryCoordinator : NSObject
@property (class, readonly, strong) MMTelemetryCoordinator *sharedCoordinator;
/**
* Current polling interval in seconds. Defaults to 1.0 second.
*/
@property (nonatomic, assign) NSTimeInterval sampleInterval;
/**
* Whether the sampling timer is actively running.
*/
@property (nonatomic, readonly, getter=isRunning) BOOL running;
/**
* Snapshot delivery callback dispatched on the main thread whenever a new sampling cycle completes.
*/
@property (nonatomic, copy, nullable) MMTelemetrySnapshotHandler snapshotHandler;
/**
* Registers a provider. Thread-safe.
*/
- (void)registerProvider:(id<MMTelemetryProvider>)provider;
/**
* Unregisters a provider by its unique identifier. Thread-safe.
*/
- (void)unregisterProviderWithIdentifier:(NSString *)identifier;
/**
* Returns a list of currently registered providers.
*/
- (NSArray<id<MMTelemetryProvider>> *)registeredProviders;
/**
* Starts the recurring telemetry dispatch timer.
*/
- (void)start;
/**
* Stops the recurring telemetry dispatch timer.
*/
- (void)stop;
/**
* Triggers an immediate one-shot sample pass across all providers.
*/
- (void)sampleImmediately;
@end
NS_ASSUME_NONNULL_END
#endif /* MMTelemetryCoordinator_h */
+171
View File
@@ -0,0 +1,171 @@
#import "MMTelemetryCoordinator.h"
#import <os/lock.h>
@interface MMTelemetryCoordinator () {
os_unfair_lock _lock;
dispatch_queue_t _telemetryQueue;
dispatch_source_t _timerSource;
NSMutableDictionary<NSString *, id<MMTelemetryProvider>> *_providers;
BOOL _running;
}
@end
@implementation MMTelemetryCoordinator
+ (instancetype)sharedCoordinator {
static MMTelemetryCoordinator *instance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [[self alloc] init];
});
return instance;
}
- (instancetype)init {
self = [super init];
if (self) {
_lock = OS_UNFAIR_LOCK_INIT;
_sampleInterval = 1.0;
_providers = [NSMutableDictionary dictionary];
dispatch_queue_attr_t qosAttr = dispatch_queue_attr_make_with_qos_class(
DISPATCH_QUEUE_SERIAL,
QOS_CLASS_UTILITY,
0
);
_telemetryQueue = dispatch_queue_create("com.i3omb.macmonitor.telemetry", qosAttr);
}
return self;
}
- (void)dealloc {
[self stop];
}
- (void)setSampleInterval:(NSTimeInterval)sampleInterval {
os_unfair_lock_lock(&_lock);
if (sampleInterval < 0.1) {
sampleInterval = 0.1; // clamp minimum sampling interval to 100ms
}
_sampleInterval = sampleInterval;
BOOL shouldRestart = _running;
os_unfair_lock_unlock(&_lock);
if (shouldRestart) {
[self stop];
[self start];
}
}
- (BOOL)isRunning {
os_unfair_lock_lock(&_lock);
BOOL r = _running;
os_unfair_lock_unlock(&_lock);
return r;
}
- (void)registerProvider:(id<MMTelemetryProvider>)provider {
if (!provider || !provider.providerIdentifier) return;
os_unfair_lock_lock(&_lock);
_providers[provider.providerIdentifier] = provider;
os_unfair_lock_unlock(&_lock);
if ([provider respondsToSelector:@selector(startMonitoring)]) {
[provider startMonitoring];
}
}
- (void)unregisterProviderWithIdentifier:(NSString *)identifier {
if (!identifier) return;
id<MMTelemetryProvider> provider = nil;
os_unfair_lock_lock(&_lock);
provider = _providers[identifier];
[_providers removeObjectForKey:identifier];
os_unfair_lock_unlock(&_lock);
if (provider && [provider respondsToSelector:@selector(stopMonitoring)]) {
[provider stopMonitoring];
}
}
- (NSArray<id<MMTelemetryProvider>> *)registeredProviders {
os_unfair_lock_lock(&_lock);
NSArray *list = [_providers.allValues copy];
os_unfair_lock_unlock(&_lock);
return list;
}
- (void)start {
os_unfair_lock_lock(&_lock);
if (_running) {
os_unfair_lock_unlock(&_lock);
return;
}
_running = YES;
_timerSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, _telemetryQueue);
uint64_t intervalNs = (uint64_t)(_sampleInterval * NSEC_PER_SEC);
dispatch_source_set_timer(_timerSource, dispatch_time(DISPATCH_TIME_NOW, 0), intervalNs, intervalNs / 10);
__weak typeof(self) weakSelf = self;
dispatch_source_set_event_handler(_timerSource, ^{
[weakSelf performSamplePass];
});
dispatch_resume(_timerSource);
os_unfair_lock_unlock(&_lock);
}
- (void)stop {
os_unfair_lock_lock(&_lock);
if (!_running) {
os_unfair_lock_unlock(&_lock);
return;
}
_running = NO;
if (_timerSource) {
dispatch_source_cancel(_timerSource);
_timerSource = nil;
}
os_unfair_lock_unlock(&_lock);
}
- (void)sampleImmediately {
__weak typeof(self) weakSelf = self;
dispatch_async(_telemetryQueue, ^{
[weakSelf performSamplePass];
});
}
- (void)performSamplePass {
@autoreleasepool {
NSArray<id<MMTelemetryProvider>> *providersToSample = [self registeredProviders];
NSMutableDictionary<NSString *, NSDictionary<NSString *, id> *> *snapshot =
[NSMutableDictionary dictionaryWithCapacity:providersToSample.count];
for (id<MMTelemetryProvider> provider in providersToSample) {
@autoreleasepool {
if (!provider.isAvailable) {
continue;
}
NSError *sampleError = nil;
NSDictionary<NSString *, id> *metrics = [provider sampleTelemetryWithError:&sampleError];
if (metrics) {
snapshot[provider.providerIdentifier] = metrics;
}
}
}
NSDictionary<NSString *, NSDictionary<NSString *, id> *> *immutableSnapshot = [snapshot copy];
MMTelemetrySnapshotHandler handler = self.snapshotHandler;
if (handler) {
dispatch_async(dispatch_get_main_queue(), ^{
handler(immutableSnapshot);
});
}
}
}
@end
+40
View File
@@ -0,0 +1,40 @@
#ifndef MMTelemetryDomain_h
#define MMTelemetryDomain_h
#import <Foundation/Foundation.h>
typedef NS_ENUM(NSInteger, MMTelemetryDomain) {
MMTelemetryDomainSystem NS_SWIFT_NAME(system) = 0,
MMTelemetryDomainCPU NS_SWIFT_NAME(cpu),
MMTelemetryDomainThermal NS_SWIFT_NAME(thermal),
MMTelemetryDomainFan NS_SWIFT_NAME(fan),
MMTelemetryDomainMemory NS_SWIFT_NAME(memory),
MMTelemetryDomainStorage NS_SWIFT_NAME(storage),
MMTelemetryDomainNetwork NS_SWIFT_NAME(network),
MMTelemetryDomainProcess NS_SWIFT_NAME(process),
MMTelemetryDomainGPU NS_SWIFT_NAME(gpu),
MMTelemetryDomainPower NS_SWIFT_NAME(power),
MMTelemetryDomainAudio NS_SWIFT_NAME(audio),
MMTelemetryDomainPeripherals NS_SWIFT_NAME(peripherals),
MMTelemetryDomainDisplay NS_SWIFT_NAME(display)
};
static inline NSString * _Nonnull MMTelemetryDomainToString(MMTelemetryDomain domain) {
switch (domain) {
case MMTelemetryDomainSystem: return @"System";
case MMTelemetryDomainCPU: return @"CPU";
case MMTelemetryDomainThermal: return @"Thermal";
case MMTelemetryDomainFan: return @"Fan";
case MMTelemetryDomainMemory: return @"Memory";
case MMTelemetryDomainStorage: return @"Storage";
case MMTelemetryDomainNetwork: return @"Network";
case MMTelemetryDomainProcess: return @"Process";
case MMTelemetryDomainGPU: return @"GPU";
case MMTelemetryDomainPower: return @"Power";
case MMTelemetryDomainAudio: return @"Audio";
case MMTelemetryDomainPeripherals: return @"Peripherals";
case MMTelemetryDomainDisplay: return @"Display";
}
}
#endif /* MMTelemetryDomain_h */
+34
View File
@@ -0,0 +1,34 @@
#ifndef MMTelemetryProvider_h
#define MMTelemetryProvider_h
#import <Foundation/Foundation.h>
#import "MMTelemetryDomain.h"
NS_ASSUME_NONNULL_BEGIN
/**
* MMTelemetryProvider
* Protocol defining the standard contract for all low-level telemetry sampling engines.
*/
@protocol MMTelemetryProvider <NSObject>
@property (nonatomic, readonly) MMTelemetryDomain domain;
@property (nonatomic, readonly, copy) NSString *providerIdentifier;
@property (nonatomic, readonly, getter=isAvailable) BOOL available;
/**
* Samples instantaneous or delta telemetry metrics.
* Called exclusively on the background utility telemetry queue: com.i3omb.macmonitor.telemetry.
* Returns an immutable dictionary containing scalar values, strings, or arrays.
*/
- (nullable NSDictionary<NSString *, id> *)sampleTelemetryWithError:(NSError **)error;
@optional
- (void)startMonitoring;
- (void)stopMonitoring;
@end
NS_ASSUME_NONNULL_END
#endif /* MMTelemetryProvider_h */
@@ -0,0 +1,58 @@
#ifndef MMAppleSMCClient_h
#define MMAppleSMCClient_h
#import <Foundation/Foundation.h>
#import "MMSMCDefines.h"
#import "MMSMCParser.h"
NS_ASSUME_NONNULL_BEGIN
/**
* MMAppleSMCClient
* Thread-safe singleton providing direct communication with the AppleSMC driver
* on Intel Mac platforms via IOKit user client calls.
*/
@interface MMAppleSMCClient : NSObject
@property (class, readonly, strong) MMAppleSMCClient *sharedClient;
/**
* Whether the AppleSMC IOKit driver connection is open and active.
*/
@property (nonatomic, readonly, getter=isAvailable) BOOL available;
/**
* Total number of SMC keys advertised by hardware (#KEY), or 0 if unavailable.
*/
@property (nonatomic, readonly) NSInteger totalKeyCount;
/**
* Explicitly opens the IOKit AppleSMC connection.
*/
- (BOOL)openWithError:(NSError **)error;
/**
* Closes the active IOKit AppleSMC connection.
*/
- (void)close;
/**
* Reads raw bytes for a given 4-character SMC key (e.g. "TC0P", "F0Ac").
*/
- (nullable NSData *)readBytesForKey:(NSString *)key error:(NSError **)error;
/**
* Reads and automatically decodes a numeric value according to its SMC type descriptor.
*/
- (nullable NSNumber *)readNumericValueForKey:(NSString *)key error:(NSError **)error;
/**
* Returns the FourCC data type string for an SMC key (e.g. "sp78", "fpe2", "ui16").
*/
- (nullable NSString *)readDataTypeForKey:(NSString *)key error:(NSError **)error;
@end
NS_ASSUME_NONNULL_END
#endif /* MMAppleSMCClient_h */
@@ -0,0 +1,245 @@
#import "MMAppleSMCClient.h"
#import <IOKit/IOKitLib.h>
#import <os/lock.h>
@interface MMAppleSMCClient () {
os_unfair_lock _lock;
io_connect_t _connection;
NSMutableDictionary<NSString *, NSValue *> *_keyInfoCache;
BOOL _opened;
NSInteger _totalKeyCount;
}
@end
@implementation MMAppleSMCClient
+ (instancetype)sharedClient {
static MMAppleSMCClient *instance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [[self alloc] init];
});
return instance;
}
- (instancetype)init {
self = [super init];
if (self) {
_lock = OS_UNFAIR_LOCK_INIT;
_keyInfoCache = [NSMutableDictionary dictionary];
[self openWithError:nil];
}
return self;
}
- (void)dealloc {
[self close];
}
- (BOOL)isAvailable {
os_unfair_lock_lock(&_lock);
BOOL avail = (_connection != IO_OBJECT_NULL && _opened);
os_unfair_lock_unlock(&_lock);
return avail;
}
- (NSInteger)totalKeyCount {
os_unfair_lock_lock(&_lock);
NSInteger count = _totalKeyCount;
os_unfair_lock_unlock(&_lock);
return count;
}
- (BOOL)openWithError:(NSError **)error {
os_unfair_lock_lock(&_lock);
if (_opened && _connection != IO_OBJECT_NULL) {
os_unfair_lock_unlock(&_lock);
return YES;
}
CFMutableDictionaryRef matching = IOServiceMatching("AppleSMCClient");
if (!matching) {
os_unfair_lock_unlock(&_lock);
if (error) {
*error = [NSError errorWithDomain:@"com.i3omb.MacMonitor.SMC"
code:-1
userInfo:@{NSLocalizedDescriptionKey: @"Failed to create AppleSMCClient matching dictionary"}];
}
return NO;
}
io_service_t service = IOServiceGetMatchingService(kIOMainPortDefault, matching);
if (service == IO_OBJECT_NULL) {
os_unfair_lock_unlock(&_lock);
if (error) {
*error = [NSError errorWithDomain:@"com.i3omb.MacMonitor.SMC"
code:-2
userInfo:@{NSLocalizedDescriptionKey: @"AppleSMCClient service not found (running on non-Mac or unsupported VM)"}];
}
return NO;
}
kern_return_t kr = IOServiceOpen(service, mach_task_self(), 0, &_connection);
IOObjectRelease(service);
if (kr != KERN_SUCCESS || _connection == IO_OBJECT_NULL) {
_connection = IO_OBJECT_NULL;
_opened = NO;
os_unfair_lock_unlock(&_lock);
if (error) {
*error = [NSError errorWithDomain:@"com.i3omb.MacMonitor.SMC"
code:kr
userInfo:@{NSLocalizedDescriptionKey: [NSString stringWithFormat:@"IOServiceOpen failed with code: 0x%x", kr]}];
}
return NO;
}
_opened = YES;
os_unfair_lock_unlock(&_lock);
// Probe total keys count
NSNumber *keysCount = [self readNumericValueForKey:@"#KEY" error:nil];
if (keysCount) {
os_unfair_lock_lock(&_lock);
_totalKeyCount = [keysCount integerValue];
os_unfair_lock_unlock(&_lock);
}
return YES;
}
- (void)close {
os_unfair_lock_lock(&_lock);
if (_connection != IO_OBJECT_NULL) {
IOServiceClose(_connection);
_connection = IO_OBJECT_NULL;
}
_opened = NO;
[_keyInfoCache removeAllObjects];
os_unfair_lock_unlock(&_lock);
}
- (BOOL)callSMCWithInput:(const SMCKeyData_t *)input output:(SMCKeyData_t *)output {
if (_connection == IO_OBJECT_NULL) return NO;
size_t inputSize = sizeof(SMCKeyData_t);
size_t outputSize = sizeof(SMCKeyData_t);
kern_return_t kr = IOConnectCallStructMethod(
_connection,
kSMCHandleYPCEvent,
input,
inputSize,
output,
&outputSize
);
return (kr == KERN_SUCCESS && output->result == 0);
}
- (BOOL)fetchKeyInfo:(NSString *)key info:(SMCKeyInfoData *)outInfo {
if (key.length != 4) return NO;
os_unfair_lock_lock(&_lock);
NSValue *cached = _keyInfoCache[key];
if (cached) {
[cached getValue:outInfo size:sizeof(SMCKeyInfoData)];
os_unfair_lock_unlock(&_lock);
return YES;
}
SMCKeyData_t input = {0};
SMCKeyData_t output = {0};
input.key = MMSMCToFourCharCode([key UTF8String]);
input.data8 = kSMCGetKeyInfo;
BOOL success = [self callSMCWithInput:&input output:&output];
if (success) {
*outInfo = output.keyInfo;
_keyInfoCache[key] = [NSValue value:outInfo withObjCType:@encode(SMCKeyInfoData)];
}
os_unfair_lock_unlock(&_lock);
return success;
}
- (nullable NSData *)readBytesForKey:(NSString *)key error:(NSError **)error {
if (![self isAvailable]) {
if (![self openWithError:error]) return nil;
}
SMCKeyInfoData info = {0};
if (![self fetchKeyInfo:key info:&info]) {
if (error) {
*error = [NSError errorWithDomain:@"com.i3omb.MacMonitor.SMC"
code:-3
userInfo:@{NSLocalizedDescriptionKey: [NSString stringWithFormat:@"Failed to retrieve key info for '%@'", key]}];
}
return nil;
}
if (info.dataSize > 32) {
if (error) {
*error = [NSError errorWithDomain:@"com.i3omb.MacMonitor.SMC"
code:-4
userInfo:@{NSLocalizedDescriptionKey: [NSString stringWithFormat:@"Data size %lu exceeds 32 bytes for key '%@'", (unsigned long)info.dataSize, key]}];
}
return nil;
}
os_unfair_lock_lock(&_lock);
SMCKeyData_t input = {0};
SMCKeyData_t output = {0};
input.key = MMSMCToFourCharCode([key UTF8String]);
input.keyInfo.dataSize = info.dataSize;
input.data8 = kSMCReadKey;
BOOL success = [self callSMCWithInput:&input output:&output];
os_unfair_lock_unlock(&_lock);
if (!success) {
if (error) {
*error = [NSError errorWithDomain:@"com.i3omb.MacMonitor.SMC"
code:-5
userInfo:@{NSLocalizedDescriptionKey: [NSString stringWithFormat:@"SMC read command failed for key '%@'", key]}];
}
return nil;
}
return [NSData dataWithBytes:output.bytes length:info.dataSize];
}
- (nullable NSString *)readDataTypeForKey:(NSString *)key error:(NSError **)error {
SMCKeyInfoData info = {0};
if (![self fetchKeyInfo:key info:&info]) {
if (error) {
*error = [NSError errorWithDomain:@"com.i3omb.MacMonitor.SMC"
code:-3
userInfo:@{NSLocalizedDescriptionKey: [NSString stringWithFormat:@"Failed to retrieve key info for '%@'", key]}];
}
return nil;
}
char typeStr[5] = {0};
MMSMCFromFourCharCode(info.dataType, typeStr);
return [NSString stringWithUTF8String:typeStr];
}
- (nullable NSNumber *)readNumericValueForKey:(NSString *)key error:(NSError **)error {
SMCKeyInfoData info = {0};
if (![self fetchKeyInfo:key info:&info]) {
return nil;
}
NSData *data = [self readBytesForKey:key error:error];
if (!data) return nil;
char typeStr[5] = {0};
MMSMCFromFourCharCode(info.dataType, typeStr);
NSString *dataType = [NSString stringWithUTF8String:typeStr];
return [MMSMCParser decodeValueWithDataType:dataType bytes:data.bytes size:data.length];
}
@end
+79
View File
@@ -0,0 +1,79 @@
#ifndef MMSMCDefines_h
#define MMSMCDefines_h
#import <Foundation/Foundation.h>
#import <IOKit/IOKitLib.h>
#define KERNEL_INDEX_SMC 2
// SMC Call Selectors
#define kSMCUserClientOpen 0
#define kSMCUserClientClose 1
#define kSMCHandleYPCEvent 2
#define kSMCReadKey 5
#define kSMCWriteKey 6
#define kSMCGetKeyFromIndex 8
#define kSMCGetKeyInfo 9
#pragma pack(push, 1)
typedef struct {
unsigned char major;
unsigned char minor;
unsigned char build;
unsigned char reserved[1];
unsigned short release;
} SMCVersion;
typedef struct {
uint16_t version;
uint16_t length;
uint32_t cpuPLimit;
uint32_t gpuPLimit;
uint32_t memPLimit;
} SMCPLimitData;
typedef struct {
IOByteCount dataSize;
UInt32 dataType;
UInt8 dataAttributes;
} SMCKeyInfoData;
typedef struct {
UInt32 key;
SMCVersion vers;
SMCPLimitData pLimitData;
SMCKeyInfoData keyInfo;
UInt8 result;
UInt8 status;
UInt8 data8;
UInt32 data32;
UInt8 bytes[32];
} SMCKeyData_t;
typedef struct {
char key[5];
UInt32 dataSize;
char dataType[5];
UInt8 bytes[32];
} SMCVal_t;
#pragma pack(pop)
static inline UInt32 MMSMCToFourCharCode(const char * _Nonnull str) {
UInt32 code = 0;
for (int i = 0; i < 4 && str[i] != '\0'; i++) {
code = (code << 8) | (UInt8)str[i];
}
return code;
}
static inline void MMSMCFromFourCharCode(UInt32 code, char * _Nonnull outStr) {
outStr[0] = (char)((code >> 24) & 0xFF);
outStr[1] = (char)((code >> 16) & 0xFF);
outStr[2] = (char)((code >> 8) & 0xFF);
outStr[3] = (char)(code & 0xFF);
outStr[4] = '\0';
}
#endif /* MMSMCDefines_h */
+50
View File
@@ -0,0 +1,50 @@
#ifndef MMSMCParser_h
#define MMSMCParser_h
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface MMSMCParser : NSObject
/**
* Decodes a signed 8.8 fixed-point format (sp78), used widely for Intel Mac temperature sensors.
*/
+ (float)decodeSP78:(const uint8_t *)bytes;
/**
* Decodes an unsigned 14.2 fixed-point format (fpe2), used for Fan RPM values.
*/
+ (float)decodeFPE2:(const uint8_t *)bytes;
/**
* Decodes a standard IEEE 754 32-bit floating point value (flt).
*/
+ (float)decodeFLT:(const uint8_t *)bytes;
/**
* Decodes an unsigned 8-bit integer (ui8).
*/
+ (uint8_t)decodeUI8:(const uint8_t *)bytes;
/**
* Decodes a big-endian unsigned 16-bit integer (ui16).
*/
+ (uint16_t)decodeUI16:(const uint8_t *)bytes;
/**
* Decodes a big-endian unsigned 32-bit integer (ui32).
*/
+ (uint32_t)decodeUI32:(const uint8_t *)bytes;
/**
* Generic parser taking an SMC dataType FourCC string ("sp78", "fpe2", "flt", "ui8 ", "ui16", "ui32", "flag")
* and returning a parsed NSNumber.
*/
+ (nullable NSNumber *)decodeValueWithDataType:(NSString *)dataType bytes:(const uint8_t *)bytes size:(NSUInteger)size;
@end
NS_ASSUME_NONNULL_END
#endif /* MMSMCParser_h */
+67
View File
@@ -0,0 +1,67 @@
#import "MMSMCParser.h"
#import <string.h>
@implementation MMSMCParser
+ (float)decodeSP78:(const uint8_t *)bytes {
if (!bytes) return 0.0f;
int16_t raw = (int16_t)(((uint16_t)bytes[0] << 8) | (uint16_t)bytes[1]);
return (float)raw / 256.0f;
}
+ (float)decodeFPE2:(const uint8_t *)bytes {
if (!bytes) return 0.0f;
uint16_t raw = ((uint16_t)bytes[0] << 8) | (uint16_t)bytes[1];
return (float)raw / 4.0f;
}
+ (float)decodeFLT:(const uint8_t *)bytes {
if (!bytes) return 0.0f;
float val = 0.0f;
memcpy(&val, bytes, sizeof(float));
return val;
}
+ (uint8_t)decodeUI8:(const uint8_t *)bytes {
if (!bytes) return 0;
return bytes[0];
}
+ (uint16_t)decodeUI16:(const uint8_t *)bytes {
if (!bytes) return 0;
return ((uint16_t)bytes[0] << 8) | (uint16_t)bytes[1];
}
+ (uint32_t)decodeUI32:(const uint8_t *)bytes {
if (!bytes) return 0;
return ((uint32_t)bytes[0] << 24) |
((uint32_t)bytes[1] << 16) |
((uint32_t)bytes[2] << 8) |
(uint32_t)bytes[3];
}
+ (nullable NSNumber *)decodeValueWithDataType:(NSString *)dataType bytes:(const uint8_t *)bytes size:(NSUInteger)size {
if (!bytes || size == 0) return nil;
NSString *trimmedType = [dataType stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
if ([trimmedType isEqualToString:@"sp78"]) {
if (size >= 2) return @([self decodeSP78:bytes]);
} else if ([trimmedType isEqualToString:@"fpe2"]) {
if (size >= 2) return @([self decodeFPE2:bytes]);
} else if ([trimmedType isEqualToString:@"flt"]) {
if (size >= 4) return @([self decodeFLT:bytes]);
} else if ([trimmedType isEqualToString:@"ui8"]) {
if (size >= 1) return @([self decodeUI8:bytes]);
} else if ([trimmedType isEqualToString:@"ui16"]) {
if (size >= 2) return @([self decodeUI16:bytes]);
} else if ([trimmedType isEqualToString:@"ui32"]) {
if (size >= 4) return @([self decodeUI32:bytes]);
} else if ([trimmedType isEqualToString:@"flag"]) {
if (size >= 1) return @(bytes[0] != 0);
}
return nil;
}
@end
+227
View File
@@ -0,0 +1,227 @@
import SwiftUI
public struct ContentView: View {
@State private var store = SystemTelemetryStore.shared
public init() {}
public var body: some View {
NavigationSplitView {
sidebarContent
} detail: {
detailContent
}
.frame(minWidth: 800, minHeight: 520)
.onAppear {
store.start()
}
}
private var sidebarContent: some View {
List {
Section("System Status") {
Label("Dashboard", systemImage: "gauge.with.needle")
Label("Architecture", systemImage: "cpu")
}
Section("Telemetry Domains") {
Label("CPU & Thermals", systemImage: "flame")
Label("Fans & Cooling", systemImage: "fanblades")
Label("Memory & Swap", systemImage: "memorychip")
Label("Disks & Volumes", systemImage: "internaldrive")
Label("Processes", systemImage: "list.bullet.rectangle")
Label("Network", systemImage: "network")
Label("Graphics (GPU)", systemImage: "display")
}
}
.listStyle(.sidebar)
.navigationSplitViewColumnWidth(min: 200, ideal: 220, max: 280)
}
private var detailContent: some View {
ScrollView {
VStack(alignment: .leading, spacing: 20) {
// Header Banner
headerCard
// Telemetry Engine Control
engineControlCard
// System Specifications
specsCard
// Providers Status
providersCard
}
.padding(24)
}
.navigationTitle("MacMonitor Dashboard")
}
private var headerCard: some View {
HStack(alignment: .center, spacing: 16) {
Image(systemName: "macpro.gen3.fill")
.resizable()
.scaledToFit()
.frame(width: 48, height: 48)
.foregroundStyle(.blue)
VStack(alignment: .leading, spacing: 4) {
Text(store.hostModel)
.font(.title2.bold())
Text("Intel x86_64 • \(store.osVersion)")
.font(.subheadline)
.foregroundStyle(.secondary)
}
Spacer()
HStack(spacing: 8) {
Circle()
.fill(store.isRunning ? Color.green : Color.red)
.frame(width: 10, height: 10)
Text(store.isRunning ? "ENGINE ACTIVE" : "ENGINE PAUSED")
.font(.caption.bold())
.foregroundStyle(store.isRunning ? .green : .secondary)
}
.padding(.horizontal, 12)
.padding(.vertical, 6)
.background(.quaternary, in: Capsule())
}
.padding(16)
.background(.background, in: RoundedRectangle(cornerRadius: 12))
.overlay(RoundedRectangle(cornerRadius: 12).stroke(.separator, lineWidth: 1))
}
private var engineControlCard: some View {
VStack(alignment: .leading, spacing: 12) {
Text("Telemetry Coordinator Engine")
.font(.headline)
HStack(spacing: 16) {
Button(action: {
if store.isRunning {
store.stop()
} else {
store.start()
}
}) {
Label(store.isRunning ? "Pause Engine" : "Start Engine",
systemImage: store.isRunning ? "pause.fill" : "play.fill")
}
.buttonStyle(.borderedProminent)
.tint(store.isRunning ? .orange : .green)
Button(action: {
store.triggerManualSample()
}) {
Label("Sample Now", systemImage: "arrow.clockwise")
}
.buttonStyle(.bordered)
Spacer()
Picker("Polling Interval:", selection: Binding(
get: { store.sampleInterval },
set: { store.setSampleInterval($0) }
)) {
Text("0.5s").tag(0.5)
Text("1.0s").tag(1.0)
Text("2.0s").tag(2.0)
Text("5.0s").tag(5.0)
}
.pickerStyle(.segmented)
.frame(width: 220)
}
HStack {
Text("Last Sample Cycle: \(store.lastUpdateTimestamp.formatted(date: .omitted, time: .standard))")
.font(.caption)
.foregroundStyle(.secondary)
Spacer()
Text("Queue: com.i3omb.macmonitor.telemetry (QOS_UTILITY)")
.font(.caption)
.foregroundStyle(.secondary)
}
}
.padding(16)
.background(.background, in: RoundedRectangle(cornerRadius: 12))
.overlay(RoundedRectangle(cornerRadius: 12).stroke(.separator, lineWidth: 1))
}
private var specsCard: some View {
VStack(alignment: .leading, spacing: 12) {
Text("Host Architecture & Kernel")
.font(.headline)
Grid(alignment: .leading, horizontalSpacing: 24, verticalSpacing: 10) {
GridRow {
Text("Hardware Model:").foregroundStyle(.secondary)
Text(store.hostModel).bold()
Text("Physical Cores:").foregroundStyle(.secondary)
Text("\(store.physicalCpuCount)").bold()
}
GridRow {
Text("Darwin Kernel:").foregroundStyle(.secondary)
Text(store.kernelVersion).bold()
Text("Logical Cores:").foregroundStyle(.secondary)
Text("\(store.logicalCpuCount)").bold()
}
GridRow {
Text("Target Architecture:").foregroundStyle(.secondary)
Text("Intel x86_64").bold()
Text("Deployment Target:").foregroundStyle(.secondary)
Text("macOS 14.0 (Sonoma)").bold()
}
}
}
.padding(16)
.background(.background, in: RoundedRectangle(cornerRadius: 12))
.overlay(RoundedRectangle(cornerRadius: 12).stroke(.separator, lineWidth: 1))
}
private var providersCard: some View {
VStack(alignment: .leading, spacing: 12) {
HStack {
Text("Registered Telemetry Providers")
.font(.headline)
Spacer()
Text("\(store.registeredProviderCount) Active")
.font(.caption.bold())
.padding(.horizontal, 8)
.padding(.vertical, 4)
.background(Color.blue.opacity(0.15))
.foregroundStyle(.blue)
.clipShape(Capsule())
}
if store.latestSnapshot.isEmpty {
HStack(spacing: 12) {
Image(systemName: "info.circle")
.foregroundStyle(.secondary)
Text("Telemetry coordinator initialized. Awaiting provider metrics.")
.font(.subheadline)
.foregroundStyle(.secondary)
}
.padding(.vertical, 8)
} else {
ForEach(Array(store.latestSnapshot.keys.sorted()), id: \.self) { key in
HStack {
Image(systemName: "checkmark.circle.fill")
.foregroundStyle(.green)
Text(key)
.font(.system(.body, design: .monospaced))
Spacer()
Text("\(store.latestSnapshot[key]?.count ?? 0) metrics")
.font(.caption)
.foregroundStyle(.secondary)
}
.padding(.vertical, 4)
}
}
}
.padding(16)
.background(.background, in: RoundedRectangle(cornerRadius: 12))
.overlay(RoundedRectangle(cornerRadius: 12).stroke(.separator, lineWidth: 1))
}
}
+73
View File
@@ -0,0 +1,73 @@
import XCTest
@testable import MacMonitor
final class MMSMCTests: XCTestCase {
func testSP78TemperatureDecoding() {
// 0x36, 0x80 -> 54.5°C in sp78 (signed 8.8 fixed point: 0x36 = 54, 0x80 = 128 / 256 = 0.5)
let bytes: [UInt8] = [0x36, 0x80]
let temp = MMSMCParser.decodeSP78(bytes)
XCTAssertEqual(Double(temp), 54.5, accuracy: 0.001)
// Negative temperature: -10.25°C -> -10.25 * 256 = -2624 = 0xF5C0
let negBytes: [UInt8] = [0xF5, 0xC0]
let negTemp = MMSMCParser.decodeSP78(negBytes)
XCTAssertEqual(Double(negTemp), -10.25, accuracy: 0.001)
}
func testFPE2FanRPMDecoding() {
// 1800 RPM in fpe2 (unsigned 14.2 fixed point: 1800 * 4 = 7200 = 0x1C20)
let bytes: [UInt8] = [0x1C, 0x20]
let rpm = MMSMCParser.decodeFPE2(bytes)
XCTAssertEqual(Double(rpm), 1800.0, accuracy: 0.001)
// 2450.5 RPM: 2450.5 * 4 = 9802 = 0x264A
let bytesFrac: [UInt8] = [0x26, 0x4A]
let rpmFrac = MMSMCParser.decodeFPE2(bytesFrac)
XCTAssertEqual(Double(rpmFrac), 2450.5, accuracy: 0.001)
}
func testFLTDecoding() {
// 3.14159f in IEEE 754 float
var original: Float = 3.14159
let bytes = withUnsafeBytes(of: &original) { Array($0) }
let decoded = MMSMCParser.decodeFLT(bytes)
XCTAssertEqual(Double(decoded), 3.14159, accuracy: 0.0001)
}
func testIntegerDecoding() {
// UI8
let byteUI8: [UInt8] = [0x7F]
XCTAssertEqual(MMSMCParser.decodeUI8(byteUI8), 127)
// UI16 big endian (0x1234 = 4660)
let bytesUI16: [UInt8] = [0x12, 0x34]
XCTAssertEqual(MMSMCParser.decodeUI16(bytesUI16), 4660)
// UI32 big endian (0x12345678 = 305419896)
let bytesUI32: [UInt8] = [0x12, 0x34, 0x56, 0x78]
XCTAssertEqual(MMSMCParser.decodeUI32(bytesUI32), 305419896)
}
func testGenericDecoderWithType() {
let tempBytes: [UInt8] = [0x41, 0x00] // 65.0°C in sp78
let val = MMSMCParser.decodeValue(withDataType: "sp78", bytes: tempBytes, size: 2)
XCTAssertNotNil(val)
XCTAssertEqual(val?.doubleValue ?? 0, 65.0, accuracy: 0.001)
let flagBytes: [UInt8] = [0x01]
let flagVal = MMSMCParser.decodeValue(withDataType: "flag", bytes: flagBytes, size: 1)
XCTAssertNotNil(flagVal)
XCTAssertEqual(flagVal?.boolValue, true)
}
func testSMCClientSingletonAndKeyCount() {
let client = MMAppleSMCClient.shared
XCTAssertNotNil(client)
// On physical Intel Macs, client.isAvailable should be true and totalKeyCount > 0.
// In headless environments or VMs without AppleSMC, it gracefully degrades.
if client.isAvailable {
XCTAssertGreaterThan(client.totalKeyCount, 0)
}
}
}
+26
View File
@@ -0,0 +1,26 @@
import XCTest
@testable import MacMonitor
final class MacMonitorCoreTests: XCTestCase {
func testCoordinatorLifecycle() {
let coordinator = MMTelemetryCoordinator.shared
XCTAssertNotNil(coordinator)
coordinator.sampleInterval = 0.5
XCTAssertEqual(coordinator.sampleInterval, 0.5)
coordinator.start()
XCTAssertTrue(coordinator.isRunning)
coordinator.stop()
XCTAssertFalse(coordinator.isRunning)
}
func testTelemetryDomainConversion() {
let domainStr = MMTelemetryDomainToString(.cpu)
XCTAssertEqual(domainStr, "CPU")
let thermalStr = MMTelemetryDomainToString(.thermal)
XCTAssertEqual(thermalStr, "Thermal")
}
}
+63
View File
@@ -0,0 +1,63 @@
name: MacMonitor
options:
bundleIdPrefix: com.i3omb
deploymentTarget:
macOS: "14.0"
xcodeVersion: "16.0"
createIntermediateGroups: true
settings:
base:
ARCHS: "x86_64"
ONLY_ACTIVE_ARCH: NO
VALID_ARCHS: "x86_64"
MACOSX_DEPLOYMENT_TARGET: "14.0"
SWIFT_VERSION: "5.0"
CLANG_ENABLE_OBJC_ARC: YES
CLANG_ENABLE_MODULES: YES
GENERATE_INFOPLIST_FILE: YES
CODE_SIGNING_ALLOWED: NO
CODE_SIGN_IDENTITY: ""
GCC_C_LANGUAGE_STANDARD: "gnu17"
CLANG_CXX_LANGUAGE_STANDARD: "gnu++20"
targets:
MacMonitor:
type: application
platform: macOS
deploymentTarget: "14.0"
sources:
- path: Sources
excludes:
- "Bridging/MacMonitor-Bridging-Header.h"
settings:
base:
PRODUCT_BUNDLE_IDENTIFIER: "com.i3omb.MacMonitor"
INFOPLIST_KEY_CFBundleDisplayName: "MacMonitor"
INFOPLIST_KEY_NSPrincipalClass: "NSApplication"
CODE_SIGN_ENTITLEMENTS: "MacMonitor.entitlements"
SWIFT_OBJC_BRIDGING_HEADER: "Sources/Bridging/MacMonitor-Bridging-Header.h"
HEADER_SEARCH_PATHS:
- "$(SRCROOT)/Sources/Core"
- "$(SRCROOT)/Sources/Bridging"
- "$(SRCROOT)/Sources/Hardware/**"
dependencies:
- sdk: IOKit.framework
- sdk: CoreFoundation.framework
MacMonitorTests:
type: bundle.unit-test
platform: macOS
deploymentTarget: "14.0"
sources:
- path: Tests
settings:
base:
PRODUCT_BUNDLE_IDENTIFIER: "com.i3omb.MacMonitorTests"
SWIFT_OBJC_BRIDGING_HEADER: "Sources/Bridging/MacMonitor-Bridging-Header.h"
HEADER_SEARCH_PATHS:
- "$(SRCROOT)/Sources/Core"
- "$(SRCROOT)/Sources/Bridging"
- "$(SRCROOT)/Sources/Hardware/**"
dependencies:
- target: MacMonitor