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