////////////////////////////////////////////////////////////////////////////////
// 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;

 {
 TWinManControl - Window Manager Control.
 Usually has only one instance - see wmctrl.
 Uses to manipulate windows: list,find,close,
 kill,move,resize,activate,etc.
 }
type
 TWinManControl=class(TMasterObject)
 private
  {$IFDEF UNIX}
  myDispHand:PDisplay;
  myDispName:LongString;
  {$ENDIF ~UNIX}
 public
  constructor Create;
  destructor  Destroy; override;
 public
  function  OpenDisplay:Boolean;
  procedure CloseDisplay;
 public
  function  ListWindows(aPid:TPid; aClass,aTitle:LongString; aMode:Integer=0):LongString;
  function  FindWindow(aPid:TPid; aClass,aTitle:LongString; aIndex:Integer=0):HWND;
 public // Name of Window Manager like fly-wm or windows
  function  WindowManagerName:LongString;
 public // Desktops.
  function  DesktopCount:Integer;
  function  ActiveDesktop:Integer;
  function  SwitchToDesktop(aDesktop:Integer):Boolean;
 public // Active window.
  function  ActiveWindow:HWND;
  function  ActivateWindow(win:HWND; switch_desktop:Boolean=true):Boolean;
 public // Get window properties.
  function  WindowPid(win:HWND):TPid;
  function  WindowDesktop(win:HWND):Integer;
  function  WindowHost(win:HWND):LongString;
  function  WindowTitle(win:HWND):LongString;
  function  WindowClass(win:HWND):LongString;
  function  WindowBounds(win:HWND):TRect;
  function  WindowStateList(win:HWND):LongString;
  function  WindowStateFlags(win:HWND):Cardinal;
  function  SupportedList:LongString;
 public // Operations
  function  MoveResizeWindow(win:HWND; arg:LongString):Boolean;
 public // Close window, kill process.
  function  CloseWindow(win:HWND):Boolean;
  function  KillWindow(win:HWND; sig:cint=0):Boolean;
 public // Service
  function  StrToWSF(str:LongString):Cardinal; // String to Window State Flags
  function  WSFToStr(wsf:Cardinal):LongString; // Window State Flags to String
 end;

function wmctrl:TWinManControl;

const // List of available window states
 wm_all_window_states='_NET_WM_STATE_MODAL,'
                     +'_NET_WM_STATE_STICKY,'
                     +'_NET_WM_STATE_MAXIMIZED_VERT,'
                     +'_NET_WM_STATE_MAXIMIZED_HORZ,'
                     +'_NET_WM_STATE_SHADED,'
                     +'_NET_WM_STATE_SKIP_TASKBAR,'
                     +'_NET_WM_STATE_SKIP_PAGER,'
                     +'_NET_WM_STATE_HIDDEN,'
                     +'_NET_WM_STATE_FULLSCREEN,'
                     +'_NET_WM_STATE_ABOVE,'
                     +'_NET_WM_STATE_BELOW,'
                     +'_NET_WM_STATE_DEMANDS_ATTENTION,'
                     +'_NET_WM_STATE_FOCUSED';
const // Window state flags
 WSF_MODAL             = $00000001;
 WSF_STICKY            = $00000002;
 WSF_MAXIMIZED_VERT    = $00000004;
 WSF_MAXIMIZED_HORZ    = $00000008;
 WSF_SHADED            = $00000010;
 WSF_SKIP_TASKBAR      = $00000020;
 WSF_SKIP_PAGER        = $00000040;
 WSF_HIDDEN            = $00000080;
 WSF_FULLSCREEN        = $00000100;
 WSF_ABOVE             = $00000200;
 WSF_BELOW             = $00000400;
 WSF_DEMANDS_ATTENTION = $00000800;
 WSF_FOCUSED           = $00001000;

const
 _NET_WM_STATE_REMOVE = 0; // remove/unset property
 _NET_WM_STATE_ADD    = 1; // add/set property
 _NET_WM_STATE_TOGGLE = 2; // toggle property

 {
 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:TPid; 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:TPid; aClass,aTitle:LongString; aIndex:Integer=0):HWND;

 { For internal use. Format line with given Mode. }
