Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,18 +29,24 @@ jobs:
go-version: ${{ matrix.go }}
- name: Checkout code
uses: actions/checkout@v6
with:
persist-credentials: false
- name: Test
run: go test -v ./...
lint:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v6
with:
persist-credentials: false
- name: go mod tidy
run: |
go mod tidy
git diff --exit-code
- name: Lint
run: |
docker run --rm -v ./:/go/src/github.com/moby/term -w /go/src/github.com/moby/term \
golangci/golangci-lint:v2.8-alpine golangci-lint run -v
docker run --rm \
-v ./:/go/src/github.com/moby/term \
-w /go/src/github.com/moby/term \
golangci/golangci-lint:v2.11-alpine golangci-lint run -v
5 changes: 2 additions & 3 deletions term.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,8 @@ type Winsize struct {
Height uint16
Width uint16

// Only used on Unix
x uint16
y uint16
x uint16 //nolint:unused // only used on Unix
y uint16 //nolint:unused // only used on Unix
}

// StdStreams returns the standard streams (stdin, stdout, stderr).
Expand Down
13 changes: 13 additions & 0 deletions term_unix.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package term
import (
"errors"
"io"
"math"
"os"

"golang.org/x/sys/unix"
Expand All @@ -31,12 +32,18 @@ func getFdInfo(in interface{}) (uintptr, bool) {
}

func getWinsize(fd uintptr) (*Winsize, error) {
if fd > math.MaxInt {
return nil, errors.New("invalid file descriptor")
}
uws, err := unix.IoctlGetWinsize(int(fd), unix.TIOCGWINSZ)
ws := &Winsize{Height: uws.Row, Width: uws.Col, x: uws.Xpixel, y: uws.Ypixel}
return ws, err
}

func setWinsize(fd uintptr, ws *Winsize) error {
if fd > math.MaxInt {
return errors.New("invalid file descriptor")
}
return unix.IoctlSetWinsize(int(fd), unix.TIOCSWINSZ, &unix.Winsize{
Row: ws.Height,
Col: ws.Width,
Expand Down Expand Up @@ -81,6 +88,9 @@ func setRawTerminalOutput(uintptr) (*State, error) {
}

func tcget(fd uintptr) (*unix.Termios, error) {
if fd > math.MaxInt {
return nil, errors.New("invalid file descriptor")
}
p, err := unix.IoctlGetTermios(int(fd), getTermios)
if err != nil {
return nil, err
Expand All @@ -89,5 +99,8 @@ func tcget(fd uintptr) (*unix.Termios, error) {
}

func tcset(fd uintptr, p *unix.Termios) error {
if fd > math.MaxInt {
return errors.New("invalid file descriptor")
}
return unix.IoctlSetTermios(int(fd), setTermios, p)
}
15 changes: 11 additions & 4 deletions term_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package term
import (
"errors"
"io"
"math"
"os"
"os/signal"

Expand Down Expand Up @@ -94,13 +95,19 @@ func getWinsize(fd uintptr) (*Winsize, error) {
return nil, err
}

w := int32(info.Window.Right) - int32(info.Window.Left) + 1
h := int32(info.Window.Bottom) - int32(info.Window.Top) + 1
if w < 0 || w > math.MaxUint16 || h < 0 || h > math.MaxUint16 {
return nil, errors.New("invalid console window size")
}

return &Winsize{
Width: uint16(info.Window.Right - info.Window.Left + 1),
Height: uint16(info.Window.Bottom - info.Window.Top + 1),
Width: uint16(w),
Height: uint16(h),
}, nil
}

func setWinsize(fd uintptr, ws *Winsize) error {
func setWinsize(uintptr, *Winsize) error {
return errors.New("not implemented on Windows")
}

Expand Down Expand Up @@ -167,7 +174,7 @@ func restoreAtInterrupt(fd uintptr, state *State) {
signal.Notify(sigchan, os.Interrupt)

go func() {
_ = <-sigchan
<-sigchan
_ = RestoreTerminal(fd, state)
os.Exit(0)
}()
Expand Down
45 changes: 21 additions & 24 deletions windows/ansi_reader.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,10 @@ const (

// ansiReader wraps a standard input file (e.g., os.Stdin) providing ANSI sequence translation.
type ansiReader struct {
file *os.File
fd uintptr
buffer []byte
cbBuffer int
command []byte
file *os.File
fd uintptr
buffer []byte
command []byte
}

// NewAnsiReader returns an io.ReadCloser that provides VT100 terminal emulation on top of a
Expand Down Expand Up @@ -99,11 +98,8 @@ func (ar *ansiReader) Read(p []byte) (int, error) {

// readInputEvents polls until at least one event is available.
func readInputEvents(ar *ansiReader, maxBytes int) ([]winterm.INPUT_RECORD, error) {
// Determine the maximum number of records to retrieve
// -- Cast around the type system to obtain the size of a single INPUT_RECORD.
// unsafe.Sizeof requires an expression vs. a type-reference; the casting
// tricks the type system into believing it has such an expression.
recordSize := int(unsafe.Sizeof(*((*winterm.INPUT_RECORD)(unsafe.Pointer(&maxBytes)))))
// Determine the size of a single INPUT_RECORD.
recordSize := int(unsafe.Sizeof(winterm.INPUT_RECORD{}))
countRecords := maxBytes / recordSize
if countRecords > ansiterm.MAX_INPUT_EVENTS {
countRecords = ansiterm.MAX_INPUT_EVENTS
Expand Down Expand Up @@ -167,9 +163,10 @@ var keyMapPrefix = map[uint16]string{
// translateKeyEvents converts the input events into the appropriate ANSI string.
func translateKeyEvents(events []winterm.INPUT_RECORD, escapeSequence []byte) []byte {
var buffer bytes.Buffer
for _, event := range events {
for i := range events {
event := events[i]
if event.EventType == winterm.KEY_EVENT && event.KeyEvent.KeyDown != 0 {
buffer.WriteString(keyToString(&event.KeyEvent, escapeSequence))
buffer.WriteString(keyToString(&events[i].KeyEvent, escapeSequence))
}
}

Expand All @@ -181,24 +178,24 @@ func keyToString(keyEvent *winterm.KEY_EVENT_RECORD, escapeSequence []byte) stri
if keyEvent.UnicodeChar == 0 {
return formatVirtualKey(keyEvent.VirtualKeyCode, keyEvent.ControlKeyState, escapeSequence)
}

key := string(rune(keyEvent.UnicodeChar))
_, alt, control := getControlKeys(keyEvent.ControlKeyState)
if control {
switch {
case control && !alt:
// TODO(azlinux): Implement following control sequences
// <Ctrl>-D Signals the end of input from the keyboard; also exits current shell.
// <Ctrl>-H Deletes the first character to the left of the cursor. Also called the ERASE key.
// <Ctrl>-Q Restarts printing after it has been stopped with <Ctrl>-s.
// <Ctrl>-S Suspends printing on the screen (does not stop the program).
// <Ctrl>-U Deletes all characters on the current line. Also called the KILL key.
// <Ctrl>-E Quits current command and creates a core
return key
case !control && alt:
// <Alt>+Key generates ESC N Key
return ansiterm.KEY_ESC_N + strings.ToLower(key)
default:
return key
}

// <Alt>+Key generates ESC N Key
if !control && alt {
return ansiterm.KEY_ESC_N + strings.ToLower(string(rune(keyEvent.UnicodeChar)))
}

return string(rune(keyEvent.UnicodeChar))
}

// formatVirtualKey converts a virtual key (e.g., up arrow) into the appropriate ANSI string.
Expand All @@ -219,9 +216,9 @@ func formatVirtualKey(key uint16, controlState uint32, escapeSequence []byte) st

// getControlKeys extracts the shift, alt, and ctrl key states.
func getControlKeys(controlState uint32) (shift, alt, control bool) {
shift = 0 != (controlState & winterm.SHIFT_PRESSED)
alt = 0 != (controlState & (winterm.LEFT_ALT_PRESSED | winterm.RIGHT_ALT_PRESSED))
control = 0 != (controlState & (winterm.LEFT_CTRL_PRESSED | winterm.RIGHT_CTRL_PRESSED))
shift = controlState&winterm.SHIFT_PRESSED != 0
alt = controlState&(winterm.LEFT_ALT_PRESSED|winterm.RIGHT_ALT_PRESSED) != 0
control = controlState&(winterm.LEFT_CTRL_PRESSED|winterm.RIGHT_CTRL_PRESSED) != 0
return shift, alt, control
}

Expand Down
1 change: 0 additions & 1 deletion windows/ansi_writer.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ type ansiWriter struct {
infoReset *winterm.CONSOLE_SCREEN_BUFFER_INFO
command []byte
escapeSequence []byte
inAnsiSequence bool
parser *ansiterm.AnsiParser
}

Expand Down
Loading