init commit
commit
1daaa1e3fe
@ -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 @@
|
||||
__version__ = '1'
|
||||
@ -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