Skip to main content

tauri_utils/
lib.rs

1// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
2// SPDX-License-Identifier: Apache-2.0
3// SPDX-License-Identifier: MIT
4
5//! This crate contains common code that is reused in many places and offers useful utilities like parsing configuration files, detecting platform triples, injecting the CSP, and managing assets.
6
7#![doc(
8  html_logo_url = "https://github.com/tauri-apps/tauri/raw/dev/.github/icon.png",
9  html_favicon_url = "https://github.com/tauri-apps/tauri/raw/dev/.github/icon.png"
10)]
11#![warn(missing_docs, rust_2018_idioms)]
12#![allow(clippy::deprecated_semver)]
13
14use std::{
15  ffi::OsString,
16  fmt::Display,
17  path::{Path, PathBuf},
18};
19
20use semver::Version;
21use serde::{Deserialize, Deserializer, Serialize, Serializer};
22
23pub mod acl;
24pub mod assets;
25pub mod config;
26pub mod config_v1;
27#[cfg(feature = "html-manipulation")]
28pub mod html;
29#[cfg(feature = "html-manipulation-2")]
30pub mod html2;
31pub mod io;
32pub mod mime_type;
33pub mod platform;
34pub mod plugin;
35/// Prepare application resources and sidecars.
36#[cfg(feature = "resources")]
37pub mod resources;
38#[cfg(any(feature = "build", feature = "build-2"))]
39pub mod tokens;
40
41#[cfg(any(feature = "build", feature = "build-2"))]
42pub mod build;
43
44/// Application pattern.
45pub mod pattern;
46
47/// `tauri::App` package information.
48#[derive(Debug, Clone)]
49pub struct PackageInfo {
50  /// App name
51  pub name: String,
52  /// App version
53  pub version: Version,
54  /// The crate authors.
55  pub authors: &'static str,
56  /// The crate description.
57  pub description: &'static str,
58  /// The crate name.
59  pub crate_name: &'static str,
60}
61
62#[allow(deprecated)]
63mod window_effects {
64  use super::*;
65
66  #[derive(Debug, PartialEq, Eq, Clone, Copy, Deserialize, Serialize)]
67  #[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
68  #[serde(rename_all = "camelCase")]
69  #[non_exhaustive]
70  /// Platform-specific window effects
71  pub enum WindowEffect {
72    /// A default material appropriate for the view's effectiveAppearance. **macOS 10.14-**
73    #[deprecated(
74      since = "macOS 10.14",
75      note = "You should instead choose an appropriate semantic material."
76    )]
77    AppearanceBased,
78    /// **macOS 10.14-**
79    #[deprecated(since = "macOS 10.14", note = "Use a semantic material instead.")]
80    Light,
81    /// **macOS 10.14-**
82    #[deprecated(since = "macOS 10.14", note = "Use a semantic material instead.")]
83    Dark,
84    /// **macOS 10.14-**
85    #[deprecated(since = "macOS 10.14", note = "Use a semantic material instead.")]
86    MediumLight,
87    /// **macOS 10.14-**
88    #[deprecated(since = "macOS 10.14", note = "Use a semantic material instead.")]
89    UltraDark,
90    /// **macOS 10.10+**
91    Titlebar,
92    /// **macOS 10.10+**
93    Selection,
94    /// **macOS 10.11+**
95    Menu,
96    /// **macOS 10.11+**
97    Popover,
98    /// **macOS 10.11+**
99    Sidebar,
100    /// **macOS 10.14+**
101    HeaderView,
102    /// **macOS 10.14+**
103    Sheet,
104    /// **macOS 10.14+**
105    WindowBackground,
106    /// **macOS 10.14+**
107    HudWindow,
108    /// **macOS 10.14+**
109    FullScreenUI,
110    /// **macOS 10.14+**
111    Tooltip,
112    /// **macOS 10.14+**
113    ContentBackground,
114    /// **macOS 10.14+**
115    UnderWindowBackground,
116    /// **macOS 10.14+**
117    UnderPageBackground,
118    /// **macOS 26.0+**
119    LiquidGlassRegular,
120    /// **macOS 26.0+**
121    LiquidGlassClear,
122    /// Mica effect that matches the system dark preference **Windows 11 Only**
123    Mica,
124    /// Mica effect with dark mode but only if dark mode is enabled on the system **Windows 11 Only**
125    MicaDark,
126    /// Mica effect with light mode **Windows 11 Only**
127    MicaLight,
128    /// Tabbed effect that matches the system dark preference **Windows 11 Only**
129    Tabbed,
130    /// Tabbed effect with dark mode but only if dark mode is enabled on the system **Windows 11 Only**
131    TabbedDark,
132    /// Tabbed effect with light mode **Windows 11 Only**
133    TabbedLight,
134    /// **Windows 7/10/11(22H1) Only**
135    ///
136    /// ## Notes
137    ///
138    /// This effect has bad performance when resizing/dragging the window on Windows 11 build 22621.
139    Blur,
140    /// **Windows 10/11 Only**
141    ///
142    /// ## Notes
143    ///
144    /// This effect has bad performance when resizing/dragging the window on Windows 10 v1903+ and Windows 11 build 22000.
145    Acrylic,
146  }
147
148  /// Window effect state **macOS only**
149  ///
150  /// <https://developer.apple.com/documentation/appkit/nsvisualeffectview/state>
151  #[derive(Debug, PartialEq, Eq, Clone, Copy, Deserialize, Serialize)]
152  #[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
153  #[serde(rename_all = "camelCase")]
154  pub enum WindowEffectState {
155    /// Make window effect state follow the window's active state
156    FollowsWindowActiveState,
157    /// Make window effect state always active
158    Active,
159    /// Make window effect state always inactive
160    Inactive,
161  }
162}
163
164pub use window_effects::{WindowEffect, WindowEffectState};
165
166/// How the window title bar should be displayed on macOS.
167#[derive(Debug, Clone, PartialEq, Eq, Copy, Default)]
168#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
169#[non_exhaustive]
170pub enum TitleBarStyle {
171  /// A normal title bar.
172  #[default]
173  Visible,
174  /// Makes the title bar transparent, so the window background color is shown instead.
175  ///
176  /// Useful if you don't need to have actual HTML under the title bar. This lets you avoid the caveats of using `TitleBarStyle::Overlay`. Will be more useful when Tauri lets you set a custom window background color.
177  Transparent,
178  /// Shows the title bar as a transparent overlay over the window's content.
179  ///
180  /// Keep in mind:
181  /// - The height of the title bar is different on different OS versions, which can lead to window the controls and title not being where you don't expect.
182  /// - You need to define a custom drag region to make your window draggable, however due to a limitation you can't drag the window when it's not in focus <https://github.com/tauri-apps/tauri/issues/4316>.
183  /// - The color of the window title depends on the system theme.
184  Overlay,
185}
186
187impl Serialize for TitleBarStyle {
188  fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
189  where
190    S: Serializer,
191  {
192    serializer.serialize_str(self.to_string().as_ref())
193  }
194}
195
196impl<'de> Deserialize<'de> for TitleBarStyle {
197  fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
198  where
199    D: Deserializer<'de>,
200  {
201    let s = String::deserialize(deserializer)?;
202    Ok(match s.to_lowercase().as_str() {
203      "transparent" => Self::Transparent,
204      "overlay" => Self::Overlay,
205      _ => Self::Visible,
206    })
207  }
208}
209
210impl Display for TitleBarStyle {
211  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
212    write!(
213      f,
214      "{}",
215      match self {
216        Self::Visible => "Visible",
217        Self::Transparent => "Transparent",
218        Self::Overlay => "Overlay",
219      }
220    )
221  }
222}
223
224/// System theme.
225#[derive(Debug, Copy, Clone, PartialEq, Eq)]
226#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
227#[non_exhaustive]
228pub enum Theme {
229  /// Light theme.
230  Light,
231  /// Dark theme.
232  Dark,
233}
234
235impl Serialize for Theme {
236  fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
237  where
238    S: Serializer,
239  {
240    serializer.serialize_str(self.to_string().as_ref())
241  }
242}
243
244impl<'de> Deserialize<'de> for Theme {
245  fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
246  where
247    D: Deserializer<'de>,
248  {
249    let s = String::deserialize(deserializer)?;
250    Ok(match s.to_lowercase().as_str() {
251      "dark" => Self::Dark,
252      _ => Self::Light,
253    })
254  }
255}
256
257impl Display for Theme {
258  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
259    write!(
260      f,
261      "{}",
262      match self {
263        Self::Light => "light",
264        Self::Dark => "dark",
265      }
266    )
267  }
268}
269
270/// Information about environment variables.
271#[derive(Debug, Clone)]
272#[non_exhaustive]
273pub struct Env {
274  /// The APPIMAGE environment variable.
275  #[cfg(target_os = "linux")]
276  pub appimage: Option<std::ffi::OsString>,
277  /// The APPDIR environment variable.
278  #[cfg(target_os = "linux")]
279  pub appdir: Option<std::ffi::OsString>,
280  /// The command line arguments of the current process.
281  pub args_os: Vec<OsString>,
282}
283
284#[allow(clippy::derivable_impls)]
285impl Default for Env {
286  fn default() -> Self {
287    let args_os = std::env::args_os().collect();
288    #[cfg(target_os = "linux")]
289    {
290      let env = Self {
291        #[cfg(target_os = "linux")]
292        appimage: std::env::var_os("APPIMAGE"),
293        #[cfg(target_os = "linux")]
294        appdir: std::env::var_os("APPDIR"),
295        args_os,
296      };
297      if env.appimage.is_some() || env.appdir.is_some() {
298        // validate that we're actually running on an AppImage
299        // an AppImage is mounted to `/$TEMPDIR/.mount_${appPrefix}${hash}`
300        // see <https://github.com/AppImage/AppImageKit/blob/1681fd84dbe09c7d9b22e13cdb16ea601aa0ec47/src/runtime.c#L501>
301        // note that it is safe to use `std::env::current_exe` here since we just loaded an AppImage.
302        let is_temp = std::env::current_exe()
303          .map(|p| {
304            p.display()
305              .to_string()
306              .starts_with(&format!("{}/.mount_", std::env::temp_dir().display()))
307          })
308          .unwrap_or(true);
309
310        if !is_temp {
311          log::warn!(
312            "`APPDIR` or `APPIMAGE` environment variable found but this application was not detected as an AppImage; this might be a security issue."
313          );
314        }
315      }
316      env
317    }
318    #[cfg(not(target_os = "linux"))]
319    {
320      Self { args_os }
321    }
322  }
323}
324
325/// The result type of `tauri-utils`.
326pub type Result<T> = std::result::Result<T, Error>;
327
328/// The error type of `tauri-utils`.
329#[derive(Debug, thiserror::Error)]
330#[non_exhaustive]
331pub enum Error {
332  /// Target triple architecture error
333  #[error("Unable to determine target-architecture")]
334  Architecture,
335  /// Target triple OS error
336  #[error("Unable to determine target-os")]
337  Os,
338  /// Target triple environment error
339  #[error("Unable to determine target-environment")]
340  Environment,
341  /// Tried to get resource on an unsupported platform
342  #[error("Unsupported platform for reading resources")]
343  UnsupportedPlatform,
344  /// Get parent process error
345  #[error("Could not get parent process")]
346  ParentProcess,
347  /// Get parent process PID error
348  #[error("Could not get parent PID")]
349  ParentPid,
350  /// Get child process error
351  #[error("Could not get child process")]
352  ChildProcess,
353  /// IO error
354  #[error("{0}")]
355  Io(#[from] std::io::Error),
356  /// Invalid pattern.
357  #[error("invalid pattern `{0}`. Expected either `brownfield` or `isolation`.")]
358  InvalidPattern(String),
359  /// Invalid glob pattern.
360  #[cfg(feature = "resources")]
361  #[error("{0}")]
362  GlobPattern(#[from] glob::PatternError),
363  /// Failed to use glob pattern.
364  #[cfg(feature = "resources")]
365  #[error("`{0}`")]
366  Glob(#[from] glob::GlobError),
367  /// Glob pattern did not find any results.
368  #[cfg(feature = "resources")]
369  #[error("glob pattern {0} path not found or didn't match any files.")]
370  GlobPathNotFound(String),
371  /// Error walking directory.
372  #[cfg(feature = "resources")]
373  #[error("{0}")]
374  WalkdirError(#[from] walkdir::Error),
375  /// Not allowed to walk dir.
376  #[cfg(feature = "resources")]
377  #[error(
378    "could not walk directory `{0}`, try changing `allow_walk` to true on the `ResourcePaths` constructor."
379  )]
380  NotAllowedToWalkDir(std::path::PathBuf),
381  /// Resource path doesn't exist
382  #[cfg(feature = "resources")]
383  #[error("resource path `{0}` doesn't exist")]
384  ResourcePathNotFound(std::path::PathBuf),
385}
386
387/// Reconstructs a path from its components using the platform separator then converts it to String and removes UNC prefixes on Windows if it exists.
388pub fn display_path<P: AsRef<Path>>(p: P) -> String {
389  dunce::simplified(&p.as_ref().components().collect::<PathBuf>())
390    .display()
391    .to_string()
392}
393
394/// Write the file only if the content of the existing file (if any) is different.
395///
396/// This will always write unless the file exists with identical content.
397pub fn write_if_changed<P, C>(path: P, content: C) -> std::io::Result<()>
398where
399  P: AsRef<Path>,
400  C: AsRef<[u8]>,
401{
402  if std::fs::read(&path).is_ok_and(|existing| existing == content.as_ref()) {
403    return Ok(());
404  }
405
406  std::fs::write(path, content)
407}