Skip to content

Commit

Permalink
plover/machine/keyboard: Refactor key press delay code into generic k…
Browse files Browse the repository at this point in the history
…eyboard emulation machine
  • Loading branch information
sammdot committed Sep 27, 2023
1 parent cfcc65a commit 6a88de0
Show file tree
Hide file tree
Showing 4 changed files with 36 additions and 58 deletions.
30 changes: 5 additions & 25 deletions plover/oslayer/linux/keyboardcontrol_x11.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
import os
import select
import threading
from time import sleep

from Xlib import X, XK
from Xlib.display import Display
Expand All @@ -34,7 +33,7 @@
from plover import log
from plover.key_combo import add_modifiers_aliases, parse_key_combo
from plover.machine.keyboard_capture import Capture
from plover.output import Output
from plover.output.keyboard import GenericKeyboardEmulation


# Enable support for media keys.
Expand Down Expand Up @@ -1128,7 +1127,7 @@ def keysym_to_string(keysym):
return chr(code)


class KeyboardEmulation(Output):
class KeyboardEmulation(GenericKeyboardEmulation):

class Mapping:

Expand Down Expand Up @@ -1158,7 +1157,6 @@ def __init__(self):
super().__init__()
self._display = Display()
self._update_keymap()
self._key_press_delay = 0

