/* This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

/****************************************************************************
 *
 * This is all original code by Robert Nation
 * which reads motif mwm window manager
 * hints from a window, and makes necessary adjustments for fvwm.
 *
 * Definitions of the hint structure and the constants are courtesy of
 * mitnits@bgumail.bgu.ac.il (Roman Mitnitski ), who sent this note,
 * after conferring with a friend at the OSF:
 * Hi, Rob.
 *
 *I'm happy to announce, that you can use motif public
 *headers in any way you can... I just got the letter from
 *my friend, it says literally:
 *
 *>    Hi.
 *>
 *> Yes, you can use motif public header files, in particular
 *> because there is NO limitation on inclusion of this files
 *> in your programms....Also, no one can put copyright to the NUMBERS
 *> (I mean binary flags for decorations) or DATA STRUCTURES
 *> (I mean little structure used by motif to pass description
 *> of the decorations to the mwm). Call it another name, if you are
 *> THAT MUCH concerned.
 *>
 *> You can even use the little piece of code I've passed to you -
 *> we are talking about 10M distribution against two pages of code.
 *> Don't be silly.
 *>
 *> Best wishes.
 *> Eli.
 *
 *
 ****************************************************************************/

#include "config.h"

#include <stdio.h>

#include "libs/fvwmlib.h"
#include "libs/FShape.h"
#include "fvwm.h"
#include "externs.h"
#include "cursor.h"
#include "functions.h"
#include "bindings.h"
#include "misc.h"
#include "screen.h"
#include "decorations.h"
#include <X11/Xproto.h>
#include "lang-strings.h"
#include "update.h"
#include "style.h"


extern Atom _XA_MwmAtom;

/* Motif  window hints */
typedef struct
{
  long flags;
  long functions;
  long decorations;
  long inputMode;
} PropMotifWmHints;

typedef PropMotifWmHints        PropMwmHints;

/* Motif window hints */
#define MWM_HINTS_FUNCTIONS           (1L << 0)
#define MWM_HINTS_DECORATIONS         (1L << 1)

/* bit definitions for MwmHints.functions */
#define MWM_FUNC_ALL            (1L << 0)
#define MWM_FUNC_RESIZE         (1L << 1)
#define MWM_FUNC_MOVE           (1L << 2)
#define MWM_FUNC_MINIMIZE       (1L << 3)
#define MWM_FUNC_MAXIMIZE       (1L << 4)
#define MWM_FUNC_CLOSE          (1L << 5)

/* bit definitions for MwmHints.decorations */
#if 0
#define MWM_DECOR_ALL                 (1L << 0)
#define MWM_DECOR_BORDER              (1L << 1)
#define MWM_DECOR_RESIZEH             (1L << 2)
#define MWM_DECOR_TITLE               (1L << 3)
#define MWM_DECOR_MENU                (1L << 4)
#define MWM_DECOR_MINIMIZE            (1L << 5)
#define MWM_DECOR_MAXIMIZE            (1L << 6)
#endif

#define PROP_MOTIF_WM_HINTS_ELEMENTS  4
#define PROP_MWM_HINTS_ELEMENTS       PROP_MOTIF_WM_HINTS_ELEMENTS

/* bit definitions for OL hints; I just
 *  made these up, OL stores hints as atoms */
#define OL_DECOR_CLOSE                (1L << 0)
#define OL_DECOR_RESIZEH              (1L << 1)
#define OL_DECOR_HEADER               (1L << 2)
#define OL_DECOR_ICON_NAME            (1L << 3)
#define OL_DECOR_ALL                  (OL_DECOR_CLOSE | OL_DECOR_RESIZEH | OL_DECOR_HEADER | OL_DECOR_ICON_NAME)
/* indicates if there are any OL hints */
#define OL_ANY_HINTS                  (1L << 7)

extern FvwmWindow *Tmp_win;

/****************************************************************************
 *
 * Reads the property MOTIF_WM_HINTS
 *
 *****************************************************************************/
