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}