mirror of https://github.com/liamg/aminal.git
lots of unicode work
This commit is contained in:
parent
b0c60e7336
commit
1a20091388
43
README.md
43
README.md
|
@ -1,6 +1,8 @@
|
||||||
Raft is a terminal emulator utilising OpenGL.
|
Raft is a terminal emulator utilising OpenGL v4.1.
|
||||||
|
|
||||||
This project is purely a learning exercise right now.
|
The project is purely a learning exercise right now.
|
||||||
|
|
||||||
|
Ensure you have your latest graphics card drivers installed before use.
|
||||||
|
|
||||||
## Build Dependencies
|
## Build Dependencies
|
||||||
|
|
||||||
|
@ -9,11 +11,40 @@ This project is purely a learning exercise right now.
|
||||||
- On Ubuntu/Debian-like Linux distributions, you need `libgl1-mesa-dev xorg-dev`.
|
- On Ubuntu/Debian-like Linux distributions, you need `libgl1-mesa-dev xorg-dev`.
|
||||||
- On CentOS/Fedora-like Linux distributions, you need `libX11-devel libXcursor-devel libXrandr-devel libXinerama-devel mesa-libGL-devel libXi-devel`.
|
- On CentOS/Fedora-like Linux distributions, you need `libX11-devel libXcursor-devel libXrandr-devel libXinerama-devel mesa-libGL-devel libXi-devel`.
|
||||||
|
|
||||||
|
## Planned Features
|
||||||
|
|
||||||
|
| Feature | Done | Notes |
|
||||||
|
|-------------------|------|-------|
|
||||||
|
| Pty allocation | ✔ | Linux only so far
|
||||||
|
| OpenGL rendering | ✔ |
|
||||||
|
| Resizing/content reordering | ✔ |
|
||||||
|
| ANSI escape codes | 50% |
|
||||||
|
| UTF-8 input | 90% | No copy + paste as yet
|
||||||
|
| UTF-8 output | ✘ |
|
||||||
|
| Copy/paste | ✘ |
|
||||||
|
| Customisable colour schemes | ✔ | Complete, but the config file has no entry for this yet
|
||||||
|
| Config file | 5% |
|
||||||
|
| Scrolling | 50% | Infinite buffer implemented, need GUI scrollbar & render updates
|
||||||
|
| Sweet render effects | ✘ |
|
||||||
|
||||
|
||||||
|
|
||||||
## Platform Support
|
## Platform Support
|
||||||
|
|
||||||
| Platform | Supported
|
| Platform | Supported
|
||||||
|---------|------------
|
|----------|------------
|
||||||
| Linux | Yes
|
| Linux | ✔
|
||||||
| MacOSX | Not yet...
|
| MacOSX | ✘ (nearly)
|
||||||
| Windows | Not yet...
|
| Windows | ✘
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
Raft looks for a config file in the following places: `~/.raft.yml`, `~/.raft/config.yml`, `~/.config/raft/config.yml` (earlier in the list prioritised).
|
||||||
|
|
||||||
|
Example config:
|
||||||
|
```
|
||||||
|
debug: False
|
||||||
|
```
|
||||||
|
|
||||||
|
The following options are available:
|
||||||
|
| Name | Type | Description
|
||||||
|
| debug | bool | Enables debug logging
|
||||||
|
|
|
@ -1,5 +1,22 @@
|
||||||
package config
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"gitlab.com/liamg/raft/terminal"
|
||||||
|
yaml "gopkg.in/yaml.v2"
|
||||||
|
)
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
DebugMode bool
|
DebugMode bool `yaml:"debug"`
|
||||||
|
ColourScheme terminal.ColourScheme
|
||||||
|
}
|
||||||
|
|
||||||
|
var DefaultConfig = Config{
|
||||||
|
DebugMode: false,
|
||||||
|
ColourScheme: terminal.DefaultColourScheme,
|
||||||
|
}
|
||||||
|
|
||||||
|
func Parse(data []byte) (*Config, error) {
|
||||||
|
c := DefaultConfig
|
||||||
|
err := yaml.Unmarshal(data, &c)
|
||||||
|
return &c, err
|
||||||
}
|
}
|
||||||
|
|
24
gui/cell.go
24
gui/cell.go
|
@ -14,14 +14,16 @@ type Cell struct {
|
||||||
colourAttr uint32
|
colourAttr uint32
|
||||||
points []float32
|
points []float32
|
||||||
colour [3]float32
|
colour [3]float32
|
||||||
|
hidden bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *GUI) NewCell(font *v41.Font, x float32, y float32, w float32, h float32, colourAttr uint32) Cell {
|
func (gui *GUI) NewCell(font *v41.Font, x float32, y float32, w float32, h float32, colourAttr uint32, bgColour [3]float32) Cell {
|
||||||
cell := Cell{
|
cell := Cell{
|
||||||
text: v41.NewText(font, 1.0, 1.1),
|
text: v41.NewText(font, 1.0, 1.1),
|
||||||
colourAttr: colourAttr,
|
colourAttr: colourAttr,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cell.colour = bgColour
|
||||||
cell.text.SetPosition(mgl32.Vec2{x, y})
|
cell.text.SetPosition(mgl32.Vec2{x, y})
|
||||||
|
|
||||||
x = (x - (w / 2)) / (float32(gui.width) / 2)
|
x = (x - (w / 2)) / (float32(gui.width) / 2)
|
||||||
|
@ -97,12 +99,17 @@ func (cell *Cell) makeVao() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cell *Cell) DrawBg() {
|
func (cell *Cell) DrawBg() {
|
||||||
|
if cell.hidden {
|
||||||
|
return
|
||||||
|
}
|
||||||
gl.BindVertexArray(cell.vao)
|
gl.BindVertexArray(cell.vao)
|
||||||
gl.DrawArrays(gl.TRIANGLES, 0, 6)
|
gl.DrawArrays(gl.TRIANGLES, 0, 6)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cell *Cell) DrawText() {
|
func (cell *Cell) DrawText() {
|
||||||
|
if cell.hidden {
|
||||||
|
return
|
||||||
|
}
|
||||||
if cell.text != nil {
|
if cell.text != nil {
|
||||||
cell.text.Draw()
|
cell.text.Draw()
|
||||||
}
|
}
|
||||||
|
@ -110,21 +117,22 @@ func (cell *Cell) DrawText() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cell *Cell) Show() {
|
func (cell *Cell) Show() {
|
||||||
if cell.text != nil {
|
cell.hidden = false
|
||||||
cell.text.Show()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cell *Cell) Hide() {
|
func (cell *Cell) Hide() {
|
||||||
if cell.text != nil {
|
cell.hidden = true
|
||||||
cell.text.Hide()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cell *Cell) SetRune(r rune) {
|
func (cell *Cell) SetRune(r rune) {
|
||||||
if cell.text != nil {
|
if cell.text != nil {
|
||||||
|
if r == '%' {
|
||||||
|
cell.text.SetString("%%")
|
||||||
|
} else {
|
||||||
cell.text.SetString(string(r))
|
cell.text.SetString(string(r))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cell *Cell) Release() {
|
func (cell *Cell) Release() {
|
||||||
|
|
64
gui/gui.go
64
gui/gui.go
|
@ -64,6 +64,8 @@ func (gui *GUI) resize(w *glfw.Window, width int, height int) {
|
||||||
gui.font.ResizeWindow(float32(width), float32(height))
|
gui.font.ResizeWindow(float32(width), float32(height))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
gl.Viewport(0, 0, int32(gui.width), int32(gui.height))
|
||||||
|
|
||||||
scaleMin, scaleMax := float32(1.0), float32(1.1)
|
scaleMin, scaleMax := float32(1.0), float32(1.1)
|
||||||
text := v41.NewText(gui.font, scaleMin, scaleMax)
|
text := v41.NewText(gui.font, scaleMin, scaleMax)
|
||||||
text.SetString("A")
|
text.SetString("A")
|
||||||
|
@ -98,12 +100,7 @@ func (gui *GUI) updateTexts() {
|
||||||
|
|
||||||
c, err := gui.terminal.GetCellAtPos(terminal.Position{Line: row, Col: col})
|
c, err := gui.terminal.GetCellAtPos(terminal.Position{Line: row, Col: col})
|
||||||
|
|
||||||
if err != nil || c == nil {
|
if err != nil || c == nil || c.IsHidden() {
|
||||||
gui.cells[row][col].Hide()
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.IsHidden() {
|
|
||||||
gui.cells[row][col].Hide()
|
gui.cells[row][col].Hide()
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -113,6 +110,13 @@ func (gui *GUI) updateTexts() {
|
||||||
gui.cells[row][col].SetRune(c.GetRune())
|
gui.cells[row][col].SetRune(c.GetRune())
|
||||||
gui.cells[row][col].Show()
|
gui.cells[row][col].Show()
|
||||||
|
|
||||||
|
if gui.terminal.GetPosition().Col == col && gui.terminal.GetPosition().Line == row {
|
||||||
|
gui.cells[row][col].SetBgColour(
|
||||||
|
gui.config.ColourScheme.Cursor[0],
|
||||||
|
gui.config.ColourScheme.Cursor[1],
|
||||||
|
gui.config.ColourScheme.Cursor[2],
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -134,7 +138,7 @@ func (gui *GUI) createTexts() {
|
||||||
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))
|
||||||
|
|
||||||
cells[row] = append(cells[row], gui.NewCell(gui.font, x, y, gui.charWidth, gui.charHeight, gui.colourAttr))
|
cells[row] = append(cells[row], gui.NewCell(gui.font, x, y, gui.charWidth, gui.charHeight, gui.colourAttr, gui.config.ColourScheme.DefaultBg))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -171,8 +175,8 @@ func (gui *GUI) Render() error {
|
||||||
gl.BindFragDataLocation(program, 0, gl.Str("outColour\x00"))
|
gl.BindFragDataLocation(program, 0, gl.Str("outColour\x00"))
|
||||||
|
|
||||||
gui.logger.Debugf("Loading font...")
|
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("/usr/share/fonts/nerd-fonts-complete/ttf/Roboto Mono Nerd Font Complete.ttf", 12); err != nil {
|
||||||
if err := gui.loadFont("./fonts/CamingoCode-Regular.ttf", 12); err != nil {
|
//if err := gui.loadFont("./fonts/CamingoCode-Regular.ttf", 12); err != nil {
|
||||||
return fmt.Errorf("Failed to load font: %s", err)
|
return fmt.Errorf("Failed to load font: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -202,51 +206,60 @@ func (gui *GUI) Render() error {
|
||||||
text.SetColor(mgl32.Vec3{1, 0, 0})
|
text.SetColor(mgl32.Vec3{1, 0, 0})
|
||||||
text.SetPosition(mgl32.Vec2{0, 0})
|
text.SetPosition(mgl32.Vec2{0, 0})
|
||||||
|
|
||||||
ticker := time.NewTicker(time.Second)
|
ticker := time.NewTicker(time.Millisecond * 100)
|
||||||
defer ticker.Stop()
|
defer ticker.Stop()
|
||||||
|
|
||||||
//gl.Disable(gl.MULTISAMPLE)
|
//gl.Disable(gl.MULTISAMPLE)
|
||||||
// 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
|
updateRequired := 0
|
||||||
|
|
||||||
gui.logger.Debugf("Starting render...")
|
gui.logger.Debugf("Starting render...")
|
||||||
|
|
||||||
|
gl.UseProgram(program)
|
||||||
|
|
||||||
// todo set bg colour
|
// todo set bg colour
|
||||||
//bgColour := gui.terminal.colourScheme.DefaultBgColor
|
//bgColour :=
|
||||||
gl.ClearColor(0.1, 0.1, 0.1, 1.0)
|
gl.ClearColor(
|
||||||
|
gui.config.ColourScheme.DefaultBg[0],
|
||||||
|
gui.config.ColourScheme.DefaultBg[1],
|
||||||
|
gui.config.ColourScheme.DefaultBg[2],
|
||||||
|
1.0,
|
||||||
|
)
|
||||||
|
|
||||||
for !gui.window.ShouldClose() {
|
for !gui.window.ShouldClose() {
|
||||||
|
|
||||||
updateRequired = false
|
if updateRequired > 0 {
|
||||||
|
|
||||||
|
updateRequired--
|
||||||
|
|
||||||
|
} else {
|
||||||
CheckUpdate:
|
CheckUpdate:
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-updateChan:
|
case <-updateChan:
|
||||||
updateRequired = true
|
updateRequired = 2
|
||||||
case <-ticker.C:
|
case <-ticker.C:
|
||||||
text.SetString(fmt.Sprintf("%dx%d", gui.cols, gui.rows))
|
text.SetString(fmt.Sprintf("%dx%d@%d,%d", gui.cols, gui.rows, gui.terminal.GetPosition().Col, gui.terminal.GetPosition().Line))
|
||||||
updateRequired = true
|
updateRequired = 2
|
||||||
default:
|
default:
|
||||||
break CheckUpdate
|
break CheckUpdate
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
gl.UseProgram(program)
|
gl.UseProgram(program)
|
||||||
|
|
||||||
if updateRequired {
|
if updateRequired > 0 {
|
||||||
|
|
||||||
gl.Viewport(0, 0, int32(gui.width), int32(gui.height))
|
|
||||||
|
|
||||||
gui.updateTexts()
|
gui.updateTexts()
|
||||||
|
|
||||||
// Render the string.
|
// Render the string.
|
||||||
gui.window.SetTitle(gui.terminal.GetTitle())
|
gui.window.SetTitle(gui.terminal.GetTitle())
|
||||||
|
|
||||||
|
//gl.ClearColor(0.5, 0.5, 0.5, 1.0)
|
||||||
gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)
|
gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)
|
||||||
|
|
||||||
cols, rows := gui.getTermSize()
|
cols, rows := gui.getTermSize()
|
||||||
|
|
||||||
for row := 0; row < rows; row++ {
|
for row := 0; row < rows; row++ {
|
||||||
|
@ -257,16 +270,16 @@ func (gui *GUI) Render() error {
|
||||||
|
|
||||||
for row := 0; row < rows; row++ {
|
for row := 0; row < rows; row++ {
|
||||||
for col := 0; col < cols; col++ {
|
for col := 0; col < cols; col++ {
|
||||||
|
|
||||||
gui.cells[row][col].DrawText()
|
gui.cells[row][col].DrawText()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
text.Draw()
|
// debug to show co-ords
|
||||||
|
//text.Draw()
|
||||||
}
|
}
|
||||||
|
|
||||||
glfw.PollEvents()
|
glfw.PollEvents()
|
||||||
if updateRequired {
|
if updateRequired > 0 {
|
||||||
gui.window.SwapBuffers()
|
gui.window.SwapBuffers()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -286,7 +299,8 @@ func (gui *GUI) loadFont(path string, scale int32) error {
|
||||||
|
|
||||||
runeRanges := make(gltext.RuneRanges, 0)
|
runeRanges := make(gltext.RuneRanges, 0)
|
||||||
runeRanges = append(runeRanges, gltext.RuneRange{Low: 32, High: 127})
|
runeRanges = append(runeRanges, gltext.RuneRange{Low: 32, High: 127})
|
||||||
/*runeRanges = append(runeRanges, gltext.RuneRange{Low: 0x3000, High: 0x3030})
|
/*
|
||||||
|
runeRanges = append(runeRanges, gltext.RuneRange{Low: 0x0, High: 0x3030})
|
||||||
runeRanges = append(runeRanges, gltext.RuneRange{Low: 0x3040, High: 0x309f})
|
runeRanges = append(runeRanges, gltext.RuneRange{Low: 0x3040, High: 0x309f})
|
||||||
runeRanges = append(runeRanges, gltext.RuneRange{Low: 0x30a0, High: 0x30ff})
|
runeRanges = append(runeRanges, gltext.RuneRange{Low: 0x30a0, High: 0x30ff})
|
||||||
runeRanges = append(runeRanges, gltext.RuneRange{Low: 0x4e00, High: 0x9faf})
|
runeRanges = append(runeRanges, gltext.RuneRange{Low: 0x4e00, High: 0x9faf})
|
||||||
|
|
13
gui/input.go
13
gui/input.go
|
@ -14,12 +14,25 @@ func (gui *GUI) key(w *glfw.Window, key glfw.Key, scancode int, action glfw.Acti
|
||||||
|
|
||||||
switch true {
|
switch true {
|
||||||
case mods&glfw.ModControl > 0:
|
case mods&glfw.ModControl > 0:
|
||||||
|
|
||||||
|
if mods&glfw.ModAlt > 0 {
|
||||||
|
// ctrl + alt +
|
||||||
|
switch key {
|
||||||
|
case glfw.KeyV:
|
||||||
|
// todo handle both these errors
|
||||||
|
if buf, err := gui.window.GetClipboardString(); err == nil {
|
||||||
|
_ = gui.terminal.Write([]byte(buf))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// ctrl +
|
||||||
switch key {
|
switch key {
|
||||||
case glfw.KeyC: // ctrl^c
|
case glfw.KeyC: // ctrl^c
|
||||||
gui.logger.Debugf("Sending CTRL^C")
|
gui.logger.Debugf("Sending CTRL^C")
|
||||||
gui.terminal.Write([]byte{0x3}) // send EOT
|
gui.terminal.Write([]byte{0x3}) // send EOT
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
switch key {
|
switch key {
|
||||||
case glfw.KeyEnter:
|
case glfw.KeyEnter:
|
||||||
|
|
67
main.go
67
main.go
|
@ -2,6 +2,7 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"gitlab.com/liamg/raft/config"
|
"gitlab.com/liamg/raft/config"
|
||||||
|
@ -11,10 +12,33 @@ import (
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func loadConfig() config.Config {
|
||||||
|
|
||||||
// parse this
|
home := os.Getenv("HOME")
|
||||||
conf := config.Config{DebugMode: true}
|
if home == "" {
|
||||||
|
return config.DefaultConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
places := []string{
|
||||||
|
fmt.Sprintf("%s/.raft.yml", home),
|
||||||
|
fmt.Sprintf("%s/.raft/config.yml", home),
|
||||||
|
fmt.Sprintf("%s/.config/raft/config.yml", home),
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, place := range places {
|
||||||
|
if b, err := ioutil.ReadFile(place); err == nil {
|
||||||
|
if c, err := config.Parse(b); err == nil {
|
||||||
|
return *c
|
||||||
|
} else {
|
||||||
|
fmt.Printf("Invalid config at %s: %s\n", place, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return config.DefaultConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
func getLogger(conf config.Config) (*zap.SugaredLogger, error) {
|
||||||
|
|
||||||
var logger *zap.Logger
|
var logger *zap.Logger
|
||||||
var err error
|
var err error
|
||||||
|
@ -24,30 +48,35 @@ func main() {
|
||||||
logger, err = zap.NewProduction()
|
logger, err = zap.NewProduction()
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Failed to create logger: %s", err)
|
return nil, fmt.Errorf("Failed to create logger: %s", err)
|
||||||
|
}
|
||||||
|
return logger.Sugar(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
|
||||||
|
// parse this
|
||||||
|
conf := loadConfig()
|
||||||
|
|
||||||
|
logger, err := getLogger(conf)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Failed to create logger: %s\n", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
sugaredLogger := logger.Sugar()
|
defer logger.Sync()
|
||||||
defer sugaredLogger.Sync()
|
|
||||||
|
|
||||||
sugaredLogger.Infof("Allocationg pty...")
|
logger.Infof("Allocating pty...")
|
||||||
pty, err := pty.NewPtyWithShell()
|
pty, err := pty.NewPtyWithShell()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
sugaredLogger.Fatalf("Failed to allocate pty: %s", err)
|
logger.Fatalf("Failed to allocate pty: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
sugaredLogger.Infof("Creating terminal...")
|
logger.Infof("Creating terminal...")
|
||||||
terminal := terminal.New(pty, sugaredLogger, terminal.DefaultColourScheme)
|
terminal := terminal.New(pty, logger, conf.ColourScheme)
|
||||||
/*
|
|
||||||
go func() {
|
|
||||||
time.Sleep(time.Second * 1)
|
|
||||||
terminal.Write([]byte("tput cols && tput lines\n"))
|
|
||||||
terminal.Write([]byte("ls -la\n"))
|
|
||||||
}()
|
|
||||||
*/
|
|
||||||
|
|
||||||
g := gui.New(conf, terminal, sugaredLogger)
|
g := gui.New(conf, terminal, logger)
|
||||||
if err := g.Render(); err != nil {
|
if err := g.Render(); err != nil {
|
||||||
sugaredLogger.Fatalf("Render error: %s", err)
|
logger.Fatalf("Render error: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,15 @@ type CellAttributes struct {
|
||||||
Hidden bool
|
Hidden bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (terminal *Terminal) NewCell() Cell {
|
||||||
|
return Cell{
|
||||||
|
attr: CellAttributes{
|
||||||
|
FgColour: terminal.colourScheme.DefaultFg,
|
||||||
|
BgColour: terminal.colourScheme.DefaultBg,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (cell *Cell) GetRune() rune {
|
func (cell *Cell) GetRune() rune {
|
||||||
return cell.r
|
return cell.r
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package terminal
|
package terminal
|
||||||
|
|
||||||
type ColourScheme struct {
|
type ColourScheme struct {
|
||||||
|
Cursor [3]float32
|
||||||
DefaultFg [3]float32
|
DefaultFg [3]float32
|
||||||
BlackFg [3]float32
|
BlackFg [3]float32
|
||||||
RedFg [3]float32
|
RedFg [3]float32
|
||||||
|
@ -38,6 +39,7 @@ type ColourScheme struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
var DefaultColourScheme = ColourScheme{
|
var DefaultColourScheme = ColourScheme{
|
||||||
|
Cursor: [3]float32{0.8, 0.8, 0.8},
|
||||||
//fg
|
//fg
|
||||||
DefaultFg: [3]float32{1, 1, 1},
|
DefaultFg: [3]float32{1, 1, 1},
|
||||||
BlackFg: [3]float32{0, 0, 0},
|
BlackFg: [3]float32{0, 0, 0},
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package terminal
|
package terminal
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
@ -126,6 +127,10 @@ func (terminal *Terminal) incrementPosition() {
|
||||||
position := terminal.getPosition()
|
position := terminal.getPosition()
|
||||||
if position.Col+1 >= int(terminal.size.Width) {
|
if position.Col+1 >= int(terminal.size.Width) {
|
||||||
position.Line++
|
position.Line++
|
||||||
|
_, h := terminal.GetSize()
|
||||||
|
if position.Line >= h {
|
||||||
|
position.Line--
|
||||||
|
}
|
||||||
position.Col = 0
|
position.Col = 0
|
||||||
} else {
|
} else {
|
||||||
position.Col++
|
position.Col++
|
||||||
|
@ -137,6 +142,10 @@ func (terminal *Terminal) SetPosition(position Position) {
|
||||||
terminal.position = position
|
terminal.position = position
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (terminal *Terminal) GetPosition() Position {
|
||||||
|
return terminal.position
|
||||||
|
}
|
||||||
|
|
||||||
func (terminal *Terminal) GetTitle() string {
|
func (terminal *Terminal) GetTitle() string {
|
||||||
return terminal.title
|
return terminal.title
|
||||||
}
|
}
|
||||||
|
@ -151,11 +160,13 @@ func (terminal *Terminal) ClearToEndOfLine() {
|
||||||
|
|
||||||
position := terminal.getPosition()
|
position := terminal.getPosition()
|
||||||
|
|
||||||
if position.Line < len(terminal.lines) {
|
line := terminal.getBufferedLine(position.Line)
|
||||||
if position.Col < len(terminal.lines[position.Line].Cells) {
|
if line != nil {
|
||||||
terminal.lines[position.Line].Cells = terminal.lines[position.Line].Cells[:position.Col]
|
if position.Col < len(line.Cells) {
|
||||||
|
line.Cells = line.Cells[:position.Col]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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)
|
// 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)
|
||||||
|
@ -172,12 +183,7 @@ func (terminal *Terminal) getBufferedLine(line int) *Line {
|
||||||
return &terminal.lines[line]
|
return &terminal.lines[line]
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read needs to be run on a goroutine, as it continually reads output to set on the terminal
|
func (terminal *Terminal) processInput(buffer chan rune) {
|
||||||
func (terminal *Terminal) Read() error {
|
|
||||||
|
|
||||||
buffer := make(chan byte, 0xffff)
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
|
|
||||||
// https://en.wikipedia.org/wiki/ANSI_escape_code
|
// https://en.wikipedia.org/wiki/ANSI_escape_code
|
||||||
|
|
||||||
|
@ -188,9 +194,9 @@ func (terminal *Terminal) Read() error {
|
||||||
b = <-buffer
|
b = <-buffer
|
||||||
switch b {
|
switch b {
|
||||||
case 0x5b: // CSI: Control Sequence Introducer ]
|
case 0x5b: // CSI: Control Sequence Introducer ]
|
||||||
var final byte
|
var final rune
|
||||||
params := []byte{}
|
params := []rune{}
|
||||||
intermediate := []byte{}
|
intermediate := []rune{}
|
||||||
CSI:
|
CSI:
|
||||||
for {
|
for {
|
||||||
b = <-buffer
|
b = <-buffer
|
||||||
|
@ -206,11 +212,11 @@ func (terminal *Terminal) Read() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
switch final {
|
switch final {
|
||||||
case byte('A'):
|
case rune('A'):
|
||||||
distance := 1
|
distance := 1
|
||||||
if len(params) > 0 {
|
if len(params) > 0 {
|
||||||
var err error
|
var err error
|
||||||
distance, err = strconv.Atoi(string([]byte{params[0]}))
|
distance, err = strconv.Atoi(string(params[0]))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
distance = 1
|
distance = 1
|
||||||
}
|
}
|
||||||
|
@ -218,11 +224,11 @@ func (terminal *Terminal) Read() error {
|
||||||
if terminal.position.Line-distance >= 0 {
|
if terminal.position.Line-distance >= 0 {
|
||||||
terminal.position.Line -= distance
|
terminal.position.Line -= distance
|
||||||
}
|
}
|
||||||
case byte('B'):
|
case rune('B'):
|
||||||
distance := 1
|
distance := 1
|
||||||
if len(params) > 0 {
|
if len(params) > 0 {
|
||||||
var err error
|
var err error
|
||||||
distance, err = strconv.Atoi(string([]byte{params[0]}))
|
distance, err = strconv.Atoi(string(params[0]))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
distance = 1
|
distance = 1
|
||||||
}
|
}
|
||||||
|
@ -231,12 +237,12 @@ func (terminal *Terminal) Read() error {
|
||||||
terminal.position.Line += distance
|
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] == rune('0') {
|
||||||
terminal.ClearToEndOfLine()
|
terminal.ClearToEndOfLine()
|
||||||
} else {
|
} else {
|
||||||
terminal.logger.Errorf("Unsupported EL")
|
terminal.logger.Errorf("Unsupported EL")
|
||||||
}
|
}
|
||||||
case byte('m'):
|
case rune('m'):
|
||||||
// SGR: colour and shit
|
// SGR: colour and shit
|
||||||
sgr := string(params)
|
sgr := string(params)
|
||||||
sgrParams := strings.Split(sgr, ";")
|
sgrParams := strings.Split(sgr, ";")
|
||||||
|
@ -270,21 +276,88 @@ func (terminal *Terminal) Read() error {
|
||||||
case "28":
|
case "28":
|
||||||
terminal.cellAttr.Hidden = false
|
terminal.cellAttr.Hidden = false
|
||||||
case "39":
|
case "39":
|
||||||
|
terminal.cellAttr.FgColour = terminal.colourScheme.DefaultFg
|
||||||
|
case "30":
|
||||||
|
terminal.cellAttr.FgColour = terminal.colourScheme.BlackFg
|
||||||
|
case "31":
|
||||||
|
terminal.cellAttr.FgColour = terminal.colourScheme.RedFg
|
||||||
|
case "32":
|
||||||
|
terminal.cellAttr.FgColour = terminal.colourScheme.GreenFg
|
||||||
|
case "33":
|
||||||
|
terminal.cellAttr.FgColour = terminal.colourScheme.YellowFg
|
||||||
|
case "34":
|
||||||
|
terminal.cellAttr.FgColour = terminal.colourScheme.BlueFg
|
||||||
|
case "35":
|
||||||
|
terminal.cellAttr.FgColour = terminal.colourScheme.MagentaFg
|
||||||
|
case "36":
|
||||||
|
terminal.cellAttr.FgColour = terminal.colourScheme.CyanFg
|
||||||
|
case "37":
|
||||||
|
terminal.cellAttr.FgColour = terminal.colourScheme.LightGreyFg
|
||||||
|
case "90":
|
||||||
|
terminal.cellAttr.FgColour = terminal.colourScheme.DarkGreyFg
|
||||||
|
case "91":
|
||||||
|
terminal.cellAttr.FgColour = terminal.colourScheme.LightRedFg
|
||||||
|
case "92":
|
||||||
|
terminal.cellAttr.FgColour = terminal.colourScheme.LightGreenFg
|
||||||
|
case "93":
|
||||||
|
terminal.cellAttr.FgColour = terminal.colourScheme.LightYellowFg
|
||||||
|
case "94":
|
||||||
|
terminal.cellAttr.FgColour = terminal.colourScheme.LightBlueFg
|
||||||
|
case "95":
|
||||||
|
terminal.cellAttr.FgColour = terminal.colourScheme.LightMagentaFg
|
||||||
|
case "96":
|
||||||
|
terminal.cellAttr.FgColour = terminal.colourScheme.LightCyanFg
|
||||||
|
case "97":
|
||||||
|
terminal.cellAttr.FgColour = terminal.colourScheme.WhiteFg
|
||||||
|
case "49":
|
||||||
|
terminal.cellAttr.BgColour = terminal.colourScheme.DefaultBg
|
||||||
|
case "40":
|
||||||
|
terminal.cellAttr.BgColour = terminal.colourScheme.BlackBg
|
||||||
|
case "41":
|
||||||
|
terminal.cellAttr.BgColour = terminal.colourScheme.RedBg
|
||||||
|
case "42":
|
||||||
|
terminal.cellAttr.BgColour = terminal.colourScheme.GreenBg
|
||||||
|
case "43":
|
||||||
|
terminal.cellAttr.BgColour = terminal.colourScheme.YellowBg
|
||||||
|
case "44":
|
||||||
|
terminal.cellAttr.BgColour = terminal.colourScheme.BlueBg
|
||||||
|
case "45":
|
||||||
|
terminal.cellAttr.BgColour = terminal.colourScheme.MagentaBg
|
||||||
|
case "46":
|
||||||
|
terminal.cellAttr.BgColour = terminal.colourScheme.CyanBg
|
||||||
|
case "47":
|
||||||
|
terminal.cellAttr.BgColour = terminal.colourScheme.LightGreenBg
|
||||||
|
case "100":
|
||||||
|
terminal.cellAttr.BgColour = terminal.colourScheme.DarkGreyBg
|
||||||
|
case "101":
|
||||||
|
terminal.cellAttr.BgColour = terminal.colourScheme.LightRedBg
|
||||||
|
case "102":
|
||||||
|
terminal.cellAttr.BgColour = terminal.colourScheme.LightGreenBg
|
||||||
|
case "103":
|
||||||
|
terminal.cellAttr.BgColour = terminal.colourScheme.LightYellowBg
|
||||||
|
case "104":
|
||||||
|
terminal.cellAttr.BgColour = terminal.colourScheme.LightBlueBg
|
||||||
|
case "105":
|
||||||
|
terminal.cellAttr.BgColour = terminal.colourScheme.LightMagentaBg
|
||||||
|
case "106":
|
||||||
|
terminal.cellAttr.BgColour = terminal.colourScheme.LightCyanBg
|
||||||
|
case "107":
|
||||||
|
terminal.cellAttr.BgColour = terminal.colourScheme.WhiteBg
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
terminal.logger.Debugf("SGR params %#v intermediate %#v", params, intermediate)
|
|
||||||
default:
|
default:
|
||||||
b = <-buffer
|
b = <-buffer
|
||||||
terminal.logger.Debugf("Unknown CSI control sequence: 0x%02X (%s)", final, string([]byte{final}))
|
terminal.logger.Errorf("Unknown CSI control sequence: 0x%02X (%s)", final, string(final))
|
||||||
}
|
}
|
||||||
case 0x5d: // OSC: Operating System Command
|
case 0x5d: // OSC: Operating System Command
|
||||||
b = <-buffer
|
b = <-buffer
|
||||||
switch b {
|
switch b {
|
||||||
case byte('0'):
|
case rune('0'):
|
||||||
b = <-buffer
|
b = <-buffer
|
||||||
if b == byte(';') {
|
if b == rune(';') {
|
||||||
title := []byte{}
|
title := []rune{}
|
||||||
for {
|
for {
|
||||||
b = <-buffer
|
b = <-buffer
|
||||||
if b == 0x07 {
|
if b == 0x07 {
|
||||||
|
@ -295,24 +368,29 @@ func (terminal *Terminal) Read() error {
|
||||||
terminal.logger.Debugf("Terminal title set to: %s", string(title))
|
terminal.logger.Debugf("Terminal title set to: %s", string(title))
|
||||||
terminal.title = string(title)
|
terminal.title = string(title)
|
||||||
} else {
|
} else {
|
||||||
terminal.logger.Debugf("Invalid OSC 0 control sequence: 0x%02X", b)
|
terminal.logger.Errorf("Invalid OSC 0 control sequence: 0x%02X", b)
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
terminal.logger.Debugf("Unknown OSC control sequence: 0x%02X", b)
|
terminal.logger.Errorf("Unknown OSC control sequence: 0x%02X", b)
|
||||||
}
|
}
|
||||||
case byte('c'):
|
case rune('c'):
|
||||||
terminal.logger.Errorf("RIS not yet supported")
|
terminal.logger.Errorf("RIS not yet supported")
|
||||||
|
case rune(')'), rune('('):
|
||||||
|
b = <-buffer
|
||||||
|
terminal.logger.Debugf("Ignoring character set control code )%s", string(b))
|
||||||
default:
|
default:
|
||||||
terminal.logger.Debugf("Unknown control sequence: 0x%02X", b)
|
terminal.logger.Errorf("Unknown control sequence: 0x%02X [%s]", b, string(b))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
switch b {
|
switch b {
|
||||||
case 0x0a:
|
case 0x0a:
|
||||||
for terminal.position.Line+1 >= len(terminal.lines) {
|
|
||||||
terminal.lines = append(terminal.lines, NewLine())
|
|
||||||
}
|
|
||||||
terminal.position.Line++
|
terminal.position.Line++
|
||||||
|
_, h := terminal.GetSize()
|
||||||
|
if terminal.position.Line >= h {
|
||||||
|
terminal.position.Line--
|
||||||
|
}
|
||||||
|
terminal.lines = append(terminal.lines, NewLine())
|
||||||
case 0x0d:
|
case 0x0d:
|
||||||
terminal.position.Col = 0
|
terminal.position.Col = 0
|
||||||
case 0x08:
|
case 0x08:
|
||||||
|
@ -323,28 +401,28 @@ 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}))
|
||||||
terminal.writeRune([]rune(string([]byte{b}))[0])
|
terminal.writeRune(b)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
terminal.triggerOnUpdate()
|
terminal.triggerOnUpdate()
|
||||||
}
|
}
|
||||||
}()
|
}
|
||||||
|
|
||||||
|
// Read needs to be run on a goroutine, as it continually reads output to set on the terminal
|
||||||
|
func (terminal *Terminal) Read() error {
|
||||||
|
|
||||||
|
buffer := make(chan rune, 0xffff)
|
||||||
|
|
||||||
|
reader := bufio.NewReader(terminal.pty)
|
||||||
|
|
||||||
|
go terminal.processInput(buffer)
|
||||||
for {
|
for {
|
||||||
readBytes := make([]byte, 1024)
|
r, size, err := reader.ReadRune()
|
||||||
n, err := terminal.pty.Read(readBytes)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
terminal.logger.Errorf("Failed to read from pty: %s", err)
|
|
||||||
return err
|
return err
|
||||||
}
|
} else if size > 0 {
|
||||||
if len(readBytes) > 0 {
|
buffer <- r
|
||||||
readBytes = readBytes[:n]
|
|
||||||
fmt.Printf("Data in: %q\n", string(readBytes))
|
|
||||||
for _, x := range readBytes {
|
|
||||||
buffer <- x
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -380,7 +458,7 @@ func (terminal *Terminal) GetCellAtPos(pos Position) (*Cell, error) {
|
||||||
return nil, fmt.Errorf("Line missing")
|
return nil, fmt.Errorf("Line missing")
|
||||||
}
|
}
|
||||||
for pos.Col >= len(line.Cells) {
|
for pos.Col >= len(line.Cells) {
|
||||||
line.Cells = append(line.Cells, Cell{})
|
line.Cells = append(line.Cells, terminal.NewCell())
|
||||||
}
|
}
|
||||||
return &line.Cells[pos.Col], nil
|
return &line.Cells[pos.Col], nil
|
||||||
}
|
}
|
||||||
|
@ -396,15 +474,17 @@ func (terminal *Terminal) setRuneAtPos(pos Position, r rune) error {
|
||||||
fmt.Printf("\n\nSetting %d %d to %q\n\n\n", pos.Line, pos.Col, string(r))
|
fmt.Printf("\n\nSetting %d %d to %q\n\n\n", pos.Line, pos.Col, string(r))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for terminal.position.Line >= len(terminal.lines) {
|
||||||
|
terminal.lines = append(terminal.lines, NewLine())
|
||||||
|
}
|
||||||
|
|
||||||
line := terminal.getBufferedLine(pos.Line)
|
line := terminal.getBufferedLine(pos.Line)
|
||||||
if line == nil {
|
if line == nil {
|
||||||
|
|
||||||
return fmt.Errorf("Impossible?")
|
return fmt.Errorf("Impossible?")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for pos.Col >= len(line.Cells) {
|
for pos.Col >= len(line.Cells) {
|
||||||
line.Cells = append(line.Cells, Cell{})
|
line.Cells = append(line.Cells, terminal.NewCell())
|
||||||
}
|
}
|
||||||
|
|
||||||
line.Cells[pos.Col].attr = terminal.cellAttr
|
line.Cells[pos.Col].attr = terminal.cellAttr
|
||||||
|
|
Loading…
Reference in New Issue