Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion pifinder_setup.sh
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,14 @@ sudo systemctl unmask hostapd
# open permissisons on wpa_supplicant file so we can adjust network config
sudo chmod 666 /etc/wpa_supplicant/wpa_supplicant.conf

# Bluetooth HID keyboards
if [[ -f /etc/bluetooth/input.conf ]]; then
sudo sed -i \
-e 's/^#\?UserspaceHID=.*/UserspaceHID=true/' \
-e 's/^#\?LEAutoSecurity=.*/LEAutoSecurity=true/' \
/etc/bluetooth/input.conf
fi

# Samba config
sudo cp ~/PiFinder/pi_config_files/smb.conf /etc/samba/smb.conf

Expand Down Expand Up @@ -92,4 +100,3 @@ sudo systemctl enable pifinder
sudo systemctl enable pifinder_splash

echo "PiFinder setup complete, please restart the Pi"

13 changes: 13 additions & 0 deletions python/PiFinder/keyboard_interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@


class KeyboardInterface:
TEXT_BASE = 1000
NA = 10
PLUS = 11
MINUS = 12
Expand All @@ -30,6 +31,18 @@ class KeyboardInterface:
LNG_SQUARE = 204
POWER_BTN = 300

@classmethod
def text_key(cls, char: str) -> int:
return cls.TEXT_BASE + ord(char)

@classmethod
def is_text_key(cls, keycode: int) -> bool:
return cls.TEXT_BASE <= keycode < cls.TEXT_BASE + 256

@classmethod
def text_from_keycode(cls, keycode: int) -> str:
return chr(keycode - cls.TEXT_BASE)

def __init__(self, q=None):
self.q = q

Expand Down
293 changes: 277 additions & 16 deletions python/PiFinder/keyboard_pi.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

