From 77eec0bc41ea4f2d8dfb4b3e1f388b8bd61f1fc0 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Fri, 3 May 2024 23:37:18 +0200 Subject: [PATCH 1/3] Implement x11, mac, windows autotype --- gui/src/gui/quickaccess.py | 13 ++- gui/src/linux/autotype/libportal_autotype.py | 101 ----------------- gui/src/services/autotype/autotype.py | 14 +++ .../services/autotype/libportal_autotype.py | 106 ++++++++++++++++++ .../services/autotype/pyautogui_autotype.py | 5 + 5 files changed, 132 insertions(+), 107 deletions(-) delete mode 100644 gui/src/linux/autotype/libportal_autotype.py create mode 100644 gui/src/services/autotype/autotype.py create mode 100644 gui/src/services/autotype/libportal_autotype.py create mode 100644 gui/src/services/autotype/pyautogui_autotype.py diff --git a/gui/src/gui/quickaccess.py b/gui/src/gui/quickaccess.py index ac21fcb..b2a272e 100644 --- a/gui/src/gui/quickaccess.py +++ b/gui/src/gui/quickaccess.py @@ -6,6 +6,7 @@ import gc import time from gi.repository import Gtk, Adw, GLib, Notify, Gdk from ..services import goldwarden +from ..services.autotype import autotype from threading import Thread from .resource_loader import load_template import sys @@ -71,19 +72,19 @@ class GoldwardenQuickAccessApp(Adw.Application): # totp code if keyval == Gdk.KEY_t or keyval == Gdk.KEY_T: if auto_type_combo: - self.autotype(totp.totp(self.filtered_logins[self.selected_index]["totp"])) + self.run_autotype(totp.totp(self.filtered_logins[self.selected_index]["totp"])) if copy_combo: self.set_clipboard(totp.totp(self.filtered_logins[self.selected_index]["totp"])) if keyval == Gdk.KEY_u or keyval == Gdk.KEY_U: if auto_type_combo: - self.autotype(self.filtered_logins[self.selected_index]["username"]) + self.run_autotype(self.filtered_logins[self.selected_index]["username"]) if copy_combo: self.set_clipboard(self.filtered_logins[self.selected_index]["username"]) if keyval == Gdk.KEY_p or keyval == Gdk.KEY_P: if auto_type_combo: - self.autotype(self.filtered_logins[self.selected_index]["password"]) + self.run_autotype(self.filtered_logins[self.selected_index]["password"]) if copy_combo: self.set_clipboard(self.filtered_logins[self.selected_index]["password"]) @@ -100,18 +101,18 @@ class GoldwardenQuickAccessApp(Adw.Application): if keyval == Gdk.KEY_Return: if auto_type_combo: - self.autotype(f"{self.filtered_logins[self.selected_index]['username']}\t{self.filtered_logins[self.selected_index]['password']}") + self.run_autotype(f"{self.filtered_logins[self.selected_index]['username']}\t{self.filtered_logins[self.selected_index]['password']}") def update(self): self.update_list() self.render_list() return True - def autotype(self, text): + def run_autotype(self, text): def perform_autotype(text): self.window.hide() time.sleep(0.1) - goldwarden.autotype(text) + autotype.autotype(text) time.sleep(0.1) os._exit(0) thread = Thread(target=perform_autotype, args=(text,)) diff --git a/gui/src/linux/autotype/libportal_autotype.py b/gui/src/linux/autotype/libportal_autotype.py deleted file mode 100644 index 2f75c12..0000000 --- a/gui/src/linux/autotype/libportal_autotype.py +++ /dev/null @@ -1,101 +0,0 @@ -# TODO??!?!? for now using golang implementation - -import dbus -import dbus.mainloop.glib -from dbus.mainloop.glib import DBusGMainLoop - -from gi.repository import GLib - -import random -import time - -step = 0 - -def typestring(text): - step = 0 - handle = "" - - def handler(*args, **kwargs): - global step - if step == 0: - handle_xdp_session_created(*args, **kwargs) - elif step == 1: - handle_xdp_devices_selected(*args, **kwargs) - elif step == 2: - handle_session_start(*args, **kwargs) - else: - print(args, kwargs) - step += 1 - - def handle_session_start(code, results, object_path): - global handle - - if code != 0: - raise Exception("Could not start session") - - for sym in text: - if sym == "\t": - inter.NotifyKeyboardKeycode(handle, {}, 15, 1) - time.sleep(0.001) - inter.NotifyKeyboardKeycode(handle, {}, 15, 0) - time.sleep(0.001) - else: - inter.NotifyKeyboardKeysym(handle, {}, ord(sym), 1) - time.sleep(0.001) - inter.NotifyKeyboardKeysym(handle, {}, ord(sym), 0) - time.sleep(0.001) - - bus - - def handle_xdp_devices_selected(code, results, object_path): - global handle - - if code != 0: - raise Exception("Could not select devices") - - start_options = { - "handle_token": "krfb" + str(random.randint(0, 999999999)) - } - reply = inter.Start(handle, "", start_options) - print(reply) - - def handle_xdp_session_created(code, results, object_path): - global handle - - if code != 0: - raise Exception("Could not create session") - print(results) - handle = results["session_handle"] - - # select sources for the session - selection_options = { - "types": dbus.UInt32(7), # request all (KeyBoard, Pointer, TouchScreen) - "handle_token": "krfb" + str(random.randint(0, 999999999)) - } - selector_reply = inter.SelectDevices(handle, selection_options) - print(selector_reply) - - def main(): - global bus - global inter - loop = GLib.MainLoop() - dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) - bus = dbus.SessionBus() - obj = bus.get_object("org.freedesktop.portal.Desktop", "/org/freedesktop/portal/desktop") - inter = dbus.Interface(obj, "org.freedesktop.portal.RemoteDesktop") - - bus.add_signal_receiver( - handler, - signal_name="Response", - dbus_interface="org.freedesktop.portal.Request", - bus_name="org.freedesktop.portal.Desktop", - path_keyword="object_path") - - print(inter) - result = inter.CreateSession({ - "session_handle_token": "sessionhandletoken" - }) - print(result) - loop.run() - - main() diff --git a/gui/src/services/autotype/autotype.py b/gui/src/services/autotype/autotype.py new file mode 100644 index 0000000..50faffb --- /dev/null +++ b/gui/src/services/autotype/autotype.py @@ -0,0 +1,14 @@ +import sys +import os + +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 diff --git a/gui/src/services/autotype/libportal_autotype.py b/gui/src/services/autotype/libportal_autotype.py new file mode 100644 index 0000000..229ebda --- /dev/null +++ b/gui/src/services/autotype/libportal_autotype.py @@ -0,0 +1,106 @@ +# TODO??!?!? for now using golang implementation +from ..goldwarden import autotype + +def libportal_autotype(text): + print("autotypeing with libportal") + goldwarden.autotype(text) + +# import dbus +# import dbus.mainloop.glib +# from dbus.mainloop.glib import DBusGMainLoop + +# from gi.repository import GLib + +# import random +# import time + +# step = 0 + +# def typestring(text): +# step = 0 +# handle = "" + +# def handler(*args, **kwargs): +# global step +# if step == 0: +# handle_xdp_session_created(*args, **kwargs) +# elif step == 1: +# handle_xdp_devices_selected(*args, **kwargs) +# elif step == 2: +# handle_session_start(*args, **kwargs) +# else: +# print(args, kwargs) +# step += 1 + +# def handle_session_start(code, results, object_path): +# global handle + +# if code != 0: +# raise Exception("Could not start session") + +# for sym in text: +# if sym == "\t": +# inter.NotifyKeyboardKeycode(handle, {}, 15, 1) +# time.sleep(0.001) +# inter.NotifyKeyboardKeycode(handle, {}, 15, 0) +# time.sleep(0.001) +# else: +# inter.NotifyKeyboardKeysym(handle, {}, ord(sym), 1) +# time.sleep(0.001) +# inter.NotifyKeyboardKeysym(handle, {}, ord(sym), 0) +# time.sleep(0.001) + +# bus + +# def handle_xdp_devices_selected(code, results, object_path): +# global handle + +# if code != 0: +# raise Exception("Could not select devices") + +# start_options = { +# "handle_token": "krfb" + str(random.randint(0, 999999999)) +# } +# reply = inter.Start(handle, "", start_options) +# print(reply) + +# def handle_xdp_session_created(code, results, object_path): +# global handle + +# if code != 0: +# raise Exception("Could not create session") +# print(results) +# handle = results["session_handle"] + +# # select sources for the session +# selection_options = { +# "types": dbus.UInt32(7), # request all (KeyBoard, Pointer, TouchScreen) +# "handle_token": "krfb" + str(random.randint(0, 999999999)) +# } +# selector_reply = inter.SelectDevices(handle, selection_options) +# print(selector_reply) + +# def main(): +# global bus +# global inter +# loop = GLib.MainLoop() +# dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) +# bus = dbus.SessionBus() +# obj = bus.get_object("org.freedesktop.portal.Desktop", "/org/freedesktop/portal/desktop") +# inter = dbus.Interface(obj, "org.freedesktop.portal.RemoteDesktop") + +# bus.add_signal_receiver( +# handler, +# signal_name="Response", +# dbus_interface="org.freedesktop.portal.Request", +# bus_name="org.freedesktop.portal.Desktop", +# path_keyword="object_path") + +# print(inter) +# result = inter.CreateSession({ +# "session_handle_token": "sessionhandletoken" +# }) +# print(result) +# loop.run() + +# main() diff --git a/gui/src/services/autotype/pyautogui_autotype.py b/gui/src/services/autotype/pyautogui_autotype.py new file mode 100644 index 0000000..3b8057a --- /dev/null +++ b/gui/src/services/autotype/pyautogui_autotype.py @@ -0,0 +1,5 @@ +import pyautogui + +def autotype_pyautogui(text): + print("autotypeing with pyautogui") + pyautogui.write(text, interval=0.02) \ No newline at end of file From f0773b5fd31f51e3130c06f3cc935efa6c97a0cd Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Fri, 3 May 2024 23:39:16 +0200 Subject: [PATCH 2/3] Remove python autotype implementation --- .../services/autotype/libportal_autotype.py | 103 +----------------- 1 file changed, 1 insertion(+), 102 deletions(-) diff --git a/gui/src/services/autotype/libportal_autotype.py b/gui/src/services/autotype/libportal_autotype.py index 229ebda..e5de665 100644 --- a/gui/src/services/autotype/libportal_autotype.py +++ b/gui/src/services/autotype/libportal_autotype.py @@ -1,106 +1,5 @@ -# TODO??!?!? for now using golang implementation from ..goldwarden import autotype def libportal_autotype(text): print("autotypeing with libportal") - goldwarden.autotype(text) - -# import dbus -# import dbus.mainloop.glib -# from dbus.mainloop.glib import DBusGMainLoop - -# from gi.repository import GLib - -# import random -# import time - -# step = 0 - -# def typestring(text): -# step = 0 -# handle = "" - -# def handler(*args, **kwargs): -# global step -# if step == 0: -# handle_xdp_session_created(*args, **kwargs) -# elif step == 1: -# handle_xdp_devices_selected(*args, **kwargs) -# elif step == 2: -# handle_session_start(*args, **kwargs) -# else: -# print(args, kwargs) -# step += 1 - -# def handle_session_start(code, results, object_path): -# global handle - -# if code != 0: -# raise Exception("Could not start session") - -# for sym in text: -# if sym == "\t": -# inter.NotifyKeyboardKeycode(handle, {}, 15, 1) -# time.sleep(0.001) -# inter.NotifyKeyboardKeycode(handle, {}, 15, 0) -# time.sleep(0.001) -# else: -# inter.NotifyKeyboardKeysym(handle, {}, ord(sym), 1) -# time.sleep(0.001) -# inter.NotifyKeyboardKeysym(handle, {}, ord(sym), 0) -# time.sleep(0.001) - -# bus - -# def handle_xdp_devices_selected(code, results, object_path): -# global handle - -# if code != 0: -# raise Exception("Could not select devices") - -# start_options = { -# "handle_token": "krfb" + str(random.randint(0, 999999999)) -# } -# reply = inter.Start(handle, "", start_options) -# print(reply) - -# def handle_xdp_session_created(code, results, object_path): -# global handle - -# if code != 0: -# raise Exception("Could not create session") -# print(results) -# handle = results["session_handle"] - -# # select sources for the session -# selection_options = { -# "types": dbus.UInt32(7), # request all (KeyBoard, Pointer, TouchScreen) -# "handle_token": "krfb" + str(random.randint(0, 999999999)) -# } -# selector_reply = inter.SelectDevices(handle, selection_options) -# print(selector_reply) - -# def main(): -# global bus -# global inter -# loop = GLib.MainLoop() -# dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) -# bus = dbus.SessionBus() -# obj = bus.get_object("org.freedesktop.portal.Desktop", "/org/freedesktop/portal/desktop") -# inter = dbus.Interface(obj, "org.freedesktop.portal.RemoteDesktop") - -# bus.add_signal_receiver( -# handler, -# signal_name="Response", -# dbus_interface="org.freedesktop.portal.Request", -# bus_name="org.freedesktop.portal.Desktop", -# path_keyword="object_path") - -# print(inter) -# result = inter.CreateSession({ -# "session_handle_token": "sessionhandletoken" -# }) -# print(result) -# loop.run() - -# main() + goldwarden.autotype(text) \ No newline at end of file From e5779265c487b14782558229aae2b318bbb12191 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Sat, 4 May 2024 00:04:18 +0200 Subject: [PATCH 3/3] 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) +