Search code examples
macosvimgostdintty

Why does this exec.Command to a different opened tty not work properly


Can anybody please help me figure out what I'm doing wrong here.

I'm trying to execute a command (opening vim in this case) that runs in a different tty, in this case /dev/ttys001, which is opened in another tab in my terminal.

Running the code below does render vim in /dev/ttys001's window, however, actually typing to stdin from that window doesn't register properly.

Any advice is much appreciated!

package main

import (
    "log"
    "os"
    "os/exec"
)

func main() {
    tty, err := os.OpenFile("/dev/ttys001", os.O_RDWR, os.ModePerm)
    if err != nil {
        log.Fatalln(err)
    }
    defer tty.Close()

    c := exec.Command("vim")
    c.Stdin = tty
    c.Stdout = tty
    c.Stderr = tty
    if err := c.Run(); err != nil {
        log.Fatalln(err)
    }
}

I have also tried setting the command's SysProcAttr field with the following code but receive the error: fork/exec /usr/local/bin/vim: inappropriate ioctl for device.

procAttr := &syscall.SysProcAttr{
    Setpgid:    true,
    Ctty:       int(tty.Fd()),
    Foreground: true,
}
c.SysProcAttr = procAttr

Solution

  • For anybody who's interested, I came up with a solution, but I ended up having to use system calls instead of the os/exec package's functions.

    package main
    
    import (
        "log"
        "os"
        "syscall"
        "unsafe"
    )
    
    var tty = "/dev/ttys001"
    var cmd = "vim\n"
    
    func main() {
        ttyFile, err := os.Open(tty)
        if err != nil {
            log.Fatalln(err)
        }
        defer ttyFile.Close()
    
        cbs, err := syscall.ByteSliceFromString(cmd)
        if err != nil {
            log.Fatalln(err)
        }
    
        var eno syscall.Errno
        for _, c := range cbs {
            _, _, eno = syscall.Syscall(syscall.SYS_IOCTL,
                ttyFile.Fd(),
                syscall.TIOCSTI,
                uintptr(unsafe.Pointer(&c)),
            )
            if eno != 0 {
                log.Fatalln(eno)
            }
        }
    }