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