diff --git a/README.md b/README.md index 11c11e8..253de44 100644 --- a/README.md +++ b/README.md @@ -298,6 +298,19 @@ numeric key 0 Switch from single view to grid view mode and unpause rotati escape Switch from single view to grid view mode and unpause rotation. letter 'q' Quit camplayer. ``` +## Mouse gestures +Please note there is no visible mouse. Nor any absolute positioning. So you cannot click directly on +a specific camera. To view a specific camera in single view, scroll down for the first camera, then +continue to scroll or gesture left/right for the other cameras. +``` +direct control +- click right Pause/unpause automatic screen rotation. +- scroll down/up Switch from grid to single view mode and back. +while holding left button down +- left/right move Switch to previous/next screen (or window in single view mode). +- up/down move Increase/decrease stream quality (if multiple subchannels/substreams configured). +quit sequence Left down; Right down; Left release; Right release +``` ## Roadmap ### Camplayer 2 diff --git a/camplayer/camplayer.py b/camplayer/camplayer.py index 5e85b4a..8a37031 100644 --- a/camplayer/camplayer.py +++ b/camplayer/camplayer.py @@ -310,4 +310,4 @@ def main(): if __name__ == "__main__": - main() \ No newline at end of file + main() diff --git a/camplayer/utils/inputhandler.py b/camplayer/utils/inputhandler.py index 79de6da..a745148 100644 --- a/camplayer/utils/inputhandler.py +++ b/camplayer/utils/inputhandler.py @@ -18,6 +18,12 @@ def __init__(self, event_type=['release', 'press', 'hold'], scan_interval=2500): self._event_hold = True if 'hold' in event_type else False self._running = True self._monitor_thread = threading.Thread(target=self._monitor, daemon=True).start() + self._mouse_inhibit = time.monotonic() + self._mouse_inhibit_duration = 0.5 + self._mouse_btn_state = 0 + self._mouse_abs_x = 500 + self._mouse_abs_y = 500 + self._mouse_quit_sequence = 0 def destroy(self): """Stop monitoring thread""" @@ -62,13 +68,102 @@ def _monitor(self): while True: event = device.read_one() if event: + # keyboard and button events if event.type == evdev.ecodes.EV_KEY: - if self._event_up and event.value == 0: + + # mouse buttons + if ( event.code in {evdev.ecodes.BTN_MOUSE, + evdev.ecodes.BTN_RIGHT, + evdev.ecodes.BTN_MIDDLE} ): + # Left button + if event.code == evdev.ecodes.BTN_MOUSE: + # Left click: track mouse button state + self._mouse_btn_state = event.value + # Left down: set Quit sequence tracker + if event.value == 1: + self._mouse_quit_sequence = 1 + # Left release: advance Quit sequence tracker + elif event.value == 0 and self._mouse_quit_sequence == 2: + self._mouse_quit_sequence = 3 + # Right button + elif event.code == evdev.ecodes.BTN_RIGHT: + # Right down: advance Quit sequence tracker + if event.value == 1 and self._mouse_quit_sequence == 1: + self._mouse_quit_sequence = 2 + # Right release, as completion of Quit sequence: Quit program + elif event.value == 0 and self._mouse_quit_sequence == 3: + event.type = evdev.ecodes.EV_KEY + event.code = evdev.ecodes.KEY_Q + self._event_queue.put_nowait(event) + # Right release, while left is still down: Blank screen + elif event.value == 0 and self._mouse_btn_state == 1: + self._mouse_quit_sequence = 0 + event.type = evdev.ecodes.EV_KEY + event.code = evdev.ecodes.KEY_B + self._event_queue.put_nowait(event) + # Right release, while left is not down: Pause autorotate + elif event.value == 0 and self._mouse_btn_state == 1: + self._mouse_quit_sequence = 0 + event.type = evdev.ecodes.EV_KEY + event.code = evdev.ecodes.KEY_SPACE + self._event_queue.put_nowait(event) + + # other keys / keyboard keys + elif self._event_up and event.value == 0: self._event_queue.put_nowait(event) elif self._event_down and event.value == 1: self._event_queue.put_nowait(event) elif self._event_hold and event.value == 2: self._event_queue.put_nowait(event) + + # mouse movement events + elif event.type == evdev.ecodes.EV_REL: + + # unused for now, track absolute position + if event.code == evdev.ecodes.REL_X: + self._mouse_abs_x += event.value; + self._mouse_abs_x = min(1920, self._mouse_abs_x) + self._mouse_abs_x = max(1, self._mouse_abs_x) + elif event.code == evdev.ecodes.REL_Y: + self._mouse_abs_y += event.value; + self._mouse_abs_y = min(1080, self._mouse_abs_y) + self._mouse_abs_y = max(1, self._mouse_abs_y) + + # Gestures, only one per timeslot. + if (time.monotonic() > self._mouse_inhibit + self._mouse_inhibit_duration): + # wheel up/down is zoom in/out, iow, single/grid view + if ( event.code == evdev.ecodes.REL_WHEEL + and abs(event.value) > 0 ): + if event.value > 0: + event.code = evdev.ecodes.KEY_ESC + else: + event.code = evdev.ecodes.KEY_ENTER + event.type = evdev.ecodes.EV_KEY + self._mouse_inhibit = time.monotonic() + self._event_queue.put_nowait(event) + # move left/right while button down is prev/next screen + elif ( event.code == evdev.ecodes.REL_X + and self._mouse_btn_state == 1 + and abs(event.value) > 10 ): + if event.value > 0: + event.code = evdev.ecodes.KEY_RIGHT + else: + event.code = evdev.ecodes.KEY_LEFT + event.type = evdev.ecodes.EV_KEY + self._mouse_inhibit = time.monotonic() + self._event_queue.put_nowait(event) + # move up/down while button down is higher/lower quality + elif ( event.code == evdev.ecodes.REL_Y + and self._mouse_btn_state == 1 + and abs(event.value) > 10 ): + if event.value > 0: + event.code = evdev.ecodes.KEY_DOWN + else: + event.code = evdev.ecodes.KEY_UP + event.type = evdev.ecodes.EV_KEY + self._mouse_inhibit = time.monotonic() + self._event_queue.put_nowait(event) + del event else: break