void GetMwmHints(FvwmWindow *t)
{
  int actual_format;
  Atom actual_type;
  unsigned long nitems, bytesafter;

  if (t->mwm_hints)
  {
    XFree((char *)t->mwm_hints);
    t->mwm_hints = NULL;
  }
  if(XGetWindowProperty (dpy, t->w, _XA_MwmAtom, 0L, 20L, False,
			 _XA_MwmAtom, &actual_type, &actual_format, &nitems,
			 &bytesafter,(unsigned char **)&t->mwm_hints)==Success)
  {
    if(nitems >= PROP_MOTIF_WM_HINTS_ELEMENTS)
    {
      return;
    }
  }

  t->mwm_hints = NULL;
}

/****************************************************************************
 *
 * Reads the openlook properties _OL_WIN_ATTR, _OL_DECOR_ADD, _OL_DECOR_DEL
 *
 * _OL_WIN_ATTR - the win_type field is the either the first atom if the
 * property has length three or the second atom if the property has
 * length five.  It can be any of:
 *     _OL_WT_BASE (no changes)
 *     _OL_WT_CMD (no minimize decoration)
 *     _OL_WT_HELP (no minimize, maximize or resize handle decorations)
 *     _OL_WT_NOTICE (no minimize, maximize, system menu, resize handle
 *         or titlebar decorations)
 *     _OL_WT_OTHER (no minimize, maximize, system menu, resize handle
 *         or titlebar decorations)
 * In addition, if the _OL_WIN_ATTR property is in the three atom format
 * or if the type is _OL_WT_OTHER, then the icon name is not displayed
 * (same behavior as olvwm).
 *
 * _OL_DECOR_ADD or _OL_DECOR_DEL - indivdually add or remove minimize
 * button (_OL_DECOR_CLOSE), resize handles (_OL_DECOR_RESIZE), title bar
 * (_OL_DECOR_HEADER), or icon name (_OL_DECOR_ICON_NAME).
 *
 * The documentation for the Open Look hints was taken from "Advanced X
 * Window Application Programming", Eric F. Johnson and Kevin Reichard
 * (M&T Books), and the olvwm source code (available at ftp.x.org in
 * /R5contrib).
 *****************************************************************************/
