{
  Everything search engine interface via IPC

  Copyright (C) 2017-2023 Alexander Koblov (alexx2000@mail.ru)

  Based on Everything command line interface source

  Copyright (C) 2016 David Carpenter

  Permission is hereby granted, free of charge, to any person obtaining
  a copy of this software and associated documentation files (the
  "Software"), to deal in the Software without restriction, including
  without limitation the rights to use, copy, modify, merge, publish,
  distribute, sublicense, and/or sell copies of the Software, and to
  permit persons to whom the Software is furnished to do so, subject to
  the following conditions:

  The above copyright notice and this permission notice shall be included
  in all copies or substantial portions of the Software.

  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
  IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
  CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
  TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
  SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
}

unit everything;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, Windows;

const
  EVERYTHING_DSX_WNDCLASS         = 'EVERYTHING_DSX';

const
  COPYDATA_IPCTEST_QUERYCOMPLETEW = 0;
  MSGFLT_RESET                    = 0;
  MSGFLT_ALLOW                    = 1;
  MSGFLT_DISALLOW                 = 2;

  EVERYTHING_IPC_COPYDATAQUERYW   = 2;

  EVERYTHING_IPC_SEARCH_WNDCLASS  = 'EVERYTHING';
  EVERYTHING_IPC_WNDCLASS         = 'EVERYTHING_TASKBAR_NOTIFICATION';

  EVERYTHING_IPC_ALLRESULTS       = $FFFFFFFF; // all results

  EVERYTHING_IPC_FOLDER           = $00000001;  // The item is a folder. (its a file if not set)
  EVERYTHING_IPC_DRIVE            = $00000002;  // The folder is a drive. Path will be an empty string.

  // search flags for querys
  EVERYTHING_IPC_MATCHCASE        = $00000001;  // match case
  EVERYTHING_IPC_MATCHPATH        = $00000004;  // include paths in search
  EVERYTHING_IPC_REGEX            = $00000008;  // enable regex

type
  PChangeFilterStruct = ^TChangeFilterStruct;
  TChangeFilterStruct = record
    cbSize: DWORD;
    ExtStatus: DWORD;
  end;

  {$push}{$packrecords 1}

  TEVERYTHING_IPC_QUERYW = record
    // the window that will receive the new results.
    reply_hwnd: HWND;

    // the value to set the dwData member in the COPYDATASTRUCT struct
    // sent by Everything when the query is complete.
    reply_copydata_message: ULONG_PTR;

    // search flags (see EVERYTHING_MATCHCASE | EVERYTHING_MATCHWHOLEWORD | EVERYTHING_MATCHPATH)
    search_flags: DWORD;

    // only return results after 'offset' results (0 to return the first result)
    // useful for scrollable lists
    offset: DWORD;

    // the number of results to return
    // zero to return no results
    // EVERYTHING_IPC_ALLRESULTS to return ALL results
    max_results: DWORD;

    // null terminated string. arbitrary sized search_string buffer.
    search_string: WCHAR;
  end;

  PEVERYTHING_IPC_ITEMW = ^TEVERYTHING_IPC_ITEMW;
  TEVERYTHING_IPC_ITEMW = record
    // item flags
    flags: DWORD;

    // The offset of the filename from the beginning of the list structure.
    // (wchar_t *)((char *)everything_list + everythinglist->name_offset)
    filename_offset: DWORD;

    // The offset of the filename from the beginning of the list structure.
    // (wchar_t *)((char *)everything_list + everythinglist->path_offset)
    path_offset: DWORD;
  end;

  PEVERYTHING_IPC_LISTW = ^TEVERYTHING_IPC_LISTW;
  TEVERYTHING_IPC_LISTW = record
    // the total number of folders found.
    totfolders: DWORD;

    // the total number of files found.
    totfiles: DWORD;

    // totfolders + totfiles
    totitems: DWORD;

    // the number of folders available.
    numfolders: DWORD;

    // the number of files available.
    numfiles: DWORD;

    // the number of items available.
    numitems: DWORD;

    // index offset of the first result in the item list.
    offset: DWORD;

    // arbitrary sized item list.
    // use numitems to determine the actual number of items available.
    items: TEVERYTHING_IPC_ITEMW;
  end;

  {$pop}

type
  TFindData = class
    Cancel: Boolean;
    PluginNumber: Integer;
    FoundCallback: procedure(FileName: PWideChar; UserData: Pointer);
  end;

procedure Start(FileMask: String; Flags: Integer; FindData: TFindData);

implementation

var
  ChangeWindowMessageFilterEx: function(hWnd: HWND; message: UINT; action: DWORD; filter: PChangeFilterStruct): BOOL; stdcall;

function SendQuery(hwnd: HWND; num: DWORD; const search_string: UnicodeString; search_flags: integer): Boolean;
var
  len: Int32;
  size: Int32;
  cds: COPYDATASTRUCT;
  everything_hwnd: HWND;
  query: ^TEVERYTHING_IPC_QUERYW;
