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}