mirror of
https://github.com/gizak/termui.git
synced 2025-04-26 13:48:54 +08:00
fixed xterm (added TIOCGWINSZ querying), vendored go-tty temporarily
This commit is contained in:
parent
78c8702461
commit
6f80c95d3c
5013
_examples/image_snake.go
Normal file
5013
_examples/image_snake.go
Normal file
File diff suppressed because it is too large
Load Diff
29
render.go
29
render.go
@ -21,7 +21,11 @@ type Drawable interface {
|
||||
}
|
||||
|
||||
func Render(items ...Drawable) {
|
||||
// draw background, etc for items with ANSI escape strings
|
||||
for _, item := range items {
|
||||
if len(item.GetANSIString()) > 0 {
|
||||
continue
|
||||
}
|
||||
buf := NewBuffer(item.GetRect())
|
||||
item.Lock()
|
||||
item.Draw(buf)
|
||||
@ -35,12 +39,35 @@ func Render(items ...Drawable) {
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
tb.Flush()
|
||||
|
||||
// draw the image over the already filled cells
|
||||
// draw images, etc over the already filled cells with ANSI escape strings (sixel, ...)
|
||||
for _, item := range items {
|
||||
if ansiString := item.GetANSIString(); len(ansiString) > 0 {
|
||||
fmt.Printf("%s", ansiString)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// draw items without ANSI strings last in case the ANSI escape strings ended messed up
|
||||
for _, item := range items {
|
||||
if len(item.GetANSIString()) == 0 {
|
||||
continue
|
||||
}
|
||||
buf := NewBuffer(item.GetRect())
|
||||
item.Lock()
|
||||
item.Draw(buf)
|
||||
item.Unlock()
|
||||
for point, cell := range buf.CellMap {
|
||||
if point.In(buf.Rectangle) {
|
||||
tb.SetCell(
|
||||
point.X, point.Y,
|
||||
cell.Rune,
|
||||
tb.Attribute(cell.Style.Fg+1)|tb.Attribute(cell.Style.Modifier), tb.Attribute(cell.Style.Bg+1),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
tb.Flush()
|
||||
}
|
||||
|
21
vendor/github.com/mattn/go-tty/LICENSE
generated
vendored
Normal file
21
vendor/github.com/mattn/go-tty/LICENSE
generated
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2018 Yasuhiro Matsumoto
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
49
vendor/github.com/mattn/go-tty/README.md
generated
vendored
Normal file
49
vendor/github.com/mattn/go-tty/README.md
generated
vendored
Normal file
@ -0,0 +1,49 @@
|
||||
# go-tty
|
||||
|
||||
Simple tty utility
|
||||
|
||||
## Usage
|
||||
|
||||
```go
|
||||
tty, err := tty.Open()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer tty.Close()
|
||||
|
||||
for {
|
||||
r, err := tty.ReadRune()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
// handle key event
|
||||
}
|
||||
```
|
||||
|
||||
if you are on windows and want to display ANSI colors, use <a href="https://github.com/mattn/go-colorable">go-colorable</a>.
|
||||
|
||||
```go
|
||||
tty, err := tty.Open()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer tty.Close()
|
||||
|
||||
out := colorable.NewColorable(tty.Output())
|
||||
|
||||
fmt.Fprintln(out, "\x1b[2J")
|
||||
```
|
||||
|
||||
## Installation
|
||||
|
||||
```
|
||||
$ go get github.com/mattn/go-tty
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
|
||||
## Author
|
||||
|
||||
Yasuhiro Matsumoto (a.k.a mattn)
|
125
vendor/github.com/mattn/go-tty/tty.go
generated
vendored
Normal file
125
vendor/github.com/mattn/go-tty/tty.go
generated
vendored
Normal file
@ -0,0 +1,125 @@
|
||||
package tty
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
func Open() (*TTY, error) {
|
||||
return open()
|
||||
}
|
||||
|
||||
func (tty *TTY) Raw() (func() error, error) {
|
||||
return tty.raw()
|
||||
}
|
||||
|
||||
func (tty *TTY) MustRaw() func() error {
|
||||
f, err := tty.raw()
|
||||
if err != nil {
|
||||
panic(err.Error())
|
||||
}
|
||||
return f
|
||||
}
|
||||
|
||||
func (tty *TTY) Buffered() bool {
|
||||
return tty.buffered()
|
||||
}
|
||||
|
||||
func (tty *TTY) ReadRune() (rune, error) {
|
||||
return tty.readRune()
|
||||
}
|
||||
|
||||
func (tty *TTY) Close() error {
|
||||
return tty.close()
|
||||
}
|
||||
|
||||
func (tty *TTY) Size() (int, int, error) {
|
||||
x, y, err := tty.size()
|
||||
return x, y, err
|
||||
}
|
||||
|
||||
func (tty *TTY) SizePixel() (int, int, int, int, error) {
|
||||
return tty.sizePixel()
|
||||
}
|
||||
|
||||
func (tty *TTY) Input() *os.File {
|
||||
return tty.input()
|
||||
}
|
||||
|
||||
func (tty *TTY) Output() *os.File {
|
||||
return tty.output()
|
||||
}
|
||||
|
||||
// Display types.
|
||||
const (
|
||||
displayNone = iota
|
||||
displayRune
|
||||
displayMask
|
||||
)
|
||||
|
||||
func (tty *TTY) readString(displayType int) (string, error) {
|
||||
rs := []rune{}
|
||||
loop:
|
||||
for {
|
||||
r, err := tty.readRune()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
switch r {
|
||||
case 13:
|
||||
break loop
|
||||
case 8, 127:
|
||||
if len(rs) > 0 {
|
||||
rs = rs[:len(rs)-1]
|
||||
if displayType != displayNone {
|
||||
tty.Output().WriteString("\b \b")
|
||||
}
|
||||
}
|
||||
default:
|
||||
if unicode.IsPrint(r) {
|
||||
rs = append(rs, r)
|
||||
switch displayType {
|
||||
case displayRune:
|
||||
tty.Output().WriteString(string(r))
|
||||
case displayMask:
|
||||
tty.Output().WriteString("*")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return string(rs), nil
|
||||
}
|
||||
|
||||
func (tty *TTY) ReadString() (string, error) {
|
||||
defer tty.Output().WriteString("\n")
|
||||
return tty.readString(displayRune)
|
||||
}
|
||||
|
||||
func (tty *TTY) ReadPassword() (string, error) {
|
||||
defer tty.Output().WriteString("\n")
|
||||
return tty.readString(displayMask)
|
||||
}
|
||||
|
||||
func (tty *TTY) ReadPasswordNoEcho() (string, error) {
|
||||
defer tty.Output().WriteString("\n")
|
||||
return tty.readString(displayNone)
|
||||
}
|
||||
|
||||
func (tty *TTY) ReadPasswordClear() (string, error) {
|
||||
s, err := tty.readString(displayMask)
|
||||
tty.Output().WriteString(
|
||||
strings.Repeat("\b", len(s)) +
|
||||
strings.Repeat(" ", len(s)) +
|
||||
strings.Repeat("\b", len(s)))
|
||||
return s, err
|
||||
}
|
||||
|
||||
type WINSIZE struct {
|
||||
W int
|
||||
H int
|
||||
}
|
||||
|
||||
func (tty *TTY) SIGWINCH() chan WINSIZE {
|
||||
return tty.sigwinch()
|
||||
}
|
12
vendor/github.com/mattn/go-tty/tty_bsd.go
generated
vendored
Normal file
12
vendor/github.com/mattn/go-tty/tty_bsd.go
generated
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
// +build darwin dragonfly freebsd netbsd openbsd
|
||||
|
||||
package tty
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
)
|
||||
|
||||
const (
|
||||
ioctlReadTermios = syscall.TIOCGETA
|
||||
ioctlWriteTermios = syscall.TIOCSETA
|
||||
)
|
8
vendor/github.com/mattn/go-tty/tty_linux.go
generated
vendored
Normal file
8
vendor/github.com/mattn/go-tty/tty_linux.go
generated
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
// +build linux
|
||||
|
||||
package tty
|
||||
|
||||
const (
|
||||
ioctlReadTermios = 0x5401 // syscall.TCGETS
|
||||
ioctlWriteTermios = 0x5402 // syscall.TCSETS
|
||||
)
|
69
vendor/github.com/mattn/go-tty/tty_plan9.go
generated
vendored
Normal file
69
vendor/github.com/mattn/go-tty/tty_plan9.go
generated
vendored
Normal file
@ -0,0 +1,69 @@
|
||||
package tty
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"os"
|
||||
"syscall"
|
||||
"errors"
|
||||
)
|
||||
s
|
||||
type TTY struct {
|
||||
in *os.File
|
||||
bin *bufio.Reader
|
||||
out *os.File
|
||||
}
|
||||
|
||||
func open() (*TTY, error) {
|
||||
tty := new(TTY)
|
||||
|
||||
in, err := os.Open("/dev/cons")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tty.in = in
|
||||
tty.bin = bufio.NewReader(in)
|
||||
|
||||
out, err := os.OpenFile("/dev/cons", syscall.O_WRONLY, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tty.out = out
|
||||
|
||||
return tty, nil
|
||||
}
|
||||
|
||||
func (tty *TTY) buffered() bool {
|
||||
return tty.bin.Buffered() > 0
|
||||
}
|
||||
|
||||
func (tty *TTY) readRune() (rune, error) {
|
||||
r, _, err := tty.bin.ReadRune()
|
||||
return r, err
|
||||
}
|
||||
|
||||
func (tty *TTY) close() (err error) {
|
||||
if err2 := tty.in.Close(); err2 != nil {
|
||||
err = err2
|
||||
}
|
||||
if err2 := tty.out.Close(); err2 != nil {
|
||||
err = err2
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (tty *TTY) size() (int, int, error) {
|
||||
return 80, 24, nil
|
||||
}
|
||||
|
||||
func (tty *TTY) sizePixel() (int, int, int, int, error) {
|
||||
x, y, _ := tty.size()
|
||||
return x, y, -1, -1, errors.New("no implemented method for querying size in pixels on Plan 9")
|
||||
}
|
||||
|
||||
func (tty *TTY) input() *os.File {
|
||||
return tty.in
|
||||
}
|
||||
|
||||
func (tty *TTY) output() *os.File {
|
||||
return tty.out
|
||||
}
|
137
vendor/github.com/mattn/go-tty/tty_unix.go
generated
vendored
Normal file
137
vendor/github.com/mattn/go-tty/tty_unix.go
generated
vendored
Normal file
@ -0,0 +1,137 @@
|
||||
// +build !windows
|
||||
// +build !plan9
|
||||
|
||||
package tty
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
type TTY struct {
|
||||
in *os.File
|
||||
bin *bufio.Reader
|
||||
out *os.File
|
||||
termios syscall.Termios
|
||||
ws chan WINSIZE
|
||||
ss chan os.Signal
|
||||
}
|
||||
|
||||
func open() (*TTY, error) {
|
||||
tty := new(TTY)
|
||||
|
||||
in, err := os.Open("/dev/tty")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tty.in = in
|
||||
tty.bin = bufio.NewReader(in)
|
||||
|
||||
out, err := os.OpenFile("/dev/tty", syscall.O_WRONLY, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tty.out = out
|
||||
|
||||
if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(tty.in.Fd()), ioctlReadTermios, uintptr(unsafe.Pointer(&tty.termios)), 0, 0, 0); err != 0 {
|
||||
return nil, err
|
||||
}
|
||||
newios := tty.termios
|
||||
newios.Iflag &^= syscall.ISTRIP | syscall.INLCR | syscall.ICRNL | syscall.IGNCR | syscall.IXON | syscall.IXOFF
|
||||
newios.Lflag &^= syscall.ECHO | syscall.ICANON /*| syscall.ISIG*/
|
||||
if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(tty.in.Fd()), ioctlWriteTermios, uintptr(unsafe.Pointer(&newios)), 0, 0, 0); err != 0 {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tty.ws = make(chan WINSIZE)
|
||||
tty.ss = make(chan os.Signal, 1)
|
||||
signal.Notify(tty.ss, syscall.SIGWINCH)
|
||||
go func() {
|
||||
defer close(tty.ws)
|
||||
for sig := range tty.ss {
|
||||
switch sig {
|
||||
case syscall.SIGWINCH:
|
||||
if w, h, err := tty.size(); err == nil {
|
||||
tty.ws <- WINSIZE{
|
||||
W: w,
|
||||
H: h,
|
||||
}
|
||||
}
|
||||
default:
|
||||
}
|
||||
}
|
||||
}()
|
||||
return tty, nil
|
||||
}
|
||||
|
||||
func (tty *TTY) buffered() bool {
|
||||
return tty.bin.Buffered() > 0
|
||||
}
|
||||
|
||||
func (tty *TTY) readRune() (rune, error) {
|
||||
r, _, err := tty.bin.ReadRune()
|
||||
return r, err
|
||||
}
|
||||
|
||||
func (tty *TTY) close() error {
|
||||
signal.Stop(tty.ss)
|
||||
close(tty.ss)
|
||||
_, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(tty.in.Fd()), ioctlWriteTermios, uintptr(unsafe.Pointer(&tty.termios)), 0, 0, 0)
|
||||
return err
|
||||
}
|
||||
|
||||
func (tty *TTY) size() (int, int, error) {
|
||||
x, y, _, _, err := tty.sizePixel()
|
||||
return x, y, err
|
||||
}
|
||||
|
||||
func (tty *TTY) sizePixel() (int, int, int, int, error) {
|
||||
var dim [4]uint16
|
||||
if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(tty.out.Fd()), uintptr(syscall.TIOCGWINSZ), uintptr(unsafe.Pointer(&dim)), 0, 0, 0); err != 0 {
|
||||
return -1, -1, -1, -1, err
|
||||
}
|
||||
return int(dim[1]), int(dim[0]), int(dim[2]), int(dim[3]), nil
|
||||
}
|
||||
|
||||
func (tty *TTY) input() *os.File {
|
||||
return tty.in
|
||||
}
|
||||
|
||||
func (tty *TTY) output() *os.File {
|
||||
return tty.out
|
||||
}
|
||||
|
||||
func (tty *TTY) raw() (func() error, error) {
|
||||
termios, err := unix.IoctlGetTermios(int(tty.in.Fd()), ioctlReadTermios)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
backup := *termios
|
||||
|
||||
termios.Iflag &^= unix.IGNBRK | unix.BRKINT | unix.PARMRK | unix.ISTRIP | unix.INLCR | unix.IGNCR | unix.ICRNL | unix.IXON
|
||||
termios.Oflag &^= unix.OPOST
|
||||
termios.Lflag &^= unix.ECHO | unix.ECHONL | unix.ICANON | unix.ISIG | unix.IEXTEN
|
||||
termios.Cflag &^= unix.CSIZE | unix.PARENB
|
||||
termios.Cflag |= unix.CS8
|
||||
termios.Cc[unix.VMIN] = 1
|
||||
termios.Cc[unix.VTIME] = 0
|
||||
if err := unix.IoctlSetTermios(int(tty.in.Fd()), ioctlWriteTermios, termios); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return func() error {
|
||||
if err := unix.IoctlSetTermios(int(tty.in.Fd()), ioctlWriteTermios, &backup); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (tty *TTY) sigwinch() chan WINSIZE {
|
||||
return tty.ws
|
||||
}
|
333
vendor/github.com/mattn/go-tty/tty_windows.go
generated
vendored
Normal file
333
vendor/github.com/mattn/go-tty/tty_windows.go
generated
vendored
Normal file
@ -0,0 +1,333 @@
|
||||
// +build windows
|
||||
|
||||
package tty
|
||||
|
||||
import (
|
||||
"os"
|
||||
"errors"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"github.com/mattn/go-isatty"
|
||||
)
|
||||
|
||||
const (
|
||||
rightAltPressed = 1
|
||||
leftAltPressed = 2
|
||||
rightCtrlPressed = 4
|
||||
leftCtrlPressed = 8
|
||||
shiftPressed = 0x0010
|
||||
ctrlPressed = rightCtrlPressed | leftCtrlPressed
|
||||
altPressed = rightAltPressed | leftAltPressed
|
||||
)
|
||||
|
||||
const (
|
||||
enableProcessedInput = 0x1
|
||||
enableLineInput = 0x2
|
||||
enableEchoInput = 0x4
|
||||
enableWindowInput = 0x8
|
||||
enableMouseInput = 0x10
|
||||
enableInsertMode = 0x20
|
||||
enableQuickEditMode = 0x40
|
||||
enableExtendedFlag = 0x80
|
||||
|
||||
enableProcessedOutput = 1
|
||||
enableWrapAtEolOutput = 2
|
||||
|
||||
keyEvent = 0x1
|
||||
mouseEvent = 0x2
|
||||
windowBufferSizeEvent = 0x4
|
||||
)
|
||||
|
||||
var kernel32 = syscall.NewLazyDLL("kernel32.dll")
|
||||
|
||||
var (
|
||||
procAllocConsole = kernel32.NewProc("AllocConsole")
|
||||
procSetStdHandle = kernel32.NewProc("SetStdHandle")
|
||||
procGetStdHandle = kernel32.NewProc("GetStdHandle")
|
||||
procSetConsoleScreenBufferSize = kernel32.NewProc("SetConsoleScreenBufferSize")
|
||||
procCreateConsoleScreenBuffer = kernel32.NewProc("CreateConsoleScreenBuffer")
|
||||
procGetConsoleScreenBufferInfo = kernel32.NewProc("GetConsoleScreenBufferInfo")
|
||||
procWriteConsoleOutputCharacter = kernel32.NewProc("WriteConsoleOutputCharacterW")
|
||||
procWriteConsoleOutputAttribute = kernel32.NewProc("WriteConsoleOutputAttribute")
|
||||
procGetConsoleCursorInfo = kernel32.NewProc("GetConsoleCursorInfo")
|
||||
procSetConsoleCursorInfo = kernel32.NewProc("SetConsoleCursorInfo")
|
||||
procSetConsoleCursorPosition = kernel32.NewProc("SetConsoleCursorPosition")
|
||||
procReadConsoleInput = kernel32.NewProc("ReadConsoleInputW")
|
||||
procGetConsoleMode = kernel32.NewProc("GetConsoleMode")
|
||||
procSetConsoleMode = kernel32.NewProc("SetConsoleMode")
|
||||
procFillConsoleOutputCharacter = kernel32.NewProc("FillConsoleOutputCharacterW")
|
||||
procFillConsoleOutputAttribute = kernel32.NewProc("FillConsoleOutputAttribute")
|
||||
procScrollConsoleScreenBuffer = kernel32.NewProc("ScrollConsoleScreenBufferW")
|
||||
)
|
||||
|
||||
type wchar uint16
|
||||
type short int16
|
||||
type dword uint32
|
||||
type word uint16
|
||||
|
||||
type coord struct {
|
||||
x short
|
||||
y short
|
||||
}
|
||||
|
||||
type smallRect struct {
|
||||
left short
|
||||
top short
|
||||
right short
|
||||
bottom short
|
||||
}
|
||||
|
||||
type consoleScreenBufferInfo struct {
|
||||
size coord
|
||||
cursorPosition coord
|
||||
attributes word
|
||||
window smallRect
|
||||
maximumWindowSize coord
|
||||
}
|
||||
|
||||
type consoleCursorInfo struct {
|
||||
size dword
|
||||
visible int32
|
||||
}
|
||||
|
||||
type inputRecord struct {
|
||||
eventType word
|
||||
_ [2]byte
|
||||
event [16]byte
|
||||
}
|
||||
|
||||
type keyEventRecord struct {
|
||||
keyDown int32
|
||||
repeatCount word
|
||||
virtualKeyCode word
|
||||
virtualScanCode word
|
||||
unicodeChar wchar
|
||||
controlKeyState dword
|
||||
}
|
||||
|
||||
type windowBufferSizeRecord struct {
|
||||
size coord
|
||||
}
|
||||
|
||||
type mouseEventRecord struct {
|
||||
mousePos coord
|
||||
buttonState dword
|
||||
controlKeyState dword
|
||||
eventFlags dword
|
||||
}
|
||||
|
||||
type charInfo struct {
|
||||
unicodeChar wchar
|
||||
attributes word
|
||||
}
|
||||
|
||||
type TTY struct {
|
||||
in *os.File
|
||||
out *os.File
|
||||
st uint32
|
||||
rs []rune
|
||||
ws chan WINSIZE
|
||||
}
|
||||
|
||||
func readConsoleInput(fd uintptr, record *inputRecord) (err error) {
|
||||
var w uint32
|
||||
r1, _, err := procReadConsoleInput.Call(fd, uintptr(unsafe.Pointer(record)), 1, uintptr(unsafe.Pointer(&w)))
|
||||
if r1 == 0 {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func open() (*TTY, error) {
|
||||
tty := new(TTY)
|
||||
if false && isatty.IsTerminal(os.Stdin.Fd()) {
|
||||
tty.in = os.Stdin
|
||||
} else {
|
||||
in, err := syscall.Open("CONIN$", syscall.O_RDWR, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tty.in = os.NewFile(uintptr(in), "/dev/tty")
|
||||
}
|
||||
|
||||
if isatty.IsTerminal(os.Stdout.Fd()) {
|
||||
tty.out = os.Stdout
|
||||
} else {
|
||||
procAllocConsole.Call()
|
||||
out, err := syscall.Open("CONOUT$", syscall.O_RDWR, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tty.out = os.NewFile(uintptr(out), "/dev/tty")
|
||||
}
|
||||
|
||||
h := tty.in.Fd()
|
||||
var st uint32
|
||||
r1, _, err := procGetConsoleMode.Call(h, uintptr(unsafe.Pointer(&st)))
|
||||
if r1 == 0 {
|
||||
return nil, err
|
||||
}
|
||||
tty.st = st
|
||||
|
||||
st &^= enableEchoInput
|
||||
st &^= enableInsertMode
|
||||
st &^= enableLineInput
|
||||
st &^= enableMouseInput
|
||||
st &^= enableWindowInput
|
||||
st &^= enableExtendedFlag
|
||||
st &^= enableQuickEditMode
|
||||
|
||||
// ignore error
|
||||
procSetConsoleMode.Call(h, uintptr(st))
|
||||
|
||||
tty.ws = make(chan WINSIZE)
|
||||
|
||||
return tty, nil
|
||||
}
|
||||
|
||||
func (tty *TTY) buffered() bool {
|
||||
return len(tty.rs) > 0
|
||||
}
|
||||
|
||||
func (tty *TTY) readRune() (rune, error) {
|
||||
if len(tty.rs) > 0 {
|
||||
r := tty.rs[0]
|
||||
tty.rs = tty.rs[1:]
|
||||
return r, nil
|
||||
}
|
||||
var ir inputRecord
|
||||
err := readConsoleInput(tty.in.Fd(), &ir)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
switch ir.eventType {
|
||||
case windowBufferSizeEvent:
|
||||
wr := (*windowBufferSizeRecord)(unsafe.Pointer(&ir.event))
|
||||
tty.ws <- WINSIZE{
|
||||
W: int(wr.size.x),
|
||||
H: int(wr.size.y),
|
||||
}
|
||||
case keyEvent:
|
||||
kr := (*keyEventRecord)(unsafe.Pointer(&ir.event))
|
||||
if kr.keyDown != 0 {
|
||||
if kr.controlKeyState&altPressed != 0 && kr.unicodeChar > 0 {
|
||||
tty.rs = []rune{rune(kr.unicodeChar)}
|
||||
return rune(0x1b), nil
|
||||
}
|
||||
if kr.unicodeChar > 0 {
|
||||
if kr.controlKeyState&shiftPressed != 0 {
|
||||
switch kr.unicodeChar {
|
||||
case 0x09:
|
||||
tty.rs = []rune{0x5b, 0x5a}
|
||||
return rune(0x1b), nil
|
||||
}
|
||||
}
|
||||
return rune(kr.unicodeChar), nil
|
||||
}
|
||||
vk := kr.virtualKeyCode
|
||||
switch vk {
|
||||
case 0x21: // page-up
|
||||
tty.rs = []rune{0x5b, 0x35, 0x7e}
|
||||
return rune(0x1b), nil
|
||||
case 0x22: // page-down
|
||||
tty.rs = []rune{0x5b, 0x36, 0x7e}
|
||||
return rune(0x1b), nil
|
||||
case 0x23: // end
|
||||
tty.rs = []rune{0x5b, 0x46}
|
||||
return rune(0x1b), nil
|
||||
case 0x24: // home
|
||||
tty.rs = []rune{0x5b, 0x48}
|
||||
return rune(0x1b), nil
|
||||
case 0x25: // left
|
||||
tty.rs = []rune{0x5b, 0x44}
|
||||
return rune(0x1b), nil
|
||||
case 0x26: // up
|
||||
tty.rs = []rune{0x5b, 0x41}
|
||||
return rune(0x1b), nil
|
||||
case 0x27: // right
|
||||
tty.rs = []rune{0x5b, 0x43}
|
||||
return rune(0x1b), nil
|
||||
case 0x28: // down
|
||||
tty.rs = []rune{0x5b, 0x42}
|
||||
return rune(0x1b), nil
|
||||
case 0x2e: // delete
|
||||
tty.rs = []rune{0x5b, 0x33, 0x7e}
|
||||
return rune(0x1b), nil
|
||||
case 0x70, 0x71, 0x72, 0x73: // F1,F2,F3,F4
|
||||
tty.rs = []rune{0x5b, 0x4f, rune(vk) - 0x20}
|
||||
return rune(0x1b), nil
|
||||
case 0x074, 0x75, 0x76, 0x77: // F5,F6,F7,F8
|
||||
tty.rs = []rune{0x5b, 0x31, rune(vk) - 0x3f, 0x7e}
|
||||
return rune(0x1b), nil
|
||||
case 0x78, 0x79: // F9,F10
|
||||
tty.rs = []rune{0x5b, 0x32, rune(vk) - 0x48, 0x7e}
|
||||
return rune(0x1b), nil
|
||||
case 0x7a, 0x7b: // F11,F12
|
||||
tty.rs = []rune{0x5b, 0x32, rune(vk) - 0x47, 0x7e}
|
||||
return rune(0x1b), nil
|
||||
}
|
||||
return 0, nil
|
||||
}
|
||||
}
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
func (tty *TTY) close() error {
|
||||
close(tty.ws)
|
||||
procSetConsoleMode.Call(tty.in.Fd(), uintptr(tty.st))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (tty *TTY) size() (int, int, error) {
|
||||
var csbi consoleScreenBufferInfo
|
||||
r1, _, err := procGetConsoleScreenBufferInfo.Call(tty.out.Fd(), uintptr(unsafe.Pointer(&csbi)))
|
||||
if r1 == 0 {
|
||||
return 0, 0, err
|
||||
}
|
||||
return int(csbi.window.right - csbi.window.left + 1), int(csbi.window.bottom - csbi.window.top + 1), nil
|
||||
}
|
||||
|
||||
func (tty *TTY) sizePixel() (int, int, int, int, error) {
|
||||
x, y, err := tty.size()
|
||||
if err != nil {
|
||||
x = -1
|
||||
y = -1
|
||||
}
|
||||
return x, y, -1, -1, errors.New("no implemented method for querying size in pixels on Windows")
|
||||
}
|
||||
|
||||
func (tty *TTY) input() *os.File {
|
||||
return tty.in
|
||||
}
|
||||
|
||||
func (tty *TTY) output() *os.File {
|
||||
return tty.out
|
||||
}
|
||||
|
||||
func (tty *TTY) raw() (func() error, error) {
|
||||
var st uint32
|
||||
r1, _, err := procGetConsoleMode.Call(tty.in.Fd(), uintptr(unsafe.Pointer(&st)))
|
||||
if r1 == 0 {
|
||||
return nil, err
|
||||
}
|
||||
mode := st &^ (enableEchoInput | enableProcessedInput | enableLineInput | enableProcessedOutput)
|
||||
r1, _, err = procSetConsoleMode.Call(tty.in.Fd(), uintptr(mode))
|
||||
if r1 == 0 {
|
||||
return nil, err
|
||||
}
|
||||
return func() error {
|
||||
r1, _, err := procSetConsoleMode.Call(tty.in.Fd(), uintptr(st))
|
||||
if r1 == 0 {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (tty *TTY) sigwinch() chan WINSIZE {
|
||||
return tty.ws
|
||||
}
|
136
widgets/image.go
136
widgets/image.go
@ -35,24 +35,22 @@ type Image struct {
|
||||
var (
|
||||
sixelCapable, isIterm2 bool
|
||||
charBoxWidthInPixels, charBoxHeightInPixels float64
|
||||
charBoxWidthColumns, charBoxHeightRows int
|
||||
lastImageDimensions image.Rectangle
|
||||
)
|
||||
|
||||
func init() {
|
||||
// example query: "\033[0c"
|
||||
// possible answer from the terminal (here xterm): "\033[[?63;1;2;4;6;9;15;22c"
|
||||
// possible answer from the terminal (here xterm): "\033[[?63;1;2;4;6;9;15;22c", vte(?): ...62,9;c
|
||||
// the "4" signals that the terminal is capable of sixel
|
||||
// conhost.exe knows this sequence.
|
||||
termCapabilities := queryTerm("\033[0c")
|
||||
for i, cap := range termCapabilities {
|
||||
if i == 0 || i == len(termCapabilities)-1 {
|
||||
if i == 0 || i == len(termCapabilities) - 1 {
|
||||
continue
|
||||
}
|
||||
if string(cap) == `4` {
|
||||
sixelCapable = true
|
||||
|
||||
// terminal character box size measured in pixels
|
||||
charBoxWidthInPixels, charBoxHeightInPixels = getTermCharBoxSize()
|
||||
}
|
||||
}
|
||||
// # https://superuser.com/a/683971
|
||||
@ -79,9 +77,10 @@ func (self *Image) Draw(buf *Buffer) {
|
||||
}
|
||||
}
|
||||
|
||||
// urxvt pixbuf / ...
|
||||
|
||||
// fall back - draw with box characters
|
||||
// possible enhancement: make use of further box characters like chafa:
|
||||
// https://hpjansson.org/chafa/
|
||||
// https://github.com/hpjansson/chafa/
|
||||
self.drawFallBack(buf)
|
||||
}
|
||||
|
||||
@ -269,27 +268,49 @@ func (self *Image) drawANSI(buf *Buffer) (err error) {
|
||||
imageWidthInColumns := self.Inner.Dx()
|
||||
imageHeightInRows := self.Inner.Dy()
|
||||
|
||||
// terminal size in cells and pixels and calculated terminal character box size in pixels
|
||||
var termWidthInColumns, termHeightInRows int
|
||||
var charBoxWidthInPixelsTemp, charBoxHeightInPixelsTemp float64
|
||||
termWidthInColumns, termHeightInRows, _, _, charBoxWidthInPixelsTemp, charBoxHeightInPixelsTemp, err = getTermSize()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// update if value is more precise
|
||||
if termWidthInColumns > charBoxWidthColumns {
|
||||
charBoxWidthInPixels = charBoxWidthInPixelsTemp
|
||||
}
|
||||
if termHeightInRows > charBoxHeightRows {
|
||||
charBoxHeightInPixels = charBoxHeightInPixelsTemp
|
||||
}
|
||||
|
||||
// calculate image size in pixels
|
||||
imageWidthInPixels := int(float64(imageWidthInColumns) * charBoxWidthInPixels)
|
||||
imageHeightInPixels := int(float64(imageHeightInRows) * charBoxHeightInPixels)
|
||||
// subtract 1 pixel for small deviations from char box size (float64)
|
||||
imageWidthInPixels := int(float64(imageWidthInColumns) * charBoxWidthInPixels) - 1
|
||||
imageHeightInPixels := int(float64(imageHeightInRows) * charBoxHeightInPixels) - 1
|
||||
if imageWidthInPixels == 0 || imageHeightInPixels == 0 {
|
||||
return fmt.Errorf("could not calculate the image size in pixels")
|
||||
}
|
||||
|
||||
termWidthInColumns, termHeightInRows := getTermSizeInChars()
|
||||
|
||||
// handle only partially displayed image
|
||||
// otherwise we get scrolling
|
||||
var needsCrop bool
|
||||
imgCroppedWidth := imageWidthInPixels
|
||||
imgCroppedHeight := imageHeightInPixels
|
||||
if self.Max.X > int(termWidthInColumns)+1 {
|
||||
imgCroppedWidth = int(float64(int(termWidthInColumns)-self.Inner.Min.X) * charBoxWidthInPixels)
|
||||
needsCrop = true
|
||||
var needsCropX, needsCropY bool
|
||||
var imgCroppedWidth, imgCroppedHeight int
|
||||
imgCroppedWidth = imageWidthInPixels
|
||||
imgCroppedHeight = imageHeightInPixels
|
||||
if self.Max.Y >= int(termHeightInRows) {
|
||||
var scrollExtraRows int
|
||||
// remove last 2 rows for xterm when cropped vertically to prevent scrolling
|
||||
if len(os.Getenv("XTERM_VERSION")) > 0 {
|
||||
scrollExtraRows = 2
|
||||
}
|
||||
// subtract 1 pixel for small deviations from char box size (float64)
|
||||
imgCroppedHeight = int(float64(int(termHeightInRows) - self.Inner.Min.Y - scrollExtraRows) * charBoxHeightInPixels) - 1
|
||||
needsCropY = true
|
||||
}
|
||||
if self.Max.Y > int(termHeightInRows)+1 {
|
||||
imgCroppedHeight = int(float64(int(termHeightInRows)-self.Inner.Min.Y) * charBoxHeightInPixels)
|
||||
needsCrop = true
|
||||
if self.Max.X >= int(termWidthInColumns) {
|
||||
var scrollExtraColumns int
|
||||
imgCroppedWidth = int(float64(int(termWidthInColumns) - self.Inner.Min.X - scrollExtraColumns) * charBoxWidthInPixels) - 1
|
||||
needsCropX = true
|
||||
}
|
||||
|
||||
// this is meant for comparison and for positioning in the ANSI string
|
||||
@ -304,7 +325,7 @@ func (self *Image) drawANSI(buf *Buffer) (err error) {
|
||||
|
||||
// resize and crop the image //
|
||||
img := imaging.Resize(self.Image, imageWidthInPixels, imageHeightInPixels, imaging.Lanczos)
|
||||
if needsCrop {
|
||||
if needsCropX || needsCropY {
|
||||
img = imaging.Crop(img, image.Rectangle{Min: image.Point{X: 0, Y: 0}, Max: image.Point{X: imgCroppedWidth, Y: imgCroppedHeight}})
|
||||
}
|
||||
|
||||
@ -330,7 +351,15 @@ func (self *Image) drawANSI(buf *Buffer) (err error) {
|
||||
}
|
||||
skipIterm2:
|
||||
|
||||
// possible enhancements:
|
||||
// kitty https://sw.kovidgoyal.net/kitty/graphics-protocol.html
|
||||
// Terminology (from Enlightenment) https://www.enlightenment.org/docs/apps/terminology.md#tycat https://github.com/billiob/terminology
|
||||
// urxvt pixbuf / ...
|
||||
//
|
||||
// Tektronix 4014, ReGis
|
||||
|
||||
// sixel
|
||||
// https://vt100.net/docs/vt3xx-gp/chapter14.html
|
||||
if sixelCapable {
|
||||
byteBuf := new(bytes.Buffer)
|
||||
enc := sixel.NewEncoder(byteBuf)
|
||||
@ -338,12 +367,12 @@ func (self *Image) drawANSI(buf *Buffer) (err error) {
|
||||
if err := enc.Encode(img); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// position where the image should appear (upper left corner) + sixel
|
||||
// https://github.com/mintty/mintty/wiki/CtrlSeqs#sixel-graphics-end-position
|
||||
// "\033[?8452h" sets the cursor next right to the bottom of the image instead of below
|
||||
// this prevents vertical scrolling when the image fills the last line.
|
||||
// horizontal scrolling because of this did not happen in my test cases.
|
||||
// "\033[?80l" disables sixel scrolling if it isn't already.
|
||||
self.Block.ANSIString = fmt.Sprintf("\033[%d;%dH\033[?8452h%s", imageDimensions.Min.Y, imageDimensions.Min.X, byteBuf.String())
|
||||
// test string "HI"
|
||||
// self.Block.ANSIString = fmt.Sprintf("\033[%d;%dH\033[?8452h%s", self.Inner.Min.Y+1, self.Inner.Min.X+1, "\033Pq#0;2;0;0;0#1;2;100;100;0#2;2;0;100;0#1~~@@vv@@~~@@~~$#2??}}GG}}??}}??-#1!14@\033\\")
|
||||
@ -354,16 +383,44 @@ func (self *Image) drawANSI(buf *Buffer) (err error) {
|
||||
return errors.New("no method applied for ANSI drawing")
|
||||
}
|
||||
|
||||
func getTermCharBoxSize() (x, y float64) {
|
||||
if cx, cy := getTermSizeInChars(); cx != 0 && cy != 0 {
|
||||
px, py := getTermSizeInPixels()
|
||||
x = float64(px) / float64(cx)
|
||||
y = float64(py) / float64(cy)
|
||||
func getTermSize() (termWidthInColumns, termHeightInRows, termWidthInPixels, termHeightInPixels int, charBoxWidthInPixels, charBoxHeightInPixels float64, err error) {
|
||||
// this uses a combination of TIOCGWINSZ and \033[14t , \033[18t
|
||||
// the syscall to TIOCGWINSZ only works locally
|
||||
|
||||
var cx, cy, px, py int
|
||||
err = nil
|
||||
|
||||
t, err := tty.Open()
|
||||
defer t.Close()
|
||||
|
||||
cx, cy, px, py, err = t.SizePixel()
|
||||
if err == nil {
|
||||
if cx > 0 && cy > 0 {
|
||||
if px <= 0 || py <= 0 {
|
||||
px, py = getTermSizeInPixels()
|
||||
}
|
||||
} else {
|
||||
if cx, cy = getTermSizeInChars(); cx != 0 && cy != 0 {
|
||||
if px <= 0 || py <= 0 {
|
||||
px, py = getTermSizeInPixels()
|
||||
}
|
||||
} else {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
termWidthInColumns = cx
|
||||
termHeightInRows = cy
|
||||
termWidthInPixels = px
|
||||
termHeightInPixels = py
|
||||
charBoxWidthInPixels = float64(px) / float64(cx)
|
||||
charBoxHeightInPixels = float64(py) / float64(cy)
|
||||
return
|
||||
}
|
||||
|
||||
func getTermSizeInChars() (x, y uint) {
|
||||
func getTermSizeInChars() (x, y int) {
|
||||
// query terminal size in character boxes
|
||||
// answer: <termHeightInRows>;<termWidthInColumns>t
|
||||
q := queryTerm("\033[18t")
|
||||
@ -374,8 +431,8 @@ func getTermSizeInChars() (x, y uint) {
|
||||
|
||||
if yy, err := strconv.Atoi(string(q[1])); err == nil {
|
||||
if xx, err := strconv.Atoi(string(q[2])); err == nil {
|
||||
x = uint(xx)
|
||||
y = uint(yy)
|
||||
x = xx
|
||||
y = yy
|
||||
} else {
|
||||
return
|
||||
}
|
||||
@ -386,7 +443,7 @@ func getTermSizeInChars() (x, y uint) {
|
||||
return
|
||||
}
|
||||
|
||||
func getTermSizeInPixels() (x, y uint) {
|
||||
func getTermSizeInPixels() (x, y int) {
|
||||
// query terminal size in pixels
|
||||
// answer: <termHeightInPixels>;<termWidthInPixels>t
|
||||
q := queryTerm("\033[14t")
|
||||
@ -397,8 +454,8 @@ func getTermSizeInPixels() (x, y uint) {
|
||||
|
||||
if yy, err := strconv.Atoi(string(q[1])); err == nil {
|
||||
if xx, err := strconv.Atoi(string(q[2])); err == nil {
|
||||
x = uint(xx)
|
||||
y = uint(yy)
|
||||
x = xx
|
||||
y = yy
|
||||
} else {
|
||||
return
|
||||
}
|
||||
@ -410,29 +467,29 @@ func getTermSizeInPixels() (x, y uint) {
|
||||
}
|
||||
|
||||
func queryTerm(qs string) (ret [][]rune) {
|
||||
// temporary fix for xterm
|
||||
// temporary fix for xterm - not completely sure if still needed
|
||||
// otherwise TUI wouldn't react to any further events
|
||||
// resizing still works though
|
||||
if len(os.Getenv("XTERM_VERSION")) > 0 {
|
||||
if len(os.Getenv("XTERM_VERSION")) > 0 && qs != "\033[0c" {
|
||||
return
|
||||
}
|
||||
|
||||
var b []rune
|
||||
|
||||
tty, err := tty.Open()
|
||||
t, err := tty.Open()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer tty.Close()
|
||||
|
||||
ch := make(chan bool, 1)
|
||||
|
||||
go func() {
|
||||
defer t.Close()
|
||||
// query terminal
|
||||
fmt.Printf(qs)
|
||||
|
||||
for {
|
||||
r, err := tty.ReadRune()
|
||||
r, err := t.ReadRune()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@ -452,7 +509,8 @@ func queryTerm(qs string) (ret [][]rune) {
|
||||
ch <- true
|
||||
}()
|
||||
|
||||
timer := time.NewTimer(50 * time.Millisecond)
|
||||
// on my system the terminals mlterm, xterm need at least around 100 microseconds
|
||||
timer := time.NewTimer(500 * time.Microsecond)
|
||||
defer timer.Stop()
|
||||
|
||||
select {
|
||||
|
Loading…
x
Reference in New Issue
Block a user