function wmctrl_format_line(aWin:HWND; aDesk:Integer; aPid:TPid;
               aClass:LongString; cLen:Integer; 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 binary string (data_dump). }
function wmctrl_get_property(disp:PDisplay; win:TWindow; xa_prop_type:TAtom;
         prop_name:PChar; out data_dump:LongString; count:PInteger=nil):Boolean;

 { 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;

 { Check if window manager supported property (prop). }
function wmctrl_is_supported(disp:PDisplay; prop:PChar):Boolean;

 { Get list of supported properties. }
function wmctrl_get_supported_list(disp:PDisplay; Delim:LongString=EOL):LongString;

 { Get window from _NET_SUPPORTING_WM_CHECK (to test WindowManager). }
function wmctrl_supporting_wm_check(disp:PDisplay):TWindow;

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

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

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

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

 { Get window desktop number, 0-bases. }
function wmctrl_get_window_desktop(disp:PDisplay; win:TWindow; def:Integer=0):Integer;

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

  { 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 window bounds. }
function wmctrl_get_window_bounds(disp:PDisplay; win:TWindow):TRect;

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

 {
 Move/Resize window by given arg.
 arg='gravity,x,y,w,h'. For example, '1,*,*,200,300'.
 gravity = 0..10 = Default(0),NorthWest(1),North(2),NorthEast(3),West(4),
           Center(5),East(6),SouthWest(7),South(8),SouthEast(9),Static(10).
 x,y,w,h = pos(x,y) and size(w,h). Use * to mark "not change".
 }
function wmctrl_window_move_resize(disp:PDisplay; win:TWindow; arg:LongString):Boolean;

 { 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}

{$IFDEF WINDOWS}
function winapi_get_window_class(win:HWND):LongString;
function winapi_get_window_title(win:HWND):LongString;
function winapi_get_window_bounds(win:HWND):TRect;
function winapi_close_window(win:HWND):Boolean;
function winapi_kill_pid(pid:DWORD; code:Integer):Boolean;
function winapi_kill_window(win:HWND; sig:Integer):Boolean;
{$ENDIF ~WINDOWS}

 { Convert point P to string 'X,Y'. }
function PointToStr(const P:TPoint; Delims:LongString=','):LongString;

 { Convert rectangle R to string 'Left,Top,Right,Bottom'. }
function RectToStr(const R:TRect; Delims:LongString=','):LongString;

implementation

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

{$IFDEF WINDOWS}

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

function EnumGlowProc(Wnd:HWND; Param:LPARAM):BOOL; stdcall;
var aPid:TPid; 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:TPid; aClass,aTitle:LongString; aMode:Integer=0):LongString;
var R:TEnumGlowRec; const BuffSize=MAX_WM_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;

function winapi_get_window_class(win:HWND):LongString;
var Buffer:LongString;
begin
 Result:='';
 if IsWindow(win) then begin
  Buffer:=StringBuffer(MAX_WM_PROPERTY_VALUE_LEN);
  SetString(Result,PChar(Buffer),GetClassName(win,PChar(Buffer),Length(Buffer)));
 end;
end;

function winapi_get_window_title(win:HWND):LongString;
var Buffer:LongString;
begin
 Result:='';
 if IsWindow(win) then begin
  Buffer:=StringBuffer(MAX_WM_PROPERTY_VALUE_LEN);
  SetString(Result,PChar(Buffer),GetWindowText(win,PChar(Buffer),Length(Buffer)));
 end;
end;

function winapi_get_window_bounds(win:HWND):TRect;
begin
 Result:=Rect(0,0,0,0);
 if IsWindow(win) then
 if not GetWindowRect(win,Result) then Result:=Rect(0,0,0,0);
end;

function winapi_close_window(win:HWND):Boolean;
begin
 Result:=false;
 if IsWindow(win) then
 Result:=PostMessage(win,WM_CLOSE,0,0);
end;

function winapi_kill_pid(pid:DWORD; code:Integer):Boolean;
var hp:THandle; ar:DWORD;
begin
 Result:=false;
 if (pid=0) then Exit;
 hp:=OpenProcess(PROCESS_QUERY_INFORMATION or PROCESS_TERMINATE,false,pid);
 if (hp<>0) then
 try
  Result:=TerminateProcess(hp,code);
 finally
  CloseHandle(hp);
 end;
