diff options
-rw-r--r-- | README.rst | 18 | ||||
-rwxr-xr-x | mpv-test.py | 35 | ||||
-rw-r--r-- | mpv.py | 32 |
3 files changed, 74 insertions, 11 deletions
@@ -85,16 +85,14 @@ Advanced Usage # Option access, in general these require the core to reinitialize player['vo'] = 'opengl' - def my_q_binding(state, key): - if state[0] == 'd': - print('THERE IS NO ESCAPE') - player.register_key_binding('q', my_q_binding) - - def my_s_binding(state, key): - if state[0] == 'd': - pillow_img = player.screenshot_raw() - pillow_img.save('screenshot.png') - player.register_key_binding('s', my_s_binding) + @player.on_key_press('q') + def my_q_binding(): + print('THERE IS NO ESCAPE') + + @player.on_key_press('s') + def my_s_binding(): + pillow_img = player.screenshot_raw() + pillow_img.save('screenshot.png') player.play('https://youtu.be/DLzxrzFCyOs') player.wait_for_playback() diff --git a/mpv-test.py b/mpv-test.py index d829dba..96bf225 100755 --- a/mpv-test.py +++ b/mpv-test.py @@ -316,6 +316,41 @@ class KeyBindingTest(MpvTestCase): self.assertNotIn(b('b'), self.m._key_binding_handlers) self.assertIn(b('c'), self.m._key_binding_handlers) + def test_register_simple_decorator_fun_chaining(self): + b = mpv.MPV._binding_name + + handler1, handler2 = mock.Mock(), mock.Mock() + + @self.m.on_key_press('a') + @self.m.on_key_press('b') + def reg_test_fun(*args, **kwargs): + handler1(*args, **kwargs) + + @self.m.on_key_press('c') + def reg_test_fun_2_stay_intact(*args, **kwargs): + handler2(*args, **kwargs) + + self.assertEqual(reg_test_fun.mpv_key_bindings, ['b', 'a']) + self.assertIn(b('a'), self.m._key_binding_handlers) + self.assertIn(b('b'), self.m._key_binding_handlers) + self.assertIn(b('c'), self.m._key_binding_handlers) + + self.m._key_binding_handlers[b('a')]('p-', 'q') + handler1.assert_has_calls([ mock.call() ]) + handler2.assert_has_calls([]) + handler1.reset_mock() + self.m._key_binding_handlers[b('b')]('p-', 'q') + handler1.assert_has_calls([ mock.call() ]) + handler2.assert_has_calls([]) + self.m._key_binding_handlers[b('c')]('p-', 'q') + handler1.assert_has_calls([]) + handler2.assert_has_calls([ mock.call() ]) + + reg_test_fun.unregister_mpv_key_bindings() + self.assertNotIn(b('a'), self.m._key_binding_handlers) + self.assertNotIn(b('b'), self.m._key_binding_handlers) + self.assertIn(b('c'), self.m._key_binding_handlers) + class TestLifecycle(unittest.TestCase): def test_create_destroy(self): thread_names = lambda: [ t.name for t in threading.enumerate() ] @@ -915,8 +915,38 @@ class MPV(object): def _binding_name(callback_or_cmd): return 'py_kb_{:016x}'.format(hash(callback_or_cmd)&0xffffffffffffffff) + def on_key_press(self, keydef, mode='force'): + """ Function decorator to register a simplified key binding. The callback is called whenever the key + given is *pressed*. + + To unregister the callback function, you can call its ```unregister_mpv_key_bindings``` attribute: + + ``` + player = mpv.MPV() + @player.on_key_press('Q') + def binding(): + print('blep') + + binding.unregister_mpv_key_bindings() + ``` + + WARNING: For a single keydef only a single callback/command can be registered at the same time. If you register + a binding multiple times older bindings will be overwritten and there is a possibility of references leaking. So + don't do that. + + The BIG FAT WARNING regarding untrusted keydefs from the key_binding method applies here as well. """ + + def register(fun): + @self.key_binding(keydef, mode) + @wraps(fun) + def wrapper(state='p-', name=None): + if state[0] in ('d', 'p'): + fun() + return wrapper + return register + def key_binding(self, keydef, mode='force'): - """ Function decorator to register a key binding. + """ Function decorator to register a low-level key binding. The callback function signature is ```fun(key_state, key_name)``` where ```key_state``` is either ```'U'``` for "key up" or ```'D'``` for "key down". |