// // 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 Foundation #if canImport(Glibc) import Glibc #endif // ── Linux input_event struct (64-bit) ───────────────────────────── struct input_event { var time_sec: Int // 8 bytes var time_usec: Int // 8 bytes var type: UInt16 // 2 bytes var code: UInt16 // 2 bytes var value: Int32 // 4 bytes } // Event Types let EV_KEY: UInt16 = 1 let EV_REL: UInt16 = 2 let EV_ABS: UInt16 = 3 // Rel Codes let REL_X: UInt16 = 0 let REL_Y: UInt16 = 1 // Abs Codes let ABS_X: UInt16 = 0 let ABS_Y: UInt16 = 1 // Btn Codes let BTN_LEFT: UInt16 = 0x110 let BTN_RIGHT: UInt16 = 0x111 // ── ArkOS Input Protocol ────────────────────────────────────────── // Sent over /dev/input.sock // Wire format: 8 bytes per event // [type: UInt8][padding: UInt8][code: UInt16][value: Int32] struct ArkInputEvent { var type: UInt8 var padding: UInt8 = 0 var code: UInt16 var value: Int32 } public struct InputService { private static let inputQueue = DispatchQueue(label: "ark.system.input", attributes: .concurrent) private static var serverFd: Int32 = -1 private static var clients: [Int32] = [] private static let lock = NSLock() public static func start() { print("arkrt: InputService starting...") // Setup Unix Domain Socket server for /dev/input.sock serverFd = socket(AF_UNIX, Int32(SOCK_STREAM.rawValue), 0) if serverFd >= 0 { let socketPath = "/dev/input.sock" unlink(socketPath) var addr = sockaddr_un() addr.sun_family = sa_family_t(AF_UNIX) let pathBytes = socketPath.utf8CString withUnsafeMutablePointer(to: &addr.sun_path) { sunPathPtr in let rawPtr = UnsafeMutableRawPointer(sunPathPtr).assumingMemoryBound(to: CChar.self) for i in 0...size let bindResult = withUnsafePointer(to: &addr) { addrPtr in addrPtr.withMemoryRebound(to: sockaddr.self, capacity: 1) { saPtr in bind(serverFd, saPtr, socklen_t(addrSize)) } } if bindResult == 0 { listen(serverFd, 10) chmod(socketPath, 0o666) // Allow clients to connect // Accept clients loop inputQueue.async { while true { let clientFd = accept(serverFd, nil, nil) if clientFd >= 0 { lock.lock() clients.append(clientFd) lock.unlock() print("arkrt: Input client connected (\(clientFd))") } } } } } // Scan and open input devices (/dev/input/event0 to event10) periodically inputQueue.async { var openedDevices = Set() while true { for i in 0...10 { let path = "/dev/input/event\(i)" if !openedDevices.contains(path) { let fd = open(path, O_RDONLY) if fd >= 0 { openedDevices.insert(path) print("arkrt: Opened input device \(path)") inputQueue.async { readInputLoop(fd: fd) } } } } usleep(1_000_000) // Scan every 1 second } } } private static func readInputLoop(fd: Int32) { let evSize = MemoryLayout.size let buf = UnsafeMutablePointer.allocate(capacity: 1) defer { buf.deallocate(); close(fd) } while true { let bytesRead = read(fd, buf, evSize) if bytesRead == evSize { let ev = buf.pointee if ev.type == EV_KEY || ev.type == EV_REL || ev.type == EV_ABS { broadcast(type: UInt8(ev.type), code: ev.code, value: ev.value) } } else if bytesRead <= 0 { break } } } private static func broadcast(type: UInt8, code: UInt16, value: Int32) { var event = ArkInputEvent(type: type, padding: 0, code: code, value: value) lock.lock() var disconnected: [Int32] = [] for clientFd in clients { var sent = 0 withUnsafeBytes(of: &event) { bytes in sent = write(clientFd, bytes.baseAddress, bytes.count) } if sent < 0 { disconnected.append(clientFd) } } for d in disconnected { clients.removeAll(where: { $0 == d }) close(d) } lock.unlock() } }