TUI Library written in Go
Files | Log | Commits | Refs | README
Size: 7656 bytes
package main import ( "bufio" "fmt" "log" "os" "os/user" "runtime" "strings" "syscall" tb "tinybox-example/tinybox" "time" ) type SystemInfo struct { Hostname string Username string OS string Arch string CPUs int Uptime string LoadAvg string MemoryTotal uint64 MemoryFree uint64 DiskUsage string CurrentTime string } func getSystemInfo() *SystemInfo { info := &SystemInfo{} if hostname, err := os.Hostname(); err == nil { info.Hostname = hostname } if currentUser, err := user.Current(); err == nil { info.Username = currentUser.Username } info.OS = runtime.GOOS info.Arch = runtime.GOARCH info.CPUs = runtime.NumCPU() info.CurrentTime = time.Now().Format("2006-01-02 15:04:05") if data, err := os.ReadFile("/proc/uptime"); err == nil { parts := strings.Fields(string(data)) if len(parts) > 0 { info.Uptime = parts[0] + " seconds" } } if data, err := os.ReadFile("/proc/loadavg"); err == nil { parts := strings.Fields(string(data)) if len(parts) >= 3 { info.LoadAvg = fmt.Sprintf("%s %s %s", parts[0], parts[1], parts[2]) } } if file, err := os.Open("/proc/meminfo"); err == nil { defer file.Close() scanner := bufio.NewScanner(file) for scanner.Scan() { line := scanner.Text() if strings.HasPrefix(line, "MemTotal:") { fmt.Sscanf(line, "MemTotal: %d kB", &info.MemoryTotal) info.MemoryTotal *= 1024 } else if strings.HasPrefix(line, "MemAvailable:") { fmt.Sscanf(line, "MemAvailable: %d kB", &info.MemoryFree) info.MemoryFree *= 1024 } } } var stat syscall.Statfs_t if err := syscall.Statfs("/", &stat); err == nil { total := stat.Blocks * uint64(stat.Bsize) free := stat.Bavail * uint64(stat.Bsize) used := total - free usedPercent := float64(used) / float64(total) * 100 info.DiskUsage = fmt.Sprintf("%.1f%% used (%s / %s)", usedPercent, formatBytes(used), formatBytes(total)) } return info } func formatBytes(bytes uint64) string { const unit = 1024 if bytes < unit { return fmt.Sprintf("%d B", bytes) } div, exp := int64(unit), 0 for n := bytes / unit; n >= unit; n /= unit { div *= unit exp++ } return fmt.Sprintf("%.1f %cB", float64(bytes)/float64(div), "KMGTPE"[exp]) } func drawHeader(width int) { tb.SetColor(11, 4) tb.Fill(0, 0, width, 3, ' ') tb.SetAttr(true, false, false, false) tb.SetColor(15, 4) tb.DrawTextCenter(1, "Demo using tinybox", 15, 4) tb.ResetAttr() } func drawSystemInfo(info *SystemInfo, startY int) int { y := startY tb.Box(1, y, 50, 12) tb.SetAttr(true, false, true, false) tb.SetColor(14, 0) tb.PrintAt(3, y+1, "SYSTEM INFORMATION") tb.ResetAttr() y += 3 fields := []struct{ label, value string }{ {"Hostname:", info.Hostname}, {"User:", info.Username}, {"OS/Arch:", fmt.Sprintf("%s/%s", info.OS, info.Arch)}, {"CPUs:", fmt.Sprintf("%d", info.CPUs)}, {"Uptime:", info.Uptime}, {"Load Avg:", info.LoadAvg}, {"Time:", info.CurrentTime}, } for _, field := range fields { if field.value != "" { tb.SetColor(10, 0) tb.PrintAt(3, y, field.label) tb.SetColor(15, 0) tb.PrintAt(15, y, field.value) y++ } } return y + 2 } func drawMemoryUsage(info *SystemInfo, x, y int) { if info.MemoryTotal == 0 { return } tb.Box(x, y, 35, 6) tb.SetAttr(true, false, false, false) tb.SetColor(13, 0) tb.PrintAt(x+2, y+1, "MEMORY USAGE") tb.ResetAttr() memUsed := info.MemoryTotal - info.MemoryFree memPercent := float64(memUsed) / float64(info.MemoryTotal) * 100 tb.SetColor(15, 0) tb.PrintAt(x+2, y+3, fmt.Sprintf("Used: %s (%.1f%%)", formatBytes(memUsed), memPercent)) tb.PrintAt(x+2, y+4, fmt.Sprintf("Total: %s", formatBytes(info.MemoryTotal))) barWidth := 25 usedWidth := int(float64(barWidth) * memPercent / 100.0) tb.SetColor(2, 0) for i := 0; i < usedWidth; i++ { tb.PrintAt(x+2+i, y+5, "█") } tb.SetColor(8, 0) for i := usedWidth; i < barWidth; i++ { tb.PrintAt(x+2+i, y+5, "░") } } func drawDiskUsage(info *SystemInfo, x, y int) { if info.DiskUsage == "" { return } tb.Box(x, y, 35, 4) tb.SetAttr(true, false, false, false) tb.SetColor(12, 0) tb.PrintAt(x+2, y+1, "DISK USAGE (/)") tb.ResetAttr() tb.SetColor(15, 0) tb.PrintAt(x+2, y+2, info.DiskUsage) } func drawControls(y, width int) int { tb.HLine(0, y, width, '─') y++ tb.SetColor(8, 0) controls := []string{ "R - Refresh", "M - Mouse", "S - Suspend", "B - Bell", "Q - Quit", } x := 2 for i, ctrl := range controls { if i > 0 { tb.PrintAt(x, y, " | ") x += 3 } tb.PrintAt(x, y, ctrl) x += len(ctrl) } return y + 2 } func drawStatusLine(message string, width, height int) { tb.SetColor(0, 7) tb.Fill(0, height-1, width, 1, ' ') tb.PrintAt(1, height-1, fmt.Sprintf(" Status: %s", message)) } func main() { if err := tb.Init(); err != nil { log.Fatal(err) } defer tb.Close() info := getSystemInfo() mouseEnabled := false status := "Ready - Press keys to interact" for { tb.Clear() width, height := tb.Size() drawHeader(width) y := drawSystemInfo(info, 4) drawMemoryUsage(info, 55, 4) drawDiskUsage(info, 55, 11) tb.SaveBuffer() tb.SetColor(6, 0) tb.PrintAt(3, y, "Buffer saved - demonstrating save/restore") tb.Present() time.Sleep(500 * time.Millisecond) tb.RestoreBuffer() controlY := drawControls(height-4, width) tb.SetColor(11, 0) mouseStatus := "OFF" if mouseEnabled { mouseStatus = "ON" } tb.PrintAt(2, controlY, fmt.Sprintf("Mouse: %s | Terminal: %dx%d | Raw Mode: %t", mouseStatus, width, height, tb.IsRawMode())) drawStatusLine(status, width, height) tb.Present() event, err := tb.PollEventTimeout(time.Second) if err != nil && err.Error() == "timeout" { info.CurrentTime = time.Now().Format("2006-01-02 15:04:05") status = "Clock updated" continue } else if err != nil { status = "Error: " + err.Error() continue } switch event.Type { case tb.EventKey: switch event.Ch { case 'q', 'Q': return case 'r', 'R': info = getSystemInfo() status = "System information refreshed" case 'm', 'M': if mouseEnabled { tb.DisableMouseFunc() mouseEnabled = false status = "Mouse disabled" } else { tb.EnableMouseFunc() mouseEnabled = true status = "Mouse enabled - try clicking!" } case 's', 'S': status = "Suspending... (Ctrl+Z will work too)" tb.Present() time.Sleep(500 * time.Millisecond) tb.Suspend() status = "Resumed from suspension" case 'b', 'B': tb.Bell() status = "Bell rung" default: if event.Ch != 0 { status = fmt.Sprintf("Key pressed: '%c' (code: %d)", event.Ch, event.Ch) } } switch event.Key { case tb.KeyCtrlC: return case tb.KeyArrowUp: tb.Scroll(-1) status = "Scrolled up" case tb.KeyArrowDown: tb.Scroll(1) status = "Scrolled down" case tb.KeyArrowLeft, tb.KeyArrowRight: status = fmt.Sprintf("Arrow key: %v", event.Key) case tb.KeyEnter: x, y := tb.GetCursorPos() status = fmt.Sprintf("Cursor position: %d,%d", x, y) } case tb.EventMouse: buttonName := map[tb.MouseButton]string{ tb.MouseLeft: "LEFT", tb.MouseMiddle: "MIDDLE", tb.MouseRight: "RIGHT", tb.MouseWheelUp: "WHEEL_UP", tb.MouseWheelDown: "WHEEL_DOWN", }[event.Button] if buttonName == "" { buttonName = "UNKNOWN" } status = fmt.Sprintf("Mouse %s at (%d,%d)", buttonName, event.X, event.Y) case tb.EventResize: status = fmt.Sprintf("Terminal resized to %dx%d", width, height) case tb.EventPaste: status = "Paste event detected" } } }