constructor TFPWMClient.Create(wm: TFPWMWindowManager; w: TWindow);
var
  attr: TXWindowAttributes;
begin
  {$ifdef FPWM_TRACE}
  FPWMTrace('[TFPWMClient.Create] BEGIN');
  {$endif}
  m_window := w;
  m_transient := None;
  m_state := WithdrawnState;
  m_colormap := None;
  m_windowManager := wm;
  XGetWindowAttributes(display(), m_window, @attr);
  m_x := attr.x;
  m_y := attr.y;
  m_w := attr.width;
  m_h := attr.height;
  m_bw := attr.border_width;
  m_name := '';
  m_iconName := '';
  m_sizeHints.flags := 0;
  m_label := m_defaultLabel;
  m_border := TFPWMBorder.Create(Self, w);
  if (attr.map_state = IsViewable) then manage(True);
  {$ifdef FPWM_TRACE}
  FPWMTrace('[TFPWMClient.Create] END');
  {$endif}
end;
destructor TFPWMClient.Destroy();
begin
    {$ifdef FPWM_TRACE}
    FPWMTrace('[TFPWMClient.Destroy]');
    {$endif}
end;    
procedure TFPWMClient.release();
begin
    {$ifdef FPWM_TRACE}
    FPWMTrace('[TFPWMClient.release]');
    {$endif}
    if (m_window = None) then
    begin 
	Writeln('wm2: invalid parent in TFPWMClient.release (released twice?)');
    end;
    windowManager().skipInRevert(Self, m_revert);
    if isHidden() then unhide(False);
    m_border.Free;
    m_window := None;
    if isActive() then
    begin
	if CONFIG_CLICK_TO_FOCUS then
	begin
	    if m_revert <> nil then
	    begin
		windowManager().setActiveClient(m_revert);
		m_revert.activate();
	    end else 
		windowManager().setActiveClient(nil);
	end else 
	    windowManager().setActiveClient(nil);
    end;
    if (m_colormapWinCount > 0) then
    begin
	XFree(PChar(m_colormapWindows));
    end;
end;
procedure TFPWMClient.unreparent();
var
    wc: TXWindowChanges;
begin
    {$ifdef FPWM_TRACE}
    FPWMTrace('[TFPWMClient.unreparent]');
    {$endif}
  if not isWithdrawn() then
  begin
    gravitate(True);
    XReparentWindow(display(), m_window, root(), m_x, m_y);
  end;
  wc.border_width := m_bw;
  XConfigureWindow(display(), m_window, CWBorderWidth, @wc);
  XSync(display(), True);
end;
procedure TFPWMClient.installColormap();
var
  i, found: cint;
  cc: TFPWMClient = nil;
begin
  {$ifdef FPWM_TRACE}
  FPWMTrace('[TFPWMClient.installColormap]');
  {$endif}
  if (m_colormapWinCount <> 0) then
  begin
    found := 0;
    for i := m_colormapWinCount - 1 downto 0 do
    begin
      windowManager().installColormap(m_windowColormaps[i]);
      if (m_colormapWindows[i] = m_window) then Inc(found);
    end;
    if (found = 0) then windowManager().installColormap(m_colormap);
    Exit;
  end;
  cc := windowManager().windowToClient(m_transient);
  if (m_transient <> None) and (cc <> nil) then
    cc.installColormap()
  else
    windowManager().installColormap(m_colormap);
end;
procedure TFPWMClient.manage(mapped: Boolean);
var
    shouldHide, reshape: Boolean;
    msize: clong;
    state, dw, dh: cint;
    hints: PXWMHints = nil;
    d: PDisplay;
