From 6bc1bac531b820704905b477d4ed1f6272c3614b Mon Sep 17 00:00:00 2001 From: NeoTheFox Date: Fri, 16 Apr 2021 22:55:23 +0300 Subject: [PATCH 1/5] partial threaded input implementation removed the config reload signal because it's becoming harder to maintain thanks to threading --- huion_keys.py | 251 +++++++++++++++++++++++++++----------------------- 1 file changed, 137 insertions(+), 114 deletions(-) diff --git a/huion_keys.py b/huion_keys.py index 162cd1c..7a60327 100755 --- a/huion_keys.py +++ b/huion_keys.py @@ -1,8 +1,8 @@ #!/usr/bin/env python3 import os import time -import signal import argparse +import threading import configparser from _xdo_cffi import ffi, lib @@ -17,10 +17,21 @@ TABLET_MODELS = { BUTTON_BINDINGS = {} BUTTON_BINDINGS_HOLD = {} CYCLE_BUTTON = None -CYCLE_MODE = 1 CYCLE_MODES = 1 DIAL_MODES = {} +BUTTON_BITS = { + 0x01: 1, + 0x02: 2, + 0x04: 3, + 0x08: 4, + 0x10: 5, + 0x20: 6, + 0x40: 7, + 0x80: 8, +} + + def main(): #Commandline arguments processing parser = argparse.ArgumentParser( @@ -41,8 +52,6 @@ def main(): else: CONFIG_FILE_PATH = os.path.expanduser(args.config) - global CYCLE_MODES, CYCLE_MODE, CYCLE_BUTTON - xdo = lib.xdo_new(ffi.NULL) if os.path.isfile(CONFIG_FILE_PATH): read_config(CONFIG_FILE_PATH) else: @@ -50,73 +59,157 @@ def main(): create_default_config(CONFIG_FILE_PATH) print("Created an example config file at " + CONFIG_FILE_PATH) return 1 - signal.signal(signal.SIGUSR1, handle_reload_signal) # Reload the config if recieved SIGUSR1 - prev_button = None + + hidraw_paths = [] while True: - hidraw_path = None # search for a known tablet device for device_name, device_id in TABLET_MODELS.items(): hidraw_path = get_tablet_hidraw(device_id) if hidraw_path is not None: - print("Found %s at %s" % (device_name, hidraw_path)) - break - if hidraw_path is None: - print("Could not find tablet hidraw device") + hidraw_paths = hidraw_paths + hidraw_path + + if hidraw_paths: + print("Found %s at %s" % (device_name, hidraw_paths)) + break + elif not hidraw_paths: + print("Could not find any tablet hidraw devices") time.sleep(2) - continue - try: - hidraw = open(hidraw_path, 'rb') - except PermissionError as e: - print(e) - print("Trying again in 5 seconds...") - time.sleep(5) - continue + continue + + threads = [] + for hidraw_path in hidraw_paths: + thread = PollThread(hidraw_path) + thread.daemon = True + threads.append(thread) + thread.start() + + for thread in threads: + thread.join() + +class PollThread(threading.Thread): + + cycle_mode = None + hidraw_path = None + xdo = None + + SCROLL_STATE=None + + def __init__(self, hidraw_path): + super(PollThread, self).__init__() + self.xdo = lib.xdo_new(ffi.NULL) + self.hidraw_path = hidraw_path + self.cycle_mode = 1 + + def run(self): + global BUTTON_BINDINGS, BUTTON_BINDINGS_HOLD, CYCLE_MODES, CYCLE_BUTTON while True: try: - btn = get_button_press(hidraw) + hidraw = open(self.hidraw_path, 'rb') + break + except PermissionError as e: + print(e) + print("Trying again in 5 seconds...") + time.sleep(5) + continue + + while True: + try: + btn = self.get_button_press(hidraw) except OSError as e: print("Lost connection with the tablet - searching for tablet...") time.sleep(3) break print("Got button %s" % (btn,)) + print(BUTTON_BINDINGS) if btn == CYCLE_BUTTON and CYCLE_BUTTON is not None: - CYCLE_MODE = CYCLE_MODE + 1 - if CYCLE_MODE > CYCLE_MODES: - CYCLE_MODE = 1 - print("Cycling to mode %s" % (CYCLE_MODE,)) - elif CYCLE_MODE in DIAL_MODES and btn in DIAL_MODES[CYCLE_MODE]: - print("Sending %s from Mode %d" % (DIAL_MODES[CYCLE_MODE][btn], CYCLE_MODE),) - lib.xdo_send_keysequence_window( - xdo, lib.CURRENTWINDOW, DIAL_MODES[CYCLE_MODE][btn], 1000) - elif btn in BUTTON_BINDINGS_HOLD: - print("Pressing %s" % (BUTTON_BINDINGS_HOLD[btn],)) - lib.xdo_send_keysequence_window_down(xdo, lib.CURRENTWINDOW, BUTTON_BINDINGS_HOLD[btn], 12000) - get_button_release(hidraw) - print("Releasing %s" % (BUTTON_BINDINGS_HOLD[btn],)) - lib.xdo_send_keysequence_window_up(xdo, lib.CURRENTWINDOW, BUTTON_BINDINGS_HOLD[btn], 12000) - elif btn in BUTTON_BINDINGS: - print("Sending %s" % (BUTTON_BINDINGS[btn],)) - lib.xdo_send_keysequence_window( - xdo, lib.CURRENTWINDOW, BUTTON_BINDINGS[btn], 1000) - + self.cycle_mode = self.cycle_mode + 1 + if self.cycle_mode > CYCLE_MODES: + self.cycle_mode = 1 + print("Cycling to mode %s" % (self.cycle_mode,)) + elif self.cycle_mode in DIAL_MODES and btn in DIAL_MODES[self.cycle_mode]: + print("Sending %s from Mode %d" % (DIAL_MODES[self.cycle_mode][btn], self.cycle_mode),) + lib.xdo_send_keysequence_window( + self.xdo, lib.CURRENTWINDOW, DIAL_MODES[self.cycle_mode][btn], 1000) + elif btn in BUTTON_BINDINGS_HOLD: + print("Pressing %s" % (BUTTON_BINDINGS_HOLD[btn],)) + lib.xdo_send_keysequence_window_down(self.xdo, lib.CURRENTWINDOW, BUTTON_BINDINGS_HOLD[btn], 12000) + self.get_button_release(hidraw) + print("Releasing %s" % (BUTTON_BINDINGS_HOLD[btn],)) + lib.xdo_send_keysequence_window_up(xdo, lib.CURRENTWINDOW, BUTTON_BINDINGS_HOLD[btn], 12000) + elif btn in BUTTON_BINDINGS: + print("Sending %s" % (BUTTON_BINDINGS[btn],)) + lib.xdo_send_keysequence_window( + self.xdo, lib.CURRENTWINDOW, BUTTON_BINDINGS[btn], 1000) + + def get_button_press(self,hidraw): + while True: + sequence = hidraw.read(12) + # 0xf7 is what my Kamvas Pro 22 reads + # another model seems to send 0x08 + # Q620M reads as 0xf9 + if sequence[0] != 0xf7 and sequence[0] != 0x08 and sequence[0] != 0xf9: + pass + if sequence[1] == 0xe0: # buttons + # doesn't seem like the tablet will let you push two buttons at once + if sequence[4] > 0: + return BUTTON_BITS[sequence[4]] + elif sequence[5] > 0: + # right-side buttons are 8-15, so add 8 + return BUTTON_BITS[sequence[5]] + 8 + else: + # must be button release (all zeros) + continue + elif sequence[1] == 0xf0: # scroll strip + scroll_pos = sequence[5] + if scroll_pos == 0: + # reset scroll state after lifting finger off scroll strip + self.SCROLL_STATE = None + elif self.SCROLL_STATE is not None: + # scroll strip is numbered from top to bottom so a greater new + # value means they scrolled down + if scroll_pos > self.SCROLL_STATE: + self.SCROLL_STATE = scroll_pos + return 'scroll_down' + elif scroll_pos < self.SCROLL_STATE: + self.SCROLL_STATE = scroll_pos + return 'scroll_up' + else: + self.SCROLL_STATE = scroll_pos + continue + elif sequence[1] == 0xf1: # dial on Q620M, practically 2 buttons + if sequence[5] == 0x1: + return 'dial_cw' + elif sequence[5] == 0xff: + return 'dial_ccw' + else: + continue + + def get_button_release(self,hidraw): + while True: + sequence = hidraw.read(12) + if sequence[1] == 0xe0 and sequence[4] == 0 and sequence[5] == 0: + return True def get_tablet_hidraw(device_id): - """Finds the /dev/hidrawX file that belongs to the given device ID (in xxxx:xxxx format).""" + """Finds the /dev/hidrawX file or files that belong to the given device ID (in xxxx:xxxx format).""" # TODO: is this too fragile? hidraws = os.listdir('/sys/class/hidraw') + inputs = []; for h in hidraws: device_path = os.readlink(os.path.join('/sys/class/hidraw', h, 'device')) if device_id.upper() in device_path: - # need to confirm that there's "input" because there are two hidraw - # files listed for the tablet, but only one of them carries the + # need to confirm that there's "input" because there are two or more hidraw + # files listed for the tablet, but only few of them carry the # mouse/keyboard input if os.path.exists(os.path.join('/sys/class/hidraw', h, 'device/input')): - return os.path.join('/dev', os.path.basename(h)) + inputs.append(os.path.join('/dev', os.path.basename(h))) + if inputs: + return inputs return None def read_config(config_file): - global CYCLE_MODES, CYCLE_MODE, CYCLE_BUTTON + global CYCLE_MODES, CYCLE_BUTTON, BUTTON_BINDINGS, BUTTON_BINDINGS_HOLD CONFIG = configparser.ConfigParser() CONFIG.read(config_file) # It is still better for performance to pre-encode these values @@ -158,11 +251,6 @@ def read_config(config_file): for binding in CONFIG[key]: DIAL_MODES[mode][binding] = CONFIG[key][binding].encode('utf-8') - -def handle_reload_signal(signum, frame): - print("SIGUSR1 recieved - reloading config..") - read_config(CONFIG_FILE_PATH) - def make_rules(): for device_name, device_id in TABLET_MODELS.items(): print("# %s" % (device_name, )) @@ -198,70 +286,5 @@ dial_cw=minus dial_ccw=equal """) - -BUTTON_BITS = { - 0x01: 1, - 0x02: 2, - 0x04: 3, - 0x08: 4, - 0x10: 5, - 0x20: 6, - 0x40: 7, - 0x80: 8, -} - -SCROLL_STATE=None - -def get_button_press(hidraw): - global SCROLL_STATE - while True: - sequence = hidraw.read(12) - # 0xf7 is what my Kamvas Pro 22 reads - # another model seems to send 0x08 - # Q620M reads as 0xf9 - if sequence[0] != 0xf7 and sequence[0] != 0x08 and sequence[0] != 0xf9: - pass - if sequence[1] == 0xe0: # buttons - # doesn't seem like the tablet will let you push two buttons at once - if sequence[4] > 0: - return BUTTON_BITS[sequence[4]] - elif sequence[5] > 0: - # right-side buttons are 8-15, so add 8 - return BUTTON_BITS[sequence[5]] + 8 - else: - # must be button release (all zeros) - continue - elif sequence[1] == 0xf0: # scroll strip - scroll_pos = sequence[5] - if scroll_pos == 0: - # reset scroll state after lifting finger off scroll strip - SCROLL_STATE = None - elif SCROLL_STATE is not None: - # scroll strip is numbered from top to bottom so a greater new - # value means they scrolled down - if scroll_pos > SCROLL_STATE: - SCROLL_STATE = scroll_pos - return 'scroll_down' - elif scroll_pos < SCROLL_STATE: - SCROLL_STATE = scroll_pos - return 'scroll_up' - else: - SCROLL_STATE = scroll_pos - continue - elif sequence[1] == 0xf1: # dial on Q620M, practically 2 buttons - if sequence[5] == 0x1: - return 'dial_cw' - elif sequence[5] == 0xff: - return 'dial_ccw' - else: - continue - -def get_button_release(hidraw): - while True: - sequence = hidraw.read(12) - if sequence[1] == 0xe0 and sequence[4] == 0 and sequence[5] == 0: - return True - - if __name__ == "__main__": main() From 611621d4abd7db763c3b9462747c22a83961cbbb Mon Sep 17 00:00:00 2001 From: NeoTheFox Date: Fri, 16 Apr 2021 23:47:02 +0300 Subject: [PATCH 2/5] minor typo fixes --- huion_keys.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/huion_keys.py b/huion_keys.py index 7a60327..7e27cb8 100755 --- a/huion_keys.py +++ b/huion_keys.py @@ -18,7 +18,7 @@ BUTTON_BINDINGS = {} BUTTON_BINDINGS_HOLD = {} CYCLE_BUTTON = None CYCLE_MODES = 1 -DIAL_MODES = {} +DIAL_MODES = {} BUTTON_BITS = { 0x01: 1, @@ -30,10 +30,9 @@ BUTTON_BITS = { 0x40: 7, 0x80: 8, } - def main(): - #Commandline arguments processing + # Commandline arguments processing parser = argparse.ArgumentParser( description='Linux utility to create custom key bindings for the Huion Kamvas Pro (2019), Inspiroy Q620M, and potentially other tablets.') parser.add_argument('--rules', action='store_true', default=False, @@ -101,7 +100,7 @@ class PollThread(threading.Thread): self.cycle_mode = 1 def run(self): - global BUTTON_BINDINGS, BUTTON_BINDINGS_HOLD, CYCLE_MODES, CYCLE_BUTTON + global BUTTON_BINDINGS, BUTTON_BINDINGS_HOLD, CYCLE_MODES, CYCLE_BUTTON, DIAL_MODES while True: try: hidraw = open(self.hidraw_path, 'rb') @@ -120,7 +119,8 @@ class PollThread(threading.Thread): time.sleep(3) break print("Got button %s" % (btn,)) - print(BUTTON_BINDINGS) + print(CYCLE_MODES) + print(self.cycle_mode) if btn == CYCLE_BUTTON and CYCLE_BUTTON is not None: self.cycle_mode = self.cycle_mode + 1 if self.cycle_mode > CYCLE_MODES: @@ -135,7 +135,7 @@ class PollThread(threading.Thread): lib.xdo_send_keysequence_window_down(self.xdo, lib.CURRENTWINDOW, BUTTON_BINDINGS_HOLD[btn], 12000) self.get_button_release(hidraw) print("Releasing %s" % (BUTTON_BINDINGS_HOLD[btn],)) - lib.xdo_send_keysequence_window_up(xdo, lib.CURRENTWINDOW, BUTTON_BINDINGS_HOLD[btn], 12000) + lib.xdo_send_keysequence_window_up(self.xdo, lib.CURRENTWINDOW, BUTTON_BINDINGS_HOLD[btn], 12000) elif btn in BUTTON_BINDINGS: print("Sending %s" % (BUTTON_BINDINGS[btn],)) lib.xdo_send_keysequence_window( From 5d3f0778f10200bb62e8a4d4219b605ec9f16875 Mon Sep 17 00:00:00 2001 From: NeoTheFox Date: Sun, 18 Apr 2021 23:45:25 +0300 Subject: [PATCH 3/5] Fixed XDO not working General cleanup in accordance with pep8 --- huion_keys.py | 90 ++++++++++++++++++++++++++------------------------- 1 file changed, 46 insertions(+), 44 deletions(-) diff --git a/huion_keys.py b/huion_keys.py index 7e27cb8..7276ed9 100755 --- a/huion_keys.py +++ b/huion_keys.py @@ -31,6 +31,7 @@ BUTTON_BITS = { 0x80: 8, } + def main(): # Commandline arguments processing parser = argparse.ArgumentParser( @@ -66,14 +67,13 @@ def main(): hidraw_path = get_tablet_hidraw(device_id) if hidraw_path is not None: hidraw_paths = hidraw_paths + hidraw_path - if hidraw_paths: print("Found %s at %s" % (device_name, hidraw_paths)) break elif not hidraw_paths: print("Could not find any tablet hidraw devices") time.sleep(2) - continue + continue threads = [] for hidraw_path in hidraw_paths: @@ -85,14 +85,14 @@ def main(): for thread in threads: thread.join() + class PollThread(threading.Thread): cycle_mode = None + scroll_state = None hidraw_path = None xdo = None - - SCROLL_STATE=None - + def __init__(self, hidraw_path): super(PollThread, self).__init__() self.xdo = lib.xdo_new(ffi.NULL) @@ -111,7 +111,7 @@ class PollThread(threading.Thread): time.sleep(5) continue - while True: + while True: try: btn = self.get_button_press(hidraw) except OSError as e: @@ -119,29 +119,27 @@ class PollThread(threading.Thread): time.sleep(3) break print("Got button %s" % (btn,)) - print(CYCLE_MODES) - print(self.cycle_mode) if btn == CYCLE_BUTTON and CYCLE_BUTTON is not None: - self.cycle_mode = self.cycle_mode + 1 + self.cycle_mode = self.cycle_mode + 1 if self.cycle_mode > CYCLE_MODES: self.cycle_mode = 1 - print("Cycling to mode %s" % (self.cycle_mode,)) - elif self.cycle_mode in DIAL_MODES and btn in DIAL_MODES[self.cycle_mode]: - print("Sending %s from Mode %d" % (DIAL_MODES[self.cycle_mode][btn], self.cycle_mode),) - lib.xdo_send_keysequence_window( + print("Cycling to mode %s" % (self.cycle_mode,)) + elif self.cycle_mode in DIAL_MODES and btn in DIAL_MODES[self.cycle_mode]: + print("Sending %s from Mode %d" % (DIAL_MODES[self.cycle_mode][btn], self.cycle_mode),) + lib.xdo_send_keysequence_window( self.xdo, lib.CURRENTWINDOW, DIAL_MODES[self.cycle_mode][btn], 1000) - elif btn in BUTTON_BINDINGS_HOLD: - print("Pressing %s" % (BUTTON_BINDINGS_HOLD[btn],)) - lib.xdo_send_keysequence_window_down(self.xdo, lib.CURRENTWINDOW, BUTTON_BINDINGS_HOLD[btn], 12000) - self.get_button_release(hidraw) - print("Releasing %s" % (BUTTON_BINDINGS_HOLD[btn],)) - lib.xdo_send_keysequence_window_up(self.xdo, lib.CURRENTWINDOW, BUTTON_BINDINGS_HOLD[btn], 12000) - elif btn in BUTTON_BINDINGS: - print("Sending %s" % (BUTTON_BINDINGS[btn],)) - lib.xdo_send_keysequence_window( - self.xdo, lib.CURRENTWINDOW, BUTTON_BINDINGS[btn], 1000) - - def get_button_press(self,hidraw): + elif btn in BUTTON_BINDINGS_HOLD: + print("Pressing %s" % (BUTTON_BINDINGS_HOLD[btn],)) + lib.xdo_send_keysequence_window_down(self.xdo, lib.CURRENTWINDOW, BUTTON_BINDINGS_HOLD[btn], 12000) + self.get_button_release(hidraw) + print("Releasing %s" % (BUTTON_BINDINGS_HOLD[btn],)) + lib.xdo_send_keysequence_window_up(self.xdo, lib.CURRENTWINDOW, BUTTON_BINDINGS_HOLD[btn], 12000) + elif btn in BUTTON_BINDINGS: + print("Sending %s" % (BUTTON_BINDINGS[btn],)) + lib.xdo_send_keysequence_window( + self.xdo, lib.CURRENTWINDOW, BUTTON_BINDINGS[btn], 1000) + + def get_button_press(self, hidraw): while True: sequence = hidraw.read(12) # 0xf7 is what my Kamvas Pro 22 reads @@ -149,7 +147,7 @@ class PollThread(threading.Thread): # Q620M reads as 0xf9 if sequence[0] != 0xf7 and sequence[0] != 0x08 and sequence[0] != 0xf9: pass - if sequence[1] == 0xe0: # buttons + if sequence[1] == 0xe0: # buttons # doesn't seem like the tablet will let you push two buttons at once if sequence[4] > 0: return BUTTON_BITS[sequence[4]] @@ -159,42 +157,43 @@ class PollThread(threading.Thread): else: # must be button release (all zeros) continue - elif sequence[1] == 0xf0: # scroll strip + elif sequence[1] == 0xf0: # scroll strip scroll_pos = sequence[5] if scroll_pos == 0: # reset scroll state after lifting finger off scroll strip - self.SCROLL_STATE = None - elif self.SCROLL_STATE is not None: + self.scroll_state = None + elif self.scroll_state is not None: # scroll strip is numbered from top to bottom so a greater new # value means they scrolled down - if scroll_pos > self.SCROLL_STATE: - self.SCROLL_STATE = scroll_pos + if scroll_pos > self.scroll_state: + self.scroll_state = scroll_pos return 'scroll_down' - elif scroll_pos < self.SCROLL_STATE: - self.SCROLL_STATE = scroll_pos + elif scroll_pos < self.scroll_state: + self.scroll_state = scroll_pos return 'scroll_up' else: - self.SCROLL_STATE = scroll_pos + self.scroll_state = scroll_pos continue - elif sequence[1] == 0xf1: # dial on Q620M, practically 2 buttons + elif sequence[1] == 0xf1: # dial on Q620M, practically 2 buttons if sequence[5] == 0x1: return 'dial_cw' - elif sequence[5] == 0xff: + elif sequence[5] == 0xff: return 'dial_ccw' else: continue - - def get_button_release(self,hidraw): + + def get_button_release(self, hidraw): while True: sequence = hidraw.read(12) if sequence[1] == 0xe0 and sequence[4] == 0 and sequence[5] == 0: return True + def get_tablet_hidraw(device_id): """Finds the /dev/hidrawX file or files that belong to the given device ID (in xxxx:xxxx format).""" # TODO: is this too fragile? hidraws = os.listdir('/sys/class/hidraw') - inputs = []; + inputs = [] for h in hidraws: device_path = os.readlink(os.path.join('/sys/class/hidraw', h, 'device')) if device_id.upper() in device_path: @@ -215,7 +214,7 @@ def read_config(config_file): # It is still better for performance to pre-encode these values for binding in CONFIG['Bindings']: if binding.isdigit(): - # store button configs with their 1-indexed ID + # store button configs with their 1-indexed ID BUTTON_BINDINGS[int(binding)] = CONFIG['Bindings'][binding].encode('utf-8') elif binding == 'scroll_up': BUTTON_BINDINGS['scroll_up'] = CONFIG['Bindings'][binding].encode('utf-8') @@ -226,10 +225,10 @@ def read_config(config_file): elif binding == 'dial_ccw': BUTTON_BINDINGS['dial_ccw'] = CONFIG['Bindings'][binding].encode('utf-8') elif binding == '': - continue # ignore empty line + continue # ignore empty line else: print("[WARN] unrecognized regular binding '%s'" % (binding,)) - #Same, but for buttons that should be held down + # Same, but for buttons that should be held down if 'Hold' in CONFIG: for binding in CONFIG['Hold']: if binding.isdigit(): @@ -237,26 +236,28 @@ def read_config(config_file): elif binding == '': continue else: - print ("[WARN] unrecognized hold binding '%s'" % (binding,)) + print("[WARN] unrecognized hold binding '%s'" % (binding,)) # Assume that if cycle is assigned we have modes for now if 'Dial' in CONFIG: CYCLE_BUTTON = int(CONFIG['Dial']['cycle']) for key in CONFIG: if key.startswith("Mode"): # Count the modes - mode = int(key.split(' ')[1]) + mode = int(key.split(' ')[1]) if mode > CYCLE_MODES: CYCLE_MODES = mode DIAL_MODES[mode] = {} for binding in CONFIG[key]: DIAL_MODES[mode][binding] = CONFIG[key][binding].encode('utf-8') + def make_rules(): for device_name, device_id in TABLET_MODELS.items(): print("# %s" % (device_name, )) VID, PID = device_id.split(':') print('KERNEL=="hidraw*", ATTRS{idVendor}=="%s", ATTRS{idProduct}=="%s", MODE="0660", TAG+="uaccess"' % (VID, PID, )) + def create_default_config(config_file): with open(config_file, 'w') as config: config.write(""" @@ -286,5 +287,6 @@ dial_cw=minus dial_ccw=equal """) + if __name__ == "__main__": main() From fde83ef33b7ea941d509ce4577d8b6067ee273c9 Mon Sep 17 00:00:00 2001 From: NeoTheFox Date: Mon, 19 Apr 2021 00:07:28 +0300 Subject: [PATCH 4/5] missing --rules flag in README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c9f351b..9ad2483 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ Inspiroy Q620M, and potentially other tablets. 6. Edit the config file to set up your key bindings. See the below section for more instructions. 7. Install the udev rules to set up permissions for the tablet. This program can generate the udev rules for you, so you can do something like: - `python huion_keys.py > /etc/udev/rules.d/50-huion-tablet.rules` + `python huion_keys.py --rules > /etc/udev/rules.d/50-huion-tablet.rules` Alternatively, you can run `huion_keys.py` as root or give yourself read permission for your tablet's hidraw file. For example: `chmod o+r /dev/hidraw3` From e93fd2599b45af8a29a204296255f1a132a7bf2a Mon Sep 17 00:00:00 2001 From: NeoTheFox Date: Mon, 19 Apr 2021 01:57:42 +0300 Subject: [PATCH 5/5] do not terminate if the tablet is disconnected --- huion_keys.py | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/huion_keys.py b/huion_keys.py index 7276ed9..a8992c6 100755 --- a/huion_keys.py +++ b/huion_keys.py @@ -62,30 +62,31 @@ def main(): hidraw_paths = [] while True: - # search for a known tablet device + # search for a known tablet devices for device_name, device_id in TABLET_MODELS.items(): hidraw_path = get_tablet_hidraw(device_id) if hidraw_path is not None: + print("Found %s at %s" % (device_name, hidraw_path)) hidraw_paths = hidraw_paths + hidraw_path - if hidraw_paths: - print("Found %s at %s" % (device_name, hidraw_paths)) - break - elif not hidraw_paths: + if not hidraw_paths: print("Could not find any tablet hidraw devices") - time.sleep(2) + time.sleep(3) + continue + elif hidraw_paths: + threads = [] + for hidraw_path in hidraw_paths: + thread = PollThread(hidraw_path) + # Do not let the threads to continue if main script is terminated + thread.daemon = True + threads.append(thread) + thread.start() + # TODO: Maybe should be reworked for the edge case of having more than one tablet connected at the same time. + for thread in threads: + thread.join() + hidraw_paths.clear() continue - threads = [] - for hidraw_path in hidraw_paths: - thread = PollThread(hidraw_path) - thread.daemon = True - threads.append(thread) - thread.start() - - for thread in threads: - thread.join() - - + class PollThread(threading.Thread): cycle_mode = None @@ -115,8 +116,7 @@ class PollThread(threading.Thread): try: btn = self.get_button_press(hidraw) except OSError as e: - print("Lost connection with the tablet - searching for tablet...") - time.sleep(3) + print("%s lost connection with the tablet..." % (self.name,)) break print("Got button %s" % (btn,)) if btn == CYCLE_BUTTON and CYCLE_BUTTON is not None: