2
0

VMDisplayTerminalViewController.swift 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246
  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. var vmSerialPort: CSPort {
  22. willSet {
  23. vmSerialPort.delegate = nil
  24. newValue.delegate = self
  25. terminalView.getTerminal().resetToInitialState()
  26. terminalView.getTerminal().softReset()
  27. }
  28. }
  29. private var style: UTMConfigurationTerminal?
  30. private var keyboardDelta: CGFloat = 0
  31. required init(port: CSPort, style: UTMConfigurationTerminal? = nil) {
  32. self.vmSerialPort = port
  33. super.init(nibName: nil, bundle: nil)
  34. port.delegate = self
  35. self.style = style
  36. }
  37. required init?(coder: NSCoder) {
  38. return nil
  39. }
  40. override func loadView() {
  41. super.loadView()
  42. terminalView = TerminalView(frame: makeFrame (keyboardDelta: 0))
  43. terminalView.terminalDelegate = self
  44. view.insertSubview(terminalView, at: 0)
  45. styleTerminal()
  46. }
  47. override func viewWillAppear(_ animated: Bool) {
  48. super.viewWillAppear(animated)
  49. setupKeyboardMonitor()
  50. }
  51. override func viewWillDisappear(_ animated: Bool) {
  52. super.viewWillDisappear(animated)
  53. cleanupKeyboardMonitor()
  54. }
  55. override func enterLive() {
  56. super.enterLive()
  57. DispatchQueue.main.async {
  58. let terminalSize = CGSize(width: self.terminalView.getTerminal().cols, height: self.terminalView.getTerminal().rows)
  59. self.delegate.displayViewSize = terminalSize
  60. }
  61. }
  62. override func showKeyboard() {
  63. super.showKeyboard()
  64. _ = terminalView.becomeFirstResponder()
  65. }
  66. override func hideKeyboard() {
  67. super.hideKeyboard()
  68. _ = terminalView.resignFirstResponder()
  69. }
  70. }
  71. // MARK: - Layout terminal
  72. extension VMDisplayTerminalViewController {
  73. var useAutoLayout: Bool {
  74. get { true }
  75. }
  76. // This prevents curved edge from cutting off the content
  77. var additionalTopPadding: CGFloat {
  78. if UIDevice.current.userInterfaceIdiom == .pad {
  79. let scenes = UIApplication.shared.connectedScenes
  80. let windowScene = scenes.first as? UIWindowScene
  81. guard let window = windowScene?.windows.first else { return 0 }
  82. return window.safeAreaInsets.bottom
  83. } else {
  84. return 0
  85. }
  86. }
  87. func makeFrame (keyboardDelta: CGFloat, _ fn: String = #function, _ ln: Int = #line) -> CGRect
  88. {
  89. if useAutoLayout {
  90. return CGRect.zero
  91. } else {
  92. return CGRect (x: view.safeAreaInsets.left,
  93. y: view.safeAreaInsets.top + additionalTopPadding,
  94. width: view.frame.width - view.safeAreaInsets.left - view.safeAreaInsets.right,
  95. height: view.frame.height - view.safeAreaInsets.top - keyboardDelta)
  96. }
  97. }
  98. func setupKeyboardMonitor ()
  99. {
  100. if #available(iOS 15.0, *), useAutoLayout {
  101. #if os(visionOS)
  102. let inputAccessoryHeight: CGFloat = 0
  103. #else
  104. let inputAccessoryHeight = terminalView.inputAccessoryView?.frame.height ?? 0
  105. #endif
  106. terminalView.translatesAutoresizingMaskIntoConstraints = false
  107. terminalView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: additionalTopPadding).isActive = true
  108. terminalView.leftAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leftAnchor).isActive = true
  109. terminalView.rightAnchor.constraint(equalTo: view.safeAreaLayoutGuide.rightAnchor).isActive = true
  110. terminalView.bottomAnchor.constraint(equalTo: view.keyboardLayoutGuide.topAnchor, constant: -inputAccessoryHeight).isActive = true
  111. } else {
  112. NotificationCenter.default.addObserver(
  113. self,
  114. selector: #selector(keyboardWillShow),
  115. name: UIWindow.keyboardWillShowNotification,
  116. object: nil)
  117. NotificationCenter.default.addObserver(
  118. self,
  119. selector: #selector(keyboardWillHide),
  120. name: UIWindow.keyboardWillHideNotification,
  121. object: nil)
  122. }
  123. }
  124. func cleanupKeyboardMonitor() {
  125. if #unavailable(iOS 15) {
  126. NotificationCenter.default.removeObserver(self, name: UIWindow.keyboardWillShowNotification, object: nil)
  127. NotificationCenter.default.removeObserver(self, name: UIWindow.keyboardWillHideNotification, object: nil)
  128. }
  129. }
  130. @objc private func keyboardWillShow(_ notification: NSNotification) {
  131. guard let keyboardValue = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue else { return }
  132. let keyboardScreenEndFrame = keyboardValue.cgRectValue
  133. let keyboardViewEndFrame = view.convert(keyboardScreenEndFrame, from: view.window)
  134. keyboardDelta = keyboardViewEndFrame.height
  135. terminalView.frame = makeFrame(keyboardDelta: keyboardViewEndFrame.height)
  136. }
  137. @objc private func keyboardWillHide(_ notification: NSNotification) {
  138. //let key = UIResponder.keyboardFrameBeginUserInfoKey
  139. keyboardDelta = 0
  140. terminalView.frame = makeFrame(keyboardDelta: 0)
  141. }
  142. }
  143. // MARK: - Style terminal
  144. extension VMDisplayTerminalViewController {
  145. private func styleTerminal() {
  146. guard let style = style else {
  147. return
  148. }
  149. let fontSize = style.fontSize
  150. let fontName = style.font.rawValue
  151. if fontName != "" {
  152. let orig = terminalView.font
  153. let new = UIFont(name: fontName, size: CGFloat(fontSize)) ?? orig
  154. terminalView.font = new
  155. } else {
  156. let orig = terminalView.font
  157. let new = UIFont(descriptor: orig.fontDescriptor, size: CGFloat(fontSize))
  158. terminalView.font = new
  159. }
  160. if let consoleTextColor = style.foregroundColor,
  161. let textColor = Color(hexString: consoleTextColor),
  162. let consoleBackgroundColor = style.backgroundColor,
  163. let backgroundColor = Color(hexString: consoleBackgroundColor) {
  164. terminalView.nativeForegroundColor = UIColor(textColor)
  165. terminalView.nativeBackgroundColor = UIColor(backgroundColor)
  166. }
  167. terminalView.getTerminal().setCursorStyle(style.hasCursorBlink ? .blinkBlock : .steadyBlock)
  168. terminalView.optionAsMetaKey = boolForSetting("OptionAsMetaKey")
  169. }
  170. }
  171. // MARK: - TerminalViewDelegate
  172. extension VMDisplayTerminalViewController: TerminalViewDelegate {
  173. func sizeChanged(source: TerminalView, newCols: Int, newRows: Int) {
  174. delegate?.displayViewSize = CGSize(width: newCols, height: newRows)
  175. }
  176. func setTerminalTitle(source: TerminalView, title: String) {
  177. }
  178. func requestOpenLink(source: TerminalView, link: String, params: [String : String]) {
  179. }
  180. func hostCurrentDirectoryUpdate(source: TerminalView, directory: String?) {
  181. }
  182. func send(source: TerminalView, data: ArraySlice<UInt8>) {
  183. delegate?.displayDidAssertUserInteraction()
  184. vmSerialPort.write(Data(data))
  185. }
  186. func scrolled(source: TerminalView, position: Double) {
  187. delegate?.displayDidAssertUserInteraction()
  188. }
  189. func bell(source: TerminalView) {
  190. }
  191. func rangeChanged(source: TerminalView, startY: Int, endY: Int) {
  192. }
  193. func clipboardCopy(source: TerminalView, content: Data) {
  194. if let str = String(bytes: content, encoding: .utf8) {
  195. UIPasteboard.general.string = str
  196. }
  197. }
  198. }
  199. // MARK: - CSPortDelegate
  200. extension VMDisplayTerminalViewController: CSPortDelegate {
  201. func portDidDisconect(_ port: CSPort) {
  202. }
  203. func port(_ port: CSPort, didError error: String) {
  204. delegate?.serialDidError(error)
  205. }
  206. func port(_ port: CSPort, didRecieveData data: Data) {
  207. if let terminalView = terminalView {
  208. let arr = [UInt8](data)[...]
  209. DispatchQueue.main.async {
  210. terminalView.feed(byteArray: arr)
  211. }
  212. }
  213. }
  214. }