mirror of
				https://github.com/taigrr/gopher-os
				synced 2025-01-18 04:43:13 -08:00 
			
		
		
		
	Merge pull request #3 from achilleasa/implement-simple-terminal
Implement simple terminal
This commit is contained in:
		
						commit
						08142d90f6
					
				
							
								
								
									
										18
									
								
								kernel/driver/tty/tty.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								kernel/driver/tty/tty.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,18 @@ | |||||||
|  | package tty | ||||||
|  | 
 | ||||||
|  | import "io" | ||||||
|  | 
 | ||||||
|  | // Tty is implemented by objects that can register themselves as ttys. | ||||||
|  | type Tty interface { | ||||||
|  | 	io.Writer | ||||||
|  | 
 | ||||||
|  | 	// Position returns the current cursor position (x, y). | ||||||
|  | 	Position() (uint16, uint16) | ||||||
|  | 
 | ||||||
|  | 	// SetPosition sets the current cursor position to (x,y). Console implementations | ||||||
|  | 	// must clip the provided cursor position if it exceeds the console dimensions. | ||||||
|  | 	SetPosition(x, y uint16) | ||||||
|  | 
 | ||||||
|  | 	// Clear clears the terminal. | ||||||
|  | 	Clear() | ||||||
|  | } | ||||||
							
								
								
									
										118
									
								
								kernel/driver/tty/vt.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										118
									
								
								kernel/driver/tty/vt.go
									
									
									
									
									
										Normal 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) | ||||||
|  | } | ||||||
							
								
								
									
										70
									
								
								kernel/driver/tty/vt_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								kernel/driver/tty/vt_test.go
									
									
									
									
									
										Normal 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) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
| @ -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 | // Clear clears the specified rectangular region | ||||||
| func (cons *Vga) Clear(x, y, width, height uint16) { | func (cons *Vga) Clear(x, y, width, height uint16) { | ||||||
| 	var ( | 	var ( | ||||||
|  | |||||||
| @ -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) | 		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") | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user