1
2
3
4 package tty
5
6 import (
7 "bufio"
8 "os"
9 "os/signal"
10 "syscall"
11 "unsafe"
12
13 "golang.org/x/sys/unix"
14 )
15
16 type TTY struct {
17 in *os.File
18 bin *bufio.Reader
19 out *os.File
20 termios syscall.Termios
21 ss chan os.Signal
22 }
23
24 func open(path string) (*TTY, error) {
25 tty := new(TTY)
26
27 in, err := os.Open(path)
28 if err != nil {
29 return nil, err
30 }
31 tty.in = in
32 tty.bin = bufio.NewReader(in)
33
34 out, err := os.OpenFile(path, syscall.O_WRONLY, 0)
35 if err != nil {
36 return nil, err
37 }
38 tty.out = out
39
40 if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, uintptr(tty.in.Fd()), ioctlReadTermios, uintptr(unsafe.Pointer(&tty.termios))); err != 0 {
41 return nil, err
42 }
43 newios := tty.termios
44 newios.Iflag &^= syscall.ISTRIP | syscall.INLCR | syscall.ICRNL | syscall.IGNCR | syscall.IXOFF
45 newios.Lflag &^= syscall.ECHO | syscall.ICANON
46 if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, uintptr(tty.in.Fd()), ioctlWriteTermios, uintptr(unsafe.Pointer(&newios))); err != 0 {
47 return nil, err
48 }
49
50 tty.ss = make(chan os.Signal, 1)
51
52 return tty, nil
53 }
54
55 func (tty *TTY) buffered() bool {
56 return tty.bin.Buffered() > 0
57 }
58
59 func (tty *TTY) readRune() (rune, error) {
60 r, _, err := tty.bin.ReadRune()
61 return r, err
62 }
63
64 func (tty *TTY) close() error {
65 signal.Stop(tty.ss)
66 close(tty.ss)
67 _, _, err := syscall.Syscall(syscall.SYS_IOCTL, uintptr(tty.in.Fd()), ioctlWriteTermios, uintptr(unsafe.Pointer(&tty.termios)))
68 return err
69 }
70
71 func (tty *TTY) size() (int, int, error) {
72 x, y, _, _, err := tty.sizePixel()
73 return x, y, err
74 }
75
76 func (tty *TTY) sizePixel() (int, int, int, int, error) {
77 var dim [4]uint16
78 if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, uintptr(tty.out.Fd()), uintptr(syscall.TIOCGWINSZ), uintptr(unsafe.Pointer(&dim))); err != 0 {
79 return -1, -1, -1, -1, err
80 }
81 return int(dim[1]), int(dim[0]), int(dim[2]), int(dim[3]), nil
82 }
83
84 func (tty *TTY) input() *os.File {
85 return tty.in
86 }
87
88 func (tty *TTY) output() *os.File {
89 return tty.out
90 }
91
92 func (tty *TTY) raw() (func() error, error) {
93 termios, err := unix.IoctlGetTermios(int(tty.in.Fd()), ioctlReadTermios)
94 if err != nil {
95 return nil, err
96 }
97 backup := *termios
98
99 termios.Iflag &^= unix.IGNBRK | unix.BRKINT | unix.PARMRK | unix.ISTRIP | unix.INLCR | unix.IGNCR | unix.ICRNL | unix.IXON
100 termios.Oflag &^= unix.OPOST
101 termios.Lflag &^= unix.ECHO | unix.ECHONL | unix.ICANON | unix.ISIG | unix.IEXTEN
102 termios.Cflag &^= unix.CSIZE | unix.PARENB
103 termios.Cflag |= unix.CS8
104 termios.Cc[unix.VMIN] = 1
105 termios.Cc[unix.VTIME] = 0
106 if err := unix.IoctlSetTermios(int(tty.in.Fd()), ioctlWriteTermios, termios); err != nil {
107 return nil, err
108 }
109
110 return func() error {
111 if err := unix.IoctlSetTermios(int(tty.in.Fd()), ioctlWriteTermios, &backup); err != nil {
112 return err
113 }
114 return nil
115 }, nil
116 }
117
118 func (tty *TTY) sigwinch() <-chan WINSIZE {
119 signal.Notify(tty.ss, syscall.SIGWINCH)
120
121 ws := make(chan WINSIZE)
122 go func() {
123 defer close(ws)
124 for sig := range tty.ss {
125 if sig != syscall.SIGWINCH {
126 continue
127 }
128
129 w, h, err := tty.size()
130 if err != nil {
131 continue
132 }
133
134 select {
135 case ws <- WINSIZE{W: w, H: h}:
136 default:
137 }
138
139 }
140 }()
141 return ws
142 }
143
View as plain text