//! Menu actions and app state for sovereign_browser //! //! Port of the menu proxy functions from `main.c` in the C project. //! These are called from the hamburger menu in `tab_manager.rs`. //! //! This module also owns the app state (signer, pubkey, method) so that //! both the binary (`main.rs`) and the library modules (`tab_manager.rs`, //! `menu.rs`) can share it without cross-crate-root references. use gtk::prelude::*; use nostr_signer::traits::NostrSigner; use std::sync::Arc; use std::sync::Mutex; use once_cell::sync::Lazy; use crate::key_store; use crate::login_dialog; use crate::nostr_bridge; use crate::tab_manager; /// App state (signer, identity, method). pub struct AppState { pub signer: Option>, pub pubkey_hex: String, pub privkey_hex: String, pub method: key_store::KeyStoreMethod, pub readonly: bool, } impl Default for AppState { fn default() -> Self { AppState { signer: None, pubkey_hex: String::new(), privkey_hex: String::new(), method: key_store::KeyStoreMethod::None, readonly: true, } } } /// Global app state. static G_STATE: Lazy> = Lazy::new(|| Mutex::new(AppState::default())); /// Set the signer and update dependent modules. pub fn app_set_signer( signer: Option>, pubkey_hex: &str, privkey_hex: &str, method: key_store::KeyStoreMethod, readonly: bool, ) { let mut state = G_STATE.lock().unwrap(); state.signer = signer; state.pubkey_hex = pubkey_hex.to_string(); state.privkey_hex = privkey_hex.to_string(); state.method = method; state.readonly = readonly; nostr_bridge::nostr_bridge_set_signer(state.signer.clone(), &state.pubkey_hex, state.readonly); crate::agent_login::agent_login_set_signer(state.signer.clone(), &state.pubkey_hex); crate::settings_sync::settings_sync_init(state.signer.clone(), &state.pubkey_hex); crate::agent_conversations::agent_conversations_init(state.signer.clone(), &state.pubkey_hex); crate::agent_skills::agent_skills_init(state.signer.clone(), &state.pubkey_hex); } /// Clear the signer and reset app state (used by logout / lock session). pub fn app_clear_signer() { let mut state = G_STATE.lock().unwrap(); state.signer = None; state.pubkey_hex.clear(); state.privkey_hex.clear(); state.method = key_store::KeyStoreMethod::None; state.readonly = true; nostr_bridge::nostr_bridge_set_signer(None, "", true); } /// Get the current pubkey. pub fn app_get_pubkey_hex() -> String { let state = G_STATE.lock().unwrap(); state.pubkey_hex.clone() } /// Get the current signer. pub fn app_get_signer() -> Option> { let state = G_STATE.lock().unwrap(); state.signer.clone() } /// Get the current method. pub fn app_get_method() -> key_store::KeyStoreMethod { let state = G_STATE.lock().unwrap(); state.method } /// Get whether the session is read-only. pub fn app_get_readonly() -> bool { let state = G_STATE.lock().unwrap(); state.readonly } /// Run the login dialog and install the resulting identity. /// Switch to the per-user profile database. pub fn switch_to_user_db(pubkey_hex: &str) -> i32 { match crate::db::db_init(pubkey_hex) { Ok(_) => { println!("[db] Switched to profile database for {}", pubkey_hex); 0 } Err(e) => { eprintln!("[db] Failed to open database: {}", e); -1 } } } /// Run the login dialog and install the resulting identity. fn do_login(parent: >k::Window) -> bool { let result = login_dialog::login_dialog_run(parent); match result { Some(login_result) => { let pubkey_hex = login_result.pubkey_hex.clone(); let privkey_hex = login_result.identity.privkey_hex.clone(); let method = login_result.method; let readonly = login_result.signer.is_none(); if login_result.no_login || pubkey_hex.is_empty() { println!("[login] No-login mode (browsing without Nostr identity)"); app_set_signer(None, "", "", key_store::KeyStoreMethod::None, true); crate::settings::settings_load(); crate::shortcuts::shortcuts_load(); return true; } if switch_to_user_db(&pubkey_hex) != 0 { return false; } println!( "[login] signed in via {} as {}{}", method.as_str(), pubkey_hex, if readonly { " (read-only)" } else { "" } ); app_set_signer(login_result.signer, &pubkey_hex, &privkey_hex, method, readonly); // Refresh the tab-bar avatar (the kind 0 profile may now be in // the per-user database). crate::tab_manager::tab_manager_set_avatar(Some(&pubkey_hex)); let _ = key_store::key_store_save_profile_identity(&pubkey_hex, method); crate::settings::settings_load(); crate::shortcuts::shortcuts_load(); let pubkey = pubkey_hex.clone(); std::thread::spawn(move || { crate::relay_fetch::relay_fetch_thread(pubkey); }); true } None => false, } } /// Menu action: switch identity (re-show the login dialog). pub fn menu_switch_identity() { if let Some(nb) = tab_manager::tab_manager_get_main_notebook() { if let Some(win) = nb.toplevel().and_then(|w| w.downcast::().ok()) { if do_login(&win) { println!("[identity] switched identity"); } } } } /// Menu action: logout (clear signer, re-show login). pub fn menu_logout() { app_clear_signer(); println!("[identity] logged out"); if let Some(nb) = tab_manager::tab_manager_get_main_notebook() { if let Some(win) = nb.toplevel().and_then(|w| w.downcast::().ok()) { do_login(&win); } } } /// Menu action: lock session (clear signer, keep identity). pub fn menu_lock_session() { app_clear_signer(); println!("[identity] session locked (signer cleared, identity preserved)"); }