{
    This unit displays the main popup menu when a hotkey is pressed or
    Mouse Button Trigger

    Detects global hotkeys
}


unit UnitFrmMainPopup;

interface
uses

  UnitFolderWriteTest{},
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, Clipbrd, Menus, StrUtils, ShellAPI, ExtCtrls, Buttons,
  IniFiles {for hashed}, {UnitTooltipPopup, } UnitFrmConfig,
    UnitClipQueue, UnitTWideChar, UnitFramePopup,
   UnitFrameACPopup, UnitFrmACPopup, Vcl.Imaging.pngimage, System.Generics.collections;


  type
  TTargetProgramData = record
    icon : HICON;
    ExeName : string;
    x : integer;
    y : integer;
    SelectCompatible : boolean;
    SelectedText : string;
    ForegroundWindow : cardinal;
    ForegroundWindowClassname : string;
    FocusControl : cardinal;
    FocusControlClassname : string;
    DropFilesCompatible : boolean;
  end;

  type
  TFrmMainPopup = class(TForm)
    btnHide: TButton;
    imgA: TImage;
    imgRightArrow: TImage;
    imgIcon: TImage;
    imgFull: TImage;
    imgPasteAll: TImage;
    imgPaste: TImage;
    imgEdit: TImage;
    imgFlush: TImage;
    imgConfMode: TImage;
    imgPermCasc: TImage;
    imgRemoved: TImage;
    timMouseHeld: TTimer;
    ImgUnicode: TImage;
    imgRichText: TImage;
    imgFile: TImage;
    imgPlain: TImage;
    imgHTML: TImage;
    imgUnkown: TImage;
    imgPic: TImage;
    ImgMisc: TImage;
    imgAICon: TImage;
    timAutoSave: TTimer;
    imgSearch: TImage;
    imgDown: TImage;
    imgUp: TImage;
    imgPasteMini: TImage;
    timJumplist: TTimer;
    timLongCtrlVPress: TTimer;
    imgSearch2: TImage;
    imgNewWindow: TImage;
    imgClipPaste: TImage;
    imgRepeatIcon: TImage;
    timSharedUpdate: TTimer;
    imgPin: TImage;
    imgPin2: TImage;
    imgSystem: TImage;
    imgMore: TImage;
    imgLess: TImage;
    imgSearchTool: TImage;

    procedure FormCreate(Sender: TObject);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
    procedure btnHideClick(Sender: TObject);
    procedure timMouseHeldTimer(Sender: TObject);
    procedure timAutoSaveTimer(Sender: TObject);
    procedure timJumplistTimer(Sender: TObject);
    procedure timLongCtrlVPressTimer(Sender: TObject);
    procedure timSharedUpdateTimer(Sender: TObject);

    {$ENDREGION}
  private
  protected
    {configuration options}
    DoRememberHistory : boolean;

    IgnoreHotkey : boolean;
    UseMousePositionOnce : boolean;
    UseNoWarningsModeOnce : boolean;
    ExplorerEditDetected : boolean;

    //  Globals for concurrency issues
    PopupShowing : boolean;
    HotkeyActive : boolean;
    PopupActive : boolean;
    fIsSendingText : boolean;

    GatherTargetWarned : boolean;


    SkipFocusReturn : boolean;
    LastHotkeyTime : cardinal;

    ACPopup : TACPopup;

    WarnedEXE : TDictionary<string,boolean>;
    procedure HandleCloseEvent;

    procedure GetTargetControl(var TargetData : TTargetProgramData);
    procedure ShowPopup(X,Y: integer);
    procedure SendText(PasteMode: integer; s : string; ci : TClipItem = nil; ForceFormatted : boolean = false);
    //
    // util functions
    //
    procedure ShowPreviewForm;
    procedure ShowPopupWithFocusRules;

    procedure CreateParams(var Params: TCreateParams); override;
    procedure WMStyleChanging(var msg : TWMStyleChanging); message WM_STYLECHANGING;
    procedure WMShow(var msg : TWMShowWindow); message WM_SHOWWINDOW;
  public
    TargetData : TTargetProgramData;
    CurrentClipboard : TClipItem;


    procedure GatherTargetWindowData(overridehandle : THandle = 0);
    procedure ShowPopupCallback(Sender : TObject);

    function GetTargetEXE : string;


    procedure ShowPreviewEditForm;
    procedure ClearCurrentLast;

    procedure SendPlainText(s : string);
    procedure SendMacro(s : string);
    procedure SendClip(ci : TClipItem; ForceFormatted : boolean = false);

    function GetNextWindow(inh : THandle=0) : THandle;
    procedure ShowOnNextWindow(ShowPopup : boolean = true);
    procedure ShowOnScratchPad(ShowPopup : boolean = true);
    procedure ShowOnClipboardBar(ShowPopup : boolean = true);
    function GetNextWindowProgramName : string;
    function PopupIsShowing : boolean;

    procedure ShowOnTaskbar;


    {configuration}
    function GetNeedsFocus : boolean;
    procedure SetRememberHistory(DoRemember: boolean);



    procedure SearchMenuItemClickEvent(Sender: Tobject);
    procedure SearchJumplistClickEvent();


    {system tray stuff/ windows messages}
    procedure WMHotKey(var Msg : TWMHotKey); message WM_HOTKEY;
    procedure WMEndSession(var msg : TWMQueryEndSession); message WM_QUERYENDSESSION;


    procedure ShowOnSystemTray(overrideh : Thandle =0);

    procedure ApplicationException(Sender: TObject; E: Exception);
    procedure NewClipboardCallback;

    procedure SkipFocusReturnOnce;
    procedure FakePopupHotkey;
    procedure JumplistDefault;

    function IsSendingText : boolean;

    property MainPopup : TACPopup read acpopup;


    procedure showCue(name, text : string);

    procedure ActivateAfterLoad;
    procedure NextWindowDebug(h : THandle);
  end;


implementation

uses UnitMisc, UnitFrmClipboardManager,
  UnitFrmPasteSelected, UnitOtherQueue, UnitKeyboardQuery,
  TLHelp32,  UnitFrmPermanentNew,
   UnitFrmSysTrayMenu, UnitPaste, Math,
  CommCtrl,
  UnitFrmEditItem, UnitMenuItemTagdata , UnitFrmSearch, UnitSound
  , UnitFrmDummyClipboardBar, UnitFrmEditTextExternal, Types,
  UnitFrmEditHistory, UnitIntegrity, UnitFrmTooltipNew,
  UnitFrmChainWatcher, UnitPopupGenerate, UnitFrmDebug, UnitJS,
  UnitClipDatabase, UnitFocusManager, UnitThreadAttach;

{$R *.dfm}

CONST PASTE_ONLY_FILE : string = 'nopaste.txt';
const HAS_SELECTED_TEXT : string = '[Has Selected Text]';


procedure TFrmMainPopup.WMShow(var msg : TWMShowWindow);
begin
    msg.Result := 0;
end;
procedure TFrmMainPopup.WMStyleChanging(var msg : TWMStyleChanging);
begin
    msg.StyleStruct.styleNew  := msg.StyleStruct.styleNew or  WS_EX_TOOLWINDOW;
    ShowWindow(self.Handle, SW_HIDE);
end;



procedure TFrmMainPopup.NextWindowDebug(h : THandle);
var h2 : THandle;
begin
    FrmDebug.AppendLog('call 1');
    h2 := GetNextWindow(h);
    FrmDebug.AppendLog('call 2');
    h2 := GetNextWindow(h2);
    FrmDebug.AppendLog('call 3');
    h2 := GetNextWindow(h2);
    FrmDebug.AppendLog('call done');
