pub(crate) mod chrome;
mod clipboard;
mod core;
pub(crate) mod dialog_extra;
mod helpers;
mod menu_set;
mod power_process;
pub(crate) mod secrets_auth;
mod shell_os;
mod shortcut;
pub(crate) mod state_shared;
pub(crate) mod store_auto;
pub(crate) mod webview_cap;
mod window_extra;
use serde_json::json;
use tauri::{AppHandle, Emitter, Manager};
use crate::state::AppState;
#[tauri::command]
pub fn desktop_protocol() -> serde_json::Value {
json!({ "protocol": crate::state::PROTOCOL_VERSION })
}
#[tauri::command]
pub async fn desktop_invoke(
app: AppHandle,
cmd: String,
args: Option<serde_json::Value>,
) -> Result<serde_json::Value, String> {
let args = args.unwrap_or(json!({}));
let is_handler = app.state::<AppState>().handlers.lock().contains_key(&cmd);
let trace = std::env::var("TISH_DESKTOP_TRACE_INVOKE").as_deref() == Ok("1");
if trace {
eprintln!("[desktop_invoke] → {cmd} (handler={is_handler})");
}
let started = std::time::Instant::now();
let out = if is_handler {
let app2 = app.clone();
let cmd2 = cmd.clone();
match tauri::async_runtime::spawn_blocking(move || {
let state = app2.state::<AppState>();
dispatch(&app2, state.inner(), &cmd2, args)
})
.await
{
Ok(r) => r,
Err(e) => Err(format!("desktop_invoke worker failed: {e}")),
}
} else {
let state = app.state::<AppState>();
dispatch(&app, state.inner(), &cmd, args)
};
if trace {
eprintln!(
"[desktop_invoke] ← {cmd} ({}ms)",
started.elapsed().as_millis()
);
}
out
}
type ExtraDispatch =
fn(&AppHandle, &AppState, &str, &serde_json::Value) -> Option<Result<serde_json::Value, String>>;
pub fn invoke_for_handle(
app: &AppHandle,
state: &AppState,
cmd: &str,
args: serde_json::Value,
) -> Result<serde_json::Value, String> {
dispatch(app, state, cmd, args)
}
const LEGACY_MODULES: &[ExtraDispatch] = &[
core::try_dispatch,
chrome::try_dispatch,
window_extra::try_dispatch,
clipboard::try_dispatch,
shortcut::try_dispatch,
shell_os::try_dispatch,
store_auto::try_dispatch, power_process::try_dispatch,
secrets_auth::try_dispatch,
menu_set::try_dispatch,
];
fn dispatch(
app: &AppHandle,
state: &AppState,
cmd: &str,
args: serde_json::Value,
) -> Result<serde_json::Value, String> {
if state.handlers.lock().contains_key(cmd) {
return state.call_handler(cmd, args);
}
if let Some(result) = state_shared::try_dispatch(app, state, cmd, &args) {
return result;
}
if let Some(result) = crate::caps::try_caps(app, state, cmd, &args) {
return result;
}
for try_dispatch in LEGACY_MODULES {
if let Some(result) = try_dispatch(app, state, cmd, &args) {
return result;
}
}
Err(format!("unknown command: {cmd}"))
}
#[tauri::command]
pub fn desktop_emit_tick(app: AppHandle, ts: u64) -> Result<(), String> {
app.emit("tick", json!({ "ts": ts })).map_err(|e| e.to_string())
}
pub fn spawn_tick_loop(app: AppHandle, tick_ms: u64) {
std::thread::spawn(move || {
let interval = std::time::Duration::from_millis(tick_ms.max(1));
loop {
std::thread::sleep(interval);
let ts = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_millis() as u64)
.unwrap_or(0);
if let Err(e) = desktop_emit_tick(app.clone(), ts) {
eprintln!("tishlang_desktop: tick emit failed: {e}");
}
}
});
}