void GetOlHints(FvwmWindow *t)
{
  int actual_format;
  Atom actual_type;
  unsigned long nitems, bytesafter;
  Atom *hints;
  int i;
  Atom win_type;

  t->ol_hints = OL_DECOR_ALL;

  if (XGetWindowProperty (dpy, t->w, _XA_OL_WIN_ATTR, 0L, 20L, False,
                          _XA_OL_WIN_ATTR, &actual_type, &actual_format,
			  &nitems, &bytesafter,
			  (unsigned char **)&hints)==Success)
  {
    if (nitems > 0)
    {
      t->ol_hints |= OL_ANY_HINTS;

      if (nitems == 3)
	win_type = hints[0];
      else
	win_type = hints[1];

      /* got this from olvwm and sort of mapped it to
       * FVWM/MWM hints */
      if (win_type == _XA_OL_WT_BASE)
	t->ol_hints = OL_DECOR_ALL;
      else if (win_type == _XA_OL_WT_CMD)
	t->ol_hints = OL_DECOR_ALL & ~OL_DECOR_CLOSE;
      else if (win_type == _XA_OL_WT_HELP)
	t->ol_hints = OL_DECOR_ALL & ~(OL_DECOR_CLOSE | OL_DECOR_RESIZEH);
      else if (win_type == _XA_OL_WT_NOTICE)
	t->ol_hints = OL_DECOR_ALL & ~(OL_DECOR_CLOSE | OL_DECOR_RESIZEH |
				       OL_DECOR_HEADER | OL_DECOR_ICON_NAME);
      else if (win_type == _XA_OL_WT_OTHER)
	t->ol_hints = 0;
      else
	t->ol_hints = OL_DECOR_ALL;

      if (nitems == 3)
	t->ol_hints &= ~OL_DECOR_ICON_NAME;
    }

    if (hints)
      XFree (hints);
  }

  if(XGetWindowProperty (dpy, t->w, _XA_OL_DECOR_ADD, 0L, 20L, False,
			 XA_ATOM, &actual_type, &actual_format, &nitems,
			 &bytesafter,(unsigned char **)&hints)==Success)
  {
    for (i = 0; i < nitems; i++)
    {
      t->ol_hints |= OL_ANY_HINTS;
      if (hints[i] == _XA_OL_DECOR_CLOSE)
	t->ol_hints |= OL_DECOR_CLOSE;
      else if (hints[i] == _XA_OL_DECOR_RESIZE)
	t->ol_hints |= OL_DECOR_RESIZEH;
      else if (hints[i] == _XA_OL_DECOR_HEADER)
	t->ol_hints |= OL_DECOR_HEADER;
      else if (hints[i] == _XA_OL_DECOR_ICON_NAME)
	t->ol_hints |= OL_DECOR_ICON_NAME;
    }
    if (hints)
      XFree (hints);
  }

  if(XGetWindowProperty (dpy, t->w, _XA_OL_DECOR_DEL, 0L, 20L, False,
			 XA_ATOM, &actual_type, &actual_format, &nitems,
			 &bytesafter,(unsigned char **)&hints)==Success)
  {
    for (i = 0; i < nitems; i++)
    {
      t->ol_hints |= OL_ANY_HINTS;
      if (hints[i] == _XA_OL_DECOR_CLOSE)
	t->ol_hints &= ~OL_DECOR_CLOSE;
      else if (hints[i] == _XA_OL_DECOR_RESIZE)
	t->ol_hints &= ~OL_DECOR_RESIZEH;
      else if (hints[i] == _XA_OL_DECOR_HEADER)
	t->ol_hints &= ~OL_DECOR_HEADER;
      else if (hints[i] == _XA_OL_DECOR_ICON_NAME)
	t->ol_hints &= ~OL_DECOR_ICON_NAME;
    }
    if (hints)
      XFree (hints);
  }
}


/****************************************************************************
 *
 * Interprets the property MOTIF_WM_HINTS, sets decoration and functions
 * accordingly
 *
 *****************************************************************************/
