VMDisplayTerminalViewController.swift 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  1. //
  2. // Copyright © 2022 osy. All rights reserved.
  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. import Foundation
  17. import SwiftTerm
  18. import SwiftUI
  19. @objc class VMDisplayTerminalViewController: VMDisplayViewController {
  20. private var terminalView: TerminalView!
  21. private var vmSerialPort: CSPort
  22. private var style: UTMConfigurationTerminal?
  23. private var keyboardDelta: CGFloat = 0
  24. required init(port: CSPort, style: UTMConfigurationTerminal? = nil) {
  25. self.vmSerialPort = port
  26. super.init(nibName: nil, bundle: nil)
  27. port.delegate = self
  28. self.style = style
  29. }
  30. required init?(coder: NSCoder) {
  31. return nil
  32. }
  33. override func loadView() {
  34. super.loadView()
  35. terminalView = TerminalView(frame: makeFrame (keyboardDelta: 0))
  36. terminalView.terminalDelegate = self
  37. view.insertSubview(terminalView, at: 0)
  38. styleTerminal()
  39. }
  40. override func viewDidLoad() {
  41. super.viewDidLoad()
  42. setupKeyboardMonitor()
  43. }
  44. override func enterLive() {
  45. super.enterLive()
  46. DispatchQueue.main.async {
  47. let terminalSize = CGSize(width: self.terminalView.getTerminal().cols, height: self.terminalView.getTerminal().rows)
  48. self.delegate.displayViewSize = terminalSize
  49. }
  50. }
  51. override func showKeyboard() {
  52. terminalView.becomeFirstResponder()
  53. }
  54. override func hideKeyboard() {
  55. _ = terminalView.resignFirstResponder()
  56. }
  57. }
  58. // MARK: - Layout terminal
  59. extension VMDisplayTerminalViewController {
  60. var useAutoLayout: Bool {
  61. get { true }
  62. }
  63. func makeFrame (keyboardDelta: CGFloat, _ fn: String = #function, _ ln: Int = #line) -> CGRect
  64. {
  65. if useAutoLayout {
  66. return CGRect.zero
  67. } else {
  68. return CGRect (x: view.safeAreaInsets.left,
  69. y: view.safeAreaInsets.top,
  70. width: view.frame.width - view.safeAreaInsets.left - view.safeAreaInsets.right,
  71. height: view.frame.height - view.safeAreaInsets.top - keyboardDelta)
  72. }
  73. }
  74. func setupKeyboardMonitor ()
  75. {
  76. if #available(iOS 15.0, *), useAutoLayout {
  77. terminalView.translatesAutoresizingMaskIntoConstraints = false
  78. terminalView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true
  79. terminalView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
  80. terminalView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
  81. terminalView.keyboardLayoutGuide.topAnchor.constraint(equalTo: terminalView.bottomAnchor).isActive = true
  82. } else {
  83. NotificationCenter.default.addObserver(
  84. self,
  85. selector: #selector(keyboardWillShow),
  86. name: UIWindow.keyboardWillShowNotification,
  87. object: nil)
  88. NotificationCenter.default.addObserver(
  89. self,
  90. selector: #selector(keyboardWillHide),
  91. name: UIWindow.keyboardWillHideNotification,
  92. object: nil)
  93. }
  94. }
  95. @objc private func keyboardWillShow(_ notification: NSNotification) {
  96. guard let keyboardValue = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue else { return }
  97. let keyboardScreenEndFrame = keyboardValue.cgRectValue
  98. let keyboardViewEndFrame = view.convert(keyboardScreenEndFrame, from: view.window)
  99. keyboardDelta = keyboardViewEndFrame.height
  100. terminalView.frame = makeFrame(keyboardDelta: keyboardViewEndFrame.height)
  101. }
  102. @objc private func keyboardWillHide(_ notification: NSNotification) {
  103. //let key = UIResponder.keyboardFrameBeginUserInfoKey
  104. keyboardDelta = 0
  105. terminalView.frame = makeFrame(keyboardDelta: 0)
  106. }
  107. }
  108. // MARK: - Style terminal
  109. extension VMDisplayTerminalViewController {
  110. private func styleTerminal() {
  111. guard let style = style else {
  112. return
  113. }
  114. let fontSize = style.fontSize
  115. let fontName = style.font.rawValue
  116. if fontName != "" {
  117. let orig = terminalView.font
  118. let new = UIFont(name: fontName, size: CGFloat(fontSize)) ?? orig
  119. terminalView.font = new
  120. } else {
  121. let orig = terminalView.font
  122. let new = UIFont(descriptor: orig.fontDescriptor, size: CGFloat(fontSize))
  123. terminalView.font = new
  124. }
  125. if let consoleTextColor = style.foregroundColor,
  126. let textColor = Color(hexString: consoleTextColor),
  127. let consoleBackgroundColor = style.backgroundColor,
  128. let backgroundColor = Color(hexString: consoleBackgroundColor) {
  129. terminalView.nativeForegroundColor = UIColor(textColor)
  130. terminalView.nativeBackgroundColor = UIColor(backgroundColor)
  131. }
  132. }
  133. }
  134. // MARK: - TerminalViewDelegate
  135. extension VMDisplayTerminalViewController: TerminalViewDelegate {
  136. func sizeChanged(source: TerminalView, newCols: Int, newRows: Int) {
  137. delegate.displayViewSize = CGSize(width: newCols, height: newRows)
  138. }
  139. func setTerminalTitle(source: TerminalView, title: String) {
  140. }
  141. func requestOpenLink(source: TerminalView, link: String, params: [String : String]) {
  142. }
  143. func hostCurrentDirectoryUpdate(source: TerminalView, directory: String?) {
  144. }
  145. func send(source: TerminalView, data: ArraySlice<UInt8>) {
  146. delegate.displayDidAssertUserInteraction()
  147. vmSerialPort.write(Data(data))
  148. }
  149. func scrolled(source: TerminalView, position: Double) {
  150. delegate.displayDidAssertUserInteraction()
  151. }
  152. func bell(source: TerminalView) {
  153. }
  154. }
  155. // MARK: - CSPortDelegate
  156. extension VMDisplayTerminalViewController: CSPortDelegate {
  157. func portDidDisconect(_ port: CSPort) {
  158. }
  159. func port(_ port: CSPort, didError error: String) {
  160. showAlert(error, actions: nil)
  161. }
  162. func port(_ port: CSPort, didRecieveData data: Data) {
  163. if let terminalView = terminalView {
  164. let arr = [UInt8](data)[...]
  165. DispatchQueue.main.async {
  166. terminalView.feed(byteArray: arr)
  167. }
  168. }
  169. }
  170. }