begin
    {$ifdef FPWM_TRACE}
    FPWMTrace('[TFPWMClient.manage] BEGIN');
    {$endif}
    d := display();
    XSelectInput(d, m_window, ColormapChangeMask or EnterWindowMask or
		 PropertyChangeMask or FocusChangeMask);
    m_iconName := getProperty(XA_WM_ICON_NAME);
    m_name := getProperty(XA_WM_NAME);
    setLabel();
    getColormaps();
    getProtocols();
    getTransient();
    hints := XGetWMHints(d, m_window);
    if not getState(state) then
    begin
	if(hints<>nil) then state := hints^.initial_state
        else state := NormalState;
    end;
    shouldHide := (state = IconicState);
    if (hints<>nil) then XFree(hints);
    if (XGetWMNormalHints(d, m_window, @m_sizeHints, @mSize) = 0) or
	(m_sizeHints.flags = 0) then
    begin
       m_sizeHints.flags := xutil.PSize;
    end;
    m_fixedSize := False;
    if ((m_sizeHints.flags and (PMinSize or PMaxSize)) = (PMinSize or PMaxSize)) and
      (m_sizeHints.min_width  = m_sizeHints.max_width) and
      (m_sizeHints.min_height = m_sizeHints.max_height) then m_fixedSize := True;
    reshape := not(mapped);
    if m_fixedSize then
    begin
        if (m_sizeHints.flags and USPosition) <> 0 then reshape := False;
	if ((m_sizeHints.flags and PPosition) <> 0) and shouldHide then reshape := False;
	if (m_transient <> None) then reshape := False;
    end;
    if (m_sizeHints.flags and PBaseSize) <> 0 then
    begin
	m_minWidth  := m_sizeHints.base_width;
	m_minHeight := m_sizeHints.base_height;
    end else if (m_sizeHints.flags and PMinSize) <> 0 then
    begin
	m_minWidth  := m_sizeHints.min_width;
	m_minHeight := m_sizeHints.min_height;
    end else 
    begin
	m_minWidth := 50;
	m_minHeight := 50;
    end;
    gravitate(False);
   dw := DisplayWidth(display(), 0);
   dh := DisplayHeight(display(), 0);
    if (m_w < m_minWidth) then begin
	m_w := m_minWidth; m_fixedSize := False; reshape := True;
    end;
    if (m_h < m_minHeight) then begin
	m_h := m_minHeight; m_fixedSize := False; reshape := True;
    end;
    if (m_w > dw - 8) then m_w := dw - 8;
    if (m_h > dh - 8) then m_h := dh - 8;
    if (m_x > dw - m_border.xIndent()) then m_x := dw - m_border.xIndent;
    if (m_y > dh - m_border.yIndent()) then m_y := dh - m_border.yIndent;
    if (m_x < m_border.xIndent()) then m_x := m_border.xIndent();
    if (m_y < m_border.yIndent()) then m_y := m_border.yIndent();
    m_border.configure(m_x, m_y, m_w, m_h, 0, Above);
    if mapped then m_reparenting := True;
    if (reshape) and (not m_fixedSize) then XResizeWindow(d, m_window, m_w, m_h);
    XSetWindowBorderWidth(d, m_window, 0);
    m_border.reparent();
    XAddToSaveSet(d, m_window);
    m_managed := True;
    if (shouldHide) then hide()
    else
    begin
	XMapWindow(d, m_window);
	m_border.map();
	setState(NormalState);
	if (CONFIG_CLICK_TO_FOCUS) or
	    ((m_transient <> None) and (activeClient <> nil) and
	    (activeClient().m_window = m_transient)) then
	begin
	    activate();
	    mapRaised();
	end else
	begin
	    deactivate();
	end;
    end;
    if (activeClient()<>nil) and (not isActive) then
    begin
	activeClient().installColormap();
    end;
    if (CONFIG_AUTO_RAISE) then
    begin
	m_windowManager.stopConsideringFocus();
	focusIfAppropriate(False);
    end;
    {$ifdef FPWM_TRACE}
    FPWMTrace('[TFPWMClient.manage] END');
    {$endif}
end;
procedure TFPWMClient.selectOnMotion(w: TWindow; select: Boolean);
var
  tmp: Integer;
begin
    {$ifdef FPWM_TRACE}
    FPWMTrace('[TFPWMClient.selectOnMotion]');
    {$endif}
    if not CONFIG_AUTO_RAISE then Exit();
    if (w=0) or (w = root()) then Exit();
    if (w = m_window) or (m_border.hasWindow(w)) then
    begin 
      if (select) then tmp := PointerMotionMask
      else tmp := 0;
      XSelectInput(display(), m_window, 
        ColormapChangeMask or EnterWindowMask or
        PropertyChangeMask or FocusChangeMask or tmp);
      XSelectInput(display(), w, tmp);
    end;
end;
procedure TFPWMClient.decorate(active: Boolean);
begin
    {$ifdef FPWM_TRACE}
    FPWMTrace('[TFPWMClient.decorate]');
    {$endif}
    m_border.decorate(active, m_w, m_h);
