unit UnitPaste;
{
    Pasting, macro support - including keystrokes
}
interface
uses UnitClipQueue, INIFiles, Classes, System.Types;


const
    KEYS_STR = '[KEYS]';
    KEYS_IGNORENL_STR = '[KEYS="IGNORENL"]';

type TPasteMethod = (
    PASTE_CTRL_V=0,
    PASTE_SHIFT_INS=1,
    PASTE_MIMIC=2,
    PASTE_CLIPBOARD=3,
    PASTE_DEFAULT=4,
    //PASTE_PASTEMACRO=5,
    PASTE_PROGRAMCUSTOMMACRO=6
);

type TPasteVariable = (
pv_NONE, pv_INSERTDAT,
pv_date, pv_wait, pv_prompt, pv_popupitem, pv_clipbrd, pv_clipbrdcurrent, pv_clear, pv_clearpoup,
pv_windowconfig, pv_windowremoved, pv_windowhistory, pv_windowclipboard, pv_windowpermanent,
pv_windowsearch, pv_selectall, pv_selectleft, pv_selectright, pv_windowpastesel,
pv_clipboardonly, pv_left, pv_right, pv_up, pv_down, pv_backspace, pv_home, pv_end,
pv_tab, pv_delete, pv_insert, pv_space, pv_enter, pv_key, pv_cut, pv_copy, pv_paste, pv_run, pv_endrun,
pv_clipboardfind, pv_disablemonitoring, pv_deletepopupclip, pv_trimclipboard,
pv_clipboardupper, pv_clipboardlower, pv_clipboardcapwords, pv_clipboardinverse,
pv_copywait, pv_pushclipboard, pv_popfirst, pv_poplast,
pv_toclipboard, pv_pastedefault, pv_moveclip, pv_waitforclip, pv_totextfile,
pv_clipboardsplit, pv_splititem, pv_splitloop, pv_saveclipboard, pv_newclip
);
type TCopyFunction =
(COPY_NORMAL, COPY_PREPEND, COPY_APPEND, COPY_PLAIN, COPY_FORMAT);

// variables that don't need to send keystrokes to the target program
const
TNoFocusVariables = [
    pv_wait, pv_clear, pv_clearpoup,pv_windowconfig, pv_windowremoved,
    pv_windowhistory, pv_windowclipboard, pv_windowpermanent, pv_windowsearch,
    {pv_run,} pv_endrun, pv_clipboardfind, pv_disablemonitoring, pv_deletepopupclip, pv_trimclipboard,
    pv_clipboardupper, pv_clipboardlower, pv_clipboardcapwords, pv_clipboardinverse,
    pv_copywait, pv_pushclipboard, pv_popfirst, pv_poplast,
    pv_toclipboard, pv_moveclip, pv_waitforclip, pv_totextfile
];
TInsertTextVariables = [
    pv_none, pv_clipbrd, pv_clipbrdcurrent, pv_popupitem
];
TRepeatedKeysVariables = [
    pv_selectleft, pv_selectright,
    pv_left, pv_right, pv_up, pv_down,
    pv_backspace, pv_home, pv_end,
    pv_tab, pv_delete, pv_space, pv_enter
];


TNoFocusClipboardOnlyMode = TNoFocusVariables + TInsertTextVariables;
type
TPasteMode = (pm_None, pm_ClipboardOnly,pm_CtrlV,pm_Mimic,pm_ShiftIns,pm_CustomScript);
TPasteModeOverride = (pmo_None, pmo_ClipboardOnlyOnce, pmo_CtrlVOnce,pmo_MimicOnce,pmo_ShiftInsOnce,pmo_CustomScriptOnce );


type
TPaste = class;
TMacroEngine = class(TObject)
    protected
        nuggets : TList;
        VariableStart : TStringHash;

        MacroMimic : boolean;
        MacroNoMonitoring : boolean;
        MonitoringState : boolean;
        fMacroRequiresFocus : boolean;
        MacroClipboardState : string;
        HasEndRun : integer;
        Paste : TPaste;

        SplitClipboard : TStringDynArray;
        SplitIndex : integer;
        fNestedMode : boolean;
        SavedPasteModeOverride : TPasteModeOverride;
        forceClipboardOnly : boolean;
        SplitList : TStringList;
        lastJavaScriptErr : string;
        procedure ClearNuggets;
        procedure DefineVariableStart(starttext : string; vartype : TPasteVariable);
        function IsVariable(starttext : string) : boolean;
        function VariableType(starttext : string) : TPasteVariable;


        procedure pushPasteFlags;
        procedure popPasteFlags;
        procedure NestedModeOn;
    public
        IgnoreNewLines : boolean;
        constructor Create(inPaste : TPaste);
        destructor Destroy;
        procedure ParseMacro(maintext : string);
        procedure ExecuteMacro;
        function MacroRequiresFocus : boolean;
end;
TPaste = class(TObject)
    private
        PasteMode : TPasteMode;
        SavedPasteMode : TPasteMode;
        MacroPasteMode : TPasteMode;
        PasteModeOverride : TPasteModeOverride;
        SavedPasteModeOverride : TPasteModeOverride;

        EXEPasteList : THashedStringList;


        fIsPasting, fIsPastingKeystrokes : Boolean;

        macroEngine : TMacroEngine;
        PasteMacro : string;
        fLastJavaScriptError : string;
        var
            b, b1,b2,b3,b4 : boolean;
            pm1,pm2,pm3,pm4,pm5 : boolean;
        procedure PushFlags;
        procedure PopFlags;
        procedure PushPasteMode;
        procedure PopPasteMode;
        procedure ClearMethods;

        function PutClipOnCLipboard(ci : TClipItem; NoPlaintext:boolean=false) : boolean;
        procedure HandlePasteMethod;
        procedure SendUsingKeyboardMimic(s : WideString);
        procedure SendText(s: string; ci  : TClipItem = nil; noPlaintext : boolean = false);
    public
        constructor Create;
        destructor Destroy; override;
        function IsMacro(s : string) : boolean;
        procedure SendPlainText(s : string);
        procedure SendClip(ci : TClipItem; ForceFormatted : boolean = false);
        procedure SendMacro(maintext : string);

        function SendJavaScript(javascript : string; handle : THandle=0) : boolean;

        procedure ParseMacro(maintext : string);
        procedure ExecuteMacro;
        function MacroRequiresFocus : boolean;

        procedure PlaceOnClipboardDontBypassClipboardManager(s : string);


        function SetKeyUp(key : byte; up : boolean = true) : boolean;
        procedure SendSHIFT_INSERT;
        procedure SendCTRL_V;
        procedure SendCTRL_C;
        procedure SendCTRL_X;        
        procedure SendCTRL_A;
        procedure SendCTRL_Release;
        procedure SendShift_Release;
        procedure SendALT_Release;
        procedure SendKeyCustom(keys : string);
        procedure SendTAB;
        procedure SendENTER;
        procedure SendINSERT;
        procedure SendDELETE;
        procedure SendBACKSPACE;
        procedure SendHOME;
        procedure SendEND;
        procedure SendUP;
        procedure SendDOWN;
        procedure SendDOWNSpecial;
        procedure SendLEFT;
        procedure SendRIGHT;
        procedure SendESC;
        procedure SendSPACE;
        procedure SendCTRL_SHIFT_LEFT;
        procedure SendCTRL_SHIFT_RIGHT;
        procedure SendCTRL_RIGHT;
        {Used by FrmCONFIG to set pasting method}
        procedure SetClipboardOnly;
        function GetClipboardOnly : boolean;
        procedure SetMimicTyping;
        procedure SetUsePaste;
        procedure SetUsePasteSI;
        procedure SetUseCustomScript;

        {Used by FrmMainPopup}
        procedure SetClipboardOnlyOnce;
        procedure SetKeyboardMimicOnce;
        function GetKeyboardMimicOnce : boolean;
        procedure SetUsePasteCVOnce;
        procedure SetUsePasteSIOnce;
        procedure SetUseCustomScriptOnce;
        procedure ClearOnceFlags;
        function GetClipboardOnlyOnce : boolean;

        procedure AssignPaste(EXEName : string; method : TPasteMethod);
        function GetPasteMethod(EXEName : string) : TPasteMethod;
        function GetDefaultPasteMethod : TPasteMethod;
        procedure GetEXEPasteList(var sl : TStringList);

        function IsPasting : boolean;

        procedure SetPasteMacro(macro : string);

        property LastJavaScriptErr : string read fLastJavaScriptError;
end;


var Paste : TPaste;

implementation

uses Windows, SysUtils, StrUtils, UnitFrmMainPopup, UnitPopupGenerate, clipbrd,
    UnitFrmClipboardManager, UnitOtherQueue, UnitMisc, UnitToken, Forms, Dialogs,
  UnitFrmConfig, UnitFrmEditHistory, UnitFrmSysTrayMenu, UnitFrmPermanentNew,
  UnitFrmSearch, UnitFrmHotKey, UnitKeyboardQuery, UnitFrmEditTextExternal,
  UnitFrmPasteSelected, System.Character, Generics.Collections, UnitFrmDebug,
  UnitJS, UnitFocusManager
  ;

const  EXEPASTE_FILE = 'exepaste.ini';
{ TPaste }

type TMacroClipboardList = class(TObject)
    private
        l : TList<string>;
    public
        constructor Create;
        procedure Push;
        procedure PopFirst;
        procedure PopLast;
        procedure Clear;
end;
constructor TMacroClipboardList.Create;
begin
    l := TList<string>.Create;
end;
procedure TMacroClipboardList.Push;
begin
    l.Add( Clipboard.AsText );
end;
procedure TMacroClipboardList.PopFirst;
var result : string;
begin
    result := l.First;
    l.Delete(0);

    ClipBoard.SetTextBuf(pchar(result));
end;
procedure TMacroClipboardList.PopLast;
var result : string;
begin
    result := l.Last;
    l.Delete(l.Count-1);

    ClipBoard.SetTextBuf(pchar(result));
end;
procedure TMacroClipboardList.Clear;
begin
    l.Clear;
end;
var MacroClipboardList : TMacroClipboardList;


function ToPasteMode(pmo : TPasteModeOverride) : TPasteMode;
begin
    result := pm_none;
    case pmo of
        pmo_ClipboardOnlyOnce: result := pm_ClipboardOnly;
        pmo_CtrlVOnce: result := pm_CtrlV;
        pmo_MimicOnce: result := pm_Mimic;
        pmo_ShiftInsOnce: result := pm_ShiftIns;
        pmo_CustomScriptOnce: result := pm_CustomScript;
    end;
end;