"""

from time import sleep, time
from time import monotonic, sleep, time
import libinput
from PiFinder.keyboard_interface import KeyboardInterface
import RPi.GPIO as GPIO
Expand All @@ -16,6 +16,80 @@
logger = logging.getLogger("Keyboard.Pi")


KEY_ESC = 1
KEY_1 = 2
KEY_2 = 3
KEY_3 = 4
KEY_4 = 5
KEY_5 = 6
KEY_6 = 7
KEY_7 = 8
KEY_8 = 9
KEY_9 = 10
KEY_0 = 11
KEY_MINUS = 12
KEY_EQUAL = 13
KEY_BACKSPACE = 14
KEY_Q = 16
KEY_W = 17
KEY_E = 18
KEY_R = 19
KEY_T = 20
KEY_Y = 21
KEY_U = 22
KEY_I = 23
KEY_O = 24
KEY_P = 25
KEY_ENTER = 28
KEY_LEFTCTRL = 29
KEY_A = 30
KEY_S = 31
KEY_D = 32
KEY_F = 33
KEY_G = 34
KEY_H = 35
KEY_J = 36
KEY_K = 37
KEY_L = 38
KEY_LEFTSHIFT = 42
KEY_Z = 44
KEY_X = 45
KEY_C = 46
KEY_V = 47
KEY_B = 48
KEY_N = 49
KEY_M = 50
KEY_RIGHTSHIFT = 54
KEY_LEFTALT = 56
KEY_SPACE = 57
KEY_KP7 = 71
KEY_KP8 = 72
KEY_KP9 = 73
KEY_KPMINUS = 74
KEY_KP4 = 75
KEY_KP5 = 76
KEY_KP6 = 77
KEY_KPPLUS = 78
KEY_KP1 = 79
KEY_KP2 = 80
KEY_KP3 = 81
KEY_KP0 = 82
KEY_KPENTER = 96
KEY_RIGHTCTRL = 97
KEY_RIGHTALT = 100
KEY_UP = 103
KEY_LEFT = 105
KEY_RIGHT = 106
KEY_DOWN = 108

PHYSICAL_HOLD_SECONDS = 1.0
PHYSICAL_REPEAT_SECONDS = 0.08
PHYSICAL_CTRL_KEYS = {KEY_LEFTCTRL, KEY_RIGHTCTRL}
PHYSICAL_SHIFT_KEYS = {KEY_LEFTSHIFT, KEY_RIGHTSHIFT}
PHYSICAL_ALT_KEYS = {KEY_LEFTALT, KEY_RIGHTALT}
PHYSICAL_MODIFIER_KEYS = PHYSICAL_CTRL_KEYS | PHYSICAL_SHIFT_KEYS | PHYSICAL_ALT_KEYS


class KeyboardPi(KeyboardInterface):
def __init__(self, q):
self.q = q
Expand Down Expand Up @@ -68,36 +142,223 @@ def __init__(self, q):
# physical keyboard support init
self.li_kb = libinput.LibInput(context_type=libinput.ContextType.UDEV)
self.li_kb.assign_seat("seat0")
self.physical_pressed = set()
self.physical_press_times: dict[int, float] = {}
self.physical_last_repeat_times: dict[int, float] = {}
self.physical_hold_sent: set[int] = set()
self.physical_press_modifiers: dict[int, set[int]] = {}

self.text_physical_key_mapping: dict[int, int] = {
KEY_SPACE: self.text_key(" "),
KEY_A: self.text_key("a"),
KEY_B: self.text_key("b"),
KEY_C: self.text_key("c"),
KEY_D: self.text_key("d"),
KEY_E: self.text_key("e"),
KEY_F: self.text_key("f"),
KEY_G: self.text_key("g"),
KEY_H: self.text_key("h"),
KEY_I: self.text_key("i"),
KEY_J: self.text_key("j"),
KEY_K: self.text_key("k"),
KEY_L: self.text_key("l"),
KEY_M: self.text_key("m"),
KEY_N: self.text_key("n"),
KEY_O: self.text_key("o"),
KEY_P: self.text_key("p"),
KEY_Q: self.text_key("q"),
KEY_R: self.text_key("r"),
KEY_S: self.text_key("s"),
KEY_T: self.text_key("t"),
KEY_U: self.text_key("u"),
KEY_V: self.text_key("v"),
KEY_W: self.text_key("w"),
KEY_X: self.text_key("x"),
KEY_Y: self.text_key("y"),
KEY_Z: self.text_key("z"),
}
self.shift_text_physical_key_mapping: dict[int, int] = {
key: self.text_key(chr(value - self.TEXT_BASE).upper())
for key, value in self.text_physical_key_mapping.items()
}
self.physical_key_mapping: dict[int, int] = {
KEY_UP: self.UP,
KEY_DOWN: self.DOWN,
KEY_LEFT: self.LEFT,
KEY_RIGHT: self.RIGHT,
KEY_ENTER: self.SQUARE,
KEY_KPENTER: self.SQUARE,
KEY_ESC: self.LEFT,
KEY_BACKSPACE: self.MINUS,
KEY_EQUAL: self.PLUS,
KEY_KPPLUS: self.PLUS,
KEY_MINUS: self.MINUS,
KEY_KPMINUS: self.MINUS,
KEY_1: 1,
KEY_2: 2,
KEY_3: 3,
KEY_4: 4,
KEY_5: 5,
KEY_6: 6,
KEY_7: 7,
KEY_8: 8,
KEY_9: 9,
KEY_0: 0,
KEY_KP1: 1,
KEY_KP2: 2,
KEY_KP3: 3,
KEY_KP4: 4,
KEY_KP5: 5,
KEY_KP6: 6,
KEY_KP7: 7,
KEY_KP8: 8,
KEY_KP9: 9,
KEY_KP0: 0,
}
self.alt_physical_key_mapping: dict[int, int] = {
KEY_UP: self.ALT_UP,
KEY_DOWN: self.ALT_DOWN,
KEY_LEFT: self.ALT_LEFT,
KEY_RIGHT: self.ALT_RIGHT,
KEY_EQUAL: self.ALT_PLUS,
KEY_KPPLUS: self.ALT_PLUS,
KEY_MINUS: self.ALT_MINUS,
KEY_KPMINUS: self.ALT_MINUS,
KEY_0: self.ALT_0,
KEY_KP0: self.ALT_0,
KEY_ENTER: self.ALT_SQUARE,
KEY_KPENTER: self.ALT_SQUARE,
}
self.long_physical_key_mapping: dict[int, int] = {
KEY_UP: self.LNG_UP,
KEY_DOWN: self.LNG_DOWN,
KEY_LEFT: self.LNG_LEFT,
KEY_RIGHT: self.LNG_RIGHT,
KEY_ENTER: self.LNG_SQUARE,
KEY_KPENTER: self.LNG_SQUARE,
}

def _remember_physical_press(self, key: int) -> None:
if key in self.physical_pressed:
return

self.physical_press_times[key] = monotonic()
self.physical_last_repeat_times.pop(key, None)
self.physical_hold_sent.discard(key)
self.physical_press_modifiers[key] = set(
self.physical_pressed & PHYSICAL_MODIFIER_KEYS
)
self.physical_pressed.add(key)

def _forget_physical_press(self, key: int) -> None:
self.physical_pressed.discard(key)
self.physical_press_times.pop(key, None)
self.physical_last_repeat_times.pop(key, None)
self.physical_hold_sent.discard(key)
self.physical_press_modifiers.pop(key, None)

def _physical_key_modifiers(self, key: int) -> set[int]:
return (
set(self.physical_press_modifiers.get(key, set()))
| (self.physical_pressed & PHYSICAL_MODIFIER_KEYS)
)

def _get_physical_hold_key(self) -> int:
now = monotonic()
held_keys = sorted(
self.physical_pressed,
key=lambda key: self.physical_press_times.get(key, now),
)

for key in held_keys:
if key in PHYSICAL_MODIFIER_KEYS:
continue

press_time = self.physical_press_times.get(key)
if press_time is None or now - press_time < PHYSICAL_HOLD_SECONDS:
continue

modifiers = self._physical_key_modifiers(key)
if modifiers & PHYSICAL_ALT_KEYS:
continue

if key in [KEY_UP, KEY_DOWN]:
if modifiers:
continue

last_repeat = self.physical_last_repeat_times.get(key)
if last_repeat is None or now - last_repeat >= PHYSICAL_REPEAT_SECONDS:
self.physical_last_repeat_times[key] = now
return self.physical_key_mapping[key]
continue

if key in self.physical_hold_sent:
continue

mapped_key = self.long_physical_key_mapping.get(key)
if mapped_key is not None:
self.physical_hold_sent.add(key)
return mapped_key

return 0

def get_keyboard_key(self) -> int:
"""
Checks libinput keyboard, if keyrelesed
map to our keycode and return
Checks libinput keyboard and maps key events to PiFinder keycodes.

