|
2 | 2 | # SPDX-FileCopyrightText: 2021-2023 Harald Sitter <sitter@kde.org>
|
3 | 3 |
|
4 | 4 | import base64
|
5 |
| -from datetime import datetime, timedelta |
6 |
| -import numpy as np |
7 |
| -import tempfile |
8 |
| -import time |
9 |
| -import traceback |
10 |
| -from flask import Flask, request, jsonify |
11 |
| -import uuid |
12 | 5 | import json
|
13 |
| -import sys |
| 6 | +import logging |
14 | 7 | import os
|
15 | 8 | import signal
|
16 | 9 | import subprocess
|
17 |
| -from werkzeug.exceptions import HTTPException |
| 10 | +import sys |
| 11 | +import tempfile |
| 12 | +import time |
| 13 | +import traceback |
| 14 | +import uuid |
| 15 | +from datetime import datetime, timedelta |
| 16 | +from typing import cast |
18 | 17 |
|
| 18 | +import gi |
| 19 | +import numpy as np |
19 | 20 | import pyatspi
|
| 21 | +from flask import Flask, jsonify, request |
20 | 22 | from lxml import etree
|
| 23 | +from werkzeug.exceptions import HTTPException |
| 24 | + |
| 25 | +from app_roles import ROLE_NAMES |
21 | 26 |
|
22 |
| -import gi |
23 |
| -from gi.repository import GLib |
24 |
| -from gi.repository import Gio |
25 | 27 | gi.require_version('Gdk', '3.0')
|
26 |
| -from gi.repository import Gdk |
27 | 28 | gi.require_version('Gtk', '3.0')
|
28 |
| -from gi.repository import Gtk |
29 |
| - |
30 |
| -from app_roles import ROLE_NAMES |
| 29 | +from gi.repository import Gdk, Gio, GLib, Gtk |
31 | 30 |
|
32 | 31 | # Exposes AT-SPI as a webdriver. This is written in python because C sucks and pyatspi is a first class binding so
|
33 | 32 | # we lose nothing but gain the reduced sucking of python.
|
|
40 | 39 | EVENTLOOP_TIME = 0.1
|
41 | 40 | EVENTLOOP_TIME_LONG = 0.5
|
42 | 41 | sys.stdout = sys.stderr
|
43 |
| -sessions = {} # global dict of open sessions |
| 42 | +sessions = {} # global dict of open sessions |
| 43 | + |
| 44 | +logger = logging.Logger("selenium-webdriver-at-spi", logging.INFO) |
44 | 45 |
|
45 | 46 | # Give the GUI enough time to react. tests run on the CI won't always be responsive in the tight schedule established by at-spi2 (800ms) and run risk
|
46 | 47 | # of timing out on (e.g.) click events. The second value is the timeout for app startup, we keep that the same as upstream.
|
|
49 | 50 | # Using flask because I know nothing about writing REST in python and it seemed the most straight-forward framework.
|
50 | 51 | app = Flask(__name__)
|
51 | 52 |
|
| 53 | + |
52 | 54 | @app.errorhandler(Exception)
|
53 | 55 | def unknown_error(e):
|
54 | 56 | if isinstance(e, HTTPException):
|
@@ -641,7 +643,28 @@ def session_element_value(session_id, element_id):
|
641 | 643 | break
|
642 | 644 | if not processed:
|
643 | 645 | raise RuntimeError("element's actions list didn't contain SetFocus. The element may be malformed")
|
644 |
| - return json.dumps({'value': None}), 200, {'content-type': 'application/json'} |
| 646 | + |
| 647 | + |
| 648 | +@app.route('/session/<session_id>/execute/sync', methods=['POST']) |
| 649 | +def session_execute(session_id): |
| 650 | + session = sessions[session_id] |
| 651 | + if not session: |
| 652 | + return json.dumps({'value': {'error': 'no such window'}}), 404, {'content-type': 'application/json'} |
| 653 | + |
| 654 | + blob = json.loads(request.data) |
| 655 | + script = cast(str, blob['script']) |
| 656 | + args = blob['args'] |
| 657 | + |
| 658 | + logger.info(script) |
| 659 | + logger.info(args) |
| 660 | + |
| 661 | + match script: |
| 662 | + case "mobile: getClipboard": |
| 663 | + content_type = cast(str, args['contentType'] if 'contentType' in args else 'plaintext') |
| 664 | + data = cast(str, get_clipboard(content_type)) |
| 665 | + return json.dumps({'value': base64.b64encode(data.encode('utf-8')).decode('utf-8')}), 200, {'content-type': 'application/json'} |
| 666 | + |
| 667 | + return json.dumps({'value': None}), 200, {'content-type': 'application/json'} |
645 | 668 |
|
646 | 669 |
|
647 | 670 | @app.route('/session/<session_id>/element/<element_id>/clear', methods=['POST'])
|
@@ -784,25 +807,7 @@ def session_appium_device_get_clipboard(session_id):
|
784 | 807 |
|
785 | 808 | blob = json.loads(request.data)
|
786 | 809 | contentType = blob['contentType']
|
787 |
| - |
788 |
| - # NOTE: need a window because on wayland we must be the active window to manipulate the clipboard (currently anyway) |
789 |
| - window = Gtk.Window() |
790 |
| - window.set_default_size(20, 20) |
791 |
| - window.show() |
792 |
| - display = window.get_display() |
793 |
| - clipboard = Gtk.Clipboard.get_for_display(display, Gdk.SELECTION_CLIPBOARD) |
794 |
| - |
795 |
| - spin_glib_main_context() |
796 |
| - |
797 |
| - data = None |
798 |
| - if contentType == 'plaintext': |
799 |
| - data = clipboard.wait_for_text() |
800 |
| - else: |
801 |
| - raise 'content type not currently supported' |
802 |
| - |
803 |
| - window.close() |
804 |
| - |
805 |
| - spin_glib_main_context() |
| 810 | + data = cast(str, get_clipboard(contentType)) |
806 | 811 |
|
807 | 812 | return json.dumps({'value': base64.b64encode(data.encode('utf-8')).decode('utf-8')}), 200, {'content-type': 'application/json'}
|
808 | 813 |
|
@@ -1065,6 +1070,29 @@ def char_to_keyval(ch):
|
1065 | 1070 | return keyval
|
1066 | 1071 |
|
1067 | 1072 |
|
| 1073 | +def get_clipboard(content_type): |
| 1074 | + # NOTE: need a window because on wayland we must be the active window to manipulate the clipboard (currently anyway) |
| 1075 | + window = Gtk.Window() |
| 1076 | + window.set_default_size(20, 20) |
| 1077 | + window.show() |
| 1078 | + display = window.get_display() |
| 1079 | + clipboard = Gtk.Clipboard.get_for_display(display, Gdk.SELECTION_CLIPBOARD) |
| 1080 | + |
| 1081 | + spin_glib_main_context() |
| 1082 | + |
| 1083 | + data = None |
| 1084 | + if content_type == 'plaintext': |
| 1085 | + data = clipboard.wait_for_text() |
| 1086 | + else: |
| 1087 | + raise 'content type not currently supported' |
| 1088 | + |
| 1089 | + window.close() |
| 1090 | + |
| 1091 | + spin_glib_main_context() |
| 1092 | + |
| 1093 | + return data |
| 1094 | + |
| 1095 | + |
1068 | 1096 | def spin_glib_main_context(repeat: int = 4):
|
1069 | 1097 | context = GLib.MainContext.default()
|
1070 | 1098 | for _ in range(repeat):
|
|
0 commit comments