AntonGepting / tmux-interface-rs

Rust language library for communication with TMUX via CLI

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

How to attach to a session from outside tmux?

phansch opened this issue · comments

Hi, thanks for this great library! I might be missing some information, but I'm having trouble attaching to an existing tmux session from outside tmux.

A simplified example:

use tmux_interface::{AttachSession, TmuxInterface};

fn main() {
    let tmux = TmuxInterface::new();

    let attach = NewSession {
        target_session: Some("foobar"),
        ..Default::default()
    };
    match tmux.attach_session(&attach) {
        Ok(output) => {
            if !output.status.success() {
                eprintln!("{}", std::str::from_utf8(&output.stderr).unwrap().trim().to_string());
            }
        },
        Err(e) => {
            eprintln!("err_type: {}; {}", e.err_type, e.err_text);
        }
    }
}

Given an existing foobar session, doing cargo run results in:

attach_session fails with 'open terminal failed: not a terminal'

I suppose it fails because it tries to create a new tmux client within a std::process::Command?

I'm on linux, using urxvt as my terminal, attaching from my terminal directly, works of course. Is there any way to make this work such that executing cargo run will attach to the tmux session directly?

Hi,

thank you for your feedback, for choosing/using the library, and for reporting the bug
with full information needed.

I have tested your example on v0.0.5 (master branch) as well on dev
branch, both with the same error message.

I suppose it fails because it tries to create a new tmux client within a std::process::Command?

Library is using std::process::Command::output() function for executing all tmux subcommands
such as new-session, attach-session... I'm suspecting it has something to
do with pty (not sure). I will check analogous libraries written in other languages.

Is there any way to make this work such that executing cargo run will attach to the tmux session directly?

Before I suggest some temporary workaround, please be advised, the library (especially dev branch) is still in experimental development stage and it is not well tested:

  • many features are not implemented, or can fail
  • some APIs/structures/names/... can be changed in future
  • some design patterns of the library can be changed
  • etc...

So you use unstable versions on your own risk.

Current temporary workaround, which I can suggest:

  • The solution requires tmux_interface 0.0.6 from dev branch:

    git clone --branch dev https://github.com/AntonGepting/tmux-interface-rs.git
    
  • Create new test project:

    cargo new issue1 --bin
    
  • Add dependencies using local path for the library and additional crate in issue1/Cargo.toml

    ...
    
    [dependencies]
    tmux_interface = { version = "0.0.6", path = "../tmux-interface" }
    exec = "0.3.1"
    
  • TmuxInterface.pre_hook structure field will be used for setting up a callback function, which will be executed before/instead of the actual std::process::Command::output(). In the body of the callback function Exec::exec() function is called, which in turn is a wrapper around execvp() system call. Library documentation is still missing for this field because it is not completely implemented yet. At the moment you can look at sources src/tmux_interface.rs and the example below:

    Example as issue1/src/main.rs

    use tmux_interface::{AttachSession, NewSession, TmuxInterface};
    
    fn main() {
        let mut tmux = TmuxInterface::new();
    
        let new_session = NewSession {
            session_name: Some("foobar"),
            detached: Some(true),
            ..Default::default()
        };
        tmux.new_session(Some(&new_session)).unwrap();
    
        let attach = AttachSession {
            target_session: Some("foobar"),
            ..Default::default()
        };
    
        // install the hook
        tmux.pre_hook = Some(Box::new(|bin, options, subcmd| {
            //println!("prehook: {:?} {:?} {:?}", bin, options, subcmd);
    
            // something like this...
            exec::Command::new(bin).args(subcmd).args(options).exec();
            Ok(())
        }));
        tmux.attach_session(Some(&attach)).unwrap();
        // remove the hook,
        // so other functions calls of the same TmuxInterface instance will not be affected
        tmux.pre_hook = None;
    }
    

You can try it, then please give me some feedback if this behavior is something like what you was expecting. Thank you in advance.

This idea was introduced by Jezza. You can take a look at the Jezza's fork and
a small project Jezza's txl-project using it.

If there is some better way (without any additional crates), I would appreciate any suggestions to make a nice straight forward solution.

After some research I think I've got the cause of the issue.

Accordingly to std::process::Command documentation
doc.rust-lang.org:

By default, stdout and stderr are captured (and used to provide the resulting
output). Stdin is not inherited from the parent and any attempt by the child
process to read from the stdin stream will result in the stream immediately
closing.

It seems like when tmux is executed, it's trying to setup stdin, after this
attempt the stream is immediately closed by std::process::Command::output(), that causes
tmux to exit with error open terminal failed: not a terminal.

With the new commit (12e8c44) a small bug-fix was added. Soon after some tests and few preparations will be made, a new minor version of the library will be released.

Please check if it completely solves your problem and has no side effects for your purposes, in order to close this issue.

@AntonGepting Sorry for the late reply. Inheriting stdin seems to have fixed the problem, thanks!