evan
6 years ago
commit
1daaa1e3fe
6 changed files with 231 additions and 0 deletions
@ -0,0 +1,4 @@ |
|||||||
|
**/*.pyc |
||||||
|
dist/ |
||||||
|
remarkable_mouse.egg-info |
||||||
|
**/__pycache__ |
@ -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 @@ |
|||||||
|
#!/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 @@ |
|||||||
|
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