resize.go 3.6 KB

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