unit UnitFrmChainWatcher;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, ExtCtrls, UnitClipQueue;

{$REGION 'PURPOSE:'}
{
    Purpose:
        Monitor the clipboard problems caused by other peoples
        f*cked up programs. *sigh*
    Notes:
    	No longer needed or used in Vista or Above.

    Updates:
        option to disable watcher (used by Clipboard Manager)

        Disabled Clipboard chain monitor no longer running
        error reporting by default.

        --------------------
        Re-wrote "get parent" routine
        Always clear previous errors

        ---------------
        Handle EXE Name retreive errors
        --------------
        Keep track of last known program name for reporting if
        current owner is no longer running

        -----------------
        New option to report problem program
        Watcher re-written
        ---------------
        I was refreshing the chain wether it needed it or not
}
{$ENDREGION}


type
  TFrmChainWatcher = class(TForm)
    Timer1: TTimer;
    procedure Timer1TimerNew(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
  private
    { Private declarations }

    serial : cardinal;
    //LastClipItem : TClipItem;
    LastClipboardOwner : string;
    DialogShownOnce : boolean;

    MasterOverrideSet : boolean;
  public
    { Public declarations }
    procedure NotifyOfClipboardActivity;
    procedure Disable;
    procedure Enable;
    procedure PermanentDisable(enabled : boolean);
    function GetEnabled : boolean;
    procedure FireTimer;
  end;

var
  FrmChainWatcher: TFrmChainWatcher;

{////////////////////}
{//}implementation{//}
{////////////////////}

uses Clipbrd, UnitFrmClipboardManager, UnitFrmMainPopup,
   UnitMisc, UnitFrmConfig, UnitReportError, UnitFrmDebug;
{$R *.dfm}

type ByteArray = array of byte;


function TFrmChainWatcher.GetEnabled : boolean;
begin
    result := MasterOverrideSet;
end;

procedure TFrmChainWatcher.Disable;
begin
    timer1.Enabled := false;
end;

procedure TFrmChainWatcher.Enable;
begin
    timer1.Enabled := true and (not self.MasterOverrideSet);
end;

procedure TFrmChainWatcher.FireTimer;
begin
    self.Timer1TimerNew(self); 
end;

procedure TFrmChainWatcher.FormCreate(Sender: TObject);
begin
FrmDebug.AppendLog('FrmChainWatcher - Creating');

    self.PermanentDisable(FrmConfig.cbDisableWatcher.checked);
    DialogShownOnce := false;

    //
    // get me started
    //
    serial := 0;
    if timer1.Enabled  then
        self.Timer1TimerNew(self);
end;


procedure TFrmChainWatcher.FormDestroy(Sender: TObject);
begin
    timer1.Enabled := false;
end;

procedure TFrmChainWatcher.NotifyOfClipboardActivity;
begin
    // Reset the timer to not trigger until later
    // update: make sure timer does not trigger here

    self.Disable;
    serial := Windows.GetClipboardSequenceNumber;
    self.Enable;
end;


procedure TFrmChainWatcher.PermanentDisable(enabled: boolean);
begin
    self.MasterOverrideSet := enabled;
    self.Enable;
end;

//
// I should be shot for this.
//
procedure TFrmChainWatcher.Timer1TimerNew(Sender: TObject);
var
    ChainBroken : boolean;
    problem : THandle;
    ownerTitle : string;

    procedure HandleBrokenChain;
    var s, s2 : string;
    begin
        s := ownerTitle;
        s2 := LastClipboardOwner;
        FrmClipboardManager.RefreshClipboardMonitor;

        if not (DialogShownOnce or
            FrmConfig.cbDisableChainNotification.checked)  then begin

            FrmDebug.AppendLog('Watcher: chain broken');

            self.Enabled := false;

            Windows.SetForegroundWindow(self.handle);

            DialogShownOnce :=  true;
            if (s <> '') then begin
                FrmReportError.ShowForm( s );
            end else if (s2 <> '') then begin
                FrmReportError.ShowForm( s2 );
            end else begin
                Dialogs.ShowMessage(
                'ERROR: The Clipboard chain has been broken.' + #13#10 +
                'Please note which programs are running that may have caused this problem.' + #13#10 +
                #13#10 +
                'The Clipboard Monitor will automatically be refreshed and this message'+#13#10+
                'will not be shown again this session.');
            end;

            self.Enabled := true;
        end;


        
        // If this is successful, the timer will be enabled by
        // NotifyClipboardActivity
    end;
var h : THandle;
begin
    FrmDebug.AppendLog('Watcher probe starting');
    Windows.SetLastError(ERROR_SUCCESS);

    if not assigned(frmClipboardManager) then EXIT;
    
    if not frmClipboardManager.GetIsOnChain then begin
        FrmDebug.AppendLog('Watcher: ClipMonitor not attached - refreshing');
        frmClipboardManager.RefreshClipboardMonitor;
        EXIT;
    end;

    FrmDebug.AppendLog('Watcher probe: ' + SysErrorMessage(GetLastError));
    SetLastError(ERROR_SUCCESS);

    // First time here?
    if (serial = 0) then begin
        serial := Windows.GetClipboardSequenceNumber;
        FrmDebug.AppendLog('Watcher: initing');
    end else begin
        ChainBroken := (serial <> Windows.GetClipboardSequenceNumber);
        if not ChainBroken then begin
            serial := Windows.GetClipboardSequenceNumber;
            EXIT;
        end;

        ownerTitle := '';
        problem := Windows.GetClipboardViewer;
        if (problem <> 0) then begin
            h := problem;
            while (h <> 0) do begin
                problem := h;
                h := GetWindowLong(h, GWL_HWNDPARENT);
            end;
            if (GetLastError <> ERROR_SUCCESS) then begin
                FrmDebug.AppendLog('Get parent failed: ' + SysErrorMessage(GetLastError));
                EXIT;
            end;

            ownerTitle := lowercase(ExtractFileName(WindowHandleToEXEName(problem)));
            if (GetLastError <> ERROR_SUCCESS) then begin
                FrmDebug.AppendLog('EXEName error: ' + SysErrorMessage(GetLastError));
            end;
        end else begin
            FrmDebug.AppendLog('No viewer found: ' + SysErrorMessage(GetLastError));
        end;

        FrmDebug.AppendLog('Clipboard Owner = ' + ownerTitle);
        if (ownerTitle <> '') then begin
            if (ownerTitle <> 'arsclip.exe') and (ownerTitle <> EXENAME_ERROR) then begin
                LastClipboardOwner := ownerTitle;
            end;
        end;

        // If the clipboard owner is AWOL and configured to check this,
        // or the clip has changed and we don't know about it, then
        // the chain has been broken

        if ((ownerTitle = '') and
            frmConfig.cbEnableExtendedChainChecking.Checked) and
            (ownerTitle <> EXENAME_ERROR) then begin
            if (ownerTitle = 'arsclip.exe') then begin
                ownerTitle := '';
            end;
            if (LastClipboardOwner = 'arsclip.exe') then begin
                LastClipboardOwner := '';
            end;

            //HandleBrokenChain;
        end;
        HandleBrokenChain;
        // clean up the previous copy
        // store for next time this event fires
    end;
end;



end.
