// // Copyright 2026 Aarav Ravindra Kharade // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // import CSystem public struct ArkInputEvent { public var type: UInt8 public var padding: UInt8 public var code: UInt16 public var value: Int32 } public class ArkInput { private var fd: Int32 = -1 public private(set) var isConnected: Bool = false // Mouse state public var mouseX: Int = 512 public var mouseY: Int = 384 public var leftMouseDown: Bool = false public var rightMouseDown: Bool = false public var leftShiftDown: Bool = false public var rightShiftDown: Bool = false // Key codes private static let keyMap: [UInt16: Character] = [ 2: "1", 3: "2", 4: "3", 5: "4", 6: "5", 7: "6", 8: "7", 9: "8", 10: "9", 11: "0", 12: "-", 13: "=", 16: "q", 17: "w", 18: "e", 19: "r", 20: "t", 21: "y", 22: "u", 23: "i", 24: "o", 25: "p", 26: "[", 27: "]", 28: "\n", 30: "a", 31: "s", 32: "d", 33: "f", 34: "g", 35: "h", 36: "j", 37: "k", 38: "l", 39: ";", 40: "'", 41: "`", 43: "\\", 44: "z", 45: "x", 46: "c", 47: "v", 48: "b", 49: "n", 50: "m", 51: ",", 52: ".", 53: "/", 57: " ", // Space 14: "\u{08}" // Backspace ] private static let shiftKeyMap: [UInt16: Character] = [ 2: "!", 3: "@", 4: "#", 5: "$", 6: "%", 7: "^", 8: "&", 9: "*", 10: "(", 11: ")", 12: "_", 13: "+", 16: "Q", 17: "W", 18: "E", 19: "R", 20: "T", 21: "Y", 22: "U", 23: "I", 24: "O", 25: "P", 26: "{", 27: "}", 28: "\n", 30: "A", 31: "S", 32: "D", 33: "F", 34: "G", 35: "H", 36: "J", 37: "K", 38: "L", 39: ":", 40: "\"", 41: "~", 43: "|", 44: "Z", 45: "X", 46: "C", 47: "V", 48: "B", 49: "N", 50: "M", 51: "<", 52: ">", 53: "?", 57: " ", 14: "\u{08}" ] public init() { #if canImport(Glibc) fd = socket(AF_UNIX, Int32(SOCK_STREAM.rawValue), 0) #else fd = socket(AF_UNIX, Int32(SOCK_STREAM), 0) #endif if fd < 0 { return } var addr = sockaddr_un() addr.sun_family = sa_family_t(AF_UNIX) let socketPath = "/dev/input.sock" let pathBytes = socketPath.utf8CString withUnsafeMutablePointer(to: &addr.sun_path) { sunPathPtr in let rawPtr = UnsafeMutableRawPointer(sunPathPtr).assumingMemoryBound(to: CChar.self) for i in 0.. 0 { let result = withUnsafePointer(to: &addr) { addrPtr in addrPtr.withMemoryRebound(to: sockaddr.self, capacity: 1) { saPtr in connect(fd, saPtr, socklen_t(MemoryLayout.size)) } } if result == 0 { isConnected = true // Use MSG_DONTWAIT in recv() instead of setting socket to non-blocking // to avoid variadic fcntl() linking issues in Swift Musl. return } usleep(100_000) retries -= 1 } close(fd) } public func pollEvents(scrW: Int, scrH: Int) -> [ArkInputEvent] { guard isConnected else { return [] } var events = [ArkInputEvent]() let evSize = 8 let buf = UnsafeMutablePointer.allocate(capacity: evSize) defer { buf.deallocate() } while true { let bytesRead = recv(fd, buf, evSize, Int32(MSG_DONTWAIT)) if bytesRead <= 0 { break // EAGAIN or closed } var totalRead = bytesRead while totalRead < evSize { let more = read(fd, buf + totalRead, evSize - totalRead) if more <= 0 { break } totalRead += more } if totalRead == evSize { let type = buf[0] var code: UInt16 = 0 var value: Int32 = 0 memcpy(&code, buf + 2, 2) memcpy(&value, buf + 4, 4) let event = ArkInputEvent(type: type, padding: 0, code: code, value: value) events.append(event) // Update internal state if type == 1 { // EV_KEY if code == 0x110 { // BTN_LEFT leftMouseDown = (value != 0) } else if code == 0x111 { // BTN_RIGHT rightMouseDown = (value != 0) } else if code == 42 { // KEY_LEFTSHIFT leftShiftDown = (value != 0) } else if code == 54 { // KEY_RIGHTSHIFT rightShiftDown = (value != 0) } } else if type == 2 { // EV_REL if code == 0 { // REL_X mouseX = max(0, min(scrW - 1, mouseX + Int(value))) } else if code == 1 { // REL_Y mouseY = max(0, min(scrH - 1, mouseY - Int(value))) } } else if type == 3 { // EV_ABS if code == 0 { // ABS_X let scaledX = (Int(value) * scrW) / 32768 mouseX = max(0, min(scrW - 1, scaledX)) } else if code == 1 { // ABS_Y let scaledY = (Int(value) * scrH) / 32768 mouseY = max(0, min(scrH - 1, (scrH - 1) - scaledY)) } } } else { break // Stream broken } } return events } public func mapKey(code: UInt16) -> Character? { let isShiftDown = leftShiftDown || rightShiftDown if isShiftDown { return ArkInput.shiftKeyMap[code] } else { return ArkInput.keyMap[code] } } }