resize.go 4.2 KB

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