Browse Source

feat: add thermal states to powerMonitor (#37935)

* feat: add thermal states to powerMonitor

* update docs
Jeremy Rose 2 years ago
parent
commit
ba835ddac1

+ 29 - 1
docs/api/power-monitor.md

@@ -24,6 +24,30 @@ Emitted when the system changes to AC power.
 
 Emitted when system changes to battery power.
 
+### Event: 'thermal-state-change' _macOS_
+
+* `state` string - The system's new thermal state. Can be `unknown`, `nominal`, `fair`, `serious`, `critical`.
+
+Emitted when the thermal state of the system changes. Notification of a change
+in the thermal status of the system, such as entering a critical temperature
+range. Depending on the severity, the system might take steps to reduce said
+temperature, for example, throttling the CPU or switching on the fans if
+available.
+
+Apps may react to the new state by reducing expensive computing tasks (e.g.
+video encoding), or notifying the user. The same state might be received
+repeatedly.
+
+See https://developer.apple.com/library/archive/documentation/Performance/Conceptual/power_efficiency_guidelines_osx/RespondToThermalStateChanges.html
+
+### Event: 'speed-limit-change' _macOS_ _Windows_
+
+* `limit` number - The operating system's advertised speed limit for CPUs, in percent.
+
+Notification of a change in the operating system's advertised speed limit for
+CPUs, in percent. Values below 100 indicate that the system is impairing
+processing power due to thermal management.
+
 ### Event: 'shutdown' _Linux_ _macOS_
 
 Emitted when the system is about to reboot or shut down. If the event handler
@@ -55,7 +79,7 @@ The `powerMonitor` module has the following methods:
 
 * `idleThreshold` Integer
 
-Returns `string` - The system's current state. Can be `active`, `idle`, `locked` or `unknown`.
+Returns `string` - The system's current idle state. Can be `active`, `idle`, `locked` or `unknown`.
 
 Calculate the system idle state. `idleThreshold` is the amount of time (in seconds)
 before considered idle.  `locked` is available on supported systems only.
@@ -66,6 +90,10 @@ Returns `Integer` - Idle time in seconds
 
 Calculate system idle time in seconds.
 
+### `powerMonitor.getCurrentThermalState()` _macOS_
+
+Returns `string` - The system's current thermal state. Can be `unknown`, `nominal`, `fair`, `serious`, or `critical`.
+
 ### `powerMonitor.isOnBatteryPower()`
 
 Returns `boolean` - Whether the system is on battery power.

+ 5 - 0
lib/browser/api/power-monitor.ts

@@ -4,6 +4,7 @@ const {
   createPowerMonitor,
   getSystemIdleState,
   getSystemIdleTime,
+  getCurrentThermalState,
   isOnBatteryPower
 } = process._linkedBinding('electron_browser_power_monitor');
 
@@ -40,6 +41,10 @@ class PowerMonitor extends EventEmitter {
     return getSystemIdleState(idleThreshold);
   }
 
+  getCurrentThermalState () {
+    return getCurrentThermalState();
+  }
+
   getSystemIdleTime () {
     return getSystemIdleTime();
   }

+ 47 - 0
shell/browser/api/electron_api_power_monitor.cc

@@ -6,8 +6,11 @@
 
 #include "base/power_monitor/power_monitor.h"
 #include "base/power_monitor/power_monitor_device_source.h"
+#include "base/power_monitor/power_observer.h"
+#include "gin/data_object_builder.h"
 #include "gin/handle.h"
 #include "shell/browser/browser.h"
+#include "shell/browser/javascript_environment.h"
 #include "shell/common/gin_converters/callback_converter.h"
 #include "shell/common/gin_helper/dictionary.h"
 #include "shell/common/gin_helper/object_template_builder.h"
@@ -33,6 +36,26 @@ struct Converter<ui::IdleState> {
   }
 };
 
