Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
8 changed files
with
472 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
Copyright (c) 2011 Georgi Valkov. All rights reserved. | ||
|
||
Redistribution and use in source and binary forms, with or without | ||
modification, are permitted provided that the following conditions are | ||
met: | ||
|
||
1. Redistributions of source code must retain the above copyright | ||
notice, this list of conditions and the following disclaimer. | ||
|
||
2. Redistributions in binary form must reproduce the above copyright | ||
notice, this list of conditions and the following disclaimer in | ||
the documentation and/or other materials provided with the | ||
distribution. | ||
|
||
3. Neither the name of author nor the names of its contributors may | ||
be used to endorse or promote products derived from this software | ||
without specific prior written permission. | ||
|
||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND | ||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
DISCLAIMED. IN NO EVENT SHALL GEORGI VALKOV BE LIABLE FOR ANY | ||
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | ||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | ||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | ||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | ||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
include README.rst |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,138 @@ | ||
Pyzmo | ||
===== | ||
|
||
Pyzmo is a small hotkey library for Python 2.7+ based on | ||
python-evdev_. It has support for keys, key chords (simultaneously | ||
pressed keys) and key sequences (keys pressed in succession). | ||
|
||
|
||
Usage | ||
===== | ||
|
||
:: | ||
|
||
from pyzmo import * | ||
from evdev.ecodes import * | ||
|
||
# triggered when KEY_PLAYPAUSE is pressed | ||
@key(KEY_PLAYPAUSE) | ||
def playpause(events): | ||
pass | ||
|
||
# triggered when KEY_B is pressed, held or released | ||
@key(KEY_B, states=['down', 'hold', 'up']) | ||
def back(events): | ||
pass | ||
|
||
# triggered when either KEY_C or KEY_D are pressed | ||
@key(KEY_C, KEY_D) | ||
def c_or_d(events): | ||
pass | ||
|
||
# triggered when KEY_LEFTCTRL, KEY_LEFTALT and KEY_DELETE are | ||
# pressed at the same time | ||
@chord(KEY_LEFTCTRL, KEY_LEFTALT, KEY_DELETE) | ||
def ctrlaltdel(events): | ||
pass | ||
|
||
# triggered when KEY_A, KEY_B and KEY_C are pressed one after the other | ||
@keyseq(KEY_A, KEY_B, KEY_C) | ||
def abc(events): | ||
pass | ||
|
||
# specifying multiple sequences for one callback (syntax applies for | ||
# @chord and @event as well) | ||
@keyseq( (KEY_1, KEY_2, KEY_3), (KEY_Z, KEY_X, KEY_C) ) | ||
def zxc_or_123(events): | ||
pass | ||
|
||
# each handler receives the list of input events, because of | ||
# which it was triggered | ||
@chord(e.KEY_LEFTMETA, e.KEY_A) | ||
def term(events): | ||
for event in events: | ||
print(event) # instance of evdev.events.InputEvent | ||
#event at 1352244701.749908, code 125, type 01, val 01 | ||
#event at 1352244701.861897, code 30, type 01, val 01 | ||
|
||
## Note 1: | ||
# If we define two triggers: | ||
# - @chord(KEY_LEFTCTRL, KEY_V) | ||
# - @key(KEY_V) | ||
# | ||
# Pressing 'ctrl-v' will run both their callbacks. To stop | ||
# processing any further triggers after a match is made, use: | ||
@chord(KEY_LEFTCTRL, KEY_V, quick=True) | ||
def copy(events): | ||
pass | ||
|
||
## Note 2: | ||
# Pyzmo can actually match arbitrary input events. The following | ||
# will be triggered on scroll-wheel movement: | ||
@event(EV_REL, REL_WHEEL, -1) | ||
def vertical_scroll(events): | ||
pass | ||
|
||
# start main loop | ||
poll('/dev/input/event1', '/dev/input/event2') | ||
|
||
# .. or if you wish to get exclusive access to a input device | ||
from evdev import InputDevice | ||
dev = InputDevice('/dev/input/eventX') | ||
dev.grab() | ||
poll(dev) | ||
|
||
|
||
You can avoid polluting the global namespace with:: | ||
|
||
from pyzmo import EventHandler | ||
from evdev import ecodes as e | ||
|
||
app = EventHandler('name') | ||
|
||
@app.key(e.KEY_F) | ||
def f(events): pass | ||
|
||
@app.poll(...) | ||
|
||
|
||
Installing | ||
---------- | ||
|
||
The latest stable version of pyzmo is available on pypi, while the | ||
development version can be installed from github: | ||
|
||
.. code-block:: bash | ||
$ pip install pyzmo # latest stable version | ||
$ pip install git+git://github.com/gvalkov/pyzmo.git # latest development version | ||
Alternatively, you can install it manually like any other python package: | ||
|
||
.. code-block:: bash | ||
$ git clone git@github.com:gvalkov/pyzmo.git | ||
$ cd pyzmo | ||
$ git reset --hard HEAD $versiontag | ||
$ python setup.py install | ||
Similar Projects | ||
---------------- | ||
|
||
- triggerhappy_ | ||
|
||
- actkbd_ | ||
|
||
|
||
License | ||
------- | ||
|
||
Pyzmo is released under the terms of the `New BSD License`_. | ||
|
||
|
||
.. _python-evdev: https://github.com:gvalkov/python-evdev.git | ||
.. _triggerhappy: https://github.com/wertarbyte/triggerhappy.git | ||
.. _actkbd: http://users.softlab.ece.ntua.gr/~thkala/projects/actkbd/ | ||
.. _`NEW BSD License`: https://raw.github.com/gvalkov/pyzmo/master/LICENSE | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
import functools | ||
from pyzmo.handler import EventHandler | ||
from evdev import ecodes | ||
|
||
|
||
default_handler = EventHandler('default-handler') | ||
|
||
def default_handler_wrap(name): | ||
'Return a callable that relays calls to a default handler.' | ||
|
||
# @functools.wraps(getattr(default_handler, name)) | ||
def wrapper(*args, **kw): | ||
return getattr(default_handler, name)(*args, **kw) | ||
|
||
return wrapper | ||
|
||
key = default_handler_wrap('key') | ||
chord = default_handler_wrap('chord') | ||
keyseq = default_handler_wrap('keyseq') | ||
event = default_handler_wrap('event') | ||
poll = default_handler_wrap('poll') | ||
|
||
|
||
__all__ = ('key', 'chord', 'keyseq', 'event', 'poll', 'default_handler', | ||
'EventHandler', 'ecodes') |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
import logging | ||
|
||
from evdev import ecodes as e | ||
from pyzmo.util import maketuple, raise_on_unknown_key | ||
|
||
log = logging.getLogger('decorator') | ||
|
||
|
||
class Decorator: | ||
def __init__(self, triggers, *events, **options): | ||
''' | ||
@param triggers: reference to EventHandler.triggers | ||
@param events: list of events - subclass specific | ||
@param options: dictionary of options | ||
''' | ||
|
||
self.triggers = triggers | ||
self.options = options | ||
self.evseq = tuple(self.create_event_sequence(events)) | ||
|
||
def create_event_sequence(self, events): | ||
raise NotImplementedError | ||
|
||
def __call__(self, func): | ||
for seq in self.evseq: | ||
self.triggers[seq] = (func, self.options) | ||
return func | ||
|
||
class EventDecorator(Decorator): | ||
''' | ||
Arbitrary input event decorator. Example: | ||
@event(EV_REL, REL_WHEEL, -1) | ||
@event((EV_REL, REL_WHEEL, 1), (EV_REL, REL_HWHEEL, 1)) | ||
The latter is used to specify multiple events for a single | ||
callback (not a sequence of events). | ||
''' | ||
|
||
def create_event_sequence(self, events): | ||
for ev in maketuple(events): | ||
yield (ev,) | ||
|
||
class KeyDecorator(Decorator): | ||
''' | ||
Single key (EV_KEY or EV_BTN) decorator. Example: | ||
@key(KEY_A) | ||
@key(KEY_B, KEY_C) | ||
@key(KEY_D, states=('up', 'down', 'hold')) | ||
By default callbacks will be triggered once the key is | ||
pressed (value 1). The ``states`` options | ||
''' | ||
def create_event_sequence(self, keys): | ||
states = self.options.get('states', ['down']) | ||
|
||
for pair in maketuple(keys): | ||
for key in pair: | ||
raise_on_unknown_key(key) | ||
for state in states: | ||
val = {'up':0, 'down':1, 'hold':2}[state] | ||
ev = [(e.EV_KEY, key, val)] | ||
yield tuple(ev) | ||
|
||
class ChordDecorator(Decorator): | ||
''' | ||
A combination of keys, pressed simultaneously. Example: | ||
@chord(KEY_LEFTCTRL, KEY_LEFTALT, KEY_DELETE) | ||
@chord((KEY_LEFTCTRL, KEY_C), (KEY_LEFTCTRL, KEY_X)) | ||
''' | ||
def create_event_sequence(self, keys): | ||
for pair in maketuple(keys): | ||
seq = [] | ||
for key in pair: | ||
raise_on_unknown_key(key) | ||
ev = (e.EV_KEY, key, 1) | ||
seq.append(ev) | ||
|
||
yield tuple(seq) | ||
|
||
class SequenceDecorator(Decorator): | ||
''' | ||
A sequence of keys, pressed one after the other. Example: | ||
@keyseq(KEY_Q, KEY_W, KEY_E) | ||
@keyseq((KEY_A, KEY_B, KEY_C), (KEY_1, KEY_2, KEY_3)) | ||
''' | ||
def create_event_sequence(self, keys): | ||
for pair in maketuple(keys): | ||
seq = [] | ||
for key in pair: | ||
raise_on_unknown_key(key) | ||
events = ((e.EV_KEY, key, 1), (e.EV_KEY, key, 0)) | ||
for ev in events: | ||
seq.append(ev) | ||
|
||
yield tuple(seq) |
Oops, something went wrong.