end;
procedure TFrmMainPopup.SetRememberHistory(DoRemember: boolean);
begin
    self.DoRememberHistory := DoRemember;
end;
procedure TFrmMainPopup.showCue(name, text : string);
var
    h : THandle;
    r : TRect;
    tt : TFrmTooltipNew;
begin
    if not frmConfig.getShowClipCues then EXIT;

    h := GetForegroundWindow;
    if (h=0) then
        h := GetTopWindow(0);
    windows.GetWindowRect(h, r);

    inc(r.top, 5);
    inc(r.left, 5);

    tt := TFrmTooltipNew.Create(nil);
    tt.HideHeader;
    tt.SmallFontOnce := true;
    tt.ShowTooltip('['+name+']: '+text,
        point(r.left, r.top)
    );
    tt.TimClose.Interval := 2500;
    tt.TimClose.Enabled := true;
end;



//
// currently unused Keyboard Hook
//
procedure TFrmMainPopup.timLongCtrlVPressTimer(Sender: TObject);
begin
    timLongCtrlVPress.Enabled := false;
//    ShowMessage('hello');
end;
var
    KBHook : HHOOk;
    VStartTime : boolean;
type
  tagKBDLLHOOKSTRUCT =  packed record
    vkCode :            DWORD;
    scanCode :          DWORD;
    flags :             DWORD;
    time :              DWORD;
    dwExtraInfo :       Integer;
  end;
  KBDLLHOOKSTRUCT      =  tagKBDLLHOOKSTRUCT;
  PKBDLLHOOKSTRUCT     =  ^KBDLLHOOKSTRUCT;
function KeyboardHookProc(Code: Integer; WP: WPARAM; LP : LPARAM) : LongInt; stdcall;
var msg : PKBDLLHOOKSTRUCT;
    key : word;
    ss : TShiftState;
    c : char;
    function KeyDown(c : LPARAM) : boolean;
    begin
        result := (c and $80000000) = 0;
    end;
begin
    result := 0;
    if code < 0 then begin
        result := CallNextHookEx(KBHook, code, wp, lp);
        EXIT;
    end;
    msg := pointer(lp);
    key := msg.vkCode;
    ss := KeyDataToShiftState(msg.flags);

    if (code = HC_ACTION) and
        FrmMainPopup.PopupIsShowing and (not FrmMainPopup.GetNeedsFocus)then begin

        if not (key in [VK_LWIN, VK_RWIN, VK_CONTROL, VK_MENU])
            and not ((key = Ord('V')) and (ss=[ssCtrl]))
            and not ((key = VK_INSERT) and (ss=[ssShift]))
            then begin
            case wp of
                WM_KEYDOWN:
                begin
                    FrmMainPopup.MainPopup.KeyDown(key, ss);
                end;
                WM_KEYUP:
                begin
                    FrmMainPopup.MainPopup.KeyUp(key, ss);
                    c := ConvertVitualKey(key);
                    FrmMainPopup.MainPopup.KeyPress( c );
                end;
            end;
            result := 1;
        end;
{    end else if (code = HC_ACTION) and (wp=WM_KEYDOWN) and (key = Ord('V')) and (ss=[ssCtrl] ) then begin
        FrmMainPopup.timLongCtrlVPress.Enabled := false;
        FrmMainPopup.timLongCtrlVPress.Enabled := true;
    end else if (code = HC_ACTION) and (wp=WM_KEYUP) and (key = Ord('V')) and (ss=[ssCtrl] ) then begin
        FrmMainPopup.timLongCtrlVPress.Enabled := false;}
    end;
end;


//
procedure TFrmMainPopup.CreateParams(var Params: TCreateParams);
begin
    inherited CreateParams(params);

   	//Params.Style := WS_POPUP {or WS_TABSTOP or WS_CLIPSIBLINGS};
    Params.ExStyle := WS_EX_TOOLWINDOW or WS_EX_TOPMOST {or WS_EX_LAYERED};

    params.WndParent := GetDesktopWindow;
    {WS_EX_TRANSPARENT screws up in WinXP - causing a blank redraw}
end;
procedure TFrmMainPopup.FormCreate(Sender: TObject);
begin
    imgIcon.picture.Icon := application.Icon;
    //
    // popup & menu items init
    //
    self.ShowHint := true;
    self.CurrentClipboard := TClipItem.Create;
    LastHotkeyTime := 0;

    ACPopup := TACPopup.Create(self);
    self.PopupShowing := false;

    WarnedEXE := TDictionary<string,boolean>.create;
    if self.Visible then
        self.SetFocus; // Experiment with multimonitor and dialogs
end;
procedure TFrmMainPopup.FormClose(Sender: TObject; var Action: TCloseAction);
begin
    self.HandleCloseEvent;
    if USE_POPUP_KEYBOARD_HOOK and (KBHook <> 0) then begin
        WIndows.UnhookWindowsHookEx(KBHook);
    end;
end;
procedure TfrmMainPopup.btnHideClick(Sender: TObject);
begin
    self.Hide;
end;

procedure TfrmMainPopup.ActivateAfterLoad;
begin
    timSharedUpdate.Interval := FrmConfig.getSharedUpdateTimeMS;
    timSharedUpdate.Enabled := true;

    if frmConfig.getSearchRemovedOn then ACPopup.searchremovedon;
end;


//
// When OS is shutting down/logging off
// AutoSave trigger
//
procedure TFrmMainPopup.WMEndSession(var msg : TWMQueryEndSession);
begin
    self.HandleCloseEvent;
    msg.Result := 1;       // do allow close
end;
procedure TFrmMainPopup.HandleCloseEvent;
begin
    timMouseHeld.Enabled := false;
    if self.DoRememberHistory then begin
        frmClipboardManager.SaveHistory;
    end;

    //FrmConfig.SaveClipboardBarState;

    FrmConfig.SaveLastPermPath;
    FrmPermanent.Close;

    MyFree(Self.CurrentClipboard);

    if not Application.Terminated  then begin
        Application.Terminate;
    end;
end;
procedure TFrmMainPopup.timAutoSaveTimer(Sender: TObject);
begin
    // AutoSave, but don't finalize and kill the process
    // -- This must be the same as HandleCloseEvent(), minus a few items
    if self.DoRememberHistory then begin
        frmClipboardManager.SaveHistory(false);
    end;

    //FrmConfig.SaveClipboardBarState;
    FrmConfig.SaveLastPermPath;
//    FrmPermanent.Close;
    FrmDebug.AppendLog('[AutoSave] -- Activating');
end;
procedure TFrmMainPopup.timSharedUpdateTimer(Sender: TObject);
begin
    TSharedMetadata.update;
end;



//----------------------------
// Jumplist Trigger
//----------------------------
procedure TFrmMainPopup.timJumplistTimer(Sender: TObject);
begin
    timJumplist.enabled := false;

//Show Configuration
//Trigger Popup
//Show Permanent Clips
//Show Edit Clips
//Show Search Window
//Edit Clipboard
    if FrmConfig.cbxClickedAction.ItemIndex <> 1 then begin
        TFocusManager.ForceForeground(Application.Handle);
    end;

    case FrmConfig.cbxClickedAction.ItemIndex of
    0:  begin
            FrmConfig.Show;
        end;
    1:  begin
            self.ShowOnTaskbar;
        end;
    2:  begin
