////////////////////////////////////////////////////////////////////////////////
// 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.                                           //
// See:                                                                       //
// 1) https://specifications.freedesktop.org/wm-spec/wm-spec-latest.html      //
// 2) https://www.x.org/releases/X11R7.7/doc/libX11/libX11/libX11.html        //
// 3) https://x.org/releases/X11R7.6/doc/xorg-docs/specs/ICCCM/icccm.html     //
////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////
// 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}
 {$IFDEF WINDOWS} jwawindows, {$ENDIF}
 sysutils, classes, math, types, lcltype, ctypes,
 {$IFDEF UNIX} unix, baseunix, x, xlib, xatom, {$ENDIF}
 _crw_alloc, _crw_cmdargs, _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;
  myUseOldErrorHandler:Boolean;
  myUseOldIOErrorHandler:Boolean;
  myOldErrorHandler:TXErrorHandler;
  myOldIOErrorHandler:TXIOErrorHandler;
  function  DispArg:PChar;
  function  GetDisplayName:LongString;
  procedure SetDisplayName(aName:LongString);
  {$ENDIF ~UNIX}
 public
  constructor Create;
  destructor  Destroy; override;
 public
  procedure CloseDisplay;
  function  OpenDisplay:Boolean;
  function  DisplayBalance:SizeUInt;
  function  DisplayInfo(mode:Integer=-1):LongString;
 public
  function  XLibInfo(mode:Integer=-1):LongString;
  {$IFDEF UNIX}
  function  SetErrorHandler(aHandler:TXErrorHandler):TXErrorHandler;
  function  SetIOErrorHandler(aHandler:TXIOErrorHandler):TXIOErrorHandler;
  property  DisplayName:LongString read GetDisplayName write SetDisplayName;
 private
  function  GetUseOldErrorHandler:Boolean;
  procedure SetUseOldErrorHandler(aUse:Boolean);
  function  GetUseOldIOErrorHandler:Boolean;
  procedure SetUseOldIOErrorHandler(aUse:Boolean);
 public
  property  UseOldErrorHandler:Boolean read GetUseOldErrorHandler write SetUseOldErrorHandler;
  property  UseOldIOErrorHandler:Boolean read GetUseOldIOErrorHandler write SetUseOldIOErrorHandler;
  {$ENDIF ~UNIX}
 public // List/Find window by PID,CLASS,TITLE
  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  WindowTypeList(win:HWND):LongString;
  function  WindowStateFlags(win:HWND):QWord;
  function  WindowTypeFlags(win:HWND):QWord;
  function  SetWindowStateList(win:HWND; wsc:Integer; state_list:LongString):QWord;
  function  SetWindowStateFlags(win:HWND; wsc:Integer; state_flags:QWord):QWord;
  function  SupportedList:LongString;
 public // Operations
  function  MoveResizeWindow(win:HWND; arg:LongString):Boolean;
 public // Close window, kill process.
  function  CloseWindow(win:HWND; timeout:Integer=0):Boolean;
  function  KillWindow(win:HWND; sig:cint=0; timeout:Integer=0):Boolean;
 public // Service
  function IsWindow(win:HWND):Boolean;
  class function  StrToFlags(str,list:LongString):QWord;
  class function  FlagsToStr(flags:QWord; list:LongString):LongString;
  class function  StrToWSF(str:LongString):QWord; // String to Window State Flags
  class function  WSFToStr(wsf:QWord):LongString; // Window State Flags to String
  class function  StrToWTF(str:LongString):QWord; // String to Window Type Flags
  class function  WTFToStr(wtf:QWord):LongString; // Window Type Flags to String
 public // The "instance.Class" for WM_CLASS property accorting to ICCCM 2.0.
  class function  IcccmClass(aInst:LongString=''; aClass:LongString=''):LongString;
 end;

 {
 The instance of TWinManControl.
 }
function wmctrl:TWinManControl;

{$IFDEF UNIX}
 {
 Default X trap, i.e. error handler for Xlib errors.
 }
function wmctrl_def_x_trap(disp:PDisplay; xevent:PXErrorEvent):cint;cdecl;
function wmctrl_def_x_io_trap(disp:PDisplay):cint;cdecl;
{$ENDIF ~UNIX}

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

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;
 WSF_MAXIMIZED         = WSF_MAXIMIZED_VERT or WSF_MAXIMIZED_HORZ;

const // Window state commands
 WSC_REMOVE = 0; // remove/unset property
 WSC_ADD    = 1; // add/set property
 WSC_TOGGLE = 2; // toggle property

const
 wm_all_window_types='_NET_WM_WINDOW_TYPE_DESKTOP,'
                    +'_NET_WM_WINDOW_TYPE_DOCK,'
                    +'_NET_WM_WINDOW_TYPE_TOOLBAR,'
                    +'_NET_WM_WINDOW_TYPE_MENU,'
                    +'_NET_WM_WINDOW_TYPE_UTILITY,'
                    +'_NET_WM_WINDOW_TYPE_SPLASH,'
                    +'_NET_WM_WINDOW_TYPE_DIALOG,'
                    +'_NET_WM_WINDOW_TYPE_DROPDOWN_MENU,'
                    +'_NET_WM_WINDOW_TYPE_POPUP_MENU,'
                    +'_NET_WM_WINDOW_TYPE_TOOLTIP,'
                    +'_NET_WM_WINDOW_TYPE_NOTIFICATION,'
                    +'_NET_WM_WINDOW_TYPE_COMBO,'
                    +'_NET_WM_WINDOW_TYPE_DND,'
                    +'_NET_WM_WINDOW_TYPE_NORMAL';
