Skip to content

Commit

Permalink
Config refactor; Added send_press_duration
Browse files Browse the repository at this point in the history
  • Loading branch information
IGalat committed Apr 7, 2024
1 parent 384dd34 commit 6662221
Show file tree
Hide file tree
Showing 16 changed files with 218 additions and 68 deletions.
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -319,12 +319,12 @@ By default, trigger key is suppressed:
Tap("a+b", "hoy")
```

This will result in `ahoy`, as `b` is suppressed. You can modify this per-Tap/Group, or default in config:
This will result in `ahoy`, as `b` is suppressed. You can modify this per-Tap/Group, including on `root`:

```python
default_trigger_suppression = ListenerResult.PROPAGATE
root.suppress_trigger = False
# or
Tap("h+e", "llo", suppress_trigger=ListenerResult.PROPAGATE)
Tap("h+e", "llo", suppress_trigger=False)
```

This will result in `hello` not `hllo`.
Expand Down
5 changes: 3 additions & 2 deletions docs/dev/plans.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,11 @@ Small change plans:

Potential ideas:

- skip - DONE(Only keyboard). Linux impl
- skip - Make sure it catches signals before other programs on OS
- DONE(Only keyboard). Linux impl
- Make sure it catches signals before other programs on OS
- Action queue, maybe with time limit when put? i.e. I press "ab" quickly, both are hotkeys, and if "a" is still going
on the last millisecond, "b" can trigger afterwards. Whole can of worms though.
- trigger conditions: have IDE autocomplete


## Limitations of the current design
Expand Down
11 changes: 9 additions & 2 deletions src/tapper/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,14 @@
Group = _tap_tree.Group
"""Group of Taps, and/or other Groups."""

root = Group("root")
"""Root group, the parent to all except control group."""
root = Group(
"root",
executor=0,
suppress_trigger=True,
send_interval=0.01,
send_press_duration=0.01,
)
"""Root group, the parent to all except control group. For config options doc, see `Tap`"""

control_group = Group("control_group")
"""
Expand Down Expand Up @@ -55,6 +61,7 @@ def init() -> None:
"""Initializes all underlying tools."""
global _listeners
global _initialized
_initializer.set_default_controls_if_empty(control_group)
_listeners = _initializer.init(root, control_group, _send_processor, send)
_initialized = True

Expand Down
36 changes: 36 additions & 0 deletions src/tapper/action/wrapper.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import threading
from dataclasses import dataclass

from tapper.model import types_
from tapper.model.tap_tree_shadow import STap


@dataclass
class ActionConfig:
"""Config for a single action. See tap_tree for info on configs."""

send_interval: float
send_press_duration: float

@classmethod
def from_tap(cls, tap: STap) -> "ActionConfig":
return ActionConfig(
send_interval=tap.send_interval, send_press_duration=tap.send_press_duration
)


config_thread_local_storage = threading.local()


def wrapped_action(tap: STap) -> types_.Action:
"""
:param tap: source.
:return: Action that sets config before running.
"""
config = ActionConfig.from_tap(tap)

def wrapped() -> None:
config_thread_local_storage.action_config = config
tap.action()

return wrapped
33 changes: 22 additions & 11 deletions src/tapper/boot/initializer.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
from tapper.parser.send_parser import SendParser
from tapper.parser.trigger_parser import TriggerParser
from tapper.signal.base_listener import SignalListener
from tapper.signal.processor import SignalProcessor
from tapper.signal.signal_processor import SignalProcessor
from tapper.signal.wrapper import ListenerWrapper
from tapper.state import keeper
from tapper.util import datastructs
Expand Down Expand Up @@ -81,10 +81,10 @@ def init(
transformer = TreeTransformer(
send, default_trigger_parser(os), config.kw_trigger_conditions
)
_fill_default_properties(iroot)
verify_settings_exist(iroot)
root = transformer.transform(iroot)
_fill_control_if_empty(icontrol)
_fill_default_properties(icontrol)
set_default_controls_if_empty(icontrol)
control_config_fill(icontrol)
control = transformer.transform(icontrol)

runner = default_action_runner()
Expand Down Expand Up @@ -122,7 +122,7 @@ def init(
return listeners


def _fill_control_if_empty(icontrol: Group) -> None:
def set_default_controls_if_empty(icontrol: Group) -> None:
"""Sets default controls if none."""
if not icontrol._children:
icontrol.add(
Expand All @@ -133,12 +133,23 @@ def _fill_control_if_empty(icontrol: Group) -> None:
)


def _fill_default_properties(group: Group) -> None:
"""Sets absent properties to defaults."""
if group.executor is None:
group.executor = 0
if group.suppress_trigger is None:
group.suppress_trigger = config.default_trigger_suppression
def control_config_fill(group: Group) -> None:
group.executor = group.executor or 0
group.suppress_trigger = group.suppress_trigger or constants.ListenerResult.SUPPRESS
group.send_interval = group.send_interval or 0
group.send_press_duration = group.send_press_duration or 0


def verify_settings_exist(group: Group) -> None:
if (
group.executor is None
or group.suppress_trigger is None
or group.send_interval is None
or group.send_press_duration is None
):
raise AttributeError(
f"Group '{group.name}' does not have mandatory configs set."
)


def start(listeners: list[SignalListener]) -> None:
Expand Down
14 changes: 13 additions & 1 deletion src/tapper/boot/tree_transformer.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ def __init__(
def transform(self, group: Group) -> SGroup:
result = SGroup()
result.original = group
result.send_interval = group.send_interval # type: ignore # it's not None here
result.send_press_duration = group.send_press_duration # type: ignore # it's not None here
result.trigger_conditions = transform_trigger_conditions(
self.possible_trigger_conditions, group.trigger_conditions
)
Expand All @@ -80,9 +82,15 @@ def _transform_tap(self, tap: Tap) -> STap:
executor = find_property("executor", tap._parent)
if (suppress_trigger := tap.suppress_trigger) is None:
suppress_trigger = find_property("suppress_trigger", tap._parent)
if (send_interval := tap.send_interval) is None:
send_interval = find_property("send_interval", tap._parent)
if (send_press_duration := tap.send_press_duration) is None:
send_press_duration = find_property("send_press_duration", tap._parent)
action = self.to_action(tap.action)
result = STap(trigger, action, executor, suppress_trigger)
result.original = tap
result.send_interval = send_interval
result.send_press_duration = send_press_duration
result.trigger_conditions = transform_trigger_conditions(
self.possible_trigger_conditions, tap.trigger_conditions
)
Expand All @@ -95,10 +103,14 @@ def _transform_dict(
result = []
executor = find_property("executor", parent)
suppress_trigger = find_property("suppress_trigger", parent)
send_interval = find_property("send_interval", parent)
send_press_duration = find_property("send_press_duration", parent)
for trigger_str, action in taps.items():
trigger = self.trigger_parser.parse(trigger_str)
action = self.to_action(action)
stap = STap(trigger, action, executor, suppress_trigger)
stap.send_interval = send_interval
stap.send_press_duration = send_press_duration
stap.trigger_conditions = []
result.append(stap)
return result
Expand All @@ -107,5 +119,5 @@ def to_action(self, action: Action | str) -> Action:
if isinstance(action, str):
if action in all_symbols and action not in keyboard.chars_en:
action = f"$({action})"
action = partial(self.send, action) # type: ignore
action = partial(self.send, action)
return action
7 changes: 0 additions & 7 deletions src/tapper/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,6 @@
------------------------------------
"""

default_send_interval = 0.01
"""
Interval between clicks for send command.
This is just a default value. You can use a different one in each send:
send("Hellow", interval=1)
"""

default_trigger_suppression = ListenerResult.SUPPRESS
"""
Will the triggering signal be suppressed for other apps, when a Tap triggers?
Expand Down
52 changes: 34 additions & 18 deletions src/tapper/controller/send_processor.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import time
from typing import Callable

from tapper.action.wrapper import config_thread_local_storage
from tapper.controller.keyboard.kb_api import KeyboardController
from tapper.controller.mouse.mouse_api import MouseController
from tapper.model import constants
Expand Down Expand Up @@ -31,42 +32,49 @@ def __init__(
parser: SendParser,
kb_controller: KeyboardController,
mouse_controller: MouseController,
default_interval: Callable[[], float],
) -> None:
self.os = os
self.parser = parser
self.kb_controller = kb_controller
self.mouse_controller = mouse_controller
self.default_interval = default_interval # type: ignore

@classmethod
def from_none(cls) -> "SendCommandProcessor":
"""To be filled during init."""
return SendCommandProcessor(None, None, None, None, None) # type: ignore
return SendCommandProcessor(None, None, None, None) # type: ignore

def send(
self, command: str, interval: float | None = None, speed: float = 1
self,
command: str,
interval: float | None = None,
press_duration: float | None = None,
speed: float = 1,
) -> None:
"""
Entry point, processes the command and sends instructions.
:param command: What to send.
:param interval: Time before each key/button press.
:param speed: All sleep commands are divided by this number. Does not influence interval.
:param press_duration: Time between key press and release, only applies on click, not on up/down.
:param speed: All sleep commands are divided by this number. Does not influence interval or press_duration.
"""
interval_ = interval or self.default_interval() # type: ignore
config = config_thread_local_storage.action_config
interval = interval if interval is not None else config.send_interval
press_duration = (
press_duration if press_duration is not None else config.send_press_duration
)

instructions: list[SendInstruction] = self.parser.parse(
command, self.shift_down()
)
for index, instruction in enumerate(instructions):
for instruction in instructions:
if isinstance(instruction, KeyInstruction):
if (
instruction.dir in [constants.KeyDir.DOWN, constants.KeyDir.CLICK]
and interval_
and index != 0
and interval
):
self.sleep_fn(interval_) # type: ignore
self._send_key_instruction(instruction)
self.sleep_fn(interval)
self._send_key_instruction(instruction, press_duration)
elif isinstance(instruction, WheelInstruction):
self.mouse_controller.press(instruction.wheel_symbol)
elif isinstance(instruction, CursorMoveInstruction):
Expand All @@ -83,7 +91,7 @@ def shift_down(self) -> str | None:
return shift
return None

def _send_key_instruction(self, ki: KeyInstruction) -> None:
def _send_key_instruction(self, ki: KeyInstruction, press_duration: float) -> None:
symbol = ki.symbol
cmd: KeyboardController | MouseController
if symbol in keyboard.get_keys(self.os):
Expand All @@ -98,15 +106,23 @@ def _send_key_instruction(self, ki: KeyInstruction) -> None:
elif ki.dir == constants.KeyDir.UP:
cmd.release(symbol)
elif ki.dir == constants.KeyDir.CLICK:
cmd.press(symbol)
cmd.release(symbol)
self._click(cmd, symbol, press_duration)
elif ki.dir == constants.KeyDir.ON:
if not cmd.toggled(symbol):
cmd.press(symbol)
cmd.release(symbol)
self._click(cmd, symbol, press_duration)
elif ki.dir == constants.KeyDir.OFF:
if cmd.toggled(symbol):
cmd.press(symbol)
cmd.release(symbol)
self._click(cmd, symbol, press_duration)
else:
raise SendError

def _click(
self,
cmd: KeyboardController | MouseController,
symbol: str,
press_duration: float,
) -> None:
cmd.press(symbol)
if press_duration:
self.sleep_fn(press_duration)
cmd.release(symbol)
19 changes: 17 additions & 2 deletions src/tapper/model/tap_tree.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,18 @@ class TapGeneric(ABC):
"""

executor: Optional[int]
"""Which executor to run the action in. see ActionRunner."""
"""Which executor to run the action in. see `ActionRunner`."""
suppress_trigger: Optional[ListenerResult]
"""Whether to suppress main key when an action is triggered."""
send_interval: Optional[float]
"""Time before keypress for `send` command, in seconds. Will be overridden if specified on `send` itself."""
send_press_duration: Optional[float]
"""
Time between key press and release, in seconds. Will be overridden if specified on `send` itself.
Only applies to click, not up/down. `e down;e up` will not have this time inbetween, `e` will.
"""
trigger_conditions: dict[str, Any]
"""Keyword trigger conditions that can be used as part of Tap or Group. See config for docs."""
"""Keyword trigger conditions that can be used as part of `Tap` or `Group`. See config for docs."""


@dataclass(init=False)
Expand All @@ -42,6 +49,8 @@ def __init__(
action: Action | str,
executor: Optional[int] = None,
suppress_trigger: Optional[bool] = None,
send_interval: Optional[float] = None,
send_press_duration: Optional[float] = None,
**trigger_conditions: Any,
) -> None:
self.trigger = trigger
Expand All @@ -50,6 +59,8 @@ def __init__(
self.suppress_trigger = (
ListenerResult(suppress_trigger) if suppress_trigger else None
)
self.send_interval = send_interval
self.send_press_duration = send_press_duration
self.trigger_conditions = trigger_conditions

def conditions(self, **trigger_conditions: Any) -> "Tap":
Expand Down Expand Up @@ -95,13 +106,17 @@ def __init__(
name: Optional[str] = None,
executor: Optional[int] = None,
suppress_trigger: Optional[bool] = None,
send_interval: Optional[float] = None,
send_press_duration: Optional[float] = None,
**trigger_conditions: Any,
) -> None:
self.name = name
self.executor = executor
self.suppress_trigger = (
ListenerResult(suppress_trigger) if suppress_trigger else None
)
self.send_interval = send_interval
self.send_press_duration = send_press_duration
self.trigger_conditions = trigger_conditions

self._children = []
Expand Down
2 changes: 2 additions & 0 deletions src/tapper/model/tap_tree_shadow.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ class STapGeneric(ABC):

original: Optional[TapGeneric]
"""Item based on which this shadow is created."""
send_interval: float
send_press_duration: float
trigger_conditions: list[TriggerConditionFn]

@abstractmethod
Expand Down
Loading

0 comments on commit 6662221

Please sign in to comment.