1.5.0 - Now displaying all sensors on Apple Silicon.

This commit is contained in:
macmade
2021-09-23 20:09:29 +02:00
parent 7b77d4ca36
commit 81dd599094
26 changed files with 1816 additions and 15 deletions

BIN
Assets/menu-arm.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 332 KiB

BIN
Assets/sensors.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 970 KiB

View File

@@ -32,6 +32,20 @@
057F8B1B251D2A970048F1E1 /* Icon.icns in Resources */ = {isa = PBXBuildFile; fileRef = 057F8B1A251D2A970048F1E1 /* Icon.icns */; };
057F8B65251D2D5F0048F1E1 /* GitHubUpdates.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 057F8B5C251D2D510048F1E1 /* GitHubUpdates.framework */; };
057F8B67251D2D680048F1E1 /* GitHubUpdates.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 057F8B5C251D2D510048F1E1 /* GitHubUpdates.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
05A012C226FB9AEB00D3F6B6 /* SensorIcon.swift in Sources */ = {isa = PBXBuildFile; fileRef = 05A012B026FB9AEB00D3F6B6 /* SensorIcon.swift */; };
05A012C326FB9AEB00D3F6B6 /* SensorData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 05A012B126FB9AEB00D3F6B6 /* SensorData.swift */; };
05A012C426FB9AEB00D3F6B6 /* SensorsWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 05A012B226FB9AEB00D3F6B6 /* SensorsWindowController.swift */; };
05A012C626FB9AEB00D3F6B6 /* SensorValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 05A012B526FB9AEB00D3F6B6 /* SensorValue.swift */; };
05A012C826FB9AEB00D3F6B6 /* ArrayIsNotEmpty.swift in Sources */ = {isa = PBXBuildFile; fileRef = 05A012B726FB9AEB00D3F6B6 /* ArrayIsNotEmpty.swift */; };
05A012C926FB9AEB00D3F6B6 /* SensorItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 05A012B826FB9AEB00D3F6B6 /* SensorItem.swift */; };
05A012CA26FB9AEB00D3F6B6 /* ArrayIsEmpty.swift in Sources */ = {isa = PBXBuildFile; fileRef = 05A012BA26FB9AEB00D3F6B6 /* ArrayIsEmpty.swift */; };
05A012CB26FB9AEB00D3F6B6 /* SensorGraphView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 05A012BB26FB9AEB00D3F6B6 /* SensorGraphView.swift */; };
05A012CC26FB9AEB00D3F6B6 /* NSImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 05A012BC26FB9AEB00D3F6B6 /* NSImage.swift */; };
05A012CD26FB9AEB00D3F6B6 /* Synchronizable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 05A012BD26FB9AEB00D3F6B6 /* Synchronizable.swift */; };
05A012CE26FB9AEB00D3F6B6 /* Sensors.m in Sources */ = {isa = PBXBuildFile; fileRef = 05A012BE26FB9AEB00D3F6B6 /* Sensors.m */; };
05A012CF26FB9AEB00D3F6B6 /* Colors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 05A012BF26FB9AEB00D3F6B6 /* Colors.swift */; };
05A012D626FB9B8B00D3F6B6 /* SensorItem.xib in Resources */ = {isa = PBXBuildFile; fileRef = 05A012D226FB9B8B00D3F6B6 /* SensorItem.xib */; };
05A012D726FB9B8B00D3F6B6 /* SensorsWindowController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 05A012D426FB9B8B00D3F6B6 /* SensorsWindowController.xib */; };
05AA407A251CF3D200106CEA /* ApplicationDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 05AA4079251CF3D200106CEA /* ApplicationDelegate.swift */; };
05AA407F251CF3D600106CEA /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 05AA407D251CF3D600106CEA /* MainMenu.xib */; };
05AA408D251CFA0600106CEA /* ThermalLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 05AA408C251CFA0600106CEA /* ThermalLog.swift */; };
@@ -155,6 +169,22 @@
057F8B53251D2C840048F1E1 /* ccache.sh */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = ccache.sh; sourceTree = "<group>"; };
057F8B54251D2C840048F1E1 /* Debug - zld.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Debug - zld.xcconfig"; sourceTree = "<group>"; };
057F8B56251D2D510048F1E1 /* GitHubUpdates.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = GitHubUpdates.xcodeproj; path = Submodules/GitHubUpdates/GitHubUpdates.xcodeproj; sourceTree = "<group>"; };
05A012B026FB9AEB00D3F6B6 /* SensorIcon.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SensorIcon.swift; sourceTree = "<group>"; };
05A012B126FB9AEB00D3F6B6 /* SensorData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SensorData.swift; sourceTree = "<group>"; };
05A012B226FB9AEB00D3F6B6 /* SensorsWindowController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SensorsWindowController.swift; sourceTree = "<group>"; };
05A012B426FB9AEB00D3F6B6 /* Sensors.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Sensors.h; sourceTree = "<group>"; };
05A012B526FB9AEB00D3F6B6 /* SensorValue.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SensorValue.swift; sourceTree = "<group>"; };
05A012B726FB9AEB00D3F6B6 /* ArrayIsNotEmpty.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ArrayIsNotEmpty.swift; sourceTree = "<group>"; };
05A012B826FB9AEB00D3F6B6 /* SensorItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SensorItem.swift; sourceTree = "<group>"; };
05A012B926FB9AEB00D3F6B6 /* IOHID.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IOHID.h; sourceTree = "<group>"; };
05A012BA26FB9AEB00D3F6B6 /* ArrayIsEmpty.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ArrayIsEmpty.swift; sourceTree = "<group>"; };
05A012BB26FB9AEB00D3F6B6 /* SensorGraphView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SensorGraphView.swift; sourceTree = "<group>"; };
05A012BC26FB9AEB00D3F6B6 /* NSImage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NSImage.swift; sourceTree = "<group>"; };
05A012BD26FB9AEB00D3F6B6 /* Synchronizable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Synchronizable.swift; sourceTree = "<group>"; };
05A012BE26FB9AEB00D3F6B6 /* Sensors.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Sensors.m; sourceTree = "<group>"; };
05A012BF26FB9AEB00D3F6B6 /* Colors.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Colors.swift; sourceTree = "<group>"; };
05A012D326FB9B8B00D3F6B6 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/SensorItem.xib; sourceTree = "<group>"; };
05A012D526FB9B8B00D3F6B6 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/SensorsWindowController.xib; sourceTree = "<group>"; };
05AA4076251CF3D200106CEA /* Hot.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Hot.app; sourceTree = BUILT_PRODUCTS_DIR; };
05AA4079251CF3D200106CEA /* ApplicationDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApplicationDelegate.swift; sourceTree = "<group>"; };
05AA407E251CF3D600106CEA /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = "<group>"; };
@@ -333,6 +363,36 @@
name = Frameworks;
sourceTree = "<group>";
};
05A012AF26FB9AEB00D3F6B6 /* Sensors */ = {
isa = PBXGroup;
children = (
05A012BA26FB9AEB00D3F6B6 /* ArrayIsEmpty.swift */,
05A012B726FB9AEB00D3F6B6 /* ArrayIsNotEmpty.swift */,
05A012BF26FB9AEB00D3F6B6 /* Colors.swift */,
05A012B926FB9AEB00D3F6B6 /* IOHID.h */,
05A012B226FB9AEB00D3F6B6 /* SensorsWindowController.swift */,
05A012BC26FB9AEB00D3F6B6 /* NSImage.swift */,
05A012B126FB9AEB00D3F6B6 /* SensorData.swift */,
05A012BB26FB9AEB00D3F6B6 /* SensorGraphView.swift */,
05A012B026FB9AEB00D3F6B6 /* SensorIcon.swift */,
05A012B826FB9AEB00D3F6B6 /* SensorItem.swift */,
05A012B426FB9AEB00D3F6B6 /* Sensors.h */,
05A012BE26FB9AEB00D3F6B6 /* Sensors.m */,
05A012B526FB9AEB00D3F6B6 /* SensorValue.swift */,
05A012BD26FB9AEB00D3F6B6 /* Synchronizable.swift */,
);
path = Sensors;
sourceTree = "<group>";
};
05A012D126FB9B8B00D3F6B6 /* Sensors */ = {
isa = PBXGroup;
children = (
05A012D226FB9B8B00D3F6B6 /* SensorItem.xib */,
05A012D426FB9B8B00D3F6B6 /* SensorsWindowController.xib */,
);
path = Sensors;
sourceTree = "<group>";
};
05AA406D251CF3D200106CEA = {
isa = PBXGroup;
children = (
@@ -368,6 +428,7 @@
05AA4088251CF3F400106CEA /* Classes */ = {
isa = PBXGroup;
children = (
05A012AF26FB9AEB00D3F6B6 /* Sensors */,
057F8B0B251D273E0048F1E1 /* AboutWindowController.swift */,
05AA4079251CF3D200106CEA /* ApplicationDelegate.swift */,
05221E87267009A8007246FF /* SensorViewController.swift */,
@@ -394,6 +455,7 @@
05AA4089251CF3FC00106CEA /* UI */ = {
isa = PBXGroup;
children = (
05A012D126FB9B8B00D3F6B6 /* Sensors */,
057F8B13251D27650048F1E1 /* AboutWindowController.xib */,
057F8AFE251D22360048F1E1 /* Images */,
055A9321252778D00070F771 /* InfoViewController.xib */,
@@ -434,7 +496,7 @@
isa = PBXProject;
attributes = {
LastSwiftUpdateCheck = 1200;
LastUpgradeCheck = 1250;
LastUpgradeCheck = 1300;
TargetAttributes = {
05AA4075251CF3D200106CEA = {
CreatedOnToolsVersion = 12.0;
@@ -495,8 +557,10 @@
057F8B1B251D2A970048F1E1 /* Icon.icns in Resources */,
052FBCD5251F842C00C4322E /* CPUTemplate.pdf in Resources */,
05221E8B26700A57007246FF /* SensorViewController.xib in Resources */,
05A012D626FB9B8B00D3F6B6 /* SensorItem.xib in Resources */,
055A9323252778D00070F771 /* InfoViewController.xib in Resources */,
05B0DFA526714A830068FF9B /* UnknownTemplate.pdf in Resources */,
05A012D726FB9B8B00D3F6B6 /* SensorsWindowController.xib in Resources */,
057F8B15251D27650048F1E1 /* AboutWindowController.xib in Resources */,
052FBCA7251EE46700C4322E /* PreferencesWindowController.xib in Resources */,
05AA407F251CF3D600106CEA /* MainMenu.xib in Resources */,
@@ -536,19 +600,31 @@
files = (
055A932525277E9E0070F771 /* IntegerToString.swift in Sources */,
055A932725277F6E0070F771 /* TemperatureToString.swift in Sources */,
05A012CD26FB9AEB00D3F6B6 /* Synchronizable.swift in Sources */,
05221E8626700772007246FF /* DictionaryIsEmpty.swift in Sources */,
05A012C626FB9AEB00D3F6B6 /* SensorValue.swift in Sources */,
05A012C326FB9AEB00D3F6B6 /* SensorData.swift in Sources */,
052FBCA3251EE43B00C4322E /* PreferencesWindowController.swift in Sources */,
055A932B252780FD0070F771 /* GraphView.swift in Sources */,
05A012CC26FB9AEB00D3F6B6 /* NSImage.swift in Sources */,
05221E62266FD5BD007246FF /* AppleSilicon.m in Sources */,
05221E88267009A8007246FF /* SensorViewController.swift in Sources */,
05A012C426FB9AEB00D3F6B6 /* SensorsWindowController.swift in Sources */,
057F8B11251D274C0048F1E1 /* NSApplication+LaunchServices.m in Sources */,
05A012C926FB9AEB00D3F6B6 /* SensorItem.swift in Sources */,
057F8B0C251D273E0048F1E1 /* AboutWindowController.swift in Sources */,
05A012CA26FB9AEB00D3F6B6 /* ArrayIsEmpty.swift in Sources */,
055A9320252778C10070F771 /* InfoViewController.swift in Sources */,
05A012CB26FB9AEB00D3F6B6 /* SensorGraphView.swift in Sources */,
05A012CF26FB9AEB00D3F6B6 /* Colors.swift in Sources */,
05AA408D251CFA0600106CEA /* ThermalLog.swift in Sources */,
055A932925277F990070F771 /* PercentToString.swift in Sources */,
05AA407A251CF3D200106CEA /* ApplicationDelegate.swift in Sources */,
05BA0BAA26F342DA00AA7BFC /* PressureToString.swift in Sources */,
05A012C226FB9AEB00D3F6B6 /* SensorIcon.swift in Sources */,
05A012CE26FB9AEB00D3F6B6 /* Sensors.m in Sources */,
052FBC38251ECB8B00C4322E /* SMC.c in Sources */,
05A012C826FB9AEB00D3F6B6 /* ArrayIsNotEmpty.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -595,6 +671,22 @@
name = AboutWindowController.xib;
sourceTree = "<group>";
};
05A012D226FB9B8B00D3F6B6 /* SensorItem.xib */ = {
isa = PBXVariantGroup;
children = (
05A012D326FB9B8B00D3F6B6 /* Base */,
);
name = SensorItem.xib;
sourceTree = "<group>";
};
05A012D426FB9B8B00D3F6B6 /* SensorsWindowController.xib */ = {
isa = PBXVariantGroup;
children = (
05A012D526FB9B8B00D3F6B6 /* Base */,
);
name = SensorsWindowController.xib;
sourceTree = "<group>";
};
05AA407D251CF3D600106CEA /* MainMenu.xib */ = {
isa = PBXVariantGroup;
children = (

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1250"
LastUpgradeVersion = "1300"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"

View File

@@ -26,11 +26,12 @@ import Cocoa
import GitHubUpdates
@NSApplicationMain
class ApplicationDelegate: NSObject, NSApplicationDelegate
class ApplicationDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate
{
private var statusItem: NSStatusItem?
private var aboutWindowController: AboutWindowController?
private var preferencesWindowController: PreferencesWindowController?
private var sensorsWindowController: SensorsWindowController?
private var observations: [ NSKeyValueObservation ] = []
private var sensorViewControllers: [ SensorViewController ] = []
@@ -252,4 +253,37 @@ class ApplicationDelegate: NSObject, NSApplicationDelegate
self.sensorsMenu.addItem( item )
}
}
@IBAction public func viewAllSensors( _ sender: Any? )
{
if self.sensorsWindowController == nil
{
self.sensorsWindowController = SensorsWindowController()
}
guard let window = self.sensorsWindowController?.window else
{
NSSound.beep()
return
}
window.delegate = self
if window.isVisible == false
{
window.layoutIfNeeded()
window.center()
}
NSApp.activate( ignoringOtherApps: true )
window.makeKeyAndOrderFront( nil )
}
func windowWillClose( _ notification: Notification )
{
self.sensorsWindowController?.stop()
self.sensorsWindowController = nil
}
}

View File

@@ -25,3 +25,4 @@
#import "NSApplication+LaunchServices.h"
#import "SMC.h"
#import "AppleSilicon.h"
#import "Sensors.h"

View File

@@ -58,14 +58,6 @@ public class PreferencesWindowController: NSWindowController
}
}
@objc public dynamic var startAtLogin = NSApp.isLoginItemEnabled()
{
didSet
{
NSApp.setLoginItemEnabled( self.startAtLogin )
}
}
@objc public dynamic var hideStatusIcon = UserDefaults.standard.bool( forKey: "hideStatusIcon" )
{
didSet
@@ -74,6 +66,14 @@ public class PreferencesWindowController: NSWindowController
}
}
@objc public dynamic var startAtLogin = NSApp.isLoginItemEnabled()
{
didSet
{
NSApp.setLoginItemEnabled( self.startAtLogin )
}
}
@objc private dynamic var colorizeStatusItemTextLabel: String?
public override var windowNibName: NSNib.Name?

View File

@@ -0,0 +1,49 @@
/*******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2021 Jean-David Gadina - www.xs-labs.com
*
* 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 Cocoa
@objc( ArrayIsEmpty )
public class ArrayIsEmpty: ValueTransformer
{
public override class func transformedValueClass() -> AnyClass
{
NSNumber.self
}
public override class func allowsReverseTransformation() -> Bool
{
false
}
public override func transformedValue( _ value: Any? ) -> Any?
{
guard let array = value as? NSArray else
{
return NSNumber( booleanLiteral: true )
}
return NSNumber( booleanLiteral: array.count == 0 )
}
}

View File

@@ -0,0 +1,49 @@
/*******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2021 Jean-David Gadina - www.xs-labs.com
*
* 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 Cocoa
@objc( ArrayIsNotEmpty )
public class ArrayIsNotEmpty: ValueTransformer
{
public override class func transformedValueClass() -> AnyClass
{
NSNumber.self
}
public override class func allowsReverseTransformation() -> Bool
{
false
}
public override func transformedValue( _ value: Any? ) -> Any?
{
guard let array = value as? NSArray else
{
return NSNumber( booleanLiteral: false )
}
return NSNumber( booleanLiteral: array.count > 0 )
}
}

View File

@@ -0,0 +1,41 @@
/*******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2021 Jean-David Gadina - www.xs-labs.com
*
* 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 Cocoa
public class Colors
{
public class func color( for kind: SensorData.Kind ) -> NSColor
{
switch kind
{
case .thermal: return NSColor.systemOrange
case .voltage: return NSColor.systemPurple
case .current: return NSColor.systemTeal
}
}
private init()
{}
}

127
Hot/Classes/Sensors/IOHID.h Normal file
View File

@@ -0,0 +1,127 @@
/*******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2021 Jean-David Gadina - www.xs-labs.com
*
* 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 CoreFoundation;
#import <IOKit/hidsystem/IOHIDEventSystemClient.h>
#import <IOKit/hidsystem/IOHIDServiceClient.h>
#import <IOKit/hid/IOHIDKeys.h>
/* @see https://opensource.apple.com/source/IOHIDFamily/ */
typedef NS_ENUM( int64_t, IOHIDPage )
{
IOHIDPageAppleVendor = 0xFF00,
IOHIDPageAppleVendorKeyboard = 0xFF01,
IOHIDPageAppleVendorMouse = 0xFF02,
IOHIDPageAppleVendorAccelerometer = 0xFF03,
IOHIDPageAppleVendorAmbientLightSensor = 0xFF04,
IOHIDPageAppleVendorTemperatureSensor = 0xFF05,
IOHIDPageAppleVendorHeadset = 0xFF07,
IOHIDPageAppleVendorPowerSensor = 0xFF08,
IOHIDPageAppleVendorSmartCover = 0xFF09,
IOHIDPageAppleVendorPlatinum = 0xFF0A,
IOHIDPageAppleVendorLisa = 0xFF0B,
IOHIDPageAppleVendorMotion = 0xFF0C,
IOHIDPageAppleVendorBattery = 0xFF0D,
IOHIDPageAppleVendorIRRemote = 0xFF0E,
IOHIDPageAppleVendorDebug = 0xFF0F,
IOHIDPageAppleVendorFilteredEvent = 0xFF50,
IOHIDPageAppleVendorMultitouch = 0xFF60,
IOHIDPageAppleVendorDisplay = 0xFF92,
IOHIDPageAppleVendorTopCase = 0x00FF
};
typedef NS_ENUM( int64_t, IOHIDUsageAppleVendor )
{
IOHIDUsageAppleVendorTopCase = 0x01,
IOHIDUsageAppleVendorDisplay = 0x02,
IOHIDUsageAppleVendorAccelerometer = 0x03,
IOHIDUsageAppleVendorAmbientLightSensor = 0x04,
IOHIDUsageAppleVendorTemperatureSensor = 0x05,
IOHIDUsageAppleVendorKeyboard = 0x06,
IOHIDUsageAppleVendorHeadset = 0x07,
IOHIDUsageAppleVendorProximitySensor = 0x08,
IOHIDUsageAppleVendorGyro = 0x09,
IOHIDUsageAppleVendorCompass = 0x0A,
IOHIDUsageAppleVendorDeviceManagement = 0x0B,
IOHIDUsageAppleVendorTrackpad = 0x0C,
IOHIDUsageAppleVendorTopCaseReserved = 0x0D,
IOHIDUsageAppleVendorMotion = 0x0E,
IOHIDUsageAppleVendorKeyboardBacklight = 0x0F,
IOHIDUsageAppleVendorDeviceMotionLite = 0x10,
IOHIDUsageAppleVendorForce = 0x11,
IOHIDUsageAppleVendorBluetoothRadio = 0x12,
IOHIDUsageAppleVendorOrb = 0x13,
IOHIDUsageAppleVendorAccessoryBattery = 0x14
};
typedef NS_ENUM( int64_t, IOHIDUsageAppleVendorPowerSensor )
{
IOHIDUsageAppleVendorPowerSensorPower = 0x1,
IOHIDUsageAppleVendorPowerSensorCurrent = 0x2,
IOHIDUsageAppleVendorPowerSensorVoltage = 0x3
};
typedef NS_ENUM( int64_t, IOHIDEvent )
{
IOHIDEventTypeNULL = 0x00,
IOHIDEventTypeVendorDefined = 0x01,
IOHIDEventTypeButton = 0x02,
IOHIDEventTypeKeyboard = 0x03,
IOHIDEventTypeTranslation = 0x04,
IOHIDEventTypeRotation = 0x05,
IOHIDEventTypeScroll = 0x06,
IOHIDEventTypeScale = 0x07,
IOHIDEventTypeZoom = 0x08,
IOHIDEventTypeVelocity = 0x09,
IOHIDEventTypeOrientation = 0x0A,
IOHIDEventTypeDigitizer = 0x0B,
IOHIDEventTypeAmbientLightSensor = 0x0C,
IOHIDEventTypeAccelerometer = 0x0D,
IOHIDEventTypeProximity = 0x0E,
IOHIDEventTypeTemperature = 0x0F,
IOHIDEventTypeNavigationSwipe = 0x10,
IOHIDEventTypeSwipe = IOHIDEventTypeNavigationSwipe,
IOHIDEventTypeMouse = 0x11,
IOHIDEventTypeProgress = 0x12,
IOHIDEventTypeCount = 0x13,
IOHIDEventTypeGyro = 0x14,
IOHIDEventTypeCompass = 0x15,
IOHIDEventTypeZoomToggle = 0x16,
IOHIDEventTypeDockSwipe = 0x17,
IOHIDEventTypeSymbolicHotKey = 0x18,
IOHIDEventTypePower = 0x19,
IOHIDEventTypeBrightness = 0x1A,
IOHIDEventTypeFluidTouchGesture = 0x1B,
IOHIDEventTypeBoundaryScroll = 0x1C,
IOHIDEventTypeReset = 0x1D
};
extern IOHIDEventSystemClientRef IOHIDEventSystemClientCreate( CFAllocatorRef );
extern void IOHIDEventSystemClientSetMatching( IOHIDEventSystemClientRef, CFDictionaryRef );
extern CFTypeRef IOHIDServiceClientCopyEvent( IOHIDServiceClientRef, IOHIDEvent, int64_t, int64_t );
extern CFDictionaryRef IOHIDServiceClientCopyProperties( IOHIDServiceClientRef, CFArrayRef );
extern double IOHIDEventGetFloatValue( CFTypeRef, int64_t );

View File

@@ -0,0 +1,46 @@
/*******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2021 Jean-David Gadina - www.xs-labs.com
*
* 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 Cocoa
extension NSImage
{
public func tinted( with color: NSColor ) -> NSImage
{
guard let copy = self.copy() as? NSImage else
{
return self
}
copy.isTemplate = false
let rect = NSRect( origin: NSZeroPoint, size: copy.size )
copy.lockFocus()
color.set()
rect.fill( using: .sourceAtop )
copy.unlockFocus()
return copy;
}
}

View File

@@ -0,0 +1,137 @@
/*******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2021 Jean-David Gadina - www.xs-labs.com
*
* 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 Foundation
@objc public class SensorData: NSObject, Synchronizable
{
@objc( SensorDataKind )
public enum Kind: Int, CustomStringConvertible
{
case thermal
case voltage
case current
public var description: String
{
switch self
{
case .thermal: return "thermal"
case .voltage: return "voltage"
case .current: return "current"
}
}
}
@objc public private( set ) dynamic var kind: Kind
@objc public private( set ) dynamic var name: String
@objc public private( set ) dynamic var properties: [ String : Any ]?
private var data: [ Double ] = []
@objc public dynamic var values: [ NSNumber ]
{
return self.synchronized
{
return self.data.map { NSNumber( floatLiteral: $0 ) }
}
}
@objc public dynamic var min: NSNumber?
{
return self.synchronized
{
guard let max = self.data.min() else
{
return nil
}
return NSNumber( floatLiteral: max )
}
}
@objc public dynamic var max: NSNumber?
{
return self.synchronized
{
guard let min = self.data.max() else
{
return nil
}
return NSNumber( floatLiteral: min )
}
}
@objc public dynamic var last: NSNumber?
{
return self.synchronized
{
guard let last = self.data.last else
{
return nil
}
return NSNumber( floatLiteral: last )
}
}
@objc public init( kind: Kind, name: String, properties: [ String : Any ]? )
{
self.kind = kind
self.name = name
self.properties = properties
}
@objc( addValue: )
public func add( value: Double )
{
self.synchronized
{
var data = self.data
data.append( value )
self.willChangeValue( for: \.values )
self.willChangeValue( for: \.min )
self.willChangeValue( for: \.max )
self.willChangeValue( for: \.last )
self.data = data.suffix( 50 )
self.didChangeValue( for: \.values )
self.didChangeValue( for: \.min )
self.didChangeValue( for: \.max )
self.didChangeValue( for: \.last )
}
}
public override var description: String
{
let min = String( format: "%.2f", self.min?.doubleValue ?? 0 )
let max = String( format: "%.2f", self.max?.doubleValue ?? 0 )
return "\( super.description ): \( self.name ) (\( self.kind ), min: \( min ), max: \( max ))"
}
}

View File

@@ -0,0 +1,248 @@
/*******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2021 Jean-David Gadina - www.xs-labs.com
*
* 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 Cocoa
public class SensorGraphView: NSView
{
@objc public enum GraphStyle: Int
{
case gradient
case line
case fill
case bars
}
@objc public dynamic var sensor: SensorData?
private var graphStyleObserver: NSKeyValueObservation?
public override init( frame: NSRect )
{
super.init( frame: frame )
UserDefaults.standard.addObserver( self, forKeyPath: "graphStyle", options: .new, context: nil )
}
required init?( coder: NSCoder )
{
super.init( coder: coder )
UserDefaults.standard.addObserver( self, forKeyPath: "graphStyle", options: .new, context: nil )
}
deinit
{
UserDefaults.standard.removeObserver( self, forKeyPath: "graphStyle" )
}
public override func observeValue( forKeyPath keyPath: String?, of object: Any?, change: [ NSKeyValueChangeKey : Any ]?, context: UnsafeMutableRawPointer? )
{
if let keyPath = keyPath, keyPath == "graphStyle", let object = object as? UserDefaults, object == UserDefaults.standard
{
self.needsDisplay = true
}
else
{
super.observeValue( forKeyPath: keyPath, of: object, change: change, context: context )
}
}
private var graphStyle: GraphStyle
{
GraphStyle( rawValue: UserDefaults.standard.integer( forKey: "graphStyle" ) ) ?? .gradient
}
public override func draw( _ rect: NSRect )
{
self.drawBorder( in: rect )
self.drawBackground( in: rect )
guard let sensor = self.sensor, sensor.values.count >= 2 else
{
return
}
let values = sensor.values.map { CGFloat( $0.doubleValue ) }
var min: CGFloat = values.min() ?? 0
var max: CGFloat = values.max() ?? 0
if sensor.kind == .thermal && min >= 0 && max <= 100
{
min = 0
max = 100
}
if self.graphStyle == .bars
{
self.drawBars( in: rect,
kind: sensor.kind,
values: values,
min: min,
max: max,
color: Colors.color( for: sensor.kind )
)
}
else
{
self.drawGraph( in: rect.insetBy( dx: 10, dy: 10 ),
kind: sensor.kind,
values: values,
min: min,
max: max,
color: Colors.color( for: sensor.kind )
)
}
}
private func drawBorder( in rect: NSRect )
{
if self.graphStyle == .bars
{
return
}
let border = NSBezierPath( roundedRect: rect.insetBy( dx: 1, dy: 1 ), xRadius: 10, yRadius: 10 )
border.lineWidth = 1
NSColor.gray.setStroke()
border.stroke()
}
private func drawBackground( in rect: NSRect )
{
if self.graphStyle == .bars
{
let c = 15
let h = rect.size.height / CGFloat( c )
let n = rect.size.width / h
for i in 0 ..< Int( n )
{
for j in 0 ..< c
{
let square = NSRect( x: rect.origin.x + h * CGFloat( i ), y: rect.origin.y + h * CGFloat( j ), width: h, height: h ).insetBy( dx: 1, dy: 1 )
NSColor.gray.withAlphaComponent( 0.2 ).setFill()
square.fill()
}
}
}
else
{
let border = NSBezierPath( roundedRect: rect.insetBy( dx: 1, dy: 1 ), xRadius: 10, yRadius: 10 )
NSColor.gray.withAlphaComponent( 0.2 ).setFill()
border.fill()
}
}
private func drawGraph( in rect: NSRect, kind: SensorData.Kind, values: [ CGFloat ], min: CGFloat, max: CGFloat, color: NSColor )
{
let p1 = NSBezierPath()
let p2 = NSBezierPath()
p1.lineWidth = 1
p2.lineWidth = 0
if min == max && kind != .thermal
{
p1.move( to: NSPoint( x: rect.origin.x, y: rect.origin.y + rect.size.height / 2 ) )
p1.line( to: NSPoint( x: rect.origin.x + rect.size.width, y: rect.origin.y + rect.size.height / 2 ) )
p2.move( to: NSPoint( x: rect.origin.x, y: rect.origin.y + rect.size.height / 2 ) )
p2.line( to: NSPoint( x: rect.origin.x + rect.size.width, y: rect.origin.y + rect.size.height / 2 ) )
}
else
{
for i in 0 ..< values.count
{
let value = values[ i ]
let dx = rect.size.width / CGFloat( values.count - 1 )
let x = rect.origin.x + CGFloat( i ) * dx
let v = ( value - min ) / ( max - min )
let y = rect.origin.y + v * rect.size.height
if i == 0
{
p1.move( to: NSPoint( x: x, y: y ) )
p2.move( to: NSPoint( x: x, y: y ) )
}
else
{
p1.line( to: NSPoint( x: x, y: y ) )
p2.line( to: NSPoint( x: x, y: y ) )
}
}
}
p2.line( to: NSPoint( x: rect.origin.x + rect.size.width, y: rect.origin.y - 3 ) )
p2.line( to: NSPoint( x: rect.origin.x, y: rect.origin.y - 3 ) )
p2.close()
if self.graphStyle == .gradient
{
let gradient = NSGradient( colors: [ color.withAlphaComponent( 0.25 ), color.withAlphaComponent( 0 ) ] )
gradient?.draw( in: p2, angle: -90 )
}
else if self.graphStyle == .fill
{
color.setFill()
p2.fill()
}
color.setStroke()
p1.stroke()
}
private func drawBars( in rect: NSRect, kind: SensorData.Kind, values: [ CGFloat ], min: CGFloat, max: CGFloat, color: NSColor )
{
let c = 15
let h = rect.size.height / CGFloat( c )
let n = rect.size.width / h
let values = Array( values.suffix( Int( n ) ) )
for i in 0 ..< Int( n )
{
if i >= values.count
{
return
}
let v = values[ i ]
let p = ( v - min ) / ( max - min )
for j in 0 ..< c
{
if CGFloat( j ) / CGFloat( c ) > p
{
break
}
let square = NSRect( x: rect.origin.x + h * CGFloat( i ), y: rect.origin.y + h * CGFloat( j ), width: h, height: h ).insetBy( dx: 1, dy: 1 )
color.setFill()
square.fill()
}
}
}
}

View File

@@ -0,0 +1,54 @@
/*******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2021 Jean-David Gadina - www.xs-labs.com
*
* 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 Cocoa
@objc( SensorIcon )
public class SensorIcon: ValueTransformer
{
public override class func transformedValueClass() -> AnyClass
{
NSImage.self
}
public override class func allowsReverseTransformation() -> Bool
{
false
}
public override func transformedValue( _ value: Any? ) -> Any?
{
guard let data = value as? SensorData else
{
return nil
}
switch data.kind
{
case .thermal: return NSImage( named: "ThermalTemplate" )?.tinted( with: Colors.color( for: data.kind ) )
case .voltage: return NSImage( named: "VoltageTemplate" )?.tinted( with: Colors.color( for: data.kind ) )
case .current: return NSImage( named: "CurrentTemplate" )?.tinted( with: Colors.color( for: data.kind ) )
}
}
}

View File

@@ -0,0 +1,54 @@
/*******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2021 Jean-David Gadina - www.xs-labs.com
*
* 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 Cocoa
public class SensorItem: NSCollectionViewItem
{
@objc public dynamic var sensor: SensorData?
{
didSet
{
self.graph.sensor = self.sensor
}
}
private var graph = SensorGraphView()
@IBOutlet private var graphContainer: NSView!
public override func viewDidLoad()
{
super.viewDidLoad()
self.graph.translatesAutoresizingMaskIntoConstraints = false
self.graph.frame = self.graphContainer.bounds
self.graphContainer.addSubview( self.graph )
self.graphContainer.addConstraint( NSLayoutConstraint( item: self.graph, attribute: .width, relatedBy: .equal, toItem: self.graphContainer, attribute: .width, multiplier: 1, constant: 0 ) )
self.graphContainer.addConstraint( NSLayoutConstraint( item: self.graph, attribute: .height, relatedBy: .equal, toItem: self.graphContainer, attribute: .height, multiplier: 1, constant: 0 ) )
self.graphContainer.addConstraint( NSLayoutConstraint( item: self.graph, attribute: .centerX, relatedBy: .equal, toItem: self.graphContainer, attribute: .centerX, multiplier: 1, constant: 0 ) )
self.graphContainer.addConstraint( NSLayoutConstraint( item: self.graph, attribute: .centerY, relatedBy: .equal, toItem: self.graphContainer, attribute: .centerY, multiplier: 1, constant: 0 ) )
}
}

View File

@@ -0,0 +1,49 @@
/*******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2021 Jean-David Gadina - www.xs-labs.com
*
* 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 Cocoa
@objc( SensorValue )
public class SensorValue: ValueTransformer
{
public override class func transformedValueClass() -> AnyClass
{
NSString.self
}
public override class func allowsReverseTransformation() -> Bool
{
false
}
public override func transformedValue( _ value: Any? ) -> Any?
{
guard let n = value as? NSNumber else
{
return "--"
}
return String( format: "%.02f", n.doubleValue )
}
}

View File

@@ -0,0 +1,39 @@
/*******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2021 Jean-David Gadina - www.xs-labs.com
*
* 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 Foundation;
NS_ASSUME_NONNULL_BEGIN
@class SensorData;
@interface Sensors: NSObject
@property( atomic, readwrite, strong ) NSArray< SensorData * > * data;
- ( void )stop: ( void ( ^ _Nullable )( void ) )completion;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,173 @@
/*******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2021 Jean-David Gadina - www.xs-labs.com
*
* 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 "Sensors.h"
#import "Hot-Swift.h"
#import "IOHID.h"
NS_ASSUME_NONNULL_BEGIN
@interface Sensors()
@property( nonatomic, readwrite, strong ) NSMutableDictionary< NSString *, SensorData * > * sensors;
@property( atomic, readwrite, strong, nullable ) void ( ^ completion )( void );
@property( atomic, readwrite, assign ) BOOL shouldRun;
- ( void )run;
- ( void )readSensors: ( NSTimer * )timer;
- ( void )readSensors: ( SensorDataKind )kind page: ( IOHIDPage )page usage: ( NSInteger )usage event: ( IOHIDEvent )eventType client: ( IOHIDEventSystemClientRef )client;
- ( void )addSensorData: ( double )value name: ( NSString * )name kind: ( SensorDataKind )kind properties: ( nullable NSDictionary * )properties;
@end
NS_ASSUME_NONNULL_END
@implementation Sensors
- ( instancetype )init
{
if( ( self = [ super init ] ) )
{
self.sensors = [ NSMutableDictionary new ];
self.data = [ NSArray new ];
self.shouldRun = YES;
dispatch_async
(
dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0 ),
^( void )
{
[ self run ];
}
);
}
return self;
}
- ( void )stop: ( void ( ^ _Nullable )( void ) )completion
{
if( self.shouldRun == false )
{
return;
}
self.completion = completion;
self.shouldRun = false;
}
- ( void )run
{
IOHIDEventSystemClientRef client = IOHIDEventSystemClientCreate( kCFAllocatorDefault );
NSDictionary * info = @{ @"Client" : [ NSValue valueWithPointer: client ] };
NSTimer * timer = [ NSTimer timerWithTimeInterval: 1 target: self selector: @selector( readSensors: ) userInfo: info repeats: YES ];
[ self readSensors: timer ];
[ [ NSRunLoop currentRunLoop ] addTimer: timer forMode: NSDefaultRunLoopMode ];
while( self.shouldRun )
{
[ [ NSRunLoop currentRunLoop ] runUntilDate: [ NSDate dateWithTimeIntervalSinceNow: 5 ] ];
}
void ( ^ completion )( void ) = self.completion;
self.completion = nil;
if( completion != nil )
{
completion();
}
}
- ( void )readSensors: ( NSTimer * )timer
{
IOHIDEventSystemClientRef client = ( ( NSValue * )( timer.userInfo[ @"Client" ] ) ).pointerValue;
if( client != nil )
{
[ self readSensors: SensorDataKindThermal page: IOHIDPageAppleVendor usage: IOHIDUsageAppleVendorTemperatureSensor event: IOHIDEventTypeTemperature client: client ];
[ self readSensors: SensorDataKindVoltage page: IOHIDPageAppleVendorPowerSensor usage: IOHIDUsageAppleVendorPowerSensorVoltage event: IOHIDEventTypePower client: client ];
[ self readSensors: SensorDataKindCurrent page: IOHIDPageAppleVendorPowerSensor usage: IOHIDUsageAppleVendorPowerSensorCurrent event: IOHIDEventTypePower client: client ];
dispatch_async
(
dispatch_get_main_queue(),
^( void )
{
self.data = self.sensors.allValues;
}
);
}
}
- ( void )readSensors: ( SensorDataKind )kind page: ( IOHIDPage )page usage: ( NSInteger )usage event: ( IOHIDEvent )eventType client: ( IOHIDEventSystemClientRef )client
{
NSDictionary * filter =
@{
@"PrimaryUsagePage" : [ NSNumber numberWithLongLong: page ],
@"PrimaryUsage" : [ NSNumber numberWithLongLong: usage ]
};
IOHIDEventSystemClientSetMatching( client, ( __bridge CFDictionaryRef )filter );
{
NSArray * services = CFBridgingRelease( IOHIDEventSystemClientCopyServices( client ) );
for( id o in services )
{
IOHIDServiceClientRef service = ( __bridge IOHIDServiceClientRef )o;
CFTypeRef event = IOHIDServiceClientCopyEvent( service, eventType, 0, 0 );
NSString * name = CFBridgingRelease( IOHIDServiceClientCopyProperty( service, CFSTR( "Product" ) ) );
NSArray * keys = @[ @"IOClass", @"IOProviderClass", @"IONameMatch", @"Product", @"DeviceUsagePairs" ];
NSDictionary * properties = CFBridgingRelease( IOHIDServiceClientCopyProperties( service, ( __bridge CFArrayRef )keys ) );
if( name != nil && event != nil )
{
[ self addSensorData: IOHIDEventGetFloatValue( event, eventType << 16 ) name: name kind: kind properties: properties ];
}
if( event != nil )
{
CFRelease( event );
}
}
}
}
- ( void )addSensorData: ( double )value name: ( NSString * )name kind: ( SensorDataKind )kind properties: ( nullable NSDictionary * )properties
{
NSString * key = [ NSString stringWithFormat: @"%llu.%@", ( uint64_t )kind, name ];
SensorData * data = [ self.sensors objectForKey: key ];
if( data == nil )
{
data = [ [ SensorData alloc ] initWithKind: kind name: name properties: properties ];
[ self.sensors setObject: data forKey: key ];
}
[ data addValue: value ];
}
@end

View File

@@ -0,0 +1,146 @@
/*******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2021 Jean-David Gadina - www.xs-labs.com
*
* 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 Cocoa
public class SensorsWindowController: NSWindowController
{
@objc private dynamic var sensors: Sensors? = Sensors()
@objc private dynamic var showTemperature = true
{
didSet { self.updateFilters() }
}
@objc private dynamic var showVoltage = true
{
didSet { self.updateFilters() }
}
@objc private dynamic var showCurrent = true
{
didSet { self.updateFilters() }
}
@objc private dynamic var searchText: String?
{
didSet { self.updateFilters() }
}
@objc private dynamic var graphStyle = UserDefaults.standard.integer( forKey: "graphStyle" )
{
didSet
{
UserDefaults.standard.set( self.graphStyle, forKey: "graphStyle" )
}
}
@IBOutlet private var collectionView: NSCollectionView!
@IBOutlet private var arrayController: NSArrayController!
public override var windowNibName: NSNib.Name?
{
"SensorsWindowController"
}
public override func windowDidLoad()
{
super.windowDidLoad()
self.arrayController.sortDescriptors = [ NSSortDescriptor( key: "name", ascending: true, selector: #selector( NSString.localizedCaseInsensitiveCompare( _: ) ) ) ]
}
public func stop()
{
self.sensors?.stop
{
DispatchQueue.main.async
{
self.sensors = nil
}
}
}
private func updateFilters()
{
var predicates = [ NSPredicate ]()
if self.showTemperature == false
{
predicates.append( NSPredicate { o, i in ( o as? SensorData )?.kind != .thermal } )
}
if self.showVoltage == false
{
predicates.append( NSPredicate { o, i in ( o as? SensorData )?.kind != .voltage } )
}
if self.showCurrent == false
{
predicates.append( NSPredicate { o, i in ( o as? SensorData )?.kind != .current } )
}
if let search = self.searchText, search.count > 0
{
predicates.append( NSPredicate( format: "name contains[c] %@", search ) )
}
if predicates.count > 0
{
self.arrayController.filterPredicate = NSCompoundPredicate( andPredicateWithSubpredicates: predicates )
}
else
{
self.arrayController.filterPredicate = nil
}
}
}
extension SensorsWindowController: NSCollectionViewDataSource
{
public func numberOfSections( in collectionView: NSCollectionView ) -> Int
{
1
}
public func collectionView( _ collectionView: NSCollectionView, numberOfItemsInSection section: Int ) -> Int
{
( self.arrayController.arrangedObjects as? [ SensorData ] )?.count ?? 0
}
public func collectionView( _ itemForRepresentedObjectAtcollectionView: NSCollectionView, itemForRepresentedObjectAt indexPath: IndexPath ) -> NSCollectionViewItem
{
let item = collectionView.makeItem( withIdentifier: NSUserInterfaceItemIdentifier( rawValue: "SensorItem" ), for: indexPath )
if let item = item as? SensorItem, let sensors = self.arrayController.arrangedObjects as? [ SensorData ]
{
if sensors.count > indexPath.item
{
item.sensor = sensors[ indexPath.item ]
}
}
return item
}
}

View File

@@ -0,0 +1,57 @@
/*******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2021 Jean-David Gadina - www.xs-labs.com
*
* 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 Foundation
public protocol Synchronizable
{
static func synchronized< T >( closure: () -> T ) -> T
func synchronized< T >( closure: () -> T ) -> T
}
public extension Synchronizable
{
static func synchronized< T >( closure: () -> T ) -> T
{
objc_sync_enter( self )
let r = closure()
objc_sync_exit( self )
return r
}
func synchronized< T >( closure: () -> T ) -> T
{
objc_sync_enter( self )
let r = closure()
objc_sync_exit( self )
return r
}
}

View File

@@ -17,7 +17,7 @@
<key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key>
<string>1.4.1</string>
<string>1.5.0</string>
<key>CFBundleVersion</key>
<string>75</string>
<key>LSApplicationCategoryType</key>

View File

@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="18122" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="19162" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="18122"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="19162"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="NSApplication">
@@ -720,6 +720,17 @@
</binding>
</connections>
</menuItem>
<menuItem title="View all Sensors..." id="0oq-7C-y22">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="viewAllSensors:" target="Voe-Tx-rLC" id="K7Q-o7-xbP"/>
<binding destination="Voe-Tx-rLC" name="hidden" keyPath="self.infoViewController.isARM" id="y1k-9f-cAb">
<dictionary key="options">
<string key="NSValueTransformerName">NSNegateBoolean</string>
</dictionary>
</binding>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="Nbp-cG-gpV"/>
<menuItem title="Preferences..." keyEquivalent="," id="JFH-mp-6PU">
<connections>

View File

@@ -0,0 +1,142 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="18122" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="18122"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner"/>
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<customView id="c22-O7-iKe">
<rect key="frame" x="0.0" y="0.0" width="245" height="120"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<subviews>
<imageView horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="Zhs-tE-zrk">
<rect key="frame" x="0.0" y="100" width="20" height="20"/>
<constraints>
<constraint firstAttribute="height" constant="20" id="g2z-r1-gYP"/>
<constraint firstAttribute="width" constant="20" id="kP1-0x-y1M"/>
</constraints>
<imageCell key="cell" refusesFirstResponder="YES" alignment="left" imageScaling="proportionallyUpOrDown" image="NSApplicationIcon" id="YpG-g1-XTw"/>
<connections>
<binding destination="ek2-AR-oS5" name="value" keyPath="self.sensor" id="EbR-ou-eai">
<dictionary key="options">
<string key="NSValueTransformerName">SensorIcon</string>
</dictionary>
</binding>
</connections>
</imageView>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="Vx7-2w-JY2">
<rect key="frame" x="26" y="105" width="28" height="11"/>
<textFieldCell key="cell" controlSize="mini" lineBreakMode="truncatingTail" title="Label" id="4QE-tU-2iF">
<font key="font" metaFont="miniSystem"/>
<color key="textColor" name="secondaryLabelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<connections>
<binding destination="ek2-AR-oS5" name="toolTip" keyPath="self.sensor.name" id="sk7-rh-Bgb"/>
<binding destination="ek2-AR-oS5" name="value" keyPath="self.sensor.name" id="COB-ax-oMZ"/>
</connections>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="qyW-PJ-lOk">
<rect key="frame" x="219" y="105" width="28" height="11"/>
<textFieldCell key="cell" controlSize="mini" lineBreakMode="truncatingTail" title="Label" id="05S-DK-Pm8">
<font key="font" metaFont="miniSystem"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<connections>
<binding destination="ek2-AR-oS5" name="toolTip" keyPath="self.sensor.name" id="RUP-1u-HBl"/>
<binding destination="ek2-AR-oS5" name="value" keyPath="self.sensor.last" id="671-ZR-hc1">
<dictionary key="options">
<string key="NSValueTransformerName">SensorValue</string>
</dictionary>
</binding>
</connections>
</textField>
<customView translatesAutoresizingMaskIntoConstraints="NO" id="XSC-DH-60t">
<rect key="frame" x="0.0" y="19" width="245" height="73"/>
</customView>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="KB3-h4-RKJ">
<rect key="frame" x="-2" y="0.0" width="23" height="11"/>
<textFieldCell key="cell" controlSize="mini" lineBreakMode="clipping" title="Min:" id="P1F-RJ-VTt">
<font key="font" metaFont="miniSystem"/>
<color key="textColor" name="secondaryLabelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="WDq-1D-eKf">
<rect key="frame" x="25" y="0.0" width="28" height="11"/>
<textFieldCell key="cell" controlSize="mini" lineBreakMode="clipping" title="Label" id="pVf-bC-aUU">
<font key="font" metaFont="miniSystem"/>
<color key="textColor" name="secondaryLabelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<connections>
<binding destination="ek2-AR-oS5" name="value" keyPath="self.sensor.min" id="Hz7-WE-4XS">
<dictionary key="options">
<string key="NSValueTransformerName">SensorValue</string>
</dictionary>
</binding>
</connections>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="PeT-bn-qut">
<rect key="frame" x="190" y="0.0" width="25" height="11"/>
<textFieldCell key="cell" controlSize="mini" lineBreakMode="clipping" title="Max:" id="piP-vw-2QX">
<font key="font" metaFont="miniSystem"/>
<color key="textColor" name="secondaryLabelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="zT4-AQ-MeS">
<rect key="frame" x="219" y="0.0" width="28" height="11"/>
<textFieldCell key="cell" controlSize="mini" lineBreakMode="clipping" title="Label" id="0uv-Pe-gZH">
<font key="font" metaFont="miniSystem"/>
<color key="textColor" name="secondaryLabelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<connections>
<binding destination="ek2-AR-oS5" name="value" keyPath="self.sensor.max" id="q5h-49-6GI">
<dictionary key="options">
<string key="NSValueTransformerName">SensorValue</string>
</dictionary>
</binding>
</connections>
</textField>
</subviews>
<constraints>
<constraint firstItem="qyW-PJ-lOk" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="Vx7-2w-JY2" secondAttribute="trailing" constant="8" symbolic="YES" id="37w-zR-zSG"/>
<constraint firstAttribute="bottom" secondItem="zT4-AQ-MeS" secondAttribute="bottom" id="6Eb-Jb-ktv"/>
<constraint firstItem="zT4-AQ-MeS" firstAttribute="leading" secondItem="PeT-bn-qut" secondAttribute="trailing" constant="8" symbolic="YES" id="6Iv-Zk-l6p"/>
<constraint firstItem="Vx7-2w-JY2" firstAttribute="centerY" secondItem="Zhs-tE-zrk" secondAttribute="centerY" id="E6p-Sp-hhE"/>
<constraint firstItem="Zhs-tE-zrk" firstAttribute="top" secondItem="c22-O7-iKe" secondAttribute="top" id="F4w-Ta-FMp"/>
<constraint firstItem="zT4-AQ-MeS" firstAttribute="centerY" secondItem="PeT-bn-qut" secondAttribute="centerY" id="GLP-jq-o9j"/>
<constraint firstItem="Zhs-tE-zrk" firstAttribute="leading" secondItem="c22-O7-iKe" secondAttribute="leading" id="H4R-eE-jNf"/>
<constraint firstItem="KB3-h4-RKJ" firstAttribute="leading" secondItem="c22-O7-iKe" secondAttribute="leading" id="LlD-cK-jrf"/>
<constraint firstItem="XSC-DH-60t" firstAttribute="top" secondItem="Zhs-tE-zrk" secondAttribute="bottom" constant="8" symbolic="YES" id="RBs-v9-fzB"/>
<constraint firstItem="XSC-DH-60t" firstAttribute="leading" secondItem="c22-O7-iKe" secondAttribute="leading" id="Y8P-AG-Uex"/>
<constraint firstItem="qyW-PJ-lOk" firstAttribute="centerY" secondItem="Vx7-2w-JY2" secondAttribute="centerY" id="ZfP-Ip-Qp3"/>
<constraint firstItem="WDq-1D-eKf" firstAttribute="leading" secondItem="KB3-h4-RKJ" secondAttribute="trailing" constant="8" symbolic="YES" id="bnr-IM-2mn"/>
<constraint firstItem="PeT-bn-qut" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="WDq-1D-eKf" secondAttribute="trailing" constant="8" symbolic="YES" id="dRS-W2-HF3"/>
<constraint firstAttribute="bottom" secondItem="KB3-h4-RKJ" secondAttribute="bottom" id="gjk-x9-oBq"/>
<constraint firstAttribute="trailing" secondItem="zT4-AQ-MeS" secondAttribute="trailing" id="ivM-u5-F81"/>
<constraint firstItem="WDq-1D-eKf" firstAttribute="centerY" secondItem="KB3-h4-RKJ" secondAttribute="centerY" id="k8I-Sa-B0p"/>
<constraint firstAttribute="trailing" secondItem="XSC-DH-60t" secondAttribute="trailing" id="ma2-2p-gXZ"/>
<constraint firstItem="Vx7-2w-JY2" firstAttribute="leading" secondItem="Zhs-tE-zrk" secondAttribute="trailing" constant="8" symbolic="YES" id="pSn-l6-1WW"/>
<constraint firstItem="KB3-h4-RKJ" firstAttribute="top" secondItem="XSC-DH-60t" secondAttribute="bottom" constant="8" symbolic="YES" id="v2T-jr-FYT"/>
<constraint firstAttribute="trailing" secondItem="qyW-PJ-lOk" secondAttribute="trailing" id="zrr-cs-S3r"/>
</constraints>
<point key="canvasLocation" x="26.5" y="125"/>
</customView>
<collectionViewItem id="ek2-AR-oS5" customClass="SensorItem" customModule="Sensors" customModuleProvider="target">
<connections>
<outlet property="graphContainer" destination="XSC-DH-60t" id="FUo-iJ-Gkw"/>
<outlet property="view" destination="c22-O7-iKe" id="W6K-LU-Klf"/>
</connections>
</collectionViewItem>
</objects>
<resources>
<image name="NSApplicationIcon" width="32" height="32"/>
</resources>
</document>

View File

@@ -0,0 +1,239 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="19162" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="19162"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="SensorsWindowController" customModule="Hot" customModuleProvider="target">
<connections>
<outlet property="arrayController" destination="k4f-4o-be4" id="F6X-Lo-Fc0"/>
<outlet property="collectionView" destination="d8j-id-ziA" id="tbm-Yo-sK6"/>
<outlet property="window" destination="QvC-M9-y7g" id="3Jc-9C-AuF"/>
</connections>
</customObject>
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<window title="Sensors" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" restorable="NO" releasedWhenClosed="NO" visibleAtLaunch="NO" frameAutosaveName="MainWindow" animationBehavior="default" titlebarAppearsTransparent="YES" id="QvC-M9-y7g">
<windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES" fullSizeContentView="YES"/>
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
<rect key="contentRect" x="196" y="240" width="800" height="515"/>
<rect key="screenRect" x="0.0" y="0.0" width="1680" height="1025"/>
<value key="minSize" type="size" width="800" height="515"/>
<view key="contentView" wantsLayer="YES" id="EiT-Mj-1SZ">
<rect key="frame" x="0.0" y="0.0" width="800" height="515"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<visualEffectView blendingMode="behindWindow" material="underWindowBackground" state="followsWindowActiveState" translatesAutoresizingMaskIntoConstraints="NO" id="uRj-rz-hcT">
<rect key="frame" x="0.0" y="0.0" width="800" height="515"/>
<subviews>
<scrollView wantsLayer="YES" borderType="none" autohidesScrollers="YES" horizontalLineScroll="10" horizontalPageScroll="10" verticalLineScroll="10" verticalPageScroll="10" hasHorizontalScroller="NO" usesPredominantAxisScrolling="NO" translatesAutoresizingMaskIntoConstraints="NO" id="aKe-JS-2Ui">
<rect key="frame" x="20" y="102" width="760" height="383"/>
<clipView key="contentView" drawsBackground="NO" id="zVN-hJ-YmX">
<rect key="frame" x="0.0" y="0.0" width="760" height="383"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<collectionView id="d8j-id-ziA">
<rect key="frame" x="0.0" y="0.0" width="760" height="383"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES"/>
<collectionViewFlowLayout key="collectionViewLayout" minimumInteritemSpacing="10" minimumLineSpacing="10" id="jv5-sj-5QH">
<size key="itemSize" width="245" height="120"/>
</collectionViewFlowLayout>
<color key="primaryBackgroundColor" white="1" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<connections>
<binding destination="k4f-4o-be4" name="content" keyPath="arrangedObjects" id="WfZ-ms-k8f"/>
<binding destination="k4f-4o-be4" name="selectionIndexes" keyPath="selectionIndexes" previousBinding="WfZ-ms-k8f" id="JRi-HO-Ojp"/>
<outlet property="dataSource" destination="-2" id="BJq-MM-cYl"/>
</connections>
</collectionView>
</subviews>
</clipView>
<scroller key="horizontalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" horizontal="YES" id="pbH-A9-JB1">
<rect key="frame" x="-100" y="-100" width="233" height="15"/>
<autoresizingMask key="autoresizingMask"/>
</scroller>
<scroller key="verticalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" doubleValue="1" horizontal="NO" id="F9Y-Pq-ygs">
<rect key="frame" x="744" y="0.0" width="16" height="368"/>
<autoresizingMask key="autoresizingMask"/>
</scroller>
<connections>
<binding destination="-2" name="hidden" keyPath="self.sensors.data" id="CrI-nj-hKU">
<dictionary key="options">
<string key="NSValueTransformerName">ArrayIsEmpty</string>
</dictionary>
</binding>
</connections>
</scrollView>
<customView translatesAutoresizingMaskIntoConstraints="NO" id="X4f-ON-Yh7">
<rect key="frame" x="0.0" y="0.0" width="800" height="102"/>
<subviews>
<searchField wantsLayer="YES" verticalHuggingPriority="750" textCompletion="NO" translatesAutoresizingMaskIntoConstraints="NO" id="0b0-bM-ppa">
<rect key="frame" x="20" y="60" width="760" height="22"/>
<searchFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" borderStyle="bezel" usesSingleLineMode="YES" bezelStyle="round" id="m6w-FG-kg5">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</searchFieldCell>
<connections>
<binding destination="-2" name="value" keyPath="self.searchText" id="n2O-a4-5b5">
<dictionary key="options">
<bool key="NSContinuouslyUpdatesValue" value="YES"/>
</dictionary>
</binding>
</connections>
</searchField>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="CWx-dr-5aC">
<rect key="frame" x="18" y="21" width="103" height="18"/>
<buttonCell key="cell" type="check" title="Temperature" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="lZo-Np-2Ev">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<binding destination="-2" name="value" keyPath="self.showTemperature" id="IqV-HK-3FM"/>
</connections>
</button>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="YH9-KV-isM">
<rect key="frame" x="127" y="21" width="72" height="18"/>
<buttonCell key="cell" type="check" title="Voltage" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="7Kn-WX-eN1">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<binding destination="-2" name="value" keyPath="self.showVoltage" id="SEA-Tu-BOl"/>
</connections>
</button>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="DL0-ry-nBa">
<rect key="frame" x="205" y="21" width="72" height="18"/>
<buttonCell key="cell" type="check" title="Current" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="WBA-tr-bDK">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<binding destination="-2" name="value" keyPath="self.showCurrent" id="McY-1z-eLE"/>
</connections>
</button>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="wh8-P7-8p4">
<rect key="frame" x="610" y="22" width="79" height="16"/>
<textFieldCell key="cell" lineBreakMode="clipping" title="Graph Style:" id="6Ty-8X-UI6">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<popUpButton wantsLayer="YES" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="b2Z-bC-lIL">
<rect key="frame" x="692" y="16" width="92" height="25"/>
<popUpButtonCell key="cell" type="push" title="Gradient" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="UpT-Zz-emE" id="ToP-qb-kLc">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
<menu key="menu" id="bNn-yW-MVT">
<items>
<menuItem title="Gradient" state="on" id="UpT-Zz-emE"/>
<menuItem title="Line" tag="1" id="VP7-OH-4an"/>
<menuItem title="Fill" tag="2" id="Uix-Lw-WIT"/>
<menuItem title="Bars" tag="3" id="j4P-bQ-osF"/>
</items>
</menu>
</popUpButtonCell>
<connections>
<binding destination="-2" name="selectedTag" keyPath="self.graphStyle" id="LYW-IL-Fda"/>
</connections>
</popUpButton>
</subviews>
<constraints>
<constraint firstItem="0b0-bM-ppa" firstAttribute="leading" secondItem="X4f-ON-Yh7" secondAttribute="leading" constant="20" symbolic="YES" id="7Hg-zM-lO0"/>
<constraint firstItem="wh8-P7-8p4" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="DL0-ry-nBa" secondAttribute="trailing" constant="8" symbolic="YES" id="AMf-YL-aMc"/>
<constraint firstItem="YH9-KV-isM" firstAttribute="centerY" secondItem="CWx-dr-5aC" secondAttribute="centerY" id="DiY-62-WNM"/>
<constraint firstItem="0b0-bM-ppa" firstAttribute="top" secondItem="X4f-ON-Yh7" secondAttribute="top" constant="20" symbolic="YES" id="MLt-9Q-NWw"/>
<constraint firstItem="wh8-P7-8p4" firstAttribute="centerY" secondItem="DL0-ry-nBa" secondAttribute="centerY" id="TS8-6H-T7P"/>
<constraint firstItem="DL0-ry-nBa" firstAttribute="centerY" secondItem="YH9-KV-isM" secondAttribute="centerY" id="UFB-Be-d69"/>
<constraint firstItem="b2Z-bC-lIL" firstAttribute="leading" secondItem="wh8-P7-8p4" secondAttribute="trailing" constant="8" symbolic="YES" id="V4o-Be-jIe"/>
<constraint firstItem="CWx-dr-5aC" firstAttribute="leading" secondItem="X4f-ON-Yh7" secondAttribute="leading" constant="20" symbolic="YES" id="kHN-fy-wU3"/>
<constraint firstItem="DL0-ry-nBa" firstAttribute="leading" secondItem="YH9-KV-isM" secondAttribute="trailing" constant="8" symbolic="YES" id="kXC-gz-rYd"/>
<constraint firstItem="b2Z-bC-lIL" firstAttribute="top" secondItem="0b0-bM-ppa" secondAttribute="bottom" constant="20" symbolic="YES" id="nmc-VG-jfo"/>
<constraint firstAttribute="bottom" secondItem="b2Z-bC-lIL" secondAttribute="bottom" constant="20" symbolic="YES" id="qF9-cm-S3p"/>
<constraint firstItem="YH9-KV-isM" firstAttribute="leading" secondItem="CWx-dr-5aC" secondAttribute="trailing" constant="8" symbolic="YES" id="sOb-Yn-u3A"/>
<constraint firstAttribute="trailing" secondItem="0b0-bM-ppa" secondAttribute="trailing" constant="20" symbolic="YES" id="vhy-3U-orM"/>
<constraint firstAttribute="trailing" secondItem="b2Z-bC-lIL" secondAttribute="trailing" constant="20" symbolic="YES" id="vmq-Sh-oFu"/>
<constraint firstItem="b2Z-bC-lIL" firstAttribute="centerY" secondItem="wh8-P7-8p4" secondAttribute="centerY" id="wRu-AK-pi5"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="DL0-ry-nBa" secondAttribute="trailing" constant="20" symbolic="YES" id="yDe-dg-UV7"/>
</constraints>
<connections>
<binding destination="-2" name="hidden" keyPath="self.sensors.data" id="oYT-Hj-xEu">
<dictionary key="options">
<string key="NSValueTransformerName">ArrayIsEmpty</string>
</dictionary>
</binding>
</connections>
</customView>
<customView translatesAutoresizingMaskIntoConstraints="NO" id="Asg-vC-Nsi">
<rect key="frame" x="252" y="98" width="296" height="320"/>
<subviews>
<imageView horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="yFI-k2-eyH">
<rect key="frame" x="20" y="44" width="256" height="256"/>
<constraints>
<constraint firstAttribute="height" constant="256" id="3bQ-I0-4D7"/>
<constraint firstAttribute="width" constant="256" id="wmc-Wj-n9C"/>
</constraints>
<imageCell key="cell" refusesFirstResponder="YES" alignment="left" imageScaling="proportionallyUpOrDown" image="NSApplicationIcon" id="ccv-7E-1yf"/>
</imageView>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="caY-ih-a0p">
<rect key="frame" x="64" y="20" width="168" height="16"/>
<textFieldCell key="cell" lineBreakMode="clipping" alignment="center" title="No sensor data available..." id="dA5-eI-Kva">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
</subviews>
<constraints>
<constraint firstItem="yFI-k2-eyH" firstAttribute="leading" secondItem="Asg-vC-Nsi" secondAttribute="leading" constant="20" symbolic="YES" id="6hv-WO-Mp2"/>
<constraint firstItem="caY-ih-a0p" firstAttribute="centerX" secondItem="yFI-k2-eyH" secondAttribute="centerX" id="FCJ-Ar-PuC"/>
<constraint firstAttribute="bottom" secondItem="caY-ih-a0p" secondAttribute="bottom" constant="20" symbolic="YES" id="GPb-JF-OQU"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="caY-ih-a0p" secondAttribute="trailing" constant="20" symbolic="YES" id="aav-Ni-BKP"/>
<constraint firstAttribute="trailing" secondItem="yFI-k2-eyH" secondAttribute="trailing" constant="20" symbolic="YES" id="isW-X4-uYe"/>
<constraint firstItem="caY-ih-a0p" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="Asg-vC-Nsi" secondAttribute="leading" constant="20" symbolic="YES" id="pxk-3O-W79"/>
<constraint firstItem="caY-ih-a0p" firstAttribute="top" secondItem="yFI-k2-eyH" secondAttribute="bottom" constant="8" symbolic="YES" id="vsg-hK-dIh"/>
<constraint firstItem="yFI-k2-eyH" firstAttribute="top" secondItem="Asg-vC-Nsi" secondAttribute="top" constant="20" symbolic="YES" id="woG-6u-Efg"/>
</constraints>
<connections>
<binding destination="-2" name="hidden" keyPath="self.sensors.data" id="nDO-gY-oWD">
<dictionary key="options">
<string key="NSValueTransformerName">ArrayIsNotEmpty</string>
</dictionary>
</binding>
</connections>
</customView>
</subviews>
<constraints>
<constraint firstItem="X4f-ON-Yh7" firstAttribute="top" secondItem="aKe-JS-2Ui" secondAttribute="bottom" id="5IN-cs-jqm"/>
<constraint firstItem="Asg-vC-Nsi" firstAttribute="centerX" secondItem="uRj-rz-hcT" secondAttribute="centerX" id="961-zO-gq7"/>
<constraint firstItem="aKe-JS-2Ui" firstAttribute="leading" secondItem="uRj-rz-hcT" secondAttribute="leading" constant="20" symbolic="YES" id="K0o-wa-efC"/>
<constraint firstAttribute="bottom" secondItem="X4f-ON-Yh7" secondAttribute="bottom" id="L6P-Pp-IP7"/>
<constraint firstAttribute="trailing" secondItem="X4f-ON-Yh7" secondAttribute="trailing" id="Lxr-7P-w7M"/>
<constraint firstAttribute="trailing" secondItem="aKe-JS-2Ui" secondAttribute="trailing" constant="20" symbolic="YES" id="RZP-rx-Xju"/>
<constraint firstItem="aKe-JS-2Ui" firstAttribute="top" secondItem="uRj-rz-hcT" secondAttribute="top" constant="30" id="b5q-1K-Vb2"/>
<constraint firstItem="X4f-ON-Yh7" firstAttribute="leading" secondItem="uRj-rz-hcT" secondAttribute="leading" id="ffA-eK-qjQ"/>
<constraint firstItem="Asg-vC-Nsi" firstAttribute="centerY" secondItem="uRj-rz-hcT" secondAttribute="centerY" id="hRR-yE-RmG"/>
</constraints>
</visualEffectView>
</subviews>
<constraints>
<constraint firstItem="uRj-rz-hcT" firstAttribute="top" secondItem="EiT-Mj-1SZ" secondAttribute="top" id="WwN-si-Yl6"/>
<constraint firstAttribute="trailing" secondItem="uRj-rz-hcT" secondAttribute="trailing" id="eKa-0b-qZ9"/>
<constraint firstItem="uRj-rz-hcT" firstAttribute="leading" secondItem="EiT-Mj-1SZ" secondAttribute="leading" id="f7k-yq-eVb"/>
<constraint firstAttribute="bottom" secondItem="uRj-rz-hcT" secondAttribute="bottom" id="v4a-ky-3o2"/>
</constraints>
</view>
<point key="canvasLocation" x="-110" y="144"/>
</window>
<arrayController selectsInsertedObjects="NO" avoidsEmptySelection="NO" clearsFilterPredicateOnInsertion="NO" automaticallyRearrangesObjects="YES" id="k4f-4o-be4">
<connections>
<binding destination="-2" name="contentArray" keyPath="self.sensors.data" id="V1H-EA-tCm"/>
</connections>
</arrayController>
</objects>
<resources>
<image name="NSApplicationIcon" width="32" height="32"/>
</resources>
</document>

View File

@@ -12,7 +12,20 @@ Hot
Hot is macOS menu bar application that displays the CPU speed limit due to thermal issues.
![Menu](Assets/menu.png "Menu")
#### Differences between the Intel and Apple Silicon versions
On an Intel machine, Hot will display the CPU temperature, CPU speed limit (throttling), scheduler limit and number of available CPUs.
By default, the menu bar text will be colorized in orange if the CPU speed limit falls below 60%.
On Apple Silicon, these informations are not available.
Along with the CPU temperatire, Hot will display the system's thermal pressure.
The menu bar text will be colorized in orange if the pressure is not nominal.
A graph view for all sensors may also be displayed on Apple Silicon.
![Intel Menu](Assets/menu.png "Intel Menu")
![Apple Silicon Menu](Assets/menu-arm.png "Apple Silicon Menu")
![Sensors](Assets/sensors.png "Sensors")
License
-------