const // Window type flags
 WTF_DESKTOP           = $00000001;
 WTF_DOCK              = $00000002;
 WTF_TOOLBAR           = $00000004;
 WTF_MENU              = $00000008;
 WTF_UTILITY           = $00000010;
 WTF_SPLASH            = $00000020;
 WTF_DIALOG            = $00000040;
 WTF_DROPDOWN_MENU     = $00000080;
 WTF_POPUP_MENU        = $00000100;
 WTF_TOOLTIP           = $00000200;
 WTF_NOTIFICATION      = $00000400;
 WTF_COMBO             = $00000800;
 WTF_DND               = $00001000;
 WTF_NORMAL            = $00002000;

const // Sleep time for polling.
 wmctrl_sleeping_time:Cardinal=1;

 {
 System WINDOW classes from WinAPI.
 See https://docs.microsoft.com/en-us/windows/win32/winmsg/about-window-classes
 }
const                                   // System classes that are available for use by all processes.
 swc_Button     = 'Button'; 	        // The class for a button.
 swc_ComboBox   = 'ComboBox';           // The class for a combo box.
 swc_Edit       = 'Edit';               // The class for an edit control.
 swc_ListBox    = 'ListBox'; 	        // The class for a list box.
 swc_MDIClient  = 'MDIClient'; 	        // The class for an MDI client window.
 swc_ScrollBar  = 'ScrollBar';          // The class for a scroll bar.
 swc_Static     = 'Static';             // The class for a static control.
const                                   // System classes that are available only for use by the system.
 swcComboLBox   = 'ComboLBox';          // The class for the list box contained in a combo box.
 swc_DDEMLEvent = 'DDEMLEvent';         // The class for Dynamic Data Exchange Management Library (DDEML) events.
 swc_Message    = 'Message';            // The class for a message-only window.
 swc_Menu       = '#32768';             // The class for a menu.
 swc_Desktop    = '#32769';             // The class for the desktop window.
 swc_DialogBox  = '#32770';             // The class for a dialog box.
 swc_TaskSwitch = '#32771';             // The class for the task switch window.
 swc_IconTitles = '#32772';             // The class for icon titles.
 swc_ConsoleWin = 'ConsoleWindowClass'; // The classs for console window.

 {
 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; sync:Boolean=true):Boolean;