def _update_keymap(self):
'''Analyse keymap, build a mapping of keysym to (keycode + modifiers),
Expand Down Expand Up @@ -1218,20 +1216,14 @@ def _update_keymap(self):
# Get modifier mapping.
self.modifier_mapping = self._display.get_modifier_mapping()

def set_key_press_delay(self, delay_ms):
self._key_press_delay = delay_ms

def send_backspaces(self, count):
for x in range(count):
for x in self.with_delay(range(count)):
self._send_keycode(self._backspace_mapping.keycode,
self._backspace_mapping.modifiers)
self._display.sync()

if self._key_press_delay > 0:
sleep(self._key_press_delay / 1000)

def send_string(self, string):
for char in string:
for char in self.with_delay(string):
keysym = uchr_to_keysym(char)
# TODO: can we find mappings for multiple keys at a time?
mapping = self._get_mapping(keysym, automatically_map=False)
Expand All @@ -1240,36 +1232,24 @@ def send_string(self, string):
mapping = self._get_mapping(keysym, automatically_map=True)
if mapping is None:
continue
if self._key_press_delay > 0:
self._display.sync()
sleep(self._key_press_delay / 2000)
mapping_changed = True

self._send_keycode(mapping.keycode,
mapping.modifiers)

self._display.sync()

if self._key_press_delay > 0:
if mapping_changed:
sleep(self._key_press_delay / 2000)
else:
sleep(self._key_press_delay / 1000)

def send_key_combination(self, combo):
# Parse and validate combo.
key_events = [
(keycode, X.KeyPress if pressed else X.KeyRelease) for keycode, pressed
in parse_key_combo(combo, self._get_keycode_from_keystring)
]
# Emulate the key combination by sending key events.
for keycode, event_type in key_events:
for keycode, event_type in self.with_delay(key_events):
xtest.fake_input(self._display, event_type, keycode)
self._display.sync()

if self._key_press_delay > 0:
sleep(self._key_press_delay / 1000)

def _send_keycode(self, keycode, modifiers=0):
"""Emulate a key press and release.
Expand Down
19 changes: 5 additions & 14 deletions plover/oslayer/osx/keyboardcontrol.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
from plover import log
from plover.key_combo import add_modifiers_aliases, parse_key_combo, KEYNAME_TO_CHAR
from plover.machine.keyboard_capture import Capture
from plover.output import Output
from plover.output.keyboard import GenericKeyboardEmulation

from .keyboardlayout import KeyboardLayout

Expand Down Expand Up @@ -302,28 +302,25 @@ def _run(self):
self.key_down(key)


class KeyboardEmulation(Output):
class KeyboardEmulation(GenericKeyboardEmulation):

RAW_PRESS, STRING_PRESS = range(2)

def __init__(self):
super().__init__()
self._layout = KeyboardLayout()
self._key_press_delay = 0

def set_key_press_delay(self, delay_ms):
self._key_press_delay = delay_ms

def send_backspaces(self, count):
for _ in range(count):
for _ in self.with_delay(range(count)):
backspace_down = CGEventCreateKeyboardEvent(
OUTPUT_SOURCE, BACK_SPACE, True)
backspace_up = CGEventCreateKeyboardEvent(
OUTPUT_SOURCE, BACK_SPACE, False)
CGEventPost(kCGSessionEventTap, backspace_down)
CGEventPost(kCGSessionEventTap, backspace_up)
if self._key_press_delay > 0:
sleep(self._key_press_delay / 1000)

def send_string(self, string):
# Key plan will store the type of output
Expand Down Expand Up @@ -366,15 +363,12 @@ def apply_raw():
apply_raw()

# We have a key plan for the whole string, grouping modifiers.
for press_type, sequence in key_plan:
for press_type, sequence in self.with_delay(key_plan):
if press_type is self.STRING_PRESS:
self._send_string_press(sequence)
elif press_type is self.RAW_PRESS:
self._send_sequence(sequence)

if self._key_press_delay > 0:
sleep(self._key_press_delay / 1000)

@staticmethod
def _send_string_press(c):
event = CGEventCreateKeyboardEvent(OUTPUT_SOURCE, 0, True)
Expand Down Expand Up @@ -405,9 +399,6 @@ def name_to_code(name):
# Send events...
self._send_sequence(key_events)

if self._key_press_delay > 0:
sleep(self._key_press_delay / 1000)

@staticmethod
def _modifier_to_keycodes(modifier):
keycodes = []
Expand Down Expand Up @@ -456,7 +447,7 @@ def _send_sequence(sequence):
# If mods_flags is not zero at the end then bad things might happen.
mods_flags = 0

for keycode, key_down in sequence:
for keycode, key_down in self.with_delay(sequence):
if keycode >= NX_KEY_OFFSET:
# Handle media (NX) key.
event = KeyboardEmulation._get_media_event(
Expand Down
24 changes: 5 additions & 19 deletions plover/oslayer/windows/keyboardcontrol.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
"""

from ctypes import windll, wintypes
from time import sleep
import atexit
import ctypes
import multiprocessing
Expand All @@ -27,7 +26,7 @@
from plover.key_combo import parse_key_combo
from plover.machine.keyboard_capture import Capture
from plover.misc import to_surrogate_pair
from plover.output import Output
from plover.output.keyboard import GenericKeyboardEmulation

from .keyboardlayout import KeyboardLayout

Expand Down Expand Up @@ -426,12 +425,11 @@ def suppress(self, suppressed_keys=()):
self._proc.suppress(self._suppressed_keys)


class KeyboardEmulation(Output):
class KeyboardEmulation(GenericKeyboardEmulation):

def __init__(self):
super().__init__()
self.keyboard_layout = KeyboardLayout()
self._key_press_delay = 0

# Sends input types to buffer
@staticmethod
Expand Down Expand Up @@ -499,37 +497,25 @@ def _key_unicode(self, char):
for code in pairs]
self._send_input(*inputs)

def set_key_press_delay(self, delay_ms):
self._key_press_delay = delay_ms

def send_backspaces(self, count):
for _ in range(count):
for _ in self.with_delay(range(count)):
self._key_press('\x08')

if self._key_press_delay > 0:
sleep(self._key_press_delay / 1000)

def send_string(self, string):
self._refresh_keyboard_layout()
for char in string:
for char in self.with_delay(string):
if char in self.keyboard_layout.char_to_vk_ss:
# We know how to simulate the character.
self._key_press(char)
else:
# Otherwise, we send it as a Unicode string.
self._key_unicode(char)

if self._key_press_delay > 0:
sleep(self._key_press_delay / 1000)

def send_key_combination(self, combo):
# Make sure keyboard layout is up-to-date.
self._refresh_keyboard_layout()
# Parse and validate combo.
key_events = parse_key_combo(combo, self.keyboard_layout.keyname_to_vk.get)
# Send events...
for keycode, pressed in key_events:
for keycode, pressed in self.with_delay(key_events):
self._key_event(keycode, pressed)

if self._key_press_delay > 0:
sleep(self._key_press_delay / 1000)
21 changes: 21 additions & 0 deletions plover/output/keyboard.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
from time import sleep

from plover.output import Output


class GenericKeyboardEmulation(Output):
def __init__(self):
super().__init__()
self._key_press_delay_ms = 0

def set_key_press_delay(self, delay_ms):
self._key_press_delay_ms = delay_ms

def delay(self):
if self._key_press_delay_ms > 0:
sleep(self._key_press_delay_ms / 1000)

def with_delay(self, iterable):
for item in iterable:
yield item
self.delay()

0 comments on commit 6a88de0

Please sign in to comment.