Skip to main content

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