github.com/sealerio/sealer@v0.11.1-0.20240507115618-f4f89c5853ae/pkg/debug/resize.go (about) 1 /* 2 Copyright 2016 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package debug 18 19 import ( 20 "fmt" 21 22 "github.com/moby/term" 23 "k8s.io/apimachinery/pkg/util/runtime" 24 "k8s.io/client-go/tools/remotecommand" 25 ) 26 27 // GetSize returns the current size of the user's terminal. If it isn't a terminal, 28 // nil is returned. 29 func (t TTY) GetSize() *remotecommand.TerminalSize { 30 outFd, isTerminal := term.GetFdInfo(t.Out) 31 if !isTerminal { 32 return nil 33 } 34 return GetSize(outFd) 35 } 36 37 // GetSize returns the current size of the terminal associated with fd. 38 func GetSize(fd uintptr) *remotecommand.TerminalSize { 39 size, err := term.GetWinsize(fd) 40 if err != nil { 41 runtime.HandleError(fmt.Errorf("unable to get terminal size: %v", err)) 42 return nil 43 } 44 45 return &remotecommand.TerminalSize{Width: size.Width, Height: size.Height} 46 } 47 48 // MonitorSize monitors the terminal's size. It returns a TerminalSizeQueue primed with 49 // initialSizes, or nil if there's no TTY present. 50 func (t *TTY) MonitorSize(initialSizes ...*remotecommand.TerminalSize) remotecommand.TerminalSizeQueue { 51 outFd, isTerminal := term.GetFdInfo(t.Out) 52 if !isTerminal { 53 return nil 54 } 55 56 t.sizeQueue = &sizeQueue{ 57 t: *t, 58 // make it buffered, so we can send the initial terminal sizes without blocking, prior to starting 59 // the streaming below 60 resizeChan: make(chan remotecommand.TerminalSize, len(initialSizes)), 61 stopResizing: make(chan struct{}), 62 } 63 64 t.sizeQueue.monitorSize(outFd, initialSizes...) 65 66 return t.sizeQueue 67 } 68 69 // sizeQueue implements remotecommand.TerminalSizeQueue 70 type sizeQueue struct { 71 t TTY 72 // resizeChan receives a Size each time the user's terminal is resized. 73 resizeChan chan remotecommand.TerminalSize 74 stopResizing chan struct{} 75 } 76 77 // make sure sizeQueue implements the resize.TerminalSizeQueue interface 78 var _ remotecommand.TerminalSizeQueue = &sizeQueue{} 79 80 // monitorSize primes resizeChan with initialSizes and then monitors for resize events. With each 81 // new event, it sends the current terminal size to resizeChan. 82 func (s *sizeQueue) monitorSize(outFd uintptr, initialSizes ...*remotecommand.TerminalSize) { 83 // send the initial sizes 84 for i := range initialSizes { 85 if initialSizes[i] != nil { 86 s.resizeChan <- *initialSizes[i] 87 } 88 } 89 90 resizeEvents := make(chan remotecommand.TerminalSize, 1) 91 92 monitorResizeEvents(outFd, resizeEvents, s.stopResizing) 93 94 // listen for resize events in the background 95 go func() { 96 defer runtime.HandleCrash() 97 98 for { 99 select { 100 case size, ok := <-resizeEvents: 101 if !ok { 102 return 103 } 104 105 select { 106 // try to send the size to resizeChan, but don't block 107 case s.resizeChan <- size: 108 // send successful 109 default: 110 // unable to send / no-op 111 } 112 case <-s.stopResizing: 113 return 114 } 115 } 116 }() 117 } 118 119 // Next returns the new terminal size after the terminal has been resized. It returns nil when 120 // monitoring has been stopped. 121 func (s *sizeQueue) Next() *remotecommand.TerminalSize { 122 size, ok := <-s.resizeChan 123 if !ok { 124 return nil 125 } 126 return &size 127 } 128 129 // stop the background goroutine that is monitoring for terminal resizes. 130 func (s *sizeQueue) stop() { 131 close(s.stopResizing) 132 }