pygpt-net 2.7.4__py3-none-any.whl → 2.7.6__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pygpt_net/CHANGELOG.txt +15 -0
- pygpt_net/__init__.py +4 -4
- pygpt_net/app_core.py +4 -2
- pygpt_net/controller/__init__.py +5 -1
- pygpt_net/controller/assistant/assistant.py +1 -4
- pygpt_net/controller/assistant/batch.py +5 -504
- pygpt_net/controller/assistant/editor.py +5 -5
- pygpt_net/controller/assistant/files.py +16 -16
- pygpt_net/controller/chat/handler/google_stream.py +307 -1
- pygpt_net/controller/chat/handler/worker.py +10 -25
- pygpt_net/controller/chat/handler/xai_stream.py +621 -52
- pygpt_net/controller/chat/image.py +2 -2
- pygpt_net/controller/debug/fixtures.py +3 -2
- pygpt_net/controller/dialogs/confirm.py +73 -101
- pygpt_net/controller/files/files.py +65 -4
- pygpt_net/controller/lang/mapping.py +9 -9
- pygpt_net/controller/painter/capture.py +50 -1
- pygpt_net/controller/presets/presets.py +2 -1
- pygpt_net/controller/remote_store/__init__.py +12 -0
- pygpt_net/{provider/core/assistant_file/db_sqlite → controller/remote_store/google}/__init__.py +2 -2
- pygpt_net/controller/remote_store/google/batch.py +402 -0
- pygpt_net/controller/remote_store/google/store.py +615 -0
- pygpt_net/controller/remote_store/openai/__init__.py +12 -0
- pygpt_net/controller/remote_store/openai/batch.py +524 -0
- pygpt_net/controller/{assistant → remote_store/openai}/store.py +63 -60
- pygpt_net/controller/remote_store/remote_store.py +35 -0
- pygpt_net/controller/ui/ui.py +20 -1
- pygpt_net/core/assistants/assistants.py +3 -15
- pygpt_net/core/db/database.py +5 -3
- pygpt_net/core/filesystem/url.py +4 -1
- pygpt_net/core/locale/placeholder.py +35 -0
- pygpt_net/core/remote_store/__init__.py +12 -0
- pygpt_net/core/remote_store/google/__init__.py +11 -0
- pygpt_net/core/remote_store/google/files.py +224 -0
- pygpt_net/core/remote_store/google/store.py +248 -0
- pygpt_net/core/remote_store/openai/__init__.py +11 -0
- pygpt_net/core/{assistants → remote_store/openai}/files.py +26 -19
- pygpt_net/core/{assistants → remote_store/openai}/store.py +32 -15
- pygpt_net/core/remote_store/remote_store.py +24 -0
- pygpt_net/core/render/web/body.py +3 -2
- pygpt_net/core/types/chunk.py +27 -0
- pygpt_net/data/config/config.json +8 -4
- pygpt_net/data/config/models.json +77 -3
- pygpt_net/data/config/settings.json +45 -0
- pygpt_net/data/js/app/template.js +1 -1
- pygpt_net/data/js/app.min.js +2 -2
- pygpt_net/data/locale/locale.de.ini +44 -41
- pygpt_net/data/locale/locale.en.ini +56 -43
- pygpt_net/data/locale/locale.es.ini +44 -41
- pygpt_net/data/locale/locale.fr.ini +44 -41
- pygpt_net/data/locale/locale.it.ini +44 -41
- pygpt_net/data/locale/locale.pl.ini +45 -42
- pygpt_net/data/locale/locale.uk.ini +44 -41
- pygpt_net/data/locale/locale.zh.ini +44 -41
- pygpt_net/data/locale/plugin.cmd_history.de.ini +1 -1
- pygpt_net/data/locale/plugin.cmd_history.en.ini +1 -1
- pygpt_net/data/locale/plugin.cmd_history.es.ini +1 -1
- pygpt_net/data/locale/plugin.cmd_history.fr.ini +1 -1
- pygpt_net/data/locale/plugin.cmd_history.it.ini +1 -1
- pygpt_net/data/locale/plugin.cmd_history.pl.ini +1 -1
- pygpt_net/data/locale/plugin.cmd_history.uk.ini +1 -1
- pygpt_net/data/locale/plugin.cmd_history.zh.ini +1 -1
- pygpt_net/data/locale/plugin.cmd_mouse_control.en.ini +14 -0
- pygpt_net/data/locale/plugin.cmd_web.de.ini +1 -1
- pygpt_net/data/locale/plugin.cmd_web.en.ini +1 -1
- pygpt_net/data/locale/plugin.cmd_web.es.ini +1 -1
- pygpt_net/data/locale/plugin.cmd_web.fr.ini +1 -1
- pygpt_net/data/locale/plugin.cmd_web.it.ini +1 -1
- pygpt_net/data/locale/plugin.cmd_web.pl.ini +1 -1
- pygpt_net/data/locale/plugin.cmd_web.uk.ini +1 -1
- pygpt_net/data/locale/plugin.cmd_web.zh.ini +1 -1
- pygpt_net/data/locale/plugin.idx_llama_index.de.ini +2 -2
- pygpt_net/data/locale/plugin.idx_llama_index.en.ini +2 -2
- pygpt_net/data/locale/plugin.idx_llama_index.es.ini +2 -2
- pygpt_net/data/locale/plugin.idx_llama_index.fr.ini +2 -2
- pygpt_net/data/locale/plugin.idx_llama_index.it.ini +2 -2
- pygpt_net/data/locale/plugin.idx_llama_index.pl.ini +2 -2
- pygpt_net/data/locale/plugin.idx_llama_index.uk.ini +2 -2
- pygpt_net/data/locale/plugin.idx_llama_index.zh.ini +2 -2
- pygpt_net/item/assistant.py +1 -211
- pygpt_net/item/ctx.py +3 -3
- pygpt_net/item/store.py +238 -0
- pygpt_net/js_rc.py +2449 -2447
- pygpt_net/migrations/Version20260102190000.py +35 -0
- pygpt_net/migrations/__init__.py +3 -1
- pygpt_net/plugin/cmd_mouse_control/config.py +471 -1
- pygpt_net/plugin/cmd_mouse_control/plugin.py +487 -22
- pygpt_net/plugin/cmd_mouse_control/worker.py +464 -87
- pygpt_net/plugin/cmd_mouse_control/worker_sandbox.py +729 -0
- pygpt_net/plugin/idx_llama_index/config.py +2 -2
- pygpt_net/provider/api/anthropic/__init__.py +10 -8
- pygpt_net/provider/api/google/__init__.py +21 -58
- pygpt_net/provider/api/google/chat.py +545 -129
- pygpt_net/provider/api/google/computer.py +190 -0
- pygpt_net/provider/api/google/realtime/realtime.py +2 -2
- pygpt_net/provider/api/google/remote_tools.py +93 -0
- pygpt_net/provider/api/google/store.py +546 -0
- pygpt_net/provider/api/google/worker/__init__.py +0 -0
- pygpt_net/provider/api/google/worker/importer.py +392 -0
- pygpt_net/provider/api/openai/__init__.py +7 -3
- pygpt_net/provider/api/openai/computer.py +10 -1
- pygpt_net/provider/api/openai/responses.py +0 -0
- pygpt_net/provider/api/openai/store.py +6 -6
- pygpt_net/provider/api/openai/worker/importer.py +24 -24
- pygpt_net/provider/api/x_ai/__init__.py +10 -9
- pygpt_net/provider/api/x_ai/chat.py +272 -102
- pygpt_net/provider/core/config/patch.py +16 -1
- pygpt_net/provider/core/config/patches/patch_before_2_6_42.py +3 -3
- pygpt_net/provider/core/model/patch.py +17 -3
- pygpt_net/provider/core/preset/json_file.py +13 -7
- pygpt_net/provider/core/{assistant_file → remote_file}/__init__.py +1 -1
- pygpt_net/provider/core/{assistant_file → remote_file}/base.py +9 -9
- pygpt_net/provider/core/remote_file/db_sqlite/__init__.py +12 -0
- pygpt_net/provider/core/{assistant_file → remote_file}/db_sqlite/patch.py +1 -1
- pygpt_net/provider/core/{assistant_file → remote_file}/db_sqlite/provider.py +23 -20
- pygpt_net/provider/core/{assistant_file → remote_file}/db_sqlite/storage.py +35 -27
- pygpt_net/provider/core/{assistant_file → remote_file}/db_sqlite/utils.py +5 -4
- pygpt_net/provider/core/{assistant_store → remote_store}/__init__.py +1 -1
- pygpt_net/provider/core/{assistant_store → remote_store}/base.py +10 -10
- pygpt_net/provider/core/{assistant_store → remote_store}/db_sqlite/__init__.py +1 -1
- pygpt_net/provider/core/{assistant_store → remote_store}/db_sqlite/patch.py +1 -1
- pygpt_net/provider/core/{assistant_store → remote_store}/db_sqlite/provider.py +16 -15
- pygpt_net/provider/core/{assistant_store → remote_store}/db_sqlite/storage.py +30 -23
- pygpt_net/provider/core/{assistant_store → remote_store}/db_sqlite/utils.py +5 -4
- pygpt_net/provider/core/{assistant_store → remote_store}/json_file.py +9 -9
- pygpt_net/provider/llms/google.py +2 -2
- pygpt_net/tools/image_viewer/ui/dialogs.py +298 -12
- pygpt_net/tools/text_editor/ui/widgets.py +5 -1
- pygpt_net/ui/base/config_dialog.py +3 -2
- pygpt_net/ui/base/context_menu.py +44 -1
- pygpt_net/ui/dialog/assistant.py +3 -3
- pygpt_net/ui/dialog/plugins.py +3 -1
- pygpt_net/ui/dialog/remote_store_google.py +539 -0
- pygpt_net/ui/dialog/{assistant_store.py → remote_store_openai.py} +95 -95
- pygpt_net/ui/dialogs.py +5 -3
- pygpt_net/ui/layout/chat/attachments_uploaded.py +3 -3
- pygpt_net/ui/layout/toolbox/computer_env.py +26 -8
- pygpt_net/ui/layout/toolbox/indexes.py +22 -19
- pygpt_net/ui/layout/toolbox/model.py +28 -5
- pygpt_net/ui/menu/tools.py +13 -5
- pygpt_net/ui/widget/dialog/remote_store_google.py +56 -0
- pygpt_net/ui/widget/dialog/{assistant_store.py → remote_store_openai.py} +9 -9
- pygpt_net/ui/widget/element/button.py +4 -4
- pygpt_net/ui/widget/image/display.py +25 -8
- pygpt_net/ui/widget/lists/remote_store_google.py +248 -0
- pygpt_net/ui/widget/lists/{assistant_store.py → remote_store_openai.py} +21 -21
- pygpt_net/ui/widget/option/checkbox_list.py +47 -9
- pygpt_net/ui/widget/option/combo.py +39 -3
- pygpt_net/ui/widget/tabs/output.py +9 -1
- pygpt_net/ui/widget/textarea/editor.py +14 -1
- pygpt_net/ui/widget/textarea/input.py +20 -7
- pygpt_net/ui/widget/textarea/notepad.py +24 -1
- pygpt_net/ui/widget/textarea/output.py +23 -1
- pygpt_net/ui/widget/textarea/web.py +16 -1
- {pygpt_net-2.7.4.dist-info → pygpt_net-2.7.6.dist-info}/METADATA +41 -2
- {pygpt_net-2.7.4.dist-info → pygpt_net-2.7.6.dist-info}/RECORD +158 -132
- {pygpt_net-2.7.4.dist-info → pygpt_net-2.7.6.dist-info}/LICENSE +0 -0
- {pygpt_net-2.7.4.dist-info → pygpt_net-2.7.6.dist-info}/WHEEL +0 -0
- {pygpt_net-2.7.4.dist-info → pygpt_net-2.7.6.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,729 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
# ================================================== #
|
|
4
|
+
# This file is a part of PYGPT package #
|
|
5
|
+
# Website: https://pygpt.net #
|
|
6
|
+
# GitHub: https://github.com/szczyglis-dev/py-gpt #
|
|
7
|
+
# MIT License #
|
|
8
|
+
# Created By : Marcin Szczygliński #
|
|
9
|
+
# Updated Date: 2026.01.02 02:00:00 #
|
|
10
|
+
# ================================================== #
|
|
11
|
+
|
|
12
|
+
import time
|
|
13
|
+
import threading
|
|
14
|
+
from typing import Optional, List
|
|
15
|
+
|
|
16
|
+
from PySide6.QtCore import Slot, Signal
|
|
17
|
+
from pygpt_net.plugin.base.worker import BaseWorker, BaseSignals
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class WorkerSandboxSignals(BaseSignals):
|
|
21
|
+
screenshot = Signal(dict, object)
|
|
22
|
+
start = Signal()
|
|
23
|
+
call = Signal(str, dict, object, object) # op: str, params: dict, ret: dict (container), done: threading.Event
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class Worker(BaseWorker):
|
|
27
|
+
"""
|
|
28
|
+
Sandbox worker: executes computer-use actions via Plugin in main thread.
|
|
29
|
+
It mirrors the public API of host worker. Each result includes "url".
|
|
30
|
+
All Playwright operations are delegated to plugin through signals to avoid
|
|
31
|
+
cross-thread greenlet violations.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
WAIT_TIMEOUT = 60.0 # seconds to wait for a main-thread operation
|
|
35
|
+
|
|
36
|
+
def __init__(self, *args, **kwargs):
|
|
37
|
+
super(Worker, self).__init__()
|
|
38
|
+
self.signals = WorkerSandboxSignals()
|
|
39
|
+
self.window = None
|
|
40
|
+
self.args = args
|
|
41
|
+
self.kwargs = kwargs
|
|
42
|
+
self.plugin = None
|
|
43
|
+
self.cmds = None
|
|
44
|
+
self.ctx = None
|
|
45
|
+
self.msg = None
|
|
46
|
+
|
|
47
|
+
# Viewport and pointer tracking (synced from plugin on ensure)
|
|
48
|
+
self.viewport_w = 1440
|
|
49
|
+
self.viewport_h = 900
|
|
50
|
+
self._mouse_x = 0
|
|
51
|
+
self._mouse_y = 0
|
|
52
|
+
self._last_url = ""
|
|
53
|
+
|
|
54
|
+
# Defaults
|
|
55
|
+
self._default_scroll_px = 800
|
|
56
|
+
|
|
57
|
+
# ========================= Lifecycle ========================= #
|
|
58
|
+
|
|
59
|
+
@Slot()
|
|
60
|
+
def run(self):
|
|
61
|
+
try:
|
|
62
|
+
responses = []
|
|
63
|
+
for item in self.cmds:
|
|
64
|
+
if self.is_stopped():
|
|
65
|
+
break
|
|
66
|
+
|
|
67
|
+
response = None
|
|
68
|
+
try:
|
|
69
|
+
cmd = item.get("cmd")
|
|
70
|
+
if not cmd:
|
|
71
|
+
continue
|
|
72
|
+
|
|
73
|
+
# alias before gating
|
|
74
|
+
if cmd == "screenshot":
|
|
75
|
+
item = dict(item)
|
|
76
|
+
item["cmd"] = "get_screenshot"
|
|
77
|
+
cmd = "get_screenshot"
|
|
78
|
+
|
|
79
|
+
# allow only plugin-declared commands
|
|
80
|
+
allowed = getattr(self.plugin, "allowed_cmds", None)
|
|
81
|
+
if isinstance(allowed, (list, set, tuple)) and cmd not in allowed:
|
|
82
|
+
continue
|
|
83
|
+
|
|
84
|
+
response = self._dispatch(item)
|
|
85
|
+
if response:
|
|
86
|
+
responses.append(response)
|
|
87
|
+
|
|
88
|
+
except Exception as e:
|
|
89
|
+
responses.append(self.make_response(item, self.throw_error(e)))
|
|
90
|
+
|
|
91
|
+
if responses:
|
|
92
|
+
self.reply_more(responses)
|
|
93
|
+
|
|
94
|
+
except Exception as e:
|
|
95
|
+
self.error(e)
|
|
96
|
+
finally:
|
|
97
|
+
self.cleanup()
|
|
98
|
+
|
|
99
|
+
def on_destroy(self):
|
|
100
|
+
"""Handle destroyed event."""
|
|
101
|
+
self.cleanup()
|
|
102
|
+
|
|
103
|
+
def cleanup(self):
|
|
104
|
+
"""Playwright is owned by the plugin; worker does not close it."""
|
|
105
|
+
return
|
|
106
|
+
|
|
107
|
+
# ========================= Dispatch ========================= #
|
|
108
|
+
|
|
109
|
+
def _dispatch(self, item: dict) -> dict:
|
|
110
|
+
cmd = item["cmd"]
|
|
111
|
+
|
|
112
|
+
handler = None
|
|
113
|
+
|
|
114
|
+
# Legacy-compatible API
|
|
115
|
+
if cmd == "open_web_browser":
|
|
116
|
+
handler = self.cmd_open_web_browser
|
|
117
|
+
elif cmd == "get_mouse_position":
|
|
118
|
+
handler = self.cmd_mouse_get_pos
|
|
119
|
+
elif cmd == "mouse_move":
|
|
120
|
+
handler = self.cmd_mouse_move
|
|
121
|
+
elif cmd == "mouse_drag":
|
|
122
|
+
handler = self.cmd_mouse_drag
|
|
123
|
+
elif cmd == "mouse_click":
|
|
124
|
+
handler = self.cmd_mouse_click
|
|
125
|
+
elif cmd == "mouse_scroll":
|
|
126
|
+
handler = self.cmd_mouse_scroll
|
|
127
|
+
elif cmd == "get_screenshot":
|
|
128
|
+
handler = self.cmd_make_screenshot
|
|
129
|
+
elif cmd == "keyboard_key":
|
|
130
|
+
handler = self.cmd_keyboard_key
|
|
131
|
+
elif cmd == "keyboard_keys":
|
|
132
|
+
handler = self.cmd_keyboard_keys
|
|
133
|
+
elif cmd == "keyboard_type":
|
|
134
|
+
handler = self.cmd_keyboard_type
|
|
135
|
+
elif cmd == "wait":
|
|
136
|
+
handler = self.cmd_wait
|
|
137
|
+
|
|
138
|
+
# Google/OpenAI Computer Use
|
|
139
|
+
elif cmd == "wait_5_seconds":
|
|
140
|
+
handler = self.cmd_wait_5_seconds
|
|
141
|
+
elif cmd == "go_back":
|
|
142
|
+
handler = self.cmd_go_back
|
|
143
|
+
elif cmd == "go_forward":
|
|
144
|
+
handler = self.cmd_go_forward
|
|
145
|
+
elif cmd == "search":
|
|
146
|
+
handler = self.cmd_search
|
|
147
|
+
elif cmd == "navigate":
|
|
148
|
+
handler = self.cmd_navigate
|
|
149
|
+
elif cmd == "click_at":
|
|
150
|
+
handler = self.cmd_click_at
|
|
151
|
+
elif cmd == "hover_at":
|
|
152
|
+
handler = self.cmd_hover_at
|
|
153
|
+
elif cmd == "type_text_at":
|
|
154
|
+
handler = self.cmd_type_text_at
|
|
155
|
+
elif cmd == "key_combination":
|
|
156
|
+
handler = self.cmd_key_combination
|
|
157
|
+
elif cmd == "scroll_document":
|
|
158
|
+
handler = self.cmd_scroll_document
|
|
159
|
+
elif cmd == "scroll_at":
|
|
160
|
+
handler = self.cmd_scroll_at
|
|
161
|
+
elif cmd == "drag_and_drop":
|
|
162
|
+
handler = self.cmd_drag_and_drop
|
|
163
|
+
|
|
164
|
+
# Action-style
|
|
165
|
+
elif cmd == "click":
|
|
166
|
+
handler = self.cmd_click
|
|
167
|
+
elif cmd == "double_click":
|
|
168
|
+
handler = self.cmd_double_click
|
|
169
|
+
elif cmd == "move":
|
|
170
|
+
handler = self.cmd_move
|
|
171
|
+
elif cmd == "type":
|
|
172
|
+
handler = self.cmd_type_text
|
|
173
|
+
elif cmd == "keypress":
|
|
174
|
+
handler = self.cmd_keypress
|
|
175
|
+
elif cmd == "scroll":
|
|
176
|
+
handler = self.cmd_scroll
|
|
177
|
+
elif cmd == "drag":
|
|
178
|
+
handler = self.cmd_drag
|
|
179
|
+
|
|
180
|
+
if not handler:
|
|
181
|
+
return None
|
|
182
|
+
|
|
183
|
+
return handler(item)
|
|
184
|
+
|
|
185
|
+
# ========================= Helpers ========================= #
|
|
186
|
+
|
|
187
|
+
def _call(self, op: str, params: dict) -> dict:
|
|
188
|
+
"""
|
|
189
|
+
Call plugin to perform a Playwright operation in the main thread and wait for result.
|
|
190
|
+
"""
|
|
191
|
+
ret = {}
|
|
192
|
+
done = threading.Event()
|
|
193
|
+
self.signals.call.emit(op, dict(params or {}), ret, done)
|
|
194
|
+
if not done.wait(self.WAIT_TIMEOUT):
|
|
195
|
+
raise TimeoutError(f"Playwright op timeout: {op}")
|
|
196
|
+
# update cached state if provided
|
|
197
|
+
self._update_state_from_ret(ret)
|
|
198
|
+
return ret
|
|
199
|
+
|
|
200
|
+
def _update_state_from_ret(self, ret: dict):
|
|
201
|
+
url = ret.get("url")
|
|
202
|
+
if isinstance(url, str):
|
|
203
|
+
self._last_url = url
|
|
204
|
+
vw = ret.get("viewport_w")
|
|
205
|
+
vh = ret.get("viewport_h")
|
|
206
|
+
if isinstance(vw, int) and isinstance(vh, int) and vw > 0 and vh > 0:
|
|
207
|
+
self.viewport_w, self.viewport_h = vw, vh
|
|
208
|
+
mx = ret.get("mouse_x")
|
|
209
|
+
my = ret.get("mouse_y")
|
|
210
|
+
if isinstance(mx, int) and isinstance(my, int):
|
|
211
|
+
self._mouse_x, self._mouse_y = mx, my
|
|
212
|
+
|
|
213
|
+
def _ensure_browser(self):
|
|
214
|
+
"""Ensure Playwright is started in the main thread and sync viewport."""
|
|
215
|
+
self.signals.start.emit()
|
|
216
|
+
ret = self._call("ensure", {})
|
|
217
|
+
if "viewport_w" in ret and "viewport_h" in ret:
|
|
218
|
+
self.viewport_w = int(ret["viewport_w"] or self.viewport_w)
|
|
219
|
+
self.viewport_h = int(ret["viewport_h"] or self.viewport_h)
|
|
220
|
+
|
|
221
|
+
def _current_url(self) -> str:
|
|
222
|
+
return self.plugin.get_last_url()
|
|
223
|
+
return self._last_url or ""
|
|
224
|
+
|
|
225
|
+
def _result_with_url(self, base: dict) -> dict:
|
|
226
|
+
base = dict(base or {})
|
|
227
|
+
base["url"] = self._current_url()
|
|
228
|
+
return base
|
|
229
|
+
|
|
230
|
+
def _get_screen_and_pointer(self, item: dict = None) -> dict:
|
|
231
|
+
current_step = self.get_param(item, "current_step", "")
|
|
232
|
+
screen_w, screen_h = self.viewport_w, self.viewport_h
|
|
233
|
+
return {
|
|
234
|
+
"current_step": current_step,
|
|
235
|
+
"screen_w": screen_w,
|
|
236
|
+
"screen_h": screen_h,
|
|
237
|
+
"mouse_x": self._mouse_x,
|
|
238
|
+
"mouse_y": self._mouse_y,
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
def _get_current(self, item: dict = None) -> dict:
|
|
242
|
+
base = self._get_screen_and_pointer(item)
|
|
243
|
+
return self._result_with_url(base)
|
|
244
|
+
|
|
245
|
+
def _denorm_x(self, x_norm: int) -> int:
|
|
246
|
+
x_norm = max(0, min(999, int(x_norm)))
|
|
247
|
+
return int(round(x_norm / 1000.0 * self.viewport_w))
|
|
248
|
+
|
|
249
|
+
def _denorm_y(self, y_norm: int) -> int:
|
|
250
|
+
y_norm = max(0, min(999, int(y_norm)))
|
|
251
|
+
return int(round(y_norm / 1000.0 * self.viewport_h))
|
|
252
|
+
|
|
253
|
+
def _button_from_name(self, name: Optional[str]) -> str:
|
|
254
|
+
if not name:
|
|
255
|
+
return "left"
|
|
256
|
+
name = name.lower()
|
|
257
|
+
if name in ("left", "right", "middle"):
|
|
258
|
+
return name
|
|
259
|
+
return "left"
|
|
260
|
+
|
|
261
|
+
def _permit(self, option_name: str) -> bool:
|
|
262
|
+
try:
|
|
263
|
+
if hasattr(self.plugin, "get_option_value"):
|
|
264
|
+
val = self.plugin.get_option_value(option_name)
|
|
265
|
+
if val is None:
|
|
266
|
+
return True
|
|
267
|
+
return bool(val)
|
|
268
|
+
except Exception:
|
|
269
|
+
pass
|
|
270
|
+
return True
|
|
271
|
+
|
|
272
|
+
# ========================= Legacy-compatible commands ========================= #
|
|
273
|
+
|
|
274
|
+
def cmd_open_web_browser(self, item: dict) -> dict:
|
|
275
|
+
try:
|
|
276
|
+
self.msg = "Open web browser (sandbox via plugin)"
|
|
277
|
+
self.log(self.msg)
|
|
278
|
+
self._ensure_browser()
|
|
279
|
+
url = ""
|
|
280
|
+
if self.has_param(item, "url"):
|
|
281
|
+
url = self.get_param(item, "url") or ""
|
|
282
|
+
if url:
|
|
283
|
+
self._call("navigate", {"url": url})
|
|
284
|
+
result = self._get_current(item)
|
|
285
|
+
self.log("Response: {}".format(result))
|
|
286
|
+
except Exception as e:
|
|
287
|
+
result = self.throw_error(e)
|
|
288
|
+
|
|
289
|
+
if self.has_param(item, "no_screenshot"):
|
|
290
|
+
result["no_screenshot"] = True
|
|
291
|
+
return self.make_response(item, result)
|
|
292
|
+
|
|
293
|
+
def cmd_wait(self, item: dict) -> dict:
|
|
294
|
+
wait_time = 5
|
|
295
|
+
try:
|
|
296
|
+
if self.has_param(item, "seconds"):
|
|
297
|
+
wait_time = int(self.get_param(item, "seconds"))
|
|
298
|
+
self.msg = "Wait (sandbox)"
|
|
299
|
+
self.log(self.msg)
|
|
300
|
+
result = self._get_current(item)
|
|
301
|
+
self.log("Response: {}".format(result))
|
|
302
|
+
except Exception as e:
|
|
303
|
+
result = self.throw_error(e)
|
|
304
|
+
if self.has_param(item, "no_screenshot"):
|
|
305
|
+
result["no_screenshot"] = True
|
|
306
|
+
time.sleep(max(0, wait_time))
|
|
307
|
+
return self.make_response(item, result)
|
|
308
|
+
|
|
309
|
+
def cmd_mouse_get_pos(self, item: dict) -> dict:
|
|
310
|
+
try:
|
|
311
|
+
self.msg = "Mouse get position (sandbox)"
|
|
312
|
+
self.log(self.msg)
|
|
313
|
+
result = self._get_current(item)
|
|
314
|
+
self.log("Response: {}".format(result))
|
|
315
|
+
except Exception as e:
|
|
316
|
+
result = self.throw_error(e)
|
|
317
|
+
if self.has_param(item, "no_screenshot"):
|
|
318
|
+
result["no_screenshot"] = True
|
|
319
|
+
return self.make_response(item, result)
|
|
320
|
+
|
|
321
|
+
def cmd_mouse_move(self, item: dict) -> dict:
|
|
322
|
+
error = None
|
|
323
|
+
try:
|
|
324
|
+
if not self._permit("allow_mouse_move"):
|
|
325
|
+
raise RuntimeError("Mouse move not permitted by settings.")
|
|
326
|
+
self._ensure_browser()
|
|
327
|
+
x = int(self.get_param(item, "x", self.get_param(item, "mouse_x", 0)))
|
|
328
|
+
y = int(self.get_param(item, "y", self.get_param(item, "mouse_y", 0)))
|
|
329
|
+
click = self.get_param(item, "click", None)
|
|
330
|
+
num = int(self.get_param(item, "num_clicks", 1))
|
|
331
|
+
if click:
|
|
332
|
+
# Single combined call: click at explicit coordinates
|
|
333
|
+
self._call("click", {"x": x, "y": y, "button": self._button_from_name(click), "count": max(1, num)})
|
|
334
|
+
else:
|
|
335
|
+
self._call("move", {"x": x, "y": y})
|
|
336
|
+
result = self._get_current(item)
|
|
337
|
+
except Exception as e:
|
|
338
|
+
error = str(e)
|
|
339
|
+
result = self._get_current(item)
|
|
340
|
+
result["error"] = error
|
|
341
|
+
self.log("Error: {}".format(e))
|
|
342
|
+
|
|
343
|
+
if self.has_param(item, "no_screenshot"):
|
|
344
|
+
result["no_screenshot"] = True
|
|
345
|
+
return self.make_response(item, result)
|
|
346
|
+
|
|
347
|
+
def cmd_mouse_click(self, item: dict) -> dict:
|
|
348
|
+
try:
|
|
349
|
+
if not self._permit("allow_mouse_click"):
|
|
350
|
+
raise RuntimeError("Mouse click not permitted by settings.")
|
|
351
|
+
self._ensure_browser()
|
|
352
|
+
button = self._button_from_name(self.get_param(item, "button", "left"))
|
|
353
|
+
num = int(self.get_param(item, "num_clicks", 1))
|
|
354
|
+
x = self.get_param(item, "x", None)
|
|
355
|
+
y = self.get_param(item, "y", None)
|
|
356
|
+
payload = {"button": button, "count": max(1, num)}
|
|
357
|
+
if x is not None and y is not None:
|
|
358
|
+
payload["x"] = int(x)
|
|
359
|
+
payload["y"] = int(y)
|
|
360
|
+
self._call("click", payload)
|
|
361
|
+
result = self._get_current(item)
|
|
362
|
+
except Exception as e:
|
|
363
|
+
result = self.throw_error(e)
|
|
364
|
+
if self.has_param(item, "no_screenshot"):
|
|
365
|
+
result["no_screenshot"] = True
|
|
366
|
+
return self.make_response(item, result)
|
|
367
|
+
|
|
368
|
+
def cmd_mouse_scroll(self, item: dict) -> dict:
|
|
369
|
+
try:
|
|
370
|
+
if not self._permit("allow_mouse_scroll"):
|
|
371
|
+
raise RuntimeError("Mouse scroll not permitted by settings.")
|
|
372
|
+
self._ensure_browser()
|
|
373
|
+
x = self.get_param(item, "x", self.get_param(item, "mouse_x", None))
|
|
374
|
+
y = self.get_param(item, "y", self.get_param(item, "mouse_y", None))
|
|
375
|
+
dx = int(self.get_param(item, "dx", 0))
|
|
376
|
+
dy = int(self.get_param(item, "dy", 0))
|
|
377
|
+
unit = self.get_param(item, "unit", "px")
|
|
378
|
+
if unit == "step":
|
|
379
|
+
dx = int(dx) * 30
|
|
380
|
+
dy = int(dy) * 30
|
|
381
|
+
payload = {"dx": dx, "dy": dy}
|
|
382
|
+
if x is not None and y is not None:
|
|
383
|
+
payload["x"] = int(x)
|
|
384
|
+
payload["y"] = int(y)
|
|
385
|
+
self._call("scroll", payload)
|
|
386
|
+
result = self._get_current(item)
|
|
387
|
+
except Exception as e:
|
|
388
|
+
result = self.throw_error(e)
|
|
389
|
+
if self.has_param(item, "no_screenshot"):
|
|
390
|
+
result["no_screenshot"] = True
|
|
391
|
+
return self.make_response(item, result)
|
|
392
|
+
|
|
393
|
+
def cmd_mouse_drag(self, item: dict) -> dict:
|
|
394
|
+
try:
|
|
395
|
+
self._ensure_browser()
|
|
396
|
+
x = int(self.get_param(item, "x"))
|
|
397
|
+
y = int(self.get_param(item, "y"))
|
|
398
|
+
dx = int(self.get_param(item, "dx"))
|
|
399
|
+
dy = int(self.get_param(item, "dy"))
|
|
400
|
+
self._call("drag", {"x": x, "y": y, "dx": dx, "dy": dy})
|
|
401
|
+
result = self._get_current(item)
|
|
402
|
+
except Exception as e:
|
|
403
|
+
result = self.throw_error(e)
|
|
404
|
+
if self.has_param(item, "no_screenshot"):
|
|
405
|
+
result["no_screenshot"] = True
|
|
406
|
+
return self.make_response(item, result)
|
|
407
|
+
|
|
408
|
+
def cmd_keyboard_keys(self, item: dict) -> dict:
|
|
409
|
+
error = None
|
|
410
|
+
try:
|
|
411
|
+
if not self._permit("allow_keyboard"):
|
|
412
|
+
raise RuntimeError("Keyboard not permitted by settings.")
|
|
413
|
+
self._ensure_browser()
|
|
414
|
+
keys = self.get_param(item, "keys", []) or []
|
|
415
|
+
self._call("keypress_combo", {"keys": keys})
|
|
416
|
+
result = self._get_current(item)
|
|
417
|
+
except Exception as e:
|
|
418
|
+
error = str(e)
|
|
419
|
+
result = self._get_current(item)
|
|
420
|
+
result["error"] = error
|
|
421
|
+
self.log("Error: {}".format(e))
|
|
422
|
+
if self.has_param(item, "no_screenshot"):
|
|
423
|
+
result["no_screenshot"] = True
|
|
424
|
+
return self.make_response(item, result)
|
|
425
|
+
|
|
426
|
+
def cmd_keyboard_key(self, item: dict) -> dict:
|
|
427
|
+
error = None
|
|
428
|
+
try:
|
|
429
|
+
if not self._permit("allow_keyboard"):
|
|
430
|
+
raise RuntimeError("Keyboard not permitted by settings.")
|
|
431
|
+
self._ensure_browser()
|
|
432
|
+
key = self.get_param(item, "key")
|
|
433
|
+
modifier = self.get_param(item, "modifier", None)
|
|
434
|
+
if modifier:
|
|
435
|
+
self._call("keypress_combo", {"keys": [modifier, key]})
|
|
436
|
+
else:
|
|
437
|
+
self._call("keypress", {"key": key})
|
|
438
|
+
result = self._get_current(item)
|
|
439
|
+
except Exception as e:
|
|
440
|
+
error = str(e)
|
|
441
|
+
result = self._get_current(item)
|
|
442
|
+
result["error"] = error
|
|
443
|
+
self.log("Error: {}".format(e))
|
|
444
|
+
if self.has_param(item, "no_screenshot"):
|
|
445
|
+
result["no_screenshot"] = True
|
|
446
|
+
return self.make_response(item, result)
|
|
447
|
+
|
|
448
|
+
def cmd_keyboard_type(self, item: dict) -> dict:
|
|
449
|
+
try:
|
|
450
|
+
if not self._permit("allow_keyboard"):
|
|
451
|
+
raise RuntimeError("Keyboard not permitted by settings.")
|
|
452
|
+
self._ensure_browser()
|
|
453
|
+
text = self.get_param(item, "text", "") or ""
|
|
454
|
+
modifier = self.get_param(item, "modifier", None)
|
|
455
|
+
payload = {"text": text}
|
|
456
|
+
if modifier:
|
|
457
|
+
payload["modifier"] = modifier
|
|
458
|
+
self._call("type", payload)
|
|
459
|
+
result = self._get_current(item)
|
|
460
|
+
except Exception as e:
|
|
461
|
+
result = self.throw_error(e)
|
|
462
|
+
if self.has_param(item, "no_screenshot"):
|
|
463
|
+
result["no_screenshot"] = True
|
|
464
|
+
return self.make_response(item, result)
|
|
465
|
+
|
|
466
|
+
def cmd_make_screenshot(self, item: dict) -> dict:
|
|
467
|
+
try:
|
|
468
|
+
self._ensure_browser()
|
|
469
|
+
ret = self._call("screenshot", {"full_page": False})
|
|
470
|
+
img_bytes = ret.get("image", b"")
|
|
471
|
+
meta = self._get_current(item)
|
|
472
|
+
if img_bytes:
|
|
473
|
+
self.signals.screenshot.emit(meta, img_bytes)
|
|
474
|
+
result = meta
|
|
475
|
+
self.log("Response: {}".format(result))
|
|
476
|
+
except Exception as e:
|
|
477
|
+
result = self.throw_error(e)
|
|
478
|
+
if self.has_param(item, "no_screenshot"):
|
|
479
|
+
result["no_screenshot"] = True
|
|
480
|
+
return self.make_response(item, result)
|
|
481
|
+
|
|
482
|
+
# ========================= Google/OpenAI Computer Use commands ========================= #
|
|
483
|
+
|
|
484
|
+
def cmd_wait_5_seconds(self, item: dict) -> dict:
|
|
485
|
+
return self.cmd_wait({"cmd": "wait", "params": {"seconds": 5}})
|
|
486
|
+
|
|
487
|
+
def cmd_go_back(self, item: dict) -> dict:
|
|
488
|
+
try:
|
|
489
|
+
self._ensure_browser()
|
|
490
|
+
self._call("go_back", {})
|
|
491
|
+
result = self._get_current(item)
|
|
492
|
+
except Exception as e:
|
|
493
|
+
result = self.throw_error(e)
|
|
494
|
+
return self.make_response(item, result)
|
|
495
|
+
|
|
496
|
+
def cmd_go_forward(self, item: dict) -> dict:
|
|
497
|
+
try:
|
|
498
|
+
self._ensure_browser()
|
|
499
|
+
self._call("go_forward", {})
|
|
500
|
+
result = self._get_current(item)
|
|
501
|
+
except Exception as e:
|
|
502
|
+
result = self.throw_error(e)
|
|
503
|
+
return self.make_response(item, result)
|
|
504
|
+
|
|
505
|
+
def cmd_search(self, item: dict) -> dict:
|
|
506
|
+
try:
|
|
507
|
+
self._ensure_browser()
|
|
508
|
+
self._call("navigate", {"url": "https://www.google.com"})
|
|
509
|
+
result = self._get_current(item)
|
|
510
|
+
except Exception as e:
|
|
511
|
+
result = self.throw_error(e)
|
|
512
|
+
return self.make_response(item, result)
|
|
513
|
+
|
|
514
|
+
def cmd_navigate(self, item: dict) -> dict:
|
|
515
|
+
try:
|
|
516
|
+
self._ensure_browser()
|
|
517
|
+
url = self.get_param(item, "url", "") or ""
|
|
518
|
+
if url:
|
|
519
|
+
self._call("navigate", {"url": url})
|
|
520
|
+
result = self._get_current(item)
|
|
521
|
+
except Exception as e:
|
|
522
|
+
result = self.throw_error(e)
|
|
523
|
+
return self.make_response(item, result)
|
|
524
|
+
|
|
525
|
+
def cmd_click_at(self, item: dict) -> dict:
|
|
526
|
+
try:
|
|
527
|
+
self._ensure_browser()
|
|
528
|
+
x = self._denorm_x(int(self.get_param(item, "x")))
|
|
529
|
+
y = self._denorm_y(int(self.get_param(item, "y")))
|
|
530
|
+
# Single combined call
|
|
531
|
+
self._call("click_at", {"x": x, "y": y, "button": "left", "count": 1})
|
|
532
|
+
result = self._get_current(item)
|
|
533
|
+
except Exception as e:
|
|
534
|
+
result = self.throw_error(e)
|
|
535
|
+
return self.make_response(item, result)
|
|
536
|
+
|
|
537
|
+
def cmd_hover_at(self, item: dict) -> dict:
|
|
538
|
+
try:
|
|
539
|
+
self._ensure_browser()
|
|
540
|
+
x = self._denorm_x(int(self.get_param(item, "x")))
|
|
541
|
+
y = self._denorm_y(int(self.get_param(item, "y")))
|
|
542
|
+
# Single combined call for hover
|
|
543
|
+
self._call("hover_at", {"x": x, "y": y})
|
|
544
|
+
result = self._get_current(item)
|
|
545
|
+
except Exception as e:
|
|
546
|
+
result = self.throw_error(e)
|
|
547
|
+
return self.make_response(item, result)
|
|
548
|
+
|
|
549
|
+
def cmd_type_text_at(self, item: dict) -> dict:
|
|
550
|
+
try:
|
|
551
|
+
self._ensure_browser()
|
|
552
|
+
x = self._denorm_x(int(self.get_param(item, "x")))
|
|
553
|
+
y = self._denorm_y(int(self.get_param(item, "y")))
|
|
554
|
+
text = self.get_param(item, "text", "") or ""
|
|
555
|
+
press_enter = bool(self.get_param(item, "press_enter", True))
|
|
556
|
+
clear_before = bool(self.get_param(item, "clear_before_typing", True))
|
|
557
|
+
# Single combined call for full flow
|
|
558
|
+
self._call("type_text_at", {
|
|
559
|
+
"x": x,
|
|
560
|
+
"y": y,
|
|
561
|
+
"text": text,
|
|
562
|
+
"press_enter": press_enter,
|
|
563
|
+
"clear_before_typing": clear_before,
|
|
564
|
+
})
|
|
565
|
+
result = self._get_current(item)
|
|
566
|
+
except Exception as e:
|
|
567
|
+
result = self.throw_error(e)
|
|
568
|
+
return self.make_response(item, result)
|
|
569
|
+
|
|
570
|
+
def cmd_key_combination(self, item: dict) -> dict:
|
|
571
|
+
try:
|
|
572
|
+
self._ensure_browser()
|
|
573
|
+
keys = self.get_param(item, "keys", None)
|
|
574
|
+
if isinstance(keys, str):
|
|
575
|
+
parts = [p.strip() for p in keys.replace("+", " ").split() if p.strip()]
|
|
576
|
+
else:
|
|
577
|
+
parts = list(keys or [])
|
|
578
|
+
self._call("keypress_combo", {"keys": parts})
|
|
579
|
+
result = self._get_current(item)
|
|
580
|
+
except Exception as e:
|
|
581
|
+
result = self.throw_error(e)
|
|
582
|
+
return self.make_response(item, result)
|
|
583
|
+
|
|
584
|
+
def cmd_scroll_document(self, item: dict) -> dict:
|
|
585
|
+
try:
|
|
586
|
+
self._ensure_browser()
|
|
587
|
+
direction = str(self.get_param(item, "direction", "down")).lower()
|
|
588
|
+
magnitude = int(self.get_param(item, "magnitude", self._default_scroll_px))
|
|
589
|
+
dx, dy = 0, 0
|
|
590
|
+
if direction == "down":
|
|
591
|
+
dy = magnitude
|
|
592
|
+
elif direction == "up":
|
|
593
|
+
dy = -magnitude
|
|
594
|
+
elif direction == "left":
|
|
595
|
+
dx = -magnitude
|
|
596
|
+
elif direction == "right":
|
|
597
|
+
dx = magnitude
|
|
598
|
+
self._call("scroll", {"dx": dx, "dy": dy})
|
|
599
|
+
result = self._get_current(item)
|
|
600
|
+
except Exception as e:
|
|
601
|
+
result = self.throw_error(e)
|
|
602
|
+
return self.make_response(item, result)
|
|
603
|
+
|
|
604
|
+
def cmd_scroll_at(self, item: dict) -> dict:
|
|
605
|
+
try:
|
|
606
|
+
self._ensure_browser()
|
|
607
|
+
direction = str(self.get_param(item, "direction", "down")).lower()
|
|
608
|
+
magnitude = int(self.get_param(item, "magnitude", self._default_scroll_px))
|
|
609
|
+
x = self._denorm_x(int(self.get_param(item, "x"))) if self.has_param(item, "x") else None
|
|
610
|
+
y = self._denorm_y(int(self.get_param(item, "y"))) if self.has_param(item, "y") else None
|
|
611
|
+
dx, dy = 0, 0
|
|
612
|
+
if direction == "down":
|
|
613
|
+
dy = magnitude
|
|
614
|
+
elif direction == "up":
|
|
615
|
+
dy = -magnitude
|
|
616
|
+
elif direction == "left":
|
|
617
|
+
dx = -magnitude
|
|
618
|
+
elif direction == "right":
|
|
619
|
+
dx = magnitude
|
|
620
|
+
payload = {"dx": dx, "dy": dy}
|
|
621
|
+
if x is not None and y is not None:
|
|
622
|
+
payload["x"] = x
|
|
623
|
+
payload["y"] = y
|
|
624
|
+
self._call("scroll", payload)
|
|
625
|
+
result = self._get_current(item)
|
|
626
|
+
except Exception as e:
|
|
627
|
+
result = self.throw_error(e)
|
|
628
|
+
return self.make_response(item, result)
|
|
629
|
+
|
|
630
|
+
def cmd_drag_and_drop(self, item: dict) -> dict:
|
|
631
|
+
try:
|
|
632
|
+
self._ensure_browser()
|
|
633
|
+
x0 = self._denorm_x(int(self.get_param(item, "x")))
|
|
634
|
+
y0 = self._denorm_y(int(self.get_param(item, "y")))
|
|
635
|
+
x1 = self._denorm_x(int(self.get_param(item, "destination_x")))
|
|
636
|
+
y1 = self._denorm_y(int(self.get_param(item, "destination_y")))
|
|
637
|
+
self._call("drag", {"x": x0, "y": y0, "dx": x1, "dy": y1})
|
|
638
|
+
result = self._get_current(item)
|
|
639
|
+
except Exception as e:
|
|
640
|
+
result = self.throw_error(e)
|
|
641
|
+
return self.make_response(item, result)
|
|
642
|
+
|
|
643
|
+
# ========================= Action-style convenience ========================= #
|
|
644
|
+
|
|
645
|
+
def cmd_click(self, item: dict) -> dict:
|
|
646
|
+
item2 = dict(item)
|
|
647
|
+
if self.has_param(item, "x") and self.has_param(item, "y"):
|
|
648
|
+
x = int(self.get_param(item, "x")); y = int(self.get_param(item, "y"))
|
|
649
|
+
if 0 <= x <= 999 and 0 <= y <= 999:
|
|
650
|
+
item2["params"] = dict(item.get("params", {}))
|
|
651
|
+
item2["params"]["x"] = self._denorm_x(x)
|
|
652
|
+
item2["params"]["y"] = self._denorm_y(y)
|
|
653
|
+
return self.cmd_mouse_click(item2)
|
|
654
|
+
|
|
655
|
+
def cmd_double_click(self, item: dict) -> dict:
|
|
656
|
+
item2 = dict(item)
|
|
657
|
+
p = dict(item.get("params", {}))
|
|
658
|
+
p["num_clicks"] = 2
|
|
659
|
+
item2["params"] = p
|
|
660
|
+
return self.cmd_click(item2)
|
|
661
|
+
|
|
662
|
+
def cmd_move(self, item: dict) -> dict:
|
|
663
|
+
item2 = dict(item)
|
|
664
|
+
p = dict(item.get("params", {}))
|
|
665
|
+
if "x" in p and "y" in p:
|
|
666
|
+
x = int(p["x"]); y = int(p["y"])
|
|
667
|
+
if 0 <= x <= 999 and 0 <= y <= 999:
|
|
668
|
+
p["x"] = self._denorm_x(x); p["y"] = self._denorm_y(y)
|
|
669
|
+
item2["params"] = p
|
|
670
|
+
item2["cmd"] = "mouse_move"
|
|
671
|
+
return self.cmd_mouse_move(item2)
|
|
672
|
+
|
|
673
|
+
def cmd_type_text(self, item: dict) -> dict:
|
|
674
|
+
item2 = dict(item)
|
|
675
|
+
item2["cmd"] = "keyboard_type"
|
|
676
|
+
return self.cmd_keyboard_type(item2)
|
|
677
|
+
|
|
678
|
+
def cmd_keypress(self, item: dict) -> dict:
|
|
679
|
+
try:
|
|
680
|
+
self._ensure_browser()
|
|
681
|
+
keys = self.get_param(item, "keys", []) or []
|
|
682
|
+
for k in keys:
|
|
683
|
+
self._call("keypress", {"key": k})
|
|
684
|
+
result = self._get_current(item)
|
|
685
|
+
except Exception as e:
|
|
686
|
+
result = self.throw_error(e)
|
|
687
|
+
return self.make_response(item, result)
|
|
688
|
+
|
|
689
|
+
def cmd_scroll(self, item: dict) -> dict:
|
|
690
|
+
item2 = dict(item)
|
|
691
|
+
p = dict(item.get("params", {}))
|
|
692
|
+
x = p.get("x", None); y = p.get("y", None)
|
|
693
|
+
if x is not None and y is not None:
|
|
694
|
+
if 0 <= int(x) <= 999 and 0 <= int(y) <= 999:
|
|
695
|
+
p["x"] = self._denorm_x(int(x)); p["y"] = self._denorm_y(int(y))
|
|
696
|
+
dx = int(p.get("scroll_x", p.get("dx", 0))); dy = int(p.get("scroll_y", p.get("dy", 0)))
|
|
697
|
+
p["dx"], p["dy"] = dx, dy
|
|
698
|
+
p["unit"] = "px"
|
|
699
|
+
item2["params"] = p
|
|
700
|
+
item2["cmd"] = "mouse_scroll"
|
|
701
|
+
return self.cmd_mouse_scroll(item2)
|
|
702
|
+
|
|
703
|
+
def cmd_drag(self, item: dict) -> dict:
|
|
704
|
+
try:
|
|
705
|
+
self._ensure_browser()
|
|
706
|
+
path = self.get_param(item, "path", [])
|
|
707
|
+
if not path or len(path) < 2:
|
|
708
|
+
x = self.get_param(item, "x", None)
|
|
709
|
+
y = self.get_param(item, "y", None)
|
|
710
|
+
dx = self.get_param(item, "dx", None)
|
|
711
|
+
dy = self.get_param(item, "dy", None)
|
|
712
|
+
if None in (x, y, dx, dy):
|
|
713
|
+
return self.make_response(item, self._get_current(item))
|
|
714
|
+
pts = [{"x": x, "y": y}, {"x": dx, "y": dy}]
|
|
715
|
+
else:
|
|
716
|
+
pts = path
|
|
717
|
+
|
|
718
|
+
def den(p):
|
|
719
|
+
xx = int(p["x"]); yy = int(p["y"])
|
|
720
|
+
if 0 <= xx <= 999 and 0 <= yy <= 999:
|
|
721
|
+
return self._denorm_x(xx), self._denorm_y(yy)
|
|
722
|
+
return xx, yy
|
|
723
|
+
|
|
724
|
+
x0, y0 = den(pts[0]); x1, y1 = den(pts[1])
|
|
725
|
+
self._call("drag", {"x": x0, "y": y0, "dx": x1, "dy": y1})
|
|
726
|
+
result = self._get_current(item)
|
|
727
|
+
except Exception as e:
|
|
728
|
+
result = self.throw_error(e)
|
|
729
|
+
return self.make_response(item, result)
|