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}