1
0
mirror of https://github.com/taigrr/gopher-os synced 2025-01-18 04:43:13 -08:00

Implement simple terminal

The terminal uses console.Vga as its output device. A proper terminal
implementation would be using a console.Console interface as its output.
However, at this point we cannot use Go interfaces as the fn pointers in
the itables have not been yet initialized. The Go runtime bits that set
up the itables need access to a memory allocator, a facility which is
not yet provided by the kernel.
This commit is contained in:
Achilleas Anagnostopoulos 2017-03-26 21:35:17 +01:00
parent 95ce4c6057
commit 616fc6a412
4 changed files with 207 additions and 0 deletions

118
kernel/driver/tty/vt.go Normal file
View File

@ -0,0 +1,118 @@
package tty
import "github.com/achilleasa/gopher-os/kernel/driver/video/console"
const (
defaultFg = console.LightGrey
defaultBg = console.Black
)
// Vt implements a simple terminal that can process LF and CR characters. The
// terminal uses a console device for its output.
type Vt struct {
// Go interfaces will not work before we can get memory allocation working.
// Till then we need to use concrete types instead.
cons *console.Vga
width uint16
height uint16
curX uint16
curY uint16
curAttr console.Attr
}
func (t *Vt) Init(cons *console.Vga) {
t.cons = cons
t.width, t.height = cons.Dimensions()
t.curX = 0
t.curY = 0
// Default to lightgrey on black text.
t.curAttr = makeAttr(defaultFg, defaultBg)
}
// Clear clears the terminal.
func (t *Vt) Clear() {
t.cons.Lock()
defer t.cons.Unlock()
t.clear()
}
// Position returns the current cursor position (x, y).
func (t *Vt) Position() (uint16, uint16) {
t.cons.Lock()
defer t.cons.Unlock()
return t.curX, t.curY
}
// SetPosition sets the current cursor position to (x,y).
func (t *Vt) SetPosition(x, y uint16) {
t.cons.Lock()
defer t.cons.Unlock()
if x >= t.width {
x = t.width - 1
}
if y >= t.height {
y = t.height - 1
}
t.curX, t.curY = x, y
}
// Write implements io.Writer.
func (t *Vt) Write(data []byte) (int, error) {
t.cons.Lock()
defer t.cons.Unlock()
attr := t.curAttr
for _, b := range data {
switch b {
case '\r':
t.cr()
case '\n':
t.cr()
t.lf()
default:
t.cons.Write(b, attr, t.curX, t.curY)
t.curX++
if t.curX == t.width {
t.lf()
}
}
}
return len(data), nil
}
// cls clears the terminal.
func (t *Vt) clear() {
t.cons.Clear(0, 0, t.width, t.height)
}
// cr resets the x coordinate of the terminal cursor to 0.
func (t *Vt) cr() {
t.curX = 0
}
// lf advances the y coordinate of the terminal cursor by one line scrolling
// the terminal contents if the end of the last terminal line is reached.
func (t *Vt) lf() {
if t.curY+1 < t.height {
t.curY++
return
}
t.cons.Scroll(console.Up, 1)
t.cons.Clear(0, t.height-1, t.width, 1)
return
}
func makeAttr(fg, bg console.Attr) console.Attr {
return (bg << 4) | (fg & 0xF)
}

View File

@ -0,0 +1,70 @@
package tty
import (
"testing"
"github.com/achilleasa/gopher-os/kernel/driver/video/console"
)
func TestVtPosition(t *testing.T) {
specs := []struct {
inX, inY uint16
expX, expY uint16
}{
{20, 20, 20, 20},
{100, 20, 79, 20},
{10, 200, 10, 24},
{10, 200, 10, 24},
{100, 100, 79, 24},
}
var cons console.Vga
cons.Init()
var vt Vt
vt.Init(&cons)
for specIndex, spec := range specs {
vt.SetPosition(spec.inX, spec.inY)
if x, y := vt.Position(); x != spec.expX || y != spec.expY {
t.Errorf("[spec %d] expected setting position to (%d, %d) to update the position to (%d, %d); got (%d, %d)", specIndex, spec.inX, spec.inY, spec.expX, spec.expY, x, y)
}
}
}
func TestWrite(t *testing.T) {
fb := make([]uint16, 80*25)
cons := &console.Vga{}
cons.OverrideFb(fb)
cons.Init()
var vt Vt
vt.Init(cons)
vt.Clear()
vt.SetPosition(0, 1)
vt.Write([]byte("12\n3\n4\r56"))
// Trigger scroll
vt.SetPosition(79, 24)
vt.Write([]byte{'!'})
specs := []struct {
x, y uint16
expChar byte
}{
{0, 0, '1'},
{1, 0, '2'},
{0, 1, '3'},
{0, 2, '5'},
{1, 2, '6'},
{79, 23, '!'},
}
for specIndex, spec := range specs {
ch := (byte)(fb[(spec.y*vt.width)+spec.x] & 0xFF)
if ch != spec.expChar {
t.Errorf("[spec %d] expected char at (%d, %d) to be %c; got %c", specIndex, spec.x, spec.y, spec.expChar, ch)
}
}
}

View File

@ -42,6 +42,13 @@ func (cons *Vga) Init() {
}))
}
// OverrideFb overrides the console framebuffer slice with the supplied slice.
// This is a temporary function used by tests that will be removed once we can work
// with interfaces.
func (cons *Vga) OverrideFb(fb []uint16) {
cons.fb = fb
}
// Clear clears the specified rectangular region
func (cons *Vga) Clear(x, y, width, height uint16) {
var (

View File

@ -202,3 +202,15 @@ func TestVgaWrite(t *testing.T) {
t.Errorf("expected call to Write() to set fb[0] to %d; got %d", expVal, got)
}
}
func TestVgaOverrideFb(t *testing.T) {
var cons = Vga{}
cons.Init()
fb := []uint16{}
cons.OverrideFb(fb)
if len(cons.fb) != len(fb) {
t.Fatalf("expected calling OverrideFb to change the framebuffer for the console")
}
}