mirror of
https://github.com/storytold/effectcraft.git
synced 2026-10-14 19:27:05 -04:00
control: keep channel responsive when window is covered (disable App Nap); fail fast on occluded screenshots
Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
9f0cfb3373
commit
214345df13
4 files changed
+31
No files matched your search
Generated
+8
@@ -1048,6 +1048,7 @@ dependencies = [
|
||||
"effectcraft-ui-egui",
|
||||
"eframe",
|
||||
"egui",
|
||||
"objc2-foundation 0.3.2",
|
||||
"rfd",
|
||||
"serde_json",
|
||||
"winit",
|
||||
@@ -5465,6 +5466,13 @@ version = "0.8.29"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e450f9b2ed1dff33c94c12589a87338689467b9c4f5d8a5710bd09a847d2c8a7"
|
||||
|
||||
[[package]]
|
||||
name = "xtask"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "yoke"
|
||||
version = "0.8.3"
|
||||
|
||||
@@ -21,3 +21,5 @@ rfd = { workspace = true }
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
# Same winit as eframe: macOS event-loop options (no app activation when driven by an agent).
|
||||
winit = { version = "0.30", default-features = false }
|
||||
# Keep the control channel responsive when the window is covered (App Nap / occlusion throttling).
|
||||
objc2-foundation = { version = "0.3", default-features = false, features = ["std", "NSProcessInfo", "NSString", "NSObject"] }
|
||||
@@ -70,6 +70,7 @@ fn main() -> eframe::Result {
|
||||
app.hooks.pick_open_project =
|
||||
Some(Box::new(|| rfd::FileDialog::new().add_filter("EffectCraft Project", &["ecproj"]).pick_file().map(|p| p.to_string_lossy().to_string())));
|
||||
if let Some(port) = control_port {
|
||||
disable_app_nap();
|
||||
let rx = control_server::start(port, cc.egui_ctx.clone());
|
||||
app = app.with_control(rx);
|
||||
}
|
||||
@@ -91,3 +92,17 @@ fn agent_event_loop(agent: bool) -> Option<eframe::EventLoopBuilderHook> {
|
||||
let _ = agent;
|
||||
None
|
||||
}
|
||||
|
||||
/// Agents drive the app while its window is covered or on another Space. macOS App Nap would
|
||||
/// throttle the event loop then, stalling the control channel; opt out for `--control` runs.
|
||||
fn disable_app_nap() {
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
use objc2_foundation::{NSActivityOptions, NSProcessInfo, NSString};
|
||||
let info = NSProcessInfo::processInfo();
|
||||
let reason = NSString::from_str("EffectCraft control channel");
|
||||
let opts = NSActivityOptions::UserInitiatedAllowingIdleSystemSleep | NSActivityOptions::LatencyCritical;
|
||||
// Leaked on purpose: the activity lasts for the life of the process.
|
||||
std::mem::forget(info.beginActivityWithOptions_reason(opts, &reason));
|
||||
}
|
||||
}
|
||||
@@ -313,6 +313,12 @@ impl EffectcraftApp {
|
||||
}
|
||||
control::Outcome::AfterInput => self.input_waiters.push(reply),
|
||||
control::Outcome::Screenshot { path, crop } => {
|
||||
// A covered or minimized window presents no frames (macOS skips its redraws), so
|
||||
// a screenshot would never arrive and nothing would tick its timeout.
|
||||
if ctx.input(|i| i.viewport().occluded == Some(true) || i.viewport().minimized == Some(true)) {
|
||||
let _ = reply.send(json!({"ok": false, "error": "window is covered or minimized: call ui.focus first (render.frame renders comp pixels without the window)"}));
|
||||
continue;
|
||||
}
|
||||
let token = self.next_token;
|
||||
self.next_token += 1;
|
||||
let settle = now + 0.35;
|
||||
|
||||
Reference in new issue
Block a user