resize observer + run an ls command

This commit is contained in:
sawka 2024-05-14 22:37:04 -07:00
parent 35c6b232fc
commit 5b2a5eb5eb
4 changed files with 139 additions and 12 deletions

View File

@ -64,29 +64,33 @@ const TerminalView = ({ blockId }: { blockId: string }) => {
fitAddon.fit(); fitAddon.fit();
term.write("Hello, world!\r\n"); term.write("Hello, world!\r\n");
setTerm(term); setTerm(term);
return () => {
term.dispose(); // resize observer
}; const rszObs = new ResizeObserver(() => {
}, [connectElemRef.current]); fitAddon.fit();
React.useEffect(() => { });
if (!term) { rszObs.observe(connectElemRef.current);
return;
} // block subject
const blockSubject = getBlockSubject(blockId); const blockSubject = getBlockSubject(blockId);
blockSubject.subscribe((data) => { blockSubject.subscribe((data) => {
// base64 decode // base64 decode
const decodedData = base64ToArray(data.ptydata); const decodedData = base64ToArray(data.ptydata);
term.write(decodedData); term.write(decodedData);
}); });
return () => { return () => {
term.dispose();
rszObs.disconnect();
blockSubject.release(); blockSubject.release();
}; };
}, [term]); }, [connectElemRef.current]);
async function handleRunClick() { async function handleRunClick() {
try { try {
await BlockService.StartBlock(blockId); await BlockService.StartBlock(blockId);
await BlockService.SendCommand(blockId, { command: "message", message: "Run clicked" }); let termSize = { rows: term.rows, cols: term.cols };
await BlockService.SendCommand(blockId, { command: "run", cmdstr: "ls -l", termsize: termSize });
} catch (e) { } catch (e) {
console.log("run click error: ", e); console.log("run click error: ", e);
} }

View File

@ -7,10 +7,14 @@ import (
"encoding/base64" "encoding/base64"
"encoding/json" "encoding/json"
"fmt" "fmt"
"log"
"os/exec"
"sync" "sync"
"github.com/wailsapp/wails/v3/pkg/application" "github.com/wailsapp/wails/v3/pkg/application"
"github.com/wavetermdev/thenextwave/pkg/eventbus" "github.com/wavetermdev/thenextwave/pkg/eventbus"
"github.com/wavetermdev/thenextwave/pkg/shellexec"
"github.com/wavetermdev/thenextwave/pkg/util/shellutil"
) )
var globalLock = &sync.Mutex{} var globalLock = &sync.Mutex{}
@ -29,6 +33,16 @@ func (mc *MessageCommand) GetCommand() string {
return "message" return "message"
} }
type RunCommand struct {
Command string `json:"command"`
CmdStr string `json:"cmdstr"`
TermSize shellexec.TermSize `json:"termsize"`
}
func (rc *RunCommand) GetCommand() string {
return "run"
}
type BlockController struct { type BlockController struct {
BlockId string BlockId string
InputCh chan BlockCommand InputCh chan BlockCommand
@ -51,11 +65,45 @@ func ParseCmdMap(cmdMap map[string]any) (BlockCommand, error) {
return nil, fmt.Errorf("error unmarshalling message command: %w", err) return nil, fmt.Errorf("error unmarshalling message command: %w", err)
} }
return &cmd, nil return &cmd, nil
case "run":
var cmd RunCommand
err := json.Unmarshal(mapJson, &cmd)
if err != nil {
return nil, fmt.Errorf("error unmarshalling run command: %w", err)
}
return &cmd, nil
default: default:
return nil, fmt.Errorf("unknown command type %q", cmdType) return nil, fmt.Errorf("unknown command type %q", cmdType)
} }
} }
func (bc *BlockController) StartShellCommand(rc *RunCommand) error {
cmdStr := rc.CmdStr
shellPath := shellutil.DetectLocalShellPath()
ecmd := exec.Command(shellPath, "-c", cmdStr)
log.Printf("running shell command: %q %q\n", shellPath, cmdStr)
barr, err := shellexec.RunSimpleCmdInPty(ecmd, rc.TermSize)
if err != nil {
return err
}
for len(barr) > 0 {
part := barr
if len(part) > 4096 {
part = part[:4096]
}
eventbus.SendEvent(application.WailsEvent{
Name: "block:ptydata",
Data: map[string]any{
"blockid": bc.BlockId,
"blockfile": "main",
"ptydata": base64.StdEncoding.EncodeToString(part),
},
})
barr = barr[len(part):]
}
return nil
}
func (bc *BlockController) Run() { func (bc *BlockController) Run() {
defer func() { defer func() {
eventbus.SendEvent(application.WailsEvent{ eventbus.SendEvent(application.WailsEvent{
@ -81,6 +129,14 @@ func (bc *BlockController) Run() {
"ptydata": base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("message %d\r\n", messageCount))), "ptydata": base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("message %d\r\n", messageCount))),
}, },
}) })
case *RunCommand:
fmt.Printf("RUN: %s | %q\n", bc.BlockId, cmd.CmdStr)
go func() {
err := bc.StartShellCommand(cmd)
if err != nil {
log.Printf("error running shell command: %v\n", err)
}
}()
default: default:
fmt.Printf("unknown command type %T\n", cmd) fmt.Printf("unknown command type %T\n", cmd)

