#sidecar in rust backend with argument

127 messages · Page 1 of 1 (latest)

signal tulip
#

I am trying to run a binary with an argument as a sidecar but I cannot understand how to do it in the rust backend,

what I did so far:

  1. tauri.conf.json:
"tauri": {
    "allowlist": {
      "all": false,
      "shell": {
        "all": false,
        "open": true,
        "sidecar": true,
        "scope": [
          { "name": "binaries/rusty_torrent", "sidecar": true, "args": ["-V"] }
        ]
      }
    },
    "bundle": {
      "active": true,
      "targets": "all",
      "identifier": "com.tauri.dev",
      "icon": [
        "icons/32x32.png",
        "icons/128x128.png",
        "icons/[email protected]",
        "icons/icon.icns",
        "icons/icon.ico"
      ],
      "externalBin": [
        "binaries/rusty_torrent"
      ]
  1. the binary is in src-tauri/binaries/rusty_torrent-x86_64-pc-windows-msvc.exe
  2. Cargo.toml
[dependencies]
tauri = { version = "1.4", features = [ "shell-sidecar", "shell-open", "process-command-api"] }

continue in first reply as there is a 2000 char limit...

anyone got any tips, haven't developed with tauri lately and I am a begginer developer

thanks

#

4, main.rs

// Prevents additional console window on Windows in release, DO NOT REMOVE!!
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]

use tauri::api::process::Command;
use tauri::api::process::CommandEvent;

// Learn more about Tauri commands at https://tauri.app/v1/guides/features/command
#[tauri::command]
fn greet(name: &str, window: tauri::Window) -> String {
    println!("one");
    let (mut rx, mut child) = Command::new_sidecar("rusty_torrent")
  .expect("failed to create `rusty_torrent` binary command")
  .spawn()
  .expect("Failed to spawn sidecar");
println!("two");
tauri::async_runtime::spawn(async move {
  println!("three");
    // read events such as stdout
    while let Some(event) = rx.recv().await {
      println!("four");
      if let CommandEvent::Stdout(line) = event {
        println!("five");
        window
          .emit("message", Some(format!("'{}'", line)))
          .expect("failed to emit event");
        println!("six");
        // write to stdin
        child.write("message from Rust\n".as_bytes()).unwrap();
        println!("seven");
      }
      println!("eight");
    }
    println!("nine");
  });
    format!("Hello, {}! output: ", name)
}



fn main() {
    tauri::Builder::default()
        .invoke_handler(tauri::generate_handler![greet])
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}
  1. haven't changed the main.js

  2. rust terminal output:

pnpm tauri dev

> [email protected] tauri C:\1ZenFOSS\ZenProjects1\FreeBeerF\FreeBeerF
> tauri "dev"

        Info Watching C:\1ZenFOSS\ZenProjects1\FreeBeerF\FreeBeerF\src-tauri for changes...
   Compiling freebeerf v0.0.0 (C:\1ZenFOSS\ZenProjects1\FreeBeerF\FreeBeerF\src-tauri)
    Finished dev [unoptimized + debuginfo] target(s) in 3.80s
one
two
three
four
eight
four
eight
four
eight
four
eight
four
eight
four
eight
four
eight
four
eight
nine
signal tulip
#

The wierd thing is that the if closure is not activated after println four

signal tulip
#

Also i cant figure out what to put as a parameter in the tauri command in js side as in rust side i got argument window: tauri::Window

stark reef
#

I have the same question. How to pass the argument in Rust?
Should it be Command::new_sidecar("rusty_torrent -arg") ?

I also tried this:
let (mut rx, mut child) = Command::new_sidecar("nomad")
.args("agent")
-^^^^ method not found in Result<Command, Error>

signal tulip
#

should I just use rust commandExt to run a binary that I will have bundled with my program?

#

and what is the way to bundle a cargo binary with my program?

signal tulip
grave rampart
signal tulip
#

Nvm

signal tulip
signal tulip
#

how to print the output from new_sidecar in rust terminal?

signal tulip
#

It also implements Debug trait, so just println!("{cmd:?}")

#

@signal tulip are you ok?

#

one more question:

fn main() {
  tauri::Builder::default()
    .setup(|app| {
      let window = app.get_window("main").unwrap();
      tauri::async_runtime::spawn(async move {
        let (mut rx, mut child) = Command::new_sidecar("rusty_torrent")
          .expect("failed to setup `rusty_torrent` sidecar")
          .spawn()
          .expect("Failed to spawn packaged node");

        let mut i = 0;
        while let Some(event) = rx.recv().await {
          if let CommandEvent::Stdout(line) = event {
            window
              .emit("message", Some(format!("'{}'", line)))
              .expect("failed to emit event");
            i += 1;
            if i == 4 {
              child.write("message from Rust\n".as_bytes()).unwrap();
              i = 0;
            }
          }
        }
      });

      Ok(())
    })
    .invoke_handler(tauri::generate_handler![greet])
    .run(tauri::generate_context!())
    .expect("error while running tauri application");
}

this code is in main I want it to be in another function so I can call it. how do I go about doing that?

signal tulip
signal tulip
#

I see

#

.spawn() calls tauri::async_runtime::channel() internally which is just a mpsc channel

grave rampart
#

what does the "pinched fingers" emoji mean to you @signal tulip ?

#

just asking because you use it a lot haha

signal tulip
#

thank you, you rock

grave rampart
#

ahhh - because in Italy it means something quite a bit different AFAIK

signal tulip
#

its italian for awesome

grave rampart
#

really?

signal tulip
#

what?

grave rampart
#

well, it can mean a lot of things

signal tulip
#

yeah

grave rampart
#

I am not Italian, but my understanding has always been in line with "What do you want?" or "What are you saying?"

#

anyway not a big deal

signal tulip
#

well I have seen italians use it for nice pasta or nice pizza

#

etc

grave rampart
#

that's why @signal tulip asked if you were ok (I think)

signal tulip
#

oh ok

#
use tauri::api::process::{Command,CommandEvent};

#[tauri::command]
fn greet(name: &str) -> String {
      tauri::async_runtime::spawn(async move {
        let (mut rx, mut child) = Command::new_sidecar("rusty_torrent")
          .expect("failed to setup `rusty_torrent` sidecar")
          .spawn()
          .expect("Failed to spawn packaged node");

        let mut i = 0;
        while let Some(event) = rx.recv().await {
          if let CommandEvent::Stdout(line) = event {
            println!("got: {}", line);
            i += 1;
            if i == 4 {
              child.write("message from Rust\n".as_bytes()).unwrap();
              i = 0;
            }
          }
        }
      });
    format!("Hello, {}! output: ", name)
}

this doesnt result in anything writen to the rust console, only the app works (entering a name and clicking go works)

#

is my tauri.conf.json ok?

"tauri": {
    "allowlist": {
      "all": false,
      "shell": {
        "all": false,
        "open": true,
        "sidecar": true,
        "scope": [
          { "name": "binaries/rusty_torrent", "sidecar": true, "args": ["-V"] }
        ]
      }
    },
    "bundle": {
      "active": true,
      "targets": "all",
      "identifier": "com.tauri.dev",
      "icon": [
        "icons/32x32.png",
        "icons/128x128.png",
        "icons/[email protected]",
        "icons/icon.icns",
        "icons/icon.ico"
      ],
      "externalBin": [
        "binaries/rusty_torrent"
      ]
    },
    "security": {
      "csp": null
    },
#

Are you sure the command is invoked?

#

seems to me like the sidecar is not running? is validation mandatory? or "security":{"csp":null}` wrong?

signal tulip
#

No, you have to invoke the greet command for the code to run

#

but I reckon the rusty_torrent command is not running thus the if close is not running

#

invoke("greet") from JS side

#

Or you can move it to the setup function

#

greet is invoked by entering a name in the default app and clicking go

#

I get the
format!("Hello, {}! output: ", name)
in the program window so greet is running

#

ok sidecar is running but println!("got line: {}",line); is not running but println!("got child: {}",child; is running and gives me this output in rust!

got child: CommandChild { inner: SharedChild { child: Mutex { data: Child { stdin: None, stdout: None, stderr: None, .. }, poisoned: false, .. }, state_lock: Mutex { data: Exited(ExitStatus(ExitStatus(2))), poisoned: false, .. }, state_condvar: Condvar { .. } }, stdin_writer: PipeWriter(File { handle: 0x528 }) }

#[tauri::command]
fn greet(name: &str) -> String {
tauri::async_runtime::spawn(async move {
let (mut rx, mut child) = Command::new_sidecar("rusty_torrent")
.expect("failed to setup rusty_torrent sidecar")
.spawn()
.expect("Failed to spawn packaged node");

    let mut i = 0;
    while let Some(event) = rx.recv().await {
      if let CommandEvent::Stdout(line) = event {
        println!("got line: {}", line);
        i += 1;
        if i == 4 {
          child.write("message from Rust\n".as_bytes()).unwrap();
          i = 0;
        }
      }
    }
    println!("got child: {:?}", child);
  });
  
format!("Hello, {}! output: ", name)

}

#

why is the line println not working? anyone got any idea?

#

it says no stdout/in/err

#

so the sidecar is not running?

signal tulip
#

Tauri needs a tauri.conf.json checker

signal tulip
#

I think i have it installed?

#

Not sure if it validates the configuration file though

#

Also tauri is focusing on the js side, the rust side is under documented

#

I have used tauri before and anything rust side i had to troubleshoot extensivly

#

Should i use js side sidecar invocation? Thats what the tauri docs suggest

grave rampart
signal tulip
#

I try to look into them

grave rampart
#

I guess we made the assumption that rustdevs would be doing that more

#

Would you mind visiting the docs channel and leave your feedback there? Its not likely to be seen by as many people here in the support threads

signal tulip
#

sure

#

one question

#

sidecar works only on the js side with js invocation?

#
// `new_sidecar()` expects just the filename, NOT the whole path like in JavaScript
let (mut rx, mut child) = Command::new_sidecar("my-sidecar")
  .expect("failed to create `my-sidecar` binary command")
  .spawn()
  .expect("Failed to spawn sidecar");

tauri::async_runtime::spawn(async move {
  // read events such as stdout
  while let Some(event) = rx.recv().await {
    if let CommandEvent::Stdout(line) = event {
      window
        .emit("message", Some(format!("'{}'", line)))
        .expect("failed to emit event");
      // write to stdin
      child.write("message from Rust\n".as_bytes()).unwrap();
    }
  }
});

in this example what do I need to do to get the window?

grave rampart
#

No. Its likely the JS side is more used and tested. Invoke only sends a message to the rust side

signal tulip
#

ok ty

signal tulip
#

is there a way to bundle an executable in tauri windows without sidecar?

#

so i can use command or commandExt?

signal tulip
#

managed to make it work with sidecar using JS... if someone wants to elaborate with me so I can use Rust backend to use sidecar...

tired needle
#

Maybe I missed it but I didn't see anyone address the first question about adding arguments.

let (mut rx, mut child) = Command::new_sidecar("my-sidecar")
  .expect("failed to create `my-sidecar` binary command")
  // Takes `IntoIterator<&str>` types like `["-V"]`
  .args(["-V"])
  .spawn()
  .expect("Failed to spawn sidecar");
#

Sorry, misread those docs. It doesn't take a &str, it only takes the IntoIterator.

#

Based on your config in the first message, my-sidecar would be replaced with binaries/rusty_torrent.

signal tulip
#

thank you very much

#

yeah after fidling around you have to put .args after the .expect but still doesnt work

also I corrected the binaries/rusty_torrent thank you

tired needle
# stark reef I have the same question. How to pass the argument in Rust? Should it be Comman...

The code you're using doesn't check if there is an error. You need to determine if the sidecar exists before you can start adding to it. The example above uses .expect but you could use a match if you wish to gracefully handle the error. An example of match:

match Command::new_sidecar("nomad") {
  Ok(sidecar) => {
    match sidecar.args(["agent"]).spawn() {
      Ok((mut rx, mut child)) => {
        // Whatever you were going to do with these.
      },
      Err(e) => println!("error spawning `nomad` sidecar: {}", e),
    }
  },
  Err(e) => println!("error creating `nomad` sidecar: {}", e),
}
tired needle
signal tulip
#

I mean the if statement from the rust example in the tauri docs doesnt even trigger

tired needle
#

That's a little strange. Does your sidecar binary output anything when you run it in a terminal?

signal tulip
#

yes

#
rusty_torrent 0.9.3```
#

works with js sidecar too

#

so its something with the rust side thats the issue

tired needle
#

I wonder if this is a race condition, since you're spawning the process then setting up the listener. To be perfectly honest, I believe the API should be refactored to create the listener before the call to spawn().

signal tulip
#

maybe use .into ?

tired needle
signal tulip
#

I dont recall the exact word

#

I just saw a streamer trying to do it in rust and in a bit he gave up and did it in js instead

#

like if a dev that knows what he is doing and streams and uploads to youtube it tells a story of how rust in tauri is not documented even basic "it works" and even if it doesnt work there is no explanation of each component so you can troubleshoot when an example breaks with an update

#

I think the whole purpose of tauri is to run anything on rust side so it runs faster and stable

tired needle
#

Docs and mobile are the major focus of Tauri v2 and I plan to make a lot of examples for common questions I've seen here.

signal tulip
#

and the whole rust ecosystem of programms and code

signal tulip
#

ping me if you want a tester

#

I mean I will try all the examples and see where the new docs lack

tired needle
#

Getting my hands on a device that can run a recent version of macOS has proven to be the biggest challenge to my testing so far 😆

signal tulip
#

I can understand

#

main.js

const { invoke } = window.__TAURI__.tauri

const { Command } = window.__TAURI__.shell
const rustyTorrent = Command.sidecar('binaries/rusty-torrent', ["-V"])

let greetInputEl;
let greetMsgEl;
let rustyOutputMsgEl;

async function greet() {
  // Learn more about Tauri commands at https://tauri.app/v1/guides/features/command
  greetMsgEl.textContent = await invoke("greet", { name: greetInputEl.value});
}
async function fn_rusty_torrent() {
   let rustyOutput = await rustyTorrent.execute();
   console.log(rustyOutput);
   rustyOutputMsgEl.innerHTML = "<p>stdout: «" + rustyOutput.stdout + "»</p>";
   rustyOutputMsgEl.innerHTML += "<p>stderr: «" + rustyOutput.stderr + "»</p>";
   rustyOutputMsgEl.innerHTML += "<p>code: «" + rustyOutput.code + "»</p>";
   rustyOutputMsgEl.innerHTML += "<p>signal: «" + rustyOutput.signal + "»</p>";
}



window.addEventListener("DOMContentLoaded", () => {
  greetInputEl = document.querySelector("#greet-input");
  greetMsgEl = document.querySelector("#greet-msg");
  rustyOutputMsgEl = document.querySelector("#rusty-torrent-output");
  document.querySelector("#greet-form").addEventListener("submit", (e) => {
    e.preventDefault();
    greet();
  });
  document.querySelector("#run-sidecar-button").addEventListener(("click"), (e) => {
    e.preventDefault();
    fn_rusty_torrent();
  });
});

the html for the button and p tag that displays the output

<button id="run-sidecar-button">Run Sidecar Rusty Torrent</button>
      <p id="rusty-torrent-output"></p>
#

also the docs say that you need:
const {Command} = window.__TAURI__.shell.Command
but I managed to make sidecar work with the following:
const {Command} = window.__TAURI__.shell as you see in my code

tired needle
#

If you just want the version output, you can use .output() to collect all of the output instead.

signal tulip
#

I would like to control the rusty_torrent so I can manage torrents

#

can I do it with output()?

tired needle
#

Not if you need it to keep running while you interact with it. You'll definitely need .spawn().

signal tulip
#

yeap

#

thank you very much

#

ping me when you want me to take a look at the new docs!

tired needle
signal tulip
#

sure

tired needle
#

Ah, I see what you're talking about now. If you remove the { and }, it will stop resolving that. Seems to be a JavaScript shorthand syntax of sorts.

#

Similar to how import and export are declared.

signal tulip
#

I am talking about .Command

#

Removing that works

tired needle
#

To my knowledge, the examples use const Command = window.__TAURI__.shell.Command.
const { Command } = window.__TAURI__.shell is similar to import { Command } from '@tauri-apps/api/shell', just for browsers instead of Node.js.

signal tulip
#

found the solution based on the github issue here: https://github.com/tauri-apps/tauri/discussions/4440

what worked:

#[tauri::command]
async fn spawn_sidecar(/*text: &str*/) -> Result<String, String> {
  let (mut rx, mut child) = Command::new_sidecar("rusty-torrent")
    .expect("failed to create `rusty-torrent` binary command")
    .args(["-V"])
    .spawn()
    .expect("Failed to spawn sidecar");

  child.write("message from Rust\n".as_bytes()).expect("Failed to write to sidecar");
  drop(child);

  let mut output = String::new();
  while let Some(event) = rx.recv().await {
    if let CommandEvent::Stdout(line) = event {
      output.push_str(&line);
    }
  }

  Ok(output)
}

with:

use tauri::api::process::{Command,CommandEvent};
GitHub

Hi, I'm using Tauri's sidecar feature to bundle an esbuild binary. This works well so far and with the Command.sidecar API, I'm able to use it. However, one issue arose: esbuild offers ...

signal tulip
# tired needle The code you're using doesn't check if there is an error. You need to determine ...

trying your code now I think I will need some help: this is my code, and in images is the rust-analyzer info:

#[tauri::command]
async fn spawn_sidecar(/*text: &str*/) -> Result<String, String> {
  match Command::new_sidecar("rusty-torrent") {
    Ok(sidecar) => {
      match sidecar.args(["-V"]).spawn() {
        Ok(mut rx, mut child) => {
          child.write("message from Rust\n".as_bytes()).expect("Failed to write to sidecar");
          drop(child);
          let mut output = String::new();
          while let Some(event) = rx.recv().await {
            if let CommandEvent::Stdout(line) = event {
              output.push_str(&line);
            }
          }
          Ok(output)
        },
        Err(e) => println!("error spawning `rusty-torrent` sidecar: {}", e),
      }
    },
    Err(e) => println!("error creating `rusty-torrent` sidecar: {}", e),
  }
}
signal tulip
#

made it work, managed to fix those 2 issues that produced ton more issues along the way, fixed them all,

here is the code that works based on your match and error handling code!!!

// Prevents additional console window on Windows in release, DO NOT REMOVE!!
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]

use tauri::api::process::{Command,CommandEvent};



// Learn more about Tauri commands at https://tauri.app/v1/guides/features/command
#[tauri::command]
fn greet(name: &str) -> String {
  format!("Hello, {}!", name)
}

#[tauri::command]
async fn spawn_sidecar(/*text: &str*/) -> String {
  match Command::new_sidecar("rusty-torrent") {
    Ok(sidecar) => {
      match sidecar.args(["-V"]).spawn() {
        Ok((mut rx, mut child)) => {
          child.write("message from Rust\n".as_bytes()).expect("Failed to write to sidecar");
          drop(child);
          let mut output = String::new();
          while let Some(event) = rx.recv().await {
            if let CommandEvent::Stdout(line) = event {
              output.push_str(&line);
            }
          }
          format!("{}", output)
        },
        Err(e) => { 
          println!("error spawning `rusty-torrent` sidecar: {}", e);
          format!("{}", e)
        }
      }
    },
    Err(e) => {
      println!("error creating `rusty-torrent` sidecar: {}", e);
      format!("{}",e)
    }
  }
}

fn main() {
  tauri::Builder::default()
    .invoke_handler(tauri::generate_handler![greet,spawn_sidecar])
    .run(tauri::generate_context!())
    .expect("error while running tauri application");
}
signal tulip
#

when doing drop(child) does it force end the sidecar?

tired needle
#

I'm not seeing any overrides for drop in Tauri or the library it uses. According to Rust's docs, the Child continues to run when dropped.

tired needle
#

Sorry about the bug in my code. Forgot to make it a tuple. I've corrected the example. I need to stop writing code in Discord.