diff --git a/README.md b/README.md index 2635136..c6d2c17 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,7 @@ - [Config Examples](#config-examples-) - [Multi-Monitor](#multi-monitor-) - [Shortcuts](#shortcuts-) +- [Ubuntu Example for Huion Kamvas Pro 12"](#ubuntu-example-for-gt-116-) - [Help Welcomed](#help-welcomed-) - [To do](#to-do-) @@ -163,6 +164,36 @@ b9 = key 6 # turn right (krita) [See an example with multiple menus in the wiki](https://github.com/joseluis/huion-linux-drivers/wiki/Buttons-Shortcuts#12-example-with-multiple-menus) +## Ubuntu Example for GT-116 [↑](#table-of-contents "Back to TOC") +*Tested on Ubuntu LTS 16.04* + +First, adapt the parameters in `config.ini` to your tablet model. In case of a Huion Kamvas Pro 12" (GT-116), you could change the following options to use the 5 buttons menu for Gimp and Krita. +```ini +current_tablet = [tablet_gt116] +... +enable_multi_pointer = true +... +start_menu = [menu_5b_multi] +``` + +After doing all the pre-requisites listed above, copy the 3 files `TabletDriver`, `config.ini` and `python-tablet-driver.py` into `/usr/local/huion-linux-driver`. +```bash +$ sudo mkdir /usr/local/huion-linux-driver +$ sudo cp config.ini /usr/local/huion-linux-driver +$ sudo cp python-table-driver.py /usr/local/huion-linux-driver +$ sudo cp TabletDriver /usr/local/huion-linux-driver +``` + +Create a symbolic link to the launcher file `TabletDriver`: +```bash +$ sudo ln -s /usr/local/huion-linux-driver/TabletDriver /usr/local/bin/TabletDriver +``` + +Then you can launch the driver: +```bash +$ sudo TableDriver +``` +The console screen lists the configuration options and then displays the current active menu. You can change the active menu using the lower left button. This configuration creates 2 pointers: one for the mouse that you can keep on your primary screen and one for the tablet pencil that you use to draw on the tablet. The mouse can move on both screens: if you loose it, it could be on the tablet screen. ## Help Welcomed [↑](#table-of-contents "Back to TOC") diff --git a/TabletDriver b/TabletDriver new file mode 100755 index 0000000..4631157 --- /dev/null +++ b/TabletDriver @@ -0,0 +1,23 @@ +#!/usr/bin/env bash + +# Adjust this as needed +DRIVER="/usr/local/huion-linux-drivers/huion-tablet-driver.py" +CONFIG_FILE="/usr/local/huion-linux-drivers/config.ini" + +T="256c:006e" +BUS=$(lsusb | grep "$T" | sed -e 's|Bus \([0-9]*\) Device \([0-9]*\):.*$|\1|g') +DEV=$(lsusb | grep "$T" | sed -e 's|Bus \([0-9]*\) Device \([0-9]*\):.*$|\2|g') + +sudo rmmod hid_uclogic 2>/dev/null +sudo modprobe uinput +sudo uclogic-probe $BUS $DEV | uclogic-decode +sleep 3 # wait until device is released again + +# Create new pencil pointer on the tablet +# The following code has been added into the Python code +#INPD=$(xinput | grep 'Tablet Monitor Pen' | cut -f 2) +#INPD=${INPD:3} +#OUTPD=$(xrandr --listactivemonitors | sort | cut -f 6 -d ' ' | head -2 | tail -1) +#xinput map-to-output ${INPD:-'15'} ${OUTPD:-'HDMI-1'} + +sudo python3 $DRIVER $CONFIG_FILE diff --git a/config.ini b/config.ini index 6dad20e..68686c2 100644 --- a/config.ini +++ b/config.ini @@ -16,7 +16,7 @@ # Your tablet. Find the supported models at the end of this file. # Or use [tablet_debug] for just printing input info for sharing. -current_tablet = [tablet_gt221pro] +current_tablet = [tablet_gt116] # Configure buttons enable_buttons = true @@ -31,6 +31,9 @@ enable_multi_monitor = false enable_xrandr = false current_monitor_setup = [monitor_2] +# Multi Pointers +enable_multi_pointer = true + # Calibration Data enable_calibration = false calibrate_min_x = 250 @@ -47,12 +50,12 @@ scrollbar_notifications = false uclogic_bins = /usr/local/bin refresh_rate_fps = 300 -debug_mode = true +debug_mode = false # Here you can select a menu with the appropriate number of buttons for your tablet. E.g.: #start_menu = [menu_simple_4b] #start_menu = [menu_main_10b] -start_menu = +start_menu = [menu_5b_multi] # @@ -71,6 +74,41 @@ su = key ctrl+minus # zoom out (krita) sd = key ctrl+plus # zoom in (krita) +[menu_5b_multi] +title = % Main Menu % +b0 = [menu_5b_krita] +b1 = [menu_5b_gimp] +b2 = key ctrl+s # save +b3 = key ctrl+o # open +b4 = key Escape +# scrollbar (up/down) +su = click 4 # mouse wheel up +sd = click 5 # mouse wheel down + +[menu_5b_krita] +title = % Krita % +b0 = key Tab # hide interface +b1 = key r # pick layer +b2 = key ctrl+z # undo +b3 = key ctrl+shift+z # redo +b4 = [menu_5b_multi] +# scrollbar (up/down) +su = key ctrl+minus # zoom out +sd = key ctrl+plus # zoom in + +[menu_5b_gimp] +title = % Gimp % +b0 = key Tab # hide interface +b1 = key r # rect select +b2 = key ctrl+z # undo +b3 = key ctrl+y # redo +b4 = [menu_5b_multi] +# +# scrollbar (up/down) +su = key minus # zoom out +sd = key plus # zoom in + + [menu_simple_10b] # upper buttons b0 = key Tab # hide interface @@ -289,8 +327,8 @@ xrandr_args = ${xrandr_output1} ${xrandr_output2} # 2 monitors arranged horizontally # screens widths and heights -screen_1W = 2560 -screen_1H = 1440 +screen_1W = 1366 +screen_1H = 768 screen_2W = 1920 screen_2H = 1080 @@ -386,7 +424,7 @@ pen_max_x = 53580 pen_max_y = 33640 pen_max_z = 8191 resolution = 5080 -buttons = 4 +buttons = 5 scrollbar = 1 [tablet_gt133] diff --git a/huion-tablet-driver.py b/huion-tablet-driver.py index 7624aca..c1fbff7 100755 --- a/huion-tablet-driver.py +++ b/huion-tablet-driver.py @@ -10,8 +10,12 @@ import numexpr from configparser import ConfigParser, ExtendedInterpolation from time import gmtime, strftime, sleep +import atexit MENU = {} +XINPUT_DEVICE_KEYWORD = "Tablet Monitor Pen" # your pen's name in the terminal output of xinput +XOUTPUT_DEVICE_ARGUMENT = -1 # defaults to final active monitor in terminal output of xrandr + # ... change to select specific monitor in xoutput_devices # ----------------------------------------------------------------------------- @@ -32,6 +36,8 @@ def run(): setup_driver() calibrate() multi_monitor() + calibrate_mapping() + multi_pointer() main_loop() @@ -180,6 +186,11 @@ def setup_driver(): else: print("\tScreen disabled") + if main.settings['enable_multi_pointer']: + print("\tMulti Pointer ENABLED") + else: + print("\tMulti Pointer disabled") + if main.settings['debug_mode']: print("\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~") print("\t\t\t< DEBUG MODE ENABLED >") @@ -259,6 +270,91 @@ def multi_monitor(): main.settings['screen_width'], main.settings['screen_height'], main.settings['tablet_offset_x'], main.settings['tablet_offset_y'])) + +# ----------------------------------------------------------------------------- +def multi_pointer(xinput_device_keyword=XINPUT_DEVICE_KEYWORD): + """ + Set up multi pointer: + - The mouse can be used on the main display and the tablet. + - The stylet is used on the tablet. + """ + + if not main.settings['enable_multi_pointer']: + return + + print("\nSetting up multiple pointers. . . ") + + if main.settings['enable_multi_pointer']: + atexit.register(cleanup_multi_pointer, xinput_device_keyword) + + print("Create master input. . .") + cmd = "xinput create-master 'HuionTablet'" + if main.settings['debug_mode']: + print('» {}'.format(cmd)) + os.popen(cmd) + + xinput_device = os.popen("xinput | grep '%s' | cut -f 2" % xinput_device_keyword).read() + xinput_device = xinput_device.split('\n')[0][3:] # Chose the first instance of keyword, strips off "id=" + master = os.popen("xinput | grep 'HuionTablet pointer' | cut -f 2").read() + master = master.split('\n')[0][3:] + if main.settings['debug_mode']: + print('» input device = {}'.format(xinput_device)) + + cmd = "xinput reattach {} {}".format(xinput_device, master) + if main.settings['debug_mode']: + print('» {}'.format(cmd)) + os.popen(cmd) + + +# ----------------------------------------------------------------------------- +def cleanup_multi_pointer(xinput_device_keyword=XINPUT_DEVICE_KEYWORD): + """ + Cleanup multi pointer at program exit. + """ + master = os.popen("xinput | grep 'HuionTablet pointer' | cut -f 2").read() + master = master.split('\n')[0][3:] + # Removing existing master + cmd = "xinput remove-master {}".format(master) + if main.settings['debug_mode']: + print('» {}'.format(cmd)) + os.popen(cmd) + + +# ----------------------------------------------------------------------------- +def calibrate_mapping(xinput_device_keyword=XINPUT_DEVICE_KEYWORD, xoutput_device_argument=XOUTPUT_DEVICE_ARGUMENT): + """ + Maps tablet pen, specified by xinput_device_keyword, to a given monitor, specified by xoutput_device_argument. + If xoutput_device_argument=-1, the pen's bounds will calibrate to the last active monitor in xrandr. + """ + try: + # Locate tablet pen device number + xinput_device = os.popen("xinput | grep '%s' | cut -f 2" % xinput_device_keyword).read() + xinput_device = xinput_device.split('\n')[0][3:] # Chose the first instance of keyword, strips off "id=" + + # Locate output device name + xoutput_devices = os.popen("xrandr --listactivemonitors | sort | cut -f 6 -d ' ' ").read() + xoutput_devices = list(filter(None, xoutput_devices.split('\n'))) # lists all active monitors + xoutput_device = xoutput_devices[xoutput_device_argument] + + # Lenovo's defaults + if xinput_device == '': + xinput_device = 15 + if xoutput_device == '': + xoutput_device = "HDMI-1" + + # Calibrate by mapping input to graphic pad output + os.system("xinput map-to-output %s %s" % (xinput_device, xoutput_device)) + print() + sys.stdout.write("Calibrating graphics monitor via xinput mapping. . .") + except: + print("Error with calibrate_mapping") + + print("Done") + print("xinput_device = %s" % xinput_device) + print("xoutput_device = %s" % xoutput_device) + print("xoutput_mainscreen = %s" % xoutput_devices[0]) + + # ----------------------------------------------------------------------------- def calibrate(): @@ -384,7 +480,10 @@ def main_loop(): # convert to the exponent (0, 1, 2, 3, 4...) BUTTON_VAL = int(math.log(BUTTON_VAL, 2)) if main.current_menu: - do_shortcut("button", MENU[main.current_menu][BUTTON_VAL]) + try: + do_shortcut("button", MENU[main.current_menu][BUTTON_VAL]) + except: + print("Unknown key: MENU[%s][%d]" % (main.current_menu, BUTTON_VAL)) # SCROLLBAR EVENT @@ -495,7 +594,7 @@ def keypress(title, sequence): try: sp.run(cmd, shell=True, check=True) except sp.CalledProcessError as e: - run_error(e, cmd) + run_error(e, cmd, False) # ----------------------------------------------------------------------------- @@ -520,7 +619,7 @@ def switch_menu(new_menu): try: sp.run(cmd, shell=True, check=True) except sp.CalledProcessError as e: - run_error(e, cmd) + run_error(e, cmd, False) # ----------------------------------------------------------------------------- @@ -541,9 +640,15 @@ def read_config(): sys.stdout.write("Reading configuration. . . ") - if os.path.exists('config.ini'): + # Config file can be given as command line parameter + config_file = '' + if len(sys.argv) > 1: + config_file = sys.argv[1] + print("Config file is %s" % config_file) + + if os.path.exists('config.ini') or config_file != '': config = ConfigParser(interpolation=ExtendedInterpolation()) - config.read('config.ini') + config.read([config_file, 'config.ini']) else: print("ERROR: Couldn't locate config.ini") sys.exit(2) @@ -683,6 +788,12 @@ def read_config(): except: current_monitor_setup = "none" + # multi pointer + try: + main.settings['enable_multi_pointer'] = config.get('config', 'enable_multi_pointer') + except: + main.settings['enable_multi_pointer'] = False + # tablet calibration try: @@ -753,3 +864,4 @@ def read_config(): # ----------------------------------------------------------------------------- if __name__ == '__main__': main.run() +