View File

@ -15,14 +15,26 @@ import (
"github.com/wavetermdev/thenextwave/pkg/util/shellutil" "github.com/wavetermdev/thenextwave/pkg/util/shellutil"
) )
func RunSimpleCmdInPty(ecmd *exec.Cmd) ([]byte, error) { type TermSize struct {
Rows int `json:"rows"`
Cols int `json:"cols"`
}
func RunSimpleCmdInPty(ecmd *exec.Cmd, termSize TermSize) ([]byte, error) {
ecmd.Env = os.Environ() ecmd.Env = os.Environ()
shellutil.UpdateCmdEnv(ecmd, shellutil.WaveshellEnvVars(shellutil.DefaultTermType)) shellutil.UpdateCmdEnv(ecmd, shellutil.WaveshellEnvVars(shellutil.DefaultTermType))
cmdPty, cmdTty, err := pty.Open() cmdPty, cmdTty, err := pty.Open()
if err != nil { if err != nil {
return nil, fmt.Errorf("opening new pty: %w", err) return nil, fmt.Errorf("opening new pty: %w", err)
} }
pty.Setsize(cmdPty, &pty.Winsize{Rows: shellutil.DefaultTermRows, Cols: shellutil.DefaultTermCols}) if termSize.Rows == 0 || termSize.Cols == 0 {
termSize.Rows = shellutil.DefaultTermRows
termSize.Cols = shellutil.DefaultTermCols
}
if termSize.Rows <= 0 || termSize.Cols <= 0 {
return nil, fmt.Errorf("invalid term size: %v", termSize)
}
pty.Setsize(cmdPty, &pty.Winsize{Rows: uint16(termSize.Rows), Cols: uint16(termSize.Cols)})
ecmd.Stdin = cmdTty ecmd.Stdin = cmdTty
ecmd.Stdout = cmdTty ecmd.Stdout = cmdTty
ecmd.Stderr = cmdTty ecmd.Stderr = cmdTty

View File

@ -4,9 +4,15 @@
package shellutil package shellutil
import ( import (
"context"
"os" "os"
"os/exec" "os/exec"
"os/user"
"regexp"
"runtime"
"strings" "strings"
"sync"
"time"
"github.com/wavetermdev/thenextwave/pkg/wavebase" "github.com/wavetermdev/thenextwave/pkg/wavebase"
) )
@ -15,6 +21,55 @@ const DefaultTermType = "xterm-256color"
const DefaultTermRows = 24 const DefaultTermRows = 24
const DefaultTermCols = 80 const DefaultTermCols = 80
var cachedMacUserShell string
var macUserShellOnce = &sync.Once{}
var userShellRegexp = regexp.MustCompile(`^UserShell: (.*)$`)
const DefaultShellPath = "/bin/bash"
func DetectLocalShellPath() string {
shellPath := GetMacUserShell()
if shellPath == "" {
shellPath = os.Getenv("SHELL")
}
if shellPath == "" {
return DefaultShellPath
}
return shellPath
}
func GetMacUserShell() string {
if runtime.GOOS != "darwin" {
return ""
}
macUserShellOnce.Do(func() {
cachedMacUserShell = internalMacUserShell()
})
return cachedMacUserShell
}
// dscl . -read /User/[username] UserShell
// defaults to /bin/bash
func internalMacUserShell() string {
osUser, err := user.Current()
if err != nil {
return DefaultShellPath
}
ctx, cancelFn := context.WithTimeout(context.Background(), 2*time.Second)
defer cancelFn()
userStr := "/Users/" + osUser.Username
out, err := exec.CommandContext(ctx, "dscl", ".", "-read", userStr, "UserShell").CombinedOutput()
if err != nil {
return DefaultShellPath
}
outStr := strings.TrimSpace(string(out))
m := userShellRegexp.FindStringSubmatch(outStr)
if m == nil {
return DefaultShellPath
}
return m[1]
}
func WaveshellEnvVars(termType string) map[string]string { func WaveshellEnvVars(termType string) map[string]string {
rtn := make(map[string]string) rtn := make(map[string]string)
if termType != "" { if termType != "" {