tauri_plugin_opener/
lib.rs

1// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
2// SPDX-License-Identifier: Apache-2.0
3// SPDX-License-Identifier: MIT
4
5use std::path::Path;
6
7use tauri::{plugin::TauriPlugin, Manager, Runtime};
8
9#[cfg(mobile)]
10use tauri::plugin::PluginHandle;
11#[cfg(target_os = "android")]
12const PLUGIN_IDENTIFIER: &str = "app.tauri.opener";
13#[cfg(target_os = "ios")]
14tauri::ios_plugin_binding!(init_plugin_opener);
15
16mod commands;
17mod config;
18mod error;
19mod open;
20mod reveal_item_in_dir;
21mod scope;
22mod scope_entry;
23
24pub use error::Error;
25type Result<T> = std::result::Result<T, Error>;
26
27pub use open::{open_path, open_url};
28pub use reveal_item_in_dir::reveal_item_in_dir;
29
30pub struct Opener<R: Runtime> {
31    // we use `fn() -> R` to silence the unused generic error
32    // while keeping this struct `Send + Sync` without requiring `R` to be
33    #[cfg(not(mobile))]
34    _marker: std::marker::PhantomData<fn() -> R>,
35    #[cfg(mobile)]
36    mobile_plugin_handle: PluginHandle<R>,
37    require_literal_leading_dot: Option<bool>,
38}
39
40impl<R: Runtime> Opener<R> {
41    /// Open a url with a default or specific program.
42    ///
43    /// # Examples
44    ///
45    /// ```rust,no_run
46    /// use tauri_plugin_opener::OpenerExt;
47    ///
48    /// tauri::Builder::default()
49    ///   .setup(|app| {
50    ///     // open the given URL on the system default browser
51    ///     app.opener().open_url("https://github.com/tauri-apps/tauri", None::<&str>)?;
52    ///     Ok(())
53    ///   });
54    /// ```
55    ///
56    /// ## Platform-specific:
57    ///
58    /// - **Android / iOS**: Always opens using default program, unless `with` is provided as "inAppBrowser".
59    #[cfg(desktop)]
60    pub fn open_url(&self, url: impl Into<String>, with: Option<impl Into<String>>) -> Result<()> {
61        crate::open::open(url.into(), with.map(Into::into))
62    }
63
64    /// Open a url with a default or specific program.
65    ///
66    /// # Examples
67    ///
68    /// ```rust,no_run
69    /// use tauri_plugin_opener::OpenerExt;
70    ///
71    /// tauri::Builder::default()
72    ///   .setup(|app| {
73    ///     // open the given URL on the system default browser
74    ///     app.opener().open_url("https://github.com/tauri-apps/tauri", None::<&str>)?;
75    ///     Ok(())
76    ///   });
77    /// ```
78    ///
79    /// ## Platform-specific:
80    ///
81    /// - **Android / iOS**: Always opens using default program, unless `with` is provided as "inAppBrowser".
82    #[cfg(mobile)]
83    pub fn open_url(&self, url: impl Into<String>, with: Option<impl Into<String>>) -> Result<()> {
84        self.mobile_plugin_handle
85            .run_mobile_plugin(
86                "open",
87                serde_json::json!({ "url": url.into(), "with": with.map(Into::into) }),
88            )
89            .map_err(Into::into)
90    }
91
92    /// Open a path with a default or specific program.
93    ///
94    /// # Examples
95    ///
96    /// ```rust,no_run
97    /// use tauri_plugin_opener::OpenerExt;
98    ///
99    /// tauri::Builder::default()
100    ///   .setup(|app| {
101    ///     // open the given path on the system default explorer
102    ///     app.opener().open_path("/path/to/file", None::<&str>)?;
103    ///     Ok(())
104    ///   });
105    /// ```
106    ///
107    /// ## Platform-specific:
108    ///
109    /// - **Android / iOS**: Always opens using default program.
110    #[cfg(desktop)]
111    pub fn open_path(
112        &self,
113        path: impl Into<String>,
114        with: Option<impl Into<String>>,
115    ) -> Result<()> {
116        crate::open::open(path.into(), with.map(Into::into))
117    }
118
119    /// Open a path with a default or specific program.
120    ///
121    /// # Examples
122    ///
123    /// ```rust,no_run
124    /// use tauri_plugin_opener::OpenerExt;
125    ///
126    /// tauri::Builder::default()
127    ///   .setup(|app| {
128    ///     // open the given path on the system default explorer
129    ///     app.opener().open_path("/path/to/file", None::<&str>)?;
130    ///     Ok(())
131    ///   });
132    /// ```
133    ///
134    /// ## Platform-specific:
135    ///
136    /// - **Android / iOS**: Always opens using default program.
137    #[cfg(mobile)]
138    pub fn open_path(
139        &self,
140        path: impl Into<String>,
141        _with: Option<impl Into<String>>,
142    ) -> Result<()> {
143        self.mobile_plugin_handle
144            .run_mobile_plugin("open", path.into())
145            .map_err(Into::into)
146    }
147
148    pub fn reveal_item_in_dir<P: AsRef<Path>>(&self, p: P) -> Result<()> {
149        crate::reveal_item_in_dir::reveal_item_in_dir(p)
150    }
151}
152
153/// Extensions to [`tauri::App`], [`tauri::AppHandle`], [`tauri::WebviewWindow`], [`tauri::Webview`] and [`tauri::Window`] to access the opener APIs.
154pub trait OpenerExt<R: Runtime> {
155    fn opener(&self) -> &Opener<R>;
156}
157
158impl<R: Runtime, T: Manager<R>> OpenerExt<R> for T {
159    fn opener(&self) -> &Opener<R> {
160        self.state::<Opener<R>>().inner()
161    }
162}
163
164/// The opener plugin Builder.
165pub struct Builder {
166    open_js_links_on_click: bool,
167}
168
169impl Default for Builder {
170    fn default() -> Self {
171        Self {
172            open_js_links_on_click: true,
173        }
174    }
175}
176
177impl Builder {
178    /// Create a new opener plugin Builder.
179    pub fn new() -> Self {
180        Self::default()
181    }
182
183    /// Whether the plugin should inject a JS script to open URLs in default browser
184    /// when clicking on `<a>` elements that has `_blank` target, or when pressing `Ctrl` or `Shift` while clicking it.
185    ///
186    /// Enabled by default for `http:`, `https:`, `mailto:`, `tel:` links.
187    pub fn open_js_links_on_click(mut self, open: bool) -> Self {
188        self.open_js_links_on_click = open;
189        self
190    }
191
192    /// Build and Initializes the plugin.
193    pub fn build<R: Runtime>(self) -> TauriPlugin<R, Option<config::Config>> {
194        let mut builder = tauri::plugin::Builder::<R, Option<config::Config>>::new("opener")
195            .setup(|app, api| {
196                #[cfg(target_os = "android")]
197                let handle = api.register_android_plugin(PLUGIN_IDENTIFIER, "OpenerPlugin")?;
198                #[cfg(target_os = "ios")]
199                let handle = api.register_ios_plugin(init_plugin_opener)?;
200
201                app.manage(Opener {
202                    #[cfg(not(mobile))]
203                    _marker: std::marker::PhantomData::<fn() -> R>,
204                    #[cfg(mobile)]
205                    mobile_plugin_handle: handle,
206                    require_literal_leading_dot: api
207                        .config()
208                        .as_ref()
209                        .and_then(|c| c.require_literal_leading_dot),
210                });
211                Ok(())
212            })
213            .invoke_handler(tauri::generate_handler![
214                commands::open_url,
215                commands::open_path,
216                commands::reveal_item_in_dir
217            ]);
218
219        if self.open_js_links_on_click {
220            builder = builder.js_init_script(include_str!("init-iife.js").to_string());
221        }
222
223        builder.build()
224    }
225}
226
227/// Initializes the plugin.
228pub fn init<R: Runtime>() -> TauriPlugin<R, Option<config::Config>> {
229    Builder::default().build()
230}