{%MainUnit fpwmmanager.pas}
{#include "Manager.h"
#include "Client.h"
#include <string.h>
#include <X11/Xproto.h>
#include <sys/types.h>
#include <sys/wait.h>
#include "Cursors.h"

int     WindowManager::m_signalled = False;
Boolean WindowManager::m_initialising = False;}
var
  ignoreBadWindowErrors: Boolean;

{const char *const WindowManager::m_menuCreateLabel = "New";

implementPList(ClientList, Client);}


constructor TFPWMWindowManager.Create();
begin
  {$ifdef FPWM_TRACE}
  FPWMTrace('[TFPWMWindowManager.Create]');
  {$endif}
  inherited Create;
  //
  m_clients := TClientList.Create;
  m_hiddenClients := TClientList.Create;
end;


destructor TFPWMWindowManager.Destroy;
begin
  {$ifdef FPWM_TRACE}
  FPWMTrace('[TFPWMWindowManager.Destroy]');
  {$endif}
  // empty
  inherited Destroy;
end;

procedure TFPWMWindowManager.InitWM();
var
  dummy: cint;
begin
  {$ifdef FPWM_TRACE}
  FPWMTrace('[TFPWMWindowManager.InitWM] BEGIN');
  {$endif}
//    m_menuGC(0), m_menuWindow(0), m_menuFont(0), m_focusChanging(False)

  // Program description
  WriteLn(
      '  Welcome to the Free Pascal Window Manager!' + LineEnding
    + '' + LineEnding
    + '  Copyright (c) 2006-2010 Felipe Monteiro de Carvalho, Daniel Franzini, Andrew Haines' + LineEnding
    + '  Copyright (c) 2010 Renata Yuki, Vitor Julio, Alessandro Palmeira' + LineEnding
    + '  Parts derived from wm2 Copyright (c) 1996-97 Chris Cannam' + LineEnding
    + '  Copying and redistribution encouraged.  ' + LineEnding
    + '  No warranty' + LineEnding
    + ''
    );

  // Program configuration description

  if (CONFIG_AUTO_RAISE) then
  begin
    if (CONFIG_CLICK_TO_FOCUS) then
      fatal('can''t have auto-raise-with-delay with click-to-focus')
    else if (CONFIG_RAISE_ON_FOCUS) then
      fatal('can''t have raise-on-focus AND auto-raise-with-delay')
    else
      WriteLn('     Focus follows, auto-raise with delay.  ');
  end
  else
  begin
    if (CONFIG_CLICK_TO_FOCUS) then
    begin
      if (CONFIG_RAISE_ON_FOCUS) then
	WriteLn('     Click to focus.  ')
      else
	fatal('can''t have click-to-focus without raise-on-focus');
    end
    else
    begin
      if (CONFIG_RAISE_ON_FOCUS) then
	WriteLn('     Focus follows, auto-raise.  ')
      else
	WriteLn('     Focus follows pointer.  ');
    end;
  end;

  if (CONFIG_EVERYTHING_ON_ROOT_MENU) then
    WriteLn('All clients on menu.')
  else WriteLn('Hidden clients only on menu.');

  if (CONFIG_PROD_SHAPE) then WriteLn('     Shape prodding on.  ')
  else WriteLn('     Shape prodding off.  ');

  if (CONFIG_TABLET_MODE) then WriteLn('  FPWM configured in TABLET mode.')
  else WriteLn('  FPWM configured in DESKTOP mode.');

  WriteLn('');
  WriteLn('     (To reconfigure, simply edit and recompile.)');
  WriteLn('');
  WriteLn('');

  m_display := XOpenDisplay(nil);
  if (m_display = nil) then fatal('can''t open display');

  m_shell := PChar(GetEnvironmentVariable('SHELL'));
  if (m_shell = nil) then m_shell := '/bin/sh';

  m_initialising := True;
  XSetErrorHandler(@errorHandler);
  ignoreBadWindowErrors := False;

  // 9wm does more, I think for nohup
  //c/signal(SIGTERM, sigHandler);
  //c/signal(SIGINT,  sigHandler);
  //c/signal(SIGHUP,  sigHandler);

  m_currentTime := -1;
  m_activeClient := nil;

  Atoms.wm_state      := XInternAtom(m_display, 'WM_STATE',            False);
  Atoms.wm_changeState:= XInternAtom(m_display, 'WM_CHANGE_STATE',     False);
  Atoms.wm_protocols  := XInternAtom(m_display, 'WM_PROTOCOLS',        False);
  Atoms.wm_delete     := XInternAtom(m_display, 'WM_DELETE_WINDOW',    False);
  Atoms.wm_takeFocus  := XInternAtom(m_display, 'WM_TAKE_FOCUS',       False);
  Atoms.wm_colormaps  := XInternAtom(m_display, 'WM_COLORMAP_WINDOWS', False);
  Atoms.fpwm_running   := XInternAtom(m_display, '_WM2_RUNNING',        False);

  if (XShapeQueryExtension(m_display, @m_shapeEvent, @dummy) = False) then
    fatal('no shape extension, can''t run without it');

  // we only cope with one screen!
  initialiseScreen();

  XSetSelectionOwner(m_display, Atoms.fpwm_running,
    m_menuWindow, timestamp(True));
  XSync(m_display, False);
  m_initialising := False;
  m_returnCode := 0;

  clearFocus();
  scanInitialWindows();

  {$ifdef FPWM_TRACE}
  FPWMTrace('[TFPWMWindowManager.InitWM] END');
  {$endif}
