mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-09-12 03:03:51 +08:00
195 lines
4.8 KiB
Python
195 lines
4.8 KiB
Python
import atexit
|
|
import os
|
|
import struct
|
|
import time
|
|
from collections import deque
|
|
from glob import glob
|
|
|
|
import pyray as rl
|
|
|
|
EVENT_FORMAT = "llHHi"
|
|
EVENT_SIZE = struct.calcsize(EVENT_FORMAT)
|
|
EV_KEY = 0x01
|
|
|
|
KEY_LEFTSHIFT = 42
|
|
KEY_RIGHTSHIFT = 54
|
|
KEY_CAPSLOCK = 58
|
|
|
|
LINUX_TO_RL_KEY = {
|
|
1: rl.KEY_ESCAPE,
|
|
14: rl.KEY_BACKSPACE,
|
|
28: rl.KEY_ENTER,
|
|
96: rl.KEY_ENTER, # keypad enter
|
|
102: rl.KEY_HOME,
|
|
103: rl.KEY_UP,
|
|
105: rl.KEY_LEFT,
|
|
106: rl.KEY_RIGHT,
|
|
107: rl.KEY_END,
|
|
108: rl.KEY_DOWN,
|
|
111: rl.KEY_DELETE,
|
|
}
|
|
|
|
LETTER_BY_CODE = {
|
|
16: "q", 17: "w", 18: "e", 19: "r", 20: "t", 21: "y", 22: "u", 23: "i", 24: "o", 25: "p",
|
|
30: "a", 31: "s", 32: "d", 33: "f", 34: "g", 35: "h", 36: "j", 37: "k", 38: "l",
|
|
44: "z", 45: "x", 46: "c", 47: "v", 48: "b", 49: "n", 50: "m",
|
|
}
|
|
|
|
UNSHIFTED_BY_CODE = {
|
|
2: "1", 3: "2", 4: "3", 5: "4", 6: "5", 7: "6", 8: "7", 9: "8", 10: "9", 11: "0",
|
|
12: "-", 13: "=",
|
|
26: "[", 27: "]",
|
|
39: ";", 40: "'", 41: "`", 43: "\\",
|
|
51: ",", 52: ".", 53: "/",
|
|
57: " ",
|
|
}
|
|
|
|
SHIFTED_BY_CODE = {
|
|
2: "!", 3: "@", 4: "#", 5: "$", 6: "%", 7: "^", 8: "&", 9: "*", 10: "(", 11: ")",
|
|
12: "_", 13: "+",
|
|
26: "{", 27: "}",
|
|
39: ":", 40: "\"", 41: "~", 43: "|",
|
|
51: "<", 52: ">", 53: "?",
|
|
57: " ",
|
|
}
|
|
|
|
|
|
class USBKeyboard:
|
|
"""Fallback keyboard reader for platforms where raylib keyboard input is unavailable."""
|
|
def __init__(self):
|
|
self._fds: dict[str, int] = {}
|
|
self._last_scan_t = 0.0
|
|
self._left_shift_down = False
|
|
self._right_shift_down = False
|
|
self._caps_lock_on = False
|
|
self._key_queue: deque[int] = deque(maxlen=128)
|
|
self._char_queue: deque[int] = deque(maxlen=256)
|
|
atexit.register(self.close)
|
|
|
|
def _device_paths(self) -> list[str]:
|
|
paths = []
|
|
paths.extend(glob("/dev/input/by-id/*-kbd"))
|
|
paths.extend(glob("/dev/input/by-path/*-kbd"))
|
|
# Keep deterministic order and de-duplicate paths if both globs resolve to same target.
|
|
return sorted(set(paths))
|
|
|
|
def _refresh_devices(self) -> None:
|
|
now = time.monotonic()
|
|
if (now - self._last_scan_t) < 1.0:
|
|
return
|
|
|
|
self._last_scan_t = now
|
|
current_paths = set(self._device_paths())
|
|
|
|
for path in list(self._fds):
|
|
if path not in current_paths:
|
|
try:
|
|
os.close(self._fds[path])
|
|
except OSError:
|
|
pass
|
|
del self._fds[path]
|
|
|
|
for path in current_paths:
|
|
if path in self._fds:
|
|
continue
|
|
try:
|
|
fd = os.open(path, os.O_RDONLY | os.O_NONBLOCK)
|
|
except OSError:
|
|
continue
|
|
self._fds[path] = fd
|
|
|
|
def _key_to_char(self, code: int) -> str | None:
|
|
shift = self._left_shift_down or self._right_shift_down
|
|
|
|
if code in LETTER_BY_CODE:
|
|
c = LETTER_BY_CODE[code]
|
|
return c.upper() if (self._caps_lock_on ^ shift) else c
|
|
|
|
if code in UNSHIFTED_BY_CODE:
|
|
if shift and code in SHIFTED_BY_CODE:
|
|
return SHIFTED_BY_CODE[code]
|
|
return UNSHIFTED_BY_CODE[code]
|
|
|
|
return None
|
|
|
|
def _process_event(self, event_type: int, code: int, value: int) -> None:
|
|
if event_type != EV_KEY:
|
|
return
|
|
|
|
if code == KEY_LEFTSHIFT:
|
|
self._left_shift_down = value != 0
|
|
return
|
|
if code == KEY_RIGHTSHIFT:
|
|
self._right_shift_down = value != 0
|
|
return
|
|
if code == KEY_CAPSLOCK and value == 1:
|
|
self._caps_lock_on = not self._caps_lock_on
|
|
return
|
|
|
|
# Linux key events: 0=release, 1=press, 2=repeat
|
|
if value not in (1, 2):
|
|
return
|
|
|
|
key = LINUX_TO_RL_KEY.get(code)
|
|
if key is not None:
|
|
self._key_queue.append(key)
|
|
|
|
char = self._key_to_char(code)
|
|
if char is not None:
|
|
self._char_queue.append(ord(char))
|
|
|
|
def _poll(self) -> None:
|
|
self._refresh_devices()
|
|
|
|
for path, fd in list(self._fds.items()):
|
|
while True:
|
|
try:
|
|
data = os.read(fd, EVENT_SIZE * 32)
|
|
except BlockingIOError:
|
|
break
|
|
except OSError:
|
|
try:
|
|
os.close(fd)
|
|
except OSError:
|
|
pass
|
|
del self._fds[path]
|
|
break
|
|
|
|
if not data:
|
|
break
|
|
|
|
valid_bytes = len(data) - (len(data) % EVENT_SIZE)
|
|
for i in range(0, valid_bytes, EVENT_SIZE):
|
|
_, _, event_type, code, value = struct.unpack_from(EVENT_FORMAT, data, i)
|
|
self._process_event(event_type, code, value)
|
|
|
|
if len(data) < (EVENT_SIZE * 32):
|
|
break
|
|
|
|
def get_key_pressed(self) -> int:
|
|
self._poll()
|
|
return self._key_queue.popleft() if self._key_queue else 0
|
|
|
|
def get_char_pressed(self) -> int:
|
|
self._poll()
|
|
return self._char_queue.popleft() if self._char_queue else 0
|
|
|
|
def close(self) -> None:
|
|
for fd in self._fds.values():
|
|
try:
|
|
os.close(fd)
|
|
except OSError:
|
|
pass
|
|
self._fds.clear()
|
|
|
|
|
|
_usb_keyboard = USBKeyboard()
|
|
|
|
|
|
def get_usb_key_pressed() -> int:
|
|
return _usb_keyboard.get_key_pressed()
|
|
|
|
|
|
def get_usb_char_pressed() -> int:
|
|
return _usb_keyboard.get_char_pressed()
|