//            FrmPermanent.ShowForm;
            FrmEditHistory.ShowPermanent;
        end;
    3:  begin
            frmEditHistory.ShowPopup;
        end;
    4:  begin
            frmSearch.ShowAutomatted();
        end;
    5:  begin
            frmEditTextExternal.EditClipboard;
        end;
    end;

end;
procedure TFrmMainPopup.JumplistDefault;
begin
    timJumplist.enabled := true;
end;
procedure TFrmMainPopup.FakePopupHotkey;
var msg : TWMHotKey;
begin
    msg.HotKey  := FrmConfig.GetHotKeyID;
    self.WMHotKey(msg);
end;

//----------------------------
// Hotkey/Mouse Triggers
//----------------------------
procedure TFrmMainPopup.timMouseHeldTimer(Sender: TObject);
var msg : TWMHotKey;

    //tw : TTooltipWindow;
    tt : TFrmTooltipNew;

    p : TPoint;

    h : THandle;
    m : TMonitor;
    r : TRect;
    startpoint : TPoint;
    wp : tagWINDOWPLACEMENT;
    name : string;
    delay, curdelay : integer;
    ShowPopup, SkipSecondCheck : boolean;
    fore : THandle;
    UserAcceptedDialog : boolean;
CONST
    MOUSE_THRESHOLD = 6;
    function IsRightTrigger : boolean;
    begin
        result := FrmConfig.cbxMouseButton.ItemIndex = 0;
    end;
    function IsLeftRightTrigger : boolean;
    begin
        result := FrmConfig.cbxMouseButton.ItemIndex = 2;
    end;
    function MouseHeld(MouseUp : boolean=false) : boolean;
    begin
        if IsRightTrigger then begin
            result :=  KeyboardQuery.IsClicked(rightButton);
        end else begin
            if frmconfig.cbxMouseButton.ItemIndex = 1 then begin
                result := KeyboardQuery.IsClicked(MiddleButton);
            end else begin
                if MouseUp then begin
                    result := KeyboardQuery.IsClicked(leftButton) or
                        KeyboardQuery.IsClicked(RightButton);
                end else begin
                    result := KeyboardQuery.IsClicked(leftButton) and
                        KeyboardQuery.IsClicked(RightButton);
                end;
            end;
        end;
    end;
    function MouseMoved : boolean;
    begin
        result :=
            (abs(startpoint.X - mouse.CursorPos.X) > MOUSE_THRESHOLD) or
            (abs(startpoint.Y - mouse.CursorPos.Y) > MOUSE_THRESHOLD);
    end;
    procedure WaitForDelayWhileHeld;
    var c : cardinal;
    begin
        delay := FrmConfig.GetRightClickDelay;
        curdelay := 0;
        c := Windows.GetTickCount;
        while (Windows.GetTickCount - c) + 40 < delay do begin
            mysleep(50);
            if not MouseHeld then begin
                BREAK;
            end;
        end;
    end;
    procedure WaitForMouseRelease;
    begin
        while mouseheld(true) do begin
            MySleep(50);
        end;
    end;
    function CurrentProgramOK : boolean;
    begin
        result := not (frmSysTrayMenu.IsNoRightClickEXE(name) or
            (lowercase(name)='arsclip.exe'));
    end;
    function MonitorAndWindowOK : boolean;
    begin
        result := true;
        if GetWindowRect(h,r) then begin
            m := screen.MonitorFromRect(r);
            if m = nil then begin
                FrmDebug.AppendLog('MonitorFromRect failed', true);
                result := false;
                EXIT;
            end;

            SkipSecondCheck := false;
            if GetWindowPlacement(h, wp) then begin
                if wp.showCmd = SW_SHOWMAXIMIZED then begin
                    FrmDebug.AppendLog('RightClickTrigger - Maximized detected');
                    result := true;
                    EXIT;
                end;
            end;


            FrmDebug.AppendLog('RightClickTrigger monitor' + inttostr(m.boundsrect.Right) + ' ' + inttostr(m.boundsrect.bottom) );
            FrmDebug.AppendLog('RightClickTrigger monitor' + inttostr(m.boundsrect.left) + ' ' + inttostr(m.boundsrect.top) );
            FrmDebug.AppendLog('RightClickTrigger test ' + name  + inttostr(r.Right) + ' ' + inttostr(r.bottom) );
            FrmDebug.AppendLog('RightClickTrigger test ' + name + inttostr(r.left) + ' ' + inttostr(r.top) );

            if ((r.Bottom-r.Top) >= (m.BoundsRect.Bottom-m.BoundsRect.top)) and
                ((r.Right-r.left) >= (m.boundsrect.Right-m.boundsrect.Left))
            then begin
                if lowercase(name) <> 'explorer.exe' then begin
                    FrmDebug.AppendLog('fullscreen detected');
                    result := false;
                    EXIT;
                end;
            end;
        end else begin
            FrmDebug.AppendLog('GetWindowRect failed', true);
        end;
    end;
    procedure MousePopupSetup;
    begin
        TargetData.ForegroundWindow := Windows.GetForegroundWindow();
        if (TargetData.ForegroundWindow = 0) then begin
            FrmDebug.AppendLog('No Foreground Window');
            EXIT;
        end;
        self.GatherTargetWindowData;
    end;

    procedure ShowTooltip(caption : string = '       Popup') ;
    begin
        p := mouse.cursorpos;
        dec(p.x, 10);
        dec(p.Y, 5);

        tt := TFrmTooltipNew.Create(application);
        tt.MinWidth := 80;
        tt.HideHeader;
        tt.CloseOnLostFocus := False;
        tt.SmallFontOnce := true;
        tt.OnClick := nil;
        tt.DodgeMouse := false;
        tt.ShowTooltip(caption,p);
        if IsRightTrigger or IsLeftRightTrigger  then
            TFocusManager.ForceForeground(tt.Handle);
    end;
    function SelectedText(isText : boolean) : boolean;
    var
        aStart, aEnd : integer;
    begin
        result := false;
        aStart := 0;
        aEnd := 0;
        if (true) then begin
            if TargetData.FocusControl <> 0 then begin
                SendMessage(TargetData.FocusControl, EM_GETSEL, Integer(@aStart), Integer(@aEnd));

                if (astart <> aend ) and (aStart < aEnd) then begin


                    result := true;
                end;
            end;
        end;
    end;
    procedure HideTooltip;
    begin
        tt.Hide;
        myfree(tt);
        mysleep(50);
    end;
    procedure TriggerPopup;
    begin
        msg.HotKey := FrmConfig.GetHotKeyID;
        self.UseMousePositionOnce := true;
        self.WMHotKey(msg);
    end;
    procedure DismissContextMenus;
    var
        h : THandle;
        style, exstyle : integer;
        procedure closeit;
        begin
            FrmDebug.AppendLog('Closing Context Menu');
            Windows.SendMessage(h,WM_CLOSE,0,0);
        end;
    begin
       if IsRightTrigger then  begin
            h := GetForegroundWindow;
            if (h<>0) then begin
                style := GetWindowLong(h, GWL_STYLE);
                exstyle := GetWindowLong(h, GWL_EXSTYLE);
                FrmDebug.AppendLog(UnitMisc.GWLStyleText(style));
                FrmDebug.AppendLog(unitmisc.GWLExStyleText(exstyle));
                if ((exstyle and WS_EX_TOOLWINDOW)>0) then begin
                    Paste.SendESC;
//                    closeit;
                end else if FindWindow('#32768', 0) <> 0 then begin
                    Paste.SendESC;