end;

procedure TFPWMWindowManager.release();
var
  normalList, unparentList: TClientList;
  c: TFPWMClient;
  i: cint;
begin
  {$ifdef FPWM_TRACE}
  FPWMTrace('[TFPWMWindowManager.release]');
  {$endif}

  normalList := TClientList.Create;
  unparentList := TClientList.Create;
  try
    if (m_returnCode <> 0) then Exit; // hasty exit

    for i := 0 to  m_clients.count - 1 do
    begin
        c := m_clients.items[i];
  //c/    fprintf(stderr, "client %d is %p\n", i, c);

        if (c.isNormal()) then normalList.add(c)
        else unparentList.add(c);
    end;

    for i := normalList.count-1 downto 0 do
    begin
        unparentList.Add(normalList.items[i]);
    end;

    m_clients.clear();

    for i := 0 to unparentList.count - 1 do
    begin
  //c/	fprintf(stderr, "unparenting client %p\n",unparentList.item(i));
        unparentList.items[i].unreparent();
        unparentList.items[i].release();
        unparentList.items[i] := nil;
    end;

    XSetInputFocus(m_display, PointerRoot, RevertToPointerRoot,
		   timestamp(False));
    installColormap(None);

    XFreeCursor(m_display, m_cursor);
    XFreeCursor(m_display, m_xCursor);
    XFreeCursor(m_display, m_vCursor);
    XFreeCursor(m_display, m_hCursor);
    XFreeCursor(m_display, m_vhCursor);

    XFreeFont(m_display, m_menuFont);
    XFreeGC(m_display, m_menuGC);

    XCloseDisplay(m_display);
  finally
    normalList.Free;
    unparentList.Free;
  end;
end;


procedure TFPWMWindowManager.fatal(const message: string);
begin
  {$ifdef FPWM_TRACE}
  FPWMTrace('[TFPWMWindowManager.fatal]');
  {$endif}
  WriteLn('fpwm fatal error: ' + message);
  Halt(1);
end;

class function TFPWMWindowManager.makeCursor(d: PDisplay; w: TWindow;
      bits, mask_bits: PByte; width, height, xhot, yhot: cint;
      var fg, bg: TXColor): TCursor;
var
  pixmap, mask: TPixmap;
  cursor: TCursor;
