Files

192 lines
6.2 KiB
Rust

//! 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<Arc<dyn NostrSigner>>,
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<Mutex<AppState>> = Lazy::new(|| Mutex::new(AppState::default()));
/// Set the signer and update dependent modules.
pub fn app_set_signer(
signer: Option<Arc<dyn NostrSigner>>,
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<Arc<dyn NostrSigner>> {
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: &gtk::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::<gtk::Window>().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::<gtk::Window>().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)");
}