Permalink
Browse files

squash wip commits

  • Loading branch information...
1 parent c068585 commit 5fd8b044214d90f9c4c163e8320b339f06f76973 @gvalkov committed Nov 6, 2012
Showing with 472 additions and 0 deletions.
  1. +28 −0 LICENSE
  2. +1 −0 MANIFEST.in
  3. +138 −0 README.rst
  4. +25 −0 pyzmo/__init__.py
  5. +99 −0 pyzmo/decorators.py
  6. +105 −0 pyzmo/handler.py
  7. +32 −0 pyzmo/util.py
  8. +44 −0 setup.py
View
28 LICENSE
@@ -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.
View
@@ -0,0 +1 @@
+include README.rst
View
@@ -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
+
View
@@ -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')
View
@@ -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.

0 comments on commit 5fd8b04

Please sign in to comment.