diff --git a/crates/auto_update/src/lib.rs b/crates/auto_update/src/lib.rs index ce962806..92b4c63d 100644 --- a/crates/auto_update/src/lib.rs +++ b/crates/auto_update/src/lib.rs @@ -3,7 +3,7 @@ use std::sync::Arc; use std::sync::atomic::{AtomicBool, AtomicU64, Ordering}; -use gpui::{App, AppContext, Context, Entity, Global, SharedString, Task, Window}; +use gpui::{App, AppContext, Context, Entity, Global, SharedString, Task}; use gpui_updater_core::{EngineConfig, Release, UpdateEngine, UpdateStatus, Verification, Version}; use instant::Duration; @@ -35,7 +35,7 @@ fn uses_managed_updates() -> bool { } /// Initialize the auto-update system. -pub fn init(window: &mut Window, cx: &mut App) { +pub fn init(cx: &mut App) { if uses_managed_updates() { log::info!( "Skipping auto-update initialization: updates are managed by the installed distribution channel (Flatpak/Snap)" @@ -60,10 +60,7 @@ pub fn init(window: &mut Window, cx: &mut App) { return; }; - AutoUpdater::set_global( - cx.new(|cx| AutoUpdater::new(window, version, filter, cx)), - cx, - ); + AutoUpdater::set_global(cx.new(|cx| AutoUpdater::new(version, filter, cx)), cx); } struct GlobalAutoUpdater(Entity); @@ -103,21 +100,17 @@ impl AutoUpdater { cx.set_global(GlobalAutoUpdater(state)); } - fn new( - window: &mut Window, - version: Version, - filter: AssetFilter, - cx: &mut Context, - ) -> Self { + fn new(version: Version, filter: AssetFilter, cx: &mut Context) -> Self { + let entity = cx.entity().downgrade(); let source = GiteaSource::new(GITEA_API_BASE, GITEA_REPO_OWNER, GITEA_REPO_NAME, filter); let config = EngineConfig::new(version.clone()).verification(Verification::Checksum); let engine = Arc::new(UpdateEngine::new(source, config)); // Schedule an auto-check after a 2-minute delay - cx.defer_in(window, |_this, _window, cx| { - cx.spawn(async move |this, cx| { + cx.defer(move |cx| { + cx.spawn(async move |cx| { cx.background_executor().timer(AUTO_CHECK_DELAY).await; - this.update(cx, |this, cx| this.check(cx)).ok(); + entity.update(cx, |this, cx| this.check(cx)).ok(); }) .detach(); }); diff --git a/crates/chat/src/lib.rs b/crates/chat/src/lib.rs index e1dd9dca..d9b22462 100644 --- a/crates/chat/src/lib.rs +++ b/crates/chat/src/lib.rs @@ -26,8 +26,8 @@ pub use state::FileAttachment; /// A static keypair used only for signing locally-cached rumor events. static LOCAL_KEYS: LazyLock = LazyLock::new(Keys::generate); -pub fn init(window: &mut Window, cx: &mut App) { - ChatRegistry::set_global(cx.new(|cx| ChatRegistry::new(window, cx)), cx); +pub fn init(cx: &mut App) { + ChatRegistry::set_global(cx.new(ChatRegistry::new), cx); } struct GlobalChatRegistry(Entity); @@ -150,7 +150,8 @@ impl ChatRegistry { } /// Create a new chat registry instance - fn new(window: &mut Window, cx: &mut Context) -> Self { + fn new(cx: &mut Context) -> Self { + let entity = cx.entity().downgrade(); let nostr = NostrRegistry::global(cx); let (tx, rx) = flume::unbounded::(); let mut subscriptions = smallvec![]; @@ -167,9 +168,12 @@ impl ChatRegistry { }), ); - // Run at the end of the current cycle - cx.defer_in(window, |this, _window, cx| { - this.get_rooms(cx); + cx.defer(move |cx| { + entity + .update(cx, |this, cx| { + this.get_rooms(cx); + }) + .ok(); }); Self { diff --git a/crates/community/src/lib.rs b/crates/community/src/lib.rs index 6d387ca8..09fa81c2 100644 --- a/crates/community/src/lib.rs +++ b/crates/community/src/lib.rs @@ -4,7 +4,7 @@ use anyhow::Result; use concord::CommunityId; use concord::cord01::KIND_WRAP; use concord::store::CommunityState; -use gpui::{App, AppContext, Context, Entity, EventEmitter, Global, Subscription, Task, Window}; +use gpui::{App, AppContext, Context, Entity, EventEmitter, Global, Subscription, Task}; use nostr_sdk::prelude::*; use smallvec::{SmallVec, smallvec}; use state::NostrRegistry; @@ -15,8 +15,8 @@ mod sync; pub use community::*; pub use sync::*; -pub fn init(window: &mut Window, cx: &mut App) { - CommunityRegistry::set_global(cx.new(|cx| CommunityRegistry::new(window, cx)), cx); +pub fn init(cx: &mut App) { + CommunityRegistry::set_global(cx.new(CommunityRegistry::new), cx); } struct GlobalCommunityRegistry(Entity); @@ -56,7 +56,8 @@ impl CommunityRegistry { cx.set_global(GlobalCommunityRegistry(state)); } - fn new(window: &mut Window, cx: &mut Context) -> Self { + fn new(cx: &mut Context) -> Self { + let entity = cx.entity().downgrade(); let nostr = NostrRegistry::global(cx); let (tx, rx) = flume::bounded::(256); let mut subscriptions = smallvec![]; @@ -69,12 +70,15 @@ impl CommunityRegistry { } })); - cx.defer_in(window, move |this, _window, cx| { - this.handle_notifications(cx); - - if nostr.read(cx).current_user().is_some() { - this.load(cx); - } + cx.defer(move |cx| { + entity + .update(cx, |this, cx| { + this.handle_notifications(cx); + if nostr.read(cx).current_user().is_some() { + this.load(cx); + } + }) + .ok(); }); Self { diff --git a/crates/device/src/lib.rs b/crates/device/src/lib.rs index ddd2f34c..3bca1745 100644 --- a/crates/device/src/lib.rs +++ b/crates/device/src/lib.rs @@ -24,8 +24,8 @@ use ui::{Disableable, Sizable, StyledExt, WindowExtension, h_flex, v_flex}; const IDENTIFIER: &str = "coop:device"; -pub fn init(window: &mut Window, cx: &mut App) { - DeviceRegistry::set_global(cx.new(|cx| DeviceRegistry::new(window, cx)), cx); +pub fn init(cx: &mut App) { + DeviceRegistry::set_global(cx.new(DeviceRegistry::new), cx); } struct GlobalDeviceRegistry(Entity); @@ -89,7 +89,8 @@ impl DeviceRegistry { } /// Create a new device registry instance - fn new(window: &mut Window, cx: &mut Context) -> Self { + fn new(cx: &mut Context) -> Self { + let entity = cx.entity().downgrade(); let nostr = NostrRegistry::global(cx); let settings = AppSettings::global(cx); @@ -114,8 +115,10 @@ impl DeviceRegistry { }), ); - cx.defer_in(window, |this, window, cx| { - this.handle_notifications(window, cx); + cx.defer(move |cx| { + entity + .update(cx, |this, cx| this.handle_notifications(cx)) + .ok(); }); Self { @@ -127,7 +130,7 @@ impl DeviceRegistry { } } - fn handle_notifications(&mut self, window: &mut Window, cx: &mut Context) { + fn handle_notifications(&mut self, cx: &mut Context) { let nostr = NostrRegistry::global(cx); let client = nostr.read(cx).client(); let signer = nostr.read(cx).signer(); @@ -168,18 +171,18 @@ impl DeviceRegistry { Ok(()) })); - self.tasks.push(cx.spawn_in(window, async move |this, cx| { + self.tasks.push(cx.spawn(async move |this, cx| { while let Ok(event) = rx.recv_async().await { match event.kind { Kind::Custom(10044) => { - this.update_in(cx, |this, _window, cx| { + this.update(cx, |this, cx| { this.set_encryption(&event, cx); })?; } // New request event from other device Kind::Custom(4454) => { - this.update_in(cx, |this, window, cx| { - this.ask_for_approval(event, window, cx); + this.update(cx, |this, cx| { + this.ask_for_approval(event, cx); })?; } // New response event from the master device @@ -591,7 +594,7 @@ impl DeviceRegistry { } /// Handle encryption request - fn ask_for_approval(&mut self, event: Event, window: &mut Window, cx: &mut Context) { + fn ask_for_approval(&mut self, event: Event, cx: &mut Context) { // Ignore if there is already a pending request if self.pending_request { return; @@ -600,7 +603,18 @@ impl DeviceRegistry { // Show notification let notification = self.notification(event, cx); - window.push_notification(notification, cx); + + // The registry is global and not bound to a window, so surface the + // request in an open window. + if let Some(window) = cx.windows().first().copied() { + if let Err(error) = window.update(cx, |_view, window, cx| { + window.push_notification(notification, cx); + }) { + log::warn!("Failed to show encryption key request: {error}"); + } + } else { + log::warn!("Failed to show encryption key request: no open window"); + } } /// Build a notification for the encryption request. diff --git a/crates/person/src/lib.rs b/crates/person/src/lib.rs index 28e41e4a..c1aadd1b 100644 --- a/crates/person/src/lib.rs +++ b/crates/person/src/lib.rs @@ -3,7 +3,7 @@ use std::sync::RwLock; use anyhow::{Error, anyhow}; use common::EventExt; -use gpui::{App, AppContext, Context, Entity, Global, Task, Window}; +use gpui::{App, AppContext, Context, Entity, Global, Task}; use instant::Duration; use nostr_sdk::prelude::*; use smallvec::{SmallVec, smallvec}; @@ -13,8 +13,8 @@ mod person; pub use person::*; -pub fn init(window: &mut Window, cx: &mut App) { - PersonRegistry::set_global(cx.new(|cx| PersonRegistry::new(window, cx)), cx); +pub fn init(cx: &mut App) { + PersonRegistry::set_global(cx.new(PersonRegistry::new), cx); } struct GlobalPersonRegistry(Entity); @@ -56,7 +56,8 @@ impl PersonRegistry { } /// Create a new person registry instance - fn new(window: &mut Window, cx: &mut Context) -> Self { + fn new(cx: &mut Context) -> Self { + let entity = cx.entity().downgrade(); let nostr = NostrRegistry::global(cx); let client = nostr.read(cx).client(); @@ -96,8 +97,8 @@ impl PersonRegistry { })); // Load all user profiles from the database - cx.defer_in(window, |this, _window, cx| { - this.load(cx); + cx.defer(move |cx| { + entity.update(cx, |this, cx| this.load(cx)).ok(); }); Self { diff --git a/crates/settings/src/lib.rs b/crates/settings/src/lib.rs index dac56ba0..055f7e3c 100644 --- a/crates/settings/src/lib.rs +++ b/crates/settings/src/lib.rs @@ -8,8 +8,8 @@ use serde::{Deserialize, Serialize}; use smallvec::{SmallVec, smallvec}; use theme::{Theme, ThemeFamily, ThemeMode}; -pub fn init(window: &mut Window, cx: &mut App) { - AppSettings::set_global(cx.new(|cx| AppSettings::new(window, cx)), cx) +pub fn init(cx: &mut App) { + AppSettings::set_global(cx.new(AppSettings::new), cx) } const DEFAULT_FILE_SERVER: &str = "https://nostr.download/"; @@ -195,7 +195,8 @@ impl AppSettings { cx.set_global(GlobalAppSettings(state)); } - fn new(window: &mut Window, cx: &mut Context) -> Self { + fn new(cx: &mut Context) -> Self { + let entity = cx.entity().downgrade(); let inner = cx.new(|_| Settings::default()); let mut subscriptions = smallvec![]; @@ -207,8 +208,8 @@ impl AppSettings { ); // Run at the end of current cycle - cx.defer_in(window, |this, window, cx| { - this.load(window, cx); + cx.defer(move |cx| { + entity.update(cx, |this, cx| this.load(cx)).ok(); }); Self { @@ -226,7 +227,7 @@ impl AppSettings { } /// Load settings - fn load(&mut self, window: &mut Window, cx: &mut Context) { + fn load(&mut self, cx: &mut Context) { let task: Task> = cx.background_spawn(async move { #[cfg(not(target_arch = "wasm32"))] { @@ -238,7 +239,7 @@ impl AppSettings { Err(anyhow!("Not found")) }); - cx.spawn_in(window, async move |this, cx| { + cx.spawn(async move |this, cx| { let mut settings = task.await.unwrap_or(Settings::default()); // Move settings still pointed at the old default file server over to the new one @@ -247,9 +248,10 @@ impl AppSettings { } // Update settings - this.update_in(cx, |this, window, cx| { + this.update(cx, |this, cx| { this.set_settings(settings, cx); - this.apply_theme(window, cx); + this.apply_theme(None, cx); + cx.refresh_windows(); }) .ok(); }) @@ -281,7 +283,7 @@ impl AppSettings { }); // Apply the new theme - self.apply_theme(window, cx); + self.apply_theme(Some(window), cx); } /// Reset theme @@ -290,22 +292,22 @@ impl AppSettings { this.theme = None; cx.notify(); }); - self.apply_theme(window, cx); + self.apply_theme(Some(window), cx); } /// Apply theme - pub fn apply_theme(&mut self, window: &mut Window, cx: &mut Context) { + pub fn apply_theme(&mut self, mut window: Option<&mut Window>, cx: &mut Context) { if let Some(name) = self.inner.read(cx).theme.as_ref() { let mode = self.inner.read(cx).theme_mode; if let Ok(new_theme) = ThemeFamily::from_assets(name) { - Theme::apply_theme(Rc::new(new_theme), Some(window), cx); - Theme::change(mode, Some(window), cx); + Theme::apply_theme(Rc::new(new_theme), window.as_deref_mut(), cx); + Theme::change(mode, window, cx); } else { log::info!("Failed to load theme: {name}"); } } else { - Theme::apply_theme(Rc::new(ThemeFamily::default()), Some(window), cx); + Theme::apply_theme(Rc::new(ThemeFamily::default()), window, cx); } } diff --git a/crates/state/src/lib.rs b/crates/state/src/lib.rs index 4313d38d..8c11861f 100644 --- a/crates/state/src/lib.rs +++ b/crates/state/src/lib.rs @@ -4,7 +4,7 @@ use anyhow::{Error, anyhow}; #[cfg(not(target_arch = "wasm32"))] use browser_signer_proxy::prelude::*; use common::config_dir; -use gpui::{App, AppContext, Context, Entity, EventEmitter, Global, Task, Window}; +use gpui::{App, AppContext, Context, Entity, EventEmitter, Global, Task}; use gpui_tokio::Tokio; use instant::Duration; use nostr_connect::prelude::*; @@ -29,7 +29,7 @@ pub use nip4e::*; pub use nip05::*; pub use signer::{CoopAuthUrlHandler, UniversalSigner}; -pub fn init(window: &mut Window, cx: &mut App, cli_key: Option) { +pub fn init(cx: &mut App, cli_key: Option) { // rustls uses the `aws_lc_rs` provider by default // This only errors if the default provider has already // been installed. We can ignore this `Result`. @@ -42,7 +42,7 @@ pub fn init(window: &mut Window, cx: &mut App, cli_key: Option) { #[cfg(not(target_arch = "wasm32"))] gpui_tokio::init(cx); - NostrRegistry::set_global(cx.new(|cx| NostrRegistry::new(window, cx, cli_key)), cx); + NostrRegistry::set_global(cx.new(|cx| NostrRegistry::new(cx, cli_key)), cx); } struct GlobalNostrRegistry(Entity); @@ -105,7 +105,8 @@ impl NostrRegistry { } /// Create a new nostr instance - fn new(window: &mut Window, cx: &mut Context, cli_key: Option) -> Self { + fn new(cx: &mut Context, cli_key: Option) -> Self { + let entity = cx.entity().downgrade(); let signer = UniversalSigner::new(Keys::generate()); let authenticator = SignerAuthenticator::new(signer.clone()); @@ -132,19 +133,23 @@ impl NostrRegistry { }) .build(); - // Connect to bootstrap relays after the window is ready - cx.defer_in(window, |this, _window, cx| { - this.connect_bootstrap_relays(cx); + // Connect to bootstrap relays once the registry has been returned to the app + cx.defer(move |cx| { + entity + .update(cx, |this, cx| { + this.connect_bootstrap_relays(cx); - if cfg!(target_arch = "wasm32") { - cx.emit(StateEvent::NoSigner); - } else if let Some(secret) = cli_key { - // Use CLI-provided key -- same path as get_user_credential - let keys = Keys::new(secret); - this.set_signer(keys, cx); - } else { - this.get_user_credential(cx); - } + if cfg!(target_arch = "wasm32") { + cx.emit(StateEvent::NoSigner); + } else if let Some(secret) = cli_key { + // Use CLI-provided key -- same path as get_user_credential + let keys = Keys::new(secret); + this.set_signer(keys, cx); + } else { + this.get_user_credential(cx); + } + }) + .ok(); }); Self { diff --git a/desktop/src/main.rs b/desktop/src/main.rs index 57779b65..a275dc70 100644 --- a/desktop/src/main.rs +++ b/desktop/src/main.rs @@ -31,6 +31,12 @@ fn main() { .with_assets(Assets) .with_http_client(Arc::new(reqwest_client::ReqwestClient::new())) .run(move |cx| { + // Initialize components + ui::init(cx); + + // Initialize theme registry + theme::init(cx); + // Load embedded fonts in assets/fonts load_embedded_fonts(cx); @@ -55,6 +61,29 @@ fn main() { disabled: false, }]); + // Initialize settings + settings::init(cx); + + // Initialize the nostr client + state::init(cx, cli_key); + + // Initialize person registry + person::init(cx); + + // Initialize device signer + // + // NIP-4e: https://github.com/nostr-protocol/nips/blob/per-device-keys/4e.md + device::init(cx); + + // Initialize app registry + chat::init(cx); + + // Initialize community registry + community::init(cx); + + // Initialize auto update + auto_update::init(cx); + // Set up the window bounds let bounds = Bounds::centered(None, size(px(960.0), px(720.0)), cx); @@ -77,43 +106,11 @@ fn main() { ..Default::default() }; - // Open a window with default options cx.open_window(opts, |window, cx| { - // Initialize components - ui::init(cx); - - // Initialize theme registry - theme::init(cx); - - // Initialize settings - settings::init(window, cx); - - // Initialize the nostr client - state::init(window, cx, cli_key); - - // Initialize person registry - person::init(window, cx); - - // Initialize device signer - // - // NIP-4e: https://github.com/nostr-protocol/nips/blob/per-device-keys/4e.md - device::init(window, cx); - - // Initialize app registry - chat::init(window, cx); - - // Initialize community registry - community::init(window, cx); - - // Initialize auto update - auto_update::init(window, cx); - - // Root view cx.new(|cx| Root::new(workspace::init(window, cx).into(), window, cx)) }) .expect("Failed to open window. Please restart the application."); - // Bring the app to the foreground cx.activate(true); }); } diff --git a/web/src/lib.rs b/web/src/lib.rs index 9bb87f6d..d8130655 100644 --- a/web/src/lib.rs +++ b/web/src/lib.rs @@ -47,35 +47,33 @@ pub fn run() -> Result<(), JsValue> { }; app.run(|cx| { - // Open the root window + // Initialize components + ui::init(cx); + + // Initialize theme registry + theme::init(cx); + + // Initialize settings + settings::init(cx); + + // Initialize the nostr client + state::init(cx, None); + + // Initialize person registry + person::init(cx); + + // Initialize device signer + // + // NIP-4e: https://github.com/nostr-protocol/nips/blob/per-device-keys/4e.md + device::init(cx); + + // Initialize app registry + chat::init(cx); + + // Initialize community registry + community::init(cx); + cx.open_window(WindowOptions::default(), |window, cx| { - // Initialize components - ui::init(cx); - - // Initialize theme registry - theme::init(cx); - - // Initialize settings - settings::init(window, cx); - - // Initialize the nostr client - state::init(window, cx, None); - - // Initialize person registry - person::init(window, cx); - - // Initialize device signer - // - // NIP-4e: https://github.com/nostr-protocol/nips/blob/per-device-keys/4e.md - device::init(window, cx); - - // Initialize app registry - chat::init(window, cx); - - // Initialize community registry - community::init(window, cx); - - // Root view cx.new(|cx| Root::new(workspace::init(window, cx).into(), window, cx)) }) .expect("Failed to open window. Please restart the application.");