diff --git a/gui/cell.go b/gui/cell.go index c8f45ef..2f7cfb5 100644 --- a/gui/cell.go +++ b/gui/cell.go @@ -2,29 +2,111 @@ package gui import ( v41 "github.com/4ydx/gltext/v4.1" + "github.com/go-gl/gl/v4.1-core/gl" "github.com/go-gl/mathgl/mgl32" ) type Cell struct { - text *v41.Text + text *v41.Text + vao uint32 + vbo uint32 + cv uint32 + colourAttr uint32 + points []float32 + colour [3]float32 } -func NewCell(font *v41.Font, x float32, y float32, w float32, h float32) Cell { +func (gui *GUI) NewCell(font *v41.Font, x float32, y float32, w float32, h float32, colourAttr uint32) Cell { cell := Cell{ - text: v41.NewText(font, 1.0, 1.1), + text: v41.NewText(font, 1.0, 1.1), + colourAttr: colourAttr, } cell.text.SetPosition(mgl32.Vec2{x, y}) + x = (x - (w / 2)) / (float32(gui.width) / 2) + y = (y - (h / 2)) / (float32(gui.height) / 2) + w = (w / float32(gui.width/2)) + h = (h / float32(gui.height/2)) + cell.points = []float32{ + x, y + h, 0, + x, y, 0, + x + w, y, 0, + x, y + h, 0, + x + w, y + h, 0, + x + w, y, 0, + } + + cell.makeVao() + return cell } -func (cell *Cell) Draw() { +func (cell *Cell) SetFgColour(r, g, b float32) { + if cell.text != nil { + cell.text.SetColor(mgl32.Vec3{r, g, b}) + } +} + +func (cell *Cell) SetBgColour(r float32, g float32, b float32) { + + if cell.colour[0] == r && cell.colour[1] == g && cell.colour[2] == b { + return + } + + cell.colour = [3]float32{r, g, b} + cell.Clean() + cell.makeVao() +} + +func (cell *Cell) Clean() { + gl.DeleteVertexArrays(1, &cell.vao) + gl.DeleteBuffers(1, &cell.vbo) +} + +func (cell *Cell) makeVao() { + + gl.GenBuffers(1, &cell.vbo) + gl.BindBuffer(gl.ARRAY_BUFFER, cell.vbo) + gl.BufferData(gl.ARRAY_BUFFER, 4*len(cell.points), gl.Ptr(cell.points), gl.STATIC_DRAW) + + gl.GenVertexArrays(1, &cell.vao) + gl.BindVertexArray(cell.vao) + gl.EnableVertexAttribArray(0) + gl.BindBuffer(gl.ARRAY_BUFFER, cell.vbo) + gl.VertexAttribPointer(0, 3, gl.FLOAT, false, 0, nil) + + // COLOUR + gl.GenBuffers(1, &cell.cv) + gl.BindBuffer(gl.ARRAY_BUFFER, cell.cv) + triColor := []float32{ + cell.colour[0], cell.colour[1], cell.colour[2], + cell.colour[0], cell.colour[1], cell.colour[2], + cell.colour[0], cell.colour[1], cell.colour[2], + cell.colour[0], cell.colour[1], cell.colour[2], + cell.colour[0], cell.colour[1], cell.colour[2], + cell.colour[0], cell.colour[1], cell.colour[2], + } + gl.BufferData(gl.ARRAY_BUFFER, len(triColor)*4, gl.Ptr(triColor), gl.STATIC_DRAW) + gl.EnableVertexAttribArray(cell.colourAttr) + gl.VertexAttribPointer(cell.colourAttr, 3, gl.FLOAT, false, 0, gl.PtrOffset(0)) + + // END COLOUR + +} + +func (cell *Cell) DrawBg() { + gl.BindVertexArray(cell.vao) + gl.DrawArrays(gl.TRIANGLES, 0, 6) +} + +func (cell *Cell) DrawText() { if cell.text != nil { cell.text.Draw() } + } func (cell *Cell) Show() { @@ -45,12 +127,6 @@ func (cell *Cell) SetRune(r rune) { } } -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() diff --git a/gui/gui.go b/gui/gui.go index 8ddaf27..7037107 100644 --- a/gui/gui.go +++ b/gui/gui.go @@ -31,7 +31,7 @@ type GUI struct { cells [][]Cell cols int rows int - capslock bool + colourAttr uint32 } func New(config config.Config, terminal *terminal.Terminal, logger *zap.SugaredLogger) *GUI { @@ -63,7 +63,6 @@ func (gui *GUI) resize(w *glfw.Window, width int, height int) { if gui.font != nil { gui.font.ResizeWindow(float32(width), float32(height)) } - gl.Viewport(0, 0, int32(width), int32(height)) scaleMin, scaleMax := float32(1.0), float32(1.1) text := v41.NewText(gui.font, scaleMin, scaleMax) @@ -105,18 +104,12 @@ func (gui *GUI) updateTexts() { } if c.IsHidden() { - gui.cells[row][col].Hide() - - // debug - //gui.texts[row][col].SetColor(c.GetColourVec()) - //gui.texts[row][col].SetString("?") - //gui.texts[row][col].Show() - // end debug continue } - gui.cells[row][col].SetColour(c.GetColour()) + gui.cells[row][col].SetFgColour(c.GetFgColour()) + gui.cells[row][col].SetBgColour(c.GetBgColour()) gui.cells[row][col].SetRune(c.GetRune()) gui.cells[row][col].Show() @@ -141,7 +134,7 @@ func (gui *GUI) createTexts() { x := ((float32(col) * gui.charWidth) - (float32(gui.width) / 2)) + (gui.charWidth / 2) y := -(((float32(row) * gui.charHeight) - (float32(gui.height) / 2)) + (gui.charHeight / 2)) - cells[row] = append(cells[row], NewCell(gui.font, x, y, gui.charWidth, gui.charHeight)) + cells[row] = append(cells[row], gui.NewCell(gui.font, x, y, gui.charWidth, gui.charHeight, gui.colourAttr)) } } } @@ -151,6 +144,10 @@ func (gui *GUI) createTexts() { gui.updateTexts() } +func (gui *GUI) Close() { + gui.window.SetShouldClose(true) +} + func (gui *GUI) Render() error { gui.logger.Debugf("Locking OS thread...") @@ -170,6 +167,9 @@ func (gui *GUI) Render() error { return fmt.Errorf("Failed to initialise OpenGL: %s", err) } + gui.colourAttr = uint32(gl.GetAttribLocation(program, gl.Str("inColour\x00"))) + gl.BindFragDataLocation(program, 0, gl.Str("outColour\x00")) + gui.logger.Debugf("Loading font...") //gui.font, err = gui.loadFont("/usr/share/fonts/nerd-fonts-complete/ttf/Roboto Mono Nerd Font Complete.ttf", 12) if err := gui.loadFont("./fonts/CamingoCode-Regular.ttf", 12); err != nil { @@ -182,8 +182,6 @@ func (gui *GUI) Render() error { w, h := gui.window.GetSize() gui.resize(gui.window, w, h) - gl.Viewport(0, 0, int32(gui.width), int32(gui.height)) - gui.logger.Debugf("Starting pty read handling...") updateChan := make(chan bool, 1024) @@ -191,7 +189,13 @@ func (gui *GUI) Render() error { gui.terminal.OnUpdate(func() { updateChan <- true }) - go gui.terminal.Read() + go func() { + err := gui.terminal.Read() + if err != nil { + gui.logger.Errorf("Read from pty failed: %s", err) + } + gui.Close() + }() text := v41.NewText(gui.font, 1.0, 1.1) text.SetString("") @@ -209,6 +213,8 @@ func (gui *GUI) Render() error { gui.logger.Debugf("Starting render...") + // todo set bg colour + //bgColour := gui.terminal.colourScheme.DefaultBgColor gl.ClearColor(0.1, 0.1, 0.1, 1.0) for !gui.window.ShouldClose() { @@ -232,6 +238,8 @@ func (gui *GUI) Render() error { if updateRequired { + gl.Viewport(0, 0, int32(gui.width), int32(gui.height)) + gui.updateTexts() // Render the string. @@ -243,7 +251,14 @@ func (gui *GUI) Render() error { for row := 0; row < rows; row++ { for col := 0; col < cols; col++ { - gui.cells[row][col].Draw() + gui.cells[row][col].DrawBg() + } + } + + for row := 0; row < rows; row++ { + for col := 0; col < cols; col++ { + + gui.cells[row][col].DrawText() } } @@ -320,7 +335,21 @@ func (gui *GUI) createProgram() (uint32, error) { } gui.logger.Infof("OpenGL version %s", gl.GoStr(gl.GetString(gl.VERSION))) + gui.logger.Debugf("Compiling shaders...") + + vertexShader, err := compileShader(vertexShaderSource, gl.VERTEX_SHADER) + if err != nil { + return 0, err + } + + fragmentShader, err := compileShader(fragmentShaderSource, gl.FRAGMENT_SHADER) + if err != nil { + return 0, err + } + prog := gl.CreateProgram() + gl.AttachShader(prog, vertexShader) + gl.AttachShader(prog, fragmentShader) gl.LinkProgram(prog) return prog, nil diff --git a/gui/shapes.go b/gui/shapes.go new file mode 100644 index 0000000..ec8d58f --- /dev/null +++ b/gui/shapes.go @@ -0,0 +1,53 @@ +package gui + +import ( + "fmt" + "strings" + + "github.com/go-gl/gl/v4.1-core/gl" // OR: github.com/go-gl/gl/v2.1/gl +) + +const ( + vertexShaderSource = ` + #version 410 + in vec3 vp; + attribute vec3 inColour; + smooth out vec3 theColour; + void main() { + gl_Position = vec4(vp, 1.0); + theColour = inColour; + } + ` + "\x00" + + fragmentShaderSource = ` + #version 410 + smooth in vec3 theColour; + out vec4 outColour; + void main() { + outColour = vec4(theColour, 1.0); + } + ` + "\x00" +) + +func compileShader(source string, shaderType uint32) (uint32, error) { + shader := gl.CreateShader(shaderType) + + csources, free := gl.Strs(source) + gl.ShaderSource(shader, 1, csources, nil) + free() + gl.CompileShader(shader) + + var status int32 + gl.GetShaderiv(shader, gl.COMPILE_STATUS, &status) + if status == gl.FALSE { + var logLength int32 + gl.GetShaderiv(shader, gl.INFO_LOG_LENGTH, &logLength) + + log := strings.Repeat("\x00", int(logLength+1)) + gl.GetShaderInfoLog(shader, logLength, nil, gl.Str(log)) + + return 0, fmt.Errorf("failed to compile %v: %v", source, log) + } + + return shader, nil +} diff --git a/main.go b/main.go index 990040e..668188e 100644 --- a/main.go +++ b/main.go @@ -37,7 +37,7 @@ func main() { } sugaredLogger.Infof("Creating terminal...") - terminal := terminal.New(pty, sugaredLogger) + terminal := terminal.New(pty, sugaredLogger, terminal.DefaultColourScheme) /* go func() { time.Sleep(time.Second * 1) diff --git a/terminal/cell.go b/terminal/cell.go index 1729b4f..8d5c4bc 100644 --- a/terminal/cell.go +++ b/terminal/cell.go @@ -1,11 +1,19 @@ package terminal -import "github.com/go-gl/mathgl/mgl32" - type Cell struct { - r rune - wrapper bool - isWrapped bool + r rune + attr CellAttributes +} + +type CellAttributes struct { + FgColour [3]float32 + BgColour [3]float32 + Bold bool + Dim bool + Underline bool + Blink bool + Reverse bool + Hidden bool } func (cell *Cell) GetRune() rune { @@ -13,28 +21,19 @@ func (cell *Cell) GetRune() rune { } func (cell *Cell) IsHidden() bool { - return cell.r == 0 + return cell.attr.Hidden } -func (cell *Cell) GetColour() (r float32, g float32, b float32) { - - if cell.wrapper { - return 0, 1, 0 +func (cell *Cell) GetFgColour() (r float32, g float32, b float32) { + if cell.attr.Reverse { + return cell.attr.BgColour[0], cell.attr.BgColour[1], cell.attr.BgColour[2] } - - if cell.isWrapped { - return 1, 1, 0 - } - - if cell.IsHidden() { - return 0, 0, 1 - } - - return 1, 1, 1 - + return cell.attr.FgColour[0], cell.attr.FgColour[1], cell.attr.FgColour[2] } -func (cell *Cell) GetColourVec() mgl32.Vec3 { - r, g, b := cell.GetColour() - return mgl32.Vec3{r, g, b} +func (cell *Cell) GetBgColour() (r float32, g float32, b float32) { + if cell.attr.Reverse { + return cell.attr.FgColour[0], cell.attr.FgColour[1], cell.attr.FgColour[2] + } + return cell.attr.BgColour[0], cell.attr.BgColour[1], cell.attr.BgColour[2] } diff --git a/terminal/colours.go b/terminal/colours.go new file mode 100644 index 0000000..679c5e6 --- /dev/null +++ b/terminal/colours.go @@ -0,0 +1,77 @@ +package terminal + +type ColourScheme struct { + DefaultFg [3]float32 + BlackFg [3]float32 + RedFg [3]float32 + GreenFg [3]float32 + YellowFg [3]float32 + BlueFg [3]float32 + MagentaFg [3]float32 + CyanFg [3]float32 + LightGreyFg [3]float32 + DarkGreyFg [3]float32 + LightRedFg [3]float32 + LightGreenFg [3]float32 + LightYellowFg [3]float32 + LightBlueFg [3]float32 + LightMagentaFg [3]float32 + LightCyanFg [3]float32 + WhiteFg [3]float32 + DefaultBg [3]float32 + BlackBg [3]float32 + RedBg [3]float32 + GreenBg [3]float32 + YellowBg [3]float32 + BlueBg [3]float32 + MagentaBg [3]float32 + CyanBg [3]float32 + LightGreyBg [3]float32 + DarkGreyBg [3]float32 + LightRedBg [3]float32 + LightGreenBg [3]float32 + LightYellowBg [3]float32 + LightBlueBg [3]float32 + LightMagentaBg [3]float32 + LightCyanBg [3]float32 + WhiteBg [3]float32 +} + +var DefaultColourScheme = ColourScheme{ + //fg + DefaultFg: [3]float32{1, 1, 1}, + BlackFg: [3]float32{0, 0, 0}, + RedFg: [3]float32{1, 0, 0}, + GreenFg: [3]float32{0, 1, 0}, + YellowFg: [3]float32{1, 1, 0}, + BlueFg: [3]float32{0, 0, 1}, + MagentaFg: [3]float32{1, 0, 1}, + CyanFg: [3]float32{0, 1, 1}, + LightGreyFg: [3]float32{0.7, 0.7, 0.7}, + DarkGreyFg: [3]float32{0.3, 0.3, 0.3}, + LightRedFg: [3]float32{1, 0.5, 0.5}, + LightGreenFg: [3]float32{0.5, 1, 0.5}, + LightYellowFg: [3]float32{1, 1, 0.5}, + LightBlueFg: [3]float32{0.5, 0.5, 1}, + LightMagentaFg: [3]float32{1, 0.5, 1}, + LightCyanFg: [3]float32{0.5, 1, 1}, + WhiteFg: [3]float32{1, 1, 1}, + // bg + DefaultBg: [3]float32{0.1, 0.1, 0.1}, + BlackBg: [3]float32{0, 0, 0}, + RedBg: [3]float32{1, 0, 0}, + GreenBg: [3]float32{0, 1, 0}, + YellowBg: [3]float32{1, 1, 0}, + BlueBg: [3]float32{0, 0, 1}, + MagentaBg: [3]float32{1, 0, 1}, + CyanBg: [3]float32{0, 1, 1}, + LightGreyBg: [3]float32{0.7, 0.7, 0.7}, + DarkGreyBg: [3]float32{0.3, 0.3, 0.3}, + LightRedBg: [3]float32{1, 0.5, 0.5}, + LightGreenBg: [3]float32{0.5, 1, 0.5}, + LightYellowBg: [3]float32{1, 1, 0.5}, + LightBlueBg: [3]float32{0.5, 0.5, 1}, + LightMagentaBg: [3]float32{1, 0.5, 1}, + LightCyanBg: [3]float32{0.5, 1, 1}, + WhiteBg: [3]float32{1, 1, 1}, +} diff --git a/terminal/terminal.go b/terminal/terminal.go index 8a60ab3..fd77c62 100644 --- a/terminal/terminal.go +++ b/terminal/terminal.go @@ -4,6 +4,7 @@ import ( "fmt" "os" "strconv" + "strings" "sync" "syscall" "unsafe" @@ -12,14 +13,17 @@ import ( ) type Terminal struct { - lines []Line // lines, where 0 is earliest, n is latest - position Position // line and col - lock sync.Mutex - pty *os.File - logger *zap.SugaredLogger - title string - onUpdate []func() - size Winsize + lines []Line // lines, where 0 is earliest, n is latest + position Position // line and col + lock sync.Mutex + pty *os.File + logger *zap.SugaredLogger + title string + onUpdate []func() + size Winsize + colourScheme ColourScheme + cellAttr CellAttributes + defaultCellAttr CellAttributes } type Line struct { @@ -84,14 +88,23 @@ type Position struct { Col int } -func New(pty *os.File, logger *zap.SugaredLogger) *Terminal { +func New(pty *os.File, logger *zap.SugaredLogger, colourScheme ColourScheme) *Terminal { + + defaultCellAttr := CellAttributes{ + FgColour: colourScheme.DefaultFg, + BgColour: colourScheme.DefaultBg, + } + return &Terminal{ lines: []Line{ NewLine(), }, - pty: pty, - logger: logger, - onUpdate: []func(){}, + pty: pty, + logger: logger, + onUpdate: []func(){}, + cellAttr: defaultCellAttr, + defaultCellAttr: defaultCellAttr, + colourScheme: colourScheme, } } @@ -225,6 +238,42 @@ func (terminal *Terminal) Read() error { } case byte('m'): // SGR: colour and shit + sgr := string(params) + sgrParams := strings.Split(sgr, ";") + for i := range sgrParams { + param := sgrParams[i] + switch param { + case "0": + terminal.cellAttr = terminal.defaultCellAttr + case "1": + terminal.cellAttr.Bold = true + case "2": + terminal.cellAttr.Dim = true + case "4": + terminal.cellAttr.Underline = true + case "5": + terminal.cellAttr.Blink = true + case "7": + terminal.cellAttr.Reverse = true + case "8": + terminal.cellAttr.Hidden = true + case "21": + terminal.cellAttr.Bold = false + case "22": + terminal.cellAttr.Dim = false + case "24": + terminal.cellAttr.Underline = false + case "25": + terminal.cellAttr.Blink = false + case "27": + terminal.cellAttr.Reverse = false + case "28": + terminal.cellAttr.Hidden = false + case "39": + + } + } + terminal.logger.Debugf("SGR params %#v intermediate %#v", params, intermediate) default: b = <-buffer terminal.logger.Debugf("Unknown CSI control sequence: 0x%02X (%s)", final, string([]byte{final})) @@ -251,6 +300,8 @@ func (terminal *Terminal) Read() error { default: terminal.logger.Debugf("Unknown OSC control sequence: 0x%02X", b) } + case byte('c'): + terminal.logger.Errorf("RIS not yet supported") default: terminal.logger.Debugf("Unknown control sequence: 0x%02X", b) } @@ -285,6 +336,7 @@ func (terminal *Terminal) Read() error { n, err := terminal.pty.Read(readBytes) if err != nil { terminal.logger.Errorf("Failed to read from pty: %s", err) + return err } if len(readBytes) > 0 { readBytes = readBytes[:n] @@ -355,6 +407,7 @@ func (terminal *Terminal) setRuneAtPos(pos Position, r rune) error { line.Cells = append(line.Cells, Cell{}) } + line.Cells[pos.Col].attr = terminal.cellAttr line.Cells[pos.Col].r = r return nil }