begin
  {$ifdef FPWM_TRACE}
  FPWMTrace('[TFPWMWindowManager.makeCursor]');
  {$endif}
  pixmap := XCreateBitmapFromData(d, w, PChar(bits), width, height);

  mask := XCreateBitmapFromData(d, w, PChar(mask_bits), width, height);

  cursor := XCreatePixmapCursor(d, pixmap, mask, @fg, @bg, xhot, yhot);
  XFreePixmap(d, pixmap);
  XFreePixmap(d, mask);

  Result := cursor;
end;


procedure TFPWMWindowManager.initialiseScreen();
var
  values: TXGCValues;
  i: Integer = 0;
  black, white, temp: TXColor;
  attr: TXSetWindowAttributes;
begin
  {$ifdef FPWM_TRACE}
  FPWMTrace('[TFPWMWindowManager.initialiseScreen]');
  {$endif}

  m_screenNumber := i;

  m_root := RootWindow(m_display, i);
  m_defaultColormap := DefaultColormap(m_display, i);
  m_minimumColormaps := MinCmapsOfScreen(ScreenOfDisplay(m_display, i));

  if (XAllocNamedColor(m_display, m_defaultColormap, 'black', @black, @temp) = 0) then
      fatal('couldn''t load colour "black"!');
  if (XAllocNamedColor(m_display, m_defaultColormap, 'white', @white, @temp)= 0) then
      fatal('couldn''t load colour "white"!');

  m_cursor := makeCursor
      (m_display, m_root, cursor_bits, cursor_mask_bits,
       cursor_width, cursor_height, cursor_x_hot,
       cursor_y_hot, black, white);

  m_xCursor := makeCursor
      (m_display, m_root, ninja_cross_bits, ninja_cross_mask_bits,
       ninja_cross_width, ninja_cross_height, ninja_cross_x_hot,
       ninja_cross_y_hot, black, white);

  m_hCursor := makeCursor
      (m_display, m_root, cursor_right_bits, cursor_right_mask_bits,
       cursor_right_width, cursor_right_height, cursor_right_x_hot,
       cursor_right_y_hot, black, white);

  m_vCursor := makeCursor
      (m_display, m_root, cursor_down_bits, cursor_down_mask_bits,
       cursor_down_width, cursor_down_height, cursor_down_x_hot,
       cursor_down_y_hot, black, white);

  m_vhCursor := makeCursor
      (m_display, m_root, cursor_down_right_bits, cursor_down_right_mask_bits,
       cursor_down_right_width, cursor_down_right_height,
       cursor_down_right_x_hot, cursor_down_right_y_hot, black, white);

  attr.cursor := m_cursor;
  attr.event_mask := SubstructureRedirectMask or SubstructureNotifyMask or
      ColormapChangeMask or ButtonPressMask or ButtonReleaseMask or
      PropertyChangeMask;
  XChangeWindowAttributes(m_display, m_root, CWCursor or CWEventMask, @attr);
  XSync(m_display, False);

  m_menuForegroundPixel :=
      allocateColour(CONFIG_MENU_FOREGROUND, 'menu foreground');
  m_menuBackgroundPixel :=
      allocateColour(CONFIG_MENU_BACKGROUND, 'menu background');
  m_menuBorderPixel :=
      allocateColour(CONFIG_MENU_BORDERS, 'menu border');

  m_menuWindow := XCreateSimpleWindow
      (m_display, m_root, 0, 0, 1, 1, 1,
       m_menuBorderPixel, m_menuBackgroundPixel);

  if (DoesSaveUnders(ScreenOfDisplay(m_display, m_screenNumber))) <> 0 then
  begin
      attr.save_under := -1; //True;
      XChangeWindowAttributes(m_display, m_menuWindow, CWSaveUnder, @attr);
  end;

  values.background := m_menuBackgroundPixel;
  values.foreground := m_menuForegroundPixel xor m_menuBackgroundPixel;
  values._function := GXxor;
  values.line_width := 0;
  values.subwindow_mode := IncludeInferiors;

  m_menuFont := XLoadQueryFont(display(), CONFIG_NICE_MENU_FONT);
  if (m_menuFont = nil) then m_menuFont := XLoadQueryFont(display(),
					       CONFIG_NASTY_FONT);
  if (m_menuFont = nil) then fatal('couldn''t load default menu font');

  values.font := m_menuFont^.fid;
  m_menuGC := XCreateGC
      (display(), root(), GCForeground or GCBackground or
       GCFunction or GCLineWidth or GCSubwindowMode or GCFont, @values);
