Skip to content

Commit

Permalink
feat: add termios
Browse files Browse the repository at this point in the history
  • Loading branch information
aymanbagabas committed May 8, 2024
1 parent f48c8f1 commit aaaa5ac
Show file tree
Hide file tree
Showing 14 changed files with 482 additions and 0 deletions.
7 changes: 7 additions & 0 deletions 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 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 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 }
5 changes: 5 additions & 0 deletions termios/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module github.com/charmbracelet/x/termios

go 1.18

require golang.org/x/sys v0.20.0
2 changes: 2 additions & 0 deletions termios/go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
11 changes: 11 additions & 0 deletions 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 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 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
}
249 changes: 249 additions & 0 deletions termios/termios.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,249 @@
//go:build darwin || netbsd || freebsd || openbsd || linux || dragonfly || solaris
// +build darwin netbsd freebsd openbsd linux dragonfly solaris

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
}
setSpeed(term, ispeed, 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,
}
13 changes: 13 additions & 0 deletions termios/termios_bsd.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
//go:build darwin || netbsd || freebsd || openbsd || dragonfly
// +build darwin netbsd freebsd openbsd dragonfly

package termios

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

const (
ioctlGets = unix.TIOCGETA
ioctlSets = unix.TIOCSETA
ioctlGetWinSize = unix.TIOCGWINSZ
ioctlSetWinSize = unix.TIOCSWINSZ
)
13 changes: 13 additions & 0 deletions termios/termios_linux.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
//go:build linux
// +build linux

package termios

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

const (
ioctlGets = unix.TCGETS
ioctlSets = unix.TCSETS
ioctlGetWinSize = unix.TIOCGWINSZ
ioctlSetWinSize = unix.TIOCSWINSZ
)
15 changes: 15 additions & 0 deletions termios/termios_other.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
//go:build darwin || netbsd || freebsd || openbsd || linux || dragonfly
// +build darwin netbsd freebsd openbsd linux dragonfly

package termios

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

func setSpeed(term *unix.Termios, ispeed, ospeed uint32) {
term.Ispeed = speed(ispeed)
term.Ospeed = speed(ospeed)
}

func getSpeed(term *unix.Termios) (uint32, uint32) {
return uint32(term.Ispeed), uint32(term.Ospeed)
}
26 changes: 26 additions & 0 deletions termios/termios_solaris.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
//go:build solaris
// +build solaris

package termios

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

// see https://src.illumos.org/source/xref/illumos-gate/usr/src/lib/libc/port/gen/isatty.c
// see https://github.com/omniti-labs/illumos-omnios/blob/master/usr/src/uts/common/sys/termios.h
const (
ioctlSets = unix.TCSETA
ioctlGets = unix.TCGETA
ioctlSetWinSize = (int('T') << 8) | 103
ioctlGetWinSize = (int('T') << 8) | 104
)

func setSpeed(*unix.Termios, uint32, uint32) {
// TODO: support setting speed on Solaris?
// see cfgetospeed(3C) and cfsetospeed(3C)
// see cfgetispeed(3C) and cfsetispeed(3C)
// https://github.com/omniti-labs/illumos-omnios/blob/master/usr/src/uts/common/sys/termios.h#L103
}

func getSpeed(*unix.Termios) (uint32, uint32) {
return 0, 0
}
Loading

0 comments on commit aaaa5ac

Please sign in to comment.