+template <>
+struct Converter<base::PowerThermalObserver::DeviceThermalState> {
+  static v8::Local<v8::Value> ToV8(
+      v8::Isolate* isolate,
+      const base::PowerThermalObserver::DeviceThermalState& in) {
+    switch (in) {
+      case base::PowerThermalObserver::DeviceThermalState::kUnknown:
+        return StringToV8(isolate, "unknown");
+      case base::PowerThermalObserver::DeviceThermalState::kNominal:
+        return StringToV8(isolate, "nominal");
+      case base::PowerThermalObserver::DeviceThermalState::kFair:
+        return StringToV8(isolate, "fair");
+      case base::PowerThermalObserver::DeviceThermalState::kSerious:
+        return StringToV8(isolate, "serious");
+      case base::PowerThermalObserver::DeviceThermalState::kCritical:
+        return StringToV8(isolate, "critical");
+    }
+  }
+};
+
 }  // namespace gin
 
 namespace electron::api {
@@ -47,6 +70,7 @@ PowerMonitor::PowerMonitor(v8::Isolate* isolate) {
 
   base::PowerMonitor::AddPowerStateObserver(this);
   base::PowerMonitor::AddPowerSuspendObserver(this);
+  base::PowerMonitor::AddPowerThermalObserver(this);
 
 #if BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN)
   InitPlatformSpecificMonitors();
@@ -56,6 +80,7 @@ PowerMonitor::PowerMonitor(v8::Isolate* isolate) {
 PowerMonitor::~PowerMonitor() {
   base::PowerMonitor::RemovePowerStateObserver(this);
   base::PowerMonitor::RemovePowerSuspendObserver(this);
+  base::PowerMonitor::RemovePowerThermalObserver(this);
 }
 
 bool PowerMonitor::ShouldShutdown() {
@@ -77,6 +102,22 @@ void PowerMonitor::OnResume() {
   Emit("resume");
 }
 
+void PowerMonitor::OnThermalStateChange(DeviceThermalState new_state) {
+  v8::Isolate* isolate = JavascriptEnvironment::GetIsolate();
+  v8::HandleScope scope(isolate);
+  EmitWithoutEvent(
+      "thermal-state-change",
+      gin::DataObjectBuilder(isolate).Set("state", new_state).Build());
+}
+
+void PowerMonitor::OnSpeedLimitChange(int speed_limit) {
+  v8::Isolate* isolate = JavascriptEnvironment::GetIsolate();
+  v8::HandleScope scope(isolate);
+  EmitWithoutEvent(
+      "speed-limit-change",
+      gin::DataObjectBuilder(isolate).Set("limit", speed_limit).Build());
+}
+
 #if BUILDFLAG(IS_LINUX)
 void PowerMonitor::SetListeningForShutdown(bool is_listening) {
   if (is_listening) {
@@ -137,6 +178,10 @@ bool IsOnBatteryPower() {
   return base::PowerMonitor::IsOnBatteryPower();
 }
 
+base::PowerThermalObserver::DeviceThermalState GetCurrentThermalState() {
+  return base::PowerMonitor::GetCurrentThermalState();
+}
+
 void Initialize(v8::Local<v8::Object> exports,
                 v8::Local<v8::Value> unused,
                 v8::Local<v8::Context> context,
@@ -147,6 +192,8 @@ void Initialize(v8::Local<v8::Object> exports,
                  base::BindRepeating(&PowerMonitor::Create));
   dict.SetMethod("getSystemIdleState",
                  base::BindRepeating(&GetSystemIdleState));
+  dict.SetMethod("getCurrentThermalState",
+                 base::BindRepeating(&GetCurrentThermalState));
   dict.SetMethod("getSystemIdleTime", base::BindRepeating(&GetSystemIdleTime));
   dict.SetMethod("isOnBatteryPower", base::BindRepeating(&IsOnBatteryPower));
 }

+ 6 - 1
shell/browser/api/electron_api_power_monitor.h

@@ -21,7 +21,8 @@ class PowerMonitor : public gin::Wrappable<PowerMonitor>,
                      public gin_helper::EventEmitterMixin<PowerMonitor>,
                      public gin_helper::Pinnable<PowerMonitor>,
                      public base::PowerStateObserver,
-                     public base::PowerSuspendObserver {
+                     public base::PowerSuspendObserver,
+                     public base::PowerThermalObserver {
  public:
   static v8::Local<v8::Value> Create(v8::Isolate* isolate);
 
@@ -57,6 +58,10 @@ class PowerMonitor : public gin::Wrappable<PowerMonitor>,
   void OnSuspend() override;
   void OnResume() override;
 
+  // base::PowerThermalObserver
+  void OnThermalStateChange(DeviceThermalState new_state) override;
+  void OnSpeedLimitChange(int speed_limit) override;
+
 #if BUILDFLAG(IS_WIN)
   // Static callback invoked when a message comes in to our messaging window.
   static LRESULT CALLBACK WndProcStatic(HWND hwnd,

+ 6 - 0
spec/api-power-monitor-spec.ts

@@ -178,6 +178,12 @@ describe('powerMonitor', () => {
       });
     });
 
+    describe('powerMonitor.getCurrentThermalState', () => {
+      it('returns a valid state', () => {
+        expect(powerMonitor.getCurrentThermalState()).to.be.oneOf(['unknown', 'nominal', 'fair', 'serious', 'critical']);
+      });
+    });
+
     describe('powerMonitor.onBatteryPower', () => {
       it('returns a boolean', () => {
         expect(powerMonitor.onBatteryPower).to.be.a('boolean');