2020-06-23 00:03:00 +02:00
//
// p o p u p . s w i f t
2020-07-06 15:52:57 +02:00
// S e n s o r s
2020-06-23 00:03:00 +02:00
//
// C r e a t e d b y S e r h i y M y t r o v t s i y o n 2 2 / 0 6 / 2 0 2 0 .
// U s i n g S w i f t 5 . 0 .
// R u n n i n g o n m a c O S 1 0 . 1 5 .
//
// C o p y r i g h t © 2 0 2 0 S e r h i y M y t r o v t s i y . A l l r i g h t s r e s e r v e d .
//
import Cocoa
2021-06-04 19:37:29 +02:00
import Kit
2020-06-23 00:03:00 +02:00
2023-03-18 19:23:51 +01:00
private struct Sensor_t : KeyValue_p {
let key : String
let name : String ?
var value : String
var additional : Any ?
var index : Int {
2024-08-03 21:45:04 +02:00
get { Store . shared . int ( key : " sensors_ \( self . key ) _index " , defaultValue : - 1 ) }
set { Store . shared . set ( key : " sensors_ \( self . key ) _index " , value : newValue ) }
2023-03-18 19:23:51 +01:00
}
2020-06-23 00:03:00 +02:00
2023-03-18 19:23:51 +01:00
init ( key : String , value : String , name : String ? = nil ) {
self . key = key
self . value = value
self . name = name
}
}
2023-03-25 14:54:30 +01:00
internal class Popup : PopupWrapper {
2023-03-18 19:23:51 +01:00
private var list : [ String : NSView ] = [ : ]
2023-05-02 17:56:14 +02:00
2024-08-03 21:45:04 +02:00
private var unknownSensorsState : Bool { Store . shared . bool ( key : " Sensors_unknown " , defaultValue : false ) }
2023-05-05 20:12:41 +02:00
private var fanValueState : FanValue = . percentage
2023-03-18 19:23:51 +01:00
private var sensors : [ Sensor_p ] = [ ]
2024-04-24 06:10:27 +02:00
private let settingsView : NSStackView = NSStackView ( )
2022-12-22 18:13:34 +01:00
2023-05-02 17:56:14 +02:00
private var fanControlState : Bool {
2024-04-24 06:10:27 +02:00
get { Store . shared . bool ( key : " Sensors_fanControl " , defaultValue : true ) }
set { Store . shared . set ( key : " Sensors_fanControl " , value : newValue ) }
2023-05-02 17:56:14 +02:00
}
2020-06-23 00:03:00 +02:00
public init ( ) {
super . init ( frame : NSRect ( x : 0 , y : 0 , width : Constants . Popup . width , height : 0 ) )
2021-12-11 13:54:14 +01:00
2024-04-24 06:10:27 +02:00
self . fanValueState = FanValue ( rawValue : Store . shared . string ( key : " Sensors_popup_fanValue " , defaultValue : self . fanValueState . rawValue ) ) ? ? . percentage
2021-12-11 13:54:14 +01:00
self . orientation = . vertical
self . spacing = 0
2023-05-02 17:56:14 +02:00
self . translatesAutoresizingMaskIntoConstraints = false
2023-05-05 20:12:41 +02:00
2024-04-24 06:10:27 +02:00
self . settingsView . orientation = . vertical
self . settingsView . spacing = Constants . Settings . margin
2023-07-01 19:34:54 +02:00
2024-04-24 06:10:27 +02:00
self . settingsView . addArrangedSubview ( PreferencesSection ( [
PreferencesRow ( localizedString ( " Fan value " ) , component : selectView (
action : #selector ( self . toggleFanValue ) ,
items : FanValues ,
selected : self . fanValueState . rawValue
) )
] ) )
2020-06-23 00:03:00 +02:00
}
required init ? ( coder : NSCoder ) {
fatalError ( " init(coder:) has not been implemented " )
}
2023-03-18 19:23:51 +01:00
internal func setup ( _ values : [ Sensor_p ] ? = nil , reload : Bool = false ) {
guard let values = reload ? self . sensors : values else { return }
2024-08-03 21:45:04 +02:00
let fans = values . filter ( { $0 . type = = . fan && $0 . popupState } )
var sensors = values
2022-12-22 18:13:34 +01:00
if ! self . unknownSensorsState {
sensors = sensors . filter ( { $0 . group != . unknown } )
}
2020-06-23 00:03:00 +02:00
2023-03-18 19:23:51 +01:00
self . subviews . forEach ( { $0 . removeFromSuperview ( ) } )
if ! reload {
2024-04-24 06:10:27 +02:00
self . settingsView . subviews . filter ( { $0 . identifier = = NSUserInterfaceItemIdentifier ( " sensor " ) } ) . forEach { v in
v . removeFromSuperview ( )
}
2021-12-11 13:54:14 +01:00
}
if ! fans . isEmpty {
2023-05-02 17:56:14 +02:00
self . addArrangedSubview ( self . fansSeparatorView ( ) )
2021-12-11 13:54:14 +01:00
let container = NSStackView ( )
container . orientation = . vertical
2023-12-28 18:21:14 +01:00
container . spacing = Constants . Popup . spacing
2021-12-11 13:54:14 +01:00
fans . forEach { ( f : Sensor_p ) in
if let fan = f as ? Fan {
2024-08-03 21:45:04 +02:00
if f . isComputed {
let sensor = SensorView ( fan , width : self . frame . width , toggleable : false ) { }
self . list [ fan . key ] = sensor
container . addArrangedSubview ( sensor )
} else {
let view = FanView ( fan , width : self . frame . width ) { [ weak self ] in
let h = container . arrangedSubviews . map ( { $0 . bounds . height + container . spacing } ) . reduce ( 0 , + ) - container . spacing
if container . frame . size . height != h && h >= 0 {
container . setFrameSize ( NSSize ( width : container . frame . width , height : h ) )
}
self ? . recalculateHeight ( )
2021-12-11 13:54:14 +01:00
}
2024-08-03 21:45:04 +02:00
self . list [ fan . key ] = view
container . addArrangedSubview ( view )
2021-12-11 13:54:14 +01:00
}
}
}
let h = container . arrangedSubviews . map ( { $0 . bounds . height + container . spacing } ) . reduce ( 0 , + ) - container . spacing
if container . frame . size . height != h {
container . setFrameSize ( NSSize ( width : container . frame . width , height : h ) )
}
self . addArrangedSubview ( container )
}
2021-04-10 15:50:02 +02:00
var types : [ SensorType ] = [ ]
2021-12-11 13:54:14 +01:00
sensors . forEach { ( s : Sensor_p ) in
2020-07-16 19:43:43 +02:00
if ! types . contains ( s . type ) {
types . append ( s . type )
}
2020-06-23 00:03:00 +02:00
}
2023-12-28 18:21:14 +01:00
2021-12-11 13:54:14 +01:00
types . forEach { ( typ : SensorType ) in
2023-03-18 19:23:51 +01:00
var filtered = sensors . filter { $0 . type = = typ }
2021-04-10 15:50:02 +02:00
var groups : [ SensorGroup ] = [ ]
2021-08-07 12:33:23 +03:00
filtered . forEach { ( s : Sensor_p ) in
2020-07-16 19:43:43 +02:00
if ! groups . contains ( s . group ) {
groups . append ( s . group )
}
2020-06-23 00:03:00 +02:00
}
2023-12-28 18:21:14 +01:00
2023-03-18 19:23:51 +01:00
if ! reload {
2024-04-24 06:10:27 +02:00
let section = PreferencesSection ( label : typ . rawValue )
section . identifier = NSUserInterfaceItemIdentifier ( " sensor " )
2023-03-18 19:23:51 +01:00
groups . forEach { ( group : SensorGroup ) in
filtered . filter { $0 . group = = group } . forEach { ( s : Sensor_p ) in
2024-04-24 06:10:27 +02:00
let btn = switchView (
action : #selector ( self . toggleSensor ) ,
state : s . popupState
)
btn . identifier = NSUserInterfaceItemIdentifier ( rawValue : s . key )
section . add ( PreferencesRow ( localizedString ( s . name ) , component : btn ) )
2023-03-18 19:23:51 +01:00
}
}
2024-04-24 06:10:27 +02:00
self . settingsView . addArrangedSubview ( section )
2023-03-18 19:23:51 +01:00
}
2023-12-28 18:21:14 +01:00
if typ = = . fan { return }
2023-03-18 19:23:51 +01:00
filtered = filtered . filter { $0 . popupState }
if filtered . isEmpty { return }
2023-12-28 18:21:14 +01:00
2023-03-18 19:23:51 +01:00
self . addArrangedSubview ( separatorView ( localizedString ( typ . rawValue ) , width : self . frame . width ) )
2022-01-15 11:49:45 +01:00
groups . forEach { ( group : SensorGroup ) in
2021-12-11 13:54:14 +01:00
filtered . filter { $0 . group = = group } . forEach { ( s : Sensor_p ) in
2024-08-03 21:45:04 +02:00
let sensor = SensorView ( s , width : self . frame . width ) { [ weak self ] in
2022-03-03 19:16:39 +01:00
self ? . recalculateHeight ( )
}
2022-01-29 14:42:56 +01:00
self . addArrangedSubview ( sensor )
self . list [ s . key ] = sensor
2020-06-23 00:03:00 +02:00
}
}
}
2023-03-18 19:23:51 +01:00
if ! reload {
self . sensors = values
}
2021-12-11 13:54:14 +01:00
self . recalculateHeight ( )
2020-06-23 00:03:00 +02:00
}
2021-08-07 12:33:23 +03:00
internal func usageCallback ( _ values : [ Sensor_p ] ) {
2020-11-26 15:39:36 +01:00
DispatchQueue . main . async ( execute : {
2022-03-14 18:34:58 +01:00
values . filter ( { $0 is Sensor } ) . forEach { ( s : Sensor_p ) in
if let sensor = self . list [ s . key ] as ? SensorView {
sensor . addHistoryPoint ( s )
}
}
2021-05-22 14:58:20 +02:00
if self . window ? . isVisible ? ? false {
2021-08-07 12:33:23 +03:00
values . forEach { ( s : Sensor_p ) in
2021-12-11 13:54:14 +01:00
switch self . list [ s . key ] {
case let fan as FanView :
if let f = s as ? Fan {
fan . update ( f )
}
2022-01-29 14:42:56 +01:00
case let sensor as SensorView :
sensor . update ( s )
2021-12-29 16:09:02 +01:00
case . none , . some :
2021-12-11 13:54:14 +01:00
break
}
}
}
} )
}
private func recalculateHeight ( ) {
let h = self . arrangedSubviews . map ( { $0 . bounds . height + self . spacing } ) . reduce ( 0 , + ) - self . spacing
if self . frame . size . height != h {
self . setFrameSize ( NSSize ( width : self . frame . width , height : h ) )
self . sizeCallback ? ( self . frame . size )
}
}
2022-10-04 21:44:27 +02:00
// MARK: - S e t t i n g s
2023-03-25 14:54:30 +01:00
public override func settings ( ) -> NSView ? {
2023-03-18 19:23:51 +01:00
self . settingsView
}
2023-05-05 20:12:41 +02:00
@objc private func toggleFanValue ( _ sender : NSMenuItem ) {
if let key = sender . representedObject as ? String , let value = FanValue ( rawValue : key ) {
self . fanValueState = value
Store . shared . set ( key : " Sensors_popup_fanValue " , value : self . fanValueState . rawValue )
}
}
2023-03-18 19:23:51 +01:00
// MARK: h e l p e r s
2023-05-02 17:56:14 +02:00
private func fansSeparatorView ( ) -> NSView {
let view : NSStackView = NSStackView ( frame : NSRect ( x : 0 , y : 0 , width : self . frame . width , height : 26 ) )
view . widthAnchor . constraint ( equalToConstant : view . frame . width ) . isActive = true
view . heightAnchor . constraint ( equalToConstant : view . bounds . height ) . isActive = true
view . orientation = . horizontal
view . spacing = 0
view . distribution = . fillEqually
view . alignment = . top
let labelView : NSTextField = TextView ( )
labelView . stringValue = localizedString ( " Fans " )
labelView . alignment = . center
labelView . textColor = . secondaryLabelColor
labelView . font = NSFont . systemFont ( ofSize : 12 , weight : . medium )
let btnContainer = NSView ( )
let button = NSButton ( )
button . frame = CGRect ( x : ( self . frame . width / 3 ) - 20 , y : 10 , width : 15 , height : 15 )
button . bezelStyle = . regularSquare
button . isBordered = false
button . imageScaling = NSImageScaling . scaleAxesIndependently
button . contentTintColor = . lightGray
button . action = #selector ( self . toggleFanControl )
button . target = self
button . toolTip = localizedString ( " Control " )
button . image = Bundle ( for : Module . self ) . image ( forResource : " tune " ) !
btnContainer . addSubview ( button )
view . addArrangedSubview ( NSView ( ) )
view . addArrangedSubview ( labelView )
view . addArrangedSubview ( btnContainer )
return view
}
@objc private func toggleSensor ( _ sender : NSControl ) {
2023-03-18 19:23:51 +01:00
guard let id = sender . identifier else { return }
Store . shared . set ( key : " sensor_ \( id . rawValue ) _popup " , value : controlState ( sender ) )
self . setup ( reload : true )
2022-10-04 21:44:27 +02:00
}
2023-05-02 17:56:14 +02:00
@objc private func toggleFanControl ( ) {
self . fanControlState = ! self . fanControlState
NotificationCenter . default . post ( name : . toggleFanControl , object : nil , userInfo : [ " state " : self . fanControlState ] )
}
2021-12-11 13:54:14 +01:00
}
2022-01-29 14:42:56 +01:00
// MARK: - S e n s o r v i e w
internal class SensorView : NSStackView {
2022-03-03 19:16:39 +01:00
public var sizeCallback : ( ( ) -> Void )
private var valueView : ValueSensorView !
private var chartView : ChartSensorView !
private var openned : Bool = false
2024-08-03 21:45:04 +02:00
private var fanValueState : FanValue {
FanValue ( rawValue : Store . shared . string ( key : " Sensors_popup_fanValue " , defaultValue : FanValue . percentage . rawValue ) ) ? ? . percentage
}
public init ( _ sensor : Sensor_p , width : CGFloat , toggleable : Bool = true , callback : @ escaping ( ( ) -> Void ) ) {
2022-03-03 19:16:39 +01:00
self . sizeCallback = callback
super . init ( frame : NSRect ( x : 0 , y : 0 , width : width , height : 22 ) )
self . orientation = . vertical
self . distribution = . fillProportionally
self . spacing = 0
2024-08-03 21:45:04 +02:00
self . valueView = ValueSensorView ( sensor , width : width , toggleable : toggleable , callback : { [ weak self ] in
2022-03-03 19:16:39 +01:00
self ? . open ( )
} )
2022-05-24 23:26:53 +02:00
self . chartView = ChartSensorView ( width : width , suffix : sensor . unit )
2022-03-03 19:16:39 +01:00
self . addArrangedSubview ( self . valueView )
NSLayoutConstraint . activate ( [
self . widthAnchor . constraint ( equalToConstant : self . bounds . width )
] )
}
required init ? ( coder : NSCoder ) {
fatalError ( " init(coder:) has not been implemented " )
}
public func update ( _ sensor : Sensor_p ) {
2024-08-03 21:45:04 +02:00
var value = sensor . formattedPopupValue
if let fan = sensor as ? Fan {
value = self . fanValueState = = . percentage ? " \( fan . percentage ) % " : fan . formattedValue
}
self . valueView . update ( value )
2022-03-11 19:02:40 +01:00
}
public func addHistoryPoint ( _ sensor : Sensor_p ) {
2023-12-19 14:09:27 +01:00
self . chartView . update ( sensor . localValue )
2022-03-03 19:16:39 +01:00
}
private func open ( ) {
if self . openned {
self . chartView . removeFromSuperview ( )
} else {
self . addArrangedSubview ( self . chartView )
}
self . openned = ! self . openned
let h = self . arrangedSubviews . map ( { $0 . bounds . height } ) . reduce ( 0 , + )
self . setFrameSize ( NSSize ( width : self . frame . width , height : h ) )
self . sizeCallback ( )
}
}
internal class ValueSensorView : NSStackView {
public var callback : ( ( ) -> Void )
2022-01-29 14:42:56 +01:00
private var labelView : LabelField = {
2024-01-19 21:05:48 +01:00
let view = LabelField ( frame : NSRect . zero )
2022-01-29 14:42:56 +01:00
view . cell ? . truncatesLastVisibleLine = true
return view
} ( )
2024-01-19 21:05:48 +01:00
private var valueView : ValueField = ValueField ( frame : NSRect . zero )
2022-01-29 14:42:56 +01:00
2024-08-03 21:45:04 +02:00
private let isToggleable : Bool
public init ( _ sensor : Sensor_p , width : CGFloat , toggleable : Bool = true , callback : @ escaping ( ( ) -> Void ) ) {
2022-03-03 19:16:39 +01:00
self . callback = callback
2024-08-03 21:45:04 +02:00
self . isToggleable = toggleable
2022-01-29 14:42:56 +01:00
super . init ( frame : NSRect ( x : 0 , y : 0 , width : width , height : 22 ) )
self . wantsLayer = true
self . orientation = . horizontal
self . distribution = . fillProportionally
self . spacing = 0
self . layer ? . cornerRadius = 3
self . labelView . stringValue = sensor . name
self . labelView . toolTip = sensor . key
self . valueView . stringValue = sensor . formattedValue
self . addArrangedSubview ( self . labelView )
self . addArrangedSubview ( self . valueView )
2024-08-03 21:45:04 +02:00
if self . isToggleable {
self . addTrackingArea ( NSTrackingArea (
rect : NSRect ( x : 0 , y : 0 , width : self . frame . width , height : 22 ) ,
options : [ NSTrackingArea . Options . activeAlways , NSTrackingArea . Options . mouseEnteredAndExited , NSTrackingArea . Options . activeInActiveApp ] ,
owner : self ,
userInfo : nil
) )
}
2022-01-29 14:42:56 +01:00
NSLayoutConstraint . activate ( [
self . labelView . heightAnchor . constraint ( equalToConstant : 16 ) ,
self . widthAnchor . constraint ( equalToConstant : self . bounds . width ) ,
self . heightAnchor . constraint ( equalToConstant : self . bounds . height )
] )
}
required init ? ( coder : NSCoder ) {
fatalError ( " init(coder:) has not been implemented " )
}
2022-03-03 19:16:39 +01:00
public func update ( _ value : String ) {
self . valueView . stringValue = value
}
override func mouseDown ( with theEvent : NSEvent ) {
2024-08-03 21:45:04 +02:00
guard self . isToggleable else { return }
2022-03-03 19:16:39 +01:00
self . callback ( )
2022-01-29 14:42:56 +01:00
}
public override func mouseEntered ( with : NSEvent ) {
2024-08-03 21:45:04 +02:00
guard self . isToggleable else { return }
2022-01-29 14:42:56 +01:00
self . layer ? . backgroundColor = . init ( gray : 0.01 , alpha : 0.05 )
}
public override func mouseExited ( with : NSEvent ) {
2024-08-03 21:45:04 +02:00
guard self . isToggleable else { return }
2022-01-29 14:42:56 +01:00
self . layer ? . backgroundColor = . none
}
}
2022-03-03 19:16:39 +01:00
internal class ChartSensorView : NSStackView {
private var chart : LineChartView ? = nil
2022-01-29 14:42:56 +01:00
2022-05-24 23:26:14 +02:00
public init ( width : CGFloat , suffix : String ) {
2022-03-03 19:16:39 +01:00
super . init ( frame : NSRect ( x : 0 , y : 0 , width : width , height : 60 ) )
self . wantsLayer = true
self . layer ? . backgroundColor = NSColor . lightGray . withAlphaComponent ( 0.1 ) . cgColor
self . orientation = . horizontal
self . distribution = . fillProportionally
self . spacing = 0
self . layer ? . cornerRadius = 3
2023-02-10 09:35:47 +01:00
self . chart = LineChartView ( frame : NSRect ( x : 0 , y : 0 , width : self . frame . width , height : self . frame . height ) , num : 120 , scale : . linear )
2022-05-24 23:26:14 +02:00
self . chart ? . suffix = suffix
2022-03-03 19:16:39 +01:00
if let view = self . chart {
self . addArrangedSubview ( view )
}
NSLayoutConstraint . activate ( [
self . widthAnchor . constraint ( equalToConstant : self . bounds . width ) ,
self . heightAnchor . constraint ( equalToConstant : self . bounds . height )
] )
}
2022-01-29 14:42:56 +01:00
2022-03-03 19:16:39 +01:00
required init ? ( coder : NSCoder ) {
fatalError ( " init(coder:) has not been implemented " )
2022-01-29 14:42:56 +01:00
}
2022-03-03 19:16:39 +01:00
public func update ( _ value : Double ) {
2022-03-14 18:34:58 +01:00
self . chart ? . addValue ( value / 100 )
2022-03-03 19:16:39 +01:00
}
2022-01-29 14:42:56 +01:00
}
// MARK: - F a n v i e w
2021-12-11 13:54:14 +01:00
internal class FanView : NSStackView {
public var sizeCallback : ( ( ) -> Void )
private var fan : Fan
private var ready : Bool = false
2022-12-10 09:27:00 +01:00
private var helperView : NSView ? = nil
private var controlView : NSView ? = nil
private var buttonsView : NSView ? = nil
2021-12-11 13:54:14 +01:00
private var valueField : NSTextField ? = nil
private var sliderValueField : NSTextField ? = nil
private var slider : NSSlider ? = nil
private var modeButtons : ModeButtons ? = nil
private var debouncer : DispatchWorkItem ? = nil
2023-05-23 17:53:48 +02:00
private var barView : NSView ? = nil
2021-12-11 13:54:14 +01:00
private var minBtn : NSButton ? = nil
private var maxBtn : NSButton ? = nil
private var speedState : Bool {
2022-11-18 22:51:34 +01:00
Store . shared . bool ( key : " Sensors_speed " , defaultValue : false )
2021-12-11 13:54:14 +01:00
}
2023-05-02 17:56:14 +02:00
private var syncState : Bool {
2022-11-22 16:46:49 +01:00
Store . shared . bool ( key : " Sensors_fansSync " , defaultValue : false )
}
2021-12-11 13:54:14 +01:00
private var speed : Double {
get {
2022-04-26 07:36:31 +02:00
if let v = self . fan . customSpeed , self . speedState {
2021-12-11 13:54:14 +01:00
return Double ( v )
}
return self . fan . value
}
}
private var resetModeAfterSleep : Bool = false
2023-05-02 17:56:14 +02:00
private var controlState : Bool
2023-05-05 20:12:41 +02:00
private var fanValue : FanValue {
FanValue ( rawValue : Store . shared . string ( key : " Sensors_popup_fanValue " , defaultValue : FanValue . percentage . rawValue ) ) ? ? . percentage
}
2021-12-11 13:54:14 +01:00
2022-01-17 18:28:25 +01:00
private var horizontalMargin : CGFloat {
2022-11-18 22:51:34 +01:00
self . edgeInsets . top + self . edgeInsets . bottom + ( self . spacing * CGFloat ( self . arrangedSubviews . count ) )
2022-01-17 18:28:25 +01:00
}
2023-04-28 21:03:32 +02:00
private var willSleepMode : FanMode ? = nil // f a n m o d e b e f o r e s l e e p
private var willSleepSpeed : Int ? = nil // f a n s p e e d b e f o r e s l e e p
2022-08-16 22:47:17 +02:00
2021-12-11 13:54:14 +01:00
public init ( _ fan : Fan , width : CGFloat , callback : @ escaping ( ( ) -> Void ) ) {
self . fan = fan
self . sizeCallback = callback
2023-05-02 17:56:14 +02:00
self . controlState = Store . shared . bool ( key : " Sensors_fanControl " , defaultValue : true )
2021-12-11 13:54:14 +01:00
let inset : CGFloat = 5
super . init ( frame : NSRect ( x : 0 , y : 0 , width : width - ( inset * 2 ) , height : 0 ) )
2022-12-10 09:27:00 +01:00
self . helperView = self . noHelper ( )
2021-12-11 13:54:14 +01:00
self . controlView = self . control ( )
2022-12-10 09:27:00 +01:00
self . buttonsView = self . mode ( )
2021-12-11 13:54:14 +01:00
self . orientation = . vertical
self . alignment = . centerX
self . distribution = . fillProportionally
2022-01-15 11:49:45 +01:00
self . spacing = 1
2021-12-11 13:54:14 +01:00
self . edgeInsets = NSEdgeInsets ( top : inset , left : inset , bottom : inset , right : inset )
self . wantsLayer = true
self . layer ? . cornerRadius = 2
2022-12-10 09:27:00 +01:00
self . nameAndSpeed ( )
2023-05-02 17:56:14 +02:00
self . setupControls ( )
2021-12-11 13:54:14 +01:00
NSWorkspace . shared . notificationCenter . addObserver ( self , selector : #selector ( self . wakeListener ) , name : NSWorkspace . didWakeNotification , object : nil )
2022-08-16 22:47:17 +02:00
NSWorkspace . shared . notificationCenter . addObserver ( self , selector : #selector ( self . sleepListener ) , name : NSWorkspace . willSleepNotification , object : nil )
2023-05-02 17:56:14 +02:00
NotificationCenter . default . addObserver ( self , selector : #selector ( self . syncFanSpeed ) , name : . syncFansControl , object : nil )
NotificationCenter . default . addObserver ( self , selector : #selector ( self . changeHelperState ) , name : . fanHelperState , object : nil )
NotificationCenter . default . addObserver ( self , selector : #selector ( self . controlCallback ) , name : . toggleFanControl , object : nil )
2022-04-21 22:58:28 +02:00
2022-04-26 07:36:31 +02:00
if let fanMode = self . fan . customMode , self . speedState && fanMode != FanMode . automatic {
SMCHelper . shared . setFanMode ( fan . id , mode : fanMode . rawValue )
self . modeButtons ? . setMode ( FanMode ( rawValue : fanMode . rawValue ) ? ? . automatic )
2022-04-21 22:58:28 +02:00
self . setSpeed ( value : Int ( self . speed ) , then : {
DispatchQueue . main . async {
self . sliderValueField ? . textColor = . systemBlue
}
} )
}
2021-12-11 13:54:14 +01:00
}
required init ? ( coder : NSCoder ) {
fatalError ( " init(coder:) has not been implemented " )
}
deinit {
NSWorkspace . shared . notificationCenter . removeObserver ( self )
2023-05-02 17:56:14 +02:00
NotificationCenter . default . removeObserver ( self , name : . syncFansControl , object : nil )
NotificationCenter . default . removeObserver ( self , name : . fanHelperState , object : nil )
NotificationCenter . default . removeObserver ( self , name : . toggleSettings , object : nil )
2021-12-11 13:54:14 +01:00
}
override func updateLayer ( ) {
2023-06-20 16:51:19 +02:00
self . layer ? . backgroundColor = ( isDarkMode ? NSColor ( red : 17 / 255 , green : 17 / 255 , blue : 17 / 255 , alpha : 0.25 ) : NSColor ( red : 245 / 255 , green : 245 / 255 , blue : 245 / 255 , alpha : 1 ) ) . cgColor
2021-12-11 13:54:14 +01:00
}
2022-12-10 09:27:00 +01:00
private func nameAndSpeed ( ) {
2022-01-15 11:49:45 +01:00
let row : NSStackView = NSStackView ( frame : NSRect ( x : 0 , y : 0 , width : self . frame . width , height : 16 ) )
row . widthAnchor . constraint ( equalToConstant : self . frame . width ) . isActive = true
2021-12-11 13:54:14 +01:00
row . heightAnchor . constraint ( equalToConstant : row . bounds . height ) . isActive = true
2022-01-15 11:49:45 +01:00
row . orientation = . horizontal
2023-05-23 17:53:48 +02:00
row . distribution = . fillEqually
2022-01-15 11:49:45 +01:00
row . spacing = 0
2021-12-11 13:54:14 +01:00
2022-01-15 11:49:45 +01:00
let nameField : NSTextField = TextView ( )
2021-12-11 13:54:14 +01:00
nameField . stringValue = self . fan . name
2022-01-29 14:42:56 +01:00
nameField . toolTip = self . fan . key
2021-12-11 13:54:14 +01:00
nameField . cell ? . truncatesLastVisibleLine = true
let value = self . fan . value
2022-01-15 11:49:45 +01:00
let valueField : NSTextField = TextView ( )
valueField . font = NSFont . systemFont ( ofSize : 13 , weight : . regular )
valueField . alignment = . right
2023-05-05 20:12:41 +02:00
valueField . stringValue = self . fanValue = = . percentage ? " \( self . fan . percentage ) % " : self . fan . formattedValue
2022-01-15 11:49:45 +01:00
valueField . toolTip = " \( value ) "
2023-05-23 17:53:48 +02:00
let bar : NSView = NSView ( frame : NSRect ( x : 0 , y : 0 , width : 80 , height : 8 ) )
bar . widthAnchor . constraint ( equalToConstant : bar . bounds . width ) . isActive = true
bar . heightAnchor . constraint ( equalToConstant : bar . bounds . height ) . isActive = true
bar . wantsLayer = true
bar . layer ? . backgroundColor = NSColor . textBackgroundColor . cgColor
2023-12-28 18:21:14 +01:00
bar . layer ? . borderColor = NSColor . quaternaryLabelColor . cgColor
bar . layer ? . borderWidth = 1
2023-05-23 17:53:48 +02:00
bar . layer ? . cornerRadius = 2
let width : CGFloat = ( bar . frame . width * CGFloat ( self . fan . percentage < 0 ? 0 : self . fan . percentage ) ) / 100
let barInner = NSView ( frame : NSRect ( x : 0 , y : 0 , width : width , height : bar . frame . height ) )
barInner . wantsLayer = true
barInner . layer ? . backgroundColor = NSColor . controlAccentColor . cgColor
bar . addSubview ( barInner )
2022-01-15 11:49:45 +01:00
row . addArrangedSubview ( nameField )
2023-05-23 17:53:48 +02:00
row . addArrangedSubview ( bar )
2022-01-15 11:49:45 +01:00
row . addArrangedSubview ( valueField )
self . valueField = valueField
2023-05-23 17:53:48 +02:00
self . barView = barInner
2021-12-11 13:54:14 +01:00
2022-12-10 09:27:00 +01:00
self . addArrangedSubview ( row )
}
private func noHelper ( ) -> NSView {
let view : NSView = NSView ( frame : NSRect ( x : 0 , y : 0 , width : self . frame . width , height : 30 ) )
view . heightAnchor . constraint ( equalToConstant : view . bounds . height ) . isActive = true
let container = NSStackView ( frame : NSRect ( x : 0 , y : 4 , width : view . frame . width , height : view . frame . height - 8 ) )
container . wantsLayer = true
container . layer ? . cornerRadius = 3
container . layer ? . borderWidth = 1
container . layer ? . borderColor = NSColor . lightGray . cgColor
container . orientation = . horizontal
container . alignment = . centerY
container . distribution = . fillProportionally
container . spacing = 0
let button : NSButton = NSButton ( title : localizedString ( " Install fan helper " ) , target : nil , action : #selector ( self . installHelper ) )
button . isBordered = false
button . target = self
container . addArrangedSubview ( button )
view . addSubview ( container )
return view
2021-12-11 13:54:14 +01:00
}
private func mode ( ) -> NSView {
let view : NSView = NSView ( frame : NSRect ( x : 0 , y : 0 , width : self . frame . width , height : 30 ) )
view . heightAnchor . constraint ( equalToConstant : view . bounds . height ) . isActive = true
let buttons = ModeButtons ( frame : NSRect (
x : 0 ,
y : 4 ,
width : view . frame . width ,
height : view . frame . height - 8
) , mode : self . fan . mode )
buttons . callback = { [ weak self ] ( mode : FanMode ) in
if let fan = self ? . fan , fan . mode != mode {
self ? . fan . mode = mode
2022-04-26 07:36:31 +02:00
self ? . fan . customMode = mode
2021-12-11 13:54:14 +01:00
SMCHelper . shared . setFanMode ( fan . id , mode : mode . rawValue )
}
self ? . toggleControlView ( mode = = . forced )
}
2023-08-04 12:09:53 +02:00
buttons . off = { [ weak self ] in
if let fan = self ? . fan {
if self ? . fan . mode != . forced {
self ? . fan . mode = . forced
SMCHelper . shared . setFanMode ( fan . id , mode : FanMode . forced . rawValue )
}
SMCHelper . shared . setFanSpeed ( fan . id , speed : 0 )
self ? . fan . customSpeed = 0
}
self ? . toggleControlView ( false )
}
2021-12-11 13:54:14 +01:00
buttons . turbo = { [ weak self ] in
if let fan = self ? . fan {
if self ? . fan . mode != . forced {
self ? . fan . mode = . forced
SMCHelper . shared . setFanMode ( fan . id , mode : FanMode . forced . rawValue )
}
SMCHelper . shared . setFanSpeed ( fan . id , speed : Int ( fan . maxSpeed ) )
2022-04-26 07:36:31 +02:00
self ? . fan . customSpeed = Int ( fan . maxSpeed )
2021-12-11 13:54:14 +01:00
}
self ? . toggleControlView ( false )
}
view . addSubview ( buttons )
self . modeButtons = buttons
return view
}
private func control ( ) -> NSView {
let view : NSView = NSView ( frame : NSRect ( x : 0 , y : 0 , width : self . frame . width , height : 46 ) )
view . identifier = NSUserInterfaceItemIdentifier ( rawValue : " control " )
view . heightAnchor . constraint ( equalToConstant : view . bounds . height ) . isActive = true
let controls : NSStackView = NSStackView ( frame : NSRect ( x : 0 , y : 14 , width : view . frame . width , height : 30 ) )
controls . orientation = . horizontal
controls . spacing = 0
let slider : NSSlider = NSSlider ( frame : NSRect ( x : 0 , y : 0 , width : view . frame . width , height : 26 ) )
slider . minValue = self . fan . minSpeed
slider . maxValue = self . fan . maxSpeed
slider . doubleValue = self . speed
slider . isContinuous = true
slider . action = #selector ( self . sliderCallback )
slider . target = self
let levels : NSView = NSView ( frame : NSRect ( x : 0 , y : 0 , width : view . frame . width , height : 16 ) )
let minBtn : NSButton = NSButton ( frame : NSRect ( x : 0 , y : 0 , width : 50 , height : levels . frame . height ) )
minBtn . title = " \( Int ( self . fan . minSpeed ) ) "
minBtn . toolTip = localizedString ( " Min " )
minBtn . setButtonType ( . toggle )
minBtn . isBordered = false
minBtn . target = self
minBtn . state = . off
minBtn . action = #selector ( self . setMin )
minBtn . wantsLayer = true
minBtn . layer ? . cornerRadius = 3
minBtn . layer ? . borderWidth = 1
minBtn . layer ? . borderColor = NSColor . lightGray . cgColor
let valueField : NSTextField = TextView ( frame : NSRect ( x : 80 , y : 0 , width : levels . frame . width - 160 , height : levels . frame . height ) )
valueField . font = NSFont . systemFont ( ofSize : 11 , weight : . light )
valueField . textColor = . secondaryLabelColor
valueField . alignment = . center
let maxBtn : NSButton = NSButton ( frame : NSRect ( x : levels . frame . width - 50 , y : 0 , width : 50 , height : levels . frame . height ) )
maxBtn . title = " \( Int ( self . fan . maxSpeed ) ) "
maxBtn . toolTip = localizedString ( " Max " )
maxBtn . setButtonType ( . toggle )
maxBtn . isBordered = false
maxBtn . target = self
maxBtn . state = . off
maxBtn . wantsLayer = true
maxBtn . action = #selector ( self . setMax )
maxBtn . layer ? . cornerRadius = 3
maxBtn . layer ? . borderWidth = 1
maxBtn . layer ? . borderColor = NSColor . lightGray . cgColor
controls . addArrangedSubview ( slider )
levels . addSubview ( minBtn )
levels . addSubview ( valueField )
levels . addSubview ( maxBtn )
view . addSubview ( controls )
view . addSubview ( levels )
self . slider = slider
self . sliderValueField = valueField
self . minBtn = minBtn
self . maxBtn = maxBtn
return view
}
private func toggleControlView ( _ state : Bool ) {
guard let view = self . controlView else {
return
}
if state {
self . slider ? . doubleValue = self . speed
if self . speedState {
self . setSpeed ( value : Int ( self . speed ) , then : {
DispatchQueue . main . async {
self . sliderValueField ? . textColor = . systemBlue
}
} )
}
self . addArrangedSubview ( view )
} else {
self . sliderValueField ? . stringValue = " "
self . sliderValueField ? . textColor = . secondaryLabelColor
self . minBtn ? . state = . off
self . maxBtn ? . state = . off
view . removeFromSuperview ( )
}
2022-01-17 18:28:25 +01:00
let h = self . arrangedSubviews . map ( { $0 . bounds . height } ) . reduce ( 0 , + )
self . setFrameSize ( NSSize ( width : self . frame . width , height : h + self . horizontalMargin ) )
2021-12-11 13:54:14 +01:00
self . sizeCallback ( )
}
private func setSpeed ( value : Int , then : @ escaping ( ) -> Void = { } ) {
self . sliderValueField ? . stringValue = " \( value ) RPM "
self . sliderValueField ? . textColor = . secondaryLabelColor
2022-04-26 07:36:31 +02:00
self . fan . customSpeed = value
2021-12-11 13:54:14 +01:00
self . debouncer ? . cancel ( )
let task = DispatchWorkItem { [ weak self ] in
DispatchQueue . global ( qos : . userInteractive ) . async { [ weak self ] in
if let id = self ? . fan . id {
SMCHelper . shared . setFanSpeed ( id , speed : value )
}
then ( )
}
}
self . debouncer = task
DispatchQueue . main . asyncAfter ( deadline : DispatchTime . now ( ) + 0.3 , execute : task )
}
@objc private func sliderCallback ( _ sender : NSSlider ) {
2023-03-21 18:22:24 +01:00
var value = sender . doubleValue
if value > self . fan . maxSpeed {
value = self . fan . maxSpeed
} else if value < self . fan . minSpeed {
value = self . fan . minSpeed
}
2021-12-11 13:54:14 +01:00
self . minBtn ? . state = . off
self . maxBtn ? . state = . off
self . setSpeed ( value : Int ( value ) , then : {
DispatchQueue . main . async {
2022-06-28 18:28:09 +02:00
self . slider ? . intValue = Int32 ( value )
2021-12-11 13:54:14 +01:00
self . sliderValueField ? . textColor = . systemBlue
}
} )
2022-06-28 18:28:09 +02:00
if sender . tag != 4 {
2023-04-21 17:25:50 +02:00
if self . fan . minSpeed != 0 && self . fan . maxSpeed != 0 && self . fan . maxSpeed != self . fan . minSpeed {
let percentage = Int ( ( 100 * ( value - self . fan . minSpeed ) ) / ( self . fan . maxSpeed - self . fan . minSpeed ) )
NotificationCenter . default . post ( name : . syncFansControl , object : nil , userInfo : [ " percentage " : percentage ] )
} else {
NotificationCenter . default . post ( name : . syncFansControl , object : nil , userInfo : [ " speed " : Int ( value ) ] )
}
2022-06-28 18:28:09 +02:00
}
2021-12-11 13:54:14 +01:00
}
@objc func setMin ( _ sender : NSButton ) {
self . slider ? . doubleValue = self . fan . minSpeed
self . maxBtn ? . state = . off
self . setSpeed ( value : Int ( self . fan . minSpeed ) )
2022-06-28 18:28:09 +02:00
NotificationCenter . default . post ( name : . syncFansControl , object : nil , userInfo : [ " speed " : Int ( self . fan . minSpeed ) ] )
2021-12-11 13:54:14 +01:00
}
@objc func setMax ( _ sender : NSButton ) {
self . slider ? . doubleValue = self . fan . maxSpeed
self . minBtn ? . state = . off
self . setSpeed ( value : Int ( self . fan . maxSpeed ) )
2022-06-28 18:28:09 +02:00
NotificationCenter . default . post ( name : . syncFansControl , object : nil , userInfo : [ " speed " : Int ( self . fan . maxSpeed ) ] )
2021-12-11 13:54:14 +01:00
}
@objc private func wakeListener ( aNotification : NSNotification ) {
self . resetModeAfterSleep = true
2022-08-16 22:47:17 +02:00
if self . speedState {
if let mode = self . willSleepMode , let speed = self . willSleepSpeed {
2023-04-28 21:03:32 +02:00
DispatchQueue . main . asyncAfter ( deadline : . now ( ) + 3 ) {
SMCHelper . shared . setFanMode ( self . fan . id , mode : mode . rawValue )
self . modeButtons ? . setMode ( mode )
if mode != . automatic {
self . setSpeed ( value : speed , then : {
DispatchQueue . main . async {
self . sliderValueField ? . textColor = . systemBlue
}
} )
}
2022-08-16 22:47:17 +02:00
}
}
self . willSleepMode = nil
self . willSleepSpeed = nil
}
2022-04-26 07:36:31 +02:00
if let value = self . fan . customSpeed , self . fan . mode != . automatic {
2023-04-28 21:03:32 +02:00
self . setSpeed ( value : value , then : {
DispatchQueue . main . async {
self . sliderValueField ? . textColor = . systemBlue
}
} )
2022-04-21 22:58:28 +02:00
}
2021-12-11 13:54:14 +01:00
}
2022-08-16 22:47:17 +02:00
@objc private func sleepListener ( aNotification : NSNotification ) {
2022-11-18 22:51:34 +01:00
guard SMCHelper . shared . isActive ( ) && self . fan . customMode != . automatic else { return }
2022-08-26 11:34:00 +02:00
2022-08-16 22:47:17 +02:00
self . willSleepMode = self . fan . customMode
self . willSleepSpeed = self . fan . customSpeed
SMCHelper . shared . setFanMode ( fan . id , mode : FanMode . automatic . rawValue )
self . modeButtons ? . setMode ( . automatic )
}
2022-06-28 18:28:09 +02:00
@objc private func syncFanSpeed ( _ notification : Notification ) {
2023-05-02 17:56:14 +02:00
guard self . syncState else { return }
2023-04-21 17:25:50 +02:00
var speed = notification . userInfo ? [ " speed " ] as ? Int
if let percentage = notification . userInfo ? [ " percentage " ] as ? Int {
speed = ( ( Int ( self . fan . maxSpeed - self . fan . minSpeed ) * percentage ) / 100 ) + Int ( self . fan . minSpeed )
2022-06-28 18:28:09 +02:00
}
2023-04-21 17:25:50 +02:00
guard let speed , self . fan . customSpeed != speed else { return }
2022-06-28 18:28:09 +02:00
let slider = NSSlider ( )
slider . tag = 4
slider . maxValue = 30000
slider . intValue = Int32 ( speed )
self . sliderCallback ( slider )
}
2021-12-11 13:54:14 +01:00
public func update ( _ value : Fan ) {
DispatchQueue . main . async ( execute : {
if ( self . window ? . isVisible ? ? false ) || ! self . ready {
self . fan . value = value . value
2023-05-05 20:12:41 +02:00
var newValue = " "
2022-03-07 18:28:52 +01:00
if value . value != 1 {
if self . fan . maxSpeed = = 1 || self . fan . maxSpeed = = 0 {
2023-05-05 20:12:41 +02:00
newValue = " \( Int ( value . value ) ) RPM "
2022-03-07 18:28:52 +01:00
} else {
2023-05-05 20:12:41 +02:00
newValue = self . fanValue = = . percentage ? " \( value . percentage ) % " : value . formattedValue
2022-03-07 18:28:52 +01:00
}
2021-12-11 13:54:14 +01:00
}
2023-05-05 20:12:41 +02:00
self . valueField ? . stringValue = newValue
2022-01-15 11:49:45 +01:00
self . valueField ? . toolTip = value . formattedValue
2021-12-11 13:54:14 +01:00
2023-05-23 17:53:48 +02:00
if let v = self . barView {
let width : CGFloat = ( 80 * CGFloat ( value . percentage < 0 ? 0 : value . percentage ) ) / 100
v . setFrameSize ( NSSize ( width : width , height : v . frame . height ) )
}
2021-12-11 13:54:14 +01:00
if self . resetModeAfterSleep && value . mode != . automatic {
if self . sliderValueField ? . stringValue != " " && self . slider ? . doubleValue != value . value {
self . slider ? . doubleValue = value . value
self . sliderValueField ? . stringValue = " "
2020-06-23 00:03:00 +02:00
}
2022-04-21 22:58:28 +02:00
self . modeButtons ? . setMode ( . forced )
2021-12-11 13:54:14 +01:00
self . resetModeAfterSleep = false
2020-11-26 15:39:36 +01:00
}
2021-12-11 13:54:14 +01:00
self . ready = true
2020-06-23 00:03:00 +02:00
}
2020-11-26 15:39:36 +01:00
} )
2020-06-23 00:03:00 +02:00
}
2022-12-10 09:27:00 +01:00
@objc private func installHelper ( _ sender : NSButton ) {
SMCHelper . shared . install { status in
NotificationCenter . default . post ( name : . fanHelperState , object : nil , userInfo : [ " state " : status ] )
}
}
2023-05-02 17:56:14 +02:00
private func setupControls ( _ isInstalled : Bool ? = nil ) {
let helperState = isInstalled ? ? SMCHelper . shared . isInstalled
if ! self . controlState {
2022-12-10 09:27:00 +01:00
self . helperView ? . removeFromSuperview ( )
self . controlView ? . removeFromSuperview ( )
2023-05-02 17:56:14 +02:00
self . buttonsView ? . removeFromSuperview ( )
} else {
if helperState {
self . helperView ? . removeFromSuperview ( )
if self . fan . maxSpeed != self . fan . minSpeed , let v = self . buttonsView {
self . addArrangedSubview ( v )
}
if self . fan . mode = = . forced , let v = self . controlView {
self . addArrangedSubview ( v )
}
} else {
self . buttonsView ? . removeFromSuperview ( )
self . controlView ? . removeFromSuperview ( )
if let v = self . helperView {
self . addArrangedSubview ( v )
}
2022-12-10 09:27:00 +01:00
}
}
let h = self . arrangedSubviews . map ( { $0 . bounds . height } ) . reduce ( 0 , + )
self . setFrameSize ( NSSize ( width : self . frame . width , height : h + self . horizontalMargin ) )
self . sizeCallback ( )
}
@objc private func changeHelperState ( _ notification : Notification ) {
2022-12-23 11:46:33 +01:00
guard let state = notification . userInfo ? [ " state " ] as ? Bool else { return }
2022-12-10 09:27:00 +01:00
self . setupControls ( state )
}
2023-05-02 17:56:14 +02:00
@objc private func controlCallback ( _ notification : Notification ) {
guard let state = notification . userInfo ? [ " state " ] as ? Bool else { return }
self . controlState = state
self . setupControls ( )
}
2020-06-23 00:03:00 +02:00
}
2021-12-11 13:54:14 +01:00
private class ModeButtons : NSStackView {
public var callback : ( FanMode ) -> Void = { _ in }
public var turbo : ( ) -> Void = { }
2023-08-04 12:09:53 +02:00
public var off : ( ) -> Void = { }
2021-12-11 13:54:14 +01:00
2022-06-27 19:15:48 +02:00
private var fansSyncState : Bool {
2022-11-22 16:46:49 +01:00
Store . shared . bool ( key : " Sensors_fansSync " , defaultValue : false )
2022-06-27 19:15:48 +02:00
}
2023-08-04 12:09:53 +02:00
private var offBtn : NSButton
2021-12-11 13:54:14 +01:00
private var autoBtn : NSButton = NSButton ( title : localizedString ( " Automatic " ) , target : nil , action : #selector ( autoMode ) )
private var manualBtn : NSButton = NSButton ( title : localizedString ( " Manual " ) , target : nil , action : #selector ( manualMode ) )
2023-08-04 12:09:53 +02:00
private var turboBtn : NSButton
2021-12-11 13:54:14 +01:00
public init ( frame : NSRect , mode : FanMode ) {
2023-08-04 12:09:53 +02:00
var turboIcon : NSImage = NSImage ( named : NSImage . Name ( " ac_unit " ) ) !
var offIcon : NSImage = NSImage ( named : NSImage . Name ( " ac_unit " ) ) !
if #available ( macOS 12.0 , * ) {
if let icon = iconFromSymbol ( name : " snowflake " , scale : . large ) {
turboIcon = icon
}
if let icon = iconFromSymbol ( name : " fanblades.slash " , scale : . medium ) {
offIcon = icon
}
}
self . offBtn = NSButton ( image : offIcon , target : nil , action : #selector ( offMode ) )
self . turboBtn = NSButton ( image : turboIcon , target : nil , action : #selector ( turboMode ) )
2021-12-11 13:54:14 +01:00
super . init ( frame : frame )
self . orientation = . horizontal
self . alignment = . centerY
self . distribution = . fillProportionally
self . spacing = 0
self . wantsLayer = true
self . layer ? . cornerRadius = 3
self . layer ? . borderWidth = 1
self . layer ? . borderColor = NSColor . lightGray . cgColor
let modes : NSStackView = NSStackView ( frame : NSRect ( x : 0 , y : 0 , width : self . frame . width , height : self . frame . height ) )
modes . orientation = . horizontal
modes . alignment = . centerY
modes . distribution = . fillEqually
self . autoBtn . setButtonType ( . toggle )
self . autoBtn . isBordered = false
self . autoBtn . target = self
self . autoBtn . state = mode = = . automatic ? . on : . off
self . manualBtn . setButtonType ( . toggle )
self . manualBtn . isBordered = false
self . manualBtn . target = self
self . manualBtn . state = mode = = . forced ? . on : . off
modes . addArrangedSubview ( self . autoBtn )
modes . addArrangedSubview ( self . manualBtn )
2023-08-04 12:09:53 +02:00
self . offBtn . setButtonType ( . toggle )
self . offBtn . isBordered = false
self . offBtn . target = self
2021-12-11 13:54:14 +01:00
self . turboBtn . setButtonType ( . toggle )
self . turboBtn . isBordered = false
self . turboBtn . target = self
NSLayoutConstraint . activate ( [
2023-08-04 12:09:53 +02:00
self . offBtn . widthAnchor . constraint ( equalToConstant : 26 ) ,
self . offBtn . heightAnchor . constraint ( equalToConstant : self . frame . height ) ,
2021-12-11 13:54:14 +01:00
self . turboBtn . widthAnchor . constraint ( equalToConstant : 26 ) ,
self . turboBtn . heightAnchor . constraint ( equalToConstant : self . frame . height ) ,
modes . heightAnchor . constraint ( equalToConstant : self . frame . height )
] )
self . addArrangedSubview ( modes )
2023-08-04 12:09:53 +02:00
self . addArrangedSubview ( self . offBtn )
2021-12-11 13:54:14 +01:00
self . addArrangedSubview ( self . turboBtn )
2022-06-27 19:15:48 +02:00
NotificationCenter . default . addObserver ( self , selector : #selector ( syncFanMode ) , name : . syncFansControl , object : nil )
2021-12-11 13:54:14 +01:00
}
required init ? ( coder : NSCoder ) {
fatalError ( " init(coder:) has not been implemented " )
}
2022-06-27 19:15:48 +02:00
deinit {
NotificationCenter . default . removeObserver ( self )
}
2021-12-11 13:54:14 +01:00
@objc private func autoMode ( _ sender : NSButton ) {
if sender . state . rawValue = = 0 {
self . autoBtn . state = . on
return
}
self . manualBtn . state = . off
2023-08-04 12:09:53 +02:00
self . offBtn . state = . off
2021-12-11 13:54:14 +01:00
self . turboBtn . state = . off
self . callback ( . automatic )
2022-06-27 19:15:48 +02:00
NotificationCenter . default . post ( name : . syncFansControl , object : nil , userInfo : [ " mode " : " automatic " ] )
2021-12-11 13:54:14 +01:00
}
@objc private func manualMode ( _ sender : NSButton ) {
if sender . state . rawValue = = 0 {
self . manualBtn . state = . on
return
}
self . autoBtn . state = . off
2023-08-04 12:09:53 +02:00
self . offBtn . state = . off
2021-12-11 13:54:14 +01:00
self . turboBtn . state = . off
self . callback ( . forced )
2022-06-27 19:15:48 +02:00
NotificationCenter . default . post ( name : . syncFansControl , object : nil , userInfo : [ " mode " : " forced " ] )
2021-12-11 13:54:14 +01:00
}
2023-08-04 12:09:53 +02:00
@objc private func offMode ( _ sender : NSButton ) {
if sender . state . rawValue = = 0 {
2023-08-15 13:45:06 +02:00
self . offBtn . state = . on
2023-08-04 12:09:53 +02:00
return
}
2023-08-15 13:45:06 +02:00
if ! Store . shared . bool ( key : " Sensors_turnOffFanAlert " , defaultValue : false ) {
let alert = NSAlert ( )
alert . messageText = localizedString ( " Turn off fan " )
2023-08-19 09:39:18 +02:00
alert . informativeText = localizedString ( " You are going to turn off the fan. This is not recommended action that can damage your mac, are you sure you want to do that? " )
2023-08-15 13:45:06 +02:00
alert . showsSuppressionButton = true
alert . addButton ( withTitle : localizedString ( " Turn off " ) )
alert . addButton ( withTitle : localizedString ( " Cancel " ) )
if alert . runModal ( ) = = . alertFirstButtonReturn {
if let suppressionButton = alert . suppressionButton , suppressionButton . state = = . on {
Store . shared . set ( key : " Sensors_turnOffFanAlert " , value : true )
}
self . toggleOffMode ( sender )
} else {
self . offBtn . state = . off
}
} else {
self . toggleOffMode ( sender )
}
}
private func toggleOffMode ( _ sender : NSButton ) {
2023-08-04 12:09:53 +02:00
self . manualBtn . state = . off
self . autoBtn . state = . off
self . offBtn . state = . on
self . turboBtn . state = . off
self . off ( )
if sender . tag != 4 {
NotificationCenter . default . post ( name : . syncFansControl , object : nil , userInfo : [ " mode " : " off " ] )
}
}
2021-12-11 13:54:14 +01:00
@objc private func turboMode ( _ sender : NSButton ) {
if sender . state . rawValue = = 0 {
self . turboBtn . state = . on
return
}
self . manualBtn . state = . off
self . autoBtn . state = . off
2023-08-04 12:09:53 +02:00
self . offBtn . state = . off
2022-06-27 19:15:48 +02:00
self . turboBtn . state = . on
2021-12-11 13:54:14 +01:00
self . turbo ( )
2022-06-27 19:15:48 +02:00
2022-06-28 18:28:09 +02:00
if sender . tag != 4 {
2022-06-27 19:15:48 +02:00
NotificationCenter . default . post ( name : . syncFansControl , object : nil , userInfo : [ " mode " : " turbo " ] )
}
}
@objc private func syncFanMode ( _ notification : Notification ) {
guard let mode = notification . userInfo ? [ " mode " ] as ? String , self . fansSyncState else {
return
}
if mode = = " automatic " {
self . setMode ( . automatic )
} else if mode = = " forced " {
self . setMode ( . forced )
2023-08-04 12:09:53 +02:00
} else if mode = = " off " {
let btn = NSButton ( )
btn . state = . on
btn . tag = 4
self . offMode ( btn )
2022-06-27 19:15:48 +02:00
} else if mode = = " turbo " {
let btn = NSButton ( )
btn . state = . on
2022-06-28 18:28:09 +02:00
btn . tag = 4
2022-06-27 19:15:48 +02:00
self . turboMode ( btn )
}
2021-12-11 13:54:14 +01:00
}
2022-04-21 22:58:28 +02:00
public func setMode ( _ mode : FanMode ) {
if mode = = . automatic {
self . autoBtn . state = . on
self . manualBtn . state = . off
2023-08-04 12:09:53 +02:00
self . offBtn . state = . off
2022-04-21 22:58:28 +02:00
self . turboBtn . state = . off
self . callback ( . automatic )
} else if mode = = . forced {
self . manualBtn . state = . on
self . autoBtn . state = . off
2023-08-04 12:09:53 +02:00
self . offBtn . state = . off
2022-04-21 22:58:28 +02:00
self . turboBtn . state = . off
self . callback ( . forced )
}
2021-12-11 13:54:14 +01:00
}
}