Procházet zdrojové kódy

feat(api/shell): allow open command to open files (#1341)

Noah Klayman před 4 roky
rodič
revize
7c0bf642a9

+ 8 - 5
api/src/shell.ts

@@ -7,7 +7,7 @@ import { invokeTauriCommand } from './helpers/tauri'
  * @param [args] command args
  * @return promise resolving to the stdout text
  */
-async function execute(
+async function execute (
   command: string,
   args?: string | string[]
 ): Promise<string> {
@@ -26,16 +26,19 @@ async function execute(
 }
 
 /**
- * opens an URL on the user default browser
+ * opens a path or URL with the system's default app,
+ * or the one specified with `openWith`
  *
- * @param url the URL to open
+ * @param path the path or URL to open
+ * @param openWith the app to open the file or URL with
  */
-async function open(url: string): Promise<void> {
+async function open (path: string, openWith?: string): Promise<void> {
   return invokeTauriCommand({
     __tauriModule: 'Shell',
     message: {
       cmd: 'open',
-      uri: url
+      path,
+      with: openWith
     }
   })
 }

+ 1 - 0
tauri-api/Cargo.toml

@@ -37,6 +37,7 @@ clap = { version = "=3.0.0-beta.2", optional = true }
 notify-rust = { version = "4.3.0", optional = true }
 once_cell = "1.7.2"
 tauri-hotkey = { git = "https://github.com/tauri-apps/tauri-hotkey-rs", branch = "dev", optional = true }
+open = "1.5.1"
 
 [dev-dependencies]
 quickcheck = "1.0.3"

+ 3 - 0
tauri-api/src/error.rs

@@ -61,4 +61,7 @@ pub enum Error {
   #[cfg(feature = "global-shortcut")]
   #[error("shortcut error: {0}")]
   Shortcut(#[from] tauri_hotkey::Error),
+  /// Shell error.
+  #[error("shell error: {0}")]
+  Shell(String),
 }

+ 2 - 0
tauri-api/src/lib.rs

@@ -15,6 +15,8 @@ pub mod http;
 pub mod path;
 /// The RPC module includes utilities to send messages to the JS layer of the webview.
 pub mod rpc;
+/// The shell api.
+pub mod shell;
 /// TCP ports access API.
 pub mod tcp;
 /// The semver API.

+ 23 - 0
tauri-api/src/shell.rs

@@ -0,0 +1,23 @@
+/// Open path or URL with `with`, or system default
+pub fn open(path: String, with: Option<String>) -> crate::Result<()> {
+  {
+    let exit_status = if let Some(with) = with {
+      open::with(&path, &with)
+    } else {
+      open::that(&path)
+    };
+    match exit_status {
+      Ok(status) => {
+        if status.success() {
+          Ok(())
+        } else {
+          Err(crate::Error::Shell("open command failed".into()))
+        }
+      }
+      Err(err) => Err(crate::Error::Shell(format!(
+        "failed to open: {}",
+        err.to_string()
+      ))),
+    }
+  }
+}

+ 0 - 1
tauri/Cargo.toml

@@ -21,7 +21,6 @@ features = [ "api-all" ]
 serde_json = "1.0"
 serde = { version = "1.0", features = [ "derive" ] }
 base64 = "0.13.0"
-webbrowser = "0.5.5"
 lazy_static = "1.4.0"
 tokio = { version = "1.2", features = ["rt", "rt-multi-thread", "sync"] }
 futures = "0.3"

+ 25 - 14
tauri/examples/api/src-tauri/Cargo.lock

@@ -1,5 +1,7 @@
 # This file is automatically @generated by Cargo.
 # It is not intended for manual editing.
+version = 3
+
 [[package]]
 name = "ab_glyph_rasterizer"
 version = "0.1.4"
@@ -1855,9 +1857,19 @@ dependencies = [
 
 [[package]]
 name = "once_cell"
-version = "1.7.0"
+version = "1.7.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "af8b08b04175473088b46763e51ee54da5f9a164bc162f615b91bc179dbf15a3"
+
+[[package]]
+name = "open"
+version = "1.5.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "10acf907b94fc1b1a152d08ef97e7759650268cf986bf127f387e602b02c7e5a"
+checksum = "b2033f93630dd4b04768ecf5e16bcd3002a89e1e1dbef375bf290dd67e2b7a4d"
+dependencies = [
+ "which",
+ "winapi 0.3.9",
+]
 
 [[package]]
 name = "openssl"
@@ -2766,7 +2778,6 @@ dependencies = [
  "thiserror",
  "tokio",
  "uuid",
- "webbrowser",
  "wry",
 ]
 
@@ -2782,6 +2793,7 @@ dependencies = [
  "http",
  "notify-rust",
  "once_cell",
+ "open",
  "rand 0.8.3",
  "reqwest",
  "rfd",
@@ -3337,17 +3349,6 @@ dependencies = [
  "wasm-bindgen",
 ]
 
-[[package]]
-name = "webbrowser"
-version = "0.5.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ecad156490d6b620308ed411cfee90d280b3cbd13e189ea0d3fada8acc89158a"
-dependencies = [
- "web-sys",
- "widestring",
- "winapi 0.3.9",
-]
-
 [[package]]
 name = "webkit2gtk"
 version = "0.11.0"
@@ -3430,6 +3431,16 @@ dependencies = [
  "cc",
 ]
 
+[[package]]
+name = "which"
+version = "4.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "87c14ef7e1b8b8ecfc75d5eca37949410046e66f15d185c01d70824f1f8111ef"
+dependencies = [
+ "libc",
+ "thiserror",
+]
+
 [[package]]
 name = "widestring"
 version = "0.4.3"

+ 14 - 5
tauri/examples/api/src/components/Window.svelte

@@ -26,7 +26,9 @@
     setIcon
   } = appWindow
 
-  let urlValue = "https://tauri.studio";
+  export let onMessage;
+  let pathValue = "https://tauri.studio";
+  let openWith = "";
   let resizable = true
   let maximized = false
   let decorations = false
@@ -44,7 +46,7 @@
   let windowTitle = 'Awesome Tauri Example!';
 
   function openUrl() {
-    open(urlValue);
+    open(pathValue, !!openWith ? openWith : undefined).catch(onMessage);
   }
 
   function setTitle_() {
@@ -188,8 +190,15 @@
   <button class="button" type="submit">Set title</button>
 </form>
 <form style="margin-top: 24px" on:submit|preventDefault={openUrl}>
-  <input id="url" bind:value={urlValue} />
-  <button class="button" id="open-url">
-    Open URL
+  <div>
+    <label for="path">Path or URL:</label>
+    <input id="path" bind:value={pathValue} />
+  </div>
+  <div>
+    <label for="openWith">Open With (Optional):</label>
+    <input id="openWith" bind:value={openWith} />
+  </div>
+  <button class="button" id="open-path">
+    Open Path or Url
   </button>
 </form>

+ 13 - 29
tauri/src/endpoints/shell.rs

@@ -6,9 +6,14 @@ use serde::Deserialize;
 #[serde(tag = "cmd", rename_all = "camelCase")]
 pub enum Cmd {
   /// The execute script API.
-  Execute { command: String, args: Vec<String> },
-  /// The open URL in browser API
-  Open { uri: String },
+  Execute {
+    command: String,
+    args: Vec<String>,
+  },
+  Open {
+    path: String,
+    with: Option<String>,
+  },
 }
 
 impl Cmd {
@@ -28,37 +33,16 @@ impl Cmd {
           "shell > execute".to_string(),
         ))
       }
-      Self::Open { uri } => {
+      Self::Open { path, with } => {
         #[cfg(shell_open)]
-        {
-          open_browser(uri);
-          Ok(().into())
+        match crate::api::shell::open(path, with) {
+          Ok(_) => Ok(().into()),
+          Err(err) => Err(crate::Error::FailedToExecuteApi(err)),
         }
+
         #[cfg(not(shell_open))]
         Err(crate::Error::ApiNotAllowlisted("shell > open".to_string()))
       }
     }
   }
 }
-
-#[cfg(shell_open)]
-pub fn open_browser(uri: String) {
-  #[cfg(test)]
-  assert!(uri.contains("http://"));
-
-  #[cfg(not(test))]
-  webbrowser::open(&uri).expect("Failed to open webbrowser with uri");
-}
-
-#[cfg(test)]
-mod test {
-  use proptest::prelude::*;
-  // Test the open func to see if proper uris can be opened by the browser.
-  proptest! {
-    #[cfg(shell_open)]
-    #[test]
-    fn check_open(uri in r"(http://)([\\w\\d\\.]+([\\w]{2,6})?)") {
-      super::open_browser(uri);
-    }
-  }
-}