////////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2001-2023 Alexey Kuryakin daqgroup@mail.ru under MIT license //
////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////
// This file is part of the CRW-DAQ project by DaqGroup - component CRWLIB.   //
////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////
// Purpose:                                                                   //
// Window manager control routines.                                           //
////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////
// History:                                                                   //
// 20230816 - Modified for FPC (A.K.) from _task                              //
////////////////////////////////////////////////////////////////////////////////

unit _crw_wmctrl; // Window control routines.

{$I _crw_sysdef}

{$IFDEF FPC}{$mode Delphi}{$ENDIF}{$H+}

{$WARN 5023 off : Unit "$1" not used in $2}

interface

uses
 _crw_sharemem, // NB: THIS UNIT MUST BE FIRST !!!
 {$IFDEF UNIX} cthreads, dl, {$ENDIF}
 {$IFDEF WINDOWS} windows, {$ENDIF}
 sysutils, classes, math, types, lcltype, ctypes,
 {$IFDEF UNIX} unix, baseunix, x, xlib, xatom, {$ENDIF}
 _crw_alloc, _crw_ef, _crw_str, _crw_fio, _crw_proc, _crw_crypt;

 {
 Get list of desktop windows with given Mode.
 Mode 0: $Hex(HWND), PID, CLASS, TITLE             Default
 Mode 1: HWND, PID, "CLASS", "TITLE"               Like Default, but quoted
 Mode 2: 0xHex(HWND) DESK HOST TITLE               Like wmctrl -l
 Mode 3: 0xHex(HWND) DESK PID HOST TITLE           Like wmctrl -lp
 Mode 4: 0xHex(HWND) DESK CLASS HOST TITLE         Like wmctrl -lx
 Mode 5: 0xHex(HWND) DESK PID CLASS HOST TITLE     Like wmctrl -lpx
 Mode 6: HWND, DESK, PID, "CLASS", "HOST", "TITLE" Like wmctrl -lpx, but quoted
 Uses filters by PID,CLASS,TITLE to select windows.
 PID=0 or empty CLASS,TITLE means "any value".
 }
function GetListOfWindows(aPid:culong; aClass,aTitle:LongString; aMode:Integer=0):LongString; overload;

 {
 Get list of desktop windows by arg='PID "Window Class" "Window Title"'.
 }
function GetListOfWindows(const arg:LongString; aMode:Integer=0):LongString; overload;

 {
 Find window by PID, CLASS, TITLE and return it's handle.
 }
function FindWindowByPidClassTitle(aPid:culong; aClass,aTitle:LongString; aIndex:Integer=0):HWND;

 { For internal use. Format line with given Mode. }
function wmctrl_format_line(aWin:HWND; aDesk,aPid:culong;
               aClass:LongString; cLen:culong; aHost:LongString;
               aTitle:LongString; aMode:Integer):LongString;

{$IFDEF UNIX}
 { For internal use. Open display if one not opened. Remember if need to close after. }
function wmctrl_open_display(var disp:PDisplay; out NeedClose:Boolean):Boolean;

 { For internal use. Close display if need to close. }
procedure wmctrl_close_display(disp:PDisplay; NeedClose:Boolean);

 { Get property (prop_name) of window (win) on display (disp) as string dump. }
function wmctrl_get_property(disp:PDisplay; win:TWindow; xa_prop_type:TAtom; prop_name:PChar):LongString;

 { Send a client message (msg) to window (win). }
function wmctrl_client_msg(disp:PDisplay; win:TWindow; msg:PChar;
        data0,data1,data2,data3,data4:clong):Boolean;

 { Get number of destops on this display (disp). }
function wmctrl_get_number_of_desktops(disp:PDisplay; def:culong=0):culong;

 { Switch to specified 0-based desktop number. }
function wmctrl_switch_to_desktop(disp:PDisplay; desktop:culong):Boolean;

 { Get active desktop number. }