end;
procedure TFPWMClient.activate();
begin
    {$ifdef FPWM_TRACE}
    FPWMTrace('[TFPWMClient.activate]');
    {$endif}
    if (parent() = root()) then
    begin
	Writeln('wm2: warning: bad parent in TFPWMClient.activate');
	Exit();
    end;
    if (not m_managed) or (isHidden()) or (isWithdrawn()) then Exit();
    if isActive() then
    begin
	decorate(True);
	if CONFIG_AUTO_RAISE or CONFIG_RAISE_ON_FOCUS then mapRaised();
	Exit();
    end;
    if activeClient() <> nil then
    begin
	activeClient().deactivate();
    end;
    XUngrabButton(display(), AnyButton, AnyModifier, parent());
    XSetInputFocus(display(), m_window, RevertToPointerRoot,
		   windowManager().timestamp(False));
    if (m_protocol and PtakeFocus) <>0 then
    begin
	sendMessage(Atoms.wm_protocols, Atoms.wm_takeFocus);
    end;
    windowManager().skipInRevert(Self, m_revert);
    m_revert := activeClient();
    while (m_revert<>nil) and (not m_revert.isNormal()) do m_revert := m_revert.revertTo();
    windowManager().setActiveClient(Self);
    decorate(True);
    installColormap();		
end;
procedure TFPWMClient.deactivate();	
begin
    {$ifdef FPWM_TRACE}
    FPWMTrace('[TFPWMClient.deactivate]');
    {$endif}
    if (parent() = root()) then
    begin
	Writeln('fpwm: warning: bad parent in TFPWMClient.deactivate');
	Exit();
    end;
    XGrabButton(display(), AnyButton, AnyModifier, parent(), 0,
		ButtonPressMask or ButtonReleaseMask,
		GrabModeAsync, GrabModeSync, None, None);
    decorate(False);
end;
procedure TFPWMClient.sendMessage(a: TAtom; l: clong);
var
  ev: TXEvent;
  status: Integer;
  mask: clong;
begin
    {$ifdef FPWM_TRACE}
    FPWMTrace('[TFPWMClient.sendMessage]');
    {$endif}
    Fillchar(ev, 0, sizeof(ev));
    ev.xclient._type := ClientMessage;
    ev.xclient.window := m_window;
    ev.xclient.message_type := a;
    ev.xclient.format := 32;
    ev.xclient.data.l[0] := l;
    ev.xclient.data.l[1] := windowManager().timestamp(False);
    mask := 0;
    status := XSendEvent(display(), m_window, False, mask, @ev);
    if (status = 0) then
    begin
	Writeln('fpwm: warning: TFPWMClient.sendMessage failed');
    end;
end;
 function getProperty_aux(d: PDisplay; w: TWindow; a, type_: TAtom; len: clong;
   p: PPByte): cint;
var
  realType: TAtom;
  format_: integer;
  n, extra :integer;
  status: integer;
begin
  {$ifdef FPWM_TRACE}
  FPWMTrace(Format('[getProperty_aux] w=%d p=%d', [w, PtrInt(p)]));
  {$endif}
  status := XGetWindowProperty(d, w, a, 0, len, False, type_, @realType, @format_, @n, @extra, p);
  if (status <> Success) or (p^ = nil) then Exit(-1);
  if (n = 0) then XFree(p^);
  Exit(n);
end;
function TFPWMClient.getProperty(a: TAtom): string;
var
  p: PByte;
begin
  {$ifdef FPWM_TRACE}
  FPWMTrace('[TFPWMClient.getProperty]');
  {$endif}
  if (getProperty_aux(display(), m_window, a, XA_STRING, 100, @p) <= 0) then Exit('');
  Result := PChar(p);
end;
function TFPWMClient.getAtomProperty(a, type_: TAtom): cint;
var
  p: PPChar;
  x: PChar;
begin
  {$ifdef FPWM_TRACE}
  FPWMTrace('[TFPWMClient.getAtomProperty]');
  {$endif}
  if (getProperty_aux(display(), m_window, a, type_, 1,
    PPByte(@p)) <= 0) then
  begin
    Exit(0);
  end;
  x := p^;
  XFree(Pointer(p));
  Result := cint(x);
end;
function TFPWMClient.getIntegerProperty(a: TAtom): cint;
begin
  {$ifdef FPWM_TRACE}
  FPWMTrace('[TFPWMClient.getIntegerProperty]');
  {$endif}
  Result := getAtomProperty(a, XA_INTEGER);
end;
procedure TFPWMClient.setState(state: cint);
var
  data: array[0..1] of Integer;
begin
  {$ifdef FPWM_TRACE}
  FPWMTrace('[TFPWMClient.setState]');
  {$endif}
  data[0] := state;
  data[1] := None;
  m_state := state;
  XChangeProperty(display(), m_window, Atoms.wm_state, Atoms.wm_state,
    32, PropModeReplace, PByte(@data[0]), 2);
end;
function TFPWMClient.getState(var state: cint): Boolean;
var
  p: pculong = nil;
