////////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2001-2026 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                              //
// 20240104 - Add TExtWinManHints, wmhints                                    //
// 20240531 - Improve FindWindow UTF8 compatibility (use IsSameText)          //
// 20241115 - wmctrl.query,wmctrl_query                                       //
// 20250129 - Use TAtomicCounter                                              //
// 20250214 - WM_GRAVITY_DEFAULT                                              //
// 20250902 - StrToWnd                                                        //
// 20251125 - IsHidden,HasWindowStateFlags                                    //
////////////////////////////////////////////////////////////////////////////////

unit _crw_wmctrl; // Window control routines.

{$I _crw_sysdef.inc}

{$I _crw_sysmode.inc}

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

interface

uses
 //////////////////////////////////////////////////////
 {$I _crw_uses_first.inc} // NB: MUST BE FIRST USES !!!
 //////////////////////////////////////////////////////
 {$IFDEF WINDOWS} jwawindows, {$ENDIF}
 sysutils, classes, math, types, lcltype, ctypes, strutils,
 {$IFDEF UNIX} unix, baseunix, x, xlib, xatom, glib2, {$ENDIF}
 _crw_alloc, _crw_cmdargs, _crw_rtc, _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:SizeInt;
  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 // Show/hide window in different states.
  function  ShowWindow(win:HWND; nCmdShow:Integer):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  SetWindowDesktop(win:HWND; aDesktop:Integer):Boolean;
  function  SetWindowTitle(win:HWND; aTitle:LongString):Boolean;
  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;
  function  IsHidden(win:HWND):Boolean;
  function  HasWindowStateFlags(win:HWND; state_flags:QWord):Boolean;
  class function  StrToWnd(const arg:LongString):HWnd;
  class function  StrToFlags(str,list:LongString; prefix: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
  class function  StrToWsc(str:LongString; def:Integer):Integer;
  class function  WscToStr(wsc:Integer):LongString;
  class function  StrToSw(str:LongString; def:Integer):Integer;
  class function  SwToStr(sw:Integer):LongString;
  class function  ValidateState(const s:LongString):LongString;
 public // The "instance.Class" for WM_CLASS property accorting to ICCCM 2.0.
  class function  IcccmClass(aInst:LongString=''; aClass:LongString=''):LongString;
 public // Get list of desktop managers from environment
  class function  ListDesktopManagers(Delim:LongString=EOL; Update:Boolean=false):LongString;
  class function  DesktopManager(n:Integer=1):LongString;
 public // Get list of console terminals from environment
  class function  ListTerminals(Delim:LongString=EOL; Update:Boolean=false):LongString;
  class function  Terminal(n:Integer=1):LongString;
 public // Default params for Query
  class var DefWndFmt:Integer;   // (0,1,2,3)=(d,u,x,$)=(%d,%u,0x%.8x,$.8%x)
  class var DefListMode:Integer; // See GetListOfWindows
 public // Query information
  function Query(const args:LongString):LongString;
 public // Default gravity uses by wmctrl
  class var WM_GRAVITY_DEFAULT:Integer;
 end;

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

 {
 wmctrl_query(args) - query wmctrl to get/set parameters.
 wmctrl_query DisplayInfo -mode m
 wmctrl_query XlibInfo -mode m
 wmctrl_query ListModes
 wmctrl_query ListWindows -pid p -class "c" -title "t" -mode m
 wmctrl_query FindWindow -pid p -class "c" -title "t" -index i
 wmctrl_query WindowManagerName
 wmctrl_query DesktopCount
 wmctrl_query ActiveDesktop
 wmctrl_query SwitchToDesktop -desktop d
 wmctrl_query ActiveWindow
 wmctrl_query ActivateWindow -wnd w -switch-desktop s
 wmctrl_query ShowWindow -wnd w -sw n
 wmctrl_query ShowWindow -wnd w
 wmctrl_query HideWindow -wnd w
 wmctrl_query MinimizeWindow -wnd w
 wmctrl_query MaximizeWindow -wnd w
 wmctrl_query RestoreWindow -wnd w
 wmctrl_query WindowPid -wnd w
 wmctrl_query WindowDesktop -wnd w
 wmctrl_query WindowHost -wnd w
 wmctrl_query WindowTitle -wnd w
 wmctrl_query WindowClass -wnd w
 wmctrl_query WindowBounds -wnd w
 wmctrl_query WindowStateList -wnd w
 wmctrl_query WindowTypeList -wnd w
 wmctrl_query WindowStateFlags -wnd w
 wmctrl_query WindowTypeFlags -wnd w
 wmctrl_query SetWindowStateList -wnd w -wsc c -state-list l
 wmctrl_query SetWindowStateFlags -wnd w -wsc c -state-flags f
 wmctrl_query SetWindowDesktop -wnd w -desktop d
 wmctrl_query SetWindowTitle -wnd w -title "t"
 wmctrl_query SupportedList
 wmctrl_query MoveResizeWindow -wnd w -geom g
 wmctrl_query CloseWindow -wnd w -timeout t
 wmctrl_query KillWindow -wnd w -sig s -timeout t
 wmctrl_query IcccmClass
 wmctrl_query ListDesktopManagers
 wmctrl_query DesktopManager -index i
 wmctrl_query ListTerminals
 wmctrl_query Terminal -index i
 }
function wmctrl_query(const args:LongString):LongString;

 {
 TExtWinManHints - Extended Window Manager Hints.
 https://specifications.freedesktop.org/wm-spec/wm-spec-latest.html
 }
type
 TExtWinManHints=class(TMasterObject)
 public // Extended Window Manager Hints
  class function _NET_SUPPORTED:LongString;
  class function _NET_CLIENT_LIST:LongString;
  class function _NET_NUMBER_OF_DESKTOPS:LongString;
  class function _NET_DESKTOP_GEOMETRY:LongString;
  class function _NET_DESKTOP_VIEWPORT:LongString;
  class function _NET_CURRENT_DESKTOP:LongString;
  class function _NET_DESKTOP_NAMES:LongString;
  class function _NET_ACTIVE_WINDOW:LongString;
  class function _NET_WORKAREA:LongString;
  class function _NET_SUPPORTING_WM_CHECK:LongString;
  class function _NET_VIRTUAL_ROOTS:LongString;
  class function _NET_DESKTOP_LAYOUT:LongString;
  class function _NET_SHOWING_DESKTOP:LongString;
  class function _NET_CLOSE_WINDOW:LongString;
  class function _NET_MOVERESIZE_WINDOW:LongString;
  class function _NET_WM_MOVERESIZE:LongString;
  class function _NET_RESTACK_WINDOW:LongString;
  class function _NET_REQUEST_FRAME_EXTENTS:LongString;
  class function _NET_WM_NAME:LongString;
  class function _NET_WM_VISIBLE_NAME:LongString;
  class function _NET_WM_ICON_NAME:LongString;
  class function _NET_WM_VISIBLE_ICON_NAME:LongString;
  class function _NET_WM_DESKTOP:LongString;
  class function _NET_WM_WINDOW_TYPE:LongString;
  class function _NET_WM_STATE:LongString;
  class function _NET_WM_ALLOWED_ACTIONS:LongString;
  class function _NET_WM_STRUT:LongString;
  class function _NET_WM_STRUT_PARTIAL:LongString;
  class function _NET_WM_ICON_GEOMETRY:LongString;
  class function _NET_WM_ICON:LongString;
  class function _NET_WM_PID:LongString;
  class function _NET_WM_HANDLED_ICONS:LongString;
  class function _NET_WM_USER_TIME:LongString;
  class function _NET_WM_USER_TIME_WINDOW:LongString;
  class function _NET_FRAME_EXTENTS:LongString;
  class function _NET_WM_OPAQUE_REGION:LongString;
  class function _NET_WM_BYPASS_COMPOSITOR:LongString;
  class function _NET_WM_PING:LongString;
  class function _NET_WM_SYNC_REQUEST:LongString;
  class function _NET_WM_FULLSCREEN_MONITORS:LongString;
  class function _NET_WM_FULL_PLACEMENT:LongString;
 public
  class function _WIN_CLIENT_LIST:LongString;
  class function _WIN_WORKSPACE:LongString;
  class function WM_CLIENT_MACHINE:LongString;
  class function WM_NAME:LongString;
  class function WM_CLASS:LongString;
 public // List of ALL hints, window states, window types.
  class function ListAllHints(const Delim:LongString=EOL):LongString;
  class function ListAllWindowTypes(const Delim:LongString=','):LongString;
  class function ListAllWindowStates(const Delim:LongString=','):LongString;
 end;

 {
 The instance of TExtWinManHints.
 }
function wmhints:TExtWinManHints;

{$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';
 wm_WindowStatePrefix='_NET_WM_STATE_';
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
 wsc_all_commands='WSC_REMOVE,WSC_ADD,WSC_TOGGLE';

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';
 wm_windowtypeprefix='_NET_WM_WINDOW_TYPE_';
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;

const                         // Values for TGdkGravity
 GDK_GRAVITY_DEFAULT    = 0;  // Marker for use default - WM_GRAVITY_DEFAULT
 GDK_GRAVITY_NORTH_WEST = 1;  // Gtk2/3
 GDK_GRAVITY_NORTH      = 2;  // Gtk2/3
 GDK_GRAVITY_NORTH_EAST = 3;  // Gtk2/3
 GDK_GRAVITY_WEST       = 4;  // Gtk2/3
 GDK_GRAVITY_CENTER     = 5;  // Gtk2/3
 GDK_GRAVITY_EAST       = 6;  // Gtk2/3
 GDK_GRAVITY_SOUTH_WEST = 7;  // Gtk2/3
 GDK_GRAVITY_SOUTH      = 8;  // Gtk2/3
 GDK_GRAVITY_SOUTH_EAST = 9;  // Gtk2/3
 GDK_GRAVITY_STATIC     = 10; // Gtk2/3
 GDK_GRAVITY_MIN        = 0;  // Alex
 GDK_GRAVITY_MAX        = 10; // Alex

const // ShowWindow Commands
 SW_HIDE            = 0;  // Hides the window and activates another window.
 SW_SHOWNORMAL      = 1;  // Activates and displays a window.
 SW_NORMAL          = 1;  // Synonym
 SW_SHOWMINIMIZED   = 2;  // Activates window, displays it as a minimized window.
 SW_SHOWMAXIMIZED   = 3;  // Activates window, displays it as a maximized window.
 SW_MAXIMIZE        = 3;  // Synonym
 SW_SHOWNOACTIVATE  = 4;  // Like SW_SHOWNORMAL, but window is not activated.
 SW_SHOW            = 5;  // Activate window & display it in current size and position.
 SW_MINIMIZE        = 6;  // Minimizes window, activate next top-level window in Z order.
 SW_SHOWMINNOACTIVE = 7;  // Like SW_SHOWMINIMIZED, but window is not activated.
 SW_SHOWNA          = 8;  // Like SW_SHOW, but window is not activated.
 SW_RESTORE         = 9;  // Activates and displays the window (restore after minimized).
 SW_SHOWDEFAULT     = 10; // Show in startup state.
 SW_MAX             = 10;
const // List of all ShowWindow commands.
 wm_sw_all_commands = 'SW_HIDE,SW_SHOWNORMAL,SW_SHOWMINIMIZED,SW_SHOWMAXIMIZED,'
                     +'SW_SHOWNOACTIVATE,SW_SHOW,SW_MINIMIZE,SW_SHOWMINNOACTIVE,'
                     +'SW_SHOWNA,SW_RESTORE,SW_SHOWDEFAULT';
 wm_sw_prefix       = 'SW_';

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

 {
 Get window class name by handle.
 }
function GetWindowClassName(hWnd:HWND):LongString;

 {
 Get window owner process ID by handle.
 }
function GetWindowProcessId(hWnd:HWND):DWORD;

 {
 Find window handle by PID and ClassName or return 0.
 }
function FindWindowByPidAndClassName(aPid:DWORD; const aClassName:LongString):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:LongString; 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:LongString;
        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:LongString):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;

 { Set window desktop number, 0-bases. }
function wmctrl_set_window_desktop(disp:PDisplay; win:TWindow; desktop:Integer):Boolean;

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

 { Get console window handle, for console applications only. Requires Win2k+. }
function GetConsoleWindow:HWND;

 { Send WM_COPYDATA message to process with ID aPid, to window with aClassName. }
function WM_COPYDATA_SendToWindowByPidAndClassName(hSender:HWND; aPid:DWORD;
         const aClassName,aData:LongString):LRESULT;

 { Get window by ThreadID. }
function GetThreadMainWindowHandle(aThreadID:THandle):hWnd;
{$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:TAtomicCounter=nil;

procedure InitXCounters;
begin
 LockedInit(XDisplayBalance);
end;

procedure FreeXCounters;
begin
 LockedFree(XDisplayBalance);
end;

function xGravity(g:cint):cint;
begin
 Result:=IfThen(g=GDK_GRAVITY_DEFAULT,TWinManControl.WM_GRAVITY_DEFAULT,g);
end;

{$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_set_window_title(win:HWND; aTitle:WideString):Boolean;
begin
 Result:=false;
 if IsWindow(win) then begin
  Result:=SetWindowTextW(win,PWChar(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);
  FakeNop(OTopLevel);
 end;
end;

function winapi_set_window_state_flags(win:HWND; wsc:cint; flags:QWord):QWord;
begin
 Result:=0;
 if IsWindow(win) then begin
  if HasFlags(flags,WSF_HIDDEN) then begin
   case wsc of
    WSC_REMOVE: begin
     if not IsWindowVisible(win) then ShowWindow(win,SW_SHOWNORMAL);
     if IsIconic(win) then ShowWindow(win,SW_RESTORE);
    end;
    WSC_ADD: begin
     if IsWindowVisible(win) then
     if not IsIconic(win) then ShowWindow(win,SW_SHOWMINNOACTIVE);
    end;
    WSC_TOGGLE: begin
     if IsIconic(win) or not IsWindowVisible(win)
     then ShowWindow(win,SW_RESTORE)
     else ShowWindow(win,SW_SHOWMINNOACTIVE);
    end;
   end;
  end;
  Result:=winapi_get_window_state_flags(win);
 end;
end;

function winapi_show_window(win:HWND; sw:Integer):Boolean;
begin
 Result:=false;
 if IsWindow(win) then begin
  Result:=windows.ShowWindow(win,sw);
 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;
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:=GDK_GRAVITY_DEFAULT; // f=flags; arg=gravity,X,Y,Width,Height
  if TryStrToInt(ExtractWord(1,arg,ScanSpaces),g) then f:=xGravity(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,GDK_GRAVITY_MIN,GDK_GRAVITY_MAX) 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;

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 IsSameText(lpClass,aClass) then Exit;
   aTitle:=winapi_get_window_title(Wnd);
   if (lpTitle<>'') and not IsSameText(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;
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(PointerToPtrInt(@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;

function GetConsoleWindow:HWND;
const _GetConsoleWindow:function:HWND; stdcall = nil;
begin
 if not Assigned(_GetConsoleWindow)
 then @_GetConsoleWindow:=GetProcAddress(GetModuleHandle('kernel32.dll'),'GetConsoleWindow');
 if Assigned(_GetConsoleWindow) then Result:=_GetConsoleWindow else Result:=0;
 if (Result<>0) then if not IsWindow(Result) then Result:=0;
end;

function GetThreadMainWindowHandle(aThreadID:THandle):hWnd;
 function CheckThreadWindows(Wnd: HWND; Data:LPARAM): BOOL; stdcall;
 var ParentWnd:hWnd; ExStyle:DWORD; Caption:TParsingBuffer;
 begin
  Result:=True;
  if (Wnd<>0) then
  if (Data<>0) then
  if IsWindow(Wnd) then begin
   ExStyle:=GetWindowLong(Wnd,GWL_EXSTYLE);
   ParentWnd:=GetWindowLong(Wnd,GWL_HWNDPARENT);
   if (ParentWnd=0) or (ParentWnd=GetDesktopWindow) then
   if (ExStyle and WS_EX_TOOLWINDOW=0) or (ExStyle and WS_EX_APPWINDOW<>0) then
   if GetWindowText(Wnd,Caption,SizeOf(Caption))>0 then begin
    hWnd(PtrIntToPointer(Data)^):=Wnd;
    Result:=false;
   end;
  end;
 end;
begin
 Result:=0;
 try
  EnumThreadWindows(aThreadID, @CheckThreadWindows, LPARAM(PointerToPtrInt(@Result)));
 except
  on E:Exception do BugReport(E,nil,'GetThreadMainWindowHandle');
 end;
end;

function WM_COPYDATA_SendToWindowByPidAndClassName(hSender:HWND; aPid:DWORD; const aClassName,aData:LongString):LRESULT;
var hWin:HWND; DataRec:TCopyDataStruct;
begin
 Result:=0;
 if (aData<>'') then
 try
  if (aPid<>0) then begin
   hWin:=FindWindowByPidClassTitle(aPid,aClassName,'');
   if hWin<>0 then begin
    DataRec.dwData:=aPid;
    DataRec.cbData:=Length(aData);
    DataRec.lpData:=PChar(aData);
    Result:=SendMessage(hWin,WM_COPYDATA,hSender,PointerToPtrInt(@DataRec));
   end;
  end;
 except
  on E:Exception do BugReport(E,nil,'WM_COPYDATA_SendToWindowByPidAndClassName');
 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:LongString; 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 (prop_name<>'') then
 try
  if wmctrl_open_display(disp,NeedClose) then
  try
   xa_prop_name:=XInternAtom(disp,PChar(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:LongString;
        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 (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,PChar(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:LongString):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,PChar(prop),false);
  if wmctrl_get_property(disp,rwin,XA_ATOM,wmhints._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,wmhints._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,wmhints._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,wmhints._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,wmhints._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,wmhints._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,wmhints._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,wmhints._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,wmhints._NET_WM_DESKTOP,s);
  if (s='') then wmctrl_get_property(disp,win,XA_CARDINAL,wmhints._WIN_WORKSPACE,s);
  if (Length(s)=SizeOf(culong)) then Result:=culong(Pointer(s)^);
 finally
  wmctrl_close_display(disp,NeedClose);
 end;
end;

function wmctrl_set_window_desktop(disp:PDisplay; win:TWindow; desktop:Integer):Boolean;
var NeedClose:Boolean; desknum:Integer;
begin
 Result:=false;
 if OkWindow(win) then
 if wmctrl_open_display(disp,NeedClose) then
 try
  desknum:=wmctrl_get_number_of_desktops(disp);
  if (desknum>0) and (desktop<desknum) then begin
   Result:=wmctrl_client_msg(disp,win,wmhints._NET_WM_DESKTOP,desktop,0,0,0,0);
  end;
 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,wmhints._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,wmhints.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,wmhints._NET_WM_NAME,Result);
  if (Result='') then wmctrl_get_property(disp,win,XA_STRING,wmhints.WM_NAME,Result);
  if (Result='') then Result:=def else Result:=TrimRightChars(Result,[#0]);
 finally
  wmctrl_close_display(disp,NeedClose);
 end;
end;

function wmctrl_set_window_title(disp:PDisplay; win:TWindow; title:LongString):Boolean;
var NeedClose:Boolean; xa1,xa2:TAtom; ret:cint; title_utf8:pgchar;
begin
 Result:=false;
 if OkWindow(win) then
 if wmctrl_open_display(disp,NeedClose) then
 try
  title_utf8:=g_strdup(PChar(title));
  xa1:=XInternAtom(disp,PChar(wmhints._NET_WM_NAME),false);
  xa2:=XInternAtom(disp,'UTF8_STRING',false);
  ret:=XChangeProperty(disp,win,xa1,xa2,8,PropModeReplace,
                       pcuchar(title_utf8),strlen(title_utf8));
  g_free(title_utf8);
  Result:=(ret<>-1);
 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,wmhints.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,wmhints._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,wmhints._NET_WM_WINDOW_TYPE,Delim);
end;

function wmctrl_set_window_state_list(disp:PDisplay; win:TWindow; wsc:Integer; state_list:LongString):QWord;
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:=TWinManControl.ValidateState(ExtractWord(iw1,state_list,ScanSpaces));
   sw2:=TWinManControl.ValidateState(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,wmhints._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,wmhints._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,wmhints._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:=GDK_GRAVITY_DEFAULT; // f=flags; arg=gravity,X,Y,Width,Height
  if TryStrToInt(ExtractWord(1,arg,ScanSpaces),g) then f:=xGravity(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,GDK_GRAVITY_MIN,GDK_GRAVITY_MAX) then Exit; // Invalid gravity found
  if not HasFlags(f,hasX+hasY+hasW+hasH) then Exit; // Bad coordinates
  if wmctrl_is_supported(disp,wmhints._NET_MOVERESIZE_WINDOW) then begin
   Result:=wmctrl_client_msg(disp,win,wmhints._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,wmhints._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 wmctrl_show_window(disp:PDisplay; win:TWindow; nCmdShow:Integer):Boolean;
var NeedClose:Boolean; wFlags:QWord;
 function RemWsf(wsf:QWord):Boolean;
 var wsl:LongString;
 begin
  wsf:=wsf and wFlags; // Remove flags if one present.
  wsl:=TWinManControl.WSFToStr(wsf); if (wsf=0) then Exit(true);
  Result:=(wmctrl_set_window_state_list(disp,win,WSC_REMOVE,wsl)<>0);
 end;
 function AddWsf(wsf:QWord):Boolean;
 var wsl:LongString;
 begin
  wsf:=wsf and not wFlags; // Add flags if one not present.
  wsl:=TWinManControl.WSFToStr(wsf); if (wsf=0) then Exit(true);
  Result:=(wmctrl_set_window_state_list(disp,win,WSC_ADD,wsl)<>0);
 end;
begin
 Result:=false;
 if OkWindow(win) then
 if wmctrl_open_display(disp,NeedClose) then
 try
  wFlags:=TWinManControl.StrToWSF(wmctrl_get_window_state_list(disp,win));
  case nCmdShow of
   SW_HIDE: begin
    Result:=RemWsf(WSF_MAXIMIZED+WSF_FULLSCREEN)
        and AddWsf(WSF_HIDDEN);
   end;
   SW_SHOWNORMAL: begin
    Result:=RemWsf(WSF_MAXIMIZED+WSF_FULLSCREEN+WSF_HIDDEN)
        and AddWsf(0);
    Result:=Result and wmctrl_activate_window(disp,win,false);
   end;
   SW_SHOWMINIMIZED:begin
    Result:=RemWsf(WSF_MAXIMIZED+WSF_FULLSCREEN)
        and AddWsf(WSF_HIDDEN);
   end;
   SW_SHOWMAXIMIZED:begin
    Result:=RemWsf(WSF_HIDDEN+WSF_FULLSCREEN)
        and AddWsf(WSF_MAXIMIZED);
    Result:=Result and wmctrl_activate_window(disp,win,false);
   end;
   SW_SHOWNOACTIVATE:begin
    Result:=RemWsf(WSF_MAXIMIZED+WSF_FULLSCREEN+WSF_HIDDEN)
        and AddWsf(0);
   end;
   SW_SHOW:begin
    Result:=RemWsf(WSF_MAXIMIZED+WSF_FULLSCREEN+WSF_HIDDEN)
        and AddWsf(0);
    Result:=Result and wmctrl_activate_window(disp,win,false);
   end;
   SW_MINIMIZE:begin
    Result:=RemWsf(WSF_MAXIMIZED+WSF_FULLSCREEN)
        and AddWsf(WSF_HIDDEN);
   end;
   SW_SHOWMINNOACTIVE:begin
    Result:=RemWsf(WSF_MAXIMIZED+WSF_FULLSCREEN)
        and AddWsf(WSF_HIDDEN);
   end;
   SW_SHOWNA:begin
    Result:=RemWsf(WSF_MAXIMIZED+WSF_FULLSCREEN+WSF_HIDDEN)
        and AddWsf(0);
   end;
   SW_RESTORE:begin
    Result:=RemWsf(WSF_MAXIMIZED+WSF_FULLSCREEN+WSF_HIDDEN)
        and AddWsf(0);
    Result:=Result and wmctrl_activate_window(disp,win,false);
   end;
   SW_SHOWDEFAULT:begin
    Result:=RemWsf(WSF_MAXIMIZED+WSF_FULLSCREEN+WSF_HIDDEN)
        and AddWsf(0);
    Result:=Result and wmctrl_activate_window(disp,win,false);
   end;
  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 IsSameText(swc,aClass) then continue;
    if (aTitle<>'') and not IsSameText(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;

function GetWindowClassName(hWnd:HWND):LongString;
begin
 Result:=wmctrl.WindowClass(hWnd);
end;

function GetWindowProcessId(hWnd:HWND):DWORD;
begin
 Result:=wmctrl.WindowPid(hWnd);
end;

function FindWindowByPidAndClassName(aPid:DWORD; const aClassName:LongString):HWND;
begin
 Result:=wmctrl.FindWindow(aPid,aClassName,'');
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;
   myDispName:=aName;
   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:SizeInt;
begin
 Result:=LockedGet(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,line:LongString;
 begin
  err:=GetErrorText;
  if (err<>'') then msg:=err;
  dsp:=StrPas(XDisplayName(nil));
  if (dsp<>'') then dsp:=' on display '+dsp;
  line:='XError'+dsp+' => '+msg;
  if SysLogNotable(SeverityOfXLibBugs)
  then SysLogNote(0,SeverityOfXLibBugs,sdr_SysXLib,line);
  Echo(line);
 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,line:LongString;
 begin
  dsp:=StrPas(XDisplayName(nil));
  if (dsp<>'') then dsp:=' on display '+dsp;
  line:='XIOError'+dsp+' => '+msg;
  if SysLogNotable(SeverityOfXLibFail)
  then SysLogNote(0,SeverityOfXLibFail,sdr_SysXLib,line);
  Echo(line);
 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.SetWindowDesktop(win:HWND; aDesktop:Integer):Boolean;
begin
 Result:=false;
 if Assigned(Self) then
 if OpenDisplay then
 try
  {$IFDEF UNIX}
  Result:=wmctrl_set_window_desktop(myDispHand,win,aDesktop);
  {$ENDIF ~UNIX}
  {$IFDEF WINDOWS}
  Result:=true;
  {$ENDIF ~WINDOWS}
 except
  on E:Exception do BugReport(E,Self,'SetWindowDesktop');
 end;
end;

function TWinManControl.SetWindowTitle(win:HWND; aTitle:LongString):Boolean;
begin
 Result:=false;
 if Assigned(Self) then
 if OpenDisplay then
 try
  {$IFDEF UNIX}
  Result:=wmctrl_set_window_title(myDispHand,win,aTitle);
  {$ENDIF ~UNIX}
  {$IFDEF WINDOWS}
  Result:=winapi_set_window_title(win,StrToWide(aTitle));
  {$ENDIF ~WINDOWS}
 except
  on E:Exception do BugReport(E,Self,'SetWindowDesktop');
 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.ShowWindow(win:HWND; nCmdShow:Integer):Boolean;
begin
 Result:=false;
 if Assigned(Self) then
 if OpenDisplay then
 try
  {$IFDEF UNIX}
  Result:=wmctrl_show_window(myDispHand,win,nCmdShow);
  {$ENDIF ~UNIX}
  {$IFDEF WINDOWS}
  Result:=winapi_show_window(win,nCmdShow);
  {$ENDIF ~WINDOWS}
 except
  on E:Exception do BugReport(E,Self,'ShowWindow');
 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;

function TWinManControl.IsHidden(win:HWND):Boolean;
begin
 if Assigned(Self)
 then Result:=HasWindowStateFlags(win,WSF_HIDDEN)
 else Result:=false;
end;

function TWinManControl.HasWindowStateFlags(win:HWND; state_flags:QWord):Boolean;
begin
 Result:=false;
 if Assigned(Self) then
 if OpenDisplay then
 try
  Result:=IsWindow(win) and HasFlags(WindowStateFlags(win),state_flags);
 except
  on E:Exception do BugReport(E,Self,'HasWindowStateFlags');
 end;
end;

class function TWinManControl.StrToWnd(const arg:LongString):HWnd;
var S:LongString;
begin
 Result:=0; S:='';
 if IsEmptyStr(arg) then Exit else S:=Trim(arg);
 if StartsText('0x',S) then S:='$'+TailStr(S,3);
 Result:=StrToIntDef(S,0);
end;

class function TWinManControl.StrToFlags(str,list:LongString; prefix: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) and (prefix<>'')
  then nb:=WordIndex(prefix+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,wm_windowstateprefix);
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,wm_windowtypeprefix);
end;

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

class function TWinManControl.StrToWsc(str:LongString; def:Integer):Integer;
var i:Integer; sw:LongString;
const Prefix='WSC_';
begin
 str:=UpperCase(Trim(str));
 Result:=StrToIntDef(str,-1); if (Result>=0) then Exit;
 for i:=1 to WordCount(wsc_all_commands,ScanSpaces) do begin
  sw:=ExtractWord(i,wsc_all_commands,ScanSpaces);
  if SameText(sw,Prefix+str) then Exit(i-1);
  if SameText(sw,str) then Exit(i-1);
 end;
 if (Result<0) then Result:=def;
end;

class function TWinManControl.WscToStr(wsc:Integer):LongString;
begin
 Result:=ExtractWord(1+wsc,wsc_all_commands,ScanSpaces);
 if (Result='') then Result:=IntToStr(wsc);
end;

class function TWinManControl.StrToSw(str:LongString; def:Integer):Integer;
const Prefix=wm_sw_prefix; var i:Integer; sw:LongString;
begin
 str:=UpperCase(Trim(str));
 Result:=StrToIntDef(str,-1); if (Result>=0) then Exit;
 for i:=1 to WordCount(wm_sw_all_commands,ScanSpaces) do begin
  sw:=ExtractWord(i,wm_sw_all_commands,ScanSpaces);
  if SameText(sw,Prefix+str) then Exit(i-1);
  if SameText(sw,str) then Exit(i-1);
 end;
 if (Result<0) then Result:=def;
end;

class function TWinManControl.SwToStr(sw:Integer):LongString;
begin
 Result:=ExtractWord(1+sw,wm_sw_all_commands,ScanSpaces);
 if (Result='') then Result:=IntToStr(sw);
end;


class function TWinManControl.ValidateState(const s:LongString):LongString;
const Prefix=wm_WindowStatePrefix;
begin
 Result:=UpperCase(s);
 if (Pos(Prefix,Result)=0) then
 if (WordIndex(Prefix+Result,wm_all_window_states,ScanSpaces)>0)
 then Result:=Prefix+Result;
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;

const
 TheListDesktopManagers:LongString='';

class function TWinManControl.ListDesktopManagers(Delim:LongString=EOL; Update:Boolean=false):LongString;
const DmVars='XDG_CURRENT_DESKTOP,XDG_SESSION_DESKTOP,DESKTOP_SESSION,DE';
var List:TStringList; i,j:Integer; dmv,dms:LongString;
begin
 if Update then TheListDesktopManagers:='';
 Result:=TheListDesktopManagers;
 if (Result='') then
 try
  List:=TStringList.Create;
  try
   List.Duplicates:=dupIgnore;
   if IsWindows then List.Add('windows');
   if IsUnix then begin
    for i:=1 to WordCount(DmVars,ScanSpaces) do begin
     dmv:=GetEnv(ExtractWord(i,DmVars,ScanSpaces));
     for j:=1 to WordCount(dmv,ScanSpaces+[':']) do begin
      dms:=ExtractWord(j,dmv,ScanSpaces+[':']);
      if (List.IndexOf(dms)<0) then List.Add(dms);
     end;
    end;
   end;
   Result:=List.Text;
   TheListDesktopManagers:=Result;
  finally
   List.Free;
  end;
 except
  on E:Exception do BugReport(E,nil,'ListDesktopManagers');
 end;
 if (Result<>'') and (Delim<>EOL)
 then Result:=StringReplace(Trim(Result),EOL,Delim,[rfReplaceAll]);
end;

class function TWinManControl.DesktopManager(n:Integer=1):LongString;
begin
 Result:=ExtractWord(n,ListDesktopManagers,ScanSpaces);
end;

const
 TheListTerminals:LongString='';

class function TWinManControl.ListTerminals(Delim:LongString=EOL; Update:Boolean=false):LongString;
const KnownXTerminals='xterm,x-terminal-emulator,fly-term,gnome-terminal,xfce4-terminal,konsole,aterm,wterm,rxvt';
const KnownWTerminals='cmd,powershell';
var List:TStringList; i:Integer; wterm,xterm,dlist,xlist:LongString;
begin
 if Update then TheListTerminals:='';
 Result:=TheListTerminals;
 if (Result='') then
 try
  List:=TStringList.Create;
  try
   List.Duplicates:=dupIgnore;
   if IsWindows then begin
    for i:=1 to WordCount(KnownWTerminals,ScanSpaces) do begin
     wterm:=ExtractWord(i,KnownWTerminals,ScanSpaces);
     if (file_which(wterm+'.exe')='') then continue;
     if (List.IndexOf(wterm)<0) then List.Add(wterm);
    end;
   end;
   if IsUnix then begin
    xlist:=KnownXTerminals; dlist:=ListDesktopManagers(',');
    for i:=1 to WordCount(dlist,ScanSpaces) do begin
     xlist:=xlist+','+ExtractWord(i,dlist,ScanSpaces)+'-term';
     xlist:=xlist+','+ExtractWord(i,dlist,ScanSpaces)+'-terminal';
    end;
    for i:=1 to WordCount(xlist,ScanSpaces) do begin
     xterm:=ExtractWord(i,xlist,ScanSpaces);
     if (List.IndexOf(xterm)<0) then List.Add(xterm);
    end;
    xlist:=List.CommaText; List.Clear;
    for i:=1 to WordCount(xlist,ScanSpaces) do begin
     xterm:=ExtractWord(i,xlist,ScanSpaces);
     wterm:=file_which(xterm);
     if (wterm='') then continue;
     if (List.IndexOf(xterm)<0) then List.Add(xterm);
     wterm:=ExtractFileName(GetRealFilePathName(wterm));
     if IsSameText(xterm,wterm) or (file_which(wterm)='') then continue;
     if (List.IndexOf(wterm)<0) then List.Add(wterm);
    end;
   end;
   Result:=List.Text;
   TheListTerminals:=Result;
  finally
   List.Free;
  end;
 except
  on E:Exception do BugReport(E,nil,'ListTerminals');
 end;
 if (Result<>'') and (Delim<>EOL)
 then Result:=StringReplace(Trim(Result),EOL,Delim,[rfReplaceAll]);
end;

class function TWinManControl.Terminal(n:Integer=1):LongString;
begin
 Result:=ExtractWord(n,ListTerminals,ScanSpaces);
end;

function TWinManControl.Query(const args:LongString):LongString;
var mode,pid,index,desktop,swd,wsc,iter,timeout,sig,wndfmt,sw:Integer; wnd:HWND;
var sMode,sPid,sIndex,sDesktop,sSwd,sSL,sSF,sWsc,sGeom,sTimeout,sSig:LongString;
var what,opt,tail,sWnd,sClass,sTitle,sInst,sWndFmt,sSw:LongString; sf:QWord;
const MaxIter=20;
 procedure Fix0x(var s:LongString);
 begin
  if StartsText(s,'0x') and IsLexeme(DropNLeadStr(s,2),lex_xdigit)
  then s:='$'+DropNLeadStr(s,2);
 end;
 function FormatWnd(wnd:HWND):LongString;
 begin
  case wndfmt of
   0:   Result:=IntToStr(wnd);
   1:   Result:=Format('%u',[wnd]);
   2:   Result:=Format('0x%.8x',[wnd]);
   3:   Result:=Format('$%.8x',[wnd]);
   else Result:=IntToStr(wnd);
  end;
 end;
 function StrToWndDef(s:LongString; def:HWND):HWND;
 begin
  Result:=StrToInt64Def(Trim(s),def);
 end;
 function StrToWndFmtDef(s:LongString; def:Integer):Integer;
 begin
  if (WordIndex(Trim(s),'d,0',ScanSpaces)>0) then Exit(0);
  if (WordIndex(Trim(s),'u,1',ScanSpaces)>0) then Exit(1);
  if (WordIndex(Trim(s),'x,2',ScanSpaces)>0) then Exit(2);
  if (WordIndex(Trim(s),'$,3',ScanSpaces)>0) then Exit(3);
  Result:=StrToIntDef(Trim(s),def);
 end;

 function ShiftWord(var arg:LongString):LongString;
 begin
  Result:=ExtractWord(1,arg,JustSpaces);
  arg:=SkipWords(1,arg,JustSpaces);
 end;
 function ShiftPhrase(var arg:LongString):LongString;
 begin
  Result:=ExtractPhrase(1,arg,JustSpaces);
  arg:=SkipPhrases(1,arg,JustSpaces);
 end;
 function CheckOpt(const opt,idn:LongString; var tail,s:LongString; fix:Boolean):Boolean;
 begin
  Result:=false;
  if IsOption(opt,idn,'-'+idn) then begin
   s:=ShiftPhrase(tail);
   if fix then Fix0x(s);
   Result:=true;
  end;
 end;
 procedure ValidateWnd(var w:HWND; sp,sc,st:LongString);
 var p,n:Integer;
 begin
  if (w=0) then begin
   p:=StrToIntDef(sp,0);
   n:=BoolToInt(p<>0)+BoolToInt(sc<>'')+BoolToInt(st<>'')*2;
   if (n>1) then w:=FindWindow(p,sc,st);
  end;
 end;
 procedure FlagToWnd(var str:LongString; wnd:HWND);
 begin
  if (wnd<>0) and SameText(str,'1') then str:=FormatWnd(wnd);
 end;
begin
 Result:='';
 if Assigned(Self) then
 try
  mode:=0; pid:=0; index:=0; desktop:=0; sw:=0;
  swd:=0; wsc:=0; iter:=0; timeout:=0; sig:=0; wndfmt:=0; sWndFmt:=''; sSw:='';
  what:=''; opt:=''; sMode:=''; sPid:=''; sIndex:=''; sDesktop:=''; sSwd:='';
  sSL:=''; sSF:=''; sWsc:=''; sGeom:=''; sTimeout:=''; sSig:=''; sInst:='';
  sWnd:=''; sClass:=''; sTitle:='';
  tail:=args;
  what:=ShiftWord(tail);
  while IsOption(tail) do begin
   opt:=ShiftWord(tail);
   if (Iter>MaxIter) then Exit;
   if CheckOpt(opt,'-mode',tail,sMode,true)
   or CheckOpt(opt,'-pid',tail,sPid,true)
   or CheckOpt(opt,'-index',tail,sIndex,true)
   or CheckOpt(opt,'-desktop',tail,sDesktop,true)
   or CheckOpt(opt,'-switch-desktop',tail,sSwd,true)
   or CheckOpt(opt,'-wnd',tail,sWnd,true)
   or CheckOpt(opt,'-win',tail,sWnd,true)
   or CheckOpt(opt,'-class',tail,sClass,false)
   or CheckOpt(opt,'-title',tail,sTitle,false)
   or CheckOpt(opt,'-state-list',tail,sSL,false)
   or CheckOpt(opt,'-state-flags',tail,sSF,false)
   or CheckOpt(opt,'-wsc',tail,sWsc,true)
   or CheckOpt(opt,'-geom',tail,sGeom,false)
   or CheckOpt(opt,'-timeout',tail,sTimeout,true)
   or CheckOpt(opt,'-wndfmt',tail,sWndFmt,true)
   or CheckOpt(opt,'-sig',tail,sSig,false)
   or CheckOpt(opt,'-inst',tail,sInst,false)
   or CheckOpt(opt,'-sw',tail,sSw,true)
   then Inc(Iter)
   else Exit;
  end;
  wndfmt:=StrToWndFmtDef(sWndFmt,DefWndFmt);
  if IsSameText(what,'DisplayInfo') then begin
   mode:=StrToIntDef(sMode,-1);
   Result:=DisplayInfo(mode);
  end else
  if IsSameText(what,'XLibInfo') then begin
   mode:=StrToIntDef(sMode,-1);
   Result:=XLibInfo(mode);
  end else
  if IsSameText(what,'ListWindows') then begin
   pid:=StrToIntDef(sPid,0);
   mode:=StrToIntDef(sMode,DefListMode);
   Result:=ListWindows(pid,sClass,sTitle,mode);
  end else
  if IsSameText(what,'ListModes') then begin
   Result:='ListWindows -mode values:'+EOL+
           ' -mode 0 : $WND, PID, CLASS, TITLE'+EOL+
           ' -mode 1 : WND, PID, "CLASS", "TITLE"'+EOL+
           ' -mode 2 : 0xWND DSK HOST TITLE'+EOL+
           ' -mode 3 : 0xWND DSK PID HOST TITLE'+EOL+
           ' -mode 4 : 0xWND DSK CLASS  HOST TITLE'+EOL+
           ' -mode 5 : 0xWND DSK PID CLASS  HOST TITLE'+EOL+
           ' -mode 6 : WND, DSK, PID, "CLASS", "HOST", "TITLE"'+EOL+
           'where:'+EOL+
           ' WND,$WND,0xWND - window ID as DEC,$HEX,0xHEX'+EOL+
           ' DSK   - desktop number where window located'+EOL+
           ' PID   - window owner Process ID'+EOL+
           ' HOST  - window owner hostname'+EOL+
           ' CLASS - window class name'+EOL+
           ' TITLE - window title text'+EOL;
  end else
  if IsSameText(what,'FindWindow') then begin
   pid:=StrToIntDef(sPid,0);
   index:=StrToIntDef(sIndex,0);
   Result:=FormatWnd(FindWindow(pid,sClass,sTitle,index));
  end else
  if IsSameText(what,'WindowManagerName') then begin
   Result:=WindowManagerName;
  end else
  if IsSameText(what,'DesktopCount') then begin
   Result:=IntToStr(DesktopCount);
  end else
  if IsSameText(what,'ActiveDesktop') then begin
   Result:=IntToStr(ActiveDesktop);
  end else
  if IsSameText(what,'SwitchToDesktop') then begin
   desktop:=StrToIntDef(sDesktop,-1);
   if (desktop>=0) then Result:=IntToStr(Ord(SwitchToDesktop(desktop)));
   if SameText(Result,'1') then Result:=IntToStr(desktop) else Result:='';
  end else
  if IsSameText(what,'ActiveWindow') then begin
   Result:=FormatWnd(ActiveWindow);
  end else
  if IsSameText(what,'ActivateWindow') then begin
   wnd:=StrToWndDef(sWnd,0);
   swd:=StrToIntDef(sSwd,1);
   ValidateWnd(wnd,sPid,sClass,sTitle);
   if (wnd<>0) then Result:=IntToStr(Ord(ActivateWindow(wnd,(swd<>0))));
   FlagToWnd(Result,wnd);
  end else
  if IsSameText(what,'WindowPid') then begin
   wnd:=StrToWndDef(sWnd,0);
   ValidateWnd(wnd,sPid,sClass,sTitle);
   if (wnd<>0) then Result:=IntToStr(WindowPid(wnd));
  end else
  if IsSameText(what,'WindowDesktop') then begin
   wnd:=StrToWndDef(sWnd,0);
   ValidateWnd(wnd,sPid,sClass,sTitle);
   if (wnd<>0) then Result:=IntToStr(WindowDesktop(wnd));
  end else
  if IsSameText(what,'WindowHost') then begin
   wnd:=StrToWndDef(sWnd,0);
   ValidateWnd(wnd,sPid,sClass,sTitle);
   if (wnd<>0) then Result:=WindowHost(wnd);
  end else
  if IsSameText(what,'WindowTitle') then begin
   wnd:=StrToWndDef(sWnd,0);
   ValidateWnd(wnd,sPid,sClass,sTitle);
   if (wnd<>0) then Result:=WindowTitle(wnd);
  end else
  if IsSameText(what,'WindowClass') then begin
   wnd:=StrToWndDef(sWnd,0);
   ValidateWnd(wnd,sPid,sClass,sTitle);
   if (wnd<>0) then Result:=WindowClass(wnd);
  end else
  if IsSameText(what,'WindowBounds') then begin
   wnd:=StrToWndDef(sWnd,0);
   ValidateWnd(wnd,sPid,sClass,sTitle);
   if (wnd<>0) then with WindowBounds(wnd)
   do Result:=Format('%d %d %d %d %d %d',[Left,Top,Right,Bottom,Width,Height]);
  end else
  if IsSameText(what,'WindowStateList') then begin
   wnd:=StrToWndDef(sWnd,0);
   ValidateWnd(wnd,sPid,sClass,sTitle);
   if (wnd<>0) then Result:=WindowStateList(wnd);
  end else
  if IsSameText(what,'WindowTypeList') then begin
   wnd:=StrToWndDef(sWnd,0);
   ValidateWnd(wnd,sPid,sClass,sTitle);
   if (wnd<>0) then Result:=WindowTypeList(wnd);
  end else
  if IsSameText(what,'WindowStateFlags') then begin
   wnd:=StrToWndDef(sWnd,0);
   ValidateWnd(wnd,sPid,sClass,sTitle);
   if (wnd<>0) then Result:=Format('$%x',[WindowStateFlags(wnd)]);
  end else
  if IsSameText(what,'WindowTypeFlags') then begin
   wnd:=StrToWndDef(sWnd,0);
   ValidateWnd(wnd,sPid,sClass,sTitle);
   if (wnd<>0) then Result:=Format('$%x',[WindowTypeFlags(wnd)]);
  end else
  if IsSameText(what,'SetWindowStateList') then begin
   wnd:=StrToWndDef(sWnd,0);
   wsc:=StrToWsc(sWsc,-1);
   ValidateWnd(wnd,sPid,sClass,sTitle);
   if (wnd<>0) and (wsc>=0) and (sSL<>'')
   then Result:=IntToStr(SetWindowStateList(wnd,wsc,sSL));
  end else
  if IsSameText(what,'SetWindowStateFlags') then begin
   wnd:=StrToWndDef(sWnd,0);
   sf:=StrToInt64Def(sSF,0);
   wsc:=StrToWsc(sWsc,-1);
   ValidateWnd(wnd,sPid,sClass,sTitle);
   if (wnd<>0) and (wsc>=0) and (sSF<>'')
   then Result:=IntToStr(SetWindowStateFlags(wnd,wsc,sf));
  end else
  if IsSameText(what,'SetWindowDesktop') then begin
   wnd:=StrToWndDef(sWnd,0);
   desktop:=StrToIntDef(sDesktop,-1);
   ValidateWnd(wnd,sPid,sClass,sTitle);
   if (wnd<>0) and (desktop>=0)
   then Result:=IntToStr(Ord(SetWindowDesktop(wnd,desktop)));
   FlagToWnd(Result,wnd);
  end else
  if IsSameText(what,'SetWindowTitle') then begin
   wnd:=StrToWndDef(sWnd,0);
   ValidateWnd(wnd,sPid,sClass,sTitle);
   if (wnd<>0) and (sTitle<>'')
   then Result:=IntToStr(Ord(SetWindowTitle(wnd,sTitle)));
   FlagToWnd(Result,wnd);
  end else
  if IsSameText(what,'SupportedList') then begin
   Result:=SupportedList;
  end else
  if IsSameText(what,'MoveResizeWindow') then begin
   wnd:=StrToWndDef(sWnd,0);
   ValidateWnd(wnd,sPid,sClass,sTitle);
   if (wnd<>0) and (sGeom<>'')
   then Result:=IntToStr(Ord(MoveResizeWindow(wnd,sGeom)));
   FlagToWnd(Result,wnd);
  end else
  if IsSameText(what,'CloseWindow') then begin
   wnd:=StrToWndDef(sWnd,0);
   timeout:=StrToIntDef(sTimeout,0);
   ValidateWnd(wnd,sPid,sClass,sTitle);
   if (wnd<>0) and (timeout>=0)
   then Result:=IntToStr(Ord(CloseWindow(wnd,timeout)));
   FlagToWnd(Result,wnd);
  end else
  if IsSameText(what,'KillWindow') then begin
   wnd:=StrToWndDef(sWnd,0);
   sig:=StringToSigCode(sSig,0);
   timeout:=StrToIntDef(sTimeout,0);
   ValidateWnd(wnd,sPid,sClass,sTitle);
   if (wnd<>0) and (sig>=0) and (timeout>=0)
   then Result:=IntToStr(Ord(KillWindow(wnd,sig,timeout)));
   FlagToWnd(Result,wnd);
  end else
  if IsSameText(what,'IcccmClass') then begin
   Result:=IcccmClass(sInst,sClass);
  end else
  if IsSameText(what,'ListDesktopManagers') then begin
   Result:=ListDesktopManagers;
  end else
  if IsSameText(what,'DesktopManager') then begin
   index:=StrToIntDef(sIndex,1);
   Result:=DesktopManager(index);
  end else
  if IsSameText(what,'ListTerminals') then begin
   Result:=ListTerminals;
  end else
  if IsSameText(what,'Terminal') then begin
   index:=StrToIntDef(sIndex,1);
   Result:=Terminal(index);
  end else
  if IsSameText(what,'ShowWindow') then begin
   wnd:=StrToWndDef(sWnd,0);
   sw:=StrToSW(sSw,SW_SHOWNORMAL);
   ValidateWnd(wnd,sPid,sClass,sTitle);
   if (wnd<>0) and InRange(sw,SW_HIDE,SW_MAX)
   then Result:=IntToStr(Ord(ShowWindow(wnd,sw)));
   FlagToWnd(Result,wnd);
  end else
  if IsSameText(what,'HideWindow') then begin
   wnd:=StrToWndDef(sWnd,0);
   sw:=StrToSW(sSw,SW_HIDE);
   ValidateWnd(wnd,sPid,sClass,sTitle);
   if (wnd<>0) and InRange(sw,SW_HIDE,SW_MAX)
   then Result:=IntToStr(Ord(ShowWindow(wnd,sw)));
   FlagToWnd(Result,wnd);
  end else
  if IsSameText(what,'MinimizeWindow') then begin
   wnd:=StrToWndDef(sWnd,0);
   sw:=StrToSW(sSw,SW_SHOWMINIMIZED);
   ValidateWnd(wnd,sPid,sClass,sTitle);
   if (wnd<>0) and InRange(sw,SW_HIDE,SW_MAX)
   then Result:=IntToStr(Ord(ShowWindow(wnd,sw)));
   FlagToWnd(Result,wnd);
  end else
  if IsSameText(what,'MaximizeWindow') then begin
   wnd:=StrToWndDef(sWnd,0);
   sw:=StrToSW(sSw,SW_SHOWMAXIMIZED);
   ValidateWnd(wnd,sPid,sClass,sTitle);
   if (wnd<>0) and InRange(sw,SW_HIDE,SW_MAX)
   then Result:=IntToStr(Ord(ShowWindow(wnd,sw)));
   FlagToWnd(Result,wnd);
  end else
  if IsSameText(what,'RestoreWindow') then begin
   wnd:=StrToWndDef(sWnd,0);
   sw:=StrToSW(sSw,SW_RESTORE);
   ValidateWnd(wnd,sPid,sClass,sTitle);
   if (wnd<>0) and InRange(sw,SW_HIDE,SW_MAX)
   then Result:=IntToStr(Ord(ShowWindow(wnd,sw)));
   FlagToWnd(Result,wnd);
  end else
  Result:='';
 except
  on E:Exception do BugReport(E,Self,'Query');
 end;
end;

function wmctrl_query(const args:LongString):LongString;
begin
 Result:=wmctrl.Query(args);
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;

/////////////////////////////////
// TExtWinManHints implementation
/////////////////////////////////

class function TExtWinManHints._NET_SUPPORTED:LongString;              begin Result:='_NET_SUPPORTED';              end;
class function TExtWinManHints._NET_CLIENT_LIST:LongString;            begin Result:='_NET_CLIENT_LIST';            end;
class function TExtWinManHints._NET_NUMBER_OF_DESKTOPS:LongString;     begin Result:='_NET_NUMBER_OF_DESKTOPS';     end;
class function TExtWinManHints._NET_DESKTOP_GEOMETRY:LongString;       begin Result:='_NET_DESKTOP_GEOMETRY';       end;
class function TExtWinManHints._NET_DESKTOP_VIEWPORT:LongString;       begin Result:='_NET_DESKTOP_VIEWPORT';       end;
class function TExtWinManHints._NET_CURRENT_DESKTOP:LongString;        begin Result:='_NET_CURRENT_DESKTOP';        end;
class function TExtWinManHints._NET_DESKTOP_NAMES:LongString;          begin Result:='_NET_DESKTOP_NAMES';          end;
class function TExtWinManHints._NET_ACTIVE_WINDOW:LongString;          begin Result:='_NET_ACTIVE_WINDOW';          end;
class function TExtWinManHints._NET_WORKAREA:LongString;               begin Result:='_NET_WORKAREA';               end;
class function TExtWinManHints._NET_SUPPORTING_WM_CHECK:LongString;    begin Result:='_NET_SUPPORTING_WM_CHECK';    end;
class function TExtWinManHints._NET_VIRTUAL_ROOTS:LongString;          begin Result:='_NET_VIRTUAL_ROOTS';          end;
class function TExtWinManHints._NET_DESKTOP_LAYOUT:LongString;         begin Result:='_NET_DESKTOP_LAYOUT';         end;
class function TExtWinManHints._NET_SHOWING_DESKTOP:LongString;        begin Result:='_NET_SHOWING_DESKTOP';        end;
class function TExtWinManHints._NET_CLOSE_WINDOW:LongString;           begin Result:='_NET_CLOSE_WINDOW';           end;
class function TExtWinManHints._NET_MOVERESIZE_WINDOW:LongString;      begin Result:='_NET_MOVERESIZE_WINDOW';      end;
class function TExtWinManHints._NET_WM_MOVERESIZE:LongString;          begin Result:='_NET_WM_MOVERESIZE';          end;
class function TExtWinManHints._NET_RESTACK_WINDOW:LongString;         begin Result:='_NET_RESTACK_WINDOW';         end;
class function TExtWinManHints._NET_REQUEST_FRAME_EXTENTS:LongString;  begin Result:='_NET_REQUEST_FRAME_EXTENTS';  end;
class function TExtWinManHints._NET_WM_NAME:LongString;                begin Result:='_NET_WM_NAME';                end;
class function TExtWinManHints._NET_WM_VISIBLE_NAME:LongString;        begin Result:='_NET_WM_VISIBLE_NAME';        end;
class function TExtWinManHints._NET_WM_ICON_NAME:LongString;           begin Result:='_NET_WM_ICON_NAME';           end;
class function TExtWinManHints._NET_WM_VISIBLE_ICON_NAME:LongString;   begin Result:='_NET_WM_VISIBLE_ICON_NAME';   end;
class function TExtWinManHints._NET_WM_DESKTOP:LongString;             begin Result:='_NET_WM_DESKTOP';             end;
class function TExtWinManHints._NET_WM_WINDOW_TYPE:LongString;         begin Result:='_NET_WM_WINDOW_TYPE';         end;
class function TExtWinManHints._NET_WM_STATE:LongString;               begin Result:='_NET_WM_STATE';               end;
class function TExtWinManHints._NET_WM_ALLOWED_ACTIONS:LongString;     begin Result:='_NET_WM_ALLOWED_ACTIONS';     end;
class function TExtWinManHints._NET_WM_STRUT:LongString;               begin Result:='_NET_WM_STRUT';               end;
class function TExtWinManHints._NET_WM_STRUT_PARTIAL:LongString;       begin Result:='_NET_WM_STRUT_PARTIAL';       end;
class function TExtWinManHints._NET_WM_ICON_GEOMETRY:LongString;       begin Result:='_NET_WM_ICON_GEOMETRY';       end;
class function TExtWinManHints._NET_WM_ICON:LongString;                begin Result:='_NET_WM_ICON';                end;
class function TExtWinManHints._NET_WM_PID:LongString;                 begin Result:='_NET_WM_PID';                 end;
class function TExtWinManHints._NET_WM_HANDLED_ICONS:LongString;       begin Result:='_NET_WM_HANDLED_ICONS';       end;
class function TExtWinManHints._NET_WM_USER_TIME:LongString;           begin Result:='_NET_WM_USER_TIME';           end;
class function TExtWinManHints._NET_WM_USER_TIME_WINDOW:LongString;    begin Result:='_NET_WM_USER_TIME_WINDOW';    end;
class function TExtWinManHints._NET_FRAME_EXTENTS:LongString;          begin Result:='_NET_FRAME_EXTENTS';          end;
class function TExtWinManHints._NET_WM_OPAQUE_REGION:LongString;       begin Result:='_NET_WM_OPAQUE_REGION';       end;
class function TExtWinManHints._NET_WM_BYPASS_COMPOSITOR:LongString;   begin Result:='_NET_WM_BYPASS_COMPOSITOR';   end;
class function TExtWinManHints._NET_WM_PING:LongString;                begin Result:='_NET_WM_PING';                end;
class function TExtWinManHints._NET_WM_SYNC_REQUEST:LongString;        begin Result:='_NET_WM_SYNC_REQUEST';        end;
class function TExtWinManHints._NET_WM_FULLSCREEN_MONITORS:LongString; begin Result:='_NET_WM_FULLSCREEN_MONITORS'; end;
class function TExtWinManHints._NET_WM_FULL_PLACEMENT:LongString;      begin Result:='_NET_WM_FULL_PLACEMENT';      end;
class function TExtWinManHints._WIN_CLIENT_LIST:LongString;            begin Result:='_WIN_CLIENT_LIST';            end;
class function TExtWinManHints._WIN_WORKSPACE:LongString;              begin Result:='_WIN_WORKSPACE';              end;
class function TExtWinManHints.WM_CLIENT_MACHINE:LongString;           begin Result:='WM_CLIENT_MACHINE';           end;
class function TExtWinManHints.WM_NAME:LongString;                     begin Result:='WM_NAME';                     end;
class function TExtWinManHints.WM_CLASS:LongString;                    begin Result:='WM_CLASS';                    end;
/////////////////////////////////////////////////////////////////////////
// Extended Window Manager Hints version 1.5 29 November 2011
// See https://specifications.freedesktop.org/wm-spec/wm-spec-latest.html
/////////////////////////////////////////////////////////////////////////
class function TExtWinManHints.ListAllHints(const Delim:LongString=EOL):LongString;
begin
 Result:='_NET_SUPPORTED'+EOL
        +'_NET_CLIENT_LIST'+EOL
        +'_NET_NUMBER_OF_DESKTOPS'+EOL
        +'_NET_DESKTOP_GEOMETRY'+EOL
        +'_NET_DESKTOP_VIEWPORT'+EOL
        +'_NET_CURRENT_DESKTOP'+EOL
        +'_NET_DESKTOP_NAMES'+EOL
        +'_NET_ACTIVE_WINDOW'+EOL
        +'_NET_WORKAREA'+EOL
        +'_NET_SUPPORTING_WM_CHECK'+EOL
        +'_NET_VIRTUAL_ROOTS'+EOL
        +'_NET_DESKTOP_LAYOUT'+EOL
        +'_NET_SHOWING_DESKTOP'+EOL
        +'_NET_CLOSE_WINDOW'+EOL
        +'_NET_MOVERESIZE_WINDOW'+EOL
        +'_NET_WM_MOVERESIZE'+EOL
        +'_NET_RESTACK_WINDOW'+EOL
        +'_NET_REQUEST_FRAME_EXTENTS'+EOL
        +'_NET_WM_NAME'+EOL
        +'_NET_WM_VISIBLE_NAME'+EOL
        +'_NET_WM_ICON_NAME'+EOL
        +'_NET_WM_VISIBLE_ICON_NAME'+EOL
        +'_NET_WM_DESKTOP'+EOL
        +'_NET_WM_WINDOW_TYPE'+EOL
        +'_NET_WM_STATE'+EOL
        +'_NET_WM_ALLOWED_ACTIONS'+EOL
        +'_NET_WM_STRUT'+EOL
        +'_NET_WM_STRUT_PARTIAL'+EOL
        +'_NET_WM_ICON_GEOMETRY'+EOL
        +'_NET_WM_ICON'+EOL
        +'_NET_WM_PID'+EOL
        +'_NET_WM_HANDLED_ICONS'+EOL
        +'_NET_WM_USER_TIME'+EOL
        +'_NET_WM_USER_TIME_WINDOW'+EOL
        +'_NET_FRAME_EXTENTS'+EOL
        +'_NET_WM_OPAQUE_REGION'+EOL
        +'_NET_WM_BYPASS_COMPOSITOR'+EOL
        +'_NET_WM_PING'+EOL
        +'_NET_WM_SYNC_REQUEST'+EOL
        +'_NET_WM_FULLSCREEN_MONITORS'+EOL
        +'_NET_WM_FULL_PLACEMENT'+EOL
        +'_WIN_CLIENT_LIST'+EOL
        +'_WIN_WORKSPACE'+EOL
        +'WM_CLIENT_MACHINE'+EOL
        +'WM_NAME'+EOL
        +'WM_CLASS'+EOL;
 if (Delim<>EOL) then Result:=StringReplace(Trim(Result),EOL,Delim,[rfReplaceAll]);
end;

class function TExtWinManHints.ListAllWindowTypes(const Delim:LongString=','):LongString;
begin
 Result:=wm_all_window_types;
 if (Delim<>',') then Result:=StringReplace(Result,',',Delim,[rfReplaceAll]);
end;

class function TExtWinManHints.ListAllWindowStates(const Delim:LongString=','):LongString;
begin
 Result:=wm_all_window_states;
 if (Delim<>',') then Result:=StringReplace(Result,',',Delim,[rfReplaceAll]);
end;

/////////////////////////
// wmhints implementation
/////////////////////////

const
 the_wmhints:TExtWinManHints=nil;

function wmhints:TExtWinManHints;
begin
 if not Assigned(the_wmhints) then begin
  the_wmhints:=TExtWinManHints.Create;
  the_wmhints.Master:=@the_wmhints;
 end;
 Result:=the_wmhints;
end;

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

procedure Init_crw_wmctrl;
begin
 InitXCounters;
 TWinManControl.WM_GRAVITY_DEFAULT:=GDK_GRAVITY_DEFAULT;
 wmctrl.Ok;
 wmhints.Ok;
 {$IFDEF UNIX}
 wmctrl.SetErrorHandler(wmctrl_def_x_trap);
 wmctrl.SetIOErrorHandler(wmctrl_def_x_io_trap);
 {$ENDIF ~UNIX}
 TheListDesktopManagers:='';
 TheListTerminals:='';
 wmctrl.DefListMode:=0;
 wmctrl.DefWndFmt:=2;
end;

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

initialization

 Init_crw_wmctrl;

finalization

 Free_crw_wmctrl;

end.

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