void SelectDecor(FvwmWindow *t, window_style *pstyle, short *buttons)
{
  int decor;
  int i;
  short border_width;
  short handle_width;
  PropMwmHints *prop;
  style_flags *sflags = &(pstyle->flags);

  border_width = (SHAS_BORDER_WIDTH(sflags)) ?
    SGET_BORDER_WIDTH(*pstyle) : DEFAULT_BORDER_WIDTH;
  if (border_width > MAX_BORDER_WIDTH)
    border_width = MAX_BORDER_WIDTH;
  handle_width = (SHAS_HANDLE_WIDTH(sflags)) ?
    SGET_HANDLE_WIDTH(*pstyle) : DEFAULT_HANDLE_WIDTH;
  if (handle_width > MAX_HANDLE_WIDTH)
    handle_width = MAX_HANDLE_WIDTH;

  *buttons = (1 << NUMBER_OF_BUTTONS) - 1;

  decor = MWM_DECOR_ALL;
  t->functions = MWM_FUNC_ALL;
  if(t->mwm_hints)
  {
    prop = (PropMwmHints *)t->mwm_hints;
    if(SHAS_MWM_DECOR(sflags))
    {
      if(prop->flags & MWM_HINTS_DECORATIONS)
      {
	decor = 0;
	if (prop->decorations & 0x1)
	  decor |= MWM_DECOR_ALL;
	if (prop->decorations & 0x2)
	  decor |= MWM_DECOR_BORDER;
	if (prop->decorations & 0x4)
	  decor |= MWM_DECOR_RESIZEH;
	if (prop->decorations & 0x8)
	  decor |= MWM_DECOR_TITLE;
	if (prop->decorations & 0x10)
	  decor |= MWM_DECOR_MENU;
	if (prop->decorations & 0x20)
	  decor |= MWM_DECOR_MINIMIZE;
	if (prop->decorations & 0x40)
	  decor |= MWM_DECOR_MAXIMIZE;
      }
    }
    if(SHAS_MWM_FUNCTIONS(sflags))
    {
      if(prop->flags & MWM_HINTS_FUNCTIONS)
	t->functions = prop->functions;
    }
  }

  /* functions affect the decorations! if the user says
   * no iconify function, then the iconify button doesn't show
   * up. */
  if(t->functions & MWM_FUNC_ALL)
  {
    /* If we get ALL + some other things, that means to use
     * ALL except the other things... */
    t->functions &= ~MWM_FUNC_ALL;
    t->functions = (MWM_FUNC_RESIZE | MWM_FUNC_MOVE | MWM_FUNC_MINIMIZE |
		    MWM_FUNC_MAXIMIZE | MWM_FUNC_CLOSE) & (~(t->functions));
  }
  if(SHAS_MWM_FUNCTIONS(sflags) && IS_TRANSIENT(t))
  {
    t->functions &= ~(MWM_FUNC_MAXIMIZE|MWM_FUNC_MINIMIZE);
  }

  if(decor & MWM_DECOR_ALL)
  {
    /* If we get ALL + some other things, that means to use
     * ALL except the other things... */
    decor &= ~MWM_DECOR_ALL;
    decor = MWM_DECOR_EVERYTHING & (~decor);
  }

  /* now add/remove any functions specified in the OL hints */
  if (SHAS_OL_DECOR(sflags) && (t->ol_hints & OL_ANY_HINTS))
  {
    if (t->ol_hints & OL_DECOR_CLOSE)
    {
      t->functions |= MWM_FUNC_MINIMIZE;
      decor        |= MWM_FUNC_MINIMIZE;
    }
    else
    {
      t->functions &= ~MWM_FUNC_MINIMIZE;
      decor        &= ~MWM_FUNC_MINIMIZE;
    }
    if (t->ol_hints & OL_DECOR_RESIZEH)
    {
      t->functions |= (MWM_FUNC_RESIZE | MWM_FUNC_MAXIMIZE);
      decor        |= (MWM_FUNC_RESIZE | MWM_FUNC_MAXIMIZE);
    }
    else
    {
      t->functions &= ~(MWM_FUNC_RESIZE | MWM_FUNC_MAXIMIZE);
      decor        &= ~(MWM_FUNC_RESIZE | MWM_FUNC_MAXIMIZE);
    }
    if (t->ol_hints & OL_DECOR_HEADER)
    {
      t->functions |= (MWM_DECOR_MENU | MWM_FUNC_MINIMIZE |
		       MWM_FUNC_MAXIMIZE | MWM_DECOR_TITLE);
      decor        |= (MWM_DECOR_MENU | MWM_FUNC_MINIMIZE |
		       MWM_FUNC_MAXIMIZE | MWM_DECOR_TITLE);
    }
    else
    {
      t->functions &= ~(MWM_DECOR_MENU | MWM_FUNC_MINIMIZE |
			MWM_FUNC_MAXIMIZE | MWM_DECOR_TITLE);
      decor        &= ~(MWM_DECOR_MENU | MWM_FUNC_MINIMIZE |
			MWM_FUNC_MAXIMIZE | MWM_DECOR_TITLE);
    }
    if (t->ol_hints & OL_DECOR_ICON_NAME)
    {
      SET_HAS_NO_ICON_TITLE(t, 0);
    }
    else
    {
      SET_HAS_NO_ICON_TITLE(t, 1);
    }
  }

  /* Now I have the un-altered decor and functions, but with the
   * ALL attribute cleared and interpreted. I need to modify the
   * decorations that are affected by the functions */
  if(!(t->functions & MWM_FUNC_RESIZE))
    decor &= ~MWM_DECOR_RESIZEH;
  /* MWM_FUNC_MOVE has no impact on decorations. */
  if(!(t->functions & MWM_FUNC_MINIMIZE))
    decor &= ~MWM_DECOR_MINIMIZE;
  if(!(t->functions & MWM_FUNC_MAXIMIZE))
    decor &= ~MWM_DECOR_MAXIMIZE;
  /* MWM_FUNC_CLOSE has no impact on decorations. */

  /* This rule is implicit, but its easier to deal with if
   * I take care of it now */
  if(decor & (MWM_DECOR_MENU| MWM_DECOR_MINIMIZE | MWM_DECOR_MAXIMIZE))
    decor |= MWM_DECOR_TITLE;

  /* Selected the mwm-decor field, now trim down, based on
   * .fvwmrc entries */
  if (SHAS_NO_TITLE(sflags) ||
      (!SDO_DECORATE_TRANSIENT(sflags) && IS_TRANSIENT(t)))
    decor &= ~MWM_DECOR_TITLE;

  if (SHAS_NO_BORDER(sflags) ||
      (!SDO_DECORATE_TRANSIENT(sflags) && IS_TRANSIENT(t)))
    decor &= ~MWM_DECOR_RESIZEH;

  if(SHAS_MWM_DECOR(sflags) && IS_TRANSIENT(t))
  {
    decor &= ~(MWM_DECOR_MAXIMIZE|MWM_DECOR_MINIMIZE);
  }

  if (FShapesSupported)
  {
    if(t->wShaped)
      decor &= ~(MWM_DECOR_BORDER|MWM_DECOR_RESIZEH);
  }
  /* Assume no decorations, and build up */
  SET_HAS_TITLE(t, 0);
  SET_HAS_BORDER(t, 0);

  t->boundary_width = 0;

  if(decor & MWM_DECOR_BORDER)
  {
    /* A narrow border is displayed (5 pixels - 2 relief, 1 top,
     * (2 shadow) */
    t->boundary_width = border_width;
  }
  if(decor & MWM_DECOR_TITLE)
  {
    /*  A title bar with no buttons in it
     * window gets a 1 pixel wide black border. */
    SET_HAS_TITLE(t, 1);
  }
  if(decor & MWM_DECOR_RESIZEH)
  {
    /* A wide border, with corner tiles is desplayed
     * (10 pixels - 2 relief, 2 shadow) */
    SET_HAS_BORDER(t, 1);
    t->boundary_width = handle_width;
  }
  if(!(decor & MWM_DECOR_MENU))
  {
    /*  title-bar menu button omitted
     * window gets 1 pixel wide black border */
    /* disable any buttons with the MWMDecorMenu flag */
    int i;
    for (i = 0; i < NUMBER_OF_BUTTONS; ++i)
    {
      if (TB_HAS_MWM_DECOR_MENU(GetDecor(t, buttons[i])))
      {
	*buttons &= ~(1 << i);
      }
    }
  }
  if(!(decor & MWM_DECOR_MINIMIZE))
  {
    /* title-bar + iconify button, no menu button.
     * window gets 1 pixel wide black border */
    /* disable any buttons with the MWMDecorMinimize/MWMDecorShaded flag */
    int i;
    for (i = 0; i < NUMBER_OF_BUTTONS; ++i)
    {
      if (TB_HAS_MWM_DECOR_MINIMIZE(GetDecor(t, buttons[i])))
      {
	*buttons &= ~(1 << i);
      }
    }
  }
  if(!(decor & MWM_DECOR_MAXIMIZE))
  {
    /* title-bar + maximize button, no menu button, no iconify.
     * window has 1 pixel wide black border */
    /* disable any buttons with the MWMDecorMaximize flag */
    int i;
    for (i = 0; i < NUMBER_OF_BUTTONS; ++i)
    {
      if (TB_HAS_MWM_DECOR_MAXIMIZE(GetDecor(t, buttons[i])))
      {
	*buttons &= ~(1 << i);
      }
    }
  }
  for (i = (1 << (NUMBER_OF_BUTTONS - 1)); i; i >>= 1)
  {
    if (t->buttons & i)
      *buttons &= ~i;
  }

  t->nr_left_buttons = Scr.nr_left_buttons;
  t->nr_right_buttons = Scr.nr_right_buttons;

  for (i = 0; i / 2 < Scr.nr_left_buttons; i += 2)
  {
    if((*buttons & (1 << i)) == 0)
      t->nr_left_buttons--;
  }
  for (i = 1; i / 2 < Scr.nr_right_buttons; i += 2)
  {
    if((*buttons & (1 << i)) == 0)
      t->nr_right_buttons--;
  }

  if(t->boundary_width <= 0)
  {
    t->boundary_width = 0;
    SET_HAS_BORDER(t, 0);
  }
}