Returns 0 for no key registered
"""
key_mapping: dict[int, int] = {
103: self.UP,
108: self.DOWN,
105: self.LEFT,
106: self.RIGHT,
28: self.SQUARE,
78: self.MINUS,
74: self.PLUS,
}

while True:
while True:
if hold_key := self._get_physical_hold_key():
return hold_key

self.li_kb._libinput.libinput_dispatch(self.li_kb._li)
hevent = self.li_kb._libinput.libinput_get_event(self.li_kb._li)
if not hevent:
return 0
return self._get_physical_hold_key()
type_ = self.li_kb._libinput.libinput_event_get_type(hevent)

if type_.is_keyboard():
kbev = libinput.KeyboardEvent(hevent, self.li_kb._libinput)
if kbev.key_state == libinput.constant.KeyState.RELEASED:
return key_mapping.get(kbev.key, 0)
if kbev.key_state == libinput.constant.KeyState.PRESSED:
self._remember_physical_press(kbev.key)
continue
if kbev.key_state != libinput.constant.KeyState.RELEASED:
continue

press_modifiers = self.physical_press_modifiers.get(
kbev.key, set()
)
release_modifiers = (
set(self.physical_pressed) | set(press_modifiers)
)
was_hold_sent = kbev.key in self.physical_hold_sent
self._forget_physical_press(kbev.key)

if kbev.key in PHYSICAL_MODIFIER_KEYS:
continue
if was_hold_sent:
continue

alt_pressed = bool(PHYSICAL_ALT_KEYS & release_modifiers)
if alt_pressed:
mapped_key = self.alt_physical_key_mapping.get(kbev.key)
return mapped_key if mapped_key is not None else 0

ctrl_pressed = bool(PHYSICAL_CTRL_KEYS & release_modifiers)
shift_pressed = bool(PHYSICAL_SHIFT_KEYS & release_modifiers)
if ctrl_pressed or shift_pressed:
mapped_key = self.long_physical_key_mapping.get(kbev.key)
if mapped_key is not None:
return mapped_key
if ctrl_pressed:
return 0
return self.shift_text_physical_key_mapping.get(kbev.key, 0)

return self.physical_key_mapping.get(
kbev.key, self.text_physical_key_mapping.get(kbev.key, 0)
)

def run_keyboard(self, log_queue):
"""
Expand Down
Loading