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