end;


function TFPWMWindowManager.allocateColour(name, desc: string): culong;
var
  nearest, ideal: TXColor;
  error: array[0..99] of Char; //[100];
begin
  {$ifdef FPWM_TRACE}
//  FPWMTrace('[TFPWMWindowManager.allocateColour]');
  {$endif}

  if (XAllocNamedColor
    (display(), DefaultColormap(display(), m_screenNumber), PChar(name),
    @nearest, @ideal) = 0) then
  begin
    error := Format('couldn''t load %s colour', [desc]);
    fatal(error);
  end
  else Result := nearest.pixel;
end;


procedure TFPWMWindowManager.installCursor(c: RootCursor);
begin
  {$ifdef FPWM_TRACE}
  FPWMTrace('[TFPWMWindowManager.installCursor]');
  {$endif}

  installCursorOnWindow(c, m_root);
end;


procedure TFPWMWindowManager.installCursorOnWindow(c: RootCursor; w: TWindow);
var
  attr: TXSetWindowAttributes;
begin
  {$ifdef FPWM_TRACE}
  FPWMTrace('[TFPWMWindowManager.installCursorOnWindow]');
  {$endif}

  case c of
    DeleteCursor:    attr.cursor := m_xCursor;
    DownCursor:      attr.cursor := m_vCursor;
    RightCursor:     attr.cursor := m_hCursor;
    DownrightCursor: attr.cursor := m_vhCursor;
    NormalCursor:    attr.cursor := m_cursor;
  end;

  XChangeWindowAttributes(m_display, w, CWCursor, @attr);
end;


function TFPWMWindowManager.timestamp(reset: Boolean): x.TTime;
var
  event: TXEvent;
  str: array[0..0] of Char;
begin
  {$ifdef FPWM_TRACE}
  FPWMTrace('[TFPWMWindowManager.timestamp]');
  {$endif}

  if (reset) then m_currentTime := CurrentTime;

  if (m_currentTime = CurrentTime) then
  begin
    str[0] := #0;
    XChangeProperty(m_display, m_root, Atoms.fpwm_running,
      Atoms.fpwm_running, 8, PropModeAppend, @str[0], 0);
    XMaskEvent(m_display, PropertyChangeMask, @event);

    m_currentTime := event.xproperty.time;
  end;

  Result := m_currentTime;
end;

class procedure TFPWMWindowManager.sigHandler();
begin
  {$ifdef FPWM_TRACE}
  FPWMTrace('[TFPWMWindowManager.sigHandler]');
  {$endif}

  m_signalled := -1; //True;
end;

procedure TFPWMWindowManager.scanInitialWindows();
var
  i, n: cuint;
  w1, w2: TWindow;
  wins: PWindow;
  attr: TXWindowAttributes;
  str: PChar;
begin
  {$ifdef FPWM_TRACE}
  FPWMTrace('[TFPWMWindowManager.scanInitialWindows] BEGIN');
  {$endif}

  XQueryTree(m_display, m_root, @w1, @w2, @wins, @n);

  {$ifdef FPWM_TRACE}
  FPWMTrace(Format('[TFPWMWindowManager.scanInitialWindows] n=%d', [n]));
  {$endif}

  for i := 0 to n - 1 do
  begin
      XGetWindowAttributes(m_display, wins[i], @attr);

      // Doesn't scan our own windows and windows marked with override_redirect
      {$ifdef FPWM_TRACE}
      if XFetchName(m_display, wins[i], @Str) = 0 then Str := '';

      FPWMTrace(Format('[TFPWMWindowManager.scanInitialWindows] i=%d attr.override_redirect=%d wins[i]=%d name=%s',
        [i, attr.override_redirect, wins[i], str]));

      //XFree(Str);
      {$endif}
      if (attr.override_redirect <> 0) then Continue;
      if (wins[i] = m_menuWindow) then Continue;

      {(void)}windowToClient(wins[i], True);
  end;

  //XFree(wins);

  {$ifdef FPWM_TRACE}
  FPWMTrace('[TFPWMWindowManager.scanInitialWindows] END');
  {$endif}
