Files
Spark b102a4a206 feat(core): moteur PromptKey — bibliothèque .md, variables, injection, raccourcis
- PromptStore/Frontmatter : un prompt = un fichier Markdown, en-tête au
  sous-ensemble strict du document d'architecture (§7), écriture UTF-8
- VariableEngine : variables typées + automatiques (selection, clipboard,
  date, app, cursor), résolution et profils de destination
- Injector/ClipboardGuard : focus capturé/rendu, presse-papiers sauvegardé
  puis restauré conditionnellement, aucun envoi automatique par défaut
- Search : classement flou insensible aux accents, pondéré par l'usage
- UsageStats (usage.json), HotkeyManager (conflits signalés, jamais muets)
- Settings : config.ini + profils par IA (claude, chatgpt, qwen, terminal…)
- PromptKey.ahk : tray, instance unique, capture du contexte, surveillance
  de la bibliothèque, installation du pack au premier lancement
2026-10-11 10:17:30 -04:00

95 lines
3.9 KiB
AutoHotkey

; 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", "")
}
}