Merge pull request #194 from quexten/feature/x11-autotype
Implement x11, mac, windows autotype
This commit is contained in:
commit
63ee486be0
|
@ -1,3 +1,8 @@
|
|||
{
|
||||
"name": "python3-requirements",
|
||||
"buildsystem": "simple",
|
||||
"build-commands": [],
|
||||
"modules": [
|
||||
{
|
||||
"name": "python3-tendo",
|
||||
"buildsystem": "simple",
|
||||
|
@ -11,4 +16,20 @@
|
|||
"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"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
|
@ -1 +1,2 @@
|
|||
tendo==0.3.0
|
||||
python3-xlib==0.15
|
|
@ -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,))
|
||||
|
|
|
@ -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()
|
|
@ -0,0 +1,16 @@
|
|||
import sys
|
||||
import os
|
||||
|
||||
is_linux = sys.platform == 'linux'
|
||||
is_wayland = os.environ.get('XDG_SESSION_TYPE') == 'wayland'
|
||||
|
||||
def autotype(text):
|
||||
if is_linux and is_wayland:
|
||||
from .libportal_autotype import autotype_libportal
|
||||
autotype_libportal(text)
|
||||
elif is_linux:
|
||||
from .x11autotype import type
|
||||
type(text)
|
||||
else:
|
||||
from .pyautogui_autotype import autotype_pyautogui
|
||||
autotype_pyautogui(text)
|
|
@ -0,0 +1,5 @@
|
|||
from ..goldwarden import autotype
|
||||
|
||||
def libportal_autotype(text):
|
||||
print("autotypeing with libportal")
|
||||
goldwarden.autotype(text)
|
|
@ -0,0 +1,5 @@
|
|||
import pyautogui
|
||||
|
||||
def autotype_pyautogui(text):
|
||||
print("autotypeing with pyautogui")
|
||||
pyautogui.write(text, interval=0.02)
|
|
@ -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)
|
||||
|
Loading…
Reference in New Issue