1
0
mirror of https://github.com/gdamore/tcell.git synced 2025-04-27 13:48:50 +08:00
tcell/tscreen_linux.go
Graham Clark bff4943f9a Provide a fallback terminal size if the OS returns (0,0)
Sometimes, the TIOCGWINSZ ioctl returns all zeroes (including columns,
rows) without an error. I found this when experimenting with the jexer
TUI toolkit for java e.g.

mvn dependency:get -Dartifact=com.gitlab.klamonte:jexer:0.3.2
jexer -jar ~/.m2/repository/com/gitlab/klamonte/jexer/0.3.2/jexer-0.3.2.jar

Once the demo starts, click the "Terminal" button, then type

stty size

it returns

0 0

My understanding is that this is normal, and happens until SIGWINCH is
received, or the size is set explicitly with TIOCSWINSZ.

My tcell application crashed under the jexer terminal because I didn't
anticipate a window size of (0,0).

If you run vim under the jexer terminal, it correctly sizes itself to
80x24. The shell's TERM variable is xterm, and infocmp xterm | grep cols
shows a default of 80 and a default of 24 for lines. The documentation
for vim explains how it computes the terminal size:

https://github.com/vim/vim/blob/master/runtime/doc/term.txt#L629

- an ioctl call (TIOCGSIZE or TIOCGWINSZ, depends on your system)
- the environment variables "LINES" and "COLUMNS"
- from the termcap entries "li" and "co"

This PR replicates that logic in the getWinSize() function. I think it
makes sense here because tScreen holds the TermInfo struct but the
tcell.Screen interface does not expose TermInfo to clients - of course
because the screen is abstracted to work on Windows too.
2020-01-14 19:03:18 -08:00

143 lines
3.0 KiB
Go

// +build linux
// Copyright 2019 The TCell Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use file except in compliance with the License.
// You may obtain a copy of the license at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package tcell
import (
"os"
"os/signal"
"strconv"
"syscall"
"golang.org/x/sys/unix"
)
type termiosPrivate struct {
tio *unix.Termios
}
func (t *tScreen) termioInit() error {
var e error
var raw *unix.Termios
var tio *unix.Termios
if t.in, e = os.OpenFile("/dev/tty", os.O_RDONLY, 0); e != nil {
goto failed
}
if t.out, e = os.OpenFile("/dev/tty", os.O_WRONLY, 0); e != nil {
goto failed
}
tio, e = unix.IoctlGetTermios(int(t.out.Fd()), unix.TCGETS)
if e != nil {
goto failed
}
t.tiosp = &termiosPrivate{tio: tio}
// make a local copy, to make it raw
raw = &unix.Termios{
Cflag: tio.Cflag,
Oflag: tio.Oflag,
Iflag: tio.Iflag,
Lflag: tio.Lflag,
Cc: tio.Cc,
}
raw.Iflag &^= (unix.IGNBRK | unix.BRKINT | unix.PARMRK | unix.ISTRIP |
unix.INLCR | unix.IGNCR | unix.ICRNL | unix.IXON)
raw.Oflag &^= unix.OPOST
raw.Lflag &^= (unix.ECHO | unix.ECHONL | unix.ICANON | unix.ISIG |
unix.IEXTEN)
raw.Cflag &^= (unix.CSIZE | unix.PARENB)
raw.Cflag |= unix.CS8
// This is setup for blocking reads. In the past we attempted to
// use non-blocking reads, but now a separate input loop and timer
// copes with the problems we had on some systems (BSD/Darwin)
// where close hung forever.
raw.Cc[unix.VMIN] = 1
raw.Cc[unix.VTIME] = 0
e = unix.IoctlSetTermios(int(t.out.Fd()), unix.TCSETS, raw)
if e != nil {
goto failed
}
signal.Notify(t.sigwinch, syscall.SIGWINCH)
if w, h, e := t.getWinSize(); e == nil && w != 0 && h != 0 {
t.cells.Resize(w, h)
}
return nil
failed:
if t.in != nil {
t.in.Close()
}
if t.out != nil {
t.out.Close()
}
return e
}
func (t *tScreen) termioFini() {
signal.Stop(t.sigwinch)
<-t.indoneq
if t.out != nil && t.tiosp != nil {
unix.IoctlSetTermios(int(t.out.Fd()), unix.TCSETSF, t.tiosp.tio)
t.out.Close()
}
if t.in != nil {
t.in.Close()
}
}
func (t *tScreen) getWinSize() (int, int, error) {
wsz, err := unix.IoctlGetWinsize(int(t.out.Fd()), unix.TIOCGWINSZ)
if err != nil {
return -1, -1, err
}
cols := int(wsz.Col)
rows := int(wsz.Row)
if cols == 0 {
colsEnv := os.Getenv("COLUMNS")
if colsEnv != "" {
if cols, err = strconv.Atoi(colsEnv); err != nil {
return -1, -1, err
}
} else {
cols = t.ti.Columns
}
}
if rows == 0 {
rowsEnv := os.Getenv("LINES")
if rowsEnv != "" {
if rows, err = strconv.Atoi(rowsEnv); err != nil {
return -1, -1, err
}
} else {
rows = t.ti.Lines
}
}
return cols, rows, nil
}