Skip to content

Commit

Permalink
feat(term): termios package (#35)
Browse files Browse the repository at this point in the history
* feat(term): termios package

This will help us remove the dep on u-root/u-root from /ssh, and also
adds support to netbsd.

This one is less generic though, and specifically made to be used with
ssh.TerminalModes, which is our main use case for now.

Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>

* chore: fmt

Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>

* fix: do not couple with ssh

Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>

* chore: remove comments

Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>

* fix: rename field

* fix: darwin

Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>

* ci: test build termios on many goos/goarch

Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>

* test: open a pty and test it

only on linux, on other OSs this will help by simply failing to compile

Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>

* ci: go test instead of build

Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>

* feat: support iox

Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>

* fix: typo

Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>

* ci: fixes

Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>

* ci: fixes

Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>

* ci: fixes

Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>

* fix: bits

Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>

* refactor: constants

Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>

* wip: syscalls

Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>

* fix: flush

Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>

* fix: do not attempt to test/build on windows

* ci: only test termios when it changes

* fix: more tests, improvements

* test: fix

---------

Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>
  • Loading branch information
caarlos0 authored Feb 2, 2024
1 parent d293985 commit 7acb62e
Show file tree
Hide file tree
Showing 12 changed files with 504 additions and 3 deletions.
66 changes: 66 additions & 0 deletions .github/workflows/termios.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
name: termios
on:
push:
pull_request:
paths:
- "exp/term/termios/**"

jobs:
test:
runs-on: ubuntu-latest
defaults:
run:
working-directory: ./exp/term
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version-file: ./exp/term/go.mod
cache: true
cache-dependency-path: ./exp/term.sum
# https://go.dev/doc/install/source#environment
- run: GOOS=darwin GOARCH=amd64 go test -c -v ./termios/...
- run: GOOS=darwin GOARCH=arm64 go test -c -v ./termios/...
- run: GOOS=dragonfly GOARCH=amd64 go test -c -v ./termios/...
- run: GOOS=freebsd GOARCH=386 go test -c -v ./termios/...
- run: GOOS=freebsd GOARCH=amd64 go test -c -v ./termios/...
- run: GOOS=freebsd GOARCH=arm go test -c -v ./termios/...
- run: GOOS=linux GOARCH=386 go test -c -v ./termios/...
- run: GOOS=linux GOARCH=amd64 go test -c -v ./termios/...
- run: GOOS=linux GOARCH=arm go test -c -v ./termios/...
- run: GOOS=linux GOARCH=arm64 go test -c -v ./termios/...
- run: GOOS=linux GOARCH=mips go test -c -v ./termios/...
- run: GOOS=linux GOARCH=mips64 go test -c -v ./termios/...
- run: GOOS=linux GOARCH=mips64le go test -c -v ./termios/...
- run: GOOS=linux GOARCH=mipsle go test -c -v ./termios/...
- run: GOOS=linux GOARCH=ppc64 go test -c -v ./termios/...
- run: GOOS=linux GOARCH=ppc64le go test -c -v ./termios/...
- run: GOOS=linux GOARCH=riscv64 go test -c -v ./termios/...
- run: GOOS=linux GOARCH=s390x go test -c -v ./termios/...
- run: GOOS=netbsd GOARCH=386 go test -c -v ./termios/...
- run: GOOS=netbsd GOARCH=amd64 go test -c -v ./termios/...
- run: GOOS=netbsd GOARCH=arm go test -c -v ./termios/...
- run: GOOS=openbsd GOARCH=386 go test -c -v ./termios/...
- run: GOOS=openbsd GOARCH=amd64 go test -c -v ./termios/...
- run: GOOS=openbsd GOARCH=arm go test -c -v ./termios/...
- run: GOOS=openbsd GOARCH=arm64 go test -c -v ./termios/...

# unsupported
# - run: GOOS=aix GOARCH=ppc64 go test -c -v ./termios/...
# - run: GOOS=android GOARCH=386 go test -c -v ./termios/...
# - run: GOOS=android GOARCH=amd64 go test -c -v ./termios/...
# - run: GOOS=android GOARCH=arm go test -c -v ./termios/...
# - run: GOOS=android GOARCH=arm64 go test -c -v ./termios/...
# - run: GOOS=illumos GOARCH=amd64 go test -c -v ./termios/...
# - run: GOOS=ios GOARCH=arm64 go test -c -v ./termios/...
# - run: GOOS=js GOARCH=wasm go test -c -v ./termios/...
# - run: GOOS=linux GOARCH=loong64 go test -c -v ./termios/...
# - run: GOOS=plan9 GOARCH=386 go test -c -v ./termios/...
# - run: GOOS=plan9 GOARCH=amd64 go test -c -v ./termios/...
# - run: GOOS=plan9 GOARCH=arm go test -c -v ./termios/...
# - run: GOOS=solaris GOARCH=amd64 go test -c -v ./termios/...
# - run: GOOS=wasip1 GOARCH=wasm go test -c -v ./termios/...
# - run: GOOS=windows GOARCH=386 go test -c -v ./termios/...
# - run: GOOS=windows GOARCH=amd64 go test -c -v ./termios/...
# - run: GOOS=windows GOARCH=arm go test -c -v ./termios/...
# - run: GOOS=windows GOARCH=arm64 go test -c -v ./termios/...
7 changes: 4 additions & 3 deletions exp/term/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ module github.com/charmbracelet/x/exp/term

go 1.17

require golang.org/x/sys v0.16.0

require github.com/charmbracelet/x/errors v0.0.0-20240117030013-d31dba354651
require (
github.com/charmbracelet/x/errors v0.0.0-20240117030013-d31dba354651
golang.org/x/sys v0.16.0
)
7 changes: 7 additions & 0 deletions exp/term/termios/bit_bsd.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
//go:build netbsd || openbsd
// +build netbsd openbsd

package termios

func speed(b uint32) int32 { return int32(b) }
func bit(b uint32) uint32 { return b }
7 changes: 7 additions & 0 deletions exp/term/termios/bit_darwin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
//go:build darwin
// +build darwin

package termios

func speed(b uint32) uint64 { return uint64(b) }
func bit(b uint32) uint64 { return uint64(b) }
7 changes: 7 additions & 0 deletions exp/term/termios/bit_other.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
//go:build !darwin && !netbsd && !openbsd
// +build !darwin,!netbsd,!openbsd

package termios

func speed(b uint32) uint32 { return b }
func bit(b uint32) uint32 { return b }
11 changes: 11 additions & 0 deletions exp/term/termios/syscalls_bsd.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
//go:build darwin && netbsd && freebsd && netbsd
// +build darwin,netbsd,freebsd,netbsd

package term

import "syscall"

func init() {
allCcOpts[STATUS] = syscall.VSTATUS
allCcOpts[DSUSP] = syscall.VDSUSP
}
10 changes: 10 additions & 0 deletions exp/term/termios/syscalls_darwin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
//go:build darwin
// +build darwin

package termios

import "syscall"

func init() {
allLineOpts[IUTF8] = syscall.IUTF8
}
14 changes: 14 additions & 0 deletions exp/term/termios/syscalls_linux.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
//go:build linux
// +build linux

package termios

import "syscall"

func init() {
allCcOpts[SWTCH] = syscall.VSWTC
allInputOpts[IUCLC] = syscall.IUCLC
allLineOpts[IUTF8] = syscall.IUTF8
allLineOpts[XCASE] = syscall.XCASE
allOutputOpts[OLCUC] = syscall.OLCUC
}
250 changes: 250 additions & 0 deletions exp/term/termios/termios.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,250 @@
//go:build darwin || netbsd || freebsd || openbsd || linux || dragonfly
// +build darwin netbsd freebsd openbsd linux dragonfly

package termios

import (
"syscall"

"golang.org/x/sys/unix"
)

// SetWinSize sets window size for an fd from a Winsize.
func SetWinSize(fd int, w *unix.Winsize) error {
return unix.IoctlSetWinsize(fd, ioctlSetWinSize, w)
}

// GetWinSize gets window size for an fd.
func GetWinSize(fd int) (*unix.Winsize, error) {
return unix.IoctlGetWinsize(fd, ioctlSetWinSize)
}

// GetTermios gets the termios of the given fd.
func GetTermios(fd int) (*unix.Termios, error) {
return unix.IoctlGetTermios(fd, ioctlGets)
}

// SetTermios sets the given termios over the given fd's current termios.
func SetTermios(
fd int,
ispeed uint32,
ospeed uint32,
cc map[CC]uint8,
iflag map[I]bool,
oflag map[O]bool,
cflag map[C]bool,
lflag map[L]bool,
) error {
term, err := unix.IoctlGetTermios(fd, ioctlGets)
if err != nil {
return err
}
term.Ispeed = speed(ispeed)
term.Ospeed = speed(ospeed)

for key, value := range cc {
call, ok := allCcOpts[key]
if !ok {
continue
}
term.Cc[call] = value
}

for key, value := range iflag {
mask, ok := allInputOpts[key]
if ok {
if value {
term.Iflag |= bit(mask)
} else {
term.Iflag &= ^bit(mask)
}
}
}
for key, value := range oflag {
mask, ok := allOutputOpts[key]
if ok {
if value {
term.Oflag |= bit(mask)
} else {
term.Oflag &= ^bit(mask)
}
}
}
for key, value := range cflag {
mask, ok := allControlOpts[key]
if ok {
if value {
term.Cflag |= bit(mask)
} else {
term.Cflag &= ^bit(mask)
}
}
}
for key, value := range lflag {
mask, ok := allLineOpts[key]
if ok {
if value {
term.Lflag |= bit(mask)
} else {
term.Lflag &= ^bit(mask)
}
}
}
return unix.IoctlSetTermios(fd, ioctlSets, term)
}

// CC is the termios cc field.
//
// It stores an array of special characters related to terminal I/O.
type CC uint8

// CC possible values.
const (
INTR CC = iota
QUIT
ERASE
KILL
EOF
EOL
EOL2
START
STOP
SUSP
WERASE
RPRNT
LNEXT
DISCARD
STATUS
SWTCH
DSUSP
FLUSH
)

// https://www.man7.org/linux/man-pages/man3/termios.3.html
var allCcOpts = map[CC]int{
INTR: syscall.VINTR,
QUIT: syscall.VQUIT,
ERASE: syscall.VERASE,
KILL: syscall.VQUIT,
EOF: syscall.VEOF,
EOL: syscall.VEOL,
EOL2: syscall.VEOL2,
START: syscall.VSTART,
STOP: syscall.VSTOP,
SUSP: syscall.VSUSP,
WERASE: syscall.VWERASE,
RPRNT: syscall.VREPRINT,
LNEXT: syscall.VLNEXT,
DISCARD: syscall.VDISCARD,

// XXX: these syscalls don't exist for any OS
// FLUSH: syscall.VFLUSH,
}

// Input Controls
type I uint8

// Input possible values.
const (
IGNPAR I = iota
PARMRK
INPCK
ISTRIP
INLCR
IGNCR
ICRNL
IXON
IXANY
IXOFF
IMAXBEL
IUCLC
)

var allInputOpts = map[I]uint32{
IGNPAR: syscall.IGNPAR,
PARMRK: syscall.PARMRK,
INPCK: syscall.INPCK,
ISTRIP: syscall.ISTRIP,
INLCR: syscall.INLCR,
IGNCR: syscall.IGNCR,
ICRNL: syscall.ICRNL,
IXON: syscall.IXON,
IXANY: syscall.IXANY,
IXOFF: syscall.IXOFF,
IMAXBEL: syscall.IMAXBEL,
}

// Output Controls
type O uint8

// Output possible values.
const (
OPOST O = iota
ONLCR
OCRNL
ONOCR
ONLRET
OLCUC
)

var allOutputOpts = map[O]uint32{
OPOST: syscall.OPOST,
ONLCR: syscall.ONLCR,
OCRNL: syscall.OCRNL,
ONOCR: syscall.ONOCR,
ONLRET: syscall.ONLRET,
}

// Control
type C uint8

// Control possible values.
const (
CS7 C = iota
CS8
PARENB
PARODD
)

var allControlOpts = map[C]uint32{
CS7: syscall.CS7,
CS8: syscall.CS8,
PARENB: syscall.PARENB,
PARODD: syscall.PARODD,
}

// Line Controls.
type L uint8

// Line possible values.
const (
ISIG L = iota
ICANON
ECHO
ECHOE
ECHOK
ECHONL
NOFLSH
TOSTOP
IEXTEN
ECHOCTL
ECHOKE
PENDIN
IUTF8
XCASE
)

var allLineOpts = map[L]uint32{
ISIG: syscall.ISIG,
ICANON: syscall.ICANON,
ECHO: syscall.ECHO,
ECHOE: syscall.ECHOE,
ECHOK: syscall.ECHOK,
ECHONL: syscall.ECHONL,
NOFLSH: syscall.NOFLSH,
TOSTOP: syscall.TOSTOP,
IEXTEN: syscall.IEXTEN,
ECHOCTL: syscall.ECHOCTL,
ECHOKE: syscall.ECHOKE,
PENDIN: syscall.PENDIN,
}
Loading

0 comments on commit 7acb62e

Please sign in to comment.