diff --git a/fb.go b/fb.go index 71a15b8..0879e8c 100644 --- a/fb.go +++ b/fb.go @@ -12,6 +12,11 @@ const ( fbioGetVScreenInfo = 0x4600 fbioBlank = 0x4611 fbBlankUnblank = 0 + + kdSetMode = 0x4b3a + kdText = 0x00 + kdGraphics = 0x01 + consolePath = "/dev/tty0" ) // The variable screen info is a flat run of 40 u32s, so it is read as an array @@ -28,6 +33,7 @@ const ( type framebuffer struct { file *os.File + tty *os.File mem []byte back []byte w int @@ -38,6 +44,22 @@ type framebuffer struct { bShift uint } +// The kernel console draws into the same framebuffer, including a blinking +// cursor and any keyboard echo, which fights every frame we write. Putting the +// active console into graphics mode stops it touching the framebuffer at all. +func takeConsole() (*os.File, error) { + tty, err := os.OpenFile(consolePath, os.O_RDWR, 0) + if err != nil { + return nil, err + } + if _, _, errno := unix.Syscall(unix.SYS_IOCTL, tty.Fd(), + kdSetMode, kdGraphics); errno != 0 { + tty.Close() + return nil, fmt.Errorf("console to graphics mode: %w", errno) + } + return tty, nil +} + func openFramebuffer(path string) (*framebuffer, error) { f, err := os.OpenFile(path, os.O_RDWR, 0) if err != nil { @@ -81,12 +103,21 @@ func openFramebuffer(path string) (*framebuffer, error) { return nil, fmt.Errorf("mmap: %w", err) } fb.back = make([]byte, fb.stride*fb.h) + + fb.tty, err = takeConsole() + if err != nil { + unix.Munmap(fb.mem) + f.Close() + return nil, err + } return fb, nil } func (fb *framebuffer) close() { fb.fill(rgb{}) fb.flush() + unix.Syscall(unix.SYS_IOCTL, fb.tty.Fd(), kdSetMode, kdText) + fb.tty.Close() unix.Munmap(fb.mem) fb.file.Close() }