fix: xdotool prioritaire sur clipboard (VM/Citrix), cosmétique sidebar
Some checks failed
security-audit / Bandit (scan statique) (push) Successful in 12s
security-audit / pip-audit (CVE dépendances) (push) Successful in 11s
security-audit / Scan secrets (grep) (push) Successful in 8s
tests / Lint (ruff + black) (push) Successful in 14s
tests / Tests unitaires (sans GPU) (push) Failing after 13s
tests / Tests sécurité (critique) (push) Has been skipped
Some checks failed
security-audit / Bandit (scan statique) (push) Successful in 12s
security-audit / pip-audit (CVE dépendances) (push) Successful in 11s
security-audit / Scan secrets (grep) (push) Successful in 8s
tests / Lint (ruff + black) (push) Successful in 14s
tests / Tests unitaires (sans GPU) (push) Failing after 13s
tests / Tests sécurité (critique) (push) Has been skipped
safe_type_text() : xdotool type en priorité au lieu du presse-papier. Le clipboard xclip ne traverse pas les VM (QEMU) ni Citrix/RDP. xdotool envoie des frappes X11 réelles que les VM capturent. Délai 20ms entre caractères pour fiabilité. Cosmétique : couleur texte forcée sur les items workflow du sidebar (color: var(--text-primary)) — était blanc sur blanc. Logs diagnostic ajoutés dans execute_workflow_thread et execute_action. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -24,23 +24,32 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def safe_type_text(text):
|
||||
"""Saisie de texte compatible avec tous les claviers (AZERTY/QWERTY).
|
||||
|
||||
pyautogui.write() envoie des codes de touches QWERTY qui produisent
|
||||
des caractères erronés sur AZERTY (* → µ, ( → 5, ) → °, etc.).
|
||||
"""Saisie de texte compatible VM/Citrix et claviers AZERTY/QWERTY.
|
||||
|
||||
Priorité :
|
||||
1. Presse-papier (xclip) + Ctrl+V → instantané, fiable pour tout texte
|
||||
2. xdotool type → respecte le layout clavier
|
||||
3. pyautogui.write() → dernier recours
|
||||
1. xdotool type → frappes clavier réelles, traverse QEMU/Citrix/RDP
|
||||
2. Presse-papier (xclip) + Ctrl+V → fallback local uniquement
|
||||
3. pyautogui.write() → dernier recours
|
||||
"""
|
||||
import shutil
|
||||
import pyautogui
|
||||
|
||||
# Méthode 1 : Presse-papier (le plus fiable, gère UTF-8/accents/CJK)
|
||||
# xclip reste en vie comme daemon X11 pour servir le clipboard.
|
||||
# On ne doit PAS attendre sa terminaison — juste envoyer les données
|
||||
# et coller immédiatement.
|
||||
# Méthode 1 : xdotool type (frappes clavier réelles)
|
||||
# Envoie des événements X11 que QEMU/Citrix/RDP capturent et transmettent.
|
||||
# Le presse-papier ne traverse PAS les VM — xdotool si.
|
||||
if shutil.which('xdotool'):
|
||||
try:
|
||||
subprocess.run(
|
||||
['xdotool', 'type', '--delay', '20', '--clearmodifiers', '--', text],
|
||||
timeout=max(30, len(text) * 0.05),
|
||||
check=True
|
||||
)
|
||||
print(f" ✅ Saisie via xdotool type ({len(text)} car.)")
|
||||
return
|
||||
except Exception as e:
|
||||
print(f" ⚠️ xdotool type échoué: {e}")
|
||||
|
||||
# Méthode 2 : Presse-papier (fonctionne seulement en local, pas en VM)
|
||||
xclip = shutil.which('xclip')
|
||||
if xclip:
|
||||
try:
|
||||
@@ -52,7 +61,6 @@ def safe_type_text(text):
|
||||
)
|
||||
p.stdin.write(text.encode('utf-8'))
|
||||
p.stdin.close()
|
||||
# Ne PAS attendre xclip — il reste vivant comme owner du clipboard
|
||||
time.sleep(0.2)
|
||||
pyautogui.hotkey('ctrl', 'v')
|
||||
time.sleep(0.3)
|
||||
@@ -61,19 +69,6 @@ def safe_type_text(text):
|
||||
except Exception as e:
|
||||
print(f" ⚠️ xclip échoué: {e}")
|
||||
|
||||
# Méthode 2 : xdotool type (respecte le layout clavier actif)
|
||||
if shutil.which('xdotool'):
|
||||
try:
|
||||
subprocess.run(
|
||||
['xdotool', 'type', '--delay', '0', '--clearmodifiers', '--', text],
|
||||
timeout=max(30, len(text) * 0.05),
|
||||
check=True
|
||||
)
|
||||
print(f" ✅ Saisie via xdotool type ({len(text)} car.)")
|
||||
return
|
||||
except Exception as e:
|
||||
print(f" ⚠️ xdotool type échoué: {e}")
|
||||
|
||||
# Méthode 3 : pyautogui (dernier recours — problèmes AZERTY possibles)
|
||||
print(" ⚠️ Saisie via pyautogui.write() (AZERTY non garanti)")
|
||||
pyautogui.write(text)
|
||||
@@ -87,7 +82,8 @@ def minimize_active_window():
|
||||
# Minimiser la fenêtre active
|
||||
subprocess.run(['xdotool', 'getactivewindow', 'windowminimize'],
|
||||
capture_output=True, timeout=2)
|
||||
print("📦 [Execute] Fenêtre du navigateur minimisée")
|
||||
time.sleep(1.5)
|
||||
print("📦 [Execute] Fenêtre du navigateur minimisée (+ 1.5s d'attente)")
|
||||
return True
|
||||
except FileNotFoundError:
|
||||
print("⚠️ [Execute] xdotool non installé - impossible de minimiser")
|
||||
@@ -183,7 +179,11 @@ def execute_workflow_thread(execution_id: str, workflow_id: str, app):
|
||||
db.session.add(step_result)
|
||||
db.session.commit()
|
||||
|
||||
print(f"📋 [Execute] Étape {index + 1}/{len(steps)}: {step.action_type} (id={step.id})")
|
||||
print(f"\n{'='*60}")
|
||||
print(f"📋 [Execute] Étape {index + 1}/{len(steps)}: {step.action_type}")
|
||||
print(f" step_id={step.id}, label={step.label}")
|
||||
print(f" anchor_id={step.anchor_id or 'aucune'}")
|
||||
print(f" mode={_execution_state.get('execution_mode')}")
|
||||
|
||||
try:
|
||||
# === VALIDATION CONTRAT STRICT ===
|
||||
@@ -780,13 +780,19 @@ def execute_action(action_type: str, params: dict) -> dict:
|
||||
|
||||
execution_mode = _execution_state.get('execution_mode', 'basic')
|
||||
|
||||
print(f" [dispatch] action={action_type} mode={execution_mode}")
|
||||
print(f" [dispatch] params_keys={list(params.keys())}")
|
||||
|
||||
try:
|
||||
if action_type in ['click_anchor', 'click', 'double_click_anchor', 'right_click_anchor']:
|
||||
# Récupérer les coordonnées depuis l'ancre
|
||||
anchor = params.get('visual_anchor', {})
|
||||
bbox = anchor.get('bounding_box', {})
|
||||
screenshot_base64 = anchor.get('screenshot')
|
||||
|
||||
print(f" [click] bbox={bbox}")
|
||||
print(f" [click] has_screenshot={bool(screenshot_base64)} ({len(screenshot_base64) if screenshot_base64 else 0} chars)")
|
||||
print(f" [click] screen_size={pyautogui.size()}")
|
||||
|
||||
if not bbox:
|
||||
return {'success': False, 'error': 'Pas de bounding_box dans visual_anchor'}
|
||||
|
||||
|
||||
@@ -167,6 +167,7 @@ body {
|
||||
.workflow-list li {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
color: var(--text-primary);
|
||||
padding: 0;
|
||||
cursor: pointer;
|
||||
border-bottom: 1px solid var(--border);
|
||||
@@ -179,6 +180,10 @@ body {
|
||||
|
||||
.workflow-list li.active {
|
||||
background: var(--primary-light);
|
||||
color: white !important;
|
||||
}
|
||||
|
||||
.workflow-list li.active .wf-name {
|
||||
color: white;
|
||||
}
|
||||
|
||||
@@ -192,6 +197,7 @@ body {
|
||||
.workflow-list .wf-name {
|
||||
flex: 1;
|
||||
font-weight: 500;
|
||||
color: var(--text-primary);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
Reference in New Issue
Block a user