{ For internal use. Get list of window's atoms names by given hint. }
function wmctrl_get_atoms_list(disp:PDisplay; win:TWindow; hint:LongString; Delim:LongString=EOL):LongString;

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

 { Get list of window state atoms, see wm_all_window_states. }
function wmctrl_get_window_state_list(disp:PDisplay; win:TWindow; Delim:LongString=EOL):LongString;

 { Get list of window type atoms, see wm_all_window_types. }
function wmctrl_get_window_type_list(disp:PDisplay; win:TWindow; Delim:LongString=EOL):LongString;

 {
 Move/Resize window by given arg.
 arg='gravity,x,y,w,h'. For example, '0,*,*,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; timeout:Integer=0):Boolean;

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

{$IFDEF WINDOWS}
function winapi_get_window_pid(win:HWND):TPid;
function winapi_get_active_window:HWND;
function winapi_activate_window(win:HWND):Boolean;
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_get_window_type_flags(win:HWND):QWord;
function winapi_get_window_state_flags(win:HWND):QWord;
function winapi_close_window(win:HWND; timeout:Integer=0):Boolean;
function winapi_kill_pid(pid:DWORD; code:Integer; win:HWND=0; timeout:Integer=0):Boolean;
function winapi_kill_window(win:HWND; sig:Integer=SIGTERM; timeout:Integer=0):Boolean;
function winapi_window_move_resize(win:HWND; arg:LongString):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
 XDisplayBalance:SizeUInt=0;

{$IFDEF WINDOWS}

function winapi_get_window_pid(win:HWND):TPid;
begin
 Result:=0;
 if (GetWindowThreadProcessId(win,@Result)=0) then Result:=0;
end;

function winapi_get_active_window:HWND;
begin
 Result:=GetForegroundWindow;
end;

function winapi_activate_window(win:HWND):Boolean;
begin
 Result:=SetForegroundWindow(win);
end;

function winapi_get_window_class(win:HWND):LongString;
var Buffer,aClass:WideString;
begin
 Result:='';
 if IsWindow(win) then begin
  Buffer:=''; SetLength(Buffer,MAX_WM_PROPERTY_VALUE_LEN);
  SetString(aClass,PWChar(Buffer),GetClassNameW(win,PWChar(Buffer),Length(Buffer)));
  Result:=Utf8Encode(aClass);
 end;
end;

function winapi_get_window_title(win:HWND):LongString;
var Buffer,aTitle:WideString;
begin
 Result:='';
 if IsWindow(win) then begin
  Buffer:=''; SetLength(Buffer,MAX_WM_PROPERTY_VALUE_LEN);
  SetString(aTitle,PWChar(Buffer),GetWindowTextW(win,PWChar(Buffer),Length(Buffer)));
  Result:=Utf8Encode(aTitle);
 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_get_window_type_flags(win:HWND):QWord;
var Desktop:HWND; wClass:LongString;
 procedure AddFlag(var Flags:QWord; Flag:QWord; Cond:Boolean);
 begin
  if Cond then Flags:=Flags or Flag;
 end;
begin
 Result:=0;
 if IsWindow(win) then begin
  Desktop:=GetDesktopWindow;
  wClass:=winapi_get_window_class(win);
  AddFlag(Result,WTF_DESKTOP, (win=Desktop));
  AddFlag(Result,WTF_DESKTOP, SameStr(wClass,swc_Desktop));
  AddFlag(Result,WTF_DIALOG,  SameStr(wClass,swc_DialogBox));
  // Finally, if no flags, assume it's NORMAL window
  AddFlag(Result,WTF_NORMAL,  (Result=0) );
 end;
end;

function winapi_get_window_state_flags(win:HWND):QWord;
var WChild,WIconic,WZoomed,WVisible,WEnabled,OEnabled,WPopup,WModal:Boolean;
var WStyle,EStyle:Cardinal; Owner,Parent,Desktop:HWND;
var OTopLevel,PTopLevel,WOnTaskBar:Boolean;
 procedure AddFlag(var Flags:QWord; Flag:QWord; Cond:Boolean);
 begin
  if Cond then Flags:=Flags or Flag;
 end;
begin
 Result:=0;
 if IsWindow(win) then begin
  Desktop:=GetDesktopWindow;                    // desktop window
  Owner:=GetWindow(win,GW_OWNER);               // owner   window
  Parent:=GetWindowLong(win,GWL_HWNDPARENT);    // parent  window
  WStyle:=GetWindowLong(win,GWL_STYLE);         // window style
  EStyle:=GetWindowLong(win,GWL_EXSTYLE);       // extended style
  WChild:=HasFlags(WStyle,WS_CHILD);            // child window
  WPopup:=HasFlags(WStyle,WS_POPUP);            // popup window
  WIconic:=IsIconic(win);                       // minimized
  WZoomed:=IsZoomed(win);                       // maximized
  WVisible:=IsWindowVisible(win);               // window is visible
  WEnabled:=IsWindowEnabled(win);               // window enabled
  OEnabled:=IsWindowEnabled(Owner);             // owner  enabled
  OTopLevel:=(Owner=0) or (Owner=Desktop);      // Owner  is top level
  PTopLevel:=(Parent=0) or (Parent=Desktop);    // Parent is top level
  WOnTaskBar:=HasFlags(EStyle,WS_EX_APPWINDOW) or not HasFlags(EStyle,WS_EX_TOOLWINDOW);
  WModal:=WVisible and WEnabled and WPopup and not WChild and not OEnabled;
  AddFlag(Result,WSF_MAXIMIZED,    WZoomed and WVisible);
  AddFlag(Result,WSF_HIDDEN,       WIconic or not WVisible);
  AddFlag(Result,WSF_MODAL,        WModal and WVisible and WEnabled);
  AddFlag(Result,WSF_SKIP_TASKBAR, WVisible and PTopLevel and not WOnTaskBar);
 end;
end;

function winapi_set_window_state_flags(win:HWND; swc:cint; flags:QWord):QWord;
begin
 Result:=0;
 if IsWindow(win) then begin
 end;
end;

function winapi_close_window(win:HWND; timeout:Integer=0):Boolean;
var pid:TPid; sc,st:LongString; deadline:QWord;
begin
 Result:=false;
 if IsWindow(win) then begin
  if (timeout>0) then pid:=winapi_get_window_pid(win) else pid:=0;
  if (timeout>0) then sc:=winapi_get_window_class(win) else sc:='';
  if (timeout>0) then st:=winapi_get_window_title(win) else st:='';
  Result:=PostMessage(win,WM_CLOSE,0,0);
  if (timeout<=0) or not Result then Exit;
  deadline:=GetTickCount64+timeout;
  while (GetTickCount64<deadline) do begin
   if (FindWindowByPidClassTitle(pid,sc,st)=0) then Break;
   Sleep(Max(1,Min(1000,wmctrl_sleeping_time)));
  end;
 end;
end;

function winapi_kill_pid(pid:DWORD; code:Integer; win:HWND=0; timeout:Integer=0):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
  if (timeout>0) and IsWindow(win) then winapi_close_window(win,timeout);
  Result:=TerminateProcess(hp,code);
 finally
  CloseHandle(hp);
 end;
end;

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

function winapi_window_move_resize(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 value was 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;

type TedmRec=packed record n,i,p:SizeInt; r:TRect; end;
function edmProc(mon:HMONITOR; dc:HDC; rect:PRECT; par:LPARAM):BOOL;stdcall;
var mi:TMonitorInfo;
begin
 Result:=(par<>0); if not Result then Exit;
 SafeFillChar(mi,SizeOf(mi),0); mi.cbSize:=SizeOf(mi);
 if GetMonitorInfo(mon,@mi) then with TedmRec(PtrIntToPointer(par)^) do begin
  if HasFlags(mi.dwFlags,MONITORINFOF_PRIMARY) then p:=n;
  if (n=i) then r:=mi.rcMonitor;
  inc(n);
 end;
end;

function winapi_get_display_string(mode:Integer=0):LongString;
var i,sc,sw,sh,sd:Integer; edm:TedmRec; dc:HDC;
begin
 Result:='';
 Result:=':0'; if (mode=0) then Exit;
 sc:=GetSystemMetrics(SM_CMONITORS); sd:=-1;
 SafeFillChar(edm,SizeOf(edm),0); edm.p:=-1;
 dc:=GetDc(0);
 try
  if EnumDisplayMonitors(dc,nil,edmProc,PointerToPtrInt(@edm)) then begin
   if (edm.p>=0) then sd:=edm.p;
   if (edm.n>0) then sc:=edm.n;
  end;
 finally
  ReleaseDC(0,dc);
 end;
 if HasFlags(mode,1) then begin
  Result:=Format('%s, ScreenCount: %d',[Result,sc]);
 end;
 if HasFlags(mode,2) then begin
  Result:=Format('%s, DefScreen: %d',[Result,sd]);
 end;
 if HasFlags(mode,4) then begin
  for i:=0 to sc-1 do begin
   SafeFillChar(edm,SizeOf(edm),0); edm.i:=i; sd:=0;
   dc:=GetDc(0);
   try
    if EnumDisplayMonitors(dc,nil,edmProc,PointerToPtrInt(@edm))
    then sd:=GetDeviceCaps(dc,BITSPIXEL);
   finally
    ReleaseDC(0,dc);
   end;
   sw:=edm.r.Width;
   sh:=edm.r.Height;
   Result:=Format('%s, Screen%u: %u:%u:%u',[Result,i,sw,sh,sd]);
  end;
 end;
 if (Result<>'') then Result:=TrimChars(Result,ScanSpaces,ScanSpaces);
end;

type
 TEnumGlowRec = packed record
  List:TStringList;
  lpClass,lpTitle: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(PtrIntToPointer(Param)^) do
 try
  if (Wnd=0) or not IsWindow(Wnd) then Exit;
  aPid:=winapi_get_window_pid(Wnd);
  if (Pid<>0) and (Pid<>aPid) then Exit;
  aClass:=''; aTitle:='';
  try
   aClass:=winapi_get_window_class(Wnd);
   if (lpClass<>'') and not SameText(lpClass,aClass) then Exit;
   aTitle:=winapi_get_window_title(Wnd);
   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);
  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:='';
  end;
 except
  on E:Exception do BugReport(E,nil,'GetListOfWindows');
 end;
end;

{$ENDIF ~WINDOWS}

{$IFDEF UNIX}
function xlib_get_version(disp:PDisplay; mode:Integer):LongString;
var pv,pr,sr:Integer; sv:LongString;
begin
 Result:='';
 if (mode=0) then Exit;
 if not Assigned(disp) then Exit;
 if HasFlags(mode,1) then begin
  pv:=XProtocolVersion(disp);
  pr:=XProtocolRevision(disp);
  Result:=Format('%s XProtocol: %u.%u',[Result,pv,pr]);
 end;
 if HasFlags(mode,2) then begin
  sr:=XVendorRelease(disp);
  sv:=StrPas(XServerVendor(disp));
  if IsEmptyStr(sv) then sv:='Unknown';
  Result:=Format('%s, XServer: %s release %u',[Result,sv,sr]);
 end;
 if (Result<>'') then Result:=TrimChars(Result,ScanSpaces,ScanSpaces);
end;

function xlib_get_display_string(disp:PDisplay; mode:Integer):LongString;
var i,sc,sw,sh,sd:Integer;
begin
 Result:='';
 if not Assigned(disp) then Exit;
 Result:=StrPas(XDisplayString(disp)); if (mode=0) then Exit;
 sc:=XScreenCount(disp);
 if HasFlags(mode,1) then begin
  Result:=Format('%s, ScreenCount: %u',[Result,sc]);
 end;
 if HasFlags(mode,2) then begin
  sd:=XDefaultScreen(disp);
  Result:=Format('%s, DefScreen: %u',[Result,sd]);
 end;
 if HasFlags(mode,4) then begin
  for i:=0 to sc-1 do begin
   sw:=XDisplayWidth(disp,i);
   sh:=XDisplayHeight(disp,i);
   sd:=XDisplayPlanes(disp,i);
   Result:=Format('%s, Screen%u: %u:%u:%u',[Result,i,sw,sh,sd]);
  end;
 end;
 if (Result<>'') then Result:=TrimChars(Result,ScanSpaces,ScanSpaces);
end;

function xlib_open_display(id:PChar):PDisplay;
begin
 Result:=XOpenDisplay(id);
 if Assigned(Result) then LockedInc(XDisplayBalance);
end;

procedure xlib_close_display(disp:PDisplay);
begin
 if Assigned(disp) then begin
  LockedDec(XDisplayBalance);
  XCloseDisplay(disp);
 end;
end;

function wmctrl_open_display(var disp:PDisplay; out NeedClose:Boolean):Boolean;
begin
 NeedClose:=false;
 if not Assigned(disp) then begin
  disp:=xlib_open_display(nil);
  NeedClose:=Assigned(disp);
 end;
 Result:=Assigned(disp);
end;

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

function OkWindow(win:TWindow):Boolean; inline;
begin
 Result:=(win<>0);
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 OkWindow(win) 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; sync:Boolean=true):Boolean;
var NeedClose:Boolean; event:TXEvent; mask:clong;
begin
 Result:=false;
 if OkWindow(win) 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:=XNextRequest(disp);
   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);
   if sync then 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_get_atoms_list(disp:PDisplay; win:TWindow; hint:LongString; Delim:LongString=EOL):LongString;
