Skip to content
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion camplayer/camplayer.py
Original file line number Diff line number Diff line change
Expand Up @@ -310,4 +310,4 @@ def main():


if __name__ == "__main__":
main()
main()
97 changes: 96 additions & 1 deletion camplayer/utils/inputhandler.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"""
Expand Down Expand Up @@ -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
Expand Down