Logo

Tauri 跨平台开发实践(一)

avatar ouyang 27 Aug 2024

什么是 Tauri?

Tauri 是一个相对较新的跨平台 GUI 框架,可以帮助开发者为主流平台制作应用程序(如 MacOS,Windows,Linux,iOS,Android 等)。几乎支持所有现有的前端框架(如 React, Vue 等)。

我们日常使用的许多跨平台应用,如 VS Code、Notion 和 Apifox 都在 Electron 框架上运行。但 Electron 的缺点是,由于每个应用都需要携带完整的 Chromium 内核,因此占用大量磁盘空间。而且 Chromium 在处理器和内存使用方面效率不高。Tauri 与 Electron 最大的区别在于,Tauri 使用系统原生的 Webview,这使得 Tauri 应用程序的体积更小、性能更高,且后端的语言也不同,Electron 使用 NodeJS,而 Tauri 使用 Rust。

在本文中,我将分享开发一个壁纸工具(系统托盘应用)的一些细节。

项目创建

项目创建可参考官方文档:Tauri 官方文档,本文使用 Tauri 2.0, 与 Tauri 1.0 相比有较大改动。

前端使用 Vue3 + Vite + TypeScript 的组合。

UI 实现

UI 方面使用了 TailwindCSS 搭建了一个简洁的界面,写法和 Web 是一样的。

WechatIMG175.jpg

毛玻璃及圆角效果的实现

使用 window-vibrancy 实现,不支持 Linux, 且圆角效果只支持 MacOS,需根据不同平台进行不同的设置。

index.html

html, body { background: transparent }

tauri.conf.json

使用 macOSPrivateApi 会导致 App Store 拒绝通过

{
  "app": {
    "windows": [
      {
        "transparent": true
      }
    ],
    "macOSPrivateApi": true
  }
}

src/main.rs

use window_vibrancy::*;

tauri::Builder::default()
.setup(move |app| {
  let window = app.get_webview_window("main").unwrap();

  #[cfg(target_os = "macos")]
  apply_vibrancy(&window, NSVisualEffectMaterial::HudWindow, None, Some(8.0))
      .expect("Unsupported platform! 'apply_vibrancy' is only supported on macOS");

  #[cfg(target_os = "windows")]
  apply_blur(&window, Some((18, 18, 18, 125)))
      .expect("Unsupported platform! 'apply_blur' is only supported on Windows");
});

常驻系统托盘,添加右键菜单

使用 Tauri 自带的 SystemTray 模块实现系统托盘。

WechatIMG174.jpg

创建托盘方法

use tauri::{
  menu::{Menu, MenuItem, PredefinedMenuItem},
  tray::{MouseButton, MouseButtonState, TrayIconBuilder, TrayIconEvent},
  Manager, Runtime,
};

pub fn create_tray<R: Runtime>(app: &tauri::AppHandle<R>) -> tauri::Result<()> {
  let next_photo_i = MenuItem::with_id(app, "switch", "Switch wallpaper", true, None::<&str>)?;
  let quit_i = MenuItem::with_id(app, "quit", "Quit", true, None::<&str>)?;
  let menu = Menu::with_items(
    app,
    &[
      &next_photo_i,
      &PredefinedMenuItem::separator(app)?,
      &quit_i,
    ],
  )?;

  let _ = TrayIconBuilder::with_id("tray")
    .icon(app.default_window_icon().unwrap().clone())
    .menu(&menu)
    .menu_on_left_click(false)
    .on_menu_event(move |app, event| match event.id.as_ref() {
      "switch" => {
        // TODO: 切换壁纸
      }
      "quit" => {
        app.exit(0);
      }
      _ => {}
    })
    .build(app);

  Ok(())
}

初始化时调用创建托盘方法

tauri::Builder::default()
  .setup(move |app| {
    #[cfg(all(desktop))]
    {
      let handle = app.handle();
      tray::create_tray(&handle)?;
    }
    // mac的docker栏隐藏需要隐藏icon
    #[cfg(target_os = "macos")]
    app.set_activation_policy(tauri::ActivationPolicy::Accessory);
  });

窗口固定

