Skip to main content

tauri_plugin_shell/
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
5//! Access the system shell. Allows you to spawn child processes.
6//!
7//! To open files and URLs with their default application, use `tauri-plugin-opener`.
8
9#![doc(
10    html_logo_url = "https://github.com/tauri-apps/tauri/raw/dev/app-icon.png",
11    html_favicon_url = "https://github.com/tauri-apps/tauri/raw/dev/app-icon.png"
12)]
13
14use std::{
15    collections::HashMap,
16    ffi::OsStr,
17    path::Path,
18    sync::{Arc, Mutex},
19};
20
21use process::{Command, CommandChild};
22use tauri::{
23    AppHandle, Manager, RunEvent, Runtime,
24    plugin::{Builder, TauriPlugin},
25};
26
27mod commands;
28mod error;
29pub mod process;
30mod scope;
31mod scope_entry;
32
33pub use error::Error;
34type Result<T> = std::result::Result<T, Error>;
35
36type ChildStore = Arc<Mutex<HashMap<u32, CommandChild>>>;
37
38/// Access to the shell APIs.
39///
40/// Get an instance of this type with [`ShellExt::shell`].
41pub struct Shell<R: Runtime> {
42    #[allow(dead_code)]
43    app: AppHandle<R>,
44    children: ChildStore,
45}
46
47impl<R: Runtime> Shell<R> {
48    /// Creates a new Command for launching the given program.
49    pub fn command(&self, program: impl AsRef<OsStr>) -> Command {
50        Command::new(program)
51    }
52
53    /// Creates a new Command for launching the given sidecar program.
54    ///
55    /// A sidecar program is a embedded external binary in order to make your application work
56    /// or to prevent users having to install additional dependencies (e.g. Node.js, Python, etc).
57    pub fn sidecar(&self, program: impl AsRef<Path>) -> Result<Command> {
58        Command::new_sidecar(program)
59    }
60}
61
62/// Extensions to [`tauri::App`], [`tauri::AppHandle`], [`tauri::WebviewWindow`],
63/// [`tauri::Webview`] and [`tauri::Window`] to access the shell APIs.
64pub trait ShellExt<R: Runtime> {
65    /// Gets the shell APIs.
66    ///
67    /// # Examples
68    ///
69    /// ```no_run
70    /// use tauri_plugin_shell::ShellExt;
71    ///
72    /// async fn run_echo<R: tauri::Runtime>(app: &tauri::AppHandle<R>) {
73    ///     let output = app.shell().command("echo").args(["hello"]).output().await.unwrap();
74    ///     println!("{}", String::from_utf8_lossy(&output.stdout));
75    /// }
76    /// ```
77    fn shell(&self) -> &Shell<R>;
78}
79
80impl<R: Runtime, T: Manager<R>> ShellExt<R> for T {
81    fn shell(&self) -> &Shell<R> {
82        self.state::<Shell<R>>().inner()
83    }
84}
85
86pub fn init<R: Runtime>() -> TauriPlugin<R> {
87    Builder::new("shell")
88        .initialization_script(include_str!("init-iife.js").to_string())
89        .invoke_handler(tauri::generate_handler![
90            commands::execute,
91            commands::spawn,
92            commands::stdin_write,
93            commands::kill,
94        ])
95        .setup(|app, _api| {
96            app.manage(Shell {
97                app: app.clone(),
98                children: Default::default(),
99            });
100            Ok(())
101        })
102        .on_event(|app, event| {
103            if let RunEvent::Exit = event {
104                let shell = app.state::<Shell<R>>();
105                let children = {
106                    let mut lock = shell.children.lock().unwrap();
107                    std::mem::take(&mut *lock)
108                };
109                for child in children.into_values() {
110                    let _ = child.kill();
111                }
112            }
113        })
114        .build()
115}