Skip to content

Commit

Permalink
Merge pull request #3 from quillcraftsman/capturing
Browse files Browse the repository at this point in the history
Capturing keeboard
  • Loading branch information
quillcraftsman committed Feb 28, 2024
2 parents f70334a + 361909d commit d85dccb
Show file tree
Hide file tree
Showing 30 changed files with 543 additions and 44 deletions.
2 changes: 2 additions & 0 deletions .coveragerc
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[run]
omit = ./testing/*
7 changes: 4 additions & 3 deletions .github/workflows/run-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ jobs:
python -m pip install --upgrade pip
pip install -r requirements.txt
pip install -r dev-requirements.txt
- name: Test with pytest
run: |
make coverage
- name: Run pytest with xvfb
uses: coactions/setup-xvfb@v1
with:
run: make coverage
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -157,4 +157,7 @@ cython_debug/
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
.idea/
.idea/

# sequence files
*.sequence
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,9 +92,8 @@ See more in [Full Documentation](https://replaywizard.craftsman.lol/install.html
## Quickstart

```python
from replay_wizard import info

print(info())
from replay_wizard.capturing import capture
capture('one')
```

### More examples in [Full Documentation][documentation_path]
Expand Down Expand Up @@ -139,12 +138,13 @@ This features will be built during 4 weeks.

- python (library was tested with **3.10**, **3.11** versions)
- pynput (this package use [LGPL-3.0 license](https://github.com/moses-palmer/pynput/blob/master/COPYING.LGPL) and used in this project as a third party library without modifications)
- pydantic

See more in [Full Documentation](https://replaywizard.craftsman.lol/about.html#requirements)

## Development Status

- Package available soon on [PyPi](https://pypi.org/project/replay-wizard/)
- Package available on [PyPi](https://pypi.org/project/replay-wizard/)

See more in [Full Documentation](https://replaywizard.craftsman.lol/about.html#development-status)

Expand Down
7 changes: 7 additions & 0 deletions main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
"""
Manual run module
"""
from quickstart.main import main

if __name__ == '__main__':
main()
3 changes: 3 additions & 0 deletions pylintrc
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
[MAIN]
ignore-paths=./replay_wizard/package.py
disable=
W0621, # Redefining name outer scope (pytest fixtures)
R0903, # To few public methods
4 changes: 2 additions & 2 deletions quickstart/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,6 @@ def main():
ReplayWizard simple usage
:return:
"""
from replay_wizard import info # pylint: disable=import-outside-toplevel
from replay_wizard.capturing import capture # pylint: disable=import-outside-toplevel

print(info())
capture('one')
4 changes: 2 additions & 2 deletions replay_wizard/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""
PyGenesis package
ReplayWizard package
"""
from .main import info
#from .capturing import capture
4 changes: 4 additions & 0 deletions replay_wizard/capturing/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
"""
Capturing package
"""
from .capture import capture
35 changes: 35 additions & 0 deletions replay_wizard/capturing/capture.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
"""
Capture process module
"""
import json
import pynput
from replay_wizard.models import Sequence
from .keyboard import on_press, on_release


def capture(name):
"""
capture user actions
:param name: sequence name
"""
sequence = Sequence(
name=name
)

def on_press_handler(key):
return on_press(sequence, key)

def on_release_handler(key):
return on_release(sequence, key)

with pynput.keyboard.Listener(
on_press=on_press_handler,
on_release=on_release_handler) as listener:
listener.join()

# save result
result_dict = sequence.model_dump()
file_name = f'{name}.sequence'
with open(file_name, 'w', encoding='utf-8') as f:
json.dump(result_dict, f)
15 changes: 15 additions & 0 deletions replay_wizard/capturing/errors.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
"""
Capturing Errors
"""


class UnknownKeyError(Exception):
"""
Input key was unknown (pynput return None)
"""

def __str__(self):
"""
Exception to str
"""
return 'Key was unknown (None value)'
93 changes: 93 additions & 0 deletions replay_wizard/capturing/keyboard.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
"""
Capturing keyboard events
"""
from pynput.keyboard import Key, KeyCode

from replay_wizard.capturing.errors import UnknownKeyError
from replay_wizard.models import Sequence, ActionEnum, Action, Subtypes


def key_to_value(key):
"""
Convert key different types to action value
:param key: key to convert
"""
if isinstance(key, Key):
_, name = str(key).split('.')
return name
if isinstance(key, KeyCode):
return key.char
if key is None:
raise UnknownKeyError
raise ValueError(key)


def on_key_input(sequence: Sequence, key, action_type: ActionEnum):
"""
Key input
:param sequence: current sequence
:param key: pressed key
:param action: action type
"""
value = key_to_value(key)
if value == 'esc':
return False

action = Action(
subtype=Subtypes.KEYBOARD,
value=key_to_value(key),
action=action_type,
timedelta=0,
)
sequence.add(action)
return True


def on_press(sequence: Sequence, key):
"""
Key was pressed
:param sequence: current sequence
:param key: pressed key
"""
return on_key_input(sequence, key, ActionEnum.PRESS)


def on_release(sequence: Sequence, key):
"""
Key was release
:param sequence: current sequence
:param key: pressed key
"""
return on_key_input(sequence, key, ActionEnum.RELEASE)

#
#
# def on_release(key, f):
# if key == keyboard.Key.esc:
# # Stop listener
# return False
# print('{0} released'.format(
# key))
# f.write(f'{key.char} released\n')
#
#
# def capture(name):
# file_name = f'{name}.sequence'
# with open(file_name, 'w', encoding='utf-8') as f:
# on_press_handler = lambda key: on_press(key, f)
# on_release_handler = lambda key: on_release(key, f)
#
# with keyboard.Listener(
# on_press=on_press_handler,
# on_release=on_release_handler) as listener:
# listener.join()
#
# # ...or, in a non-blocking fashion:
# # listener = keyboard.Listener(
# # on_press=on_press,
# # on_release=on_release)
# # listener.start()
13 changes: 0 additions & 13 deletions replay_wizard/main.py
Original file line number Diff line number Diff line change
@@ -1,13 +0,0 @@
"""
Main module
"""


def info():
"""
ReplayWizard info
:return: ReplayWizard usage info
"""
result = ('This is the mock of ReplayWizard package: '
'https://github.com/quillcraftsman/replay-wizard')
return result
5 changes: 5 additions & 0 deletions replay_wizard/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
"""
Models package
"""
from .action import Action, Subtypes, ActionEnum
from .sequence import Sequence
32 changes: 32 additions & 0 deletions replay_wizard/models/action.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
"""
Action module
"""
from enum import Enum
from pydantic import BaseModel, ConfigDict


class Subtypes(str, Enum):
"""
Action subtype
"""
KEYBOARD = 'keyboard'
MOUSE = 'mouse'


class ActionEnum(str, Enum):
"""
press or release
"""
PRESS = 'press'
RELEASE = 'release'


class Action(BaseModel):
"""
Action model
"""
model_config = ConfigDict(frozen=True)

subtype: Subtypes
value: str
action: ActionEnum = ActionEnum.PRESS
32 changes: 32 additions & 0 deletions replay_wizard/models/sequence.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
"""
Sequence module
"""
from typing import List
from pydantic import BaseModel
from .action import Action


class Sequence(BaseModel):
"""
Action sequence
"""
name: str
actions: List[Action] = []

def __len__(self):
return len(self.actions)

def add(self, new_action: Action):
"""
Add action to sequence
:param new_action: Action to add
:return: None
"""
self.actions.append(new_action)

def __contains__(self, item):
"""
in method
"""
return item in self.actions
2 changes: 1 addition & 1 deletion replay_wizard/package.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@
Package info
"""
name = 'replay-wizard'
version = '0.0.1'
version = '0.0.2'
status = '2 - Pre-Alpha'
3 changes: 2 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
pynput==1.7.6
pynput==1.7.6
pydantic==2.6.3
7 changes: 4 additions & 3 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,12 @@ def get_value_from_package_info(line, value, old_value):
PACKAGE_NAME = "replay_wizard"

PROJECT_URLS = {
# 'Documentation': 'https://replaywizard.org',
'Documentation': 'https://replaywizard.craftsman.lol/',
'Source': 'https://github.com/quillcraftsman/replay-wizard',
'Tracker': 'https://github.com/quillcraftsman/replay-wizard/issues',
'Release notes': 'https://github.com/quillcraftsman/replay-wizard/releases',
'Changelog': 'https://github.com/quillcraftsman/replay-wizard/releases',
# 'Download': 'https://pypi.org/project/replay-wizard/',
'Download': 'https://pypi.org/project/replay-wizard/',
}

with open_local([PACKAGE_NAME, "package.py"]) as fp:
Expand Down Expand Up @@ -91,6 +91,7 @@ def read(filename):
],
install_requires=[
'pynput==1.7.6',
'pydantic==2.6.3',
],
python_requires=">=3",
classifiers=[
Expand All @@ -104,5 +105,5 @@ def read(filename):
"Topic :: Home Automation",
"Topic :: Scientific/Engineering :: Artificial Intelligence",
],
project_urls= PROJECT_URLS,
project_urls=PROJECT_URLS,
)
14 changes: 0 additions & 14 deletions testing/test_pygenesis/test_main.py

This file was deleted.

File renamed without changes.
15 changes: 15 additions & 0 deletions testing/test_replay_wizard/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
"""
Pytest fixtures
"""
import pytest
from replay_wizard.models import Sequence

@pytest.fixture
def empty_sequence():
"""
Empty sequence fixture
"""
return Sequence(
name='open youtube',
actions=[]
)
Empty file.
Loading

0 comments on commit d85dccb

Please sign in to comment.