//                    closeit;
                end;
            end;
        end;
    end;
    function UserRejected : boolean;
    begin
        result := false;
        if not FrmConfig.GetRightclickTriggerWarned then begin
            UserAcceptedDialog := true;

            if Dialogs.MessageDlg(
                '(Setting: [Configuration>Popup Hotkey>Alternatives])'#13#10#13#10 +
                'ArsClip is set by default to trigger the popup when this mouse button is held. Keep this setting?'
                ,
                Dialogs.mtConfirmation, [mbYes, mbNo], 0
            ) = mrNo then begin
                FrmConfig.cbRightTriggerEnable.Checked := false;
                result := true;
            end;
            FrmConfig.SetRightclickTriggerWarned;
        end;
    end;
    function isTextCursor : boolean;
    var
        cursor : HICON;
        ta : TThreadAttach;
    begin
        cursor := 0;
        ta := TThreadAttach.create(GetForegroundWindow);
        if ta.isattached then begin
            cursor :=  Windows.GetCursor;
        end;
        myfree(ta);
        result := cursor = Screen.Cursors[crIBeam];
    end;
    procedure ExitCode;
    begin
        WaitForMouseRelease;
        timMouseHeld.Enabled := true;
    end;
var c : cardinal;
    isText : boolean;
begin
    if not Assigned(frmSysTrayMenu) then EXIT;
    if not Assigned(frmConfig) then EXIT;
    if self.PopupActive  then EXIT;

    if MouseHeld then begin
        c := WIndows.GetTickCount;
        timMouseHeld.Enabled := false;
        startpoint := mouse.CursorPos;
        h := Windows.GetForegroundWindow;
        name := WindowHandleToEXEName(h);

        WaitForDelayWhileHeld;
        FrmDebug.AppendLog('Trigger Release = ' + IntToStr(Windows.GetTickCount-c) + ' '+IntToStr(delay));
        fore := GetForegroundWindow;
        //isText := isTextCursor;

        if (not MouseHeld) or MouseMoved then begin
            ExitCode;
            EXIT;
        end;
        if not CurrentProgramOK then begin
            ExitCode;
            EXIT;
        end;
        if not MonitorAndWindowOK then begin
            ExitCode;
            EXIT;
        end;

        if MouseHeld(true) then begin
            MousePopupSetup;
            if false then begin{(SelectedText(isText)) then begin}
                mysleep(100);
                ShowTooltip('             Copy Text');
                WaitForMouseRelease;
                HideTooltip;
                DismissContextMenus;
                Paste.SendCTRL_C;
            end else begin
                ShowTooltip;
                if not UserRejected then begin
                    WaitForMouseRelease;
                    HideTooltip;
                    Application.ProcessMessages;
                    DismissContextMenus;
                    self.ShowPopupWithFocusRules;
                end else begin
                    WaitForMouseRelease;
                    HideTooltip;
                    DismissContextMenus;
                end;
            end;
//            TriggerPopup;
        end;

        timMouseHeld.Enabled := true;
    end;

end;

procedure TFrmMainPopup.WMHotKey(var Msg: TWMHotKey);
label EXIT_CODE;
var mods, key : word;

    function WaitOn(key, mods: integer) : boolean;
    var w : word;
        b : boolean;
    begin
        result := false;
        if Boolean(mods and key) then begin
            result := true;
            case key of
            MOD_ALT : w := VK_MENU;
            MOD_CONTROL : w := VK_CONTROL;
            MOD_SHIFT : w := VK_SHIFT;
            MOD_WIN : w := 0;
            else
                w := 0;
            end;
            if w = 0 then begin
                KeyboardQuery.WaitUntilRelease(VK_LWIN);
                KeyboardQuery.WaitUntilRelease(VK_RWIN);
            end else begin
                b := KeyboardQuery.WaitUntilRelease(w);
                case key of
                MOD_ALT:
                    begin
                        if (b) then begin
                            Paste.SendALT_Release;
                        end else begin
                            // workaround when ALT is released before the other key
                            Paste.SetKeyUp(VK_MENU, false);
                            Paste.SetKeyUp(VK_MENU, true);
                        end;
                    end;
                MOD_CONTROL:
                    Paste.SendCTRL_Release;
                MOD_SHIFT:
                    Paste.SendShift_Release;
                end;
            end;
        end;
    end;
var
    delay : integer;
    b : boolean;
    tt : TFrmTooltipNew ;
    ta : TThreadAttach;
    function IsIgnoredRepeat : boolean;
    begin
        // Detect autorepeat being held down
        SystemParametersInfo(SPI_GETKEYBOARDSPEED, 0, @delay, 0); // repeats per second
        delay := max(delay,1); // NO DIVIDE BY ZERO
        delay := 1000 div delay; // convert to milliseconds

        if self.LastHotkeyTime <> 0 then begin
            if self.LastHotkeyTime > Windows.GetTickCount then begin
                self.LastHotkeyTime := 0;
            end else begin
                if (Windows.GetTickCount - self.LastHotkeyTime) <= (delay*2)  then begin
                    FrmDebug.AppendLog('Ignoring auto repeat');
                    msg.Result := 0;
                    result := true;
                end;
            end;
        end;
        self.LastHotkeyTime := Windows.GetTickCount;
        result := false;
    end;
    function IsPermanentHotkey : boolean;
    begin
        result := not (msg.HotKey = FrmConfig.GetHotKeyID) and
            (FrmPermanent.IsHotkeyIdent(msg.hotkey));
    end;
    function IsReportedToPopup : boolean;
    begin
        result := false;
        // Hotkey pressed while the popup is showing;
        if (msg.HotKey = frmConfig.GetHotKeyID) then begin
            if (ACPopup.Showing) then begin
                FrmDebug.AppendLog('Duplicate hotkey press detected - moving highlight');
                ACPopup.HandleHotkeyWhileVisible;
                result := true;
            end else if (self.HotkeyActive) then begin
                mysleep(30);
                // helps with autorepeat?
            end;
        end;
    end;
    function HandleTargetInfo : boolean;
    begin
        // get current foreground window
        // get the data needed to show the popup from the window

        result := false;
        TargetData.ForegroundWindow := Windows.GetForegroundWindow();
        if (TargetData.ForegroundWindow = 0) then begin
            FrmDebug.AppendLog('No Foreground Window');
            EXIT;
        end;
        self.GatherTargetWindowData;
        result := true;
    end;
    procedure HandlePopupHotkey;
    begin
        b := FrmChainWatcher.GetEnabled;
        if b then FrmChainWatcher.Disable;

        self.ShowPopupWithFocusRules;

        if b then FrmChainWatcher.Enable;
    end;
    procedure HandlePlaintextHotkey;
    begin
        CurrentClipboard.GetClipboardItem(0);

        if (Paste.GetPasteMethod(TargetData.ExeName) in [PASTE_MIMIC]) or
        (Paste.GetDefaultPasteMethod in [PASTE_MIMIC]) then begin
            WaitOn(MOD_WIN, mods);
        end;
        // don't paste if a WINKEY is held down
        if CurrentClipboard.GetFormat = CF_HDROP then begin
            Paste.SendPlainText(CurrentClipboard.GetAsPlaintext);
        end else begin
            Paste.SendPlainText(Clipboard.AsText);
        end;
        if (Paste.GetPasteMethod(FrmMainPopup.TargetData.ExeName) = PASTE_CLIPBOARD) then begin
            self.showCue('ArsClip','Clipboard Only');
        end;
    end;
    procedure HandlePermanentHotkey;
    var b : boolean;
    begin
        // wait for the hotkeys to be released
        mods := TMessage(msg).LParamLo;
        key := TMessage(msg).LParamHi;

        KeyboardQuery.WaitUntilRelease(key);

        b := false;
        b := b or WaitOn(MOD_ALT, mods);
        b := b or WaitOn(MOD_WIN, mods);
        b := b or WaitOn(MOD_CONTROL, mods);
        b := b or WaitOn(MOD_SHIFT, mods);

        FrmPermanent.ReportHotkey(msg.hotkey);

    end;