end;

function TFPWMWindowManager.windowToClient(w: TWindow; acreate: Boolean): TFPWMClient;
var
  i: Integer;
begin
  {$ifdef FPWM_TRACE}
  FPWMTrace('[TFPWMWindowManager.windowToClient] BEGIN');
  {$endif}

  if (w = 0) then
  begin
    {$ifdef FPWM_TRACE}
    FPWMTrace('[TFPWMWindowManager.windowToClient] END (w = 0)');
    {$endif}
    Exit(nil);
  end;

  for i := m_clients.count-1 downto 0 do
  begin
    if (m_clients.items[i].hasWindow(w)) then
    begin
      {$ifdef FPWM_TRACE}
      FPWMTrace('[TFPWMWindowManager.windowToClient] END (m_clients.items[i].hasWindow(w))');
      {$endif}
      Exit(m_clients.items[i]);
    end;
  end;

  if (not acreate) then
  begin
    {$ifdef FPWM_TRACE}
    FPWMTrace('[TFPWMWindowManager.windowToClient] END (not acreate)');
    {$endif}
    Exit(nil)
  end
  else
  begin
    Result := TFPWMClient.Create(Self, w);
    m_clients.add(Result);
  end;
  {$ifdef FPWM_TRACE}
  FPWMTrace('[TFPWMWindowManager.windowToClient] END');
  {$endif}
end;

procedure TFPWMWindowManager.installColormap(cmap: TColormap);
begin
  {$ifdef FPWM_TRACE}
  FPWMTrace('[TFPWMWindowManager.installColormap]');
  {$endif}

  if (cmap = None) then
    XInstallColormap(m_display, m_defaultColormap)
  else
    XInstallColormap(m_display, cmap);
end;

var
  w: TWindow = 0; // for TFPWMWindowManager.clearFocus

procedure TFPWMWindowManager.clearFocus();
var
  active: TFPWMClient;
  attr: TXSetWindowAttributes;
  c: TFPWMClient;
  mask: cint;
begin
  {$ifdef FPWM_TRACE}
  FPWMTrace('[TFPWMWindowManager.clearFocus]');
  {$endif}

  active := activeClient();

    if CONFIG_AUTO_RAISE or (not CONFIG_CLICK_TO_FOCUS) then
    begin
	setActiveClient(nil);
	Exit;
    end;

    if (active <> nil) then
    begin
	setActiveClient(nil);
	active.deactivate();

        c := active.revertTo();
	while c <> nil do
        begin
	    if (c.isNormal()) then
            begin
		c.activate();
		Exit;
	    end;

            c := c.revertTo();
        end;

	installColormap(None);
    end;

    if (w = 0) then
    begin
	mask := CWOverrideRedirect;
	attr.override_redirect := 1;

	w := XCreateWindow(display(), root(), 0, 0, 1, 1, 0,
			  CopyFromParent, InputOnly, PVisual(CopyFromParent),
			  mask, @attr);

	XMapWindow(display(), w);
    end;

    XSetInputFocus(display(), w, RevertToPointerRoot, timestamp(False));
end;


procedure TFPWMWindowManager.skipInRevert(c, myRevert: TFPWMClient);
var
  i: Integer;
begin
  {$ifdef FPWM_TRACE}
  FPWMTrace('[TFPWMWindowManager.skipInRevert]');
  {$endif}

  for i := 0 to  m_clients.count - 1 do
  begin
	if (m_clients.items[i] <> c) and
	    (m_clients.items[i].revertTo() = c) then
	    m_clients.items[i].setRevertTo(myRevert);
  end;
