tauri_plugin_shell/
lib.rs1#![doc(
8 html_logo_url = "https://github.com/tauri-apps/tauri/raw/dev/app-icon.png",
9 html_favicon_url = "https://github.com/tauri-apps/tauri/raw/dev/app-icon.png"
10)]
11
12use std::{
13 collections::HashMap,
14 ffi::OsStr,
15 path::Path,
16 sync::{Arc, Mutex},
17};
18
19use process::{Command, CommandChild};
20use regex::Regex;
21use tauri::{
22 AppHandle, Manager, RunEvent, Runtime,
23 plugin::{Builder, TauriPlugin},
24};
25
26mod commands;
27mod config;
28mod error;
29#[deprecated(since = "2.1.0", note = "Use tauri-plugin-opener instead.")]
30#[allow(deprecated)]
31pub mod open;
32pub mod process;
34mod scope;
35mod scope_entry;
36
37pub use error::Error;
38type Result<T> = std::result::Result<T, Error>;
39
40#[cfg(mobile)]
41use tauri::plugin::PluginHandle;
42#[cfg(target_os = "android")]
43const PLUGIN_IDENTIFIER: &str = "app.tauri.shell";
44#[cfg(target_os = "ios")]
45tauri::ios_plugin_binding!(init_plugin_shell);
46
47type ChildStore = Arc<Mutex<HashMap<u32, CommandChild>>>;
48
49pub struct Shell<R: Runtime> {
53 #[allow(dead_code)]
54 app: AppHandle<R>,
55 #[cfg(mobile)]
56 mobile_plugin_handle: PluginHandle<R>,
57 open_scope: scope::OpenScope,
58 children: ChildStore,
59}
60
61impl<R: Runtime> Shell<R> {
62 pub fn command(&self, program: impl AsRef<OsStr>) -> Command {
64 Command::new(program)
65 }
66
67 pub fn sidecar(&self, program: impl AsRef<Path>) -> Result<Command> {
72 Command::new_sidecar(program)
73 }
74
75 #[cfg(desktop)]
79 #[deprecated(since = "2.1.0", note = "Use tauri-plugin-opener instead.")]
80 #[allow(deprecated)]
81 pub fn open(&self, path: impl Into<String>, with: Option<open::Program>) -> Result<()> {
82 open::open(None, path.into(), with)
83 }
84
85 #[cfg(mobile)]
89 #[deprecated(since = "2.1.0", note = "Use tauri-plugin-opener instead.")]
90 #[allow(deprecated)]
91 pub fn open(&self, path: impl Into<String>, _with: Option<open::Program>) -> Result<()> {
92 self.mobile_plugin_handle
93 .run_mobile_plugin("open", path.into())
94 .map_err(Into::into)
95 }
96}
97
98pub trait ShellExt<R: Runtime> {
101 fn shell(&self) -> &Shell<R>;
114}
115
116impl<R: Runtime, T: Manager<R>> ShellExt<R> for T {
117 fn shell(&self) -> &Shell<R> {
118 self.state::<Shell<R>>().inner()
119 }
120}
121
122pub fn init<R: Runtime>() -> TauriPlugin<R, Option<config::Config>> {
135 Builder::<R, Option<config::Config>>::new("shell")
136 .js_init_script(include_str!("init-iife.js").to_string())
137 .invoke_handler(tauri::generate_handler![
138 commands::execute,
139 commands::spawn,
140 commands::stdin_write,
141 commands::kill,
142 commands::open
143 ])
144 .setup(|app, api| {
145 let default_config = config::Config::default();
146 let config = api.config().as_ref().unwrap_or(&default_config);
147
148 #[cfg(target_os = "android")]
149 let handle = api.register_android_plugin(PLUGIN_IDENTIFIER, "ShellPlugin")?;
150 #[cfg(target_os = "ios")]
151 let handle = api.register_ios_plugin(init_plugin_shell)?;
152
153 app.manage(Shell {
154 app: app.clone(),
155 children: Default::default(),
156 open_scope: open_scope(&config.open),
157
158 #[cfg(mobile)]
159 mobile_plugin_handle: handle,
160 });
161 Ok(())
162 })
163 .on_event(|app, event| {
164 if let RunEvent::Exit = event {
165 let shell = app.state::<Shell<R>>();
166 let children = {
167 let mut lock = shell.children.lock().unwrap();
168 std::mem::take(&mut *lock)
169 };
170 for child in children.into_values() {
171 let _ = child.kill();
172 }
173 }
174 })
175 .build()
176}
177
178fn open_scope(open: &config::ShellAllowlistOpen) -> scope::OpenScope {
179 let shell_scope_open = match open {
180 config::ShellAllowlistOpen::Flag(false) => None,
181 config::ShellAllowlistOpen::Unset | config::ShellAllowlistOpen::Flag(true) => {
183 Some(Regex::new(r"^((mailto:\w+)|(tel:\w+)|(https?://\w+)).+").unwrap())
184 }
185 config::ShellAllowlistOpen::Validate(validator) => {
186 let regex = format!("^{validator}$");
187 let validator =
188 Regex::new(®ex).unwrap_or_else(|e| panic!("invalid regex {regex}: {e}"));
189 Some(validator)
190 }
191 };
192
193 scope::OpenScope {
194 open: shell_scope_open,
195 }
196}