end;

function winapi_kill_window(win:HWND; sig:Integer):Boolean;
var pid:DWORD;
begin
 Result:=false;
 if IsWindow(win) then begin
  pid:=0; if (GetWindowThreadProcessId(win,pid)=0) then Exit;
  if (pid=0) then Exit;
  case sig of
   SIGHUP:  Result:=winapi_close_window(win);
   SIGINT:  Result:=winapi_close_window(win);
   SIGQUIT: Result:=winapi_close_window(win);
   SIGABRT: Result:=winapi_close_window(win);
   SIGKILL: Result:=winapi_kill_pid(pid,sig);
   SIGTERM: Result:=winapi_close_window(win);
   else     Result:=winapi_kill_pid(pid,sig);
  end;
 end;
end;

function winapi_set_window_bounds(win:HWND; arg:LongString):Boolean;
const hasX=(1 shl 8); hasY=(1 shl 9); hasW=(1 shl 10); hasH=(1 shl 11);
var f,g,x,y,w,h,swp:cint; R:TRect;
begin
 Result:=false;
 if IsWindow(win) then begin
  R:=winapi_get_window_bounds(win);
  f:=0; // f=flags; arg=gravity,X,Y,Width,Height
  if TryStrToInt(ExtractWord(1,arg,ScanSpaces),g) then f:=g           else g:=-1;
  if TryStrToInt(ExtractWord(2,arg,ScanSpaces),x) then f:=(f or hasX) else x:=R.Left;
  if TryStrToInt(ExtractWord(3,arg,ScanSpaces),y) then f:=(f or hasY) else y:=R.Top;
  if TryStrToInt(ExtractWord(4,arg,ScanSpaces),w) then f:=(f or hasW) else w:=R.Width;
  if TryStrToInt(ExtractWord(5,arg,ScanSpaces),h) then f:=(f or hasH) else h:=R.Height;
  if not InRange(g,0,10) then Exit; // Invalid gravity found
  if not HasFlags(f,hasX+hasY+hasW+hasH) then Exit; // Bad coordinates
  swp:=SWP_NOACTIVATE;
  if not HasFlags(f,hasX+hasY) then swp:=swp or SWP_NOMOVE;
  if not HasFlags(f,hasW+hasH) then swp:=swp or SWP_NOSIZE;
  Result:=SetWindowPos(win,0,x,y,w,h,swp);
 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; out data_dump:LongString; count:PInteger=nil):Boolean;
var xa_prop_name,xa_ret_type:TAtom; ret_format:cint;
var ret_nitems,ret_bytes_after:culong; ret_prop:Pcuchar;
var NeedClose:Boolean; item_size,data_size:Integer;
begin
 Result:=false;
 data_dump:='';
 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_WM_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;
     data_dump:=StringBuffer(PChar(ret_prop),data_size);
     if Assigned(count) then count^:=ret_nitems;
     Result:=true;
    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);
   // XSync(disp,false); // ?
  finally
   wmctrl_close_display(disp,NeedClose);
  end;
 except
  on E:Exception do BugReport(E,nil,'wmctrl_client_msg');
 end;
end;

function wmctrl_is_supported(disp:PDisplay; prop:PChar):Boolean;
var NeedClose:Boolean; rwin:TWindow; s:LongString;
var xa,xa_prop:TAtom; list:PAtom; count,i:cint;
begin
 Result:=false;
 if wmctrl_open_display(disp,NeedClose) then
 try
  rwin:=DefaultRootWindow(disp);
  xa_prop:=XInternAtom(disp,prop,false);
  if wmctrl_get_property(disp,rwin,XA_ATOM,'_NET_SUPPORTED',s) then begin
   list:=Pointer(s); count:=(Length(s) div SizeOf(list[0]));
   for i:=0 to count-1 do begin
    xa:=list[i];
    if (xa=xa_prop) then begin
     Result:=true;
     Break;
    end;
   end;
  end;
 finally
  wmctrl_close_display(disp,NeedClose);
 end;
end;