function wmctrl_get_active_desktop(disp:PDisplay; def:culong=0):culong;

 { Get client's TWindow list as delimited string. }
function wmctrl_get_client_list(disp:PDisplay; Delim:LongString=EOL):LongString;

 { Get window desktop ID. }
function wmctrl_get_desktop_id(disp:PDisplay; win:TWindow; def:culong=0):culong;

  { Get window PID. }
function wmctrl_get_window_pid(disp:PDisplay; win:TWindow; def:culong=0):culong;

  { Get client machine name. }
function wmctrl_get_client_machine(disp:PDisplay; win:TWindow; def:LongString=''):LongString;

  { Get window title. }
function wmctrl_get_window_title(disp:PDisplay; win:TWindow; def:LongString=''):LongString;

  { Get window class. }
function wmctrl_get_window_class(disp:PDisplay; win:TWindow; def:LongString=''):LongString;

 { Get active window handle. }
function wmctrl_get_active_window(disp:PDisplay; def:TWindow=0):TWindow;

 { Activate specified window and switch to desktop if required. }
function wmctrl_activate_window(disp:PDisplay; win:TWindow; switch_desktop:Boolean):Boolean;

 { Send a message to Close specified window. }
function wmctrl_close_window(disp:PDisplay; win:TWindow):Boolean;

 { Kill process associated with specified window. }
function wmctrl_kill_window(disp:PDisplay; win:TWindow; sig:Integer=SIGTERM):Boolean;
{$ENDIF ~UNIX}

implementation

const // Buffer size for data.
 MAX_PROPERTY_VALUE_LEN = 4096;

{$IFDEF WINDOWS}

type
 TEnumGlowRec = packed record
  List:TStringList;
  lpClass,lpTitle,Buffer:LongString;
  Pid:culong;
  Mode:Integer;
 end;

function EnumGlowProc(Wnd:HWND; Param:LPARAM):BOOL; stdcall;
var aPid:DWORD; aClass,aTitle:LongString;
begin
 Result:=true;
 if (Param<>0) then with TEnumGlowRec(Pointer(Param)^) do
 try
  if (Wnd=0) or not IsWindow(Wnd) then Exit;
  aPid:=0; GetWindowThreadProcessId(Wnd,@aPid);
  if (Pid<>0) and (Pid<>aPid) then Exit;
  aClass:=''; aTitle:='';
  try
   SetString(aClass,PChar(Buffer),GetClassName(Wnd,PChar(Buffer),Length(Buffer)));
   if (lpClass<>'') and not SameText(lpClass,aClass) then Exit;
   SetString(aTitle,PChar(Buffer),GetWindowText(Wnd,PChar(Buffer),Length(Buffer)));
   if (lpTitle<>'') and not SameText(lpTitle,aTitle) then Exit;
   List.Add(wmctrl_format_line(Wnd,0,aPid,aClass,1,HostName,aTitle,Mode));
  finally
   aClass:=''; aTitle:='';
  end;
 except
  on E:Exception do BugReport(E,nil,'EnumGlowProc');
 end;
end;

// List windows as $HWND, PID, Class, Title
function GetListOfWindows(aPid:culong; aClass,aTitle:LongString; aMode:Integer=0):LongString;
var R:TEnumGlowRec; const BuffSize=MAX_PROPERTY_VALUE_LEN;
begin
 Result:='';
 try
  SafeFillChar(R,SizeOf(R),0);
  SetLength(R.Buffer,BuffSize);
  R.List:=TStringList.Create;
  // List windows as $HWND, PID, Class, Title
  try
   R.Pid:=aPid;
   R.lpClass:=aClass;
   R.lpTitle:=aTitle;
   R.Mode:=aMode;
   EnumWindows(@EnumGlowProc,LPARAM(@R));
   if (R.List.Count>0) then Result:=R.List.Text;
  finally
   R.List.Free;
   R.lpClass:='';
   R.lpTitle:='';
   R.Buffer:='';
  end;
 except
  on E:Exception do BugReport(E,nil,'GetListOfWindows');
 end;
end;
{$ENDIF ~WINDOWS}

{$IFDEF UNIX}
function wmctrl_open_display(var disp:PDisplay; out NeedClose:Boolean):Boolean;
begin
 NeedClose:=false;
 if not Assigned(disp) then begin
  disp:=XOpenDisplay(nil);
  NeedClose:=true;
 end;
 Result:=Assigned(disp);
end;

procedure wmctrl_close_display(disp:PDisplay; NeedClose:Boolean);
begin
 if Assigned(disp) then
 if NeedClose then XCloseDisplay(disp);
end;

function wmctrl_get_property(disp:PDisplay; win:TWindow; xa_prop_type:TAtom; prop_name:PChar):LongString;
var xa_prop_name,xa_ret_type:TAtom; ret_format:cint; ret_nitems,ret_bytes_after:culong; ret_prop:Pcuchar;
var NeedClose:Boolean; item_size,data_size:Integer;
begin
 Result:='';
 if (win<>0) then
 if Assigned(prop_name) then
 try
  if wmctrl_open_display(disp,NeedClose) then
  try
   xa_prop_name:=XInternAtom(disp,prop_name,false);
   if (XGetWindowProperty(disp,win,xa_prop_name,0,(MAX_PROPERTY_VALUE_LEN div 4),false,
       xa_prop_type,@xa_ret_type,@ret_format,@ret_nitems,@ret_bytes_after,@ret_prop)=Success)
   then
   try
    if (xa_ret_type=xa_prop_type) then begin
     case ret_format of
      8:   item_size:=SizeOf(Byte);   // expected 1
      16:  item_size:=SizeOf(Word);   // expected 2
      32:  item_size:=SizeOf(culong); // expected 4/8 on 32/64 CPU
      else item_size:=0;
     end;
     data_size:=item_size*ret_nitems;
     Result:=StringBuffer(PChar(ret_prop),data_size);
    end;
   finally
    XFree(ret_prop);
   end;
  finally
   wmctrl_close_display(disp,NeedClose);
  end;
 except
  on E:Exception do BugReport(E,nil,'get_property');
 end;
end;

function wmctrl_client_msg(disp:PDisplay; win:TWindow; msg:PChar;
        data0,data1,data2,data3,data4:clong):Boolean;
var NeedClose:Boolean; event:TXEvent; mask:clong;
begin
 Result:=false;
 if (win<>0) then
 if Assigned(msg) then
 try
  if wmctrl_open_display(disp,NeedClose) then
  try
   SafeFillChar(event,SizeOf(event),0);
   mask:=SubstructureRedirectMask or SubstructureNotifyMask;
   event.xclient._type:=ClientMessage;
   event.xclient.serial:=0;
   event.xclient.send_event:=Ord(true);
   event.xclient.message_type:=XInternAtom(disp,msg,false);
   event.xclient.window:=win;
   event.xclient.format:=32;
   event.xclient.data.l[0]:=data0;
   event.xclient.data.l[1]:=data1;
   event.xclient.data.l[2]:=data2;
   event.xclient.data.l[3]:=data3;
   event.xclient.data.l[4]:=data4;
   Result:=(XSendEvent(disp,DefaultRootWindow(disp),false,mask,@event)<>0);
  finally
   wmctrl_close_display(disp,NeedClose);
  end;
 except
  on E:Exception do BugReport(E,nil,'wmctrl_client_msg');
 end;
end;

function wmctrl_get_number_of_desktops(disp:PDisplay; def:culong=0):culong;
var NeedClose:Boolean; rwin:TWindow; s:LongString;
begin
 Result:=def;
 if wmctrl_open_display(disp,NeedClose) then
 try
  rwin:=DefaultRootWindow(disp);
  s:=wmctrl_get_property(disp,rwin,XA_CARDINAL,'_NET_NUMBER_OF_DESKTOPS');
  if (Length(s)=SizeOf(culong)) then Result:=culong(Pointer(s)^);
 finally
  wmctrl_close_display(disp,NeedClose);
 end;
end;

function wmctrl_switch_to_desktop(disp:PDisplay; desktop:culong):Boolean;
var NeedClose:Boolean; rwin:TWindow; desknum:culong;
begin
 Result:=false;
 if wmctrl_open_display(disp,NeedClose) then
 try
  desknum:=wmctrl_get_number_of_desktops(disp);
  if (desknum>0) and (desktop<desknum) then begin
   rwin:=DefaultRootWindow(disp);
   Result:=wmctrl_client_msg(disp,rwin,'_NET_CURRENT_DESKTOP',desktop,0,0,0,0);
  end;
 finally
  wmctrl_close_display(disp,NeedClose);
 end;
end;

function wmctrl_get_active_desktop(disp:PDisplay; def:culong=0):culong;
var NeedClose:Boolean; rwin:TWindow; s:LongString;
begin
 Result:=def;
 if wmctrl_open_display(disp,NeedClose) then
 try
  rwin:=DefaultRootWindow(disp);
  s:=wmctrl_get_property(disp,rwin,XA_CARDINAL,'_NET_CURRENT_DESKTOP');
  if (Length(s)=SizeOf(culong)) then Result:=culong(Pointer(s)^);
 finally
  wmctrl_close_display(disp,NeedClose);
 end;
end;

function wmctrl_get_client_list(disp:PDisplay; Delim:LongString=EOL):LongString;
var NeedClose:Boolean; rwin:TWindow; List:TStringList;
var pw:PWindow; buff,sw:LongString; iw,cw:Integer;
var tw:TWindow;
begin
 Result:='';
 if wmctrl_open_display(disp,NeedClose) then
 try
  List:=TStringList.Create;
  rwin:=DefaultRootWindow(disp);
  buff:=wmctrl_get_property(disp,rwin,XA_WINDOW,'_NET_CLIENT_LIST');
  if (buff<>'') then begin
   pw:=Pointer(buff);
   cw:=(Length(buff) div SizeOf(pw[0]));
   for iw:=0 to cw-1 do begin
    tw:=pw[iw];
    sw:=IntToStr(tw);
    List.Add(sw);
   end;
  end;
  if (buff='') then begin
   buff:=wmctrl_get_property(disp,rwin,XA_CARDINAL,'_WIN_CLIENT_LIST');
   if (buff<>'') then begin
    pw:=Pointer(buff);
    cw:=(Length(buff) div SizeOf(pw[0]));
    for iw:=0 to cw-1 do begin
     tw:=pw[iw];
     sw:=IntToStr(tw);
     List.Add(sw);
    end;
   end;
  end;
  Result:=Trim(List.Text);
  if (Delim<>EOL) then Result:=StringReplace(Result,EOL,Delim,[rfReplaceAll]);
 finally
  List.Free;
  wmctrl_close_display(disp,NeedClose);
 end;
end;

function wmctrl_get_desktop_id(disp:PDisplay; win:TWindow; def:culong=0):culong;
var NeedClose:Boolean; s:LongString;
begin
 Result:=def;
 if wmctrl_open_display(disp,NeedClose) then
 try
  s:=wmctrl_get_property(disp,win,XA_CARDINAL,'_NET_WM_DESKTOP');
  if (s='') then s:=wmctrl_get_property(disp,win,XA_CARDINAL,'_WIN_WORKSPACE');
  if (Length(s)=SizeOf(culong)) then Result:=culong(Pointer(s)^);
 finally
  wmctrl_close_display(disp,NeedClose);
 end;
end;

function wmctrl_get_window_pid(disp:PDisplay; win:TWindow; def:culong=0):culong;
var NeedClose:Boolean; s:LongString;
begin
 Result:=def;
 if wmctrl_open_display(disp,NeedClose) then
 try
  s:=wmctrl_get_property(disp,win,XA_CARDINAL,'_NET_WM_PID');
  if (Length(s)=SizeOf(culong)) then Result:=culong(Pointer(s)^);
 finally
  wmctrl_close_display(disp,NeedClose);
 end;
end;

function wmctrl_get_client_machine(disp:PDisplay; win:TWindow; def:LongString=''):LongString;
var NeedClose:Boolean;
begin
 Result:=def;
 if wmctrl_open_display(disp,NeedClose) then
 try
  Result:=wmctrl_get_property(disp,win,XA_STRING,'WM_CLIENT_MACHINE');
  if (Result='') then Result:=def else Result:=TrimRightChars(Result,[#0]);
 finally
  wmctrl_close_display(disp,NeedClose);
 end;
end;

function wmctrl_get_window_title(disp:PDisplay; win:TWindow; def:LongString=''):LongString;
var NeedClose:Boolean;
begin
 Result:=def;
 if wmctrl_open_display(disp,NeedClose) then
 try
  Result:=wmctrl_get_property(disp,win,XInternAtom(disp,'UTF8_STRING',false),'_NET_WM_NAME');
  if (Result='') then Result:=wmctrl_get_property(disp,win,XA_STRING,'WM_NAME');
  if (Result='') then Result:=def else Result:=TrimRightChars(Result,[#0]);
 finally
  wmctrl_close_display(disp,NeedClose);
 end;
end;

function wmctrl_get_window_class(disp:PDisplay; win:TWindow; def:LongString=''):LongString;
var NeedClose:Boolean;
begin
 Result:=def;
 if wmctrl_open_display(disp,NeedClose) then
 try
  Result:=wmctrl_get_property(disp,win,XA_STRING,'WM_CLASS');
  if (Result='') then Result:=def else Result:=StringReplace(TrimRightChars(Result,[#0]),#0,'.',[]);
 finally
  wmctrl_close_display(disp,NeedClose);
 end;
end;

function wmctrl_get_active_window(disp:PDisplay; def:TWindow=0):TWindow;
var NeedClose:Boolean; s:LongString; rwin:TWindow;
begin
 Result:=def;
 if wmctrl_open_display(disp,NeedClose) then
 try
  rwin:=DefaultRootWindow(disp);
  s:=wmctrl_get_property(disp,rwin,XA_WINDOW,'_NET_ACTIVE_WINDOW');
  if (Length(s)=SizeOf(culong)) then Result:=culong(Pointer(s)^);
 finally
  wmctrl_close_display(disp,NeedClose);
 end;
end;

function wmctrl_activate_window(disp:PDisplay; win:TWindow; switch_desktop:Boolean):Boolean;
var NeedClose:Boolean; desk:culong;
begin
 Result:=false;
 if wmctrl_open_display(disp,NeedClose) then
 try
  desk:=wmctrl_get_desktop_id(disp,win,High(desk));
  if switch_desktop and (desk<High(desk))
  then wmctrl_switch_to_desktop(disp,desk);
  Result:=wmctrl_client_msg(disp,win,'_NET_ACTIVE_WINDOW',0,0,0,0,0);
  XMapRaised(disp,win);
 finally
  wmctrl_close_display(disp,NeedClose);
 end;
end;

function wmctrl_close_window(disp:PDisplay; win:TWindow):Boolean;
var NeedClose:Boolean;
begin
 Result:=false;
 if wmctrl_open_display(disp,NeedClose) then
 try
  Result:=wmctrl_client_msg(disp,win,'_NET_CLOSE_WINDOW',0,0,0,0,0);
  // XSync(disp,false); // ?
 finally
  wmctrl_close_display(disp,NeedClose);
 end;
end;

function wmctrl_kill_window(disp:PDisplay; win:TWindow; sig:Integer=SIGTERM):Boolean;
var NeedClose:Boolean; pid:culong; ec:cint;
begin
 Result:=false;
 if wmctrl_open_display(disp,NeedClose) then
 try
  pid:=wmctrl_get_window_pid(disp,win);
  if (pid<>0) then begin
   ec:=fpKill(pid,sig);
   Result:=(ec=0);
  end;
 finally
  wmctrl_close_display(disp,NeedClose);
 end;
end;

function GetListOfWindows(aPid:culong; aClass,aTitle:LongString; aMode:Integer=0):LongString;
var disp:PDisplay; List,Wins:TStringList; dsk,pid:culong; line,scm,swt,swc:LongString;
var win:TWindow; sWin:LongString; cWin,iWin,lcm:Integer;
begin
 Result:='';
 try
  disp:=XOpenDisplay(nil);
  if (disp=nil) then Exit;
  List:=TStringList.Create;
  Wins:=TStringList.Create;
  try
   sWin:=wmctrl_get_client_list(disp);
   Wins.Text:=sWin; cWin:=Wins.Count;
   lcm:=0;
   for iWin:=0 to cWin-1 do begin
    win:=StrToQWordDef(Wins[iWin],0);
    scm:=wmctrl_get_client_machine(disp,win);
    lcm:=Max(lcm,Length(scm));
   end;
   for iWin:=0 to cWin-1 do begin
    win:=StrToQWordDef(Wins[iWin],0);
    pid:=wmctrl_get_window_pid(disp,win,0);
    swc:=wmctrl_get_window_class(disp,win,'N/A');
    swt:=wmctrl_get_window_title(disp,win,'N/A');
    scm:=wmctrl_get_client_machine(disp,win,'N/A');
    dsk:=wmctrl_get_desktop_id(disp,win,High(dsk));
    if (aPid<>0) and (pid<>aPid) then continue;
    if (aClass<>'') and not SameText(swc,aClass) then continue;
    if (aTitle<>'') and not SameText(swt,aTitle) then continue;
    line:=wmctrl_format_line(win,dsk,pid,swc,lcm,scm,swt,aMode);
    //line:=Format('0x%.8x %2d %-6u %-20s %*s %s',[win,dsk,pid,swc,lcm,scm,swt]);
    List.Add(line);
   end;
   Result:=List.Text;
  finally
   Wins.Free;
   List.Free;
   XCloseDisplay(disp);
  end;
 except
  on E:Exception do BugReport(E,nil,'GetListOfWindows');
 end;
end;
{$ENDIF ~UNIX}

function wmctrl_format_line(aWin:HWND; aDesk,aPid:culong;
               aClass:LongString; cLen:culong; aHost:LongString;
               aTitle:LongString; aMode:Integer):LongString;
var qClass,qHost,qTitle:LongString;
begin
 qHost:=AnsiQuotedStr(aHost,QuoteMark);
 qClass:=AnsiQuotedStr(aClass,QuoteMark);
 qTitle:=AnsiQuotedStr(aTitle,QuoteMark);
 case aMode of
  //// Default
  //// $5800007, 4413, fly-term.fly-term, ~ : bash — Терминал Fly
  //////////////////////////////////////////////////////////////////////////////
  0:   Result:=Format('$%X, %u, %s, %s',[aWin,aPid,aClass,aTitle]);
  ////
  //// Like Default but Decimal with Quites and Commas
  //// 92274695, 4413, "fly-term.fly-term", "~ : bash — Терминал Fly"
  //////////////////////////////////////////////////////////////////////////////
  ////
  1:   Result:=Format('%u, %u, %s, %s',[aWin,aPid,qClass,qTitle]);
  //// Like wmctrl -l
  //// 0x05800007  1 y510p ~ : bash — Терминал Fly
  //////////////////////////////////////////////////////////////////////////////
  2:   Result:=Format('0x%.8x %2d %*s %s',[aWin,aDesk,cLen,aHost,aTitle]);
  ////
  //// Like wmctrl -lp
  //// 0x05800007  1 4413   y510p ~ : bash — Терминал Fly
  //////////////////////////////////////////////////////////////////////////////
  3:   Result:=Format('0x%.8x %2d %-6u %*s %s',[aWin,aDesk,aPid,cLen,aHost,aTitle]);
  ////
  //// Like wmctrl -lx
  //// 0x05800007  1 fly-term.fly-term    y510p ~ : bash — Терминал Fly
  //////////////////////////////////////////////////////////////////////////////
  4:   Result:=Format('0x%.8x %2d %-20s %*s %s',[aWin,aDesk,aClass,cLen,aHost,aTitle]);
  ////
  //// Like wmctrl -lpx
  //// 0x05800007  1 4413   fly-term.fly-term    y510p ~ : bash — Терминал Fly
  //////////////////////////////////////////////////////////////////////////////
  5:   Result:=Format('0x%.8x %2d %-6u %-20s %*s %s',[aWin,aDesk,aPid,aClass,cLen,aHost,aTitle]);
  ////
  //// Special like "wmctrl -lpx" but Decimal with Quotes and Commas
  //// 92274695, 1, 4413, "fly-term.fly-term", "y510p", "~ : bash — Терминал Fly"
  //////////////////////////////////////////////////////////////////////////////
  6:   Result:=Format('%u, %u, %u, %s, %s, %s',[aWin,aDesk,aPid,qClass,qHost,qTitle]);
  ////
  //// Fallback = Default
  //// $5800007, 4413, fly-term.fly-term, ~ : bash — Терминал Fly
  //////////////////////////////////////////////////////////////////////////////
  else Result:=Format('$%X, %u, %s, %s',[aWin,aPid,aClass,aTitle]);
 end;
end;

function GetListOfWindows(const arg:LongString; aMode:Integer=0):LongString;
var pid:Integer; w1,w2:LongString;
begin
 Result:='';
 pid:=StrToIntDef(ExtractFirstParam(arg,QuoteMark,ScanSpaces),0);
 w2:=SkipFirstParam(arg,QuoteMark,ScanSpaces);
 w1:=ExtractFirstParam(w2,QuoteMark,ScanSpaces);
 w2:=SkipFirstParam(w2,QuoteMark,ScanSpaces);
 if (StrFetch(w2,1)=QuoteMark) then w2:=AnsiDeQuotedStr(w2,QuoteMark);
 Result:=GetListOfWindows(pid,w1,w2,aMode);
end;

function FindWindowByPidClassTitle(aPid:culong; aClass,aTitle:LongString; aIndex:Integer=0):HWND;
var list,line,swin:LongString;
begin
 Result:=0;
 if (aIndex<0) then Exit;
 try
  list:=GetListOfWindows(aPid,aClass,aTitle);
  line:=ExtractWord(aIndex+1,list,[ASCII_NUL,ASCII_CR,ASCII_LF]);
  swin:=ExtractWord(1,line,ScanSpaces);
  Result:=StrToQWordDef(swin,0);
 except
  on E:Exception do BugReport(E,nil,'FindWindowByPidClassTitle');
 end;
end;

///////////////////////////////////////
// Unit initialization and finalization
///////////////////////////////////////

procedure Init_crw_wmctrl;
begin
end;

procedure Free_crw_wmctrl;
begin
end;

initialization

 Init_crw_wmctrl;

finalization

 Free_crw_wmctrl;

end.

//////////////
// END OF FILE
//////////////

