diff --git a/README.md b/README.md index 3762a1a..f6f6c88 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,9 @@ pip install remarkable-mouse remouse ``` -By default, `10.11.99.1` is used as the address. Seems to work pretty well wirelessly, too. By default ssh-agent is used to authenticate if it is available, otherwise you are asked for your password. Find your password in the reMarkable's [settings menu](https://remarkablewiki.com/tech/ssh). +By default, `10.11.99.1` is used as the address. Find your password in the reMarkable's [settings menu](https://remarkablewiki.com/tech/ssh). If you are on Linux using X11, you can use the `--evdev` option for pressure support. + +To use the `--region` flag, you may need to install the `python3-tk` or `python3-tkinter` package with your package manager. # Examples @@ -34,23 +36,25 @@ remouse --key ~/.ssh/remarkable # Usage - usage: remouse [-h] [--debug] [--key PATH] [--password PASSWORD] - [--address ADDRESS] [--mode {fit,fill}] - [--orientation {top,left,right,bottom}] [--monitor NUM] - [--threshold THRESH] [--evdev] - - use reMarkable tablet as a mouse input - - optional arguments: - -h, --help show this help message and exit - --debug enable debug messages - --key PATH ssh private key - --password PASSWORD ssh password - --address ADDRESS device address - --mode {fit,fill} scale setting - --orientation {top,left,right,bottom} - position of tablet buttons - --monitor NUM monitor to output to - --threshold THRESH stylus pressure threshold (default 600) - --evdev use evdev to support pen pressure (requires root, - Linux only) +``` +usage: remouse [-h] [--debug] [--key PATH] [--password PASSWORD] [--address ADDRESS] [--mode {fit,fill,stretch}] [--orientation {top,left,right,bottom}] [--monitor NUM] [--region] [--threshold THRESH] + [--evdev] + +use reMarkable tablet as a mouse input + +optional arguments: + -h, --help show this help message and exit + --debug enable debug messages + --key PATH ssh private key + --password PASSWORD ssh password + --address ADDRESS device address + --mode {fit,fill,stretch} + Scale setting. Fit (default): take up the entire tablet, but not necessarily the entire monitor. Fill: take up the entire monitor, but not necessarily the entire tablet. Stretch: + take up both the entire tablet and monitor, but don't maintain aspect ratio. + --orientation {top,left,right,bottom} + position of tablet buttons + --monitor NUM monitor to output to + --region Use a GUI to position the output area. Overrides --monitor + --threshold THRESH stylus pressure threshold (default 600) + --evdev use evdev to support pen pressure (requires root, Linux only) +``` diff --git a/remarkable_mouse/common.py b/remarkable_mouse/common.py new file mode 100644 index 0000000..80ee305 --- /dev/null +++ b/remarkable_mouse/common.py @@ -0,0 +1,67 @@ +#!/usr/bin/env python + +import logging +import sys +from screeninfo import get_monitors, Monitor + +logging.basicConfig(format='%(message)s') +log = logging.getLogger('remouse') + +# get info of where we want to map the tablet to +def get_monitor(region, monitor_num, orientation): + if region is not None: + monitor = get_region(orientation) + else: + monitor = get_monitors()[monitor_num] + + return monitor + +# Pop up a window, ask the user to move the window, and then get the position of the window's contents +def get_region(orientation): + try: + import tkinter as tk + from tkinter import ttk + except ImportError: + print( + "Unable to import tkinter; please follow the instructions at https://tkdocs.com/tutorial/install.html to install it") + sys.exit(1) + + window = tk.Tk() + + # A bit of an ugly hack to get this function to run synchronously + # Ideally would use full async support, but this solution required minimal changes to rest of code + selected_pos = None + + def on_click(): + nonlocal selected_pos + selected_pos = Monitor( + window.winfo_x(), + window.winfo_y(), + window.winfo_width(), + window.winfo_height(), + name="Fake monitor from region selection" + ) + window.destroy() + + confirm = ttk.Button(window, text="Drag and resize this button to the desired mouse range, then click", + command=on_click) + confirm.grid(column=0, row=0, sticky=(tk.N, tk.S, tk.E, tk.W)) + + window.columnconfigure(0, weight=1) + window.rowconfigure(0, weight=1) + + window.attributes('-alpha', 0.5) + window.title("Remarkable Mouse") + + if orientation == 'bottom' or orientation == 'top': + window.geometry("702x936") + else: + window.geometry("936x702") + + window.mainloop() + + if selected_pos is None: + log.debug("Window closed without giving mouse range") + sys.exit(1) + + return selected_pos diff --git a/remarkable_mouse/pynput.py b/remarkable_mouse/pynput.py index d042896..b9d2e76 100644 --- a/remarkable_mouse/pynput.py +++ b/remarkable_mouse/pynput.py @@ -2,6 +2,8 @@ import logging import struct from screeninfo import get_monitors +from .common import get_monitor + logging.basicConfig(format='%(message)s') log = logging.getLogger('remouse') @@ -45,26 +47,32 @@ def remap(x, y, wacom_width, wacom_height, monitor_width, ratio_width, ratio_height = monitor_width / wacom_width, monitor_height / wacom_height if mode == 'fill': - scaling = max(ratio_width, ratio_height) + scaling_x = max(ratio_width, ratio_height) + scaling_y = scaling_x elif mode == 'fit': - scaling = min(ratio_width, ratio_height) + scaling_x = min(ratio_width, ratio_height) + scaling_y = scaling_x + elif mode == 'stretch': + scaling_x = ratio_width + scaling_y = ratio_height else: raise NotImplementedError return ( - scaling * (x - (wacom_width - monitor_width / scaling) / 2), - scaling * (y - (wacom_height - monitor_height / scaling) / 2) + scaling_x * (x - (wacom_width - monitor_width / scaling_x) / 2), + scaling_y * (y - (wacom_height - monitor_height / scaling_y) / 2) ) -def read_tablet(rm_inputs, *, orientation, monitor, threshold, mode): +def read_tablet(rm_inputs, *, orientation, monitor_num, region, threshold, mode): """Loop forever and map evdev events to mouse Args: rm_inputs (dictionary of paramiko.ChannelFile): dict of pen, button and touch input streams orientation (str): tablet orientation - monitor (int): monitor number to map to + monitor_num (int): monitor number to map to + region (boolean): whether to selection mapping region with region tool threshold (int): pressure threshold mode (str): mapping mode """ @@ -76,7 +84,7 @@ def read_tablet(rm_inputs, *, orientation, monitor, threshold, mode): mouse = Controller() - monitor = get_monitors()[monitor] + monitor = get_monitor(monitor_num, region, orientation) log.debug('Chose monitor: {}'.format(monitor)) while True: diff --git a/remarkable_mouse/remarkable_mouse.py b/remarkable_mouse/remarkable_mouse.py index a057df2..e7958fe 100755 --- a/remarkable_mouse/remarkable_mouse.py +++ b/remarkable_mouse/remarkable_mouse.py @@ -112,9 +112,13 @@ def main(): 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") - parser.add_argument('--mode', default='fill', choices=['fit', 'fill'], help="scale setting") - parser.add_argument('--orientation', default='right', choices=['top', 'left', 'right', 'bottom'], help="position of charging port") + parser.add_argument('--mode', default='fill', choices=['fit', 'fill', 'stretch'], help="""Scale setting. + Fit (default): take up the entire tablet, but not necessarily the entire monitor. + Fill: take up the entire monitor, but not necessarily the entire tablet. + Stretch: take up both the entire tablet and monitor, but don't maintain aspect ratio.""") + parser.add_argument('--orientation', default='right', choices=['top', 'left', 'right', 'bottom'], help="position of tablet buttons") parser.add_argument('--monitor', default=0, type=int, metavar='NUM', help="monitor to output to") + parser.add_argument('--region', action='store_true', default=False, help="Use a GUI to position the output area. Overrides --monitor") parser.add_argument('--threshold', metavar='THRESH', default=600, type=int, help="stylus pressure threshold (default 600)") parser.add_argument('--evdev', action='store_true', default=False, help="use evdev to support pen pressure (requires root, Linux only)") @@ -146,7 +150,8 @@ def main(): read_tablet( rm_inputs, orientation=args.orientation, - monitor=args.monitor, + monitor_num=args.monitor, + region=args.region, threshold=args.threshold, mode=args.mode, )