mirror of
https://github.com/taigrr/gopher-os
synced 2025-01-18 04:43:13 -08:00
Rename Ega console to VgaTextConsole and implement the updated interface
This commit is contained in:
parent
b72725742a
commit
286b8d9c71
@ -7,8 +7,8 @@ type ScrollDir uint8
|
|||||||
|
|
||||||
// The supported list of scroll directions for the console Scroll() calls.
|
// The supported list of scroll directions for the console Scroll() calls.
|
||||||
const (
|
const (
|
||||||
Up ScrollDir = iota
|
ScrollDirUp ScrollDir = iota
|
||||||
Down
|
ScrollDirDown
|
||||||
)
|
)
|
||||||
|
|
||||||
// The Device interface is implemented by objects that can function as system
|
// The Device interface is implemented by objects that can function as system
|
||||||
|
198
src/gopheros/device/video/console/vga_text.go
Normal file
198
src/gopheros/device/video/console/vga_text.go
Normal file
@ -0,0 +1,198 @@
|
|||||||
|
package console
|
||||||
|
|
||||||
|
import (
|
||||||
|
"gopheros/kernel"
|
||||||
|
"gopheros/kernel/cpu"
|
||||||
|
"image/color"
|
||||||
|
"reflect"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
var portWriteByteFn = cpu.PortWriteByte
|
||||||
|
|
||||||
|
// VgaTextConsole implements an EGA-compatible 80x25 text console using VGA
|
||||||
|
// mode 0x3. The console supports the default 16 EGA colors which can be
|
||||||
|
// overridden using the SetPaletteColor method.
|
||||||
|
//
|
||||||
|
// Each character in the console framebuffer is represented using two bytes,
|
||||||
|
// a byte for the character ASCII code and a byte that encodes the foreground
|
||||||
|
// and background colors (4 bits for each).
|
||||||
|
//
|
||||||
|
// The default settings for the console are:
|
||||||
|
// - light gray text (color 7) on black background (color 0).
|
||||||
|
// - space as the clear character
|
||||||
|
type VgaTextConsole struct {
|
||||||
|
width uint16
|
||||||
|
height uint16
|
||||||
|
|
||||||
|
fb []uint16
|
||||||
|
|
||||||
|
palette color.Palette
|
||||||
|
defaultFg uint8
|
||||||
|
defaultBg uint8
|
||||||
|
clearChar uint16
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewVgaTextConsole creates an new vga text console with its
|
||||||
|
// framebuffer mapped to fbPhysAddr.
|
||||||
|
func NewVgaTextConsole(columns, rows uint16, fbPhysAddr uintptr) *VgaTextConsole {
|
||||||
|
return &VgaTextConsole{
|
||||||
|
width: columns,
|
||||||
|
height: rows,
|
||||||
|
clearChar: uint16(' '),
|
||||||
|
// overlay a 16bit slice over the fbPhysAddr
|
||||||
|
fb: *(*[]uint16)(unsafe.Pointer(&reflect.SliceHeader{
|
||||||
|
Len: 80 * 25,
|
||||||
|
Cap: 80 * 25,
|
||||||
|
Data: fbPhysAddr,
|
||||||
|
})),
|
||||||
|
palette: color.Palette{
|
||||||
|
color.RGBA{R: 0, G: 0, B: 1}, /* black */
|
||||||
|
color.RGBA{R: 0, G: 0, B: 128}, /* blue */
|
||||||
|
color.RGBA{R: 0, G: 128, B: 1}, /* green */
|
||||||
|
color.RGBA{R: 0, G: 128, B: 128}, /* cyan */
|
||||||
|
color.RGBA{R: 128, G: 0, B: 1}, /* red */
|
||||||
|
color.RGBA{R: 128, G: 0, B: 128}, /* magenta */
|
||||||
|
color.RGBA{R: 64, G: 64, B: 1}, /* brown */
|
||||||
|
color.RGBA{R: 128, G: 128, B: 128}, /* light gray */
|
||||||
|
color.RGBA{R: 64, G: 64, B: 64}, /* dark gray */
|
||||||
|
color.RGBA{R: 0, G: 0, B: 255}, /* light blue */
|
||||||
|
color.RGBA{R: 0, G: 255, B: 1}, /* light green */
|
||||||
|
color.RGBA{R: 0, G: 255, B: 255}, /* light cyan */
|
||||||
|
color.RGBA{R: 255, G: 0, B: 1}, /* light red */
|
||||||
|
color.RGBA{R: 255, G: 0, B: 255}, /* light magenta */
|
||||||
|
color.RGBA{R: 255, G: 255, B: 1}, /* yellow */
|
||||||
|
color.RGBA{R: 255, G: 255, B: 255}, /* white */
|
||||||
|
},
|
||||||
|
// light gray text on black background
|
||||||
|
defaultFg: 7,
|
||||||
|
defaultBg: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dimensions returns the console width and height in characters.
|
||||||
|
func (cons *VgaTextConsole) Dimensions() (uint16, uint16) {
|
||||||
|
return cons.width, cons.height
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultColors returns the default foreground and background colors
|
||||||
|
// used by this console.
|
||||||
|
func (cons *VgaTextConsole) DefaultColors() (fg uint8, bg uint8) {
|
||||||
|
return cons.defaultFg, cons.defaultBg
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fill sets the contents of the specified rectangular region to the requested
|
||||||
|
// color. Both x and y coordinates are 1-based.
|
||||||
|
func (cons *VgaTextConsole) Fill(x, y, width, height uint16, fg, bg uint8) {
|
||||||
|
var (
|
||||||
|
clr = (((uint16(bg) << 4) | uint16(fg)) << 8) | cons.clearChar
|
||||||
|
rowOffset, colOffset uint16
|
||||||
|
)
|
||||||
|
|
||||||
|
// clip rectangle
|
||||||
|
if x == 0 {
|
||||||
|
x = 1
|
||||||
|
} else if x >= cons.width {
|
||||||
|
x = cons.width
|
||||||
|
}
|
||||||
|
|
||||||
|
if y == 0 {
|
||||||
|
y = 1
|
||||||
|
} else if y >= cons.height {
|
||||||
|
y = cons.height
|
||||||
|
}
|
||||||
|
|
||||||
|
if x+width > cons.width {
|
||||||
|
width = cons.width - x
|
||||||
|
}
|
||||||
|
|
||||||
|
if y+height > cons.height {
|
||||||
|
height = cons.height - y
|
||||||
|
}
|
||||||
|
|
||||||
|
rowOffset = ((y - 1) * cons.width) + (x - 1)
|
||||||
|
for ; height > 0; height, rowOffset = height-1, rowOffset+cons.width {
|
||||||
|
for colOffset = rowOffset; colOffset < rowOffset+width; colOffset++ {
|
||||||
|
cons.fb[colOffset] = clr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scroll the console contents to the specified direction. The caller
|
||||||
|
// is responsible for updating (e.g. clear or replace) the contents of
|
||||||
|
// the region that was scrolled.
|
||||||
|
func (cons *VgaTextConsole) Scroll(dir ScrollDir, lines uint16) {
|
||||||
|
if lines == 0 || lines > cons.height {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var i uint16
|
||||||
|
offset := lines * cons.width
|
||||||
|
|
||||||
|
switch dir {
|
||||||
|
case ScrollDirUp:
|
||||||
|
for ; i < (cons.height-lines)*cons.width; i++ {
|
||||||
|
cons.fb[i] = cons.fb[i+offset]
|
||||||
|
}
|
||||||
|
case ScrollDirDown:
|
||||||
|
for i = cons.height*cons.width - 1; i >= lines*cons.width; i-- {
|
||||||
|
cons.fb[i] = cons.fb[i-offset]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write a char to the specified location. If fg or bg exceed the supported
|
||||||
|
// colors for this console, they will be set to their default value. Both x and
|
||||||
|
// y coordinates are 1-based
|
||||||
|
func (cons *VgaTextConsole) Write(ch byte, fg, bg uint8, x, y uint16) {
|
||||||
|
if x < 1 || x > cons.width || y < 1 || y > cons.height {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
maxColorIndex := uint8(len(cons.palette) - 1)
|
||||||
|
if fg > maxColorIndex {
|
||||||
|
fg = cons.defaultFg
|
||||||
|
}
|
||||||
|
if bg >= maxColorIndex {
|
||||||
|
bg = cons.defaultBg
|
||||||
|
}
|
||||||
|
|
||||||
|
cons.fb[((y-1)*cons.width)+(x-1)] = (((uint16(bg) << 4) | uint16(fg)) << 8) | uint16(ch)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Palette returns the active color palette for this console.
|
||||||
|
func (cons *VgaTextConsole) Palette() color.Palette {
|
||||||
|
return cons.palette
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetPaletteColor updates the color definition for the specified
|
||||||
|
// palette index. Passing a color index greated than the number of
|
||||||
|
// supported colors should be a no-op.
|
||||||
|
func (cons *VgaTextConsole) SetPaletteColor(index uint8, rgba color.RGBA) {
|
||||||
|
if index >= uint8(len(cons.palette)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
cons.palette[index] = rgba
|
||||||
|
|
||||||
|
// Load palette entry to the DAC. In this mode, colors are specified
|
||||||
|
// using 6-bits for each component; the RGB values need to be converted
|
||||||
|
// to the 0-63 range.
|
||||||
|
portWriteByteFn(0x3c8, index)
|
||||||
|
portWriteByteFn(0x3c9, rgba.R>>2)
|
||||||
|
portWriteByteFn(0x3c9, rgba.G>>2)
|
||||||
|
portWriteByteFn(0x3c9, rgba.B>>2)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DriverName returns the name of this driver.
|
||||||
|
func (cons *VgaTextConsole) DriverName() string {
|
||||||
|
return "vga_text_console"
|
||||||
|
}
|
||||||
|
|
||||||
|
// DriverVersion returns the version of this driver.
|
||||||
|
func (cons *VgaTextConsole) DriverVersion() (uint16, uint16, uint16) {
|
||||||
|
return 0, 0, 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// DriverInit initializes this driver.
|
||||||
|
func (cons *VgaTextConsole) DriverInit() *kernel.Error { return nil }
|
320
src/gopheros/device/video/console/vga_text_test.go
Normal file
320
src/gopheros/device/video/console/vga_text_test.go
Normal file
@ -0,0 +1,320 @@
|
|||||||
|
package console
|
||||||
|
|
||||||
|
import (
|
||||||
|
"gopheros/device"
|
||||||
|
"gopheros/kernel/cpu"
|
||||||
|
"image/color"
|
||||||
|
"testing"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestVgaTextDimensions(t *testing.T) {
|
||||||
|
cons := NewVgaTextConsole(80, 25, 0)
|
||||||
|
if w, h := cons.Dimensions(); w != 80 || h != 25 {
|
||||||
|
t.Fatalf("expected console dimensions to be 80x25; got %dx%d", w, h)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVgaTextDefaultColors(t *testing.T) {
|
||||||
|
cons := NewVgaTextConsole(80, 25, 0)
|
||||||
|
if fg, bg := cons.DefaultColors(); fg != 7 || bg != 0 {
|
||||||
|
t.Fatalf("expected console default colors to be fg:7, bg:0; got fg:%d, bg: %d", fg, bg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVgaTextFill(t *testing.T) {
|
||||||
|
specs := []struct {
|
||||||
|
// Input rect
|
||||||
|
x, y, w, h uint16
|
||||||
|
|
||||||
|
// Expected area to be cleared
|
||||||
|
expX, expY, expW, expH uint16
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
0, 0, 500, 500,
|
||||||
|
0, 0, 80, 25,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
10, 10, 11, 50,
|
||||||
|
10, 10, 11, 15,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
10, 10, 110, 1,
|
||||||
|
10, 10, 70, 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
70, 20, 20, 20,
|
||||||
|
70, 20, 10, 5,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
90, 25, 20, 20,
|
||||||
|
0, 0, 0, 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
12, 12, 5, 6,
|
||||||
|
12, 12, 5, 6,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
fb := make([]uint16, 80*25)
|
||||||
|
cons := NewVgaTextConsole(80, 25, uintptr(unsafe.Pointer(&fb[0])))
|
||||||
|
cw, ch := cons.Dimensions()
|
||||||
|
|
||||||
|
testPat := uint16(0xDEAD)
|
||||||
|
clearPat := uint16(cons.clearChar)
|
||||||
|
|
||||||
|
nextSpec:
|
||||||
|
for specIndex, spec := range specs {
|
||||||
|
// Fill FB with test pattern
|
||||||
|
for i := 0; i < len(fb); i++ {
|
||||||
|
fb[i] = testPat
|
||||||
|
}
|
||||||
|
|
||||||
|
cons.Fill(spec.x, spec.y, spec.w, spec.h, 0, 0)
|
||||||
|
|
||||||
|
var x, y uint16
|
||||||
|
for y = 1; y <= ch; y++ {
|
||||||
|
for x = 1; x <= cw; x++ {
|
||||||
|
fbVal := fb[((y-1)*cw)+(x-1)]
|
||||||
|
|
||||||
|
if x < spec.expX || y < spec.expY || x >= spec.expX+spec.expW || y >= spec.expY+spec.expH {
|
||||||
|
if fbVal != testPat {
|
||||||
|
t.Errorf("[spec %d] expected char at (%d, %d) not to be cleared", specIndex, x, y)
|
||||||
|
continue nextSpec
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if fbVal != clearPat {
|
||||||
|
t.Errorf("[spec %d] expected char at (%d, %d) to be cleared", specIndex, x, y)
|
||||||
|
continue nextSpec
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVgaTextScroll(t *testing.T) {
|
||||||
|
fb := make([]uint16, 80*25)
|
||||||
|
cons := NewVgaTextConsole(80, 25, uintptr(unsafe.Pointer(&fb[0])))
|
||||||
|
cw, ch := cons.Dimensions()
|
||||||
|
|
||||||
|
t.Run("up", func(t *testing.T) {
|
||||||
|
specs := []uint16{
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
2,
|
||||||
|
}
|
||||||
|
nextSpec:
|
||||||
|
for specIndex, lines := range specs {
|
||||||
|
// Fill buffer with test pattern
|
||||||
|
var x, y, index uint16
|
||||||
|
for y = 0; y < ch; y++ {
|
||||||
|
for x = 0; x < cw; x++ {
|
||||||
|
fb[index] = (y << 8) | x
|
||||||
|
index++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cons.Scroll(ScrollDirUp, lines)
|
||||||
|
|
||||||
|
// Check that rows 1 to (height - lines) have been scrolled up
|
||||||
|
index = 0
|
||||||
|
for y = 0; y < ch-lines; y++ {
|
||||||
|
for x = 0; x < cw; x++ {
|
||||||
|
expVal := ((y + lines) << 8) | x
|
||||||
|
if fb[index] != expVal {
|
||||||
|
t.Errorf("[spec %d] expected value at (%d, %d) to be %d; got %d", specIndex, x, y, expVal, fb[index])
|
||||||
|
continue nextSpec
|
||||||
|
}
|
||||||
|
index++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("down", func(t *testing.T) {
|
||||||
|
specs := []uint16{
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
2,
|
||||||
|
}
|
||||||
|
|
||||||
|
nextSpec:
|
||||||
|
for specIndex, lines := range specs {
|
||||||
|
// Fill buffer with test pattern
|
||||||
|
var x, y, index uint16
|
||||||
|
for y = 0; y < ch; y++ {
|
||||||
|
for x = 0; x < cw; x++ {
|
||||||
|
fb[index] = (y << 8) | x
|
||||||
|
index++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cons.Scroll(ScrollDirDown, lines)
|
||||||
|
|
||||||
|
// Check that rows lines to height have been scrolled down
|
||||||
|
index = lines * cw
|
||||||
|
for y = lines; y < ch-lines; y++ {
|
||||||
|
for x = 0; x < cw; x++ {
|
||||||
|
expVal := ((y - lines) << 8) | x
|
||||||
|
if fb[index] != expVal {
|
||||||
|
t.Errorf("[spec %d] expected value at (%d, %d) to be %d; got %d", specIndex, x, y, expVal, fb[index])
|
||||||
|
continue nextSpec
|
||||||
|
}
|
||||||
|
index++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVgaTextWrite(t *testing.T) {
|
||||||
|
fb := make([]uint16, 80*25)
|
||||||
|
cons := NewVgaTextConsole(80, 25, uintptr(unsafe.Pointer(&fb[0])))
|
||||||
|
defaultFg, defaultBg := cons.DefaultColors()
|
||||||
|
|
||||||
|
t.Run("off-screen", func(t *testing.T) {
|
||||||
|
specs := []struct {
|
||||||
|
x, y uint16
|
||||||
|
}{
|
||||||
|
{81, 26},
|
||||||
|
{90, 24},
|
||||||
|
{79, 30},
|
||||||
|
{100, 100},
|
||||||
|
}
|
||||||
|
|
||||||
|
nextSpec:
|
||||||
|
for specIndex, spec := range specs {
|
||||||
|
for i := 0; i < len(fb); i++ {
|
||||||
|
fb[i] = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
cons.Write('!', 1, 2, spec.x, spec.y)
|
||||||
|
|
||||||
|
for i := 0; i < len(fb); i++ {
|
||||||
|
if got := fb[i]; got != 0 {
|
||||||
|
t.Errorf("[spec %d] expected Write() with off-screen coords to be a no-op", specIndex)
|
||||||
|
continue nextSpec
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("success", func(t *testing.T) {
|
||||||
|
for i := 0; i < len(fb); i++ {
|
||||||
|
fb[i] = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
fg := uint8(1)
|
||||||
|
bg := uint8(2)
|
||||||
|
expAttr := uint16((uint16(bg) << 4) | uint16(fg))
|
||||||
|
|
||||||
|
cons.Write('!', fg, bg, 1, 1)
|
||||||
|
|
||||||
|
expVal := (expAttr << 8) | uint16('!')
|
||||||
|
if got := fb[0]; got != expVal {
|
||||||
|
t.Errorf("expected call to Write() to set fb[0] to %d; got %d", expVal, got)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("fg out of range", func(t *testing.T) {
|
||||||
|
for i := 0; i < len(fb); i++ {
|
||||||
|
fb[i] = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
fg := uint8(128)
|
||||||
|
bg := uint8(2)
|
||||||
|
expAttr := uint16((uint16(bg) << 4) | uint16(defaultFg))
|
||||||
|
|
||||||
|
cons.Write('!', fg, bg, 1, 1)
|
||||||
|
|
||||||
|
expVal := (expAttr << 8) | uint16('!')
|
||||||
|
if got := fb[0]; got != expVal {
|
||||||
|
t.Errorf("expected call to Write() to set fb[0] to %d; got %d", expVal, got)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("bg out of range", func(t *testing.T) {
|
||||||
|
for i := 0; i < len(fb); i++ {
|
||||||
|
fb[i] = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
fg := uint8(8)
|
||||||
|
bg := uint8(255)
|
||||||
|
expAttr := uint16((uint16(defaultBg) << 4) | uint16(fg))
|
||||||
|
|
||||||
|
cons.Write('!', fg, bg, 1, 1)
|
||||||
|
|
||||||
|
expVal := (expAttr << 8) | uint16('!')
|
||||||
|
if got := fb[0]; got != expVal {
|
||||||
|
t.Errorf("expected call to Write() to set fb[0] to %d; got %d", expVal, got)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVgaTextSetPaletteColor(t *testing.T) {
|
||||||
|
defer func() {
|
||||||
|
portWriteByteFn = cpu.PortWriteByte
|
||||||
|
}()
|
||||||
|
|
||||||
|
cons := NewVgaTextConsole(80, 25, 0)
|
||||||
|
|
||||||
|
t.Run("success", func(t *testing.T) {
|
||||||
|
expWrites := []struct {
|
||||||
|
port uint16
|
||||||
|
val uint8
|
||||||
|
}{
|
||||||
|
// Values will be normalized in the 0-31 range
|
||||||
|
{0x3c8, 1},
|
||||||
|
{0x3c9, 63},
|
||||||
|
{0x3c9, 31},
|
||||||
|
{0x3c9, 0},
|
||||||
|
}
|
||||||
|
|
||||||
|
writeCallCount := 0
|
||||||
|
portWriteByteFn = func(port uint16, val uint8) {
|
||||||
|
exp := expWrites[writeCallCount]
|
||||||
|
if port != exp.port || val != exp.val {
|
||||||
|
t.Errorf("[port write %d] expected port: 0x%x, val: %d; got port: 0x%x, val: %d", writeCallCount, exp.port, exp.val, port, val)
|
||||||
|
}
|
||||||
|
|
||||||
|
writeCallCount++
|
||||||
|
}
|
||||||
|
|
||||||
|
rgba := color.RGBA{R: 255, G: 127, B: 0}
|
||||||
|
cons.SetPaletteColor(1, rgba)
|
||||||
|
|
||||||
|
if got := cons.Palette()[1]; got != rgba {
|
||||||
|
t.Errorf("expected color at index 1 to be:\n%v\ngot:\n%v", rgba, got)
|
||||||
|
}
|
||||||
|
|
||||||
|
if writeCallCount != len(expWrites) {
|
||||||
|
t.Errorf("expected cpu.portWriteByty to be called %d times; got %d", len(expWrites), writeCallCount)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("color index out of range", func(t *testing.T) {
|
||||||
|
portWriteByteFn = func(_ uint16, _ uint8) {
|
||||||
|
t.Error("unexpected call to cpu.PortWriteByte")
|
||||||
|
}
|
||||||
|
|
||||||
|
rgba := color.RGBA{R: 255, G: 127, B: 0}
|
||||||
|
cons.SetPaletteColor(50, rgba)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVgaTextDriverInterface(t *testing.T) {
|
||||||
|
var dev device.Driver = NewVgaTextConsole(80, 25, 0)
|
||||||
|
|
||||||
|
if err := dev.DriverInit(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if dev.DriverName() == "" {
|
||||||
|
t.Fatal("DriverName() returned an empty string")
|
||||||
|
}
|
||||||
|
|
||||||
|
if major, minor, patch := dev.DriverVersion(); major+minor+patch == 0 {
|
||||||
|
t.Fatal("DriverVersion() returned an invalid version number")
|
||||||
|
}
|
||||||
|
}
|
@ -1,100 +0,0 @@
|
|||||||
package console
|
|
||||||
|
|
||||||
import (
|
|
||||||
"reflect"
|
|
||||||
"unsafe"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
clearColor = Black
|
|
||||||
clearChar = byte(' ')
|
|
||||||
)
|
|
||||||
|
|
||||||
// Ega implements an EGA-compatible text console. At the moment, it uses the
|
|
||||||
// ega console physical address as its outpucons. After implementing a memory
|
|
||||||
// allocator, each console will use its own framebuffer while the active console
|
|
||||||
// will periodically sync its internal buffer with the physical screen buffer.
|
|
||||||
type Ega struct {
|
|
||||||
width uint16
|
|
||||||
height uint16
|
|
||||||
|
|
||||||
fb []uint16
|
|
||||||
}
|
|
||||||
|
|
||||||
// Init sets up the console.
|
|
||||||
func (cons *Ega) Init(width, height uint16, fbPhysAddr uintptr) {
|
|
||||||
cons.width = width
|
|
||||||
cons.height = height
|
|
||||||
|
|
||||||
cons.fb = *(*[]uint16)(unsafe.Pointer(&reflect.SliceHeader{
|
|
||||||
Len: int(cons.width * cons.height),
|
|
||||||
Cap: int(cons.width * cons.height),
|
|
||||||
Data: fbPhysAddr,
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clear clears the specified rectangular region
|
|
||||||
func (cons *Ega) Clear(x, y, width, height uint16) {
|
|
||||||
var (
|
|
||||||
attr = uint16((clearColor << 4) | clearColor)
|
|
||||||
clr = attr | uint16(clearChar)
|
|
||||||
rowOffset, colOffset uint16
|
|
||||||
)
|
|
||||||
|
|
||||||
// clip rectangle
|
|
||||||
if x >= cons.width {
|
|
||||||
x = cons.width
|
|
||||||
}
|
|
||||||
if y >= cons.height {
|
|
||||||
y = cons.height
|
|
||||||
}
|
|
||||||
|
|
||||||
if x+width > cons.width {
|
|
||||||
width = cons.width - x
|
|
||||||
}
|
|
||||||
if y+height > cons.height {
|
|
||||||
height = cons.height - y
|
|
||||||
}
|
|
||||||
|
|
||||||
rowOffset = (y * cons.width) + x
|
|
||||||
for ; height > 0; height, rowOffset = height-1, rowOffset+cons.width {
|
|
||||||
for colOffset = rowOffset; colOffset < rowOffset+width; colOffset++ {
|
|
||||||
cons.fb[colOffset] = clr
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Dimensions returns the console width and height in characters.
|
|
||||||
func (cons *Ega) Dimensions() (uint16, uint16) {
|
|
||||||
return cons.width, cons.height
|
|
||||||
}
|
|
||||||
|
|
||||||
// Scroll a particular number of lines to the specified direction.
|
|
||||||
func (cons *Ega) Scroll(dir ScrollDir, lines uint16) {
|
|
||||||
if lines == 0 || lines > cons.height {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var i uint16
|
|
||||||
offset := lines * cons.width
|
|
||||||
|
|
||||||
switch dir {
|
|
||||||
case Up:
|
|
||||||
for ; i < (cons.height-lines)*cons.width; i++ {
|
|
||||||
cons.fb[i] = cons.fb[i+offset]
|
|
||||||
}
|
|
||||||
case Down:
|
|
||||||
for i = cons.height*cons.width - 1; i >= lines*cons.width; i-- {
|
|
||||||
cons.fb[i] = cons.fb[i-offset]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write a char to the specified location.
|
|
||||||
func (cons *Ega) Write(ch byte, attr Attr, x, y uint16) {
|
|
||||||
if x >= cons.width || y >= cons.height {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
cons.fb[(y*cons.width)+x] = (uint16(attr) << 8) | uint16(ch)
|
|
||||||
}
|
|
@ -1,212 +0,0 @@
|
|||||||
package console
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
"unsafe"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestEgaInit(t *testing.T) {
|
|
||||||
var cons Ega
|
|
||||||
cons.Init(80, 25, 0xB8000)
|
|
||||||
|
|
||||||
var expWidth uint16 = 80
|
|
||||||
var expHeight uint16 = 25
|
|
||||||
|
|
||||||
if w, h := cons.Dimensions(); w != expWidth || h != expHeight {
|
|
||||||
t.Fatalf("expected console dimensions after Init() to be (%d, %d); got (%d, %d)", expWidth, expHeight, w, h)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEgaClear(t *testing.T) {
|
|
||||||
specs := []struct {
|
|
||||||
// Input rect
|
|
||||||
x, y, w, h uint16
|
|
||||||
|
|
||||||
// Expected area to be cleared
|
|
||||||
expX, expY, expW, expH uint16
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
0, 0, 500, 500,
|
|
||||||
0, 0, 80, 25,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
10, 10, 11, 50,
|
|
||||||
10, 10, 11, 15,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
10, 10, 110, 1,
|
|
||||||
10, 10, 70, 1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
70, 20, 20, 20,
|
|
||||||
70, 20, 10, 5,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
90, 25, 20, 20,
|
|
||||||
0, 0, 0, 0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
12, 12, 5, 6,
|
|
||||||
12, 12, 5, 6,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
fb := make([]uint16, 80*25)
|
|
||||||
var cons Ega
|
|
||||||
cons.Init(80, 25, uintptr(unsafe.Pointer(&fb[0])))
|
|
||||||
|
|
||||||
testPat := uint16(0xDEAD)
|
|
||||||
clearPat := (uint16(clearColor) << 8) | uint16(clearChar)
|
|
||||||
|
|
||||||
nextSpec:
|
|
||||||
for specIndex, spec := range specs {
|
|
||||||
// Fill FB with test pattern
|
|
||||||
for i := 0; i < len(cons.fb); i++ {
|
|
||||||
fb[i] = testPat
|
|
||||||
}
|
|
||||||
|
|
||||||
cons.Clear(spec.x, spec.y, spec.w, spec.h)
|
|
||||||
|
|
||||||
var x, y uint16
|
|
||||||
for y = 0; y < cons.height; y++ {
|
|
||||||
for x = 0; x < cons.width; x++ {
|
|
||||||
fbVal := fb[(y*cons.width)+x]
|
|
||||||
|
|
||||||
if x < spec.expX || y < spec.expY || x >= spec.expX+spec.expW || y >= spec.expY+spec.expH {
|
|
||||||
if fbVal != testPat {
|
|
||||||
t.Errorf("[spec %d] expected char at (%d, %d) not to be cleared", specIndex, x, y)
|
|
||||||
continue nextSpec
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if fbVal != clearPat {
|
|
||||||
t.Errorf("[spec %d] expected char at (%d, %d) to be cleared", specIndex, x, y)
|
|
||||||
continue nextSpec
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEgaScrollUp(t *testing.T) {
|
|
||||||
specs := []uint16{
|
|
||||||
0,
|
|
||||||
1,
|
|
||||||
2,
|
|
||||||
}
|
|
||||||
|
|
||||||
fb := make([]uint16, 80*25)
|
|
||||||
var cons Ega
|
|
||||||
cons.Init(80, 25, uintptr(unsafe.Pointer(&fb[0])))
|
|
||||||
|
|
||||||
nextSpec:
|
|
||||||
for specIndex, lines := range specs {
|
|
||||||
// Fill buffer with test pattern
|
|
||||||
var x, y, index uint16
|
|
||||||
for y = 0; y < cons.height; y++ {
|
|
||||||
for x = 0; x < cons.width; x++ {
|
|
||||||
fb[index] = (y << 8) | x
|
|
||||||
index++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
cons.Scroll(Up, lines)
|
|
||||||
|
|
||||||
// Check that rows 1 to (height - lines) have been scrolled up
|
|
||||||
index = 0
|
|
||||||
for y = 0; y < cons.height-lines; y++ {
|
|
||||||
for x = 0; x < cons.width; x++ {
|
|
||||||
expVal := ((y + lines) << 8) | x
|
|
||||||
if fb[index] != expVal {
|
|
||||||
t.Errorf("[spec %d] expected value at (%d, %d) to be %d; got %d", specIndex, x, y, expVal, cons.fb[index])
|
|
||||||
continue nextSpec
|
|
||||||
}
|
|
||||||
index++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEgaScrollDown(t *testing.T) {
|
|
||||||
specs := []uint16{
|
|
||||||
0,
|
|
||||||
1,
|
|
||||||
2,
|
|
||||||
}
|
|
||||||
|
|
||||||
fb := make([]uint16, 80*25)
|
|
||||||
var cons Ega
|
|
||||||
cons.Init(80, 25, uintptr(unsafe.Pointer(&fb[0])))
|
|
||||||
|
|
||||||
nextSpec:
|
|
||||||
for specIndex, lines := range specs {
|
|
||||||
// Fill buffer with test pattern
|
|
||||||
var x, y, index uint16
|
|
||||||
for y = 0; y < cons.height; y++ {
|
|
||||||
for x = 0; x < cons.width; x++ {
|
|
||||||
fb[index] = (y << 8) | x
|
|
||||||
index++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
cons.Scroll(Down, lines)
|
|
||||||
|
|
||||||
// Check that rows lines to height have been scrolled down
|
|
||||||
index = lines * cons.width
|
|
||||||
for y = lines; y < cons.height-lines; y++ {
|
|
||||||
for x = 0; x < cons.width; x++ {
|
|
||||||
expVal := ((y - lines) << 8) | x
|
|
||||||
if fb[index] != expVal {
|
|
||||||
t.Errorf("[spec %d] expected value at (%d, %d) to be %d; got %d", specIndex, x, y, expVal, cons.fb[index])
|
|
||||||
continue nextSpec
|
|
||||||
}
|
|
||||||
index++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEgaWriteWithOffScreenCoords(t *testing.T) {
|
|
||||||
fb := make([]uint16, 80*25)
|
|
||||||
var cons Ega
|
|
||||||
cons.Init(80, 25, uintptr(unsafe.Pointer(&fb[0])))
|
|
||||||
|
|
||||||
specs := []struct {
|
|
||||||
x, y uint16
|
|
||||||
}{
|
|
||||||
{80, 25},
|
|
||||||
{90, 24},
|
|
||||||
{79, 30},
|
|
||||||
{100, 100},
|
|
||||||
}
|
|
||||||
|
|
||||||
nextSpec:
|
|
||||||
for specIndex, spec := range specs {
|
|
||||||
for i := 0; i < len(cons.fb); i++ {
|
|
||||||
fb[i] = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
cons.Write('!', Red, spec.x, spec.y)
|
|
||||||
|
|
||||||
for i := 0; i < len(cons.fb); i++ {
|
|
||||||
if got := fb[i]; got != 0 {
|
|
||||||
t.Errorf("[spec %d] expected Write() with off-screen coords to be a no-op", specIndex)
|
|
||||||
continue nextSpec
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEgaWrite(t *testing.T) {
|
|
||||||
fb := make([]uint16, 80*25)
|
|
||||||
var cons Ega
|
|
||||||
cons.Init(80, 25, uintptr(unsafe.Pointer(&fb[0])))
|
|
||||||
|
|
||||||
attr := (Black << 4) | Red
|
|
||||||
cons.Write('!', attr, 0, 0)
|
|
||||||
|
|
||||||
expVal := uint16(attr<<8) | uint16('!')
|
|
||||||
if got := fb[0]; got != expVal {
|
|
||||||
t.Errorf("expected call to Write() to set fb[0] to %d; got %d", expVal, got)
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
x
Reference in New Issue
Block a user