| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174 |
- //
- // 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..<min(pathBytes.count, 108) {
- rawPtr[i] = pathBytes[i]
- }
- }
-
- var retries = 50
- while retries > 0 {
- let result = withUnsafePointer(to: &addr) { addrPtr in
- addrPtr.withMemoryRebound(to: sockaddr.self, capacity: 1) { saPtr in
- connect(fd, saPtr, socklen_t(MemoryLayout<sockaddr_un>.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<UInt8>.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]
- }
- }
- }
|