shell.rs 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164
  1. // Copyright 2019-2021 Tauri Programme within The Commons Conservancy
  2. // SPDX-License-Identifier: Apache-2.0
  3. // SPDX-License-Identifier: MIT
  4. use crate::{endpoints::InvokeResponse, Params, Window};
  5. use serde::Deserialize;
  6. #[cfg(shell_execute)]
  7. use std::sync::{Arc, Mutex};
  8. use std::{collections::HashMap, path::PathBuf};
  9. type ChildId = u32;
  10. #[cfg(shell_execute)]
  11. type ChildStore = Arc<Mutex<HashMap<ChildId, crate::api::process::CommandChild>>>;
  12. #[cfg(shell_execute)]
  13. fn command_childs() -> &'static ChildStore {
  14. use once_cell::sync::Lazy;
  15. static STORE: Lazy<ChildStore> = Lazy::new(Default::default);
  16. &STORE
  17. }
  18. #[derive(Deserialize)]
  19. #[serde(untagged)]
  20. pub enum Buffer {
  21. Text(String),
  22. Raw(Vec<u8>),
  23. }
  24. fn default_env() -> Option<HashMap<String, String>> {
  25. Some(Default::default())
  26. }
  27. #[allow(dead_code)]
  28. #[derive(Default, Deserialize)]
  29. #[serde(rename_all = "camelCase")]
  30. pub struct CommandOptions {
  31. #[serde(default)]
  32. sidecar: bool,
  33. cwd: Option<PathBuf>,
  34. // by default we don't add any env variables to the spawned process
  35. // but the env is an `Option` so when it's `None` we clear the env.
  36. #[serde(default = "default_env")]
  37. env: Option<HashMap<String, String>>,
  38. }
  39. /// The API descriptor.
  40. #[derive(Deserialize)]
  41. #[serde(tag = "cmd", rename_all = "camelCase")]
  42. pub enum Cmd {
  43. /// The execute script API.
  44. #[serde(rename_all = "camelCase")]
  45. Execute {
  46. program: String,
  47. args: Vec<String>,
  48. on_event_fn: String,
  49. #[serde(default)]
  50. options: CommandOptions,
  51. },
  52. StdinWrite {
  53. pid: ChildId,
  54. buffer: Buffer,
  55. },
  56. KillChild {
  57. pid: ChildId,
  58. },
  59. Open {
  60. path: String,
  61. with: Option<String>,
  62. },
  63. }
  64. impl Cmd {
  65. #[allow(unused_variables)]
  66. pub fn run<M: Params>(self, window: Window<M>) -> crate::Result<InvokeResponse> {
  67. match self {
  68. Self::Execute {
  69. program,
  70. args,
  71. on_event_fn,
  72. options,
  73. } => {
  74. #[cfg(shell_execute)]
  75. {
  76. let mut command = if options.sidecar {
  77. crate::api::process::Command::new_sidecar(program)?
  78. } else {
  79. crate::api::process::Command::new(program)
  80. };
  81. command = command.args(args);
  82. if let Some(cwd) = options.cwd {
  83. command = command.current_dir(cwd);
  84. }
  85. if let Some(env) = options.env {
  86. command = command.envs(env);
  87. } else {
  88. command = command.env_clear();
  89. }
  90. let (mut rx, child) = command.spawn()?;
  91. let pid = child.pid();
  92. command_childs().lock().unwrap().insert(pid, child);
  93. crate::async_runtime::spawn(async move {
  94. while let Some(event) = rx.recv().await {
  95. if matches!(event, crate::api::process::CommandEvent::Terminated(_)) {
  96. command_childs().lock().unwrap().remove(&pid);
  97. }
  98. let js = crate::api::rpc::format_callback(on_event_fn.clone(), &event)
  99. .expect("unable to serialize CommandEvent");
  100. let _ = window.eval(js.as_str());
  101. }
  102. });
  103. Ok(pid.into())
  104. }
  105. #[cfg(not(shell_execute))]
  106. Err(crate::Error::ApiNotAllowlisted(
  107. "shell > execute".to_string(),
  108. ))
  109. }
  110. Self::KillChild { pid } => {
  111. #[cfg(shell_execute)]
  112. {
  113. if let Some(child) = command_childs().lock().unwrap().remove(&pid) {
  114. child.kill()?;
  115. }
  116. Ok(().into())
  117. }
  118. #[cfg(not(shell_execute))]
  119. Err(crate::Error::ApiNotAllowlisted(
  120. "shell > execute".to_string(),
  121. ))
  122. }
  123. Self::StdinWrite { pid, buffer } => {
  124. #[cfg(shell_execute)]
  125. {
  126. if let Some(child) = command_childs().lock().unwrap().get_mut(&pid) {
  127. match buffer {
  128. Buffer::Text(t) => child.write(t.as_bytes())?,
  129. Buffer::Raw(r) => child.write(&r)?,
  130. }
  131. }
  132. Ok(().into())
  133. }
  134. #[cfg(not(shell_execute))]
  135. Err(crate::Error::ApiNotAllowlisted(
  136. "shell > execute".to_string(),
  137. ))
  138. }
  139. Self::Open { path, with } => {
  140. #[cfg(shell_open)]
  141. match crate::api::shell::open(path, with) {
  142. Ok(_) => Ok(().into()),
  143. Err(err) => Err(crate::Error::FailedToExecuteApi(err)),
  144. }
  145. #[cfg(not(shell_open))]
  146. Err(crate::Error::ApiNotAllowlisted("shell > open".to_string()))
  147. }
  148. }
  149. }
  150. }