{-------------------------------------------------------------------------------
 
 Copyright (c) 1999-2021 Ralf Junker, Yunqa
 Internet: https://www.yunqa.de
 E-Mail:   delphi@yunqa.de

-------------------------------------------------------------------------------}

unit DISHChangeNotify;

{$I DICompilers.inc}

interface

uses
  Windows, Messages, Classes, {$IFNDEF COMPILER_6_UP}Forms, {$ENDIF}ShlObj;

const

  SHCNRF_InterruptLevel = $0001;

  SHCNRF_ShellLevel = $0002;

  SHCNRF_RecursiveInterrupt = $1000;

type
  TDICustomSHChangeNotify = class;

  TDIShChangeNotifyEvent = procedure(
    const ASender: TDICustomSHChangeNotify;
    const AEvent: Cardinal;
    const APidl1, APidl2: PItemIDList) of object;

  TDICustomSHChangeNotify = class(TComponent)
  private
    FActive: Boolean;
    fEvents: Cardinal;
    FHWnd: hWnd;
    FHNotify: THandle;
    FNotification: PItemIDList;
    FNotificationRecursive: Boolean;
    fSources: Cardinal;
    FOnNotify: TDIShChangeNotifyEvent;
    procedure SetActive(const AValue: Boolean);
    procedure SetEvents(const AValue: Cardinal);
    procedure SetNotifyPidl(const AValue: PItemIDList);
    procedure SetSources(const AValue: Cardinal);
    procedure ShellNotifyDeregister;
    procedure ShellNotifyRegister;
    procedure WndProc(var AMsg: TMessage);
    procedure SetNotifyRecursive(const AValue: Boolean);
  protected
    procedure DoNotify(const AEvent: Integer; const APidl1, APidl2: PItemIDList); dynamic;
    procedure Loaded; override;
    property Events: Cardinal read fEvents write SetEvents default SHCNE_ALLEVENTS;
    property NotifyPidl: PItemIDList read FNotification write SetNotifyPidl;
    property NotifyRecursive: Boolean read FNotificationRecursive write SetNotifyRecursive;
    property Sources: Cardinal read fSources write SetSources default SHCNRF_InterruptLevel or SHCNRF_ShellLevel;
    property OnNotify: TDIShChangeNotifyEvent read FOnNotify write FOnNotify;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    function Registered: Boolean;
  published
    property Active: Boolean read FActive write SetActive;
  end;

  TDISHChangeNotify = class(TDICustomSHChangeNotify)
  public
    property Events;
    property NotifyPidl;
    property NotifyRecursive;
    property Sources;
    property OnNotify;
  end;

procedure FreePidl(const APidl: PItemIDList);

function PathToPIDL(const APath: WideString): PItemIDList;
function PidlToPath(const APidl: PItemIDList): WideString;
procedure StrRetFree(var AStrRet: TStrRet);
function StrRetToStr(const AStrRet: TStrRet; const APidl: PItemIDList): WideString;

implementation

uses
  ActiveX, ShellAPI;

var
  ShellMalloc: IMalloc;

const

  SHCNRF_NewDelivery = $8000;

  WM_SHELLNOTIFY = WM_USER + 1;

type

  TSHChangeNotifyEntry = record
    pidl: PItemIDList;
    frecursive: BOOL;
  end;
  PSHChangeNotifyEntry = ^TSHChangeNotifyEntry;

function SHChangeNotifyRegister(hWnd: hWnd; fSources: DWORD; fEvents: ulong; wMsg: uInt; cEntries: DWORD; pfsne: PSHChangeNotifyEntry): THandle; stdcall; external shell32 Index 2;
function SHChangeNotifyDeregister(hWnd: hWnd): Boolean; stdcall; external shell32 Index 4;

procedure FreePidl(const APidl: PItemIDList);
begin
  ShellMalloc.Free(APidl);
end;

function PathToPIDL(const APath: WideString): PItemIDList;
var
  Desktop: IShellFolder;
  pchEaten, dwAttributes: Cardinal;
begin
  SHGetDesktopFolder(Desktop);
  dwAttributes := 0;
  if Assigned(Desktop) then
    Desktop.ParseDisplayName(0, nil, PWideChar(APath), pchEaten, Result, dwAttributes)
  else
    Result := nil;
end;

function PidlToPath(const APidl: PItemIDList): WideString;
var
  Desktop: IShellFolder;
  StrRet: TStrRet;
begin
  SHGetDesktopFolder(Desktop);
  FillChar(StrRet, SizeOf(StrRet), 0);
  Desktop.GetDisplayNameOf(APidl, SHGDN_FORPARSING, StrRet);
  Result := StrRetToStr(StrRet, APidl);
  StrRetFree(StrRet);
end;