begin
  {$ifdef FPWM_TRACE}
  FPWMTrace('[TFPWMClient.getState]');
  {$endif}
  if (getProperty_aux(display(), m_window, Atoms.wm_state, Atoms.wm_state,
	2, PPByte(@p)) <= 0) then
	Exit(False);
  state := cint(p^);
  XFree(PChar(p));
  Result := True;
end;
procedure TFPWMClient.getProtocols();
var
  n, i: clong;
  p: PAtom;
begin
    {$ifdef FPWM_TRACE}
    FPWMTrace('[TFPWMClient.getProtocols]');
    {$endif}
    m_protocol := 0;
    n := getProperty_aux(display(), m_window, Atoms.wm_protocols, XA_ATOM,
			     20, PPByte(@p));
    if (n <= 0) then Exit;
    for i := 0 to n - 1 do
    begin
	if (p[i] = Atoms.wm_delete) then
	    m_protocol := m_protocol or Pdelete
	else if (p[i] = Atoms.wm_takeFocus) then
	    m_protocol := m_protocol or PtakeFocus;
    end;
    XFree(PChar(p));
end;
procedure TFPWMClient.gravitate(invert: Boolean);
var
    gravity: cint;
    w: cint = 0;
    h: cint = 0;
    xdelta, ydelta: cint;
begin
    {$ifdef FPWM_TRACE}
    FPWMTrace('[TFPWMClient.gravitate]');
    {$endif}
    gravity := NorthWestGravity;
    if (m_sizeHints.flags and PWinGravity) <> 0 then
      gravity := m_sizeHints.win_gravity;
    xdelta := m_bw - m_border.xIndent();
    ydelta := m_bw - m_border.yIndent();
    case gravity of
    NorthWestGravity:
    begin
    end;
    NorthGravity:
	w := xdelta;
    NorthEastGravity:
	w := xdelta + m_bw-1;
    WestGravity:
	h := ydelta;
    CenterGravity,
     StaticGravity:
    begin
        w := xdelta;
	h := ydelta;
    end;
    EastGravity:
    begin
	w := xdelta + m_bw-1;
	h := ydelta;
    end;
    SouthWestGravity:
	h := ydelta + m_bw-1;
    SouthGravity:
    begin
	w := xdelta;
	h := ydelta + m_bw-1;
    end;
    SouthEastGravity:
    begin
	w := xdelta + m_bw-1;
	h := ydelta + m_bw-1;
    end;
    else
	Exit;
    end;
    w := w + m_border.xIndent();
    h := h + m_border.yIndent();
    if (invert) then
    begin
      w := -w;
      h := -h;
    end;
    m_x := m_x + w;
    m_y := m_y + h;
end;
function TFPWMClient.setLabel(): Boolean;
var
  newLabel: string;
begin
    {$ifdef FPWM_TRACE}
    FPWMTrace('[TFPWMClient.setLabel]');
    {$endif}
    if (m_name <> '') then newLabel := m_name
    else if (m_iconName <> '') then newLabel := m_iconName
    else newLabel := m_defaultLabel;
    if (m_label = '') then
    begin
	m_label := newLabel;
	Exit(True);
    end else if m_label = newLabel then 
    begin
	m_label := newLabel;
	Exit(True);
    end else Result := True;
end;
procedure TFPWMClient.getColormaps();
var
    i, n: cint;
    cw: PWindow;
    attr: TXWindowAttributes;
begin
    {$ifdef FPWM_TRACE}
    FPWMTrace('[TFPWMClient.getColormaps]');
    {$endif}
    if (not m_managed) then
    begin
	XGetWindowAttributes(display(), m_window, @attr);
	m_colormap := attr.colormap;
    end;
    n := getProperty_aux(display(), m_window, Atoms.wm_colormaps, XA_WINDOW,
	100, PPByte(@cw));
    if (m_colormapWinCount <> 0) then
    begin
	XFree(PChar(m_colormapWindows));
	FreeMem(m_windowColormaps);
    end;
    if (n <= 0) then
    begin
	m_colormapWinCount := 0;
	Exit;
    end;
    m_colormapWinCount := n;
    m_colormapWindows := cw;
    m_windowColormaps := PColormap(GetMem(n * sizeof(TColormap)));
    for i := 0 to n - 1 do
    begin
	if (cw[i] = m_window) then
	    m_windowColormaps[i] := m_colormap
	else
        begin
	    XSelectInput(display(), cw[i], ColormapChangeMask);
	    XGetWindowAttributes(display(), cw[i], @attr);
	    m_windowColormaps[i] := attr.colormap;
        end;
    end;
