Detailed changes
@@ -43,8 +43,8 @@ use crate::{
Platform, PlatformDisplay, PlatformKeyboardLayout, PlatformKeyboardMapper, Point, Priority,
PromptBuilder, PromptButton, PromptHandle, PromptLevel, Render, RenderImage,
RenderablePromptHandle, Reservation, ScreenCaptureSource, SharedString, SubscriberSet,
- Subscription, SvgRenderer, Task, TextRenderingMode, TextSystem, Window, WindowAppearance,
- WindowHandle, WindowId, WindowInvalidator,
+ Subscription, SvgRenderer, Task, TextRenderingMode, TextSystem, ThermalState, Window,
+ WindowAppearance, WindowHandle, WindowId, WindowInvalidator,
colors::{Colors, GlobalColors},
current_platform, hash, init_app_menus,
};
@@ -618,6 +618,7 @@ pub struct App {
pub(crate) keystroke_observers: SubscriberSet<(), KeystrokeObserver>,
pub(crate) keystroke_interceptors: SubscriberSet<(), KeystrokeObserver>,
pub(crate) keyboard_layout_observers: SubscriberSet<(), Handler>,
+ pub(crate) thermal_state_observers: SubscriberSet<(), Handler>,
pub(crate) release_listeners: SubscriberSet<EntityId, ReleaseListener>,
pub(crate) global_observers: SubscriberSet<TypeId, Handler>,
pub(crate) quit_observers: SubscriberSet<(), QuitHandler>,
@@ -702,6 +703,7 @@ impl App {
keystroke_observers: SubscriberSet::new(),
keystroke_interceptors: SubscriberSet::new(),
keyboard_layout_observers: SubscriberSet::new(),
+ thermal_state_observers: SubscriberSet::new(),
global_observers: SubscriberSet::new(),
quit_observers: SubscriberSet::new(),
restart_observers: SubscriberSet::new(),
@@ -740,6 +742,18 @@ impl App {
}
}));
+ platform.on_thermal_state_change(Box::new({
+ let app = Rc::downgrade(&app);
+ move || {
+ if let Some(app) = app.upgrade() {
+ let cx = &mut app.borrow_mut();
+ cx.thermal_state_observers
+ .clone()
+ .retain(&(), move |callback| (callback)(cx));
+ }
+ }
+ }));
+
platform.on_quit(Box::new({
let cx = app.clone();
move || {
@@ -1082,6 +1096,27 @@ impl App {
.cloned()
}
+ /// Returns the current thermal state of the system.
+ pub fn thermal_state(&self) -> ThermalState {
+ self.platform.thermal_state()
+ }
+
+ /// Invokes a handler when the thermal state changes
+ pub fn on_thermal_state_change<F>(&self, mut callback: F) -> Subscription
+ where
+ F: 'static + FnMut(&mut App),
+ {
+ let (subscription, activate) = self.thermal_state_observers.insert(
+ (),
+ Box::new(move |cx| {
+ callback(cx);
+ true
+ }),
+ );
+ activate();
+ subscription
+ }
+
/// Returns the appearance of the application's windows.
pub fn window_appearance(&self) -> WindowAppearance {
self.platform.window_appearance()
@@ -261,6 +261,9 @@ pub(crate) trait Platform: 'static {
fn on_will_open_app_menu(&self, callback: Box<dyn FnMut()>);
fn on_validate_app_menu_command(&self, callback: Box<dyn FnMut(&dyn Action) -> bool>);
+ fn thermal_state(&self) -> ThermalState;
+ fn on_thermal_state_change(&self, callback: Box<dyn FnMut()>);
+
fn compositor_name(&self) -> &'static str {
""
}
@@ -323,6 +326,19 @@ pub trait PlatformDisplay: Send + Sync + Debug {
}
}
+/// Thermal state of the system
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum ThermalState {
+ /// System has no thermal constraints
+ Nominal,
+ /// System is slightly constrained, reduce discretionary work
+ Fair,
+ /// System is moderately constrained, reduce CPU/GPU intensive work
+ Serious,
+ /// System is critically constrained, minimize all resource usage
+ Critical,
+}
+
/// Metadata for a given [ScreenCaptureSource]
#[derive(Clone)]
pub struct SourceMetadata {
@@ -26,7 +26,7 @@ use crate::{
ForegroundExecutor, Keymap, LinuxDispatcher, Menu, MenuItem, OwnedMenu, PathPromptOptions,
Pixels, Platform, PlatformDisplay, PlatformKeyboardLayout, PlatformKeyboardMapper,
PlatformTextSystem, PlatformWindow, Point, PriorityQueueCalloopReceiver, Result,
- RunnableVariant, Task, WindowAppearance, WindowParams, px,
+ RunnableVariant, Task, ThermalState, WindowAppearance, WindowParams, px,
};
#[cfg(any(feature = "wayland", feature = "x11"))]
@@ -203,6 +203,12 @@ impl<P: LinuxClient + 'static> Platform for P {
self.with_common(|common| common.callbacks.keyboard_layout_change = Some(callback));
}
+ fn on_thermal_state_change(&self, _callback: Box<dyn FnMut()>) {}
+
+ fn thermal_state(&self) -> ThermalState {
+ ThermalState::Nominal
+ }
+
fn run(&self, on_finish_launching: Box<dyn FnOnce()>) {
on_finish_launching();
@@ -5,8 +5,8 @@ use crate::{
Action, AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, ForegroundExecutor,
KeyContext, Keymap, MacDispatcher, MacDisplay, MacWindow, Menu, MenuItem, OsMenu, OwnedMenu,
PathPromptOptions, Platform, PlatformDisplay, PlatformKeyboardLayout, PlatformKeyboardMapper,
- PlatformTextSystem, PlatformWindow, Result, SystemMenuType, Task, WindowAppearance,
- WindowParams, platform::mac::pasteboard::Pasteboard,
+ PlatformTextSystem, PlatformWindow, Result, SystemMenuType, Task, ThermalState,
+ WindowAppearance, WindowParams, platform::mac::pasteboard::Pasteboard,
};
use anyhow::{Context as _, anyhow};
use block::ConcreteBlock;
@@ -144,6 +144,11 @@ unsafe fn build_classes() {
on_keyboard_layout_change as extern "C" fn(&mut Object, Sel, id),
);
+ decl.add_method(
+ sel!(onThermalStateChange:),
+ on_thermal_state_change as extern "C" fn(&mut Object, Sel, id),
+ );
+
decl.register()
}
}
@@ -161,6 +166,7 @@ pub(crate) struct MacPlatformState {
find_pasteboard: Pasteboard,
reopen: Option<Box<dyn FnMut()>>,
on_keyboard_layout_change: Option<Box<dyn FnMut()>>,
+ on_thermal_state_change: Option<Box<dyn FnMut()>>,
quit: Option<Box<dyn FnMut()>>,
menu_command: Option<Box<dyn FnMut(&dyn Action)>>,
validate_menu_command: Option<Box<dyn FnMut(&dyn Action) -> bool>>,
@@ -204,6 +210,7 @@ impl MacPlatform {
finish_launching: None,
dock_menu: None,
on_keyboard_layout_change: None,
+ on_thermal_state_change: None,
menus: None,
keyboard_mapper,
}))
@@ -877,6 +884,24 @@ impl Platform for MacPlatform {
self.0.lock().validate_menu_command = Some(callback);
}
+ fn on_thermal_state_change(&self, callback: Box<dyn FnMut()>) {
+ self.0.lock().on_thermal_state_change = Some(callback);
+ }
+
+ fn thermal_state(&self) -> ThermalState {
+ unsafe {
+ let process_info: id = msg_send![class!(NSProcessInfo), processInfo];
+ let state: NSInteger = msg_send![process_info, thermalState];
+ match state {
+ 0 => ThermalState::Nominal,
+ 1 => ThermalState::Fair,
+ 2 => ThermalState::Serious,
+ 3 => ThermalState::Critical,
+ _ => ThermalState::Nominal,
+ }
+ }
+ }
+
fn keyboard_layout(&self) -> Box<dyn PlatformKeyboardLayout> {
Box::new(MacKeyboardLayout::new())
}
@@ -1178,6 +1203,14 @@ extern "C" fn did_finish_launching(this: &mut Object, _: Sel, _: id) {
object: nil
];
+ let thermal_name = ns_string("NSProcessInfoThermalStateDidChangeNotification");
+ let process_info: id = msg_send![class!(NSProcessInfo), processInfo];
+ let _: () = msg_send![notification_center, addObserver: this as id
+ selector: sel!(onThermalStateChange:)
+ name: thermal_name
+ object: process_info
+ ];
+
let platform = get_mac_platform(this);
let callback = platform.0.lock().finish_launching.take();
if let Some(callback) = callback {
@@ -1224,6 +1257,20 @@ extern "C" fn on_keyboard_layout_change(this: &mut Object, _: Sel, _: id) {
}
}
+extern "C" fn on_thermal_state_change(this: &mut Object, _: Sel, _: id) {
+ let platform = unsafe { get_mac_platform(this) };
+ let mut lock = platform.0.lock();
+ if let Some(mut callback) = lock.on_thermal_state_change.take() {
+ drop(lock);
+ callback();
+ platform
+ .0
+ .lock()
+ .on_thermal_state_change
+ .get_or_insert(callback);
+ }
+}
+
extern "C" fn open_urls(this: &mut Object, _: Sel, _: id, urls: id) {
let urls = unsafe {
(0..urls.count())
@@ -3,7 +3,7 @@ use crate::{
DummyKeyboardMapper, ForegroundExecutor, Keymap, NoopTextSystem, Platform, PlatformDisplay,
PlatformKeyboardLayout, PlatformKeyboardMapper, PlatformTextSystem, PromptButton,
ScreenCaptureFrame, ScreenCaptureSource, ScreenCaptureStream, SourceMetadata, Task,
- TestDisplay, TestWindow, WindowAppearance, WindowParams, size,
+ TestDisplay, TestWindow, ThermalState, WindowAppearance, WindowParams, size,
};
use anyhow::Result;
use collections::VecDeque;
@@ -246,6 +246,12 @@ impl Platform for TestPlatform {
fn on_keyboard_layout_change(&self, _: Box<dyn FnMut()>) {}
+ fn on_thermal_state_change(&self, _: Box<dyn FnMut()>) {}
+
+ fn thermal_state(&self) -> ThermalState {
+ ThermalState::Nominal
+ }
+
fn run(&self, _on_finish_launching: Box<dyn FnOnce()>) {
unimplemented!()
}
@@ -250,4 +250,10 @@ impl Platform for VisualTestPlatform {
}
fn on_keyboard_layout_change(&self, _callback: Box<dyn FnMut()>) {}
+
+ fn thermal_state(&self) -> super::ThermalState {
+ super::ThermalState::Nominal
+ }
+
+ fn on_thermal_state_change(&self, _callback: Box<dyn FnMut()>) {}
}
@@ -394,6 +394,12 @@ impl Platform for WindowsPlatform {
.set(Some(callback));
}
+ fn on_thermal_state_change(&self, _callback: Box<dyn FnMut()>) {}
+
+ fn thermal_state(&self) -> ThermalState {
+ ThermalState::Nominal
+ }
+
fn run(&self, on_finish_launching: Box<dyn 'static + FnOnce()>) {
on_finish_launching();
if !self.headless {
@@ -14,7 +14,7 @@ use crate::{
Replay, ResizeEdge, SMOOTH_SVG_SCALE_FACTOR, SUBPIXEL_VARIANTS_X, SUBPIXEL_VARIANTS_Y,
ScaledPixels, Scene, Shadow, SharedString, Size, StrikethroughStyle, Style, SubpixelSprite,
SubscriberSet, Subscription, SystemWindowTab, SystemWindowTabController, TabStopMap,
- TaffyLayoutEngine, Task, TextRenderingMode, TextStyle, TextStyleRefinement,
+ TaffyLayoutEngine, Task, TextRenderingMode, TextStyle, TextStyleRefinement, ThermalState,
TransformationMatrix, Underline, UnderlineStyle, WindowAppearance, WindowBackgroundAppearance,
WindowBounds, WindowControls, WindowDecorations, WindowOptions, WindowParams, WindowTextSystem,
point, prelude::*, px, rems, size, transparent_black,
@@ -1155,6 +1155,7 @@ impl Window {
let needs_present = Rc::new(Cell::new(false));
let next_frame_callbacks: Rc<RefCell<Vec<FrameCallback>>> = Default::default();
let input_rate_tracker = Rc::new(RefCell::new(InputRateTracker::default()));
+ let last_frame_time = Rc::new(Cell::new(None));
platform_window
.request_decorations(window_decorations.unwrap_or(WindowDecorations::Server));
@@ -1184,6 +1185,20 @@ impl Window {
let next_frame_callbacks = next_frame_callbacks.clone();
let input_rate_tracker = input_rate_tracker.clone();
move |request_frame_options| {
+ let thermal_state = cx.update(|cx| cx.thermal_state());
+
+ if thermal_state == ThermalState::Serious || thermal_state == ThermalState::Critical
+ {
+ let now = Instant::now();
+ let last_frame_time = last_frame_time.replace(Some(now));
+
+ if let Some(last_frame) = last_frame_time
+ && now.duration_since(last_frame) < Duration::from_micros(16667)
+ {
+ return;
+ }
+ }
+
let next_frame_callbacks = next_frame_callbacks.take();
if !next_frame_callbacks.is_empty() {
handle