begin

    Windows.SetLastError(ERROR_SUCCESS);
    mods := TMessage(msg).LParamLo;
    key := TMessage(msg).LParamHi;

    if IsIgnoredRepeat then EXIT;
    if IsPermanentHotkey then begin
        HandlePermanentHotkey;
        EXIT;
    end;
    if IsReportedToPopup then EXIT;

    try
        self.HotkeyActive := true;
        FrmDebug.AppendLog('WM_Hotkey - received');
        if (self.IgnoreHotkey) then begin
            FrmDebug.AppendLog('ignoring hotkey');
            EXIT;
        end;
        if HandleTargetInfo then begin
            if (msg.HotKey = FrmConfig.GetHotKeyID)  then begin
                HandlePopupHotkey;
            end else if (msg.hotkey = FrmConfig.GetPlaintextHokeyID) then begin
                HandlePlaintextHotkey;
            end;
        end;
    finally
        self.HotkeyActive := false;
        FrmDebug.AppendLog('WM_Hotkey - end');
    end;
end;

procedure TFrmMainPopup.GatherTargetWindowData;
var answer : longint;
    rect : TRect;

    procedure HandleNoFocusControl;
    begin
        FrmDebug.AppendLog('gtwd: no focus control!');
        TargetData.x := Mouse.CursorPos.X;
        TargetData.y := Mouse.CursorPos.Y;
        //
        // Bitch if were not in ClipboardOnly mode
        // or per-program override can't paste the item right by default.
        // Allow user to always use ClipboardOnly for the problematic
        // program
        // [Answer Yes = add EXE name to NoPaste file]
        //
        if (Paste.GetPasteMethod(TargetData.ExeName) in [PASTE_CLIPBOARD, PASTE_MIMIC]) or
        (Paste.GetDefaultPasteMethod in [PASTE_CLIPBOARD, PASTE_MIMIC])
         then begin
            self.GatherTargetWarned := true;
            // Lie - we don't need a warning when we're already using non-default paste workarounds
        end;

        if (not (Paste.GetPasteMethod(TargetData.ExeName) in [PASTE_CLIPBOARD, PASTE_MIMIC]))
            and
           (not (Paste.GetDefaultPasteMethod in [PASTE_CLIPBOARD, PASTE_MIMIC]))
            then begin
            self.GatherTargetWarned := true;
            Windows.SetForegroundWindow(self.Handle);
            if Integrity.GetLevel(TargetData.ForegroundWindow) = Integrity.IL_HIGH then begin
                if not WarnedEXE.ContainsKey(TargetData.ExeName) then begin
                    WarnedExe.Add(TargetData.ExeName, true);
                    ShowMessage('UAC Admin program detected. Using Clipboard Only for pasting.');
                end;
                Paste.SetClipboardOnlyOnce;
            end else begin
                // automatically use Clipboard Only for the Command Prompt
                if (not self.UseNoWarningsModeOnce ) then begin
                    if not WarnedEXE.ContainsKey(TargetData.ExeName) then begin
                        answer := MessageDlg('Coudn''t find control to target in program ' + uppercase(TargetData.ExeName) + #13#10
                            + 'Would you like to use "Copy to clipboard only" for this program?',
                            mtConfirmation, [mbyes, mbno],0 );
                        if (answer = mryes) then begin
                            Paste.AssignPaste(TargetData.ExeName, PASTE_CLIPBOARD);
                            Windows.SetForegroundWindow(TargetData.ForegroundWindow);
                            Paste.SetClipboardOnlyOnce;
                            EXIT;
                        end;
                        WarnedExe.Add(TargetData.ExeName, true);
                    end;
                end else begin
                    Paste.SetClipboardOnlyOnce;
                    answer := mrno;
                end;
            end;
        end;
        if self.GetNeedsFocus then
            Windows.SetForegroundWindow(self.Handle);
    end;