procedure TPaste.PlaceOnClipboardDontBypassClipboardManager(s: string);
begin
    frmClipboardManager.DisablePasteProtectionOnce;
    // No clearing, do not prevent the Clipboard from detecting the change
    clipboard.SetTextBuf(PChar(s));
end;

constructor TPaste.Create;
var s : string;
begin
    self.EXEPasteList := THashedStringList.Create;
    s := UnitMisc.GetAppPath;
    s := s + EXEPASTE_FILE;
    if FileExists(s) then begin
        self.EXEPasteList.LoadFromFile(s);
    end;



    macroEngine := TMacroEngine.Create(self);
end;
destructor TPaste.Destroy;
var s : string;
begin
    s := UnitMisc.GetAppPath;
    s := s + EXEPASTE_FILE;
    self.EXEPasteList.SaveToFile(s);
    MyFree(EXEPasteList);
end;


procedure TPaste.PushFlags;
begin
    SavedPasteModeOverride := PasteModeOverride;
end;
procedure TPaste.PopFlags;
begin
    PasteModeOverride := SavedPasteModeOverride;
end;

procedure TPaste.PushPasteMode;
begin
    SavedPasteMode := PasteMode;
end;
procedure TPaste.PopPasteMode;
begin
    PasteMode := SavedPasteMode;
end;
procedure TPaste.SetPasteMacro(macro: string);
begin
    self.PasteMacro := macro;
end;
//
// Either send text S or use ClipItem if specificed
//

procedure TPaste.SendUsingKeyboardMimic(s : WideString);
var
    i : integer;
    ms : integer;
    inp : array[0..1] of tagINPUT;