通过托盘图标的点击事件实现显示或隐藏窗口,并使用 positioner 实现MacOS 平台下,窗口固定在顶部, Windows 平台下,窗口固定在底部。

初始化时,将插件实例传递给 plugin 方法

tauri::Builder::default().plugin(tauri_plugin_positioner::init())

在托盘的点击事件中调用 move_window 方法

use tauri::{
  tray::{MouseButton, MouseButtonState, TrayIconBuilder, TrayIconEvent},
  Manager, Runtime,
};
use tauri_plugin_positioner::{Position, WindowExt};

pub fn create_tray<R: Runtime>(app: &tauri::AppHandle<R>) -> tauri::Result<()> {
  let _ = TrayIconBuilder::with_id("tray")
		// ...
    .on_tray_icon_event(|app, event| {
      tauri_plugin_positioner::on_tray_event(app.app_handle(), &event);
      if let TrayIconEvent::Click {
        button: MouseButton::Left,
        button_state: MouseButtonState::Up,
        ..
      } = event
      {
        let app = app.app_handle();
        if let Some(window) = app.get_webview_window("main") {
          #[cfg(target_os = "windows")]
          let _ = window.move_window(Position::Center);

          #[cfg(not(target_os = "windows"))]
          let _ = window.move_window(Position::TrayBottomCenter);

          if window.is_visible().unwrap() {
            window.hide().unwrap();
          } else {
            window.show().unwrap();
            window.set_focus().unwrap();
          }
        }
      }
    })
    .build(app);

  Ok(())
}

开机自启动

省去每次开机后都要重新打开的麻烦,使用 autostart 实现。

use tauri_plugin_autostart::MacosLauncher;
let mut builder = tauri::Builder::default().plugin(tauri_plugin_autostart::init(MacosLauncher::LaunchAgent, None))

设置相关权限

{
  "permissions": [
    "autostart:allow-enable",
    "autostart:allow-disable",
    "autostart:allow-is-enabled"
  ]
}

前端启用或禁用自启动

import { enable, isEnabled, disable } from '@tauri-apps/plugin-autostart';

await enable();
console.log(`registered for autostart? ${await isEnabled()}`);
disable();

实现更换壁纸功能

使用 Unsplash 提供的接口获取图片,然后使用 wallpaper 设置壁纸,并在 on_menu_event 的点击事件中调用。

// 下载到指定目录
pub async fn download(&mut self, url: &str, file_name: &str, path: PathBuf) -> Result<String, String> {
  let response = http::get(url.to_string());
  let data = match response.await {
    Ok(data) => data,
    Err(_) => return Err("Could not fetch image".to_string())
  };
  let path = path.join(format!("{}.jpg", file_name));
  let mut file = File::create(path.clone()).unwrap();
  let content = data.bytes().await.unwrap();
  copy(&mut content.as_ref(), &mut file).unwrap();
  Ok(path.display().to_string())
}

pub async fn set_wallpaper(&mut self, url: &str, file_name: &str) -> Result<(), String> {
  // 获取系统缓存目录
  let cache_dir = match dirs::cache_dir() {
    Some(path) => path,
    None => {
      return Err("Could not find download directory".to_string())
    }
  };

  let path = cache_dir.join(Self::CACHE_DIR);
  // 如果目录不存在,先创建
  let _ = std::fs::create_dir_all(cache_dir.join(Self::CACHE_DIR));

  let path = Self::download(self, url, file_name, path.clone()).await.unwrap();
  // 指定本地文件作为壁纸
  let _ = wallpaper::set_from_path(&path);
  Ok(())
}

pub async fn switch_wallpaper(&mut self) -> Result<(), String> {
  let res = http::get("<https://api.unsplash.com/photos/random>".to_string()).await.unwrap();
  let data = match res.json::<Random>().await {
    Ok(data) => data,
    Err(_) => return Err("json parse fail".to_string())
  };
  Self::set_wallpaper(self, &data.urls.raw, &data.id).await.unwrap();
  Ok(())
}

总结

这篇文章介绍了 Tauri 的基本概念,以及如何使用 Tauri 实现一些常见的功能,由于篇幅有限,只简单介绍一些功能,更多的功能将在后续文章中介绍。

Tags
tauri
rust
跨平台