2026-09-22 21:46:01 +03:00
|
|
|
//! Команды обёртки: типизированный мост «веб-клиент → Rust» (docs/client-tauri.md §2).
|
|
|
|
|
//!
|
|
|
|
|
//! Команд ровно столько, сколько нужно странице: прочитать и сохранить
|
|
|
|
|
//! настройки, сообщить о готовности, показать уведомление, обновить счётчик
|
|
|
|
|
//! непрочитанных, открыть настройки/клиент и проверить обновления. Никакой
|
|
|
|
|
//! бизнес-логики здесь нет — только платформенные действия.
|
|
|
|
|
|
|
|
|
|
use tauri::{AppHandle, Manager, Runtime};
|
|
|
|
|
|
|
|
|
|
use crate::bridge::{self, WebMessage};
|
|
|
|
|
use crate::settings::Settings;
|
|
|
|
|
use crate::state::AppState;
|
|
|
|
|
use crate::{autostart, instance, notifications, shortcuts, updates, window};
|
|
|
|
|
|
|
|
|
|
/// Текущие настройки обёртки.
|
|
|
|
|
#[tauri::command]
|
|
|
|
|
pub fn desktop_settings<R: Runtime>(app: AppHandle<R>) -> Settings {
|
|
|
|
|
app.state::<AppState>().settings()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Версия приложения — показывается на странице настроек.
|
|
|
|
|
#[tauri::command]
|
|
|
|
|
pub fn desktop_version<R: Runtime>(app: AppHandle<R>) -> String {
|
|
|
|
|
app.package_info().version.to_string()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Сохраняет настройки и применяет их: автозапуск, шорткаты, адрес инстанса.
|
|
|
|
|
#[tauri::command]
|
|
|
|
|
pub fn desktop_save_settings<R: Runtime>(
|
|
|
|
|
app: AppHandle<R>,
|
|
|
|
|
settings: Settings,
|
|
|
|
|
) -> Result<Settings, String> {
|
|
|
|
|
// Адрес проверяем до применения, чтобы не оставить обёртку без клиента.
|
|
|
|
|
let url = instance::normalize(&settings.instance_url)?;
|
|
|
|
|
let mut next = settings;
|
|
|
|
|
next.instance_url = instance::display(&url);
|
|
|
|
|
|
|
|
|
|
let previous = app.state::<AppState>().settings();
|
|
|
|
|
// Шорткаты применяем первыми: если комбинация занята, настройки не сохраняем.
|
|
|
|
|
if next.ptt_shortcut != previous.ptt_shortcut
|
|
|
|
|
|| next.mute_shortcut != previous.mute_shortcut
|
|
|
|
|
|| next.deafen_shortcut != previous.deafen_shortcut
|
|
|
|
|
{
|
|
|
|
|
if let Err(error) = shortcuts::apply(&app, &next) {
|
|
|
|
|
let _ = shortcuts::apply(&app, &previous);
|
|
|
|
|
return Err(error);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if let Err(error) = autostart::apply(&app, next.autostart) {
|
|
|
|
|
log::warn!("{error}");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
app.state::<AppState>().set_settings(next.clone());
|
|
|
|
|
crate::settings::save(&app, &next)?;
|
|
|
|
|
crate::tray::refresh(&app);
|
|
|
|
|
announce_settings(&app, &next);
|
|
|
|
|
if next.instance_url != previous.instance_url {
|
|
|
|
|
window::open_instance_page(&app)?;
|
|
|
|
|
}
|
|
|
|
|
Ok(next)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Веб-клиент загрузился: отдаём накопленные deep link.
|
|
|
|
|
#[tauri::command]
|
|
|
|
|
pub fn desktop_ready<R: Runtime>(app: AppHandle<R>) {
|
|
|
|
|
let pending = app.state::<AppState>().mark_web_ready();
|
|
|
|
|
for url in pending {
|
|
|
|
|
bridge::dispatch(&app, &WebMessage::DeepLink { url });
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Показывает системное уведомление.
|
|
|
|
|
#[tauri::command]
|
|
|
|
|
pub fn desktop_notify<R: Runtime>(
|
|
|
|
|
app: AppHandle<R>,
|
|
|
|
|
payload: notifications::NotifyPayload,
|
|
|
|
|
) -> Result<(), String> {
|
|
|
|
|
notifications::show(&app, &payload)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Обновляет счётчик непрочитанных в доке и трее.
|
|
|
|
|
#[tauri::command]
|
|
|
|
|
pub fn desktop_set_badge<R: Runtime>(app: AppHandle<R>, count: u32) {
|
|
|
|
|
notifications::set_badge(&app, count);
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-22 23:17:27 +03:00
|
|
|
/// Веб-клиент сообщает, есть ли несохранённые изменения: от этого зависит
|
|
|
|
|
/// перехват закрытия окна (docs/client-tauri.md §5).
|
|
|
|
|
#[tauri::command]
|
|
|
|
|
pub fn desktop_set_dirty<R: Runtime>(app: AppHandle<R>, dirty: bool) {
|
|
|
|
|
app.state::<AppState>().set_dirty(dirty);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Закрывает приложение по решению пользователя в диалоге клиента
|
|
|
|
|
/// («Сохранить» после успешного сохранения или «Выйти без сохранения»).
|
|
|
|
|
#[tauri::command]
|
|
|
|
|
pub fn desktop_close_window<R: Runtime>(app: AppHandle<R>) {
|
|
|
|
|
log::info!("закрываем приложение по решению пользователя");
|
|
|
|
|
app.exit(0);
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-22 21:46:01 +03:00
|
|
|
/// Открывает локальную страницу настроек обёртки.
|
|
|
|
|
#[tauri::command]
|
|
|
|
|
pub fn desktop_open_settings<R: Runtime>(app: AppHandle<R>) -> Result<(), String> {
|
|
|
|
|
window::open_settings_page(&app)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Возвращает окно к клиенту инстанса.
|
|
|
|
|
#[tauri::command]
|
|
|
|
|
pub fn desktop_open_instance<R: Runtime>(app: AppHandle<R>) -> Result<(), String> {
|
|
|
|
|
window::open_instance_page(&app)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Перезагружает клиент инстанса.
|
|
|
|
|
#[tauri::command]
|
|
|
|
|
pub fn desktop_reload<R: Runtime>(app: AppHandle<R>) -> Result<(), String> {
|
|
|
|
|
window::reload_instance(&app)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Проверяет обновления (кнопка на странице настроек).
|
|
|
|
|
#[tauri::command]
|
|
|
|
|
pub async fn desktop_check_updates<R: Runtime>(app: AppHandle<R>) {
|
2026-09-26 18:12:35 +03:00
|
|
|
updates::check(app, true, false).await;
|
2026-09-22 21:46:01 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Открывает ссылку в системном браузере.
|
|
|
|
|
#[tauri::command]
|
|
|
|
|
pub fn desktop_open_external<R: Runtime>(app: AppHandle<R>, url: String) -> Result<(), String> {
|
|
|
|
|
let parsed = instance::normalize(&url)?;
|
|
|
|
|
tauri_plugin_opener::OpenerExt::opener(&app)
|
|
|
|
|
.open_url(parsed.as_str(), None::<&str>)
|
|
|
|
|
.map_err(|error| format!("не удалось открыть ссылку: {error}"))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Сообщает веб-клиенту, что настройки обёртки изменились.
|
|
|
|
|
pub fn announce_settings<R: Runtime>(app: &AppHandle<R>, settings: &Settings) {
|
|
|
|
|
bridge::send(
|
|
|
|
|
app,
|
|
|
|
|
&WebMessage::Settings {
|
|
|
|
|
settings: settings.clone(),
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
}
|