begin
    Windows.SetLastError(ERROR_SUCCESS);
    self.GatherTargetWarned := false;

    // find control with focus on foreground window
    // ingore error when pasting option is "Clipboard Only"

    self.ExplorerEditDetected := false;
    if (overridehandle = 0) then begin
        self.GetTargetControl(TargetData);
    end else begin
        TargetData.ForegroundWindow := overridehandle;
        TargetData.focuscontrol := 1;
    end;


    TargetData.ForegroundWindowClassname := StringOfChar(#0, MAX_PATH+1);
    GetClassName(TargetData.ForegroundWindow, @TargetData.ForegroundWindowClassname[1], MAX_PATH);
    TargetData.ForegroundWindowClassname := PChar(TargetData.ForegroundWindowClassname);
    FrmDebug.AppendLog('ClassName: '+TargetData.ForegroundWindowClassname);

    if (TargetData.icon <> 0) then begin
        DestroyIcon(TargetData.icon);
    end;
    if FrmConfig.getExtractIconForPopup then begin
        TargetData.icon := FrmClipboardManager.GetClipboardOwnerIcon(TargetData.ForegroundWindow);
    end else begin
        TargetData.icon := 0;
    end;

    // override locations
    // Mouse Cursor, Program Corner
    if (frmConfig.rbLocationProgramCorner.Checked) then begin
        FrmDebug.AppendLog('gtwd: showcorner');
        Windows.GetWindowRect(TargetData.ForegroundWindow, rect);
        self.UseMousePositionOnce := false;
        TargetData.x := rect.Left;
        TargetData.y := rect.Top;
    end;
    if (FrmConfig.rbLocationMouseCursor.Checked) or
        (self.UseMousePositionOnce and FrmConfig.rbLocationAutomatic.Checked) then begin
        FrmDebug.AppendLog('gtwd: showatmouse');
        self.UseMousePositionOnce := false;
        TargetData.x := Mouse.CursorPos.X + 15;
        TargetData.y := Mouse.CursorPos.Y + 10;
    end;

    // detect Explorer AutoMode

    TargetData.ExeName := WindowHandleToEXEName(TargetData.ForegroundWindow);
    ExplorerEditDetected :=
        (
            (lowercase(TargetData.ExeName) = 'explorer.exe') or
            (targetdata.ForegroundWindowClassname='#32770')
        )
        and
        (
            (AnsiStartsText('Edit',TargetData.FocusControlClassname)) or
            (AnsiStartsText('DirectUIHWND',TargetData.FocusControlClassname))
        );
    FrmDebug.AppendLog('Program: '+targetdata.ExeName);
    ExplorerEditDetected := ExplorerEditDetected or frmSysTrayMenu.IsExplorerCompatEXE(TargetData.ExeName);

    Paste.ClearOnceFlags;
    case Paste.GetPasteMethod(TargetData.ExeName) of
        PASTE_CTRL_V    :  Paste.SetUsePasteCVOnce;
        PASTE_SHIFT_INS :  Paste.SetUsePasteSIOnce;
        PASTE_MIMIC     :  Paste.SetKeyboardMimicOnce;
        PASTE_CLIPBOARD :
            begin
                Paste.SetClipboardOnlyOnce;
                ExplorerEditDetected := false;
            end;
        PASTE_PROGRAMCUSTOMMACRO:
            begin
                Paste.SetUseCustomScriptOnce;
                Paste.SetPasteMacro(frmSysTrayMenu.GetProgramMacro(TargetData.ExeName));
            end;
        PASTE_DEFAULT :
            begin
            end;
    end;



    ACPopup.ExplorerMode := ExplorerEditDetected;

    if (lowercase(TargetData.ExeName) = 'cmd.exe') and
     not (Paste.GetPasteMethod(TargetData.ExeName) in [PASTE_CLIPBOARD, PASTE_MIMIC])  then begin
        Paste.SetClipboardOnlyOnce;
        Paste.AssignPaste(TargetData.ExeName, PASTE_CLIPBOARD);
    end;
    //
    // Default to mouse position when can't target a control
    //
    if (TargetData.focuscontrol = 0) then begin
        HandleNoFocusControl;
    end;
end;
procedure TfrmMainPopup.GetTargetControl(var TargetData : TTargetProgramData );
var CaretPos, pt : TPoint;
    c : cardinal;
    ta : TThreadAttach;
    foregroundrect, controlrect : TRect;

    function getTopLevelParent(h : THandle) : THandle;
    var h2 : THandle;
    begin
        result := h;
        repeat
            h2 := result;
            result := GetParent(h2);
        until result = 0;
        result := h2;
    end;
begin
    Windows.SetLastError(ERROR_SUCCESS);

    // attempt to get target control's handle and
    // the position of the caret in the text window
    //
    TargetData.focuscontrol := 0;
    TargetData.selectedText := '';
    TargetData.SelectCompatible := false;
    TargetData.DropFilesCompatible := false;

    ta := TThreadAttach.create(TargetData.ForegroundWindow);
    if not ta.IsAttached then begin
        myfree(ta);
        EXIT;
    end;



    Windows.GetWindowRect(TargetData.ForegroundWindow, foregroundrect);
    TargetData.focuscontrol := Windows.GetFocus();
    if getTopLevelParent(TargetData.FocusControl) <> TargetData.ForegroundWindow then begin
        FrmDebug.AppendLog('FocusControl not in foregroundwindow');
        SetForegroundWindow(TargetData.ForegroundWindow);
        TargetData.focuscontrol := Windows.GetFocus();
    end;


    if (TargetData.focuscontrol <> 0) then begin

        Windows.GetWindowRect(TargetData.FocusControl, controlrect);

        if foregroundrect=controlrect then begin
            // assume the program will lie about the CaretPos anyways, since this
            // failed
            FrmDebug.AppendLog('GetTargetControl: Foregroundrect=controlrect, use mouse');
            self.UseMousePositionOnce := true
        end else begin
            c := Windows.GetWindowLong(TargetData.ForegroundWindow, GWL_EXSTYLE);
            TargetData.DropFilesCompatible := (c and WS_EX_ACCEPTFILES) > 0;

            fillchar(CaretPos,sizeof(CaretPos),#0);
            if Windows.GetCaretPos(CaretPos) then begin
                pt := CaretPos;
                Windows.ClientToScreen(TargetData.focuscontrol, pt);
                TargetData.x := pt.X;
                TargetData.y := pt.Y;

                FrmDebug.AppendLog(Format('GetTargetControl: caretx=%d carety=%d x=%d y=%d',[caretpos.x, caretpos.y, TargetData.X,TargetData.y]));
                if (pt.X = controlrect.left) and (pt.Y = controlrect.top) then begin
                    FrmDebug.AppendLog('GetTargetControl: CaretPos is the same as control rect - using control top-left');
                    TargetData.x := controlrect.Left;
                    TargetData.y := controlrect.Top;
                end;
            end else begin
                if ptinrect(controlrect, mouse.CursorPos) then begin
                    TargetData.x := controlrect.Left;
                    TargetData.y := controlrect.Top;
                    FrmDebug.AppendLog('GetTargetControl: ==GetCaretPos failed - using control top-left==');
                end else begin
                    self.UseMousePositionOnce := true;
                    FrmDebug.AppendLog('GetTargetControl: ==GetCaretPos failed - using mouse position==');
                end;
            end;
        end;
    end else begin
        self.UseMousePositionOnce := true;
        FrmDebug.AppendLog('GetTargetControl: ==GetWindowRect failed - Using Mouse Position instead==');
    end;
    myfree(ta);

    // only use mouse if the cursor is inside the current window
    if self.UseMousePositionOnce and not PtInRect(foregroundrect, mouse.CursorPos) then begin
        self.UseMousePositionOnce := false;
        TargetData.x := foregroundrect.Left;
        TargetData.y := foregroundrect.Top;
        FrmDebug.AppendLog('GetTargetControl: Mouse not in Program region');
    end;


    if TargetData.FocusControl <> 0 then begin
        TargetData.FocusControlClassname := StringOfChar(#0, MAX_PATH+1);
        GetClassName(TargetData.FocusControl, @TargetData.FocusControlClassname[1], MAX_PATH);
        TargetData.FocusControlClassname := PChar(TargetData.FocusControlClassname);
        FrmDebug.AppendLog('TargetData.FocusControlClassname: '+TargetData.FocusControlClassname);
    end;
end;
function TFrmMainPopup.GetTargetEXE: string;
begin
    result := TargetData.ExeName;
end;
function TFrmMainPopup.GetNeedsFocus : boolean;
begin
    result := not self.ExplorerEditDetected;
end;



//
// ShowPopupWithFocusRules: Wrapper function to call ShowPopup enforcing the
// focus rules. The rule is to return focus to the original window.
//
// Once the Hotkey has been pressed, you can reshow the
// popup again by calling this procedure.
//
// Program flow continues to a XXXClickEvent routine when the user
// selects an item from the popup window

procedure TFrmMainPopup.ShowPopupWithFocusRules;
var ta : TThreadAttach;
begin
    Windows.SetLastError(ERROR_SUCCESS);


    ACPopup.OnHideEvent := self.ShowPopupCallback;
    if (self.GetNeedsFocus) then begin
        // make sure focus is returned
        if TFocusManager.ForceForeground(self.handle) then begin //if (self.ThreadAttach(TargetData.ForegroundWindow)) then begin
            self.ShowPopup(TargetData.x, TargetData.y);
        end else begin
            if (not self.GatherTargetWarned) and
                (not self.UseNoWarningsModeOnce) and
                not (Paste.GetPasteMethod(TargetData.ExeName) in [PASTE_CLIPBOARD, PASTE_MIMIC])
              then
                showmessage('Permission denied on target program.');
            // try anyways
            windows.SetForegroundWindow(self.Handle);
            self.ShowPopup(TargetData.x, TargetData.y);
        end;
    end else begin
        ta := TThreadAttach.Create(TargetData.ForegroundWindow);
        if (not ta.IsAttached) then begin
            if (not self.UseNoWarningsModeOnce) then
                ShowMessage('ERROR: Couldn''t attach popup menu to this window');
            myfree(ta);
        	EXIT;
        end;

        self.ShowPopup(TargetData.x, TargetData.y);
        myfree(ta);
    end;

    // Can't clear flags here, MenuClick type events have not yet been fired
    //Paste.ClearOnceFlags;
    //self.UseNoWarningsModeOnce := false;
end;
procedure TFrmMainPopup.ShowPopupCallback(Sender: TObject);
begin
	if not self.SkipFocusReturn  then begin
		TFocusManager.ForceForeground(TargetData.ForegroundWindow);
        FrmDebug.AppendLog('Returning Focus to - ' + WindowHandleToEXEName(TargetData.ForegroundWindow));
    end;
end;
procedure TFrmMainPopup.NewClipboardCallback;
begin
end;
procedure TfrmMainPopup.ShowPopup(X,Y: integer);
var
	AppData: TAppBarData;
begin
    if (self.PopupActive) then begin
        EXIT;
    end;
    self.IgnoreHotkey := true;
    self.PopupActive := true;

    try
        if not frmSysTrayMenu.GetJustSwitched then begin
            FrmPermanent.AutoSwitch(TargetData.ExeName);
        end;

        FrmDebug.AppendLog('Popup Start');
        self.SkipFocusReturn := false;

        if not acpopup.Showing then begin
            FrmDebug.AppendLog('Populate Popup');

            if USE_POPUP_KEYBOARD_HOOK and
                ExplorerEditDetected and (KBHook = 0) then begin
                KBHook:=SetWindowsHookEx(
                    WH_KEYBOARD_LL,
                    @KeyboardHookProc,
                    0{HInstance}, 0{GetCurrentThreadId()}
                );
            end;

            ACPopup.AutoPopulate;
            FrmDebug.AppendLog('Popup Activated');
            if (TargetData.ExeName = 'explorer.exe') then begin
                AppData.cbSize := SizeOf(TAppBarData);
                // keep popup from showing under vertical taskbar
                if SHAppBarMessage(ABM_GETTASKBARPOS, AppData) <> 0 then begin
                    if (screen.DesktopRect.TopLeft = appdata.rc.TopLeft) and
                    (y >= appdata.rc.Top) and (x < appdata.rc.right) then begin
                        x := appdata.rc.Right;
                    end;
                end;
            end;
            ACPopup.ShowPopup(max(x, screen.DesktopRect.left), max(y, screen.DesktopRect.top));
        end;

        FrmDebug.AppendLog('Popup End');
        //
        // show
        // NOTE: This code finishes before .SendText() is ever executed
        //       I need to stop ignoring here because the popup can be dismissed
        //       by ESC key or focus lost.
        //frmClipboardManager.SetIgnoreClipboard(false);
    finally
        self.IgnoreHotkey := false;
        self.PopupActive := false;
    end;
end;

procedure TFrmMainPopup.ShowPreviewForm;
begin
end;
procedure TFrmMainPopup.SkipFocusReturnOnce;
begin
    self.SkipFocusReturn := true;
end;
procedure TFrmMainPopup.SearchJumplistClickEvent;
var
    newclip : string;
    ci : TClipItem;
begin
    frmSearch.StatusBar1.panels[1].text := 'Pasting to Clipboard only';
    Windows.SetForegroundWindow(FrmSearch.Handle);
    Windows.SetForegroundWindow(FrmSearch.Handle);
    if frmSearch.Showing = false then frmSearch.ShowModal;

    frmSearch.StatusBar1.panels[1].text := '';
    if FrmSearch.ModalResult = mrOK then begin
        newclip := frmSearch.GetTextToPaste;
        if newclip <> '' then begin
            Paste.SetClipboardOnlyOnce;
            ci := FrmSearch.GetClipToPaste;
            if (ci = nil) then begin
                self.SendPlainText(newclip);
            end else begin
                self.SendClip(ci);
            end;
        end;
    end;

end;
procedure TFrmMainPopup.SearchMenuItemClickEvent(Sender: Tobject);
begin
    FrmSearch.ShowAutomatted;
end;
procedure TFrmMainPopup.ClearCurrentLast;
begin
    self.CurrentClipboard.Free;
    self.CurrentClipboard := TClipItem.Create;
end;

procedure TFrmMainPopup.ShowOnTaskbar;
var h : THandle;
    top, forced : string;
    msg : TWMHotKey;
begin
    TFocusManager.ForceForeground(Application.Handle);
    Application.ProcessMessages;

    h := GetTopWindow(0);
    top := lowercase(WindowHandleToEXEName(h));
    FrmDebug.AppendLog('TopLevel:'+top);
    if (top='arsclip.exe') then begin
        h := GetNextWindow;
        forced := lowercase(WindowHandleToEXEName(h));
        if forced = 'explorer.exe' then begin
            FrmDebug.AppendLog('Forced1:'+forced);
            h := GetNextWindow(h);
        end;
    end else begin
        h := GetNextWindow;
    end;
    forced := lowercase(WindowHandleToEXEName(h));
    FrmDebug.AppendLog('Forced2:'+forced);
    if (top='explorer.exe') and (forced=top) then begin
        h := self.GetNextWindow(h);
        forced := lowercase(WindowHandleToEXEName(h));
        FrmDebug.AppendLog('Forced3:'+forced);
    end;
    TFocusManager.ForceForeground(h);

    FrmDebug.AppendLog('next:'+self.GetNextWindowProgramName);

    msg.HotKey  := FrmConfig.GetHotKeyID;
    self.WMHotKey(msg);
end;
function TFrmMainPopup.GetNextWindow(inh : THandle=0): THandle;
    function GetForegroundSafe : THandle;
    var
        h : THandle;
        i, ms : integer;
    begin
        h := Windows.GetForegroundWindow;
        result := 0;
        ms := 50;
        i := 0;
        while (h = 0) and (i<400) do begin
            h := Windows.GetForegroundWindow;
            mysleep(ms);
            inc(i,ms);
        end;
        if (h = 0) then begin
            FrmDebug.AppendLog('Can''t get foreground window', true);
            exit;
        end;

        result := h;
    end;
    function NextVisible(h : THandle) : THandle;
    var
        starting : THandle;
    begin
        Result := 0;
        h := Windows.GetNextWindow(h, GW_HWNDNEXT);
        if (h = 0) then begin
            FrmDebug.AppendLog('Can''t get next window', true);
            exit;
        end;
        starting := h;
        while not (
            (Windows.IsWindowVisible(h) or Windows.IsIconic(h))
            and (Windows.GetParent(h) in [0, GetDesktopWindow])
            )

            and (h<> 0)
         do begin
            h := Windows.GetNextWindow(h, GW_HWNDNEXT);
            if (h = 0) then begin
                FrmDebug.AppendLog('Can''t get next window in loop', true);
                BREAK;
            end;

            if (h=starting) then BREAK; {safety to prevent an infinite loop}
        end;

        result := h;
    end;
var h : thandle;
    i : integer;
const
    debug = True;

begin
    Result := 0;

    if inh = 0 then begin
        h := GetForegroundSafe;
    end else begin
        h := inh;
    end;

    if h = 0 then Exit;

    FrmDebug.AppendLog('foreground: ' + WindowHandleToEXEName(h));


    h := NextVisible(h);
    if h = 0 then Exit;

    FrmDebug.AppendLog('NextVisible: ' + WindowHandleToEXEName(h));

    // the fix for Application.MainFormOnTaskBar caused a change in the
    // "visible" window order -- causing this program to be the previous window
    i := 0;
    while  (ExtractFileName(Application.ExeName) = UnitMisc.WindowHandleToEXEName(h)) and (i<5) do begin
        h := NextVisible(h);
        FrmDebug.AppendLog('Self Found: ' + WindowHandleToEXEName(h));
        inc(i);
    end;


    result := h;
end;
procedure TFrmMainPopup.ShowOnNextWindow(ShowPopup : boolean = true);
var h : THandle;
    ta : TThreadAttach;
    isOK : boolean;
begin
    Windows.SetLastError(ERROR_SUCCESS);
    if (self.PopupShowing) then EXIT;
    try
    self.PopupShowing := true;

    // find the current foreground window
    // find the "next" window (which means below the current, not above)
    // skip any invisible windows

    // move back the the "next" window and attempt
    // to make the previously focused control get keyboard focus again

    h := self.GetNextWindow;
    if (h=0) then begin
        FrmDebug.appendlog('ShowOnNextWindow: can''t GetNextWindow');
        EXIT;
    end;

    if (ShowPopup) then begin
        ta := TThreadAttach.create(h);
        isOK := ta.IsAttached;
        myfree(ta);
        if (isOK) then begin
            TFocusManager.ForceForeground(h);
        end else begin
            showmessage('Permission denied on target program.');
        end;
    end;

    TargetData.ForegroundWindow := h;
    self.GatherTargetWindowData;


    if (ShowPopup) then begin
        self.ShowPopupWithFocusRules;
    end;

    finally
        self.PopupShowing := false;
    end;
end;
procedure TFrmMainPopup.ShowOnScratchPad(ShowPopup : boolean = true);
var h : THandle;
begin
    Windows.SetLastError(ERROR_SUCCESS);
    if (ACPopup.Showing) then EXIT;
    try
    self.PopupShowing := true;

    // find the current foreground window
    // find the "next" window (which means below the current, not above)
    // skip any invisible windows

    // move back the the "next" window and attempt
    // to make the previously focused control get keyboard focus again



    h := GetForegroundWindow;
    self.UseMousePositionOnce := true;
    self.UseNoWarningsModeOnce := true;
    {if (ShowPopup) then begin
        if (self.ThreadAttach(h)) then begin
            Windows.SetForegroundWindow(h);
            self.ThreadDetach;
        end else begin
            //showmessage('Permission denied on target program.');
        end;
    end;
    }
    TargetData.ForegroundWindow := h;

    self.GatherTargetWindowData;



    if (ShowPopup) then begin
        self.ShowPopupWithFocusRules;
    end;
    //Paste.ClearOnceFlags;
    self.UseNoWarningsModeOnce := false;
    finally
        self.PopupShowing := false;
    end;
end;
procedure TFrmMainPopup.ShowOnClipboardBar(ShowPopup : boolean = true);
var h : THandle;
begin
    Windows.SetLastError(ERROR_SUCCESS);
    if (ACPopup.Showing) then EXIT;
    try
    self.PopupShowing := true;

    // find the current foreground window
    // find the "next" window (which means below the current, not above)
    // skip any invisible windows

    // move back the the "next" window and attempt
    // to make the previously focused control get keyboard focus again


    h := GetForegroundWindow;
    self.UseMousePositionOnce := true;
    self.UseNoWarningsModeOnce := true;
    TargetData.ForegroundWindow := h;

    self.GatherTargetWindowData;



    if (ShowPopup) then begin
        self.ShowPopupWithFocusRules;
    end;
    //Paste.ClearOnceFlags;
    self.UseNoWarningsModeOnce := false;
    finally
        self.PopupShowing := false;
    end;

end;

function TFrmMainPopup.GetNextWindowProgramName: string;
var h : THandle;
begin
    Windows.SetLastError(ERROR_SUCCESS);

    h := self.GetNextWindow;
    if (h <> 0) then begin
        result := ExtractFilename(WindowHandleToEXEName(h));
    end else begin
        result := '';
    end;
end;
procedure TFrmMainPopup.ShowOnSystemTray(overrideh : Thandle = 0);
var h : THandle;
    CursorPos : TPoint;
begin
    Windows.SetLastError(ERROR_SUCCESS);
    if (self.PopupShowing) then EXIT;
    try
    self.PopupShowing := true;


    Windows.GetCursorPos(CursorPos);

    // find the current foreground window
    // find the "next" window (which means below the current, not above)
    // skip any invisible windows


    h := self.GetNextWindow;
    if (h=0) then begin
        FrmDebug.appendlog('ShowOnSystemTray: can''t GetNextWindow');
        EXIT;
    end;
    TargetData.ForegroundWindow := h;

    self.GatherTargetWindowData(h);
    TargetData.focuscontrol := 0;
    self.TargetData.x := CursorPos.x;
    self.TargetData.y := CursorPos.Y;


    Windows.SetForegroundWindow(self.Handle);

    self.ShowPopup(CursorPos.x + 15, CursorPos.y - 10);
    ACPopup.DodgePoint(cursorpos);
    finally
        self.PopupShowing := false;
    end;
end;
function TFrmMainPopup.PopupIsShowing : boolean;
begin
    result := ACPopup.Showing;
end;

//
// Pasting With Focus Rules
//
const
    PASTEMODE_MACRO = 1;
    PASTEMODE_TEXT = 2;
    PASTEMODE_CLIP = 3;
    PASTEMODE_JAVASCRIPT = 4;

procedure TFrmMainPopup.SendMacro(s : string);
begin
    SendText(PASTEMODE_MACRO, s);
end;
procedure TFrmMainPopup.SendPlainText(s : string);
begin
    SendText(PASTEMODE_TEXT, s);
end;
procedure TFrmMainPopup.SendClip(ci : TClipItem; ForceFormatted : boolean = false);
var s : string;
begin
    if (ci is TPermanentClipItem) then begin
        s := ci.GetAsPlaintext;
        if Paste.IsMacro(s) then begin
            SendText(PASTEMODE_MACRO, s);
            EXIT;
        end else if TJavaScript.isJavaScript(s) then begin
            SendText(PASTEMODE_JAVASCRIPT, s);
            EXIT;
        end;
    end;
    SendText(PASTEMODE_CLIP, '', ci, ForceFormatted);
end;
//
procedure TFrmMainPopup.SendText(PasteMode : integer; s: string; ci : TClipItem = nil; ForceFormatted : boolean = false);
var prefix : string;
    h2,h3 : THandle;
begin
    Windows.SetLastError(ERROR_SUCCESS);
    FrmDebug.AppendLog('FrmMain>SendText start');
    fIsSendingText := true;
    try
        //
        // Since the popup may have stolen focus from the target,
        // it must be given keyboard focus again
        //
        if (self.GetNeedsFocus) then begin
            TFocusManager.SetOn(TargetData.ForegroundWindow, TargetData.FocusControl);
        end;

        case PasteMode of
        PASTEMODE_MACRO:
            begin
                Paste.SendMacro(s);
            end;
        PASTEMODE_JAVASCRIPT:
            begin
                Paste.SendJavaScript(s);
            end;
        PASTEMODE_TEXT:
            begin
                Paste.SendPlainText(s);
            end;
        PASTEMODE_CLIP:
            begin
                Paste.SendClip(ci, ForceFormatted);
            end;
        end;
//        if TJavaScript.isJavaScript(s) then begin
//            if not Paste.SendJavaScript(s) then begin
//               self.showCue('ArsClip' ,'error executing JavaScript');
//            end;
//        end;

    finally
        fIsSendingText := false;
    end;

    FrmDebug.AppendLog('FrmMain>SendText end');
end;
function TfrmMainPopup.IsSendingText : boolean;
begin
    result := fisSendingText;
end;
{
--===========
-- // Etc. //
--===========
}
procedure TFrmMainPopup.ApplicationException(Sender: TObject; E: Exception);
begin
    FrmDebug.ApplicationException(Sender, E);
end;
procedure TFrmMainPopup.ShowPreviewEditForm;
begin
    Windows.SetForegroundWindow(FrmEditItem.Handle);

    FrmEditItem.Left := mouse.CursorPos.x;
    FrmEditItem.Top := mouse.CursorPos.y;

    FrmEditItem.ShowModal;
end;


end.
