; Injector.ahk — SEUL module autorisé à toucher au focus, au presse-papiers et à la frappe ; simulée pour livrer un prompt (architecture §11.1). Aucun autre module n'appelle ; Send / A_Clipboard pour l'injection. class Injector { Guard := ClipboardGuard() SettingsRef := "" __New(settings) { this.SettingsRef := settings } ; options : Map { hwnd, method ("clipboard"|"sendtext"), sendAfter (0/1), sensitive (0/1), cursorFromEnd } ; Retourne Map { ok, method, error } Deliver(text, options) { st := this.SettingsRef method := options.Has("method") && options["method"] != "" ? options["method"] : st.Method hwnd := options.Has("hwnd") ? options["hwnd"] : 0 if (Trim(text) = "") return Map("ok", false, "method", method, "error", "Texte vide : rien à injecter.") if (hwnd && !WinExist("ahk_id " hwnd)) { ; Fenêtre disparue : copie de secours (§11.5). A_Clipboard := text TrayTip("La fenêtre d'origine a disparu — le prompt a été copié dans le presse-papiers.", "PromptKey") return Map("ok", false, "method", method, "error", "Fenêtre cible disparue, prompt copié.") } if (method = "sendtext" && StrLen(text) > 4000) { TrayTip("Prompt long (" StrLen(text) " caractères) : la frappe simulée sera lente.", "PromptKey") } ; Rendre le focus à la fenêtre d'origine. if (hwnd) { try WinActivate("ahk_id " hwnd) try { if !WinWaitActive("ahk_id " hwnd, , 0.5) { A_Clipboard := text TrayTip("Impossible de réactiver la fenêtre cible — prompt copié dans le presse-papiers.", "PromptKey") return Map("ok", false, "method", method, "error", "Réactivation de la fenêtre impossible.") } } Sleep(60) } if (method = "sendtext") { SendText(text) } else { this.Guard.Save() this.Guard.InjectedText := text A_Clipboard := text if !ClipWait(0.5) { this.Guard.RestoreIfUnchanged() return Map("ok", false, "method", method, "error", "Presse-papiers non prêt.") } Send("^v") Sleep(st.PasteDelayMs) ; Restauration (ou vidage) différée, conditionnelle (§11.3). guard := this.Guard sensitive := options.Has("sensitive") ? options["sensitive"] : 0 if sensitive SetTimer(() => guard.WipeIfUnchanged(), -st.RestoreDelayMs) else SetTimer(() => guard.RestoreIfUnchanged(), -st.RestoreDelayMs) } ; Marqueur {{cursor}} : replacer le curseur (§10.3). if (options.Has("cursorFromEnd") && options["cursorFromEnd"] > 0) { n := options["cursorFromEnd"] Send("{Left " n "}") } ; Envoi automatique : seulement si le profil le demande explicitement (§12.4, D5). if (options.Has("sendAfter") && options["sendAfter"]) { delay := st.SendAfterDelayMs TrayTip("Envoi automatique dans " Round(delay / 1000, 1) " s — Échap pour annuler.", "PromptKey") ; Fenêtre d'annulation : on écoute Échap pendant le délai. cancelled := false endTime := A_TickCount + delay while (A_TickCount < endTime) { if GetKeyState("Escape", "P") { cancelled := true break } Sleep(25) } if (!cancelled) Send("{Enter}") else TrayTip("Envoi annulé — le texte reste en place.", "PromptKey") } Util.Log("Injection OK (" method ", " StrLen(text) " caractères)") return Map("ok", true, "method", method, "error", "") } }