function wmctrl_get_supported_list(disp:PDisplay; Delim:LongString=EOL):LongString;
var NeedClose:Boolean; rwin:TWindow; s:LongString; Lines:TStringList;
var xa:TAtom; list:PAtom; count,i:cint; name:PChar;
begin
 Result:='';
 if wmctrl_open_display(disp,NeedClose) then
 try
  Lines:=nil;
  try
   rwin:=DefaultRootWindow(disp);
   if wmctrl_get_property(disp,rwin,XA_ATOM,'_NET_SUPPORTED',s) then begin
    list:=Pointer(s); count:=(Length(s) div SizeOf(list[0]));
    if (count>4) then Lines:=TStringList.Create;
    for i:=0 to count-1 do begin
     xa:=list[i];
     name:=XGetAtomName(disp,xa);
     if (name=nil) then continue;
     if Assigned(Lines)
     then Lines.Add(name)
     else Result:=Result+name+EOL;
     XFree(name);
    end;
    if Assigned(Lines) then Result:=Lines.Text;
    if (Delim<>EOL) then Result:=StringReplace(Trim(Result),EOL,Delim,[rfReplaceAll]);
   end;
  finally
   Kill(Lines);
  end;
 finally
  wmctrl_close_display(disp,NeedClose);
 end;
end;

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

function wmctrl_get_number_of_desktops(disp:PDisplay; def:Integer=0):Integer;
var NeedClose:Boolean; rwin:TWindow; s:LongString;
begin
 Result:=def;
 if wmctrl_open_display(disp,NeedClose) then
 try
  rwin:=DefaultRootWindow(disp);
  if wmctrl_get_property(disp,rwin,XA_CARDINAL,'_NET_NUMBER_OF_DESKTOPS',s) then
  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:Integer):Boolean;
var NeedClose:Boolean; rwin:TWindow; desknum:Integer;
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:Integer=0):Integer;
var NeedClose:Boolean; rwin:TWindow; s:LongString;
begin
 Result:=def;
 if wmctrl_open_display(disp,NeedClose) then
 try
  rwin:=DefaultRootWindow(disp);
  if wmctrl_get_property(disp,rwin,XA_CARDINAL,'_NET_CURRENT_DESKTOP',s) then
  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);
  if wmctrl_get_property(disp,rwin,XA_WINDOW,'_NET_CLIENT_LIST',buff) then begin
   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
   if wmctrl_get_property(disp,rwin,XA_CARDINAL,'_WIN_CLIENT_LIST',buff) then begin
    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]);
  end;
 finally
  List.Free;
  wmctrl_close_display(disp,NeedClose);
 end;
end;

function wmctrl_get_window_desktop(disp:PDisplay; win:TWindow; def:Integer=0):Integer;
var NeedClose:Boolean; s:LongString;
begin
 Result:=def;
 if (win<>0) then
 if wmctrl_open_display(disp,NeedClose) then
 try
  wmctrl_get_property(disp,win,XA_CARDINAL,'_NET_WM_DESKTOP',s);
  if (s='') then wmctrl_get_property(disp,win,XA_CARDINAL,'_WIN_WORKSPACE',s);
  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:TPid=0):TPid;
