This repository has been archived on 2025-01-25. You can view files and clone it, but cannot push or open issues or pull requests.
PyBitmessage-2025-01-25/mockenv/lib/python3.6/site-packages/telenium/mods/telenium_client.py
2022-07-22 16:13:59 +05:30

478 lines
12 KiB
Python

# coding=utf-8
VERSION = 2
import sys
import os
import re
import threading
import traceback
from kivy.logger import Logger
from kivy.app import App
from kivy.clock import Clock
from werkzeug.wrappers import Request, Response
from werkzeug.serving import run_simple
from jsonrpc import JSONRPCResponseManager, dispatcher
from telenium.xpath import XpathParser
from kivy.input.motionevent import MotionEvent
from kivy.input.provider import MotionEventProvider
from kivy.compat import unichr
from itertools import count
from time import time
nextid = count()
telenium_input = None
def kivythread(f):
def f2(*args, **kwargs):
ev = threading.Event()
ev_value = threading.Event()
def custom_call(dt):
if f(*args, **kwargs):
ev_value.set()
ev.set()
Clock.schedule_once(custom_call, 0)
ev.wait()
return ev_value.is_set()
return f2
def pick_widget(widget, x, y):
ret = None
# try to filter widgets that are not visible (invalid inspect target)
if (hasattr(widget, 'visible') and not widget.visible):
return ret
if widget.collide_point(x, y):
ret = widget
x2, y2 = widget.to_local(x, y)
# reverse the loop - look at children on top first
for child in reversed(widget.children):
ret = pick_widget(child, x2, y2) or ret
return ret
def collide_at(widget, x, y):
if widget.collide_point(x, y):
x2, y2 = widget.to_local(x, y)
have_results = False
for child in reversed(widget.children):
for ret in collide_at(child, x2, y2):
yield ret
have_results = True
if not have_results:
yield widget
class TeleniumMotionEvent(MotionEvent):
def depack(self, args):
self.is_touch = True
self.sx, self.sy = args[:2]
super(TeleniumMotionEvent, self).depack(args)
class TeleniumInputProvider(MotionEventProvider):
events = []
def update(self, dispatch_fn):
while self.events:
event = self.events.pop(0)
dispatch_fn(*event)
def selectAll(selector, root=None):
if root is None:
root = App.get_running_app().root.parent
parser = XpathParser()
matches = parser.parse(selector)
matches = matches.execute(root)
return matches or []
def selectFirst(selector, root=None):
matches = selectAll(selector, root=root)
if matches:
return matches[0]
def rpc_getattr(selector, key):
widget = selectFirst(selector)
if widget:
return getattr(widget, key)
def path_to(widget):
from kivy.core.window import Window
root = Window
if widget.parent is root or widget.parent == widget or not widget.parent:
return "/{}".format(widget.__class__.__name__)
return "{}/{}[{}]".format(
path_to(widget.parent), widget.__class__.__name__,
widget.parent.children.index(widget))
def rpc_ping():
return True
def rpc_version():
return VERSION
def rpc_get_token():
return os.environ.get("TELENIUM_TOKEN")
@kivythread
def rpc_app_quit():
App.get_running_app().stop()
return True
def rpc_app_ready():
app = App.get_running_app()
if app is None:
return False
if app.root is None:
return False
return True
def rpc_select(selector, with_bounds=False):
if not with_bounds:
return list(map(path_to, selectAll(selector)))
results = []
for widget in selectAll(selector):
left, bottom = widget.to_window(widget.x, widget.y)
right, top = widget.to_window(widget.x + widget.width, widget.y + widget.height)
bounds = (left, bottom, right, top)
path = path_to(widget)
results.append((path, bounds))
return results
def rpc_highlight(selector):
if not selector:
results = []
else:
try:
results = rpc_select(selector, with_bounds=True)
except:
_highlight([])
raise
_highlight(results)
return results
@kivythread
def _highlight(results):
from kivy.graphics import Color, Rectangle, Canvas
from kivy.core.window import Window
if not hasattr(Window, "_telenium_canvas"):
Window._telenium_canvas = Canvas()
_canvas = Window._telenium_canvas
Window.canvas.remove(_canvas)
Window.canvas.add(_canvas)
_canvas.clear()
with _canvas:
Color(1, 0, 0, 0.5)
for widget, bounds in results:
left, bottom, right, top = bounds
Rectangle(pos=(left, bottom), size=(right-left, top-bottom))
@kivythread
def rpc_setattr(selector, key, value):
ret = False
for widget in selectAll(selector):
setattr(widget, key, value)
ret = True
return ret
@kivythread
def rpc_element(selector):
if selectFirst(selector):
return True
idmap = {}
@kivythread
def rpc_execute(cmd):
app = App.get_running_app()
idmap["app"] = app
print("execute", cmd)
try:
exec(cmd, idmap, idmap)
except Exception:
traceback.print_exc()
return False
return True
def rpc_evaluate(cmd):
ev = threading.Event()
result = []
_rpc_evaluate(cmd, ev, result)
ev.wait()
return result[0]
@kivythread
def _rpc_evaluate(cmd, ev, result):
app = App.get_running_app()
idmap["app"] = app
res = None
try:
res = eval(cmd, idmap, idmap)
result.append(res)
finally:
if not result:
result.append(None)
ev.set()
def rpc_evaluate_and_store(key, cmd):
ev = threading.Event()
result = []
_rpc_evaluate(cmd, ev, result)
ev.wait()
idmap[key] = result[0]
return True
def rpc_select_and_store(key, selector):
idmap[key] = result = selectFirst(selector)
return result is not None
def rpc_pick(all=False):
from kivy.core.window import Window
widgets = []
ev = threading.Event()
def on_touch_down(touch):
root = App.get_running_app().root
for widget in Window.children:
if all:
widgets.extend(list(collide_at(root, touch.x, touch.y)))
else:
widget = pick_widget(root, touch.x, touch.y)
widgets.append(widget)
ev.set()
return True
orig_on_touch_down = Window.on_touch_down
Window.on_touch_down = on_touch_down
ev.wait()
Window.on_touch_down = orig_on_touch_down
if widgets:
if all:
ret = list(map(path_to, widgets))
else:
ret = path_to(widgets[0])
return ret
@kivythread
def rpc_click_on(selector):
w = selectFirst(selector)
if w:
from kivy.core.window import Window
cx, cy = w.to_window(w.center_x, w.center_y)
sx = cx / float(Window.width)
sy = cy / float(Window.height)
me = TeleniumMotionEvent("telenium",
id=next(nextid),
args=[sx, sy])
telenium_input.events.append(("begin", me))
telenium_input.events.append(("end", me))
return True
@kivythread
def rpc_drag(selector, target, duration):
from kivy.base import EventLoop
w1 = selectFirst(selector)
w2 = selectFirst(target)
duration = float(duration)
if w1 and w2:
from kivy.core.window import Window
cx1, cy1 = w1.to_window(w1.center_x, w1.center_y)
sx1 = cx1 / float(Window.width)
sy1 = cy1 / float(Window.height)
me = TeleniumMotionEvent("telenium",
id=next(nextid),
args=[sx1, sy1])
telenium_input.events.append(("begin", me))
if not duration:
telenium_input.events.append(("end", me))
else:
d = 0
while d < duration:
t = time()
EventLoop.idle()
dt = time() - t
# need to compute that ever frame, it could have moved
cx2, cy2 = w2.to_window(w2.center_x, w2.center_y)
sx2 = cx2 / float(Window.width)
sy2 = cy2 / float(Window.height)
dsx = dt * (sx2 - me.sx) / (duration - d)
dsy = dt * (sy2 - me.sy) / (duration - d)
me.sx += dsx
me.sy += dsy
telenium_input.events.append(("update", me))
d += dt
telenium_input.events.append(("end", me))
return True
def rpc_send_keycode(keycodes):
# very hard to get it right, not fully tested and fail proof.
# just the basics.
from kivy.core.window import Keyboard
keys = keycodes.split("+")
scancode = 0
key = None
sym = ""
modifiers = []
for el in keys:
if re.match("^[A-Z]", el):
lower_el = el.lower()
# modifier detected ? add it
if lower_el in ("ctrl", "meta", "alt", "shift"):
modifiers.append(lower_el)
continue
# not a modifier, convert to scancode
sym = lower_el
key = Keyboard.keycodes.get(lower_el, 0)
else:
# may fail, so nothing would be done.
try:
key = int(el)
sym = unichr(key)
except:
traceback.print_exc()
return False
_send_keycode(key, scancode, sym, modifiers)
return True
@kivythread
def _send_keycode(key, scancode, sym, modifiers):
from kivy.core.window import Window
print("Telenium: send key key={!r} scancode={} sym={!r} modifiers={}".format(
key, scancode, sym, modifiers
))
if not Window.dispatch("on_key_down", key, scancode, sym, modifiers):
Window.dispatch("on_keyboard", key, scancode, sym, modifiers)
Window.dispatch("on_key_up", key, scancode)
return True
def rpc_screenshot():
ev = threading.Event()
result = []
_rpc_screenshot(ev, result)
ev.wait()
return result[0]
@kivythread
def _rpc_screenshot(ev, result):
import base64
filename = None
data = None
try:
from kivy.core.window import Window
filename = Window.screenshot()
with open(filename, "rb") as fd:
data = fd.read()
os.unlink(filename)
return True
finally:
result.append({
"filename": filename,
"data": base64.b64encode(data).decode("utf-8")
})
ev.set()
def register_input_provider():
global telenium_input
telenium_input = TeleniumInputProvider("telenium", None)
from kivy.base import EventLoop
EventLoop.add_input_provider(telenium_input)
@Request.application
def application(request):
print("application request", request.data)
try:
response = JSONRPCResponseManager.handle(
request.data, dispatcher)
print("application response", response)
print("application response", response.json)
except Exception as e:
print("application exception", e)
raise
return Response(response.json, mimetype='application/json')
def run_telenium():
Logger.info("TeleniumClient: Started at 0.0.0.0:9901")
register_input_provider()
dispatcher.add_method(rpc_version, "version")
dispatcher.add_method(rpc_ping, "ping")
dispatcher.add_method(rpc_get_token, "get_token")
dispatcher.add_method(rpc_app_quit, "app_quit")
dispatcher.add_method(rpc_app_ready, "app_ready")
dispatcher.add_method(rpc_select, "select")
dispatcher.add_method(rpc_highlight, "highlight")
dispatcher.add_method(rpc_getattr, "getattr")
dispatcher.add_method(rpc_setattr, "setattr")
dispatcher.add_method(rpc_element, "element")
dispatcher.add_method(rpc_execute, "execute")
dispatcher.add_method(rpc_evaluate, "evaluate")
dispatcher.add_method(rpc_evaluate_and_store, "evaluate_and_store")
dispatcher.add_method(rpc_select_and_store, "select_and_store")
dispatcher.add_method(rpc_pick, "pick")
dispatcher.add_method(rpc_click_on, "click_on")
dispatcher.add_method(rpc_drag, "drag")
dispatcher.add_method(rpc_send_keycode, "send_keycode")
dispatcher.add_method(rpc_screenshot, "screenshot")
run_simple("0.0.0.0", 9901, application)
def install():
thread = threading.Thread(target=run_telenium)
thread.daemon = True
thread.start()
def start(win, ctx):
Logger.info("TeleniumClient: Start")
ctx.thread = threading.Thread(target=run_telenium)
ctx.thread.daemon = True
ctx.thread.start()
def stop(win, ctx):
pass