mirror of
https://github.com/gizak/termui.git
synced 2025-04-27 13:48:51 +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) {
|
func Render(items ...Drawable) {
|
||||||
|
// draw background, etc for items with ANSI escape strings
|
||||||
for _, item := range items {
|
for _, item := range items {
|
||||||
|
if len(item.GetANSIString()) > 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
buf := NewBuffer(item.GetRect())
|
buf := NewBuffer(item.GetRect())
|
||||||
item.Lock()
|
item.Lock()
|
||||||
item.Draw(buf)
|
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 {
|
if ansiString := item.GetANSIString(); len(ansiString) > 0 {
|
||||||
fmt.Printf("%s", ansiString)
|
fmt.Printf("%s", ansiString)
|
||||||
continue
|
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()
|
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 (
|
var (
|
||||||
sixelCapable, isIterm2 bool
|
sixelCapable, isIterm2 bool
|
||||||
charBoxWidthInPixels, charBoxHeightInPixels float64
|
charBoxWidthInPixels, charBoxHeightInPixels float64
|
||||||
|
charBoxWidthColumns, charBoxHeightRows int
|
||||||
lastImageDimensions image.Rectangle
|
lastImageDimensions image.Rectangle
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
// example query: "\033[0c"
|
// 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
|
// the "4" signals that the terminal is capable of sixel
|
||||||
// conhost.exe knows this sequence.
|
// conhost.exe knows this sequence.
|
||||||
termCapabilities := queryTerm("\033[0c")
|
termCapabilities := queryTerm("\033[0c")
|
||||||
for i, cap := range termCapabilities {
|
for i, cap := range termCapabilities {
|
||||||
if i == 0 || i == len(termCapabilities)-1 {
|
if i == 0 || i == len(termCapabilities) - 1 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if string(cap) == `4` {
|
if string(cap) == `4` {
|
||||||
sixelCapable = true
|
sixelCapable = true
|
||||||
|
|
||||||
// terminal character box size measured in pixels
|
|
||||||
charBoxWidthInPixels, charBoxHeightInPixels = getTermCharBoxSize()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// # https://superuser.com/a/683971
|
// # https://superuser.com/a/683971
|
||||||
@ -79,9 +77,10 @@ func (self *Image) Draw(buf *Buffer) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// urxvt pixbuf / ...
|
|
||||||
|
|
||||||
// fall back - draw with box characters
|
// 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)
|
self.drawFallBack(buf)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -269,27 +268,49 @@ func (self *Image) drawANSI(buf *Buffer) (err error) {
|
|||||||
imageWidthInColumns := self.Inner.Dx()
|
imageWidthInColumns := self.Inner.Dx()
|
||||||
imageHeightInRows := self.Inner.Dy()
|
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
|
// calculate image size in pixels
|
||||||
imageWidthInPixels := int(float64(imageWidthInColumns) * charBoxWidthInPixels)
|
// subtract 1 pixel for small deviations from char box size (float64)
|
||||||
imageHeightInPixels := int(float64(imageHeightInRows) * charBoxHeightInPixels)
|
imageWidthInPixels := int(float64(imageWidthInColumns) * charBoxWidthInPixels) - 1
|
||||||
|
imageHeightInPixels := int(float64(imageHeightInRows) * charBoxHeightInPixels) - 1
|
||||||
if imageWidthInPixels == 0 || imageHeightInPixels == 0 {
|
if imageWidthInPixels == 0 || imageHeightInPixels == 0 {
|
||||||
return fmt.Errorf("could not calculate the image size in pixels")
|
return fmt.Errorf("could not calculate the image size in pixels")
|
||||||
}
|
}
|
||||||
|
|
||||||
termWidthInColumns, termHeightInRows := getTermSizeInChars()
|
|
||||||
|
|
||||||
// handle only partially displayed image
|
// handle only partially displayed image
|
||||||
// otherwise we get scrolling
|
// otherwise we get scrolling
|
||||||
var needsCrop bool
|
var needsCropX, needsCropY bool
|
||||||
imgCroppedWidth := imageWidthInPixels
|
var imgCroppedWidth, imgCroppedHeight int
|
||||||
imgCroppedHeight := imageHeightInPixels
|
imgCroppedWidth = imageWidthInPixels
|
||||||
if self.Max.X > int(termWidthInColumns)+1 {
|
imgCroppedHeight = imageHeightInPixels
|
||||||
imgCroppedWidth = int(float64(int(termWidthInColumns)-self.Inner.Min.X) * charBoxWidthInPixels)
|
if self.Max.Y >= int(termHeightInRows) {
|
||||||
needsCrop = true
|
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 {
|
if self.Max.X >= int(termWidthInColumns) {
|
||||||
imgCroppedHeight = int(float64(int(termHeightInRows)-self.Inner.Min.Y) * charBoxHeightInPixels)
|
var scrollExtraColumns int
|
||||||
needsCrop = true
|
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
|
// 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 //
|
// resize and crop the image //
|
||||||
img := imaging.Resize(self.Image, imageWidthInPixels, imageHeightInPixels, imaging.Lanczos)
|
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}})
|
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:
|
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
|
// sixel
|
||||||
|
// https://vt100.net/docs/vt3xx-gp/chapter14.html
|
||||||
if sixelCapable {
|
if sixelCapable {
|
||||||
byteBuf := new(bytes.Buffer)
|
byteBuf := new(bytes.Buffer)
|
||||||
enc := sixel.NewEncoder(byteBuf)
|
enc := sixel.NewEncoder(byteBuf)
|
||||||
@ -338,12 +367,12 @@ func (self *Image) drawANSI(buf *Buffer) (err error) {
|
|||||||
if err := enc.Encode(img); err != nil {
|
if err := enc.Encode(img); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// position where the image should appear (upper left corner) + sixel
|
// position where the image should appear (upper left corner) + sixel
|
||||||
// https://github.com/mintty/mintty/wiki/CtrlSeqs#sixel-graphics-end-position
|
// 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
|
// "\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.
|
// this prevents vertical scrolling when the image fills the last line.
|
||||||
// horizontal scrolling because of this did not happen in my test cases.
|
// 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())
|
self.Block.ANSIString = fmt.Sprintf("\033[%d;%dH\033[?8452h%s", imageDimensions.Min.Y, imageDimensions.Min.X, byteBuf.String())
|
||||||
// test string "HI"
|
// 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\\")
|
// 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")
|
return errors.New("no method applied for ANSI drawing")
|
||||||
}
|
}
|
||||||
|
|
||||||
func getTermCharBoxSize() (x, y float64) {
|
func getTermSize() (termWidthInColumns, termHeightInRows, termWidthInPixels, termHeightInPixels int, charBoxWidthInPixels, charBoxHeightInPixels float64, err error) {
|
||||||
if cx, cy := getTermSizeInChars(); cx != 0 && cy != 0 {
|
// this uses a combination of TIOCGWINSZ and \033[14t , \033[18t
|
||||||
px, py := getTermSizeInPixels()
|
// the syscall to TIOCGWINSZ only works locally
|
||||||
x = float64(px) / float64(cx)
|
|
||||||
y = float64(py) / float64(cy)
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func getTermSizeInChars() (x, y uint) {
|
func getTermSizeInChars() (x, y int) {
|
||||||
// query terminal size in character boxes
|
// query terminal size in character boxes
|
||||||
// answer: <termHeightInRows>;<termWidthInColumns>t
|
// answer: <termHeightInRows>;<termWidthInColumns>t
|
||||||
q := queryTerm("\033[18t")
|
q := queryTerm("\033[18t")
|
||||||
@ -374,8 +431,8 @@ func getTermSizeInChars() (x, y uint) {
|
|||||||
|
|
||||||
if yy, err := strconv.Atoi(string(q[1])); err == nil {
|
if yy, err := strconv.Atoi(string(q[1])); err == nil {
|
||||||
if xx, err := strconv.Atoi(string(q[2])); err == nil {
|
if xx, err := strconv.Atoi(string(q[2])); err == nil {
|
||||||
x = uint(xx)
|
x = xx
|
||||||
y = uint(yy)
|
y = yy
|
||||||
} else {
|
} else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -386,7 +443,7 @@ func getTermSizeInChars() (x, y uint) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func getTermSizeInPixels() (x, y uint) {
|
func getTermSizeInPixels() (x, y int) {
|
||||||
// query terminal size in pixels
|
// query terminal size in pixels
|
||||||
// answer: <termHeightInPixels>;<termWidthInPixels>t
|
// answer: <termHeightInPixels>;<termWidthInPixels>t
|
||||||
q := queryTerm("\033[14t")
|
q := queryTerm("\033[14t")
|
||||||
@ -397,8 +454,8 @@ func getTermSizeInPixels() (x, y uint) {
|
|||||||
|
|
||||||
if yy, err := strconv.Atoi(string(q[1])); err == nil {
|
if yy, err := strconv.Atoi(string(q[1])); err == nil {
|
||||||
if xx, err := strconv.Atoi(string(q[2])); err == nil {
|
if xx, err := strconv.Atoi(string(q[2])); err == nil {
|
||||||
x = uint(xx)
|
x = xx
|
||||||
y = uint(yy)
|
y = yy
|
||||||
} else {
|
} else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -410,29 +467,29 @@ func getTermSizeInPixels() (x, y uint) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func queryTerm(qs string) (ret [][]rune) {
|
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
|
// otherwise TUI wouldn't react to any further events
|
||||||
// resizing still works though
|
// resizing still works though
|
||||||
if len(os.Getenv("XTERM_VERSION")) > 0 {
|
if len(os.Getenv("XTERM_VERSION")) > 0 && qs != "\033[0c" {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var b []rune
|
var b []rune
|
||||||
|
|
||||||
tty, err := tty.Open()
|
t, err := tty.Open()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer tty.Close()
|
|
||||||
|
|
||||||
ch := make(chan bool, 1)
|
ch := make(chan bool, 1)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
|
defer t.Close()
|
||||||
// query terminal
|
// query terminal
|
||||||
fmt.Printf(qs)
|
fmt.Printf(qs)
|
||||||
|
|
||||||
for {
|
for {
|
||||||
r, err := tty.ReadRune()
|
r, err := t.ReadRune()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -452,7 +509,8 @@ func queryTerm(qs string) (ret [][]rune) {
|
|||||||
ch <- true
|
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()
|
defer timer.Stop()
|
||||||
|
|
||||||
select {
|
select {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user