end;
procedure TFPWMClient.getTransient();
var
  t: TWindow = None;
begin
    {$ifdef FPWM_TRACE}
    FPWMTrace('[TFPWMClient.getTransient]');
    {$endif}
    if (XGetTransientForHint(display(), m_window, @t) <> 0) then
    begin
	if (windowManager().windowToClient(t) = Self) then
        begin
	    m_transient := None;
	end
        else
	    m_transient := t;
    end
    else
	m_transient := None;
end;
procedure TFPWMClient.hide();
begin
    {$ifdef FPWM_TRACE}
    FPWMTrace('[TFPWMClient.hide]');
    {$endif}
    if (isHidden()) then
    begin
	Exit;
    end;
    m_border.unmap();
    XUnmapWindow(display(), m_window);
    if (isActive()) then windowManager().clearFocus();
    setState(IconicState);
    windowManager().addToHiddenList(Self);
end;
procedure TFPWMClient.unhide(map: Boolean);
begin
    {$ifdef FPWM_TRACE}
    FPWMTrace('[TFPWMClient.unhide]');
    {$endif}
    if (not isHidden()) then
    begin
	Exit;
    end;
    windowManager().removeFromHiddenList(Self);
    if (map) then
    begin
	setState(NormalState);
	XMapWindow(display(), m_window);
	mapRaised();
	if (CONFIG_AUTO_RAISE) then focusIfAppropriate(False)
	else if (CONFIG_CLICK_TO_FOCUS) then activate();
    end;
end;
procedure TFPWMClient.sendConfigureNotify();
var
  ce: TXConfigureEvent;
begin
    {$ifdef FPWM_TRACE}
    FPWMTrace('[TFPWMClient.sendConfigureNotify]');
    {$endif}
    ce._type  := ConfigureNotify;
    ce.event  := m_window;
    ce.window := m_window;
    ce.x := m_x;
    ce.y := m_y;
    ce.width  := m_w;
    ce.height := m_h;
    ce.border_width := m_bw;
    ce.above := None;
    ce.override_redirect := 0;
    XSendEvent(display(), m_window, False, StructureNotifyMask, PXEvent(@ce));
end;
procedure TFPWMClient.withdraw(changeState: Boolean);
begin
    {$ifdef FPWM_TRACE}
    FPWMTrace('[TFPWMClient.withdraw]');
    {$endif}
    m_border.unmap();
    gravitate(True);
    XReparentWindow(display(), m_window, root(), m_x, m_y);
    gravitate(False);
    if (changeState) then
    begin
	XRemoveFromSaveSet(display(), m_window);
	setState(WithdrawnState);
    end;
    ignoreBadWindowErrors := True;
    XSync(display(), False);
    ignoreBadWindowErrors := False;
end;
procedure TFPWMClient.rename();
begin
    {$ifdef FPWM_TRACE}
    FPWMTrace('[TFPWMClient.rename]');
    {$endif}
    m_border.configure(0, 0, m_w, m_h, CWWidth or CWHeight, Above);
end;
procedure TFPWMClient.mapRaised();
begin
    {$ifdef FPWM_TRACE}
    FPWMTrace('[TFPWMClient.mapRaised]');
    {$endif}
    m_border.mapRaised();
    windowManager().raiseTransients(Self);
end;
procedure TFPWMClient.kill();
begin
    {$ifdef FPWM_TRACE}
    FPWMTrace('[TFPWMClient.kill]');
    {$endif}
    if (m_protocol and Pdelete) <> 0 then
	sendMessage(Atoms.wm_protocols, Atoms.wm_delete)
    else
	XKillClient(display(), m_window);
end;
procedure TFPWMClient.ensureVisible();
var
  mx, my, px, py: cint;
begin
    {$ifdef FPWM_TRACE}
    FPWMTrace('[TFPWMClient.ensureVisible]');
    {$endif}
    mx := DisplayWidth(display(), 0) - 1; 
    my := DisplayHeight(display(), 0) - 1;
    px := m_x;
    py := m_y;
    if (m_x + m_w > mx) then m_x := mx - m_w;
    if (m_y + m_h > my) then m_y := my - m_h;
    if (m_x < 0) then m_x := 0;
    if (m_y < 0) then m_y := 0;
    if (m_x <> px) or (m_y <> py) then m_border.moveTo(m_x, m_y);
end;
procedure TFPWMClient.lower();
begin
    {$ifdef FPWM_TRACE}
    FPWMTrace('[TFPWMClient.lower]');
    {$endif}
    m_border.lower();
end;