Skip to content

Instantly share code, notes, and snippets.

@SCP002
Last active October 25, 2025 04:01
Show Gist options
  • Save SCP002/c7c3bf4aafd3e32e0dc0aa65dda2bf14 to your computer and use it in GitHub Desktop.
Save SCP002/c7c3bf4aafd3e32e0dc0aa65dda2bf14 to your computer and use it in GitHub Desktop.
Golang: Write a message to any console's input on POSIX.
//go:build darwin
package main
import (
"fmt"
"path/filepath"
"golang.org/x/sys/unix"
)
// GetTerm returns TTY device of the process with PID `pid`.
func GetTerm(pid int) (string, error) {
kProc, err := unix.SysctlKinfoProc("kern.proc.pid", int(pid))
if err != nil {
return "", fmt.Errorf("Get terminal for PID %v: Get kernel process info: %w", pid, err)
}
termMap, err := getTerminalMap()
if err != nil {
return "", fmt.Errorf("Get terminal for PID %v: %w", pid, err)
}
term, ok := termMap[kProc.Eproc.Tdev]
if !ok {
return "", fmt.Errorf("Get terminal for PID %v: Terminal not found", pid)
}
return term, nil
}
// getTerminalMap returns mapping between 'st_rdev' and TTY device.
func getTerminalMap() (map[int32]string, error) {
out := make(map[int32]string)
termFiles, err := filepath.Glob("/dev/tty*")
if err != nil {
return nil, fmt.Errorf("Get terminal map: List TTY devices: %w", err)
}
for _, termFile := range termFiles {
stat := unix.Stat_t{}
if err := unix.Stat(termFile, &stat); err == nil {
out[stat.Rdev] = termFile
}
}
return out, nil
}
//go:build !darwin && !windows
package main
import (
"fmt"
"github.com/shirou/gopsutil/v4/process"
)
// GetTerm returns TTY device of the process with PID `pid`.
func GetTerm(pid int) (string, error) {
proc, err := process.NewProcess(int32(pid))
if err != nil {
return "", fmt.Errorf("Get terminal of process with PID %v: Create process object: %w", pid, err)
}
term, err := proc.Terminal()
if err != nil {
return "", fmt.Errorf("Get terminal of process with PID %v: %w", pid, err)
}
if term == "" {
return "", fmt.Errorf("Get terminal of process with PID %v: Terminal not found", pid)
}
return "/dev" + term, nil
}
//go:build !windows
// Used in https://github.com/SCP002/terminator.
// For Windows, see: https://gist.github.com/SCP002/1b7fd91a519a2dc60fc5b179f90472b6.
package main
import (
"fmt"
"os"
"golang.org/x/sys/unix"
)
func main() {
var pid int
fmt.Print("PID: ")
fmt.Scanln(&pid)
err := Write(pid, "Hello, World!\n")
if err != nil {
panic(err)
}
}
// Write writes a `msg` message to the console process with PID `pid`.
//
// Requires root privilegies (e.g. run as sudo).
func Write(pid int, msg string) error {
term, err := GetTerm(pid)
if err != nil {
return fmt.Errorf("Write message to stdin of the process with PID %v: %w", pid, err)
}
file, err := os.OpenFile(term, os.O_WRONLY, 0644)
if err != nil {
return fmt.Errorf("Write message to stdin of the process with PID %v: %w", pid, err)
}
defer file.Close()
for _, char := range msg {
if err = unix.IoctlSetPointerInt(int(file.Fd()), unix.TIOCSTI, int(char)); err != nil {
return fmt.Errorf("Write message to stdin of the process with PID %v: %w", pid, err)
}
}
return nil
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment