How to use Lemonbar with Rust's code?

So there is this taskbar known as Lemonbar for Linux and it takes information (such as texts) and gets piped into the taskbar.

For example like this. echo "Hello Bar" | lemonbar -p

If I wrote this in Rust I can do it like this.

fn main()
{
    println!("Hello, bar!");
}

and then compiled it and run it like this ./bar | lemonbar -p -F#ffffff -B#041024 it works just fine.

But if I want to do something more complicated like this:
echo "%{l}Hello%{c}yellow%{r}Bar" | lemonbar -p -F#000000 -B#FFFF00 How would I output %{l}Hello%{c}yellow%{r}Bar from rust code? Because putting characters like {} Rust does not like that. Is there a way to perhaps escape those characters or something so I can use it?

To print a literal %{l} in Rust using println!(), you need to escape the { and } by doubling them. println!("{{l}}") will print a literal "{l}". For more details, see the std::fmt documentation.

Alternatively, if you have no interpolation, you can print an entire string as a single argument to println!(), and then none of the {} need escaping. That would look like println!("{}", "%{l}Hello%{c}yellow%{r}Bar");.

1 Like

That makes sense

Alternatively, if you have no interpolation

what do you by interpolation in terms of code?

I guess that wasn't exactly the right term. I meant if you don't use {} or similar things inside the format string to insert variables. For instance, if you did this:

println!("%{{l}}Hello%{{c}}{}", "world");

Then using the second technique in my post above wouldn't work, as this is using the format machinery, and the string needs to be the first argument to println!() to actually act as a format string (rather than a string which is just being printed).

1 Like

Ah I get what you mean now, thanks man :slight_smile:

One other question, echo "some characters" | lemonbar -p -F#ffffff -B#041024 With this particular command, is there a way to get Rust to fully run this entire command without pushing it through an external shell script or something?

Is std::process::Command what you're looking for? Something like

Command::new("lemonbar")
    .arg("-p")
   // and so on

I'm not sure how lemonbar wants the "some characters" input, if using an arg doesn't work, you can try calling spawn on the Command to make a Child, get its stdin and then write "some characters" into it.

If I do this then the bar outputs the "some characters" into the bar itself.

Would you happen to know how to do this?

The third example at std::process - Rust shows how to write to the stdin of a process you spawned.

Is it much faster than to use a shell scripting language to run the command?

Potentially slightly, but I doubt there will be many practical differences. I think the main benefit would come from being able to run a single rust binary, rather than having to run a shell script containing the command to run your rust binary + lemonbar.

It's worth noting that when writing to stdin, the write!() macro will provide equivalent functionality to println!() - you'd use it like write!(stdin_handle, "format string", args).unwrap();, just like println!("format string", args);.

1 Like

So if I used the write!() macro, then what is the stdin_handle, would I write echo Hello Bar! inside the stdin_handle or is this a keyword where I have to litereally write stdin_handle?

I don't think you would need echo at all. echo is a command line application who's entire purpose is to write to stdout, and then the | bash primitive forwards everything from echo's stdout into lemonbar's stdin. If you're writing directly to stdin, you don't need any of that.

With that in mind, there are some special things | will do that rust's Command does not do by default. Mainly, it flushes stdin and closes lemonbar's stdin when echo exits. I've included some code here to do that in Rust.

This rust program:

use std::process::{Command, Stdio};
use std::io::prelude::*;

fn main() -> Result<(), std::io::Error> {
    let mut child = Command::new("./lemonbar").stdin(Stdio::piped()).spawn()?;
    
    let mut lemonbar_stdin = child.stdin.take().unwrap();
    
    // write! does not include a newline, writeln! does.
    writeln!(lemonbar_stdin, "Hello world")?;

    // ensure everything's been written to the child.
    lemonbar_stdin.flush()?;
    
    // close stdin to tell lemonbar that no more input is coming.
    drop(lemonbar_stdin);

    child.wait()?;

    Ok(())
}

should be 100% equivalent to this shell script:

echo "Hello world" | ./lemonbar
2 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.