diff --git a/build.sh b/build.sh new file mode 100755 index 00000000000..2bf13d609a1 --- /dev/null +++ b/build.sh @@ -0,0 +1,6 @@ +#/bin/sh +export CGO_ENABLED=1 +rm -rf /tmp/go-link-* +rm -rf /tmp/go-build* +go clean -r -cache -testcache -modcache +go build -a -buildvcs=false -ldflags="-linkmode=internal" . diff --git a/go.mod b/go.mod index e3f1ad9de48..7ea294d56b6 100644 --- a/go.mod +++ b/go.mod @@ -2,7 +2,6 @@ module github.com/junegunn/fzf require ( github.com/gdamore/tcell/v2 v2.5.4 - github.com/mattn/go-isatty v0.0.17 github.com/mattn/go-runewidth v0.0.14 github.com/mattn/go-shellwords v1.0.12 github.com/rivo/uniseg v0.4.4 diff --git a/go.sum b/go.sum index 6556c5aa547..4159377c9a1 100644 --- a/go.sum +++ b/go.sum @@ -4,8 +4,6 @@ github.com/gdamore/tcell/v2 v2.5.4 h1:TGU4tSjD3sCL788vFNeJnTdzpNKIw1H5dgLnJRQVv/ github.com/gdamore/tcell/v2 v2.5.4/go.mod h1:dZgRy5v4iMobMEcWNYBtREnDZAT9DYmfqIkrgEMxLyw= github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= -github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= -github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebGE2xrk= @@ -31,6 +29,7 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q= golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= diff --git a/src/terminal_unix.go b/src/terminal_unix.go index 1ce7854ee45..e1f20c3e1d1 100644 --- a/src/terminal_unix.go +++ b/src/terminal_unix.go @@ -2,26 +2,66 @@ package fzf +/* +#include +#include +#include + +// Wrapper for kill(2) +int native_kill(pid_t pid, int sig) { + return kill(pid, sig); +} + +// Wrapper for getpgid(2) +pid_t native_getpgid(pid_t pid) { + return getpgid(pid); +} +*/ +import "C" import ( "os" "os/signal" "strings" "syscall" + "fmt" - "golang.org/x/sys/unix" +// "golang.org/x/sys/unix" ) +// Kill sends a signal to a process. +func Kill(pid int, signal syscall.Signal) error { + // C.kill returns 0 on success, -1 on failure + res := C.native_kill(C.pid_t(pid), C.int(signal)) + if res != 0 { + return fmt.Errorf("kill failed for pid %d", pid) + } + return nil +} + + +// Getpgid returns the process group ID for the given process ID. +func Getpgid(pid int) (int, error) { + // C.getpgid returns the PGID or -1 on failure + res := C.native_getpgid(C.pid_t(pid)) + if res < 0 { + return 0, fmt.Errorf("getpgid failed for pid %d", pid) + } + return int(res), nil +} + + + func notifyOnResize(resizeChan chan<- os.Signal) { signal.Notify(resizeChan, syscall.SIGWINCH) } func notifyStop(p *os.Process) { pid := p.Pid - pgid, err := unix.Getpgid(pid) + pgid, err := Getpgid(pid) if err == nil { pid = pgid * -1 } - unix.Kill(pid, syscall.SIGSTOP) + Kill(pid, syscall.SIGSTOP) } func notifyOnCont(resizeChan chan<- os.Signal) { diff --git a/src/tui/light_unix.go b/src/tui/light_unix.go index 46188869d63..2bb3fb263d0 100644 --- a/src/tui/light_unix.go +++ b/src/tui/light_unix.go @@ -2,18 +2,113 @@ package tui +/* +#include +#include +#include + +// Simple wrapper to call the ioctl +int native_get_winsize(int fd, struct winsize *ws) { + return ioctl(fd, TIOCGWINSZ, ws); +} + +// Wrapper to call the stable POSIX tcgetattr function +int get_terminal_state(int fd, struct termios *t) { + return tcgetattr(fd, t); +} + +// Helper to manually set the terminal to raw mode using Haiku's C headers +int haiku_make_raw(int fd, struct termios *old) { + struct termios raw; + if (tcgetattr(fd, old) != 0) return -1; + raw = *old; + + // POSIX raw mode flags + raw.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON); + raw.c_oflag &= ~OPOST; + raw.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN); + raw.c_cflag &= ~(CSIZE | PARENB); + raw.c_cflag |= CS8; + raw.c_cc[VMIN] = 1; + raw.c_cc[VTIME] = 0; + + return tcsetattr(fd, TCSAFLUSH, &raw); +} + +int haiku_restore(int fd, struct termios *old) { + return tcsetattr(fd, TCSAFLUSH, old); +} +*/ +import "C" import ( "fmt" "os" "os/exec" "strings" "syscall" + "errors" + "unsafe" "github.com/junegunn/fzf/src/util" - "golang.org/x/sys/unix" +// "golang.org/x/sys/unix" "golang.org/x/term" ) +// State wraps the C termios structure to keep it compatible with your existing code. +type State struct { + state +} + +type state struct { + termios C.struct_termios +} + +func MakeRaw(fd int) (*State, error) { + var oldState state + if C.haiku_make_raw(C.int(fd), &oldState.termios) != 0 { + return nil, fmt.Errorf("failed to set raw mode on Haiku via CGO") + } + return &State{state: oldState}, nil +} + +func Restore(fd int, oldState *State) error { + //term_state := (*term.State)(unsafe.Pointer(oldState)) + if C.haiku_restore(C.int(fd), &oldState.termios) != 0 { + return fmt.Errorf("haiku: failed to restore terminal") + } + return nil +} + +// Attempt to replace: +// Ref: https://cs.opensource.google/go/x/term/+/refs/tags/v0.38.0:term_unix.go +// func getState(fd int) (*State, error) { +// termios, err := unix.IoctlGetTermios(fd, ioctlReadTermios) +// if err != nil { +// return nil, err +// } +// +// return &State{state{termios: *termios}}, nil +//} + +func GetState(fd int) (*State, error) { + var t C.struct_termios + + // Call the C wrapper. 0 is success, -1 is failure. + if C.get_terminal_state(C.int(fd), &t) != 0 { + return nil, errors.New("failed to get terminal state (tcgetattr)") + } + + return &State{state{termios: t}}, nil +} + +// Winsize matches the POSIX terminal window size structure +type Winsize struct { + Rows uint16 + Cols uint16 + Xpixel uint16 + Ypixel uint16 +} + func IsLightRendererSupported() bool { return true } @@ -35,12 +130,16 @@ func (r *LightRenderer) fd() int { func (r *LightRenderer) initPlatform() error { fd := r.fd() - origState, err := term.GetState(fd) - if err != nil { + origState, err := GetState(fd) +// if err != nil { +// return err +// } + if false { return err } - r.origState = origState - term.MakeRaw(fd) + r.origState = (*term.State)(unsafe.Pointer(origState)) + + MakeRaw(fd) return nil } @@ -64,11 +163,14 @@ func openTtyIn() *os.File { } func (r *LightRenderer) setupTerminal() { - term.MakeRaw(r.fd()) + MakeRaw(r.fd()) } func (r *LightRenderer) restoreTerminal() { - term.Restore(r.fd(), r.origState) + + state := (*State)(unsafe.Pointer(r.origState)) + Restore(r.fd(), state) + //Restore(r.fd(), r.origState) } func (r *LightRenderer) updateTerminalSize() { @@ -111,9 +213,22 @@ func (r *LightRenderer) getch(nonblock bool) (int, bool) { } func (r *LightRenderer) Size() TermSize { - ws, err := unix.IoctlGetWinsize(int(r.ttyin.Fd()), unix.TIOCGWINSZ) - if err != nil { + var ws C.struct_winsize + res := C.native_get_winsize(C.int(r.ttyin.Fd()), &ws) + + if res != 0 { return TermSize{} } - return TermSize{int(ws.Row), int(ws.Col), int(ws.Xpixel), int(ws.Ypixel)} + + return TermSize{int(ws.ws_row), int(ws.ws_col), int(ws.ws_xpixel), int(ws.ws_ypixel)} + +// var cRows, cCols C.int +// rs := C.get_terminal_size(C.int(r.ttyin.Fd()), &cRows, &cCols) + +// ws, err := unix.IoctlGetWinsize(int(r.ttyin.Fd()), unix.TIOCGWINSZ) +// if err != nil { +// return TermSize{} +// } +// return TermSize{int(ws.Row), int(ws.Col), int(ws.Xpixel), int(ws.Ypixel)} +// return TermSize{0,0,0,0} } diff --git a/src/util/util.go b/src/util/util.go index 73f53daf6bd..5beeda26c50 100644 --- a/src/util/util.go +++ b/src/util/util.go @@ -1,12 +1,16 @@ package util +/* +#include +*/ +import "C" import ( "math" "os" "strings" "time" - "github.com/mattn/go-isatty" +// "github.com/mattn/go-isatty" "github.com/mattn/go-runewidth" "github.com/rivo/uniseg" ) @@ -140,12 +144,16 @@ func DurWithin( // IsTty returns true if stdin is a terminal func IsTty() bool { - return isatty.IsTerminal(os.Stdin.Fd()) + fd := C.int(os.Stdin.Fd()) + return (C.isatty(fd) != 0) + //return isatty.IsTerminal(os.Stdin.Fd()) } // ToTty returns true if stdout is a terminal func ToTty() bool { - return isatty.IsTerminal(os.Stdout.Fd()) + fd := C.int(os.Stdout.Fd()) + return (C.isatty(fd) != 0) + //return isatty.IsTerminal(os.Stdout.Fd()) } // Once returns a function that returns the specified boolean value only once diff --git a/src/util/util_unix.go b/src/util/util_unix.go index 2991fd2cf36..d0377f0a932 100644 --- a/src/util/util_unix.go +++ b/src/util/util_unix.go @@ -7,7 +7,7 @@ import ( "os/exec" "syscall" - "golang.org/x/sys/unix" +// "golang.org/x/sys/unix" ) // ExecCommand executes the given command with $SHELL @@ -49,5 +49,9 @@ func Read(fd int, b []byte) (int, error) { } func SetStdin(file *os.File) { - unix.Dup2(int(file.Fd()), 0) + //unix.Dup2(int(file.Fd()), 0) + //unix.Close(0) + //r1, _, errno := + syscall.Syscall(syscall.SYS_DUP2, uintptr(file.Fd()), uintptr(0), 0) + //_ = r1 }