From e5779265c487b14782558229aae2b318bbb12191 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Sat, 4 May 2024 00:04:18 +0200 Subject: [PATCH] Add x11 autotype --- gui/python3-requirements.json | 37 ++++++-- gui/requirements.txt | 3 +- gui/src/services/autotype/autotype.py | 10 ++- gui/src/services/autotype/x11autotype.py | 107 +++++++++++++++++++++++ 4 files changed, 144 insertions(+), 13 deletions(-) create mode 100644 gui/src/services/autotype/x11autotype.py diff --git a/gui/python3-requirements.json b/gui/python3-requirements.json index 1989579..283b77c 100644 --- a/gui/python3-requirements.json +++ b/gui/python3-requirements.json @@ -1,14 +1,35 @@ { - "name": "python3-tendo", + "name": "python3-requirements", "buildsystem": "simple", - "build-commands": [ - "pip3 install --verbose --exists-action=i --no-index --find-links=\"file://${PWD}\" --prefix=${FLATPAK_DEST} \"tendo==0.3.0\" --no-build-isolation" - ], - "sources": [ + "build-commands": [], + "modules": [ { - "type": "file", - "url": "https://files.pythonhosted.org/packages/ce/3f/761077d55732b0b1a673b15d4fdaa947a7c1eb5c9a23b7142df557019823/tendo-0.3.0-py3-none-any.whl", - "sha256": "026b70b355ea4c9da7c2123fa2d5c280c8983c1b34e329ff49260e2e78b93be7" + "name": "python3-tendo", + "buildsystem": "simple", + "build-commands": [ + "pip3 install --verbose --exists-action=i --no-index --find-links=\"file://${PWD}\" --prefix=${FLATPAK_DEST} \"tendo==0.3.0\" --no-build-isolation" + ], + "sources": [ + { + "type": "file", + "url": "https://files.pythonhosted.org/packages/ce/3f/761077d55732b0b1a673b15d4fdaa947a7c1eb5c9a23b7142df557019823/tendo-0.3.0-py3-none-any.whl", + "sha256": "026b70b355ea4c9da7c2123fa2d5c280c8983c1b34e329ff49260e2e78b93be7" + } + ] + }, + { + "name": "python3-python3-xlib", + "buildsystem": "simple", + "build-commands": [ + "pip3 install --verbose --exists-action=i --no-index --find-links=\"file://${PWD}\" --prefix=${FLATPAK_DEST} \"python3-xlib==0.15\" --no-build-isolation" + ], + "sources": [ + { + "type": "file", + "url": "https://files.pythonhosted.org/packages/ef/c6/2c5999de3bb1533521f1101e8fe56fd9c266732f4d48011c7c69b29d12ae/python3-xlib-0.15.tar.gz", + "sha256": "dc4245f3ae4aa5949c1d112ee4723901ade37a96721ba9645f2bfa56e5b383f8" + } + ] } ] } \ No newline at end of file diff --git a/gui/requirements.txt b/gui/requirements.txt index 66aeab9..d39a0c1 100644 --- a/gui/requirements.txt +++ b/gui/requirements.txt @@ -1 +1,2 @@ -tendo==0.3.0 \ No newline at end of file +tendo==0.3.0 +python3-xlib==0.15 \ No newline at end of file diff --git a/gui/src/services/autotype/autotype.py b/gui/src/services/autotype/autotype.py index 50faffb..ee8a38e 100644 --- a/gui/src/services/autotype/autotype.py +++ b/gui/src/services/autotype/autotype.py @@ -5,10 +5,12 @@ is_linux = sys.platform == 'linux' is_wayland = os.environ.get('XDG_SESSION_TYPE') == 'wayland' def autotype(text): - print("autotypeing, is_linux: {}, is_wayland: {}".format(is_linux, is_wayland)) if is_linux and is_wayland: from .libportal_autotype import autotype_libportal autotype_libportal(text) - - from .pyautogui_autotype import autotype_pyautogui - autotype_pyautogui(text) \ No newline at end of file + elif is_linux: + from .x11autotype import type + type(text) + else: + from .pyautogui_autotype import autotype_pyautogui + autotype_pyautogui(text) \ No newline at end of file diff --git a/gui/src/services/autotype/x11autotype.py b/gui/src/services/autotype/x11autotype.py new file mode 100644 index 0000000..20531d1 --- /dev/null +++ b/gui/src/services/autotype/x11autotype.py @@ -0,0 +1,107 @@ +# https://github.com/gemdude46/autotype/blob/master/LICENSE +# MIT License + +# Copyright (c) 2017 gemdude46 + +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: + +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +import os +import time + +from Xlib.display import Display +from Xlib import X +from Xlib.ext.xtest import fake_input + +_display = Display(os.environ['DISPLAY']) + +def type(text, delay=0.03, down_for=0): + '''Fake key presses to type out text. + + Args: + text (str): The text to type out. + delay (float): The time to wait between presses. (In seconds) + down_for (float): How long to keep each key down. (In seconds) + + Returns: + None + ''' + + # Save the users existing keyboard mapping + kbm_backup = _display.get_keyboard_mapping(8,246) + + try: + + text = text.replace('\n', '\r') # Because X + + # Splits the text into blocks containing no more than 245 unique characters + # This is because there is a limited number of valid xmodmap keycodes + while text: + chars = set() + + i = 0 + while i < len(text) and len(chars) < 245: + char = ord(text[i]) + chars.add(char) + i += 1 + + block = text[:i] + text = text[i:] + + _type_lt245(block, delay, down_for) + + finally: + + # Restore the keyboard layout to how it was originally + _display.change_keyboard_mapping(8, kbm_backup) + _display.sync() + +def _type_lt245(text, delay, down_for): + + xmm = '' + + chars = [] + + keys = [] + + text = [(1 << 24) + ord(i) for i in text] + + for char in text: + if char not in chars: + chars.append(char) + + keys.append(chars.index(char) + 8) + + for i in range(8, 255): + fake_input(_display, X.KeyRelease, i) + + _display.change_keyboard_mapping(8, [(i, ) * 15 for i in chars]) + _display.sync() + + for key in keys: + + fake_input(_display, X.KeyPress, key) + _display.sync() + + time.sleep(down_for) + + fake_input(_display, X.KeyRelease, key) + _display.sync() + + time.sleep(delay) +