var NeedClose:Boolean; s:LongString; Lines:TStringList;
var xa:TAtom; list:PAtom; count,i:cint; name:PChar;
begin
 Result:='';
 if (hint<>'') then
 if OkWindow(win) then
 if wmctrl_open_display(disp,NeedClose) then
 try
  Lines:=nil;
  try
   if wmctrl_get_property(disp,win,XA_ATOM,PChar(hint),s) then begin
    list:=Pointer(s); count:=(Length(s) div SizeOf(list[0]));
    if (count>8) 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(StrPas(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_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;
begin
 Result:='';
 if wmctrl_open_display(disp,NeedClose) then
 try
  rwin:=DefaultRootWindow(disp);
  Result:=wmctrl_get_atoms_list(disp,rwin,'_NET_SUPPORTED',Delim);
 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 OkWindow(win) 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 OkWindow(win) 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 OkWindow(win) 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; xa:TAtom;
begin
 Result:=def;
 if OkWindow(win) then
 if wmctrl_open_display(disp,NeedClose) then
 try
  xa:=XInternAtom(disp,'UTF8_STRING',false);
  wmctrl_get_property(disp,win,xa,'_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 OkWindow(win) 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 OkWindow(win) 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;
begin
 Result:=wmctrl_get_atoms_list(disp,win,'_NET_WM_STATE',Delim);
end;

function wmctrl_get_window_type_list(disp:PDisplay; win:TWindow; Delim:LongString=EOL):LongString;
begin
 Result:=wmctrl_get_atoms_list(disp,win,'_NET_WM_WINDOW_TYPE',Delim);
end;

function wmctrl_set_window_state_list(disp:PDisplay; win:TWindow; wsc:Integer; state_list:LongString):Integer;
var NeedClose:Boolean; nw,ip,iw1,iw2,ia1,ia2:Integer; sw1,sw2:LongString; ma1,ma2,ma3:Int64; xa1,xa2,xa3:TAtom;
begin
 Result:=0;
 if (wmctrl.StrToWSF(state_list)=0) then Exit;
 if not InRange(wsc,WSC_REMOVE,WSC_TOGGLE) then Exit;
 if OkWindow(win) then
 if wmctrl_open_display(disp,NeedClose) then
 try
  nw:=WordCount(state_list,ScanSpaces);
  for ip:=0 to ((nw+1) div 2)-1 do begin
   iw1:=ip*2+1; xa1:=0; ma1:=0;
   iw2:=ip*2+2; xa2:=0; ma2:=0;
   sw1:=ExtractWord(iw1,state_list,ScanSpaces);
   sw2:=ExtractWord(iw2,state_list,ScanSpaces);
   ia1:=WordIndex(sw1,wm_all_window_states,ScanSpaces)-1;
   ia2:=WordIndex(sw2,wm_all_window_states,ScanSpaces)-1;
   if (ia1>=0) then xa1:=XInternAtom(disp,PChar(sw1),false);
   if (ia2>=0) then xa2:=XInternAtom(disp,PChar(sw2),false);
   if (xa1<>0) then ma1:=GetBitMask(ia1);
   if (xa2<>0) then ma2:=GetBitMask(ia2);
   if (xa1=0) then begin
    xa3:=xa1; xa1:=xa2; xa2:=xa3;
    ma3:=ma1; ma1:=ma2; ma2:=ma3;
   end;
   if (xa1<>0) then
   if wmctrl_client_msg(disp,win,'_NET_WM_STATE',wsc,xa1,xa2,0,0)
   then Result:=Result or ma1 or ma2;
  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 OkWindow(win) then
 if wmctrl_open_display(disp,NeedClose) then
 try
  desk:=wmctrl_get_window_desktop(disp,win,High(desk));
  if switch_desktop and (desk>=0)
  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; R:TRect;
begin
 Result:=false;
 if OkWindow(win) then
 if wmctrl_open_display(disp,NeedClose) then
 try
  R:=wmctrl_get_window_bounds(disp,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 value was 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 not HasFlags(f,hasH+hasW) then Result:=(XMoveWindow(disp,win,x,y)=0) else
   if not HasFlags(f,hasX+hasY) then Result:=(XResizeWindow(disp,win,w,h)=0) else
   if HasFlags(f,hasX+hasY) and HasFlags(f,hasH+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; timeout:Integer=0):Boolean;
var NeedClose:Boolean; pid:TPid; sc,st:LongString; deadline:QWord;
begin
 Result:=false;
 if OkWindow(win) then
 if wmctrl_open_display(disp,NeedClose) then
 try
  if (timeout>0) then pid:=wmctrl_get_window_pid(disp,win) else pid:=0;
  if (timeout>0) then sc:=wmctrl_get_window_class(disp,win) else sc:='';
  if (timeout>0) then st:=wmctrl_get_window_title(disp,win) else st:='';
  Result:=wmctrl_client_msg(disp,win,'_NET_CLOSE_WINDOW',0,0,0,0,0);
  if (timeout<=0) or not Result then Exit;
  deadline:=GetTickCount64+timeout;
  while (GetTickCount64<deadline) do begin
   if (FindWindowByPidClassTitle(pid,sc,st)=0) then Break;
   Sleep(Max(1,Min(1000,wmctrl_sleeping_time)));
  end;
 finally
  wmctrl_close_display(disp,NeedClose);
 end;
end;

function wmctrl_kill_window(disp:PDisplay; win:TWindow; sig:Integer=SIGTERM; timeout:Integer=0):Boolean;
var NeedClose:Boolean; pid:TPid; ec:cint;
begin
 Result:=false;
 if OkWindow(win) then
 if wmctrl_open_display(disp,NeedClose) then
 try
  pid:=wmctrl_get_window_pid(disp,win);
  if (pid<>0) then begin
   if (timeout>0) then wmctrl_close_window(disp,win,timeout);
   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:=xlib_open_display(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;
   xlib_close_display(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:='';
 myOldErrorHandler:=nil;
 myOldIOErrorHandler:=nil;
 myUseOldErrorHandler:=false;
 myUseOldIOErrorHandler:=false;
 {$ENDIF ~UNIX}
end;

destructor TWinManControl.Destroy;
begin;
 CloseDisplay;
 {$IFDEF UNIX}
 SetErrorHandler(nil);
 SetIOErrorHandler(nil);
 {$ENDIF ~UNIX}
 inherited Destroy;
end;

{$IFDEF UNIX}
function TWinManControl.DispArg:PChar;
begin
 Result:=nil;
 if Assigned(Self) then
 if (myDispName<>'') then Result:=PChar(myDispName);
end;

function TWinManControl.GetDisplayName:LongString;
begin
 Result:='';
 if Assigned(Self) then Result:=myDispName;
end;

procedure TWinManControl.SetDisplayName(aName:LongString);
begin
 aName:=Trim(aName);
 if Assigned(Self) then
 if (aName<>myDispName) then begin
  if Assigned(myDispHand) then begin
   CloseDisplay;
   OpenDisplay;
  end;
  myDispName:=aName;
 end;
end;

{$ENDIF ~UNIX}

function  TWinManControl.OpenDisplay:Boolean;
begin
 Result:=false;
 if not Assigned(Self) then Exit;
 {$IFDEF UNIX}
 if not Assigned(myDispHand) then myDispHand:=xlib_open_display(DispArg);
 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
  xlib_close_display(myDispHand);
  myDispHand:=nil;
 end;
 myDispName:='';
 {$ENDIF ~UNIX}
end;

function TWinManControl.DisplayBalance:SizeUInt;
begin
 Result:=XDisplayBalance;
end;

function TWinManControl.DisplayInfo(mode:Integer=-1):LongString;
begin
 Result:='';
 if Assigned(Self) then
 if OpenDisplay then
 try
  {$IFDEF UNIX}
  Result:=xlib_get_display_string(myDispHand,mode);
  {$ENDIF ~UNIX}
  {$IFDEF WINDOWS}
  Result:=winapi_get_display_string(mode);
  {$ENDIF ~WINDOWS}
 except
  on E:Exception do BugReport(E,Self,'DisplayInfo');
 end;
end;


function TWinManControl.XLibInfo(mode:Integer=-1):LongString;
begin
 Result:='';
 if Assigned(Self) then
 if OpenDisplay then
 try
  {$IFDEF UNIX}
  Result:=xlib_get_version(myDispHand,mode);
  {$ENDIF ~UNIX}
  {$IFDEF WINDOWS}
  Result:='(none)';
  {$ENDIF ~WINDOWS}
 except
  on E:Exception do BugReport(E,Self,'XLibVersion');
 end;
end;

{$IFDEF UNIX}
function wmctrl_def_x_trap(disp:PDisplay; xevent:PXErrorEvent):cint;cdecl;
var oldhandler:TXErrorHandler;
 function GetErrorText:LongString;
 var Buff:LongString;
 begin
  Result:='';
  Buff:=StringBuffer(MAX_WM_PROPERTY_VALUE_LEN);
  XGetErrorText(disp,xevent.error_code,PChar(Buff),Length(Buff));
  Result:=StrPas(PChar(Buff));
 end;
 procedure Report(msg:LongString);
 var err,dsp:LongString;
 begin
  err:=GetErrorText;
  if (err<>'') then msg:=err;
  dsp:=StrPas(XDisplayName(nil));
  if (dsp<>'') then dsp:=' on display '+dsp;
  Echo('XError'+dsp+' => '+msg);
 end;
begin
 Result:=0;
 try
  if wmctrl.UseOldErrorHandler then begin
   oldhandler:=wmctrl.myOldErrorHandler;
   if Assigned(oldhandler) then Result:=oldhandler(disp,xevent);
   Exit;
  end;
  case xevent.error_code of
   BadRequest:        Report('BadRequest');
   BadValue:          Report('BadValue');
   BadWindow:         Report('BadWindow');
   BadPixmap:         Report('BadPixmap');
   BadAtom:           Report('BadAtom');
   BadCursor:         Report('BadCursor');
   BadFont:           Report('BadFont');
   BadMatch:          Report('BadMatch');
   BadDrawable:       Report('BadDrawable');
   BadAccess:         Report('BadAccess');
   BadAlloc:          Report('BadAlloc');
   BadColor:          Report('BadColor');
   BadGC:             Report('BadGC');
   BadIDChoice:       Report('BadIDChoice');
   BadName:           Report('BadName');
   BadLength:         Report('BadLength');
   BadImplementation: Report('BadImplementation');
   else               Report('Unknown Error '+IntToStr(xevent.error_code));
  end;
 except
  on E:Exception do BugReport(E,nil,'wmctrl_def_x_trap');
 end;
end;
function wmctrl_def_x_io_trap(disp:PDisplay):cint;cdecl;
var oldhandler:TXIOErrorHandler;
 procedure Report(msg:LongString);
 var dsp:LongString;
 begin
  dsp:=StrPas(XDisplayName(nil));
  if (dsp<>'') then dsp:=' on display '+dsp;
  Echo('XIOError'+dsp+' => '+msg);
 end;
begin
 Result:=0;
 try
  if wmctrl.UseOldIOErrorHandler then begin
   oldhandler:=wmctrl.myOldIOErrorHandler;
   if Assigned(oldhandler) then Result:=oldhandler(disp);
   Exit;
  end;
  Report('fatal IO error.');
 except
  on E:Exception do BugReport(E,nil,'wmctrl_def_x_io_trap');
 end;
end;
{$ENDIF ~UNIX}

{$IFDEF UNIX}
function TWinManControl.SetErrorHandler(aHandler:TXErrorHandler):TXErrorHandler;
begin
 Result:=nil;
 if not Assigned(Self) then Exit;
 if Assigned(aHandler) then begin
  Result:=XSetErrorHandler(aHandler);
  myOldErrorHandler:=Result;
 end else begin
  Result:=XSetErrorHandler(myOldErrorHandler);
  myOldErrorHandler:=nil;
 end;
end;

function TWinManControl.SetIOErrorHandler(aHandler:TXIOErrorHandler):TXIOErrorHandler;
begin
 Result:=nil;
 if not Assigned(Self) then Exit;
 if Assigned(aHandler) then begin
  Result:=XSetIOErrorHandler(aHandler);
  myOldIOErrorHandler:=Result;
 end else begin
  Result:=XSetIOErrorHandler(myOldIOErrorHandler);
  myOldIOErrorHandler:=nil;
 end;
end;

function  TWinManControl.GetUseOldErrorHandler:Boolean;
begin
 if Assigned(Self) then Result:=myUseOldErrorHandler else Result:=false;
end;

procedure TWinManControl.SetUseOldErrorHandler(aUse:Boolean);
begin
 if Assigned(Self) then myUseOldErrorHandler:=aUse;
end;

function  TWinManControl.GetUseOldIOErrorHandler:Boolean;
begin
 if Assigned(Self) then Result:=myUseOldIOErrorHandler else Result:=false;
end;

procedure TWinManControl.SetUseOldIOErrorHandler(aUse:Boolean);
begin
 if Assigned(Self) then myUseOldIOErrorHandler:=aUse;
end;
{$ENDIF ~UNIX}

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
 try
  {$IFDEF UNIX}
  Result:=WindowTitle(wmctrl_supporting_wm_check(myDispHand));
  {$ENDIF ~UNIX}
  {$IFDEF WINDOWS}
  Result:='windows';
  {$ENDIF ~WINDOWS}
 except
  on E:Exception do BugReport(E,Self,'WindowManagerName');
 end;
end;

function TWinManControl.DesktopCount:Integer;
begin
 Result:=0;
 if Assigned(Self) then
 if OpenDisplay then
 try
  {$IFDEF UNIX}
  Result:=wmctrl_get_number_of_desktops(myDispHand);
  {$ENDIF ~UNIX}
  {$IFDEF WINDOWS}
  Result:=1;
  {$ENDIF ~WINDOWS}
 except
  on E:Exception do BugReport(E,Self,'DesktopCount');
 end;
end;

function TWinManControl.ActiveDesktop:Integer;
begin
 Result:=0;
 if Assigned(Self) then
 if OpenDisplay then
 try
  {$IFDEF UNIX}
  Result:=wmctrl_get_active_desktop(myDispHand);
  {$ENDIF ~UNIX}
  {$IFDEF WINDOWS}
  Result:=0;
  {$ENDIF ~WINDOWS}
 except
  on E:Exception do BugReport(E,Self,'ActiveDesktop');
 end;
end;

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

function TWinManControl.ActiveWindow:HWND;
begin
 Result:=0;
 if Assigned(Self) then
 if OpenDisplay then
 try
  {$IFDEF UNIX}
  Result:=wmctrl_get_active_window(myDispHand);
  {$ENDIF ~UNIX}
  {$IFDEF WINDOWS}
  Result:=winapi_get_active_window;
  {$ENDIF ~WINDOWS}
 except
  on E:Exception do BugReport(E,Self,'ActiveWindow');
 end;
end;

function TWinManControl.ActivateWindow(win:HWND; switch_desktop:Boolean=true):Boolean;
begin
 Result:=false;
 if Assigned(Self) then
 if OpenDisplay then
 try
  {$IFDEF UNIX}
  Result:=wmctrl_activate_window(myDispHand,win,switch_desktop);
  {$ENDIF ~UNIX}
  {$IFDEF WINDOWS}
  Result:=winapi_activate_window(win);
  {$ENDIF ~WINDOWS}
 except
  on E:Exception do BugReport(E,Self,'ActivateWindow');
 end;
end;

function TWinManControl.WindowPid(win:HWND):TPid;
begin
 Result:=0;
 if Assigned(Self) then
 if OpenDisplay then
 try
  {$IFDEF UNIX}
  Result:=wmctrl_get_window_pid(myDispHand,win);
  {$ENDIF ~UNIX}
  {$IFDEF WINDOWS}
  Result:=winapi_get_window_pid(win);
  {$ENDIF ~WINDOWS}
 except
  on E:Exception do BugReport(E,Self,'WindowPid');
 end;
end;

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

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

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

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

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

function TWinManControl.SupportedList:LongString;
begin
 Result:='';
 if Assigned(Self) then
 if OpenDisplay then
 try
  {$IFDEF UNIX}
  Result:=wmctrl_get_supported_list(myDispHand);
  {$ENDIF ~UNIX}
  {$IFDEF WINDOWS}
  // Nothing
  {$ENDIF ~WINDOWS}
 except
  on E:Exception do BugReport(E,Self,'SupportedList');
 end;
end;

function TWinManControl.WindowStateList(win:HWND):LongString;
begin
 Result:='';
 if Assigned(Self) then
 if OpenDisplay then
 try
  {$IFDEF UNIX}
  Result:=wmctrl_get_window_state_list(myDispHand,win);
  {$ENDIF ~UNIX}
  {$IFDEF WINDOWS}
  Result:=WsfToStr(winapi_get_window_state_flags(win));
  {$ENDIF ~WINDOWS}
 except
  on E:Exception do BugReport(E,Self,'WindowStateList');
 end;
end;

function TWinManControl.WindowTypeList(win:HWND):LongString;
begin
 Result:='';
 if Assigned(Self) then
 if OpenDisplay then
 try
  {$IFDEF UNIX}
  Result:=wmctrl_get_window_type_list(myDispHand,win);
  {$ENDIF ~UNIX}
  {$IFDEF WINDOWS}
  Result:=WtfToStr(winapi_get_window_type_flags(win));
  {$ENDIF ~WINDOWS}
 except
  on E:Exception do BugReport(E,Self,'WindowTypeList');
 end;
end;

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

function TWinManControl.WindowTypeFlags(win:HWND):QWord;
begin
 Result:=0;
 if Assigned(Self) then
 Result:=StrToWtf(WindowTypeList(win));
end;

function TWinManControl.SetWindowStateList(win:HWND; wsc:Integer; state_list:LongString):QWord;
begin
 Result:=0;
 if Assigned(Self) then
 if OpenDisplay then
 try
  {$IFDEF UNIX}
  Result:=wmctrl_set_window_state_list(myDispHand,win,wsc,state_list);
  {$ENDIF ~UNIX}
  {$IFDEF WINDOWS}
  Result:=winapi_set_window_state_flags(win,wsc,StrToWsf(state_list));
  {$ENDIF ~WINDOWS}
 except
  on E:Exception do BugReport(E,Self,'SetWindowStateList');
 end;
end;

function TWinManControl.SetWindowStateFlags(win:HWND; wsc:Integer; state_flags:QWord):QWord;
begin
 Result:=0;
 if Assigned(Self) then
 Result:=SetWindowStateList(win,wsc,WsfToStr(state_flags));
end;

function TWinManControl.MoveResizeWindow(win:HWND; arg:LongString):Boolean;
begin
 Result:=false;
 if Assigned(Self) then
 if OpenDisplay then
 try
  {$IFDEF UNIX}
  Result:=wmctrl_window_move_resize(myDispHand,win,arg);
  {$ENDIF ~UNIX}
  {$IFDEF WINDOWS}
  Result:=winapi_window_move_resize(win,arg);
  {$ENDIF ~WINDOWS}
 except
  on E:Exception do BugReport(E,Self,'MoveResizeWindow');
 end;
end;

function TWinManControl.CloseWindow(win:HWND; timeout:Integer=0):Boolean;
begin
 Result:=false;
 if Assigned(Self) then
 if OpenDisplay then
 try
  {$IFDEF UNIX}
  Result:=wmctrl_close_window(myDispHand,win,timeout);
  {$ENDIF ~UNIX}
  {$IFDEF WINDOWS}
  Result:=winapi_close_window(win,timeout);
  {$ENDIF ~WINDOWS}
 except
  on E:Exception do BugReport(E,Self,'CloseWindow');
 end;
end;

function TWinManControl.KillWindow(win:HWND; sig:cint=0; timeout:Integer=0):Boolean;
begin
 Result:=false;
 if Assigned(Self) then
 if OpenDisplay then
 try
  {$IFDEF UNIX}
  if (sig<=0) then sig:=SIGTERM;
  Result:=wmctrl_kill_window(myDispHand,win,sig,timeout);
  {$ENDIF ~UNIX}
  {$IFDEF WINDOWS}
  Result:=winapi_kill_window(win,sig,timeout);
  {$ENDIF ~WINDOWS}
 except
  on E:Exception do BugReport(E,Self,'KillWindow');
 end;
end;

function TWinManControl.IsWindow(win:HWND):Boolean;
begin
 Result:=false;
 if Assigned(Self) then
 if OpenDisplay then
 try
  {$IFDEF UNIX}
  Result:=OkWindow(win);
  {$ENDIF ~UNIX}
  {$IFDEF WINDOWS}
  Result:=(win<>0) and windows.IsWindow(win);
  {$ENDIF ~WINDOWS}
 except
  on E:Exception do BugReport(E,Self,'IsWindow');
 end;
end;

class function TWinManControl.StrToFlags(str,list:LongString):QWord;
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,list,ScanSpaces)-1;
  if (nb>=0) then Result:=Result or GetBitMask(nb);
 end;
end;

class function TWinManControl.FlagsToStr(flags:QWord; list:LongString):LongString;
var i:Integer; sw:LongString;
begin
 Result:='';
 if (flags=0) then Exit;
 for i:=0 to WordCount(list,ScanSpaces)-1 do begin
  if IsBit(flags,i) then sw:=ExtractWord(i+1,list,ScanSpaces) else sw:='';
  if (sw<>'') then Result:=Result+sw+',';
 end;
 if (Result<>'') then Result:=Copy(Result,1,Length(Result)-1);
end;

class function TWinManControl.StrToWSF(str:LongString):QWord;
begin
 Result:=StrToFlags(str,wm_all_window_states);
end;

class function TWinManControl.WSFToStr(wsf:QWord):LongString;
begin
 Result:=FlagsToStr(wsf,wm_all_window_states);
end;

class function TWinManControl.StrToWTF(str:LongString):QWord;
begin
 Result:=StrToFlags(str,wm_all_window_types);
end;

class function TWinManControl.WTFToStr(wtf:QWord):LongString;
begin
 Result:=FlagsToStr(wtf,wm_all_window_types);
end;

function GetLongOption(opt:LongString):LongString;
var args,arg:LongString;
begin
 Result:='';
 args:=GetCommandLine;
 if (Pos(opt,args)>0) then
 repeat
  arg:=ExtractFirstParam(args);
  args:=SkipFirstParam(args);
  if SameStr(arg,opt) then begin
   Result:=ExtractFirstParam(args);
   if (Result<>'') then Break;
  end;
  if (Pos(opt+'=',args)=1) then begin
   Result:=ExtractFirstParam(Copy(args,Length(opt)+2,Length(args)));
   if (Result<>'') then Break;
  end;
 until IsEmptyStr(args);
end;

const
 TheLongOptionName:LongString='';

function GetLongOptionName:LongString;
begin
 if (TheLongOptionName='')
 then TheLongOptionName:=Trim(GetLongOption('--name'));
 Result:=TheLongOptionName;
end;

const
 TheBaseProgName:LongString='';

function GetBaseProgName:LongString;
begin
 if (TheBaseProgName='')
 then TheBaseProgName:=ExtractFileName(ProgName);
 Result:=TheBaseProgName;
end;

 ///////////////////////////////////////////////////////////////////////////////
 // According to Inter-Client Communication Conventions Manual (ICCCM) ver 2,
 // window class (see WM_CLASS) should be like INSTANCE.CLASS, where INSTANCE
 // is option --name=INSTANCE or --name INSTANCE or the program`s basename.
 // In FPC programs CLASS is program`s basename with uppercase first char.
 // For  example, program "demo" has window class like "demo.Demo".
 ///////////////////////////////////////////////////////////////////////////////
class function TWinManControl.IcccmClass(aInst:LongString=''; aClass:LongString=''):LongString;
var oName:LongString;
begin
 aInst:=Trim(aInst);
 aClass:=Trim(aClass);
 if (aInst='') then begin
  oName:=GetLongOptionName;
  if IsEmptyStr(oName)
  then aInst:=GetBaseProgName
  else aInst:=oName;
 end;
 if (aClass='') then begin
  aClass:=ExtractFileName(ProgName);
  aClass:=UpCaseStr(Copy(aClass,1,1))+Copy(aClass,2,Length(aClass)-1);
 end;
 Result:=aInst+'.'+aClass;
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;
 {$IFDEF UNIX}
 wmctrl.SetErrorHandler(wmctrl_def_x_trap);
 wmctrl.SetIOErrorHandler(wmctrl_def_x_io_trap);
 {$ENDIF ~UNIX}
end;

procedure Free_crw_wmctrl;
begin
 Kill(TObject(the_wmctrl));
 TheLongOptionName:=''; TheBaseProgName:='';
 ResourceLeakageLog(Format('%-60s = %d',['Balance of XOpen/CloseDisplay', XDisplayBalance]));
end;

initialization

 Init_crw_wmctrl;

finalization

 Free_crw_wmctrl;

end.

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

