--- /dev/null
+launch.py
\ No newline at end of file
--- /dev/null
+from functools import partial
+from pathlib import Path
+
+import pygame
+from pynput.keyboard import Key, KeyCode
+
+from ui import Button
+from ui.multitouch import MultitouchHandler
+
+from .touchbutton import TouchButton
+
+KEYBOARD = (
+ ("Esc", Key.esc),
+ (None, None),
+ ("F1", Key.f1),
+ ("F2", Key.f2),
+ ("F3", Key.f3),
+ ("F4", Key.f4),
+ (None, None),
+ ("F5", Key.f5),
+ ("F6", Key.f6),
+ ("F7", Key.f7),
+ ("F8", Key.f8),
+ (None, None),
+ ("F9", Key.f9),
+ ("F10", Key.f10),
+ ("F11", Key.f11),
+ ("F12", Key.f12),
+ "\n",
+ ("§", KeyCode.from_vk(0xa7), {"x_hint": 1.5}),
+ ("1", KeyCode.from_vk(0x31)),
+ ("2", KeyCode.from_vk(0x32)),
+ ("3", KeyCode.from_vk(0x33)),
+ ("4", KeyCode.from_vk(0x34)),
+ ("5", KeyCode.from_vk(0x35)),
+ ("6", KeyCode.from_vk(0x36)),
+ ("7", KeyCode.from_vk(0x37)),
+ ("8", KeyCode.from_vk(0x38)),
+ ("9", KeyCode.from_vk(0x39)),
+ ("0", KeyCode.from_vk(0x30)),
+ ("'", KeyCode.from_vk(0x27)),
+ ("^", KeyCode.from_vk(0xfe52)),
+ ("←", Key.backspace, {"x_hint": 1.5}),
+ "\n",
+ ("Tab", Key.tab, {"x_hint": 2}),
+ ("Q", KeyCode.from_vk(0x71)),
+ ("W", KeyCode.from_vk(0x77)),
+ ("E", KeyCode.from_vk(0x65)),
+ ("R", KeyCode.from_vk(0x72)),
+ ("T", KeyCode.from_vk(0x74)),
+ ("Z", KeyCode.from_vk(0x7a)),
+ ("U", KeyCode.from_vk(0x75)),
+ ("I", KeyCode.from_vk(0x69)),
+ ("O", KeyCode.from_vk(0x6f)),
+ ("P", KeyCode.from_vk(0x70)),
+ ("ü", KeyCode.from_vk(0xfc)),
+ ("¨", KeyCode.from_vk(0xfe57)),
+ ("$", KeyCode.from_vk(0x24)),
+ "\n",
+ ("CpsLk", Key.caps_lock, {"x_hint": 2.5, "led_mask": 1}),
+ ("A", KeyCode.from_vk(0x61)),
+ ("S", KeyCode.from_vk(0x73)),
+ ("D", KeyCode.from_vk(0x64)),
+ ("F", KeyCode.from_vk(0x66)),
+ ("G", KeyCode.from_vk(0x67)),
+ ("H", KeyCode.from_vk(0x68)),
+ ("J", KeyCode.from_vk(0x6a)),
+ ("K", KeyCode.from_vk(0x6b)),
+ ("L", KeyCode.from_vk(0x6c)),
+ ("ö", KeyCode.from_vk(0xf6)),
+ ("ä", KeyCode.from_vk(0xe4)),
+ ("↵", Key.enter, {"x_hint": 1.5}),
+ "\n",
+ ("↑", Key.shift, {"x_hint": 2}),
+ ("<", KeyCode.from_vk(0x3c)),
+ ("Y", KeyCode.from_vk(0x79)),
+ ("X", KeyCode.from_vk(0x78)),
+ ("C", KeyCode.from_vk(0x63)),
+ ("V", KeyCode.from_vk(0x76)),
+ ("B", KeyCode.from_vk(0x62)),
+ ("N", KeyCode.from_vk(0x6e)),
+ ("M", KeyCode.from_vk(0x6d)),
+ (",", KeyCode.from_vk(0x2c)),
+ (".", KeyCode.from_vk(0x2e)),
+ ("-", KeyCode.from_vk(0x2d)),
+ ("↑", Key.shift, {"x_hint": 2}),
+ "\n",
+ ("Ctrl", Key.ctrl_l, {"x_hint": 2}),
+ ("⊞", Key.cmd),
+ ("Alt", Key.alt_l),
+ ("␣", Key.space, {"x_hint": 7}),
+ ("⚙", "Settings"),
+ ("AltGr", KeyCode.from_vk(0xfe03)),
+ ("Ctrl", Key.ctrl_r, {"x_hint": 2}),
+)
+
+CURSOR_KEYS = (
+ ("INS", Key.insert),
+ ("HOME", Key.home),
+ ("PG UP", Key.page_up),
+ "\n",
+ ("DEL", Key.delete),
+ ("END", Key.end),
+ ("PG DN", Key.page_down),
+ "\n",
+ "\n",
+ (None, None),
+ ("↑", Key.up),
+ (None, None),
+ "\n",
+ ("←", Key.left),
+ ("↓", Key.down),
+ ("→", Key.right),
+)
+
+KEYPAD_KEYS = (
+ ("NUM", Key.num_lock, {"led_mask": 2}),
+ ("/", KeyCode.from_vk(0xffaf)),
+ ("*", KeyCode.from_vk(0xffaa)),
+ ("-", KeyCode.from_vk(0xffad)),
+ "\n",
+ ("7", KeyCode.from_vk(0xffb7)),
+ ("8", KeyCode.from_vk(0xffb8)),
+ ("9", KeyCode.from_vk(0xffb9)),
+ ("+", KeyCode.from_vk(0xffab), {"y_hint": 2}),
+ "\n",
+ ("4", KeyCode.from_vk(0xffb4)),
+ ("5", KeyCode.from_vk(0xffb5)),
+ ("6", KeyCode.from_vk(0xffb6)),
+ "\n",
+ ("1", KeyCode.from_vk(0xffb1)),
+ ("2", KeyCode.from_vk(0xffb2)),
+ ("3", KeyCode.from_vk(0xffb3)),
+ ("Enter", KeyCode.from_vk(0xff8d), {"y_hint": 2}),
+ "\n",
+ ("0", KeyCode.from_vk(0xffb0), {"x_hint": 2}),
+ (".", KeyCode.from_vk(0xffae)),
+)
+
+
+class KeyButton(Button):
+ def __init__(self, *args, **kwargs):
+ self.data = kwargs.pop("data", None)
+ self.led_mask = kwargs.pop("led_mask", None)
+ super().__init__(*args, **kwargs)
+ self.touch_key = None
+ # sounds from https://freesoundsite.com/sound-collections/click-sound-effects/
+ self.sounds = (
+ pygame.mixer.Sound(Path(__file__).parent / "sounds" / "Flicker_Sound_Effect.mp3"),
+ pygame.mixer.Sound(Path(__file__).parent / "sounds" / "Clutch.mp3"),
+ )
+ for sound in self.sounds:
+ sound.set_volume(0.5)
+
+ def press(self, touch_key=None):
+ if self.pushed:
+ return
+ if not self.root.muted:
+ self.sounds[0].play()
+ self.touch_key = touch_key
+ self.pushed = True
+ self.dirty = True
+ self.callback(self.data)
+
+ def release(self, touch_key=None):
+ if not self.pushed or touch_key != self.touch_key:
+ return
+ if not self.root.muted:
+ self.sounds[1].play()
+ self.touch_key = None
+ self.pushed = False
+ self.dirty = True
+ self.callback(self.data, True)
+
+ def handle_mousebuttondown(self, event):
+ if event.button == 1 and self.rect.collidepoint(event.pos):
+ self.press()
+
+ def handle_mousemotion(self, event):
+ if event.buttons[0] and not self.rect.collidepoint(event.pos):
+ self.release()
+
+ def handle_mousebuttonup(self, event):
+ if event.button == 1 and self.rect.collidepoint(event.pos):
+ self.release()
+
+ def handle_fingerdown(self, event):
+ if self.rect.collidepoint(
+ MultitouchHandler.map_coords((event.x, event.y), self.surf.get_size())
+ ):
+ self.press((event.touch_id, event.finger_id))
+
+ def handle_fingermotion(self, event):
+ if not self.rect.collidepoint(
+ MultitouchHandler.map_coords((event.x, event.y), self.surf.get_size())
+ ):
+ self.release((event.touch_id, event.finger_id))
+
+ def handle_fingerup(self, event):
+ self.release((event.touch_id, event.finger_id))
+
+ def draw(self):
+ if self.led_mask:
+ self.highlight = self.root.led_mask & self.led_mask
+ super().draw()
+
+
+def apply_y_spans(x_sums, y_spans):
+ for i, (width, y_remaining) in enumerate(y_spans):
+ if y_remaining <= 0:
+ continue
+ x_sums[-1] += width
+ y_spans[i][1] = y_remaining - 1
+
+
+def get_key_size(size, x_sums_iter, x_sums_len):
+ x_sum = next(x_sums_iter)
+ if x_sum == 0:
+ return (0, size[1] / x_sums_len)
+ return (size[0] / x_sum, size[1] / x_sums_len)
+
+
+def get_keyboard_keys(rect, parent, callback, keys):
+ result = []
+ x_sums = [0]
+ y_spans = []
+ for key in keys:
+ if key == "\n":
+ x_sums.append(0)
+ apply_y_spans(x_sums, y_spans)
+ elif len(key) > 2:
+ width = key[2].get("x_hint", 1)
+ x_sums[-1] += width
+ y_hint = key[2].get("y_hint", 1)
+ if y_hint > 1:
+ y_spans.append([width, y_hint - 1])
+ else:
+ x_sums[-1] += 1
+ apply_y_spans(x_sums, y_spans)
+ assert all(item[1] <= 0 for item in y_spans)
+ x, y = rect.topleft
+ x_sums_iter = iter(x_sums)
+ x_sums_len = len(x_sums)
+ key_size = get_key_size(rect.size, x_sums_iter, x_sums_len)
+ for key in keys:
+ if key == "\n":
+ x, y = rect.left, y + key_size[1]
+ key_size = get_key_size(rect.size, x_sums_iter, x_sums_len)
+ continue
+ key_width, key_height = key_size
+ if key[1] == "Settings":
+ result.append(
+ TouchButton(
+ parent,
+ pygame.Rect((x + 8, y + 8), (key_width - 16, key_height - 16)),
+ key[0],
+ partial(callback, key[1]),
+ )
+ )
+ elif key != (None, None):
+ if len(key) > 2:
+ key_width *= key[2].get("x_hint", 1)
+ key_height *= key[2].get("y_hint", 1)
+ led_mask = key[2].get("led_mask")
+ else:
+ led_mask = None
+ result.append(
+ KeyButton(
+ parent,
+ pygame.Rect((x + 8, y + 8), (key_width - 16, key_height - 16)),
+ key[0],
+ callback,
+ data=key[1],
+ led_mask=led_mask
+ )
+ )
+ x += key_width
+ return result
--- /dev/null
+#!/usr/bin/env python3
+
+from pynput.keyboard import Controller as KeyboardController, Key, KeyCode
+from pynput._util.xorg_keysyms import KEYPAD_KEYS
+
+from launch import pygame
+from multitouch import MultitouchHandler
+from ui import BaseRoot, FPSWidget
+
+from .key import CURSOR_KEYS, KEYBOARD, KEYPAD_KEYS, get_keyboard_keys
+from .settings import Settings
+
+# add a launcher symbol
+
+
+class Root(BaseRoot):
+ BACKGROUND_COLOR = "black"
+
+ def __init__(self):
+ super().__init__(
+ pygame.display.set_mode((0, 0), pygame.NOFRAME, display=1),
+ pygame.font.SysFont("DejaVu Sans", size=64),
+ )
+ size = self.surf.get_size()
+ widget_size = (size[0] // 3, size[1] // 2)
+ get_keyboard_keys(
+ pygame.Rect((0, 0), (size[0], widget_size[1])), self, self.key_cb, KEYBOARD
+ )
+ get_keyboard_keys(
+ pygame.Rect((0, widget_size[1]), widget_size),
+ self,
+ self.key_cb,
+ CURSOR_KEYS,
+ )
+ get_keyboard_keys(
+ pygame.Rect(widget_size, widget_size), self, self.key_cb, KEYPAD_KEYS
+ )
+ self.pushed = set()
+ self.mt = MultitouchHandler(
+ MultitouchHandler.find_device(name="ELAN9009:00 04F3:425A")
+ )
+ self.keyboard = KeyboardController()
+ self.display = self.keyboard._display
+ self.led_mask = self.display.get_keyboard_control().led_mask
+ self.muted = False
+ FPSWidget(self)
+ self.settings = Settings(
+ self,
+ pygame.Rect((size[0] // 4, size[1] // 4), (size[0] // 2, size[1] // 2)),
+ )
+
+ def key_cb(self, key, release=False):
+ if key == "Settings":
+ self.settings.activate()
+ if isinstance(key, (Key, KeyCode)):
+ (self.keyboard.press, self.keyboard.release)[release](key)
+ if release and key in self.pushed:
+ self.pushed.remove(key)
+ elif not release:
+ self.pushed.add(key)
+
+ def handle_events(self):
+ self.mt.handle_events()
+ super().handle_events()
+
+ def update(self):
+ super().update()
+ led_mask = self.keyboard._display.get_keyboard_control().led_mask
+ if led_mask != self.led_mask:
+ self.led_mask = led_mask
+ self.dirty = True
+
+ def run(self):
+ try:
+ with self.mt.device.grab_context():
+ super().run()
+ finally:
+ for key in self.pushed:
+ self.keyboard.release(key)
--- /dev/null
+import os
+import sys
+from pathlib import Path
+
+import pygame
+
+from ui import Modal, Rect
+
+from .touchbutton import TouchButton
+
+# what settings do we need?
+# - configure widgets
+# - keyboard above, two widgets below
+# - keyboard above, three widgets below
+# - keyboard below, two widgets above
+# - keyboard below, three widgets above
+# - three available widgets: touchpad mouse, number keypad, cursor and navkeys
+
+
+class Settings(Modal):
+ def __init__(self, parent, rect):
+ super().__init__(parent)
+ self.rect = rect
+ Rect(self, self.rect, "black", "gray")
+ width = rect.width * 2 / 3
+ height = rect.height / 7
+ left = rect.left + (rect.width - width) / 2 + 8
+ y = rect.top + height + 8
+ rect_size = (width - 16, height - 16)
+ self.buttons = []
+ for args in (
+ ("Mute", self.mute, self.root.muted),
+ ("Layout...", self.layout),
+ ("Restart", self.restart),
+ ("Exit", self.root.handle_quit),
+ ("Back", self.deactivate),
+ ):
+ self.buttons.append(
+ TouchButton(self, pygame.Rect((left, y), rect_size), *args)
+ )
+ y += height
+
+ def mute(self):
+ self.root.muted ^= True
+ self.buttons[0].highlight = self.root.muted
+
+ def layout(self):
+ print("stub layout")
+
+ def restart(self):
+ executable = Path(sys.executable).name
+ os.execl(
+ sys.executable,
+ executable,
+ *sys.orig_argv[sys.orig_argv[0] == executable:],
+ )
--- /dev/null
+from ui import Button
+from ui.multitouch import MultitouchHandler
+
+
+class TouchButton(Button):
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self.touch_key = None
+
+ def handle_fingerdown(self, event):
+ if not self.pushed and self.rect.collidepoint(
+ MultitouchHandler.map_coords((event.x, event.y), self.surf.get_size())
+ ):
+ self.touch_key = (event.touch_id, event.finger_id)
+ self.pushed = True
+ self.dirty = True
+
+ def handle_fingermotion(self, event):
+ if not self.pushed or self.touch_key is None:
+ return
+ if (
+ (event.touch_id, event.finger_id) == self.touch_key
+ and not self.rect.collidepoint(
+ MultitouchHandler.map_coords((event.x, event.y), self.surf.get_size())
+ )
+ ):
+ self.touch_key = None
+ self.pushed = False
+ self.dirty = True
+
+ def handle_fingerup(self, event):
+ if not self.pushed or self.touch_key is None:
+ return
+ if (event.touch_id, event.finger_id) == self.touch_key:
+ if self.rect.collidepoint(
+ MultitouchHandler.map_coords((event.x, event.y), self.surf.get_size())
+ ):
+ self.callback()
+ self.touch_key = None
+ self.pushed = False
+ self.dirty = True
if not has_pygame or all(
p.stat().st_mtime < yesterday for p in walk_outer(venv_dir)
):
- run_cmd((executable, "-m", "pip", "install", "-qU", "pip", "pygame"))
+ run_cmd(
+ (
+ executable,
+ "-m",
+ "pip",
+ "install",
+ "-qU",
+ "pip",
+ "pygame",
+ "pynput",
+ "evdev",
+ )
+ )
venv_dir.touch(0o755, True)
elif not has_pygame:
raise ConnectionError("Internet needed to install requirement: pygame")
self.pushed = True
self.dirty = True
- def handle_mousebuttonup(self, ev):
- if ev.button == 1 and self.pushed and self.rect.collidepoint(ev.pos):
- self.pushed = False
- self.callback()
- self.dirty = True
-
def handle_mousemotion(self, ev):
if not self.pushed:
return
if ev.buttons[0] and not self.rect.collidepoint(ev.pos):
self.pushed = False
self.dirty = True
+
+ def handle_mousebuttonup(self, ev):
+ if ev.button == 1 and self.pushed:
+ if self.rect.collidepoint(ev.pos):
+ self.callback()
+ self.pushed = False
+ self.dirty = True
-from .root import BaseRoot
+import ui
class Focusable:
- root: BaseRoot
+ root: "ui.root.BaseRoot"
dirty: bool
@property
+import pygame
from evdev import InputDevice, ecodes, list_devices
class MultitouchHandler:
+ STRINGY_FIELDS = False
+
+ @staticmethod
+ def map_coords(event_pos, size):
+ return (event_pos[0] * size[0], event_pos[1] * size[1])
+
@staticmethod
def find_device(**kwargs):
+ if "path" in kwargs:
+ return InputDevice(kwargs["path"])
for path in list_devices():
device = InputDevice(path)
for k, v in kwargs.items():
return device
return None
- @staticmethod
- def multitouch_ranges(device):
- return {
- v: (info.min, info.max)
- for v, info in (
- (v, device.absinfo(v))
- for v in (
- ecodes.ABS_X,
- ecodes.ABS_Y,
- ecodes.ABS_MT_POSITION_X,
- ecodes.ABS_MT_POSITION_Y,
- )
- )
+ def __init__(self, device):
+ self.device = device
+ self.touch_id = int(self.device.path[self.device.path.find("event") + 5:])
+ x_info = self.device.absinfo(ecodes.ABS_MT_POSITION_X)
+ y_info = self.device.absinfo(ecodes.ABS_MT_POSITION_Y)
+ self.info = {
+ ecodes.ABS_MT_POSITION_X: (x_info.min, x_info.max),
+ ecodes.ABS_MT_POSITION_Y: (y_info.min, y_info.max),
}
-
- def __init__(self, **kwargs):
- self.device = self.find_device(**kwargs)
- self.info = self.multitouch_ranges(self.device)
- self.context_app = kwargs.get("context_app")
- self.slots = [{}]
+ self._fingers = [{}]
+ self.fingers = []
+ self.dropped = False
self.slot = 0
+ self.event_handlers = {
+ ecodes.EV_SYN: self.handle_syn,
+ ecodes.EV_ABS: self.handle_abs,
+ ecodes.EV_MSC: self.handle_msc,
+ ecodes.EV_KEY: self.handle_key,
+ }
+ self.tracking_id_lookup = self.get_lookup(ecodes.ABS, ecodes.ABS_MT_TRACKING_ID)
+ self.x_lookup = self.get_lookup(ecodes.ABS, ecodes.ABS_MT_POSITION_X)
+ self.y_lookup = self.get_lookup(ecodes.ABS, ecodes.ABS_MT_POSITION_Y)
+
+ def current_finger(self):
+ while self.slot >= len(self._fingers):
+ self._fingers.append({})
+ return self._fingers[self.slot]
+
+ def _clear_fingers(self):
+ for finger in self._fingers:
+ finger.clear()
+
+ @classmethod
+ def get_lookup(cls, mapping, code):
+ if cls.STRINGY_FIELDS:
+ return mapping[code]
+ return code
+
+ def push_event(self, event_type, finger_new, finger_old):
+ x_info = self.info[ecodes.ABS_MT_POSITION_X]
+ y_info = self.info[ecodes.ABS_MT_POSITION_Y]
+ x = (finger_new.get(self.x_lookup, x_info[1]) - x_info[0]) / (x_info[1] - x_info[0])
+ y = (finger_new.get(self.y_lookup, x_info[1]) - y_info[0]) / (y_info[1] - y_info[0])
+ pygame.event.post(
+ pygame.event.Event(
+ event_type,
+ {
+ "finger_id": finger_new[self.tracking_id_lookup],
+ "touch_id": self.touch_id,
+ "x": x,
+ "y": y,
+ **(
+ {
+ "dx": x - (
+ finger_old.get(self.x_lookup, x_info[1]) - x_info[0]
+ ) / x_info[1],
+ "dy": y - (
+ finger_old.get(self.y_lookup, y_info[1]) - y_info[0]
+ ) / y_info[1],
+ } if finger_old is not None else {"dx": 0.0, "dy": 0.0}
+ ),
+ "evdev_event": finger_new.copy(),
+ **({"dropped": True} if self.dropped else {}),
+ }
+ )
+ )
- def current_slot(self):
- while self.slot >= len(self.slots):
- self.slots.append({})
- return self.slots[self.slot]
+ @staticmethod
+ def cmp_fingers(finger_new, finger_old):
+ for key in (*finger_new, *finger_old):
+ if key == ecodes.MSC_TIMESTAMP:
+ continue
+ if finger_new.get(key) != finger_old.get(key):
+ return True
+ return False
+
+ def sync(self):
+ num_fingers = len(self._fingers)
+ while len(self.fingers) < num_fingers:
+ self.fingers.append({})
+ for finger_new, finger_old in zip(self._fingers, self.fingers):
+ old_finger_id = finger_old.get(self.tracking_id_lookup)
+ new_finger_id = finger_new.get(self.tracking_id_lookup)
+ if old_finger_id is not None:
+ if old_finger_id == new_finger_id:
+ if self.cmp_fingers(finger_new, finger_old):
+ self.push_event(pygame.FINGERMOTION, finger_new, finger_old)
+ else:
+ self.push_event(pygame.FINGERUP, finger_old, None)
+ if new_finger_id is not None and (
+ old_finger_id is None or old_finger_id != new_finger_id
+ ):
+ self.push_event(pygame.FINGERDOWN, finger_new, None)
+ finger_old.clear()
+ finger_old.update(finger_new)
+
+ def handle_syn(self, event):
+ if event.code == ecodes.SYN_DROPPED:
+ self._clear_fingers()
+ self.dropped = True
+ elif event.code == ecodes.SYN_REPORT:
+ self.sync()
+ self.dropped = False
+
+ def handle_abs(self, event):
+ if event.code == ecodes.ABS_MT_SLOT:
+ self.slot = event.value
+ elif event.code == ecodes.ABS_MT_TRACKING_ID and event.value == -1:
+ self.current_finger().clear()
+ return
+ self.current_finger()[self.get_lookup(ecodes.ABS, event.code)] = event.value
+
+ def handle_msc(self, event):
+ self.current_finger()[self.get_lookup(ecodes.MSC, event.code)] = event.value
- def handle_event(self, event):
+ def handle_key(self, event):
+ if event.code == ecodes.BTN_TOUCH and event.value == 0:
+ self._clear_fingers()
+
+ @staticmethod
+ def log_event(event):
if event.type == ecodes.EV_SYN:
- current_slot = self.current_slot()
- if current_slot.get(ecodes.ABS_MT_TRACKING_ID) == -1:
- current_slot.clear()
+ code = ecodes.SYN[event.code]
elif event.type == ecodes.EV_ABS:
- if event.code == ecodes.ABS_MT_SLOT:
- self.slot = event.value
- else:
- self.current_slot()[event.code] = event.value
+ code = ecodes.ABS[event.code]
elif event.type == ecodes.EV_MSC:
- self.current_slot()[event.code] = event.value
- elif event.type == ecodes.EV_KEY:
- if event.code == ecodes.BTN_TOUCH:
- if event.value == 0:
- for s in self.slots:
- s.clear()
+ code = ecodes.MSC[event.code]
+ elif event.type == ecodes.EV_KEY and event.code in {
+ *ecodes.KEY, *ecodes.BTN
+ }:
+ code = ecodes.KEY.get(
+ event.code, ecodes.BTN[event.code]
+ )
+ else:
+ code = f"{ecodes.EV[event.type]}[{event.code}]"
+ print(ecodes.EV[event.type], code, event.value)
def handle_events(self):
while (event := self.device.read_one()) is not None:
- self.handle_event(event)
-
- def map_coords(self, rect):
- for slot in self.slots:
- if ecodes.ABS_MT_POSITION_X in slot and ecodes.ABS_MT_POSITION_Y in slot:
- infos = (
- self.info[ecodes.ABS_MT_POSITION_X],
- self.info[ecodes.ABS_MT_POSITION_Y],
- )
- pos = (slot[ecodes.ABS_MT_POSITION_X], slot[ecodes.ABS_MT_POSITION_Y])
- elif ecodes.ABS_X in slot and ecodes.ABS_Y in slot:
- infos = (self.info[ecodes.ABS_X], self.info[ecodes.ABS_Y])
- pos = (slot[ecodes.ABS_X], slot[ecodes.ABS_Y])
- else:
- continue
- yield tuple(
- offset + (value - info[0]) * scale / (info[1] - info[0])
- for value, info, offset, scale in zip(
- pos,
- infos,
- rect.topleft,
- rect.size,
- )
- ), slot
+ # self.log_event(event)
+ self.event_handlers.get(event.type, lambda e: None)(event)
import pygame
+from .focus import Focusable
from .parent import Parent
-class BaseRoot(Parent):
+class BaseRoot(Focusable, Parent):
BACKGROUND_COLOR: pygame.Color
ACTIVATE_EVENTS = (
pygame.WINDOWENTER,
key_methods = {frozenset(): {pygame.K_ESCAPE: handle_quit}}
- @property
- def is_focused(self):
- return self.focus_stack[-1] is self
+ def handle_events(self):
+ for ev in pygame.event.get():
+ self.stop_event = False
+ self.handle_event(ev)
def handle_event(self, ev):
if ev.type in self.ACTIVATE_EVENTS:
def run(self):
while True:
- for ev in pygame.event.get():
- self.stop_event = False
- self.handle_event(ev)
- if not self.running:
- return
+ self.handle_events()
+ if not self.running:
+ return
self.update()
+ if not self.running:
+ return
if self.dirty:
self.draw()
pygame.display.update()