mirror of https://github.com/liamg/aminal.git
fixed resizing and got basic input working
This commit is contained in:
parent
392a6239ef
commit
49e502cc0b
|
@ -0,0 +1,58 @@
|
||||||
|
package gui
|
||||||
|
|
||||||
|
import (
|
||||||
|
v41 "github.com/4ydx/gltext/v4.1"
|
||||||
|
"github.com/go-gl/mathgl/mgl32"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Cell struct {
|
||||||
|
text *v41.Text
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCell(font *v41.Font, x float32, y float32, w float32, h float32) Cell {
|
||||||
|
cell := Cell{
|
||||||
|
text: v41.NewText(font, 1.0, 1.1),
|
||||||
|
}
|
||||||
|
|
||||||
|
cell.text.SetPosition(mgl32.Vec2{x, y})
|
||||||
|
|
||||||
|
return cell
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cell *Cell) Draw() {
|
||||||
|
|
||||||
|
if cell.text != nil {
|
||||||
|
cell.text.Draw()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cell *Cell) Show() {
|
||||||
|
if cell.text != nil {
|
||||||
|
cell.text.Show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cell *Cell) Hide() {
|
||||||
|
if cell.text != nil {
|
||||||
|
cell.text.Hide()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cell *Cell) SetRune(r rune) {
|
||||||
|
if cell.text != nil {
|
||||||
|
cell.text.SetString(string(r))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cell *Cell) SetColour(r float32, g float32, b float32) {
|
||||||
|
if cell.text != nil {
|
||||||
|
cell.text.SetColor(mgl32.Vec3{r, g, b})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cell *Cell) Release() {
|
||||||
|
if cell.text != nil {
|
||||||
|
cell.text.Release()
|
||||||
|
}
|
||||||
|
}
|
181
gui/gui.go
181
gui/gui.go
|
@ -5,7 +5,6 @@ import (
|
||||||
"math"
|
"math"
|
||||||
"os"
|
"os"
|
||||||
"runtime"
|
"runtime"
|
||||||
"sync"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/4ydx/gltext"
|
"github.com/4ydx/gltext"
|
||||||
|
@ -29,10 +28,10 @@ type GUI struct {
|
||||||
height int
|
height int
|
||||||
charWidth float32
|
charWidth float32
|
||||||
charHeight float32
|
charHeight float32
|
||||||
texts [][]*v41.Text
|
cells [][]Cell
|
||||||
textLock sync.Mutex
|
|
||||||
cols int
|
cols int
|
||||||
rows int
|
rows int
|
||||||
|
capslock bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(config config.Config, terminal *terminal.Terminal, logger *zap.SugaredLogger) *GUI {
|
func New(config config.Config, terminal *terminal.Terminal, logger *zap.SugaredLogger) *GUI {
|
||||||
|
@ -44,17 +43,46 @@ func New(config config.Config, terminal *terminal.Terminal, logger *zap.SugaredL
|
||||||
width: 600,
|
width: 600,
|
||||||
height: 300,
|
height: 300,
|
||||||
terminal: terminal,
|
terminal: terminal,
|
||||||
texts: [][]*v41.Text{},
|
cells: [][]Cell{},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// inspired by https://kylewbanks.com/blog/tutorial-opengl-with-golang-part-1-hello-opengl
|
// inspired by https://kylewbanks.com/blog/tutorial-opengl-with-golang-part-1-hello-opengl
|
||||||
|
|
||||||
func (gui *GUI) SetSize(w int, h int) {
|
func (gui *GUI) key(w *glfw.Window, key glfw.Key, scancode int, action glfw.Action, mods glfw.ModifierKey) {
|
||||||
gui.window.SetSize(w, h)
|
|
||||||
gui.resize(gui.window, w, h)
|
caps := gui.capslock
|
||||||
|
|
||||||
|
if mods&glfw.ModShift > 0 {
|
||||||
|
caps = !caps
|
||||||
|
}
|
||||||
|
|
||||||
|
if action == glfw.Repeat || action == glfw.Press {
|
||||||
|
|
||||||
|
switch key {
|
||||||
|
case glfw.KeyCapsLock:
|
||||||
|
gui.capslock = !gui.capslock
|
||||||
|
case glfw.KeyEnter:
|
||||||
|
gui.terminal.Write([]byte{0x0a})
|
||||||
|
default:
|
||||||
|
if key >= 0x41 && key <= 0x5a { // A-Z, normalise to lower
|
||||||
|
key += 0x20
|
||||||
|
}
|
||||||
|
if key >= 0x61 && key <= 0x7a { // a-z
|
||||||
|
if caps {
|
||||||
|
key -= 0x20
|
||||||
|
}
|
||||||
|
}
|
||||||
|
gui.terminal.Write([]byte{byte(key)})
|
||||||
|
}
|
||||||
|
|
||||||
|
//gui.logger.Debugf("Key pressed: 0x%X %q", key, string([]byte{byte(key)}))
|
||||||
|
//gui.terminal.Write([]byte{byte(scancode)})
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// can only be called on OS thread
|
||||||
func (gui *GUI) resize(w *glfw.Window, width int, height int) {
|
func (gui *GUI) resize(w *glfw.Window, width int, height int) {
|
||||||
|
|
||||||
if width == gui.width && height == gui.height {
|
if width == gui.width && height == gui.height {
|
||||||
|
@ -90,10 +118,10 @@ func (gui *GUI) getTermSize() (int, int) {
|
||||||
return gui.cols, gui.rows
|
return gui.cols, gui.rows
|
||||||
}
|
}
|
||||||
|
|
||||||
// checks if the terminals cells have been updated, and updates the text objects if needed
|
// checks if the terminals cells have been updated, and updates the text objects if needed - only call on OS thread
|
||||||
func (gui *GUI) updateTexts() {
|
func (gui *GUI) updateTexts() {
|
||||||
gui.textLock.Lock()
|
|
||||||
defer gui.textLock.Unlock()
|
// runtime.LockOSThread() ?
|
||||||
|
|
||||||
//gui.logger.Debugf("Updating texts...")
|
//gui.logger.Debugf("Updating texts...")
|
||||||
|
|
||||||
|
@ -102,69 +130,56 @@ func (gui *GUI) updateTexts() {
|
||||||
for row := 0; row < rows; row++ {
|
for row := 0; row < rows; row++ {
|
||||||
for col := 0; col < cols; col++ {
|
for col := 0; col < cols; col++ {
|
||||||
|
|
||||||
r, err := gui.terminal.GetRuneAtPos(terminal.Position{Row: row, Col: col})
|
c, err := gui.terminal.GetCellAtPos(terminal.Position{Line: row, Col: col})
|
||||||
if err != nil {
|
|
||||||
gui.logger.Errorf("Failed to read rune: %s", err)
|
if err != nil || c == nil {
|
||||||
|
gui.cells[row][col].Hide()
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
if r > 0 {
|
|
||||||
gui.texts[row][col].SetString(string(r))
|
if c.IsHidden() {
|
||||||
gui.texts[row][col].SetColor(mgl32.Vec3{1, 1, 1})
|
|
||||||
// @todo set colour
|
gui.cells[row][col].Hide()
|
||||||
gui.texts[row][col].Show()
|
|
||||||
} else {
|
// debug
|
||||||
//gui.texts[row][col].Hide()
|
//gui.texts[row][col].SetColor(c.GetColourVec())
|
||||||
gui.texts[row][col].SetString("?")
|
//gui.texts[row][col].SetString("?")
|
||||||
gui.texts[row][col].SetColor(mgl32.Vec3{0.1, 0.1, 0.15})
|
//gui.texts[row][col].Show()
|
||||||
|
// end debug
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
gui.cells[row][col].SetColour(c.GetColour())
|
||||||
|
gui.cells[row][col].SetRune(c.GetRune())
|
||||||
|
gui.cells[row][col].Show()
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// builds text objects
|
// builds text objects - only call on OS thread
|
||||||
func (gui *GUI) createTexts() {
|
func (gui *GUI) createTexts() {
|
||||||
gui.textLock.Lock()
|
|
||||||
scaleMin, scaleMax := float32(1.0), float32(1.1)
|
|
||||||
|
|
||||||
cols, rows := gui.getTermSize()
|
cols, rows := gui.getTermSize()
|
||||||
|
|
||||||
texts := [][]*v41.Text{}
|
cells := [][]Cell{}
|
||||||
for row := 0; row < rows; row++ {
|
for row := 0; row < rows; row++ {
|
||||||
|
|
||||||
if len(texts) <= row {
|
if len(cells) <= row {
|
||||||
texts = append(texts, []*v41.Text{})
|
cells = append(cells, []Cell{})
|
||||||
}
|
}
|
||||||
for col := 0; col < cols; col++ {
|
for col := 0; col < cols; col++ {
|
||||||
if len(texts[row]) <= col {
|
if len(cells[row]) <= col {
|
||||||
text := v41.NewText(gui.font, scaleMin, scaleMax)
|
|
||||||
text.Hide()
|
|
||||||
|
|
||||||
if row < len(gui.texts) {
|
|
||||||
if col < len(gui.texts[row]) {
|
|
||||||
text.SetString(gui.texts[row][col].String)
|
|
||||||
gui.texts[row][col].Release()
|
|
||||||
if gui.texts[row][col].String != "" {
|
|
||||||
text.Show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
x := ((float32(col) * gui.charWidth) - (float32(gui.width) / 2)) + (gui.charWidth / 2)
|
x := ((float32(col) * gui.charWidth) - (float32(gui.width) / 2)) + (gui.charWidth / 2)
|
||||||
y := -(((float32(row) * gui.charHeight) - (float32(gui.height) / 2)) + (gui.charHeight / 2))
|
y := -(((float32(row) * gui.charHeight) - (float32(gui.height) / 2)) + (gui.charHeight / 2))
|
||||||
|
|
||||||
if col == 0 && row == 0 {
|
cells[row] = append(cells[row], NewCell(gui.font, x, y, gui.charWidth, gui.charHeight))
|
||||||
gui.logger.Debugf("0,0 is at %f,%f", x, y)
|
|
||||||
} else if col == cols-1 && row == rows-1 {
|
|
||||||
gui.logger.Debugf("%d,%d is at %f,%f", col, row, x, y)
|
|
||||||
}
|
|
||||||
|
|
||||||
text.SetPosition(mgl32.Vec2{x, y})
|
|
||||||
texts[row] = append(texts[row], text)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
gui.texts = texts
|
gui.cells = cells
|
||||||
gui.textLock.Unlock()
|
|
||||||
|
|
||||||
gui.updateTexts()
|
gui.updateTexts()
|
||||||
}
|
}
|
||||||
|
@ -195,13 +210,19 @@ func (gui *GUI) Render() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
gui.window.SetFramebufferSizeCallback(gui.resize)
|
gui.window.SetFramebufferSizeCallback(gui.resize)
|
||||||
|
gui.window.SetKeyCallback(gui.key)
|
||||||
w, h := gui.window.GetSize()
|
w, h := gui.window.GetSize()
|
||||||
gui.resize(gui.window, w, h)
|
gui.resize(gui.window, w, h)
|
||||||
|
|
||||||
gl.Viewport(0, 0, int32(gui.width), int32(gui.height))
|
gl.Viewport(0, 0, int32(gui.width), int32(gui.height))
|
||||||
|
|
||||||
gui.logger.Debugf("Starting pty read handling...")
|
gui.logger.Debugf("Starting pty read handling...")
|
||||||
gui.terminal.OnUpdate(gui.updateTexts)
|
|
||||||
|
updateChan := make(chan bool, 1024)
|
||||||
|
|
||||||
|
gui.terminal.OnUpdate(func() {
|
||||||
|
updateChan <- true
|
||||||
|
})
|
||||||
go gui.terminal.Read()
|
go gui.terminal.Read()
|
||||||
|
|
||||||
scaleMin, scaleMax := float32(1.0), float32(1.1)
|
scaleMin, scaleMax := float32(1.0), float32(1.1)
|
||||||
|
@ -218,40 +239,57 @@ func (gui *GUI) Render() error {
|
||||||
// stop smoothing fonts
|
// stop smoothing fonts
|
||||||
gl.TexParameterf(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST)
|
gl.TexParameterf(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST)
|
||||||
|
|
||||||
|
updateRequired := false
|
||||||
|
|
||||||
gui.logger.Debugf("Starting render...")
|
gui.logger.Debugf("Starting render...")
|
||||||
|
|
||||||
|
gl.ClearColor(0.1, 0.1, 0.1, 1.0)
|
||||||
|
|
||||||
for !gui.window.ShouldClose() {
|
for !gui.window.ShouldClose() {
|
||||||
|
|
||||||
select {
|
updateRequired = false
|
||||||
case <-ticker.C:
|
|
||||||
text.SetString(fmt.Sprintf("%d fps | %d, %d | %s", frames, gui.terminal.GetPosition().Col, gui.terminal.GetPosition().Row, gui.texts[0][0].String))
|
CheckUpdate:
|
||||||
frames = 0
|
for {
|
||||||
default:
|
select {
|
||||||
|
case <-updateChan:
|
||||||
|
updateRequired = true
|
||||||
|
case <-ticker.C:
|
||||||
|
text.SetString(fmt.Sprintf("%d fps | %dx%d", frames, gui.cols, gui.rows))
|
||||||
|
frames = 0
|
||||||
|
updateRequired = true
|
||||||
|
default:
|
||||||
|
break CheckUpdate
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
gl.UseProgram(program)
|
gl.UseProgram(program)
|
||||||
gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)
|
|
||||||
|
|
||||||
// Render the string.
|
if updateRequired {
|
||||||
gui.window.SetTitle(gui.terminal.GetTitle())
|
|
||||||
|
|
||||||
gui.textLock.Lock()
|
gui.updateTexts()
|
||||||
cols, rows := gui.getTermSize()
|
|
||||||
|
|
||||||
for row := 0; row < rows; row++ {
|
// Render the string.
|
||||||
for col := 0; col < cols; col++ {
|
gui.window.SetTitle(gui.terminal.GetTitle())
|
||||||
gui.texts[row][col].SetColor(mgl32.Vec3{1, 1, 1})
|
|
||||||
//gui.texts[row][col].SetString("?")
|
gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)
|
||||||
gui.texts[row][col].Draw()
|
|
||||||
|
cols, rows := gui.getTermSize()
|
||||||
|
|
||||||
|
for row := 0; row < rows; row++ {
|
||||||
|
for col := 0; col < cols; col++ {
|
||||||
|
gui.cells[row][col].Draw()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
gui.textLock.Unlock()
|
|
||||||
|
|
||||||
text.Draw()
|
text.Draw()
|
||||||
|
}
|
||||||
|
|
||||||
|
frames++
|
||||||
|
|
||||||
glfw.PollEvents()
|
glfw.PollEvents()
|
||||||
gui.window.SwapBuffers()
|
gui.window.SwapBuffers()
|
||||||
|
|
||||||
frames++
|
|
||||||
}
|
}
|
||||||
|
|
||||||
gui.logger.Debugf("Stopping render...")
|
gui.logger.Debugf("Stopping render...")
|
||||||
|
@ -320,5 +358,6 @@ func (gui *GUI) createProgram() (uint32, error) {
|
||||||
|
|
||||||
prog := gl.CreateProgram()
|
prog := gl.CreateProgram()
|
||||||
gl.LinkProgram(prog)
|
gl.LinkProgram(prog)
|
||||||
|
|
||||||
return prog, nil
|
return prog, nil
|
||||||
}
|
}
|
||||||
|
|
2
main.go
2
main.go
|
@ -41,7 +41,7 @@ func main() {
|
||||||
terminal := terminal.New(pty, sugaredLogger)
|
terminal := terminal.New(pty, sugaredLogger)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
time.Sleep(time.Second * 5)
|
time.Sleep(time.Second * 1)
|
||||||
terminal.Write([]byte("tput cols && tput lines\n"))
|
terminal.Write([]byte("tput cols && tput lines\n"))
|
||||||
terminal.Write([]byte("ls -la\n"))
|
terminal.Write([]byte("ls -la\n"))
|
||||||
}()
|
}()
|
||||||
|
|
|
@ -1,5 +1,40 @@
|
||||||
package terminal
|
package terminal
|
||||||
|
|
||||||
|
import "github.com/go-gl/mathgl/mgl32"
|
||||||
|
|
||||||
type Cell struct {
|
type Cell struct {
|
||||||
r rune
|
r rune
|
||||||
|
wrapper bool
|
||||||
|
isWrapped bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cell *Cell) GetRune() rune {
|
||||||
|
return cell.r
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cell *Cell) IsHidden() bool {
|
||||||
|
return cell.r == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cell *Cell) GetColour() (r float32, g float32, b float32) {
|
||||||
|
|
||||||
|
if cell.wrapper {
|
||||||
|
return 0, 1, 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if cell.isWrapped {
|
||||||
|
return 1, 1, 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if cell.IsHidden() {
|
||||||
|
return 0, 0, 1
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1, 1, 1
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cell *Cell) GetColourVec() mgl32.Vec3 {
|
||||||
|
r, g, b := cell.GetColour()
|
||||||
|
return mgl32.Vec3{r, g, b}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ package terminal
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"strconv"
|
||||||
"sync"
|
"sync"
|
||||||
"syscall"
|
"syscall"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
@ -11,13 +12,64 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type Terminal struct {
|
type Terminal struct {
|
||||||
cells [][]Cell // y, x
|
lines []Line // lines, where 0 is earliest, n is latest
|
||||||
|
position Position // line and col
|
||||||
lock sync.Mutex
|
lock sync.Mutex
|
||||||
pty *os.File
|
pty *os.File
|
||||||
logger *zap.SugaredLogger
|
logger *zap.SugaredLogger
|
||||||
title string
|
title string
|
||||||
position Position
|
|
||||||
onUpdate []func()
|
onUpdate []func()
|
||||||
|
size Winsize
|
||||||
|
}
|
||||||
|
|
||||||
|
type Line struct {
|
||||||
|
Cells []Cell
|
||||||
|
wrapped bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewLine() Line {
|
||||||
|
return Line{
|
||||||
|
Cells: []Cell{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (line *Line) String() string {
|
||||||
|
s := ""
|
||||||
|
for _, c := range line.Cells {
|
||||||
|
s += string(c.r)
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (line *Line) CutCellsAfter(n int) []Cell {
|
||||||
|
cut := line.Cells[n:]
|
||||||
|
line.Cells = line.Cells[:n]
|
||||||
|
return cut
|
||||||
|
}
|
||||||
|
|
||||||
|
func (line *Line) CutCellsFromBeginning(n int) []Cell {
|
||||||
|
if n > len(line.Cells) {
|
||||||
|
n = len(line.Cells)
|
||||||
|
}
|
||||||
|
cut := line.Cells[:n]
|
||||||
|
line.Cells = line.Cells[n:]
|
||||||
|
return cut
|
||||||
|
}
|
||||||
|
|
||||||
|
func (line *Line) CutCellsFromEnd(n int) []Cell {
|
||||||
|
cut := line.Cells[len(line.Cells)-n:]
|
||||||
|
line.Cells = line.Cells[:len(line.Cells)-n]
|
||||||
|
return cut
|
||||||
|
}
|
||||||
|
|
||||||
|
func (line *Line) GetRenderedLength() int {
|
||||||
|
l := 0
|
||||||
|
for x, c := range line.Cells {
|
||||||
|
if c.r > 0 {
|
||||||
|
l = x
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return l
|
||||||
}
|
}
|
||||||
|
|
||||||
type Winsize struct {
|
type Winsize struct {
|
||||||
|
@ -28,13 +80,13 @@ type Winsize struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type Position struct {
|
type Position struct {
|
||||||
Col int
|
Line int
|
||||||
Row int
|
Col int
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(pty *os.File, logger *zap.SugaredLogger) *Terminal {
|
func New(pty *os.File, logger *zap.SugaredLogger) *Terminal {
|
||||||
return &Terminal{
|
return &Terminal{
|
||||||
cells: [][]Cell{},
|
lines: []Line{},
|
||||||
pty: pty,
|
pty: pty,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
onUpdate: []func(){},
|
onUpdate: []func(){},
|
||||||
|
@ -51,6 +103,25 @@ func (terminal *Terminal) triggerOnUpdate() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (terminal *Terminal) getPosition() Position {
|
||||||
|
return terminal.position
|
||||||
|
}
|
||||||
|
|
||||||
|
func (terminal *Terminal) incrementPosition() {
|
||||||
|
position := terminal.getPosition()
|
||||||
|
if position.Col+1 >= int(terminal.size.Width) {
|
||||||
|
position.Line++
|
||||||
|
position.Col = 0
|
||||||
|
} else {
|
||||||
|
position.Col++
|
||||||
|
}
|
||||||
|
terminal.SetPosition(position)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (terminal *Terminal) SetPosition(position Position) {
|
||||||
|
terminal.position = position
|
||||||
|
}
|
||||||
|
|
||||||
func (terminal *Terminal) GetTitle() string {
|
func (terminal *Terminal) GetTitle() string {
|
||||||
return terminal.title
|
return terminal.title
|
||||||
}
|
}
|
||||||
|
@ -61,13 +132,29 @@ func (terminal *Terminal) Write(data []byte) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (terminal *Terminal) ClearToEndOfLine() error {
|
func (terminal *Terminal) ClearToEndOfLine() {
|
||||||
w, _ := terminal.GetSize()
|
|
||||||
for i := terminal.position.Col; i < w; i++ {
|
position := terminal.getPosition()
|
||||||
// @todo handle errors?
|
|
||||||
terminal.setRuneAtPos(Position{Row: terminal.position.Row, Col: i}, 0)
|
if position.Line < len(terminal.lines) {
|
||||||
|
if position.Col < len(terminal.lines[position.Line].Cells) {
|
||||||
|
terminal.lines[position.Line].Cells = terminal.lines[position.Line].Cells[:position.Col]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return nil
|
}
|
||||||
|
|
||||||
|
// we have thousands of lines of output. if the terminal is X lines high, we just want to lookat the most recent X lines to render (unless scroll etc)
|
||||||
|
func (terminal *Terminal) getBufferedLine(line int) *Line {
|
||||||
|
|
||||||
|
if len(terminal.lines) >= int(terminal.size.Height) {
|
||||||
|
line = len(terminal.lines) - int(terminal.size.Height) + line
|
||||||
|
}
|
||||||
|
|
||||||
|
if line >= len(terminal.lines) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return &terminal.lines[line]
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read needs to be run on a goroutine, as it continually reads output to set on the terminal
|
// Read needs to be run on a goroutine, as it continually reads output to set on the terminal
|
||||||
|
@ -104,6 +191,30 @@ func (terminal *Terminal) Read() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
switch final {
|
switch final {
|
||||||
|
case byte('A'):
|
||||||
|
distance := 1
|
||||||
|
if len(params) > 0 {
|
||||||
|
var err error
|
||||||
|
distance, err = strconv.Atoi(string([]byte{params[0]}))
|
||||||
|
if err != nil {
|
||||||
|
distance = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if terminal.position.Line-distance >= 0 {
|
||||||
|
terminal.position.Line -= distance
|
||||||
|
}
|
||||||
|
case byte('B'):
|
||||||
|
distance := 1
|
||||||
|
if len(params) > 0 {
|
||||||
|
var err error
|
||||||
|
distance, err = strconv.Atoi(string([]byte{params[0]}))
|
||||||
|
if err != nil {
|
||||||
|
distance = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
terminal.position.Line += distance
|
||||||
|
|
||||||
case 0x4b: // K - EOL - Erase to end of line
|
case 0x4b: // K - EOL - Erase to end of line
|
||||||
if len(params) == 0 || params[0] == byte('0') {
|
if len(params) == 0 || params[0] == byte('0') {
|
||||||
terminal.ClearToEndOfLine()
|
terminal.ClearToEndOfLine()
|
||||||
|
@ -151,9 +262,7 @@ func (terminal *Terminal) Read() error {
|
||||||
default:
|
default:
|
||||||
// render character at current location
|
// render character at current location
|
||||||
// fmt.Printf("%s\n", string([]byte{b}))
|
// fmt.Printf("%s\n", string([]byte{b}))
|
||||||
if err := terminal.writeRune([]rune(string([]byte{b}))[0]); err != nil {
|
terminal.writeRune([]rune(string([]byte{b}))[0])
|
||||||
terminal.logger.Errorf("Failed to write rune %s", string([]byte{b}))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -178,102 +287,166 @@ func (terminal *Terminal) Read() error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (terminal *Terminal) writeRune(r rune) error {
|
func (terminal *Terminal) writeRune(r rune) {
|
||||||
|
terminal.setRuneAtPos(terminal.position, r)
|
||||||
|
terminal.incrementPosition()
|
||||||
|
|
||||||
err := terminal.setRuneAtPos(terminal.position, r)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
w, h := terminal.GetSize()
|
|
||||||
if terminal.position.Col < w-1 {
|
|
||||||
terminal.position.Col++
|
|
||||||
} else {
|
|
||||||
terminal.position.Col = 0
|
|
||||||
if terminal.position.Row <= h-1 {
|
|
||||||
terminal.position.Row++
|
|
||||||
} else {
|
|
||||||
panic(fmt.Errorf("Not implemented - need to shuffle all rows up one"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (terminal *Terminal) newLine() {
|
func (terminal *Terminal) newLine() {
|
||||||
_, h := terminal.GetSize()
|
if terminal.position.Line >= len(terminal.lines) {
|
||||||
terminal.position.Col = 0
|
terminal.lines = append(terminal.lines, NewLine())
|
||||||
if terminal.position.Row <= h-1 {
|
|
||||||
terminal.position.Row++
|
|
||||||
} else {
|
|
||||||
panic(fmt.Errorf("Not implemented - need to shuffle all rows up one"))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
terminal.position.Col = 0
|
||||||
|
terminal.position.Line++
|
||||||
}
|
}
|
||||||
|
|
||||||
func (terminal *Terminal) Clear() {
|
func (terminal *Terminal) Clear() {
|
||||||
for y := range terminal.cells {
|
// @todo actually should just add a bunch of newlines?
|
||||||
for x := range terminal.cells[y] {
|
for i := 0; i < int(terminal.size.Height); i++ {
|
||||||
terminal.cells[y][x].r = 0
|
terminal.newLine()
|
||||||
}
|
|
||||||
}
|
}
|
||||||
terminal.position = Position{0, 0}
|
terminal.SetPosition(Position{Line: 0, Col: 0})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (terminal *Terminal) GetPosition() Position {
|
func (terminal *Terminal) GetCellAtPos(pos Position) (*Cell, error) {
|
||||||
return terminal.position
|
|
||||||
}
|
|
||||||
|
|
||||||
func (terminal *Terminal) GetRuneAtPos(pos Position) (rune, error) {
|
if int(terminal.size.Height) <= pos.Line {
|
||||||
if len(terminal.cells) <= pos.Row {
|
terminal.logger.Errorf("Line %d does not exist", pos.Line)
|
||||||
return 0, fmt.Errorf("Row %d does not exist", pos.Row)
|
return nil, fmt.Errorf("Line %d does not exist", pos.Line)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(terminal.cells) < 1 || len(terminal.cells[0]) <= pos.Col {
|
if int(terminal.size.Width) <= pos.Col {
|
||||||
return 0, fmt.Errorf("Col %d does not exist", pos.Col)
|
terminal.logger.Errorf("Col %d does not exist", pos.Col)
|
||||||
|
return nil, fmt.Errorf("Col %d does not exist", pos.Col)
|
||||||
}
|
}
|
||||||
|
|
||||||
return terminal.cells[pos.Row][pos.Col].r, nil
|
line := terminal.getBufferedLine(pos.Line)
|
||||||
|
if line == nil {
|
||||||
|
return nil, fmt.Errorf("Line missing")
|
||||||
|
}
|
||||||
|
for pos.Col >= len(line.Cells) {
|
||||||
|
line.Cells = append(line.Cells, Cell{})
|
||||||
|
}
|
||||||
|
return &line.Cells[pos.Col], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (terminal *Terminal) setRuneAtPos(pos Position, r rune) error {
|
func (terminal *Terminal) setRuneAtPos(pos Position, r rune) error {
|
||||||
|
|
||||||
if len(terminal.cells) <= pos.Row {
|
if int(terminal.size.Height) <= pos.Line {
|
||||||
return fmt.Errorf("Row %d does not exist", pos.Row)
|
terminal.logger.Errorf("Line %d does not exist", pos.Line)
|
||||||
|
return fmt.Errorf("Line %d does not exist", pos.Line)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(terminal.cells) < 1 || len(terminal.cells[0]) <= pos.Col {
|
if int(terminal.size.Width) <= pos.Col {
|
||||||
|
terminal.logger.Errorf("Col %d does not exist", pos.Col)
|
||||||
return fmt.Errorf("Col %d does not exist", pos.Col)
|
return fmt.Errorf("Col %d does not exist", pos.Col)
|
||||||
}
|
}
|
||||||
|
|
||||||
if pos.Row == 0 && pos.Col == 0 {
|
if pos.Line == 0 && pos.Col == 0 {
|
||||||
fmt.Printf("\n\nSetting %d %d to %q\n\n\n", pos.Row, pos.Col, string(r))
|
fmt.Printf("\n\nSetting %d %d to %q\n\n\n", pos.Line, pos.Col, string(r))
|
||||||
}
|
}
|
||||||
|
|
||||||
terminal.cells[pos.Row][pos.Col].r = r
|
line := terminal.getBufferedLine(pos.Line)
|
||||||
|
if line == nil {
|
||||||
|
for pos.Line >= len(terminal.lines) {
|
||||||
|
terminal.lines = append(terminal.lines, NewLine())
|
||||||
|
}
|
||||||
|
line = terminal.getBufferedLine(pos.Line)
|
||||||
|
if line == nil {
|
||||||
|
panic(fmt.Errorf("Impossible?"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for pos.Col >= len(line.Cells) {
|
||||||
|
line.Cells = append(line.Cells, Cell{})
|
||||||
|
}
|
||||||
|
|
||||||
|
line.Cells[pos.Col].r = r
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (terminal *Terminal) GetSize() (int, int) {
|
func (terminal *Terminal) GetSize() (int, int) {
|
||||||
terminal.lock.Lock()
|
return int(terminal.size.Width), int(terminal.size.Height)
|
||||||
defer terminal.lock.Unlock()
|
|
||||||
if len(terminal.cells) == 0 {
|
|
||||||
return 0, 0
|
|
||||||
}
|
|
||||||
return len(terminal.cells[0]), len(terminal.cells)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (terminal *Terminal) SetSize(cols int, rows int) error {
|
func (terminal *Terminal) SetSize(newCols int, newLines int) error {
|
||||||
terminal.lock.Lock()
|
terminal.lock.Lock()
|
||||||
defer terminal.lock.Unlock()
|
defer terminal.lock.Unlock()
|
||||||
cells := make([][]Cell, rows)
|
|
||||||
for i := range cells {
|
oldCols := int(terminal.size.Width)
|
||||||
cells[i] = make([]Cell, cols)
|
oldLines := int(terminal.size.Height)
|
||||||
|
|
||||||
|
if oldLines > 0 && oldCols > 0 { // only bother resizing content if there is some
|
||||||
|
if newCols < oldCols { // if the width decreased, we need to do some line trimming
|
||||||
|
|
||||||
|
for l := range terminal.lines {
|
||||||
|
if terminal.lines[l].GetRenderedLength() > newCols {
|
||||||
|
cells := terminal.lines[l].CutCellsAfter(newCols)
|
||||||
|
line := Line{
|
||||||
|
Cells: cells,
|
||||||
|
wrapped: true,
|
||||||
|
}
|
||||||
|
terminal.lines = append(terminal.lines[:l+1], append([]Line{line}, terminal.lines[l+1:]...)...)
|
||||||
|
if terminal.getPosition().Line > l {
|
||||||
|
terminal.position.Line++
|
||||||
|
} else if terminal.getPosition().Line == l {
|
||||||
|
if terminal.getPosition().Col >= newCols {
|
||||||
|
terminal.position.Line++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if newCols > oldCols { // if width increased, we need to potentially unwrap some lines
|
||||||
|
for l := 0; l < len(terminal.lines); l++ {
|
||||||
|
if terminal.lines[l].GetRenderedLength() < newCols { // there is space here to unwrap a line if needed
|
||||||
|
if l+1 < len(terminal.lines) {
|
||||||
|
if terminal.lines[l+1].wrapped {
|
||||||
|
wrapSize := newCols - terminal.lines[l].GetRenderedLength()
|
||||||
|
cells := terminal.lines[l+1].CutCellsFromBeginning(wrapSize)
|
||||||
|
terminal.lines[l].Cells = append(terminal.lines[l].Cells, cells...)
|
||||||
|
if terminal.lines[l+1].GetRenderedLength() == 0 {
|
||||||
|
// remove line
|
||||||
|
terminal.lines = append(terminal.lines[:l+1], terminal.lines[l+2:]...)
|
||||||
|
if terminal.getPosition().Line >= l+1 {
|
||||||
|
terminal.position.Line--
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
terminal.cells = cells
|
|
||||||
|
terminal.size.Width = uint16(newCols)
|
||||||
|
terminal.size.Height = uint16(newLines)
|
||||||
|
|
||||||
_, _, err := syscall.Syscall(syscall.SYS_IOCTL, uintptr(terminal.pty.Fd()),
|
_, _, err := syscall.Syscall(syscall.SYS_IOCTL, uintptr(terminal.pty.Fd()),
|
||||||
uintptr(syscall.TIOCSWINSZ), uintptr(unsafe.Pointer(&Winsize{Width: uint16(cols), Height: uint16(rows)})))
|
uintptr(syscall.TIOCSWINSZ), uintptr(unsafe.Pointer(&terminal.size)))
|
||||||
if err != 0 {
|
if err != 0 {
|
||||||
return fmt.Errorf("Failed to set terminal size vai ioctl: Error no %d", err)
|
return fmt.Errorf("Failed to set terminal size vai ioctl: Error no %d", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
------------------ ->
|
||||||
|
ssssssssssssssssss
|
||||||
|
ssssPPPPPPPPPPPPPP
|
||||||
|
xxxxxxxxx
|
||||||
|
xxxxxxxxxxxxxxxxxx
|
||||||
|
--------------------------
|
||||||
|
ssssssssssssssssss
|
||||||
|
SsssPPPPPPPPPPPPPP
|
||||||
|
xxxxxxxxx
|
||||||
|
xxxxxxxxxxxxxxxxxx
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
Loading…
Reference in New Issue