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, reveal_items_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(
62            url.into(),
63            with.map(Into::into).filter(|with| with != "inAppBrowser"),
64        )
65    }
66
67    /// Open a url with a default or specific program.
68    ///
69    /// # Examples
70    ///
71    /// ```rust,no_run
72    /// use tauri_plugin_opener::OpenerExt;
73    ///
74    /// tauri::Builder::default()
75    ///   .setup(|app| {
76    ///     // open the given URL on the system default browser
77    ///     app.opener().open_url("https://github.com/tauri-apps/tauri", None::<&str>)?;
78    ///     Ok(())
79    ///   });
80    /// ```
81    ///
82    /// ## Platform-specific:
83    ///
84    /// - **Android / iOS**: Always opens using default program, unless `with` is provided as "inAppBrowser".
85    #[cfg(mobile)]
86    pub fn open_url(&self, url: impl Into<String>, with: Option<impl Into<String>>) -> Result<()> {
87        self.mobile_plugin_handle
88            .run_mobile_plugin(
89                "open",
90                serde_json::json!({ "url": url.into(), "with": with.map(Into::into) }),
91            )
92            .map_err(Into::into)
93    }
94
95    /// Open a path with a default or specific program.
96    ///
97    /// # Examples
98    ///
99    /// ```rust,no_run
100    /// use tauri_plugin_opener::OpenerExt;
101    ///
102    /// tauri::Builder::default()
103    ///   .setup(|app| {
104    ///     // open the given path on the system default explorer
105    ///     app.opener().open_path("/path/to/file", None::<&str>)?;
106    ///     Ok(())
107    ///   });
108    /// ```
109    ///
110    /// ## Platform-specific:
111    ///
112    /// - **Android / iOS**: Always opens using default program.
113    #[cfg(desktop)]
114    pub fn open_path(
115        &self,
116        path: impl Into<String>,
117        with: Option<impl Into<String>>,
118    ) -> Result<()> {
119        crate::open::open(
120            path.into(),
121            with.map(Into::into).filter(|with| with != "inAppBrowser"),
122        )
123    }
124
125    /// Open a path with a default or specific program.
126    ///
127    /// # Examples
128    ///
129    /// ```rust,no_run
130    /// use tauri_plugin_opener::OpenerExt;
131    ///
132    /// tauri::Builder::default()
133    ///   .setup(|app| {
134    ///     // open the given path on the system default explorer
135    ///     app.opener().open_path("/path/to/file", None::<&str>)?;
136    ///     Ok(())
137    ///   });
138    /// ```
139    ///
140    /// ## Platform-specific:
141    ///
142    /// - **Android / iOS**: Always opens using default program.
143    #[cfg(mobile)]
144    pub fn open_path(
145        &self,
146        path: impl Into<String>,
147        _with: Option<impl Into<String>>,
148    ) -> Result<()> {
149        self.mobile_plugin_handle
150            .run_mobile_plugin("open", path.into())
151            .map_err(Into::into)
152    }
153
154    pub fn reveal_item_in_dir<P: AsRef<Path>>(&self, p: P) -> Result<()> {
155        reveal_item_in_dir(p)
156    }
157
158    pub fn reveal_items_in_dir<I, P>(&self, paths: I) -> Result<()>
159    where
160        I: IntoIterator<Item = P>,
161        P: AsRef<Path>,
162    {
163        reveal_items_in_dir(paths)
164    }
165}
166
167/// Extensions to [`tauri::App`], [`tauri::AppHandle`], [`tauri::WebviewWindow`], [`tauri::Webview`] and [`tauri::Window`] to access the opener APIs.
168pub trait OpenerExt<R: Runtime> {
169    fn opener(&self) -> &Opener<R>;
170}
171
172impl<R: Runtime, T: Manager<R>> OpenerExt<R> for T {
173    fn opener(&self) -> &Opener<R> {
174        self.state::<Opener<R>>().inner()
175    }
176}
177
178/// The opener plugin Builder.
179pub struct Builder {
180    open_js_links_on_click: bool,
181}
182
183impl Default for Builder {
184    fn default() -> Self {
185        Self {
186            open_js_links_on_click: true,
187        }
188    }
189}
190
191impl Builder {
192    /// Create a new opener plugin Builder.
193    pub fn new() -> Self {
194        Self::default()
195    }
196
197    /// Whether the plugin should inject a JS script to open URLs in default browser
198    /// when clicking on `<a>` elements that has `_blank` target, or when pressing `Ctrl` or `Shift` while clicking it.
199    ///
200    /// Enabled by default for `http:`, `https:`, `mailto:`, `tel:` links.
201    pub fn open_js_links_on_click(mut self, open: bool) -> Self {
202        self.open_js_links_on_click = open;
203        self
204    }
205
206    /// Build and Initializes the plugin.
207    pub fn build<R: Runtime>(self) -> TauriPlugin<R, Option<config::Config>> {
208        let mut builder = tauri::plugin::Builder::<R, Option<config::Config>>::new("opener")
209            .setup(|app, api| {
210                #[cfg(target_os = "android")]
211                let handle = api.register_android_plugin(PLUGIN_IDENTIFIER, "OpenerPlugin")?;
212                #[cfg(target_os = "ios")]
213                let handle = api.register_ios_plugin(init_plugin_opener)?;
214
215                app.manage(Opener {
216                    #[cfg(not(mobile))]
217                    _marker: std::marker::PhantomData::<fn() -> R>,
218                    #[cfg(mobile)]
219                    mobile_plugin_handle: handle,
220                    require_literal_leading_dot: api
221                        .config()
222                        .as_ref()
223                        .and_then(|c| c.require_literal_leading_dot),
224                });
225                Ok(())
226            })
227            .invoke_handler(tauri::generate_handler![
228                commands::open_url,
229                commands::open_path,
230                commands::reveal_item_in_dir,
231            ]);
232
233        if self.open_js_links_on_click {
234            builder = builder.js_init_script(include_str!("init-iife.js").to_string());
235        }
236
237        builder.build()
238    }
239}
240
241/// Initializes the plugin.
242pub fn init<R: Runtime>() -> TauriPlugin<R, Option<config::Config>> {
243    Builder::default().build()
244}