Implements full cleanup on program exit (normal, Ctrl+C, crash): - EXIT PROCEDURE auto-execution (reverse module order) - AtExit callback registry (LIFO order) - All WorkAreas auto-close (child before parent) - Terminal restore (raw → normal) on signal/exit - Static variables clear - Signal handlers (SIGINT, SIGTERM) for clean shutdown - shutdown.go: Harbour hb_vmQuit() 25-step sequence adapted for Five - vm.go: Run() now calls Shutdown() via defer - rawtty.go: terminal restore registered with shutdown system Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
138 lines
3.1 KiB
Go
138 lines
3.1 KiB
Go
// Copyright (c) 2026 Charles KWON OhJun (charleskwonohjun@gmail.com)
|
|
// All rights reserved.
|
|
|
|
// Raw terminal for Five. Uses /dev/tty opened fresh each ReadKey call.
|
|
package hbrtl
|
|
|
|
import (
|
|
"five/hbrt"
|
|
"os"
|
|
"syscall"
|
|
"unsafe"
|
|
)
|
|
|
|
var (
|
|
origTermios syscall.Termios
|
|
rawModeOn bool
|
|
stdinFd int
|
|
)
|
|
|
|
// InitRawTerminal sets raw mode on stdin.
|
|
func InitRawTerminal() {
|
|
if rawModeOn {
|
|
return
|
|
}
|
|
stdinFd = int(os.Stdin.Fd())
|
|
|
|
// Save original
|
|
_, _, e := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(stdinFd), 0x5401, uintptr(unsafe.Pointer(&origTermios)), 0, 0, 0)
|
|
if e != 0 {
|
|
return
|
|
}
|
|
|
|
// Set raw on stdin
|
|
raw := origTermios
|
|
raw.Lflag &^= syscall.ICANON | syscall.ECHO | syscall.ISIG
|
|
raw.Oflag &^= syscall.OPOST
|
|
raw.Cc[syscall.VMIN] = 1
|
|
raw.Cc[syscall.VTIME] = 0
|
|
syscall.Syscall6(syscall.SYS_IOCTL, uintptr(stdinFd), 0x5402, uintptr(unsafe.Pointer(&raw)), 0, 0, 0)
|
|
|
|
rawModeOn = true
|
|
|
|
// Register terminal restore with VM shutdown system
|
|
hbrt.SetTerminalRestore(RestoreTerminal)
|
|
}
|
|
|
|
// RestoreTerminal restores original terminal.
|
|
func RestoreTerminal() {
|
|
if !rawModeOn {
|
|
return
|
|
}
|
|
syscall.Syscall6(syscall.SYS_IOCTL, uintptr(stdinFd), 0x5402, uintptr(unsafe.Pointer(&origTermios)), 0, 0, 0)
|
|
rawModeOn = false
|
|
}
|
|
|
|
// IsRawMode returns true if raw mode is active.
|
|
func IsRawMode() bool {
|
|
return rawModeOn
|
|
}
|
|
|
|
// ReadKey reads one key. Opens /dev/tty fresh each time to avoid buffered data.
|
|
func ReadKey() int {
|
|
if !rawModeOn {
|
|
InitRawTerminal()
|
|
}
|
|
os.Stdout.Sync()
|
|
|
|
// Open /dev/tty fresh — no stale data possible
|
|
fd, err := syscall.Open("/dev/tty", syscall.O_RDONLY, 0)
|
|
if err != nil {
|
|
return 27 // ESC
|
|
}
|
|
defer syscall.Close(fd)
|
|
|
|
// Set this fd to raw too
|
|
var t syscall.Termios
|
|
syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), 0x5401, uintptr(unsafe.Pointer(&t)), 0, 0, 0)
|
|
t.Lflag &^= syscall.ICANON | syscall.ECHO | syscall.ISIG
|
|
t.Cc[syscall.VMIN] = 1
|
|
t.Cc[syscall.VTIME] = 0
|
|
syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), 0x5402, uintptr(unsafe.Pointer(&t)), 0, 0, 0)
|
|
|
|
// Flush input buffer (TCFLSH = 0x540B, TCIFLUSH = 0)
|
|
syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), 0x540B, 0)
|
|
|
|
buf := make([]byte, 1)
|
|
n, e := syscall.Read(fd, buf)
|
|
if e != nil || n == 0 {
|
|
return 27
|
|
}
|
|
b := buf[0]
|
|
|
|
if b == 27 { // ESC — bare ESC or sequence?
|
|
// Short timeout to check for sequence
|
|
var t2 syscall.Termios
|
|
syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), 0x5401, uintptr(unsafe.Pointer(&t2)), 0, 0, 0)
|
|
t2.Cc[syscall.VMIN] = 0
|
|
t2.Cc[syscall.VTIME] = 1 // 100ms
|
|
syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), 0x5402, uintptr(unsafe.Pointer(&t2)), 0, 0, 0)
|
|
|
|
n2, _ := syscall.Read(fd, buf)
|
|
if n2 == 0 {
|
|
return 27 // bare ESC
|
|
}
|
|
if buf[0] == '[' {
|
|
n3, _ := syscall.Read(fd, buf)
|
|
if n3 == 0 {
|
|
return 27
|
|
}
|
|
switch buf[0] {
|
|
case 'A':
|
|
return 'A'
|
|
case 'B':
|
|
return 'B'
|
|
case 'C':
|
|
return 'C'
|
|
case 'D':
|
|
return 'D'
|
|
case '5':
|
|
syscall.Read(fd, buf)
|
|
return '5'
|
|
case '6':
|
|
syscall.Read(fd, buf)
|
|
return '6'
|
|
case 'H':
|
|
return 'H'
|
|
case 'F':
|
|
return 'F'
|
|
default:
|
|
return 27
|
|
}
|
|
}
|
|
return 27
|
|
}
|
|
|
|
return int(b)
|
|
}
|