var NeedClose:Boolean; s:LongString;
begin
 Result:=def;
 if (win<>0) then
 if wmctrl_open_display(disp,NeedClose) then
 try
  if wmctrl_get_property(disp,win,XA_CARDINAL,'_NET_WM_PID',s) then
  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 (win<>0) then
 if wmctrl_open_display(disp,NeedClose) then
 try
  wmctrl_get_property(disp,win,XA_STRING,'WM_CLIENT_MACHINE',Result);
  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 (win<>0) then
 if wmctrl_open_display(disp,NeedClose) then
 try
  wmctrl_get_property(disp,win,XInternAtom(disp,'UTF8_STRING',false),'_NET_WM_NAME',Result);
  if (Result='') then wmctrl_get_property(disp,win,XA_STRING,'WM_NAME',Result);
  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 (win<>0) then
 if wmctrl_open_display(disp,NeedClose) then
 try
  wmctrl_get_property(disp,win,XA_STRING,'WM_CLASS',Result);
  if (Result='') then Result:=def else Result:=StringReplace(TrimRightChars(Result,[#0]),#0,'.',[]);
 finally
  wmctrl_close_display(disp,NeedClose);
 end;
end;

function wmctrl_get_window_bounds(disp:PDisplay; win:TWindow):TRect;
var NeedClose:Boolean; attrs:TXWindowAttributes; child:TWindow;
begin
 Result:=Rect(0,0,0,0);
 if (win<>0) then
 if wmctrl_open_display(disp,NeedClose) then
 try
  SafeFillChar(attrs,SizeOf(attrs),0);
  if (XGetWindowAttributes(disp,win,@attrs)<>0) then begin
   if (XTranslateCoordinates(disp,win,attrs.root,0,0,@attrs.x,@attrs.y,@child)<>0) then begin
    Result:=Rect(attrs.x,attrs.y,attrs.x+attrs.width,attrs.y+attrs.height);
   end;
  end;
 finally
  wmctrl_close_display(disp,NeedClose);
 end;
end;

function wmctrl_get_window_state_list(disp:PDisplay; win:TWindow; Delim:LongString=EOL):LongString;
var NeedClose:Boolean; s:LongString; Lines:TStringList;
var xa:TAtom; list:PAtom; count,i:cint; name:PChar;
begin
 Result:='';
 if (win<>0) then
 if wmctrl_open_display(disp,NeedClose) then
 try
  Lines:=nil;
  try
   if wmctrl_get_property(disp,win,XA_ATOM,'_NET_WM_STATE',s) then begin
    list:=Pointer(s); count:=(Length(s) div SizeOf(list[0]));
    if (count>4) then Lines:=TStringList.Create;
    for i:=0 to count-1 do begin
     xa:=list[i];
     name:=XGetAtomName(disp,xa);
     if (name=nil) then continue;
     if Assigned(Lines)
     then Lines.Add(name)
     else Result:=Result+name+EOL;
     XFree(name);
    end;
    if Assigned(Lines) then Result:=Lines.Text;
    if (Delim<>EOL) then Result:=StringReplace(Trim(Result),EOL,Delim,[rfReplaceAll]);
   end;
  finally
   Kill(Lines);
  end;
 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);
  if wmctrl_get_property(disp,rwin,XA_WINDOW,'_NET_ACTIVE_WINDOW',s) then
  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:Integer;
begin
 Result:=false;
 if (win<>0) then
 if wmctrl_open_display(disp,NeedClose) then
 try
  desk:=wmctrl_get_window_desktop(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_window_move_resize(disp:PDisplay; win:TWindow; arg:LongString):Boolean;
const hasX=(1 shl 8); hasY=(1 shl 9); hasW=(1 shl 10); hasH=(1 shl 11);
var NeedClose:Boolean; f,g,x,y,w,h:cint;
begin
 Result:=false;
 if (win<>0) then
 if wmctrl_open_display(disp,NeedClose) then
 try
  f:=0; // f=flags; arg=gravity,X,Y,Width,Height
  if TryStrToInt(ExtractWord(1,arg,ScanSpaces),g) then f:=g           else g:=-1;
  if TryStrToInt(ExtractWord(2,arg,ScanSpaces),x) then f:=(f or hasX) else x:=-1;
  if TryStrToInt(ExtractWord(3,arg,ScanSpaces),y) then f:=(f or hasY) else y:=-1;
  if TryStrToInt(ExtractWord(4,arg,ScanSpaces),w) then f:=(f or hasW) else w:=-1;
  if TryStrToInt(ExtractWord(5,arg,ScanSpaces),h) then f:=(f or hasH) else h:=-1;
  if not InRange(g,0,10) then Exit; // Invalid gravity found
  if not HasFlags(f,hasX+hasY+hasW+hasH) then Exit; // Bad coordinates
  if wmctrl_is_supported(disp,'_NET_MOVERESIZE_WINDOW') then begin
   Result:=wmctrl_client_msg(disp,win,'_NET_MOVERESIZE_WINDOW',f,x,y,w,h);
  end else begin
   // WM doesn't support _NET_MOVERESIZE_WINDOW. Gravity will be ignored.
   if HasFlags(f,hasX) and HasFlags(f,hasY) and not HasFlags(f,hasH+hasW)
   then Result:=(XMoveWindow(disp,win,x,y)=0) else
   if HasFlags(f,hasH) and HasFlags(f,hasW) and not HasFlags(f,hasX+hasY)
   then Result:=(XResizeWindow(disp,win,w,h)=0) else
   if HasFlags(f,hasX) and HasFlags(f,hasY) and HasFlags(f,hasH) and HasFlags(f,hasW)
   then Result:=(XMoveResizeWindow(disp,win,x,y,w,h)=0);
  end;
 finally
  wmctrl_close_display(disp,NeedClose);
 end;
end;

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

function wmctrl_kill_window(disp:PDisplay; win:TWindow; sig:Integer=SIGTERM):Boolean;
var NeedClose:Boolean; pid:TPid; ec:cint;
begin
 Result:=false;
 if (win<>0) then
 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:TPid; aClass,aTitle:LongString; aMode:Integer=0):LongString;
var disp:PDisplay; List,Wins:TStringList; pid:TPid; line,scm,swt,swc:LongString;
var win:TWindow; sWin:LongString; cWin,iWin,lcm,dsk: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_window_desktop(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:Integer; aPid:TPid;
               aClass:LongString; cLen:Integer; 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 PointToStr(const P:TPoint; Delims:LongString=','):LongString;
begin
 with P do
 Result:=Format('%d%s%d',[X,Delims,Y]);
end;

function RectToStr(const R:TRect; Delims:LongString=','):LongString;
begin
 with R do
 Result:=Format('%d%s%d%s%d%s%d',[Left,Delims,Top,Delims,Right,Delims,Bottom]);
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:TPid; 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;

////////////////////////////////
// TWinManControl implementation
////////////////////////////////

constructor TWinManControl.Create;
begin
 inherited Create;
 {$IFDEF UNIX}
 myDispHand:=nil;
 myDispName:='';
 {$ENDIF ~UNIX}
end;

destructor TWinManControl.Destroy;
begin;
 CloseDisplay;
 inherited Destroy;
end;

function  TWinManControl.OpenDisplay:Boolean;
var id:PChar;
begin
 Result:=false;
 if not Assigned(Self) then Exit;
 {$IFDEF UNIX}
 if not Assigned(myDispHand) then begin
  if (myDispName<>'') then id:=PChar(myDispName) else id:=nil;
  myDispHand:=XOpenDisplay(id);
 end;
 if not Assigned(myDispHand) then Exit;
 {$ENDIF ~UNIX}
 Result:=true;
end;

procedure TWinManControl.CloseDisplay;
begin
 if not Assigned(Self) then Exit;
 {$IFDEF UNIX}
 if Assigned(myDispHand) then begin
  XCloseDisplay(myDispHand);
  myDispHand:=nil;
 end;
 myDispName:='';
 {$ENDIF ~UNIX}
end;

function TWinManControl.ListWindows(aPid:TPid; aClass,aTitle:LongString; aMode:Integer=0):LongString;
begin
 Result:=GetListOfWindows(aPid,aClass,aTitle,aMode);
end;

function TWinManControl.FindWindow(aPid:TPid; aClass,aTitle:LongString; aIndex:Integer=0):HWND;
begin
 Result:=FindWindowByPidClassTitle(aPid,aClass,aTitle,aIndex);
end;

function TWinManControl.WindowManagerName:LongString;
begin
 Result:='';
 if Assigned(Self) then
 if OpenDisplay then begin
  {$IFDEF UNIX}
  Result:=WindowTitle(wmctrl_supporting_wm_check(myDispHand));
  {$ENDIF ~UNIX}
  {$IFDEF WINDOWS}
  Result:='windows';
  {$ENDIF ~WINDOWS}
 end;
end;

function TWinManControl.DesktopCount:Integer;
begin
 Result:=0;
 if Assigned(Self) then
 if OpenDisplay then begin
  {$IFDEF UNIX}
  Result:=wmctrl_get_number_of_desktops(myDispHand);
  {$ENDIF ~UNIX}
  {$IFDEF WINDOWS}
  Result:=1;
  {$ENDIF ~WINDOWS}
 end;
end;

function TWinManControl.ActiveDesktop:Integer;
begin
 Result:=0;
 if Assigned(Self) then
 if OpenDisplay then begin
  {$IFDEF UNIX}
  Result:=wmctrl_get_active_desktop(myDispHand);
  {$ENDIF ~UNIX}
  {$IFDEF WINDOWS}
  Result:=0;
  {$ENDIF ~WINDOWS}
 end;
end;

function TWinManControl.SwitchToDesktop(aDesktop:Integer):Boolean;
begin
 Result:=false;
 if Assigned(Self) then
 if OpenDisplay then begin
  {$IFDEF UNIX}
  Result:=wmctrl_switch_to_desktop(myDispHand,aDesktop);
  {$ENDIF ~UNIX}
  {$IFDEF WINDOWS}
  // Nothing
  {$ENDIF ~WINDOWS}
 end;
end;

function TWinManControl.ActiveWindow:HWND;
begin
 Result:=0;
 if Assigned(Self) then
 if OpenDisplay then begin
  {$IFDEF UNIX}
  Result:=wmctrl_get_active_window(myDispHand);
  {$ENDIF ~UNIX}
  {$IFDEF WINDOWS}
  Result:=GetForegroundWindow;
  {$ENDIF ~WINDOWS}
 end;
end;

function TWinManControl.ActivateWindow(win:HWND; switch_desktop:Boolean=true):Boolean;
begin
 Result:=false;
 if Assigned(Self) then
 if OpenDisplay then begin
  {$IFDEF UNIX}
  Result:=wmctrl_activate_window(myDispHand,win,switch_desktop);
  {$ENDIF ~UNIX}
  {$IFDEF WINDOWS}
  Result:=SetForegroundWindow(win);
  {$ENDIF ~WINDOWS}
 end;
end;

function TWinManControl.WindowPid(win:HWND):TPid;
begin
 Result:=0;
 if Assigned(Self) then
 if OpenDisplay then begin
  {$IFDEF UNIX}
  Result:=wmctrl_get_window_pid(myDispHand,win);
  {$ENDIF ~UNIX}
  {$IFDEF WINDOWS}
  if (GetWindowThreadProcessId(win,@Result)=0) then Result:=0;
  {$ENDIF ~WINDOWS}
 end;
end;

function TWinManControl.WindowDesktop(win:HWND):Integer;
begin
 Result:=0;
 if Assigned(Self) then
 if OpenDisplay then begin
  {$IFDEF UNIX}
  Result:=wmctrl_get_window_desktop(myDispHand,win);
  {$ENDIF ~UNIX}
  {$IFDEF WINDOWS}
  Result:=0;
  {$ENDIF ~WINDOWS}
 end;
end;

function TWinManControl.WindowHost(win:HWND):LongString;
begin
 Result:='';
 if Assigned(Self) then
 if OpenDisplay then begin
  {$IFDEF UNIX}
  Result:=wmctrl_get_client_machine(myDispHand,win);
  {$ENDIF ~UNIX}
  {$IFDEF WINDOWS}
  Result:=HostName;
  {$ENDIF ~WINDOWS}
 end;
end;

function TWinManControl.WindowTitle(win:HWND):LongString;
begin
 Result:='';
 if Assigned(Self) then
 if OpenDisplay then begin
  {$IFDEF UNIX}
  Result:=wmctrl_get_window_title(myDispHand,win);
  {$ENDIF ~UNIX}
  {$IFDEF WINDOWS}
  Result:=winapi_get_window_title(win);
  {$ENDIF ~WINDOWS}
 end;
end;

function TWinManControl.WindowClass(win:HWND):LongString;
begin
 Result:='';
 if Assigned(Self) then
 if OpenDisplay then begin
  {$IFDEF UNIX}
  Result:=wmctrl_get_window_class(myDispHand,win);
  {$ENDIF ~UNIX}
  {$IFDEF WINDOWS}
  Result:=winapi_get_window_class(win);
  {$ENDIF ~WINDOWS}
 end;
end;

function TWinManControl.WindowBounds(win:HWND):TRect;
begin
 Result:=Rect(0,0,0,0);
 if Assigned(Self) then
 if OpenDisplay then begin
  {$IFDEF UNIX}
  Result:=wmctrl_get_window_bounds(myDispHand,win);
  {$ENDIF ~UNIX}
  {$IFDEF WINDOWS}
  Result:=winapi_get_window_bounds(win);
  {$ENDIF ~WINDOWS}
 end;
end;

function TWinManControl.SupportedList:LongString;
begin
 Result:='';
 if Assigned(Self) then
 if OpenDisplay then begin
  {$IFDEF UNIX}
  Result:=wmctrl_get_supported_list(myDispHand);
  {$ENDIF ~UNIX}
  {$IFDEF WINDOWS}
  // Nothing
  {$ENDIF ~WINDOWS}
 end;
end;

function TWinManControl.WindowStateList(win:HWND):LongString;
begin
 Result:='';
 if Assigned(Self) then
 if OpenDisplay then begin
  {$IFDEF UNIX}
  Result:=wmctrl_get_window_state_list(myDispHand,win);
  {$ENDIF ~UNIX}
  {$IFDEF WINDOWS}
  // Nothing
  {$ENDIF ~WINDOWS}
 end;
end;

function TWinManControl.WindowStateFlags(win:HWND):Cardinal;
begin
 Result:=0;
 if Assigned(Self) then
 Result:=StrToWsf(WindowStateList(win));
end;

function TWinManControl.MoveResizeWindow(win:HWND; arg:LongString):Boolean;
begin
 Result:=false;
 if Assigned(Self) then
 if OpenDisplay then begin
  {$IFDEF UNIX}
  Result:=wmctrl_window_move_resize(myDispHand,win,arg);
  {$ENDIF ~UNIX}
  {$IFDEF WINDOWS}
  Result:=winapi_set_window_bounds(win,arg);
  {$ENDIF ~WINDOWS}
 end;
end;

function TWinManControl.CloseWindow(win:HWND):Boolean;
begin
 Result:=false;
 if Assigned(Self) then
 if OpenDisplay then begin
  {$IFDEF UNIX}
  Result:=wmctrl_close_window(myDispHand,win);
  {$ENDIF ~UNIX}
  {$IFDEF WINDOWS}
  Result:=winapi_close_window(win);
  {$ENDIF ~WINDOWS}
 end;
end;

function TWinManControl.KillWindow(win:HWND; sig:cint=0):Boolean;
begin
 Result:=false;
 if Assigned(Self) then
 if OpenDisplay then begin
  {$IFDEF UNIX}
  if (sig<=0) then sig:=SIGTERM;
  Result:=wmctrl_kill_window(myDispHand,win,sig);
  {$ENDIF ~UNIX}
  {$IFDEF WINDOWS}
  Result:=winapi_kill_window(win,sig);
  {$ENDIF ~WINDOWS}
 end;
end;

function TWinManControl.StrToWSF(str:LongString):Cardinal;
var iw,nb:Integer; sw:LongString;
begin
 Result:=0;
 if (str='') then Exit;
 for iw:=1 to WordCount(str,ScanSpaces) do begin
  sw:=ExtractWord(iw,str,ScanSpaces);
  nb:=WordIndex(sw,wm_all_window_states,ScanSpaces)-1;
  if (nb>=0) then Result:=Result or GetBitMask(nb);
 end;
end;

function TWinManControl.WSFToStr(wsf:Cardinal):LongString;
var i,wc:Integer; sw:LongString;
begin
 Result:='';
 wc:=WordCount(wm_all_window_states,ScanSpaces);
 for i:=0 to wc-1 do
 if IsBit(wsf,i) then begin
  sw:=ExtractWord(i+1,wm_all_window_states,ScanSpaces);
  if (sw<>'') then Result:=Result+sw+',';
 end;
 if (Result<>'') then Result:=Copy(Result,1,Length(Result)-1);
end;

////////////////////////
// wmctrl implementation
////////////////////////

const
 the_wmctrl:TWinManControl=nil;

function wmctrl:TWinManControl;
begin
 if not Assigned(the_wmctrl) then begin
  the_wmctrl:=TWinManControl.Create;
  the_wmctrl.Master:=@the_wmctrl;
 end;
 Result:=the_wmctrl;
end;

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

procedure Init_crw_wmctrl;
begin
 wmctrl.Ok;
end;

procedure Free_crw_wmctrl;
begin
 Kill(TObject(the_wmctrl));
end;

initialization

 Init_crw_wmctrl;

finalization

 Free_crw_wmctrl;

end.

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