begin
    fillchar(inp[0],sizeof(inp[0]),0);
    fillchar(inp[1],sizeof(inp[1]),0);

    inp[0].Itype := INPUT_KEYBOARD;
    inp[0].ki.dwFlags := KEYEVENTF_UNICODE;
    inp[1] := inp[0];
    inp[1].ki.dwFlags := inp[1].ki.dwFlags or KEYEVENTF_KEYUP;

    ms := frmconfig.getMimicDelayMS;
    for i := 1 to length(s) do begin
        if (s[i]=#13) and ((i+1)<=length(s)) and (s[i+1]=#10) then CONTINUE;
        if (s[i]=#0) then CONTINUE;


        inp[0].ki.wScan := word(s[i]);
        inp[1].ki.wScan := word(s[i]);
        SendInput(1, inp[0], sizeof(tagINPUT));
        SendInput(1, inp[1], sizeof(tagINPUT));

        if ms <> 0 then begin
            sleep(ms);
        end;
    end;
end;
function TPaste.IsPasting : boolean;
begin
    result := self.fIsPasting or self.fIsPastingKeystrokes;
end;
function TPaste.PutClipOnClipboard(ci : TClipItem; NoPlaintext:boolean=false) : boolean;
    function PutClipOnCLipboard(ci : TClipItem; Format : word = 0) : boolean;
    var duph : THandle;
        dups : cardinal;
        pc : PChar;
        s : string;
        st : TStream;

        procedure TextOnClipboard(s : ansistring);
        begin
            dups := length(s) + 1;
            //dups := StrSize(s+#0) + 1;
            duph := Windows.GlobalAlloc(GMEM_MOVEABLE or GMEM_ZEROINIT, dups);
            pc := Windows.GlobalLock(duph);
            Windows.MoveMemory(pc, pansichar(s+#0), dups);
            Windows.GlobalUnlock(duph);
            Windows.SetClipboardData(CF_TEXT, duph);

        end;
//    var rawstring : RawByteString;
    begin
        result := false;

        if (Format = CF_TEXT) then begin
            FrmDebug.AppendLog('PutClipOnClipboard: text' );
            s := ci.GetAsPlaintext +#0;
            duph := UnitMisc.DupPointerToHandle(@s[1], length(s) * SizeOf(Char) );
            Windows.SetClipboardData(CF_UNICODETEXT, duph);
        end else begin

            st := ci.GetStream;
            duph := 0;
            if ci.GetFormatType = FT_UNICODE then begin
                FrmDebug.AppendLog('PutClipOnClipboard: text' );
                s := ci.GetAsPlaintext + #0;
                duph := UnitMisc.DupPointerToHandle(@s[1], length(s) * SizeOf(Char) );
            end else if (st.size <> 0) then begin
                FrmDebug.AppendLog('PutClipOnClipboard: format = ' + ci.GetFormatName );
                duph := UnitMisc.DupStreamToHandle(st);
            end else begin
                FrmDebug.AppendLog('SendText: Missing Pointer/Unicode string');
            end;
            ci.FinishedWithStream;

            if (duph <> 0) then begin
                Windows.SetClipboardData(ci.GetFormat, duph);
            end else begin
                FrmDebug.AppendLog('SendText: Unable to dup ClipItem to place on clipboard');
                EXIT;
            end;
        end;

        result := true;
    end;

begin
    result := false;
    //
    // No calls to unit Clipbrd can be used to in this section
    // since it will try to open and already open clipboard.
    // Place both the plain text version and the complex item on the clipboard
    //
    Windows.OpenClipboard(Application.Handle);
    Windows.EmptyClipboard;
    //Clipboard.Clear; // <- Makes use owner
    if not PutClipOnCLipboard(ci) then begin
        Windows.CloseClipboard;
        EXIT;
    end;

    if not noPlaintext then begin
        if ci.HasText and (ci.GetFormat <> Windows.CF_DIB)  then begin
            if not PutClipOnCLipboard(ci, CF_TEXT) then begin
                Windows.CloseClipboard;
                EXIT;
            end;
        end;
    end;

    Windows.CloseClipboard;
    result := true;
end;
procedure TPaste.HandlePasteMethod;
begin
    case PasteMode of
    pm_CtrlV:
        begin
            self.SendCTRL_V;
            FrmDebug.AppendLog('Paste: CTRL+V')
        end;
        pm_ShiftIns:
        begin
            self.SendSHIFT_INSERT;
            FrmDebug.AppendLog('Paste: Shift Insert');
        end;
        pm_Mimic:
        begin
            SendUsingKeyboardMimic(Clipboard.AsText);
            FrmDebug.AppendLog('Paste: Mimic Typing');
        end;
        pm_ClipboardOnly:
        begin
            FrmDebug.AppendLog('Paste: Clipboard Only');
        end;
        pm_CustomScript: begin
            FrmDebug.AppendLog('Paste: Custom Script');
            self.SendMacro(KEYS_STR+self.PasteMacro);
        end;
        else
        begin
            self.SendCTRL_V;
            FrmDebug.AppendLog('Paste: CTRL+V');
        end;
    end;
end;


function TPaste.IsMacro(s : string) : boolean;
begin
    result :=  AnsiStartsText(KEYS_STR, s) or
            AnsiStartsText(KEYS_IGNORENL_STR, s);
end;




function TPaste.SetKeyUp(key : byte; up : boolean = true) : boolean;
begin
    result := false;
    if up then begin
        result := KeyboardQuery.IsPressed(key);
        if result  then begin
            case key of
                VK_CONTROL:
                begin
                    if Keyboardquery.IsPressed(VK_LCONTROL) then begin
                        keybd_event(VK_LCONTROL,0,KEYEVENTF_KEYUP, 0);
                    end else begin
                        keybd_event(VK_RCONTROL,0,KEYEVENTF_KEYUP, 0);
                    end;
                    //sleep(15);
                end;
                VK_SHIFT:
                begin
                    if KeyboardQuery.IsPressed(VK_LSHIFT) then begin
                        keybd_event(VK_LSHIFT,0,KEYEVENTF_KEYUP, 0);
                    end else begin
                        keybd_event(VK_RSHIFT,0,KEYEVENTF_KEYUP, 0);
                    end;
                    //sleep(15);
                end;
                VK_MENU:
                begin
                    keybd_event(VK_MENU,0,KEYEVENTF_KEYUP, 0);
                    if KeyboardQuery.IsPressed(VK_LMENU) then begin
                        keybd_event(VK_LMENU,0,KEYEVENTF_KEYUP, 0);
                    end else begin
                        keybd_event(VK_RMENU,0,KEYEVENTF_KEYUP, 0);
                    end;
                    //sleep(16);
                end;
            else
                begin
                    keybd_event(key,key,KEYEVENTF_KEYUP, 0);
                    //sleep(10);
                end;
            end;
        end;
    end else begin
        keybd_event(key,0,0, 0);
        //sleep(10);
    end;
end;

//
// used by SendText and OtherItemClickEvent to simulate the user
// pressing CTRL+V to paste
//
procedure TPaste.SendCTRL_V;
var w : word;
begin
    FrmDebug.AppendLog('sending CTRL+V');

    // clear any phantom keystrokes
    keybd_event(VK_SHIFT, VkKeyScan(char(VK_SHIFT)), KEYEVENTF_KEYUP, 0);
    keybd_event(VK_CONTROL , VkKeyScan(char(VK_control)), KEYEVENTF_KEYUP, 0);
    sleep(10);

    // press CTRL, press V, release V, release CTRL
    keybd_event(VK_CONTROL , VkKeyScan(char(VK_control)), 0, 0);
    sleep(10);
            w := VkKeyScan('V');
            keybd_event(lo(w), w, 0, 0);
            sleep(25);

            keybd_event(lo(w), w, KEYEVENTF_KEYUP, 0);
//            sleep(10);
    keybd_event(VK_CONTROL , VkKeyScan(char(VK_control)), KEYEVENTF_KEYUP, 0);

    FrmDebug.AppendLog('sent CTRL+V');
end;
procedure TPaste.SendCTRL_C;
var w : word;
begin
    FrmDebug.AppendLog('sending CTRL+C');

    // clear any phantom keystrokes
    keybd_event(VK_SHIFT, VkKeyScan(char(VK_SHIFT)), KEYEVENTF_KEYUP, 0);
    keybd_event(VK_CONTROL , VkKeyScan(char(VK_control)), KEYEVENTF_KEYUP, 0);
    sleep(10);

    // press CTRL, press V, release V, release CTRL
    keybd_event(VK_CONTROL , VkKeyScan(char(VK_control)), 0, 0);
    sleep(10);
            w := VkKeyScan('C');
            keybd_event(lo(w), w, 0, 0);
            sleep(10);

            keybd_event(lo(w), w, KEYEVENTF_KEYUP, 0);
            sleep(10);
    keybd_event(VK_CONTROL , VkKeyScan(char(VK_control)), KEYEVENTF_KEYUP, 0);
    //sleep(10);
    FrmDebug.AppendLog('sent CTRL+C');
end;
procedure TPaste.SendCTRL_X;
var w : word;
begin
    FrmDebug.AppendLog('sending CTRL+X');

    // clear any phantom keystrokes
    keybd_event(VK_SHIFT, VkKeyScan(char(VK_SHIFT)), KEYEVENTF_KEYUP, 0);
    keybd_event(VK_CONTROL , VkKeyScan(char(VK_control)), KEYEVENTF_KEYUP, 0);
    sleep(10);

    // press CTRL, press V, release V, release CTRL
    keybd_event(VK_CONTROL , VkKeyScan(char(VK_control)), 0, 0);
    sleep(10);
            w := VkKeyScan('X');
            keybd_event(lo(w), w, 0, 0);
            sleep(10);

            keybd_event(lo(w), w, KEYEVENTF_KEYUP, 0);
            sleep(10);
    keybd_event(VK_CONTROL , VkKeyScan(char(VK_control)), KEYEVENTF_KEYUP, 0);
    //sleep(10);
    FrmDebug.AppendLog('sent CTRL+X');
end;
procedure TPaste.SendCTRL_A;
var w : word;
begin
    FrmDebug.AppendLog('sending CTRL+A');

    // clear any phantom keystrokes
    keybd_event(VK_SHIFT, VkKeyScan(char(VK_SHIFT)), KEYEVENTF_KEYUP, 0);
    keybd_event(VK_CONTROL , VkKeyScan(char(VK_control)), KEYEVENTF_KEYUP, 0);
    sleep(10);

    // press CTRL, press V, release V, release CTRL
    keybd_event(VK_CONTROL , VkKeyScan(char(VK_control)), 0, 0);
    sleep(10);
            w := VkKeyScan('A');
            keybd_event(lo(w), w, 0, 0);
            sleep(50);

            keybd_event(lo(w), w, KEYEVENTF_KEYUP, 0);
            sleep(50);
    keybd_event(VK_CONTROL , VkKeyScan(char(VK_control)), KEYEVENTF_KEYUP, 0);
    sleep(10);
    FrmDebug.AppendLog('sent CTRL+A');
end;

procedure TPaste.SendCTRL_SHIFT_RIGHT;
var w : word;
begin
     // clear any phantom keystrokes
    keybd_event(VK_SHIFT, VkKeyScan(char(VK_SHIFT)), KEYEVENTF_KEYUP, 0);
    keybd_event(VK_CONTROL , VkKeyScan(char(VK_control)), KEYEVENTF_KEYUP, 0);
    sleep(10);

    // press CTRL, press V, release V, release CTRL
    keybd_event(VK_CONTROL , VkKeyScan(char(VK_control)), 0, 0);
    sleep(1);
    keybd_event(VK_SHIFT, Lo(MapVirtualKey(VK_SHIFT,0)),0,0);  // win9x
    sleep(10);
            //w := VkKeyScan('C');
            w := VK_RIGHT;
            keybd_event(lo(w), w, KEYEVENTF_EXTENDEDKEY, 0);
            sleep(10);

            keybd_event(lo(w), w, KEYEVENTF_EXTENDEDKEY or KEYEVENTF_KEYUP, 0);
            sleep(10);
    keybd_event(VK_SHIFT, Lo(MapVirtualKey(VK_SHIFT,0)), KEYEVENTF_KEYUP, 0);
    keybd_event(VK_CONTROL , VkKeyScan(char(VK_control)), KEYEVENTF_KEYUP, 0);

    //sleep(10);
    FrmDebug.AppendLog('sent CTRL+SHIFT+LEFT');
end;
procedure TPaste.SendCTRL_SHIFT_LEFT;
var w : word;
begin
     // clear any phantom keystrokes
    keybd_event(VK_SHIFT, VkKeyScan(char(VK_SHIFT)), KEYEVENTF_KEYUP, 0);
    keybd_event(VK_CONTROL , VkKeyScan(char(VK_control)), KEYEVENTF_KEYUP, 0);
    sleep(10);

    // press CTRL, press V, release V, release CTRL
    keybd_event(VK_CONTROL , VkKeyScan(char(VK_control)), 0, 0);
    sleep(1);
    keybd_event(VK_SHIFT, Lo(MapVirtualKey(VK_SHIFT,0)),0,0);  // win9x
    sleep(10);
            //w := VkKeyScan('C');
            w := VK_LEFT;
            keybd_event(lo(w), w, KEYEVENTF_EXTENDEDKEY, 0);
            sleep(10);

            keybd_event(lo(w), w, KEYEVENTF_EXTENDEDKEY or KEYEVENTF_KEYUP, 0);
            sleep(10);
    keybd_event(VK_SHIFT, Lo(MapVirtualKey(VK_SHIFT,0)), KEYEVENTF_KEYUP, 0);
    keybd_event(VK_CONTROL , VkKeyScan(char(VK_control)), KEYEVENTF_KEYUP, 0);

    //sleep(10);
    FrmDebug.AppendLog('sent CTRL+SHIFT+LEFT');
end;

procedure TPaste.SendCTRL_Release;
begin
	SetKeyUp(VK_CONTROL, true);
end;
procedure TPaste.SendShift_Release;
begin
	SetKeyUp(VK_SHIFT, true);
end;
procedure TPaste.SendALT_Release;
begin
    SetKeyUp(VK_MENU, true);
end;

procedure TPaste.SendKeyCustom(keys: string);
var w : word;
    hk : THotkeydata;
var flags : cardinal;
begin

// These keys require are known to require KEYEVENTF_EXTENDEDKEY
// like for SHIFT+HOME and SHIFT+END
//
//HOME & END
//Left Win 		E0 5B
//Right Win 		E0 5C
//Application 	E0 5D
//ACPI Power 	E0 5E
//ACPI Sleep 	E0 5F
//ACPI Wake 	E0 63
//Numeric / 	E0 35
//Up Arrow 	E0 48
//Dn Arrow 	E0 50
//Page Up 	E0 49
//Page Down 	E0 51
//R Arrow 	E0 4D
//L Arrow 	E0 4B


    hk := FrmHotkey.FromString(keys);
    // unpress mod keys
    // simulate modifiers down
    if hk.win then begin
        keybd_event(VK_LWIN, VkKeyScan(char(VK_LWIN)), 0, 0);
    end;
    if hk.alt then begin
        keybd_event(VK_MENU, VkKeyScan(char(VK_MENU)), 0, 0);
    end;
    if hk.ctrl then begin
        keybd_event(VK_CONTROL , VkKeyScan(char(VK_control)), 0, 0);
    end;
    if hk.shft  then begin
        keybd_event(VK_SHIFT, Lo(MapVirtualKey(VK_SHIFT,0)),0,0);  // win9x
    end;
    if (hk.shft or hk.win or hk.alt or hk.ctrl) then begin
        sleep(10);
    end;

    if (hk.key > 0) then begin
            // main key press
            //w := VkKeyScan('C');
            w := hk.key;

            flags := 0;
            if (hk.key >= ord('A')) and (hk.key <= ord('Z')) then begin
                w := VKKeyScan(char(lo(w)));
            end;
            if hk.key in [VK_LEFT, VK_RIGHT, VK_HOME, VK_END, VK_NEXT, VK_PRIOR, VK_INSERT, VK_DELETE ] then
                flags := KEYEVENTF_EXTENDEDKEY;

            keybd_event(lo(w), w, flags, 0);
            sleep(10);

            keybd_event(lo(w), w, flags or KEYEVENTF_KEYUP, 0);
            sleep(10);
    end;

    // release modifiers
    if hk.shft then
        keybd_event(VK_SHIFT, VkKeyScan(char(VK_SHIFT)), KEYEVENTF_KEYUP, 0);
    if hk.ctrl then
        keybd_event(VK_CONTROL, VkKeyScan(char(VK_control)), KEYEVENTF_KEYUP, 0);
    if hk.alt then
        keybd_event(VK_MENU, VkKeyScan(char(VK_MENU)), KEYEVENTF_KEYUP, 0);
    if hk.win then
        keybd_event(VK_LWIN, VkKeyScan(char(VK_LWIN)), KEYEVENTF_KEYUP, 0);

    //sleep(10);
    FrmDebug.AppendLog('sent Keystroke ' + hk.name);
end;


procedure TPaste.SendSHIFT_INSERT;
begin
    FrmDebug.AppendLog('sending SHIFT+INSERT');

    // clear any phantom keystrokes
    keybd_event(VK_SHIFT, VkKeyScan(char(VK_SHIFT)), KEYEVENTF_KEYUP, 0);
    keybd_event(VK_CONTROL , VkKeyScan(char(VK_control)), KEYEVENTF_KEYUP, 0);
    sleep(10);

    // press SHIFT, press INSERT, release INSERT, release SHIFT

    // Label VK_INSERT as extended, othewise it freaks out when combined with SHIFT

    keybd_event(VK_SHIFT, Lo(MapVirtualKey(VK_SHIFT,0)),0,0);  // win9x
    sleep(1);
            keybd_event(VK_INSERT, Lo(MapVirtualKey(VK_INSERT,0)),KEYEVENTF_EXTENDEDKEY, 0);
            sleep(1);

            keybd_event(VK_INSERT, Lo(MapVirtualKey(VK_INSERT,0)),KEYEVENTF_EXTENDEDKEY or KEYEVENTF_KEYUP,0);
            sleep(1);

    keybd_event(VK_SHIFT, Lo(MapVirtualKey(VK_SHIFT,0)), KEYEVENTF_KEYUP, 0);
    sleep(5);
    FrmDebug.AppendLog('sent SHIFT+INSERT');
end;
procedure TPaste.SendCTRL_RIGHT;
var w : word;
begin
     // clear any phantom keystrokes
    keybd_event(VK_SHIFT, VkKeyScan(char(VK_SHIFT)), KEYEVENTF_KEYUP, 0);
    keybd_event(VK_CONTROL , VkKeyScan(char(VK_control)), KEYEVENTF_KEYUP, 0);
    sleep(10);

    // press CTRL, press V, release V, release CTRL
    keybd_event(VK_CONTROL , VkKeyScan(char(VK_control)), 0, 0);
    sleep(1);
    //keybd_event(VK_SHIFT, Lo(MapVirtualKey(VK_SHIFT,0)),0,0);  // win9x
    sleep(10);
            //w := VkKeyScan('C');
            w := VK_RIGHT;
            keybd_event(lo(w), w, KEYEVENTF_EXTENDEDKEY, 0);
            sleep(10);

            keybd_event(lo(w), w, KEYEVENTF_EXTENDEDKEY or KEYEVENTF_KEYUP, 0);
            sleep(10);
    //keybd_event(VK_SHIFT, Lo(MapVirtualKey(VK_SHIFT,0)), KEYEVENTF_KEYUP, 0);
    keybd_event(VK_CONTROL , VkKeyScan(char(VK_control)), KEYEVENTF_KEYUP, 0);

    //sleep(10);
    FrmDebug.AppendLog('sent CTRL+SHIFT+LEFT');

end;

procedure TPaste.SendTAB;
begin
    FrmDebug.AppendLog('sending tab');
    keybd_event(VK_TAB , 0, 0, 0);
    sleep(30);

    keybd_event(VK_TAB, 0, KEYEVENTF_KEYUP, 0);
    sleep(10);
    FrmDebug.AppendLog('sent tab');
end;
procedure TPaste.SendDELETE;
begin
    FrmDebug.AppendLog('sending del');
    keybd_event(VK_DELETE, 0, 0, 0);
    sleep(30);

    keybd_event(VK_DELETE, 0, KEYEVENTF_KEYUP, 0);
    sleep(10);
    FrmDebug.AppendLog('sent del');

end;
procedure TPaste.SendINSERT;
begin
    FrmDebug.AppendLog('sending INSERT');
    keybd_event(VK_INSERT, 0, 0, 0);
    sleep(30);

    keybd_event(VK_INSERT, 0, KEYEVENTF_KEYUP, 0);
    sleep(10);
    FrmDebug.AppendLog('sent INSERT');
end;
procedure TPaste.SendENTER;
begin
    FrmDebug.AppendLog('sending ENTER');
    keybd_event(VK_RETURN, MapVirtualKey(VK_RETURN,0), 0, 0);
    sleep(30);

    keybd_event(VK_RETURN, MapVirtualKey(VK_RETURN,0), KEYEVENTF_KEYUP, 0);
    sleep(10);
    FrmDebug.AppendLog('sent ENTER');
end;
procedure TPaste.SendSPACE;
begin
    FrmDebug.AppendLog('sending tab');
    keybd_event(VK_SPACE , 0, 0, 0);
    sleep(30);

    keybd_event(VK_SPACE, 0, KEYEVENTF_KEYUP, 0);
    sleep(10);
    FrmDebug.AppendLog('sending space');
end;

procedure TPaste.ClearMethods;
begin
    PasteMode := pm_None;
end;
procedure TPaste.SetClipboardOnly;
begin
    PasteMode := pm_ClipboardOnly;
end;
procedure TPaste.SetMimicTyping;
begin
    PasteMode := pm_Mimic;
end;
procedure TPaste.SetUsePaste;
begin
    PasteMode := pm_CtrlV;
end;
procedure TPaste.SetUsePasteSI;
begin
    PasteMode := pm_ShiftIns;
end;
procedure TPaste.SetUseCustomScript;
begin
    PasteMode := pm_CustomScript;
end;

procedure TPaste.SetKeyboardMimicOnce;
begin
    PasteModeOverride := pmo_MimicOnce;
end;
procedure TPaste.SetUsePasteCVOnce;
begin
    PasteModeOverride := pmo_CtrlVOnce;
end;
procedure TPaste.SetUsePasteSIOnce;
begin
    PasteModeOverride := pmo_ShiftInsOnce;
end;
procedure TPaste.SetClipboardOnlyOnce;
begin

    FrmDebug.AppendLog('[Paste]: ClipboardOnce set');
    PasteModeOverride := pmo_ClipboardOnlyOnce;
end;
procedure TPaste.SetUseCustomScriptOnce;
begin
    FrmDebug.AppendLog('[Paste]: CustomScriptOnce set');
    PasteModeOverride := pmo_CustomScriptOnce;
end;
function TPaste.GetClipboardOnlyOnce : boolean;
begin
    result := PasteModeOverride = pmo_ClipboardOnlyOnce;
end;

function TPaste.GetClipboardOnly: boolean;
begin
    result := PasteMode = pm_ClipboardOnly;
end;


function TPaste.GetKeyboardMimicOnce: boolean;
begin
    result := PasteModeOverride = pmo_MimicOnce;
end;

type TKeyProc = procedure;
type TNugget = class(TObject)
    vartype : TPasteVariable;
    vartext : string;
    varvalue : integer;
    varnested : string;
end;

///
/// Main Pasting Routines
///
function TPaste.SendJavaScript(javascript : string; handle : THandle=0) : boolean;
var
    js : TJavaScript;
begin
    js := TJavaScript.Create;
    fLastJavaScriptError := '';
    if (handle = 0) then handle := FrmMainPopup.Handle;
    result := js.executeJS(Handle, self, javascript);
    if not result then begin
        fLastJavaScriptError := js.getLastErr;
    end;
    myfree(js);
end;
procedure TPaste.SendMacro(maintext: string);
var s : string;
    pm : TPasteMode;

    procedure setPasteMode;
    begin
        pm := PasteMode;
        PasteMode := ToPasteMode(PasteModeOverride);
        PasteModeOverride := pmo_none;
    end;
    procedure restorePasteMode;
    begin
        PasteMode := pm;
    end;
begin
    try
        FrmDebug.AppendLog('Macro [Start]');
        setPasteMode;
        self.fIsPastingKeystrokes := true;

        macroEngine.IgnoreNewLines := AnsiStartsText(KEYS_IGNORENL_STR, maintext);
        if macroEngine.IgnoreNewLines then begin
            DeleteFromStart(maintext, KEYS_IGNORENL_STR);
        end else begin
            DeleteFromStart(maintext, KEYS_STR);
        end;

        macroEngine.ParseMacro(maintext);
        macroEngine.ExecuteMacro
    finally
        Self.fIsPastingKeystrokes := false;
        restorePasteMode;
        FrmDebug.AppendLog('Macro [End]');
    end;
end;
procedure TPaste.SendPlainText(s : string);
begin
    self.SendText(s);
end;
procedure TPaste.SendClip(ci : TClipItem; ForceFormatted : boolean = false);
begin
    self.SendText('', ci, ForceFormatted);
end;
// private root routine
procedure TPaste.SendText(s: string; ci : TClipItem = nil; NoPlaintext : boolean = false);
    {$REGION 'Legacy'}
    {procedure SendUsingKeyboardMimic(s : string);
    var c : char;
        w : smallint;
        i : integer;
        delay : Integer;
        ShiftPressed, EnterPressed, AltPressed, CtrlPressed : boolean;
    begin
        //FrmDebug.AppendLog('mimic typing');
        delay := FrmConfig.GetMimicDelayMS;
        for i := 1 to length(s) do begin
            c := s[i];
            if c=#0 then BREAK; // issue with nulls showing up in text

            w := VkKeyScan(c);
            ShiftPressed := (hi(w) and 1) > 0;
            CtrlPressed := (hi(w) and 2) > 0;   //this is for German/Swiss keyboards with an AltGr (CTLR+ALT) keystroke
            AltPressed := (hi(w) and 4) > 0;


            EnterPressed := (byte(c) = VK_RETURN);
            //VkKeyScan: The first bit of the hi byte set means shift is pressed
            //Ditch LF - assume CR came first
            if (c <> #10) then begin
                if ShiftPressed and (not EnterPressed) then begin
                    keybd_event(VK_SHIFT, 0,0,0);
                end;
                if AltPressed and (not EnterPressed) then begin
                    keybd_event(VK_MENU, 0, 0, 0);
                end;
                //sleep(1);
                if CtrlPressed and (not EnterPressed) then begin
                    keybd_event(VK_CONTROL , 0, 0, 0);
                end;
                //sleep(1);

                //Press and release key
                keybd_event(lo(w), 0, 0, 0);
                sleep(delay);
                keybd_event(lo(w), 0, KEYEVENTF_KEYUP, 0);


                if CtrlPressed and (not EnterPressed) then begin
                    keybd_event(VK_CONTROL, 0, KEYEVENTF_KEYUP, 0);
                end;

                if AltPressed and (not EnterPressed) then begin
                    keybd_event(VK_MENU, 0, KEYEVENTF_KEYUP, 0);
                end;
                if ShiftPressed and (not EnterPressed) then begin
                    //keybd_event(VK_RSHIFT, 0, KEYEVENTF_KEYUP, 0);
                     keybd_event(VK_SHIFT, 0, KEYEVENTF_KEYUP, 0);
                end;

            end;

            if (i mod 10 = 0) then sleep(1); // give the keyboard buffer a little break
        end;
    end; }
    {procedure SendUsingKeyboardMimic2(s : WideString);
    var
        i : integer;
        inp : array[0..1] of tagINPUT;
    begin
        fillchar(inp[0],sizeof(inp[0]),0);
        fillchar(inp[1],sizeof(inp[1]),0);

        inp[0].Itype := INPUT_KEYBOARD;
        inp[0].ki.dwFlags := KEYEVENTF_UNICODE;
        inp[1] := inp[0];
        inp[1].ki.dwFlags := inp[1].ki.dwFlags or KEYEVENTF_KEYUP;

        for i := 1 to length(s) do begin
            inp[0].ki.wScan := word(s[i]);
            inp[1].ki.wScan := word(s[i]);
            SendInput(1, inp[0], sizeof(tagINPUT));
            SendInput(1, inp[1], sizeof(tagINPUT));
        end;
    end; }
    {$ENDREGION}
    procedure PutOnClipboard(s : string);
    var
        duph : THandle;
        s2 : string;
    begin
        if (s <> '') and (s <> #0) then begin
            Windows.OpenClipboard(Application.Handle);
            Windows.EmptyClipboard;

            s2 := s + #0;
            duph := UnitMisc.DupPointerToHandle(@s[1], length(s) * SizeOf(Char) );
            Windows.SetClipboardData(CF_UNICODETEXT, duph);

            Windows.CloseClipboard;
        end;
    end;
var
    pm : TPasteMode;
begin
    if (s = '') and (ci = nil) then begin
        self.ClearOnceFlags;
        EXIT;
    end;
    FrmDebug.AppendLog('[Paste Start]');

try
    self.fIsPasting := true;
    s := s + #0;
    UnitMisc.MySleep(FrmConfig.GetPasteDelay);
    pm := PasteMode;
    if (PasteModeOverride <> pmo_None) then begin
        PasteMode :=  ToPasteMode(PasteModeOverride);
        FrmDebug.AppendLog('PasteMode=' + IntToStr(integer(Pastemode)));
    end;


    // send text using configured method
    // Update: ClipboardOnlyWindow will override and force
    //          a clipboard only operation
    if (ci = nil) then begin
        FrmDebug.AppendLog('Plain text version');
        if (PasteMode = pm_Mimic)  then begin
            FrmDebug.AppendLog('Paste: Mimic');
            SendUsingKeyboardMimic(s);
        end else begin
            Windows.EmptyClipboard;
            PutOnClipboard(s);
            HandlePasteMethod;
        end;
    end else begin
        FrmDebug.AppendLog('complex text version');
        if not self.PutClipOnClipboard(ci, noPlaintext) then EXIT;
        if (PasteMode = pm_Mimic)  then begin
            PasteModeOverride := pmo_None;
            if not ci.HasText then begin
                PasteMode := pm_CtrlV;
                HandlePasteMethod;
            end else begin
                SendUsingKeyboardMimic(ci.GetAsPlaintext);
                FrmDebug.AppendLog('Paste: Mimic Type');
            end;
        end else begin
            HandlePasteMethod;
        end;
    end;
    PasteMode := pm;
finally
    self.fIsPasting := false;
end;


    FrmDebug.AppendLog('[Paste End]');
end;


//
// Macro Execution
//
procedure TPaste.ParseMacro(maintext : string);
begin
    MacroPasteMode := PasteMode;
    PasteMode := ToPasteMode(PasteModeOverride);
    macroEngine.ParseMacro(maintext);
end;
function TPaste.MacroRequiresFocus : boolean;
begin
    result := macroEngine.MacroRequiresFocus;
end;
procedure TPaste.ExecuteMacro;
begin
    macroEngine.ExecuteMacro;
    PasteMode := MacroPasteMode;
end;





procedure TPaste.SendBACKSPACE;
begin
    FrmDebug.AppendLog('sending back');
    keybd_event(VK_BACK , 0, 0, 0);
    sleep(30);

    keybd_event(VK_BACK, 0, KEYEVENTF_KEYUP, 0);
    sleep(10);
    FrmDebug.AppendLog('sent back');
end;
procedure TPaste.SendEND;
begin
    FrmDebug.AppendLog('sending end');
    keybd_event(VK_END  , 0, 0, 0);
    sleep(30);

    keybd_event(VK_END, 0, KEYEVENTF_KEYUP, 0);
    sleep(10);
    FrmDebug.AppendLog('sent end');
end;
procedure TPaste.SendHOME;
begin
    FrmDebug.AppendLog('sending VK_HOME');
    keybd_event(VK_HOME  , 0, 0, 0);
    sleep(30);

    keybd_event(VK_HOME, 0, KEYEVENTF_KEYUP, 0);
    sleep(10);
    FrmDebug.AppendLog('sent VK_HOME');
end;
procedure TPaste.SendDOWN;
begin
     FrmDebug.AppendLog('sending VK_DOWN');
    keybd_event(VK_DOWN   , 0, 0, 0);
    sleep(30);

    keybd_event(VK_DOWN, 0, KEYEVENTF_KEYUP, 0);
    sleep(10);
    FrmDebug.AppendLog('sent VK_DOWN');
end;
procedure TPaste.SendDOWNSpecial;
var lwindown, rwindown : boolean;


begin
    FrmDebug.AppendLog('sending VK_DOWN');

    lwindown := SetKeyUp(VK_LWIN);
    rwindown := SetKeyUp(VK_RWIN);

    keybd_event(VK_DOWN, 0, 0, 0);
    sleep(10);
    keybd_event(VK_DOWN, 0, KEYEVENTF_KEYUP, 0);
    sleep(10);
    if lwindown then SetKeyUp(VK_LWIN, false);
    if rwindown then SetKeyUp(VK_RWIN, false);


    FrmDebug.AppendLog('sent VK_DOWN');
end;
procedure TPaste.SendLEFT;
begin
  FrmDebug.AppendLog('sending VK_LEFT');
    keybd_event(VK_LEFT   , 0, 0, 0);
    sleep(30);

    keybd_event(VK_LEFT, 0, KEYEVENTF_KEYUP, 0);
    sleep(10);
    FrmDebug.AppendLog('sent VK_LEFT');
end;
procedure TPaste.SendRIGHT;
begin
   FrmDebug.AppendLog('sending VK_RIGHT');
    keybd_event(VK_RIGHT   , 0, 0, 0);
    sleep(30);

    keybd_event(VK_RIGHT, 0, KEYEVENTF_KEYUP, 0);
    sleep(10);
    FrmDebug.AppendLog('sent VK_RIGHT');
end;
procedure TPaste.SendUP;
begin
   FrmDebug.AppendLog('sending VK_UP');
    keybd_event(VK_UP   , 0, 0, 0);
    sleep(30);

    keybd_event(VK_UP, 0, KEYEVENTF_KEYUP, 0);
    sleep(10);
    FrmDebug.AppendLog('sent VK_UP');
end;
procedure TPaste.SendESC;
begin
    FrmDebug.AppendLog('sending ESC');
    keybd_event(VK_ESCAPE  , 0, 0, 0);
    sleep(150);

    keybd_event(VK_ESCAPE, 0, KEYEVENTF_KEYUP, 0);
    sleep(30);
end;

procedure TPaste.ClearOnceFlags;
begin
    PasteModeOverride := pmo_None;
end;

//
// Assigned Paste Methods
//


procedure TPaste.AssignPaste(EXEName: string; method: TPasteMethod);
begin
    EXEPasteList.Values[EXEName] := IntToStr(Integer(method));
end;
function TPaste.GetPasteMethod(EXEName : string) : TPasteMethod;
var s : string;
begin
    result := PASTE_DEFAULT;
    s := EXEPasteList.Values[EXEName];

    if (s <> '') then begin
        result := TPasteMethod(StrToInt(s));
    end;
end;
function TPaste.GetDefaultPasteMethod: TPasteMethod;
begin
    result := PASTE_DEFAULT; // this case should never fire

    case PasteMode of
    pm_Mimic:
        result := PASTE_MIMIC;
    pm_ShiftIns:
        result := PASTE_SHIFT_INS;
    pm_CtrlV:
        result := PASTE_CTRL_V;
    pm_ClipboardOnly:
    result := PASTE_CLIPBOARD
    end;
end;
procedure TPaste.GetEXEPasteList(var sl : TStringList);
begin
    sl.AddStrings(EXEPasteList); 
end;







//--------------------------------------------------------------
// TMacroEngine
//--------------------------------------------------------------
// These are pseudo commands and context sensitive. They only appear
// after a [CLIPBOARDSPLIT] command.
const
    END_CLIPBOARDSPLIT = '[ENDSPLITLOOP]';
    INSERT_PIECE = '[SPLITPIECE=$i]';
constructor TMacroEngine.Create;
begin
    Paste := inPaste;
    SplitList := TStringList.Create;
    SplitIndex := 0;

    nuggets := TList.Create;

    VariableStart := TStringHash.Create;
    self.DefineVariableStart('date', pv_date);
    self.DefineVariableStart('wait', pv_wait);
    self.DefineVariableStart('prompt', pv_prompt);
    self.DefineVariableStart('popupitem', pv_popupitem);
    self.DefineVariableStart('clipbrd', pv_clipbrd);
    self.DefineVariableStart('CLIPBRDCURRENT', pv_clipbrdcurrent);
    self.DefineVariableStart('clear', pv_clear);
    self.DefineVariableStart('clearpopup', pv_clearpoup);
    self.DefineVariableStart('windowconfig', pv_windowconfig);
    self.DefineVariableStart('windowremoved', pv_windowremoved);
    self.DefineVariableStart('windowhistory', pv_windowhistory);
    self.DefineVariableStart('windowclipboard', pv_windowclipboard);
    self.DefineVariableStart('windowpermanent', pv_windowpermanent);
    self.DefineVariableStart('windowsearch', pv_windowsearch);
    self.DefineVariableStart('selectall', pv_selectall);
    self.DefineVariableStart('selectleft', pv_selectleft);
    self.DefineVariableStart('selectright', pv_selectright);
    self.DefineVariableStart('windowpastesel', pv_windowpastesel);
    self.DefineVariableStart('clipboardonly', pv_clipboardonly);

    self.DefineVariableStart('left', pv_left);
    self.DefineVariableStart('right', pv_right);
    self.DefineVariableStart('up', pv_up);
    self.DefineVariableStart('down', pv_down);
    self.DefineVariableStart('back', pv_backspace);
    self.DefineVariableStart('home', pv_home);
    self.DefineVariableStart('end', pv_end);
    self.DefineVariableStart('tab', pv_tab);
    self.DefineVariableStart('del', pv_delete);
    self.DefineVariableStart('insert', pv_insert);
    self.DefineVariableStart('space', pv_space);
    self.DefineVariableStart('enter', pv_enter);
    self.DefineVariableStart('key', pv_key);
    self.DefineVariableStart('run', pv_run);
    self.DefineVariableStart('endrun', pv_endrun);

    {
    self.DefineVariableStart('left', pv_left);
    self.DefineVariableStart('right', pv_right);
    self.DefineVariableStart('up', pv_up);
    self.DefineVariableStart('down', pv_down);
    self.DefineVariableStart('back', pv_backspace);
    self.DefineVariableStart('home', pv_home);
    self.DefineVariableStart('end', pv_end);
    self.DefineVariableStart('tab', pv_tab);
    self.DefineVariableStart('del', pv_delete);
    self.DefineVariableStart('insert', pv_insert);
    self.DefineVariableStart('space', pv_space);
    self.DefineVariableStart('enter', pv_enter);
    self.DefineVariableStart('key', pv_key);
    self.DefineVariableStart('run', pv_run);
    self.DefineVariableStart('endrun', pv_endrun);  }


    self.DefineVariableStart('copy', pv_copy);
    self.DefineVariableStart('cut', pv_cut);
    Self.DefineVariableStart('clipboardfind', pv_clipboardfind);
    Self.DefineVariableStart('deleteclip', pv_deletepopupclip);
    self.DefineVariableStart('trimclipboard', pv_trimclipboard);
    self.DefineVariableStart('clipboardupper', pv_clipboardupper);
    self.DefineVariableStart('clipboardlower', pv_clipboardlower);
    self.DefineVariableStart('clipboardcapwords', pv_clipboardcapwords);
    self.DefineVariableStart('clipboardinverse', pv_clipboardinverse);
    self.DefineVariableStart('copywait', pv_copywait);
    self.DefineVariableStart('pushclipboard', pv_pushclipboard);
    self.DefineVariableStart('popfirst', pv_popfirst);
    self.DefineVariableStart('poplast', pv_poplast);
    self.DefineVariableStart('toclipboard', pv_toclipboard);
    self.DefineVariableStart('pastedefault', pv_pastedefault);
    self.DefineVariableStart('moveclip', pv_moveclip);
    self.DefineVariableStart('waitforclip', pv_waitforclip);
    self.DefineVariableStart('totextfile', pv_totextfile);
    self.DefineVariableStart('clipboardsplit', pv_clipboardsplit);
    self.DefineVariableStart('splitpiece', pv_splititem);
    self.DefineVariableStart('splitloop', pv_splitloop);
    self.DefineVariableStart('saveclipboard', pv_saveclipboard);
    self.DefineVariableStart('newclip', pv_newclip);



end;
destructor TMacroEngine.Destroy;
begin
    MyFree(SplitList);
    ClearNuggets;
end;
procedure TMacroEngine.NestedModeOn;
begin
    fNestedMode := true;
end;
procedure TMacroEngine.pushPasteFlags;
begin
    SavedPasteModeOverride := Paste.PasteModeOverride;
end;
procedure TMacroEngine.popPasteFlags;
begin
    Paste.PasteModeOverride  :=   SavedPasteModeOverride;
end;
procedure TMacroEngine.ClearNuggets;
var i : integer;
    n : TNugget;
begin
    for i := nuggets.Count-1 downto 0 do begin
        n := TNugget(nuggets.Items[i]);
        n.Free;
    end;
    nuggets.Clear;
end;
procedure TMacroEngine.ParseMacro(maintext : string);
var
    temps, literaltext, varname, vartext, datastr : string;
    i, j, startIdx : integer;
    nugget : TNugget;
    nuggettype : TPasteVariable;
    //forceclipboard : boolean;
    runmode : boolean;

    function isNewlines(str :string) : boolean;
    var c : char;
    begin
        result := false;
        for c in str do begin
            result := c in [#13,#10];
            if not result then EXIT;
        end;
    end;

    procedure DetectContextFreeCommands;
    begin
        forceClipboardOnly := false;
        if ContainsText(maintext,'[CLIPBOARDONLY]') then begin
//            Paste.ClipboardOnlyOnce := true;
            forceClipboardOnly := true;
            maintext := StringReplace(maintext, '[CLIPBOARDONLY]','',[rfIgnoreCase, rfReplaceAll]);
        end;
        if ContainsText(maintext,'[MIMIC]') then begin
            MacroMimic := true;
            maintext := StringReplace(maintext, '[MIMIC]','',[rfIgnoreCase, rfReplaceAll]);
        end;
        MacroNoMonitoring := false;
        if ContainsText(maintext,'[DISABLEMONITORING]') then begin
            MacroNoMonitoring := true;
            maintext := StringReplace(maintext, '[DISABLEMONITORING]','',[rfIgnoreCase, rfReplaceAll]);
            MonitoringState := frmClipboardManager.GetMonitoring;
            frmClipboardManager.SetDisableMonitoring(true);
        end;
    end;
    procedure HandleVariable(varname, vartext : string);
    var
        holder : string;
        i : integer;

    begin
        // preceeding text is alway a litteral
        if literaltext <> '' then begin
            nugget := TNugget.Create;
            nugget.vartype := pv_NONE;
            nugget.vartext := literaltext;
            literaltext := '';
            nuggets.Add(nugget);
        end;

        nuggettype := self.VariableType(varname);

        if (nuggettype in TRepeatedKeysVariables) then begin
            // handle items with an "=X" tail number
            datastr := TokenString(vartext, '=', false);
            datastr := vartext;

            nugget := TNugget.Create;
            nugget.vartype := nuggettype;
            nugget.vartext := '';
            nugget.varvalue := 1;
            try
                if (datastr<>'') then begin
                    nugget.varvalue := StrToInt(datastr);
                end;
            except
            end;
        end else begin
            // handle the insert variables and convert to literal text
            case nuggettype of
            pv_clipbrd: begin
                nugget := TNugget.Create;
                nugget.vartype := pv_INSERTDAT;
                nugget.vartext := MacroClipboardState;
            end;
            pv_copy: begin
                TokenString(vartext, '"', false);
                datastr := TokenStringStuffed(vartext, '"', false);
                nugget := TNugget.Create;
                nugget.vartype := nuggettype;
                nugget.varvalue := integer(COPY_NORMAL);
                if datastr = 'PREPEND' then begin
                    nugget.varvalue := integer(COPY_PREPEND);
                end else if datastr = 'APPEND' then begin
                    nugget.varvalue := integer(COPY_APPEND);
                end else if datastr = 'PLAIN' then begin
                    nugget.varvalue := integer(COPY_PLAIN);
                end else if datastr = 'FORMAT' then begin
                    nugget.varvalue := integer(COPY_FORMAT);
                end;
            end;
            pv_date: begin  //DATE="mm/yy/ddd"
                TokenString(vartext, '"', false);
                datastr := TokenStringStuffed(vartext, '"', false);
                nugget := TNugget.Create;
                nugget.vartype := pv_INSERTDAT;
                nugget.vartext := '';
                try
                    nugget.vartext := FormatDateTime(datastr, now);
                except
                end;
            end;
            pv_POPUPITEM: begin   //POPUPITEM=X
                datastr := TokenString(vartext, '=', false);
                datastr := vartext;
                //datastr := TokenString(vartext, ']"', false);
                nugget := TNugget.Create;
                nugget.vartype := pv_INSERTDAT;
                nugget.vartext := '';
                try
                    nugget.varvalue := StrToInt(datastr);
                    if nugget.varvalue < ClipQueue.GetQueueCount then
                        nugget.vartext := ClipQueue.GetItemText( nugget.varvalue );
                except
                end;
            end;
            pv_splitloop: begin
                i := Pos(END_CLIPBOARDSPLIT,Uppercase(temps));
                if i > 0 then begin
                    datastr := MidStr(temps,1,i-1);
                    delete(temps,1,i-1);
                    delete(temps,1,length(END_CLIPBOARDSPLIT));
                end else begin
                    datastr := temps;
                    temps := '';
                end;
                nugget := TNugget.Create;
                nugget.vartype := pv_splitloop;
                nugget.vartext := vartext;
                nugget.varnested := datastr;
            end
            else
                begin
                    nugget := TNugget.Create;
                    nugget.vartype := nuggettype;
                    nugget.vartext := vartext;
                end;
            end;
        end;
        nuggets.Add(nugget);
    end;
    procedure HandleLitteral(var literaltext : string; isLast : boolean);
    begin
        if not IsLast then begin
            literaltext := literaltext + '['; // add back removed
        end;

        if literaltext <> '' then begin
            if self.IgnoreNewLines and isNewlines(literaltext) then begin
            end else begin
                nugget := TNugget.Create;
                nugget.vartype := pv_NONE;
                nugget.vartext := literaltext;
                literaltext := '';
                nuggets.Add(nugget);
            end;
        end;

        // litterals sent to the clipboard do not require focus
        fMacroRequiresFocus := fMacroRequiresFocus or not (Paste.GetClipboardOnlyOnce or Paste.GetClipboardOnly);
    end;
    function posOfNonAlpha(str : string) : integer;
    begin
        result := 0;
        while (result<Length(str)) and
            CharInSet(LowerCase(str[result+1])[1], ['a'..'z']) do
            Inc(result);
    end;
begin
    HasEndRun := 0;
    RunMode := false;
    MacroMimic := false;
    DetectContextFreeCommands;

    //
    // turn remaining text into a list of nuggets commands
    //
    self.ClearNuggets;
    temps := maintext;
    MacroClipboardState := Clipboard.AsText;
    literaltext := '';
    fMacroRequiresFocus := false;

    while temps <> '' do begin
        // text ahead of the '[' is always litteral
        if (temps[1]<>'[') then begin
            literaltext := TokenString(temps,'[', false);
        end else begin
            TokenString(temps,'[', false);
            literaltext := '';
        end;

        varname := Copy(temps,1, posOfNonAlpha(temps));
        if self.IsVariable(varname) then begin
            if literaltext <> '' then begin
                HandleLitteral(literaltext, true);
            end;
            nuggettype := VariableType(varname);
            if (nuggettype = pv_endrun) then begin
                Inc(HasEndRun);
            end;
            if (nuggettype = pv_run) then begin
                runmode := true;
                if not ContainsText(temps,'[endrun]') then begin
                    temps := temps + '[endrun]';
                end;
            end else if (nuggettype = pv_end) then begin
                runmode := false;
            end;

            if (nuggettype = pv_windowsearch) and AnsiStartsText('windowsearch="', temps) then begin
                vartext := TokenString(temps,'"]', false) + '"';
            end else begin
                vartext := TokenString(temps,']', false);
            end;
            HandleVariable(varname, vartext);

            if forceClipboardOnly then begin
                fMacroRequiresFocus := fMacroRequiresFocus or not (nuggettype in TNoFocusClipboardOnlyMode)
            end else begin
                fMacroRequiresFocus := fMacroRequiresFocus or not (nuggettype in TNoFocusVariables)
            end;
        end else begin
            // '[' and following may also be litteral if it's not a variable
            HandleLitteral(literaltext, temps='');
        end;
    end;
end;
procedure TMacroEngine.ExecuteMacro;
const
    SLEEPMS = 10;
    DEFAULT_WAIT_MS = 2000;
var
    ShowSearchWindow : boolean;
    pastestr : string;
    prompts : TStringList;
    i : integer;
    nugget : TNugget;
    nuggettype : TPasteVariable;
    StartRun : boolean;
    RunStr : string;
    foreground : THandle;
    RequiresFocusBack : boolean;

    varValue : string;

    function IsRunMode : boolean;
    begin
        result := StartRun and (HasEndRun<>0);
    end;

    function url_encode(url : string) : string;
    var
    	i: integer;
    begin
        result:='';
        for i := 1 to length(url) do begin
        case url[i] of
        'a'..'z','A'..'Z','0'..'9','/','.','&','-' :
        	result:=result+ url[i];
        else
        	result := result+'%'+uppercase(inttohex(ord(url[i]),2));
        end;
        end;
    end;
    procedure PreKey(pastestr : string; varType : TPasteVariable = pv_NONE);
    begin
        if pastestr <> '' then begin
            if IsRunMode then begin
                if AnsiStartsText('http:', RunStr) and (varType = pv_INSERTDAT) then begin
                    RunStr := RunStr + url_encode(pastestr);
                end else begin
                    RunStr := RunStr + pastestr;
                end;
            end else begin
                frmClipboardManager.IgnoreClipboardOnce;
//                Paste.PushFlags;
                if MacroMimic then Paste.SetKeyboardMimicOnce;
                if RequiresFocusBack then begin
                    RequiresFocusBack := false;
                    TFocusManager.ForceForeground(foreground);
                end;
                Paste.SendText(pastestr);
//                Paste.PopFlags;
                frmClipboardManager.ClearIgnoreClipboardOnce;

                // sleep is not needed, Paste object has a predefined paste delay
                //sleep(SLEEPMS);
            end;
        end;
    end;
    procedure PostKey;
    begin
        // TODO: Rethink this
        //Application.ProcessMessages;
        pastestr := '';
    end;
    procedure ExecuteNugget(var i : integer; nugget : TNugget; nuggets : TList);
    var
        pastestr, temptoken, temptoken2, before : string;
        j, seq, k, listIndex : Integer;
        h : THandle;
        plist : array of string;
        type TKeystrokeProc = procedure of object;
        procedure executeKeystroke(proc : TKeystrokeProc; count : integer);
        var i : integer;
        begin
            for i := 0 to count-1 do begin
                proc();
            end;
        end;

        procedure InitWaitForClip;
        begin
            seq := Windows.GetClipboardSequenceNumber;
        end;
        function WaitForClip(j : integer) : boolean;
        var
            x : integer;
        begin
            k := 0;

            repeat
                Application.ProcessMessages; // give the clipboard a chance to work
                inc(k, 100);
                sleep(100);
                x :=   Windows.GetClipboardSequenceNumber;
            until not (x =seq) or
                not (k < j);

            result :=  not (x = seq);
        end;
        procedure SplitClipboard(separator : string; metavariables : boolean);
        var
            s : string;
            vardata : string;
        begin
            SplitList.Clear;

            if metavariables then begin
                separator := StringReplace(separator,'\t', #9, [rfReplaceAll, rfIgnoreCase]);
                separator := StringReplace(separator,'\n', #13#10, [rfReplaceAll, rfIgnoreCase]);
            end;

            s := Clipboard.AsText;
            while (s <> '') do begin
                SplitList.Add(TokenString(s, separator));
            end;
        end;

    var
        ci : TClipItem;
        me : TMacroEngine;
        isSplitMode : boolean;
        idx : integer;
    begin
        case nugget.vartype of
        pv_NONE, pv_INSERTDAT: {$region 'pv_none'}
            begin
                // literal text -- concat all text nuggets into a single item
                // TODO:
                // pv_clipbrdcurrent is always them same until a keystroke/command is executed.
                // This is a BUG, but is better left as-is until a proper fix
                // is thought of
                pastestr := nugget.vartext;
                if not IsRunMode then begin
                    while (i+1 < nuggets.count) and
                        (TNugget(nuggets[i+1]).vartype in [pv_NONE,pv_INSERTDAT, pv_clipbrdcurrent, pv_clipbrd]) do begin
                        Inc(i);
                        nugget := TNugget(nuggets[i]);

                        if nugget.vartype = pv_clipbrdcurrent then begin
                            pastestr := pastestr + Clipboard.AsText;
                        end else begin
                            pastestr := pastestr + nugget.vartext;
                        end;
                    end;
                end else begin
                    // run mode may need to URL encode the CLIPBOARD separately
                    // can't optimize the pasting
                    pastestr := nugget.vartext;
                end;
                PreKey(pastestr, nugget.vartype);
            end;
            {$endregion}

        pv_run:  {$region 'pv_run'}
            begin // [RUN]...[ENDRUN]
                // run all text items afterwords
                RunStr := '';
                StartRun := true;
            end;
            {$endregion}
        pv_endrun: {$region 'pv_enrun'}
            begin // [RUN]...[ENDRUN]

                Dec(HasEndRun);
                StartRun := false;

                foreground := Windows.GetForegroundWindow;
                FrmDebug.AppendLog('ENDRUN - program=' + UnitMisc.WindowHandleToEXEName(foreground));
                UnitMisc.ShellExecute(FrmMainPopup.Handle, RunStr);

                FrmDebug.AppendLog('ENDRUN - foreground=' + UnitMisc.WindowHandleToEXEName(Windows.GetForegroundWindow));
                if (foreground <> Windows.GetForegroundWindow) then begin
                    RequiresFocusBack := UnitMisc.IsHiddenCommandPrompt(RunStr);
                    FrmDebug.AppendLog('ENDRUN - requiresfocusback='+BoolToStr(RequiresFocusBack));
                end;
            end;
            {$endregion}
        pv_wait:  {$region 'pv_wait'}
            begin //WAIT or WAIT=100
            j := 100;
            if ContainsText(nugget.vartext, '=') then begin
                TokenString(nugget.vartext,'=');
                temptoken := TokenString(nugget.vartext,']');
                try
                    j := StrToInt(temptoken);
                except
                end;
            end;
            sleep(j);
            end;
            {$endregion}
        pv_waitforclip: {$region 'pv_waitforclip'}
            begin
                j := DEFAULT_WAIT_MS;
                if ContainsText(nugget.vartext, '=') then begin
                    TokenString(nugget.vartext,'=');
                    temptoken := TokenString(nugget.vartext,']');
                    try
                        j := StrToInt(temptoken);
                    except
                    end;
                end;
                InitWaitForClip;
                WaitForClip(j);
            end;
            {$endregion}
        pv_totextfile: {$region 'pv_totextfile'}
            begin
                TokenString(nugget.vartext,'"');
                temptoken := TokenStringStuffed(nugget.vartext,'"');

                if FileExists(temptoken) then begin
                    k := 0;
                    while FileExists(temptoken+'.bak'+IntToStr(k)) do begin
                         inc(k);
                    end;
                    RenameFile(temptoken, temptoken+'.bak'+IntToStr(k))
                end;


                with TStreamWriter.Create(temptoken, false, TEncoding.Unicode) do begin
                    Write(clipboard.astext);
                    Free();
                end;
            end;
            {$endregion}
        pv_prompt: {$region 'pv_prompt'}
            begin    //PROMPT="Prompt Text"
            mysleep(100);
            TokenString(nugget.vartext,'"');
            temptoken := TokenStringStuffed(nugget.vartext,'"');
            pastestr := prompts.Values[LowerCase(temptoken)];
            if pastestr = '' then begin
                h := GetForegroundWindow;
                TFocusManager.ForceForeground(FrmMainPopup.Handle);
                SetLength(plist, ClipQueue.GetQueueCount);
                for j := 0 to ClipQueue.GetQueueCount-1 do begin
                    plist[j] := ClipQueue.GetClipItem(j).GetAsPlaintext;
                end;
                if InputDropdown('ArsClip Prompt', temptoken, plist, pastestr, listIndex ) then begin
                    if (listIndex <> -1) then begin
                        pastestr := ClipQueue.GetClipItem(listIndex).getDisplayText;
                    end else begin

                    end;
                end else begin
                    pastestr := '';
                end;
                if h<>0 then TFocusManager.ForceForeground(h);

                prompts.Values[LowerCase(temptoken)] := pastestr;
            end;
            if pastestr <> '' then begin
                PreKey(pastestr);
            end;
        end;
        {$endregion}
        pv_clipboardfind: {$region 'pv_clipboardfind'}
            begin //CLIPBOARDFIND="" REPLACE=""
                // detect \n or \t
                TokenString(nugget.vartext, '"', false);
                temptoken := TokenStringStuffed(nugget.vartext, '"', false);
                TokenString(nugget.vartext, '"', false);
                temptoken2 := TokenStringStuffed(nugget.vartext, '"', false);

                pastestr := Clipboard.AsText;
                before := pastestr;
                // auto-detect Unix style or Windows style linefeeds
                if ContainsStr(temptoken,'\n') then begin
                    if ContainsStr(pastestr, #10) then begin
                        if ContainsStr(pastestr, #13) then begin
                            temptoken := StringReplace(temptoken,'\n',#13#10,[rfReplaceAll, rfIgnoreCase]);
                        end else begin
                            temptoken := StringReplace(temptoken,'\n',#10,[rfReplaceAll, rfIgnoreCase]);
                        end;
                    end;
                end;
                temptoken2 := StringReplace(temptoken2,'\t', #9, [rfReplaceAll, rfIgnoreCase]);
                temptoken2 := StringReplace(temptoken2,'\n', #13#10, [rfReplaceAll, rfIgnoreCase]);

                pastestr := StringReplace(pastestr,temptoken,temptoken2,[rfReplaceAll, rfIgnoreCase]);

                if before <> pastestr then
                    Clipboard.SetTextBuf(@pastestr[1]);
            end;
        {$endregion}
        pv_clipboardupper: {$region 'pv_clipboardupper'}
        begin
            pastestr := UpperCase(Clipboard.AsText);
            Clipboard.SetTextBuf(@pastestr[1]);
        end;
        {$endregion}
        pv_clipboardlower: {$region ''}begin
            before := Clipboard.AsText;
            pastestr := LowerCase(before);
            if before <> pastestr then
                Clipboard.SetTextBuf(@pastestr[1]);
        end;
        {$endregion}
        pv_clipboardcapwords: {$region ''}begin
            before := Clipboard.AsText;
            pastestr := UnitToken.CapitalizeWords(before);
            if before <> pastestr then
                Clipboard.SetTextBuf(@pastestr[1]);
        end;
        {$endregion}
        pv_clipboardinverse: {$region ''}begin
            pastestr := Clipboard.AsText;
            before := pastestr;
            for j := 1 to length(pastestr) do begin
                if TCharacter.IsUpper(pastestr[j]) then begin
                    pastestr[j] := TCharacter.ToLower(pastestr[j]);
                end else begin
                    pastestr[j] := TCharacter.ToUpper(pastestr[j]);
                end;
            end;
            if before <> pastestr then
                Clipboard.SetTextBuf(@pastestr[1]);
        end;
        {$endregion}
        pv_popupitem: begin end;
        pv_deletepopupclip: {$region 'pv_deletepopupclip'}begin //
            TokenString(nugget.vartext, '=');
            try
                j := StrToInt(nugget.vartext);
                if j < ClipQueue.GetQueueCount then begin
                    ClipQueue.DeleteItem(j);
                end;
            except
            end;
        end; {$endregion}
        pv_key: begin {$region ''}  //KEY="datahere"
            TokenString(nugget.vartext, '"');
            pastestr := TokenStringStuffed(nugget.vartext, '"');
            Paste.SendKeyCustom(pastestr);
        end;  {$endregion}
        pv_clipbrd: begin {$region ''}
            pastestr := nugget.vartext;
            PreKey(pastestr);
        end; {$endregion}
        pv_clipbrdcurrent: {$region ''}begin
            sleep(10); // breathing room for any previous COPY command
            pastestr := clipboard.AsText;
            PreKey(pastestr);
        end;  {$endregion}
        pv_trimclipboard: {$region ''}begin
            frmClipboardManager.IgnoreClipboardOnce;  // TODO: Is this needed?
            before := Clipboard.AsText;
            pastestr := Trim(before);
            if before <> pastestr then
                Clipboard.SetTextBuf(@pastestr[1]);
        end;  {$endregion}
        pv_clear: frmClipboardManager.ClearClipboard(true);
        pv_clearpoup: ClipQueue.ClearQueue;
        pv_windowconfig: {$region ''}begin
            FrmConfig.Show;
            TFocusManager.ForceForeground(FrmConfig.Handle);
        end; {$endregion}
        pv_windowremoved: {$region ''}begin
            frmEditHistory.ShowRemoved;
            TFocusManager.ForceForeground(frmEditHistory.Handle);
        end;  {$endregion}
        pv_windowhistory: {$region ''}begin
            frmEditHistory.ShowRemoved;
            TFocusManager.ForceForeground(frmEditHistory.Handle);
        end; {$endregion}
        pv_windowclipboard: {$region ''}begin
            frmEditTextExternal.EditClipboard;
        end;   {$endregion}
        pv_windowpermanent: {$region ''}begin
            frmEditHistory.ShowPermanent;
            TFocusManager.ForceForeground(frmEditHistory.Handle);
        end; {$endregion}
        pv_windowsearch: {$region ''}begin
            if ContainsText(nugget.vartext, '="') then begin
                TokenString(nugget.vartext,'="');
                temptoken := TokenString(nugget.vartext,'"');
                if temptoken<>'' then begin
                    temptoken := StringReplace(temptoken,'[CLIPBRDCURRENT]',clipboard.AsText,[rfIgnoreCase,rfReplaceAll]);
                    temptoken := StringReplace(temptoken,'[CLIPBRD]',MacroClipboardState,[rfIgnoreCase,rfReplaceAll]);
                    frmsearch.txtFind.Text := temptoken;
                end;
            end;
            FrmSearch.ShowAutomatted;
        end;   {$endregion}
        pv_windowpastesel: FrmPasteSelected.ShowaAutomated;
        pv_selectall:   Paste.SendCTRL_A;
        pv_selectleft:  executeKeystroke(Paste.SendCTRL_SHIFT_LEFT, nugget.varvalue);
        pv_selectright: executeKeystroke(Paste.SendCTRL_SHIFT_RIGHT, nugget.varvalue);
        pv_left:        executeKeystroke(Paste.SendLEFT, nugget.varvalue);
        pv_right:       executeKeystroke(Paste.SendRIGHT, nugget.varvalue);
        pv_up:          executeKeystroke(Paste.SendUP, nugget.varvalue);
        pv_down:        executeKeystroke(Paste.SendDOWN, nugget.varvalue);
        pv_backspace:   executeKeystroke(Paste.SendBACKSPACE, nugget.varvalue);
        pv_home:        executeKeystroke(Paste.SendHOME, nugget.varvalue);
        pv_end:         executeKeystroke(Paste.SendEND, nugget.varvalue);
        pv_tab:         executeKeystroke(Paste.SendTAB, nugget.varvalue);
        pv_delete:      executeKeystroke(Paste.SendDELETE, nugget.varvalue);
        pv_insert:      Paste.SendINSERT;
        pv_space:       executeKeystroke(Paste.SendSPACE, nugget.varvalue);
        pv_enter:       executeKeystroke(Paste.SendENTER, nugget.varvalue);
        pv_cut:         Paste.SendCTRL_X;
        pv_copy: begin  {$REGION 'COPY'}
            case TCopyFunction(nugget.varvalue) of
            COPY_NORMAL: begin
                Paste.SendCTRL_C;
            end;
            COPY_PREPEND, COPY_APPEND: begin
                InitWaitForClip;
                temptoken := Clipboard.AsText;
                Paste.SendCTRL_C;
                if WaitForClip(DEFAULT_WAIT_MS) then begin
                    case TCopyFunction(nugget.varvalue) of
                    COPY_PREPEND: Clipboard.SetTextBuf(PChar(Clipboard.AsText + temptoken));
                    COPY_APPEND: Clipboard.SetTextBuf(PChar(temptoken + Clipboard.AsText));
                    end;
                end;
            end;
            COPY_PLAIN: begin
                InitWaitForClip;
                Paste.SendCTRL_C;
                if WaitForClip(DEFAULT_WAIT_MS) then begin
                    ci := ClipQueue.GetClipSafe(0);
                    if (ci<>nil) then begin
                        Clipboard.SetTextBuf(PChar(ci.GetAsPlaintext));
                    end;
                end;
            end;
            COPY_FORMAT: begin
                InitWaitForClip;
                Paste.SendCTRL_C;
                if WaitForClip(DEFAULT_WAIT_MS) then begin
                    ci := ClipQueue.GetClipSafe(0);
                    if (ci<>nil) then begin
                        Paste.SetClipboardOnlyOnce;
                        Paste.SendText('',ci,true);
                        Paste.ClearOnceFlags;
                    end;
                end;
            end;
            end;
            {$ENDREGION}
        end;
        pv_copywait: {$region ''}begin
            j := 2000; //default wait time
            if ContainsText(nugget.vartext, '=') then begin
                TokenString(nugget.vartext,'=');
                temptoken := TokenString(nugget.vartext,']');
                try
                    j := StrToInt(temptoken);
                except
                end;
            end;

            // send copy keystroke
            // wait for new clip signal or timer to run out
            k := 0;
            seq := frmClipboardManager.GetSequenceCode;
            Paste.SendCTRL_C;

            while (frmClipboardManager.GetSequenceCode=seq) and
                (k < j) do begin
                Application.ProcessMessages; // give the clipboard a chance to work
                inc(k, 100);
                sleep(100);
            end;
        end; {$endregion}
        pv_pushclipboard: MacroClipboardList.Push;
        pv_popfirst: MacroClipboardList.PopFirst;
        pv_poplast: MacroClipboardList.PopLast;
        pv_toclipboard: {$region 'TOCLIPBOARD'}
            begin
                TokenString(nugget.vartext,'=');
                temptoken := TokenString(nugget.vartext,']');
                try
                    j := StrToInt(temptoken);
                    if j < ClipQueue.GetQueueCount then begin
                        Paste.PutClipOnCLipboard(ClipQueue.GetClipItem(j));
                    end;
                except
                end;
            end;  {$endregion}
        pv_moveclip:  {$region ''}
            begin   //MOVECLIP="0" TO="1"
                TokenString(nugget.vartext, '"', false);
                temptoken := TokenString(nugget.vartext, '"', false);
                TokenString(nugget.vartext, '"', false);
                temptoken2 := TokenStringStuffed(nugget.vartext, '"', false);

                try
                    j := StrToInt(temptoken);
                    k := StrToInt(temptoken2);

                    ClipQueue.Move(j, k);
                except
                end;
            end; {$endregion}
        pv_pastedefault: {$region ''}
        begin
            if (forceClipboardOnly) then begin
                Paste.PopPasteMode;
            end;
            Paste.HandlePasteMethod;
            if (forceClipboardOnly) then begin
                Paste.ClearMethods;
                Paste.SetClipboardOnly;
            end;
        end;  {$endregion}
        pv_clipboardsplit: {$region 'split'}
        begin
            TokenString(nugget.vartext,'"');
            varValue := TokenStringStuffed(nugget.vartext,'"');
            SplitClipboard(varValue, true);
            if (SplitList.Count <= 1) and (temptoken <> nugget.vartext) then begin
                // just in case the \n or \t was a litteral
                SplitClipboard(nugget.vartext, false);
            end;
        end;
        {$endregion}
        pv_splitloop: {$region 'splitloop'}begin
            me := TMacroEngine.Create(self.Paste);
            me.NestedModeOn;

            TokenString(nugget.vartext, '"');
            varValue := TokenStringStuffed(nugget.vartext, '"');;

            temptoken2 := ReplaceText(INSERT_PIECE, '$i', varValue);

            for idx := 0 to SplitList.Count-1 do begin
                temptoken := ReplaceText(nugget.varnested, temptoken2, SplitList[idx]);
                if (temptoken <> '') then begin
                    me.ParseMacro(temptoken);
                    me.ExecuteMacro;
                end;
            end;
            MyFree(me);
        end;
        {$endregion}
        pv_splititem: {$region 'splitpiece'}
        begin
            TokenString(nugget.vartext, '=');
            varValue := TokenString(nugget.vartext, ']');;

            PreKey(SplitList[StrToInt(varValue)]);
        end;
        {$endregion}
        pv_saveclipboard: {$region 'saveclipboard'}
        begin
            ci := TClipITem.Create;
            ci.GetClipboardItem(0,CF_UNICODETEXT);
            ClipQueue.InsertAtStart(ci);
        end;
        {$endregion}
        pv_newclip: {$region 'newclip'}
        begin
            TokenString(nugget.vartext,'"');
            tempToken := TokenStringStuffed(nugget.vartext,'"',false);

            ci := TClipITem.Create;
            ci.SetFromPlainText(temptoken);
            ClipQueue.InsertAtStart(ci);
        end;
        {$endregion}
        end;
    end;
begin
    if not self.fNestedMode then begin
        self.pushPasteFlags;
    end;
    if self.forceClipboardOnly then begin
        Paste.PushPasteMode;
        Paste.ClearMethods;
        Paste.SetClipboardOnly;
    end;
    prompts := TStringList.Create;
    ShowSearchWindow := false;
    StartRun := false;
    RunStr := '';
    foreground := 0;
    RequiresFocusBack := false;
    try
        //
        // Execute nugget commands
        //
        i := 0;
        while i < nuggets.Count do begin
            nugget := TNugget(nuggets.items[i]);
            ExecuteNugget(i, nugget, nuggets);
            PostKey;
            inc(i);
        end;
    finally
        if MacroNoMonitoring then begin
            if MonitoringState <> frmClipboardManager.GetMonitoring then begin
                frmClipboardManager.SetDisableMonitoring(not MonitoringState);
            end;
        end;

        if not fNestedMode then begin
            self.popPasteFlags;
            MacroClipboardList.Clear;
        end;
        if (self.forceClipboardOnly) then begin
            Paste.PopPasteMode;
        end;
    end;
end;
function TMacroEngine.MacroRequiresFocus : boolean;
begin
    result :=  fMacroRequiresFocus;
end;
procedure TMacroEngine.DefineVariableStart(starttext : string; vartype : TPasteVariable);
begin
    if VariableStart.ValueOf(LowerCase(starttext)) = -1 then begin
        VariableStart.Add(LowerCase(starttext), Integer(vartype));
    end;
end;
function TMacroEngine.IsVariable(starttext : string) : boolean;
begin
    result := VariableStart.ValueOf(LowerCase(starttext)) <> -1;
end;
function TMacroEngine.VariableType(starttext : string) : TPasteVariable;
var i : integer;
begin
    result := pv_NONE;
    i := VariableStart.ValueOf(LowerCase(starttext));
    if i <> -1 then
        Result := TPasteVariable(i);
end;

initialization
begin
    Paste := TPaste.Create;
    MacroClipboardList := TMacroClipboardList.Create;
end;
finalization
begin
    MyFree(MacroClipboardList);
    MyFree(Paste);
end;
end.