/*
** seemed kind of silly to have check_allowed_function and
** check_allowed_function2 partially overlapping in their checks, so I
** combined them here and made them wrapper functions instead.
*/
int check_if_function_allowed(int function, FvwmWindow *t,
			      Bool override_allowed, char *menu_string)
{
  if (!t)
    return 1;

  if (override_allowed && HAS_MWM_OVERRIDE_HINTS(t))
    return 1;

  switch(function)
  {
    case F_DELETE:
      if (!WM_DELETES_WINDOW(t))
	return 0;
      /* fall through to close clause */
    case F_CLOSE:
      if (!(t->functions & MWM_FUNC_CLOSE))
	return 0;
      break;
    case F_DESTROY: /* shouldn't destroy always be allowed??? */
      if (!(t->functions & MWM_FUNC_CLOSE))
	return 0;
      break;
    case F_RESIZE:
      if (!(t->functions & MWM_FUNC_RESIZE))
	return 0;
      break;
    case F_ICONIFY:
      if ((!IS_ICONIFIED(t))&&
	  (!(t->functions & MWM_FUNC_MINIMIZE)))
	return 0;
      break;
    case F_MAXIMIZE:
      if (!(t->functions & MWM_FUNC_MAXIMIZE))
	return 0;
      break;
    case F_MOVE:
      if (!(t->functions & MWM_FUNC_MOVE))
	return 0;
      break;
    case F_FUNCTION:
      /* Hard part! What to do now? */
      /* Hate to do it, but for lack of a better idea,
       * check based on the menu entry name */
      /* Complex functions are a little tricky, ignore them if no menu item*/
      if (menu_string)
      {
	if((!(t->functions & MWM_FUNC_MOVE))&&
	   (StrEquals(menu_string,MOVE_STRING)))
	  return 0;
	if((!(t->functions & MWM_FUNC_RESIZE))&&
	   (StrEquals(menu_string,RESIZE_STRING1)))
	  return 0;
	if((!(t->functions & MWM_FUNC_RESIZE))&&
	   (StrEquals(menu_string,RESIZE_STRING2)))
	  return 0;
	if((!(t->functions & MWM_FUNC_MINIMIZE))&&
	   (!IS_ICONIFIED(t))&&
	   (StrEquals(menu_string,MINIMIZE_STRING)))
	  return 0;
	if((!(t->functions & MWM_FUNC_MINIMIZE))&&
	   (StrEquals(menu_string,MINIMIZE_STRING2)))
	  return 0;
	if((!(t->functions & MWM_FUNC_MAXIMIZE))&&
	   (StrEquals(menu_string,MAXIMIZE_STRING)))
	  return 0;
	if((!(t->functions & MWM_FUNC_CLOSE))&&
	   (StrEquals(menu_string,CLOSE_STRING1)))
	  return 0;
	if((!(t->functions & MWM_FUNC_CLOSE))&&
	   (StrEquals(menu_string,CLOSE_STRING2)))
	  return 0;
	if((!(t->functions & MWM_FUNC_CLOSE))&&
	   (StrEquals(menu_string,CLOSE_STRING3)))
	  return 0;
	if((!(t->functions & MWM_FUNC_CLOSE))&&
	   (StrEquals(menu_string,CLOSE_STRING4)))
	  return 0;
      }
      break;
    default:
      break;
  } /* end of switch */
  /* if we fell through, just return a 1 */
  return 1;
}


syntax highlighted by Code2HTML, v. 0.9.1