end;


procedure TFPWMWindowManager.addToHiddenList(c: TFPWMClient);
var
  i: Integer;
begin
  {$ifdef FPWM_TRACE}
  FPWMTrace('[TFPWMWindowManager.addToHiddenList]');
  {$endif}

  for i := 0 to  m_hiddenClients.count - 1 do
  begin
    if (m_hiddenClients.items[i] = c) then Exit;
  end;

  m_hiddenClients.add(c);
end;


procedure TFPWMWindowManager.removeFromHiddenList(c: TFPWMClient);
var
  i: Integer;
begin
  {$ifdef FPWM_TRACE}
  FPWMTrace('[TFPWMWindowManager.removeFromHiddenList]');
  {$endif}
  for i := 0 to  m_hiddenClients.count - 1 do
  begin
    if (m_hiddenClients.items[i] = c) then
    begin
      m_hiddenClients.delete(i);
      Exit;
    end;
  end;
end;

function TFPWMWindowManager.raiseTransients(c: TFPWMClient): Boolean;
var
  i: Integer;
  first: TFPWMClient = nil;
begin
  {$ifdef FPWM_TRACE}
  FPWMTrace('[TFPWMWindowManager.raiseTransients]');
  {$endif}
    if not c.isNormal() then Exit(False);

    for i := 0 to m_clients.count - 1 do
    begin
	if (m_clients.items[i].isNormal()) and
	    (m_clients.items[i].isTransient()) then
        begin
	    if (c.hasWindow(m_clients.items[i].transientFor())) then
            begin
		if first = nil then first := m_clients.items[i]
		else m_clients.items[i].mapRaised();
	    end;
	end;
    end;

    if first <> nil then
    begin
	first.mapRaised();
	Exit(True);
    end
    else
      Exit(False);
end;

//#ifdef sgi
//extern "C" {
//extern int putenv(char *);	/* not POSIX */
//}
//#endif

{@@
  This function will launch a new terminal window for the user.
  It is accessed from the desktop menu
}
procedure TFPWMWindowManager.spawn();
var
  displayName, pstring: string;
  params: array of string;
begin
  {$ifdef FPWM_TRACE}
  FPWMTrace('[TFPWMWindowManager.spawn]');
  {$endif}
  // strange code thieved from 9wm to avoid leaving zombies

  displayName := DisplayString(m_display);

  if (fpfork() = 0) then
  begin
    if (fpfork() = 0) then
    begin
      fpclose(ConnectionNumber(m_display));

      // if you don't have putenv, miss out this next
      // conditional and its contents

{     if (displayName <> '') then
      begin
	  //pstring = (char *)malloc(strlen(displayName) + 10);
	  pstring := Format('DISPLAY=%s', [displayName]);
	  fpputenv(pstring);
      end;}

      if (CONFIG_EXEC_USING_SHELL) then
      begin
          SetLength(params, 3);
          params[0] := m_shell;
          params[1] := '-c';
          params[2] := CONFIG_NEW_WINDOW_COMMAND;
	  fpexecl(m_shell, params);
	  WriteLn(Format('fpwm: exec %s failed', [m_shell]));
//		fpperror(' failed');
      end;

      SetLength(params, 1);
      params[0] := CONFIG_NEW_WINDOW_COMMAND;
      fpexeclp(CONFIG_NEW_WINDOW_COMMAND, params);
      WriteLn(Format('fpwm: exec %s failed', [CONFIG_NEW_WINDOW_COMMAND]));
//    fprintf(stderr, "wm2: exec %s", CONFIG_NEW_WINDOW_COMMAND);
//    perror(" failed");

      SetLength(params, 2);
      params[0] := 'xterm';
      params[1] := '-ut';
      fpexeclp('xterm', params);
      WriteLn('fpwm: exec xterm failed');
//      perror("wm2: exec xterm failed");
      fpexit(1);
    end;
    fpexit(0);
  end;
  Sleep(0);
//  fpwait(0);
end;

