This repository has been archived on 2026-05-07. You can view files and clone it, but you cannot make any changes to its state, such as pushing and creating new issues, pull requests or comments.
ccrm/daemon/internal/tmux/tmux.go

131 lines
3.3 KiB
Go

package tmux
import (
"fmt"
"os/exec"
"strings"
)
// CreateSession creates a new tmux session with the given name and working directory.
func CreateSession(name, workDir string) error {
return run("new-session", "-d", "-s", name, "-c", workDir)
}
// SetOption sets a tmux user option on a session.
func SetOption(session, key, value string) error {
return run("set-option", "-t", session, key, value)
}
// GetOption reads a tmux user option from a session.
func GetOption(session, key string) (string, error) {
out, err := output("show-option", "-t", session, "-v", key)
if err != nil {
return "", err
}
return strings.TrimSpace(out), nil
}
// SendKeys sends keystrokes to a tmux session.
func SendKeys(session, keys string) error {
return run("send-keys", "-t", session, "-l", keys)
}
// SendEnter sends an Enter keystroke to a tmux session.
func SendEnter(session string) error {
return run("send-keys", "-t", session, "Enter")
}
// ListSessions returns all tmux session names.
func ListSessions() ([]string, error) {
out, err := output("list-sessions", "-F", "#{session_name}")
if err != nil {
// No sessions is not an error
if strings.Contains(err.Error(), "no server running") ||
strings.Contains(err.Error(), "no sessions") {
return nil, nil
}
return nil, err
}
lines := strings.Split(strings.TrimSpace(out), "\n")
var sessions []string
for _, line := range lines {
line = strings.TrimSpace(line)
if line != "" {
sessions = append(sessions, line)
}
}
return sessions, nil
}
// ListSessionsWithPrefix returns tmux sessions matching a prefix.
func ListSessionsWithPrefix(prefix string) ([]string, error) {
all, err := ListSessions()
if err != nil {
return nil, err
}
var result []string
for _, s := range all {
if strings.HasPrefix(s, prefix) {
result = append(result, s)
}
}
return result, nil
}
// KillSession kills a tmux session.
func KillSession(session string) error {
return run("kill-session", "-t", session)
}
// HasSession checks if a tmux session exists.
func HasSession(session string) bool {
err := run("has-session", "-t", session)
return err == nil
}
// SessionAttachedClients returns the number of clients attached to a session.
func SessionAttachedClients(session string) (int, error) {
out, err := output("display-message", "-t", session, "-p", "#{session_attached}")
if err != nil {
return 0, err
}
val := strings.TrimSpace(out)
if val == "" {
return 0, nil
}
n := 0
for _, c := range val {
if c >= '0' && c <= '9' {
n = n*10 + int(c-'0')
}
}
return n, nil
}
// SendCtrlC sends Ctrl-C to interrupt the current pane command.
func SendCtrlC(session string) error {
return run("send-keys", "-t", session, "C-c")
}
// RespawnPane replaces the current pane content with a new command.
func RespawnPane(session, cmd string) error {
return run("respawn-pane", "-k", "-t", session, cmd)
}
func run(args ...string) error {
cmd := exec.Command("tmux", args...)
out, err := cmd.CombinedOutput()
if err != nil {
return fmt.Errorf("tmux %s: %w: %s", args[0], err, string(out))
}
return nil
}
func output(args ...string) (string, error) {
cmd := exec.Command("tmux", args...)
out, err := cmd.CombinedOutput()
if err != nil {
return "", fmt.Errorf("tmux %s: %w: %s", args[0], err, string(out))
}
return string(out), nil
}