Skip to content

Commit 4800e36

Browse files
fix(macos): prevent background timer suspension (#74)
* feat(macos): add objc2 dependencies and implement app nap and webview suspension handling - Introduced new dependencies for macOS: objc2, objc2-foundation, and objc2-web-kit. - Added modules for managing app nap and webview suspension specific to macOS. - Updated the main application logic to disable app nap and webview suspension when running on macOS. * fix(macos): use NSActivityUserInitiatedAllowingIdleSystemSleep instead of NSActivityBackground NSActivityBackground (0xFF) does not actually prevent App Nap — it marks work as discretionary. Switch to NSActivityUserInitiatedAllowingIdleSystemSleep which reliably prevents App Nap while still allowing system sleep. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 819b9bb commit 4800e36

5 files changed

Lines changed: 82 additions & 0 deletions

File tree

src-tauri/Cargo.lock

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src-tauri/Cargo.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,3 +37,8 @@ tauri-plugin-updater = "2"
3737
tauri-plugin-process = "2"
3838
tokio = { version = "1", features = ["rt-multi-thread", "macros"] }
3939
regex-lite = "0.1.9"
40+
41+
[target.'cfg(target_os = "macos")'.dependencies]
42+
objc2 = "0.6"
43+
objc2-foundation = { version = "0.3", features = ["NSProcessInfo", "NSString"] }
44+
objc2-web-kit = { version = "0.3", features = ["WKPreferences", "WKWebView", "WKWebViewConfiguration"] }

src-tauri/src/app_nap.rs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
//! App Nap prevention for macOS.
2+
//!
3+
//! macOS App Nap can suspend background processes to save energy.
4+
//! This module prevents that so timers and background work continue.
5+
6+
use std::sync::Once;
7+
8+
use objc2::msg_send;
9+
use objc2::rc::Retained;
10+
use objc2_foundation::{NSObject, NSProcessInfo, NSString};
11+
12+
static INIT: Once = Once::new();
13+
14+
/// Disables App Nap by starting a background activity.
15+
///
16+
/// Tells macOS that the app needs periodic background work and should not
17+
/// be suspended. The activity persists for the app's lifetime.
18+
pub fn disable_app_nap() {
19+
INIT.call_once(|| {
20+
unsafe {
21+
let process_info = NSProcessInfo::processInfo();
22+
// NSActivityUserInitiatedAllowingIdleSystemSleep (0x00EFFFFF)
23+
// Prevents App Nap but still allows the system to sleep per user preferences.
24+
// NSActivityBackground (0xFF) does NOT prevent App Nap.
25+
let options: u64 = 0x00FFFFFF & !(1u64 << 20);
26+
let reason = NSString::from_str("Periodic usage data refresh");
27+
let token: Retained<NSObject> = msg_send![
28+
&process_info,
29+
beginActivityWithOptions: options,
30+
reason: &*reason
31+
];
32+
// Intentionally leak -- the activity token must persist for the app's lifetime.
33+
// Dropping it would re-enable App Nap.
34+
std::mem::forget(token);
35+
log::info!("App Nap disabled via NSProcessInfo background activity");
36+
}
37+
});
38+
}

src-tauri/src/lib.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
1+
#[cfg(target_os = "macos")]
2+
mod app_nap;
13
mod panel;
24
mod plugin_engine;
35
mod tray;
6+
#[cfg(target_os = "macos")]
7+
mod webkit_config;
48

59
use std::collections::{HashMap, HashSet};
610
use tauri_plugin_aptabase::EventTracker;
@@ -284,6 +288,12 @@ pub fn run() {
284288
#[cfg(target_os = "macos")]
285289
app.set_activation_policy(tauri::ActivationPolicy::Accessory);
286290

291+
#[cfg(target_os = "macos")]
292+
{
293+
app_nap::disable_app_nap();
294+
webkit_config::disable_webview_suspension(app.handle());
295+
}
296+
287297
use tauri::Manager;
288298

289299
let version = app.package_info().version.to_string();

src-tauri/src/webkit_config.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
//! WebKit configuration for disabling background suspension on macOS.
2+
//!
3+
//! By default, WebKit suspends JavaScript execution when the webview is not visible.
4+
//! This module disables that behavior so auto-update timers continue to fire.
5+
6+
use tauri::Manager;
7+
8+
pub fn disable_webview_suspension(app_handle: &tauri::AppHandle) {
9+
let Some(window) = app_handle.get_webview_window("main") else {
10+
log::warn!("webkit_config: main window not found");
11+
return;
12+
};
13+
14+
if let Err(e) = window.with_webview(|webview| {
15+
unsafe {
16+
use objc2_web_kit::{WKInactiveSchedulingPolicy, WKWebView};
17+
let wk_webview: &WKWebView = &*webview.inner().cast();
18+
let config = wk_webview.configuration();
19+
let prefs = config.preferences();
20+
prefs.setInactiveSchedulingPolicy(WKInactiveSchedulingPolicy::None);
21+
log::info!("WebKit inactiveSchedulingPolicy set to None");
22+
}
23+
}) {
24+
log::warn!("Failed to configure WebKit scheduling: {e}");
25+
}
26+
}

0 commit comments

Comments
 (0)