evan
6 years ago
commit
1daaa1e3fe
6 changed files with 231 additions and 0 deletions
@ -0,0 +1,4 @@
@@ -0,0 +1,4 @@
|
||||
**/*.pyc |
||||
dist/ |
||||
remarkable_mouse.egg-info |
||||
**/__pycache__ |
@ -0,0 +1,22 @@
@@ -0,0 +1,22 @@
|
||||
# remarkable_mouse |
||||
|
||||
Use your reMarkable as a graphics tablet. |
||||
|
||||
Special thanks to [canselcik](https://github.com/canselcik/libremarkable) and [LinusCDE](https://github.com/LinusCDE/rmWacomToMouse) for inspiration. |
||||
|
||||
# Usage |
||||
|
||||
``` bash |
||||
pip install remarkable-mouse |
||||
remouse |
||||
``` |
||||
|
||||
By default, `10.11.99.1` is used as the address. Seems to work pretty well wirelessly, too. |
||||
|
||||
# Passwordless login |
||||
|
||||
``` bash |
||||
ssh-keygen -f ~/.ssh/remarkable -N '' |
||||
ssh-copy-id -i ~/.ssh/remarkable.pub root@10.11.99.1 |
||||
remouse --key ~/.ssh/remarkable |
||||
``` |
@ -0,0 +1,175 @@
@@ -0,0 +1,175 @@
|
||||
#!/bin/env python |
||||
# Evan Widloski - 2019-02-23 |
||||
# Use reMarkable as mouse input |
||||
|
||||
import argparse |
||||
import logging |
||||
import os |
||||
import sys |
||||
import struct |
||||
from getpass import getpass |
||||
import paramiko |
||||
from screeninfo import get_monitors |
||||
from pynput.mouse import Button, Controller |
||||
|
||||
EV_SYNC = 0 |
||||
EV_KEY = 1 |
||||
EV_ABS = 3 |
||||
|
||||
WACOM_EVCODE_PRESSURE = 24 |
||||
WACOM_EVCODE_DISTANCE = 25 |
||||
WACOM_EVCODE_XTILT = 26 |
||||
WACOM_EVCODE_YTILT = 27 |
||||
WACOM_EVCODE_XPOS = 0 |
||||
WACOM_EVCODE_YPOS = 1 |
||||
|
||||
wacom_width = 15725 |
||||
wacom_height = 20951 |
||||
wacom_click_pressure_min = 1000 |
||||
screen_width = 675 |
||||
screen_height = 900 |
||||
|
||||
mouse = Controller() |
||||
logging.basicConfig(format='%(message)s') |
||||
log = logging.getLogger(__name__) |
||||
|
||||
|
||||
# remap wacom coordinates in various orientations |
||||
def fit(x, y, wacom_width, wacom_height, monitor, orientation): |
||||
|
||||
if orientation == 'vertical': |
||||
y = wacom_height - y |
||||
elif orientation == 'right': |
||||
x, y = y, x |
||||
wacom_width, wacom_height = wacom_height, wacom_width |
||||
elif orientation == 'left': |
||||
x, y = wacom_height - y, wacom_width - x |
||||
wacom_width, wacom_height = wacom_height, wacom_width |
||||
|
||||
ratio_width, ratio_height = monitor.width / wacom_width, monitor.height / wacom_height |
||||
scaling = ratio_width if ratio_width > ratio_height else ratio_height |
||||
|
||||
return ( |
||||
scaling * (x - (wacom_width - monitor.width / scaling) / 2), |
||||
scaling * (y - (wacom_height - monitor.height / scaling) / 2) |
||||
) |
||||
|
||||
|
||||
def open_eventfile(args): |
||||
"""ssh to reMarkable and open event0""" |
||||
|
||||
if args.key is not None: |
||||
password = None |
||||
try: |
||||
pkey = paramiko.RSAKey.from_private_key_file(os.path.expanduser(args.key)) |
||||
except paramiko.ssh_exception.PasswordRequiredException: |
||||
passphrase = getpass( |
||||
"Enter passphrase for key '{}': ".format(os.path.expanduser(args.key)) |
||||
) |
||||
pkey = paramiko.RSAKey.from_private_key_file( |
||||
os.path.expanduser(args.key), |
||||
password=passphrase |
||||
) |
||||
else: |
||||
password = getpass( |
||||
"Password for '{}': ".format(args.address) |
||||
) |
||||
pkey = None |
||||
|
||||
client = paramiko.SSHClient() |
||||
client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) |
||||
client.connect( |
||||
args.address, |
||||
username='root', |
||||
password=password, |
||||
pkey=pkey, |
||||
look_for_keys=False |
||||
) |
||||
|
||||
# Start reading events |
||||
_, stdout, _ = client.exec_command('cat /dev/input/event0') |
||||
|
||||
return stdout |
||||
|
||||
|
||||
def read_tablet(args): |
||||
"""Loop forever and map evdev events to mouse""" |
||||
|
||||
updates_since_pressure = 0 |
||||
new_x = new_y = False |
||||
|
||||
monitor = get_monitors()[args.monitor] |
||||
log.debug('Chose monitor: {}'.format(monitor)) |
||||
|
||||
stdout = open_eventfile(args) |
||||
|
||||
while True: |
||||
_, _, _, _, e_type, e_code, e_value = struct.unpack('4IHHI', stdout.read(24)) |
||||
|
||||
if e_type == EV_ABS: |
||||
|
||||
# handle x direction |
||||
if e_code == WACOM_EVCODE_YPOS: |
||||
log.debug(f'{e_value}') |
||||
x = e_value |
||||
new_x = True |
||||
updates_since_pressure += 1 |
||||
|
||||
# handle y direction |
||||
if e_code == WACOM_EVCODE_XPOS: |
||||
log.debug(f'\t{e_value}') |
||||
updates_since_pressure += 1 |
||||
y = e_value |
||||
new_y = True |
||||
|
||||
# handle draw |
||||
if e_code == WACOM_EVCODE_PRESSURE: |
||||
log.debug(f'\t\t{e_value}') |
||||
updates_since_pressure = 0 |
||||
if e_value > 1000: |
||||
mouse.press(Button.left) |
||||
else: |
||||
mouse.release(Button.left) |
||||
|
||||
# FIXME: bug |
||||
# sometimes the last detected pressure is above threshold |
||||
# make sure pen doesnt get stuck down |
||||
if updates_since_pressure > 8: |
||||
updates_since_pressure = 0 |
||||
mouse.release(Button.left) |
||||
|
||||
# only move when x and y are updated for smoother mouse |
||||
if new_x and new_y: |
||||
x, y = fit(x, y, wacom_width, wacom_height, monitor, args.orientation) |
||||
mouse.move( |
||||
monitor.x + x - mouse.position[0], |
||||
monitor.y + y - mouse.position[1] |
||||
) |
||||
count = 0 |
||||
new_x = new_y = False |
||||
|
||||
def main(): |
||||
|
||||
parser = argparse.ArgumentParser(description="use reMarkable tablet as a mouse input") |
||||
parser.add_argument('--orientation', default='left', choices=['vertical', 'left', 'right']) |
||||
parser.add_argument('--monitor', default=0, type=int, metavar='NUM', help="monitor to use") |
||||
parser.add_argument('--offset', default=(0, 0), type=int, metavar=('x', 'y'), nargs=2, help="offset mapped region on monitor") |
||||
parser.add_argument('--debug', action='store_true', default=False, help="enable debug messages") |
||||
parser.add_argument('--key', type=str, metavar='PATH', help="ssh private key") |
||||
parser.add_argument('--password', default=None, type=str, help="ssh password") |
||||
parser.add_argument('--address', default='10.11.99.1', type=str, help="device address") |
||||
args = parser.parse_args() |
||||
|
||||
if args.debug: |
||||
print('Debugging enabled...') |
||||
logging.getLogger('').setLevel(logging.DEBUG) |
||||
log.setLevel(logging.DEBUG) |
||||
|
||||
read_tablet(args) |
||||
|
||||
|
||||
if __name__ == '__main__': |
||||
try: |
||||
main() |
||||
except KeyboardInterrupt: |
||||
pass |
@ -0,0 +1,29 @@
@@ -0,0 +1,29 @@
|
||||
from setuptools import setup |
||||
from remarkable_mouse import version |
||||
|
||||
setup( |
||||
name='remarkable-mouse', |
||||
version=version.__version__, |
||||
packages=['remarkable_mouse'], |
||||
author="Evan Widloski", |
||||
author_email="evan@evanw.org", |
||||
description="use reMarkable as a graphics tablet", |
||||
long_description=open('README.md').read(), |
||||
license="GPLv3", |
||||
keywords="remarkable tablet evdev", |
||||
url="https://github.com/evidlo/remarkable_mouse", |
||||
entry_points={ |
||||
'console_scripts': [ |
||||
'remarkable-mouse = remarkable_mouse.remarkable_mouse:main', |
||||
'remouse = remarkable_mouse.remarkable_mouse:main' |
||||
] |
||||
}, |
||||
install_requires=[ |
||||
'paramiko', |
||||
'screeninfo', |
||||
'pynput' |
||||
], |
||||
classifiers=[ |
||||
"Programming Language :: Python :: 3", |
||||
] |
||||
) |
Loading…
Reference in new issue