github.com/mvdan/u-root-coreutils@v0.0.0-20230122170626-c2eef2898555/pkg/boot/menu/menu_term.go (about) 1 // Copyright 2021 the u-root Authors. All rights reserved 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package menu 6 7 import ( 8 "fmt" 9 "io" 10 "log" 11 "os" 12 "syscall" 13 "time" 14 15 "golang.org/x/term" 16 ) 17 18 type MenuTerminal interface { 19 io.Writer 20 ReadLine() (string, error) 21 SetPrompt(string) 22 SetEntryCallback(func()) 23 SetTimeout(time.Duration) error 24 Close() error 25 } 26 27 var _ = MenuTerminal(&xterm{}) 28 29 // xterm is a wrapper for term.Terminal following the MenuTerminal interface 30 type xterm struct { 31 term.Terminal 32 // Save variables needed to close the xterm 33 fileInput *os.File 34 oldState *term.State 35 } 36 37 // NewTerminal opens an xTerminal using the given file input. 38 // Note that the xterm is in raw mode. Write \r\n whenever you would 39 // write a \n. When testing in qemu, it might look fine because 40 // there might be another tty cooking the newlines. In for 41 // example minicom, the behavior is different. And you would 42 // see something like: 43 // 44 // Select a boot option to edit: 45 // > 46 // 47 // Instead of: 48 // 49 // Select a boot option to edit: 50 // > 51 func NewTerminal(f *os.File) *xterm { 52 oldState, err := term.MakeRaw(int(f.Fd())) 53 if err != nil { 54 log.Printf("BUG: Please report: We cannot actually let you choose from menu (MakeRaw failed): %v", err) 55 } 56 57 if err = syscall.SetNonblock(int(f.Fd()), true); err != nil { 58 log.Printf("BUG: Error setting Fd %d to nonblocking: %v", f.Fd(), err) 59 } 60 61 return &xterm{ 62 *term.NewTerminal(f, ""), 63 f, 64 oldState, 65 } 66 } 67 68 func (t *xterm) Close() error { 69 if t.oldState == nil { 70 return fmt.Errorf("cannot restore terminal state to nil") 71 } 72 return term.Restore(int(t.fileInput.Fd()), t.oldState) 73 } 74 75 func (t *xterm) SetTimeout(dur time.Duration) error { 76 return t.fileInput.SetDeadline(time.Now().Add(dur)) 77 } 78 79 // Sets the timeout for the file and adds a timeout refresh on user entry 80 func (t *xterm) SetEntryCallback(f func()) { 81 t.AutoCompleteCallback = func(line string, pos int, key rune) (string, int, bool) { 82 f() 83 return "", 0, false 84 } 85 }