procedure StrRetFree(var AStrRet: TStrRet);
begin
  with AStrRet do
    if (uType = STRRET_WSTR) and Assigned(pOleStr) then
      begin
        ShellMalloc.Free(pOleStr);
        pOleStr := nil;
      end;
end;

function StrRetToStr(const AStrRet: TStrRet; const APidl: PItemIDList): WideString;
var
  p: PAnsiChar;
begin
  with AStrRet do
    case uType of

      STRRET_CSTR:
        SetString(Result, cStr, lStrLenA(cStr));

      STRRET_OFFSET:
        if Assigned(APidl) then
          begin
            p := PAnsiChar(@APidl.mkid.abID[uOffset - SizeOf(APidl.mkid.cb)]);
            SetString(Result, p, APidl.mkid.cb - uOffset);
          end
        else
          Result := '';

      STRRET_WSTR:
        Result := pOleStr;
    end;
end;

constructor TDICustomSHChangeNotify.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  fSources := SHCNRF_InterruptLevel or SHCNRF_ShellLevel;
  fEvents := SHCNE_ALLEVENTS;
  if not (csDesigning in ComponentState) then
    FHWnd := {$IFDEF COMPILER_6_UP}Classes{$ELSE}Forms{$ENDIF}.AllocateHWnd(WndProc);
end;

destructor TDICustomSHChangeNotify.Destroy;
begin
  ShellNotifyDeregister;
  FreePidl(FNotification);
  inherited;
end;

procedure TDICustomSHChangeNotify.DoNotify(const AEvent: Integer; const APidl1, APidl2: PItemIDList);
begin
  if Assigned(FOnNotify) then
    FOnNotify(Self, AEvent, APidl1, APidl2);
end;

procedure TDICustomSHChangeNotify.Loaded;
begin
  inherited;
  if FActive and not Registered then
    ShellNotifyRegister;
end;

procedure TDICustomSHChangeNotify.ShellNotifyDeregister;
begin
  if Registered then
    begin
      SHChangeNotifyDeregister(FHNotify);
      FHNotify := 0;
    end;
end;

procedure TDICustomSHChangeNotify.ShellNotifyRegister;
var
  NR: TSHChangeNotifyEntry;
  s: Cardinal;
begin
  ShellNotifyDeregister;
  s := fSources and not SHCNRF_NewDelivery;
  NR.pidl := FNotification;
  NR.frecursive := FNotificationRecursive;
  FHNotify := SHChangeNotifyRegister(FHWnd, s, fEvents, WM_SHELLNOTIFY, 1, @NR);
end;

function TDICustomSHChangeNotify.Registered: Boolean;
begin
  Result := FHNotify <> 0;
end;

procedure TDICustomSHChangeNotify.SetActive(const AValue: Boolean);
begin
  if FActive <> AValue then
    begin
      FActive := AValue;
      if not (csDesigning in ComponentState) then
        if FActive then
          begin
            if not Registered then
              ShellNotifyRegister;
          end
        else
          ShellNotifyDeregister;
    end;
end;

procedure TDICustomSHChangeNotify.SetEvents(const AValue: Cardinal);
begin
  if fEvents <> AValue then
    begin
      fEvents := AValue;
      if FActive then
        ShellNotifyRegister;
    end;
end;

procedure TDICustomSHChangeNotify.SetNotifyPidl(const AValue: PItemIDList);
begin
  FNotification := AValue;
  if FActive then
    ShellNotifyRegister;
end;

procedure TDICustomSHChangeNotify.SetNotifyRecursive(const AValue: Boolean);
begin
  if FNotificationRecursive <> AValue then
    begin
      FNotificationRecursive := AValue;
      if FActive then
        ShellNotifyRegister;
    end;
end;

procedure TDICustomSHChangeNotify.SetSources(const AValue: Cardinal);
begin
  if fSources <> AValue then
    begin
      fSources := AValue;
      if FActive then
        ShellNotifyRegister;
    end;
end;

procedure TDICustomSHChangeNotify.WndProc(var AMsg: TMessage);
type

  TShellNotifyRec = record
    Pidl1: PItemIDList;
    Pidl2: PItemIDList;
  end;
  PShellNotifyRec = ^TShellNotifyRec;
begin
  case AMsg.Msg of
    WM_SHELLNOTIFY:
      DoNotify(
        AMsg.LParam,
        PShellNotifyRec(AMsg.wParam).Pidl1,
        PShellNotifyRec(AMsg.wParam).Pidl2);
  else
    AMsg.Result := DefWindowProc(FHWnd, AMsg.Msg, AMsg.wParam, AMsg.LParam);
  end;
end;

initialization
  SHGetMalloc(ShellMalloc);

finalization
  ShellMalloc := nil;

end.

