ArkInput.swift 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  1. //
  2. // Copyright 2026 Aarav Ravindra Kharade
  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 CSystem
  17. public struct ArkInputEvent {
  18. public var type: UInt8
  19. public var padding: UInt8
  20. public var code: UInt16
  21. public var value: Int32
  22. }
  23. public class ArkInput {
  24. private var fd: Int32 = -1
  25. public private(set) var isConnected: Bool = false
  26. // Mouse state
  27. public var mouseX: Int = 512
  28. public var mouseY: Int = 384
  29. public var leftMouseDown: Bool = false
  30. public var rightMouseDown: Bool = false
  31. public var leftShiftDown: Bool = false
  32. public var rightShiftDown: Bool = false
  33. // Key codes
  34. private static let keyMap: [UInt16: Character] = [
  35. 2: "1", 3: "2", 4: "3", 5: "4", 6: "5", 7: "6", 8: "7", 9: "8", 10: "9", 11: "0",
  36. 12: "-", 13: "=",
  37. 16: "q", 17: "w", 18: "e", 19: "r", 20: "t", 21: "y", 22: "u", 23: "i", 24: "o", 25: "p",
  38. 26: "[", 27: "]", 28: "\n",
  39. 30: "a", 31: "s", 32: "d", 33: "f", 34: "g", 35: "h", 36: "j", 37: "k", 38: "l",
  40. 39: ";", 40: "'", 41: "`", 43: "\\",
  41. 44: "z", 45: "x", 46: "c", 47: "v", 48: "b", 49: "n", 50: "m",
  42. 51: ",", 52: ".", 53: "/",
  43. 57: " ", // Space
  44. 14: "\u{08}" // Backspace
  45. ]
  46. private static let shiftKeyMap: [UInt16: Character] = [
  47. 2: "!", 3: "@", 4: "#", 5: "$", 6: "%", 7: "^", 8: "&", 9: "*", 10: "(", 11: ")",
  48. 12: "_", 13: "+",
  49. 16: "Q", 17: "W", 18: "E", 19: "R", 20: "T", 21: "Y", 22: "U", 23: "I", 24: "O", 25: "P",
  50. 26: "{", 27: "}", 28: "\n",
  51. 30: "A", 31: "S", 32: "D", 33: "F", 34: "G", 35: "H", 36: "J", 37: "K", 38: "L",
  52. 39: ":", 40: "\"", 41: "~", 43: "|",
  53. 44: "Z", 45: "X", 46: "C", 47: "V", 48: "B", 49: "N", 50: "M",
  54. 51: "<", 52: ">", 53: "?",
  55. 57: " ",
  56. 14: "\u{08}"
  57. ]
  58. public init() {
  59. #if canImport(Glibc)
  60. fd = socket(AF_UNIX, Int32(SOCK_STREAM.rawValue), 0)
  61. #else
  62. fd = socket(AF_UNIX, Int32(SOCK_STREAM), 0)
  63. #endif
  64. if fd < 0 { return }
  65. var addr = sockaddr_un()
  66. addr.sun_family = sa_family_t(AF_UNIX)
  67. let socketPath = "/dev/input.sock"
  68. let pathBytes = socketPath.utf8CString
  69. withUnsafeMutablePointer(to: &addr.sun_path) { sunPathPtr in
  70. let rawPtr = UnsafeMutableRawPointer(sunPathPtr).assumingMemoryBound(to: CChar.self)
  71. for i in 0..<min(pathBytes.count, 108) {
  72. rawPtr[i] = pathBytes[i]
  73. }
  74. }
  75. var retries = 50
  76. while retries > 0 {
  77. let result = withUnsafePointer(to: &addr) { addrPtr in
  78. addrPtr.withMemoryRebound(to: sockaddr.self, capacity: 1) { saPtr in
  79. connect(fd, saPtr, socklen_t(MemoryLayout<sockaddr_un>.size))
  80. }
  81. }
  82. if result == 0 {
  83. isConnected = true
  84. // Use MSG_DONTWAIT in recv() instead of setting socket to non-blocking
  85. // to avoid variadic fcntl() linking issues in Swift Musl.
  86. return
  87. }
  88. usleep(100_000)
  89. retries -= 1
  90. }
  91. close(fd)
  92. }
  93. public func pollEvents(scrW: Int, scrH: Int) -> [ArkInputEvent] {
  94. guard isConnected else { return [] }
  95. var events = [ArkInputEvent]()
  96. let evSize = 8
  97. let buf = UnsafeMutablePointer<UInt8>.allocate(capacity: evSize)
  98. defer { buf.deallocate() }
  99. while true {
  100. let bytesRead = recv(fd, buf, evSize, Int32(MSG_DONTWAIT))
  101. if bytesRead <= 0 {
  102. break // EAGAIN or closed
  103. }
  104. var totalRead = bytesRead
  105. while totalRead < evSize {
  106. let more = read(fd, buf + totalRead, evSize - totalRead)
  107. if more <= 0 { break }
  108. totalRead += more
  109. }
  110. if totalRead == evSize {
  111. let type = buf[0]
  112. var code: UInt16 = 0
  113. var value: Int32 = 0
  114. memcpy(&code, buf + 2, 2)
  115. memcpy(&value, buf + 4, 4)
  116. let event = ArkInputEvent(type: type, padding: 0, code: code, value: value)
  117. events.append(event)
  118. // Update internal state
  119. if type == 1 { // EV_KEY
  120. if code == 0x110 { // BTN_LEFT
  121. leftMouseDown = (value != 0)
  122. } else if code == 0x111 { // BTN_RIGHT
  123. rightMouseDown = (value != 0)
  124. } else if code == 42 { // KEY_LEFTSHIFT
  125. leftShiftDown = (value != 0)
  126. } else if code == 54 { // KEY_RIGHTSHIFT
  127. rightShiftDown = (value != 0)
  128. }
  129. } else if type == 2 { // EV_REL
  130. if code == 0 { // REL_X
  131. mouseX = max(0, min(scrW - 1, mouseX + Int(value)))
  132. } else if code == 1 { // REL_Y
  133. mouseY = max(0, min(scrH - 1, mouseY - Int(value)))
  134. }
  135. } else if type == 3 { // EV_ABS
  136. if code == 0 { // ABS_X
  137. let scaledX = (Int(value) * scrW) / 32768
  138. mouseX = max(0, min(scrW - 1, scaledX))
  139. } else if code == 1 { // ABS_Y
  140. let scaledY = (Int(value) * scrH) / 32768
  141. mouseY = max(0, min(scrH - 1, (scrH - 1) - scaledY))
  142. }
  143. }
  144. } else {
  145. break // Stream broken
  146. }
  147. }
  148. return events
  149. }
  150. public func mapKey(code: UInt16) -> Character? {
  151. let isShiftDown = leftShiftDown || rightShiftDown
  152. if isShiftDown {
  153. return ArkInput.shiftKeyMap[code]
  154. } else {
  155. return ArkInput.keyMap[code]
  156. }
  157. }
  158. }