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  }