github.com/panekj/cli@v0.0.0-20230304125325-467dd2f3797e/cli/command/container/tty.go (about)

     1  package container
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"os"
     7  	gosignal "os/signal"
     8  	"runtime"
     9  	"time"
    10  
    11  	"github.com/docker/cli/cli/command"
    12  	"github.com/docker/docker/api/types"
    13  	"github.com/docker/docker/client"
    14  	"github.com/moby/sys/signal"
    15  	"github.com/sirupsen/logrus"
    16  )
    17  
    18  // resizeTtyTo resizes tty to specific height and width
    19  func resizeTtyTo(ctx context.Context, client client.ContainerAPIClient, id string, height, width uint, isExec bool) error {
    20  	if height == 0 && width == 0 {
    21  		return nil
    22  	}
    23  
    24  	options := types.ResizeOptions{
    25  		Height: height,
    26  		Width:  width,
    27  	}
    28  
    29  	var err error
    30  	if isExec {
    31  		err = client.ContainerExecResize(ctx, id, options)
    32  	} else {
    33  		err = client.ContainerResize(ctx, id, options)
    34  	}
    35  
    36  	if err != nil {
    37  		logrus.Debugf("Error resize: %s\r", err)
    38  	}
    39  	return err
    40  }
    41  
    42  // resizeTty is to resize the tty with cli out's tty size
    43  func resizeTty(ctx context.Context, cli command.Cli, id string, isExec bool) error {
    44  	height, width := cli.Out().GetTtySize()
    45  	return resizeTtyTo(ctx, cli.Client(), id, height, width, isExec)
    46  }
    47  
    48  // initTtySize is to init the tty's size to the same as the window, if there is an error, it will retry 10 times.
    49  func initTtySize(ctx context.Context, cli command.Cli, id string, isExec bool, resizeTtyFunc func(ctx context.Context, cli command.Cli, id string, isExec bool) error) {
    50  	rttyFunc := resizeTtyFunc
    51  	if rttyFunc == nil {
    52  		rttyFunc = resizeTty
    53  	}
    54  	if err := rttyFunc(ctx, cli, id, isExec); err != nil {
    55  		go func() {
    56  			var err error
    57  			for retry := 0; retry < 10; retry++ {
    58  				time.Sleep(time.Duration(retry+1) * 10 * time.Millisecond)
    59  				if err = rttyFunc(ctx, cli, id, isExec); err == nil {
    60  					break
    61  				}
    62  			}
    63  			if err != nil {
    64  				fmt.Fprintln(cli.Err(), "failed to resize tty, using default size")
    65  			}
    66  		}()
    67  	}
    68  }
    69  
    70  // MonitorTtySize updates the container tty size when the terminal tty changes size
    71  func MonitorTtySize(ctx context.Context, cli command.Cli, id string, isExec bool) error {
    72  	initTtySize(ctx, cli, id, isExec, resizeTty)
    73  	if runtime.GOOS == "windows" {
    74  		go func() {
    75  			prevH, prevW := cli.Out().GetTtySize()
    76  			for {
    77  				time.Sleep(time.Millisecond * 250)
    78  				h, w := cli.Out().GetTtySize()
    79  
    80  				if prevW != w || prevH != h {
    81  					resizeTty(ctx, cli, id, isExec)
    82  				}
    83  				prevH = h
    84  				prevW = w
    85  			}
    86  		}()
    87  	} else {
    88  		sigchan := make(chan os.Signal, 1)
    89  		gosignal.Notify(sigchan, signal.SIGWINCH)
    90  		go func() {
    91  			for range sigchan {
    92  				resizeTty(ctx, cli, id, isExec)
    93  			}
    94  		}()
    95  	}
    96  	return nil
    97  }