begin
  everything_hwnd:= FindWindow(EVERYTHING_IPC_WNDCLASS, nil);
  if (everything_hwnd = 0) then
    MessageBoxW(0, 'Everything not found!', nil, MB_OK or MB_ICONERROR)
  else begin
    len:= Length(search_string);
    size := SizeOf(TEVERYTHING_IPC_QUERYW) + len * SizeOf(WideChar);

    query:= GetMem(size);
    if (query = nil) then
    begin
      MessageBoxW(0, PWideChar(UnicodeString(SysErrorMessage(E_OUTOFMEMORY))), nil, MB_OK or MB_ICONERROR);
      Exit(False);
    end;
    try
      query^.offset := 0;
      query^.max_results := num;
      query^.reply_copydata_message := COPYDATA_IPCTEST_QUERYCOMPLETEW;
      query^.search_flags := search_flags;
      query^.reply_hwnd := hwnd;
      StrPLCopy(@query^.search_string, search_string, len);

      cds.cbData := size;
      cds.dwData := EVERYTHING_IPC_COPYDATAQUERYW;
      cds.lpData := query;

      if (SendMessage(everything_hwnd, WM_COPYDATA, WPARAM(hwnd), LPARAM(@cds)) <> 0) then
      begin
        Exit(True);
      end;
    finally
      FreeMem(query);
    end;
  end;
  Result:= False;
end;

function EVERYTHING_IPC_ITEMPATHW(AList: PEVERYTHING_IPC_LISTW; Item: PEVERYTHING_IPC_ITEMW): PWideChar; inline;
begin
  Result:= PWideChar(PByte(AList) + Item^.path_offset);
end;

function EVERYTHING_IPC_ITEMFILENAMEW(AList: PEVERYTHING_IPC_LISTW; Item: PEVERYTHING_IPC_ITEMW): PWideChar; inline;
begin
  Result:= PWideChar(PByte(AList) + Item^.filename_offset);
end;

procedure ListResults(hWnd: HWND; AList: PEVERYTHING_IPC_LISTW);
var
  I: DWORD = 0;
  Temp: UnicodeString;
  FileName: PWideChar;
  FindData: TFindData;
  Item: PEVERYTHING_IPC_ITEMW;
  dwOldLong: LONG_PTR absolute FindData;
begin
  dwOldLong:= GetWindowLongPtr(hWnd, GWL_USERDATA);

  while I < AList^.numitems do
  begin
    Item:= PEVERYTHING_IPC_ITEMW(@AList^.items) + I;

    if (Item^.flags and EVERYTHING_IPC_DRIVE) <> 0 then
    begin
      FileName:= EVERYTHING_IPC_ITEMFILENAMEW(AList, Item);
    end
    else
    begin
      Temp:= UnicodeString(EVERYTHING_IPC_ITEMPATHW(AList, Item)) + PathDelim + UnicodeString(EVERYTHING_IPC_ITEMFILENAMEW(AList, Item));
      FileName:= PWideChar(Temp);
    end;

    if (FindData.Cancel) then Break;

    FindData.FoundCallback(FileName, FindData);
    Inc(I);
  end;
  FindData.FoundCallback(nil, FindData);
  PostMessage(hWnd, WM_CLOSE, 0, 0);
end;

function WindowProc(hwnd: HWND; msg: UINT; wParam: WPARAM; lParam: LPARAM): LRESULT; stdcall;
var
  cds: PCopyDataStruct absolute lParam;
begin
  Result:= 0;
  case msg of
    WM_COPYDATA:
    begin
      if cds^.dwData = COPYDATA_IPCTEST_QUERYCOMPLETEW then
      begin
        ListResults(hwnd, PEVERYTHING_IPC_LISTW(cds^.lpData));
        Exit(1);
      end;
    end;
    WM_CLOSE:
      DestroyWindow(hwnd);
    else
      Result:= DefWindowProc(hwnd, msg, wParam, lParam);
  end;
end;

procedure Start(FileMask: String; Flags: Integer; FindData: TFindData);
var
  hWnd: Windows.HWND;
  dwNewLong: LONG_PTR absolute FindData;
begin
  hWnd := CreateWindow(EVERYTHING_DSX_WNDCLASS,
                       '', 0, 0, 0, 0, 0, 0, 0, HINSTANCE,nil);

  if Assigned(ChangeWindowMessageFilterEx) then
  begin
    ChangeWindowMessageFilterEx(hWnd, WM_COPYDATA, MSGFLT_ALLOW, nil);
  end;

  SetWindowLongPtr(hWnd, GWL_USERDATA, dwNewLong);

  if not SendQuery(hWnd, EVERYTHING_IPC_ALLRESULTS, UnicodeString(FileMask), Flags) then
  begin
    FindData.FoundCallback(nil, FindData);
    PostMessage(hWnd, WM_CLOSE, 0, 0);
  end;
end;

procedure Initialize;
var
  hUser32: HMODULE;
  wcex: TWndClassEx;
begin
  wcex:= Default(TWndClassEx);
  wcex.cbSize:= SizeOf(TWndClassEx);
  wcex.hInstance:= System.HINSTANCE;
  wcex.lpfnWndProc:= @WindowProc;
  wcex.lpszClassName:= EVERYTHING_DSX_WNDCLASS;

  if (RegisterClassEx(@wcex) = 0) then
  begin
    // WriteLn('failed to register IPCTEST window class');
  end;
  hUser32:= GetModuleHandle(User32);
  Pointer(ChangeWindowMessageFilterEx):= GetProcAddress(hUser32, 'ChangeWindowMessageFilterEx');
end;

initialization
  Initialize;

end.

